289 lines
10 KiB
Lua
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
|