nonebot-plugin-werewolf 1.0.4__py3-none-any.whl → 1.0.6__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/constant.py +40 -18
- nonebot_plugin_werewolf/game.py +105 -46
- nonebot_plugin_werewolf/matchers.py +1 -2
- nonebot_plugin_werewolf/player.py +60 -43
- nonebot_plugin_werewolf/player_set.py +2 -14
- nonebot_plugin_werewolf/utils.py +4 -4
- {nonebot_plugin_werewolf-1.0.4.dist-info → nonebot_plugin_werewolf-1.0.6.dist-info}/METADATA +22 -7
- nonebot_plugin_werewolf-1.0.6.dist-info/RECORD +13 -0
- nonebot_plugin_werewolf-1.0.4.dist-info/RECORD +0 -13
- {nonebot_plugin_werewolf-1.0.4.dist-info → nonebot_plugin_werewolf-1.0.6.dist-info}/WHEEL +0 -0
- {nonebot_plugin_werewolf-1.0.4.dist-info → nonebot_plugin_werewolf-1.0.6.dist-info}/licenses/LICENSE +0 -0
@@ -1,32 +1,36 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
from dataclasses import dataclass
|
2
4
|
from enum import Enum, auto
|
3
5
|
from typing import TYPE_CHECKING
|
4
6
|
|
7
|
+
from strenum import StrEnum
|
8
|
+
|
5
9
|
from .config import config
|
6
10
|
|
7
11
|
if TYPE_CHECKING:
|
8
12
|
from .player import Player
|
9
13
|
|
10
14
|
|
11
|
-
class Role(
|
15
|
+
class Role(StrEnum):
|
12
16
|
# 狼人
|
13
|
-
|
14
|
-
|
17
|
+
Werewolf = auto()
|
18
|
+
WolfKing = auto()
|
15
19
|
|
16
20
|
# 神职
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
21
|
+
Prophet = auto()
|
22
|
+
Witch = auto()
|
23
|
+
Hunter = auto()
|
24
|
+
Guard = auto()
|
25
|
+
Idiot = auto()
|
22
26
|
|
23
27
|
# 平民
|
24
|
-
|
28
|
+
Civilian = auto()
|
25
29
|
|
26
30
|
|
27
|
-
class RoleGroup(
|
28
|
-
|
29
|
-
|
31
|
+
class RoleGroup(StrEnum):
|
32
|
+
Werewolf = auto()
|
33
|
+
GoodGuy = auto()
|
30
34
|
|
31
35
|
|
32
36
|
class KillReason(Enum):
|
@@ -45,16 +49,33 @@ class GameStatus(Enum):
|
|
45
49
|
@dataclass
|
46
50
|
class GameState:
|
47
51
|
day: int
|
48
|
-
killed:
|
49
|
-
shoot: tuple[
|
50
|
-
protected:
|
51
|
-
potion: tuple[
|
52
|
+
killed: Player | None = None
|
53
|
+
shoot: tuple[Player, Player] | tuple[None, None] = (None, None)
|
54
|
+
protected: Player | None = None
|
55
|
+
potion: tuple[Player | None, tuple[bool, bool]] = (None, (False, False))
|
56
|
+
|
57
|
+
|
58
|
+
role_name_conv: dict[Role, str] = {
|
59
|
+
Role.Werewolf: "狼人",
|
60
|
+
Role.WolfKing: "狼王",
|
61
|
+
Role.Prophet: "预言家",
|
62
|
+
Role.Witch: "女巫",
|
63
|
+
Role.Hunter: "猎人",
|
64
|
+
Role.Guard: "守卫",
|
65
|
+
Role.Idiot: "白痴",
|
66
|
+
Role.Civilian: "平民",
|
67
|
+
}
|
52
68
|
|
53
69
|
|
70
|
+
role_group_name_conv: dict[RoleGroup, str] = {
|
71
|
+
RoleGroup.Werewolf: "狼人",
|
72
|
+
RoleGroup.GoodGuy: "好人",
|
73
|
+
}
|
74
|
+
|
54
75
|
player_preset: dict[int, tuple[int, int, int]] = {
|
55
76
|
# 总人数: (狼, 神, 民)
|
56
|
-
6: (1,
|
57
|
-
7: (2,
|
77
|
+
6: (1, 2, 3),
|
78
|
+
7: (2, 2, 3),
|
58
79
|
8: (2, 3, 3),
|
59
80
|
9: (2, 4, 3),
|
60
81
|
10: (3, 4, 3),
|
@@ -64,3 +85,4 @@ player_preset: dict[int, tuple[int, int, int]] = {
|
|
64
85
|
|
65
86
|
if config.override_preset is not None:
|
66
87
|
player_preset |= {i[0]: i[1:] for i in config.override_preset}
|
88
|
+
player_preset |= {i[0]: i[1:] for i in config.override_preset}
|
nonebot_plugin_werewolf/game.py
CHANGED
@@ -1,16 +1,20 @@
|
|
1
1
|
import asyncio
|
2
2
|
import asyncio.timeouts
|
3
|
+
import contextlib
|
3
4
|
import random
|
5
|
+
import time
|
4
6
|
|
5
7
|
from nonebot.adapters import Bot
|
8
|
+
from nonebot.log import logger
|
6
9
|
from nonebot_plugin_alconna import Target, UniMessage
|
7
10
|
|
8
11
|
from .constant import GameState, GameStatus, KillReason, Role, RoleGroup, player_preset
|
9
12
|
from .player import Player
|
10
13
|
from .player_set import PlayerSet
|
14
|
+
from .utils import InputStore
|
11
15
|
|
12
16
|
starting_games: dict[str, dict[str, str]] = {}
|
13
|
-
running_games: dict[str, tuple["Game", asyncio.Task[None]]] = {}
|
17
|
+
running_games: dict[str, tuple["Game", asyncio.Task[None], asyncio.Task[None]]] = {}
|
14
18
|
|
15
19
|
|
16
20
|
def init_players(bot: Bot, game: "Game", players: dict[str, str]) -> PlayerSet:
|
@@ -22,11 +26,19 @@ def init_players(bot: Bot, game: "Game", players: dict[str, str]) -> PlayerSet:
|
|
22
26
|
)
|
23
27
|
|
24
28
|
roles: list[Role] = []
|
25
|
-
roles.extend(
|
26
|
-
|
27
|
-
|
29
|
+
roles.extend(
|
30
|
+
[Role.Werewolf, Role.Werewolf, Role.WolfKing, Role.Werewolf][: preset[0]]
|
31
|
+
)
|
32
|
+
roles.extend(
|
33
|
+
[Role.Prophet, Role.Witch, Role.Hunter, Role.Guard, Role.Idiot][: preset[1]]
|
34
|
+
)
|
35
|
+
roles.extend([Role.Civilian] * preset[2])
|
28
36
|
|
29
|
-
random.
|
37
|
+
r = random.Random(time.time())
|
38
|
+
shuffled: list[Role] = []
|
39
|
+
for _ in range(len(players)):
|
40
|
+
idx = r.randint(0, len(roles) - 1)
|
41
|
+
shuffled.append(roles.pop(idx))
|
30
42
|
|
31
43
|
async def selector(target_: Target, b: Bot):
|
32
44
|
return target_.self_id == bot.self_id and b is bot
|
@@ -44,7 +56,7 @@ def init_players(bot: Bot, game: "Game", players: dict[str, str]) -> PlayerSet:
|
|
44
56
|
),
|
45
57
|
players[user_id],
|
46
58
|
)
|
47
|
-
for user_id, role in zip(players,
|
59
|
+
for user_id, role in zip(players, shuffled)
|
48
60
|
)
|
49
61
|
|
50
62
|
|
@@ -65,6 +77,7 @@ class Game:
|
|
65
77
|
self.group = group
|
66
78
|
self.players = init_players(bot, self, players)
|
67
79
|
self.state = GameState(0)
|
80
|
+
self.killed_players = []
|
68
81
|
|
69
82
|
async def send(self, message: str | UniMessage):
|
70
83
|
if isinstance(message, str):
|
@@ -73,20 +86,20 @@ class Game:
|
|
73
86
|
|
74
87
|
def at_all(self) -> UniMessage:
|
75
88
|
msg = UniMessage()
|
76
|
-
for p in sorted(self.players, key=lambda p: (p.
|
89
|
+
for p in sorted(self.players, key=lambda p: (p.role_name, p.user_id)):
|
77
90
|
msg.at(p.user_id)
|
78
91
|
return msg
|
79
92
|
|
80
93
|
def check_game_status(self) -> GameStatus:
|
81
94
|
players = self.players.alive()
|
82
|
-
w = players.select(RoleGroup
|
83
|
-
p = players.exclude(RoleGroup
|
95
|
+
w = players.select(RoleGroup.Werewolf)
|
96
|
+
p = players.exclude(RoleGroup.Werewolf)
|
84
97
|
|
85
98
|
if w.size >= p.size:
|
86
99
|
return GameStatus.Bad
|
87
|
-
if not p.select(Role
|
100
|
+
if not p.select(Role.Civilian):
|
88
101
|
return GameStatus.Bad
|
89
|
-
if not p.exclude(Role
|
102
|
+
if not p.exclude(Role.Civilian):
|
90
103
|
return GameStatus.Bad
|
91
104
|
if not w.size:
|
92
105
|
return GameStatus.Good
|
@@ -111,7 +124,7 @@ class Game:
|
|
111
124
|
case KillReason.Shoot:
|
112
125
|
msg += " 射杀"
|
113
126
|
case KillReason.Vote:
|
114
|
-
msg += "
|
127
|
+
msg += " 票出"
|
115
128
|
msg += "\n\n"
|
116
129
|
|
117
130
|
return msg.strip()
|
@@ -136,7 +149,15 @@ class Game:
|
|
136
149
|
if isinstance(players, Player):
|
137
150
|
players = PlayerSet([players])
|
138
151
|
|
139
|
-
|
152
|
+
async def wait(p: Player):
|
153
|
+
while True:
|
154
|
+
msg = await InputStore.fetch(p.user_id, self.group.id)
|
155
|
+
if msg.extract_plain_text() == "/stop":
|
156
|
+
break
|
157
|
+
|
158
|
+
with contextlib.suppress(TimeoutError):
|
159
|
+
async with asyncio.timeouts.timeout(timeout_secs):
|
160
|
+
await asyncio.gather(*[wait(p) for p in players])
|
140
161
|
|
141
162
|
async def interact(
|
142
163
|
self,
|
@@ -145,7 +166,7 @@ class Game:
|
|
145
166
|
) -> None:
|
146
167
|
players = self.players.alive().select(type_)
|
147
168
|
text = (
|
148
|
-
type_.
|
169
|
+
type_.role_name # Player
|
149
170
|
if isinstance(type_, Player)
|
150
171
|
else (
|
151
172
|
type_.name # Role
|
@@ -164,8 +185,8 @@ class Game:
|
|
164
185
|
players = self.players.alive()
|
165
186
|
self.state.killed = None
|
166
187
|
|
167
|
-
w = players.select(RoleGroup
|
168
|
-
await self.interact(RoleGroup
|
188
|
+
w = players.select(RoleGroup.Werewolf)
|
189
|
+
await self.interact(RoleGroup.Werewolf, 120)
|
169
190
|
if (s := w.player_selected()).size == 1:
|
170
191
|
self.state.killed = s.pop()
|
171
192
|
await w.broadcast(f"今晚选择的目标为: {self.state.killed.name}")
|
@@ -173,8 +194,8 @@ class Game:
|
|
173
194
|
await w.broadcast("狼人阵营意见未统一,此晚空刀")
|
174
195
|
|
175
196
|
# 如果女巫存活,正常交互,限时1分钟
|
176
|
-
if players.include(Role
|
177
|
-
await self.interact(Role
|
197
|
+
if players.include(Role.Witch):
|
198
|
+
await self.interact(Role.Witch, 60)
|
178
199
|
# 否则等待 5-20s
|
179
200
|
else:
|
180
201
|
await asyncio.sleep(random.uniform(5, 20))
|
@@ -211,15 +232,15 @@ class Game:
|
|
211
232
|
await self.send(
|
212
233
|
UniMessage.text("玩家 ")
|
213
234
|
.at(shoot.user_id)
|
214
|
-
.text(f" 被{shooter.
|
235
|
+
.text(f" 被{shooter.role_name}射杀, 请发表遗言\n")
|
215
236
|
.text("限时1分钟, 发送 “/stop” 结束发言")
|
216
237
|
)
|
217
238
|
await self.wait_stop(shoot, 60)
|
239
|
+
self.state.shoot = (None, None)
|
218
240
|
await self.post_kill(shoot)
|
219
|
-
self.state.shoot = (None, None)
|
220
241
|
|
221
242
|
async def run_vote(self) -> None:
|
222
|
-
#
|
243
|
+
# 筛选当前存活玩家
|
223
244
|
players = self.players.alive()
|
224
245
|
|
225
246
|
# 被票玩家: [投票玩家]
|
@@ -231,9 +252,9 @@ class Game:
|
|
231
252
|
|
232
253
|
# 投票结果公示
|
233
254
|
msg = UniMessage.text("投票结果:\n")
|
234
|
-
for p, v in sorted(vote_result.items(), key=lambda x: x[1], reverse=True):
|
255
|
+
for p, v in sorted(vote_result.items(), key=lambda x: len(x[1]), reverse=True):
|
235
256
|
if p is not None:
|
236
|
-
msg.at(p.user_id).text(f": {v} 票\n")
|
257
|
+
msg.at(p.user_id).text(f": {len(v)} 票\n")
|
237
258
|
vote_reversed[len(v)] = [*vote_reversed.get(len(v), []), p]
|
238
259
|
if v := (len(players) - total_votes):
|
239
260
|
msg.text(f"弃票: {v} 票\n")
|
@@ -244,6 +265,11 @@ class Game:
|
|
244
265
|
await self.send("没有人被票出")
|
245
266
|
return
|
246
267
|
|
268
|
+
# 弃票大于最高票
|
269
|
+
if (len(players) - total_votes) >= max(vote_reversed.keys()):
|
270
|
+
await self.send("弃票数大于最高票数, 没有人被票出")
|
271
|
+
return
|
272
|
+
|
247
273
|
# 平票
|
248
274
|
if len(vs := vote_reversed[max(vote_reversed.keys())]) != 1:
|
249
275
|
await self.send(
|
@@ -270,6 +296,7 @@ class Game:
|
|
270
296
|
await self.post_kill(voted)
|
271
297
|
|
272
298
|
async def run_dead_channel(self) -> None:
|
299
|
+
loop = asyncio.get_event_loop()
|
273
300
|
queue: asyncio.Queue[tuple[Player, UniMessage]] = asyncio.Queue()
|
274
301
|
|
275
302
|
async def send():
|
@@ -279,20 +306,29 @@ class Game:
|
|
279
306
|
await self.players.dead().exclude(player).broadcast(msg)
|
280
307
|
|
281
308
|
async def recv(player: Player):
|
309
|
+
counter = 0
|
310
|
+
|
311
|
+
def decrease():
|
312
|
+
nonlocal counter
|
313
|
+
counter -= 1
|
314
|
+
|
282
315
|
while True:
|
283
316
|
if not player.killed:
|
284
317
|
await asyncio.sleep(1)
|
285
318
|
continue
|
286
319
|
msg = await player.receive()
|
287
|
-
|
320
|
+
counter += 1
|
321
|
+
if counter <= 10:
|
322
|
+
await queue.put((player, msg))
|
323
|
+
loop.call_later(60, decrease)
|
324
|
+
else:
|
325
|
+
await player.send("发言频率超过限制, 该消息被屏蔽")
|
288
326
|
|
289
327
|
await asyncio.gather(send(), *[recv(p) for p in self.players])
|
290
328
|
|
291
329
|
async def run(self) -> None:
|
292
330
|
# 告知玩家角色信息
|
293
331
|
await self.notify_player_role()
|
294
|
-
# 死者频道
|
295
|
-
dead_channel = asyncio.create_task(self.run_dead_channel())
|
296
332
|
# 天数记录 主要用于第一晚狼人击杀的遗言
|
297
333
|
day_count = 0
|
298
334
|
|
@@ -305,9 +341,10 @@ class Game:
|
|
305
341
|
# 狼人、预言家、守卫 同时交互,女巫在狼人后交互
|
306
342
|
await asyncio.gather(
|
307
343
|
self.select_killed(),
|
308
|
-
players.select(Role
|
309
|
-
|
310
|
-
self.interact(Role
|
344
|
+
players.select(Role.Witch).broadcast("请等待狼人决定目标..."),
|
345
|
+
players.select(Role.Civilian).broadcast("请等待其他玩家结束交互..."),
|
346
|
+
self.interact(Role.Prophet, 60),
|
347
|
+
self.interact(Role.Guard, 60),
|
311
348
|
)
|
312
349
|
|
313
350
|
# 狼人击杀目标
|
@@ -321,40 +358,42 @@ class Game:
|
|
321
358
|
if killed is not None:
|
322
359
|
# 除非守卫保护或女巫使用解药,否则狼人正常击杀玩家
|
323
360
|
if not ((killed is protected) or (antidote and potioned is killed)):
|
324
|
-
await killed.kill(
|
361
|
+
await killed.kill(
|
362
|
+
KillReason.Kill, *players.select(RoleGroup.Werewolf)
|
363
|
+
)
|
325
364
|
# 如果女巫使用毒药且守卫未保护,杀死该玩家
|
326
365
|
if poison and (potioned is not None) and (potioned is not protected):
|
327
|
-
await potioned.kill(KillReason.Poison, *players.select(Role
|
366
|
+
await potioned.kill(KillReason.Poison, *players.select(Role.Witch))
|
328
367
|
|
329
368
|
day_count += 1
|
330
369
|
msg = UniMessage.text(f"『第{day_count}天』天亮了...\n")
|
331
370
|
# 没有玩家死亡,平安夜
|
332
371
|
if not (dead := players.dead()):
|
333
372
|
await self.send(msg.text("昨晚是平安夜"))
|
334
|
-
#
|
373
|
+
# 有玩家死亡,公布死者名单
|
335
374
|
else:
|
336
|
-
# 公布死者名单
|
337
375
|
msg.text("昨晚的死者是:")
|
338
376
|
for p in dead.sorted():
|
339
377
|
msg.text("\n").at(p.user_id)
|
340
378
|
await self.send(msg)
|
341
|
-
await self.post_kill(dead)
|
342
|
-
|
343
|
-
# 判断游戏状态
|
344
|
-
if self.check_game_status() != GameStatus.Unset:
|
345
|
-
break
|
346
|
-
|
347
|
-
# 公示存活玩家
|
348
|
-
await self.send(f"当前存活玩家: \n\n{self.players.alive().show()}")
|
349
379
|
|
350
380
|
# 第一晚被狼人杀死的玩家发表遗言
|
351
381
|
if day_count == 1 and killed is not None and not killed.alive:
|
352
382
|
await self.send(
|
353
383
|
UniMessage.text("当前为第一天\n请被狼人杀死的 ")
|
354
384
|
.at(killed.user_id)
|
355
|
-
.text(" 发表遗言\n
|
385
|
+
.text(" 发表遗言\n")
|
386
|
+
.text("限时1分钟, 发送 “/stop” 结束发言")
|
356
387
|
)
|
357
388
|
await self.wait_stop(killed, 60)
|
389
|
+
await self.post_kill(dead)
|
390
|
+
|
391
|
+
# 判断游戏状态
|
392
|
+
if self.check_game_status() != GameStatus.Unset:
|
393
|
+
break
|
394
|
+
|
395
|
+
# 公示存活玩家
|
396
|
+
await self.send(f"当前存活玩家: \n\n{self.players.alive().show()}")
|
358
397
|
|
359
398
|
# 开始自由讨论
|
360
399
|
await self.send("接下来开始自由讨论\n限时2分钟, 全员发送 “/stop” 结束发言")
|
@@ -365,11 +404,31 @@ class Game:
|
|
365
404
|
await self.run_vote()
|
366
405
|
|
367
406
|
# 游戏结束
|
368
|
-
dead_channel.cancel()
|
369
407
|
winner = "好人" if self.check_game_status() == GameStatus.Good else "狼人"
|
370
408
|
msg = UniMessage.text(f"🎉游戏结束,{winner}获胜\n\n")
|
371
409
|
for p in sorted(self.players, key=lambda p: (p.role.value, p.user_id)):
|
372
|
-
msg.at(p.user_id).text(f": {p.
|
373
|
-
msg.text(f"\n{self.show_killed_players()}")
|
410
|
+
msg.at(p.user_id).text(f": {p.role_name}\n")
|
374
411
|
await self.send(msg)
|
375
|
-
|
412
|
+
await self.send(f"玩家死亡报告:\n\n{self.show_killed_players()}")
|
413
|
+
|
414
|
+
def start(self):
|
415
|
+
task = asyncio.create_task(self.run())
|
416
|
+
dead_channel = asyncio.create_task(self.run_dead_channel())
|
417
|
+
|
418
|
+
async def daemon():
|
419
|
+
while not task.done(): # noqa: ASYNC110
|
420
|
+
await asyncio.sleep(1)
|
421
|
+
|
422
|
+
try:
|
423
|
+
task.result()
|
424
|
+
except asyncio.CancelledError as err:
|
425
|
+
logger.warning(f"狼人杀游戏进程被取消: {err}")
|
426
|
+
except Exception as err:
|
427
|
+
msg = f"狼人杀游戏进程出现错误: {err!r}"
|
428
|
+
logger.opt(exception=err).error(msg)
|
429
|
+
await self.send(msg)
|
430
|
+
finally:
|
431
|
+
dead_channel.cancel()
|
432
|
+
running_games.pop(self.group.id, None)
|
433
|
+
|
434
|
+
running_games[self.group.id] = (self, task, asyncio.create_task(daemon()))
|
@@ -1,13 +1,15 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import asyncio
|
2
4
|
import asyncio.timeouts
|
3
5
|
from dataclasses import dataclass
|
4
|
-
from typing import TYPE_CHECKING, ClassVar, TypeVar
|
6
|
+
from typing import TYPE_CHECKING, ClassVar, TypeVar, final
|
5
7
|
from typing_extensions import override
|
6
8
|
|
7
9
|
from nonebot.adapters import Bot
|
8
10
|
from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage
|
9
11
|
|
10
|
-
from .constant import KillReason, Role, RoleGroup
|
12
|
+
from .constant import KillReason, Role, RoleGroup, role_name_conv,role_group_name_conv
|
11
13
|
from .utils import InputStore, check_index
|
12
14
|
|
13
15
|
if TYPE_CHECKING:
|
@@ -16,7 +18,7 @@ if TYPE_CHECKING:
|
|
16
18
|
|
17
19
|
|
18
20
|
P = TypeVar("P", bound=type["Player"])
|
19
|
-
PLAYER_CLASS: dict[Role, type[
|
21
|
+
PLAYER_CLASS: dict[Role, type[Player]] = {}
|
20
22
|
|
21
23
|
|
22
24
|
def register_role(cls: P) -> P:
|
@@ -27,7 +29,7 @@ def register_role(cls: P) -> P:
|
|
27
29
|
@dataclass
|
28
30
|
class KillInfo:
|
29
31
|
reason: KillReason
|
30
|
-
killers:
|
32
|
+
killers: PlayerSet
|
31
33
|
|
32
34
|
|
33
35
|
class Player:
|
@@ -35,52 +37,61 @@ class Player:
|
|
35
37
|
role_group: ClassVar[RoleGroup]
|
36
38
|
|
37
39
|
bot: Bot
|
38
|
-
game:
|
40
|
+
game: Game
|
39
41
|
user: Target
|
40
42
|
name: str
|
41
43
|
alive: bool = True
|
42
44
|
killed: bool = False
|
43
45
|
kill_info: KillInfo | None = None
|
44
|
-
selected:
|
46
|
+
selected: Player | None = None
|
45
47
|
|
46
|
-
|
48
|
+
@final
|
49
|
+
def __init__(self, bot: Bot, game: Game, user: Target, name: str) -> None:
|
47
50
|
self.bot = bot
|
48
51
|
self.game = game
|
49
52
|
self.user = user
|
50
53
|
self.name = name
|
51
54
|
|
55
|
+
@final
|
52
56
|
@classmethod
|
53
57
|
def new(
|
54
58
|
cls,
|
55
59
|
role: Role,
|
56
60
|
bot: Bot,
|
57
|
-
game:
|
61
|
+
game: Game,
|
58
62
|
user: Target,
|
59
63
|
name: str,
|
60
|
-
) ->
|
64
|
+
) -> Player:
|
61
65
|
if role not in PLAYER_CLASS:
|
62
66
|
raise ValueError(f"Unexpected role: {role!r}")
|
63
67
|
|
64
68
|
return PLAYER_CLASS[role](bot, game, user, name)
|
65
69
|
|
66
70
|
def __repr__(self) -> str:
|
67
|
-
return f"<{self.
|
71
|
+
return f"<{self.role_name}: user={self.user} alive={self.alive}>"
|
68
72
|
|
69
73
|
@property
|
70
74
|
def user_id(self) -> str:
|
71
75
|
return self.user.id
|
72
76
|
|
77
|
+
@property
|
78
|
+
def role_name(self) -> str:
|
79
|
+
return role_name_conv[self.role]
|
80
|
+
|
81
|
+
@final
|
73
82
|
async def send(self, message: str | UniMessage) -> Receipt:
|
74
83
|
if isinstance(message, str):
|
75
84
|
message = UniMessage.text(message)
|
76
85
|
|
77
86
|
return await message.send(target=self.user, bot=self.bot)
|
78
87
|
|
88
|
+
@final
|
79
89
|
async def receive(self, prompt: str | UniMessage | None = None) -> UniMessage:
|
80
90
|
if prompt:
|
81
91
|
await self.send(prompt)
|
82
92
|
return await InputStore.fetch(self.user.id)
|
83
93
|
|
94
|
+
@final
|
84
95
|
async def receive_text(self) -> str:
|
85
96
|
return (await self.receive()).extract_plain_text()
|
86
97
|
|
@@ -88,13 +99,15 @@ class Player:
|
|
88
99
|
return
|
89
100
|
|
90
101
|
async def notify_role(self) -> None:
|
91
|
-
await self.send(f"你的身份: {self.
|
102
|
+
await self.send(f"你的身份: {self.role_name}")
|
92
103
|
|
93
104
|
async def kill(
|
94
105
|
self,
|
95
106
|
reason: KillReason,
|
96
|
-
*killers:
|
107
|
+
*killers: Player,
|
97
108
|
) -> bool:
|
109
|
+
from .player_set import PlayerSet
|
110
|
+
|
98
111
|
self.alive = False
|
99
112
|
self.kill_info = KillInfo(reason, PlayerSet(killers))
|
100
113
|
return True
|
@@ -102,15 +115,16 @@ class Player:
|
|
102
115
|
async def post_kill(self) -> None:
|
103
116
|
self.killed = True
|
104
117
|
|
105
|
-
async def vote(self, players:
|
118
|
+
async def vote(self, players: PlayerSet) -> tuple[Player, Player] | None:
|
106
119
|
await self.send(
|
107
120
|
f"请选择需要投票的玩家:\n{players.show()}"
|
108
121
|
"\n\n发送编号选择玩家\n发送 “/stop” 弃票"
|
109
122
|
)
|
110
123
|
|
111
124
|
while True:
|
112
|
-
text =
|
125
|
+
text = await self.receive_text()
|
113
126
|
if text == "/stop":
|
127
|
+
await self.send("你选择了弃票")
|
114
128
|
return None
|
115
129
|
index = check_index(text, len(players))
|
116
130
|
if index is not None:
|
@@ -131,9 +145,9 @@ class CanShoot(Player):
|
|
131
145
|
return await super().post_kill()
|
132
146
|
|
133
147
|
await self.game.send(
|
134
|
-
UniMessage.text(f"{self.
|
148
|
+
UniMessage.text(f"{self.role_name} ")
|
135
149
|
.at(self.user_id)
|
136
|
-
.text(f" 死了\n请{self.
|
150
|
+
.text(f" 死了\n请{self.role_name}决定击杀目标...")
|
137
151
|
)
|
138
152
|
|
139
153
|
self.game.state.shoot = (None, None)
|
@@ -142,14 +156,14 @@ class CanShoot(Player):
|
|
142
156
|
if shoot is not None:
|
143
157
|
self.game.state.shoot = (self, shoot)
|
144
158
|
await self.send(
|
145
|
-
UniMessage.text(f"{self.
|
159
|
+
UniMessage.text(f"{self.role_name} ")
|
146
160
|
.at(self.user_id)
|
147
161
|
.text(" 射杀了玩家 ")
|
148
162
|
.at(shoot.user_id)
|
149
163
|
)
|
150
164
|
await shoot.kill(KillReason.Shoot, self)
|
151
165
|
else:
|
152
|
-
await self.send(f"{self.
|
166
|
+
await self.send(f"{self.role_name}选择了取消技能")
|
153
167
|
return await super().post_kill()
|
154
168
|
|
155
169
|
async def shoot(self) -> Player | None:
|
@@ -178,20 +192,23 @@ class CanShoot(Player):
|
|
178
192
|
|
179
193
|
@register_role
|
180
194
|
class 狼人(Player):
|
181
|
-
role: ClassVar[Role] = Role
|
182
|
-
role_group: ClassVar[RoleGroup] = RoleGroup
|
195
|
+
role: ClassVar[Role] = Role.Werewolf
|
196
|
+
role_group: ClassVar[RoleGroup] = RoleGroup.Werewolf
|
183
197
|
|
184
198
|
@override
|
185
199
|
async def notify_role(self) -> None:
|
186
200
|
await super().notify_role()
|
187
|
-
partners = self.game.players.alive().select(RoleGroup
|
188
|
-
|
189
|
-
|
201
|
+
partners = self.game.players.alive().select(RoleGroup.Werewolf).exclude(self)
|
202
|
+
if partners:
|
203
|
+
await self.send(
|
204
|
+
"你的队友:\n"
|
205
|
+
+ "\n".join(f" {p.role_name}: {p.name}" for p in partners)
|
206
|
+
)
|
190
207
|
|
191
208
|
@override
|
192
209
|
async def interact(self) -> None:
|
193
210
|
players = self.game.players.alive()
|
194
|
-
partners = players.select(RoleGroup
|
211
|
+
partners = players.select(RoleGroup.Werewolf).exclude(self)
|
195
212
|
|
196
213
|
# 避免阻塞
|
197
214
|
def broadcast(msg: str | UniMessage):
|
@@ -201,7 +218,7 @@ class 狼人(Player):
|
|
201
218
|
if partners:
|
202
219
|
msg = (
|
203
220
|
msg.text("你的队友:\n")
|
204
|
-
.text("\n".join(f" {p.
|
221
|
+
.text("\n".join(f" {p.role_name}: {p.name}" for p in partners))
|
205
222
|
.text("\n所有私聊消息将被转发至队友\n\n")
|
206
223
|
)
|
207
224
|
await self.send(
|
@@ -221,7 +238,7 @@ class 狼人(Player):
|
|
221
238
|
if index is not None:
|
222
239
|
selected = index - 1
|
223
240
|
msg = f"当前选择玩家: {players[selected].name}"
|
224
|
-
await self.send(msg)
|
241
|
+
await self.send(f"{msg}\n发送 “/stop” 结束回合")
|
225
242
|
broadcast(f"队友 {self.name} {msg}")
|
226
243
|
if text == "/stop":
|
227
244
|
if selected is not None:
|
@@ -237,14 +254,14 @@ class 狼人(Player):
|
|
237
254
|
|
238
255
|
@register_role
|
239
256
|
class 狼王(CanShoot, 狼人):
|
240
|
-
role: ClassVar[Role] = Role
|
241
|
-
role_group: ClassVar[RoleGroup] = RoleGroup
|
257
|
+
role: ClassVar[Role] = Role.WolfKing
|
258
|
+
role_group: ClassVar[RoleGroup] = RoleGroup.Werewolf
|
242
259
|
|
243
260
|
|
244
261
|
@register_role
|
245
262
|
class 预言家(Player):
|
246
|
-
role: ClassVar[Role] = Role
|
247
|
-
role_group: ClassVar[RoleGroup] = RoleGroup
|
263
|
+
role: ClassVar[Role] = Role.Prophet
|
264
|
+
role_group: ClassVar[RoleGroup] = RoleGroup.GoodGuy
|
248
265
|
|
249
266
|
@override
|
250
267
|
async def interact(self) -> None:
|
@@ -264,14 +281,14 @@ class 预言家(Player):
|
|
264
281
|
await self.send("输入错误,请发送编号选择玩家")
|
265
282
|
|
266
283
|
player = players[selected]
|
267
|
-
result =
|
284
|
+
result = role_group_name_conv[player.role_group]
|
268
285
|
await self.send(f"玩家 {player.name} 的阵营是『{result}』")
|
269
286
|
|
270
287
|
|
271
288
|
@register_role
|
272
289
|
class 女巫(Player):
|
273
|
-
role: ClassVar[Role] = Role
|
274
|
-
role_group: ClassVar[RoleGroup] = RoleGroup
|
290
|
+
role: ClassVar[Role] = Role.Witch
|
291
|
+
role_group: ClassVar[RoleGroup] = RoleGroup.GoodGuy
|
275
292
|
antidote: int = 1
|
276
293
|
poison: int = 1
|
277
294
|
|
@@ -296,7 +313,7 @@ class 女巫(Player):
|
|
296
313
|
async def handle_killed(self) -> bool:
|
297
314
|
msg = UniMessage()
|
298
315
|
if (killed := self.game.state.killed) is not None:
|
299
|
-
msg.text(f"今晚 {killed} 被刀了\n\n")
|
316
|
+
msg.text(f"今晚 {killed.name} 被刀了\n\n")
|
300
317
|
else:
|
301
318
|
await self.send("今晚没有人被刀")
|
302
319
|
return False
|
@@ -359,14 +376,14 @@ class 女巫(Player):
|
|
359
376
|
|
360
377
|
@register_role
|
361
378
|
class 猎人(CanShoot, Player):
|
362
|
-
role: ClassVar[Role] = Role
|
363
|
-
role_group: ClassVar[RoleGroup] = RoleGroup
|
379
|
+
role: ClassVar[Role] = Role.Hunter
|
380
|
+
role_group: ClassVar[RoleGroup] = RoleGroup.GoodGuy
|
364
381
|
|
365
382
|
|
366
383
|
@register_role
|
367
384
|
class 守卫(Player):
|
368
|
-
role: ClassVar[Role] = Role
|
369
|
-
role_group: ClassVar[RoleGroup] = RoleGroup
|
385
|
+
role: ClassVar[Role] = Role.Guard
|
386
|
+
role_group: ClassVar[RoleGroup] = RoleGroup.GoodGuy
|
370
387
|
|
371
388
|
@override
|
372
389
|
async def interact(self) -> None:
|
@@ -397,8 +414,8 @@ class 守卫(Player):
|
|
397
414
|
|
398
415
|
@register_role
|
399
416
|
class 白痴(Player):
|
400
|
-
role: ClassVar[Role] = Role
|
401
|
-
role_group: ClassVar[RoleGroup] = RoleGroup
|
417
|
+
role: ClassVar[Role] = Role.Idiot
|
418
|
+
role_group: ClassVar[RoleGroup] = RoleGroup.GoodGuy
|
402
419
|
voted: bool = False
|
403
420
|
|
404
421
|
@override
|
@@ -418,7 +435,7 @@ class 白痴(Player):
|
|
418
435
|
return await super().kill(reason, *killers)
|
419
436
|
|
420
437
|
@override
|
421
|
-
async def vote(self, players:
|
438
|
+
async def vote(self, players: PlayerSet) -> tuple[Player, Player] | None:
|
422
439
|
if self.voted:
|
423
440
|
await self.send("你已经发动过白痴身份的技能,无法参与本次投票")
|
424
441
|
return None
|
@@ -427,5 +444,5 @@ class 白痴(Player):
|
|
427
444
|
|
428
445
|
@register_role
|
429
446
|
class 平民(Player):
|
430
|
-
role: ClassVar[Role] = Role
|
431
|
-
role_group: ClassVar[RoleGroup] = RoleGroup
|
447
|
+
role: ClassVar[Role] = Role.Civilian
|
448
|
+
role_group: ClassVar[RoleGroup] = RoleGroup.GoodGuy
|
@@ -1,12 +1,10 @@
|
|
1
1
|
import asyncio
|
2
2
|
import asyncio.timeouts
|
3
|
-
import contextlib
|
4
3
|
|
5
4
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
6
5
|
|
7
6
|
from .constant import Role, RoleGroup
|
8
7
|
from .player import Player
|
9
|
-
from .utils import InputStore
|
10
8
|
|
11
9
|
|
12
10
|
class PlayerSet(set[Player]):
|
@@ -52,12 +50,13 @@ class PlayerSet(set[Player]):
|
|
52
50
|
await asyncio.gather(*[p.interact() for p in self.alive()])
|
53
51
|
|
54
52
|
async def vote(self, timeout_secs: float = 60) -> dict[Player, list[Player]]:
|
55
|
-
async def vote(player: Player) ->
|
53
|
+
async def vote(player: Player) -> tuple[Player, Player] | None:
|
56
54
|
try:
|
57
55
|
async with asyncio.timeouts.timeout(timeout_secs):
|
58
56
|
return await player.vote(self)
|
59
57
|
except TimeoutError:
|
60
58
|
await player.send("投票超时,将视为弃票")
|
59
|
+
return None
|
61
60
|
|
62
61
|
result: dict[Player, list[Player]] = {}
|
63
62
|
for item in await asyncio.gather(*[vote(p) for p in self.alive()]):
|
@@ -69,17 +68,6 @@ class PlayerSet(set[Player]):
|
|
69
68
|
async def broadcast(self, message: str | UniMessage) -> None:
|
70
69
|
await asyncio.gather(*[p.send(message) for p in self])
|
71
70
|
|
72
|
-
async def wait_group_stop(self, group_id: str, timeout_secs: float) -> None:
|
73
|
-
async def wait(p: Player):
|
74
|
-
while True:
|
75
|
-
msg = await InputStore.fetch(p.user_id, group_id)
|
76
|
-
if msg.extract_plain_text() == "/stop":
|
77
|
-
break
|
78
|
-
|
79
|
-
with contextlib.suppress(TimeoutError):
|
80
|
-
async with asyncio.timeouts.timeout(timeout_secs):
|
81
|
-
await asyncio.gather(*[wait(p) for p in self])
|
82
|
-
|
83
71
|
def show(self) -> str:
|
84
72
|
return "\n".join(f"{i}. {p.name}" for i, p in enumerate(self.sorted(), 1))
|
85
73
|
|
nonebot_plugin_werewolf/utils.py
CHANGED
@@ -9,7 +9,7 @@ from nonebot.rule import to_me
|
|
9
9
|
from nonebot_plugin_alconna import MsgTarget, UniMessage, UniMsg
|
10
10
|
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
|
11
11
|
|
12
|
-
from .
|
12
|
+
from .constant import player_preset
|
13
13
|
|
14
14
|
|
15
15
|
def check_index(text: str, arrlen: int) -> int | None:
|
@@ -47,7 +47,7 @@ def user_in_game(user_id: str, group_id: str | None) -> bool:
|
|
47
47
|
if group_id is not None and group_id not in running_games:
|
48
48
|
return False
|
49
49
|
games = running_games.values() if group_id is None else [running_games[group_id]]
|
50
|
-
for game, _ in games:
|
50
|
+
for game, *_ in games:
|
51
51
|
return any(user_id == player.user_id for player in game.players)
|
52
52
|
return False
|
53
53
|
|
@@ -173,12 +173,12 @@ async def _prepare_game_handle(
|
|
173
173
|
|
174
174
|
|
175
175
|
async def prepare_game(event: Event, players: dict[str, str]) -> None:
|
176
|
-
queue = asyncio.Queue()
|
176
|
+
queue: asyncio.Queue[tuple[str, str, str]] = asyncio.Queue()
|
177
177
|
task_receive = asyncio.create_task(
|
178
178
|
_prepare_game_receive(
|
179
179
|
queue,
|
180
180
|
event,
|
181
|
-
UniMessage.get_target().id,
|
181
|
+
UniMessage.get_target(event).id,
|
182
182
|
)
|
183
183
|
)
|
184
184
|
|
{nonebot_plugin_werewolf-1.0.4.dist-info → nonebot_plugin_werewolf-1.0.6.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: nonebot-plugin-werewolf
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.6
|
4
4
|
Summary: Default template for PDM package
|
5
5
|
Author-Email: wyf7685 <wyf7685@163.com>
|
6
6
|
License: MIT
|
@@ -9,6 +9,7 @@ Requires-Dist: nonebot2>=2.3.3
|
|
9
9
|
Requires-Dist: nonebot-plugin-alconna>=0.52.1
|
10
10
|
Requires-Dist: nonebot-plugin-userinfo>=0.2.6
|
11
11
|
Requires-Dist: nonebot-plugin-waiter>=0.7.1
|
12
|
+
Requires-Dist: StrEnum>=0.4.15
|
12
13
|
Description-Content-Type: text/markdown
|
13
14
|
|
14
15
|
<div align="center">
|
@@ -116,9 +117,18 @@ _✨ 简单的狼人杀插件 ✨_
|
|
116
117
|
|
117
118
|
### 指令表
|
118
119
|
|
119
|
-
| 指令 |
|
120
|
-
| :-----------------: |
|
121
|
-
| `werewolf`/`狼人杀` |
|
120
|
+
| 指令 | 权限 | 需要@ | 范围 | 说明 |
|
121
|
+
| :-----------------: | :--------: | :---: | :--: | :---------------------------------: |
|
122
|
+
| `werewolf`/`狼人杀` | 群员 | 是 | 群聊 | 发起游戏 (进入准备阶段) |
|
123
|
+
| `开始游戏` | 游戏发起者 | 是 | 群聊 | _[准备阶段]_ 游戏发起者开始游戏 |
|
124
|
+
| `结束游戏` | 游戏发起者 | 是 | 群聊 | _[准备阶段]_ 游戏发起者结束游戏 |
|
125
|
+
| `当前玩家` | 群员 | 是 | 群聊 | _[准备阶段]_ 列出参与游戏的玩家列表 |
|
126
|
+
| `加入游戏` | 群员 | 是 | 群聊 | _[准备阶段]_ 玩家加入游戏 |
|
127
|
+
| `退出游戏` | 群员 | 是 | 群聊 | _[准备阶段]_ 玩家退出游戏 |
|
128
|
+
|
129
|
+
_其他交互参考游戏内提示_
|
130
|
+
|
131
|
+
对于 `OneBot V11` 适配器, 启用配置项 `werewolf__enable_poke` 后, 可以使用戳一戳代替 _准备阶段_ 的 `加入游戏` 操作 和 游戏内的 `/stop` 命令
|
122
132
|
|
123
133
|
### 游戏内容
|
124
134
|
|
@@ -132,8 +142,8 @@ _✨ 简单的狼人杀插件 ✨_
|
|
132
142
|
|
133
143
|
| 总人数 | 狼人 | 神职 | 平民 |
|
134
144
|
| ------ | ---- | ---- | ---- |
|
135
|
-
| 6 | 1 |
|
136
|
-
| 7 | 2 |
|
145
|
+
| 6 | 1 | 2 | 3 |
|
146
|
+
| 7 | 2 | 2 | 3 |
|
137
147
|
| 8 | 2 | 3 | 3 |
|
138
148
|
| 9 | 2 | 4 | 3 |
|
139
149
|
| 10 | 3 | 4 | 3 |
|
@@ -171,9 +181,14 @@ werewolf__override_preset='
|
|
171
181
|
<details>
|
172
182
|
<summary>更新日志</summary>
|
173
183
|
|
174
|
-
- 2024.09.
|
184
|
+
- 2024.09.03 v1.0.6
|
185
|
+
|
186
|
+
- 修复预言家查验狼王返回好人的 bug
|
187
|
+
|
188
|
+
- 2024.09.03 v1.0.5
|
175
189
|
|
176
190
|
- 优化玩家交互体验
|
191
|
+
- 添加游戏结束后死亡报告
|
177
192
|
|
178
193
|
- 2024.08.31 v1.0.1
|
179
194
|
|
@@ -0,0 +1,13 @@
|
|
1
|
+
nonebot_plugin_werewolf-1.0.6.dist-info/METADATA,sha256=VEQ_yjPRs3bB28Khz98EJA5XTlJ6fMrP_YNZBBp9PQc,6839
|
2
|
+
nonebot_plugin_werewolf-1.0.6.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
|
3
|
+
nonebot_plugin_werewolf-1.0.6.dist-info/licenses/LICENSE,sha256=B_WbEqjGr6GYVNfEJPY31T1Opik7OtgOkhRs4Ig3e2M,1064
|
4
|
+
nonebot_plugin_werewolf/__init__.py,sha256=WEwQN8NtEtOfTrH23_MysaaS4OnCjxtGb-_brG9bGTQ,734
|
5
|
+
nonebot_plugin_werewolf/config.py,sha256=3O63P1pjvJEwgOwxApAHbKQzvCR1zoG5UjTClZ_OJts,315
|
6
|
+
nonebot_plugin_werewolf/constant.py,sha256=k4r0vKYtePDzAqNOdpAlSXnP37YvJSgmJvmGVoJDdeI,1746
|
7
|
+
nonebot_plugin_werewolf/game.py,sha256=oGuqV4zmuX95CH7zjtx7wMFzQjzObWXbnnO8PHSnzYk,15525
|
8
|
+
nonebot_plugin_werewolf/matchers.py,sha256=D8F2FsV03YhBfXCUeUJmpSFvMk9Z7F0lKeFx-lRN9To,2281
|
9
|
+
nonebot_plugin_werewolf/ob11_ext.py,sha256=I6bPCv5SgAStTJuvBl5F7wqgiksWeFkb4R7n06jXprA,2399
|
10
|
+
nonebot_plugin_werewolf/player.py,sha256=KjGtfMT7mZG-ACfn5z2z0UJDlLH6t1-6mls_H4UxtWU,14175
|
11
|
+
nonebot_plugin_werewolf/player_set.py,sha256=pEXyjmA7INog23i5-TmuJA559y5eKUehrPxHcnzWCTU,2571
|
12
|
+
nonebot_plugin_werewolf/utils.py,sha256=8ddVr_PyObxZ8PfWry_-IXnH2dys2bTowKmHfqVbhPE,6233
|
13
|
+
nonebot_plugin_werewolf-1.0.6.dist-info/RECORD,,
|
@@ -1,13 +0,0 @@
|
|
1
|
-
nonebot_plugin_werewolf-1.0.4.dist-info/METADATA,sha256=AkymAo7r7BJckqlRFyjnFBGYCKhpyREppGHPGFowtx8,5815
|
2
|
-
nonebot_plugin_werewolf-1.0.4.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
|
3
|
-
nonebot_plugin_werewolf-1.0.4.dist-info/licenses/LICENSE,sha256=B_WbEqjGr6GYVNfEJPY31T1Opik7OtgOkhRs4Ig3e2M,1064
|
4
|
-
nonebot_plugin_werewolf/__init__.py,sha256=Ia0RlpR6eFLWMI-0OyLGMbBp5W7Oh6s96r45rGWGtDw,734
|
5
|
-
nonebot_plugin_werewolf/config.py,sha256=3O63P1pjvJEwgOwxApAHbKQzvCR1zoG5UjTClZ_OJts,315
|
6
|
-
nonebot_plugin_werewolf/constant.py,sha256=kMn5DHXdYuOXeGpJjnG_WX9AD-OytLKXSjrww0T5OYg,1234
|
7
|
-
nonebot_plugin_werewolf/game.py,sha256=PI6j-T70Logax87fpmf8HBS3LihF7J40nespvoNFyus,13449
|
8
|
-
nonebot_plugin_werewolf/matchers.py,sha256=o0LdO7AOUjTPcNaNTHbmw5d0CcnMc7mX8oko0JT8Np4,2351
|
9
|
-
nonebot_plugin_werewolf/ob11_ext.py,sha256=I6bPCv5SgAStTJuvBl5F7wqgiksWeFkb4R7n06jXprA,2399
|
10
|
-
nonebot_plugin_werewolf/player.py,sha256=z_yVjqfDOxnQlDtDxSbdmvEapIQikkho2hCjb46zqKY,13799
|
11
|
-
nonebot_plugin_werewolf/player_set.py,sha256=ptNZb13H8DgcjQ9tm3uT2ISXnnWioQG1W4R506HlaMU,3057
|
12
|
-
nonebot_plugin_werewolf/utils.py,sha256=B8YipTx96k2Y-trrCDfzRQnmHs-yAMcU5-g8FUrRQ64,6186
|
13
|
-
nonebot_plugin_werewolf-1.0.4.dist-info/RECORD,,
|
File without changes
|
{nonebot_plugin_werewolf-1.0.4.dist-info → nonebot_plugin_werewolf-1.0.6.dist-info}/licenses/LICENSE
RENAMED
File without changes
|