nonebot-plugin-werewolf 1.0.3__tar.gz → 1.0.5__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nonebot-plugin-werewolf
3
- Version: 1.0.3
3
+ Version: 1.0.5
4
4
  Summary: Default template for PDM package
5
5
  Author-Email: wyf7685 <wyf7685@163.com>
6
6
  License: MIT
@@ -116,9 +116,18 @@ _✨ 简单的狼人杀插件 ✨_
116
116
 
117
117
  ### 指令表
118
118
 
119
- | 指令 | 权限 | 需要@ | 范围 | 说明 |
120
- | :-----------------: | :--: | :---: | :--: | :------: |
121
- | `werewolf`/`狼人杀` | 群员 | 是 | 群聊 | 发起游戏 |
119
+ | 指令 | 权限 | 需要@ | 范围 | 说明 |
120
+ | :-----------------: | :--------: | :---: | :--: | :---------------------------------: |
121
+ | `werewolf`/`狼人杀` | 群员 | 是 | 群聊 | 发起游戏 (进入准备阶段) |
122
+ | `开始游戏` | 游戏发起者 | 是 | 群聊 | _[准备阶段]_ 游戏发起者开始游戏 |
123
+ | `结束游戏` | 游戏发起者 | 是 | 群聊 | _[准备阶段]_ 游戏发起者结束游戏 |
124
+ | `当前玩家` | 群员 | 是 | 群聊 | _[准备阶段]_ 列出参与游戏的玩家列表 |
125
+ | `加入游戏` | 群员 | 是 | 群聊 | _[准备阶段]_ 玩家加入游戏 |
126
+ | `退出游戏` | 群员 | 是 | 群聊 | _[准备阶段]_ 玩家退出游戏 |
127
+
128
+ _其他交互参考游戏内提示_
129
+
130
+ 对于 `OneBot V11` 适配器, 启用配置项 `werewolf__enable_poke` 后, 可以使用戳一戳代替 _准备阶段_ 的 `加入游戏` 操作 和 游戏内的 `/stop` 命令
122
131
 
123
132
  ### 游戏内容
124
133
 
@@ -132,8 +141,8 @@ _✨ 简单的狼人杀插件 ✨_
132
141
 
133
142
  | 总人数 | 狼人 | 神职 | 平民 |
134
143
  | ------ | ---- | ---- | ---- |
135
- | 6     | 1  | 3   | 2   |
136
- | 7     | 2  | 3   | 2   |
144
+ | 6     | 1  | 2   | 3   |
145
+ | 7     | 2  | 2   | 3   |
137
146
  | 8     | 2  | 3   | 3   |
138
147
  | 9     | 2  | 4   | 3   |
139
148
  | 10   | 3  | 4   | 3   |
@@ -171,9 +180,10 @@ werewolf__override_preset='
171
180
  <details>
172
181
  <summary>更新日志</summary>
173
182
 
174
- - 2024.09.01 v1.0.3
183
+ - 2024.09.03 v1.0.5
175
184
 
176
185
  - 优化玩家交互体验
186
+ - 添加游戏结束后死亡报告
177
187
 
178
188
  - 2024.08.31 v1.0.1
179
189
 
@@ -103,9 +103,18 @@ _✨ 简单的狼人杀插件 ✨_
103
103
 
104
104
  ### 指令表
105
105
 
106
- | 指令 | 权限 | 需要@ | 范围 | 说明 |
107
- | :-----------------: | :--: | :---: | :--: | :------: |
108
- | `werewolf`/`狼人杀` | 群员 | 是 | 群聊 | 发起游戏 |
106
+ | 指令 | 权限 | 需要@ | 范围 | 说明 |
107
+ | :-----------------: | :--------: | :---: | :--: | :---------------------------------: |
108
+ | `werewolf`/`狼人杀` | 群员 | 是 | 群聊 | 发起游戏 (进入准备阶段) |
109
+ | `开始游戏` | 游戏发起者 | 是 | 群聊 | _[准备阶段]_ 游戏发起者开始游戏 |
110
+ | `结束游戏` | 游戏发起者 | 是 | 群聊 | _[准备阶段]_ 游戏发起者结束游戏 |
111
+ | `当前玩家` | 群员 | 是 | 群聊 | _[准备阶段]_ 列出参与游戏的玩家列表 |
112
+ | `加入游戏` | 群员 | 是 | 群聊 | _[准备阶段]_ 玩家加入游戏 |
113
+ | `退出游戏` | 群员 | 是 | 群聊 | _[准备阶段]_ 玩家退出游戏 |
114
+
115
+ _其他交互参考游戏内提示_
116
+
117
+ 对于 `OneBot V11` 适配器, 启用配置项 `werewolf__enable_poke` 后, 可以使用戳一戳代替 _准备阶段_ 的 `加入游戏` 操作 和 游戏内的 `/stop` 命令
109
118
 
110
119
  ### 游戏内容
111
120
 
@@ -119,8 +128,8 @@ _✨ 简单的狼人杀插件 ✨_
119
128
 
120
129
  | 总人数 | 狼人 | 神职 | 平民 |
121
130
  | ------ | ---- | ---- | ---- |
122
- | 6     | 1  | 3   | 2   |
123
- | 7     | 2  | 3   | 2   |
131
+ | 6     | 1  | 2   | 3   |
132
+ | 7     | 2  | 2   | 3   |
124
133
  | 8     | 2  | 3   | 3   |
125
134
  | 9     | 2  | 4   | 3   |
126
135
  | 10   | 3  | 4   | 3   |
@@ -158,9 +167,10 @@ werewolf__override_preset='
158
167
  <details>
159
168
  <summary>更新日志</summary>
160
169
 
161
- - 2024.09.01 v1.0.3
170
+ - 2024.09.03 v1.0.5
162
171
 
163
172
  - 优化玩家交互体验
173
+ - 添加游戏结束后死亡报告
164
174
 
165
175
  - 2024.08.31 v1.0.1
166
176
 
@@ -8,7 +8,7 @@ require("nonebot_plugin_waiter")
8
8
  from . import matchers as matchers
9
9
  from .config import Config
10
10
 
11
- __version__ = "1.0.3"
11
+ __version__ = "1.0.5"
12
12
  __plugin_meta__ = PluginMetadata(
13
13
  name="狼人杀",
14
14
  description="适用于 Nonebot2 的狼人杀插件",
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  from dataclasses import dataclass
2
4
  from enum import Enum, auto
3
5
  from typing import TYPE_CHECKING
@@ -45,16 +47,16 @@ class GameStatus(Enum):
45
47
  @dataclass
46
48
  class GameState:
47
49
  day: int
48
- killed: "Player | None" = None
49
- shoot: tuple["Player", "Player"] | tuple[None, None] = (None, None)
50
- protected: "Player | None" = None
51
- potion: tuple["Player | None", tuple[bool, bool]] = (None, (False, False))
50
+ killed: Player | None = None
51
+ shoot: tuple[Player, Player] | tuple[None, None] = (None, None)
52
+ protected: Player | None = None
53
+ potion: tuple[Player | None, tuple[bool, bool]] = (None, (False, False))
52
54
 
53
55
 
54
56
  player_preset: dict[int, tuple[int, int, int]] = {
55
57
  # 总人数: (狼, 神, 民)
56
- 6: (1, 3, 2),
57
- 7: (2, 3, 2),
58
+ 6: (1, 2, 3),
59
+ 7: (2, 2, 3),
58
60
  8: (2, 3, 3),
59
61
  9: (2, 4, 3),
60
62
  10: (3, 4, 3),
@@ -1,16 +1,20 @@
1
1
  import asyncio
2
2
  import asyncio.timeouts
3
+ import contextlib
3
4
  import random
5
+ import time
4
6
 
5
7
  from nonebot.adapters import Bot
8
+ from nonebot.log import logger
6
9
  from nonebot_plugin_alconna import Target, UniMessage
7
10
 
8
11
  from .constant import GameState, GameStatus, KillReason, Role, RoleGroup, player_preset
9
12
  from .player import Player
10
13
  from .player_set import PlayerSet
14
+ from .utils import InputStore
11
15
 
12
16
  starting_games: dict[str, dict[str, str]] = {}
13
- running_games: dict[str, tuple["Game", asyncio.Task[None]]] = {}
17
+ running_games: dict[str, tuple["Game", asyncio.Task[None], asyncio.Task[None]]] = {}
14
18
 
15
19
 
16
20
  def init_players(bot: Bot, game: "Game", players: dict[str, str]) -> PlayerSet:
@@ -26,7 +30,11 @@ def init_players(bot: Bot, game: "Game", players: dict[str, str]) -> PlayerSet:
26
30
  roles.extend([Role.预言家, Role.女巫, Role.猎人, Role.守卫, Role.白痴][: preset[1]])
27
31
  roles.extend([Role.平民] * preset[2])
28
32
 
29
- random.shuffle(roles)
33
+ r = random.Random(time.time())
34
+ shuffled: list[Role] = []
35
+ for _ in range(len(players)):
36
+ idx = r.randint(0, len(roles) - 1)
37
+ shuffled.append(roles.pop(idx))
30
38
 
31
39
  async def selector(target_: Target, b: Bot):
32
40
  return target_.self_id == bot.self_id and b is bot
@@ -44,7 +52,7 @@ def init_players(bot: Bot, game: "Game", players: dict[str, str]) -> PlayerSet:
44
52
  ),
45
53
  players[user_id],
46
54
  )
47
- for user_id, role in zip(players, roles)
55
+ for user_id, role in zip(players, shuffled)
48
56
  )
49
57
 
50
58
 
@@ -65,6 +73,7 @@ class Game:
65
73
  self.group = group
66
74
  self.players = init_players(bot, self, players)
67
75
  self.state = GameState(0)
76
+ self.killed_players = []
68
77
 
69
78
  async def send(self, message: str | UniMessage):
70
79
  if isinstance(message, str):
@@ -111,7 +120,7 @@ class Game:
111
120
  case KillReason.Shoot:
112
121
  msg += " 射杀"
113
122
  case KillReason.Vote:
114
- msg += " 投票放逐"
123
+ msg += " 票出"
115
124
  msg += "\n\n"
116
125
 
117
126
  return msg.strip()
@@ -136,7 +145,15 @@ class Game:
136
145
  if isinstance(players, Player):
137
146
  players = PlayerSet([players])
138
147
 
139
- await players.wait_group_stop(self.group.id, timeout_secs)
148
+ async def wait(p: Player):
149
+ while True:
150
+ msg = await InputStore.fetch(p.user_id, self.group.id)
151
+ if msg.extract_plain_text() == "/stop":
152
+ break
153
+
154
+ with contextlib.suppress(TimeoutError):
155
+ async with asyncio.timeouts.timeout(timeout_secs):
156
+ await asyncio.gather(*[wait(p) for p in players])
140
157
 
141
158
  async def interact(
142
159
  self,
@@ -215,11 +232,11 @@ class Game:
215
232
  .text("限时1分钟, 发送 “/stop” 结束发言")
216
233
  )
217
234
  await self.wait_stop(shoot, 60)
235
+ self.state.shoot = (None, None)
218
236
  await self.post_kill(shoot)
219
- self.state.shoot = (None, None)
220
237
 
221
238
  async def run_vote(self) -> None:
222
- # 统计投票结果
239
+ # 筛选当前存活玩家
223
240
  players = self.players.alive()
224
241
 
225
242
  # 被票玩家: [投票玩家]
@@ -231,9 +248,9 @@ class Game:
231
248
 
232
249
  # 投票结果公示
233
250
  msg = UniMessage.text("投票结果:\n")
234
- for p, v in sorted(vote_result.items(), key=lambda x: x[1], reverse=True):
251
+ for p, v in sorted(vote_result.items(), key=lambda x: len(x[1]), reverse=True):
235
252
  if p is not None:
236
- msg.at(p.user_id).text(f": {v} 票\n")
253
+ msg.at(p.user_id).text(f": {len(v)} 票\n")
237
254
  vote_reversed[len(v)] = [*vote_reversed.get(len(v), []), p]
238
255
  if v := (len(players) - total_votes):
239
256
  msg.text(f"弃票: {v} 票\n")
@@ -244,6 +261,11 @@ class Game:
244
261
  await self.send("没有人被票出")
245
262
  return
246
263
 
264
+ # 弃票大于最高票
265
+ if (len(players) - total_votes) >= max(vote_reversed.keys()):
266
+ await self.send("弃票数大于最高票数, 没有人被票出")
267
+ return
268
+
247
269
  # 平票
248
270
  if len(vs := vote_reversed[max(vote_reversed.keys())]) != 1:
249
271
  await self.send(
@@ -270,6 +292,7 @@ class Game:
270
292
  await self.post_kill(voted)
271
293
 
272
294
  async def run_dead_channel(self) -> None:
295
+ loop = asyncio.get_event_loop()
273
296
  queue: asyncio.Queue[tuple[Player, UniMessage]] = asyncio.Queue()
274
297
 
275
298
  async def send():
@@ -279,20 +302,29 @@ class Game:
279
302
  await self.players.dead().exclude(player).broadcast(msg)
280
303
 
281
304
  async def recv(player: Player):
305
+ counter = 0
306
+
307
+ def decrease():
308
+ nonlocal counter
309
+ counter -= 1
310
+
282
311
  while True:
283
312
  if not player.killed:
284
313
  await asyncio.sleep(1)
285
314
  continue
286
315
  msg = await player.receive()
287
- await queue.put((player, msg))
316
+ counter += 1
317
+ if counter <= 10:
318
+ await queue.put((player, msg))
319
+ loop.call_later(60, decrease)
320
+ else:
321
+ await player.send("发言频率超过限制, 该消息被屏蔽")
288
322
 
289
323
  await asyncio.gather(send(), *[recv(p) for p in self.players])
290
324
 
291
325
  async def run(self) -> None:
292
326
  # 告知玩家角色信息
293
327
  await self.notify_player_role()
294
- # 死者频道
295
- dead_channel = asyncio.create_task(self.run_dead_channel())
296
328
  # 天数记录 主要用于第一晚狼人击杀的遗言
297
329
  day_count = 0
298
330
 
@@ -306,6 +338,7 @@ class Game:
306
338
  await asyncio.gather(
307
339
  self.select_killed(),
308
340
  players.select(Role.女巫).broadcast("请等待狼人决定目标..."),
341
+ players.select(Role.平民).broadcast("请等待其他玩家结束交互..."),
309
342
  self.interact(Role.预言家, 60),
310
343
  self.interact(Role.守卫, 60),
311
344
  )
@@ -331,30 +364,30 @@ class Game:
331
364
  # 没有玩家死亡,平安夜
332
365
  if not (dead := players.dead()):
333
366
  await self.send(msg.text("昨晚是平安夜"))
334
- # 有玩家死亡,执行死亡流程
367
+ # 有玩家死亡,公布死者名单
335
368
  else:
336
- # 公布死者名单
337
369
  msg.text("昨晚的死者是:")
338
370
  for p in dead.sorted():
339
371
  msg.text("\n").at(p.user_id)
340
372
  await self.send(msg)
341
- await self.post_kill(dead)
342
-
343
- # 判断游戏状态
344
- if self.check_game_status() != GameStatus.Unset:
345
- break
346
-
347
- # 公示存活玩家
348
- await self.send(f"当前存活玩家: \n\n{self.players.alive().show()}")
349
373
 
350
374
  # 第一晚被狼人杀死的玩家发表遗言
351
375
  if day_count == 1 and killed is not None and not killed.alive:
352
376
  await self.send(
353
377
  UniMessage.text("当前为第一天\n请被狼人杀死的 ")
354
378
  .at(killed.user_id)
355
- .text(" 发表遗言\n限时1分钟, 发送 “/stop” 结束发言")
379
+ .text(" 发表遗言\n")
380
+ .text("限时1分钟, 发送 “/stop” 结束发言")
356
381
  )
357
382
  await self.wait_stop(killed, 60)
383
+ await self.post_kill(dead)
384
+
385
+ # 判断游戏状态
386
+ if self.check_game_status() != GameStatus.Unset:
387
+ break
388
+
389
+ # 公示存活玩家
390
+ await self.send(f"当前存活玩家: \n\n{self.players.alive().show()}")
358
391
 
359
392
  # 开始自由讨论
360
393
  await self.send("接下来开始自由讨论\n限时2分钟, 全员发送 “/stop” 结束发言")
@@ -365,11 +398,31 @@ class Game:
365
398
  await self.run_vote()
366
399
 
367
400
  # 游戏结束
368
- dead_channel.cancel()
369
401
  winner = "好人" if self.check_game_status() == GameStatus.Good else "狼人"
370
402
  msg = UniMessage.text(f"🎉游戏结束,{winner}获胜\n\n")
371
403
  for p in sorted(self.players, key=lambda p: (p.role.value, p.user_id)):
372
404
  msg.at(p.user_id).text(f": {p.role.name}\n")
373
- msg.text(f"\n{self.show_killed_players()}")
374
405
  await self.send(msg)
375
- running_games.pop(self.group.id, None)
406
+ await self.send(f"玩家死亡报告:\n\n{self.show_killed_players()}")
407
+
408
+ def start(self):
409
+ task = asyncio.create_task(self.run())
410
+ dead_channel = asyncio.create_task(self.run_dead_channel())
411
+
412
+ async def daemon():
413
+ while not task.done(): # noqa: ASYNC110
414
+ await asyncio.sleep(1)
415
+
416
+ try:
417
+ task.result()
418
+ except asyncio.CancelledError as err:
419
+ logger.warning(f"狼人杀游戏进程被取消: {err}")
420
+ except Exception as err:
421
+ msg = f"狼人杀游戏进程出现错误: {err!r}"
422
+ logger.opt(exception=err).error(msg)
423
+ await self.send(msg)
424
+ finally:
425
+ dead_channel.cancel()
426
+ running_games.pop(self.group.id, None)
427
+
428
+ running_games[self.group.id] = (self, task, asyncio.create_task(daemon()))
@@ -62,5 +62,4 @@ async def handle_start(
62
62
  del starting_games[target.id]
63
63
 
64
64
  game = Game(bot=bot, group=target, players=players)
65
- task = asyncio.create_task(game.run())
66
- running_games[target.id] = (game, task)
65
+ game.start()
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  import asyncio.timeouts
3
5
  from dataclasses import dataclass
@@ -16,7 +18,7 @@ if TYPE_CHECKING:
16
18
 
17
19
 
18
20
  P = TypeVar("P", bound=type["Player"])
19
- PLAYER_CLASS: dict[Role, type["Player"]] = {}
21
+ PLAYER_CLASS: dict[Role, type[Player]] = {}
20
22
 
21
23
 
22
24
  def register_role(cls: P) -> P:
@@ -27,7 +29,7 @@ def register_role(cls: P) -> P:
27
29
  @dataclass
28
30
  class KillInfo:
29
31
  reason: KillReason
30
- killers: "PlayerSet"
32
+ killers: PlayerSet
31
33
 
32
34
 
33
35
  class Player:
@@ -35,15 +37,15 @@ class Player:
35
37
  role_group: ClassVar[RoleGroup]
36
38
 
37
39
  bot: Bot
38
- game: "Game"
40
+ game: Game
39
41
  user: Target
40
42
  name: str
41
43
  alive: bool = True
42
44
  killed: bool = False
43
45
  kill_info: KillInfo | None = None
44
- selected: "Player | None" = None
46
+ selected: Player | None = None
45
47
 
46
- def __init__(self, bot: Bot, game: "Game", user: Target, name: str) -> None:
48
+ def __init__(self, bot: Bot, game: Game, user: Target, name: str) -> None:
47
49
  self.bot = bot
48
50
  self.game = game
49
51
  self.user = user
@@ -54,10 +56,10 @@ class Player:
54
56
  cls,
55
57
  role: Role,
56
58
  bot: Bot,
57
- game: "Game",
59
+ game: Game,
58
60
  user: Target,
59
61
  name: str,
60
- ) -> "Player":
62
+ ) -> Player:
61
63
  if role not in PLAYER_CLASS:
62
64
  raise ValueError(f"Unexpected role: {role!r}")
63
65
 
@@ -93,8 +95,10 @@ class Player:
93
95
  async def kill(
94
96
  self,
95
97
  reason: KillReason,
96
- *killers: "Player",
98
+ *killers: Player,
97
99
  ) -> bool:
100
+ from .player_set import PlayerSet
101
+
98
102
  self.alive = False
99
103
  self.kill_info = KillInfo(reason, PlayerSet(killers))
100
104
  return True
@@ -102,15 +106,16 @@ class Player:
102
106
  async def post_kill(self) -> None:
103
107
  self.killed = True
104
108
 
105
- async def vote(self, players: "PlayerSet") -> "tuple[Player, Player] | None":
109
+ async def vote(self, players: PlayerSet) -> tuple[Player, Player] | None:
106
110
  await self.send(
107
111
  f"请选择需要投票的玩家:\n{players.show()}"
108
112
  "\n\n发送编号选择玩家\n发送 “/stop” 弃票"
109
113
  )
110
114
 
111
115
  while True:
112
- text = (await self.receive()).extract_plain_text()
116
+ text = await self.receive_text()
113
117
  if text == "/stop":
118
+ await self.send("你选择了弃票")
114
119
  return None
115
120
  index = check_index(text, len(players))
116
121
  if index is not None:
@@ -185,8 +190,11 @@ class 狼人(Player):
185
190
  async def notify_role(self) -> None:
186
191
  await super().notify_role()
187
192
  partners = self.game.players.alive().select(RoleGroup.狼人).exclude(self)
188
- msg = "你的队友:\n" + "\n".join(f" {p.role.name}: {p.name}" for p in partners)
189
- await self.send(msg)
193
+ if partners:
194
+ await self.send(
195
+ "你的队友:\n"
196
+ + "\n".join(f" {p.role.name}: {p.name}" for p in partners)
197
+ )
190
198
 
191
199
  @override
192
200
  async def interact(self) -> None:
@@ -221,7 +229,7 @@ class 狼人(Player):
221
229
  if index is not None:
222
230
  selected = index - 1
223
231
  msg = f"当前选择玩家: {players[selected].name}"
224
- await self.send(msg)
232
+ await self.send(f"{msg}\n发送 “/stop” 结束回合")
225
233
  broadcast(f"队友 {self.name} {msg}")
226
234
  if text == "/stop":
227
235
  if selected is not None:
@@ -296,7 +304,7 @@ class 女巫(Player):
296
304
  async def handle_killed(self) -> bool:
297
305
  msg = UniMessage()
298
306
  if (killed := self.game.state.killed) is not None:
299
- msg.text(f"今晚 {killed} 被刀了\n\n")
307
+ msg.text(f"今晚 {killed.name} 被刀了\n\n")
300
308
  else:
301
309
  await self.send("今晚没有人被刀")
302
310
  return False
@@ -418,7 +426,7 @@ class 白痴(Player):
418
426
  return await super().kill(reason, *killers)
419
427
 
420
428
  @override
421
- async def vote(self, players: "PlayerSet") -> "tuple[Player, Player] | None":
429
+ async def vote(self, players: PlayerSet) -> tuple[Player, Player] | None:
422
430
  if self.voted:
423
431
  await self.send("你已经发动过白痴身份的技能,无法参与本次投票")
424
432
  return None
@@ -1,12 +1,10 @@
1
1
  import asyncio
2
2
  import asyncio.timeouts
3
- import contextlib
4
3
 
5
4
  from nonebot_plugin_alconna.uniseg import UniMessage
6
5
 
7
6
  from .constant import Role, RoleGroup
8
7
  from .player import Player
9
- from .utils import InputStore
10
8
 
11
9
 
12
10
  class PlayerSet(set[Player]):
@@ -52,12 +50,13 @@ class PlayerSet(set[Player]):
52
50
  await asyncio.gather(*[p.interact() for p in self.alive()])
53
51
 
54
52
  async def vote(self, timeout_secs: float = 60) -> dict[Player, list[Player]]:
55
- async def vote(player: Player) -> "tuple[Player, Player] | None":
53
+ async def vote(player: Player) -> tuple[Player, Player] | None:
56
54
  try:
57
55
  async with asyncio.timeouts.timeout(timeout_secs):
58
56
  return await player.vote(self)
59
57
  except TimeoutError:
60
58
  await player.send("投票超时,将视为弃票")
59
+ return None
61
60
 
62
61
  result: dict[Player, list[Player]] = {}
63
62
  for item in await asyncio.gather(*[vote(p) for p in self.alive()]):
@@ -69,17 +68,6 @@ class PlayerSet(set[Player]):
69
68
  async def broadcast(self, message: str | UniMessage) -> None:
70
69
  await asyncio.gather(*[p.send(message) for p in self])
71
70
 
72
- async def wait_group_stop(self, group_id: str, timeout_secs: float) -> None:
73
- async def wait(p: Player):
74
- while True:
75
- msg = await InputStore.fetch(p.user_id, group_id)
76
- if msg.extract_plain_text() == "/stop":
77
- break
78
-
79
- with contextlib.suppress(TimeoutError):
80
- async with asyncio.timeouts.timeout(timeout_secs):
81
- await asyncio.gather(*[wait(p) for p in self])
82
-
83
71
  def show(self) -> str:
84
72
  return "\n".join(f"{i}. {p.name}" for i, p in enumerate(self.sorted(), 1))
85
73
 
@@ -9,7 +9,7 @@ from nonebot.rule import to_me
9
9
  from nonebot_plugin_alconna import MsgTarget, UniMessage, UniMsg
10
10
  from nonebot_plugin_userinfo import EventUserInfo, UserInfo
11
11
 
12
- from .game import player_preset, running_games
12
+ from .constant import player_preset
13
13
 
14
14
 
15
15
  def check_index(text: str, arrlen: int) -> int | None:
@@ -42,15 +42,19 @@ class InputStore:
42
42
 
43
43
 
44
44
  def user_in_game(user_id: str, group_id: str | None) -> bool:
45
+ from .game import running_games
46
+
45
47
  if group_id is not None and group_id not in running_games:
46
48
  return False
47
49
  games = running_games.values() if group_id is None else [running_games[group_id]]
48
- for game, _ in games:
50
+ for game, *_ in games:
49
51
  return any(user_id == player.user_id for player in game.players)
50
52
  return False
51
53
 
52
54
 
53
55
  async def rule_in_game(event: Event, target: MsgTarget) -> bool:
56
+ from .game import running_games
57
+
54
58
  if not running_games:
55
59
  return False
56
60
  if target.private:
@@ -169,12 +173,12 @@ async def _prepare_game_handle(
169
173
 
170
174
 
171
175
  async def prepare_game(event: Event, players: dict[str, str]) -> None:
172
- queue = asyncio.Queue()
176
+ queue: asyncio.Queue[tuple[str, str, str]] = asyncio.Queue()
173
177
  task_receive = asyncio.create_task(
174
178
  _prepare_game_receive(
175
179
  queue,
176
180
  event,
177
- UniMessage.get_target().id,
181
+ UniMessage.get_target(event).id,
178
182
  )
179
183
  )
180
184
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nonebot-plugin-werewolf"
3
- version = "1.0.3"
3
+ version = "1.0.5"
4
4
  description = "Default template for PDM package"
5
5
  authors = [
6
6
  { name = "wyf7685", email = "wyf7685@163.com" },