nonebot-plugin-werewolf 1.1.6__py3-none-any.whl → 1.1.8__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 (35) hide show
  1. nonebot_plugin_werewolf/__init__.py +1 -1
  2. nonebot_plugin_werewolf/config.py +76 -18
  3. nonebot_plugin_werewolf/constant.py +54 -46
  4. nonebot_plugin_werewolf/exception.py +2 -4
  5. nonebot_plugin_werewolf/game.py +193 -166
  6. nonebot_plugin_werewolf/matchers/__init__.py +1 -0
  7. nonebot_plugin_werewolf/matchers/depends.py +4 -4
  8. nonebot_plugin_werewolf/matchers/edit_behavior.py +205 -0
  9. nonebot_plugin_werewolf/matchers/edit_preset.py +11 -11
  10. nonebot_plugin_werewolf/matchers/message_in_game.py +3 -1
  11. nonebot_plugin_werewolf/matchers/poke/chronocat_poke.py +8 -5
  12. nonebot_plugin_werewolf/matchers/poke/ob11_poke.py +3 -3
  13. nonebot_plugin_werewolf/matchers/start_game.py +213 -175
  14. nonebot_plugin_werewolf/matchers/superuser_ops.py +3 -3
  15. nonebot_plugin_werewolf/models.py +31 -19
  16. nonebot_plugin_werewolf/player_set.py +10 -8
  17. nonebot_plugin_werewolf/players/__init__.py +1 -1
  18. nonebot_plugin_werewolf/players/can_shoot.py +15 -15
  19. nonebot_plugin_werewolf/players/civilian.py +1 -1
  20. nonebot_plugin_werewolf/players/guard.py +16 -14
  21. nonebot_plugin_werewolf/players/hunter.py +1 -1
  22. nonebot_plugin_werewolf/players/idiot.py +3 -3
  23. nonebot_plugin_werewolf/players/{joker.py → jester.py} +4 -5
  24. nonebot_plugin_werewolf/players/player.py +93 -29
  25. nonebot_plugin_werewolf/players/prophet.py +11 -10
  26. nonebot_plugin_werewolf/players/werewolf.py +63 -31
  27. nonebot_plugin_werewolf/players/witch.py +29 -12
  28. nonebot_plugin_werewolf/players/wolfking.py +1 -1
  29. nonebot_plugin_werewolf/utils.py +106 -7
  30. {nonebot_plugin_werewolf-1.1.6.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/METADATA +27 -20
  31. nonebot_plugin_werewolf-1.1.8.dist-info/RECORD +35 -0
  32. {nonebot_plugin_werewolf-1.1.6.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/WHEEL +1 -1
  33. nonebot_plugin_werewolf-1.1.6.dist-info/RECORD +0 -34
  34. {nonebot_plugin_werewolf-1.1.6.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/LICENSE +0 -0
  35. {nonebot_plugin_werewolf-1.1.6.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/top_level.txt +0 -0
@@ -10,7 +10,7 @@ from . import matchers as matchers
10
10
  from . import players as players
11
11
  from .config import Config
12
12
 
13
- __version__ = "1.1.6"
13
+ __version__ = "1.1.8"
14
14
  __plugin_meta__ = PluginMetadata(
15
15
  name="狼人杀",
16
16
  description="适用于 Nonebot2 的狼人杀插件",
@@ -1,45 +1,103 @@
1
1
  import json
2
+ from pathlib import Path
3
+ from typing import Any, ClassVar, Final
4
+ from typing_extensions import Self
2
5
 
3
- from nonebot import get_plugin_config, logger
6
+ import nonebot
4
7
  from nonebot.compat import model_dump, type_validate_json
5
8
  from nonebot_plugin_localstore import get_plugin_data_file
6
9
  from pydantic import BaseModel, Field
7
- from typing_extensions import Self
8
10
 
9
11
  from .constant import (
10
- default_priesthood_proirity,
11
- default_role_preset,
12
- default_werewolf_priority,
12
+ DEFAULT_PRIESTHOOD_PRIORITY,
13
+ DEFAULT_ROLE_PRESET,
14
+ DEFAULT_WEREWOLF_PRIORITY,
15
+ stop_command_prompt,
13
16
  )
14
17
  from .models import Role
15
18
 
16
19
 
17
- class PresetData(BaseModel):
18
- role_preset: dict[int, tuple[int, int, int]] = default_role_preset.copy()
19
- werewolf_priority: list[Role] = default_werewolf_priority.copy()
20
- priesthood_proirity: list[Role] = default_priesthood_proirity.copy()
21
- joker_probability: float = Field(default=0.0, ge=0.0, le=1.0)
20
+ class ConfigFile(BaseModel):
21
+ FILE: ClassVar[Path]
22
+ _cache: ClassVar[Self | None] = None
23
+
24
+ def __init_subclass__(cls, **kwargs: Any) -> None: # noqa: ANN401
25
+ super().__init_subclass__(**kwargs)
26
+ if not cls.FILE.exists():
27
+ cls().save()
22
28
 
23
29
  @classmethod
24
30
  def load(cls) -> Self:
25
- return type_validate_json(cls, preset_data_file.read_text())
31
+ return type_validate_json(cls, cls.FILE.read_text())
32
+
33
+ @classmethod
34
+ def get(cls, *, use_cache: bool = True) -> Self:
35
+ if cls._cache is None or not use_cache:
36
+ cls._cache = cls.load()
37
+ return cls._cache
26
38
 
27
39
  def save(self) -> None:
28
- preset_data_file.write_text(json.dumps(model_dump(self)))
40
+ self.FILE.write_text(json.dumps(model_dump(self)))
41
+ type(self)._cache = self # noqa: SLF001
42
+
43
+
44
+ class PresetData(ConfigFile):
45
+ FILE: ClassVar[Path] = get_plugin_data_file("preset.json")
46
+
47
+ role_preset: dict[int, tuple[int, int, int]] = DEFAULT_ROLE_PRESET.copy()
48
+ werewolf_priority: list[Role] = DEFAULT_WEREWOLF_PRIORITY.copy()
49
+ priesthood_proirity: list[Role] = DEFAULT_PRIESTHOOD_PRIORITY.copy()
50
+ jester_probability: float = Field(default=0.0, ge=0.0, le=1.0)
51
+
52
+
53
+ class GameBehavior(ConfigFile):
54
+ FILE: ClassVar[Path] = get_plugin_data_file("behavior.json")
55
+
56
+ show_roles_list_on_start: bool = False
57
+ speak_in_turn: bool = False
58
+ dead_channel_rate_limit: int = 8 # per minute
59
+
60
+ class _Timeout(BaseModel):
61
+ prepare: int = Field(default=5 * 60, ge=5 * 60)
62
+ speak: int = Field(default=60, ge=60)
63
+ group_speak: int = Field(default=120, ge=120)
64
+ interact: int = Field(default=60, ge=60)
65
+ vote: int = Field(default=60, ge=60)
66
+ werewolf: int = Field(default=120, ge=120)
67
+
68
+ @property
69
+ def speak_timeout_prompt(self) -> str:
70
+ return (
71
+ f"限时{self.speak / 60:.1f}分钟, "
72
+ f"发送 “{stop_command_prompt()}” 结束发言"
73
+ )
74
+
75
+ @property
76
+ def group_speak_timeout_prompt(self) -> str:
77
+ return (
78
+ f"限时{self.group_speak / 60:.1f}分钟, "
79
+ f"全员发送 “{stop_command_prompt()}” 结束发言"
80
+ )
81
+
82
+ timeout: Final[_Timeout] = _Timeout()
29
83
 
30
84
 
31
85
  class PluginConfig(BaseModel):
32
86
  enable_poke: bool = True
33
87
  enable_button: bool = False
88
+ stop_command: str | set[str] = "stop"
89
+
90
+ def get_stop_command(self) -> list[str]:
91
+ return (
92
+ [self.stop_command]
93
+ if isinstance(self.stop_command, str)
94
+ else sorted(self.stop_command, key=len)
95
+ )
34
96
 
35
97
 
36
98
  class Config(BaseModel):
37
99
  werewolf: PluginConfig = PluginConfig()
38
100
 
39
101
 
40
- preset_data_file = get_plugin_data_file("preset.json")
41
- if not preset_data_file.exists():
42
- PresetData().save()
43
-
44
- config = get_plugin_config(Config).werewolf
45
- logger.debug(f"加载插件配置: {config}")
102
+ config = nonebot.get_plugin_config(Config).werewolf
103
+ nonebot.logger.debug(f"加载插件配置: {config}")
@@ -1,55 +1,63 @@
1
+ import functools
2
+
1
3
  import nonebot
2
4
 
3
5
  from .models import GameStatus, KillReason, Role, RoleGroup
4
6
 
7
+ STOP_COMMAND = "{{stop}}"
5
8
  COMMAND_START = next(
6
9
  iter(sorted(nonebot.get_driver().config.command_start, key=len)), ""
7
10
  )
8
- STOP_COMMAND_PROMPT = f"{COMMAND_START}stop"
9
- STOP_COMMAND = "{{stop}}"
10
11
 
11
12
 
12
- role_name_conv: dict[Role | RoleGroup, str] = {
13
- Role.Werewolf: "狼人",
14
- Role.WolfKing: "狼王",
15
- Role.Prophet: "预言家",
16
- Role.Witch: "女巫",
17
- Role.Hunter: "猎人",
18
- Role.Guard: "守卫",
19
- Role.Idiot: "白痴",
20
- Role.Joker: "小丑",
21
- Role.Civilian: "平民",
22
- RoleGroup.Werewolf: "狼人",
23
- RoleGroup.GoodGuy: "好人",
24
- RoleGroup.Others: "其他",
13
+ @functools.cache
14
+ def stop_command_prompt() -> str:
15
+ from .config import config # circular import
16
+
17
+ return COMMAND_START + config.get_stop_command()[0]
18
+
19
+
20
+ ROLE_NAME_CONV: dict[Role | RoleGroup, str] = {
21
+ Role.WEREWOLF: "狼人",
22
+ Role.WOLFKING: "狼王",
23
+ Role.PROPHET: "预言家",
24
+ Role.WITCH: "女巫",
25
+ Role.HUNTER: "猎人",
26
+ Role.GUARD: "守卫",
27
+ Role.IDIOT: "白痴",
28
+ Role.JESTER: "小丑",
29
+ Role.CIVILIAN: "平民",
30
+ RoleGroup.WEREWOLF: "狼人",
31
+ RoleGroup.GOODGUY: "好人",
32
+ RoleGroup.OTHERS: "其他",
25
33
  }
26
34
 
27
- role_emoji: dict[Role, str] = {
28
- Role.Werewolf: "🐺",
29
- Role.WolfKing: "🐺👑",
30
- Role.Prophet: "🔮",
31
- Role.Witch: "🧙‍♀️",
32
- Role.Hunter: "🕵️",
33
- Role.Guard: "🛡️",
34
- Role.Idiot: "👨🏻‍🦲",
35
- Role.Joker: "🤡",
36
- Role.Civilian: "👨🏻‍🌾",
35
+ ROLE_EMOJI: dict[Role, str] = {
36
+ Role.WEREWOLF: "🐺",
37
+ Role.WOLFKING: "🐺👑",
38
+ Role.PROPHET: "🔮",
39
+ Role.WITCH: "🧙‍♀️",
40
+ Role.HUNTER: "🕵️",
41
+ Role.GUARD: "🛡️",
42
+ Role.IDIOT: "👨🏻‍🦲",
43
+ Role.JESTER: "🤡",
44
+ Role.CIVILIAN: "👨🏻‍🌾",
37
45
  }
38
46
 
39
- game_status_conv: dict[GameStatus, str] = {
40
- GameStatus.GoodGuy: "好人",
41
- GameStatus.Werewolf: "狼人",
42
- GameStatus.Joker: "小丑",
47
+ GAME_STATUS_CONV: dict[GameStatus, str] = {
48
+ GameStatus.GOODGUY: "好人",
49
+ GameStatus.WEREWOLF: "狼人",
50
+ GameStatus.JESTER: "小丑",
43
51
  }
44
52
 
45
- report_text: dict[KillReason, tuple[str, str]] = {
46
- KillReason.Werewolf: ("🔪", "刀了"),
47
- KillReason.Poison: ("🧪", "毒死"),
48
- KillReason.Shoot: ("🔫", "射杀"),
49
- KillReason.Vote: ("🗳️", "票出"),
53
+ REPORT_TEXT: dict[KillReason, tuple[str, str]] = {
54
+ KillReason.WEREWOLF: ("🔪", "刀了"),
55
+ KillReason.POISON: ("🧪", "毒死"),
56
+ KillReason.SHOOT: ("🔫", "射杀"),
57
+ KillReason.VOTE: ("🗳️", "票出"),
50
58
  }
51
59
 
52
- default_role_preset: dict[int, tuple[int, int, int]] = {
60
+ DEFAULT_ROLE_PRESET: dict[int, tuple[int, int, int]] = {
53
61
  # 总人数: (狼, 神, 民)
54
62
  6: (1, 2, 3),
55
63
  7: (2, 2, 3),
@@ -59,16 +67,16 @@ default_role_preset: dict[int, tuple[int, int, int]] = {
59
67
  11: (3, 5, 3),
60
68
  12: (4, 5, 3),
61
69
  }
62
- default_werewolf_priority: list[Role] = [
63
- Role.Werewolf,
64
- Role.Werewolf,
65
- Role.WolfKing,
66
- Role.Werewolf,
70
+ DEFAULT_WEREWOLF_PRIORITY: list[Role] = [
71
+ Role.WEREWOLF,
72
+ Role.WEREWOLF,
73
+ Role.WOLFKING,
74
+ Role.WEREWOLF,
67
75
  ]
68
- default_priesthood_proirity: list[Role] = [
69
- Role.Witch,
70
- Role.Prophet,
71
- Role.Hunter,
72
- Role.Guard,
73
- Role.Idiot,
76
+ DEFAULT_PRIESTHOOD_PRIORITY: list[Role] = [
77
+ Role.WITCH,
78
+ Role.PROPHET,
79
+ Role.HUNTER,
80
+ Role.GUARD,
81
+ Role.IDIOT,
74
82
  ]
@@ -1,5 +1,3 @@
1
- from __future__ import annotations
2
-
3
1
  from typing import TYPE_CHECKING
4
2
 
5
3
  if TYPE_CHECKING:
@@ -13,7 +11,7 @@ class Error(Exception):
13
11
  class GameFinished(Error): # noqa: N818
14
12
  """游戏结束时抛出,无视游戏进程进入结算"""
15
13
 
16
- status: GameStatus
14
+ status: "GameStatus"
17
15
 
18
- def __init__(self, status: GameStatus) -> None:
16
+ def __init__(self, status: "GameStatus") -> None:
19
17
  self.status = status