diff --git a/.luadefs/eaw-types-emmyluadoc.lua b/.luadefs/eaw-types-emmyluadoc.lua index 493c18f..7172ce4 100644 --- a/.luadefs/eaw-types-emmyluadoc.lua +++ b/.luadefs/eaw-types-emmyluadoc.lua @@ -241,6 +241,7 @@ function GameObject.Is_Transport() end function GameObject.Can_Land_On_Planet(planet) end ---@public +---@return GameObjectType function GameObject.Get_Game_Scoring_Type() end ---@public diff --git a/Data/Scripts/Miscellaneous/GameManager/Constants.lua b/Data/Scripts/Miscellaneous/GameManager/Constants.lua index 94b5e14..fe24489 100644 --- a/Data/Scripts/Miscellaneous/GameManager/Constants.lua +++ b/Data/Scripts/Miscellaneous/GameManager/Constants.lua @@ -18,6 +18,12 @@ TextManager = TutorialTextManager:New() ---@type PlayerObject|nil LocalPlayer = nil +---Frag Index of Kill Stat Tables +FragIndex = 1 + +---Death Index of Kill Stat Tables +DeathIndex = 2 + -- ================================================== -- Define Faction constants -- ================================================== diff --git a/Data/Scripts/Miscellaneous/GameManager/SkirmishGameClass.lua b/Data/Scripts/Miscellaneous/GameManager/SkirmishGameClass.lua index b025432..623422c 100644 --- a/Data/Scripts/Miscellaneous/GameManager/SkirmishGameClass.lua +++ b/Data/Scripts/Miscellaneous/GameManager/SkirmishGameClass.lua @@ -155,6 +155,19 @@ function SkirmishGameClass:GetTeam(id) return self.Teams[id] end +---Gets the Team of the Player ID. +---@param id integer Player ID. +---@return TeamStruct|nil +function SkirmishGameClass:GetTeamForPlayer(id) + local player = self.Players[id] + + if not player then + return nil + end + + return self.Teams[player.TeamId] +end + ---Gets the Players for the Team ID. ---@param id integer Team ID ---@return PlayerClass[] @@ -203,6 +216,113 @@ function SkirmishGameClass:CalculateTeamTotalIncome(id) return totalIncome end +--- ================================================== +--- Stat tables +--- ================================================== + +---Updates the Tactical Kill Stats Table with the killed object. +---@param unit UnitClass The object that was killed. +---@param killer PlayerObject The player that killed the object. +function SkirmishGameClass:UpdateTacticalKillStatsTable(unit, killer) + if not TestValid(killer) then + return + end + + -- Update Frags + local fragEntry = self.KillStatsTable[FragIndex] + + if not fragEntry then + fragEntry = {} + self.KillStatsTable[FragIndex] = fragEntry + end + + local killerId = killer.Get_ID() + + if self.IsLocalSpectator then + local killerTeam = self:GetTeamForPlayer(killerId) + + if killerTeam and killerTeam.Number == 1 then + -- For the spectator, let's show Team 1's kills on the left side + killerId = LocalPlayer.Get_ID() + end + end + + local killerEntry = fragEntry[killerId] + + if not killerEntry then + killerEntry = {} + fragEntry[killerId] = killerEntry + end + + local killerObjEntry = killerEntry[unit.GameObjectType] + + if not killerObjEntry then + killerObjEntry = { + kills = 1, + combat_power = unit.CombatRating, + build_cost = unit.BuildCost, + score_value = unit.ScoreValue + } + killerEntry[unit.GameObjectType] = killerObjEntry + else + killerObjEntry.kills = killerObjEntry.kills + 1 + killerObjEntry.combat_power = killerObjEntry.combat_power + unit.CombatRating + killerObjEntry.build_cost = killerObjEntry.build_cost + unit.BuildCost + killerObjEntry.score_value = killerObjEntry.score_value + unit.ScoreValue + end + + -- Update Deaths + local deathEntry = self.KillStatsTable[DeathIndex] + + if not deathEntry then + deathEntry = {} + self.KillStatsTable[DeathIndex] = deathEntry + end + + local ownerId = unit.OwnerId + + if self.IsLocalSpectator then + local ownerTeam = self:GetTeamForPlayer(ownerId) + + if ownerTeam and ownerTeam.Number == 1 then + -- For the spectator, let's show Team 1's lost units on the left side + ownerId = LocalPlayer.Get_ID() + end + else + -- Non-spectator: show all teammate deaths as local player's losses + local ownerTeam = self:GetTeamForPlayer(ownerId) + local localTeam = self:GetTeamForPlayer(LocalPlayer.Get_ID()) + + if ownerTeam and localTeam and ownerTeam.Id == localTeam.Id then + ownerId = LocalPlayer.Get_ID() + end + end + + local ownerEntry = deathEntry[ownerId] + + if not ownerEntry then + ownerEntry = {} + deathEntry[ownerId] = ownerEntry + end + + local ownerObjEntry = ownerEntry[unit.GameObjectType] + + if not ownerObjEntry then + ownerObjEntry = { + kills = 1, + combat_power = unit.CombatRating, + build_cost = unit.BuildCost, + score_value = unit.ScoreValue + } + ownerEntry[unit.GameObjectType] = ownerObjEntry + else + ownerObjEntry.kills = ownerObjEntry.kills + 1 + ownerObjEntry.combat_power = ownerObjEntry.combat_power + unit.CombatRating + ownerObjEntry.build_cost = ownerObjEntry.build_cost + unit.BuildCost + ownerObjEntry.score_value = ownerObjEntry.score_value + unit.ScoreValue + end +end + -- ================================================== -- Private functions -- ================================================== diff --git a/Data/Scripts/Miscellaneous/GameManager/TacticalGameClass.lua b/Data/Scripts/Miscellaneous/GameManager/TacticalGameClass.lua index 83c5cb4..4ac35f9 100644 --- a/Data/Scripts/Miscellaneous/GameManager/TacticalGameClass.lua +++ b/Data/Scripts/Miscellaneous/GameManager/TacticalGameClass.lua @@ -8,6 +8,7 @@ require("GameManager/TutorialTextFeed") ---@field StartTime integer The start time. ---@field Players PlayerClass[] The combatant players in this game. ---@field CombatFeed TutorialTextFeed Text feed for unit builds and kills. +---@field KillStatsTable table The engine-owned TacticalKillStatsTable reference, set from GameScoring.lua. TacticalGameClass = {} TacticalGameClass.__index = TacticalGameClass @@ -147,6 +148,7 @@ function TacticalGameClass:UnitKilled(gameObject, killer) if killedUnit then killedUnit:SetDead(killer.Get_ID()) + self:UpdateTacticalKillStatsTable(killedUnit, killer) self:CombatTextUnitKilled(killedUnit) end end @@ -189,6 +191,84 @@ function TacticalGameClass:CombatTextUnitKilled(unit) end end +--- ================================================== +--- Stat tables +--- ================================================== + +---Updates the Tactical Kill Stats Table with the killed object. +---@param unit UnitClass The object that was killed. +---@param killer PlayerObject The player that killed the object. +function TacticalGameClass:UpdateTacticalKillStatsTable(unit, killer) + if not TestValid(killer) then + return + end + + -- Update Frags + local fragEntry = self.KillStatsTable[FragIndex] + + if not fragEntry then + fragEntry = {} + self.KillStatsTable[FragIndex] = fragEntry + end + + local killerId = killer.Get_ID() + local killerEntry = fragEntry[killerId] + + if not killerEntry then + killerEntry = {} + fragEntry[killerId] = killerEntry + end + + local killerObjEntry = killerEntry[unit.GameObjectType] + + if not killerObjEntry then + killerObjEntry = { + kills = 1, + combat_power = unit.CombatRating, + build_cost = unit.BuildCost, + score_value = unit.ScoreValue + } + killerEntry[unit.GameObjectType] = killerObjEntry + else + killerObjEntry.kills = killerObjEntry.kills + 1 + killerObjEntry.combat_power = killerObjEntry.combat_power + unit.CombatRating + killerObjEntry.build_cost = killerObjEntry.build_cost + unit.BuildCost + killerObjEntry.score_value = killerObjEntry.score_value + unit.ScoreValue + end + + -- Update Deaths + local deathEntry = self.KillStatsTable[DeathIndex] + + if not deathEntry then + deathEntry = {} + self.KillStatsTable[DeathIndex] = deathEntry + end + + local ownerEntry = deathEntry[unit.OwnerId] + + if not ownerEntry then + ownerEntry = {} + deathEntry[unit.OwnerId] = ownerEntry + end + + local ownerObjEntry = ownerEntry[unit.GameObjectType] + + if not ownerObjEntry then + ownerObjEntry = { + kills = 1, + combat_power = unit.CombatRating, + build_cost = unit.BuildCost, + score_value = unit.ScoreValue + } + ownerEntry[unit.GameObjectType] = ownerObjEntry + else + ownerObjEntry.kills = ownerObjEntry.kills + 1 + ownerObjEntry.combat_power = ownerObjEntry.combat_power + unit.CombatRating + ownerObjEntry.build_cost = ownerObjEntry.build_cost + unit.BuildCost + ownerObjEntry.score_value = ownerObjEntry.score_value + unit.ScoreValue + end +end + -- ================================================== -- Private functions -- ================================================== diff --git a/Data/Scripts/Miscellaneous/GameManager/UnitClass.lua b/Data/Scripts/Miscellaneous/GameManager/UnitClass.lua index 3f3f318..1f475e4 100644 --- a/Data/Scripts/Miscellaneous/GameManager/UnitClass.lua +++ b/Data/Scripts/Miscellaneous/GameManager/UnitClass.lua @@ -6,6 +6,7 @@ require("PGBase") ---@field OwnerId integer Owner Player ID. ---@field BuildCost integer Tactical build cost. ---@field CombatRating integer AI combat power rating. +---@field ScoreValue integer Score cost credits. ---@field BuildTime number Game time that this unit was built. ---@field AliveTime number Game time that this unit became "alive". ---@field DeathTime number Game time that this unit was killed. @@ -26,6 +27,7 @@ function UnitClass:New(objectType, playerId) self.OwnerId = playerId self.BuildCost = objectType.Get_Tactical_Build_Cost() self.CombatRating = objectType.Get_Combat_Rating() + self.ScoreValue = objectType.Get_Score_Cost_Credits() self.BuildTime = GetCurrentTime() self.AliveTime = -1 self.DeathTime = -1 diff --git a/Data/Scripts/Miscellaneous/GameScoring.lua b/Data/Scripts/Miscellaneous/GameScoring.lua index 4cebef9..fd19f9e 100755 --- a/Data/Scripts/Miscellaneous/GameScoring.lua +++ b/Data/Scripts/Miscellaneous/GameScoring.lua @@ -12,7 +12,7 @@ require("GameManager/SkirmishGameClass") local DebugCounter = 0 ---Debugging service rate. -local ServiceDebugRate = 5 +local ServiceDebugRate = 10 ---The Galactic Game manager. ---@type GalacticGameClass|nil @@ -38,6 +38,13 @@ GameSpy_Game_Stats = {} ---@deprecated GameSpy_Player_Stats = {} +---Tactical Frag/Death Stat Table for post-game. +---[frag|death][playerid][object_type][build_count, credits_spent, combat_power] +TacticalKillStatsTable = { + [FragIndex] = {}, + [DeathIndex] = {} +} + -- ================================================== -- Script State Functions -- ================================================== @@ -84,7 +91,31 @@ end ---Script debug servicing. function ServiceDebug() + DebugMessage("%s -- TacticalKillStatsTable dump:", tostring(Script)) + local fragEntry = TacticalKillStatsTable[FragIndex] + if fragEntry then + for playerId, unitTypes in pairs(fragEntry) do + for objType, data in pairs(unitTypes) do + DebugMessage(" FRAG player=%d type=%s kills=%d cp=%d cost=%d score=%d", + playerId, tostring(objType), data.kills, data.combat_power, data.build_cost, data.score_value) + end + end + else + DebugMessage(" FRAG (empty)") + end + + local deathEntry = TacticalKillStatsTable[DeathIndex] + if deathEntry then + for playerId, unitTypes in pairs(deathEntry) do + for objType, data in pairs(unitTypes) do + DebugMessage(" DEATH player=%d type=%s kills=%d cp=%d cost=%d score=%d", + playerId, tostring(objType), data.kills, data.combat_power, data.build_cost, data.score_value) + end + end + else + DebugMessage(" DEATH (empty)") + end end -- ================================================== @@ -95,6 +126,15 @@ end function Reset_Tactical_Stats() DebugMessage("%s -- In Reset_Tactical_Stats", tostring(Script)) + TacticalKillStatsTable = { + [FragIndex] = {}, + [DeathIndex] = {} + } + TacticalTeamKillStatsTable = { + [FragIndex] = {}, + [DeathIndex] = {} + } + ResetTacticalRegistry() end @@ -394,6 +434,7 @@ function Init_Tactical(gameMode) DebugMessage("%s -- Initializing Tactical %s rules.", tostring(Script), gameMode) Reset_Tactical_Stats() TacticalGame = TacticalGameClass:New(gameMode) + TacticalGame.KillStatsTable = TacticalKillStatsTable GUIManager:InitTactical() TextManager:RegisterView(TacticalGame.CombatFeed) TextManager:SetCurrentView("combat_feed") @@ -418,6 +459,7 @@ function Init_Skirmish(gameMode) DebugMessage("%s -- Initializing Skirmish Tactical %s rules.", tostring(Script), gameMode) Reset_Stats() TacticalGame = SkirmishGameClass:New(gameMode) + TacticalGame.KillStatsTable = TacticalKillStatsTable GUIManager:InitSkirmish(TacticalGame.IsLocalSpectator) TextManager:RegisterView(TacticalGame.CombatFeed) TextManager:SetCurrentView("combat_feed")