initial commit
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
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
|
||||
Reference in New Issue
Block a user