Skip to content

Commit 0b83bfe

Browse files
committed
Merge remote-tracking branch 'origin/auto' into auto
# Conflicts: # src/main/kotlin/dev/robothanzo/werewolf/game/steps/NightStep.kt
2 parents 38058da + 3123315 commit 0b83bfe

36 files changed

Lines changed: 1190 additions & 245 deletions

src/dashboard/src/constants/gameData.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,13 @@ export const GAME_ROLES: Record<string, GameEntityConfig> = {
190190
translationKey: 'roles.labels.DARK_MERCHANT',
191191
camp: 'GOD',
192192
},
193+
MIRACLE_MERCHANT: {
194+
id: 'MIRACLE_MERCHANT',
195+
color: '#f59e0b', // Amber-500
196+
icon: ShoppingBag,
197+
translationKey: 'roles.labels.MIRACLE_MERCHANT',
198+
camp: 'GOD',
199+
},
193200
HIDDEN: {
194201
id: 'HIDDEN',
195202
color: '#64748b', // Slate-500
@@ -254,6 +261,12 @@ export const GAME_ACTIONS: Record<string, GameEntityConfig> = {
254261
icon: ShoppingBag,
255262
translationKey: 'roles.actions.DARK_MERCHANT_TRADE',
256263
},
264+
MIRACLE_MERCHANT_TRADE_GUARD: {
265+
id: 'MIRACLE_MERCHANT_TRADE_GUARD',
266+
color: '#f59e0b',
267+
icon: Shield,
268+
translationKey: 'roles.actions.MIRACLE_MERCHANT_TRADE_GUARD',
269+
},
257270
MERCHANT_SEER_CHECK: {
258271
id: 'MERCHANT_SEER_CHECK',
259272
color: '#06b6d4',
@@ -272,6 +285,12 @@ export const GAME_ACTIONS: Record<string, GameEntityConfig> = {
272285
icon: Crosshair,
273286
translationKey: 'roles.actions.MERCHANT_GUN',
274287
},
288+
MERCHANT_GUARD_PROTECT: {
289+
id: 'MERCHANT_GUARD_PROTECT',
290+
color: '#f59e0b',
291+
icon: Shield,
292+
translationKey: 'roles.actions.MERCHANT_GUARD_PROTECT',
293+
},
275294
DREAM_WEAVER_LINK: {
276295
id: 'DREAM_WEAVER_LINK',
277296
color: '#ec4899',
@@ -321,6 +340,7 @@ export const getRoleConfig = (roleName: string): GameEntityConfig => {
321340
if (roleName.includes('魔術師')) return GAME_ROLES.MAGICIAN;
322341
if (roleName.includes('平民') || roleName.includes('村民')) return GAME_ROLES.VILLAGER;
323342
if (roleName.includes('黑市')) return GAME_ROLES.DARK_MERCHANT;
343+
if (roleName.includes('奇蹟')) return GAME_ROLES.MIRACLE_MERCHANT;
324344
if (roleName.includes('複製')) return GAME_ROLES.CLONE;
325345

326346
return GAME_ROLES.HIDDEN;

src/dashboard/src/locales/zh-TW.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@
216216
"HUNTER": "獵人",
217217
"VILLAGER": "平民",
218218
"DARK_MERCHANT": "黑市商人",
219+
"MIRACLE_MERCHANT": "奇蹟商人",
219220
"WOLF_KING": "狼王",
220221
"WOLF_YOUNGER_BROTHER": "狼弟",
221222
"WOLF_BROTHER": "狼兄",
@@ -244,9 +245,11 @@
244245
"HUNTER_REVENGE": "獵人開槍",
245246
"WOLF_KING_REVENGE": "狼王開槍",
246247
"DARK_MERCHANT_TRADE": "黑市商人交易",
248+
"MIRACLE_MERCHANT_TRADE_GUARD": "奇蹟商人交易 (給予守護)",
247249
"MERCHANT_SEER_CHECK": "商人技能:查驗",
248250
"MERCHANT_POISON": "商人技能:毒藥",
249251
"MERCHANT_GUN": "商人技能:開槍",
252+
"MERCHANT_GUARD_PROTECT": "商人技能:守護",
250253
"DREAM_WEAVER_LINK": "攝夢人:攝夢",
251254
"NIGHTMARE_FEAR": "夢魘:恐懼",
252255
"DEATH": "角色死亡",

src/main/kotlin/dev/robothanzo/werewolf/audio/Audio.kt

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package dev.robothanzo.werewolf.audio
22

33
import com.sedmelluq.discord.lavaplayer.player.AudioLoadResultHandler
4+
import com.sedmelluq.discord.lavaplayer.player.event.AudioEventAdapter
45
import com.sedmelluq.discord.lavaplayer.tools.FriendlyException
56
import com.sedmelluq.discord.lavaplayer.track.AudioPlaylist
67
import com.sedmelluq.discord.lavaplayer.track.AudioTrack
8+
import com.sedmelluq.discord.lavaplayer.track.AudioTrackEndReason
79
import dev.robothanzo.werewolf.WerewolfApplication
810
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel
911
import org.slf4j.LoggerFactory
@@ -12,13 +14,36 @@ import java.util.*
1214
object Audio {
1315
private val log = LoggerFactory.getLogger(Audio::class.java)
1416

15-
fun VoiceChannel.play(resource: Resource) {
17+
fun VoiceChannel.play(resource: Resource, onFinished: (() -> Unit)? = null) {
1618
val resourcePath = "sounds/$resource.mp3"
1719
log.info("Attempting to play audio resource: {} in channel: {}", resourcePath, this.name)
1820
try {
1921
val audioManager = this.guild.audioManager
2022
val player = WerewolfApplication.playerManager.createPlayer()
2123

24+
if (onFinished != null) {
25+
player.addListener(object : AudioEventAdapter() {
26+
override fun onTrackEnd(
27+
player: com.sedmelluq.discord.lavaplayer.player.AudioPlayer,
28+
track: AudioTrack,
29+
endReason: AudioTrackEndReason
30+
) {
31+
if (endReason.mayStartNext) {
32+
onFinished()
33+
}
34+
}
35+
36+
override fun onTrackException(
37+
player: com.sedmelluq.discord.lavaplayer.player.AudioPlayer,
38+
track: AudioTrack,
39+
exception: FriendlyException
40+
) {
41+
log.error("Track exception for audio resource: {}. Error: {}", resourcePath, exception.message)
42+
onFinished()
43+
}
44+
})
45+
}
46+
2247
// Always set/update the sending handler to the player we just created for this clip
2348
audioManager.sendingHandler = AudioPlayerSendHandler(player)
2449

@@ -36,25 +61,32 @@ object Audio {
3661

3762
override fun playlistLoaded(playlist: AudioPlaylist) {
3863
log.debug("Playlist loaded (unexpected for single clip): {}", resourceName(resource))
64+
onFinished?.invoke()
3965
}
4066

4167
override fun noMatches() {
4268
log.warn("No matches found for audio resource: {}", resourcePath)
69+
onFinished?.invoke()
4370
}
4471

4572
override fun loadFailed(exception: FriendlyException) {
4673
log.error("Load failed for audio resource: {}. Error: {}", resourcePath, exception.message)
74+
onFinished?.invoke()
4775
}
4876
})
4977
} catch (e: Exception) {
5078
log.error("Error while trying to play sound resource: {}", resourcePath, e)
79+
onFinished?.invoke()
5180
}
5281
}
5382

5483
private fun resourceName(resource: Resource): String = resource.toString()
5584

5685
enum class Resource {
57-
EXPEL_POLL, POLICE_ENROLL, POLICE_POLL, TIMER_ENDED, ENROLL_10S_REMAINING, POLL_10S_REMAINING, TIMER_30S_REMAINING;
86+
MORNING, NIGHT,
87+
EXPEL_POLL, POLL_10S_REMAINING,
88+
ENROLL_10S_REMAINING, POLICE_ENROLL, POLICE_POLL,
89+
TIMER_ENDED, TIMER_30S_REMAINING;
5890

5991
override fun toString(): String {
6092
return super.toString().lowercase(Locale.ROOT)

src/main/kotlin/dev/robothanzo/werewolf/commands/Game.kt

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ package dev.robothanzo.werewolf.commands
33
import dev.robothanzo.jda.interactions.annotations.slash.Command
44
import dev.robothanzo.jda.interactions.annotations.slash.Subcommand
55
import dev.robothanzo.werewolf.WerewolfApplication
6+
import dev.robothanzo.werewolf.database.documents.LogType
67
import dev.robothanzo.werewolf.game.model.DeathCause
8+
import kotlinx.coroutines.DelicateCoroutinesApi
9+
import kotlinx.coroutines.GlobalScope
10+
import kotlinx.coroutines.launch
711
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent
812

913
@Command
@@ -35,9 +39,26 @@ class Game {
3539
return@withLockedSession
3640
}
3741

42+
if (player.roles.contains("夢魘")) {
43+
event.hook.editOriginal(":x: 夢魘不可自爆").queue()
44+
return@withLockedSession
45+
}
46+
3847
// Execute detonation
39-
session.addLog(dev.robothanzo.werewolf.database.documents.LogType.SYSTEM, "${player.nickname} 選擇自爆")
40-
player.died(DeathCause.EXPEL, allowLastWords = false)
48+
session.addLog(LogType.SYSTEM, "${player.nickname} 選擇自爆")
49+
player.markDead(DeathCause.EXPEL)
50+
51+
// Launch async death events (Last Words etc)
52+
@OptIn(DelicateCoroutinesApi::class)
53+
GlobalScope.launch {
54+
try {
55+
val currentSession = WerewolfApplication.gameSessionService.getSession(guildId).orElse(null)
56+
val p = currentSession?.getPlayer(player.id)
57+
p?.runDeathEvents(allowLastWords = true)
58+
} catch (e: Exception) {
59+
e.printStackTrace()
60+
}
61+
}
4162

4263
// End the current phase (usually day speech)
4364
WerewolfApplication.gameStateService.nextStep(session)

src/main/kotlin/dev/robothanzo/werewolf/controller/GameController.kt

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package dev.robothanzo.werewolf.controller
22

3-
import dev.robothanzo.werewolf.controller.dto.*
3+
import dev.robothanzo.werewolf.controller.dto.ApiResponse
4+
import dev.robothanzo.werewolf.controller.dto.GameRequests
5+
import dev.robothanzo.werewolf.controller.dto.GuildMembersResponse
6+
import dev.robothanzo.werewolf.controller.dto.StateActionResponse
47
import dev.robothanzo.werewolf.database.documents.LogType
58
import dev.robothanzo.werewolf.database.documents.UserRole
9+
import dev.robothanzo.werewolf.game.model.DeathCause
610
import dev.robothanzo.werewolf.security.annotations.CanManageGuild
711
import dev.robothanzo.werewolf.security.annotations.CanViewGuild
812
import dev.robothanzo.werewolf.service.*
@@ -12,6 +16,9 @@ import io.swagger.v3.oas.annotations.media.Content
1216
import io.swagger.v3.oas.annotations.media.Schema
1317
import io.swagger.v3.oas.annotations.responses.ApiResponses
1418
import io.swagger.v3.oas.annotations.tags.Tag
19+
import kotlinx.coroutines.DelicateCoroutinesApi
20+
import kotlinx.coroutines.GlobalScope
21+
import kotlinx.coroutines.launch
1522
import org.springframework.context.annotation.Lazy
1623
import org.springframework.http.ResponseEntity
1724
import org.springframework.web.bind.annotation.*
@@ -246,8 +253,18 @@ class GameController(
246253
): ResponseEntity<ApiResponse> {
247254
val session = gameSessionService.getSession(guildId.toLong())
248255
.orElseThrow { Exception("Session not found") }
249-
gameActionService.markPlayerDead(session, playerId, lastWords)
250-
return ResponseEntity.ok(ApiResponse.ok(message = "Player marked as dead"))
256+
val player = session.getPlayer(playerId) ?: throw Exception("Player not found")
257+
258+
@OptIn(DelicateCoroutinesApi::class)
259+
GlobalScope.launch {
260+
try {
261+
player.processDeath(DeathCause.UNKNOWN, lastWords)
262+
} catch (e: Exception) {
263+
e.printStackTrace()
264+
}
265+
}
266+
267+
return ResponseEntity.ok(ApiResponse.ok(message = "Player death processing started"))
251268
}
252269

253270
@Operation(summary = "Revive Player", description = "Revives a dead player.")

0 commit comments

Comments
 (0)