nonebot-plugin-werewolf 1.1.7__py3-none-any.whl → 1.1.8__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.
Files changed (35) hide show
  1. nonebot_plugin_werewolf/__init__.py +1 -1
  2. nonebot_plugin_werewolf/config.py +73 -15
  3. nonebot_plugin_werewolf/constant.py +54 -46
  4. nonebot_plugin_werewolf/exception.py +2 -4
  5. nonebot_plugin_werewolf/game.py +154 -136
  6. nonebot_plugin_werewolf/matchers/__init__.py +1 -0
  7. nonebot_plugin_werewolf/matchers/depends.py +4 -4
  8. nonebot_plugin_werewolf/matchers/edit_behavior.py +205 -0
  9. nonebot_plugin_werewolf/matchers/edit_preset.py +11 -11
  10. nonebot_plugin_werewolf/matchers/message_in_game.py +3 -1
  11. nonebot_plugin_werewolf/matchers/poke/chronocat_poke.py +8 -5
  12. nonebot_plugin_werewolf/matchers/poke/ob11_poke.py +3 -3
  13. nonebot_plugin_werewolf/matchers/start_game.py +213 -175
  14. nonebot_plugin_werewolf/matchers/superuser_ops.py +3 -3
  15. nonebot_plugin_werewolf/models.py +31 -19
  16. nonebot_plugin_werewolf/player_set.py +10 -8
  17. nonebot_plugin_werewolf/players/__init__.py +1 -1
  18. nonebot_plugin_werewolf/players/can_shoot.py +15 -15
  19. nonebot_plugin_werewolf/players/civilian.py +1 -1
  20. nonebot_plugin_werewolf/players/guard.py +16 -14
  21. nonebot_plugin_werewolf/players/hunter.py +1 -1
  22. nonebot_plugin_werewolf/players/idiot.py +3 -3
  23. nonebot_plugin_werewolf/players/{joker.py → jester.py} +4 -5
  24. nonebot_plugin_werewolf/players/player.py +90 -28
  25. nonebot_plugin_werewolf/players/prophet.py +11 -10
  26. nonebot_plugin_werewolf/players/werewolf.py +46 -11
  27. nonebot_plugin_werewolf/players/witch.py +29 -12
  28. nonebot_plugin_werewolf/players/wolfking.py +1 -1
  29. nonebot_plugin_werewolf/utils.py +105 -6
  30. {nonebot_plugin_werewolf-1.1.7.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/METADATA +23 -20
  31. nonebot_plugin_werewolf-1.1.8.dist-info/RECORD +35 -0
  32. {nonebot_plugin_werewolf-1.1.7.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/WHEEL +1 -1
  33. nonebot_plugin_werewolf-1.1.7.dist-info/RECORD +0 -34
  34. {nonebot_plugin_werewolf-1.1.7.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/LICENSE +0 -0
  35. {nonebot_plugin_werewolf-1.1.7.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,8 @@
1
- from nonebot_plugin_alconna.uniseg import UniMessage
2
1
  from typing_extensions import override
3
2
 
4
- from ..constant import STOP_COMMAND_PROMPT
3
+ from nonebot_plugin_alconna.uniseg import UniMessage
4
+
5
+ from ..constant import stop_command_prompt
5
6
  from ..models import KillReason
6
7
  from .player import Player
7
8
 
@@ -9,7 +10,7 @@ from .player import Player
9
10
  class CanShoot(Player):
10
11
  @override
11
12
  async def post_kill(self) -> None:
12
- if self.kill_info and self.kill_info.reason == KillReason.Poison:
13
+ if self.kill_info and self.kill_info.reason == KillReason.POISON:
13
14
  await self.send("⚠️你昨晚被女巫毒杀,无法使用技能")
14
15
  return await super().post_kill()
15
16
 
@@ -21,33 +22,32 @@ class CanShoot(Player):
21
22
 
22
23
  self.game.state.shoot = None
23
24
  shoot = await self.shoot()
24
-
25
+ msg = UniMessage.text("玩家 ").at(self.user_id).text(" ")
25
26
  if shoot is not None:
26
27
  self.game.state.shoot = self
27
- await self.send(
28
- UniMessage.text(f"🔫{self.role_name} ")
29
- .at(self.user_id)
30
- .text(" 射杀了玩家 ")
31
- .at(shoot.user_id)
32
- )
33
- await shoot.kill(KillReason.Shoot, self)
28
+ await self.game.send("🔫" + msg.text("射杀了玩家 ").at(shoot.user_id))
29
+ await shoot.kill(KillReason.SHOOT, self)
34
30
  self.selected = shoot
35
31
  else:
36
- await self.send(f"ℹ️{self.role_name}选择了取消技能")
32
+ await self.game.send("ℹ️" + msg.text("选择了取消技能"))
33
+
37
34
  return await super().post_kill()
38
35
 
39
36
  async def shoot(self) -> Player | None:
40
37
  players = self.game.players.alive().exclude(self)
41
38
  await self.send(
42
39
  "💫请选择需要射杀的玩家:\n"
43
- + players.show()
44
- + "\n\n🔫发送编号选择玩家"
45
- + f"\n❌发送 “{STOP_COMMAND_PROMPT}” 取消技能"
40
+ f"{players.show()}\n\n"
41
+ "🔫发送编号选择玩家\n"
42
+ f"❌发送 “{stop_command_prompt()}” 取消技能",
43
+ stop_btn_label="取消技能",
44
+ select_players=players,
46
45
  )
47
46
 
48
47
  if selected := await self._select_player(
49
48
  players,
50
49
  on_stop="ℹ️已取消技能,回合结束",
50
+ stop_btn_label="取消技能",
51
51
  ):
52
52
  await self.send(f"🎯选择射杀的玩家: {selected.name}")
53
53
 
@@ -2,6 +2,6 @@ from ..models import Role, RoleGroup
2
2
  from .player import Player
3
3
 
4
4
 
5
- @Player.register_role(Role.Civilian, RoleGroup.GoodGuy)
5
+ @Player.register_role(Role.CIVILIAN, RoleGroup.GOODGUY)
6
6
  class Civilian(Player):
7
7
  pass
@@ -1,31 +1,33 @@
1
- from nonebot_plugin_alconna.uniseg import UniMessage
2
1
  from typing_extensions import override
3
2
 
4
- from ..constant import STOP_COMMAND_PROMPT
5
- from ..models import Role, RoleGroup
3
+ from ..constant import stop_command_prompt
4
+ from ..models import GameState, Role, RoleGroup
6
5
  from .player import Player
7
6
 
8
7
 
9
- @Player.register_role(Role.Guard, RoleGroup.GoodGuy)
8
+ @Player.register_role(Role.GUARD, RoleGroup.GOODGUY)
10
9
  class Guard(Player):
11
10
  @override
12
11
  async def _check_selected(self, player: Player) -> Player | None:
13
- if player is not self.selected:
14
- return player
15
- await self.send("⚠️守卫不能连续两晚保护同一目标,请重新选择")
16
- return None
12
+ if self.game.state.state == GameState.State.NIGHT and player is self.selected:
13
+ await self.send("⚠️守卫不能连续两晚保护同一目标,请重新选择")
14
+ return None
15
+
16
+ return player
17
17
 
18
18
  @override
19
- async def interact(self) -> None:
19
+ async def _interact(self) -> None:
20
20
  players = self.game.players.alive()
21
21
  await self.send(
22
- UniMessage.text("💫请选择需要保护的玩家:\n")
23
- .text(players.show())
24
- .text("\n\n🛡️发送编号选择玩家")
25
- .text(f"\n❌发送 “{STOP_COMMAND_PROMPT}” 结束回合")
22
+ "💫请选择需要保护的玩家:\n"
23
+ f"{players.show()}\n\n"
24
+ "🛡️发送编号选择玩家\n"
25
+ f"❌发送 “{stop_command_prompt()}” 结束回合",
26
+ stop_btn_label="结束回合",
27
+ select_players=players,
26
28
  )
27
29
 
28
- self.selected = await self._select_player(players)
30
+ self.selected = await self._select_player(players, stop_btn_label="结束回合")
29
31
  if self.selected:
30
32
  self.game.state.protected.add(self.selected)
31
33
  await self.send(f"✅本回合保护的玩家: {self.selected.name}")
@@ -3,6 +3,6 @@ from .can_shoot import CanShoot
3
3
  from .player import Player
4
4
 
5
5
 
6
- @Player.register_role(Role.Hunter, RoleGroup.GoodGuy)
6
+ @Player.register_role(Role.HUNTER, RoleGroup.GOODGUY)
7
7
  class Hunter(CanShoot, Player):
8
8
  pass
@@ -1,9 +1,9 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from typing import TYPE_CHECKING
4
+ from typing_extensions import override
4
5
 
5
6
  from nonebot_plugin_alconna.uniseg import UniMessage
6
- from typing_extensions import override
7
7
 
8
8
  from ..models import KillReason, Role, RoleGroup
9
9
  from .player import Player
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
12
12
  from ..player_set import PlayerSet
13
13
 
14
14
 
15
- @Player.register_role(Role.Idiot, RoleGroup.GoodGuy)
15
+ @Player.register_role(Role.IDIOT, RoleGroup.GOODGUY)
16
16
  class Idiot(Player):
17
17
  voted: bool = False
18
18
 
@@ -25,7 +25,7 @@ class Idiot(Player):
25
25
 
26
26
  @override
27
27
  async def kill(self, reason: KillReason, *killers: Player) -> bool:
28
- if reason == KillReason.Vote and not self.voted:
28
+ if reason == KillReason.VOTE and not self.voted:
29
29
  self.voted = True
30
30
  await self.game.send(
31
31
  UniMessage.text("⚙️玩家")
@@ -1,5 +1,4 @@
1
1
  from typing import TYPE_CHECKING
2
-
3
2
  from typing_extensions import override
4
3
 
5
4
  from ..exception import GameFinished
@@ -7,8 +6,8 @@ from ..models import GameStatus, KillReason, Role, RoleGroup
7
6
  from .player import Player
8
7
 
9
8
 
10
- @Player.register_role(Role.Joker, RoleGroup.Others)
11
- class Joker(Player):
9
+ @Player.register_role(Role.JESTER, RoleGroup.OTHERS)
10
+ class Jester(Player):
12
11
  @override
13
12
  async def notify_role(self) -> None:
14
13
  await super().notify_role()
@@ -17,9 +16,9 @@ class Joker(Player):
17
16
  @override
18
17
  async def kill(self, reason: KillReason, *killers: Player) -> bool:
19
18
  await super().kill(reason, *killers)
20
- if reason == KillReason.Vote:
19
+ if reason == KillReason.VOTE:
21
20
  if TYPE_CHECKING:
22
21
  assert self.kill_info is not None
23
22
  self.game.killed_players.append((self.name, self.kill_info))
24
- raise GameFinished(GameStatus.Joker)
23
+ raise GameFinished(GameStatus.JESTER)
25
24
  return True
@@ -10,9 +10,17 @@ from nonebot.utils import escape_tag
10
10
  from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage
11
11
  from nonebot_plugin_uninfo import SceneType
12
12
 
13
- from ..constant import STOP_COMMAND, STOP_COMMAND_PROMPT, role_emoji, role_name_conv
13
+ from ..config import GameBehavior
14
+ from ..constant import ROLE_EMOJI, ROLE_NAME_CONV, STOP_COMMAND, stop_command_prompt
14
15
  from ..models import KillInfo, KillReason, Role, RoleGroup
15
- from ..utils import InputStore, check_index, link
16
+ from ..utils import (
17
+ InputStore,
18
+ SendHandler,
19
+ add_players_button,
20
+ add_stop_button,
21
+ check_index,
22
+ link,
23
+ )
16
24
 
17
25
  if TYPE_CHECKING:
18
26
  from ..game import Game
@@ -24,6 +32,17 @@ _P = TypeVar("_P", bound=type["Player"])
24
32
  logger = nonebot.logger.opt(colors=True)
25
33
 
26
34
 
35
+ class _SendHandler(SendHandler[str | None]):
36
+ def solve_msg(
37
+ self,
38
+ msg: UniMessage,
39
+ stop_btn_label: str | None = None,
40
+ ) -> UniMessage:
41
+ if stop_btn_label is not None:
42
+ msg = add_stop_button(msg, stop_btn_label)
43
+ return msg
44
+
45
+
27
46
  class Player:
28
47
  __player_class: ClassVar[dict[Role, type["Player"]]] = {}
29
48
  role: ClassVar[Role]
@@ -47,6 +66,8 @@ class Player:
47
66
  self.bot = bot
48
67
  self.killed = anyio.Event()
49
68
  self._member = None
69
+ self._send_handler = _SendHandler()
70
+ self._send_handler.update(self.__user, bot)
50
71
 
51
72
  @classmethod
52
73
  def register_role(cls, role: Role, role_group: RoleGroup, /) -> Callable[[_P], _P]:
@@ -67,9 +88,7 @@ class Player:
67
88
  return cls.__player_class[role](bot, game, user_id)
68
89
 
69
90
  def __repr__(self) -> str:
70
- return (
71
- f"<Player {self.role_name}: user={self.user_id!r} " f"alive={self.alive}>"
72
- )
91
+ return f"<Player {self.role_name}: user={self.user_id!r} alive={self.alive}>"
73
92
 
74
93
  @property
75
94
  def game(self) -> "Game":
@@ -83,7 +102,7 @@ class Player:
83
102
 
84
103
  @functools.cached_property
85
104
  def role_name(self) -> str:
86
- return role_name_conv[self.role]
105
+ return ROLE_NAME_CONV[self.role]
87
106
 
88
107
  async def _fetch_member(self) -> None:
89
108
  member = await self.game.interface.get_member(
@@ -128,41 +147,75 @@ class Player:
128
147
  return name
129
148
 
130
149
  @final
131
- def _log(self, text: str) -> None:
150
+ def log(self, text: str) -> None:
132
151
  text = text.replace("\n", "\\n")
133
- logger.info(
134
- f"{self.game.colored_name} | "
135
- f"[<b><m>{self.role_name}</m></b>] "
136
- f"{self.colored_name} | {text}",
137
- )
152
+ self.game.log(f"[<b><m>{self.role_name}</m></b>] {self.colored_name} | {text}")
138
153
 
139
154
  @final
140
- async def send(self, message: str | UniMessage) -> Receipt:
155
+ async def send(
156
+ self,
157
+ message: str | UniMessage,
158
+ *,
159
+ stop_btn_label: str | None = None,
160
+ select_players: "PlayerSet | None" = None,
161
+ skip_handler: bool = False,
162
+ ) -> Receipt:
141
163
  if isinstance(message, str):
142
164
  message = UniMessage.text(message)
143
165
 
144
- self._log(f"<g>Send</g> | {escape_tag(str(message))}")
145
- return await message.send(target=self.__user, bot=self.bot)
166
+ self.log(f"<g>Send</g> | {escape_tag(str(message))}")
146
167
 
147
- @final
148
- async def receive(self, prompt: str | UniMessage | None = None) -> UniMessage:
149
- if prompt:
150
- await self.send(prompt)
168
+ if select_players:
169
+ message = add_players_button(message, select_players)
170
+ if skip_handler:
171
+ return await message.send(self.__user, self.bot)
172
+ return await self._send_handler.send(message, stop_btn_label)
151
173
 
174
+ @final
175
+ async def receive(self) -> UniMessage:
152
176
  result = await InputStore.fetch(self.user_id)
153
- self._log(f"<y>Recv</y> | {escape_tag(str(result))}")
177
+ self.log(f"<y>Recv</y> | {escape_tag(str(result))}")
154
178
  return result
155
179
 
156
180
  @final
157
181
  async def receive_text(self) -> str:
158
182
  return (await self.receive()).extract_plain_text()
159
183
 
160
- async def interact(self) -> None:
184
+ @property
185
+ def interact_timeout(self) -> float:
186
+ return GameBehavior.get().timeout.interact
187
+
188
+ async def _before_interact(self) -> None:
161
189
  return
162
190
 
191
+ async def _interact(self) -> None:
192
+ return
193
+
194
+ async def _after_interact(self) -> None:
195
+ return
196
+
197
+ async def interact(self) -> None:
198
+ if not getattr(self._interact, "__override__", False):
199
+ await self.send("ℹ️请等待其他玩家结束交互...")
200
+ return
201
+
202
+ await self._before_interact()
203
+
204
+ timeout = self.interact_timeout
205
+ await self.send(f"✏️{self.role_name}交互开始,限时 {timeout / 60:.2f} 分钟")
206
+
207
+ try:
208
+ with anyio.fail_after(timeout):
209
+ await self._interact()
210
+ except TimeoutError:
211
+ logger.debug(f"{self.role_name}交互超时 (<y>{timeout}</y>s)")
212
+ await self.send(f"⚠️{self.role_name}交互超时")
213
+
214
+ await self._after_interact()
215
+
163
216
  async def notify_role(self) -> None:
164
217
  await self._fetch_member()
165
- await self.send(f"⚙️你的身份: {role_emoji[self.role]}{self.role_name}")
218
+ await self.send(f"⚙️你的身份: {ROLE_EMOJI[self.role]}{self.role_name}")
166
219
 
167
220
  async def kill(self, reason: KillReason, *killers: "Player") -> bool:
168
221
  if self.alive:
@@ -175,13 +228,17 @@ class Player:
175
228
 
176
229
  async def vote(self, players: "PlayerSet") -> "Player | None":
177
230
  await self.send(
178
- f"💫请选择需要投票的玩家:\n{players.show()}"
179
- f"\n\n🗳️发送编号选择玩家\n❌发送 “{STOP_COMMAND_PROMPT}” 弃票"
180
- f"\n\n限时1分钟,超时将视为弃票"
231
+ f"💫请选择需要投票的玩家:\n"
232
+ f"{players.show()}\n\n"
233
+ "🗳️发送编号选择玩家\n"
234
+ f"❌发送 “{stop_command_prompt()}” 弃票\n\n"
235
+ "限时1分钟,超时将视为弃票",
236
+ stop_btn_label="弃票",
237
+ select_players=players,
181
238
  )
182
239
 
183
240
  try:
184
- with anyio.fail_after(60):
241
+ with anyio.fail_after(GameBehavior.get().timeout.vote):
185
242
  selected = await self._select_player(
186
243
  players,
187
244
  on_stop="⚠️你选择了弃票",
@@ -204,10 +261,11 @@ class Player:
204
261
  *,
205
262
  on_stop: str | None = None,
206
263
  on_index_error: str | None = None,
264
+ stop_btn_label: str | None = None,
207
265
  ) -> "Player | None":
208
266
  on_stop = on_stop or "ℹ️你选择了取消,回合结束"
209
267
  on_index_error = (
210
- on_index_error or f"⚠️输入错误: 请发送玩家编号或 “{STOP_COMMAND_PROMPT}”"
268
+ on_index_error or f"⚠️输入错误: 请发送玩家编号或 “{stop_command_prompt()}”"
211
269
  )
212
270
  selected = None
213
271
 
@@ -219,7 +277,11 @@ class Player:
219
277
  return None
220
278
  index = check_index(text, len(players))
221
279
  if index is None:
222
- await self.send(on_index_error)
280
+ await self.send(
281
+ on_index_error,
282
+ stop_btn_label=stop_btn_label,
283
+ select_players=players,
284
+ )
223
285
  continue
224
286
  selected = await self._check_selected(players[index - 1])
225
287
 
@@ -1,23 +1,24 @@
1
- from nonebot_plugin_alconna.uniseg import UniMessage
2
1
  from typing_extensions import override
3
2
 
4
- from ..constant import STOP_COMMAND_PROMPT
3
+ from ..constant import stop_command_prompt
5
4
  from ..models import Role, RoleGroup
6
5
  from .player import Player
7
6
 
8
7
 
9
- @Player.register_role(Role.Prophet, RoleGroup.GoodGuy)
8
+ @Player.register_role(Role.PROPHET, RoleGroup.GOODGUY)
10
9
  class Prophet(Player):
11
10
  @override
12
- async def interact(self) -> None:
11
+ async def _interact(self) -> None:
13
12
  players = self.game.players.alive().exclude(self)
14
13
  await self.send(
15
- UniMessage.text("💫请选择需要查验身份的玩家:\n")
16
- .text(players.show())
17
- .text("\n\n🔮发送编号选择玩家")
18
- .text(f"\n❌发送 “{STOP_COMMAND_PROMPT}” 结束回合(不查验身份)")
14
+ "💫请选择需要查验身份的玩家:\n"
15
+ f"{players.show()}\n\n"
16
+ "🔮发送编号选择玩家\n"
17
+ f"❌发送 “{stop_command_prompt()}” 结束回合(不查验身份)",
18
+ stop_btn_label="结束回合",
19
+ select_players=players,
19
20
  )
20
21
 
21
- if selected := await self._select_player(players):
22
- result = "狼人" if selected.role_group == RoleGroup.Werewolf else "好人"
22
+ if selected := await self._select_player(players, stop_btn_label="结束回合"):
23
+ result = "狼人" if selected.role_group == RoleGroup.WEREWOLF else "好人"
23
24
  await self.send(f"✏️玩家 {selected.name} 的阵营是『{result}』")
@@ -1,10 +1,13 @@
1
+ import secrets
1
2
  from typing import TYPE_CHECKING
3
+ from typing_extensions import override
2
4
 
3
5
  import anyio
6
+ import nonebot
4
7
  from nonebot_plugin_alconna.uniseg import UniMessage
5
- from typing_extensions import override
6
8
 
7
- from ..constant import STOP_COMMAND, STOP_COMMAND_PROMPT
9
+ from ..config import GameBehavior
10
+ from ..constant import STOP_COMMAND, stop_command_prompt
8
11
  from ..models import Role, RoleGroup
9
12
  from ..utils import ObjectStream, check_index
10
13
  from .player import Player
@@ -12,15 +15,22 @@ from .player import Player
12
15
  if TYPE_CHECKING:
13
16
  from ..player_set import PlayerSet
14
17
 
18
+ logger = nonebot.logger.opt(colors=True)
19
+
15
20
 
16
- @Player.register_role(Role.Werewolf, RoleGroup.Werewolf)
21
+ @Player.register_role(Role.WEREWOLF, RoleGroup.WEREWOLF)
17
22
  class Werewolf(Player):
18
23
  stream: ObjectStream[str | UniMessage]
19
24
 
25
+ @property
26
+ @override
27
+ def interact_timeout(self) -> float:
28
+ return GameBehavior.get().timeout.werewolf
29
+
20
30
  @override
21
31
  async def notify_role(self) -> None:
22
32
  await super().notify_role()
23
- partners = self.game.players.alive().select(RoleGroup.Werewolf).exclude(self)
33
+ partners = self.game.players.alive().select(RoleGroup.WEREWOLF).exclude(self)
24
34
  if partners:
25
35
  await self.send(
26
36
  "🐺你的队友:\n"
@@ -37,7 +47,11 @@ class Werewolf(Player):
37
47
  if index is not None:
38
48
  self.selected = players[index - 1]
39
49
  msg = f"当前选择玩家: {self.selected.name}"
40
- await self.send(f"🎯{msg}\n发送 “{STOP_COMMAND_PROMPT}” 结束回合")
50
+ await self.send(
51
+ f"🎯{msg}\n发送 “{stop_command_prompt()}” 结束回合",
52
+ stop_btn_label="结束回合",
53
+ select_players=players,
54
+ )
41
55
  await self.stream.send(f"📝队友 {self.name} {msg}")
42
56
  if text == STOP_COMMAND:
43
57
  if self.selected is not None:
@@ -45,9 +59,14 @@ class Werewolf(Player):
45
59
  await self.stream.send(f"📝队友 {self.name} 结束当前回合")
46
60
  self.stream.close()
47
61
  return
48
- await self.send("⚠️当前未选择玩家,无法结束回合")
62
+ await self.send(
63
+ "⚠️当前未选择玩家,无法结束回合",
64
+ select_players=players,
65
+ )
49
66
  else:
50
- await self.stream.send(UniMessage(f"💬队友 {self.name}:\n") + input_msg)
67
+ await self.stream.send(
68
+ UniMessage.text(f"💬队友 {self.name}:\n") + input_msg
69
+ )
51
70
 
52
71
  async def _handle_broadcast(self, partners: "PlayerSet") -> None:
53
72
  while not self.stream.closed:
@@ -59,9 +78,9 @@ class Werewolf(Player):
59
78
  await partners.broadcast(message)
60
79
 
61
80
  @override
62
- async def interact(self) -> None:
81
+ async def _interact(self) -> None:
63
82
  players = self.game.players.alive()
64
- partners = players.select(RoleGroup.Werewolf).exclude(self)
83
+ partners = players.select(RoleGroup.WEREWOLF).exclude(self)
65
84
 
66
85
  msg = UniMessage()
67
86
  if partners:
@@ -74,8 +93,9 @@ class Werewolf(Player):
74
93
  msg.text("💫请选择今晚的目标:\n")
75
94
  .text(players.show())
76
95
  .text("\n\n🔪发送编号选择玩家")
77
- .text(f"\n❌发送 “{STOP_COMMAND_PROMPT}” 结束回合")
78
- .text("\n\n⚠️意见未统一将空刀")
96
+ .text(f"\n❌发送 “{stop_command_prompt()}” 结束回合")
97
+ .text("\n\n⚠️意见未统一将空刀"),
98
+ select_players=players,
79
99
  )
80
100
 
81
101
  self.stream = ObjectStream[str | UniMessage](8)
@@ -86,3 +106,18 @@ class Werewolf(Player):
86
106
  tg.start_soon(self._handle_broadcast, partners)
87
107
  finally:
88
108
  del self.stream
109
+
110
+ @override
111
+ async def _after_interact(self) -> None:
112
+ state = self.game.state
113
+ if not state.werewolf_finished.is_set():
114
+ state.werewolf_finished.set()
115
+ w = self.game.players.alive().select(RoleGroup.WEREWOLF)
116
+ if (s := w.player_selected()).size == 1:
117
+ state.killed = s.pop()
118
+ await w.broadcast(f"🔪今晚选择的目标为: {state.killed.name}")
119
+ else:
120
+ await w.broadcast("⚠️狼人阵营意见未统一,此晚空刀")
121
+
122
+ if not self.game.players.alive().select(Role.WITCH):
123
+ await anyio.sleep(5 + secrets.randbelow(15))
@@ -1,13 +1,17 @@
1
- from nonebot_plugin_alconna.uniseg import UniMessage
2
1
  from typing_extensions import override
3
2
 
4
- from ..constant import STOP_COMMAND_PROMPT
3
+ import nonebot
4
+ from nonebot_plugin_alconna.uniseg import UniMessage
5
+
6
+ from ..constant import stop_command_prompt
5
7
  from ..models import Role, RoleGroup
6
8
  from ..utils import as_player_set
7
9
  from .player import Player
8
10
 
11
+ logger = nonebot.logger.opt(colors=True)
12
+
9
13
 
10
- @Player.register_role(Role.Witch, RoleGroup.GoodGuy)
14
+ @Player.register_role(Role.WITCH, RoleGroup.GOODGUY)
11
15
  class Witch(Player):
12
16
  antidote: bool = True
13
17
  poison: bool = True
@@ -23,13 +27,18 @@ class Witch(Player):
23
27
  await self.send(msg.text("⚙️你已经用过解药了"))
24
28
  return False
25
29
 
26
- msg.text(f"✏️使用解药请发送 “1”\n❌不使用解药请发送 “{STOP_COMMAND_PROMPT}”")
27
- await self.send(msg)
30
+ msg.text(f"✏️使用解药请发送 “1”\n❌不使用解药请发送 “{stop_command_prompt()}”")
31
+ await self.send(
32
+ msg,
33
+ stop_btn_label="不使用解药",
34
+ select_players=as_player_set(killed),
35
+ )
28
36
 
29
37
  if not await self._select_player(
30
38
  as_player_set(killed),
31
39
  on_stop=f"ℹ️你选择不对 {killed.name} 使用解药",
32
- on_index_error=f"⚠️输入错误: 请输入 “1” 或 “{STOP_COMMAND_PROMPT}”",
40
+ on_index_error=f"⚠️输入错误: 请输入 “1” 或 “{stop_command_prompt()}”",
41
+ stop_btn_label="不使用解药",
33
42
  ):
34
43
  return False
35
44
 
@@ -40,7 +49,12 @@ class Witch(Player):
40
49
  return True
41
50
 
42
51
  @override
43
- async def interact(self) -> None:
52
+ async def _before_interact(self) -> None:
53
+ await self.send("ℹ️请等待狼人决定目标...")
54
+ await self.game.state.werewolf_finished.wait()
55
+
56
+ @override
57
+ async def _interact(self) -> None:
44
58
  if await self.handle_killed():
45
59
  return
46
60
 
@@ -50,16 +64,19 @@ class Witch(Player):
50
64
 
51
65
  players = self.game.players.alive()
52
66
  await self.send(
53
- UniMessage.text("💫你有一瓶毒药\n")
54
- .text("玩家列表:\n")
55
- .text(players.show())
56
- .text("\n\n🧪发送玩家编号使用毒药")
57
- .text(f"\n❌发送 “{STOP_COMMAND_PROMPT}” 结束回合(不使用药水)")
67
+ "💫你有一瓶毒药\n"
68
+ "玩家列表:\n"
69
+ f"{players.show()}\n\n"
70
+ "🧪发送玩家编号使用毒药\n"
71
+ f"❌发送 “{stop_command_prompt()}” 结束回合(不使用药水)",
72
+ stop_btn_label="结束回合",
73
+ select_players=players,
58
74
  )
59
75
 
60
76
  if selected := await self._select_player(
61
77
  players,
62
78
  on_stop="ℹ️你选择不使用毒药,回合结束",
79
+ stop_btn_label="结束回合",
63
80
  ):
64
81
  self.poison = False
65
82
  self.selected = selected
@@ -6,7 +6,7 @@ from .player import Player
6
6
  from .werewolf import Werewolf
7
7
 
8
8
 
9
- @Player.register_role(Role.WolfKing, RoleGroup.Werewolf)
9
+ @Player.register_role(Role.WOLFKING, RoleGroup.WEREWOLF)
10
10
  class WolfKing(CanShoot, Werewolf):
11
11
  @override
12
12
  async def notify_role(self) -> None: