package electrosphere.entity.state.movement; import electrosphere.entity.state.collidable.Impulse; import electrosphere.entity.state.gravity.GravityUtils; import electrosphere.collision.CollisionEngine; import electrosphere.collision.PhysicsUtils; import electrosphere.collision.collidable.Collidable; import electrosphere.engine.Globals; import electrosphere.engine.Main; 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.BehaviorTree; import electrosphere.entity.state.attack.ServerAttackTree; import electrosphere.entity.state.attack.ServerAttackTree.AttackTreeState; import electrosphere.entity.state.movement.ServerSprintTree.SprintTreeState; import electrosphere.net.NetUtils; import electrosphere.net.parser.net.message.EntityMessage; import electrosphere.renderer.anim.Animation; import electrosphere.server.datacell.utils.DataCellSearchUtils; import electrosphere.server.poseactor.PoseActor; 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.Quaterniond; import org.joml.Quaternionf; import org.joml.Vector3d; import org.joml.Vector3f; /* Behavior tree for movement in an entity */ public class ServerGroundMovementTree 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; ServerSprintTree sprintTree; ServerJumpTree jumpTree; ServerFallTree fallTree; Entity parent; Collidable collidable; CopyOnWriteArrayList networkMessageQueue = new CopyOnWriteArrayList(); long lastUpdateTime = 0; public ServerGroundMovementTree(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 Vector3d position = EntityUtils.getPosition(parent); Quaterniond rotation = EntityUtils.getRotation(parent); Vector3d facingVector = CreatureUtils.getFacingVector(parent); float velocity = CreatureUtils.getVelocity(parent); DataCellSearchUtils.getEntityDataCell(parent).broadcastNetworkMessage( 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 Vector3d position = EntityUtils.getPosition(parent); Quaterniond rotation = EntityUtils.getRotation(parent); Vector3d facingVector = CreatureUtils.getFacingVector(parent); float velocity = CreatureUtils.getVelocity(parent); DataCellSearchUtils.getEntityDataCell(parent).broadcastNetworkMessage( 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 deltaTime){ float velocity = CreatureUtils.getVelocity(parent); float acceleration = CreatureUtils.getAcceleration(parent); float maxNaturalVelocity = sprintTree != null && sprintTree.state == SprintTreeState.SPRINTING ? sprintTree.maxVelocity : CreatureUtils.getMaxNaturalVelocity(parent); PoseActor entityPoseActor = EntityUtils.getPoseActor(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); Quaterniond movementQuaternion = new Quaterniond().rotationTo(new Vector3d(0,0,1), new Vector3d(facingVector.x,0,facingVector.z)).normalize(); Quaterniond 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: { } 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.serverAttemptActivateGravity(parent); break; case 1: state = MovementTreeState.MOVE; // System.out.println("Set state MOVE"); GravityUtils.serverAttemptActivateGravity(parent); break; case 2: state = MovementTreeState.SLOWDOWN; // System.out.println("Set state SLOWDOWN"); GravityUtils.serverAttemptActivateGravity(parent); break; case 3: state = MovementTreeState.IDLE; // System.out.println("Set state IDLE"); break; } //we want to always update the server facing vector with where the client says they're facing EntityUtils.getRotation(parent).set(message.getrotationX(),message.getrotationY(),message.getrotationZ(),message.getrotationW()); CreatureUtils.setFacingVector(parent, new Vector3d(0,0,1).rotate(EntityUtils.getRotation(parent))); break; } } break; default: break; } } // System.out.println(movementVector + " " + velocity * Main.deltaTime); //state machine switch(state){ case STARTUP: if(entityPoseActor != null){ String animationToPlay = determineCorrectAnimation(); if( !entityPoseActor.isPlayingAnimation() || !entityPoseActor.isPlayingAnimation(animationToPlay) && (jumpTree == null || !jumpTree.isJumping()) && (fallTree == null || !fallTree.isFalling()) ){ entityPoseActor.playAnimation(animationToPlay,1); entityPoseActor.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); // //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.serverAttemptActivateGravity(parent); DataCellSearchUtils.getEntityDataCell(parent).broadcastNetworkMessage( EntityMessage.constructmoveUpdateMessage( parent.getId(), Main.getCurrentFrame(), position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, velocity, 0 ) ); break; case MOVE: //check if can restart animation //if yes, restart animation if(entityPoseActor != null){ String animationToPlay = determineCorrectAnimation(); if( !entityPoseActor.isPlayingAnimation() || !entityPoseActor.isPlayingAnimation(animationToPlay) && (jumpTree == null || !jumpTree.isJumping()) && (fallTree == null || !fallTree.isFalling()) ){ entityPoseActor.playAnimation(animationToPlay,1); entityPoseActor.incrementAnimationTime(0.0001); } } if(velocity != maxNaturalVelocity){ velocity = maxNaturalVelocity; CreatureUtils.setVelocity(parent, velocity); } 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.serverAttemptActivateGravity(parent); DataCellSearchUtils.getEntityDataCell(parent).broadcastNetworkMessage( EntityMessage.constructmoveUpdateMessage( parent.getId(), Main.getCurrentFrame(), position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, velocity, 1 ) ); break; case SLOWDOWN: //run slowdown code if(entityPoseActor != null){ String animationToPlay = determineCorrectAnimation(); if( !entityPoseActor.isPlayingAnimation() || !entityPoseActor.isPlayingAnimation(animationToPlay) && (jumpTree == null || !jumpTree.isJumping()) && (fallTree == null || !fallTree.isFalling()) ){ entityPoseActor.playAnimation(animationToPlay,1); entityPoseActor.incrementAnimationTime(0.0001); } } //velocity stuff velocity = velocity - acceleration * Main.deltaFrames; //check if can transition state if(velocity <= 0){ velocity = 0; state = MovementTreeState.IDLE; if(entityPoseActor != null){ String animationToPlay = determineCorrectAnimation(); if(entityPoseActor.isPlayingAnimation() && entityPoseActor.isPlayingAnimation(animationToPlay)){ entityPoseActor.stopAnimation(animationToPlay); } } } CreatureUtils.setVelocity(parent, velocity); 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.serverAttemptActivateGravity(parent); DataCellSearchUtils.getEntityDataCell(parent).broadcastNetworkMessage( EntityMessage.constructmoveUpdateMessage( parent.getId(), Main.getCurrentFrame(), position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, rotation.w, velocity, 2 ) ); break; case IDLE: break; } } public void addNetworkMessage(EntityMessage networkMessage) { networkMessageQueue.add(networkMessage); } public boolean canStartMoving(){ boolean rVal = true; if(parent.containsKey(EntityDataStrings.SERVER_ATTACK_TREE) && ((ServerAttackTree)parent.getData(EntityDataStrings.SERVER_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 setServerSprintTree(ServerSprintTree sprintTree){ this.sprintTree = sprintTree; } public void setServerJumpTree(ServerJumpTree jumpTree){ this.jumpTree = jumpTree; } public void setServerFallTree(ServerFallTree 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; } }