nonebot-plugin-werewolf 1.1.7__py3-none-any.whl → 1.1.8__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 +73 -15
- nonebot_plugin_werewolf/constant.py +54 -46
- nonebot_plugin_werewolf/exception.py +2 -4
- nonebot_plugin_werewolf/game.py +154 -136
- nonebot_plugin_werewolf/matchers/__init__.py +1 -0
- nonebot_plugin_werewolf/matchers/depends.py +4 -4
- nonebot_plugin_werewolf/matchers/edit_behavior.py +205 -0
- nonebot_plugin_werewolf/matchers/edit_preset.py +11 -11
- nonebot_plugin_werewolf/matchers/message_in_game.py +3 -1
- nonebot_plugin_werewolf/matchers/poke/chronocat_poke.py +8 -5
- nonebot_plugin_werewolf/matchers/poke/ob11_poke.py +3 -3
- nonebot_plugin_werewolf/matchers/start_game.py +213 -175
- nonebot_plugin_werewolf/matchers/superuser_ops.py +3 -3
- nonebot_plugin_werewolf/models.py +31 -19
- nonebot_plugin_werewolf/player_set.py +10 -8
- nonebot_plugin_werewolf/players/__init__.py +1 -1
- nonebot_plugin_werewolf/players/can_shoot.py +15 -15
- nonebot_plugin_werewolf/players/civilian.py +1 -1
- nonebot_plugin_werewolf/players/guard.py +16 -14
- nonebot_plugin_werewolf/players/hunter.py +1 -1
- nonebot_plugin_werewolf/players/idiot.py +3 -3
- nonebot_plugin_werewolf/players/{joker.py → jester.py} +4 -5
- nonebot_plugin_werewolf/players/player.py +90 -28
- nonebot_plugin_werewolf/players/prophet.py +11 -10
- nonebot_plugin_werewolf/players/werewolf.py +46 -11
- nonebot_plugin_werewolf/players/witch.py +29 -12
- nonebot_plugin_werewolf/players/wolfking.py +1 -1
- nonebot_plugin_werewolf/utils.py +105 -6
- {nonebot_plugin_werewolf-1.1.7.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/METADATA +23 -20
- nonebot_plugin_werewolf-1.1.8.dist-info/RECORD +35 -0
- {nonebot_plugin_werewolf-1.1.7.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/WHEEL +1 -1
- nonebot_plugin_werewolf-1.1.7.dist-info/RECORD +0 -34
- {nonebot_plugin_werewolf-1.1.7.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/LICENSE +0 -0
- {nonebot_plugin_werewolf-1.1.7.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/top_level.txt +0 -0
@@ -1,12 +1,14 @@
|
|
1
1
|
import json
|
2
2
|
import re
|
3
|
+
from dataclasses import dataclass
|
4
|
+
from typing import TYPE_CHECKING
|
3
5
|
|
4
6
|
import anyio
|
5
7
|
import nonebot
|
6
8
|
import nonebot_plugin_waiter as waiter
|
7
9
|
from nonebot.adapters import Bot, Event
|
8
10
|
from nonebot.internal.matcher import current_bot
|
9
|
-
from nonebot.permission import
|
11
|
+
from nonebot.permission import SuperUser
|
10
12
|
from nonebot.rule import Rule, to_me
|
11
13
|
from nonebot.typing import T_State
|
12
14
|
from nonebot.utils import escape_tag
|
@@ -24,17 +26,20 @@ from nonebot_plugin_alconna import (
|
|
24
26
|
from nonebot_plugin_localstore import get_plugin_data_file
|
25
27
|
from nonebot_plugin_uninfo import QryItrface, Uninfo
|
26
28
|
|
27
|
-
from ..config import
|
28
|
-
from ..constant import
|
29
|
-
from ..game import Game
|
30
|
-
from ..utils import ObjectStream, extract_session_member_nick
|
29
|
+
from ..config import GameBehavior, PresetData
|
30
|
+
from ..constant import stop_command_prompt
|
31
|
+
from ..game import Game, get_running_games, get_starting_games
|
32
|
+
from ..utils import ObjectStream, SendHandler, extract_session_member_nick
|
31
33
|
from .depends import rule_not_in_game
|
32
34
|
from .poke import poke_enabled
|
33
35
|
|
36
|
+
if TYPE_CHECKING:
|
37
|
+
from collections.abc import Awaitable, Callable
|
38
|
+
|
34
39
|
start_game = on_alconna(
|
35
40
|
Alconna(
|
36
41
|
"werewolf",
|
37
|
-
Option("restart|--restart|重开", dest="restart"),
|
42
|
+
Option("restart|-r|--restart|重开", dest="restart"),
|
38
43
|
),
|
39
44
|
rule=to_me() & rule_not_in_game,
|
40
45
|
aliases={"狼人杀"},
|
@@ -59,200 +64,233 @@ def dump_players(target: Target, players: dict[str, str]) -> None:
|
|
59
64
|
|
60
65
|
|
61
66
|
def load_players(target: Target) -> dict[str, str] | None:
|
62
|
-
|
63
|
-
|
64
|
-
for item in data:
|
67
|
+
for item in json.loads(player_data_file.read_text(encoding="utf-8")):
|
65
68
|
if Target.load(item["target"]).verify(target):
|
66
69
|
return item["players"]
|
67
70
|
return None
|
68
71
|
|
69
72
|
|
70
73
|
def solve_button(msg: UniMessage) -> UniMessage:
|
71
|
-
|
72
|
-
|
73
|
-
*[
|
74
|
-
Button("input", i, text=i)
|
75
|
-
for i in ["加入游戏", "退出游戏", "当前玩家", "开始游戏", "结束游戏"]
|
76
|
-
]
|
77
|
-
)
|
78
|
-
return msg
|
79
|
-
|
74
|
+
def btn(text: str) -> Button:
|
75
|
+
return Button("input", label=text, text=text)
|
80
76
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
) -> None:
|
86
|
-
@Rule
|
87
|
-
async def same_group(target: MsgTarget) -> bool:
|
88
|
-
return group.verify(target)
|
89
|
-
|
90
|
-
@waiter.waiter(
|
91
|
-
waits=[event_type],
|
92
|
-
keep_session=False,
|
93
|
-
rule=same_group & rule_not_in_game,
|
77
|
+
return (
|
78
|
+
msg.keyboard(btn("当前玩家"))
|
79
|
+
.keyboard(btn("加入游戏"), btn("退出游戏"))
|
80
|
+
.keyboard(btn("开始游戏"), btn("结束游戏"))
|
94
81
|
)
|
95
|
-
def wait(event: Event, msg: UniMsg, session: Uninfo) -> tuple[Event, str, str]:
|
96
|
-
text = msg.extract_plain_text().strip()
|
97
|
-
name = extract_session_member_nick(session) or event.get_user_id()
|
98
|
-
return (event, text, re.sub(r"[\u2066-\u2069]", "", name))
|
99
|
-
|
100
|
-
async for event, text, name in wait(default=(None, "", "")):
|
101
|
-
if event is None:
|
102
|
-
continue
|
103
|
-
await stream.send((event, text, name))
|
104
|
-
|
105
|
-
|
106
|
-
async def _prepare_handle(
|
107
|
-
stream: ObjectStream[tuple[Event, str, str]],
|
108
|
-
players: dict[str, str],
|
109
|
-
admin_id: str,
|
110
|
-
) -> None:
|
111
|
-
logger = nonebot.logger.opt(colors=True)
|
112
82
|
|
113
|
-
async def send(msg: str, /, *, button: bool = True) -> None:
|
114
|
-
message = UniMessage.text(msg)
|
115
|
-
if button:
|
116
|
-
message = solve_button(message)
|
117
83
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
84
|
+
class PrepareGame:
|
85
|
+
@dataclass
|
86
|
+
class _Current:
|
87
|
+
id: str
|
88
|
+
name: str
|
89
|
+
colored: str
|
90
|
+
is_admin: bool
|
91
|
+
is_super_user: bool
|
92
|
+
|
93
|
+
class _SendHandler(SendHandler):
|
94
|
+
def __init__(self) -> None:
|
95
|
+
self.reply_to = True
|
96
|
+
|
97
|
+
def solve_msg(self, msg: UniMessage) -> UniMessage:
|
98
|
+
return solve_button(msg)
|
99
|
+
|
100
|
+
async def send_finished(self) -> None:
|
101
|
+
btn_start = Button("input", label="发起游戏", text="werewolf")
|
102
|
+
btn_restart = Button("input", label="重开上次游戏", text="werewolf restart")
|
103
|
+
msg = (
|
104
|
+
UniMessage.text("ℹ️已结束当前游戏")
|
105
|
+
.keyboard(btn_start)
|
106
|
+
.keyboard(btn_restart)
|
107
|
+
)
|
108
|
+
async with anyio.create_task_group() as tg:
|
109
|
+
tg.start_soon(self._edit)
|
110
|
+
tg.start_soon(self._send, msg)
|
111
|
+
|
112
|
+
def __init__(self, event: Event, players: dict[str, str]) -> None:
|
113
|
+
self.event = event
|
114
|
+
self.admin_id = event.get_user_id()
|
115
|
+
self.group = UniMessage.get_target(event)
|
116
|
+
self.stream = ObjectStream[tuple[Event, str, str]](16)
|
117
|
+
self.players = players
|
118
|
+
self.send_handler = self._SendHandler()
|
119
|
+
self.logger = nonebot.logger.opt(colors=True)
|
120
|
+
self.shoud_start_game = False
|
121
|
+
self._msg_handler: dict[str, Callable[[], Awaitable[bool | None]]] = {
|
122
|
+
"开始游戏": self._handle_start,
|
123
|
+
"结束游戏": self._handle_end,
|
124
|
+
"加入游戏": self._handle_join,
|
125
|
+
"退出游戏": self._handle_quit,
|
126
|
+
"当前玩家": self._handle_list,
|
127
|
+
}
|
128
|
+
get_starting_games()[self.group] = self.players
|
129
|
+
|
130
|
+
async def run(self) -> None:
|
131
|
+
try:
|
132
|
+
async with anyio.create_task_group() as tg:
|
133
|
+
self.task_group = tg
|
134
|
+
tg.start_soon(self._wait_cancel)
|
135
|
+
tg.start_soon(self._receive)
|
136
|
+
tg.start_soon(self._handle)
|
137
|
+
except Exception as err:
|
138
|
+
await UniMessage(f"狼人杀准备阶段出现未知错误: {err!r}").finish()
|
139
|
+
|
140
|
+
del get_starting_games()[self.group]
|
141
|
+
if not self.shoud_start_game:
|
142
|
+
await start_game.finish()
|
143
|
+
|
144
|
+
async def _wait_cancel(self) -> None:
|
145
|
+
await self.stream.wait_closed()
|
146
|
+
self.task_group.cancel_scope.cancel()
|
147
|
+
|
148
|
+
async def _receive(self) -> None:
|
149
|
+
async def same_group(target: MsgTarget) -> bool:
|
150
|
+
return self.group.verify(target)
|
151
|
+
|
152
|
+
@waiter.waiter(
|
153
|
+
waits=[self.event.get_type()],
|
154
|
+
keep_session=False,
|
155
|
+
rule=Rule(same_group) & rule_not_in_game,
|
122
156
|
)
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
157
|
+
def wait(event: Event, msg: UniMsg, session: Uninfo) -> tuple[Event, str, str]:
|
158
|
+
text = msg.extract_plain_text().strip()
|
159
|
+
name = extract_session_member_nick(session) or event.get_user_id()
|
160
|
+
return (event, text, re.sub(r"[\u2066-\u2069]", "", name))
|
161
|
+
|
162
|
+
async for event, text, name in wait(default=(None, "", "")):
|
163
|
+
if event is not None:
|
164
|
+
await self.stream.send((event, text, name))
|
165
|
+
|
166
|
+
async def _send(self, msg: str | UniMessage) -> None:
|
167
|
+
await self.send_handler.send(msg)
|
168
|
+
|
169
|
+
async def _send_finished(self) -> None:
|
170
|
+
await self.send_handler.send_finished()
|
171
|
+
|
172
|
+
async def _handle_start(self) -> bool:
|
173
|
+
if not self.current.is_admin:
|
174
|
+
await self._send("⚠️只有游戏发起者可以开始游戏")
|
175
|
+
return False
|
176
|
+
|
177
|
+
player_num = len(self.players)
|
178
|
+
role_preset = PresetData.get().role_preset
|
179
|
+
if player_num < min(role_preset):
|
180
|
+
await self._send(
|
181
|
+
f"⚠️游戏至少需要 {min(role_preset)} 人, 当前已有 {player_num} 人"
|
182
|
+
)
|
183
|
+
elif player_num > max(role_preset):
|
184
|
+
await self._send(
|
185
|
+
f"⚠️游戏最多需要 {max(role_preset)} 人, 当前已有 {player_num} 人"
|
186
|
+
)
|
187
|
+
elif player_num not in role_preset:
|
188
|
+
await self._send(
|
189
|
+
f"⚠️不存在总人数为 {player_num} 的预设, 无法开始游戏\n"
|
190
|
+
f"可用的预设总人数: {', '.join(map(str, role_preset))}"
|
191
|
+
)
|
192
|
+
else:
|
193
|
+
await self._send("✏️游戏即将开始...")
|
194
|
+
self.logger.info(f"游戏发起者 {self.current.colored} 开始游戏")
|
195
|
+
self.stream.close()
|
196
|
+
self.shoud_start_game = True
|
197
|
+
return True
|
198
|
+
|
199
|
+
return False
|
200
|
+
|
201
|
+
async def _handle_end(self) -> bool:
|
202
|
+
if self.current.is_admin or self.current.is_super_user:
|
203
|
+
prefix = "游戏发起者" if self.current.is_admin else "超级用户"
|
204
|
+
self.logger.info(f"{prefix} {self.current.colored} 结束游戏")
|
205
|
+
await self._send_finished()
|
206
|
+
self.stream.close()
|
207
|
+
return True
|
208
|
+
|
209
|
+
await self._send("⚠️只有游戏发起者或超级用户可以结束游戏")
|
210
|
+
return False
|
211
|
+
|
212
|
+
async def _handle_join(self) -> None:
|
213
|
+
if self.current.is_admin:
|
214
|
+
await self._send("⚠️只有游戏发起者可以开始游戏")
|
215
|
+
return
|
216
|
+
|
217
|
+
if self.current.id not in self.players:
|
218
|
+
self.players[self.current.id] = self.current.name
|
219
|
+
self.logger.info(f"玩家 {self.current.colored} 加入游戏")
|
220
|
+
await self._send("✅成功加入游戏")
|
221
|
+
else:
|
222
|
+
await self._send("ℹ️你已经加入游戏了")
|
223
|
+
|
224
|
+
async def _handle_quit(self) -> None:
|
225
|
+
if self.current.is_admin:
|
226
|
+
await self._send("ℹ️游戏发起者无法退出游戏")
|
227
|
+
return
|
228
|
+
|
229
|
+
if self.current.id in self.players:
|
230
|
+
del self.players[self.current.id]
|
231
|
+
self.logger.info(f"玩家 {self.current.colored} 退出游戏")
|
232
|
+
await self._send("✅成功退出游戏")
|
233
|
+
else:
|
234
|
+
await self._send("ℹ️你还没有加入游戏")
|
235
|
+
|
236
|
+
async def _handle_list(self) -> None:
|
237
|
+
lines = (
|
238
|
+
f"{idx}. {self.players[user_id]}"
|
239
|
+
for idx, user_id in enumerate(self.players, 1)
|
240
|
+
)
|
241
|
+
await self._send("✨当前玩家:\n" + "\n".join(lines))
|
242
|
+
|
243
|
+
async def _handle(self) -> None:
|
244
|
+
bot = current_bot.get()
|
245
|
+
superuser = SuperUser()
|
246
|
+
|
247
|
+
while not self.stream.closed:
|
248
|
+
event, text, name = await self.stream.recv()
|
249
|
+
user_id = event.get_user_id()
|
250
|
+
colored = f"<y>{escape_tag(name)}</y>(<c>{escape_tag(user_id)}</c>)"
|
251
|
+
self.current = self._Current(
|
252
|
+
id=user_id,
|
253
|
+
name=name,
|
254
|
+
colored=colored,
|
255
|
+
is_admin=user_id == self.admin_id,
|
256
|
+
is_super_user=await superuser(bot, event),
|
257
|
+
)
|
258
|
+
self.send_handler.update(event)
|
259
|
+
|
260
|
+
# 更新用户名
|
261
|
+
# 当用户通过 chronoca:poke 加入游戏时, 插件无法获取用户名, 原字典值为用户ID
|
262
|
+
if user_id in self.players and self.players.get(user_id) != name:
|
263
|
+
self.logger.debug(f"更新玩家显示名称: {self.current.colored}")
|
264
|
+
self.players[user_id] = name
|
265
|
+
|
266
|
+
handler = self._msg_handler.get(text)
|
267
|
+
if handler is not None and await handler():
|
165
268
|
return
|
166
269
|
|
167
|
-
case ("结束游戏", False):
|
168
|
-
if await SUPERUSER(current_bot.get(), event):
|
169
|
-
logger.info(f"超级用户 {colored} 结束游戏")
|
170
|
-
await send("ℹ️已结束当前游戏", button=False)
|
171
|
-
stream.close()
|
172
|
-
return
|
173
|
-
await send("⚠️只有游戏发起者或超级用户可以结束游戏")
|
174
|
-
|
175
|
-
case ("加入游戏", True):
|
176
|
-
await send("ℹ️游戏发起者已经加入游戏了")
|
177
|
-
|
178
|
-
case ("加入游戏", False):
|
179
|
-
if user_id not in players:
|
180
|
-
players[user_id] = name
|
181
|
-
logger.info(f"玩家 {colored} 加入游戏")
|
182
|
-
await send("✅成功加入游戏")
|
183
|
-
else:
|
184
|
-
await send("ℹ️你已经加入游戏了")
|
185
|
-
|
186
|
-
case ("退出游戏", True):
|
187
|
-
await send("ℹ️游戏发起者无法退出游戏")
|
188
|
-
|
189
|
-
case ("退出游戏", False):
|
190
|
-
if user_id in players:
|
191
|
-
del players[user_id]
|
192
|
-
logger.info(f"玩家 {colored} 退出游戏")
|
193
|
-
await send("✅成功退出游戏")
|
194
|
-
else:
|
195
|
-
await send("ℹ️你还没有加入游戏")
|
196
|
-
|
197
|
-
case ("当前玩家", _):
|
198
|
-
await send(
|
199
|
-
"✨当前玩家:\n"
|
200
|
-
+ "\n".join(
|
201
|
-
f"{idx}. {players[user_id]}"
|
202
|
-
for idx, user_id in enumerate(players, 1)
|
203
|
-
)
|
204
|
-
)
|
205
|
-
|
206
|
-
|
207
|
-
async def prepare_game(event: Event, players: dict[str, str]) -> None:
|
208
|
-
admin_id = event.get_user_id()
|
209
|
-
group = UniMessage.get_target(event)
|
210
|
-
Game.starting_games[group] = players
|
211
|
-
|
212
|
-
stream = ObjectStream[tuple[Event, str, str]](16)
|
213
|
-
|
214
|
-
async def _handle_cancel() -> None:
|
215
|
-
await stream.wait_closed()
|
216
|
-
tg.cancel_scope.cancel()
|
217
|
-
|
218
|
-
try:
|
219
|
-
async with anyio.create_task_group() as tg:
|
220
|
-
tg.start_soon(_handle_cancel)
|
221
|
-
tg.start_soon(_prepare_receive, stream, event.get_type(), group)
|
222
|
-
tg.start_soon(_prepare_handle, stream, players, admin_id)
|
223
|
-
except Exception as err:
|
224
|
-
await UniMessage(f"狼人杀准备阶段出现未知错误: {err!r}").send()
|
225
|
-
|
226
|
-
del Game.starting_games[group]
|
227
|
-
if players.pop("#$start_game$#", None) != admin_id:
|
228
|
-
await start_game.finish()
|
229
|
-
|
230
270
|
|
231
271
|
@start_game.handle()
|
232
|
-
async def handle_notice(target: MsgTarget
|
272
|
+
async def handle_notice(target: MsgTarget) -> None:
|
233
273
|
if target.private:
|
234
274
|
await UniMessage("⚠️请在群组中创建新游戏").finish(reply_to=True)
|
235
|
-
if any(target.verify(
|
275
|
+
if any(target.verify(game.group) for game in get_running_games()):
|
236
276
|
await (
|
237
277
|
UniMessage.text("⚠️当前群组内有正在进行的游戏\n")
|
238
278
|
.text("无法开始新游戏")
|
239
279
|
.finish(reply_to=True)
|
240
280
|
)
|
241
281
|
|
242
|
-
msg = (
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
282
|
+
msg = UniMessage.text(
|
283
|
+
"🎉成功创建游戏\n\n"
|
284
|
+
" 玩家请发送 “加入游戏”、“退出游戏”\n"
|
285
|
+
" 玩家发送 “当前玩家” 可查看玩家列表\n"
|
286
|
+
" 游戏发起者发送 “结束游戏” 可结束当前游戏\n"
|
287
|
+
" 玩家均加入后,游戏发起者请发送 “开始游戏”\n"
|
248
288
|
)
|
249
289
|
if poke_enabled():
|
250
|
-
msg.text(f"\n💫可使用戳一戳代替游戏交互中的 “{
|
290
|
+
msg.text(f"\n💫可使用戳一戳代替游戏交互中的 “{stop_command_prompt()}” 命令\n")
|
251
291
|
msg.text("\nℹ️游戏准备阶段限时5分钟,超时将自动结束")
|
252
292
|
await solve_button(msg).send(reply_to=True, fallback=FallbackStrategy.ignore)
|
253
293
|
|
254
|
-
state["players"] = {}
|
255
|
-
|
256
294
|
|
257
295
|
@start_game.assign("restart")
|
258
296
|
async def handle_restart(target: MsgTarget, state: T_State) -> None:
|
@@ -278,14 +316,14 @@ async def handle_start(
|
|
278
316
|
interface: QryItrface,
|
279
317
|
state: T_State,
|
280
318
|
) -> None:
|
281
|
-
players: dict[str, str] = state
|
319
|
+
players: dict[str, str] = state.get("players", {})
|
282
320
|
admin_id = event.get_user_id()
|
283
321
|
admin_name = extract_session_member_nick(session) or admin_id
|
284
322
|
players[admin_id] = admin_name
|
285
323
|
|
286
324
|
try:
|
287
|
-
with anyio.fail_after(
|
288
|
-
await
|
325
|
+
with anyio.fail_after(GameBehavior.get().timeout.prepare):
|
326
|
+
await PrepareGame(event, players).run()
|
289
327
|
except TimeoutError:
|
290
328
|
await UniMessage.text("⚠️游戏准备超时,已自动结束").finish()
|
291
329
|
|
@@ -2,11 +2,11 @@ from nonebot.permission import SUPERUSER
|
|
2
2
|
from nonebot.rule import to_me
|
3
3
|
from nonebot_plugin_alconna import Alconna, MsgTarget, UniMessage, on_alconna
|
4
4
|
|
5
|
-
from ..game import
|
5
|
+
from ..game import get_running_games
|
6
6
|
|
7
7
|
|
8
8
|
def rule_game_running(target: MsgTarget) -> bool:
|
9
|
-
return any(target.verify(g.group) for g in
|
9
|
+
return any(target.verify(g.group) for g in get_running_games())
|
10
10
|
|
11
11
|
|
12
12
|
force_stop = on_alconna(
|
@@ -19,6 +19,6 @@ force_stop = on_alconna(
|
|
19
19
|
|
20
20
|
@force_stop.handle()
|
21
21
|
async def _(target: MsgTarget) -> None:
|
22
|
-
game = next(g for g in
|
22
|
+
game = next(g for g in get_running_games() if target.verify(g.group))
|
23
23
|
game.terminate()
|
24
24
|
await UniMessage.text("已中止当前群组的游戏进程").finish(reply_to=True)
|
@@ -2,52 +2,63 @@ import dataclasses
|
|
2
2
|
from enum import Enum, auto
|
3
3
|
from typing import TYPE_CHECKING
|
4
4
|
|
5
|
+
import anyio
|
6
|
+
|
5
7
|
if TYPE_CHECKING:
|
6
8
|
from .players import Player
|
7
9
|
|
8
10
|
|
9
11
|
class Role(int, Enum):
|
10
12
|
# 狼人
|
11
|
-
|
12
|
-
|
13
|
+
WEREWOLF = 1
|
14
|
+
WOLFKING = 2
|
13
15
|
|
14
16
|
# 神职
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
PROPHET = 11
|
18
|
+
WITCH = 12
|
19
|
+
HUNTER = 13
|
20
|
+
GUARD = 14
|
21
|
+
IDIOT = 15
|
20
22
|
|
21
23
|
# 其他
|
22
|
-
|
24
|
+
JESTER = 51
|
23
25
|
|
24
26
|
# 平民
|
25
|
-
|
27
|
+
CIVILIAN = 0
|
26
28
|
|
27
29
|
|
28
30
|
class RoleGroup(Enum):
|
29
|
-
|
30
|
-
|
31
|
-
|
31
|
+
WEREWOLF = auto()
|
32
|
+
GOODGUY = auto()
|
33
|
+
OTHERS = auto()
|
32
34
|
|
33
35
|
|
34
36
|
class KillReason(Enum):
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
WEREWOLF = auto()
|
38
|
+
POISON = auto()
|
39
|
+
SHOOT = auto()
|
40
|
+
VOTE = auto()
|
39
41
|
|
40
42
|
|
41
43
|
class GameStatus(Enum):
|
42
|
-
|
43
|
-
|
44
|
-
|
44
|
+
GOODGUY = auto()
|
45
|
+
WEREWOLF = auto()
|
46
|
+
JESTER = auto()
|
45
47
|
|
46
48
|
|
47
49
|
@dataclasses.dataclass
|
48
50
|
class GameState:
|
51
|
+
class State(Enum):
|
52
|
+
DAY = auto()
|
53
|
+
VOTE = auto()
|
54
|
+
NIGHT = auto()
|
55
|
+
|
49
56
|
day: int
|
50
57
|
"""当前天数记录, 不会被 `reset()` 重置"""
|
58
|
+
state: State = State.NIGHT
|
59
|
+
"""当前游戏状态, 不会被 `reset()` 重置"""
|
60
|
+
werewolf_finished: anyio.Event = dataclasses.field(default_factory=anyio.Event)
|
61
|
+
"""狼人交互是否结束"""
|
51
62
|
killed: "Player | None" = None
|
52
63
|
"""当晚狼人击杀目标, `None` 则为空刀"""
|
53
64
|
shoot: "Player | None" = None
|
@@ -60,6 +71,7 @@ class GameState:
|
|
60
71
|
"""当晚守卫保护的目标"""
|
61
72
|
|
62
73
|
def reset(self) -> None:
|
74
|
+
self.werewolf_finished = anyio.Event()
|
63
75
|
self.killed = None
|
64
76
|
self.shoot = None
|
65
77
|
self.antidote = set()
|
@@ -49,11 +49,6 @@ class PlayerSet(set[Player]):
|
|
49
49
|
def sorted(self) -> list[Player]:
|
50
50
|
return sorted(self, key=lambda p: p.user_id)
|
51
51
|
|
52
|
-
async def interact(self) -> None:
|
53
|
-
async with anyio.create_task_group() as tg:
|
54
|
-
for p in self.alive():
|
55
|
-
tg.start_soon(p.interact)
|
56
|
-
|
57
52
|
async def vote(self) -> dict[Player, list[Player]]:
|
58
53
|
players = self.alive()
|
59
54
|
result: dict[Player, list[Player]] = {}
|
@@ -75,10 +70,17 @@ class PlayerSet(set[Player]):
|
|
75
70
|
|
76
71
|
async with anyio.create_task_group() as tg:
|
77
72
|
for p in self:
|
78
|
-
|
73
|
+
func = functools.partial(
|
74
|
+
p.send,
|
75
|
+
message=message,
|
76
|
+
stop_btn_label=None,
|
77
|
+
select_players=None,
|
78
|
+
skip_handler=True,
|
79
|
+
)
|
80
|
+
tg.start_soon(func)
|
79
81
|
|
80
82
|
def show(self) -> str:
|
81
83
|
return "\n".join(f"{i}. {p.name}" for i, p in enumerate(self.sorted, 1))
|
82
84
|
|
83
|
-
def __getitem__(self,
|
84
|
-
return self.sorted[
|
85
|
+
def __getitem__(self, index: int, /) -> Player:
|
86
|
+
return self.sorted[index]
|
@@ -2,7 +2,7 @@ from .civilian import Civilian as Civilian
|
|
2
2
|
from .guard import Guard as Guard
|
3
3
|
from .hunter import Hunter as Hunter
|
4
4
|
from .idiot import Idiot as Idiot
|
5
|
-
from .
|
5
|
+
from .jester import Jester as Jester
|
6
6
|
from .player import Player as Player
|
7
7
|
from .prophet import Prophet as Prophet
|
8
8
|
from .werewolf import Werewolf as Werewolf
|