This is due for years now, though I wish I didn't forget to look into it so much..
The main issue lies here:
local function LoadSaveDataFromMissingSpawnInfo(player, missingspawninfo) -- add this -- can also check for _timetoattack < 5 or so, and extend the saved _timetoattack -- if you don't want the player to get attacked while loading or very very shortly after if _warning and not missingspawninfo._spawninfo then --print("[hounded][LoadSaveDataFromMissingSpawnInfo] Currently in warning phase and player record had no spawns set, not setting delayed spawn info.", player.name, player.userid) return end _delayedplayerspawninfo[player] = { _warning = missingspawninfo._warning, _timetoattack = missingspawninfo._timetoattack, _warnduration = missingspawninfo._warnduration, _timetonextwarningsound = missingspawninfo._timetonextwarningsound, _announcewarningsoundinterval = missingspawninfo._announcewarningsoundinterval, } if missingspawninfo._targetstatus then _targetableplayers[player.GUID] = missingspawninfo._targetstatus end if missingspawninfo._spawninfo then local spawninforec = missingspawninfo._spawninfo _delayedplayerspawninfo[player]._spawninfo = { players = {[player] = spawninforec.count}, timetonext = spawninforec.timetonext, averageplayerage = spawninforec.averageplayerage, } end end
Basically, this is what happens:
- A player leaves during the warning phase, the info about the scheduled attack is saved for them.
- That same player returns during the warning phase (unavoidable if there's no other players in the shard to keep the main timer going).
- The saved info gets set to be handled on its own, but.. the player is also still being handled by the main attack.
- This results in both the main attack and the delayed info going through, essentially running two sets of spawns for this player.
- Note: the issue doesn't happen if the warning phase is over and spawns are determined before the player comes back.
The line I added basically just prevents the saved scheduled attack from being set if the current attack is in the warning phase, since the player can just become a part of it again at that point, unless the player left after the warning phase was over and this is the warning of another attack, in which case ignore them for the main attack and let the delayed one take over (there's a few other issues that need to be addressed to account for this, shown further below).
As noted, a check for the time to attack being too short can also be added, to in which case, let the player get handled individually outside of the main attack, and ensuring the saved time to attack is also a certain minimum, to avoid targeting the player while loading in, or very shortly after.
Lastly, it would be possible to track which attack is which with a counter that gets saved by the game, and is stored for players that leave, if you want to really really confirm when they come back that the attack is indeed the same one.
Anyhow, fixing the issue alone won't do, as there's other issues that need to be addressed.
The "water immunity" checks:
-- vanilla local function ClearWaterImunity() for GUID,data in pairs(_targetableplayers) do _targetableplayers[GUID] = nil end end -- tweaked local function ClearWaterImunity() for GUID,data in pairs(_targetableplayers) do local player = Ents[GUID] -- ensure water immunity isn't cleared if a delayed spawn is being handled for this player, let that handle it instead if not player or not _delayedplayerspawninfo[player] then _targetableplayers[GUID] = nil end end end -- vanilla local function CheckForWaterImunityAllPlayers() for i, v in ipairs(_activeplayers) do CheckForWaterImunity(v) end end -- tweaked local function CheckForWaterImunityAllPlayers() for i, v in ipairs(_activeplayers) do -- ensure water immunity isn't checked if a delayed spawn is being handled for this player, let that handle it instead if not _delayedplayerspawninfo[v] then CheckForWaterImunity(v) end end end
(also please take a look at this report, this doesn't seem intended given that ice tiles are temporary tiles in the middle of the ocean)
The warning speeches:
-- vanilla function self:DoWarningSpeech() for GUID,data in pairs(_targetableplayers) do if data == "land" then local player = Ents[GUID] player:DoTaskInTime(math.random() * 2, _DoWarningSpeech) end end end -- tweaked function self:DoWarningSpeech() for GUID,data in pairs(_targetableplayers) do if data == "land" then local player = Ents[GUID] -- ignore player if they have a delayed spawn, let that handle it if player and not _delayedplayerspawninfo[player] then player:DoTaskInTime(math.random() * 2, _DoWarningSpeech) end end end end
The warning sounds:
-- vanilla function self:DoWarningSound() for k,v in pairs(self:GetWarningSoundList()) do if _timetoattack <= v.time or _timetoattack == nil then for GUID,data in pairs(_targetableplayers)do local player = Ents[GUID] if player and data == "land" then player:PushEvent("houndwarning",HOUNDWARNINGTYPE[v.sound]) end end break end end end -- tweaked function self:DoWarningSound() for k,v in pairs(self:GetWarningSoundList()) do if _timetoattack <= v.time or _timetoattack == nil then for GUID,data in pairs(_targetableplayers)do if data == "land" then local player = Ents[GUID] -- ignore player if they have a delayed spawn, let that handle it if player and not _delayedplayerspawninfo[player] then player:PushEvent("houndwarning",HOUNDWARNINGTYPE[v.sound]) end end end break end end end
One oversight in the OnUpdate function:
-- vanilla function self:OnUpdate(dt) if _spawnmode == "never" then return end for player, data in pairs (_delayedplayerspawninfo) do data._timetoattack = data._timetoattack - dt if data._timetoattack < 0 then _warning = false -- tweaked function self:OnUpdate(dt) if _spawnmode == "never" then return end for player, data in pairs (_delayedplayerspawninfo) do data._timetoattack = data._timetoattack - dt if data._timetoattack < 0 then -- correctly disable the warning in the scheduled spawn, instead of touching _warning data._warning = false
And lastly, GetWaveAmounts (very important):
-- vanilla local function GetWaveAmounts() -- first bundle up the players into groups based on proximity -- we want to send slightly reduced hound waves when players are clumped so that -- the numbers aren't overwhelming local groupindex = {} local nextgroup = 1 for i, playerA in ipairs(_activeplayers) do for j, playerB in ipairs(_activeplayers) do -- tweaked local function GetWaveAmounts() -- first bundle up the players into groups based on proximity -- we want to send slightly reduced hound waves when players are clumped so that -- the numbers aren't overwhelming local groupindex = {} local nextgroup = 1 local valid_players = shallowcopy(_activeplayers) -- ignore players being handled by a delayed spawn for i, player in ipairs(valid_players) do if _delayedplayerspawninfo[player] then table.remove(valid_players, i) end end for i, playerA in ipairs(valid_players) do for j, playerB in ipairs(valid_players) do
If a player has a delayed spawn being handled (like from a previous attack if they left a while ago and came back during another one), ignore them when determining spawns. More of an edge case thing, but still worth accounting for it. This is also the second bit of why the duplicated spawns happen, there's no accounting for players with delayed spawns being handled.
And uh, that's about it, I apologize that this is a bit big. I attached the file override I used to test all this, if that'd make more digestible.
One last thing? Please look into this report comment regarding the Greater Depth Worm scheduled spawn not saving? It's been there for a good while now, it wouldn't take much to fix it.
Edit: yeah I figured there'd be some specifics with _wave_pre_upgraded that need to be covered with delayed spawns, so here's what I found.
Normally, a main attack will use the worm boss upgrade if available, set it as "used", and then on planning the next attack, it will get set to nil and the process will restart basically.
There's two issues with that. One is that if a worm boss wave happens but no spawns happen (possible due to spawnsToRelease being 0, for players too new to the server, due to another reported issue, but could also happen normally if a newer player stays and older players leave), the boss won't spawn, _wave_pre_upgraded will be set to nil, and the wave upgrade chance will still reset.
In caves.lua:
specialupgradecheck = function(wave_pre_upgraded, wave_override_chance, _wave_override_settings) wave_pre_upgraded = nil -- HERE, don't set to nil, preserve if it's "available" and skip the chance steps below local chance = wave_override_chance * (_wave_override_settings["worm_boss"] or 1) if _wave_override_settings["worm_boss"] ~= 0 and (math.random() < chance or _wave_override_settings["worm_boss"] == 9999) then wave_pre_upgraded = "available" end if wave_pre_upgraded == "available" then wave_override_chance = 0 elseif TheWorld.state.cycles > TUNING.WORM_BOSS_DAYS then wave_override_chance = math.min(0.5, wave_override_chance + 0.05) end return wave_pre_upgraded, wave_override_chance end,
The other issue is that a delayed player attack might spawn a worm boss, but won't set _wave_pre_upgraded to nil afterwards, meaning it'll stay as "used", which will still cause worm boss warnings and player speech to play (but no worm boss will spawn), for both main attacks, or other players with delayed worm attacks.
You could do something like this, or handle it through HandleSpawnInfoRec itself:
function self:OnUpdate(dt) if _spawnmode == "never" then return end for player, data in pairs (_delayedplayerspawninfo) do data._timetoattack = data._timetoattack - dt if data._timetoattack < 0 then data._warning = false -- Okay, it's hound-day, get number of dogs for each player if data._spawninfo == nil then GetDelayedPlayerWaveAmounts(player, data) end local groupsdone = {} CheckForWaterImunity(player) for i, spawninforec in ipairs(data._spawninfo) do local has_pre_upgrade = _wave_pre_upgraded == "available" HandleSpawnInfoRec(dt, i, spawninforec, groupsdone) -- remove upgrade if used, in case there's more than one delayed player attack -- so the others get the proper warnings, and to make sure the next main one doesn't incorrectly start with the wrong warning sounds if has_pre_upgrade and _wave_pre_upgraded == "used" then _wave_pre_upgraded = nil end end
On your own, with no other players:
- Start a server with caves.
- Wait until you can hear the warning from a hound attack.
- Disconnect or enter the caves.
- Reconnect or exit the caves.
- Notice how warning sounds and warning speeches are duplicated, and the amount of hounds that'll spawn will be doubled or so. The same thing will apply to the worms in the caves if exiting the caves or disconnecting during the warning and reentering/reconnecting.
With more players, or by spawning fake players:
- Start a server with caves.
- Wait until you can hear the warning from a hound attack.
- Enter the caves.
- Return to the surface after the warnings have stopped up there.
- Notice how things work fine.
- Repeat the steps but instead return to the surface while the warning is still going up there.
- Notice the duplicated warning sounds, warning speeches, and amount of hounds.
-
2
A developer has marked this issue as fixed. This means that the issue has been addressed in the current development build and will likely be in the next update.
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now