Added velocity & WIP shared instances

This commit is contained in:
TheMode 2019-08-24 20:34:01 +02:00
parent 7a557169bd
commit c25c846dce
29 changed files with 1174 additions and 469 deletions

View File

@ -5,7 +5,7 @@ plugins {
group 'fr.themode.minestom'
version '1.0'
sourceCompatibility = 1.8
sourceCompatibility = 1.11
repositories {
mavenCentral()

View File

@ -22,13 +22,14 @@ import java.lang.reflect.InvocationTargetException;
public class Main {
// Thread number
public static final int THREAD_COUNT_PACKET_WRITER = 3;
public static final int THREAD_COUNT_PACKET_WRITER = 5;
public static final int THREAD_COUNT_CHUNK_IO = 2;
public static final int THREAD_COUNT_CHUNK_BATCH = 2;
public static final int THREAD_COUNT_ENTITIES = 2;
public static final int THREAD_COUNT_PLAYERS_ENTITIES = 2;
public static final int THREAD_COUNT_ENTITIES = 1;
public static final int THREAD_COUNT_PLAYERS_ENTITIES = 3;
public static final int TICK_MS = 50;
public static final int TICK_PER_SECOND = 1000 / TICK_MS;
// Networking
private static ConnectionManager connectionManager;
@ -41,7 +42,7 @@ public class Main {
private static BlockManager blockManager;
private static EntityManager entityManager;
public static void main(String[] args) throws InterruptedException {
public static void main(String[] args) {
connectionManager = new ConnectionManager();
packetProcessor = new PacketProcessor();
packetListenerManager = new PacketListenerManager();
@ -95,7 +96,7 @@ public class Main {
@Override
public void onException(Server server, Connection connection, Throwable cause) {
cause.printStackTrace();
// cause.printStackTrace();
}
});
@ -127,7 +128,11 @@ public class Main {
//String perfMessage = "Online: " + getConnectionManager().getOnlinePlayers().size() + " Tick time: " + (TICK_MS - sleepTime) + " ms";
//getConnectionManager().getOnlinePlayers().forEach(player -> player.sendMessage(perfMessage));
Thread.sleep(sleepTime);
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

View File

@ -23,9 +23,12 @@ public interface Viewable {
return;
PacketWriter.writeCallbackPacket(packet, buffer -> {
int size = getViewers().size();
if (size == 0)
return;
buffer.getData().retain(getViewers().size()).markReaderIndex();
getViewers().forEach(player -> {
player.getPlayerConnection().sendUnencodedPacket(buffer);
player.getPlayerConnection().writeUnencodedPacket(buffer);
buffer.getData().resetReaderIndex();
});
});
@ -39,7 +42,7 @@ public interface Viewable {
PacketWriter.writeCallbackPacket(packet, buffer -> {
buffer.getData().retain(getViewers().size()).markReaderIndex();
getViewers().forEach(player -> {
player.getPlayerConnection().sendUnencodedPacket(buffer);
player.getPlayerConnection().writeUnencodedPacket(buffer);
buffer.getData().resetReaderIndex();
});
});
@ -50,12 +53,12 @@ public interface Viewable {
if (this instanceof Player) {
PacketWriter.writeCallbackPacket(packet, buffer -> {
buffer.getData().retain(getViewers().size() + 1).markReaderIndex();
((Player) this).getPlayerConnection().sendUnencodedPacket(buffer);
((Player) this).getPlayerConnection().writeUnencodedPacket(buffer);
buffer.getData().resetReaderIndex();
if (!getViewers().isEmpty()) {
getViewers().forEach(player -> {
buffer.getData().resetReaderIndex();
player.getPlayerConnection().sendUnencodedPacket(buffer);
player.getPlayerConnection().writeUnencodedPacket(buffer);
});
}
});

View File

@ -13,6 +13,7 @@ import fr.themode.minestom.instance.Instance;
import fr.themode.minestom.net.packet.server.play.*;
import fr.themode.minestom.utils.Position;
import fr.themode.minestom.utils.Utils;
import fr.themode.minestom.utils.Vector;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@ -36,6 +37,7 @@ public abstract class Entity implements Viewable, DataContainer {
protected Instance instance;
protected Position position;
protected boolean onGround;
protected double lastX, lastY, lastZ;
protected float lastYaw, lastPitch;
private int id;
@ -53,6 +55,15 @@ public abstract class Entity implements Viewable, DataContainer {
private int entityType;
private long lastUpdate;
// Velocity
// TODO gravity implementation for entity other than players
protected float velocityX, velocityY, velocityZ; // Movement in block per second
protected long velocityTime; // Reset velocity to 0 after countdown
// Synchronization
private long synchronizationDelay = 2000; // In ms
private long lastSynchronizationTime;
// Metadata
protected boolean onFire;
protected boolean crouched;
@ -92,6 +103,9 @@ public abstract class Entity implements Viewable, DataContainer {
public abstract void update();
// Called when entity a new instance is set
public abstract void spawn();
public void teleport(Position position) {
if (isChunkUnloaded(position.getX(), position.getZ()))
return;
@ -101,13 +115,14 @@ public abstract class Entity implements Viewable, DataContainer {
EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket();
entityTeleportPacket.entityId = getEntityId();
entityTeleportPacket.position = position;
entityTeleportPacket.onGround = true;
entityTeleportPacket.onGround = onGround;
sendPacketToViewers(entityTeleportPacket);
}
@Override
public void addViewer(Player player) {
this.viewers.add(player);
player.getPlayerConnection().sendPacket(getVelocityPacket());
}
@Override
@ -153,7 +168,36 @@ public abstract class Entity implements Viewable, DataContainer {
remove();
return;
} else if (shouldUpdate()) {
long time = System.currentTimeMillis();
// Velocity
if (velocityTime != 0) {
if (time >= velocityTime) {
// TODO send synchronization packet?
resetVelocity();
} else {
if (this instanceof Player) {
sendPacketToViewersAndSelf(getVelocityPacket());
} else {
float tps = Main.TICK_PER_SECOND;
refreshPosition(position.getX() + velocityX / tps, position.getY() + velocityY / tps, position.getZ() + velocityZ / tps);
if (this instanceof ObjectEntity) {
sendPacketToViewers(getVelocityPacket());
} else if (this instanceof EntityCreature) {
teleport(getPosition());
}
}
}
}
update();
// Synchronization
if (time - lastSynchronizationTime >= synchronizationDelay) {
lastSynchronizationTime = System.currentTimeMillis();
sendPositionSynchronization();
}
this.lastUpdate = System.currentTimeMillis();
}
@ -214,6 +258,21 @@ public abstract class Entity implements Viewable, DataContainer {
this.isActive = true;
this.instance = instance;
instance.addEntity(this);
spawn();
}
public void setVelocity(Vector velocity, long velocityTime) {
this.velocityX = velocity.getX();
this.velocityY = velocity.getY();
this.velocityZ = velocity.getZ();
this.velocityTime = System.currentTimeMillis() + velocityTime;
}
public void resetVelocity() {
this.velocityX = 0;
this.velocityY = 0;
this.velocityZ = 0;
this.velocityTime = 0;
}
public float getDistance(Entity entity) {
@ -252,6 +311,11 @@ public abstract class Entity implements Viewable, DataContainer {
sendMetadataIndex(0);
}
public void setGlowing(boolean glowing) {
this.glowing = glowing;
sendMetadataIndex(0);
}
public void setNoGravity(boolean noGravity) {
this.noGravity = noGravity;
sendMetadataIndex(5);
@ -322,6 +386,16 @@ public abstract class Entity implements Viewable, DataContainer {
this.scheduledRemoveTime = System.currentTimeMillis() + delay;
}
protected EntityVelocityPacket getVelocityPacket() {
final float strength = 8000f / Main.TICK_PER_SECOND;
EntityVelocityPacket velocityPacket = new EntityVelocityPacket();
velocityPacket.entityId = getEntityId();
velocityPacket.velocityX = (short) (velocityX * strength);
velocityPacket.velocityY = (short) (velocityY * strength);
velocityPacket.velocityZ = (short) (velocityZ * strength);
return velocityPacket;
}
public EntityMetaDataPacket getMetadataPacket() {
EntityMetaDataPacket metaDataPacket = new EntityMetaDataPacket();
metaDataPacket.entityId = getEntityId();
@ -344,9 +418,11 @@ public abstract class Entity implements Viewable, DataContainer {
EntityMetaDataPacket metaDataPacket = new EntityMetaDataPacket();
metaDataPacket.entityId = getEntityId();
metaDataPacket.data = buffer;
sendPacketToViewers(metaDataPacket);
if (this instanceof Player) {
((Player) this).getPlayerConnection().sendPacket(metaDataPacket);
Player player = (Player) this;
player.sendPacketToViewersAndSelf(metaDataPacket);
} else {
sendPacketToViewers(metaDataPacket);
}
}
@ -390,6 +466,14 @@ public abstract class Entity implements Viewable, DataContainer {
buffer.putByte(index0);
}
protected void sendPositionSynchronization() {
EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket();
entityTeleportPacket.entityId = getEntityId();
entityTeleportPacket.position = getPosition();
entityTeleportPacket.onGround = onGround;
sendPacketToViewers(entityTeleportPacket);
}
private void fillAirTickMetaData(Buffer buffer) {
buffer.putByte((byte) 1);
buffer.putByte(METADATA_VARINT);

View File

@ -9,13 +9,22 @@ 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);
}
@Override
public void update() {
super.update();
}
public void move(float x, float y, float z) {
if (velocityTime != 0) {
// Cancel movements if velocity is active
return;
}
// TODO change yaw/pitch based on next position?
Position position = getPosition();
float newX = position.getX() + x;
float newY = position.getY() + y;
@ -35,6 +44,7 @@ public abstract class EntityCreature extends LivingEntity {
refreshPosition(newX, newY, newZ);
}
@Override
public void kill() {
this.isDead = true;
triggerStatus((byte) 3);
@ -59,7 +69,4 @@ public abstract class EntityCreature extends LivingEntity {
playerConnection.sendPacket(getMetadataPacket());
}
public boolean isDead() {
return isDead;
}
}

View File

@ -6,6 +6,7 @@ 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 fr.themode.minestom.utils.GroupedCollections;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
@ -24,19 +25,21 @@ public class EntityManager {
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
// TODO optimize update engine for when there are too many entities on one chunk
testTick1(instance);
}
}
private void waitingPlayersTick() {
Player waitingPlayer = null;
Player waitingPlayer;
while ((waitingPlayer = waitingPlayers.poll()) != null) {
final Player playerCache = waitingPlayer;
playersPool.submit(() -> {
playersPool.execute(() -> {
PlayerLoginEvent loginEvent = new PlayerLoginEvent();
playerCache.callEvent(PlayerLoginEvent.class, loginEvent);
Instance spawningInstance = loginEvent.getSpawningInstance() == null ? instanceManager.createInstance() : loginEvent.getSpawningInstance();
// TODO load multiple chunks around player (based on view distance) instead of only one
spawningInstance.loadChunk(playerCache.getPosition(), chunk -> {
playerCache.spawned = true;
playerCache.setInstance(spawningInstance);
@ -55,7 +58,7 @@ public class EntityManager {
Set<Player> players = chunk.getPlayers();
if (!creatures.isEmpty() || !objects.isEmpty()) {
entitiesPool.submit(() -> {
entitiesPool.execute(() -> {
for (EntityCreature creature : creatures) {
creature.tick();
}
@ -66,7 +69,7 @@ public class EntityManager {
}
if (!players.isEmpty()) {
playersPool.submit(() -> {
playersPool.execute(() -> {
for (Player player : players) {
player.tick();
}
@ -75,38 +78,33 @@ public class EntityManager {
}
}
private void testTick1(Instance instance) {
GroupedCollections<ObjectEntity> objects = instance.getObjectEntities();
GroupedCollections<EntityCreature> creatures = instance.getCreatures();
GroupedCollections<Player> players = instance.getPlayers();
if (!creatures.isEmpty() || !objects.isEmpty()) {
entitiesPool.execute(() -> {
for (EntityCreature creature : creatures) {
creature.tick();
}
for (ObjectEntity objectEntity : objects) {
objectEntity.tick();
}
});
}
if (!players.isEmpty()) {
playersPool.execute(() -> {
for (Player player : players) {
player.tick();
}
});
}
}
public void addWaitingPlayer(Player player) {
this.waitingPlayers.add(player);
}
/*private void testTick(Instance instance) {
// Creatures
for (EntityCreature creature : instance.getCreatures()) {
creaturesPool.submit(() -> {
boolean shouldRemove = creature.shouldRemove();
if (!shouldRemove) {
creature.tick();
}
if (creature.shouldRemove()) {
instance.removeEntity(creature);
}
});
}
// Players
for (Player player : instance.getPlayers()) {
playersPool.submit(() -> {
boolean shouldRemove = player.shouldRemove();
if (!shouldRemove) {
player.tick();
}
if (player.shouldRemove()) {
instance.removeEntity(player);
}
});
}
}*/
}

View File

@ -2,8 +2,8 @@ package fr.themode.minestom.entity;
import fr.adamaq01.ozao.net.Buffer;
import fr.themode.minestom.item.ItemStack;
import fr.themode.minestom.net.packet.server.play.EntityRelativeMovePacket;
import fr.themode.minestom.utils.Utils;
import fr.themode.minestom.utils.Vector;
public class ItemEntity extends ObjectEntity {
@ -17,11 +17,17 @@ public class ItemEntity extends ObjectEntity {
@Override
public void update() {
// TODO how to keep items at the same position?
EntityRelativeMovePacket entityRelativeMovePacket = new EntityRelativeMovePacket();
entityRelativeMovePacket.entityId = getEntityId();
entityRelativeMovePacket.onGround = false;
sendPacketToViewers(entityRelativeMovePacket);
}
@Override
public void spawn() {
setVelocity(new Vector(0, 1, 0), 5000);
}
@Override
public void addViewer(Player player) {
super.addViewer(player);
}
@Override

View File

@ -1,11 +1,18 @@
package fr.themode.minestom.entity;
import fr.adamaq01.ozao.net.Buffer;
import fr.themode.minestom.event.PickupItemEvent;
import fr.themode.minestom.instance.Chunk;
import fr.themode.minestom.item.ItemStack;
import fr.themode.minestom.net.packet.server.play.CollectItemPacket;
import java.util.Set;
// TODO attributes https://wiki.vg/Protocol#Entity_Properties
public abstract class LivingEntity extends Entity {
protected boolean onGround;
protected boolean canPickupItem;
protected boolean isDead;
private boolean isHandActive;
private boolean activeHand;
@ -15,6 +22,40 @@ public abstract class LivingEntity extends Entity {
super(entityType);
}
public abstract void kill();
@Override
public void update() {
if (canPickupItem) {
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, () -> {
CollectItemPacket collectItemPacket = new CollectItemPacket();
collectItemPacket.collectedEntityId = itemEntity.getEntityId();
collectItemPacket.collectorEntityId = getEntityId();
collectItemPacket.pickupItemCount = item.getAmount();
sendPacketToViewersAndSelf(collectItemPacket);
objectEntity.remove();
});
}
}
}
}
}
}
@Override
public Buffer getMetadataBuffer() {
Buffer buffer = super.getMetadataBuffer();
@ -32,9 +73,25 @@ public abstract class LivingEntity extends Entity {
return buffer;
}
public boolean isDead() {
return isDead;
}
public boolean canPickupItem() {
return canPickupItem;
}
public void setCanPickupItem(boolean canPickupItem) {
this.canPickupItem = canPickupItem;
}
public void refreshActiveHand(boolean isHandActive, boolean offHand, boolean riptideSpinAttack) {
this.isHandActive = isHandActive;
this.activeHand = offHand;
this.riptideSpinAttack = riptideSpinAttack;
}
public void refreshIsDead(boolean isDead) {
this.isDead = isDead;
}
}

View File

@ -14,7 +14,6 @@ public abstract class ObjectEntity extends Entity {
@Override
public void addViewer(Player player) {
super.addViewer(player);
PlayerConnection playerConnection = player.getPlayerConnection();
SpawnObjectPacket spawnObjectPacket = new SpawnObjectPacket();
@ -25,6 +24,7 @@ public abstract class ObjectEntity extends Entity {
spawnObjectPacket.data = getObjectData();
playerConnection.sendPacket(spawnObjectPacket);
playerConnection.sendPacket(getMetadataPacket());
super.addViewer(player); // Add player to viewers list and send velocity packet
}
@Override

View File

@ -5,24 +5,23 @@ import fr.themode.minestom.bossbar.BossBar;
import fr.themode.minestom.chat.Chat;
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;
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.utils.BlockPosition;
import fr.themode.minestom.utils.Position;
import fr.themode.minestom.utils.Vector;
import fr.themode.minestom.world.Dimension;
import fr.themode.minestom.world.LevelType;
import java.io.File;
import java.util.Collections;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
@ -39,8 +38,7 @@ 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;
@ -53,18 +51,16 @@ public class Player extends LivingEntity {
private Set<BossBar> bossBars = new CopyOnWriteArraySet<>();
// Synchronization
private long synchronizationDelay = 1000; // In ms
private long lastSynchronizationTime;
// Vehicle
private float sideways;
private float forward;
private static Instance instance;
static {
ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo();
instance = Main.getInstanceManager().createInstance(new File("C:\\Users\\themo\\OneDrive\\Bureau\\Minestom data"));
//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;
@ -84,32 +80,29 @@ public class Player extends LivingEntity {
this.username = username;
this.playerConnection = playerConnection;
this.settings = new PlayerSettings();
this.inventory = new PlayerInventory(this);
/*setEventCallback(PickupItemEvent.class, event -> {
sendMessage("Hey you're trying to pick an item!");
event.setCancelled(true);
});*/
/*setEventCallback(StartDiggingEvent.class, event -> {
Random random = new Random();
boolean cancel = random.nextBoolean();
event.setCancelled(cancel);
sendMessage("Cancelled: " + cancel);
});*/
/*setEventCallback(BlockPlaceEvent.class, event -> {
event.setCancelled(true);
sendMessage("CANCELLED");
});*/
setCanPickupItem(true); // By default
setEventCallback(AttackEvent.class, event -> {
Entity entity = event.getTarget();
if (entity instanceof EntityCreature) {
((EntityCreature) entity).kill();
sendMessage("You killed an entity!");
} else if (entity instanceof Player) {
Player player = (Player) entity;
Vector velocity = getPosition().clone().getDirection().multiply(6);
velocity.setY(3.5f);
player.setVelocity(velocity, 150);
AnimationPacket animationPacket = new AnimationPacket();
animationPacket.entityId = player.getEntityId();
animationPacket.animation = AnimationPacket.Animation.TAKE_DAMAGE;
sendPacketToViewersAndSelf(animationPacket);
sendMessage("ATTACK");
}
sendMessage("ATTACK");
/*UpdateHealthPacket updateHealthPacket = new UpdateHealthPacket();
updateHealthPacket.health = -1f;
updateHealthPacket.food = 5;
@ -127,22 +120,27 @@ public class Player extends LivingEntity {
if (event.getHand() != Hand.MAIN)
return;
sendMessage("Save chunk data...");
/*sendMessage("Save chunk data...");
long time = System.currentTimeMillis();
getInstance().saveToFolder(() -> {
sendMessage("Saved in " + (System.currentTimeMillis() - time) + " ms");
});
});*/
for (Player player : Main.getConnectionManager().getOnlinePlayers()) {
player.teleport(getPosition());
}
});
setEventCallback(PickupItemEvent.class, event -> {
event.setCancelled(!getInventory().addItemStack(event.getItemStack())); // Cancel event if player does not have enough inventory space
});
// 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");
setGameMode(GameMode.CREATIVE);
setGameMode(GameMode.SURVIVAL);
teleport(new Position(0, 66, 0));
for (int cx = 0; cx < 4; cx++)
for (int cz = 0; cz < 4; cz++) {
@ -152,16 +150,43 @@ public class Player extends LivingEntity {
chickenCreature.setInstance(instance);
//chickenCreature.addPassenger(player);
}
/*for (int ix = 0; ix < 4; ix++)
for (int iz = 0; iz < 4; iz++) {
ItemEntity itemEntity = new ItemEntity(new ItemStack(1, (byte) 32));
itemEntity.refreshPosition(ix, 66, iz);
itemEntity.setNoGravity(true);
itemEntity.setInstance(instance);
//itemEntity.remove();
}*/
TeamsPacket teamsPacket = new TeamsPacket();
teamsPacket.teamName = "TEAMNAME" + new Random().nextInt(100);
teamsPacket.action = TeamsPacket.Action.CREATE_TEAM;
teamsPacket.teamDisplayName = Chat.rawText("WOWdisplay");
teamsPacket.nameTagVisibility = "always";
teamsPacket.teamColor = 2;
teamsPacket.teamPrefix = Chat.rawText("pre");
teamsPacket.teamSuffix = Chat.rawText("suf");
teamsPacket.collisionRule = "never";
teamsPacket.entities = new String[]{getUsername()};
System.out.println(getViewers().size());
sendPacketToViewersAndSelf(teamsPacket);
});
}
@Override
public void update() {
ClientPlayPacket packet = null;
playerConnection.flush();
ClientPlayPacket packet;
while ((packet = packets.poll()) != null) {
packet.process(this);
}
super.update(); // Super update (item pickup)
// Target block stage
if (targetCustomBlock != null) {
int timeBreak = targetCustomBlock.getBreakDelay(this);
@ -178,37 +203,6 @@ public class Player extends LivingEntity {
}
}
// Item pickup
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();
}
});
}
}
}
}
// Multiplayer sync
Position position = getPosition();
@ -226,17 +220,12 @@ public class Player extends LivingEntity {
entityLookAndRelativeMovePacket.pitch = position.getPitch();
entityLookAndRelativeMovePacket.onGround = onGround;
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
entityHeadLookPacket.entityId = getEntityId();
entityHeadLookPacket.yaw = position.getYaw();
lastX = position.getX();
lastY = position.getY();
lastZ = position.getZ();
lastYaw = position.getYaw();
lastPitch = position.getPitch();
updatePacket = entityLookAndRelativeMovePacket;
optionalUpdatePacket = entityHeadLookPacket;
} else if (positionChanged) {
EntityRelativeMovePacket entityRelativeMovePacket = new EntityRelativeMovePacket();
entityRelativeMovePacket.entityId = getEntityId();
@ -255,15 +244,18 @@ public class Player extends LivingEntity {
entityLookPacket.pitch = position.getPitch();
entityLookPacket.onGround = onGround;
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
entityHeadLookPacket.entityId = getEntityId();
entityHeadLookPacket.yaw = position.getYaw();
lastYaw = position.getYaw();
lastPitch = position.getPitch();
updatePacket = entityLookPacket;
}
if (viewChanged) {
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
entityHeadLookPacket.entityId = getEntityId();
entityHeadLookPacket.yaw = position.getYaw();
optionalUpdatePacket = entityHeadLookPacket;
}
if (updatePacket != null) {
if (optionalUpdatePacket != null) {
sendPacketsToViewers(updatePacket, optionalUpdatePacket);
@ -271,20 +263,11 @@ public class Player extends LivingEntity {
sendPacketToViewers(updatePacket);
}
}
playerConnection.sendPacket(new UpdateViewPositionPacket(instance.getChunkAt(getPosition())));
}
@Override
public void spawn() {
// Synchronization
long time = System.currentTimeMillis();
if (time - lastSynchronizationTime >= synchronizationDelay) {
lastSynchronizationTime = System.currentTimeMillis();
for (Player viewer : getViewers()) {
EntityTeleportPacket teleportPacket = new EntityTeleportPacket();
teleportPacket.entityId = viewer.getEntityId();
teleportPacket.position = viewer.getPosition();
teleportPacket.onGround = viewer.onGround;
playerConnection.sendPacket(teleportPacket);
}
}
}
@Override
@ -305,6 +288,7 @@ public class Player extends LivingEntity {
connection.sendPacket(pInfoPacket);
connection.sendPacket(spawnPlayerPacket);
connection.sendPacket(getMetadataPacket());
for (EntityEquipmentPacket.Slot slot : EntityEquipmentPacket.Slot.values()) {
syncEquipment(slot); // TODO only send packets to "player" and not all viewers
@ -327,6 +311,12 @@ public class Player extends LivingEntity {
super.setInstance(instance);
}
@Override
public void kill() {
this.isDead = true;
// TODO set health to -1
}
public void sendBlockBreakAnimation(BlockPosition blockPosition, byte destroyStage) {
BlockBreakAnimationPacket breakAnimationPacket = new BlockBreakAnimationPacket();
breakAnimationPacket.entityId = getEntityId() + 1;
@ -342,6 +332,7 @@ public class Player extends LivingEntity {
@Override
public void teleport(Position position) {
super.teleport(position); // Send new position to all viewers directly
if (isChunkUnloaded(position.getX(), position.getZ()))
return;

View File

@ -1,6 +1,7 @@
package fr.themode.minestom.entity.demo;
import fr.themode.minestom.entity.EntityCreature;
import fr.themode.minestom.utils.Vector;
public class ChickenCreature extends EntityCreature {
@ -10,6 +11,7 @@ public class ChickenCreature extends EntityCreature {
@Override
public void update() {
super.update();
float speed = 0.05f;
/*if (hasPassenger()) {
@ -42,6 +44,11 @@ public class ChickenCreature extends EntityCreature {
}
}*/
move(0, 0, speed);
//move(0, 0, speed);
}
@Override
public void spawn() {
setVelocity(new Vector(0, 1, 0), 1000);
}
}

View File

@ -12,13 +12,22 @@ public class TestArrow extends ObjectEntity {
this.shooter = shooter;
}
@Override
public void update() {
}
@Override
public void spawn() {
}
@Override
public int getObjectData() {
return shooter.getEntityId() + 1;
}
@Override
public void update() {
public LivingEntity getShooter() {
return shooter;
}
}

View File

@ -11,11 +11,11 @@ public class BlockBatch implements BlockModifier {
private static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(2);
private Instance instance;
private InstanceContainer instance;
private Map<Chunk, List<BlockData>> data = new HashMap<>();
public BlockBatch(Instance instance) {
public BlockBatch(InstanceContainer instance) {
this.instance = instance;
}
@ -55,7 +55,7 @@ public class BlockBatch implements BlockModifier {
for (Map.Entry<Chunk, List<BlockData>> entry : data.entrySet()) {
Chunk chunk = entry.getKey();
List<BlockData> dataList = entry.getValue();
batchesPool.submit(() -> {
batchesPool.execute(() -> {
synchronized (chunk) {
for (BlockData data : dataList) {
data.apply(chunk);

View File

@ -124,10 +124,6 @@ public class Chunk {
}
}
public short[] getBlocksId() {
return blocksId;
}
public Biome getBiome() {
return biome;
}
@ -176,7 +172,7 @@ public class Chunk {
// TODO customblock id map (StringId -> short id)
// TODO List of (sectionId;blockcount;blocktype;blockarray)
// TODO block data
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++) {

View File

@ -12,12 +12,12 @@ public class ChunkBatch implements BlockModifier {
private static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_CHUNK_BATCH);
private Instance instance;
private InstanceContainer instance;
private Chunk chunk;
private List<BlockData> dataList = new ArrayList<>();
public ChunkBatch(Instance instance, Chunk chunk) {
public ChunkBatch(InstanceContainer instance, Chunk chunk) {
this.instance = instance;
this.chunk = chunk;
}
@ -46,7 +46,7 @@ public class ChunkBatch implements BlockModifier {
public void flush(Consumer<Chunk> callback) {
synchronized (chunk) {
batchesPool.submit(() -> {
batchesPool.execute(() -> {
for (BlockData data : dataList) {
data.apply(chunk);
}

View File

@ -25,7 +25,7 @@ public class ChunkLoaderIO {
}
protected void saveChunk(Chunk chunk, File folder, Runnable callback) {
chunkLoaderPool.submit(() -> {
chunkLoaderPool.execute(() -> {
File chunkFile = getChunkFile(chunk.getChunkX(), chunk.getChunkZ(), folder);
try (FileOutputStream fos = new FileOutputStream(chunkFile)) {
byte[] data = chunk.getSerializedData();
@ -49,7 +49,7 @@ public class ChunkLoaderIO {
}
protected void loadChunk(int chunkX, int chunkZ, Instance instance, Consumer<Chunk> callback) {
chunkLoaderPool.submit(() -> {
chunkLoaderPool.execute(() -> {
File chunkFile = getChunkFile(chunkX, chunkZ, instance.getFolder());
if (!chunkFile.exists()) {
instance.createChunk(chunkX, chunkZ, callback); // Chunk file does not exist, create new chunk
@ -67,8 +67,8 @@ public class ChunkLoaderIO {
int decompressedLength = SerializerUtils.bytesToInt(array);
byte[] compressedChunkData = new byte[array.length - 4];
System.arraycopy(array, 4, compressedChunkData, 0, compressedChunkData.length); // Remove the decompressed length from the array
byte[] compressedChunkData = new byte[array.length - Integer.BYTES];
System.arraycopy(array, Integer.BYTES, compressedChunkData, 0, compressedChunkData.length); // Remove the decompressed length from the array
byte[] decompressed = new byte[decompressedLength];
long result = Zstd.decompress(decompressed, compressedChunkData); // Decompressed in an array with the max size
@ -85,10 +85,10 @@ public class ChunkLoaderIO {
chunk = new Chunk(biome, chunkX, chunkZ);
while (true) {
// TODO block data
int index = stream.readInt();
boolean isCustomBlock = stream.readBoolean();
short blockId = stream.readShort();
//System.out.println("id: " + blockId);
byte[] chunkPos = SerializerUtils.indexToChunkPosition(index);
if (isCustomBlock) {

View File

@ -5,327 +5,126 @@ import fr.themode.minestom.entity.Entity;
import fr.themode.minestom.entity.EntityCreature;
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.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.io.File;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.Collection;
import java.util.UUID;
import java.util.function.Consumer;
public class Instance implements BlockModifier {
public interface Instance extends BlockModifier {
private static ChunkLoaderIO chunkLoaderIO = new ChunkLoaderIO();
ChunkLoaderIO CHUNK_LOADER_IO = new ChunkLoaderIO();
private UUID uniqueId;
private File folder;
void breakBlock(Player player, BlockPosition blockPosition, short blockId);
private GroupedCollections<ObjectEntity> objectEntities = new GroupedCollections<>(new CopyOnWriteArrayList<>());
private GroupedCollections<EntityCreature> creatures = new GroupedCollections<>(new CopyOnWriteArrayList());
private GroupedCollections<Player> players = new GroupedCollections<>(new CopyOnWriteArrayList());
void loadChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
private ChunkGenerator chunkGenerator;
private Map<Long, Chunk> chunks = new ConcurrentHashMap<>();
Chunk getChunk(int chunkX, int chunkZ);
public Instance(UUID uniqueId, File folder) {
this.uniqueId = uniqueId;
this.folder = folder;
}
void saveToFolder(Runnable callback);
@Override
public synchronized void setBlock(int x, int y, int z, short blockId) {
Chunk chunk = getChunkAt(x, z);
synchronized (chunk) {
chunk.setBlock((byte) (x % 16), (byte) y, (byte) (z % 16), blockId);
chunk.refreshDataPacket();
sendChunkUpdate(chunk);
/*PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> {
chunk.setFullDataPacket(buffer);
sendChunkUpdate(chunk);
});*/
}
}
BlockBatch createBlockBatch();
@Override
public synchronized void setBlock(int x, int y, int z, String blockId) {
Chunk chunk = getChunkAt(x, z);
synchronized (chunk) {
chunk.setBlock((byte) (x % 16), (byte) y, (byte) (z % 16), blockId);
chunk.refreshDataPacket();
sendChunkUpdate(chunk);
/*PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> {
chunk.setFullDataPacket(buffer);
sendChunkUpdate(chunk);
});*/
}
}
ChunkBatch createChunkBatch(Chunk chunk);
public void breakBlock(Player player, BlockPosition blockPosition, short blockId) {
BlockBreakEvent blockBreakEvent = new BlockBreakEvent(blockPosition);
player.callEvent(BlockBreakEvent.class, blockBreakEvent);
if (!blockBreakEvent.isCancelled()) {
// TODO blockbreak setBlock result
int x = blockPosition.getX();
int y = blockPosition.getY();
int z = blockPosition.getZ();
setBlock(x, y, z, (short) 0);
ParticlePacket particlePacket = new ParticlePacket(); // TODO change to a proper particle API
particlePacket.particleId = 3; // Block particle
particlePacket.longDistance = false;
particlePacket.x = x + 0.5f;
particlePacket.y = y;
particlePacket.z = z + 0.5f;
particlePacket.offsetX = 0.4f;
particlePacket.offsetY = 0.6f;
particlePacket.offsetZ = 0.4f;
particlePacket.particleData = 0.3f;
particlePacket.particleCount = 75;
particlePacket.blockId = blockId;
player.getPlayerConnection().sendPacket(particlePacket);
player.sendPacketToViewers(particlePacket);
} else {
sendChunkUpdate(player, getChunkAt(blockPosition));
}
}
void setChunkGenerator(ChunkGenerator chunkGenerator);
public void breakBlock(Player player, BlockPosition blockPosition, CustomBlock customBlock) {
breakBlock(player, blockPosition, customBlock.getType());
}
Collection<Chunk> getChunks();
public void loadChunk(int chunkX, int chunkZ, Consumer<Chunk> callback) {
Chunk chunk = getChunk(chunkX, chunkZ);
if (chunk != null) {
if (callback != null)
callback.accept(chunk);
} else {
retrieveChunk(chunkX, chunkZ, callback);
}
}
GroupedCollections<ObjectEntity> getObjectEntities();
public void loadChunk(int chunkX, int chunkZ) {
loadChunk(chunkX, chunkZ, null);
}
GroupedCollections<EntityCreature> getCreatures();
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);
}
GroupedCollections<Player> getPlayers();
public boolean isChunkLoaded(int chunkX, int chunkZ) {
return getChunk(chunkX, chunkZ) != null;
}
UUID getUniqueId();
public short getBlockId(int x, int y, int z) {
Chunk chunk = getChunkAt(x, z);
return chunk.getBlockId((byte) (x % 16), (byte) y, (byte) (z % 16));
}
File getFolder();
public short getBlockId(BlockPosition blockPosition) {
return getBlockId(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
}
void setFolder(File folder);
public CustomBlock getCustomBlock(int x, int y, int z) {
Chunk chunk = getChunkAt(x, z);
return chunk.getCustomBlock((byte) (x % 16), (byte) y, (byte) (z % 16));
}
void sendChunkUpdate(Player player, Chunk chunk);
public BlockBatch createBlockBatch() {
return new BlockBatch(this);
}
void retrieveChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
public Chunk getChunk(int chunkX, int chunkZ) {
return chunks.get(getChunkKey(chunkX, chunkZ));
}
void createChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
public Chunk getChunkAt(double x, double z) {
int chunkX = Math.floorDiv((int) x, 16);
int chunkZ = Math.floorDiv((int) z, 16);
return getChunk(chunkX, chunkZ);
}
void sendChunks(Player player);
public Chunk getChunkAt(BlockPosition blockPosition) {
return getChunkAt(blockPosition.getX(), blockPosition.getZ());
}
SharedInstance createSharedInstance();
public Chunk getChunkAt(Position position) {
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;
}
public Collection<Chunk> getChunks() {
return Collections.unmodifiableCollection(chunks.values());
}
public void addEntity(Entity entity) {
Instance lastInstance = entity.getInstance();
if (lastInstance != null && lastInstance != this) {
lastInstance.removeEntity(entity);
}
// TODO based on distance with players
getPlayers().forEach(p -> entity.addViewer(p));
if (entity instanceof Player) {
Player player = (Player) entity;
sendChunks(player);
getObjectEntities().forEach(objectEntity -> objectEntity.addViewer(player));
getCreatures().forEach(entityCreature -> entityCreature.addViewer(player));
getPlayers().forEach(p -> p.addViewer(player));
}
Chunk chunk = getChunkAt(entity.getPosition());
chunk.addEntity(entity);
}
public void removeEntity(Entity entity) {
Instance entityInstance = entity.getInstance();
if (entityInstance == null || entityInstance != this)
return;
entity.getViewers().forEach(p -> entity.removeViewer(p));
if (!(entity instanceof Player)) {
DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket();
destroyEntitiesPacket.entityIds = new int[]{entity.getEntityId()};
entity.getViewers().forEach(p -> p.getPlayerConnection().sendPacket(destroyEntitiesPacket)); // TODO destroy batch
} else {
// TODO optimize (cache all entities that the player see)
Player player = (Player) entity;
getObjectEntities().forEach(objectEntity -> objectEntity.removeViewer(player));
getCreatures().forEach(entityCreature -> entityCreature.removeViewer(player));
getPlayers().forEach(p -> p.removeViewer(player));
}
Chunk chunk = getChunkAt(entity.getPosition());
chunk.removeEntity(entity);
}
public GroupedCollections<ObjectEntity> getObjectEntities() {
return objectEntities;
}
public GroupedCollections<EntityCreature> getCreatures() {
return creatures;
}
public GroupedCollections<Player> getPlayers() {
return players;
}
public UUID getUniqueId() {
return uniqueId;
}
public File getFolder() {
return folder;
}
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);
cacheChunk(chunk);
if (chunkGenerator != null) {
ChunkBatch chunkBatch = createChunkBatch(chunk);
chunkGenerator.generateChunkData(chunkBatch, chunkX, chunkZ);
chunkBatch.flush(callback);
}
}
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) {
return new ChunkBatch(this, chunk);
}
protected void sendChunkUpdate(Chunk chunk) {
if (getPlayers().isEmpty())
return;
//
default void sendChunkUpdate(Iterable<Player> players, Chunk chunk) {
Buffer chunkData = chunk.getFullDataPacket();
chunkData.getData().retain(getPlayers().size()).markReaderIndex();
getPlayers().forEach(player -> {
players.forEach(player -> {
player.getPlayerConnection().sendUnencodedPacket(chunkData);
chunkData.getData().resetReaderIndex();
});
}
private void sendChunks(Player player) {
for (Chunk chunk : getChunks()) {
Buffer chunkData = chunk.getFullDataPacket();
if (chunkData == null) {
PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> {
buffer.getData().retain(1).markReaderIndex();
player.getPlayerConnection().sendUnencodedPacket(buffer);
buffer.getData().resetReaderIndex();
chunk.setFullDataPacket(buffer);
});
} else {
chunkData.getData().retain(1).markReaderIndex();
player.getPlayerConnection().sendUnencodedPacket(chunkData);
chunkData.getData().resetReaderIndex();
}
}
//
default void breakBlock(Player player, BlockPosition blockPosition, CustomBlock customBlock) {
breakBlock(player, blockPosition, customBlock.getType());
}
private long getChunkKey(int chunkX, int chunkZ) {
default void loadChunk(int chunkX, int chunkZ) {
loadChunk(chunkX, chunkZ, null);
}
default 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);
}
default short getBlockId(int x, int y, int z) {
Chunk chunk = getChunkAt(x, z);
return chunk.getBlockId((byte) (x % 16), (byte) y, (byte) (z % 16));
}
default short getBlockId(BlockPosition blockPosition) {
return getBlockId(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
}
default CustomBlock getCustomBlock(int x, int y, int z) {
Chunk chunk = getChunkAt(x, z);
return chunk.getCustomBlock((byte) (x % 16), (byte) y, (byte) (z % 16));
}
default Chunk getChunkAt(double x, double z) {
int chunkX = Math.floorDiv((int) x, 16);
int chunkZ = Math.floorDiv((int) z, 16);
return getChunk(chunkX, chunkZ);
}
default boolean isChunkLoaded(int chunkX, int chunkZ) {
return getChunk(chunkX, chunkZ) != null;
}
default Chunk getChunkAt(BlockPosition blockPosition) {
return getChunkAt(blockPosition.getX(), blockPosition.getZ());
}
default Chunk getChunkAt(Position position) {
return getChunkAt(position.getX(), position.getZ());
}
default void saveToFolder() {
saveToFolder(null);
}
default long getChunkKey(int chunkX, int chunkZ) {
return (((long) chunkX) << 32) | (chunkZ & 0xffffffffL);
}
// UNSAFE METHODS
void addEntity(Entity entity);
void removeEntity(Entity entity);
}

View File

@ -0,0 +1,294 @@
package fr.themode.minestom.instance;
import fr.adamaq01.ozao.net.Buffer;
import fr.themode.minestom.entity.Entity;
import fr.themode.minestom.entity.EntityCreature;
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.DestroyEntitiesPacket;
import fr.themode.minestom.net.packet.server.play.ParticlePacket;
import fr.themode.minestom.utils.BlockPosition;
import fr.themode.minestom.utils.GroupedCollections;
import java.io.File;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
/**
* InstanceContainer is an instance that contains chunks in contrary to SharedInstance.
*/
public class InstanceContainer implements Instance {
private UUID uniqueId;
private File folder;
// Entities present in this instance
private GroupedCollections<ObjectEntity> objectEntities = new GroupedCollections<>(new CopyOnWriteArrayList<>());
private GroupedCollections<EntityCreature> creatures = new GroupedCollections<>(new CopyOnWriteArrayList());
private GroupedCollections<Player> players = new GroupedCollections<>(new CopyOnWriteArrayList());
// Entities present in all shared instances referenced AND this instance
private GroupedCollections<ObjectEntity> referencedObjectEntities = new GroupedCollections<>(new CopyOnWriteArrayList<>());
private GroupedCollections<EntityCreature> referencedCreatures = new GroupedCollections<>(new CopyOnWriteArrayList());
private GroupedCollections<Player> referencedPlayers = new GroupedCollections<>(new CopyOnWriteArrayList());
private ChunkGenerator chunkGenerator;
private Map<Long, Chunk> chunks = new ConcurrentHashMap<>();
public InstanceContainer(UUID uniqueId, File folder) {
this.uniqueId = uniqueId;
this.folder = folder;
}
@Override
public synchronized void setBlock(int x, int y, int z, short blockId) {
Chunk chunk = getChunkAt(x, z);
synchronized (chunk) {
chunk.setBlock((byte) (x % 16), (byte) y, (byte) (z % 16), blockId);
PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> {
chunk.setFullDataPacket(buffer);
sendChunkUpdate(chunk);
});
}
}
@Override
public synchronized void setBlock(int x, int y, int z, String blockId) {
Chunk chunk = getChunkAt(x, z);
synchronized (chunk) {
chunk.setBlock((byte) (x % 16), (byte) y, (byte) (z % 16), blockId);
PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> {
chunk.setFullDataPacket(buffer);
sendChunkUpdate(chunk);
});
}
}
// TODO deplace
@Override
public void breakBlock(Player player, BlockPosition blockPosition, short blockId) {
BlockBreakEvent blockBreakEvent = new BlockBreakEvent(blockPosition);
player.callEvent(BlockBreakEvent.class, blockBreakEvent);
if (!blockBreakEvent.isCancelled()) {
// TODO blockbreak setBlock result
int x = blockPosition.getX();
int y = blockPosition.getY();
int z = blockPosition.getZ();
setBlock(x, y, z, (short) 0);
ParticlePacket particlePacket = new ParticlePacket(); // TODO change to a proper particle API
particlePacket.particleId = 3; // Block particle
particlePacket.longDistance = false;
particlePacket.x = x + 0.5f;
particlePacket.y = y;
particlePacket.z = z + 0.5f;
particlePacket.offsetX = 0.4f;
particlePacket.offsetY = 0.6f;
particlePacket.offsetZ = 0.4f;
particlePacket.particleData = 0.3f;
particlePacket.particleCount = 75;
particlePacket.blockId = blockId;
player.getPlayerConnection().sendPacket(particlePacket);
player.sendPacketToViewers(particlePacket);
} else {
sendChunkUpdate(player, getChunkAt(blockPosition));
}
}
@Override
public void loadChunk(int chunkX, int chunkZ, Consumer<Chunk> callback) {
Chunk chunk = getChunk(chunkX, chunkZ);
if (chunk != null) {
if (callback != null)
callback.accept(chunk);
} else {
retrieveChunk(chunkX, chunkZ, callback);
}
}
@Override
public Chunk getChunk(int chunkX, int chunkZ) {
return chunks.get(getChunkKey(chunkX, chunkZ));
}
@Override
public void saveToFolder(Runnable callback) {
if (folder == null)
throw new UnsupportedOperationException("You cannot save an instance without setting a folder.");
Iterator<Chunk> chunks = getChunks().iterator();
while (chunks.hasNext()) {
Chunk chunk = chunks.next();
boolean isLast = !chunks.hasNext();
CHUNK_LOADER_IO.saveChunk(chunk, getFolder(), isLast ? callback : null);
}
}
@Override
public BlockBatch createBlockBatch() {
return new BlockBatch(this);
}
@Override
public ChunkBatch createChunkBatch(Chunk chunk) {
return new ChunkBatch(this, chunk);
}
@Override
public void addEntity(Entity entity) {
Instance lastInstance = entity.getInstance();
if (lastInstance != null && lastInstance != this) {
lastInstance.removeEntity(entity);
}
// TODO based on distance with players
getPlayers().forEach(p -> entity.addViewer(p));
if (entity instanceof Player) {
Player player = (Player) entity;
sendChunks(player);
getObjectEntities().forEach(objectEntity -> objectEntity.addViewer(player));
getCreatures().forEach(entityCreature -> entityCreature.addViewer(player));
getPlayers().forEach(p -> p.addViewer(player));
}
Chunk chunk = getChunkAt(entity.getPosition());
chunk.addEntity(entity);
}
@Override
public void removeEntity(Entity entity) {
Instance entityInstance = entity.getInstance();
if (entityInstance == null || entityInstance != this)
return;
entity.getViewers().forEach(p -> entity.removeViewer(p));
if (!(entity instanceof Player)) {
DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket();
destroyEntitiesPacket.entityIds = new int[]{entity.getEntityId()};
entity.getViewers().forEach(p -> p.getPlayerConnection().sendPacket(destroyEntitiesPacket)); // TODO destroy batch
} else {
// TODO optimize (cache all entities that the player see)
Player player = (Player) entity;
getObjectEntities().forEach(objectEntity -> objectEntity.removeViewer(player));
getCreatures().forEach(entityCreature -> entityCreature.removeViewer(player));
getPlayers().forEach(p -> p.removeViewer(player));
}
Chunk chunk = getChunkAt(entity.getPosition());
chunk.removeEntity(entity);
}
@Override
public void sendChunkUpdate(Player player, Chunk chunk) {
Buffer chunkData = chunk.getFullDataPacket();
chunkData.getData().retain(1).markReaderIndex();
player.getPlayerConnection().sendUnencodedPacket(chunkData);
chunkData.getData().resetReaderIndex();
}
@Override
public void retrieveChunk(int chunkX, int chunkZ, Consumer<Chunk> callback) {
if (folder != null) {
// Load from file if possible
CHUNK_LOADER_IO.loadChunk(chunkX, chunkZ, this, chunk -> {
cacheChunk(chunk);
if (callback != null)
callback.accept(chunk);
});
} else {
createChunk(chunkX, chunkZ, callback);
}
}
@Override
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);
cacheChunk(chunk);
if (chunkGenerator != null) {
ChunkBatch chunkBatch = createChunkBatch(chunk);
chunkGenerator.generateChunkData(chunkBatch, chunkX, chunkZ);
chunkBatch.flush(callback);
}
}
public void sendChunkUpdate(Chunk chunk) { // TODO work with referenced SharedInstances
if (getPlayers().isEmpty())
return;
sendChunkUpdate(getPlayers(), chunk);
}
@Override
public void sendChunks(Player player) {
for (Chunk chunk : getChunks()) {
Buffer chunkData = chunk.getFullDataPacket();
if (chunkData == null) {
PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> {
buffer.getData().retain(1).markReaderIndex();
player.getPlayerConnection().sendUnencodedPacket(buffer);
buffer.getData().resetReaderIndex();
chunk.setFullDataPacket(buffer);
});
} else {
chunkData.getData().retain(1).markReaderIndex();
player.getPlayerConnection().sendUnencodedPacket(chunkData);
chunkData.getData().resetReaderIndex();
}
}
}
@Override
public SharedInstance createSharedInstance() {
SharedInstance sharedInstance = new SharedInstance(this);
// TODO add list of entities
return sharedInstance;
}
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);
}
public void setChunkGenerator(ChunkGenerator chunkGenerator) {
this.chunkGenerator = chunkGenerator;
}
public Collection<Chunk> getChunks() {
return Collections.unmodifiableCollection(chunks.values());
}
public GroupedCollections<ObjectEntity> getObjectEntities() {
return objectEntities;
}
public GroupedCollections<EntityCreature> getCreatures() {
return creatures;
}
public GroupedCollections<Player> getPlayers() {
return players;
}
public UUID getUniqueId() {
return uniqueId;
}
public File getFolder() {
return folder;
}
public void setFolder(File folder) {
this.folder = folder;
}
}

View File

@ -11,7 +11,7 @@ public class InstanceManager {
private Set<Instance> instances = Collections.synchronizedSet(new HashSet<>());
public Instance createInstance(File folder) {
Instance instance = new Instance(UUID.randomUUID(), folder);
Instance instance = new InstanceContainer(UUID.randomUUID(), folder);
this.instances.add(instance);
return instance;
}

View File

@ -0,0 +1,145 @@
package fr.themode.minestom.instance;
import fr.themode.minestom.entity.Entity;
import fr.themode.minestom.entity.EntityCreature;
import fr.themode.minestom.entity.ObjectEntity;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.utils.BlockPosition;
import fr.themode.minestom.utils.GroupedCollections;
import java.io.File;
import java.util.Collection;
import java.util.UUID;
import java.util.function.Consumer;
/**
* Shared instance is an instance that share the same chunks as instanceContainer, entities are separated.
*/
public class SharedInstance implements Instance {
private InstanceContainer instanceContainer;
public SharedInstance(InstanceContainer instanceContainer) {
this.instanceContainer = instanceContainer;
}
@Override
public void breakBlock(Player player, BlockPosition blockPosition, short blockId) {
instanceContainer.breakBlock(player, blockPosition, blockId);
}
@Override
public void loadChunk(int chunkX, int chunkZ, Consumer<Chunk> callback) {
instanceContainer.loadChunk(chunkX, chunkZ, callback);
}
@Override
public Chunk getChunk(int chunkX, int chunkZ) {
return instanceContainer.getChunk(chunkX, chunkZ);
}
@Override
public void saveToFolder(Runnable callback) {
instanceContainer.saveToFolder(callback);
}
@Override
public BlockBatch createBlockBatch() {
return instanceContainer.createBlockBatch();
}
@Override
public ChunkBatch createChunkBatch(Chunk chunk) {
return instanceContainer.createChunkBatch(chunk);
}
@Override
public void setChunkGenerator(ChunkGenerator chunkGenerator) {
instanceContainer.setChunkGenerator(chunkGenerator);
}
@Override
public Collection<Chunk> getChunks() {
return instanceContainer.getChunks();
}
@Override
public GroupedCollections<ObjectEntity> getObjectEntities() {
return null;
}
@Override
public GroupedCollections<EntityCreature> getCreatures() {
return null;
}
@Override
public GroupedCollections<Player> getPlayers() {
return null;
}
@Override
public UUID getUniqueId() {
// FIXME: share same UUID ?
return null;
}
@Override
public File getFolder() {
return instanceContainer.getFolder();
}
@Override
public void setFolder(File folder) {
instanceContainer.setFolder(folder);
}
@Override
public void sendChunkUpdate(Player player, Chunk chunk) {
instanceContainer.sendChunkUpdate(player, chunk);
}
@Override
public void retrieveChunk(int chunkX, int chunkZ, Consumer<Chunk> callback) {
instanceContainer.retrieveChunk(chunkX, chunkZ, callback);
}
@Override
public void createChunk(int chunkX, int chunkZ, Consumer<Chunk> callback) {
instanceContainer.createChunk(chunkX, chunkZ, callback);
}
@Override
public void sendChunks(Player player) {
instanceContainer.sendChunks(player);
}
@Override
public SharedInstance createSharedInstance() {
return new SharedInstance(instanceContainer);
}
@Override
public void addEntity(Entity entity) {
}
@Override
public void removeEntity(Entity entity) {
}
@Override
public void setBlock(int x, int y, int z, short blockId) {
instanceContainer.setBlock(x, y, z, blockId);
}
@Override
public void setBlock(int x, int y, int z, String blockId) {
instanceContainer.setBlock(x, y, z, blockId);
}
public InstanceContainer getContainer() {
return instanceContainer;
}
}

View File

@ -41,6 +41,7 @@ public class PlayerPositionListener {
}
private static void processMovement(Player player, float x, float y, float z, Runnable runnable) {
//System.out.println("MOVEMENT PACKET " + Math.round(x) + ":" + Math.round(y) + ":" + Math.round(z));
boolean chunkTest = player.isChunkUnloaded(x, z);
if (chunkTest) {
player.teleport(player.getPosition());

View File

@ -17,6 +17,7 @@ public class StatusListener {
player.getPlayerConnection().sendPacket(respawnPacket);
PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(player.getPosition());
player.callEvent(PlayerRespawnEvent.class, respawnEvent);
player.refreshIsDead(false);
player.teleport(respawnEvent.getRespawnPosition());
player.getInventory().update();
break;

View File

@ -15,7 +15,7 @@ public class PacketWriter {
private static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_PACKET_WRITER);
public static void writeCallbackPacket(ServerPacket serverPacket, Consumer<Buffer> consumer) {
batchesPool.submit(() -> {
batchesPool.execute(() -> {
Packet p = PacketUtils.writePacket(serverPacket);
consumer.accept(PacketUtils.encode(p)); // TODO accept in another thread?
});

View File

@ -22,7 +22,7 @@ public class ResponsePacket implements ServerPacket {
" ]\n" +
" },\t\n" +
" \"description\": {\n" +
" \"text\": \"Wallah les cubes\"\n" +
" \"text\": \"Hey guys!\"\n" +
" },\n" +
" \"favicon\": \"data:image/png;base64,<data>\"\n" +
"}";

View File

@ -37,17 +37,15 @@ public class TeamsPacket implements ServerPacket {
break;
case REMOVE_TEAM:
break;
case ADD_PLAYERS_TEAM:
case REMOVE_PLAYERS_TEAM:
Utils.writeVarInt(buffer, entities.length);
for (String entity : entities) {
Utils.writeString(buffer, entity);
}
break;
}
if (action == Action.CREATE_TEAM) {
if (action == Action.CREATE_TEAM || action == Action.ADD_PLAYERS_TEAM || action == Action.REMOVE_PLAYERS_TEAM) {
if (entities == null) {
Utils.writeVarInt(buffer, 0);
return;
}
Utils.writeVarInt(buffer, entities.length);
for (String entity : entities) {
Utils.writeString(buffer, entity);

View File

@ -48,14 +48,14 @@ public class PlayerConnection {
getChannel().write(packet.getData());
}
public void flush() {
getChannel().flush();
}
public void sendPacket(ServerPacket serverPacket) {
sendPacket(PacketUtils.writePacket(serverPacket));
}
public void flush() {
getChannel().flush();
}
public Connection getConnection() {
return connection;
}

View File

@ -0,0 +1,9 @@
package fr.themode.minestom.utils;
public class MathUtils {
public static float square(float num) {
return num * num;
}
}

View File

@ -28,7 +28,63 @@ public class Position {
}
public float getDistance(Position position) {
return (float) Math.sqrt(Math.pow(position.getX() - getX(), 2) + Math.pow(position.getY() - getY(), 2) + Math.pow(position.getZ() - getZ(), 2));
return (float) Math.sqrt(MathUtils.square(position.getX() - getX()) + MathUtils.square(position.getY() - getY()) + MathUtils.square(position.getZ() - getZ()));
}
/**
* Gets a unit-vector pointing in the direction that this Location is
* facing.
*
* @return a vector pointing the direction of this location's {@link
* #getPitch() pitch} and {@link #getYaw() yaw}
*/
public Vector getDirection() {
Vector vector = new Vector();
float rotX = this.getYaw();
float rotY = this.getPitch();
vector.setY((float) -Math.sin(Math.toRadians(rotY)));
double xz = Math.cos(Math.toRadians(rotY));
vector.setX((float) (-xz * Math.sin(Math.toRadians(rotX))));
vector.setZ((float) (xz * Math.cos(Math.toRadians(rotX))));
return vector;
}
/**
* Sets the {@link #getYaw() yaw} and {@link #getPitch() pitch} to point
* in the direction of the vector.
*/
public Position setDirection(Vector vector) {
/*
* Sin = Opp / Hyp
* Cos = Adj / Hyp
* Tan = Opp / Adj
*
* x = -Opp
* z = Adj
*/
final double _2PI = 2 * Math.PI;
final float x = vector.getX();
final float z = vector.getZ();
if (x == 0 && z == 0) {
pitch = vector.getY() > 0 ? -90 : 90;
return this;
}
double theta = Math.atan2(-x, z);
yaw = (float) Math.toDegrees((theta + _2PI) % _2PI);
float x2 = MathUtils.square(x);
float z2 = MathUtils.square(z);
float xz = (float) Math.sqrt(x2 + z2);
pitch = (float) Math.toDegrees(Math.atan(-vector.getY() / xz));
return this;
}
public Position clone() {

View File

@ -0,0 +1,239 @@
package fr.themode.minestom.utils;
public class Vector implements Cloneable {
private static final double epsilon = 0.000001;
protected float x, y, z;
public Vector() {
this.x = 0;
this.y = 0;
this.z = 0;
}
public Vector(float x, float y, float z) {
this.x = x;
this.y = y;
this.z = z;
}
public Vector add(Vector vec) {
x += vec.x;
y += vec.y;
z += vec.z;
return this;
}
/**
* Subtracts a vector from this one.
*
* @param vec The other vector
* @return the same vector
*/
public Vector subtract(Vector vec) {
x -= vec.x;
y -= vec.y;
z -= vec.z;
return this;
}
/**
* Multiplies the vector by another.
*
* @param vec The other vector
* @return the same vector
*/
public Vector multiply(Vector vec) {
x *= vec.x;
y *= vec.y;
z *= vec.z;
return this;
}
/**
* Divides the vector by another.
*
* @param vec The other vector
* @return the same vector
*/
public Vector divide(Vector vec) {
x /= vec.x;
y /= vec.y;
z /= vec.z;
return this;
}
/**
* Copies another vector
*
* @param vec The other vector
* @return the same vector
*/
public Vector copy(Vector vec) {
x = vec.x;
y = vec.y;
z = vec.z;
return this;
}
/**
* Gets the magnitude of the vector, defined as sqrt(x^2+y^2+z^2). The
* value of this method is not cached and uses a costly square-root
* function, so do not repeatedly call this method to get the vector's
* magnitude. NaN will be returned if the inner result of the sqrt()
* function overflows, which will be caused if the length is too long.
*
* @return the magnitude
*/
public double length() {
return Math.sqrt(MathUtils.square(x) + MathUtils.square(y) + MathUtils.square(z));
}
/**
* Get the distance between this vector and another. The value of this
* method is not cached and uses a costly square-root function, so do not
* repeatedly call this method to get the vector's magnitude. NaN will be
* returned if the inner result of the sqrt() function overflows, which
* will be caused if the distance is too long.
*
* @param o The other vector
* @return the distance
*/
public double distance(Vector o) {
return Math.sqrt(MathUtils.square(x - o.x) + MathUtils.square(y - o.y) + MathUtils.square(z - o.z));
}
/**
* Performs scalar multiplication, multiplying all components with a
* scalar.
*
* @param m The factor
* @return the same vector
*/
public Vector multiply(int m) {
x *= m;
y *= m;
z *= m;
return this;
}
/**
* Performs scalar multiplication, multiplying all components with a
* scalar.
*
* @param m The factor
* @return the same vector
*/
public Vector multiply(double m) {
x *= m;
y *= m;
z *= m;
return this;
}
/**
* Performs scalar multiplication, multiplying all components with a
* scalar.
*
* @param m The factor
* @return the same vector
*/
public Vector multiply(float m) {
x *= m;
y *= m;
z *= m;
return this;
}
/**
* Converts this vector to a unit vector (a vector with length of 1).
*
* @return the same vector
*/
public Vector normalize() {
double length = length();
x /= length;
y /= length;
z /= length;
return this;
}
/**
* Zero this vector's components.
*
* @return the same vector
*/
public Vector zero() {
x = 0;
y = 0;
z = 0;
return this;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Vector)) {
return false;
}
Vector other = (Vector) obj;
return Math.abs(x - other.x) < epsilon && Math.abs(y - other.y) < epsilon && Math.abs(z - other.z) < epsilon && (this.getClass().equals(obj.getClass()));
}
/**
* Returns a hash code for this vector
*
* @return hash code
*/
@Override
public int hashCode() {
int hash = 7;
hash = 79 * hash + (int) (Double.doubleToLongBits(this.x) ^ (Double.doubleToLongBits(this.x) >>> 32));
hash = 79 * hash + (int) (Double.doubleToLongBits(this.y) ^ (Double.doubleToLongBits(this.y) >>> 32));
hash = 79 * hash + (int) (Double.doubleToLongBits(this.z) ^ (Double.doubleToLongBits(this.z) >>> 32));
return hash;
}
/**
* Get a new vector.
*
* @return vector
*/
@Override
public Vector clone() {
try {
return (Vector) super.clone();
} catch (CloneNotSupportedException e) {
throw new Error(e);
}
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
public float getZ() {
return z;
}
public void setZ(float z) {
this.z = z;
}
}