Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions engine/Sim.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1171,8 +1171,8 @@ function SetArmyUnitCap(army, unitCap)
end

--- Sets the command source of an army to match another army's command source.
---@param targetArmyIndex number # The target army that we're managing command sources for
---@param sourceHumanIndex number # The source index that is added or removed as a command source
---@param targetArmyIndex number # The target army that we're managing command sources for. This is a 0-based index, unlike army indices that are 1-based.
---@param sourceHumanIndex number # The source index that is added or removed as a command source. This is a 0-based index, including only actual sources. Mimics the index of `GetClientSessions` in the UI.
---@param enable boolean # Whether or not the source index is a command source for the target army
function SetCommandSource(targetArmyIndex, sourceHumanIndex, enable)
end
Expand Down
4 changes: 4 additions & 0 deletions loc/RU/strings_db.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5341,6 +5341,10 @@ lobui_CAUDesc="У каждого игрока есть своя армия и с
lobui_CACommon="Единая армия, союзный контроль"
lobui_CACDesc="Каждая команда - это одна армия. Все юниты и ресурсы общие, все члены команды могут отдавать команды."

lobui_CAUponDisconnectTitle="Союзный контроль при отключении"
lobui_CAUponDisconnectDescription="Изначально у каждого игрока есть своя собственная армия и собственные ресурсы. Когда игрок отключается, его армия не считается побеждённой, и условие передачи управления не срабатывает. Вместо этого союзные игроки могут переключить фокус на отключившуюся армию и отдавать ей приказы."


lobui_666="Замечено несовпадение версии игры с %s. \r\n - хост: %s (@%s)\r\n - %s: %s (@%s). \r\n\r\nДля предотвращения рассинхронизации, %s был исключен автоматически. Возможно была опубликована новая версия игры. Если это продолжает происходить, то следует создать новое лобби."

AutoLobbyHeaderText="Состояние соединения"
Expand Down
2 changes: 2 additions & 0 deletions loc/US/strings_db.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4713,6 +4713,8 @@ lobui_CAUnion="Multiple armies, union control"
lobui_CAUDesc="Each player has their own army and their own resources. Allied players can switch focus to your army and to issue commands."
lobui_CACommon="Single army, union control"
lobui_CACDesc="Each team is one army. All units and resources are shared, all team members can issue commands."
lobui_CAUponDisconnectTitle="Union control upon disconnect"
lobui_CAUponDisconnectDescription="Initially each player has their own army and their own resources. When a player disconnects the army is not considered defeated and the share condition does not trigger. Instead, allied players can switch focus to the disconnected army to issue commands."

lobui_666="Game version missmatch detected with %s. \r\n - host: %s (@%s)\r\n - %s: %s (@%s). \r\n\r\nTo prevent desyncs, %s is ejected automatically. It is possible that a new game version is released. If this keeps happening then it is better to rehost."

Expand Down
2 changes: 1 addition & 1 deletion lua/AIChatSorian.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function AIChat(group, text, sender)
end
ChatTo:Set(group)
msg = { to = ChatTo(), Chat = true }
msg.text = text
msg.text = LOC(text) or text
msg.aisender = sender
local armynumber = GetArmyData(sender)
if ChatTo() == 'allies' then
Expand Down
1 change: 1 addition & 0 deletions lua/UserSync.lua
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ function OnSync()
import("/lua/ui/game/avatars.lua").FocusArmyChanged()
import("/lua/ui/game/multifunction.lua").FocusArmyChanged()
import("/lua/ui/notify/notify.lua").focusArmyChanged()
import("/lua/ui/game/tabs.lua").FocusArmyChanged()
end

if Sync.CampaignMode then
Expand Down
58 changes: 57 additions & 1 deletion lua/aibrain.lua
Original file line number Diff line number Diff line change
Expand Up @@ -471,13 +471,69 @@ AIBrain = Class(FactoryManagerBrainComponent, StatManagerBrainComponent, JammerM
end
end,

--- Called by the engine when a player disconnects.
--- Attempts to share the control of this (abandoned) army with the remaining allied players.
---@param self AIBrain
---@return boolean # A flag indicating whether the control was successfully shared with any allied player.
ShareControlWithAllies = function(self)
local SyncAIChat = import('/lua/simsyncutils.lua').SyncAIChat

-- An option to share control with allied human players when this army is abandoned, instead of
-- defeating the army straight away. The usual army defeat conditions still apply. This enables
-- previously defeated players to join back into the game by taking control of the abandoned army.

-- The source index is the index in the UI function 'GetSessionClients' we mimic this index here
-- by skipping all non-human brains in the loop below.
local sourceIndex = 0
local armyIndex = self:GetArmyIndex()
local sharedSuccessfully = false

for i, brain in ArmyBrains do
if brain.BrainType ~= 'Human' then continue end
sourceIndex = sourceIndex + 1

-- only take into account allied players are also abandoned
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix incorrect comment.

The comment states "are also abandoned" but the logic on line 496 actually skips abandoned players (if brain.AbandonedAt then continue end). The comment should read "are NOT also abandoned" or "that are not abandoned".

📝 Proposed fix
-            -- only take into account allied players are also abandoned
+            -- only take into account allied players that are not abandoned
🤖 Prompt for AI Agents
In @lua/aibrain.lua at line 494, The inline comment above the check "if
brain.AbandonedAt then continue end" is incorrect (it says allied players "are
also abandoned")—update that comment to say allied players "are NOT also
abandoned" or "that are not abandoned" so it matches the actual logic; locate
the comment near the brain.AbandonedAt check in aibrain.lua and replace the
wording to correctly reflect the skip behavior.

if i == armyIndex then continue end -- do not share with ourselves
if brain.AbandonedAt then continue end -- do not share with other abandoned army (sources)
if not IsAlly(i, armyIndex) then continue end -- do not share with enemies or neutrals

-- These indices in the function 'SetCommandSource' is 0-based instead of 1-based, hence we manually subtract 1 here
SetCommandSource(armyIndex - 1, sourceIndex - 1, true)

-- inform allied players that this happened.
SyncAIChat({sender=self.Nickname, group=sourceIndex, text="<LOC _AbandonedByPlayer>I disconnected, you and all other allies can now switch focus army to me via the scoreboard to issue commands."})

-- inform developers that this happened (we show 1-based index for armies here and a 0-based index for sources)
SPEW(string.format("Army %d %s control shared with army %d %s (source index %d)", armyIndex, tostring(self.Nickname), i, tostring(brain.Nickname), sourceIndex - 1))

-- keep track that we successfully applied union control
sharedSuccessfully = true
end

return sharedSuccessfully
end,

--- Called by the engine when all remaining players that has command control of this army left the game. Command control can be adjusted with `SetCommandSource` and verified with `OkayToMessWithArmy`.
---@param self AIBrain
AbandonedByPlayer = function(self)
if IsGameOver() then
return
end

self.AbandonedAt = GetGameTick()
SPEW(string.format("Army %d %s has been abandoned by all players with command control", self:GetArmyIndex(), tostring(self.Nickname)))

if ScenarioInfo.Options.CommonArmy == "UnionWhenDisconnected" and

-- do not trigger this behavior when this army is already defeated
(not self:IsDefeated())
then
-- attempt to share control of this army with remaining allied players
local succeeded = self:ShareControlWithAllies()
if succeeded then
return
end
end

self:SetDefeatStatus("Defeat")

local opt = ScenarioInfo.Options
Expand Down
6 changes: 6 additions & 0 deletions lua/simInit.lua
Original file line number Diff line number Diff line change
Expand Up @@ -457,12 +457,16 @@ end

--- Setup for union army, where all teams can control the units of its allies
function BeginSessionUnionArmy(teams)
SPEW("Initialing session with union army control...")

local humanIndex = 0
for i, brain in ArmyBrains do
if brain.BrainType ~= 'Human' then continue end
for i2, _ in ArmyBrains do
if not IsAlly(i, i2) then continue end
SetCommandSource(i2 - 1, humanIndex, true)

SPEW("Army " .. tostring(i2) .. " control shared with army " .. tostring(i) .. " (source index " .. tostring(humanIndex) .. ")")
end
humanIndex = humanIndex + 1
end
Expand All @@ -471,6 +475,8 @@ end

--- Setup for common army, where all teams are batched together into one army
function BeginSessionCommonArmy(teams)
SPEW("Initialing session with common army control...")

local humanIndex = 0
local IsHuman = {}
for _, brain in ArmyBrains do
Expand Down
13 changes: 13 additions & 0 deletions lua/ui/game/tabs.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,15 @@ function RemoveModeText(modeID)
UpdateModeDisplay()
end

function ClearModeText()
local keys = table.keys(modes, false)
for k = 1, table.getn(keys) do
modes[keys[k]] = nil
end

UpdateModeDisplay()
end

function UpdateModeDisplay()
if controls.modeDisplay then
controls.modeDisplay:Destroy()
Expand Down Expand Up @@ -1260,3 +1269,7 @@ function UpdateModeDisplay()
controls.modeDisplay.maxCap:DisableHitTest()
end
end

FocusArmyChanged = function()
ClearModeText()
end
7 changes: 6 additions & 1 deletion lua/ui/lobby/lobbyOptions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,14 @@ teamOptions =
help = "<LOC lobui_CADDesc>Each player has their own army and their own resources. Allied players can not issue commands to your army. This is the default Supreme Commander experience.",
key = 'Off'
},
{
text = "<LOC lobui_CAUponDisconnectTitle>Union control upon disconnect",
help = "<LOC lobui_CAUponDisconnectDescription>Initially each player has their own army and their own resources. When a player disconnects the army is not considered defeated and the share condition does not trigger. Instead, allied players can switch focus to the disconnected army to issue commands.",
key = 'UnionWhenDisconnected'
},
{
text = "<LOC lobui_CAUnion>Multiple armies, union control",
help = "<LOC lobui_CAUDesc>Each player has their own army and their own resources. Allied players can switch focus to your army and to issue commands.",
help = "<LOC lobui_CAUDesc>Each player has their own army and their own resources. Allied players can switch focus to your army and to issue commands.",
key = 'Union'
},
{
Expand Down