mirror of
https://github.com/PaperMC/Paper.git
synced 2026-11-04 08:52:01 -05:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6409d5ce9e | ||
|
|
2de25adfc9 | ||
|
|
4731020c9d | ||
|
|
dbd8747173 | ||
|
|
d4895653fa | ||
|
|
4397b319a7 | ||
|
|
298a8bfd32 | ||
|
|
bcf5a3fffe | ||
|
|
84a73d8984 | ||
|
|
f46fbbe20b | ||
|
|
ad45c84240 | ||
|
|
e4114b0e0e | ||
|
|
d56b548552 | ||
|
|
8e32dcbfe7 | ||
|
|
6968eb065e | ||
|
|
d55864cc09 | ||
|
|
c77b8418bf | ||
|
|
f4074a073e | ||
|
|
05eb2e4543 |
No files matched your search
@@ -10,8 +10,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Called when a block is ignited. If you want to catch when a Player places
|
||||
* fire, you need to use {@link BlockPlaceEvent}.
|
||||
* Called when a block is ignited.
|
||||
* <p>
|
||||
* If this event is cancelled, the block will not be ignited.
|
||||
*/
|
||||
|
||||
@@ -23838,7 +23838,7 @@ index 3c1490ac7c259da04031db2f170e0c0a5f512191..470d7c770ae9d045b97e2df145cfe3cf
|
||||
}
|
||||
}
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index e5925d778c28bd19ee4cb911fb115edc6351c26b..b4d7ccc2fd9e6c928445b09dbc6ac896c81fc2ef 100644
|
||||
index c11e2ce21e43f677bfccb65e6b5ad374912381fa..9aae782f8f9f1c0f7eae394ec0a1bda79e93f6f4 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -191,7 +191,7 @@ import net.minecraft.world.scores.ScoreboardSaveData;
|
||||
@@ -23943,7 +23943,7 @@ index e5925d778c28bd19ee4cb911fb115edc6351c26b..b4d7ccc2fd9e6c928445b09dbc6ac896
|
||||
|
||||
public MinecraftServer(
|
||||
// CraftBukkit start
|
||||
@@ -843,7 +929,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -853,7 +939,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
level.setSpawnSettings(level.isSpawningMonsters()); // Paper - per level difficulty (from setDifficulty(ServerLevel, Difficulty, boolean))
|
||||
this.updateEffectiveRespawnData();
|
||||
this.forceTicks = false; // CraftBukkit
|
||||
@@ -23952,7 +23952,7 @@ index e5925d778c28bd19ee4cb911fb115edc6351c26b..b4d7ccc2fd9e6c928445b09dbc6ac896
|
||||
new org.bukkit.event.world.WorldLoadEvent(level.getWorld()).callEvent(); // Paper - call WorldLoadEvent
|
||||
}
|
||||
|
||||
@@ -866,6 +952,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -876,6 +962,11 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
public abstract boolean shouldRconBroadcast();
|
||||
|
||||
public boolean saveAllChunks(final boolean silent, final boolean flush, final boolean force) {
|
||||
@@ -23964,7 +23964,7 @@ index e5925d778c28bd19ee4cb911fb115edc6351c26b..b4d7ccc2fd9e6c928445b09dbc6ac896
|
||||
this.scoreboard.storeToSaveDataIfDirty(this.getDataStorage().computeIfAbsent(ScoreboardSaveData.TYPE));
|
||||
boolean result = false;
|
||||
|
||||
@@ -874,7 +965,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -884,7 +975,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
LOGGER.info("Saving chunks for level '{}'/{}", level, level.dimension().identifier());
|
||||
}
|
||||
|
||||
@@ -23973,7 +23973,7 @@ index e5925d778c28bd19ee4cb911fb115edc6351c26b..b4d7ccc2fd9e6c928445b09dbc6ac896
|
||||
result = true;
|
||||
}
|
||||
|
||||
@@ -971,7 +1062,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -981,7 +1072,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23982,7 +23982,7 @@ index e5925d778c28bd19ee4cb911fb115edc6351c26b..b4d7ccc2fd9e6c928445b09dbc6ac896
|
||||
this.nextTickTimeNanos = Util.getNanos() + TimeUtil.NANOSECONDS_PER_MILLISECOND;
|
||||
|
||||
for (ServerLevel levelx : this.getAllLevels()) {
|
||||
@@ -982,17 +1073,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -992,17 +1083,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
this.waitUntilNextTick();
|
||||
}
|
||||
|
||||
@@ -24007,7 +24007,7 @@ index e5925d778c28bd19ee4cb911fb115edc6351c26b..b4d7ccc2fd9e6c928445b09dbc6ac896
|
||||
|
||||
this.isSaving = false;
|
||||
this.savedDataStorage.close();
|
||||
@@ -1013,6 +1101,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1023,6 +1111,14 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
this.services().nameToIdCache().save(false); // Paper - Perf: Async GameProfileCache saving
|
||||
}
|
||||
// Spigot end
|
||||
@@ -24022,7 +24022,7 @@ index e5925d778c28bd19ee4cb911fb115edc6351c26b..b4d7ccc2fd9e6c928445b09dbc6ac896
|
||||
// Paper start - Improved watchdog support - move final shutdown items here
|
||||
Util.shutdownExecutors();
|
||||
this.onServerExit();
|
||||
@@ -1107,16 +1203,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1117,16 +1213,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
// execute small amounts of other tasks just in case the number of tasks we are
|
||||
// draining is large - chunk system and packet processing may be latency sensitive
|
||||
|
||||
@@ -24057,7 +24057,7 @@ index e5925d778c28bd19ee4cb911fb115edc6351c26b..b4d7ccc2fd9e6c928445b09dbc6ac896
|
||||
profiler.pop(); // moonrise:run_all_chunk
|
||||
profiler.pop(); // moonrise:run_all_tasks
|
||||
|
||||
@@ -1417,6 +1528,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1427,6 +1538,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
|
||||
private boolean pollTaskInternal() {
|
||||
if (super.pollTask()) {
|
||||
@@ -24065,7 +24065,7 @@ index e5925d778c28bd19ee4cb911fb115edc6351c26b..b4d7ccc2fd9e6c928445b09dbc6ac896
|
||||
return true;
|
||||
} else {
|
||||
boolean ret = false; // Paper - force execution of all worlds, do not just bias the first
|
||||
@@ -1558,6 +1670,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1568,6 +1680,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
// Paper - improve tick loop - moved into runAllTasksAtTickStart
|
||||
this.runAllTasksAtTickStart(); // Paper - improve tick loop
|
||||
this.tickServer(sprinting ? () -> false : this::haveTime);
|
||||
@@ -24079,7 +24079,7 @@ index e5925d778c28bd19ee4cb911fb115edc6351c26b..b4d7ccc2fd9e6c928445b09dbc6ac896
|
||||
this.tickFrame.end();
|
||||
this.recordEndOfTick(); // Paper - improve tick loop
|
||||
profiler.pop();
|
||||
@@ -2600,6 +2719,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -2610,6 +2729,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30053,7 +30053,7 @@ index e07a7bda45146686a6ac2d20507df9fc8630514f..f84e651e2a34e76a0a71993332e4be1b
|
||||
|
||||
// Paper start - Affects Spawning API
|
||||
diff --git a/net/minecraft/world/level/Level.java b/net/minecraft/world/level/Level.java
|
||||
index 81c3f9f04040b6cfa014ce55e517bd9b374ec252..0958fe0438c3e934b885acf78a0f8003ef8bb694 100644
|
||||
index 30183d8752aa397a5c1fc554da49f595be286534..5566f449fb79b04822e533df4fd6de42116ffd14 100644
|
||||
--- a/net/minecraft/world/level/Level.java
|
||||
+++ b/net/minecraft/world/level/Level.java
|
||||
@@ -87,6 +87,7 @@ import net.minecraft.world.level.storage.LevelData;
|
||||
|
||||
@@ -5,10 +5,10 @@ Subject: [PATCH] Incremental chunk and player saving
|
||||
|
||||
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index 40a500011e7fcb6f5b07ff289f737ed9d3550296..da8c04aa02032c4c582709727f8f7f0b66b697b1 100644
|
||||
index 1e88021d476710cf4fce9bf50c02993e076e36ee..8c4b40362f77cdea6e57315a6639006ac7ab27c7 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -958,7 +958,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -968,7 +968,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
}
|
||||
public boolean saveAllChunks(final boolean silent, final boolean flush, final boolean force, final boolean close) {
|
||||
// Paper end - add close param
|
||||
@@ -17,7 +17,7 @@ index 40a500011e7fcb6f5b07ff289f737ed9d3550296..da8c04aa02032c4c582709727f8f7f0b
|
||||
boolean result = false;
|
||||
|
||||
for (ServerLevel level : this.getAllLevels()) {
|
||||
@@ -970,13 +970,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -980,13 +980,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
result = true;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ index 40a500011e7fcb6f5b07ff289f737ed9d3550296..da8c04aa02032c4c582709727f8f7f0b
|
||||
|
||||
if (flush) {
|
||||
for (ServerLevel level : this.getAllLevels()) {
|
||||
@@ -990,11 +984,25 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1000,11 +994,25 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ index 40a500011e7fcb6f5b07ff289f737ed9d3550296..da8c04aa02032c4c582709727f8f7f0b
|
||||
boolean result = this.saveAllChunks(silent, flush, force);
|
||||
this.warnOnLowDiskSpace();
|
||||
var5 = result;
|
||||
@@ -1641,9 +1649,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1651,9 +1659,31 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
}
|
||||
|
||||
this.ticksUntilAutosave--;
|
||||
|
||||
@@ -20,10 +20,10 @@ index 2bc436cdf5180a7943c45fabb9fbbedae6f7db56..f312a7f5b1b2a777ab36b94ce7cbf387
|
||||
|
||||
@Override
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index da8c04aa02032c4c582709727f8f7f0b66b697b1..3923f21c67165e2ecd2c9cecd5266758435a6806 100644
|
||||
index 8c4b40362f77cdea6e57315a6639006ac7ab27c7..a5217deb236ee922d020a7a6a03141a5f67acd77 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -1784,32 +1784,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1794,32 +1794,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,10 +48,10 @@ index 0000000000000000000000000000000000000000..24a2090e068ad3c0d08705050944abdf
|
||||
+ }
|
||||
+}
|
||||
diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java
|
||||
index 3923f21c67165e2ecd2c9cecd5266758435a6806..92acd256fdf815dfb8dd743cf8ecd0eb377d996e 100644
|
||||
index a5217deb236ee922d020a7a6a03141a5f67acd77..4fdf7354bc01789cf7480b14c2fc8fe75b748563 100644
|
||||
--- a/net/minecraft/server/MinecraftServer.java
|
||||
+++ b/net/minecraft/server/MinecraftServer.java
|
||||
@@ -1836,6 +1836,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
@@ -1846,6 +1846,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
|
||||
level.hasPhysicsEvent = org.bukkit.event.block.BlockPhysicsEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - BlockPhysicsEvent
|
||||
level.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent
|
||||
level.updateLagCompensationTick(); // Paper - lag compensation
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,34 @@
|
||||
package io.papermc.paper.util.capture;
|
||||
|
||||
import java.util.Optional;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public interface BlockPlacementPredictor {
|
||||
|
||||
Optional<BlockState> getLatestBlockAt(BlockPos pos);
|
||||
|
||||
Optional<LoadedBlockState> getLatestBlockAtIfLoaded(BlockPos pos);
|
||||
|
||||
Optional<BlockEntityPlacement> getLatestBlockEntityAt(BlockPos pos);
|
||||
|
||||
record BlockEntityPlacement(boolean removed, @Nullable BlockEntity blockEntity) {
|
||||
|
||||
public static final Optional<BlockEntityPlacement> ABSENT = Optional.of(new BlockEntityPlacement(false, null));
|
||||
|
||||
public @Nullable BlockEntity res() {
|
||||
return this.removed ? null : this.blockEntity;
|
||||
}
|
||||
}
|
||||
|
||||
record LoadedBlockState(boolean present, @Nullable BlockState state) {
|
||||
|
||||
public static final Optional<LoadedBlockState> UNLOADED = Optional.of(new LoadedBlockState(false, null));
|
||||
|
||||
public @Nullable BlockState res() {
|
||||
return this.present ? this.state : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
package io.papermc.paper.util.capture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.bukkit.craftbukkit.block.CraftBlock;
|
||||
import org.bukkit.craftbukkit.block.CraftBlockStates;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/*
|
||||
The premise of this storage is to essentially "mock" block placement in the game.
|
||||
This should attempt to mirror block placement the best it can, in order to provide a read solution of possibly what blocks
|
||||
are going to be placed in the world.
|
||||
|
||||
The reason why we use a custom capture map rather than just storing the blocks placed in the world is to ensure
|
||||
that blocks are placed in the correct order, and that the vanilla block chain can occur, which includes physics updates.
|
||||
*/
|
||||
public final class CaptureRecordMap {
|
||||
|
||||
private final Map<BlockPos, CaptureRecord> recordsByPos = new HashMap<>();
|
||||
|
||||
public void setLatestBlockStateAt(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags) {
|
||||
this.add(new CaptureRecord(pos, blockState, updateFlags));
|
||||
}
|
||||
|
||||
public void setLatestBlockEntityAt(BlockPos pos, boolean clearPreviousBlockEntity, @Nullable BlockEntity add) {
|
||||
CaptureRecord oldRecord = this.recordsByPos.get(pos);
|
||||
if (oldRecord != null) {
|
||||
oldRecord.setBlockEntity(clearPreviousBlockEntity, add);
|
||||
}
|
||||
}
|
||||
|
||||
private void add(CaptureRecord record) {
|
||||
this.recordsByPos.put(record.pos, record);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return this.recordsByPos.isEmpty();
|
||||
}
|
||||
|
||||
public @Nullable BlockState getLatestBlockStateAt(BlockPos pos) {
|
||||
CaptureRecord record = this.recordsByPos.get(pos);
|
||||
if (record == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return record.blockState;
|
||||
}
|
||||
|
||||
// Null indicates that it's not present, no override
|
||||
// Optional empty indicates its being removed
|
||||
public @Nullable Optional<BlockEntity> getLatestBlockEntityAt(BlockPos pos) {
|
||||
CaptureRecord record = this.recordsByPos.get(pos);
|
||||
if (record == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Optional.ofNullable(record.blockEntity);
|
||||
}
|
||||
|
||||
public void applyBlockEntities(ServerLevel parent) {
|
||||
this.recordsByPos.keySet().forEach((pos) -> {
|
||||
Optional<BlockEntity> res = this.getLatestBlockEntityAt(pos);
|
||||
if (res != null && res.isPresent()) {
|
||||
parent.setBlockEntity(res.get());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void applyApiPatch(ServerLevel level) {
|
||||
this.recordsByPos.keySet().forEach((pos) -> {
|
||||
this.recordsByPos.get(pos).applyApiPatch(level);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Clean this up
|
||||
public List<org.bukkit.block.BlockState> calculateLatestSnapshots(ServerLevel level) {
|
||||
List<org.bukkit.block.BlockState> out = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<BlockPos, CaptureRecord> entry : this.recordsByPos.entrySet()) {
|
||||
CaptureRecord captureRecord = entry.getValue();
|
||||
out.add(CraftBlockStates.getBlockState(level.getWorld(), entry.getKey(), captureRecord.blockState, captureRecord.blockEntity));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public Stream<org.bukkit.block.Block> getAffectedBlocks(ServerLevel level) {
|
||||
return this.recordsByPos.keySet().stream().map(pos -> CraftBlock.at(level, pos));
|
||||
}
|
||||
|
||||
public static class CaptureRecord {
|
||||
|
||||
private final BlockPos pos;
|
||||
|
||||
private BlockState blockState;
|
||||
private @Nullable BlockEntity blockEntity;
|
||||
private boolean clearPreviousBlockEntity;
|
||||
private @Block.UpdateFlags int updateFlags;
|
||||
|
||||
public CaptureRecord(BlockPos pos, BlockState blockState, BlockEntity blockEntity) {
|
||||
this.pos = pos;
|
||||
this.blockState = blockState;
|
||||
this.blockEntity = blockEntity;
|
||||
}
|
||||
|
||||
public CaptureRecord(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags) {
|
||||
this.pos = pos;
|
||||
this.blockState = blockState;
|
||||
this.updateFlags = updateFlags;
|
||||
}
|
||||
|
||||
public CaptureRecord(boolean remove, @Nullable BlockEntity add, BlockPos pos) {
|
||||
this.clearPreviousBlockEntity = remove;
|
||||
this.blockEntity = add;
|
||||
this.pos = pos;
|
||||
}
|
||||
|
||||
public void applyApiPatch(ServerLevel level) {
|
||||
if (this.clearPreviousBlockEntity) {
|
||||
level.removeBlockEntity(this.pos);
|
||||
}
|
||||
|
||||
level.setBlock(this.pos, this.blockState, this.updateFlags);
|
||||
if (this.blockEntity != null) {
|
||||
level.setBlockEntity(this.blockEntity);
|
||||
}
|
||||
}
|
||||
|
||||
public void setBlockEntity(boolean clearPreviousBlockEntity, @Nullable BlockEntity add) {
|
||||
this.clearPreviousBlockEntity = clearPreviousBlockEntity;
|
||||
this.blockEntity = add;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package io.papermc.paper.util.capture;
|
||||
|
||||
import net.minecraft.Optionull;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.data.worldgen.features.TreeFeatures;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
|
||||
import org.bukkit.TreeType;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public final class GrowthContext {
|
||||
|
||||
public static GrowthContext empty() {
|
||||
return new GrowthContext(null, false);
|
||||
}
|
||||
|
||||
public static GrowthContext usingBoneMeal(@Nullable Player user) {
|
||||
return new GrowthContext(user, true);
|
||||
}
|
||||
|
||||
private final @Nullable Player player;
|
||||
private final boolean usedBoneMeal;
|
||||
|
||||
public boolean cancelled = false;
|
||||
public Holder<ConfiguredFeature<?, ?>> feature; // just make this a field
|
||||
|
||||
private GrowthContext(@Nullable Player player, boolean usedBoneMeal) {
|
||||
this.player = player;
|
||||
this.usedBoneMeal = usedBoneMeal;
|
||||
}
|
||||
|
||||
public org.bukkit.entity.@Nullable Player getBukkitPlayer() {
|
||||
return (org.bukkit.entity.Player) Optionull.map(this.player, Entity::getBukkitEntity);
|
||||
}
|
||||
|
||||
public boolean usedBoneMeal() {
|
||||
return this.usedBoneMeal;
|
||||
}
|
||||
|
||||
public TreeType getTreeSpecies() {
|
||||
return getTreeSpecies(this.feature);
|
||||
}
|
||||
|
||||
private static TreeType getTreeSpecies(Holder<ConfiguredFeature<?, ?>> feature) {
|
||||
if (feature.is(TreeFeatures.HUGE_RED_MUSHROOM)) {
|
||||
return TreeType.RED_MUSHROOM;
|
||||
} else if (feature.is(TreeFeatures.HUGE_BROWN_MUSHROOM)) {
|
||||
return TreeType.BROWN_MUSHROOM;
|
||||
} else if (feature.is(TreeFeatures.WARPED_FUNGUS_PLANTED)) {
|
||||
return TreeType.WARPED_FUNGUS;
|
||||
} else if (feature.is(TreeFeatures.CRIMSON_FUNGUS_PLANTED)) {
|
||||
return TreeType.CRIMSON_FUNGUS;
|
||||
} else if (feature.is(TreeFeatures.OAK) || feature.is(TreeFeatures.OAK_BEES_005)) {
|
||||
return TreeType.TREE;
|
||||
} else if (feature.is(TreeFeatures.JUNGLE_TREE)) {
|
||||
return TreeType.COCOA_TREE;
|
||||
} else if (feature.is(TreeFeatures.JUNGLE_TREE_NO_VINE)) {
|
||||
return TreeType.SMALL_JUNGLE;
|
||||
} else if (feature.is(TreeFeatures.PINE)) {
|
||||
return TreeType.TALL_REDWOOD;
|
||||
} else if (feature.is(TreeFeatures.SPRUCE)) {
|
||||
return TreeType.REDWOOD;
|
||||
} else if (feature.is(TreeFeatures.ACACIA)) {
|
||||
return TreeType.ACACIA;
|
||||
} else if (feature.is(TreeFeatures.BIRCH) || feature.is(TreeFeatures.BIRCH_BEES_005)) {
|
||||
return TreeType.BIRCH;
|
||||
} else if (feature.is(TreeFeatures.SUPER_BIRCH_BEES_0002)) {
|
||||
return TreeType.TALL_BIRCH;
|
||||
} else if (feature.is(TreeFeatures.SWAMP_OAK)) {
|
||||
return TreeType.SWAMP;
|
||||
} else if (feature.is(TreeFeatures.FANCY_OAK) || feature.is(TreeFeatures.FANCY_OAK_BEES_005)) {
|
||||
return TreeType.BIG_TREE;
|
||||
} else if (feature.is(TreeFeatures.JUNGLE_BUSH)) {
|
||||
return TreeType.JUNGLE_BUSH;
|
||||
} else if (feature.is(TreeFeatures.DARK_OAK)) {
|
||||
return TreeType.DARK_OAK;
|
||||
} else if (feature.is(TreeFeatures.MEGA_SPRUCE)) {
|
||||
return TreeType.MEGA_REDWOOD;
|
||||
} else if (feature.is(TreeFeatures.MEGA_PINE)) {
|
||||
return TreeType.MEGA_PINE;
|
||||
} else if (feature.is(TreeFeatures.MEGA_JUNGLE_TREE)) {
|
||||
return TreeType.JUNGLE;
|
||||
} else if (feature.is(TreeFeatures.AZALEA_TREE)) {
|
||||
return TreeType.AZALEA;
|
||||
} else if (feature.is(TreeFeatures.MANGROVE)) {
|
||||
return TreeType.MANGROVE;
|
||||
} else if (feature.is(TreeFeatures.TALL_MANGROVE)) {
|
||||
return TreeType.TALL_MANGROVE;
|
||||
} else if (feature.is(TreeFeatures.CHERRY) || feature.is(TreeFeatures.CHERRY_BEES_005)) {
|
||||
return TreeType.CHERRY;
|
||||
} else if (feature.is(TreeFeatures.PALE_OAK) || feature.is(TreeFeatures.PALE_OAK_BONEMEAL)) {
|
||||
return TreeType.PALE_OAK;
|
||||
} else if (feature.is(TreeFeatures.PALE_OAK_CREAKING)) {
|
||||
return TreeType.PALE_OAK_CREAKING;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown tree feature: " + feature);
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package io.papermc.paper.util.capture;
|
||||
|
||||
import java.util.Optional;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
public record LayeredBlockPlacementPredictor(
|
||||
BlockPlacementPredictor... predictors
|
||||
) implements BlockPlacementPredictor {
|
||||
|
||||
@Override
|
||||
public Optional<BlockState> getLatestBlockAt(BlockPos pos) {
|
||||
for (BlockPlacementPredictor predictor : this.predictors) {
|
||||
Optional<BlockState> state = predictor.getLatestBlockAt(pos);
|
||||
if (state.isPresent()) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LoadedBlockState> getLatestBlockAtIfLoaded(BlockPos pos) {
|
||||
for (BlockPlacementPredictor predictor : this.predictors) {
|
||||
Optional<LoadedBlockState> state = predictor.getLatestBlockAtIfLoaded(pos);
|
||||
if (state.isPresent()) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BlockEntityPlacement> getLatestBlockEntityAt(BlockPos pos) {
|
||||
for (BlockPlacementPredictor predictor : this.predictors) {
|
||||
Optional<BlockEntityPlacement> state = predictor.getLatestBlockEntityAt(pos);
|
||||
if (state.isPresent()) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package io.papermc.paper.util.capture;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.function.Supplier;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public record LiveBlockPlacementLayer(WorldCapturer capturer, ServerLevel level) implements BlockPlacementPredictor {
|
||||
|
||||
@Override
|
||||
public Optional<BlockState> getLatestBlockAt(BlockPos pos) {
|
||||
return Optional.of(provideLive(() -> this.level.getBlockState(pos)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LoadedBlockState> getLatestBlockAtIfLoaded(BlockPos pos) {
|
||||
BlockState state = provideLive(() -> this.level.getBlockStateIfLoaded(pos));
|
||||
if (state == null) {
|
||||
return LoadedBlockState.UNLOADED;
|
||||
}
|
||||
|
||||
return Optional.of(new LoadedBlockState(true, state));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BlockEntityPlacement> getLatestBlockEntityAt(BlockPos pos) {
|
||||
BlockEntity blockEntity = provideLive(() -> this.level.getBlockEntity(pos));
|
||||
if (blockEntity == null) {
|
||||
return BlockEntityPlacement.ABSENT;
|
||||
}
|
||||
|
||||
return Optional.of(new BlockEntityPlacement(false, blockEntity));
|
||||
}
|
||||
|
||||
public <T> @Nullable T provideLive(Supplier<@Nullable T> valueProvider) {
|
||||
SimpleBlockCapture blockCapture = this.capturer.getCapture();
|
||||
this.capturer.releaseCapture(null);
|
||||
T value = valueProvider.get();
|
||||
this.capturer.releaseCapture(blockCapture);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
package io.papermc.paper.util.capture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.RegistryAccess;
|
||||
import net.minecraft.core.particles.ParticleOptions;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.sounds.SoundEvent;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.DifficultyInstance;
|
||||
import net.minecraft.world.attribute.EnvironmentAttributeReader;
|
||||
import net.minecraft.world.entity.Entity;
|
||||
import net.minecraft.world.entity.player.Player;
|
||||
import net.minecraft.world.flag.FeatureFlagSet;
|
||||
import net.minecraft.world.level.biome.Biome;
|
||||
import net.minecraft.world.level.biome.BiomeManager;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.border.WorldBorder;
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.status.ChunkStatus;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.entity.EntityTypeTest;
|
||||
import net.minecraft.world.level.gameevent.GameEvent;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.lighting.LevelLightEngine;
|
||||
import net.minecraft.world.level.material.Fluid;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import net.minecraft.world.level.storage.LevelData;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.minecraft.world.phys.Vec3;
|
||||
import net.minecraft.world.ticks.LevelTickAccess;
|
||||
import net.minecraft.world.ticks.ScheduledTick;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public class MinecraftCaptureBridge implements PaperCapturingWorldLevel {
|
||||
|
||||
private final ServerLevel parent;
|
||||
private final List<Runnable> queuedTasks = new ArrayList<>();
|
||||
|
||||
// Effective represents plugin set blocks -> predicted blocks -> actual server level
|
||||
private final BlockPlacementPredictor effectiveReadLayer;
|
||||
private SimpleBlockPlacementPredictor writeLayer;
|
||||
|
||||
// This is the layer that is written to in the server level potentially.
|
||||
// Mostly plugin set blocks
|
||||
private final SimpleBlockPlacementPredictor serverLevelOverlayLayer;
|
||||
|
||||
private final CapturingTickAccess<Block> blocks;
|
||||
private final CapturingTickAccess<Fluid> liquids;
|
||||
|
||||
private Consumer<Runnable> sink = this.queuedTasks::add;
|
||||
|
||||
public MinecraftCaptureBridge(ServerLevel parent, BlockPlacementPredictor baseReadLayer) {
|
||||
this.parent = parent;
|
||||
|
||||
this.serverLevelOverlayLayer = new SimpleBlockPlacementPredictor();
|
||||
SimpleBlockPlacementPredictor predictedBlocks = new SimpleBlockPlacementPredictor();
|
||||
|
||||
this.effectiveReadLayer = new LayeredBlockPlacementPredictor(
|
||||
this.serverLevelOverlayLayer, // The overlay layer represents plugin set blocks!
|
||||
predictedBlocks, // Now predicted blocks
|
||||
baseReadLayer
|
||||
);
|
||||
|
||||
this.writeLayer = predictedBlocks; // Predicting
|
||||
|
||||
this.blocks = new CapturingTickAccess<>(parent.getBlockTicks());
|
||||
this.liquids = new CapturingTickAccess<>(parent.getFluidTicks());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSeed() {
|
||||
return this.parent.getSeed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerLevel getLevel() {
|
||||
return this.parent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DifficultyInstance getCurrentDifficultyAt(BlockPos pos) {
|
||||
return this.parent.getCurrentDifficultyAt(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long nextSubTickCount() {
|
||||
return this.parent.nextSubTickCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LevelData getLevelData() {
|
||||
return this.parent.getLevelData();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable MinecraftServer getServer() {
|
||||
return this.parent.getServer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerChunkCache getChunkSource() {
|
||||
return this.parent.getChunkSource();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setBlockAndUpdate(BlockPos pos, BlockState blockState) {
|
||||
return this.setBlock(pos, blockState, Block.UPDATE_ALL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SimpleBlockCapture forkCaptureSession() {
|
||||
return this.parent.capturer.createCaptureSession(new BlockPlacementPredictor() {
|
||||
@Override
|
||||
public Optional<BlockState> getLatestBlockAt(BlockPos pos) {
|
||||
return MinecraftCaptureBridge.this.effectiveReadLayer.getLatestBlockAt(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LoadedBlockState> getLatestBlockAtIfLoaded(BlockPos pos) {
|
||||
return MinecraftCaptureBridge.this.effectiveReadLayer.getLatestBlockAtIfLoaded(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BlockEntityPlacement> getLatestBlockEntityAt(BlockPos pos) {
|
||||
return MinecraftCaptureBridge.this.effectiveReadLayer.getLatestBlockEntityAt(pos);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public RandomSource getRandom() {
|
||||
// TODO: Support rolling this back?
|
||||
return this.parent.getRandom();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playSound(@Nullable Entity except, BlockPos pos, SoundEvent sound, SoundSource source, float volume, float pitch) {
|
||||
this.addTask((level) -> level.playSound(except, pos, sound, source, volume, pitch));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addParticle(ParticleOptions particle, double x, double y, double z, double xd, double yd, double zd) {
|
||||
this.addTask((level) -> level.addParticle(particle, x, y, z, xd, yd, zd));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void levelEvent(@Nullable Entity source, int type, BlockPos pos, int data) {
|
||||
this.addTask((level) -> level.levelEvent(source, type, pos, data));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void gameEvent(Holder<GameEvent> gameEvent, Vec3 position, GameEvent.Context context) {
|
||||
this.addTask((level) -> level.gameEvent(gameEvent, position, context));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LevelLightEngine getLightEngine() {
|
||||
return this.parent.getLightEngine();
|
||||
}
|
||||
|
||||
@Override
|
||||
public WorldBorder getWorldBorder() {
|
||||
return this.parent.getWorldBorder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BlockEntity getBlockEntity(BlockPos pos) {
|
||||
return this.effectiveReadLayer.getLatestBlockEntityAt(pos)
|
||||
.map(BlockPlacementPredictor.BlockEntityPlacement::blockEntity)
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
return this.effectiveReadLayer.getLatestBlockAt(pos).orElseThrow(); // Should not ever be null, parent should pass value
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BlockState getBlockStateIfLoaded(BlockPos pos) {
|
||||
return this.effectiveReadLayer.getLatestBlockAtIfLoaded(pos).map(BlockPlacementPredictor.LoadedBlockState::state).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable FluidState getFluidIfLoaded(BlockPos pos) {
|
||||
return this.getBlockStateIfLoaded(pos).getFluidState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FluidState getFluidState(BlockPos pos) {
|
||||
return this.getBlockState(pos).getFluidState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Entity> getEntities(@Nullable Entity except, AABB bb, Predicate<? super Entity> selector) {
|
||||
return this.parent.getEntities(except, bb, selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends Entity> List<T> getEntities(EntityTypeTest<Entity, T> entityTypeTest, AABB bb, Predicate<? super T> selector) {
|
||||
return this.parent.getEntities(entityTypeTest, bb, selector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends Player> players() {
|
||||
return this.parent.players();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ChunkAccess getChunk(int chunkX, int chunkZ, ChunkStatus status, boolean loadOrGenerate) {
|
||||
return this.parent.getChunk(chunkX, chunkZ, status, loadOrGenerate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ChunkAccess getChunkIfLoadedImmediately(int x, int z) {
|
||||
return this.parent.getChunkIfLoadedImmediately(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight(Heightmap.Types type, int x, int z) {
|
||||
return this.parent.getHeight(type, x, z); // TODO?
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSkyDarken() {
|
||||
return this.parent.getSkyDarken();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BiomeManager getBiomeManager() {
|
||||
return this.parent.getBiomeManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<Biome> getUncachedNoiseBiome(int quartX, int quartY, int quartZ) {
|
||||
return this.parent.getUncachedNoiseBiome(quartX, quartY, quartZ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClientSide() {
|
||||
return this.parent.isClientSide();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSeaLevel() {
|
||||
return this.parent.getSeaLevel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DimensionType dimensionType() {
|
||||
return this.parent.dimensionType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RegistryAccess registryAccess() {
|
||||
return this.parent.registryAccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FeatureFlagSet enabledFeatures() {
|
||||
return this.parent.enabledFeatures();
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnvironmentAttributeReader environmentAttributes() {
|
||||
return this.parent.environmentAttributes();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateAtPosition(BlockPos pos, Predicate<BlockState> predicate) {
|
||||
return predicate.test(this.getBlockState(pos));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFluidAtPosition(BlockPos pos, Predicate<FluidState> predicate) {
|
||||
return predicate.test(this.getFluidState(pos));
|
||||
}
|
||||
|
||||
public boolean silentSet(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags) {
|
||||
return this.writeLayer.setBlockState(this.effectiveReadLayer, pos, blockState, updateFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setBlock(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit) {
|
||||
BlockPos copy = pos.immutable();
|
||||
this.addTask((level) -> level.setBlock(copy, blockState, updateFlags, updateLimit));
|
||||
|
||||
return this.writeLayer.setBlockState(this.effectiveReadLayer, copy, blockState, updateFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeBlock(BlockPos pos, boolean movedByPiston) {
|
||||
BlockPos copy = pos.immutable();
|
||||
this.addTask((level) -> level.removeBlock(copy, movedByPiston));
|
||||
|
||||
FluidState fluidState = this.getFluidState(copy);
|
||||
return this.silentSet(copy, fluidState.createLegacyBlock(), Block.UPDATE_ALL | (movedByPiston ? Block.UPDATE_MOVE_BY_PISTON : 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean destroyBlock(BlockPos pos, boolean dropResources, @Nullable Entity breaker, int updateLimit) {
|
||||
BlockPos copy = pos.immutable();
|
||||
this.addTask((level) -> level.destroyBlock(copy, dropResources, breaker, updateLimit));
|
||||
|
||||
BlockState blockState = this.getBlockState(copy);
|
||||
if (blockState.isAir()) {
|
||||
return false;
|
||||
} else {
|
||||
FluidState fluidState = this.getFluidState(copy);
|
||||
|
||||
return this.silentSet(copy, fluidState.createLegacyBlock(), Block.UPDATE_ALL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendBlockUpdated(BlockPos pos, BlockState old, BlockState current, @Block.UpdateFlags int updateFlags) {
|
||||
BlockPos copy = pos.immutable();
|
||||
this.addTask((level) -> level.sendBlockUpdated(copy, old, current, updateFlags));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBlockEntity(BlockEntity blockEntity) {
|
||||
this.writeLayer.getRecordMap().setLatestBlockEntityAt(blockEntity.getBlockPos().immutable(), false, blockEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setBlockSilent(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit) {
|
||||
BlockPos copy = pos.immutable();
|
||||
return this.silentSet(copy, blockState, updateFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LevelTickAccess<Block> getBlockTicks() {
|
||||
return this.blocks;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LevelTickAccess<Fluid> getFluidTicks() {
|
||||
return this.liquids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public GameRules getGameRules() {
|
||||
return this.parent.getGameRules();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTask(Consumer<ServerLevel> level) {
|
||||
this.sink.accept(() -> level.accept(this.parent));
|
||||
}
|
||||
|
||||
public @Nullable BlockState getLatestBlockState(BlockPos pos) {
|
||||
return this.effectiveReadLayer.getLatestBlockAt(pos).orElse(null);
|
||||
}
|
||||
|
||||
public @Nullable Optional<BlockEntity> getLatestBlockEntity(BlockPos pos) {
|
||||
Optional<BlockPlacementPredictor.BlockEntityPlacement> placement = this.effectiveReadLayer.getLatestBlockEntityAt(pos);
|
||||
if (placement.isEmpty() || (placement.get().blockEntity() == null && !placement.get().removed())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return placement.map(BlockPlacementPredictor.BlockEntityPlacement::blockEntity);
|
||||
}
|
||||
|
||||
public List<org.bukkit.block.BlockState> calculateLatestSnapshots(ServerLevel level) {
|
||||
return this.writeLayer.getRecordMap().calculateLatestSnapshots(level);
|
||||
}
|
||||
|
||||
public Stream<org.bukkit.block.Block> getAffectedBlocks(ServerLevel level) {
|
||||
return this.writeLayer.getRecordMap().getAffectedBlocks(level);
|
||||
}
|
||||
|
||||
public void applyTasks() {
|
||||
this.sink = Runnable::run;
|
||||
for (Runnable runnable : this.queuedTasks) {
|
||||
runnable.run();
|
||||
}
|
||||
this.blocks.apply();
|
||||
this.liquids.apply();
|
||||
|
||||
// If we have changes that the plugin applied on top of the already existing changes, we know that we can apply them.
|
||||
// So, do that!
|
||||
if (!this.serverLevelOverlayLayer.isEmpty()) {
|
||||
this.serverLevelOverlayLayer.getRecordMap().applyApiPatch(this.parent);
|
||||
}
|
||||
|
||||
// Apply block entities, those may have been written in our estimation. So just apply them.
|
||||
// I don't really like this, as I in general don't really want to apply things from the prediction layer.
|
||||
// But, these may be mutated by anything.
|
||||
if (!this.writeLayer.getRecordMap().isEmpty()) {
|
||||
this.writeLayer.getRecordMap().applyBlockEntities(this.parent);
|
||||
}
|
||||
}
|
||||
|
||||
public void allowWriteOnLevel() {
|
||||
this.writeLayer = this.serverLevelOverlayLayer;
|
||||
}
|
||||
|
||||
public static class CapturingTickAccess<T> implements LevelTickAccess<T> {
|
||||
|
||||
private final LevelTickAccess<T> wrapped;
|
||||
private final Set<BlockPos> scheduled = new HashSet<>();
|
||||
private final List<ScheduledTick<T>> ticks = new ArrayList<>();
|
||||
|
||||
public CapturingTickAccess(LevelTickAccess<T> wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean willTickThisTick(BlockPos pos, T type) {
|
||||
return this.wrapped.willTickThisTick(pos, type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schedule(ScheduledTick<T> tick) {
|
||||
this.scheduled.add(tick.pos());
|
||||
this.ticks.add(tick);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasScheduledTick(BlockPos pos, T type) {
|
||||
return this.wrapped.hasScheduledTick(pos, type) || this.scheduled.contains(pos);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int count() {
|
||||
return this.wrapped.count() + this.scheduled.size();
|
||||
}
|
||||
|
||||
public void apply() {
|
||||
this.ticks.forEach(this.wrapped::schedule);
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package io.papermc.paper.util.capture;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerChunkCache;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
|
||||
public interface PaperCapturingWorldLevel extends WorldGenLevel {
|
||||
|
||||
GameRules getGameRules();
|
||||
|
||||
void sendBlockUpdated(BlockPos pos, BlockState old, BlockState current, @Block.UpdateFlags int updateFlags);
|
||||
|
||||
void setBlockEntity(BlockEntity blockEntity);
|
||||
|
||||
boolean setBlockSilent(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit);
|
||||
|
||||
ServerChunkCache getChunkSource();
|
||||
|
||||
boolean setBlockAndUpdate(BlockPos pos, BlockState blockState);
|
||||
|
||||
void addTask(Consumer<ServerLevel> level);
|
||||
|
||||
SimpleBlockCapture forkCaptureSession();
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package io.papermc.paper.util.capture;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.gamerules.GameRules;
|
||||
|
||||
public interface ServerLevelPaperCapturingWorldLevel extends PaperCapturingWorldLevel {
|
||||
|
||||
@Override
|
||||
default GameRules getGameRules() {
|
||||
return this.getLevel().getGameRules();
|
||||
}
|
||||
|
||||
@Override
|
||||
default void sendBlockUpdated(BlockPos pos, BlockState old, BlockState current, @Block.UpdateFlags int updateFlags) {
|
||||
this.getLevel().sendBlockUpdated(pos, old, current, updateFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void setBlockEntity(BlockEntity blockEntity) {
|
||||
this.getLevel().setBlockEntity(blockEntity);
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean setBlockSilent(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit) {
|
||||
return this.getLevel().setBlock(pos, blockState, updateFlags, updateLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
default boolean setBlockAndUpdate(BlockPos pos, BlockState blockState) {
|
||||
return this.getLevel().setBlockAndUpdate(pos, blockState);
|
||||
}
|
||||
|
||||
@Override
|
||||
default void addTask(Consumer<ServerLevel> level) {
|
||||
level.accept(this.getLevel());
|
||||
}
|
||||
|
||||
@Override
|
||||
default SimpleBlockCapture forkCaptureSession() {
|
||||
return this.getLevel().capturer.createCaptureSession();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package io.papermc.paper.util.capture;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
public class SimpleBlockCapture implements AutoCloseable {
|
||||
|
||||
private final MinecraftCaptureBridge capturingWorldLevel;
|
||||
private final ServerLevel level;
|
||||
private final @Nullable SimpleBlockCapture oldCapture;
|
||||
|
||||
private boolean isOverlayingCaptureOnLevel = false;
|
||||
|
||||
public SimpleBlockCapture(BlockPlacementPredictor base, ServerLevel level, @Nullable SimpleBlockCapture oldCapture) {
|
||||
this.capturingWorldLevel = new MinecraftCaptureBridge(level, base);
|
||||
this.level = level;
|
||||
this.oldCapture = oldCapture;
|
||||
}
|
||||
|
||||
public MinecraftCaptureBridge capturingWorldLevel() {
|
||||
return this.capturingWorldLevel;
|
||||
}
|
||||
|
||||
public boolean isCapturing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<BlockState> getCapturedSnapshots() {
|
||||
return this.capturingWorldLevel.calculateLatestSnapshots(this.level);
|
||||
}
|
||||
|
||||
public Stream<Block> getAffectedBlocks() {
|
||||
return this.capturingWorldLevel.getAffectedBlocks(this.level);
|
||||
}
|
||||
|
||||
public net.minecraft.world.level.block.state.@Nullable BlockState getOverlayBlockState(BlockPos pos) {
|
||||
return this.capturingWorldLevel.getLatestBlockState(pos);
|
||||
}
|
||||
|
||||
public @Nullable Optional<BlockEntity> getOverlayBlockEntity(BlockPos pos) {
|
||||
return this.capturingWorldLevel.getLatestBlockEntity(pos);
|
||||
}
|
||||
|
||||
// This is done so that the captured blocks appear ontop of the world.
|
||||
public void overlayCaptureOnLevel() {
|
||||
this.isOverlayingCaptureOnLevel = true;
|
||||
this.capturingWorldLevel.allowWriteOnLevel();
|
||||
}
|
||||
|
||||
public boolean isOverlayingCaptureOnLevel() {
|
||||
return this.isOverlayingCaptureOnLevel;
|
||||
}
|
||||
|
||||
public void finalizePlacement() {
|
||||
this.level.capturer.releaseCapture(this.oldCapture);
|
||||
this.capturingWorldLevel.applyTasks();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.level.capturer.releaseCapture(this.oldCapture);
|
||||
}
|
||||
|
||||
public net.minecraft.world.level.block.state.@Nullable BlockState getCaptureBlockStateIfLoaded(BlockPos pos) {
|
||||
return this.capturingWorldLevel.getBlockStateIfLoaded(pos);
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package io.papermc.paper.util.capture;
|
||||
|
||||
import java.util.Optional;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.Blocks;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
|
||||
// Block state
|
||||
class SimpleBlockPlacementPredictor implements BlockPlacementPredictor {
|
||||
|
||||
private final CaptureRecordMap guesstimationMap = new CaptureRecordMap();
|
||||
|
||||
public boolean setBlockState(BlockPlacementPredictor layer, BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags) {
|
||||
BlockState oldState = layer.getLatestBlockAt(pos).orElse(Blocks.AIR.defaultBlockState());
|
||||
// Don't do any processing if the same
|
||||
if (oldState == blockState) {
|
||||
return false;
|
||||
} else {
|
||||
Block newBlock = blockState.getBlock();
|
||||
|
||||
this.setLatestBlockAt(pos, blockState, updateFlags);
|
||||
|
||||
// TODO: local heightmaps?
|
||||
// this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING).update(i, y, i2, blockState);
|
||||
// this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES).update(i, y, i2, blockState);
|
||||
// this.heightmaps.get(Heightmap.Types.OCEAN_FLOOR).update(i, y, i2, blockState);
|
||||
// this.heightmaps.get(Heightmap.Types.WORLD_SURFACE).update(i, y, i2, blockState);
|
||||
|
||||
// LIGHT ENGINE CALCULATIONS
|
||||
|
||||
boolean blockChanged = !oldState.is(newBlock);
|
||||
if (blockChanged && oldState.hasBlockEntity() && !blockState.shouldChangedStateKeepBlockEntity(oldState)) {
|
||||
this.removeBlockEntity(pos);
|
||||
}
|
||||
|
||||
// if ((blockChanged || newBlock instanceof BaseRailBlock) && ((updateFlags & Block.UPDATE_NEIGHBORS) != 0 || updateMoveByPiston)) {
|
||||
// BlockState finalOldState = oldState;
|
||||
// this.capturingWorldLevel.addTask((level) -> {
|
||||
// finalOldState.affectNeighborsAfterRemoval(level, pos, updateMoveByPiston);
|
||||
// });
|
||||
// }
|
||||
|
||||
if (blockState.hasBlockEntity()) {
|
||||
BlockEntity blockEntity = this.getLatestBlockEntityAt(pos).map(BlockEntityPlacement::blockEntity).orElse(null);
|
||||
if (blockEntity != null && !blockEntity.isValidBlockState(blockState)) {
|
||||
blockEntity = null;
|
||||
}
|
||||
|
||||
if (blockEntity == null) {
|
||||
blockEntity = ((EntityBlock) newBlock).newBlockEntity(pos, blockState);
|
||||
if (blockEntity != null) {
|
||||
this.addAndRegisterBlockEntity(blockEntity);
|
||||
}
|
||||
} else {
|
||||
blockEntity.setBlockState(blockState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private void addAndRegisterBlockEntity(BlockEntity blockEntity) {
|
||||
this.guesstimationMap.setLatestBlockEntityAt(blockEntity.getBlockPos(), false, blockEntity);
|
||||
}
|
||||
|
||||
private void removeBlockEntity(BlockPos pos) {
|
||||
this.guesstimationMap.setLatestBlockEntityAt(pos, true, null);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return this.guesstimationMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BlockEntityPlacement> getLatestBlockEntityAt(BlockPos pos) {
|
||||
Optional<BlockEntity> value = this.guesstimationMap.getLatestBlockEntityAt(pos);
|
||||
if (value == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
return value
|
||||
.map(block -> new BlockEntityPlacement(false, block))
|
||||
.or(() -> BlockEntityPlacement.ABSENT);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<BlockState> getLatestBlockAt(BlockPos pos) {
|
||||
return Optional.ofNullable(this.guesstimationMap.getLatestBlockStateAt(pos));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<LoadedBlockState> getLatestBlockAtIfLoaded(BlockPos pos) {
|
||||
return Optional.ofNullable(this.guesstimationMap.getLatestBlockStateAt(pos))
|
||||
.map((state) -> new LoadedBlockState(true, state));
|
||||
}
|
||||
|
||||
public void setLatestBlockAt(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags) {
|
||||
this.guesstimationMap.setLatestBlockStateAt(pos, blockState, updateFlags);
|
||||
}
|
||||
|
||||
public CaptureRecordMap getRecordMap() {
|
||||
return this.guesstimationMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package io.papermc.paper.util.capture;
|
||||
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
// TODO: Cleanup this state, because its held on the server world. I had proper state handling but threw it away at some point
|
||||
public class WorldCapturer {
|
||||
|
||||
public final ServerLevel level;
|
||||
|
||||
private @Nullable SimpleBlockCapture capture;
|
||||
|
||||
public WorldCapturer(Level level) {
|
||||
this.level = (ServerLevel) level;
|
||||
}
|
||||
|
||||
public SimpleBlockCapture createCaptureSession(BlockPlacementPredictor blockPlacementPredictor) {
|
||||
this.capture = new SimpleBlockCapture(blockPlacementPredictor, this.level, this.capture);
|
||||
return this.capture;
|
||||
}
|
||||
|
||||
public SimpleBlockCapture createCaptureSession() {
|
||||
return this.createCaptureSession(new LiveBlockPlacementLayer(this, this.level));
|
||||
}
|
||||
|
||||
public void releaseCapture(@Nullable SimpleBlockCapture oldCapture) {
|
||||
this.capture = oldCapture;
|
||||
}
|
||||
|
||||
public @Nullable SimpleBlockCapture getCapture() {
|
||||
return this.capture;
|
||||
}
|
||||
|
||||
public boolean isCapturing() {
|
||||
return this.capture != null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@NullMarked
|
||||
package io.papermc.paper.util.capture;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
@@ -17,6 +17,8 @@ import io.papermc.paper.raytracing.PositionedRayTraceConfigurationBuilderImpl;
|
||||
import io.papermc.paper.raytracing.RayTraceTarget;
|
||||
import io.papermc.paper.registry.RegistryAccess;
|
||||
import io.papermc.paper.registry.RegistryKey;
|
||||
import io.papermc.paper.util.capture.MinecraftCaptureBridge;
|
||||
import io.papermc.paper.util.capture.SimpleBlockCapture;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
@@ -60,14 +62,12 @@ import net.minecraft.sounds.SoundEvents;
|
||||
import net.minecraft.sounds.SoundSource;
|
||||
import net.minecraft.util.Mth;
|
||||
import net.minecraft.util.NullOps;
|
||||
import net.minecraft.util.TriState;
|
||||
import net.minecraft.world.attribute.BedRule;
|
||||
import net.minecraft.world.attribute.EnvironmentAttributes;
|
||||
import net.minecraft.world.entity.EntitySpawnReason;
|
||||
import net.minecraft.world.entity.EntityType;
|
||||
import net.minecraft.world.entity.LightningBolt;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiManager;
|
||||
import net.minecraft.world.entity.ai.village.poi.PoiRecord;
|
||||
import net.minecraft.world.entity.item.FallingBlockEntity;
|
||||
import net.minecraft.world.entity.item.ItemEntity;
|
||||
import net.minecraft.world.entity.raid.Raids;
|
||||
@@ -110,7 +110,6 @@ import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.boss.DragonBattle;
|
||||
import org.bukkit.craftbukkit.block.CraftBiome;
|
||||
import org.bukkit.craftbukkit.block.CraftBlock;
|
||||
import org.bukkit.craftbukkit.block.CraftBlockState;
|
||||
import org.bukkit.craftbukkit.block.CraftBlockType;
|
||||
import org.bukkit.craftbukkit.block.data.CraftBlockData;
|
||||
import org.bukkit.craftbukkit.boss.CraftDragonBattle;
|
||||
@@ -130,6 +129,7 @@ import org.bukkit.craftbukkit.util.CraftNamespacedKey;
|
||||
import org.bukkit.craftbukkit.util.CraftRayTraceResult;
|
||||
import org.bukkit.craftbukkit.util.CraftSpawnCategory;
|
||||
import org.bukkit.craftbukkit.util.CraftStructureSearchResult;
|
||||
import org.bukkit.craftbukkit.util.RandomSourceWrapper;
|
||||
import org.bukkit.entity.AbstractArrow;
|
||||
import org.bukkit.entity.Arrow;
|
||||
import org.bukkit.entity.Entity;
|
||||
@@ -746,26 +746,20 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
||||
|
||||
@Override
|
||||
public boolean generateTree(Location loc, TreeType type, BlockChangeDelegate delegate) {
|
||||
this.world.captureTreeGeneration = true;
|
||||
this.world.captureBlockStates = true;
|
||||
boolean grownTree = this.generateTree(loc, type);
|
||||
this.world.captureBlockStates = false;
|
||||
this.world.captureTreeGeneration = false;
|
||||
if (grownTree) { // Copy block data to delegate
|
||||
for (BlockState blockstate : this.world.capturedBlockStates.values()) {
|
||||
BlockPos position = ((CraftBlockState) blockstate).getPosition();
|
||||
net.minecraft.world.level.block.state.BlockState oldBlock = this.world.getBlockState(position);
|
||||
int flags = ((CraftBlockState) blockstate).getFlags();
|
||||
delegate.setBlockData(blockstate.getX(), blockstate.getY(), blockstate.getZ(), blockstate.getBlockData());
|
||||
net.minecraft.world.level.block.state.BlockState newBlock = this.world.getBlockState(position);
|
||||
this.world.notifyAndUpdatePhysics(position, null, oldBlock, newBlock, newBlock, flags, net.minecraft.world.level.block.Block.UPDATE_LIMIT);
|
||||
}
|
||||
this.world.capturedBlockStates.clear();
|
||||
return true;
|
||||
} else {
|
||||
this.world.capturedBlockStates.clear();
|
||||
return false;
|
||||
}
|
||||
try (SimpleBlockCapture capture = this.world.forkCaptureSession()) {
|
||||
MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel();
|
||||
|
||||
BlockPos pos = CraftLocation.toBlockPos(loc);
|
||||
boolean generated = this.generateTree(captureTreeGeneration, this.getHandle().getMinecraftWorld().getChunkSource().getGenerator(), pos, new RandomSourceWrapper(CraftWorld.rand), type);
|
||||
if (generated) {
|
||||
List<BlockState> blocks = captureTreeGeneration.calculateLatestSnapshots(this.world);
|
||||
for (BlockState state : blocks) {
|
||||
delegate.setBlockData(state.getX(), state.getY(), state.getZ(), state.getBlockData());
|
||||
}
|
||||
}
|
||||
|
||||
return generated;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
package org.bukkit.craftbukkit.block;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.util.RandomSource;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.WorldGenLevel;
|
||||
import net.minecraft.world.level.block.entity.BeehiveBlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public final class CapturedBlockState extends CraftBlockState {
|
||||
|
||||
private final boolean treeBlock;
|
||||
|
||||
public CapturedBlockState(Block block, @net.minecraft.world.level.block.Block.UpdateFlags int capturedFlags, boolean treeBlock) {
|
||||
super(block, capturedFlags);
|
||||
|
||||
this.treeBlock = treeBlock;
|
||||
}
|
||||
|
||||
private CapturedBlockState(CapturedBlockState state, Location location) {
|
||||
super(state, location);
|
||||
this.treeBlock = state.treeBlock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean update(boolean force, boolean applyPhysics) {
|
||||
boolean result = super.update(force, applyPhysics);
|
||||
|
||||
if (result) {
|
||||
this.addBees();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean place(@net.minecraft.world.level.block.Block.UpdateFlags int flags) {
|
||||
boolean result = super.place(flags);
|
||||
this.addBees();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void addBees() {
|
||||
// SPIGOT-5537: Horrible hack to manually add bees given Level#captureTreeGeneration does not support block entities
|
||||
if (this.treeBlock && this.getType() == Material.BEE_NEST) {
|
||||
WorldGenLevel worldGenLevel = this.world.getHandle();
|
||||
BlockPos pos = this.getPosition();
|
||||
RandomSource randomSource = worldGenLevel.getRandom();
|
||||
|
||||
// Begin copied block from BeehiveDecorator
|
||||
worldGenLevel.getBlockEntity(pos, BlockEntityType.BEEHIVE).ifPresent(beehiveBlockEntity -> {
|
||||
int i1 = 2 + randomSource.nextInt(2);
|
||||
|
||||
for (int i2 = 0; i2 < i1; i2++) {
|
||||
beehiveBlockEntity.storeBee(BeehiveBlockEntity.Occupant.create(randomSource.nextInt(599)));
|
||||
}
|
||||
});
|
||||
// End copied block
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CapturedBlockState copy() {
|
||||
return new CapturedBlockState(this, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CapturedBlockState copy(Location location) {
|
||||
return new CapturedBlockState(this, location);
|
||||
}
|
||||
|
||||
public static CapturedBlockState getTreeBlockState(Level world, BlockPos pos, @net.minecraft.world.level.block.Block.UpdateFlags int flags) {
|
||||
return new CapturedBlockState(CraftBlock.at(world, pos), flags, true);
|
||||
}
|
||||
}
|
||||
@@ -521,41 +521,10 @@ public class CraftBlock implements Block {
|
||||
Direction direction = blockFaceToNotch(face);
|
||||
Preconditions.checkArgument(direction != null, face + " is not a valid cartesian face");
|
||||
|
||||
BlockFertilizeEvent event = null;
|
||||
ServerLevel world = this.getCraftWorld().getHandle();
|
||||
UseOnContext context = new UseOnContext(world, null, InteractionHand.MAIN_HAND, Items.BONE_MEAL.getDefaultInstance(), new BlockHitResult(Vec3.ZERO, direction, this.getPosition(), false));
|
||||
|
||||
// SPIGOT-6895: Call StructureGrowEvent and BlockFertilizeEvent
|
||||
world.captureTreeGeneration = true;
|
||||
InteractionResult result = BoneMealItem.applyBonemeal(context);
|
||||
world.captureTreeGeneration = false;
|
||||
|
||||
if (!world.capturedBlockStates.isEmpty()) {
|
||||
TreeType treeType = SaplingBlock.treeType;
|
||||
SaplingBlock.treeType = null;
|
||||
List<org.bukkit.block.BlockState> states = new ArrayList<>(world.capturedBlockStates.values());
|
||||
world.capturedBlockStates.clear();
|
||||
StructureGrowEvent structureEvent = null;
|
||||
|
||||
if (treeType != null) {
|
||||
structureEvent = new StructureGrowEvent(this.getLocation(), treeType, true, null, states);
|
||||
Bukkit.getPluginManager().callEvent(structureEvent);
|
||||
}
|
||||
|
||||
event = new BlockFertilizeEvent(CraftBlock.at(world, this.getPosition()), null, states);
|
||||
event.setCancelled(structureEvent != null && structureEvent.isCancelled());
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
|
||||
if (!event.isCancelled()) {
|
||||
for (org.bukkit.block.BlockState state : states) {
|
||||
CraftBlockState craftBlockState = (CraftBlockState) state;
|
||||
craftBlockState.place(craftBlockState.getFlags());
|
||||
world.checkCapturedTreeStateForObserverNotify(this.position, craftBlockState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result == InteractionResult.SUCCESS && (event == null || !event.isCancelled());
|
||||
return BoneMealItem.applyBonemeal(context) == InteractionResult.SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -28,21 +28,16 @@ public class CraftBlockState implements BlockState {
|
||||
protected final CraftWorld world;
|
||||
private final BlockPos position;
|
||||
protected net.minecraft.world.level.block.state.BlockState block;
|
||||
@net.minecraft.world.level.block.Block.UpdateFlags
|
||||
protected int capturedFlags; // todo move out of this class
|
||||
private WeakReference<LevelAccessor> weakLevel;
|
||||
|
||||
protected CraftBlockState(final Block block) {
|
||||
this(block.getWorld(), ((CraftBlock) block).getPosition(), ((CraftBlock) block).getBlockState());
|
||||
this.capturedFlags = net.minecraft.world.level.block.Block.UPDATE_ALL;
|
||||
|
||||
this.setWorldHandle(((CraftBlock) block).getLevel());
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
protected CraftBlockState(final Block block, @net.minecraft.world.level.block.Block.UpdateFlags int capturedFlags) {
|
||||
this(block);
|
||||
this.capturedFlags = capturedFlags;
|
||||
}
|
||||
|
||||
// world can be null for non-placed BlockStates.
|
||||
@@ -62,7 +57,6 @@ public class CraftBlockState implements BlockState {
|
||||
this.position = CraftLocation.toBlockPos(location);
|
||||
}
|
||||
this.block = from.block;
|
||||
this.capturedFlags = from.capturedFlags;
|
||||
this.setWorldHandle(from.getWorldHandle());
|
||||
}
|
||||
|
||||
@@ -181,14 +175,6 @@ public class CraftBlockState implements BlockState {
|
||||
return this.block.getBukkitMaterial();
|
||||
}
|
||||
|
||||
public void setFlags(@net.minecraft.world.level.block.Block.UpdateFlags int flags) {
|
||||
this.capturedFlags = flags;
|
||||
}
|
||||
|
||||
public @net.minecraft.world.level.block.Block.UpdateFlags int getFlags() {
|
||||
return this.capturedFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getLightLevel() {
|
||||
return this.getBlock().getLightLevel();
|
||||
@@ -252,17 +238,6 @@ public class CraftBlockState implements BlockState {
|
||||
return this.getWorldHandle().setBlock(this.position, this.block, flags);
|
||||
}
|
||||
|
||||
// used to revert a block placement due to an event being cancelled for example
|
||||
public boolean revertPlace() {
|
||||
return this.place(
|
||||
net.minecraft.world.level.block.Block.UPDATE_CLIENTS |
|
||||
net.minecraft.world.level.block.Block.UPDATE_KNOWN_SHAPE |
|
||||
net.minecraft.world.level.block.Block.UPDATE_SUPPRESS_DROPS |
|
||||
net.minecraft.world.level.block.Block.UPDATE_SKIP_ON_PLACE |
|
||||
net.minecraft.world.level.block.Block.UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte getRawData() {
|
||||
return CraftMagicNumbers.toLegacyData(this.block);
|
||||
|
||||
@@ -15,6 +15,10 @@ import io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent;
|
||||
import io.papermc.paper.event.entity.ItemTransportingEntityValidateTargetEvent;
|
||||
import io.papermc.paper.event.player.PlayerBedFailEnterEvent;
|
||||
import io.papermc.paper.event.player.PlayerToggleEntityAgeLockEvent;
|
||||
import io.papermc.paper.util.capture.GrowthContext;
|
||||
import io.papermc.paper.util.capture.MinecraftCaptureBridge;
|
||||
import io.papermc.paper.util.capture.PaperCapturingWorldLevel;
|
||||
import io.papermc.paper.util.capture.SimpleBlockCapture;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumMap;
|
||||
@@ -108,6 +112,7 @@ import org.bukkit.craftbukkit.inventory.CraftInventoryCrafting;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemType;
|
||||
import org.bukkit.craftbukkit.potion.CraftPotionUtil;
|
||||
import org.bukkit.craftbukkit.util.CraftLocation;
|
||||
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
|
||||
import org.bukkit.craftbukkit.util.CraftVector;
|
||||
import org.bukkit.entity.AbstractHorse;
|
||||
@@ -148,6 +153,7 @@ import org.bukkit.event.block.BlockDispenseLootEvent;
|
||||
import org.bukkit.event.block.BlockDropItemEvent;
|
||||
import org.bukkit.event.block.BlockExplodeEvent;
|
||||
import org.bukkit.event.block.BlockFadeEvent;
|
||||
import org.bukkit.event.block.BlockFertilizeEvent;
|
||||
import org.bukkit.event.block.BlockFormEvent;
|
||||
import org.bukkit.event.block.BlockGrowEvent;
|
||||
import org.bukkit.event.block.BlockIgniteEvent;
|
||||
@@ -264,6 +270,7 @@ import org.bukkit.event.weather.LightningStrikeEvent;
|
||||
import org.bukkit.event.world.EntitiesLoadEvent;
|
||||
import org.bukkit.event.world.EntitiesUnloadEvent;
|
||||
import org.bukkit.event.world.LootGenerateEvent;
|
||||
import org.bukkit.event.world.StructureGrowEvent;
|
||||
import org.bukkit.inventory.CraftingRecipe;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.InventoryView;
|
||||
@@ -490,20 +497,20 @@ public class CraftEventFactory {
|
||||
return event.getResonatedEntities().stream().map((bukkitEntity) -> ((CraftLivingEntity) bukkitEntity).getHandle());
|
||||
}
|
||||
|
||||
public static BlockMultiPlaceEvent callBlockMultiPlaceEvent(ServerLevel level, net.minecraft.world.entity.player.Player player, InteractionHand hand, List<BlockState> blockStates, BlockPos clickedPos) {
|
||||
public static BlockMultiPlaceEvent callBlockMultiPlaceEvent(ServerLevel level, net.minecraft.world.entity.player.Player player, InteractionHand hand, List<BlockState> replacedSnapshots, BlockPos clickedPos) {
|
||||
Player cplayer = (Player) player.getBukkitEntity();
|
||||
Block clickedBlock = CraftBlock.at(level, clickedPos);
|
||||
|
||||
boolean canBuild = true;
|
||||
for (BlockState blockState : blockStates) {
|
||||
if (!CraftEventFactory.canBuild(level, cplayer, blockState.getX(), blockState.getZ())) {
|
||||
for (BlockState snapshot : replacedSnapshots) {
|
||||
if (!CraftEventFactory.canBuild(level, cplayer, snapshot.getX(), snapshot.getZ())) {
|
||||
canBuild = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EquipmentSlot handSlot = CraftEquipmentSlot.getHand(hand);
|
||||
BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(blockStates, clickedBlock, cplayer.getInventory().getItem(handSlot), cplayer, canBuild, handSlot);
|
||||
BlockMultiPlaceEvent event = new BlockMultiPlaceEvent(replacedSnapshots, clickedBlock, cplayer.getInventory().getItem(handSlot), cplayer, canBuild, handSlot);
|
||||
event.callEvent();
|
||||
|
||||
return event;
|
||||
@@ -1278,7 +1285,7 @@ public class CraftEventFactory {
|
||||
return event;
|
||||
}
|
||||
|
||||
public static boolean handleBlockGrowEvent(Level level, BlockPos pos, net.minecraft.world.level.block.state.BlockState state, @net.minecraft.world.level.block.Block.UpdateFlags int flags) {
|
||||
public static boolean handleBlockGrowEvent(LevelAccessor level, BlockPos pos, net.minecraft.world.level.block.state.BlockState state, @net.minecraft.world.level.block.Block.UpdateFlags int flags) {
|
||||
CraftBlockState snapshot = CraftBlockStates.getBlockState(level, pos);
|
||||
snapshot.setBlock(state);
|
||||
|
||||
@@ -2397,6 +2404,54 @@ public class CraftEventFactory {
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo block list is sometimes empty for trees in both events
|
||||
public static boolean structureEvent(PaperCapturingWorldLevel level, BlockPos pos, Function<PaperCapturingWorldLevel, Boolean> worldGenCapture, GrowthContext context) {
|
||||
ServerLevel originalLevel = level.getLevel();
|
||||
try (SimpleBlockCapture capture = level.forkCaptureSession()) {
|
||||
MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel();
|
||||
if (worldGenCapture.apply(captureTreeGeneration)) {
|
||||
Location location = CraftLocation.toBukkit(pos, originalLevel);
|
||||
List<BlockState> blocks = captureTreeGeneration.calculateLatestSnapshots(originalLevel);
|
||||
StructureGrowEvent event = new StructureGrowEvent(location, context.getTreeSpecies(), context.usedBoneMeal(), context.getBukkitPlayer(), blocks);
|
||||
event.setCancelled(context.cancelled);
|
||||
|
||||
if (event.callEvent()) {
|
||||
capture.finalizePlacement(); // todo block list is mutable
|
||||
return true;
|
||||
} else {
|
||||
context.cancelled = true;
|
||||
}
|
||||
} else {
|
||||
capture.finalizePlacement();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo cancelling fertilize event doesn't work for azalea
|
||||
public static boolean fertilizeBlock(ServerLevel level, BlockPos pos, Function<PaperCapturingWorldLevel, Boolean> worldGenCapture, GrowthContext context) {
|
||||
try (SimpleBlockCapture capture = level.forkCaptureSession()) {
|
||||
MinecraftCaptureBridge captureTreeGeneration = capture.capturingWorldLevel();
|
||||
if (worldGenCapture.apply(captureTreeGeneration)) {
|
||||
List<BlockState> blocks = captureTreeGeneration.calculateLatestSnapshots(level);
|
||||
BlockFertilizeEvent event = new BlockFertilizeEvent(CraftBlock.at(level, pos), context.getBukkitPlayer(), blocks);
|
||||
event.setCancelled(context.cancelled);
|
||||
|
||||
if (event.callEvent()) {
|
||||
capture.finalizePlacement(); // todo block list is mutable
|
||||
return true;
|
||||
} else {
|
||||
context.cancelled = true;
|
||||
}
|
||||
} else {
|
||||
capture.finalizePlacement();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static boolean callPlayerToggleEntityAgeLockEvent(net.minecraft.world.entity.player.Player player, Mob target, ItemStack itemUsed, InteractionHand hand, boolean ageLocked, @Nullable EntityDataAccessor<?> accessorToResync) {
|
||||
PlayerToggleEntityAgeLockEvent event = new PlayerToggleEntityAgeLockEvent(
|
||||
(org.bukkit.entity.Player) player.getBukkitEntity(),
|
||||
|
||||
+15
-10
@@ -18,11 +18,11 @@ import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.minecraft.world.level.levelgen.Heightmap;
|
||||
import net.minecraft.world.level.material.FluidState;
|
||||
import net.minecraft.world.level.storage.LevelData;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.craftbukkit.block.CraftBlockState;
|
||||
import org.bukkit.craftbukkit.block.CraftBlockStates;
|
||||
|
||||
@@ -38,7 +38,7 @@ public class BlockStateListPopulator extends DummyLevelAccessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public net.minecraft.world.level.block.state.BlockState getBlockState(BlockPos pos) {
|
||||
public BlockState getBlockState(BlockPos pos) {
|
||||
CapturedBlock block = this.blocks.get(pos);
|
||||
return block != null ? block.state() : this.level.getBlockState(pos);
|
||||
}
|
||||
@@ -56,7 +56,7 @@ public class BlockStateListPopulator extends DummyLevelAccessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean setBlock(BlockPos pos, net.minecraft.world.level.block.state.BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit) {
|
||||
public boolean setBlock(BlockPos pos, BlockState blockState, @Block.UpdateFlags int updateFlags, int updateLimit) {
|
||||
pos = pos.immutable();
|
||||
// remove first to keep last updated order
|
||||
this.blocks.remove(pos);
|
||||
@@ -81,7 +81,7 @@ public class BlockStateListPopulator extends DummyLevelAccessor {
|
||||
|
||||
@Override
|
||||
public boolean destroyBlock(BlockPos pos, boolean dropResources, Entity breaker, int updateLimit) {
|
||||
net.minecraft.world.level.block.state.BlockState blockState = this.getBlockState(pos);
|
||||
BlockState blockState = this.getBlockState(pos);
|
||||
if (blockState.isAir()) {
|
||||
return false;
|
||||
}
|
||||
@@ -101,7 +101,6 @@ public class BlockStateListPopulator extends DummyLevelAccessor {
|
||||
CraftBlockState snapshot = CraftBlockStates.getBlockState(
|
||||
this.getMinecraftWorld().getWorld(), entry.getKey(), block.state(), block.blockEntity()
|
||||
);
|
||||
snapshot.setFlags(block.flags());
|
||||
snapshot.setWorldHandle(this.level);
|
||||
callback.accept(snapshot);
|
||||
}
|
||||
@@ -111,7 +110,7 @@ public class BlockStateListPopulator extends DummyLevelAccessor {
|
||||
this.placeSomeBlocks(_ -> true);
|
||||
}
|
||||
|
||||
public void placeSomeBlocks(Predicate<? super BlockState> filter) {
|
||||
public void placeSomeBlocks(Predicate<? super CraftBlockState> filter) {
|
||||
this.placeSomeBlocks(_ -> {}, filter);
|
||||
}
|
||||
|
||||
@@ -119,22 +118,28 @@ public class BlockStateListPopulator extends DummyLevelAccessor {
|
||||
this.placeSomeBlocks(beforeRun, _ -> true);
|
||||
}
|
||||
|
||||
public void placeSomeBlocks(Consumer<? super CraftBlockState> beforeRun, Predicate<? super BlockState> filter) {
|
||||
public void placeSomeBlocks(Consumer<? super CraftBlockState> beforeRun, Predicate<? super CraftBlockState> filter) {
|
||||
for (CraftBlockState snapshot : this.getSnapshotBlocks()) {
|
||||
if (filter.test(snapshot)) {
|
||||
int flags = this.getEffectiveFlags(snapshot.getPosition());
|
||||
beforeRun.accept(snapshot);
|
||||
snapshot.place(snapshot.getFlags());
|
||||
snapshot.place(flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public @Block.UpdateFlags int getEffectiveFlags(BlockPos pos) {
|
||||
CapturedBlock block = this.blocks.get(pos);
|
||||
return block != null ? block.flags() : Block.UPDATE_ALL; // fallback for new API-added blocks
|
||||
}
|
||||
|
||||
public List<CraftBlockState> getSnapshotBlocks() {
|
||||
if (this.snapshots == null) {
|
||||
List<CraftBlockState> snapshots = new ArrayList<>();
|
||||
this.iterateSnapshots(snapshots::add);
|
||||
this.snapshots = snapshots;
|
||||
}
|
||||
return snapshots;
|
||||
return this.snapshots;
|
||||
}
|
||||
|
||||
// For tree generation
|
||||
@@ -155,7 +160,7 @@ public class BlockStateListPopulator extends DummyLevelAccessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isStateAtPosition(BlockPos pos, Predicate<net.minecraft.world.level.block.state.BlockState> predicate) {
|
||||
public boolean isStateAtPosition(BlockPos pos, Predicate<BlockState> predicate) {
|
||||
return predicate.test(this.getBlockState(pos));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package io.papermc.testplugin;
|
||||
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.TreeType;
|
||||
import org.bukkit.block.BlockFace;
|
||||
import org.bukkit.block.BlockState;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.BlockFertilizeEvent;
|
||||
import org.bukkit.event.block.BlockMultiPlaceEvent;
|
||||
import org.bukkit.event.block.BlockPlaceEvent;
|
||||
import org.bukkit.event.player.PlayerSwapHandItemsEvent;
|
||||
import org.bukkit.event.world.StructureGrowEvent;
|
||||
|
||||
import static net.kyori.adventure.text.Component.text;
|
||||
|
||||
public record OwenPlayground() implements Listener {
|
||||
|
||||
public static final OwenPlayground INSTANCE = new OwenPlayground();
|
||||
|
||||
@EventHandler
|
||||
public void on(BlockFertilizeEvent event) {
|
||||
//event.setCancelled(true);
|
||||
|
||||
// event.getPlayer().sendBlockChanges(event.getBlocks());
|
||||
//
|
||||
// new BukkitRunnable(){
|
||||
//
|
||||
// @Override
|
||||
// public void run() {
|
||||
// event.getBlocks().forEach((state) -> event.getPlayer().sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData()));
|
||||
// }
|
||||
// }.runTaskLater(this, 20 * 2);
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void on(StructureGrowEvent event) {
|
||||
// event.setCancelled(true);
|
||||
//
|
||||
// event.getPlayer().sendBlockChanges(event.getBlocks());
|
||||
//
|
||||
// new BukkitRunnable(){
|
||||
//
|
||||
// @Override
|
||||
// public void run() {
|
||||
// event.getBlocks().forEach((state) -> event.getPlayer().sendBlockChange(state.getLocation(), state.getBlock().getType().createBlockData()));
|
||||
// }
|
||||
// }.runTaskLater(this, 20 * 2);
|
||||
}
|
||||
|
||||
|
||||
@EventHandler
|
||||
public void on(PlayerSwapHandItemsEvent event) {
|
||||
event.getPlayer().getTargetBlockExact(5).applyBoneMeal(BlockFace.UP);
|
||||
|
||||
event.getPlayer().getWorld().generateTree(event.getPlayer().getLocation(), TreeType.values()[(int) (TreeType.values().length * Math.random())]);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(BlockPlaceEvent event) {
|
||||
event.getPlayer().sendActionBar(text("Replaced: " + event.getBlockReplacedState().getType()));
|
||||
if (event.getPlayer().getInventory().contains(Material.DIAMOND)) {
|
||||
event.getBlock().setType(Material.AIR);
|
||||
} else if (event.getPlayer().getInventory().contains(Material.GOLD_INGOT)) {
|
||||
event.setCancelled(true);
|
||||
} else if (event.getPlayer().getInventory().contains(Material.EMERALD)) {
|
||||
event.getBlock().setType(Material.STONE);
|
||||
} else if (event.getPlayer().getInventory().contains(Material.GOLD_NUGGET)) {
|
||||
event.getBlock().getRelative(BlockFace.SOUTH).setType(Material.STONE);
|
||||
}
|
||||
event.getPlayer().sendMessage(event.getBlock().getType().toString());
|
||||
|
||||
event.getBlockAgainst().setType(Material.DIAMOND_BLOCK);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void on(BlockMultiPlaceEvent event) {
|
||||
event.getPlayer().sendActionBar(text("Replaced: " + event.getReplacedBlockStates().stream().map(BlockState::getBlockData).toList()));
|
||||
|
||||
event.getPlayer().sendMessage(event.getBlock().getType().toString());
|
||||
if (event.getPlayer().getInventory().contains(Material.DIAMOND)) {
|
||||
event.setCancelled(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ public final class TestPlugin extends JavaPlugin implements Listener {
|
||||
@Override
|
||||
public void onEnable() {
|
||||
this.getServer().getPluginManager().registerEvents(this, this);
|
||||
this.getServer().getPluginManager().registerEvents(OwenPlayground.INSTANCE, this);
|
||||
|
||||
// io.papermc.testplugin.brigtests.Registration.registerViaOnEnable(this);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user