// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.npc.role;

import java.util.function.Supplier;
import com.hypixel.hytale.server.core.inventory.Inventory;
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
import com.hypixel.hytale.server.npc.util.InventoryHelper;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import com.hypixel.hytale.server.core.modules.item.ItemModule;
import com.hypixel.hytale.server.npc.util.ComponentInfo;
import com.hypixel.hytale.server.npc.instructions.BodyMotion;
import com.hypixel.hytale.common.util.ArrayUtil;
import java.util.Arrays;
import com.hypixel.hytale.server.core.modules.splitvelocity.VelocityConfig;
import com.hypixel.hytale.protocol.BlockMaterial;
import com.hypixel.hytale.server.npc.util.NPCPhysicsMath;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.core.modules.entity.damage.DeathComponent;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.protocol.MovementStates;
import com.hypixel.hytale.math.random.RandomExtra;
import java.util.Collection;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.server.core.asset.type.model.config.Model;
import java.util.logging.Level;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.server.npc.statetransition.StateTransitionController;
import com.hypixel.hytale.server.npc.util.IAnnotatedComponent;
import java.util.Iterator;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.math.util.TrigMathUtil;
import java.util.HashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.HashSet;
import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport;
import com.hypixel.hytale.server.npc.role.builders.BuilderRole;
import com.hypixel.hytale.server.npc.instructions.Instruction;
import com.hypixel.hytale.server.npc.movement.controllers.MotionController;
import java.util.Map;
import com.hypixel.hytale.server.npc.role.support.RoleStats;
import java.util.List;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import java.util.Set;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.server.npc.movement.GroupSteeringAccumulator;
import com.hypixel.hytale.server.npc.movement.steeringforces.SteeringForceAvoidCollision;
import com.hypixel.hytale.server.npc.movement.Steering;
import com.hypixel.hytale.server.npc.role.support.DebugSupport;
import com.hypixel.hytale.server.npc.role.support.PositionCache;
import com.hypixel.hytale.server.npc.role.support.EntitySupport;
import com.hypixel.hytale.server.npc.role.support.WorldSupport;
import com.hypixel.hytale.server.npc.role.support.MarkedEntitySupport;
import com.hypixel.hytale.server.npc.role.support.StateSupport;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.npc.role.support.CombatSupport;
import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection;

public class Role implements IAnnotatedComponentCollection
{
    public static final double INTERACTION_PLAYER_DISTANCE = 10.0;
    public static final boolean DEBUG_APPLIED_FORCES = false;
    @Nonnull
    protected final CombatSupport combatSupport;
    @Nonnull
    protected final StateSupport stateSupport;
    @Nonnull
    protected final MarkedEntitySupport markedEntitySupport;
    @Nonnull
    protected final WorldSupport worldSupport;
    @Nonnull
    protected final EntitySupport entitySupport;
    @Nonnull
    protected final PositionCache positionCache;
    @Nonnull
    protected final DebugSupport debugSupport;
    protected final int initialMaxHealth;
    protected final double collisionProbeDistance;
    protected final double collisionRadius;
    protected final double collisionForceFalloff;
    protected final float collisionViewAngle;
    protected final float collisionViewHalfAngleCosine;
    protected final Steering bodySteering;
    protected final Steering headSteering;
    protected final SteeringForceAvoidCollision steeringForceAvoidCollision;
    protected final GroupSteeringAccumulator groupSteeringAccumulator;
    protected final Vector3d separation;
    protected final Set<Ref<EntityStore>> ignoredEntitiesForAvoidance;
    protected final double entityAvoidanceStrength;
    protected final AvoidanceMode avoidanceMode;
    protected final boolean isAvoidingEntities;
    protected final double separationDistance;
    protected final double separationWeight;
    protected final double separationDistanceTarget;
    protected final double separationNearRadiusTarget;
    protected final double separationFarRadiusTarget;
    protected final boolean applySeparation;
    protected final Vector3d lastSeparationSteering;
    @Nullable
    protected final float[] headPitchAngleRange;
    protected final boolean stayInEnvironment;
    protected final String allowedEnvironments;
    @Nullable
    protected final String[] flockSpawnTypes;
    protected final boolean flockSpawnTypesRandom;
    @Nonnull
    protected final String[] flockAllowedRoles;
    protected final boolean canLeadFlock;
    protected final double flockWeightAlignment;
    protected final double flockWeightSeparation;
    protected final double flockWeightCohesion;
    protected final double flockInfluenceRange;
    protected final boolean corpseStaysInFlock;
    protected final double inertia;
    protected final double knockbackScale;
    protected final boolean breathesInAir;
    protected final boolean breathesInWater;
    protected final boolean pickupDropOnDeath;
    @Nullable
    protected final String[] hotbarItems;
    @Nullable
    protected final String[] offHandItems;
    protected final double deathAnimationTime;
    protected final float despawnAnimationTime;
    protected final String dropListId;
    @Nullable
    protected final String deathInteraction;
    protected final boolean invulnerable;
    protected final int inventorySlots;
    protected final String inventoryContentsDropList;
    protected final int hotbarSlots;
    protected final int offHandSlots;
    protected final byte defaultOffHandSlot;
    protected final List<DeferredAction> deferredActions;
    protected final RoleStats roleStats;
    @Nullable
    protected final String balanceAsset;
    @Nullable
    protected final Map<String, String> interactionVars;
    protected int roleIndex;
    protected String roleName;
    protected String appearance;
    protected boolean isActivated;
    @Nonnull
    protected Map<String, MotionController> motionControllers;
    protected MotionController activeMotionController;
    protected int[] flockSpawnTypeIndices;
    protected boolean requiresLeashPosition;
    protected boolean hasReachedTerminalAction;
    @Nullable
    protected String[] armor;
    protected boolean[] flags;
    protected Instruction rootInstruction;
    @Nullable
    protected Instruction lastBodyMotionStep;
    @Nullable
    protected Instruction lastHeadMotionStep;
    protected Instruction[] indexedInstructions;
    @Nullable
    protected Instruction interactionInstruction;
    @Nullable
    protected Instruction deathInstruction;
    protected Instruction currentTreeModeStep;
    protected boolean roleChangeRequested;
    protected final boolean isMemory;
    protected final String memoriesNameOverride;
    protected final boolean isMemoriesNameOverriden;
    protected final float spawnLockTime;
    protected final String nameTranslationKey;
    protected boolean backingAway;
    
    public Role(@Nonnull final BuilderRole builder, @Nonnull final BuilderSupport builderSupport) {
        this.bodySteering = new Steering();
        this.headSteering = new Steering();
        this.steeringForceAvoidCollision = new SteeringForceAvoidCollision();
        this.groupSteeringAccumulator = new GroupSteeringAccumulator();
        this.separation = new Vector3d();
        this.ignoredEntitiesForAvoidance = new HashSet<Ref<EntityStore>>();
        this.lastSeparationSteering = new Vector3d();
        this.deferredActions = new ObjectArrayList<DeferredAction>();
        this.motionControllers = new HashMap<String, MotionController>();
        final NPCEntity npcComponent = builderSupport.getEntity();
        this.combatSupport = new CombatSupport(npcComponent, builder, builderSupport);
        this.stateSupport = new StateSupport(builder, builderSupport);
        this.markedEntitySupport = new MarkedEntitySupport(npcComponent);
        this.worldSupport = new WorldSupport(npcComponent, builder, builderSupport);
        this.entitySupport = new EntitySupport(npcComponent, builder);
        this.positionCache = new PositionCache(this);
        this.debugSupport = new DebugSupport(npcComponent, builder);
        this.initialMaxHealth = builder.getMaxHealth(builderSupport);
        this.nameTranslationKey = builder.getNameTranslationKey(builderSupport);
        this.appearance = builder.getAppearance(builderSupport);
        this.hotbarItems = builder.getHotbarItems(builderSupport);
        this.offHandItems = builder.getOffHandItems(builderSupport);
        this.defaultOffHandSlot = builder.getDefaultOffHandSlot(builderSupport);
        this.inventoryContentsDropList = builder.getInventoryItemsDropList(builderSupport);
        this.armor = builder.getArmor();
        this.inertia = builder.getInertia();
        for (final MotionController motionController : this.motionControllers.values()) {
            motionController.setInertia(this.inertia);
        }
        this.knockbackScale = builder.getKnockbackScale(builderSupport);
        for (final MotionController motionController : this.motionControllers.values()) {
            motionController.setKnockbackScale(this.knockbackScale);
        }
        this.positionCache.setOpaqueBlockSet(builder.getOpaqueBlockSet());
        this.dropListId = builder.getDropListId(builderSupport);
        this.isAvoidingEntities = builder.isAvoidingEntities();
        this.avoidanceMode = builder.getAvoidanceMode();
        this.collisionProbeDistance = builder.getCollisionDistance();
        this.collisionForceFalloff = builder.getCollisionForceFalloff();
        this.collisionRadius = builder.getCollisionRadius();
        this.collisionViewAngle = builder.getCollisionViewAngle();
        this.collisionViewHalfAngleCosine = TrigMathUtil.cos(this.collisionViewAngle / 2.0f);
        this.separationDistance = builder.getSeparationDistance();
        this.separationWeight = builder.getSeparationWeight();
        this.separationDistanceTarget = builder.getSeparationDistanceTarget();
        this.separationNearRadiusTarget = builder.getSeparationNearRadiusTarget();
        this.separationFarRadiusTarget = builder.getSeparationFarRadiusTarget();
        this.applySeparation = builder.isApplySeparation(builderSupport);
        if (builder.isOverridingHeadPitchAngle(builderSupport)) {
            this.headPitchAngleRange = builder.getHeadPitchAngleRange(builderSupport);
        }
        else {
            this.headPitchAngleRange = null;
        }
        this.stayInEnvironment = builder.isStayingInEnvironment();
        this.allowedEnvironments = builder.getAllowedEnvironments();
        this.entityAvoidanceStrength = builder.getEntityAvoidanceStrength();
        this.flockSpawnTypes = builder.getFlockSpawnTypes(builderSupport);
        this.flockSpawnTypesRandom = builder.isFlockSpawnTypeRandom(builderSupport);
        this.flockAllowedRoles = builder.getFlockAllowedRoles(builderSupport);
        this.canLeadFlock = builder.isCanLeadFlock(builderSupport);
        this.flockWeightAlignment = builder.getFlockWeightAlignment();
        this.flockWeightSeparation = builder.getFlockWeightSeparation();
        this.flockWeightCohesion = builder.getFlockWeightCohesion();
        this.flockInfluenceRange = builder.getFlockInfluenceRange();
        this.invulnerable = builder.isInvulnerable(builderSupport);
        this.breathesInAir = builder.isBreathesInAir(builderSupport);
        this.breathesInWater = builder.isBreathesInWater(builderSupport);
        this.pickupDropOnDeath = builder.isPickupDropOnDeath();
        this.deathAnimationTime = builder.getDeathAnimationTime();
        this.deathInteraction = builder.getDeathInteraction(builderSupport);
        this.despawnAnimationTime = builder.getDespawnAnimationTime();
        this.inventorySlots = builder.getInventorySlots();
        this.hotbarSlots = builder.getHotbarSlots();
        this.offHandSlots = builder.getOffHandSlots();
        this.corpseStaysInFlock = builder.isCorpseStaysInFlock();
        this.roleStats = builderSupport.getRoleStats();
        this.balanceAsset = builder.getBalanceAsset(builderSupport);
        this.interactionVars = builder.getInteractionVars(builderSupport);
        this.isMemory = builder.isMemory(builderSupport.getExecutionContext());
        this.memoriesNameOverride = builder.getMemoriesNameOverride(builderSupport.getExecutionContext());
        this.isMemoriesNameOverriden = (this.memoriesNameOverride != null && !this.memoriesNameOverride.isEmpty());
        this.spawnLockTime = builder.getSpawnLockTime(builderSupport);
        this.entitySupport.pickRandomDisplayName(builderSupport.getHolder(), false);
        List<Instruction> instructionList = builder.getInstructionList(builderSupport);
        if (instructionList == null) {
            instructionList = new ObjectArrayList<Instruction>();
        }
        final Instruction[] instructions = instructionList.toArray(Instruction[]::new);
        this.rootInstruction = Instruction.createRootInstruction(instructions, builderSupport);
        this.interactionInstruction = builder.getInteractionInstruction(builderSupport);
        this.deathInstruction = builder.getDeathInstruction(builderSupport);
        builder.registerStateEvaluator(builderSupport);
        this.setMotionControllers(builderSupport.getEntity(), builder.getMotionControllerMap(builderSupport), builder.getInitialMotionController(builderSupport));
        if (this.interactionInstruction != null) {
            builderSupport.trackInteractions();
        }
    }
    
    public int getInitialMaxHealth() {
        return this.initialMaxHealth;
    }
    
    public boolean isAvoidingEntities() {
        return this.isAvoidingEntities;
    }
    
    public double getCollisionProbeDistance() {
        return this.collisionProbeDistance;
    }
    
    public boolean isApplySeparation() {
        return this.applySeparation;
    }
    
    public double getSeparationDistance() {
        return this.separationDistance;
    }
    
    public Instruction getRootInstruction() {
        return this.rootInstruction;
    }
    
    @Nullable
    public Instruction getInteractionInstruction() {
        return this.interactionInstruction;
    }
    
    @Nullable
    public Instruction getDeathInstruction() {
        return this.deathInstruction;
    }
    
    @Nonnull
    public Steering getBodySteering() {
        return this.bodySteering;
    }
    
    @Nonnull
    public Steering getHeadSteering() {
        return this.headSteering;
    }
    
    @Nonnull
    public Set<Ref<EntityStore>> getIgnoredEntitiesForAvoidance() {
        return this.ignoredEntitiesForAvoidance;
    }
    
    public String getDropListId() {
        return this.dropListId;
    }
    
    @Nullable
    public String getBalanceAsset() {
        return this.balanceAsset;
    }
    
    @Nullable
    public Map<String, String> getInteractionVars() {
        return this.interactionVars;
    }
    
    public boolean isMemory() {
        return this.isMemory;
    }
    
    public String getMemoriesNameOverride() {
        return this.memoriesNameOverride;
    }
    
    public String getNameTranslationKey() {
        return this.nameTranslationKey;
    }
    
    public boolean isMemoriesNameOverriden() {
        return this.isMemoriesNameOverriden;
    }
    
    public float getSpawnLockTime() {
        return this.spawnLockTime;
    }
    
    public void postRoleBuilt(@Nonnull final BuilderSupport builderSupport) {
        this.requiresLeashPosition = builderSupport.requiresLeashPosition();
        this.flags = builderSupport.allocateFlags();
        this.indexedInstructions = builderSupport.getInstructionSlotMappings();
        this.stateSupport.postRoleBuilt(builderSupport);
        this.worldSupport.postRoleBuilt(builderSupport);
        this.entitySupport.postRoleBuilt(builderSupport);
        this.markedEntitySupport.postRoleBuilder(builderSupport);
        this.rootInstruction.setContext(this, 0);
    }
    
    public void loaded() {
        this.rootInstruction.loaded(this);
        if (this.interactionInstruction != null) {
            this.interactionInstruction.loaded(this);
        }
        if (this.deathInstruction != null) {
            this.deathInstruction.loaded(this);
        }
        final StateTransitionController stateTransitions = this.stateSupport.getStateTransitionController();
        if (stateTransitions != null) {
            stateTransitions.loaded(this);
        }
    }
    
    public void spawned(@Nonnull final Holder<EntityStore> holder, @Nonnull final NPCEntity npcComponent) {
        final MotionController activeMotionController = this.getActiveMotionController();
        if (activeMotionController != null) {
            activeMotionController.spawned();
        }
        this.entitySupport.pickRandomDisplayName(holder, true);
        this.rootInstruction.spawned(this);
        if (this.interactionInstruction != null) {
            this.interactionInstruction.spawned(this);
        }
        if (this.deathInstruction != null) {
            this.deathInstruction.spawned(this);
        }
        final StateTransitionController stateTransitions = this.stateSupport.getStateTransitionController();
        if (stateTransitions != null) {
            stateTransitions.spawned(this);
        }
        this.initialiseInventories(npcComponent);
    }
    
    public void unloaded() {
        this.worldSupport.unloaded();
        this.markedEntitySupport.unloaded();
        this.deferredActions.clear();
        this.rootInstruction.unloaded(this);
        if (this.interactionInstruction != null) {
            this.interactionInstruction.unloaded(this);
        }
        if (this.deathInstruction != null) {
            this.deathInstruction.unloaded(this);
        }
        final StateTransitionController stateTransitions = this.stateSupport.getStateTransitionController();
        if (stateTransitions != null) {
            stateTransitions.unloaded(this);
        }
    }
    
    public void removed() {
        this.worldSupport.resetAllBlockSensors();
        this.rootInstruction.removed(this);
        if (this.interactionInstruction != null) {
            this.interactionInstruction.removed(this);
        }
        if (this.deathInstruction != null) {
            this.deathInstruction.removed(this);
        }
        final StateTransitionController stateTransitions = this.stateSupport.getStateTransitionController();
        if (stateTransitions != null) {
            stateTransitions.removed(this);
        }
    }
    
    public void teleported(@Nonnull final World from, @Nonnull final World to) {
        this.rootInstruction.teleported(this, from, to);
        if (this.interactionInstruction != null) {
            this.interactionInstruction.teleported(this, from, to);
        }
        if (this.deathInstruction != null) {
            this.deathInstruction.teleported(this, from, to);
        }
        final StateTransitionController stateTransitions = this.stateSupport.getStateTransitionController();
        if (stateTransitions != null) {
            stateTransitions.teleported(this, from, to);
        }
    }
    
    public String getAppearanceName() {
        return this.appearance;
    }
    
    public MotionController getActiveMotionController() {
        return this.activeMotionController;
    }
    
    @Nonnull
    public CombatSupport getCombatSupport() {
        return this.combatSupport;
    }
    
    @Nonnull
    public StateSupport getStateSupport() {
        return this.stateSupport;
    }
    
    @Nonnull
    public WorldSupport getWorldSupport() {
        return this.worldSupport;
    }
    
    @Nonnull
    public MarkedEntitySupport getMarkedEntitySupport() {
        return this.markedEntitySupport;
    }
    
    @Nonnull
    public PositionCache getPositionCache() {
        return this.positionCache;
    }
    
    @Nonnull
    public EntitySupport getEntitySupport() {
        return this.entitySupport;
    }
    
    @Nonnull
    public DebugSupport getDebugSupport() {
        return this.debugSupport;
    }
    
    public boolean isRoleChangeRequested() {
        return this.roleChangeRequested;
    }
    
    public void setRoleChangeRequested() {
        this.roleChangeRequested = true;
    }
    
    public boolean setActiveMotionController(@Nullable final Ref<EntityStore> ref, @Nonnull final NPCEntity npcComponent, @Nonnull final String name, @Nullable final ComponentAccessor<EntityStore> componentAccessor) {
        final MotionController motionController = this.motionControllers.get(name);
        if (motionController == null) {
            NPCPlugin.get().getLogger().at(Level.SEVERE).log("Failed to set MotionController for NPC of type '%s': MotionController '%s' not found! ", this.getRoleName(), name);
            return false;
        }
        this.setActiveMotionController(ref, npcComponent, motionController, componentAccessor);
        return true;
    }
    
    public void setActiveMotionController(@Nullable final Ref<EntityStore> ref, @Nonnull final NPCEntity npcComponent, @Nonnull final MotionController motionController, @Nullable final ComponentAccessor<EntityStore> componentAccessor) {
        if (this.activeMotionController != motionController) {
            if (this.activeMotionController != null) {
                this.activeMotionController.deactivate();
            }
            (this.activeMotionController = motionController).activate();
            this.motionControllerChanged(ref, npcComponent, this.activeMotionController, componentAccessor);
        }
    }
    
    protected void motionControllerChanged(@Nullable final Ref<EntityStore> ref, @Nonnull final NPCEntity npcComponent, @Nullable final MotionController motionController, @Nullable final ComponentAccessor<EntityStore> componentAccessor) {
        this.rootInstruction.motionControllerChanged(ref, npcComponent, motionController, componentAccessor);
        if (this.deathInstruction != null) {
            this.deathInstruction.motionControllerChanged(ref, npcComponent, motionController, componentAccessor);
        }
        if (this.interactionInstruction != null) {
            this.interactionInstruction.motionControllerChanged(ref, npcComponent, motionController, componentAccessor);
        }
        final StateTransitionController stateTransitions = this.stateSupport.getStateTransitionController();
        if (stateTransitions != null) {
            stateTransitions.motionControllerChanged(ref, npcComponent, motionController, componentAccessor);
        }
    }
    
    public void setMotionControllers(@Nonnull final NPCEntity npcComponent, @Nonnull final Map<String, MotionController> motionControllers, @Nullable final String initialMotionController) {
        this.motionControllers = motionControllers;
        this.updateMotionControllers(null, null, null, null);
        if (!this.motionControllers.isEmpty()) {
            if (initialMotionController != null && this.setActiveMotionController(null, npcComponent, initialMotionController, null)) {
                return;
            }
            this.setActiveMotionController(null, npcComponent, RandomExtra.randomElement(new ObjectArrayList<MotionController>(motionControllers.values())), null);
        }
    }
    
    public void updateMotionControllers(@Nullable final Ref<EntityStore> ref, @Nullable final Model model, @Nullable final Box boundingBox, @Nullable final ComponentAccessor<EntityStore> componentAccessor) {
        for (final MotionController motionController : this.motionControllers.values()) {
            motionController.setRole(this);
            motionController.setInertia(this.inertia);
            motionController.setKnockbackScale(this.knockbackScale);
            motionController.setHeadPitchAngleRange(this.headPitchAngleRange);
            if (boundingBox != null && model != null) {
                motionController.updateModelParameters(ref, model, boundingBox, componentAccessor);
                motionController.updatePhysicsValues(model.getPhysicsValues());
            }
        }
    }
    
    public void updateMovementState(@Nonnull final Ref<EntityStore> ref, @Nonnull final MovementStates movementStates, @Nonnull final Vector3d velocity, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        if (this.activeMotionController != null) {
            this.activeMotionController.updateMovementState(ref, movementStates, this.bodySteering, velocity, componentAccessor);
        }
    }
    
    public void tick(@Nonnull final Ref<EntityStore> ref, final float tickTime, @Nonnull final Store<EntityStore> store) {
        int i = 0;
        while (i < this.deferredActions.size()) {
            final DeferredAction action = this.deferredActions.get(i);
            if (action.tick(ref, this, tickTime, store)) {
                this.deferredActions.remove(i);
            }
            else {
                ++i;
            }
        }
        this.computeActionsAndSteering(ref, tickTime, this.bodySteering, this.headSteering, store);
    }
    
    public void addDeferredAction(@Nonnull final DeferredAction handler) {
        this.deferredActions.add(handler);
    }
    
    protected void computeActionsAndSteering(@Nonnull final Ref<EntityStore> ref, final double tickTime, @Nonnull final Steering bodySteering, @Nonnull final Steering headSteering, @Nonnull final Store<EntityStore> store) {
        final boolean isDead = store.getArchetype(ref).contains(DeathComponent.getComponentType());
        if (isDead) {
            if (this.deathInstruction != null) {
                this.deathInstruction.execute(ref, this, tickTime, store);
            }
            return;
        }
        if (this.interactionInstruction != null) {
            this.positionCache.forEachPlayer((d, _playerRef, _this, _selfRef, _store) -> {
                _this.stateSupport.setInteractionIterationTarget(_playerRef);
                _this.interactionInstruction.execute(_selfRef, _this, d, _store);
                return;
            }, this, ref, store, tickTime, store);
            this.stateSupport.setInteractionIterationTarget(null);
            this.entitySupport.clearTargetPlayerActiveTasks();
        }
        this.getActiveMotionController().beforeInstructionSensorsAndActions(tickTime);
        this.entitySupport.clearNextBodyMotionStep();
        this.entitySupport.clearNextHeadMotionStep();
        if (!this.stateSupport.runTransitionActions(ref, this, tickTime, store)) {
            this.rootInstruction.execute(ref, this, tickTime, store);
        }
        final NPCEntity npcComponent = store.getComponent(ref, NPCEntity.getComponentType());
        assert npcComponent != null;
        if (npcComponent.isPlayingDespawnAnim()) {
            return;
        }
        this.getActiveMotionController().beforeInstructionMotion(tickTime);
        final Instruction nextBodyMotionStep = this.entitySupport.getNextBodyMotionStep();
        if (nextBodyMotionStep != this.lastBodyMotionStep) {
            if (this.lastBodyMotionStep != null) {
                this.lastBodyMotionStep.getBodyMotion().deactivate(ref, this, store);
                this.lastBodyMotionStep.onEndMotion();
            }
            if (nextBodyMotionStep != null) {
                nextBodyMotionStep.getBodyMotion().activate(ref, this, store);
            }
        }
        this.lastBodyMotionStep = nextBodyMotionStep;
        final Instruction nextHeadMotionStep = this.entitySupport.getNextHeadMotionStep();
        if (nextHeadMotionStep != this.lastHeadMotionStep) {
            if (this.lastHeadMotionStep != null) {
                this.lastHeadMotionStep.getHeadMotion().deactivate(ref, this, store);
                this.lastHeadMotionStep.onEndMotion();
            }
            if (nextHeadMotionStep != null) {
                nextHeadMotionStep.getHeadMotion().activate(ref, this, store);
            }
        }
        this.lastHeadMotionStep = nextHeadMotionStep;
        if (nextBodyMotionStep != null) {
            nextBodyMotionStep.getBodyMotion().computeSteering(ref, this, nextBodyMotionStep.getSensor().getSensorInfo(), tickTime, bodySteering, store);
        }
        if (nextHeadMotionStep != null) {
            nextHeadMotionStep.getHeadMotion().computeSteering(ref, this, nextHeadMotionStep.getSensor().getSensorInfo(), tickTime, headSteering, store);
        }
    }
    
    public void blendSeparation(@Nonnull final Ref<EntityStore> selfRef, @Nonnull final Vector3d position, @Nonnull final Steering steering, @Nonnull final ComponentType<EntityStore, TransformComponent> transformComponentType, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        this.lastSeparationSteering.assign(Vector3d.ZERO);
        double maxRange = this.separationDistance;
        final Ref<EntityStore> targetRef = this.markedEntitySupport.getTargetReferenceToIgnoreForAvoidance();
        if (targetRef != null && targetRef.isValid()) {
            final TransformComponent targetTransformComponent = commandBuffer.getComponent(targetRef, transformComponentType);
            assert targetTransformComponent != null;
            final double distance = targetTransformComponent.getPosition().distanceSquaredTo(position);
            if (distance <= this.separationNearRadiusTarget * this.separationNearRadiusTarget) {
                maxRange = this.separationDistanceTarget;
            }
            else if (distance < this.separationFarRadiusTarget * this.separationFarRadiusTarget) {
                final double s = (Math.sqrt(distance) - this.separationNearRadiusTarget) / (this.separationFarRadiusTarget - this.separationNearRadiusTarget);
                maxRange = NPCPhysicsMath.lerp(this.separationDistanceTarget, this.separationDistance, s);
            }
        }
        this.groupSteeringAccumulator.setComponentSelector(this.activeMotionController.getComponentSelector());
        this.groupSteeringAccumulator.setMaxRange(maxRange);
        this.groupSteeringAccumulator.setViewConeHalfAngleCosine(this.collisionViewHalfAngleCosine);
        this.groupSteeringAccumulator.begin(selfRef, commandBuffer);
        this.positionCache.forEachEntityInAvoidanceRange(this.ignoredEntitiesForAvoidance, (ref, _groupSteeringAccumulator, _role, _buffer) -> _groupSteeringAccumulator.processEntity(ref, this.separationWeight, 1.0, 1.0, _buffer), this.groupSteeringAccumulator, this, commandBuffer);
        this.groupSteeringAccumulator.end();
        if (this.groupSteeringAccumulator.getCount() > 0) {
            final Vector3d sumOfDistances = this.groupSteeringAccumulator.getSumOfDistances();
            if (sumOfDistances.squaredLength() > 1.0000000000000002E-10) {
                final double speed = steering.getSpeed();
                this.separation.assign(sumOfDistances).setLength(-0.5);
                this.lastSeparationSteering.assign(this.separation);
                if (speed > 0.0) {
                    this.separation.add(steering.getTranslation());
                    this.separation.setLength(speed);
                }
                steering.setTranslation(this.separation);
            }
        }
    }
    
    @Nonnull
    public Vector3d getLastSeparationSteering() {
        return this.lastSeparationSteering;
    }
    
    public void blendAvoidance(@Nonnull final Ref<EntityStore> ref, @Nonnull final Vector3d position, @Nonnull final Steering steering, @Nonnull final CommandBuffer<EntityStore> commandBuffer) {
        this.steeringForceAvoidCollision.setDebug(this.debugSupport.isDebugRoleSteering());
        this.steeringForceAvoidCollision.setAvoidanceMode(this.getAvoidanceMode());
        this.steeringForceAvoidCollision.setSelf(ref, position, commandBuffer);
        if (!this.activeMotionController.estimateVelocity(steering, this.steeringForceAvoidCollision.getSelfVelocity())) {
            this.steeringForceAvoidCollision.setVelocityFromEntity(ref, commandBuffer);
        }
        if (this.collisionRadius >= 0.0) {
            this.steeringForceAvoidCollision.setSelfRadius(this.collisionRadius);
        }
        this.steeringForceAvoidCollision.setMaxDistance(this.collisionProbeDistance);
        this.steeringForceAvoidCollision.setFalloff(this.collisionForceFalloff);
        this.steeringForceAvoidCollision.setComponentSelector(this.activeMotionController.getComponentSelector());
        this.steeringForceAvoidCollision.reset();
        this.positionCache.forEachEntityInAvoidanceRange(this.ignoredEntitiesForAvoidance, (_ref, _steeringForceAvoidCollision, _buffer) -> _steeringForceAvoidCollision.add(_ref, _buffer), this.steeringForceAvoidCollision, commandBuffer);
        this.steeringForceAvoidCollision.compute(steering);
    }
    
    @Nonnull
    public Vector3d getLastAvoidanceSteering() {
        return this.steeringForceAvoidCollision.getLastSteeringDirection();
    }
    
    public void resetInstruction(final int instruction) {
        this.indexedInstructions[instruction].reset();
    }
    
    public String getRoleName() {
        return this.roleName;
    }
    
    public int getRoleIndex() {
        return this.roleIndex;
    }
    
    public void setRoleIndex(final int roleIndex, @Nonnull final String roleName) {
        this.roleIndex = roleIndex;
        this.roleName = roleName;
    }
    
    public boolean isInvulnerable() {
        return this.invulnerable;
    }
    
    public boolean isBreathesInAir() {
        return this.breathesInAir;
    }
    
    public boolean isBreathesInWater() {
        return this.breathesInWater;
    }
    
    public double getInertia() {
        return this.inertia;
    }
    
    public double getKnockbackScale() {
        return this.knockbackScale;
    }
    
    public boolean canBreathe(@Nonnull final BlockMaterial breathingMaterial, final int fluidId) {
        return this.isInvulnerable() || this.couldBreathe(breathingMaterial, fluidId);
    }
    
    public boolean couldBreathe(@Nonnull final BlockMaterial breathingMaterial, final int fluidId) {
        if (fluidId != 0) {
            return this.breathesInWater;
        }
        return breathingMaterial == BlockMaterial.Empty && this.breathesInAir;
    }
    
    public boolean couldBreatheCached() {
        return this.positionCache.couldBreatheCached();
    }
    
    public void addForce(@Nonnull final Vector3d velocity, @Nullable final VelocityConfig velocityConfig) {
        if (this.activeMotionController != null) {
            this.activeMotionController.addForce(velocity, velocityConfig);
        }
    }
    
    public void forceVelocity(@Nonnull final Vector3d velocity, @Nullable final VelocityConfig velocityConfig, final boolean ignoreDamping) {
        if (this.activeMotionController != null) {
            this.activeMotionController.forceVelocity(velocity, velocityConfig, ignoreDamping);
        }
    }
    
    public void processAddVelocityInstruction(@Nonnull final Vector3d velocity, @Nullable final VelocityConfig velocityConfig) {
        if (this.activeMotionController != null) {
            this.activeMotionController.addForce(velocity, velocityConfig);
        }
    }
    
    public void processSetVelocityInstruction(@Nonnull final Vector3d velocity, @Nullable final VelocityConfig velocityConfig) {
        if (this.activeMotionController != null) {
            this.activeMotionController.forceVelocity(Vector3d.ZERO, null, false);
            this.activeMotionController.addForce(velocity, velocityConfig);
        }
    }
    
    public boolean isOnGround() {
        return this.getActiveMotionController() != null && this.getActiveMotionController().onGround();
    }
    
    public void setArmor(@Nonnull final NPCEntity npcComponent, @Nullable final String[] armor) {
        this.armor = armor;
        if (armor != null) {
            for (final String s : armor) {
                RoleUtils.setArmor(npcComponent, s);
            }
        }
    }
    
    public boolean isPickupDropOnDeath() {
        return this.pickupDropOnDeath;
    }
    
    public boolean requiresLeashPosition() {
        return this.requiresLeashPosition;
    }
    
    public void clearOnce() {
        this.rootInstruction.clearOnce();
        if (this.interactionInstruction != null) {
            this.interactionInstruction.clearOnce();
        }
        if (this.deathInstruction != null) {
            this.deathInstruction.clearOnce();
        }
        this.stateSupport.pollNeedClearOnce();
    }
    
    public void clearOnceIfNeeded() {
        if (this.stateSupport.pollNeedClearOnce()) {
            this.clearOnce();
            this.stateSupport.resetLocalStateMachines();
        }
    }
    
    public void setMarkedTarget(@Nonnull final String targetSlot, @Nonnull final Ref<EntityStore> target) {
        this.markedEntitySupport.setMarkedEntity(targetSlot, target);
    }
    
    public boolean isFriendly(@Nonnull final Ref<EntityStore> ref, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        return !this.combatSupport.getCanCauseDamage(ref, componentAccessor);
    }
    
    public boolean isIgnoredForAvoidance(@Nonnull final Ref<EntityStore> entityReference) {
        return this.ignoredEntitiesForAvoidance.contains(entityReference);
    }
    
    public AvoidanceMode getAvoidanceMode() {
        return this.avoidanceMode;
    }
    
    public double getCollisionRadius() {
        return this.collisionRadius;
    }
    
    public int[] getFlockSpawnTypes() {
        if (this.flockSpawnTypeIndices != null) {
            return this.flockSpawnTypeIndices;
        }
        final int length = (this.flockSpawnTypes == null) ? 0 : this.flockSpawnTypes.length;
        this.flockSpawnTypeIndices = new int[length];
        for (int i = 0; i < length; ++i) {
            final int index = NPCPlugin.get().getIndex(this.flockSpawnTypes[i]);
            if (index == Integer.MIN_VALUE) {
                throw new IllegalStateException(String.format("Role %s contains unknown FlockSpawnTypes NPC %s", this.roleName, this.flockSpawnTypes[i]));
            }
            this.flockSpawnTypeIndices[i] = index;
        }
        return this.flockSpawnTypeIndices;
    }
    
    @Nonnull
    public String[] getFlockAllowedRoles() {
        return (this.flockAllowedRoles != null) ? Arrays.copyOf(this.flockAllowedRoles, this.flockAllowedRoles.length) : ArrayUtil.EMPTY_STRING_ARRAY;
    }
    
    public boolean isFlockSpawnTypesRandom() {
        return this.flockSpawnTypesRandom;
    }
    
    public boolean isCanLeadFlock() {
        return this.canLeadFlock;
    }
    
    public double getFlockInfluenceRange() {
        return this.flockInfluenceRange;
    }
    
    public double getDeathAnimationTime() {
        return this.deathAnimationTime;
    }
    
    @Nullable
    public String getDeathInteraction() {
        return this.deathInteraction;
    }
    
    public float getDespawnAnimationTime() {
        return this.despawnAnimationTime;
    }
    
    public void setReachedTerminalAction(final boolean hasReached) {
        this.hasReachedTerminalAction = hasReached;
    }
    
    public boolean hasReachedTerminalAction() {
        return this.hasReachedTerminalAction;
    }
    
    public void setFlag(final int index, final boolean value) {
        if (this.flags == null) {
            throw new NullPointerException(String.format("Trying to set a flag in role %s but flags are null", this.getRoleName()));
        }
        if (index < 0 || index >= this.flags.length) {
            throw new IllegalArgumentException(String.format("Flag value cannot be less than 0 and must be less than array length %s. Value was %s", this.flags.length, index));
        }
        this.flags[index] = value;
    }
    
    public boolean isFlagSet(final int index) {
        return this.flags != null && index >= 0 && index < this.flags.length && this.flags[index];
    }
    
    public boolean isBackingAway() {
        return this.backingAway;
    }
    
    public void setBackingAway(final boolean backingAway) {
        this.backingAway = backingAway;
    }
    
    public Instruction swapTreeModeSteps(final Instruction newStep) {
        final Instruction old = this.currentTreeModeStep;
        this.currentTreeModeStep = newStep;
        return old;
    }
    
    public void notifySensorMatch() {
        if (this.currentTreeModeStep == null) {
            return;
        }
        this.currentTreeModeStep.notifyChildSensorMatch();
    }
    
    public void resetAllInstructions() {
        for (final Instruction instruction : this.indexedInstructions) {
            instruction.reset();
        }
    }
    
    @Nullable
    public String getSteeringMotionName() {
        if (this.lastBodyMotionStep == null) {
            return null;
        }
        BodyMotion motion = this.lastBodyMotionStep.getBodyMotion();
        if (motion != null) {
            motion = motion.getSteeringMotion();
        }
        return (motion == null) ? null : motion.getClass().getSimpleName();
    }
    
    @Override
    public int componentCount() {
        return 1;
    }
    
    @Override
    public IAnnotatedComponent getComponent(final int index) {
        return this.rootInstruction;
    }
    
    @Override
    public void getInfo(final Role role, final ComponentInfo holder) {
    }
    
    @Override
    public int getIndex() {
        throw new UnsupportedOperationException("Roles do not have component indexes!");
    }
    
    @Override
    public void setContext(final IAnnotatedComponent parent, final int index) {
        throw new UnsupportedOperationException("Roles do not have parent contexts!");
    }
    
    @Nullable
    @Override
    public IAnnotatedComponent getParent() {
        return null;
    }
    
    @Override
    public String getLabel() {
        return this.roleName;
    }
    
    private void initialiseInventories(@Nonnull final NPCEntity npcComponent) {
        List<ItemStack> inventoryItems = null;
        if (this.inventoryContentsDropList != null) {
            final ItemModule itemModule = ItemModule.get();
            if (itemModule.isEnabled()) {
                inventoryItems = itemModule.getRandomItemDrops(this.inventoryContentsDropList);
            }
        }
        final int inventorySlots = (inventoryItems != null && inventoryItems.size() > this.inventorySlots) ? inventoryItems.size() : this.inventorySlots;
        if (inventorySlots > 0 || this.hotbarSlots > 3 || this.offHandSlots > 0) {
            npcComponent.setInventorySize(this.hotbarSlots, inventorySlots, this.offHandSlots);
        }
        if (inventoryItems != null) {
            final ItemContainer inventory = npcComponent.getInventory().getStorage();
            for (final ItemStack item : inventoryItems) {
                inventory.addItemStack(item);
            }
        }
        if (this.hotbarItems != null && this.hotbarItems.length > 0 && npcComponent.getInventory().getHotbar().isEmpty()) {
            final Inventory inventory2 = npcComponent.getInventory();
            final ItemContainer hotbar = inventory2.getHotbar();
            for (byte i = 0; i < this.hotbarItems.length; ++i) {
                if (this.hotbarItems[i] != null) {
                    if (this.hotbarItems[i].startsWith("Droplist:")) {
                        if (!InventoryHelper.checkHotbarSlot(inventory2, i)) {
                            continue;
                        }
                        final List<ItemStack> items = ItemModule.get().getRandomItemDrops(this.hotbarItems[i].substring("Droplist:".length()));
                        hotbar.setItemStackForSlot(i, items.get(RandomExtra.randomRange(items.size())));
                    }
                    InventoryHelper.setHotbarItem(inventory2, this.hotbarItems[i], i);
                }
            }
        }
        if (this.offHandItems != null && this.offHandItems.length > 0) {
            RoleUtils.setOffHandItems(npcComponent, this.offHandItems);
        }
        if (this.defaultOffHandSlot >= 0) {
            InventoryHelper.setOffHandSlot(npcComponent.getInventory(), this.defaultOffHandSlot);
        }
        this.setArmor(npcComponent, this.armor);
    }
    
    public boolean isCorpseStaysInFlock() {
        return this.corpseStaysInFlock;
    }
    
    public void onLoadFromWorldGenOrPrefab(@Nonnull final Ref<EntityStore> ref, @Nonnull final NPCEntity npcComponent, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        this.entitySupport.pickRandomDisplayName(ref, true, componentAccessor);
        this.initialiseInventories(npcComponent);
    }
    
    public RoleStats getRoleStats() {
        return this.roleStats;
    }
    
    public enum AvoidanceMode implements Supplier<String>
    {
        Slowdown("Only slow down NPC"), 
        Evade("Only evade"), 
        Any("Any avoidance allowed");
        
        @Nonnull
        private final String description;
        
        private AvoidanceMode(final String description) {
            this.description = description;
        }
        
        @Nonnull
        @Override
        public String get() {
            return this.description;
        }
    }
    
    @FunctionalInterface
    public interface DeferredAction
    {
        boolean tick(@Nonnull final Ref<EntityStore> p0, @Nonnull final Role p1, final double p2, @Nonnull final Store<EntityStore> p3);
    }
}
