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
@@ -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
@@ -118,6 +118,7 @@ alc = Alconna(
118
118
  edit_behavior = on_alconna(
119
119
  alc,
120
120
  permission=SUPERUSER,
121
+ use_cmd_start=config.use_cmd_start,
121
122
  priority=config.matcher_priority.behavior,
122
123
  )
123
124
 
@@ -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(
@@ -73,6 +72,7 @@ alc = Alconna(
73
72
  edit_preset = on_alconna(
74
73
  alc,
75
74
  permission=SUPERUSER,
75
+ use_cmd_start=config.use_cmd_start,
76
76
  priority=config.matcher_priority.preset,
77
77
  )
78
78
 
@@ -82,7 +82,7 @@ async def finish(text: str) -> NoReturn:
82
82
 
83
83
 
84
84
  def display_roles(roles: list[Role]) -> str:
85
- return ", ".join(map(ROLE_NAME_CONV.__getitem__, roles))
85
+ return ", ".join(role.display for role in roles)
86
86
 
87
87
 
88
88
  @edit_preset.assign("role")
@@ -164,7 +164,7 @@ async def assign_werewolf(state: T_State) -> None:
164
164
 
165
165
  data.werewolf_priority = result
166
166
  data.save()
167
- await finish("设置成功: " + display_roles(result))
167
+ await finish(f"设置成功: {display_roles(result)}")
168
168
 
169
169
 
170
170
  @edit_preset.assign("priesthood")
@@ -213,7 +213,7 @@ async def assign_priesthood(state: T_State) -> None:
213
213
 
214
214
  data.priesthood_proirity = result
215
215
  data.save()
216
- await finish("设置成功: " + display_roles(result))
216
+ await finish(f"设置成功: {display_roles(result)}")
217
217
 
218
218
 
219
219
  @edit_preset.assign("jester")
@@ -248,15 +248,17 @@ async def reset_preset() -> None:
248
248
 
249
249
  @edit_preset.handle()
250
250
  async def handle_default() -> None:
251
- lines = ["当前游戏预设:", ""]
252
251
  data = PresetData.load()
253
252
 
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%}")
253
+ lines = ["当前游戏预设:\n"]
254
+ lines.extend(
255
+ f"{total} 人: 狼人x{w}, 神职x{p}, 平民x{c}"
256
+ for total, (w, p, c) in data.role_preset.items()
257
+ )
258
+ lines.append(
259
+ f"\n狼人优先级: {display_roles(data.werewolf_priority)}"
260
+ f"\n神职优先级: {display_roles(data.priesthood_proirity)}"
261
+ f"\n小丑概率: {data.jester_probability:.0%}"
262
+ )
261
263
 
262
264
  await finish("\n".join(lines))
@@ -25,8 +25,8 @@ stopcmd = on_alconna(
25
25
  Alconna(config.get_stop_command()[0]),
26
26
  rule=rule_in_game,
27
27
  block=True,
28
- use_cmd_start=True,
29
28
  aliases=set(aliases) if (aliases := config.get_stop_command()[1:]) else None,
29
+ use_cmd_start=config.use_cmd_start,
30
30
  priority=config.matcher_priority.stop,
31
31
  )
32
32
 
@@ -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