nonebot-plugin-werewolf 1.1.2__py3-none-any.whl → 1.1.3__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 +8 -4
  2. nonebot_plugin_werewolf/config.py +11 -15
  3. nonebot_plugin_werewolf/constant.py +17 -2
  4. nonebot_plugin_werewolf/game.py +77 -98
  5. nonebot_plugin_werewolf/matchers/__init__.py +2 -0
  6. nonebot_plugin_werewolf/matchers/message_in_game.py +15 -0
  7. nonebot_plugin_werewolf/{ob11_ext.py → matchers/ob11_ext.py} +26 -20
  8. nonebot_plugin_werewolf/matchers/start_game.py +56 -0
  9. nonebot_plugin_werewolf/player_set.py +6 -4
  10. nonebot_plugin_werewolf/players/__init__.py +10 -0
  11. nonebot_plugin_werewolf/players/can_shoot.py +59 -0
  12. nonebot_plugin_werewolf/players/civilian.py +7 -0
  13. nonebot_plugin_werewolf/players/guard.py +37 -0
  14. nonebot_plugin_werewolf/players/hunter.py +8 -0
  15. nonebot_plugin_werewolf/players/idiot.py +44 -0
  16. nonebot_plugin_werewolf/players/joker.py +21 -0
  17. nonebot_plugin_werewolf/players/player.py +161 -0
  18. nonebot_plugin_werewolf/players/prophet.py +30 -0
  19. nonebot_plugin_werewolf/players/werewolf.py +67 -0
  20. nonebot_plugin_werewolf/players/witch.py +72 -0
  21. nonebot_plugin_werewolf/players/wolfking.py +14 -0
  22. nonebot_plugin_werewolf/utils.py +83 -64
  23. {nonebot_plugin_werewolf-1.1.2.dist-info → nonebot_plugin_werewolf-1.1.3.dist-info}/METADATA +11 -3
  24. nonebot_plugin_werewolf-1.1.3.dist-info/RECORD +29 -0
  25. nonebot_plugin_werewolf/matchers.py +0 -62
  26. nonebot_plugin_werewolf/player.py +0 -462
  27. nonebot_plugin_werewolf-1.1.2.dist-info/RECORD +0 -16
  28. {nonebot_plugin_werewolf-1.1.2.dist-info → nonebot_plugin_werewolf-1.1.3.dist-info}/LICENSE +0 -0
  29. {nonebot_plugin_werewolf-1.1.2.dist-info → nonebot_plugin_werewolf-1.1.3.dist-info}/WHEEL +0 -0
  30. {nonebot_plugin_werewolf-1.1.2.dist-info → nonebot_plugin_werewolf-1.1.3.dist-info}/top_level.txt +0 -0
@@ -2,13 +2,14 @@ from nonebot import require
2
2
  from nonebot.plugin import PluginMetadata, inherit_supported_adapters
3
3
 
4
4
  require("nonebot_plugin_alconna")
5
- require("nonebot_plugin_userinfo")
5
+ require("nonebot_plugin_uninfo")
6
6
  require("nonebot_plugin_waiter")
7
7
 
8
8
  from . import matchers as matchers
9
+ from . import players as players
9
10
  from .config import Config
10
11
 
11
- __version__ = "1.1.2"
12
+ __version__ = "1.1.3"
12
13
  __plugin_meta__ = PluginMetadata(
13
14
  name="狼人杀",
14
15
  description="适用于 Nonebot2 的狼人杀插件",
@@ -18,8 +19,11 @@ __plugin_meta__ = PluginMetadata(
18
19
  config=Config,
19
20
  supported_adapters=inherit_supported_adapters(
20
21
  "nonebot_plugin_alconna",
21
- "nonebot_plugin_userinfo",
22
+ "nonebot_plugin_uninfo",
22
23
  "nonebot_plugin_waiter",
23
24
  ),
24
- extra={"author": "wyf7685"},
25
+ extra={
26
+ "Author": "wyf7685",
27
+ "Bug Tracker": "https://github.com/wyf7685/nonebot-plugin-werewolf/issues",
28
+ },
25
29
  )
@@ -1,4 +1,4 @@
1
- from typing import Literal, overload
1
+ from typing import Any, Literal, overload
2
2
 
3
3
  from nonebot import get_plugin_config, logger
4
4
  from nonebot.compat import PYDANTIC_V2
@@ -7,6 +7,8 @@ from typing_extensions import Self
7
7
 
8
8
  from .constant import (
9
9
  Role,
10
+ RolePresetConfig,
11
+ RolePresetDict,
10
12
  default_priesthood_proirity,
11
13
  default_role_preset,
12
14
  default_werewolf_priority,
@@ -18,12 +20,12 @@ else:
18
20
  from pydantic import root_validator
19
21
 
20
22
  @overload
21
- def model_validator(*, mode: Literal["before"]): ... # noqa: ANN201
23
+ def model_validator(*, mode: Literal["before"]) -> Any: ... # noqa: ANN401
22
24
 
23
25
  @overload
24
- def model_validator(*, mode: Literal["after"]): ... # noqa: ANN201
26
+ def model_validator(*, mode: Literal["after"]) -> Any: ... # noqa: ANN401
25
27
 
26
- def model_validator(*, mode: Literal["before", "after"]):
28
+ def model_validator(*, mode: Literal["before", "after"]) -> Any:
27
29
  return root_validator(
28
30
  pre=mode == "before", # pyright: ignore[reportArgumentType]
29
31
  allow_reuse=True,
@@ -31,16 +33,10 @@ else:
31
33
 
32
34
 
33
35
  class PluginConfig(BaseModel):
34
- enable_poke: bool = Field(default=True)
35
- role_preset: list[tuple[int, int, int, int]] | dict[int, tuple[int, int, int]] = (
36
- Field(default_factory=default_role_preset.copy)
37
- )
38
- werewolf_priority: list[Role] = Field(
39
- default_factory=default_werewolf_priority.copy
40
- )
41
- priesthood_proirity: list[Role] = Field(
42
- default_factory=default_priesthood_proirity.copy
43
- )
36
+ enable_poke: bool = True
37
+ role_preset: RolePresetConfig = default_role_preset.copy()
38
+ werewolf_priority: list[Role] = default_werewolf_priority.copy()
39
+ priesthood_proirity: list[Role] = default_priesthood_proirity.copy()
44
40
  joker_probability: float = Field(default=0.0, ge=0.0, le=1.0)
45
41
 
46
42
  @model_validator(mode="after")
@@ -72,7 +68,7 @@ class PluginConfig(BaseModel):
72
68
 
73
69
  return self
74
70
 
75
- def get_role_preset(self) -> dict[int, tuple[int, int, int]]:
71
+ def get_role_preset(self) -> RolePresetDict:
76
72
  if isinstance(self.role_preset, list):
77
73
  self.role_preset = {i[0]: i[1:] for i in self.role_preset}
78
74
  return self.role_preset
@@ -5,7 +5,7 @@ from enum import Enum, auto
5
5
  from typing import TYPE_CHECKING
6
6
 
7
7
  if TYPE_CHECKING:
8
- from .player import Player
8
+ from .players import Player
9
9
 
10
10
 
11
11
  class Role(Enum):
@@ -71,7 +71,22 @@ role_name_conv: dict[Role | RoleGroup, str] = {
71
71
  RoleGroup.Others: "其他",
72
72
  }
73
73
 
74
- default_role_preset: dict[int, tuple[int, int, int]] = {
74
+ role_emoji: dict[Role, str] = {
75
+ Role.Werewolf: "🐺",
76
+ Role.WolfKing: "🐺👑",
77
+ Role.Prophet: "🔮",
78
+ Role.Witch: "🧙‍♀️",
79
+ Role.Hunter: "🕵️",
80
+ Role.Guard: "🛡️",
81
+ Role.Idiot: "👨🏻‍🦲",
82
+ Role.Joker: "🤡",
83
+ Role.Civilian: "👨🏻‍🌾",
84
+ }
85
+
86
+ RolePresetDict = dict[int, tuple[int, int, int]]
87
+ RolePresetConfig = RolePresetDict | list[tuple[int, int, int, int]]
88
+
89
+ default_role_preset: RolePresetDict = {
75
90
  # 总人数: (狼, 神, 民)
76
91
  6: (1, 2, 3),
77
92
  7: (2, 2, 3),
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import asyncio
4
4
  import contextlib
5
5
  import secrets
6
- from typing import TYPE_CHECKING, NoReturn
6
+ from typing import TYPE_CHECKING, ClassVar, NoReturn
7
7
 
8
8
  from nonebot.log import logger
9
9
  from nonebot_plugin_alconna import At, Target, UniMessage
@@ -12,23 +12,19 @@ from ._timeout import timeout
12
12
  from .config import config
13
13
  from .constant import GameState, GameStatus, KillReason, Role, RoleGroup, role_name_conv
14
14
  from .exception import GameFinished
15
- from .player import Player
16
15
  from .player_set import PlayerSet
16
+ from .players import Player
17
17
  from .utils import InputStore
18
18
 
19
19
  if TYPE_CHECKING:
20
20
  from nonebot.adapters import Bot
21
21
  from nonebot_plugin_alconna.uniseg.message import Receipt
22
22
 
23
- starting_games: dict[str, dict[str, str]] = {}
24
- running_games: dict[str, Game] = {}
25
-
26
23
 
27
24
  def init_players(bot: Bot, game: Game, players: dict[str, str]) -> PlayerSet:
28
25
  logger.opt(colors=True).debug(f"初始化 <c>{game.group.id}</c> 的玩家职业")
29
26
  role_preset = config.get_role_preset()
30
- preset = role_preset.get(len(players))
31
- if preset is None:
27
+ if (preset := role_preset.get(len(players))) is None:
32
28
  raise ValueError(
33
29
  f"玩家人数不符: "
34
30
  f"应为 {', '.join(map(str, role_preset))} 人, 传入{len(players)}人"
@@ -44,34 +40,22 @@ def init_players(bot: Bot, game: Game, players: dict[str, str]) -> PlayerSet:
44
40
  roles.remove(Role.Civilian)
45
41
  roles.append(Role.Joker)
46
42
 
47
- shuffled: list[Role] = []
48
- while roles:
49
- idx = secrets.randbelow(len(roles))
50
- shuffled.append(roles.pop(idx))
51
-
52
- logger.debug(f"职业分配: {shuffled}")
53
-
54
- async def selector(target_: Target, b: Bot) -> bool:
55
- return target_.self_id == bot.self_id and b is bot
56
-
57
- return PlayerSet(
58
- Player.new(
59
- role,
60
- bot,
61
- game,
62
- Target(
63
- user_id,
64
- private=True,
65
- self_id=bot.self_id,
66
- selector=selector,
67
- ),
68
- players[user_id],
69
- )
70
- for user_id, role in zip(players, shuffled, strict=True)
43
+ def _select_role() -> Role:
44
+ return roles.pop(secrets.randbelow(len(roles)))
45
+
46
+ player_set = PlayerSet(
47
+ Player.new(_select_role(), bot, game, user_id, players[user_id])
48
+ for user_id in players
71
49
  )
50
+ logger.debug(f"职业分配完成: {player_set}")
51
+
52
+ return player_set
72
53
 
73
54
 
74
55
  class Game:
56
+ starting_games: ClassVar[dict[Target, dict[str, str]]] = {}
57
+ running_games: ClassVar[set[Game]] = set()
58
+
75
59
  bot: Bot
76
60
  group: Target
77
61
  players: PlayerSet
@@ -124,52 +108,45 @@ class Game:
124
108
  raise GameFinished(GameStatus.GoodGuy)
125
109
 
126
110
  def show_killed_players(self) -> str:
127
- msg = ""
111
+ result: list[str] = []
128
112
 
129
113
  for player in self.killed_players:
130
114
  if player.kill_info is None:
131
115
  continue
132
116
 
133
- msg += f"{player.name} 被 " + ", ".join(
117
+ line = f"{player.name} 被 " + ", ".join(
134
118
  p.name for p in player.kill_info.killers
135
119
  )
136
120
  match player.kill_info.reason:
137
121
  case KillReason.Werewolf:
138
- msg += " 刀了"
122
+ line = f"🔪 {line} 刀了"
139
123
  case KillReason.Poison:
140
- msg += " 毒死"
124
+ line = f"🧪 {line} 毒死"
141
125
  case KillReason.Shoot:
142
- msg += " 射杀"
126
+ line = f"🔫 {line} 射杀"
143
127
  case KillReason.Vote:
144
- msg += " 票出"
145
- msg += "\n\n"
128
+ line = f"🗳️ {line} 票出"
129
+ result.append(line)
146
130
 
147
- return msg.strip()
131
+ return "\n\n".join(result)
148
132
 
149
133
  async def notify_player_role(self) -> None:
150
134
  preset = config.get_role_preset()[len(self.players)]
151
135
  await asyncio.gather(
152
136
  self.send(
153
137
  self.at_all()
154
- .text("\n\n正在分配职业,请注意查看私聊消息\n")
138
+ .text("\n\n📝正在分配职业,请注意查看私聊消息\n")
155
139
  .text(f"当前玩家数: {len(self.players)}\n")
156
140
  .text(f"职业分配: 狼人x{preset[0]}, 神职x{preset[1]}, 平民x{preset[2]}")
157
141
  ),
158
142
  *[p.notify_role() for p in self.players],
159
143
  )
160
144
 
161
- async def wait_stop(
162
- self,
163
- players: Player | PlayerSet,
164
- timeout_secs: float,
165
- ) -> None:
166
- if isinstance(players, Player):
167
- players = PlayerSet([players])
168
-
145
+ async def wait_stop(self, *players: Player, timeout_secs: float) -> None:
169
146
  async def wait(p: Player) -> None:
170
147
  while True:
171
148
  msg = await InputStore.fetch(p.user_id, self.group.id)
172
- if msg.extract_plain_text() == "/stop":
149
+ if msg.extract_plain_text().strip() == "/stop":
173
150
  break
174
151
 
175
152
  with contextlib.suppress(TimeoutError):
@@ -178,26 +155,23 @@ class Game:
178
155
 
179
156
  async def interact(
180
157
  self,
181
- type_: Player | Role | RoleGroup,
158
+ player_type: Player | Role | RoleGroup,
182
159
  timeout_secs: float,
183
160
  ) -> None:
184
- players = self.players.alive().select(type_)
185
- text = (
186
- type_.role_name # Player
187
- if isinstance(type_, Player)
188
- else (
189
- role_name_conv[type_] # Role
190
- if isinstance(type_, Role)
191
- else f"{role_name_conv[type_]}阵营" # RoleGroup
192
- )
193
- )
194
-
195
- await players.broadcast(f"{text}交互开始,限时 {timeout_secs/60:.2f} 分钟")
161
+ players = self.players.alive().select(player_type)
162
+ if isinstance(player_type, Player):
163
+ text = player_type.role_name
164
+ elif isinstance(player_type, Role):
165
+ text = role_name_conv[player_type]
166
+ else: # RoleGroup
167
+ text = f"{role_name_conv[player_type]}阵营"
168
+
169
+ await players.broadcast(f"✏️{text}交互开始,限时 {timeout_secs/60:.2f} 分钟")
196
170
  try:
197
171
  await players.interact(timeout_secs)
198
172
  except TimeoutError:
199
- logger.opt(colors=True).debug(f"{text}交互超时 (<y>{timeout_secs}</y>s)")
200
- await players.broadcast(f"{text}交互时间结束")
173
+ logger.opt(colors=True).debug(f"⚠️{text}交互超时 (<y>{timeout_secs}</y>s)")
174
+ await players.broadcast(f"ℹ️{text}交互时间结束")
201
175
 
202
176
  async def select_killed(self) -> None:
203
177
  players = self.players.alive()
@@ -207,9 +181,9 @@ class Game:
207
181
  await self.interact(RoleGroup.Werewolf, 120)
208
182
  if (s := w.player_selected()).size == 1:
209
183
  self.state.killed = s.pop()
210
- await w.broadcast(f"今晚选择的目标为: {self.state.killed.name}")
184
+ await w.broadcast(f"🔪今晚选择的目标为: {self.state.killed.name}")
211
185
  else:
212
- await w.broadcast("狼人阵营意见未统一,此晚空刀")
186
+ await w.broadcast("⚠️狼人阵营意见未统一,此晚空刀")
213
187
 
214
188
  # 如果女巫存活,正常交互,限时1分钟
215
189
  if players.include(Role.Witch):
@@ -226,12 +200,12 @@ class Game:
226
200
 
227
201
  await asyncio.gather(
228
202
  players.broadcast(
229
- "你已加入死者频道,请勿在群内继续发言\n"
203
+ "ℹ️你已加入死者频道,请勿在群内继续发言\n"
230
204
  "私聊发送消息将转发至其他已死亡玩家"
231
205
  ),
232
206
  self.players.dead()
233
207
  .exclude(*players)
234
- .broadcast(f"玩家 {', '.join(p.name for p in players)} 加入了死者频道"),
208
+ .broadcast(f"ℹ️玩家 {', '.join(p.name for p in players)} 加入了死者频道"),
235
209
  )
236
210
 
237
211
  async def post_kill(self, players: Player | PlayerSet) -> None:
@@ -253,7 +227,7 @@ class Game:
253
227
  .text(f" 被{shooter.role_name}射杀, 请发表遗言\n")
254
228
  .text("限时1分钟, 发送 “/stop” 结束发言")
255
229
  )
256
- await self.wait_stop(shoot, 60)
230
+ await self.wait_stop(shoot, timeout_secs=60)
257
231
  self.state.shoot = (None, None)
258
232
  await self.post_kill(shoot)
259
233
 
@@ -271,34 +245,35 @@ class Game:
271
245
  logger.debug(f"投票结果: {vote_result}")
272
246
 
273
247
  # 投票结果公示
274
- msg = UniMessage.text("投票结果:\n")
248
+ msg = UniMessage.text("📊投票结果:\n")
275
249
  for p, v in sorted(vote_result.items(), key=lambda x: len(x[1]), reverse=True):
276
250
  if p is not None:
277
251
  msg.at(p.user_id).text(f": {len(v)} 票\n")
278
252
  vote_reversed[len(v)] = [*vote_reversed.get(len(v), []), p]
279
253
  if v := (len(players) - total_votes):
280
- msg.text(f"弃票: {v} 票\n")
281
- await self.send(msg)
254
+ msg.text(f"弃票: {v} 票\n\n")
282
255
 
283
256
  # 全员弃票 # 不是哥们?
284
257
  if total_votes == 0:
285
- await self.send("没有人被票出")
258
+ await self.send(msg.text("🔨没有人被票出"))
286
259
  return
287
260
 
288
261
  # 弃票大于最高票
289
262
  if (len(players) - total_votes) >= max(vote_reversed.keys()):
290
- await self.send("弃票数大于最高票数, 没有人被票出")
263
+ await self.send(msg.text("🔨弃票数大于最高票数, 没有人被票出"))
291
264
  return
292
265
 
293
266
  # 平票
294
267
  if len(vs := vote_reversed[max(vote_reversed.keys())]) != 1:
295
268
  await self.send(
296
- UniMessage.text("玩家 ")
269
+ msg.text("🔨玩家 ")
297
270
  .text(", ".join(p.name for p in vs))
298
271
  .text(" 平票, 没有人被票出")
299
272
  )
300
273
  return
301
274
 
275
+ await self.send(msg)
276
+
302
277
  # 仅有一名玩家票数最高
303
278
  voted = vs.pop()
304
279
  if not await voted.kill(KillReason.Vote, *vote_result[voted]):
@@ -307,15 +282,15 @@ class Game:
307
282
 
308
283
  # 遗言
309
284
  await self.send(
310
- UniMessage.text("玩家 ")
285
+ UniMessage.text("🔨玩家 ")
311
286
  .at(voted.user_id)
312
287
  .text(" 被投票放逐, 请发表遗言\n")
313
288
  .text("限时1分钟, 发送 “/stop” 结束发言")
314
289
  )
315
- await self.wait_stop(voted, 60)
290
+ await self.wait_stop(voted, timeout_secs=60)
316
291
  await self.post_kill(voted)
317
292
 
318
- async def run_dead_channel(self) -> None:
293
+ async def run_dead_channel(self) -> NoReturn:
319
294
  loop = asyncio.get_event_loop()
320
295
  queue: asyncio.Queue[tuple[Player, UniMessage]] = asyncio.Queue()
321
296
 
@@ -342,7 +317,7 @@ class Game:
342
317
  await queue.put((player, msg))
343
318
  loop.call_later(60, decrease)
344
319
  else:
345
- await player.send("发言频率超过限制, 该消息被屏蔽")
320
+ await player.send("❌发言频率超过限制, 该消息被屏蔽")
346
321
 
347
322
  await asyncio.gather(send(), *[recv(p) for p in self.players])
348
323
 
@@ -357,17 +332,17 @@ class Game:
357
332
  # 重置游戏状态,进入下一夜
358
333
  self.state = GameState(day_count)
359
334
  players = self.players.alive()
360
- await self.send("天黑请闭眼...")
335
+ await self.send("🌙天黑请闭眼...")
361
336
 
362
337
  # 狼人、预言家、守卫 同时交互,女巫在狼人后交互
363
338
  await asyncio.gather(
364
339
  self.select_killed(),
365
340
  self.interact(Role.Prophet, 60),
366
341
  self.interact(Role.Guard, 60),
367
- players.select(Role.Witch).broadcast("请等待狼人决定目标..."),
342
+ players.select(Role.Witch).broadcast("ℹ️请等待狼人决定目标..."),
368
343
  players.exclude(
369
344
  RoleGroup.Werewolf, Role.Prophet, Role.Witch, Role.Guard
370
- ).broadcast("请等待其他玩家结束交互..."),
345
+ ).broadcast("ℹ️请等待其他玩家结束交互..."),
371
346
  )
372
347
 
373
348
  # 狼人击杀目标
@@ -391,40 +366,44 @@ class Game:
391
366
  await witch.selected.kill(KillReason.Poison, witch)
392
367
 
393
368
  day_count += 1
394
- msg = UniMessage.text(f"『第{day_count}天』天亮了...\n")
369
+ msg = UniMessage.text(f"『第{day_count}天』☀️天亮了...\n")
395
370
  # 没有玩家死亡,平安夜
396
371
  if not (dead := players.dead()):
397
372
  await self.send(msg.text("昨晚是平安夜"))
398
373
  # 有玩家死亡,公布死者名单
399
374
  else:
400
- msg.text("昨晚的死者是:")
401
- for p in dead.sorted():
375
+ msg.text("☠️昨晚的死者是:")
376
+ for p in dead.sorted:
402
377
  msg.text("\n").at(p.user_id)
403
378
  await self.send(msg)
404
379
 
405
380
  # 第一晚被狼人杀死的玩家发表遗言
406
381
  if day_count == 1 and killed is not None and not killed.alive:
407
382
  await self.send(
408
- UniMessage.text("当前为第一天\n请被狼人杀死的 ")
383
+ UniMessage.text("⚙️当前为第一天\n请被狼人杀死的 ")
409
384
  .at(killed.user_id)
410
385
  .text(" 发表遗言\n")
411
386
  .text("限时1分钟, 发送 “/stop” 结束发言")
412
387
  )
413
- await self.wait_stop(killed, 60)
388
+ await self.wait_stop(killed, timeout_secs=60)
414
389
  await self.post_kill(dead)
415
390
 
416
391
  # 判断游戏状态
417
392
  self.check_game_status()
418
393
 
419
394
  # 公示存活玩家
420
- await self.send(f"当前存活玩家: \n\n{self.players.alive().show()}")
395
+ await self.send(f"📝当前存活玩家: \n\n{self.players.alive().show()}")
421
396
 
422
397
  # 开始自由讨论
423
- await self.send("接下来开始自由讨论\n限时2分钟, 全员发送 “/stop” 结束发言")
424
- await self.wait_stop(self.players.alive(), 120)
398
+ await self.send(
399
+ "💬接下来开始自由讨论\n限时2分钟, 全员发送 “/stop” 结束发言"
400
+ )
401
+ await self.wait_stop(*self.players.alive(), timeout_secs=120)
425
402
 
426
403
  # 开始投票
427
- await self.send("讨论结束, 进入投票环节,限时1分钟\n请在私聊中进行投票交互")
404
+ await self.send(
405
+ "🗳️讨论结束, 进入投票环节,限时1分钟\n请在私聊中进行投票交互"
406
+ )
428
407
  await self.run_vote()
429
408
 
430
409
  # 判断游戏状态
@@ -464,16 +443,16 @@ class Game:
464
443
  except Exception as err:
465
444
  msg = f"{self.group.id} 的狼人杀游戏进程出现未知错误: {err!r}"
466
445
  logger.opt(exception=err).error(msg)
467
- await self.send(msg)
446
+ await self.send(f"❌狼人杀游戏进程出现未知错误: {err!r}")
468
447
  finally:
469
448
  dead_channel.cancel()
470
- running_games.pop(self.group.id, None)
449
+ self.running_games.discard(self)
471
450
 
472
451
  def daemon_callback(task: asyncio.Task[None]) -> None:
473
452
  if err := task.exception():
474
- logger.opt(exception=err).error(
475
- f"{self.group.id} 的狼人杀守护进程出现错误: {err!r}"
476
- )
453
+ msg = f"{self.group.id} 的狼人杀守护进程出现错误: {err!r}"
454
+ logger.opt(exception=err).error(msg)
477
455
 
478
- running_games[self.group.id] = self
479
- asyncio.create_task(daemon()).add_done_callback(daemon_callback)
456
+ self.running_games.add(self)
457
+ daemon_task = asyncio.create_task(daemon())
458
+ daemon_task.add_done_callback(daemon_callback)
@@ -0,0 +1,2 @@
1
+ from . import message_in_game as message_in_game
2
+ from . import start_game as start_game
@@ -0,0 +1,15 @@
1
+ from nonebot import on_message
2
+ from nonebot.adapters import Event
3
+ from nonebot_plugin_alconna import MsgTarget, UniMsg
4
+
5
+ from ..utils import InputStore, rule_in_game
6
+
7
+ message_in_game = on_message(rule=rule_in_game)
8
+
9
+
10
+ @message_in_game.handle()
11
+ async def handle_input(event: Event, target: MsgTarget, msg: UniMsg) -> None:
12
+ if target.private:
13
+ InputStore.put(msg, target.id)
14
+ else:
15
+ InputStore.put(msg, event.get_user_id(), target.id)
@@ -1,12 +1,12 @@
1
1
  import contextlib
2
2
 
3
- from nonebot import on_type
3
+ from nonebot import on_notice
4
4
  from nonebot.internal.matcher import current_bot
5
- from nonebot_plugin_alconna import UniMessage
5
+ from nonebot_plugin_alconna import MsgTarget, UniMessage
6
6
 
7
- from .config import config
8
- from .game import starting_games
9
- from .utils import InputStore, user_in_game
7
+ from ..config import config
8
+ from ..game import Game
9
+ from ..utils import InputStore, user_in_game
10
10
 
11
11
 
12
12
  def ob11_ext_enabled() -> bool:
@@ -14,11 +14,11 @@ def ob11_ext_enabled() -> bool:
14
14
 
15
15
 
16
16
  with contextlib.suppress(ImportError):
17
- from nonebot.adapters.onebot.v11 import Bot, MessageSegment
17
+ from nonebot.adapters.onebot.v11 import Bot
18
18
  from nonebot.adapters.onebot.v11.event import PokeNotifyEvent
19
19
 
20
20
  # 游戏内戳一戳等效 "/stop"
21
- async def _rule_poke_1(event: PokeNotifyEvent) -> bool:
21
+ async def _rule_poke_stop(bot: Bot, event: PokeNotifyEvent) -> bool:
22
22
  if not config.enable_poke:
23
23
  return False
24
24
 
@@ -27,19 +27,21 @@ with contextlib.suppress(ImportError):
27
27
  return (
28
28
  config.enable_poke
29
29
  and (event.target_id == event.self_id)
30
- and user_in_game(user_id, group_id)
30
+ and user_in_game(bot.self_id, user_id, group_id)
31
31
  )
32
32
 
33
- @on_type(PokeNotifyEvent, rule=_rule_poke_1).handle()
34
- async def handle_poke_1(event: PokeNotifyEvent) -> None:
33
+ @on_notice(rule=_rule_poke_stop).handle()
34
+ async def handle_poke_stop(event: PokeNotifyEvent) -> None:
35
35
  InputStore.put(
36
+ msg=UniMessage.text("/stop"),
36
37
  user_id=str(event.user_id),
37
38
  group_id=str(event.group_id) if event.group_id is not None else None,
38
- msg=UniMessage.text("/stop"),
39
39
  )
40
40
 
41
41
  # 准备阶段戳一戳等效加入游戏
42
- async def _rule_poke_2(event: PokeNotifyEvent) -> bool:
42
+ async def _rule_poke_join(
43
+ bot: Bot, event: PokeNotifyEvent, target: MsgTarget
44
+ ) -> bool:
43
45
  if not config.enable_poke or event.group_id is None:
44
46
  return False
45
47
 
@@ -47,15 +49,19 @@ with contextlib.suppress(ImportError):
47
49
  group_id = str(event.group_id)
48
50
  return (
49
51
  (event.target_id == event.self_id)
50
- and not user_in_game(user_id, group_id)
51
- and group_id in starting_games
52
+ and not user_in_game(bot.self_id, user_id, group_id)
53
+ and any(target.verify(group) for group in Game.starting_games)
52
54
  )
53
55
 
54
- @on_type(PokeNotifyEvent, rule=_rule_poke_2).handle()
55
- async def handle_poke_2(bot: Bot, event: PokeNotifyEvent) -> None:
56
- user_id = str(event.user_id)
57
- group_id = str(event.group_id)
58
- players = starting_games[group_id]
56
+ @on_notice(rule=_rule_poke_join).handle()
57
+ async def handle_poke_join(
58
+ bot: Bot,
59
+ event: PokeNotifyEvent,
60
+ target: MsgTarget,
61
+ ) -> None:
62
+ user_id = event.get_user_id()
63
+ group_id = target.id
64
+ players = next(p for g, p in Game.starting_games.items() if target.verify(g))
59
65
 
60
66
  if user_id not in players:
61
67
  res: dict[str, str] = await bot.get_group_member_info(
@@ -63,7 +69,7 @@ with contextlib.suppress(ImportError):
63
69
  user_id=int(user_id),
64
70
  )
65
71
  players[user_id] = res.get("card") or res.get("nickname") or user_id
66
- await bot.send(event, MessageSegment.at(user_id) + "成功加入游戏")
72
+ await UniMessage.at(user_id).text("\n✅成功加入游戏").send(target, bot)
67
73
 
68
74
  def ob11_ext_enabled() -> bool:
69
75
  if not config.enable_poke: