package com.hypixel.hytale.server.npc.role.support; import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap; import com.hypixel.hytale.component.CommandBuffer; import com.hypixel.hytale.component.ComponentAccessor; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.function.consumer.DoubleQuadObjectConsumer; import com.hypixel.hytale.function.consumer.QuadConsumer; import com.hypixel.hytale.function.consumer.TriConsumer; import com.hypixel.hytale.function.predicate.QuadPredicate; import com.hypixel.hytale.math.iterator.BlockIterator; import com.hypixel.hytale.math.random.RandomExtra; import com.hypixel.hytale.math.shape.Box; import com.hypixel.hytale.math.util.ChunkUtil; import com.hypixel.hytale.math.util.MathUtil; import com.hypixel.hytale.math.vector.Vector2d; import com.hypixel.hytale.math.vector.Vector3d; import com.hypixel.hytale.protocol.GameMode; import com.hypixel.hytale.protocol.Opacity; import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType; import com.hypixel.hytale.server.core.entity.entities.Player; import com.hypixel.hytale.server.core.modules.blockset.BlockSetModule; import com.hypixel.hytale.server.core.modules.collision.CollisionMath; import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox; 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.item.ItemComponent; import com.hypixel.hytale.server.core.modules.entity.player.PlayerSettings; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk; import com.hypixel.hytale.server.core.universe.world.storage.EntityStore; import com.hypixel.hytale.server.npc.NPCPlugin; import com.hypixel.hytale.server.npc.entities.NPCEntity; import com.hypixel.hytale.server.npc.role.Role; import com.hypixel.hytale.server.npc.util.NPCPhysicsMath; import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.IntSet; import it.unimi.dsi.fastutil.objects.Object2ByteMap; import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectArrayList; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Consumer; import java.util.function.Predicate; import javax.annotation.Nonnull; import javax.annotation.Nullable; public class PositionCache { public static final BiPredicate, ComponentAccessor> IS_VALID_PLAYER = (ref, componentAccessor) -> { Player playerComponent = componentAccessor.getComponent(ref, Player.getComponentType()); if (playerComponent != null && !playerComponent.isWaitingForClientReady()) { if (playerComponent.getGameMode() == GameMode.Adventure) { return true; } else if (playerComponent.getGameMode() != GameMode.Creative) { return false; } else { PlayerSettings playerSettingsComponent = componentAccessor.getComponent(ref, PlayerSettings.getComponentType()); return playerSettingsComponent != null && playerSettingsComponent.creativeSettings().allowNPCDetection(); } } else { return false; } }; public static final BiPredicate, ComponentAccessor> IS_VALID_NPC = (ref, accessor) -> accessor.getArchetype(ref) .contains(NPCEntity.getComponentType()); public static final double MIN_LOS_BLOCKING_DISTANCE_SQUARED = 1.0E-6; public static final String FUNCTION_CAN_BE_ONLY_CALLED_WHILE_CONFIGURING_POSITION_CACHE = "function can be only called while configuring PositionCache"; private static final float LOS_CACHE_TTL_MIN_SECONDS = 0.09F; private static final float LOS_CACHE_TTL_MAX_SECONDS = 0.11F; private static final float POSITION_CACHE_TTL_SECONDS = 0.2F; private static final ComponentType TRANSFORM_COMPONENT_TYPE = TransformComponent.getComponentType(); private static final ComponentType ITEM_COMPONENT_TYPE = ItemComponent.getComponentType(); private static final ComponentType MODEL_COMPONENT_TYPE = ModelComponent.getComponentType(); protected static final ComponentType BOUNDING_BOX_COMPONENT_TYPE = BoundingBox.getComponentType(); private double maxDroppedItemDistance; private double maxSpawnMarkerDistance; private int maxSpawnBeaconDistance; @Nonnull private final Role role; private int opaqueBlockSet; protected EntityList players; protected EntityList npcs; protected final List> externalRegistrations = new ObjectArrayList(); private final List> droppedItems = new ObjectArrayList(); private final List> spawnMarkers = new ObjectArrayList(); private final List> spawnBeacons = new ObjectArrayList(); private final Object2ByteMap> lineOfSightCache = new Object2ByteOpenHashMap(); private final Object2ByteMap> inverseLineOfSightCache = new Object2ByteOpenHashMap(); private final Object2ByteMap> friendlyFireCache = new Object2ByteOpenHashMap(); protected final PositionCache.LineOfSightBuffer lineOfSightComputeBuffer = new PositionCache.LineOfSightBuffer(); protected final PositionCache.LineOfSightEntityBuffer lineOfSightEntityComputeBuffer = new PositionCache.LineOfSightEntityBuffer(); private float cacheTTL = 0.09F; private float positionCacheNextUpdate; private boolean isBenchmarking; private boolean isConfiguring; private boolean couldBreathe = true; public PositionCache(@Nonnull Role role) { this.role = role; this.players = new EntityList(null, IS_VALID_PLAYER); this.npcs = new EntityList(null, IS_VALID_NPC); } public boolean isBenchmarking() { return this.isBenchmarking; } public void setBenchmarking(boolean benchmarking) { this.isBenchmarking = benchmarking; } public void setCouldBreathe(boolean couldBreathe) { this.couldBreathe = couldBreathe; } public EntityList getPlayers() { return this.players; } public EntityList getNpcs() { return this.npcs; } public boolean tickPositionCacheNextUpdate(float dt) { return (this.positionCacheNextUpdate -= dt) <= 0.0F; } public void resetPositionCacheNextUpdate() { this.positionCacheNextUpdate = 0.2F; } public double getMaxDroppedItemDistance() { return this.maxDroppedItemDistance; } public double getMaxSpawnMarkerDistance() { return this.maxSpawnMarkerDistance; } public int getMaxSpawnBeaconDistance() { return this.maxSpawnBeaconDistance; } public void addExternalPositionCacheRegistration(Consumer registration) { this.externalRegistrations.add(registration); } @Nonnull public List> getExternalRegistrations() { return this.externalRegistrations; } public void reset(boolean isConfiguring) { this.players.reset(); this.npcs.reset(); this.maxDroppedItemDistance = 0.0; this.droppedItems.clear(); this.spawnMarkers.clear(); this.spawnBeacons.clear(); this.positionCacheNextUpdate = RandomExtra.randomRange(0.0F, 0.2F); this.clearLineOfSightCache(); this.isConfiguring = isConfiguring; } public void finalizeConfiguration() { this.isConfiguring = false; this.npcs.finalizeConfiguration(); this.players.finalizeConfiguration(); RoleStats roleStats = this.role.getRoleStats(); if (roleStats != null) { roleStats.trackBuckets(false, this.npcs.getBucketRanges()); roleStats.trackBuckets(true, this.players.getBucketRanges()); } } public void clear(double tickTime) { this.clearLineOfSightCache(tickTime); if (this.isBenchmarking) { NPCPlugin.get().collectSensorSupportTickDone(this.role.getRoleIndex()); } this.isBenchmarking = false; } public boolean couldBreatheCached() { return this.couldBreathe; } public void forEachPlayer( @Nonnull DoubleQuadObjectConsumer, T, U, V> consumer, T t, U u, V v, double d, ComponentAccessor componentAccessor ) { this.players.forEachEntity(consumer, t, u, v, d, componentAccessor); } @Nullable public Ref getClosestPlayerInRange(double minRange, double maxRange, @Nonnull ComponentAccessor componentAccessor) { return this.getClosestPlayerInRange(minRange, maxRange, p -> true, componentAccessor); } @Nullable public Ref getClosestPlayerInRange( double minRange, double maxRange, @Nonnull Predicate> filter, @Nonnull ComponentAccessor componentAccessor ) { return this.players.getClosestEntityInRange(minRange, maxRange, filter, componentAccessor); } @Nullable public Ref getClosestNPCInRange( double minRange, double maxRange, @Nonnull Predicate> filter, @Nonnull ComponentAccessor componentAccessor ) { return this.npcs.getClosestEntityInRange(minRange, maxRange, filter, componentAccessor); } public void processNPCsInRange( @Nonnull Ref ref, double minRange, double maxRange, boolean useProjectedDistance, Ref ignoredEntityReference, @Nonnull Role role, @Nonnull QuadPredicate, Role, T> filter, S s, T t, @Nonnull ComponentAccessor componentAccessor ) { this.processEntitiesInRange(ref, this.npcs, minRange, maxRange, useProjectedDistance, ignoredEntityReference, role, filter, s, t, componentAccessor); } public void processPlayersInRange( @Nonnull Ref ref, double minRange, double maxRange, boolean useProjectedDistance, Ref ignoredEntityReference, @Nonnull Role role, @Nonnull QuadPredicate, Role, T> filter, S s, T t, @Nonnull ComponentAccessor componentAccessor ) { this.processEntitiesInRange(ref, this.players, minRange, maxRange, useProjectedDistance, ignoredEntityReference, role, filter, s, t, componentAccessor); } public void processEntitiesInRange( @Nonnull Ref ref, @Nonnull EntityList entities, double minRange, double maxRange, boolean useProjectedDistance, Ref ignoredEntityReference, @Nonnull Role role, @Nonnull QuadPredicate, Role, T> filter, S s, T t, @Nonnull ComponentAccessor componentAccessor ) { if (useProjectedDistance) { entities.getClosestEntityInRangeProjected( ref, ignoredEntityReference, role.getActiveMotionController(), minRange, maxRange, filter, role, s, t, componentAccessor ); } else { entities.getClosestEntityInRange(ignoredEntityReference, minRange, maxRange, filter, role, s, t, componentAccessor); } } @Nullable public Ref getClosestDroppedItemInRange( @Nonnull Ref ref, double minRange, double maxRange, @Nonnull QuadPredicate, Role, ComponentAccessor> filter, Role role, S s, @Nonnull ComponentAccessor componentAccessor ) { int droppedItemsSize = this.droppedItems.size(); if (droppedItemsSize == 0) { return null; } else { TransformComponent transformComponent = componentAccessor.getComponent(ref, TRANSFORM_COMPONENT_TYPE); assert transformComponent != null; Vector3d position = transformComponent.getPosition(); minRange *= minRange; maxRange *= maxRange; for (int index = 0; index < droppedItemsSize; index++) { Ref itemEntityRef = this.droppedItems.get(index); if (itemEntityRef.isValid()) { ItemComponent itemComponent = componentAccessor.getComponent(itemEntityRef, ITEM_COMPONENT_TYPE); if (itemComponent != null) { TransformComponent itemEntityTransformComponent = componentAccessor.getComponent(itemEntityRef, TRANSFORM_COMPONENT_TYPE); assert itemEntityTransformComponent != null; double squaredDistance = itemEntityTransformComponent.getPosition().distanceSquaredTo(position); if (!(squaredDistance < minRange)) { if (squaredDistance >= maxRange) { break; } if (filter.test(s, itemEntityRef, role, componentAccessor)) { return itemEntityRef; } } } } } return null; } } public boolean isEntityCountInRange( double minRange, double maxRange, int minCount, int maxCount, boolean findPlayers, Role role, @Nonnull QuadPredicate, Role, ComponentAccessor> filter, S s, ComponentAccessor componentAccessor ) { int count = 0; if (findPlayers) { count = this.players.countEntitiesInRange(minRange, maxRange, maxCount + 1, filter, s, role, componentAccessor); if (count > maxCount) { return false; } } count += this.npcs.countEntitiesInRange(minRange, maxRange, maxCount - count + 1, filter, s, role, componentAccessor); return count >= minCount && count <= maxCount; } public int countEntitiesInRange( double minRange, double maxRange, boolean findPlayers, @Nonnull QuadPredicate, T, ComponentAccessor> filter, S s, T t, ComponentAccessor componentAccessor ) { int count = 0; if (findPlayers) { count = this.players.countEntitiesInRange(minRange, maxRange, Integer.MAX_VALUE, filter, s, t, componentAccessor); } return count + this.npcs.countEntitiesInRange(minRange, maxRange, Integer.MAX_VALUE, filter, s, t, componentAccessor); } public void requirePlayerDistanceSorted(double v) { int value = MathUtil.ceil(v); if (!this.isConfiguring) { throw new IllegalStateException("function can be only called while configuring PositionCache"); } else { this.players.requireDistanceSorted(value); RoleStats roleStats = this.role.getRoleStats(); if (roleStats != null) { roleStats.trackRange(true, RoleStats.RangeType.SORTED, value); } } } public void requirePlayerDistanceUnsorted(double v) { int value = MathUtil.ceil(v); if (!this.isConfiguring) { throw new IllegalStateException("function can be only called while configuring PositionCache"); } else { this.players.requireDistanceUnsorted(value); RoleStats roleStats = this.role.getRoleStats(); if (roleStats != null) { roleStats.trackRange(true, RoleStats.RangeType.UNSORTED, value); } } } public void requirePlayerDistanceAvoidance(double v) { int value = MathUtil.ceil(v); if (!this.isConfiguring) { throw new IllegalStateException("function can be only called while configuring PositionCache"); } else { this.players.requireDistanceAvoidance(value); RoleStats roleStats = this.role.getRoleStats(); if (roleStats != null) { roleStats.trackRange(true, RoleStats.RangeType.AVOIDANCE, value); } } } public void requireEntityDistanceSorted(double v) { int value = MathUtil.ceil(v); if (!this.isConfiguring) { throw new IllegalStateException("function can be only called while configuring PositionCache"); } else { this.npcs.requireDistanceSorted(value); RoleStats roleStats = this.role.getRoleStats(); if (roleStats != null) { roleStats.trackRange(false, RoleStats.RangeType.SORTED, value); } } } public void requireEntityDistanceUnsorted(double v) { int value = MathUtil.ceil(v); if (!this.isConfiguring) { throw new IllegalStateException("function can be only called while configuring PositionCache"); } else { this.npcs.requireDistanceUnsorted(value); RoleStats roleStats = this.role.getRoleStats(); if (roleStats != null) { roleStats.trackRange(false, RoleStats.RangeType.UNSORTED, value); } } } public void requireEntityDistanceAvoidance(double v) { int value = MathUtil.ceil(v); if (!this.isConfiguring) { throw new IllegalStateException("function can be only called while configuring PositionCache"); } else { value = this.npcs.requireDistanceAvoidance(value); RoleStats roleStats = this.role.getRoleStats(); if (roleStats != null) { roleStats.trackRange(false, RoleStats.RangeType.AVOIDANCE, value); } } } public void requireDroppedItemDistance(double value) { if (this.maxDroppedItemDistance < value) { this.maxDroppedItemDistance = value; } } public void requireSpawnMarkerDistance(double value) { if (this.maxSpawnMarkerDistance < value) { this.maxSpawnMarkerDistance = value; } } public void requireSpawnBeaconDistance(int value) { if (this.maxSpawnBeaconDistance < value) { this.maxSpawnBeaconDistance = value; } } @Nonnull public Role getRole() { return this.role; } public void forEachNPCUnordered( double maxDistance, @Nonnull QuadPredicate, T, U, ComponentAccessor> predicate, @Nonnull QuadConsumer, T, V, R> consumer, T t, U u, V v, R r, ComponentAccessor componentAccessor ) { this.npcs.forEachEntityUnordered(maxDistance, predicate, consumer, t, u, v, r, componentAccessor); } public void forEachEntityInAvoidanceRange( @Nonnull Set> ignoredEntitiesForAvoidance, @Nonnull TriConsumer, T, CommandBuffer> consumer, T t, CommandBuffer commandBuffer ) { this.npcs.forEachEntityAvoidance(ignoredEntitiesForAvoidance, consumer, t, commandBuffer); this.players.forEachEntityAvoidance(ignoredEntitiesForAvoidance, consumer, t, commandBuffer); } public void forEachEntityInAvoidanceRange( @Nonnull Set> ignoredEntitiesForAvoidance, @Nonnull QuadConsumer, T, U, CommandBuffer> consumer, T t, U u, CommandBuffer commandBuffer ) { this.npcs.forEachEntityAvoidance(ignoredEntitiesForAvoidance, consumer, t, u, commandBuffer); this.players.forEachEntityAvoidance(ignoredEntitiesForAvoidance, consumer, t, u, commandBuffer); } public void setOpaqueBlockSet(int blockSet) { this.opaqueBlockSet = blockSet; } private static boolean testLineOfSightRays( @Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull PositionCache.RayPredicate predicate, @Nonnull T t, @Nonnull ComponentAccessor componentAccessor ) { TransformComponent transformComponent = componentAccessor.getComponent(ref, TRANSFORM_COMPONENT_TYPE); assert transformComponent != null; Vector3d position = transformComponent.getPosition(); ModelComponent modelComponent = componentAccessor.getComponent(ref, MODEL_COMPONENT_TYPE); float eyeHeight = modelComponent != null ? modelComponent.getModel().getEyeHeight() : 0.0F; double sx = position.getX(); double sy = position.getY() + eyeHeight; double sz = position.getZ(); TransformComponent targetTransformComponent = componentAccessor.getComponent(targetRef, TRANSFORM_COMPONENT_TYPE); assert targetTransformComponent != null; Vector3d targetPosition = targetTransformComponent.getPosition(); double tx = targetPosition.getX(); double ty = targetPosition.getY(); double tz = targetPosition.getZ(); ModelComponent targetModelComponent = componentAccessor.getComponent(targetRef, MODEL_COMPONENT_TYPE); if (targetModelComponent != null) { return predicate.test(sx, sy, sz, tx, ty + targetModelComponent.getModel().getEyeHeight(), tz, t, componentAccessor); } else { double ox = 0.0; double oy = 0.0; double oz = 0.0; BoundingBox boundingBoxComponent = componentAccessor.getComponent(targetRef, BOUNDING_BOX_COMPONENT_TYPE); if (boundingBoxComponent != null) { Box boundingBox = boundingBoxComponent.getBoundingBox(); ox = (boundingBox.getMax().getX() + boundingBox.getMin().getX()) / 2.0; oy = (boundingBox.getMax().getY() + boundingBox.getMin().getY()) / 2.0; oz = (boundingBox.getMax().getZ() + boundingBox.getMin().getZ()) / 2.0; } return predicate.test(sx, sy, sz, tx + ox, ty + oy, tz + oz, t, componentAccessor); } } private boolean hasLineOfSightInternal( @Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor ) { if (ref.equals(targetRef)) { return false; } else { TransformComponent transformComponent = componentAccessor.getComponent(ref, TRANSFORM_COMPONENT_TYPE); assert transformComponent != null; TransformComponent targetTransformComponent = componentAccessor.getComponent(targetRef, TRANSFORM_COMPONENT_TYPE); assert targetTransformComponent != null; if (transformComponent.getPosition().distanceSquaredTo(targetTransformComponent.getPosition()) <= 1.0E-12) { return true; } else { World world = componentAccessor.getExternalData().getWorld(); Objects.requireNonNull(world, "World can't be null in isLOS"); Int2ObjectMap blockSets = BlockSetModule.getInstance().getBlockSets(); IntSet opaqueSet = this.opaqueBlockSet >= 0 && blockSets != null ? (IntSet)blockSets.get(this.opaqueBlockSet) : null; boolean var9; try { this.lineOfSightComputeBuffer.result = true; this.lineOfSightComputeBuffer.assetMap = BlockType.getAssetMap(); this.lineOfSightComputeBuffer.opaqueSet = opaqueSet; this.lineOfSightComputeBuffer.world = world; var9 = testLineOfSightRays( ref, targetRef, (sx, sy, sz, tx, ty, tz, buffer, accessor) -> { buffer.chunk = buffer.world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(sx, sz)); if (buffer.chunk == null) { return false; } else { BlockIterator.iterateFromTo( sx, sy, sz, tx, ty, tz, (x, y, z, px, py, pz, qx, qy, qz, iBuffer) -> { if (!ChunkUtil.isInsideChunk(iBuffer.chunk.getX(), iBuffer.chunk.getZ(), x, z)) { iBuffer.chunk = iBuffer.world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x, z)); if (iBuffer.chunk == null) { iBuffer.result = false; return false; } } int blockId = iBuffer.chunk.getBlock(x, y, z); if (blockId == 0) { return true; } else { BlockType blockType = iBuffer.assetMap.getAsset(blockId); if (blockType != BlockType.UNKNOWN && blockType.getOpacity() != null && blockType.getOpacity() == Opacity.Transparent && (iBuffer.opaqueSet == null || !iBuffer.opaqueSet.contains(blockId))) { return true; } else { iBuffer.result = false; return false; } } }, buffer ); return buffer.result; } }, this.lineOfSightComputeBuffer, componentAccessor ); } finally { this.lineOfSightComputeBuffer.clearRefs(); } return var9; } } } public boolean hasLineOfSight(@Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor) { boolean cached = this.lineOfSightCache.containsKey(targetRef); if (cached) { if (this.isBenchmarking) { NPCPlugin.get().collectSensorSupportLosTest(this.role.getRoleIndex(), true, 0L); } return this.lineOfSightCache.getByte(targetRef) != 0; } else { boolean hasLineOfSight; if (this.isBenchmarking) { long start = System.nanoTime(); hasLineOfSight = this.hasLineOfSightInternal(ref, targetRef, componentAccessor); NPCPlugin.get().collectSensorSupportLosTest(this.role.getRoleIndex(), false, System.nanoTime() - start); } else { hasLineOfSight = this.hasLineOfSightInternal(ref, targetRef, componentAccessor); } this.lineOfSightCache.put(targetRef, (byte)(hasLineOfSight ? 1 : 0)); return hasLineOfSight; } } public boolean hasInverseLineOfSight( @Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor ) { boolean cached = this.inverseLineOfSightCache.containsKey(targetRef); if (this.isBenchmarking) { NPCPlugin.get().collectSensorSupportInverseLosTest(this.role.getRoleIndex(), cached); } if (cached) { return this.inverseLineOfSightCache.getByte(targetRef) != 0; } else { boolean hasLineOfSight = this.hasLineOfSightInternal(targetRef, ref, componentAccessor); this.inverseLineOfSightCache.put(targetRef, (byte)(hasLineOfSight ? 1 : 0)); return hasLineOfSight; } } public boolean isFriendlyBlockingLineOfSight( @Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull ComponentAccessor componentAccessor ) { boolean cached = this.friendlyFireCache.containsKey(targetRef); if (this.isBenchmarking) { NPCPlugin.get().collectSensorSupportFriendlyBlockingTest(this.role.getRoleIndex(), cached); } if (cached) { return this.friendlyFireCache.getByte(targetRef) != 0; } else { boolean blocking = testLineOfSightRays( ref, targetRef, (sx, sy, sz, tx, ty, tz, _this, accessor) -> { PositionCache.LineOfSightEntityBuffer buffer = _this.lineOfSightEntityComputeBuffer; buffer.pos.assign(sx, sy, sz); buffer.dir.assign(tx - sx, ty - sy, tz - sz); double squaredLength = buffer.dir.squaredLength(); return squaredLength < 1.0E-6 ? false : _this.players .testAnyEntityDistanceSquared( squaredLength, (positionCache, targetRef1, buffer1, componentAccessor1, length2) -> positionCache.testLineOfSightEntity( ref, targetRef1, buffer1, componentAccessor1, length2 ), _this, buffer, accessor ) || _this.npcs .testAnyEntityDistanceSquared( squaredLength, (positionCache1, targetRef2, buffer2, componentAccessor2, length3) -> positionCache1.testLineOfSightEntity( ref, targetRef2, buffer2, componentAccessor2, length3 ), _this, buffer, accessor ); }, this, componentAccessor ); this.friendlyFireCache.put(targetRef, (byte)(blocking ? 1 : 0)); return blocking; } } private boolean testLineOfSightEntity( @Nonnull Ref ref, @Nonnull Ref targetRef, @Nonnull PositionCache.LineOfSightEntityBuffer buffer, @Nonnull ComponentAccessor componentAccessor, double length2 ) { return !targetRef.equals(ref) && this.role.isFriendly(targetRef, componentAccessor) && rayIsIntersectingEntity(targetRef, buffer.pos, buffer.dir, buffer.minMax, length2, componentAccessor); } private void clearLineOfSightCache(double tickTime) { this.cacheTTL = (float)(this.cacheTTL - tickTime); if (this.cacheTTL <= 0.0F) { this.clearLineOfSightCache(); } } private void clearLineOfSightCache() { this.cacheTTL = RandomExtra.randomRange(0.09F, 0.11F); this.lineOfSightCache.clear(); this.inverseLineOfSightCache.clear(); this.friendlyFireCache.clear(); } protected static boolean rayIsIntersectingEntity( @Nonnull Ref ref, @Nonnull Vector3d pos, @Nonnull Vector3d dir, @Nonnull Vector2d minMax, double length2, @Nonnull ComponentAccessor componentAccessor ) { BoundingBox boundingBoxComponent = componentAccessor.getComponent(ref, BOUNDING_BOX_COMPONENT_TYPE); if (boundingBoxComponent == null) { return false; } else { TransformComponent transformComponent = componentAccessor.getComponent(ref, TRANSFORM_COMPONENT_TYPE); assert transformComponent != null; Vector3d position = transformComponent.getPosition(); double px = position.getX(); double py = position.getY(); double pz = position.getZ(); double dx = px - pos.x; double dy = py - pos.y; double dz = pz - pos.z; double dotProduct = NPCPhysicsMath.dotProduct(dir.x, dir.y, dir.z, dx, dy, dz); if (dotProduct <= 0.0) { return false; } else { double dist2 = NPCPhysicsMath.dotProduct(dx, dy, dz); return dotProduct * dotProduct >= dist2 * length2 ? false : CollisionMath.intersectRayAABB(pos, dir, px, py, pz, boundingBoxComponent.getBoundingBox(), minMax); } } } @Nonnull public List> getDroppedItemList() { return this.droppedItems; } @Nonnull public List> getSpawnMarkerList() { return this.spawnMarkers; } @Nonnull public List> getSpawnBeaconList() { return this.spawnBeacons; } private static class LineOfSightBuffer { @Nullable public World world; @Nullable public WorldChunk chunk; @Nullable public IntSet opaqueSet; @Nullable public BlockTypeAssetMap assetMap; public boolean result; public void clearRefs() { this.world = null; this.chunk = null; this.opaqueSet = null; this.assetMap = null; } } private static class LineOfSightEntityBuffer { public final Vector3d pos = new Vector3d(); public final Vector3d dir = new Vector3d(); public final Vector2d minMax = new Vector2d(); } @FunctionalInterface public interface RayPredicate { boolean test(double var1, double var3, double var5, double var7, double var9, double var11, T var13, @Nonnull ComponentAccessor var14); } }