From ce7a6ae1bfcef0839e0f9f2e6a642b7b7a1680df Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Sat, 5 Sep 2026 20:53:57 +0000 Subject: [PATCH 1/2] Only log a rocket as fired when its ammo count drops Factorio has no fired event, so the log was written on every ammo inventory change, which includes loading rockets for the first time and swapping between rocket types. The last seen name and count of each ammo slot is now kept per player, and a shot is only logged when a slot holds the same ammo with one fewer, or empties from a single round. Only the three configured ammo types are logged, rather than every ammo change. Fixes #242 --- .../module/control/deconstruction_log.lua | 64 +++++++++++++++---- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/exp_scenario/module/control/deconstruction_log.lua b/exp_scenario/module/control/deconstruction_log.lua index ddfeb97909..7688e5d51a 100644 --- a/exp_scenario/module/control/deconstruction_log.lua +++ b/exp_scenario/module/control/deconstruction_log.lua @@ -3,6 +3,7 @@ Log certain actions into a file when events are triggered ]] local ExpUtil = require("modules/exp_util") +local Storage = require("modules/exp_util/storage") local Roles = require("modules/exp_roles") local config = require("modules.exp_legacy.config.deconlog") @@ -130,29 +131,63 @@ local function on_player_mined_entity(event) add_log_line(player, "mined_entity", format_entity(event.entity)) end ---- Log when rocket is fired +--- Ammo which is logged when fired +local logged_ammo = { + ["rocket"] = config.fired_rocket, + ["explosive-rocket"] = config.fired_explosive_rocket, + ["atomic-bomb"] = config.fired_nuke, +} + +--- @class ExpScenario_DeconstructionLog.AmmoSlot +--- @field name string +--- @field count number + +--- The last seen contents of each ammo slot, keyed by player index then slot index +local ammo_slots = {} --- @type table> +Storage.register(ammo_slots, function(tbl) + ammo_slots = tbl +end) + +--- Log a shot, there is no fired event so a slot losing one of the same ammo is taken as a shot --- @param event EventData.on_player_ammo_inventory_changed local function on_player_ammo_inventory_changed(event) local player = get_log_player(event) if not player or not player.character then return end - local character_ammo = assert(player.get_inventory(defines.inventory.character_ammo)) - local gun_index = player.character.selected_gun_index --[[@as uint]] - local item = character_ammo[gun_index] - if not item or not item.valid or not item.valid_for_read then - return + local slots = ammo_slots[player.index] + if not slots then + slots = {} + ammo_slots[player.index] = slots end - local action_name = "shot-" .. item.name - if not config.fired_rocket and action_name == "shot-rocket" then - return - elseif not config.fired_explosive_rocket and action_name == "shot-explosive-rocket" then - return - elseif not config.fired_nuke and action_name == "shot-atomic-bomb" then - return + local character_ammo = assert(player.get_inventory(defines.inventory.character_ammo)) + for index = 1, #character_ammo do + local stack = character_ammo[index --[[@as uint]]] + local previous = slots[index] + local fired = nil --- @type string? + + if stack.valid_for_read then + if previous and previous.name == stack.name and previous.count == stack.count + 1 then + fired = stack.name + end + slots[index] = { name = stack.name, count = stack.count } + else + if previous and previous.count == 1 then + fired = previous.name + end + slots[index] = nil + end + + if fired and logged_ammo[fired] then + add_log_line(player, "shot-" .. fired, format_position(player.physical_position), format_position(player.shooting_state.position)) + end end +end - add_log_line(player, action_name, format_position(player.physical_position), format_position(player.shooting_state.position)) +--- Forget the ammo of a player who left, their slots are read again on the next change +--- @param event EventData.on_player_left_game +local function on_player_left_game(event) + ammo_slots[event.player_index] = nil end @@ -175,6 +210,7 @@ end if config.fired_rocket or config.fired_explosive_rocket or config.fired_nuke then events[e.on_player_ammo_inventory_changed] = on_player_ammo_inventory_changed + events[e.on_player_left_game] = on_player_left_game end return { From 397033e8a6b907b1bd3f2041f173dcfa3c5d2a76 Mon Sep 17 00:00:00 2001 From: bbassie <17990055+bbassie@users.noreply.github.com> Date: Sun, 6 Sep 2026 00:45:53 +0000 Subject: [PATCH 2/2] Read ammo slots on join so the first shot is logged Clearing the slots on leave meant nothing was known about a player's ammo until it changed, so the first shot after rejoining was missed. The slots are now read when a player joins and when they respawn, since a new character starts empty and a stale single round would otherwise count as a shot. The change handler reads the slots the same way and compares them with what was stored. --- .../module/control/deconstruction_log.lua | 60 ++++++++++++------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/exp_scenario/module/control/deconstruction_log.lua b/exp_scenario/module/control/deconstruction_log.lua index 7688e5d51a..325bb3f81b 100644 --- a/exp_scenario/module/control/deconstruction_log.lua +++ b/exp_scenario/module/control/deconstruction_log.lua @@ -148,34 +148,42 @@ Storage.register(ammo_slots, function(tbl) ammo_slots = tbl end) +--- Read the ammo slots of a player, empty while they have no character +--- @param player LuaPlayer +--- @return table +local function read_ammo_slots(player) + local slots = {} + if not player.character then return slots end + + local character_ammo = assert(player.get_inventory(defines.inventory.character_ammo)) + for index = 1, #character_ammo do + local stack = character_ammo[index --[[@as uint]]] + if stack.valid_for_read then + slots[index] = { name = stack.name, count = stack.count } + end + end + return slots +end + --- Log a shot, there is no fired event so a slot losing one of the same ammo is taken as a shot --- @param event EventData.on_player_ammo_inventory_changed local function on_player_ammo_inventory_changed(event) local player = get_log_player(event) - if not player or not player.character then return end + if not player then return end - local slots = ammo_slots[player.index] - if not slots then - slots = {} - ammo_slots[player.index] = slots - end + local previous_slots = ammo_slots[player.index] or {} + local slots = read_ammo_slots(player) + ammo_slots[player.index] = slots - local character_ammo = assert(player.get_inventory(defines.inventory.character_ammo)) - for index = 1, #character_ammo do - local stack = character_ammo[index --[[@as uint]]] - local previous = slots[index] + for index, previous in pairs(previous_slots) do + local current = slots[index] local fired = nil --- @type string? - - if stack.valid_for_read then - if previous and previous.name == stack.name and previous.count == stack.count + 1 then - fired = stack.name + if current then + if previous.name == current.name and previous.count == current.count + 1 then + fired = current.name end - slots[index] = { name = stack.name, count = stack.count } - else - if previous and previous.count == 1 then - fired = previous.name - end - slots[index] = nil + elseif previous.count == 1 then + fired = previous.name end if fired and logged_ammo[fired] then @@ -184,13 +192,19 @@ local function on_player_ammo_inventory_changed(event) end end ---- Forget the ammo of a player who left, their slots are read again on the next change +--- Read the ammo of a player when they join or get a new character, so the first shot afterwards is seen +--- @param event EventData.on_player_joined_game | EventData.on_player_respawned +local function on_player_character_changed(event) + local player = assert(game.get_player(event.player_index)) + ammo_slots[player.index] = read_ammo_slots(player) +end + +--- Forget the ammo of a player who left --- @param event EventData.on_player_left_game local function on_player_left_game(event) ammo_slots[event.player_index] = nil end - local e = defines.events local events = { [e.on_multiplayer_init] = clear_log, @@ -210,6 +224,8 @@ end if config.fired_rocket or config.fired_explosive_rocket or config.fired_nuke then events[e.on_player_ammo_inventory_changed] = on_player_ammo_inventory_changed + events[e.on_player_joined_game] = on_player_character_changed + events[e.on_player_respawned] = on_player_character_changed events[e.on_player_left_game] = on_player_left_game end