From 1be066c6ed1e7702bfe8ec030d6df309b83acc31 Mon Sep 17 00:00:00 2001 From: Drew C Date: Tue, 10 Mar 2026 23:05:20 -0500 Subject: [PATCH] Major refactor to include tactical combat feed --- .luadefs/eaw-types-emmyluadoc.lua | 2 +- .../Miscellaneous/GameManager/Constants.lua | 18 ++ .../Miscellaneous/GameManager/GUIManager.lua | 170 ++-------- .../GameManager/GalacticGameClass.lua | 6 +- .../Miscellaneous/GameManager/PlayerClass.lua | 9 +- .../GameManager/ResourceClass.lua | 1 - .../GameManager/SkirmishGameClass.lua | 296 ++++++++++++------ .../GameManager/TacticalGameClass.lua | 206 ++++++++---- .../GameManager/TutorialTextFeed.lua | 12 +- .../Miscellaneous/GameManager/UnitClass.lua | 18 +- .../Miscellaneous/GameManager/Utilities.lua | 20 ++ Data/Scripts/Miscellaneous/GameScoring.lua | 106 +++---- 12 files changed, 490 insertions(+), 374 deletions(-) create mode 100644 Data/Scripts/Miscellaneous/GameManager/Utilities.lua diff --git a/.luadefs/eaw-types-emmyluadoc.lua b/.luadefs/eaw-types-emmyluadoc.lua index 6bf07da..493c18f 100644 --- a/.luadefs/eaw-types-emmyluadoc.lua +++ b/.luadefs/eaw-types-emmyluadoc.lua @@ -921,7 +921,7 @@ function PlayerObject.Release_Credits_For_Tactical(amount) end ---@param level number function PlayerObject.Set_Tech_Level(level) end ----@class PlanetObject +---@class PlanetObject : GameObject local PlanetObject = {} ---@public ---@return boolean diff --git a/Data/Scripts/Miscellaneous/GameManager/Constants.lua b/Data/Scripts/Miscellaneous/GameManager/Constants.lua index 7c9c8b2..94b5e14 100644 --- a/Data/Scripts/Miscellaneous/GameManager/Constants.lua +++ b/Data/Scripts/Miscellaneous/GameManager/Constants.lua @@ -1,4 +1,22 @@ require("GameManager/FactionStruct") +require("GameManager/GUIManager") +require("GameManager/TutorialTextManager") + +-- ================================================== +-- Define Manager constants +-- ================================================== + +---The singleton GUI Manager. +---@type GUIManager +GUIManager = GUIManager:New() + +---The singleton Tutorial Text Manager. +---@type TutorialTextManager +TextManager = TutorialTextManager:New() + +---The local player. +---@type PlayerObject|nil +LocalPlayer = nil -- ================================================== -- Define Faction constants diff --git a/Data/Scripts/Miscellaneous/GameManager/GUIManager.lua b/Data/Scripts/Miscellaneous/GameManager/GUIManager.lua index f637838..fd63db8 100644 --- a/Data/Scripts/Miscellaneous/GameManager/GUIManager.lua +++ b/Data/Scripts/Miscellaneous/GameManager/GUIManager.lua @@ -1,10 +1,7 @@ require("PGBase") -require("GameManager/Constants") +require("GameManager/Utilities") ---@class GUIManager ----@field TacticalGame TacticalGameClass|nil The local Tactical Game state. ----@field SkirmishGame SkirmishGameClass|nil The local Skirmish Game state. ----@field ShowTeamId integer The Team ID units to display. GUIManager = {} GUIManager.__index = GUIManager @@ -13,22 +10,15 @@ GUIManager.__index = GUIManager function GUIManager:New() local self = setmetatable({}, GUIManager) - self.TacticalGame = nil - self.SkirmishGame = nil - self.ShowTeamId = 0 - return self end ---Initializes the GUI Manager for Skirmish mode. ----@param game SkirmishGameClass The Skirmish Game state. -function GUIManager:InitSkirmish(game) - self.SkirmishGame = game - self.TacticalGame = game.TacticalGame +---@param isSpectator boolean Whether to initialize in spectator mode. +function GUIManager:InitSkirmish(isSpectator) + self:InitTactical() - GUI_Component_Visibility(UI_GameTime, true) - - if self.SkirmishGame.IsLocalSpectator then + if isSpectator then GUI_Component_Visibility(UI_CreditsT1, true) GUI_Component_Visibility(UI_IconT1, true) @@ -41,18 +31,12 @@ function GUIManager:InitSkirmish(game) end ---Initializes the GUI Manager for Tactical mode. ----@param game TacticalGameClass The Tactical Game state. -function GUIManager:InitTactical(game) - self.TacticalGame = game - +function GUIManager:InitTactical() GUI_Component_Visibility(UI_GameTime, true) end ---Resets the GUI Manager. function GUIManager:Reset() - self.SkirmishGame = nil - self.TacticalGame = nil - GUI_Component_Text(UI_GameTime, "") GUI_Text_Color(UI_GameTime, 1, 1, 1, 1) GUI_Component_Visibility(UI_GameTime, false) @@ -75,129 +59,43 @@ function GUIManager:Reset() GUI_Component_Visibility(UI_CreditsTactical, true) end ----Services the GUI Manager. -function GUIManager:Service() - if not self.TacticalGame then - -- Nothing to service - return - end - - ScriptMessage("Servicing GUI Updates...") - ServiceGameTime(self.TacticalGame) - ServiceTeamCredits(self.SkirmishGame) - --ServiceUnitDisplay(self.SkirmishGame, self.ShowTeamId) - ServiceScreenText(self.SkirmishGame) -end - ---Services Game Time display. ----@param tacticalGame TacticalGameClass The current Tactical Game state -function ServiceGameTime(tacticalGame) - if not tacticalGame then - return - end - - local gameTime = Dirty_Floor(GetCurrentTime()) - tacticalGame.StartTime - local minutes = Dirty_Floor(gameTime / 60) - local minutesText = tostring(minutes) - - if tonumber(minutes) < 10 then - minutesText = "0" .. minutesText - end - - local seconds = gameTime - (minutes * 60) - local secondsText = tostring(seconds) - - if tonumber(seconds) < 10 then - secondsText = "0" .. secondsText - end - - local text = string.format("Time: %s:%s", minutesText, secondsText) +---@param gameTime number The current game time. +function GUIManager:DisplayGameTime(gameTime) + local timeText = FormatTime(gameTime) + local text = string.format("Time: %s", timeText) GUI_Component_Text(UI_GameTime, text) end ----Services Team Credits display. ----@param skirmishGame SkirmishGameClass The current Skirmish Game state. -function ServiceTeamCredits(skirmishGame) - if not skirmishGame then +---Displays credits for the specified team +---@param team TeamStruct The team. +---@param credits number The team credits. +---@param income number The team income. +function GUIManager:DisplayTeamCredits(team, credits, income) + local uiText, uiIcon = "", "" + + if team.Number == 1 then + uiText = UI_CreditsT1 + uiIcon = UI_IconT1 + elseif team.Number == 2 then + uiText = UI_CreditsT2 + uiIcon = UI_IconT2 + else return end - if not skirmishGame.IsLocalSpectator then - return + local positiveText = "+" + + if income < 0 then + positiveText = "" end - ---Displays credits for the specified team - ---@param team TeamStruct The team to service. - ---@param uiText string The name of the UI Component to display Team credits in. - ---@param uiIcon string The name of the UI Component to display the Faction icon in. - local function DisplayTeamCredits(team, uiText, uiIcon) - local teamCredits = skirmishGame:CalculateTeamTotalCredits(team.Id) - local teamIncome = skirmishGame:CalculateTeamTotalIncome(team.Id) - local positiveText = "+" + local text = string.format("Team %d: $%d (%s%d)", team.Number, credits, positiveText, income) + local icon = team.Faction.Icon + local color = team.Faction.Color - if teamIncome < 0 then - positiveText = "" - end - - local text = string.format("Team %d: $%d (%s%d)", team.Number, teamCredits, positiveText, teamIncome) - local icon = team.Faction.Icon - local color = team.Faction.Color - - GUI_Button_Icon(uiIcon, icon, color.R, color.G, color.B, 1) - GUI_Component_Text(uiText, text) - GUI_Text_Color(uiText, color.R, color.G, color.B, 1) - end - - for _, team in pairs(skirmishGame.Teams) do - if team.Number == 1 then - DisplayTeamCredits(team, UI_CreditsT1, UI_IconT1) - elseif team.Number == 2 then - DisplayTeamCredits(team, UI_CreditsT2, UI_IconT2) - end - end -end - ----Services Team Units display. ----@param skirmishGame SkirmishGameClass The current Skirmish Game state. ----@param teamId integer The Team ID's units to display. -function ServiceUnitDisplay(skirmishGame, teamId) - if not skirmishGame then - return - end - - if not skirmishGame.IsLocalSpectator then - return - end - - local localPlayer = Find_Player("local") - - if not localPlayer then - return - end - - local function DisplayTeamUnit(unit, uiComponent) - - end - - local players = skirmishGame:GetPlayersOnTeam(teamId) - ---@type table Units Table by name with alive count. - local units = {} - - for _, player in pairs(players) do - local playerUnits = player:GetAliveUnits() - - for _, playerUnit in pairs(playerUnits) do - localPlayer.Select_Object(playerUnit.GameObjectWrapper) - - if not units[playerUnit.Name] then - units[playerUnit.Name] = 1 - else - units[playerUnit.Name] = units[playerUnit.Name] + 1 - end - end - end -end - -function ServiceScreenText(skirmishGame) + GUI_Button_Icon(uiIcon, icon, color.R, color.G, color.B, 1) + GUI_Component_Text(uiText, text) + GUI_Text_Color(uiText, color.R, color.G, color.B, 1) end diff --git a/Data/Scripts/Miscellaneous/GameManager/GalacticGameClass.lua b/Data/Scripts/Miscellaneous/GameManager/GalacticGameClass.lua index b0abb4e..2457b7e 100644 --- a/Data/Scripts/Miscellaneous/GameManager/GalacticGameClass.lua +++ b/Data/Scripts/Miscellaneous/GameManager/GalacticGameClass.lua @@ -65,9 +65,9 @@ end ---@param player table The FoC PlayerWrapper object that owns the new Unit. function GalacticGameClass:UnitBuilt(objectType, player) local playerId = player.Get_ID() - local player = self.Players[playerId] + local playerEntry = self.Players[playerId] - if player then - player:AddUnit(objectType) + if playerEntry then + playerEntry:AddUnit(objectType) end end diff --git a/Data/Scripts/Miscellaneous/GameManager/PlayerClass.lua b/Data/Scripts/Miscellaneous/GameManager/PlayerClass.lua index a93cf36..c65862c 100644 --- a/Data/Scripts/Miscellaneous/GameManager/PlayerClass.lua +++ b/Data/Scripts/Miscellaneous/GameManager/PlayerClass.lua @@ -45,10 +45,10 @@ function PlayerClass:Service() -- Pass 1: Collect assigned game object wrappers from alive units for _, unit in pairs(self:GetAliveUnits()) do - if TestValid(unit.GameObjectWrapper) then - assignedUnits[unit.GameObjectWrapper] = true + if TestValid(unit.GameObject) then + assignedUnits[unit.GameObject] = true else - unit:SetDead() + unit:SetDead(-1) end end @@ -81,11 +81,14 @@ end ---Adds a Unit type to the Player's reinforcement list. ---@param objectType table FoC GameObjectTypeWrapper object that was built. +---@return UnitClass function PlayerClass:AddUnit(objectType) local unit = UnitClass:New(objectType, self.Id) table.insert(self.Units, unit) ScriptMessage("Unit Added: %s", unit:Debug()) + + return unit end ---Gets all Reinforcement Units for this Player. diff --git a/Data/Scripts/Miscellaneous/GameManager/ResourceClass.lua b/Data/Scripts/Miscellaneous/GameManager/ResourceClass.lua index ce880c9..fb5fad9 100644 --- a/Data/Scripts/Miscellaneous/GameManager/ResourceClass.lua +++ b/Data/Scripts/Miscellaneous/GameManager/ResourceClass.lua @@ -20,7 +20,6 @@ end ---@param credits number function ResourceClass:SetCredits(credits) self.LastCredits = self.Credits - self.Credits = credits if credits < 0 then self.Credits = 0 diff --git a/Data/Scripts/Miscellaneous/GameManager/SkirmishGameClass.lua b/Data/Scripts/Miscellaneous/GameManager/SkirmishGameClass.lua index 45bfb7d..e77f8b7 100644 --- a/Data/Scripts/Miscellaneous/GameManager/SkirmishGameClass.lua +++ b/Data/Scripts/Miscellaneous/GameManager/SkirmishGameClass.lua @@ -1,110 +1,26 @@ -require("PGBase") -require("GameManager/Constants") require("GameManager/TacticalGameClass") require("GameManager/SpectatorStruct") require("GameManager/TeamStruct") ----@class SkirmishGameClass ----@field TacticalGame TacticalGameClass The underlying Tactical Game context. +---@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({}, SkirmishGameClass) + local self = setmetatable(TacticalGameClass:New(gameMode), SkirmishGameClass) - ---Finds all spectators in the game. - ---@return SpectatorStruct[] - local function FindSpectators() - ---@type SpectatorStruct[] - local spectators = {} - ---@type string - local spectatorMarker = nil - - if StringCompare(gameMode, "Space") then - spectatorMarker = Spectator_Marker_Space - elseif StringCompare(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 - - return spectators - end - - self.TacticalGame = TacticalGameClass:New(gameMode) - self.Spectators = FindSpectators() - - ---Gets whether the local player is a spectator. - ---@returns boolean - local function GetLocalSpectator() - local localPlayer = Find_Player("local") - - if not localPlayer then - return false - end - - return self:GetSpectator(localPlayer.Get_ID()) ~= nil - end - - self.IsLocalSpectator = GetLocalSpectator() - - ---Builds all teams in the game. - ---@return TeamStruct[] - local function BuildTeams() - ---@type TeamStruct[] - local teams = {} - - -- Build combatant player teams - for playerId, player in pairs(self.TacticalGame.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 - - return teams - end - - self.Teams = BuildTeams() + ---@cast self SkirmishGameClass + self:_FindSpectators() + self.IsLocalSpectator = self:_GetLocalSpectator() + self:_BuildTeams() ScriptMessage("Skirmish Game initialized!") return self @@ -112,31 +28,116 @@ end ---Services the Skirmish Game. function SkirmishGameClass:Service() - ScriptMessage("Servicing Skirmish Game...") -- Service TacticalGame - self.TacticalGame:Service() + 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) + 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\t [%s]: BUILT %s ($%d)", timeText, team.Number, owner.Name, unit.Name, + 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) + 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.Name) + 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.Name, + 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) - for playerId, spectator in pairs(self.Spectators) do - if playerId == id then - return spectator - end - end + return self.Spectators[id] end ---Gets the Team by ID. ---@param id integer Team ID ---@return TeamStruct|nil function SkirmishGameClass:GetTeam(id) - for teamId, team in pairs(self.Teams) do - if teamId == id then - return team - end - end + return self.Teams[id] end ---Gets the Players for the Team ID. @@ -146,7 +147,7 @@ function SkirmishGameClass:GetPlayersOnTeam(id) ---@type PlayerClass[] local players = {} - for playerId, player in pairs(self.TacticalGame.Players) do + for playerId, player in pairs(self.Players) do if player.TeamId == id then players[playerId] = player end @@ -155,6 +156,10 @@ function SkirmishGameClass:GetPlayersOnTeam(id) return players end +-- ================================================== +-- Team functions +-- ================================================== + ---Calculates the Team ID's current total credits. ---@param id integer Team ID. ---@return integer @@ -182,3 +187,86 @@ function SkirmishGameClass:CalculateTeamTotalIncome(id) return totalIncome 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 diff --git a/Data/Scripts/Miscellaneous/GameManager/TacticalGameClass.lua b/Data/Scripts/Miscellaneous/GameManager/TacticalGameClass.lua index 2f1ed7c..d4d151e 100644 --- a/Data/Scripts/Miscellaneous/GameManager/TacticalGameClass.lua +++ b/Data/Scripts/Miscellaneous/GameManager/TacticalGameClass.lua @@ -1,13 +1,13 @@ require("PGBase") +require("GameManager/Constants") require("GameManager/PlayerClass") +require("GameManager/TutorialTextFeed") ---@class TacticalGameClass ---@field GameMode string The tactical game mode. ---@field StartTime integer The start time. ---@field Players PlayerClass[] The combatant players in this game. ----@field ReinforcementUnitCount table Key: Unit Type, Value: Count ----@field AliveUnitCount table Key: Unit Type, Value: Count ----@field DeadUnitCount table Key: Unit Type, Value: Count +---@field CombatFeed TutorialTextFeed Text feed for unit builds and kills. TacticalGameClass = {} TacticalGameClass.__index = TacticalGameClass @@ -18,42 +18,10 @@ function TacticalGameClass:New(gameMode) ScriptMessage("Initializing Tactical game...") local self = setmetatable({}, TacticalGameClass) - ---Finds all combatant players in the game. - ---@return PlayerClass[] - local function FindPlayers() - ---@type PlayerClass[] - local players = {} - - for k, faction in pairs(Factions) do - local startingUnit = nil - - if StringCompare(gameMode, "Land") then - startingUnit = faction.LandStartUnitName - elseif StringCompare(gameMode, "Space") then - startingUnit = faction.SpaceStartUnitName - end - - if startingUnit then - for i, unit in pairs(Find_All_Objects_Of_Type(startingUnit)) do - local playerWrapper = unit.Get_Owner() - local playerId = playerWrapper.Get_ID() - - if players[playerId] == nil then - local player = PlayerClass:New(playerWrapper) - - players[playerId] = player - ScriptMessage("Found Player: %s", player:Debug()) - end - end - end - end - - return players - end - self.GameMode = gameMode self.StartTime = GetCurrentTime() - self.Players = FindPlayers() + self.Players = self:_FindPlayers(gameMode) + self.CombatFeed = TutorialTextFeed:New("combat_feed", 6) ScriptMessage("Tactical Game initialized!") return self @@ -67,8 +35,16 @@ function TacticalGameClass:Service() for _, player in pairs(self.Players) do player:Service() end + + local gameTime = GetCurrentTime() - self.StartTime + + GUIManager:DisplayGameTime(gameTime) end +-- ================================================== +-- State functions +-- ================================================== + ---Whether this Tactical game is in space mode. ---@return boolean function TacticalGameClass:IsSpaceMode() @@ -80,41 +56,163 @@ function TacticalGameClass:IsLandMode() return StringCompare(self.GameMode, "Land") end +-- ================================================== +-- Player functions +-- ================================================== + ---Gets the Player by ID. ---@param id integer Player ID ---@return PlayerClass|nil function TacticalGameClass:GetPlayer(id) - for playerId, player in pairs(self.Players) do - if playerId == id then - return player - end - end + return self.Players[id] end ---Records the Player ID as having quit at the current time. ---@param id integer Player ID. function TacticalGameClass:PlayerQuit(id) - for playerId, player in pairs(self.Players) do - if playerId == id then - player:Quit() - end + local player = self.Players[id] + + if player then + player:Quit() end end +-- ================================================== +-- Unit event handlers +-- ================================================== + ---Adds a built unit to the Unit table. ----@param objectType table The FoC GameObjectTypeWrapper object type that was built. ----@param player table The FoC PlayerWrapper object that owns the new Unit. +---@param objectType GameObjectType The FoC GameObjectTypeWrapper object type that was built. +---@param player PlayerObject The FoC PlayerWrapper object that owns the new Unit. function TacticalGameClass:UnitBuilt(objectType, player) local playerId = player.Get_ID() - local player = self.Players[playerId] + local playerEntry = self.Players[playerId] - if player then - player:AddUnit(objectType) + if playerEntry then + local unit = playerEntry:AddUnit(objectType) + + self:CombatTextUnitBuilt(unit) end end +---Displays unit built combat text. +---@param unit UnitClass The built unit. +function TacticalGameClass:CombatTextUnitBuilt(unit) + local owner = self.Players[unit.OwnerId] + + if not owner then + return + end + + local timeText = FormatTime(unit.BuildTime) + local text = string.format("%s\t[%s]: BUILT %s ($%d)", timeText, owner.Name, unit.Name, 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) +end + ---Sets a unit as killed. ----@param gameObject table The FoC GameObjectWrapper object that was killed. -function TacticalGameClass:UnitKilled(gameObject) - -- Find a way to set the unit as killed +---@param gameObject GameObject The FoC GameObjectWrapper object that was killed. +---@param killer PlayerObject The FoC PlayerWrapper object that killed the Unit. +function TacticalGameClass:UnitKilled(gameObject, killer) + local ownerId = gameObject.Get_Owner().Get_ID() + local owner = self.Players[ownerId] + + if not owner then + return + end + + ---@type UnitClass|nil + local killedUnit = nil + + for _, unit in pairs(owner.Units) do + if unit.GameObject == gameObject then + killedUnit = unit + break + end + end + + if killedUnit then + killedUnit:SetDead(killer.Get_ID()) + self:CombatTextUnitKilled(killedUnit) + end +end + +---Displays killed unit combat text. +---@param unit UnitClass The killed unit. +function TacticalGameClass:CombatTextUnitKilled(unit) + 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 text = string.format("%s\t[%s]: MISTAKES WERE MADE %s", timeText, owner.Name, unit.Name) + local color = ColorStruct:New(0.8, 0.8, 0.8, 1) + + self.CombatFeed:Append(text, -1, color) + else + local text = string.format("%s\t[%s]: KILLED %s (%s)", timeText, killer.Name, unit.Name, 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 + +-- ================================================== +-- Private functions +-- ================================================== + +---@private +---@param gameMode string The current game mode. +---@return PlayerClass[] +function TacticalGameClass:_FindPlayers(gameMode) + ---@type PlayerClass[] + local players = {} + + for k, faction in pairs(Factions) do + local startingUnit = nil + + if StringCompare(gameMode, "Land") then + startingUnit = faction.LandStartUnitName + elseif StringCompare(gameMode, "Space") then + startingUnit = faction.SpaceStartUnitName + end + + if startingUnit then + for i, unit in pairs(Find_All_Objects_Of_Type(startingUnit)) do + local playerWrapper = unit.Get_Owner() + local playerId = playerWrapper.Get_ID() + + if players[playerId] == nil then + local player = PlayerClass:New(playerWrapper) + + players[playerId] = player + ScriptMessage("Found Player: %s", player:Debug()) + end + end + end + end + + return players end diff --git a/Data/Scripts/Miscellaneous/GameManager/TutorialTextFeed.lua b/Data/Scripts/Miscellaneous/GameManager/TutorialTextFeed.lua index 6457c60..39010a8 100644 --- a/Data/Scripts/Miscellaneous/GameManager/TutorialTextFeed.lua +++ b/Data/Scripts/Miscellaneous/GameManager/TutorialTextFeed.lua @@ -8,7 +8,7 @@ require("GameManager/TutorialTextView") ---@field MaxLines integer Maximum number of lines to display at once. ---@field _KeyOrder string[] Insertion-ordered list of active entry keys (oldest first). ---@field _NextId integer Counter used to generate unique entry keys. -TutorialTextFeed = setmetatable({}, {__index = TutorialTextView}) +TutorialTextFeed = setmetatable({}, { __index = TutorialTextView }) TutorialTextFeed.__index = TutorialTextFeed ---Creates a new Tutorial Text Feed. @@ -16,9 +16,9 @@ TutorialTextFeed.__index = TutorialTextFeed ---@param maxLines integer Maximum number of lines to display at once. ---@return TutorialTextFeed function TutorialTextFeed:New(name, maxLines) - local self = TutorialTextView.New(TutorialTextView, name) - setmetatable(self, TutorialTextFeed) + local self = setmetatable(TutorialTextView:New(name), TutorialTextFeed) + ---@cast self TutorialTextFeed self.MaxLines = maxLines self._KeyOrder = {} self._NextId = 0 @@ -33,7 +33,8 @@ end ---@param duration number|nil Duration in seconds, defaults to -1 (infinite). ---@param color ColorStruct|nil The text color, defaults to white. function TutorialTextFeed:Append(text, duration, color) - if #self._KeyOrder >= self.MaxLines then + ---@diagnostic disable-next-line + if table.getn(self._KeyOrder) >= self.MaxLines then local oldestKey = table.remove(self._KeyOrder, 1) self.Entries[oldestKey] = nil if self.IsActive then @@ -68,5 +69,6 @@ end function TutorialTextFeed:Debug() return string.format("TutorialTextFeed '%s' (%d/%d lines)", - self.Name, #self._KeyOrder, self.MaxLines) + ---@diagnostic disable-next-line + self.Name, table.getn(self._KeyOrder), self.MaxLines) end diff --git a/Data/Scripts/Miscellaneous/GameManager/UnitClass.lua b/Data/Scripts/Miscellaneous/GameManager/UnitClass.lua index 64a53d5..77de944 100644 --- a/Data/Scripts/Miscellaneous/GameManager/UnitClass.lua +++ b/Data/Scripts/Miscellaneous/GameManager/UnitClass.lua @@ -8,9 +8,9 @@ require("PGBase") ---@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. ----@field GameObjectWrapper table|nil The FoC GameObjectWrapper object. ----@field GameObjectTypeWrapper table The FoC GameObjectTypeWrapper object. ----@field Icon string Name of the icon for this object. +---@field KillerId integer|nil The Player ID that killed this unit. +---@field GameObject GameObject|nil The FoC GameObjectWrapper object. +---@field GameObjectType GameObjectType The FoC GameObjectTypeWrapper object. UnitClass = {} UnitClass.__index = UnitClass @@ -27,9 +27,9 @@ function UnitClass:New(objectType, playerId) self.BuildTime = GetCurrentTime() self.AliveTime = -1 self.DeathTime = -1 - self.GameObjectWrapper = nil - self.GameObjectTypeWrapper = objectType - self.Icon = "I_BUTTON_COMMAND_BAR_PIRATE_SYMBOL.TGA" + self.KillerId = nil + self.GameObject = nil + self.GameObjectType = objectType return self end @@ -61,18 +61,20 @@ function UnitClass:SetAlive(object) end self.AliveTime = GetCurrentTime() - self.GameObjectWrapper = object + self.GameObject = object ScriptMessage("Unit Spawned: %s", self:Debug()) end ---Sets the unit as dead. -function UnitClass:SetDead() +---@param killerId integer The Player ID that killed this unit. +function UnitClass:SetDead(killerId) if self.DeathTime >= 0 then -- Do not override once set return end self.DeathTime = GetCurrentTime() + self.KillerId = killerId ScriptMessage("Unit Killed: %s", self:Debug()) end diff --git a/Data/Scripts/Miscellaneous/GameManager/Utilities.lua b/Data/Scripts/Miscellaneous/GameManager/Utilities.lua new file mode 100644 index 0000000..91eef17 --- /dev/null +++ b/Data/Scripts/Miscellaneous/GameManager/Utilities.lua @@ -0,0 +1,20 @@ +---Formats the game time in seconds to MM:SS. +---@param gameTime number The game time in seconds. +---@return string +function FormatTime(gameTime) + local minutes = Dirty_Floor(gameTime / 60) + local minutesText = tostring(minutes) + + if tonumber(minutes) < 10 then + minutesText = "0" .. minutesText + end + + local seconds = Dirty_Floor(gameTime - (minutes * 60)) + local secondsText = tostring(seconds) + + if tonumber(seconds) < 10 then + secondsText = "0" .. secondsText + end + + return string.format("%s:%s", minutesText, secondsText) +end diff --git a/Data/Scripts/Miscellaneous/GameScoring.lua b/Data/Scripts/Miscellaneous/GameScoring.lua index 23cd7e2..4cebef9 100755 --- a/Data/Scripts/Miscellaneous/GameScoring.lua +++ b/Data/Scripts/Miscellaneous/GameScoring.lua @@ -1,6 +1,5 @@ require("PGBase") require("GameManager/Constants") -require("GameManager/GUIManager") require("GameManager/GalacticGameClass") require("GameManager/TacticalGameClass") require("GameManager/SkirmishGameClass") @@ -15,25 +14,14 @@ local DebugCounter = 0 ---Debugging service rate. local ServiceDebugRate = 5 ----The Local Player FoC PlayerWrapper object. -local LocalPlayer = nil - ----The GUI Manager. ----@type GUIManager -local GUIManager = GUIManager:New() - ---The Galactic Game manager. ---@type GalacticGameClass|nil local GalacticGame = nil ---The Tactical Game manager. ----@type TacticalGameClass|nil +---@type TacticalGameClass|SkirmishGameClass|nil local TacticalGame = nil ----The Skirmish Game manager. ----@type SkirmishGameClass|nil -local SkirmishGame = nil - -- ================================================== -- Script Globals -- ================================================== @@ -59,7 +47,6 @@ function Base_Definitions() DebugMessage("%s -- In Base_Definitions.", tostring(Script)) DebugCounter = 0 - LocalPlayer = Find_Player("local") end ---Main script function. Does event pumps and servicing. @@ -88,18 +75,10 @@ function GameService() if GalacticGame then GalacticGame:Service() - - if TacticalGame then - TacticalGame:Service() - end end - if SkirmishGame then - SkirmishGame:Service() - end - - if GUIManager then - GUIManager:Service() + if TacticalGame then + TacticalGame:Service() end end @@ -142,6 +121,8 @@ end function Game_Mode_Starting_Event(mode, map) DebugMessage("%s -- Game Mode %s (%s) now starting.", tostring(Script), mode, map) + LocalPlayer = Find_Player("local") + if StringCompare(mode, "Galactic") then -- Galactic Campaign Init_Galactic() @@ -169,10 +150,12 @@ function Game_Mode_Ending_Event(oldMode) -- Skirmish mode ending Reset_Skirmish() end + + LocalPlayer = nil end ---This event is triggered when a player quits the game. ----@param player table FoC PlayerWrapper object that just quit. +---@param player PlayerObject FoC PlayerWrapper object that just quit. function Player_Quit_Event(player) DebugMessage("%s -- Player %s (%d) has quit.", tostring(Script), player.Get_Name(), player.Get_ID()) @@ -182,9 +165,9 @@ function Player_Quit_Event(player) end ---This event is triggered when production has finished in a tactical mode ----@param objectType table FoC GameObjectTypeWrapper object that was just built. ----@param player table FoC PlayerWrapper object that built the object. ----@param location table|nil FoC GameObjectWrapper of the planet. +---@param objectType GameObjectType FoC GameObjectTypeWrapper object that was just built. +---@param player PlayerObject FoC PlayerWrapper object that built the object. +---@param location PlanetObject|nil FoC GameObjectWrapper of the planet. function Tactical_Production_End_Event(objectType, player, location) if location then DebugMessage("%s -- Tactical production of unit %s by %s ended at %s.", tostring(Script), objectType.Get_Name(), @@ -201,20 +184,20 @@ function Tactical_Production_End_Event(objectType, player, location) end ---This event is triggered when a unit is destroyed in tactical mode. ----@param object table FoC GameObjectWrapper object that was just killed. ----@param killer table FoC PlayerWrapper object that killed the object. +---@param object GameObject FoC GameObjectWrapper object that was just killed. +---@param killer PlayerObject FoC PlayerWrapper object that killed the object. function Tactical_Unit_Destroyed_Event(object, killer) DebugMessage("%s -- Tactical unit %s destroyed by %s.", tostring(Script), object.Get_Type().Get_Name(), killer.Get_Name()) if TacticalGame then - TacticalGame:UnitKilled(object) + TacticalGame:UnitKilled(object, killer) end end ---This event is triggered when production has begun on an item at a given planet. ----@param planet table FoC GameObjectWrapper object of the planet. ----@param objectType table FoC GameObjectTypeWrapper object that was just queued. +---@param planet PlanetObject FoC GameObjectWrapper object of the planet. +---@param objectType GameObjectType FoC GameObjectTypeWrapper object that was just queued. function Galactic_Production_Begin_Event(planet, objectType) DebugMessage("%s -- Galactic production of %s started at %s.", tostring(Script), objectType.Get_Name(), planet.Get_Type().Get_Name()) @@ -222,16 +205,16 @@ end ---This event is triggered when production has been prematurely canceled ---on an item at a given planet. ----@param planet table FoC GameObjectWrapper object of the planet. ----@param objectType table FoC GameObjectTypeWrapper object that was just canceled.. +---@param planet PlanetObject FoC GameObjectWrapper object of the planet. +---@param objectType GameObjectType FoC GameObjectTypeWrapper object that was just canceled.. function Galactic_Production_Canceled_Event(planet, objectType) DebugMessage("%s -- Galactic production of %s canceled at %s.", tostring(Script), objectType.Get_Name(), planet.Get_Type().Get_Name()) end ---This event is triggered when production has finished on an item at a given planet. ----@param planet table FoC GameObjectWrapper or of the planet. ----@param object table FoC GameObjectWrapper or GameObjectTypeWrapper of the object that was just created. +---@param planet PlanetObject FoC GameObjectWrapper or of the planet. +---@param object GameObject|GameObjectType FoC GameObjectWrapper or GameObjectTypeWrapper of the object that was just created. function Galactic_Production_End_Event(planet, object) local typeName = "" @@ -249,16 +232,16 @@ function Galactic_Production_End_Event(planet, object) end ---This event is triggered when a unit is destroyed in galactic mode. ----@param object table FoC GameObjectWrapper object that was just killed. ----@param killer table FoC PlayerWrapper object that killed the object. +---@param object GameObject FoC GameObjectWrapper object that was just killed. +---@param killer PlayerObject FoC PlayerWrapper object that killed the object. function Galactic_Unit_Destroyed_Event(object, killer) DebugMessage("%s -- Object %s killed by %s.", tostring(Script), object.Get_Type().Get_Name(), killer.Get_Name()) end ---This event is triggered when the level of a starbase changes. ----@param planet table FoC GameObjectWrapper object of the planet. ----@param oldType table FoC GameObjectTypeWrapper object of the old starbase type. ----@param newType table FoC GameObjectTypeWrapper object of the new starbase type. +---@param planet PlanetObject FoC GameObjectWrapper object of the planet. +---@param oldType GameObjectType FoC GameObjectTypeWrapper object of the old starbase type. +---@param newType GameObjectType FoC GameObjectTypeWrapper object of the new starbase type. function Galactic_Starbase_Level_Change(planet, oldType, newType) DebugMessage("%s -- %s Starbase changed from %s to %s.", tostring(Script), planet.Get_Type().Get_Name(), tostring(oldType), tostring(newType)) @@ -295,9 +278,9 @@ function Galactic_Starbase_Level_Change(planet, oldType, newType) end ---This event is called when a planet changes faction in galactic mode. ----@param planet table FoC GameObjectWrapper object of the planet. ----@param newOwner table FoC PlayerWrapper of the new owner. ----@param oldOwner table FoC PlayerWrapper of the old owner. +---@param planet PlanetObject FoC GameObjectWrapper object of the planet. +---@param newOwner PlayerObject FoC PlayerWrapper of the new owner. +---@param oldOwner PlayerObject FoC PlayerWrapper of the old owner. function Galactic_Planet_Faction_Change(planet, newOwner, oldOwner) local planetName = planet.Get_Type().Get_Name() @@ -306,8 +289,8 @@ function Galactic_Planet_Faction_Change(planet, newOwner, oldOwner) end ---This event is called when a hero is neutralized by another hero in galactic mode. ----@param heroType table FoC GameObjectTypeWrapper object for the neutralized hero type. ----@param killer table FoC GameObjectWrapper object that killed the hero. +---@param heroType GameObjectType FoC GameObjectTypeWrapper object for the neutralized hero type. +---@param killer GameObject FoC GameObjectWrapper object that killed the hero. function Galactic_Neutralized_Event(heroType, killer) DebugMessage("%s -- Hero %s killed by %s.", tostring(Script), heroType.Get_Name(), killer.Get_Owner().Get_Name()) end @@ -317,23 +300,23 @@ end --- ================================================== ---Returns the number of frags by player for the given object type. ----@param objectType table FoC GameObjectTypeWrapper object to query. ----@param player table FoC PlayerWrapper object to query. +---@param objectType GameObjectType FoC GameObjectTypeWrapper object to query. +---@param player PlayerObject FoC PlayerWrapper object to query. ---@return integer function Get_Frag_Count_For_Type(objectType, player) return 0 end ---Returns the number of heroes neutralized by player for the given object type. ----@param objectType table FoC GameObjectTypeWrapper object to query. ----@param player table FoC PlayerWrapper object to query. +---@param objectType GameObjectType FoC GameObjectTypeWrapper object to query. +---@param player PlayerObject FoC PlayerWrapper object to query. ---@return integer function Get_Neutralized_Count_For_Type(objectType, player) return 0 end ---Returns a game stat for the provided control ID. ----@param player table FoC PlayerWrapper object to query. +---@param player PlayerObject FoC PlayerWrapper object to query. ---@param controlId string The Control ID. ---@param isTactical boolean Whether the game mode was a tactical mode. ---@return number|string @@ -377,7 +360,8 @@ function Update_GameSpy_Game_Stats() end ---Updates the GameSpy player stats table ----@param player table FoC PlayerWrapper object. +---@param player PlayerObject FoC PlayerWrapper object. +---@deprecated function Update_GameSpy_Player_Stats(player) GameSpy_Player_Stats = {} end @@ -410,7 +394,9 @@ function Init_Tactical(gameMode) DebugMessage("%s -- Initializing Tactical %s rules.", tostring(Script), gameMode) Reset_Tactical_Stats() TacticalGame = TacticalGameClass:New(gameMode) - GUIManager:InitTactical(TacticalGame) + GUIManager:InitTactical() + TextManager:RegisterView(TacticalGame.CombatFeed) + TextManager:SetCurrentView("combat_feed") end ---Resets Tactical mode. @@ -419,6 +405,7 @@ function Reset_Tactical() Reset_Tactical_Stats() TacticalGame = nil GUIManager:Reset() + TextManager:Reset() end --- ================================================== @@ -428,18 +415,19 @@ end ---Initializes Skirmish mode. ---@param gameMode string The tactical game mode. function Init_Skirmish(gameMode) - DebugMessage("%s -- Initializing Skirmish Tacitcal %s rules.", tostring(Script), gameMode) + DebugMessage("%s -- Initializing Skirmish Tactical %s rules.", tostring(Script), gameMode) Reset_Stats() - SkirmishGame = SkirmishGameClass:New(gameMode) - TacticalGame = SkirmishGame.TacticalGame - GUIManager:InitSkirmish(SkirmishGame) + TacticalGame = SkirmishGameClass:New(gameMode) + GUIManager:InitSkirmish(TacticalGame.IsLocalSpectator) + TextManager:RegisterView(TacticalGame.CombatFeed) + TextManager:SetCurrentView("combat_feed") end ---Resets Skirmish mode. function Reset_Skirmish() DebugMessage("%s -- Resetting Skirmish Tactical rules.", tostring(Script)) Reset_Stats() - SkirmishGame = nil TacticalGame = nil GUIManager:Reset() + TextManager:Reset() end