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

This commit is contained in:
Player Name 2026-06-28 01:55:33 +02:00
parent 4eeda96ff2
commit a22fd8185f
3 changed files with 26 additions and 21 deletions

View file

@ -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);

View file

@ -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<GroundItem> 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<GroundItem> 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);
}
}
}
}

View file

@ -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<RegionChunk> = ArrayList()
val current: MutableList<MutableList<RegionChunk>> = 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<RegionChunk>()
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