nonebot-plugin-werewolf 1.1.10__py3-none-any.whl → 1.1.12__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 (30) hide show
  1. nonebot_plugin_werewolf/__init__.py +1 -1
  2. nonebot_plugin_werewolf/config.py +57 -35
  3. nonebot_plugin_werewolf/constant.py +0 -19
  4. nonebot_plugin_werewolf/dead_channel.py +79 -0
  5. nonebot_plugin_werewolf/game.py +48 -117
  6. nonebot_plugin_werewolf/matchers/_prepare_game.py +223 -0
  7. nonebot_plugin_werewolf/matchers/depends.py +4 -4
  8. nonebot_plugin_werewolf/matchers/edit_behavior.py +2 -2
  9. nonebot_plugin_werewolf/matchers/edit_preset.py +20 -20
  10. nonebot_plugin_werewolf/matchers/message_in_game.py +5 -1
  11. nonebot_plugin_werewolf/matchers/poke/__init__.py +2 -1
  12. nonebot_plugin_werewolf/matchers/poke/chronocat_poke.py +7 -12
  13. nonebot_plugin_werewolf/matchers/poke/ob11_poke.py +8 -11
  14. nonebot_plugin_werewolf/matchers/start_game.py +23 -233
  15. nonebot_plugin_werewolf/matchers/superuser_ops.py +16 -12
  16. nonebot_plugin_werewolf/models.py +19 -0
  17. nonebot_plugin_werewolf/player.py +35 -31
  18. nonebot_plugin_werewolf/player_set.py +10 -0
  19. nonebot_plugin_werewolf/players/guard.py +2 -2
  20. nonebot_plugin_werewolf/players/prophet.py +2 -2
  21. nonebot_plugin_werewolf/players/shooter.py +2 -2
  22. nonebot_plugin_werewolf/players/werewolf.py +18 -19
  23. nonebot_plugin_werewolf/players/witch.py +4 -4
  24. nonebot_plugin_werewolf/utils.py +18 -56
  25. {nonebot_plugin_werewolf-1.1.10.dist-info → nonebot_plugin_werewolf-1.1.12.dist-info}/METADATA +122 -58
  26. nonebot_plugin_werewolf-1.1.12.dist-info/RECORD +37 -0
  27. {nonebot_plugin_werewolf-1.1.10.dist-info → nonebot_plugin_werewolf-1.1.12.dist-info}/WHEEL +1 -1
  28. nonebot_plugin_werewolf-1.1.10.dist-info/RECORD +0 -35
  29. {nonebot_plugin_werewolf-1.1.10.dist-info → nonebot_plugin_werewolf-1.1.12.dist-info}/licenses/LICENSE +0 -0
  30. {nonebot_plugin_werewolf-1.1.10.dist-info → nonebot_plugin_werewolf-1.1.12.dist-info}/top_level.txt +0 -0
@@ -1,49 +1,38 @@
1
1
  import json
2
- import re
3
- from dataclasses import dataclass
4
- from typing import TYPE_CHECKING
5
2
 
6
3
  import anyio
7
- import nonebot
8
- import nonebot_plugin_waiter as waiter
9
4
  from nonebot.adapters import Bot, Event
10
- from nonebot.internal.matcher import current_bot
11
- from nonebot.permission import SuperUser
12
- from nonebot.rule import Rule, to_me
5
+ from nonebot.rule import to_me
13
6
  from nonebot.typing import T_State
14
- from nonebot.utils import escape_tag
15
7
  from nonebot_plugin_alconna import (
16
8
  Alconna,
17
- Button,
18
9
  FallbackStrategy,
19
10
  MsgTarget,
20
11
  Option,
21
12
  Target,
22
13
  UniMessage,
23
- UniMsg,
24
14
  on_alconna,
25
15
  )
26
16
  from nonebot_plugin_localstore import get_plugin_data_file
27
- from nonebot_plugin_uninfo import QryItrface, Uninfo
17
+ from nonebot_plugin_uninfo import Uninfo
28
18
 
29
- from ..config import GameBehavior, PresetData
30
- from ..constant import stop_command_prompt
31
- from ..game import Game, get_running_games, get_starting_games
32
- from ..utils import ObjectStream, SendHandler, extract_session_member_nick
19
+ from ..config import GameBehavior, config, stop_command_prompt
20
+ from ..game import Game, get_running_games
21
+ from ..utils import extract_session_member_nick
22
+ from ._prepare_game import PrepareGame, solve_button
33
23
  from .depends import rule_not_in_game
34
24
  from .poke import poke_enabled
35
25
 
36
- if TYPE_CHECKING:
37
- from collections.abc import Awaitable, Callable
38
-
39
26
  start_game = on_alconna(
40
27
  Alconna(
41
28
  "werewolf",
42
29
  Option("restart|-r|--restart|重开", dest="restart"),
43
30
  ),
44
- rule=to_me() & rule_not_in_game,
31
+ rule=to_me() & rule_not_in_game
32
+ if config.get_require_at("start")
33
+ else rule_not_in_game,
45
34
  aliases={"狼人杀"},
46
- use_cmd_start=True,
35
+ priority=config.matcher_priority.start,
47
36
  )
48
37
  player_data_file = get_plugin_data_file("players.json")
49
38
  if not player_data_file.exists():
@@ -70,210 +59,11 @@ def load_players(target: Target) -> dict[str, str] | None:
70
59
  return None
71
60
 
72
61
 
73
- def solve_button(msg: UniMessage) -> UniMessage:
74
- def btn(text: str) -> Button:
75
- return Button("input", label=text, text=text)
76
-
77
- return (
78
- msg.keyboard(btn("当前玩家"))
79
- .keyboard(btn("加入游戏"), btn("退出游戏"))
80
- .keyboard(btn("开始游戏"), btn("结束游戏"))
81
- )
82
-
83
-
84
- class PrepareGame:
85
- @dataclass
86
- class _Current:
87
- id: str
88
- name: str
89
- colored: str
90
- is_admin: bool
91
- is_super_user: bool
92
-
93
- class _SendHandler(SendHandler):
94
- def __init__(self) -> None:
95
- self.reply_to = True
96
-
97
- def solve_msg(self, msg: UniMessage) -> UniMessage:
98
- return solve_button(msg)
99
-
100
- async def send_finished(self) -> None:
101
- btn_start = Button("input", label="发起游戏", text="werewolf")
102
- btn_restart = Button("input", label="重开上次游戏", text="werewolf restart")
103
- msg = (
104
- UniMessage.text("ℹ️已结束当前游戏")
105
- .keyboard(btn_start)
106
- .keyboard(btn_restart)
107
- )
108
- async with anyio.create_task_group() as tg:
109
- tg.start_soon(self._edit)
110
- tg.start_soon(self._send, msg)
111
-
112
- def __init__(self, event: Event, players: dict[str, str]) -> None:
113
- self.event = event
114
- self.admin_id = event.get_user_id()
115
- self.group = UniMessage.get_target(event)
116
- self.stream = ObjectStream[tuple[Event, str, str]](16)
117
- self.players = players
118
- self.send_handler = self._SendHandler()
119
- self.logger = nonebot.logger.opt(colors=True)
120
- self.shoud_start_game = False
121
- get_starting_games()[self.group] = self.players
122
-
123
- self._handlers: dict[str, Callable[[], Awaitable[bool | None]]] = {
124
- "开始游戏": self._handle_start,
125
- "结束游戏": self._handle_end,
126
- "加入游戏": self._handle_join,
127
- "退出游戏": self._handle_quit,
128
- "当前玩家": self._handle_list,
129
- }
130
-
131
- async def run(self) -> None:
132
- try:
133
- async with anyio.create_task_group() as tg:
134
- self.task_group = tg
135
- tg.start_soon(self._wait_cancel)
136
- tg.start_soon(self._receive)
137
- tg.start_soon(self._handle)
138
- except Exception as err:
139
- await UniMessage(f"狼人杀准备阶段出现未知错误: {err!r}").finish()
140
-
141
- del get_starting_games()[self.group]
142
- if not self.shoud_start_game:
143
- await start_game.finish()
144
-
145
- async def _wait_cancel(self) -> None:
146
- await self.stream.wait_closed()
147
- self.task_group.cancel_scope.cancel()
148
-
149
- async def _receive(self) -> None:
150
- async def same_group(target: MsgTarget) -> bool:
151
- return self.group.verify(target)
152
-
153
- @waiter.waiter(
154
- waits=[self.event.get_type()],
155
- keep_session=False,
156
- rule=Rule(same_group) & rule_not_in_game,
157
- )
158
- def wait(event: Event, msg: UniMsg, session: Uninfo) -> tuple[Event, str, str]:
159
- text = msg.extract_plain_text().strip()
160
- name = extract_session_member_nick(session) or event.get_user_id()
161
- return (event, text, re.sub(r"[\u2066-\u2069]", "", name))
162
-
163
- async for event, text, name in wait(default=(None, "", "")):
164
- if event is not None:
165
- await self.stream.send((event, text, name))
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
-
194
- async def _send(self, msg: str | UniMessage) -> None:
195
- await self.send_handler.send(msg)
196
-
197
- async def _send_finished(self) -> None:
198
- await self.send_handler.send_finished()
199
-
200
- async def _handle_start(self) -> bool:
201
- if not self.current.is_admin:
202
- await self._send("⚠️只有游戏发起者可以开始游戏")
203
- return False
204
-
205
- player_num = len(self.players)
206
- role_preset = PresetData.get().role_preset
207
- if player_num < min(role_preset):
208
- await self._send(
209
- f"⚠️游戏至少需要 {min(role_preset)} 人, 当前已有 {player_num} 人"
210
- )
211
- elif player_num > max(role_preset):
212
- await self._send(
213
- f"⚠️游戏最多需要 {max(role_preset)} 人, 当前已有 {player_num} 人"
214
- )
215
- elif player_num not in role_preset:
216
- await self._send(
217
- f"⚠️不存在总人数为 {player_num} 的预设, 无法开始游戏\n"
218
- f"可用的预设总人数: {', '.join(map(str, role_preset))}"
219
- )
220
- else:
221
- await self._send("✏️游戏即将开始...")
222
- self.logger.info(f"游戏发起者 {self.current.colored} 开始游戏")
223
- self.stream.close()
224
- self.shoud_start_game = True
225
- return True
226
-
227
- return False
228
-
229
- async def _handle_end(self) -> bool:
230
- if self.current.is_admin or self.current.is_super_user:
231
- prefix = "游戏发起者" if self.current.is_admin else "超级用户"
232
- self.logger.info(f"{prefix} {self.current.colored} 结束游戏")
233
- await self._send_finished()
234
- self.stream.close()
235
- return True
236
-
237
- await self._send("⚠️只有游戏发起者或超级用户可以结束游戏")
238
- return False
239
-
240
- async def _handle_join(self) -> None:
241
- if self.current.is_admin:
242
- await self._send("⚠️只有游戏发起者可以开始游戏")
243
- return
244
-
245
- if self.current.id not in self.players:
246
- self.players[self.current.id] = self.current.name
247
- self.logger.info(f"玩家 {self.current.colored} 加入游戏")
248
- await self._send("✅成功加入游戏")
249
- else:
250
- await self._send("ℹ️你已经加入游戏了")
251
-
252
- async def _handle_quit(self) -> None:
253
- if self.current.is_admin:
254
- await self._send("ℹ️游戏发起者无法退出游戏")
255
- return
256
-
257
- if self.current.id in self.players:
258
- del self.players[self.current.id]
259
- self.logger.info(f"玩家 {self.current.colored} 退出游戏")
260
- await self._send("✅成功退出游戏")
261
- else:
262
- await self._send("ℹ️你还没有加入游戏")
263
-
264
- async def _handle_list(self) -> None:
265
- lines = (
266
- f"{idx}. {self.players[user_id]}"
267
- for idx, user_id in enumerate(self.players, 1)
268
- )
269
- await self._send("✨当前玩家:\n" + "\n".join(lines))
270
-
271
-
272
62
  @start_game.handle()
273
63
  async def handle_notice(target: MsgTarget) -> None:
274
64
  if target.private:
275
65
  await UniMessage("⚠️请在群组中创建新游戏").finish(reply_to=True)
276
- if any(target.verify(game.group) for game in get_running_games()):
66
+ if target in get_running_games():
277
67
  await (
278
68
  UniMessage.text("⚠️当前群组内有正在进行的游戏\n")
279
69
  .text("无法开始新游戏")
@@ -288,8 +78,10 @@ async def handle_notice(target: MsgTarget) -> None:
288
78
  " 玩家均加入后,游戏发起者请发送 “开始游戏”\n"
289
79
  )
290
80
  if poke_enabled():
291
- msg.text(f"\n💫可使用戳一戳代替游戏交互中的 “{stop_command_prompt()}” 命令\n")
292
- msg.text("\nℹ️游戏准备阶段限时5分钟,超时将自动结束")
81
+ msg.text(f"\n💫可使用戳一戳代替游戏交互中的 “{stop_command_prompt}” 命令\n")
82
+
83
+ prepare_timeout = GameBehavior.get().timeout.prepare
84
+ msg.text(f"\nℹ️游戏准备阶段限时{prepare_timeout / 60:.1f}分钟,超时将自动结束")
293
85
  await solve_button(msg).send(reply_to=True, fallback=FallbackStrategy.ignore)
294
86
 
295
87
 
@@ -312,22 +104,20 @@ async def handle_restart(target: MsgTarget, state: T_State) -> None:
312
104
  async def handle_start(
313
105
  bot: Bot,
314
106
  event: Event,
315
- target: MsgTarget,
316
- session: Uninfo,
317
- interface: QryItrface,
318
107
  state: T_State,
108
+ session: Uninfo,
109
+ target: MsgTarget,
319
110
  ) -> None:
320
111
  players: dict[str, str] = state.get("players", {})
321
112
  admin_id = event.get_user_id()
322
113
  admin_name = extract_session_member_nick(session) or admin_id
323
114
  players[admin_id] = admin_name
324
115
 
325
- try:
326
- with anyio.fail_after(GameBehavior.get().timeout.prepare):
327
- await PrepareGame(event, players).run()
328
- except TimeoutError:
329
- await UniMessage.text("⚠️游戏准备超时,已自动结束").finish()
116
+ with anyio.move_on_after(GameBehavior.get().timeout.prepare) as scope:
117
+ await PrepareGame(event, players).run()
118
+ if scope.cancelled_caught:
119
+ await UniMessage.text("⚠️游戏准备超时,已自动结束").finish(reply_to=True)
330
120
 
331
121
  dump_players(target, players)
332
- game = await Game.new(bot, target, set(players), interface)
333
- await game.start()
122
+ game = await Game.new(bot, target, set(players))
123
+ game.start()
@@ -1,24 +1,28 @@
1
+ from typing import Annotated
2
+
3
+ from nonebot.params import Depends
1
4
  from nonebot.permission import SUPERUSER
2
5
  from nonebot.rule import to_me
3
6
  from nonebot_plugin_alconna import Alconna, MsgTarget, UniMessage, on_alconna
4
7
 
5
- from ..game import get_running_games
6
-
7
-
8
- def rule_game_running(target: MsgTarget) -> bool:
9
- return any(target.verify(g.group) for g in get_running_games())
8
+ from ..config import config
9
+ from ..game import Game, get_running_games
10
10
 
11
-
12
- force_stop = on_alconna(
11
+ terminate = on_alconna(
13
12
  Alconna("中止游戏"),
14
- rule=to_me() & rule_game_running,
13
+ rule=to_me() if config.get_require_at("terminate") else None,
15
14
  permission=SUPERUSER,
16
- use_cmd_start=True,
15
+ priority=config.matcher_priority.terminate,
17
16
  )
18
17
 
19
18
 
20
- @force_stop.handle()
21
- async def _(target: MsgTarget) -> None:
22
- game = next(g for g in get_running_games() if target.verify(g.group))
19
+ async def running_game(target: MsgTarget) -> Game:
20
+ if (game := get_running_games().get(target)) is None:
21
+ terminate.skip()
22
+ return game
23
+
24
+
25
+ @terminate.handle()
26
+ async def _(game: Annotated[Game, Depends(running_game)]) -> None:
23
27
  game.terminate()
24
28
  await UniMessage.text("已中止当前群组的游戏进程").finish(reply_to=True)
@@ -1,4 +1,5 @@
1
1
  import dataclasses
2
+ import functools
2
3
  from enum import Enum, auto
3
4
  from typing import TYPE_CHECKING
4
5
 
@@ -26,12 +27,30 @@ class Role(int, Enum):
26
27
  # 平民
27
28
  CIVILIAN = 0
28
29
 
30
+ @functools.cached_property
31
+ def emoji(self) -> str:
32
+ from .constant import ROLE_EMOJI
33
+
34
+ return ROLE_EMOJI[self]
35
+
36
+ @functools.cached_property
37
+ def display(self) -> str:
38
+ from .constant import ROLE_NAME_CONV
39
+
40
+ return ROLE_NAME_CONV[self]
41
+
29
42
 
30
43
  class RoleGroup(Enum):
31
44
  WEREWOLF = auto()
32
45
  GOODGUY = auto()
33
46
  OTHERS = auto()
34
47
 
48
+ @functools.cached_property
49
+ def display(self) -> str:
50
+ from .constant import ROLE_NAME_CONV
51
+
52
+ return ROLE_NAME_CONV[self]
53
+
35
54
 
36
55
  class KillReason(Enum):
37
56
  WEREWOLF = auto()
@@ -9,9 +9,10 @@ import nonebot
9
9
  from nonebot.adapters import Bot
10
10
  from nonebot.utils import escape_tag
11
11
  from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage
12
- from nonebot_plugin_uninfo import Interface, SceneType
12
+ from nonebot_plugin_uninfo import Interface, SceneType, get_interface
13
13
 
14
- from .constant import ROLE_EMOJI, ROLE_NAME_CONV, STOP_COMMAND, stop_command_prompt
14
+ from .config import stop_command_prompt
15
+ from .constant import STOP_COMMAND
15
16
  from .models import KillInfo, KillReason, Role, RoleGroup
16
17
  from .utils import (
17
18
  InputStore,
@@ -92,7 +93,7 @@ class NotifyProvider(ActionProvider[_P], Generic[_P]):
92
93
  return message
93
94
 
94
95
  async def notify(self) -> None:
95
- msg = UniMessage.text(f"⚙️你的身份: {ROLE_EMOJI[self.role]}{self.role_name}\n")
96
+ msg = UniMessage.text(f"⚙️你的身份: {self.role.emoji}{self.role_name}\n")
96
97
  await self.p.send(self.message(msg))
97
98
 
98
99
 
@@ -135,14 +136,18 @@ class Player:
135
136
  @override
136
137
  def __init_subclass__(cls) -> None:
137
138
  super().__init_subclass__()
138
- if hasattr(cls, "role") and hasattr(cls, "role_group"):
139
- cls._player_class[cls.role] = cls
140
- if not hasattr(cls, "interact_provider"):
141
- cls.interact_provider = None
142
- if not hasattr(cls, "kill_provider"):
143
- cls.kill_provider = KillProvider
144
- if not hasattr(cls, "notify_provider"):
145
- cls.notify_provider = NotifyProvider
139
+ if not (hasattr(cls, "role") and hasattr(cls, "role_group")):
140
+ return
141
+
142
+ assert cls.role not in cls._player_class # noqa: S101
143
+ cls._player_class[cls.role] = cls
144
+ for k, v in {
145
+ "interact_provider": None,
146
+ "kill_provider": KillProvider,
147
+ "notify_provider": NotifyProvider,
148
+ }.items():
149
+ if not hasattr(cls, k):
150
+ setattr(cls, k, v)
146
151
 
147
152
  @final
148
153
  @classmethod
@@ -152,7 +157,6 @@ class Player:
152
157
  bot: Bot,
153
158
  game: "Game",
154
159
  user_id: str,
155
- interface: Interface,
156
160
  ) -> "Player":
157
161
  if role not in cls._player_class:
158
162
  raise ValueError(f"Unexpected role: {role!r}")
@@ -166,7 +170,9 @@ class Player:
166
170
  extra=game.group.extra,
167
171
  )
168
172
  self = cls._player_class[role](bot, game, user)
169
- await self._fetch_member(interface)
173
+
174
+ if interface := get_interface(bot):
175
+ await self._fetch_member(interface)
170
176
  return self
171
177
 
172
178
  def __repr__(self) -> str:
@@ -187,7 +193,7 @@ class Player:
187
193
  @final
188
194
  @functools.cached_property
189
195
  def role_name(self) -> str:
190
- return ROLE_NAME_CONV[self.role]
196
+ return self.role.display
191
197
 
192
198
  @final
193
199
  async def _fetch_member(self, interface: Interface) -> None:
@@ -278,14 +284,12 @@ class Player:
278
284
  provider = self.interact_provider(self)
279
285
 
280
286
  await provider.before()
281
-
282
287
  timeout = self.interact_timeout
283
288
  await self.send(f"✏️{self.role_name}交互开始,限时 {timeout / 60:.2f} 分钟")
284
289
 
285
- try:
286
- with anyio.fail_after(timeout):
287
- await provider.interact()
288
- except TimeoutError:
290
+ with anyio.move_on_after(timeout) as scope:
291
+ await provider.interact()
292
+ if scope.cancelled_caught:
289
293
  logger.debug(f"{self.role_name}交互超时 (<y>{timeout}</y>s)")
290
294
  await self.send(f"⚠️{self.role_name}交互超时")
291
295
 
@@ -304,25 +308,25 @@ class Player:
304
308
  self.killed.set()
305
309
 
306
310
  async def vote(self, players: "PlayerSet") -> "Player | None":
311
+ vote_timeout = self.game.behavior.timeout.vote
307
312
  await self.send(
308
313
  f"💫请选择需要投票的玩家:\n"
309
314
  f"{players.show()}\n\n"
310
315
  "🗳️发送编号选择玩家\n"
311
- f"❌发送 “{stop_command_prompt()}” 弃票\n\n"
312
- "限时1分钟,超时将视为弃票",
316
+ f"❌发送 “{stop_command_prompt}” 弃票\n\n"
317
+ f"限时{vote_timeout / 60:.1f}分钟,超时将视为弃票",
313
318
  stop_btn_label="弃票",
314
319
  select_players=players,
315
320
  )
316
321
 
317
- try:
318
- with anyio.fail_after(self.game.behavior.timeout.vote):
319
- selected = await self.select_player(
320
- players,
321
- on_stop="⚠️你选择了弃票",
322
- on_index_error="⚠️输入错误: 请发送编号选择玩家",
323
- )
324
- except TimeoutError:
325
- selected = None
322
+ selected = None
323
+ with anyio.move_on_after(vote_timeout) as scope:
324
+ selected = await self.select_player(
325
+ players,
326
+ on_stop="⚠️你选择了弃票",
327
+ on_index_error="⚠️输入错误: 请发送编号选择玩家",
328
+ )
329
+ if scope.cancelled_caught:
326
330
  await self.send("⚠️投票超时,将视为弃票")
327
331
 
328
332
  if selected is not None:
@@ -343,7 +347,7 @@ class Player:
343
347
  ) -> "Player | None":
344
348
  on_stop = on_stop if on_stop is not None else "ℹ️你选择了取消,回合结束"
345
349
  on_index_error = (
346
- on_index_error or f"⚠️输入错误: 请发送玩家编号或 “{stop_command_prompt()}”"
350
+ on_index_error or f"⚠️输入错误: 请发送玩家编号或 “{stop_command_prompt}”"
347
351
  )
348
352
  selected = None
349
353
 
@@ -1,6 +1,7 @@
1
1
  import functools
2
2
  import random
3
3
  from collections.abc import Iterable
4
+ from collections.abc import Set as AbstractSet
4
5
  from typing_extensions import Self
5
6
 
6
7
  import anyio
@@ -100,3 +101,12 @@ class PlayerSet(set[Player]):
100
101
 
101
102
  def __getitem__(self, index: int, /) -> Player:
102
103
  return self.sorted[index]
104
+
105
+ def __and__(self, other: AbstractSet[Player], /) -> Self: # type: ignore[override]
106
+ return self.from_(super().__and__(other))
107
+
108
+ def __or__(self, other: AbstractSet[Player], /) -> Self: # type: ignore[override]
109
+ return self.from_(super().__or__(other))
110
+
111
+ def __sub__(self, other: AbstractSet[Player], /) -> Self: # type: ignore[override]
112
+ return self.from_(super().__sub__(other))
@@ -1,6 +1,6 @@
1
1
  from typing_extensions import override
2
2
 
3
- from ..constant import stop_command_prompt
3
+ from ..config import stop_command_prompt
4
4
  from ..models import GameState, Role, RoleGroup
5
5
  from ..player import InteractProvider, Player
6
6
 
@@ -13,7 +13,7 @@ class GuardInteractProvider(InteractProvider["Guard"]):
13
13
  "💫请选择需要保护的玩家:\n"
14
14
  f"{players.show()}\n\n"
15
15
  "🛡️发送编号选择玩家\n"
16
- f"❌发送 “{stop_command_prompt()}” 结束回合",
16
+ f"❌发送 “{stop_command_prompt}” 结束回合",
17
17
  stop_btn_label="结束回合",
18
18
  select_players=players,
19
19
  )
@@ -1,6 +1,6 @@
1
1
  from typing_extensions import override
2
2
 
3
- from ..constant import stop_command_prompt
3
+ from ..config import stop_command_prompt
4
4
  from ..models import Role, RoleGroup
5
5
  from ..player import InteractProvider, Player
6
6
 
@@ -13,7 +13,7 @@ class ProphetInteractProvider(InteractProvider["Prophet"]):
13
13
  "💫请选择需要查验身份的玩家:\n"
14
14
  f"{players.show()}\n\n"
15
15
  "🔮发送编号选择玩家\n"
16
- f"❌发送 “{stop_command_prompt()}” 结束回合(不查验身份)",
16
+ f"❌发送 “{stop_command_prompt}” 结束回合(不查验身份)",
17
17
  stop_btn_label="结束回合",
18
18
  select_players=players,
19
19
  )
@@ -2,7 +2,7 @@ from typing_extensions import override
2
2
 
3
3
  from nonebot_plugin_alconna.uniseg import UniMessage
4
4
 
5
- from ..constant import stop_command_prompt
5
+ from ..config import stop_command_prompt
6
6
  from ..models import KillReason
7
7
  from ..player import KillProvider, Player
8
8
 
@@ -39,7 +39,7 @@ class ShooterKillProvider(KillProvider["Player"]):
39
39
  "💫请选择需要射杀的玩家:\n"
40
40
  f"{players.show()}\n\n"
41
41
  "🔫发送编号选择玩家\n"
42
- f"❌发送 “{stop_command_prompt()}” 取消技能",
42
+ f"❌发送 “{stop_command_prompt}” 取消技能",
43
43
  stop_btn_label="取消技能",
44
44
  select_players=players,
45
45
  )