diff --git a/.vscode/settings.json b/.vscode/settings.json index d4784a5..7faf9ba 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,6 @@ { "Lua.workspace.userThirdParty": [ - "c:\\Users\\jangr\\AppData\\Roaming\\Code\\User\\workspaceStorage\\340ead7f10d9ae8505ebc6dec42b1e88\\justarandomgeek.factoriomod-debug\\sumneko-3rd" + "c:\\Users\\jangr\\AppData\\Roaming\\Code\\User\\workspaceStorage\\d5b7556f3ceaa76c3801170429d1766d\\justarandomgeek.factoriomod-debug\\sumneko-3rd" ], "Lua.workspace.checkThirdParty": "ApplyInMemory", "factorio.versions": [ diff --git a/control.lua b/control.lua index 0c4f6ae..9ab266a 100644 --- a/control.lua +++ b/control.lua @@ -4,6 +4,7 @@ require("pollution-stats") require("research-stats") require("power-stats") require("logistic-network-stats") +require("train-stats") tickInterval = tonumber(settings.global["factorio-metrics-exporter-tick-interval"].value) or 300 udpAddress = 52555 @@ -37,6 +38,13 @@ script.on_init(function () storage.labs = {} storage.playerKillCount = {} storage.representativePoles = {} + storage.playerDeathCause = {} + storage.constructedEntites = {} + storage.deconstructedEntities = {} + storage.trainStats = {} + + ---@type LuaTrain[] + storage.trains = {} storage.scannedGrids = false storage.scannedLabs = false @@ -83,8 +91,15 @@ script.on_configuration_changed(function() storage.representativePoles = storage.representativePoles or {} storage.scannedGrids = storage.scannedGrids or false storage.scannedLabs = storage.scannedLabs or false + storage.playerDeathCause = storage.playerDeathCause or {} + storage.constructedEntites = storage.constructedEntites or {} + storage.deconstructedEntities = storage.deconstructedEntities or{} + storage.trains = storage.trains or {} + ---@type table + storage.trainStats = storage.trainStats or {} ScanNetworks() ScanLabs() + ScanTrains() end ) @@ -134,12 +149,15 @@ end) script.on_event(defines.events.on_player_died, function(event) + --Log player cause by player if event.cause and event.cause.type == "character" then local killer = event.cause.player if killer then local killer_index = killer.index local victim_index = event.player_index - log(("Player ID %d killed player ID %d"):format(killer_index,victim_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)) storage.playerKillCount[killer_index] = storage.playerKillCount[killer_index] or {} @@ -148,12 +166,22 @@ script.on_event(defines.events.on_player_died, function(event) (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 {} + + storage.playerDeathCause[event.player_index][event.cause.type] = + (storage.playerDeathCause[event.player_index][event.cause.type] or 0) + 1 + + log(("Player %s died from type %s"):format(game.players[event.player_index].name,event.cause.type)) + + 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 = {} @@ -163,6 +191,7 @@ function SendGameStats() returnParts[#returnParts+1] = GetPlayerTime() returnParts[#returnParts+1] = GetPlayerDeaths() returnParts[#returnParts+1] = GetPlayerKills() + returnParts[#returnParts+1] = GetPlayerEntityStats() helpers.send_udp(udpAddress, table.concat(returnParts, "\n"), serverIndex) end end @@ -190,24 +219,22 @@ function SendAll(event) scannedLabs = true end - if options.enableMod==true then - local interval = math.max(1, math.floor(tickInterval / 9)) + local interval = math.max(1, math.floor(tickInterval / 10)) if event.tick % interval ~= 0 then return end - - - sendIndex = (sendIndex % 9) + 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 == 7 then SendLogisticStats() end - if sendIndex == 8 then SendPowerStats() end - if sendIndex == 9 then SendGameStats()end -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 == 7 then SendLogisticStats() end + if sendIndex == 8 then SendPowerStats() end + if sendIndex == 9 then SendGameStats() end + if sendIndex == 10 then SendTrainStats() end + end end function UpdateStorage(event) @@ -240,15 +267,82 @@ function RemoveStorage(event) 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 + 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 + end + end + + + --Event is RobotPlaced + if event.name == defines.events.on_robot_built_entity then + if event.entity.name ~= "entity-ghost" 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 + + + --Event is spaceplatform build + if event.name == defines.events.on_space_platform_built_entity then + if event.entity.name ~= "entity-ghost" 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 + UpdateStorage(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 + end + + if event.name == defines.events.on_robot_mined_entity then + if event.entity.name ~= "entity-ghost" 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 + + if event.name == defines.events.on_space_platform_mined_entity then + if event.entity.name ~= "entity-ghost" 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 + + if event.name == defines.events.on_entity_died then + end + RemoveStorage(event) +end + script.on_event(defines.events.on_tick, SendAll) --Script hooks for power and lab stats -script.on_event(defines.events.on_built_entity,UpdateStorage,{{filter = "type", type = "electric-pole"},{filter ="type", type="lab"}}) -script.on_event(defines.events.on_player_mined_entity, RemoveStorage,{{filter = "type", type = "electric-pole"},{filter ="type", type="lab"},{filter = "type", type="container"}}) -script.on_event(defines.events.on_robot_built_entity,UpdateStorage,{{filter = "type", type = "electric-pole"},{filter ="type", type="lab"}}) -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,UpdateStorage,{{filter = "type", type = "electric-pole"},{filter ="type", type="lab"}}) +--script.on_event(defines.events.on_player_mined_entity, RemoveStorage,{{filter = "type", type = "electric-pole"},{filter ="type", type="lab"},{filter = "type", type="container"}}) +--script.on_event(defines.events.on_robot_built_entity,UpdateStorage,{{filter = "type", type = "electric-pole"},{filter ="type", type="lab"}}) +--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 diff --git a/game-stats.lua b/game-stats.lua index 4df9e54..baf06f1 100644 --- a/game-stats.lua +++ b/game-stats.lua @@ -21,6 +21,24 @@ function GetPlayerKills() return table.concat(killParts,"\n") end +function GetPlayerEntityStats() + local entityParts = {} + 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) + 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) + end + end + return table.concat(entityParts) +end + function GetMapSeed() return("---map-seed---\n%d"):format(game.surfaces["nauvis"].map_gen_settings.seed) end diff --git a/info.json b/info.json index fea4eeb..1fe301a 100644 --- a/info.json +++ b/info.json @@ -1,6 +1,6 @@ { "name": "factorio-metrics-exporter", - "version": "0.1.9", + "version": "0.1.11", "title": "Prometheus Metrics Exporter", "author": "Jan Grießhaber", "contact": "jan@griesshaber.systems", diff --git a/power-stats.lua b/power-stats.lua index ccea159..d1dc6d2 100644 --- a/power-stats.lua +++ b/power-stats.lua @@ -28,7 +28,7 @@ function GetNetworks() end function ScanNetworks() - storage.representative_poles = {} + storage.representativePoles = {} for _, surface in pairs(game.surfaces) do for _, pole in pairs(surface.find_entities_filtered{type = "electric-pole"}) do diff --git a/train-stats.lua b/train-stats.lua new file mode 100644 index 0000000..50399d8 --- /dev/null +++ b/train-stats.lua @@ -0,0 +1,162 @@ +function ScanTrains() + storage.trains = {} + for _,train in pairs(game.train_manager.get_trains({})) do + storage.trains[train.id] = train + end +end + +function GetTrainPlayerKills() + local trainKills = {} + 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] = ("%d%s%s%d"):format(train.id,killedPlayerID,game.players[killedPlayerID].name,killedPlayerCount) + end + end + return table.concat(trainKills,"\n") +end + + +function GetTrainTotalKills() + local trainKills = {} + trainKills[#trainKills+1] = "---train-total-kills---" + ---@type LuaTrain + for _, train in pairs(storage.trains) do + trainKills[#trainKills+1] = ("%d:%d"):format(train.id,train.kill_count) + end + return table.concat(trainKills,"\n") +end + +function GetTrainStates() + local trainsDriving = 0 + local trainsWaiting = 0 + local trainsProblems = 0 + local trainsManual = 0 + for _, train in pairs(storage.trains) do + if train.state == defines.train_state.wait_station or defines.train_state.destination_full or defines.train_state.no_schedule then trainsWaiting = trainsWaiting + 1 end + if train.state == defines.train_state.on_the_path or defines.train_state.arrive_signal or defines.train_state.wait_signal then trainsDriving = trainsDriving + 1 end + if train.state == defines.train_state.manual_control or defines.train_state.manual_control_stop then trainsManual = trainsManual + 1 end + if train.state == defines.train_state.no_path then trainsProblems = trainsProblems + 1 end + end + return ("---trains-states---\n%d:%d:%d:%d"):format(trainsDriving,trainsManual,trainsProblems,trainsWaiting) +end + +---@class trainStat +trainStat = { + lastInventory={}, + currentInventory={}, + lastState = 0, + lastStationUnitNumber = 0, + currentStationUnitNumber = 0, + totalCargoCount = 0, + totalCargo = {}, + lastArrivalTime = 0, + currentArrivalTime = 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 + t[key] = item.count + end + return t +end + +---@param oldInv table[] +---@param newInv table[] +---@return table[] +local function inventoryDiff(oldInv, newInv) + local oldLookup = toLookup(oldInv) + local newLookup = toLookup(newInv) + local diff = {} + + -- Items, die neu hinzugekommen oder verändert wurden + for key, newCount in pairs(newLookup) do + local oldCount = oldLookup[key] or 0 + if newCount ~= oldCount then + local name, quality = key:match("([^:]+):([^:]+)") + table.insert(diff, { + name = name, + quality = tonumber(quality), + oldCount = oldCount, + newCount = newCount, + delta = newCount - oldCount + }) + end + end + + -- Items, die komplett entfernt wurden + for key, oldCount in pairs(oldLookup) do + if newLookup[key] == nil then + local name, quality = key:match("([^:]+):([^:]+)") + table.insert(diff, { + name = name, + quality = tonumber(quality), + oldCount = oldCount, + newCount = 0, + delta = -oldCount + }) + end + end + + return diff +end + + +function onTrainStateChange(event) + -- Train arrived at station, so we store current data + ---@type LuaTrain + local train = event.train + + local trainID = train.id + ---@type trainStat + local stat = storage.trainStats[trainID] or {} + + if event.train.state == defines.train_state.wait_station then + + stat.lastStationUnitNumber = stat.currentStationUnitNumber + stat.lastInventory = stat.currentInventory + stat.lastArrivalTime = stat.currentArrivalTime + + stat.currentStationUnitNumber = train.station.unit_number + stat.currentInventory = train.get_contents() + stat.currentArrivalTime = game.tick + + + 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) + value.delta + end + + storage.trainStats[trainID] = stat + + end + end + log("inEvent") +end + +function GetTrainStatistics() + local trainParts = {} + trainParts[#trainParts+1] = "---train-total-statistics---\n" + for trainID, stat in pairs(storage.trainStats) do + trainParts[#trainParts+1] = ("%d:%d"):format(trainID,stat.totalCargoCount) + end + return table.concat(trainParts,"\n") +end + + +function SendTrainStats() + ScanTrains() + local returnParts = {} + returnParts[#returnParts+1] = GetTrainPlayerKills() + returnParts[#returnParts+1] = GetTrainTotalKills() + returnParts[#returnParts+1] = GetTrainStates() + returnParts[#returnParts+1] = GetTrainStatistics() + helpers.send_udp(udpAddress,table.concat(returnParts,"\n"),serverIndex) +end \ No newline at end of file