mirror of
https://github.com/PHIDIAS0303/ExpCluster.git
synced 2025-12-27 11:35:22 +09:00
Added Statistics
This commit is contained in:
@@ -43,6 +43,7 @@ return {
|
|||||||
'modules.addons.tree-decon',
|
'modules.addons.tree-decon',
|
||||||
|
|
||||||
--- Data
|
--- Data
|
||||||
|
'modules.data.statistics',
|
||||||
'modules.data.player-colours',
|
'modules.data.player-colours',
|
||||||
'modules.data.greetings',
|
'modules.data.greetings',
|
||||||
'modules.data.quickbar',
|
'modules.data.quickbar',
|
||||||
|
|||||||
29
config/statistics.lua
Normal file
29
config/statistics.lua
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
--- A list of all tracked statistics and the events which trigger them
|
||||||
|
-- @config Statistics
|
||||||
|
|
||||||
|
local e = defines.events -- order as per lua api as it was easier just to go down the list
|
||||||
|
return {
|
||||||
|
Playtime = true, --- @setting Playtime If playtime is tracked for a player, play time measured in minutes
|
||||||
|
AfkTime = true, --- @setting AfkTime If afk time is tracked for a player, play time measured in minutes, afk is once a player does nothing for 5 minutes
|
||||||
|
DistanceTraveled = true, --- @settings DistanceTraveled If distance traveled is checked, only counts if not afk
|
||||||
|
MachinesRemoved = true, --- @setting MachinesRemoved If removed machines are tracked, includes marked for decon and player mined entity
|
||||||
|
OreMined = true, --- @settings OreMined If ore mined is tracked for a player, includes player mined entity but only ore,
|
||||||
|
DamageDealt = true, --- @settings DamageDealt If damage dealt is tracked for a player, includes any damage to entities not on the same force or neutral
|
||||||
|
Kills = true, --- @settings Kills If kills are tracked for a player, includes all kills not on same force or neutral
|
||||||
|
counters = { --- @setting counters Simple statistics that just go up by one each time an event happens
|
||||||
|
MachinesBuilt = e.on_built_entity,
|
||||||
|
MapTagsMade = e.on_chart_tag_added,
|
||||||
|
ChatMessages = e.on_console_chat,
|
||||||
|
CommandsUsed = e.on_console_command,
|
||||||
|
ItemsPickedUp = e.on_picked_up_item,
|
||||||
|
TilesBuilt = e.on_player_built_tile,
|
||||||
|
ItemsCrafted = e.on_player_crafted_item,
|
||||||
|
MapsPlayed = e.on_player_created,
|
||||||
|
DeconstructionPlanerUsed = e.on_player_deconstructed_area,
|
||||||
|
Deaths = e.on_player_died,
|
||||||
|
JoinCount = e.on_player_joined_game,
|
||||||
|
TilesRemoved = e.on_player_mined_tile,
|
||||||
|
CapsulesUsed = e.on_player_used_capsule,
|
||||||
|
RocketsLaunched = e.on_rocket_launched
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -391,7 +391,7 @@ self:write_action('save', 'TestKey', 'Foo')
|
|||||||
function Datastore:write_action(action, key, value)
|
function Datastore:write_action(action, key, value)
|
||||||
local data = {action, self.name, key}
|
local data = {action, self.name, key}
|
||||||
if value ~= nil then
|
if value ~= nil then
|
||||||
data[4] = type(value) == 'table' and game.table_to_json(value) or tostring(value)
|
data[4] = type(value) == 'table' and game.table_to_json(value) or value
|
||||||
end
|
end
|
||||||
game.write_file('ext/datastore.out', table.concat(data, ' ')..'\n', true, 0)
|
game.write_file('ext/datastore.out', table.concat(data, ' ')..'\n', true, 0)
|
||||||
end
|
end
|
||||||
@@ -517,7 +517,7 @@ ExampleData:increment('TestNumber')
|
|||||||
function Datastore:increment(key, delta)
|
function Datastore:increment(key, delta)
|
||||||
key = self:serialize(key)
|
key = self:serialize(key)
|
||||||
local value = self:raw_get(key) or 0
|
local value = self:raw_get(key) or 0
|
||||||
return Datastore:set(key, value + (delta or 1))
|
return self:set(key, value + (delta or 1))
|
||||||
end
|
end
|
||||||
|
|
||||||
local function update_error(err) error('An error ocurred in datastore update: '..trace(err), 2) end
|
local function update_error(err) error('An error ocurred in datastore update: '..trace(err), 2) end
|
||||||
@@ -560,7 +560,7 @@ function Datastore:remove(key)
|
|||||||
if self.parent and self.parent.auto_save then return self.parent:save(key) end
|
if self.parent and self.parent.auto_save then return self.parent:save(key) end
|
||||||
end
|
end
|
||||||
|
|
||||||
local function filter_error(err) print('An error ocurred in a datastore filter:'..trace(err)) end
|
local function filter_error(err) log('An error ocurred in a datastore filter:'..trace(err)) end
|
||||||
--[[-- Internal, Used to filter elements from a table
|
--[[-- Internal, Used to filter elements from a table
|
||||||
@tparam table tbl The table that will have the filter applied to it
|
@tparam table tbl The table that will have the filter applied to it
|
||||||
@tparam[opt] function callback The function that will be used as a filter, if none giving then the provided table is returned
|
@tparam[opt] function callback The function that will be used as a filter, if none giving then the provided table is returned
|
||||||
@@ -737,7 +737,7 @@ end
|
|||||||
----- Events
|
----- Events
|
||||||
-- @section events
|
-- @section events
|
||||||
|
|
||||||
local function event_error(err) print('An error ocurred in a datastore event handler: '..trace(err)) end
|
local function event_error(err) log('An error ocurred in a datastore event handler: '..trace(err)) end
|
||||||
--[[-- Internal, Raise an event on this datastore
|
--[[-- Internal, Raise an event on this datastore
|
||||||
@tparam string event_name The name of the event to raise for this datastore
|
@tparam string event_name The name of the event to raise for this datastore
|
||||||
@tparam string key The key that this event is being raised for
|
@tparam string key The key that this event is being raised for
|
||||||
|
|||||||
@@ -83,26 +83,30 @@ end)
|
|||||||
--- Async function called after 5 seconds with no player data loaded
|
--- Async function called after 5 seconds with no player data loaded
|
||||||
local check_data_loaded = Async.register(function(player)
|
local check_data_loaded = Async.register(function(player)
|
||||||
local player_data = PlayerData:get(player)
|
local player_data = PlayerData:get(player)
|
||||||
if not player_data then
|
if not player_data or not player_data.valid then
|
||||||
player.print{'expcore-data.data-failed'}
|
player.print{'expcore-data.data-failed'}
|
||||||
Datastore.ingest('request', 'PlayerData', player.name, '{"failed_load":true}')
|
Datastore.ingest('request', 'PlayerData', player.name, '{"valid":false}')
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
--- When player data loads tell the player if the load had failed previously
|
--- When player data loads tell the player if the load had failed previously
|
||||||
PlayerData:on_load(function(player_name, player_data)
|
PlayerData:on_load(function(player_name, player_data)
|
||||||
if not player_data or player_data.failed_load then return end
|
if not player_data or not player_data.valid then return end
|
||||||
local existing_data = PlayerData:get(player_name)
|
local existing_data = PlayerData:get(player_name)
|
||||||
if existing_data and existing_data.failed_load then
|
if existing_data and existing_data.valid == false then
|
||||||
game.players[player_name].print{'expcore-data.data-restore'}
|
game.players[player_name].print{'expcore-data.data-restore'}
|
||||||
end
|
end
|
||||||
|
player_data.valid = true
|
||||||
end)
|
end)
|
||||||
|
|
||||||
--- Remove data that the player doesnt want to have stored
|
--- Remove data that the player doesnt want to have stored
|
||||||
PlayerData:on_save(function(player_name, player_data)
|
PlayerData:on_save(function(player_name, player_data)
|
||||||
local dataPreference = DataSavingPreference:get(player_name)
|
local dataPreference = DataSavingPreference:get(player_name)
|
||||||
dataPreference = PreferenceEnum[dataPreference]
|
dataPreference = PreferenceEnum[dataPreference]
|
||||||
if dataPreference == PreferenceEnum.All then return player_data end
|
if dataPreference == PreferenceEnum.All then
|
||||||
|
player_data.valid = nil
|
||||||
|
return player_data
|
||||||
|
end
|
||||||
|
|
||||||
local saved_player_data = { PlayerRequired = player_data.PlayerRequired, DataSavingPreference = PreferenceEnum[dataPreference] }
|
local saved_player_data = { PlayerRequired = player_data.PlayerRequired, DataSavingPreference = PreferenceEnum[dataPreference] }
|
||||||
if dataPreference <= PreferenceEnum.Settings then saved_player_data.PlayerSettings = player_data.PlayerSettings end
|
if dataPreference <= PreferenceEnum.Settings then saved_player_data.PlayerSettings = player_data.PlayerSettings end
|
||||||
@@ -119,17 +123,17 @@ end)
|
|||||||
--- Load player data when they join
|
--- Load player data when they join
|
||||||
Event.add(defines.events.on_player_joined_game, function(event)
|
Event.add(defines.events.on_player_joined_game, function(event)
|
||||||
local player = game.players[event.player_index]
|
local player = game.players[event.player_index]
|
||||||
PlayerData:request(player)
|
|
||||||
Async.wait(300, check_data_loaded, player)
|
Async.wait(300, check_data_loaded, player)
|
||||||
|
PlayerData:request(player)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
--- Unload player data when they leave
|
--- Unload player data when they leave
|
||||||
Event.add(defines.events.on_player_left_game, function(event)
|
Event.add(defines.events.on_player_left_game, function(event)
|
||||||
local player = game.players[event.player_index]
|
local player = game.players[event.player_index]
|
||||||
local player_data = PlayerData:get(player)
|
local player_data = PlayerData:get(player)
|
||||||
if player_data.failed_load then
|
if player_data.valid == true then
|
||||||
PlayerData:raw_set(player)
|
PlayerData:unload(player)
|
||||||
else PlayerData:unload(player) end
|
else PlayerData:raw_set(player) end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
----- Module Return -----
|
----- Module Return -----
|
||||||
|
|||||||
123
modules/data/statistics.lua
Normal file
123
modules/data/statistics.lua
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
|
||||||
|
local Event = require 'utils.event' ---@dep utils.event
|
||||||
|
local config = require 'config.statistics' ---@dep config.statistics
|
||||||
|
local floor = math.floor
|
||||||
|
local afk_required = 5*3600 -- 5 minutes
|
||||||
|
|
||||||
|
--- Stores the statistics on a player
|
||||||
|
local PlayerData = require 'expcore.player_data' --- @dep expcore.player_data
|
||||||
|
local AllPlayerData = PlayerData.All
|
||||||
|
local Statistics = PlayerData.Statistics
|
||||||
|
|
||||||
|
--- Update your statistics with any which happened before the data was valid
|
||||||
|
Statistics:on_load(function(player_name, player_statistics)
|
||||||
|
local existing_data = AllPlayerData:get(player_name)
|
||||||
|
if existing_data and existing_data.valid then return end
|
||||||
|
local counters = config.counters
|
||||||
|
for key, value in pairs(Statistics:get(player_name, {})) do
|
||||||
|
if config[key] or counters[key] then
|
||||||
|
if not player_statistics[key] then
|
||||||
|
player_statistics[key] = value
|
||||||
|
else
|
||||||
|
player_statistics[key] = player_statistics[key] + value
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return player_statistics
|
||||||
|
end)
|
||||||
|
|
||||||
|
--- Add Playtime and AfkTime if it is enabled
|
||||||
|
if config.Playtime or config.AfkTime then
|
||||||
|
local playtime, afk_time
|
||||||
|
if config.Playtime then playtime = Statistics:combine('Playtime') end
|
||||||
|
if config.AfkTime then afk_time = Statistics:combine('AfkTime') end
|
||||||
|
Event.on_nth_tick(3600, function()
|
||||||
|
if game.tick == 0 then return end
|
||||||
|
for _, player in pairs(game.connected_players) do
|
||||||
|
if playtime then playtime:increment(player) end
|
||||||
|
if afk_time and player.afk_time > afk_required then afk_time:increment(player) end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Add DistanceTraveled if it is enabled
|
||||||
|
if config.DistanceTraveled then
|
||||||
|
local stat = Statistics:combine('DistanceTraveled')
|
||||||
|
Event.add(defines.events.on_player_changed_position, function(event)
|
||||||
|
local player = game.players[event.player_index]
|
||||||
|
if not player.valid or not player.connected or player.afk_time > afk_required then return end
|
||||||
|
stat:increment(player)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Add MachinesRemoved if it is enabled
|
||||||
|
if config.MachinesRemoved then
|
||||||
|
local stat = Statistics:combine('MachinesRemoved')
|
||||||
|
local function on_event(event)
|
||||||
|
if not event.player_index then return end -- Check player is valid
|
||||||
|
local player = game.players[event.player_index]
|
||||||
|
if not player.valid or not player.connected then return end
|
||||||
|
local entity = event.entity -- Check entity is valid
|
||||||
|
if not entity.valid or entity.force ~= player.force then return end
|
||||||
|
stat:increment(player)
|
||||||
|
end
|
||||||
|
Event.add(defines.events.on_marked_for_deconstruction, on_event)
|
||||||
|
Event.add(defines.events.on_player_mined_entity, on_event)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Add OreMined if it is enabled
|
||||||
|
if config.OreMined then
|
||||||
|
local stat = Statistics:combine('OreMined')
|
||||||
|
Event.add(defines.events.on_player_mined_entity, function(event)
|
||||||
|
if not event.player_index then return end -- Check player is valid
|
||||||
|
local player = game.players[event.player_index]
|
||||||
|
if not player.valid or not player.connected then return end
|
||||||
|
local entity = event.entity -- Check entity is valid
|
||||||
|
if not entity.valid or entity.type ~= 'resource' then return end
|
||||||
|
stat:increment(player)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Add DamageDealt if it is enabled
|
||||||
|
if config.DamageDealt then
|
||||||
|
local stat = Statistics:combine('DamageDealt')
|
||||||
|
Event.add(defines.events.on_entity_damaged, function(event)
|
||||||
|
local character = event.cause -- Check character is valid
|
||||||
|
if not character.valid or character.type ~= 'character' then return end
|
||||||
|
local player = character.player -- Check player is valid
|
||||||
|
if not player.valid or not player.connected then return end
|
||||||
|
local entity = event.entity -- Check entity is valid
|
||||||
|
if not entity.valid or entity.force == player.force or entity.force.name == 'neutral' then return end
|
||||||
|
stat:increment(player, floor(event.final_damage_amount))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Add Kills if it is enabled
|
||||||
|
if config.DamageDealt then
|
||||||
|
local stat = Statistics:combine('Kills')
|
||||||
|
Event.add(defines.events.on_entity_died, function(event)
|
||||||
|
local character = event.cause -- Check character is valid
|
||||||
|
if not character.valid or character.type ~= 'character' then return end
|
||||||
|
local player = character.player -- Check player is valid
|
||||||
|
if not player.valid or not player.connected then return end
|
||||||
|
local entity = event.entity -- Check entity is valid
|
||||||
|
if not entity.valid or entity.force == player.force or entity.force.name == 'neutral' then return end
|
||||||
|
stat:increment(player)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
--- Add all the remaining statistics from the config
|
||||||
|
for statistic, event_name in pairs(config.counters) do
|
||||||
|
local stat = Statistics:combine(statistic)
|
||||||
|
Event.add(event_name, function(event)
|
||||||
|
if event.player_index then
|
||||||
|
local player = game.players[event.player_index]
|
||||||
|
if not player.valid or not player.connected then return end
|
||||||
|
stat:increment(player)
|
||||||
|
else
|
||||||
|
for _, player in pairs(game.connected_players) do
|
||||||
|
stat:increment(player)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
@@ -11,11 +11,16 @@ local event_handlers = {}
|
|||||||
-- map of nth_tick to handlers[]
|
-- map of nth_tick to handlers[]
|
||||||
local on_nth_tick_event_handlers = {}
|
local on_nth_tick_event_handlers = {}
|
||||||
|
|
||||||
local pcall = pcall
|
local trace = debug.traceback
|
||||||
|
local xpcall = xpcall
|
||||||
local log = log
|
local log = log
|
||||||
local script_on_event = script.on_event
|
local script_on_event = script.on_event
|
||||||
local script_on_nth_tick = script.on_nth_tick
|
local script_on_nth_tick = script.on_nth_tick
|
||||||
|
|
||||||
|
local function handler_error(err)
|
||||||
|
log('\n\t'..trace(err))
|
||||||
|
end
|
||||||
|
|
||||||
local function call_handlers(handlers, event)
|
local function call_handlers(handlers, event)
|
||||||
if _DEBUG then
|
if _DEBUG then
|
||||||
for i = 1, #handlers do
|
for i = 1, #handlers do
|
||||||
@@ -24,11 +29,7 @@ local function call_handlers(handlers, event)
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
for i = 1, #handlers do
|
for i = 1, #handlers do
|
||||||
local handler = handlers[i]
|
xpcall(handlers[i], handler_error, event)
|
||||||
local success, error = pcall(handler, event)
|
|
||||||
if not success then
|
|
||||||
log('\n\t'..error)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user