Skip to content

fix: end a lure once every absent player's logout grace has run out - #18

Merged
XxFran10xX merged 3 commits into
mainfrom
lure-stuck
Oct 23, 2026
Merged

XxFran10xX merged 3 commits into
mainfrom
lure-stuck

Conversation

@XxFran10xX

@XxFran10xX XxFran10xX commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Problem

A lure stayed active while any committed player still owed a death on login. A committed player whose 300 s logout grace ran out was marked to die on next login, and hasLivingOrGrace treated that pending death as a reason to keep the lure. If the only committed player logged out and never came back on that character, the lure never ended:

  • the province got no ambient spawns
  • nobody could place a new lure there
  • uncommitted visitors kept taking deserter damage every second

Fix

Only presence or a running grace holds a lure now. In hasLivingOrGrace, a committed player still counts only if they are online, alive and on the committed character, or still inside their logout grace. Once the grace runs out, that player has left the fight. If nobody committed is present and nobody is still in grace, checkWipe fails the lure through the normal failLure path: hologram and lure furniture removed, clearLure, ambient reconcile, save, wither sound and lure-fail messages. That is the same path as any other failure. Neither path calls the map export, which only publishes province, severity and group, so there is nothing about the lure to update.

The death they owe outlives the failed lure. Infestation.clearLure() wipes deathOnLogin and logoutCharacter, so failing the lure on its own would have erased the penalty. I kept the debt because the player abandoned a running fight. Before clearLure, failLure now moves every identified debt (committed, with a known character, or the account on servers without characters) into a new Infestation.owedDeaths map (account → characters). That map:

  • is not touched by placeLure/clearLure, so a new lure in the same province does not erase it and does not block the account's other characters from joining
  • is saved as owedDeaths in infestations.json. Older files without the field load as empty.
  • is enforced on characterReturned: returning as the owing character kills it (same max(40, health + 10) damage as today). The kill does not remove the debt. Only a settled death does (settleDeath, reached from the death event the kill causes). If the damage is cancelled, reduced, or survived through a totem or invulnerability, the debt stays saved and is owed again on the next return. One return kills at most once, however many debts it owes. A player who is already dead on return is not killed again. If another character of the account has since died, that character's debt is dropped. Another living character's debt waits.
  • is settled by a death through settleDeath: a saved character death (onCharacterDeath) or the player's death as that character, or on servers without characters the account's death
  • also turns on sticky character tracking when loaded, the same way logoutCharacter does, so a provider outage cannot turn a character debt into an account debt

Unidentified (held) debts still end with the lure, as before: no character can be reached to settle them. Victory and admin clear/set remove the whole infestation, and owed deaths with it. Victory behaves as it did in v0.3.2, where a win also wiped pending debts.

Running-lure deaths follow the same rule. Before this PR, a return as the owing character removed deathOnLogin and logoutCharacter before calling damage, so a kill that did not land forgave the death. That was true in v0.3.2 too. Now the kill leaves the debt, commitment and character in place, and settleDeath clears them when the death actually happens. A kill that does not land is owed again on the next return. Across running and failed lures, one return kills at most once. If a death lands, it settles every debt for that character. Two cases still drop the debt without a kill, as before: another character of the account has died, or the account is no longer committed. A player who survives the kill and then runs /lure leave no longer escapes: leaving moves their running-lure debt into owedDeaths instead of erasing it.

Restart path is unchanged. A shutdown is still not a departure, so nobody gets a grace or a debt from it. A lure with nobody back fails on the first tick after startup with no penalty. I extended restartIsNotADepartureSoTheEmptyLureFailsWithoutPenalty to check that nothing is owed and that logging back in deals no damage.

Tests

New in InfestationManagerTest, driven through the real tick:

  • soleCommittedPlayerPastGraceFailsTheLureButStillDiesOnReturningAsThatCharacter: the lure holds during grace. After the grace expires, the next tick fails it: furniture and hologram removed, ambient reconciled, visitor told lure-fail, and no more deserter damage. The debt is owed to ALICE. Returning as BOB does nothing, returning as ALICE deals the kill, the debt stays until the death event settles it, and a later login does nothing.
  • aKillThatDoesNotLandKeepsTheOwedDeathAcrossSaveAndLoadUntilADeathSettlesIt: two infestations owe ALICE's death and the damage leaves her alive. One return deals one kill, not two. Both debts survive saving and reloading from disk. The next return kills again, the death settles both debts, and a later login does nothing.
  • oneCommittedPlayerPastGraceLeavesTheFightWhileAnotherKeepsItAlive: the lure continues while the second player fights, even with the departed player back online on another character. When the fighter dies, the lure fails and the departed player's debt is kept. It is dropped once that character is recorded dead.
  • returningWithinGraceKeepsTheLureWithoutPenalty: no failure, no damage, nothing owed.
  • aRunningLureKillThatDoesNotLandIsOwedAgainUntilADeathSettlesIt: two running lures owe ALICE's death while another player keeps both alive. The damage leaves her alive, and one return deals one kill. Debt, commitment and character are all kept, and the next return kills again. Once a kill lands, the death settles both lures, which stay active for the fighter, and a later login does nothing.
  • leavingTheLureAfterSurvivingTheKillStillOwesTheDeath: surviving the kill and then using /lure leave moves the debt to owedDeaths, and the next return kills again.
  • owedDeathsSettleByAccountWithoutCharactersAndByTheSavedDeathWithThem: standalone account debts, a player already dead on return (no kill, debt kept), account death, sticky tracking from loaded owed deaths, and per-character saved deaths.

Updated:

  • graceExpiresIntoDeathOnLoginAndWipeNeedsNoSurvivorsOrGrace: an expired grace no longer holds the lure.
  • unidentifiedDebtsDoNotKeepTheLureAliveAcrossSaveAndLoad, renamed debtsDoNotKeepTheLureAliveAndOnlyTaggedOnesOutliveItAcrossSaveAndLoad: the tagged debt now moves to owedDeaths and survives save and load.
  • deathOnLoginWaitsForTheCharacterWhoLoggedOffAndIsDroppedOnceThatCharacterDied, returningDebtStopsWhenDamageReentersAnotherSessionEvenForTheSameCharacter, standalonePenaltyLoopStopsWhenAProviderAppearsDuringDamage and InfestationsServicesTest.sharedStartAppliesOnlyTheCurrentCharactersGraceAndDeath: a kill that did not land now keeps the debt, and these tests used to assert it was gone. They still check that one return deals only one kill.
  • InfestationDataTest: round trip, malformed or legacy rows, and placeLure/clearLure keeping owedDeaths.

mvn -B clean verify passes locally on JDK 21 with 181 tests and 100% line coverage.

Notes

  • No version bump, following this repo's convention: the pom stays main-SNAPSHOT and releases are tagged separately.
  • Edge case, same as v0.3.2: a player who logged out shortly before a restart and whose grace ran out while the server was down owes the death. The lure now fails at startup instead of waiting for them, but they still die when that character logs in.

🤖 Generated with Claude Code

A committed player whose logout grace expired was marked to die on next
login, and that pending death kept the lure alive. If the player never
came back on that character, the lure never ended: the province got no
ambient spawns, nobody could place a new lure, and visitors kept taking
deserter damage.

Only presence or a running grace now holds a lure. When the grace runs
out the player has left the fight, so the lure fails as usual once
nobody else is present. Because they abandoned a running fight, the
death they owe moves to the infestation when the lure fails and still
kills that character on its next login, or is dropped once that
character has died. Unidentified (held) debts still end with the lure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b229a5ba-ae79-4051-a1c7-62ce6c839243
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the size:L 100-499 effective changed lines (tests excluded in mixed PRs). label Oct 23, 2026
XxFran10xX and others added 2 commits October 23, 2026 20:27
Returning as the owing character removed the saved debt before the kill,
so damage that was cancelled, reduced or survived (totem,
invulnerability) forgave the penalty for good. The kill now leaves the
debt in place; only a settled death clears it, so a kill that does not
land is owed again on the next return. One return kills at most once,
whatever number of debts it owes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Returning as the character that owed a running lure's death removed
deathOnLogin before the kill, so damage that was cancelled, reduced or
survived forgave the death. The kill now leaves the debt, commitment and
character in place; settleDeath clears them when the death happens, so
a kill that does not land is owed again on the next return. A return
kills at most once across running and failed lures, and leaving the lure
after surviving moves the debt to the infestation instead of erasing it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@XxFran10xX
XxFran10xX merged commit 64c1091 into main Oct 23, 2026
3 checks passed
@XxFran10xX
XxFran10xX deleted the lure-stuck branch October 23, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 effective changed lines (tests excluded in mixed PRs).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant