Auto chunk load & death manager

This commit is contained in:
TheMode 2019-08-25 20:03:43 +02:00
parent fc6f6afccc
commit e0eb35e81e
38 changed files with 601 additions and 310 deletions

View File

@ -31,6 +31,10 @@ public class Main {
public static final int TICK_MS = 50;
public static final int TICK_PER_SECOND = 1000 / TICK_MS;
// Config
public static final int CHUNK_VIEW_DISTANCE = 10;
public static final int ENTITY_VIEW_DISTANCE = 10; // TODO
// Networking
private static ConnectionManager connectionManager;
private static PacketProcessor packetProcessor;

View File

@ -16,6 +16,10 @@ public class Data {
return (T) data.get(key);
}
// TODO serialize
public <T> T getOrDefault(String key, T defaultValue) {
return (T) data.getOrDefault(key, defaultValue);
}
// TODO serialization
}

View File

@ -38,12 +38,14 @@ public abstract class Entity implements Viewable, DataContainer {
protected Instance instance;
protected Position position;
protected boolean onGround;
protected double lastX, lastY, lastZ;
protected float lastX, lastY, lastZ;
protected float lastYaw, lastPitch;
private int id;
protected Entity vehicle;
private Map<Class<Event>, Callback> eventCallbacks = new ConcurrentHashMap<>();
// Velocity
// TODO gravity implementation for entity other than players
protected Vector velocity = new Vector(); // Movement in block per second
private Set<Player> viewers = new CopyOnWriteArraySet<>();
private Data data;
private Set<Entity> passengers = new CopyOnWriteArraySet<>();
@ -54,10 +56,7 @@ public abstract class Entity implements Viewable, DataContainer {
private long scheduledRemoveTime;
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
private Map<Class<? extends Event>, Callback> eventCallbacks = new ConcurrentHashMap<>();
protected long velocityTime; // Reset velocity to 0 after countdown
// Synchronization
@ -106,22 +105,45 @@ public abstract class Entity implements Viewable, DataContainer {
// Called when entity a new instance is set
public abstract void spawn();
public void teleport(Position position) {
if (isChunkUnloaded(position.getX(), position.getZ()))
public void teleport(Position position, Runnable callback) {
if (instance == null)
return;
refreshPosition(position.getX(), position.getY(), position.getZ());
refreshView(position.getYaw(), position.getPitch());
EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket();
entityTeleportPacket.entityId = getEntityId();
entityTeleportPacket.position = position;
entityTeleportPacket.onGround = onGround;
sendPacketToViewers(entityTeleportPacket);
if (instance.hasEnabledAutoChunkLoad()) {
instance.loadChunk(position, chunk -> {
refreshPosition(position.getX(), position.getY(), position.getZ());
refreshView(position.getYaw(), position.getPitch());
EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket();
entityTeleportPacket.entityId = getEntityId();
entityTeleportPacket.position = position;
entityTeleportPacket.onGround = onGround;
sendPacketToViewers(entityTeleportPacket);
if (callback != null)
callback.run();
});
} else {
if (isChunkUnloaded(position.getX(), position.getZ()))
return;
refreshPosition(position.getX(), position.getY(), position.getZ());
refreshView(position.getYaw(), position.getPitch());
EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket();
entityTeleportPacket.entityId = getEntityId();
entityTeleportPacket.position = position;
entityTeleportPacket.onGround = onGround;
sendPacketToViewers(entityTeleportPacket);
if (callback != null)
callback.run();
}
}
public void teleport(Position position) {
teleport(position, null);
}
@Override
public void addViewer(Player player) {
this.viewers.add(player);
player.viewableEntity.add(this);
player.getPlayerConnection().sendPacket(getVelocityPacket());
}
@ -135,6 +157,7 @@ public abstract class Entity implements Viewable, DataContainer {
destroyEntitiesPacket.entityIds = new int[]{getEntityId()};
player.getPlayerConnection().sendPacket(destroyEntitiesPacket);
}
player.viewableEntity.remove(this);
}
@Override
@ -180,7 +203,7 @@ public abstract class Entity implements Viewable, DataContainer {
sendPacketToViewersAndSelf(getVelocityPacket());
} else {
float tps = Main.TICK_PER_SECOND;
refreshPosition(position.getX() + velocityX / tps, position.getY() + velocityY / tps, position.getZ() + velocityZ / tps);
refreshPosition(position.getX() + velocity.getX() / tps, position.getY() + velocity.getY() / tps, position.getZ() + velocity.getZ() / tps);
if (this instanceof ObjectEntity) {
sendPacketToViewers(getVelocityPacket());
} else if (this instanceof EntityCreature) {
@ -207,11 +230,11 @@ public abstract class Entity implements Viewable, DataContainer {
}
public <E extends Event> void setEventCallback(Class<E> eventClass, Callback<E> callback) {
this.eventCallbacks.put((Class<Event>) eventClass, callback);
this.eventCallbacks.put(eventClass, callback);
}
public <E extends Event> Callback<E> getEventCallback(Class<E> eventClass) {
return this.eventCallbacks.getOrDefault(eventClass, null);
return this.eventCallbacks.get(eventClass);
}
public <E extends Event> void callEvent(Class<E> eventClass, E event) {
@ -261,17 +284,17 @@ public abstract class Entity implements Viewable, DataContainer {
spawn();
}
public Vector getVelocity() {
return velocity;
}
public void setVelocity(Vector velocity, long velocityTime) {
this.velocityX = velocity.getX();
this.velocityY = velocity.getY();
this.velocityZ = velocity.getZ();
this.velocity = velocity;
this.velocityTime = System.currentTimeMillis() + velocityTime;
}
public void resetVelocity() {
this.velocityX = 0;
this.velocityY = 0;
this.velocityZ = 0;
this.velocity = new Vector();
this.velocityTime = 0;
}
@ -341,6 +364,9 @@ public abstract class Entity implements Viewable, DataContainer {
synchronized (instance) {
instance.removeEntityFromChunk(this, lastChunk);
instance.addEntityToChunk(this, newChunk);
if (this instanceof Player)
((Player) this).onChunkChange(lastChunk, newChunk); // Refresh loaded chunk
// TODO compare with viewers and remove if too far away
}
}
}
@ -388,9 +414,9 @@ public abstract class Entity implements Viewable, DataContainer {
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);
velocityPacket.velocityX = (short) (velocity.getX() * strength);
velocityPacket.velocityY = (short) (velocity.getY() * strength);
velocityPacket.velocityZ = (short) (velocity.getZ() * strength);
return velocityPacket;
}

View File

@ -1,5 +1,6 @@
package fr.themode.minestom.entity;
import fr.themode.minestom.event.DeathEvent;
import fr.themode.minestom.net.packet.server.play.EntityPacket;
import fr.themode.minestom.net.packet.server.play.EntityRelativeMovePacket;
import fr.themode.minestom.net.packet.server.play.SpawnMobPacket;
@ -19,11 +20,6 @@ public abstract class EntityCreature extends LivingEntity {
}
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;
@ -49,6 +45,8 @@ public abstract class EntityCreature extends LivingEntity {
this.isDead = true;
triggerStatus((byte) 3);
scheduleRemove(1000);
DeathEvent deathEvent = new DeathEvent();
callEvent(DeathEvent.class, deathEvent);
}
@Override

View File

@ -2,15 +2,18 @@ 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.event.PlayerSpawnEvent;
import fr.themode.minestom.instance.Chunk;
import fr.themode.minestom.instance.Instance;
import fr.themode.minestom.instance.InstanceManager;
import fr.themode.minestom.utils.ChunkUtils;
import fr.themode.minestom.utils.Position;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
public class EntityManager {
@ -24,7 +27,7 @@ public class EntityManager {
public void update() {
waitingPlayersTick();
for (Instance instance : instanceManager.getInstances()) {
testTick1(instance);
testTick2(instance);
}
}
@ -37,13 +40,24 @@ public class EntityManager {
PlayerLoginEvent loginEvent = new PlayerLoginEvent();
playerCache.callEvent(PlayerLoginEvent.class, loginEvent);
Instance spawningInstance = loginEvent.getSpawningInstance() == null ? instanceManager.createInstanceContainer() : 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);
PlayerSpawnPacket spawnPacket = new PlayerSpawnPacket();
playerCache.callEvent(PlayerSpawnPacket.class, spawnPacket);
});
Position position = playerCache.getPosition();
long[] visibleChunks = ChunkUtils.getVisibleChunks(position);
for (int i = 0; i < visibleChunks.length; i++) {
int[] chunkPos = ChunkUtils.getChunkCoord(visibleChunks[i]);
int chunkX = chunkPos[0];
int chunkZ = chunkPos[1];
boolean isLast = i == visibleChunks.length - 1;
Consumer<Chunk> callback = isLast ? chunk -> {
System.out.println("END CHUNK LOADING");
playerCache.spawned = true;
playerCache.setInstance(spawningInstance);
PlayerSpawnEvent spawnEvent = new PlayerSpawnEvent();
playerCache.callEvent(PlayerSpawnEvent.class, spawnEvent);
} : null;
spawningInstance.loadChunk(chunkX, chunkZ, callback); // TODO loadOptionalChunk for not loading chunks when autoload is false
}
});
}
}

View File

@ -3,7 +3,6 @@ package fr.themode.minestom.entity;
import fr.adamaq01.ozao.net.Buffer;
import fr.themode.minestom.item.ItemStack;
import fr.themode.minestom.utils.Utils;
import fr.themode.minestom.utils.Vector;
public class ItemEntity extends ObjectEntity {
@ -22,7 +21,7 @@ public class ItemEntity extends ObjectEntity {
@Override
public void spawn() {
setVelocity(new Vector(0, 1, 0), 5000);
// setVelocity(new Vector(0, 1, 0), 5000);
}
@Override

View File

@ -3,8 +3,11 @@ 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.data.Data;
import fr.themode.minestom.data.DataType;
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.InstanceContainer;
@ -17,6 +20,7 @@ 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.ChunkUtils;
import fr.themode.minestom.utils.Position;
import fr.themode.minestom.utils.Vector;
import fr.themode.minestom.world.Dimension;
@ -41,10 +45,30 @@ public class Player extends LivingEntity {
private GameMode gameMode;
private LevelType levelType;
static {
ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo();
//instance = Main.getInstanceManager().createInstance(new File("C:\\Users\\themo\\OneDrive\\Bureau\\Minestom data"));
instanceContainer = Main.getInstanceManager().createInstanceContainer();
instanceContainer.enableAutoChunkLoad(true);
instanceContainer.setChunkGenerator(chunkGeneratorDemo);
int loopStart = -3;
int loopEnd = 3;
long time = System.currentTimeMillis();
for (int x = loopStart; x < loopEnd; x++)
for (int z = loopStart; z < loopEnd; z++) {
instanceContainer.loadChunk(x, z);
}
System.out.println("Time to load all chunks: " + (System.currentTimeMillis() - time) + " ms");
}
protected Set<Entity> viewableEntity = new CopyOnWriteArraySet<>();
private float health;
private PlayerSettings settings;
private PlayerInventory inventory;
private short heldSlot;
private Inventory openInventory;
private int food;
private CustomBlock targetCustomBlock;
private BlockPosition targetBlockPosition;
@ -58,21 +82,7 @@ public class Player extends LivingEntity {
private float forward;
private static InstanceContainer instanceContainer;
static {
ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo();
//instance = Main.getInstanceManager().createInstance(new File("C:\\Users\\themo\\OneDrive\\Bureau\\Minestom data"));
instanceContainer = Main.getInstanceManager().createInstanceContainer();
instanceContainer.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++) {
instanceContainer.loadChunk(x, z);
}
System.out.println("Time to load all chunks: " + (System.currentTimeMillis() - time) + " ms");
}
private float foodSaturation;
protected boolean spawned;
@ -82,6 +92,8 @@ public class Player extends LivingEntity {
this.username = username;
this.playerConnection = playerConnection;
refreshHealth();
this.settings = new PlayerSettings();
this.inventory = new PlayerInventory(this);
@ -97,28 +109,17 @@ public class Player extends LivingEntity {
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);
player.damage(2);
sendMessage("ATTACK");
}
/*UpdateHealthPacket updateHealthPacket = new UpdateHealthPacket();
updateHealthPacket.health = -1f;
updateHealthPacket.food = 5;
updateHealthPacket.foodSaturation = 0;
playerConnection.sendPacket(updateHealthPacket);*/
});
setEventCallback(BlockPlaceEvent.class, event -> {
/*sendMessage("Placed block! " + event.getHand());
Data data = new Data();
data.set("test", 5, DataType.INTEGER);
setData(data);
setEventCallback(PlayerBlockPlaceEvent.class, event -> {
sendMessage("Placed block! " + event.getHand());
int value = getData().getOrDefault("test", 0);
getData().set("test", value + 1, DataType.INTEGER);
sendMessage("Data: " + getData().get("test"));*/
System.out.println("OLD DATA VALUE: " + value);
if (event.getHand() != Hand.MAIN)
return;
@ -128,9 +129,10 @@ public class Player extends LivingEntity {
sendMessage("Saved in " + (System.currentTimeMillis() - time) + " ms");
});*/
for (Player player : Main.getConnectionManager().getOnlinePlayers()) {
player.teleport(getPosition());
}
/*for (Player player : instance.getPlayers()) {
if (player != this)
player.teleport(getPosition());
}*/
});
setEventCallback(PickupItemEvent.class, event -> {
@ -139,19 +141,17 @@ public class Player extends LivingEntity {
setEventCallback(PlayerLoginEvent.class, event -> {
event.setSpawningInstance(instanceContainer);
setData(new Data());
});
setEventCallback(PlayerSpawnPacket.class, event -> {
setGameMode(GameMode.SURVIVAL);
setEventCallback(PlayerSpawnEvent.class, event -> {
System.out.println("SPAWN ");
setGameMode(GameMode.CREATIVE);
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(getInstance());
//chickenCreature.addPassenger(player);
}
ChickenCreature chickenCreature = new ChickenCreature();
chickenCreature.refreshPosition(2, 65, 2);
chickenCreature.setInstance(getInstance());
for (int ix = 0; ix < 4; ix++)
for (int iz = 0; iz < 4; iz++) {
@ -199,7 +199,7 @@ public class Player extends LivingEntity {
}
this.targetLastStage = stage;
if (stage > 9) {
instance.breakBlock(this, targetBlockPosition, targetCustomBlock);
instance.breakBlock(this, targetBlockPosition);
resetTargetBlock();
}
}
@ -271,6 +271,15 @@ public class Player extends LivingEntity {
}
@Override
public void remove() {
clearBossBars();
if (getOpenInventory() != null)
getOpenInventory().removeViewer(this);
this.viewableEntity.forEach(entity -> entity.removeViewer(this));
super.remove();
}
@Override
public void addViewer(Player player) {
super.addViewer(player);
@ -315,7 +324,7 @@ public class Player extends LivingEntity {
@Override
public void kill() {
this.isDead = true;
// TODO set health to -1
setHealth(-1);
}
public void sendBlockBreakAnimation(BlockPosition blockPosition, byte destroyStage) {
@ -331,19 +340,165 @@ public class Player extends LivingEntity {
playerConnection.sendPacket(chatMessagePacket);
}
@Override
public void teleport(Position position) {
super.teleport(position); // Send new position to all viewers directly
if (isChunkUnloaded(position.getX(), position.getZ()))
public void damage(float amount) {
AnimationPacket animationPacket = new AnimationPacket();
animationPacket.entityId = getEntityId();
animationPacket.animation = AnimationPacket.Animation.TAKE_DAMAGE;
sendPacketToViewersAndSelf(animationPacket);
setHealth(health - amount);
}
public float getHealth() {
return health;
}
public void setHealth(float health) {
this.health = health;
sendUpdateHealthPacket();
if (this.health <= 0) {
// Kill player
refreshIsDead(true);
EntityStatusPacket entityStatusPacket = new EntityStatusPacket();
entityStatusPacket.entityId = getEntityId();
entityStatusPacket.status = 3; // Death sound/animation
sendPacketToViewers(entityStatusPacket);
DeathEvent deathEvent = new DeathEvent();
callEvent(DeathEvent.class, deathEvent);
}
}
public int getFood() {
return food;
}
public void setFood(int food) {
this.food = food;
sendUpdateHealthPacket();
}
public float getFoodSaturation() {
return foodSaturation;
}
public void setFoodSaturation(float foodSaturation) {
this.foodSaturation = foodSaturation;
sendUpdateHealthPacket();
}
public void respawn() {
if (!isDead())
return;
refreshPosition(position.getX(), position.getY(), position.getZ());
refreshView(position.getYaw(), position.getPitch());
PlayerPositionAndLookPacket positionAndLookPacket = new PlayerPositionAndLookPacket();
positionAndLookPacket.position = position;
positionAndLookPacket.flags = 0x00;
positionAndLookPacket.teleportId = 67;
playerConnection.sendPacket(positionAndLookPacket);
refreshHealth();
RespawnPacket respawnPacket = new RespawnPacket();
respawnPacket.dimension = getDimension();
respawnPacket.gameMode = getGameMode();
respawnPacket.levelType = getLevelType();
getPlayerConnection().sendPacket(respawnPacket);
PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(getPosition());
callEvent(PlayerRespawnEvent.class, respawnEvent);
refreshIsDead(false);
// Runnable called when teleportation is successfull (after loading and sending necessary chunk)
teleport(respawnEvent.getRespawnPosition(), () -> {
getInventory().update();
SpawnPlayerPacket spawnPlayerPacket = new SpawnPlayerPacket();
spawnPlayerPacket.entityId = getEntityId();
spawnPlayerPacket.playerUuid = getUuid();
spawnPlayerPacket.position = getPosition();
sendPacketToViewers(spawnPlayerPacket);
});
}
private void refreshHealth() {
// TODO improve
this.health = 20;
this.food = 20;
this.foodSaturation = 5;
}
protected void sendUpdateHealthPacket() {
UpdateHealthPacket updateHealthPacket = new UpdateHealthPacket();
updateHealthPacket.health = health;
updateHealthPacket.food = food;
updateHealthPacket.foodSaturation = foodSaturation;
playerConnection.sendPacket(updateHealthPacket);
}
protected void onChunkChange(Chunk lastChunk, Chunk newChunk) {
long[] lastVisibleChunks = ChunkUtils.getVisibleChunks(new Position(16 * lastChunk.getChunkX(), 0, 16 * lastChunk.getChunkZ()));
long[] newVisibleChunks = ChunkUtils.getVisibleChunks(new Position(16 * newChunk.getChunkX(), 0, 16 * newChunk.getChunkZ()));
// Unload old chunks
for (int l = 0; l < lastVisibleChunks.length; l++) {
long lastVisibleChunk = lastVisibleChunks[l];
boolean contains = false;
for (int n = 0; n < newVisibleChunks.length; n++) {
long newVisibleChunk = newVisibleChunks[n];
if (newVisibleChunk == lastVisibleChunk) {
contains = true;
break;
}
}
if (!contains) {
int[] chunkPos = ChunkUtils.getChunkCoord(lastVisibleChunk);
UnloadChunkPacket unloadChunkPacket = new UnloadChunkPacket();
unloadChunkPacket.chunkX = chunkPos[0];
unloadChunkPacket.chunkZ = chunkPos[1];
playerConnection.sendPacket(unloadChunkPacket);
}
}
// Load new chunks
for (int n = 0; n < newVisibleChunks.length; n++) {
long newVisibleChunk = newVisibleChunks[n];
boolean contains = false;
for (int l = 0; l < lastVisibleChunks.length; l++) {
long lastVisibleChunk = lastVisibleChunks[l];
if (lastVisibleChunk == newVisibleChunk) {
contains = true;
break;
}
}
if (!contains) {
int[] chunkPos = ChunkUtils.getChunkCoord(newVisibleChunk);
instance.loadOptionalChunk(chunkPos[0], chunkPos[1], chunk -> {
if (chunk == null) {
return; // Cannot load chunk (autoload not enabled)
}
instance.sendChunk(this, chunk);
});
}
}
UpdateViewPositionPacket updateViewPositionPacket = new UpdateViewPositionPacket(newChunk);
playerConnection.sendPacket(updateViewPositionPacket);
}
@Override
public void teleport(Position position, Runnable callback) {
if (instance == null)
return;
super.teleport(position, () -> {
if (!instance.hasEnabledAutoChunkLoad() && isChunkUnloaded(position.getX(), position.getZ()))
return;
refreshPosition(position.getX(), position.getY(), position.getZ());
refreshView(position.getYaw(), position.getPitch());
PlayerPositionAndLookPacket positionAndLookPacket = new PlayerPositionAndLookPacket();
positionAndLookPacket.position = position;
positionAndLookPacket.flags = 0x00;
positionAndLookPacket.teleportId = 67;
playerConnection.sendPacket(positionAndLookPacket);
if (callback != null)
callback.run();
});
}
@Override
public void teleport(Position position) {
teleport(position, null);
}
public String getUsername() {
@ -465,6 +620,10 @@ public class Player extends LivingEntity {
inventory.update();
}
public void clearBossBars() {
this.bossBars.forEach(bossBar -> bossBar.removeViewer(this));
}
public void syncEquipment(EntityEquipmentPacket.Slot slot) {
EntityEquipmentPacket equipmentPacket = new EntityEquipmentPacket();
equipmentPacket.entityId = getEntityId();

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 {
@ -42,12 +43,11 @@ public class ChickenCreature extends EntityCreature {
move(x * speed, 0, z * speed);
}
}*/
move(0, 0, speed);
}
@Override
public void spawn() {
// setVelocity(new Vector(0, 1, 0), 1000);
setVelocity(new Vector(0, 1, 0), 3000);
}
}

View File

@ -0,0 +1,7 @@
package fr.themode.minestom.event;
public class DeathEvent extends Event {
// TODO cause
}

View File

@ -1,4 +1,5 @@
package fr.themode.minestom.event;
public class Event {
}

View File

@ -1,16 +0,0 @@
package fr.themode.minestom.event;
import fr.themode.minestom.entity.Entity;
public class InteractEvent extends Event {
private Entity target;
public InteractEvent(Entity target) {
this.target = target;
}
public Entity getTarget() {
return target;
}
}

View File

@ -2,11 +2,11 @@ package fr.themode.minestom.event;
import fr.themode.minestom.utils.BlockPosition;
public class BlockBreakEvent extends CancellableEvent {
public class PlayerBlockBreakEvent extends CancellableEvent {
private BlockPosition blockPosition;
public BlockBreakEvent(BlockPosition blockPosition) {
public PlayerBlockBreakEvent(BlockPosition blockPosition) {
this.blockPosition = blockPosition;
}

View File

@ -3,13 +3,13 @@ package fr.themode.minestom.event;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.utils.BlockPosition;
public class BlockPlaceEvent extends CancellableEvent {
public class PlayerBlockPlaceEvent extends CancellableEvent {
private short blockId;
private BlockPosition blockPosition;
private Player.Hand hand;
public BlockPlaceEvent(short blockId, BlockPosition blockPosition, Player.Hand hand) {
public PlayerBlockPlaceEvent(short blockId, BlockPosition blockPosition, Player.Hand hand) {
this.blockId = blockId;
this.blockPosition = blockPosition;
this.hand = hand;

View File

@ -0,0 +1,23 @@
package fr.themode.minestom.event;
import fr.themode.minestom.entity.Entity;
import fr.themode.minestom.entity.Player;
public class PlayerInteractEvent extends Event {
private Entity target;
private Player.Hand hand;
public PlayerInteractEvent(Entity target, Player.Hand hand) {
this.target = target;
this.hand = hand;
}
public Entity getTarget() {
return target;
}
public Player.Hand getHand() {
return hand;
}
}

View File

@ -0,0 +1,5 @@
package fr.themode.minestom.event;
public class PlayerSpawnEvent extends Event {
}

View File

@ -1,5 +0,0 @@
package fr.themode.minestom.event;
public class PlayerSpawnPacket extends Event {
}

View File

@ -2,11 +2,11 @@ package fr.themode.minestom.event;
import fr.themode.minestom.instance.CustomBlock;
public class StartDiggingEvent extends CancellableEvent {
public class PlayerStartDiggingEvent extends CancellableEvent {
private CustomBlock customBlock;
public StartDiggingEvent(CustomBlock customBlock) {
public PlayerStartDiggingEvent(CustomBlock customBlock) {
this.customBlock = customBlock;
}

View File

@ -3,12 +3,12 @@ package fr.themode.minestom.event;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.item.ItemStack;
public class UseItemEvent extends Event {
public class PlayerUseItemEvent extends Event {
private Player.Hand hand;
private ItemStack itemStack;
public UseItemEvent(Player.Hand hand, ItemStack itemStack) {
public PlayerUseItemEvent(Player.Hand hand, ItemStack itemStack) {
this.hand = hand;
this.itemStack = itemStack;
}

View File

@ -76,7 +76,7 @@ public class BlockBatch implements BlockModifier {
if (blockIdentifier == null) {
chunk.setBlock((byte) x, (byte) y, (byte) z, blockId);
} else {
chunk.setBlock((byte) x, (byte) y, (byte) z, blockIdentifier);
chunk.setCustomBlock((byte) x, (byte) y, (byte) z, blockIdentifier);
}
}

View File

@ -14,10 +14,10 @@ import java.util.concurrent.CopyOnWriteArraySet;
public class Chunk {
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;
public static final int CHUNK_SIZE_X = 16;
public static final int CHUNK_SIZE_Y = 256;
public static final int CHUNK_SIZE_Z = 16;
public static final int CHUNK_SIZE = CHUNK_SIZE_X * CHUNK_SIZE_Y * CHUNK_SIZE_Z;
private Biome biome;
private int chunkX, chunkZ;
@ -41,7 +41,7 @@ public class Chunk {
setBlock(x, y, z, blockId, (short) 0);
}
protected void setBlock(byte x, byte y, byte z, String blockId) {
protected void setCustomBlock(byte x, byte y, byte z, String blockId) {
CustomBlock customBlock = Main.getBlockManager().getBlock(blockId);
if (customBlock == null)
throw new IllegalArgumentException("The block " + blockId + " does not exist or isn't registered");
@ -151,15 +151,27 @@ public class Chunk {
}
protected ChunkDataPacket getFreshFullDataPacket() {
public ChunkDataPacket getFreshFullDataPacket() {
ChunkDataPacket fullDataPacket = new ChunkDataPacket();
fullDataPacket.chunk = this;
fullDataPacket.fullChunk = true;
return fullDataPacket;
}
public ChunkDataPacket getFreshPartialDataPacket() {
ChunkDataPacket fullDataPacket = new ChunkDataPacket();
fullDataPacket.chunk = this;
fullDataPacket.fullChunk = false;
return fullDataPacket;
}
protected void refreshDataPacket() {
Packet packet = PacketUtils.writePacket(getFreshFullDataPacket());
this.fullDataPacket = PacketUtils.encode(packet); // TODO write packet buffer in another thread (heavy calculations)
}
@Override
public String toString() {
return "Chunk[" + chunkX + ":" + chunkZ + "]";
}
}

View File

@ -50,6 +50,7 @@ public class ChunkBatch implements BlockModifier {
for (BlockData data : dataList) {
data.apply(chunk);
}
// System.out.println("FINISHED chunk creation " + chunk.getChunkX() + ":" + chunk.getChunkZ());
chunk.refreshDataPacket(); // TODO partial refresh instead of full
instance.sendChunkUpdate(chunk); // TODO partial chunk data
if (callback != null)
@ -68,7 +69,7 @@ public class ChunkBatch implements BlockModifier {
if (blockIdentifier == null) {
chunk.setBlock(x, y, z, blockId);
} else {
chunk.setBlock(x, y, z, blockIdentifier);
chunk.setCustomBlock(x, y, z, blockIdentifier);
}
}

View File

@ -5,8 +5,8 @@ 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.net.packet.server.play.DestroyEntitiesPacket;
import fr.themode.minestom.utils.BlockPosition;
import fr.themode.minestom.utils.ChunkUtils;
import fr.themode.minestom.utils.Position;
import java.io.File;
@ -30,10 +30,13 @@ public abstract class Instance implements BlockModifier {
this.uniqueId = uniqueId;
}
public abstract void breakBlock(Player player, BlockPosition blockPosition, short blockId);
public abstract void breakBlock(Player player, BlockPosition blockPosition);
public abstract void loadChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
// Load only if auto chunk load is enabled
public abstract void loadOptionalChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
public abstract Chunk getChunk(int chunkX, int chunkZ);
public abstract void saveToFolder(Runnable callback);
@ -52,12 +55,18 @@ public abstract class Instance implements BlockModifier {
public abstract void sendChunkUpdate(Player player, Chunk chunk);
public abstract void retrieveChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
protected abstract void retrieveChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
public abstract void createChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
public abstract void sendChunks(Player player);
public abstract void sendChunk(Player player, Chunk chunk);
public abstract void enableAutoChunkLoad(boolean enable);
public abstract boolean hasEnabledAutoChunkLoad();
//
protected void sendChunkUpdate(Collection<Player> players, Chunk chunk) {
Buffer chunkData = chunk.getFullDataPacket();
@ -82,11 +91,7 @@ public abstract class Instance implements BlockModifier {
}
public Set<Entity> getChunkEntities(Chunk chunk) {
return Collections.unmodifiableSet(getEntitiesInChunk(getChunkIndex(chunk.getChunkX(), chunk.getChunkZ())));
}
public void breakBlock(Player player, BlockPosition blockPosition, CustomBlock customBlock) {
breakBlock(player, blockPosition, customBlock.getType());
return Collections.unmodifiableSet(getEntitiesInChunk(ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ())));
}
public void loadChunk(int chunkX, int chunkZ) {
@ -139,24 +144,21 @@ public abstract class Instance implements BlockModifier {
return uniqueId;
}
protected long getChunkIndex(int chunkX, int chunkZ) {
return (((long) chunkX) << 32) | (chunkZ & 0xffffffffL);
}
// UNSAFE METHODS (need most of time to be synchronized)
public void addEntity(Entity entity) {
Instance lastInstance = entity.getInstance();
if (lastInstance != null && lastInstance != this) {
lastInstance.removeEntity(entity);
lastInstance.removeEntity(entity); // If entity is in another instance, remove it from there and add it to this
}
// TODO based on distance with players
getPlayers().forEach(p -> entity.addViewer(p));
getPlayers().forEach(p -> entity.addViewer(p)); // Add new entity to all players viewable list
if (entity instanceof Player) {
Player player = (Player) entity;
sendChunks(player);
// Send player all current entity in the instance
getObjectEntities().forEach(objectEntity -> objectEntity.addViewer(player));
getCreatures().forEach(entityCreature -> entityCreature.addViewer(player));
getPlayers().forEach(p -> p.addViewer(player));
@ -171,28 +173,14 @@ public abstract class Instance implements BlockModifier {
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));
}
entity.getViewers().forEach(p -> entity.removeViewer(p)); // Remove this entity from players viewable list and send delete entities packet
Chunk chunk = getChunkAt(entity.getPosition());
removeEntityFromChunk(entity, chunk);
}
public void addEntityToChunk(Entity entity, Chunk chunk) {
long chunkIndex = getChunkIndex(chunk.getChunkX(), chunk.getChunkZ());
long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ());
Set<Entity> entities = getEntitiesInChunk(chunkIndex);
entities.add(entity);
this.chunkEntities.put(chunkIndex, entities);
@ -206,10 +194,14 @@ public abstract class Instance implements BlockModifier {
}
public void removeEntityFromChunk(Entity entity, Chunk chunk) {
long chunkIndex = getChunkIndex(chunk.getChunkX(), chunk.getChunkZ());
long chunkIndex = ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ());
Set<Entity> entities = getEntitiesInChunk(chunkIndex);
entities.remove(entity);
this.chunkEntities.put(chunkIndex, entities);
if (entities.isEmpty()) {
this.chunkEntities.remove(chunkIndex);
} else {
this.chunkEntities.put(chunkIndex, entities);
}
if (entity instanceof Player) {
this.players.remove(entity);
} else if (entity instanceof EntityCreature) {

View File

@ -2,10 +2,11 @@ package fr.themode.minestom.instance;
import fr.adamaq01.ozao.net.Buffer;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.event.BlockBreakEvent;
import fr.themode.minestom.event.PlayerBlockBreakEvent;
import fr.themode.minestom.net.PacketWriter;
import fr.themode.minestom.net.packet.server.play.ParticlePacket;
import fr.themode.minestom.utils.BlockPosition;
import fr.themode.minestom.utils.ChunkUtils;
import java.io.File;
import java.util.*;
@ -25,6 +26,8 @@ public class InstanceContainer extends Instance {
private ChunkGenerator chunkGenerator;
private Map<Long, Chunk> chunks = new ConcurrentHashMap<>();
private boolean autoChunkLoad;
protected InstanceContainer(UUID uniqueId, File folder) {
super(uniqueId);
this.folder = folder;
@ -35,7 +38,7 @@ public class InstanceContainer extends Instance {
Chunk chunk = getChunkAt(x, z);
synchronized (chunk) {
chunk.setBlock((byte) (x % 16), (byte) y, (byte) (z % 16), blockId);
PacketWriter.writeCallbackPacket(chunk.getFreshFullDataPacket(), buffer -> {
PacketWriter.writeCallbackPacket(chunk.getFreshPartialDataPacket(), buffer -> {
chunk.setFullDataPacket(buffer);
sendChunkUpdate(chunk);
});
@ -46,8 +49,8 @@ public class InstanceContainer extends Instance {
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.setCustomBlock((byte) (x % 16), (byte) y, (byte) (z % 16), blockId);
PacketWriter.writeCallbackPacket(chunk.getFreshPartialDataPacket(), buffer -> {
chunk.setFullDataPacket(buffer);
sendChunkUpdate(chunk);
});
@ -56,9 +59,16 @@ public class InstanceContainer extends Instance {
// TODO deplace
@Override
public void breakBlock(Player player, BlockPosition blockPosition, short blockId) {
BlockBreakEvent blockBreakEvent = new BlockBreakEvent(blockPosition);
player.callEvent(BlockBreakEvent.class, blockBreakEvent);
public void breakBlock(Player player, BlockPosition blockPosition) {
Chunk chunk = getChunkAt(blockPosition);
short blockId = chunk.getBlockId((byte) (blockPosition.getX() % 16), (byte) blockPosition.getY(), (byte) (blockPosition.getZ() % 16));
if (blockId == 0) {
sendChunkUpdate(player, chunk);
return;
}
PlayerBlockBreakEvent blockBreakEvent = new PlayerBlockBreakEvent(blockPosition);
player.callEvent(PlayerBlockBreakEvent.class, blockBreakEvent);
if (!blockBreakEvent.isCancelled()) {
// TODO blockbreak setBlock result
int x = blockPosition.getX();
@ -71,16 +81,16 @@ public class InstanceContainer extends Instance {
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.offsetX = 0.45f;
particlePacket.offsetY = 0.55f;
particlePacket.offsetZ = 0.45f;
particlePacket.particleData = 0.3f;
particlePacket.particleCount = 75;
particlePacket.particleCount = 100;
particlePacket.blockId = blockId;
player.getPlayerConnection().sendPacket(particlePacket);
player.sendPacketToViewers(particlePacket);
} else {
sendChunkUpdate(player, getChunkAt(blockPosition));
sendChunkUpdate(player, chunk);
}
}
@ -95,9 +105,24 @@ public class InstanceContainer extends Instance {
}
}
@Override
public void loadOptionalChunk(int chunkX, int chunkZ, Consumer<Chunk> callback) {
Chunk chunk = getChunk(chunkX, chunkZ);
if (chunk != null) {
if (callback != null)
callback.accept(chunk);
} else {
if (hasEnabledAutoChunkLoad()) {
retrieveChunk(chunkX, chunkZ, callback);
} else {
callback.accept(null);
}
}
}
@Override
public Chunk getChunk(int chunkX, int chunkZ) {
return chunks.get(getChunkIndex(chunkX, chunkZ));
return chunks.get(ChunkUtils.getChunkIndex(chunkX, chunkZ));
}
@Override
@ -123,54 +148,6 @@ public class InstanceContainer extends Instance {
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();
@ -180,7 +157,7 @@ public class InstanceContainer extends Instance {
}
@Override
public void retrieveChunk(int chunkX, int chunkZ, Consumer<Chunk> callback) {
protected 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 -> {
@ -189,6 +166,7 @@ public class InstanceContainer extends Instance {
callback.accept(chunk);
});
} else {
// Folder isn't defined, create new chunk
createChunk(chunkX, chunkZ, callback);
}
}
@ -212,40 +190,54 @@ public class InstanceContainer extends Instance {
sendChunkUpdate(getPlayers(), chunk);
// Update for shared instances
this.sharedInstances.forEach(sharedInstance -> {
if (!sharedInstance.getPlayers().isEmpty())
sendChunkUpdate(sharedInstance.getPlayers(), chunk);
});
if (!sharedInstances.isEmpty())
this.sharedInstances.forEach(sharedInstance -> {
Set<Player> instancePlayers = sharedInstance.getPlayers();
if (!instancePlayers.isEmpty())
sendChunkUpdate(instancePlayers, 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();
}
sendChunk(player, chunk);
}
}
@Override
public void sendChunk(Player player, Chunk chunk) {
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 void enableAutoChunkLoad(boolean enable) {
this.autoChunkLoad = enable;
}
@Override
public boolean hasEnabledAutoChunkLoad() {
return autoChunkLoad;
}
protected void addSharedInstance(SharedInstance sharedInstance) {
this.sharedInstances.add(sharedInstance);
}
private void cacheChunk(Chunk chunk) {
//this.objectEntities.addCollection(chunk.objectEntities);
//this.creatures.addCollection(chunk.creatures);
//this.players.addCollection(chunk.players);
this.chunks.put(getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()), chunk);
this.chunks.put(ChunkUtils.getChunkIndex(chunk.getChunkX(), chunk.getChunkZ()), chunk);
}
public void setChunkGenerator(ChunkGenerator chunkGenerator) {

View File

@ -21,8 +21,8 @@ public class SharedInstance extends Instance {
}
@Override
public void breakBlock(Player player, BlockPosition blockPosition, short blockId) {
instanceContainer.breakBlock(player, blockPosition, blockId);
public void breakBlock(Player player, BlockPosition blockPosition) {
instanceContainer.breakBlock(player, blockPosition);
}
@Override
@ -30,6 +30,11 @@ public class SharedInstance extends Instance {
instanceContainer.loadChunk(chunkX, chunkZ, callback);
}
@Override
public void loadOptionalChunk(int chunkX, int chunkZ, Consumer<Chunk> callback) {
instanceContainer.loadOptionalChunk(chunkX, chunkZ, callback);
}
@Override
public Chunk getChunk(int chunkX, int chunkZ) {
return instanceContainer.getChunk(chunkX, chunkZ);
@ -60,12 +65,6 @@ public class SharedInstance extends Instance {
return instanceContainer.getChunks();
}
@Override
public UUID getUniqueId() {
// FIXME: share same UUID ?
return null;
}
@Override
public File getFolder() {
return instanceContainer.getFolder();
@ -96,6 +95,21 @@ public class SharedInstance extends Instance {
instanceContainer.sendChunks(player);
}
@Override
public void sendChunk(Player player, Chunk chunk) {
instanceContainer.sendChunk(player, chunk);
}
@Override
public void enableAutoChunkLoad(boolean enable) {
instanceContainer.enableAutoChunkLoad(enable);
}
@Override
public boolean hasEnabledAutoChunkLoad() {
return instanceContainer.hasEnabledAutoChunkLoad();
}
@Override
public void setBlock(int x, int y, int z, short blockId) {
instanceContainer.setBlock(x, y, z, blockId);

View File

@ -1,7 +1,7 @@
package fr.themode.minestom.listener;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.event.BlockPlaceEvent;
import fr.themode.minestom.event.PlayerBlockPlaceEvent;
import fr.themode.minestom.instance.Chunk;
import fr.themode.minestom.instance.Instance;
import fr.themode.minestom.net.packet.client.play.ClientPlayerBlockPlacementPacket;
@ -23,9 +23,10 @@ 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, packet.hand);
player.callEvent(BlockPlaceEvent.class, blockPlaceEvent);
if (!blockPlaceEvent.isCancelled()) {
PlayerBlockPlaceEvent playerBlockPlaceEvent = new PlayerBlockPlaceEvent((short) 10, blockPosition, packet.hand);
player.callEvent(PlayerBlockPlaceEvent.class, playerBlockPlaceEvent);
// TODO if player does not have block in hand, then call InteractEvent with blockPosition
if (!playerBlockPlaceEvent.isCancelled()) {
instance.setBlock(blockPosition, "custom_block");
// TODO consume block in hand for survival players
} else {

View File

@ -2,7 +2,7 @@ package fr.themode.minestom.listener;
import fr.themode.minestom.entity.GameMode;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.event.StartDiggingEvent;
import fr.themode.minestom.event.PlayerStartDiggingEvent;
import fr.themode.minestom.instance.CustomBlock;
import fr.themode.minestom.instance.Instance;
import fr.themode.minestom.net.packet.client.play.ClientPlayerDiggingPacket;
@ -27,9 +27,9 @@ public class PlayerDiggingListener {
if (instance != null) {
CustomBlock customBlock = instance.getCustomBlock(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
if (customBlock != null) {
StartDiggingEvent startDiggingEvent = new StartDiggingEvent(customBlock);
player.callEvent(StartDiggingEvent.class, startDiggingEvent);
if (!startDiggingEvent.isCancelled()) {
PlayerStartDiggingEvent playerStartDiggingEvent = new PlayerStartDiggingEvent(customBlock);
player.callEvent(PlayerStartDiggingEvent.class, playerStartDiggingEvent);
if (!playerStartDiggingEvent.isCancelled()) {
player.refreshTargetBlock(customBlock, blockPosition);
}
addEffect(player);
@ -52,8 +52,7 @@ public class PlayerDiggingListener {
} else {
Instance instance = player.getInstance();
if (instance != null) {
short blockId = instance.getBlockId(blockPosition);
instance.breakBlock(player, blockPosition, blockId);
instance.breakBlock(player, blockPosition);
}
}
break;

View File

@ -1,25 +1,14 @@
package fr.themode.minestom.listener;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.event.PlayerRespawnEvent;
import fr.themode.minestom.net.packet.client.play.ClientStatusPacket;
import fr.themode.minestom.net.packet.server.play.RespawnPacket;
public class StatusListener {
public static void listener(ClientStatusPacket packet, Player player) {
switch (packet.action) {
case PERFORM_RESPAWN:
RespawnPacket respawnPacket = new RespawnPacket();
respawnPacket.dimension = player.getDimension();
respawnPacket.gameMode = player.getGameMode();
respawnPacket.levelType = player.getLevelType();
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();
player.respawn();
break;
case REQUEST_STATS:
// TODO stats

View File

@ -1,10 +1,10 @@
package fr.themode.minestom.listener;
import fr.themode.minestom.entity.Entity;
import fr.themode.minestom.entity.EntityCreature;
import fr.themode.minestom.entity.LivingEntity;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.event.AttackEvent;
import fr.themode.minestom.event.InteractEvent;
import fr.themode.minestom.event.PlayerInteractEvent;
import fr.themode.minestom.net.packet.client.play.ClientUseEntityPacket;
public class UseEntityListener {
@ -15,17 +15,17 @@ 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
if (entity instanceof LivingEntity && ((LivingEntity) entity).isDead()) // Can't attack dead entities
return;
AttackEvent attackEvent = new AttackEvent(entity);
player.callEvent(AttackEvent.class, attackEvent);
} else if (type == ClientUseEntityPacket.Type.INTERACT) {
InteractEvent interactEvent = new InteractEvent(entity);
player.callEvent(InteractEvent.class, interactEvent);
PlayerInteractEvent playerInteractEvent = new PlayerInteractEvent(entity, packet.hand);
player.callEvent(PlayerInteractEvent.class, playerInteractEvent);
} else {
InteractEvent interactEvent = new InteractEvent(entity); // TODO find difference with INTERACT
player.callEvent(InteractEvent.class, interactEvent);
PlayerInteractEvent playerInteractEvent = new PlayerInteractEvent(entity, packet.hand); // TODO find difference with INTERACT
player.callEvent(PlayerInteractEvent.class, playerInteractEvent);
}
}

View File

@ -1,7 +1,7 @@
package fr.themode.minestom.listener;
import fr.themode.minestom.entity.Player;
import fr.themode.minestom.event.UseItemEvent;
import fr.themode.minestom.event.PlayerUseItemEvent;
import fr.themode.minestom.inventory.PlayerInventory;
import fr.themode.minestom.item.ItemStack;
import fr.themode.minestom.net.packet.client.play.ClientUseItemPacket;
@ -12,8 +12,8 @@ public class UseItemListener {
PlayerInventory inventory = player.getInventory();
Player.Hand hand = packet.hand;
ItemStack itemStack = hand == Player.Hand.MAIN ? inventory.getItemInMainHand() : inventory.getItemInOffHand();
UseItemEvent useItemEvent = new UseItemEvent(hand, itemStack);
player.callEvent(UseItemEvent.class, useItemEvent);
PlayerUseItemEvent playerUseItemEvent = new PlayerUseItemEvent(hand, itemStack);
player.callEvent(PlayerUseItemEvent.class, playerUseItemEvent);
// TODO check if item in main or off hand is food or item with animation (bow/crossbow/riptide)
// TODO in material enum?

View File

@ -80,7 +80,7 @@ public class LoginStartPacket implements ClientPreplayPacket {
joinGamePacket.dimension = dimension;
joinGamePacket.maxPlayers = 0; // Unused
joinGamePacket.levelType = levelType;
joinGamePacket.viewDistance = 14;
joinGamePacket.viewDistance = Main.CHUNK_VIEW_DISTANCE;
joinGamePacket.reducedDebugInfo = false;
connection.sendPacket(joinGamePacket);

View File

@ -82,11 +82,11 @@ public class ChunkDataPacket implements ServerPacket {
Utils.writeVarInt(buffer, blockEntities.size());
for (Integer index : blockEntities) {
BlockPosition blockPosition = SerializerUtils.indexToBlockPosition(index, chunk.getChunkX(), chunk.getChunkZ());
BlockPosition blockPosition = SerializerUtils.indexToChunkBlockPosition(index);
CompoundTag blockEntity = new CompoundTag();
blockEntity.put("x", new DoubleTag(blockPosition.getX()));
blockEntity.put("x", new DoubleTag(blockPosition.getX() + 16 * chunk.getChunkX()));
blockEntity.put("y", new DoubleTag(blockPosition.getY()));
blockEntity.put("z", new DoubleTag(blockPosition.getZ()));
blockEntity.put("z", new DoubleTag(blockPosition.getZ() + 16 * chunk.getChunkZ()));
ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
blockEntity.serialize(new DataOutputStream(os), 100);

View File

@ -12,6 +12,7 @@ public class SpawnPlayerPacket implements ServerPacket {
public int entityId;
public UUID playerUuid;
public Position position;
public Buffer metadata;
@Override
public void write(Buffer buffer) {
@ -23,7 +24,11 @@ public class SpawnPlayerPacket implements ServerPacket {
buffer.putDouble(position.getZ());
buffer.putByte((byte) (position.getYaw() * 256f / 360f));
buffer.putByte((byte) (position.getPitch() * 256f / 360f));
buffer.putByte((byte) 0xff); // TODO Metadata
if (metadata != null) {
buffer.putBuffer(metadata);
} else {
buffer.putByte((byte) 0xff);
}
}
@Override

View File

@ -0,0 +1,20 @@
package fr.themode.minestom.net.packet.server.play;
import fr.adamaq01.ozao.net.Buffer;
import fr.themode.minestom.net.packet.server.ServerPacket;
public class UnloadChunkPacket implements ServerPacket {
public int chunkX, chunkZ;
@Override
public void write(Buffer buffer) {
buffer.putInt(chunkX);
buffer.putInt(chunkZ);
}
@Override
public int getId() {
return 0x1D;
}
}

View File

@ -0,0 +1,35 @@
package fr.themode.minestom.utils;
import fr.themode.minestom.Main;
public class ChunkUtils {
public static long getChunkIndex(int chunkX, int chunkZ) {
return (((long) chunkX) << 32) | (chunkZ & 0xffffffffL);
}
public static int[] getChunkCoord(long index) {
int chunkX = (int) (index >> 32);
int chunkZ = (int) index;
return new int[]{chunkX, chunkZ};
}
public static long[] getVisibleChunks(final Position position) {
final int viewDistance = Main.CHUNK_VIEW_DISTANCE;
long[] visibleChunks = new long[MathUtils.square(viewDistance + 1)];
final int startLoop = -(viewDistance / 2);
final int endLoop = viewDistance / 2 + 1;
int counter = 0;
for (int x = startLoop; x < endLoop; x++) {
for (int z = startLoop; z < endLoop; z++) {
int chunkX = Math.floorDiv((int) (position.getX() + 16 * x), 16);
int chunkZ = Math.floorDiv((int) (position.getZ() + 16 * z), 16);
visibleChunks[counter] = getChunkIndex(chunkX, chunkZ);
counter++;
}
}
return visibleChunks;
}
}

View File

@ -2,6 +2,10 @@ package fr.themode.minestom.utils;
public class MathUtils {
public static int square(int num) {
return num * num;
}
public static float square(float num) {
return num * num;
}

View File

@ -21,10 +21,11 @@ public class Position {
this(0, 0, 0);
}
public void add(float x, float y, float z) {
public Position add(float x, float y, float z) {
this.x += x;
this.y += y;
this.z += z;
return this;
}
public float getDistance(Position position) {

View File

@ -32,11 +32,22 @@ public class SerializerUtils {
return new byte[]{x, y, z};
}
public static BlockPosition indexToBlockPosition(int index, int chunkX, int chunkZ) {
public static BlockPosition indexToChunkBlockPosition(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 * chunkX, y, z + 16 * chunkZ);
return new BlockPosition(x, y, z);
}
public static long positionToLong(int x, int y, int z) {
return (((long) x & 0x3FFFFFF) << 38) | (((long) z & 0x3FFFFFF) << 12) | ((long) y & 0xFFF);
}
public static BlockPosition longToBlockPosition(long value) {
int x = (int) (value >> 38);
int y = (int) (value & 0xFFF);
int z = (int) (value << 26 >> 38);
return new BlockPosition(x, y, z);
}
}

View File

@ -121,7 +121,7 @@ public class Utils {
}
public static void writePosition(Buffer buffer, int x, int y, int z) {
buffer.putLong((((long) x & 0x3FFFFFF) << 38) | (((long) z & 0x3FFFFFF) << 12) | ((long) y & 0xFFF));
buffer.putLong(SerializerUtils.positionToLong(x, y, z));
}
public static void writePosition(Buffer buffer, BlockPosition blockPosition) {
@ -129,11 +129,7 @@ public class Utils {
}
public static BlockPosition readPosition(Buffer buffer) {
long val = buffer.getLong();
int x = (int) (val >> 38);
int y = (int) (val & 0xFFF);
int z = (int) (val << 26 >> 38);
return new BlockPosition(x, y, z);
return SerializerUtils.longToBlockPosition(buffer.getLong());
}
public static void writeUuid(Buffer buffer, UUID uuid) {