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.
- nonebot_plugin_werewolf/__init__.py +1 -1
- nonebot_plugin_werewolf/config.py +51 -37
- nonebot_plugin_werewolf/constant.py +0 -17
- nonebot_plugin_werewolf/dead_channel.py +79 -0
- nonebot_plugin_werewolf/game.py +46 -115
- nonebot_plugin_werewolf/matchers/_prepare_game.py +223 -0
- nonebot_plugin_werewolf/matchers/depends.py +2 -2
- nonebot_plugin_werewolf/matchers/edit_behavior.py +1 -0
- nonebot_plugin_werewolf/matchers/edit_preset.py +14 -12
- nonebot_plugin_werewolf/matchers/message_in_game.py +1 -1
- nonebot_plugin_werewolf/matchers/poke/chronocat_poke.py +3 -3
- nonebot_plugin_werewolf/matchers/poke/ob11_poke.py +3 -3
- nonebot_plugin_werewolf/matchers/start_game.py +20 -232
- nonebot_plugin_werewolf/matchers/superuser_ops.py +5 -8
- nonebot_plugin_werewolf/models.py +19 -0
- nonebot_plugin_werewolf/player.py +35 -31
- nonebot_plugin_werewolf/players/guard.py +2 -2
- nonebot_plugin_werewolf/players/prophet.py +2 -2
- nonebot_plugin_werewolf/players/shooter.py +2 -2
- nonebot_plugin_werewolf/players/werewolf.py +18 -19
- nonebot_plugin_werewolf/players/witch.py +4 -4
- nonebot_plugin_werewolf/utils.py +18 -56
- {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.13.dist-info}/METADATA +71 -41
- nonebot_plugin_werewolf-1.1.13.dist-info/RECORD +37 -0
- {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.13.dist-info}/WHEEL +1 -1
- nonebot_plugin_werewolf-1.1.11.dist-info/RECORD +0 -35
- {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.13.dist-info}/licenses/LICENSE +0 -0
- {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
|
@@ -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(
|
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("设置成功:
|
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("设置成功:
|
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
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
lines.append(
|
260
|
-
|
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
|
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 =
|
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
|
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 =
|
63
|
+
players = preparing_games[target].players
|
64
64
|
|
65
65
|
if event.group_id is None or user_id in players:
|
66
66
|
return
|