Skip to content

Commit

Permalink
Quicken inevitable voting results (FAForever#5088)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hdt80bro committed Apr 20, 2024
1 parent 2547d18 commit 24abffa
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 62 deletions.
1 change: 1 addition & 0 deletions lua/aibrain.lua
Expand Up @@ -1211,6 +1211,7 @@ AIBrain = Class(AIBrainHQComponent, AIBrainStatisticsComponent, AIBrainJammerCom
end,

OnRecalled = function(self)
-- TODO: create a common function for `OnDefeat` and `OnRecall`
self.Status = "Recalled"

local army = self.Army
Expand Down
99 changes: 63 additions & 36 deletions lua/sim/Recall.lua
Expand Up @@ -2,7 +2,7 @@
--** Shared under the MIT license
--**************************************************************************************************

-- import recall parameters
-- collect recall parameters (note it is not imported)
doscript "/lua/shared/RecallParams.lua"

-- TODO: generalize to abstract voting system, decoupled from recall
Expand Down Expand Up @@ -64,9 +64,8 @@ function OnArmyChange()
StartTime = votingThreadBrain.RecallVoteStartTime,
Open = VoteTime * 0.1,
Blocks = teamSize,
-- TODO: rename to `Yes` and `No`
Accept = yes,
Veto = no,
Yes = yes,
No = no,
CanVote = GetArmyBrain(focus).Vote ~= nil,
}
end
Expand All @@ -76,16 +75,16 @@ end
---@param data {From: number, To: number}
function OnAllianceChange(data)
local armyFrom, armyTo = data.From, data.To
local oldTeammates = 0
local oldTeamSize = 0
local oldTeam = {}
local votingThreadBrain
for index, ally in ArmyBrains do
if (IsAlly(armyFrom, index) or IsAlly(armyTo, index))
and not ally:IsDefeated()
and not ArmyIsCivilian(index)
then
oldTeammates = oldTeammates + 1
oldTeam[oldTeammates] = ally.Nickname
oldTeamSize = oldTeamSize + 1
oldTeam[oldTeamSize] = ally.Nickname
-- Found a voting thread. We really do need a better way to handle team data...
if ally.recallVotingThread then
votingThreadBrain = ally
Expand All @@ -95,7 +94,7 @@ function OnAllianceChange(data)
if votingThreadBrain then
SPEW("Canceling recall voting for team " .. table.concat(oldTeam, ", ") .. " due to alliance break")
votingThreadBrain.VoteCancelled = true
coroutine.resume(votingThreadBrain.recallVotingThread)
ResumeThread(votingThreadBrain.recallVotingThread)
if IsAlly(votingThreadBrain, GetFocusArmy()) then
SyncCancelRecallVote()
SyncRecallStatus()
Expand All @@ -112,7 +111,7 @@ end
function RecallRequestCooldown(lastTeamVote, lastPlayerRequest, playerGatein)
-- note that this doesn't always return the reason that currently has the longest cooldown, it
-- returns the more "fundamental" one (i.e. the reason whose base cooldown is longest)
-- this is more useful in reporting the reason, and isn't a problem when put in a loop
-- this is more useful in reporting the reason, and isn't a problem as the reason checker is a loop
local gametime = GetGameTick()
local gateCooldown = (playerGatein or 0) + PlayerGateCooldown - gametime
if gateCooldown > 0 then
Expand Down Expand Up @@ -186,12 +185,12 @@ local function RecallVotingThread(requestingArmy)

local gametick = GetGameTick()
local yesVotes = 0
local teammates = 0
local teamSize = 0
local team = {}
for index, brain in ArmyBrains do
if not brain:IsDefeated() and IsAlly(requestingArmy, brain.Army) and not ArmyIsCivilian(index) then
teammates = teammates + 1
team[teammates] = brain
teamSize = teamSize + 1
team[teamSize] = brain
if brain.RecallVote then
yesVotes = yesVotes + 1
end
Expand All @@ -200,7 +199,7 @@ local function RecallVotingThread(requestingArmy)
end
end
-- this function is found in the recall params file, for those looking
local recallPassed = RecallRequestAccepted(yesVotes, teammates)
local recallPassed = RecallRequestAccepted(yesVotes, teamSize)
if focus ~= -1 and IsAlly(focus, requestingArmy) then
SyncCloseRecallVote(recallPassed)
-- the recall UI will handle the announcement in this case
Expand All @@ -212,16 +211,16 @@ local function RecallVotingThread(requestingArmy)
}
end
local listTeam = team[1].Nickname
for i = 2, teammates do
for i = 2, teamSize do
listTeam = listTeam .. ", " .. team[i].Nickname
end
if recallPassed then
SPEW("Recalling team " .. listTeam .. " at the request of " .. requestingBrain.Nickname .. " (vote passed " .. yesVotes .. " to " .. (teammates - yesVotes ) .. ")")
SPEW("Recalling team " .. listTeam .. " at the request of " .. requestingBrain.Nickname .. " (vote passed " .. yesVotes .. " to " .. (teamSize - yesVotes ) .. ")")
for _, brain in team do
brain:RecallAllCommanders()
end
else
SPEW("Not recalling team " .. listTeam .. " (vote failed " .. yesVotes .. " to " .. (teammates - yesVotes ) .. ")")
SPEW("Not recalling team " .. listTeam .. " (vote failed " .. yesVotes .. " to " .. (teamSize - yesVotes ) .. ")")
requestingBrain.LastRecallRequestTime = gametick
end
if focus ~= -1 and IsAlly(requestingArmy, focus) then
Expand All @@ -238,16 +237,20 @@ end
---@return boolean # if further user sync should happen
local function ArmyVoteRecall(army, vote, lastVote)
if lastVote then
local foundThread = false
for index, ally in ArmyBrains do
if army ~= index and IsAlly(army, index) and not ally:IsDefeated() then
if army ~= index and IsAlly(army, index) then
local thread = ally.recallVotingThread
if thread then
-- end voting period
ResumeThread(thread)
ResumeThread(thread) -- end voting period
foundThread = true
break
end
end
end
if not foundThread then
SPEW("Unable to find recall voting thread for " .. GetArmyBrain(army).Nickname .. '!')
end
end

local focus = GetFocusArmy()
Expand All @@ -272,9 +275,7 @@ local function ArmyRequestRecall(army, teammates)
end
else
-- it's just us; recall our army
SPEW("Recalling " .. brain.Nickname)
brain:RecallAllCommanders()

end
end

Expand All @@ -291,30 +292,44 @@ function SetRecallVote(data)
end
return
end
local brain = GetArmyBrain(army)
if brain:IsDefeated() then
SyncCannotRequestRecall("observer")
SPEW("Defeated army " .. tostring(army) .. " (" .. GetArmyBrain(army).Nickname .. ") trying to vote for recall!")
return
end
local vote = data.Vote and true or false

-- determine team voting status
local isRequest = true
local lastVote = true
local likeVotes = 0
local teammates = 0
local team = {}
for index, ally in ArmyBrains do
if army ~= index and not ally:IsDefeated() and IsAlly(army, index) and not ArmyIsCivilian(index) then
if ally.BrainType ~= "Human" then
if army == focus then
SyncCannotRequestRecall("ai")
if army ~= index and IsAlly(army, index) and not ArmyIsCivilian(index) then
if not ally:IsDefeated() then
if ally.BrainType ~= "Human" then
if army == focus then
SyncCannotRequestRecall("ai")
end
return
end
if ally.RecallVote == vote then
likeVotes = likeVotes + 1
end
return

local allyHasVoted = ally.RecallVote ~= nil
lastVote = lastVote and allyHasVoted -- only the last vote if all allies have also voted
isRequest = isRequest and not allyHasVoted -- only a request if no allies have voted yet
teammates = teammates + 1
team[teammates] = ally.Nickname
elseif ally.recallVotingThread then
isRequest = false
end
local allyHasVoted = ally.RecallVote ~= nil
lastVote = lastVote and allyHasVoted -- only the last vote if all allies have also voted
isRequest = isRequest and not allyHasVoted -- only the last vote if no allies have voted
teammates = teammates + 1
team[teammates] = ally.Nickname
end
end

local brain = GetArmyBrain(army)
if isRequest then
-- the player is making a recall request; this will reset their recall request cooldown
local reason = ArmyRecallRequestCooldown(army)
Expand All @@ -324,14 +339,26 @@ function SetRecallVote(data)
end
return
end
SPEW("Army " .. tostring(army) .. " is requesting recall for " .. table.concat(team, ','))
if teammates > 0 then
SPEW("Recall request from " .. brain.Nickname .. " for " .. table.concat(team, ','))
else
SPEW("Recalling " .. brain.Nickname)
end
brain.RecallVote = vote
ArmyRequestRecall(army, teammates)
else
-- the player is responding to a recall request; we don't count this against their
-- individual recall request cooldown
SPEW("Army " .. tostring(army) .. " recall vote: " .. (vote and "yes" or "no"))
SPEW("Recall vote for " .. brain.Nickname .. ": " .. (vote and "yes" or "no"))
brain.RecallVote = vote

-- if the vote will already be decided with this vote, close the voting session
if not lastVote and (
vote and RecallRequestAccepted(likeVotes + 1, teammates) or -- will succeed with our vote
not vote and not RecallRequestAccepted(teammates - (likeVotes + 1), teammates) -- won't ever be able to succeed
) then
lastVote = true
end
ArmyVoteRecall(army, vote, lastVote)
end
end
Expand Down Expand Up @@ -374,9 +401,9 @@ function SyncRecallVote(vote)
Sync.RecallRequest = recallSync
end
if vote then
recallSync.Accept = (recallSync.Accept or 0) + 1
recallSync.Yes = (recallSync.Yes or 0) + 1
else
recallSync.Veto = (recallSync.Veto or 0) + 1
recallSync.No = (recallSync.No or 0) + 1
end
end

Expand Down
44 changes: 21 additions & 23 deletions lua/ui/game/recall.lua
Expand Up @@ -42,7 +42,7 @@ function SetLayout()
local Scale = LayoutHelpers.ScaleNumber
local height = Scale(-4) + panel.label.Height() + Scale(5) + panel.votes.Height()
-- make sure these register as a dependency
local voteHeight = panel.buttonAccept.Height()
local voteHeight = panel.buttonYes.Height()
local progHeight = panel.progressBarBG.Height()
local startTime = panel.startTime()
if panel.canVote() then
Expand Down Expand Up @@ -73,7 +73,7 @@ function RequestHandler(data)
if data.Open then
panel:StartVote(data.Blocks, data.Open, data.CanVote, data.StartTime)
end
local yes, no = data.Accept, data.Veto -- TODO: rename to `Yes` and `No`
local yes, no = data.Yes, data.No
if yes or no then
panel:AddVotes(yes, no)
end
Expand All @@ -95,9 +95,8 @@ RecallPanel = ClassUI(NinePatch.NinePatch) {
self.collapseArrow = UIUtil.CreateCollapseArrow(parent, "t")
self.label = UIUtil.CreateText(self, "<LOC diplomacy_0018>Ready for recall", 18, UIUtil.bodyFont, true)
self.votes = Group(self)
-- TODO: rename to `buttonYes` and `buttonNo`
self.buttonAccept = UIUtil.CreateButtonStd(self, "/widgets02/small", "<LOC diplomacy_0016>Yes", 16)
self.buttonVeto = UIUtil.CreateButtonStd(self, "/widgets02/small", "<LOC diplomacy_0017>No", 16)
self.buttonYes = UIUtil.CreateButtonStd(self, "/widgets02/small", "<LOC diplomacy_0016>Yes", 16)
self.buttonNo = UIUtil.CreateButtonStd(self, "/widgets02/small", "<LOC diplomacy_0017>No", 16)
self.progressBarBG = UIUtil.CreateBitmapColor(self, "Gray")
self.progressBar = UIUtil.CreateBitmapColor(self.progressBarBG, "Yellow")

Expand Down Expand Up @@ -135,12 +134,12 @@ RecallPanel = ClassUI(NinePatch.NinePatch) {
end)
:End()

local buttonYes = Layouter(self.buttonAccept)
local buttonYes = Layouter(self.buttonYes)
:AtLeftIn(self, 8)
:AnchorToBottom(votes, 5)
:End()

local buttonNo = Layouter(self.buttonVeto)
local buttonNo = Layouter(self.buttonNo)
:AtRightIn(self, 8)
:AnchorToBottom(votes, 5)
:End()
Expand All @@ -160,9 +159,8 @@ RecallPanel = ClassUI(NinePatch.NinePatch) {
:End()

Tooltip.AddCheckboxTooltip(collapseArrow, "voting_collapse")
-- TODO: rename to `dip_recall_request_yes` and `dip_recall_request_no`
Tooltip.AddButtonTooltip(buttonYes, "dip_recall_request_accept")
Tooltip.AddButtonTooltip(buttonNo, "dip_recall_request_veto")
Tooltip.AddButtonTooltip(buttonYes, "dip_recall_request_yes")
Tooltip.AddButtonTooltip(buttonNo, "dip_recall_request_no")
end,

LayoutBlocks = function(self, blocks)
Expand Down Expand Up @@ -243,8 +241,8 @@ RecallPanel = ClassUI(NinePatch.NinePatch) {
local function ShowForVote(button, hide)
return not hide and not self.canVote()
end
self.buttonAccept.OnHide = ShowForVote
self.buttonAccept.OnClick = function()
self.buttonYes.OnHide = ShowForVote
self.buttonYes.OnClick = function()
SimCallback({
Func = "SetRecallVote",
Args = {
Expand All @@ -254,8 +252,8 @@ RecallPanel = ClassUI(NinePatch.NinePatch) {
})
self:SetCanVote(false)
end
self.buttonVeto.OnHide = ShowForVote
self.buttonVeto.OnClick = function()
self.buttonNo.OnHide = ShowForVote
self.buttonNo.OnClick = function()
SimCallback({
Func = "SetRecallVote",
Args = {
Expand All @@ -268,8 +266,8 @@ RecallPanel = ClassUI(NinePatch.NinePatch) {
end,

SetCanVote = function(self, canVote)
local buttonYes = self.buttonAccept
local buttonNo = self.buttonVeto
local buttonYes = self.buttonYes
local buttonNo = self.buttonNo
self.canVote:Set(canVote)
if canVote then
buttonYes:Show()
Expand Down Expand Up @@ -338,11 +336,11 @@ RecallPanel = ClassUI(NinePatch.NinePatch) {
if passed then
self:OnVoteAccepted()
else
self:OnVoteVetoed()
self:OnVoteRejected()
end
end,

AddVotes = function(self, accept, veto)
AddVotes = function(self, yes, no)
local votes = self.votes
if votes.blocks < 3 then return end
local function SetTextures(vote, filename)
Expand All @@ -357,16 +355,16 @@ RecallPanel = ClassUI(NinePatch.NinePatch) {
break
end
end
if accept then
for _ = 1, accept do
if yes then
for _ = 1, yes do
local vote = votes[index]
index = index + 1
vote.cast = "yes"
SetTextures(vote, "/game/recall-panel/recall-accept")
end
end
if veto then
for _ = 1, veto do
if no then
for _ = 1, no do
local vote = votes[index]
index = index + 1
vote.cast = "no"
Expand Down Expand Up @@ -431,7 +429,7 @@ RecallPanel = ClassUI(NinePatch.NinePatch) {
self.label:SetText(LOC("<LOC diplomacy_0023>Recalling..."))
end,

OnVoteVetoed = function(self)
OnVoteRejected = function(self)
import("/lua/ui/game/announcement.lua").CreateAnnouncement(LOC("<LOC diplomacy_0022>The recall vote did not pass."))
self.label:SetText(LOC("<LOC diplomacy_0024>Not ready for recall"))
end,
Expand Down
5 changes: 2 additions & 3 deletions lua/ui/help/tooltips.lua
Expand Up @@ -1756,12 +1756,11 @@ Tooltips = {
title = "<LOC tooltipui0726>Cannot Recall",
description = "<LOC tooltipui0731>Your team has had a recall vote too recently.",
},
-- TODO: rename to `dip_recall_request_yes` and `dip_recall_request_no`
dip_recall_request_accept = {
dip_recall_request_yes = {
title = "<LOC tooltipui0732>Yes Vote",
description = "<LOC tooltipui0733>Vote yes to your team recalling from battle as a defeat.",
},
dip_recall_request_veto = {
dip_recall_request_no = {
title = "<LOC tooltipui0734>No Vote",
description = "<LOC tooltipui0735>Vote no to your team recalling from battle as a defeat.",
},
Expand Down

0 comments on commit 24abffa

Please sign in to comment.