nonebot-plugin-werewolf 1.1.11__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 (26) hide show
  1. nonebot_plugin_werewolf/__init__.py +1 -1
  2. nonebot_plugin_werewolf/config.py +36 -35
  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_preset.py +13 -12
  9. nonebot_plugin_werewolf/matchers/poke/chronocat_poke.py +3 -3
  10. nonebot_plugin_werewolf/matchers/poke/ob11_poke.py +3 -3
  11. nonebot_plugin_werewolf/matchers/start_game.py +19 -232
  12. nonebot_plugin_werewolf/matchers/superuser_ops.py +4 -8
  13. nonebot_plugin_werewolf/models.py +19 -0
  14. nonebot_plugin_werewolf/player.py +35 -31
  15. nonebot_plugin_werewolf/players/guard.py +2 -2
  16. nonebot_plugin_werewolf/players/prophet.py +2 -2
  17. nonebot_plugin_werewolf/players/shooter.py +2 -2
  18. nonebot_plugin_werewolf/players/werewolf.py +18 -19
  19. nonebot_plugin_werewolf/players/witch.py +4 -4
  20. nonebot_plugin_werewolf/utils.py +18 -56
  21. {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.12.dist-info}/METADATA +55 -34
  22. nonebot_plugin_werewolf-1.1.12.dist-info/RECORD +37 -0
  23. {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.12.dist-info}/WHEEL +1 -1
  24. nonebot_plugin_werewolf-1.1.11.dist-info/RECORD +0 -35
  25. {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.12.dist-info}/licenses/LICENSE +0 -0
  26. {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.12.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,223 @@
1
+ import re
2
+ from collections.abc import AsyncIterator, Awaitable, Callable
3
+ from dataclasses import dataclass
4
+
5
+ import anyio
6
+ import nonebot
7
+ import nonebot_plugin_waiter.unimsg as waiter
8
+ from nonebot.adapters import Event
9
+ from nonebot.internal.matcher import current_bot
10
+ from nonebot.matcher import Matcher
11
+ from nonebot.permission import SuperUser
12
+ from nonebot.rule import Rule
13
+ from nonebot.utils import escape_tag
14
+ from nonebot_plugin_alconna import (
15
+ Button,
16
+ MsgTarget,
17
+ Target,
18
+ UniMessage,
19
+ UniMsg,
20
+ get_target,
21
+ )
22
+ from nonebot_plugin_uninfo import Uninfo
23
+
24
+ from ..config import PresetData
25
+ from ..utils import SendHandler as BaseSendHandler
26
+ from ..utils import btn, extract_session_member_nick
27
+ from .depends import rule_not_in_game
28
+
29
+ preparing_games: dict[Target, "PrepareGame"] = {}
30
+
31
+
32
+ def solve_button(msg: UniMessage) -> UniMessage:
33
+ def _btn(text: str) -> Button:
34
+ return btn(text, text)
35
+
36
+ return (
37
+ msg.keyboard(_btn("当前玩家"))
38
+ .keyboard(_btn("加入游戏"), _btn("退出游戏"))
39
+ .keyboard(_btn("开始游戏"), _btn("结束游戏"))
40
+ )
41
+
42
+
43
+ class SendHandler(BaseSendHandler):
44
+ def __init__(self) -> None:
45
+ self.reply_to = True
46
+
47
+ def solve_msg(self, msg: UniMessage) -> UniMessage:
48
+ return solve_button(msg)
49
+
50
+ async def send_finished(self) -> None:
51
+ msg = (
52
+ UniMessage.text("ℹ️已结束当前游戏")
53
+ .keyboard(btn("发起游戏", "werewolf"))
54
+ .keyboard(btn("重开上次游戏", "werewolf restart"))
55
+ )
56
+ async with anyio.create_task_group() as tg:
57
+ tg.start_soon(self._edit)
58
+ tg.start_soon(self._send, msg)
59
+
60
+
61
+ def create_waiter(
62
+ event: Event, group: Target
63
+ ) -> AsyncIterator[tuple[Event | None, str, str]]:
64
+ async def same_group(target: MsgTarget) -> bool:
65
+ return group.verify(target)
66
+
67
+ @waiter.waiter(
68
+ waits=[event.get_type()],
69
+ keep_session=False,
70
+ rule=Rule(same_group, rule_not_in_game),
71
+ )
72
+ def wait(event: Event, msg: UniMsg, session: Uninfo) -> tuple[Event, str, str]:
73
+ text = msg.extract_plain_text().strip()
74
+ name = (
75
+ re.sub(r"[\u2066-\u2069]", "", (extract_session_member_nick(session) or ""))
76
+ or event.get_user_id()
77
+ )
78
+ return (event, text, name)
79
+
80
+ return wait(default=(None, "", ""))
81
+
82
+
83
+ @dataclass
84
+ class Current:
85
+ id: str
86
+ name: str
87
+ colored: str
88
+ is_admin: bool
89
+ is_super_user: Callable[[], Awaitable[bool]]
90
+
91
+
92
+ class PrepareGame:
93
+ def __init__(self, event: Event, players: dict[str, str]) -> None:
94
+ self.bot = current_bot.get()
95
+ self.event = event
96
+ self.admin_id = event.get_user_id()
97
+ self.group = get_target(event)
98
+ self.stream = anyio.create_memory_object_stream[tuple[Event, str, str]](16)
99
+ self.players = players
100
+ self.send_handler = SendHandler()
101
+ self.logger = nonebot.logger.opt(colors=True)
102
+ self.shoud_start_game = False
103
+ preparing_games[self.group] = self
104
+
105
+ self._handlers: dict[str, Callable[[], Awaitable[None]]] = {
106
+ "开始游戏": self._handle_start,
107
+ "结束游戏": self._handle_end,
108
+ "加入游戏": self._handle_join,
109
+ "退出游戏": self._handle_quit,
110
+ "当前玩家": self._handle_list,
111
+ }
112
+
113
+ async def run(self) -> None:
114
+ try:
115
+ async with anyio.create_task_group() as tg:
116
+ self.task_group = tg
117
+ async for event, text, name in create_waiter(self.event, self.group):
118
+ if event is not None:
119
+ tg.start_soon(self._handle, event, text, name)
120
+ except Exception as err:
121
+ await UniMessage(f"狼人杀准备阶段出现未知错误: {err!r}").finish()
122
+
123
+ del preparing_games[self.group]
124
+ if not self.shoud_start_game:
125
+ await Matcher.finish()
126
+
127
+ async def _handle(self, event: Event, text: str, name: str) -> None:
128
+ user_id = event.get_user_id()
129
+
130
+ # 更新用户名
131
+ # 当用户通过 chronoca:poke 加入游戏时, 插件无法获取用户名, 原字典值为用户ID
132
+ if user_id in self.players and self.players.get(user_id) != name:
133
+ self.logger.debug(f"更新玩家显示名称: {self.current.colored}")
134
+ self.players[user_id] = name
135
+
136
+ if (handler := self._handlers.get(text)) is None:
137
+ return
138
+
139
+ self.current = Current(
140
+ id=user_id,
141
+ name=name,
142
+ colored=f"<y>{escape_tag(name)}</y>(<c>{escape_tag(user_id)}</c>)",
143
+ is_admin=user_id == self.admin_id,
144
+ is_super_user=lambda: SuperUser()(self.bot, event),
145
+ )
146
+ self.send_handler.update(event, self.bot)
147
+ await handler()
148
+
149
+ async def _send(self, msg: str | UniMessage) -> None:
150
+ await self.send_handler.send(msg)
151
+
152
+ async def _send_finished(self) -> None:
153
+ await self.send_handler.send_finished()
154
+
155
+ def _finish(self) -> None:
156
+ self.task_group.cancel_scope.cancel()
157
+
158
+ async def _handle_start(self) -> None:
159
+ if not self.current.is_admin:
160
+ await self._send("⚠️只有游戏发起者可以开始游戏")
161
+ return
162
+
163
+ player_num = len(self.players)
164
+ role_preset = PresetData.get().role_preset
165
+ if player_num < min(role_preset):
166
+ await self._send(
167
+ f"⚠️游戏至少需要 {min(role_preset)} 人, 当前已有 {player_num} 人"
168
+ )
169
+ elif player_num > max(role_preset):
170
+ await self._send(
171
+ f"⚠️游戏最多需要 {max(role_preset)} 人, 当前已有 {player_num} 人"
172
+ )
173
+ elif player_num not in role_preset:
174
+ await self._send(
175
+ f"⚠️不存在总人数为 {player_num} 的预设, 无法开始游戏\n"
176
+ f"可用的预设总人数: {', '.join(map(str, role_preset))}"
177
+ )
178
+ else:
179
+ self.logger.info(f"游戏发起者 {self.current.colored} 开始游戏")
180
+ await self._send("✏️游戏即将开始...")
181
+ self._finish()
182
+ self.shoud_start_game = True
183
+
184
+ async def _handle_end(self) -> None:
185
+ if not (self.current.is_admin or await self.current.is_super_user()):
186
+ await self._send("⚠️只有游戏发起者或超级用户可以结束游戏")
187
+ return
188
+
189
+ prefix = "游戏发起者" if self.current.is_admin else "超级用户"
190
+ self.logger.info(f"{prefix} {self.current.colored} 结束游戏")
191
+ await self._send_finished()
192
+ self._finish()
193
+
194
+ async def _handle_join(self) -> None:
195
+ if self.current.is_admin:
196
+ await self._send("⚠️只有游戏发起者可以开始游戏")
197
+ return
198
+
199
+ if self.current.id not in self.players:
200
+ self.players[self.current.id] = self.current.name
201
+ self.logger.info(f"玩家 {self.current.colored} 加入游戏")
202
+ await self._send("✅成功加入游戏")
203
+ else:
204
+ await self._send("ℹ️你已经加入游戏了")
205
+
206
+ async def _handle_quit(self) -> None:
207
+ if self.current.is_admin:
208
+ await self._send("ℹ️游戏发起者无法退出游戏")
209
+ return
210
+
211
+ if self.current.id in self.players:
212
+ del self.players[self.current.id]
213
+ self.logger.info(f"玩家 {self.current.colored} 退出游戏")
214
+ await self._send("✅成功退出游戏")
215
+ else:
216
+ await self._send("ℹ️你还没有加入游戏")
217
+
218
+ async def _handle_list(self) -> None:
219
+ lines = (
220
+ f"{idx}. {self.players[user_id]}"
221
+ for idx, user_id in enumerate(self.players, 1)
222
+ )
223
+ await self._send("✨当前玩家:\n" + "\n".join(lines))
@@ -10,13 +10,13 @@ def user_in_game(self_id: str, user_id: str, group_id: str | None) -> bool:
10
10
  if group_id is None:
11
11
  return any(
12
12
  self_id == p.bot.self_id and user_id == p.user_id
13
- for p in itertools.chain(*[g.players for g in get_running_games()])
13
+ for p in itertools.chain(*[g.players for g in get_running_games().values()])
14
14
  )
15
15
 
16
16
  def check(game: Game) -> bool:
17
17
  return self_id == game.group.self_id and group_id == game.group.id
18
18
 
19
- if game := next(filter(check, get_running_games()), None):
19
+ if game := next(filter(check, get_running_games().values()), None):
20
20
  return any(user_id == player.user_id for player in game.players)
21
21
 
22
22
  return False
@@ -15,7 +15,6 @@ from nonebot_plugin_alconna import (
15
15
  )
16
16
 
17
17
  from ..config import PresetData, config
18
- from ..constant import ROLE_NAME_CONV
19
18
  from ..models import Role
20
19
 
21
20
  alc = Alconna(
@@ -82,7 +81,7 @@ async def finish(text: str) -> NoReturn:
82
81
 
83
82
 
84
83
  def display_roles(roles: list[Role]) -> str:
85
- return ", ".join(map(ROLE_NAME_CONV.__getitem__, roles))
84
+ return ", ".join(role.display for role in roles)
86
85
 
87
86
 
88
87
  @edit_preset.assign("role")
@@ -164,7 +163,7 @@ async def assign_werewolf(state: T_State) -> None:
164
163
 
165
164
  data.werewolf_priority = result
166
165
  data.save()
167
- await finish("设置成功: " + display_roles(result))
166
+ await finish(f"设置成功: {display_roles(result)}")
168
167
 
169
168
 
170
169
  @edit_preset.assign("priesthood")
@@ -213,7 +212,7 @@ async def assign_priesthood(state: T_State) -> None:
213
212
 
214
213
  data.priesthood_proirity = result
215
214
  data.save()
216
- await finish("设置成功: " + display_roles(result))
215
+ await finish(f"设置成功: {display_roles(result)}")
217
216
 
218
217
 
219
218
  @edit_preset.assign("jester")
@@ -248,15 +247,17 @@ async def reset_preset() -> None:
248
247
 
249
248
  @edit_preset.handle()
250
249
  async def handle_default() -> None:
251
- lines = ["当前游戏预设:", ""]
252
250
  data = PresetData.load()
253
251
 
254
- for total, (w, p, c) in data.role_preset.items():
255
- lines.append(f"{total} 人: 狼人x{w}, 神职x{p}, 平民x{c}")
256
- lines.append("")
257
-
258
- lines.append("狼人优先级: " + display_roles(data.werewolf_priority))
259
- lines.append("神职优先级: " + display_roles(data.priesthood_proirity))
260
- lines.append(f"小丑概率: {data.jester_probability:.0%}")
252
+ lines = ["当前游戏预设:\n"]
253
+ lines.extend(
254
+ f"{total} 人: 狼人x{w}, 神职x{p}, 平民x{c}"
255
+ for total, (w, p, c) in data.role_preset.items()
256
+ )
257
+ lines.append(
258
+ f"\n狼人优先级: {display_roles(data.werewolf_priority)}"
259
+ f"\n神职优先级: {display_roles(data.priesthood_proirity)}"
260
+ f"\n小丑概率: {data.jester_probability:.0%}"
261
+ )
261
262
 
262
263
  await finish("\n".join(lines))
@@ -6,8 +6,8 @@ from nonebot_plugin_alconna import MsgTarget, UniMessage
6
6
 
7
7
  from ...config import config
8
8
  from ...constant import STOP_COMMAND
9
- from ...game import get_starting_games
10
9
  from ...utils import InputStore
10
+ from .._prepare_game import preparing_games
11
11
  from ..depends import user_in_game
12
12
 
13
13
 
@@ -74,7 +74,7 @@ with contextlib.suppress(ImportError, RuntimeError):
74
74
  user_id=user_id,
75
75
  group_id=(event.guild and event.guild.id) or event.channel.id,
76
76
  )
77
- and target in get_starting_games()
77
+ and target in preparing_games
78
78
  )
79
79
 
80
80
  @on_message(rule=_rule_poke_join).handle()
@@ -84,7 +84,7 @@ with contextlib.suppress(ImportError, RuntimeError):
84
84
  target: MsgTarget,
85
85
  ) -> None:
86
86
  user_id = extract_poke_tome(event) or event.get_user_id()
87
- players = get_starting_games()[target]
87
+ players = preparing_games[target].players
88
88
 
89
89
  if user_id not in players:
90
90
  # XXX:
@@ -6,8 +6,8 @@ from nonebot_plugin_alconna import MsgTarget, UniMessage
6
6
 
7
7
  from ...config import config
8
8
  from ...constant import STOP_COMMAND
9
- from ...game import get_starting_games
10
9
  from ...utils import InputStore
10
+ from .._prepare_game import preparing_games
11
11
  from ..depends import user_in_game
12
12
 
13
13
 
@@ -50,7 +50,7 @@ with contextlib.suppress(ImportError, RuntimeError):
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 target in get_starting_games()
53
+ and target in preparing_games
54
54
  )
55
55
 
56
56
  @on_notice(rule=_rule_poke_join).handle()
@@ -60,7 +60,7 @@ with contextlib.suppress(ImportError, RuntimeError):
60
60
  target: MsgTarget,
61
61
  ) -> None:
62
62
  user_id = event.get_user_id()
63
- players = get_starting_games()[target]
63
+ players = preparing_games[target].players
64
64
 
65
65
  if event.group_id is None or user_id in players:
66
66
  return
@@ -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",
@@ -73,210 +59,11 @@ def load_players(target: Target) -> dict[str, str] | None:
73
59
  return None
74
60
 
75
61
 
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
62
  @start_game.handle()
276
63
  async def handle_notice(target: MsgTarget) -> None:
277
64
  if target.private:
278
65
  await UniMessage("⚠️请在群组中创建新游戏").finish(reply_to=True)
279
- if any(target.verify(game.group) for game in get_running_games()):
66
+ if target in get_running_games():
280
67
  await (
281
68
  UniMessage.text("⚠️当前群组内有正在进行的游戏\n")
282
69
  .text("无法开始新游戏")
@@ -291,8 +78,10 @@ async def handle_notice(target: MsgTarget) -> None:
291
78
  " 玩家均加入后,游戏发起者请发送 “开始游戏”\n"
292
79
  )
293
80
  if poke_enabled():
294
- msg.text(f"\n💫可使用戳一戳代替游戏交互中的 “{stop_command_prompt()}” 命令\n")
295
- 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}分钟,超时将自动结束")
296
85
  await solve_button(msg).send(reply_to=True, fallback=FallbackStrategy.ignore)
297
86
 
298
87
 
@@ -315,22 +104,20 @@ async def handle_restart(target: MsgTarget, state: T_State) -> None:
315
104
  async def handle_start(
316
105
  bot: Bot,
317
106
  event: Event,
318
- target: MsgTarget,
319
- session: Uninfo,
320
- interface: QryItrface,
321
107
  state: T_State,
108
+ session: Uninfo,
109
+ target: MsgTarget,
322
110
  ) -> None:
323
111
  players: dict[str, str] = state.get("players", {})
324
112
  admin_id = event.get_user_id()
325
113
  admin_name = extract_session_member_nick(session) or admin_id
326
114
  players[admin_id] = admin_name
327
115
 
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()
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)
333
120
 
334
121
  dump_players(target, players)
335
- game = await Game.new(bot, target, set(players), interface)
336
- await game.start()
122
+ game = await Game.new(bot, target, set(players))
123
+ game.start()
@@ -17,16 +17,12 @@ terminate = on_alconna(
17
17
 
18
18
 
19
19
  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)]
20
+ if (game := get_running_games().get(target)) is None:
21
+ terminate.skip()
22
+ return game
27
23
 
28
24
 
29
25
  @terminate.handle()
30
- async def _(game: RunningGame) -> None:
26
+ async def _(game: Annotated[Game, Depends(running_game)]) -> None:
31
27
  game.terminate()
32
28
  await UniMessage.text("已中止当前群组的游戏进程").finish(reply_to=True)