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