diff --git a/src/main/java/com/volmit/iris/engine/IrisEngine.java b/src/main/java/com/volmit/iris/engine/IrisEngine.java index a2a473169..c8c277189 100644 --- a/src/main/java/com/volmit/iris/engine/IrisEngine.java +++ b/src/main/java/com/volmit/iris/engine/IrisEngine.java @@ -23,6 +23,7 @@ import com.volmit.iris.core.IrisSettings; import com.volmit.iris.engine.framework.*; import com.volmit.iris.engine.hunk.Hunk; import com.volmit.iris.engine.object.*; +import com.volmit.iris.engine.object.engine.IrisEngineData; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.documentation.BlockCoordinates; @@ -86,9 +87,12 @@ public class IrisEngine extends BlockPopulator implements Engine { @Getter private double maxBiomeDecoratorDensity; + private IrisEngineData engineData; + public IrisEngine(EngineTarget target, EngineCompound compound, int index) { Iris.info("Initializing Engine: " + target.getWorld().name() + "/" + target.getDimension().getLoadKey() + " (" + target.getHeight() + " height)"); metrics = new EngineMetrics(32); + engineData = new IrisEngineData(); this.target = target; this.framework = new IrisEngineFramework(this); worldManager = new IrisWorldManager(this); @@ -218,6 +222,16 @@ public class IrisEngine extends BlockPopulator implements Engine { close(); } + @Override + public void saveProperties() { + + } + + @Override + public IrisEngineData getEngineData() { + return engineData; + } + @ChunkCoordinates @Override public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk c) { diff --git a/src/main/java/com/volmit/iris/engine/IrisWorldManager.java b/src/main/java/com/volmit/iris/engine/IrisWorldManager.java index 26fd39f6a..f6784e434 100644 --- a/src/main/java/com/volmit/iris/engine/IrisWorldManager.java +++ b/src/main/java/com/volmit/iris/engine/IrisWorldManager.java @@ -22,11 +22,16 @@ import com.volmit.iris.Iris; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.framework.EngineAssignedWorldManager; import com.volmit.iris.engine.object.*; +import com.volmit.iris.engine.object.common.IRare; +import com.volmit.iris.engine.object.engine.IrisEngineData; +import com.volmit.iris.engine.object.engine.IrisEngineSpawnerCooldown; +import com.volmit.iris.engine.stream.convert.SelectionStream; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.documentation.ChunkCoordinates; import com.volmit.iris.util.math.M; import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.reflect.V; import com.volmit.iris.util.scheduling.ChronoLatch; import com.volmit.iris.util.scheduling.J; import org.bukkit.Chunk; @@ -35,9 +40,12 @@ import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.entity.EntitySpawnEvent; import org.bukkit.inventory.ItemStack; +import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class IrisWorldManager extends EngineAssignedWorldManager { private boolean spawnable; @@ -45,15 +53,17 @@ public class IrisWorldManager extends EngineAssignedWorldManager { private final KMap spawnCooldowns; private int entityCount = 0; private ChronoLatch cl = new ChronoLatch(5000); + private int actuallySpawned = 0; public IrisWorldManager(Engine engine) { super(engine); spawnCooldowns = new KMap<>(); spawnable = true; - art = J.ar(this::onAsyncTick, 7); + art = J.ar(this::onAsyncTick, 20); } private void onAsyncTick() { + actuallySpawned = 0; if (!getEngine().getWorld().hasRealWorld()) { return; } @@ -68,25 +78,20 @@ public class IrisWorldManager extends EngineAssignedWorldManager { J.s(() -> entityCount = getEngine().getWorld().realWorld().getEntities().size()); } - int biomeBaseCooldownSeconds = 1; - int biomeSpawnedCooldownSeconds = 0; - int biomeNotSpawnedCooldownSeconds = 1; - int actuallySpawned = 0; + int maxGroups = 3; + int biomeBaseCooldownSeconds = 15; for(UUID i : spawnCooldowns.k()) { if(M.ms() - spawnCooldowns.get(i) > TimeUnit.SECONDS.toMillis(biomeBaseCooldownSeconds)) { spawnCooldowns.remove(i); - Iris.debug("Biome " + i.toString() + " is off cooldown"); } } KMap> data = mapChunkBiomes(); int spawnBuffer = 32; - Iris.debug("Checking " + data.size() + " Loaded Biomes for new spawns..."); - for(UUID i : data.k().shuffleCopy(RNG.r)) { if(spawnCooldowns.containsKey(i)) @@ -99,19 +104,10 @@ public class IrisWorldManager extends EngineAssignedWorldManager { break; } - Iris.debug(" Spawning for " + i.toString()); - for(int ig = 0; ig < data.get(i).size() / 8; ig++) { - boolean g = spawnIn(data.get(i).getRandom(), i); - spawnCooldowns.put(i, g ? - (M.ms() + TimeUnit.SECONDS.toMillis(biomeSpawnedCooldownSeconds)) : - (M.ms() + TimeUnit.SECONDS.toMillis(biomeNotSpawnedCooldownSeconds))); - - if(g) - { - actuallySpawned++; - } + spawnIn(data.get(i).getRandom(), i, maxGroups); + spawnCooldowns.put(i, M.ms()); } } @@ -121,51 +117,88 @@ public class IrisWorldManager extends EngineAssignedWorldManager { } } - private boolean spawnIn(Chunk c, UUID id) { + private void spawnIn(Chunk c, UUID id, int max) { if(c.getEntities().length > 2) { - Iris.debug(" Not spawning in " + id.toString() + " (" + c.getX() + ", " + c.getZ() + "). More than 2 entities in this chunk."); - return false; + return; } - return new KList>(() -> { - IrisBiome biome = getEngine().getSurfaceBiome(c.getX() << 4, c.getZ() << 4); + //@builder + puffen(Stream.concat(getData().getSpawnerLoader().loadAll(getDimension().getEntitySpawners()) + .shuffleCopy(RNG.r).stream().filter(this::canSpawn) + .flatMap(this::stream), + Stream.concat(getData().getSpawnerLoader() + .loadAll(getEngine().getRegion(c.getX() << 4, c.getZ() << 4).getEntitySpawners()) + .shuffleCopy(RNG.r).stream().filter(this::canSpawn) + .flatMap(this::stream), + getData().getSpawnerLoader() + .loadAll(getEngine().getSurfaceBiome(c.getX() << 4, c.getZ() << 4).getEntitySpawners()) + .shuffleCopy(RNG.r).stream().filter(this::canSpawn) + .flatMap(this::stream))) + .collect(Collectors.toList())) + .popRandom(RNG.r, max).forEach((i) -> spawn(c, id, i)); + //@done + } - for(IrisSpawner i : getData().getSpawnerLoader().loadAll(biome.getEntitySpawners()).shuffleCopy(RNG.r)) + private void spawn(Chunk c, UUID id, IrisEntitySpawn i) { + if(i.spawn(getEngine(), c, RNG.r)) + { + actuallySpawned++; + getCooldown(i.getReferenceSpawner()).spawn(getEngine()); + } + } + + private Stream stream(IrisSpawner s) + { + for(IrisEntitySpawn i : s.getSpawns()) + { + i.setReferenceSpawner(s); + } + + return s.getSpawns().stream(); + } + + private KList puffen(List types) + { + KList rarityTypes = new KList<>(); + int totalRarity = 0; + for (IrisEntitySpawn i : types) { + totalRarity += IRare.get(i); + } + + for (IrisEntitySpawn i : types) { + rarityTypes.addMultiple(i, totalRarity / IRare.get(i)); + } + + return rarityTypes; + } + + public boolean canSpawn(IrisSpawner i) + { + return i.isValid(getEngine().getWorld().realWorld()) && getCooldown(i).canSpawn(i.getMaximumRate()); + } + + private IrisEngineSpawnerCooldown getCooldown(IrisSpawner i) + { + IrisEngineData ed = getEngine().getEngineData(); + IrisEngineSpawnerCooldown cd = null; + + for (IrisEngineSpawnerCooldown j : ed.getSpawnerCooldowns()) { + if (j.getSpawner().equals(i.getLoadKey())) { - if(i.spawnInChunk(getEngine(), c)) - { - Iris.debug(" Spawning Biome Entities in Chunk " + c.getX() + "," + c.getZ() + " Biome ID: " + id); - return true; - } + cd = j; } + } - return false; - }, () -> { - IrisRegion region = getEngine().getRegion(c.getX() << 4, c.getZ() << 4); + if(cd == null) + { + cd = new IrisEngineSpawnerCooldown(); + cd.setSpawner(i.getLoadKey()); + cd.setLastSpawn(M.ms() - i.getMaximumRate().getInterval()); + ed.getSpawnerCooldowns().add(cd); + } - for(IrisSpawner i : getData().getSpawnerLoader().loadAll(region.getEntitySpawners()).shuffleCopy(RNG.r)) - { - if(i.spawnInChunk(getEngine(), c)) - { - Iris.debug(" Spawning Region Entities in Chunk " + c.getX() + "," + c.getZ() + " Biome ID: " + id); - return true; - } - } - - return false; - }, () -> { - for(IrisSpawner i : getData().getSpawnerLoader().loadAll(getDimension().getEntitySpawners()).shuffleCopy(RNG.r)) - { - if(i.spawnInChunk(getEngine(), c)) - { - Iris.debug(" Spawning Dimension Entities in Chunk " + c.getX() + "," + c.getZ() + " Biome ID: " + id); - return true; - } - } - - return false; - }).getRandom().get(); + return cd; } public KMap> mapChunkBiomes() @@ -191,29 +224,6 @@ public class IrisWorldManager extends EngineAssignedWorldManager { getEngine().getParallax().saveAll(); } - private boolean trySpawn(KList s, EntitySpawnEvent e) { - for (IrisEntitySpawnOverride i : s) { - spawnable = false; - - if (i.on(getEngine(), e.getLocation(), e.getEntityType(), e) != null) { - e.setCancelled(true); - e.getEntity().remove(); - return true; - } else { - spawnable = true; - } - } - - return false; - } - - @ChunkCoordinates - private void trySpawn(KList s, Chunk c, RNG rng) { - for (IrisEntitySpawn i : s) { - i.spawn(getEngine(), c, rng); - } - } - @Override public void onBlockBreak(BlockBreakEvent e) { if(e.getBlock().getWorld().equals(getTarget().getWorld().realWorld()) && getEngine().contains(e.getBlock().getLocation())) diff --git a/src/main/java/com/volmit/iris/engine/framework/Engine.java b/src/main/java/com/volmit/iris/engine/framework/Engine.java index aebca9521..46f6cba59 100644 --- a/src/main/java/com/volmit/iris/engine/framework/Engine.java +++ b/src/main/java/com/volmit/iris/engine/framework/Engine.java @@ -28,6 +28,7 @@ import com.volmit.iris.engine.data.DataProvider; import com.volmit.iris.engine.hunk.Hunk; import com.volmit.iris.engine.object.*; import com.volmit.iris.engine.object.common.IrisWorld; +import com.volmit.iris.engine.object.engine.IrisEngineData; import com.volmit.iris.engine.parallax.ParallaxAccess; import com.volmit.iris.engine.parallel.MultiBurst; import com.volmit.iris.util.collection.KList; @@ -403,8 +404,7 @@ public interface Engine extends DataProvider, Fallible, GeneratorAccess, LootPro void hotloading(); - default void saveProperties() - { + void saveProperties(); - } + IrisEngineData getEngineData(); } diff --git a/src/main/java/com/volmit/iris/engine/object/IrisEffect.java b/src/main/java/com/volmit/iris/engine/object/IrisEffect.java index 015d7dace..1667a5586 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisEffect.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisEffect.java @@ -32,9 +32,11 @@ import lombok.experimental.Accessors; import org.bukkit.Location; import org.bukkit.Particle; import org.bukkit.Sound; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffectType; +import org.bukkit.util.Vector; @Accessors(chain = true) @NoArgsConstructor @@ -254,4 +256,40 @@ public class IrisEffect { true, false, false))); } } + + public void apply(Entity p) { + if (!canTick()) { + return; + } + + if (RNG.r.nextInt(chance) != 0) { + return; + } + + if (sound != null) { + Location part = p.getLocation().clone().add(RNG.r.i(-soundDistance, soundDistance), RNG.r.i(-soundDistance, soundDistance), RNG.r.i(-soundDistance, soundDistance)); + + J.s(() -> p.getWorld().playSound(part, getSound(), (float) volume, (float) RNG.r.d(minPitch, maxPitch))); + } + + if (particleEffect != null) { + Location part = p.getLocation().clone().add(0, 0.25, 0).add(new Vector(1,1,1).multiply(RNG.r.d())).subtract(new Vector(1,1,1).multiply(RNG.r.d())); + part.add(RNG.r.d(), 0, RNG.r.d()); + if (extra != 0) { + J.s(() -> p.getWorld().spawnParticle(particleEffect, part.getX(), part.getY() + RNG.r.i(particleOffset), + part.getZ(), + particleCount, + randomAltX ? RNG.r.d(-particleAltX, particleAltX) : particleAltX, + randomAltY ? RNG.r.d(-particleAltY, particleAltY) : particleAltY, + randomAltZ ? RNG.r.d(-particleAltZ, particleAltZ) : particleAltZ, + extra)); + } else { + J.s(() -> p.getWorld().spawnParticle(particleEffect, part.getX(), part.getY() + RNG.r.i(particleOffset), part.getZ(), + particleCount, + randomAltX ? RNG.r.d(-particleAltX, particleAltX) : particleAltX, + randomAltY ? RNG.r.d(-particleAltY, particleAltY) : particleAltY, + randomAltZ ? RNG.r.d(-particleAltZ, particleAltZ) : particleAltZ)); + } + } + } } diff --git a/src/main/java/com/volmit/iris/engine/object/IrisEntity.java b/src/main/java/com/volmit/iris/engine/object/IrisEntity.java index 04e26a857..5d370fcfe 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisEntity.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisEntity.java @@ -129,6 +129,9 @@ public class IrisEntity extends IrisRegistrant { @Desc("If specified, this entity will be leashed by this entity. I.e. THIS ENTITY Leashed by SPECIFIED. This has no effect on EnderDragons, Withers, Players, or Bats.Non-living entities excluding leashes will not persist as leashholders.") private IrisEntity leashHolder = null; + @Desc("If specified, this entity will spawn with an effect") + private IrisEffect spawnEffect = null; + @Desc("The main gene for a panda if the entity type is a panda") private Gene pandaMainGene = Gene.NORMAL; @@ -150,7 +153,6 @@ public class IrisEntity extends IrisRegistrant { e.setGravity(isGravity()); e.setInvulnerable(isInvulnerable()); e.setSilent(isSilent()); - e.setPersistent(true); int gg = 0; for (IrisEntity i : passengers) { @@ -257,6 +259,11 @@ public class IrisEntity extends IrisRegistrant { m.setAware(isAware()); } + if(spawnEffect != null) + { + spawnEffect.apply(e); + } + return e; } diff --git a/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java b/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java index 4094d39a9..1a9a4afcf 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisEntitySpawn.java @@ -27,6 +27,7 @@ import com.volmit.iris.engine.object.annotations.MinNumber; import com.volmit.iris.engine.object.annotations.RegistryListEntity; import com.volmit.iris.engine.object.annotations.Required; import com.volmit.iris.engine.object.common.IRare; +import com.volmit.iris.util.format.C; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.scheduling.J; import lombok.AllArgsConstructor; @@ -60,6 +61,7 @@ public class IrisEntitySpawn implements IRare { @Desc("The max of this entity to spawn") private int maxSpawns = 1; + private transient IrisSpawner referenceSpawner; private final transient AtomicCache rng = new AtomicCache<>(); private final transient AtomicCache ent = new AtomicCache<>(); @@ -99,8 +101,13 @@ public class IrisEntitySpawn implements IRare { private Entity spawn100(Engine g, Location at) { try { Location l = at.clone().add(0.5, 1, 0.5); - Iris.debug(" Spawned " + "Entity<" + getEntity() + "> at " + l.getBlockX() + "," + l.getBlockY() + "," + l.getBlockZ()); - return getRealEntity(g).spawn(g, l, rng.aquire(() -> new RNG(g.getTarget().getWorld().seed() + 4))); + Entity e = getRealEntity(g).spawn(g, l, rng.aquire(() -> new RNG(g.getTarget().getWorld().seed() + 4))); + 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()); + } + + return e; } catch (Throwable e) { Iris.reportError(e); Iris.error(" Failed to retrieve real entity @ " + at); diff --git a/src/main/java/com/volmit/iris/engine/object/IrisRate.java b/src/main/java/com/volmit/iris/engine/object/IrisRate.java index b263a3092..439591f47 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisRate.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisRate.java @@ -43,7 +43,7 @@ public class IrisRate { public long getInterval() { - long t = per.getMilliseconds() / amount; + long t = per.getMilliseconds() / (amount == 0 ? 1 : amount); return Math.abs(t <= 0 ? 1 : t); } diff --git a/src/main/java/com/volmit/iris/engine/object/IrisTimeBlock.java b/src/main/java/com/volmit/iris/engine/object/IrisTimeBlock.java index 0cb13fe59..21e96331f 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisTimeBlock.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisTimeBlock.java @@ -18,7 +18,9 @@ package com.volmit.iris.engine.object; +import com.volmit.iris.Iris; import com.volmit.iris.engine.object.annotations.Desc; +import com.volmit.iris.util.math.CDou; import lombok.Data; import org.bukkit.World; @@ -33,24 +35,19 @@ public class IrisTimeBlock { public boolean isWithin(World world) { - return isWithin(world.getTime() / 1000D); + return isWithin(((world.getTime() / 1000D)+6)%24); } public boolean isWithin(double hour) { if(startHour == endHour) { - if(endHour == -1) - { - return false; - } - - return true; + return endHour != -1; } if(startHour > endHour) { - return !(hour >= startHour && hour <= endHour); + return hour >= startHour || hour <= endHour; } return hour >= startHour && hour <= endHour; diff --git a/src/main/java/com/volmit/iris/engine/object/IrisWeather.java b/src/main/java/com/volmit/iris/engine/object/IrisWeather.java index 428c02049..81c1e07aa 100644 --- a/src/main/java/com/volmit/iris/engine/object/IrisWeather.java +++ b/src/main/java/com/volmit/iris/engine/object/IrisWeather.java @@ -41,8 +41,8 @@ public enum IrisWeather { return switch(this) { case NONE -> world.isClearWeather(); - case DOWNFALL -> world.hasStorm() && world.isThundering(); - case DOWNFALL_WITH_THUNDER -> world.hasStorm(); + case DOWNFALL -> world.hasStorm(); + case DOWNFALL_WITH_THUNDER -> world.hasStorm() && world.isThundering(); case ANY -> true; }; } diff --git a/src/main/java/com/volmit/iris/engine/object/engine/IrisEngineData.java b/src/main/java/com/volmit/iris/engine/object/engine/IrisEngineData.java index 00bcc39f5..7420c646e 100644 --- a/src/main/java/com/volmit/iris/engine/object/engine/IrisEngineData.java +++ b/src/main/java/com/volmit/iris/engine/object/engine/IrisEngineData.java @@ -18,10 +18,11 @@ package com.volmit.iris.engine.object.engine; +import com.volmit.iris.util.collection.KList; import lombok.Data; @Data public class IrisEngineData { - + private KList spawnerCooldowns = new KList<>(); } diff --git a/src/main/java/com/volmit/iris/engine/stream/ProceduralStream.java b/src/main/java/com/volmit/iris/engine/stream/ProceduralStream.java index 3ac33df9e..318fb8fb1 100644 --- a/src/main/java/com/volmit/iris/engine/stream/ProceduralStream.java +++ b/src/main/java/com/volmit/iris/engine/stream/ProceduralStream.java @@ -305,9 +305,13 @@ public interface ProceduralStream extends ProceduralLayer, Interpolated { @SuppressWarnings("unchecked") default ProceduralStream selectRarity(V... types) { KList rarityTypes = new KList<>(); + int totalRarity = 0; + for (V i : types) { + totalRarity += IRare.get(i); + } for (V i : types) { - rarityTypes.addMultiple(i, IRare.get(i)); + rarityTypes.addMultiple(i, totalRarity / IRare.get(i)); } return new SelectionStream(this, rarityTypes); diff --git a/src/main/java/com/volmit/iris/util/collection/KList.java b/src/main/java/com/volmit/iris/util/collection/KList.java index e6d97839b..d850b1572 100644 --- a/src/main/java/com/volmit/iris/util/collection/KList.java +++ b/src/main/java/com/volmit/iris/util/collection/KList.java @@ -472,6 +472,18 @@ public class KList extends ArrayList implements List { return remove(M.irand(0, last())); } + public T popRandom(RNG rng) { + if (isEmpty()) { + return null; + } + + if (size() == 1) { + return pop(); + } + + return remove(rng.i(0, last())); + } + public static KList fromJSONAny(JSONArray oo) { KList s = new KList(); @@ -613,6 +625,23 @@ public class KList extends ArrayList implements List { return get(M.irand(0, last())); } + public KList popRandom(RNG rng, int c) + { + KList m = new KList<>(); + + for(int i = 0; i < c; i++) + { + if(isEmpty()) + { + break; + } + + m.add(popRandom()); + } + + return m; + } + public T getRandom(RNG rng) { if (isEmpty()) { return null;