From a22fd8185f4c8b083ddefca92ad16eaf5a2deab1 Mon Sep 17 00:00:00 2001 From: Player Name Date: Sun, 28 Jun 2026 01:55:33 +0200 Subject: [PATCH] Critical fixes that fix all the current problems: no flashing POHs, no duplicate items, no missing items when transitioning >3 chunks, no accidental goddamn game protocol changes --- .../core/game/node/entity/player/Player.java | 5 ---- .../main/core/game/world/map/RegionChunk.java | 17 +++++++------ .../game/world/update/MapChunkRenderer.kt | 25 ++++++++++++------- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/Server/src/main/core/game/node/entity/player/Player.java b/Server/src/main/core/game/node/entity/player/Player.java index ea08ea6bf..cb94a83bd 100644 --- a/Server/src/main/core/game/node/entity/player/Player.java +++ b/Server/src/main/core/game/node/entity/player/Player.java @@ -545,11 +545,6 @@ public class Player extends Entity { super.update(); if (playerFlags.isUpdateSceneGraph()) { updateSceneGraph(false); - // The scene graph rebuild wipes the client's build area. Reset lastViewport so MapChunkRenderer - // re-synchronizes every visible chunk this tick (restoring scenery immediately and re-sending - // each ground item exactly once), rather than only sending incremental updates for overlap. - // This fixes POHs flashing their cached states (hotspots) on z transition. - playerFlags.setLastViewport(new RegionChunk[13][13]); } PlayerRenderer.render(this); NPCRenderer.render(this); diff --git a/Server/src/main/core/game/world/map/RegionChunk.java b/Server/src/main/core/game/world/map/RegionChunk.java index 89813e385..f5d57337c 100644 --- a/Server/src/main/core/game/world/map/RegionChunk.java +++ b/Server/src/main/core/game/world/map/RegionChunk.java @@ -252,7 +252,7 @@ public class RegionChunk { * @param item The item. */ public void add(GroundItem item) { - items.add(item); + getItems().add(item); if (item.isPrivate()) { if (item.getDropper() != null) { PacketRepository.send(ConstructGroundItem.class, new BuildItemContext(item.getDropper(), item)); @@ -404,12 +404,15 @@ public class RegionChunk { } } } - ArrayList totalItems = drawItems(items, player); - for (GroundItem item : totalItems) { - if (item != null && item.isActive() && item.getLocation() != null) { - if (!item.isPrivate() || item.droppedBy(player)) { - ConstructGroundItem.write(buffer, item); - updated = true; + // Dynamic regions (POHs etc.) deliver items once via add(GroundItem) and the client retains them across scene rebuilds; + // re-sending here would duplicate them (the client doesn't de-dup). Static regions still need it for chunks entering view. + if (!(getRegion() instanceof DynamicRegion)) { + ArrayList totalItems = drawItems(items, player); + for (GroundItem item : totalItems) { + if (item != null && item.isActive() && item.getLocation() != null) { + if (!item.isPrivate() || item.droppedBy(player)) { + ConstructGroundItem.write(buffer, item); + } } } } diff --git a/Server/src/main/core/game/world/update/MapChunkRenderer.kt b/Server/src/main/core/game/world/update/MapChunkRenderer.kt index 8f1b84171..7d8976905 100644 --- a/Server/src/main/core/game/world/update/MapChunkRenderer.kt +++ b/Server/src/main/core/game/world/update/MapChunkRenderer.kt @@ -1,7 +1,8 @@ package core.game.world.update - import core.game.node.entity.player.Player +import core.game.world.map.Location import core.game.world.map.RegionChunk +import core.game.world.map.build.DynamicRegion import core.net.packet.PacketRepository import core.net.packet.context.ClearChunkContext import core.net.packet.out.ClearRegionChunk @@ -13,21 +14,23 @@ import java.util.* */ object MapChunkRenderer { const val buildAreaDepth = 6 - - /** - * Sends the map chunk rendering packet. - * @param player The player. - */ @JvmStatic fun render(player: Player) { val last = player.playerFlags.lastViewport val updated: MutableList = ArrayList() val current: MutableList> = ArrayList() + // Center the build area on the loaded scene anchor (lastSceneGraph), NOT the live player position. + // The client's 13x13 build area is anchored at lastSceneGraph; addressing chunks relative to it keeps + // every chunk packet inside the client's scene window even when the player has drifted up to 3 chunks + // from the anchor (the scene graph only rebuilds at a drift of 4). Using player.location here instead + // pushes the viewport edge to drift+6 chunks from the anchor, off-window, so edge items never render. + val anchor = player.playerFlags.lastSceneGraph ?: player.location + val center = Location.create(anchor.x, anchor.y, player.location.z) for (dcx in -buildAreaDepth..+buildAreaDepth) { val addX = ArrayList() for (dcy in -buildAreaDepth..+buildAreaDepth) { - val newloc = player.location.transform(dcx*8, dcy*8, 0) + val newloc = center.transform(dcx * 8, dcy * 8, 0) addX.add(newloc.chunk) } current.add(addX) @@ -40,11 +43,15 @@ object MapChunkRenderer { val previous = last[x][y] ?: continue if (containsChunk(current, previous)) { updated.add(previous) - } else { - PacketRepository.send(ClearRegionChunk::class.java, ClearChunkContext(player, previous)) + continue } + if (previous.region is DynamicRegion) { + continue //dynamic regions do not need a clear packet and briefly flash their cached states (e.g. POH with only hotspots) if you do send one + } + PacketRepository.send(ClearRegionChunk::class.java, ClearChunkContext(player, previous)) } } + sizeX = current.size for (x in 0 until sizeX) { val sizeY: Int = current[x].size