Skip to content

fortnite - #248

Open
Deftu wants to merge 144 commits into
mainfrom
feat/fortnite
Open

Deftu wants to merge 144 commits into
mainfrom
feat/fortnite

Conversation

@Deftu

@Deftu Deftu commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

Closes #261, closes #12, closes #14, closes #63, closes #236, closes #251

@Wyvest Wyvest added this to Mods Nov 6, 2026
@Deftu
Deftu requested a review from lunaynx November 6, 2026 19:20
@Wyvest
Wyvest marked this pull request as ready for review November 10, 2026 07:46
@Deftu Deftu self-assigned this Nov 10, 2026
}
}
val data = meta
val serverName = data?.string("serverName")?.takeIf { it.isNotBlank() }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [P1] Respect hidden server addresses in replay entries

Multiplayer serverName contains the actual address, which the new replay cards display even when the user has hidden server IPs. This can expose a private address while streaming. [Replay Mod’s viewer](https://github.com/ReplayMod/ReplayMod/blob/stable/src/main/java/com/replaymod/replay/gui/screen/GuiReplayViewer.java) uses the custom name when available and honors its IP-hiding setting.

runCatching {
Files.createDirectories(folder)
atomicWrite(folder.resolve(name), data)
stale.forEach(Path::deleteIfExists)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [P1] Persist the replacement index before deleting captures

Evicted capture files are deleted before the new index is written. If that write fails or the process exits in between, the old index points to deleted captures and the new capture is unindexed.

var kept = state.scenes - replaced.toSet()
var total = kept.sumOf { it.bytes } + data.size
val spare = kept.sortedWith(compareBy({ it.dataVersion == LobbySnapshot.dataVersion }, ::rank)).toMutableList()
while (total > MAX_TOTAL_BYTES && spare.isNotEmpty()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 [P1] Reject an oversized capture before evicting the library

A snapshot larger than the 16 MiB budget causes the loop to evict every existing capture, then is admitted anyway. A sufficiently dense scene can therefore erase the capture library while still exceeding the limit.

atomicWrite(indexFile, index.toByteArray(Charsets.UTF_8))
}.onFailure { LOBBY_LOGGER.warn("Could not save a scene", it) }
}
return true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 [P2] Report capture success only after the write succeeds

admit returns success immediately after queueing a daemon-thread write. The UI consequently says the scene was saved even if the write fails or the game exits before it runs; the capture disappears on the next launch.

val custom = sameSpot.filter { it.renamed }.maxByOrNull { it.capturedAt }
replaced += sameFile
if (!candidate.manual) {
replaced += rivals.filter { near(it, candidate) }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 [P2] Protect selected captures from automatic replacement

A higher-scoring automatic capture within 48 blocks can delete the backdrop the user selected and reset the selection to Automatic. The size-limit eviction also includes selected and manually saved captures.

val to = target.coerceIn(0, list.size() - 1)
if (from == to) return
val step = if (to > from) 1 else -1
while (from != to) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 [P2] Save server order once after dragging

On Minecraft 1.21.1, ServerList.swap() synchronously saves servers.dat. Dragging across a long list rewrites and backs up the file once per crossed entry on the render thread, then saves it again.

}

val stale = subscribedPlayers.filter { it !in visible }
val stale = subscribedPlayers.filter { it !in visible && it !in watchedFriends }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 [P2] Reserve cosmetic subscriptions for visible players

Watched friends remain subscribed while playing, including offline friends, and share the 512-player limit with visible players. On a large server or with a large friends list, those subscriptions can exhaust the limit and prevent nearby players from receiving cosmetic updates.

val written = hooks.writtenTicks
val chunk = hooks.chunkTicks
if (written != null && chunk != null) {
runCatching { (written.getInt(recorder).toLong() + chunk.getInt(recorder).toLong()) * MILLIS_PER_TICK }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 [P3] Use Flashback’s total tick count for recording duration

Flashback increments both counters for every recorded tick. Adding them makes the displayed duration too long and causes it to jump backwards when a chunk is saved.


private fun hostedPlace(): Place {
val joined = P2PSessionManager.joinedSessionId
hosted?.let { (id, place) -> if (id == joined) return place }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 [P3] Refresh hosted-world names after resolution

If the host’s name has not resolved when a hosted world is entered, the generic “Friend’s world” label is cached for that session ID. It remains in stats even after the name lookup completes.

private fun CompoundTag.putLight(name: String, layer: DataLayer?) {
if (layer == null) return
val bytes = layer.data
if (bytes.all { it == bytes[0] }) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 [P3] Check both light nibbles before using fill encoding

Equal packed bytes do not necessarily represent equal light values: each byte contains two nibbles. A layer of repeated 0xf0 bytes is restored as uniform zero light, changing half its values.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status
Status: No status

2 participants