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.
- nonebot_plugin_werewolf/__init__.py +1 -1
- nonebot_plugin_werewolf/config.py +36 -35
- 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_preset.py +13 -12
- 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 +19 -232
- nonebot_plugin_werewolf/matchers/superuser_ops.py +4 -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.12.dist-info}/METADATA +55 -34
- nonebot_plugin_werewolf-1.1.12.dist-info/RECORD +37 -0
- {nonebot_plugin_werewolf-1.1.11.dist-info → nonebot_plugin_werewolf-1.1.12.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.12.dist-info}/licenses/LICENSE +0 -0
- {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(
|
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("设置成功:
|
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("设置成功:
|
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
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
lines.append(
|
260
|
-
|
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
|
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
|
@@ -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.
|
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
|
17
|
+
from nonebot_plugin_uninfo import Uninfo
|
29
18
|
|
30
|
-
from ..config import GameBehavior,
|
31
|
-
from ..
|
32
|
-
from ..
|
33
|
-
from
|
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
|
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
|
295
|
-
|
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
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
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)
|
336
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
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:
|
26
|
+
async def _(game: Annotated[Game, Depends(running_game)]) -> None:
|
31
27
|
game.terminate()
|
32
28
|
await UniMessage.text("已中止当前群组的游戏进程").finish(reply_to=True)
|