1357 lines
35 KiB
Java
1357 lines
35 KiB
Java
package org.foo_projects.sofar.Sedimentology;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Random;
|
|
import java.util.Map;
|
|
|
|
import org.bukkit.block.Biome;
|
|
import org.bukkit.block.Block;
|
|
import org.bukkit.block.BlockFace;
|
|
import org.bukkit.command.Command;
|
|
import org.bukkit.command.CommandExecutor;
|
|
import org.bukkit.command.CommandSender;
|
|
import org.bukkit.entity.Player;
|
|
import org.bukkit.event.EventHandler;
|
|
import org.bukkit.event.Listener;
|
|
import org.bukkit.event.world.ChunkLoadEvent;
|
|
import org.bukkit.event.world.ChunkUnloadEvent;
|
|
import org.bukkit.plugin.java.JavaPlugin;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.Chunk;
|
|
import org.bukkit.Location;
|
|
import org.bukkit.Material;
|
|
import org.bukkit.Sound;
|
|
import org.bukkit.World;
|
|
import org.bukkit.World.Environment;
|
|
import org.bukkit.scheduler.BukkitScheduler;
|
|
|
|
import com.bekvon.bukkit.residence.Residence;
|
|
import com.bekvon.bukkit.residence.protection.ClaimedResidence;
|
|
|
|
import com.massivecraft.factions.entity.BoardColls;
|
|
import com.massivecraft.factions.entity.Faction;
|
|
import com.massivecraft.mcore.ps.PS;
|
|
|
|
import com.palmergames.bukkit.towny.object.TownyUniverse;
|
|
|
|
import com.sk89q.worldguard.bukkit.WGBukkit;
|
|
import com.sk89q.worldguard.protection.ApplicableRegionSet;
|
|
import com.sk89q.worldguard.protection.managers.RegionManager;
|
|
|
|
/*
|
|
* Sedimentology concepts
|
|
*
|
|
* Erosion & deposition go hand in hand - at higher elevation (>64) erosion dominates, and at
|
|
* lower elevations, sedimentation dominates.
|
|
*
|
|
* Decay cycles affect rocks - stone breaks into gravel, into sand, into clay. Each step takes longer
|
|
*
|
|
* Transports are wind, water, ice.
|
|
*
|
|
* Wind can displace sand - create dunes.
|
|
* Water displaces sand and clay
|
|
* Ice displaces all blocks
|
|
*
|
|
* Thicker snow packs behave like ice: crunch rocks into gravel
|
|
*
|
|
* Blocks do not disappear, the volume of blocks remains equal througout the process
|
|
*
|
|
* slope affects erosion - a higher slope means more erosion
|
|
*
|
|
* deposition happens where the slope reduces - a lower slope means more deposition
|
|
*
|
|
*/
|
|
|
|
public final class Sedimentology extends JavaPlugin {
|
|
private List<SedWorld> sedWorldList;
|
|
public Random rnd = new Random();
|
|
|
|
private long stat_considered;
|
|
private long stat_displaced;
|
|
private long stat_degraded;
|
|
private long stat_errors;
|
|
private long stat_protected;
|
|
private long stat_ignored_edge;
|
|
private long stat_ignored_type;
|
|
private long stat_ignored_storm;
|
|
private long stat_ignored_vegetation;
|
|
private long stat_ignored_resistance;
|
|
private long stat_ignored_water;
|
|
private long stat_ignored_wave;
|
|
private long stat_ignored_sand;
|
|
private long stat_ignored_hardness;
|
|
private long stat_ignored_lockedin;
|
|
private long stat_ignored_rate;
|
|
private int stat_lastx;
|
|
private int stat_lasty;
|
|
private int stat_lastz;
|
|
|
|
private enum sedType {
|
|
SED_BLOCK,
|
|
SED_SNOW;
|
|
}
|
|
|
|
private long conf_blocks = 10;
|
|
private long conf_snowblocks = 100;
|
|
private long conf_ticks = 1;
|
|
private boolean conf_protect = true;
|
|
private boolean conf_compensate = true;
|
|
|
|
private boolean have_factions = false;
|
|
private boolean have_factions_old = false;
|
|
private boolean have_towny = false;
|
|
private boolean have_worldguard = false;
|
|
private boolean have_residence = false;
|
|
|
|
@SuppressWarnings("deprecation")
|
|
private byte getData(Block block)
|
|
{
|
|
return block.getData();
|
|
}
|
|
|
|
@SuppressWarnings("deprecation")
|
|
private void setData(Block block, byte data)
|
|
{
|
|
block.setData(data);
|
|
}
|
|
|
|
private boolean enableWorld(String worldName)
|
|
{
|
|
getLogger().info("enabling world \"" + worldName + "\"");
|
|
|
|
if (org.bukkit.Bukkit.getWorld(worldName) == null) {
|
|
getLogger().info("No such world");
|
|
return false;
|
|
}
|
|
if (!sedWorldList.isEmpty()) {
|
|
for (SedWorld ww: sedWorldList) {
|
|
if (org.bukkit.Bukkit.getWorld(worldName).getName().equals(ww.world.getName())) {
|
|
getLogger().info("Already enabled for this world");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
/* nether/end world environments are not supported */
|
|
if (org.bukkit.Bukkit.getWorld(worldName).getEnvironment() != Environment.NORMAL) {
|
|
getLogger().info("Invalid environment for this world");
|
|
return false;
|
|
}
|
|
|
|
List<String> worldStringList = getConfig().getStringList("worlds");
|
|
if (worldStringList.indexOf(worldName) == -1) {
|
|
worldStringList.add(worldName);
|
|
getConfig().set("worlds", worldStringList);
|
|
saveConfig();
|
|
}
|
|
|
|
World w = org.bukkit.Bukkit.getWorld(worldName);
|
|
SedWorld s = new SedWorld(w);
|
|
|
|
/* fill initial chunkmap here */
|
|
Chunk chunkList[] = w.getLoadedChunks();
|
|
for (Chunk c: chunkList)
|
|
s.load(c.getX(), c.getZ());
|
|
|
|
sedWorldList.add(s);
|
|
|
|
return true;
|
|
}
|
|
|
|
private boolean disableWorld(String worldName)
|
|
{
|
|
if (org.bukkit.Bukkit.getWorld(worldName) == null) {
|
|
getLogger().info("No such world");
|
|
return false;
|
|
}
|
|
for (SedWorld ww: sedWorldList) {
|
|
if (org.bukkit.Bukkit.getWorld(worldName).getName().equals(ww.world.getName())) {
|
|
List<String> worldStringList = getConfig().getStringList("worlds");
|
|
worldStringList.remove(worldName);
|
|
getConfig().set("worlds", worldStringList);
|
|
saveConfig();
|
|
|
|
sedWorldList.remove(ww);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
getLogger().info("world not currently enabled");
|
|
|
|
return false;
|
|
}
|
|
|
|
private class offset {
|
|
public offset(int i, int j) {
|
|
x = i;
|
|
z = j;
|
|
}
|
|
public int x;
|
|
public int z;
|
|
}
|
|
|
|
private int walker_start;
|
|
private int walker_step;
|
|
private int walker_phase;
|
|
private offset walker[];
|
|
|
|
public offset walker_f(offset o) {
|
|
/*
|
|
* attempt to walk a grid as follows:
|
|
* a(4)-b(4)-c(4)-d(8)-e(4)
|
|
* edcde
|
|
* dbabd
|
|
* caOac
|
|
* dbabd
|
|
* edcde
|
|
*/
|
|
|
|
if (o.x == 0 && o.z == 0)
|
|
walker_step = 0;
|
|
|
|
switch (walker_step) {
|
|
case 0:
|
|
case 4:
|
|
case 8:
|
|
case 20:
|
|
walker_start = (int)Math.round(rnd.nextDouble() * 4.0);
|
|
walker_phase = 4;
|
|
break;
|
|
case 12:
|
|
walker_start = (int)Math.round(rnd.nextDouble() * 8.0);
|
|
walker_phase = 8;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
int section_start = walker_step - (walker_step % walker_phase);
|
|
int section_part = ((walker_step - section_start) + walker_start) % walker_phase;
|
|
|
|
walker_step++;
|
|
|
|
return walker[section_start + section_part];
|
|
}
|
|
|
|
private class SedDice {
|
|
private Random r;
|
|
|
|
public SedDice(Random random) {
|
|
this.r = random;
|
|
}
|
|
|
|
public boolean roll(double bet) {
|
|
return (r.nextDouble() > bet);
|
|
}
|
|
}
|
|
|
|
private class SedBlock {
|
|
private Block block;
|
|
|
|
public SedBlock(Block b) {
|
|
block = b;
|
|
}
|
|
|
|
/* arid envirnments where no precipitation happens */
|
|
public boolean inAridBiome() {
|
|
switch (block.getBiome()) {
|
|
case DESERT:
|
|
case DESERT_HILLS:
|
|
case DESERT_MOUNTAINS:
|
|
case MESA:
|
|
case MESA_BRYCE:
|
|
case MESA_PLATEAU:
|
|
case MESA_PLATEAU_FOREST:
|
|
case MESA_PLATEAU_FOREST_MOUNTAINS:
|
|
case MESA_PLATEAU_MOUNTAINS:
|
|
case SAVANNA:
|
|
case SAVANNA_MOUNTAINS:
|
|
case SAVANNA_PLATEAU:
|
|
case SAVANNA_PLATEAU_MOUNTAINS:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* environments where clay is naturally found */
|
|
public boolean inClayBiome() {
|
|
switch (block.getBiome()) {
|
|
case RIVER:
|
|
case OCEAN:
|
|
case DEEP_OCEAN:
|
|
case SWAMPLAND:
|
|
case FROZEN_RIVER:
|
|
case FROZEN_OCEAN:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* environments where sand is naturally found */
|
|
public boolean inSandBiome() {
|
|
switch (block.getBiome()) {
|
|
case OCEAN:
|
|
case BEACH:
|
|
case COLD_BEACH:
|
|
case STONE_BEACH:
|
|
case MUSHROOM_SHORE:
|
|
case DESERT:
|
|
case DESERT_HILLS:
|
|
case FROZEN_OCEAN:
|
|
case FROZEN_RIVER:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public boolean isProtected() {
|
|
if (!conf_protect)
|
|
return false;
|
|
|
|
if (have_factions_old) {
|
|
com.massivecraft.factions.Faction faction = com.massivecraft.factions.Board.getFactionAt(new com.massivecraft.factions.FLocation(block));
|
|
if (!faction.isNone())
|
|
return true;
|
|
}
|
|
if (have_factions) {
|
|
Faction faction = BoardColls.get().getFactionAt(PS.valueOf(block.getLocation()));
|
|
if (!faction.isNone())
|
|
return true;
|
|
}
|
|
if (have_towny) {
|
|
if (TownyUniverse.getTownBlock(block.getLocation()) != null)
|
|
return true;
|
|
}
|
|
if (have_worldguard) {
|
|
RegionManager rm = WGBukkit.getRegionManager(block.getWorld());
|
|
if (rm == null)
|
|
return false;
|
|
ApplicableRegionSet set = rm.getApplicableRegions(block.getLocation());
|
|
return (set.size() > 0);
|
|
}
|
|
if (have_residence) {
|
|
ClaimedResidence res = Residence.getResidenceManager().getByLoc(block.getLocation());
|
|
if (res != null)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public Material getType() {
|
|
return block.getType();
|
|
}
|
|
|
|
public void setType(Material material) {
|
|
block.setType(material);
|
|
}
|
|
|
|
public byte getData() {
|
|
return Sedimentology.this.getData(block);
|
|
}
|
|
|
|
public void setData(byte data) {
|
|
Sedimentology.this.setData(block, data);
|
|
}
|
|
|
|
public Biome getBiome() {
|
|
return block.getBiome();
|
|
}
|
|
|
|
}
|
|
|
|
private class SedWorld {
|
|
private Map<String, Integer> chunkMap;
|
|
private World world;
|
|
|
|
public SedWorld(World w) {
|
|
this.chunkMap = new HashMap<String, Integer>();
|
|
this.world = w;
|
|
}
|
|
|
|
public boolean isLoaded(int x, int z) {
|
|
String key = "(" + x + "," + z + ")";
|
|
if (chunkMap.get(key) == null)
|
|
return false;
|
|
return chunkMap.get(key) == 1;
|
|
}
|
|
|
|
public void load(int x, int z) {
|
|
String key = "(" + x + "," + z + ")";
|
|
chunkMap.put(key, 1);
|
|
}
|
|
|
|
public void unload(int x, int z) {
|
|
String key = "(" + x + "," + z + ")";
|
|
chunkMap.put(key, 0);
|
|
}
|
|
|
|
public void sedRandomBlock(sedType type) {
|
|
Chunk ChunkList[] = world.getLoadedChunks();
|
|
if (ChunkList.length == 0)
|
|
return;
|
|
Chunk c = ChunkList[(int) Math.abs(rnd.nextDouble() * ChunkList.length)];
|
|
|
|
int bx = (int)(Math.round(rnd.nextDouble() * 16));
|
|
int bz = (int)(Math.round(rnd.nextDouble() * 16));
|
|
|
|
/* don't bother touching chunks at the edges */
|
|
for (int xx = c.getX() - 1 ; xx <= c.getX() + 1; xx++) {
|
|
for (int zz = c.getZ() - 1 ; zz <= c.getZ() + 1; zz++) {
|
|
if (!this.isLoaded(xx, zz)) {
|
|
stat_ignored_edge++;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
int x = bx + (c.getX() * 16);
|
|
int z = bz + (c.getZ() * 16);
|
|
|
|
switch(type) {
|
|
case SED_BLOCK:
|
|
sedBlock(x, z);
|
|
break;
|
|
case SED_SNOW:
|
|
sedSnowBlock(x, z);
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
private int snowdepth(int x, int y, int z)
|
|
{
|
|
int stackheight = 1;
|
|
Block bottom = world.getBlockAt(x, y, z);
|
|
Block top = bottom;
|
|
|
|
while (true) {
|
|
if (top.getRelative(BlockFace.UP).getType() != Material.SNOW)
|
|
break;
|
|
|
|
top = top.getRelative(BlockFace.UP);
|
|
stackheight++;
|
|
}
|
|
|
|
return stackheight;
|
|
}
|
|
|
|
private int snow(int x, int y, int z) {
|
|
byte snowcount = 0;
|
|
int snowheight = 0;
|
|
int stackheight;
|
|
Block bottom = world.getBlockAt(x, y, z);
|
|
|
|
stackheight = snowdepth(x, y, z);
|
|
|
|
Block top = world.getBlockAt(x, y + stackheight - 1, z);
|
|
|
|
/* scan area for snow depth to even out snow height */
|
|
for (int xx = x - 1; xx <= x + 1; xx++) {
|
|
for (int zz = z - 1; zz <= z + 1; zz++) {
|
|
if ((xx == x) && (zz == z))
|
|
continue;
|
|
stack:
|
|
for (int yy = top.getY() + 2; yy >= y - 2; yy--) {
|
|
if (world.getBlockAt(xx, yy, zz).getType() == Material.SNOW) {
|
|
snowcount++;
|
|
snowheight += getData(world.getBlockAt(xx, yy, zz)) + (yy * 8) + 1;
|
|
break stack;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* thaw ? */
|
|
if (!world.hasStorm()) {
|
|
if (top.getLightLevel() >= 12) {
|
|
/* Snow melt is currently slower than snow accumulation, but melt happens more than
|
|
* snow accumulation. This means that for default setting, you're most likely will see
|
|
* snow mostly accumulate to near the max snow levels. However, increasing snowblocks
|
|
* higher will result in the max snow height being reached quicker, and subsequently snow
|
|
* decaying faster than it grows, causing snow to melt more.
|
|
*
|
|
* Bottom line - if you want lots of snow, set snowblocks low (10 or lower), but if you
|
|
* prefer snow to come and go fast, set snowblocks high (50-100).
|
|
*/
|
|
if ((top.getY() != bottom.getY()) || (getData(top) > 0)) {
|
|
/* smooth snow melt */
|
|
if (snowcount > 0) {
|
|
int avg = (snowheight / snowcount);
|
|
if ((((top.getY() * 8) + getData(top) + 1) > avg - 1) || (Math.random() > 0.9)) {
|
|
if (getData(top) > 0)
|
|
setData(top, (byte)(getData(top) - 1));
|
|
else
|
|
top.setType(Material.AIR);
|
|
}
|
|
} else {
|
|
setData(top, (byte)(getData(top) - 1));
|
|
}
|
|
} else {
|
|
/* remove snow only at the edges */
|
|
if ((snowcount < 6) && (top.getY() == bottom.getY()))
|
|
top.setType(Material.AIR);
|
|
}
|
|
}
|
|
return stackheight;
|
|
}
|
|
|
|
/* cold enough for snow accumulation? */
|
|
if (bottom.getTemperature() > 0.25)
|
|
return stackheight;
|
|
|
|
/* don't stack snow on leaves and plants */
|
|
if (isCrushable(bottom.getRelative(BlockFace.DOWN)))
|
|
return stackheight;
|
|
|
|
|
|
/* don't grow snow if there's no neighbour blocks with snow */
|
|
if (snowcount == 0)
|
|
return stackheight;
|
|
|
|
/* snow stack? */
|
|
long maxstackheight = Math.max((y - 64) / 16, Math.round((0.25 - bottom.getTemperature()) / 0.9));
|
|
|
|
/* grow, but must be completely surrounded by snow blocks */
|
|
if ((getData(top) == 7) && (snowcount == 8) && (stackheight < maxstackheight)) {
|
|
Block above = top.getRelative(BlockFace.UP);
|
|
above.setType(Material.SNOW);
|
|
setData(above, (byte)0);
|
|
} else {
|
|
/* if neighbours do not have snow, don't stack so high */
|
|
int avg = (snowheight / snowcount);
|
|
if ((((top.getY() * 8) + getData(top) + 1) < avg + 1) || (Math.random() > 0.9))
|
|
setData(top, (byte)Math.min((int)getData(top) + 1, ((snowcount > 0) ? snowcount - 1 : 0)));
|
|
}
|
|
|
|
return stackheight;
|
|
}
|
|
|
|
private void sedSnowBlock(int x, int z)
|
|
{
|
|
World world = this.world;
|
|
int y;
|
|
|
|
y = world.getHighestBlockYAt(x, z);
|
|
switch (world.getBlockAt(x, y, z).getType()) {
|
|
case SNOW:
|
|
snow(x, y, z);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void sedBlock(int x, int z) {
|
|
World world = this.world;
|
|
SedDice dice = new SedDice(rnd);
|
|
|
|
int y;
|
|
|
|
int snowdepth = 0;
|
|
|
|
double hardness;
|
|
double resistance;
|
|
double waterfactor;
|
|
double vegetationfactor;
|
|
double stormfactor;
|
|
|
|
boolean underwater = false;
|
|
boolean undermovingwater = false;
|
|
boolean targetunderwater = false;
|
|
boolean undersnow = false;
|
|
|
|
stat_considered++;
|
|
|
|
/* handle snow separately first */
|
|
y = world.getHighestBlockYAt(x, z);
|
|
switch (world.getBlockAt(x, y, z).getType()) {
|
|
case SNOW:
|
|
snowdepth = snowdepth(x, y, z);
|
|
undersnow = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* find highest block, even if underwater */
|
|
y = world.getHighestBlockYAt(x, z) - 1;
|
|
switch (world.getBlockAt(x, y, z).getType()) {
|
|
case WATER:
|
|
undermovingwater = true;
|
|
case STATIONARY_WATER:
|
|
underwater = true;
|
|
y = findDepositLocation(x, y, z) - 1;
|
|
break;
|
|
case SNOW:
|
|
y = findDepositLocation(x, y, z) - 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
SedBlock b = new SedBlock(world.getBlockAt(x, y, z));
|
|
|
|
if (b.isProtected()) {
|
|
stat_protected++;
|
|
return;
|
|
}
|
|
|
|
/* filter out blocks we don't erode right now */
|
|
hardness = 1.0;
|
|
resistance = 1.0;
|
|
switch (b.getType()) {
|
|
case SOIL:
|
|
case DIRT:
|
|
case GRASS:
|
|
break;
|
|
case SAND: /* this covers red sand */
|
|
/*
|
|
* breaking sand into clay is hard, this also prevents all
|
|
* sand from turning into clay
|
|
*/
|
|
hardness = 0.01;
|
|
break;
|
|
case GRAVEL:
|
|
hardness = 0.15;
|
|
resistance = 0.75;
|
|
break;
|
|
case CLAY:
|
|
resistance = 0.3;
|
|
break;
|
|
case HARD_CLAY:
|
|
case STAINED_CLAY:
|
|
case SANDSTONE:
|
|
case MOSSY_COBBLESTONE:
|
|
case COBBLESTONE:
|
|
hardness = 0.05;
|
|
resistance = 0.05;
|
|
break;
|
|
case STONE:
|
|
hardness = 0.01;
|
|
resistance = 0.01;
|
|
break;
|
|
/* ores don't break down much at all, but they are displaced as easy stone */
|
|
case COAL_ORE:
|
|
case IRON_ORE:
|
|
case LAPIS_ORE:
|
|
case EMERALD_ORE:
|
|
case GOLD_ORE:
|
|
case DIAMOND_ORE:
|
|
case REDSTONE_ORE:
|
|
hardness = 0.0001;
|
|
resistance = 0.01;
|
|
break;
|
|
default:
|
|
stat_ignored_type++;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* if under a significant snow cover, lower hardness drastically.
|
|
*
|
|
* There's no need to lower resistance, since that is the obvious result
|
|
* of lowering the hardness as the resulting block will be easier to move.
|
|
*/
|
|
if ((undersnow) && (snowdepth >= 2))
|
|
hardness = Math.pow(hardness, 1 / (snowdepth - 0.5));
|
|
|
|
/* lower overall chance due to lack of water */
|
|
stormfactor = 0.1;
|
|
if (world.hasStorm()) {
|
|
if (!b.inAridBiome())
|
|
stormfactor = 1.0;
|
|
}
|
|
|
|
if ((!underwater) && (dice.roll(stormfactor))) {
|
|
stat_ignored_storm++;
|
|
return;
|
|
}
|
|
|
|
// water increases displacement/degradation
|
|
waterfactor = 0.01; //this is probably too low
|
|
|
|
if (undermovingwater)
|
|
waterfactor = 1.0;
|
|
else if (underwater)
|
|
waterfactor = 0.5;
|
|
else {
|
|
waterloop:
|
|
for (int xx = x - 2; xx < x + 2; xx++) {
|
|
for (int zz = z - 2; zz < z + 2; zz++) {
|
|
for (int yy = y - 2; yy < y + 2; yy++) {
|
|
switch(world.getBlockAt(xx, yy, zz).getType()) {
|
|
case WATER:
|
|
waterfactor = 0.25;
|
|
break waterloop;
|
|
case STATIONARY_WATER:
|
|
waterfactor = 0.125;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dice.roll(waterfactor)) {
|
|
stat_ignored_water++;
|
|
return;
|
|
}
|
|
|
|
/* slow down when deeper under the sealevel */
|
|
if (underwater) {
|
|
if (y < world.getSeaLevel()) {
|
|
/* exponentially slower with depth. 100% at 1 depth, 50% at 2, 25% at 3 etc... */
|
|
if (dice.roll(2.0 * Math.pow(0.5, world.getSeaLevel() - y))) {
|
|
stat_ignored_wave++;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// vegetation slows down displacement
|
|
vegetationfactor = 1.0;
|
|
|
|
for (int xx = x - 3; xx < x + 3; xx++) {
|
|
for (int zz = z - 3; zz < z + 3; zz++) {
|
|
for (int yy = y - 3; yy < y + 3; yy++) {
|
|
switch(world.getBlockAt(xx, yy, zz).getType()) {
|
|
case LEAVES:
|
|
case CACTUS:
|
|
case SAPLING:
|
|
case LOG:
|
|
case LONG_GRASS:
|
|
case DEAD_BUSH:
|
|
case YELLOW_FLOWER:
|
|
case RED_ROSE:
|
|
case BROWN_MUSHROOM:
|
|
case RED_MUSHROOM:
|
|
case CROPS:
|
|
case MELON_STEM:
|
|
case PUMPKIN_STEM:
|
|
case MELON_BLOCK:
|
|
case PUMPKIN:
|
|
case VINE:
|
|
case SUGAR_CANE:
|
|
case DOUBLE_PLANT:
|
|
case LEAVES_2:
|
|
case LOG_2:
|
|
/* distance to vegetation: 3.0 (far) to 0.3 (near) */
|
|
double d = (Math.abs(xx - x) + Math.abs(yy - y) + Math.abs(zz -z)) / 3.0;
|
|
/* somewhat complex calculation here to make the chance
|
|
* proportional to the distance: 0.5 / (1.0 -> 3.7)
|
|
* Basically ends up being 0.5 (far) to 0.135 (near)
|
|
*/
|
|
double f = 0.5 / (4.0 - d);
|
|
if (f < vegetationfactor)
|
|
vegetationfactor = f;
|
|
break; //vegetationloop;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dice.roll(vegetationfactor)) {
|
|
stat_ignored_vegetation++;
|
|
return;
|
|
}
|
|
|
|
/* displace block? */
|
|
displace:
|
|
if (true) {
|
|
int step, steps;
|
|
|
|
/* our block must be able to move sideways, otherwise it could leave
|
|
* strange gaps. So check if all 4 sides horizontally are solid
|
|
*/
|
|
if (!(world.getBlockAt(x + 1, y, z).isEmpty() || world.getBlockAt(x + 1, y, z).isLiquid() ||
|
|
world.getBlockAt(x - 1, y, z).isEmpty() || world.getBlockAt(x - 1, y, z).isLiquid() ||
|
|
world.getBlockAt(x, y, z + 1).isEmpty() || world.getBlockAt(x, y, z + 1).isLiquid() ||
|
|
world.getBlockAt(x, y, z - 1).isEmpty() || world.getBlockAt(x, y, z - 1).isLiquid())) {
|
|
stat_ignored_lockedin++;
|
|
break displace;
|
|
}
|
|
|
|
/* find the most suitable target location to move this block to */
|
|
if ((b.getType() == Material.SAND) || (underwater))
|
|
steps = 24;
|
|
else
|
|
steps = 8;
|
|
|
|
int lowest = y;
|
|
offset lowestoffset = new offset(0, 0);
|
|
offset o = new offset(0, 0);
|
|
int tx = 0, ty = 0, tz = 0;
|
|
|
|
for (step = 0; step < steps; step++) {
|
|
o = walker_f(o);
|
|
int h = findDepositLocation(x + o.x, lowest, z + o.z);
|
|
|
|
if (h < lowest) {
|
|
lowest = h;
|
|
lowestoffset = o;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* flat ? */
|
|
if (lowest == y)
|
|
break displace;
|
|
|
|
tx = x + lowestoffset.x;
|
|
ty = lowest;
|
|
tz = z + lowestoffset.z;
|
|
|
|
SedBlock t = new SedBlock(world.getBlockAt(tx, ty, tz));
|
|
|
|
if (t.isProtected()) {
|
|
stat_protected++;
|
|
return;
|
|
}
|
|
|
|
/* a snow cover slows down displacement */
|
|
if (undersnow) {
|
|
if (dice.roll(0.25)) {
|
|
stat_ignored_water++;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* roll to move it */
|
|
if (dice.roll(resistance)) {
|
|
stat_ignored_resistance++;
|
|
return;
|
|
}
|
|
|
|
/* It's time to move it, move it. */
|
|
if (isCrushable(t.block) || t.block.isLiquid() || t.block.isEmpty()) {
|
|
/* play a sound at the deposition area */
|
|
Sound snd;
|
|
|
|
switch (world.getBlockAt(tx, ty, z).getType()) {
|
|
case WATER:
|
|
case STATIONARY_WATER:
|
|
targetunderwater = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
Material mat = b.getType();
|
|
byte dat = b.getData();
|
|
/* fix water issues at sealevel */
|
|
if ((y <= world.getSeaLevel()) &&
|
|
((world.getBlockAt(x - 1, y, z).getType() == Material.STATIONARY_WATER) ||
|
|
(world.getBlockAt(x + 1, y, z).getType() == Material.STATIONARY_WATER) ||
|
|
(world.getBlockAt(x, y, z - 1).getType() == Material.STATIONARY_WATER) ||
|
|
(world.getBlockAt(x, y, z + 1).getType() == Material.STATIONARY_WATER)) &&
|
|
((world.getBlockAt(x - 1, y, z).getType() != Material.AIR) &&
|
|
(world.getBlockAt(x + 1, y, z).getType() != Material.AIR) &&
|
|
(world.getBlockAt(x, y, z - 1).getType() != Material.AIR) &&
|
|
(world.getBlockAt(x, y, z + 1).getType() != Material.AIR)))
|
|
b.setType(Material.STATIONARY_WATER);
|
|
else
|
|
b.setType(Material.AIR);
|
|
b.setData((byte)0);
|
|
t.setType(mat);
|
|
t.setData(dat);
|
|
|
|
if (targetunderwater && !underwater) {
|
|
snd = Sound.SPLASH;
|
|
} else if (y - ty > 2) {
|
|
snd = Sound.FALL_BIG;
|
|
} else {
|
|
switch(b.getType()) {
|
|
case CLAY:
|
|
case SAND:
|
|
snd = Sound.DIG_SAND;
|
|
case DIRT:
|
|
case GRASS:
|
|
snd = Sound.DIG_GRASS;
|
|
break;
|
|
case GRAVEL:
|
|
snd = Sound.DIG_GRAVEL;
|
|
break;
|
|
case HARD_CLAY:
|
|
case STAINED_CLAY:
|
|
case COBBLESTONE:
|
|
case STONE:
|
|
case COAL_ORE:
|
|
case IRON_ORE:
|
|
case LAPIS_ORE:
|
|
case EMERALD_ORE:
|
|
case GOLD_ORE:
|
|
case DIAMOND_ORE:
|
|
case REDSTONE_ORE:
|
|
snd = Sound.DIG_STONE;
|
|
break;
|
|
default:
|
|
snd = Sound.FALL_SMALL;
|
|
break;
|
|
}
|
|
}
|
|
world.playSound(new Location(world, tx, ty, tz), snd, 1, 1);
|
|
|
|
stat_displaced++;
|
|
|
|
/* fix waterfall stuff above sealevel */
|
|
if (b.block.getRelative(BlockFace.UP).isLiquid()) {
|
|
Block u = b.block.getRelative(BlockFace.UP);
|
|
while (u.isLiquid() && u.getRelative(BlockFace.UP).isLiquid())
|
|
u = u.getRelative(BlockFace.UP);
|
|
if (u.getY() > world.getSeaLevel() && (getData(u) != 0)) {
|
|
u.setType(Material.AIR);
|
|
while (u.getRelative(BlockFace.DOWN).getY() != b.block.getY() && u.getY() > world.getSeaLevel()){
|
|
u = u.getRelative(BlockFace.DOWN);
|
|
u.setType(Material.AIR);
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
} else {
|
|
/* figure out how this happened */
|
|
stat_errors++;
|
|
getLogger().info("Attempted to move into a block of " + t.getType().name());
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* degrade block? */
|
|
|
|
// degrade should factor in elevation?
|
|
|
|
/*
|
|
* compensate for speed - this prevents at high block rates that
|
|
* everything just turns into a mudbath - We do want the occasional
|
|
* mudbath to appear, but only after rain or by chance, not always
|
|
*/
|
|
if (conf_compensate && (conf_blocks > 10) && (b.getType() == Material.GRASS)) {
|
|
if (dice.roll(10.0 / conf_blocks)) {
|
|
stat_ignored_rate++;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* do not decay gravel (and dirt, sand, etc.) further if under snow */
|
|
if ((b.getType() == Material.GRAVEL) && (undersnow)) {
|
|
stat_ignored_sand++;
|
|
return;
|
|
}
|
|
|
|
/* do not decay sand further unless in a wet Biome, and under water, and under sealevel */
|
|
if (b.getType() == Material.SAND) {
|
|
if (!(b.inClayBiome() && underwater && b.block.getY() < world.getSeaLevel())) {
|
|
stat_ignored_sand++;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* For now, don't decay into sand for biomes that are mostly dirtish, unless under water */
|
|
if (b.getType() == Material.DIRT){
|
|
if (!(b.inSandBiome() || underwater)) {
|
|
stat_ignored_sand++;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (dice.roll(hardness)) {
|
|
stat_ignored_hardness++;
|
|
return;
|
|
}
|
|
|
|
switch (b.getType()) {
|
|
case DIRT:
|
|
b.setType(Material.SAND);
|
|
b.setData((byte)0);
|
|
break;
|
|
case SOIL:
|
|
case GRASS:
|
|
b.setType(Material.DIRT);
|
|
break;
|
|
case SAND:
|
|
b.setType(Material.CLAY);
|
|
b.setData((byte)0);
|
|
break;
|
|
case GRAVEL:
|
|
b.setType(Material.DIRT);
|
|
break;
|
|
case CLAY:
|
|
/* we can displace clay, but not degrade */
|
|
return;
|
|
case HARD_CLAY:
|
|
case STAINED_CLAY:
|
|
case SANDSTONE:
|
|
case MOSSY_COBBLESTONE:
|
|
case COBBLESTONE:
|
|
b.setType(Material.GRAVEL);
|
|
b.setData((byte)0);
|
|
break;
|
|
case STONE:
|
|
switch (b.getBiome()) {
|
|
case MEGA_TAIGA:
|
|
case MEGA_TAIGA_HILLS:
|
|
case MEGA_SPRUCE_TAIGA:
|
|
case MEGA_SPRUCE_TAIGA_HILLS:
|
|
b.setType(Material.MOSSY_COBBLESTONE);
|
|
break;
|
|
default:
|
|
b.setType(Material.COBBLESTONE);
|
|
break; /* breaks inner switch only */
|
|
}
|
|
break;
|
|
case COAL_ORE:
|
|
case IRON_ORE:
|
|
case LAPIS_ORE:
|
|
case EMERALD_ORE:
|
|
case GOLD_ORE:
|
|
case DIAMOND_ORE:
|
|
case REDSTONE_ORE:
|
|
b.setType(Material.STONE);
|
|
break;
|
|
default:
|
|
stat_errors++;
|
|
return;
|
|
}
|
|
|
|
stat_lastx = x;
|
|
stat_lasty = y;
|
|
stat_lastz = z;
|
|
stat_degraded++;
|
|
}
|
|
|
|
/* helper function to find lowest deposit elevation ignoring water */
|
|
private int findDepositLocation(int x, int y, int z) {
|
|
int yy = y;
|
|
while (true) {
|
|
Block b = world.getBlockAt(x, yy, z);
|
|
if (isCrushable(b) || b.isLiquid() || b.isEmpty()) {
|
|
yy--;
|
|
if (yy == 0)
|
|
return yy;
|
|
} else {
|
|
return yy + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean isCrushable(Block b) {
|
|
switch (b.getType()) {
|
|
case LEAVES:
|
|
case CACTUS:
|
|
case SAPLING:
|
|
case LONG_GRASS:
|
|
case DEAD_BUSH:
|
|
case YELLOW_FLOWER:
|
|
case RED_ROSE:
|
|
case BROWN_MUSHROOM:
|
|
case RED_MUSHROOM:
|
|
case CROPS:
|
|
case MELON_STEM:
|
|
case PUMPKIN_STEM:
|
|
case MELON_BLOCK:
|
|
case PUMPKIN:
|
|
case VINE:
|
|
case SUGAR_CANE:
|
|
case DOUBLE_PLANT:
|
|
case LEAVES_2:
|
|
case SNOW:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private class SedimentologyRunnable implements Runnable {
|
|
public void run() {
|
|
for (SedWorld sedWorld: sedWorldList) {
|
|
for (int j = 0; j < conf_snowblocks; j++)
|
|
sedWorld.sedRandomBlock(sedType.SED_SNOW);
|
|
for (int j = 0; j < conf_blocks; j++)
|
|
sedWorld.sedRandomBlock(sedType.SED_BLOCK);
|
|
}
|
|
}
|
|
}
|
|
|
|
class SedimentologyCommand implements CommandExecutor {
|
|
public boolean onCommand(CommandSender sender, Command command, String label, String[] split) {
|
|
String msg = "unknown command";
|
|
String helpmsg = "\n" +
|
|
"/sedimentology help - display this help message\n" +
|
|
"/sedimentology stats - display statistics\n" +
|
|
"/sedimentology list - display enabled worlds\n" +
|
|
"/sedimentology blocks <int> - sed number of block attempts per cycle\n" +
|
|
"/sedimentology snowblocks <int> - sed number of snowblock attempts per cycle\n" +
|
|
"/sedimentology enable <world> - enable for world\n" +
|
|
"/sedimentology disable <world> - enable for world";
|
|
|
|
command:
|
|
if (split.length >= 1) {
|
|
switch (split[0]) {
|
|
case "blocks":
|
|
if (split.length == 2) {
|
|
conf_blocks = Long.parseLong(split[1]);
|
|
getConfig().set("blocks", conf_blocks);
|
|
saveConfig();
|
|
}
|
|
msg = "number of blocks set to " + conf_blocks;
|
|
break;
|
|
case "snowblocks":
|
|
if (split.length == 2) {
|
|
conf_snowblocks = Long.parseLong(split[1]);
|
|
getConfig().set("snowblocks", conf_snowblocks);
|
|
saveConfig();
|
|
}
|
|
msg = "number of snowblocks set to " + conf_snowblocks;
|
|
break;
|
|
case "list":
|
|
msg = "plugin enabled for worlds:\n";
|
|
for (SedWorld s: sedWorldList) {
|
|
msg += "- " + s.world.getName() + "\n";
|
|
}
|
|
break;
|
|
case "enable":
|
|
if (split.length != 2) {
|
|
msg = "enable requires a world name";
|
|
break;
|
|
};
|
|
if (!enableWorld(split[1]))
|
|
msg = "unable to enable for world \"" + split[1] + "\"";
|
|
else
|
|
msg = "enabled for world \"" + split[1] + "\"";
|
|
break;
|
|
case "disable":
|
|
if (split.length != 2) {
|
|
msg = "disable requires a world name";
|
|
break;
|
|
};
|
|
if (!disableWorld(split[1]))
|
|
msg = "unable to disable for world \"" + split[1] + "\"";
|
|
else
|
|
msg = "disabled for world \"" + split[1] + "\"";
|
|
break;
|
|
case "stats":
|
|
long chunks = 0;
|
|
for (SedWorld sw: sedWorldList)
|
|
chunks += sw.world.getLoadedChunks().length;
|
|
msg = String.format("blocks: %d snowblocks: %d ticks: %d protect: %s\n" +
|
|
"considered %d, displaced %d, degraded %d blocks in %d chunks %d errors\nlast one at %d %d %d\n" +
|
|
"ignored: edge %d, type %d, storm %d, vegetation %d, resistance %d, water %d, wave %d, sand %d, hardness %d, " +
|
|
"protected %d, locked in %d, rate %d",
|
|
conf_blocks, conf_snowblocks, conf_ticks, conf_protect ? "true" : "false",
|
|
stat_considered, stat_displaced, stat_degraded, chunks, stat_errors,
|
|
stat_lastx, stat_lasty, stat_lastz,
|
|
stat_ignored_edge, stat_ignored_type, stat_ignored_storm, stat_ignored_vegetation,
|
|
stat_ignored_resistance, stat_ignored_water, stat_ignored_wave, stat_ignored_sand, stat_ignored_hardness,
|
|
stat_protected, stat_ignored_lockedin, stat_ignored_rate);
|
|
break;
|
|
case "test":
|
|
if (split.length != 7) {
|
|
msg = "test requires 6 parameters: world x1 z1 x2 z2 blocks";
|
|
break;
|
|
};
|
|
for (SedWorld sw: sedWorldList) {
|
|
if (sw.world.getName().equals(split[1])) {
|
|
int x1 = Integer.parseInt(split[2]);
|
|
int z1 = Integer.parseInt(split[3]);
|
|
int x2 = Integer.parseInt(split[4]);
|
|
int z2 = Integer.parseInt(split[5]);
|
|
if (x1 > x2) {
|
|
int t = x1;
|
|
x1 = x2;
|
|
x2 = t;
|
|
}
|
|
if (z1 > z2) {
|
|
int t = z1;
|
|
z1 = z2;
|
|
z2 = t;
|
|
}
|
|
for (long i = 0; i < Long.parseLong(split[6]); i++)
|
|
for (int x = x1; x <= x2; x++)
|
|
for (int z = z1; z <= z2; z++)
|
|
sw.sedBlock(x, z);
|
|
msg = "test cycle finished";
|
|
break command;
|
|
}
|
|
}
|
|
msg = "Invalid world name - world must be enabled already";
|
|
break;
|
|
case "snowtest":
|
|
if (split.length != 7) {
|
|
msg = "snowtest requires 6 parameters: world x1 z1 x2 z2 blocks";
|
|
break;
|
|
};
|
|
for (SedWorld sw: sedWorldList) {
|
|
if (sw.world.getName().equals(split[1])) {
|
|
int x1 = Integer.parseInt(split[2]);
|
|
int z1 = Integer.parseInt(split[3]);
|
|
int x2 = Integer.parseInt(split[4]);
|
|
int z2 = Integer.parseInt(split[5]);
|
|
if (x1 > x2) {
|
|
int t = x1;
|
|
x1 = x2;
|
|
x2 = t;
|
|
}
|
|
if (z1 > z2) {
|
|
int t = z1;
|
|
z1 = z2;
|
|
z2 = t;
|
|
}
|
|
for (long i = 0; i < Long.parseLong(split[6]); i++)
|
|
for (int x = x1; x <= x2; x++)
|
|
for (int z = z1; z <= z2; z++)
|
|
sw.sedSnowBlock(x, z);
|
|
msg = "test cycle finished";
|
|
break command;
|
|
}
|
|
}
|
|
msg = "Invalid world name - world must be enabled already";
|
|
break;
|
|
case "help":
|
|
default:
|
|
msg = helpmsg;
|
|
break;
|
|
}
|
|
} else {
|
|
msg = helpmsg;
|
|
}
|
|
|
|
if (!(sender instanceof Player)) {
|
|
getLogger().info(msg);
|
|
} else {
|
|
Player player = (Player) sender;
|
|
player.sendMessage(msg);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
class SedimentologyListener implements Listener {
|
|
@EventHandler
|
|
public void onChunkLoadEvent(ChunkLoadEvent event) {
|
|
World w = event.getWorld();
|
|
for (SedWorld ww: sedWorldList) {
|
|
if (ww.world.equals(w)) {
|
|
Chunk c = event.getChunk();
|
|
ww.load(c.getX(), c.getZ());
|
|
}
|
|
}
|
|
}
|
|
|
|
@EventHandler
|
|
public void onChunkUnloadEvent(ChunkUnloadEvent event) {
|
|
World w = event.getWorld();
|
|
for (SedWorld ww: sedWorldList) {
|
|
if (ww.world.equals(w)) {
|
|
Chunk c = event.getChunk();
|
|
ww.unload(c.getX(), c.getZ());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void onEnable() {
|
|
sedWorldList = new ArrayList<SedWorld>();
|
|
|
|
/* these are used to displace blocks */
|
|
walker = new offset[24];
|
|
|
|
walker[0] = new offset(0,1);
|
|
walker[1] = new offset(1,0);
|
|
walker[2] = new offset(0,-1);
|
|
walker[3] = new offset(-1,0);
|
|
|
|
walker[4] = new offset(1,1);
|
|
walker[5] = new offset(1,-1);
|
|
walker[6] = new offset(-1,1);
|
|
walker[7] = new offset(-1,-1);
|
|
|
|
walker[8] = new offset(2,0);
|
|
walker[9] = new offset(-2,0);
|
|
walker[10] = new offset(0,2);
|
|
walker[11] = new offset(0,-2);
|
|
|
|
walker[12] = new offset(2,1);
|
|
walker[13] = new offset(2,-1);
|
|
walker[14] = new offset(1,2);
|
|
walker[15] = new offset(1,-2);
|
|
walker[16] = new offset(-1,2);
|
|
walker[17] = new offset(-1,-2);
|
|
walker[18] = new offset(-2,1);
|
|
walker[19] = new offset(-2,-1);
|
|
|
|
walker[20] = new offset(2,2);
|
|
walker[21] = new offset(-2,2);
|
|
walker[22] = new offset(2,-2);
|
|
walker[23] = new offset(-2,-2);
|
|
|
|
/* config data handling */
|
|
saveDefaultConfig();
|
|
|
|
conf_blocks = getConfig().getInt("blocks");
|
|
conf_snowblocks = getConfig().getInt("snowblocks");
|
|
conf_ticks = getConfig().getInt("ticks");
|
|
getLogger().info("blocks: " + conf_blocks + " snowblocks: " + conf_snowblocks + " ticks: " + conf_ticks);
|
|
|
|
List<String> worldStringList = getConfig().getStringList("worlds");
|
|
|
|
/* populate chunk cache for each world */
|
|
for (int i = 0; i < worldStringList.size(); i++)
|
|
enableWorld(worldStringList.get(i));
|
|
|
|
/* Detect Factions */
|
|
if (org.bukkit.Bukkit.getPluginManager().isPluginEnabled("Factions")) {
|
|
try {
|
|
/* this is an old API thing */
|
|
new com.massivecraft.factions.FLocation();
|
|
} catch (NoClassDefFoundError e) {
|
|
have_factions = true;
|
|
}
|
|
if (!have_factions)
|
|
have_factions_old = true;
|
|
}
|
|
|
|
/* Towny */
|
|
if (org.bukkit.Bukkit.getPluginManager().isPluginEnabled("Towny"))
|
|
have_towny = true;
|
|
|
|
/* WorldGuard */
|
|
if (org.bukkit.Bukkit.getPluginManager().isPluginEnabled("WorldGuard"))
|
|
have_worldguard = true;
|
|
|
|
if (org.bukkit.Bukkit.getPluginManager().isPluginEnabled("Residence"))
|
|
have_residence = true;
|
|
|
|
getLogger().info("Protection plugins: " +
|
|
(have_factions | have_factions_old ? "+" : "-") + "Factions, " +
|
|
(have_towny ? "+" : "-") + "Towny, " +
|
|
(have_worldguard ? "+" : "-") + "WorldGuard, " +
|
|
(have_residence ? "+" : "-") + "Residence"
|
|
);
|
|
|
|
conf_protect = getConfig().getBoolean("protect");
|
|
getLogger().info("protection is " + (conf_protect ? "on" : "off"));
|
|
|
|
/* even handler takes care of updating it from there */
|
|
getServer().getPluginManager().registerEvents(new SedimentologyListener(), this);
|
|
|
|
getCommand("sedimentology").setExecutor(new SedimentologyCommand());
|
|
|
|
BukkitScheduler scheduler = Bukkit.getServer().getScheduler();
|
|
scheduler.scheduleSyncRepeatingTask(this, new SedimentologyRunnable(), 1L, conf_ticks);
|
|
}
|
|
}
|
|
|