Playtime

by thuejk

Adds a clock to the top left corner, saying how long the current game has been running.

Utilities
4 days ago
0.15 - 2.1
27.7K

b [Fixed in 1.1.4] Desync on 2nd connect

4 months ago

When hosting a dedicated multiplayer server, the 2nd time a client attempts to connect results in a desync. Tracked down to this mod causing it. Desync tagged tick heuristics shows the first byte in the <custom-gui> tag differs between server and client (FE on server, FF on client).

4 months ago

Hmm, I can't reproduce. But perhaps that is just normal in the wonderful world of desyncs.

I am not sure what the problem is, but I am no expert (it is a very simple mod). But ChatGPT suggested the code below, that the problem was iterating over connecting players - if you feel like making the effort, could you replace the contents of control.lua, and see if it helps?


local mod_gui = require("mod-gui")

local playtime = {
play_time_seconds = -1
}

local function ensure_gui(player)
local flow = mod_gui.get_button_flow(player)

local button = flow.clockGUI
if button and button.valid then
    return button
end

return flow.add{
    type = "button",
    name = "clockGUI",
    style = mod_gui.button_style,
    caption = ""
}

end

local function update_button(player)
local button = ensure_gui(player)

local seconds = playtime.play_time_seconds % 60
local minutes = math.floor(playtime.play_time_seconds / 60) % 60
local hours   = math.floor(playtime.play_time_seconds / 3600)

if hours > 0 then
    button.caption = string.format(
        "Total time: %d:%02d:%02d",
        hours,
        minutes,
        seconds
    )
else
    button.caption = string.format(
        "Total time: %02d:%02d",
        minutes,
        seconds
    )
end

end

script.on_init(function()
for _, player in pairs(game.players) do
ensure_gui(player)
end
end)

script.on_configuration_changed(function()
for _, player in pairs(game.players) do
ensure_gui(player)
end
end)

script.on_event(defines.events.on_player_created, function(event)
local player = game.get_player(event.player_index)
if player then
ensure_gui(player)
end
end)

script.on_event(defines.events.on_player_joined_game, function(event)
local player = game.get_player(event.player_index)
if player then
ensure_gui(player)
update_button(player)
end
end)

script.on_event(defines.events.on_tick, function(event)
local previous = playtime.play_time_seconds
playtime.play_time_seconds = math.floor(event.tick / 60)

if previous == playtime.play_time_seconds then
    return
end

for _, player in pairs(game.connected_players) do
    update_button(player)
end

end)

5 days ago

I managed to confirm that this bug is still present.

When hosting a host&play server, I have a friend join, and then the exact same situation shows up as the OP reports.

I dived into the source code a bit, and found a very likely candidate: The playtime.play_time_seconds is a plain Lua global, not part of storage, so it isn't saved with the map; reset to -1 every time control.lua loads, which is a problem for a newly joined client. When they join, the first on_tick sees previous ~= current and adds, or updates, the clockGUI button for every connected player. On the server, previous already equals the current second, so it does nothing until the next second boundary; meaning the client ends up with one more GUI element, then the CRC check fails.

The game DOES NOT DESYNC until the server resumes if it was paused when the player(s) connect. That's why this bug is a bit tricky to find; the server creates the same button, then the two states match again; resulting in a desync report saved a few ticks later that shows identical client/server levels.

Your replacement code doesn't fix the issue because it only skips the update based on a remembered value which isn't saved.

The fix is to make sure the UI only updates at one tick interval; for example, tick % 60 == 0.

4 days ago

Ah, excellent debugging. Obvious in hindsight, after you explain it so well. I have uploaded a new version.

New response