nonebot-plugin-werewolf 1.1.3__py3-none-any.whl → 1.1.5__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 (29) hide show
  1. nonebot_plugin_werewolf/__init__.py +2 -1
  2. nonebot_plugin_werewolf/constant.py +23 -1
  3. nonebot_plugin_werewolf/game.py +172 -113
  4. nonebot_plugin_werewolf/matchers/depends.py +38 -0
  5. nonebot_plugin_werewolf/matchers/message_in_game.py +14 -4
  6. nonebot_plugin_werewolf/matchers/poke/__init__.py +8 -0
  7. nonebot_plugin_werewolf/matchers/poke/chronocat_poke.py +117 -0
  8. nonebot_plugin_werewolf/matchers/{ob11_ext.py → poke/ob11_poke.py} +21 -19
  9. nonebot_plugin_werewolf/matchers/start_game.py +161 -15
  10. nonebot_plugin_werewolf/player_set.py +33 -34
  11. nonebot_plugin_werewolf/players/can_shoot.py +12 -18
  12. nonebot_plugin_werewolf/players/civilian.py +2 -2
  13. nonebot_plugin_werewolf/players/guard.py +15 -22
  14. nonebot_plugin_werewolf/players/hunter.py +2 -2
  15. nonebot_plugin_werewolf/players/idiot.py +3 -3
  16. nonebot_plugin_werewolf/players/joker.py +2 -2
  17. nonebot_plugin_werewolf/players/player.py +137 -65
  18. nonebot_plugin_werewolf/players/prophet.py +7 -15
  19. nonebot_plugin_werewolf/players/werewolf.py +51 -29
  20. nonebot_plugin_werewolf/players/witch.py +32 -38
  21. nonebot_plugin_werewolf/players/wolfking.py +2 -2
  22. nonebot_plugin_werewolf/utils.py +56 -190
  23. {nonebot_plugin_werewolf-1.1.3.dist-info → nonebot_plugin_werewolf-1.1.5.dist-info}/METADATA +14 -2
  24. nonebot_plugin_werewolf-1.1.5.dist-info/RECORD +31 -0
  25. {nonebot_plugin_werewolf-1.1.3.dist-info → nonebot_plugin_werewolf-1.1.5.dist-info}/WHEEL +1 -1
  26. nonebot_plugin_werewolf/_timeout.py +0 -110
  27. nonebot_plugin_werewolf-1.1.3.dist-info/RECORD +0 -29
  28. {nonebot_plugin_werewolf-1.1.3.dist-info → nonebot_plugin_werewolf-1.1.5.dist-info}/LICENSE +0 -0
  29. {nonebot_plugin_werewolf-1.1.3.dist-info → nonebot_plugin_werewolf-1.1.5.dist-info}/top_level.txt +0 -0
@@ -2,10 +2,10 @@ from typing_extensions import override
2
2
 
3
3
  from ..constant import GameStatus, KillReason, Role, RoleGroup
4
4
  from ..exception import GameFinished
5
- from .player import Player, register_role
5
+ from .player import Player
6
6
 
7
7
 
8
- @register_role(Role.Joker, RoleGroup.Others)
8
+ @Player.register_role(Role.Joker, RoleGroup.Others)
9
9
  class Joker(Player):
10
10
  @override
11
11
  async def notify_role(self) -> None:
@@ -1,95 +1,151 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
1
  import functools
5
2
  import weakref
3
+ from collections.abc import Callable
6
4
  from dataclasses import dataclass
7
5
  from typing import TYPE_CHECKING, ClassVar, Final, TypeVar, final
8
6
 
7
+ import anyio
8
+ from nonebot.adapters import Bot
9
9
  from nonebot.log import logger
10
+ from nonebot.utils import escape_tag
10
11
  from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage
11
-
12
- from ..constant import KillReason, Role, RoleGroup, role_emoji, role_name_conv
13
- from ..utils import InputStore, check_index
12
+ from nonebot_plugin_uninfo import SceneType
13
+
14
+ from ..constant import (
15
+ STOP_COMMAND,
16
+ STOP_COMMAND_PROMPT,
17
+ KillReason,
18
+ Role,
19
+ RoleGroup,
20
+ role_emoji,
21
+ role_name_conv,
22
+ )
23
+ from ..utils import InputStore, as_player_set, check_index, link
14
24
 
15
25
  if TYPE_CHECKING:
16
- from collections.abc import Callable
17
-
18
- from nonebot.adapters import Bot
19
-
20
26
  from ..game import Game
21
27
  from ..player_set import PlayerSet
22
28
 
23
29
 
24
- P = TypeVar("P", bound=type["Player"])
25
- PLAYER_CLASS: dict[Role, type[Player]] = {}
30
+ _P = TypeVar("_P", bound=type["Player"])
26
31
 
27
32
 
28
33
  @dataclass
29
34
  class KillInfo:
30
35
  reason: KillReason
31
- killers: PlayerSet
36
+ killers: "PlayerSet"
32
37
 
33
38
 
34
39
  class Player:
40
+ __player_class: ClassVar[dict[Role, type["Player"]]] = {}
35
41
  role: ClassVar[Role]
36
42
  role_group: ClassVar[RoleGroup]
37
43
 
38
44
  bot: Final[Bot]
39
- _game_ref: Final[weakref.ReferenceType[Game]]
40
- user: Final[Target]
41
- name: Final[str]
42
45
  alive: bool = True
43
- killed: Final[asyncio.Event]
46
+ killed: Final[anyio.Event]
44
47
  kill_info: KillInfo | None = None
45
- selected: Player | None = None
48
+ selected: "Player | None" = None
46
49
 
47
50
  @final
48
- def __init__(self, bot: Bot, game: Game, user_id: str, name: str) -> None:
49
- self.bot = bot
50
- self._game_ref = weakref.ref(game)
51
- self.user = Target(
51
+ def __init__(self, bot: Bot, game: "Game", user_id: str) -> None:
52
+ self.__user = Target(
52
53
  user_id,
53
54
  private=True,
54
55
  self_id=bot.self_id,
55
56
  adapter=bot.adapter.get_name(),
56
57
  )
57
- self.name = name
58
- self.killed = asyncio.Event()
58
+ self.__game_ref = weakref.ref(game)
59
+ self.bot = bot
60
+ self.killed = anyio.Event()
61
+ self._member = None
62
+
63
+ @classmethod
64
+ def register_role(cls, role: Role, role_group: RoleGroup, /) -> Callable[[_P], _P]:
65
+ def decorator(c: _P, /) -> _P:
66
+ c.role = role
67
+ c.role_group = role_group
68
+ cls.__player_class[role] = c
69
+ return c
70
+
71
+ return decorator
59
72
 
60
73
  @final
61
74
  @classmethod
62
- def new(cls, role: Role, bot: Bot, game: Game, user_id: str, name: str) -> Player:
63
- if role not in PLAYER_CLASS:
75
+ def new(cls, role: Role, bot: Bot, game: "Game", user_id: str) -> "Player":
76
+ if role not in cls.__player_class:
64
77
  raise ValueError(f"Unexpected role: {role!r}")
65
78
 
66
- return PLAYER_CLASS[role](bot, game, user_id, name)
79
+ return cls.__player_class[role](bot, game, user_id)
67
80
 
68
81
  def __repr__(self) -> str:
69
- return f"<Player {self.role_name}: user={self.name!r} alive={self.alive}>"
82
+ return (
83
+ f"<Player {self.role_name}: user={self.user_id!r} " f"alive={self.alive}>"
84
+ )
70
85
 
71
86
  @property
72
- def game(self) -> Game:
73
- if game := self._game_ref():
87
+ def game(self) -> "Game":
88
+ if game := self.__game_ref():
74
89
  return game
75
90
  raise ValueError("Game not exist")
76
91
 
77
92
  @functools.cached_property
78
93
  def user_id(self) -> str:
79
- return self.user.id
94
+ return self.__user.id
80
95
 
81
96
  @functools.cached_property
82
97
  def role_name(self) -> str:
83
98
  return role_name_conv[self.role]
84
99
 
100
+ async def _fetch_member(self) -> None:
101
+ member = await self.game.interface.get_member(
102
+ SceneType.GROUP,
103
+ self.game.group.id,
104
+ self.user_id,
105
+ )
106
+ if member is None:
107
+ member = await self.game.interface.get_member(
108
+ SceneType.GUILD,
109
+ self.game.group.id,
110
+ self.user_id,
111
+ )
112
+
113
+ self._member = member
114
+
115
+ @final
116
+ @property
117
+ def _member_nick(self) -> str | None:
118
+ return self._member and (
119
+ self._member.nick or self._member.user.nick or self._member.user.name
120
+ )
121
+
122
+ @final
123
+ @property
124
+ def name(self) -> str:
125
+ return self._member_nick or self.user_id
126
+
85
127
  @final
86
- def _log(self, text: str) -> None:
128
+ @property
129
+ def colored_name(self) -> str:
130
+ name = escape_tag(self.user_id)
131
+
132
+ if self._member is None or (nick := self._member_nick) is None:
133
+ name = f"<b><e>{name}</e></b>"
134
+ else:
135
+ name = f"<y>{nick}</y>(<b><e>{name}</e></b>)"
136
+
137
+ if self._member is not None and self._member.user.avatar is not None:
138
+ name = link(name, self._member.user.avatar)
139
+
140
+ return name
141
+
142
+ @final
143
+ async def _log(self, text: str) -> None:
87
144
  text = text.replace("\n", "\\n")
88
145
  logger.opt(colors=True).info(
89
- f"<b><e>{self.game.group.id}</e></b> | "
146
+ f"{self.game.colored_name} | "
90
147
  f"[<b><m>{self.role_name}</m></b>] "
91
- f"<y>{self.name}</y>(<b><e>{self.user_id}</e></b>) | "
92
- f"{text}",
148
+ f"{self.colored_name} | {text}",
93
149
  )
94
150
 
95
151
  @final
@@ -97,16 +153,16 @@ class Player:
97
153
  if isinstance(message, str):
98
154
  message = UniMessage.text(message)
99
155
 
100
- self._log(f"<g>Send</g> | {message}")
101
- return await message.send(target=self.user, bot=self.bot)
156
+ await self._log(f"<g>Send</g> | {escape_tag(str(message))}")
157
+ return await message.send(target=self.__user, bot=self.bot)
102
158
 
103
159
  @final
104
160
  async def receive(self, prompt: str | UniMessage | None = None) -> UniMessage:
105
161
  if prompt:
106
162
  await self.send(prompt)
107
163
 
108
- result = await InputStore.fetch(self.user.id)
109
- self._log(f"<y>Recv</y> | {result}")
164
+ result = await InputStore.fetch(self.user_id)
165
+ await self._log(f"<y>Recv</y> | {escape_tag(str(result))}")
110
166
  return result
111
167
 
112
168
  @final
@@ -117,45 +173,61 @@ class Player:
117
173
  return
118
174
 
119
175
  async def notify_role(self) -> None:
176
+ await self._fetch_member()
120
177
  await self.send(f"⚙️你的身份: {role_emoji[self.role]}{self.role_name}")
121
178
 
122
- async def kill(self, reason: KillReason, *killers: Player) -> bool:
123
- from ..player_set import PlayerSet
124
-
179
+ async def kill(self, reason: KillReason, *killers: "Player") -> bool:
125
180
  self.alive = False
126
- self.kill_info = KillInfo(reason=reason, killers=PlayerSet(killers))
181
+ self.kill_info = KillInfo(reason=reason, killers=as_player_set(*killers))
127
182
  return True
128
183
 
129
184
  async def post_kill(self) -> None:
130
185
  self.killed.set()
131
186
 
132
- async def vote(self, players: PlayerSet) -> tuple[Player, Player] | None:
187
+ async def vote(self, players: "PlayerSet") -> "Player | None":
133
188
  await self.send(
134
189
  f"💫请选择需要投票的玩家:\n{players.show()}"
135
- "\n\n🗳️发送编号选择玩家\n❌发送 “/stop” 弃票"
190
+ f"\n\n🗳️发送编号选择玩家\n❌发送 “{STOP_COMMAND_PROMPT}” 弃票"
191
+ f"\n\n限时1分钟,超时将视为弃票"
136
192
  )
137
193
 
138
- while True:
194
+ try:
195
+ with anyio.fail_after(60):
196
+ selected = await self._select_player(
197
+ players,
198
+ on_stop="⚠️你选择了弃票",
199
+ on_index_error="⚠️输入错误: 请发送编号选择玩家",
200
+ )
201
+ except TimeoutError:
202
+ selected = None
203
+ await self.send("⚠️投票超时,将视为弃票")
204
+
205
+ if selected is not None:
206
+ await self.send(f"🔨投票的玩家: {selected.name}")
207
+ return selected
208
+
209
+ async def _check_selected(self, player: "Player") -> "Player | None":
210
+ return player
211
+
212
+ async def _select_player(
213
+ self,
214
+ players: "PlayerSet",
215
+ *,
216
+ on_stop: str | None = "ℹ️你选择了取消,回合结束",
217
+ on_index_error: str = f"⚠️输入错误: 请发送玩家编号或 “{STOP_COMMAND_PROMPT}”",
218
+ ) -> "Player | None":
219
+ selected = None
220
+
221
+ while selected is None:
139
222
  text = await self.receive_text()
140
- if text == "/stop":
141
- await self.send("⚠️你选择了弃票")
223
+ if text == STOP_COMMAND:
224
+ if on_stop is not None:
225
+ await self.send(on_stop)
142
226
  return None
143
227
  index = check_index(text, len(players))
144
- if index is not None:
145
- selected = index - 1
146
- break
147
- await self.send("⚠️输入错误: 请发送编号选择玩家")
148
-
149
- player = players[selected]
150
- await self.send(f"🔨投票的玩家: {player.name}")
151
- return self, player
152
-
153
-
154
- def register_role(role: Role, role_group: RoleGroup, /) -> Callable[[P], P]:
155
- def decorator(cls: P, /) -> P:
156
- cls.role = role
157
- cls.role_group = role_group
158
- PLAYER_CLASS[role] = cls
159
- return cls
228
+ if index is None:
229
+ await self.send(on_index_error)
230
+ continue
231
+ selected = await self._check_selected(players[index - 1])
160
232
 
161
- return decorator
233
+ return selected
@@ -1,12 +1,11 @@
1
1
  from nonebot_plugin_alconna.uniseg import UniMessage
2
2
  from typing_extensions import override
3
3
 
4
- from ..constant import Role, RoleGroup
5
- from ..utils import check_index
6
- from .player import Player, register_role
4
+ from ..constant import STOP_COMMAND_PROMPT, Role, RoleGroup
5
+ from .player import Player
7
6
 
8
7
 
9
- @register_role(Role.Prophet, RoleGroup.GoodGuy)
8
+ @Player.register_role(Role.Prophet, RoleGroup.GoodGuy)
10
9
  class Prophet(Player):
11
10
  @override
12
11
  async def interact(self) -> None:
@@ -15,16 +14,9 @@ class Prophet(Player):
15
14
  UniMessage.text("💫请选择需要查验身份的玩家:\n")
16
15
  .text(players.show())
17
16
  .text("\n\n🔮发送编号选择玩家")
17
+ .text(f"\n❌发送 “{STOP_COMMAND_PROMPT}” 结束回合(不查验身份)")
18
18
  )
19
19
 
20
- while True:
21
- text = await self.receive_text()
22
- index = check_index(text, len(players))
23
- if index is not None:
24
- selected = index - 1
25
- break
26
- await self.send("⚠️输入错误: 请发送编号选择玩家")
27
-
28
- player = players[selected]
29
- result = "狼人" if player.role_group == RoleGroup.Werewolf else "好人"
30
- await self.send(f"✏️玩家 {player.name} 的阵营是『{result}』")
20
+ if selected := await self._select_player(players):
21
+ result = "狼人" if selected.role_group == RoleGroup.Werewolf else "好人"
22
+ await self.send(f"✏️玩家 {selected.name} 的阵营是『{result}』")
@@ -1,14 +1,19 @@
1
- import asyncio
1
+ from typing import TYPE_CHECKING
2
2
 
3
+ import anyio
4
+ from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
3
5
  from nonebot_plugin_alconna.uniseg import UniMessage
4
6
  from typing_extensions import override
5
7
 
6
- from ..constant import Role, RoleGroup
8
+ from ..constant import STOP_COMMAND, STOP_COMMAND_PROMPT, Role, RoleGroup
7
9
  from ..utils import check_index
8
- from .player import Player, register_role
10
+ from .player import Player
9
11
 
12
+ if TYPE_CHECKING:
13
+ from ..player_set import PlayerSet
10
14
 
11
- @register_role(Role.Werewolf, RoleGroup.Werewolf)
15
+
16
+ @Player.register_role(Role.Werewolf, RoleGroup.Werewolf)
12
17
  class Werewolf(Player):
13
18
  @override
14
19
  async def notify_role(self) -> None:
@@ -20,15 +25,47 @@ class Werewolf(Player):
20
25
  + "\n".join(f" {p.role_name}: {p.name}" for p in partners)
21
26
  )
22
27
 
28
+ async def _handle_interact(
29
+ self,
30
+ players: "PlayerSet",
31
+ stream: MemoryObjectSendStream[str | UniMessage],
32
+ finished: anyio.Event,
33
+ ) -> None:
34
+ self.selected = None
35
+
36
+ while True:
37
+ input_msg = await self.receive()
38
+ text = input_msg.extract_plain_text()
39
+ index = check_index(text, len(players))
40
+ if index is not None:
41
+ self.selected = players[index - 1]
42
+ msg = f"当前选择玩家: {self.selected.name}"
43
+ await self.send(f"🎯{msg}\n发送 “{STOP_COMMAND_PROMPT}” 结束回合")
44
+ await stream.send(f"📝队友 {self.name} {msg}")
45
+ if text == STOP_COMMAND:
46
+ if self.selected is not None:
47
+ await self.send("✅你已结束当前回合")
48
+ await stream.send(f"📝队友 {self.name} 结束当前回合")
49
+ finished.set()
50
+ return
51
+ await self.send("⚠️当前未选择玩家,无法结束回合")
52
+ else:
53
+ await stream.send(UniMessage.text(f"💬队友 {self.name}:\n") + input_msg)
54
+
55
+ async def _handle_broadcast(
56
+ self,
57
+ partners: "PlayerSet",
58
+ stream: MemoryObjectReceiveStream[str | UniMessage],
59
+ finished: anyio.Event,
60
+ ) -> None:
61
+ while not finished.is_set() or stream.statistics().tasks_waiting_receive:
62
+ await partners.broadcast(await stream.receive())
63
+
23
64
  @override
24
65
  async def interact(self) -> None:
25
66
  players = self.game.players.alive()
26
67
  partners = players.select(RoleGroup.Werewolf).exclude(self)
27
68
 
28
- # 避免阻塞
29
- def broadcast(msg: str | UniMessage) -> asyncio.Task[None]:
30
- return asyncio.create_task(partners.broadcast(msg))
31
-
32
69
  msg = UniMessage()
33
70
  if partners:
34
71
  msg = (
@@ -40,28 +77,13 @@ class Werewolf(Player):
40
77
  msg.text("💫请选择今晚的目标:\n")
41
78
  .text(players.show())
42
79
  .text("\n\n🔪发送编号选择玩家")
43
- .text("\n❌发送 “/stop” 结束回合")
80
+ .text(f"\n❌发送 “{STOP_COMMAND_PROMPT}” 结束回合")
44
81
  .text("\n\n⚠️意见未统一将空刀")
45
82
  )
46
83
 
47
- selected = None
48
- finished = False
49
- while selected is None or not finished:
50
- input_msg = await self.receive()
51
- text = input_msg.extract_plain_text()
52
- index = check_index(text, len(players))
53
- if index is not None:
54
- selected = index - 1
55
- msg = f"当前选择玩家: {players[selected].name}"
56
- await self.send(f"🎯{msg}\n发送 “/stop” 结束回合")
57
- broadcast(f"📝队友 {self.name} {msg}")
58
- if text == "/stop":
59
- if selected is not None:
60
- finished = True
61
- await self.send("✅你已结束当前回合")
62
- broadcast(f"📝队友 {self.name} 结束当前回合")
63
- else:
64
- await self.send("⚠️当前未选择玩家,无法结束回合")
65
- broadcast(UniMessage.text(f"💬队友 {self.name}:\n") + input_msg)
84
+ send, recv = anyio.create_memory_object_stream[str | UniMessage]()
85
+ finished = anyio.Event()
66
86
 
67
- self.selected = players[selected]
87
+ async with anyio.create_task_group() as tg:
88
+ tg.start_soon(self._handle_interact, players, send, finished)
89
+ tg.start_soon(self._handle_broadcast, partners, recv, finished)
@@ -1,41 +1,42 @@
1
1
  from nonebot_plugin_alconna.uniseg import UniMessage
2
2
  from typing_extensions import override
3
3
 
4
- from ..constant import Role, RoleGroup
5
- from ..utils import check_index
6
- from .player import Player, register_role
4
+ from ..constant import STOP_COMMAND_PROMPT, Role, RoleGroup
5
+ from ..utils import as_player_set
6
+ from .player import Player
7
7
 
8
8
 
9
- @register_role(Role.Witch, RoleGroup.GoodGuy)
9
+ @Player.register_role(Role.Witch, RoleGroup.GoodGuy)
10
10
  class Witch(Player):
11
- antidote: int = 1
12
- poison: int = 1
11
+ antidote: bool = True
12
+ poison: bool = True
13
13
 
14
14
  async def handle_killed(self) -> bool:
15
- msg = UniMessage()
16
- if (killed := self.game.state.killed) is not None:
17
- msg.text(f"🔪今晚 {killed.name} 被刀了\n\n")
18
- else:
15
+ if (killed := self.game.state.killed) is None:
19
16
  await self.send("ℹ️今晚没有人被刀")
20
17
  return False
21
18
 
19
+ msg = UniMessage.text(f"🔪今晚 {killed.name} 被刀了\n\n")
20
+
22
21
  if not self.antidote:
23
22
  await self.send(msg.text("⚙️你已经用过解药了"))
24
23
  return False
25
24
 
26
- await self.send(msg.text("✏️使用解药请发送 “1”\n❌不使用解药请发送 “/stop”"))
25
+ msg.text(f"✏️使用解药请发送 “1”\n❌不使用解药请发送 “{STOP_COMMAND_PROMPT}”")
26
+ await self.send(msg)
27
+
28
+ if not await self._select_player(
29
+ as_player_set(killed),
30
+ on_stop=f"ℹ️你选择不对 {killed.name} 使用解药",
31
+ on_index_error=f"⚠️输入错误: 请输入 “1” 或 “{STOP_COMMAND_PROMPT}”",
32
+ ):
33
+ return False
27
34
 
28
- while True:
29
- text = await self.receive_text()
30
- if text == "1":
31
- self.antidote = 0
32
- self.selected = killed
33
- self.game.state.antidote.add(killed)
34
- await self.send(f"✅你对 {killed.name} 使用了解药,回合结束")
35
- return True
36
- if text == "/stop":
37
- return False
38
- await self.send("⚠️输入错误: 请输入 “1” 或 “/stop”")
35
+ self.antidote = False
36
+ self.selected = killed
37
+ self.game.state.antidote.add(killed)
38
+ await self.send(f"✅你对 {killed.name} 使用了解药,回合结束")
39
+ return True
39
40
 
40
41
  @override
41
42
  async def interact(self) -> None:
@@ -52,21 +53,14 @@ class Witch(Player):
52
53
  .text("玩家列表:\n")
53
54
  .text(players.show())
54
55
  .text("\n\n🧪发送玩家编号使用毒药")
55
- .text("\n❌发送 “/stop” 结束回合(不使用药水)")
56
+ .text(f"\n❌发送 “{STOP_COMMAND_PROMPT}” 结束回合(不使用药水)")
56
57
  )
57
58
 
58
- while True:
59
- text = await self.receive_text()
60
- index = check_index(text, len(players))
61
- if index is not None:
62
- selected = index - 1
63
- break
64
- if text == "/stop":
65
- await self.send("ℹ️你选择不使用毒药,回合结束")
66
- return
67
- await self.send("⚠️输入错误: 请发送玩家编号或 “/stop”")
68
-
69
- self.poison = 0
70
- self.selected = players[selected]
71
- self.game.state.poison.add(self)
72
- await self.send(f"✅当前回合选择对玩家 {self.selected.name} 使用毒药\n回合结束")
59
+ if selected := await self._select_player(
60
+ players,
61
+ on_stop="ℹ️你选择不使用毒药,回合结束",
62
+ ):
63
+ self.poison = False
64
+ self.selected = selected
65
+ self.game.state.poison.add(self)
66
+ await self.send(f"✅当前回合选择对玩家 {selected.name} 使用毒药\n回合结束")
@@ -2,11 +2,11 @@ from typing_extensions import override
2
2
 
3
3
  from ..constant import Role, RoleGroup
4
4
  from .can_shoot import CanShoot
5
- from .player import register_role
5
+ from .player import Player
6
6
  from .werewolf import Werewolf
7
7
 
8
8
 
9
- @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: