package com.hypixel.hytale.server.spawning.local; import com.hypixel.hytale.builtin.weather.components.WeatherTracker; import com.hypixel.hytale.component.Archetype; import com.hypixel.hytale.component.ComponentType; import com.hypixel.hytale.component.Ref; import com.hypixel.hytale.component.ResourceType; import com.hypixel.hytale.component.Store; import com.hypixel.hytale.component.spatial.SpatialResource; import com.hypixel.hytale.component.system.tick.TickingSystem; import com.hypixel.hytale.function.function.TriIntObjectDoubleToByteFunction; import com.hypixel.hytale.math.util.ChunkUtil; 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.asset.type.blocktype.config.BlockType; import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent; import com.hypixel.hytale.server.core.modules.time.WorldTimeResource; import com.hypixel.hytale.server.core.universe.PlayerRef; import com.hypixel.hytale.server.core.universe.world.World; import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk; 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.spawning.SpawningPlugin; import com.hypixel.hytale.server.spawning.assets.spawns.LightType; import com.hypixel.hytale.server.spawning.beacons.LegacySpawnBeaconEntity; import com.hypixel.hytale.server.spawning.util.LightRangePredicate; import com.hypixel.hytale.server.spawning.wrappers.BeaconSpawnWrapper; import it.unimi.dsi.fastutil.Pair; import it.unimi.dsi.fastutil.objects.Object2ByteMap; import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectList; import java.util.List; import java.util.logging.Level; import javax.annotation.Nonnull; public class LocalSpawnControllerSystem extends TickingSystem { public static final double RUN_FREQUENCY_SECONDS = 5.0; private static final int LIGHT_LEVEL_EVALUATION_RADIUS = 4; private final Archetype controllerArchetype; private final ComponentType spawnControllerComponentType; private final ComponentType transformComponentype; private final ComponentType weatherTrackerComponentType; private final ComponentType localSpawnBeaconComponentType; private final ComponentType spawnBeaconComponentType; private final ResourceType localSpawnStateResourceType; private final ResourceType, EntityStore>> beaconSpatialComponent; public LocalSpawnControllerSystem( ComponentType spawnControllerComponentType, ComponentType transformComponentype, ComponentType weatherTrackerComponentType, ComponentType localSpawnBeaconComponentType, ComponentType spawnBeaconComponentType, ResourceType localSpawnStateResourceType, ResourceType, EntityStore>> beaconSpatialComponent ) { this.spawnControllerComponentType = spawnControllerComponentType; this.transformComponentype = transformComponentype; this.weatherTrackerComponentType = weatherTrackerComponentType; this.localSpawnBeaconComponentType = localSpawnBeaconComponentType; this.spawnBeaconComponentType = spawnBeaconComponentType; this.localSpawnStateResourceType = localSpawnStateResourceType; this.beaconSpatialComponent = beaconSpatialComponent; this.controllerArchetype = Archetype.of(spawnControllerComponentType, PlayerRef.getComponentType()); } @Override public void tick(float dt, int systemIndex, @Nonnull Store store) { LocalSpawnState localSpawnState = store.getResource(this.localSpawnStateResourceType); List> controllers = localSpawnState.getLocalControllerList(); store.forEachChunk(this.controllerArchetype, (archetypeChunk, commandBuffer) -> { for (int indexx = 0; indexx < archetypeChunk.size(); indexx++) { LocalSpawnController spawnControllerComponentx = archetypeChunk.getComponent(indexx, this.spawnControllerComponentType); assert spawnControllerComponentx != null; if (spawnControllerComponentx.tickTimeToNextRunSeconds(dt)) { controllers.add(archetypeChunk.getReferenceTo(indexx)); } } }); if (!controllers.isEmpty()) { World world = store.getExternalData().getWorld(); List pendingSpawns = localSpawnState.getLocalPendingSpawns(); ObjectList> existingBeacons = SpatialResource.getThreadLocalReferenceList(); for (int index = 0; index < controllers.size(); index++) { Ref reference = controllers.get(index); LocalSpawnController spawnControllerComponent = store.getComponent(reference, this.spawnControllerComponentType); assert spawnControllerComponent != null; PlayerRef playerRefComponent = store.getComponent(reference, PlayerRef.getComponentType()); assert playerRefComponent != null; SpawningPlugin.get().getLogger().at(Level.FINE).log("Running local spawn controller for player %s", playerRefComponent.getUsername()); TransformComponent transformComponent = store.getComponent(reference, this.transformComponentype); assert transformComponent != null; WeatherTracker weatherTrackerComponent = store.getComponent(reference, this.weatherTrackerComponentType); assert weatherTrackerComponent != null; weatherTrackerComponent.updateEnvironment(transformComponent, store); int environmentIndex = weatherTrackerComponent.getEnvironmentId(); List possibleBeacons = SpawningPlugin.get().getBeaconSpawnsForEnvironment(environmentIndex); if (possibleBeacons != null && !possibleBeacons.isEmpty()) { BeaconSpawnWrapper firstBeacon = possibleBeacons.getFirst(); double largestDistance = firstBeacon.getBeaconRadius(); int[] firstRange = firstBeacon.getSpawn().getYRange(); int lowestY = firstRange[0]; int highestY = firstRange[1]; for (int i = 1; i < possibleBeacons.size(); i++) { BeaconSpawnWrapper beacon = possibleBeacons.get(i); double radius = beacon.getBeaconRadius(); if (radius > largestDistance) { largestDistance = radius; } int[] yRange = beacon.getSpawn().getYRange(); if (yRange[0] < lowestY) { lowestY = yRange[0]; } if (yRange[1] > highestY) { highestY = yRange[1]; } } largestDistance *= 2.0; Vector3d position = transformComponent.getPosition(); double largestDistanceSquared = largestDistance * largestDistance; int yDistance = Math.abs(lowestY) + Math.abs(highestY); int y = MathUtil.floor(position.getY()); int minY = Math.max(0, y - yDistance); int maxY = Math.min(319, y + yDistance); SpatialResource, EntityStore> spatialResource = store.getResource(this.beaconSpatialComponent); spatialResource.getSpatialStructure().ordered(position, largestDistance, existingBeacons); WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType()); double sunlightFactor = worldTimeResource.getSunlightFactor(); int xPos = MathUtil.floor(position.getX()); int yPos = MathUtil.floor(position.getY()); int zPos = MathUtil.floor(position.getZ()); Object2ByteOpenHashMap averageLightValues = new Object2ByteOpenHashMap(); averageLightValues.defaultReturnValue((byte)-1); label134: for (int i = 0; i < possibleBeacons.size(); i++) { BeaconSpawnWrapper possibleBeacon = possibleBeacons.get(i); if (possibleBeacon.spawnParametersMatch(store)) { for (int j = 0; j < existingBeacons.size(); j++) { Ref existingBeaconReference = (Ref)existingBeacons.get(j); LegacySpawnBeaconEntity existingBeaconComponent = store.getComponent(existingBeaconReference, this.spawnBeaconComponentType); assert existingBeaconComponent != null; TransformComponent existingBeaconTransformComponent = store.getComponent(existingBeaconReference, this.transformComponentype); assert existingBeaconTransformComponent != null; double existingY = existingBeaconTransformComponent.getPosition().getY(); if (!(existingY > maxY) && !(existingY < minY)) { int existingBeaconIndex = existingBeaconComponent.getSpawnWrapper().getSpawnIndex(); if (existingBeaconIndex == possibleBeacon.getSpawnIndex()) { continue label134; } } } for (int j = 0; j < pendingSpawns.size(); j++) { LegacySpawnBeaconEntity pending = pendingSpawns.get(j); Ref pendingReference = pending.getReference(); TransformComponent pendingTransformComponent = store.getComponent(pendingReference, TransformComponent.getComponentType()); assert pendingTransformComponent != null; Vector3d pendingPosition = pendingTransformComponent.getPosition(); double pendingY = pendingPosition.getY(); if (!(pendingY > maxY) && !(pendingY < minY)) { double xDiff = position.x - pendingPosition.x; double zDiff = position.z - pendingPosition.z; double distSquared = xDiff * xDiff + zDiff * zDiff; if (!(distSquared > largestDistanceSquared)) { int existingBeaconIndex = pending.getSpawnWrapper().getSpawnIndex(); if (existingBeaconIndex == possibleBeacon.getSpawnIndex()) { continue label134; } } } } if (spawnLightLevelMatches(world, xPos, yPos, zPos, sunlightFactor, possibleBeacon, averageLightValues)) { Pair, LegacySpawnBeaconEntity> beaconEntityPair = LegacySpawnBeaconEntity.create( possibleBeacon, transformComponent.getPosition(), transformComponent.getRotation(), store ); Ref beaconRef = (Ref)beaconEntityPair.first(); if (beaconRef != null && beaconRef.isValid()) { store.ensureComponent(beaconRef, this.localSpawnBeaconComponentType); SpawningPlugin.get() .getLogger() .at(Level.FINE) .log( "Placed spawn beacon of type %s at position %s for player %s", possibleBeacon.getSpawn().getId(), position, playerRefComponent.getUsername() ); pendingSpawns.add((LegacySpawnBeaconEntity)beaconEntityPair.second()); } } } } existingBeacons.clear(); averageLightValues.clear(); spawnControllerComponent.setTimeToNextRunSeconds(5.0); } else { spawnControllerComponent.setTimeToNextRunSeconds(5.0); } } controllers.clear(); pendingSpawns.clear(); } } private static boolean spawnLightLevelMatches( @Nonnull World world, int x, int y, int z, double sunlightFactor, @Nonnull BeaconSpawnWrapper wrapper, @Nonnull Object2ByteMap averageValues ) { LightRangePredicate lightRangePredicate = wrapper.getLightRangePredicate(); if (lightRangePredicate.isTestLightValue()) { byte lightValue = getCachedAverageLightValue( LightType.Light, world, x, y, z, sunlightFactor, (_x, _y, _z, _chunk, _sunlightFactor) -> LightRangePredicate.calculateLightValue(_chunk, _x, _y, _z, _sunlightFactor), averageValues ); if (!lightRangePredicate.testLight(lightValue)) { return false; } } if (lightRangePredicate.isTestSkyLightValue()) { byte lightValue = getCachedAverageLightValue( LightType.SkyLight, world, x, y, z, sunlightFactor, (_x, _y, _z, _chunk, _sunlightFactor) -> _chunk.getSkyLight(_x, _y, _z), averageValues ); if (!lightRangePredicate.testSkyLight(lightValue)) { return false; } } if (lightRangePredicate.isTestSunlightValue()) { byte lightValue = getCachedAverageLightValue( LightType.Sunlight, world, x, y, z, sunlightFactor, (_x, _y, _z, _chunk, _sunlightFactor) -> (byte)(_chunk.getSkyLight(_x, _y, _z) * _sunlightFactor), averageValues ); if (!lightRangePredicate.testSunlight(lightValue)) { return false; } } if (lightRangePredicate.isTestRedLightValue()) { byte lightValue = getCachedAverageLightValue( LightType.RedLight, world, x, y, z, sunlightFactor, (_x, _y, _z, _chunk, _sunlightFactor) -> _chunk.getRedBlockLight(_x, _y, _z), averageValues ); if (!lightRangePredicate.testRedLight(lightValue)) { return false; } } if (lightRangePredicate.isTestGreenLightValue()) { byte lightValue = getCachedAverageLightValue( LightType.GreenLight, world, x, y, z, sunlightFactor, (_x, _y, _z, _chunk, _sunlightFactor) -> _chunk.getGreenBlockLight(_x, _y, _z), averageValues ); if (!lightRangePredicate.testGreenLight(lightValue)) { return false; } } if (lightRangePredicate.isTestBlueLightValue()) { byte lightValue = getCachedAverageLightValue( LightType.BlueLight, world, x, y, z, sunlightFactor, (_x, _y, _z, _chunk, _sunlightFactor) -> _chunk.getBlueBlockLight(_x, _y, _z), averageValues ); return lightRangePredicate.testBlueLight(lightValue); } else { return true; } } private static byte getCachedAverageLightValue( LightType lightType, @Nonnull World world, int x, int y, int z, double sunlightFactor, @Nonnull TriIntObjectDoubleToByteFunction valueCalculator, @Nonnull Object2ByteMap averageValues ) { byte cachedValue = averageValues.getByte(lightType); if (cachedValue < 0) { int counted = 0; int total = 0; for (int xOffset = x - 4; xOffset < x + 4; xOffset++) { for (int zOffset = z - 4; zOffset < z + 4; zOffset++) { WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(xOffset, zOffset)); if (chunk != null) { BlockChunk blockChunk = chunk.getBlockChunk(); for (int yOffset = y; yOffset < y + 4; yOffset++) { int blockId = chunk.getBlock(xOffset, yOffset, zOffset); if (blockId == 0 || BlockType.getAssetMap().getAsset(blockId).getMaterial() != BlockMaterial.Solid) { counted++; total += valueCalculator.apply(xOffset, yOffset, zOffset, blockChunk, sunlightFactor); } } } } } cachedValue = counted > 0 ? (byte)((float)total / counted) : 0; averageValues.put(lightType, cachedValue); } return cachedValue; } }