386 lines
12 KiB
Lua
386 lines
12 KiB
Lua
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 CombatFeed TutorialTextFeed Text feed for unit builds and kills.
|
|
---@field BuildStatsTable table The engine-owned TacticalBuildStatsTable reference, set from GameScoring.lua.
|
|
---@field KillStatsTable table The engine-owned TacticalKillStatsTable reference, set from GameScoring.lua.
|
|
TacticalGameClass = {}
|
|
TacticalGameClass.__index = TacticalGameClass
|
|
|
|
---Creates a new Tactical Game context.
|
|
---@param gameMode string The game mode.
|
|
---@return TacticalGameClass
|
|
function TacticalGameClass:New(gameMode)
|
|
ScriptMessage("Initializing Tactical game...")
|
|
local self = setmetatable({}, TacticalGameClass)
|
|
|
|
self.GameMode = gameMode
|
|
self.StartTime = GetCurrentTime()
|
|
self.Players = self:_FindPlayers(gameMode)
|
|
self.CombatFeed = TutorialTextFeed:New("combat_feed", 6)
|
|
ScriptMessage("Tactical Game initialized!")
|
|
|
|
return self
|
|
end
|
|
|
|
---Services the Tactical Game.
|
|
function TacticalGameClass:Service()
|
|
ScriptMessage("Servicing Tactical Game...")
|
|
|
|
-- Service Players
|
|
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()
|
|
return StringCompare(self.GameMode, "Space")
|
|
end
|
|
|
|
---Whether this Tactical game is in land mode.
|
|
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)
|
|
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)
|
|
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 GameObjectType The game object type that was built.
|
|
---@param player PlayerObject The player that owns the new game object.
|
|
---@param planet PlanetObject|nil The planet the game object type was built at.
|
|
function TacticalGameClass:UnitBuilt(objectType, player, planet)
|
|
local playerId = player.Get_ID()
|
|
local playerEntry = self.Players[playerId]
|
|
|
|
if playerEntry then
|
|
local unit = playerEntry:AddUnit(objectType)
|
|
|
|
self:UpdateTacticalBuildStatsTable(unit, planet)
|
|
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
|
|
|
|
if not StringCompare(LocalPlayer.Get_Faction_Name(), owner.Faction) then
|
|
-- Do not display combat feed for players on opposite factions
|
|
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 GameObject The game object that was killed.
|
|
---@param killer PlayerObject The player that killed the game object.
|
|
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:UpdateTacticalKillStatsTable(killedUnit, killer)
|
|
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
|
|
|
|
if not StringCompare(LocalPlayer.Get_Faction_Name(), owner.Faction) then
|
|
-- Do not display combat feed for players on opposite factions
|
|
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
|
|
|
|
--- ==================================================
|
|
--- Stat tables
|
|
--- ==================================================
|
|
|
|
---Updates the Tactical Build Stats Table with the build object type.
|
|
---@param unit UnitClass The game object type that was built.
|
|
---@param planet PlanetObject|nil The planet the game object type was built at.
|
|
function TacticalGameClass:UpdateTacticalBuildStatsTable(unit, planet)
|
|
---@type GameObjectType|integer
|
|
local planetType = 1
|
|
|
|
if planet then
|
|
planetType = planet.Get_Type()
|
|
end
|
|
|
|
local playerEntry = self.BuildStatsTable[unit.OwnerId]
|
|
|
|
if not playerEntry then
|
|
playerEntry = {}
|
|
self.BuildStatsTable[unit.OwnerId] = playerEntry
|
|
end
|
|
|
|
local planetEntry = playerEntry[planetType]
|
|
|
|
if not planetEntry then
|
|
planetEntry = {}
|
|
playerEntry[planetType] = planetEntry
|
|
end
|
|
|
|
local typeEntry = planetEntry[unit.Name]
|
|
|
|
if not typeEntry then
|
|
typeEntry = {
|
|
build_count = 1,
|
|
combat_power = unit.CombatRating,
|
|
build_cost = unit.BuildCost,
|
|
score_value = unit.ScoreValue
|
|
}
|
|
planetEntry[unit.Name] = typeEntry
|
|
else
|
|
typeEntry.build_count = typeEntry.build_count + 1
|
|
typeEntry.combat_power = typeEntry.combat_power + unit.CombatRating
|
|
typeEntry.build_cost = typeEntry.build_cost + unit.BuildCost
|
|
typeEntry.score_value = typeEntry.score_value + unit.ScoreValue
|
|
end
|
|
end
|
|
|
|
---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
|
|
-- ==================================================
|
|
|
|
---@private
|
|
---Finds all players of defined Factions via FoCAPI.
|
|
---@param gameMode string The current game mode.
|
|
---@return PlayerClass[]
|
|
function TacticalGameClass:_FindPlayers(gameMode)
|
|
local isSuccess, result = pcall(Get_All_Players)
|
|
|
|
if isSuccess then
|
|
local players = {}
|
|
|
|
for _, playerWrapper in pairs(result) do
|
|
local factionName = playerWrapper.Get_Faction_Name()
|
|
|
|
for _, faction in pairs(Factions) do
|
|
if faction.Name == factionName then
|
|
local player = PlayerClass:New(playerWrapper)
|
|
|
|
players[player.Id] = player
|
|
end
|
|
end
|
|
end
|
|
|
|
return players
|
|
end
|
|
|
|
return self:_FindPlayersFallback(gameMode)
|
|
end
|
|
|
|
---@private
|
|
---Finds all players of defined Factions via starting unit.
|
|
---Note: this will not work when starting units are disabled.
|
|
---For the spectator team, it will only ever find the player in seat 1.
|
|
---@param gameMode string The current game mode.
|
|
---@return PlayerClass[]
|
|
function TacticalGameClass:_FindPlayersFallback(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
|