Compare commits

...

3 Commits

Author SHA1 Message Date
8fff833e84 fix: correct reflected fields for chunk needs saving
- fixes #4064
2023-07-16 14:46:26 +01:00
f867867a42 Fix typo in comment 2023-07-15 19:56:44 +02:00
59eefd6865 fix: even better handling of schematic heights (#4102) 2023-07-10 23:13:36 +02:00
9 changed files with 99 additions and 56 deletions

View File

@ -34,6 +34,7 @@ import com.plotsquared.core.queue.ZeroedDelegateScopedQueueCoordinator;
import com.plotsquared.core.util.ChunkManager;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.math.BlockVector2;
import com.sk89q.worldedit.math.BlockVector3;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bukkit.HeightMap;
@ -420,7 +421,11 @@ public class BukkitPlotGenerator extends ChunkGenerator implements GeneratorWrap
if (lastPlotArea != null && name.equals(this.levelName) && chunkX == lastChunkX && chunkZ == lastChunkZ) {
return lastPlotArea;
}
PlotArea area = UncheckedWorldLocation.at(name, chunkX << 4, 0, chunkZ << 4).getPlotArea();
BlockVector3 loc = BlockVector3.at(chunkX << 4, 0, chunkZ << 4);
if (lastPlotArea != null && lastPlotArea.getRegion().contains(loc) && lastPlotArea.getRegion().contains(loc)) {
return lastPlotArea;
}
PlotArea area = UncheckedWorldLocation.at(name, loc).getPlotArea();
if (area == null) {
throw new IllegalStateException(String.format(
"Cannot generate chunk that does not belong to a plot area. World: %s",

View File

@ -26,6 +26,7 @@ import com.plotsquared.core.plot.Plot;
import com.plotsquared.core.plot.PlotArea;
import com.plotsquared.core.plot.world.PlotAreaManager;
import com.plotsquared.core.plot.world.SinglePlotArea;
import com.plotsquared.core.util.ReflectionUtils;
import com.plotsquared.core.util.ReflectionUtils.RefClass;
import com.plotsquared.core.util.ReflectionUtils.RefField;
import com.plotsquared.core.util.ReflectionUtils.RefMethod;
@ -64,9 +65,11 @@ public class ChunkListener implements Listener {
private final PlotAreaManager plotAreaManager;
private final int version;
private RefMethod methodSetUnsaved;
private RefMethod methodGetHandleChunk;
private RefMethod methodGetHandleWorld;
private RefField mustSave;
private RefField mustNotSave;
private Object objChunkStatusFull = null;
/*
private RefMethod methodGetFullChunk;
private RefMethod methodGetBukkitChunk;
@ -79,7 +82,6 @@ public class ChunkListener implements Listener {
*/
private Chunk lastChunk;
private boolean ignoreUnload = false;
private boolean isTrueForNotSave = true;
@Inject
public ChunkListener(final @NonNull PlotAreaManager plotAreaManager) {
@ -90,22 +92,27 @@ public class ChunkListener implements Listener {
}
try {
RefClass classCraftWorld = getRefClass("{cb}.CraftWorld");
this.methodGetHandleWorld = classCraftWorld.getMethod("getHandle");
RefClass classCraftChunk = getRefClass("{cb}.CraftChunk");
this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle");
ReflectionUtils.RefClass classChunkAccess = getRefClass("net.minecraft.world.level.chunk.IChunkAccess");
this.methodSetUnsaved = classChunkAccess.getMethod("a", boolean.class);
try {
this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle");
} catch (NoSuchMethodException ignored) {
try {
RefClass classChunkStatus = getRefClass("net.minecraft.world.level.chunk.ChunkStatus");
this.objChunkStatusFull = classChunkStatus.getRealClass().getField("n").get(null);
this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle", classChunkStatus.getRealClass());
} catch (NoSuchMethodException ex) {
throw new RuntimeException(ex);
}
}
try {
if (version < 17) {
RefClass classChunk = getRefClass("{nms}.Chunk");
if (version == 13) {
this.mustSave = classChunk.getField("mustSave");
this.isTrueForNotSave = false;
} else {
this.mustSave = classChunk.getField("mustNotSave");
}
this.mustNotSave = classChunk.getField("mustNotSave");
} else {
RefClass classChunk = getRefClass("net.minecraft.world.level.chunk.Chunk");
this.mustSave = classChunk.getField("mustNotSave");
this.mustNotSave = classChunk.getField("mustNotSave");
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
@ -167,10 +174,13 @@ public class ChunkListener implements Listener {
if (safe && shouldSave(world, chunk.getX(), chunk.getZ())) {
return false;
}
Object c = this.methodGetHandleChunk.of(chunk).call();
RefField.RefExecutor field = this.mustSave.of(c);
if ((Boolean) field.get() != isTrueForNotSave) {
field.set(isTrueForNotSave);
Object c = objChunkStatusFull != null
? this.methodGetHandleChunk.of(chunk).call(objChunkStatusFull)
: this.methodGetHandleChunk.of(chunk).call();
RefField.RefExecutor field = this.mustNotSave.of(c);
methodSetUnsaved.of(c).call(false);
if (!((Boolean) field.get())) {
field.set(true);
if (chunk.isLoaded()) {
ignoreUnload = true;
chunk.unload(false);

View File

@ -1167,7 +1167,7 @@ public class PlayerEventListener implements Listener {
}
}
if (type.isEdible()) {
//Allow all players to eat while also allowing the block place event ot be fired
//Allow all players to eat while also allowing the block place event to be fired
return;
}
if (type == Material.ARMOR_STAND) {

View File

@ -31,45 +31,39 @@ import org.bukkit.event.Listener;
import org.bukkit.event.world.ChunkEvent;
import org.bukkit.event.world.ChunkLoadEvent;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import static com.plotsquared.core.util.ReflectionUtils.getRefClass;
public class SingleWorldListener implements Listener {
private final Method methodGetHandleChunk;
private Field shouldSave = null;
private final Method methodSetUnsaved;
private Method methodGetHandleChunk;
private Object objChunkStatusFull = null;
public SingleWorldListener() throws Exception {
ReflectionUtils.RefClass classCraftChunk = getRefClass("{cb}.CraftChunk");
this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle").getRealMethod();
ReflectionUtils.RefClass classChunkAccess = getRefClass("net.minecraft.world.level.chunk.IChunkAccess");
this.methodSetUnsaved = classChunkAccess.getMethod("a", boolean.class).getRealMethod();
try {
if (PlotSquared.platform().serverVersion()[1] < 17) {
ReflectionUtils.RefClass classChunk = getRefClass("{nms}.Chunk");
if (PlotSquared.platform().serverVersion()[1] == 13) {
this.shouldSave = classChunk.getField("mustSave").getRealField();
} else {
this.shouldSave = classChunk.getField("s").getRealField();
}
} else if (PlotSquared.platform().serverVersion()[1] == 17) {
ReflectionUtils.RefClass classChunk = getRefClass("net.minecraft.world.level.chunk.Chunk");
this.shouldSave = classChunk.getField("r").getRealField();
} else if (PlotSquared.platform().serverVersion()[1] == 18) {
ReflectionUtils.RefClass classChunk = getRefClass("net.minecraft.world.level.chunk.IChunkAccess");
this.shouldSave = classChunk.getField("b").getRealField();
this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle").getRealMethod();
} catch (NoSuchMethodException ignored) {
try {
ReflectionUtils.RefClass classChunkStatus = getRefClass("net.minecraft.world.level.chunk.ChunkStatus");
this.objChunkStatusFull = classChunkStatus.getRealClass().getField("n").get(null);
this.methodGetHandleChunk = classCraftChunk.getMethod("getHandle", classChunkStatus.getRealClass()).getRealMethod();
} catch (NoSuchMethodException ex) {
throw new RuntimeException(ex);
}
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
public void markChunkAsClean(Chunk chunk) {
try {
Object nmsChunk = methodGetHandleChunk.invoke(chunk);
if (shouldSave != null) {
this.shouldSave.set(nmsChunk, false);
}
Object nmsChunk = objChunkStatusFull != null
? this.methodGetHandleChunk.invoke(chunk, objChunkStatusFull)
: this.methodGetHandleChunk.invoke(chunk);
methodSetUnsaved.invoke(nmsChunk, false);
} catch (Throwable e) {
e.printStackTrace();
}
@ -85,7 +79,12 @@ public class SingleWorldListener implements Listener {
if (!SinglePlotArea.isSinglePlotWorld(name)) {
return;
}
int x = event.getChunk().getX();
int z = event.getChunk().getZ();
if (x < 16 && x > -16 && z < 16 && z > -16) {
// Allow spawn to generate
return;
}
markChunkAsClean(event.getChunk());
}

View File

@ -69,8 +69,8 @@ public class HybridGen extends IndependentPlotGenerator {
EnumSet<SchematicFeature> features
) {
int minY; // Math.min(world.PLOT_HEIGHT, world.ROAD_HEIGHT);
if ((features.contains(SchematicFeature.ROAD) && Settings.Schematics.PASTE_ROAD_ON_TOP)
|| (!features.contains(SchematicFeature.ROAD) && Settings.Schematics.PASTE_ON_TOP)) {
boolean isRoad = features.contains(SchematicFeature.ROAD);
if ((isRoad && Settings.Schematics.PASTE_ROAD_ON_TOP) || (!isRoad && Settings.Schematics.PASTE_ON_TOP)) {
minY = world.SCHEM_Y;
} else {
minY = world.getMinBuildHeight();

View File

@ -162,6 +162,7 @@ public class HybridPlotManager extends ClassicPlotManager {
} else {
minY = hybridPlotWorld.getMinBuildHeight();
}
int schemYDiff = (isRoad ? hybridPlotWorld.getRoadYStart() : hybridPlotWorld.getPlotYStart()) - minY;
BaseBlock airBlock = BlockTypes.AIR.getDefaultState().toBaseBlock();
for (int x = pos1.getX(); x <= pos2.getX(); x++) {
short absX = (short) ((x - hybridPlotWorld.ROAD_OFFSET_X) % size);
@ -178,10 +179,14 @@ public class HybridPlotManager extends ClassicPlotManager {
for (int y = 0; y < blocks.length; y++) {
if (blocks[y] != null) {
queue.setBlock(x, minY + y, z, blocks[y]);
} else if (!isRoad) {
// This is necessary, otherwise any blocks not specified in the schematic will remain after a clear
// Do not set air for road as this may cause cavernous roads when debugroadregen is used
} else if (y > schemYDiff) {
// This is necessary, otherwise any blocks not specified in the schematic will remain after a clear.
// This should only be done where the schematic has actually "started"
queue.setBlock(x, minY + y, z, airBlock);
} else if (isRoad) {
queue.setBlock(x, minY + y, z, hybridPlotWorld.ROAD_BLOCK.toPattern());
} else {
queue.setBlock(x, minY + y, z, hybridPlotWorld.MAIN_BLOCK.toPattern());
}
}
}

View File

@ -76,6 +76,9 @@ public class HybridPlotWorld extends ClassicPlotWorld {
* The Y level at which schematic generation will start, lowest of either road or plot schematic generation.
*/
public int SCHEM_Y;
private int plotY;
private int roadY;
private Location SIGN_LOCATION;
private File root = null;
private int lastOverlayHeightError = Integer.MIN_VALUE;
@ -252,13 +255,13 @@ public class HybridPlotWorld extends ClassicPlotWorld {
SCHEM_Y = schematicStartHeight();
// plotY and roadY are important to allow plot and/or road schematic "overflow" into each other without causing AIOOB
// exceptions when attempting either to set blocks to, or get block from G_SCH
// plotY and roadY are important to allow plot and/or road schematic "overflow" into each other
// without causing AIOOB exceptions when attempting either to set blocks to, or get block from G_SCH
// Default plot schematic start height, normalized to the minimum height schematics are pasted from.
int plotY = PLOT_HEIGHT - SCHEM_Y;
plotY = PLOT_HEIGHT - SCHEM_Y;
int minRoadWall = Settings.Schematics.USE_WALL_IN_ROAD_SCHEM_HEIGHT ? Math.min(ROAD_HEIGHT, WALL_HEIGHT) : ROAD_HEIGHT;
// Default road schematic start height, normalized to the minimum height schematics are pasted from.
int roadY = minRoadWall - SCHEM_Y;
roadY = minRoadWall - SCHEM_Y;
int worldGenHeight = getMaxGenHeight() - getMinGenHeight() + 1;
@ -267,14 +270,15 @@ public class HybridPlotWorld extends ClassicPlotWorld {
// SCHEM_Y should be normalised to the plot "start" height
if (schematic3 != null) {
plotSchemHeight = maxSchematicHeight = schematic3.getClipboard().getDimensions().getY();
if (maxSchematicHeight == worldGenHeight) {
plotSchemHeight = schematic3.getClipboard().getDimensions().getY();
if (plotSchemHeight == worldGenHeight) {
SCHEM_Y = getMinGenHeight();
plotY = 0;
} else if (!Settings.Schematics.PASTE_ON_TOP) {
SCHEM_Y = getMinBuildHeight();
plotY = 0;
}
maxSchematicHeight = plotY + plotSchemHeight;
}
int roadSchemHeight;
@ -554,4 +558,24 @@ public class HybridPlotWorld extends ClassicPlotWorld {
return this.root;
}
/**
* Get the y value where the plot schematic should be pasted from.
*
* @return plot schematic y start value
* @since TODO
*/
public int getPlotYStart() {
return SCHEM_Y + plotY;
}
/**
* Get the y value where the road schematic should be pasted from.
*
* @return road schematic y start value
* @since TODO
*/
public int getRoadYStart() {
return SCHEM_Y + roadY;
}
}

View File

@ -668,7 +668,7 @@ public class HybridUtils {
}
if (condition) {
BaseBlock[] blocks = plotWorld.G_SCH.get(MathMan.pair(absX, absZ));
int minY = Settings.Schematics.PASTE_ROAD_ON_TOP ? plotWorld.SCHEM_Y : area.getMinGenHeight() + 1;
int minY = plotWorld.getRoadYStart();
int maxDy = Math.max(extend, blocks.length);
for (int dy = 0; dy < maxDy; dy++) {
if (dy > blocks.length - 1) {

View File

@ -29,17 +29,17 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@Singleton
public class DefaultPlotAreaManager implements PlotAreaManager {
final PlotArea[] noPlotAreas = new PlotArea[0];
private final Map<String, PlotWorld> plotWorlds = new HashMap<>();
private final Map<String, PlotWorld> plotWorlds = new ConcurrentHashMap<>();
@Override
public @NonNull PlotArea[] getAllPlotAreas() {