Private
Public Access
1
0

Add metrics combinator entity, GUI, and related functionality; enhance power and production stats tracking

Greatly improved train trip perfomance by culling table after sending

Fixed formatting in many places
This commit is contained in:
Jan Grießhaber
2026-01-06 02:02:30 +01:00
parent 81e831a236
commit d58c24b8d4
11 changed files with 822 additions and 284 deletions

View File

@@ -1,6 +1,6 @@
{ {
"Lua.workspace.userThirdParty": [ "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", "Lua.workspace.checkThirdParty": "ApplyInMemory",
"factorio.versions": [ "factorio.versions": [

View File

@@ -5,6 +5,7 @@ require("research-stats")
require("power-stats") require("power-stats")
require("logistic-network-stats") require("logistic-network-stats")
require("train-stats") require("train-stats")
require("metrics-combinator")
tickInterval = tonumber(settings.global["factorio-metrics-exporter-tick-interval"].value) or 300 tickInterval = tonumber(settings.global["factorio-metrics-exporter-tick-interval"].value) or 300
udpAddress = 52555 udpAddress = 52555
@@ -13,9 +14,11 @@ sendIndex = 0
serverIndex = 0 serverIndex = 0
scannedGrids = false scannedGrids = false
scannedLabs = false scannedLabs = false
scannedGenerators = false
autotrainGroupName = "" autotrainGroupName = ""
autotrainDepotName = "" autotrainDepotName = ""
options = { options = {
enableMod = false, enableMod = false,
enablePlayers = false, enablePlayers = false,
@@ -31,7 +34,7 @@ options = {
} }
script.on_init(function () script.on_init(function()
storage.electricGrids = {} storage.electricGrids = {}
storage.researchedTechnologies = {} storage.researchedTechnologies = {}
storage.playerDeathCount = {} storage.playerDeathCount = {}
@@ -46,9 +49,19 @@ script.on_init(function ()
storage.deconstructedEntities = {} storage.deconstructedEntities = {}
storage.trainStats = {} storage.trainStats = {}
storage.networkCache = {} storage.networkCache = {}
---@type LuaEntity[]
storage.powerGenerators = {}
---@type LuaTrain[] ---@type LuaTrain[]
storage.trains = {} storage.trains = {}
storage.powerStats = {}
storage.cargoStats = {}
storage.metrics = {}
storage.cliffsDestroyed = 0
storage.nuclearReactorDeaths = 0
storage.scannedGrids = false storage.scannedGrids = false
storage.scannedLabs = false storage.scannedLabs = false
@@ -67,14 +80,12 @@ script.on_init(function ()
options.enableTrainTrips = settings.global["factorio-metrics-exporter-export_train_trips"].value options.enableTrainTrips = settings.global["factorio-metrics-exporter-export_train_trips"].value
autotrainGroupName = settings.global["factorio-metrics-exporter-autotrain_group_name"].value autotrainGroupName = settings.global["factorio-metrics-exporter-autotrain_group_name"].value
autotrainDepotName = settings.global["factorio-metrics-exporter-autotrain_depot_name"].value autotrainDepotName = settings.global["factorio-metrics-exporter-autotrain_depot_name"].value
end) end)
script.on_load(function () script.on_load(function()
log("factorio-metrics-exporter: on_load") log("factorio-metrics-exporter: on_load")
log("tickInterval: "..tickInterval) log("tickInterval: " .. tickInterval)
log("udpAddress: "..udpAddress) log("udpAddress: " .. udpAddress)
options.enableMod = settings.global["factorio-metrics-exporter-enable"].value options.enableMod = settings.global["factorio-metrics-exporter-enable"].value
options.enableProduction = settings.global["factorio-metrics-exporter-export_production_stats"].value options.enableProduction = settings.global["factorio-metrics-exporter-export_production_stats"].value
@@ -105,21 +116,29 @@ script.on_configuration_changed(function()
storage.scannedLabs = storage.scannedLabs or false storage.scannedLabs = storage.scannedLabs or false
storage.playerDeathCause = storage.playerDeathCause or {} storage.playerDeathCause = storage.playerDeathCause or {}
storage.constructedEntites = storage.constructedEntites or {} storage.constructedEntites = storage.constructedEntites or {}
storage.deconstructedEntities = storage.deconstructedEntities or{} storage.deconstructedEntities = storage.deconstructedEntities or {}
storage.networkCache = storage.networkCache or {} storage.networkCache = storage.networkCache or {}
storage.trains = storage.trains or {} storage.trains = storage.trains or {}
---@type table<uint, trainStat> ---@type table<uint, trainStat>
storage.trainStats = storage.trainStats or {} 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() ScanNetworks()
ScanLabs() ScanLabs()
ScanTrains() ScanTrains()
ScanGenerators()
end end
) )
script.on_event(defines.events.on_runtime_mod_setting_changed, function(event) 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 if event.setting == "factorio-metrics-exporter-tick-interval" then
tickInterval = settings.global["factorio-metrics-exporter-tick-interval"].value tickInterval = settings.global["factorio-metrics-exporter-tick-interval"].value
end 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 if event.setting == "factorio-metrics-exporter-autotrain_depot_name" then
autotrainDepotName = settings.global["factorio-metrics-exporter-autotrain_depot_name"].value autotrainDepotName = settings.global["factorio-metrics-exporter-autotrain_depot_name"].value
end 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 autotrainGroupName = settings.global["factorio-metrics-exporter-autotrain_group_name"].value
end end
if event.setting == "factorio-metrics-exporter-export_train_trips" then if event.setting == "factorio-metrics-exporter-export_train_trips" then
options.enableTrainTrips = settings.global["factorio-metrics-exporter-export_train_trips"].value options.enableTrainTrips = settings.global["factorio-metrics-exporter-export_train_trips"].value
end end
end) end)
@@ -182,55 +200,56 @@ script.on_event(defines.events.on_player_died, function(event)
local victim_index = event.player_index local victim_index = event.player_index
local killerName = killer.name local killerName = killer.name
local victimName = game.players[victim_index].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] =
storage.playerKillCount[killer_index] or {} storage.playerKillCount[killer_index] or {}
storage.playerKillCount[killer_index][victim_index] = 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
end end
--Log cause of player death --Log cause of player death
if event.cause and event.cause.type then if event.cause and event.cause.name then
storage.playerDeathCause[event.player_index] = storage.playerDeathCause[event.player_index] =
storage.playerDeathCause[event.player_index] or {} storage.playerDeathCause[event.player_index] or {}
storage.playerDeathCause[event.player_index][event.cause.type] = storage.playerDeathCause[event.player_index][event.cause.name] =
(storage.playerDeathCause[event.player_index][event.cause.type] or 0) + 1 (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 end
--Log player death count --Log player death count
storage.playerDeathCount[event.player_index] = (storage.playerDeathCount[event.player_index] or 0) + 1 storage.playerDeathCount[event.player_index] = (storage.playerDeathCount[event.player_index] or 0) + 1
end) end)
function SendGameStats() function SendGameStats()
if options.enablePlayers then if options.enablePlayers then
local returnParts = {} local returnParts = {}
GetMods() GetMods()
returnParts[#returnParts+1] = GetMapSeed() returnParts[#returnParts + 1] = GetMapSeed()
returnParts[#returnParts+1] = GetRocketsLaunched() returnParts[#returnParts + 1] = GetEvolution()
returnParts[#returnParts+1] = GetPlayerTime() returnParts[#returnParts + 1] = GetRocketsLaunched()
returnParts[#returnParts+1] = GetPlayerDeaths() returnParts[#returnParts + 1] = GetTotalPlayTime()
returnParts[#returnParts+1] = GetPlayerDeathCauses() returnParts[#returnParts + 1] = GetPlayerTime()
returnParts[#returnParts+1] = GetPlayerKills() returnParts[#returnParts + 1] = GetPlayerDeaths()
returnParts[#returnParts+1] = GetPlayerEntityStats() returnParts[#returnParts + 1] = GetPlayerDeathCauses()
helpers.send_udp(udpAddress, table.concat(returnParts, "\n"), serverIndex) returnParts[#returnParts + 1] = GetPlayerKills()
returnParts[#returnParts + 1] = GetPlayerEntityStats()
helpers.send_udp(udpAddress, table.concat(returnParts, "\n"), serverIndex)
end end
end end
function SendAll(event) function SendAll(event)
if (isInitialized == false) then
if(isInitialized == false) then
if game.is_multiplayer() then if game.is_multiplayer() then
serverIndex = 0 serverIndex = 0
log("Loaded game as mulitplayer") log("Loaded game as mulitplayer")
else else
log("Loaded game as singleplayer") log("Loaded game as singleplayer")
serverIndex = 1 serverIndex = 1
end end
end end
isInitialized = true isInitialized = true
@@ -241,22 +260,30 @@ function SendAll(event)
scannedGrids = true scannedGrids = true
end end
if scannedGenerators == false then
storage.powerGenerators = {}
ScanGenerators()
scannedGenerators = true
end
if scannedLabs == false then if scannedLabs == false then
ScanLabs() ScanLabs()
scannedLabs = true scannedLabs = true
end end
if options.enableMod==true then if options.enableMod == true then
local interval = math.max(1, math.floor(tickInterval / 10))
local interval = math.max(1, math.floor(tickInterval / 10)) if event.tick % interval ~= 0 then return end
if event.tick % interval ~= 0 then return end
sendIndex = (sendIndex % 10) + 1 sendIndex = (sendIndex % 10) + 1
if sendIndex == 1 then SendProductionStats() end if sendIndex == 1 then SendProductionStats() end
if sendIndex == 2 then SendPollutionStats() end if sendIndex == 2 then SendPollutionStats() end
if sendIndex == 3 then SendKillStats() end if sendIndex == 3 then SendKillStats() end
if sendIndex == 4 then SendFluidProductionStats() end if sendIndex == 4 then SendFluidProductionStats() end
if sendIndex == 5 then SendBuildStats() 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 == 7 then SendLogisticStats() end
if sendIndex == 8 then SendPowerStats() end if sendIndex == 8 then SendPowerStats() end
if sendIndex == 9 then SendGameStats() end if sendIndex == 9 then SendGameStats() end
@@ -272,24 +299,41 @@ function UpdateStorage(event)
if event.entity.type == "electric-pole" then if event.entity.type == "electric-pole" then
AddPowerPole(event) AddPowerPole(event)
end end
if event.entity.type == "generator"
or event.entity.type == "fusion-generator"
or event.entity.type == "solar-panel" then
AddGenerator(event)
end
end end
function RemoveStorage(event) function RemoveStorage(event)
if not event then return end if not event then return end
if event.entity.type =="lab" then if event.entity.type == "lab" then
RemoveLab(event) RemoveLab(event)
end end
if event.entity.type == "electric-pole" then if event.entity.type == "electric-pole" then
RemovePowerPole(event) RemovePowerPole(event)
end 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) --log(event.entity.name)
if event.entity.name == "crash-site-spaceship" then if event.entity.name == "crash-site-spaceship" then
--log(event.name) --log(event.name)
if event.name == defines.events.on_player_mined_entity then if event.name == defines.events.on_player_mined_entity then
--log("in ban call") --log("in ban call")
if settings.global["factorio-metrics-exporter-enable_denkmalschutz"].value == true then if settings.global["factorio-metrics-exporter-enable_denkmalschutz"].value == true then
game.ban_player(event.player_index,"You violated the rules of DENKMALSCHUTZ!!!") game.ban_player(event.player_index, "You violated the rules of DENKMALSCHUTZ!!!")
end 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 end
end end
@@ -300,68 +344,91 @@ function CreateEntity(event)
--Event is PlayerPlaced --Event is PlayerPlaced
if event.name == defines.events.on_built_entity then 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] = 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
end end
--Event is RobotPlaced --Event is RobotPlaced
if event.name == defines.events.on_robot_built_entity then 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 if event.entity.last_user then
local lastUser = event.entity.last_user.index local lastUser = event.entity.last_user.index
storage.constructedEntites[lastUser] = storage.constructedEntites[lastUser] or {} storage.constructedEntites[lastUser] = storage.constructedEntites[lastUser] or {}
storage.constructedEntites[lastUser][event.entity.name] = (storage.constructedEntites[lastUser][event.entity.name] or 0) + 1 storage.constructedEntites[lastUser][event.entity.name] = (storage.constructedEntites[lastUser][event.entity.name] or 0) +
1
end
end end
end end
end
--Event is spaceplatform build --Event is spaceplatform build
if event.name == defines.events.on_space_platform_built_entity then if event.name == defines.events.on_space_platform_built_entity then
if event.entity.name ~= "entity-ghost" then if event.entity.name ~= "entity-ghost"
if event.entity.last_user then and event.entity.name ~= "tile-ghost"
local lastUser = event.entity.last_user.index and event.entity.name ~= "deconstructible_tile_proxy" then
storage.constructedEntites[lastUser] = storage.constructedEntites[lastUser] or {} if event.entity.last_user then
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 end
end
UpdateStorage(event) UpdateStorage(event)
CreateMetricsEntry(event)
end end
function RemoveEntity(event) function RemoveEntity(event)
if event.name == defines.events.on_player_mined_entity then if event.name == defines.events.on_player_mined_entity then
storage.deconstructedEntities[event.player_index] = storage.deconstructedEntities[event.player_index] or {} if event.entity.name ~= "entity-ghost"
storage.deconstructedEntities[event.player_index][event.entity.name] = (storage.deconstructedEntities[event.player_index][event.entity.name] or 0) + 1 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 end
if event.name == defines.events.on_robot_mined_entity then 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 if event.entity.last_user then
local lastUser = event.entity.last_user.index local lastUser = event.entity.last_user.index
storage.deconstructedEntities[lastUser] = storage.deconstructedEntities[lastUser] or {} storage.deconstructedEntities[lastUser] = storage.deconstructedEntities[lastUser] or {}
storage.deconstructedEntities[lastUser][event.entity.name] = (storage.deconstructedEntities[lastUser][event.entity.name] or 0) + 1 storage.deconstructedEntities[lastUser][event.entity.name] = (storage.deconstructedEntities[lastUser][event.entity.name] or 0) +
end 1
end end
end
end end
if event.name == defines.events.on_space_platform_mined_entity then 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 if event.entity.last_user then
local lastUser = event.entity.last_user.index local lastUser = event.entity.last_user.index
storage.deconstructedEntities[lastUser] = storage.deconstructedEntities[lastUser] or {} storage.deconstructedEntities[lastUser] = storage.deconstructedEntities[lastUser] or {}
storage.deconstructedEntities[lastUser][event.entity.name] = (storage.deconstructedEntities[lastUser][event.entity.name] or 0) + 1 storage.deconstructedEntities[lastUser][event.entity.name] = (storage.deconstructedEntities[lastUser][event.entity.name] or 0) +
1
end
end end
end end
end
if event.name == defines.events.on_entity_died then if event.name == defines.events.on_entity_died then
end end
CheckReactor(event)
RemoveStorage(event) RemoveStorage(event)
onMetricsCombinatorDied(event)
onMetricsCombinatorMined(event)
end end
script.on_event(defines.events.on_tick, SendAll) 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_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_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_built_entity, CreateEntity)
script.on_event(defines.events.on_robot_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_space_platform_built_entity, CreateEntity)
script.on_event(defines.events.on_player_mined_entity,RemoveEntity) 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_robot_mined_entity, RemoveEntity)
script.on_event(defines.events.on_space_platform_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_entity_died, RemoveEntity)
script.on_event(defines.events.on_train_changed_state,onTrainStateChange) 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)

View File

@@ -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
})

View File

@@ -2,93 +2,111 @@
function GetMods() function GetMods()
local mods = script.active_mods local mods = script.active_mods
local modstring = "---mod-info---\n" local modstring = "---mod-info---\n"
for k,v in pairs(mods) do for k, v in pairs(mods) do
modstring = modstring .. ("%s:%s\n"):format(k,v) modstring = modstring .. ("%s:%s\n"):format(k, v)
end end
helpers.send_udp(udpAddress, modstring,serverIndex) helpers.send_udp(udpAddress, modstring, serverIndex)
end end
function GetPlayerColors() function GetPlayerColors()
local colorParts = {} local colorParts = {}
colorParts[#colorParts+1] = "---player-colors---\n" colorParts[#colorParts + 1] = "---player-colors---\n"
for index, player in pairs(game.players) do for index, player in pairs(game.players) do
local colorSettings = player.color 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 end
return table.concat(colorParts,"\n") return table.concat(colorParts, "\n")
end end
function GetPlayerKills() function GetPlayerKills()
local killParts = {} local killParts = {}
killParts[#killParts+1] = "---player-kills---\n" killParts[#killParts + 1] = "---player-kills---\n"
for killerIndex, victims in pairs(storage.playerKillCount) do for killerIndex, victims in pairs(storage.playerKillCount) do
for victimIndex, kills in pairs(victims) do for victimIndex, kills in pairs(victims) do
local killerName = game.players[killerIndex].name local killerName = game.players[killerIndex].name
local victimName = game.players[victimIndex].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
end end
return table.concat(killParts,"\n") return table.concat(killParts, "\n")
end end
function GetPlayerEntityStats() function GetPlayerEntityStats()
local entityParts = {} local entityParts = {}
entityParts[#entityParts+1] = "---player-build-stats---" entityParts[#entityParts + 1] = "---player-build-stats---"
for playerIndex, items in pairs(storage.constructedEntites) do for playerIndex, items in pairs(storage.constructedEntites) do
local playerName = game.players[playerIndex].name local playerName = game.players[playerIndex].name
for itemName, itemCount in pairs(items) do 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
end end
for playerIndex, items in pairs(storage.deconstructedEntities) do for playerIndex, items in pairs(storage.deconstructedEntities) do
local playerName = game.players[playerIndex].name local playerName = game.players[playerIndex].name
for itemName, itemCount in pairs(items) do 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
end end
return table.concat(entityParts,"\n") return table.concat(entityParts, "\n")
end end
function GetMapSeed() 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 end
function GetRocketsLaunched() function GetRocketsLaunched()
return("---rocket-launches---\n%d"):format(game.forces["player"].rockets_launched) return ("---rocket-launches---\n%d"):format(game.forces["player"].rockets_launched)
end end
---Concats all player online times into a single string ---Concats all player online times into a single string
---Takes all players that ever visited the server into account ---Takes all players that ever visited the server into account
---@return string ---@return string
function GetPlayerTime() function GetPlayerTime()
local timeParts = {} local timeParts = {}
timeParts[#timeParts+1] = "---player-times---\n" timeParts[#timeParts + 1] = "---player-times---\n"
for _,player in pairs(game.players) do for _, player in pairs(game.players) do
timeParts[#timeParts+1] = ("%s:%d:%d"):format(player.name, player.index, player.online_time) timeParts[#timeParts + 1] = ("%s:%d:%d"):format(player.name, player.index, player.online_time)
end end
return table.concat(timeParts, "\n") return table.concat(timeParts, "\n")
end end
---comment ---comment
---@return string ---@return string
function GetPlayerDeaths() function GetPlayerDeaths()
local deathParts = {} local deathParts = {}
deathParts[#deathParts+1] = "---player-deaths---\n" deathParts[#deathParts + 1] = "---player-deaths---\n"
for _,player in pairs(game.players) do for _, player in pairs(game.players) do
deathParts[#deathParts+1] = ("%s:%d:%d"):format(player.name, player.index, storage.playerDeathCount[player.index]or 0) deathParts[#deathParts + 1] = ("%s:%d:%d"):format(player.name, player.index,
end storage.playerDeathCount[player.index] or 0)
return table.concat(deathParts, "\n") end
return table.concat(deathParts, "\n")
end end
function GetPlayerDeathCauses() function GetPlayerDeathCauses()
local deathParts = {} local deathParts = {}
deathParts[#deathParts+1] = "---player-death-cause---\n" deathParts[#deathParts + 1] = "---player-death-cause---\n"
for playerIndex,deathCauses in pairs(storage.playerDeathCause) do for playerIndex, deathCauses in pairs(storage.playerDeathCause) do
for causeName, causeCount in pairs(deathCauses) 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
end end
return table.concat(deathParts,"\n") 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 end

View File

@@ -1,6 +1,6 @@
{ {
"name": "factorio-metrics-exporter", "name": "factorio-metrics-exporter",
"version": "0.2.6", "version": "0.3.0",
"title": "Prometheus Metrics Exporter", "title": "Prometheus Metrics Exporter",
"author": "Jan Grießhaber", "author": "Jan Grießhaber",
"contact": "jan@griesshaber.systems", "contact": "jan@griesshaber.systems",

175
metrics-combinator.lua Normal file
View File

@@ -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

View File

@@ -1,53 +1,66 @@
function SendPollutionStats() function SendPollutionStats()
if options.enablePollution then if options.enablePollution then
local pollutionParts = {} local pollutionParts = {}
pollutionParts[#pollutionParts+1] = "---pollution-stats---\n" pollutionParts[#pollutionParts + 1] = "---pollution-stats---\n"
for _,surface in pairs(game.surfaces) do for _, surface in pairs(game.surfaces) do
local surface_name = surface.name local surface_name = surface.name
local pollution_input = game.surfaces[surface_name].pollution_statistics.input_counts local pollution_input = game.surfaces[surface_name].pollution_statistics.input_counts
local pollution_output = game.surfaces[surface_name].pollution_statistics.output_counts local pollution_output = game.surfaces[surface_name].pollution_statistics.output_counts
if surface.platform ~= nil then if surface.platform ~= nil then
surface_name = surface.platform.name 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 end
helpers.send_udp(udpAddress, table.concat(pollutionParts, "\n"), serverIndex)
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 end
helpers.send_udp(udpAddress, table.concat(pollutionParts,"\n"),serverIndex)
end
end end
function SendKillStats() function SendKillStats()
if options.enableKills then if options.enableKills then
local killParts = {} local killParts = {}
killParts[#killParts+1] = "---kill-stats---\n" killParts[#killParts + 1] = "---kill-stats---\n"
for _,surface in pairs(game.surfaces) do for _, surface in pairs(game.surfaces) do
local surface_name = surface.name local surface_name = surface.name
local kill_input = game.forces["player"].get_kill_count_statistics(surface_name).input_counts 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 local kill_output = game.forces["player"].get_kill_count_statistics(surface_name).output_counts
if surface.platform ~= nil then if surface.platform ~= nil then
surface_name = surface.platform.name 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 end
helpers.send_udp(udpAddress, table.concat(killParts, "\n"), serverIndex)
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 end
helpers.send_udp(udpAddress, table.concat(killParts,"\n"),serverIndex)
end end
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 end

View File

@@ -41,7 +41,7 @@ function ScanNetworks()
storage.networkCache = {} storage.networkCache = {}
for _, surface in pairs(game.surfaces) do 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 if pole.valid and pole.electric_network_id then
storage.representativePoles[pole.unit_number] = pole storage.representativePoles[pole.unit_number] = pole
storage.networkCache[pole.electric_network_id] = pole storage.networkCache[pole.electric_network_id] = pole
@@ -50,25 +50,143 @@ function ScanNetworks()
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() function SendPowerStats()
if options.enablePower then if options.enablePower then
local possiblePower = GetPossiblePower()
local powerPart = {} local powerPart = {}
powerPart[#powerPart+1] = "---power-stats---\n" powerPart[#powerPart + 1] = "---power-stats---\n"
for _, pole in pairs(GetNetworks()) do for _, pole in pairs(GetNetworks()) do
if pole.valid and pole.type == "electric-pole" then if pole.valid and pole.type == "electric-pole" then
local input = pole.electric_network_statistics.input_counts local input = pole.electric_network_statistics.input_counts
local output = pole.electric_network_statistics.output_counts local output = pole.electric_network_statistics.output_counts
local capacity = pole.electric_network_statistics.storage_counts
local surfaceName = pole.surface.name local surfaceName = pole.surface.name
for item,value in pairs(input) do for item, value in pairs(input) do
powerPart[#powerPart+1] = ("%s:%d:in:%s:%d"):format(surfaceName, pole.electric_network_id, item, value) powerPart[#powerPart + 1] = ("%s:%d:in:%s:%d"):format(surfaceName, pole.electric_network_id, item,
value)
end end
for item, value in pairs(output) do 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 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
end end

View File

@@ -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() function SendProductionStats()
if options.enableProduction then if options.enableProduction then
local productionParts = {} local productionParts = {}
@@ -5,6 +29,9 @@ function SendProductionStats()
for _,surface in pairs(game.surfaces) do for _,surface in pairs(game.surfaces) do
local surfaceName = surface.name local surfaceName = surface.name
--TODO
--local test = GetProductionWithQuality(surface)
local inputStats = game.forces["player"].get_item_production_statistics(surfaceName).input_counts local inputStats = game.forces["player"].get_item_production_statistics(surfaceName).input_counts
local outputStats = game.forces["player"].get_item_production_statistics(surfaceName).output_counts local outputStats = game.forces["player"].get_item_production_statistics(surfaceName).output_counts
@@ -81,3 +108,12 @@ function SendBuildStats()
helpers.send_udp(udpAddress, table.concat(buildParts, "\n"), serverIndex) helpers.send_udp(udpAddress, table.concat(buildParts, "\n"), serverIndex)
end end
end 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

View File

@@ -12,8 +12,6 @@ function ScanLabs()
end end
end end
function UpdateLabs(event) function UpdateLabs(event)
local lab = event.entity local lab = event.entity
if lab and lab.valid then if lab and lab.valid then
@@ -28,8 +26,6 @@ function RemoveLab(event)
end end
end end
function GetEstimatedResearchTime() function GetEstimatedResearchTime()
-- Base time in seconds for research -- Base time in seconds for research
local playerForce = game.forces["player"] local playerForce = game.forces["player"]
@@ -40,7 +36,8 @@ function GetEstimatedResearchTime()
local researchName = currentResearch.name.."-"..currentResearch.level local researchName = currentResearch.name.."-"..currentResearch.level
local researchTotalCost = currentResearch.research_unit_count * (currentResearch.research_unit_energy/60) 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 remainingPercentage = 1-math.min(playerForce.research_progress,1)
local remainingUnits = remainingPercentage*researchTotalCost local remainingUnits = remainingPercentage*researchTotalCost
@@ -81,9 +78,18 @@ function UpdateLabInfos()
storage.totalResearchSpeed = totalSpeed storage.totalResearchSpeed = totalSpeed
end 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() function SendResearchStats()
if options.enableResearch == true then if options.enableResearch == true then
UpdateLabInfos()
local researchTimeInfo = GetEstimatedResearchTime() local researchTimeInfo = GetEstimatedResearchTime()
if researchTimeInfo then if researchTimeInfo then
helpers.send_udp(udpAddress, researchTimeInfo, serverIndex) helpers.send_udp(udpAddress, researchTimeInfo, serverIndex)

View File

@@ -1,6 +1,6 @@
function ScanTrains() function ScanTrains()
storage.trains = {} 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 storage.trains[train.id] = train
end end
end end
@@ -12,7 +12,7 @@ function GetTrainName(train)
return train.locomotives.front_movers[1].backer_name or "" return train.locomotives.front_movers[1].backer_name or ""
elseif train.locomotives.back_movers[1] then elseif train.locomotives.back_movers[1] then
return train.locomotives.back_movers[1].backer_name or "" return train.locomotives.back_movers[1].backer_name or ""
end end
return "" return ""
end end
@@ -20,70 +20,94 @@ function GetTrainsInDepot()
local trainsInDepot = 0 local trainsInDepot = 0
for trainsID, train in pairs(storage.trains) do for trainsID, train in pairs(storage.trains) do
if train.state == defines.train_state.wait_station if train.state == defines.train_state.wait_station
and train.station and train.station.backer_name == autotrainDepotName then and train.station and train.station.backer_name == autotrainDepotName then
trainsInDepot = trainsInDepot + 1 trainsInDepot = trainsInDepot + 1
end end
end end
local trainsInGroup = #game.train_manager.get_trains({group=autotrainGroupName}) local trainsInGroup = #game.train_manager.get_trains({ group = autotrainGroupName })
return ("---autotrain-stats---\n%d:%d"):format(trainsInDepot,trainsInGroup) return ("---autotrain-stats---\n%d:%d"):format(trainsInDepot, trainsInGroup)
end end
function GetTrainPlayerKills() function GetTrainPlayerKills()
local trainKills = {} local trainKills = {}
trainKills[#trainKills+1] = "---train-player-kills---" trainKills[#trainKills + 1] = "---train-player-kills---"
---@type LuaTrain ---@type LuaTrain
for _, train in pairs(storage.trains ) do for _, train in pairs(storage.trains) do
for killedPlayerID,killedPlayerCount in pairs(train.killed_players) 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) trainKills[#trainKills + 1] = ("%s:%s:%s:%s:%d"):format(train.id, GetTrainName(train), killedPlayerID,
game.players[killedPlayerID].name, killedPlayerCount)
end end
end end
return table.concat(trainKills,"\n") return table.concat(trainKills, "\n")
end end
function GetTrainTotalKills() function GetTrainTotalKills()
local trainKills = {} local trainKills = {}
trainKills[#trainKills+1] = "---train-total-kills---" trainKills[#trainKills + 1] = "---train-total-kills---"
---@type LuaTrain ---@type LuaTrain
for _, train in pairs(storage.trains) do 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 end
return table.concat(trainKills,"\n") return table.concat(trainKills, "\n")
end end
--TODO: Seperate by surface
function GetTrainStates() function GetTrainStates()
local trainsDriving = 0 local trainsWaiting = {}
local trainsWaiting = 0 local trainsDriving = {}
local trainsProblems = 0 local trainsManual = {}
local trainsManual = 0 local trainsProblems = {}
for _, train in pairs(storage.trains) do for _, train in pairs(storage.trains) do
if train.state == defines.train_state.wait_station local surfaceName
or train.state == defines.train_state.destination_full if train.locomotives then
or train.state == defines.train_state.no_schedule if train.locomotives.front_movers[1] then
then trainsWaiting = trainsWaiting + 1 surfaceName = train.locomotives.front_movers[1].surface.name
end
elseif train.state == defines.train_state.on_the_path elseif train.locomotives.back_movers[1] then
or train.state == defines.train_state.arrive_signal surfaceName = train.locomotives.back_movers[1].surface.name
or train.state == defines.train_state.arrive_station end
or train.state == defines.train_state.wait_signal if surfaceName then
then trainsDriving = trainsDriving + 1 if train.state == defines.train_state.wait_station
or train.state == defines.train_state.destination_full
elseif train.state == defines.train_state.manual_control or train.state == defines.train_state.no_schedule
or train.state == defines.train_state.manual_control_stop then
then trainsManual = trainsManual + 1 trainsWaiting[surfaceName] = (trainsWaiting[surfaceName] or 0) + 1
elseif train.state == defines.train_state.on_the_path
elseif train.state == defines.train_state.no_path or train.state == defines.train_state.arrive_signal
then trainsProblems = trainsProblems + 1 end 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 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 end
---@class trainStat ---@class trainStat
trainStat = { trainStat = {
trainID = 0, trainID = 0,
trainName = "", trainName = "",
lastInventory={}, lastInventory = {},
currentInventory={}, currentInventory = {},
lastState = 0, lastState = 0,
lastStationUnitNumber = 0, lastStationUnitNumber = 0,
currentStationUnitNumber = 0, currentStationUnitNumber = 0,
@@ -95,18 +119,19 @@ trainStat = {
trips = {} trips = {}
} }
---@class trip ---@class trip
trip = { trip = {
startStation= {}, startStation = {},
endStation = {}, endStation = {},
timeTaken = 0 timeTaken = 0,
} tick = 0
}
---@param inv table[] ---@param inv table[]
---@return table<string, table> ---@return table<string, table>
local function toLookup(inv) local function toLookup(inv)
local t = {} local t = {}
for _, item in ipairs(inv) do 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 t[key] = item.count
end end
return t return t
@@ -166,7 +191,6 @@ function onTrainStateChange(event)
stat.trainName = GetTrainName(train) stat.trainName = GetTrainName(train)
if event.train.state == defines.train_state.wait_station then if event.train.state == defines.train_state.wait_station then
if not train.station then return end if not train.station then return end
if train.station.unit_number == stat.lastStationUnitNumber then return end if train.station.unit_number == stat.lastStationUnitNumber then return end
@@ -179,19 +203,21 @@ function onTrainStateChange(event)
stat.currentArrivalTime = game.tick stat.currentArrivalTime = game.tick
if stat.lastStationUnitNumber if stat.lastStationUnitNumber
and stat.currentStationUnitNumber and stat.currentStationUnitNumber
and (stat.lastStationUnitNumber ~= stat.currentStationUnitNumber) then and (stat.lastStationUnitNumber ~= stat.currentStationUnitNumber) then
local tripIdentifier = tostring(stat.lastStationUnitNumber) .. tostring(stat.currentStationUnitNumber) local tripIdentifier = tostring(stat.lastStationUnitNumber) .. tostring(stat.currentStationUnitNumber)
stat.trips[tripIdentifier] = { stat.trips[tripIdentifier] = {
startStation = game.get_entity_by_unit_number(stat.lastStationUnitNumber), startStation = game.get_entity_by_unit_number(stat.lastStationUnitNumber),
endStation = game.get_entity_by_unit_number(stat.currentStationUnitNumber), endStation = game.get_entity_by_unit_number(stat.currentStationUnitNumber),
timeTaken = stat.currentArrivalTime-stat.lastArrivalTime} timeTaken = stat.currentArrivalTime - stat.lastArrivalTime,
time = game.tick
}
end end
if stat.currentInventory and stat.lastInventory then if stat.currentInventory and stat.lastInventory then
--Get Total Cargo --Get Total Cargo
for key, value in pairs(inventoryDiff(stat.lastInventory,stat.currentInventory)) do for key, value in pairs(inventoryDiff(stat.lastInventory, stat.currentInventory)) do
stat.totalCargoCount = (stat.totalCargoCount or 0) + math.abs(value.delta) stat.totalCargoCount = (stat.totalCargoCount or 0) + math.abs(value.delta)
end end
end end
@@ -199,14 +225,15 @@ function onTrainStateChange(event)
end end
--log("inEvent") --log("inEvent")
end end
--Checks if trip is still valid by checking of st --Checks if trip is still valid by checking of st
function isTripValid(trip,tripID,trainID) function isTripValid(trip, tripID, trainID)
if trip.startStation == nil if trip.startStation == nil
or trip.endStation==nil or trip.endStation == nil
or trip.startStation.valid == false or trip.startStation.valid == false
or trip.endStation.valid == false then or trip.endStation.valid == false then
--One station is nil so we delete this trip --One station is nil so we delete this trip
log("Deleting trip"..tripID) log("Deleting trip" .. tripID)
storage.trainStats[trainID].trips[tripID] = nil storage.trainStats[trainID].trips[tripID] = nil
return false return false
end end
@@ -214,40 +241,55 @@ function isTripValid(trip,tripID,trainID)
return true return true
end 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() function GetTrainTripStats()
--SortTrips()
local tripParts = {} local tripParts = {}
local tripCount = 0 local tripCount = 0
tripParts[#tripParts+1] = "---train-trips---\n" tripParts[#tripParts + 1] = "---train-trips---\n"
for trainID,stats in pairs(storage.trainStats) do for trainID, stats in pairs(storage.trainStats) do
for tripIndex,trip in pairs(stats.trips or {}) do for tripIndex, trip in pairs(stats.trips or {}) do
if isTripValid(trip,tripIndex,trainID) then if isTripValid(trip, tripIndex, trainID) then
tripParts[#tripParts+1] = tripParts[#tripParts + 1] =
("%d:%s:%s:%s:%d"):format( ("%d:%s:%s:%s:%s:%d"):format(
trainID, trainID,
stats.trainName, stats.trainName,
trip.startStation.backer_name, trip.startStation.surface.name,
trip.endStation.backer_name, trip.startStation.backer_name,
trip.timeTaken) trip.endStation.backer_name,
tripCount = tripCount + 1 trip.timeTaken)
tripCount = tripCount + 1
end end
--To clean up, we delete this trip now
stats.trips[tripIndex] = nil
end end
if #tripParts > 400 then if #tripParts > 400 then
log("Sending at "..tripCount.." trips") log("Sending at " .. tripCount .. " trips")
helpers.send_udp(udpAddress,table.concat(tripParts,"\n"),serverIndex) helpers.send_udp(udpAddress, table.concat(tripParts, "\n"), serverIndex)
tripParts = {} tripParts = {}
tripParts[#tripParts + 1] = "---train-trips---\n"
end end
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") --return table.concat(tripParts,"\n")
end end
function GetTrainStatistics() function GetTrainStatistics()
local trainParts = {} local trainParts = {}
trainParts[#trainParts+1] = "---train-total-statistics---\n" trainParts[#trainParts + 1] = "---train-total-statistics---\n"
for trainID, stat in pairs(storage.trainStats) do 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 end
return table.concat(trainParts,"\n") return table.concat(trainParts, "\n")
end end
---Purges stats for trains that no longer exist ---Purges stats for trains that no longer exist
@@ -270,21 +312,20 @@ function PurgeDeadTrainStats(trainID)
return false return false
end end
function SendTrainStats() function SendTrainStats()
if options.enableTrains then if options.enableTrains then
ScanTrains() ScanTrains()
PurgeDeadTrainStats() PurgeDeadTrainStats()
local returnParts = {} local returnParts = {}
returnParts[#returnParts+1] = GetTrainPlayerKills() returnParts[#returnParts + 1] = GetTrainPlayerKills()
returnParts[#returnParts+1] = GetTrainTotalKills() returnParts[#returnParts + 1] = GetTrainTotalKills()
returnParts[#returnParts+1] = GetTrainStates() returnParts[#returnParts + 1] = GetTrainStates()
returnParts[#returnParts+1] = GetTrainStatistics() returnParts[#returnParts + 1] = GetTrainStatistics()
returnParts[#returnParts+1] = GetTrainsInDepot() returnParts[#returnParts + 1] = GetTrainsInDepot()
if options.enableTrainTrips then if options.enableTrainTrips then
GetTrainTripStats() GetTrainTripStats()
end end
log("Sending Train statistics") log("Sending Train statistics")
helpers.send_udp(udpAddress,table.concat(returnParts,"\n"),serverIndex) helpers.send_udp(udpAddress, table.concat(returnParts, "\n"), serverIndex)
end end
end end