From 2dacbe9edd3e2e57cc2aba3f3b86887630010f86 Mon Sep 17 00:00:00 2001 From: Cooldude2606 Date: Sat, 30 May 2020 01:45:02 +0100 Subject: [PATCH] Added Statistics --- config/_file_loader.lua | 1 + config/statistics.lua | 29 +++++++++ expcore/datastore.lua | 8 +-- expcore/player_data.lua | 22 ++++--- modules/data/statistics.lua | 123 ++++++++++++++++++++++++++++++++++++ utils/event_core.lua | 13 ++-- 6 files changed, 177 insertions(+), 19 deletions(-) create mode 100644 config/statistics.lua create mode 100644 modules/data/statistics.lua diff --git a/config/_file_loader.lua b/config/_file_loader.lua index 28d4ebde..5fd71d8d 100644 --- a/config/_file_loader.lua +++ b/config/_file_loader.lua @@ -43,6 +43,7 @@ return { 'modules.addons.tree-decon', --- Data + 'modules.data.statistics', 'modules.data.player-colours', 'modules.data.greetings', 'modules.data.quickbar', diff --git a/config/statistics.lua b/config/statistics.lua new file mode 100644 index 00000000..d26fa2f8 --- /dev/null +++ b/config/statistics.lua @@ -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 + } +} \ No newline at end of file diff --git a/expcore/datastore.lua b/expcore/datastore.lua index f13a4c50..ffa23f99 100644 --- a/expcore/datastore.lua +++ b/expcore/datastore.lua @@ -391,7 +391,7 @@ self:write_action('save', 'TestKey', 'Foo') function Datastore:write_action(action, key, value) local data = {action, self.name, key} 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 game.write_file('ext/datastore.out', table.concat(data, ' ')..'\n', true, 0) end @@ -517,7 +517,7 @@ ExampleData:increment('TestNumber') function Datastore:increment(key, delta) key = self:serialize(key) 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 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 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 @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 @@ -737,7 +737,7 @@ end ----- 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 @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 diff --git a/expcore/player_data.lua b/expcore/player_data.lua index 576674f6..c3861a3c 100644 --- a/expcore/player_data.lua +++ b/expcore/player_data.lua @@ -83,26 +83,30 @@ end) --- Async function called after 5 seconds with no player data loaded local check_data_loaded = Async.register(function(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'} - Datastore.ingest('request', 'PlayerData', player.name, '{"failed_load":true}') + Datastore.ingest('request', 'PlayerData', player.name, '{"valid":false}') end end) --- When player data loads tell the player if the load had failed previously 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) - 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'} end + player_data.valid = true end) --- Remove data that the player doesnt want to have stored PlayerData:on_save(function(player_name, player_data) local dataPreference = DataSavingPreference:get(player_name) 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] } if dataPreference <= PreferenceEnum.Settings then saved_player_data.PlayerSettings = player_data.PlayerSettings end @@ -119,17 +123,17 @@ end) --- Load player data when they join Event.add(defines.events.on_player_joined_game, function(event) local player = game.players[event.player_index] - PlayerData:request(player) Async.wait(300, check_data_loaded, player) + PlayerData:request(player) end) --- Unload player data when they leave Event.add(defines.events.on_player_left_game, function(event) local player = game.players[event.player_index] local player_data = PlayerData:get(player) - if player_data.failed_load then - PlayerData:raw_set(player) - else PlayerData:unload(player) end + if player_data.valid == true then + PlayerData:unload(player) + else PlayerData:raw_set(player) end end) ----- Module Return ----- diff --git a/modules/data/statistics.lua b/modules/data/statistics.lua new file mode 100644 index 00000000..65d30fcb --- /dev/null +++ b/modules/data/statistics.lua @@ -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 \ No newline at end of file diff --git a/utils/event_core.lua b/utils/event_core.lua index b34715e1..9525167a 100644 --- a/utils/event_core.lua +++ b/utils/event_core.lua @@ -11,11 +11,16 @@ local event_handlers = {} -- map of nth_tick to handlers[] local on_nth_tick_event_handlers = {} -local pcall = pcall +local trace = debug.traceback +local xpcall = xpcall local log = log local script_on_event = script.on_event 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) if _DEBUG then for i = 1, #handlers do @@ -24,11 +29,7 @@ local function call_handlers(handlers, event) end else for i = 1, #handlers do - local handler = handlers[i] - local success, error = pcall(handler, event) - if not success then - log('\n\t'..error) - end + xpcall(handlers[i], handler_error, event) end end end