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
@@ -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",
|
@@ -46,6 +32,7 @@ start_game = on_alconna(
|
|
46
32
|
if config.get_require_at("start")
|
47
33
|
else rule_not_in_game,
|
48
34
|
aliases={"狼人杀"},
|
35
|
+
use_cmd_start=config.use_cmd_start,
|
49
36
|
priority=config.matcher_priority.start,
|
50
37
|
)
|
51
38
|
player_data_file = get_plugin_data_file("players.json")
|
@@ -73,210 +60,11 @@ def load_players(target: Target) -> dict[str, str] | None:
|
|
73
60
|
return None
|
74
61
|
|
75
62
|
|
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
63
|
@start_game.handle()
|
276
64
|
async def handle_notice(target: MsgTarget) -> None:
|
277
65
|
if target.private:
|
278
66
|
await UniMessage("⚠️请在群组中创建新游戏").finish(reply_to=True)
|
279
|
-
if
|
67
|
+
if target in get_running_games():
|
280
68
|
await (
|
281
69
|
UniMessage.text("⚠️当前群组内有正在进行的游戏\n")
|
282
70
|
.text("无法开始新游戏")
|
@@ -291,8 +79,10 @@ async def handle_notice(target: MsgTarget) -> None:
|
|
291
79
|
" 玩家均加入后,游戏发起者请发送 “开始游戏”\n"
|
292
80
|
)
|
293
81
|
if poke_enabled():
|
294
|
-
msg.text(f"\n💫可使用戳一戳代替游戏交互中的 “{stop_command_prompt
|
295
|
-
|
82
|
+
msg.text(f"\n💫可使用戳一戳代替游戏交互中的 “{stop_command_prompt}” 命令\n")
|
83
|
+
|
84
|
+
prepare_timeout = GameBehavior.get().timeout.prepare
|
85
|
+
msg.text(f"\nℹ️游戏准备阶段限时{prepare_timeout / 60:.1f}分钟,超时将自动结束")
|
296
86
|
await solve_button(msg).send(reply_to=True, fallback=FallbackStrategy.ignore)
|
297
87
|
|
298
88
|
|
@@ -315,22 +105,20 @@ async def handle_restart(target: MsgTarget, state: T_State) -> None:
|
|
315
105
|
async def handle_start(
|
316
106
|
bot: Bot,
|
317
107
|
event: Event,
|
318
|
-
target: MsgTarget,
|
319
|
-
session: Uninfo,
|
320
|
-
interface: QryItrface,
|
321
108
|
state: T_State,
|
109
|
+
session: Uninfo,
|
110
|
+
target: MsgTarget,
|
322
111
|
) -> None:
|
323
112
|
players: dict[str, str] = state.get("players", {})
|
324
113
|
admin_id = event.get_user_id()
|
325
114
|
admin_name = extract_session_member_nick(session) or admin_id
|
326
115
|
players[admin_id] = admin_name
|
327
116
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
await UniMessage.text("⚠️游戏准备超时,已自动结束").finish()
|
117
|
+
with anyio.move_on_after(GameBehavior.get().timeout.prepare) as scope:
|
118
|
+
await PrepareGame(event, players).run()
|
119
|
+
if scope.cancelled_caught:
|
120
|
+
await UniMessage.text("⚠️游戏准备超时,已自动结束").finish(reply_to=True)
|
333
121
|
|
334
122
|
dump_players(target, players)
|
335
|
-
game = await Game.new(bot, target, set(players)
|
336
|
-
|
123
|
+
game = await Game.new(bot, target, set(players))
|
124
|
+
game.start()
|
@@ -12,21 +12,18 @@ terminate = on_alconna(
|
|
12
12
|
Alconna("中止游戏"),
|
13
13
|
rule=to_me() if config.get_require_at("terminate") else None,
|
14
14
|
permission=SUPERUSER,
|
15
|
+
use_cmd_start=config.use_cmd_start,
|
15
16
|
priority=config.matcher_priority.terminate,
|
16
17
|
)
|
17
18
|
|
18
19
|
|
19
20
|
async def running_game(target: MsgTarget) -> Game:
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
return terminate.skip()
|
24
|
-
|
25
|
-
|
26
|
-
RunningGame = Annotated[Game, Depends(running_game)]
|
21
|
+
if (game := get_running_games().get(target)) is None:
|
22
|
+
terminate.skip()
|
23
|
+
return game
|
27
24
|
|
28
25
|
|
29
26
|
@terminate.handle()
|
30
|
-
async def _(game:
|
27
|
+
async def _(game: Annotated[Game, Depends(running_game)]) -> None:
|
31
28
|
game.terminate()
|
32
29
|
await UniMessage.text("已中止当前群组的游戏进程").finish(reply_to=True)
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import dataclasses
|
2
|
+
import functools
|
2
3
|
from enum import Enum, auto
|
3
4
|
from typing import TYPE_CHECKING
|
4
5
|
|
@@ -26,12 +27,30 @@ class Role(int, Enum):
|
|
26
27
|
# 平民
|
27
28
|
CIVILIAN = 0
|
28
29
|
|
30
|
+
@functools.cached_property
|
31
|
+
def emoji(self) -> str:
|
32
|
+
from .constant import ROLE_EMOJI
|
33
|
+
|
34
|
+
return ROLE_EMOJI[self]
|
35
|
+
|
36
|
+
@functools.cached_property
|
37
|
+
def display(self) -> str:
|
38
|
+
from .constant import ROLE_NAME_CONV
|
39
|
+
|
40
|
+
return ROLE_NAME_CONV[self]
|
41
|
+
|
29
42
|
|
30
43
|
class RoleGroup(Enum):
|
31
44
|
WEREWOLF = auto()
|
32
45
|
GOODGUY = auto()
|
33
46
|
OTHERS = auto()
|
34
47
|
|
48
|
+
@functools.cached_property
|
49
|
+
def display(self) -> str:
|
50
|
+
from .constant import ROLE_NAME_CONV
|
51
|
+
|
52
|
+
return ROLE_NAME_CONV[self]
|
53
|
+
|
35
54
|
|
36
55
|
class KillReason(Enum):
|
37
56
|
WEREWOLF = auto()
|
@@ -9,9 +9,10 @@ import nonebot
|
|
9
9
|
from nonebot.adapters import Bot
|
10
10
|
from nonebot.utils import escape_tag
|
11
11
|
from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage
|
12
|
-
from nonebot_plugin_uninfo import Interface, SceneType
|
12
|
+
from nonebot_plugin_uninfo import Interface, SceneType, get_interface
|
13
13
|
|
14
|
-
from .
|
14
|
+
from .config import stop_command_prompt
|
15
|
+
from .constant import STOP_COMMAND
|
15
16
|
from .models import KillInfo, KillReason, Role, RoleGroup
|
16
17
|
from .utils import (
|
17
18
|
InputStore,
|
@@ -92,7 +93,7 @@ class NotifyProvider(ActionProvider[_P], Generic[_P]):
|
|
92
93
|
return message
|
93
94
|
|
94
95
|
async def notify(self) -> None:
|
95
|
-
msg = UniMessage.text(f"⚙️你的身份: {
|
96
|
+
msg = UniMessage.text(f"⚙️你的身份: {self.role.emoji}{self.role_name}\n")
|
96
97
|
await self.p.send(self.message(msg))
|
97
98
|
|
98
99
|
|
@@ -135,14 +136,18 @@ class Player:
|
|
135
136
|
@override
|
136
137
|
def __init_subclass__(cls) -> None:
|
137
138
|
super().__init_subclass__()
|
138
|
-
if hasattr(cls, "role") and hasattr(cls, "role_group"):
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
139
|
+
if not (hasattr(cls, "role") and hasattr(cls, "role_group")):
|
140
|
+
return
|
141
|
+
|
142
|
+
assert cls.role not in cls._player_class # noqa: S101
|
143
|
+
cls._player_class[cls.role] = cls
|
144
|
+
for k, v in {
|
145
|
+
"interact_provider": None,
|
146
|
+
"kill_provider": KillProvider,
|
147
|
+
"notify_provider": NotifyProvider,
|
148
|
+
}.items():
|
149
|
+
if not hasattr(cls, k):
|
150
|
+
setattr(cls, k, v)
|
146
151
|
|
147
152
|
@final
|
148
153
|
@classmethod
|
@@ -152,7 +157,6 @@ class Player:
|
|
152
157
|
bot: Bot,
|
153
158
|
game: "Game",
|
154
159
|
user_id: str,
|
155
|
-
interface: Interface,
|
156
160
|
) -> "Player":
|
157
161
|
if role not in cls._player_class:
|
158
162
|
raise ValueError(f"Unexpected role: {role!r}")
|
@@ -166,7 +170,9 @@ class Player:
|
|
166
170
|
extra=game.group.extra,
|
167
171
|
)
|
168
172
|
self = cls._player_class[role](bot, game, user)
|
169
|
-
|
173
|
+
|
174
|
+
if interface := get_interface(bot):
|
175
|
+
await self._fetch_member(interface)
|
170
176
|
return self
|
171
177
|
|
172
178
|
def __repr__(self) -> str:
|
@@ -187,7 +193,7 @@ class Player:
|
|
187
193
|
@final
|
188
194
|
@functools.cached_property
|
189
195
|
def role_name(self) -> str:
|
190
|
-
return
|
196
|
+
return self.role.display
|
191
197
|
|
192
198
|
@final
|
193
199
|
async def _fetch_member(self, interface: Interface) -> None:
|
@@ -278,14 +284,12 @@ class Player:
|
|
278
284
|
provider = self.interact_provider(self)
|
279
285
|
|
280
286
|
await provider.before()
|
281
|
-
|
282
287
|
timeout = self.interact_timeout
|
283
288
|
await self.send(f"✏️{self.role_name}交互开始,限时 {timeout / 60:.2f} 分钟")
|
284
289
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
except TimeoutError:
|
290
|
+
with anyio.move_on_after(timeout) as scope:
|
291
|
+
await provider.interact()
|
292
|
+
if scope.cancelled_caught:
|
289
293
|
logger.debug(f"{self.role_name}交互超时 (<y>{timeout}</y>s)")
|
290
294
|
await self.send(f"⚠️{self.role_name}交互超时")
|
291
295
|
|
@@ -304,25 +308,25 @@ class Player:
|
|
304
308
|
self.killed.set()
|
305
309
|
|
306
310
|
async def vote(self, players: "PlayerSet") -> "Player | None":
|
311
|
+
vote_timeout = self.game.behavior.timeout.vote
|
307
312
|
await self.send(
|
308
313
|
f"💫请选择需要投票的玩家:\n"
|
309
314
|
f"{players.show()}\n\n"
|
310
315
|
"🗳️发送编号选择玩家\n"
|
311
|
-
f"❌发送 “{stop_command_prompt
|
312
|
-
"限时
|
316
|
+
f"❌发送 “{stop_command_prompt}” 弃票\n\n"
|
317
|
+
f"限时{vote_timeout / 60:.1f}分钟,超时将视为弃票",
|
313
318
|
stop_btn_label="弃票",
|
314
319
|
select_players=players,
|
315
320
|
)
|
316
321
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
selected = None
|
322
|
+
selected = None
|
323
|
+
with anyio.move_on_after(vote_timeout) as scope:
|
324
|
+
selected = await self.select_player(
|
325
|
+
players,
|
326
|
+
on_stop="⚠️你选择了弃票",
|
327
|
+
on_index_error="⚠️输入错误: 请发送编号选择玩家",
|
328
|
+
)
|
329
|
+
if scope.cancelled_caught:
|
326
330
|
await self.send("⚠️投票超时,将视为弃票")
|
327
331
|
|
328
332
|
if selected is not None:
|
@@ -343,7 +347,7 @@ class Player:
|
|
343
347
|
) -> "Player | None":
|
344
348
|
on_stop = on_stop if on_stop is not None else "ℹ️你选择了取消,回合结束"
|
345
349
|
on_index_error = (
|
346
|
-
on_index_error or f"⚠️输入错误: 请发送玩家编号或 “{stop_command_prompt
|
350
|
+
on_index_error or f"⚠️输入错误: 请发送玩家编号或 “{stop_command_prompt}”"
|
347
351
|
)
|
348
352
|
selected = None
|
349
353
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from typing_extensions import override
|
2
2
|
|
3
|
-
from ..
|
3
|
+
from ..config import stop_command_prompt
|
4
4
|
from ..models import GameState, Role, RoleGroup
|
5
5
|
from ..player import InteractProvider, Player
|
6
6
|
|
@@ -13,7 +13,7 @@ class GuardInteractProvider(InteractProvider["Guard"]):
|
|
13
13
|
"💫请选择需要保护的玩家:\n"
|
14
14
|
f"{players.show()}\n\n"
|
15
15
|
"🛡️发送编号选择玩家\n"
|
16
|
-
f"❌发送 “{stop_command_prompt
|
16
|
+
f"❌发送 “{stop_command_prompt}” 结束回合",
|
17
17
|
stop_btn_label="结束回合",
|
18
18
|
select_players=players,
|
19
19
|
)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from typing_extensions import override
|
2
2
|
|
3
|
-
from ..
|
3
|
+
from ..config import stop_command_prompt
|
4
4
|
from ..models import Role, RoleGroup
|
5
5
|
from ..player import InteractProvider, Player
|
6
6
|
|
@@ -13,7 +13,7 @@ class ProphetInteractProvider(InteractProvider["Prophet"]):
|
|
13
13
|
"💫请选择需要查验身份的玩家:\n"
|
14
14
|
f"{players.show()}\n\n"
|
15
15
|
"🔮发送编号选择玩家\n"
|
16
|
-
f"❌发送 “{stop_command_prompt
|
16
|
+
f"❌发送 “{stop_command_prompt}” 结束回合(不查验身份)",
|
17
17
|
stop_btn_label="结束回合",
|
18
18
|
select_players=players,
|
19
19
|
)
|
@@ -2,7 +2,7 @@ from typing_extensions import override
|
|
2
2
|
|
3
3
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
4
4
|
|
5
|
-
from ..
|
5
|
+
from ..config import stop_command_prompt
|
6
6
|
from ..models import KillReason
|
7
7
|
from ..player import KillProvider, Player
|
8
8
|
|
@@ -39,7 +39,7 @@ class ShooterKillProvider(KillProvider["Player"]):
|
|
39
39
|
"💫请选择需要射杀的玩家:\n"
|
40
40
|
f"{players.show()}\n\n"
|
41
41
|
"🔫发送编号选择玩家\n"
|
42
|
-
f"❌发送 “{stop_command_prompt
|
42
|
+
f"❌发送 “{stop_command_prompt}” 取消技能",
|
43
43
|
stop_btn_label="取消技能",
|
44
44
|
select_players=players,
|
45
45
|
)
|
@@ -5,23 +5,23 @@ from typing_extensions import override
|
|
5
5
|
import anyio
|
6
6
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
7
7
|
|
8
|
-
from ..
|
8
|
+
from ..config import stop_command_prompt
|
9
|
+
from ..constant import STOP_COMMAND
|
9
10
|
from ..models import Role, RoleGroup
|
10
11
|
from ..player import InteractProvider, NotifyProvider, Player
|
11
|
-
from ..utils import
|
12
|
+
from ..utils import as_player_set, check_index
|
12
13
|
|
13
14
|
if TYPE_CHECKING:
|
14
15
|
from ..player_set import PlayerSet
|
15
16
|
|
16
17
|
|
17
18
|
class WerewolfInteractProvider(InteractProvider["Werewolf"]):
|
18
|
-
stream: ObjectStream[str | UniMessage]
|
19
|
-
|
20
19
|
@override
|
21
20
|
async def before(self) -> None:
|
22
21
|
self.game.state.werewolf_start()
|
23
22
|
|
24
23
|
async def handle_interact(self, players: "PlayerSet") -> None:
|
24
|
+
stream = self.stream[0]
|
25
25
|
self.selected = None
|
26
26
|
|
27
27
|
while True:
|
@@ -32,30 +32,31 @@ class WerewolfInteractProvider(InteractProvider["Werewolf"]):
|
|
32
32
|
self.selected = players[index - 1]
|
33
33
|
msg = f"当前选择玩家: {self.selected.name}"
|
34
34
|
await self.p.send(
|
35
|
-
f"🎯{msg}\n发送 “{stop_command_prompt
|
35
|
+
f"🎯{msg}\n发送 “{stop_command_prompt}” 结束回合",
|
36
36
|
stop_btn_label="结束回合",
|
37
37
|
select_players=players,
|
38
38
|
)
|
39
|
-
await
|
39
|
+
await stream.send(f"📝队友 {self.p.name} {msg}")
|
40
40
|
if text == STOP_COMMAND:
|
41
41
|
if self.selected is not None:
|
42
42
|
await self.p.send("✅你已结束当前回合")
|
43
|
-
await
|
44
|
-
|
43
|
+
await stream.send(f"📝队友 {self.p.name} 结束当前回合")
|
44
|
+
stream.close()
|
45
45
|
return
|
46
46
|
await self.p.send(
|
47
47
|
"⚠️当前未选择玩家,无法结束回合",
|
48
48
|
select_players=players,
|
49
49
|
)
|
50
50
|
else:
|
51
|
-
await
|
51
|
+
await stream.send(
|
52
52
|
UniMessage.text(f"💬队友 {self.p.name}:\n") + input_msg
|
53
53
|
)
|
54
54
|
|
55
55
|
async def handle_broadcast(self, partners: "PlayerSet") -> None:
|
56
|
-
|
56
|
+
stream = self.stream[1]
|
57
|
+
while True:
|
57
58
|
try:
|
58
|
-
message = await
|
59
|
+
message = await stream.receive()
|
59
60
|
except anyio.EndOfStream:
|
60
61
|
return
|
61
62
|
|
@@ -77,19 +78,17 @@ class WerewolfInteractProvider(InteractProvider["Werewolf"]):
|
|
77
78
|
msg.text("💫请选择今晚的目标:\n")
|
78
79
|
.text(players.show())
|
79
80
|
.text("\n\n🔪发送编号选择玩家")
|
80
|
-
.text(f"\n❌发送 “{stop_command_prompt
|
81
|
+
.text(f"\n❌发送 “{stop_command_prompt}” 结束回合")
|
81
82
|
.text("\n\n⚠️意见未统一将空刀"),
|
82
83
|
select_players=players,
|
83
84
|
)
|
84
85
|
|
85
|
-
self.stream =
|
86
|
+
self.stream = anyio.create_memory_object_stream[str | UniMessage](8)
|
87
|
+
send, recv = self.stream
|
86
88
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
tg.start_soon(self.handle_broadcast, partners)
|
91
|
-
finally:
|
92
|
-
del self.stream
|
89
|
+
async with send, recv, anyio.create_task_group() as tg:
|
90
|
+
tg.start_soon(self.handle_interact, players)
|
91
|
+
tg.start_soon(self.handle_broadcast, partners)
|
93
92
|
|
94
93
|
async def finalize(self) -> None:
|
95
94
|
w = self.game.players.alive().select(RoleGroup.WEREWOLF)
|