mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-26 02:57:37 +01:00
Added velocity & WIP shared instances
This commit is contained in:
parent
7a557169bd
commit
c25c846dce
@ -5,7 +5,7 @@ plugins {
|
||||
group 'fr.themode.minestom'
|
||||
version '1.0'
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
sourceCompatibility = 1.11
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}*/
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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++) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
145
src/main/java/fr/themode/minestom/instance/SharedInstance.java
Normal file
145
src/main/java/fr/themode/minestom/instance/SharedInstance.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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());
|
||||
|
@ -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;
|
||||
|
@ -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?
|
||||
});
|
||||
|
@ -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" +
|
||||
"}";
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
9
src/main/java/fr/themode/minestom/utils/MathUtils.java
Normal file
9
src/main/java/fr/themode/minestom/utils/MathUtils.java
Normal file
@ -0,0 +1,9 @@
|
||||
package fr.themode.minestom.utils;
|
||||
|
||||
public class MathUtils {
|
||||
|
||||
public static float square(float num) {
|
||||
return num * num;
|
||||
}
|
||||
|
||||
}
|
@ -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() {
|
||||
|
239
src/main/java/fr/themode/minestom/utils/Vector.java
Normal file
239
src/main/java/fr/themode/minestom/utils/Vector.java
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user