nonebot-plugin-werewolf 1.1.0__py3-none-any.whl → 1.1.2__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/_timeout.py +110 -0
- nonebot_plugin_werewolf/config.py +3 -3
- nonebot_plugin_werewolf/constant.py +5 -5
- nonebot_plugin_werewolf/exception.py +1 -1
- nonebot_plugin_werewolf/game.py +36 -40
- nonebot_plugin_werewolf/matchers.py +2 -3
- nonebot_plugin_werewolf/player.py +21 -33
- nonebot_plugin_werewolf/player_set.py +3 -3
- nonebot_plugin_werewolf/utils.py +0 -1
- {nonebot_plugin_werewolf-1.1.0.dist-info → nonebot_plugin_werewolf-1.1.2.dist-info}/METADATA +21 -3
- nonebot_plugin_werewolf-1.1.2.dist-info/RECORD +16 -0
- {nonebot_plugin_werewolf-1.1.0.dist-info → nonebot_plugin_werewolf-1.1.2.dist-info}/WHEEL +1 -1
- nonebot_plugin_werewolf-1.1.0.dist-info/RECORD +0 -15
- {nonebot_plugin_werewolf-1.1.0.dist-info → nonebot_plugin_werewolf-1.1.2.dist-info}/LICENSE +0 -0
- {nonebot_plugin_werewolf-1.1.0.dist-info → nonebot_plugin_werewolf-1.1.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,110 @@
|
|
1
|
+
import asyncio
|
2
|
+
import enum
|
3
|
+
import sys
|
4
|
+
from types import TracebackType
|
5
|
+
from typing import final
|
6
|
+
|
7
|
+
if sys.version_info >= (3, 11):
|
8
|
+
from asyncio.timeouts import timeout as timeout
|
9
|
+
|
10
|
+
else:
|
11
|
+
# ruff: noqa: S101
|
12
|
+
|
13
|
+
class _State(enum.Enum):
|
14
|
+
CREATED = "created"
|
15
|
+
ENTERED = "active"
|
16
|
+
EXPIRING = "expiring"
|
17
|
+
EXPIRED = "expired"
|
18
|
+
EXITED = "finished"
|
19
|
+
|
20
|
+
@final
|
21
|
+
class Timeout:
|
22
|
+
def __init__(self, when: float | None) -> None:
|
23
|
+
self._state = _State.CREATED
|
24
|
+
self._timeout_handler: asyncio.Handle | None = None
|
25
|
+
self._task: asyncio.Task | None = None
|
26
|
+
if when is not None:
|
27
|
+
when = asyncio.get_running_loop().time() + when
|
28
|
+
self._when = when
|
29
|
+
|
30
|
+
def when(self) -> float | None:
|
31
|
+
return self._when
|
32
|
+
|
33
|
+
def reschedule(self, when: float | None) -> None:
|
34
|
+
if self._state is not _State.ENTERED:
|
35
|
+
if self._state is _State.CREATED:
|
36
|
+
raise RuntimeError("Timeout has not been entered")
|
37
|
+
raise RuntimeError(
|
38
|
+
f"Cannot change state of {self._state.value} Timeout",
|
39
|
+
)
|
40
|
+
|
41
|
+
self._when = when
|
42
|
+
|
43
|
+
if self._timeout_handler is not None:
|
44
|
+
self._timeout_handler.cancel()
|
45
|
+
|
46
|
+
if when is None:
|
47
|
+
self._timeout_handler = None
|
48
|
+
else:
|
49
|
+
loop = asyncio.get_running_loop()
|
50
|
+
if when <= loop.time():
|
51
|
+
self._timeout_handler = loop.call_soon(self._on_timeout)
|
52
|
+
else:
|
53
|
+
self._timeout_handler = loop.call_at(when, self._on_timeout)
|
54
|
+
|
55
|
+
def expired(self) -> bool:
|
56
|
+
return self._state in (_State.EXPIRING, _State.EXPIRED)
|
57
|
+
|
58
|
+
def __repr__(self) -> str:
|
59
|
+
info = [""]
|
60
|
+
if self._state is _State.ENTERED:
|
61
|
+
when = round(self._when, 3) if self._when is not None else None
|
62
|
+
info.append(f"when={when}")
|
63
|
+
info_str = " ".join(info)
|
64
|
+
return f"<Timeout [{self._state.value}]{info_str}>"
|
65
|
+
|
66
|
+
async def __aenter__(self) -> "Timeout":
|
67
|
+
if self._state is not _State.CREATED:
|
68
|
+
raise RuntimeError("Timeout has already been entered")
|
69
|
+
task = asyncio.current_task()
|
70
|
+
if task is None:
|
71
|
+
raise RuntimeError("Timeout should be used inside a task")
|
72
|
+
self._state = _State.ENTERED
|
73
|
+
self._task = task
|
74
|
+
self.reschedule(self._when)
|
75
|
+
return self
|
76
|
+
|
77
|
+
async def __aexit__(
|
78
|
+
self,
|
79
|
+
exc_type: type[BaseException] | None,
|
80
|
+
exc_val: BaseException | None,
|
81
|
+
exc_tb: TracebackType | None,
|
82
|
+
) -> bool | None:
|
83
|
+
assert self._state in (_State.ENTERED, _State.EXPIRING)
|
84
|
+
|
85
|
+
if self._timeout_handler is not None:
|
86
|
+
self._timeout_handler.cancel()
|
87
|
+
self._timeout_handler = None
|
88
|
+
|
89
|
+
if self._state is _State.EXPIRING:
|
90
|
+
self._state = _State.EXPIRED
|
91
|
+
|
92
|
+
if exc_type is asyncio.CancelledError:
|
93
|
+
raise TimeoutError from exc_val
|
94
|
+
elif self._state is _State.ENTERED:
|
95
|
+
self._state = _State.EXITED
|
96
|
+
|
97
|
+
return None
|
98
|
+
|
99
|
+
def _on_timeout(self) -> None:
|
100
|
+
assert self._state is _State.ENTERED
|
101
|
+
assert self._task is not None
|
102
|
+
self._task.cancel()
|
103
|
+
self._state = _State.EXPIRING
|
104
|
+
self._timeout_handler = None
|
105
|
+
|
106
|
+
def timeout(delay: float | None) -> Timeout:
|
107
|
+
return Timeout(delay)
|
108
|
+
|
109
|
+
|
110
|
+
__all__ = ["timeout"]
|
@@ -48,7 +48,7 @@ class PluginConfig(BaseModel):
|
|
48
48
|
if isinstance(self.role_preset, list):
|
49
49
|
for preset in self.role_preset:
|
50
50
|
if preset[0] != sum(preset[1:]):
|
51
|
-
raise
|
51
|
+
raise ValueError(
|
52
52
|
"配置项 `role_preset` 错误: "
|
53
53
|
f"预设总人数为 {preset[0]}, 实际总人数为 {sum(preset[1:])} "
|
54
54
|
f"({', '.join(map(str, preset[1:]))})"
|
@@ -60,13 +60,13 @@ class PluginConfig(BaseModel):
|
|
60
60
|
|
61
61
|
min_length = max(i[0] for i in self.role_preset.values())
|
62
62
|
if len(self.werewolf_priority) < min_length:
|
63
|
-
raise
|
63
|
+
raise ValueError(
|
64
64
|
f"配置项 `werewolf_priority` 错误: 应至少为 {min_length} 项"
|
65
65
|
)
|
66
66
|
|
67
67
|
min_length = max(i[1] for i in self.role_preset.values())
|
68
68
|
if len(self.priesthood_proirity) < min_length:
|
69
|
-
raise
|
69
|
+
raise ValueError(
|
70
70
|
f"配置项 `priesthood_proirity` 错误: 应至少为 {min_length} 项"
|
71
71
|
)
|
72
72
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
-
|
3
|
+
import dataclasses
|
4
4
|
from enum import Enum, auto
|
5
5
|
from typing import TYPE_CHECKING
|
6
6
|
|
@@ -43,17 +43,17 @@ class KillReason(Enum):
|
|
43
43
|
class GameStatus(Enum):
|
44
44
|
GoodGuy = auto()
|
45
45
|
Werewolf = auto()
|
46
|
-
Unset = auto()
|
47
46
|
Joker = auto()
|
48
47
|
|
49
48
|
|
50
|
-
@dataclass
|
49
|
+
@dataclasses.dataclass
|
51
50
|
class GameState:
|
52
51
|
day: int
|
53
52
|
killed: Player | None = None
|
54
53
|
shoot: tuple[Player, Player] | tuple[None, None] = (None, None)
|
55
|
-
|
56
|
-
|
54
|
+
antidote: set[Player] = dataclasses.field(default_factory=set)
|
55
|
+
poison: set[Player] = dataclasses.field(default_factory=set)
|
56
|
+
protected: set[Player] = dataclasses.field(default_factory=set)
|
57
57
|
|
58
58
|
|
59
59
|
role_name_conv: dict[Role | RoleGroup, str] = {
|
nonebot_plugin_werewolf/game.py
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import asyncio
|
4
|
-
import asyncio.timeouts
|
5
4
|
import contextlib
|
6
|
-
import
|
7
|
-
import time
|
5
|
+
import secrets
|
8
6
|
from typing import TYPE_CHECKING, NoReturn
|
9
7
|
|
10
8
|
from nonebot.log import logger
|
11
9
|
from nonebot_plugin_alconna import At, Target, UniMessage
|
12
10
|
|
11
|
+
from ._timeout import timeout
|
13
12
|
from .config import config
|
14
13
|
from .constant import GameState, GameStatus, KillReason, Role, RoleGroup, role_name_conv
|
15
|
-
from .exception import
|
14
|
+
from .exception import GameFinished
|
16
15
|
from .player import Player
|
17
16
|
from .player_set import PlayerSet
|
18
17
|
from .utils import InputStore
|
@@ -35,20 +34,19 @@ def init_players(bot: Bot, game: Game, players: dict[str, str]) -> PlayerSet:
|
|
35
34
|
f"应为 {', '.join(map(str, role_preset))} 人, 传入{len(players)}人"
|
36
35
|
)
|
37
36
|
|
37
|
+
w, p, c = preset
|
38
38
|
roles: list[Role] = []
|
39
|
-
roles.extend(config.werewolf_priority[:
|
40
|
-
roles.extend(config.priesthood_proirity[:
|
41
|
-
roles.extend([Role.Civilian] *
|
39
|
+
roles.extend(config.werewolf_priority[:w])
|
40
|
+
roles.extend(config.priesthood_proirity[:p])
|
41
|
+
roles.extend([Role.Civilian] * c)
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
if roles.count(Role.Civilian) >= 2 and r.random() <= config.joker_probability:
|
43
|
+
if c >= 2 and secrets.randbelow(100) <= config.joker_probability * 100:
|
46
44
|
roles.remove(Role.Civilian)
|
47
45
|
roles.append(Role.Joker)
|
48
46
|
|
49
47
|
shuffled: list[Role] = []
|
50
|
-
|
51
|
-
idx =
|
48
|
+
while roles:
|
49
|
+
idx = secrets.randbelow(len(roles))
|
52
50
|
shuffled.append(roles.pop(idx))
|
53
51
|
|
54
52
|
logger.debug(f"职业分配: {shuffled}")
|
@@ -107,25 +105,23 @@ class Game:
|
|
107
105
|
msg.at(p.user_id)
|
108
106
|
return msg
|
109
107
|
|
110
|
-
def check_game_status(self) ->
|
108
|
+
def check_game_status(self) -> None:
|
111
109
|
players = self.players.alive()
|
112
110
|
w = players.select(RoleGroup.Werewolf)
|
113
111
|
p = players.exclude(RoleGroup.Werewolf)
|
114
112
|
|
115
113
|
# 狼人数量大于其他职业数量
|
116
114
|
if w.size >= p.size:
|
117
|
-
raise
|
115
|
+
raise GameFinished(GameStatus.Werewolf)
|
118
116
|
# 屠边-村民/中立全灭
|
119
117
|
if not p.select(Role.Civilian, RoleGroup.Others).size:
|
120
|
-
raise
|
118
|
+
raise GameFinished(GameStatus.Werewolf)
|
121
119
|
# 屠边-神职全灭
|
122
120
|
if not p.exclude(Role.Civilian).size:
|
123
|
-
raise
|
121
|
+
raise GameFinished(GameStatus.Werewolf)
|
124
122
|
# 狼人全灭
|
125
123
|
if not w.size:
|
126
|
-
raise
|
127
|
-
|
128
|
-
return GameStatus.Unset
|
124
|
+
raise GameFinished(GameStatus.GoodGuy)
|
129
125
|
|
130
126
|
def show_killed_players(self) -> str:
|
131
127
|
msg = ""
|
@@ -177,7 +173,7 @@ class Game:
|
|
177
173
|
break
|
178
174
|
|
179
175
|
with contextlib.suppress(TimeoutError):
|
180
|
-
async with
|
176
|
+
async with timeout(timeout_secs):
|
181
177
|
await asyncio.gather(*[wait(p) for p in players])
|
182
178
|
|
183
179
|
async def interact(
|
@@ -220,7 +216,7 @@ class Game:
|
|
220
216
|
await self.interact(Role.Witch, 60)
|
221
217
|
# 否则等待 5-20s
|
222
218
|
else:
|
223
|
-
await asyncio.sleep(
|
219
|
+
await asyncio.sleep(5 + secrets.randbelow(15))
|
224
220
|
|
225
221
|
async def handle_new_dead(self, players: Player | PlayerSet) -> None:
|
226
222
|
if isinstance(players, Player):
|
@@ -369,28 +365,30 @@ class Game:
|
|
369
365
|
self.interact(Role.Prophet, 60),
|
370
366
|
self.interact(Role.Guard, 60),
|
371
367
|
players.select(Role.Witch).broadcast("请等待狼人决定目标..."),
|
372
|
-
players.
|
373
|
-
|
374
|
-
),
|
368
|
+
players.exclude(
|
369
|
+
RoleGroup.Werewolf, Role.Prophet, Role.Witch, Role.Guard
|
370
|
+
).broadcast("请等待其他玩家结束交互..."),
|
375
371
|
)
|
376
372
|
|
377
373
|
# 狼人击杀目标
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
potioned, (antidote, poison) = self.state.potion
|
383
|
-
|
384
|
-
# 狼人未空刀,除非守卫保护或女巫使用解药,否则狼人正常击杀玩家
|
385
|
-
if killed is not None and (
|
386
|
-
not ((killed is protected) or (antidote and potioned is killed))
|
374
|
+
if (
|
375
|
+
(killed := self.state.killed) is not None # 狼人未空刀
|
376
|
+
and killed not in self.state.protected # 守卫保护
|
377
|
+
and killed not in self.state.antidote # 女巫使用解药
|
387
378
|
):
|
379
|
+
# 狼人正常击杀玩家
|
388
380
|
await killed.kill(
|
389
|
-
KillReason.Werewolf,
|
381
|
+
KillReason.Werewolf,
|
382
|
+
*players.select(RoleGroup.Werewolf),
|
390
383
|
)
|
391
|
-
|
392
|
-
|
393
|
-
|
384
|
+
|
385
|
+
# 女巫操作目标
|
386
|
+
for witch in self.state.poison:
|
387
|
+
if witch.selected is None:
|
388
|
+
continue
|
389
|
+
if witch.selected not in self.state.protected: # 守卫未保护
|
390
|
+
# 女巫毒杀玩家
|
391
|
+
await witch.selected.kill(KillReason.Poison, witch)
|
394
392
|
|
395
393
|
day_count += 1
|
396
394
|
msg = UniMessage.text(f"『第{day_count}天』天亮了...\n")
|
@@ -440,8 +438,6 @@ class Game:
|
|
440
438
|
winner = "狼人"
|
441
439
|
case GameStatus.Joker:
|
442
440
|
winner = "小丑"
|
443
|
-
case GameStatus.Unset:
|
444
|
-
raise RuntimeError(f"错误的游戏状态: {status!r}")
|
445
441
|
|
446
442
|
msg = UniMessage.text(f"🎉游戏结束,{winner}获胜\n\n")
|
447
443
|
for p in sorted(self.players, key=lambda p: (p.role.value, p.user_id)):
|
@@ -462,7 +458,7 @@ class Game:
|
|
462
458
|
game_task.result()
|
463
459
|
except asyncio.CancelledError:
|
464
460
|
logger.warning(f"{self.group.id} 的狼人杀游戏进程被取消")
|
465
|
-
except
|
461
|
+
except GameFinished as result:
|
466
462
|
await self.handle_game_finish(result.status)
|
467
463
|
logger.info(f"{self.group.id} 的狼人杀游戏进程正常退出")
|
468
464
|
except Exception as err:
|
@@ -1,5 +1,3 @@
|
|
1
|
-
import asyncio
|
2
|
-
import asyncio.timeouts
|
3
1
|
from typing import Annotated
|
4
2
|
|
5
3
|
from nonebot import on_command, on_message
|
@@ -9,6 +7,7 @@ from nonebot.rule import to_me
|
|
9
7
|
from nonebot_plugin_alconna import MsgTarget, UniMessage, UniMsg
|
10
8
|
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
|
11
9
|
|
10
|
+
from ._timeout import timeout
|
12
11
|
from .game import Game
|
13
12
|
from .ob11_ext import ob11_ext_enabled
|
14
13
|
from .utils import InputStore, is_group, prepare_game, rule_in_game, rule_not_in_game
|
@@ -52,7 +51,7 @@ async def handle_start(
|
|
52
51
|
players = {admin_id: admin_info.user_name}
|
53
52
|
|
54
53
|
try:
|
55
|
-
async with
|
54
|
+
async with timeout(5 * 60):
|
56
55
|
await prepare_game(event, players)
|
57
56
|
except FinishedException:
|
58
57
|
raise
|
@@ -1,7 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import asyncio
|
4
|
-
import
|
4
|
+
import weakref
|
5
5
|
from dataclasses import dataclass
|
6
6
|
from typing import TYPE_CHECKING, ClassVar, TypeVar, final
|
7
7
|
|
@@ -10,7 +10,7 @@ from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage
|
|
10
10
|
from typing_extensions import override
|
11
11
|
|
12
12
|
from .constant import GameStatus, KillReason, Role, RoleGroup, role_name_conv
|
13
|
-
from .exception import
|
13
|
+
from .exception import GameFinished
|
14
14
|
from .utils import InputStore, check_index
|
15
15
|
|
16
16
|
if TYPE_CHECKING:
|
@@ -47,7 +47,7 @@ class Player:
|
|
47
47
|
role_group: ClassVar[RoleGroup]
|
48
48
|
|
49
49
|
bot: Bot
|
50
|
-
|
50
|
+
_game_ref: weakref.ReferenceType[Game]
|
51
51
|
user: Target
|
52
52
|
name: str
|
53
53
|
alive: bool = True
|
@@ -58,7 +58,7 @@ class Player:
|
|
58
58
|
@final
|
59
59
|
def __init__(self, bot: Bot, game: Game, user: Target, name: str) -> None:
|
60
60
|
self.bot = bot
|
61
|
-
self.
|
61
|
+
self._game_ref = weakref.ref(game)
|
62
62
|
self.user = user
|
63
63
|
self.name = name
|
64
64
|
self.killed = asyncio.Event()
|
@@ -81,6 +81,12 @@ class Player:
|
|
81
81
|
def __repr__(self) -> str:
|
82
82
|
return f"<{self.role_name}: user={self.name!r} alive={self.alive}>"
|
83
83
|
|
84
|
+
@property
|
85
|
+
def game(self) -> Game:
|
86
|
+
if game := self._game_ref():
|
87
|
+
return game
|
88
|
+
raise ValueError("Game not exist")
|
89
|
+
|
84
90
|
@property
|
85
91
|
def user_id(self) -> str:
|
86
92
|
return self.user.id
|
@@ -95,7 +101,7 @@ class Player:
|
|
95
101
|
logger.opt(colors=True).info(
|
96
102
|
f"<b><e>{self.game.group.id}</e></b> | "
|
97
103
|
f"[<b><m>{self.role_name}</m></b>] "
|
98
|
-
f"<y>{self.name}</y>(<e>{self.user_id}</e>) | "
|
104
|
+
f"<y>{self.name}</y>(<b><e>{self.user_id}</e></b>) | "
|
99
105
|
f"{text}",
|
100
106
|
)
|
101
107
|
|
@@ -307,24 +313,6 @@ class Witch(Player):
|
|
307
313
|
antidote: int = 1
|
308
314
|
poison: int = 1
|
309
315
|
|
310
|
-
def set_state(
|
311
|
-
self,
|
312
|
-
*,
|
313
|
-
antidote: Player | None = None,
|
314
|
-
posion: Player | None = None,
|
315
|
-
) -> None:
|
316
|
-
if antidote is not None:
|
317
|
-
self.antidote = 0
|
318
|
-
self.selected = antidote
|
319
|
-
self.game.state.potion = (antidote, (True, False))
|
320
|
-
elif posion is not None:
|
321
|
-
self.poison = 0
|
322
|
-
self.selected = posion
|
323
|
-
self.game.state.potion = (posion, (False, True))
|
324
|
-
else:
|
325
|
-
self.selected = None
|
326
|
-
self.game.state.potion = (None, (False, False))
|
327
|
-
|
328
316
|
async def handle_killed(self) -> bool:
|
329
317
|
msg = UniMessage()
|
330
318
|
if (killed := self.game.state.killed) is not None:
|
@@ -343,7 +331,8 @@ class Witch(Player):
|
|
343
331
|
text = await self.receive_text()
|
344
332
|
if text == "1":
|
345
333
|
self.antidote = 0
|
346
|
-
self.
|
334
|
+
self.selected = killed
|
335
|
+
self.game.state.antidote.add(killed)
|
347
336
|
await self.send(f"你对 {killed.name} 使用了解药,回合结束")
|
348
337
|
return True
|
349
338
|
if text == "/stop":
|
@@ -357,7 +346,6 @@ class Witch(Player):
|
|
357
346
|
|
358
347
|
if not self.poison:
|
359
348
|
await self.send("你没有可以使用的药水,回合结束")
|
360
|
-
self.set_state()
|
361
349
|
return
|
362
350
|
|
363
351
|
players = self.game.players.alive()
|
@@ -377,14 +365,13 @@ class Witch(Player):
|
|
377
365
|
break
|
378
366
|
if text == "/stop":
|
379
367
|
await self.send("你选择不使用毒药,回合结束")
|
380
|
-
self.set_state()
|
381
368
|
return
|
382
369
|
await self.send("输入错误: 请发送玩家编号或 “/stop”")
|
383
370
|
|
384
371
|
self.poison = 0
|
385
|
-
self.selected =
|
386
|
-
self.
|
387
|
-
await self.send(f"当前回合选择对玩家 {
|
372
|
+
self.selected = players[selected]
|
373
|
+
self.game.state.poison.add(self)
|
374
|
+
await self.send(f"当前回合选择对玩家 {self.selected.name} 使用毒药\n回合结束")
|
388
375
|
|
389
376
|
|
390
377
|
@register_role(Role.Hunter, RoleGroup.GoodGuy)
|
@@ -418,7 +405,8 @@ class Guard(Player):
|
|
418
405
|
break
|
419
406
|
await self.send("输入错误,请发送编号选择玩家")
|
420
407
|
|
421
|
-
self.
|
408
|
+
self.selected = players[selected]
|
409
|
+
self.game.state.protected.add(self.selected)
|
422
410
|
await self.send(f"本回合保护的玩家: {self.selected.name}")
|
423
411
|
|
424
412
|
|
@@ -462,11 +450,11 @@ class Joker(Player):
|
|
462
450
|
|
463
451
|
@override
|
464
452
|
async def kill(self, reason: KillReason, *killers: Player) -> bool:
|
465
|
-
|
453
|
+
await super().kill(reason, *killers)
|
466
454
|
if reason == KillReason.Vote:
|
467
455
|
self.game.killed_players.append(self)
|
468
|
-
raise
|
469
|
-
return
|
456
|
+
raise GameFinished(GameStatus.Joker)
|
457
|
+
return True
|
470
458
|
|
471
459
|
|
472
460
|
@register_role(Role.Civilian, RoleGroup.GoodGuy)
|
@@ -1,9 +1,9 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import asyncio
|
4
|
-
import asyncio.timeouts
|
5
4
|
from typing import TYPE_CHECKING
|
6
5
|
|
6
|
+
from ._timeout import timeout
|
7
7
|
from .player import Player
|
8
8
|
|
9
9
|
if TYPE_CHECKING:
|
@@ -54,13 +54,13 @@ class PlayerSet(set[Player]):
|
|
54
54
|
return sorted(self, key=lambda p: p.user_id)
|
55
55
|
|
56
56
|
async def interact(self, timeout_secs: float = 60) -> None:
|
57
|
-
async with
|
57
|
+
async with timeout(timeout_secs):
|
58
58
|
await asyncio.gather(*[p.interact() for p in self.alive()])
|
59
59
|
|
60
60
|
async def vote(self, timeout_secs: float = 60) -> dict[Player, list[Player]]:
|
61
61
|
async def vote(player: Player) -> tuple[Player, Player] | None:
|
62
62
|
try:
|
63
|
-
async with
|
63
|
+
async with timeout(timeout_secs):
|
64
64
|
return await player.vote(self)
|
65
65
|
except TimeoutError:
|
66
66
|
await player.send("投票超时,将视为弃票")
|
nonebot_plugin_werewolf/utils.py
CHANGED
{nonebot_plugin_werewolf-1.1.0.dist-info → nonebot_plugin_werewolf-1.1.2.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: nonebot-plugin-werewolf
|
3
|
-
Version: 1.1.
|
3
|
+
Version: 1.1.2
|
4
4
|
Summary: 适用于 Nonebot2 的狼人杀插件
|
5
5
|
Author-email: wyf7685 <wyf7685@163.com>
|
6
6
|
License: MIT
|
@@ -14,7 +14,7 @@ Requires-Dist: nonebot-plugin-waiter >=0.7.1
|
|
14
14
|
|
15
15
|
<div align="center">
|
16
16
|
<a href="https://v2.nonebot.dev/store">
|
17
|
-
<img src="https://
|
17
|
+
<img src="https://raw.githubusercontent.com/wyf7685/wyf7685/main/assets/NoneBotPlugin.svg" width="300" alt="logo">
|
18
18
|
</a>
|
19
19
|
</div>
|
20
20
|
|
@@ -29,10 +29,15 @@ _✨ 简单的狼人杀插件 ✨_
|
|
29
29
|
[](https://www.python.org/)
|
30
30
|
|
31
31
|
[](https://github.com/astral-sh/uv)
|
32
|
+
[](https://github.com/astral-sh/ruff)
|
32
33
|
[](https://pycqa.github.io/isort/)
|
33
34
|
[](https://github.com/psf/black)
|
34
35
|
[](https://github.com/Microsoft/pyright)
|
35
|
-
|
36
|
+
|
37
|
+
[](https://wakatime.com/badge/user/b097681b-c224-44ec-8e04-e1cf71744655/project/70a7f68d-5625-4989-9476-be6877408332)
|
38
|
+
[](https://results.pre-commit.ci/latest/github/wyf7685/nonebot-plugin-werewolf/master)
|
39
|
+
[](https://github.com/wyf7685/nonebot-plugin-werewolf/actions/workflows/pyright.yml)
|
40
|
+
[](https://github.com/wyf7685/nonebot-plugin-werewolf/actions/workflows/pypi-publish.yml)
|
36
41
|
|
37
42
|
[](https://registry.nonebot.dev/plugin/nonebot-plugin-werewolf:nonebot_plugin_werewolf)
|
38
43
|
[](https://registry.nonebot.dev/plugin/nonebot-plugin-werewolf:nonebot_plugin_werewolf)
|
@@ -45,6 +50,10 @@ _✨ 简单的狼人杀插件 ✨_
|
|
45
50
|
|
46
51
|
## 💿 安装
|
47
52
|
|
53
|
+
> [!note]
|
54
|
+
>
|
55
|
+
> 请确保 NoneBot2 使用的 Python 解释器版本 >=3.10
|
56
|
+
|
48
57
|
<details open>
|
49
58
|
<summary>使用 nb-cli 安装</summary>
|
50
59
|
在 nonebot2 项目的根目录下打开命令行, 输入以下指令即可安装
|
@@ -231,6 +240,15 @@ werewolf__priesthood_proirity=[11, 12, 13, 14, 15]
|
|
231
240
|
|
232
241
|
<!-- CHANGELOG -->
|
233
242
|
|
243
|
+
- 2024.09.18 v1.1.2
|
244
|
+
|
245
|
+
- 修改 Python 需求为 `>=3.10`
|
246
|
+
|
247
|
+
- 2024.09.11 v1.1.1
|
248
|
+
|
249
|
+
- 修改 Python 需求为 `>=3.11`
|
250
|
+
- 优化交互结果处理 ~~_可以在一局游戏中加入多个女巫了_~~
|
251
|
+
|
234
252
|
- 2024.09.09 v1.1.0
|
235
253
|
|
236
254
|
- 新增职业 `小丑`
|
@@ -0,0 +1,16 @@
|
|
1
|
+
nonebot_plugin_werewolf/__init__.py,sha256=qCS-gVMiMZcF9yT3SIMe8np-wmKX2vGT5DxYJ2jRxgY,734
|
2
|
+
nonebot_plugin_werewolf/_timeout.py,sha256=MVkA5oMoxOTV8Luc0BH2QPP8wwz4Tr9CJjeYgiPu_O4,3649
|
3
|
+
nonebot_plugin_werewolf/config.py,sha256=a5dyF21_3T1o6AFrK6onIiwXUNyEwj5Yg-c7V9qoueI,2923
|
4
|
+
nonebot_plugin_werewolf/constant.py,sha256=gUApZs-N3DRSwV2_B05mPi6fCmynSzVZb28fgqdLX6E,1894
|
5
|
+
nonebot_plugin_werewolf/exception.py,sha256=YSwxeogIB0YJqH9MP1bgxojiu-I_xQE44XnSk5bC1AQ,400
|
6
|
+
nonebot_plugin_werewolf/game.py,sha256=FqYAs8CalCRUKoyPTpqvKK5iAelV9ZQ5EmefNo5wE9E,17459
|
7
|
+
nonebot_plugin_werewolf/matchers.py,sha256=Xnq1n4xobdr1T_8ilsQfXns_iEsNx_h_-aQ5jZwpWx0,2080
|
8
|
+
nonebot_plugin_werewolf/ob11_ext.py,sha256=P8uc3AdN5K5MzJaK80WDK85VKFg_CK5avDHu7ueMkho,2418
|
9
|
+
nonebot_plugin_werewolf/player.py,sha256=2jWlJOIWNK3JVVKNaqROfvKPRQfopDXv7KAXGtXNmh4,14973
|
10
|
+
nonebot_plugin_werewolf/player_set.py,sha256=7f4GHklp-wBGROddVqGUQAMeMzKlN2iec_w5UJQXvz0,2726
|
11
|
+
nonebot_plugin_werewolf/utils.py,sha256=LiNitH3UYNqXnCOOAzjKifVpJNhurIt4wYgWVBUW1Zo,6811
|
12
|
+
nonebot_plugin_werewolf-1.1.2.dist-info/LICENSE,sha256=B_WbEqjGr6GYVNfEJPY31T1Opik7OtgOkhRs4Ig3e2M,1064
|
13
|
+
nonebot_plugin_werewolf-1.1.2.dist-info/METADATA,sha256=0W7Nb6qqwJvW7xVdLHxM4643DuJ3YN9683owdDoUOhs,10363
|
14
|
+
nonebot_plugin_werewolf-1.1.2.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
|
15
|
+
nonebot_plugin_werewolf-1.1.2.dist-info/top_level.txt,sha256=wLTfg8sTKbH9lLT9LtU118C9cTspEBJareLsrYM52YA,24
|
16
|
+
nonebot_plugin_werewolf-1.1.2.dist-info/RECORD,,
|
@@ -1,15 +0,0 @@
|
|
1
|
-
nonebot_plugin_werewolf/__init__.py,sha256=A2138SSGhd2FdZ8V9Op50k5-pdfIVSfT0xBNG6qWacM,734
|
2
|
-
nonebot_plugin_werewolf/config.py,sha256=8HnSt7sk8WIwnQlHm0tLdiSk6OTwbysDwoQf8cvFgoE,2929
|
3
|
-
nonebot_plugin_werewolf/constant.py,sha256=yrhyw67KTaZ7DoGWZl3USWheaHSsUI94kl6ChDs0phI,1829
|
4
|
-
nonebot_plugin_werewolf/exception.py,sha256=Ui8rB1sBg6_p8JHSvrjCpUaXJlgrM_9XsLJ09k8F1bg,391
|
5
|
-
nonebot_plugin_werewolf/game.py,sha256=t1Gq-adeD5xprDH4uxfGF70wV_N-YZKBXn2CrMEH5Sc,17761
|
6
|
-
nonebot_plugin_werewolf/matchers.py,sha256=5FioDfARlxTEibGL1JMKUMzmTzOwljjWKAJxBnyzaYs,2106
|
7
|
-
nonebot_plugin_werewolf/ob11_ext.py,sha256=P8uc3AdN5K5MzJaK80WDK85VKFg_CK5avDHu7ueMkho,2418
|
8
|
-
nonebot_plugin_werewolf/player.py,sha256=IdkVKt31DMw6W_0idW04c-ix6JVtg7VPl7ZaegxUN0Y,15360
|
9
|
-
nonebot_plugin_werewolf/player_set.py,sha256=AI8v6KjH9ACtP4lvrd5IJfayan0qALmpTRLC3s_3DFw,2754
|
10
|
-
nonebot_plugin_werewolf/utils.py,sha256=l7oNSK471SV5CaB1eEVyZm10XSBFf_TwNotvb30U6vE,6835
|
11
|
-
nonebot_plugin_werewolf-1.1.0.dist-info/LICENSE,sha256=B_WbEqjGr6GYVNfEJPY31T1Opik7OtgOkhRs4Ig3e2M,1064
|
12
|
-
nonebot_plugin_werewolf-1.1.0.dist-info/METADATA,sha256=6hjSQYd7yYi9UgZ4OLqDUDwHhL6imH21MMDEu9hS6AA,9234
|
13
|
-
nonebot_plugin_werewolf-1.1.0.dist-info/WHEEL,sha256=cVxcB9AmuTcXqmwrtPhNK88dr7IR_b6qagTj0UvIEbY,91
|
14
|
-
nonebot_plugin_werewolf-1.1.0.dist-info/top_level.txt,sha256=wLTfg8sTKbH9lLT9LtU118C9cTspEBJareLsrYM52YA,24
|
15
|
-
nonebot_plugin_werewolf-1.1.0.dist-info/RECORD,,
|
File without changes
|
{nonebot_plugin_werewolf-1.1.0.dist-info → nonebot_plugin_werewolf-1.1.2.dist-info}/top_level.txt
RENAMED
File without changes
|