Skip to content

Commit ae959a2

Browse files
committed
[ChunkDataThrottle] Add detectLavaPool
1 parent f29f88c commit ae959a2

File tree

3 files changed

+101
-39
lines changed

3 files changed

+101
-39
lines changed

bukkit/src/main/kotlin/io/github/rothes/esu/bukkit/module/NetworkThrottleModule.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ object NetworkThrottleModule: BukkitModule<NetworkThrottleModule.ModuleConfig, N
117117
Enable this could help with saving bandwidth in nether, as there's many single-block lava.
118118
""")
119119
val detectInvisibleSingleBlock: Boolean = false,
120+
@Comment("""
121+
Detect lava pool, and consider lava blocks which being covered invisible.
122+
This step takes extra ~0.03ms, so it's not enabled by default.
123+
It also makes the plugin detect nearby blocks everytime player moves.
124+
Enable this could help with saving bandwidth, especially in nether.
125+
""")
126+
val detectLavaPool: Boolean = false,
120127
@RenamedFrom("single-valued-section-block-list")
121128
@Comment("""
122129
This feature doesn't support running along with any other anti-xray plugins.

bukkit/src/main/kotlin/io/github/rothes/esu/bukkit/module/networkthrottle/chunkdatathrottle/ChunkDataThrottleHandler.kt

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,6 @@ interface ChunkDataThrottleHandler {
88
fun disable()
99
fun reload()
1010

11-
companion object {
12-
// The block B is in center. if Y_MINUS, block in B's bottom is occluding(i.e. blocking) .
13-
const val X_PLUS = 0b00001.toByte()
14-
const val X_MINUS = 0b00010.toByte()
15-
const val Z_PLUS = 0b00100.toByte()
16-
const val Z_MINUS = 0b01000.toByte()
17-
const val Y_MINUS = 0b10000.toByte()
18-
const val INVISIBLE = 0b11111.toByte()
19-
}
20-
2111
data class Counter(
2212
var minimalChunks: Long = 0,
2313
var resentChunks: Long = 0,

bukkit/version/v1_18/src/main/kotlin/io/github/rothes/esu/bukkit/module/networkthrottle/chunkdatathrottle/v1_18/ChunkDataThrottleHandlerImpl.kt

Lines changed: 94 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,6 @@ import io.github.retrooper.packetevents.util.SpigotConversionUtil
2828
import io.github.rothes.esu.bukkit.module.NetworkThrottleModule
2929
import io.github.rothes.esu.bukkit.module.NetworkThrottleModule.config
3030
import io.github.rothes.esu.bukkit.module.networkthrottle.chunkdatathrottle.ChunkDataThrottleHandler
31-
import io.github.rothes.esu.bukkit.module.networkthrottle.chunkdatathrottle.ChunkDataThrottleHandler.Companion.INVISIBLE
32-
import io.github.rothes.esu.bukkit.module.networkthrottle.chunkdatathrottle.ChunkDataThrottleHandler.Companion.X_MINUS
33-
import io.github.rothes.esu.bukkit.module.networkthrottle.chunkdatathrottle.ChunkDataThrottleHandler.Companion.X_PLUS
34-
import io.github.rothes.esu.bukkit.module.networkthrottle.chunkdatathrottle.ChunkDataThrottleHandler.Companion.Y_MINUS
35-
import io.github.rothes.esu.bukkit.module.networkthrottle.chunkdatathrottle.ChunkDataThrottleHandler.Companion.Z_MINUS
36-
import io.github.rothes.esu.bukkit.module.networkthrottle.chunkdatathrottle.ChunkDataThrottleHandler.Companion.Z_PLUS
3731
import io.github.rothes.esu.bukkit.module.networkthrottle.chunkdatathrottle.ChunkSender
3832
import io.github.rothes.esu.bukkit.module.networkthrottle.chunkdatathrottle.LevelHandler
3933
import io.github.rothes.esu.bukkit.module.networkthrottle.chunkdatathrottle.PalettedContainerReader
@@ -61,12 +55,15 @@ import org.bukkit.World.Environment
6155
import org.bukkit.craftbukkit.v1_18_R1.entity.CraftPlayer
6256
import org.bukkit.entity.Player
6357
import org.bukkit.event.EventHandler
58+
import org.bukkit.event.EventPriority
6459
import org.bukkit.event.HandlerList
6560
import org.bukkit.event.Listener
6661
import org.bukkit.event.player.PlayerJoinEvent
62+
import org.bukkit.event.player.PlayerMoveEvent
6763
import org.bukkit.event.player.PlayerQuitEvent
6864
import java.nio.file.StandardOpenOption
6965
import java.util.*
66+
import kotlin.experimental.and
7067
import kotlin.experimental.or
7168
import kotlin.io.path.fileSize
7269
import kotlin.io.path.outputStream
@@ -81,13 +78,25 @@ class ChunkDataThrottleHandlerImpl: ChunkDataThrottleHandler,
8178
PacketListenerAbstract(PacketListenerPriority.HIGHEST), Listener {
8279

8380
companion object {
81+
// The block B is in center. if Y_MINUS, block in B's bottom is occluding(i.e. blocking) .
82+
const val X_PLUS: Byte = 0b00_00001
83+
const val X_MINUS: Byte = 0b00_00010
84+
const val Z_PLUS: Byte = 0b00_00100
85+
const val Z_MINUS: Byte = 0b00_01000
86+
const val Y_MINUS: Byte = 0b00_10000
87+
const val INVISIBLE: Byte = 0b00_11111
88+
89+
const val X_LAVA: Byte = 0b01_00000
90+
const val Z_LAVA: Byte = 0b10_00000
8491

8592
const val SECTION_BLOCKS = 16 * 16 * 16
8693
const val BITS_ALL_TRUE = -1L
8794

88-
const val BV_VISIBLE: Byte = 0
89-
const val BV_OCCLUDING: Byte = 1
90-
const val BV_LAVA: Byte = 11
95+
const val BV_VISIBLE: Byte = 0b0
96+
const val BV_INVISIBLE: Byte = 0b1
97+
const val BV_LAVA_COVERED: Byte = 0b1011 // Why this value? flowing-lava id is 11, also the bit on BV_UPPER_OCCLUDING is 1
98+
99+
const val BV_UPPER_OCCLUDING: Byte = 0b10
91100

92101
private var LAVA_MIN = Int.MAX_VALUE
93102
private var LAVA_MAX = Int.MIN_VALUE
@@ -131,7 +140,7 @@ class ChunkDataThrottleHandlerImpl: ChunkDataThrottleHandler,
131140
}
132141
}.apply {
133142
for (i in LAVA_MIN..LAVA_MAX) {
134-
this[i] = BV_LAVA
143+
this[i] = BV_LAVA_COVERED
135144
}
136145
}
137146
}
@@ -264,6 +273,20 @@ class ChunkDataThrottleHandlerImpl: ChunkDataThrottleHandler,
264273
}
265274
}
266275

276+
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
277+
fun onMove(e: PlayerMoveEvent) {
278+
if (!config.chunkDataThrottle.detectLavaPool)
279+
return
280+
val player = e.player.nms
281+
val level = levelHandler.level(player)
282+
// Need to use chunk.getBlockState on Folia
283+
val chunk = level.getChunkIfLoaded(player.blockPosition()) ?: return
284+
val pos = listOf(player.blockPosition(), player.blockPosition().offset(0, 1, 0), player.blockPosition().offset(0, -1, 0))
285+
if (pos.find { chunk.getBlockState(it).bukkitMaterial == Material.LAVA } != null) {
286+
checkBlockUpdate(player.bukkitEntity, player.blockPosition())
287+
}
288+
}
289+
267290
override fun onPacketSend(event: PacketSendEvent) {
268291
if (event.isCancelled) return
269292
try {
@@ -279,7 +302,7 @@ class ChunkDataThrottleHandlerImpl: ChunkDataThrottleHandler,
279302
val world = player.world
280303
val minHeight = world.minHeight
281304
for (block in wrapper.blocks) {
282-
if (block.blockId.blocksView == BV_OCCLUDING) {
305+
if (block.blockId.blocksView == BV_INVISIBLE) {
283306
// Only check update if blocks get broken or transformed to non-blocksView
284307
continue
285308
}
@@ -289,7 +312,7 @@ class ChunkDataThrottleHandlerImpl: ChunkDataThrottleHandler,
289312

290313
PacketType.Play.Server.BLOCK_CHANGE -> {
291314
val wrapper = WrapperPlayServerBlockChange(event)
292-
if (wrapper.blockId.blocksView == BV_OCCLUDING) {
315+
if (wrapper.blockId.blocksView == BV_INVISIBLE) {
293316
// Only check update if blocks get broken or transformed to non-blocksView
294317
return
295318
}
@@ -326,10 +349,10 @@ class ChunkDataThrottleHandlerImpl: ChunkDataThrottleHandler,
326349
val invisible = ByteArray(height shl 8) // 0: visible; 1: invisible; 2: visible, upper block blocks view
327350
if (!minimalHeightInvisibleCheck) for (i in 0 until 16 * 16) bvArr[i] = Y_MINUS
328351
// Handle neighbour chunks starts
329-
handleNeighbourChunk(bvArr, level, column.x + 1, column.z, 0x00, 0x10, +0x0f, X_PLUS)
330-
handleNeighbourChunk(bvArr, level, column.x - 1, column.z, 0x0f, 0x10, -0x0f, X_MINUS)
331-
handleNeighbourChunk(bvArr, level, column.x, column.z + 1, 0x00, 0x01, +0xf0, Z_PLUS)
332-
handleNeighbourChunk(bvArr, level, column.x, column.z - 1, 0xf0, 0x01, -0xf0, Z_MINUS)
352+
handleNeighbourChunk(bvArr, level, column.x + 1, column.z, 0x00, 0x10, +0x0f, X_PLUS, X_LAVA)
353+
handleNeighbourChunk(bvArr, level, column.x - 1, column.z, 0x0f, 0x10, -0x0f, X_MINUS, X_LAVA)
354+
handleNeighbourChunk(bvArr, level, column.x, column.z + 1, 0x00, 0x01, +0xf0, Z_PLUS, Z_LAVA)
355+
handleNeighbourChunk(bvArr, level, column.x, column.z - 1, 0xf0, 0x01, -0xf0, Z_MINUS, Z_LAVA)
333356
// Handle neighbour chunks ends
334357

335358
class SectionData(
@@ -378,18 +401,18 @@ class ChunkDataThrottleHandlerImpl: ChunkDataThrottleHandler,
378401

379402
for (i in 0 until SECTION_BLOCKS) {
380403
when (blockingArr[data[i]]) {
381-
BV_OCCLUDING -> {
404+
BV_INVISIBLE -> {
382405
addNearby(bvArr, id)
383406
// Make sure it's not out of bounds, while we are processing bedrock layer
384407
if (id >= 0x100) {
385408
// Check if previous block is complete invisible
386409
val previous = id - 0x100
387-
invisible[previous] = if (bvArr[previous] == INVISIBLE) 1 else 2
410+
invisible[previous] = if (bvArr[previous] and INVISIBLE == INVISIBLE) BV_INVISIBLE else BV_UPPER_OCCLUDING
388411
}
389412
}
390-
BV_LAVA -> {
413+
BV_LAVA_COVERED -> {
391414
if (id >= 0x100) {
392-
invisible[id - 0x100] = BV_LAVA
415+
invisible[id - 0x100] = BV_LAVA_COVERED
393416
}
394417
}
395418
}
@@ -423,16 +446,47 @@ class ChunkDataThrottleHandlerImpl: ChunkDataThrottleHandler,
423446
if (z != 15) pending.add(id + 0x010)
424447
}
425448
for (i in pending.iterator()) {
426-
if (bvArr[i] == INVISIBLE) {
427-
invisible[i] = 1
449+
if (bvArr[i] and INVISIBLE == INVISIBLE) {
450+
invisible[i] = BV_INVISIBLE
451+
}
452+
}
453+
}
454+
if (config.detectLavaPool) {
455+
val pending = IntArrayList()
456+
id = -1
457+
while (++id < invisible.size) {
458+
if (invisible[id] != BV_LAVA_COVERED) continue
459+
460+
val x = id and 0xf
461+
val z = id shr 4 and 0xf
462+
fun checkBlock(id: Int, b: Byte) {
463+
if (invisible[id] and BV_UPPER_OCCLUDING == BV_UPPER_OCCLUDING) {
464+
bvArr[id] = bvArr[id] or b
465+
pending.add(id)
466+
}
467+
}
468+
fun checkEdge(id: Int, check: Byte, set: Byte) {
469+
if (bvArr[id] and check != 0.toByte()) {
470+
bvArr[id] = bvArr[id] and set
471+
}
472+
}
473+
if (x != 0 ) checkBlock(id - 0x001, X_PLUS ) else checkEdge(id - 0x001, X_LAVA, X_MINUS)
474+
if (x != 15) checkBlock(id + 0x001, X_MINUS) else checkEdge(id - 0x001, X_LAVA, X_PLUS )
475+
if (z != 0 ) checkBlock(id - 0x010, Z_PLUS ) else checkEdge(id - 0x010, Z_LAVA, Z_MINUS)
476+
if (z != 15) checkBlock(id + 0x010, Z_MINUS) else checkEdge(id - 0x010, Z_LAVA, Z_PLUS )
477+
checkBlock(id + 0x100, Y_MINUS)
478+
}
479+
for (i in pending.iterator()) {
480+
if (bvArr[i] and INVISIBLE == INVISIBLE) {
481+
invisible[i] = BV_INVISIBLE
428482
}
429483
}
430484
}
431485
if (!config.netherRoofInvisibleCheck && world.environment == Environment.NETHER) {
432486
// We could do the same thing to the top section,
433487
// but it never happens in vanilla generated chunks,
434488
// so, no.
435-
checkSurfaceInvisible(bvArr, invisible, 0x1000 * (8 - 1), BV_OCCLUDING)
489+
checkSurfaceInvisible(bvArr, invisible, 0x1000 * (8 - 1), BV_INVISIBLE)
436490
}
437491

438492
val array = invisible.toLongArray()
@@ -559,7 +613,7 @@ class ChunkDataThrottleHandlerImpl: ChunkDataThrottleHandler,
559613
// private val timesl = LongArrayList(10000000)
560614

561615
private fun handleNeighbourChunk(blocking: ByteArray, level: ServerLevel, chunkX: Int, chunkZ: Int,
562-
bid: Int, bidStep: Int, arrOffset: Int, arrValue: Byte) {
616+
bid: Int, bidStep: Int, arrOffset: Int, arrValue: Byte, lavaValue: Byte) {
563617
val chunk = level.getChunkIfLoaded(chunkX, chunkZ) ?: return
564618

565619
val indexLoop = 0x100 - bidStep * 16
@@ -568,10 +622,15 @@ class ChunkDataThrottleHandlerImpl: ChunkDataThrottleHandler,
568622
val states = section.container
569623
val palette = containerReader.getPalette(states)
570624
if (palette is SingleValuePalette<BlockState>) {
571-
if (palette.valueFor(0).blocksView == BV_OCCLUDING) {
625+
if (palette.valueFor(0).blocksView != BV_VISIBLE) {
626+
val or = when (palette.valueFor(0).blocksView) {
627+
BV_INVISIBLE -> arrValue
628+
BV_LAVA_COVERED -> lavaValue
629+
else -> throw AssertionError()
630+
}
572631
for (y in 0 until 16) {
573632
for (j in 0 until 16) {
574-
(blockId + arrOffset).let { blocking[it] = blocking[it] or arrValue }
633+
(blockId + arrOffset).let { blocking[it] = blocking[it] or or }
575634
blockId += bidStep
576635
}
577636
blockId += indexLoop
@@ -597,8 +656,10 @@ class ChunkDataThrottleHandlerImpl: ChunkDataThrottleHandler,
597656

598657
for (y in 0 until 16) {
599658
for (j in 0 until 16) {
600-
if (blockingArr[storage.get(blockId and 0xfff)] == BV_OCCLUDING)
601-
(blockId + arrOffset).let { blocking[it] = blocking[it] or arrValue }
659+
when (blockingArr[storage.get(blockId and 0xfff)]) {
660+
BV_INVISIBLE -> (blockId + arrOffset).let { blocking[it] = blocking[it] or arrValue }
661+
BV_LAVA_COVERED -> (blockId + arrOffset).let { blocking[it] = blocking[it] or lavaValue }
662+
}
602663
blockId += bidStep
603664
}
604665
blockId += indexLoop
@@ -609,7 +670,7 @@ class ChunkDataThrottleHandlerImpl: ChunkDataThrottleHandler,
609670

610671
private fun checkSurfaceInvisible(bvArr: ByteArray, invisible: ByteArray, id: Int, setTo: Byte) {
611672
for (i in id - 1 downTo id - 0x101) {
612-
if (bvArr[i] == INVISIBLE) {
673+
if (bvArr[i] and INVISIBLE == INVISIBLE) {
613674
invisible[i] = setTo
614675
return
615676
}
@@ -642,6 +703,10 @@ class ChunkDataThrottleHandlerImpl: ChunkDataThrottleHandler,
642703
return arr
643704
}
644705

706+
private fun checkBlockUpdate(player: Player, blockPos: BlockPos, minHeight: Int = player.world.minHeight) {
707+
return checkBlockUpdate(player, blockPos.x, blockPos.y, blockPos.z, minHeight)
708+
}
709+
645710
private fun checkBlockUpdate(player: Player, blockLocation: Vector3i, minHeight: Int = player.world.minHeight) {
646711
return checkBlockUpdate(player, blockLocation.x, blockLocation.y, blockLocation.z, minHeight)
647712
}

0 commit comments

Comments
 (0)