package com.hypixel.hytale.server.npc.systems;
import com.hypixel.hytale.common.collection.BucketItemPool;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.component.Archetype;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.CommandBuffer;
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.OrderPriority;
import com.hypixel.hytale.component.dependency.SystemDependency;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.component.spatial.SpatialResource;
import com.hypixel.hytale.component.system.HolderSystem;
import com.hypixel.hytale.component.system.RefChangeSystem;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.protocol.BlockMaterial;
import com.hypixel.hytale.server.core.entity.LivingEntity;
import com.hypixel.hytale.server.core.modules.entity.EntityModule;
import com.hypixel.hytale.server.core.modules.entity.component.ModelComponent;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.entity.system.PlayerSpatialSystem;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.flock.FlockMembership;
import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.server.npc.decisionmaker.stateevaluator.StateEvaluator;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.server.npc.instructions.Instruction;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.server.npc.role.support.EntityList;
import com.hypixel.hytale.server.npc.role.support.PositionCache;
import com.hypixel.hytale.server.npc.statetransition.StateTransitionController;
import com.hypixel.hytale.server.spawning.SpawningPlugin;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class PositionCacheSystems {
public PositionCacheSystems() {
}
public static void initialisePositionCache(@Nonnull Role role, @Nullable StateEvaluator stateEvaluator, double flockInfluenceRange) {
PositionCache positionCache = role.getPositionCache();
positionCache.reset(true);
if (role.isAvoidingEntities()) {
double collisionProbeDistance = role.getCollisionProbeDistance();
positionCache.requireEntityDistanceAvoidance(collisionProbeDistance);
positionCache.requirePlayerDistanceAvoidance(collisionProbeDistance);
}
if (role.isApplySeparation()) {
double separationDistance = role.getSeparationDistance();
positionCache.requireEntityDistanceAvoidance(separationDistance);
positionCache.requirePlayerDistanceAvoidance(separationDistance);
}
if (flockInfluenceRange > 0.0) {
positionCache.requireEntityDistanceAvoidance(flockInfluenceRange);
positionCache.requirePlayerDistanceAvoidance(flockInfluenceRange);
}
Instruction instruction = role.getRootInstruction();
instruction.registerWithSupport(role);
Instruction interactionInstruction = role.getInteractionInstruction();
if (interactionInstruction != null) {
interactionInstruction.registerWithSupport(role);
positionCache.requirePlayerDistanceUnsorted(10.0);
}
Instruction deathInstruction = role.getDeathInstruction();
if (deathInstruction != null) {
deathInstruction.registerWithSupport(role);
}
StateTransitionController stateTransitions = role.getStateSupport().getStateTransitionController();
if (stateTransitions != null) {
stateTransitions.registerWithSupport(role);
}
if (stateEvaluator != null) {
stateEvaluator.setupNPC(role);
}
for(Consumer<Role> registration : positionCache.getExternalRegistrations()) {
registration.accept(role);
}
positionCache.finalizeConfiguration();
}
public static class OnFlockJoinSystem extends RefChangeSystem<EntityStore, FlockMembership> {
@Nonnull
private final ComponentType<EntityStore, FlockMembership> flockMembershipComponentType;
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcComponentType;
@Nonnull
private final ComponentType<EntityStore, StateEvaluator> stateEvaluatorComponentType;
@Nonnull
private final Query<EntityStore> query;
public OnFlockJoinSystem(@Nonnull ComponentType<EntityStore, NPCEntity> npcComponentType, @Nonnull ComponentType<EntityStore, FlockMembership> flockMembershipComponentType) {
this.flockMembershipComponentType = flockMembershipComponentType;
this.npcComponentType = npcComponentType;
this.stateEvaluatorComponentType = StateEvaluator.getComponentType();
this.query = Archetype.of(npcComponentType, flockMembershipComponentType);
}
@Nonnull
public Query<EntityStore> getQuery() {
return this.query;
}
@Nonnull
public ComponentType<EntityStore, FlockMembership> componentType() {
return this.flockMembershipComponentType;
}
public void onComponentAdded(@Nonnull Ref<EntityStore> ref, @Nonnull FlockMembership component, @Nonnull Store<EntityStore> store, @Nonnull CommandBuffer<EntityStore> commandBuffer) {
NPCEntity npcComponent = (NPCEntity)store.getComponent(ref, this.npcComponentType);
assert npcComponent != null;
Role role = npcComponent.getRole();
PositionCacheSystems.initialisePositionCache(role, (StateEvaluator)store.getComponent(ref, this.stateEvaluatorComponentType), role.getFlockInfluenceRange());
}
public void onComponentSet(@Nonnull Ref<EntityStore> ref, FlockMembership oldComponent, @Nonnull FlockMembership newComponent, @Nonnull Store<EntityStore> store, @Nonnull CommandBuffer<EntityStore> commandBuffer) {
NPCEntity npcComponent = (NPCEntity)store.getComponent(ref, this.npcComponentType);
assert npcComponent != null;
Role role = npcComponent.getRole();
PositionCacheSystems.initialisePositionCache(role, (StateEvaluator)store.getComponent(ref, this.stateEvaluatorComponentType), role.getFlockInfluenceRange());
}
public void onComponentRemoved(@Nonnull Ref<EntityStore> ref, @Nonnull FlockMembership component, @Nonnull Store<EntityStore> store, @Nonnull CommandBuffer<EntityStore> commandBuffer) {
}
}
public static class RoleActivateSystem extends HolderSystem<EntityStore> {
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcComponentType;
@Nonnull
private final ComponentType<EntityStore, StateEvaluator> stateEvaluatorComponentType;
public RoleActivateSystem(@Nonnull ComponentType<EntityStore, NPCEntity> npcComponentType, @Nonnull ComponentType<EntityStore, StateEvaluator> stateEvaluatorComponentType) {
this.npcComponentType = npcComponentType;
this.stateEvaluatorComponentType = stateEvaluatorComponentType;
}
public void onEntityAdd(@Nonnull Holder<EntityStore> holder, @Nonnull AddReason reason, @Nonnull Store<EntityStore> store) {
NPCEntity npcComponent = (NPCEntity)holder.getComponent(this.npcComponentType);
assert npcComponent != null;
Role role = npcComponent.getRole();
double influenceRadius;
if (holder.getComponent(FlockMembership.getComponentType()) != null) {
influenceRadius = role.getFlockInfluenceRange();
} else {
influenceRadius = 0.0;
}
StateEvaluator stateEvaluator = (StateEvaluator)holder.getComponent(this.stateEvaluatorComponentType);
if (stateEvaluator != null) {
stateEvaluator.setupNPC(holder);
}
PositionCacheSystems.initialisePositionCache(role, stateEvaluator, influenceRadius);
}
public void onEntityRemoved(@Nonnull Holder<EntityStore> holder, @Nonnull RemoveReason reason, @Nonnull Store<EntityStore> store) {
NPCEntity npcComponent = (NPCEntity)holder.getComponent(this.npcComponentType);
assert npcComponent != null;
npcComponent.getRole().getPositionCache().reset(false);
}
@Nonnull
public Query<EntityStore> getQuery() {
return this.npcComponentType;
}
}
public static class UpdateSystem extends SteppableTickingSystem {
@Nonnull
private static final ThreadLocal<BucketItemPool<Ref<EntityStore>>> BUCKET_POOL_THREAD_LOCAL = ThreadLocal.withInitial(BucketItemPool::new);
@Nonnull
private final ComponentType<EntityStore, NPCEntity> npcComponentType;
@Nonnull
private final ComponentType<EntityStore, ModelComponent> modelComponentType;
@Nonnull
private final ComponentType<EntityStore, TransformComponent> transformComponentType;
@Nonnull
private final ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> playerSpatialResource;
@Nonnull
private final ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> npcSpatialResource;
@Nonnull
private final ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> itemSpatialResource;
@Nonnull
private final Query<EntityStore> query;
@Nonnull
private final Set<Dependency<EntityStore>> dependencies;
public UpdateSystem(@Nonnull ComponentType<EntityStore, NPCEntity> npcComponentType, @Nonnull ResourceType<EntityStore, SpatialResource<Ref<EntityStore>, EntityStore>> npcSpatialResource) {
this.dependencies = Set.of(new SystemDependency(Order.AFTER, PlayerSpatialSystem.class, OrderPriority.CLOSEST), new SystemDependency(Order.BEFORE, RoleSystems.PreBehaviourSupportTickSystem.class));
this.npcComponentType = npcComponentType;
this.modelComponentType = ModelComponent.getComponentType();
this.transformComponentType = TransformComponent.getComponentType();
this.playerSpatialResource = EntityModule.get().getPlayerSpatialResourceType();
this.npcSpatialResource = npcSpatialResource;
this.itemSpatialResource = EntityModule.get().getItemSpatialResourceType();
this.query = Query.<EntityStore>and(npcComponentType, this.transformComponentType, this.modelComponentType);
}
@Nonnull
public Set<Dependency<EntityStore>> getDependencies() {
return this.dependencies;
}
public boolean isParallel(int archetypeChunkSize, int taskCount) {
return false;
}
@Nonnull
public Query<EntityStore> getQuery() {
return this.query;
}
public void steppedTick(float dt, int index, @Nonnull ArchetypeChunk<EntityStore> archetypeChunk, @Nonnull Store<EntityStore> store, @Nonnull CommandBuffer<EntityStore> commandBuffer) {
Ref<EntityStore> ref = archetypeChunk.getReferenceTo(index);
NPCEntity npcComponent = (NPCEntity)archetypeChunk.getComponent(index, this.npcComponentType);
assert npcComponent != null;
Role role = npcComponent.getRole();
PositionCache positionCache = role.getPositionCache();
positionCache.setBenchmarking(NPCPlugin.get().isBenchmarkingSensorSupport());
long packed = LivingEntity.getPackedMaterialAndFluidAtBreathingHeight(ref, commandBuffer);
BlockMaterial material = BlockMaterial.VALUES[MathUtil.unpackLeft(packed)];
int fluidId = MathUtil.unpackRight(packed);
positionCache.setCouldBreathe(role.canBreathe(material, fluidId));
if (positionCache.tickPositionCacheNextUpdate(dt)) {
positionCache.resetPositionCacheNextUpdate();
TransformComponent transformComponent = (TransformComponent)archetypeChunk.getComponent(index, this.transformComponentType);
assert transformComponent != null;
Vector3d position = transformComponent.getPosition();
EntityList players = positionCache.getPlayers();
if (players.getSearchRadius() > 0) {
SpatialResource<Ref<EntityStore>, EntityStore> spatialResource = (SpatialResource)store.getResource(this.playerSpatialResource);
players.setBucketItemPool((BucketItemPool)BUCKET_POOL_THREAD_LOCAL.get());
if (positionCache.isBenchmarking()) {
long startTime = System.nanoTime();
addEntities(ref, position, players, spatialResource, commandBuffer);
long getTime = System.nanoTime();
NPCPlugin.get().collectSensorSupportPlayerList(role.getRoleIndex(), getTime - startTime, (double)players.getMaxDistanceSorted(), (double)players.getMaxDistanceUnsorted(), (double)players.getMaxDistanceAvoidance(), 0);
} else {
addEntities(ref, position, players, spatialResource, commandBuffer);
}
}
EntityList npcEntities = positionCache.getNpcs();
if (npcEntities.getSearchRadius() > 0) {
SpatialResource<Ref<EntityStore>, EntityStore> spatialResource = (SpatialResource)store.getResource(this.npcSpatialResource);
npcEntities.setBucketItemPool((BucketItemPool)BUCKET_POOL_THREAD_LOCAL.get());
if (positionCache.isBenchmarking()) {
long startTime = System.nanoTime();
addEntities(ref, position, npcEntities, spatialResource, commandBuffer);
long getTime = System.nanoTime();
NPCPlugin.get().collectSensorSupportEntityList(role.getRoleIndex(), getTime - startTime, (double)npcEntities.getMaxDistanceSorted(), (double)npcEntities.getMaxDistanceUnsorted(), (double)npcEntities.getMaxDistanceAvoidance(), 0);
} else {
addEntities(ref, position, npcEntities, spatialResource, commandBuffer);
}
}
double maxDroppedItemDistance = positionCache.getMaxDroppedItemDistance();
if (maxDroppedItemDistance > 0.0) {
SpatialResource<Ref<EntityStore>, EntityStore> spatialResource = (SpatialResource)store.getResource(this.itemSpatialResource);
List<Ref<EntityStore>> list = positionCache.getDroppedItemList();
list.clear();
spatialResource.getSpatialStructure().ordered(position, (double)((int)maxDroppedItemDistance + 1), list);
}
double maxSpawnMarkerDistance = positionCache.getMaxSpawnMarkerDistance();
if (maxSpawnMarkerDistance > 0.0) {
SpatialResource<Ref<EntityStore>, EntityStore> spatialResource = (SpatialResource)store.getResource(SpawningPlugin.get().getSpawnMarkerSpatialResource());
List<Ref<EntityStore>> list = positionCache.getSpawnMarkerList();
list.clear();
spatialResource.getSpatialStructure().collect(position, (double)((int)maxSpawnMarkerDistance + 1), list);
}
int maxSpawnBeaconDistance = positionCache.getMaxSpawnBeaconDistance();
if (maxSpawnBeaconDistance > 0) {
SpatialResource<Ref<EntityStore>, EntityStore> spatialResource = (SpatialResource)store.getResource(SpawningPlugin.get().getManualSpawnBeaconSpatialResource());
List<Ref<EntityStore>> list = positionCache.getSpawnBeaconList();
list.clear();
spatialResource.getSpatialStructure().ordered(position, (double)(maxSpawnBeaconDistance + 1), list);
}
}
}
private static void addEntities(@Nonnull Ref<EntityStore> self, @Nonnull Vector3d position, @Nonnull EntityList entityList, @Nonnull SpatialResource<Ref<EntityStore>, EntityStore> spatialResource, @Nonnull CommandBuffer<EntityStore> commandBuffer) {
List<Ref<EntityStore>> results = SpatialResource.getThreadLocalReferenceList();
spatialResource.getSpatialStructure().collect(position, (double)entityList.getSearchRadius(), results);
for(Ref<EntityStore> result : results) {
if (result.isValid() && !result.equals(self)) {
entityList.add(result, position, commandBuffer);
}
}
}
}
}