Mirror von
https://github.com/PaperMC/Paper.git
synchronisiert 2024-12-15 11:00:06 +01:00
Drop unapplied patches
1012-Use-distance-map-to-optimise-entity-tracker.patch: 1025-Collision-optimisations.patch: 1034-Actually-optimise-explosions.patch: 1039-Send-full-pos-packets-for-hard-colliding-entities.patch: Implemented in Moonrise patch 1037-Distance-manager-tick-timings.patch: Not needed 0668-Implement-regenerateChunk.patch: API does not appear to be used, and it is a real pain to implement this properly. The old patch did not handle populators correctly, for example.
Dieser Commit ist enthalten in:
Ursprung
90ae1dc573
Commit
821081d646
@ -1,104 +0,0 @@
|
|||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Nassim Jahnke <nassim@njahnke.dev>
|
|
||||||
Date: Mon, 31 Jan 2022 11:21:50 +0100
|
|
||||||
Subject: [PATCH] Implement regenerateChunk
|
|
||||||
|
|
||||||
Co-authored-by: Jason Penilla <11360596+jpenilla@users.noreply.github.com>
|
|
||||||
|
|
||||||
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
||||||
index 8d41492cd6305331deca3748eb24d689f23e280b..365a23218c57f9685c79b520d7721e5a49b7771c 100644
|
|
||||||
--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
||||||
+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
|
|
||||||
@@ -151,6 +151,7 @@ import org.jetbrains.annotations.Nullable;
|
|
||||||
public class CraftWorld extends CraftRegionAccessor implements World {
|
|
||||||
public static final int CUSTOM_DIMENSION_OFFSET = 10;
|
|
||||||
private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry();
|
|
||||||
+ private static final ChunkStatus[] REGEN_CHUNK_STATUSES = {ChunkStatus.BIOMES, ChunkStatus.NOISE, ChunkStatus.SURFACE, ChunkStatus.CARVERS, ChunkStatus.FEATURES, ChunkStatus.INITIALIZE_LIGHT}; // Paper - implement regenerate chunk method
|
|
||||||
|
|
||||||
private final ServerLevel world;
|
|
||||||
private WorldBorder worldBorder;
|
|
||||||
@@ -421,27 +422,70 @@ public class CraftWorld extends CraftRegionAccessor implements World {
|
|
||||||
@Override
|
|
||||||
public boolean regenerateChunk(int x, int z) {
|
|
||||||
org.spigotmc.AsyncCatcher.catchOp("chunk regenerate"); // Spigot
|
|
||||||
- throw new UnsupportedOperationException("Not supported in this Minecraft version! Unless you can fix it, this is not a bug :)");
|
|
||||||
- /*
|
|
||||||
- if (!unloadChunk0(x, z, false)) {
|
|
||||||
- return false;
|
|
||||||
+ // Paper start - implement regenerateChunk method
|
|
||||||
+ final ServerLevel serverLevel = this.world;
|
|
||||||
+ final net.minecraft.server.level.ServerChunkCache serverChunkCache = serverLevel.getChunkSource();
|
|
||||||
+ final ChunkPos chunkPos = new ChunkPos(x, z);
|
|
||||||
+ final net.minecraft.world.level.chunk.LevelChunk levelChunk = serverChunkCache.getChunk(chunkPos.x, chunkPos.z, true);
|
|
||||||
+ for (final BlockPos blockPos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), serverLevel.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), serverLevel.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ())) {
|
|
||||||
+ levelChunk.removeBlockEntity(blockPos);
|
|
||||||
+ serverLevel.setBlock(blockPos, net.minecraft.world.level.block.Blocks.AIR.defaultBlockState(), 16);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ for (final ChunkStatus chunkStatus : REGEN_CHUNK_STATUSES) {
|
|
||||||
+ final List<ChunkAccess> list = new ArrayList<>();
|
|
||||||
+ final int range = Math.max(1, chunkStatus.getRange());
|
|
||||||
+ for (int chunkX = chunkPos.z - range; chunkX <= chunkPos.z + range; chunkX++) {
|
|
||||||
+ for (int chunkZ = chunkPos.x - range; chunkZ <= chunkPos.x + range; chunkZ++) {
|
|
||||||
+ ChunkAccess chunkAccess = serverChunkCache.getChunk(chunkZ, chunkX, chunkStatus.getParent(), true);
|
|
||||||
+ if (chunkAccess instanceof ImposterProtoChunk accessProtoChunk) {
|
|
||||||
+ chunkAccess = new ImposterProtoChunk(accessProtoChunk.getWrapped(), true);
|
|
||||||
+ } else if (chunkAccess instanceof net.minecraft.world.level.chunk.LevelChunk accessLevelChunk) {
|
|
||||||
+ chunkAccess = new ImposterProtoChunk(accessLevelChunk, true);
|
|
||||||
+ }
|
|
||||||
+ list.add(chunkAccess);
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ final java.util.concurrent.CompletableFuture<ChunkAccess> future = chunkStatus.generate(
|
|
||||||
+ new net.minecraft.world.level.chunk.status.WorldGenContext(
|
|
||||||
+ serverLevel,
|
|
||||||
+ serverChunkCache.getGenerator(),
|
|
||||||
+ serverLevel.getStructureManager(),
|
|
||||||
+ serverChunkCache.getLightEngine()
|
|
||||||
+ ),
|
|
||||||
+ Runnable::run,
|
|
||||||
+ chunk -> {
|
|
||||||
+ throw new UnsupportedOperationException("Not creating full chunks here");
|
|
||||||
+ },
|
|
||||||
+ list
|
|
||||||
+ );
|
|
||||||
+ serverChunkCache.mainThreadProcessor.managedBlock(future::isDone);
|
|
||||||
+ if (chunkStatus == ChunkStatus.NOISE) {
|
|
||||||
+ net.minecraft.world.level.levelgen.Heightmap.primeHeightmaps(future.join(), ChunkStatus.POST_FEATURES);
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
|
|
||||||
- final long chunkKey = ChunkCoordIntPair.pair(x, z);
|
|
||||||
- world.getChunkProvider().unloadQueue.remove(chunkKey);
|
|
||||||
+ for (final BlockPos blockPos : BlockPos.betweenClosed(chunkPos.getMinBlockX(), serverLevel.getMinBuildHeight(), chunkPos.getMinBlockZ(), chunkPos.getMaxBlockX(), serverLevel.getMaxBuildHeight() - 1, chunkPos.getMaxBlockZ())) {
|
|
||||||
+ serverChunkCache.blockChanged(blockPos);
|
|
||||||
+ }
|
|
||||||
|
|
||||||
- net.minecraft.server.Chunk chunk = world.getChunkProvider().generateChunk(x, z);
|
|
||||||
- PlayerChunk playerChunk = world.getPlayerChunkMap().getChunk(x, z);
|
|
||||||
- if (playerChunk != null) {
|
|
||||||
- playerChunk.chunk = chunk;
|
|
||||||
+ final Set<ChunkPos> chunksToRelight = new HashSet<>(9);
|
|
||||||
+ for (int chunkX = chunkPos.x - 1; chunkX <= chunkPos.x + 1 ; chunkX++) {
|
|
||||||
+ for (int chunkZ = chunkPos.z - 1; chunkZ <= chunkPos.z + 1 ; chunkZ++) {
|
|
||||||
+ chunksToRelight.add(new ChunkPos(chunkX, chunkZ));
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
|
|
||||||
- if (chunk != null) {
|
|
||||||
- refreshChunk(x, z);
|
|
||||||
+ for (final ChunkPos pos : chunksToRelight) {
|
|
||||||
+ final ChunkAccess chunk = serverChunkCache.getChunk(pos.x, pos.z, false);
|
|
||||||
+ if (chunk != null) {
|
|
||||||
+ serverChunkCache.getLightEngine().lightChunk(chunk, false);
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
|
|
||||||
- return chunk != null;
|
|
||||||
- */
|
|
||||||
+ return true;
|
|
||||||
+ // Paper end - implement regenerate chunk method
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
@ -1,341 +0,0 @@
|
|||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
|
||||||
Date: Tue, 5 May 2020 20:18:05 -0700
|
|
||||||
Subject: [PATCH] Use distance map to optimise entity tracker
|
|
||||||
|
|
||||||
Use the distance map to find candidate players for tracking.
|
|
||||||
|
|
||||||
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
||||||
index 4621c33ed73b0db64e78e7b9be7013a2ba7393c8..48f7997e8a20f5a5a77516cbde990d0aacc2078a 100644
|
|
||||||
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
||||||
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
|
|
||||||
@@ -149,6 +149,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
||||||
|
|
||||||
// Paper start - distance maps
|
|
||||||
private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
|
|
||||||
+ // Paper start - use distance map to optimise tracker
|
|
||||||
+ public static boolean isLegacyTrackingEntity(Entity entity) {
|
|
||||||
+ return entity.isLegacyTrackingEntity;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ // inlined EnumMap, TrackingRange.TrackingRangeType
|
|
||||||
+ static final org.spigotmc.TrackingRange.TrackingRangeType[] TRACKING_RANGE_TYPES = org.spigotmc.TrackingRange.TrackingRangeType.values();
|
|
||||||
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap[] playerEntityTrackerTrackMaps;
|
|
||||||
+ final int[] entityTrackerTrackRanges;
|
|
||||||
+ public final int getEntityTrackerRange(final int ordinal) {
|
|
||||||
+ return this.entityTrackerTrackRanges[ordinal];
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ private int convertSpigotRangeToVanilla(final int vanilla) {
|
|
||||||
+ return net.minecraft.server.MinecraftServer.getServer().getScaledTrackingDistance(vanilla);
|
|
||||||
+ }
|
|
||||||
+ // Paper end - use distance map to optimise tracker
|
|
||||||
|
|
||||||
void addPlayerToDistanceMaps(ServerPlayer player) {
|
|
||||||
int chunkX = io.papermc.paper.util.MCUtil.getChunkCoordinate(player.getX());
|
|
||||||
@@ -156,6 +173,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
||||||
// Note: players need to be explicitly added to distance maps before they can be updated
|
|
||||||
this.nearbyPlayers.addPlayer(player);
|
|
||||||
this.level.playerChunkLoader.addPlayer(player); // Paper - replace chunk loader
|
|
||||||
+ // Paper start - use distance map to optimise entity tracker
|
|
||||||
+ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) {
|
|
||||||
+ com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i];
|
|
||||||
+ int trackRange = this.entityTrackerTrackRanges[i];
|
|
||||||
+
|
|
||||||
+ trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player)));
|
|
||||||
+ }
|
|
||||||
+ // Paper end - use distance map to optimise entity tracker
|
|
||||||
}
|
|
||||||
|
|
||||||
void removePlayerFromDistanceMaps(ServerPlayer player) {
|
|
||||||
@@ -164,6 +189,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
||||||
// Note: players need to be explicitly added to distance maps before they can be updated
|
|
||||||
this.nearbyPlayers.removePlayer(player);
|
|
||||||
this.level.playerChunkLoader.removePlayer(player); // Paper - replace chunk loader
|
|
||||||
+ // Paper start - use distance map to optimise tracker
|
|
||||||
+ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) {
|
|
||||||
+ this.playerEntityTrackerTrackMaps[i].remove(player);
|
|
||||||
+ }
|
|
||||||
+ // Paper end - use distance map to optimise tracker
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateMaps(ServerPlayer player) {
|
|
||||||
@@ -172,6 +202,14 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
||||||
// Note: players need to be explicitly added to distance maps before they can be updated
|
|
||||||
this.nearbyPlayers.tickPlayer(player);
|
|
||||||
this.level.playerChunkLoader.updatePlayer(player); // Paper - replace chunk loader
|
|
||||||
+ // Paper start - use distance map to optimise entity tracker
|
|
||||||
+ for (int i = 0, len = TRACKING_RANGE_TYPES.length; i < len; ++i) {
|
|
||||||
+ com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i];
|
|
||||||
+ int trackRange = this.entityTrackerTrackRanges[i];
|
|
||||||
+
|
|
||||||
+ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.system.ChunkSystem.getSendViewDistance(player)));
|
|
||||||
+ }
|
|
||||||
+ // Paper end - use distance map to optimise entity tracker
|
|
||||||
}
|
|
||||||
// Paper end
|
|
||||||
// Paper start
|
|
||||||
@@ -258,6 +296,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
||||||
this.regionManagers.add(this.dataRegionManager);
|
|
||||||
this.nearbyPlayers = new io.papermc.paper.util.player.NearbyPlayers(this.level);
|
|
||||||
// Paper end
|
|
||||||
+ // Paper start - use distance map to optimise entity tracker
|
|
||||||
+ this.playerEntityTrackerTrackMaps = new com.destroystokyo.paper.util.misc.PlayerAreaMap[TRACKING_RANGE_TYPES.length];
|
|
||||||
+ this.entityTrackerTrackRanges = new int[TRACKING_RANGE_TYPES.length];
|
|
||||||
+
|
|
||||||
+ org.spigotmc.SpigotWorldConfig spigotWorldConfig = this.level.spigotConfig;
|
|
||||||
+
|
|
||||||
+ for (int ordinal = 0, len = TRACKING_RANGE_TYPES.length; ordinal < len; ++ordinal) {
|
|
||||||
+ org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = TRACKING_RANGE_TYPES[ordinal];
|
|
||||||
+ int configuredSpigotValue;
|
|
||||||
+ switch (trackingRangeType) {
|
|
||||||
+ case PLAYER:
|
|
||||||
+ configuredSpigotValue = spigotWorldConfig.playerTrackingRange;
|
|
||||||
+ break;
|
|
||||||
+ case ANIMAL:
|
|
||||||
+ configuredSpigotValue = spigotWorldConfig.animalTrackingRange;
|
|
||||||
+ break;
|
|
||||||
+ case MONSTER:
|
|
||||||
+ configuredSpigotValue = spigotWorldConfig.monsterTrackingRange;
|
|
||||||
+ break;
|
|
||||||
+ case MISC:
|
|
||||||
+ configuredSpigotValue = spigotWorldConfig.miscTrackingRange;
|
|
||||||
+ break;
|
|
||||||
+ case OTHER:
|
|
||||||
+ configuredSpigotValue = spigotWorldConfig.otherTrackingRange;
|
|
||||||
+ break;
|
|
||||||
+ case ENDERDRAGON:
|
|
||||||
+ configuredSpigotValue = EntityType.ENDER_DRAGON.clientTrackingRange() * 16;
|
|
||||||
+ break;
|
|
||||||
+ case DISPLAY:
|
|
||||||
+ configuredSpigotValue = spigotWorldConfig.displayTrackingRange;
|
|
||||||
+ break;
|
|
||||||
+ default:
|
|
||||||
+ throw new IllegalStateException("Missing case for enum " + trackingRangeType);
|
|
||||||
+ }
|
|
||||||
+ configuredSpigotValue = convertSpigotRangeToVanilla(configuredSpigotValue);
|
|
||||||
+
|
|
||||||
+ int trackRange = (configuredSpigotValue >>> 4) + ((configuredSpigotValue & 15) != 0 ? 1 : 0);
|
|
||||||
+ this.entityTrackerTrackRanges[ordinal] = trackRange;
|
|
||||||
+
|
|
||||||
+ this.playerEntityTrackerTrackMaps[ordinal] = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets);
|
|
||||||
+ }
|
|
||||||
+ // Paper end - use distance map to optimise entity tracker
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paper start
|
|
||||||
@@ -951,17 +1031,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
public void move(ServerPlayer player) {
|
|
||||||
- ObjectIterator objectiterator = this.entityMap.values().iterator();
|
|
||||||
-
|
|
||||||
- while (objectiterator.hasNext()) {
|
|
||||||
- ChunkMap.TrackedEntity playerchunkmap_entitytracker = (ChunkMap.TrackedEntity) objectiterator.next();
|
|
||||||
-
|
|
||||||
- if (playerchunkmap_entitytracker.entity == player) {
|
|
||||||
- playerchunkmap_entitytracker.updatePlayers(this.level.players());
|
|
||||||
- } else {
|
|
||||||
- playerchunkmap_entitytracker.updatePlayer(player);
|
|
||||||
- }
|
|
||||||
- }
|
|
||||||
+ // Paper - delay this logic for the entity tracker tick, no need to duplicate it
|
|
||||||
|
|
||||||
SectionPos sectionposition = player.getLastSectionPos();
|
|
||||||
SectionPos sectionposition1 = SectionPos.of((EntityAccess) player);
|
|
||||||
@@ -1038,7 +1108,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
||||||
|
|
||||||
entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker
|
|
||||||
this.entityMap.put(entity.getId(), playerchunkmap_entitytracker);
|
|
||||||
- playerchunkmap_entitytracker.updatePlayers(this.level.players());
|
|
||||||
+ playerchunkmap_entitytracker.updatePlayers(entity.getPlayersInTrackRange()); // Paper - don't search all players
|
|
||||||
if (entity instanceof ServerPlayer) {
|
|
||||||
ServerPlayer entityplayer = (ServerPlayer) entity;
|
|
||||||
|
|
||||||
@@ -1080,9 +1150,37 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
||||||
entity.tracker = null; // Paper - We're no longer tracked
|
|
||||||
}
|
|
||||||
|
|
||||||
- protected void tick() {
|
|
||||||
- // Paper - replaced by PlayerChunkLoader
|
|
||||||
+ // Paper start - optimised tracker
|
|
||||||
+ private final void processTrackQueue() {
|
|
||||||
+ this.level.timings.tracker1.startTiming();
|
|
||||||
+ try {
|
|
||||||
+ for (TrackedEntity tracker : this.entityMap.values()) {
|
|
||||||
+ // update tracker entry
|
|
||||||
+ tracker.updatePlayers(tracker.entity.getPlayersInTrackRange());
|
|
||||||
+ }
|
|
||||||
+ } finally {
|
|
||||||
+ this.level.timings.tracker1.stopTiming();
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+ this.level.timings.tracker2.startTiming();
|
|
||||||
+ try {
|
|
||||||
+ for (TrackedEntity tracker : this.entityMap.values()) {
|
|
||||||
+ tracker.serverEntity.sendChanges();
|
|
||||||
+ }
|
|
||||||
+ } finally {
|
|
||||||
+ this.level.timings.tracker2.stopTiming();
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ // Paper end - optimised tracker
|
|
||||||
|
|
||||||
+ protected void tick() {
|
|
||||||
+ // Paper start - optimized tracker
|
|
||||||
+ if (true) {
|
|
||||||
+ this.processTrackQueue();
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
+ // Paper end - optimized tracker
|
|
||||||
List<ServerPlayer> list = Lists.newArrayList();
|
|
||||||
List<ServerPlayer> list1 = this.level.players();
|
|
||||||
ObjectIterator objectiterator = this.entityMap.values().iterator();
|
|
||||||
@@ -1230,6 +1328,42 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
|
|
||||||
this.lastSectionPos = SectionPos.of((EntityAccess) entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
+ // Paper start - use distance map to optimise tracker
|
|
||||||
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> lastTrackerCandidates;
|
|
||||||
+
|
|
||||||
+ final void updatePlayers(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newTrackerCandidates) {
|
|
||||||
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> oldTrackerCandidates = this.lastTrackerCandidates;
|
|
||||||
+ this.lastTrackerCandidates = newTrackerCandidates;
|
|
||||||
+
|
|
||||||
+ if (newTrackerCandidates != null) {
|
|
||||||
+ Object[] rawData = newTrackerCandidates.getBackingSet();
|
|
||||||
+ for (int i = 0, len = rawData.length; i < len; ++i) {
|
|
||||||
+ Object raw = rawData[i];
|
|
||||||
+ if (!(raw instanceof ServerPlayer)) {
|
|
||||||
+ continue;
|
|
||||||
+ }
|
|
||||||
+ ServerPlayer player = (ServerPlayer)raw;
|
|
||||||
+ this.updatePlayer(player);
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ if (oldTrackerCandidates == newTrackerCandidates) {
|
|
||||||
+ // this is likely the case.
|
|
||||||
+ // means there has been no range changes, so we can just use the above for tracking.
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ // stuff could have been removed, so we need to check the trackedPlayers set
|
|
||||||
+ // for players that were removed
|
|
||||||
+
|
|
||||||
+ for (ServerPlayerConnection conn : this.seenBy.toArray(new ServerPlayerConnection[0])) { // avoid CME
|
|
||||||
+ if (newTrackerCandidates == null || !newTrackerCandidates.contains(conn.getPlayer())) {
|
|
||||||
+ this.updatePlayer(conn.getPlayer());
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ // Paper end - use distance map to optimise tracker
|
|
||||||
+
|
|
||||||
public boolean equals(Object object) {
|
|
||||||
return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity) object).entity.getId() == this.entity.getId() : false;
|
|
||||||
}
|
|
||||||
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
|
|
||||||
index 26b21af65ff63c83fb3be80740e41b40f0feb944..030e20d1030c9e9c26b2bc456ff0b477a048cd03 100644
|
|
||||||
--- a/src/main/java/net/minecraft/world/entity/Entity.java
|
|
||||||
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
|
|
||||||
@@ -59,6 +59,7 @@ import net.minecraft.network.syncher.SyncedDataHolder;
|
|
||||||
import net.minecraft.network.syncher.SynchedEntityData;
|
|
||||||
import net.minecraft.resources.ResourceKey;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
+import io.papermc.paper.util.MCUtil;
|
|
||||||
import net.minecraft.server.MinecraftServer;
|
|
||||||
import net.minecraft.server.level.ServerLevel;
|
|
||||||
import net.minecraft.server.level.ServerPlayer;
|
|
||||||
@@ -470,6 +471,38 @@ public abstract class Entity implements SyncedDataHolder, Nameable, EntityAccess
|
|
||||||
this.teleportTo(worldserver, null);
|
|
||||||
}
|
|
||||||
// Paper end - make end portalling safe
|
|
||||||
+ // Paper start - optimise entity tracking
|
|
||||||
+ final org.spigotmc.TrackingRange.TrackingRangeType trackingRangeType = org.spigotmc.TrackingRange.getTrackingRangeType(this);
|
|
||||||
+
|
|
||||||
+ public boolean isLegacyTrackingEntity = false;
|
|
||||||
+
|
|
||||||
+ public final void setLegacyTrackingEntity(final boolean isLegacyTrackingEntity) {
|
|
||||||
+ this.isLegacyTrackingEntity = isLegacyTrackingEntity;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> getPlayersInTrackRange() {
|
|
||||||
+ // determine highest range of passengers
|
|
||||||
+ if (this.passengers.isEmpty()) {
|
|
||||||
+ return ((ServerLevel)this.level).getChunkSource().chunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()]
|
|
||||||
+ .getObjectsInRange(MCUtil.getCoordinateKey(this));
|
|
||||||
+ }
|
|
||||||
+ Iterable<Entity> passengers = this.getIndirectPassengers();
|
|
||||||
+ net.minecraft.server.level.ChunkMap chunkMap = ((ServerLevel)this.level).getChunkSource().chunkMap;
|
|
||||||
+ org.spigotmc.TrackingRange.TrackingRangeType type = this.trackingRangeType;
|
|
||||||
+ int range = chunkMap.getEntityTrackerRange(type.ordinal());
|
|
||||||
+
|
|
||||||
+ for (Entity passenger : passengers) {
|
|
||||||
+ org.spigotmc.TrackingRange.TrackingRangeType passengerType = passenger.trackingRangeType;
|
|
||||||
+ int passengerRange = chunkMap.getEntityTrackerRange(passengerType.ordinal());
|
|
||||||
+ if (passengerRange > range) {
|
|
||||||
+ type = passengerType;
|
|
||||||
+ range = passengerRange;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ return chunkMap.playerEntityTrackerTrackMaps[type.ordinal()].getObjectsInRange(MCUtil.getCoordinateKey(this));
|
|
||||||
+ }
|
|
||||||
+ // Paper end - optimise entity tracking
|
|
||||||
public float getBukkitYaw() {
|
|
||||||
return this.yRot;
|
|
||||||
}
|
|
||||||
diff --git a/src/main/java/org/spigotmc/TrackingRange.java b/src/main/java/org/spigotmc/TrackingRange.java
|
|
||||||
index bb06f89a29f30144e7e2113e088a503db006a83c..e4425b242fe73d1fd2bd10c313aa16925432329f 100644
|
|
||||||
--- a/src/main/java/org/spigotmc/TrackingRange.java
|
|
||||||
+++ b/src/main/java/org/spigotmc/TrackingRange.java
|
|
||||||
@@ -55,4 +55,48 @@ public class TrackingRange
|
|
||||||
return config.otherTrackingRange;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+
|
|
||||||
+ // Paper start - optimise entity tracking
|
|
||||||
+ // copied from above, TODO check on update
|
|
||||||
+ public static TrackingRangeType getTrackingRangeType(Entity entity)
|
|
||||||
+ {
|
|
||||||
+ if (entity instanceof net.minecraft.world.entity.boss.enderdragon.EnderDragon) return TrackingRangeType.ENDERDRAGON; // Paper - enderdragon is exempt
|
|
||||||
+ if ( entity instanceof ServerPlayer )
|
|
||||||
+ {
|
|
||||||
+ return TrackingRangeType.PLAYER;
|
|
||||||
+ // Paper start - Simplify and set water mobs to animal tracking range
|
|
||||||
+ }
|
|
||||||
+ switch (entity.activationType) {
|
|
||||||
+ case RAIDER:
|
|
||||||
+ case MONSTER:
|
|
||||||
+ case FLYING_MONSTER:
|
|
||||||
+ return TrackingRangeType.MONSTER;
|
|
||||||
+ case WATER:
|
|
||||||
+ case VILLAGER:
|
|
||||||
+ case ANIMAL:
|
|
||||||
+ return TrackingRangeType.ANIMAL;
|
|
||||||
+ case MISC:
|
|
||||||
+ }
|
|
||||||
+ if ( entity instanceof ItemFrame || entity instanceof Painting || entity instanceof ItemEntity || entity instanceof ExperienceOrb )
|
|
||||||
+ // Paper end
|
|
||||||
+ {
|
|
||||||
+ return TrackingRangeType.MISC;
|
|
||||||
+ } else if (entity instanceof Display) {
|
|
||||||
+ return TrackingRangeType.DISPLAY;
|
|
||||||
+ } else
|
|
||||||
+ {
|
|
||||||
+ return TrackingRangeType.OTHER;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ public static enum TrackingRangeType {
|
|
||||||
+ PLAYER,
|
|
||||||
+ ANIMAL,
|
|
||||||
+ MONSTER,
|
|
||||||
+ MISC,
|
|
||||||
+ OTHER,
|
|
||||||
+ ENDERDRAGON,
|
|
||||||
+ DISPLAY;
|
|
||||||
+ }
|
|
||||||
+ // Paper end - optimise entity tracking
|
|
||||||
}
|
|
Datei-Diff unterdrückt, da er zu groß ist
Diff laden
@ -1,521 +0,0 @@
|
|||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
|
||||||
Date: Tue, 12 Sep 2023 06:50:16 -0700
|
|
||||||
Subject: [PATCH] Actually optimise explosions
|
|
||||||
|
|
||||||
The vast majority of blocks an explosion of power ~4 tries
|
|
||||||
to destroy are duplicates. The core of the block destroying
|
|
||||||
part of this patch is to cache the block state, resistance, and
|
|
||||||
whether it should explode - as those will not change.
|
|
||||||
|
|
||||||
The other part of this patch is to optimise the visibility
|
|
||||||
percentage calculation. The new visibility calculation takes
|
|
||||||
advantage of the block caching already done by the explosion logic.
|
|
||||||
It continues to update the cache as the visibility calculation
|
|
||||||
uses many rays which can overlap significantly.
|
|
||||||
|
|
||||||
Effectively, the patch uses a lot of caching to eliminate
|
|
||||||
redundant operations.
|
|
||||||
|
|
||||||
Performance benchmarking explosions is challenging, as it varies
|
|
||||||
depending on the power, the number of nearby entities, and the
|
|
||||||
nearby terrain. This means that no benchmark can cover all the cases.
|
|
||||||
I decided to test a giant block of TNT, as that's where the optimisations
|
|
||||||
would be needed the most.
|
|
||||||
|
|
||||||
I tested using a 50x10x50 block of TNT above ground
|
|
||||||
and determined the following:
|
|
||||||
|
|
||||||
Vanilla time per explosion: 2.27ms
|
|
||||||
Lithium time per explosion: 1.07ms
|
|
||||||
This patch time per explosion: 0.45ms
|
|
||||||
|
|
||||||
The results indicate that this logic is 5 times faster than Vanilla
|
|
||||||
and 2.3 times faster than Lithium.
|
|
||||||
|
|
||||||
diff --git a/src/main/java/net/minecraft/world/level/Explosion.java b/src/main/java/net/minecraft/world/level/Explosion.java
|
|
||||||
index bff83fe413c7baef4ba56a3270ea4463a58c792f..7aa9ddb1d61ffb7da3f867e5a5bd04e3432b5621 100644
|
|
||||||
--- a/src/main/java/net/minecraft/world/level/Explosion.java
|
|
||||||
+++ b/src/main/java/net/minecraft/world/level/Explosion.java
|
|
||||||
@@ -113,6 +113,271 @@ public class Explosion {
|
|
||||||
this.yield = this.blockInteraction == Explosion.BlockInteraction.DESTROY_WITH_DECAY ? 1.0F / this.radius : 1.0F; // CraftBukkit
|
|
||||||
}
|
|
||||||
|
|
||||||
+ // Paper start - optimise collisions
|
|
||||||
+ private static final double[] CACHED_RAYS;
|
|
||||||
+ static {
|
|
||||||
+ final it.unimi.dsi.fastutil.doubles.DoubleArrayList rayCoords = new it.unimi.dsi.fastutil.doubles.DoubleArrayList();
|
|
||||||
+
|
|
||||||
+ for (int x = 0; x <= 15; ++x) {
|
|
||||||
+ for (int y = 0; y <= 15; ++y) {
|
|
||||||
+ for (int z = 0; z <= 15; ++z) {
|
|
||||||
+ if ((x == 0 || x == 15) || (y == 0 || y == 15) || (z == 0 || z == 15)) {
|
|
||||||
+ double xDir = (double)((float)x / 15.0F * 2.0F - 1.0F);
|
|
||||||
+ double yDir = (double)((float)y / 15.0F * 2.0F - 1.0F);
|
|
||||||
+ double zDir = (double)((float)z / 15.0F * 2.0F - 1.0F);
|
|
||||||
+
|
|
||||||
+ double mag = Math.sqrt(
|
|
||||||
+ xDir * xDir + yDir * yDir + zDir * zDir
|
|
||||||
+ );
|
|
||||||
+
|
|
||||||
+ rayCoords.add((xDir / mag) * (double)0.3F);
|
|
||||||
+ rayCoords.add((yDir / mag) * (double)0.3F);
|
|
||||||
+ rayCoords.add((zDir / mag) * (double)0.3F);
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ CACHED_RAYS = rayCoords.toDoubleArray();
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ private static final int CHUNK_CACHE_SHIFT = 2;
|
|
||||||
+ private static final int CHUNK_CACHE_MASK = (1 << CHUNK_CACHE_SHIFT) - 1;
|
|
||||||
+ private static final int CHUNK_CACHE_WIDTH = 1 << CHUNK_CACHE_SHIFT;
|
|
||||||
+
|
|
||||||
+ private static final int BLOCK_EXPLOSION_CACHE_SHIFT = 3;
|
|
||||||
+ private static final int BLOCK_EXPLOSION_CACHE_MASK = (1 << BLOCK_EXPLOSION_CACHE_SHIFT) - 1;
|
|
||||||
+ private static final int BLOCK_EXPLOSION_CACHE_WIDTH = 1 << BLOCK_EXPLOSION_CACHE_SHIFT;
|
|
||||||
+
|
|
||||||
+ // resistance = (res + 0.3F) * 0.3F;
|
|
||||||
+ // so for resistance = 0, we need res = -0.3F
|
|
||||||
+ private static final Float ZERO_RESISTANCE = Float.valueOf(-0.3f);
|
|
||||||
+ private it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<ExplosionBlockCache> blockCache = null;
|
|
||||||
+
|
|
||||||
+ public static final class ExplosionBlockCache {
|
|
||||||
+
|
|
||||||
+ public final long key;
|
|
||||||
+ public final BlockPos immutablePos;
|
|
||||||
+ public final BlockState blockState;
|
|
||||||
+ public final FluidState fluidState;
|
|
||||||
+ public final float resistance;
|
|
||||||
+ public final boolean outOfWorld;
|
|
||||||
+ public Boolean shouldExplode; // null -> not called yet
|
|
||||||
+ public net.minecraft.world.phys.shapes.VoxelShape cachedCollisionShape;
|
|
||||||
+
|
|
||||||
+ public ExplosionBlockCache(long key, BlockPos immutablePos, BlockState blockState, FluidState fluidState, float resistance,
|
|
||||||
+ boolean outOfWorld) {
|
|
||||||
+ this.key = key;
|
|
||||||
+ this.immutablePos = immutablePos;
|
|
||||||
+ this.blockState = blockState;
|
|
||||||
+ this.fluidState = fluidState;
|
|
||||||
+ this.resistance = resistance;
|
|
||||||
+ this.outOfWorld = outOfWorld;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ private long[] chunkPosCache = null;
|
|
||||||
+ private net.minecraft.world.level.chunk.LevelChunk[] chunkCache = null;
|
|
||||||
+
|
|
||||||
+ private ExplosionBlockCache getOrCacheExplosionBlock(final int x, final int y, final int z,
|
|
||||||
+ final long key, final boolean calculateResistance) {
|
|
||||||
+ ExplosionBlockCache ret = this.blockCache.get(key);
|
|
||||||
+ if (ret != null) {
|
|
||||||
+ return ret;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ BlockPos pos = new BlockPos(x, y, z);
|
|
||||||
+
|
|
||||||
+ if (!this.level.isInWorldBounds(pos)) {
|
|
||||||
+ ret = new ExplosionBlockCache(key, pos, null, null, 0.0f, true);
|
|
||||||
+ } else {
|
|
||||||
+ net.minecraft.world.level.chunk.LevelChunk chunk;
|
|
||||||
+ long chunkKey = io.papermc.paper.util.CoordinateUtils.getChunkKey(x >> 4, z >> 4);
|
|
||||||
+ int chunkCacheKey = ((x >> 4) & CHUNK_CACHE_MASK) | (((z >> 4) << CHUNK_CACHE_SHIFT) & (CHUNK_CACHE_MASK << CHUNK_CACHE_SHIFT));
|
|
||||||
+ if (this.chunkPosCache[chunkCacheKey] == chunkKey) {
|
|
||||||
+ chunk = this.chunkCache[chunkCacheKey];
|
|
||||||
+ } else {
|
|
||||||
+ this.chunkPosCache[chunkCacheKey] = chunkKey;
|
|
||||||
+ this.chunkCache[chunkCacheKey] = chunk = this.level.getChunk(x >> 4, z >> 4);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ BlockState blockState = chunk.getBlockStateFinal(x, y, z);
|
|
||||||
+ FluidState fluidState = blockState.getFluidState();
|
|
||||||
+
|
|
||||||
+ Optional<Float> resistance = !calculateResistance ? Optional.empty() : this.damageCalculator.getBlockExplosionResistance((Explosion)(Object)this, this.level, pos, blockState, fluidState);
|
|
||||||
+
|
|
||||||
+ ret = new ExplosionBlockCache(
|
|
||||||
+ key, pos, blockState, fluidState,
|
|
||||||
+ (resistance.orElse(ZERO_RESISTANCE).floatValue() + 0.3f) * 0.3f,
|
|
||||||
+ false
|
|
||||||
+ );
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ this.blockCache.put(key, ret);
|
|
||||||
+
|
|
||||||
+ return ret;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ private boolean clipsAnything(final Vec3 from, final Vec3 to,
|
|
||||||
+ final io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext context,
|
|
||||||
+ final ExplosionBlockCache[] blockCache,
|
|
||||||
+ final BlockPos.MutableBlockPos currPos) {
|
|
||||||
+ // assume that context.delegated = false
|
|
||||||
+ final double adjX = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.x - to.x);
|
|
||||||
+ final double adjY = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.y - to.y);
|
|
||||||
+ final double adjZ = io.papermc.paper.util.CollisionUtil.COLLISION_EPSILON * (from.z - to.z);
|
|
||||||
+
|
|
||||||
+ if (adjX == 0.0 && adjY == 0.0 && adjZ == 0.0) {
|
|
||||||
+ return false;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ final double toXAdj = to.x - adjX;
|
|
||||||
+ final double toYAdj = to.y - adjY;
|
|
||||||
+ final double toZAdj = to.z - adjZ;
|
|
||||||
+ final double fromXAdj = from.x + adjX;
|
|
||||||
+ final double fromYAdj = from.y + adjY;
|
|
||||||
+ final double fromZAdj = from.z + adjZ;
|
|
||||||
+
|
|
||||||
+ int currX = Mth.floor(fromXAdj);
|
|
||||||
+ int currY = Mth.floor(fromYAdj);
|
|
||||||
+ int currZ = Mth.floor(fromZAdj);
|
|
||||||
+
|
|
||||||
+ final double diffX = toXAdj - fromXAdj;
|
|
||||||
+ final double diffY = toYAdj - fromYAdj;
|
|
||||||
+ final double diffZ = toZAdj - fromZAdj;
|
|
||||||
+
|
|
||||||
+ final double dxDouble = Math.signum(diffX);
|
|
||||||
+ final double dyDouble = Math.signum(diffY);
|
|
||||||
+ final double dzDouble = Math.signum(diffZ);
|
|
||||||
+
|
|
||||||
+ final int dx = (int)dxDouble;
|
|
||||||
+ final int dy = (int)dyDouble;
|
|
||||||
+ final int dz = (int)dzDouble;
|
|
||||||
+
|
|
||||||
+ final double normalizedDiffX = diffX == 0.0 ? Double.MAX_VALUE : dxDouble / diffX;
|
|
||||||
+ final double normalizedDiffY = diffY == 0.0 ? Double.MAX_VALUE : dyDouble / diffY;
|
|
||||||
+ final double normalizedDiffZ = diffZ == 0.0 ? Double.MAX_VALUE : dzDouble / diffZ;
|
|
||||||
+
|
|
||||||
+ double normalizedCurrX = normalizedDiffX * (diffX > 0.0 ? (1.0 - Mth.frac(fromXAdj)) : Mth.frac(fromXAdj));
|
|
||||||
+ double normalizedCurrY = normalizedDiffY * (diffY > 0.0 ? (1.0 - Mth.frac(fromYAdj)) : Mth.frac(fromYAdj));
|
|
||||||
+ double normalizedCurrZ = normalizedDiffZ * (diffZ > 0.0 ? (1.0 - Mth.frac(fromZAdj)) : Mth.frac(fromZAdj));
|
|
||||||
+
|
|
||||||
+ for (;;) {
|
|
||||||
+ currPos.set(currX, currY, currZ);
|
|
||||||
+
|
|
||||||
+ // ClipContext.Block.COLLIDER -> BlockBehaviour.BlockStateBase::getCollisionShape
|
|
||||||
+ // ClipContext.Fluid.NONE -> ignore fluids
|
|
||||||
+
|
|
||||||
+ // read block from cache
|
|
||||||
+ final long key = BlockPos.asLong(currX, currY, currZ);
|
|
||||||
+
|
|
||||||
+ final int cacheKey =
|
|
||||||
+ (currX & BLOCK_EXPLOSION_CACHE_MASK) |
|
|
||||||
+ (currY & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT) |
|
|
||||||
+ (currZ & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT + BLOCK_EXPLOSION_CACHE_SHIFT);
|
|
||||||
+ ExplosionBlockCache cachedBlock = blockCache[cacheKey];
|
|
||||||
+ if (cachedBlock == null || cachedBlock.key != key) {
|
|
||||||
+ blockCache[cacheKey] = cachedBlock = this.getOrCacheExplosionBlock(currX, currY, currZ, key, false);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ final BlockState blockState = cachedBlock.blockState;
|
|
||||||
+ if (blockState != null && !blockState.emptyCollisionShape()) {
|
|
||||||
+ net.minecraft.world.phys.shapes.VoxelShape collision = cachedBlock.cachedCollisionShape;
|
|
||||||
+ if (collision == null) {
|
|
||||||
+ collision = blockState.getConstantCollisionShape();
|
|
||||||
+ if (collision == null) {
|
|
||||||
+ collision = blockState.getCollisionShape(this.level, currPos, context);
|
|
||||||
+ if (!context.isDelegated()) {
|
|
||||||
+ // if it was not delegated during this call, assume that for any future ones it will not be delegated
|
|
||||||
+ // again, and cache the result
|
|
||||||
+ cachedBlock.cachedCollisionShape = collision;
|
|
||||||
+ }
|
|
||||||
+ } else {
|
|
||||||
+ cachedBlock.cachedCollisionShape = collision;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ if (!collision.isEmpty() && collision.clip(from, to, currPos) != null) {
|
|
||||||
+ return true;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ if (normalizedCurrX > 1.0 && normalizedCurrY > 1.0 && normalizedCurrZ > 1.0) {
|
|
||||||
+ return false;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ // inc the smallest normalized coordinate
|
|
||||||
+
|
|
||||||
+ if (normalizedCurrX < normalizedCurrY) {
|
|
||||||
+ if (normalizedCurrX < normalizedCurrZ) {
|
|
||||||
+ currX += dx;
|
|
||||||
+ normalizedCurrX += normalizedDiffX;
|
|
||||||
+ } else {
|
|
||||||
+ // x < y && x >= z <--> z < y && z <= x
|
|
||||||
+ currZ += dz;
|
|
||||||
+ normalizedCurrZ += normalizedDiffZ;
|
|
||||||
+ }
|
|
||||||
+ } else if (normalizedCurrY < normalizedCurrZ) {
|
|
||||||
+ // y <= x && y < z
|
|
||||||
+ currY += dy;
|
|
||||||
+ normalizedCurrY += normalizedDiffY;
|
|
||||||
+ } else {
|
|
||||||
+ // y <= x && z <= y <--> z <= y && z <= x
|
|
||||||
+ currZ += dz;
|
|
||||||
+ normalizedCurrZ += normalizedDiffZ;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ private float getSeenFraction(final Vec3 source, final Entity target,
|
|
||||||
+ final ExplosionBlockCache[] blockCache,
|
|
||||||
+ final BlockPos.MutableBlockPos blockPos) {
|
|
||||||
+ final AABB boundingBox = target.getBoundingBox();
|
|
||||||
+ final double diffX = boundingBox.maxX - boundingBox.minX;
|
|
||||||
+ final double diffY = boundingBox.maxY - boundingBox.minY;
|
|
||||||
+ final double diffZ = boundingBox.maxZ - boundingBox.minZ;
|
|
||||||
+
|
|
||||||
+ final double incX = 1.0 / (diffX * 2.0 + 1.0);
|
|
||||||
+ final double incY = 1.0 / (diffY * 2.0 + 1.0);
|
|
||||||
+ final double incZ = 1.0 / (diffZ * 2.0 + 1.0);
|
|
||||||
+
|
|
||||||
+ if (incX < 0.0 || incY < 0.0 || incZ < 0.0) {
|
|
||||||
+ return 0.0f;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ final double offX = (1.0 - Math.floor(1.0 / incX) * incX) * 0.5 + boundingBox.minX;
|
|
||||||
+ final double offY = boundingBox.minY;
|
|
||||||
+ final double offZ = (1.0 - Math.floor(1.0 / incZ) * incZ) * 0.5 + boundingBox.minZ;
|
|
||||||
+
|
|
||||||
+ final io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext context = new io.papermc.paper.util.CollisionUtil.LazyEntityCollisionContext(target);
|
|
||||||
+
|
|
||||||
+ int totalRays = 0;
|
|
||||||
+ int missedRays = 0;
|
|
||||||
+
|
|
||||||
+ for (double dx = 0.0; dx <= 1.0; dx += incX) {
|
|
||||||
+ final double fromX = Math.fma(dx, diffX, offX);
|
|
||||||
+ for (double dy = 0.0; dy <= 1.0; dy += incY) {
|
|
||||||
+ final double fromY = Math.fma(dy, diffY, offY);
|
|
||||||
+ for (double dz = 0.0; dz <= 1.0; dz += incZ) {
|
|
||||||
+ ++totalRays;
|
|
||||||
+
|
|
||||||
+ final Vec3 from = new Vec3(
|
|
||||||
+ fromX,
|
|
||||||
+ fromY,
|
|
||||||
+ Math.fma(dz, diffZ, offZ)
|
|
||||||
+ );
|
|
||||||
+
|
|
||||||
+ if (!this.clipsAnything(from, source, context, blockCache, blockPos)) {
|
|
||||||
+ ++missedRays;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ return (float)missedRays / (float)totalRays;
|
|
||||||
+ }
|
|
||||||
+ // Paper end - optimise collisions
|
|
||||||
+
|
|
||||||
private ExplosionDamageCalculator makeDamageCalculator(@Nullable Entity entity) {
|
|
||||||
return (ExplosionDamageCalculator) (entity == null ? Explosion.EXPLOSION_DAMAGE_CALCULATOR : new EntityBasedExplosionDamageCalculator(entity));
|
|
||||||
}
|
|
||||||
@@ -173,40 +438,88 @@ public class Explosion {
|
|
||||||
int i;
|
|
||||||
int j;
|
|
||||||
|
|
||||||
- for (int k = 0; k < 16; ++k) {
|
|
||||||
- for (i = 0; i < 16; ++i) {
|
|
||||||
- for (j = 0; j < 16; ++j) {
|
|
||||||
- if (k == 0 || k == 15 || i == 0 || i == 15 || j == 0 || j == 15) {
|
|
||||||
- double d0 = (double) ((float) k / 15.0F * 2.0F - 1.0F);
|
|
||||||
- double d1 = (double) ((float) i / 15.0F * 2.0F - 1.0F);
|
|
||||||
- double d2 = (double) ((float) j / 15.0F * 2.0F - 1.0F);
|
|
||||||
- double d3 = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2);
|
|
||||||
-
|
|
||||||
- d0 /= d3;
|
|
||||||
- d1 /= d3;
|
|
||||||
- d2 /= d3;
|
|
||||||
+ // Paper start - optimise explosions
|
|
||||||
+ this.blockCache = new it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap<>();
|
|
||||||
+
|
|
||||||
+ this.chunkPosCache = new long[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
|
|
||||||
+ java.util.Arrays.fill(this.chunkPosCache, ChunkPos.INVALID_CHUNK_POS);
|
|
||||||
+
|
|
||||||
+ this.chunkCache = new net.minecraft.world.level.chunk.LevelChunk[CHUNK_CACHE_WIDTH * CHUNK_CACHE_WIDTH];
|
|
||||||
+
|
|
||||||
+ final ExplosionBlockCache[] blockCache = new ExplosionBlockCache[BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH * BLOCK_EXPLOSION_CACHE_WIDTH];
|
|
||||||
+ // use initial cache value that is most likely to be used: the source position
|
|
||||||
+ final ExplosionBlockCache initialCache;
|
|
||||||
+ {
|
|
||||||
+ final int blockX = Mth.floor(this.x);
|
|
||||||
+ final int blockY = Mth.floor(this.y);
|
|
||||||
+ final int blockZ = Mth.floor(this.z);
|
|
||||||
+
|
|
||||||
+ final long key = BlockPos.asLong(blockX, blockY, blockZ);
|
|
||||||
+
|
|
||||||
+ initialCache = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true);
|
|
||||||
+ }
|
|
||||||
+ // only ~1/3rd of the loop iterations in vanilla will result in a ray, as it is iterating the perimeter of
|
|
||||||
+ // a 16x16x16 cube
|
|
||||||
+ // we can cache the rays and their normals as well, so that we eliminate the excess iterations / checks and
|
|
||||||
+ // calculations in one go
|
|
||||||
+ // additional aggressive caching of block retrieval is very significant, as at low power (i.e tnt) most
|
|
||||||
+ // block retrievals are not unique
|
|
||||||
+ for (int ray = 0, len = CACHED_RAYS.length; ray < len;) {
|
|
||||||
+ {
|
|
||||||
+ {
|
|
||||||
+ {
|
|
||||||
+ ExplosionBlockCache cachedBlock = initialCache;
|
|
||||||
+
|
|
||||||
+ double d0 = CACHED_RAYS[ray];
|
|
||||||
+ double d1 = CACHED_RAYS[ray + 1];
|
|
||||||
+ double d2 = CACHED_RAYS[ray + 2];
|
|
||||||
+ ray += 3;
|
|
||||||
+ // Paper end - optimise explosions
|
|
||||||
float f = this.radius * (0.7F + this.level.random.nextFloat() * 0.6F);
|
|
||||||
double d4 = this.x;
|
|
||||||
double d5 = this.y;
|
|
||||||
double d6 = this.z;
|
|
||||||
|
|
||||||
for (float f1 = 0.3F; f > 0.0F; f -= 0.22500001F) {
|
|
||||||
- BlockPos blockposition = BlockPos.containing(d4, d5, d6);
|
|
||||||
- BlockState iblockdata = this.level.getBlockState(blockposition);
|
|
||||||
- if (!iblockdata.isDestroyable()) continue; // Paper - Protect Bedrock and End Portal/Frames from being destroyed
|
|
||||||
- FluidState fluid = iblockdata.getFluidState(); // Paper - Perf: Optimize call to getFluid for explosions
|
|
||||||
+ // Paper start - optimise explosions
|
|
||||||
+ final int blockX = Mth.floor(d4);
|
|
||||||
+ final int blockY = Mth.floor(d5);
|
|
||||||
+ final int blockZ = Mth.floor(d6);
|
|
||||||
+
|
|
||||||
+ final long key = BlockPos.asLong(blockX, blockY, blockZ);
|
|
||||||
+
|
|
||||||
+ if (cachedBlock.key != key) {
|
|
||||||
+ final int cacheKey =
|
|
||||||
+ (blockX & BLOCK_EXPLOSION_CACHE_MASK) |
|
|
||||||
+ (blockY & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT) |
|
|
||||||
+ (blockZ & BLOCK_EXPLOSION_CACHE_MASK) << (BLOCK_EXPLOSION_CACHE_SHIFT + BLOCK_EXPLOSION_CACHE_SHIFT);
|
|
||||||
+ cachedBlock = blockCache[cacheKey];
|
|
||||||
+ if (cachedBlock == null || cachedBlock.key != key) {
|
|
||||||
+ blockCache[cacheKey] = cachedBlock = this.getOrCacheExplosionBlock(blockX, blockY, blockZ, key, true);
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
|
|
||||||
- if (!this.level.isInWorldBounds(blockposition)) {
|
|
||||||
+ if (cachedBlock.outOfWorld) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
- Optional<Float> optional = this.damageCalculator.getBlockExplosionResistance(this, this.level, blockposition, iblockdata, fluid);
|
|
||||||
+ BlockPos blockposition = cachedBlock.immutablePos;
|
|
||||||
+ BlockState iblockdata = cachedBlock.blockState;
|
|
||||||
+ // Paper end - optimise explosions
|
|
||||||
|
|
||||||
- if (optional.isPresent()) {
|
|
||||||
- f -= ((Float) optional.get() + 0.3F) * 0.3F;
|
|
||||||
- }
|
|
||||||
+ if (!iblockdata.isDestroyable()) continue; // Paper
|
|
||||||
+ // Paper - optimise explosions
|
|
||||||
|
|
||||||
- if (f > 0.0F && this.damageCalculator.shouldBlockExplode(this, this.level, blockposition, iblockdata, f)) {
|
|
||||||
+ f -= cachedBlock.resistance; // Paper - optimise explosions
|
|
||||||
+
|
|
||||||
+ if (f > 0.0F && cachedBlock.shouldExplode == null) { // Paper - optimise explosions
|
|
||||||
+ // Paper start - optimise explosions
|
|
||||||
+ // note: we expect shouldBlockExplode to be pure with respect to power, as Vanilla currently is.
|
|
||||||
+ // basically, it is unused, which allows us to cache the result
|
|
||||||
+ final boolean shouldExplode = this.damageCalculator.shouldBlockExplode(this, this.level, cachedBlock.immutablePos, cachedBlock.blockState, f);
|
|
||||||
+ cachedBlock.shouldExplode = shouldExplode ? Boolean.TRUE : Boolean.FALSE;
|
|
||||||
+ if (shouldExplode && (this.fire || !cachedBlock.blockState.isAir())) {
|
|
||||||
+ // Paper end - optimise explosions
|
|
||||||
set.add(blockposition);
|
|
||||||
// Paper start - prevent headless pistons from forming
|
|
||||||
if (!io.papermc.paper.configuration.GlobalConfiguration.get().unsupportedSettings.allowHeadlessPistons && iblockdata.getBlock() == Blocks.MOVING_PISTON) {
|
|
||||||
@@ -217,11 +530,12 @@ public class Explosion {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Paper end - prevent headless pistons from forming
|
|
||||||
+ } // Paper - optimise explosions
|
|
||||||
}
|
|
||||||
|
|
||||||
- d4 += d0 * 0.30000001192092896D;
|
|
||||||
- d5 += d1 * 0.30000001192092896D;
|
|
||||||
- d6 += d2 * 0.30000001192092896D;
|
|
||||||
+ d4 += d0; // Paper - optimise explosions
|
|
||||||
+ d5 += d1; // Paper - optimise explosions
|
|
||||||
+ d6 += d2; // Paper - optimise explosions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -241,6 +555,8 @@ public class Explosion {
|
|
||||||
Vec3 vec3d = new Vec3(this.x, this.y, this.z);
|
|
||||||
Iterator iterator = list.iterator();
|
|
||||||
|
|
||||||
+ final BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos(); // Paper - optimise explosions
|
|
||||||
+
|
|
||||||
while (iterator.hasNext()) {
|
|
||||||
Entity entity = (Entity) iterator.next();
|
|
||||||
|
|
||||||
@@ -276,11 +592,11 @@ public class Explosion {
|
|
||||||
for (EnderDragonPart entityComplexPart : ((EnderDragon) entity).subEntities) {
|
|
||||||
// Calculate damage separately for each EntityComplexPart
|
|
||||||
if (list.contains(entityComplexPart)) {
|
|
||||||
- entityComplexPart.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity));
|
|
||||||
+ entityComplexPart.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entityComplexPart, getSeenFraction(vec3d, entityComplexPart, blockCache, blockPos))); // Paper - actually optimise explosions and use the right entity to calculate the damage
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
- entity.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity));
|
|
||||||
+ entity.hurt(this.damageSource, this.damageCalculator.getEntityDamageAmount(this, entity, getSeenFraction(vec3d, entity, blockCache, blockPos))); // Paper - actually optimise explosions
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entity.lastDamageCancelled) { // SPIGOT-5339, SPIGOT-6252, SPIGOT-6777: Skip entity if damage event was cancelled
|
|
||||||
@@ -289,7 +605,7 @@ public class Explosion {
|
|
||||||
// CraftBukkit end
|
|
||||||
}
|
|
||||||
|
|
||||||
- double d12 = (1.0D - d7) * this.getBlockDensity(vec3d, entity) * (double) this.damageCalculator.getKnockbackMultiplier(entity); // Paper - Optimize explosions
|
|
||||||
+ double d12 = (1.0D - d7) * this.getBlockDensity(vec3d, entity, blockCache, blockPos) * (double) this.damageCalculator.getKnockbackMultiplier(entity); // Paper - Optimize explosions
|
|
||||||
double d13;
|
|
||||||
|
|
||||||
if (entity instanceof LivingEntity) {
|
|
||||||
@@ -328,6 +644,9 @@ public class Explosion {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+ this.blockCache = null; // Paper - optimise explosions
|
|
||||||
+ this.chunkPosCache = null; // Paper - optimise explosions
|
|
||||||
+ this.chunkCache = null; // Paper - optimise explosions
|
|
||||||
}
|
|
||||||
|
|
||||||
public void finalizeExplosion(boolean particles) {
|
|
||||||
@@ -547,14 +866,14 @@ public class Explosion {
|
|
||||||
private BlockInteraction() {}
|
|
||||||
}
|
|
||||||
// Paper start - Optimize explosions
|
|
||||||
- private float getBlockDensity(Vec3 vec3d, Entity entity) {
|
|
||||||
+ private float getBlockDensity(Vec3 vec3d, Entity entity, ExplosionBlockCache[] blockCache, BlockPos.MutableBlockPos blockPos) { // Paper - optimise explosions
|
|
||||||
if (!this.level.paperConfig().environment.optimizeExplosions) {
|
|
||||||
- return getSeenPercent(vec3d, entity);
|
|
||||||
+ return this.getSeenFraction(vec3d, entity, blockCache, blockPos); // Paper - optimise explosions
|
|
||||||
}
|
|
||||||
CacheKey key = new CacheKey(this, entity.getBoundingBox());
|
|
||||||
Float blockDensity = this.level.explosionDensityCache.get(key);
|
|
||||||
if (blockDensity == null) {
|
|
||||||
- blockDensity = getSeenPercent(vec3d, entity);
|
|
||||||
+ blockDensity = this.getSeenFraction(vec3d, entity, blockCache, blockPos); // Paper - optimise explosions;
|
|
||||||
this.level.explosionDensityCache.put(key, blockDensity);
|
|
||||||
}
|
|
||||||
|
|
||||||
diff --git a/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java b/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java
|
|
||||||
index 0ef9b402d129b072134688c06719a56328581158..1eb259b48bcab6172c15546744eea410c6a3e1fe 100644
|
|
||||||
--- a/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java
|
|
||||||
+++ b/src/main/java/net/minecraft/world/level/ExplosionDamageCalculator.java
|
|
||||||
@@ -26,11 +26,17 @@ public class ExplosionDamageCalculator {
|
|
||||||
return 1.0F;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ @io.papermc.paper.annotation.DoNotUse @Deprecated // Paper
|
|
||||||
public float getEntityDamageAmount(Explosion explosion, Entity entity) {
|
|
||||||
+ // Paper start - actually optimise explosions
|
|
||||||
+ return this.getEntityDamageAmount(explosion, entity, Explosion.getSeenPercent(explosion.center(), entity));
|
|
||||||
+ }
|
|
||||||
+ public float getEntityDamageAmount(Explosion explosion, Entity entity, double seenPercent) {
|
|
||||||
+ // Paper end - actually optimise explosions
|
|
||||||
float f = explosion.radius() * 2.0F;
|
|
||||||
Vec3 vec3 = explosion.center();
|
|
||||||
double d = Math.sqrt(entity.distanceToSqr(vec3)) / (double)f;
|
|
||||||
- double e = (1.0 - d) * (double)Explosion.getSeenPercent(vec3, entity);
|
|
||||||
+ double e = (1.0 - d) * seenPercent; // Paper - actually optimise explosions
|
|
||||||
return (float)((e * e + e) / 2.0 * 7.0 * (double)f + 1.0);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
|
||||||
Date: Sat, 18 Jul 2020 16:03:57 -0700
|
|
||||||
Subject: [PATCH] Distance manager tick timings
|
|
||||||
|
|
||||||
Recently this has been taking up more time, so add a timings to
|
|
||||||
really figure out how much.
|
|
||||||
|
|
||||||
diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java
|
|
||||||
index 46449728f69ee7d4f78470f8da23c055acd53a3b..4b467f1af93452d13829f756d55dee18b8889d40 100644
|
|
||||||
--- a/src/main/java/co/aikar/timings/MinecraftTimings.java
|
|
||||||
+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java
|
|
||||||
@@ -47,6 +47,7 @@ public final class MinecraftTimings {
|
|
||||||
public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update");
|
|
||||||
public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate");
|
|
||||||
public static final Timing scoreboardScoreSearch = Timings.ofSafe("Scoreboard score search"); // Paper - add timings for scoreboard search
|
|
||||||
+ public static final Timing distanceManagerTick = Timings.ofSafe("Distance Manager Tick"); // Paper - add timings for distance manager
|
|
||||||
|
|
||||||
public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks");
|
|
||||||
|
|
||||||
diff --git a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
|
|
||||||
index 5b446e6ac151f99f64f0c442d0b40b5e251bc4c4..6bc7c6f16a1649fc9e24e7cf90fca401e5bd4875 100644
|
|
||||||
--- a/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
|
|
||||||
+++ b/src/main/java/io/papermc/paper/chunk/system/scheduling/ChunkHolderManager.java
|
|
||||||
@@ -1316,7 +1316,9 @@ public final class ChunkHolderManager {
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean processTicketUpdates() {
|
|
||||||
+ co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Paper - add timings for distance manager
|
|
||||||
return this.processTicketUpdates(true, true, null);
|
|
||||||
+ } finally { co.aikar.timings.MinecraftTimings.distanceManagerTick.stopTiming(); } // Paper - add timings for distance manager
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final ThreadLocal<List<ChunkProgressionTask>> CURRENT_TICKET_UPDATE_SCHEDULING = new ThreadLocal<>();
|
|
@ -1,23 +0,0 @@
|
|||||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
||||||
From: Spottedleaf <Spottedleaf@users.noreply.github.com>
|
|
||||||
Date: Tue, 16 Feb 2021 00:16:56 -0800
|
|
||||||
Subject: [PATCH] Send full pos packets for hard colliding entities
|
|
||||||
|
|
||||||
Prevent collision problems due to desync (i.e boats)
|
|
||||||
|
|
||||||
Configurable under
|
|
||||||
`send-full-pos-for-hard-colliding-entities`
|
|
||||||
|
|
||||||
diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java
|
|
||||||
index 22eec853588ded2d255ab69d408f8e987832abe2..4f103f731623a8570238a6867fda1c5f83fca4e4 100644
|
|
||||||
--- a/src/main/java/net/minecraft/server/level/ServerEntity.java
|
|
||||||
+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java
|
|
||||||
@@ -183,7 +183,7 @@ public class ServerEntity {
|
|
||||||
long i1 = this.positionCodec.encodeZ(vec3d);
|
|
||||||
boolean flag6 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L;
|
|
||||||
|
|
||||||
- if (!flag6 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.onGround()) {
|
|
||||||
+ if (!flag6 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.onGround()&& !(io.papermc.paper.configuration.GlobalConfiguration.get().collisions.sendFullPosForHardCollidingEntities && this.entity.hardCollides())) { // Paper - send full pos for hard colliding entities to prevent collision problems due to desync
|
|
||||||
if ((!flag2 || !flag3) && !(this.entity instanceof AbstractArrow)) {
|
|
||||||
if (flag2) {
|
|
||||||
packet1 = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) ((int) k), (short) ((int) l), (short) ((int) i1), this.entity.onGround());
|
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren