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.
Files changed (32) hide show
  1. nonebot_plugin_werewolf/__init__.py +8 -4
  2. nonebot_plugin_werewolf/_timeout.py +110 -0
  3. nonebot_plugin_werewolf/config.py +15 -18
  4. nonebot_plugin_werewolf/constant.py +18 -3
  5. nonebot_plugin_werewolf/exception.py +1 -1
  6. nonebot_plugin_werewolf/game.py +91 -110
  7. nonebot_plugin_werewolf/matchers/__init__.py +2 -0
  8. nonebot_plugin_werewolf/matchers/message_in_game.py +15 -0
  9. nonebot_plugin_werewolf/{ob11_ext.py → matchers/ob11_ext.py} +26 -20
  10. nonebot_plugin_werewolf/matchers/start_game.py +56 -0
  11. nonebot_plugin_werewolf/player_set.py +9 -7
  12. nonebot_plugin_werewolf/players/__init__.py +10 -0
  13. nonebot_plugin_werewolf/players/can_shoot.py +59 -0
  14. nonebot_plugin_werewolf/players/civilian.py +7 -0
  15. nonebot_plugin_werewolf/players/guard.py +37 -0
  16. nonebot_plugin_werewolf/players/hunter.py +8 -0
  17. nonebot_plugin_werewolf/players/idiot.py +44 -0
  18. nonebot_plugin_werewolf/players/joker.py +21 -0
  19. nonebot_plugin_werewolf/players/player.py +161 -0
  20. nonebot_plugin_werewolf/players/prophet.py +30 -0
  21. nonebot_plugin_werewolf/players/werewolf.py +67 -0
  22. nonebot_plugin_werewolf/players/witch.py +72 -0
  23. nonebot_plugin_werewolf/players/wolfking.py +14 -0
  24. nonebot_plugin_werewolf/utils.py +83 -65
  25. {nonebot_plugin_werewolf-1.1.1.dist-info → nonebot_plugin_werewolf-1.1.3.dist-info}/METADATA +20 -8
  26. nonebot_plugin_werewolf-1.1.3.dist-info/RECORD +29 -0
  27. {nonebot_plugin_werewolf-1.1.1.dist-info → nonebot_plugin_werewolf-1.1.3.dist-info}/WHEEL +1 -1
  28. nonebot_plugin_werewolf/matchers.py +0 -63
  29. nonebot_plugin_werewolf/player.py +0 -455
  30. nonebot_plugin_werewolf-1.1.1.dist-info/RECORD +0 -15
  31. {nonebot_plugin_werewolf-1.1.1.dist-info → nonebot_plugin_werewolf-1.1.3.dist-info}/LICENSE +0 -0
  32. {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("nonebot_plugin_userinfo")
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.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
- "nonebot_plugin_userinfo",
22
+ "nonebot_plugin_uninfo",
22
23
  "nonebot_plugin_waiter",
23
24
  ),
24
- extra={"author": "wyf7685"},
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 Literal, Self, overload
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: ANN201
23
+ def model_validator(*, mode: Literal["before"]) -> Any: ... # noqa: ANN401
21
24
 
22
25
  @overload
23
- def model_validator(*, mode: Literal["after"]): ... # noqa: ANN201
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 = Field(default=True)
34
- role_preset: list[tuple[int, int, int, int]] | dict[int, tuple[int, int, int]] = (
35
- Field(default_factory=default_role_preset.copy)
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 RuntimeError(
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 RuntimeError(
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 RuntimeError(
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) -> dict[int, tuple[int, int, int]]:
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 .player import Player
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[tuple[Player, Player]] = dataclasses.field(default_factory=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
- default_role_preset: dict[int, tuple[int, int, int]] = {
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),
@@ -10,7 +10,7 @@ class Error(Exception):
10
10
  """插件错误类型基类"""
11
11
 
12
12
 
13
- class GameFinishedError(Error):
13
+ class GameFinished(Error): # noqa: N818
14
14
  """游戏结束时抛出,无视游戏进程进入结算"""
15
15
 
16
16
  status: GameStatus