diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandSupport.java b/core/src/main/java/com/volmit/iris/core/commands/CommandSupport.java index 0f7baad0d..943cbecac 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandSupport.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandSupport.java @@ -21,7 +21,6 @@ package com.volmit.iris.core.commands; import com.volmit.iris.Iris; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.pregenerator.ChunkUpdater; -import com.volmit.iris.core.service.IrisEngineSVC; import com.volmit.iris.core.tools.IrisPackBenchmarking; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.framework.Engine; diff --git a/core/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java b/core/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java index 6575abd37..6300573ad 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java +++ b/core/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java @@ -44,6 +44,7 @@ import lombok.EqualsAndHashCode; import lombok.ToString; import java.io.*; +import java.util.Arrays; import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; @@ -219,6 +220,10 @@ public class ResourceLoader implements MeteredCache { return s.map(this::load); } + public Stream streamAllPossible() { + return streamAll(Arrays.stream(getPossibleKeys())); + } + public KList loadAll(KList s) { KList m = new KList<>(); diff --git a/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java b/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java index 08962e4af..629ab9e88 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java @@ -27,7 +27,6 @@ import com.volmit.iris.engine.object.*; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KSet; -import com.volmit.iris.util.format.Form; import com.volmit.iris.util.mantle.Mantle; import com.volmit.iris.util.mantle.MantleFlag; import com.volmit.iris.util.math.M; @@ -72,7 +71,6 @@ public class IrisWorldManager extends EngineAssignedWorldManager { private final Looper looper; private final int id; private final KList updateQueue = new KList<>(); - private final ChronoLatch cl; private final ChronoLatch clw; private final ChronoLatch ecl; private final ChronoLatch cln; @@ -83,12 +81,10 @@ public class IrisWorldManager extends EngineAssignedWorldManager { private long charge = 0; private int actuallySpawned = 0; private int cooldown = 0; - private List precount = new KList<>(); private KSet injectBiomes = new KSet<>(); public IrisWorldManager() { super(null); - cl = null; ecl = null; cln = null; clw = null; @@ -103,7 +99,6 @@ public class IrisWorldManager extends EngineAssignedWorldManager { chunkUpdater = new ChronoLatch(3000); chunkDiscovery = new ChronoLatch(5000); cln = new ChronoLatch(60000); - cl = new ChronoLatch(3000); ecl = new ChronoLatch(250); clw = new ChronoLatch(1000, true); id = engine.getCacheID(); @@ -151,27 +146,12 @@ public class IrisWorldManager extends EngineAssignedWorldManager { engine.getEngineData().cleanup(getEngine()); } - if (precount != null) { - entityCount = 0; - for (Entity i : precount) { - if (i instanceof LivingEntity) { - if (!i.isDead()) { - entityCount++; - } - } - } - - precount = null; - } - if (energy < 650) { if (ecl.flip()) { energy *= 1 + (0.02 * M.clip((1D - getEntitySaturation()), 0D, 1D)); fixEnergy(); } } - - onAsyncTick(); } return IrisSettings.get().getWorld().getAsyncTickIntervalMS(); @@ -214,7 +194,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { int finalZ = c.getZ() + z; J.a(() -> getMantle().raiseFlag(finalX, finalZ, MantleFlag.INITIAL_SPAWNED_MARKER, () -> { - J.a(() -> spawnIn(cx, true), RNG.r.i(5, 200)); + J.a(() -> spawnIn(cx), RNG.r.i(5, 200)); getSpawnersFromMarkers(cx).forEach((blockf, spawners) -> { if (spawners.isEmpty()) { return; @@ -222,7 +202,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { IrisPosition block = new IrisPosition(blockf.getX(), blockf.getY() + getEngine().getWorld().minHeight(), blockf.getZ()); IrisSpawner s = new KList<>(spawners).getRandom(); - spawn(block, s, true); + spawn(block, s); }); })); } @@ -232,73 +212,16 @@ public class IrisWorldManager extends EngineAssignedWorldManager { } } - private boolean onAsyncTick() { - if (getEngine().isClosed()) { - return false; - } - - actuallySpawned = 0; - - if (energy < 100) { - J.sleep(200); - return false; - } - - if (!getEngine().getWorld().hasRealWorld()) { - Iris.debug("Can't spawn. No real world"); - J.sleep(5000); - return false; - } - - double epx = getEntitySaturation(); - if (epx > IrisSettings.get().getWorld().getTargetSpawnEntitiesPerChunk()) { - Iris.debug("Can't spawn. The entity per chunk ratio is at " + Form.pc(epx, 2) + " > 100% (total entities " + entityCount + ")"); - J.sleep(5000); - return false; - } - - if (cl.flip()) { - try { - J.s(() -> precount = getEngine().getWorld().realWorld().getEntities()); - } catch (Throwable e) { - close(); - } - } - - int spawnBuffer = RNG.r.i(2, 12); - - Chunk[] cc = getEngine().getWorld().realWorld().getLoadedChunks(); - while (spawnBuffer-- > 0) { - if (cc.length == 0) { - Iris.debug("Can't spawn. No chunks!"); - return false; - } - - Chunk c = cc[RNG.r.nextInt(cc.length)]; - - if (!c.isLoaded() || !Chunks.isSafe(c.getWorld(), c.getX(), c.getZ())) { - continue; - } - - spawnIn(c, false); - } - - energy -= (actuallySpawned / 2D); - return actuallySpawned > 0; - } - private void fixEnergy() { energy = M.clip(energy, 1D, getDimension().getMaximumEnergy()); } - private void spawnIn(Chunk c, boolean initial) { + private void spawnIn(Chunk c) { if (getEngine().isClosed()) { return; } - if (initial) { - energy += 1.2; - } + energy += 1.2; if (IrisSettings.get().getWorld().isMarkerEntitySpawningSystem()) { getSpawnersFromMarkers(c).forEach((blockf, spawners) -> { @@ -308,9 +231,8 @@ public class IrisWorldManager extends EngineAssignedWorldManager { IrisPosition block = new IrisPosition(blockf.getX(), blockf.getY() + getEngine().getWorld().minHeight(), blockf.getZ()); IrisSpawner s = new KList<>(spawners).getRandom(); - spawn(block, s, false); J.a(() -> getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.INITIAL_SPAWNED_MARKER, - () -> spawn(block, s, true))); + () -> spawn(block, s))); }); } @@ -341,7 +263,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { .stream() .filter(filter))) .filter(counter) - .flatMap((i) -> stream(i, initial)) + .flatMap(this::stream) .collect(Collectors.toList())) .getRandom(); //@done @@ -378,13 +300,13 @@ public class IrisWorldManager extends EngineAssignedWorldManager { } } - private Stream stream(IrisSpawner s, boolean initial) { - for (IrisEntitySpawn i : initial ? s.getInitialSpawns() : s.getSpawns()) { + private Stream stream(IrisSpawner s) { + for (IrisEntitySpawn i : s.getInitialSpawns()) { i.setReferenceSpawner(s); i.setReferenceMarker(s.getReferenceMarker()); } - return (initial ? s.getInitialSpawns() : s.getSpawns()).stream(); + return (s.getInitialSpawns()).stream(); } private KList spawnRandomly(List types) { @@ -431,7 +353,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { } } - private void spawn(IrisPosition block, IrisSpawner spawner, boolean initial) { + private void spawn(IrisPosition block, IrisSpawner spawner) { if (getEngine().isClosed()) { return; } @@ -440,7 +362,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { return; } - KList s = initial ? spawner.getInitialSpawns() : spawner.getSpawns(); + KList s = spawner.getInitialSpawns(); if (s.isEmpty()) { return; } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisEntity.java b/core/src/main/java/com/volmit/iris/engine/object/IrisEntity.java index dfb2508d0..a7ddb6aaf 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisEntity.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisEntity.java @@ -404,7 +404,7 @@ public class IrisEntity extends IrisRegistrant { }); - return e; + return e.isValid() ? e : null; } private int surfaceY(Location l) { diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java b/core/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java index a507c342f..e71159c6e 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java @@ -28,19 +28,12 @@ import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.math.Vector3d; import com.volmit.iris.util.matter.MatterMarker; import com.volmit.iris.util.matter.slices.MarkerMatter; -import io.lumine.mythic.bukkit.adapters.BukkitEntity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.World; +import org.bukkit.*; import org.bukkit.entity.Entity; -import org.bukkit.entity.EntityType; -import org.bukkit.entity.LivingEntity; -import org.bukkit.util.BoundingBox; @Snippet("entity-spawn") @Accessors(chain = true) @@ -69,6 +62,10 @@ public class IrisEntitySpawn implements IRare { private transient IrisSpawner referenceSpawner; private transient IrisMarker referenceMarker; + public boolean check(Engine eng, IrisPosition c, ChunkSnapshot snapshot) { + return getRealEntity(eng).getSurface().matches(snapshot.getBlockData(c.getX() & 15, c.getY(), c.getZ() & 15)); + } + public int spawn(Engine gen, Chunk c, RNG rng) { int spawns = minSpawns == maxSpawns ? minSpawns : rng.i(Math.min(minSpawns, maxSpawns), Math.max(minSpawns, maxSpawns)); int s = 0; @@ -168,7 +165,7 @@ public class IrisEntitySpawn implements IRare { return null; } - if (!ignoreSurfaces && !irisEntity.getSurface().matches(at.clone().subtract(0, 1, 0).getBlock())) { + if (!ignoreSurfaces && !irisEntity.getSurface().matches(at.clone().subtract(0, 1, 0).getBlock().getBlockData())) { return null; } @@ -183,6 +180,10 @@ public class IrisEntitySpawn implements IRare { Entity e = irisEntity.spawn(g, at.add(0.5, 0.5, 0.5), rng.aquire(() -> new RNG(g.getSeedManager().getEntity()))); if (e != null) { Iris.debug("Spawned " + C.DARK_AQUA + "Entity<" + getEntity() + "> " + C.GREEN + e.getType() + C.LIGHT_PURPLE + " @ " + C.GRAY + e.getLocation().getX() + ", " + e.getLocation().getY() + ", " + e.getLocation().getZ()); + + if (referenceSpawner != null) { + referenceSpawner.getConditions().apply(e); + } } return e; diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisSpawnCondition.java b/core/src/main/java/com/volmit/iris/engine/object/IrisSpawnCondition.java new file mode 100644 index 000000000..e3038771e --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisSpawnCondition.java @@ -0,0 +1,58 @@ +package com.volmit.iris.engine.object; + +import com.volmit.iris.Iris; +import com.volmit.iris.engine.object.annotations.Desc; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Entity; +import org.bukkit.entity.SpawnCategory; +import org.bukkit.persistence.PersistentDataType; + +import java.util.UUID; +import java.util.function.BooleanSupplier; + +@Accessors(chain = true) +@NoArgsConstructor +@AllArgsConstructor +@Desc("Conditions for a spawner to be triggered") +@Data +public class IrisSpawnCondition { + private static final NamespacedKey CATEGORY_KEY = new NamespacedKey(Iris.instance, "spawn_category"); + + private SpawnCategory category = SpawnCategory.AMBIENT; + private int maxEntities = 60; + + public boolean check(KMap> cache, KList entities) { + int entityCount = 0; + for (Entity entity : entities) { + var map = cache.computeIfAbsent(entity.getUniqueId(), k -> new KMap<>()); + if (check(map, "category_" + category.name(), () -> checkCategory(entity, category)) && ++entityCount >= maxEntities) + return false; + } + return true; + } + + public void apply(Entity entity) { + var pdc = entity.getPersistentDataContainer(); + pdc.set(CATEGORY_KEY, PersistentDataType.STRING, category.name()); + } + + private static boolean check(KMap cache, String key, BooleanSupplier predicate) { + return cache.computeIfAbsent(key, k -> predicate.getAsBoolean()) == Boolean.TRUE; + } + + private static boolean checkCategory(Entity entity, SpawnCategory category) { + if (entity.getSpawnCategory() == category) + return true; + + var pdc = entity.getPersistentDataContainer(); + if (!pdc.has(CATEGORY_KEY, PersistentDataType.STRING)) + return false; + return category.name().equals(pdc.get(CATEGORY_KEY, PersistentDataType.STRING)); + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisSpawner.java b/core/src/main/java/com/volmit/iris/engine/object/IrisSpawner.java index c3343a2a0..53340721e 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisSpawner.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisSpawner.java @@ -74,6 +74,9 @@ public class IrisSpawner extends IrisRegistrant { @Desc("Where should these spawns be placed") private IrisSpawnGroup group = IrisSpawnGroup.NORMAL; + @Desc("Conditions for this spawner to be triggered") + private IrisSpawnCondition conditions = new IrisSpawnCondition(); + public boolean isValid(IrisBiome biome) { return switch (group) { case NORMAL -> switch (biome.getInferredType()) { diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisSurface.java b/core/src/main/java/com/volmit/iris/engine/object/IrisSurface.java index e8a2e2e3c..885ba95f5 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisSurface.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisSurface.java @@ -20,7 +20,7 @@ package com.volmit.iris.engine.object; import com.volmit.iris.engine.object.annotations.Desc; import org.bukkit.Material; -import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; import org.bukkit.block.data.Waterlogged; @Desc("The type of surface entities should spawn on") @@ -47,8 +47,8 @@ public enum IrisSurface { * @param state The blockstate * @return True if it matches */ - public boolean matches(Block state) { - Material type = state.getType(); + public boolean matches(BlockData state) { + Material type = state.getMaterial(); if (type.isSolid()) { return this == LAND || this == OVERWORLD || (this == ANIMAL && (type == Material.GRASS_BLOCK || type == Material.DIRT diff --git a/core/src/main/java/com/volmit/iris/engine/service/EngineMobHandlerSVC.java b/core/src/main/java/com/volmit/iris/engine/service/EngineMobHandlerSVC.java new file mode 100644 index 000000000..e184d8488 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/engine/service/EngineMobHandlerSVC.java @@ -0,0 +1,305 @@ +package com.volmit.iris.engine.service; + +import com.volmit.iris.Iris; +import com.volmit.iris.core.IrisSettings; +import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.nms.container.Pair; +import com.volmit.iris.engine.IrisWorldManager; +import com.volmit.iris.engine.framework.Engine; +import com.volmit.iris.engine.object.*; +import com.volmit.iris.util.collection.KList; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.collection.KSet; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.mantle.Mantle; +import com.volmit.iris.util.math.BlockPosition; +import com.volmit.iris.util.math.Position2; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.matter.MatterMarker; +import com.volmit.iris.util.parallel.Sync; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import io.papermc.lib.PaperLib; +import lombok.SneakyThrows; +import org.bukkit.ChunkSnapshot; +import org.bukkit.GameRule; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerChangedWorldEvent; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class EngineMobHandlerSVC extends IrisEngineService { + private static final List CAVE_TAGS = List.of("cave_floor", "cave_ceiling"); + private static final int SAFE_RADIUS = 16; + private static final int MAX_RADIUS = 128; + + private final AtomicLong currentTick = new AtomicLong(); + private final Sync sync = new Sync<>(); + private final Set players = ConcurrentHashMap.newKeySet(); + private KList entities = new KList<>(); + private Thread asyncTicker = null; + private Thread entityCollector = null; + private int task = -1; + + public EngineMobHandlerSVC(Engine engine) { + super(engine); + } + + @Override + public void onEnable(boolean hotload) { + if (task != -1) J.csr(task); + task = J.sr(() -> sync.advance(currentTick.getAndIncrement()), 0); + + cancel(asyncTicker); + cancel(entityCollector); + asyncTicker = Thread.ofPlatform() + .name("Iris Async Mob Spawning - " + engine.getWorld().name()) + .priority(9) + .start(() -> { + while (!engine.isClosed()) { + if (Thread.interrupted()) + return; + + try { + asyncTick(); + } catch (Throwable e) { + Iris.error("Error in async tick for " + engine.getWorld().name()); + e.printStackTrace(); + + J.sleep(100); + } + } + }); + entityCollector = Thread.ofVirtual() + .name("Iris Async Entity Collector - " + engine.getWorld().name()) + .start(() -> { + while (!engine.isClosed()) { + if (Thread.interrupted()) + return; + + try { + sync.next().join(); + var world = engine.getWorld().realWorld(); + if (world == null) continue; + J.s(() -> entities = new KList<>(world.getEntities())); + } catch (Throwable e) { + Iris.error("Error in async tick for " + engine.getWorld().name()); + e.printStackTrace(); + + J.sleep(100); + } + } + }); + } + + @Override + public void onDisable(boolean hotload) { + J.csr(task); + cancel(asyncTicker); + cancel(entityCollector); + } + + @SneakyThrows + private void asyncTick() { + long tick = sync.next().join(); + var manager = (IrisWorldManager) engine.getWorldManager(); + var world = engine.getWorld().realWorld(); + if (world == null + || noSpawning() + || Boolean.FALSE.equals(world.getGameRuleValue(GameRule.DO_MOB_SPAWNING)) + || players.isEmpty() + || manager.getEnergy() < 100) + return; + + var p = PrecisionStopwatch.start(); + var entities = new KList<>(this.entities); + + var conditionCache = new KMap>(); + var data = engine.getData(); + var invalid = data.getSpawnerLoader() + .streamAllPossible() + .filter(Predicate.not(spawner -> spawner.canSpawn(engine) + && spawner.getConditions().check(conditionCache, entities))) + .map(IrisSpawner::getLoadKey) + .collect(Collectors.toSet()); + + var centers = players.stream() + .filter(Objects::nonNull) + .filter(Player::isOnline) + .map(Player::getLocation) + .map(BlockPosition::fromLocation) + .collect(KList.collector()) + .shuffle(); + + if (centers.isEmpty()) + return; + + double delta = 0; + int actuallySpawned = 0; + + KMap> cache = new KMap<>(); + while (centers.isNotEmpty()) { + var center = centers.pop(); + var pos = center.randomPoint(MAX_RADIUS, SAFE_RADIUS); + if (pos.getY() < world.getMinHeight() || pos.getY() >= world.getMaxHeight()) + continue; + + var chunkPos = new Position2(center.getX() >> 4, center.getZ() >> 4); + var pair = cache.computeIfAbsent(chunkPos, cPos -> { + try { + return PaperLib.getChunkAtAsync(world, cPos.getX(), cPos.getZ(), false) + .thenApply(c -> c != null ? new Pair<>(c.getEntities(), c.getChunkSnapshot(false, false, false)) : null) + .get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + }); + if (pair == null) + continue; + + var spawners = spawnersAt(pair.getB(), pos, invalid); + spawners.removeIf(i -> invalid.contains(i.getLoadKey())); + spawners.removeIf(i -> !i.canSpawn(engine, chunkPos.getX(), chunkPos.getZ())); + + if (spawners.isEmpty()) + continue; + + boolean failed = true; + IrisPosition irisPos = new IrisPosition(pos.getX(), pos.getY(), pos.getZ()); + for (var spawner : spawners) { + var spawns = spawner.getSpawns().copy(); + spawns.removeIf(spawn -> !spawn.check(engine, irisPos, pair.getB())); + + var entity = IRare.pick(spawns, RNG.r.nextDouble()); + if (entity == null) + continue; + + entity.setReferenceSpawner(spawner); + entity.setReferenceMarker(spawner.getReferenceMarker()); + int spawned = entity.spawn(engine, irisPos, RNG.r); + if (spawned == 0) + continue; + + delta += spawned * ((entity.getEnergyMultiplier() * spawner.getEnergyMultiplier() * 1)); + actuallySpawned += spawned; + + spawner.spawn(engine, chunkPos.getX(), chunkPos.getZ()); + if (!spawner.canSpawn(engine)) + invalid.add(spawner.getLoadKey()); + failed = false; + break; + } + if (failed && p.getMilliseconds() < 1000) + centers.add(center); + } + manager.setEnergy(manager.getEnergy() - delta); + if (actuallySpawned > 0) { + Iris.info("Async Mob Spawning " + world.getName() + " used " + delta + " energy and took " + Form.duration((long) p.getMilliseconds())); + } + } + + private KSet spawnersAt(ChunkSnapshot chunk, BlockPosition pos, Set invalid) { + KSet spawners = markerAt(chunk, pos, invalid); + + var loader = engine.getData().getSpawnerLoader(); + int y = pos.getY() - engine.getWorld().minHeight(); + Stream.concat(engine.getRegion(pos.getX(), pos.getZ()) + .getEntitySpawners() + .stream(), + engine.getBiomeOrMantle(pos.getX(), y, pos.getZ()) + .getEntitySpawners() + .stream()) + .filter(Predicate.not(invalid::contains)) + .map(loader::load) + .forEach(spawners::add); + + return spawners; + } + + private KSet markerAt(ChunkSnapshot chunk, BlockPosition pos, Set invalid) { + if (!IrisSettings.get().getWorld().isMarkerEntitySpawningSystem()) + return new KSet<>(); + + int y = pos.getY() - engine.getWorld().minHeight(); + Mantle mantle = engine.getMantle().getMantle(); + MatterMarker matter = mantle.get(pos.getX(), y, pos.getZ(), MatterMarker.class); + if (matter == null || CAVE_TAGS.contains(matter.getTag())) + return new KSet<>(); + IrisData data = engine.getData(); + IrisMarker mark = data.getMarkerLoader().load(matter.getTag()); + if (mark == null) + return new KSet<>(); + + if (mark.isEmptyAbove()) { + int x = pos.getX() & 15, z = pos.getZ() & 15; + boolean remove = chunk.getBlockData(x, pos.getY() + 1, z).getMaterial().isSolid() || chunk.getBlockData(x, pos.getY() + 2, z).getMaterial().isSolid(); + if (remove) { + mantle.remove(pos.getX(), y, pos.getZ(), MatterMarker.class); + return new KSet<>(); + } + } + + KSet spawners = new KSet<>(); + for (String key : mark.getSpawners()) { + if (invalid.contains(key)) + continue; + + IrisSpawner spawner = data.getSpawnerLoader().load(key); + if (spawner == null) { + Iris.error("Cannot load spawner: " + key + " for marker " + matter.getTag()); + continue; + } + + spawner.setReferenceMarker(mark); + spawners.add(spawner); + } + return spawners; + } + + @EventHandler(priority = EventPriority.LOWEST) + public void on(PlayerJoinEvent event) { + var player = event.getPlayer(); + if (player.getWorld() != engine.getWorld().realWorld()) + return; + players.add(player); + } + + @EventHandler(priority = EventPriority.LOWEST) + public void on(PlayerQuitEvent event) { + players.remove(event.getPlayer()); + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void on(PlayerChangedWorldEvent event) { + var player = event.getPlayer(); + if (player.getWorld() == engine.getWorld().realWorld()) + players.add(player); + else + players.remove(player); + } + + @SneakyThrows + private static void cancel(Thread thread) { + if (thread == null || !thread.isAlive()) return; + thread.interrupt(); + } + + private static boolean noSpawning() { + var world = IrisSettings.get().getWorld(); + return !world.isMarkerEntitySpawningSystem() && !world.isAnbientEntitySpawningSystem(); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java index 0e89c1162..3bb90230c 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java @@ -21,7 +21,6 @@ package com.volmit.iris.util.mantle; import com.google.common.util.concurrent.AtomicDouble; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.service.IrisEngineSVC; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.engine.framework.Engine; @@ -425,7 +424,7 @@ public class Mantle { ioTrim.set(true); unloadLock.lock(); try { - if (lastUse != null && IrisEngineSVC.instance != null) { + if (lastUse != null) { if (!lastUse.isEmpty()) { Iris.debug("Trimming Tectonic Plates older than " + Form.duration(adjustedIdleDuration.get(), 0)); for (long i : new ArrayList<>(lastUse.keySet())) { @@ -435,7 +434,6 @@ public class Mantle { if (lastUseTime != null && M.ms() - lastUseTime >= finalAdjustedIdleDuration) { toUnload.add(i); Iris.debug("Tectonic Region added to unload"); - IrisEngineSVC.instance.trimActiveAlive.reset(); } }); } @@ -454,53 +452,49 @@ public class Mantle { AtomicInteger i = new AtomicInteger(); unloadLock.lock(); BurstExecutor burst = null; - if (IrisEngineSVC.instance != null) { - try { - KList copy = toUnload.copy(); - if (!disableClear) toUnload.clear(); - burst = MultiBurst.burst.burst(copy.size()); - burst.setMulticore(copy.size() > tectonicLimit); - for (int j = 0; j < copy.size(); j++) { - Long id = copy.get(j); - if (id == null) { - Iris.error("Null id in unloadTectonicPlate at index " + j); - continue; - } - - burst.queue(() -> - hyperLock.withLong(id, () -> { - TectonicPlate m = loadedRegions.get(id); - if (m != null) { - if (m.inUse()) { - Iris.debug("Tectonic Plate was added to unload while in use " + C.DARK_GREEN + m.getX() + " " + m.getZ()); - if (disableClear) toUnload.remove(id); - lastUse.put(id, M.ms()); - return; - } - try { - m.write(fileForRegion(dataFolder, id)); - loadedRegions.remove(id); - lastUse.remove(id); - if (disableClear) toUnload.remove(id); - i.incrementAndGet(); - Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(id) + " " + Cache.keyZ(id)); - IrisEngineSVC.instance.unloadActiveAlive.reset(); - } catch (IOException e) { - Iris.reportError(e); - } - } - })); + try { + KList copy = toUnload.copy(); + if (!disableClear) toUnload.clear(); + burst = MultiBurst.burst.burst(copy.size()); + burst.setMulticore(copy.size() > tectonicLimit); + for (int j = 0; j < copy.size(); j++) { + Long id = copy.get(j); + if (id == null) { + Iris.error("Null id in unloadTectonicPlate at index " + j); + continue; } - burst.complete(); - } catch (Throwable e) { - e.printStackTrace(); - if (burst != null) - burst.complete(); - } finally { - unloadLock.unlock(); - ioTectonicUnload.set(true); + + burst.queue(() -> + hyperLock.withLong(id, () -> { + TectonicPlate m = loadedRegions.get(id); + if (m != null) { + if (m.inUse()) { + Iris.debug("Tectonic Plate was added to unload while in use " + C.DARK_GREEN + m.getX() + " " + m.getZ()); + if (disableClear) toUnload.remove(id); + lastUse.put(id, M.ms()); + return; + } + try { + m.write(fileForRegion(dataFolder, id)); + loadedRegions.remove(id); + lastUse.remove(id); + if (disableClear) toUnload.remove(id); + i.incrementAndGet(); + Iris.debug("Unloaded Tectonic Plate " + C.DARK_GREEN + Cache.keyX(id) + " " + Cache.keyZ(id)); + } catch (IOException e) { + Iris.reportError(e); + } + } + })); } - return i.get(); + burst.complete(); + } catch (Throwable e) { + e.printStackTrace(); + if (burst != null) + burst.complete(); + } finally { + unloadLock.unlock(); + ioTectonicUnload.set(true); } return i.get(); } diff --git a/core/src/main/java/com/volmit/iris/util/math/BlockPosition.java b/core/src/main/java/com/volmit/iris/util/math/BlockPosition.java index 9ca1363f8..767faaa5f 100644 --- a/core/src/main/java/com/volmit/iris/util/math/BlockPosition.java +++ b/core/src/main/java/com/volmit/iris/util/math/BlockPosition.java @@ -19,11 +19,14 @@ package com.volmit.iris.util.math; import lombok.Data; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.block.Block; import java.util.Objects; +import static com.volmit.iris.util.math.RNG.r; + @Data public class BlockPosition { //Magic numbers @@ -43,6 +46,10 @@ public class BlockPosition { this.z = z; } + public static BlockPosition fromLocation(Location loc) { + return new BlockPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); + } + public static long toLong(int x, int y, int z) { long var3 = 0L; var3 |= (x & m4) << m3; @@ -108,4 +115,18 @@ public class BlockPosition { setY(Math.max(i.getY(), getY())); setZ(Math.max(i.getZ(), getZ())); } + + public BlockPosition randomPoint(int radius, int innerRadius) { + int max = radius * radius; + int min = innerRadius * innerRadius; + + while (true) { + int x = r.nextInt(-radius, radius + 1); + int y = r.nextInt(-radius, radius + 1); + int z = r.nextInt(-radius, radius + 1); + double dist = x * x + y * y + z * z; + if (dist < min || dist > max) continue; + return add(x, y, z); + } + } } diff --git a/core/src/main/java/com/volmit/iris/util/parallel/Sync.java b/core/src/main/java/com/volmit/iris/util/parallel/Sync.java new file mode 100644 index 000000000..864c4a42a --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/parallel/Sync.java @@ -0,0 +1,20 @@ +package com.volmit.iris.util.parallel; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; + +public class Sync { + private final AtomicReference> tick = new AtomicReference<>(new CompletableFuture<>()); + + public void cancel(boolean mayInterruptIfRunning) { + tick.getAndSet(new CompletableFuture<>()).cancel(mayInterruptIfRunning); + } + + public CompletableFuture next() { + return tick.get(); + } + + public void advance(T value) { + tick.getAndSet(new CompletableFuture<>()).complete(value); + } +} diff --git a/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/IrisChunkGenerator.java b/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/IrisChunkGenerator.java index 653959edf..28314691f 100644 --- a/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/IrisChunkGenerator.java +++ b/nms/v1_21_R3/src/main/java/com/volmit/iris/core/nms/v1_21_R3/IrisChunkGenerator.java @@ -209,7 +209,7 @@ public class IrisChunkGenerator extends CustomChunkGenerator { @Override public WeightedRandomList getMobsAt(Holder holder, StructureManager structuremanager, MobCategory enumcreaturetype, BlockPos blockposition) { - return delegate.getMobsAt(holder, structuremanager, enumcreaturetype, blockposition); + return WeightedRandomList.create(); } @Override