Add throttled player-position tracking, mod side (Phase 7b)

Adds PlayerPosition (common/protocol) and BackendConnection.sendPlayerPositions(),
wired into MCMapperMod.java: a new playerPositionIntervalTicks-throttled tick timer
(gated by playerTrackingEnabled) sends the full current overworld online-player
roster to the backend each interval, mirroring the "current state, not a diff"
philosophy of columns/sections — a player logging out just stops appearing next
send, no separate leave message needed.

Wire message: {"type":"player_positions","dimension":0,"players":[...]}.
Backend-side receive/relay + admin visibility toggle land in a follow-up commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015tKdPZt78zbPUZMXWzKEKt
This commit is contained in:
2026-08-09 19:38:16 +02:00
parent 52583f2056
commit ef47d4481a
5 changed files with 102 additions and 3 deletions
@@ -5,8 +5,11 @@ import com.octoturge.mcmapper.common.DefaultBackendConnection;
import com.octoturge.mcmapper.common.ReconciliationScheduler;
import com.octoturge.mcmapper.common.config.MapperConfig;
import com.octoturge.mcmapper.common.protocol.DeltaEvent;
import com.octoturge.mcmapper.common.protocol.PlayerPosition;
import com.octoturge.mcmapper.common.protocol.SectionData;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.server.MinecraftServer;
import net.minecraft.util.math.MathHelper;
import net.minecraft.world.WorldServer;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Configuration;
@@ -33,7 +36,9 @@ import java.util.Set;
* 3D mesh rendering. Phase 3 adds `/mcmapper link` and the two-way chat bridge (see LinkCommand,
* Forge1122ChatBridge). Phase 7 adds a periodic reconciliation sweep (see {@link
* ReconciliationScheduler}) that catches non-event mutations (world-gen, other mods, `/fill`)
* the event-driven hooks in {@link Forge1122ChunkAdapter} never see.
* the event-driven hooks in {@link Forge1122ChunkAdapter} never see. Phase 7b adds throttled
* online-player position tracking (config-gated via {@code playerTrackingEnabled}), overworld
* only — see {@link #sendPlayerPositions()}.
*/
@Mod(modid = MCMapperMod.MOD_ID, name = "MCMapper", version = MCMapperMod.VERSION)
public class MCMapperMod {
@@ -49,8 +54,10 @@ public class MCMapperMod {
private final List<DeltaEvent> pendingDeltas = Collections.synchronizedList(new ArrayList<>());
private final Set<Long> pendingSectionChunks = Collections.synchronizedSet(new LinkedHashSet<>());
private final ReconciliationScheduler reconciliationScheduler = new ReconciliationScheduler();
private MinecraftServer mcServer;
private int ticksSinceFlush = 0;
private int ticksSinceReconciliation = 0;
private int ticksSincePlayerPositions = 0;
@Mod.EventHandler
public void preInit(FMLPreInitializationEvent event) {
@@ -75,6 +82,14 @@ public class MCMapperMod {
config.reconciliationChunksPerSweep = forgeConfig.getInt("reconciliationChunksPerSweep", "network",
config.reconciliationChunksPerSweep, 1, 5000,
"Max chunks to re-read and resend per reconciliation sweep");
config.playerTrackingEnabled = forgeConfig.getBoolean("playerTrackingEnabled", "network",
config.playerTrackingEnabled,
"Whether to send throttled online-player positions to the backend at all. The " +
"backend also has its own per-server admin toggle deciding whether it " +
"relays this on to web viewers — this setting only controls the mod side.");
config.playerPositionIntervalTicks = forgeConfig.getInt("playerPositionIntervalTicks", "network",
config.playerPositionIntervalTicks, 5, 20 * 60,
"How often (in ticks) to send the online-player roster to the backend, when playerTrackingEnabled");
if (forgeConfig.hasChanged()) forgeConfig.save();
}
@@ -85,6 +100,7 @@ public class MCMapperMod {
return;
}
mcServer = event.getServer();
connection = new DefaultBackendConnection(LOGGER::info, LOGGER::warn);
connection.connect(config.backendUrl, config.serverToken);
LOGGER.info("MCMapper (1.12.2 leaf) connecting to " + config.backendUrl);
@@ -130,6 +146,21 @@ public class MCMapperMod {
ticksSinceReconciliation = 0;
reconcile();
}
if (config.playerTrackingEnabled && ++ticksSincePlayerPositions >= config.playerPositionIntervalTicks) {
ticksSincePlayerPositions = 0;
sendPlayerPositions();
}
}
private void sendPlayerPositions() {
List<PlayerPosition> players = new ArrayList<>();
for (EntityPlayerMP player : mcServer.getPlayerList().getPlayers()) {
if (player.dimension != 0) continue;
players.add(new PlayerPosition(player.getUniqueID().toString(), player.getName(),
MathHelper.floor(player.posX), MathHelper.floor(player.posY), MathHelper.floor(player.posZ)));
}
connection.sendPlayerPositions(0, players);
}
private void reconcile() {