require("GameManager/TacticalGameClass") require("GameManager/SpectatorStruct") require("GameManager/TeamStruct") ---@class SkirmishGameClass : TacticalGameClass ---@field Spectators SpectatorStruct[] The spectator players in this game. ---@field IsLocalSpectator boolean Whether the local player is a spectator. ---@field Teams TeamStruct[] The teams in this game. SkirmishGameClass = {} SkirmishGameClass.__index = SkirmishGameClass setmetatable(SkirmishGameClass, { __index = TacticalGameClass }) ---Creates a new Skirmish Game context. ---@param gameMode string The game mode. ---@return SkirmishGameClass function SkirmishGameClass:New(gameMode) ScriptMessage("Initializing Skirmish game...") local self = setmetatable(TacticalGameClass:New(gameMode), SkirmishGameClass) ---@cast self SkirmishGameClass self:_FindSpectators() self.IsLocalSpectator = self:_GetLocalSpectator() self:_BuildTeams() ScriptMessage("Skirmish Game initialized!") return self end ---Services the Skirmish Game. function SkirmishGameClass:Service() -- Service TacticalGame TacticalGameClass.Service(self) ScriptMessage("Servicing Skirmish Game...") for _, team in pairs(self.Teams) do local credits = self:CalculateTeamTotalCredits(team.Id) local income = self:CalculateTeamTotalIncome(team.Id) GUIManager:DisplayTeamCredits(team, credits, income) end end -- ================================================== -- Unit event handlers -- ================================================== ---Displays unit built combat text. ---@param unit UnitClass The built unit. function SkirmishGameClass:CombatTextUnitBuilt(unit) if not self.IsLocalSpectator then -- No Text feed for combatants return end local owner = self.Players[unit.OwnerId] if not owner then return end local team = self.Teams[owner.TeamId] if not team then return end local timeText = FormatTime(unit.BuildTime) local text = string.format("%s [%s]: BUILT %s ($ %d)", timeText, team.Number, owner.Faction, unit.DisplayName, unit.BuildCost) local faction = Factions[owner.Faction] ---@type ColorStruct local color if faction then color = faction.Color else color = ColorStruct:New(1, 1, 1, 1) end self.CombatFeed:Append(text, -1, color) TextManager:SetCurrentView("combat_feed") end ---Displays killed unit combat text. ---@param unit UnitClass The killed unit. function SkirmishGameClass:CombatTextUnitKilled(unit) -- Right now, we don't really care about seeing killed units in the combat feed -- If we want it in the future, uncomment the lines below. return -- if not self.IsLocalSpectator then -- -- No Text feed for combatants -- return -- end -- local owner = self.Players[unit.OwnerId] -- if not owner then -- return -- end -- local killer = self.Players[unit.KillerId] -- local timeText = FormatTime(unit.DeathTime) -- if not killer then -- local team = self.Teams[owner.TeamId] -- if not team then -- return -- end -- local text = string.format("%s\t [%s]: MISTAKES WERE MADE %s", timeText, team.Number, owner.Name, -- unit.DisplayName) -- local color = ColorStruct:New(0.8, 0.8, 0.8, 1) -- self.CombatFeed:Append(text, -1, color) -- else -- local team = self.Teams[killer.TeamId] -- if not team then -- return -- end -- local text = string.format("%s\t [%s]: KILLED %s (%s)", timeText, team.Number, killer.Name, unit -- .DisplayName, -- owner.Name) -- local faction = Factions[killer.Faction] -- ---@type ColorStruct -- local color = nil -- if faction then -- color = faction.Color -- else -- color = ColorStruct:New(1, 1, 1, 1) -- end -- self.CombatFeed:Append(text, -1, color) -- end end -- ================================================== -- Player functions -- ================================================== ---Gets the Spectator by Player ID. ---@param id integer Player ID ---@return SpectatorStruct|nil function SkirmishGameClass:GetSpectator(id) return self.Spectators[id] end ---Gets the Team by ID. ---@param id integer Team ID ---@return TeamStruct|nil 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[] function SkirmishGameClass:GetPlayersOnTeam(id) ---@type PlayerClass[] local players = {} for playerId, player in pairs(self.Players) do if player.TeamId == id then players[playerId] = player end end return players end -- ================================================== -- Team functions -- ================================================== ---Calculates the Team ID's current total credits. ---@param id integer Team ID. ---@return integer function SkirmishGameClass:CalculateTeamTotalCredits(id) local players = self:GetPlayersOnTeam(id) local totalCredits = 0 for playerId, player in pairs(players) do totalCredits = totalCredits + player.Resource.Credits end return totalCredits end ---Calculates the Team ID's current total income. ---@param id integer Team ID. ---@return integer function SkirmishGameClass:CalculateTeamTotalIncome(id) local players = self:GetPlayersOnTeam(id) local totalIncome = 0 for playerId, player in pairs(players) do totalIncome = totalIncome + player.Resource.Income end 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 -- ================================================== ---@private ---Finds all spectators in the game. function SkirmishGameClass:_FindSpectators() ---@type SpectatorStruct[] local spectators = {} ---@type string local spectatorMarker = nil if StringCompare(self.GameMode, "Space") then spectatorMarker = Spectator_Marker_Space elseif StringCompare(self.GameMode, "Land") then spectatorMarker = Spectator_Marker_Land end if spectatorMarker then for k, marker in pairs(Find_All_Objects_Of_Type(spectatorMarker)) do local playerWrapper = marker.Get_Owner() local playerId = playerWrapper.Get_ID() if not spectators[playerId] then local spectator = SpectatorStruct:New(playerWrapper) spectators[playerId] = spectator ScriptMessage("Found Spectator: %s", spectator:Debug()) end end end self.Spectators = spectators end ---@private ---Gets whether the local player is a spectator. ---@returns boolean function SkirmishGameClass:_GetLocalSpectator() if not LocalPlayer then return false end return self:GetSpectator(LocalPlayer.Get_ID()) ~= nil end ---@private ---Builds all teams in the game. function SkirmishGameClass:_BuildTeams() ---@type TeamStruct[] local teams = {} -- Build combatant player teams for playerId, player in pairs(self.Players) do local teamId = player.TeamId if teamId >= 0 then if teams[teamId] == nil then teams[teamId] = TeamStruct:New(teamId, player.Faction, false) end end end -- Build spectator player teams for playerId, spectator in pairs(self.Spectators) do local teamId = spectator.TeamId if teamId >= 0 then local team = teams[teamId] if team == nil then team = TeamStruct:New(teamId, spectator.Faction, true) teams[teamId] = team ScriptMessage("Found Team: %s", team:Debug()) else team.IsSpectator = true end end end self.Teams = teams end