package electrosphere.entity.types.creature;

import org.joml.Quaterniond;
import org.joml.Vector3d;
import org.ode4j.ode.DBody;

import electrosphere.collision.PhysicsEntityUtils;
import electrosphere.collision.collidable.Collidable;
import electrosphere.engine.Globals;
import electrosphere.entity.Entity;
import electrosphere.entity.EntityCreationUtils;
import electrosphere.entity.EntityDataStrings;
import electrosphere.entity.EntityTags;
import electrosphere.entity.EntityUtils;
import electrosphere.entity.ServerEntityUtils;
import electrosphere.entity.btree.BehaviorTree;
import electrosphere.entity.state.attack.ClientAttackTree;
import electrosphere.entity.state.attack.ServerAttackTree;
import electrosphere.entity.state.attack.ShooterTree;
import electrosphere.entity.state.block.ClientBlockTree;
import electrosphere.entity.state.block.ServerBlockTree;
import electrosphere.entity.state.client.firstPerson.FirstPersonTree;
import electrosphere.entity.state.equip.ClientEquipState;
import electrosphere.entity.state.equip.ServerEquipState;
import electrosphere.entity.state.gravity.ClientGravityTree;
import electrosphere.entity.state.gravity.ServerGravityTree;
import electrosphere.entity.state.hitbox.HitboxCollectionState;
import electrosphere.entity.state.idle.ClientIdleTree;
import electrosphere.entity.state.idle.ServerIdleTree;
import electrosphere.entity.state.inventory.ClientInventoryState;
import electrosphere.entity.state.inventory.InventoryUtils;
import electrosphere.entity.state.inventory.RelationalInventoryState;
import electrosphere.entity.state.inventory.ServerInventoryState;
import electrosphere.entity.state.inventory.UnrelationalInventoryState;
import electrosphere.entity.state.life.ClientLifeTree;
import electrosphere.entity.state.life.ServerLifeTree;
import electrosphere.entity.state.movement.FallTree;
import electrosphere.entity.state.movement.ServerFallTree;
import electrosphere.entity.state.movement.ServerSprintTree;
import electrosphere.entity.state.movement.SprintTree;
import electrosphere.entity.state.movement.groundmove.ClientGroundMovementTree;
import electrosphere.entity.state.movement.groundmove.ServerGroundMovementTree;
import electrosphere.entity.state.movement.jump.ClientJumpTree;
import electrosphere.entity.state.movement.jump.ServerJumpTree;
import electrosphere.entity.state.movement.speed.ClientWalkTree;
import electrosphere.entity.state.movement.speed.ServerWalkTree;
import electrosphere.entity.state.physicssync.upright.ClientAlwaysUprightTree;
import electrosphere.entity.state.physicssync.upright.ServerAlwaysUprightTree;
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;
import electrosphere.game.data.creature.type.attack.AttackMove;
import electrosphere.game.data.creature.type.movement.FallMovementSystem;
import electrosphere.game.data.creature.type.movement.GroundMovementSystem;
import electrosphere.game.data.creature.type.movement.JumpMovementSystem;
import electrosphere.game.data.creature.type.movement.MovementSystem;
import electrosphere.game.data.creature.type.movement.WalkMovementSystem;
import electrosphere.game.data.creature.type.rotator.RotatorConstraint;
import electrosphere.game.data.creature.type.rotator.RotatorItem;
import electrosphere.game.data.creature.type.rotator.RotatorSystem;
import electrosphere.game.data.creature.type.visualattribute.AttributeVariant;
import electrosphere.game.data.creature.type.visualattribute.VisualAttribute;
import electrosphere.logger.LoggerInterface;
import electrosphere.net.NetUtils;
import electrosphere.net.parser.net.message.EntityMessage;
import electrosphere.net.parser.net.message.NetworkMessage;
import electrosphere.net.server.player.Player;
import electrosphere.net.synchronization.transport.StateCollection;
import electrosphere.renderer.actor.Actor;
import electrosphere.renderer.actor.ActorBoneRotator;
import electrosphere.renderer.actor.ActorStaticMorph;
import electrosphere.server.datacell.Realm;
import electrosphere.server.datacell.utils.ServerBehaviorTreeUtils;
import electrosphere.server.datacell.utils.ServerEntityTagUtils;
import electrosphere.server.poseactor.PoseActor;
import electrosphere.util.Utilities;
import electrosphere.util.math.MathUtils;

/**
 * Utilities for creating creatures on the client and server
 */
public class CreatureUtils {

    //The entity type int for creatures
    public static final int ENTITY_TYPE_CREATURE = 0;
    
    /**
     * Spawns a client-side creature entity
     * @param type The type of creature
     * @param template The creature template if applicable
     * @return The creature entity
     */
    public static Entity clientSpawnBasicCreature(String type, CreatureTemplate template){
        CreatureData rawType = Globals.gameConfigCurrent.getCreatureTypeLoader().getCreature(type);
        Entity rVal = EntityCreationUtils.createClientSpatialEntity();
        EntityCreationUtils.makeEntityDrawable(rVal, rawType.getModelPath());
        Actor creatureActor = EntityUtils.getActor(rVal);
        EntityUtils.setEntityType(rVal, ENTITY_TYPE_CREATURE);
        EntityUtils.setEntitySubtype(rVal, type);


        ///
        ///
        /// HITBOX DATA
        ///
        ///
        HitboxCollectionState.attachHitboxState(Globals.clientSceneWrapper.getHitboxManager(), false, rVal, rawType.getHitboxes());


        //
        //
        // PHYSICS
        //
        //
        if(rawType.getCollidable() != null){
            CollidableTemplate physicsTemplate = rawType.getCollidable();
            PhysicsEntityUtils.clientAttachCollidableTemplate(rVal, physicsTemplate);
            ClientAlwaysUprightTree.attachTree(rVal);
        }
        
        //
        //
        //   MOVEMENT SYSTEMS
        //
        //
        for(MovementSystem movementSystem : rawType.getMovementSystems()){
            switch(movementSystem.getType()){
                //
                // Generic ground
                case GroundMovementSystem.GROUND_MOVEMENT_SYSTEM:
                    GroundMovementSystem groundMovementSystem = (GroundMovementSystem)movementSystem;
                    ClientGroundMovementTree moveTree = ClientGroundMovementTree.attachTree(rVal, CollisionObjUtils.getCollidable(rVal), groundMovementSystem);
                    if(groundMovementSystem.getAnimationStartup() != null){
                        moveTree.setAnimationStartUp(groundMovementSystem.getAnimationStartup().getNameThirdPerson());
                    }
                    if(groundMovementSystem.getAnimationLoop() != null){
                        moveTree.setAnimationMain(groundMovementSystem.getAnimationLoop().getNameThirdPerson());
                    }
                    if(groundMovementSystem.getAnimationWindDown()!= null){
                        moveTree.setAnimationSlowDown(groundMovementSystem.getAnimationWindDown().getNameThirdPerson());
                    }
                    //sprint system
                    if(groundMovementSystem.getSprintSystem() != null){
                        SprintSystem sprintSystem = groundMovementSystem.getSprintSystem();
                        SprintTree sprintTree = new SprintTree(rVal,sprintSystem.getMaxVelocity(),sprintSystem.getStaminaMax());
                        if(sprintSystem.getAnimationStartUp()!= null){
                            moveTree.setAnimationSprintStartUp(sprintSystem.getAnimationStartUp().getNameThirdPerson());
                        }
                        if(sprintSystem.getAnimationMain()!= null){
                            moveTree.setAnimationSprint(sprintSystem.getAnimationMain().getNameThirdPerson());
                        }
                        if(sprintSystem.getAnimationWindDown()!= null){
                            moveTree.setAnimationSprintWindDown(sprintSystem.getAnimationWindDown().getNameThirdPerson());
                        }
                        sprintTree.setGroundMovementTree(moveTree);
                        moveTree.setSprintTree(sprintTree);
                        rVal.putData(EntityDataStrings.CLIENT_SPRINT_TREE,sprintTree);
                        Globals.clientScene.registerBehaviorTree(sprintTree);
                        Globals.clientScene.registerEntityToTag(rVal, EntityTags.SPRINTABLE);
                    }
                    //round out end of move system
                    rVal.putData(EntityDataStrings.CLIENT_MOVEMENT_BT, moveTree);
                    CreatureUtils.setFacingVector(rVal, MathUtils.getOriginVector());
                    rVal.putData(EntityDataStrings.DATA_STRING_MAX_NATURAL_VELOCITY, groundMovementSystem.getMaxVelocity());
                    rVal.putData(EntityDataStrings.DATA_STRING_ACCELERATION, groundMovementSystem.getAcceleration());
                    rVal.putData(EntityDataStrings.DATA_STRING_VELOCITY, 0f);
                    Globals.clientScene.registerBehaviorTree(moveTree);
                    Globals.clientScene.registerEntityToTag(rVal, EntityTags.MOVEABLE);
                    break;
                    //
                    // Jump
                case JumpMovementSystem.JUMP_MOVEMENT_SYSTEM:
                    JumpMovementSystem jumpMovementSystem = (JumpMovementSystem)movementSystem;
                    ClientJumpTree jumpTree = ClientJumpTree.attachTree(rVal, jumpMovementSystem);
                    if(jumpMovementSystem.getAnimationJump() != null){
                        jumpTree.setAnimationJump(jumpMovementSystem.getAnimationJump().getNameThirdPerson());
                    }
                    if(CreatureUtils.clientGetEntityMovementTree(rVal) != null && CreatureUtils.clientGetEntityMovementTree(rVal) instanceof ClientGroundMovementTree){
                        ((ClientGroundMovementTree)CreatureUtils.clientGetEntityMovementTree(rVal)).setClientJumpTree(jumpTree);
                    }
                    if(FallTree.getFallTree(rVal)!=null){
                        FallTree.getFallTree(rVal).setJumpTree(jumpTree);
                    }
                    break;
                    //
                    // Falling
                case FallMovementSystem.FALL_MOVEMENT_SYSTEM:
                    FallMovementSystem fallMovementSystem = (FallMovementSystem)movementSystem;
                    FallTree fallTree = new FallTree(rVal, fallMovementSystem);
                    if(CreatureUtils.clientGetEntityMovementTree(rVal) != null && CreatureUtils.clientGetEntityMovementTree(rVal) instanceof ClientGroundMovementTree){
                        ((ClientGroundMovementTree)CreatureUtils.clientGetEntityMovementTree(rVal)).setClientFallTree(fallTree);
                    }
                    if(ClientJumpTree.getClientJumpTree(rVal)!=null){
                        fallTree.setJumpTree(ClientJumpTree.getClientJumpTree(rVal));
                    }
                    rVal.putData(EntityDataStrings.FALL_TREE, fallTree);
                    Globals.clientScene.registerBehaviorTree(fallTree);
                    break;
                case WalkMovementSystem.WALK_MOVEMENT_SYSTEM: {
                    ClientWalkTree.attachTree(rVal, (WalkMovementSystem)movementSystem);
                } break;
            }
        }
        if(rawType.getEquipPoints() != null && rawType.getEquipPoints().size() > 0){
            ClientEquipState.attachTree(rVal, rawType.getEquipPoints());
            rVal.putData(EntityDataStrings.EQUIP_INVENTORY, RelationalInventoryState.buildRelationalInventoryStateFromEquipList(rawType.getEquipPoints()));
        }
        if(rawType.getBlockSystem() != null){
            ClientBlockTree.attachTree(rVal, rawType.getBlockSystem());
        }
        for(String token : rawType.getTokens()){
            switch(token){
                case "ATTACKER":
                    ClientAttackTree.attachTree(rVal);
                    rVal.putData(EntityDataStrings.ATTACK_MOVE_TYPE_ACTIVE, null);
                    Globals.clientScene.registerEntityToTag(rVal, EntityTags.ATTACKER);
                    //add all attack moves
                    if(rawType.getAttackMoves() != null && rawType.getAttackMoves().size() > 0){
                        for(AttackMove attackMove : rawType.getAttackMoves()){
                            rVal.putData(attackMove.getType(), rawType.getAttackMoveResolver().getMoveset(attackMove.getType()));
                            // switch(attackMove.getType()){
                            //     case EntityDataStrings.ATTACK_MOVE_TYPE_MELEE_SWING_ONE_HAND:
                            //         rVal.putData(EntityDataStrings.ATTACK_MOVE_TYPE_MELEE_SWING_ONE_HAND, rawType.getAttackMoveResolver().getMoveset(EntityDataStrings.ATTACK_MOVE_TYPE_MELEE_SWING_ONE_HAND));
                            //         break;
                            //     case EntityDataStrings.ATTACK_MOVE_TYPE_BOW_TWO_HAND:
                            //     rVal.putData(EntityDataStrings.ATTACK_MOVE_TYPE_MELEE_SWING_ONE_HAND, rawType.getAttackMoveResolver().getMoveset(EntityDataStrings.ATTACK_MOVE_TYPE_MELEE_SWING_ONE_HAND));
                            //     break;
                            // }
                        }
                    }
                    break;
                case "SHOOTER": {
                    ShooterTree shooterTree = new ShooterTree(rVal);
                    ShooterTree.setShooterTree(rVal, shooterTree);
                    Globals.clientScene.registerBehaviorTree(shooterTree);
                } break;
                case "GRAVITY":
                    Collidable collidable = (Collidable)rVal.getData(EntityDataStrings.PHYSICS_COLLIDABLE);
                    DBody collisionObject = (DBody)rVal.getData(EntityDataStrings.PHYSICS_COLLISION_BODY);
                    ClientGravityTree.attachTree(rVal, collidable, collisionObject, 30);
                    rVal.putData(EntityDataStrings.GRAVITY_ENTITY, true);
                    break;
                case "TARGETABLE":
                Globals.clientScene.registerEntityToTag(rVal, EntityTags.TARGETABLE);
                    break;
                case "INVENTORY":
                    rVal.putData(EntityDataStrings.NATURAL_INVENTORY,UnrelationalInventoryState.createUnrelationalInventory(10));
                    InventoryUtils.clientSetInventoryState(rVal, ClientInventoryState.clientCreateInventoryState(rVal));
                    break;
                case "OUTLINE":
                    rVal.putData(EntityDataStrings.DRAW_OUTLINE, true);
                    break;
            }
        }
        //variants
        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.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.putAttributeValue(attributeType.getAttributeId(), variantId);
                                    break;
                                }
                            }
                        }
                        //make sure stored template contains creature data
                        if(storedTemplate.getAttributeValue(attributeType.getAttributeId())==null){
                            storedTemplate.putAttributeValue(attributeType.getAttributeId(), attributeType.getVariants().get(0).getId());
                        }
                        // attributeType.getAttributeId();
                        // variant.getId();
                        rVal.putData(EntityDataStrings.CREATURE_ATTRIBUTE_VARIANT + attributeType.getAttributeId(), variant.getId());
                        Globals.assetManager.addModelPathToQueue(variant.getModel());
                        for(String mesh : variant.getMeshes()){
                            creatureActor.getMeshMask().queueMesh(variant.getModel(), mesh);
                        }
                    }
                }
                if(attributeType.getType().equals("bone")){
                    if(staticMorph == null){
                        staticMorph = new ActorStaticMorph();
                        creatureActor.setActorStaticMorph(staticMorph);
                    }
                    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.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.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.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.putAttributeValue(attributeType.getAttributeId(), midpoint);
                    }
                }
            }
        }
        //set race
        storedTemplate.creatureType = rawType.getCreatureId();
        //store template on creature
        CreatureUtils.setCreatureTemplate(rVal, storedTemplate);
        //rotator system
        if(rawType.getRotatorSystem() != null){
            RotatorSystem system = rawType.getRotatorSystem();
            RotatorTree rotatorTree = new RotatorTree(rVal);
            for(RotatorItem item : system.getRotatorItems()){
                //put actor rotator
                ActorBoneRotator newRotator = new ActorBoneRotator();
                creatureActor.addBoneRotator(item.getBoneName(), newRotator);
                //construct node for tree
                RotatorHierarchyNode hierarchyNode = new RotatorHierarchyNode();
                hierarchyNode.setBone(item.getBoneName());
                for(RotatorConstraint constraint : item.getConstraints()){
                    hierarchyNode.addRotatorConstraint(new electrosphere.entity.state.rotator.RotatorConstraint(constraint));
                }
                rotatorTree.addRotatorNode(hierarchyNode);
            }
            rVal.putData(EntityDataStrings.CLIENT_ROTATOR_TREE, rotatorTree);
            Globals.clientScene.registerBehaviorTree(rotatorTree);
        }
        //bone groups
        if(rawType.getBoneGroups() != null){
            creatureActor.setBoneGroups(rawType.getBoneGroups());
        }
        //add health system
        if(rawType.getHealthSystem() != null){
            ClientLifeTree.attachTree(rVal,rawType.getHealthSystem());
            Globals.clientScene.registerEntityToTag(rVal, EntityTags.LIFE_STATE);
        }
        //idle tree & generic stuff all creatures have
        ClientIdleTree idleTree = new ClientIdleTree(rVal);
        rVal.putData(EntityDataStrings.TREE_IDLE, idleTree);
        Globals.clientScene.registerBehaviorTree(idleTree);
        Globals.clientScene.registerEntityToTag(rVal, EntityTags.CREATURE);
        CreatureUtils.setFacingVector(rVal, MathUtils.getOriginVector());
        rVal.putData(EntityDataStrings.DRAW_CAST_SHADOW, true);
        return rVal;
    }


    /**
     * Spawns a server-side creature
     * @param realm The realm to spawn the creature in
     * @param position The position of the creature in that realm
     * @param type The type of creature
     * @param template The creature template to use
     * @return The creature entity
     */
    public static Entity serverSpawnBasicCreature(Realm realm, Vector3d position, String type, CreatureTemplate template){
        CreatureData rawType = Globals.gameConfigCurrent.getCreatureTypeLoader().getCreature(type);
        Entity rVal = EntityCreationUtils.createServerEntity(realm, position);
        EntityCreationUtils.makeEntityPoseable(rVal, rawType.getModelPath());
        
        PoseActor creatureActor = EntityUtils.getPoseActor(rVal);
        //
        //
        // Hitbox stuff
        //
        //
        HitboxCollectionState.attachHitboxState(realm.getHitboxManager(), true, rVal, rawType.getHitboxes());
        //
        //
        // Physics stuff
        //
        //
        if(rawType.getCollidable() != null){
            CollidableTemplate physicsTemplate = rawType.getCollidable();
            PhysicsEntityUtils.serverAttachCollidableTemplate(realm, rVal, physicsTemplate);
            ServerAlwaysUprightTree.attachTree(rVal);
        }
        //
        //
        //   MOVEMENT SYSTEMS
        //
        //
        for(MovementSystem movementSystem : rawType.getMovementSystems()){
            switch(movementSystem.getType()){
                //
                // Generic ground
                case GroundMovementSystem.GROUND_MOVEMENT_SYSTEM:
                    GroundMovementSystem groundMovementSystem = (GroundMovementSystem)movementSystem;
                    ServerGroundMovementTree moveTree = ServerGroundMovementTree.attachTree(rVal,CollisionObjUtils.getCollidable(rVal),groundMovementSystem);
                    if(groundMovementSystem.getAnimationStartup() != null){
                        moveTree.setAnimationStartUp(groundMovementSystem.getAnimationStartup().getNameThirdPerson());
                    }
                    if(groundMovementSystem.getAnimationLoop() != null){
                        moveTree.setAnimationMain(groundMovementSystem.getAnimationLoop().getNameThirdPerson());
                    }
                    if(groundMovementSystem.getAnimationWindDown()!= null){
                        moveTree.setAnimationSlowDown(groundMovementSystem.getAnimationWindDown().getNameThirdPerson());
                    }
                    //sprint system
                    if(groundMovementSystem.getSprintSystem() != null){
                        SprintSystem sprintSystem = groundMovementSystem.getSprintSystem();
                        ServerSprintTree sprintTree = new ServerSprintTree(rVal,sprintSystem.getMaxVelocity(),sprintSystem.getStaminaMax());
                        if(sprintSystem.getAnimationStartUp()!= null){
                            moveTree.setAnimationSprintStartUp(sprintSystem.getAnimationStartUp().getNameThirdPerson());
                        }
                        if(sprintSystem.getAnimationMain()!= null){
                            moveTree.setAnimationSprint(sprintSystem.getAnimationMain().getNameThirdPerson());
                        }
                        if(sprintSystem.getAnimationWindDown()!= null){
                            moveTree.setAnimationSprintWindDown(sprintSystem.getAnimationWindDown().getNameThirdPerson());
                        }
                        sprintTree.setServerGroundMovementTree(moveTree);
                        moveTree.setServerSprintTree(sprintTree);
                        rVal.putData(EntityDataStrings.SERVER_SPRINT_TREE,sprintTree);
                        ServerBehaviorTreeUtils.attachBTreeToEntity(rVal, sprintTree);
                        ServerEntityTagUtils.attachTagToEntity(rVal, EntityTags.SPRINTABLE);
                    }
                    //round out end of move system
                    rVal.putData(EntityDataStrings.SERVER_MOVEMENT_BT, moveTree);
                    CreatureUtils.setFacingVector(rVal, MathUtils.getOriginVector());
                    rVal.putData(EntityDataStrings.DATA_STRING_MAX_NATURAL_VELOCITY, groundMovementSystem.getMaxVelocity());
                    rVal.putData(EntityDataStrings.DATA_STRING_ACCELERATION, groundMovementSystem.getAcceleration());
                    rVal.putData(EntityDataStrings.DATA_STRING_VELOCITY, 0f);
                    ServerBehaviorTreeUtils.attachBTreeToEntity(rVal, moveTree);
                    ServerEntityTagUtils.attachTagToEntity(rVal, EntityTags.MOVEABLE);
                    break;
                    //
                    // Jump
                case JumpMovementSystem.JUMP_MOVEMENT_SYSTEM:
                    JumpMovementSystem jumpMovementSystem = (JumpMovementSystem)movementSystem;
                    ServerJumpTree jumpTree = ServerJumpTree.attachTree(rVal, jumpMovementSystem);
                    if(jumpMovementSystem.getAnimationJump() != null){
                        jumpTree.setAnimationJump(jumpMovementSystem.getAnimationJump().getNameThirdPerson());
                    }
                    if(CreatureUtils.serverGetEntityMovementTree(rVal) != null && CreatureUtils.serverGetEntityMovementTree(rVal) instanceof ClientGroundMovementTree){
                        ((ServerGroundMovementTree)CreatureUtils.serverGetEntityMovementTree(rVal)).setServerJumpTree(jumpTree);
                    }
                    if(ServerFallTree.getFallTree(rVal)!=null){
                        ServerFallTree.getFallTree(rVal).setServerJumpTree(jumpTree);
                    }
                    break;
                    //
                    // Falling
                case FallMovementSystem.FALL_MOVEMENT_SYSTEM:
                    FallMovementSystem fallMovementSystem = (FallMovementSystem)movementSystem;
                    ServerFallTree fallTree = new ServerFallTree(rVal,fallMovementSystem);
                    if(CreatureUtils.serverGetEntityMovementTree(rVal) != null && CreatureUtils.serverGetEntityMovementTree(rVal) instanceof ClientGroundMovementTree){
                        ((ServerGroundMovementTree)CreatureUtils.serverGetEntityMovementTree(rVal)).setServerFallTree(fallTree);
                    }
                    if(ServerJumpTree.getServerJumpTree(rVal)!=null){
                        fallTree.setServerJumpTree(ServerJumpTree.getServerJumpTree(rVal));
                    }
                    rVal.putData(EntityDataStrings.FALL_TREE, fallTree);
                    ServerBehaviorTreeUtils.attachBTreeToEntity(rVal, fallTree);
                    break;
                case WalkMovementSystem.WALK_MOVEMENT_SYSTEM: {
                    ServerWalkTree.attachTree(rVal, (WalkMovementSystem)movementSystem);
                } 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": {
                    ServerAttackTree.attachTree(rVal);
                    rVal.putData(EntityDataStrings.ATTACK_MOVE_TYPE_ACTIVE, null);
                    ServerEntityTagUtils.attachTagToEntity(rVal, EntityTags.ATTACKER);
                    //add all attack moves
                    if(rawType.getAttackMoves() != null && rawType.getAttackMoves().size() > 0){
                        for(AttackMove attackMove : rawType.getAttackMoves()){
                            rVal.putData(attackMove.getType(), rawType.getAttackMoveResolver().getMoveset(attackMove.getType()));
                            // switch(attackMove.getType()){
                            //     case EntityDataStrings.ATTACK_MOVE_TYPE_MELEE_SWING_ONE_HAND:
                            //         rVal.putData(EntityDataStrings.ATTACK_MOVE_TYPE_MELEE_SWING_ONE_HAND, rawType.getAttackMoveResolver().getMoveset(EntityDataStrings.ATTACK_MOVE_TYPE_MELEE_SWING_ONE_HAND));
                            //         break;
                            //     case EntityDataStrings.ATTACK_MOVE_TYPE_BOW_TWO_HAND:
                            //     rVal.putData(EntityDataStrings.ATTACK_MOVE_TYPE_MELEE_SWING_ONE_HAND, rawType.getAttackMoveResolver().getMoveset(EntityDataStrings.ATTACK_MOVE_TYPE_MELEE_SWING_ONE_HAND));
                            //     break;
                            // }
                        }
                    }
                } break;
                case "SHOOTER": {
                    ShooterTree shooterTree = new ShooterTree(rVal);
                    ShooterTree.setShooterTree(rVal, shooterTree);
                    ServerBehaviorTreeUtils.attachBTreeToEntity(rVal, shooterTree);
                } break;
                case "GRAVITY": {
                    Collidable collidable = (Collidable)rVal.getData(EntityDataStrings.PHYSICS_COLLIDABLE);
                    DBody collisionObject = (DBody)rVal.getData(EntityDataStrings.PHYSICS_COLLISION_BODY);
                    ServerGravityTree.attachTree(rVal, collidable, collisionObject, 30);
                    rVal.putData(EntityDataStrings.GRAVITY_ENTITY, true);
                } break;
                case "TARGETABLE": {
                    ServerEntityTagUtils.attachTagToEntity(rVal, EntityTags.TARGETABLE);
                } break;
                case "INVENTORY": {
                    rVal.putData(EntityDataStrings.NATURAL_INVENTORY,UnrelationalInventoryState.createUnrelationalInventory(10));
                    InventoryUtils.serverSetInventoryState(rVal, ServerInventoryState.serverCreateInventoryState(rVal));
                } break;
                case "OUTLINE": {
                    rVal.putData(EntityDataStrings.DRAW_OUTLINE, true);
                } break;
            }
        }
        //variants
        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){
                        //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.getAttributeValue(attributeType.getAttributeId()) != null){
                            String variantId = template.getAttributeValue(attributeType.getAttributeId()).getVariantId();
                            for(AttributeVariant searchVariant : attributeType.getVariants()){
                                if(searchVariant.getId().equals(variantId)){
                                    //if we find the variant, store in on-creature template as well
                                    storedTemplate.putAttributeValue(attributeType.getAttributeId(), variantId);
                                    break;
                                }
                            }
                        }
                        //make sure stored template contains creature data
                        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
                        
                        // rVal.putData(EntityDataStrings.CREATURE_ATTRIBUTE_VARIANT + attributeType.getAttributeId(), variant.getId());
                        // Globals.assetManager.addModelPathToQueue(variant.getModel());
                        // for(String mesh : variant.getMeshes()){
                        //     creatureActor.getMeshMask().queueMesh(variant.getModel(), mesh);
                        // }
                    }
                }
                if(attributeType.getType().equals("bone")){
                    if(staticMorph == null){
                        staticMorph = new ActorStaticMorph();
                        creatureActor.setStaticMorph(staticMorph);
                    }
                    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.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.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.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.putAttributeValue(attributeType.getAttributeId(), midpoint);
                    }
                }
            }
        }
        //set race
        storedTemplate.creatureType = rawType.getCreatureId();
        //store template on creature
        CreatureUtils.setCreatureTemplate(rVal, storedTemplate);
        //rotator system
        if(rawType.getRotatorSystem() != null){
            RotatorSystem system = rawType.getRotatorSystem();
            ServerRotatorTree rotatorTree = new ServerRotatorTree(rVal);
            for(RotatorItem item : system.getRotatorItems()){
                //put actor rotator
                ActorBoneRotator newRotator = new ActorBoneRotator();
                creatureActor.addBoneRotator(item.getBoneName(), newRotator);
                //construct node for tree
                RotatorHierarchyNode hierarchyNode = new RotatorHierarchyNode();
                hierarchyNode.setBone(item.getBoneName());
                for(RotatorConstraint constraint : item.getConstraints()){
                    hierarchyNode.addRotatorConstraint(new electrosphere.entity.state.rotator.RotatorConstraint(constraint));
                }
                rotatorTree.addRotatorNode(hierarchyNode);
            }
            rVal.putData(EntityDataStrings.SERVER_ROTATOR_TREE, rotatorTree);
            ServerBehaviorTreeUtils.attachBTreeToEntity(rVal, rotatorTree);
        }

        //bone groups
        if(rawType.getBoneGroups() != null){
            creatureActor.setBoneGroups(rawType.getBoneGroups());
        }

        ///
        ///
        /// AI (This SHOULD only be applied on the server with the way AI architected currently)
        ///
        ///
        if(rawType.getAITrees() != null){
            Globals.aiManager.attachAI(rVal, rawType.getAITrees());
        }

        //add health system
        if(rawType.getHealthSystem() != null){
            ServerLifeTree.attachTree(rVal, rawType.getHealthSystem());
            ServerEntityTagUtils.attachTagToEntity(rVal, EntityTags.LIFE_STATE);
        }
        //idle tree & generic stuff all creatures have
        if(rawType.getIdleData() != null){
            ServerIdleTree.attachTree(rVal, rawType.getIdleData());
        }
        CreatureUtils.setFacingVector(rVal, MathUtils.getOriginVector());
        rVal.putData(EntityDataStrings.DRAW_CAST_SHADOW, true);
        
        //required for synchronization manager
        ServerEntityTagUtils.attachTagToEntity(rVal, EntityTags.CREATURE);
        EntityUtils.setEntityType(rVal, ENTITY_TYPE_CREATURE);
        EntityUtils.setEntitySubtype(rVal, type);

        //position entity
        //this needs to be called at the end of this function.
        //Burried underneath this is function call to initialize a server side entity.
        //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);

        //error checking
        if(Globals.realmManager.getEntityRealm(rVal) == null){
            LoggerInterface.loggerEngine.ERROR(new IllegalStateException("Created creature without it being assigned to a realm!"));
        }

        return rVal;
    }

    /**
     * Creates a viewmodel entity on the client side
     * @param type The type of creature
     * @param template The template for the creature
     * @return The entity
     */
    public static Entity clientCreateViewModel(
        String type,
        CreatureTemplate template
    ){
        CreatureData rawType = Globals.gameConfigCurrent.getCreatureTypeLoader().getCreature(type);
        Entity rVal = EntityCreationUtils.createClientSpatialEntity();

        if(rawType.getViewModelData() != null){
            EntityCreationUtils.makeEntityDrawable(rVal, rawType.getViewModelData().getFirstPersonModelPath());
            FirstPersonTree.attachTree(
                rVal,
                rawType.getViewModelData().getHeightFromOrigin(),
                rawType.getViewModelData().getCameraViewDirOffsetY(),
                rawType.getViewModelData().getCameraViewDirOffsetZ()
            );
        }
        Actor creatureActor = EntityUtils.getActor(rVal);

        if(rawType.getBoneGroups() != null){
            creatureActor.setBoneGroups(rawType.getBoneGroups());
        }

        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();
        Vector3d position = EntityUtils.getPosition(creature);
        Quaterniond rotation = EntityUtils.getRotation(creature);
        String template = Utilities.stringify(CreatureUtils.getCreatureTemplate(creature));
        NetworkMessage message = EntityMessage.constructSpawnCreatureMessage(
            id,
            template,
            position.x,
            position.y,
            position.z,
            rotation.x,
            rotation.y,
            rotation.z,
            rotation.w
        );
        player.addMessage(message);
        if(CreatureUtils.hasControllerPlayerId(creature)){
            LoggerInterface.loggerNetworking.INFO("Sending controller packets");
            player.addMessage(NetUtils.createSetCreatureControllerIdEntityMessage(creature));
        }
    }
    
    public static void setFacingVector(Entity e, Vector3d vector){
        e.putData(EntityDataStrings.DATA_STRING_FACING_VECTOR, vector);
    }
    
    public static Vector3d getFacingVector(Entity e){
        return (Vector3d)e.getData(EntityDataStrings.DATA_STRING_FACING_VECTOR);
    }
    
    public static float getAcceleration(Entity e){
        return (float)e.getData(EntityDataStrings.DATA_STRING_ACCELERATION);
    }
    
    public static void setAcceleration(Entity e, float scalar){
        e.putData(EntityDataStrings.DATA_STRING_ACCELERATION, scalar);
    }

    public static boolean hasVelocity(Entity e){
    return e.containsKey(EntityDataStrings.DATA_STRING_VELOCITY);
    }
    
    public static float getVelocity(Entity e){
        return (float)e.getData(EntityDataStrings.DATA_STRING_VELOCITY);
    }
    
    public static void setVelocity(Entity e, float scalar){
        e.putData(EntityDataStrings.DATA_STRING_VELOCITY, scalar);
    }
    
    public static float getMaxNaturalVelocity(Entity e){
        return (float)e.getData(EntityDataStrings.DATA_STRING_MAX_NATURAL_VELOCITY);
    }
    
    public static void setMaxNaturalVelocity(Entity e, float scalar){
        e.putData(EntityDataStrings.DATA_STRING_MAX_NATURAL_VELOCITY, scalar);
    }
    
    public static BehaviorTree clientGetEntityMovementTree(Entity e){
        return (BehaviorTree)e.getData(EntityDataStrings.CLIENT_MOVEMENT_BT);
    }

    public static BehaviorTree serverGetEntityMovementTree(Entity e){
        return (BehaviorTree)e.getData(EntityDataStrings.SERVER_MOVEMENT_BT);
    }
    
    public static void clientAttachEntityMessageToMovementTree(Entity e, EntityMessage em){
        BehaviorTree movementTree = clientGetEntityMovementTree(e);
        if(movementTree instanceof ClientGroundMovementTree){
            ((ClientGroundMovementTree)movementTree).addNetworkMessage(em);
        }
    }

    public static void serverAttachEntityMessageToMovementTree(Entity e, EntityMessage em){
        BehaviorTree movementTree = serverGetEntityMovementTree(e);
        if(movementTree instanceof ServerGroundMovementTree){
            ((ServerGroundMovementTree)movementTree).addNetworkMessage(em);
        }
    }
    
    /**
     * Gets the type of creature
     * @param e the entity
     * @return the type
     */
    public static String getType(Entity e){
        return (String)EntityUtils.getEntitySubtype(e);
    }
    
    public static int getControllerPlayerId(Entity e){
        return (int)e.getData(EntityDataStrings.DATA_STRING_CREATURE_CONTROLLER_PLAYER_ID);
    }
    
    public static void setControllerPlayerId(Entity e, int id){
        e.putData(EntityDataStrings.DATA_STRING_CREATURE_CONTROLLER_PLAYER_ID, id);
    }
    
    public static boolean hasControllerPlayerId(Entity e){
        return e.containsKey(EntityDataStrings.DATA_STRING_CREATURE_CONTROLLER_PLAYER_ID);
    }
    
    /**
     * Checks if this entity is a creature
     * @param e The entity
     * @return true if it is a creature, false otherwise
     */
    public static boolean isCreature(Entity e){
        if(!e.containsKey(EntityDataStrings.ENTITY_TYPE)){
            return false;
        }
        return (int)e.getData(EntityDataStrings.ENTITY_TYPE) == ENTITY_TYPE_CREATURE;
    }
    
    public static ClientAttackTree clientGetAttackTree(Entity e){
        return (ClientAttackTree)e.getData(EntityDataStrings.TREE_CLIENTATTACKTREE);
    }

    public static ServerAttackTree serverGetAttackTree(Entity e){
        return (ServerAttackTree)e.getData(EntityDataStrings.TREE_SERVERATTACKTREE);
    }
    
    public static ClientIdleTree getIdleTree(Entity e){
        return (ClientIdleTree)e.getData(EntityDataStrings.TREE_IDLE);
    }
    
    public static SprintTree clientGetSprintTree(Entity e){
        return (SprintTree)e.getData(EntityDataStrings.CLIENT_SPRINT_TREE);
    }

    public static ServerSprintTree serverGetSprintTree(Entity e){
        return (ServerSprintTree)e.getData(EntityDataStrings.SERVER_SPRINT_TREE);
    }

    public static void setCreatureTemplate(Entity e, CreatureTemplate template){
        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){
        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)));
            }
        }
        template.setStateCollection(StateCollection.getStateCollection(e));
        return template;
    }
    
    
}
