Skip to content

Commit e5e4113

Browse files
committed
fixed premature day ending
1 parent 5ec29de commit e5e4113

2 files changed

Lines changed: 127 additions & 26 deletions

File tree

src/main/kotlin/dev/robothanzo/werewolf/game/steps/NightStep.kt

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import dev.robothanzo.werewolf.service.ActionUIService
1414
import dev.robothanzo.werewolf.service.GameSessionService
1515
import dev.robothanzo.werewolf.service.GameStateService
1616
import dev.robothanzo.werewolf.service.SpeechService
17-
import dev.robothanzo.werewolf.utils.CmdUtils
1817
import kotlinx.coroutines.*
1918
import org.slf4j.LoggerFactory
2019
import org.springframework.context.annotation.Lazy
@@ -150,33 +149,27 @@ class NightStep(
150149

151150
// --- Wait Helper ---
152151
internal suspend fun waitForCondition(guildId: Long, durationSeconds: Int, condition: () -> Boolean): Boolean {
153-
val signal = CompletableDeferred<Unit>()
154-
phaseSignals[guildId] = signal
155-
156-
val timeoutTask = CmdUtils.schedule({
157-
signal.complete(Unit)
158-
}, durationSeconds * 1000L)
159-
160-
// Check condition loop
161-
val checkJob = nightScope.launch {
162-
while (isActive) {
163-
if (condition()) {
164-
signal.complete(Unit)
165-
break
166-
}
167-
delay(1000)
168-
}
169-
}
152+
val timeoutAt = System.currentTimeMillis() + durationSeconds * 1000L
153+
154+
while (System.currentTimeMillis() < timeoutAt) {
155+
if (condition()) return true
170156

171-
try {
172-
withTimeout((durationSeconds + 5) * 1000L) { // Safety timeout
173-
signal.await()
157+
val signal = CompletableDeferred<Unit>()
158+
phaseSignals[guildId] = signal
159+
160+
val remainingMs = timeoutAt - System.currentTimeMillis()
161+
if (remainingMs <= 0) break
162+
163+
try {
164+
// Wait for either onEvent (signal) or 1 second poll via timeout
165+
withTimeout(minOf(remainingMs, 1000L)) {
166+
signal.await()
167+
}
168+
} catch (_: Exception) {
169+
// Ignore timeout (which acts as a poll) or other exceptions, loop and re-check condition
170+
} finally {
171+
phaseSignals.remove(guildId)
174172
}
175-
} catch (_: Exception) {
176-
} finally {
177-
timeoutTask.cancel()
178-
checkJob.cancel()
179-
phaseSignals.remove(guildId)
180173
}
181174

182175
return condition()
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package dev.robothanzo.werewolf.game.roles
2+
3+
import dev.robothanzo.werewolf.database.documents.Player
4+
import dev.robothanzo.werewolf.database.documents.Session
5+
import dev.robothanzo.werewolf.game.model.*
6+
import dev.robothanzo.werewolf.game.roles.actions.*
7+
import org.junit.jupiter.api.Assertions.assertFalse
8+
import org.junit.jupiter.api.Assertions.assertTrue
9+
import org.junit.jupiter.api.BeforeEach
10+
import org.junit.jupiter.api.Test
11+
12+
class WitchAntidoteTests {
13+
private lateinit var session: Session
14+
private lateinit var roleRegistry: RoleRegistry
15+
private lateinit var executor: RoleActionExecutor
16+
17+
@BeforeEach
18+
fun setup() {
19+
session = Session()
20+
session.id = "test-session"
21+
session.day = 1
22+
23+
// Mock Roles
24+
val witchRole = object : BaseRole("女巫", Camp.GOD) {
25+
override fun getActions() = listOf(WitchAntidoteAction(), WitchPoisonAction())
26+
}
27+
val werewolfRole = object : BaseRole("狼人", Camp.WEREWOLF) {}
28+
val villagerRole = object : BaseRole("平民", Camp.VILLAGER) {}
29+
val ghostRiderRole = object : BaseRole("惡靈騎士", Camp.WEREWOLF) {}
30+
31+
roleRegistry = RoleRegistry(
32+
listOf(witchRole, werewolfRole, villagerRole, ghostRiderRole),
33+
listOf(
34+
WerewolfKillAction(),
35+
WitchAntidoteAction(),
36+
WitchPoisonAction(),
37+
GuardProtectAction(),
38+
DeathResolutionAction()
39+
)
40+
)
41+
executor = RoleActionExecutor(roleRegistry)
42+
43+
// Setup Players
44+
session.players["1"] = Player(id = 1, roles = mutableListOf("狼人"))
45+
session.players["2"] = Player(id = 2, roles = mutableListOf("女巫"))
46+
session.players["3"] = Player(id = 3, roles = mutableListOf("平民"))
47+
session.players["4"] = Player(id = 4, roles = mutableListOf("惡靈騎士"))
48+
49+
session.players.values.forEach { it.session = session }
50+
}
51+
52+
@Test
53+
fun testWitchAntidoteSavesVillager() {
54+
val wolfKill = RoleActionInstance(
55+
actor = 1,
56+
actorRole = "狼人",
57+
actionDefinitionId = ActionDefinitionId.WEREWOLF_KILL,
58+
targets = mutableListOf(3),
59+
submittedBy = ActionSubmissionSource.PLAYER,
60+
status = ActionStatus.SUBMITTED
61+
)
62+
val antidote = RoleActionInstance(
63+
actor = 2,
64+
actorRole = "女巫",
65+
actionDefinitionId = ActionDefinitionId.WITCH_ANTIDOTE,
66+
targets = mutableListOf(3),
67+
submittedBy = ActionSubmissionSource.PLAYER,
68+
status = ActionStatus.SUBMITTED
69+
)
70+
71+
val result = executor.executeActions(session, listOf(wolfKill, antidote))
72+
73+
assertFalse(result.deaths.values.flatten().contains(3), "Villager should be saved by Antidote")
74+
assertTrue(result.saved.contains(3), "Villager ID should be in saved list")
75+
}
76+
77+
@Test
78+
fun testWitchAntidoteOnGhostRider() {
79+
// Wolves target GR (though they shouldn't usually, but for testing logic)
80+
val wolfKill = RoleActionInstance(
81+
actor = 1,
82+
actorRole = "狼人",
83+
actionDefinitionId = ActionDefinitionId.WEREWOLF_KILL,
84+
targets = mutableListOf(4),
85+
submittedBy = ActionSubmissionSource.PLAYER,
86+
status = ActionStatus.SUBMITTED
87+
)
88+
// Witch tries to save GR
89+
val antidote = RoleActionInstance(
90+
actor = 2,
91+
actorRole = "女巫",
92+
actionDefinitionId = ActionDefinitionId.WITCH_ANTIDOTE,
93+
targets = mutableListOf(4),
94+
submittedBy = ActionSubmissionSource.PLAYER,
95+
status = ActionStatus.SUBMITTED
96+
)
97+
98+
val result = executor.executeActions(session, listOf(wolfKill, antidote))
99+
100+
// According to current logic: Witch targets GR -> Reflect!
101+
assertTrue(result.deaths[DeathCause.REFLECT]?.contains(2) == true, "Witch should be reflected")
102+
// The antidote should NOT have worked because reflection returns accumulatedState (skipping the action)
103+
assertFalse(result.saved.contains(4), "Antidote should have been skipped due to reflection")
104+
105+
// GR is immune to wolf kill at night anyway
106+
assertFalse(result.deaths.values.flatten().contains(4), "Ghost Rider should be immune to wolf kill at night")
107+
}
108+
}

0 commit comments

Comments
 (0)