nonebot-plugin-werewolf 1.1.11__py3-none-any.whl → 1.1.13__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 (28) hide show
  1. nonebot_plugin_werewolf/__init__.py +1 -1
  2. nonebot_plugin_werewolf/config.py +51 -37
  3. nonebot_plugin_werewolf/constant.py +0 -17
  4. nonebot_plugin_werewolf/dead_channel.py +79 -0
  5. nonebot_plugin_werewolf/game.py +46 -115
  6. nonebot_plugin_werewolf/matchers/_prepare_game.py +223 -0
  7. nonebot_plugin_werewolf/matchers/depends.py +2 -2
  8. nonebot_plugin_werewolf/matchers/edit_behavior.py +1 -0
  9. nonebot_plugin_werewolf/matchers/edit_preset.py +14 -12
  10. nonebot_plugin_werewolf/matchers/message_in_game.py +1 -1
  11. nonebot_plugin_werewolf/matchers/poke/chronocat_poke.py +3 -3
  12. nonebot_plugin_werewolf/matchers/poke/ob11_poke.py +3 -3
  13. nonebot_plugin_werewolf/matchers/start_game.py +20 -232
  14. nonebot_plugin_werewolf/matchers/superuser_ops.py +5 -8
  15. nonebot_plugin_werewolf/models.py +19 -0
  16. nonebot_plugin_werewolf/player.py +35 -31
  17. nonebot_plugin_werewolf/players/guard.py +2 -2
  18. nonebot_plugin_werewolf/players/prophet.py +2 -2
  19. nonebot_plugin_werewolf/players/shooter.py +2 -2
  20. nonebot_plugin_werewolf/players/werewolf.py +18 -19
  21. nonebot_plugin_werewolf/players/witch.py +4 -4
  22. nonebot_plugin_werewolf/utils.py +18 -56
  23. {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.13.dist-info}/METADATA +71 -41
  24. nonebot_plugin_werewolf-1.1.13.dist-info/RECORD +37 -0
  25. {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.13.dist-info}/WHEEL +1 -1
  26. nonebot_plugin_werewolf-1.1.11.dist-info/RECORD +0 -35
  27. {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.13.dist-info}/licenses/LICENSE +0 -0
  28. {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.13.dist-info}/top_level.txt +0 -0
@@ -1,42 +1,28 @@
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.unimsg 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
- get_target,
25
14
  on_alconna,
26
15
  )
27
16
  from nonebot_plugin_localstore import get_plugin_data_file
28
- from nonebot_plugin_uninfo import QryItrface, Uninfo
17
+ from nonebot_plugin_uninfo import Uninfo
29
18
 
30
- from ..config import GameBehavior, PresetData, config
31
- from ..constant import stop_command_prompt
32
- from ..game import Game, get_running_games, get_starting_games
33
- 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
34
23
  from .depends import rule_not_in_game
35
24
  from .poke import poke_enabled
36
25
 
37
- if TYPE_CHECKING:
38
- from collections.abc import Awaitable, Callable
39
-
40
26
  start_game = on_alconna(
41
27
  Alconna(
42
28
  "werewolf",
@@ -46,6 +32,7 @@ start_game = on_alconna(
46
32
  if config.get_require_at("start")
47
33
  else rule_not_in_game,
48
34
  aliases={"狼人杀"},
35
+ use_cmd_start=config.use_cmd_start,
49
36
  priority=config.matcher_priority.start,
50
37
  )
51
38
  player_data_file = get_plugin_data_file("players.json")
@@ -73,210 +60,11 @@ def load_players(target: Target) -> dict[str, str] | None:
73
60
  return None
74
61
 
75
62
 
76
- def solve_button(msg: UniMessage) -> UniMessage:
77
- def btn(text: str) -> Button:
78
- return Button("input", label=text, text=text)
79
-
80
- return (
81
- msg.keyboard(btn("当前玩家"))
82
- .keyboard(btn("加入游戏"), btn("退出游戏"))
83
- .keyboard(btn("开始游戏"), btn("结束游戏"))
84
- )
85
-
86
-
87
- class PrepareGame:
88
- @dataclass
89
- class _Current:
90
- id: str
91
- name: str
92
- colored: str
93
- is_admin: bool
94
- is_super_user: bool
95
-
96
- class _SendHandler(SendHandler):
97
- def __init__(self) -> None:
98
- self.reply_to = True
99
-
100
- def solve_msg(self, msg: UniMessage) -> UniMessage:
101
- return solve_button(msg)
102
-
103
- async def send_finished(self) -> None:
104
- btn_start = Button("input", label="发起游戏", text="werewolf")
105
- btn_restart = Button("input", label="重开上次游戏", text="werewolf restart")
106
- msg = (
107
- UniMessage.text("ℹ️已结束当前游戏")
108
- .keyboard(btn_start)
109
- .keyboard(btn_restart)
110
- )
111
- async with anyio.create_task_group() as tg:
112
- tg.start_soon(self._edit)
113
- tg.start_soon(self._send, msg)
114
-
115
- def __init__(self, event: Event, players: dict[str, str]) -> None:
116
- self.event = event
117
- self.admin_id = event.get_user_id()
118
- self.group = get_target(event)
119
- self.stream = ObjectStream[tuple[Event, str, str]](16)
120
- self.players = players
121
- self.send_handler = self._SendHandler()
122
- self.logger = nonebot.logger.opt(colors=True)
123
- self.shoud_start_game = False
124
- get_starting_games()[self.group] = self.players
125
-
126
- self._handlers: dict[str, Callable[[], Awaitable[bool | None]]] = {
127
- "开始游戏": self._handle_start,
128
- "结束游戏": self._handle_end,
129
- "加入游戏": self._handle_join,
130
- "退出游戏": self._handle_quit,
131
- "当前玩家": self._handle_list,
132
- }
133
-
134
- async def run(self) -> None:
135
- try:
136
- async with anyio.create_task_group() as tg:
137
- self.task_group = tg
138
- tg.start_soon(self._wait_cancel)
139
- tg.start_soon(self._receive)
140
- tg.start_soon(self._handle)
141
- except Exception as err:
142
- await UniMessage(f"狼人杀准备阶段出现未知错误: {err!r}").finish()
143
-
144
- del get_starting_games()[self.group]
145
- if not self.shoud_start_game:
146
- await start_game.finish()
147
-
148
- async def _wait_cancel(self) -> None:
149
- await self.stream.wait_closed()
150
- self.task_group.cancel_scope.cancel()
151
-
152
- async def _receive(self) -> None:
153
- async def same_group(target: MsgTarget) -> bool:
154
- return self.group.verify(target)
155
-
156
- @waiter.waiter(
157
- waits=[self.event.get_type()],
158
- keep_session=False,
159
- rule=Rule(same_group) & rule_not_in_game,
160
- )
161
- def wait(event: Event, msg: UniMsg, session: Uninfo) -> tuple[Event, str, str]:
162
- text = msg.extract_plain_text().strip()
163
- name = extract_session_member_nick(session) or event.get_user_id()
164
- return (event, text, re.sub(r"[\u2066-\u2069]", "", name))
165
-
166
- async for event, text, name in wait(default=(None, "", "")):
167
- if event is not None:
168
- await self.stream.send((event, text, name))
169
-
170
- async def _handle(self) -> None:
171
- bot = current_bot.get()
172
- superuser = SuperUser()
173
-
174
- while not self.stream.closed:
175
- event, text, name = await self.stream.recv()
176
- user_id = event.get_user_id()
177
- colored = f"<y>{escape_tag(name)}</y>(<c>{escape_tag(user_id)}</c>)"
178
- self.current = self._Current(
179
- id=user_id,
180
- name=name,
181
- colored=colored,
182
- is_admin=user_id == self.admin_id,
183
- is_super_user=await superuser(bot, event),
184
- )
185
- self.send_handler.update(event)
186
-
187
- # 更新用户名
188
- # 当用户通过 chronoca:poke 加入游戏时, 插件无法获取用户名, 原字典值为用户ID
189
- if user_id in self.players and self.players.get(user_id) != name:
190
- self.logger.debug(f"更新玩家显示名称: {self.current.colored}")
191
- self.players[user_id] = name
192
-
193
- handler = self._handlers.get(text)
194
- if handler is not None and await handler():
195
- return
196
-
197
- async def _send(self, msg: str | UniMessage) -> None:
198
- await self.send_handler.send(msg)
199
-
200
- async def _send_finished(self) -> None:
201
- await self.send_handler.send_finished()
202
-
203
- async def _handle_start(self) -> bool:
204
- if not self.current.is_admin:
205
- await self._send("⚠️只有游戏发起者可以开始游戏")
206
- return False
207
-
208
- player_num = len(self.players)
209
- role_preset = PresetData.get().role_preset
210
- if player_num < min(role_preset):
211
- await self._send(
212
- f"⚠️游戏至少需要 {min(role_preset)} 人, 当前已有 {player_num} 人"
213
- )
214
- elif player_num > max(role_preset):
215
- await self._send(
216
- f"⚠️游戏最多需要 {max(role_preset)} 人, 当前已有 {player_num} 人"
217
- )
218
- elif player_num not in role_preset:
219
- await self._send(
220
- f"⚠️不存在总人数为 {player_num} 的预设, 无法开始游戏\n"
221
- f"可用的预设总人数: {', '.join(map(str, role_preset))}"
222
- )
223
- else:
224
- await self._send("✏️游戏即将开始...")
225
- self.logger.info(f"游戏发起者 {self.current.colored} 开始游戏")
226
- self.stream.close()
227
- self.shoud_start_game = True
228
- return True
229
-
230
- return False
231
-
232
- async def _handle_end(self) -> bool:
233
- if self.current.is_admin or self.current.is_super_user:
234
- prefix = "游戏发起者" if self.current.is_admin else "超级用户"
235
- self.logger.info(f"{prefix} {self.current.colored} 结束游戏")
236
- await self._send_finished()
237
- self.stream.close()
238
- return True
239
-
240
- await self._send("⚠️只有游戏发起者或超级用户可以结束游戏")
241
- return False
242
-
243
- async def _handle_join(self) -> None:
244
- if self.current.is_admin:
245
- await self._send("⚠️只有游戏发起者可以开始游戏")
246
- return
247
-
248
- if self.current.id not in self.players:
249
- self.players[self.current.id] = self.current.name
250
- self.logger.info(f"玩家 {self.current.colored} 加入游戏")
251
- await self._send("✅成功加入游戏")
252
- else:
253
- await self._send("ℹ️你已经加入游戏了")
254
-
255
- async def _handle_quit(self) -> None:
256
- if self.current.is_admin:
257
- await self._send("ℹ️游戏发起者无法退出游戏")
258
- return
259
-
260
- if self.current.id in self.players:
261
- del self.players[self.current.id]
262
- self.logger.info(f"玩家 {self.current.colored} 退出游戏")
263
- await self._send("✅成功退出游戏")
264
- else:
265
- await self._send("ℹ️你还没有加入游戏")
266
-
267
- async def _handle_list(self) -> None:
268
- lines = (
269
- f"{idx}. {self.players[user_id]}"
270
- for idx, user_id in enumerate(self.players, 1)
271
- )
272
- await self._send("✨当前玩家:\n" + "\n".join(lines))
273
-
274
-
275
63
  @start_game.handle()
276
64
  async def handle_notice(target: MsgTarget) -> None:
277
65
  if target.private:
278
66
  await UniMessage("⚠️请在群组中创建新游戏").finish(reply_to=True)
279
- if any(target.verify(game.group) for game in get_running_games()):
67
+ if target in get_running_games():
280
68
  await (
281
69
  UniMessage.text("⚠️当前群组内有正在进行的游戏\n")
282
70
  .text("无法开始新游戏")
@@ -291,8 +79,10 @@ async def handle_notice(target: MsgTarget) -> None:
291
79
  " 玩家均加入后,游戏发起者请发送 “开始游戏”\n"
292
80
  )
293
81
  if poke_enabled():
294
- msg.text(f"\n💫可使用戳一戳代替游戏交互中的 “{stop_command_prompt()}” 命令\n")
295
- msg.text("\nℹ️游戏准备阶段限时5分钟,超时将自动结束")
82
+ msg.text(f"\n💫可使用戳一戳代替游戏交互中的 “{stop_command_prompt}” 命令\n")
83
+
84
+ prepare_timeout = GameBehavior.get().timeout.prepare
85
+ msg.text(f"\nℹ️游戏准备阶段限时{prepare_timeout / 60:.1f}分钟,超时将自动结束")
296
86
  await solve_button(msg).send(reply_to=True, fallback=FallbackStrategy.ignore)
297
87
 
298
88
 
@@ -315,22 +105,20 @@ async def handle_restart(target: MsgTarget, state: T_State) -> None:
315
105
  async def handle_start(
316
106
  bot: Bot,
317
107
  event: Event,
318
- target: MsgTarget,
319
- session: Uninfo,
320
- interface: QryItrface,
321
108
  state: T_State,
109
+ session: Uninfo,
110
+ target: MsgTarget,
322
111
  ) -> None:
323
112
  players: dict[str, str] = state.get("players", {})
324
113
  admin_id = event.get_user_id()
325
114
  admin_name = extract_session_member_nick(session) or admin_id
326
115
  players[admin_id] = admin_name
327
116
 
328
- try:
329
- with anyio.fail_after(GameBehavior.get().timeout.prepare):
330
- await PrepareGame(event, players).run()
331
- except TimeoutError:
332
- await UniMessage.text("⚠️游戏准备超时,已自动结束").finish()
117
+ with anyio.move_on_after(GameBehavior.get().timeout.prepare) as scope:
118
+ await PrepareGame(event, players).run()
119
+ if scope.cancelled_caught:
120
+ await UniMessage.text("⚠️游戏准备超时,已自动结束").finish(reply_to=True)
333
121
 
334
122
  dump_players(target, players)
335
- game = await Game.new(bot, target, set(players), interface)
336
- await game.start()
123
+ game = await Game.new(bot, target, set(players))
124
+ game.start()
@@ -12,21 +12,18 @@ terminate = on_alconna(
12
12
  Alconna("中止游戏"),
13
13
  rule=to_me() if config.get_require_at("terminate") else None,
14
14
  permission=SUPERUSER,
15
+ use_cmd_start=config.use_cmd_start,
15
16
  priority=config.matcher_priority.terminate,
16
17
  )
17
18
 
18
19
 
19
20
  async def running_game(target: MsgTarget) -> Game:
20
- for game in get_running_games():
21
- if target.verify(game.group):
22
- return game
23
- return terminate.skip()
24
-
25
-
26
- RunningGame = Annotated[Game, Depends(running_game)]
21
+ if (game := get_running_games().get(target)) is None:
22
+ terminate.skip()
23
+ return game
27
24
 
28
25
 
29
26
  @terminate.handle()
30
- async def _(game: RunningGame) -> None:
27
+ async def _(game: Annotated[Game, Depends(running_game)]) -> None:
31
28
  game.terminate()
32
29
  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,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
  )
@@ -5,23 +5,23 @@ from typing_extensions import override
5
5
  import anyio
6
6
  from nonebot_plugin_alconna.uniseg import UniMessage
7
7
 
8
- from ..constant import STOP_COMMAND, stop_command_prompt
8
+ from ..config import stop_command_prompt
9
+ from ..constant import STOP_COMMAND
9
10
  from ..models import Role, RoleGroup
10
11
  from ..player import InteractProvider, NotifyProvider, Player
11
- from ..utils import ObjectStream, as_player_set, check_index
12
+ from ..utils import as_player_set, check_index
12
13
 
13
14
  if TYPE_CHECKING:
14
15
  from ..player_set import PlayerSet
15
16
 
16
17
 
17
18
  class WerewolfInteractProvider(InteractProvider["Werewolf"]):
18
- stream: ObjectStream[str | UniMessage]
19
-
20
19
  @override
21
20
  async def before(self) -> None:
22
21
  self.game.state.werewolf_start()
23
22
 
24
23
  async def handle_interact(self, players: "PlayerSet") -> None:
24
+ stream = self.stream[0]
25
25
  self.selected = None
26
26
 
27
27
  while True:
@@ -32,30 +32,31 @@ class WerewolfInteractProvider(InteractProvider["Werewolf"]):
32
32
  self.selected = players[index - 1]
33
33
  msg = f"当前选择玩家: {self.selected.name}"
34
34
  await self.p.send(
35
- f"🎯{msg}\n发送 “{stop_command_prompt()}” 结束回合",
35
+ f"🎯{msg}\n发送 “{stop_command_prompt}” 结束回合",
36
36
  stop_btn_label="结束回合",
37
37
  select_players=players,
38
38
  )
39
- await self.stream.send(f"📝队友 {self.p.name} {msg}")
39
+ await stream.send(f"📝队友 {self.p.name} {msg}")
40
40
  if text == STOP_COMMAND:
41
41
  if self.selected is not None:
42
42
  await self.p.send("✅你已结束当前回合")
43
- await self.stream.send(f"📝队友 {self.p.name} 结束当前回合")
44
- self.stream.close()
43
+ await stream.send(f"📝队友 {self.p.name} 结束当前回合")
44
+ stream.close()
45
45
  return
46
46
  await self.p.send(
47
47
  "⚠️当前未选择玩家,无法结束回合",
48
48
  select_players=players,
49
49
  )
50
50
  else:
51
- await self.stream.send(
51
+ await stream.send(
52
52
  UniMessage.text(f"💬队友 {self.p.name}:\n") + input_msg
53
53
  )
54
54
 
55
55
  async def handle_broadcast(self, partners: "PlayerSet") -> None:
56
- while not self.stream.closed:
56
+ stream = self.stream[1]
57
+ while True:
57
58
  try:
58
- message = await self.stream.recv()
59
+ message = await stream.receive()
59
60
  except anyio.EndOfStream:
60
61
  return
61
62
 
@@ -77,19 +78,17 @@ class WerewolfInteractProvider(InteractProvider["Werewolf"]):
77
78
  msg.text("💫请选择今晚的目标:\n")
78
79
  .text(players.show())
79
80
  .text("\n\n🔪发送编号选择玩家")
80
- .text(f"\n❌发送 “{stop_command_prompt()}” 结束回合")
81
+ .text(f"\n❌发送 “{stop_command_prompt}” 结束回合")
81
82
  .text("\n\n⚠️意见未统一将空刀"),
82
83
  select_players=players,
83
84
  )
84
85
 
85
- self.stream = ObjectStream[str | UniMessage](8)
86
+ self.stream = anyio.create_memory_object_stream[str | UniMessage](8)
87
+ send, recv = self.stream
86
88
 
87
- try:
88
- async with anyio.create_task_group() as tg:
89
- tg.start_soon(self.handle_interact, players)
90
- tg.start_soon(self.handle_broadcast, partners)
91
- finally:
92
- del self.stream
89
+ async with send, recv, anyio.create_task_group() as tg:
90
+ tg.start_soon(self.handle_interact, players)
91
+ tg.start_soon(self.handle_broadcast, partners)
93
92
 
94
93
  async def finalize(self) -> None:
95
94
  w = self.game.players.alive().select(RoleGroup.WEREWOLF)