nonebot-plugin-werewolf 1.0.5__py3-none-any.whl → 1.0.7__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 +5 -1
- nonebot_plugin_werewolf/constant.py +71 -15
- nonebot_plugin_werewolf/game.py +56 -34
- nonebot_plugin_werewolf/matchers.py +3 -0
- nonebot_plugin_werewolf/player.py +49 -51
- nonebot_plugin_werewolf/player_set.py +8 -6
- nonebot_plugin_werewolf/utils.py +16 -10
- {nonebot_plugin_werewolf-1.0.5.dist-info → nonebot_plugin_werewolf-1.0.7.dist-info}/METADATA +67 -11
- nonebot_plugin_werewolf-1.0.7.dist-info/RECORD +13 -0
- nonebot_plugin_werewolf-1.0.5.dist-info/RECORD +0 -13
- {nonebot_plugin_werewolf-1.0.5.dist-info → nonebot_plugin_werewolf-1.0.7.dist-info}/WHEEL +0 -0
- {nonebot_plugin_werewolf-1.0.5.dist-info → nonebot_plugin_werewolf-1.0.7.dist-info}/licenses/LICENSE +0 -0
@@ -1,10 +1,14 @@
|
|
1
1
|
from nonebot import get_plugin_config
|
2
2
|
from pydantic import BaseModel
|
3
3
|
|
4
|
+
from .constant import Role
|
5
|
+
|
4
6
|
|
5
7
|
class PluginConfig(BaseModel):
|
6
8
|
enable_poke: bool = True
|
7
|
-
|
9
|
+
role_preset: list[tuple[int, int, int, int]] | None = None
|
10
|
+
werewolf_priority: list[Role] | None = None
|
11
|
+
priesthood_proirity: list[Role] | None = None
|
8
12
|
|
9
13
|
|
10
14
|
class Config(BaseModel):
|
@@ -4,31 +4,29 @@ from dataclasses import dataclass
|
|
4
4
|
from enum import Enum, auto
|
5
5
|
from typing import TYPE_CHECKING
|
6
6
|
|
7
|
-
from .config import config
|
8
|
-
|
9
7
|
if TYPE_CHECKING:
|
10
8
|
from .player import Player
|
11
9
|
|
12
10
|
|
13
11
|
class Role(Enum):
|
14
12
|
# 狼人
|
15
|
-
|
16
|
-
|
13
|
+
Werewolf = 1
|
14
|
+
WolfKing = 2
|
17
15
|
|
18
16
|
# 神职
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
17
|
+
Prophet = 11
|
18
|
+
Witch = 12
|
19
|
+
Hunter = 13
|
20
|
+
Guard = 14
|
21
|
+
Idiot = 15
|
24
22
|
|
25
23
|
# 平民
|
26
|
-
|
24
|
+
Civilian = 0
|
27
25
|
|
28
26
|
|
29
27
|
class RoleGroup(Enum):
|
30
|
-
|
31
|
-
|
28
|
+
Werewolf = auto()
|
29
|
+
GoodGuy = auto()
|
32
30
|
|
33
31
|
|
34
32
|
class KillReason(Enum):
|
@@ -53,7 +51,20 @@ class GameState:
|
|
53
51
|
potion: tuple[Player | None, tuple[bool, bool]] = (None, (False, False))
|
54
52
|
|
55
53
|
|
56
|
-
|
54
|
+
role_name_conv: dict[Role | RoleGroup, str] = {
|
55
|
+
Role.Werewolf: "狼人",
|
56
|
+
Role.WolfKing: "狼王",
|
57
|
+
Role.Prophet: "预言家",
|
58
|
+
Role.Witch: "女巫",
|
59
|
+
Role.Hunter: "猎人",
|
60
|
+
Role.Guard: "守卫",
|
61
|
+
Role.Idiot: "白痴",
|
62
|
+
Role.Civilian: "平民",
|
63
|
+
RoleGroup.Werewolf: "狼人",
|
64
|
+
RoleGroup.GoodGuy: "好人",
|
65
|
+
}
|
66
|
+
|
67
|
+
role_preset: dict[int, tuple[int, int, int]] = {
|
57
68
|
# 总人数: (狼, 神, 民)
|
58
69
|
6: (1, 2, 3),
|
59
70
|
7: (2, 2, 3),
|
@@ -64,5 +75,50 @@ player_preset: dict[int, tuple[int, int, int]] = {
|
|
64
75
|
12: (4, 5, 3),
|
65
76
|
}
|
66
77
|
|
67
|
-
|
68
|
-
|
78
|
+
werewolf_priority: list[Role] = [
|
79
|
+
Role.Werewolf,
|
80
|
+
Role.Werewolf,
|
81
|
+
Role.WolfKing,
|
82
|
+
Role.Werewolf,
|
83
|
+
]
|
84
|
+
priesthood_proirity: list[Role] = [
|
85
|
+
Role.Witch,
|
86
|
+
Role.Prophet,
|
87
|
+
Role.Hunter,
|
88
|
+
Role.Guard,
|
89
|
+
Role.Idiot,
|
90
|
+
]
|
91
|
+
|
92
|
+
|
93
|
+
def __apply_config():
|
94
|
+
from .config import config
|
95
|
+
|
96
|
+
global role_preset, werewolf_priority, priesthood_proirity
|
97
|
+
|
98
|
+
if config.role_preset is not None:
|
99
|
+
for preset in config.role_preset:
|
100
|
+
if preset[0] != preset[1:]:
|
101
|
+
raise RuntimeError(
|
102
|
+
"配置项 `role_preset` 错误: "
|
103
|
+
f"预设总人数为 {preset[0]}, 实际总人数为 {sum(preset[1:])}"
|
104
|
+
)
|
105
|
+
role_preset |= {i[0]: i[1:] for i in config.role_preset}
|
106
|
+
|
107
|
+
if (priority := config.werewolf_priority) is not None:
|
108
|
+
min_length = max(i[0] for i in role_preset.values())
|
109
|
+
if len(priority) < min_length:
|
110
|
+
raise RuntimeError(
|
111
|
+
f"配置项 `werewolf_priority` 错误: 应至少为 {min_length} 项"
|
112
|
+
)
|
113
|
+
werewolf_priority = priority
|
114
|
+
|
115
|
+
if (priority := config.priesthood_proirity) is not None:
|
116
|
+
min_length = max(i[1] for i in role_preset.values())
|
117
|
+
if len(priority) < min_length:
|
118
|
+
raise RuntimeError(
|
119
|
+
f"配置项 `priesthood_proirity` 错误: 应至少为 {min_length} 项"
|
120
|
+
)
|
121
|
+
priesthood_proirity = priority
|
122
|
+
|
123
|
+
|
124
|
+
__apply_config()
|
nonebot_plugin_werewolf/game.py
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import asyncio
|
2
4
|
import asyncio.timeouts
|
3
5
|
import contextlib
|
@@ -8,27 +10,36 @@ from nonebot.adapters import Bot
|
|
8
10
|
from nonebot.log import logger
|
9
11
|
from nonebot_plugin_alconna import Target, UniMessage
|
10
12
|
|
11
|
-
from .constant import
|
13
|
+
from .constant import (
|
14
|
+
GameState,
|
15
|
+
GameStatus,
|
16
|
+
KillReason,
|
17
|
+
Role,
|
18
|
+
RoleGroup,
|
19
|
+
priesthood_proirity,
|
20
|
+
role_preset,
|
21
|
+
werewolf_priority,
|
22
|
+
)
|
12
23
|
from .player import Player
|
13
24
|
from .player_set import PlayerSet
|
14
25
|
from .utils import InputStore
|
15
26
|
|
16
27
|
starting_games: dict[str, dict[str, str]] = {}
|
17
|
-
running_games: dict[str,
|
28
|
+
running_games: dict[str, Game] = {}
|
18
29
|
|
19
30
|
|
20
|
-
def init_players(bot: Bot, game:
|
21
|
-
preset =
|
31
|
+
def init_players(bot: Bot, game: Game, players: dict[str, str]) -> PlayerSet:
|
32
|
+
preset = role_preset.get(len(players))
|
22
33
|
if preset is None:
|
23
34
|
raise ValueError(
|
24
35
|
f"玩家人数不符: "
|
25
|
-
f"应为 {', '.join(map(str,
|
36
|
+
f"应为 {', '.join(map(str, role_preset))} 人, 传入{len(players)}人"
|
26
37
|
)
|
27
38
|
|
28
39
|
roles: list[Role] = []
|
29
|
-
roles.extend([
|
30
|
-
roles.extend([
|
31
|
-
roles.extend([Role
|
40
|
+
roles.extend(werewolf_priority[: preset[0]])
|
41
|
+
roles.extend(priesthood_proirity[: preset[1]])
|
42
|
+
roles.extend([Role.Civilian] * preset[2])
|
32
43
|
|
33
44
|
r = random.Random(time.time())
|
34
45
|
shuffled: list[Role] = []
|
@@ -82,20 +93,20 @@ class Game:
|
|
82
93
|
|
83
94
|
def at_all(self) -> UniMessage:
|
84
95
|
msg = UniMessage()
|
85
|
-
for p in sorted(self.players, key=lambda p: (p.
|
96
|
+
for p in sorted(self.players, key=lambda p: (p.role_name, p.user_id)):
|
86
97
|
msg.at(p.user_id)
|
87
98
|
return msg
|
88
99
|
|
89
100
|
def check_game_status(self) -> GameStatus:
|
90
101
|
players = self.players.alive()
|
91
|
-
w = players.select(RoleGroup
|
92
|
-
p = players.exclude(RoleGroup
|
102
|
+
w = players.select(RoleGroup.Werewolf)
|
103
|
+
p = players.exclude(RoleGroup.Werewolf)
|
93
104
|
|
94
105
|
if w.size >= p.size:
|
95
106
|
return GameStatus.Bad
|
96
|
-
if not p.select(Role
|
107
|
+
if not p.select(Role.Civilian):
|
97
108
|
return GameStatus.Bad
|
98
|
-
if not p.exclude(Role
|
109
|
+
if not p.exclude(Role.Civilian):
|
99
110
|
return GameStatus.Bad
|
100
111
|
if not w.size:
|
101
112
|
return GameStatus.Good
|
@@ -126,7 +137,7 @@ class Game:
|
|
126
137
|
return msg.strip()
|
127
138
|
|
128
139
|
async def notify_player_role(self) -> None:
|
129
|
-
preset =
|
140
|
+
preset = role_preset[len(self.players)]
|
130
141
|
await asyncio.gather(
|
131
142
|
self.send(
|
132
143
|
self.at_all()
|
@@ -162,7 +173,7 @@ class Game:
|
|
162
173
|
) -> None:
|
163
174
|
players = self.players.alive().select(type_)
|
164
175
|
text = (
|
165
|
-
type_.
|
176
|
+
type_.role_name # Player
|
166
177
|
if isinstance(type_, Player)
|
167
178
|
else (
|
168
179
|
type_.name # Role
|
@@ -181,8 +192,8 @@ class Game:
|
|
181
192
|
players = self.players.alive()
|
182
193
|
self.state.killed = None
|
183
194
|
|
184
|
-
w = players.select(RoleGroup
|
185
|
-
await self.interact(RoleGroup
|
195
|
+
w = players.select(RoleGroup.Werewolf)
|
196
|
+
await self.interact(RoleGroup.Werewolf, 120)
|
186
197
|
if (s := w.player_selected()).size == 1:
|
187
198
|
self.state.killed = s.pop()
|
188
199
|
await w.broadcast(f"今晚选择的目标为: {self.state.killed.name}")
|
@@ -190,8 +201,8 @@ class Game:
|
|
190
201
|
await w.broadcast("狼人阵营意见未统一,此晚空刀")
|
191
202
|
|
192
203
|
# 如果女巫存活,正常交互,限时1分钟
|
193
|
-
if players.include(Role
|
194
|
-
await self.interact(Role
|
204
|
+
if players.include(Role.Witch):
|
205
|
+
await self.interact(Role.Witch, 60)
|
195
206
|
# 否则等待 5-20s
|
196
207
|
else:
|
197
208
|
await asyncio.sleep(random.uniform(5, 20))
|
@@ -228,7 +239,7 @@ class Game:
|
|
228
239
|
await self.send(
|
229
240
|
UniMessage.text("玩家 ")
|
230
241
|
.at(shoot.user_id)
|
231
|
-
.text(f" 被{shooter.
|
242
|
+
.text(f" 被{shooter.role_name}射杀, 请发表遗言\n")
|
232
243
|
.text("限时1分钟, 发送 “/stop” 结束发言")
|
233
244
|
)
|
234
245
|
await self.wait_stop(shoot, 60)
|
@@ -337,10 +348,10 @@ class Game:
|
|
337
348
|
# 狼人、预言家、守卫 同时交互,女巫在狼人后交互
|
338
349
|
await asyncio.gather(
|
339
350
|
self.select_killed(),
|
340
|
-
players.select(Role
|
341
|
-
players.select(Role
|
342
|
-
self.interact(Role
|
343
|
-
self.interact(Role
|
351
|
+
players.select(Role.Witch).broadcast("请等待狼人决定目标..."),
|
352
|
+
players.select(Role.Civilian).broadcast("请等待其他玩家结束交互..."),
|
353
|
+
self.interact(Role.Prophet, 60),
|
354
|
+
self.interact(Role.Guard, 60),
|
344
355
|
)
|
345
356
|
|
346
357
|
# 狼人击杀目标
|
@@ -354,10 +365,12 @@ class Game:
|
|
354
365
|
if killed is not None:
|
355
366
|
# 除非守卫保护或女巫使用解药,否则狼人正常击杀玩家
|
356
367
|
if not ((killed is protected) or (antidote and potioned is killed)):
|
357
|
-
await killed.kill(
|
368
|
+
await killed.kill(
|
369
|
+
KillReason.Kill, *players.select(RoleGroup.Werewolf)
|
370
|
+
)
|
358
371
|
# 如果女巫使用毒药且守卫未保护,杀死该玩家
|
359
372
|
if poison and (potioned is not None) and (potioned is not protected):
|
360
|
-
await potioned.kill(KillReason.Poison, *players.select(Role
|
373
|
+
await potioned.kill(KillReason.Poison, *players.select(Role.Witch))
|
361
374
|
|
362
375
|
day_count += 1
|
363
376
|
msg = UniMessage.text(f"『第{day_count}天』天亮了...\n")
|
@@ -401,28 +414,37 @@ class Game:
|
|
401
414
|
winner = "好人" if self.check_game_status() == GameStatus.Good else "狼人"
|
402
415
|
msg = UniMessage.text(f"🎉游戏结束,{winner}获胜\n\n")
|
403
416
|
for p in sorted(self.players, key=lambda p: (p.role.value, p.user_id)):
|
404
|
-
msg.at(p.user_id).text(f": {p.
|
417
|
+
msg.at(p.user_id).text(f": {p.role_name}\n")
|
405
418
|
await self.send(msg)
|
406
419
|
await self.send(f"玩家死亡报告:\n\n{self.show_killed_players()}")
|
407
420
|
|
408
421
|
def start(self):
|
409
|
-
|
422
|
+
event = asyncio.Event()
|
423
|
+
game_task = asyncio.create_task(self.run())
|
424
|
+
game_task.add_done_callback(lambda _: event.set())
|
410
425
|
dead_channel = asyncio.create_task(self.run_dead_channel())
|
411
426
|
|
412
427
|
async def daemon():
|
413
|
-
|
414
|
-
await asyncio.sleep(1)
|
428
|
+
await event.wait()
|
415
429
|
|
416
430
|
try:
|
417
|
-
|
431
|
+
game_task.result()
|
432
|
+
logger.info(f"{self.group.id} 的狼人杀游戏进程正常退出")
|
418
433
|
except asyncio.CancelledError as err:
|
419
|
-
logger.warning(f"
|
434
|
+
logger.warning(f"{self.group.id} 的狼人杀游戏进程被取消: {err}")
|
420
435
|
except Exception as err:
|
421
|
-
msg = f"
|
436
|
+
msg = f"{self.group.id} 的狼人杀游戏进程出现错误: {err!r}"
|
422
437
|
logger.opt(exception=err).error(msg)
|
423
438
|
await self.send(msg)
|
424
439
|
finally:
|
425
440
|
dead_channel.cancel()
|
426
441
|
running_games.pop(self.group.id, None)
|
427
442
|
|
428
|
-
|
443
|
+
def daemon_callback(task: asyncio.Task[None]):
|
444
|
+
if err := task.exception():
|
445
|
+
logger.opt(exception=err).error(
|
446
|
+
f"{self.group.id} 的狼人杀守护进程出现错误: {err!r}"
|
447
|
+
)
|
448
|
+
|
449
|
+
running_games[self.group.id] = self
|
450
|
+
asyncio.create_task(daemon()).add_done_callback(daemon_callback)
|
@@ -4,6 +4,7 @@ from typing import Annotated
|
|
4
4
|
|
5
5
|
from nonebot import on_command, on_message
|
6
6
|
from nonebot.adapters import Bot, Event
|
7
|
+
from nonebot.exception import FinishedException
|
7
8
|
from nonebot.rule import to_me
|
8
9
|
from nonebot_plugin_alconna import MsgTarget, UniMessage, UniMsg
|
9
10
|
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
|
@@ -56,6 +57,8 @@ async def handle_start(
|
|
56
57
|
try:
|
57
58
|
async with asyncio.timeouts.timeout(5 * 60):
|
58
59
|
await prepare_game(event, players)
|
60
|
+
except FinishedException:
|
61
|
+
raise
|
59
62
|
except TimeoutError:
|
60
63
|
await UniMessage.text("游戏准备超时,已自动结束").finish()
|
61
64
|
finally:
|
@@ -3,13 +3,13 @@ from __future__ import annotations
|
|
3
3
|
import asyncio
|
4
4
|
import asyncio.timeouts
|
5
5
|
from dataclasses import dataclass
|
6
|
-
from typing import TYPE_CHECKING, ClassVar, TypeVar
|
6
|
+
from typing import TYPE_CHECKING, ClassVar, TypeVar, final
|
7
7
|
from typing_extensions import override
|
8
8
|
|
9
9
|
from nonebot.adapters import Bot
|
10
10
|
from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage
|
11
11
|
|
12
|
-
from .constant import KillReason, Role, RoleGroup
|
12
|
+
from .constant import KillReason, Role, RoleGroup, role_name_conv
|
13
13
|
from .utils import InputStore, check_index
|
14
14
|
|
15
15
|
if TYPE_CHECKING:
|
@@ -21,9 +21,14 @@ P = TypeVar("P", bound=type["Player"])
|
|
21
21
|
PLAYER_CLASS: dict[Role, type[Player]] = {}
|
22
22
|
|
23
23
|
|
24
|
-
def register_role(
|
25
|
-
|
26
|
-
|
24
|
+
def register_role(role: Role, role_group: RoleGroup, /):
|
25
|
+
def decorator(cls: P, /) -> P:
|
26
|
+
cls.role = role
|
27
|
+
cls.role_group = role_group
|
28
|
+
PLAYER_CLASS[role] = cls
|
29
|
+
return cls
|
30
|
+
|
31
|
+
return decorator
|
27
32
|
|
28
33
|
|
29
34
|
@dataclass
|
@@ -45,12 +50,14 @@ class Player:
|
|
45
50
|
kill_info: KillInfo | None = None
|
46
51
|
selected: Player | None = None
|
47
52
|
|
53
|
+
@final
|
48
54
|
def __init__(self, bot: Bot, game: Game, user: Target, name: str) -> None:
|
49
55
|
self.bot = bot
|
50
56
|
self.game = game
|
51
57
|
self.user = user
|
52
58
|
self.name = name
|
53
59
|
|
60
|
+
@final
|
54
61
|
@classmethod
|
55
62
|
def new(
|
56
63
|
cls,
|
@@ -66,23 +73,30 @@ class Player:
|
|
66
73
|
return PLAYER_CLASS[role](bot, game, user, name)
|
67
74
|
|
68
75
|
def __repr__(self) -> str:
|
69
|
-
return f"<{self.
|
76
|
+
return f"<{self.role_name}: user={self.user} alive={self.alive}>"
|
70
77
|
|
71
78
|
@property
|
72
79
|
def user_id(self) -> str:
|
73
80
|
return self.user.id
|
74
81
|
|
82
|
+
@property
|
83
|
+
def role_name(self) -> str:
|
84
|
+
return role_name_conv[self.role]
|
85
|
+
|
86
|
+
@final
|
75
87
|
async def send(self, message: str | UniMessage) -> Receipt:
|
76
88
|
if isinstance(message, str):
|
77
89
|
message = UniMessage.text(message)
|
78
90
|
|
79
91
|
return await message.send(target=self.user, bot=self.bot)
|
80
92
|
|
93
|
+
@final
|
81
94
|
async def receive(self, prompt: str | UniMessage | None = None) -> UniMessage:
|
82
95
|
if prompt:
|
83
96
|
await self.send(prompt)
|
84
97
|
return await InputStore.fetch(self.user.id)
|
85
98
|
|
99
|
+
@final
|
86
100
|
async def receive_text(self) -> str:
|
87
101
|
return (await self.receive()).extract_plain_text()
|
88
102
|
|
@@ -90,7 +104,7 @@ class Player:
|
|
90
104
|
return
|
91
105
|
|
92
106
|
async def notify_role(self) -> None:
|
93
|
-
await self.send(f"你的身份: {self.
|
107
|
+
await self.send(f"你的身份: {self.role_name}")
|
94
108
|
|
95
109
|
async def kill(
|
96
110
|
self,
|
@@ -136,9 +150,9 @@ class CanShoot(Player):
|
|
136
150
|
return await super().post_kill()
|
137
151
|
|
138
152
|
await self.game.send(
|
139
|
-
UniMessage.text(f"{self.
|
153
|
+
UniMessage.text(f"{self.role_name} ")
|
140
154
|
.at(self.user_id)
|
141
|
-
.text(f" 死了\n请{self.
|
155
|
+
.text(f" 死了\n请{self.role_name}决定击杀目标...")
|
142
156
|
)
|
143
157
|
|
144
158
|
self.game.state.shoot = (None, None)
|
@@ -147,14 +161,14 @@ class CanShoot(Player):
|
|
147
161
|
if shoot is not None:
|
148
162
|
self.game.state.shoot = (self, shoot)
|
149
163
|
await self.send(
|
150
|
-
UniMessage.text(f"{self.
|
164
|
+
UniMessage.text(f"{self.role_name} ")
|
151
165
|
.at(self.user_id)
|
152
166
|
.text(" 射杀了玩家 ")
|
153
167
|
.at(shoot.user_id)
|
154
168
|
)
|
155
169
|
await shoot.kill(KillReason.Shoot, self)
|
156
170
|
else:
|
157
|
-
await self.send(f"{self.
|
171
|
+
await self.send(f"{self.role_name}选择了取消技能")
|
158
172
|
return await super().post_kill()
|
159
173
|
|
160
174
|
async def shoot(self) -> Player | None:
|
@@ -181,25 +195,22 @@ class CanShoot(Player):
|
|
181
195
|
return players[selected]
|
182
196
|
|
183
197
|
|
184
|
-
@register_role
|
185
|
-
class
|
186
|
-
role: ClassVar[Role] = Role.狼人
|
187
|
-
role_group: ClassVar[RoleGroup] = RoleGroup.狼人
|
188
|
-
|
198
|
+
@register_role(Role.Werewolf, RoleGroup.Werewolf)
|
199
|
+
class Werewolf(Player):
|
189
200
|
@override
|
190
201
|
async def notify_role(self) -> None:
|
191
202
|
await super().notify_role()
|
192
|
-
partners = self.game.players.alive().select(RoleGroup
|
203
|
+
partners = self.game.players.alive().select(RoleGroup.Werewolf).exclude(self)
|
193
204
|
if partners:
|
194
205
|
await self.send(
|
195
206
|
"你的队友:\n"
|
196
|
-
+ "\n".join(f" {p.
|
207
|
+
+ "\n".join(f" {p.role_name}: {p.name}" for p in partners)
|
197
208
|
)
|
198
209
|
|
199
210
|
@override
|
200
211
|
async def interact(self) -> None:
|
201
212
|
players = self.game.players.alive()
|
202
|
-
partners = players.select(RoleGroup
|
213
|
+
partners = players.select(RoleGroup.Werewolf).exclude(self)
|
203
214
|
|
204
215
|
# 避免阻塞
|
205
216
|
def broadcast(msg: str | UniMessage):
|
@@ -209,7 +220,7 @@ class 狼人(Player):
|
|
209
220
|
if partners:
|
210
221
|
msg = (
|
211
222
|
msg.text("你的队友:\n")
|
212
|
-
.text("\n".join(f" {p.
|
223
|
+
.text("\n".join(f" {p.role_name}: {p.name}" for p in partners))
|
213
224
|
.text("\n所有私聊消息将被转发至队友\n\n")
|
214
225
|
)
|
215
226
|
await self.send(
|
@@ -243,17 +254,13 @@ class 狼人(Player):
|
|
243
254
|
self.selected = players[selected]
|
244
255
|
|
245
256
|
|
246
|
-
@register_role
|
247
|
-
class
|
248
|
-
|
249
|
-
role_group: ClassVar[RoleGroup] = RoleGroup.狼人
|
250
|
-
|
257
|
+
@register_role(Role.WolfKing, RoleGroup.Werewolf)
|
258
|
+
class WolfKing(CanShoot, Werewolf):
|
259
|
+
pass
|
251
260
|
|
252
|
-
@register_role
|
253
|
-
class 预言家(Player):
|
254
|
-
role: ClassVar[Role] = Role.预言家
|
255
|
-
role_group: ClassVar[RoleGroup] = RoleGroup.好人
|
256
261
|
|
262
|
+
@register_role(Role.Prophet, RoleGroup.GoodGuy)
|
263
|
+
class Prophet(Player):
|
257
264
|
@override
|
258
265
|
async def interact(self) -> None:
|
259
266
|
players = self.game.players.alive().exclude(self)
|
@@ -272,14 +279,12 @@ class 预言家(Player):
|
|
272
279
|
await self.send("输入错误,请发送编号选择玩家")
|
273
280
|
|
274
281
|
player = players[selected]
|
275
|
-
result =
|
282
|
+
result = role_name_conv[player.role_group]
|
276
283
|
await self.send(f"玩家 {player.name} 的阵营是『{result}』")
|
277
284
|
|
278
285
|
|
279
|
-
@register_role
|
280
|
-
class
|
281
|
-
role: ClassVar[Role] = Role.女巫
|
282
|
-
role_group: ClassVar[RoleGroup] = RoleGroup.好人
|
286
|
+
@register_role(Role.Witch, RoleGroup.GoodGuy)
|
287
|
+
class Witch(Player):
|
283
288
|
antidote: int = 1
|
284
289
|
poison: int = 1
|
285
290
|
|
@@ -365,17 +370,13 @@ class 女巫(Player):
|
|
365
370
|
await self.send(f"当前回合选择对玩家 {player.name} 使用毒药\n回合结束")
|
366
371
|
|
367
372
|
|
368
|
-
@register_role
|
369
|
-
class
|
370
|
-
|
371
|
-
role_group: ClassVar[RoleGroup] = RoleGroup.好人
|
372
|
-
|
373
|
+
@register_role(Role.Hunter, RoleGroup.GoodGuy)
|
374
|
+
class Hunter(CanShoot, Player):
|
375
|
+
pass
|
373
376
|
|
374
|
-
@register_role
|
375
|
-
class 守卫(Player):
|
376
|
-
role: ClassVar[Role] = Role.守卫
|
377
|
-
role_group: ClassVar[RoleGroup] = RoleGroup.好人
|
378
377
|
|
378
|
+
@register_role(Role.Guard, RoleGroup.GoodGuy)
|
379
|
+
class Guard(Player):
|
379
380
|
@override
|
380
381
|
async def interact(self) -> None:
|
381
382
|
players = self.game.players.alive().exclude(self)
|
@@ -403,10 +404,8 @@ class 守卫(Player):
|
|
403
404
|
await self.send(f"本回合保护的玩家: {self.selected.name}")
|
404
405
|
|
405
406
|
|
406
|
-
@register_role
|
407
|
-
class
|
408
|
-
role: ClassVar[Role] = Role.白痴
|
409
|
-
role_group: ClassVar[RoleGroup] = RoleGroup.好人
|
407
|
+
@register_role(Role.Idiot, RoleGroup.GoodGuy)
|
408
|
+
class Idiot(Player):
|
410
409
|
voted: bool = False
|
411
410
|
|
412
411
|
@override
|
@@ -433,7 +432,6 @@ class 白痴(Player):
|
|
433
432
|
return await super().vote(players)
|
434
433
|
|
435
434
|
|
436
|
-
@register_role
|
437
|
-
class
|
438
|
-
|
439
|
-
role_group: ClassVar[RoleGroup] = RoleGroup.好人
|
435
|
+
@register_role(Role.Civilian, RoleGroup.GoodGuy)
|
436
|
+
class Civilian(Player):
|
437
|
+
pass
|
@@ -1,3 +1,5 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import asyncio
|
2
4
|
import asyncio.timeouts
|
3
5
|
|
@@ -12,23 +14,23 @@ class PlayerSet(set[Player]):
|
|
12
14
|
def size(self) -> int:
|
13
15
|
return len(self)
|
14
16
|
|
15
|
-
def alive(self) ->
|
17
|
+
def alive(self) -> PlayerSet:
|
16
18
|
return PlayerSet(p for p in self if p.alive)
|
17
19
|
|
18
|
-
def dead(self) ->
|
20
|
+
def dead(self) -> PlayerSet:
|
19
21
|
return PlayerSet(p for p in self if not p.alive)
|
20
22
|
|
21
|
-
def include(self, *types: Player | Role | RoleGroup) ->
|
23
|
+
def include(self, *types: Player | Role | RoleGroup) -> PlayerSet:
|
22
24
|
return PlayerSet(
|
23
25
|
player
|
24
26
|
for player in self
|
25
27
|
if (player in types or player.role in types or player.role_group in types)
|
26
28
|
)
|
27
29
|
|
28
|
-
def select(self, *types: Player | Role | RoleGroup) ->
|
30
|
+
def select(self, *types: Player | Role | RoleGroup) -> PlayerSet:
|
29
31
|
return self.include(*types)
|
30
32
|
|
31
|
-
def exclude(self, *types: Player | Role | RoleGroup) ->
|
33
|
+
def exclude(self, *types: Player | Role | RoleGroup) -> PlayerSet:
|
32
34
|
return PlayerSet(
|
33
35
|
player
|
34
36
|
for player in self
|
@@ -39,7 +41,7 @@ class PlayerSet(set[Player]):
|
|
39
41
|
)
|
40
42
|
)
|
41
43
|
|
42
|
-
def player_selected(self) ->
|
44
|
+
def player_selected(self) -> PlayerSet:
|
43
45
|
return PlayerSet(p.selected for p in self.alive() if p.selected is not None)
|
44
46
|
|
45
47
|
def sorted(self) -> list[Player]:
|
nonebot_plugin_werewolf/utils.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
import asyncio
|
2
2
|
import asyncio.timeouts
|
3
|
+
import re
|
3
4
|
from collections import defaultdict
|
4
5
|
from typing import Annotated, Any, ClassVar
|
5
6
|
|
@@ -9,7 +10,7 @@ from nonebot.rule import to_me
|
|
9
10
|
from nonebot_plugin_alconna import MsgTarget, UniMessage, UniMsg
|
10
11
|
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
|
11
12
|
|
12
|
-
from .constant import
|
13
|
+
from .constant import role_preset
|
13
14
|
|
14
15
|
|
15
16
|
def check_index(text: str, arrlen: int) -> int | None:
|
@@ -47,7 +48,7 @@ def user_in_game(user_id: str, group_id: str | None) -> bool:
|
|
47
48
|
if group_id is not None and group_id not in running_games:
|
48
49
|
return False
|
49
50
|
games = running_games.values() if group_id is None else [running_games[group_id]]
|
50
|
-
for game
|
51
|
+
for game in games:
|
51
52
|
return any(user_id == player.user_id for player in game.players)
|
52
53
|
return False
|
53
54
|
|
@@ -92,13 +93,18 @@ async def _prepare_game_receive(
|
|
92
93
|
) -> tuple[str, str, str]:
|
93
94
|
return (
|
94
95
|
event.get_user_id(),
|
95
|
-
|
96
|
+
(
|
97
|
+
(info.user_displayname or info.user_name)
|
98
|
+
if info is not None
|
99
|
+
else event.get_user_id()
|
100
|
+
),
|
96
101
|
msg.extract_plain_text().strip(),
|
97
102
|
)
|
98
103
|
|
99
104
|
async for user, name, text in wait(default=(None, "", "")):
|
100
105
|
if user is None:
|
101
106
|
continue
|
107
|
+
name = re.sub(r"[\u2066-\u2069]", "", name)
|
102
108
|
await queue.put((user, name, text))
|
103
109
|
|
104
110
|
|
@@ -114,19 +120,19 @@ async def _prepare_game_handle(
|
|
114
120
|
match (text, user == admin_id):
|
115
121
|
case ("开始游戏", True):
|
116
122
|
player_num = len(players)
|
117
|
-
if player_num < min(
|
123
|
+
if player_num < min(role_preset):
|
118
124
|
await (
|
119
|
-
msg.text(f"游戏至少需要 {min(
|
125
|
+
msg.text(f"游戏至少需要 {min(role_preset)} 人, ")
|
120
126
|
.text(f"当前已有 {player_num} 人")
|
121
127
|
.send()
|
122
128
|
)
|
123
|
-
elif player_num > max(
|
129
|
+
elif player_num > max(role_preset):
|
124
130
|
await (
|
125
|
-
msg.text(f"游戏最多需要 {max(
|
131
|
+
msg.text(f"游戏最多需要 {max(role_preset)} 人, ")
|
126
132
|
.text(f"当前已有 {player_num} 人")
|
127
133
|
.send()
|
128
134
|
)
|
129
|
-
elif player_num not in
|
135
|
+
elif player_num not in role_preset:
|
130
136
|
await (
|
131
137
|
msg.text(f"不存在总人数为 {player_num} 的预设, ")
|
132
138
|
.text("无法开始游戏")
|
@@ -167,8 +173,8 @@ async def _prepare_game_handle(
|
|
167
173
|
|
168
174
|
case ("当前玩家", _):
|
169
175
|
msg.text("\n当前玩家:\n")
|
170
|
-
for name in players.values():
|
171
|
-
msg.text(f"\n{name}")
|
176
|
+
for idx, name in enumerate(players.values(), 1):
|
177
|
+
msg.text(f"\n{idx}. {name}")
|
172
178
|
await msg.send()
|
173
179
|
|
174
180
|
|
{nonebot_plugin_werewolf-1.0.5.dist-info → nonebot_plugin_werewolf-1.0.7.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.7
|
4
4
|
Summary: Default template for PDM package
|
5
5
|
Author-Email: wyf7685 <wyf7685@163.com>
|
6
6
|
License: MIT
|
@@ -88,10 +88,14 @@ _✨ 简单的狼人杀插件 ✨_
|
|
88
88
|
|
89
89
|
在 nonebot2 项目的`.env`文件中添加下表中的必填配置
|
90
90
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
91
|
+
| 配置项 | 必填 | 默认值 | 说明 |
|
92
|
+
| :-----------------------------: | :--: | :----: | :-----------------------------------------------------------: |
|
93
|
+
| `werewolf__enable_poke` | 否 | `True` | 是否使用戳一戳简化操作流程<br/>仅在 `OneBot V11` 适配器下生效 |
|
94
|
+
| `werewolf__role_preset` | 否 | - | 覆写插件内置的职业预设 |
|
95
|
+
| `werewolf__werewolf_priority` | 否 | - | 自定义狼人职业优先级 |
|
96
|
+
| `werewolf__priesthood_proirity` | 否 | - | 自定义神职职业优先级 |
|
97
|
+
|
98
|
+
`werewolf__role_preset`, `werewolf__werewolf_priority`, `werewolf__priesthood_proirity` 的配置格式请参考 [`游戏内容`](#游戏内容) 部分
|
95
99
|
|
96
100
|
## 🎉 使用
|
97
101
|
|
@@ -149,15 +153,15 @@ _其他交互参考游戏内提示_
|
|
149
153
|
| 11 | 3 | 5 | 3 |
|
150
154
|
| 12 | 4 | 5 | 3 |
|
151
155
|
|
152
|
-
职业预设可以通过配置项 `
|
156
|
+
职业预设可以通过配置项 `werewolf__role_preset` 修改
|
153
157
|
|
154
158
|
<details>
|
155
159
|
<summary>示例</summary>
|
156
160
|
|
157
|
-
配置项 `
|
161
|
+
配置项 `werewolf__role_preset`
|
158
162
|
|
159
163
|
```env
|
160
|
-
|
164
|
+
werewolf__role_preset='
|
161
165
|
[
|
162
166
|
[6, 1, 3, 2],
|
163
167
|
[7, 2, 3, 2]
|
@@ -170,16 +174,67 @@ werewolf__override_preset='
|
|
170
174
|
</details>
|
171
175
|
<br/>
|
172
176
|
|
173
|
-
|
177
|
+
对于`狼人`和`神职`的职业分配,默认有如下优先级:
|
174
178
|
|
175
179
|
- `狼人`: `狼人`, `狼人`, `狼王`, `狼人`
|
176
|
-
- `神职`: `预言家`,
|
180
|
+
- `神职`: `女巫`, `预言家`, `猎人`, `守卫`, `白痴`
|
181
|
+
|
182
|
+
职业分配优先级可以通过配置项 `werewolf__werewolf_priority` 和 `werewolf__priesthood_proirity` 修改
|
183
|
+
|
184
|
+
<details>
|
185
|
+
<summary>示例</summary>
|
186
|
+
|
187
|
+
#### 配置项 `werewolf__werewolf_priority`
|
188
|
+
|
189
|
+
```env
|
190
|
+
werewolf__werewolf_priority=[1, 2, 1, 1]
|
191
|
+
```
|
192
|
+
|
193
|
+
上述配置中,`[1, 2, 1, 1]` 表示狼人的职业优先级为 `狼人`, `狼王`, `狼人`, `狼人`
|
194
|
+
|
195
|
+
#### 配置项 `werewolf__werewolf_priority`
|
196
|
+
|
197
|
+
```env
|
198
|
+
werewolf__priesthood_proirity=[11, 12, 13, 14, 15]
|
199
|
+
```
|
200
|
+
|
201
|
+
上述配置中,`[11, 12, 13, 14, 15]` 表示神职的职业优先级为 `预言家`, `女巫`, `猎人`, `守卫`, `白痴`
|
202
|
+
|
203
|
+
#### 职业与数字的对应关系
|
204
|
+
|
205
|
+
上述配置示例中有大量~~意义不明的~~数字, 其对应的是 [`这里`](./nonebot_plugin_werewolf/constant.py) 的枚举类 `Role` 的值
|
206
|
+
|
207
|
+
以下列出目前的枚举值供参考
|
208
|
+
|
209
|
+
| 职业 | 枚举值 |
|
210
|
+
| -------- | ------ |
|
211
|
+
| `狼人` | `1` |
|
212
|
+
| `狼王` | `2` |
|
213
|
+
| `预言家` | `11` |
|
214
|
+
| `女巫` | `12` |
|
215
|
+
| `猎人` | `13` |
|
216
|
+
| `守卫` | `14` |
|
217
|
+
| `白痴` | `15` |
|
218
|
+
| `平民` | `0` |
|
219
|
+
|
220
|
+
</details>
|
177
221
|
|
178
222
|
## 📝 更新日志
|
179
223
|
|
180
224
|
<details>
|
181
225
|
<summary>更新日志</summary>
|
182
226
|
|
227
|
+
<!-- CHANGELOG -->
|
228
|
+
|
229
|
+
- 2024.09.04 v1.0.7
|
230
|
+
|
231
|
+
- 优先使用群名片作为玩家名
|
232
|
+
- 支持通过配置项修改职业分配优先级
|
233
|
+
|
234
|
+
- 2024.09.03 v1.0.6
|
235
|
+
|
236
|
+
- 修复预言家查验狼王返回好人的 bug
|
237
|
+
|
183
238
|
- 2024.09.03 v1.0.5
|
184
239
|
|
185
240
|
- 优化玩家交互体验
|
@@ -187,7 +242,7 @@ werewolf__override_preset='
|
|
187
242
|
|
188
243
|
- 2024.08.31 v1.0.1
|
189
244
|
|
190
|
-
-
|
245
|
+
- 支持通过配置项修改职业预设
|
191
246
|
|
192
247
|
- 2024.08.31 v1.0.0
|
193
248
|
|
@@ -201,3 +256,4 @@ werewolf__override_preset='
|
|
201
256
|
- [`nonebot/plugin-alconna`](https://github.com/nonebot/plugin-alconna): 跨平台的消息处理接口
|
202
257
|
- [`noneplugin/nonebot-plugin-userinfo`](https://github.com/noneplugin/nonebot-plugin-userinfo): 用户信息获取
|
203
258
|
- [`RF-Tar-Railt/nonebot-plugin-waiter`](https://github.com/RF-Tar-Railt/nonebot-plugin-waiter): 灵活获取用户输入
|
259
|
+
- `热心群友`: 协助测试插件
|
@@ -0,0 +1,13 @@
|
|
1
|
+
nonebot_plugin_werewolf-1.0.7.dist-info/METADATA,sha256=6Zjo-smpLFf6SX6TLAefU4OFCamMLTZmIKsI5QgzXOs,8479
|
2
|
+
nonebot_plugin_werewolf-1.0.7.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
|
3
|
+
nonebot_plugin_werewolf-1.0.7.dist-info/licenses/LICENSE,sha256=B_WbEqjGr6GYVNfEJPY31T1Opik7OtgOkhRs4Ig3e2M,1064
|
4
|
+
nonebot_plugin_werewolf/__init__.py,sha256=sO1RtjlQkMIdgMEhS8bVwhJpCo8dd36Z_Fc5y-YpEoY,734
|
5
|
+
nonebot_plugin_werewolf/config.py,sha256=VSXAWZa3u6_ehXzS0q_qYpPYr5GRqHzs-WR0zHEJiRI,437
|
6
|
+
nonebot_plugin_werewolf/constant.py,sha256=w-KWhLTq1_uAVy0ALER-yCn0J1r5Wr07xYvSYyHNHsw,2859
|
7
|
+
nonebot_plugin_werewolf/game.py,sha256=ZBOM_yK8Dux0nu6om7_PruH4Kr82-JVOPfpD5JcOFdA,15909
|
8
|
+
nonebot_plugin_werewolf/matchers.py,sha256=nRnXYNjwM_Pp-Z1tBoMwd2TrxCPCB5du_qMHbXlZw3w,2373
|
9
|
+
nonebot_plugin_werewolf/ob11_ext.py,sha256=I6bPCv5SgAStTJuvBl5F7wqgiksWeFkb4R7n06jXprA,2399
|
10
|
+
nonebot_plugin_werewolf/player.py,sha256=sAT8qI-zGLgjQJqWwwq-ow0vaWFdFlpMP0sWEOj-ujg,13817
|
11
|
+
nonebot_plugin_werewolf/player_set.py,sha256=WXmJtL3jbfkFCcKbzLCLu70elhBJE5DJwPA3ENNoKJM,2595
|
12
|
+
nonebot_plugin_werewolf/utils.py,sha256=EhrlYgDLyNZbpRCGDgpcUnpp8RWJBiZ6l8rDdC5hHvM,6396
|
13
|
+
nonebot_plugin_werewolf-1.0.7.dist-info/RECORD,,
|
@@ -1,13 +0,0 @@
|
|
1
|
-
nonebot_plugin_werewolf-1.0.5.dist-info/METADATA,sha256=YAMSQ9RZA-0jUe8o1iXg5zLy-vNh7AEcpoo9ek_ar-E,6735
|
2
|
-
nonebot_plugin_werewolf-1.0.5.dist-info/WHEEL,sha256=rSwsxJWe3vzyR5HCwjWXQruDgschpei4h_giTm0dJVE,90
|
3
|
-
nonebot_plugin_werewolf-1.0.5.dist-info/licenses/LICENSE,sha256=B_WbEqjGr6GYVNfEJPY31T1Opik7OtgOkhRs4Ig3e2M,1064
|
4
|
-
nonebot_plugin_werewolf/__init__.py,sha256=7Kp0xiD5XhE803lRegOex0oEe3ZfCEnPOxSd1FqdhWM,734
|
5
|
-
nonebot_plugin_werewolf/config.py,sha256=3O63P1pjvJEwgOwxApAHbKQzvCR1zoG5UjTClZ_OJts,315
|
6
|
-
nonebot_plugin_werewolf/constant.py,sha256=MUqLz4jXfXPWc8wJdUXb0yaU0BcXZ_xbGM0Sn3IhAr4,1260
|
7
|
-
nonebot_plugin_werewolf/game.py,sha256=3W_oghotlxbUgp7RaFmzla-CW-MVzNdYRb7JD7Xp_UU,15437
|
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=AqeYhILjFEuPO0rXbp9dnCmUZO_dk0MODg_jXjwduyI,13986
|
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.5.dist-info/RECORD,,
|
File without changes
|
{nonebot_plugin_werewolf-1.0.5.dist-info → nonebot_plugin_werewolf-1.0.7.dist-info}/licenses/LICENSE
RENAMED
File without changes
|