mirror of
https://github.com/Minestom/Minestom.git
synced 2024-12-27 11:38:03 +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'
|
group 'fr.themode.minestom'
|
||||||
version '1.0'
|
version '1.0'
|
||||||
|
|
||||||
sourceCompatibility = 1.8
|
sourceCompatibility = 1.11
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -22,13 +22,14 @@ import java.lang.reflect.InvocationTargetException;
|
|||||||
public class Main {
|
public class Main {
|
||||||
|
|
||||||
// Thread number
|
// 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_IO = 2;
|
||||||
public static final int THREAD_COUNT_CHUNK_BATCH = 2;
|
public static final int THREAD_COUNT_CHUNK_BATCH = 2;
|
||||||
public static final int THREAD_COUNT_ENTITIES = 2;
|
public static final int THREAD_COUNT_ENTITIES = 1;
|
||||||
public static final int THREAD_COUNT_PLAYERS_ENTITIES = 2;
|
public static final int THREAD_COUNT_PLAYERS_ENTITIES = 3;
|
||||||
|
|
||||||
public static final int TICK_MS = 50;
|
public static final int TICK_MS = 50;
|
||||||
|
public static final int TICK_PER_SECOND = 1000 / TICK_MS;
|
||||||
|
|
||||||
// Networking
|
// Networking
|
||||||
private static ConnectionManager connectionManager;
|
private static ConnectionManager connectionManager;
|
||||||
@ -41,7 +42,7 @@ public class Main {
|
|||||||
private static BlockManager blockManager;
|
private static BlockManager blockManager;
|
||||||
private static EntityManager entityManager;
|
private static EntityManager entityManager;
|
||||||
|
|
||||||
public static void main(String[] args) throws InterruptedException {
|
public static void main(String[] args) {
|
||||||
connectionManager = new ConnectionManager();
|
connectionManager = new ConnectionManager();
|
||||||
packetProcessor = new PacketProcessor();
|
packetProcessor = new PacketProcessor();
|
||||||
packetListenerManager = new PacketListenerManager();
|
packetListenerManager = new PacketListenerManager();
|
||||||
@ -95,7 +96,7 @@ public class Main {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onException(Server server, Connection connection, Throwable cause) {
|
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";
|
//String perfMessage = "Online: " + getConnectionManager().getOnlinePlayers().size() + " Tick time: " + (TICK_MS - sleepTime) + " ms";
|
||||||
//getConnectionManager().getOnlinePlayers().forEach(player -> player.sendMessage(perfMessage));
|
//getConnectionManager().getOnlinePlayers().forEach(player -> player.sendMessage(perfMessage));
|
||||||
|
|
||||||
|
try {
|
||||||
Thread.sleep(sleepTime);
|
Thread.sleep(sleepTime);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,9 +23,12 @@ public interface Viewable {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
PacketWriter.writeCallbackPacket(packet, buffer -> {
|
PacketWriter.writeCallbackPacket(packet, buffer -> {
|
||||||
|
int size = getViewers().size();
|
||||||
|
if (size == 0)
|
||||||
|
return;
|
||||||
buffer.getData().retain(getViewers().size()).markReaderIndex();
|
buffer.getData().retain(getViewers().size()).markReaderIndex();
|
||||||
getViewers().forEach(player -> {
|
getViewers().forEach(player -> {
|
||||||
player.getPlayerConnection().sendUnencodedPacket(buffer);
|
player.getPlayerConnection().writeUnencodedPacket(buffer);
|
||||||
buffer.getData().resetReaderIndex();
|
buffer.getData().resetReaderIndex();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -39,7 +42,7 @@ public interface Viewable {
|
|||||||
PacketWriter.writeCallbackPacket(packet, buffer -> {
|
PacketWriter.writeCallbackPacket(packet, buffer -> {
|
||||||
buffer.getData().retain(getViewers().size()).markReaderIndex();
|
buffer.getData().retain(getViewers().size()).markReaderIndex();
|
||||||
getViewers().forEach(player -> {
|
getViewers().forEach(player -> {
|
||||||
player.getPlayerConnection().sendUnencodedPacket(buffer);
|
player.getPlayerConnection().writeUnencodedPacket(buffer);
|
||||||
buffer.getData().resetReaderIndex();
|
buffer.getData().resetReaderIndex();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -50,12 +53,12 @@ public interface Viewable {
|
|||||||
if (this instanceof Player) {
|
if (this instanceof Player) {
|
||||||
PacketWriter.writeCallbackPacket(packet, buffer -> {
|
PacketWriter.writeCallbackPacket(packet, buffer -> {
|
||||||
buffer.getData().retain(getViewers().size() + 1).markReaderIndex();
|
buffer.getData().retain(getViewers().size() + 1).markReaderIndex();
|
||||||
((Player) this).getPlayerConnection().sendUnencodedPacket(buffer);
|
((Player) this).getPlayerConnection().writeUnencodedPacket(buffer);
|
||||||
buffer.getData().resetReaderIndex();
|
buffer.getData().resetReaderIndex();
|
||||||
if (!getViewers().isEmpty()) {
|
if (!getViewers().isEmpty()) {
|
||||||
getViewers().forEach(player -> {
|
getViewers().forEach(player -> {
|
||||||
buffer.getData().resetReaderIndex();
|
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.net.packet.server.play.*;
|
||||||
import fr.themode.minestom.utils.Position;
|
import fr.themode.minestom.utils.Position;
|
||||||
import fr.themode.minestom.utils.Utils;
|
import fr.themode.minestom.utils.Utils;
|
||||||
|
import fr.themode.minestom.utils.Vector;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
@ -36,6 +37,7 @@ public abstract class Entity implements Viewable, DataContainer {
|
|||||||
|
|
||||||
protected Instance instance;
|
protected Instance instance;
|
||||||
protected Position position;
|
protected Position position;
|
||||||
|
protected boolean onGround;
|
||||||
protected double lastX, lastY, lastZ;
|
protected double lastX, lastY, lastZ;
|
||||||
protected float lastYaw, lastPitch;
|
protected float lastYaw, lastPitch;
|
||||||
private int id;
|
private int id;
|
||||||
@ -53,6 +55,15 @@ public abstract class Entity implements Viewable, DataContainer {
|
|||||||
private int entityType;
|
private int entityType;
|
||||||
private long lastUpdate;
|
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
|
// Metadata
|
||||||
protected boolean onFire;
|
protected boolean onFire;
|
||||||
protected boolean crouched;
|
protected boolean crouched;
|
||||||
@ -92,6 +103,9 @@ public abstract class Entity implements Viewable, DataContainer {
|
|||||||
|
|
||||||
public abstract void update();
|
public abstract void update();
|
||||||
|
|
||||||
|
// Called when entity a new instance is set
|
||||||
|
public abstract void spawn();
|
||||||
|
|
||||||
public void teleport(Position position) {
|
public void teleport(Position position) {
|
||||||
if (isChunkUnloaded(position.getX(), position.getZ()))
|
if (isChunkUnloaded(position.getX(), position.getZ()))
|
||||||
return;
|
return;
|
||||||
@ -101,13 +115,14 @@ public abstract class Entity implements Viewable, DataContainer {
|
|||||||
EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket();
|
EntityTeleportPacket entityTeleportPacket = new EntityTeleportPacket();
|
||||||
entityTeleportPacket.entityId = getEntityId();
|
entityTeleportPacket.entityId = getEntityId();
|
||||||
entityTeleportPacket.position = position;
|
entityTeleportPacket.position = position;
|
||||||
entityTeleportPacket.onGround = true;
|
entityTeleportPacket.onGround = onGround;
|
||||||
sendPacketToViewers(entityTeleportPacket);
|
sendPacketToViewers(entityTeleportPacket);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addViewer(Player player) {
|
public void addViewer(Player player) {
|
||||||
this.viewers.add(player);
|
this.viewers.add(player);
|
||||||
|
player.getPlayerConnection().sendPacket(getVelocityPacket());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -153,7 +168,36 @@ public abstract class Entity implements Viewable, DataContainer {
|
|||||||
remove();
|
remove();
|
||||||
return;
|
return;
|
||||||
} else if (shouldUpdate()) {
|
} 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();
|
update();
|
||||||
|
|
||||||
|
// Synchronization
|
||||||
|
if (time - lastSynchronizationTime >= synchronizationDelay) {
|
||||||
|
lastSynchronizationTime = System.currentTimeMillis();
|
||||||
|
sendPositionSynchronization();
|
||||||
|
}
|
||||||
|
|
||||||
this.lastUpdate = System.currentTimeMillis();
|
this.lastUpdate = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,6 +258,21 @@ public abstract class Entity implements Viewable, DataContainer {
|
|||||||
this.isActive = true;
|
this.isActive = true;
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
instance.addEntity(this);
|
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) {
|
public float getDistance(Entity entity) {
|
||||||
@ -252,6 +311,11 @@ public abstract class Entity implements Viewable, DataContainer {
|
|||||||
sendMetadataIndex(0);
|
sendMetadataIndex(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setGlowing(boolean glowing) {
|
||||||
|
this.glowing = glowing;
|
||||||
|
sendMetadataIndex(0);
|
||||||
|
}
|
||||||
|
|
||||||
public void setNoGravity(boolean noGravity) {
|
public void setNoGravity(boolean noGravity) {
|
||||||
this.noGravity = noGravity;
|
this.noGravity = noGravity;
|
||||||
sendMetadataIndex(5);
|
sendMetadataIndex(5);
|
||||||
@ -322,6 +386,16 @@ public abstract class Entity implements Viewable, DataContainer {
|
|||||||
this.scheduledRemoveTime = System.currentTimeMillis() + delay;
|
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() {
|
public EntityMetaDataPacket getMetadataPacket() {
|
||||||
EntityMetaDataPacket metaDataPacket = new EntityMetaDataPacket();
|
EntityMetaDataPacket metaDataPacket = new EntityMetaDataPacket();
|
||||||
metaDataPacket.entityId = getEntityId();
|
metaDataPacket.entityId = getEntityId();
|
||||||
@ -344,9 +418,11 @@ public abstract class Entity implements Viewable, DataContainer {
|
|||||||
EntityMetaDataPacket metaDataPacket = new EntityMetaDataPacket();
|
EntityMetaDataPacket metaDataPacket = new EntityMetaDataPacket();
|
||||||
metaDataPacket.entityId = getEntityId();
|
metaDataPacket.entityId = getEntityId();
|
||||||
metaDataPacket.data = buffer;
|
metaDataPacket.data = buffer;
|
||||||
sendPacketToViewers(metaDataPacket);
|
|
||||||
if (this instanceof Player) {
|
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);
|
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) {
|
private void fillAirTickMetaData(Buffer buffer) {
|
||||||
buffer.putByte((byte) 1);
|
buffer.putByte((byte) 1);
|
||||||
buffer.putByte(METADATA_VARINT);
|
buffer.putByte(METADATA_VARINT);
|
||||||
|
@ -9,13 +9,22 @@ import fr.themode.minestom.utils.Position;
|
|||||||
// TODO viewers synchronization each X ticks?
|
// TODO viewers synchronization each X ticks?
|
||||||
public abstract class EntityCreature extends LivingEntity {
|
public abstract class EntityCreature extends LivingEntity {
|
||||||
|
|
||||||
protected boolean isDead;
|
|
||||||
|
|
||||||
public EntityCreature(int entityType) {
|
public EntityCreature(int entityType) {
|
||||||
super(entityType);
|
super(entityType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update() {
|
||||||
|
super.update();
|
||||||
|
}
|
||||||
|
|
||||||
public void move(float x, float y, float z) {
|
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();
|
Position position = getPosition();
|
||||||
float newX = position.getX() + x;
|
float newX = position.getX() + x;
|
||||||
float newY = position.getY() + y;
|
float newY = position.getY() + y;
|
||||||
@ -35,6 +44,7 @@ public abstract class EntityCreature extends LivingEntity {
|
|||||||
refreshPosition(newX, newY, newZ);
|
refreshPosition(newX, newY, newZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void kill() {
|
public void kill() {
|
||||||
this.isDead = true;
|
this.isDead = true;
|
||||||
triggerStatus((byte) 3);
|
triggerStatus((byte) 3);
|
||||||
@ -59,7 +69,4 @@ public abstract class EntityCreature extends LivingEntity {
|
|||||||
playerConnection.sendPacket(getMetadataPacket());
|
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.Chunk;
|
||||||
import fr.themode.minestom.instance.Instance;
|
import fr.themode.minestom.instance.Instance;
|
||||||
import fr.themode.minestom.instance.InstanceManager;
|
import fr.themode.minestom.instance.InstanceManager;
|
||||||
|
import fr.themode.minestom.utils.GroupedCollections;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
@ -24,19 +25,21 @@ public class EntityManager {
|
|||||||
public void update() {
|
public void update() {
|
||||||
waitingPlayersTick();
|
waitingPlayersTick();
|
||||||
for (Instance instance : instanceManager.getInstances()) {
|
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() {
|
private void waitingPlayersTick() {
|
||||||
Player waitingPlayer = null;
|
Player waitingPlayer;
|
||||||
while ((waitingPlayer = waitingPlayers.poll()) != null) {
|
while ((waitingPlayer = waitingPlayers.poll()) != null) {
|
||||||
final Player playerCache = waitingPlayer;
|
final Player playerCache = waitingPlayer;
|
||||||
playersPool.submit(() -> {
|
playersPool.execute(() -> {
|
||||||
PlayerLoginEvent loginEvent = new PlayerLoginEvent();
|
PlayerLoginEvent loginEvent = new PlayerLoginEvent();
|
||||||
playerCache.callEvent(PlayerLoginEvent.class, loginEvent);
|
playerCache.callEvent(PlayerLoginEvent.class, loginEvent);
|
||||||
Instance spawningInstance = loginEvent.getSpawningInstance() == null ? instanceManager.createInstance() : loginEvent.getSpawningInstance();
|
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 -> {
|
spawningInstance.loadChunk(playerCache.getPosition(), chunk -> {
|
||||||
playerCache.spawned = true;
|
playerCache.spawned = true;
|
||||||
playerCache.setInstance(spawningInstance);
|
playerCache.setInstance(spawningInstance);
|
||||||
@ -55,7 +58,7 @@ public class EntityManager {
|
|||||||
Set<Player> players = chunk.getPlayers();
|
Set<Player> players = chunk.getPlayers();
|
||||||
|
|
||||||
if (!creatures.isEmpty() || !objects.isEmpty()) {
|
if (!creatures.isEmpty() || !objects.isEmpty()) {
|
||||||
entitiesPool.submit(() -> {
|
entitiesPool.execute(() -> {
|
||||||
for (EntityCreature creature : creatures) {
|
for (EntityCreature creature : creatures) {
|
||||||
creature.tick();
|
creature.tick();
|
||||||
}
|
}
|
||||||
@ -66,7 +69,7 @@ public class EntityManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!players.isEmpty()) {
|
if (!players.isEmpty()) {
|
||||||
playersPool.submit(() -> {
|
playersPool.execute(() -> {
|
||||||
for (Player player : players) {
|
for (Player player : players) {
|
||||||
player.tick();
|
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) {
|
public void addWaitingPlayer(Player player) {
|
||||||
this.waitingPlayers.add(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.adamaq01.ozao.net.Buffer;
|
||||||
import fr.themode.minestom.item.ItemStack;
|
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.Utils;
|
||||||
|
import fr.themode.minestom.utils.Vector;
|
||||||
|
|
||||||
public class ItemEntity extends ObjectEntity {
|
public class ItemEntity extends ObjectEntity {
|
||||||
|
|
||||||
@ -17,11 +17,17 @@ public class ItemEntity extends ObjectEntity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update() {
|
public void update() {
|
||||||
// TODO how to keep items at the same position?
|
|
||||||
EntityRelativeMovePacket entityRelativeMovePacket = new EntityRelativeMovePacket();
|
}
|
||||||
entityRelativeMovePacket.entityId = getEntityId();
|
|
||||||
entityRelativeMovePacket.onGround = false;
|
@Override
|
||||||
sendPacketToViewers(entityRelativeMovePacket);
|
public void spawn() {
|
||||||
|
setVelocity(new Vector(0, 1, 0), 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addViewer(Player player) {
|
||||||
|
super.addViewer(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,11 +1,18 @@
|
|||||||
package fr.themode.minestom.entity;
|
package fr.themode.minestom.entity;
|
||||||
|
|
||||||
import fr.adamaq01.ozao.net.Buffer;
|
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
|
// TODO attributes https://wiki.vg/Protocol#Entity_Properties
|
||||||
public abstract class LivingEntity extends Entity {
|
public abstract class LivingEntity extends Entity {
|
||||||
|
|
||||||
protected boolean onGround;
|
protected boolean canPickupItem;
|
||||||
|
protected boolean isDead;
|
||||||
|
|
||||||
private boolean isHandActive;
|
private boolean isHandActive;
|
||||||
private boolean activeHand;
|
private boolean activeHand;
|
||||||
@ -15,6 +22,40 @@ public abstract class LivingEntity extends Entity {
|
|||||||
super(entityType);
|
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
|
@Override
|
||||||
public Buffer getMetadataBuffer() {
|
public Buffer getMetadataBuffer() {
|
||||||
Buffer buffer = super.getMetadataBuffer();
|
Buffer buffer = super.getMetadataBuffer();
|
||||||
@ -32,9 +73,25 @@ public abstract class LivingEntity extends Entity {
|
|||||||
return buffer;
|
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) {
|
public void refreshActiveHand(boolean isHandActive, boolean offHand, boolean riptideSpinAttack) {
|
||||||
this.isHandActive = isHandActive;
|
this.isHandActive = isHandActive;
|
||||||
this.activeHand = offHand;
|
this.activeHand = offHand;
|
||||||
this.riptideSpinAttack = riptideSpinAttack;
|
this.riptideSpinAttack = riptideSpinAttack;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void refreshIsDead(boolean isDead) {
|
||||||
|
this.isDead = isDead;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ public abstract class ObjectEntity extends Entity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addViewer(Player player) {
|
public void addViewer(Player player) {
|
||||||
super.addViewer(player);
|
|
||||||
PlayerConnection playerConnection = player.getPlayerConnection();
|
PlayerConnection playerConnection = player.getPlayerConnection();
|
||||||
|
|
||||||
SpawnObjectPacket spawnObjectPacket = new SpawnObjectPacket();
|
SpawnObjectPacket spawnObjectPacket = new SpawnObjectPacket();
|
||||||
@ -25,6 +24,7 @@ public abstract class ObjectEntity extends Entity {
|
|||||||
spawnObjectPacket.data = getObjectData();
|
spawnObjectPacket.data = getObjectData();
|
||||||
playerConnection.sendPacket(spawnObjectPacket);
|
playerConnection.sendPacket(spawnObjectPacket);
|
||||||
playerConnection.sendPacket(getMetadataPacket());
|
playerConnection.sendPacket(getMetadataPacket());
|
||||||
|
super.addViewer(player); // Add player to viewers list and send velocity packet
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -5,24 +5,23 @@ import fr.themode.minestom.bossbar.BossBar;
|
|||||||
import fr.themode.minestom.chat.Chat;
|
import fr.themode.minestom.chat.Chat;
|
||||||
import fr.themode.minestom.entity.demo.ChickenCreature;
|
import fr.themode.minestom.entity.demo.ChickenCreature;
|
||||||
import fr.themode.minestom.event.*;
|
import fr.themode.minestom.event.*;
|
||||||
import fr.themode.minestom.instance.Chunk;
|
|
||||||
import fr.themode.minestom.instance.CustomBlock;
|
import fr.themode.minestom.instance.CustomBlock;
|
||||||
import fr.themode.minestom.instance.Instance;
|
import fr.themode.minestom.instance.Instance;
|
||||||
import fr.themode.minestom.instance.demo.ChunkGeneratorDemo;
|
import fr.themode.minestom.instance.demo.ChunkGeneratorDemo;
|
||||||
import fr.themode.minestom.inventory.Inventory;
|
import fr.themode.minestom.inventory.Inventory;
|
||||||
import fr.themode.minestom.inventory.PlayerInventory;
|
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.client.ClientPlayPacket;
|
||||||
import fr.themode.minestom.net.packet.server.ServerPacket;
|
import fr.themode.minestom.net.packet.server.ServerPacket;
|
||||||
import fr.themode.minestom.net.packet.server.play.*;
|
import fr.themode.minestom.net.packet.server.play.*;
|
||||||
import fr.themode.minestom.net.player.PlayerConnection;
|
import fr.themode.minestom.net.player.PlayerConnection;
|
||||||
import fr.themode.minestom.utils.BlockPosition;
|
import fr.themode.minestom.utils.BlockPosition;
|
||||||
import fr.themode.minestom.utils.Position;
|
import fr.themode.minestom.utils.Position;
|
||||||
|
import fr.themode.minestom.utils.Vector;
|
||||||
import fr.themode.minestom.world.Dimension;
|
import fr.themode.minestom.world.Dimension;
|
||||||
import fr.themode.minestom.world.LevelType;
|
import fr.themode.minestom.world.LevelType;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Random;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||||
@ -39,8 +38,7 @@ public class Player extends LivingEntity {
|
|||||||
private Dimension dimension;
|
private Dimension dimension;
|
||||||
private GameMode gameMode;
|
private GameMode gameMode;
|
||||||
private LevelType levelType;
|
private LevelType levelType;
|
||||||
// DEBUG
|
|
||||||
private static Instance instance;
|
|
||||||
private PlayerSettings settings;
|
private PlayerSettings settings;
|
||||||
private PlayerInventory inventory;
|
private PlayerInventory inventory;
|
||||||
private short heldSlot;
|
private short heldSlot;
|
||||||
@ -53,18 +51,16 @@ public class Player extends LivingEntity {
|
|||||||
|
|
||||||
private Set<BossBar> bossBars = new CopyOnWriteArraySet<>();
|
private Set<BossBar> bossBars = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
// Synchronization
|
|
||||||
private long synchronizationDelay = 1000; // In ms
|
|
||||||
private long lastSynchronizationTime;
|
|
||||||
|
|
||||||
// Vehicle
|
// Vehicle
|
||||||
private float sideways;
|
private float sideways;
|
||||||
private float forward;
|
private float forward;
|
||||||
|
|
||||||
|
private static Instance instance;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo();
|
ChunkGeneratorDemo chunkGeneratorDemo = new ChunkGeneratorDemo();
|
||||||
instance = Main.getInstanceManager().createInstance(new File("C:\\Users\\themo\\OneDrive\\Bureau\\Minestom data"));
|
//instance = Main.getInstanceManager().createInstance(new File("C:\\Users\\themo\\OneDrive\\Bureau\\Minestom data"));
|
||||||
//instance = Main.getInstanceManager().createInstance();
|
instance = Main.getInstanceManager().createInstance();
|
||||||
instance.setChunkGenerator(chunkGeneratorDemo);
|
instance.setChunkGenerator(chunkGeneratorDemo);
|
||||||
int loopStart = -2;
|
int loopStart = -2;
|
||||||
int loopEnd = 2;
|
int loopEnd = 2;
|
||||||
@ -84,32 +80,29 @@ public class Player extends LivingEntity {
|
|||||||
this.username = username;
|
this.username = username;
|
||||||
this.playerConnection = playerConnection;
|
this.playerConnection = playerConnection;
|
||||||
|
|
||||||
|
this.settings = new PlayerSettings();
|
||||||
this.inventory = new PlayerInventory(this);
|
this.inventory = new PlayerInventory(this);
|
||||||
|
|
||||||
/*setEventCallback(PickupItemEvent.class, event -> {
|
setCanPickupItem(true); // By default
|
||||||
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");
|
|
||||||
});*/
|
|
||||||
|
|
||||||
setEventCallback(AttackEvent.class, event -> {
|
setEventCallback(AttackEvent.class, event -> {
|
||||||
Entity entity = event.getTarget();
|
Entity entity = event.getTarget();
|
||||||
if (entity instanceof EntityCreature) {
|
if (entity instanceof EntityCreature) {
|
||||||
((EntityCreature) entity).kill();
|
((EntityCreature) entity).kill();
|
||||||
sendMessage("You killed an entity!");
|
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 updateHealthPacket = new UpdateHealthPacket();
|
||||||
updateHealthPacket.health = -1f;
|
updateHealthPacket.health = -1f;
|
||||||
updateHealthPacket.food = 5;
|
updateHealthPacket.food = 5;
|
||||||
@ -127,22 +120,27 @@ public class Player extends LivingEntity {
|
|||||||
if (event.getHand() != Hand.MAIN)
|
if (event.getHand() != Hand.MAIN)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
sendMessage("Save chunk data...");
|
/*sendMessage("Save chunk data...");
|
||||||
long time = System.currentTimeMillis();
|
long time = System.currentTimeMillis();
|
||||||
getInstance().saveToFolder(() -> {
|
getInstance().saveToFolder(() -> {
|
||||||
sendMessage("Saved in " + (System.currentTimeMillis() - time) + " ms");
|
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 -> {
|
setEventCallback(PlayerLoginEvent.class, event -> {
|
||||||
System.out.println("PLAYER LOGIN EVENT");
|
|
||||||
event.setSpawningInstance(instance);
|
event.setSpawningInstance(instance);
|
||||||
});
|
});
|
||||||
|
|
||||||
setEventCallback(PlayerSpawnPacket.class, event -> {
|
setEventCallback(PlayerSpawnPacket.class, event -> {
|
||||||
System.out.println("TELEPORT");
|
setGameMode(GameMode.SURVIVAL);
|
||||||
setGameMode(GameMode.CREATIVE);
|
|
||||||
teleport(new Position(0, 66, 0));
|
teleport(new Position(0, 66, 0));
|
||||||
for (int cx = 0; cx < 4; cx++)
|
for (int cx = 0; cx < 4; cx++)
|
||||||
for (int cz = 0; cz < 4; cz++) {
|
for (int cz = 0; cz < 4; cz++) {
|
||||||
@ -152,16 +150,43 @@ public class Player extends LivingEntity {
|
|||||||
chickenCreature.setInstance(instance);
|
chickenCreature.setInstance(instance);
|
||||||
//chickenCreature.addPassenger(player);
|
//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
|
@Override
|
||||||
public void update() {
|
public void update() {
|
||||||
ClientPlayPacket packet = null;
|
|
||||||
|
playerConnection.flush();
|
||||||
|
|
||||||
|
ClientPlayPacket packet;
|
||||||
while ((packet = packets.poll()) != null) {
|
while ((packet = packets.poll()) != null) {
|
||||||
packet.process(this);
|
packet.process(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
super.update(); // Super update (item pickup)
|
||||||
|
|
||||||
// Target block stage
|
// Target block stage
|
||||||
if (targetCustomBlock != null) {
|
if (targetCustomBlock != null) {
|
||||||
int timeBreak = targetCustomBlock.getBreakDelay(this);
|
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
|
// Multiplayer sync
|
||||||
Position position = getPosition();
|
Position position = getPosition();
|
||||||
@ -226,17 +220,12 @@ public class Player extends LivingEntity {
|
|||||||
entityLookAndRelativeMovePacket.pitch = position.getPitch();
|
entityLookAndRelativeMovePacket.pitch = position.getPitch();
|
||||||
entityLookAndRelativeMovePacket.onGround = onGround;
|
entityLookAndRelativeMovePacket.onGround = onGround;
|
||||||
|
|
||||||
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
|
|
||||||
entityHeadLookPacket.entityId = getEntityId();
|
|
||||||
entityHeadLookPacket.yaw = position.getYaw();
|
|
||||||
|
|
||||||
lastX = position.getX();
|
lastX = position.getX();
|
||||||
lastY = position.getY();
|
lastY = position.getY();
|
||||||
lastZ = position.getZ();
|
lastZ = position.getZ();
|
||||||
lastYaw = position.getYaw();
|
lastYaw = position.getYaw();
|
||||||
lastPitch = position.getPitch();
|
lastPitch = position.getPitch();
|
||||||
updatePacket = entityLookAndRelativeMovePacket;
|
updatePacket = entityLookAndRelativeMovePacket;
|
||||||
optionalUpdatePacket = entityHeadLookPacket;
|
|
||||||
} else if (positionChanged) {
|
} else if (positionChanged) {
|
||||||
EntityRelativeMovePacket entityRelativeMovePacket = new EntityRelativeMovePacket();
|
EntityRelativeMovePacket entityRelativeMovePacket = new EntityRelativeMovePacket();
|
||||||
entityRelativeMovePacket.entityId = getEntityId();
|
entityRelativeMovePacket.entityId = getEntityId();
|
||||||
@ -255,15 +244,18 @@ public class Player extends LivingEntity {
|
|||||||
entityLookPacket.pitch = position.getPitch();
|
entityLookPacket.pitch = position.getPitch();
|
||||||
entityLookPacket.onGround = onGround;
|
entityLookPacket.onGround = onGround;
|
||||||
|
|
||||||
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
|
|
||||||
entityHeadLookPacket.entityId = getEntityId();
|
|
||||||
entityHeadLookPacket.yaw = position.getYaw();
|
|
||||||
|
|
||||||
lastYaw = position.getYaw();
|
lastYaw = position.getYaw();
|
||||||
lastPitch = position.getPitch();
|
lastPitch = position.getPitch();
|
||||||
updatePacket = entityLookPacket;
|
updatePacket = entityLookPacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (viewChanged) {
|
||||||
|
EntityHeadLookPacket entityHeadLookPacket = new EntityHeadLookPacket();
|
||||||
|
entityHeadLookPacket.entityId = getEntityId();
|
||||||
|
entityHeadLookPacket.yaw = position.getYaw();
|
||||||
optionalUpdatePacket = entityHeadLookPacket;
|
optionalUpdatePacket = entityHeadLookPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updatePacket != null) {
|
if (updatePacket != null) {
|
||||||
if (optionalUpdatePacket != null) {
|
if (optionalUpdatePacket != null) {
|
||||||
sendPacketsToViewers(updatePacket, optionalUpdatePacket);
|
sendPacketsToViewers(updatePacket, optionalUpdatePacket);
|
||||||
@ -271,20 +263,11 @@ public class Player extends LivingEntity {
|
|||||||
sendPacketToViewers(updatePacket);
|
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
|
@Override
|
||||||
@ -305,6 +288,7 @@ public class Player extends LivingEntity {
|
|||||||
|
|
||||||
connection.sendPacket(pInfoPacket);
|
connection.sendPacket(pInfoPacket);
|
||||||
connection.sendPacket(spawnPlayerPacket);
|
connection.sendPacket(spawnPlayerPacket);
|
||||||
|
connection.sendPacket(getMetadataPacket());
|
||||||
|
|
||||||
for (EntityEquipmentPacket.Slot slot : EntityEquipmentPacket.Slot.values()) {
|
for (EntityEquipmentPacket.Slot slot : EntityEquipmentPacket.Slot.values()) {
|
||||||
syncEquipment(slot); // TODO only send packets to "player" and not all viewers
|
syncEquipment(slot); // TODO only send packets to "player" and not all viewers
|
||||||
@ -327,6 +311,12 @@ public class Player extends LivingEntity {
|
|||||||
super.setInstance(instance);
|
super.setInstance(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void kill() {
|
||||||
|
this.isDead = true;
|
||||||
|
// TODO set health to -1
|
||||||
|
}
|
||||||
|
|
||||||
public void sendBlockBreakAnimation(BlockPosition blockPosition, byte destroyStage) {
|
public void sendBlockBreakAnimation(BlockPosition blockPosition, byte destroyStage) {
|
||||||
BlockBreakAnimationPacket breakAnimationPacket = new BlockBreakAnimationPacket();
|
BlockBreakAnimationPacket breakAnimationPacket = new BlockBreakAnimationPacket();
|
||||||
breakAnimationPacket.entityId = getEntityId() + 1;
|
breakAnimationPacket.entityId = getEntityId() + 1;
|
||||||
@ -342,6 +332,7 @@ public class Player extends LivingEntity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void teleport(Position position) {
|
public void teleport(Position position) {
|
||||||
|
super.teleport(position); // Send new position to all viewers directly
|
||||||
if (isChunkUnloaded(position.getX(), position.getZ()))
|
if (isChunkUnloaded(position.getX(), position.getZ()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package fr.themode.minestom.entity.demo;
|
package fr.themode.minestom.entity.demo;
|
||||||
|
|
||||||
import fr.themode.minestom.entity.EntityCreature;
|
import fr.themode.minestom.entity.EntityCreature;
|
||||||
|
import fr.themode.minestom.utils.Vector;
|
||||||
|
|
||||||
public class ChickenCreature extends EntityCreature {
|
public class ChickenCreature extends EntityCreature {
|
||||||
|
|
||||||
@ -10,6 +11,7 @@ public class ChickenCreature extends EntityCreature {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update() {
|
public void update() {
|
||||||
|
super.update();
|
||||||
float speed = 0.05f;
|
float speed = 0.05f;
|
||||||
|
|
||||||
/*if (hasPassenger()) {
|
/*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;
|
this.shooter = shooter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void spawn() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getObjectData() {
|
public int getObjectData() {
|
||||||
return shooter.getEntityId() + 1;
|
return shooter.getEntityId() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public LivingEntity getShooter() {
|
||||||
public void update() {
|
return shooter;
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,11 +11,11 @@ public class BlockBatch implements BlockModifier {
|
|||||||
|
|
||||||
private static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(2);
|
private static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(2);
|
||||||
|
|
||||||
private Instance instance;
|
private InstanceContainer instance;
|
||||||
|
|
||||||
private Map<Chunk, List<BlockData>> data = new HashMap<>();
|
private Map<Chunk, List<BlockData>> data = new HashMap<>();
|
||||||
|
|
||||||
public BlockBatch(Instance instance) {
|
public BlockBatch(InstanceContainer instance) {
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ public class BlockBatch implements BlockModifier {
|
|||||||
for (Map.Entry<Chunk, List<BlockData>> entry : data.entrySet()) {
|
for (Map.Entry<Chunk, List<BlockData>> entry : data.entrySet()) {
|
||||||
Chunk chunk = entry.getKey();
|
Chunk chunk = entry.getKey();
|
||||||
List<BlockData> dataList = entry.getValue();
|
List<BlockData> dataList = entry.getValue();
|
||||||
batchesPool.submit(() -> {
|
batchesPool.execute(() -> {
|
||||||
synchronized (chunk) {
|
synchronized (chunk) {
|
||||||
for (BlockData data : dataList) {
|
for (BlockData data : dataList) {
|
||||||
data.apply(chunk);
|
data.apply(chunk);
|
||||||
|
@ -124,10 +124,6 @@ public class Chunk {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public short[] getBlocksId() {
|
|
||||||
return blocksId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Biome getBiome() {
|
public Biome getBiome() {
|
||||||
return biome;
|
return biome;
|
||||||
}
|
}
|
||||||
@ -176,7 +172,7 @@ public class Chunk {
|
|||||||
|
|
||||||
// TODO customblock id map (StringId -> short id)
|
// TODO customblock id map (StringId -> short id)
|
||||||
// TODO List of (sectionId;blockcount;blocktype;blockarray)
|
// TODO List of (sectionId;blockcount;blocktype;blockarray)
|
||||||
|
// TODO block data
|
||||||
for (byte x = 0; x < CHUNK_SIZE_X; x++) {
|
for (byte x = 0; x < CHUNK_SIZE_X; x++) {
|
||||||
for (byte y = -128; y < 127; y++) {
|
for (byte y = -128; y < 127; y++) {
|
||||||
for (byte z = 0; z < CHUNK_SIZE_Z; z++) {
|
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 static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_CHUNK_BATCH);
|
||||||
|
|
||||||
private Instance instance;
|
private InstanceContainer instance;
|
||||||
private Chunk chunk;
|
private Chunk chunk;
|
||||||
|
|
||||||
private List<BlockData> dataList = new ArrayList<>();
|
private List<BlockData> dataList = new ArrayList<>();
|
||||||
|
|
||||||
public ChunkBatch(Instance instance, Chunk chunk) {
|
public ChunkBatch(InstanceContainer instance, Chunk chunk) {
|
||||||
this.instance = instance;
|
this.instance = instance;
|
||||||
this.chunk = chunk;
|
this.chunk = chunk;
|
||||||
}
|
}
|
||||||
@ -46,7 +46,7 @@ public class ChunkBatch implements BlockModifier {
|
|||||||
|
|
||||||
public void flush(Consumer<Chunk> callback) {
|
public void flush(Consumer<Chunk> callback) {
|
||||||
synchronized (chunk) {
|
synchronized (chunk) {
|
||||||
batchesPool.submit(() -> {
|
batchesPool.execute(() -> {
|
||||||
for (BlockData data : dataList) {
|
for (BlockData data : dataList) {
|
||||||
data.apply(chunk);
|
data.apply(chunk);
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ public class ChunkLoaderIO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void saveChunk(Chunk chunk, File folder, Runnable callback) {
|
protected void saveChunk(Chunk chunk, File folder, Runnable callback) {
|
||||||
chunkLoaderPool.submit(() -> {
|
chunkLoaderPool.execute(() -> {
|
||||||
File chunkFile = getChunkFile(chunk.getChunkX(), chunk.getChunkZ(), folder);
|
File chunkFile = getChunkFile(chunk.getChunkX(), chunk.getChunkZ(), folder);
|
||||||
try (FileOutputStream fos = new FileOutputStream(chunkFile)) {
|
try (FileOutputStream fos = new FileOutputStream(chunkFile)) {
|
||||||
byte[] data = chunk.getSerializedData();
|
byte[] data = chunk.getSerializedData();
|
||||||
@ -49,7 +49,7 @@ public class ChunkLoaderIO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void loadChunk(int chunkX, int chunkZ, Instance instance, Consumer<Chunk> callback) {
|
protected void loadChunk(int chunkX, int chunkZ, Instance instance, Consumer<Chunk> callback) {
|
||||||
chunkLoaderPool.submit(() -> {
|
chunkLoaderPool.execute(() -> {
|
||||||
File chunkFile = getChunkFile(chunkX, chunkZ, instance.getFolder());
|
File chunkFile = getChunkFile(chunkX, chunkZ, instance.getFolder());
|
||||||
if (!chunkFile.exists()) {
|
if (!chunkFile.exists()) {
|
||||||
instance.createChunk(chunkX, chunkZ, callback); // Chunk file does not exist, create new chunk
|
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);
|
int decompressedLength = SerializerUtils.bytesToInt(array);
|
||||||
|
|
||||||
byte[] compressedChunkData = new byte[array.length - 4];
|
byte[] compressedChunkData = new byte[array.length - Integer.BYTES];
|
||||||
System.arraycopy(array, 4, compressedChunkData, 0, compressedChunkData.length); // Remove the decompressed length from the array
|
System.arraycopy(array, Integer.BYTES, compressedChunkData, 0, compressedChunkData.length); // Remove the decompressed length from the array
|
||||||
|
|
||||||
byte[] decompressed = new byte[decompressedLength];
|
byte[] decompressed = new byte[decompressedLength];
|
||||||
long result = Zstd.decompress(decompressed, compressedChunkData); // Decompressed in an array with the max size
|
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);
|
chunk = new Chunk(biome, chunkX, chunkZ);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
// TODO block data
|
||||||
int index = stream.readInt();
|
int index = stream.readInt();
|
||||||
boolean isCustomBlock = stream.readBoolean();
|
boolean isCustomBlock = stream.readBoolean();
|
||||||
short blockId = stream.readShort();
|
short blockId = stream.readShort();
|
||||||
//System.out.println("id: " + blockId);
|
|
||||||
|
|
||||||
byte[] chunkPos = SerializerUtils.indexToChunkPosition(index);
|
byte[] chunkPos = SerializerUtils.indexToChunkPosition(index);
|
||||||
if (isCustomBlock) {
|
if (isCustomBlock) {
|
||||||
|
@ -5,327 +5,126 @@ import fr.themode.minestom.entity.Entity;
|
|||||||
import fr.themode.minestom.entity.EntityCreature;
|
import fr.themode.minestom.entity.EntityCreature;
|
||||||
import fr.themode.minestom.entity.ObjectEntity;
|
import fr.themode.minestom.entity.ObjectEntity;
|
||||||
import fr.themode.minestom.entity.Player;
|
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.BlockPosition;
|
||||||
import fr.themode.minestom.utils.GroupedCollections;
|
import fr.themode.minestom.utils.GroupedCollections;
|
||||||
import fr.themode.minestom.utils.Position;
|
import fr.themode.minestom.utils.Position;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.*;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
|
||||||
import java.util.function.Consumer;
|
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;
|
void breakBlock(Player player, BlockPosition blockPosition, short blockId);
|
||||||
private File folder;
|
|
||||||
|
|
||||||
private GroupedCollections<ObjectEntity> objectEntities = new GroupedCollections<>(new CopyOnWriteArrayList<>());
|
void loadChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
|
||||||
private GroupedCollections<EntityCreature> creatures = new GroupedCollections<>(new CopyOnWriteArrayList());
|
|
||||||
private GroupedCollections<Player> players = new GroupedCollections<>(new CopyOnWriteArrayList());
|
|
||||||
|
|
||||||
private ChunkGenerator chunkGenerator;
|
Chunk getChunk(int chunkX, int chunkZ);
|
||||||
private Map<Long, Chunk> chunks = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public Instance(UUID uniqueId, File folder) {
|
void saveToFolder(Runnable callback);
|
||||||
this.uniqueId = uniqueId;
|
|
||||||
this.folder = folder;
|
BlockBatch createBlockBatch();
|
||||||
|
|
||||||
|
ChunkBatch createChunkBatch(Chunk chunk);
|
||||||
|
|
||||||
|
void setChunkGenerator(ChunkGenerator chunkGenerator);
|
||||||
|
|
||||||
|
Collection<Chunk> getChunks();
|
||||||
|
|
||||||
|
GroupedCollections<ObjectEntity> getObjectEntities();
|
||||||
|
|
||||||
|
GroupedCollections<EntityCreature> getCreatures();
|
||||||
|
|
||||||
|
GroupedCollections<Player> getPlayers();
|
||||||
|
|
||||||
|
UUID getUniqueId();
|
||||||
|
|
||||||
|
File getFolder();
|
||||||
|
|
||||||
|
void setFolder(File folder);
|
||||||
|
|
||||||
|
void sendChunkUpdate(Player player, Chunk chunk);
|
||||||
|
|
||||||
|
void retrieveChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
|
||||||
|
|
||||||
|
void createChunk(int chunkX, int chunkZ, Consumer<Chunk> callback);
|
||||||
|
|
||||||
|
void sendChunks(Player player);
|
||||||
|
|
||||||
|
SharedInstance createSharedInstance();
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
default void sendChunkUpdate(Iterable<Player> players, Chunk chunk) {
|
||||||
|
Buffer chunkData = chunk.getFullDataPacket();
|
||||||
|
chunkData.getData().retain(getPlayers().size()).markReaderIndex();
|
||||||
|
players.forEach(player -> {
|
||||||
|
player.getPlayerConnection().sendUnencodedPacket(chunkData);
|
||||||
|
chunkData.getData().resetReaderIndex();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@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);
|
|
||||||
});*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
default void breakBlock(Player player, BlockPosition blockPosition, CustomBlock customBlock) {
|
||||||
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);
|
|
||||||
});*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void breakBlock(Player player, BlockPosition blockPosition, CustomBlock customBlock) {
|
|
||||||
breakBlock(player, blockPosition, customBlock.getType());
|
breakBlock(player, blockPosition, customBlock.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadChunk(int chunkX, int chunkZ, Consumer<Chunk> callback) {
|
default void loadChunk(int chunkX, int chunkZ) {
|
||||||
Chunk chunk = getChunk(chunkX, chunkZ);
|
|
||||||
if (chunk != null) {
|
|
||||||
if (callback != null)
|
|
||||||
callback.accept(chunk);
|
|
||||||
} else {
|
|
||||||
retrieveChunk(chunkX, chunkZ, callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadChunk(int chunkX, int chunkZ) {
|
|
||||||
loadChunk(chunkX, chunkZ, null);
|
loadChunk(chunkX, chunkZ, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void loadChunk(Position position, Consumer<Chunk> callback) {
|
default void loadChunk(Position position, Consumer<Chunk> callback) {
|
||||||
int chunkX = Math.floorDiv((int) position.getX(), 16);
|
int chunkX = Math.floorDiv((int) position.getX(), 16);
|
||||||
int chunkZ = Math.floorDiv((int) position.getY(), 16);
|
int chunkZ = Math.floorDiv((int) position.getY(), 16);
|
||||||
loadChunk(chunkX, chunkZ, callback);
|
loadChunk(chunkX, chunkZ, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isChunkLoaded(int chunkX, int chunkZ) {
|
default short getBlockId(int x, int y, int z) {
|
||||||
return getChunk(chunkX, chunkZ) != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public short getBlockId(int x, int y, int z) {
|
|
||||||
Chunk chunk = getChunkAt(x, z);
|
Chunk chunk = getChunkAt(x, z);
|
||||||
return chunk.getBlockId((byte) (x % 16), (byte) y, (byte) (z % 16));
|
return chunk.getBlockId((byte) (x % 16), (byte) y, (byte) (z % 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
public short getBlockId(BlockPosition blockPosition) {
|
default short getBlockId(BlockPosition blockPosition) {
|
||||||
return getBlockId(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
return getBlockId(blockPosition.getX(), blockPosition.getY(), blockPosition.getZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomBlock getCustomBlock(int x, int y, int z) {
|
default CustomBlock getCustomBlock(int x, int y, int z) {
|
||||||
Chunk chunk = getChunkAt(x, z);
|
Chunk chunk = getChunkAt(x, z);
|
||||||
return chunk.getCustomBlock((byte) (x % 16), (byte) y, (byte) (z % 16));
|
return chunk.getCustomBlock((byte) (x % 16), (byte) y, (byte) (z % 16));
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlockBatch createBlockBatch() {
|
default Chunk getChunkAt(double x, double z) {
|
||||||
return new BlockBatch(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Chunk getChunk(int chunkX, int chunkZ) {
|
|
||||||
return chunks.get(getChunkKey(chunkX, chunkZ));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Chunk getChunkAt(double x, double z) {
|
|
||||||
int chunkX = Math.floorDiv((int) x, 16);
|
int chunkX = Math.floorDiv((int) x, 16);
|
||||||
int chunkZ = Math.floorDiv((int) z, 16);
|
int chunkZ = Math.floorDiv((int) z, 16);
|
||||||
return getChunk(chunkX, chunkZ);
|
return getChunk(chunkX, chunkZ);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Chunk getChunkAt(BlockPosition blockPosition) {
|
default boolean isChunkLoaded(int chunkX, int chunkZ) {
|
||||||
|
return getChunk(chunkX, chunkZ) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
default Chunk getChunkAt(BlockPosition blockPosition) {
|
||||||
return getChunkAt(blockPosition.getX(), blockPosition.getZ());
|
return getChunkAt(blockPosition.getX(), blockPosition.getZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Chunk getChunkAt(Position position) {
|
default Chunk getChunkAt(Position position) {
|
||||||
return getChunkAt(position.getX(), position.getZ());
|
return getChunkAt(position.getX(), position.getZ());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveToFolder(Runnable callback) {
|
default void saveToFolder() {
|
||||||
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);
|
saveToFolder(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default long getChunkKey(int chunkX, int chunkZ) {
|
||||||
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;
|
|
||||||
|
|
||||||
Buffer chunkData = chunk.getFullDataPacket();
|
|
||||||
chunkData.getData().retain(getPlayers().size()).markReaderIndex();
|
|
||||||
getPlayers().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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private long getChunkKey(int chunkX, int chunkZ) {
|
|
||||||
return (((long) chunkX) << 32) | (chunkZ & 0xffffffffL);
|
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<>());
|
private Set<Instance> instances = Collections.synchronizedSet(new HashSet<>());
|
||||||
|
|
||||||
public Instance createInstance(File folder) {
|
public Instance createInstance(File folder) {
|
||||||
Instance instance = new Instance(UUID.randomUUID(), folder);
|
Instance instance = new InstanceContainer(UUID.randomUUID(), folder);
|
||||||
this.instances.add(instance);
|
this.instances.add(instance);
|
||||||
return 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) {
|
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);
|
boolean chunkTest = player.isChunkUnloaded(x, z);
|
||||||
if (chunkTest) {
|
if (chunkTest) {
|
||||||
player.teleport(player.getPosition());
|
player.teleport(player.getPosition());
|
||||||
|
@ -17,6 +17,7 @@ public class StatusListener {
|
|||||||
player.getPlayerConnection().sendPacket(respawnPacket);
|
player.getPlayerConnection().sendPacket(respawnPacket);
|
||||||
PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(player.getPosition());
|
PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(player.getPosition());
|
||||||
player.callEvent(PlayerRespawnEvent.class, respawnEvent);
|
player.callEvent(PlayerRespawnEvent.class, respawnEvent);
|
||||||
|
player.refreshIsDead(false);
|
||||||
player.teleport(respawnEvent.getRespawnPosition());
|
player.teleport(respawnEvent.getRespawnPosition());
|
||||||
player.getInventory().update();
|
player.getInventory().update();
|
||||||
break;
|
break;
|
||||||
|
@ -15,7 +15,7 @@ public class PacketWriter {
|
|||||||
private static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_PACKET_WRITER);
|
private static volatile ExecutorService batchesPool = Executors.newFixedThreadPool(Main.THREAD_COUNT_PACKET_WRITER);
|
||||||
|
|
||||||
public static void writeCallbackPacket(ServerPacket serverPacket, Consumer<Buffer> consumer) {
|
public static void writeCallbackPacket(ServerPacket serverPacket, Consumer<Buffer> consumer) {
|
||||||
batchesPool.submit(() -> {
|
batchesPool.execute(() -> {
|
||||||
Packet p = PacketUtils.writePacket(serverPacket);
|
Packet p = PacketUtils.writePacket(serverPacket);
|
||||||
consumer.accept(PacketUtils.encode(p)); // TODO accept in another thread?
|
consumer.accept(PacketUtils.encode(p)); // TODO accept in another thread?
|
||||||
});
|
});
|
||||||
|
@ -22,7 +22,7 @@ public class ResponsePacket implements ServerPacket {
|
|||||||
" ]\n" +
|
" ]\n" +
|
||||||
" },\t\n" +
|
" },\t\n" +
|
||||||
" \"description\": {\n" +
|
" \"description\": {\n" +
|
||||||
" \"text\": \"Wallah les cubes\"\n" +
|
" \"text\": \"Hey guys!\"\n" +
|
||||||
" },\n" +
|
" },\n" +
|
||||||
" \"favicon\": \"data:image/png;base64,<data>\"\n" +
|
" \"favicon\": \"data:image/png;base64,<data>\"\n" +
|
||||||
"}";
|
"}";
|
||||||
|
@ -37,17 +37,15 @@ public class TeamsPacket implements ServerPacket {
|
|||||||
break;
|
break;
|
||||||
case REMOVE_TEAM:
|
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;
|
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);
|
Utils.writeVarInt(buffer, entities.length);
|
||||||
for (String entity : entities) {
|
for (String entity : entities) {
|
||||||
Utils.writeString(buffer, entity);
|
Utils.writeString(buffer, entity);
|
||||||
|
@ -48,14 +48,14 @@ public class PlayerConnection {
|
|||||||
getChannel().write(packet.getData());
|
getChannel().write(packet.getData());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void flush() {
|
|
||||||
getChannel().flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendPacket(ServerPacket serverPacket) {
|
public void sendPacket(ServerPacket serverPacket) {
|
||||||
sendPacket(PacketUtils.writePacket(serverPacket));
|
sendPacket(PacketUtils.writePacket(serverPacket));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void flush() {
|
||||||
|
getChannel().flush();
|
||||||
|
}
|
||||||
|
|
||||||
public Connection getConnection() {
|
public Connection getConnection() {
|
||||||
return connection;
|
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) {
|
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() {
|
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