Iris/src/main/java/com/volmit/iris/gen/ContextualTerrainProvider.java
Daniel Mills 27694036a5 .
2020-10-24 03:44:45 -04:00

486 lines
11 KiB
Java

package com.volmit.iris.gen;
import java.util.Random;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.GameMode;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.event.block.BlockDropItemEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.entity.EntitySpawnEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import com.volmit.iris.Iris;
import com.volmit.iris.gen.atomics.AtomicCache;
import com.volmit.iris.gen.atomics.AtomicMulticache;
import com.volmit.iris.gen.scaffold.GeneratedChunk;
import com.volmit.iris.gen.scaffold.IrisContext;
import com.volmit.iris.gen.scaffold.IrisMetrics;
import com.volmit.iris.gen.scaffold.Provisioned;
import com.volmit.iris.gen.scaffold.TerrainChunk;
import com.volmit.iris.gen.scaffold.TerrainProvider;
import com.volmit.iris.gen.scaffold.TerrainTarget;
import com.volmit.iris.gen.v2.DataProvider;
import com.volmit.iris.manager.IrisDataManager;
import com.volmit.iris.noise.CNG;
import com.volmit.iris.object.IrisBiome;
import com.volmit.iris.object.IrisDimension;
import com.volmit.iris.object.IrisGenerator;
import com.volmit.iris.object.IrisObject;
import com.volmit.iris.object.IrisRegion;
import com.volmit.iris.object.IrisStructure;
import com.volmit.iris.util.B;
import com.volmit.iris.util.BlockPosition;
import com.volmit.iris.util.C;
import com.volmit.iris.util.ChronoLatch;
import com.volmit.iris.util.J;
import com.volmit.iris.util.KList;
import com.volmit.iris.util.KSet;
import com.volmit.iris.util.M;
import com.volmit.iris.util.RNG;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
public abstract class ContextualTerrainProvider implements TerrainProvider, Listener, DataProvider
{
private Provisioned provisioner;
private KList<BlockPosition> noLoot;
private BlockPosition allowLoot;
private AtomicMulticache cache;
private IrisDataManager data;
private boolean failing;
private int task;
private boolean dev;
private volatile boolean initialized;
private RNG masterRandom;
private ChronoLatch perSecond;
private ChronoLatch tickLatch;
private ChronoLatch pushLatch;
private AtomicCache<IrisDimension> dimCache;
private IrisMetrics metrics;
private int generated;
private int ticks;
private long hlast;
private boolean fastPregen = false;
private boolean pregenDone;
private volatile boolean hotloadable = false;
private final TerrainTarget target;
private KSet<String> warnings;
public ContextualTerrainProvider(TerrainTarget target)
{
metrics = new IrisMetrics(256);
warnings = new KSet<>();
this.target = target;
pushLatch = new ChronoLatch(3000);
tickLatch = new ChronoLatch(650);
perSecond = new ChronoLatch(1000);
hlast = M.ms();
cache = new AtomicMulticache((IrisTerrainProvider) this);
CNG.creates = 0;
generated = 0;
ticks = 0;
task = -1;
initialized = false;
allowLoot = new BlockPosition(0, 0, 0);
failing = false;
pregenDone = false;
dimCache = new AtomicCache<>();
dev = false;
noLoot = new KList<>(1285);
}
public void warn(String warning)
{
if(!isDev())
{
return;
}
warnings.add(warning);
}
protected abstract GeneratedChunk onGenerate(RNG masterRandom, int x, int z, TerrainChunk chunk);
protected abstract void onInit(RNG masterRandom);
protected abstract void onTick(int ticks);
protected abstract void onClose();
protected abstract void onFailure(Throwable e);
protected abstract void onChunkLoaded(Chunk c);
protected abstract void onChunkUnloaded(Chunk c);
protected abstract void onPlayerJoin(Player p);
protected abstract void onPlayerLeft(Player p);
public IrisRegion loadRegion(String i)
{
return getData().getRegionLoader().load(i);
}
public IrisBiome loadBiome(String i)
{
return getData().getBiomeLoader().load(i);
}
public IrisStructure loadStructure(String i)
{
return getData().getStructureLoader().load(i);
}
public IrisObject loadObject(String i)
{
return getData().getObjectLoader().load(i);
}
public IrisDimension loadDimension(String i)
{
return getDimCache().aquire(() -> (getData() == null ? Iris.globaldata : getData()).getDimensionLoader().load(i));
}
public IrisGenerator loadGenerator(String i)
{
return getData().getGeneratorLoader().load(i);
}
public IrisDataManager getData()
{
return isDev() ? Iris.globaldata : data;
}
private void init(RNG rng)
{
if(initialized)
{
return;
}
setInitialized(true);
setData(new IrisDataManager(getTarget().getFolder()));
setMasterRandom(new RNG(getTarget().getSeed()));
setMetrics(new IrisMetrics(128));
setTask(Bukkit.getScheduler().scheduleSyncRepeatingTask(Iris.instance, this::tick, 0, 0));
Bukkit.getServer().getPluginManager().registerEvents(this, Iris.instance);
onInit(masterRandom);
setHotloadable(true);
}
private void tick()
{
if(isDev())
{
if(getPerSecond().flip())
{
if(getGenerated() > (isFastPregen() ? 1950 : 770))
{
setPregenDone(true);
}
if(isPregenDone())
{
getMetrics().getPerSecond().put(generated);
setGenerated(0);
}
doCheckHotload();
if(getNoLoot().size() > 1024)
{
// noinspection ListRemoveInLoop
for(int i = 0; i < 64; i++)
{
getNoLoot().remove(0);
}
}
}
}
else
{
setPregenDone(true);
setFastPregen(false);
}
onTick(ticks++);
}
@EventHandler(priority = EventPriority.MONITOR)
public void on(EntitySpawnEvent e)
{
onSpawn(e);
}
protected abstract void onSpawn(EntitySpawnEvent e);
@EventHandler(priority = EventPriority.MONITOR)
public void on(BlockBreakEvent e)
{
if(!getTarget().isWorld(e.getBlock().getWorld()))
{
return;
}
BlockPosition bp = new BlockPosition(e.getBlock().getX(), e.getBlock().getY(), e.getBlock().getZ());
if(!noLoot.contains(bp))
{
noLoot.add(bp);
if(e.isDropItems() && e.getPlayer().getGameMode().equals(GameMode.SURVIVAL))
{
allowLoot = new BlockPosition(e.getBlock().getX(), e.getBlock().getY(), e.getBlock().getZ());
}
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void on(BlockPlaceEvent e)
{
if(!getTarget().isWorld(e.getBlock().getWorld()))
{
return;
}
getNoLoot().addIfMissing(new BlockPosition(e.getBlock().getX(), e.getBlock().getY(), e.getBlock().getZ()));
}
@EventHandler(priority = EventPriority.MONITOR)
public void on(BlockDropItemEvent e)
{
if(!getTarget().isWorld(e.getBlock().getWorld()))
{
return;
}
BlockPosition bp = new BlockPosition(e.getBlock().getX(), e.getBlock().getY(), e.getBlock().getZ());
if(noLoot.contains(bp) && !allowLoot.equals(bp))
{
return;
}
handleDrops(e);
}
protected abstract void handleDrops(BlockDropItemEvent e);
@EventHandler(priority = EventPriority.MONITOR)
public void on(PlayerTeleportEvent e)
{
if(getTarget().isWorld(e.getFrom().getWorld()) && !getTarget().isWorld(e.getTo().getWorld()))
{
tick();
onPlayerLeft(e.getPlayer());
}
if(!getTarget().isWorld(e.getFrom().getWorld()) && getTarget().isWorld(e.getTo().getWorld()))
{
tick();
onPlayerJoin(e.getPlayer());
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void on(PlayerQuitEvent e)
{
if(getTarget().isWorld(e.getPlayer().getWorld()))
{
tick();
onPlayerLeft(e.getPlayer());
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void on(PlayerJoinEvent e)
{
if(getTarget().isWorld(e.getPlayer().getWorld()))
{
tick();
onPlayerJoin(e.getPlayer());
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void on(ChunkLoadEvent e)
{
if(getTarget().isWorld(e.getWorld()))
{
tick();
onChunkLoaded(e.getChunk());
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void on(ChunkUnloadEvent e)
{
if(getTarget().isWorld(e.getWorld()))
{
tick();
onChunkUnloaded(e.getChunk());
}
}
@EventHandler(priority = EventPriority.MONITOR)
public void on(WorldUnloadEvent e)
{
if(getTarget().isWorld(e.getWorld()))
{
close();
}
}
public void close()
{
if(!isDev())
{
return;
}
getNoLoot().clear();
getNoLoot().trimToSize();
HandlerList.unregisterAll(this);
Bukkit.getScheduler().cancelTask(getTask());
onClose();
}
protected void generateFailure(TerrainChunk chunk)
{
for(int i = 0; i < 16; i++)
{
for(int j = 0; j < 16; j++)
{
int h = 0;
if(j == i || j + i == 16)
{
chunk.setBlock(i, h, j, B.getBlockData("RED_TERRACOTTA").getType());
}
else
{
chunk.setBlock(i, h, j, B.getBlockData("BLACK_TERRACOTTA").getType());
}
}
}
}
@Override
public GeneratedChunk generate(Random no, int x, int z, TerrainChunk terrain)
{
setHotloadable(false);
if(!isDev())
{
setPregenDone(true);
setFastPregen(false);
}
if(failing)
{
generateFailure(terrain);
return GeneratedChunk.builder().build();
}
try
{
RNG random = new RNG(getTarget().getSeed());
init(random.nextParallelRNG(0));
GeneratedChunk c = onGenerate(random, x, z, terrain);
generated++;
long hits = CNG.hits;
CNG.hits = 0;
Iris.instance.hit(hits);
setHotloadable(true);
return c;
}
catch(Throwable e)
{
fail(e);
}
setHotloadable(true);
generateFailure(terrain);
return GeneratedChunk.builder().build();
}
private void doCheckHotload()
{
if(!isHotloadable())
{
return;
}
if(M.ms() - getHlast() < 1000)
{
return;
}
if(getPushLatch().flip())
{
Iris.hotloader.check((IrisContext) this);
IrisContext.pushContext((IrisContext) this);
}
}
public void onHotload()
{
setHlast(M.ms());
getDimCache().reset();
}
protected void fail(Throwable e)
{
if(isFailing())
{
return;
}
setFailing(true);
e.printStackTrace();
J.a(() ->
{
J.sleep(1000);
Iris.error("---------------------------------------------------------------------------------------------------------");
e.printStackTrace();
Iris.error("---------------------------------------------------------------------------------------------------------");
Iris.error("ERROR! Failed to generate chunk! Iris has entered a failed state!");
Iris.error("---------------------------------------------------------------------------------------------------------");
for(Player i : getTarget().getPlayers())
{
Iris.instance.imsg(i, C.DARK_RED + "Iris Generator has crashed!");
Iris.instance.imsg(i, C.RED + "- Check the console for the error.");
Iris.instance.imsg(i, C.RED + "- To Regen, use /iris std open <dim>");
}
});
onFailure(e);
}
@Override
public boolean isParallelCapable()
{
return true;
}
}