diff --git a/docs/src/progress/renderertodo.md b/docs/src/progress/renderertodo.md index ed83ba76..83057570 100644 --- a/docs/src/progress/renderertodo.md +++ b/docs/src/progress/renderertodo.md @@ -524,6 +524,15 @@ Math overhaul - Engine defined origin, up, and left vectors - Redo math for camera calculations Rotate player models to face correct direction +Remove BLENDER_TRANSFORM token +Redo of creature-spawning logic to support including attached items + - Items do not necessarily send from server if they are in the scene! They must also not have a parent +Ability to serialize/deserialize a creature with equipped items + - Serialize + - Deserialize + - Send to client + - Receive from server + # TODO diff --git a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java index 9b30da9b..d8d19347 100644 --- a/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java +++ b/src/main/java/electrosphere/client/terrain/manager/ClientTerrainManager.java @@ -119,6 +119,15 @@ public class ClientTerrainManager { public boolean containsChunkDataAtWorldPoint(int worldX, int worldY, int worldZ){ return terrainCache.containsChunkDataAtWorldPoint(worldX, worldY, worldZ); } + + /** + * Checks if the terrain cache contains chunk data at a given world position + * @param worldPos The vector containing the world-space position + * @return true if the data exists, false otherwise + */ + public boolean containsChunkDataAtWorldPoint(Vector3i worldPos){ + return terrainCache.containsChunkDataAtWorldPoint(worldPos.x, worldPos.y, worldPos.z); + } /** * Checks that the cache contains chunk data at a real-space coordinate diff --git a/src/main/java/electrosphere/client/terrain/sampling/ClientVoxelSampler.java b/src/main/java/electrosphere/client/terrain/sampling/ClientVoxelSampler.java index aeafd040..8834bd06 100644 --- a/src/main/java/electrosphere/client/terrain/sampling/ClientVoxelSampler.java +++ b/src/main/java/electrosphere/client/terrain/sampling/ClientVoxelSampler.java @@ -12,11 +12,16 @@ import electrosphere.entity.EntityUtils; * Samples voxels */ public class ClientVoxelSampler { + + /** + * Returned if a voxel is sampled from an invalid position + */ + public static final int INVALID_POSITION = -1; /** * Gets the voxel type beneath an entity * @param entity The entity - * @return The voxel type + * @return The voxel type, INVALID_POSITION if the position queried is invalid */ public static int getVoxelTypeBeneathEntity(Entity entity){ return ClientVoxelSampler.getVoxelType(EntityUtils.getPosition(entity)); @@ -25,13 +30,18 @@ public class ClientVoxelSampler { /** * Gets the voxel type at a given real-space position * @param realPos The real-space position - * @return The voxel type id + * @return The voxel type id, INVALID_POSITION if the position queried is invalid */ public static int getVoxelType(Vector3d realPos){ + int voxelId = 0; Vector3i chunkSpacePos = Globals.clientWorldData.convertRealToWorldSpace(realPos); Vector3i voxelSpacePos = Globals.clientWorldData.convertRealToVoxelSpace(realPos); - ChunkData chunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(chunkSpacePos); - int voxelId = chunkData.getType(voxelSpacePos); + if(Globals.clientTerrainManager.containsChunkDataAtWorldPoint(chunkSpacePos)){ + ChunkData chunkData = Globals.clientTerrainManager.getChunkDataAtWorldPoint(chunkSpacePos); + voxelId = chunkData.getType(voxelSpacePos); + } else { + return INVALID_POSITION; + } return voxelId; } diff --git a/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java b/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java index 782fcc98..5f56fcf7 100644 --- a/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java +++ b/src/main/java/electrosphere/engine/loadingthreads/LoadingUtils.java @@ -153,16 +153,16 @@ public class LoadingUtils { //send default template back String race = Globals.gameConfigCurrent.getCreatureTypeLoader().getPlayableRaces().get(0); CreatureData type = Globals.gameConfigCurrent.getCreatureTypeLoader().getCreature(race); - CreatureTemplate template = new CreatureTemplate(race); + CreatureTemplate template = CreatureTemplate.create(race); for(VisualAttribute attribute : type.getVisualAttributes()){ if(attribute.getType().equals(VisualAttribute.TYPE_BONE)){ float min = attribute.getMinValue(); float max = attribute.getMaxValue(); float defaultValue = min + (max - min)/2.0f; //add attribute to creature template - template.putValue(attribute.getAttributeId(), defaultValue); + template.putAttributeValue(attribute.getAttributeId(), defaultValue); } else if(attribute.getType().equals(VisualAttribute.TYPE_REMESH)){ - template.putValue(attribute.getAttributeId(), attribute.getVariants().get(0).getId()); + template.putAttributeValue(attribute.getAttributeId(), attribute.getVariants().get(0).getId()); } } //set player character template diff --git a/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java b/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java index 094b82e4..994ad94a 100644 --- a/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java +++ b/src/main/java/electrosphere/entity/state/attack/ClientAttackTree.java @@ -106,7 +106,10 @@ public class ClientAttackTree implements BehaviorTree { StateTransitionUtilItem.create( AttackTreeState.WINDUP, () -> { - TreeDataState state = currentMove.getWindupState(); + TreeDataState state = null; + if(currentMove != null){ + state = currentMove.getWindupState(); + } if(state == null){ return null; } else { @@ -114,7 +117,10 @@ public class ClientAttackTree implements BehaviorTree { } }, () -> { - TreeDataState state = currentMove.getWindupState(); + TreeDataState state = null; + if(currentMove != null){ + state = currentMove.getWindupState(); + } if(state == null){ return null; } else { @@ -126,7 +132,10 @@ public class ClientAttackTree implements BehaviorTree { StateTransitionUtilItem.create( AttackTreeState.HOLD, () -> { - TreeDataState state = currentMove.getHoldState(); + TreeDataState state = null; + if(currentMove != null){ + state = currentMove.getHoldState(); + } if(state == null){ return null; } else { @@ -134,7 +143,10 @@ public class ClientAttackTree implements BehaviorTree { } }, () -> { - TreeDataState state = currentMove.getHoldState(); + TreeDataState state = null; + if(currentMove != null){ + state = currentMove.getHoldState(); + } if(state == null){ return null; } else { @@ -146,7 +158,10 @@ public class ClientAttackTree implements BehaviorTree { StateTransitionUtilItem.create( AttackTreeState.ATTACK, () -> { - TreeDataState state = currentMove.getAttackState(); + TreeDataState state = null; + if(currentMove != null){ + state = currentMove.getAttackState(); + } if(state == null){ return null; } else { @@ -154,7 +169,10 @@ public class ClientAttackTree implements BehaviorTree { } }, () -> { - TreeDataState state = currentMove.getAttackState(); + TreeDataState state = null; + if(currentMove != null){ + state = currentMove.getAttackState(); + } if(state == null){ return null; } else { @@ -166,7 +184,10 @@ public class ClientAttackTree implements BehaviorTree { StateTransitionUtilItem.create( AttackTreeState.COOLDOWN, () -> { - TreeDataState state = currentMove.getCooldownState(); + TreeDataState state = null; + if(currentMove != null){ + state = currentMove.getCooldownState(); + } if(state == null){ return null; } else { @@ -174,7 +195,10 @@ public class ClientAttackTree implements BehaviorTree { } }, () -> { - TreeDataState state = currentMove.getCooldownState(); + TreeDataState state = null; + if(currentMove != null){ + state = currentMove.getCooldownState(); + } if(state == null){ return null; } else { diff --git a/src/main/java/electrosphere/entity/state/equip/ClientEquipState.java b/src/main/java/electrosphere/entity/state/equip/ClientEquipState.java index c03254d2..b96c7139 100644 --- a/src/main/java/electrosphere/entity/state/equip/ClientEquipState.java +++ b/src/main/java/electrosphere/entity/state/equip/ClientEquipState.java @@ -12,6 +12,7 @@ import org.ode4j.ode.DBody; import electrosphere.engine.Globals; import electrosphere.entity.Entity; +import electrosphere.entity.EntityCreationUtils; import electrosphere.entity.EntityDataStrings; import electrosphere.entity.EntityTags; import electrosphere.entity.EntityUtils; @@ -24,6 +25,7 @@ import electrosphere.entity.types.item.ItemUtils; import electrosphere.game.data.common.TreeDataAnimation; import electrosphere.game.data.creature.type.equip.EquipPoint; import electrosphere.game.data.item.type.EquipWhitelist; +import electrosphere.game.data.item.type.Item; import electrosphere.logger.LoggerInterface; import electrosphere.net.parser.net.message.InventoryMessage; import electrosphere.net.parser.net.message.NetworkMessage; @@ -165,6 +167,15 @@ public class ClientEquipState implements BehaviorTree { } } else { //does not depend on the type of creature, must be attaching to a bone + //make sure it's visible + if(EntityUtils.getActor(toEquip) == null){ + Item itemData = Globals.gameConfigCurrent.getItemMap().getItem(ItemUtils.getType(toEquip)); + EntityCreationUtils.makeEntityDrawable(toEquip, itemData.getModelPath()); + if(itemData.getIdleAnim() != null){ + toEquip.putData(EntityDataStrings.ANIM_IDLE,itemData.getIdleAnim()); + } + } + //actually equip equipMap.put(point.getEquipPointId(),toEquip); if(parent != Globals.firstPersonEntity || Globals.controlHandler.cameraIsThirdPerson()){ AttachUtils.clientAttachEntityToEntityAtBone( diff --git a/src/main/java/electrosphere/entity/state/equip/ServerEquipState.java b/src/main/java/electrosphere/entity/state/equip/ServerEquipState.java index 797fb5ca..9dab563a 100644 --- a/src/main/java/electrosphere/entity/state/equip/ServerEquipState.java +++ b/src/main/java/electrosphere/entity/state/equip/ServerEquipState.java @@ -401,7 +401,7 @@ public class ServerEquipState implements BehaviorTree { /** * Gets whether an item is equipped at a point or not - * @param point THe point to check + * @param point The point to check * @return true if an item is equipped at the point, false otherwise */ public boolean hasEquippedAtPoint(String point){ diff --git a/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java b/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java index 21962a99..6bb50605 100644 --- a/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java +++ b/src/main/java/electrosphere/entity/state/inventory/InventoryUtils.java @@ -79,8 +79,9 @@ public class InventoryUtils { * Perform the entity transforms to actually store an item in an inventory, if server this has the side effect of also sending packets on success * @param creature The creature to store the item in * @param item The item to store + * @return The in-inventory item */ - public static void serverAttemptStoreItemTransform(Entity creature, Entity item){ + public static Entity serverAttemptStoreItemTransform(Entity creature, Entity item){ boolean creatureIsCreature = CreatureUtils.isCreature(creature); boolean itemIsItem = ItemUtils.isItem(item); boolean hasInventory = hasNaturalInventory(creature); @@ -116,7 +117,9 @@ public class InventoryUtils { ItemUtils.serverDestroyInWorldItem(item); //alert script engine ServerScriptUtils.fireSignalOnEntity(creature, "itemPickup", item.getId(), inventoryItem.getId()); + return inventoryItem; } + return null; } /** @@ -129,7 +132,7 @@ public class InventoryUtils { NetworkMessage requestPickupMessage = InventoryMessage.constructaddItemToInventoryMessage( Globals.clientSceneWrapper.mapClientToServerId(item.getId()), ItemUtils.getType(item) - ); + ); Globals.clientConnection.queueOutgoingMessage(requestPickupMessage); } @@ -137,10 +140,11 @@ public class InventoryUtils { * Attempts to store the in-world item entity in a creature inventory container * @param creature the creature which has a natural inventory * @param item the in-world item entity to store + * @return The in-inventory entity if it succeeded, null otherwise */ - public static void serverAttemptStoreItem(Entity creature, Entity item){ + public static Entity serverAttemptStoreItem(Entity creature, Entity item){ //immediately attempt the transform - serverAttemptStoreItemTransform(creature,item); + return serverAttemptStoreItemTransform(creature,item); } /** diff --git a/src/main/java/electrosphere/entity/types/attach/AttachUtils.java b/src/main/java/electrosphere/entity/types/attach/AttachUtils.java index dc8f1a90..52cbb2c8 100644 --- a/src/main/java/electrosphere/entity/types/attach/AttachUtils.java +++ b/src/main/java/electrosphere/entity/types/attach/AttachUtils.java @@ -7,6 +7,7 @@ import electrosphere.entity.EntityTags; import electrosphere.entity.EntityUtils; import electrosphere.entity.types.camera.CameraEntityUtils; import electrosphere.entity.types.creature.CreatureUtils; +import electrosphere.logger.LoggerInterface; import electrosphere.renderer.actor.Actor; import electrosphere.server.datacell.ServerDataCell; import electrosphere.server.datacell.utils.ServerEntityTagUtils; @@ -181,7 +182,9 @@ public class AttachUtils { //update entities attached to bones of other entities for(Entity currentEntity : Globals.clientSceneWrapper.getScene().getEntitiesWithTag(EntityTags.BONE_ATTACHED)){ Entity parent; - if((parent = (Entity)currentEntity.getData(EntityDataStrings.ATTACH_PARENT))!=null){ + if(currentEntity == null){ + LoggerInterface.loggerEngine.ERROR(new IllegalStateException("Trying to update client bone attachment where null entity is registered!")); + } else if((parent = (Entity)currentEntity.getData(EntityDataStrings.ATTACH_PARENT))!=null){ clientUpdateEntityTransforms(currentEntity,parent); } else if(currentEntity.getData(EntityDataStrings.ATTACH_TARGET_BASE)!=null){ Vector3d positionOffset = getVectorOffset(currentEntity); diff --git a/src/main/java/electrosphere/entity/types/creature/CreatureEquipData.java b/src/main/java/electrosphere/entity/types/creature/CreatureEquipData.java new file mode 100644 index 00000000..05ac92a5 --- /dev/null +++ b/src/main/java/electrosphere/entity/types/creature/CreatureEquipData.java @@ -0,0 +1,91 @@ +package electrosphere.entity.types.creature; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +/** + * Data on what a creature has equipped currently + */ +public class CreatureEquipData { + + //Map of equip slot -> item definition + Map slotToItemMap = new HashMap(); + + /** + * Gets the slots in the equip data + * @return The set of equip slot ids + */ + public Set getSlots(){ + return slotToItemMap.keySet(); + } + + /** + * Gets the item occupying a given slot + * @param slotId The id of the slot + * @return The item definition + */ + public EquippedItem getSlotItem(String slotId){ + return slotToItemMap.get(slotId); + } + + /** + * Sets a slot as containing an item type + * @param slotId The slot + * @param itemType The item definition + */ + public void setSlotItem(String slotId, EquippedItem itemType){ + slotToItemMap.put(slotId, itemType); + } + + /** + * Clears the equip data + */ + public void clear(){ + slotToItemMap.clear(); + } + + /** + * An item that is equipped + */ + public static class EquippedItem { + + /** + * The type of the item + */ + String itemType; + + /** + * The entity id of the item + */ + int entityId; + + /** + * Constructor + * @param entityId The id of the item entity + * @param itemType The type of the item + */ + public EquippedItem(int entityId, String itemType){ + this.entityId = entityId; + this.itemType = itemType; + } + + /** + * The type of the item + * @return The type + */ + public String getItemType(){ + return itemType; + } + + /** + * The id of the entity that is this item + * @return The id + */ + public int getEntityId(){ + return entityId; + } + + } + +} diff --git a/src/main/java/electrosphere/entity/types/creature/CreatureTemplate.java b/src/main/java/electrosphere/entity/types/creature/CreatureTemplate.java index ae201d57..17e12476 100644 --- a/src/main/java/electrosphere/entity/types/creature/CreatureTemplate.java +++ b/src/main/java/electrosphere/entity/types/creature/CreatureTemplate.java @@ -3,58 +3,139 @@ package electrosphere.entity.types.creature; import java.util.HashMap; import java.util.Map; +/** + * The template used to construct the creature on the client + */ public class CreatureTemplate { + /** + * The type of the creature + */ String creatureType; + /** + * The attribute map for visual variants + */ Map attributeMap = new HashMap(); - public CreatureTemplate(String creatureType){ - this.creatureType = creatureType; + /** + * The equip data for the creature + */ + private CreatureEquipData equipData = new CreatureEquipData(); + + /** + * Creates the creature template + * @param creatureType The type of creature + * @return The creature template + */ + public static CreatureTemplate create(String creatureType){ + CreatureTemplate rVal = new CreatureTemplate(); + rVal.creatureType = creatureType; + return rVal; } - public void putValue(String attributeName, float value){ + /** + * Puts an attribute value in the template + * @param attributeName The name of the attribute + * @param value The value of the attribute + */ + public void putAttributeValue(String attributeName, float value){ attributeMap.put(attributeName,new CreatureTemplateAttributeValue(value)); } - public void putValue(String attributeName, String value){ + /** + * Puts an attribute value into the template + * @param attributeName The name of the attribute + * @param value The value of the attribute + */ + public void putAttributeValue(String attributeName, String value){ attributeMap.put(attributeName,new CreatureTemplateAttributeValue(value)); } - public CreatureTemplateAttributeValue getValue(String attributeName){ + /** + * Gets the value of an attribte + * @param attributeName The name of the attribute + * @return The value of the attribute + */ + public CreatureTemplateAttributeValue getAttributeValue(String attributeName){ return attributeMap.get(attributeName); } + /** + * Gets the type of the creature + * @return The type of the creature + */ public String getCreatureType(){ return creatureType; } + /** + * Gets the creature equip data for the template + * @return The creature equip data for the template + */ + public CreatureEquipData getCreatureEquipData(){ + return this.equipData; + } + /** + * A visual attribute of a creature (ie how wide is their nose, what type of hairstyle do they have, etc) + */ public class CreatureTemplateAttributeValue { + /** + * The string value of the attribute + */ String variantId; + + /** + * The float value of the attribute + */ float value; + /** + * Creates a float attribute + * @param value The value + */ public CreatureTemplateAttributeValue(float value){ this.value = value; } + /** + * Creates a string attribute + * @param variantId The string + */ public CreatureTemplateAttributeValue(String variantId){ this.variantId = variantId; } + /** + * Gets the float value of the attribute + * @return The value + */ public float getValue(){ return value; } + /** + * Gets the string value of the attribute + * @return The string value + */ public String getVariantId(){ return variantId; } + /** + * Sets the float value of the attribute + * @param value The float value + */ public void setValue(float value){ this.value = value; } + /** + * Sets the string value of the attribute + * @param variantId The string value + */ public void setVariantId(String variantId){ this.variantId = variantId; } diff --git a/src/main/java/electrosphere/entity/types/creature/CreatureUtils.java b/src/main/java/electrosphere/entity/types/creature/CreatureUtils.java index 3d137ac3..2733ae1c 100644 --- a/src/main/java/electrosphere/entity/types/creature/CreatureUtils.java +++ b/src/main/java/electrosphere/entity/types/creature/CreatureUtils.java @@ -45,6 +45,8 @@ import electrosphere.entity.state.rotator.RotatorHierarchyNode; import electrosphere.entity.state.rotator.RotatorTree; import electrosphere.entity.state.rotator.ServerRotatorTree; import electrosphere.entity.types.collision.CollisionObjUtils; +import electrosphere.entity.types.creature.CreatureEquipData.EquippedItem; +import electrosphere.entity.types.item.ItemUtils; import electrosphere.game.data.collidable.CollidableTemplate; import electrosphere.game.data.creature.type.CreatureData; import electrosphere.game.data.creature.type.SprintSystem; @@ -66,12 +68,10 @@ import electrosphere.net.server.player.Player; import electrosphere.renderer.actor.Actor; import electrosphere.renderer.actor.ActorBoneRotator; import electrosphere.renderer.actor.ActorStaticMorph; -import electrosphere.renderer.actor.ActorUtils; import electrosphere.server.datacell.Realm; import electrosphere.server.datacell.utils.ServerBehaviorTreeUtils; import electrosphere.server.datacell.utils.ServerEntityTagUtils; import electrosphere.server.poseactor.PoseActor; -import electrosphere.server.poseactor.PoseActorUtils; import electrosphere.util.Utilities; import electrosphere.util.math.MathUtils; @@ -248,7 +248,7 @@ public class CreatureUtils { } } //variants - CreatureTemplate storedTemplate = new CreatureTemplate(rawType.getCreatureId()); + CreatureTemplate storedTemplate = CreatureTemplate.create(rawType.getCreatureId()); if(rawType.getVisualAttributes() != null){ ActorStaticMorph staticMorph = null; for(VisualAttribute attributeType : rawType.getVisualAttributes()){ @@ -257,20 +257,20 @@ public class CreatureUtils { AttributeVariant variant = attributeType.getVariants().get(0); //if the template isn't null, try to find the variant from the template in the variant list //if the variant is found, set the variable "variant" to the searched for variant - if(template != null && template.getValue(attributeType.getAttributeId()) != null){ - String variantId = template.getValue(attributeType.getAttributeId()).getVariantId(); + if(template != null && template.getAttributeValue(attributeType.getAttributeId()) != null){ + String variantId = template.getAttributeValue(attributeType.getAttributeId()).getVariantId(); for(AttributeVariant searchVariant : attributeType.getVariants()){ if(searchVariant.getId().equals(variantId)){ variant = searchVariant; //if we find the variant, store in on-creature template as well - storedTemplate.putValue(attributeType.getAttributeId(), variantId); + storedTemplate.putAttributeValue(attributeType.getAttributeId(), variantId); break; } } } //make sure stored template contains creature data - if(storedTemplate.getValue(attributeType.getAttributeId())==null){ - storedTemplate.putValue(attributeType.getAttributeId(), attributeType.getVariants().get(0).getId()); + if(storedTemplate.getAttributeValue(attributeType.getAttributeId())==null){ + storedTemplate.putAttributeValue(attributeType.getAttributeId(), attributeType.getVariants().get(0).getId()); } // attributeType.getAttributeId(); // variant.getId(); @@ -289,25 +289,25 @@ public class CreatureUtils { if(attributeType.getPrimaryBone() != null && staticMorph.getBoneTransforms(attributeType.getPrimaryBone()) == null){ staticMorph.initBoneTransforms(attributeType.getPrimaryBone()); //if the template isn't null, set the value of the morph - if(template != null && template.getValue(attributeType.getAttributeId()) != null){ - float templateValue = template.getValue(attributeType.getAttributeId()).getValue(); + if(template != null && template.getAttributeValue(attributeType.getAttributeId()) != null){ + float templateValue = template.getAttributeValue(attributeType.getAttributeId()).getValue(); staticMorph.updateValue(attributeType.getSubtype(), attributeType.getPrimaryBone(), templateValue); } } if(attributeType.getMirrorBone() != null && staticMorph.getBoneTransforms(attributeType.getMirrorBone()) == null){ staticMorph.initBoneTransforms(attributeType.getMirrorBone()); //if the template isn't null, set the value of the morph - if(template != null && template.getValue(attributeType.getAttributeId()) != null){ - float templateValue = template.getValue(attributeType.getAttributeId()).getValue(); + if(template != null && template.getAttributeValue(attributeType.getAttributeId()) != null){ + float templateValue = template.getAttributeValue(attributeType.getAttributeId()).getValue(); staticMorph.updateValue(attributeType.getSubtype(), attributeType.getMirrorBone(), templateValue); } } //make sure stored template contains creature data - if(template != null && template.getValue(attributeType.getAttributeId()) != null) { - storedTemplate.putValue(attributeType.getAttributeId(), template.getValue(attributeType.getAttributeId()).getValue()); + if(template != null && template.getAttributeValue(attributeType.getAttributeId()) != null) { + storedTemplate.putAttributeValue(attributeType.getAttributeId(), template.getAttributeValue(attributeType.getAttributeId()).getValue()); } else { float midpoint = (attributeType.getMaxValue() - attributeType.getMinValue())/2.0f + attributeType.getMinValue(); - storedTemplate.putValue(attributeType.getAttributeId(), midpoint); + storedTemplate.putAttributeValue(attributeType.getAttributeId(), midpoint); } } } @@ -464,13 +464,31 @@ public class CreatureUtils { break; } } + + // + // + // EQUIP STATE + // + // if(rawType.getEquipPoints() != null && rawType.getEquipPoints().size() > 0){ ServerEquipState.attachTree(rVal, rawType.getEquipPoints()); rVal.putData(EntityDataStrings.EQUIP_INVENTORY, RelationalInventoryState.buildRelationalInventoryStateFromEquipList(rawType.getEquipPoints())); } + + // + // + // BLOCK STATE + // + // if(rawType.getBlockSystem() != null){ ServerBlockTree.attachTree(rVal, rawType.getBlockSystem()); } + + // + // + // TOKENS + // + // for(String token : rawType.getTokens()){ switch(token){ case "ATTACKER": { @@ -516,29 +534,27 @@ public class CreatureUtils { } } //variants - CreatureTemplate storedTemplate = new CreatureTemplate(rawType.getCreatureId()); + CreatureTemplate storedTemplate = CreatureTemplate.create(rawType.getCreatureId()); if(rawType.getVisualAttributes() != null){ ActorStaticMorph staticMorph = null; for(VisualAttribute attributeType : rawType.getVisualAttributes()){ if(attributeType.getType().equals("remesh")){ if(attributeType.getVariants() != null && attributeType.getVariants().size() > 0){ - AttributeVariant variant = attributeType.getVariants().get(0); //if the template isn't null, try to find the variant from the template in the variant list //if the variant is found, set the variable "variant" to the searched for variant - if(template != null && template.getValue(attributeType.getAttributeId()) != null){ - String variantId = template.getValue(attributeType.getAttributeId()).getVariantId(); + if(template != null && template.getAttributeValue(attributeType.getAttributeId()) != null){ + String variantId = template.getAttributeValue(attributeType.getAttributeId()).getVariantId(); for(AttributeVariant searchVariant : attributeType.getVariants()){ if(searchVariant.getId().equals(variantId)){ - variant = searchVariant; //if we find the variant, store in on-creature template as well - storedTemplate.putValue(attributeType.getAttributeId(), variantId); + storedTemplate.putAttributeValue(attributeType.getAttributeId(), variantId); break; } } } //make sure stored template contains creature data - if(storedTemplate.getValue(attributeType.getAttributeId())==null){ - storedTemplate.putValue(attributeType.getAttributeId(), attributeType.getVariants().get(0).getId()); + if(storedTemplate.getAttributeValue(attributeType.getAttributeId())==null){ + storedTemplate.putAttributeValue(attributeType.getAttributeId(), attributeType.getVariants().get(0).getId()); } //TODO: determine if this should be relevant to pose actor //pretty certain it shouldn't be but you never know @@ -558,25 +574,25 @@ public class CreatureUtils { if(attributeType.getPrimaryBone() != null && staticMorph.getBoneTransforms(attributeType.getPrimaryBone()) == null){ staticMorph.initBoneTransforms(attributeType.getPrimaryBone()); //if the template isn't null, set the value of the morph - if(template != null && template.getValue(attributeType.getAttributeId()) != null){ - float templateValue = template.getValue(attributeType.getAttributeId()).getValue(); + if(template != null && template.getAttributeValue(attributeType.getAttributeId()) != null){ + float templateValue = template.getAttributeValue(attributeType.getAttributeId()).getValue(); staticMorph.updateValue(attributeType.getSubtype(), attributeType.getPrimaryBone(), templateValue); } } if(attributeType.getMirrorBone() != null && staticMorph.getBoneTransforms(attributeType.getMirrorBone()) == null){ staticMorph.initBoneTransforms(attributeType.getMirrorBone()); //if the template isn't null, set the value of the morph - if(template != null && template.getValue(attributeType.getAttributeId()) != null){ - float templateValue = template.getValue(attributeType.getAttributeId()).getValue(); + if(template != null && template.getAttributeValue(attributeType.getAttributeId()) != null){ + float templateValue = template.getAttributeValue(attributeType.getAttributeId()).getValue(); staticMorph.updateValue(attributeType.getSubtype(), attributeType.getMirrorBone(), templateValue); } } //make sure stored template contains creature data - if(template != null && template.getValue(attributeType.getAttributeId()) != null) { - storedTemplate.putValue(attributeType.getAttributeId(), template.getValue(attributeType.getAttributeId()).getValue()); + if(template != null && template.getAttributeValue(attributeType.getAttributeId()) != null) { + storedTemplate.putAttributeValue(attributeType.getAttributeId(), template.getAttributeValue(attributeType.getAttributeId()).getValue()); } else { float midpoint = (attributeType.getMaxValue() - attributeType.getMinValue())/2.0f + attributeType.getMinValue(); - storedTemplate.putValue(attributeType.getAttributeId(), midpoint); + storedTemplate.putAttributeValue(attributeType.getAttributeId(), midpoint); } } } @@ -642,7 +658,6 @@ public class CreatureUtils { //The server initialization logic checks what type of entity this is, if this function is called prior to its type being stored //the server will not be able to synchronize it properly. ServerEntityUtils.initiallyPositionEntity(realm,rVal,position); - return rVal; } @@ -677,9 +692,13 @@ public class CreatureUtils { return rVal; } + /** + * Gets the creature to the player + * @param player The player to send the creature to + * @param creature The creature entity + */ public static void sendEntityToPlayer(Player player, Entity creature){ int id = creature.getId(); - String type = CreatureUtils.getType(creature); Vector3d position = EntityUtils.getPosition(creature); String template = Utilities.stringify(CreatureUtils.getCreatureTemplate(creature)); NetworkMessage message = EntityMessage.constructSpawnCreatureMessage( @@ -688,7 +707,6 @@ public class CreatureUtils { position.x, position.y, position.z); - // NetworkMessage message = EntityMessage.constructCreateMessage(id, EntityDataStrings.ENTITY_CATEGORY_CREATURE, type, (float)position.x, (float)position.y, (float)position.z); player.addMessage(message); if(CreatureUtils.hasControllerPlayerId(creature)){ LoggerInterface.loggerNetworking.INFO("Sending controller packets"); @@ -811,8 +829,23 @@ public class CreatureUtils { e.putData(EntityDataStrings.CREATURE_TEMPLATE, template); } + /** + * Gets the template for the creature + * @param e The creature + * @return The template + */ public static CreatureTemplate getCreatureTemplate(Entity e){ - return (CreatureTemplate)e.getData(EntityDataStrings.CREATURE_TEMPLATE); + CreatureTemplate template = (CreatureTemplate)e.getData(EntityDataStrings.CREATURE_TEMPLATE); + if(ServerEquipState.hasEquipState(e)){ + ServerEquipState serverEquipState = ServerEquipState.getEquipState(e); + CreatureEquipData equipData = template.getCreatureEquipData(); + equipData.clear(); + for(String point : serverEquipState.equippedPoints()){ + Entity item = serverEquipState.getEquippedItemAtPoint(point); + equipData.setSlotItem(point, new EquippedItem(item.getId(),ItemUtils.getType(item))); + } + } + return template; } diff --git a/src/main/java/electrosphere/entity/types/item/ItemUtils.java b/src/main/java/electrosphere/entity/types/item/ItemUtils.java index c7a87d64..843aacf5 100644 --- a/src/main/java/electrosphere/entity/types/item/ItemUtils.java +++ b/src/main/java/electrosphere/entity/types/item/ItemUtils.java @@ -18,6 +18,7 @@ import electrosphere.entity.state.AnimationPriorities; import electrosphere.entity.state.gravity.ClientGravityTree; import electrosphere.entity.state.gravity.ServerGravityTree; import electrosphere.entity.state.hitbox.HitboxCollectionState; +import electrosphere.entity.types.attach.AttachUtils; import electrosphere.game.data.collidable.CollidableTemplate; import electrosphere.game.data.item.type.EquipData; import electrosphere.game.data.item.type.EquipWhitelist; @@ -27,7 +28,6 @@ import electrosphere.net.parser.net.message.EntityMessage; import electrosphere.net.parser.net.message.NetworkMessage; import electrosphere.net.server.player.Player; import electrosphere.renderer.actor.Actor; -import electrosphere.renderer.actor.ActorUtils; import electrosphere.server.datacell.Realm; import electrosphere.server.datacell.utils.ServerEntityTagUtils; import electrosphere.server.poseactor.PoseActor; @@ -282,6 +282,34 @@ public class ItemUtils { } return (int)e.getData(EntityDataStrings.ENTITY_TYPE) == ENTITY_TYPE_ITEM; } + + /** + * Checks if an item should be sent to a client on synchronization + * @param item The item + * @return true if should be sent, false otherwise + */ + public static boolean itemShouldBeSentToClient(Entity item){ + //if the item is attached to a creature, don't send a dedicated item + //instead, the creature template should include the item + if(AttachUtils.hasParent(item)){ + return false; + } + return true; + } + + /** + * Checks if an item should be serialized to disk when saving a chunk + * @param item The item + * @return true if should be sent, false otherwise + */ + public static boolean itemShouldBeSerialized(Entity item){ + //if the item is attached to a creature, don't save a dedicated item + //instead, the creature template should include the item + if(AttachUtils.hasParent(item)){ + return false; + } + return true; + } /** * Gets the type of item @@ -353,6 +381,7 @@ public class ItemUtils { ItemUtils.setContainingParent(rVal, containingParent); EntityUtils.setEntitySubtype(rVal, EntityUtils.getEntitySubtype(item)); Globals.clientSceneWrapper.getScene().registerEntity(rVal); + Globals.clientSceneWrapper.getScene().registerEntityToTag(rVal, EntityTags.ITEM); return rVal; } else { return null; diff --git a/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsMultiplayer.java b/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsMultiplayer.java index 72006a23..5a12029b 100644 --- a/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsMultiplayer.java +++ b/src/main/java/electrosphere/menu/mainmenu/MenuGeneratorsMultiplayer.java @@ -107,7 +107,7 @@ public class MenuGeneratorsMultiplayer { ActorStaticMorph staticMorph = new ActorStaticMorph(); //create creature template - CreatureTemplate template = new CreatureTemplate(race); + CreatureTemplate template = CreatureTemplate.create(race); List controlsToAdd = new LinkedList(); //create edit controls here @@ -134,7 +134,7 @@ public class MenuGeneratorsMultiplayer { staticMorph.initBoneTransforms(attribute.getMirrorBone()); } //add attribute to creature template - template.putValue(attribute.getAttributeId(), defaultValue); + template.putAttributeValue(attribute.getAttributeId(), defaultValue); //set callback for when we change the slider value to update the static morph boneSlider.setOnValueChangeCallback(new ValueChangeEventCallback() {public void execute(ValueChangeEvent event) { if(characterActor.getStaticMorph() != null){ @@ -143,7 +143,7 @@ public class MenuGeneratorsMultiplayer { if(attribute.getMirrorBone() != null){ staticMorph.updateValue(attribute.getSubtype(), attribute.getMirrorBone(), event.getAsFloat()); } - template.getValue(attribute.getAttributeId()).setValue(event.getAsFloat()); + template.getAttributeValue(attribute.getAttributeId()).setValue(event.getAsFloat()); } }}); } else if(attribute.getType().equals(VisualAttribute.TYPE_REMESH)){ @@ -161,13 +161,13 @@ public class MenuGeneratorsMultiplayer { if(!hasAddedValue){ hasAddedValue = true; //add attribute to template - template.putValue(attribute.getAttributeId(), variant.getId()); + template.putAttributeValue(attribute.getAttributeId(), variant.getId()); } } //callback for updating remesh variantCarousel.setOnValueChangeCallback(new ValueChangeEventCallback(){public void execute(ValueChangeEvent event) { //TODO: implement updating visuals - template.getValue(attribute.getAttributeId()).setVariantId(event.getAsString()); + template.getAttributeValue(attribute.getAttributeId()).setVariantId(event.getAsString()); AttributeVariant variant = null; for(AttributeVariant variantCurrent : attribute.getVariants()){ if(variantCurrent.getId().equals(event.getAsString())){ diff --git a/src/main/java/electrosphere/net/client/protocol/EntityProtocol.java b/src/main/java/electrosphere/net/client/protocol/EntityProtocol.java index 276fed23..f799676e 100644 --- a/src/main/java/electrosphere/net/client/protocol/EntityProtocol.java +++ b/src/main/java/electrosphere/net/client/protocol/EntityProtocol.java @@ -11,7 +11,10 @@ import electrosphere.entity.ClientEntityUtils; import electrosphere.entity.Entity; import electrosphere.entity.EntityUtils; import electrosphere.entity.state.attack.ClientAttackTree; +import electrosphere.entity.state.equip.ClientEquipState; +import electrosphere.entity.state.inventory.InventoryUtils; import electrosphere.entity.types.attach.AttachUtils; +import electrosphere.entity.types.creature.CreatureEquipData.EquippedItem; import electrosphere.entity.types.creature.CreatureTemplate; import electrosphere.entity.types.creature.CreatureUtils; import electrosphere.entity.types.foliage.FoliageUtils; @@ -91,6 +94,22 @@ public class EntityProtocol implements ClientProtocolTemplate { newlySpawnedEntity = CreatureUtils.clientSpawnBasicCreature(template.getCreatureType(),template); ClientEntityUtils.initiallyPositionEntity(newlySpawnedEntity, new Vector3d(message.getpositionX(),message.getpositionY(),message.getpositionZ())); Globals.clientSceneWrapper.mapIdToId(newlySpawnedEntity.getId(), message.getentityID()); + //if the creature template includes an equip section, spawn all the equipped items + if(template != null && template.getCreatureEquipData() != null && template.getCreatureEquipData().getSlots() != null){ + for(String equipSlotId : template.getCreatureEquipData().getSlots()){ + + //add the item to the creature's inventory + EquippedItem itemDefinition = template.getCreatureEquipData().getSlotItem(equipSlotId); + Entity itemInInventory = InventoryUtils.clientConstructInInventoryItem(newlySpawnedEntity,itemDefinition.getItemType()); + + //equip the item to the slot defined in the template + ClientEquipState clientEquipState = ClientEquipState.getEquipState(newlySpawnedEntity); + clientEquipState.attemptEquip(itemInInventory, clientEquipState.getEquipPoint(equipSlotId)); + + //map the constructed item to its server id + Globals.clientSceneWrapper.mapIdToId(itemInInventory.getId(), itemDefinition.getEntityId()); + } + } } break; case SPAWNITEM: { LoggerInterface.loggerNetworking.DEBUG("Spawn Item " + message.getentityID() + " at " + message.getpositionX() + " " + message.getpositionY() + " " + message.getpositionZ()); diff --git a/src/main/java/electrosphere/server/content/serialization/ContentSerialization.java b/src/main/java/electrosphere/server/content/serialization/ContentSerialization.java index 7083caee..5c24a4ec 100644 --- a/src/main/java/electrosphere/server/content/serialization/ContentSerialization.java +++ b/src/main/java/electrosphere/server/content/serialization/ContentSerialization.java @@ -5,6 +5,11 @@ import java.util.List; import electrosphere.entity.Entity; import electrosphere.entity.EntityUtils; +import electrosphere.entity.state.equip.ServerEquipState; +import electrosphere.entity.state.inventory.InventoryUtils; +import electrosphere.entity.types.attach.AttachUtils; +import electrosphere.entity.types.creature.CreatureEquipData.EquippedItem; +import electrosphere.entity.types.creature.CreatureTemplate; import electrosphere.entity.types.creature.CreatureUtils; import electrosphere.entity.types.foliage.FoliageUtils; import electrosphere.entity.types.item.ItemUtils; @@ -12,6 +17,8 @@ import electrosphere.entity.types.object.ObjectUtils; import electrosphere.entity.types.structure.StructureUtils; import electrosphere.server.datacell.Realm; import electrosphere.server.datacell.ServerDataCell; +import electrosphere.server.datacell.utils.EntityLookupUtils; +import electrosphere.util.Utilities; /** * Contains all content for a given cell @@ -37,9 +44,12 @@ public class ContentSerialization { serializedEntity.setRotation(EntityUtils.getRotation(entity)); serializedEntity.setType(CreatureUtils.ENTITY_TYPE_CREATURE); serializedEntity.setSubtype(CreatureUtils.getType(entity)); + if(CreatureUtils.getCreatureTemplate(entity) != null){ + serializedEntity.setTemplate(Utilities.stringify(CreatureUtils.getCreatureTemplate(entity))); + } rVal.serializedEntities.add(serializedEntity); } - if(ItemUtils.isItem(entity)){ + if(ItemUtils.isItem(entity) && ItemUtils.itemShouldBeSerialized(entity)){ EntitySerialization serializedEntity = new EntitySerialization(); serializedEntity.setPosition(EntityUtils.getPosition(entity)); serializedEntity.setRotation(EntityUtils.getRotation(entity)); @@ -75,8 +85,33 @@ public class ContentSerialization { for(EntitySerialization serializedEntity : serializedEntities){ switch(serializedEntity.getType()){ case CreatureUtils.ENTITY_TYPE_CREATURE: { - Entity creature = CreatureUtils.serverSpawnBasicCreature(realm, serializedEntity.getPosition(), serializedEntity.getSubtype(), null); + CreatureTemplate template = null; + if(serializedEntity.getTemplate() != null){ + template = Utilities.deserialize(serializedEntity.getTemplate(), CreatureTemplate.class); + } + + // + //Spawn the creature itself + Entity creature = CreatureUtils.serverSpawnBasicCreature(realm, serializedEntity.getPosition(), serializedEntity.getSubtype(), template); EntityUtils.getRotation(creature).set(serializedEntity.getRotation()); + + // + //now that creature has been spawned, need to create all attached items + if(template != null && template.getCreatureEquipData() != null && template.getCreatureEquipData().getSlots() != null){ + for(String equipSlotId : template.getCreatureEquipData().getSlots()){ + + //spawn the item in the world + EquippedItem itemDefinition = template.getCreatureEquipData().getSlotItem(equipSlotId); + Entity itemInWorld = ItemUtils.serverSpawnBasicItem(realm, serializedEntity.getPosition(), itemDefinition.getItemType()); + + //add the item to the creature's inventory + Entity itemInInventory = InventoryUtils.serverAttemptStoreItem(creature, EntityLookupUtils.getEntityById(itemInWorld.getId())); + + //equip the item to the slot defined in the template + ServerEquipState serverEquipState = ServerEquipState.getEquipState(creature); + serverEquipState.commandAttemptEquip(itemInInventory,serverEquipState.getEquipPoint(equipSlotId)); + } + } } break; case ItemUtils.ENTITY_TYPE_ITEM: { Entity item = ItemUtils.serverSpawnBasicItem(realm, serializedEntity.getPosition(), serializedEntity.getSubtype()); diff --git a/src/main/java/electrosphere/server/content/serialization/EntitySerialization.java b/src/main/java/electrosphere/server/content/serialization/EntitySerialization.java index cb03cdbd..8b44ca8a 100644 --- a/src/main/java/electrosphere/server/content/serialization/EntitySerialization.java +++ b/src/main/java/electrosphere/server/content/serialization/EntitySerialization.java @@ -8,9 +8,29 @@ import org.joml.Vector3d; */ public class EntitySerialization { + /** + * The type of the entity + */ int type; + + /** + * The subtype of the entity + */ String subtype; + + /** + * The (optional) template of the entity + */ + String template; + + /** + * The position of the entity + */ Vector3d position; + + /** + * The rotation of the entity + */ Quaterniond rotation; //type getter @@ -18,37 +38,74 @@ public class EntitySerialization { return type; } - //type setter + /** + * Sets the type of the entity + * @param type The type + */ public void setType(int type){ this.type = type; } - //subtype getter + /** + * Gets the subtype of the entity + * @return The subtype + */ public String getSubtype(){ return subtype; } - //subtype setter + /** + * Sets the subtype of the entity + * @param subtype The subtype + */ public void setSubtype(String subtype){ this.subtype = subtype; } - //position getter + /** + * Gets the (optional) template of the entity + * @return The template + */ + public String getTemplate(){ + return template; + } + + /** + * Sets the template of the entity + * @param template The template + */ + public void setTemplate(String template){ + this.template = template; + } + + /** + * Gets the position of the entity + * @return The position + */ public Vector3d getPosition(){ return position; } - //position setter + /** + * Sets the position of the entity + * @param position The position + */ public void setPosition(Vector3d position){ this.position = position; } - //rotation getter + /** + * Gets the rotation of the entity + * @return The rotation + */ public Quaterniond getRotation(){ return rotation; } - //rotation setter + /** + * Sets the rotation of the entity + * @param rotation The rotation + */ public void setRotation(Quaterniond rotation){ this.rotation = rotation; } diff --git a/src/main/java/electrosphere/server/datacell/ServerDataCell.java b/src/main/java/electrosphere/server/datacell/ServerDataCell.java index 78c362ec..ee163e5b 100644 --- a/src/main/java/electrosphere/server/datacell/ServerDataCell.java +++ b/src/main/java/electrosphere/server/datacell/ServerDataCell.java @@ -147,7 +147,7 @@ public class ServerDataCell { if(CreatureUtils.isCreature(entity)){ CreatureUtils.sendEntityToPlayer(player, entity); } - if(ItemUtils.isItem(entity)){ + if(ItemUtils.isItem(entity) && ItemUtils.itemShouldBeSentToClient(entity)){ ItemUtils.sendEntityToPlayer(player, entity); } if(ObjectUtils.isObject(entity)){