Files
factorio-expcluster/exp_player_statistics/module/control.lua
T
bbassieandClaude Opus 5.5 ad1c83def8 Address review on the statistics plugin
The plugin is now exp_player_statistics (@expcluster/player-statistics),
with the module, global, permission, route and database directory
renamed to match. The exp-statistics locale section keeps its name.

Every statistic is tracked unconditionally, so the enable flags are gone
from config.lua. Playtime and JoinCount are no longer counted in game:
clusterio already keeps online time and joins for each user, so the
controller fills them in from the user when a player's totals are
requested, and the web page does the same from the user list. They are
not stored or adjusted by the plugin.

The web table has a column picker, a multiple select with check boxes,
Select all and Reset buttons, showing "N statistics selected". The
choice is kept in local storage. The user page shows the statistics of
the player in a grid like the core player stats.

The readme statistics table drops its second title and uses the short
time format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 23:36:00 +00:00

156 lines
5.1 KiB
Lua

--[[-- ExpPlayerStatistics
Player statistics kept on the controller.
What each player gains is kept here as unsent until it is sent to the
controller, every 5 minutes and when the player leaves. Once sent it is added
to the totals the controller gave when the player joined, so get() is the
total across every server either way.
Updates which are lost are not sent again. Anything unsent when the server
starts is from a save made before it was sent, so it is dropped rather than
risk counting it twice.
local ExpPlayerStatistics = require("modules/exp_player_statistics")
ExpPlayerStatistics.increment(player, "Kills")
ExpPlayerStatistics.get(player, "Kills")
for _, name in ipairs(ExpPlayerStatistics.display_order) do end
]]
local clusterio_api = require("modules/clusterio/api")
local ExpUtil = require("modules/exp_util")
local Storage = require("modules/exp_util/storage")
local config = require("modules/exp_player_statistics/config")
local format_number = require("util").format_number
--- @alias ExpPlayerStatistics.Stats table<string, number>
--- @class ExpPlayerStatistics.Totals
--- @field name string
--- @field stats ExpPlayerStatistics.Stats
--- @class ExpPlayerStatistics
local ExpPlayerStatistics = {
config = config,
--- Statistics in the order they are shown
--- @type string[]
display_order = config.display_order,
--- Shown after the value
--- @type table<string, string>
units = { DistanceTravelled = " tiles" },
}
--- Measured in minutes and shown as a time
local time_statistics = { Playtime = true, AfkTime = true }
local long_time_format = ExpUtil.format_time_factory_locale{ format = "long", coefficient = 3600, hours = true, minutes = true }
local short_time_format = ExpUtil.format_time_factory_locale{ format = "short", coefficient = 3600, hours = true, minutes = true }
local unsent = {} --- @type table<string, ExpPlayerStatistics.Stats>
local totals = {} --- @type table<string, ExpPlayerStatistics.Stats>
Storage.register({ unsent, totals }, function(tbl)
unsent = tbl[1]
totals = tbl[2]
end)
--- Add to a statistic of a player
--- @param player LuaPlayer
--- @param statistic string
--- @param amount number? Defaults to 1
function ExpPlayerStatistics.increment(player, statistic, amount)
local name = player.name
local stats = unsent[name]
if not stats then
stats = {}
unsent[name] = stats
end
stats[statistic] = (stats[statistic] or 0) + (amount or 1)
end
--- Get the total of a statistic for a player, on every server
--- @param player LuaPlayer | string
--- @param statistic string
--- @return number
function ExpPlayerStatistics.get(player, statistic)
local name = type(player) == "string" and player or player.name
local received = totals[name]
local pending = unsent[name]
return (received and received[statistic] or 0) + (pending and pending[statistic] or 0)
end
--- Get the value of a statistic as it is shown to players
--- @param statistic string
--- @param value number
--- @param short boolean? Use the short time format
--- @return LocalisedString
function ExpPlayerStatistics.format(statistic, value, short)
if time_statistics[statistic] then
return short and short_time_format(value) or long_time_format(value)
end
return format_number(value, false)
end
--- Move the unsent statistics of a player into their totals
--- @param name string
--- @return ExpPlayerStatistics.Stats?
local function take_unsent(name)
local stats = unsent[name]
if not stats then return nil end
unsent[name] = nil
local received = totals[name]
if not received then
received = {}
totals[name] = received
end
for statistic, value in pairs(stats) do
received[statistic] = (received[statistic] or 0) + value
end
return stats
end
--- Send what a player gained to the controller
--- @param name string
function ExpPlayerStatistics.send(name)
local stats = take_unsent(name)
if stats then
clusterio_api.send_json("exp_player_statistics:update", { players = { { name = name, stats = stats } } })
end
end
--- Send what every player gained to the controller, in one update
function ExpPlayerStatistics.send_all()
local players, count = {}, 0
for name in pairs(unsent) do
count = count + 1
players[count] = { name = name, stats = take_unsent(name) }
end
if count > 0 then
clusterio_api.send_json("exp_player_statistics:update", { players = players })
end
end
--- Send what a player gained and forget them, called when they leave
--- @param name string
function ExpPlayerStatistics.send_and_forget(name)
ExpPlayerStatistics.send(name)
totals[name] = nil
end
--- Called by the instance plugin over rcon when the server starts
function ExpPlayerStatistics.discard_unsent()
for name in pairs(unsent) do
unsent[name] = nil
end
end
--- Called by the instance plugin over rcon when a player joins
--- @param payload ExpPlayerStatistics.Totals
function ExpPlayerStatistics.receive_totals(payload)
local player = game.get_player(payload.name)
if not player or not player.connected then return end
totals[payload.name] = payload.stats
end
return ExpPlayerStatistics