Fixed the healing code

This commit is contained in:
Zontreck 2022-10-19 17:36:50 -07:00
parent 0d65d57219
commit b19fd441f8
9 changed files with 486 additions and 96 deletions

View file

@ -104,16 +104,6 @@ repositories {
// }
mavenCentral()
maven {
// location of the maven that hosts JEI files
name = "Progwml6 maven"
url = "https://dvs1.progwml6.com/files/maven/"
}
maven {
// location of a maven mirror for JEI files, as a fallback
name = "ModMaven"
url = "https://modmaven.dev"
}
maven {
name = "CurseMaven"
url = "https://cursemaven.com"
@ -131,10 +121,6 @@ dependencies {
// The userdev artifact is a special name and will get all sorts of transformations applied to it.
minecraft "net.minecraftforge:forge:${mc_version}-${forge_version}"
compileOnly fg.deobf("mezz.jei:jei-${mc_version}-common-api:${jei_version}")
compileOnly fg.deobf("mezz.jei:jei-${mc_version}-forge-api:${jei_version}")
runtimeOnly fg.deobf("mezz.jei:jei-${mc_version}-forge:${jei_version}")
implementation fg.deobf("dev.zontreck:libzontreck:${libz_version}:dev")
compileOnly fg.deobf("dev.zontreck:libzontreck:${libz_version}:dev")
runtimeOnly fg.deobf("dev.zontreck:libzontreck:${libz_version}")

View file

@ -7,5 +7,4 @@ my_version=1.3.4.4
mc_version=1.19.2
forge_version=43.1.40
jei_version=11.3.0.271
libz_version=1.0.1.2

View file

@ -69,7 +69,7 @@ public class OTEMod
public static Map<String,Profile> PROFILES = new HashMap<String,Profile>();
public static List<TeleportContainer> TeleportRegistry = new ArrayList<>();
public static MinecraftServer THE_SERVER;
public static boolean ALIVE;
public static boolean ALIVE=false;
public static boolean HEALER_WAIT=true; // Only on loading finish should this unlock
public static Thread HEALER_THREAD;

View file

@ -3,6 +3,7 @@ package dev.zontreck.otemod.antigrief;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import dev.zontreck.libzontreck.vectors.Vector3;
import dev.zontreck.libzontreck.vectors.WorldPosition;
@ -60,7 +61,7 @@ public class Handler
if(exploder==null)return ; // TODO: Make this check config
final Collection<StoredBlock> affectedBlocks = buildBlocks(level, event.getAffectedBlocks());
final Collection<StoredBlock> toHeal = new ArrayList<StoredBlock>(affectedBlocks.size());
Collection<StoredBlock> toHeal = new ArrayList<StoredBlock>(affectedBlocks.size());
Block tnt = Blocks.TNT;
@ -96,7 +97,22 @@ public class Handler
}
// Add to the healing queue
HealerQueue.ToHeal.addAll(toHeal);
List<StoredBlock> mainList = new ArrayList<>();
mainList.addAll(toHeal);
mainList = HealerQueue.removeSame(mainList);
HealerWorker work = new HealerWorker(mainList);
HealerQueue.ManagerInstance.registerWorker(work);
Thread tx = new Thread(work);
tx.start();
HealerQueue.ToHeal.addAll(mainList);
HealerQueue.Pass=0;
HealerQueue.ToHeal.addAll(HealerQueue.ToValidate);
HealerQueue.ToValidate = new ArrayList<>();
HealerQueue.Shuffle();
}

View file

@ -0,0 +1,72 @@
package dev.zontreck.otemod.antigrief;
import java.util.Random;
import dev.zontreck.libzontreck.vectors.Vector3;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.sounds.SoundEvent;
import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
public class HealRunner implements Runnable
{
public final StoredBlock BlockToSet;
// Play a popping sound at the block position
public final SoundEvent pop = SoundEvents.ITEM_PICKUP;
public HealRunner(StoredBlock sb)
{
BlockToSet = sb;
}
public static void scheduleHeal(StoredBlock sb){
sb.getWorldPosition().getActualDimension().getServer().execute(new HealRunner(sb));
}
@Override
public void run()
{
//BlockSnapshot bs = BlockSnapshot.create(level.dimension(), level, sb.getPos());
//BlockState current = level.getBlockState(sb.getPos());
final ServerLevel level = BlockToSet.getWorldPosition().getActualDimension();
BlockState nState = Block.updateFromNeighbourShapes(BlockToSet.getState(), level, BlockToSet.getPos());
level.setBlock(BlockToSet.getPos(), nState, Block.UPDATE_CLIENTS); // no update?
//level.setBlocksDirty(sb.getPos(), sb.getState(), level.getBlockState(sb.getPos()));
//level.markAndNotifyBlock(sb.getPos(), level.getChunkAt(sb.getPos()), sb.getState(), level.getBlockState(sb.getPos()), 2, 0);
//level.getChunkAt(sb.getPos()).setBlockState(sb.getPos(), sb.getState(), false);
BlockEntity be = level.getBlockEntity(BlockToSet.getPos());
if(be!=null){
//be.deserializeNBT(sb.getBlockEntity());
be.load(BlockToSet.getBlockEntity());
be.setChanged();
}
// Everything is restored, play sound
SoundSource ss = SoundSource.NEUTRAL;
Vector3 v3 = BlockToSet.getWorldPosition().Position;
Random rng = new Random();
level.playSound(null, v3.asBlockPos(), pop, ss, rng.nextFloat(0.75f,1.0f), rng.nextFloat(1));
/*for(ServerPlayer player : level.players())
{
Vector3 playerPos = new Vector3(player.position());
if(sb.getWorldPosition().Position.distance(playerPos) < 15)
{
// have player's client play sound (Packet?)
}
}*/
}
}

View file

@ -2,9 +2,12 @@ package dev.zontreck.otemod.antigrief;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import dev.zontreck.libzontreck.vectors.Vector3;
import dev.zontreck.libzontreck.vectors.WorldPosition;
import dev.zontreck.otemod.OTEMod;
import dev.zontreck.otemod.configs.OTEServerConfig;
import net.minecraft.server.commands.SetBlockCommand;
@ -15,6 +18,7 @@ import net.minecraft.sounds.SoundEvents;
import net.minecraft.sounds.SoundSource;
import net.minecraft.world.entity.item.FallingBlockEntity;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.FallingBlock;
import net.minecraft.world.level.block.SandBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
@ -23,8 +27,18 @@ import net.minecraftforge.common.util.BlockSnapshot;
public class HealerManager implements Runnable
{
private List<HealerWorker> Workers = new ArrayList<>();
public HealerManager(){
}
public void registerWorker(HealerWorker worker)
{
Workers.add(worker);
}
public void deregisterWorker(HealerWorker worker)
{
Workers.remove(worker);
}
@Override
public void run(){
@ -33,34 +47,159 @@ public class HealerManager implements Runnable
long lastSave = 0;
final long saveInterval = (2*60); // Every 2 minutes
boolean lastWait = false;
// Heal pass 1 is set all positions to bedrock
// Pass 2 is assert all solid blocks (Not air)
// Pass 3 is to set the air blocks and remove bedrock
while(OTEMod.ALIVE)
{
try{
// Run the queue
// We want to restore one block per run, then halt for number of seconds in config
try {
if(!skipWait)
Thread.sleep(OTEServerConfig.HEALER_TIMER.get());
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
if(lastWait != OTEMod.HEALER_WAIT){
OTEMod.LOGGER.info("Healer wait flag was toggled");
}
lastWait = OTEMod.HEALER_WAIT;
if(OTEMod.HEALER_WAIT)
continue; // Wait until the saved queue has been fully imported
try{
if(!skipWait) Thread.sleep(OTEServerConfig.HEALER_TIMER.get());
}catch(Exception E){}
if(!OTEMod.ALIVE)
{
// Server has begun to shut down while we were sleeping
// Begin tear down
break;
if(lastWait != OTEMod.HEALER_WAIT) OTEMod.LOGGER.info("Healer Wait Flag was Toggled");
lastWait = OTEMod.HEALER_WAIT;
if(OTEMod.HEALER_WAIT) continue; // Wait to process until import completes
if(!OTEMod.ALIVE) break; // Begin tear down, server has shut down
for (HealerWorker healerWorker : Workers) {
healerWorker.doTick=true;
}
if(HealerQueue.ToHeal.size()==0 && HealerQueue.ToValidate.size()==0)
{
HealerQueue.Pass=0;
if(HealerQueue.dirty())
HealerQueue.dump();
continue;
}
// Get the first block in the list
final StoredBlock sb = HealerQueue.locateLowestBlock(HealerQueue.ToHeal);
ServerLevel level = null;
StoredBlock below = null;
if(sb != null)
{
level = sb.getWorldPosition().getActualDimension();
below = HealerQueue.getExact(new WorldPosition(sb.getWorldPosition().Position.moveDown(), level));
}
switch(HealerQueue.Pass)
{
case 0:
{
// Pass 1. Set all positions to bedrock
// The code will check is the block is solid. If the block below it is not solid, it will set it to air, regardless of if it is a falling block of not
if(HealerQueue.ToHeal.size()==0)
{
// Move the validate list back into healer queue, and increment pass
OTEMod.LOGGER.info("Pass 1 completed, moving to pass 2");
HealerQueue.Pass=1;
HealerQueue.ToHeal = HealerQueue.ToValidate;
HealerQueue.ToValidate = new ArrayList<>();
HealerQueue.dump();
break; // Exit this loop
}
if(below == null){
// This line will prevent the block below from getting set to Sculk
below = StoredBlock.getSculk(new WorldPosition(sb.getWorldPosition().Position.moveDown(), level)); // below is null so it is a unknown, accept a loss if its a falling block
}
if(!sb.getState().isAir() && below.getState().isAir())
{
HealRunner.scheduleHeal(StoredBlock.getSculk(below.getWorldPosition()));
skipWait=false;
}else {
if(!sb.getState().isAir())
{
HealRunner.scheduleHeal(StoredBlock.getSculk(sb.getWorldPosition()));
skipWait=false;
} else {
skipWait=true;
}
}
HealerQueue.ToValidate.add(sb);
HealerQueue.ToHeal.remove(sb);
break;
}
case 1:
{
// Pass 2 only sets the solid blocks
if(HealerQueue.ToHeal.size()==0)
{
OTEMod.LOGGER.info("Pass 2 completed, moving to pass 3");
HealerQueue.Pass++;
HealerQueue.ToHeal = HealerQueue.ToValidate;
HealerQueue.ToValidate = new ArrayList<>();
HealerQueue.dump();
break;
}
if(!sb.getState().isAir())
{
skipWait=false;
HealRunner.scheduleHeal(sb);
}else{
skipWait=true;
HealerQueue.ToValidate.add(sb);
}
HealerQueue.ToHeal.remove(sb);
break;
}
case 2:
{
// Pass 3 removes bedrock by setting blocks that are air
if(HealerQueue.ToHeal.size()==0)
{
OTEMod.LOGGER.info("Pass 3 has been completed. Ending restore");
HealerQueue.Pass=0;
HealerQueue.ToHeal.clear();
HealerQueue.ToValidate.clear();
HealerQueue.dump();
break;
}
HealerQueue.ToHeal.remove(sb);
if(sb.getState().isAir())
{
BlockState bs = sb.getWorldPosition().getActualDimension().getBlockState(sb.getPos());
if(!bs.isAir() && !bs.is(Blocks.SCULK))
{
skipWait=true;
continue;
}
if(!bs.isAir()){
skipWait=false;
HealRunner.scheduleHeal(sb);
}else skipWait=true;
}else skipWait=true;
break;
}
default:
{
HealerQueue.Pass=0;
OTEMod.LOGGER.info("/!\\ ALERT /!\\\n\nWARNING: Unknown pass operation was added to the HealQueue");
break;
}
}
}catch(Exception e){}
}
OTEMod.ALIVE=false;
/*while(OTEMod.ALIVE) // do nothing, code is disabled here.
{
try{
// Loop back to start if no items in queue
if(HealerQueue.ToHeal.size()==0){
@ -94,13 +233,10 @@ public class HealerManager implements Runnable
continue;
}
// Play a popping sound at the block position
final SoundEvent pop = SoundEvents.ITEM_PICKUP;
// Get the first block in the list
final StoredBlock sb = HealerQueue.locateHighestBlock(HealerQueue.ToHeal);
final ServerLevel level = sb.getWorldPosition().getActualDimension();
// Remove the block from the queue now to prevent further issues
if( !HealerQueue.ToValidate.add(sb) )
{
@ -130,53 +266,6 @@ public class HealerManager implements Runnable
}else skipCount=0;
level.getServer().execute(new Runnable(){
public void run()
{
//BlockSnapshot bs = BlockSnapshot.create(level.dimension(), level, sb.getPos());
//BlockState current = level.getBlockState(sb.getPos());
BlockState nState = Block.updateFromNeighbourShapes(sb.getState(), level, sb.getPos());
level.setBlock(sb.getPos(), nState, Block.UPDATE_CLIENTS | Block.UPDATE_KNOWN_SHAPE); // no update?
//level.setBlocksDirty(sb.getPos(), sb.getState(), level.getBlockState(sb.getPos()));
//level.markAndNotifyBlock(sb.getPos(), level.getChunkAt(sb.getPos()), sb.getState(), level.getBlockState(sb.getPos()), 2, 0);
//level.getChunkAt(sb.getPos()).setBlockState(sb.getPos(), sb.getState(), false);
BlockEntity be = level.getBlockEntity(sb.getPos());
if(be!=null){
//be.deserializeNBT(sb.getBlockEntity());
be.load(sb.getBlockEntity());
be.setChanged();
}
// Everything is restored, play sound
SoundSource ss = SoundSource.NEUTRAL;
Vector3 v3 = sb.getWorldPosition().Position;
Random rng = new Random();
level.playSound(null, v3.asBlockPos(), pop, ss, rng.nextFloat(0.75f,1.0f), rng.nextFloat(1));
/*for(ServerPlayer player : level.players())
{
Vector3 playerPos = new Vector3(player.position());
if(sb.getWorldPosition().Position.distance(playerPos) < 15)
{
// have player's client play sound (Packet?)
}
}*/
}
});
if(!skipWait) // Only save the queue when sleeping appropriately
{
@ -197,7 +286,7 @@ public class HealerManager implements Runnable
e.printStackTrace();
}
}
}*/
OTEMod.LOGGER.info("Tearing down healer jobs. Saving remaining queue, stand by...");
try {

View file

@ -31,9 +31,17 @@ public class HealerQueue {
public static final String HealerQueueFile = "OTEHealerLastQueue.nbt";
public static final String HealerQueueDebugFile = "OTEHealerLastQueue.snbt";
public static List<StoredBlock> ToHeal = new ArrayList<StoredBlock>();
public static List<StoredBlock> ToValidate = new ArrayList<StoredBlock>();
public static List<StoredBlock> FinishedBlocks = new ArrayList<StoredBlock>();
public static List<StoredBlock> ToHeal = new ArrayList<StoredBlock>(); // Air and Solid Blocks get set to bedrock initially
public static List<StoredBlock> ToValidate = new ArrayList<StoredBlock>(); // This contains all the blocks except air
public static int Pass = 0;
private static List<StoredBlock> LastToHeal = new ArrayList<StoredBlock>();
private static List<StoredBlock> LastToValidate = new ArrayList<StoredBlock>();
private static int LastPass = 0;
public static HealerManager ManagerInstance=null;
public static Path getPath()
@ -68,6 +76,40 @@ public class HealerQueue {
return sb;
}
public static StoredBlock locateLowestBlock(List<StoredBlock> list)
{
StoredBlock sb = null;
double currentY = 300;
for (StoredBlock storedBlock : ToHeal) {
if(storedBlock.getWorldPosition().Position.y < currentY)
{
currentY = storedBlock.getWorldPosition().Position.y;
sb=storedBlock;
}
}
return sb;
}
public static StoredBlock getExact(WorldPosition wp)
{
for (StoredBlock storedBlock : ToHeal) {
if(storedBlock.getWorldPosition().same(wp))
{
return storedBlock;
}
}
for (StoredBlock storedBlock : ToValidate) {
if(storedBlock.getWorldPosition().same(wp))
{
return storedBlock;
}
}
return null;
}
public static boolean HasValidatePosition(BlockPos pos, ServerLevel lvl)
{
Vector3 realPos = new Vector3(pos);
@ -134,7 +176,8 @@ public class HealerQueue {
tx.start();
// Set up the HealerManager / Runner
Thread txx = new Thread(new HealerManager());
ManagerInstance = new HealerManager();
Thread txx = new Thread(ManagerInstance);
txx.start();
OTEMod.HEALER_THREAD = txx;
@ -157,6 +200,7 @@ public class HealerQueue {
tag.put("queue", lst);
tag.put("validate", lst2);
tag.putInt("pass", HealerQueue.Pass);
//OTEMod.LOGGER.info("HEAL ["+HealerQueue.ToHeal.size()+"] / VALIDATE ["+HealerQueue.ToValidate.size()+"]");
@ -165,6 +209,48 @@ public class HealerQueue {
return tag;
}
public static List<StoredBlock> removeSame(List<StoredBlock> other)
{
other=removeSameFrom(ToHeal, other);
other=removeSameFrom(ToValidate,other);
return other;
}
public static List<StoredBlock> removeSameFrom(List<StoredBlock> stored, List<StoredBlock> other)
{
for(int i = 0;i<stored.size();i++)
{
for(int x = 0;x<other.size();x++)
{
if(other.get(x).getWorldPosition().same(stored.get(i).getWorldPosition()))
{
// Both are same
other.remove(x);
x=-1; // Reset indexing
}
}
}
return other;
}
public static void removeExact(WorldPosition pos)
{
for (int i = 0; i < ToHeal.size(); i++) {
if(ToHeal.get(i).getWorldPosition().same(pos)){
ToHeal.remove(i);
}
}
for (int i = 0; i < ToValidate.size(); i++) {
if(ToValidate.get(i).getWorldPosition().same(pos)){
ToValidate.remove(i);
}
}
}
public static void deserialize(CompoundTag tag)
{
OTEMod.HEALER_WAIT=true;
@ -199,11 +285,25 @@ public class HealerQueue {
}
}
HealerQueue.Pass = tag.getInt("pass");
OTEMod.LOGGER.info("Finished loading validation queue for healer ["+HealerQueue.ToValidate.size()+"] items");
OTEMod.HEALER_WAIT=false;
}
public static boolean dirty()
{
if(ToHeal!=LastToHeal)return true;
if(ToValidate!=LastToValidate)return true;
if(Pass != LastPass)return true;
return false;
}
public static void dump() throws IOException
{
LastToHeal = ToHeal;
LastToValidate = ToValidate;
LastPass = Pass;
CompoundTag serialized = HealerQueue.serialize();
if(OTEServerConfig.DEBUG_HEALER.get())
{

View file

@ -0,0 +1,54 @@
package dev.zontreck.otemod.antigrief;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import dev.zontreck.otemod.OTEMod;
public class HealerWorker implements Runnable
{
private final List<StoredBlock> proc;
public boolean alive=false; // These are the
public boolean doTick = false;
public Thread MyThread;
public HealerWorker(List<StoredBlock> toProcess)
{
// TODO: Make this a individualized heal worker that does the task of the current HealerManager, but with a central list of positions for de-duplication. But that a second worker would not process the same positions
// The goal is to have a worker so a second explosion elsewhere does not reset the entire thing
for (StoredBlock sBlock : toProcess) {
if(!sBlock.claimed()) // Checks if a thread has been set on this storage block yet or not. This data does not get serialized.
sBlock.setClaimed(); // We are not yet in a new thread but we know we will be soon.
}
proc = toProcess;
}
@Override
public void run()
{
MyThread=Thread.currentThread();
HealerQueue.ManagerInstance.registerWorker(this);
for (StoredBlock storedBlock : proc) {
storedBlock.setClaimedBy(Thread.currentThread());
}
while(alive){
// Stay alive
// The tick event will be fired when appropriate
if(doTick)tick();
doTick=false;
}
OTEMod.LOGGER.info(Thread.currentThread().getName()+" has completed healing an area. Worker is now dismantling");
HealerQueue.ManagerInstance.deregisterWorker(this);
}
public void tick()
{
// A tick in the healer worker tells it to repair a block
// The healer manager is responsible for dispatching ticks
}
}

View file

@ -1,5 +1,7 @@
package dev.zontreck.otemod.antigrief;
import java.time.Instant;
import dev.zontreck.libzontreck.exceptions.InvalidDeserialization;
import dev.zontreck.libzontreck.vectors.Vector3;
import dev.zontreck.libzontreck.vectors.WorldPosition;
@ -8,17 +10,73 @@ import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.NbtUtils;
import net.minecraft.nbt.Tag;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
public class StoredBlock {
public class StoredBlock implements Comparable
{
public static final StoredBlock getBedrock(WorldPosition pos){
StoredBlock sb = new StoredBlock(pos.Position.asBlockPos(), Blocks.BEDROCK.defaultBlockState(), pos.getActualDimension());
return sb;
}
public static final StoredBlock getAir(WorldPosition pos){
StoredBlock sb = new StoredBlock(pos.Position.asBlockPos(), Blocks.AIR.defaultBlockState(), pos.getActualDimension());
return sb;
}
public static final StoredBlock getSculk(WorldPosition pos){
StoredBlock sb = new StoredBlock(pos.Position.asBlockPos(), Blocks.SCULK.defaultBlockState() ,pos.getActualDimension());
return sb;
}
public static final int UNSET = 0;
public static final int PHASE1 = 1;
public static final int PHASE2 = 2;
public static final int PHSAE3 = 4;
public CompoundTag blockData;
private WorldPosition position;
private BlockState state;
private CompoundTag blockEntity;
private boolean claim = false;
private long claimed_at = 0;
private Thread claimed_by;
public void setClaimed()
{
claimed_at = Instant.now().getEpochSecond();
claim=true;
}
public boolean claimed()
{
if(claimed_by == null)
{
if(claim)
{
if(Instant.now().getEpochSecond() > claimed_at+30)
{
claim=false;
claimed_at = 0; // The claim timed out as no thread was set
return false;
}else return true; // Temporary lock on claim
}else return false; // Not claimed
}else return true; // Permanent process lock
}
public void setClaimedBy(Thread tx){
claimed_by=tx;
}
public StoredBlock(final BlockPos pos, final BlockState toSave, final ServerLevel lvl)
{
position = new WorldPosition(new Vector3(pos), lvl);
@ -32,6 +90,7 @@ public class StoredBlock {
}
public final BlockPos getPos()
{
return position.Position.asBlockPos();
@ -100,6 +159,21 @@ public class StoredBlock {
final CompoundTag tmp = tag.getCompound("entity");
blockEntity = tmp.isEmpty() ? null : tmp;
}
@Override
public int compareTo(Object o) {
if(o instanceof StoredBlock)
{
StoredBlock sb = (StoredBlock)o;
if(sb.position.same(position))
{
if(sb.state.equals(state))
{
return 0;
}return -1;
}else return -1;
}return -1;
}
}