diff --git a/assets/Data/entity/creatures.json b/assets/Data/entity/creatures.json index ad6e8e7b..e8c54dcd 100644 --- a/assets/Data/entity/creatures.json +++ b/assets/Data/entity/creatures.json @@ -6,7 +6,8 @@ "files" : [ "Data/entity/creatures/human.json", "Data/entity/creatures/skeleton.json", - "Data/entity/creatures/editor.json" + "Data/entity/creatures/editor.json", + "Data/entity/creatures/animals.json" ] } diff --git a/assets/Data/entity/creatures/animals.json b/assets/Data/entity/creatures/animals.json index 399a6ad1..1a669d92 100644 --- a/assets/Data/entity/creatures/animals.json +++ b/assets/Data/entity/creatures/animals.json @@ -38,6 +38,80 @@ "onDamageIFrames" : 30 }, "modelPath" : "Models/deer1.fbx" + }, + { + "id" : "Cat", + "displayName" : "Cat", + "hitboxes" : [ + { + "type": "hurt", + "bone": "Bone", + "radius": 0.04 + } + ], + "tokens" : [ + "GRAVITY" + ], + "movementSystems" : [ + { + "type" : "GROUND", + "acceleration" : 300.0, + "maxVelocity" : 10.0, + "strafeMultiplier" : 1.0, + "backpedalMultiplier" : 0.5, + "footstepFirstAudioOffset" : 0.2, + "footstepSecondAudioOffset" : 0.6, + "animationStartup" : { + "nameThirdPerson" : "Walk", + "priorityCategory" : "CORE_MOVEMENT" + }, + "animationLoop" : { + "nameThirdPerson" : "Walk", + "priorityCategory" : "CORE_MOVEMENT" + }, + "animationWindDown" : { + "nameThirdPerson" : "Walk", + "priorityCategory" : "CORE_MOVEMENT" + } + } + ], + "collidable" : { + "type" : "CAPSULE", + "dimension1" : 0.35, + "dimension2" : 0.7, + "dimension3" : 0.35, + "linearFriction": 0.001, + "mass": 0.3, + "rotX": 0, + "rotY": 0, + "rotZ": 0, + "rotW": 1, + "offsetX" : 0, + "offsetY" : 0.7, + "offsetZ" : 0, + "angularlyStatic" : true + }, + "healthSystem" : { + "maxHealth" : 100, + "onDamageIFrames" : 30 + }, + "aiTrees" : [ + { + "name" : "Wander" + } + ], + "graphicsTemplate": { + "model": { + "idleData": { + "animation": { + "nameFirstPerson" : "Pose", + "nameThirdPerson" : "Pose", + "priorityCategory" : "IDLE" + } + }, + "path" : "Models/creatures/cat1/cat1.glb" + } + } } ], "files" : [] diff --git a/assets/Models/creatures/cat1/Cat.png b/assets/Models/creatures/cat1/Cat.png new file mode 100644 index 00000000..3b9558c2 Binary files /dev/null and b/assets/Models/creatures/cat1/Cat.png differ diff --git a/assets/Models/creatures/cat1/cat1.glb b/assets/Models/creatures/cat1/cat1.glb new file mode 100644 index 00000000..c8ece150 Binary files /dev/null and b/assets/Models/creatures/cat1/cat1.glb differ diff --git a/buildNumber.properties b/buildNumber.properties index 06c2079d..e3b1a422 100644 --- a/buildNumber.properties +++ b/buildNumber.properties @@ -1,3 +1,3 @@ #maven.buildNumber.plugin properties file -#Wed Jun 04 22:45:11 EDT 2025 -buildNumber=645 +#Thu Jul 03 09:59:19 EDT 2025 +buildNumber=646 diff --git a/src/main/java/electrosphere/client/ui/menu/script/ScriptLevelEditorUtils.java b/src/main/java/electrosphere/client/ui/menu/script/ScriptLevelEditorUtils.java index 003f00e5..6a4ad9ea 100644 --- a/src/main/java/electrosphere/client/ui/menu/script/ScriptLevelEditorUtils.java +++ b/src/main/java/electrosphere/client/ui/menu/script/ScriptLevelEditorUtils.java @@ -43,6 +43,9 @@ public class ScriptLevelEditorUtils { cursorPos = new Vector3d(centerPos).add(new Vector3d(eyePos).normalize().mul(-CollisionEngine.DEFAULT_INTERACT_DISTANCE)); } cursorPos = cursorPos.add(cursorVerticalOffset); + if(cursorPos.x < 0 || cursorPos.y < 0 || cursorPos.z < 0){ + return; + } realm.getServerWorldData().clampWithinBounds(cursorPos); CreatureUtils.serverSpawnBasicCreature(realm, cursorPos, Globals.clientState.selectedSpawntype.getId(), null); } else if(Globals.clientState.selectedSpawntype instanceof Item){ @@ -56,6 +59,9 @@ public class ScriptLevelEditorUtils { cursorPos = new Vector3d(centerPos).add(new Vector3d(eyePos).normalize().mul(-CollisionEngine.DEFAULT_INTERACT_DISTANCE)); } cursorPos = cursorPos.add(cursorVerticalOffset); + if(cursorPos.x < 0 || cursorPos.y < 0 || cursorPos.z < 0){ + return; + } ItemUtils.serverSpawnBasicItem(realm, cursorPos, Globals.clientState.selectedSpawntype.getId()); } else if(Globals.clientState.selectedSpawntype instanceof FoliageType){ LoggerInterface.loggerEngine.INFO("spawn " + Globals.clientState.selectedSpawntype.getId() + "!"); @@ -68,6 +74,9 @@ public class ScriptLevelEditorUtils { cursorPos = new Vector3d(centerPos).add(new Vector3d(eyePos).normalize().mul(-CollisionEngine.DEFAULT_INTERACT_DISTANCE)); } cursorPos = cursorPos.add(cursorVerticalOffset); + if(cursorPos.x < 0 || cursorPos.y < 0 || cursorPos.z < 0){ + return; + } FoliageUtils.serverSpawnTreeFoliage(realm, cursorPos, Globals.clientState.selectedSpawntype.getId()); } else { LoggerInterface.loggerEngine.INFO("spawn " + Globals.clientState.selectedSpawntype.getId() + "!"); @@ -80,6 +89,9 @@ public class ScriptLevelEditorUtils { cursorPos = new Vector3d(centerPos).add(new Vector3d(eyePos).normalize().mul(-CollisionEngine.DEFAULT_INTERACT_DISTANCE)); } cursorPos = cursorPos.add(cursorVerticalOffset); + if(cursorPos.x < 0 || cursorPos.y < 0 || cursorPos.z < 0){ + return; + } CommonEntityUtils.serverSpawnBasicObject(realm, cursorPos, Globals.clientState.selectedSpawntype.getId()); } } diff --git a/src/main/java/electrosphere/data/entity/creature/ai/AITreeDataSerializer.java b/src/main/java/electrosphere/data/entity/creature/ai/AITreeDataSerializer.java index 2a424809..c80dc2bc 100644 --- a/src/main/java/electrosphere/data/entity/creature/ai/AITreeDataSerializer.java +++ b/src/main/java/electrosphere/data/entity/creature/ai/AITreeDataSerializer.java @@ -11,6 +11,7 @@ import electrosphere.logger.LoggerInterface; import electrosphere.server.ai.trees.test.BlockerAITree; import electrosphere.server.ai.trees.character.StandardCharacterTree; import electrosphere.server.ai.trees.creature.AttackerAITree; +import electrosphere.server.ai.trees.creature.WanderTree; import electrosphere.server.ai.trees.hierarchy.MaslowTree; /** @@ -34,6 +35,9 @@ public class AITreeDataSerializer implements JsonDeserializer { case StandardCharacterTree.TREE_NAME: { return context.deserialize(json, StandardCharacterTreeData.class); } + case WanderTree.TREE_NAME: { + return context.deserialize(json, WanderTreeData.class); + } } LoggerInterface.loggerEngine.ERROR(new IllegalArgumentException("JSON Object provided to AITreeDataSerializer that cannot deserialize into a tree data type cleanly " + json.getAsJsonObject().get("name").getAsString())); diff --git a/src/main/java/electrosphere/data/entity/creature/ai/WanderTreeData.java b/src/main/java/electrosphere/data/entity/creature/ai/WanderTreeData.java new file mode 100644 index 00000000..164425f2 --- /dev/null +++ b/src/main/java/electrosphere/data/entity/creature/ai/WanderTreeData.java @@ -0,0 +1,20 @@ +package electrosphere.data.entity.creature.ai; + +import electrosphere.server.ai.trees.creature.WanderTree; + +/** + * Tree data for controlling a wandering creature tree + */ +public class WanderTreeData implements AITreeData { + + /** + * The name of the tree + */ + String name; + + @Override + public String getName() { + return WanderTree.TREE_NAME; + } + +} diff --git a/src/main/java/electrosphere/renderer/actor/Actor.java b/src/main/java/electrosphere/renderer/actor/Actor.java index 189c4352..5cc12689 100644 --- a/src/main/java/electrosphere/renderer/actor/Actor.java +++ b/src/main/java/electrosphere/renderer/actor/Actor.java @@ -226,7 +226,7 @@ public class Actor { this.lodLevel == Actor.LOD_LEVEL_STATIC || //actor doesn't have anything complicated render-wise (animations, custom textures, etc) ( - this.animationData.isPlayingAnimation() && + !this.animationData.isPlayingAnimation() && this.meshMask.getBlockedMeshes().size() == 0 && this.textureMap == null && this.uniformMap.isEmpty() && diff --git a/src/main/java/electrosphere/server/ai/AI.java b/src/main/java/electrosphere/server/ai/AI.java index 07432c84..93b5fe60 100644 --- a/src/main/java/electrosphere/server/ai/AI.java +++ b/src/main/java/electrosphere/server/ai/AI.java @@ -6,6 +6,7 @@ import electrosphere.data.entity.creature.ai.AITreeData; import electrosphere.data.entity.creature.ai.AttackerTreeData; import electrosphere.data.entity.creature.ai.BlockerTreeData; import electrosphere.data.entity.creature.ai.StandardCharacterTreeData; +import electrosphere.data.entity.creature.ai.WanderTreeData; import electrosphere.entity.Entity; import electrosphere.entity.EntityDataStrings; import electrosphere.entity.state.movement.groundmove.ServerGroundMovementTree; @@ -15,6 +16,7 @@ import electrosphere.server.ai.blackboard.Blackboard; import electrosphere.server.ai.nodes.AITreeNode; import electrosphere.server.ai.trees.character.StandardCharacterTree; import electrosphere.server.ai.trees.creature.AttackerAITree; +import electrosphere.server.ai.trees.creature.WanderTree; import electrosphere.server.ai.trees.hierarchy.MaslowTree; import electrosphere.server.ai.trees.test.BlockerAITree; @@ -72,6 +74,9 @@ public class AI { case StandardCharacterTree.TREE_NAME: { rVal.rootNode = StandardCharacterTree.create((StandardCharacterTreeData) aiData); } break; + case WanderTree.TREE_NAME: { + rVal.rootNode = WanderTree.create((WanderTreeData) aiData); + } break; default: { LoggerInterface.loggerEngine.ERROR(new IllegalArgumentException("Trying to construct ai tree with undefined data type! " + aiData.getName())); } break; diff --git a/src/main/java/electrosphere/server/ai/nodes/plan/TargetRandomPositionNode.java b/src/main/java/electrosphere/server/ai/nodes/plan/TargetRandomPositionNode.java new file mode 100644 index 00000000..072e84bd --- /dev/null +++ b/src/main/java/electrosphere/server/ai/nodes/plan/TargetRandomPositionNode.java @@ -0,0 +1,78 @@ +package electrosphere.server.ai.nodes.plan; + +import java.util.Random; + +import org.joml.Vector3d; + +import electrosphere.entity.Entity; +import electrosphere.entity.EntityUtils; +import electrosphere.server.ai.blackboard.Blackboard; +import electrosphere.server.ai.blackboard.BlackboardKeys; +import electrosphere.server.ai.nodes.AITreeNode; + +/** + * Sets the move-to target position to a random nearby point + */ +public class TargetRandomPositionNode implements AITreeNode { + + /** + * The key to lookup the target under + */ + String targetKey; + + double radius = 5; + + /** + * constructor + * @param targetKey The key to lookup the target under + */ + public TargetRandomPositionNode(String targetKey){ + this.targetKey = targetKey; + } + + @Override + public AITreeNodeResult evaluate(Entity entity, Blackboard blackboard){ + Object targetRaw = blackboard.get(this.targetKey); + if(targetRaw == null){ + Vector3d targetPos = null; + Random rand = new Random(); + targetPos = new Vector3d(EntityUtils.getPosition(entity)); + targetPos.add( + rand.nextFloat() * (radius * 2) - radius, + 0, + rand.nextFloat() * (radius * 2) - radius + ); + blackboard.put(targetKey, targetPos); + TargetPositionNode.setMoveToTarget(blackboard, targetPos); + } + return AITreeNodeResult.SUCCESS; + } + + /** + * Sets the move-to target of the blackboard + * @param blackboard The blackboard + * @param position The target position + */ + public static void setMoveToTarget(Blackboard blackboard, Vector3d position){ + blackboard.put(BlackboardKeys.MOVE_TO_TARGET, position); + } + + /** + * Gets the move-to target of the blackboard + * @param blackboard The blackboard + * @return The move-to target if it exists, null otherwise + */ + public static Vector3d getMoveToTarget(Blackboard blackboard){ + return (Vector3d)blackboard.get(BlackboardKeys.MOVE_TO_TARGET); + } + + /** + * Checks if the blackboard has a move to target + * @param blackboard the blackboard + * @return true if it has a move-to target, false otherwise + */ + public static boolean hasMoveToTarget(Blackboard blackboard){ + return blackboard.has(BlackboardKeys.MOVE_TO_TARGET); + } + +} diff --git a/src/main/java/electrosphere/server/ai/services/NearbyEntityService.java b/src/main/java/electrosphere/server/ai/services/NearbyEntityService.java index 824a45e9..40d7f17c 100644 --- a/src/main/java/electrosphere/server/ai/services/NearbyEntityService.java +++ b/src/main/java/electrosphere/server/ai/services/NearbyEntityService.java @@ -25,13 +25,16 @@ public class NearbyEntityService implements AIService { @Override public void exec(){ for(AI ai : Globals.serverState.aiManager.getAIList()){ - if(!ai.shouldExecute()){ + if(!ai.shouldExecute()){ continue; } - Entity entity = ai.getParent(); + Entity entity = ai.getParent(); Realm realm = Globals.serverState.realmManager.getEntityRealm(entity); if(realm != null){ Vector3d position = EntityUtils.getPosition(entity); + if(position.x < 0 || position.y < 0 || position.z < 0){ + continue; + } Collection nearbyEntities = realm.getDataCellManager().entityLookup(position, NearbyEntityService.SEARCH_DIST); NearbyEntityService.setNearbyEntities(ai.getBlackboard(), nearbyEntities); } diff --git a/src/main/java/electrosphere/server/ai/trees/creature/WanderTree.java b/src/main/java/electrosphere/server/ai/trees/creature/WanderTree.java new file mode 100644 index 00000000..0675aacf --- /dev/null +++ b/src/main/java/electrosphere/server/ai/trees/creature/WanderTree.java @@ -0,0 +1,60 @@ +package electrosphere.server.ai.trees.creature; + +import electrosphere.data.entity.creature.ai.WanderTreeData; +import electrosphere.entity.state.movement.groundmove.ClientGroundMovementTree.MovementRelativeFacing; +import electrosphere.server.ai.blackboard.BlackboardKeys; +import electrosphere.server.ai.nodes.AITreeNode; +import electrosphere.server.ai.nodes.actions.move.FaceTargetNode; +import electrosphere.server.ai.nodes.actions.move.MoveStartNode; +import electrosphere.server.ai.nodes.actions.move.MoveStopNode; +import electrosphere.server.ai.nodes.meta.DataDeleteNode; +import electrosphere.server.ai.nodes.meta.collections.RandomizerNode; +import electrosphere.server.ai.nodes.meta.collections.SequenceNode; +import electrosphere.server.ai.nodes.meta.debug.PublishStatusNode; +import electrosphere.server.ai.nodes.meta.decorators.SucceederNode; +import electrosphere.server.ai.nodes.meta.decorators.TimerNode; +import electrosphere.server.ai.nodes.plan.TargetRandomPositionNode; + +/** + * Wanders around aimlessly + */ +public class WanderTree { + + /** + * Name of the tree + */ + public static final String TREE_NAME = "Wander"; + + /** + * Creates an wander ai tree + * @return The root node of the tree + */ + public static AITreeNode create(WanderTreeData wanderTreeData){ + return new SequenceNode( + "WanderTree", + //select action to perform + new RandomizerNode( + + //wait + new SequenceNode( + "MeleeAITree", + new PublishStatusNode("Waiting"), + new SucceederNode(new MoveStopNode()), + new DataDeleteNode(BlackboardKeys.POINT_TARGET), + new TimerNode(new SucceederNode(null), 1200) + ), + + //move towards a random position + new SequenceNode( + "WanderMove", + new PublishStatusNode("Move into wander target range"), + new TargetRandomPositionNode(BlackboardKeys.POINT_TARGET), + new FaceTargetNode(BlackboardKeys.MOVE_TO_TARGET), + new SucceederNode(new MoveStartNode(MovementRelativeFacing.FORWARD)), + new TimerNode(new SucceederNode(null), 600) + ) + ) + ); + } + +}