diff --git a/.vscode/settings.json b/.vscode/settings.json index a8b239f..e06d64e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "Lua.workspace.userThirdParty": [ - "c:\\Users\\jangr\\AppData\\Roaming\\Code\\User\\workspaceStorage\\6d87f11bcadf77c8bf3a32c6545bb9a5\\justarandomgeek.factoriomod-debug\\sumneko-3rd" + "c:\\Users\\jangr\\AppData\\Roaming\\Code\\User\\workspaceStorage\\7cc4b07e291c43ff9c178f1dad6e0f26\\justarandomgeek.factoriomod-debug\\sumneko-3rd" ], "Lua.workspace.checkThirdParty": "ApplyInMemory", "factorio.versions": [ diff --git a/control.lua b/control.lua index a0704b0..7474716 100644 --- a/control.lua +++ b/control.lua @@ -5,6 +5,7 @@ require("research-stats") require("power-stats") require("logistic-network-stats") require("train-stats") +require("metrics-combinator") tickInterval = tonumber(settings.global["factorio-metrics-exporter-tick-interval"].value) or 300 udpAddress = 52555 @@ -13,12 +14,14 @@ sendIndex = 0 serverIndex = 0 scannedGrids = false scannedLabs = false +scannedGenerators = false autotrainGroupName = "" autotrainDepotName = "" + options = { enableMod = false, - enablePlayers = false, + enablePlayers = false, enableProduction = false, enableFluid = false, enableKills = false, @@ -31,7 +34,7 @@ options = { } -script.on_init(function () +script.on_init(function() storage.electricGrids = {} storage.researchedTechnologies = {} storage.playerDeathCount = {} @@ -46,9 +49,19 @@ script.on_init(function () storage.deconstructedEntities = {} storage.trainStats = {} storage.networkCache = {} - + ---@type LuaEntity[] + storage.powerGenerators = {} ---@type LuaTrain[] storage.trains = {} + storage.powerStats = {} + + storage.cargoStats = {} + + storage.metrics = {} + storage.cliffsDestroyed = 0 + + storage.nuclearReactorDeaths = 0 + storage.scannedGrids = false storage.scannedLabs = false @@ -67,15 +80,13 @@ script.on_init(function () options.enableTrainTrips = settings.global["factorio-metrics-exporter-export_train_trips"].value autotrainGroupName = settings.global["factorio-metrics-exporter-autotrain_group_name"].value autotrainDepotName = settings.global["factorio-metrics-exporter-autotrain_depot_name"].value - - end) -script.on_load(function () +script.on_load(function() log("factorio-metrics-exporter: on_load") - log("tickInterval: "..tickInterval) - log("udpAddress: "..udpAddress) - + log("tickInterval: " .. tickInterval) + log("udpAddress: " .. udpAddress) + options.enableMod = settings.global["factorio-metrics-exporter-enable"].value options.enableProduction = settings.global["factorio-metrics-exporter-export_production_stats"].value options.enablePollution = settings.global["factorio-metrics-exporter-export_pollution_stats"].value @@ -105,47 +116,55 @@ script.on_configuration_changed(function() storage.scannedLabs = storage.scannedLabs or false storage.playerDeathCause = storage.playerDeathCause or {} storage.constructedEntites = storage.constructedEntites or {} - storage.deconstructedEntities = storage.deconstructedEntities or{} + storage.deconstructedEntities = storage.deconstructedEntities or {} storage.networkCache = storage.networkCache or {} storage.trains = storage.trains or {} ---@type table storage.trainStats = storage.trainStats or {} + ---@type LuaEntity[] + storage.powerGenerators = storage.powerGenerators or {} + storage.powerStats = storage.powerStats or {} + storage.metrics = storage.metrics or {} + storage.cargoStats = storage.cargoStats or {} + storage.nuclearReactorDeaths = storage.nuclearReactorDeaths or 0 + storage.cliffsDestroyed = storage.cliffsDestroyed or 0 ScanNetworks() ScanLabs() ScanTrains() + ScanGenerators() end ) script.on_event(defines.events.on_runtime_mod_setting_changed, function(event) - log("Mod setting changed: "..event.setting) + log("Mod setting changed: " .. event.setting) if event.setting == "factorio-metrics-exporter-tick-interval" then tickInterval = settings.global["factorio-metrics-exporter-tick-interval"].value - end + end if event.setting == "factorio-metrics-exporter-enable" then options.enableMod = settings.global["factorio-metrics-exporter-enable"].value end if event.setting == "factorio-metrics-exporter-export_production_stats" then options.enableProduction = settings.global["factorio-metrics-exporter-export_production_stats"].value end - + if event.setting == "factorio-metrics-exporter-export_fluid_stats" then options.enableFluid = settings.global["factorio-metrics-exporter-export_fluid_stats"].value end - + if event.setting == "factorio-metrics-exporter-export_pollution_stats" then options.enablePollution = settings.global["factorio-metrics-exporter-export_pollution_stats"].value end - + if event.setting == "factorio-metrics-exporter-export_power_stats" then options.enablePower = settings.global["factorio-metrics-exporter-export_power_stats"].value end - + if event.setting == "factorio-metrics-exporter-export_logistic_stats" then options.enableRobots = settings.global["factorio-metrics-exporter-export_logistic_stats"].value end - + if event.setting == "factorio-metrics-exporter-export_player_stats" then options.enablePlayers = settings.global["factorio-metrics-exporter-export_player_stats"].value end @@ -153,7 +172,7 @@ script.on_event(defines.events.on_runtime_mod_setting_changed, function(event) if event.setting == "factorio-metrics-exporter-export_kill_stats" then options.enableKills = settings.global["factorio-metrics-exporter-export_kill_stats"].value end - + if event.setting == "factorio-metrics-exporter-export_research_stats" then options.enableResearch = settings.global["factorio-metrics-exporter-export_research_stats"].value end @@ -163,13 +182,12 @@ script.on_event(defines.events.on_runtime_mod_setting_changed, function(event) if event.setting == "factorio-metrics-exporter-autotrain_depot_name" then autotrainDepotName = settings.global["factorio-metrics-exporter-autotrain_depot_name"].value end - if event.setting == "factorio-metrics-exporter-autotrain_group_name" then + if event.setting == "factorio-metrics-exporter-autotrain_group_name" then autotrainGroupName = settings.global["factorio-metrics-exporter-autotrain_group_name"].value end if event.setting == "factorio-metrics-exporter-export_train_trips" then options.enableTrainTrips = settings.global["factorio-metrics-exporter-export_train_trips"].value end - end) @@ -182,55 +200,56 @@ script.on_event(defines.events.on_player_died, function(event) local victim_index = event.player_index local killerName = killer.name local victimName = game.players[victim_index].name - log(("Player ID %d:%s killed player ID %d:%s"):format(killer_index,killerName,victim_index,victimName)) + log(("Player ID %d:%s killed player ID %d:%s"):format(killer_index, killerName, victim_index, victimName)) storage.playerKillCount[killer_index] = - storage.playerKillCount[killer_index] or {} + storage.playerKillCount[killer_index] or {} storage.playerKillCount[killer_index][victim_index] = - (storage.playerKillCount[killer_index][victim_index] or 0) + 1 + (storage.playerKillCount[killer_index][victim_index] or 0) + 1 end end --Log cause of player death - if event.cause and event.cause.type then - storage.playerDeathCause[event.player_index] = - storage.playerDeathCause[event.player_index] or {} + if event.cause and event.cause.name then + storage.playerDeathCause[event.player_index] = + storage.playerDeathCause[event.player_index] or {} - storage.playerDeathCause[event.player_index][event.cause.type] = - (storage.playerDeathCause[event.player_index][event.cause.type] or 0) + 1 + storage.playerDeathCause[event.player_index][event.cause.name] = + (storage.playerDeathCause[event.player_index][event.cause.name] or 0) + 1 - log(("Player %s died from type %s"):format(game.players[event.player_index].name,event.cause.type)) - + log(("Player %s died from type %s"):format(game.players[event.player_index].name, event.cause.name)) end --Log player death count storage.playerDeathCount[event.player_index] = (storage.playerDeathCount[event.player_index] or 0) + 1 end) + function SendGameStats() if options.enablePlayers then - local returnParts = {} - GetMods() - returnParts[#returnParts+1] = GetMapSeed() - returnParts[#returnParts+1] = GetRocketsLaunched() - returnParts[#returnParts+1] = GetPlayerTime() - returnParts[#returnParts+1] = GetPlayerDeaths() - returnParts[#returnParts+1] = GetPlayerDeathCauses() - returnParts[#returnParts+1] = GetPlayerKills() - returnParts[#returnParts+1] = GetPlayerEntityStats() - helpers.send_udp(udpAddress, table.concat(returnParts, "\n"), serverIndex) + local returnParts = {} + GetMods() + returnParts[#returnParts + 1] = GetMapSeed() + returnParts[#returnParts + 1] = GetEvolution() + returnParts[#returnParts + 1] = GetRocketsLaunched() + returnParts[#returnParts + 1] = GetTotalPlayTime() + returnParts[#returnParts + 1] = GetPlayerTime() + returnParts[#returnParts + 1] = GetPlayerDeaths() + returnParts[#returnParts + 1] = GetPlayerDeathCauses() + returnParts[#returnParts + 1] = GetPlayerKills() + returnParts[#returnParts + 1] = GetPlayerEntityStats() + helpers.send_udp(udpAddress, table.concat(returnParts, "\n"), serverIndex) end end function SendAll(event) - - if(isInitialized == false) then + if (isInitialized == false) then if game.is_multiplayer() then serverIndex = 0 log("Loaded game as mulitplayer") else - log("Loaded game as singleplayer") - serverIndex = 1 + log("Loaded game as singleplayer") + serverIndex = 1 end end isInitialized = true @@ -240,23 +259,31 @@ function SendAll(event) ScanNetworks() scannedGrids = true end - + + if scannedGenerators == false then + storage.powerGenerators = {} + ScanGenerators() + scannedGenerators = true + end + if scannedLabs == false then ScanLabs() scannedLabs = true end - if options.enableMod==true then - - local interval = math.max(1, math.floor(tickInterval / 10)) - if event.tick % interval ~= 0 then return end + if options.enableMod == true then + local interval = math.max(1, math.floor(tickInterval / 10)) + if event.tick % interval ~= 0 then return end sendIndex = (sendIndex % 10) + 1 if sendIndex == 1 then SendProductionStats() end if sendIndex == 2 then SendPollutionStats() end if sendIndex == 3 then SendKillStats() end if sendIndex == 4 then SendFluidProductionStats() end if sendIndex == 5 then SendBuildStats() end - if sendIndex == 6 then SendResearchStats() end + if sendIndex == 6 then + SendResearchStats() + SendCombinatorMetrics() + end if sendIndex == 7 then SendLogisticStats() end if sendIndex == 8 then SendPowerStats() end if sendIndex == 9 then SendGameStats() end @@ -272,96 +299,136 @@ function UpdateStorage(event) if event.entity.type == "electric-pole" then AddPowerPole(event) end + if event.entity.type == "generator" + or event.entity.type == "fusion-generator" + or event.entity.type == "solar-panel" then + AddGenerator(event) + end end function RemoveStorage(event) if not event then return end - if event.entity.type =="lab" then + if event.entity.type == "lab" then RemoveLab(event) end if event.entity.type == "electric-pole" then RemovePowerPole(event) end + if event.entity.type == "generator" + or event.entity.type == "fusion-generator" + or event.entity.type == "solar-panel" then + RemoveGenerator(event) + end + --log(event.entity.name) - if event.entity.name == "crash-site-spaceship" then - --log(event.name) - if event.name == defines.events.on_player_mined_entity then - --log("in ban call") - if settings.global["factorio-metrics-exporter-enable_denkmalschutz"].value == true then - game.ban_player(event.player_index,"You violated the rules of DENKMALSCHUTZ!!!") - end + if event.entity.name == "crash-site-spaceship" then + --log(event.name) + if event.name == defines.events.on_player_mined_entity then + --log("in ban call") + if settings.global["factorio-metrics-exporter-enable_denkmalschutz"].value == true then + game.ban_player(event.player_index, "You violated the rules of DENKMALSCHUTZ!!!") + game.kick_player(event.player_index, "") + end + end + if event.name == defines.events.on_robot_mined_entity then + if event.entity.last_user then + game.ban_player(event.entity.last_user.index, "You violated the rules of DENKMALSCHUTZ!!!") + end end end end function CreateEntity(event) if not event then return end - + --Event is PlayerPlaced if event.name == defines.events.on_built_entity then - if event.entity.name ~= "entity-ghost" then + if event.entity.name ~= "entity-ghost" + and event.entity.name ~= "tile-ghost" + and event.entity.name ~= "deconstructible_tile_proxy" then storage.constructedEntites[event.player_index] = storage.constructedEntites[event.player_index] or {} - storage.constructedEntites[event.player_index][event.entity.name] = (storage.constructedEntites[event.player_index][event.entity.name] or 0) + 1 + storage.constructedEntites[event.player_index][event.entity.name] = (storage.constructedEntites[event.player_index][event.entity.name] or 0) + + 1 end end --Event is RobotPlaced if event.name == defines.events.on_robot_built_entity then - if event.entity.name ~= "entity-ghost" then + if event.entity.name ~= "entity-ghost" + and event.entity.name ~= "tile-ghost" + and event.entity.name ~= "deconstructible_tile_proxy" then if event.entity.last_user then - local lastUser = event.entity.last_user.index - storage.constructedEntites[lastUser] = storage.constructedEntites[lastUser] or {} - storage.constructedEntites[lastUser][event.entity.name] = (storage.constructedEntites[lastUser][event.entity.name] or 0) + 1 + local lastUser = event.entity.last_user.index + storage.constructedEntites[lastUser] = storage.constructedEntites[lastUser] or {} + storage.constructedEntites[lastUser][event.entity.name] = (storage.constructedEntites[lastUser][event.entity.name] or 0) + + 1 + end end end - end --Event is spaceplatform build if event.name == defines.events.on_space_platform_built_entity then - if event.entity.name ~= "entity-ghost" then - if event.entity.last_user then - local lastUser = event.entity.last_user.index - storage.constructedEntites[lastUser] = storage.constructedEntites[lastUser] or {} - storage.constructedEntites[lastUser][event.entity.name] = (storage.constructedEntites[lastUser][event.entity.name] or 0) + 1 + if event.entity.name ~= "entity-ghost" + and event.entity.name ~= "tile-ghost" + and event.entity.name ~= "deconstructible_tile_proxy" then + if event.entity.last_user then + local lastUser = event.entity.last_user.index + storage.constructedEntites[lastUser] = storage.constructedEntites[lastUser] or {} + storage.constructedEntites[lastUser][event.entity.name] = (storage.constructedEntites[lastUser][event.entity.name] or 0) + + 1 + end end end - end UpdateStorage(event) - + CreateMetricsEntry(event) end function RemoveEntity(event) if event.name == defines.events.on_player_mined_entity then - storage.deconstructedEntities[event.player_index] = storage.deconstructedEntities[event.player_index] or {} - storage.deconstructedEntities[event.player_index][event.entity.name] = (storage.deconstructedEntities[event.player_index][event.entity.name] or 0) + 1 + if event.entity.name ~= "entity-ghost" + and event.entity.name ~= "tile-ghost" + and event.entity.name ~= "deconstructible_tile_proxy" then + storage.deconstructedEntities[event.player_index] = storage.deconstructedEntities[event.player_index] or {} + storage.deconstructedEntities[event.player_index][event.entity.name] = (storage.deconstructedEntities[event.player_index][event.entity.name] or 0) + + 1 + end end if event.name == defines.events.on_robot_mined_entity then - if event.entity.name ~= "entity-ghost" then + if event.entity.name ~= "entity-ghost" + and event.entity.name ~= "tile-ghost" + and event.entity.name ~= "deconstructible_tile_proxy" then if event.entity.last_user then - local lastUser = event.entity.last_user.index - storage.deconstructedEntities[lastUser] = storage.deconstructedEntities[lastUser] or {} - storage.deconstructedEntities[lastUser][event.entity.name] = (storage.deconstructedEntities[lastUser][event.entity.name] or 0) + 1 - end - end + local lastUser = event.entity.last_user.index + storage.deconstructedEntities[lastUser] = storage.deconstructedEntities[lastUser] or {} + storage.deconstructedEntities[lastUser][event.entity.name] = (storage.deconstructedEntities[lastUser][event.entity.name] or 0) + + 1 + end + end end if event.name == defines.events.on_space_platform_mined_entity then - if event.entity.name ~= "entity-ghost" then + if event.entity.name ~= "entity-ghost" + and event.entity.name ~= "tile-ghost" + and event.entity.name ~= "deconstructible_tile_proxy" then if event.entity.last_user then - local lastUser = event.entity.last_user.index - storage.deconstructedEntities[lastUser] = storage.deconstructedEntities[lastUser] or {} - storage.deconstructedEntities[lastUser][event.entity.name] = (storage.deconstructedEntities[lastUser][event.entity.name] or 0) + 1 + local lastUser = event.entity.last_user.index + storage.deconstructedEntities[lastUser] = storage.deconstructedEntities[lastUser] or {} + storage.deconstructedEntities[lastUser][event.entity.name] = (storage.deconstructedEntities[lastUser][event.entity.name] or 0) + + 1 + end end end - end if event.name == defines.events.on_entity_died then end + CheckReactor(event) RemoveStorage(event) + onMetricsCombinatorDied(event) + onMetricsCombinatorMined(event) end script.on_event(defines.events.on_tick, SendAll) @@ -373,11 +440,20 @@ script.on_event(defines.events.on_tick, SendAll) --script.on_event(defines.events.on_robot_mined_entity,RemoveStorage,{{filter = "type", type = "electric-pole"},{filter ="type", type="lab"},{filter = "type", type="container"}}) --script.on_event(defines.events.on_entity_died,RemoveStorage,{{filter = "type", type = "electric-pole"},{filter ="type", type="lab"},{filter = "type", type="container"}}) -script.on_event(defines.events.on_built_entity,CreateEntity) -script.on_event(defines.events.on_robot_built_entity,CreateEntity) -script.on_event(defines.events.on_space_platform_built_entity,CreateEntity) -script.on_event(defines.events.on_player_mined_entity,RemoveEntity) -script.on_event(defines.events.on_robot_mined_entity,RemoveEntity) -script.on_event(defines.events.on_space_platform_mined_entity,RemoveEntity) -script.on_event(defines.events.on_entity_died,RemoveEntity) -script.on_event(defines.events.on_train_changed_state,onTrainStateChange) \ No newline at end of file +script.on_event(defines.events.on_built_entity, CreateEntity) +script.on_event(defines.events.on_robot_built_entity, CreateEntity) +script.on_event(defines.events.on_space_platform_built_entity, CreateEntity) +script.on_event(defines.events.on_player_mined_entity, RemoveEntity) +script.on_event(defines.events.on_robot_mined_entity, RemoveEntity) +script.on_event(defines.events.on_space_platform_mined_entity, RemoveEntity) +script.on_event(defines.events.on_entity_died, RemoveEntity) +script.on_event(defines.events.on_train_changed_state, onTrainStateChange) + +script.on_event(defines.events.on_gui_checked_state_changed, onGuiCheckedState) +script.on_event(defines.events.on_gui_opened, onGuiOpened) +script.on_event(defines.events.on_gui_click, onGuiClick) +script.on_event(defines.events.on_gui_closed, onClosedCombinatorGui) +script.on_event(defines.events.on_gui_text_changed, onGuiTextChanged) + +script.on_event(defines.events.on_cargo_pod_delivered_cargo, onCargoDelivered) +script.on_event(defines.events.on_rocket_launched, onCargoDelivered) diff --git a/data.lua b/data.lua index e69de29..d603015 100644 --- a/data.lua +++ b/data.lua @@ -0,0 +1,55 @@ +-- ENTITY +local entity = table.deepcopy(data.raw["constant-combinator"]["constant-combinator"]) + +entity.name = "metrics-combinator" +entity.minable = { + mining_time = 0.1, + result = "metrics-combinator" +} +entity.allow_copy_paste = true +entity.icon = "__base__/graphics/icons/constant-combinator.png" +entity.icon_size = 64 +--entity.operable = false +entity.flags = { "get-by-unit-number", "placeable-neutral", "placeable-player" } + +-- ITEM +local item = { + type = "item", + name = "metrics-combinator", + icon = "__base__/graphics/icons/constant-combinator.png", + icon_size = 64, + subgroup = "circuit-network", + order = "c[combinators]-c[metrics-combinator]", + place_result = "metrics-combinator", + stack_size = 50 +} + +-- RECIPE (sichtbar von Spielstart) +local recipe = { + type = "recipe", + name = "metrics-combinator", + enabled = true, + allow_quality = true, + + ingredients = { + { + type = "item", + name = "electronic-circuit", + amount = 1 + } + }, + + results = { + { + type = "item", + name = "metrics-combinator", + amount = 1 + } + } +} + +data:extend({ + entity, + item, + recipe +}) diff --git a/game-stats.lua b/game-stats.lua index 8450133..77490c5 100644 --- a/game-stats.lua +++ b/game-stats.lua @@ -2,93 +2,111 @@ function GetMods() local mods = script.active_mods local modstring = "---mod-info---\n" - for k,v in pairs(mods) do - modstring = modstring .. ("%s:%s\n"):format(k,v) + for k, v in pairs(mods) do + modstring = modstring .. ("%s:%s\n"):format(k, v) end - helpers.send_udp(udpAddress, modstring,serverIndex) + helpers.send_udp(udpAddress, modstring, serverIndex) end function GetPlayerColors() local colorParts = {} - colorParts[#colorParts+1] = "---player-colors---\n" + colorParts[#colorParts + 1] = "---player-colors---\n" for index, player in pairs(game.players) do local colorSettings = player.color - colorParts[#colorParts+1] = ("%d:%s:%d:%d:%d:%d"):format(index,player.name,colorSettings.r,colorSettings.g,colorSettings.b,colorSettings.a) + colorParts[#colorParts + 1] = ("%d:%s:%d:%d:%d:%d"):format(index, player.name, colorSettings.r, colorSettings.g, + colorSettings.b, colorSettings.a) end - return table.concat(colorParts,"\n") + return table.concat(colorParts, "\n") end - function GetPlayerKills() local killParts = {} - killParts[#killParts+1] = "---player-kills---\n" + killParts[#killParts + 1] = "---player-kills---\n" for killerIndex, victims in pairs(storage.playerKillCount) do for victimIndex, kills in pairs(victims) do local killerName = game.players[killerIndex].name local victimName = game.players[victimIndex].name - killParts[#killParts+1] = ("%s:%s:%s:%s:%d"):format(killerName,killerIndex,victimName,victimIndex,kills) + killParts[#killParts + 1] = ("%s:%s:%s:%s:%d"):format(killerName, killerIndex, victimName, victimIndex, kills) end end - return table.concat(killParts,"\n") + return table.concat(killParts, "\n") end function GetPlayerEntityStats() local entityParts = {} - entityParts[#entityParts+1] = "---player-build-stats---" + entityParts[#entityParts + 1] = "---player-build-stats---" for playerIndex, items in pairs(storage.constructedEntites) do local playerName = game.players[playerIndex].name for itemName, itemCount in pairs(items) do - entityParts[#entityParts+1] = ("%s:%s:constructed:%s:%s"):format(playerIndex,playerName,itemName,itemCount) + entityParts[#entityParts + 1] = ("%s:%s:constructed:%s:%s"):format(playerIndex, playerName, itemName, itemCount) end end for playerIndex, items in pairs(storage.deconstructedEntities) do local playerName = game.players[playerIndex].name for itemName, itemCount in pairs(items) do - entityParts[#entityParts+1] = ("%s:%s:deconstructed:%s:%s"):format(playerIndex,playerName,itemName,itemCount) + entityParts[#entityParts + 1] = ("%s:%s:deconstructed:%s:%s"):format(playerIndex, playerName, itemName, itemCount) end end - return table.concat(entityParts,"\n") + return table.concat(entityParts, "\n") end function GetMapSeed() - return("---map-seed---\n%d"):format(game.surfaces["nauvis"].map_gen_settings.seed) + return ("---map-seed---\n%d"):format(game.surfaces["nauvis"].map_gen_settings.seed) end function GetRocketsLaunched() - return("---rocket-launches---\n%d"):format(game.forces["player"].rockets_launched) - end + return ("---rocket-launches---\n%d"):format(game.forces["player"].rockets_launched) +end ---Concats all player online times into a single string ---Takes all players that ever visited the server into account ---@return string function GetPlayerTime() - local timeParts = {} - timeParts[#timeParts+1] = "---player-times---\n" - for _,player in pairs(game.players) do - timeParts[#timeParts+1] = ("%s:%d:%d"):format(player.name, player.index, player.online_time) - end - return table.concat(timeParts, "\n") + local timeParts = {} + timeParts[#timeParts + 1] = "---player-times---\n" + for _, player in pairs(game.players) do + timeParts[#timeParts + 1] = ("%s:%d:%d"):format(player.name, player.index, player.online_time) + end + return table.concat(timeParts, "\n") end - ---comment ---@return string function GetPlayerDeaths() - local deathParts = {} - deathParts[#deathParts+1] = "---player-deaths---\n" - for _,player in pairs(game.players) do - deathParts[#deathParts+1] = ("%s:%d:%d"):format(player.name, player.index, storage.playerDeathCount[player.index]or 0) - end - return table.concat(deathParts, "\n") + local deathParts = {} + deathParts[#deathParts + 1] = "---player-deaths---\n" + for _, player in pairs(game.players) do + deathParts[#deathParts + 1] = ("%s:%d:%d"):format(player.name, player.index, + storage.playerDeathCount[player.index] or 0) + end + return table.concat(deathParts, "\n") end function GetPlayerDeathCauses() local deathParts = {} - deathParts[#deathParts+1] = "---player-death-cause---\n" - for playerIndex,deathCauses in pairs(storage.playerDeathCause) do + deathParts[#deathParts + 1] = "---player-death-cause---\n" + for playerIndex, deathCauses in pairs(storage.playerDeathCause) do for causeName, causeCount in pairs(deathCauses) do - deathParts[#deathParts+1] = ("%s:%d:%s:%d"):format(game.players[playerIndex].name,playerIndex,causeName,causeCount) + deathParts[#deathParts + 1] = ("%s:%d:%s:%d"):format(game.players[playerIndex].name, playerIndex, causeName, + causeCount) end end - return table.concat(deathParts,"\n") -end \ No newline at end of file + return table.concat(deathParts, "\n") +end + +function GetTotalPlayTime() + return ("---game-time---\n%d:%d"):format(game.tick, game.ticks_played) +end + +function CheckReactor(event) + if not event and not event.entity then return end + if event.entity.name == "nuclear-reactor" then + if event.entity.temperature >= 900 then + storage.nuclearReactorDeaths = (storage.nuclearReactorDeaths or 0) + 1 + end + end +end + +function GetReactorExplosions() + return "---reactor-explosion---\n" .. (storage.nuclearReactorDeaths or 0) +end diff --git a/info.json b/info.json index 9792cfd..a01bd90 100644 --- a/info.json +++ b/info.json @@ -1,6 +1,6 @@ { "name": "factorio-metrics-exporter", - "version": "0.2.6", + "version": "0.3.0", "title": "Prometheus Metrics Exporter", "author": "Jan Grießhaber", "contact": "jan@griesshaber.systems", diff --git a/metrics-combinator.lua b/metrics-combinator.lua new file mode 100644 index 0000000..484594a --- /dev/null +++ b/metrics-combinator.lua @@ -0,0 +1,175 @@ +local function getMetricsStorageData(entity) + local unit = entity.unit_number + storage.metrics[unit] = storage.metrics[unit] or { + name = "", + enabled = false, + } + return storage.metrics[unit] +end + +function CreateMetricsEntry(event) + if event.entity.name == "metrics-combinator" + and event.entity.valid then + ---@type LuaEntity + local entity = event.entity + storage.metrics[entity.unit_number] = storage.metrics[entity.unit_number] or { + name = "", + enabled = false + } + end +end + +local function open_metrics_gui(player, entity) + if player.gui.screen.metrics_frame then + player.gui.screen.metrics_frame.destroy() + end + + local frame = player.gui.screen.add { + type = "frame", + name = "metrics_frame", + direction = "vertical" + } + + -- Titelzeile mit Close-Button + local titlebar = frame.add { + type = "flow", + direction = "horizontal" + } + + titlebar.drag_target = frame + + titlebar.add { + type = "label", + caption = "Metrics Combinator", + style = "frame_title" + } + + local spacer = titlebar.add { + type = "empty-widget", + style = "draggable_space_header", + } + spacer.style.horizontally_stretchable = true + spacer.drag_target = frame + + titlebar.add { + type = "sprite-button", + name = "metrics_close", + sprite = "utility/close", + style = "frame_action_button" + } + + -- Inhalt + frame.add { + type = "textfield", + name = "metrics_name", + text = storage.metrics[entity.unit_number].name + } + + frame.add { + type = "checkbox", + name = "metrics_enabled", + caption = "Enabled", + state = storage.metrics[entity.unit_number].enabled + } + + frame.force_auto_center() + frame.tags = { entity_unit_number = entity.unit_number } + player.opened = frame +end + +function onGuiClick(event) + if event.element.name ~= "metrics_close" then return end + + local player = game.get_player(event.player_index) + if not player then return end + if player.gui.screen.metrics_frame then + player.gui.screen.metrics_frame.destroy() + end +end + +function onGuiOpened(event) + if event.gui_type ~= defines.gui_type.entity then return end + if not (event.entity and event.entity.valid and event.entity.name == "metrics-combinator") then + return + end + + local player = game.get_player(event.player_index) + + -- SCHLÜSSELZEILE: Engine-GUI sofort schließen + player.opened = nil + + open_metrics_gui(player, event.entity) +end + +function onClosedCombinatorGui(event) + if event.element and event.element.name == "metrics_frame" then + event.element.destroy() + end +end + +function onGuiTextChanged(event) + if event.element.name ~= "metrics_name" then return end + + local player = game.get_player(event.player_index) + if not player then return end + local entity = player.opened + if not (entity and entity.valid) then return end + + local data = getMetricsStorageData(game.get_entity_by_unit_number(entity.tags.entity_unit_number)) + data.name = event.text +end + +function onGuiCheckedState(event) + if event.element.name ~= "metrics_enabled" then return end + + local player = game.get_player(event.player_index) + if not player then return end + local frame = player.opened + if not (frame and frame.valid) then return end + + + local data = getMetricsStorageData(game.get_entity_by_unit_number(frame.tags.entity_unit_number)) + data.enabled = event.element.state + + -- optional: echtes Abschalten + --frame.active = data.enabled +end + +function onMetricsCombinatorDied(event) + if event.entity.name ~= "metrics-combinator" then return end + storage.metrics[event.entity.unit_number] = nil +end + +function onMetricsCombinatorMined(event) + if event.entity.name ~= "metrics-combinator" then return end + storage.metrics[event.entity.unit_number] = nil +end + +function SendCombinatorMetrics() + local netParts = {} + netParts[#netParts + 1] = "---circuit-stats---\n" + for unitNumber, combinatorFlags in pairs(storage.metrics) do + if combinatorFlags.enabled then + local entity = game.get_entity_by_unit_number(unitNumber) + if entity and entity.valid then + ---@type LuaCircuitNetwork? + local redNet = entity.get_circuit_network(defines.wire_connector_id.circuit_red) + ---@type LuaCircuitNetwork? + local greenNet = entity.get_circuit_network(defines.wire_connector_id.circuit_green) + if redNet then + for _, signal in pairs(redNet.signals) do + netParts[#netParts + 1] = ("%s:red:%s:%d"):format(combinatorFlags.name, + signal.signal.name .. signal.signal.quality.name, signal.count) + end + end + if greenNet then + for _, signal in pairs(greenNet.signals) do + netParts[#netParts + 1] = ("%s:green:%s:%d"):format(combinatorFlags.name, + signal.signal.name .. signal.signal.quality.name, signal.count) + end + end + end + end + end + helpers.send_udp(udpAddress, table.concat(netParts, "\n"), serverIndex) +end diff --git a/pollution-stats.lua b/pollution-stats.lua index 119da8f..b70b407 100644 --- a/pollution-stats.lua +++ b/pollution-stats.lua @@ -1,53 +1,66 @@ function SendPollutionStats() if options.enablePollution then - local pollutionParts = {} - pollutionParts[#pollutionParts+1] = "---pollution-stats---\n" - for _,surface in pairs(game.surfaces) do - local surface_name = surface.name + local pollutionParts = {} + pollutionParts[#pollutionParts + 1] = "---pollution-stats---\n" + for _, surface in pairs(game.surfaces) do + local surface_name = surface.name - local pollution_input = game.surfaces[surface_name].pollution_statistics.input_counts - local pollution_output = game.surfaces[surface_name].pollution_statistics.output_counts - - if surface.platform ~= nil then - surface_name = surface.platform.name - end - - for name, stat in pairs(pollution_input) do - pollutionParts[#pollutionParts+1] = ("%s:in:%s:%d"):format(surface_name,name, stat) - end + local pollution_input = game.surfaces[surface_name].pollution_statistics.input_counts + local pollution_output = game.surfaces[surface_name].pollution_statistics.output_counts - for name, stat in pairs(pollution_output) do - pollutionParts[#pollutionParts+1] = ("%s:out:%s:%d"):format(surface_name,name, stat) + if surface.platform ~= nil then + surface_name = surface.platform.name + end + + for name, stat in pairs(pollution_input) do + pollutionParts[#pollutionParts + 1] = ("%s:in:%s:%d"):format(surface_name, name, stat) + end + + for name, stat in pairs(pollution_output) do + pollutionParts[#pollutionParts + 1] = ("%s:out:%s:%d"):format(surface_name, name, stat) + end end - + helpers.send_udp(udpAddress, table.concat(pollutionParts, "\n"), serverIndex) end - helpers.send_udp(udpAddress, table.concat(pollutionParts,"\n"),serverIndex) -end end function SendKillStats() if options.enableKills then - local killParts = {} - killParts[#killParts+1] = "---kill-stats---\n" - for _,surface in pairs(game.surfaces) do - local surface_name = surface.name + local killParts = {} + killParts[#killParts + 1] = "---kill-stats---\n" + for _, surface in pairs(game.surfaces) do + local surface_name = surface.name - local kill_input = game.forces["player"].get_kill_count_statistics(surface_name).input_counts - local kill_output = game.forces["player"].get_kill_count_statistics(surface_name).output_counts - - if surface.platform ~= nil then - surface_name = surface.platform.name - end - - for name, stat in pairs(kill_input) do - killParts[#killParts+1] = ("%s:in:%s:%d"):format(surface_name,name, stat) - end + local kill_input = game.forces["player"].get_kill_count_statistics(surface_name).input_counts + local kill_output = game.forces["player"].get_kill_count_statistics(surface_name).output_counts - for name, stat in pairs(kill_output) do - killParts[#killParts+1] = ("%s:out:%s:%d"):format(surface_name,name, stat) + if surface.platform ~= nil then + surface_name = surface.platform.name + end + + for name, stat in pairs(kill_input) do + killParts[#killParts + 1] = ("%s:in:%s:%d"):format(surface_name, name, stat) + end + + for name, stat in pairs(kill_output) do + killParts[#killParts + 1] = ("%s:out:%s:%d"):format(surface_name, name, stat) + end end - + helpers.send_udp(udpAddress, table.concat(killParts, "\n"), serverIndex) end - helpers.send_udp(udpAddress, table.concat(killParts,"\n"),serverIndex) end -end \ No newline at end of file + +function GetEvolution() + local evolutionParts = {} + local playerForce = game.forces["player"] + evolutionParts[#evolutionParts + 1] = "---evolution-stats---\n" + for _, surface in pairs(game.surfaces) do + evolutionParts[#evolutionParts + 1] = ("%s:%d:%d:%d:%d"):format( + surface.name, + playerForce.get_evolution_factor(surface), + playerForce.get_evolution_factor_by_pollution(surface), + playerForce.get_evolution_factor_by_time(surface), + playerForce.get_evolution_factor_by_killing_spawners(surface)) + end + return table.concat(evolutionParts, "\n") +end diff --git a/power-stats.lua b/power-stats.lua index fa87293..a1cb976 100644 --- a/power-stats.lua +++ b/power-stats.lua @@ -21,7 +21,7 @@ function GetNetworks() if storage.networkCache then return storage.networkCache end - + local networks = {} for _, pole in pairs(storage.representativePoles) do if pole.valid then @@ -31,7 +31,7 @@ function GetNetworks() end end end - + storage.networkCache = networks return networks end @@ -41,34 +41,152 @@ function ScanNetworks() storage.networkCache = {} for _, surface in pairs(game.surfaces) do - for _, pole in pairs(surface.find_entities_filtered{type = "electric-pole"}) do + for _, pole in pairs(surface.find_entities_filtered { type = "electric-pole" }) do if pole.valid and pole.electric_network_id then storage.representativePoles[pole.unit_number] = pole storage.networkCache[pole.electric_network_id] = pole end end end -end +end + +solarBase = 60 +steamBase = 900 +turbineBase = 5820 +fusionBase = 50000 + + +function AddGenerator(event) + if event then + if event.entity then + ---@type LuaEntity + local entity = event.entity + if entity.type == "solar-panel" then + storage.powerStats["solar"][entity.surface.name] = (storage.powerStats["solar"][entity.surface.name] or 0) + + (solarBase * entity.quality.default_multiplier * entity.surface.solar_power_multiplier) + end + if entity.type == "fusion-generator" then + storage.powerStats["fusion"][entity.surface.name] = (storage.powerStats["fusion"][entity.surface.name] or 0) + + (fusionBase * entity.quality.default_multiplier) + end + if entity.name == "steam-engine" then + storage.powerStats["steam"][entity.surface.name] = (storage.powerStats["steam"][entity.surface.name] or 0) + + (steamBase * entity.quality.default_multiplier) + end + if entity.name == "steam-turbine" then + storage.powerStats["turbine"][entity.surface.name] = (storage.powerStats["turbine"][entity.surface.name] or 0) + + (turbineBase * entity.quality.default_multiplier) + end + end + end +end + +function RemoveGenerator(event) + if event then + if event.entity then + ---@type LuaEntity + local entity = event.entity + if entity.type == "solar-panel" then + storage.powerStats["solar"][entity.surface.name] = (storage.powerStats["solar"][entity.surface.name] or 0) - + (solarBase * entity.quality.default_multiplier * entity.surface.solar_power_multiplier) + end + if entity.type == "fusion-generator" then + storage.powerStats["fusion"][entity.surface.name] = (storage.powerStats["fusion"][entity.surface.name] or 0) - + (fusionBase * entity.quality.default_multiplier) + end + if entity.name == "steam-engine" then + storage.powerStats["steam"][entity.surface.name] = (storage.powerStats["steam"][entity.surface.name] or 0) - + (steamBase * entity.quality.default_multiplier) + end + if entity.name == "steam-turbine" then + storage.powerStats["turbine"][entity.surface.name] = (storage.powerStats["turbine"][entity.surface.name] or 0) - + (turbineBase * entity.quality.default_multiplier) + end + end + end +end + +function ScanGenerators() + storage.powerGenerators = {} + for _, surface in pairs(game.surfaces) do + for _, generator in pairs(surface.find_entities_filtered({ filter = type, type = { "generator", "fusion-generator", "solar-panel" } })) do + storage.powerGenerators[generator.unit_number] = generator + end + end + PopulateStats() +end + +function PopulateStats() + local solar = {} + local steam = {} + local turbine = {} + local fusion = {} + + for _, generator in pairs(storage.powerGenerators) do + local surface = generator.surface + if generator.type == "solar-panel" then + local surfaceSolarFactor = surface.solar_power_multiplier + solar[surface.name] = (solar[surface.name] or 0) + + (solarBase * generator.quality.default_multiplier * surfaceSolarFactor) + end + if generator.type == "fusion-generator" then + fusion[surface.name] = (fusion[surface.name] or 0) + (fusionBase * generator.quality.default_multiplier) + end + if generator.name == "steam-engine" then + steam[surface.name] = (steam[surface.name] or 0) + (steamBase * generator.quality.default_multiplier) + end + if generator.name == "steam-turbine" then + turbine[surface.name] = (turbine[surface.name] or 0) + (turbineBase * generator.quality.default_multiplier) + end + end + storage.powerStats["solar"] = solar + storage.powerStats["steam"] = steam + storage.powerStats["turbine"] = turbine + storage.powerStats["fusion"] = fusion +end + +function GetPossiblePower() + local result = {} + result[#result + 1] = "---max-power---\n" + for _, surface in pairs(game.surfaces) do + result[#result + 1] = ("%s:%d:%d:%d:%d"):format( + surface.name, + storage.powerStats["solar"][surface.name] or 0, + storage.powerStats["steam"][surface.name] or 0, + storage.powerStats["turbine"][surface.name] or 0, + storage.powerStats["fusion"][surface.name] or 0 + ) + end + return table.concat(result, "\n") +end + function SendPowerStats() if options.enablePower then + local possiblePower = GetPossiblePower() local powerPart = {} - powerPart[#powerPart+1] = "---power-stats---\n" + powerPart[#powerPart + 1] = "---power-stats---\n" for _, pole in pairs(GetNetworks()) do if pole.valid and pole.type == "electric-pole" then local input = pole.electric_network_statistics.input_counts local output = pole.electric_network_statistics.output_counts + local capacity = pole.electric_network_statistics.storage_counts local surfaceName = pole.surface.name - for item,value in pairs(input) do - powerPart[#powerPart+1] = ("%s:%d:in:%s:%d"):format(surfaceName, pole.electric_network_id, item, value) + for item, value in pairs(input) do + powerPart[#powerPart + 1] = ("%s:%d:in:%s:%d"):format(surfaceName, pole.electric_network_id, item, + value) end for item, value in pairs(output) do - powerPart[#powerPart+1] = ("%s:%d:out:%s:%d"):format(surfaceName,pole.electric_network_id, item, value) + powerPart[#powerPart + 1] = ("%s:%d:out:%s:%d"):format(surfaceName, pole.electric_network_id, item, + value) + end + for item, value in pairs(capacity) do + powerPart[#powerPart + 1] = ("%s:%d:capacity:%s:%d"):format(surfaceName, pole.electric_network_id, + item, value) end end end - helpers.send_udp(udpAddress,table.concat(powerPart,"\n"),serverIndex) + powerPart[#powerPart + 1] = possiblePower + helpers.send_udp(udpAddress, table.concat(powerPart, "\n"), serverIndex) end end - - diff --git a/production-stats.lua b/production-stats.lua index 5867ce5..9364ec4 100644 --- a/production-stats.lua +++ b/production-stats.lua @@ -1,3 +1,27 @@ +protos = {} +--Experimental function, not in use right now +function GetProductionWithQuality(surface) + + log("StartQualProd"..surface.name) + local prodWithQualityParts = {} + local prodstat = game.forces["player"].get_item_production_statistics(surface) + for key, _ in pairs(prodstat.input_counts) do + for quality,_ in pairs(prototypes.quality) do + local count = prodstat.get_input_count({name=key,quality=quality}) + if count > 0 then + prodWithQualityParts[#prodWithQualityParts+1] = + surface.name..":".. + key..":".. + quality..":".. + count + end + end + end + log("EndQualProd"..surface.name) + return table.concat(prodWithQualityParts,"\n") +end + + function SendProductionStats() if options.enableProduction then local productionParts = {} @@ -5,6 +29,9 @@ function SendProductionStats() for _,surface in pairs(game.surfaces) do local surfaceName = surface.name + --TODO + --local test = GetProductionWithQuality(surface) + local inputStats = game.forces["player"].get_item_production_statistics(surfaceName).input_counts local outputStats = game.forces["player"].get_item_production_statistics(surfaceName).output_counts @@ -80,4 +107,13 @@ function SendBuildStats() end helpers.send_udp(udpAddress, table.concat(buildParts, "\n"), serverIndex) end -end \ No newline at end of file +end + + +function GetAllPrototypes() + for ID, _ in pairs(prototypes.item) do + for quality,_ in pairs(prototypes.quality) do + protos[#protos+1] = {name=ID,quality=quality} + end + end +end diff --git a/research-stats.lua b/research-stats.lua index bffac93..ba0797e 100644 --- a/research-stats.lua +++ b/research-stats.lua @@ -12,8 +12,6 @@ function ScanLabs() end end - - function UpdateLabs(event) local lab = event.entity if lab and lab.valid then @@ -28,8 +26,6 @@ function RemoveLab(event) end end - - function GetEstimatedResearchTime() -- Base time in seconds for research local playerForce = game.forces["player"] @@ -40,7 +36,8 @@ function GetEstimatedResearchTime() local researchName = currentResearch.name.."-"..currentResearch.level local researchTotalCost = currentResearch.research_unit_count * (currentResearch.research_unit_energy/60) - local totalSpeed = storage.totalResearchSpeed + --local totalSpeed = storage.totalResearchSpeed + local totalSpeed = GetCurrentResearchSpeed() local remainingPercentage = 1-math.min(playerForce.research_progress,1) local remainingUnits = remainingPercentage*researchTotalCost @@ -81,9 +78,18 @@ function UpdateLabInfos() storage.totalResearchSpeed = totalSpeed end +function GetCurrentResearchSpeed() + local totalResearch = 0 + local playerForce = game.forces["player"] + if not playerForce then return end + for _,surface in pairs(game.surfaces) do + totalResearch = totalResearch + playerForce.get_item_production_statistics(surface).get_flow_count({name = "science", category = "input",precision_index = defines.flow_precision_index.five_seconds}) + end + return totalResearch +end + function SendResearchStats() if options.enableResearch == true then - UpdateLabInfos() local researchTimeInfo = GetEstimatedResearchTime() if researchTimeInfo then helpers.send_udp(udpAddress, researchTimeInfo, serverIndex) diff --git a/train-stats.lua b/train-stats.lua index 1c1080e..fd086bb 100644 --- a/train-stats.lua +++ b/train-stats.lua @@ -1,6 +1,6 @@ function ScanTrains() storage.trains = {} - for _,train in pairs(game.train_manager.get_trains({})) do + for _, train in pairs(game.train_manager.get_trains({})) do storage.trains[train.id] = train end end @@ -12,101 +12,126 @@ function GetTrainName(train) return train.locomotives.front_movers[1].backer_name or "" elseif train.locomotives.back_movers[1] then return train.locomotives.back_movers[1].backer_name or "" - end + end return "" end function GetTrainsInDepot() local trainsInDepot = 0 for trainsID, train in pairs(storage.trains) do - if train.state == defines.train_state.wait_station - and train.station and train.station.backer_name == autotrainDepotName then + if train.state == defines.train_state.wait_station + and train.station and train.station.backer_name == autotrainDepotName then trainsInDepot = trainsInDepot + 1 end end - local trainsInGroup = #game.train_manager.get_trains({group=autotrainGroupName}) - return ("---autotrain-stats---\n%d:%d"):format(trainsInDepot,trainsInGroup) + local trainsInGroup = #game.train_manager.get_trains({ group = autotrainGroupName }) + return ("---autotrain-stats---\n%d:%d"):format(trainsInDepot, trainsInGroup) end function GetTrainPlayerKills() local trainKills = {} - trainKills[#trainKills+1] = "---train-player-kills---" + trainKills[#trainKills + 1] = "---train-player-kills---" ---@type LuaTrain - for _, train in pairs(storage.trains ) do - for killedPlayerID,killedPlayerCount in pairs(train.killed_players) do - trainKills[#trainKills+1] = ("%s:%s:%s:%s:%d"):format(train.id,GetTrainName(train),killedPlayerID,game.players[killedPlayerID].name,killedPlayerCount) + for _, train in pairs(storage.trains) do + for killedPlayerID, killedPlayerCount in pairs(train.killed_players) do + trainKills[#trainKills + 1] = ("%s:%s:%s:%s:%d"):format(train.id, GetTrainName(train), killedPlayerID, + game.players[killedPlayerID].name, killedPlayerCount) end end - return table.concat(trainKills,"\n") + return table.concat(trainKills, "\n") end - function GetTrainTotalKills() local trainKills = {} - trainKills[#trainKills+1] = "---train-total-kills---" + trainKills[#trainKills + 1] = "---train-total-kills---" ---@type LuaTrain for _, train in pairs(storage.trains) do - trainKills[#trainKills+1] = ("%s:%s:%d"):format(train.id,GetTrainName(train),train.kill_count) + trainKills[#trainKills + 1] = ("%s:%s:%d"):format(train.id, GetTrainName(train), train.kill_count) end - return table.concat(trainKills,"\n") + return table.concat(trainKills, "\n") end +--TODO: Seperate by surface function GetTrainStates() - local trainsDriving = 0 - local trainsWaiting = 0 - local trainsProblems = 0 - local trainsManual = 0 + local trainsWaiting = {} + local trainsDriving = {} + local trainsManual = {} + local trainsProblems = {} for _, train in pairs(storage.trains) do - if train.state == defines.train_state.wait_station - or train.state == defines.train_state.destination_full - or train.state == defines.train_state.no_schedule - then trainsWaiting = trainsWaiting + 1 - - elseif train.state == defines.train_state.on_the_path - or train.state == defines.train_state.arrive_signal - or train.state == defines.train_state.arrive_station - or train.state == defines.train_state.wait_signal - then trainsDriving = trainsDriving + 1 - - elseif train.state == defines.train_state.manual_control - or train.state == defines.train_state.manual_control_stop - then trainsManual = trainsManual + 1 - - elseif train.state == defines.train_state.no_path - then trainsProblems = trainsProblems + 1 end + local surfaceName + if train.locomotives then + if train.locomotives.front_movers[1] then + surfaceName = train.locomotives.front_movers[1].surface.name + end + elseif train.locomotives.back_movers[1] then + surfaceName = train.locomotives.back_movers[1].surface.name + end + if surfaceName then + if train.state == defines.train_state.wait_station + or train.state == defines.train_state.destination_full + or train.state == defines.train_state.no_schedule + then + trainsWaiting[surfaceName] = (trainsWaiting[surfaceName] or 0) + 1 + elseif train.state == defines.train_state.on_the_path + or train.state == defines.train_state.arrive_signal + or train.state == defines.train_state.arrive_station + or train.state == defines.train_state.wait_signal + then + trainsDriving[surfaceName] = (trainsDriving[surfaceName] or 0) + 1 + elseif train.state == defines.train_state.manual_control + or train.state == defines.train_state.manual_control_stop + then + trainsManual[surfaceName] = (trainsManual[surfaceName] or 0) + 1 + elseif train.state == defines.train_state.no_path + then + trainsProblems[surfaceName] = (trainsProblems[surfaceName] or 0) + 1 + end + end end - return ("---trains-states---\n%d:%d:%d:%d"):format(trainsDriving,trainsManual,trainsProblems,trainsWaiting) + local stateParts = {} + stateParts[#stateParts + 1] = "---trains-states---\n" + for _, surface in pairs(game.surfaces) do + local surfaceName = surface.name + stateParts[#stateParts + 1] = ("%s:%d:%d:%d:%d"):format( + surfaceName, + trainsDriving[surfaceName] or 0, + trainsManual[surfaceName] or 0, + trainsProblems[surfaceName] or 0, + trainsWaiting[surfaceName] or 0) + end + return table.concat(stateParts, "\n") end ---@class trainStat trainStat = { trainID = 0, trainName = "", - lastInventory={}, - currentInventory={}, + lastInventory = {}, + currentInventory = {}, lastState = 0, lastStationUnitNumber = 0, currentStationUnitNumber = 0, - totalCargoCount = 0, + totalCargoCount = 0, totalCargo = {}, lastArrivalTime = 0, currentArrivalTime = 0, ---@type trip - trips = {} + trips = {} } ---@class trip - trip = { - startStation= {}, - endStation = {}, - timeTaken = 0 - } +trip = { + startStation = {}, + endStation = {}, + timeTaken = 0, + tick = 0 +} ---@param inv table[] ---@return table local function toLookup(inv) local t = {} for _, item in ipairs(inv) do - local key = item.name .. ":" .. (item.quality or 0) -- eindeutiger Key + local key = item.name .. ":" .. (item.quality or 0) -- eindeutiger Key t[key] = item.count end return t @@ -166,7 +191,6 @@ function onTrainStateChange(event) stat.trainName = GetTrainName(train) if event.train.state == defines.train_state.wait_station then - if not train.station then return end if train.station.unit_number == stat.lastStationUnitNumber then return end @@ -179,19 +203,21 @@ function onTrainStateChange(event) stat.currentArrivalTime = game.tick if stat.lastStationUnitNumber - and stat.currentStationUnitNumber - and (stat.lastStationUnitNumber ~= stat.currentStationUnitNumber) then - local tripIdentifier = tostring(stat.lastStationUnitNumber) .. tostring(stat.currentStationUnitNumber) - stat.trips[tripIdentifier] = { - startStation = game.get_entity_by_unit_number(stat.lastStationUnitNumber), - endStation = game.get_entity_by_unit_number(stat.currentStationUnitNumber), - timeTaken = stat.currentArrivalTime-stat.lastArrivalTime} + and stat.currentStationUnitNumber + and (stat.lastStationUnitNumber ~= stat.currentStationUnitNumber) then + local tripIdentifier = tostring(stat.lastStationUnitNumber) .. tostring(stat.currentStationUnitNumber) + stat.trips[tripIdentifier] = { + startStation = game.get_entity_by_unit_number(stat.lastStationUnitNumber), + endStation = game.get_entity_by_unit_number(stat.currentStationUnitNumber), + timeTaken = stat.currentArrivalTime - stat.lastArrivalTime, + time = game.tick + } end if stat.currentInventory and stat.lastInventory then - --Get Total Cargo - for key, value in pairs(inventoryDiff(stat.lastInventory,stat.currentInventory)) do - stat.totalCargoCount = (stat.totalCargoCount or 0) + math.abs(value.delta) + --Get Total Cargo + for key, value in pairs(inventoryDiff(stat.lastInventory, stat.currentInventory)) do + stat.totalCargoCount = (stat.totalCargoCount or 0) + math.abs(value.delta) end end @@ -199,14 +225,15 @@ function onTrainStateChange(event) end --log("inEvent") end + --Checks if trip is still valid by checking of st -function isTripValid(trip,tripID,trainID) +function isTripValid(trip, tripID, trainID) if trip.startStation == nil - or trip.endStation==nil - or trip.startStation.valid == false - or trip.endStation.valid == false then - --One station is nil so we delete this trip - log("Deleting trip"..tripID) + or trip.endStation == nil + or trip.startStation.valid == false + or trip.endStation.valid == false then + --One station is nil so we delete this trip + log("Deleting trip" .. tripID) storage.trainStats[trainID].trips[tripID] = nil return false end @@ -214,40 +241,55 @@ function isTripValid(trip,tripID,trainID) return true end +function SortTrips() + for trainID, stat in pairs(storage.trainStats) do + table.sort(stat.trips, function(a, b) + return a.tick > b.tick + end) + end +end + function GetTrainTripStats() + --SortTrips() local tripParts = {} local tripCount = 0 - tripParts[#tripParts+1] = "---train-trips---\n" - for trainID,stats in pairs(storage.trainStats) do - for tripIndex,trip in pairs(stats.trips or {}) do - if isTripValid(trip,tripIndex,trainID) then - tripParts[#tripParts+1] = - ("%d:%s:%s:%s:%d"):format( - trainID, - stats.trainName, - trip.startStation.backer_name, - trip.endStation.backer_name, - trip.timeTaken) - tripCount = tripCount + 1 + tripParts[#tripParts + 1] = "---train-trips---\n" + for trainID, stats in pairs(storage.trainStats) do + for tripIndex, trip in pairs(stats.trips or {}) do + if isTripValid(trip, tripIndex, trainID) then + tripParts[#tripParts + 1] = + ("%d:%s:%s:%s:%s:%d"):format( + trainID, + stats.trainName, + trip.startStation.surface.name, + trip.startStation.backer_name, + trip.endStation.backer_name, + trip.timeTaken) + tripCount = tripCount + 1 end + --To clean up, we delete this trip now + stats.trips[tripIndex] = nil end if #tripParts > 400 then - log("Sending at "..tripCount.." trips") - helpers.send_udp(udpAddress,table.concat(tripParts,"\n"),serverIndex) + log("Sending at " .. tripCount .. " trips") + helpers.send_udp(udpAddress, table.concat(tripParts, "\n"), serverIndex) tripParts = {} + tripParts[#tripParts + 1] = "---train-trips---\n" end end - log("Counted "..tripCount.." trips") + --tripParts[#tripParts+1] = "--train-fin--" + helpers.send_udp(udpAddress, table.concat(tripParts, "\n"), serverIndex) + log("Counted " .. tripCount .. " trips") --return table.concat(tripParts,"\n") end function GetTrainStatistics() local trainParts = {} - trainParts[#trainParts+1] = "---train-total-statistics---\n" + trainParts[#trainParts + 1] = "---train-total-statistics---\n" for trainID, stat in pairs(storage.trainStats) do - trainParts[#trainParts+1] = ("%d:%s:%d"):format(trainID,stat.trainName,stat.totalCargoCount or 0) + trainParts[#trainParts + 1] = ("%d:%s:%d"):format(trainID, stat.trainName, stat.totalCargoCount or 0) end - return table.concat(trainParts,"\n") + return table.concat(trainParts, "\n") end ---Purges stats for trains that no longer exist @@ -270,21 +312,20 @@ function PurgeDeadTrainStats(trainID) return false end - function SendTrainStats() if options.enableTrains then - ScanTrains() - PurgeDeadTrainStats() - local returnParts = {} - returnParts[#returnParts+1] = GetTrainPlayerKills() - returnParts[#returnParts+1] = GetTrainTotalKills() - returnParts[#returnParts+1] = GetTrainStates() - returnParts[#returnParts+1] = GetTrainStatistics() - returnParts[#returnParts+1] = GetTrainsInDepot() - if options.enableTrainTrips then - GetTrainTripStats() + ScanTrains() + PurgeDeadTrainStats() + local returnParts = {} + returnParts[#returnParts + 1] = GetTrainPlayerKills() + returnParts[#returnParts + 1] = GetTrainTotalKills() + returnParts[#returnParts + 1] = GetTrainStates() + returnParts[#returnParts + 1] = GetTrainStatistics() + returnParts[#returnParts + 1] = GetTrainsInDepot() + if options.enableTrainTrips then + GetTrainTripStats() + end + log("Sending Train statistics") + helpers.send_udp(udpAddress, table.concat(returnParts, "\n"), serverIndex) end - log("Sending Train statistics") - helpers.send_udp(udpAddress,table.concat(returnParts,"\n"),serverIndex) - end -end \ No newline at end of file +end