From 2452cf194b65bfd9dd53169e2205dfc262c78b93 Mon Sep 17 00:00:00 2001 From: austin Date: Wed, 30 Apr 2025 02:28:55 -0400 Subject: [PATCH] item stacking --- assets/Data/entity/items/fabs/floors.json | 1 + docs/src/progress/renderertodo.md | 1 + .../state/equip/ServerToolbarState.java | 10 ++ .../state/inventory/ClientInventoryState.java | 6 + .../state/inventory/InventoryUtils.java | 117 ++++++++++++++---- .../state/inventory/ServerInventoryState.java | 1 + .../entity/state/item/ServerChargeState.java | 28 ++++- .../entity/types/item/ItemUtils.java | 24 ++-- .../client/protocol/InventoryProtocol.java | 12 +- .../parser/net/message/InventoryMessage.java | 60 +++++++++ .../parser/net/message/NetworkMessage.java | 5 + .../net/parser/net/message/TypeBytes.java | 2 + .../server/protocol/InventoryProtocol.java | 1 + .../net/server/protocol/TerrainProtocol.java | 2 + src/net/inventory.json | 12 ++ 15 files changed, 245 insertions(+), 37 deletions(-) diff --git a/assets/Data/entity/items/fabs/floors.json b/assets/Data/entity/items/fabs/floors.json index 7147b09d..dc57f383 100644 --- a/assets/Data/entity/items/fabs/floors.json +++ b/assets/Data/entity/items/fabs/floors.json @@ -10,6 +10,7 @@ "fabPath" : "Data/fab/wood_refined_floor.block" }, "clientSideSecondary" : "PLACE_FAB", + "maxStack" : 100, "itemAudio": { "uiGrabAudio" : "Audio/ui/items/specific/Pick Up Wood A.wav", "uiReleaseAudio" : "Audio/ui/items/specific/Drop Wood A.wav" diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index 932c992b..99e44dc6 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1605,6 +1605,7 @@ Cleaning up dead code Fab items Items keep charge state UI renders charge state +Item stacking diff --git a/src/main/java/electrosphere/entity/state/equip/ServerToolbarState.java b/src/main/java/electrosphere/entity/state/equip/ServerToolbarState.java index cae7d391..989714f4 100644 --- a/src/main/java/electrosphere/entity/state/equip/ServerToolbarState.java +++ b/src/main/java/electrosphere/entity/state/equip/ServerToolbarState.java @@ -315,6 +315,16 @@ public class ServerToolbarState implements BehaviorTree { return realWorldItem; } + + /** + * Gets the in-inventory item + * @return The item entity if it exists, null otherwise + */ + public Entity getInInventoryItem(){ + RelationalInventoryState toolbarInventory = InventoryUtils.getToolbarInventory(parent); + return toolbarInventory.getItemSlot(selectedSlot + ""); + } + /** * Attempts to change the selection to a new value * @param value The value diff --git a/src/main/java/electrosphere/entity/state/inventory/ClientInventoryState.java b/src/main/java/electrosphere/entity/state/inventory/ClientInventoryState.java index 67c9e155..b26b5ea1 100644 --- a/src/main/java/electrosphere/entity/state/inventory/ClientInventoryState.java +++ b/src/main/java/electrosphere/entity/state/inventory/ClientInventoryState.java @@ -11,6 +11,7 @@ import electrosphere.entity.Entity; import electrosphere.entity.btree.BehaviorTree; import electrosphere.entity.state.equip.ClientEquipState; import electrosphere.entity.state.equip.ClientToolbarState; +import electrosphere.entity.state.item.ClientChargeState; import electrosphere.game.data.creature.type.equip.EquipPoint; import electrosphere.net.parser.net.message.InventoryMessage; import electrosphere.net.server.protocol.InventoryProtocol; @@ -186,6 +187,11 @@ public class ClientInventoryState implements BehaviorTree { // throw new UnsupportedOperationException("TODO: in world item is null"); } } break; + case SERVERUPDATEITEMCHARGES: { + Entity clientInventoryItem = Globals.clientSceneWrapper.getEntityFromServerId(message.getentityId()); + ClientChargeState clientChargeState = ClientChargeState.getClientChargeState(clientInventoryItem); + clientChargeState.setCharges(message.getcharges()); + } break; case CLIENTREQUESTCRAFT: case CLIENTUPDATETOOLBAR: case CLIENTREQUESTADDNATURAL: diff --git a/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java b/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java index f77795e1..368ebc97 100644 --- a/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java +++ b/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java @@ -14,6 +14,7 @@ import electrosphere.entity.state.equip.ClientEquipState; import electrosphere.entity.state.equip.ServerEquipState; import electrosphere.entity.state.equip.ServerToolbarState; import electrosphere.entity.state.gravity.GravityUtils; +import electrosphere.entity.state.item.ServerChargeState; import electrosphere.entity.types.creature.CreatureUtils; import electrosphere.entity.types.item.ItemUtils; import electrosphere.game.data.item.Item; @@ -173,43 +174,111 @@ public class InventoryUtils { if(item == null){ throw new Error("Null item provided! " + item); } - boolean creatureIsCreature = CreatureUtils.isCreature(creature); - boolean itemIsItem = ItemUtils.isItem(item); - boolean hasInventory = hasNaturalInventory(creature); - //check if the item is already in an inventory - boolean itemIsInInventory = ItemUtils.itemIsInInventory(item); - if(creatureIsCreature && itemIsItem && hasInventory && !itemIsInInventory){ - //get inventory - //for the moment we're just gonna get natural inventory - //later we'll need to search through all creature inventories to find the item - UnrelationalInventoryState inventory = getNaturalInventory(creature); - //destroy in-world entity and create in-inventory item - //we're doing this so that we're not constantly sending networking messages for invisible entities attached to the player - Entity inventoryItem = ItemUtils.serverRecreateContainerItem(item, creature); - //store item in inventory - inventory.addItem(inventoryItem); - //set item containing parent - ItemUtils.setContainingParent(inventoryItem, creature); + if(!CreatureUtils.isCreature(creature)){ + throw new Error("Creature is not a creature!"); + } + if(!ItemUtils.isItem(item)){ + throw new Error("Item is not an item!"); + } + if(!InventoryUtils.hasNaturalInventory(creature)){ + throw new Error("Creature does not have a natural inventory"); + } + if(ItemUtils.itemIsInInventory(item)){ + throw new Error("Item is already in an inventory!"); + } + Item itemData = Globals.gameConfigCurrent.getItemMap().getItem(item); + //get inventory + //for the moment we're just gonna get natural inventory + //later we'll need to search through all creature inventories to find the item + UnrelationalInventoryState inventory = InventoryUtils.getNaturalInventory(creature); + + //check if it should be added to an existing stack + Entity foundExisting = null; + if(itemData.getMaxStack() != null){ + RelationalInventoryState toolbarInventory = InventoryUtils.getToolbarInventory(creature); + if(toolbarInventory != null){ + for(Entity toolbarItem : toolbarInventory.getItems()){ + if(toolbarItem == null){ + continue; + } + Item toolbarData = Globals.gameConfigCurrent.getItemMap().getItem(toolbarItem); + if(!toolbarData.getId().equals(itemData.getId())){ + continue; + } + ServerChargeState serverChargeState = ServerChargeState.getServerChargeState(toolbarItem); + if(serverChargeState.getCharges() >= itemData.getMaxStack()){ + continue; + } + foundExisting = toolbarItem; + break; + } + } + if(foundExisting == null){ + for(Entity naturalItem : inventory.getItems()){ + if(naturalItem == null){ + continue; + } + Item toolbarData = Globals.gameConfigCurrent.getItemMap().getItem(naturalItem); + if(!toolbarData.getId().equals(itemData.getId())){ + continue; + } + ServerChargeState serverChargeState = ServerChargeState.getServerChargeState(naturalItem); + if(serverChargeState.getCharges() >= itemData.getMaxStack()){ + continue; + } + foundExisting = naturalItem; + break; + } + } + } + + //increase charges + if(foundExisting != null){ + ServerChargeState serverChargeState = ServerChargeState.getServerChargeState(foundExisting); + serverChargeState.setCharges(serverChargeState.getCharges() + 1); //if we are the server, immediately send required packets ServerDataCell dataCell = DataCellSearchUtils.getEntityDataCell(item); - // ServerDataCell dataCell = Globals.dataCellLocationResolver.getDataCellAtPoint(EntityUtils.getPosition(item),item); - //broadcast destroy entity dataCell.broadcastNetworkMessage(EntityMessage.constructDestroyMessage(item.getId())); - //tell controlling player that they have an item in their inventory + //tell player that their item has another charge if(CreatureUtils.hasControllerPlayerId(creature)){ //get the player int controllerPlayerID = CreatureUtils.getControllerPlayerId(creature); Player controllerPlayer = Globals.playerManager.getPlayerFromId(controllerPlayerID); //send message - controllerPlayer.addMessage(InventoryMessage.constructaddItemToInventoryMessage(inventoryItem.getId(), ItemUtils.getType(inventoryItem))); + controllerPlayer.addMessage(InventoryMessage.constructserverUpdateItemChargesMessage(foundExisting.getId(), serverChargeState.getCharges())); } //alert script engine - ServerScriptUtils.fireSignalOnEntity(creature, "itemPickup", item.getId(), inventoryItem.getId()); + ServerScriptUtils.fireSignalOnEntity(creature, "itemPickup", item.getId(), foundExisting.getId()); //destroy the item that was left over ServerEntityUtils.destroyEntity(item); - return inventoryItem; + return foundExisting; } - return null; + + //destroy in-world entity and create in-inventory item + //we're doing this so that we're not constantly sending networking messages for invisible entities attached to the player + Entity inventoryItem = ItemUtils.serverRecreateContainerItem(item, creature); + //store item in inventory + inventory.addItem(inventoryItem); + //set item containing parent + ItemUtils.setContainingParent(inventoryItem, creature); + //if we are the server, immediately send required packets + ServerDataCell dataCell = DataCellSearchUtils.getEntityDataCell(item); + // ServerDataCell dataCell = Globals.dataCellLocationResolver.getDataCellAtPoint(EntityUtils.getPosition(item),item); + //broadcast destroy entityq + dataCell.broadcastNetworkMessage(EntityMessage.constructDestroyMessage(item.getId())); + //tell controlling player that they have an item in their inventory + if(CreatureUtils.hasControllerPlayerId(creature)){ + //get the player + int controllerPlayerID = CreatureUtils.getControllerPlayerId(creature); + Player controllerPlayer = Globals.playerManager.getPlayerFromId(controllerPlayerID); + //send message + controllerPlayer.addMessage(InventoryMessage.constructaddItemToInventoryMessage(inventoryItem.getId(), ItemUtils.getType(inventoryItem))); + } + //alert script engine + ServerScriptUtils.fireSignalOnEntity(creature, "itemPickup", item.getId(), inventoryItem.getId()); + //destroy the item that was left over + ServerEntityUtils.destroyEntity(item); + return inventoryItem; } /** diff --git a/src/main/java/electrosphere/entity/state/inventory/ServerInventoryState.java b/src/main/java/electrosphere/entity/state/inventory/ServerInventoryState.java index 9495a4a4..b21d81a9 100644 --- a/src/main/java/electrosphere/entity/state/inventory/ServerInventoryState.java +++ b/src/main/java/electrosphere/entity/state/inventory/ServerInventoryState.java @@ -94,6 +94,7 @@ public class ServerInventoryState implements BehaviorTree { case SERVERCOMMANDUNEQUIPITEM: case SERVERCOMMANDEQUIPITEM: case SERVERCOMMANDMOVEITEMCONTAINER: + case SERVERUPDATEITEMCHARGES: break; } } diff --git a/src/main/java/electrosphere/entity/state/item/ServerChargeState.java b/src/main/java/electrosphere/entity/state/item/ServerChargeState.java index 8c29b1a3..f877000a 100644 --- a/src/main/java/electrosphere/entity/state/item/ServerChargeState.java +++ b/src/main/java/electrosphere/entity/state/item/ServerChargeState.java @@ -2,6 +2,7 @@ package electrosphere.entity.state.item; import electrosphere.entity.btree.BehaviorTree; +import electrosphere.entity.state.equip.ServerToolbarState; import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.EntityDataStrings; @@ -46,6 +47,29 @@ public class ServerChargeState implements BehaviorTree { this.maxCharges = (Integer)params[0]; this.charges = 1; } + + @Override + public void simulate(float deltaTime) { + } + + /** + * Attempts to remove a charge from whatever item the parent entity currently has equipped + * @param parent The parent + */ + public static void attemptRemoveCharges(Entity parent, int charges){ + if(ServerToolbarState.hasServerToolbarState(parent)){ + ServerToolbarState serverToolbarState = ServerToolbarState.getServerToolbarState(parent); + Entity inventoryItem = serverToolbarState.getInInventoryItem(); + if(inventoryItem != null){ + ServerChargeState serverChargeState = ServerChargeState.getServerChargeState(inventoryItem); + if(serverChargeState.getCharges() - charges > 0){ + serverChargeState.setCharges(serverChargeState.getCharges() - charges); + } else { + throw new Error("Undefined! " + charges + serverChargeState.getCharges()); + } + } + } + } /** *

(initially) Automatically generated

@@ -124,8 +148,4 @@ public class ServerChargeState implements BehaviorTree { return charges; } - @Override - public void simulate(float deltaTime) { - } - } diff --git a/src/main/java/electrosphere/entity/types/item/ItemUtils.java b/src/main/java/electrosphere/entity/types/item/ItemUtils.java index e5f893a6..ccfc16a0 100644 --- a/src/main/java/electrosphere/entity/types/item/ItemUtils.java +++ b/src/main/java/electrosphere/entity/types/item/ItemUtils.java @@ -41,6 +41,8 @@ import electrosphere.net.parser.net.message.NetworkMessage; import electrosphere.net.server.player.Player; import electrosphere.renderer.actor.Actor; import electrosphere.server.datacell.Realm; +import electrosphere.server.datacell.utils.EntityLookupUtils; +import electrosphere.server.datacell.utils.ServerBehaviorTreeUtils; import electrosphere.server.datacell.utils.ServerEntityTagUtils; import electrosphere.server.entity.poseactor.PoseActor; @@ -503,19 +505,27 @@ public class ItemUtils { rVal.putData(EntityDataStrings.ITEM_EQUIP_WHITELIST, getEquipWhitelist(item)); } - // - //stacking behavior - // - if(itemData.getMaxStack() != null){ - ClientChargeState.attachTree(rVal, itemData.getMaxStack()); - } - rVal.putData(EntityDataStrings.ITEM_ICON,ItemUtils.getItemIcon(item)); rVal.putData(EntityDataStrings.ITEM_EQUIP_CLASS, item.getData(EntityDataStrings.ITEM_EQUIP_CLASS)); CommonEntityUtils.setEntityType(rVal, EntityType.ITEM); rVal.putData(EntityDataStrings.ITEM_IS_IN_INVENTORY, true); ItemUtils.setContainingParent(rVal, containingParent); CommonEntityUtils.setEntitySubtype(rVal, CommonEntityUtils.getEntitySubtype(item)); + + //attach to server tracking + Realm realm = Globals.realmManager.getEntityRealm(item); + EntityLookupUtils.registerServerEntity(rVal); + ServerBehaviorTreeUtils.registerEntity(rVal); + Globals.realmManager.mapEntityToRealm(rVal, realm); + Globals.entityDataCellMapper.registerEntity(rVal, realm.getInventoryCell()); + + // + //stacking behavior + // + if(itemData.getMaxStack() != null){ + ServerChargeState.attachTree(rVal, itemData.getMaxStack()); + } + return rVal; } else { return null; diff --git a/src/main/java/electrosphere/net/client/protocol/InventoryProtocol.java b/src/main/java/electrosphere/net/client/protocol/InventoryProtocol.java index 33c5a338..274d43cf 100644 --- a/src/main/java/electrosphere/net/client/protocol/InventoryProtocol.java +++ b/src/main/java/electrosphere/net/client/protocol/InventoryProtocol.java @@ -64,8 +64,16 @@ public class InventoryProtocol implements ClientProtocolTemplate= TypeBytes.INVENTORY_MESSAGE_TYPE_SERVERUPDATEITEMCHARGES_SIZE){ + return true; + } else { + return false; + } } return false; } @@ -806,6 +828,29 @@ public class InventoryMessage extends NetworkMessage { return rVal; } + /** + * Parses a message of type serverUpdateItemCharges + */ + public static InventoryMessage parseserverUpdateItemChargesMessage(CircularByteBuffer byteBuffer, MessagePool pool){ + InventoryMessage rVal = (InventoryMessage)pool.get(MessageType.INVENTORY_MESSAGE); + rVal.messageType = InventoryMessageType.SERVERUPDATEITEMCHARGES; + InventoryMessage.stripPacketHeader(byteBuffer); + rVal.setentityId(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + rVal.setcharges(ByteStreamUtils.popIntFromByteQueue(byteBuffer)); + return rVal; + } + + /** + * Constructs a message of type serverUpdateItemCharges + */ + public static InventoryMessage constructserverUpdateItemChargesMessage(int entityId,int charges){ + InventoryMessage rVal = new InventoryMessage(InventoryMessageType.SERVERUPDATEITEMCHARGES); + rVal.setentityId(entityId); + rVal.setcharges(charges); + rVal.serialize(); + return rVal; + } + @Override void serialize(){ byte[] intValues = new byte[8]; @@ -1047,6 +1092,21 @@ public class InventoryMessage extends NetworkMessage { rawBytes[10+i] = intValues[i]; } break; + case SERVERUPDATEITEMCHARGES: + rawBytes = new byte[2+4+4]; + //message header + rawBytes[0] = TypeBytes.MESSAGE_TYPE_INVENTORY; + //entity messaage header + rawBytes[1] = TypeBytes.INVENTORY_MESSAGE_TYPE_SERVERUPDATEITEMCHARGES; + intValues = ByteStreamUtils.serializeIntToBytes(entityId); + for(int i = 0; i < 4; i++){ + rawBytes[2+i] = intValues[i]; + } + intValues = ByteStreamUtils.serializeIntToBytes(charges); + for(int i = 0; i < 4; i++){ + rawBytes[6+i] = intValues[i]; + } + break; } serialized = true; } diff --git a/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java b/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java index ab674768..d12fc8d0 100644 --- a/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java +++ b/src/main/java/electrosphere/net/parser/net/message/NetworkMessage.java @@ -410,6 +410,11 @@ public abstract class NetworkMessage { rVal = InventoryMessage.parseclientRequestCraftMessage(byteBuffer,pool); } break; + case TypeBytes.INVENTORY_MESSAGE_TYPE_SERVERUPDATEITEMCHARGES: + if(InventoryMessage.canParseMessage(byteBuffer,secondByte)){ + rVal = InventoryMessage.parseserverUpdateItemChargesMessage(byteBuffer,pool); + } + break; } break; case TypeBytes.MESSAGE_TYPE_SYNCHRONIZATION: diff --git a/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java b/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java index 9538d247..8d488104 100644 --- a/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java +++ b/src/main/java/electrosphere/net/parser/net/message/TypeBytes.java @@ -164,6 +164,7 @@ public class TypeBytes { public static final byte INVENTORY_MESSAGE_TYPE_CLIENTUPDATETOOLBAR = 9; public static final byte INVENTORY_MESSAGE_TYPE_CLIENTREQUESTPERFORMITEMACTION = 10; public static final byte INVENTORY_MESSAGE_TYPE_CLIENTREQUESTCRAFT = 11; + public static final byte INVENTORY_MESSAGE_TYPE_SERVERUPDATEITEMCHARGES = 12; /* Inventory packet sizes */ @@ -172,6 +173,7 @@ public class TypeBytes { public static final byte INVENTORY_MESSAGE_TYPE_CLIENTREQUESTADDNATURAL_SIZE = 6; public static final byte INVENTORY_MESSAGE_TYPE_CLIENTUPDATETOOLBAR_SIZE = 6; public static final byte INVENTORY_MESSAGE_TYPE_CLIENTREQUESTCRAFT_SIZE = 14; + public static final byte INVENTORY_MESSAGE_TYPE_SERVERUPDATEITEMCHARGES_SIZE = 10; /* Synchronization subcategories diff --git a/src/main/java/electrosphere/net/server/protocol/InventoryProtocol.java b/src/main/java/electrosphere/net/server/protocol/InventoryProtocol.java index 471d6472..d955c4db 100644 --- a/src/main/java/electrosphere/net/server/protocol/InventoryProtocol.java +++ b/src/main/java/electrosphere/net/server/protocol/InventoryProtocol.java @@ -106,6 +106,7 @@ public class InventoryProtocol implements ServerProtocolTemplate { Entity targetEntity = EntityLookupUtils.getEntityById(connectionHandler.getPlayerEntityId()); Realm playerRealm = Globals.realmManager.getEntityRealm(targetEntity); ServerBlockEditing.placeBlockFab(playerRealm, worldPos, blockPos, message.getblockRotation(), message.getfabPath()); + ServerChargeState.attemptRemoveCharges(targetEntity, 1); } break; //all ignored message types case UPDATEFLUIDDATA: diff --git a/src/net/inventory.json b/src/net/inventory.json index eaf537ac..60e552cf 100644 --- a/src/net/inventory.json +++ b/src/net/inventory.json @@ -56,6 +56,10 @@ { "name" : "recipeId", "type" : "FIXED_INT" + }, + { + "name" : "charges", + "type" : "FIXED_INT" } ], "messageTypes" : [ @@ -159,6 +163,14 @@ "stationId", "recipeId" ] + }, + { + "messageName" : "serverUpdateItemCharges", + "description" : "Server tells client that its item has a set number of charges", + "data" : [ + "entityId", + "charges" + ] } ] }