nonebot-plugin-werewolf 1.1.1__py3-none-any.whl → 1.1.3__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 +8 -4
- nonebot_plugin_werewolf/_timeout.py +110 -0
- nonebot_plugin_werewolf/config.py +15 -18
- nonebot_plugin_werewolf/constant.py +18 -3
- nonebot_plugin_werewolf/exception.py +1 -1
- nonebot_plugin_werewolf/game.py +91 -110
- nonebot_plugin_werewolf/matchers/__init__.py +2 -0
- nonebot_plugin_werewolf/matchers/message_in_game.py +15 -0
- nonebot_plugin_werewolf/{ob11_ext.py → matchers/ob11_ext.py} +26 -20
- nonebot_plugin_werewolf/matchers/start_game.py +56 -0
- nonebot_plugin_werewolf/player_set.py +9 -7
- nonebot_plugin_werewolf/players/__init__.py +10 -0
- nonebot_plugin_werewolf/players/can_shoot.py +59 -0
- nonebot_plugin_werewolf/players/civilian.py +7 -0
- nonebot_plugin_werewolf/players/guard.py +37 -0
- nonebot_plugin_werewolf/players/hunter.py +8 -0
- nonebot_plugin_werewolf/players/idiot.py +44 -0
- nonebot_plugin_werewolf/players/joker.py +21 -0
- nonebot_plugin_werewolf/players/player.py +161 -0
- nonebot_plugin_werewolf/players/prophet.py +30 -0
- nonebot_plugin_werewolf/players/werewolf.py +67 -0
- nonebot_plugin_werewolf/players/witch.py +72 -0
- nonebot_plugin_werewolf/players/wolfking.py +14 -0
- nonebot_plugin_werewolf/utils.py +83 -65
- {nonebot_plugin_werewolf-1.1.1.dist-info → nonebot_plugin_werewolf-1.1.3.dist-info}/METADATA +20 -8
- nonebot_plugin_werewolf-1.1.3.dist-info/RECORD +29 -0
- {nonebot_plugin_werewolf-1.1.1.dist-info → nonebot_plugin_werewolf-1.1.3.dist-info}/WHEEL +1 -1
- nonebot_plugin_werewolf/matchers.py +0 -63
- nonebot_plugin_werewolf/player.py +0 -455
- nonebot_plugin_werewolf-1.1.1.dist-info/RECORD +0 -15
- {nonebot_plugin_werewolf-1.1.1.dist-info → nonebot_plugin_werewolf-1.1.3.dist-info}/LICENSE +0 -0
- {nonebot_plugin_werewolf-1.1.1.dist-info → nonebot_plugin_werewolf-1.1.3.dist-info}/top_level.txt +0 -0
@@ -2,13 +2,14 @@ from nonebot import require
|
|
2
2
|
from nonebot.plugin import PluginMetadata, inherit_supported_adapters
|
3
3
|
|
4
4
|
require("nonebot_plugin_alconna")
|
5
|
-
require("
|
5
|
+
require("nonebot_plugin_uninfo")
|
6
6
|
require("nonebot_plugin_waiter")
|
7
7
|
|
8
8
|
from . import matchers as matchers
|
9
|
+
from . import players as players
|
9
10
|
from .config import Config
|
10
11
|
|
11
|
-
__version__ = "1.1.
|
12
|
+
__version__ = "1.1.3"
|
12
13
|
__plugin_meta__ = PluginMetadata(
|
13
14
|
name="狼人杀",
|
14
15
|
description="适用于 Nonebot2 的狼人杀插件",
|
@@ -18,8 +19,11 @@ __plugin_meta__ = PluginMetadata(
|
|
18
19
|
config=Config,
|
19
20
|
supported_adapters=inherit_supported_adapters(
|
20
21
|
"nonebot_plugin_alconna",
|
21
|
-
"
|
22
|
+
"nonebot_plugin_uninfo",
|
22
23
|
"nonebot_plugin_waiter",
|
23
24
|
),
|
24
|
-
extra={
|
25
|
+
extra={
|
26
|
+
"Author": "wyf7685",
|
27
|
+
"Bug Tracker": "https://github.com/wyf7685/nonebot-plugin-werewolf/issues",
|
28
|
+
},
|
25
29
|
)
|
@@ -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"]
|
@@ -1,11 +1,14 @@
|
|
1
|
-
from typing import
|
1
|
+
from typing import Any, Literal, overload
|
2
2
|
|
3
3
|
from nonebot import get_plugin_config, logger
|
4
4
|
from nonebot.compat import PYDANTIC_V2
|
5
5
|
from pydantic import BaseModel, Field
|
6
|
+
from typing_extensions import Self
|
6
7
|
|
7
8
|
from .constant import (
|
8
9
|
Role,
|
10
|
+
RolePresetConfig,
|
11
|
+
RolePresetDict,
|
9
12
|
default_priesthood_proirity,
|
10
13
|
default_role_preset,
|
11
14
|
default_werewolf_priority,
|
@@ -17,12 +20,12 @@ else:
|
|
17
20
|
from pydantic import root_validator
|
18
21
|
|
19
22
|
@overload
|
20
|
-
def model_validator(*, mode: Literal["before"]): ... # noqa:
|
23
|
+
def model_validator(*, mode: Literal["before"]) -> Any: ... # noqa: ANN401
|
21
24
|
|
22
25
|
@overload
|
23
|
-
def model_validator(*, mode: Literal["after"]): ... # noqa:
|
26
|
+
def model_validator(*, mode: Literal["after"]) -> Any: ... # noqa: ANN401
|
24
27
|
|
25
|
-
def model_validator(*, mode: Literal["before", "after"]):
|
28
|
+
def model_validator(*, mode: Literal["before", "after"]) -> Any:
|
26
29
|
return root_validator(
|
27
30
|
pre=mode == "before", # pyright: ignore[reportArgumentType]
|
28
31
|
allow_reuse=True,
|
@@ -30,16 +33,10 @@ else:
|
|
30
33
|
|
31
34
|
|
32
35
|
class PluginConfig(BaseModel):
|
33
|
-
enable_poke: bool =
|
34
|
-
role_preset:
|
35
|
-
|
36
|
-
)
|
37
|
-
werewolf_priority: list[Role] = Field(
|
38
|
-
default_factory=default_werewolf_priority.copy
|
39
|
-
)
|
40
|
-
priesthood_proirity: list[Role] = Field(
|
41
|
-
default_factory=default_priesthood_proirity.copy
|
42
|
-
)
|
36
|
+
enable_poke: bool = True
|
37
|
+
role_preset: RolePresetConfig = default_role_preset.copy()
|
38
|
+
werewolf_priority: list[Role] = default_werewolf_priority.copy()
|
39
|
+
priesthood_proirity: list[Role] = default_priesthood_proirity.copy()
|
43
40
|
joker_probability: float = Field(default=0.0, ge=0.0, le=1.0)
|
44
41
|
|
45
42
|
@model_validator(mode="after")
|
@@ -47,7 +44,7 @@ class PluginConfig(BaseModel):
|
|
47
44
|
if isinstance(self.role_preset, list):
|
48
45
|
for preset in self.role_preset:
|
49
46
|
if preset[0] != sum(preset[1:]):
|
50
|
-
raise
|
47
|
+
raise ValueError(
|
51
48
|
"配置项 `role_preset` 错误: "
|
52
49
|
f"预设总人数为 {preset[0]}, 实际总人数为 {sum(preset[1:])} "
|
53
50
|
f"({', '.join(map(str, preset[1:]))})"
|
@@ -59,19 +56,19 @@ class PluginConfig(BaseModel):
|
|
59
56
|
|
60
57
|
min_length = max(i[0] for i in self.role_preset.values())
|
61
58
|
if len(self.werewolf_priority) < min_length:
|
62
|
-
raise
|
59
|
+
raise ValueError(
|
63
60
|
f"配置项 `werewolf_priority` 错误: 应至少为 {min_length} 项"
|
64
61
|
)
|
65
62
|
|
66
63
|
min_length = max(i[1] for i in self.role_preset.values())
|
67
64
|
if len(self.priesthood_proirity) < min_length:
|
68
|
-
raise
|
65
|
+
raise ValueError(
|
69
66
|
f"配置项 `priesthood_proirity` 错误: 应至少为 {min_length} 项"
|
70
67
|
)
|
71
68
|
|
72
69
|
return self
|
73
70
|
|
74
|
-
def get_role_preset(self) ->
|
71
|
+
def get_role_preset(self) -> RolePresetDict:
|
75
72
|
if isinstance(self.role_preset, list):
|
76
73
|
self.role_preset = {i[0]: i[1:] for i in self.role_preset}
|
77
74
|
return self.role_preset
|
@@ -5,7 +5,7 @@ from enum import Enum, auto
|
|
5
5
|
from typing import TYPE_CHECKING
|
6
6
|
|
7
7
|
if TYPE_CHECKING:
|
8
|
-
from .
|
8
|
+
from .players import Player
|
9
9
|
|
10
10
|
|
11
11
|
class Role(Enum):
|
@@ -52,7 +52,7 @@ class GameState:
|
|
52
52
|
killed: Player | None = None
|
53
53
|
shoot: tuple[Player, Player] | tuple[None, None] = (None, None)
|
54
54
|
antidote: set[Player] = dataclasses.field(default_factory=set)
|
55
|
-
poison: set[
|
55
|
+
poison: set[Player] = dataclasses.field(default_factory=set)
|
56
56
|
protected: set[Player] = dataclasses.field(default_factory=set)
|
57
57
|
|
58
58
|
|
@@ -71,7 +71,22 @@ role_name_conv: dict[Role | RoleGroup, str] = {
|
|
71
71
|
RoleGroup.Others: "其他",
|
72
72
|
}
|
73
73
|
|
74
|
-
|
74
|
+
role_emoji: dict[Role, str] = {
|
75
|
+
Role.Werewolf: "🐺",
|
76
|
+
Role.WolfKing: "🐺👑",
|
77
|
+
Role.Prophet: "🔮",
|
78
|
+
Role.Witch: "🧙♀️",
|
79
|
+
Role.Hunter: "🕵️",
|
80
|
+
Role.Guard: "🛡️",
|
81
|
+
Role.Idiot: "👨🏻🦲",
|
82
|
+
Role.Joker: "🤡",
|
83
|
+
Role.Civilian: "👨🏻🌾",
|
84
|
+
}
|
85
|
+
|
86
|
+
RolePresetDict = dict[int, tuple[int, int, int]]
|
87
|
+
RolePresetConfig = RolePresetDict | list[tuple[int, int, int, int]]
|
88
|
+
|
89
|
+
default_role_preset: RolePresetDict = {
|
75
90
|
# 总人数: (狼, 神, 民)
|
76
91
|
6: (1, 2, 3),
|
77
92
|
7: (2, 2, 3),
|