package electrosphere.entity.state.movement; import electrosphere.entity.state.collidable.Impulse; import electrosphere.entity.state.gravity.GravityTree; import electrosphere.entity.state.gravity.GravityUtils; import electrosphere.collision.dispatch.CollisionObject; import electrosphere.dynamics.RigidBody; import electrosphere.entity.types.camera.CameraEntityUtils; import electrosphere.entity.types.creature.CreatureUtils; import electrosphere.entity.Entity; import electrosphere.entity.EntityDataStrings; import electrosphere.entity.EntityUtils; import electrosphere.entity.state.AttackTree; import electrosphere.entity.state.BehaviorTree; import electrosphere.entity.state.AttackTree.AttackTreeState; import electrosphere.entity.state.movement.SprintTree.SprintTreeState; import electrosphere.game.collision.CollisionEngine; import electrosphere.game.collision.PhysicsUtils; import electrosphere.game.collision.collidable.Collidable; import electrosphere.main.Globals; import electrosphere.main.Main; import electrosphere.net.NetUtils; import electrosphere.net.parser.net.message.EntityMessage; import electrosphere.renderer.anim.Animation; import electrosphere.renderer.Model; import electrosphere.renderer.actor.Actor; import java.util.LinkedList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.joml.Quaternionf; import org.joml.Vector3d; import org.joml.Vector3f; /* Behavior tree for movement in an entity */ public class GroundMovementTree implements BehaviorTree { public static enum MovementTreeState { STARTUP, MOVE, SLOWDOWN, IDLE, } public static enum MovementRelativeFacing { FORWARD, LEFT, RIGHT, BACKWARD, FORWARD_LEFT, FORWARD_RIGHT, BACKWARD_LEFT, BACKWARD_RIGHT, } static final double STATE_DIFFERENCE_HARD_UPDATE_THRESHOLD = 1.0; static final double STATE_DIFFERENCE_SOFT_UPDATE_THRESHOLD = 0.2; static final double SOFT_UPDATE_MULTIPLIER = 0.1; String animationStartUp = Animation.ANIMATION_MOVEMENT_STARTUP; String animationMain = Animation.ANIMATION_MOVEMENT_MOVE; String animationSlowDown = Animation.ANIMATION_MOVEMENT_MOVE; String animationSprintStart = Animation.ANIMATION_SPRINT_STARTUP; String animationSprint = Animation.ANIMATION_SPRINT; String animationSprintWindDown = Animation.ANIMATION_SPRINT_WINDDOWN; MovementTreeState state; MovementRelativeFacing facing; SprintTree sprintTree; JumpTree jumpTree; FallTree fallTree; Entity parent; Collidable collidable; CopyOnWriteArrayList networkMessageQueue = new CopyOnWriteArrayList(); long lastUpdateTime = 0; public GroundMovementTree(Entity e, Collidable collidable){ state = MovementTreeState.IDLE; facing = MovementRelativeFacing.FORWARD; parent = e; this.collidable = collidable; } public MovementTreeState getState(){ return state; } public void start(MovementRelativeFacing facing){ if(canStartMoving()){ this.facing = facing; state = MovementTreeState.STARTUP; //if we aren't the server, alert the server we intend to walk forward if(!Globals.RUN_SERVER){ Vector3d position = EntityUtils.getPosition(parent); Quaternionf rotation = EntityUtils.getRotation(parent); Vector3d facingVector = CreatureUtils.getFacingVector(parent); float velocity = CreatureUtils.getVelocity(parent); Globals.clientConnection.queueOutgoingMessage( EntityMessage.constructmoveUpdateMessage( parent.getId(), Main.getCurrentFrame(), position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, velocity, 0 //magic number corresponding to state startup ) ); } } } public void interrupt(){ state = MovementTreeState.IDLE; CreatureUtils.setVelocity(parent, 0); } public void slowdown(){ state = MovementTreeState.SLOWDOWN; //if we aren't the server, alert the server we intend to slow down if(!Globals.RUN_SERVER){ Vector3d position = EntityUtils.getPosition(parent); Quaternionf rotation = EntityUtils.getRotation(parent); Vector3d facingVector = CreatureUtils.getFacingVector(parent); float velocity = CreatureUtils.getVelocity(parent); Globals.clientConnection.queueOutgoingMessage( EntityMessage.constructmoveUpdateMessage( parent.getId(), Main.getCurrentFrame(), position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, velocity, 2 //magic number corresponding to state slowdown ) ); } } public void simulate(){ float velocity = CreatureUtils.getVelocity(parent); float acceleration = CreatureUtils.getAcceleration(parent); float maxNaturalVelocity = sprintTree != null && sprintTree.state == SprintTreeState.SPRINTING ? sprintTree.maxVelocity : CreatureUtils.getMaxNaturalVelocity(parent); Actor entityActor = EntityUtils.getActor(parent); // Model entityModel = Globals.assetManager.fetchModel(EntityUtils.getEntityModelPath(parent)); Vector3d position = EntityUtils.getPosition(parent); Vector3d facingVector = CreatureUtils.getFacingVector(parent); Vector3d movementVector = new Vector3d(facingVector); switch(facing){ case FORWARD: movementVector.normalize(); break; case LEFT: movementVector.rotateY((float)(90 * Math.PI / 180)).normalize(); break; case RIGHT: movementVector.rotateY((float)(-90 * Math.PI / 180)).normalize(); break; case BACKWARD: movementVector.x = -movementVector.x; movementVector.z = -movementVector.z; movementVector.normalize(); break; case FORWARD_LEFT: movementVector.rotateY((float)(45 * Math.PI / 180)).normalize(); break; case FORWARD_RIGHT: movementVector.rotateY((float)(-45 * Math.PI / 180)).normalize(); break; case BACKWARD_LEFT: movementVector.rotateY((float)(135 * Math.PI / 180)).normalize(); break; case BACKWARD_RIGHT: movementVector.rotateY((float)(-135 * Math.PI / 180)).normalize(); break; } // float movementYaw = CameraEntityUtils.getCameraYaw(Globals.playerCamera); Quaternionf movementQuaternion = new Quaternionf().rotationTo(new Vector3f(0,0,1), new Vector3f((float)facingVector.x,0,(float)facingVector.z)).normalize(); Quaternionf rotation = EntityUtils.getRotation(parent); //parse attached network messages for(EntityMessage message : networkMessageQueue){ networkMessageQueue.remove(message); long updateTime = message.gettime(); // System.out.println("MOVE to " + message.getX() + " " + message.getY() + " " + message.getZ()); switch(message.getMessageSubtype()){ case MOVE: if(Globals.RUN_CLIENT){ position.set(message.getpositionX(), message.getpositionY(), message.getpositionZ()); } break; case SETFACING: break; case MOVEUPDATE: if(updateTime > lastUpdateTime){ lastUpdateTime = updateTime; switch(message.gettreeState()){ case 0: state = MovementTreeState.STARTUP; // System.out.println("Set state STARTUP"); GravityUtils.attemptActivateGravity(parent); break; case 1: state = MovementTreeState.MOVE; // System.out.println("Set state MOVE"); GravityUtils.attemptActivateGravity(parent); break; case 2: state = MovementTreeState.SLOWDOWN; // System.out.println("Set state SLOWDOWN"); GravityUtils.attemptActivateGravity(parent); break; case 3: state = MovementTreeState.IDLE; // System.out.println("Set state IDLE"); break; } // System.out.println(EntityUtils.getEntityPosition(parent)); // System.out.println(message.getpositionX() + " " + message.getpositionY() + " " + message.getpositionZ()); //this should only fire on the client, we don't want the server snap updating due to client position reporting if(!Globals.RUN_SERVER){ if(position.distance(message.getpositionX(),message.getpositionY(),message.getpositionZ()) > STATE_DIFFERENCE_HARD_UPDATE_THRESHOLD){ EntityUtils.getPosition(parent).set(message.getpositionX(),message.getpositionY(),message.getpositionZ()); } else if(position.distance(message.getpositionX(),message.getpositionY(),message.getpositionZ()) > STATE_DIFFERENCE_SOFT_UPDATE_THRESHOLD){ EntityUtils.getPosition(parent).add(new Vector3d(message.getpositionX(),message.getpositionY(),message.getpositionZ()).mul(SOFT_UPDATE_MULTIPLIER)); } } //we want to always update the server facing vector with where the client says they're facing CreatureUtils.setFacingVector(parent, new Vector3d(message.getrotationX(),message.getrotationY(),message.getrotationZ())); break; } break; default: break; } } // System.out.println(movementVector + " " + velocity * Main.deltaTime); //state machine switch(state){ case STARTUP: if(entityActor != null){ String animationToPlay = determineCorrectAnimation(); if( !entityActor.isPlayingAnimation() || !entityActor.isPlayingAnimation(animationToPlay) && (jumpTree == null || !jumpTree.isJumping()) && (fallTree == null || !fallTree.isFalling()) ){ entityActor.playAnimation(animationToPlay,1); entityActor.incrementAnimationTime(0.0001); } } //run startup code velocity = velocity + acceleration * Main.deltaFrames; //check if can transition state if(velocity >= maxNaturalVelocity){ velocity = maxNaturalVelocity; state = MovementTreeState.MOVE; } CreatureUtils.setVelocity(parent, velocity); // body.applyCentralForce(PhysicsUtils.jomlToVecmathVector3f(new Vector3f(movementVector.x,0,movementVector.z).normalize().mul(velocity))); // EntityUtils.getRotation(parent).set(movementQuaternion); // //move the entity // newPosition = new Vector3d(position).add(new Vector3d(movementVector).mul(velocity).mul(Main.deltaTime)); // //check/update if collision // if(!Globals.collisionEngine.checkCanOccupyPosition(Globals.commonWorldData, parent, newPosition)){ // newPosition = Globals.collisionEngine.suggestMovementPosition(Globals.commonWorldData, parent, newPosition); // } // //actually update collidable.addImpulse(new Impulse(new Vector3d(movementVector), new Vector3d(0,0,0), new Vector3d(0,0,0), velocity * Main.deltaFrames, "movement")); // position.set(newPosition); rotation.set(movementQuaternion); GravityUtils.attemptActivateGravity(parent); if(Globals.RUN_SERVER){ Globals.dataCellManager.sendNetworkMessageToChunk( EntityMessage.constructmoveUpdateMessage( parent.getId(), Main.getCurrentFrame(), position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, velocity, 0 ), Globals.serverWorldData.convertRealToChunkSpace(position.x), Globals.serverWorldData.convertRealToChunkSpace(position.z) ); } break; case MOVE: //check if can restart animation //if yes, restart animation if(entityActor != null){ String animationToPlay = determineCorrectAnimation(); if( !entityActor.isPlayingAnimation() || !entityActor.isPlayingAnimation(animationToPlay) && (jumpTree == null || !jumpTree.isJumping()) && (fallTree == null || !fallTree.isFalling()) ){ entityActor.playAnimation(animationToPlay,1); entityActor.incrementAnimationTime(0.0001); } } if(velocity != maxNaturalVelocity){ velocity = maxNaturalVelocity; CreatureUtils.setVelocity(parent, velocity); } // body.applyCentralForce(PhysicsUtils.jomlToVecmathVector3f(force)); // EntityUtils.getRotation(parent).set(movementQuaternion); //check if can move forward (collision engine) //if can, move forward by entity movement stats // newPosition = new Vector3d(position).add(new Vector3d(movementVector).mul(velocity).mul(Main.deltaTime)); // if(!Globals.collisionEngine.checkCanOccupyPosition(Globals.commonWorldData, parent, newPosition)){ // newPosition = Globals.collisionEngine.suggestMovementPosition(Globals.commonWorldData, parent, newPosition); // } collidable.addImpulse(new Impulse(new Vector3d(movementVector), new Vector3d(0,0,0), new Vector3d(0,0,0), velocity * Main.deltaFrames, "movement")); // position.set(newPosition); rotation.set(movementQuaternion); GravityUtils.attemptActivateGravity(parent); if(Globals.RUN_SERVER){ Globals.dataCellManager.sendNetworkMessageToChunk( EntityMessage.constructmoveUpdateMessage( parent.getId(), Main.getCurrentFrame(), position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, velocity, 1 ), Globals.serverWorldData.convertRealToChunkSpace(position.x), Globals.serverWorldData.convertRealToChunkSpace(position.z) ); } break; case SLOWDOWN: //run slowdown code if(entityActor != null){ String animationToPlay = determineCorrectAnimation(); if( !entityActor.isPlayingAnimation() || !entityActor.isPlayingAnimation(animationToPlay) && (jumpTree == null || !jumpTree.isJumping()) && (fallTree == null || !fallTree.isFalling()) ){ entityActor.playAnimation(animationToPlay,1); entityActor.incrementAnimationTime(0.0001); } } //velocity stuff velocity = velocity - acceleration * Main.deltaFrames; //check if can transition state if(velocity <= 0){ velocity = 0; state = MovementTreeState.IDLE; if(entityActor != null){ String animationToPlay = determineCorrectAnimation(); if(entityActor.isPlayingAnimation() && entityActor.isPlayingAnimation(animationToPlay)){ entityActor.stopAnimation(animationToPlay); } } } CreatureUtils.setVelocity(parent, velocity); // body.applyCentralForce(PhysicsUtils.jomlToVecmathVector3f(new Vector3f(movementVector).mul(-1.0f).normalize().mul(velocity))); // EntityUtils.getRotation(parent).rotationTo(new Vector3f(0,0,1), new Vector3f((float)movementVector.x,(float)movementVector.y,(float)movementVector.z)); //move the entity // newPosition = new Vector3d(position).add(new Vector3d(movementVector).mul(velocity).mul(Main.deltaTime)); // if(!Globals.collisionEngine.checkCanOccupyPosition(Globals.commonWorldData, parent, newPosition)){ // newPosition = Globals.collisionEngine.suggestMovementPosition(Globals.commonWorldData, parent, newPosition); // } collidable.addImpulse(new Impulse(new Vector3d(movementVector), new Vector3d(0,0,0), new Vector3d(0,0,0), velocity * Main.deltaFrames, "movement")); // position.set(newPosition); rotation.set(movementQuaternion); GravityUtils.attemptActivateGravity(parent); if(Globals.RUN_SERVER){ Globals.dataCellManager.sendNetworkMessageToChunk( EntityMessage.constructmoveUpdateMessage( parent.getId(), Main.getCurrentFrame(), position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, velocity, 2 ), Globals.serverWorldData.convertRealToChunkSpace(position.x), Globals.serverWorldData.convertRealToChunkSpace(position.z) ); } break; case IDLE: // body.clearForces(); // if(Globals.collisionEngine.gravityCheck(Globals.commonWorldData, parent)){ // position.set(Globals.collisionEngine.suggestMovementPosition(Globals.commonWorldData,parent,new Vector3f(position.x,position.y - 9.8f,position.z))); // } // position.set(new Vector3f(position.x,position.y - 0.08f,position.z)); // bodyTransformMatrix = new javax.vecmath.Matrix4f(PhysicsUtils.jomlToVecmathQuaternionf(rotation),PhysicsUtils.jomlToVecmathVector3f(position),1.0f); // body.setWorldTransform(new com.bulletphysics.linearmath.Transform(bodyTransformMatrix)); break; } } public void addNetworkMessage(EntityMessage networkMessage) { networkMessageQueue.add(networkMessage); } public boolean canStartMoving(){ boolean rVal = true; if(parent.containsKey(EntityDataStrings.ATTACK_TREE) && ((AttackTree)parent.getData(EntityDataStrings.ATTACK_TREE)).getState() != AttackTreeState.IDLE){ rVal = false; } return rVal; } public void setAnimationStartUp(String animationStartUp) { this.animationStartUp = animationStartUp; } public void setAnimationMain(String animationMain) { this.animationMain = animationMain; } public void setAnimationSlowDown(String animationSlowDown) { this.animationSlowDown = animationSlowDown; } public void setAnimationSprintStartUp(String animationSprintStartUp){ this.animationSprintStart = animationSprintStartUp; } public void setAnimationSprint(String animationSprint){ this.animationSprint = animationSprint; } public void setAnimationSprintWindDown(String animationSprintWindDown){ this.animationSprintWindDown = animationSprintWindDown; } public void setSprintTree(SprintTree sprintTree){ this.sprintTree = sprintTree; } public void setJumpTree(JumpTree jumpTree){ this.jumpTree = jumpTree; } public void setFallTree(FallTree fallTree){ this.fallTree = fallTree; } public MovementRelativeFacing getFacing(){ return facing; } public String determineCorrectAnimation(){ String rVal = ""; if(sprintTree != null){ switch(sprintTree.state){ case SPRINTING: switch(state){ case IDLE: break; case STARTUP: rVal = animationSprintStart; break; case MOVE: rVal = animationSprint; break; case SLOWDOWN: rVal = animationSprintWindDown; break; } break; case NOT_SPRINTING: switch(state){ case IDLE: break; case STARTUP: switch(facing){ case FORWARD: rVal = animationStartUp; break; case BACKWARD: rVal = animationStartUp; break; case LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; case FORWARD_LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case FORWARD_RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; case BACKWARD_LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case BACKWARD_RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; } break; case MOVE: switch(facing){ case FORWARD: rVal = animationMain; break; case BACKWARD: rVal = animationMain; break; case LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; case FORWARD_LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case FORWARD_RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; case BACKWARD_LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case BACKWARD_RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; } break; case SLOWDOWN: switch(facing){ case FORWARD: rVal = animationSlowDown; break; case BACKWARD: rVal = animationSlowDown; break; case LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; case FORWARD_LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case FORWARD_RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; case BACKWARD_LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case BACKWARD_RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; } break; } break; } } else { switch(state){ case IDLE: break; case STARTUP: switch(facing){ case FORWARD: rVal = animationStartUp; break; case BACKWARD: rVal = animationStartUp; break; case LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; case FORWARD_LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case FORWARD_RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; case BACKWARD_LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case BACKWARD_RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; } break; case MOVE: switch(facing){ case FORWARD: rVal = animationMain; break; case BACKWARD: rVal = animationMain; break; case LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; case FORWARD_LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case FORWARD_RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; case BACKWARD_LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case BACKWARD_RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; } break; case SLOWDOWN: switch(facing){ case FORWARD: rVal = animationSlowDown; break; case BACKWARD: rVal = animationSlowDown; break; case LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; case FORWARD_LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case FORWARD_RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; case BACKWARD_LEFT: rVal = Animation.ANIMATION_WALK_LEFT; break; case BACKWARD_RIGHT: rVal = Animation.ANIMATION_WALK_RIGHT; break; } break; } } return rVal; } }