package electrosphere.entity.state.attack; import electrosphere.collision.collidable.Collidable; import electrosphere.engine.Globals; import electrosphere.engine.Main; import electrosphere.entity.Entity; import electrosphere.entity.EntityDataStrings; import electrosphere.entity.EntityUtils; import electrosphere.entity.ServerEntityUtils; import electrosphere.entity.state.BehaviorTree; import electrosphere.entity.state.collidable.Impulse; import electrosphere.entity.state.equip.EquipState; import electrosphere.entity.state.movement.groundmove.GroundMovementTree; import electrosphere.entity.state.movement.groundmove.ServerGroundMovementTree; import electrosphere.entity.state.rotator.RotatorTree; import electrosphere.entity.state.rotator.ServerRotatorTree; import electrosphere.entity.types.attach.AttachUtils; import electrosphere.entity.types.collision.CollisionObjUtils; import electrosphere.entity.types.creature.CreatureUtils; import electrosphere.entity.types.hitbox.HitboxUtils; import electrosphere.entity.types.item.ItemUtils; import electrosphere.entity.types.projectile.ProjectileUtils; import electrosphere.game.data.creature.type.attack.AttackMove; import electrosphere.game.data.creature.type.equip.EquipPoint; import electrosphere.net.parser.net.message.EntityMessage; import electrosphere.renderer.actor.Actor; import electrosphere.renderer.anim.Animation; import electrosphere.server.datacell.Realm; import electrosphere.server.datacell.utils.EntityLookupUtils; import electrosphere.server.poseactor.PoseActor; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import org.joml.Quaterniond; import org.joml.Quaternionf; import org.joml.Quaternionfc; import org.joml.Vector3d; import org.joml.Vector3f; public class ServerAttackTree implements BehaviorTree { public static enum AttackTreeState { WINDUP, HOLD, ATTACK, COOLDOWN, IDLE, } //the state of drifting forward during the attack public static enum AttackTreeDriftState { DRIFT, NO_DRIFT, } AttackTreeState state; AttackTreeDriftState driftState; Entity parent; CopyOnWriteArrayList networkMessageQueue = new CopyOnWriteArrayList(); long lastUpdateTime = 0; float frameCurrent; String animationName = "SwingWeapon"; int maxFrame = 60; List currentMoveset = null; AttackMove currentMove = null; Entity currentWeapon = null; boolean currentMoveHasWindup; boolean currentMoveCanHold; boolean stillHold = true; boolean firesProjectile = false; String projectileToFire = null; String attackingPoint = null; public ServerAttackTree(Entity e){ state = AttackTreeState.IDLE; driftState = AttackTreeDriftState.NO_DRIFT; parent = e; } public AttackTreeState getState(){ return state; } public void start(){ currentMoveCanHold = false; currentMoveHasWindup = false; stillHold = true; firesProjectile = false; projectileToFire = null; currentWeapon = null; attackingPoint = null; //figure out attack type we should be doing String attackType = getAttackType(); //if we can attack, setup doing so if(canAttack(attackType)){ parent.putData(EntityDataStrings.ATTACK_MOVE_TYPE_ACTIVE, attackType); currentMoveset = (List)parent.getData(attackType); if(currentMoveset != null){ if(currentMove == null){ currentMove = currentMoveset.get(0); } else { currentMove = getNextMove(currentMoveset,currentMove.getNextMoveId()); } if(currentMove != null){ firesProjectile = currentMove.getFiresProjectile(); if(firesProjectile){ projectileToFire = ItemUtils.getWeaponDataRaw(currentWeapon).getProjectileModel(); } animationName = currentMove.getAttackAnimationName(); //intuit windup from presence of windup anim currentMoveHasWindup = currentMove.getWindupAnimationName() != null; if(currentMoveHasWindup){ animationName = currentMove.getWindupAnimationName(); } //intuit can hold from presence of windup anim currentMoveCanHold = currentMove.getHoldAnimationName() != null; //stop movement tree if(parent.containsKey(EntityDataStrings.SERVER_MOVEMENT_BT)){ BehaviorTree movementTree = CreatureUtils.serverGetEntityMovementTree(parent); if(movementTree instanceof ServerGroundMovementTree){ ((ServerGroundMovementTree)movementTree).interrupt(); } } Vector3d movementVector = CreatureUtils.getFacingVector(parent); EntityUtils.getRotation(parent).rotationTo(new Vector3d(0,0,1), new Vector3d(movementVector.x,movementVector.y,movementVector.z)); //set initial stuff state = AttackTreeState.WINDUP; frameCurrent = 0; } else { state = AttackTreeState.IDLE; } } } } public void release(){ stillHold = false; } public void interrupt(){ state = AttackTreeState.IDLE; } public void slowdown(){ state = AttackTreeState.COOLDOWN; } @Override public void simulate(float deltaTime){ frameCurrent = frameCurrent + (float)Globals.timekeeper.getSimFrameTime(); float velocity = CreatureUtils.getVelocity(parent); PoseActor entityPoseActor = EntityUtils.getPoseActor(parent); Vector3d position = EntityUtils.getPosition(parent); Vector3d movementVector = CreatureUtils.getFacingVector(parent); //parse attached network messages for(EntityMessage message : networkMessageQueue){ networkMessageQueue.remove(message); // System.out.println("MOVE to " + message.getX() + " " + message.getY() + " " + message.getZ()); long updateTime = message.gettime(); switch(message.getMessageSubtype()){ case ATTACKUPDATE: if(updateTime > lastUpdateTime){ lastUpdateTime = updateTime; switch(message.gettreeState()){ case 0: state = AttackTreeState.WINDUP; frameCurrent = 0; // System.out.println("Set state STARTUP"); break; case 1: frameCurrent = currentMove.getWindupFrames()+1; state = AttackTreeState.ATTACK; // System.out.println("Set state MOVE"); break; case 2: frameCurrent = currentMove.getWindupFrames()+currentMove.getAttackFrames()+1; state = AttackTreeState.COOLDOWN; // System.out.println("Set state SLOWDOWN"); break; case 3: frameCurrent = 60; state = AttackTreeState.IDLE; // System.out.println("Set state IDLE"); break; } } EntityUtils.getPosition(parent).set(message.getpositionX(),message.getpositionY(),message.getpositionZ()); CreatureUtils.setFacingVector(parent, new Vector3d(message.getrotationX(),message.getrotationY(),message.getrotationZ())); break; case ATTACHENTITYTOENTITY: case CREATE: case DESTROY: case MOVE: case MOVEUPDATE: case SETBEHAVIORTREE: case SETFACING: case SETPOSITION: case SETPROPERTY: case KILL: case SPAWNCREATURE: //silently ignore break; } } //handle the drifting if we're supposed to currently switch(driftState){ case DRIFT: if(currentMove != null){ //calculate the vector of movement CollisionObjUtils.getCollidable(parent).addImpulse(new Impulse(new Vector3d(movementVector), new Vector3d(0,0,0), new Vector3d(0,0,0), currentMove.getDriftGoal() * Globals.timekeeper.getSimFrameTime(), "movement")); if(frameCurrent > currentMove.getDriftFrameEnd()){ driftState = AttackTreeDriftState.NO_DRIFT; } } break; case NO_DRIFT: if(currentMove != null){ if(frameCurrent > currentMove.getDriftFrameStart() && frameCurrent < currentMove.getDriftFrameEnd()){ driftState = AttackTreeDriftState.DRIFT; } } break; } // if(state != AttackTreeState.IDLE){ // System.out.println(frameCurrent); // } //state machine switch(state){ case WINDUP: if(parent.containsKey(EntityDataStrings.SERVER_ROTATOR_TREE)){ ServerRotatorTree.getServerRotatorTree(parent).setActive(true); } if(entityPoseActor != null){ if(!entityPoseActor.isPlayingAnimation() || !entityPoseActor.isPlayingAnimation(animationName)){ entityPoseActor.playAnimation(animationName,1); entityPoseActor.incrementAnimationTime(0.0001); } } if(frameCurrent > currentMove.getWindupFrames()){ if(currentMoveCanHold && stillHold){ state = AttackTreeState.HOLD; } else { state = AttackTreeState.ATTACK; } } Globals.server.broadcastMessage( EntityMessage.constructattackUpdateMessage( parent.getId(), System.currentTimeMillis(), (float)position.x, (float)position.y, (float)position.z, movementVector.x, movementVector.y, movementVector.z, velocity, 0 ) ); break; case HOLD: if(entityPoseActor != null){ if(!entityPoseActor.isPlayingAnimation() || !entityPoseActor.isPlayingAnimation(animationName)){ entityPoseActor.playAnimation(animationName,1); entityPoseActor.incrementAnimationTime(0.0001); } } if(!stillHold){ state = AttackTreeState.ATTACK; } break; case ATTACK: if(parent.containsKey(EntityDataStrings.ATTACH_CHILDREN_LIST)){ List attachedEntities = AttachUtils.getChildrenList(parent); for(Entity currentAttached : attachedEntities){ if(currentAttached.containsKey(EntityDataStrings.HITBOX_ASSOCIATED_LIST)){ List hitboxes = HitboxUtils.getHitboxAssociatedList(currentAttached); for(Entity hitbox : hitboxes){ HitboxUtils.getHitboxData(hitbox).setActive(true); } } } } if(firesProjectile && projectileToFire != null){ //spawn projectile //TODO: solve spawnPosition, initialVector Vector3d spawnPosition = new Vector3d(0,0,0); Quaterniond arrowRotation = new Quaterniond(); String targetBone = null; EquipState equipState = EquipState.getEquipState(parent); EquipPoint weaponPoint = null; if((weaponPoint = equipState.getEquipPoint(attackingPoint)) != null){ targetBone = weaponPoint.getBone(); } if(targetBone != null){ Actor parentActor = EntityUtils.getActor(parent); //transform bone space spawnPosition = new Vector3d(parentActor.getBonePosition(targetBone)); spawnPosition = spawnPosition.mul(((Vector3f)EntityUtils.getScale(parent))); Quaterniond rotation = EntityUtils.getRotation(parent); spawnPosition = spawnPosition.rotate(new Quaterniond(rotation.x,rotation.y,rotation.z,rotation.w)); //transform worldspace spawnPosition.add(new Vector3d(EntityUtils.getPosition(parent))); //set // EntityUtils.getPosition(currentEntity).set(position); //set rotation // Quaternionf rotation = parentActor.getBoneRotation(targetBone); // EntityUtils.getRotation(currentEntity).set(rotation).normalize(); // Vector3d facingAngle = CreatureUtils.getFacingVector(parent); arrowRotation = parentActor.getBoneRotation(targetBone); // EntityUtils.getRotation(currentEntity).rotationTo(new Vector3f(0,0,1), new Vector3f((float)facingAngle.x,(float)facingAngle.y,(float)facingAngle.z)).mul(parentActor.getBoneRotation(targetBone)).normalize(); } Vector3f initialVector = new Vector3f((float)movementVector.x,(float)movementVector.y,(float)movementVector.z).normalize(); Realm parentRealm = Globals.realmManager.getEntityRealm(parent); ProjectileUtils.serverSpawnBasicProjectile(parentRealm, projectileToFire, spawnPosition, arrowRotation, 750, initialVector, 0.03f); projectileToFire = null; } if(frameCurrent > currentMove.getWindupFrames() + currentMove.getAttackFrames()){ state = AttackTreeState.COOLDOWN; } Globals.server.broadcastMessage( EntityMessage.constructattackUpdateMessage( parent.getId(), System.currentTimeMillis(), (float)position.x, (float)position.y, (float)position.z, movementVector.x, movementVector.y, movementVector.z, velocity, 1 ) ); break; case COOLDOWN: if(parent.containsKey(EntityDataStrings.ATTACH_CHILDREN_LIST)){ List attachedEntities = AttachUtils.getChildrenList(parent); for(Entity currentAttached : attachedEntities){ if(currentAttached.containsKey(EntityDataStrings.HITBOX_ASSOCIATED_LIST)){ List hitboxes = HitboxUtils.getHitboxAssociatedList(currentAttached); for(Entity hitbox : hitboxes){ HitboxUtils.getHitboxData(hitbox).setActive(false); } } } } if(frameCurrent > currentMove.getWindupFrames() + currentMove.getAttackFrames() + currentMove.getCooldownFrames()){ state = AttackTreeState.IDLE; frameCurrent = 0; if(parent.containsKey(EntityDataStrings.SERVER_ROTATOR_TREE)){ ServerRotatorTree.getServerRotatorTree(parent).setActive(false); } } Globals.server.broadcastMessage( EntityMessage.constructattackUpdateMessage( parent.getId(), System.currentTimeMillis(), (float)position.x, (float)position.y, (float)position.z, movementVector.x, movementVector.y, movementVector.z, velocity, 2 ) ); break; case IDLE: currentMove = null; currentMoveset = null; break; } } public void addNetworkMessage(EntityMessage networkMessage) { networkMessageQueue.add(networkMessage); } String getAttackType(){ String rVal = null; if(EquipState.hasEquipState(parent)){ EquipState equipState = EquipState.getEquipState(parent); for(String point : equipState.equippedPoints()){ Entity item = equipState.getEquippedItemAtPoint(point); if(ItemUtils.isWeapon(item)){ attackingPoint = point; currentWeapon = item; switch(ItemUtils.getWeaponClass(item)){ case "sword1h": rVal = EntityDataStrings.ATTACK_MOVE_TYPE_MELEE_SWING_ONE_HAND; break; case "bow2h": rVal = EntityDataStrings.ATTACK_MOVE_TYPE_BOW_TWO_HAND; break; } } } } return rVal; } boolean canAttack(String attackType){ boolean rVal = true; if(attackType == null){ return false; } else if(state != AttackTreeState.IDLE){ //checks if we have a next move and if we're in the specified range of frames when we're allowed to chain into it if( currentMove.getNextMoveId() != null && !currentMove.getNextMoveId().equals("") && frameCurrent >= currentMove.getMoveChainWindowStart() && frameCurrent <= currentMove.getMoveChainWindowEnd() ){ rVal = true; } } else { if(EquipState.hasEquipState(parent)){ EquipState equipState = EquipState.getEquipState(parent); // if(equipState.hasEquipPrimary()){ // switch(attackType){ // case EntityDataStrings.ATTACK_MOVE_TYPE_MELEE_SWING_ONE_HAND: // break; // default: // rVal = false; // break; // } // } else { // switch(attackType){ // case EntityDataStrings.ATTACK_MOVE_TYPE_MELEE_SWING_ONE_HAND: // rVal = false; // break; // default: // rVal = false; // break; // } // } } } return rVal; } AttackMove getNextMove(List moveset, String nextMoveId){ AttackMove rVal = null; for(AttackMove move : moveset){ if(move.getAttackMoveId().equals(nextMoveId)){ rVal = move; break; } } return rVal; } }