mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-26 11:07:53 +01:00
Basic chunk serializer & reworked chunk multi-threading
This commit is contained in:
parent
d5d3dab6c7
commit
b933a83c31
@ -16,4 +16,5 @@ dependencies {
|
||||
testCompile group: 'junit', name: 'junit', version: '4.12'
|
||||
implementation 'com.github.Adamaq01:ozao-net:2.3.1'
|
||||
compile 'com.github.Querz:NBT:4.1'
|
||||
implementation 'com.github.luben:zstd-jni:1.4.3-1'
|
||||
}
|
||||
|
@ -23,9 +23,9 @@ public class Main {
|
||||
|
||||
// Thread number
|
||||
public static final int THREAD_COUNT_PACKET_WRITER = 3;
|
||||
public static final int THREAD_COUNT_CHUNK_IO = 2;
|
||||
public static final int THREAD_COUNT_CHUNK_BATCH = 2;
|
||||
public static final int THREAD_COUNT_OBJECTS_ENTITIES = 2;
|
||||
public static final int THREAD_COUNT_CREATURES_ENTITIES = 2;
|
||||
public static final int THREAD_COUNT_ENTITIES = 2;
|
||||
public static final int THREAD_COUNT_PLAYERS_ENTITIES = 2;
|
||||
|
||||
public static final int TICK_MS = 50;
|
||||
|
21
src/main/java/fr/themode/minestom/data/Data.java
Normal file
21
src/main/java/fr/themode/minestom/data/Data.java
Normal file
@ -0,0 +1,21 @@
|
||||
package fr.themode.minestom.data;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class Data {
|
||||
|
||||
private ConcurrentHashMap<String, Object> data = new ConcurrentHashMap();
|
||||
private ConcurrentHashMap<String, DataType> dataType = new ConcurrentHashMap<>();
|
||||
|
||||
public <T> void set(String key, T value, DataType<T> type) {
|
||||
this.data.put(key, value);
|
||||
this.dataType.put(key, type);
|
||||
}
|
||||
|
||||
public <T> T get(String key) {
|
||||
return (T) data.get(key);
|
||||
}
|
||||
|
||||
// TODO serialize
|
||||
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package fr.themode.minestom.data;
|
||||
|
||||
public interface DataContainer {
|
||||
|
||||
Data getData();
|
||||
|
||||
void setData(Data data);
|
||||
|
||||
}
|
15
src/main/java/fr/themode/minestom/data/DataType.java
Normal file
15
src/main/java/fr/themode/minestom/data/DataType.java
Normal file
@ -0,0 +1,15 @@
|
||||
package fr.themode.minestom.data;
|
||||
|
||||
import fr.themode.minestom.data.type.IntegerData;
|
||||
|
||||
public abstract class DataType<T> {
|
||||
|
||||
public static final DataType INTEGER = new IntegerData();
|
||||
|
||||
public abstract byte[] encode(T value);
|
||||
|
||||
public abstract T decode(byte[] value);
|
||||
|
||||
// TODO get object type class ?
|
||||
|
||||
}
|
17
src/main/java/fr/themode/minestom/data/type/IntegerData.java
Normal file
17
src/main/java/fr/themode/minestom/data/type/IntegerData.java
Normal file
@ -0,0 +1,17 @@
|
||||
package fr.themode.minestom.data.type;
|
||||
|
||||
import fr.themode.minestom.data.DataType;
|
||||
import fr.themode.minestom.utils.SerializerUtils;
|
||||
|
||||
public class IntegerData extends DataType<Integer> {
|
||||
|
||||
@Override
|
||||
public byte[] encode(Integer value) {
|
||||
return SerializerUtils.intToBytes(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer decode(byte[] value) {
|
||||
return SerializerUtils.bytesToInt(value);
|
||||
}
|
||||
}
|
@ -3,6 +3,8 @@ package fr.themode.minestom.entity;
|
||||
import fr.adamaq01.ozao.net.Buffer;
|
||||
import fr.themode.minestom.Main;
|
||||
import fr.themode.minestom.Viewable;
|
||||
import fr.themode.minestom.data.Data;
|
||||
import fr.themode.minestom.data.DataContainer;
|
||||
import fr.themode.minestom.event.Callback;
|
||||
import fr.themode.minestom.event.CancellableEvent;
|
||||
import fr.themode.minestom.event.Event;
|
||||
@ -17,7 +19,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
public abstract class Entity implements Viewable {
|
||||
public abstract class Entity implements Viewable, DataContainer {
|
||||
|
||||
private static Map<Integer, Entity> entityById = new HashMap<>();
|
||||
private static AtomicInteger lastEntityId = new AtomicInteger();
|
||||
@ -41,6 +43,7 @@ public abstract class Entity implements Viewable {
|
||||
protected Entity vehicle;
|
||||
private Map<Class<Event>, Callback> eventCallbacks = new ConcurrentHashMap<>();
|
||||
private Set<Player> viewers = new CopyOnWriteArraySet<>();
|
||||
private Data data;
|
||||
private Set<Entity> passengers = new CopyOnWriteArraySet<>();
|
||||
|
||||
protected UUID uuid;
|
||||
@ -124,7 +127,20 @@ public abstract class Entity implements Viewable {
|
||||
return Collections.unmodifiableSet(viewers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Data getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setData(Data data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public void tick() {
|
||||
if (instance == null)
|
||||
return;
|
||||
|
||||
if (scheduledRemoveTime != 0) { // Any entity with scheduled remove does not update
|
||||
boolean finished = System.currentTimeMillis() >= scheduledRemoveTime;
|
||||
if (finished) {
|
||||
@ -132,10 +148,18 @@ public abstract class Entity implements Viewable {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (shouldUpdate()) {
|
||||
|
||||
if (shouldRemove()) {
|
||||
remove();
|
||||
return;
|
||||
} else if (shouldUpdate()) {
|
||||
update();
|
||||
this.lastUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
if (shouldRemove()) {
|
||||
remove();
|
||||
}
|
||||
}
|
||||
|
||||
public <E extends Event> void setEventCallback(Class<E> eventClass, Callback<E> callback) {
|
||||
@ -286,6 +310,7 @@ public abstract class Entity implements Viewable {
|
||||
synchronized (entityById) {
|
||||
entityById.remove(id);
|
||||
}
|
||||
instance.removeEntity(this);
|
||||
}
|
||||
|
||||
public void scheduleRemove(long delay) {
|
||||
|
@ -6,8 +6,11 @@ import fr.themode.minestom.net.packet.server.play.SpawnMobPacket;
|
||||
import fr.themode.minestom.net.player.PlayerConnection;
|
||||
import fr.themode.minestom.utils.Position;
|
||||
|
||||
// TODO viewers synchronization each X ticks?
|
||||
public abstract class EntityCreature extends LivingEntity {
|
||||
|
||||
protected boolean isDead;
|
||||
|
||||
public EntityCreature(int entityType) {
|
||||
super(entityType);
|
||||
}
|
||||
@ -33,6 +36,7 @@ public abstract class EntityCreature extends LivingEntity {
|
||||
}
|
||||
|
||||
public void kill() {
|
||||
this.isDead = true;
|
||||
triggerStatus((byte) 3);
|
||||
scheduleRemove(1000);
|
||||
}
|
||||
@ -54,4 +58,8 @@ public abstract class EntityCreature extends LivingEntity {
|
||||
playerConnection.sendPacket(spawnMobPacket);
|
||||
playerConnection.sendPacket(getMetadataPacket());
|
||||
}
|
||||
|
||||
public boolean isDead() {
|
||||
return isDead;
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
package fr.themode.minestom.entity;
|
||||
|
||||
import fr.themode.minestom.Main;
|
||||
import fr.themode.minestom.event.PlayerLoginEvent;
|
||||
import fr.themode.minestom.event.PlayerSpawnPacket;
|
||||
import fr.themode.minestom.instance.Chunk;
|
||||
import fr.themode.minestom.instance.Instance;
|
||||
import fr.themode.minestom.instance.InstanceManager;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
@ -13,50 +16,51 @@ public class EntityManager {
|
||||
|
||||
private static InstanceManager instanceManager = Main.getInstanceManager();
|
||||
|
||||
private ExecutorService objectsPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_OBJECTS_ENTITIES);
|
||||
private ExecutorService creaturesPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_CREATURES_ENTITIES);
|
||||
private ExecutorService entitiesPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_ENTITIES);
|
||||
private ExecutorService playersPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_PLAYERS_ENTITIES);
|
||||
|
||||
// TODO API for custom thread division (
|
||||
private ConcurrentLinkedQueue<Player> waitingPlayers = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public void update() {
|
||||
waitingPlayersTick();
|
||||
for (Instance instance : instanceManager.getInstances()) {
|
||||
testTick2(instance); // TODO optimize update engine for when there are too many entities on one chunk
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void waitingPlayersTick() {
|
||||
Player waitingPlayer = null;
|
||||
while ((waitingPlayer = waitingPlayers.poll()) != null) {
|
||||
final Player playerCache = waitingPlayer;
|
||||
playersPool.submit(() -> {
|
||||
PlayerLoginEvent loginEvent = new PlayerLoginEvent();
|
||||
playerCache.callEvent(PlayerLoginEvent.class, loginEvent);
|
||||
Instance spawningInstance = loginEvent.getSpawningInstance() == null ? instanceManager.createInstance() : loginEvent.getSpawningInstance();
|
||||
spawningInstance.loadChunk(playerCache.getPosition(), chunk -> {
|
||||
playerCache.spawned = true;
|
||||
playerCache.setInstance(spawningInstance);
|
||||
PlayerSpawnPacket spawnPacket = new PlayerSpawnPacket();
|
||||
playerCache.callEvent(PlayerSpawnPacket.class, spawnPacket);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void testTick2(Instance instance) {
|
||||
|
||||
for (Chunk chunk : instance.getChunks()) {
|
||||
Set<ObjectEntity> objects = chunk.getObjectEntities();
|
||||
Set<EntityCreature> creatures = chunk.getCreatures();
|
||||
Set<Player> players = chunk.getPlayers();
|
||||
|
||||
if (!objects.isEmpty()) {
|
||||
objectsPool.submit(() -> {
|
||||
for (ObjectEntity objectEntity : objects) {
|
||||
boolean shouldRemove = objectEntity.shouldRemove();
|
||||
if (!shouldRemove) {
|
||||
objectEntity.tick();
|
||||
}
|
||||
|
||||
if (objectEntity.shouldRemove()) {
|
||||
instance.removeEntity(objectEntity);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!creatures.isEmpty()) {
|
||||
creaturesPool.submit(() -> {
|
||||
if (!creatures.isEmpty() || !objects.isEmpty()) {
|
||||
entitiesPool.submit(() -> {
|
||||
for (EntityCreature creature : creatures) {
|
||||
boolean shouldRemove = creature.shouldRemove();
|
||||
if (!shouldRemove) {
|
||||
creature.tick();
|
||||
}
|
||||
|
||||
if (creature.shouldRemove()) {
|
||||
instance.removeEntity(creature);
|
||||
}
|
||||
creature.tick();
|
||||
}
|
||||
for (ObjectEntity objectEntity : objects) {
|
||||
objectEntity.tick();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -64,21 +68,18 @@ public class EntityManager {
|
||||
if (!players.isEmpty()) {
|
||||
playersPool.submit(() -> {
|
||||
for (Player player : players) {
|
||||
boolean shouldRemove = player.shouldRemove();
|
||||
if (!shouldRemove) {
|
||||
player.tick();
|
||||
}
|
||||
|
||||
if (player.shouldRemove()) {
|
||||
instance.removeEntity(player);
|
||||
}
|
||||
player.tick();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void testTick(Instance instance) {
|
||||
public void addWaitingPlayer(Player player) {
|
||||
this.waitingPlayers.add(player);
|
||||
}
|
||||
|
||||
/*private void testTick(Instance instance) {
|
||||
// Creatures
|
||||
for (EntityCreature creature : instance.getCreatures()) {
|
||||
creaturesPool.submit(() -> {
|
||||
@ -106,6 +107,6 @@ public class EntityManager {
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ public class ItemEntity extends ObjectEntity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getData() {
|
||||
public int getObjectData() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -3,13 +3,14 @@ package fr.themode.minestom.entity;
|
||||
import fr.themode.minestom.net.packet.server.play.SpawnObjectPacket;
|
||||
import fr.themode.minestom.net.player.PlayerConnection;
|
||||
|
||||
// TODO viewers synchronization each X ticks?
|
||||
public abstract class ObjectEntity extends Entity {
|
||||
|
||||
public ObjectEntity(int entityType) {
|
||||
super(entityType);
|
||||
}
|
||||
|
||||
public abstract int getData();
|
||||
public abstract int getObjectData();
|
||||
|
||||
@Override
|
||||
public void addViewer(Player player) {
|
||||
@ -21,7 +22,7 @@ public abstract class ObjectEntity extends Entity {
|
||||
spawnObjectPacket.uuid = getUuid();
|
||||
spawnObjectPacket.type = getEntityType();
|
||||
spawnObjectPacket.position = getPosition();
|
||||
spawnObjectPacket.data = getData();
|
||||
spawnObjectPacket.data = getObjectData();
|
||||
playerConnection.sendPacket(spawnObjectPacket);
|
||||
playerConnection.sendPacket(getMetadataPacket());
|
||||
}
|
||||
|
@ -1,12 +1,14 @@
|
||||
package fr.themode.minestom.entity;
|
||||
|
||||
import fr.themode.minestom.Main;
|
||||
import fr.themode.minestom.bossbar.BossBar;
|
||||
import fr.themode.minestom.chat.Chat;
|
||||
import fr.themode.minestom.event.AttackEvent;
|
||||
import fr.themode.minestom.event.BlockPlaceEvent;
|
||||
import fr.themode.minestom.event.PickupItemEvent;
|
||||
import fr.themode.minestom.entity.demo.ChickenCreature;
|
||||
import fr.themode.minestom.event.*;
|
||||
import fr.themode.minestom.instance.Chunk;
|
||||
import fr.themode.minestom.instance.CustomBlock;
|
||||
import fr.themode.minestom.instance.Instance;
|
||||
import fr.themode.minestom.instance.demo.ChunkGeneratorDemo;
|
||||
import fr.themode.minestom.inventory.Inventory;
|
||||
import fr.themode.minestom.inventory.PlayerInventory;
|
||||
import fr.themode.minestom.item.ItemStack;
|
||||
@ -19,6 +21,7 @@ import fr.themode.minestom.utils.Position;
|
||||
import fr.themode.minestom.world.Dimension;
|
||||
import fr.themode.minestom.world.LevelType;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
@ -36,6 +39,8 @@ public class Player extends LivingEntity {
|
||||
private Dimension dimension;
|
||||
private GameMode gameMode;
|
||||
private LevelType levelType;
|
||||
// DEBUG
|
||||
private static Instance instance;
|
||||
private PlayerSettings settings;
|
||||
private PlayerInventory inventory;
|
||||
private short heldSlot;
|
||||
@ -56,6 +61,23 @@ public class Player extends LivingEntity {
|
||||
private float sideways;
|
||||
private float forward;
|
||||
|
||||
static {
|
||||
ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo();
|
||||
instance = Main.getInstanceManager().createInstance(new File("C:\\Users\\themo\\OneDrive\\Bureau\\Minestom data"));
|
||||
//instance = Main.getInstanceManager().createInstance();
|
||||
instance.setChunkGenerator(chunkGeneratorDemo);
|
||||
int loopStart = -2;
|
||||
int loopEnd = 2;
|
||||
long time = System.currentTimeMillis();
|
||||
for (int x = loopStart; x < loopEnd; x++)
|
||||
for (int z = loopStart; z < loopEnd; z++) {
|
||||
instance.loadChunk(x, z);
|
||||
}
|
||||
System.out.println("Time to load all chunks: " + (System.currentTimeMillis() - time) + " ms");
|
||||
}
|
||||
|
||||
protected boolean spawned;
|
||||
|
||||
public Player(UUID uuid, String username, PlayerConnection playerConnection) {
|
||||
super(93); // FIXME verify
|
||||
this.uuid = uuid;
|
||||
@ -96,7 +118,39 @@ public class Player extends LivingEntity {
|
||||
});
|
||||
|
||||
setEventCallback(BlockPlaceEvent.class, event -> {
|
||||
sendMessage("Placed block!");
|
||||
/*sendMessage("Placed block! " + event.getHand());
|
||||
Data data = new Data();
|
||||
data.set("test", 5, DataType.INTEGER);
|
||||
setData(data);
|
||||
|
||||
sendMessage("Data: " + getData().get("test"));*/
|
||||
if (event.getHand() != Hand.MAIN)
|
||||
return;
|
||||
|
||||
sendMessage("Save chunk data...");
|
||||
long time = System.currentTimeMillis();
|
||||
getInstance().saveToFolder(() -> {
|
||||
sendMessage("Saved in " + (System.currentTimeMillis() - time) + " ms");
|
||||
});
|
||||
});
|
||||
|
||||
// TODO loginevent set instance
|
||||
setEventCallback(PlayerLoginEvent.class, event -> {
|
||||
System.out.println("PLAYER LOGIN EVENT");
|
||||
event.setSpawningInstance(instance);
|
||||
});
|
||||
|
||||
setEventCallback(PlayerSpawnPacket.class, event -> {
|
||||
System.out.println("TELEPORT");
|
||||
teleport(new Position(0, 66, 0));
|
||||
for (int cx = 0; cx < 4; cx++)
|
||||
for (int cz = 0; cz < 4; cz++) {
|
||||
ChickenCreature chickenCreature = new ChickenCreature();
|
||||
chickenCreature.refreshPosition(0 + (float) cx * 1, 65, 0 + (float) cz * 1);
|
||||
//chickenCreature.setOnFire(true);
|
||||
chickenCreature.setInstance(instance);
|
||||
//chickenCreature.addPassenger(player);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -108,7 +162,7 @@ public class Player extends LivingEntity {
|
||||
}
|
||||
|
||||
// Target block stage
|
||||
if (instance != null && targetCustomBlock != null) {
|
||||
if (targetCustomBlock != null) {
|
||||
int timeBreak = targetCustomBlock.getBreakDelay(this);
|
||||
int animationCount = 10;
|
||||
long since = System.currentTimeMillis() - targetBlockTime;
|
||||
@ -124,33 +178,31 @@ public class Player extends LivingEntity {
|
||||
}
|
||||
|
||||
// Item pickup
|
||||
if (instance != null) {
|
||||
Chunk chunk = instance.getChunkAt(getPosition()); // TODO check surrounding chunks
|
||||
Set<ObjectEntity> objectEntities = chunk.getObjectEntities();
|
||||
for (ObjectEntity objectEntity : objectEntities) {
|
||||
if (objectEntity instanceof ItemEntity) {
|
||||
ItemEntity itemEntity = (ItemEntity) objectEntity;
|
||||
if (!itemEntity.isPickable())
|
||||
continue;
|
||||
float distance = getDistance(objectEntity);
|
||||
if (distance <= 2.04) {
|
||||
synchronized (itemEntity) {
|
||||
if (itemEntity.shouldRemove())
|
||||
continue;
|
||||
ItemStack item = itemEntity.getItemStack();
|
||||
PickupItemEvent pickupItemEvent = new PickupItemEvent(item);
|
||||
callCancellableEvent(PickupItemEvent.class, pickupItemEvent, () -> {
|
||||
boolean result = getInventory().addItemStack(item);
|
||||
if (result) {
|
||||
CollectItemPacket collectItemPacket = new CollectItemPacket();
|
||||
collectItemPacket.collectedEntityId = itemEntity.getEntityId();
|
||||
collectItemPacket.collectorEntityId = getEntityId();
|
||||
collectItemPacket.pickupItemCount = item.getAmount();
|
||||
sendPacketToViewersAndSelf(collectItemPacket);
|
||||
objectEntity.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
Chunk chunk = instance.getChunkAt(getPosition()); // TODO check surrounding chunks
|
||||
Set<ObjectEntity> objectEntities = chunk.getObjectEntities();
|
||||
for (ObjectEntity objectEntity : objectEntities) {
|
||||
if (objectEntity instanceof ItemEntity) {
|
||||
ItemEntity itemEntity = (ItemEntity) objectEntity;
|
||||
if (!itemEntity.isPickable())
|
||||
continue;
|
||||
float distance = getDistance(objectEntity);
|
||||
if (distance <= 2.04) {
|
||||
synchronized (itemEntity) {
|
||||
if (itemEntity.shouldRemove())
|
||||
continue;
|
||||
ItemStack item = itemEntity.getItemStack();
|
||||
PickupItemEvent pickupItemEvent = new PickupItemEvent(item);
|
||||
callCancellableEvent(PickupItemEvent.class, pickupItemEvent, () -> {
|
||||
boolean result = getInventory().addItemStack(item);
|
||||
if (result) {
|
||||
CollectItemPacket collectItemPacket = new CollectItemPacket();
|
||||
collectItemPacket.collectedEntityId = itemEntity.getEntityId();
|
||||
collectItemPacket.collectorEntityId = getEntityId();
|
||||
collectItemPacket.pickupItemCount = item.getAmount();
|
||||
sendPacketToViewersAndSelf(collectItemPacket);
|
||||
objectEntity.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -266,6 +318,14 @@ public class Player extends LivingEntity {
|
||||
player.playerConnection.sendPacket(playerInfoPacket);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInstance(Instance instance) {
|
||||
if (!spawned)
|
||||
throw new IllegalStateException("Player#setInstance is only available during and after PlayerSpawnEvent");
|
||||
|
||||
super.setInstance(instance);
|
||||
}
|
||||
|
||||
public void sendBlockBreakAnimation(BlockPosition blockPosition, byte destroyStage) {
|
||||
BlockBreakAnimationPacket breakAnimationPacket = new BlockBreakAnimationPacket();
|
||||
breakAnimationPacket.entityId = getEntityId() + 1;
|
||||
|
@ -13,7 +13,7 @@ public class TestArrow extends ObjectEntity {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getData() {
|
||||
public int getObjectData() {
|
||||
return shooter.getEntityId() + 1;
|
||||
}
|
||||
|
||||
|
20
src/main/java/fr/themode/minestom/event/AnimationEvent.java
Normal file
20
src/main/java/fr/themode/minestom/event/AnimationEvent.java
Normal file
@ -0,0 +1,20 @@
|
||||
package fr.themode.minestom.event;
|
||||
|
||||
import fr.themode.minestom.entity.Player;
|
||||
|
||||
public class AnimationEvent extends CancellableEvent {
|
||||
|
||||
private Player.Hand hand;
|
||||
|
||||
public AnimationEvent(Player.Hand hand) {
|
||||
this.hand = hand;
|
||||
}
|
||||
|
||||
public Player.Hand getHand() {
|
||||
return hand;
|
||||
}
|
||||
|
||||
public void setHand(Player.Hand hand) {
|
||||
this.hand = hand;
|
||||
}
|
||||
}
|
@ -1,15 +1,18 @@
|
||||
package fr.themode.minestom.event;
|
||||
|
||||
import fr.themode.minestom.entity.Player;
|
||||
import fr.themode.minestom.utils.BlockPosition;
|
||||
|
||||
public class BlockPlaceEvent extends CancellableEvent {
|
||||
|
||||
private short blockId;
|
||||
private BlockPosition blockPosition;
|
||||
private Player.Hand hand;
|
||||
|
||||
public BlockPlaceEvent(short blockId, BlockPosition blockPosition) {
|
||||
public BlockPlaceEvent(short blockId, BlockPosition blockPosition, Player.Hand hand) {
|
||||
this.blockId = blockId;
|
||||
this.blockPosition = blockPosition;
|
||||
this.hand = hand;
|
||||
}
|
||||
|
||||
public short getBlockId() {
|
||||
@ -19,4 +22,8 @@ public class BlockPlaceEvent extends CancellableEvent {
|
||||
public BlockPosition getBlockPosition() {
|
||||
return blockPosition;
|
||||
}
|
||||
|
||||
public Player.Hand getHand() {
|
||||
return hand;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
package fr.themode.minestom.event;
|
||||
|
||||
import fr.themode.minestom.instance.Instance;
|
||||
|
||||
public class PlayerLoginEvent extends Event {
|
||||
|
||||
private Instance spawningInstance;
|
||||
|
||||
public Instance getSpawningInstance() {
|
||||
return spawningInstance;
|
||||
}
|
||||
|
||||
public void setSpawningInstance(Instance instance) {
|
||||
this.spawningInstance = instance;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package fr.themode.minestom.event;
|
||||
|
||||
public class PlayerSpawnPacket extends Event {
|
||||
|
||||
}
|
@ -9,14 +9,20 @@ import fr.themode.minestom.entity.ObjectEntity;
|
||||
import fr.themode.minestom.entity.Player;
|
||||
import fr.themode.minestom.net.packet.server.play.ChunkDataPacket;
|
||||
import fr.themode.minestom.utils.PacketUtils;
|
||||
import fr.themode.minestom.utils.SerializerUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
public class Chunk {
|
||||
|
||||
private static final int CHUNK_SIZE = 16 * 256 * 16;
|
||||
private static final int CHUNK_SIZE_X = 16;
|
||||
private static final int CHUNK_SIZE_Y = 256;
|
||||
private static final int CHUNK_SIZE_Z = 16;
|
||||
private static final int CHUNK_SIZE = CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z;
|
||||
|
||||
protected Set<ObjectEntity> objectEntities = new CopyOnWriteArraySet<>();
|
||||
protected Set<EntityCreature> creatures = new CopyOnWriteArraySet<>();
|
||||
@ -52,8 +58,16 @@ public class Chunk {
|
||||
setBlock(x, y, z, customBlock.getType(), customBlock.getId());
|
||||
}
|
||||
|
||||
protected void setCustomBlock(byte x, byte y, byte z, short customBlockId) {
|
||||
CustomBlock customBlock = Main.getBlockManager().getBlock(customBlockId);
|
||||
if (customBlock == null)
|
||||
throw new IllegalArgumentException("The custom block " + customBlockId + " does not exist or isn't registered");
|
||||
|
||||
setBlock(x, y, z, customBlock.getType(), customBlockId);
|
||||
}
|
||||
|
||||
private void setBlock(byte x, byte y, byte z, short blockType, short customId) {
|
||||
int index = getIndex(x, y, z);
|
||||
int index = SerializerUtils.chunkCoordToIndex(x, y, z);
|
||||
this.blocksId[index] = blockType;
|
||||
this.customBlocks[index] = customId;
|
||||
if (isBlockEntity(blockType)) {
|
||||
@ -64,11 +78,11 @@ public class Chunk {
|
||||
}
|
||||
|
||||
public short getBlockId(byte x, byte y, byte z) {
|
||||
return this.blocksId[getIndex(x, y, z)];
|
||||
return this.blocksId[SerializerUtils.chunkCoordToIndex(x, y, z)];
|
||||
}
|
||||
|
||||
public CustomBlock getCustomBlock(byte x, byte y, byte z) {
|
||||
short id = this.customBlocks[getIndex(x, y, z)];
|
||||
short id = this.customBlocks[SerializerUtils.chunkCoordToIndex(x, y, z)];
|
||||
return id != 0 ? Main.getBlockManager().getBlock(id) : null;
|
||||
}
|
||||
|
||||
@ -151,17 +165,55 @@ public class Chunk {
|
||||
return blockEntities;
|
||||
}
|
||||
|
||||
private int getIndex(byte x, byte y, byte z) {
|
||||
short index = (short) (x & 0x000F);
|
||||
index |= (y << 4) & 0x0FF0;
|
||||
index |= (z << 12) & 0xF000;
|
||||
return index & 0xffff;
|
||||
}
|
||||
|
||||
public void setFullDataPacket(Buffer fullDataPacket) {
|
||||
this.fullDataPacket = fullDataPacket;
|
||||
}
|
||||
|
||||
protected byte[] getSerializedData() throws IOException {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
DataOutputStream dos = new DataOutputStream(output);
|
||||
dos.writeByte(biome.getId());
|
||||
|
||||
// TODO customblock id map (StringId -> short id)
|
||||
// TODO List of (sectionId;blockcount;blocktype;blockarray)
|
||||
|
||||
for (byte x = 0; x < CHUNK_SIZE_X; x++) {
|
||||
for (byte y = -128; y < 127; y++) {
|
||||
for (byte z = 0; z < CHUNK_SIZE_Z; z++) {
|
||||
int index = SerializerUtils.chunkCoordToIndex(x, y, z);
|
||||
boolean isCustomBlock = customBlocks[index] != 0;
|
||||
short id = isCustomBlock ? customBlocks[index] : blocksId[index];
|
||||
if (id != 0) {
|
||||
dos.writeInt(index); // Correspond to chunk coord
|
||||
dos.writeBoolean(isCustomBlock);
|
||||
dos.writeShort(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
byte[] result = output.toByteArray();
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void loadFromFile(File file) throws IOException {
|
||||
System.out.println("LOAD FROM FILE");
|
||||
byte[] array = Files.readAllBytes(file.toPath());
|
||||
DataInputStream stream = new DataInputStream(new ByteArrayInputStream(array));
|
||||
this.chunkX = stream.readInt();
|
||||
this.chunkZ = stream.readInt();
|
||||
System.out.println("chunk: " + chunkX + " : " + chunkZ);
|
||||
try {
|
||||
while (true) {
|
||||
int index = stream.readInt();
|
||||
boolean isCustomBlock = stream.readBoolean();
|
||||
short block = stream.readShort();
|
||||
}
|
||||
} catch (EOFException e) {
|
||||
System.out.println("END");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected ChunkDataPacket getFreshFullDataPacket() {
|
||||
ChunkDataPacket fullDataPacket = new ChunkDataPacket();
|
||||
fullDataPacket.chunk = this;
|
||||
|
@ -6,6 +6,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class ChunkBatch implements BlockModifier {
|
||||
|
||||
@ -43,7 +44,7 @@ public class ChunkBatch implements BlockModifier {
|
||||
this.dataList.add(data);
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
public void flush(Consumer<Chunk> callback) {
|
||||
synchronized (chunk) {
|
||||
batchesPool.submit(() -> {
|
||||
for (BlockData data : dataList) {
|
||||
@ -51,6 +52,8 @@ public class ChunkBatch implements BlockModifier {
|
||||
}
|
||||
chunk.refreshDataPacket(); // TODO partial refresh instead of full
|
||||
instance.sendChunkUpdate(chunk); // TODO partial chunk data
|
||||
if (callback != null)
|
||||
callback.accept(chunk);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,85 @@
|
||||
package fr.themode.minestom.instance;
|
||||
|
||||
import fr.themode.minestom.Main;
|
||||
import fr.themode.minestom.utils.SerializerUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.Files;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
// TODO compression
|
||||
public class ChunkLoaderIO {
|
||||
|
||||
private ExecutorService chunkLoaderPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_CHUNK_IO);
|
||||
|
||||
private static File getChunkFile(int chunkX, int chunkZ, File folder) {
|
||||
return new File(folder, getChunkFileName(chunkX, chunkZ));
|
||||
}
|
||||
|
||||
private static String getChunkFileName(int chunkX, int chunkZ) {
|
||||
return "chunk." + chunkX + "." + chunkZ + ".data";
|
||||
}
|
||||
|
||||
protected void saveChunk(Chunk chunk, File folder, Runnable callback) {
|
||||
chunkLoaderPool.submit(() -> {
|
||||
File chunkFile = getChunkFile(chunk.getChunkX(), chunk.getChunkZ(), folder);
|
||||
try (FileOutputStream fos = new FileOutputStream(chunkFile)) {
|
||||
byte[] data = chunk.getSerializedData();
|
||||
// Zstd.compress(data, 1)
|
||||
fos.write(data);
|
||||
} catch (FileNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (callback != null)
|
||||
callback.run();
|
||||
});
|
||||
}
|
||||
|
||||
protected void loadChunk(int chunkX, int chunkZ, Instance instance, Consumer<Chunk> callback) {
|
||||
chunkLoaderPool.submit(() -> {
|
||||
File chunkFile = getChunkFile(chunkX, chunkZ, instance.getFolder());
|
||||
if (!chunkFile.exists()) {
|
||||
instance.createChunk(chunkX, chunkZ, callback); // Chunk file does not exist, create new chunk
|
||||
return;
|
||||
}
|
||||
|
||||
byte[] array = new byte[0];
|
||||
try {
|
||||
array = Files.readAllBytes(getChunkFile(chunkX, chunkZ, instance.getFolder()).toPath());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
DataInputStream stream = new DataInputStream(new ByteArrayInputStream(array));
|
||||
|
||||
Chunk chunk = null;
|
||||
try {
|
||||
Biome biome = Biome.fromId(stream.readByte());
|
||||
chunk = new Chunk(biome, chunkX, chunkZ);
|
||||
|
||||
while (true) {
|
||||
int index = stream.readInt();
|
||||
boolean isCustomBlock = stream.readBoolean();
|
||||
short blockId = stream.readShort();
|
||||
|
||||
byte[] chunkPos = SerializerUtils.indexToChunkPosition(index);
|
||||
if (isCustomBlock) {
|
||||
chunk.setCustomBlock(chunkPos[0], chunkPos[1], chunkPos[2], blockId);
|
||||
} else {
|
||||
chunk.setBlock(chunkPos[0], chunkPos[1], chunkPos[2], blockId);
|
||||
}
|
||||
}
|
||||
} catch (EOFException e) {
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
callback.accept(chunk); // Success, null if file isn't properly encoded
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -7,23 +7,24 @@ import fr.themode.minestom.entity.ObjectEntity;
|
||||
import fr.themode.minestom.entity.Player;
|
||||
import fr.themode.minestom.event.BlockBreakEvent;
|
||||
import fr.themode.minestom.net.PacketWriter;
|
||||
import fr.themode.minestom.net.packet.server.play.ChunkDataPacket;
|
||||
import fr.themode.minestom.net.packet.server.play.DestroyEntitiesPacket;
|
||||
import fr.themode.minestom.net.packet.server.play.ParticlePacket;
|
||||
import fr.themode.minestom.utils.BlockPosition;
|
||||
import fr.themode.minestom.utils.GroupedCollections;
|
||||
import fr.themode.minestom.utils.Position;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class Instance implements BlockModifier {
|
||||
|
||||
private static ChunkLoaderIO chunkLoaderIO = new ChunkLoaderIO();
|
||||
|
||||
private UUID uniqueId;
|
||||
private File folder;
|
||||
|
||||
private GroupedCollections<ObjectEntity> objectEntities = new GroupedCollections<>(new CopyOnWriteArrayList<>());
|
||||
private GroupedCollections<EntityCreature> creatures = new GroupedCollections<>(new CopyOnWriteArrayList());
|
||||
@ -32,8 +33,9 @@ public class Instance implements BlockModifier {
|
||||
private ChunkGenerator chunkGenerator;
|
||||
private Map<Long, Chunk> chunks = new ConcurrentHashMap<>();
|
||||
|
||||
public Instance(UUID uniqueId) {
|
||||
public Instance(UUID uniqueId, File folder) {
|
||||
this.uniqueId = uniqueId;
|
||||
this.folder = folder;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -41,10 +43,12 @@ public class Instance implements BlockModifier {
|
||||
Chunk chunk = getChunkAt(x, z);
|
||||
synchronized (chunk) {
|
||||
chunk.setBlock((byte) (x % 16), (byte) y, (byte) (z % 16), blockId);
|
||||
PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> {
|
||||
chunk.refreshDataPacket();
|
||||
sendChunkUpdate(chunk);
|
||||
/*PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> {
|
||||
chunk.setFullDataPacket(buffer);
|
||||
sendChunkUpdate(chunk);
|
||||
});
|
||||
});*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,10 +57,12 @@ public class Instance implements BlockModifier {
|
||||
Chunk chunk = getChunkAt(x, z);
|
||||
synchronized (chunk) {
|
||||
chunk.setBlock((byte) (x % 16), (byte) y, (byte) (z % 16), blockId);
|
||||
PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> {
|
||||
chunk.refreshDataPacket();
|
||||
sendChunkUpdate(chunk);
|
||||
/*PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> {
|
||||
chunk.setFullDataPacket(buffer);
|
||||
sendChunkUpdate(chunk);
|
||||
});
|
||||
});*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -92,9 +98,28 @@ public class Instance implements BlockModifier {
|
||||
breakBlock(player, blockPosition, customBlock.getType());
|
||||
}
|
||||
|
||||
public Chunk loadChunk(int chunkX, int chunkZ) {
|
||||
public void loadChunk(int chunkX, int chunkZ, Consumer<Chunk> callback) {
|
||||
Chunk chunk = getChunk(chunkX, chunkZ);
|
||||
return chunk == null ? createChunk(chunkX, chunkZ) : chunk; // TODO load from file
|
||||
if (chunk != null) {
|
||||
if (callback != null)
|
||||
callback.accept(chunk);
|
||||
} else {
|
||||
retrieveChunk(chunkX, chunkZ, callback);
|
||||
}
|
||||
}
|
||||
|
||||
public void loadChunk(int chunkX, int chunkZ) {
|
||||
loadChunk(chunkX, chunkZ, null);
|
||||
}
|
||||
|
||||
public void loadChunk(Position position, Consumer<Chunk> callback) {
|
||||
int chunkX = Math.floorDiv((int) position.getX(), 16);
|
||||
int chunkZ = Math.floorDiv((int) position.getY(), 16);
|
||||
loadChunk(chunkX, chunkZ, callback);
|
||||
}
|
||||
|
||||
public boolean isChunkLoaded(int chunkX, int chunkZ) {
|
||||
return getChunk(chunkX, chunkZ) != null;
|
||||
}
|
||||
|
||||
public short getBlockId(int x, int y, int z) {
|
||||
@ -133,6 +158,23 @@ public class Instance implements BlockModifier {
|
||||
return getChunkAt(position.getX(), position.getZ());
|
||||
}
|
||||
|
||||
public void saveToFolder(Runnable callback) {
|
||||
if (folder == null)
|
||||
throw new UnsupportedOperationException("You cannot save an instance without specified folder.");
|
||||
|
||||
Iterator<Chunk> chunks = getChunks().iterator();
|
||||
while (chunks.hasNext()) {
|
||||
Chunk chunk = chunks.next();
|
||||
boolean isLast = !chunks.hasNext();
|
||||
chunkLoaderIO.saveChunk(chunk, getFolder(), isLast ? callback : null);
|
||||
}
|
||||
}
|
||||
|
||||
public void saveToFolder() {
|
||||
saveToFolder(null);
|
||||
}
|
||||
|
||||
|
||||
public void setChunkGenerator(ChunkGenerator chunkGenerator) {
|
||||
this.chunkGenerator = chunkGenerator;
|
||||
}
|
||||
@ -203,26 +245,50 @@ public class Instance implements BlockModifier {
|
||||
return uniqueId;
|
||||
}
|
||||
|
||||
public void sendChunkUpdate(Player player, Chunk chunk) {
|
||||
ChunkDataPacket chunkDataPacket = new ChunkDataPacket();
|
||||
chunkDataPacket.fullChunk = false;
|
||||
chunkDataPacket.chunk = chunk;
|
||||
player.getPlayerConnection().sendPacket(chunkDataPacket); // TODO write packet buffer in another thread (Chunk packets are heavy)
|
||||
public File getFolder() {
|
||||
return folder;
|
||||
}
|
||||
|
||||
protected Chunk createChunk(int chunkX, int chunkZ) {
|
||||
public void setFolder(File folder) {
|
||||
this.folder = folder;
|
||||
}
|
||||
|
||||
public void sendChunkUpdate(Player player, Chunk chunk) {
|
||||
Buffer chunkData = chunk.getFullDataPacket();
|
||||
chunkData.getData().retain(1).markReaderIndex();
|
||||
player.getPlayerConnection().sendUnencodedPacket(chunkData);
|
||||
chunkData.getData().resetReaderIndex();
|
||||
}
|
||||
|
||||
protected void retrieveChunk(int chunkX, int chunkZ, Consumer<Chunk> callback) {
|
||||
if (folder != null) {
|
||||
// Load from file if possible
|
||||
chunkLoaderIO.loadChunk(chunkX, chunkZ, this, chunk -> {
|
||||
cacheChunk(chunk);
|
||||
if (callback != null)
|
||||
callback.accept(chunk);
|
||||
});
|
||||
} else {
|
||||
createChunk(chunkX, chunkZ, callback);
|
||||
}
|
||||
}
|
||||
|
||||
public void createChunk(int chunkX, int chunkZ, Consumer<Chunk> callback) {
|
||||
Biome biome = chunkGenerator != null ? chunkGenerator.getBiome(chunkX, chunkZ) : Biome.VOID;
|
||||
Chunk chunk = new Chunk(biome, chunkX, chunkZ);
|
||||
this.objectEntities.addCollection(chunk.objectEntities);
|
||||
this.creatures.addCollection(chunk.creatures);
|
||||
this.players.addCollection(chunk.players);
|
||||
this.chunks.put(getChunkKey(chunkX, chunkZ), chunk);
|
||||
cacheChunk(chunk);
|
||||
if (chunkGenerator != null) {
|
||||
ChunkBatch chunkBatch = createChunkBatch(chunk);
|
||||
chunkGenerator.generateChunkData(chunkBatch, chunkX, chunkZ);
|
||||
chunkBatch.flush();
|
||||
chunkBatch.flush(callback);
|
||||
}
|
||||
return chunk;
|
||||
}
|
||||
|
||||
private void cacheChunk(Chunk chunk) {
|
||||
this.objectEntities.addCollection(chunk.objectEntities);
|
||||
this.creatures.addCollection(chunk.creatures);
|
||||
this.players.addCollection(chunk.players);
|
||||
this.chunks.put(getChunkKey(chunk.getChunkX(), chunk.getChunkZ()), chunk);
|
||||
}
|
||||
|
||||
protected ChunkBatch createChunkBatch(Chunk chunk) {
|
||||
@ -230,6 +296,9 @@ public class Instance implements BlockModifier {
|
||||
}
|
||||
|
||||
protected void sendChunkUpdate(Chunk chunk) {
|
||||
if (getPlayers().isEmpty())
|
||||
return;
|
||||
|
||||
Buffer chunkData = chunk.getFullDataPacket();
|
||||
chunkData.getData().retain(getPlayers().size()).markReaderIndex();
|
||||
getPlayers().forEach(player -> {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package fr.themode.minestom.instance;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@ -9,12 +10,16 @@ public class InstanceManager {
|
||||
|
||||
private Set<Instance> instances = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
public Instance createInstance() {
|
||||
Instance instance = new Instance(UUID.randomUUID());
|
||||
public Instance createInstance(File folder) {
|
||||
Instance instance = new Instance(UUID.randomUUID(), folder);
|
||||
this.instances.add(instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
public Instance createInstance() {
|
||||
return createInstance(null);
|
||||
}
|
||||
|
||||
public Set<Instance> getInstances() {
|
||||
return Collections.unmodifiableSet(instances);
|
||||
}
|
||||
|
@ -1,16 +1,20 @@
|
||||
package fr.themode.minestom.listener;
|
||||
|
||||
import fr.themode.minestom.entity.Player;
|
||||
import fr.themode.minestom.event.AnimationEvent;
|
||||
import fr.themode.minestom.net.packet.client.play.ClientAnimationPacket;
|
||||
import fr.themode.minestom.net.packet.server.play.AnimationPacket;
|
||||
|
||||
public class AnimationListener {
|
||||
|
||||
public static void animationListener(ClientAnimationPacket packet, Player player) {
|
||||
AnimationPacket animationPacket = new AnimationPacket();
|
||||
animationPacket.entityId = player.getEntityId();
|
||||
animationPacket.animation = packet.hand == Player.Hand.MAIN ? AnimationPacket.Animation.SWING_MAIN_ARM : AnimationPacket.Animation.SWING_OFF_HAND;
|
||||
player.sendPacketToViewers(animationPacket);
|
||||
AnimationEvent animationEvent = new AnimationEvent(packet.hand);
|
||||
player.callCancellableEvent(AnimationEvent.class, animationEvent, () -> {
|
||||
AnimationPacket animationPacket = new AnimationPacket();
|
||||
animationPacket.entityId = player.getEntityId();
|
||||
animationPacket.animation = animationEvent.getHand() == Player.Hand.MAIN ? AnimationPacket.Animation.SWING_MAIN_ARM : AnimationPacket.Animation.SWING_OFF_HAND;
|
||||
player.sendPacketToViewers(animationPacket);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ public class BlockPlacementListener {
|
||||
int offsetZ = blockFace == ClientPlayerDiggingPacket.BlockFace.NORTH ? -1 : blockFace == ClientPlayerDiggingPacket.BlockFace.SOUTH ? 1 : 0;
|
||||
|
||||
blockPosition.add(offsetX, offsetY, offsetZ);
|
||||
BlockPlaceEvent blockPlaceEvent = new BlockPlaceEvent((short) 10, blockPosition);
|
||||
BlockPlaceEvent blockPlaceEvent = new BlockPlaceEvent((short) 10, blockPosition, packet.hand);
|
||||
player.callEvent(BlockPlaceEvent.class, blockPlaceEvent);
|
||||
if (!blockPlaceEvent.isCancelled()) {
|
||||
instance.setBlock(blockPosition, "custom_block");
|
||||
|
@ -1,6 +1,7 @@
|
||||
package fr.themode.minestom.listener;
|
||||
|
||||
import fr.themode.minestom.entity.Entity;
|
||||
import fr.themode.minestom.entity.EntityCreature;
|
||||
import fr.themode.minestom.entity.Player;
|
||||
import fr.themode.minestom.event.AttackEvent;
|
||||
import fr.themode.minestom.event.InteractEvent;
|
||||
@ -14,6 +15,9 @@ public class UseEntityListener {
|
||||
return;
|
||||
ClientUseEntityPacket.Type type = packet.type;
|
||||
if (type == ClientUseEntityPacket.Type.ATTACK) {
|
||||
if (entity instanceof EntityCreature && ((EntityCreature) entity).isDead()) // Can't attack dead entities
|
||||
return;
|
||||
|
||||
AttackEvent attackEvent = new AttackEvent(entity);
|
||||
player.callEvent(AttackEvent.class, attackEvent);
|
||||
} else if (type == ClientUseEntityPacket.Type.INTERACT) {
|
||||
|
@ -50,7 +50,7 @@ public class PacketProcessor {
|
||||
ConnectionState connectionState = playerConnection.getConnectionState();
|
||||
|
||||
if (!printBlackList.contains(id)) {
|
||||
System.out.println("RECEIVED ID: 0x" + Integer.toHexString(id) + " State: " + connectionState);
|
||||
//System.out.println("RECEIVED ID: 0x" + Integer.toHexString(id) + " State: " + connectionState);
|
||||
}
|
||||
|
||||
if (connectionState == ConnectionState.UNKNOWN) {
|
||||
|
@ -17,7 +17,7 @@ public class PacketWriter {
|
||||
public static void writeCallbackPacket(ServerPacket serverPacket, Consumer<Buffer> consumer) {
|
||||
batchesPool.submit(() -> {
|
||||
Packet p = PacketUtils.writePacket(serverPacket);
|
||||
consumer.accept(PacketUtils.encode(p));
|
||||
consumer.accept(PacketUtils.encode(p)); // TODO accept in another thread?
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2,25 +2,13 @@ package fr.themode.minestom.net.packet.client.login;
|
||||
|
||||
import fr.adamaq01.ozao.net.Buffer;
|
||||
import fr.themode.minestom.Main;
|
||||
import fr.themode.minestom.bossbar.BarColor;
|
||||
import fr.themode.minestom.bossbar.BarDivision;
|
||||
import fr.themode.minestom.bossbar.BossBar;
|
||||
import fr.themode.minestom.entity.GameMode;
|
||||
import fr.themode.minestom.entity.ItemEntity;
|
||||
import fr.themode.minestom.entity.Player;
|
||||
import fr.themode.minestom.entity.demo.ChickenCreature;
|
||||
import fr.themode.minestom.entity.demo.TestArrow;
|
||||
import fr.themode.minestom.instance.Instance;
|
||||
import fr.themode.minestom.instance.demo.ChunkGeneratorDemo;
|
||||
import fr.themode.minestom.inventory.PlayerInventory;
|
||||
import fr.themode.minestom.item.ItemStack;
|
||||
import fr.themode.minestom.item.Material;
|
||||
import fr.themode.minestom.net.ConnectionManager;
|
||||
import fr.themode.minestom.net.ConnectionState;
|
||||
import fr.themode.minestom.net.packet.client.ClientPreplayPacket;
|
||||
import fr.themode.minestom.net.packet.server.login.JoinGamePacket;
|
||||
import fr.themode.minestom.net.packet.server.login.LoginSuccessPacket;
|
||||
import fr.themode.minestom.net.packet.server.play.DeclareCommandsPacket;
|
||||
import fr.themode.minestom.net.packet.server.play.PlayerInfoPacket;
|
||||
import fr.themode.minestom.net.packet.server.play.PlayerPositionAndLookPacket;
|
||||
import fr.themode.minestom.net.packet.server.play.SpawnPositionPacket;
|
||||
@ -34,21 +22,24 @@ import java.util.UUID;
|
||||
public class LoginStartPacket implements ClientPreplayPacket {
|
||||
|
||||
// Test
|
||||
private static Instance instance;
|
||||
/*private static Instance instance;
|
||||
|
||||
static {
|
||||
ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo();
|
||||
instance = Main.getInstanceManager().createInstance();
|
||||
instance = Main.getInstanceManager().createInstance(new File("C:\\Users\\themo\\OneDrive\\Bureau\\Minestom data"));
|
||||
//instance = Main.getInstanceManager().createInstance();
|
||||
instance.setChunkGenerator(chunkGeneratorDemo);
|
||||
int loopStart = -2;
|
||||
int loopEnd = 2;
|
||||
long time = System.currentTimeMillis();
|
||||
for (int x = loopStart; x < loopEnd; x++)
|
||||
for (int z = loopStart; z < loopEnd; z++) {
|
||||
instance.loadChunk(x, z);
|
||||
instance.loadChunk(x, z, chunk -> {
|
||||
System.out.println("JE SUIS LE CALLBACK CHUNK");
|
||||
});
|
||||
}
|
||||
System.out.println("Time to load all chunks: " + (System.currentTimeMillis() - time) + " ms");
|
||||
}
|
||||
}*/
|
||||
|
||||
public String username;
|
||||
|
||||
@ -73,9 +64,9 @@ public class LoginStartPacket implements ClientPreplayPacket {
|
||||
GameMode gameMode = GameMode.SURVIVAL;
|
||||
Dimension dimension = Dimension.OVERWORLD;
|
||||
LevelType levelType = LevelType.DEFAULT;
|
||||
float x = 5;
|
||||
float y = 65;
|
||||
float z = 5;
|
||||
float x = 0;
|
||||
float y = 0;
|
||||
float z = 0;
|
||||
|
||||
player.refreshDimension(dimension);
|
||||
player.refreshGameMode(gameMode);
|
||||
@ -121,10 +112,14 @@ public class LoginStartPacket implements ClientPreplayPacket {
|
||||
|
||||
// Next is optional TODO put all that somewhere else (LoginEvent)
|
||||
// TODO LoginEvent in another thread (here we are in netty thread)
|
||||
System.out.println("ADD WAITING PLAYER");
|
||||
Main.getEntityManager().addWaitingPlayer(player);
|
||||
|
||||
player.setInstance(instance);
|
||||
|
||||
for (int cx = 0; cx < 4; cx++)
|
||||
// TODO REMOVE EVERYTHING DOWN THERE
|
||||
//player.setInstance(instance);
|
||||
|
||||
/*for (int cx = 0; cx < 4; cx++)
|
||||
for (int cz = 0; cz < 4; cz++) {
|
||||
ChickenCreature chickenCreature = new ChickenCreature();
|
||||
chickenCreature.refreshPosition(0 + (float) cx * 1, 65, 0 + (float) cz * 1);
|
||||
@ -140,7 +135,7 @@ public class LoginStartPacket implements ClientPreplayPacket {
|
||||
/*Inventory inv = new Inventory(InventoryType.WINDOW_3X3, "Salut je suis le titre");
|
||||
inv.setItemStack(0, new ItemStack(1, (byte) 1));
|
||||
player.openInventory(inv);
|
||||
inv.setItemStack(1, new ItemStack(1, (byte) 2));*/
|
||||
inv.setItemStack(1, new ItemStack(1, (byte) 2));
|
||||
|
||||
BossBar bossBar = new BossBar("Bossbar Title", BarColor.BLUE, BarDivision.SEGMENT_12);
|
||||
bossBar.setProgress(0.75f);
|
||||
@ -178,7 +173,7 @@ public class LoginStartPacket implements ClientPreplayPacket {
|
||||
declareCommandsPacket.rootIndex = 0;
|
||||
|
||||
|
||||
connection.sendPacket(declareCommandsPacket);
|
||||
connection.sendPacket(declareCommandsPacket);*/
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,13 +1,14 @@
|
||||
package fr.themode.minestom.net.packet.client.play;
|
||||
|
||||
import fr.adamaq01.ozao.net.Buffer;
|
||||
import fr.themode.minestom.entity.Player;
|
||||
import fr.themode.minestom.net.packet.client.ClientPlayPacket;
|
||||
import fr.themode.minestom.utils.BlockPosition;
|
||||
import fr.themode.minestom.utils.Utils;
|
||||
|
||||
public class ClientPlayerBlockPlacementPacket extends ClientPlayPacket {
|
||||
|
||||
public Hand hand;
|
||||
public Player.Hand hand;
|
||||
public BlockPosition blockPosition;
|
||||
public ClientPlayerDiggingPacket.BlockFace blockFace;
|
||||
public float cursorPositionX, cursorPositionY, cursorPositionZ;
|
||||
@ -15,7 +16,7 @@ public class ClientPlayerBlockPlacementPacket extends ClientPlayPacket {
|
||||
|
||||
@Override
|
||||
public void read(Buffer buffer) {
|
||||
this.hand = Hand.values()[Utils.readVarInt(buffer)];
|
||||
this.hand = Player.Hand.values()[Utils.readVarInt(buffer)];
|
||||
this.blockPosition = Utils.readPosition(buffer);
|
||||
this.blockFace = ClientPlayerDiggingPacket.BlockFace.values()[Utils.readVarInt(buffer)];
|
||||
this.cursorPositionX = buffer.getFloat();
|
||||
@ -24,9 +25,4 @@ public class ClientPlayerBlockPlacementPacket extends ClientPlayPacket {
|
||||
this.insideBlock = buffer.getBoolean();
|
||||
}
|
||||
|
||||
public enum Hand {
|
||||
MAIN_HAND,
|
||||
OFF_HAND;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import fr.adamaq01.ozao.net.Buffer;
|
||||
import fr.themode.minestom.instance.Chunk;
|
||||
import fr.themode.minestom.net.packet.server.ServerPacket;
|
||||
import fr.themode.minestom.utils.BlockPosition;
|
||||
import fr.themode.minestom.utils.SerializerUtils;
|
||||
import fr.themode.minestom.utils.Utils;
|
||||
import net.querz.nbt.CompoundTag;
|
||||
import net.querz.nbt.DoubleTag;
|
||||
@ -81,7 +82,7 @@ public class ChunkDataPacket implements ServerPacket {
|
||||
Utils.writeVarInt(buffer, blockEntities.size());
|
||||
|
||||
for (Integer index : blockEntities) {
|
||||
BlockPosition blockPosition = indexToBlockPosition(index);
|
||||
BlockPosition blockPosition = SerializerUtils.indexToBlockPosition(index, chunk.getChunkX(), chunk.getChunkZ());
|
||||
CompoundTag blockEntity = new CompoundTag();
|
||||
blockEntity.put("x", new DoubleTag(blockPosition.getX()));
|
||||
blockEntity.put("y", new DoubleTag(blockPosition.getY()));
|
||||
@ -110,13 +111,6 @@ public class ChunkDataPacket implements ServerPacket {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
private BlockPosition indexToBlockPosition(int index) {
|
||||
byte z = (byte) (index >> 12 & 0xF);
|
||||
byte y = (byte) (index >> 4 & 0xFF);
|
||||
byte x = (byte) (index >> 0 & 0xF);
|
||||
return new BlockPosition(x + 16 * chunk.getChunkX(), y, z + 16 * chunk.getChunkZ());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getId() {
|
||||
return 0x21;
|
||||
|
@ -6,6 +6,7 @@ import fr.adamaq01.ozao.net.server.Connection;
|
||||
import fr.themode.minestom.net.ConnectionState;
|
||||
import fr.themode.minestom.net.packet.server.ServerPacket;
|
||||
import fr.themode.minestom.utils.PacketUtils;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.socket.SocketChannel;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@ -40,12 +41,15 @@ public class PlayerConnection {
|
||||
}
|
||||
|
||||
public void sendUnencodedPacket(Buffer packet) {
|
||||
try {
|
||||
SocketChannel channel = ((SocketChannel) field.get(connection));
|
||||
channel.writeAndFlush(packet.getData());
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
getChannel().writeAndFlush(packet.getData());
|
||||
}
|
||||
|
||||
public void writeUnencodedPacket(Buffer packet) {
|
||||
getChannel().write(packet.getData());
|
||||
}
|
||||
|
||||
public void flush() {
|
||||
getChannel().flush();
|
||||
}
|
||||
|
||||
public void sendPacket(ServerPacket serverPacket) {
|
||||
@ -63,4 +67,13 @@ public class PlayerConnection {
|
||||
public ConnectionState getConnectionState() {
|
||||
return connectionState;
|
||||
}
|
||||
|
||||
private Channel getChannel() {
|
||||
try {
|
||||
return ((SocketChannel) field.get(connection));
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
20
src/main/java/fr/themode/minestom/utils/ArraysUtils.java
Normal file
20
src/main/java/fr/themode/minestom/utils/ArraysUtils.java
Normal file
@ -0,0 +1,20 @@
|
||||
package fr.themode.minestom.utils;
|
||||
|
||||
public class ArraysUtils {
|
||||
|
||||
public static byte[] concenateByteArrays(byte[]... arrays) {
|
||||
int totalLength = 0;
|
||||
for (byte[] array : arrays) {
|
||||
totalLength += array.length;
|
||||
}
|
||||
byte[] result = new byte[totalLength];
|
||||
|
||||
int startingPos = 0;
|
||||
for (byte[] array : arrays) {
|
||||
System.arraycopy(array, 0, result, startingPos, array.length);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
42
src/main/java/fr/themode/minestom/utils/SerializerUtils.java
Normal file
42
src/main/java/fr/themode/minestom/utils/SerializerUtils.java
Normal file
@ -0,0 +1,42 @@
|
||||
package fr.themode.minestom.utils;
|
||||
|
||||
public class SerializerUtils {
|
||||
|
||||
public static byte[] intToBytes(int value) {
|
||||
byte[] result = new byte[4];
|
||||
result[0] = (byte) (value >> 24);
|
||||
result[1] = (byte) (value >> 16);
|
||||
result[2] = (byte) (value >> 8);
|
||||
result[3] = (byte) (value >> 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int bytesToInt(byte[] value) {
|
||||
return ((value[0] & 0xFF) << 24) |
|
||||
((value[1] & 0xFF) << 16) |
|
||||
((value[2] & 0xFF) << 8) |
|
||||
((value[3] & 0xFF) << 0);
|
||||
}
|
||||
|
||||
public static int chunkCoordToIndex(byte x, byte y, byte z) {
|
||||
short index = (short) (x & 0x000F);
|
||||
index |= (y << 4) & 0x0FF0;
|
||||
index |= (z << 12) & 0xF000;
|
||||
return index & 0xffff;
|
||||
}
|
||||
|
||||
public static byte[] indexToChunkPosition(int index) {
|
||||
byte z = (byte) (index >> 12 & 0xF);
|
||||
byte y = (byte) (index >> 4 & 0xFF);
|
||||
byte x = (byte) (index >> 0 & 0xF);
|
||||
return new byte[]{x, y, z};
|
||||
}
|
||||
|
||||
public static BlockPosition indexToBlockPosition(int index, int chunkX, int chunkZ) {
|
||||
byte z = (byte) (index >> 12 & 0xF);
|
||||
byte y = (byte) (index >> 4 & 0xFF);
|
||||
byte x = (byte) (index >> 0 & 0xF);
|
||||
return new BlockPosition(x + 16 * chunkX, y, z + 16 * chunkZ);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user