Files
Dota-Zombie-Invasion/scripts/vscripts/skip_night_vote_manager.lua
T
2026-05-29 15:11:31 +07:00

289 lines
10 KiB
Lua

local ____lualib = require("lualib_bundle")
local __TS__Class = ____lualib.__TS__Class
local Set = ____lualib.Set
local __TS__New = ____lualib.__TS__New
local __TS__Spread = ____lualib.__TS__Spread
local __TS__ArrayIncludes = ____lualib.__TS__ArrayIncludes
local ____exports = {}
local ____DayNightCycleManager = require("DayNightCycleManager")
local DayNightCycleManager = ____DayNightCycleManager.DayNightCycleManager
local ____player_connection_state = require("utils.player_connection_state")
local DOTA_CONNECTION_STATE = ____player_connection_state.DOTA_CONNECTION_STATE
local VOTE_DURATION_SEC = 30
--- Пауза после любого исхода опроса, прежде чем снова можно предложить голосование.
local COOLDOWN_AFTER_SEC = 12
--- Сколько держать карточку с итогами после досрочного завершения (все ответили).
local RESULT_SHOW_SEC = 3
local POLL_TIMER = "SkipNightVotePoll"
--- Голосование: пропустить остаток дня и сразу начать ночь (клик по таймеру в HUD).
____exports.SkipNightVoteManager = __TS__Class()
local SkipNightVoteManager = ____exports.SkipNightVoteManager
SkipNightVoteManager.name = "SkipNightVoteManager"
SkipNightVoteManager.____file_path = "scripts/vscripts/skip_night_vote_manager.lua"
function SkipNightVoteManager.prototype.____constructor(self)
self.phase = "idle"
self.endGameTime = 0
self.resultPassed = false
self.yesVoters = __TS__New(Set)
self.noVoters = __TS__New(Set)
self.cooldownUntil = 0
CustomGameEventManager:RegisterListener(
"skip_night_vote_propose",
function(src, data)
local playerId = data and data.PlayerID
if playerId == nil or playerId < 0 then
return
end
self:onPropose(playerId)
end
)
CustomGameEventManager:RegisterListener(
"skip_night_vote_yes",
function(src, data)
local playerId = data and data.PlayerID
if playerId == nil or playerId < 0 then
return
end
self:onVoteYes(playerId)
end
)
CustomGameEventManager:RegisterListener(
"skip_night_vote_no",
function(src, data)
local playerId = data and data.PlayerID
if playerId == nil or playerId < 0 then
return
end
self:onVoteNo(playerId)
end
)
end
function SkipNightVoteManager.getInstance(self)
if not ____exports.SkipNightVoteManager.instance then
____exports.SkipNightVoteManager.instance = __TS__New(____exports.SkipNightVoteManager)
end
return ____exports.SkipNightVoteManager.instance
end
function SkipNightVoteManager.prototype.getVotablePlayerIds(self)
local out = {}
do
local i = 0
while i < DOTA_MAX_PLAYERS do
do
local p = i
if not PlayerResource:IsValidPlayerID(p) then
goto __continue12
end
if PlayerResource:GetConnectionState(p) ~= DOTA_CONNECTION_STATE.CONNECTED then
goto __continue12
end
local hero = PlayerResource:GetSelectedHeroEntity(p)
if not hero or not IsValidEntity(hero) or not hero:IsRealHero() then
goto __continue12
end
out[#out + 1] = p
end
::__continue12::
i = i + 1
end
end
return out
end
function SkipNightVoteManager.prototype.majorityNeeded(self, eligible)
if eligible <= 0 then
return 9999
end
return math.floor(eligible / 2) + 1
end
function SkipNightVoteManager.prototype.getEligibleAndPruneVotes(self)
local eligible = self:getVotablePlayerIds()
local valid = __TS__New(Set, eligible)
for ____, p in ipairs({__TS__Spread(self.yesVoters)}) do
if not valid:has(p) then
self.yesVoters:delete(p)
end
end
for ____, p in ipairs({__TS__Spread(self.noVoters)}) do
if not valid:has(p) then
self.noVoters:delete(p)
end
end
return eligible
end
function SkipNightVoteManager.prototype.buildVotesByPlayer(self, eligible)
local out = {}
for ____, p in ipairs(eligible) do
if self.yesVoters:has(p) then
out[tostring(p)] = 1
elseif self.noVoters:has(p) then
out[tostring(p)] = -1
else
out[tostring(p)] = 0
end
end
return out
end
function SkipNightVoteManager.prototype.sendUpdate(self, eligibleArg)
local eligible = eligibleArg or self:getEligibleAndPruneVotes()
local need = self:majorityNeeded(#eligible)
local votesByPlayer = self:buildVotesByPlayer(eligible)
CustomGameEventManager:Send_ServerToAllClients("skip_night_vote_update", {
active = self.phase == "idle" and 0 or 1,
canVote = self.phase == "voting" and 1 or 0,
isResult = self.phase == "result" and 1 or 0,
resultPassed = self.resultPassed and 1 or 0,
endGameTime = self.endGameTime,
yesCount = self.yesVoters.size,
noCount = self.noVoters.size,
needCount = need,
totalEligible = #eligible,
eligiblePlayerIds = eligible,
votesByPlayer = votesByPlayer
})
end
function SkipNightVoteManager.prototype.clearVote(self, _reason)
self.phase = "idle"
self.resultPassed = false
self.endGameTime = 0
self.yesVoters:clear()
self.noVoters:clear()
Timers:RemoveTimer(POLL_TIMER)
self:sendUpdate()
self.cooldownUntil = GameRules:GetGameTime() + COOLDOWN_AFTER_SEC
end
function SkipNightVoteManager.prototype.onPropose(self, proposer)
if GameRules:State_Get() ~= DOTA_GAMERULES_STATE_GAME_IN_PROGRESS then
self:sendProposerError(proposer, "not_ingame")
return
end
local d = DayNightCycleManager:getInstance()
if not d:CanVoteSkipToNight() then
self:sendProposerError(proposer, "not_day")
return
end
local g = GameRules:GetGameTime()
if g < self.cooldownUntil then
self:sendProposerError(proposer, "cooldown")
return
end
if self.phase ~= "idle" then
self:sendProposerError(proposer, "already")
return
end
local votable = self:getEligibleAndPruneVotes()
if #votable < 1 then
self:sendProposerError(proposer, "no_players")
return
end
self.phase = "voting"
self.resultPassed = false
self.yesVoters:clear()
self.noVoters:clear()
self.endGameTime = g + VOTE_DURATION_SEC
self:sendUpdate(votable)
Timers:RemoveTimer(POLL_TIMER)
Timers:CreateTimer(
POLL_TIMER,
{
endTime = 0,
callback = function()
if self.phase == "idle" then
return nil
end
if self.phase == "result" then
if GameRules:GetGameTime() >= self.endGameTime then
self:clearVote("success")
return nil
end
self:sendUpdate()
return 0.25
end
local d2 = DayNightCycleManager:getInstance()
if not d2:CanVoteSkipToNight() then
self:clearVote("invalid")
return nil
end
local eligible = self:getEligibleAndPruneVotes()
if #eligible < 1 then
self:clearVote("invalid")
return nil
end
if GameRules:GetGameTime() >= self.endGameTime then
local need = self:majorityNeeded(#eligible)
local passed = self.yesVoters.size >= need
if passed then
local ok = d2:TryInstantSwitchDayToNightFromVote()
if not ok then
self:clearVote("invalid")
return nil
end
end
self:clearVote("timeout")
return nil
end
local votedCount = self.yesVoters.size + self.noVoters.size
local allVoted = votedCount >= #eligible
if allVoted then
local need = self:majorityNeeded(#eligible)
local passed = self.yesVoters.size >= need
if passed then
local ok = d2:TryInstantSwitchDayToNightFromVote()
if not ok then
self:clearVote("invalid")
return nil
end
end
self.phase = "result"
self.resultPassed = passed
self.endGameTime = GameRules:GetGameTime() + RESULT_SHOW_SEC
self:sendUpdate(eligible)
return 0.25
end
self:sendUpdate(eligible)
return 0.25
end
}
)
end
function SkipNightVoteManager.prototype.onVoteYes(self, playerId)
if self.phase ~= "voting" then
return
end
if GameRules:GetGameTime() >= self.endGameTime then
return
end
local eligible = self:getEligibleAndPruneVotes()
if not __TS__ArrayIncludes(eligible, playerId) then
return
end
self.noVoters:delete(playerId)
self.yesVoters:add(playerId)
self:sendUpdate(eligible)
end
function SkipNightVoteManager.prototype.onVoteNo(self, playerId)
if self.phase ~= "voting" then
return
end
if GameRules:GetGameTime() >= self.endGameTime then
return
end
local eligible = self:getEligibleAndPruneVotes()
if not __TS__ArrayIncludes(eligible, playerId) then
return
end
self.yesVoters:delete(playerId)
self.noVoters:add(playerId)
self:sendUpdate(eligible)
end
function SkipNightVoteManager.prototype.sendProposerError(self, proposer, _code)
do
pcall(function()
local p = PlayerResource:GetPlayer(proposer)
if p then
CustomGameEventManager:Send_ServerToPlayer(p, "skip_night_vote_error", {code = "skip_night_err_" .. _code})
end
end)
end
end
return ____exports