package com.hypixel.hytale.server.npc;

import com.hypixel.hytale.assetstore.AssetMap;
import com.hypixel.hytale.assetstore.AssetPack;
import com.hypixel.hytale.assetstore.AssetRegistry;
import com.hypixel.hytale.assetstore.event.LoadedAssetsEvent;
import com.hypixel.hytale.assetstore.event.RemovedAssetsEvent;
import com.hypixel.hytale.assetstore.map.DefaultAssetMap;
import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap;
import com.hypixel.hytale.builtin.path.path.TransientPathDefinition;
import com.hypixel.hytale.builtin.path.waypoint.RelativeWaypointDefinition;
import com.hypixel.hytale.builtin.tagset.TagSetPlugin;
import com.hypixel.hytale.builtin.tagset.config.NPCGroup;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.codec.schema.config.Schema;
import com.hypixel.hytale.common.benchmark.TimeDistributionRecorder;
import com.hypixel.hytale.common.util.FormatUtil;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.component.ComponentRegistryProxy;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.RemoveReason;
import com.hypixel.hytale.component.ResourceType;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.dependency.Dependency;
import com.hypixel.hytale.component.dependency.Order;
import com.hypixel.hytale.component.dependency.SystemDependency;
import com.hypixel.hytale.component.spatial.KDTree;
import com.hypixel.hytale.component.spatial.SpatialResource;
import com.hypixel.hytale.event.EventRegistry;
import com.hypixel.hytale.function.consumer.TriConsumer;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.logger.sentry.SkipSentryException;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.Options;
import com.hypixel.hytale.server.core.asset.AssetModule;
import com.hypixel.hytale.server.core.asset.AssetPackRegisterEvent;
import com.hypixel.hytale.server.core.asset.AssetPackUnregisterEvent;
import com.hypixel.hytale.server.core.asset.GenerateSchemaEvent;
import com.hypixel.hytale.server.core.asset.HytaleAssetStore;
import com.hypixel.hytale.server.core.asset.LoadAssetEvent;
import com.hypixel.hytale.server.core.asset.type.item.config.Item;
import com.hypixel.hytale.server.core.asset.type.model.config.Model;
import com.hypixel.hytale.server.core.asset.type.model.config.ModelAsset;
import com.hypixel.hytale.server.core.asset.type.responsecurve.config.ResponseCurve;
import com.hypixel.hytale.server.core.entity.UUIDComponent;
import com.hypixel.hytale.server.core.modules.entity.EntityModule;
import com.hypixel.hytale.server.core.modules.entity.component.DisplayNameComponent;
import com.hypixel.hytale.server.core.modules.entity.component.HeadRotation;
import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent;
import com.hypixel.hytale.server.core.modules.entity.component.PersistentModel;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap;
import com.hypixel.hytale.server.core.modules.entitystats.EntityStatsSystems;
import com.hypixel.hytale.server.core.modules.entitystats.asset.EntityStatType;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction;
import com.hypixel.hytale.server.core.modules.migrations.MigrationModule;
import com.hypixel.hytale.server.core.modules.time.WorldTimeResource;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import com.hypixel.hytale.server.core.universe.Universe;
import com.hypixel.hytale.server.core.universe.world.npc.INonPlayerCharacter;
import com.hypixel.hytale.server.core.universe.world.path.WorldPathChangedEvent;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.core.util.Config;
import com.hypixel.hytale.server.flock.FlockPlugin;
import com.hypixel.hytale.server.flock.config.FlockAsset;
import com.hypixel.hytale.server.migrations.RenameSpawnMarkerMigration;
import com.hypixel.hytale.server.npc.asset.builder.Builder;
import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptor;
import com.hypixel.hytale.server.npc.asset.builder.BuilderFactory;
import com.hypixel.hytale.server.npc.asset.builder.BuilderInfo;
import com.hypixel.hytale.server.npc.asset.builder.BuilderManager;
import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport;
import com.hypixel.hytale.server.npc.blackboard.Blackboard;
import com.hypixel.hytale.server.npc.blackboard.view.attitude.AttitudeMap;
import com.hypixel.hytale.server.npc.blackboard.view.attitude.ItemAttitudeMap;
import com.hypixel.hytale.server.npc.blackboard.view.combat.CombatViewSystems;
import com.hypixel.hytale.server.npc.commands.NPCCommand;
import com.hypixel.hytale.server.npc.commands.NPCRunTestsCommand;
import com.hypixel.hytale.server.npc.components.FailedSpawnComponent;
import com.hypixel.hytale.server.npc.components.SortBufferProviderResource;
import com.hypixel.hytale.server.npc.components.StepComponent;
import com.hypixel.hytale.server.npc.components.Timers;
import com.hypixel.hytale.server.npc.components.messaging.BeaconSupport;
import com.hypixel.hytale.server.npc.components.messaging.NPCBlockEventSupport;
import com.hypixel.hytale.server.npc.components.messaging.NPCEntityEventSupport;
import com.hypixel.hytale.server.npc.components.messaging.PlayerBlockEventSupport;
import com.hypixel.hytale.server.npc.components.messaging.PlayerEntityEventSupport;
import com.hypixel.hytale.server.npc.config.AttitudeGroup;
import com.hypixel.hytale.server.npc.config.ItemAttitudeGroup;
import com.hypixel.hytale.server.npc.config.balancing.BalanceAsset;
import com.hypixel.hytale.server.npc.corecomponents.IEntityFilter;
import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityCollector;
import com.hypixel.hytale.server.npc.corecomponents.ISensorEntityPrioritiser;
import com.hypixel.hytale.server.npc.corecomponents.WeightedAction;
import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionAppearance;
import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionDisplayName;
import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionModelAttachment;
import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionPlayAnimation;
import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionPlaySound;
import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderActionSpawnParticles;
import com.hypixel.hytale.server.npc.corecomponents.audiovisual.builders.BuilderSensorAnimation;
import com.hypixel.hytale.server.npc.corecomponents.builders.BuilderWeightedAction;
import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderActionApplyEntityEffect;
import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderActionAttack;
import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderBodyMotionAimCharge;
import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderHeadMotionAim;
import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderSensorDamage;
import com.hypixel.hytale.server.npc.corecomponents.combat.builders.BuilderSensorIsBackingAway;
import com.hypixel.hytale.server.npc.corecomponents.debug.builders.BuilderActionLog;
import com.hypixel.hytale.server.npc.corecomponents.debug.builders.BuilderActionTest;
import com.hypixel.hytale.server.npc.corecomponents.debug.builders.BuilderBodyMotionTestProbe;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionBeacon;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionIgnoreForAvoidance;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionNotify;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionOverrideAttitude;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionReleaseTarget;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionSetMarkedTarget;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderActionSetStat;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderHeadMotionWatch;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorBeacon;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorCount;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorEntity;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorKill;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorPlayer;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorSelf;
import com.hypixel.hytale.server.npc.corecomponents.entity.builders.BuilderSensorTarget;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterAltitude;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterAnd;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterAttitude;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterCombat;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterHeightDifference;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterInsideBlock;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterInventory;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterItemInHand;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterLineOfSight;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterMovementState;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterNPCGroup;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterNot;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterOr;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterSpotsMe;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterStandingOnBlock;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterStat;
import com.hypixel.hytale.server.npc.corecomponents.entity.filters.builders.BuilderEntityFilterViewSector;
import com.hypixel.hytale.server.npc.corecomponents.entity.prioritisers.builders.BuilderSensorEntityPrioritiserAttitude;
import com.hypixel.hytale.server.npc.corecomponents.interaction.builders.BuilderActionLockOnInteractionTarget;
import com.hypixel.hytale.server.npc.corecomponents.interaction.builders.BuilderActionSetInteractable;
import com.hypixel.hytale.server.npc.corecomponents.interaction.builders.BuilderSensorCanInteract;
import com.hypixel.hytale.server.npc.corecomponents.interaction.builders.BuilderSensorHasInteracted;
import com.hypixel.hytale.server.npc.corecomponents.interaction.builders.BuilderSensorInteractionContext;
import com.hypixel.hytale.server.npc.corecomponents.items.builders.BuilderActionDropItem;
import com.hypixel.hytale.server.npc.corecomponents.items.builders.BuilderActionInventory;
import com.hypixel.hytale.server.npc.corecomponents.items.builders.BuilderActionPickUpItem;
import com.hypixel.hytale.server.npc.corecomponents.items.builders.BuilderSensorDroppedItem;
import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionDelayDespawn;
import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionDespawn;
import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionDie;
import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionRemove;
import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionRole;
import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderActionSpawn;
import com.hypixel.hytale.server.npc.corecomponents.lifecycle.builders.BuilderSensorAge;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderActionCrouch;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderActionOverrideAltitude;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderActionRecomputePath;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionFind;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionLand;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionLeave;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionMaintainDistance;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionMatchLook;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionMoveAway;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionTakeOff;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionTeleport;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionWander;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionWanderInCircle;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderBodyMotionWanderInRect;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderSensorInAir;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderSensorMotionController;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderSensorNav;
import com.hypixel.hytale.server.npc.corecomponents.movement.builders.BuilderSensorOnGround;
import com.hypixel.hytale.server.npc.corecomponents.statemachine.builders.BuilderActionParentState;
import com.hypixel.hytale.server.npc.corecomponents.statemachine.builders.BuilderActionState;
import com.hypixel.hytale.server.npc.corecomponents.statemachine.builders.BuilderActionToggleStateEvaluator;
import com.hypixel.hytale.server.npc.corecomponents.statemachine.builders.BuilderSensorIsBusy;
import com.hypixel.hytale.server.npc.corecomponents.statemachine.builders.BuilderSensorState;
import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionSetAlarm;
import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerContinue;
import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerModify;
import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerPause;
import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerRestart;
import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerStart;
import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderActionTimerStop;
import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderBodyMotionTimer;
import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderHeadMotionTimer;
import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderSensorAlarm;
import com.hypixel.hytale.server.npc.corecomponents.timer.builders.BuilderSensorTimer;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionNothing;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionRandom;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionResetInstructions;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionSequence;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionSetFlag;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderActionTimeout;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderBodyMotionNothing;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderBodyMotionSequence;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderHeadMotionNothing;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderHeadMotionSequence;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorAdjustPosition;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorAnd;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorAny;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorEval;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorFlag;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorNot;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorOr;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorRandom;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorSwitch;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderSensorValueProviderWrapper;
import com.hypixel.hytale.server.npc.corecomponents.utility.builders.BuilderValueToParameterMapping;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionMakePath;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionPlaceBlock;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionResetBlockSensors;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionResetPath;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionResetSearchRays;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionSetBlockToPlace;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionSetLeashPosition;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionStorePosition;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderActionTriggerSpawners;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderBodyMotionPath;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderHeadMotionObserve;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorBlock;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorBlockChange;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorBlockType;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorCanPlace;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorEntityEvent;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorInWater;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorLeash;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorLight;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorPath;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorReadPosition;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorSearchRay;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorTime;
import com.hypixel.hytale.server.npc.corecomponents.world.builders.BuilderSensorWeather;
import com.hypixel.hytale.server.npc.decisionmaker.core.conditions.base.Condition;
import com.hypixel.hytale.server.npc.decisionmaker.stateevaluator.StateEvaluator;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.server.npc.instructions.Action;
import com.hypixel.hytale.server.npc.instructions.ActionList;
import com.hypixel.hytale.server.npc.instructions.BodyMotion;
import com.hypixel.hytale.server.npc.instructions.HeadMotion;
import com.hypixel.hytale.server.npc.instructions.Instruction;
import com.hypixel.hytale.server.npc.instructions.Sensor;
import com.hypixel.hytale.server.npc.instructions.builders.BuilderActionList;
import com.hypixel.hytale.server.npc.instructions.builders.BuilderInstruction;
import com.hypixel.hytale.server.npc.instructions.builders.BuilderInstructionRandomized;
import com.hypixel.hytale.server.npc.instructions.builders.BuilderInstructionReference;
import com.hypixel.hytale.server.npc.interactions.ContextualUseNPCInteraction;
import com.hypixel.hytale.server.npc.interactions.SpawnNPCInteraction;
import com.hypixel.hytale.server.npc.interactions.UseNPCInteraction;
import com.hypixel.hytale.server.npc.movement.controllers.BuilderMotionControllerMapUtil;
import com.hypixel.hytale.server.npc.movement.controllers.MotionController;
import com.hypixel.hytale.server.npc.movement.controllers.builders.BuilderMotionControllerDive;
import com.hypixel.hytale.server.npc.movement.controllers.builders.BuilderMotionControllerFly;
import com.hypixel.hytale.server.npc.movement.controllers.builders.BuilderMotionControllerMap;
import com.hypixel.hytale.server.npc.movement.controllers.builders.BuilderMotionControllerWalk;
import com.hypixel.hytale.server.npc.navigation.AStarNodePoolProviderSimple;
import com.hypixel.hytale.server.npc.path.builders.BuilderRelativeWaypointDefinition;
import com.hypixel.hytale.server.npc.path.builders.BuilderTransientPathDefinition;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.server.npc.role.builders.BuilderRole;
import com.hypixel.hytale.server.npc.role.builders.BuilderRoleAbstract;
import com.hypixel.hytale.server.npc.role.builders.BuilderRoleVariant;
import com.hypixel.hytale.server.npc.statetransition.StateTransitionController;
import com.hypixel.hytale.server.npc.systems.AvoidanceSystem;
import com.hypixel.hytale.server.npc.systems.BalancingInitialisationSystem;
import com.hypixel.hytale.server.npc.systems.BlackboardSystems;
import com.hypixel.hytale.server.npc.systems.ComputeVelocitySystem;
import com.hypixel.hytale.server.npc.systems.FailedSpawnSystem;
import com.hypixel.hytale.server.npc.systems.MessageSupportSystem;
import com.hypixel.hytale.server.npc.systems.MovementStatesSystem;
import com.hypixel.hytale.server.npc.systems.NPCDamageSystems;
import com.hypixel.hytale.server.npc.systems.NPCDeathSystems;
import com.hypixel.hytale.server.npc.systems.NPCInteractionSystems;
import com.hypixel.hytale.server.npc.systems.NPCPreTickSystem;
import com.hypixel.hytale.server.npc.systems.NPCSpatialSystem;
import com.hypixel.hytale.server.npc.systems.NPCSystems;
import com.hypixel.hytale.server.npc.systems.NPCVelocityInstructionSystem;
import com.hypixel.hytale.server.npc.systems.NewSpawnStartTickingSystem;
import com.hypixel.hytale.server.npc.systems.PositionCacheSystems;
import com.hypixel.hytale.server.npc.systems.RoleBuilderSystem;
import com.hypixel.hytale.server.npc.systems.RoleChangeSystem;
import com.hypixel.hytale.server.npc.systems.RoleSystems;
import com.hypixel.hytale.server.npc.systems.StateEvaluatorSystem;
import com.hypixel.hytale.server.npc.systems.SteeringSystem;
import com.hypixel.hytale.server.npc.systems.StepCleanupSystem;
import com.hypixel.hytale.server.npc.systems.TimerSystem;
import com.hypixel.hytale.server.npc.util.SensorSupportBenchmark;
import com.hypixel.hytale.server.npc.util.expression.StdScope;
import com.hypixel.hytale.server.npc.valuestore.ValueStore;
import it.unimi.dsi.fastutil.Pair;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

public class NPCPlugin extends JavaPlugin {
   @Nonnull
   public static String FACTORY_CLASS_ROLE = "Role";
   @Nonnull
   public static String FACTORY_CLASS_BODY_MOTION = "BodyMotion";
   @Nonnull
   public static String FACTORY_CLASS_HEAD_MOTION = "HeadMotion";
   @Nonnull
   public static String FACTORY_CLASS_ACTION = "Action";
   @Nonnull
   public static String FACTORY_CLASS_SENSOR = "Sensor";
   @Nonnull
   public static String FACTORY_CLASS_INSTRUCTION = "Instruction";
   @Nonnull
   public static String FACTORY_CLASS_TRANSIENT_PATH = "Path";
   @Nonnull
   public static String FACTORY_CLASS_ACTION_LIST = "ActionList";
   @Nonnull
   public static String ROLE_ASSETS_PATH = "Server/NPC/Roles";
   private static NPCPlugin instance;
   protected List<BuilderDescriptor> builderDescriptors;
   protected final BuilderManager builderManager = new BuilderManager();
   protected boolean validateBuilder;
   protected int maxBlackboardBlockCountPerType = 20;
   protected boolean logFailingTestErrors;
   protected String[] presetCoverageTestNPCs;
   @Nonnull
   protected AtomicInteger pathChangeRevision = new AtomicInteger(0);
   @Nonnull
   protected Lock benchmarkLock = new ReentrantLock();
   @Nullable
   protected Int2ObjectMap<TimeDistributionRecorder> roleTickDistribution;
   @Nullable
   protected Int2ObjectMap<SensorSupportBenchmark> roleSensorSupportDistribution;
   @Nullable
   protected TimeDistributionRecorder roleTickDistributionAll;
   @Nullable
   protected SensorSupportBenchmark roleSensorSupportDistributionAll;
   protected boolean autoReload;
   private AttitudeMap attitudeMap;
   private ItemAttitudeMap itemAttitudeMap;
   private static final Vector3f NULL_ROTATION = new Vector3f(0.0F, 0.0F, 0.0F);
   public static final short PRIORITY_LOAD_NPC = -8;
   public static final short PRIORITY_SPAWN_VALIDATION = -7;
   private final Config<NPCPlugin.NPCConfig> config = this.withConfig("NPCModule", NPCPlugin.NPCConfig.CODEC);
   private ResourceType<EntityStore, Blackboard> blackboardResourceType;
   private ResourceType<EntityStore, CombatViewSystems.CombatDataPool> combatDataPoolResourceType;
   private ResourceType<EntityStore, RoleChangeSystem.RoleChangeQueue> roleChangeQueueResourceType;
   private ResourceType<EntityStore, NewSpawnStartTickingSystem.QueueResource> newSpawnStartTickingQueueResourceType;
   private ResourceType<EntityStore, SortBufferProviderResource> sortBufferProviderResourceResourceType;
   private ResourceType<EntityStore, AStarNodePoolProviderSimple> aStarNodePoolProviderSimpleResourceType;
   private ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> npcSpatialResource;
   private ComponentType<EntityStore, CombatViewSystems.CombatData> combatDataComponentType;
   private ComponentType<EntityStore, NPCRunTestsCommand.NPCTestData> npcTestDataComponentType;
   private ComponentType<EntityStore, BeaconSupport> beaconSupportComponentType;
   private ComponentType<EntityStore, NPCBlockEventSupport> npcBlockEventSupportComponentType;
   private ComponentType<EntityStore, PlayerBlockEventSupport> playerBlockEventSupportComponentType;
   private ComponentType<EntityStore, NPCEntityEventSupport> npcEntityEventSupportComponentType;
   private ComponentType<EntityStore, PlayerEntityEventSupport> playerEntityEventSupportComponentType;
   private ComponentType<EntityStore, StepComponent> stepComponentType;
   private ComponentType<EntityStore, FailedSpawnComponent> failedSpawnComponentType;
   private ComponentType<EntityStore, Timers> timersComponentType;
   private ComponentType<EntityStore, StateEvaluator> stateEvaluatorComponentType;
   private ComponentType<EntityStore, ValueStore> valueStoreComponentType;

   public static NPCPlugin get() {
      return instance;
   }

   public NPCPlugin(@Nonnull JavaPluginInit init) {
      super(init);
   }

   @Override
   protected void setup() {
      instance = this;
      ComponentRegistryProxy<EntityStore> entityStoreRegistry = this.getEntityStoreRegistry();
      EventRegistry eventRegistry = this.getEventRegistry();
      this.getCommandRegistry().registerCommand(new NPCCommand());
      eventRegistry.register(LoadedAssetsEvent.class, ModelAsset.class, this::onModelsChanged);
      eventRegistry.register(LoadedAssetsEvent.class, NPCGroup.class, this::onNPCGroupsLoaded);
      eventRegistry.register(RemovedAssetsEvent.class, NPCGroup.class, this::onNPCGroupsRemoved);
      eventRegistry.register(LoadedAssetsEvent.class, AttitudeGroup.class, this::onAttitudeGroupsLoaded);
      eventRegistry.register(RemovedAssetsEvent.class, AttitudeGroup.class, this::onAttitudeGroupsRemoved);
      eventRegistry.register(LoadedAssetsEvent.class, ItemAttitudeGroup.class, this::onItemAttitudeGroupsLoaded);
      eventRegistry.register(RemovedAssetsEvent.class, ItemAttitudeGroup.class, this::onItemAttitudeGroupsRemoved);
      eventRegistry.register(LoadedAssetsEvent.class, BalanceAsset.class, NPCPlugin::onBalanceAssetsChanged);
      eventRegistry.register(RemovedAssetsEvent.class, BalanceAsset.class, NPCPlugin::onBalanceAssetsRemoved);
      eventRegistry.register(WorldPathChangedEvent.class, this::onPathChange);
      eventRegistry.register(AllNPCsLoadedEvent.class, this::onNPCsLoaded);
      eventRegistry.register(
         (short)-8,
         LoadAssetEvent.class,
         event -> {
            HytaleLogger.getLogger().at(Level.INFO).log("Loading NPC assets phase...");
            long start = System.nanoTime();
            this.builderManager.setAutoReload(this.autoReload);
            boolean validateAssets = Options.getOptionSet().has(Options.VALIDATE_ASSETS);
            List<AssetPack> assetPacks = AssetModule.get().getAssetPacks();

            for (int i = 0; i < assetPacks.size(); i++) {
               boolean includeTests = i == 0;
               boolean loadSucceeded = this.builderManager.loadBuilders(assetPacks.get(i), includeTests);
               if (!loadSucceeded) {
                  event.failed(validateAssets, "failed to validate npc's");
               }
            }

            HytaleLogger.getLogger()
               .at(Level.INFO)
               .log(
                  "Loading NPC assets phase completed! Boot time %s, Took %s",
                  FormatUtil.nanosToString(System.nanoTime() - event.getBootStart()),
                  FormatUtil.nanosToString(System.nanoTime() - start)
               );
         }
      );
      eventRegistry.register(AssetPackRegisterEvent.class, event -> this.builderManager.loadBuilders(event.getAssetPack(), false));
      eventRegistry.register(AssetPackUnregisterEvent.class, event -> this.builderManager.unloadBuilders(event.getAssetPack()));
      eventRegistry.register(GenerateSchemaEvent.class, this::onSchemaGenerate);
      AssetRegistry.register(
         ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(
                              AttitudeGroup.class, new IndexedLookupTableAssetMap<>(AttitudeGroup[]::new)
                           )
                           .setPath("NPC/Attitude/Roles"))
                        .setCodec(AttitudeGroup.CODEC))
                     .setKeyFunction(AttitudeGroup::getId))
                  .setReplaceOnRemove(AttitudeGroup::new))
               .loadsAfter(NPCGroup.class))
            .build()
      );
      AssetRegistry.register(
         ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(
                              ItemAttitudeGroup.class, new IndexedLookupTableAssetMap<>(ItemAttitudeGroup[]::new)
                           )
                           .setPath("NPC/Attitude/Items"))
                        .setCodec(ItemAttitudeGroup.CODEC))
                     .setKeyFunction(ItemAttitudeGroup::getId))
                  .setReplaceOnRemove(ItemAttitudeGroup::new))
               .loadsAfter(Item.class))
            .build()
      );
      AssetRegistry.register(
         ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(
                           BalanceAsset.class, new DefaultAssetMap()
                        )
                        .setPath("NPC/Balancing"))
                     .setCodec(BalanceAsset.CODEC))
                  .setKeyFunction(BalanceAsset::getId))
               .loadsAfter(Condition.class))
            .build()
      );
      AssetRegistry.register(
         ((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)((HytaleAssetStore.Builder)HytaleAssetStore.builder(
                              Condition.class, new IndexedLookupTableAssetMap<>(Condition[]::new)
                           )
                           .setPath("NPC/DecisionMaking/Conditions"))
                        .setCodec(Condition.CODEC))
                     .setKeyFunction(Condition::getId))
                  .setReplaceOnRemove(Condition::getAlwaysTrueFor))
               .loadsAfter(ResponseCurve.class, NPCGroup.class, EntityStatType.class))
            .build()
      );
      this.getEntityRegistry().registerEntity("NPC", NPCEntity.class, NPCEntity::new, NPCEntity.CODEC);
      Interaction.CODEC.register("ContextualUseNPC", ContextualUseNPCInteraction.class, ContextualUseNPCInteraction.CODEC);
      Interaction.CODEC.register("UseNPC", UseNPCInteraction.class, UseNPCInteraction.CODEC);
      Interaction.CODEC.register("SpawnNPC", SpawnNPCInteraction.class, SpawnNPCInteraction.CODEC);
      Interaction.getAssetStore().loadAssets("Hytale:Hytale", List.of(new UseNPCInteraction("*UseNPC")));
      RootInteraction.getAssetStore().loadAssets("Hytale:Hytale", List.of(UseNPCInteraction.DEFAULT_ROOT));
      MigrationModule.get().register("spawnMarkers", RenameSpawnMarkerMigration::new);
      this.setupNPCLoading();
      this.blackboardResourceType = entityStoreRegistry.registerResource(Blackboard.class, Blackboard::new);
      this.combatDataPoolResourceType = entityStoreRegistry.registerResource(CombatViewSystems.CombatDataPool.class, CombatViewSystems.CombatDataPool::new);
      this.roleChangeQueueResourceType = entityStoreRegistry.registerResource(RoleChangeSystem.RoleChangeQueue.class, RoleChangeSystem.RoleChangeQueue::new);
      this.newSpawnStartTickingQueueResourceType = entityStoreRegistry.registerResource(
         NewSpawnStartTickingSystem.QueueResource.class, NewSpawnStartTickingSystem.QueueResource::new
      );
      this.sortBufferProviderResourceResourceType = entityStoreRegistry.registerResource(SortBufferProviderResource.class, SortBufferProviderResource::new);
      this.aStarNodePoolProviderSimpleResourceType = entityStoreRegistry.registerResource(AStarNodePoolProviderSimple.class, AStarNodePoolProviderSimple::new);
      this.npcSpatialResource = entityStoreRegistry.registerSpatialResource(() -> new KDTree<>(Ref::isValid));
      this.combatDataComponentType = entityStoreRegistry.registerComponent(CombatViewSystems.CombatData.class, CombatViewSystems.CombatData::new);
      this.npcTestDataComponentType = entityStoreRegistry.registerComponent(NPCRunTestsCommand.NPCTestData.class, NPCRunTestsCommand.NPCTestData::new);
      this.beaconSupportComponentType = entityStoreRegistry.registerComponent(BeaconSupport.class, BeaconSupport::new);
      this.npcBlockEventSupportComponentType = entityStoreRegistry.registerComponent(NPCBlockEventSupport.class, NPCBlockEventSupport::new);
      this.playerBlockEventSupportComponentType = entityStoreRegistry.registerComponent(PlayerBlockEventSupport.class, PlayerBlockEventSupport::new);
      this.npcEntityEventSupportComponentType = entityStoreRegistry.registerComponent(NPCEntityEventSupport.class, NPCEntityEventSupport::new);
      this.playerEntityEventSupportComponentType = entityStoreRegistry.registerComponent(PlayerEntityEventSupport.class, PlayerEntityEventSupport::new);
      this.stepComponentType = entityStoreRegistry.registerComponent(StepComponent.class, () -> {
         throw new UnsupportedOperationException("Not implemented");
      });
      this.failedSpawnComponentType = entityStoreRegistry.registerComponent(FailedSpawnComponent.class, FailedSpawnComponent::new);
      this.timersComponentType = entityStoreRegistry.registerComponent(Timers.class, () -> {
         throw new UnsupportedOperationException("Not implemented");
      });
      this.stateEvaluatorComponentType = entityStoreRegistry.registerComponent(StateEvaluator.class, () -> {
         throw new UnsupportedOperationException("Not implemented");
      });
      this.valueStoreComponentType = entityStoreRegistry.registerComponent(ValueStore.class, () -> {
         throw new UnsupportedOperationException("Not implemented");
      });
      ComponentType<EntityStore, NPCEntity> npcComponentType = NPCEntity.getComponentType();
      entityStoreRegistry.registerSystem(new BlackboardSystems.InitSystem(this.blackboardResourceType));
      entityStoreRegistry.registerSystem(new BlackboardSystems.TickingSystem(this.blackboardResourceType));
      entityStoreRegistry.registerSystem(new BlackboardSystems.DamageBlockEventSystem());
      entityStoreRegistry.registerSystem(new BlackboardSystems.BreakBlockEventSystem());
      entityStoreRegistry.registerSystem(new CombatViewSystems.Ensure(this.combatDataComponentType));
      entityStoreRegistry.registerSystem(new CombatViewSystems.EntityRemoved(this.combatDataComponentType, this.combatDataPoolResourceType));
      entityStoreRegistry.registerSystem(new CombatViewSystems.Ticking(this.combatDataComponentType, this.combatDataPoolResourceType));
      entityStoreRegistry.registerSystem(new NPCSystems.ModelChangeSystem());
      entityStoreRegistry.registerSystem(new RoleBuilderSystem());
      entityStoreRegistry.registerSystem(new BalancingInitialisationSystem());
      entityStoreRegistry.registerSystem(new RoleSystems.RoleActivateSystem(npcComponentType));
      entityStoreRegistry.registerSystem(new PositionCacheSystems.RoleActivateSystem(npcComponentType, this.stateEvaluatorComponentType));
      entityStoreRegistry.registerSystem(new NPCInteractionSystems.AddSimulationManagerSystem(npcComponentType));
      entityStoreRegistry.registerSystem(new NPCInteractionSystems.TickHeldInteractionsSystem(npcComponentType));
      entityStoreRegistry.registerSystem(new FailedSpawnSystem());
      entityStoreRegistry.registerSystem(new NPCSystems.AddedSystem(npcComponentType));
      entityStoreRegistry.registerSystem(new NPCSystems.AddedFromExternalSystem(npcComponentType));
      entityStoreRegistry.registerSystem(new NPCSystems.AddedFromWorldGenSystem());
      entityStoreRegistry.registerSystem(new NPCSystems.AddSpawnEntityEffectSystem(npcComponentType));
      entityStoreRegistry.registerSystem(new RoleSystems.BehaviourTickSystem(npcComponentType, this.stepComponentType));
      entityStoreRegistry.registerSystem(new RoleSystems.PreBehaviourSupportTickSystem(npcComponentType));
      entityStoreRegistry.registerSystem(new StateEvaluatorSystem(this.stateEvaluatorComponentType, npcComponentType));
      entityStoreRegistry.registerSystem(new PositionCacheSystems.UpdateSystem(npcComponentType, this.npcSpatialResource));
      entityStoreRegistry.registerSystem(new NPCPreTickSystem(npcComponentType));
      Set<Dependency<EntityStore>> postBehaviourDependency = Set.of(new SystemDependency<>(Order.AFTER, RoleSystems.PostBehaviourSupportTickSystem.class));
      entityStoreRegistry.registerSystem(new AvoidanceSystem(npcComponentType));
      entityStoreRegistry.registerSystem(new SteeringSystem(npcComponentType));
      entityStoreRegistry.registerSystem(new RoleSystems.PostBehaviourSupportTickSystem(npcComponentType));
      entityStoreRegistry.registerSystem(new RoleSystems.RoleDebugSystem(npcComponentType, postBehaviourDependency));
      entityStoreRegistry.registerSystem(new TimerSystem(this.timersComponentType, postBehaviourDependency));
      entityStoreRegistry.registerSystem(new ComputeVelocitySystem(npcComponentType, EntityModule.get().getVelocityComponentType(), postBehaviourDependency));
      entityStoreRegistry.registerSystem(
         new MovementStatesSystem(npcComponentType, EntityModule.get().getVelocityComponentType(), EntityModule.get().getMovementStatesComponentType())
      );
      entityStoreRegistry.registerSystem(new MessageSupportSystem.BeaconSystem(this.beaconSupportComponentType, postBehaviourDependency));
      entityStoreRegistry.registerSystem(new MessageSupportSystem.NPCBlockEventSystem(this.npcBlockEventSupportComponentType, postBehaviourDependency));
      entityStoreRegistry.registerSystem(new MessageSupportSystem.PlayerBlockEventSystem(this.playerBlockEventSupportComponentType, postBehaviourDependency));
      entityStoreRegistry.registerSystem(new MessageSupportSystem.NPCEntityEventSystem(this.npcEntityEventSupportComponentType, postBehaviourDependency));
      entityStoreRegistry.registerSystem(new MessageSupportSystem.PlayerEntityEventSystem(this.playerEntityEventSupportComponentType, postBehaviourDependency));
      entityStoreRegistry.registerSystem(new StepCleanupSystem(this.stepComponentType));
      entityStoreRegistry.registerSystem(new NewSpawnStartTickingSystem(this.newSpawnStartTickingQueueResourceType));
      entityStoreRegistry.registerSystem(
         new RoleChangeSystem(
            this.roleChangeQueueResourceType,
            this.beaconSupportComponentType,
            this.playerBlockEventSupportComponentType,
            this.npcBlockEventSupportComponentType,
            this.playerEntityEventSupportComponentType,
            this.npcEntityEventSupportComponentType,
            this.timersComponentType,
            this.stateEvaluatorComponentType,
            this.valueStoreComponentType
         )
      );
      entityStoreRegistry.registerSystem(new NPCSpatialSystem(this.npcSpatialResource));
      entityStoreRegistry.registerSystem(new NPCDeathSystems.NPCKillsEntitySystem());
      entityStoreRegistry.registerSystem(new NPCDeathSystems.EntityViewSystem());
      entityStoreRegistry.registerSystem(new NPCDamageSystems.FilterDamageSystem());
      entityStoreRegistry.registerSystem(new NPCDamageSystems.DamageReceivedSystem());
      entityStoreRegistry.registerSystem(new NPCDamageSystems.DamageDealtSystem());
      entityStoreRegistry.registerSystem(new NPCDamageSystems.DamageReceivedEventViewSystem());
      entityStoreRegistry.registerSystem(new NPCDamageSystems.DropDeathItems());
      entityStoreRegistry.registerSystem(new NPCSystems.OnTeleportSystem());
      entityStoreRegistry.registerSystem(new NPCSystems.OnDeathSystem());
      entityStoreRegistry.registerSystem(new NPCSystems.LegacyWorldGenId());
      entityStoreRegistry.registerSystem(new NPCSystems.KillFeedKillerEventSystem());
      entityStoreRegistry.registerSystem(new NPCSystems.KillFeedDecedentEventSystem());
      entityStoreRegistry.registerSystem(new NPCSystems.PrefabPlaceEntityEventSystem());
      entityStoreRegistry.registerSystem(new NPCVelocityInstructionSystem());
      this.getEntityStoreRegistry().registerSystem(new NPCPlugin.NPCEntityRegenerateStatsSystem());
   }

   public void onSchemaGenerate(@Nonnull GenerateSchemaEvent event) {
      Schema schema = this.builderManager.generateSchema(event.getContext());
      event.addSchema("NPCRole.json", schema);
      event.addSchemaLink("NPCRole", List.of("NPC/Roles/*.json", "NPC/Roles/**/*.json"), null);
      Schema.HytaleMetadata hytale = schema.getHytale();
      hytale.setPath("NPC/Roles");
      hytale.setExtension(".json");
      schema.setId("NPCRole.json");
      schema.setTitle("NPCRole");
   }

   @Override
   protected void start() {
      NPCPlugin.NPCConfig config = this.config.get();
      if (config.isGenerateDescriptors()) {
         this.generateDescriptors();
         if (config.isGenerateDescriptorsFile()) {
            this.saveDescriptors();
         }
      }
   }

   public ResourceType<EntityStore, Blackboard> getBlackboardResourceType() {
      return this.blackboardResourceType;
   }

   public ResourceType<EntityStore, CombatViewSystems.CombatDataPool> getCombatDataPoolResourceType() {
      return this.combatDataPoolResourceType;
   }

   public ResourceType<EntityStore, RoleChangeSystem.RoleChangeQueue> getRoleChangeQueueResourceType() {
      return this.roleChangeQueueResourceType;
   }

   public ResourceType<EntityStore, NewSpawnStartTickingSystem.QueueResource> getNewSpawnStartTickingQueueResourceType() {
      return this.newSpawnStartTickingQueueResourceType;
   }

   public ResourceType<EntityStore, SortBufferProviderResource> getSortBufferProviderResourceResourceType() {
      return this.sortBufferProviderResourceResourceType;
   }

   public ResourceType<EntityStore, AStarNodePoolProviderSimple> getAStarNodePoolProviderSimpleResourceType() {
      return this.aStarNodePoolProviderSimpleResourceType;
   }

   public ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> getNpcSpatialResource() {
      return this.npcSpatialResource;
   }

   public ComponentType<EntityStore, CombatViewSystems.CombatData> getCombatDataComponentType() {
      return this.combatDataComponentType;
   }

   public ComponentType<EntityStore, NPCRunTestsCommand.NPCTestData> getNpcTestDataComponentType() {
      return this.npcTestDataComponentType;
   }

   public ComponentType<EntityStore, BeaconSupport> getBeaconSupportComponentType() {
      return this.beaconSupportComponentType;
   }

   public ComponentType<EntityStore, NPCBlockEventSupport> getNpcBlockEventSupportComponentType() {
      return this.npcBlockEventSupportComponentType;
   }

   public ComponentType<EntityStore, PlayerBlockEventSupport> getPlayerBlockEventSupportComponentType() {
      return this.playerBlockEventSupportComponentType;
   }

   public ComponentType<EntityStore, NPCEntityEventSupport> getNpcEntityEventSupportComponentType() {
      return this.npcEntityEventSupportComponentType;
   }

   public ComponentType<EntityStore, PlayerEntityEventSupport> getPlayerEntityEventSupportComponentType() {
      return this.playerEntityEventSupportComponentType;
   }

   public ComponentType<EntityStore, StepComponent> getStepComponentType() {
      return this.stepComponentType;
   }

   public ComponentType<EntityStore, FailedSpawnComponent> getFailedSpawnComponentType() {
      return this.failedSpawnComponentType;
   }

   public ComponentType<EntityStore, Timers> getTimersComponentType() {
      return this.timersComponentType;
   }

   public ComponentType<EntityStore, StateEvaluator> getStateEvaluatorComponentType() {
      return this.stateEvaluatorComponentType;
   }

   public ComponentType<EntityStore, ValueStore> getValueStoreComponentType() {
      return this.valueStoreComponentType;
   }

   public void setupNPCLoading() {
      this.builderManager.addCategory(FACTORY_CLASS_ROLE, Role.class);
      this.builderManager.addCategory(FACTORY_CLASS_BODY_MOTION, BodyMotion.class);
      this.builderManager.addCategory(FACTORY_CLASS_HEAD_MOTION, HeadMotion.class);
      this.builderManager.addCategory(FACTORY_CLASS_ACTION, Action.class);
      this.builderManager.addCategory(FACTORY_CLASS_SENSOR, Sensor.class);
      this.builderManager.addCategory(FACTORY_CLASS_INSTRUCTION, Instruction.class);
      this.builderManager.addCategory(FACTORY_CLASS_TRANSIENT_PATH, TransientPathDefinition.class);
      this.builderManager.addCategory(FACTORY_CLASS_ACTION_LIST, ActionList.class);
      this.registerCoreFactories();
      this.registerCoreComponentType("Nothing", BuilderBodyMotionNothing::new)
         .registerCoreComponentType("Wander", BuilderBodyMotionWander::new)
         .registerCoreComponentType("WanderInCircle", BuilderBodyMotionWanderInCircle::new)
         .registerCoreComponentType("WanderInRect", BuilderBodyMotionWanderInRect::new)
         .registerCoreComponentType("Timer", BuilderBodyMotionTimer::new)
         .registerCoreComponentType("Sequence", BuilderBodyMotionSequence::new)
         .registerCoreComponentType("Flee", BuilderBodyMotionMoveAway::new)
         .registerCoreComponentType("Seek", BuilderBodyMotionFind::new)
         .registerCoreComponentType("Leave", BuilderBodyMotionLeave::new)
         .registerCoreComponentType("Path", BuilderBodyMotionPath::new)
         .registerCoreComponentType("TakeOff", BuilderBodyMotionTakeOff::new)
         .registerCoreComponentType("TestProbe", BuilderBodyMotionTestProbe::new)
         .registerCoreComponentType("Teleport", BuilderBodyMotionTeleport::new)
         .registerCoreComponentType("Land", BuilderBodyMotionLand::new)
         .registerCoreComponentType("MatchLook", BuilderBodyMotionMatchLook::new)
         .registerCoreComponentType("MaintainDistance", BuilderBodyMotionMaintainDistance::new)
         .registerCoreComponentType("AimCharge", BuilderBodyMotionAimCharge::new);
      this.registerCoreComponentType("Aim", BuilderHeadMotionAim::new)
         .registerCoreComponentType("Watch", BuilderHeadMotionWatch::new)
         .registerCoreComponentType("Observe", BuilderHeadMotionObserve::new)
         .registerCoreComponentType("Sequence", BuilderHeadMotionSequence::new)
         .registerCoreComponentType("Timer", BuilderHeadMotionTimer::new)
         .registerCoreComponentType("Nothing", BuilderHeadMotionNothing::new);
      this.registerCoreComponentType("Appearance", BuilderActionAppearance::new)
         .registerCoreComponentType("Timeout", BuilderActionTimeout::new)
         .registerCoreComponentType("Spawn", BuilderActionSpawn::new)
         .registerCoreComponentType("Nothing", BuilderActionNothing::new)
         .registerCoreComponentType("Attack", BuilderActionAttack::new)
         .registerCoreComponentType("State", BuilderActionState::new)
         .registerCoreComponentType("ReleaseTarget", BuilderActionReleaseTarget::new)
         .registerCoreComponentType("SetMarkedTarget", BuilderActionSetMarkedTarget::new)
         .registerCoreComponentType("Inventory", BuilderActionInventory::new)
         .registerCoreComponentType("DisplayName", BuilderActionDisplayName::new)
         .registerCoreComponentType("Sequence", BuilderActionSequence::new)
         .registerCoreComponentType("Random", BuilderActionRandom::new)
         .registerCoreComponentType("Beacon", BuilderActionBeacon::new)
         .registerCoreComponentType("SetLeashPosition", BuilderActionSetLeashPosition::new)
         .registerCoreComponentType("PlaySound", BuilderActionPlaySound::new)
         .registerCoreComponentType("Despawn", BuilderActionDespawn::new)
         .registerCoreComponentType("PlayAnimation", BuilderActionPlayAnimation::new)
         .registerCoreComponentType("DelayDespawn", BuilderActionDelayDespawn::new)
         .registerCoreComponentType("SpawnParticles", BuilderActionSpawnParticles::new)
         .registerCoreComponentType("Crouch", BuilderActionCrouch::new)
         .registerCoreComponentType("TimerStart", BuilderActionTimerStart::new)
         .registerCoreComponentType("TimerContinue", BuilderActionTimerContinue::new)
         .registerCoreComponentType("TimerPause", BuilderActionTimerPause::new)
         .registerCoreComponentType("TimerModify", BuilderActionTimerModify::new)
         .registerCoreComponentType("TimerStop", BuilderActionTimerStop::new)
         .registerCoreComponentType("TimerRestart", BuilderActionTimerRestart::new)
         .registerCoreComponentType("Test", BuilderActionTest::new)
         .registerCoreComponentType("Log", BuilderActionLog::new)
         .registerCoreComponentType("Role", BuilderActionRole::new)
         .registerCoreComponentType("SetFlag", BuilderActionSetFlag::new)
         .registerCoreComponentType("DropItem", BuilderActionDropItem::new)
         .registerCoreComponentType("PickUpItem", BuilderActionPickUpItem::new)
         .registerCoreComponentType("ResetInstructions", BuilderActionResetInstructions::new)
         .registerCoreComponentType("ParentState", BuilderActionParentState::new)
         .registerCoreComponentType("Notify", BuilderActionNotify::new)
         .registerCoreComponentType("TriggerSpawners", BuilderActionTriggerSpawners::new)
         .registerCoreComponentType("ResetBlockSensors", BuilderActionResetBlockSensors::new)
         .registerCoreComponentType("MakePath", BuilderActionMakePath::new)
         .registerCoreComponentType("OverrideAttitude", BuilderActionOverrideAttitude::new)
         .registerCoreComponentType("SetInteractable", BuilderActionSetInteractable::new)
         .registerCoreComponentType("LockOnInteractionTarget", BuilderActionLockOnInteractionTarget::new)
         .registerCoreComponentType("StorePosition", BuilderActionStorePosition::new)
         .registerCoreComponentType("SetBlockToPlace", BuilderActionSetBlockToPlace::new)
         .registerCoreComponentType("PlaceBlock", BuilderActionPlaceBlock::new)
         .registerCoreComponentType("RecomputePath", BuilderActionRecomputePath::new)
         .registerCoreComponentType("IgnoreForAvoidance", BuilderActionIgnoreForAvoidance::new)
         .registerCoreComponentType("ModelAttachment", BuilderActionModelAttachment::new)
         .registerCoreComponentType("SetAlarm", BuilderActionSetAlarm::new)
         .registerCoreComponentType("ToggleStateEvaluator", BuilderActionToggleStateEvaluator::new)
         .registerCoreComponentType("OverrideAltitude", BuilderActionOverrideAltitude::new)
         .registerCoreComponentType("ResetSearchRays", BuilderActionResetSearchRays::new)
         .registerCoreComponentType("Die", BuilderActionDie::new)
         .registerCoreComponentType("Remove", BuilderActionRemove::new)
         .registerCoreComponentType("ApplyEntityEffect", BuilderActionApplyEntityEffect::new)
         .registerCoreComponentType("ResetPath", BuilderActionResetPath::new)
         .registerCoreComponentType("SetStat", BuilderActionSetStat::new);
      this.registerCoreComponentType("Any", BuilderSensorAny::new)
         .registerCoreComponentType("And", BuilderSensorAnd::new)
         .registerCoreComponentType("Or", BuilderSensorOr::new)
         .registerCoreComponentType("Not", BuilderSensorNot::new)
         .registerCoreComponentType("Player", BuilderSensorPlayer::new)
         .registerCoreComponentType("Mob", BuilderSensorEntity::new)
         .registerCoreComponentType("State", BuilderSensorState::new)
         .registerCoreComponentType("InAir", BuilderSensorInAir::new)
         .registerCoreComponentType("OnGround", BuilderSensorOnGround::new)
         .registerCoreComponentType("Eval", BuilderSensorEval::new)
         .registerCoreComponentType("Damage", BuilderSensorDamage::new)
         .registerCoreComponentType("IsBackingAway", BuilderSensorIsBackingAway::new)
         .registerCoreComponentType("Kill", BuilderSensorKill::new)
         .registerCoreComponentType("Beacon", BuilderSensorBeacon::new)
         .registerCoreComponentType("MotionController", BuilderSensorMotionController::new)
         .registerCoreComponentType("Leash", BuilderSensorLeash::new)
         .registerCoreComponentType("Time", BuilderSensorTime::new)
         .registerCoreComponentType("Count", BuilderSensorCount::new)
         .registerCoreComponentType("Target", BuilderSensorTarget::new)
         .registerCoreComponentType("Timer", BuilderSensorTimer::new)
         .registerCoreComponentType("Switch", BuilderSensorSwitch::new)
         .registerCoreComponentType("Light", BuilderSensorLight::new)
         .registerCoreComponentType("Age", BuilderSensorAge::new)
         .registerCoreComponentType("Flag", BuilderSensorFlag::new)
         .registerCoreComponentType("DroppedItem", BuilderSensorDroppedItem::new)
         .registerCoreComponentType("Path", BuilderSensorPath::new)
         .registerCoreComponentType("Weather", BuilderSensorWeather::new)
         .registerCoreComponentType("Block", BuilderSensorBlock::new)
         .registerCoreComponentType("BlockChange", BuilderSensorBlockChange::new)
         .registerCoreComponentType("EntityEvent", BuilderSensorEntityEvent::new)
         .registerCoreComponentType("Random", BuilderSensorRandom::new)
         .registerCoreComponentType("CanInteract", BuilderSensorCanInteract::new)
         .registerCoreComponentType("HasInteracted", BuilderSensorHasInteracted::new)
         .registerCoreComponentType("ReadPosition", BuilderSensorReadPosition::new)
         .registerCoreComponentType("Animation", BuilderSensorAnimation::new)
         .registerCoreComponentType("CanPlaceBlock", BuilderSensorCanPlace::new)
         .registerCoreComponentType("Nav", BuilderSensorNav::new)
         .registerCoreComponentType("InWater", BuilderSensorInWater::new)
         .registerCoreComponentType("IsBusy", BuilderSensorIsBusy::new)
         .registerCoreComponentType("InteractionContext", BuilderSensorInteractionContext::new)
         .registerCoreComponentType("Alarm", BuilderSensorAlarm::new)
         .registerCoreComponentType("AdjustPosition", BuilderSensorAdjustPosition::new)
         .registerCoreComponentType("SearchRay", BuilderSensorSearchRay::new)
         .registerCoreComponentType("BlockType", BuilderSensorBlockType::new)
         .registerCoreComponentType("Self", BuilderSensorSelf::new)
         .registerCoreComponentType("ValueProviderWrapper", BuilderSensorValueProviderWrapper::new);
      this.registerCoreComponentType("Attitude", BuilderEntityFilterAttitude::new)
         .registerCoreComponentType("LineOfSight", BuilderEntityFilterLineOfSight::new)
         .registerCoreComponentType("HeightDifference", BuilderEntityFilterHeightDifference::new)
         .registerCoreComponentType("ViewSector", BuilderEntityFilterViewSector::new)
         .registerCoreComponentType("Combat", BuilderEntityFilterCombat::new)
         .registerCoreComponentType("ItemInHand", BuilderEntityFilterItemInHand::new)
         .registerCoreComponentType("NPCGroup", BuilderEntityFilterNPCGroup::new)
         .registerCoreComponentType("MovementState", BuilderEntityFilterMovementState::new)
         .registerCoreComponentType("SpotsMe", BuilderEntityFilterSpotsMe::new)
         .registerCoreComponentType("StandingOnBlock", BuilderEntityFilterStandingOnBlock::new)
         .registerCoreComponentType("Stat", BuilderEntityFilterStat::new)
         .registerCoreComponentType("Inventory", BuilderEntityFilterInventory::new)
         .registerCoreComponentType("Not", BuilderEntityFilterNot::new)
         .registerCoreComponentType("And", BuilderEntityFilterAnd::new)
         .registerCoreComponentType("Or", BuilderEntityFilterOr::new)
         .registerCoreComponentType("Altitude", BuilderEntityFilterAltitude::new)
         .registerCoreComponentType("InsideBlock", BuilderEntityFilterInsideBlock::new);
      this.registerCoreComponentType("Attitude", BuilderSensorEntityPrioritiserAttitude::new);
      NPCPlugin.NPCConfig config = this.config.get();
      this.autoReload = config.isAutoReload();
      this.validateBuilder = config.isValidateBuilder();
      this.maxBlackboardBlockCountPerType = config.getMaxBlackboardBlockType();
      this.logFailingTestErrors = config.isLogFailingTestErrors();
      this.presetCoverageTestNPCs = config.getPresetCoverageTestNPCs();
   }

   public String[] getPresetCoverageTestNPCs() {
      return this.presetCoverageTestNPCs;
   }

   @Nullable
   public Pair<Ref<EntityStore>, INonPlayerCharacter> spawnNPC(
      @Nonnull Store<EntityStore> store, @Nonnull String npcType, @Nullable String groupType, @Nonnull Vector3d position, @Nonnull Vector3f rotation
   ) {
      int roleIndex = this.getIndex(npcType);
      if (roleIndex < 0) {
         return null;
      } else {
         Pair<Ref<EntityStore>, NPCEntity> npcPair = this.spawnEntity(store, roleIndex, position, rotation, null, null);
         FlockAsset flockDefinition = groupType != null ? FlockAsset.getAssetMap().getAsset(groupType) : null;
         if (npcPair != null) {
            Ref<EntityStore> npcRef = npcPair.first();
            NPCEntity npcComponent = npcPair.second();
            FlockPlugin.trySpawnFlock(npcRef, npcComponent, store, roleIndex, position, rotation, flockDefinition, null);
            return Pair.of(npcPair.first(), npcPair.second());
         } else {
            return null;
         }
      }
   }

   public static void reloadNPCsWithRole(int roleIndex) {
      Universe.get()
         .getWorlds()
         .forEach(
            (s, world) -> world.execute(() -> world.getEntityStore().getStore().forEachChunk(NPCEntity.getComponentType(), (archetypeChunk, commandBuffer) -> {
               for (int index = 0; index < archetypeChunk.size(); index++) {
                  NPCEntity npc = archetypeChunk.getComponent(index, NPCEntity.getComponentType());
                  if (npc.getRoleIndex() == roleIndex && !npc.getRole().isRoleChangeRequested()) {
                     RoleChangeSystem.requestRoleChange(archetypeChunk.getReferenceTo(index), npc.getRole(), roleIndex, true, world.getEntityStore().getStore());
                  }
               }
            }))
         );
   }

   protected void onNPCGroupsLoaded(LoadedAssetsEvent<String, NPCGroup, AssetMap<String, NPCGroup>> event) {
      this.putNPCGroups();
   }

   protected void onNPCGroupsRemoved(RemovedAssetsEvent<String, NPCGroup, AssetMap<String, NPCGroup>> event) {
      this.putNPCGroups();
   }

   protected void onAttitudeGroupsLoaded(@Nonnull LoadedAssetsEvent<String, AttitudeGroup, AssetMap<String, AttitudeGroup>> event) {
      if (this.attitudeMap == null) {
         this.putAttitudeGroups();
      } else {
         Map<String, AttitudeGroup> loadedAssets = event.getLoadedAssets();
         IndexedLookupTableAssetMap<String, AttitudeGroup> assets = AttitudeGroup.getAssetMap();
         int attitudeGroupCount = this.attitudeMap.getAttitudeGroupCount();

         for (String id : loadedAssets.keySet()) {
            int index = assets.getIndex(id);
            if (index == Integer.MIN_VALUE) {
               throw new IllegalArgumentException("Unknown key! " + id);
            }

            if (index >= attitudeGroupCount) {
               this.putAttitudeGroups();
               return;
            }
         }

         loadedAssets.forEach((idx, group) -> {
            int indexx = assets.getIndex(idx);
            if (indexx == Integer.MIN_VALUE) {
               throw new IllegalArgumentException("Unknown key! " + idx);
            } else {
               this.attitudeMap.updateAttitudeGroup(indexx, group);
            }
         });
      }
   }

   protected void onAttitudeGroupsRemoved(RemovedAssetsEvent<String, AttitudeGroup, AssetMap<String, AttitudeGroup>> event) {
      this.putAttitudeGroups();
   }

   protected void onItemAttitudeGroupsLoaded(@Nonnull LoadedAssetsEvent<String, ItemAttitudeGroup, AssetMap<String, ItemAttitudeGroup>> event) {
      if (this.itemAttitudeMap == null) {
         this.putItemAttitudeGroups();
      } else {
         Map<String, ItemAttitudeGroup> loadedAssets = event.getLoadedAssets();
         IndexedLookupTableAssetMap<String, ItemAttitudeGroup> assets = ItemAttitudeGroup.getAssetMap();
         int attitudeGroupCount = this.itemAttitudeMap.getAttitudeGroupCount();

         for (String id : loadedAssets.keySet()) {
            int index = assets.getIndex(id);
            if (index == Integer.MIN_VALUE) {
               throw new IllegalArgumentException("Unknown key! " + id);
            }

            if (index >= attitudeGroupCount) {
               this.putItemAttitudeGroups();
               return;
            }
         }

         loadedAssets.forEach((idx, group) -> {
            int indexx = assets.getIndex(idx);
            if (indexx == Integer.MIN_VALUE) {
               throw new IllegalArgumentException("Unknown key! " + idx);
            } else {
               this.itemAttitudeMap.updateAttitudeGroup(indexx, group);
            }
         });
      }
   }

   protected void onItemAttitudeGroupsRemoved(RemovedAssetsEvent<String, ItemAttitudeGroup, AssetMap<String, ItemAttitudeGroup>> event) {
      this.putItemAttitudeGroups();
   }

   private void putItemAttitudeGroups() {
      ItemAttitudeMap.Builder builder = new ItemAttitudeMap.Builder();
      builder.addAttitudeGroups(ItemAttitudeGroup.getAssetMap().getAssetMap());
      this.itemAttitudeMap = builder.build();
   }

   protected void onPathChange(WorldPathChangedEvent event) {
      this.pathChangeRevision.getAndIncrement();
   }

   public int getPathChangeRevision() {
      return this.pathChangeRevision.get();
   }

   protected void onNPCsLoaded(AllNPCsLoadedEvent event) {
      this.putNPCGroups();
   }

   private void putNPCGroups() {
      IndexedLookupTableAssetMap<String, NPCGroup> indexedAssetMap = NPCGroup.getAssetMap();
      Object2IntOpenHashMap<String> tagSetIndexMap = new Object2IntOpenHashMap<>();
      indexedAssetMap.getAssetMap().forEach((name, group) -> {
         int index = indexedAssetMap.getIndex(name);
         if (index == Integer.MIN_VALUE) {
            throw new IllegalArgumentException("Unknown key! " + name);
         } else {
            tagSetIndexMap.put(name, index);
         }
      });
      TagSetPlugin.get(NPCGroup.class).putAssetSets(indexedAssetMap.getAssetMap(), tagSetIndexMap, this.builderManager.getNameToIndexMap());
      this.putAttitudeGroups();
   }

   private void putAttitudeGroups() {
      AttitudeMap.Builder builder = new AttitudeMap.Builder();
      builder.addAttitudeGroups(AttitudeGroup.getAssetMap().getAssetMap());
      this.attitudeMap = builder.build();
   }

   @Nullable
   public String getName(int builderIndex) {
      return this.builderManager.lookupName(builderIndex);
   }

   public int getIndex(String builderName) {
      return this.builderManager.getIndex(builderName);
   }

   @Nullable
   public Builder<Role> tryGetCachedValidRole(int roleIndex) {
      return this.builderManager.tryGetCachedValidRole(roleIndex);
   }

   @Nullable
   public BuilderInfo getBuilderInfo(Builder<?> builder) {
      return this.builderManager.getBuilderInfo(builder);
   }

   public List<String> getRoleTemplateNames(boolean spawnableOnly) {
      return this.builderManager
         .collectMatchingBuilders(
            new ObjectArrayList<>(),
            entry -> entry.getBuilder().category() == Role.class && (!spawnableOnly || entry.getBuilder().isSpawnable()),
            (builderInfo, objects) -> objects.add(builderInfo.getKeyName())
         );
   }

   public boolean hasRoleName(String roleName) {
      return this.getRoleBuilderInfo(this.getIndex(roleName)) != null;
   }

   public void validateSpawnableRole(String roleName) {
      BuilderInfo builder = this.getRoleBuilderInfo(this.getIndex(roleName));
      if (builder == null) {
         throw new SkipSentryException(new IllegalArgumentException(roleName + " does not exist as a role!"));
      } else if (!builder.getBuilder().isSpawnable()) {
         throw new SkipSentryException(new IllegalArgumentException(roleName + " is an abstract role type and cannot be spawned directly!"));
      }
   }

   @Nullable
   public BuilderInfo getRoleBuilderInfo(int roleIndex) {
      BuilderInfo builderInfo = this.builderManager.tryGetBuilderInfo(roleIndex);
      return builderInfo != null && builderInfo.getBuilder().category() == Role.class ? builderInfo : null;
   }

   public void setBuilderInvalid(int builderIndex) {
      BuilderInfo builderInfo = this.builderManager.tryGetBuilderInfo(builderIndex);
      if (builderInfo != null) {
         builderInfo.setNeedsReload();
      }
   }

   public AttitudeMap getAttitudeMap() {
      return this.attitudeMap;
   }

   public ItemAttitudeMap getItemAttitudeMap() {
      return this.itemAttitudeMap;
   }

   public boolean testAndValidateRole(@Nullable BuilderInfo builderInfo) {
      return builderInfo != null
         && builderInfo.getBuilder() != null
         && builderInfo.getBuilder().category() == Role.class
         && this.builderManager.validateBuilder(builderInfo);
   }

   public void forceValidation(int builderIndex) {
      this.builderManager.forceValidation(builderIndex);
   }

   @Nullable
   public Pair<Ref<EntityStore>, NPCEntity> spawnEntity(
      @Nonnull Store<EntityStore> store,
      int roleIndex,
      @Nonnull Vector3d position,
      @Nullable Vector3f rotation,
      @Nullable Model spawnModel,
      @Nullable TriConsumer<NPCEntity, Ref<EntityStore>, Store<EntityStore>> postSpawn
   ) {
      return this.spawnEntity(store, roleIndex, position, rotation, spawnModel, null, postSpawn);
   }

   @Nullable
   public Pair<Ref<EntityStore>, NPCEntity> spawnEntity(
      @Nonnull Store<EntityStore> store,
      int roleIndex,
      @Nonnull Vector3d position,
      @Nullable Vector3f rotation,
      @Nullable Model spawnModel,
      @Nullable TriConsumer<NPCEntity, Holder<EntityStore>, Store<EntityStore>> preAddToWorld,
      @Nullable TriConsumer<NPCEntity, Ref<EntityStore>, Store<EntityStore>> postSpawn
   ) {
      WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType());
      NPCEntity npcComponent = new NPCEntity();
      npcComponent.setSpawnInstant(worldTimeResource.getGameTime());
      if (rotation == null) {
         rotation = NULL_ROTATION;
      }

      npcComponent.saveLeashInformation(position, rotation);
      String roleName = this.getName(roleIndex);
      if (roleName == null) {
         get().getLogger().at(Level.WARNING).log("Unable to spawn entity with invalid role index: %s!", roleIndex);
         return null;
      } else {
         npcComponent.setRoleName(roleName);
         npcComponent.setRoleIndex(roleIndex);
         Holder<EntityStore> holder = EntityStore.REGISTRY.newHolder();
         holder.addComponent(NPCEntity.getComponentType(), npcComponent);
         holder.addComponent(TransformComponent.getComponentType(), new TransformComponent(position, rotation));
         holder.addComponent(HeadRotation.getComponentType(), new HeadRotation(rotation));
         DisplayNameComponent displayNameComponent = new DisplayNameComponent(Message.raw(roleName));
         holder.addComponent(DisplayNameComponent.getComponentType(), displayNameComponent);
         holder.ensureComponent(UUIDComponent.getComponentType());
         if (spawnModel != null) {
            npcComponent.setInitialModelScale(spawnModel.getScale());
            holder.addComponent(ModelComponent.getComponentType(), new ModelComponent(spawnModel));
            holder.addComponent(PersistentModel.getComponentType(), new PersistentModel(spawnModel.toReference()));
         }

         if (preAddToWorld != null) {
            preAddToWorld.accept(npcComponent, holder, store);
         }

         Ref<EntityStore> ref = store.addEntity(holder, AddReason.SPAWN);
         if (ref == null) {
            get().getLogger().at(Level.WARNING).log("Unable to handle non-spawned entity: %s!", this.getName(roleIndex));
            return null;
         } else {
            if (postSpawn != null) {
               postSpawn.accept(npcComponent, ref, store);
            }

            return Pair.of(ref, npcComponent);
         }
      }
   }

   @Nonnull
   public BuilderInfo prepareRoleBuilderInfo(int roleIndex) {
      try {
         BuilderInfo builderInfo = this.builderManager.getCachedBuilderInfo(roleIndex, Role.class);
         if (this.validateBuilder) {
            if (!builderInfo.isValidated()) {
               this.builderManager.validateBuilder(builderInfo);
            }

            if (!builderInfo.isValid()) {
               throw new SkipSentryException(new IllegalStateException("Builder " + builderInfo.getKeyName() + " failed validation!"));
            }
         }

         return builderInfo;
      } catch (RuntimeException var4) {
         throw new SkipSentryException(
            new RuntimeException(String.format("Cannot use role template '%s' (%s): %s", this.getName(roleIndex), roleIndex, var4.getMessage()), var4)
         );
      }
   }

   @Nonnull
   public static Role buildRole(@Nonnull Builder<Role> roleBuilder, @Nonnull BuilderInfo builderInfo, @Nonnull BuilderSupport builderSupport, int roleIndex) {
      Role role;
      try {
         StdScope scope = roleBuilder.getBuilderParameters().createScope();
         builderSupport.setScope(scope);
         builderSupport.setGlobalScope(scope);
         role = roleBuilder.build(builderSupport);
         role.postRoleBuilt(builderSupport);
      } catch (Throwable var6) {
         builderInfo.setNeedsReload();
         throw new SkipSentryException(var6);
      }

      role.setRoleIndex(roleIndex, builderInfo.getKeyName());
      return role;
   }

   protected void onModelsChanged(@Nonnull LoadedAssetsEvent<String, ModelAsset, DefaultAssetMap<String, ModelAsset>> event) {
      Map<String, ModelAsset> loadedModelAssets = event.getLoadedAssets();
      Universe.get()
         .getWorlds()
         .values()
         .forEach(
            world -> world.execute(
               () -> {
                  Store<EntityStore> store = world.getEntityStore().getStore();
                  store.forEachEntityParallel(
                     NPCEntity.getComponentType(),
                     (index, archetypeChunk, commandBuffer) -> {
                        ModelComponent entityModelComponent = archetypeChunk.getComponent(index, ModelComponent.getComponentType());
                        if (entityModelComponent != null) {
                           Model oldModel = entityModelComponent.getModel();
                           ModelAsset newModelAsset = loadedModelAssets.get(oldModel.getModelAssetId());
                           if (newModelAsset != null) {
                              Ref<EntityStore> entityReference = archetypeChunk.getReferenceTo(index);
                              commandBuffer.putComponent(
                                 entityReference,
                                 ModelComponent.getComponentType(),
                                 new ModelComponent(Model.createScaledModel(newModelAsset, oldModel.getScale(), oldModel.getRandomAttachmentIds()))
                              );
                           }
                        }
                     }
                  );
               }
            )
         );
   }

   protected void generateDescriptors() {
      this.getLogger().at(Level.INFO).log("===== Generating descriptors for NPC!");
      this.builderDescriptors = this.builderManager.generateDescriptors();
   }

   protected void saveDescriptors() {
      this.getLogger().at(Level.INFO).log("===== Saving descriptors for NPC!");
      Path path = Path.of("npc_descriptors.json");
      BuilderManager.saveDescriptors(this.builderDescriptors, path);
      this.getLogger().at(Level.INFO).log("Saved NPC descriptors to: %s", path);
   }

   public BuilderManager getBuilderManager() {
      return this.builderManager;
   }

   public int getMaxBlackboardBlockCountPerType() {
      return this.maxBlackboardBlockCountPerType;
   }

   public boolean isLogFailingTestErrors() {
      return this.logFailingTestErrors;
   }

   public boolean startRoleBenchmark(double seconds, @Nonnull Consumer<Int2ObjectMap<TimeDistributionRecorder>> onFinished) {
      this.benchmarkLock.lock();

      label37: {
         boolean var4;
         try {
            if (!this.isBenchmarking()) {
               this.roleTickDistribution = new Int2ObjectOpenHashMap<>();
               this.roleTickDistributionAll = new TimeDistributionRecorder(0.01, 1.0E-5);
               this.roleTickDistribution.put(-1, this.roleTickDistributionAll);
               break label37;
            }

            var4 = false;
         } finally {
            this.benchmarkLock.unlock();
         }

         return var4;
      }

      new CompletableFuture().completeOnTimeout(null, Math.round(seconds * 1000.0), TimeUnit.MILLISECONDS).thenRunAsync(() -> {
         Int2ObjectMap<TimeDistributionRecorder> distribution = this.roleTickDistribution;
         this.benchmarkLock.lock();

         try {
            this.roleTickDistribution = null;
            this.roleTickDistributionAll = null;
         } finally {
            this.benchmarkLock.unlock();
         }

         onFinished.accept(distribution);
      });
      return true;
   }

   public void collectRoleTick(int roleIndex, long nanos) {
      if (this.benchmarkLock.tryLock()) {
         try {
            if (this.roleTickDistribution != null) {
               this.roleTickDistribution.computeIfAbsent(roleIndex, i -> new TimeDistributionRecorder(0.01, 1.0E-5)).recordNanos(nanos);
               this.roleTickDistributionAll.recordNanos(nanos);
            }
         } finally {
            this.benchmarkLock.unlock();
         }
      }
   }

   public boolean isBenchmarkingRole() {
      return this.roleTickDistribution != null;
   }

   public boolean startSensorSupportBenchmark(double seconds, @Nonnull Consumer<Int2ObjectMap<SensorSupportBenchmark>> onFinished) {
      this.benchmarkLock.lock();

      label37: {
         boolean var4;
         try {
            if (!this.isBenchmarking()) {
               this.roleSensorSupportDistribution = new Int2ObjectOpenHashMap<>();
               this.roleSensorSupportDistributionAll = new SensorSupportBenchmark();
               this.roleSensorSupportDistribution.put(-1, this.roleSensorSupportDistributionAll);
               break label37;
            }

            var4 = false;
         } finally {
            this.benchmarkLock.unlock();
         }

         return var4;
      }

      new CompletableFuture().completeOnTimeout(null, Math.round(seconds * 1000.0), TimeUnit.MILLISECONDS).thenRunAsync(() -> {
         Int2ObjectMap<SensorSupportBenchmark> distribution = this.roleSensorSupportDistribution;
         this.benchmarkLock.lock();

         try {
            this.roleSensorSupportDistribution = null;
            this.roleSensorSupportDistributionAll = null;
         } finally {
            this.benchmarkLock.unlock();
         }

         onFinished.accept(distribution);
      });
      return true;
   }

   public boolean isBenchmarkingSensorSupport() {
      return this.roleSensorSupportDistributionAll != null;
   }

   protected boolean isBenchmarking() {
      return this.isBenchmarkingRole() || this.isBenchmarkingSensorSupport();
   }

   public void collectSensorSupportPlayerList(
      int roleIndex, long getNanos, double maxPlayerDistanceSorted, double maxPlayerDistance, double maxPlayerDistanceAvoidance, int numPlayers
   ) {
      if (this.benchmarkLock.tryLock()) {
         try {
            if (this.roleSensorSupportDistribution != null) {
               this.roleSensorSupportDistribution
                  .computeIfAbsent(roleIndex, i -> new SensorSupportBenchmark())
                  .collectPlayerList(getNanos, maxPlayerDistanceSorted, maxPlayerDistance, maxPlayerDistanceAvoidance, numPlayers);
               this.roleSensorSupportDistributionAll
                  .collectPlayerList(getNanos, maxPlayerDistanceSorted, maxPlayerDistance, maxPlayerDistanceAvoidance, numPlayers);
            }
         } finally {
            this.benchmarkLock.unlock();
         }
      }
   }

   public void collectSensorSupportEntityList(
      int roleIndex, long getNanos, double maxEntityDistanceSorted, double maxEntityDistance, double maxEntityDistanceAvoidance, int numEntities
   ) {
      if (this.benchmarkLock.tryLock()) {
         try {
            if (this.roleSensorSupportDistribution != null) {
               this.roleSensorSupportDistribution
                  .computeIfAbsent(roleIndex, i -> new SensorSupportBenchmark())
                  .collectEntityList(getNanos, maxEntityDistanceSorted, maxEntityDistance, maxEntityDistanceAvoidance, numEntities);
               this.roleSensorSupportDistributionAll
                  .collectEntityList(getNanos, maxEntityDistanceSorted, maxEntityDistance, maxEntityDistanceAvoidance, numEntities);
            }
         } finally {
            this.benchmarkLock.unlock();
         }
      }
   }

   public void collectSensorSupportLosTest(int roleIndex, boolean cacheHit, long time) {
      if (this.isBenchmarkingSensorSupport() && this.benchmarkLock.tryLock()) {
         try {
            if (this.roleSensorSupportDistribution != null) {
               this.roleSensorSupportDistribution.computeIfAbsent(roleIndex, i -> new SensorSupportBenchmark()).collectLosTest(cacheHit, time);
               this.roleSensorSupportDistributionAll.collectLosTest(cacheHit, time);
            }
         } finally {
            this.benchmarkLock.unlock();
         }
      }
   }

   public void collectSensorSupportInverseLosTest(int roleIndex, boolean cacheHit) {
      if (this.isBenchmarkingSensorSupport() && this.benchmarkLock.tryLock()) {
         try {
            if (this.roleSensorSupportDistribution != null) {
               this.roleSensorSupportDistribution.computeIfAbsent(roleIndex, i -> new SensorSupportBenchmark()).collectInverseLosTest(cacheHit);
               this.roleSensorSupportDistributionAll.collectInverseLosTest(cacheHit);
            }
         } finally {
            this.benchmarkLock.unlock();
         }
      }
   }

   public void collectSensorSupportFriendlyBlockingTest(int roleIndex, boolean cacheHit) {
      if (this.isBenchmarkingSensorSupport() && this.benchmarkLock.tryLock()) {
         try {
            if (this.roleSensorSupportDistribution != null) {
               this.roleSensorSupportDistribution.computeIfAbsent(roleIndex, i -> new SensorSupportBenchmark()).collectFriendlyBlockingTest(cacheHit);
               this.roleSensorSupportDistributionAll.collectFriendlyBlockingTest(cacheHit);
            }
         } finally {
            this.benchmarkLock.unlock();
         }
      }
   }

   public void collectSensorSupportTickDone(int roleIndex) {
      if (this.isBenchmarkingSensorSupport() && this.benchmarkLock.tryLock()) {
         try {
            if (this.roleSensorSupportDistribution != null) {
               this.roleSensorSupportDistribution.computeIfAbsent(roleIndex, i -> new SensorSupportBenchmark()).tickDone();
               this.roleSensorSupportDistributionAll.tickDone();
            }
         } finally {
            this.benchmarkLock.unlock();
         }
      }
   }

   @Nonnull
   public <T> NPCPlugin registerCoreComponentType(String name, @Nonnull Supplier<Builder<T>> builder) {
      BuilderFactory<T> factory = this.builderManager.getFactory(builder.get().category());
      factory.add(name, builder);
      return this;
   }

   public void setRoleBuilderNeedsReload(Builder<?> builder) {
      BuilderInfo builderInfo = this.getBuilderInfo(builder);
      Objects.requireNonNull(builderInfo, "Have builder but can't get builderInfo for it");
      builderInfo.setNeedsReload();
   }

   protected void registerCoreFactories() {
      BuilderFactory<Role> roleFactory = new BuilderFactory<>(Role.class, "Type");
      roleFactory.add("Generic", BuilderRole::new);
      roleFactory.add("Abstract", BuilderRoleAbstract::new);
      roleFactory.add("Variant", BuilderRoleVariant::new);
      this.builderManager.registerFactory(roleFactory);
      BuilderFactory<MotionController> motionControllerFactory = new BuilderFactory<>(MotionController.class, "Type");
      motionControllerFactory.add("Walk", BuilderMotionControllerWalk::new);
      motionControllerFactory.add("Fly", BuilderMotionControllerFly::new);
      motionControllerFactory.add("Dive", BuilderMotionControllerDive::new);
      this.builderManager.registerFactory(motionControllerFactory);
      BuilderFactory<Map<String, MotionController>> motionControllerMapFactory = new BuilderFactory<>(
         BuilderMotionControllerMapUtil.CLASS_REFERENCE, "Type", BuilderMotionControllerMap::new
      );
      this.builderManager.registerFactory(motionControllerMapFactory);
      BuilderFactory<ActionList> actionListFactory = new BuilderFactory<>(ActionList.class, "Type", BuilderActionList::new);
      this.builderManager.registerFactory(actionListFactory);
      BuilderFactory<Instruction> instructionFactory = new BuilderFactory<>(Instruction.class, "Type", BuilderInstruction::new);
      instructionFactory.add("Random", BuilderInstructionRandomized::new);
      instructionFactory.add("Reference", BuilderInstructionReference::new);
      this.builderManager.registerFactory(instructionFactory);
      BuilderFactory<TransientPathDefinition> transientPathFactory = new BuilderFactory<>(
         TransientPathDefinition.class, "Type", BuilderTransientPathDefinition::new
      );
      this.builderManager.registerFactory(transientPathFactory);
      BuilderFactory<RelativeWaypointDefinition> relativeWaypointFactory = new BuilderFactory<>(
         RelativeWaypointDefinition.class, "Type", BuilderRelativeWaypointDefinition::new
      );
      this.builderManager.registerFactory(relativeWaypointFactory);
      BuilderFactory<WeightedAction> weightedActionFactory = new BuilderFactory<>(WeightedAction.class, "Type", BuilderWeightedAction::new);
      this.builderManager.registerFactory(weightedActionFactory);
      BuilderFactory<BuilderValueToParameterMapping.ValueToParameterMapping> valueToParameterMappingFactory = new BuilderFactory<>(
         BuilderValueToParameterMapping.ValueToParameterMapping.class, "Type", BuilderValueToParameterMapping::new
      );
      this.builderManager.registerFactory(valueToParameterMappingFactory);
      StateTransitionController.registerFactories(this.builderManager);
      this.builderManager.registerFactory(new BuilderFactory<>(BodyMotion.class, "Type"));
      this.builderManager.registerFactory(new BuilderFactory<>(HeadMotion.class, "Type"));
      this.builderManager.registerFactory(new BuilderFactory<>(Action.class, "Type"));
      this.builderManager.registerFactory(new BuilderFactory<>(Sensor.class, "Type"));
      this.builderManager.registerFactory(new BuilderFactory<>(IEntityFilter.class, "Type"));
      this.builderManager.registerFactory(new BuilderFactory<>(ISensorEntityPrioritiser.class, "Type"));
      this.builderManager.registerFactory(new BuilderFactory<>(ISensorEntityCollector.class, "Type"));
   }

   protected static void onBalanceAssetsChanged(@Nonnull LoadedAssetsEvent<String, BalanceAsset, DefaultAssetMap<String, BalanceAsset>> event) {
      Map<String, BalanceAsset> loadedAssets = event.getLoadedAssets();
      Universe.get()
         .getWorlds()
         .forEach(
            (name, world) -> world.execute(
               () -> world.getEntityStore().getStore().forEachChunk(NPCEntity.getComponentType(), (archetypeChunk, commandBuffer) -> {
                  for (int index = 0; index < archetypeChunk.size(); index++) {
                     NPCEntity npcComponent = archetypeChunk.getComponent(index, NPCEntity.getComponentType());

                     assert npcComponent != null;

                     if (loadedAssets.containsKey(npcComponent.getRole().getBalanceAsset())) {
                        Ref<EntityStore> ref = archetypeChunk.getReferenceTo(index);
                        RoleChangeSystem.requestRoleChange(ref, npcComponent.getRole(), npcComponent.getRoleIndex(), false, world.getEntityStore().getStore());
                     }
                  }
               })
            )
         );
   }

   protected static void onBalanceAssetsRemoved(@Nonnull RemovedAssetsEvent<String, BalanceAsset, DefaultAssetMap<String, BalanceAsset>> event) {
      Set<String> removedAssets = event.getRemovedAssets();
      Universe.get()
         .getWorlds()
         .forEach(
            (name, world) -> world.execute(
               () -> world.getEntityStore().getStore().forEachChunk(NPCEntity.getComponentType(), (archetypeChunk, commandBuffer) -> {
                  for (int index = 0; index < archetypeChunk.size(); index++) {
                     NPCEntity npcComponent = archetypeChunk.getComponent(index, NPCEntity.getComponentType());

                     assert npcComponent != null;

                     if (removedAssets.contains(npcComponent.getRole().getBalanceAsset())) {
                        Ref<EntityStore> ref = archetypeChunk.getReferenceTo(index);
                        commandBuffer.removeEntity(ref, RemoveReason.REMOVE);
                     }
                  }
               })
            )
         );
   }

   public static class NPCConfig {
      public static final BuilderCodec<NPCPlugin.NPCConfig> CODEC = BuilderCodec.builder(NPCPlugin.NPCConfig.class, NPCPlugin.NPCConfig::new)
         .append(new KeyedCodec<>("Descriptors", Codec.BOOLEAN), (o, i) -> o.generateDescriptors = i, o -> o.generateDescriptors)
         .add()
         .append(new KeyedCodec<>("DescriptorsFile", Codec.BOOLEAN), (o, i) -> o.generateDescriptorsFile = i, o -> o.generateDescriptorsFile)
         .add()
         .append(new KeyedCodec<>("AutoReload", Codec.BOOLEAN), (o, i) -> o.autoReload = i, o -> o.autoReload)
         .add()
         .append(new KeyedCodec<>("ValidateBuilders", Codec.BOOLEAN), (o, i) -> o.validateBuilder = i, o -> o.validateBuilder)
         .add()
         .append(new KeyedCodec<>("MaxBlackboardBlockType", Codec.INTEGER), (o, i) -> o.maxBlackboardBlockType = i, o -> o.maxBlackboardBlockType)
         .add()
         .append(new KeyedCodec<>("LogFailingTestErrors", Codec.BOOLEAN), (o, i) -> o.logFailingTestErrors = i, o -> o.logFailingTestErrors)
         .add()
         .append(new KeyedCodec<>("PresetCoverageTestNPCs", Codec.STRING_ARRAY), (o, i) -> o.presetCoverageTestNPCs = i, o -> o.presetCoverageTestNPCs)
         .add()
         .build();
      private boolean generateDescriptors;
      private boolean generateDescriptorsFile;
      private boolean autoReload = true;
      private boolean validateBuilder = true;
      private int maxBlackboardBlockType = 20;
      private boolean logFailingTestErrors;
      private String[] presetCoverageTestNPCs = new String[]{
         "Test_Bird",
         "Test_Block_Sensor",
         "Test_Attack_Bow",
         "Test_Combat_Sensor_Sheep",
         "Test_Bow_Charge",
         "Test_OffHand_Swap",
         "Test_Patrol_Path",
         "Test_Flock_Mixed#4",
         "Test_Group_Sheep",
         "Test_Attack_Flying_Ranged",
         "Test_Interaction_Complete_Task",
         "Test_Hotbar_Weapon_Swap",
         "Test_Inventory_Contents",
         "Test_Dive_Flee",
         "Test_Jumping",
         "Test_Walk_Leave",
         "Test_Walk_Seek",
         "Test_Watch",
         "Test_Chained_Path",
         "Test_Spawn_Action",
         "Test_State_Evaluator_Toggle",
         "Test_State_Evaluator_Sleep",
         "Test_Alarm",
         "Test_Timer_Repeating",
         "Test_Action_Model_Attachment",
         "Test_Animation_State_Change",
         "Test_Block_Change",
         "Test_Crouch",
         "Test_Drop_Item",
         "Test_Entity_Damage_Event",
         "Test_Hover_Parrot",
         "Test_In_Water",
         "Test_Light_Sensor",
         "Test_Model_Change",
         "Test_Particle_Emotions",
         "Test_Place_Blocks",
         "Test_Position_Adjustment_Wrapper",
         "Test_Probe",
         "Test_Sensor_Age",
         "Test_Sensor_DroppedItem",
         "Test_Shoot_At_Block",
         "Test_Species_Attitude",
         "Test_Standing_On_Block_Sensor",
         "Test_Teleport",
         "Test_Throw_NPC",
         "Test_Trigger_Spawners",
         "Test_Weather_Sensor",
         "Test_Bird_Land"
      };

      public boolean isGenerateDescriptors() {
         return this.generateDescriptors;
      }

      public boolean isGenerateDescriptorsFile() {
         return this.generateDescriptorsFile;
      }

      public boolean isAutoReload() {
         return this.autoReload;
      }

      public boolean isValidateBuilder() {
         return this.validateBuilder;
      }

      public int getMaxBlackboardBlockType() {
         return this.maxBlackboardBlockType;
      }

      public boolean isLogFailingTestErrors() {
         return this.logFailingTestErrors;
      }

      public String[] getPresetCoverageTestNPCs() {
         return this.presetCoverageTestNPCs;
      }
   }

   public static class NPCEntityRegenerateStatsSystem extends EntityStatsSystems.Regenerate<NPCEntity> {
      public NPCEntityRegenerateStatsSystem() {
         super(EntityStatMap.getComponentType(), NPCEntity.getComponentType());
      }
   }
}
