diff --git a/client/src/main/java/nl/andrewl/aos2_client/Camera.java b/client/src/main/java/nl/andrewl/aos2_client/Camera.java index bf9c6e3..ad28dfb 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/Camera.java +++ b/client/src/main/java/nl/andrewl/aos2_client/Camera.java @@ -1,6 +1,7 @@ package nl.andrewl.aos2_client; import nl.andrewl.aos_core.MathUtils; +import nl.andrewl.aos_core.model.Player; import org.joml.Matrix4f; import org.joml.Vector2f; import org.joml.Vector3f; @@ -46,6 +47,11 @@ public class Camera { this.viewTransform = new Matrix4f(); } + public void setToPlayer(Player p) { + position.set(p.getPosition()); + velocity.set(p.getVelocity()); + } + public Matrix4f getViewTransform() { return viewTransform; } diff --git a/client/src/main/java/nl/andrewl/aos2_client/Client.java b/client/src/main/java/nl/andrewl/aos2_client/Client.java index 5703330..2617a23 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/Client.java +++ b/client/src/main/java/nl/andrewl/aos2_client/Client.java @@ -5,12 +5,15 @@ import nl.andrewl.aos2_client.control.InputHandler; import nl.andrewl.aos2_client.control.PlayerInputKeyCallback; import nl.andrewl.aos2_client.control.PlayerInputMouseClickCallback; import nl.andrewl.aos2_client.control.PlayerViewCursorCallback; +import nl.andrewl.aos2_client.model.ClientPlayer; import nl.andrewl.aos2_client.render.GameRenderer; import nl.andrewl.aos_core.config.Config; import nl.andrewl.aos_core.model.world.ColorPalette; -import nl.andrewl.aos_core.net.*; -import nl.andrewl.aos_core.net.udp.ChunkUpdateMessage; -import nl.andrewl.aos_core.net.udp.PlayerUpdateMessage; +import nl.andrewl.aos_core.net.client.*; +import nl.andrewl.aos_core.net.world.ChunkDataMessage; +import nl.andrewl.aos_core.net.world.ChunkHashMessage; +import nl.andrewl.aos_core.net.world.ChunkUpdateMessage; +import nl.andrewl.aos_core.net.world.WorldInfoMessage; import nl.andrewl.record_net.Message; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,13 +31,13 @@ public class Client implements Runnable { private final InputHandler inputHandler; private final GameRenderer gameRenderer; - private int clientId; private final ClientWorld world; + private ClientPlayer player; public Client(ClientConfig config) { this.config = config; this.communicationHandler = new CommunicationHandler(this); - this.inputHandler = new InputHandler(communicationHandler); + this.inputHandler = new InputHandler(this, communicationHandler); this.world = new ClientWorld(); this.gameRenderer = new GameRenderer(config.display, world); } @@ -43,17 +46,30 @@ public class Client implements Runnable { return config; } + public ClientPlayer getPlayer() { + return player; + } + + /** + * Called by the {@link CommunicationHandler} when a connection is + * established, and we need to begin tracking the player's state. + * @param player The player. + */ + public void setPlayer(ClientPlayer player) { + this.player = player; + } + @Override public void run() { try { - this.clientId = communicationHandler.establishConnection(); + communicationHandler.establishConnection(); } catch (IOException e) { log.error("Couldn't connect to the server: {}", e.getMessage()); return; } gameRenderer.setupWindow( - new PlayerViewCursorCallback(config.input, gameRenderer.getCamera(), communicationHandler), + new PlayerViewCursorCallback(config.input, this, gameRenderer.getCamera(), communicationHandler), new PlayerInputKeyCallback(inputHandler), new PlayerInputMouseClickCallback(inputHandler) ); @@ -75,30 +91,37 @@ public class Client implements Runnable { public void onMessageReceived(Message msg) { if (msg instanceof WorldInfoMessage worldInfo) { world.setPalette(ColorPalette.fromArray(worldInfo.palette())); - } - if (msg instanceof ChunkDataMessage chunkDataMessage) { + } else if (msg instanceof ChunkDataMessage chunkDataMessage) { world.addChunk(chunkDataMessage); - } - if (msg instanceof ChunkUpdateMessage u) { + } else if (msg instanceof ChunkUpdateMessage u) { world.updateChunk(u); // If we received an update for a chunk we don't have, request it! if (world.getChunkAt(u.getChunkPos()) == null) { communicationHandler.sendMessage(new ChunkHashMessage(u.cx(), u.cy(), u.cz(), -1)); } - } - if (msg instanceof PlayerUpdateMessage playerUpdate) { - if (playerUpdate.clientId() == clientId) { + } else if (msg instanceof PlayerUpdateMessage playerUpdate) { + if (playerUpdate.clientId() == player.getId()) { + player.getPosition().set(playerUpdate.px(), playerUpdate.py(), playerUpdate.pz()); + player.getVelocity().set(playerUpdate.vx(), playerUpdate.vy(), playerUpdate.vz()); + gameRenderer.getCamera().setToPlayer(player); + // TODO: Add getEyeHeight() and isCrouching() to main Player class. float eyeHeight = playerUpdate.crouching() ? 1.3f : 1.7f; - gameRenderer.getCamera().setPosition(playerUpdate.px(), playerUpdate.py() + eyeHeight, playerUpdate.pz()); - gameRenderer.getCamera().setVelocity(playerUpdate.vx(), playerUpdate.vy(), playerUpdate.vz()); + gameRenderer.getCamera().getPosition().y += eyeHeight; } else { world.playerUpdated(playerUpdate); } - } - if (msg instanceof PlayerJoinMessage joinMessage) { + } else if (msg instanceof ClientInventoryMessage inventoryMessage) { + player.setInventory(inventoryMessage.inv()); + System.out.println("Got inventory!"); + } else if (msg instanceof InventorySelectedStackMessage selectedStackMessage) { + player.getInventory().setSelectedIndex(selectedStackMessage.index()); + System.out.println("Selected item stack: " + player.getInventory().getSelectedItemStack().getType().getName()); + } else if (msg instanceof ItemStackMessage itemStackMessage) { + player.getInventory().getItemStacks().set(itemStackMessage.index(), itemStackMessage.stack()); + System.out.println("Item stack updated: " + itemStackMessage.index()); + } else if (msg instanceof PlayerJoinMessage joinMessage) { world.playerJoined(joinMessage); - } - if (msg instanceof PlayerLeaveMessage leaveMessage) { + } else if (msg instanceof PlayerLeaveMessage leaveMessage) { world.playerLeft(leaveMessage); } } diff --git a/client/src/main/java/nl/andrewl/aos2_client/ClientWorld.java b/client/src/main/java/nl/andrewl/aos2_client/ClientWorld.java index 20acd22..ba568b8 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/ClientWorld.java +++ b/client/src/main/java/nl/andrewl/aos2_client/ClientWorld.java @@ -5,11 +5,11 @@ import nl.andrewl.aos2_client.render.chunk.ChunkMeshGenerator; import nl.andrewl.aos_core.model.world.Chunk; import nl.andrewl.aos_core.model.Player; import nl.andrewl.aos_core.model.world.World; -import nl.andrewl.aos_core.net.ChunkDataMessage; -import nl.andrewl.aos_core.net.PlayerJoinMessage; -import nl.andrewl.aos_core.net.PlayerLeaveMessage; -import nl.andrewl.aos_core.net.udp.ChunkUpdateMessage; -import nl.andrewl.aos_core.net.udp.PlayerUpdateMessage; +import nl.andrewl.aos_core.net.world.ChunkDataMessage; +import nl.andrewl.aos_core.net.client.PlayerJoinMessage; +import nl.andrewl.aos_core.net.client.PlayerLeaveMessage; +import nl.andrewl.aos_core.net.world.ChunkUpdateMessage; +import nl.andrewl.aos_core.net.client.PlayerUpdateMessage; import org.joml.Vector3f; import org.joml.Vector3i; diff --git a/client/src/main/java/nl/andrewl/aos2_client/CommunicationHandler.java b/client/src/main/java/nl/andrewl/aos2_client/CommunicationHandler.java index 7f1c978..bbc03f8 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/CommunicationHandler.java +++ b/client/src/main/java/nl/andrewl/aos2_client/CommunicationHandler.java @@ -1,8 +1,12 @@ package nl.andrewl.aos2_client; +import nl.andrewl.aos2_client.model.ClientPlayer; import nl.andrewl.aos_core.Net; import nl.andrewl.aos_core.net.*; -import nl.andrewl.aos_core.net.udp.DatagramInit; +import nl.andrewl.aos_core.net.connect.ConnectAcceptMessage; +import nl.andrewl.aos_core.net.connect.ConnectRejectMessage; +import nl.andrewl.aos_core.net.connect.ConnectRequestMessage; +import nl.andrewl.aos_core.net.connect.DatagramInit; import nl.andrewl.record_net.Message; import nl.andrewl.record_net.util.ExtendedDataInputStream; import nl.andrewl.record_net.util.ExtendedDataOutputStream; @@ -33,7 +37,7 @@ public class CommunicationHandler { this.client = client; } - public int establishConnection() throws IOException { + public void establishConnection() throws IOException { if (socket != null && !socket.isClosed()) { socket.close(); } @@ -54,11 +58,11 @@ public class CommunicationHandler { } if (response instanceof ConnectAcceptMessage acceptMessage) { this.clientId = acceptMessage.clientId(); + client.setPlayer(new ClientPlayer(clientId, username)); establishDatagramConnection(); log.info("Connection to server established. My client id is {}.", clientId); new Thread(new TcpReceiver(in, client::onMessageReceived)).start(); new Thread(new UdpReceiver(datagramSocket, (msg, packet) -> client.onMessageReceived(msg))).start(); - return acceptMessage.clientId(); } else { throw new IOException("Server returned an unexpected message: " + response); } diff --git a/client/src/main/java/nl/andrewl/aos2_client/config/ClientConfig.java b/client/src/main/java/nl/andrewl/aos2_client/config/ClientConfig.java index a8e62ff..b4021ee 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/config/ClientConfig.java +++ b/client/src/main/java/nl/andrewl/aos2_client/config/ClientConfig.java @@ -13,7 +13,7 @@ public class ClientConfig { public static class DisplayConfig { public boolean fullscreen = false; - public boolean captureCursor = true; + public boolean captureCursor = false; public float fov = 70; } } diff --git a/client/src/main/java/nl/andrewl/aos2_client/control/InputHandler.java b/client/src/main/java/nl/andrewl/aos2_client/control/InputHandler.java index 91cda77..df00d6a 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/control/InputHandler.java +++ b/client/src/main/java/nl/andrewl/aos2_client/control/InputHandler.java @@ -1,7 +1,8 @@ package nl.andrewl.aos2_client.control; import nl.andrewl.aos2_client.CommunicationHandler; -import nl.andrewl.aos_core.net.udp.ClientInputState; +import nl.andrewl.aos_core.net.client.ClientInputState; +import nl.andrewl.aos2_client.Client; import static org.lwjgl.glfw.GLFW.*; @@ -9,16 +10,24 @@ import static org.lwjgl.glfw.GLFW.*; * Class which manages the player's input, and sending it to the server. */ public class InputHandler { + private final Client client; private final CommunicationHandler comm; private ClientInputState lastInputState = null; - public InputHandler(CommunicationHandler comm) { + public InputHandler(Client client, CommunicationHandler comm) { + this.client = client; this.comm = comm; } public void updateInputState(long window) { // TODO: Allow customized keybindings. + int selectedInventoryIndex; + selectedInventoryIndex = client.getPlayer().getInventory().getSelectedIndex(); + if (glfwGetKey(window, GLFW_KEY_1) == GLFW_PRESS) selectedInventoryIndex = 0; + if (glfwGetKey(window, GLFW_KEY_2) == GLFW_PRESS) selectedInventoryIndex = 1; + if (glfwGetKey(window, GLFW_KEY_3) == GLFW_PRESS) selectedInventoryIndex = 2; + ClientInputState currentInputState = new ClientInputState( comm.getClientId(), glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS, @@ -29,7 +38,8 @@ public class InputHandler { glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS, glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS, glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS, - glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS + glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS, + selectedInventoryIndex ); if (!currentInputState.equals(lastInputState)) { comm.sendDatagramPacket(currentInputState); diff --git a/client/src/main/java/nl/andrewl/aos2_client/control/PlayerViewCursorCallback.java b/client/src/main/java/nl/andrewl/aos2_client/control/PlayerViewCursorCallback.java index 24f6919..cbd246b 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/control/PlayerViewCursorCallback.java +++ b/client/src/main/java/nl/andrewl/aos2_client/control/PlayerViewCursorCallback.java @@ -1,9 +1,10 @@ package nl.andrewl.aos2_client.control; import nl.andrewl.aos2_client.Camera; +import nl.andrewl.aos2_client.Client; import nl.andrewl.aos2_client.CommunicationHandler; import nl.andrewl.aos2_client.config.ClientConfig; -import nl.andrewl.aos_core.net.udp.ClientOrientationState; +import nl.andrewl.aos_core.net.client.ClientOrientationState; import org.lwjgl.glfw.GLFWCursorPosCallbackI; import java.util.concurrent.ForkJoinPool; @@ -22,15 +23,17 @@ public class PlayerViewCursorCallback implements GLFWCursorPosCallbackI { private static final int ORIENTATION_UPDATE_LIMIT = 20; private final ClientConfig.InputConfig config; + private final Client client; private final Camera camera; private final CommunicationHandler comm; private float lastMouseCursorX; private float lastMouseCursorY; private long lastOrientationUpdateSentAt = 0L; - public PlayerViewCursorCallback(ClientConfig.InputConfig config, Camera camera, CommunicationHandler comm) { + public PlayerViewCursorCallback(ClientConfig.InputConfig config, Client client, Camera cam, CommunicationHandler comm) { this.config = config; - this.camera = camera; + this.client = client; + this.camera = cam; this.comm = comm; } @@ -45,13 +48,18 @@ public class PlayerViewCursorCallback implements GLFWCursorPosCallbackI { float dy = y - lastMouseCursorY; lastMouseCursorX = x; lastMouseCursorY = y; - camera.setOrientation( - camera.getOrientation().x - dx * config.mouseSensitivity, - camera.getOrientation().y - dy * config.mouseSensitivity + client.getPlayer().setOrientation( + client.getPlayer().getOrientation().x - dx * config.mouseSensitivity, + client.getPlayer().getOrientation().y - dy * config.mouseSensitivity ); + camera.setOrientation(client.getPlayer().getOrientation().x, client.getPlayer().getOrientation().y); long now = System.currentTimeMillis(); if (lastOrientationUpdateSentAt + ORIENTATION_UPDATE_LIMIT < now) { - ForkJoinPool.commonPool().submit(() -> comm.sendDatagramPacket(new ClientOrientationState(comm.getClientId(), camera.getOrientation().x, camera.getOrientation().y))); + ForkJoinPool.commonPool().submit(() -> comm.sendDatagramPacket(new ClientOrientationState( + client.getPlayer().getId(), + client.getPlayer().getOrientation().x, + client.getPlayer().getOrientation().y + ))); lastOrientationUpdateSentAt = now; } } diff --git a/client/src/main/java/nl/andrewl/aos2_client/model/ClientPlayer.java b/client/src/main/java/nl/andrewl/aos2_client/model/ClientPlayer.java new file mode 100644 index 0000000..44f8314 --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/model/ClientPlayer.java @@ -0,0 +1,25 @@ +package nl.andrewl.aos2_client.model; + +import nl.andrewl.aos_core.model.Player; +import nl.andrewl.aos_core.model.item.Inventory; + +import java.util.ArrayList; + +public class ClientPlayer extends Player { + private final Inventory inventory; + + public ClientPlayer(int id, String username) { + super(id, username); + this.inventory = new Inventory(new ArrayList<>(), 0); + } + + public Inventory getInventory() { + return inventory; + } + + public void setInventory(Inventory inv) { + this.inventory.getItemStacks().clear(); + this.inventory.getItemStacks().addAll(inv.getItemStacks()); + this.inventory.setSelectedIndex(inv.getSelectedIndex()); + } +} diff --git a/core/src/main/java/nl/andrewl/aos_core/Net.java b/core/src/main/java/nl/andrewl/aos_core/Net.java index c1dbf3c..59881f2 100644 --- a/core/src/main/java/nl/andrewl/aos_core/Net.java +++ b/core/src/main/java/nl/andrewl/aos_core/Net.java @@ -1,7 +1,14 @@ package nl.andrewl.aos_core; -import nl.andrewl.aos_core.net.*; -import nl.andrewl.aos_core.net.udp.*; +import nl.andrewl.aos_core.net.client.*; +import nl.andrewl.aos_core.net.connect.ConnectAcceptMessage; +import nl.andrewl.aos_core.net.connect.ConnectRejectMessage; +import nl.andrewl.aos_core.net.connect.ConnectRequestMessage; +import nl.andrewl.aos_core.net.connect.DatagramInit; +import nl.andrewl.aos_core.net.world.ChunkDataMessage; +import nl.andrewl.aos_core.net.world.ChunkHashMessage; +import nl.andrewl.aos_core.net.world.ChunkUpdateMessage; +import nl.andrewl.aos_core.net.world.WorldInfoMessage; import nl.andrewl.record_net.Message; import nl.andrewl.record_net.Serializer; import nl.andrewl.record_net.util.ExtendedDataInputStream; @@ -33,6 +40,10 @@ public final class Net { serializer.registerType(11, PlayerJoinMessage.class); serializer.registerType(12, PlayerLeaveMessage.class); serializer.registerType(13, WorldInfoMessage.class); + // Separate serializers for client inventory messages. + serializer.registerTypeSerializer(14, new InventorySerializer()); + serializer.registerTypeSerializer(15, new ItemStackSerializer()); + serializer.registerType(16, InventorySelectedStackMessage.class); } public static ExtendedDataInputStream getInputStream(InputStream in) { diff --git a/core/src/main/java/nl/andrewl/aos_core/model/item/BlockItemStack.java b/core/src/main/java/nl/andrewl/aos_core/model/item/BlockItemStack.java index ab417e6..f6d7f97 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/item/BlockItemStack.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/item/BlockItemStack.java @@ -1,13 +1,23 @@ package nl.andrewl.aos_core.model.item; public class BlockItemStack extends ItemStack { - private int selectedValue = 1; + private byte selectedValue = 1; - public BlockItemStack(BlockItem item, int amount) { + public BlockItemStack(BlockItem item, int amount, byte selectedValue) { super(item, amount); + this.selectedValue = selectedValue; } - public int getSelectedValue() { + public BlockItemStack(BlockItem item) { + this(item, 50, (byte) 1); + } + + public byte getSelectedValue() { return selectedValue; } + + public void setSelectedValue(byte selectedValue) { + if (selectedValue < 1) return; + this.selectedValue = selectedValue; + } } diff --git a/core/src/main/java/nl/andrewl/aos_core/model/item/GunItemStack.java b/core/src/main/java/nl/andrewl/aos_core/model/item/GunItemStack.java index 4558625..431fd99 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/item/GunItemStack.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/item/GunItemStack.java @@ -4,9 +4,29 @@ public class GunItemStack extends ItemStack { private int bulletCount; private int clipCount; - public GunItemStack(Gun gun) { + public GunItemStack(Gun gun, int bulletCount, int clipCount) { super(gun, 1); - bulletCount = gun.getMaxBulletCount(); - clipCount = gun.getMaxClipCount(); + this.bulletCount = bulletCount; + this.clipCount = clipCount; + } + + public GunItemStack(Gun gun) { + this(gun, gun.getMaxBulletCount(), gun.getMaxClipCount()); + } + + public int getBulletCount() { + return bulletCount; + } + + public void setBulletCount(int bulletCount) { + this.bulletCount = bulletCount; + } + + public int getClipCount() { + return clipCount; + } + + public void setClipCount(int clipCount) { + this.clipCount = clipCount; } } diff --git a/core/src/main/java/nl/andrewl/aos_core/model/item/Inventory.java b/core/src/main/java/nl/andrewl/aos_core/model/item/Inventory.java index 52c3692..cb20212 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/item/Inventory.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/item/Inventory.java @@ -25,13 +25,17 @@ public class Inventory { return itemStacks; } + public int getSelectedIndex() { + return selectedIndex; + } + public ItemStack getSelectedItemStack() { return itemStacks.get(selectedIndex); } public void setSelectedIndex(int newIndex) { - while (newIndex < 0) newIndex += itemStacks.size(); - while (newIndex > itemStacks.size() - 1) newIndex -= itemStacks.size(); + if (newIndex < 0) newIndex = 0; + if (newIndex > itemStacks.size() - 1) newIndex = itemStacks.size() - 1; this.selectedIndex = newIndex; } } diff --git a/core/src/main/java/nl/andrewl/aos_core/model/item/ItemStack.java b/core/src/main/java/nl/andrewl/aos_core/model/item/ItemStack.java index a920fec..d283f07 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/item/ItemStack.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/item/ItemStack.java @@ -1,5 +1,10 @@ package nl.andrewl.aos_core.model.item; +import nl.andrewl.record_net.util.ExtendedDataInputStream; +import nl.andrewl.record_net.util.ExtendedDataOutputStream; + +import java.io.IOException; + /** * Represents a stack of items in the player's inventory. This is generally * a type of item, and the amount of it. @@ -22,6 +27,53 @@ public class ItemStack { } public void setAmount(int amount) { - this.amount = amount; + if (amount > -1) { + this.amount = amount; + } + } + + public void incrementAmount() { + setAmount(amount + 1); + } + + public void decrementAmount() { + setAmount(amount - 1); + } + + public static int byteSize(ItemStack stack) { + int bytes = 2 * Integer.BYTES; + if (stack instanceof BlockItemStack) { + bytes += Byte.BYTES; + } else if (stack instanceof GunItemStack) { + bytes += 2 * Integer.BYTES; + } + return bytes; + } + + public static void write(ItemStack stack, ExtendedDataOutputStream out) throws IOException { + out.writeInt(stack.type.id); + out.writeInt(stack.amount); + if (stack instanceof BlockItemStack b) { + out.writeByte(b.getSelectedValue()); + } else if (stack instanceof GunItemStack g) { + out.writeInt(g.getClipCount()); + out.writeInt(g.getBulletCount()); + } + } + + public static ItemStack read(ExtendedDataInputStream in) throws IOException { + int typeId = in.readInt(); + Item item = ItemTypes.get(typeId); + int amount = in.readInt(); + if (item instanceof Gun g) { + int clipCount = in.readInt(); + int bulletCount = in.readInt(); + return new GunItemStack(g, bulletCount, clipCount); + } else if (item instanceof BlockItem b) { + byte selectedValue = in.readByte(); + return new BlockItemStack(b, amount, selectedValue); + } else { + throw new IOException("Invalid item stack."); + } } } diff --git a/core/src/main/java/nl/andrewl/aos_core/model/item/ItemTypes.java b/core/src/main/java/nl/andrewl/aos_core/model/item/ItemTypes.java index 23da11c..639ecf4 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/item/ItemTypes.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/item/ItemTypes.java @@ -12,9 +12,12 @@ public final class ItemTypes { private static final Map TYPES_BY_ID = new HashMap<>(); private static final Map TYPES_BY_NAME = new HashMap<>(); + public static final BlockItem BLOCK = new BlockItem(1); + public static final Rifle RIFLE = new Rifle(2); + static { - registerType(new BlockItem(1)); - registerType(new Rifle(2)); + registerType(BLOCK); + registerType(RIFLE); } public static void registerType(Item type) { diff --git a/core/src/main/java/nl/andrewl/aos_core/net/udp/ClientInputState.java b/core/src/main/java/nl/andrewl/aos_core/net/client/ClientInputState.java similarity index 70% rename from core/src/main/java/nl/andrewl/aos_core/net/udp/ClientInputState.java rename to core/src/main/java/nl/andrewl/aos_core/net/client/ClientInputState.java index c409d77..1e0b2a8 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/udp/ClientInputState.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/ClientInputState.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos_core.net.udp; +package nl.andrewl.aos_core.net.client; import nl.andrewl.record_net.Message; @@ -19,5 +19,6 @@ public record ClientInputState( // Interaction boolean hitting, // Usually a "left-click" action. - boolean interacting // Usually a "right-click" action. + boolean interacting, // Usually a "right-click" action. + int selectedInventoryIndex // The selected index in the player's inventory. ) implements Message {} diff --git a/core/src/main/java/nl/andrewl/aos_core/net/client/ClientInventoryMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/client/ClientInventoryMessage.java new file mode 100644 index 0000000..6a277bd --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/ClientInventoryMessage.java @@ -0,0 +1,15 @@ +package nl.andrewl.aos_core.net.client; + +import nl.andrewl.aos_core.model.item.Inventory; +import nl.andrewl.record_net.Message; + +/** + * A message that's sent by the server to a client with information about the + * client's full inventory configuration. Here, we use a custom serializer + * since the inventory object contains a lot of inheritance. + * + * @see InventorySerializer + */ +public record ClientInventoryMessage( + Inventory inv +) implements Message {} diff --git a/core/src/main/java/nl/andrewl/aos_core/net/udp/ClientOrientationState.java b/core/src/main/java/nl/andrewl/aos_core/net/client/ClientOrientationState.java similarity index 89% rename from core/src/main/java/nl/andrewl/aos_core/net/udp/ClientOrientationState.java rename to core/src/main/java/nl/andrewl/aos_core/net/client/ClientOrientationState.java index bb373d4..4f55085 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/udp/ClientOrientationState.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/ClientOrientationState.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos_core.net.udp; +package nl.andrewl.aos_core.net.client; import nl.andrewl.record_net.Message; diff --git a/core/src/main/java/nl/andrewl/aos_core/net/client/InventorySelectedStackMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/client/InventorySelectedStackMessage.java new file mode 100644 index 0000000..a3ceed2 --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/InventorySelectedStackMessage.java @@ -0,0 +1,11 @@ +package nl.andrewl.aos_core.net.client; + +import nl.andrewl.record_net.Message; + +/** + * A message that's sent when a player's selected inventory stack changes. + * @param index The selected index. + */ +public record InventorySelectedStackMessage( + int index +) implements Message {} diff --git a/core/src/main/java/nl/andrewl/aos_core/net/client/InventorySerializer.java b/core/src/main/java/nl/andrewl/aos_core/net/client/InventorySerializer.java new file mode 100644 index 0000000..0ca0649 --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/InventorySerializer.java @@ -0,0 +1,56 @@ +package nl.andrewl.aos_core.net.client; + +import nl.andrewl.aos_core.model.item.Inventory; +import nl.andrewl.aos_core.model.item.ItemStack; +import nl.andrewl.record_net.MessageReader; +import nl.andrewl.record_net.MessageTypeSerializer; +import nl.andrewl.record_net.MessageWriter; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +public class InventorySerializer implements MessageTypeSerializer { + @Override + public Class messageClass() { + return ClientInventoryMessage.class; + } + + @Override + public Function byteSizeFunction() { + return msg -> { + int bytes = Integer.BYTES; // For the stack count size. + for (var stack : msg.inv().getItemStacks()) { + bytes += ItemStack.byteSize(stack); + } + bytes += Integer.BYTES; // Selected index. + return bytes; + }; + } + + @Override + public MessageReader reader() { + return in -> { + int stacksCount = in.readInt(); + List stacks = new ArrayList<>(stacksCount); + for (int i = 0; i < stacksCount; i++) { + stacks.add(ItemStack.read(in)); + } + int selectedIndex = in.readInt(); + Inventory inv = new Inventory(stacks, selectedIndex); + return new ClientInventoryMessage(inv); + }; + } + + @Override + public MessageWriter writer() { + return (msg, out) -> { + Inventory inv = msg.inv(); + out.writeInt(inv.getItemStacks().size()); + for (var stack : inv.getItemStacks()) { + ItemStack.write(stack, out); + } + out.writeInt(inv.getSelectedIndex()); + }; + } +} diff --git a/core/src/main/java/nl/andrewl/aos_core/net/client/ItemStackMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/client/ItemStackMessage.java new file mode 100644 index 0000000..5e6007a --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/ItemStackMessage.java @@ -0,0 +1,18 @@ +package nl.andrewl.aos_core.net.client; + +import nl.andrewl.aos_core.model.item.Inventory; +import nl.andrewl.aos_core.model.item.ItemStack; +import nl.andrewl.record_net.Message; + +/** + * Lightweight packet that's sent when a single item stack in a player's + * inventory updates. + */ +public record ItemStackMessage( + int index, + ItemStack stack +) implements Message { + public ItemStackMessage(Inventory inv) { + this(inv.getSelectedIndex(), inv.getSelectedItemStack()); + } +} diff --git a/core/src/main/java/nl/andrewl/aos_core/net/client/ItemStackSerializer.java b/core/src/main/java/nl/andrewl/aos_core/net/client/ItemStackSerializer.java new file mode 100644 index 0000000..201bbaf --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/ItemStackSerializer.java @@ -0,0 +1,37 @@ +package nl.andrewl.aos_core.net.client; + +import nl.andrewl.aos_core.model.item.ItemStack; +import nl.andrewl.record_net.MessageReader; +import nl.andrewl.record_net.MessageTypeSerializer; +import nl.andrewl.record_net.MessageWriter; + +import java.util.function.Function; + +public class ItemStackSerializer implements MessageTypeSerializer { + @Override + public Class messageClass() { + return ItemStackMessage.class; + } + + @Override + public Function byteSizeFunction() { + return msg -> Integer.BYTES + ItemStack.byteSize(msg.stack()); + } + + @Override + public MessageReader reader() { + return in -> { + int index = in.readInt(); + ItemStack stack = ItemStack.read(in); + return new ItemStackMessage(index, stack); + }; + } + + @Override + public MessageWriter writer() { + return (msg, out) -> { + out.writeInt(msg.index()); + ItemStack.write(msg.stack(), out); + }; + } +} diff --git a/core/src/main/java/nl/andrewl/aos_core/net/PlayerJoinMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/client/PlayerJoinMessage.java similarity index 95% rename from core/src/main/java/nl/andrewl/aos_core/net/PlayerJoinMessage.java rename to core/src/main/java/nl/andrewl/aos_core/net/client/PlayerJoinMessage.java index 1e927f9..4118296 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/PlayerJoinMessage.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/PlayerJoinMessage.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos_core.net; +package nl.andrewl.aos_core.net.client; import nl.andrewl.aos_core.model.Player; import nl.andrewl.record_net.Message; diff --git a/core/src/main/java/nl/andrewl/aos_core/net/PlayerLeaveMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/client/PlayerLeaveMessage.java similarity index 84% rename from core/src/main/java/nl/andrewl/aos_core/net/PlayerLeaveMessage.java rename to core/src/main/java/nl/andrewl/aos_core/net/client/PlayerLeaveMessage.java index 26ef0b4..efe486b 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/PlayerLeaveMessage.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/PlayerLeaveMessage.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos_core.net; +package nl.andrewl.aos_core.net.client; import nl.andrewl.record_net.Message; diff --git a/core/src/main/java/nl/andrewl/aos_core/net/udp/PlayerUpdateMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/client/PlayerUpdateMessage.java similarity index 93% rename from core/src/main/java/nl/andrewl/aos_core/net/udp/PlayerUpdateMessage.java rename to core/src/main/java/nl/andrewl/aos_core/net/client/PlayerUpdateMessage.java index 034caf8..10485d3 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/udp/PlayerUpdateMessage.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/PlayerUpdateMessage.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos_core.net.udp; +package nl.andrewl.aos_core.net.client; import nl.andrewl.aos_core.model.Player; import nl.andrewl.record_net.Message; diff --git a/core/src/main/java/nl/andrewl/aos_core/net/ConnectAcceptMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/connect/ConnectAcceptMessage.java similarity index 87% rename from core/src/main/java/nl/andrewl/aos_core/net/ConnectAcceptMessage.java rename to core/src/main/java/nl/andrewl/aos_core/net/connect/ConnectAcceptMessage.java index 5208148..0f5cbbe 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/ConnectAcceptMessage.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/connect/ConnectAcceptMessage.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos_core.net; +package nl.andrewl.aos_core.net.connect; import nl.andrewl.record_net.Message; diff --git a/core/src/main/java/nl/andrewl/aos_core/net/ConnectRejectMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/connect/ConnectRejectMessage.java similarity index 85% rename from core/src/main/java/nl/andrewl/aos_core/net/ConnectRejectMessage.java rename to core/src/main/java/nl/andrewl/aos_core/net/connect/ConnectRejectMessage.java index b96aa0c..b765224 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/ConnectRejectMessage.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/connect/ConnectRejectMessage.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos_core.net; +package nl.andrewl.aos_core.net.connect; import nl.andrewl.record_net.Message; diff --git a/core/src/main/java/nl/andrewl/aos_core/net/ConnectRequestMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/connect/ConnectRequestMessage.java similarity index 73% rename from core/src/main/java/nl/andrewl/aos_core/net/ConnectRequestMessage.java rename to core/src/main/java/nl/andrewl/aos_core/net/connect/ConnectRequestMessage.java index 2e0f3c6..a869ee4 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/ConnectRequestMessage.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/connect/ConnectRequestMessage.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos_core.net; +package nl.andrewl.aos_core.net.connect; import nl.andrewl.record_net.Message; diff --git a/core/src/main/java/nl/andrewl/aos_core/net/udp/DatagramInit.java b/core/src/main/java/nl/andrewl/aos_core/net/connect/DatagramInit.java similarity index 88% rename from core/src/main/java/nl/andrewl/aos_core/net/udp/DatagramInit.java rename to core/src/main/java/nl/andrewl/aos_core/net/connect/DatagramInit.java index 80dbd19..8e8ff28 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/udp/DatagramInit.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/connect/DatagramInit.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos_core.net.udp; +package nl.andrewl.aos_core.net.connect; import nl.andrewl.record_net.Message; diff --git a/core/src/main/java/nl/andrewl/aos_core/net/ChunkDataMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/world/ChunkDataMessage.java similarity index 92% rename from core/src/main/java/nl/andrewl/aos_core/net/ChunkDataMessage.java rename to core/src/main/java/nl/andrewl/aos_core/net/world/ChunkDataMessage.java index 58b7ba9..20ab6e8 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/ChunkDataMessage.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/world/ChunkDataMessage.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos_core.net; +package nl.andrewl.aos_core.net.world; import nl.andrewl.aos_core.model.world.Chunk; import nl.andrewl.record_net.Message; diff --git a/core/src/main/java/nl/andrewl/aos_core/net/ChunkHashMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/world/ChunkHashMessage.java similarity index 94% rename from core/src/main/java/nl/andrewl/aos_core/net/ChunkHashMessage.java rename to core/src/main/java/nl/andrewl/aos_core/net/world/ChunkHashMessage.java index 6620cc3..a35d6b0 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/ChunkHashMessage.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/world/ChunkHashMessage.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos_core.net; +package nl.andrewl.aos_core.net.world; import nl.andrewl.aos_core.model.world.Chunk; import nl.andrewl.record_net.Message; diff --git a/core/src/main/java/nl/andrewl/aos_core/net/udp/ChunkUpdateMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/world/ChunkUpdateMessage.java similarity index 96% rename from core/src/main/java/nl/andrewl/aos_core/net/udp/ChunkUpdateMessage.java rename to core/src/main/java/nl/andrewl/aos_core/net/world/ChunkUpdateMessage.java index e106118..1d27930 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/udp/ChunkUpdateMessage.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/world/ChunkUpdateMessage.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos_core.net.udp; +package nl.andrewl.aos_core.net.world; import nl.andrewl.aos_core.model.world.World; import nl.andrewl.record_net.Message; diff --git a/core/src/main/java/nl/andrewl/aos_core/net/WorldInfoMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/world/WorldInfoMessage.java similarity index 89% rename from core/src/main/java/nl/andrewl/aos_core/net/WorldInfoMessage.java rename to core/src/main/java/nl/andrewl/aos_core/net/world/WorldInfoMessage.java index aa00013..b7d1604 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/WorldInfoMessage.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/world/WorldInfoMessage.java @@ -1,4 +1,4 @@ -package nl.andrewl.aos_core.net; +package nl.andrewl.aos_core.net.world; import nl.andrewl.aos_core.model.world.World; import nl.andrewl.record_net.Message; diff --git a/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java b/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java index c67dd8a..b0d7900 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java +++ b/server/src/main/java/nl/andrewl/aos2_server/ClientCommunicationHandler.java @@ -3,6 +3,14 @@ package nl.andrewl.aos2_server; import nl.andrewl.aos_core.Net; import nl.andrewl.aos_core.model.world.Chunk; import nl.andrewl.aos_core.net.*; +import nl.andrewl.aos_core.net.client.PlayerJoinMessage; +import nl.andrewl.aos_core.net.connect.ConnectAcceptMessage; +import nl.andrewl.aos_core.net.connect.ConnectRejectMessage; +import nl.andrewl.aos_core.net.connect.ConnectRequestMessage; +import nl.andrewl.aos_core.net.client.ClientInventoryMessage; +import nl.andrewl.aos_core.net.world.ChunkDataMessage; +import nl.andrewl.aos_core.net.world.ChunkHashMessage; +import nl.andrewl.aos_core.net.world.WorldInfoMessage; import nl.andrewl.record_net.Message; import nl.andrewl.record_net.util.ExtendedDataInputStream; import nl.andrewl.record_net.util.ExtendedDataOutputStream; @@ -81,6 +89,7 @@ public class ClientCommunicationHandler { try { Message msg = Net.read(in); if (msg instanceof ConnectRequestMessage connectMsg) { + log.debug("Received connect request from player \"{}\"", connectMsg.username()); // Try to set the TCP timeout back to 0 now that we've got the correct request. socket.setSoTimeout(0); this.clientAddress = socket.getInetAddress(); @@ -90,7 +99,10 @@ public class ClientCommunicationHandler { log.debug("Sent connect accept message."); sendTcpMessage(new WorldInfoMessage(server.getWorld())); - // Send join info for all players that are already connected. + // Send player's inventory information. + sendTcpMessage(new ClientInventoryMessage(player.getInventory())); + + // Send "join" info about all the players that are already connected, so the client is aware of them. for (var player : server.getPlayerManager().getPlayers()) { if (player.getId() != this.player.getId()) { sendTcpMessage(new PlayerJoinMessage(player)); diff --git a/server/src/main/java/nl/andrewl/aos2_server/PlayerManager.java b/server/src/main/java/nl/andrewl/aos2_server/PlayerManager.java index a8b5d57..bdf6a05 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/PlayerManager.java +++ b/server/src/main/java/nl/andrewl/aos2_server/PlayerManager.java @@ -1,9 +1,9 @@ package nl.andrewl.aos2_server; import nl.andrewl.aos_core.Net; -import nl.andrewl.aos_core.net.PlayerJoinMessage; -import nl.andrewl.aos_core.net.PlayerLeaveMessage; -import nl.andrewl.aos_core.net.udp.DatagramInit; +import nl.andrewl.aos_core.net.client.PlayerJoinMessage; +import nl.andrewl.aos_core.net.client.PlayerLeaveMessage; +import nl.andrewl.aos_core.net.connect.DatagramInit; import nl.andrewl.record_net.Message; import org.joml.Vector3f; import org.slf4j.Logger; diff --git a/server/src/main/java/nl/andrewl/aos2_server/Server.java b/server/src/main/java/nl/andrewl/aos2_server/Server.java index 293887e..a2e461a 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/Server.java +++ b/server/src/main/java/nl/andrewl/aos2_server/Server.java @@ -6,9 +6,9 @@ import nl.andrewl.aos_core.config.Config; import nl.andrewl.aos_core.model.world.World; import nl.andrewl.aos_core.model.world.Worlds; import nl.andrewl.aos_core.net.UdpReceiver; -import nl.andrewl.aos_core.net.udp.ClientInputState; -import nl.andrewl.aos_core.net.udp.ClientOrientationState; -import nl.andrewl.aos_core.net.udp.DatagramInit; +import nl.andrewl.aos_core.net.client.ClientInputState; +import nl.andrewl.aos_core.net.client.ClientOrientationState; +import nl.andrewl.aos_core.net.connect.DatagramInit; import nl.andrewl.record_net.Message; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,8 +66,9 @@ public class Server implements Runnable { } else if (msg instanceof ClientInputState inputState) { ServerPlayer player = playerManager.getPlayer(inputState.clientId()); if (player != null) { - player.getActionManager().setLastInputState(inputState); - playerManager.broadcastUdpMessage(player.getUpdateMessage()); + if (player.getActionManager().setLastInputState(inputState)) { + playerManager.broadcastUdpMessage(player.getUpdateMessage()); + } } } else if (msg instanceof ClientOrientationState orientationState) { ServerPlayer player = playerManager.getPlayer(orientationState.clientId()); @@ -86,7 +87,7 @@ public class Server implements Runnable { ForkJoinPool.commonPool().submit(() -> { try { handler.establishConnection(); - } catch (IOException e) { + } catch (Exception e) { e.printStackTrace(); } }); diff --git a/server/src/main/java/nl/andrewl/aos2_server/ServerPlayer.java b/server/src/main/java/nl/andrewl/aos2_server/ServerPlayer.java index 8b6d2ba..fff5536 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/ServerPlayer.java +++ b/server/src/main/java/nl/andrewl/aos2_server/ServerPlayer.java @@ -2,15 +2,20 @@ package nl.andrewl.aos2_server; import nl.andrewl.aos2_server.logic.PlayerActionManager; import nl.andrewl.aos_core.model.Player; -import nl.andrewl.aos_core.model.item.*; -import nl.andrewl.aos_core.model.item.gun.Rifle; -import nl.andrewl.aos_core.net.udp.PlayerUpdateMessage; +import nl.andrewl.aos_core.model.item.BlockItemStack; +import nl.andrewl.aos_core.model.item.GunItemStack; +import nl.andrewl.aos_core.model.item.Inventory; +import nl.andrewl.aos_core.model.item.ItemTypes; +import nl.andrewl.aos_core.net.client.PlayerUpdateMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; -import java.util.List; +/** + * An extension of the base player class with additional information that's + * needed for the server. + */ public class ServerPlayer extends Player { private static final Logger log = LoggerFactory.getLogger(ServerPlayer.class); @@ -26,16 +31,20 @@ public class ServerPlayer extends Player { public ServerPlayer(int id, String username) { super(id, username); - this.actionManager = new PlayerActionManager(this); this.inventory = new Inventory(new ArrayList<>(), 0); + this.actionManager = new PlayerActionManager(this); inventory.getItemStacks().add(new GunItemStack(ItemTypes.get("Rifle"))); - inventory.getItemStacks().add(new BlockItemStack(ItemTypes.get("Block"), 50)); + inventory.getItemStacks().add(new BlockItemStack(ItemTypes.get("Block"), 50, (byte) 1)); } public PlayerActionManager getActionManager() { return actionManager; } + public Inventory getInventory() { + return inventory; + } + /** * Helper method to build an update message for this player, to be sent to * various clients. diff --git a/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java b/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java index dc5a4b2..05f3b1f 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java +++ b/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java @@ -3,9 +3,13 @@ package nl.andrewl.aos2_server.logic; import nl.andrewl.aos2_server.Server; import nl.andrewl.aos2_server.ServerPlayer; import nl.andrewl.aos2_server.config.ServerConfig; +import nl.andrewl.aos_core.model.item.BlockItemStack; +import nl.andrewl.aos_core.model.item.ItemTypes; import nl.andrewl.aos_core.model.world.World; -import nl.andrewl.aos_core.net.udp.ChunkUpdateMessage; -import nl.andrewl.aos_core.net.udp.ClientInputState; +import nl.andrewl.aos_core.net.client.ClientInputState; +import nl.andrewl.aos_core.net.client.InventorySelectedStackMessage; +import nl.andrewl.aos_core.net.client.ItemStackMessage; +import nl.andrewl.aos_core.net.world.ChunkUpdateMessage; import org.joml.Math; import org.joml.Vector2i; import org.joml.Vector3f; @@ -30,15 +34,23 @@ public class PlayerActionManager { public PlayerActionManager(ServerPlayer player) { this.player = player; - lastInputState = new ClientInputState(player.getId(), false, false, false, false, false, false, false, false, false); + lastInputState = new ClientInputState( + player.getId(), + false, false, false, false, + false, false, false, + false, false, + player.getInventory().getSelectedIndex() + ); } public ClientInputState getLastInputState() { return lastInputState; } - public void setLastInputState(ClientInputState lastInputState) { - this.lastInputState = lastInputState; + public boolean setLastInputState(ClientInputState lastInputState) { + boolean change = !lastInputState.equals(this.lastInputState); + if (change) this.lastInputState = lastInputState; + return change; } public boolean isUpdated() { @@ -46,36 +58,65 @@ public class PlayerActionManager { } public void tick(float dt, World world, Server server) { + updated = false; // Reset the updated flag. This will be set to true if the player was updated in this tick. long now = System.currentTimeMillis(); + if (player.getInventory().getSelectedIndex() != lastInputState.selectedInventoryIndex()) { + player.getInventory().setSelectedIndex(lastInputState.selectedInventoryIndex()); + // Tell the client that their inventory slot has been updated properly. + server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new InventorySelectedStackMessage(player.getInventory().getSelectedIndex())); + updated = true; // Tell everyone else that this player's selected item has changed. + } + + if (player.getInventory().getSelectedItemStack().getType().equals(ItemTypes.BLOCK)) { + tickBlockAction(now, server, world); + } + + tickMovement(dt, world, server.getConfig().physics); + } + + private void tickBlockAction(long now, Server server, World world) { + BlockItemStack stack = (BlockItemStack) player.getInventory().getSelectedItemStack(); // Check for breaking blocks. - if (lastInputState.hitting() && now - lastBlockRemovedAt > server.getConfig().actions.blockRemoveCooldown * 1000) { + if ( + lastInputState.hitting() && + stack.getAmount() < stack.getType().getMaxAmount() && + now - lastBlockRemovedAt > server.getConfig().actions.blockRemoveCooldown * 1000 + ) { Vector3f eyePos = new Vector3f(player.getPosition()); eyePos.y += getEyeHeight(); var hit = world.getLookingAtPos(eyePos, player.getViewVector(), 10); if (hit != null) { world.setBlockAt(hit.pos().x, hit.pos().y, hit.pos().z, (byte) 0); lastBlockRemovedAt = now; + stack.incrementAmount(); + server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory())); server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(hit.pos(), world)); } } // Check for placing blocks. - if (lastInputState.interacting() && now - lastBlockPlacedAt > server.getConfig().actions.blockPlaceCooldown * 1000) { + if ( + lastInputState.interacting() && + stack.getAmount() > 0 && + now - lastBlockPlacedAt > server.getConfig().actions.blockPlaceCooldown * 1000 + ) { Vector3f eyePos = new Vector3f(player.getPosition()); eyePos.y += getEyeHeight(); var hit = world.getLookingAtPos(eyePos, player.getViewVector(), 10); if (hit != null) { Vector3i placePos = new Vector3i(hit.pos()); placePos.add(hit.norm()); - world.setBlockAt(placePos.x, placePos.y, placePos.z, (byte) 1); - lastBlockPlacedAt = now; - server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(placePos, world)); + if (!isSpaceOccupied(placePos)) { // Ensure that we can't place blocks in space we're occupying. + world.setBlockAt(placePos.x, placePos.y, placePos.z, stack.getSelectedValue()); + lastBlockPlacedAt = now; + stack.decrementAmount(); + server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory())); + server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(placePos, world)); + } } } - tickMovement(dt, world, server.getConfig().physics); } private void tickMovement(float dt, World world, ServerConfig.PhysicsConfig config) { - updated = false; // Reset the updated flag. This will be set to true if the player was updated in this tick. var velocity = player.getVelocity(); var position = player.getPosition(); boolean grounded = isGrounded(world); @@ -178,15 +219,30 @@ public class PlayerActionManager { return points; } + private boolean isSpaceOccupied(Vector3i pos) { + var playerPos = player.getPosition(); + float playerBodyMinZ = playerPos.z - RADIUS; + float playerBodyMaxZ = playerPos.z + RADIUS; + float playerBodyMinX = playerPos.x - RADIUS; + float playerBodyMaxX = playerPos.x + RADIUS; + float playerBodyMinY = playerPos.y; + float playerBodyMaxY = playerPos.y + getCurrentHeight(); + + // Compute the bounds of all blocks the player is intersecting with. + int minX = (int) Math.floor(playerBodyMinX); + int minZ = (int) Math.floor(playerBodyMinZ); + int minY = (int) Math.floor(playerBodyMinY); + int maxX = (int) Math.floor(playerBodyMaxX); + int maxZ = (int) Math.floor(playerBodyMaxZ); + int maxY = (int) Math.floor(playerBodyMaxY); + + return pos.x >= minX && pos.x <= maxX && pos.y >= minY && pos.y <= maxY && pos.z >= minZ && pos.z <= maxZ; + } + private void checkBlockCollisions(Vector3f movement, World world) { var position = player.getPosition(); var velocity = player.getVelocity(); final Vector3f nextTickPosition = new Vector3f(position).add(movement); -// System.out.printf("Pos:\t\t%.3f, %.3f, %.3f%nmov:\t\t%.3f, %.3f, %.3f%nNexttick:\t%.3f, %.3f, %.3f%n", -// position.x, position.y, position.z, -// movement.x, movement.y, movement.z, -// nextTickPosition.x, nextTickPosition.y, nextTickPosition.z -// ); float height = getCurrentHeight(); float delta = 0.00001f; final Vector3f stepSize = new Vector3f(movement).normalize(1.0f); @@ -205,7 +261,6 @@ public class PlayerActionManager { // Check if we collide with anything at this new position. - float playerBodyPrevMinZ = lastPos.z - RADIUS; float playerBodyPrevMaxZ = lastPos.z + RADIUS; float playerBodyPrevMinX = lastPos.x - RADIUS;