#[MEGA THREAD] Get Your Code Reviewed or Review Others
1 messages · Page 12 of 1
đ„ł!!!
Hello, does it seems optimized ? I check the ItemDisplay entities diff between old tick and actual tick and trigger a method while specifying the removed ItemDisplay```java
final LinkedList<ItemDisplay>[] entitiesMap = new LinkedList[]{new LinkedList<>()};
getScheduler().runTaskTimer(() -> {
LinkedList<ItemDisplay> actualEntitiesMap = new LinkedList<>();
for (World world : Bukkit.getWorlds()) {
actualEntitiesMap.addAll(world.getEntitiesByClass(ItemDisplay.class));
}
if(entitiesMap[0] == null) {
entitiesMap[0] = actualEntitiesMap;
}
entitiesMap[0].removeAll(actualEntitiesMap);
for (ItemDisplay itemDisplay : entitiesMap[0]) {
CustomBlockSuppressionListener.onCustomBlockDeath(itemDisplay);
}
entitiesMap[0] = actualEntitiesMap;
}, 1L, 1L);```
So what is the goal of that code
I missed the covo earlier
Why do you need to remove them like that
make an equivalent of EntityRemoveEvent for before 1.20.4, and just for Item displays
because I want to make custom blocks, I've got an internal list acting like cache instead of fetching every ItemDisplay and checking their PDC everytime
Are you planning on tracking all displays or just your own
Then use weak references and a removeIf
weak references instead of this ugly [0] ?
wdym by using removeIf ?
okay
I see what the method does
You can check isValid
but I don't see how can I use it with two lists
If it isn't valid anymore it should be removed
it doesn't exist sadge
You need to make one from a map
I see
okay thanks
hum
final Set<ItemDisplay> entitiesMap = Collections.newSetFromMap(
new WeakHashMap<>()
);
getScheduler().runTaskTimer(() -> {
Set<ItemDisplay> actualEntitiesMap = new HashSet<>();
for (World world : Bukkit.getWorlds()) {
actualEntitiesMap.addAll(world.getEntitiesByClass(ItemDisplay.class));
}
// If there
if(entitiesMap == null) {
entitiesMap = actualEntitiesMap;
}
// Do the diff on the old tick ItemDisplay entities
entitiesMap.removeAll(actualEntitiesMap);
// Trigger the method for every removed ItemDisplay
for (ItemDisplay itemDisplay : entitiesMap) {
CustomBlockSuppressionListener.onCustomBlockDeath(itemDisplay);
}
// Replace with the "new" old tick ItemDisplay entities
entitiesMap = actualEntitiesMap;
}, 1L, 1L);```
I can't assign entitiesMap
because it's in a lambda
Which part?
the fact that I don't need to assign entitiesMap and that I can get rid of most of the code
Gn
my system regarding player visibility to be world dependent
public interface Visibility {
default VisibilitySetter getVisibilitySetter() {
return new VisibilitySetter(this);
}
Collection<UUID> visiblePlayers();
}```
public class VisibilitySetter {
private final Visibility visibility;
public VisibilitySetter(Visibility visibility) {
this.visibility = visibility;
}
public void setVisible(Player player) {
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
boolean isInWorld = visibility.visiblePlayers().contains(onlinePlayer.getUniqueId());
if (isInWorld) {
// Show the player who joined the match to everyone else in the match and vice versa
onlinePlayer.showPlayer(player);
player.showPlayer(onlinePlayer);
} else {
// Hide the player who joined the match from everyone else
onlinePlayer.hidePlayer(player);
// Hide everyone else from the player who joined the match
player.hidePlayer(onlinePlayer);
}
}
}
}```
I have a class that is stored for each minigame match I create in my server and it is tied to a world:
@Getter
public class Match implements Visibility {
private final World world;
public BridgeMatch(int id, Map map, TeamConfiguration teamConfiguration) {
String matchDirectory = MatchManager.MATCHES_DIRECTORY + File.separator + "match-" + id;
WorldCreator creator = new WorldCreator(matchDirectory);
...
world = creator.createWorld();
}
public void addPlayer(Player player) {
...
player.teleport(map.getJoinSpawnPoint().toLocation(world))
getVisibilitySetter().setVisible(player);
...
}
public void addSpectator(Player player) {
...
player.teleport(map.getJoinSpawnPoint().toLocation(world))
getVisibilitySetter().setVisible(player);
...
}
@Override
public Collection<UUID> visiblePlayers() {
// return both spectators and players
Set<UUID> allPlayers = new HashSet<>(players);
allPlayers.addAll(spectators);
return allPlayers;
}
}```
I then have a class that handles code regarding my lobby world
public class LobbyWorld implements Visibility {
public static Location SPAWN;
public void init() {
SPAWN = new Location(Bukkit.getWorld("lobby"), 0.5D, 50.0D, 0.5D);
}
public void setup(Player player, boolean serverJoin) {
...
player.teleport(SPAWN);
...
getVisibilitySetter().setVisible(player);
...
}
@Override
public Collection<UUID> visiblePlayers() {
return SPAWN.getWorld().getPlayers().stream().map(Player::getUniqueId).collect(Collectors.toSet());
}
}```
i dont really see a point in your #getVisibilitySetter method if you never override it and it doesn't seem like it'll ever need to be overridden
and there also doesn't seem to be a way to hide players once they're visible?
well actually now that i look at it again there is a way to but it seems confusing and a bit misleading
i think a better name for VisibilitySetter#setVisible would be updateVisibility
good idea
it works kinda like
when a player joins server
itll run the method in the lobby world whcih will teleport them there and then run the visibility shit
and then when a player is sent to a match itll run the addplayer method and visibility will be updated
and then if they do /lobby they will run the same lobby method and be sent to the lobby with the visibility updated
some other things i noticed:
- why are visible players even exposed in the first place? the only place you'd really need to know which players are visible is when updating player visibility in worlds
- the entire
VisibilitySetterclass doesn't make too much sense. why is this a class that theVisibilityinterface exposes? if you're planning on making more variations, make it an interface instead and don't makegetVisibilitySelectordefault - i'm not sure what the point of the
Visibilityinterface even is. it only provides which players are visible, no other functionality. it doesn't allow you to modify which players are visible or not (which shouldn't be exposed in the first place) - players who aren't visible don't seem to even get updated on other player's visibility?
- if this is a per-world thing, why not directly tie visibility to a world, rather than making implementors handle that? it doesn't ever make sense to have another player visible on another world, since they're invisible anyways
for some reason I just really didnt like doing new VisibilitySetter(this) each time I needed to use it
i would just make it a private final field
which also means you don't have to create a new instance everytime you update visibility
how would I tie visibility directly to a world
have a getter for the world the visibility is handling
then only update visibility for players in that world
so youre saying scrap the interface
honestly yeah this seems too overcomplicated
and use what a static helper method
have a static method somewhere (probably VisibilitySetter) that updates visibility
yeah
I wanted to avoid having to define who to make visible multiple times in a class
but you won't have to
but I guess I could just keep the method
you can keep all your code in those implementing classes
just pass visible players into the static helper method
- the world
public class VisibilitySetter {
public static void updateVisibility(Player player, Collection<UUID> visiblePlayers) {
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
boolean isInWorld = visiblePlayers.contains(onlinePlayer.getUniqueId());
if (isInWorld) {
// Show the player who joined the match to everyone else in the match and vice versa
onlinePlayer.showPlayer(player);
player.showPlayer(onlinePlayer);
} else {
// Hide the player who joined the match from everyone else
onlinePlayer.hidePlayer(player);
// Hide everyone else from the player who joined the match
player.hidePlayer(onlinePlayer);
}
}
}
}```
VisibilitySetter.updateVisibility(player, visiblePlayers());
yeah that's literally the only change you need to make
actually after i look at this wouldn't it make more sense as a updateVisibility(Player player, World world)
i mean it depends on what your specific use-case is
yeah but I also have
ig you would need a bool too
public Collection<UUID> visiblePlayers() {
return Bukkit.getOnlinePlayers().stream()
.filter(player -> player.getWorld().getName().startsWith(BridgeMatchManager.MATCHES_PRESET_DIRECTORY))
.map(Entity::getUniqueId).collect(Collectors.toList());
}```
this for a map editor world
where you can see everyone who is editing worlds
where it could be multiple different worlds
yeah so doing it that way wouldn't work
but you can still see them in tab even if theyre in a different world
so I think what I have now is fine
yeah
sure i could take a quick look
bit busy tho so dont expect a huge analysis or anything
of coursew
/**
* The ObjectiveWorld interface defines methods for setting up and managing scoreboard objectives for players.
* It is to be implemented by classes that represent different worlds in the game.
*/
public interface ObjectiveWorld {
/**
* Returns an instance of ObjectiveSetter associated with this ObjectiveWorld.
*
* @return a new ObjectiveSetter instance
* @example In a class that implements ObjectiveWorld:
* ObjectiveSetter objectiveSetter = getObjectiveSetter();
*/
default ObjectiveSetter getObjectiveSetter() {
return new ObjectiveSetter(this);
}
/**
* Sets up the scoreboard objectives for a player who joins this world.
*
* @param player the player for whom the objectives are being set up
* @param scoreboard the scoreboard to which the objectives are being added
*/
default void setupObjectives(Player player, Scoreboard scoreboard) {
}
/**
* Sets the scores for the specified objectives in the world based on the player's score to show.
*
* @param objectives the set of objectives for which scores are being set
* @param playerScore the player whose scores are visible on the objectives
*/
default void setObjectiveScores(Set<Objective> objectives, Player playerScore) {
}
}```
/**
* The ObjectiveSetter class is responsible for managing and setting up scoreboard objectives for players.
*/
public class ObjectiveSetter {
private static final Map<UUID, Scoreboard> scoreboards = new HashMap<>();
private final ObjectiveWorld objectiveWorld;
public ObjectiveSetter(ObjectiveWorld objectiveWorld) {
this.objectiveWorld = objectiveWorld;
}
/**
* Sets up a new scoreboard for the specified player and stores it in the scoreboards map.
* This method should be called when a player joins the server.
*
* @param player the player for whom the scoreboard is being set up
* @example When a player joins the server:
* <p>
* ObjectiveSetter.setup(player);
*/
public static void addScoreboard(Player player) {
player.setScoreboard(player.getServer().getScoreboardManager().getNewScoreboard());
scoreboards.put(player.getUniqueId(), player.getScoreboard());
}
/**
* Retrieves the scoreboard associated with the specified UUID.
*
* @param player the UUID of the player whose scoreboard is to be retrieved
* @return the scoreboard associated with the UUID
* @example Scoreboard scoreboard = ObjectiveSetter.getScoreboard(player.getUniqueId());
*/
public static Scoreboard getScoreboard(UUID player) {
return scoreboards.get(player);
}
/**
* Removes the scoreboard associated with the specified UUID.
* This method should be called when a player leaves the server.
*
* @param player the UUID of the player whose scoreboard is to be removed
* @example When a player leaves the server:
* <p>
* ObjectiveSetter.removeScoreboard(player.getUniqueId());
*/
public static void removeScoreboard(UUID player) {
scoreboards.remove(player);
}```
/**
* Sets up the objectives for the specified player by resetting old objectives and setting up new ones.
*
* @param player the player for whom the objectives are being set up
* @example In a class that implements ObjectiveWorld:
* <p>
* getObjectiveSetter().setObjectives(player);
*/
public void setObjectives(Player player) {
Scoreboard scoreboard = scoreboards.get(player.getUniqueId());
// When transferring worlds, reset all old objectives
for (Objective objective : scoreboard.getObjectives()) {
objective.unregister();
}
// And set up the new ones
objectiveWorld.setupObjectives(player, scoreboard);
}
/**
* Sets the scores on all the objectives for the specified world.
*
* @param world the world in which the scores are being set
* @example In a class that implements ObjectiveWorld:
* <p>
* World world = Bukkit.getWorld("worldName");
* getObjectiveSetter().setScores(world);
*/
public void setScores(World world) {
for (Player playerViewing : world.getPlayers()) {
for (Player playerScore : world.getPlayers()) {
objectiveWorld.setObjectiveScores(getScoreboard(playerViewing.getUniqueId()).getObjectives(), playerScore);
}
}
}
}```
Match implements ObjectiveWorld and then
startMatch, rejoinPlayer, addSpectator
getObjectiveSetter().setObjectives(player);
getObjectiveSetter().setScores(world);
LobbyWorld implements ObjectiveWorld and then
setup:
getObjectiveSetter().setObjectives(player);```
Match:
@Override
public void setupObjectives(Player player, Scoreboard scoreboard) {
Objective nametag = scoreboard.registerNewObjective("nametag-health", Criterias.HEALTH);
nametag.setDisplaySlot(DisplaySlot.BELOW_NAME);
nametag.setDisplayName(ChatColor.RED + StringEscapeUtils.unescapeJava("â€"));
Objective tab = scoreboard.getObjective("tab-health");
if (tab == null) {
tab = scoreboard.registerNewObjective("tab-health", Criterias.HEALTH);
}
tab.setDisplaySlot(DisplaySlot.PLAYER_LIST);
}
@Override
public void setObjectiveScores(Set<Objective> objectives, Player playerScore) {
for (Objective objective : objectives) {
// all objectives are regarding player health
objective.getScore(playerScore.getName()).setScore((int) playerScore.getHealth());
}
}```
but lobbyworld doesnt override anything
when are the methods in ObjectiveWorld called?
and how does it get a Scoreboard to pass into setupObjectives?
inside ObjectiveSetter,
public void setObjectives(Player player) {
Scoreboard scoreboard = scoreboards.get(player.getUniqueId());
// When transferring worlds, reset all old objectives
for (Objective objective : scoreboard.getObjectives()) {
objective.unregister();
}
// And set up the new ones
objectiveWorld.setupObjectives(player, scoreboard);
}```
```java
public void setScores(World world) {
for (Player playerViewing : world.getPlayers()) {
for (Player playerScore : world.getPlayers()) {
objectiveWorld.setObjectiveScores(getScoreboard(playerViewing.getUniqueId()).getObjectives(), playerScore);
}
}
}```
Scoreboard scoreboard = scoreboards.get(player.getUniqueId()); based on the static map
when a player joins a server it will assign the player's scoreboard to a new one and save it in the static map
when they leave the server itll remove their scoreboard from the map
wait so there isn't any outside code that calls any methods on ObjectiveWorld?
Scoreboard scoreboard = scoreboards.get(player.getUniqueId()); is the same as Scoreboard scoreboard = player.getScoreboard();
no, only called within ObjectiveSetter
used as a sort of helper class for the interface
I may have misinterpreted the real usage of interfaces
yeah interfaces should be used in cases where you want to hide implementation details
so an easy example is List<T>
right
you don't know what type of list it is, just that you can get, set, etc. on it
so again this could all just be static
which helps with being flexible, like a method that takes in a List rather than a specific type of list like ArrayList (assuming it doesn't need specific methods from ArrayList)
because you know you have that functionality and you know it will work no matter what type of list it is
so right now I'm only using it as a sort of consistency between naming across classes
this one is a bit harder because you need to update player scoreboards when they join the world
and not really anythign else
thats already a static method
public static void addScoreboard(Player player) {
player.setScoreboard(player.getServer().getScoreboardManager().getNewScoreboard());
scoreboards.put(player.getUniqueId(), player.getScoreboard());
}
infact I probably dont need that map at all
because itll just be tied to the player object as soon as I run player.setScoreboard
yeah
but you would have to update the objectives of that scoreboard when the player joins the world
except do I make it static and have a runnable as the seocnd argument?
you'd have to map world -> world manager
im confused
so my understanding rn is this
0. Player joins the server
- Player gets assigned their own scoreboard
- Player enters world
- Player's scoreboard is cleared and its objectives are set to the specific ones of that world
- Player leaves world and joins another
- Repeat step 4
correct
what im referring to is step 4, how do you know what objectives to use?
you'd have to have some sort of way to map a world to one of your world manager classes
so you can call the necessary methods
right
I mean the Match class already has a world field
and the LobbyWorld class has a static method getting its respective world which is always the same
yeah so you'd just have to check against every class
and now you can use an interface if you want, something like:
public interface ScoreboardWorld {
void updateObjectives(Player player);
}
make every world manager implement that
when a player joins a world, you get the corresponding ScoreboardWorld and call #updateObjectives
(after clearing their old scoreboard)
now the implementing classes add the necessary objectives to the player's scoreboard
I think thats what I have
public interface ObjectiveWorld {
/**
* Sets up the scoreboard objectives for a player who joins this world.
*
* @param player the player for whom the objectives are being set up
* @param scoreboard the scoreboard to which the objectives are being added
*/
default void updateObjectives(Player player, Scoreboard scoreboard) {
}
/**
* Sets the scores for the specified objectives in the world based on the player's score to show.
*
* @param objectives the set of objectives for which scores are being set
* @param playerScore the player whose scores are visible on the objectives
*/
default void updateScores(Set<Objective> objectives, Player playerScore) {
}
}```
this is my interface
right but you have way more functionality that isn't needed
theyre default for worlds that dont actually have objectives so they dont need to impl it
like the ObjectiveSetter class
yeah
so you're saying
make it static like this?
public static void setScores(ObjectiveWorld objectiveWorld, World world) {
for (Player playerViewing : world.getPlayers()) {
for (Player playerScore : world.getPlayers()) {
Scoreboard scoreboard = playerViewing.getScoreboard();
objectiveWorld.updateScores(scoreboard.getObjectives(), playerScore);
}
}
}```
remove the ObjectiveSetter class and basically combine the two methods in ObjectiveWorld
make the implementor decide how to manage the player's scoreboard
oh and remove the scoreboard arg since you can just get the scoreboard from the player
scoreboard arg was so its easier to read than just passing 2 player arguments
because scoreboard arg is easily identifiable as the scoreboard thats changing and then the player must be the score thats showing
I wanted to avoid calling
Scoreboard scoreboard = player.getScoreboard();
// When transferring worlds, reset all old objectives
for (Objective objective : scoreboard.getObjectives()) {
objective.unregister();
}```
in each class that implements it though
interesting approach
because the functionality of that method should just be "i am adding the necessary objectives and scores to that player's scoreboard"
right
I also wanted to remove having to have this for loop in every implementation
for (Player playerViewing : world.getPlayers()) {
for (Player playerScore : world.getPlayers()) {
Scoreboard scoreboard = playerViewing.getScoreboard();
objectiveWorld.updateScores(scoreboard.getObjectives(), playerScore);
}
}```
but is it just something I have to deal with
wait now im a bit confused, are you displaying the scoreboard of a different player to that player
like you're looping over each player twice
is it like just some sidebar that shows every player name or something?
im not sure if im understanding this fully
the code with basically go from:
getObjectiveSetter().setObjectives(player);
getObjectiveSetter().setScores(world);```
to
objectiveWorld.updateObjectives(player);
for (Player playerViewing : world.getPlayers()) {
for (Player playerScore : world.getPlayers()) {
Scoreboard scoreboard = playerViewing.getScoreboard();
objectiveWorld.updateScores(scoreboard.getObjectives(), playerScore);
}
}```
basically
each player will have their own scoreboard
you should only be looping through each player once
right?
update the scoreboard of every player in that world
when a player joins a match and then that match stats their scoreboard will register nametag-health and tab-health and assign their display slots
to account for the issue where itll default at 0, it will loop through every player in the world, and then get their scoreboard, and loop through every other player, and update the score
setScores(world) will loop through every scoreboard in the world and for each scoreboard itll loop through every player and set their score correctly
its the same as this I believe
for (Scoreboard scoreboard : world.getPlayers().stream().map(Player::getScoreboard).distinct().toArray(Scoreboard[]::new)) {
for (Player playerScore : world.getPlayers()) {
objectiveWorld.updateScores(scoreboard.getObjectives(), playerScore);
}
}```
this is what im thinking an implementation of ScoreboardWorld would be like
public class MyWorldManager implements ScoreboardWorld {
private final World world;
public MyWorldManager() {
this.world = Bukkit.getWorld("my cool world");
}
@Override
public void updateObjectives(Player player) {
Scoreboard scoreboard = player.getScoreboard();
// psuedo code
Objective objective = scoreboard.addObjective("kills: " + getKills(player));
objective.setScore(0);
}
public void playerKill(Player killer) {
// psuedo code
addKill(killer);
killer.getScoreboard().reset();
updateObjectives(killer);
}
}
so then now you could have a listener on world change that gets this manager, resets the player's scoreboard, and calls #updateObjectives on that player
and then ig when they leave you reset their scoreboard
mroeso health objectives automatically update when health is changed but when they are initially registered they start at 0
whenever a player kills another player in that world, call #playerKill which will update the killer's scoreboard
same idea with health updates, have a listener that listens to when a player gets damaged or whatever and call the corresponding method on the world manager that updates the scoreboard displaying the health of that player
dont need to do that because it automatically updates in server code
then that's even easier
only time i need to set the objective score is when I register it
thats the only time it doesnt update
oh right because you're using the actual score of the objective
rather thanusing the name
when a player gets damaged the score will fix itself from being at 0
but when its first initialised, the tablist will show everyones health at 0
what I could do is when a players scoreboard is initialised, it will loop through every player in the world and set the score of that player to their health
instead of double looping
or you could just set the score to the current player's health in the #updateObjectives method
and since you're showing everyone's scores it jsut propagates through all the players in the world
updating their scoreboard to reflect the new player that joined
so it turns into this
@Override
public void updateObjectives(Player player, Scoreboard scoreboard) {
Objective nametag = scoreboard.registerNewObjective("nametag-health", Criterias.HEALTH);
nametag.setDisplaySlot(DisplaySlot.BELOW_NAME);
nametag.setDisplayName(ChatColor.RED + StringEscapeUtils.unescapeJava("â€"));
Objective tab = scoreboard.getObjective("tab-health");
if (tab == null) {
tab = scoreboard.registerNewObjective("tab-health", Criterias.HEALTH);
}
tab.setDisplaySlot(DisplaySlot.PLAYER_LIST);
for (Player playerScore : world.getPlayers()) {
nametag.getScore(playerScore.getName()).setScore((int) playerScore.getHealth());
tab.getScore(playerScore.getName()).setScore((int) playerScore.getHealth());
}
}```
and no need for set score method at all
yeah exactly
yeah correct
this is now much more intuitive too since you aren't hiding code inside ObjectiveSetter
problem is on 1.8 they dont have WorldChangeEvent
though i am still a bit confused why you need both a player and a scoreboard argument
I dont I realised
1.8 :pepe_hands:
yea
Too old! (Click the link to get the exact time)
10th time this week
lmao
wait I cant find worldchangevent on javadocs anywhere
not sure if that's the actual name
oh its playerchangedworldevent
yeah there we go
oh sweet it exists on 1.8
@EventHandler
public void onWorldChange(PlayerChangedWorldEvent event) {
Scoreboard scoreboard = event.getPlayer().getScoreboard();
// When transferring worlds, reset all old objectives
for (Objective objective : scoreboard.getObjectives()) {
objective.unregister();
}
}```
even better
registerObjectives(player);
turned into this
yeah it's much cleaner imo
@Override
public void registerObjectives(Player player) {
Scoreboard scoreboard = player.getScoreboard();
Objective nametag = scoreboard.registerNewObjective("nametag-health", Criterias.HEALTH);
nametag.setDisplaySlot(DisplaySlot.BELOW_NAME);
nametag.setDisplayName(ChatColor.RED + StringEscapeUtils.unescapeJava("â€"));
Objective tab = scoreboard.getObjective("tab-health");
if (tab == null) {
tab = scoreboard.registerNewObjective("tab-health", Criterias.HEALTH);
}
tab.setDisplaySlot(DisplaySlot.PLAYER_LIST);
for (Player playerScore : world.getPlayers()) {
nametag.getScore(playerScore.getName()).setScore((int) playerScore.getHealth());
tab.getScore(playerScore.getName()).setScore((int) playerScore.getHealth());
}
}```
no I agree
and then for classes that dont even override the method because they have no objectives
I can just remove the interface implementation
cus its handled in the listener anyway
yeah
perfect
wait could I turn visibility into a interface again to then implement into worldchangevent
so then I wont even need to call anything regarding update visibility itll all be handled automatically as its handled in the world change event
but I would have to map world to visibility implementation somewhere
just make your world managers implement both visibility and objective
then you can just use the same system for both
and yeah depending on what your use-case is you could do the same for visibility
unless I stored the world id inside of the visibility interface and did some janky check every class that implemented visibility and if the world thats overriden matches the world change then run the code
lowkey might be easiest to do an if chain
yeah but I wanted to stay dynamic because if I'm manually defining it in the world change event whats the difference from manually using the static method in the actual class
if (MyWorldManager.inWorld(player)) {
// get the manager and do something with it
} else if (MyOtherWorldManager.inWorld(player)) {
// same ehre
} else if (MyThirdWorldManager.inWorld(player)) {
// also here
} //...
at startup I could just get a list of all classes that implement visibility
yeah i mean you could just make a hashmap that maps world uuid -> manager
true
nah that needs reflection bs you dont wanna do that
you'd just have to make a WorldManager interface that includes the methods you need
so like map.get(eventworlduuid) if not null then run setvisible(worldmanager.visibleplayers)
yeah exactly
why not Map<UUID, Visibility>
if its not null and it implements visibility
I already use reflection for my listener loading and my own command system so
because then you'd have 2 maps, one for visibility and one for objectives
and it runs in the ctor so theres no real performance loss
yeah but besides some specific use-cases it's usually not recommended to use reflection
I dont need a map for objectives
oh right dont you already have a system for that?
cus the code will always be
for (Objective objective : scoreboard.getObjectives()) {
objective.unregister();
}```
where as in visibility the code will change based on what the interface implements
in that it should just be fine to do a Map<UUID, Visibility>
surely I can use reflection tho
reflection is extremely unreadable and undebuggable
this is my current reflection usage
// command reflection loading
Reflections reflections = new Reflections(Command.class.getPackage().getName());
commandClasses = reflections.getSubTypesOf(Command.class);
subCommandTypes = reflections.getTypesAnnotatedWith(SubCommandInfo.class);
Field bukkitCommandMap;
try {
bukkitCommandMap = Bukkit.getServer().getClass().getDeclaredField("commandMap");
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
bukkitCommandMap.setAccessible(true);
try {
commandMap = (CommandMap) bukkitCommandMap.get(Bukkit.getServer());
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
initCommands();
// listener reflection loading
Reflections listenerReflections = new Reflections(
ChatListener.class.getPackage().getName(),
VoidCheckListener.class.getPackage().getName()
);
listenerClasses = listenerReflections.getSubTypesOf(Listener.class);```
yeah i do that too
i come from a js background so ive engrained in my mind the usage of require(...) inside a loop
wtf is require
interesting
but doing that in java doing that leads to unreadable and unpredictable code
like registering all listeners in a package or something
honestly I think id personally prefer reflection over a pre defined hashmap
well we all have our preferences ig
i will say there is a reason why literally like no other language has reflection or anything similar
I came from c# so I love reflection tbh
i dont think it matters since 2 worlds cant have the same name anyways
but uuid takes up less memory so
why not
takes up less memory in some cases*
i guess i should say
because a uuid is basically just a 64 bit (?) integer
if a string name is more than like 8 characters it'll take up more memory
I see
depends on what characters because utf-8 is weird like that
well thank u for the advice its been really helpful
yeah np
its an absolute pain in the butt to learn like inheritance and stuff especially in java but once you do you'll be able to write much better code and in much less time
in terms of maintainability and scalability
// visibility
Set<WorldManager> worldManagers = XinCraftPlugin.get().getWorldManagers();
UUID worldTo = event.getPlayer().getWorld().getUID();
Player player = event.getPlayer();
for (WorldManager worldManager : worldManagers) {
if (worldManager.getWorldUUID().equals(worldTo)) {
// handle visibility for the player
for (Player onlinePlayer : Bukkit.getOnlinePlayers()) {
boolean isInWorld = worldManager.visiblePlayers().contains(onlinePlayer.getUniqueId());
if (isInWorld) {
// Show the player who joined the match to everyone else in the match and vice versa
onlinePlayer.showPlayer(player);
player.showPlayer(onlinePlayer);
} else {
// Hide the player who joined the match from everyone else
onlinePlayer.hidePlayer(player);
// Hide everyone else from the player who joined the match
player.hidePlayer(onlinePlayer);
}
}
}
}```
inside the world change event
realised every world manager will handle visibility so no need to split them
public interface WorldManager {
UUID getWorldUUID();
Collection<UUID> visiblePlayers();
}```
LobbyWorld:
@Override
public UUID getWorldUUID() {
return SPAWN.getWorld().getUID();
}
@Override
public Collection<UUID> visiblePlayers() {
return SPAWN.getWorld().getPlayers().stream().map(Player::getUniqueId).collect(Collectors.toSet());
}```
also no need for reflection idk what I was thinking:
on enable:
worldManagers.add(lobbyWorld);
match ctor:
world = creator.createWorld();
XinCraftPlugin.get().getWorldManagers().add(this);
looks good to me
only thing i'll say is that you could use a map for worlds instead of a set for faster look-up times but its not like you'll have 500 worlds anyways so it really doesn't matter
is looping through a list to check for uuid the same performance wise as having a map of uuid to worldmanager and running get() and then if not null
ah yeah sweet
fuck I gotta remember to remove the world from the list once the world is unloaded
its like how doing a brute-force search of something in an array list is faster than in a hashset in small sizes because hashing takes longer than just checking each element in the array list
oh yeah memory leak oops
okay nice ty I'm really happy with it now
yeah it's looking pretty nice
and worldManagers is just a private final Set<WorldManager> worldManagers = new HashSet<>();
could I make it static
Unrelated, but I think you can do this with Guava without having to import Reflections dependency
ClassPath
nah that would be static abuse
I donât understand the bad thing about making stuff static
if itâs per server why canât it be static
There are plenty of articles online about this but Iâll just give the quick rundown:
- not the purpose of static
- global state = bad
- code unpredictability
- very untestable
- does not fair well with server reloads (though that isnât supported so may or may not be applicable in your case)
- good practice
- di/singleton patterns are much more flexible
The only places you should have static fields is in immutable final constants
Unless itâs a special case
Oh and also less code coupling
so itâs better to have plugin.instance.whatever
it just feels so ugly
when thereâs only ever one plugin instanfe
@round fossil dm advertising?
it gives me these ugly warnings everywhere
@rancid fern I'm ready !
Alr give me a moment
ready for what
so i did this base for managing just UUIDs and booleans
Is there anything else i could add / optimize? except adding maybe some comments and making method names more legible
public class Manager {
private final Map<UUID, Boolean> map = new HashMap<>();
public void set(UUID u, boolean b){
map.put(u, b);
}
public Map<UUID, Boolean> get(){
return map;
}
public boolean get(Player p){
return map.get(p.getUniqueId());
}
public boolean get(UUID u){
return map.get(u);
}
}
also ```java
public static Manager getManager(){
return manager;
}
wtf does the boolean mean
wdym with that?
what
do you mean why im storing a boolean?
oh shit i could do List<UUID>
like is the player hot or whatever
I didn't read the "the" at first and thought you meant "wtf does boolean mean" and got confused lmfao
lmao
đ
I mean tbf, what does the word actually mean
who knows
its from some guy named boolean who invented boolean algebra probably
we're jjust used to the word "boolean"
John Boolean
????
Nah it was John Boolean
so bool is the best way to write it anyways
nah John Bool invented those
but stop dodging my question, whats the point of the manager?
Yeah I mean the class name really isn't descriptive
sorry lol was trying to write that and also in the Boolean name thing
so i'm creating a placeholderapi expansion with some đ€ź api and im adding the player to the list whenever the api fires an event
also a better translation would be using a set of players, not a list
and then what does the list do
when placeholder is parsed is checks if the player UUID is on the list, if it is, the placeholder returns "yes", else, returns "no"
nope, the manager just has the list and i change the list only when an event is fired
i think so
With a Map<UUID, Boolean> you usually want a Set, not a List
lol
idk why but I never thought of abstracting managers ```kt
abstract class Manager<A, B> {
private val inner = mutableMapOf<A, B>()
val entries get() = inner.entries
operator fun get(a: A) = inner[a]
operator fun set(a: A, b: B) { inner[a] = b }
}
object CooldownManager : Manager<UUID, Int>()
DRY moment
đ
just extend a map?
I mean, that would be exposing the mutable map directly, which is the manager
which you should not be doing
what are you on about
You shouldn't be exposing mutable collections
rather immutable ones
and provide functions to modify the inner mutable one
why
whats the difference
your goal is to put stuff in the map
who cares how it got there
public class Manager {
private final Set<UUID> list = new HashSet<>();
public void set(UUID u, boolean b){
if (b){
list.add(u);
return;
}
list.remove(u);
}
public boolean get(UUID u){
return list.contains(u);
}
}```smth like that?
the Map is already the abstraction
So basically you can just use an iterator like the removeIf method does:
Set<TextDisplay> trackedDisplays = Collections.newSetFromMap(new WeakHashMap<>());
getScheduler().runTaskTimer(() -> {
Iterator<DisplayEntity> iterator = trackedDisplays.iterator();
while (iterator.hasNext()) {
DisplayEntity nextDisplayEntity = iterator.next();
if (!nextDisplayEntity.isValid()) {
iterator.remove();
CustomBlockSuppressionListener.onCustomBlockDeath(nextDisplayEntity);
}
}
}, 1, 1);
feel free to invert that if statement and continue instead
a map is only mutable because you can set values in it using the functions
just wrapping around doesnt change anything except adding another layer on the cake of abstract paintings
like how is a Map implemented
pretty sure it uses the same kind of idea that your Manager does
so this manager is just an implementation of a map
I mean that eliminates the point of a manager, a manager is just an encapsulation over a map
exactly
does anyone have a Chain of Responsibility code example?
you dont need a manager when you dont have anything to manage

"java chain of responsibility pattern example"
alr ty and sorry
use a collection if you want to keep things in a collection
if there is no additional logic past the CRUD stuff, why bother writing something for it
for sure, thanks đ
how should I add values to trackedDisplays ?
I've done it when I spawn a display, but it does nothing
wait
I might just be dumb
10/10 best code I've seen, O(0) performance both in time and space complexity, completely bug free, extremely polished, well thought out and honestly state-of-the-art techniques at play here
Might be nice to make those strings constants at the top of the file in case the endpoints ever change
And just for the sake of readability
I would also consider precompiling the pattern in that last .replaceAll() call. It's a bit lengthy and will probably be used relatively frequently enough to where precompiling is just an overall good idea. Matcher has a replaceAll() method which accomplishes the same thing, only you can reuse a Pattern constant
private static final Pattern PATTERN_UUID = Pattern.compile("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})");
private static String getString(String jsonElement, String request) throws IOException {
try (BufferedReader bufferReader = new BufferedReader(new InputStreamReader(new URL(request).openStream()))) {
String foundElement = (((JsonObject) JSON_PARSER.parse(bufferReader))
.get(jsonElement))
.toString()
.replace("\"", ""); // Also change this to 'replace' from 'replaceAll', the latter uses RegEx which you don't need
return PATTERN_UUID.matcher(foundElement).replaceAll("$1-$2-$3-$4-$5");
}
}
I believe that's the same?
Code review : https://github.com/conclube/DeluxeAsyncJoinLeaveMessage
10/10 commit message
Thank God for all those events
Now I can have 7 different events for players joining with 6 priorities for each. That's 42 different stages!
Ignore the fact I can't math and wrote 38
Use this at hypixel
I think the only bad thing about this is that there is some badly formatted code
https://github.com/conclube/DeluxeAsyncJoinLeaveMessage/blob/59824aa714a7f6663a35b77dd695d88a3b8ef30a/src/main/java/me/conclure/wtf/FinalProUltimateDeluxeAsyncChatJoinMessagePluginImplFactoryAbstractCommonMainProviderBootstrapExecutorHandler.java#L77 Like, the { should have a space before
https://www.spigotmc.org/resources/deluxeasyncjoinleavemessage-fully-optimized-async-everything-open-source.88129/ - conclube/DeluxeAsyncJoinLeaveMessage
That's about it hto
I like how the name and description are so long that it pushes your face off the embed
no jspecify support
Someone take this, remove the background and mirror it
Perfect reaction image
I did that like 5h ago
i left my idscord open and didnt see the ping
i forgot to turn my pc off this morning
and had this channel open
well theyre 2cm
tyty
Holy shit I took a quick glance at this and thought I had to ban you for a second
Thought you were a porn bot
what do you think that is
i think you need to turn ur computer off for a bit if you think that looks like something
what đđ
omegalul
đ
He never found the cliborous
how can I optimise my arbitrary small structure placer
@Getter
@NoArgsConstructor
public class Cage {
public CageBlock[][][] cageBlocks;
@JsonIgnore
private CageBlock[][][] cache;
private String name;
private int width;
private int height;
private int depth;
private int yPosition;
@JsonCreator
public Cage(@JsonProperty("name") String name, @JsonProperty("yposition") int yPosition, @JsonProperty("width") int width, @JsonProperty("height") int height, @JsonProperty("depth") int depth) {
cageBlocks = new CageBlock[width][height][depth];
this.name = name;
this.yPosition = yPosition;
this.width = width;
this.height = height;
this.depth = depth;
}
public int size() {
return cageBlocks.length;
}
public void cache(Location centreOfCage) {
cache = new CageBlock[width][height][depth];
Location position = centreOfCage.clone().subtract((double) width / 2, 0, (double) depth / 2);
for (int y = 0; y < height; y++) {
for (int z = 0; z < depth; z++) {
for (int x = 0; x < width; x++) {
Block cageBlock = position.getWorld().getBlockAt(
position.getBlockX() + x,
position.getBlockY() + y,
position.getBlockZ() + z);
cache[x][y][z] = new CageBlock(cageBlock.getType(), cageBlock.getState().getData());
}
}
}
}
public void generate(Location centreOfCage) {
Location position = centreOfCage.clone().subtract((double) width / 2, 0, (double) depth / 2);
boolean isFlipped = centreOfCage.getYaw() == -180;
for (int y = 0; y < height; y++) {
for (int z = 0; z < depth; z++) {
for (int x = 0; x < width; x++) {
CageBlock cageBlock = cageBlocks[x][y][z];
if (cageBlock != null) {
int zPosition = isFlipped ? z : (depth - 1 - z);
setBlock(position, y, zPosition, x, cageBlock);
}
}
}
}
}
public void resetToCache(Location centreOfCage) {
Location position = centreOfCage.clone().subtract((double) width / 2, 0, (double) depth / 2);
for (int y = 0; y < height; y++) {
for (int z = 0; z < depth; z++) {
for (int x = 0; x < width; x++) {
CageBlock cageBlock = cache[x][y][z];
if (cageBlock != null) {
setBlock(position, y, z, x, cageBlock);
}
}
}
}
}
private void setBlock(Location position, int y, int z, int x, CageBlock cageBlock) {
Block block = position.getWorld().getBlockAt(
position.getBlockX() + x,
position.getBlockY() + y,
position.getBlockZ() + z
);
block.setType(cageBlock.getType(), false);
BlockState state = block.getState();
state.setData(cageBlock.getMaterialData());
if (!state.update(true, false)) {
throw new RuntimeException("failed to update block at " + block.getLocation());
}
}```
public void saveToJson() {
ObjectMapper objectMapper = new ObjectMapper();
try {
objectMapper.writeValue(new File(XinCraftPlugin.get().getDataFolder() + File.separator + "cages" + File.separator + name + ".json"), this);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static Cage readJson(String name) {
try {
return new ObjectMapper().readValue(new File(XinCraftPlugin.get().getDataFolder() + File.separator + "cages" + File.separator + name.replace("_", "-") + ".json"), Cage.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}```
at the start of a match it will get the cage that the player has equipped with cacheTeamCage running on each team (only ever 2 teams)
cacheTeamCage:
private void cacheTeamCage(BridgeTeam bridgeTeam) {
...
cages.put(bridgeTeam, net.xincraft.systems.match.cage.Cage.readJson(cageName));
System.out.println("Cage cached for " + bridgeTeam + ": " + cages.get(bridgeTeam).getName());
Location cageLocation = map.getTeamSpawnpoints().get(bridgeTeam).toTeamSpawn(world, bridgeTeam)
.add(0, 5 - cages.get(bridgeTeam).getYPosition(), 0);
cages.get(bridgeTeam).cache(cageLocation);
}```
cages is just a map of team to cage
the position of the cage will never change per match, and the type of cage will never change per match
so at the start of the match I read the json and save it to a map
then I cache the existing blocks that are going to be replaced by my cage into a CageBlock[][][]
then I cage the team, which runsgenerate
and then after a 5 second timer, I run resetToCache which functions similar to worldedit's undo
CageBlock is just:
@Getter
@NoArgsConstructor
public class CageBlock {
private Material type;
private byte data;
public CageBlock(Material type, MaterialData state) {
this.type = type;
this.data = state.getData(); // this fucking sucks, but I can't see a way around it
}
@Override
public String toString() {
return type.name().toCharArray()[0] + "";
}
@JsonIgnore public MaterialData getMaterialData() {
return new MaterialData(type, data); // this fucking sucks, but I can't see a way around it
}
}```
You can use Gson instead of writing these save and load methods
formatted strings instead of " + "
Records
And i dont see the point of CageBlock - if not a thin wrapper for Block
i dont see the point of having a cache
creating a instance of ObjectMapper for each write đ
cache is just a saved array of what the blocks used to be before I generated the cage
write is never used unless testing purposes so doesnât really matter
will move to gson tho I hate this lib
yeah literally just a thin wrapper
there's also Moshi too, if you don't like gson
I believe it just lags the server a tiny bit when running generate
or resettocache
I believe due to all the light updates
performance wise its probably fine
code review wise, well
theres work to be done
https://www.reddit.com/r/androiddev/comments/684flw/comment/dgx3gpm/ jesus christ Moshi is already 7 and some years old
what the fuck do you mean 2017 was 8 years ago reddit đ
just use FAWE/WE
they figured out all of this so you don't have to
block placing en masse is the one thing you want to depend on WE for
LGTM. Merged.
I click "scroll to recent messages" and this is now in the middle of my screen
use 1D arrays over 3D arrays, [] is better than [][] which is better than [][][] for both memory and performance
also instead of using .toCharArray()[index] use .charAt(index)
you can also pre-compute the material data if it will never change
java 8 no records
yeah realized
and i would know when to change coordinate with width height depth?
so precompute it into a Block array?
not quite:
@Getter
@NoArgsConstructor
public class CageBlock {
private final MaterialData data;
public CageBlock(Material type, MaterialData state) {
this.data = new MaterialData(type, state.getData());
}
@Override
public String toString() {
return String.valueOf(type.name().charAt(0));
}
@JsonIgnore public MaterialData getMaterialData() {
return this.data;
}
}
basically:
int cageIndex = x * (height * width) + y * width + z;
Maths
you can get the coords back from the index, too:
int rem = index % (height * width);
int x = index / (height * width);
int y = rem / width;
int z = rem % width;
np, you should really not use 3D arrays, search up java's memory model
basically it ends up using a ton more ram
and slows down the program because ram isn't contiguous
I used to work with something similar before
Can't quite remember why and where tho
would I set cageBlocks = new CageBlock[width * height * depth];?
because I got an ArrayIndexOutOfBounds
I think its linking the index of the array to the position in the world maybe
unless I'm stupid
public void cache(Location centreOfCage) {
cache = new CageBlock[]{}; // new CageBlock[width * height * depth];
Location position = centreOfCage.clone().subtract((double) width / 2, 0, (double) depth / 2);
for (int y = 0; y < height; y++) {
for (int z = 0; z < depth; z++) {
for (int x = 0; x < width; x++) {
Block cageBlock = position.getWorld().getBlockAt(
position.getBlockX() + x,
position.getBlockY() + y,
position.getBlockZ() + z);
int index = x * (height * width) + y * width + z;
cache[index] = new CageBlock(cageBlock.getType(), cageBlock.getState().getData());
System.out.println("Index: " + index + " x: " + x + " y: " + y + " z: " + z);
}
}
}
}```
java.lang.ArrayIndexOutOfBoundsException: 0
at net.xincraft.systems.match.cage.Cage.cache(Cage.java:75) ~[?:?]
which is this here
okay so you're making a zero-size array
you need to do
cache = new CageBlock[width * height * depth];
I tried it with that and it did an out of bounds for like 2200 or something
okay now we're getting somewhere
one sec let me reproduce
can you print width, height, depth and the multiplications of each
so like
width = 0, height = 0, depth = 0, width * height * depth = 0
yeah one sec
I had to convert all my old cage json types to the new type
dunno if that could of fucked anything up
nah it's fine
[22:28:44 INFO]: Cage cached for BLUE: egg
[22:28:44 INFO]: Width: 15 Height: 13 Depth: 10 All: 1950
[22:28:44 INFO]: Width: 15 Height: 13 Depth: 10 All: 1950
[22:28:44 INFO]: Width: 15 Height: 13 Depth: 10 All: 1950
[22:28:44 INFO]: Width: 15 Height: 13 Depth: 10 All: 1950
[22:28:44 INFO]: Width: 15 Height: 13 Depth: 10 All: 1950
[22:28:44 INFO]: Width: 15 Height: 13 Depth: 10 All: 1950
[22:28:44 INFO]: Width: 15 Height: 13 Depth: 10 All: 1950
[22:28:44 INFO]: Width: 15 Height: 13 Depth: 10 All: 1950
[22:28:44 INFO]: Width: 15 Height: 13 Depth: 10 All: 1950
[22:28:44 INFO]: Width: 15 Height: 13 Depth: 10 All: 1950
[22:28:44 INFO]: Width: 15 Height: 13 Depth: 10 All: 1950
[22:28:44 WARN]: [XinCraft] Task #29 for XinCraft v0.2.3 generated an exception
java.lang.ArrayIndexOutOfBoundsException: 1950
public void cache(Location centreOfCage) {
cache = new CageBlock[width * height * depth];
Location position = centreOfCage.clone().subtract((double) width / 2, 0, (double) depth / 2);
for (int y = 0; y < height; y++) {
for (int z = 0; z < depth; z++) {
for (int x = 0; x < width; x++) {
Block cageBlock = position.getWorld().getBlockAt(
position.getBlockX() + x,
position.getBlockY() + y,
position.getBlockZ() + z);
int index = x * (height * width) + y * width + z;
System.out.println("Width: " + width + " Height: " + height + " Depth: " + depth + " All: " + (width * height * depth));
cache[index] = new CageBlock(cageBlock.getType(), cageBlock.getState().getData());
//System.out.println("Index: " + index + " x: " + x + " y: " + y + " z: " + z + " width: ");
}
}
}
}```
ah I'm dumb
lol
well my honest suggestion
new CageBlock[width * height * depth + 1];
ill give a shot
I added more logging too
[22:32:02 INFO]: Cage cached for BLUE: egg
[22:32:02 INFO]: Width: 15 Height: 13 Depth: 10 All: 1950
[22:32:02 INFO]: Index: 0 x: 0 y: 0 z: 0
[22:32:02 INFO]: Index: 195 x: 1 y: 0 z: 0
[22:32:02 INFO]: Index: 390 x: 2 y: 0 z: 0
[22:32:02 INFO]: Index: 585 x: 3 y: 0 z: 0
[22:32:02 INFO]: Index: 780 x: 4 y: 0 z: 0
[22:32:02 INFO]: Index: 975 x: 5 y: 0 z: 0
[22:32:02 INFO]: Index: 1170 x: 6 y: 0 z: 0
[22:32:02 INFO]: Index: 1365 x: 7 y: 0 z: 0
[22:32:02 INFO]: Index: 1560 x: 8 y: 0 z: 0
[22:32:02 INFO]: Index: 1755 x: 9 y: 0 z: 0
[22:32:02 INFO]: Index: 1950 x: 10 y: 0 z: 0
[22:32:02 WARN]: [XinCraft] Task #30 for XinCraft v0.2.3 generated an exception
java.lang.ArrayIndexOutOfBoundsException: 2145
hm wait wait
looks like its not starting at index 0 and climbing up
oh I'm dumb
int index = (y * depth * width) + (z * width) + x;
lol
I forgor how to maths
I hate this code because I cannot read it
like my brain genuinely cant comprehend this sort of stuff
it makes me so mad
yea
if the cube is 10m high, 10m wide and 10m long
btw it worked but its fucked as lol
đ
props to you tho its so much faster
yeah, always remember
[] is way faster than [][]
and [][] is WAY faster than [][][]
the slowness grows exponentially the more you nest
[][][][][][]
lol
imagine trying to comprehend a 5d array
lol
can you move a bit away so I can see it đ
where are you placing
// Convert the old cageBlocks array
CageBlock[] newCageBlocks = new CageBlock[oldCage.getWidth() * oldCage.getHeight() * oldCage.getDepth()];
for (int x = 0; x < oldCage.getWidth(); x++) {
for (int y = 0; y < oldCage.getHeight(); y++) {
for (int z = 0; z < oldCage.getDepth(); z++) {
int index = x * (oldCage.getHeight() * oldCage.getDepth()) + y * oldCage.getDepth() + z;
newCageBlocks[index] = oldCage.getCageBlocks()[x][y][z];
}
}
}
// Create a new Cage object with the converted cageBlocks array
NewCage newCage = new NewCage(oldCage.getName(), oldCage.getYPosition(), oldCage.getWidth(), oldCage.getHeight(), oldCage.getDepth());
newCage.cageBlocks = newCageBlocks;```
conversion code ^
wdym
so [][][] is still there đ
okay basically, what is conversion
nah I just used two different classes
ah okay
and read from old class and then converted and then saved as new class
where are you placing the blocks
above a predetermined position based on the map
for example
{"BLUE":{"x":0,"y":99,"z":-29},"RED":{"x":0,"y":99,"z":28}}
oh lol
public void generate(Location centreOfCage) {
Location position = centreOfCage.clone().subtract((double) width / 2, 0, (double) depth / 2);
boolean isFlipped = centreOfCage.getYaw() == -180;
for (int y = 0; y < height; y++) {
for (int z = 0; z < depth; z++) {
for (int x = 0; x < width; x++) {
int index = getIndex(y, z, x);
CageBlock cageBlock = cageBlocks[index];
if (cageBlock != null) {
int zPosition = isFlipped ? z : (depth - 1 - z);
setBlock(position, y, zPosition, x, cageBlock);
}
}
}
}
}```
okay so, what is getIndex
location thats being parsed as the argument:
Location cageLocation = map.getTeamSpawnpoints().get(bridgeTeam).toTeamSpawn(world, bridgeTeam) .add(0, 5 - cages.get(bridgeTeam).getYPosition(), 0);
private int getIndex(int y, int z, int x) {
return (z * depth * width) + (y * width) + x;
}```
oh yeah
(what the heck is y z x)
I see the issue
it seems like the cache method is fine but the generate method isnt
use the formula in getIndex for every place you're doing something similar
its possible I fucked up conversion
they all need to be the exact same
pretty sure I am
nop, conversion uses x y z order and getIndex uses z y x order
x * (oldCage.getHeight() * oldCage.getDepth()) + y * oldCage.getDepth() + z; is in conversion
replace it with
z * (oldCage.getDepth() * oldCage.getWidth()) + y * oldCage.getWidth() + x;
wait
no
ah
idk bro again I cannot comprehend it so
do this
they forgot to replace something, it's okay dw
@steady shard
alright
one sec
I see I see now
sorry lol
out of bounds
Caused by: java.lang.ArrayIndexOutOfBoundsException: 315
at net.xincraft.systems.match.cage.CageConverter.convertOldCages(CageConverter.java:38) ~[?:?]
// Convert the old cageBlocks array
CageBlock[] newCageBlocks = new CageBlock[oldCage.getWidth() * oldCage.getHeight() * oldCage.getDepth()];
for (int x = 0; x < oldCage.getWidth(); x++) {
for (int y = 0; y < oldCage.getHeight(); y++) {
for (int z = 0; z < oldCage.getDepth(); z++) {
int index = z * (oldCage.getDepth() * oldCage.getWidth()) + y * oldCage.getWidth() + x;
newCageBlocks[index] = oldCage.getCageBlocks()[x][y][z];
}
}
}```
so
private int getIndex(int y, int z, int x) {
return (y * depth * width) + (z * width) + x;
}```
yus
and
y * (oldCage.getDepth() * oldCage.getWidth()) + z * oldCage.getWidth() + x;
wait wtf wheres height
you don't really need it
show
W
yeah just rule of thumb
1D arrays
are the goat
also lemme explain how it works
imagine you slice up a cube
first you do it in floors
so say you have a 10x10x10 cube
you have 10 floors
each floor has width * depth blocks inside, right?
the formula for the area of a plane is that ^
yea
right
so y = 0 would mean first floor, etc
then
split each floor into rows
you get 10 rows
each row is width length
so the same exact thing applies
z = 0 means the first row
z = 1 is the second
etc
then x is just the specific column in the row
floor_size = width * height
floor = y * floor_size
row_size = width
row = z * row_size
column = x
selected = floor + row + column
same thing applies in 2D
you just remove floor
and it becomes like
y * width + x
(you don't have z in 2D yk)
private int getIndex(int y, int z, int x) {
// slice a cube into floors
// each floor is width * depth in length, y=0 is the first floor, y=1 is the second floor, etc.
int floor = (y * depth * width);
// each row is width in length, z=0 is the first row, z=1 is the second row, etc.
int row = (z * width);
// x is the column in the row
return floor + row + x;
}```
this checks out yea
yup
that's actually a great way to remember
you should now try to remove the old class
and straight up remove the conversion
yeah will do
its still a little bit noticibly laggy when it initially runs
cache and then generate straight after
as both happen when the game starts
actually maybe its not
wha
might just be me thinking its lagging
should I benchmark?
[23:12:31 INFO]: [XinCraft] Cached and generated cages for each team (took 49ms)
I'm thinking like
I could just cache like 20x20x20 at the start of the map load
for both sides
and have it not dependent on the cage size
do the calculations async and send the block changes sync
Worker threads + chunk snapshotting + fast block placement
https://github.com/mcbrawls/inject/
I'm really not a fan of how I did the platform thing, are there any suggestions to that?
and general stuff too ofc
ready to get roasted
||-# spigot support might come soon idk đ||
Rad on Java is wild
I moved this to java yeah
wdym
I don't like how you have to do it on the instance
no I just haven't done this before đ
hmm, I assume you wanna provide library users the option to code platform agnostically against your library
I guess that'd be nice
With Kotlin you can at least fun InjectPlatform.Companion.registerPaper(injector: Injector) 
how you decide to provide the implementations donât really matter too much
Yea fairs
I mean I can say that factory is nice since you decouple creation/obtaining the service implementation from the instantiation/instancing itself
But like who cares

Yo yo lets not jump to conclusions
đ
based server
have at it ladies and gents
this is the first actual project I've posted here in a bit xD
was Y2K_ taken too
lmfao, I was about to say, you might be mistaken as a dream fan now lol
shoulda known dream would get big smh
what an oversight
wastaken used to be pretty normal before dream
yeah I used it cuz I know others who did đ€·ââïž figured yeah works for me
but dream has to ruin everything đĄ
you know you can change your gh handle right
yeah I don't want to though
and tbf I never understood why it is in past tense
it'd make more sense if it was <username>istaken but I guess it doesn't look as pretty
one might make the argument that it is in the past in the sense of it was taken when I tried using the handle but eh
then again istaken would feel like they're purposefully saying they're married in their handle lmao
guess there's no good way to phrase it
I'm going to release my revolutionary bukkit competitor if no one reviews this code đ
that sounds too overengineered, and it wouldn't really help the end user of the library because most if not all bukkit users will stick with Gson as that's what is bundled in the platform
I'd rather see people start adopting moshi but that's not going to happen anytime soon ig, since gson just works
yeah true, but this is generic, I just happened to use GSON because its bundled with minecraft wich I use for my server api
You can use Configurate 
It has support for both Jackson and GSON
as well as other formats if you want that
eh, I don't love Configurate. It is a schema validator disguised as a configuration library
It works great
never had issues with it
my issue with it is mostly the cultural perception, people believe that a configuration library should be able to handle dynamic values but Configurate is oriented towards static configuration for validation purposes, so dynamism is kind of rough with it
configurate isn't really valuable for my library though since I only really need to parse and write json
is there a reason you're implementing JsonSerialize/Deserialize separatedly instead of just implementing TypeAdapter
they're different things
TypeAdapter is lower level, faster, but harder to implement
it handles like a stream versus an easy object based adapter
so why go for the slower option
noticed anything else I should change?
its what I'm used to and I prefer not to handle json deserialization in a stream like format tbh
tbh, those classes don't seem to need a custom adapter for it
it doesn't look like you're doing anything more than just excluding fields, which could be done by marking the fields as transient in the class definition
if you don't use a custom adapter you delegate to reflection
you could delegate to Unsafe, but that's on its way out
gson has some built in de/serialization
to me it just makes more sense to instead of using the default reflective deserializer/serializer to just implement one
it really doesn't, there's nothing wrong with reflection, it is an instrospection tool
the bulk of the operation is going to be eaten by the IO costs of reading/writing to a file anyway, I don't believe performance is a worry here considering these files aren't going to be read more than once per plugin/mod
there could be the odd tool that tries to use your library for parsing plugin/mod metadata in bulk, but we're not there yet so I don't particularly think it is worth worrying about. Unless you want to specifically target that, then the best thing to do would be to actually benchmark whether the reflective approach is considerably slower for such situation, if it isn't then you can freely chip away at the line count for free
the way you use Gson leads me to believe you'd be more suited to use Jackson lol
was Cuvee the previous name of this, that's what the usage uses lol
the utility methods that get metadata from the internet should either use a single HttpClient, or warn in the javadoc that it creates a HttpClient per request. Those are not free
yeah lol I forgot to rename it
will do smart idea
If Conclube wasnât a femboy he wouldnât be buying people cat ears
why not
XD
what is chunk snapshotting and fast block placement
okay can I at least get some general feedback for this
@woeful pasture
damn bro
He's gonna go ham on it fr
I gave him asm advice earlier
yeah no fuck spigot support for now
Review it anyway
okay I will after I'm done with homeowrk
Chunk#getChunkSnapshot
?workdistro
looks good to me
I could go full CMarco, but I won't
oh god what did he do
do you not remember when he went around reviewing repositories?
and giving them ratings?
uh no
L
what is ServerThreadSupplier
I dont actually understand what this is doing
could I use this async method instead of the other one?
why so many abstractions
Y2K you have abstraction disease
it's when you can't help yourself but create more abstractions
D: and miles was yelling at me for abstracting
The design itself is rather bad to be completely honest
I haven't gotten around to reworking it
The suppliers themselves shouldn't be children of the other
I could do just a Object, but I don't really like the idea of having to do instanceof checks
I could also do their is always a return but in some cases its a Unit or something too
for that one specifically, I was thinking you could just remove it all lol
I'm going to be very blunt here, but that just looks like a half-assed scheduler on top of the bukkit scheduler
I've transformed it into this
public void cageAndCacheTeamsAsync() {
// Cache cages asynchronously
CompletableFuture<Void> redTeamCacheFuture = CompletableFuture.runAsync(() -> cacheTeamCage(BridgeTeam.RED));
CompletableFuture<Void> blueTeamCacheFuture = CompletableFuture.runAsync(() -> cacheTeamCage(BridgeTeam.BLUE));
CompletableFuture<Void> allCachesCompleted = CompletableFuture.allOf(redTeamCacheFuture, blueTeamCacheFuture);
allCachesCompleted.thenRun(() -> {
System.out.println("All cages cached, now running generation code");
// Generate cages asynchronously
CompletableFuture<Void> redTeamCageFuture = CompletableFuture.runAsync(() -> {
Location redCageLocation = map.getTeamSpawnpoints().get(BridgeTeam.RED).toTeamSpawn(world, BridgeTeam.RED)
.add(0, 5 - cages.get(BridgeTeam.RED).getYPosition(), 0);
System.out.println("Generating red cage at " + redCageLocation);
cages.get(BridgeTeam.RED).generate(redCageLocation);
System.out.println("Red cage generated");
});
CompletableFuture<Void> blueTeamCageFuture = CompletableFuture.runAsync(() -> {
Location blueCageLocation = map.getTeamSpawnpoints().get(BridgeTeam.BLUE).toTeamSpawn(world, BridgeTeam.BLUE)
.add(0, 5 - cages.get(BridgeTeam.BLUE).getYPosition(), 0);
System.out.println("Generating blue cage at " + blueCageLocation);
cages.get(BridgeTeam.BLUE).generate(blueCageLocation);
System.out.println("Blue cage generated");
});```
CompletableFuture<Void> allCagesGenerated = CompletableFuture.allOf(redTeamCageFuture, blueTeamCageFuture);
allCagesGenerated.thenRun(() -> {
System.out.println("All cages generated, now sending players to cages");
Bukkit.getScheduler().runTask(XinCraftPlugin.get(), () -> {
for (BridgeTeam bridgeTeam : BridgeTeam.values()) {
for (Player player : world.getPlayers().stream().filter(p ->
players.contains(p.getUniqueId()) &&
teams.get(p.getUniqueId()).equals(bridgeTeam)).collect(Collectors.toList())) {
player.setGameMode(GameMode.ADVENTURE);
respawn(player, true);
List<Player> players = new ArrayList<>();
for (Player player_ : worldPlayersOf(this.players)) {
if (teams.get(player_.getUniqueId()) == bridgeTeam) {
players.add(player_);
}
}
Location cageLocation = map.getTeamSpawnpoints().get(bridgeTeam).toTeamSpawn(world, bridgeTeam)
.add(0, 5 - cages.get(bridgeTeam).getYPosition(), 0);
cageCountdowns.put(bridgeTeam, new CageCountdown(this, null, players, bridgeTeam, cageLocation));
}
}
});
}).exceptionally(throwable -> {
throw new RuntimeException("Failed to generate cages", throwable);
});
}).exceptionally(throwable -> {
throw new RuntimeException("Failed to cache cages", throwable);
});
}```
and then to ensure the actual block setting happens on main thread I just schedule it
private void setBlock(Location position, int y, int z, int x, CageBlock cageBlock) {
Bukkit.getScheduler().runTask(XinCraftPlugin.get(), () -> {
Block block = position.getWorld().getBlockAt(
position.getBlockX() + x,
position.getBlockY() + y,
position.getBlockZ() + z
);
block.setType(cageBlock.getType(), false);
BlockState state = block.getState();
state.setData(cageBlock.getMaterialData());
if (!state.update(true, false)) {
throw new RuntimeException("failed to update block at " + block.getLocation());
}
});
}```
so I believe all the calculations here are running async:
public void generate(Location centreOfCage) {
Location position = centreOfCage.clone().subtract((double) width / 2, 0, (double) depth / 2);
boolean isFlipped = centreOfCage.getYaw() == -180;
for (int y = 0; y < height; y++) {
for (int z = 0; z < depth; z++) {
for (int x = 0; x < width; x++) {
int index = getIndex(y, z, x);
CageBlock cageBlock = cageBlocks[index];
if (cageBlock != null) {
int zPosition = isFlipped ? z : (depth - 1 - z);
setBlock(position, y, zPosition, x, cageBlock);
}
}
}
}
}
public void resetToCache(Location centreOfCage) {
Location position = centreOfCage.clone().subtract((double) width / 2, 0, (double) depth / 2);
for (int y = 0; y < height; y++) {
for (int z = 0; z < depth; z++) {
for (int x = 0; x < width; x++) {
int index = getIndex(y, z, x);
CageBlock cageBlock = cache[index];
if (cageBlock != null) {
setBlock(position, y, z, x, cageBlock);
}
}
}
}
}```
that's way too much code to review on discord, put it on github or some other code hosting platform
honestly he does tho
I said github specifically because it is hard to review this piece of code in isolation to whatever else you might be doing
it'd be better to get the whole picture before going on a rant about the things you're doing here specifically
I'll upload the match code to github but we dont want the whole thing open source
if it is supposed to be a closed source plugin then I guess it's fine if you don't share it, I'm going to ask a lot of questions to understand the scope here though, if that's alright with you
of course
heres a bit more
if that helps
I've only implemented this async stuff during the end of the startMatch method in BridgeMatch
you'll want to look at
cageAndCacheTeamsAsync, cacheTeamCageAsync, and the Cage class
there's way too much happening on that Match class
i know I need to abstract it real bad
Hey Anti Abstraction Senior here, Iagree with the JavierFlores guy
I dont even know where to start when abstracting this
anyway any feedback on the cage async generation
yeah no, I tried to read the code but there's just way too much happening there lol
one thing I can tell you is, you should stay back from the whole async thing
you reckon
mostly because you don't seem to understand the implications there, there's a lot of data races happening in that code
alright
and honestly how woudl I even start to abstract this code
becuase I absolutely hate it
well, Match seems to be a game loop, so center around that idea
aside from a game loop, it is also doing team management (that is, adding teams, removing teams, adding parties to teams etc)
so that part you can completely separate
like use a TeamManager of some sorts
and handle all that code in there
and then seperate all the cage stuff
yes, the BridgeMatch class should ultimately only be handling the game events, which are start match, end match, start countdown, etc
I mean, BridgeMatch does seem to be handling some listener information, however I don't particularly see why it has to handle notification of anything other than the game events
maybe like MatchListener, cus rn its just a listener that will find the player's match and then handle
whereas I could just create a new listener instance per match class
that's a huge ass plugin
when I get the chance going to split away a main plugin, there match plugin, then lobby plugin, and then bridge match plugin
if you are willing to go for a big refactor, I recommend you to go on paper and think about the class structure before chipping away at these classes
we're using pandaspigot atm which is a paper fork
I say paper because that's what I'm used to however any UML diagram thing will do, or a whiteboard
lmao
yeah, I figured it was an older version by looking at some of the code there using older APIs
honestly it used to be much much worse
I only just completed a full refactor of the match system
but it was definitely rushed
it isn't that bad I believe, it is just that I don't think the design is in a good state right now in order to reasonably make anything out of the algorithm
my end goal is to have a match plugin where I can easily just develop any minigame onto it just like bridge
your issue in question is the fact that your gamemode/minigame requires placing a lot of blocks, which is the part of concern, however we can't really look at it in isolation since it's hard to understand what is the relationship with cages and the bridge match to think about optimizing it
cages would only be apart of the bridge part of the plugin
but I agree that the bridge match class is ridiculously large
if you abstract away the concepts enough, it could be doable, but you got to think of the gamemodes/minigames as a whole and ask the question "what constitutes a Match? How will this be used by the gamemode, what if other gamemodes were to use it?"
then again, as far I can see you are working with completely custom plugins and there already seems to be a lot of framework-ish plugins in your codebase (or so I imagine from the database players and all of that), so you also have to think on how to adapt these already existing systems into that
it may seem mind-boggling, but it really just takes sitting down and thinking about the relationships
Hi, I have this huge class that is nearly 500 lines of code https://github.com/PulseBeat02/MurderRun/blob/main/src/main/java/io/github/pulsebeat02/murderrun/game/player/PlayerManager.java what can I do to help shorten it? Because I feel like making another class doesn't make sense because all the methods are manipulating all players as a whole
hi
oh cool
@sharp epoch I don't know if I'm thinking correctly but I can have an BridgeMatch class that contains HitManager, CageManager, GoalManager, TeamManager
and then take away everything regarding those classes
Iâll take a look in a bit here
You might honestly be fine though
I donât know but some ideas:
For murderers and survivors, a parent class for those so you donât need several methods doing the same thing (broadcasting) and seperating them with just an enum type.
Also in my opinion, you donât need to cache everything in a minigame plugin because the performance improvement youâll get from that over quering the entire list is negligible in a plugin thatâs not made to hold so many players
Besides that, not really much to change, could split the caching to a seperate handler
I was also going to say that I'm unsure how necessary those cache collections are
+1 for some interfaces. Good suggestion. MessageReceiver or something that both Survivor and GamePlayer could override
Exactly, minigames are likely running at most 50 players, and that small performance benefit from caching isnât worth those 50 players imo
RAM usage is likely a proportionally higher result than the performance benefit than youâd get from it
^^
Oh I'm sorry, Survivor and Killer both extend GamePlayer. Then you don't even need interfaces at all. Just a base method on GamePlayer
See brief LSP discussion in #help-development message
Applies to more than just collections and maps :)
Also if you want to save lines
private Collection<Survivor> cachedAliveSurvivors;
private Collection<Survivor> cachedSurvivors;
to
private Collection<Survivor> cachedAliveSurvivors, cachedSurvivors;
xD
don't
it's not worth it
Why not just remove newlines altogether :)
I'm personally not a fan of doing this
It's weird. There's like a time and a place to do that. I personally like doing it on occasion for very simple x, y, z variables, but yeah. Situational for sure
I 100% agree.
someone save me from this shit https://paste.md-5.net/asuhudijuq.cs
this should let specifi items to be sat on a gui
by putting it or shift clicking it
InventoryHolder and opening inventory directly in the event đ«
?gui
And delay the open a tick
how can I save items in the gui?
should I serialize it?
Oh shit interfaces and abstractions good point. And also I agree with you that the caching unnecessary the more I think about it
Oh I originally had it for Survivor out of continence so I didnât have to cash but I will use the most basic form yeah
how can I further reduce this large as file and improve my match system
@sharp epoch i did a lot of work to it
public class SkyblockJavaPlugin extends JavaPlugin {
public static SkyblockJavaPlugin instancia;
public static SkillAndStatsManager skillAndStatsManager;
public static String bungeeName = "";
public static boolean sendPlayerToServer(Player player, String server) {
try {
ByteArrayOutputStream b = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(b);
out.writeUTF("Connect");
out.writeUTF(server);
player.sendPluginMessage(instancia, "BungeeCord", b.toByteArray());
b.close();
out.close();
}
catch (Exception e) {
instancia.getLogger().info(ChatColor.RED + "ExcepciĂłn conectĂĄndose a " + server);
}
return true;
}
@Override
public void onEnable(){
getServer().getMessenger().registerOutgoingPluginChannel(this, "BungeeCord");
instancia = this;
skillAndStatsManager = new SkillAndStatsManager(this);
}
public static SkyblockJavaPlugin getInstance(){
return instancia;
}
public static SkillAndStatsManager getSkillAndStatsManager(){
return skillAndStatsManager;
}
}
simple code but i wanna make sure everything is okay
also i will update bungeeName on the onEnable so don't worry bout it
instancia đ€
do you atleast know what that means
Yeah
then
hablås español? xd
Idk spanish
alr
Still running away from duolingo brid
lmao
use a try with resources for the streams
what đ
