General optimization

This commit is contained in:
TheMode 2019-09-21 20:42:27 +02:00
parent d833963414
commit 5c0056e183
26 changed files with 555 additions and 170 deletions

View File

@ -7,6 +7,7 @@ import fr.themode.minestom.entity.Player;
import fr.themode.minestom.instance.InstanceManager;
import fr.themode.minestom.instance.block.BlockManager;
import fr.themode.minestom.instance.demo.StoneBlock;
import fr.themode.minestom.instance.demo.UpdatableBlockDemo;
import fr.themode.minestom.listener.PacketListenerManager;
import fr.themode.minestom.net.ConnectionManager;
import fr.themode.minestom.net.ConnectionUtils;
@ -15,6 +16,7 @@ import fr.themode.minestom.net.packet.PacketReader;
import fr.themode.minestom.net.packet.client.status.LegacyServerListPingPacket;
import fr.themode.minestom.net.packet.server.play.KeepAlivePacket;
import fr.themode.minestom.net.player.PlayerConnection;
import fr.themode.minestom.scoreboard.TeamManager;
import fr.themode.minestom.utils.Utils;
public class Main {
@ -23,6 +25,7 @@ public class Main {
public static final int THREAD_COUNT_PACKET_WRITER = 2;
public static final int THREAD_COUNT_IO = 2;
public static final int THREAD_COUNT_BLOCK_BATCH = 2;
public static final int THREAD_COUNT_BLOCK_UPDATE = 2;
public static final int THREAD_COUNT_ENTITIES = 2;
public static final int THREAD_COUNT_PLAYERS_ENTITIES = 2;
@ -44,6 +47,7 @@ public class Main {
private static BlockManager blockManager;
private static EntityManager entityManager;
private static DataManager dataManager;
private static TeamManager teamManager;
public static void main(String[] args) {
connectionManager = new ConnectionManager();
@ -54,8 +58,10 @@ public class Main {
blockManager = new BlockManager();
entityManager = new EntityManager();
dataManager = new DataManager();
teamManager = new TeamManager();
blockManager.registerBlock(StoneBlock::new);
blockManager.registerBlock(new StoneBlock());
blockManager.registerBlock(new UpdatableBlockDemo());
server = new Server(136434);
@ -115,7 +121,7 @@ public class Main {
entityManager.update();
// Blocks update
blockManager.update();
instanceManager.updateBlocks();
// TODO miscellaneous update (scoreboard)
@ -158,6 +164,10 @@ public class Main {
return dataManager;
}
public static TeamManager getTeamManager() {
return teamManager;
}
public static ConnectionManager getConnectionManager() {
return connectionManager;
}

View File

@ -28,6 +28,10 @@ public enum ChatColor {
this.id = id;
}
public byte getId() {
return id;
}
@Override
public String toString() {
return Chat.COLOR_CHAR + String.valueOf(HexUtils.byteToHex(id));

View File

@ -5,8 +5,8 @@ import com.google.gson.JsonObject;
import fr.themode.minestom.Main;
import fr.themode.minestom.bossbar.BossBar;
import fr.themode.minestom.chat.Chat;
import fr.themode.minestom.chat.ChatColor;
import fr.themode.minestom.collision.BoundingBox;
import fr.themode.minestom.data.Data;
import fr.themode.minestom.entity.property.Attribute;
import fr.themode.minestom.event.*;
import fr.themode.minestom.instance.Chunk;
@ -21,12 +21,14 @@ import fr.themode.minestom.net.packet.client.ClientPlayPacket;
import fr.themode.minestom.net.packet.server.ServerPacket;
import fr.themode.minestom.net.packet.server.play.*;
import fr.themode.minestom.net.player.PlayerConnection;
import fr.themode.minestom.scoreboard.BelowNameScoreboard;
import fr.themode.minestom.scoreboard.Team;
import fr.themode.minestom.scoreboard.TeamManager;
import fr.themode.minestom.utils.*;
import fr.themode.minestom.world.Dimension;
import fr.themode.minestom.world.LevelType;
import java.util.Collections;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
@ -85,6 +87,8 @@ public class Player extends LivingEntity {
private byte targetLastStage;
private Set<BossBar> bossBars = new CopyOnWriteArraySet<>();
private Team team;
private BelowNameScoreboard belowNameScoreboard;
// Vehicle
private float sideways;
@ -136,16 +140,6 @@ public class Player extends LivingEntity {
}
});
setEventCallback(PlayerStartDiggingEvent.class, event -> {
BlockPosition blockPosition = event.getBlockPosition();
Data data = getInstance().getBlockData(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
if (data == null) {
sendMessage("DATA IS NULL");
return;
}
sendMessage("BLOCK DATA: " + data.get("value"));
});
setEventCallback(PickupItemEvent.class, event -> {
event.setCancelled(!getInventory().addItemStack(event.getItemStack())); // Cancel event if player does not have enough inventory space
});
@ -178,17 +172,12 @@ public class Player extends LivingEntity {
getInventory().addItemStack(new ItemStack(1, (byte) 75));
//getInventory().addItemStack(new ItemStack(1, (byte) 100));
TeamsPacket teamsPacket = new TeamsPacket();
teamsPacket.teamName = "TEAMNAME" + new Random().nextInt(100);
teamsPacket.action = TeamsPacket.Action.CREATE_TEAM;
teamsPacket.teamDisplayName = "WOWdisplay";
teamsPacket.nameTagVisibility = "always";
teamsPacket.teamColor = 2;
teamsPacket.teamPrefix = "pre";
teamsPacket.teamSuffix = "suf";
teamsPacket.collisionRule = "never";
teamsPacket.entities = new String[]{getUsername()};
sendPacketToViewersAndSelf(teamsPacket);
TeamManager teamManager = Main.getTeamManager();
Team team = teamManager.createTeam(getUsername());
team.setTeamDisplayName("display");
team.setPrefix("[Test] ");
team.setTeamColor(ChatColor.RED);
setTeam(team);
setAttribute(Attribute.MAX_HEALTH, 10);
heal();
@ -199,6 +188,8 @@ public class Player extends LivingEntity {
}
scoreboard.addViewer(this);
scoreboard.updateLineContent("id3", "I HAVE BEEN UPDATED &2TEST");*/
setBelowNameScoreboard(new BelowNameScoreboard());
});
}
@ -349,7 +340,7 @@ public class Player extends LivingEntity {
if (player == this)
return;
super.addViewer(player);
PlayerConnection connection = player.getPlayerConnection();
PlayerConnection viewerConnection = player.getPlayerConnection();
String property = "eyJ0aW1lc3RhbXAiOjE1NjU0ODMwODQwOTYsInByb2ZpbGVJZCI6ImFiNzBlY2I0MjM0NjRjMTRhNTJkN2EwOTE1MDdjMjRlIiwicHJvZmlsZU5hbWUiOiJUaGVNb2RlOTExIiwidGV4dHVyZXMiOnsiU0tJTiI6eyJ1cmwiOiJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlL2RkOTE2NzJiNTE0MmJhN2Y3MjA2ZTRjN2IwOTBkNzhlM2Y1ZDc2NDdiNWFmZDIyNjFhZDk4OGM0MWI2ZjcwYTEifX19";
SpawnPlayerPacket spawnPlayerPacket = new SpawnPlayerPacket();
spawnPlayerPacket.entityId = getEntityId();
@ -362,13 +353,17 @@ public class Player extends LivingEntity {
addP.properties.add(p);
pInfoPacket.playerInfos.add(addP);
connection.sendPacket(pInfoPacket);
connection.sendPacket(spawnPlayerPacket);
connection.sendPacket(getMetadataPacket());
viewerConnection.sendPacket(pInfoPacket);
viewerConnection.sendPacket(spawnPlayerPacket);
viewerConnection.sendPacket(getMetadataPacket());
for (EntityEquipmentPacket.Slot slot : EntityEquipmentPacket.Slot.values()) {
player.playerConnection.sendPacket(getEquipmentPacket(slot));
viewerConnection.sendPacket(getEquipmentPacket(slot));
}
// Team
if (team != null)
viewerConnection.sendPacket(team.getTeamsCreationPacket());
}
@Override
@ -376,9 +371,14 @@ public class Player extends LivingEntity {
if (player == this)
return;
super.removeViewer(player);
PlayerConnection viewerConnection = player.getPlayerConnection();
PlayerInfoPacket playerInfoPacket = new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER);
playerInfoPacket.playerInfos.add(new PlayerInfoPacket.RemovePlayer(getUuid()));
player.playerConnection.sendPacket(playerInfoPacket);
viewerConnection.sendPacket(playerInfoPacket);
// Team
if (team != null && team.getPlayers().size() == 1) // If team only contains "this" player
viewerConnection.sendPacket(team.createTeamDestructionPacket());
}
@Override
@ -692,6 +692,36 @@ public class Player extends LivingEntity {
refreshHeldSlot(slot);
}
public void setTeam(Team team) {
if (this.team == team)
return;
if (this.team != null) {
this.team.removePlayer(this);
}
this.team = team;
if (team != null) {
team.addPlayer(this);
sendPacketToViewers(team.getTeamsCreationPacket()); // FIXME: only if viewer hasn't already register this team
}
}
public void setBelowNameScoreboard(BelowNameScoreboard belowNameScoreboard) {
if (this.belowNameScoreboard == belowNameScoreboard)
return;
if (this.belowNameScoreboard != null) {
this.belowNameScoreboard.removeViewer(this);
}
this.belowNameScoreboard = belowNameScoreboard;
if (belowNameScoreboard != null) {
belowNameScoreboard.addViewer(this);
getViewers().forEach(player -> belowNameScoreboard.addViewer(player));
}
}
public short getHeldSlot() {
return heldSlot;
}

View File

@ -8,10 +8,12 @@ import fr.themode.minestom.entity.Player;
import fr.themode.minestom.instance.block.BlockManager;
import fr.themode.minestom.instance.block.CustomBlock;
import fr.themode.minestom.instance.block.UpdateConsumer;
import fr.themode.minestom.instance.block.UpdateOption;
import fr.themode.minestom.net.packet.server.play.ChunkDataPacket;
import fr.themode.minestom.utils.BlockPosition;
import fr.themode.minestom.utils.PacketUtils;
import fr.themode.minestom.utils.SerializerUtils;
import fr.themode.minestom.utils.time.CooldownUtils;
import it.unimi.dsi.fastutil.ints.*;
import java.io.ByteArrayOutputStream;
@ -43,6 +45,8 @@ public class Chunk implements Viewable {
// Contains CustomBlocks' index which are updatable
private IntSet updatableBlocks = new IntOpenHashSet();
// (block index)/(last update in ms)
private Int2LongMap updatableBlocksLastUpdate = new Int2LongOpenHashMap();
protected volatile boolean packetUpdated;
@ -89,7 +93,7 @@ public class Chunk implements Viewable {
private void setBlock(byte x, byte y, byte z, short blockType, short customId, Data data, UpdateConsumer updateConsumer) {
int index = SerializerUtils.chunkCoordToIndex(x, y, z);
if (blockType != 0 || customId != 0) {
int value = (blockType << 16 | customId & 0xFFFF);
int value = (blockType << 16 | customId & 0xFFFF); // Merge blockType and customId to one unique Integer (16/16)
this.blocks.put(index, value);
} else {
// Block has been deleted
@ -106,8 +110,10 @@ public class Chunk implements Viewable {
// Set update consumer
if (updateConsumer != null) {
this.updatableBlocks.add(index);
this.updatableBlocksLastUpdate.put(index, System.currentTimeMillis());
} else {
this.updatableBlocks.rem(index);
this.updatableBlocks.remove(index);
this.updatableBlocksLastUpdate.remove(index);
}
if (isBlockEntity(blockType)) {
@ -159,8 +165,12 @@ public class Chunk implements Viewable {
}
public void updateBlocks(long time, Instance instance) {
if (updatableBlocks.isEmpty())
return;
// Block all chunk operation during the update
synchronized (this) {
IntIterator iterator = updatableBlocks.iterator();
IntIterator iterator = new IntOpenHashSet(updatableBlocks).iterator();
while (iterator.hasNext()) {
int index = iterator.nextInt();
byte[] blockPos = SerializerUtils.indexToChunkPosition(index);
@ -168,9 +178,17 @@ public class Chunk implements Viewable {
byte y = blockPos[1];
byte z = blockPos[2];
CustomBlock customBlock = getCustomBlock(x, y, z);
BlockPosition blockPosition = new BlockPosition(x * chunkX, y, z * chunkZ);
// Update cooldown
UpdateOption updateOption = customBlock.getUpdateOption();
long lastUpdate = updatableBlocksLastUpdate.get(index);
boolean shouldUpdate = !CooldownUtils.hasCooldown(time, lastUpdate, updateOption.getTimeUnit(), updateOption.getValue());
if (!shouldUpdate)
continue;
this.updatableBlocksLastUpdate.put(index, time); // Refresh last update time
BlockPosition blockPosition = new BlockPosition(x + 16 * chunkX, y, z + 16 * chunkZ);
Data data = getData(index);
// TODO should customBlock be updated?
customBlock.update(instance, blockPosition, data);
}
}

View File

@ -45,6 +45,8 @@ public class InstanceContainer extends Instance {
byte chunkY = (byte) y;
byte chunkZ = (byte) (z % 16);
chunk.UNSAFE_setBlock(chunkX, chunkY, chunkZ, blockId, data);
// TODO instead of sending a block change packet each time, cache changed blocks and flush them every tick with a MultiBlockChangePacket
sendBlockChange(chunk, x, y, z, blockId);
}
}
@ -58,6 +60,8 @@ public class InstanceContainer extends Instance {
byte chunkZ = (byte) (z % 16);
chunk.UNSAFE_setCustomBlock(chunkX, chunkY, chunkZ, blockId, data);
short id = BLOCK_MANAGER.getBlock(blockId).getType();
// TODO instead of sending a block change packet each time, cache changed blocks and flush them every tick with a MultiBlockChangePacket
sendBlockChange(chunk, x, y, z, id);
}
}

View File

@ -1,13 +1,19 @@
package fr.themode.minestom.instance;
import fr.themode.minestom.Main;
import fr.themode.minestom.utils.thread.MinestomThread;
import java.io.File;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
public class InstanceManager {
private ExecutorService blocksPool = new MinestomThread(Main.THREAD_COUNT_BLOCK_UPDATE, "Ms-BlockUpdatePool");
private Set<Instance> instances = Collections.synchronizedSet(new HashSet<>());
public InstanceContainer createInstanceContainer(File folder) {
@ -30,6 +36,22 @@ public class InstanceManager {
return sharedInstance;
}
public void updateBlocks() {
if (instances.isEmpty())
return;
long time = System.currentTimeMillis();
blocksPool.execute(() -> {
for (Instance instance : instances) {
if (instance instanceof InstanceContainer) { // SharedInstance should be updated at the same time (verify?)
for (Chunk chunk : instance.getChunks()) {
chunk.updateBlocks(time, instance);
}
}
}
});
}
public Set<Instance> getInstances() {
return Collections.unmodifiableSet(instances);
}

View File

@ -1,42 +1,24 @@
package fr.themode.minestom.instance.block;
import fr.themode.minestom.Main;
import fr.themode.minestom.instance.Chunk;
import fr.themode.minestom.instance.Instance;
import fr.themode.minestom.instance.InstanceManager;
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class BlockManager {
private static InstanceManager instanceManager = Main.getInstanceManager();
private Short2ObjectMap<CustomBlock> blocksInternalId = new Short2ObjectOpenHashMap<>();
private Map<String, CustomBlock> blocksId = new HashMap<>();
public void registerBlock(Supplier<CustomBlock> blocks) {
CustomBlock customBlock = blocks.get();
public void registerBlock(CustomBlock block) {
CustomBlock customBlock = block;
String identifier = customBlock.getIdentifier();
short id = customBlock.getId();
this.blocksInternalId.put(id, customBlock);
this.blocksId.put(identifier, customBlock);
}
public void update() {
long time = System.currentTimeMillis();
// TODO another thread pool
for (Instance instance : instanceManager.getInstances()) {
// FIXME: only InstanceContainer?
for (Chunk chunk : instance.getChunks()) {
chunk.updateBlocks(time, instance);
}
}
}
public CustomBlock getBlock(String identifier) {
return blocksId.get(identifier);
}

View File

@ -15,29 +15,42 @@ public abstract class CustomBlock {
private static final AtomicInteger idCounter = new AtomicInteger();
private short type;
private String identifier;
private short id;
public CustomBlock() {
public CustomBlock(short type, String identifier) {
this.type = type;
this.identifier = identifier;
this.id = (short) idCounter.incrementAndGet();
}
// TODO add another object parameter which will offer a lot of integrated features (like break animation, id change etc...)
public void update(Instance instance, BlockPosition blockPosition, Data data) {
throw new UnsupportedOperationException("Update method not overriden");
}
public abstract UpdateOption getUpdateOption();
public abstract short getType();
public abstract String getIdentifier();
/*
Time in ms
*/
public abstract int getBreakDelay(Player player);
public boolean hasUpdate() {
return getUpdateOption().getValue() > 0;
UpdateOption updateOption = getUpdateOption();
if (updateOption == null)
return false;
return updateOption.getValue() > 0;
}
public short getType() {
return type;
}
public String getIdentifier() {
return identifier;
}
public short getId() {

View File

@ -6,7 +6,5 @@ import fr.themode.minestom.utils.BlockPosition;
@FunctionalInterface
public interface UpdateConsumer {
void update(Instance instance, BlockPosition blockPosition, Data data);
}

View File

@ -1,6 +1,6 @@
package fr.themode.minestom.instance.block;
import fr.themode.minestom.timer.TimeUnit;
import fr.themode.minestom.utils.time.TimeUnit;
public class UpdateOption {

View File

@ -1,6 +1,5 @@
package fr.themode.minestom.instance.demo;
import fr.themode.minestom.data.Data;
import fr.themode.minestom.instance.Biome;
import fr.themode.minestom.instance.ChunkGenerator;
import fr.themode.minestom.instance.batch.ChunkBatch;
@ -16,8 +15,8 @@ public class ChunkGeneratorDemo extends ChunkGenerator {
for (byte x = 0; x < 16; x++)
for (byte z = 0; z < 16; z++) {
for (byte y = 0; y < 65; y++) {
if (random.nextInt(100) > 90) {
batch.setCustomBlock(x, y, z, "custom_block", new Data());
if (random.nextInt(100) > 10) {
batch.setCustomBlock(x, y, z, "custom_block");
} else {
batch.setBlock(x, y, z, (short) 10);
}

View File

@ -1,42 +1,18 @@
package fr.themode.minestom.instance.demo;
import fr.themode.minestom.data.Data;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.instance.Instance;
import fr.themode.minestom.instance.block.CustomBlock;
import fr.themode.minestom.instance.block.UpdateOption;
import fr.themode.minestom.timer.TimeUnit;
import fr.themode.minestom.utils.BlockPosition;
import java.util.concurrent.atomic.AtomicInteger;
public class StoneBlock extends CustomBlock {
private static final UpdateOption UPDATE_OPTION = new UpdateOption(1, TimeUnit.TICK);
private final AtomicInteger counter = new AtomicInteger();
public StoneBlock() {
super((short) 1, "custom_block");
}
@Override
public UpdateOption getUpdateOption() {
return UPDATE_OPTION;
}
@Override
public void update(Instance instance, BlockPosition blockPosition, Data data) {
if (data == null)
return;
data.set("value", counter.incrementAndGet(), int.class);
}
@Override
public short getType() {
return 1;
}
@Override
public String getIdentifier() {
return "custom_block";
return null;
}
@Override

View File

@ -0,0 +1,33 @@
package fr.themode.minestom.instance.demo;
import fr.themode.minestom.data.Data;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.instance.Instance;
import fr.themode.minestom.instance.block.CustomBlock;
import fr.themode.minestom.instance.block.UpdateOption;
import fr.themode.minestom.utils.BlockPosition;
import fr.themode.minestom.utils.time.TimeUnit;
public class UpdatableBlockDemo extends CustomBlock {
private static final UpdateOption UPDATE_OPTION = new UpdateOption(20, TimeUnit.TICK);
public UpdatableBlockDemo() {
super((short) 11, "updatable");
}
@Override
public void update(Instance instance, BlockPosition blockPosition, Data data) {
System.out.println("BLOCK UPDATE");
}
@Override
public UpdateOption getUpdateOption() {
return UPDATE_OPTION;
}
@Override
public int getBreakDelay(Player player) {
return 500;
}
}

View File

@ -37,10 +37,7 @@ public class DataReader {
}
int valueLength = stream.readInt();
byte[] valueCache = new byte[valueLength];
for (int i = 0; i < valueLength; i++) {
valueCache[i] = stream.readByte();
}
byte[] valueCache = stream.readNBytes(valueLength);
Class type = Class.forName(new String(typeCache));

View File

@ -42,7 +42,7 @@ public class BlockPlacementListener {
player.callEvent(PlayerBlockPlaceEvent.class, playerBlockPlaceEvent);
if (!playerBlockPlaceEvent.isCancelled()) {
instance.setCustomBlock(blockPosition, "custom_block"); // TODO set useItem's block instead
instance.setCustomBlock(blockPosition, "updatable"); // TODO set useItem's block instead
if (playerBlockPlaceEvent.doesConsumeBlock()) {
usedItem.setAmount((byte) (usedItem.getAmount() - 1));
if (usedItem.getAmount() <= 0)

View File

@ -13,13 +13,13 @@ import fr.themode.minestom.net.packet.client.handshake.HandshakePacket;
import fr.themode.minestom.net.player.PlayerConnection;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class PacketProcessor {
private Map<Client, PlayerConnection> connectionPlayerConnectionMap = new HashMap<>();
private Map<Client, PlayerConnection> connectionPlayerConnectionMap = new ConcurrentHashMap<>();
private ConnectionManager connectionManager;

View File

@ -14,11 +14,7 @@ public class RespawnPacket implements ServerPacket {
@Override
public void write(PacketWriter writer) {
int gameModeId = gameMode.getId();
if (gameMode.isHardcore())
gameModeId |= 8;
writer.writeByte((byte) gameModeId);
writer.writeByte((byte) gameMode.getId()); // Hardcore flag not included
writer.writeInt(dimension.getId());
writer.writeSizedString(levelType.getType());
}

View File

@ -11,12 +11,11 @@ public class TeamsPacket implements ServerPacket {
public String teamDisplayName;
public byte friendlyFlags;
public String nameTagVisibility;
public String collisionRule;
public NameTagVisibility nameTagVisibility;
public CollisionRule collisionRule;
public int teamColor;
public String teamPrefix;
public String teamSuffix;
public int entityCount;
public String[] entities;
@Override
@ -29,8 +28,8 @@ public class TeamsPacket implements ServerPacket {
case UPDATE_TEAM_INFO:
writer.writeSizedString(Chat.legacyTextString(teamDisplayName));
writer.writeByte(friendlyFlags);
writer.writeSizedString(nameTagVisibility);
writer.writeSizedString(collisionRule);
writer.writeSizedString(nameTagVisibility.getIdentifier());
writer.writeSizedString(collisionRule.getIdentifier());
writer.writeVarInt(teamColor);
writer.writeSizedString(Chat.legacyTextString(teamPrefix));
writer.writeSizedString(Chat.legacyTextString(teamSuffix));
@ -59,4 +58,38 @@ public class TeamsPacket implements ServerPacket {
REMOVE_PLAYERS_TEAM;
}
public enum NameTagVisibility {
ALWAYS("always"),
HIDE_FOR_OTHER_TEAMS("hideForOtherTeams"),
HIDE_FOR_OWN_TEAM("hideForOwnTeam"),
NEVER("never");
private String identifier;
NameTagVisibility(String identifier) {
this.identifier = identifier;
}
public String getIdentifier() {
return identifier;
}
}
public enum CollisionRule {
ALWAYS("always"),
PUSH_OTHER_TEAMS("pushOtherTeams"),
PUSH_OWN_TEAM("pushOwnTeam"),
NEVER("never");
private String identifier;
CollisionRule(String identifier) {
this.identifier = identifier;
}
public String getIdentifier() {
return identifier;
}
}
}

View File

@ -0,0 +1,61 @@
package fr.themode.minestom.scoreboard;
import fr.themode.minestom.Viewable;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.net.packet.server.play.DisplayScoreboardPacket;
import fr.themode.minestom.net.packet.server.play.ScoreboardObjectivePacket;
import fr.themode.minestom.net.player.PlayerConnection;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
public class BelowNameScoreboard implements Viewable {
private static final AtomicInteger counter = new AtomicInteger();
// WARNING: you shouldn't create scoreboards/teams with the same prefixes as those
private static final String SCOREBOARD_PREFIX = "bn-";
private static final String TEAM_PREFIX = "bnt-";
private Set<Player> viewers = new CopyOnWriteArraySet<>();
private String objectiveName;
private ScoreboardObjectivePacket scoreboardObjectivePacket;
private DisplayScoreboardPacket displayScoreboardPacket;
public BelowNameScoreboard() {
this.objectiveName = SCOREBOARD_PREFIX + counter.incrementAndGet();
System.out.println("DEBUG: " + objectiveName);
scoreboardObjectivePacket = new ScoreboardObjectivePacket();
scoreboardObjectivePacket.objectiveName = objectiveName;
scoreboardObjectivePacket.mode = 0;
scoreboardObjectivePacket.objectiveValue = "test:" + objectiveName;
scoreboardObjectivePacket.type = 0;
displayScoreboardPacket = new DisplayScoreboardPacket();
displayScoreboardPacket.position = 2;
displayScoreboardPacket.scoreName = objectiveName;
}
@Override
public void addViewer(Player player) {
this.viewers.add(player);
PlayerConnection playerConnection = player.getPlayerConnection();
playerConnection.sendPacket(scoreboardObjectivePacket);
// TODO score
playerConnection.sendPacket(displayScoreboardPacket);
}
@Override
public void removeViewer(Player player) {
this.viewers.remove(player);
}
@Override
public Set<Player> getViewers() {
return Collections.unmodifiableSet(viewers);
}
}

View File

@ -16,11 +16,15 @@ import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
// TODO update tick
public class Scoreboard implements Viewable {
public class Sidebar implements Viewable {
private static final AtomicInteger counter = new AtomicInteger();
// WARNING: you shouldn't create scoreboards/teams with the same prefixes as those
private static final String SCOREBOARD_PREFIX = "sb-";
private static final String TEAM_PREFIX = "sbt-";
// Limited by notchian client, do not change
private static final int MAX_LINES_COUNT = 15;
private Set<Player> viewers = new CopyOnWriteArraySet<>();
@ -32,7 +36,7 @@ public class Scoreboard implements Viewable {
private String title;
public Scoreboard(String title) {
public Sidebar(String title) {
this.title = title;
this.objectiveName = SCOREBOARD_PREFIX + counter.incrementAndGet();
@ -64,7 +68,7 @@ public class Scoreboard implements Viewable {
this.lines.add(scoreboardLine);
// Send to current viewers
sendPacketsToViewers(scoreboardLine.team.getCreationPacket(), scoreboardLine.getScoreCreationPacket(objectiveName));
sendPacketsToViewers(scoreboardLine.sidebarTeam.getCreationPacket(), scoreboardLine.getScoreCreationPacket(objectiveName));
}
}
@ -72,7 +76,7 @@ public class Scoreboard implements Viewable {
for (ScoreboardLine line : lines) {
if (line.id.equals(id)) {
line.refreshContent(content);
sendPacketToViewers(line.team.updatePrefix(content));
sendPacketToViewers(line.sidebarTeam.updatePrefix(content));
}
}
}
@ -102,7 +106,7 @@ public class Scoreboard implements Viewable {
if (line.id.equals(id)) {
// Remove the line for current viewers
sendPacketsToViewers(line.getScoreCreationPacket(objectiveName), line.team.getDestructionPacket());
sendPacketsToViewers(line.getScoreCreationPacket(objectiveName), line.sidebarTeam.getDestructionPacket());
line.returnName(availableColors);
iterator.remove();
@ -130,7 +134,7 @@ public class Scoreboard implements Viewable {
playerConnection.sendPacket(displayScoreboardPacket); // Show sidebar scoreboard (wait for scores packet)
for (ScoreboardLine line : lines) {
playerConnection.sendPacket(line.team.getCreationPacket());
playerConnection.sendPacket(line.sidebarTeam.getCreationPacket());
playerConnection.sendPacket(line.getScoreCreationPacket(objectiveName));
}
}
@ -146,7 +150,7 @@ public class Scoreboard implements Viewable {
for (ScoreboardLine line : lines) {
playerConnection.sendPacket(line.getScoreDestructionPacket(objectiveName)); // Is it necessary?
playerConnection.sendPacket(line.team.getDestructionPacket());
playerConnection.sendPacket(line.sidebarTeam.getDestructionPacket());
}
}
@ -164,7 +168,7 @@ public class Scoreboard implements Viewable {
private String teamName;
private int colorName; // Name of the score (entityName) which is essentially an ID
private String entityName;
private Team team;
private SidebarTeam sidebarTeam;
public ScoreboardLine(String id, String content, int line) {
this.id = id;
@ -179,7 +183,7 @@ public class Scoreboard implements Viewable {
}
public String getContent() {
return team == null ? content : team.getPrefix();
return sidebarTeam == null ? content : sidebarTeam.getPrefix();
}
public int getLine() {
@ -195,7 +199,7 @@ public class Scoreboard implements Viewable {
private void createTeam() {
this.entityName = Chat.COLOR_CHAR + Integer.toHexString(colorName);
this.team = new Team(teamName, content, "", entityName);
this.sidebarTeam = new SidebarTeam(teamName, content, "", entityName);
}
private void returnName(LinkedList<Integer> colors) {
@ -228,7 +232,7 @@ public class Scoreboard implements Viewable {
}
private void refreshContent(String content) {
this.team.refreshPrefix(content);
this.sidebarTeam.refreshPrefix(content);
}
}

View File

@ -0,0 +1,72 @@
package fr.themode.minestom.scoreboard;
import fr.themode.minestom.net.packet.server.play.TeamsPacket;
public class SidebarTeam {
private String teamName;
private String prefix, suffix;
private String entityName;
private String teamDisplayName = "displaynametest";
private byte friendlyFlags = 0x00;
private TeamsPacket.NameTagVisibility nameTagVisibility = TeamsPacket.NameTagVisibility.NEVER;
private TeamsPacket.CollisionRule collisionRule = TeamsPacket.CollisionRule.NEVER;
private int teamColor = 2;
protected SidebarTeam(String teamName, String prefix, String suffix, String entityName) {
this.teamName = teamName;
this.prefix = prefix;
this.suffix = suffix;
this.entityName = entityName;
}
protected TeamsPacket getCreationPacket() {
TeamsPacket teamsPacket = new TeamsPacket();
teamsPacket.teamName = teamName;
teamsPacket.action = TeamsPacket.Action.CREATE_TEAM;
teamsPacket.teamDisplayName = teamDisplayName;
teamsPacket.friendlyFlags = friendlyFlags;
teamsPacket.nameTagVisibility = nameTagVisibility;
teamsPacket.collisionRule = collisionRule;
teamsPacket.teamColor = teamColor;
teamsPacket.teamPrefix = prefix;
teamsPacket.teamSuffix = suffix;
teamsPacket.entities = new String[]{entityName};
return teamsPacket;
}
protected TeamsPacket getDestructionPacket() {
TeamsPacket teamsPacket = new TeamsPacket();
teamsPacket.teamName = teamName;
teamsPacket.action = TeamsPacket.Action.REMOVE_TEAM;
return teamsPacket;
}
protected TeamsPacket updatePrefix(String prefix) {
TeamsPacket teamsPacket = new TeamsPacket();
teamsPacket.teamName = teamName;
teamsPacket.action = TeamsPacket.Action.UPDATE_TEAM_INFO;
teamsPacket.teamDisplayName = teamDisplayName;
teamsPacket.friendlyFlags = friendlyFlags;
teamsPacket.nameTagVisibility = nameTagVisibility;
teamsPacket.collisionRule = collisionRule;
teamsPacket.teamColor = teamColor;
teamsPacket.teamPrefix = prefix;
teamsPacket.teamSuffix = suffix;
return teamsPacket;
}
protected String getEntityName() {
return entityName;
}
protected String getPrefix() {
return prefix;
}
protected void refreshPrefix(String prefix) {
this.prefix = prefix;
}
}

View File

@ -1,72 +1,160 @@
package fr.themode.minestom.scoreboard;
import com.github.simplenet.packet.Packet;
import fr.themode.minestom.chat.ChatColor;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.net.packet.server.play.TeamsPacket;
import fr.themode.minestom.utils.PacketUtils;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
public class Team {
private String teamName;
private String prefix, suffix;
private String entityName;
private String teamDisplayName = "displaynametest";
private String teamDisplayName = "";
private byte friendlyFlags = 0x00;
private String nameTagVisibility = "never";
private String collisionRule = "never";
private int teamColor = 2;
private TeamsPacket.NameTagVisibility nameTagVisibility = TeamsPacket.NameTagVisibility.ALWAYS;
private TeamsPacket.CollisionRule collisionRule = TeamsPacket.CollisionRule.NEVER;
private ChatColor teamColor = ChatColor.WHITE;
private String prefix = "", suffix = "";
private String[] entities = new String[0];
private Set<Player> players = new CopyOnWriteArraySet<>();
private TeamsPacket teamsCreationPacket;
protected Team(String teamName, String prefix, String suffix, String entityName) {
private Packet teamsDestroyPacket;
protected Team(String teamName) {
this.teamName = teamName;
teamsCreationPacket = new TeamsPacket();
teamsCreationPacket.teamName = teamName;
teamsCreationPacket.action = TeamsPacket.Action.CREATE_TEAM;
teamsCreationPacket.teamDisplayName = teamDisplayName;
teamsCreationPacket.friendlyFlags = friendlyFlags;
teamsCreationPacket.nameTagVisibility = nameTagVisibility;
teamsCreationPacket.collisionRule = collisionRule;
teamsCreationPacket.teamColor = teamColor.getId();
teamsCreationPacket.teamPrefix = prefix;
teamsCreationPacket.teamSuffix = suffix;
teamsCreationPacket.entities = entities;
TeamsPacket destroyPacket = new TeamsPacket();
destroyPacket.teamName = teamName;
destroyPacket.action = TeamsPacket.Action.REMOVE_TEAM;
PacketUtils.writePacket(destroyPacket, packet -> teamsDestroyPacket = packet); // Directly write packet since it will not change
}
public void addPlayer(Player player) {
String newElement = player.getUsername();
TeamsPacket addPlayerPacket = new TeamsPacket();
addPlayerPacket.teamName = teamName;
addPlayerPacket.action = TeamsPacket.Action.ADD_PLAYERS_TEAM;
addPlayerPacket.entities = new String[]{newElement};
for (Player p : players) {
p.getPlayerConnection().sendPacket(addPlayerPacket);
}
String[] entitiesCache = new String[entities.length + 1];
System.arraycopy(entities, 0, entitiesCache, 0, entities.length);
entitiesCache[entities.length] = newElement;
this.entities = entitiesCache;
this.teamsCreationPacket.entities = entities;
this.players.add(player);
player.getPlayerConnection().sendPacket(teamsCreationPacket);
}
public void removePlayer(Player player) {
TeamsPacket removePlayerPacket = new TeamsPacket();
removePlayerPacket.teamName = teamName;
removePlayerPacket.action = TeamsPacket.Action.REMOVE_PLAYERS_TEAM;
removePlayerPacket.entities = new String[]{player.getUsername()};
for (Player p : players) {
p.getPlayerConnection().sendPacket(removePlayerPacket);
}
this.players.remove(player);
player.getPlayerConnection().sendPacket(teamsDestroyPacket); // TODO do not destroy, simply remove the player from the team
String[] entitiesCache = new String[entities.length - 1];
int count = 0;
for (Player p : players) {
entitiesCache[count++] = p.getUsername();
}
this.entities = entitiesCache;
this.teamsCreationPacket.entities = entities;
}
public void setTeamDisplayName(String teamDisplayName) {
this.teamDisplayName = teamDisplayName;
this.teamsCreationPacket.teamDisplayName = teamDisplayName;
sendUpdatePacket();
}
public void setNameTagVisibility(TeamsPacket.NameTagVisibility nameTagVisibility) {
this.nameTagVisibility = nameTagVisibility;
this.teamsCreationPacket.nameTagVisibility = nameTagVisibility;
sendUpdatePacket();
}
public void setCollisionRule(TeamsPacket.CollisionRule collisionRule) {
this.collisionRule = collisionRule;
this.teamsCreationPacket.collisionRule = collisionRule;
sendUpdatePacket();
}
public void setTeamColor(ChatColor teamColor) {
this.teamColor = teamColor;
this.teamsCreationPacket.teamColor = teamColor.getId();
sendUpdatePacket();
}
public void setPrefix(String prefix) {
this.prefix = prefix;
this.teamsCreationPacket.teamPrefix = prefix;
sendUpdatePacket();
}
public void setSuffix(String suffix) {
this.suffix = suffix;
this.entityName = entityName;
this.teamsCreationPacket.teamSuffix = suffix;
sendUpdatePacket();
}
protected TeamsPacket getCreationPacket() {
TeamsPacket teamsPacket = new TeamsPacket();
teamsPacket.teamName = teamName;
teamsPacket.action = TeamsPacket.Action.CREATE_TEAM;
teamsPacket.teamDisplayName = teamDisplayName;
teamsPacket.friendlyFlags = friendlyFlags;
teamsPacket.nameTagVisibility = nameTagVisibility;
teamsPacket.collisionRule = collisionRule;
teamsPacket.teamColor = teamColor;
teamsPacket.teamPrefix = prefix;
teamsPacket.teamSuffix = suffix;
teamsPacket.entities = new String[]{entityName};
return teamsPacket;
public String getTeamName() {
return teamName;
}
protected TeamsPacket getDestructionPacket() {
public TeamsPacket getTeamsCreationPacket() {
return teamsCreationPacket;
}
public TeamsPacket createTeamDestructionPacket() {
TeamsPacket teamsPacket = new TeamsPacket();
teamsPacket.teamName = teamName;
teamsPacket.action = TeamsPacket.Action.REMOVE_TEAM;
return teamsPacket;
}
protected TeamsPacket updatePrefix(String prefix) {
TeamsPacket teamsPacket = new TeamsPacket();
teamsPacket.teamName = teamName;
teamsPacket.action = TeamsPacket.Action.UPDATE_TEAM_INFO;
teamsPacket.teamDisplayName = teamDisplayName;
teamsPacket.friendlyFlags = friendlyFlags;
teamsPacket.nameTagVisibility = nameTagVisibility;
teamsPacket.collisionRule = collisionRule;
teamsPacket.teamColor = teamColor;
teamsPacket.teamPrefix = prefix;
teamsPacket.teamSuffix = suffix;
return teamsPacket;
public Set<Player> getPlayers() {
return Collections.unmodifiableSet(players);
}
protected String getEntityName() {
return entityName;
}
protected String getPrefix() {
return prefix;
}
protected void refreshPrefix(String prefix) {
this.prefix = prefix;
private void sendUpdatePacket() {
TeamsPacket updatePacket = new TeamsPacket();
updatePacket.teamName = teamName;
updatePacket.action = TeamsPacket.Action.UPDATE_TEAM_INFO;
updatePacket.teamDisplayName = teamDisplayName;
updatePacket.friendlyFlags = friendlyFlags;
updatePacket.nameTagVisibility = nameTagVisibility;
updatePacket.collisionRule = collisionRule;
updatePacket.teamColor = teamColor.getId();
updatePacket.teamPrefix = prefix;
updatePacket.teamSuffix = suffix;
PacketUtils.writePacket(updatePacket, packet -> players.forEach(p -> p.getPlayerConnection().sendPacket(packet)));
}
}

View File

@ -0,0 +1,20 @@
package fr.themode.minestom.scoreboard;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
public class TeamManager {
// Represents all registered teams
private Set<Team> teams = new CopyOnWriteArraySet<>();
public Team createTeam(String teamName) {
Team team = new Team(teamName);
this.teams.add(team);
return team;
}
public Set<Team> getTeams() {
return teams;
}
}

View File

@ -0,0 +1,11 @@
package fr.themode.minestom.timer;
import fr.themode.minestom.utils.time.TimeUnit;
public class SchedulerManager {
public void addRepeatingTask(Runnable runnable, TimeUnit timeUnit, int time) {
}
}

View File

@ -0,0 +1,14 @@
package fr.themode.minestom.utils.time;
public class CooldownUtils {
public static boolean hasCooldown(long currentTime, long lastUpdate, TimeUnit timeUnit, int cooldown) {
long cooldownMs = timeUnit.toMilliseconds(cooldown);
return currentTime - lastUpdate < cooldownMs;
}
public static boolean hasCooldown(long lastUpdate, TimeUnit timeUnit, int cooldown) {
return hasCooldown(System.currentTimeMillis(), lastUpdate, timeUnit, cooldown);
}
}

View File

@ -1,4 +1,4 @@
package fr.themode.minestom.timer;
package fr.themode.minestom.utils.time;
import fr.themode.minestom.Main;