nonebot-plugin-werewolf 1.1.8__py3-none-any.whl → 1.1.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nonebot_plugin_werewolf/__init__.py +1 -1
- nonebot_plugin_werewolf/config.py +1 -0
- nonebot_plugin_werewolf/constant.py +6 -1
- nonebot_plugin_werewolf/game.py +61 -50
- nonebot_plugin_werewolf/matchers/edit_behavior.py +14 -2
- nonebot_plugin_werewolf/matchers/poke/chronocat_poke.py +2 -2
- nonebot_plugin_werewolf/matchers/poke/ob11_poke.py +2 -2
- nonebot_plugin_werewolf/matchers/start_game.py +31 -30
- nonebot_plugin_werewolf/models.py +15 -3
- nonebot_plugin_werewolf/{players/player.py → player.py} +148 -70
- nonebot_plugin_werewolf/player_set.py +39 -23
- nonebot_plugin_werewolf/players/__init__.py +0 -1
- nonebot_plugin_werewolf/players/civilian.py +3 -3
- nonebot_plugin_werewolf/players/guard.py +20 -15
- nonebot_plugin_werewolf/players/hunter.py +6 -5
- nonebot_plugin_werewolf/players/idiot.py +25 -17
- nonebot_plugin_werewolf/players/jester.py +22 -17
- nonebot_plugin_werewolf/players/prophet.py +13 -8
- nonebot_plugin_werewolf/players/{can_shoot.py → shooter.py} +10 -10
- nonebot_plugin_werewolf/players/werewolf.py +69 -45
- nonebot_plugin_werewolf/players/witch.py +30 -23
- nonebot_plugin_werewolf/players/wolfking.py +14 -8
- nonebot_plugin_werewolf/utils.py +6 -6
- {nonebot_plugin_werewolf-1.1.8.dist-info → nonebot_plugin_werewolf-1.1.9.dist-info}/METADATA +9 -2
- nonebot_plugin_werewolf-1.1.9.dist-info/RECORD +35 -0
- {nonebot_plugin_werewolf-1.1.8.dist-info → nonebot_plugin_werewolf-1.1.9.dist-info}/WHEEL +1 -1
- nonebot_plugin_werewolf-1.1.8.dist-info/RECORD +0 -35
- {nonebot_plugin_werewolf-1.1.8.dist-info → nonebot_plugin_werewolf-1.1.9.dist-info/licenses}/LICENSE +0 -0
- {nonebot_plugin_werewolf-1.1.8.dist-info → nonebot_plugin_werewolf-1.1.9.dist-info}/top_level.txt +0 -0
@@ -56,6 +56,7 @@ class GameBehavior(ConfigFile):
|
|
56
56
|
show_roles_list_on_start: bool = False
|
57
57
|
speak_in_turn: bool = False
|
58
58
|
dead_channel_rate_limit: int = 8 # per minute
|
59
|
+
werewolf_multi_select: bool = False
|
59
60
|
|
60
61
|
class _Timeout(BaseModel):
|
61
62
|
prepare: int = Field(default=5 * 60, ge=5 * 60)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import functools
|
2
|
+
from typing import TYPE_CHECKING
|
2
3
|
|
3
4
|
import nonebot
|
4
5
|
|
@@ -10,13 +11,17 @@ COMMAND_START = next(
|
|
10
11
|
)
|
11
12
|
|
12
13
|
|
13
|
-
@functools.cache
|
14
14
|
def stop_command_prompt() -> str:
|
15
15
|
from .config import config # circular import
|
16
16
|
|
17
17
|
return COMMAND_START + config.get_stop_command()[0]
|
18
18
|
|
19
19
|
|
20
|
+
if not TYPE_CHECKING:
|
21
|
+
stop_command_prompt = functools.cache(stop_command_prompt)
|
22
|
+
del TYPE_CHECKING
|
23
|
+
|
24
|
+
|
20
25
|
ROLE_NAME_CONV: dict[Role | RoleGroup, str] = {
|
21
26
|
Role.WEREWOLF: "狼人",
|
22
27
|
Role.WOLFKING: "狼王",
|
nonebot_plugin_werewolf/game.py
CHANGED
@@ -2,7 +2,8 @@ import contextlib
|
|
2
2
|
import functools
|
3
3
|
import secrets
|
4
4
|
from collections import defaultdict
|
5
|
-
from typing import NoReturn
|
5
|
+
from typing import NoReturn, final
|
6
|
+
from typing_extensions import Self
|
6
7
|
|
7
8
|
import anyio
|
8
9
|
import nonebot
|
@@ -16,8 +17,8 @@ from .config import GameBehavior, PresetData
|
|
16
17
|
from .constant import GAME_STATUS_CONV, REPORT_TEXT, ROLE_EMOJI, ROLE_NAME_CONV
|
17
18
|
from .exception import GameFinished
|
18
19
|
from .models import GameState, GameStatus, KillInfo, KillReason, Role, RoleGroup
|
20
|
+
from .player import Player
|
19
21
|
from .player_set import PlayerSet
|
20
|
-
from .players import Player
|
21
22
|
from .utils import InputStore, ObjectStream, SendHandler, add_stop_button, link
|
22
23
|
|
23
24
|
logger = nonebot.logger.opt(colors=True)
|
@@ -33,9 +34,13 @@ def get_running_games() -> set["Game"]:
|
|
33
34
|
return running_games
|
34
35
|
|
35
36
|
|
36
|
-
def init_players(
|
37
|
-
|
38
|
-
|
37
|
+
async def init_players(
|
38
|
+
bot: Bot,
|
39
|
+
game: "Game",
|
40
|
+
players: set[str],
|
41
|
+
interface: Interface,
|
42
|
+
) -> PlayerSet:
|
43
|
+
logger.debug(f"初始化 {game.colored_name} 的玩家职业")
|
39
44
|
|
40
45
|
preset_data = PresetData.get()
|
41
46
|
if (preset := preset_data.role_preset.get(len(players))) is None:
|
@@ -58,11 +63,11 @@ def init_players(bot: Bot, game: "Game", players: set[str]) -> PlayerSet:
|
|
58
63
|
def _select_role() -> Role:
|
59
64
|
return roles.pop(secrets.randbelow(len(roles)))
|
60
65
|
|
61
|
-
player_set = PlayerSet(
|
62
|
-
|
63
|
-
|
64
|
-
logger.debug(f"职业分配完成: <e>{escape_tag(str(player_set))}</e>")
|
66
|
+
player_set = PlayerSet()
|
67
|
+
for user_id in players:
|
68
|
+
player_set.add(await Player.new(_select_role(), bot, game, user_id, interface))
|
65
69
|
|
70
|
+
logger.debug(f"职业分配完成: <e>{escape_tag(str(player_set))}</e>")
|
66
71
|
return player_set
|
67
72
|
|
68
73
|
|
@@ -135,7 +140,7 @@ class DeadChannel:
|
|
135
140
|
# 推送消息
|
136
141
|
await self.stream.send((player, msg))
|
137
142
|
|
138
|
-
async def run(self) ->
|
143
|
+
async def run(self) -> None:
|
139
144
|
async with anyio.create_task_group() as tg:
|
140
145
|
self._task_group = tg
|
141
146
|
tg.start_soon(self._wait_finished)
|
@@ -148,36 +153,40 @@ class Game:
|
|
148
153
|
bot: Bot
|
149
154
|
group: Target
|
150
155
|
players: PlayerSet
|
151
|
-
interface: Interface
|
152
156
|
state: GameState
|
153
157
|
killed_players: list[tuple[str, KillInfo]]
|
154
158
|
|
155
|
-
def __init__(
|
156
|
-
self,
|
157
|
-
bot: Bot,
|
158
|
-
group: Target,
|
159
|
-
players: set[str],
|
160
|
-
interface: Interface,
|
161
|
-
) -> None:
|
159
|
+
def __init__(self, bot: Bot, group: Target) -> None:
|
162
160
|
self.bot = bot
|
163
161
|
self.group = group
|
164
|
-
self.players = init_players(bot, self, players)
|
165
|
-
self.interface = interface
|
166
162
|
self.state = GameState(0)
|
167
163
|
self.killed_players = []
|
168
|
-
self._player_map
|
164
|
+
self._player_map: dict[str, Player] = {}
|
169
165
|
self._scene = None
|
170
166
|
self._finished = None
|
171
167
|
self._task_group = None
|
172
168
|
self._send_handler = _SendHandler()
|
173
|
-
self._send_handler.update(group)
|
169
|
+
self._send_handler.update(group, bot)
|
174
170
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
171
|
+
@final
|
172
|
+
@classmethod
|
173
|
+
async def new(
|
174
|
+
cls,
|
175
|
+
bot: Bot,
|
176
|
+
group: Target,
|
177
|
+
players: set[str],
|
178
|
+
interface: Interface,
|
179
|
+
) -> Self:
|
180
|
+
self = cls(bot, group)
|
181
|
+
|
182
|
+
self._scene = await interface.get_scene(SceneType.GROUP, self.group_id)
|
183
|
+
if self._scene is None:
|
184
|
+
self._scene = await interface.get_scene(SceneType.GUILD, self.group_id)
|
179
185
|
|
180
|
-
self.
|
186
|
+
self.players = await init_players(bot, self, players, interface)
|
187
|
+
self._player_map |= {p.user_id: p for p in self.players}
|
188
|
+
|
189
|
+
return self
|
181
190
|
|
182
191
|
@functools.cached_property
|
183
192
|
def group_id(self) -> str:
|
@@ -232,6 +241,10 @@ class Game:
|
|
232
241
|
if not w.size:
|
233
242
|
raise GameFinished(GameStatus.GOODGUY)
|
234
243
|
|
244
|
+
@property
|
245
|
+
def behavior(self) -> GameBehavior:
|
246
|
+
return GameBehavior.get()
|
247
|
+
|
235
248
|
async def notify_player_role(self) -> None:
|
236
249
|
msg = UniMessage()
|
237
250
|
for p in sorted(self.players, key=lambda p: p.user_id):
|
@@ -244,7 +257,7 @@ class Game:
|
|
244
257
|
.text(f"职业分配: 狼人x{w}, 神职x{p}, 平民x{c}")
|
245
258
|
)
|
246
259
|
|
247
|
-
if
|
260
|
+
if self.behavior.show_roles_list_on_start:
|
248
261
|
role_cnt: dict[Role, int] = defaultdict(lambda: 0)
|
249
262
|
for role in sorted((p.role for p in self.players), key=lambda r: r.value):
|
250
263
|
role_cnt[role] += 1
|
@@ -264,7 +277,7 @@ class Game:
|
|
264
277
|
timeout_secs: float | None = None,
|
265
278
|
) -> None:
|
266
279
|
if timeout_secs is None:
|
267
|
-
timeout_secs =
|
280
|
+
timeout_secs = self.behavior.timeout.speak
|
268
281
|
with anyio.move_on_after(timeout_secs):
|
269
282
|
async with anyio.create_task_group() as tg:
|
270
283
|
for p in players:
|
@@ -280,18 +293,18 @@ class Game:
|
|
280
293
|
await player.post_kill()
|
281
294
|
if player.kill_info is None:
|
282
295
|
continue
|
283
|
-
|
284
296
|
self.killed_players.append((player.name, player.kill_info))
|
285
|
-
|
297
|
+
|
298
|
+
shooter = self.state.shooter
|
286
299
|
if shooter is not None and (shoot := shooter.selected) is not None:
|
287
300
|
await self.send(
|
288
301
|
UniMessage.text("🔫玩家 ")
|
289
302
|
.at(shoot.user_id)
|
290
303
|
.text(f" 被{shooter.name}射杀, 请发表遗言\n")
|
291
|
-
.text(
|
304
|
+
.text(self.behavior.timeout.speak_timeout_prompt)
|
292
305
|
)
|
293
306
|
await self.wait_stop(shoot)
|
294
|
-
self.state.
|
307
|
+
self.state.shooter = shooter.selected = None
|
295
308
|
await self.post_kill(shoot)
|
296
309
|
|
297
310
|
async def run_night(self, players: PlayerSet) -> Player | None:
|
@@ -324,10 +337,9 @@ class Game:
|
|
324
337
|
return killed
|
325
338
|
|
326
339
|
async def run_discussion(self) -> None:
|
327
|
-
|
328
|
-
timeout = behavior.timeout
|
340
|
+
timeout = self.behavior.timeout
|
329
341
|
|
330
|
-
if not behavior.speak_in_turn:
|
342
|
+
if not self.behavior.speak_in_turn:
|
331
343
|
speak_timeout = timeout.group_speak
|
332
344
|
await self.send(
|
333
345
|
f"💬接下来开始自由讨论\n{timeout.group_speak_timeout_prompt}",
|
@@ -335,11 +347,13 @@ class Game:
|
|
335
347
|
)
|
336
348
|
await self.wait_stop(*self.players.alive(), timeout_secs=speak_timeout)
|
337
349
|
else:
|
338
|
-
await self.send("
|
350
|
+
await self.send("💬接下来开始轮流发言")
|
339
351
|
speak_timeout = timeout.speak
|
340
352
|
for player in self.players.alive().sorted:
|
341
353
|
await self.send(
|
342
|
-
|
354
|
+
UniMessage.text("💬")
|
355
|
+
.at(player.user_id)
|
356
|
+
.text(f"\n轮到你发言\n{timeout.speak_timeout_prompt}"),
|
343
357
|
stop_btn_label="结束发言",
|
344
358
|
)
|
345
359
|
await self.wait_stop(player, timeout_secs=speak_timeout)
|
@@ -395,7 +409,7 @@ class Game:
|
|
395
409
|
|
396
410
|
# 仅有一名玩家票数最高
|
397
411
|
voted = vs.pop()
|
398
|
-
if
|
412
|
+
if await voted.kill(KillReason.VOTE, *vote_result[voted]) is None:
|
399
413
|
# 投票放逐失败 (例: 白痴)
|
400
414
|
return
|
401
415
|
|
@@ -404,7 +418,7 @@ class Game:
|
|
404
418
|
UniMessage.text("🔨玩家 ")
|
405
419
|
.at(voted.user_id)
|
406
420
|
.text(" 被投票放逐, 请发表遗言\n")
|
407
|
-
.text(
|
421
|
+
.text(self.behavior.timeout.speak_timeout_prompt),
|
408
422
|
stop_btn_label="结束发言",
|
409
423
|
)
|
410
424
|
await self.wait_stop(voted)
|
@@ -445,7 +459,7 @@ class Game:
|
|
445
459
|
UniMessage.text("⚙️当前为第一天\n请被狼人杀死的 ")
|
446
460
|
.at(killed.user_id)
|
447
461
|
.text(" 发表遗言\n")
|
448
|
-
.text(
|
462
|
+
.text(self.behavior.timeout.speak_timeout_prompt),
|
449
463
|
stop_btn_label="结束发言",
|
450
464
|
)
|
451
465
|
await self.wait_stop(killed)
|
@@ -476,7 +490,7 @@ class Game:
|
|
476
490
|
msg.at(p.user_id).text(f": {p.role_name}\n")
|
477
491
|
await self.send(msg)
|
478
492
|
|
479
|
-
report
|
493
|
+
report = ["📌玩家死亡报告:"]
|
480
494
|
for name, info in self.killed_players:
|
481
495
|
emoji, action = REPORT_TEXT[info.reason]
|
482
496
|
report.append(f"{emoji} {name} 被 {', '.join(info.killers)} {action}")
|
@@ -491,24 +505,21 @@ class Game:
|
|
491
505
|
await self.handle_game_finish(result.status)
|
492
506
|
logger.info(f"{self.colored_name} 的狼人杀游戏进程正常退出")
|
493
507
|
except Exception as err:
|
494
|
-
|
495
|
-
logger.exception(msg)
|
508
|
+
logger.exception(f"{self.colored_name} 的狼人杀游戏进程出现未知错误")
|
496
509
|
await self.send(f"❌狼人杀游戏进程出现未知错误: {err!r}")
|
497
510
|
finally:
|
498
511
|
if self._finished is not None:
|
499
512
|
self._finished.set()
|
500
513
|
|
501
514
|
async def start(self) -> None:
|
502
|
-
await self._fetch_group_scene()
|
503
515
|
self._finished = anyio.Event()
|
504
516
|
dead_channel = DeadChannel(self.players, self._finished)
|
505
517
|
get_running_games().add(self)
|
506
518
|
|
507
519
|
try:
|
508
|
-
async with anyio.create_task_group() as
|
509
|
-
self._task_group
|
510
|
-
|
511
|
-
tg.start_soon(dead_channel.run)
|
520
|
+
async with anyio.create_task_group() as self._task_group:
|
521
|
+
self._task_group.start_soon(self.run_daemon)
|
522
|
+
self._task_group.start_soon(dead_channel.run)
|
512
523
|
except anyio.get_cancelled_exc_class():
|
513
524
|
logger.warning(f"{self.colored_name} 的狼人杀游戏进程被取消")
|
514
525
|
except Exception as err:
|
@@ -518,7 +529,7 @@ class Game:
|
|
518
529
|
self._finished = None
|
519
530
|
self._task_group = None
|
520
531
|
get_running_games().discard(self)
|
521
|
-
InputStore.cleanup(
|
532
|
+
InputStore.cleanup(self._player_map.keys(), self.group_id)
|
522
533
|
|
523
534
|
def terminate(self) -> None:
|
524
535
|
if self._task_group is not None:
|
@@ -44,7 +44,7 @@ alc = Alconna(
|
|
44
44
|
help_text="设置游戏开始时是否显示职业列表",
|
45
45
|
),
|
46
46
|
Subcommand(
|
47
|
-
"
|
47
|
+
"speak_in_turn",
|
48
48
|
Args["enabled#是否启用", bool],
|
49
49
|
alias={"发言顺序"},
|
50
50
|
help_text="设置是否按顺序发言",
|
@@ -55,6 +55,12 @@ alc = Alconna(
|
|
55
55
|
alias={"死亡聊天"},
|
56
56
|
help_text="设置死亡玩家发言频率限制",
|
57
57
|
),
|
58
|
+
Subcommand(
|
59
|
+
"werewolf_multi_select",
|
60
|
+
Args["enabled#是否启用", bool],
|
61
|
+
alias={"狼人多选"},
|
62
|
+
help_text="设置狼人多选时是否从已选玩家中随机选择目标, 为否时将视为空刀",
|
63
|
+
),
|
58
64
|
Subcommand(
|
59
65
|
"timeout",
|
60
66
|
Subcommand(
|
@@ -122,7 +128,7 @@ async def set_show_roles(behavior: Behavior, enabled: bool) -> None:
|
|
122
128
|
await finish(f"已{'启用' if enabled else '禁用'}游戏开始时显示职业列表")
|
123
129
|
|
124
130
|
|
125
|
-
@edit_behavior.assign("
|
131
|
+
@edit_behavior.assign("speak_in_turn")
|
126
132
|
async def set_speak_order(behavior: Behavior, enabled: bool) -> None:
|
127
133
|
behavior.speak_in_turn = enabled
|
128
134
|
await finish(f"已{'启用' if enabled else '禁用'}按顺序发言")
|
@@ -136,6 +142,12 @@ async def set_dead_chat(behavior: Behavior, limit: int) -> None:
|
|
136
142
|
await finish(f"已设置死亡玩家发言限制为 {limit} 次/分钟")
|
137
143
|
|
138
144
|
|
145
|
+
@edit_behavior.assign("werewolf_multi_select")
|
146
|
+
async def set_werewolf_multi_select(behavior: Behavior, enabled: bool) -> None:
|
147
|
+
behavior.werewolf_multi_select = enabled
|
148
|
+
await finish(f"已{'启用' if enabled else '禁用'}狼人多选")
|
149
|
+
|
150
|
+
|
139
151
|
@edit_behavior.assign("timeout.prepare")
|
140
152
|
async def set_prepare_timeout(behavior: Behavior, time: int) -> None:
|
141
153
|
if time < 300:
|
@@ -76,7 +76,7 @@ with contextlib.suppress(ImportError):
|
|
76
76
|
user_id=user_id,
|
77
77
|
group_id=(event.guild and event.guild.id) or event.channel.id,
|
78
78
|
)
|
79
|
-
and
|
79
|
+
and target in get_starting_games()
|
80
80
|
)
|
81
81
|
|
82
82
|
@on_message(rule=_rule_poke_join).handle()
|
@@ -86,7 +86,7 @@ with contextlib.suppress(ImportError):
|
|
86
86
|
target: MsgTarget,
|
87
87
|
) -> None:
|
88
88
|
user_id = extract_poke_tome(event) or event.get_user_id()
|
89
|
-
players =
|
89
|
+
players = get_starting_games()[target]
|
90
90
|
|
91
91
|
if user_id not in players:
|
92
92
|
# XXX:
|
@@ -50,7 +50,7 @@ with contextlib.suppress(ImportError):
|
|
50
50
|
return (
|
51
51
|
(event.target_id == event.self_id)
|
52
52
|
and not user_in_game(bot.self_id, user_id, group_id)
|
53
|
-
and
|
53
|
+
and target in get_starting_games()
|
54
54
|
)
|
55
55
|
|
56
56
|
@on_notice(rule=_rule_poke_join).handle()
|
@@ -60,7 +60,7 @@ with contextlib.suppress(ImportError):
|
|
60
60
|
target: MsgTarget,
|
61
61
|
) -> None:
|
62
62
|
user_id = event.get_user_id()
|
63
|
-
players =
|
63
|
+
players = get_starting_games()[target]
|
64
64
|
|
65
65
|
if event.group_id is None or user_id in players:
|
66
66
|
return
|
@@ -118,14 +118,15 @@ class PrepareGame:
|
|
118
118
|
self.send_handler = self._SendHandler()
|
119
119
|
self.logger = nonebot.logger.opt(colors=True)
|
120
120
|
self.shoud_start_game = False
|
121
|
-
self.
|
121
|
+
get_starting_games()[self.group] = self.players
|
122
|
+
|
123
|
+
self._handlers: dict[str, Callable[[], Awaitable[bool | None]]] = {
|
122
124
|
"开始游戏": self._handle_start,
|
123
125
|
"结束游戏": self._handle_end,
|
124
126
|
"加入游戏": self._handle_join,
|
125
127
|
"退出游戏": self._handle_quit,
|
126
128
|
"当前玩家": self._handle_list,
|
127
129
|
}
|
128
|
-
get_starting_games()[self.group] = self.players
|
129
130
|
|
130
131
|
async def run(self) -> None:
|
131
132
|
try:
|
@@ -163,6 +164,33 @@ class PrepareGame:
|
|
163
164
|
if event is not None:
|
164
165
|
await self.stream.send((event, text, name))
|
165
166
|
|
167
|
+
async def _handle(self) -> None:
|
168
|
+
bot = current_bot.get()
|
169
|
+
superuser = SuperUser()
|
170
|
+
|
171
|
+
while not self.stream.closed:
|
172
|
+
event, text, name = await self.stream.recv()
|
173
|
+
user_id = event.get_user_id()
|
174
|
+
colored = f"<y>{escape_tag(name)}</y>(<c>{escape_tag(user_id)}</c>)"
|
175
|
+
self.current = self._Current(
|
176
|
+
id=user_id,
|
177
|
+
name=name,
|
178
|
+
colored=colored,
|
179
|
+
is_admin=user_id == self.admin_id,
|
180
|
+
is_super_user=await superuser(bot, event),
|
181
|
+
)
|
182
|
+
self.send_handler.update(event)
|
183
|
+
|
184
|
+
# 更新用户名
|
185
|
+
# 当用户通过 chronoca:poke 加入游戏时, 插件无法获取用户名, 原字典值为用户ID
|
186
|
+
if user_id in self.players and self.players.get(user_id) != name:
|
187
|
+
self.logger.debug(f"更新玩家显示名称: {self.current.colored}")
|
188
|
+
self.players[user_id] = name
|
189
|
+
|
190
|
+
handler = self._handlers.get(text)
|
191
|
+
if handler is not None and await handler():
|
192
|
+
return
|
193
|
+
|
166
194
|
async def _send(self, msg: str | UniMessage) -> None:
|
167
195
|
await self.send_handler.send(msg)
|
168
196
|
|
@@ -240,33 +268,6 @@ class PrepareGame:
|
|
240
268
|
)
|
241
269
|
await self._send("✨当前玩家:\n" + "\n".join(lines))
|
242
270
|
|
243
|
-
async def _handle(self) -> None:
|
244
|
-
bot = current_bot.get()
|
245
|
-
superuser = SuperUser()
|
246
|
-
|
247
|
-
while not self.stream.closed:
|
248
|
-
event, text, name = await self.stream.recv()
|
249
|
-
user_id = event.get_user_id()
|
250
|
-
colored = f"<y>{escape_tag(name)}</y>(<c>{escape_tag(user_id)}</c>)"
|
251
|
-
self.current = self._Current(
|
252
|
-
id=user_id,
|
253
|
-
name=name,
|
254
|
-
colored=colored,
|
255
|
-
is_admin=user_id == self.admin_id,
|
256
|
-
is_super_user=await superuser(bot, event),
|
257
|
-
)
|
258
|
-
self.send_handler.update(event)
|
259
|
-
|
260
|
-
# 更新用户名
|
261
|
-
# 当用户通过 chronoca:poke 加入游戏时, 插件无法获取用户名, 原字典值为用户ID
|
262
|
-
if user_id in self.players and self.players.get(user_id) != name:
|
263
|
-
self.logger.debug(f"更新玩家显示名称: {self.current.colored}")
|
264
|
-
self.players[user_id] = name
|
265
|
-
|
266
|
-
handler = self._msg_handler.get(text)
|
267
|
-
if handler is not None and await handler():
|
268
|
-
return
|
269
|
-
|
270
271
|
|
271
272
|
@start_game.handle()
|
272
273
|
async def handle_notice(target: MsgTarget) -> None:
|
@@ -328,5 +329,5 @@ async def handle_start(
|
|
328
329
|
await UniMessage.text("⚠️游戏准备超时,已自动结束").finish()
|
329
330
|
|
330
331
|
dump_players(target, players)
|
331
|
-
game = Game(bot, target, set(players), interface)
|
332
|
+
game = await Game.new(bot, target, set(players), interface)
|
332
333
|
await game.start()
|
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING
|
|
5
5
|
import anyio
|
6
6
|
|
7
7
|
if TYPE_CHECKING:
|
8
|
-
from .
|
8
|
+
from .player import Player
|
9
9
|
|
10
10
|
|
11
11
|
class Role(int, Enum):
|
@@ -57,11 +57,13 @@ class GameState:
|
|
57
57
|
"""当前天数记录, 不会被 `reset()` 重置"""
|
58
58
|
state: State = State.NIGHT
|
59
59
|
"""当前游戏状态, 不会被 `reset()` 重置"""
|
60
|
+
_werewolf_interact_count: int = 0
|
61
|
+
"""内部属性, 记录当晚狼人交互状态"""
|
60
62
|
werewolf_finished: anyio.Event = dataclasses.field(default_factory=anyio.Event)
|
61
63
|
"""狼人交互是否结束"""
|
62
64
|
killed: "Player | None" = None
|
63
65
|
"""当晚狼人击杀目标, `None` 则为空刀"""
|
64
|
-
|
66
|
+
shooter: "Player | None" = None
|
65
67
|
"""当前执行射杀操作的玩家"""
|
66
68
|
antidote: set["Player"] = dataclasses.field(default_factory=set)
|
67
69
|
"""当晚女巫使用解药的目标"""
|
@@ -72,12 +74,22 @@ class GameState:
|
|
72
74
|
|
73
75
|
def reset(self) -> None:
|
74
76
|
self.werewolf_finished = anyio.Event()
|
77
|
+
self._werewolf_interact_count = 0
|
75
78
|
self.killed = None
|
76
|
-
self.
|
79
|
+
self.shooter = None
|
77
80
|
self.antidote = set()
|
78
81
|
self.poison = set()
|
79
82
|
self.protected = set()
|
80
83
|
|
84
|
+
def werewolf_start(self) -> None:
|
85
|
+
self._werewolf_interact_count += 1
|
86
|
+
|
87
|
+
def werewolf_end(self) -> bool:
|
88
|
+
self._werewolf_interact_count -= 1
|
89
|
+
if self._werewolf_interact_count == 0:
|
90
|
+
self.werewolf_finished.set()
|
91
|
+
return self.werewolf_finished.is_set()
|
92
|
+
|
81
93
|
|
82
94
|
@dataclasses.dataclass
|
83
95
|
class KillInfo:
|