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

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

import com.hypixel.hytale.server.npc.config.ItemAttitudeGroup;
import com.hypixel.hytale.server.npc.config.AttitudeGroup;
import com.hypixel.hytale.server.npc.role.support.WorldSupport;
import com.hypixel.hytale.server.core.asset.type.blockset.config.BlockSet;
import com.hypixel.hytale.server.npc.asset.builder.BuilderBase;
import com.hypixel.hytale.math.vector.Vector3d;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.spawning.ISpawnable;
import com.hypixel.hytale.server.spawning.SpawnTestResult;
import com.hypixel.hytale.server.spawning.SpawningContext;
import com.hypixel.hytale.server.npc.asset.builder.BuilderInfo;
import java.util.Objects;
import com.hypixel.hytale.server.npc.asset.builder.Builder;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.server.npc.asset.builder.validators.ArrayValidator;
import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectArrayHelper;
import com.hypixel.hytale.server.npc.asset.builder.InstructionType;
import com.hypixel.hytale.server.npc.asset.builder.InstructionContextHelper;
import com.hypixel.hytale.server.npc.asset.builder.StateMappingHelper;
import com.hypixel.hytale.server.npc.asset.builder.FeatureEvaluatorHelper;
import com.hypixel.hytale.server.npc.asset.builder.BuilderValidationHelper;
import java.util.Collection;
import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSequenceValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ItemAttitudeGroupExistsValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.asset.AttitudeGroupExistsValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleArrayValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.asset.RootInteractionValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.TagSetExistsValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.StringNullOrNotEmptyValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleRangeValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ItemDropListExistsValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ItemExistsValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.IntValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.IntRangeValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.DoubleSingleValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.asset.BlockSetExistsValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.StringNotEmptyValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.StringArrayValidator;
import java.util.function.Function;
import com.hypixel.hytale.server.npc.asset.builder.validators.StringArrayNoEmptyStringsValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.AssetValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.asset.ModelExistsValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.StringValidator;
import com.hypixel.hytale.server.npc.asset.builder.validators.IntSingleValidator;
import com.google.gson.JsonElement;
import com.hypixel.hytale.server.npc.asset.builder.BuilderFactory;
import com.hypixel.hytale.server.npc.movement.controllers.builders.BuilderMotionControllerBase;
import java.util.List;
import com.hypixel.hytale.server.npc.util.expression.Scope;
import com.hypixel.hytale.server.npc.util.expression.ExecutionContext;
import com.hypixel.hytale.server.npc.validators.NPCLoadTimeValidationHelper;
import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport;
import com.hypixel.hytale.server.npc.asset.builder.BuilderDescriptorState;
import com.hypixel.hytale.server.npc.config.balancing.BalanceAsset;
import com.hypixel.hytale.codec.validation.Validator;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.server.npc.asset.builder.BuilderContext;
import com.hypixel.hytale.server.npc.movement.controllers.BuilderMotionControllerMapUtil;
import com.hypixel.hytale.server.npc.asset.builder.holder.NumberArrayHolder;
import com.hypixel.hytale.server.npc.asset.builder.BuilderTemplateInteractionVars;
import com.hypixel.hytale.server.npc.asset.builder.BuilderCombatConfig;
import com.hypixel.hytale.server.npc.decisionmaker.stateevaluator.StateEvaluator;
import com.hypixel.hytale.server.npc.asset.builder.BuilderCodecObjectHelper;
import com.hypixel.hytale.server.npc.asset.builder.holder.StringHolder;
import com.hypixel.hytale.server.npc.statetransition.StateTransitionController;
import com.hypixel.hytale.server.npc.instructions.Instruction;
import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectListHelper;
import com.hypixel.hytale.server.npc.movement.controllers.MotionController;
import java.util.Map;
import com.hypixel.hytale.server.npc.asset.builder.BuilderObjectReferenceHelper;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import com.hypixel.hytale.server.core.asset.type.attitude.Attitude;
import com.hypixel.hytale.server.npc.asset.builder.holder.EnumHolder;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.npc.asset.builder.holder.AssetArrayHolder;
import com.hypixel.hytale.server.npc.asset.builder.holder.FloatHolder;
import com.hypixel.hytale.server.npc.asset.builder.holder.StringArrayHolder;
import com.hypixel.hytale.server.npc.asset.builder.holder.BooleanHolder;
import com.hypixel.hytale.server.npc.asset.builder.holder.DoubleHolder;
import com.hypixel.hytale.server.npc.role.RoleDebugFlags;
import java.util.EnumSet;
import com.hypixel.hytale.server.npc.asset.builder.holder.IntHolder;
import com.hypixel.hytale.server.npc.asset.builder.holder.AssetHolder;
import com.hypixel.hytale.server.npc.role.SpawnEffect;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.server.npc.asset.builder.SpawnableWithModelBuilder;

public class BuilderRole extends SpawnableWithModelBuilder<Role> implements SpawnEffect
{
    protected static final double[] DEFAULT_HEAD_PITCH_RANGE;
    protected String[] displayNames;
    protected final AssetHolder appearance;
    protected final AssetHolder dropListId;
    protected final IntHolder maxHealth;
    protected String startState;
    protected String defaultSubState;
    protected int startStateIndex;
    protected int startSubStateIndex;
    protected final EnumSet<RoleDebugFlags> parsedDebugFlags;
    protected String debugFlags;
    protected double inertia;
    protected final DoubleHolder knockbackScale;
    protected String opaqueBlockSet;
    protected boolean applyAvoidance;
    protected double entityAvoidanceStrength;
    protected double collisionDistance;
    protected double collisionForceFalloff;
    protected double collisionRadius;
    protected float collisionViewAngle;
    protected double separationDistance;
    protected double separationWeight;
    protected double separationDistanceTarget;
    protected double separationNearRadiusTarget;
    protected double separationFarRadiusTarget;
    protected final BooleanHolder applySeparation;
    protected boolean stayInEnvironment;
    protected String allowedEnvironments;
    protected final StringArrayHolder flockSpawnTypes;
    protected final BooleanHolder flockSpawnTypeRandom;
    protected final StringArrayHolder flockAllowedRoles;
    protected final BooleanHolder canLeadFlock;
    protected final FloatHolder spawnLockTime;
    protected double flockWeightAlignment;
    protected double flockWeightSeparation;
    protected double flockWeightCohesion;
    protected double flockInfluenceRange;
    protected boolean corpseStaysInFlock;
    protected final BooleanHolder invulnerable;
    protected final BooleanHolder breathesInAir;
    protected final BooleanHolder breathesInWater;
    protected final AssetArrayHolder hotbarItems;
    protected final AssetArrayHolder offHandItems;
    protected final AssetHolder inventoryItemsDropList;
    protected final IntHolder defaultOffHandSlot;
    protected boolean pickupDropOnDeath;
    protected String[] armor;
    protected double deathAnimationTime;
    protected float despawnAnimationTime;
    @Nonnull
    protected AssetHolder deathInteraction;
    protected Role.AvoidanceMode avoidanceMode;
    protected boolean disableDamageFlock;
    protected final AssetArrayHolder disableDamageGroups;
    protected String spawnParticles;
    protected double[] spawnParticleOffset;
    protected double spawnViewDistance;
    protected int inventorySlots;
    protected int hotbarSlots;
    protected int offHandSlots;
    protected final EnumHolder<Attitude> defaultPlayerAttitude;
    protected final EnumHolder<Attitude> defaultNPCAttitude;
    protected final AssetHolder attitudeGroup;
    protected final AssetHolder itemAttitudeGroup;
    protected Int2ObjectMap<IntSet> busyStates;
    protected final BuilderObjectReferenceHelper<Map<String, MotionController>> motionControllers;
    protected final BuilderObjectListHelper<Instruction> instructionList;
    protected final BuilderObjectReferenceHelper<Instruction> interactionInstruction;
    protected final BuilderObjectReferenceHelper<Instruction> deathInstruction;
    protected final BuilderObjectReferenceHelper<StateTransitionController> stateTransitionController;
    protected final StringHolder initialMotionController;
    protected final BuilderCodecObjectHelper<StateEvaluator> stateEvaluator;
    protected final BuilderCombatConfig combatConfig;
    protected final BuilderTemplateInteractionVars interactionVars;
    protected final BooleanHolder isMemory;
    protected final StringHolder memoriesCategory;
    protected final StringHolder memoriesNameOverride;
    protected final StringHolder nameTranslationKey;
    protected final NumberArrayHolder headPitchAngleRange;
    protected final BooleanHolder overrideHeadPitchAngle;
    
    public BuilderRole() {
        this.appearance = new AssetHolder();
        this.dropListId = new AssetHolder();
        this.maxHealth = new IntHolder();
        this.parsedDebugFlags = EnumSet.noneOf(RoleDebugFlags.class);
        this.knockbackScale = new DoubleHolder();
        this.applySeparation = new BooleanHolder();
        this.flockSpawnTypes = new StringArrayHolder();
        this.flockSpawnTypeRandom = new BooleanHolder();
        this.flockAllowedRoles = new StringArrayHolder();
        this.canLeadFlock = new BooleanHolder();
        this.spawnLockTime = new FloatHolder();
        this.invulnerable = new BooleanHolder();
        this.breathesInAir = new BooleanHolder();
        this.breathesInWater = new BooleanHolder();
        this.hotbarItems = new AssetArrayHolder();
        this.offHandItems = new AssetArrayHolder();
        this.inventoryItemsDropList = new AssetHolder();
        this.defaultOffHandSlot = new IntHolder();
        this.deathInteraction = new AssetHolder();
        this.disableDamageGroups = new AssetArrayHolder();
        this.defaultPlayerAttitude = new EnumHolder<Attitude>();
        this.defaultNPCAttitude = new EnumHolder<Attitude>();
        this.attitudeGroup = new AssetHolder();
        this.itemAttitudeGroup = new AssetHolder();
        this.motionControllers = new BuilderObjectReferenceHelper<Map<String, MotionController>>(BuilderMotionControllerMapUtil.CLASS_REFERENCE, this);
        this.instructionList = new BuilderObjectListHelper<Instruction>(Instruction.class, this);
        this.interactionInstruction = new BuilderObjectReferenceHelper<Instruction>(Instruction.class, this);
        this.deathInstruction = new BuilderObjectReferenceHelper<Instruction>(Instruction.class, this);
        this.stateTransitionController = new BuilderObjectReferenceHelper<StateTransitionController>(StateTransitionController.class, this);
        this.initialMotionController = new StringHolder();
        this.stateEvaluator = new BuilderCodecObjectHelper<StateEvaluator>(StateEvaluator.class, StateEvaluator.CODEC, null);
        this.combatConfig = new BuilderCombatConfig(BalanceAsset.CHILD_ASSET_CODEC, BalanceAsset.VALIDATOR_CACHE.getValidator());
        this.interactionVars = new BuilderTemplateInteractionVars();
        this.isMemory = new BooleanHolder();
        this.memoriesCategory = new StringHolder();
        this.memoriesNameOverride = new StringHolder();
        this.nameTranslationKey = new StringHolder();
        this.headPitchAngleRange = new NumberArrayHolder();
        this.overrideHeadPitchAngle = new BooleanHolder();
    }
    
    @Nonnull
    @Override
    public String getShortDescription() {
        return "Generic role for NPC";
    }
    
    @Nonnull
    @Override
    public String getLongDescription() {
        return "Generic role for NPC with a core planner and list of Motion controllers.";
    }
    
    @Nonnull
    @Override
    public BuilderDescriptorState getBuilderDescriptorState() {
        return BuilderDescriptorState.Stable;
    }
    
    @Nonnull
    @Override
    public Role build(@Nonnull final BuilderSupport builderSupport) {
        return new Role(this, builderSupport);
    }
    
    @Override
    public boolean validate(final String configName, @Nonnull final NPCLoadTimeValidationHelper validationHelper, @Nonnull final ExecutionContext context, final Scope globalScope, @Nonnull final List<String> errors) {
        validationHelper.setInventorySizes(this.inventorySlots, this.hotbarSlots, this.offHandSlots);
        boolean hotbarValid;
        try {
            final String[] hotbar = this.hotbarItems.get(context);
            hotbarValid = (hotbar == null || hotbar.length == 0 || validationHelper.validateHotbarHasSlot(hotbar.length - 1, "HotbarItems", errors));
        }
        catch (final IllegalStateException e) {
            errors.add(String.format("%s: %s", configName, e.getMessage()));
            hotbarValid = false;
        }
        boolean offHandValid;
        try {
            final String[] offHand = this.offHandItems.get(context);
            offHandValid = ((offHand == null || offHand.length == 0 || validationHelper.validateOffHandHasSlot(offHand.length - 1, "OffHandItems", errors)) & validationHelper.validateOffHandHasSlot(this.defaultOffHandSlot.get(context), "DefaultOffHandSlot", errors));
        }
        catch (final IllegalStateException e2) {
            errors.add(String.format("%s: %s", configName, e2.getMessage()));
            offHandValid = false;
        }
        boolean validInitialMotionController = true;
        final String mc = this.initialMotionController.get(context);
        if (mc != null) {
            final BuilderFactory<MotionController> factory = this.builderManager.getFactory(MotionController.class);
            try {
                final BuilderMotionControllerBase builder = (BuilderMotionControllerBase)factory.createBuilder(mc);
                validationHelper.requireMotionControllerType(builder.getClassType());
            }
            catch (final IllegalArgumentException e3) {
                errors.add(String.format("%s: Unable to set InitialMotionController to %s as no such MotionController exists", configName, mc));
                validInitialMotionController = false;
            }
        }
        return super.validate(configName, validationHelper, context, globalScope, errors) & hotbarValid & offHandValid & validInitialMotionController & this.instructionList.validate(configName, validationHelper, this.builderManager, context, globalScope, errors) & this.interactionInstruction.validate(configName, validationHelper, this.builderManager, context, globalScope, errors) & this.deathInstruction.validate(configName, validationHelper, this.builderManager, context, globalScope, errors) & this.stateTransitionController.validate(configName, validationHelper, this.builderManager, context, globalScope, errors) & this.motionControllers.validate(configName, validationHelper, this.builderManager, context, globalScope, errors) & this.combatConfig.validate(configName, validationHelper, context, errors);
    }
    
    @Nonnull
    @Override
    public BuilderRole readConfig(@Nonnull final JsonElement data) {
        super.readCommonConfig(data);
        this.setNotComponent();
        this.requireInt(data, "MaxHealth", this.maxHealth, IntSingleValidator.greater0(), BuilderDescriptorState.Stable, "Max health", null);
        this.getString(data, "Debug", e -> this.debugFlags = e, "", null, BuilderDescriptorState.WorkInProgress, "Debugging flags", null);
        this.requireAsset(data, "Appearance", this.appearance, ModelExistsValidator.required(), BuilderDescriptorState.Stable, "Model to use for rendering", null);
        this.getStringArray(data, "DisplayNames", a -> this.displayNames = a, null, null, StringArrayNoEmptyStringsValidator.get(), BuilderDescriptorState.Stable, "List of possible display names to choose from", null);
        this.requireString(data, "NameTranslationKey", this.nameTranslationKey, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The translation key for this NPC's name", null);
        this.getAsset(data, "OpaqueBlockSet", v -> this.opaqueBlockSet = v, "Opaque", BlockSetExistsValidator.withConfig(AssetValidator.CanBeEmpty), BuilderDescriptorState.Stable, "Blocks blocking line of sight", null);
        this.getDouble(data, "Inertia", d -> this.inertia = d, 1.0, DoubleSingleValidator.greater(0.1), BuilderDescriptorState.Experimental, "Inertia", null);
        this.getDouble(data, "KnockbackScale", this.knockbackScale, 1.0, DoubleSingleValidator.greaterEqual0(), BuilderDescriptorState.Stable, "Scale factor for knockback", "Scale factor for knockback. Values greater 1 increase knockback. Smaller values decrease it.");
        this.getInt(data, "InventorySize", v -> this.inventorySlots = v, 0, IntRangeValidator.between(0, 36), BuilderDescriptorState.Stable, "Number of available inventory slots", null);
        this.getInt(data, "HotbarSize", v -> this.hotbarSlots = v, 3, IntRangeValidator.between(3, 8), BuilderDescriptorState.Stable, "Number of available hotbar slots", null);
        this.getInt(data, "OffHandSlots", v -> this.offHandSlots = v, 0, IntRangeValidator.between(0, 4), BuilderDescriptorState.Stable, "The number of slots for off-hand items", null);
        this.getAssetArray(data, "HotbarItems", this.hotbarItems, null, 0, 8, ItemExistsValidator.orDroplistWithConfig(AssetValidator.ListCanBeEmpty), BuilderDescriptorState.Stable, "Hotbar items (e.g. primary weapon, secondary weapon, etc)", null);
        this.getAssetArray(data, "OffHandItems", this.offHandItems, null, 0, 8, ItemExistsValidator.withConfig(AssetValidator.ListCanBeEmpty), BuilderDescriptorState.Stable, "Off-hand items (e.g. shields, torches, etc)", null);
        this.getAsset(data, "PossibleInventoryItems", this.inventoryItemsDropList, null, ItemDropListExistsValidator.withConfig(AssetValidator.CanBeEmpty), BuilderDescriptorState.Stable, "A droplist defining the possible items the NPCs inventory could contain", null);
        this.getInt(data, "DefaultOffHandSlot", this.defaultOffHandSlot, -1, IntRangeValidator.between(-1, 4), BuilderDescriptorState.Stable, "The default off-hand item slot (-1 is empty)", null);
        this.getAssetArray(data, "Armor", a -> this.armor = a, null, null, ItemExistsValidator.withConfig(AssetValidator.ListCanBeEmpty), BuilderDescriptorState.WorkInProgress, "Armor items", null);
        this.getAsset(data, "DropList", this.dropListId, null, ItemDropListExistsValidator.withConfig(AssetValidator.CanBeEmpty), BuilderDescriptorState.Stable, "Drop list to spawn when killed", null);
        this.getString(data, "StartState", s -> this.startState = s, "start", StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "Initial state", null);
        this.getDefaultSubState(data, "DefaultSubState", v -> this.defaultSubState = v, StringNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The default sub state to reference when transitioning to a main state without a specified sub state", null);
        this.getDouble(data, "CollisionDistance", d -> this.collisionDistance = d, 5.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Stable, "Collision lookahead", null);
        this.getDouble(data, "CollisionForceFalloff", d -> this.collisionForceFalloff = d, 2.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Experimental, "Falloff rate for collision force", null);
        this.getDouble(data, "CollisionRadius", d -> this.collisionRadius = d, -1.0, null, BuilderDescriptorState.Experimental, "Collision radius override", null);
        this.getFloat(data, "CollisionViewAngle", d -> this.collisionViewAngle = d, 320.0f, DoubleRangeValidator.between(0.0, 360.0), BuilderDescriptorState.Experimental, "Collision detection view cone", null);
        this.getDouble(data, "SeparationDistance", d -> this.separationDistance = d, 3.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Experimental, "Desired separation distance", null);
        this.getDouble(data, "SeparationWeight", d -> this.separationWeight = d, 1.0, DoubleSingleValidator.greaterEqual0(), BuilderDescriptorState.Experimental, "Blend factor separation", null);
        this.getDouble(data, "SeparationDistanceTarget", d -> this.separationDistanceTarget = d, 1.0, DoubleSingleValidator.greaterEqual0(), BuilderDescriptorState.Experimental, "Desired separation distance when close to target", null);
        this.getDouble(data, "SeparationNearRadiusTarget", d -> this.separationNearRadiusTarget = d, 1.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Experimental, "Distance when using SeparationDistanceTarget", null);
        this.getDouble(data, "SeparationFarRadiusTarget", d -> this.separationFarRadiusTarget = d, 5.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Experimental, "Use normal separation distance from further than this distance", null);
        this.getBoolean(data, "ApplyAvoidance", b -> this.applyAvoidance = b, false, BuilderDescriptorState.Experimental, "Apply avoidance steering force", null);
        this.getBoolean(data, "ApplySeparation", this.applySeparation, false, BuilderDescriptorState.Experimental, "Apply separation steering force", null);
        this.getEnum(data, "AvoidanceMode", e -> this.avoidanceMode = e, Role.AvoidanceMode.class, Role.AvoidanceMode.Any, BuilderDescriptorState.Experimental, "Abilities to use for avoidance", null);
        this.getDouble(data, "EntityAvoidanceStrength", d -> this.entityAvoidanceStrength = d, 1.0, DoubleSingleValidator.greaterEqual0(), BuilderDescriptorState.Experimental, "Blending factor avoidance", null);
        this.getBoolean(data, "StayInEnvironment", b -> this.stayInEnvironment = b, false, BuilderDescriptorState.Experimental, "Stay in spawning environment", null);
        this.getString(data, "AllowedEnvironments", s -> this.allowedEnvironments = s, null, StringNullOrNotEmptyValidator.get(), BuilderDescriptorState.Experimental, "Allowed environment to walk in", null);
        this.getStringArray(data, "FlockSpawnTypes", this.flockSpawnTypes, null, 0, Integer.MAX_VALUE, StringArrayNoEmptyStringsValidator.get(), BuilderDescriptorState.WorkInProgress, "Types of NPC this flock should consist off", null);
        this.getBoolean(data, "FlockSpawnTypesRandom", this.flockSpawnTypeRandom, false, BuilderDescriptorState.WorkInProgress, "Create a randomized flock if true else spawn in order of FlockSpawnTypes", null);
        this.getStringArray(data, "FlockAllowedNPC", this.flockAllowedRoles, null, 0, Integer.MAX_VALUE, StringArrayNoEmptyStringsValidator.get(), BuilderDescriptorState.Experimental, "List of NPCs allowed in flock", null);
        this.getBoolean(data, "FlockCanLead", this.canLeadFlock, false, BuilderDescriptorState.Experimental, "This NPC can be flock leader", null);
        this.getDouble(data, "FlockWeightAlignment", v -> this.flockWeightAlignment = v, 1.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Experimental, "Blending flock alignment", null);
        this.getDouble(data, "FlockWeightSeparation", v -> this.flockWeightSeparation = v, 1.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Experimental, "Blending flock separation", null);
        this.getDouble(data, "FlockWeightCohesion", v -> this.flockWeightCohesion = v, 1.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Experimental, "Blending flock cohesion", null);
        this.getDouble(data, "FlockInfluenceRange", v -> this.flockInfluenceRange = v, 10.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Experimental, "Influence radius flock forces", null);
        this.getBoolean(data, "DisableDamageFlock", b -> this.disableDamageFlock = b, true, BuilderDescriptorState.WorkInProgress, "If true disables combat damage from flock members", null);
        this.getAssetArray(data, "DisableDamageGroups", this.disableDamageGroups, null, 0, Integer.MAX_VALUE, TagSetExistsValidator.withConfig(AssetValidator.ListCanBeEmpty), BuilderDescriptorState.WorkInProgress, "Members in this list of group won't cause damage", null);
        this.getExistentStateSet(data, "BusyStates", s -> this.busyStates = s, this.stateHelper, BuilderDescriptorState.Stable, "States during which this NPC is busy and can't be interacted with", null);
        this.getCodecObject(data, "CombatConfig", this.combatConfig, BuilderDescriptorState.Stable, "The combat configuration providing optional combat action evaluator", null);
        this.getBoolean(data, "Invulnerable", this.invulnerable, false, BuilderDescriptorState.Stable, "Makes NPC ignore damage", null);
        this.getBoolean(data, "BreathesInAir", this.breathesInAir, true, BuilderDescriptorState.WorkInProgress, "Can breath in air", null);
        this.getBoolean(data, "BreathesInWater", this.breathesInWater, false, BuilderDescriptorState.WorkInProgress, "Can breath in fluid/water", null);
        this.getBoolean(data, "PickupDropOnDeath", t -> this.pickupDropOnDeath = t, false, BuilderDescriptorState.Stable, "Drop last picked item on death", null);
        this.getDouble(data, "DeathAnimationTime", d -> this.deathAnimationTime = d, 5.0, DoubleSingleValidator.greaterEqual0(), BuilderDescriptorState.Experimental, "How long to let the death animation play before removing", null);
        this.getAsset(data, "DeathInteraction", this.deathInteraction, null, RootInteractionValidator.withConfig(AssetValidator.CanBeEmpty), BuilderDescriptorState.Experimental, "Interaction to run on death", null);
        this.getFloat(data, "DespawnAnimationTime", d -> this.despawnAnimationTime = d, 0.8f, DoubleSingleValidator.greaterEqual0(), BuilderDescriptorState.Experimental, "How long to let the despawn animation play before removing", null);
        this.getString(data, "SpawnParticles", v -> this.spawnParticles = v, null, null, BuilderDescriptorState.Experimental, "Particle system when spawning", null);
        this.getVector3d(data, "SpawnParticlesOffset", v -> this.spawnParticleOffset = v, null, null, BuilderDescriptorState.Experimental, "Displacement from foot point to spawn relative to NPC heading", null);
        this.getDouble(data, "SpawnViewDistance", v -> this.spawnViewDistance = v, 75.0, DoubleSingleValidator.greater0(), BuilderDescriptorState.Experimental, "View distance for spawn particle", null);
        this.getEnum(data, "DefaultPlayerAttitude", this.defaultPlayerAttitude, Attitude.class, Attitude.HOSTILE, BuilderDescriptorState.Stable, "The default attitude of this NPC towards players", null);
        this.getEnum(data, "DefaultNPCAttitude", this.defaultNPCAttitude, Attitude.class, Attitude.NEUTRAL, BuilderDescriptorState.Stable, "The default attitude of this NPC towards other NPCs", null);
        this.getAsset(data, "AttitudeGroup", this.attitudeGroup, null, AttitudeGroupExistsValidator.withConfig(EnumSet.of(AssetValidator.Config.NULLABLE)), BuilderDescriptorState.Stable, "The attitude group towards other NPCs this NPC belongs to (often species related)", null);
        this.getAsset(data, "ItemAttitudeGroup", this.itemAttitudeGroup, null, ItemAttitudeGroupExistsValidator.withConfig(EnumSet.of(AssetValidator.Config.NULLABLE)), BuilderDescriptorState.Stable, "This NPC's item attitudes", null);
        this.getBoolean(data, "CorpseStaysInFlock", b -> this.corpseStaysInFlock = b, false, BuilderDescriptorState.Stable, "Whether the NPC should stay in the flock until corpse removal or be removed at the moment of death", null);
        this.getBoolean(data, "OverrideHeadPitchAngle", this.overrideHeadPitchAngle, true, BuilderDescriptorState.Experimental, "Whether to override the head pitch angle range", null);
        this.getDoubleRange(data, "HeadPitchAngleRange", this.headPitchAngleRange, BuilderRole.DEFAULT_HEAD_PITCH_RANGE, DoubleSequenceValidator.betweenWeaklyMonotonic(-90.0, 90.0), BuilderDescriptorState.Experimental, "Head rotation pitch range to be used instead of model camera settings", null);
        this.validateAny(this.breathesInAir, this.breathesInWater);
        this.registerStateSetter(this.startState, this.defaultSubState, (m, s) -> {
            this.startStateIndex = m;
            this.startSubStateIndex = s;
            return;
        });
        if (this.debugFlags != null && !this.debugFlags.isEmpty()) {
            this.parsedDebugFlags.addAll((Collection<?>)this.toDebugFlagSet("RoleDebugFlags", this.debugFlags));
        }
        this.requireObject(data, "MotionControllerList", this.motionControllers, BuilderDescriptorState.Stable, "Motion controllers", null, new BuilderValidationHelper(this.fileName, null, this.internalReferenceResolver, null, null, this.extraInfo, this.evaluators, this.readErrors));
        this.getArray(data, "Instructions", this.instructionList, null, BuilderDescriptorState.WorkInProgress, "List of instructions", null, new BuilderValidationHelper(this.fileName, null, this.internalReferenceResolver, this.stateHelper, new InstructionContextHelper(InstructionType.Default), this.extraInfo, this.evaluators, this.readErrors));
        this.getObject(data, "InteractionInstruction", this.interactionInstruction, BuilderDescriptorState.Stable, "Interaction instruction", "An instruction designed to evaluate and set which players can interact with an NPC, along with setting correct states upon interaction", new BuilderValidationHelper(this.fileName, null, this.internalReferenceResolver, this.stateHelper, new InstructionContextHelper(InstructionType.Interaction), this.extraInfo, this.evaluators, this.readErrors));
        this.getObject(data, "DeathInstruction", this.deathInstruction, BuilderDescriptorState.Stable, "Death instruction", "An instruction which will run only when the NPC is dead until they are removed", new BuilderValidationHelper(this.fileName, null, this.internalReferenceResolver, this.stateHelper, new InstructionContextHelper(InstructionType.Death), this.extraInfo, this.evaluators, this.readErrors));
        this.getObject(data, "StateTransitions", this.stateTransitionController, BuilderDescriptorState.Stable, "State transition actions", "A set of state transitions and the actions that will be executed during them", new BuilderValidationHelper(this.fileName, new FeatureEvaluatorHelper().lock(), this.internalReferenceResolver, this.stateHelper, new InstructionContextHelper(InstructionType.StateTransitions), this.extraInfo, this.evaluators, this.readErrors));
        this.getCodecObject(data, "StateEvaluator", this.stateEvaluator, BuilderDescriptorState.Stable, "A state evaluator", null);
        this.getString(data, "InitialMotionController", this.initialMotionController, null, StringNullOrNotEmptyValidator.get(), BuilderDescriptorState.Stable, "The initial motion controller to set", "The initial motion controller to set. If omitted and there are multiple, one will be chosen at random.");
        this.getCodecObject(data, "InteractionVars", this.interactionVars, BuilderDescriptorState.Stable, "Interaction vars to be used in interactions.", null);
        this.getBoolean(data, "IsMemory", this.isMemory, false, BuilderDescriptorState.Stable, "Used to define if the NPC has a Memory to record.", null);
        this.getString(data, "MemoriesCategory", this.memoriesCategory, "Other", StringNullOrNotEmptyValidator.get(), BuilderDescriptorState.Stable, "Category to put the NPC in, as part of the Memories Plugin", null);
        this.getString(data, "MemoriesNameOverride", this.memoriesNameOverride, "", null, BuilderDescriptorState.Stable, "Overrides the Memory name when set.", null);
        this.getFloat(data, "SpawnLockTime", this.spawnLockTime, 1.5, DoubleSingleValidator.greaterEqual0(), BuilderDescriptorState.Stable, "How long the NPC should be locked and unable to perform behavior when first spawned", null);
        final StateEvaluator stateEvaluator = this.stateEvaluator.build();
        if (stateEvaluator != null) {
            this.evaluators.add(stateEvaluator);
            stateEvaluator.prepareOptions(this.stateHelper);
            this.stateHelper.setHasStateEvaluator();
        }
        return this;
    }
    
    @Nonnull
    @Override
    public String getIdentifier() {
        final BuilderInfo builderInfo = NPCPlugin.get().getBuilderInfo(this);
        Objects.requireNonNull(builderInfo, "Have builder but can't get builderInfo for it");
        return builderInfo.getKeyName();
    }
    
    @Nonnull
    @Override
    public SpawnTestResult canSpawn(@Nonnull final SpawningContext context) {
        final Builder<Map<String, MotionController>> builder = this.motionControllers.getBuilder(this.builderManager, context.getExecutionContext(), this);
        if (builder instanceof final ISpawnable spawnable) {
            final SpawnTestResult result = spawnable.canSpawn(context);
            if (result != SpawnTestResult.TEST_OK) {
                return result;
            }
            if (!context.canBreathe(this.breathesInAir.get(context.getExecutionContext()), this.breathesInWater.get(context.getExecutionContext()))) {
                return SpawnTestResult.FAIL_NOT_BREATHABLE;
            }
        }
        return SpawnTestResult.TEST_OK;
    }
    
    @Nonnull
    @Override
    public Class<Role> category() {
        return Role.class;
    }
    
    @Override
    public String getSpawnModelName(final ExecutionContext context, final Scope modifierScope) {
        return this.appearance.get(context);
    }
    
    @Nullable
    @Override
    public Scope createModifierScope(final ExecutionContext executionContext) {
        return null;
    }
    
    @Nonnull
    @Override
    public Scope createExecutionScope() {
        return this.getBuilderParameters().createScope();
    }
    
    @Override
    public void markNeedsReload() {
        NPCPlugin.get().setRoleBuilderNeedsReload(this);
    }
    
    @Override
    public String getSpawnParticles() {
        return this.spawnParticles;
    }
    
    @Override
    public Vector3d getSpawnParticleOffset() {
        final double[] spawnParticleOffset = this.spawnParticleOffset;
        final Vector3d zero = Vector3d.ZERO;
        Objects.requireNonNull(zero);
        return BuilderBase.createVector3d(spawnParticleOffset, zero::clone);
    }
    
    @Override
    public double getSpawnViewDistance() {
        return this.spawnViewDistance;
    }
    
    @Override
    public final boolean isEnabled(final ExecutionContext context) {
        return true;
    }
    
    public int getMaxHealth(@Nonnull final BuilderSupport builderSupport) {
        return this.maxHealth.get(builderSupport.getExecutionContext());
    }
    
    @Nullable
    public String[] getDisplayNames() {
        return this.displayNames;
    }
    
    public String getNameTranslationKey(final BuilderSupport support) {
        return this.nameTranslationKey.get(support.getExecutionContext());
    }
    
    public String getAppearance(@Nonnull final BuilderSupport builderSupport) {
        return this.appearance.get(builderSupport.getExecutionContext());
    }
    
    public boolean isBreathesInAir(final BuilderSupport support) {
        return this.breathesInAir.get(support.getExecutionContext());
    }
    
    public boolean isBreathesInWater(final BuilderSupport support) {
        return this.breathesInWater.get(support.getExecutionContext());
    }
    
    public int getOpaqueBlockSet() {
        final int index = BlockSet.getAssetMap().getIndex(this.opaqueBlockSet);
        if (index == Integer.MIN_VALUE) {
            throw new IllegalArgumentException("Unknown key! " + this.opaqueBlockSet);
        }
        return index;
    }
    
    public double getInertia() {
        return this.inertia;
    }
    
    public double getKnockbackScale(@Nonnull final BuilderSupport support) {
        return this.knockbackScale.get(support.getExecutionContext());
    }
    
    @Nullable
    public String[] getHotbarItems(@Nonnull final BuilderSupport support) {
        return this.hotbarItems.get(support.getExecutionContext());
    }
    
    @Nullable
    public String[] getOffHandItems(@Nonnull final BuilderSupport support) {
        return this.offHandItems.get(support.getExecutionContext());
    }
    
    public String getInventoryItemsDropList(@Nonnull final BuilderSupport support) {
        return this.inventoryItemsDropList.get(support.getExecutionContext());
    }
    
    public String[] getArmor() {
        return this.armor;
    }
    
    public boolean isPickupDropOnDeath() {
        return this.pickupDropOnDeath;
    }
    
    public String getDropListId(@Nonnull final BuilderSupport builderSupport) {
        return this.dropListId.get(builderSupport.getExecutionContext());
    }
    
    public String getStartState() {
        return this.startState;
    }
    
    public int getStartStateIndex() {
        return this.startStateIndex;
    }
    
    public int getStartSubStateIndex() {
        return this.startSubStateIndex;
    }
    
    public double getCollisionDistance() {
        return this.collisionDistance;
    }
    
    public double getCollisionForceFalloff() {
        return this.collisionForceFalloff;
    }
    
    public boolean isAvoidingEntities() {
        return this.applyAvoidance;
    }
    
    public Role.AvoidanceMode getAvoidanceMode() {
        return this.avoidanceMode;
    }
    
    public double getCollisionRadius() {
        return this.collisionRadius;
    }
    
    public double getSeparationDistance() {
        return this.separationDistance;
    }
    
    public double getSeparationWeight() {
        return this.separationWeight;
    }
    
    public double getSeparationDistanceTarget() {
        return this.separationDistanceTarget;
    }
    
    public double getSeparationNearRadiusTarget() {
        return this.separationNearRadiusTarget;
    }
    
    public double getSeparationFarRadiusTarget() {
        return this.separationFarRadiusTarget;
    }
    
    public boolean isApplySeparation(final BuilderSupport support) {
        return this.applySeparation.get(support.getExecutionContext());
    }
    
    public boolean isStayingInEnvironment() {
        return this.stayInEnvironment;
    }
    
    public String getAllowedEnvironments() {
        return this.allowedEnvironments;
    }
    
    public double getEntityAvoidanceStrength() {
        return this.entityAvoidanceStrength;
    }
    
    public boolean isOverridingHeadPitchAngle(@Nonnull final BuilderSupport support) {
        return this.overrideHeadPitchAngle.get(support.getExecutionContext());
    }
    
    public float[] getHeadPitchAngleRange(@Nonnull final BuilderSupport support) {
        final double[] range = this.headPitchAngleRange.get(support.getExecutionContext());
        final float[] floatRange = { (float)(range[0] * 0.01745329238474369), (float)(range[1] * 0.01745329238474369) };
        return floatRange;
    }
    
    @Nullable
    public String[] getFlockSpawnTypes(@Nonnull final BuilderSupport support) {
        return this.flockSpawnTypes.get(support.getExecutionContext());
    }
    
    public boolean isFlockSpawnTypeRandom(@Nonnull final BuilderSupport support) {
        return this.flockSpawnTypeRandom.get(support.getExecutionContext());
    }
    
    @Nonnull
    public String[] getFlockAllowedRoles(@Nonnull final BuilderSupport support) {
        return this.nonNull(this.flockAllowedRoles.get(support.getExecutionContext()));
    }
    
    public boolean isCanLeadFlock(@Nonnull final BuilderSupport support) {
        return this.canLeadFlock.get(support.getExecutionContext());
    }
    
    public double getFlockWeightAlignment() {
        return this.flockWeightAlignment;
    }
    
    public double getFlockWeightSeparation() {
        return this.flockWeightSeparation;
    }
    
    public double getFlockWeightCohesion() {
        return this.flockWeightCohesion;
    }
    
    public double getFlockInfluenceRange() {
        return this.flockInfluenceRange;
    }
    
    @Nonnull
    public EnumSet<RoleDebugFlags> getDebugFlags() {
        return this.parsedDebugFlags;
    }
    
    public float getCollisionViewAngle() {
        return 0.017453292f * this.collisionViewAngle;
    }
    
    @Nullable
    public String getBalanceAsset(@Nonnull final BuilderSupport support) {
        return this.combatConfig.build(support.getExecutionContext());
    }
    
    public double getDeathAnimationTime() {
        return this.deathAnimationTime;
    }
    
    @Nullable
    public String getDeathInteraction(@Nonnull final BuilderSupport builderSupport) {
        final String val = this.deathInteraction.get(builderSupport.getExecutionContext());
        return (val == null || val.isEmpty()) ? null : val;
    }
    
    public float getDespawnAnimationTime() {
        return this.despawnAnimationTime;
    }
    
    public boolean isDisableDamageFlock() {
        return this.disableDamageFlock;
    }
    
    public int[] getDisableDamageGroups(@Nonnull final BuilderSupport support) {
        return WorldSupport.createTagSetIndexArray(this.disableDamageGroups.get(support.getExecutionContext()));
    }
    
    public boolean isInvulnerable(final BuilderSupport support) {
        return this.invulnerable.get(support.getExecutionContext());
    }
    
    public int getInventorySlots() {
        return this.inventorySlots;
    }
    
    public int getHotbarSlots() {
        return this.hotbarSlots;
    }
    
    public int getOffHandSlots() {
        return this.offHandSlots;
    }
    
    public byte getDefaultOffHandSlot(@Nonnull final BuilderSupport support) {
        return (byte)this.defaultOffHandSlot.get(support.getExecutionContext());
    }
    
    public Int2ObjectMap<IntSet> getBusyStates() {
        return this.busyStates;
    }
    
    public Attitude getDefaultPlayerAttitude(@Nonnull final BuilderSupport support) {
        return this.defaultPlayerAttitude.get(support.getExecutionContext());
    }
    
    public Attitude getDefaultNPCAttitude(@Nonnull final BuilderSupport support) {
        return this.defaultNPCAttitude.get(support.getExecutionContext());
    }
    
    public int getAttitudeGroup(@Nonnull final BuilderSupport support) {
        final String groupName = this.attitudeGroup.get(support.getExecutionContext());
        return AttitudeGroup.getAssetMap().getIndex(groupName);
    }
    
    public int getItemAttitudeGroup(@Nonnull final BuilderSupport support) {
        final String groupName = this.itemAttitudeGroup.get(support.getExecutionContext());
        return ItemAttitudeGroup.getAssetMap().getIndex(groupName);
    }
    
    public boolean isCorpseStaysInFlock() {
        return this.corpseStaysInFlock;
    }
    
    @Nullable
    public Map<String, MotionController> getMotionControllerMap(@Nonnull final BuilderSupport support) {
        return this.motionControllers.build(support);
    }
    
    public String getInitialMotionController(@Nonnull final BuilderSupport support) {
        return this.initialMotionController.get(support.getExecutionContext());
    }
    
    @Nullable
    public List<Instruction> getInstructionList(@Nonnull final BuilderSupport support) {
        support.setCurrentInstructionContext(InstructionType.Default);
        return this.instructionList.build(support);
    }
    
    @Nullable
    public Instruction getInteractionInstruction(@Nonnull final BuilderSupport support) {
        support.setCurrentInstructionContext(InstructionType.Interaction);
        return this.interactionInstruction.build(support);
    }
    
    @Nullable
    public Instruction getDeathInstruction(@Nonnull final BuilderSupport support) {
        support.setCurrentInstructionContext(InstructionType.Death);
        return this.deathInstruction.build(support);
    }
    
    @Nullable
    public StateTransitionController getStateTransitionController(@Nonnull final BuilderSupport support) {
        support.setCurrentInstructionContext(InstructionType.Default);
        return this.stateTransitionController.build(support);
    }
    
    public void registerStateEvaluator(@Nonnull final BuilderSupport support) {
        support.setStateEvaluator(this.stateEvaluator.build());
    }
    
    @Nullable
    public Map<String, String> getInteractionVars(@Nonnull final BuilderSupport support) {
        return this.interactionVars.build(support.getExecutionContext());
    }
    
    public boolean isMemory(final ExecutionContext context) {
        return this.isMemory.get(context);
    }
    
    @Override
    public boolean isMemory(final ExecutionContext context, final Scope modifierScope) {
        return this.isMemory.get(context);
    }
    
    @Override
    public String getMemoriesCategory(final ExecutionContext context, final Scope modifierScope) {
        return this.memoriesCategory.get(context);
    }
    
    public String getMemoriesNameOverride(final ExecutionContext context) {
        return this.memoriesNameOverride.get(context);
    }
    
    @Override
    public String getMemoriesNameOverride(final ExecutionContext context, @Nullable final Scope modifierScope) {
        return this.memoriesNameOverride.get(context);
    }
    
    @Nonnull
    @Override
    public String getNameTranslationKey(final ExecutionContext context, @Nullable final Scope modifierScope) {
        return this.nameTranslationKey.get(context);
    }
    
    public float getSpawnLockTime(final BuilderSupport support) {
        return this.spawnLockTime.get(support.getExecutionContext());
    }
    
    @Override
    protected void runLoadTimeValidationHelper0(final String configName, final NPCLoadTimeValidationHelper loadTimeValidationHelper, final ExecutionContext context, @Nonnull final List<String> errors) {
        final NPCPlugin npcModule = NPCPlugin.get();
        final String[] spawnTypes = this.flockSpawnTypes.get(context);
        boolean success = true;
        if (spawnTypes != null) {
            for (final String type : spawnTypes) {
                if (!npcModule.hasRoleName(type)) {
                    success = false;
                    errors.add(String.format("%s: No such role '%s' for %s", configName, type, this.flockSpawnTypes.getName()));
                }
            }
        }
        final String[] allowedTypes = this.flockAllowedRoles.get(context);
        if (allowedTypes != null) {
            for (final String type2 : allowedTypes) {
                if (!npcModule.hasRoleName(type2)) {
                    success = false;
                    errors.add(String.format("%s: No such role '%s' for %s", configName, type2, this.flockAllowedRoles.getName()));
                }
            }
        }
        if (!success) {
            throw new IllegalStateException(String.format("Invalid roles referenced for %s or %s", this.flockSpawnTypes.getName(), this.flockAllowedRoles.getName()));
        }
    }
    
    static {
        DEFAULT_HEAD_PITCH_RANGE = new double[] { -89.0, 89.0 };
    }
}
