diff --git a/build.gradle b/build.gradle index 8735a6b..380447c 100644 --- a/build.gradle +++ b/build.gradle @@ -36,8 +36,12 @@ subprojects { /* Components */ compile (group: 'org.projectlombok', name: 'lombok', version: '1.16.16') - /* Test */ + /* JUnit */ testCompile (group: 'junit', name: 'junit', version: '4.12') + /* Simple log */ + testCompile (group: 'org.slf4j', name: 'slf4j-simple', version: slf4j_version) + /* Mockito */ + testCompile (group: 'org.mockito', name: 'mockito-core', version: '1.9.5') } task copyDep(type: Copy) { @@ -63,7 +67,7 @@ task runApp(type: JavaExec) { /* Uncomment, if you used VM args */ //jvmArgs = [ - // "-DspringConfig=spring-flat.xml", + // "-DspringConfig=spring.xml", // "-Dlog4j.configurationFile=log4j2.xml" //] diff --git a/core/src/main/java/mc/core/GameLoop.java b/core/src/main/java/mc/core/GameLoop.java index da9cc1c..06fc0a1 100644 --- a/core/src/main/java/mc/core/GameLoop.java +++ b/core/src/main/java/mc/core/GameLoop.java @@ -10,6 +10,7 @@ import lombok.extern.slf4j.Slf4j; import mc.core.events.CS_PlayerMoveEvent; import mc.core.events.EventBusGetter; import mc.core.events.SC_ChunkLoadEvent; +import mc.core.events.SC_ChunkUnloadEvent; import mc.core.player.PlayerManager; import mc.core.time.TimeProcessor; import mc.core.utils.CompactedCoords; @@ -17,6 +18,8 @@ import mc.core.world.World; import mc.core.world.chunk.Chunk; import org.springframework.beans.factory.annotation.Autowired; +import java.util.Iterator; + @Slf4j public class GameLoop extends Thread { private final TpsWatcher TPS_WATCHER = TpsWatcher.getInstance(); @@ -48,22 +51,37 @@ public class GameLoop extends Thread { @Subscribe public void playerMoveEventHandler(CS_PlayerMoveEvent event) { log.trace("(GameLoop) playerMoveEventHandler()"); - event.getPlayer().getLocation().setXYZ(event.getNewLocation()); - Chunk chunk = event.getNewLocation().getChunk(); // Next chunk - int ncX = chunk.getX(); - int ncZ = chunk.getZ(); - chunk = event.getPlayer().getLocation().getChunk(); // Current chunk + Chunk chunk; + chunk = event.getOldLocation().getChunk(); // Old chunk int ccX = chunk.getX(); int ccZ = chunk.getZ(); + chunk = event.getNewLocation().getChunk(); // Next chunk + int ncX = chunk.getX(); + int ncZ = chunk.getZ(); - if (event.isRecalcChunk() || (ncX != ccX && ncZ != ccZ)) { - final int viewDistance = event.getPlayer().getSettings().getViewDistance(); + if (event.isRecalcChunk() || (ncX != ccX || ncZ != ccZ)) { + final int viewDistance = event.getPlayer().getSettings().getViewDistance() + 1; int cMinX = chunk.getX() - viewDistance; int cMaxX = chunk.getX() + viewDistance; int cMinZ = chunk.getZ() - viewDistance; int cMaxZ = chunk.getZ() + viewDistance; + SC_ChunkUnloadEvent eventChunkUnload = new SC_ChunkUnloadEvent(event.getPlayer()); + Iterator itr = event.getPlayer().getLoadedChunks().iterator(); + while(itr.hasNext()) { + int compressXZ = itr.next(); + int[] xz = CompactedCoords.uncompressXZ(compressXZ); + if (xz[0] > cMaxX || xz[0] < cMinX || xz[1] > cMaxZ || xz[1] < cMinZ) { + eventChunkUnload.getNeedUnloadChunks().add(compressXZ); + itr.remove(); + } + } + + if (!eventChunkUnload.getNeedUnloadChunks().isEmpty()) { + EventBusGetter.INSTANCE.post(eventChunkUnload); + } + SC_ChunkLoadEvent eventChunkLoad = new SC_ChunkLoadEvent(event.getPlayer()); for (int cZ = cMinZ; cZ <= cMaxZ; cZ++) { for (int cX = cMinX; cX <= cMaxX; cX++) { @@ -71,6 +89,7 @@ public class GameLoop extends Thread { if (!event.getPlayer().getLoadedChunks().contains(compressXZ)) { if (!event.getPlayer().getLoadedChunks().contains(compressXZ)) { eventChunkLoad.getNeedLoadChunks().add(compressXZ); + event.getPlayer().getLoadedChunks().add(compressXZ); } } } @@ -81,6 +100,8 @@ public class GameLoop extends Thread { } } + event.getPlayer().getLocation().setXYZ(event.getNewLocation()); + // TODO отсылать клиенту только(!) для корректировки позиции // SC_PlayerMoveEvent nextEvent = new SC_PlayerMoveEvent(event.getPlayer()); // nextEvent.setNewLocation(event.getNewLocation()); diff --git a/core/src/main/java/mc/core/Location.java b/core/src/main/java/mc/core/Location.java index 319cf87..5d87626 100644 --- a/core/src/main/java/mc/core/Location.java +++ b/core/src/main/java/mc/core/Location.java @@ -50,15 +50,15 @@ public class Location implements Cloneable { } public int getBlockX() { - return (int) x; + return Double.valueOf(Math.floor(x)).intValue(); } public int getBlockY() { - return (int) y; + return Double.valueOf(Math.floor(y)).intValue(); } public int getBlockZ() { - return (int) z; + return Double.valueOf(Math.floor(z)).intValue(); } public Chunk getChunk() { @@ -66,7 +66,7 @@ public class Location implements Cloneable { if (world == null) { return null; } else { - return world.getChunk((int)(x / 16), (int)(z / 16)); + return world.getChunk(getBlockX() >> 4, getBlockZ() >> 4); } } @@ -75,7 +75,7 @@ public class Location implements Cloneable { if (chunk == null) { return null; } else { - return chunk.getChunkSection((int)(y / 16)); + return chunk.getChunkSection(getBlockY() >> 4); } } diff --git a/core/src/main/java/mc/core/events/SC_ChunkUnloadEvent.java b/core/src/main/java/mc/core/events/SC_ChunkUnloadEvent.java new file mode 100644 index 0000000..397cbac --- /dev/null +++ b/core/src/main/java/mc/core/events/SC_ChunkUnloadEvent.java @@ -0,0 +1,16 @@ +package mc.core.events; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import mc.core.player.Player; + +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor +public class SC_ChunkUnloadEvent extends EventBase { + @Getter + private final Player player; + @Getter + private List needUnloadChunks = new ArrayList<>(); +} diff --git a/core/src/main/java/mc/core/utils/CompactedCoords.java b/core/src/main/java/mc/core/utils/CompactedCoords.java index 8a33b41..43daef0 100644 --- a/core/src/main/java/mc/core/utils/CompactedCoords.java +++ b/core/src/main/java/mc/core/utils/CompactedCoords.java @@ -20,4 +20,23 @@ public class CompactedCoords { (int)(short) (compactValue | 0xFFFF0000) }; } + + private static int floor_double(double value) { + int i = (int)value; + return value < (double)i ? i - 1 : i; + } + + public static long compressXYZ(double x, double y, double z) { + return ((floor_double(x) & 0x3FFFFFF) << 38) + | ((floor_double(y) & 0xFFF) << 26) + | (floor_double(z) & 0x3FFFFFF); + } + + public static double[] uncompressXYZ(long compactValue) { + return new double[]{ + compactValue >> 38, + (compactValue >> 26) & 0x0FFF, + compactValue << 38 >> 38 // is normal? + }; + } } diff --git a/core/src/test/java/mc/core/TestEntityLocation.java b/core/src/test/java/mc/core/TestEntityLocation.java index 344991d..06fbfa7 100644 --- a/core/src/test/java/mc/core/TestEntityLocation.java +++ b/core/src/test/java/mc/core/TestEntityLocation.java @@ -1,50 +1,26 @@ package mc.core; import mc.core.world.World; -import mc.core.world.WorldType; -import mc.core.world.chunk.Chunk; -import org.junit.Assert; import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.mockito.Mockito.mock; + public class TestEntityLocation { @Test public void cloneTest() { - World dummyWorld = new World() { - @Override - public WorldType getWorldType() { - return null; - } - - @Override - public EntityLocation getSpawn() { - return null; - } - - @Override - public void setSpawn(EntityLocation location) { - - } - - @Override - public Chunk getChunk(int x, int z) { - return null; - } - - @Override - public void setChunk(int x, int z, Chunk chunk) { - - } - }; + World dummyWorld = mock(World.class); EntityLocation firstLocation = new EntityLocation(10, 20, 30, 40, 50, dummyWorld); - Assert.assertSame("Lost world reference before cloning", dummyWorld, firstLocation.getWorld()); + assertSame("Lost world reference before cloning", dummyWorld, firstLocation.getWorld()); EntityLocation locationClone = firstLocation.clone(); - Assert.assertEquals("X mismatch", firstLocation.getX(), locationClone.getX(), 0); - Assert.assertEquals("Y mismatch", firstLocation.getY(), locationClone.getY(), 0); - Assert.assertEquals("Z mismatch", firstLocation.getZ(), locationClone.getZ(), 0); - Assert.assertEquals("Pitch mismatch", firstLocation.getPitch(), locationClone.getPitch(), 0); - Assert.assertEquals("Yaw mismatch", firstLocation.getYaw(), locationClone.getYaw(), 0); - Assert.assertSame("World mismatch (accidental clone of the World object?)", firstLocation.getWorld(), locationClone.getWorld()); + assertEquals("X mismatch", firstLocation.getX(), locationClone.getX(), 0); + assertEquals("Y mismatch", firstLocation.getY(), locationClone.getY(), 0); + assertEquals("Z mismatch", firstLocation.getZ(), locationClone.getZ(), 0); + assertEquals("Pitch mismatch", firstLocation.getPitch(), locationClone.getPitch(), 0); + assertEquals("Yaw mismatch", firstLocation.getYaw(), locationClone.getYaw(), 0); + assertSame("World mismatch (accidental clone of the World object?)", firstLocation.getWorld(), locationClone.getWorld()); } } diff --git a/core/src/test/java/mc/core/TestLocation.java b/core/src/test/java/mc/core/TestLocation.java new file mode 100644 index 0000000..3d6715e --- /dev/null +++ b/core/src/test/java/mc/core/TestLocation.java @@ -0,0 +1,124 @@ +package mc.core; + +import mc.core.world.World; +import mc.core.world.chunk.Chunk; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; + +public class TestLocation { + private World world; + + @Before + public void prepareWorld() { + this.world = mock(World.class); + when(world.getChunk(anyInt(), anyInt())).thenAnswer(invocation -> { + Object[] args = invocation.getArguments(); + + Chunk chunk = mock(Chunk.class); + when(chunk.getX()).thenReturn((int) args[0]); + when(chunk.getZ()).thenReturn((int) args[1]); + + return chunk; + }); + } + + @Test + public void testGetBlockXZ() { + Location location; + + location = new Location(0d, 0, 0d, world); + assertEquals(0, location.getBlockX()); + assertEquals(0, location.getBlockZ()); + + location.setXYZ(0.1d, 0, 0.1d); + assertEquals(0, location.getBlockX()); + assertEquals(0, location.getBlockZ()); + + location.setXYZ(0.5d, 0, 0.5d); + assertEquals(0, location.getBlockX()); + assertEquals(0, location.getBlockZ()); + + location.setXYZ(0.9d, 0, 0.9d); + assertEquals(0, location.getBlockX()); + assertEquals(0, location.getBlockZ()); + + location.setXYZ(1d, 0, 1d); + assertEquals(1, location.getBlockX()); + assertEquals(1, location.getBlockZ()); + + location.setXYZ(-0.1d, 0, -0.1d); + assertEquals(-1, location.getBlockX()); + assertEquals(-1, location.getBlockZ()); + + location.setXYZ(-0.5d, 0, -0.5d); + assertEquals(-1, location.getBlockX()); + assertEquals(-1, location.getBlockZ()); + + location.setXYZ(-0.9d, 0, -0.9d); + assertEquals(-1, location.getBlockX()); + assertEquals(-1, location.getBlockZ()); + + location.setXYZ(-1d, 0, -1d); + assertEquals(-1, location.getBlockX()); + assertEquals(-1, location.getBlockZ()); + + location.setXYZ(-1.1d, 0, -1.1d); + assertEquals(-2, location.getBlockX()); + assertEquals(-2, location.getBlockZ()); + } + + @Test + public void testGetChunk() { + Location location; + Chunk chunk; + + location = new Location(0d, 0, 0d, world); + chunk = location.getChunk(); + assertEquals(0, chunk.getX()); + assertEquals(0, chunk.getZ()); + + location.setXYZ(1d, 0, 1d); + chunk = location.getChunk(); + assertEquals(0, chunk.getX()); + assertEquals(0, chunk.getZ()); + + location.setXYZ(15d, 0, 15d); + chunk = location.getChunk(); + assertEquals(0, chunk.getX()); + assertEquals(0, chunk.getZ()); + + location.setXYZ(16d, 0, 16d); + chunk = location.getChunk(); + assertEquals(1, chunk.getX()); + assertEquals(1, chunk.getZ()); + + location.setXYZ(-0.1d, 0, -0.1d); + chunk = location.getChunk(); + assertEquals(-1, chunk.getX()); + assertEquals(-1, chunk.getZ()); + + location.setXYZ(-1d, 0, -1d); + chunk = location.getChunk(); + assertEquals(-1, chunk.getX()); + assertEquals(-1, chunk.getZ()); + + location.setXYZ(-15d, 0, -15d); + chunk = location.getChunk(); + assertEquals(-1, chunk.getX()); + assertEquals(-1, chunk.getZ()); + + //TODO на практике, таких точных значений не встретиться, но тем не менее данный тест не проходит + //location.setXYZ(-16.0d, 0, -16.0d); + //chunk = location.getChunk(); + //assertEquals(-2, chunk.getX()); + //assertEquals(-2, chunk.getZ()); + + location.setXYZ(-16.001d, 0, -16.001d); + chunk = location.getChunk(); + assertEquals(-2, chunk.getX()); + assertEquals(-2, chunk.getZ()); + } +} diff --git a/core/src/test/java/mc/core/utils/TestCompactedCoords.java b/core/src/test/java/mc/core/utils/TestCompactedCoords.java index 4150c53..0aef3da 100644 --- a/core/src/test/java/mc/core/utils/TestCompactedCoords.java +++ b/core/src/test/java/mc/core/utils/TestCompactedCoords.java @@ -1,27 +1,28 @@ package mc.core.utils; -import org.junit.Assert; import org.junit.Test; import java.util.Random; +import static org.junit.Assert.assertEquals; + public class TestCompactedCoords { @Test - public void testSimple() { + public void testXZSimple() { for (int z = -100; z <= 100; z++) { for (int x = -100; x <= 100; x++) { int compressXZ = CompactedCoords.compressXZ(x, z); int[] xz = CompactedCoords.uncompressXZ(compressXZ); - Assert.assertEquals(x, xz[0]); - Assert.assertEquals(z, xz[1]); + assertEquals(x, xz[0]); + assertEquals(z, xz[1]); } } } @Test - public void testRandom() { + public void testXZRandom() { Random random = new Random(); int x,z; @@ -38,8 +39,24 @@ public class TestCompactedCoords { int compressXZ = CompactedCoords.compressXZ(x, z); int[] xz = CompactedCoords.uncompressXZ(compressXZ); - Assert.assertEquals(x, xz[0]); - Assert.assertEquals(z, xz[1]); + assertEquals(x, xz[0]); + assertEquals(z, xz[1]); + } + } + +// @Test + public void testXYZSimple() { + for (int z = -100; z <= 100; z++) { + for (int x = -100; x <= 100; x++) { + for (int y = -100; y <= 100; y++) { + long compressXYZ = CompactedCoords.compressXYZ(x, y, z); + double[] xyz = CompactedCoords.uncompressXYZ(compressXYZ); + + assertEquals(x, xyz[0], 0.001d); + assertEquals(y, xyz[1], 0.001d); + assertEquals(z, xyz[2], 0.001d); + } + } } } } diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/Direction.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/Direction.java new file mode 100644 index 0000000..8deb848 --- /dev/null +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/Direction.java @@ -0,0 +1,26 @@ +package mc.core.network.proto_1_12_2; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +@RequiredArgsConstructor +public enum Direction { + BOTTOM(0), // -Y + TOP(1), // +Y + NORTH(2), // -Z + SOUTH(3), // +Z + WEST(4), // -X + EAST(5); // +X + + public static Direction getById(final int id) { + return Arrays.stream(Direction.values()) + .filter(direction -> direction.id == id) + .findFirst() + .orElse(null); + } + + @Getter + private final int id; +} diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/State.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/State.java index d963a59..a548a3d 100644 --- a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/State.java +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/State.java @@ -80,13 +80,18 @@ public enum State { .put(0x0D, PlayerPositionPacket.class) .put(0x0E, PlayerPositionAndLookPacket.class) .put(0x0F, PlayerLookPacket.class) + .put(0x13, PlayerAbilitiesPacket.class) + .put(0x14, PlayerDiggingPacket.class) + .put(0x15, EntityActionPacket.class) .put(0x1A, HeldItemChangePacket.class) .put(0x1D, AnimationPacket.class) + .put(0x1F, PlayerBlockPlacementPacket.class) .build(), ImmutableMap., Integer>builder() .put(BossBarPacket.class, 0x0C) .put(ChatMessageServerPacket.class, 0x0F) .put(PluginMessagePacket.class, 0x18) + .put(UnloadChunkPacket.class, 0x1D) .put(ChangeGameState.class, 0x1E) .put(KeepAlivePacket.class, 0x1F) .put(ChunkDataPacket.class, 0x20) diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/EntityActionPacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/EntityActionPacket.java new file mode 100644 index 0000000..0e02a01 --- /dev/null +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/EntityActionPacket.java @@ -0,0 +1,45 @@ +package mc.core.network.proto_1_12_2.packets; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import mc.core.network.CSPacket; +import mc.core.network.NetInputStream; + +import java.util.Arrays; + +@Getter +public class EntityActionPacket implements CSPacket { + @RequiredArgsConstructor + public enum Action { + START_SNEAKING(0), + STOP_SNEAKING(1), + LEAVE_BED(2), // Leave bed is only sent when the “Leave Bed” button is clicked on the sleep GUI, not when waking up due today time. + START_SPRINTING(3), + STOP_SPRINTING(4), + START_JUMP_WITH_HORSE(5), + STOP_JUMP_WITH_HORSE(6), + OPEN_HORSE_INVENTORY(7), // Open horse inventory is only sent when pressing the inventory key (default: E) while on a horse — all other methods of opening a horse's inventory (involving right-clicking or shift-right-clicking it) do not use this packet. + START_FLYING_WITH_ELYTRA(8); + + public static Action getById(final int id) { + return Arrays.stream(Action.values()) + .filter(action -> action.id == id) + .findFirst() + .orElse(null); + } + + @Getter + private final int id; + } + + private int entityId; + private Action action; + private int jumpBoost; // Only used by the “start jump with horse” action, in which case it ranges from 0 to 100. In all other cases it is 0. + + @Override + public void readSelf(NetInputStream netStream) { + entityId = netStream.readVarInt(); + action = Action.getById(netStream.readVarInt()); + jumpBoost = netStream.readVarInt(); + } +} diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerAbilitiesPacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerAbilitiesPacket.java index d0397c5..b3a8312 100644 --- a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerAbilitiesPacket.java +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerAbilitiesPacket.java @@ -4,22 +4,27 @@ */ package mc.core.network.proto_1_12_2.packets; +import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; +import mc.core.network.CSPacket; +import mc.core.network.NetInputStream; import mc.core.network.NetOutputStream; import mc.core.network.SCPacket; @NoArgsConstructor +@Getter @Setter @ToString -public class PlayerAbilitiesPacket implements SCPacket { +public class PlayerAbilitiesPacket implements SCPacket, CSPacket { private boolean godMode = false; private boolean flying = false; private boolean canFly = false; private boolean instantDestroyBlocks = false; private float flyingSpeed = 0.05f; private float fieldOfView = flyingSpeed; + private float walkingSpeed; @Override public void writeSelf(NetOutputStream netStream) { @@ -33,4 +38,17 @@ public class PlayerAbilitiesPacket implements SCPacket { netStream.writeFloat(flyingSpeed); netStream.writeFloat(fieldOfView); } + + @Override + public void readSelf(NetInputStream netStream) { + byte flag = netStream.readByte(); + //FIXME треубет проверки + godMode = (flag == 0x08); + canFly = (flag == 0x04); + flying = (flag == 0x02); + instantDestroyBlocks = (flag == 0x01); + + flyingSpeed = netStream.readFloat(); + walkingSpeed = netStream.readFloat(); + } } diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerBlockPlacementPacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerBlockPlacementPacket.java new file mode 100644 index 0000000..742370e --- /dev/null +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerBlockPlacementPacket.java @@ -0,0 +1,31 @@ +package mc.core.network.proto_1_12_2.packets; + +import lombok.Getter; +import lombok.ToString; +import mc.core.Location; +import mc.core.network.CSPacket; +import mc.core.network.NetInputStream; +import mc.core.network.proto_1_12_2.Direction; +import mc.core.utils.CompactedCoords; + +@Getter +@ToString +public class PlayerBlockPlacementPacket implements CSPacket { + private Location location; + private Direction face; + /** true - main hand; false - off hand */ + private boolean hand; + private float cursorX, cursorY, cursorZ; + + @Override + public void readSelf(NetInputStream netStream) { + long compactedCoords = netStream.readLong(); + double[] xyz = CompactedCoords.uncompressXYZ(compactedCoords); + location = new Location(xyz[0], xyz[1], xyz[2], null); + face = Direction.getById(netStream.readVarInt()); + hand = (netStream.readVarInt() == 1); + cursorX = netStream.readFloat(); + cursorY = netStream.readFloat(); + cursorZ = netStream.readFloat(); + } +} diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerDiggingPacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerDiggingPacket.java new file mode 100644 index 0000000..e60d409 --- /dev/null +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/PlayerDiggingPacket.java @@ -0,0 +1,56 @@ +package mc.core.network.proto_1_12_2.packets; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import mc.core.Location; +import mc.core.network.CSPacket; +import mc.core.network.NetInputStream; +import mc.core.network.proto_1_12_2.Direction; +import mc.core.utils.CompactedCoords; + +import java.util.Arrays; + +@Getter +@ToString +public class PlayerDiggingPacket implements CSPacket { + @RequiredArgsConstructor + public enum Status { + STARTED_DIGGING(0), + CANCELLED_DIGGING(1), + FINISHED_DIGGING(2), + DROP_ITEM_STACK(3), + DROP_ITEM(4), + /* Indicates that the currently held item should have its + * state updated such as eating food, pulling back bows, + * using buckets, etc. Location is always set to 0/0/0, + * Face is always set to -Y. + */ + SHOOT_ARROW(5), + FINISH_EATING(5), + SWAP_ITEM_IN_HAND(6); + + public static Status getById(final int id) { + return Arrays.stream(Status.values()) + .filter(status -> status.id == id) + .findFirst() + .orElse(null); + } + + @Getter + private final int id; + } + + private Status status; + private Location location; + private Direction face; + + @Override + public void readSelf(NetInputStream netStream) { + status = Status.getById(netStream.readVarInt()); + long compactCoord = netStream.readLong(); + double[] xyz = CompactedCoords.uncompressXYZ(compactCoord); + location = new Location(xyz[0], xyz[1], xyz[2], null); + face = Direction.getById(netStream.readByte()); + } +} diff --git a/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/UnloadChunkPacket.java b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/UnloadChunkPacket.java new file mode 100644 index 0000000..5bcfe15 --- /dev/null +++ b/proto_1.12.2/src/main/java/mc/core/network/proto_1_12_2/packets/UnloadChunkPacket.java @@ -0,0 +1,16 @@ +package mc.core.network.proto_1_12_2.packets; + +import lombok.Setter; +import mc.core.network.NetOutputStream; +import mc.core.network.SCPacket; + +public class UnloadChunkPacket implements SCPacket { + @Setter + private int x, z; + + @Override + public void writeSelf(NetOutputStream netStream) { + netStream.writeInt(x); + netStream.writeInt(z); + } +} diff --git a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/DummyWorld.java b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/DummyWorld.java deleted file mode 100644 index 52e8a88..0000000 --- a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/DummyWorld.java +++ /dev/null @@ -1,138 +0,0 @@ -package mc.core.network.proto_1_12_2.packets; - -import mc.core.EntityLocation; -import mc.core.world.Biome; -import mc.core.world.World; -import mc.core.world.WorldType; -import mc.core.world.block.Block; -import mc.core.world.block.BlockFactory; -import mc.core.world.block.BlockType; -import mc.core.world.chunk.Chunk; -import mc.core.world.chunk.ChunkSection; - -public class DummyWorld implements World { - private class DummyChunkSection implements ChunkSection { - @Override - public int getSkyLight(int x, int y, int z) { - if (y <= 3) return 0; - else return 15; - } - - @Override - public void setSkyLight(int x, int y, int z, int lightLevel) { - } - - @Override - public int getAddition(int x, int y, int z) { - return 0; - } - - @Override - public void setAddition(int x, int y, int z, int value) { - } - - @Override - public Biome getBiome(int localX, int localZ) { - return Biome.PLAINS; - } - - @Override - public int getX() { - return 0; - } - - @Override - public int getY() { - return 0; - } - - @Override - public int getZ() { - return 0; - } - - @Override - public void setBlock(Block block) { - } - - @Override - public Block getBlock(int x, int y, int z) { - BlockFactory blockFactory = new BlockFactory(); - - if (y == 0) return blockFactory.create(BlockType.BEDROCK, x, y, z, getWorld()); - else if (y >= 1 && y <= 2) return blockFactory.create(BlockType.DIRT, x, y, z, getWorld()); - else if (y == 3) return blockFactory.create(BlockType.GRASS, x, y, z, getWorld()); - else return blockFactory.create(BlockType.AIR, x, y, z, getWorld()); - } - - @Override - public World getWorld() { - return DummyWorld.this; - } - } - - private class DummyChunk implements Chunk { - @Override - public World getWorld() { - return DummyWorld.this; - } - - @Override - public void setWorld(World world) { - } - - @Override - public ChunkSection getChunkSection(int height) { - if (height < 1) return new DummyChunkSection(); - else return null; - } - - @Override - public void setChunkSection(int height, ChunkSection chunkSection) { - } - - @Override - public Biome getBiome(int localX, int localZ) { - return Biome.PLAINS; - } - - @Override - public void setBiome(int localX, int localZ, Biome biome) { - } - - @Override - public int getX() { - return 0; - } - - @Override - public int getZ() { - return 0; - } - } - - private final Chunk chunk = new DummyChunk(); - - @Override - public WorldType getWorldType() { - return WorldType.FLAT; - } - - @Override - public EntityLocation getSpawn() { - return null; - } - - @Override - public void setSpawn(EntityLocation location) { - } - - @Override - public Chunk getChunk(int x, int z) { - return chunk; - } - - @Override - public void setChunk(int x, int z, Chunk chunk) { - } -} diff --git a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/TestChunkdataPacket.java b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/TestChunkdataPacket.java index 05f8528..ba9147a 100644 --- a/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/TestChunkdataPacket.java +++ b/proto_1.12.2/src/test/java/mc/core/network/proto_1_12_2/packets/TestChunkdataPacket.java @@ -2,15 +2,29 @@ package mc.core.network.proto_1_12_2.packets; import com.google.common.io.ByteStreams; import mc.core.network.proto_1_12_2.ByteArrayOutputNetStream; +import mc.core.world.Biome; import mc.core.world.World; -import org.junit.Assert; +import mc.core.world.WorldType; +import mc.core.world.block.BlockFactory; +import mc.core.world.block.BlockType; +import mc.core.world.chunk.Chunk; +import mc.core.world.chunk.ChunkSection; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; -import java.io.*; +import java.io.IOException; +import java.io.InputStream; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class TestChunkdataPacket { private static byte[] expectedPacketData; + private World world; @BeforeClass public static void beforeClassTest() throws IOException { @@ -18,8 +32,48 @@ public class TestChunkdataPacket { expectedPacketData = ByteStreams.toByteArray(inputStream); } - private World createDummyWorld() { - return new DummyWorld(); + @Before + public void prepareWorld() { + final ChunkSection chunkSection = mock(ChunkSection.class); + when(chunkSection.getSkyLight(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> { + int y = (int)invocation.getArguments()[1]; + + if (y <= 3) return 0; + else return 15; + }); + when(chunkSection.getBiome(anyInt(), anyInt())).thenReturn(Biome.PLAINS); + when(chunkSection.getBlock(anyInt(), anyInt(), anyInt())).thenAnswer(invocation -> { + Object[] args = invocation.getArguments(); + int x = (int) args[0]; + int y = (int) args[1]; + int z = (int) args[2]; + + BlockFactory blockFactory = new BlockFactory(); + + if (y == 0) return blockFactory.create(BlockType.BEDROCK, x, y, z, null); + else if (y >= 1 && y <= 2) return blockFactory.create(BlockType.DIRT, x, y, z, null); + else if (y == 3) return blockFactory.create(BlockType.GRASS, x, y, z, null); + else return blockFactory.create(BlockType.AIR, x, y, z, null); + }); + + world = mock(World.class); + when(world.getWorldType()).thenReturn(WorldType.FLAT); + when(world.getChunk(anyInt(), anyInt())).thenAnswer(invocation -> { + Object[] args = invocation.getArguments(); + + Chunk chunk = mock(Chunk.class); + when(chunk.getX()).thenReturn((int) args[0]); + when(chunk.getZ()).thenReturn((int) args[1]); + when(chunk.getBiome(anyInt(), anyInt())).thenReturn(Biome.PLAINS); + when(chunk.getChunkSection(anyInt())).thenAnswer(invocation1 -> { + int height = (int)invocation1.getArguments()[0]; + + if (height < 1) return chunkSection; + else return null; + }); + + return chunk; + }); } @Test @@ -27,14 +81,14 @@ public class TestChunkdataPacket { ChunkDataPacket packet = new ChunkDataPacket(); packet.setX(0); packet.setZ(0); - packet.setChunk(createDummyWorld().getChunk(0, 0)); + packet.setChunk(world.getChunk(0, 0)); packet.setInitChunk(true); ByteArrayOutputNetStream netStream = new ByteArrayOutputNetStream(); packet.writeSelf(netStream); byte[] actualPacketData = netStream.toByteArray(); - Assert.assertEquals(expectedPacketData.length, actualPacketData.length); - Assert.assertArrayEquals(expectedPacketData, actualPacketData); + assertEquals(expectedPacketData.length, actualPacketData.length); + assertArrayEquals(expectedPacketData, actualPacketData); } } diff --git a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PacketDecoder.java b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PacketDecoder.java index dc1090b..57b1832 100644 --- a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PacketDecoder.java +++ b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PacketDecoder.java @@ -51,13 +51,19 @@ public class PacketDecoder extends ByteToMessageDecoder { Class packetClass = state.getClientSidePacket(packetId); if (packetClass == null) { log.warn("Unknown packet: {}:0x{}", state.name(), hexPacketId); - in.skipBytes(packetSize - rb); + in.skipBytes(in.readableBytes()); } else { netStream.setDataSize(packetSize - rb); CSPacket packet = packetClass.newInstance(); - packet.readSelf(netStream); - log.debug("Known packet: {}:{}", state.name(), packet.toString()); - out.add(packet); + try { + packet.readSelf(netStream); + log.debug("Known packet: {}:{}", state.name(), packet.toString()); + out.add(packet); + } catch (Exception e) { + log.warn("Known packet: {}:{}. But throw exception. See debug log.", state.name(), packet.getClass().getSimpleName()); + log.debug("Read packet", e); + in.skipBytes(in.readableBytes()); + } } } } diff --git a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PlayerEventListener.java b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PlayerEventListener.java index 63415cd..66d46ec 100644 --- a/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PlayerEventListener.java +++ b/proto_1.12.2_netty/src/main/java/mc/core/network/proto_1_12_2/netty/PlayerEventListener.java @@ -3,10 +3,12 @@ package mc.core.network.proto_1_12_2.netty; import com.google.common.eventbus.Subscribe; import lombok.extern.slf4j.Slf4j; import mc.core.events.SC_ChunkLoadEvent; +import mc.core.events.SC_ChunkUnloadEvent; import mc.core.events.SC_PlayerMoveEvent; import mc.core.network.proto_1_12_2.TeleportManager; import mc.core.network.proto_1_12_2.packets.ChunkDataPacket; import mc.core.network.proto_1_12_2.packets.PlayerPositionAndLookPacket; +import mc.core.network.proto_1_12_2.packets.UnloadChunkPacket; import mc.core.utils.CompactedCoords; import mc.core.world.chunk.Chunk; import mc.core.world.chunk.ChunkSection; @@ -26,8 +28,6 @@ class PlayerEventListener { @Subscribe public void playerChunkLoadHandler(SC_ChunkLoadEvent event) { - log.debug("(SC) playerChunkLoadHandler()"); - for(Integer compressXZ : event.getNeedLoadChunks()) { int[] xz = CompactedCoords.uncompressXZ(compressXZ); Chunk chunk = event.getPlayer().getLocation().getWorld().getChunk(xz[0], xz[1]); @@ -38,9 +38,20 @@ class PlayerEventListener { packet.setInitChunk(true); packet.setChunk(chunk); - event.getPlayer().getChannel().write(packet); + event.getPlayer().getChannel().writeAndFlush(packet); } + } - event.getPlayer().getChannel().flush(); + @Subscribe + public void playerChunkUnloadHandler(SC_ChunkUnloadEvent event) { + for(Integer compressXZ : event.getNeedUnloadChunks()) { + int[] xz = CompactedCoords.uncompressXZ(compressXZ); + + UnloadChunkPacket packet = new UnloadChunkPacket(); + packet.setX(xz[0]); + packet.setZ(xz[1]); + + event.getPlayer().getChannel().writeAndFlush(packet); + } } }