From e207269489dbc45c2a1b6921095417bb2aec9da0 Mon Sep 17 00:00:00 2001 From: austin Date: Wed, 30 Apr 2025 15:28:40 -0400 Subject: [PATCH] crafting improvements --- docs/src/progress/renderertodo.md | 4 + .../state/equip/ServerToolbarState.java | 20 +- .../state/inventory/ClientInventoryState.java | 2 +- .../state/inventory/InventoryUtils.java | 203 +++++++++++++++--- .../inventory/UnrelationalInventoryState.java | 3 + .../entity/state/item/ServerChargeState.java | 21 ++ .../entity/types/item/ItemUtils.java | 14 +- .../server/protocol/InventoryProtocol.java | 85 ++++++-- 8 files changed, 297 insertions(+), 55 deletions(-) diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index edf02098..09c5806b 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -1617,6 +1617,8 @@ Undo state transition "fix" that broke harvest interaction Undo voxel "fix" that messed up terrain smooth transitions Craftable wooden floor fab Using up charges destroys the item (including toolbar instances) +Crafting can consume charges +Products from crafting can add charges to existing items @@ -1628,6 +1630,8 @@ Using up charges destroys the item (including toolbar instances) +High level netcode gen needs to send packets to containing player of inventory items + Implement gadgets - Chemistry System diff --git a/src/main/java/electrosphere/entity/state/equip/ServerToolbarState.java b/src/main/java/electrosphere/entity/state/equip/ServerToolbarState.java index 1f0cbaf4..cb6b338e 100644 --- a/src/main/java/electrosphere/entity/state/equip/ServerToolbarState.java +++ b/src/main/java/electrosphere/entity/state/equip/ServerToolbarState.java @@ -76,18 +76,28 @@ public class ServerToolbarState implements BehaviorTree { * @param point The point to equip to */ public void attemptEquip(Entity inInventoryEntity, int slotId){ + if(inInventoryEntity == null){ + throw new Error("Entity is null!"); + } + if(!ItemUtils.isItem(inInventoryEntity)){ + throw new Error("Entity is not an item!"); + } RelationalInventoryState toolbarInventory = InventoryUtils.getToolbarInventory(parent); - RelationalInventoryState equipInventory = InventoryUtils.getEquipInventory(parent); ServerEquipState serverEquipState = ServerEquipState.getEquipState(parent); - boolean hasEquipped = toolbarInventory.hasItemInSlot(slotId + ""); - boolean targetIsItem = ItemUtils.isItem(inInventoryEntity); - if(!hasEquipped && targetIsItem){ + if(!toolbarInventory.hasItemInSlot(slotId + "")){ //remove from equip state - if(equipInventory != null && equipInventory.containsItem(inInventoryEntity)){ + if(InventoryUtils.hasEquipInventory(parent)){ + RelationalInventoryState equipInventory = InventoryUtils.getEquipInventory(parent); serverEquipState.serverAttemptUnequip(equipInventory.getItemSlot(inInventoryEntity)); equipInventory.tryRemoveItem(inInventoryEntity); } + //remove from natural inventory + if(InventoryUtils.hasNaturalInventory(parent)){ + UnrelationalInventoryState naturalInventory = InventoryUtils.getNaturalInventory(parent); + naturalInventory.removeItem(inInventoryEntity); + } + //add to toolbar toolbarInventory.tryRemoveItem(inInventoryEntity); this.unequip(inInventoryEntity); diff --git a/src/main/java/electrosphere/entity/state/inventory/ClientInventoryState.java b/src/main/java/electrosphere/entity/state/inventory/ClientInventoryState.java index ca439ed2..a56c8a5f 100644 --- a/src/main/java/electrosphere/entity/state/inventory/ClientInventoryState.java +++ b/src/main/java/electrosphere/entity/state/inventory/ClientInventoryState.java @@ -55,7 +55,7 @@ public class ClientInventoryState implements BehaviorTree { case REMOVEITEMFROMINVENTORY: { Entity item = Globals.clientSceneWrapper.getEntityFromServerId(message.getentityId()); if(item != null){ - InventoryUtils.removeItemFromInventories(parent, item); + InventoryUtils.clientRemoveItemFromInventories(parent, item); //attempt re-render ui WindowUtils.attemptRedrawInventoryWindows(); //destroy the item diff --git a/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java b/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java index bb245c64..69331954 100644 --- a/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java +++ b/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java @@ -1,5 +1,9 @@ package electrosphere.entity.state.inventory; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Collectors; + import org.joml.Vector3d; import electrosphere.audio.VirtualAudioSourceManager.VirtualAudioSourceType; @@ -515,7 +519,7 @@ public class InventoryUtils { * @param creature The creature to remove the item from (likely to be player entity) * @param item The item to remove */ - public static void removeItemFromInventories(Entity creature, Entity item){ + public static void clientRemoveItemFromInventories(Entity creature, Entity item){ if(creature == null){ throw new Error("Creature is null!"); } @@ -559,37 +563,172 @@ public class InventoryUtils { } } + /** + * [SERVER ONLY] Called when the server says to remove an item from all inventories + * Only does the remove, doesn't create the in-world item + * @param creature The creature to remove the item from (likely to be player entity) + * @param item The item to remove + */ + public static void serverRemoveItemFromInventories(Entity creature, Entity item){ + if(creature == null){ + throw new Error("Creature is null!"); + } + if(item == null){ + throw new Error("Item is null!"); + } + 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(!ItemUtils.itemIsInInventory(item)){ + throw new Error("Item is not in an inventory!"); + } + //check if the item is in an inventory + if(InventoryUtils.hasNaturalInventory(creature)){ + //get inventory + UnrelationalInventoryState inventory = InventoryUtils.getNaturalInventory(creature); + //remove item from inventory + inventory.removeItem(item); + } + if(InventoryUtils.hasEquipInventory(creature)){ + //get inventory + RelationalInventoryState inventory = InventoryUtils.getEquipInventory(creature); + //get real world item + Entity realWorldItem = ItemUtils.getRealWorldEntity(item); + if(realWorldItem != null){ + //drop item + ServerEquipState equipState = ServerEquipState.getEquipState(creature); + equipState.serverTransformUnequipPoint(inventory.getItemSlot(item)); + } + //remove item from inventory + inventory.tryRemoveItem(item); + } + if(InventoryUtils.hasToolbarInventory(creature)){ + RelationalInventoryState toolbarInventory = InventoryUtils.getToolbarInventory(creature); + toolbarInventory.tryRemoveItem(item); + Globals.cursorState.hintClearBlockCursor(); + ServerToolbarState.getServerToolbarState(creature).update(); + } + } - //need creature so we can figure out where to drop the item - // public static void attemptDestroyItem(Entity creature, Entity item){ - // //verify creature is creature, item is item, inventory exists, and item is in inventory - // boolean creatureIsCreature = CreatureUtils.isCreature(creature); - // boolean itemIsItem = ItemUtils.isItem(item); - // boolean hasNaturalInventory = hasNaturalInventory(creature); - // boolean hasEquipInventory = hasEquipInventory(creature); - // if(creatureIsCreature && itemIsItem){ - // if(hasNaturalInventory){ - // //get inventory - // UnrelationalInventoryState inventory = getNaturalInventory(creature); - // //remove item from inventory - // inventory.removeItem(item); - // } - // if(hasEquipInventory){ - // //get inventory - // RelationalInventoryState inventory = getEquipInventory(creature); - // //get real world item - // Entity realWorldItem = ItemUtils.getRealWorldEntity(item); - // if(realWorldItem != null){ - // //drop item - // EquipState equipState = EquipState.getEquipState(creature); - // equipState.transformUnequipPoint(inventory.getItemSlot(item)); - // } - // //remove item from inventory - // inventory.tryRemoveItem(item); - // } - // //delete in container item - // ItemUtils.destroyInInventoryItem(item); - // } - // } + /** + * Destroys an item that is in an inventory + * @param item The item + */ + public static void serverDestroyInventoryItem(Entity item){ + Entity creature = ItemUtils.getContainingParent(item); + if(creature == null){ + throw new Error("Creature is null!"); + } + if(item == null){ + throw new Error("Item is null!"); + } + 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(!ItemUtils.itemIsInInventory(item)){ + throw new Error("Item is not in an inventory!"); + } + //check if the item is in an inventory + if(InventoryUtils.hasNaturalInventory(creature)){ + //get inventory + UnrelationalInventoryState inventory = InventoryUtils.getNaturalInventory(creature); + //remove item from inventory + inventory.removeItem(item); + } + if(InventoryUtils.hasEquipInventory(creature)){ + //get inventory + RelationalInventoryState inventory = InventoryUtils.getEquipInventory(creature); + //get real world item + Entity realWorldItem = ItemUtils.getRealWorldEntity(item); + if(realWorldItem != null){ + //drop item + ServerEquipState equipState = ServerEquipState.getEquipState(creature); + equipState.serverTransformUnequipPoint(inventory.getItemSlot(item)); + } + //remove item from inventory + inventory.tryRemoveItem(item); + } + ServerEntityUtils.destroyEntity(item); + } + + /** + * Creates an item in the creature's inventory + * @param creature The creature + * @param itemId The item's ID + */ + public static void serverCreateInventoryItem(Entity creature, String itemId, int count){ + Item itemData = Globals.gameConfigCurrent.getItemMap().getItem(itemId); + if(itemData.getMaxStack() == null){ + for(int i = 0; i < count; i++){ + ItemUtils.serverCreateContainerItem(creature, itemData); + } + } else { + //scan for items to add charges to first + int added = 0; + List existingItems = InventoryUtils.getAllInventoryItems(creature); + for(Entity existingItem : existingItems){ + Item existingData = Globals.gameConfigCurrent.getItemMap().getItem(existingItem); + if(existingData.getId().equals(itemId)){ + ServerChargeState serverChargeState = ServerChargeState.getServerChargeState(existingItem); + if(serverChargeState.getCharges() < existingData.getMaxStack()){ + int available = existingData.getMaxStack() - serverChargeState.getCharges(); + //just need to add charges to this + if(available >= count - added){ + serverChargeState.attemptAddCharges(count - added); + added = count; + break; + } else { + serverChargeState.attemptAddCharges(available); + added = added + available; + } + } + } + } + //need to start creating items to add more charges + Item targetData = Globals.gameConfigCurrent.getItemMap().getItem(itemId); + if(added < count){ + int numFullItemsToAdd = (count - added) / targetData.getMaxStack(); + int remainder = (count - added) % targetData.getMaxStack(); + for(int i = 0; i < numFullItemsToAdd; i++){ + Entity newInventoryItem = ItemUtils.serverCreateContainerItem(creature, itemData); + ServerChargeState.getServerChargeState(newInventoryItem).setCharges(targetData.getMaxStack()); + } + if(remainder > 0){ + Entity newInventoryItem = ItemUtils.serverCreateContainerItem(creature, itemData); + ServerChargeState.getServerChargeState(newInventoryItem).setCharges(remainder); + } + } + } + } + + /** + * Gets the list of all items in all inventories of a creature + * @param creature The creature + * @return The list of all items + */ + public static List getAllInventoryItems(Entity creature){ + List rVal = new LinkedList(); + if(InventoryUtils.hasEquipInventory(creature)){ + RelationalInventoryState equipInventory = InventoryUtils.getEquipInventory(creature); + rVal.addAll(equipInventory.getItems()); + } + if(InventoryUtils.hasNaturalInventory(creature)){ + UnrelationalInventoryState equipInventory = InventoryUtils.getNaturalInventory(creature); + rVal.addAll(equipInventory.getItems()); + } + if(InventoryUtils.hasToolbarInventory(creature)){ + RelationalInventoryState equipInventory = InventoryUtils.getToolbarInventory(creature); + rVal.addAll(equipInventory.getItems()); + } + //filter null items + rVal = rVal.stream().filter((Entity el) -> {return el != null;}).collect(Collectors.toList()); + return rVal; + } } diff --git a/src/main/java/electrosphere/entity/state/inventory/UnrelationalInventoryState.java b/src/main/java/electrosphere/entity/state/inventory/UnrelationalInventoryState.java index 500f4197..7eb05fe9 100644 --- a/src/main/java/electrosphere/entity/state/inventory/UnrelationalInventoryState.java +++ b/src/main/java/electrosphere/entity/state/inventory/UnrelationalInventoryState.java @@ -28,6 +28,9 @@ public class UnrelationalInventoryState { // } public void addItem(Entity item){ + if(items.size() == capacity){ + throw new Error("Trying to add more items than inventory can hold!"); + } items.add(item); } diff --git a/src/main/java/electrosphere/entity/state/item/ServerChargeState.java b/src/main/java/electrosphere/entity/state/item/ServerChargeState.java index 855f5516..2e38cba2 100644 --- a/src/main/java/electrosphere/entity/state/item/ServerChargeState.java +++ b/src/main/java/electrosphere/entity/state/item/ServerChargeState.java @@ -6,6 +6,7 @@ import electrosphere.entity.state.equip.ServerToolbarState; import electrosphere.entity.state.inventory.InventoryUtils; import electrosphere.entity.state.inventory.RelationalInventoryState; import electrosphere.entity.types.creature.CreatureUtils; +import electrosphere.entity.types.item.ItemUtils; import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.EntityDataStrings; @@ -88,6 +89,26 @@ public class ServerChargeState implements BehaviorTree { } } } + + /** + * Attempts to remove a charge from whatever item the parent entity currently has equipped + * @param parent The parent + */ + public void attemptAddCharges(int charges){ + this.setCharges(this.getCharges() + charges); + Entity containingParent = ItemUtils.getContainingParent(this.parent); + if(this.charges <= 0){ + InventoryUtils.serverDestroyInventoryItem(this.parent); + } else if(containingParent != null) { + if(CreatureUtils.hasControllerPlayerId(containingParent)){ + //get the player + int controllerPlayerID = CreatureUtils.getControllerPlayerId(containingParent); + Player controllerPlayer = Globals.playerManager.getPlayerFromId(controllerPlayerID); + //send message + controllerPlayer.addMessage(SynchronizationMessage.constructUpdateClientIntStateMessage(parent.getId(), BehaviorTreeIdEnums.BTREE_SERVERCHARGESTATE_ID, FieldIdEnums.TREE_SERVERCHARGESTATE_SYNCEDFIELD_CHARGES_ID, this.getCharges())); + } + } + } /** *

(initially) Automatically generated

diff --git a/src/main/java/electrosphere/entity/types/item/ItemUtils.java b/src/main/java/electrosphere/entity/types/item/ItemUtils.java index ccfc16a0..2d663f1e 100644 --- a/src/main/java/electrosphere/entity/types/item/ItemUtils.java +++ b/src/main/java/electrosphere/entity/types/item/ItemUtils.java @@ -537,15 +537,15 @@ public class ItemUtils { * @param parent The parent that contains the item * @param itemData The item data */ - public static void serverCreateContainerItem(Entity parent, Item itemData){ + public static Entity serverCreateContainerItem(Entity parent, Item itemData){ if(parent == null || itemData == null){ throw new Error("Provided bad data! " + parent + " " + itemData); } - //make sure there's an item to store the item + //make sure there's an inventory to store the item UnrelationalInventoryState naturalInventory = InventoryUtils.getNaturalInventory(parent); RelationalInventoryState toolbarInventory = InventoryUtils.getToolbarInventory(parent); if(naturalInventory == null && toolbarInventory == null){ - return; + throw new Error("Trying to store an item in an entity with no inventories!"); } Realm realm = Globals.realmManager.getEntityRealm(parent); Vector3d parentPos = EntityUtils.getPosition(parent); @@ -558,6 +558,12 @@ public class ItemUtils { //apply item transforms to an entity ItemUtils.serverApplyItemEntityTransforms(realm, parentPos, rVal, itemData); + //store the item in its actual inventory + naturalInventory.addItem(rVal); + + //associate with parent and set other data + ItemUtils.setContainingParent(rVal, parent); + //destroy physics if(realm.getCollisionEngine() != null){ realm.getCollisionEngine().destroyPhysics(rVal); @@ -574,6 +580,8 @@ public class ItemUtils { Player player = Globals.playerManager.getPlayerFromId(playerId); player.addMessage(InventoryMessage.constructaddItemToInventoryMessage(rVal.getId(), itemData.getId())); } + + return rVal; } /** diff --git a/src/main/java/electrosphere/net/server/protocol/InventoryProtocol.java b/src/main/java/electrosphere/net/server/protocol/InventoryProtocol.java index d955c4db..4ce1871a 100644 --- a/src/main/java/electrosphere/net/server/protocol/InventoryProtocol.java +++ b/src/main/java/electrosphere/net/server/protocol/InventoryProtocol.java @@ -1,14 +1,16 @@ package electrosphere.net.server.protocol; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import electrosphere.engine.Globals; import electrosphere.entity.Entity; import electrosphere.entity.state.inventory.InventoryUtils; import electrosphere.entity.state.inventory.RelationalInventoryState; import electrosphere.entity.state.inventory.UnrelationalInventoryState; -import electrosphere.entity.types.item.ItemUtils; +import electrosphere.entity.state.item.ServerChargeState; import electrosphere.game.data.crafting.RecipeData; import electrosphere.game.data.crafting.RecipeIngredientData; import electrosphere.game.data.item.Item; @@ -28,11 +30,19 @@ import electrosphere.server.player.PlayerActions; public class InventoryProtocol implements ServerProtocolTemplate { - //the entity's equip inventory + /** + * the entity's equip inventory + */ public static final int INVENTORY_TYPE_EQUIP = 0; - //the natural inventory of the entity + + /** + * the natural inventory of the entity + */ public static final int INVENTORY_TYPE_NATURAL = 1; - //the toolbar + + /** + * the toolbar + */ public static final int INVENTORY_TYPE_TOOLBAR = 2; @Override @@ -132,13 +142,22 @@ public class InventoryProtocol implements ServerProtocolTemplate reagentList = new LinkedList(); boolean hasReagents = true; + Map ingredientTargetMap = new HashMap(); + Map ingredientAccretionMap = new HashMap(); //find the reagents we're going to use to craft for(RecipeIngredientData ingredient : recipe.getIngredients()){ + ingredientTargetMap.put(ingredient.getItemType(),ingredient.getCount()); + ingredientAccretionMap.put(ingredient.getItemType(),0); int found = 0; if(naturalInventory != null){ for(Entity itemEnt : naturalInventory.getItems()){ if(itemMap.getItem(itemEnt).getId().matches(ingredient.getItemType())){ - found++; + if(ServerChargeState.hasServerChargeState(itemEnt)){ + ServerChargeState serverChargeState = ServerChargeState.getServerChargeState(itemEnt); + found = found + serverChargeState.getCharges(); + } else { + found++; + } reagentList.add(itemEnt); } if(found >= ingredient.getCount()){ @@ -152,7 +171,12 @@ public class InventoryProtocol implements ServerProtocolTemplate= ingredient.getCount()){ @@ -167,22 +191,55 @@ public class InventoryProtocol implements ServerProtocolTemplate targetToRemove){ + throw new Error("Removed too many ingredients! " + targetToRemove + " " + alreadyFound); + } else if(alreadyFound == targetToRemove){ + continue; } - if(toolbarInventory != null){ - toolbarInventory.tryRemoveItem(reagentEnt); + + + if(ServerChargeState.hasServerChargeState(reagentEnt)){ + ServerChargeState serverChargeState = ServerChargeState.getServerChargeState(reagentEnt); + int available = serverChargeState.getCharges(); + + if(available > targetToRemove - alreadyFound){ + int chargesToRemove = targetToRemove - alreadyFound; + serverChargeState.attemptAddCharges(-chargesToRemove); + //just remove charges + ingredientAccretionMap.put(itemData.getId(),alreadyFound + chargesToRemove); + } else { + //remove item entirely (consuming all charges) + if(naturalInventory != null){ + naturalInventory.removeItem(reagentEnt); + } + if(toolbarInventory != null){ + toolbarInventory.tryRemoveItem(reagentEnt); + } + this.deleteItemInInventory(crafter, reagentEnt); + ingredientAccretionMap.put(itemData.getId(),alreadyFound + available); + } + } else { + if(naturalInventory != null){ + naturalInventory.removeItem(reagentEnt); + } + if(toolbarInventory != null){ + toolbarInventory.tryRemoveItem(reagentEnt); + } + this.deleteItemInInventory(crafter, reagentEnt); + ingredientAccretionMap.put(itemData.getId(),alreadyFound + 1); } - this.deleteItemInInventory(crafter, reagentEnt); } for(RecipeIngredientData product : recipe.getProducts()){ Item productType = itemMap.getItem(product.getItemType()); if(productType == null){ throw new Error("Could not locate product definition! " + productType + " " + product.getItemType()); } - for(int i = 0; i < product.getCount(); i++){ - ItemUtils.serverCreateContainerItem(crafter, productType); - } + InventoryUtils.serverCreateInventoryItem(crafter, product.getItemType(), product.getCount()); } } }