This commit is contained in:
Daniel Mills 2021-07-31 22:32:14 -04:00
parent 9a11021560
commit 6b97acdb50
12 changed files with 204 additions and 97 deletions

View File

@ -23,6 +23,7 @@ import com.volmit.iris.core.IrisSettings;
import com.volmit.iris.engine.framework.*; import com.volmit.iris.engine.framework.*;
import com.volmit.iris.engine.hunk.Hunk; import com.volmit.iris.engine.hunk.Hunk;
import com.volmit.iris.engine.object.*; 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.KList;
import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.documentation.BlockCoordinates; import com.volmit.iris.util.documentation.BlockCoordinates;
@ -86,9 +87,12 @@ public class IrisEngine extends BlockPopulator implements Engine {
@Getter @Getter
private double maxBiomeDecoratorDensity; private double maxBiomeDecoratorDensity;
private IrisEngineData engineData;
public IrisEngine(EngineTarget target, EngineCompound compound, int index) { public IrisEngine(EngineTarget target, EngineCompound compound, int index) {
Iris.info("Initializing Engine: " + target.getWorld().name() + "/" + target.getDimension().getLoadKey() + " (" + target.getHeight() + " height)"); Iris.info("Initializing Engine: " + target.getWorld().name() + "/" + target.getDimension().getLoadKey() + " (" + target.getHeight() + " height)");
metrics = new EngineMetrics(32); metrics = new EngineMetrics(32);
engineData = new IrisEngineData();
this.target = target; this.target = target;
this.framework = new IrisEngineFramework(this); this.framework = new IrisEngineFramework(this);
worldManager = new IrisWorldManager(this); worldManager = new IrisWorldManager(this);
@ -218,6 +222,16 @@ public class IrisEngine extends BlockPopulator implements Engine {
close(); close();
} }
@Override
public void saveProperties() {
}
@Override
public IrisEngineData getEngineData() {
return engineData;
}
@ChunkCoordinates @ChunkCoordinates
@Override @Override
public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk c) { public void populate(@NotNull World world, @NotNull Random random, @NotNull Chunk c) {

View File

@ -22,11 +22,16 @@ import com.volmit.iris.Iris;
import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.framework.EngineAssignedWorldManager; import com.volmit.iris.engine.framework.EngineAssignedWorldManager;
import com.volmit.iris.engine.object.*; 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.KList;
import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.documentation.ChunkCoordinates; import com.volmit.iris.util.documentation.ChunkCoordinates;
import com.volmit.iris.util.math.M; import com.volmit.iris.util.math.M;
import com.volmit.iris.util.math.RNG; 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.ChronoLatch;
import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.J;
import org.bukkit.Chunk; import org.bukkit.Chunk;
@ -35,9 +40,12 @@ import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntitySpawnEvent; import org.bukkit.event.entity.EntitySpawnEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class IrisWorldManager extends EngineAssignedWorldManager { public class IrisWorldManager extends EngineAssignedWorldManager {
private boolean spawnable; private boolean spawnable;
@ -45,15 +53,17 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
private final KMap<UUID, Long> spawnCooldowns; private final KMap<UUID, Long> spawnCooldowns;
private int entityCount = 0; private int entityCount = 0;
private ChronoLatch cl = new ChronoLatch(5000); private ChronoLatch cl = new ChronoLatch(5000);
private int actuallySpawned = 0;
public IrisWorldManager(Engine engine) { public IrisWorldManager(Engine engine) {
super(engine); super(engine);
spawnCooldowns = new KMap<>(); spawnCooldowns = new KMap<>();
spawnable = true; spawnable = true;
art = J.ar(this::onAsyncTick, 7); art = J.ar(this::onAsyncTick, 20);
} }
private void onAsyncTick() { private void onAsyncTick() {
actuallySpawned = 0;
if (!getEngine().getWorld().hasRealWorld()) { if (!getEngine().getWorld().hasRealWorld()) {
return; return;
} }
@ -68,25 +78,20 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
J.s(() -> entityCount = getEngine().getWorld().realWorld().getEntities().size()); J.s(() -> entityCount = getEngine().getWorld().realWorld().getEntities().size());
} }
int biomeBaseCooldownSeconds = 1; int maxGroups = 3;
int biomeSpawnedCooldownSeconds = 0; int biomeBaseCooldownSeconds = 15;
int biomeNotSpawnedCooldownSeconds = 1;
int actuallySpawned = 0;
for(UUID i : spawnCooldowns.k()) for(UUID i : spawnCooldowns.k())
{ {
if(M.ms() - spawnCooldowns.get(i) > TimeUnit.SECONDS.toMillis(biomeBaseCooldownSeconds)) if(M.ms() - spawnCooldowns.get(i) > TimeUnit.SECONDS.toMillis(biomeBaseCooldownSeconds))
{ {
spawnCooldowns.remove(i); spawnCooldowns.remove(i);
Iris.debug("Biome " + i.toString() + " is off cooldown");
} }
} }
KMap<UUID, KList<Chunk>> data = mapChunkBiomes(); KMap<UUID, KList<Chunk>> data = mapChunkBiomes();
int spawnBuffer = 32; int spawnBuffer = 32;
Iris.debug("Checking " + data.size() + " Loaded Biomes for new spawns...");
for(UUID i : data.k().shuffleCopy(RNG.r)) for(UUID i : data.k().shuffleCopy(RNG.r))
{ {
if(spawnCooldowns.containsKey(i)) if(spawnCooldowns.containsKey(i))
@ -99,19 +104,10 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
break; break;
} }
Iris.debug(" Spawning for " + i.toString());
for(int ig = 0; ig < data.get(i).size() / 8; ig++) for(int ig = 0; ig < data.get(i).size() / 8; ig++)
{ {
boolean g = spawnIn(data.get(i).getRandom(), i); spawnIn(data.get(i).getRandom(), i, maxGroups);
spawnCooldowns.put(i, g ? spawnCooldowns.put(i, M.ms());
(M.ms() + TimeUnit.SECONDS.toMillis(biomeSpawnedCooldownSeconds)) :
(M.ms() + TimeUnit.SECONDS.toMillis(biomeNotSpawnedCooldownSeconds)));
if(g)
{
actuallySpawned++;
}
} }
} }
@ -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) if(c.getEntities().length > 2)
{ {
Iris.debug(" Not spawning in " + id.toString() + " (" + c.getX() + ", " + c.getZ() + "). More than 2 entities in this chunk."); return;
return false;
} }
return new KList<Supplier<Boolean>>(() -> { //@builder
IrisBiome biome = getEngine().getSurfaceBiome(c.getX() << 4, c.getZ() << 4); 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))
{ {
if(i.spawnInChunk(getEngine(), c)) actuallySpawned++;
{ getCooldown(i.getReferenceSpawner()).spawn(getEngine());
Iris.debug(" Spawning Biome Entities in Chunk " + c.getX() + "," + c.getZ() + " Biome ID: " + id);
return true;
} }
} }
return false; private Stream<IrisEntitySpawn> stream(IrisSpawner s)
}, () -> { {
IrisRegion region = getEngine().getRegion(c.getX() << 4, c.getZ() << 4); for(IrisEntitySpawn i : s.getSpawns())
{
i.setReferenceSpawner(s);
}
for(IrisSpawner i : getData().getSpawnerLoader().loadAll(region.getEntitySpawners()).shuffleCopy(RNG.r)) return s.getSpawns().stream();
}
private KList<IrisEntitySpawn> puffen(List<IrisEntitySpawn> types)
{ {
if(i.spawnInChunk(getEngine(), c)) KList<IrisEntitySpawn> 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)
{ {
Iris.debug(" Spawning Region Entities in Chunk " + c.getX() + "," + c.getZ() + " Biome ID: " + id); return i.isValid(getEngine().getWorld().realWorld()) && getCooldown(i).canSpawn(i.getMaximumRate());
return true; }
private IrisEngineSpawnerCooldown getCooldown(IrisSpawner i)
{
IrisEngineData ed = getEngine().getEngineData();
IrisEngineSpawnerCooldown cd = null;
for (IrisEngineSpawnerCooldown j : ed.getSpawnerCooldowns()) {
if (j.getSpawner().equals(i.getLoadKey()))
{
cd = j;
} }
} }
return false; if(cd == null)
}, () -> {
for(IrisSpawner i : getData().getSpawnerLoader().loadAll(getDimension().getEntitySpawners()).shuffleCopy(RNG.r))
{ {
if(i.spawnInChunk(getEngine(), c)) cd = new IrisEngineSpawnerCooldown();
{ cd.setSpawner(i.getLoadKey());
Iris.debug(" Spawning Dimension Entities in Chunk " + c.getX() + "," + c.getZ() + " Biome ID: " + id); cd.setLastSpawn(M.ms() - i.getMaximumRate().getInterval());
return true; ed.getSpawnerCooldowns().add(cd);
}
} }
return false; return cd;
}).getRandom().get();
} }
public KMap<UUID, KList<Chunk>> mapChunkBiomes() public KMap<UUID, KList<Chunk>> mapChunkBiomes()
@ -191,29 +224,6 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
getEngine().getParallax().saveAll(); getEngine().getParallax().saveAll();
} }
private boolean trySpawn(KList<IrisEntitySpawnOverride> 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<IrisEntitySpawn> s, Chunk c, RNG rng) {
for (IrisEntitySpawn i : s) {
i.spawn(getEngine(), c, rng);
}
}
@Override @Override
public void onBlockBreak(BlockBreakEvent e) { public void onBlockBreak(BlockBreakEvent e) {
if(e.getBlock().getWorld().equals(getTarget().getWorld().realWorld()) && getEngine().contains(e.getBlock().getLocation())) if(e.getBlock().getWorld().equals(getTarget().getWorld().realWorld()) && getEngine().contains(e.getBlock().getLocation()))

View File

@ -28,6 +28,7 @@ import com.volmit.iris.engine.data.DataProvider;
import com.volmit.iris.engine.hunk.Hunk; import com.volmit.iris.engine.hunk.Hunk;
import com.volmit.iris.engine.object.*; import com.volmit.iris.engine.object.*;
import com.volmit.iris.engine.object.common.IrisWorld; 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.parallax.ParallaxAccess;
import com.volmit.iris.engine.parallel.MultiBurst; import com.volmit.iris.engine.parallel.MultiBurst;
import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.collection.KList;
@ -403,8 +404,7 @@ public interface Engine extends DataProvider, Fallible, GeneratorAccess, LootPro
void hotloading(); void hotloading();
default void saveProperties() void saveProperties();
{
} IrisEngineData getEngineData();
} }

View File

@ -32,9 +32,11 @@ import lombok.experimental.Accessors;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.Particle; import org.bukkit.Particle;
import org.bukkit.Sound; import org.bukkit.Sound;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.potion.PotionEffect; import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType; import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
@Accessors(chain = true) @Accessors(chain = true)
@NoArgsConstructor @NoArgsConstructor
@ -254,4 +256,40 @@ public class IrisEffect {
true, false, false))); 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));
}
}
}
} }

View File

@ -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.") @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; 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") @Desc("The main gene for a panda if the entity type is a panda")
private Gene pandaMainGene = Gene.NORMAL; private Gene pandaMainGene = Gene.NORMAL;
@ -150,7 +153,6 @@ public class IrisEntity extends IrisRegistrant {
e.setGravity(isGravity()); e.setGravity(isGravity());
e.setInvulnerable(isInvulnerable()); e.setInvulnerable(isInvulnerable());
e.setSilent(isSilent()); e.setSilent(isSilent());
e.setPersistent(true);
int gg = 0; int gg = 0;
for (IrisEntity i : passengers) { for (IrisEntity i : passengers) {
@ -257,6 +259,11 @@ public class IrisEntity extends IrisRegistrant {
m.setAware(isAware()); m.setAware(isAware());
} }
if(spawnEffect != null)
{
spawnEffect.apply(e);
}
return e; return e;
} }

View File

@ -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.RegistryListEntity;
import com.volmit.iris.engine.object.annotations.Required; import com.volmit.iris.engine.object.annotations.Required;
import com.volmit.iris.engine.object.common.IRare; 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.math.RNG;
import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.J;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@ -60,6 +61,7 @@ public class IrisEntitySpawn implements IRare {
@Desc("The max of this entity to spawn") @Desc("The max of this entity to spawn")
private int maxSpawns = 1; private int maxSpawns = 1;
private transient IrisSpawner referenceSpawner;
private final transient AtomicCache<RNG> rng = new AtomicCache<>(); private final transient AtomicCache<RNG> rng = new AtomicCache<>();
private final transient AtomicCache<IrisEntity> ent = new AtomicCache<>(); private final transient AtomicCache<IrisEntity> ent = new AtomicCache<>();
@ -99,8 +101,13 @@ public class IrisEntitySpawn implements IRare {
private Entity spawn100(Engine g, Location at) { private Entity spawn100(Engine g, Location at) {
try { try {
Location l = at.clone().add(0.5, 1, 0.5); Location l = at.clone().add(0.5, 1, 0.5);
Iris.debug(" Spawned " + "Entity<" + getEntity() + "> at " + l.getBlockX() + "," + l.getBlockY() + "," + l.getBlockZ()); Entity e = getRealEntity(g).spawn(g, l, rng.aquire(() -> new RNG(g.getTarget().getWorld().seed() + 4)));
return 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) { } catch (Throwable e) {
Iris.reportError(e); Iris.reportError(e);
Iris.error(" Failed to retrieve real entity @ " + at); Iris.error(" Failed to retrieve real entity @ " + at);

View File

@ -43,7 +43,7 @@ public class IrisRate {
public long getInterval() public long getInterval()
{ {
long t = per.getMilliseconds() / amount; long t = per.getMilliseconds() / (amount == 0 ? 1 : amount);
return Math.abs(t <= 0 ? 1 : t); return Math.abs(t <= 0 ? 1 : t);
} }

View File

@ -18,7 +18,9 @@
package com.volmit.iris.engine.object; package com.volmit.iris.engine.object;
import com.volmit.iris.Iris;
import com.volmit.iris.engine.object.annotations.Desc; import com.volmit.iris.engine.object.annotations.Desc;
import com.volmit.iris.util.math.CDou;
import lombok.Data; import lombok.Data;
import org.bukkit.World; import org.bukkit.World;
@ -33,24 +35,19 @@ public class IrisTimeBlock {
public boolean isWithin(World world) public boolean isWithin(World world)
{ {
return isWithin(world.getTime() / 1000D); return isWithin(((world.getTime() / 1000D)+6)%24);
} }
public boolean isWithin(double hour) public boolean isWithin(double hour)
{ {
if(startHour == endHour) if(startHour == endHour)
{ {
if(endHour == -1) return endHour != -1;
{
return false;
}
return true;
} }
if(startHour > endHour) if(startHour > endHour)
{ {
return !(hour >= startHour && hour <= endHour); return hour >= startHour || hour <= endHour;
} }
return hour >= startHour && hour <= endHour; return hour >= startHour && hour <= endHour;

View File

@ -41,8 +41,8 @@ public enum IrisWeather {
return switch(this) return switch(this)
{ {
case NONE -> world.isClearWeather(); case NONE -> world.isClearWeather();
case DOWNFALL -> world.hasStorm() && world.isThundering(); case DOWNFALL -> world.hasStorm();
case DOWNFALL_WITH_THUNDER -> world.hasStorm(); case DOWNFALL_WITH_THUNDER -> world.hasStorm() && world.isThundering();
case ANY -> true; case ANY -> true;
}; };
} }

View File

@ -18,10 +18,11 @@
package com.volmit.iris.engine.object.engine; package com.volmit.iris.engine.object.engine;
import com.volmit.iris.util.collection.KList;
import lombok.Data; import lombok.Data;
@Data @Data
public class IrisEngineData public class IrisEngineData
{ {
private KList<IrisEngineSpawnerCooldown> spawnerCooldowns = new KList<>();
} }

View File

@ -305,9 +305,13 @@ public interface ProceduralStream<T> extends ProceduralLayer, Interpolated<T> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
default <V> ProceduralStream<V> selectRarity(V... types) { default <V> ProceduralStream<V> selectRarity(V... types) {
KList<V> rarityTypes = new KList<>(); KList<V> rarityTypes = new KList<>();
int totalRarity = 0;
for (V i : types) {
totalRarity += IRare.get(i);
}
for (V i : types) { for (V i : types) {
rarityTypes.addMultiple(i, IRare.get(i)); rarityTypes.addMultiple(i, totalRarity / IRare.get(i));
} }
return new SelectionStream<V>(this, rarityTypes); return new SelectionStream<V>(this, rarityTypes);

View File

@ -472,6 +472,18 @@ public class KList<T> extends ArrayList<T> implements List<T> {
return remove(M.irand(0, last())); 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<String> fromJSONAny(JSONArray oo) { public static KList<String> fromJSONAny(JSONArray oo) {
KList<String> s = new KList<String>(); KList<String> s = new KList<String>();
@ -613,6 +625,23 @@ public class KList<T> extends ArrayList<T> implements List<T> {
return get(M.irand(0, last())); return get(M.irand(0, last()));
} }
public KList<T> popRandom(RNG rng, int c)
{
KList<T> m = new KList<>();
for(int i = 0; i < c; i++)
{
if(isEmpty())
{
break;
}
m.add(popRandom());
}
return m;
}
public T getRandom(RNG rng) { public T getRandom(RNG rng) {
if (isEmpty()) { if (isEmpty()) {
return null; return null;