item stacking
All checks were successful
studiorailgun/Renderer/pipeline/head This commit looks good

This commit is contained in:
austin 2025-04-30 02:28:55 -04:00
parent dec244c509
commit 2452cf194b
15 changed files with 245 additions and 37 deletions

View File

@ -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"

View File

@ -1605,6 +1605,7 @@ Cleaning up dead code
Fab items
Items keep charge state
UI renders charge state
Item stacking

View File

@ -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

View File

@ -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:

View File

@ -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;
}
/**

View File

@ -94,6 +94,7 @@ public class ServerInventoryState implements BehaviorTree {
case SERVERCOMMANDUNEQUIPITEM:
case SERVERCOMMANDEQUIPITEM:
case SERVERCOMMANDMOVEITEMCONTAINER:
case SERVERUPDATEITEMCHARGES:
break;
}
}

View File

@ -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());
}
}
}
}
/**
* <p> (initially) Automatically generated </p>
@ -124,8 +148,4 @@ public class ServerChargeState implements BehaviorTree {
return charges;
}
@Override
public void simulate(float deltaTime) {
}
}

View File

@ -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;

View File

@ -64,8 +64,16 @@ public class InventoryProtocol implements ClientProtocolTemplate<InventoryMessag
inventoryState.addNetworkMessage(message);
}
}
}
break;
} break;
case SERVERUPDATEITEMCHARGES: {
LoggerInterface.loggerNetworking.DEBUG("[CLIENT] SET CHARGES OF " + message.getentityId());
if(Globals.playerEntity != null){
ClientInventoryState inventoryState;
if((inventoryState = InventoryUtils.clientGetInventoryState(Globals.playerEntity))!=null){
inventoryState.addNetworkMessage(message);
}
}
} break;
case CLIENTREQUESTCRAFT:
case CLIENTUPDATETOOLBAR:
case CLIENTREQUESTADDNATURAL:

View File

@ -23,6 +23,7 @@ public class InventoryMessage extends NetworkMessage {
CLIENTUPDATETOOLBAR,
CLIENTREQUESTPERFORMITEMACTION,
CLIENTREQUESTCRAFT,
SERVERUPDATEITEMCHARGES,
}
/**
@ -42,6 +43,7 @@ public class InventoryMessage extends NetworkMessage {
double viewTargetZ;
int stationId;
int recipeId;
int charges;
/**
* Constructor
@ -245,6 +247,20 @@ public class InventoryMessage extends NetworkMessage {
this.recipeId = recipeId;
}
/**
* Gets charges
*/
public int getcharges() {
return charges;
}
/**
* Sets charges
*/
public void setcharges(int charges) {
this.charges = charges;
}
/**
* Removes the packet header from the buffer
* @param byteBuffer The buffer
@ -305,6 +321,12 @@ public class InventoryMessage extends NetworkMessage {
} else {
return false;
}
case TypeBytes.INVENTORY_MESSAGE_TYPE_SERVERUPDATEITEMCHARGES:
if(byteBuffer.getRemaining() >= 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;
}

View File

@ -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:

View File

@ -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

View File

@ -106,6 +106,7 @@ public class InventoryProtocol implements ServerProtocolTemplate<InventoryMessag
case SERVERCOMMANDUNEQUIPITEM:
case SERVERCOMMANDMOVEITEMCONTAINER:
case SERVERCOMMANDEQUIPITEM:
case SERVERUPDATEITEMCHARGES:
//silently ignore
break;
}

View File

@ -13,6 +13,7 @@ import electrosphere.client.block.BlockChunkData;
import electrosphere.client.terrain.cache.ChunkData;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.state.item.ServerChargeState;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.parser.net.message.TerrainMessage;
import electrosphere.net.server.ServerConnectionHandler;
@ -98,6 +99,7 @@ public class TerrainProtocol implements ServerProtocolTemplate<TerrainMessage> {
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:

View File

@ -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"
]
}
]
}