nonebot-plugin-werewolf 1.1.2__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 (30) hide show
  1. nonebot_plugin_werewolf/__init__.py +8 -4
  2. nonebot_plugin_werewolf/config.py +11 -15
  3. nonebot_plugin_werewolf/constant.py +17 -2
  4. nonebot_plugin_werewolf/game.py +77 -98
  5. nonebot_plugin_werewolf/matchers/__init__.py +2 -0
  6. nonebot_plugin_werewolf/matchers/message_in_game.py +15 -0
  7. nonebot_plugin_werewolf/{ob11_ext.py → matchers/ob11_ext.py} +26 -20
  8. nonebot_plugin_werewolf/matchers/start_game.py +56 -0
  9. nonebot_plugin_werewolf/player_set.py +6 -4
  10. nonebot_plugin_werewolf/players/__init__.py +10 -0
  11. nonebot_plugin_werewolf/players/can_shoot.py +59 -0
  12. nonebot_plugin_werewolf/players/civilian.py +7 -0
  13. nonebot_plugin_werewolf/players/guard.py +37 -0
  14. nonebot_plugin_werewolf/players/hunter.py +8 -0
  15. nonebot_plugin_werewolf/players/idiot.py +44 -0
  16. nonebot_plugin_werewolf/players/joker.py +21 -0
  17. nonebot_plugin_werewolf/players/player.py +161 -0
  18. nonebot_plugin_werewolf/players/prophet.py +30 -0
  19. nonebot_plugin_werewolf/players/werewolf.py +67 -0
  20. nonebot_plugin_werewolf/players/witch.py +72 -0
  21. nonebot_plugin_werewolf/players/wolfking.py +14 -0
  22. nonebot_plugin_werewolf/utils.py +83 -64
  23. {nonebot_plugin_werewolf-1.1.2.dist-info → nonebot_plugin_werewolf-1.1.3.dist-info}/METADATA +11 -3
  24. nonebot_plugin_werewolf-1.1.3.dist-info/RECORD +29 -0
  25. nonebot_plugin_werewolf/matchers.py +0 -62
  26. nonebot_plugin_werewolf/player.py +0 -462
  27. nonebot_plugin_werewolf-1.1.2.dist-info/RECORD +0 -16
  28. {nonebot_plugin_werewolf-1.1.2.dist-info → nonebot_plugin_werewolf-1.1.3.dist-info}/LICENSE +0 -0
  29. {nonebot_plugin_werewolf-1.1.2.dist-info → nonebot_plugin_werewolf-1.1.3.dist-info}/WHEEL +0 -0
  30. {nonebot_plugin_werewolf-1.1.2.dist-info → nonebot_plugin_werewolf-1.1.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,56 @@
1
+ from nonebot import on_command
2
+ from nonebot.adapters import Bot, Event
3
+ from nonebot.rule import to_me
4
+ from nonebot_plugin_alconna import MsgTarget, UniMessage
5
+ from nonebot_plugin_uninfo import Uninfo
6
+
7
+ from .._timeout import timeout
8
+ from ..game import Game
9
+ from ..utils import prepare_game, rule_not_in_game
10
+ from .ob11_ext import ob11_ext_enabled
11
+
12
+ start_game = on_command(
13
+ "werewolf",
14
+ rule=to_me() & rule_not_in_game,
15
+ aliases={"狼人杀"},
16
+ )
17
+
18
+
19
+ @start_game.handle()
20
+ async def handle_start_warning(target: MsgTarget) -> None:
21
+ if target.private:
22
+ await UniMessage("⚠️请在群组中创建新游戏").finish(reply_to=True)
23
+
24
+
25
+ @start_game.handle()
26
+ async def handle_start(
27
+ bot: Bot,
28
+ event: Event,
29
+ target: MsgTarget,
30
+ session: Uninfo,
31
+ ) -> None:
32
+ admin_id = event.get_user_id()
33
+ msg = (
34
+ UniMessage.at(admin_id)
35
+ .text("\n🎉成功创建游戏\n\n")
36
+ .text(" 玩家请 @我 发送 “加入游戏”、“退出游戏”\n")
37
+ .text(" 玩家 @我 发送 “当前玩家” 可查看玩家列表\n")
38
+ .text(" 游戏发起者 @我 发送 “结束游戏” 可结束当前游戏\n")
39
+ .text(" 玩家均加入后,游戏发起者请 @我 发送 “开始游戏”\n")
40
+ )
41
+ if ob11_ext_enabled():
42
+ msg.text("\n可使用戳一戳代替游戏交互中的 “/stop” 命令\n")
43
+ await msg.text("\n游戏准备阶段限时5分钟,超时将自动结束").send()
44
+
45
+ admin_name = session.user.nick or session.user.name or admin_id
46
+ if session.member:
47
+ admin_name = session.member.nick or admin_name
48
+ players = {admin_id: admin_name}
49
+
50
+ try:
51
+ async with timeout(5 * 60):
52
+ await prepare_game(event, players)
53
+ except TimeoutError:
54
+ await UniMessage.text("⚠️游戏准备超时,已自动结束").finish()
55
+
56
+ Game(bot, target, players).start()
@@ -1,10 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import functools
4
5
  from typing import TYPE_CHECKING
5
6
 
6
7
  from ._timeout import timeout
7
- from .player import Player
8
+ from .players import Player
8
9
 
9
10
  if TYPE_CHECKING:
10
11
  from nonebot_plugin_alconna.uniseg import UniMessage
@@ -50,6 +51,7 @@ class PlayerSet(set[Player]):
50
51
  def player_selected(self) -> PlayerSet:
51
52
  return PlayerSet(p.selected for p in self.alive() if p.selected is not None)
52
53
 
54
+ @functools.cached_property
53
55
  def sorted(self) -> list[Player]:
54
56
  return sorted(self, key=lambda p: p.user_id)
55
57
 
@@ -63,7 +65,7 @@ class PlayerSet(set[Player]):
63
65
  async with timeout(timeout_secs):
64
66
  return await player.vote(self)
65
67
  except TimeoutError:
66
- await player.send("投票超时,将视为弃票")
68
+ await player.send("⚠️投票超时,将视为弃票")
67
69
  return None
68
70
 
69
71
  result: dict[Player, list[Player]] = {}
@@ -77,7 +79,7 @@ class PlayerSet(set[Player]):
77
79
  await asyncio.gather(*[p.send(message) for p in self])
78
80
 
79
81
  def show(self) -> str:
80
- return "\n".join(f"{i}. {p.name}" for i, p in enumerate(self.sorted(), 1))
82
+ return "\n".join(f"{i}. {p.name}" for i, p in enumerate(self.sorted, 1))
81
83
 
82
84
  def __getitem__(self, __index: int) -> Player:
83
- return self.sorted()[__index]
85
+ return self.sorted[__index]
@@ -0,0 +1,10 @@
1
+ from .civilian import Civilian as Civilian
2
+ from .guard import Guard as Guard
3
+ from .hunter import Hunter as Hunter
4
+ from .idiot import Idiot as Idiot
5
+ from .joker import Joker as Joker
6
+ from .player import Player as Player
7
+ from .prophet import Prophet as Prophet
8
+ from .werewolf import Werewolf as Werewolf
9
+ from .witch import Witch as Witch
10
+ from .wolfking import WolfKing as WolfKing
@@ -0,0 +1,59 @@
1
+ from nonebot_plugin_alconna.uniseg import UniMessage
2
+ from typing_extensions import override
3
+
4
+ from ..constant import KillReason
5
+ from ..utils import check_index
6
+ from .player import Player
7
+
8
+
9
+ class CanShoot(Player):
10
+ @override
11
+ async def post_kill(self) -> None:
12
+ if self.kill_info and self.kill_info.reason == KillReason.Poison:
13
+ await self.send("⚠️你昨晚被女巫毒杀,无法使用技能")
14
+ return await super().post_kill()
15
+
16
+ await self.game.send(
17
+ UniMessage.text(f"🕵️{self.role_name} ")
18
+ .at(self.user_id)
19
+ .text(f" 死了\n请{self.role_name}决定击杀目标...")
20
+ )
21
+
22
+ self.game.state.shoot = (None, None)
23
+ shoot = await self.shoot()
24
+
25
+ if shoot is not None:
26
+ self.game.state.shoot = (self, shoot)
27
+ await self.send(
28
+ UniMessage.text(f"🔫{self.role_name} ")
29
+ .at(self.user_id)
30
+ .text(" 射杀了玩家 ")
31
+ .at(shoot.user_id)
32
+ )
33
+ await shoot.kill(KillReason.Shoot, self)
34
+ else:
35
+ await self.send(f"ℹ️{self.role_name}选择了取消技能")
36
+ return await super().post_kill()
37
+
38
+ async def shoot(self) -> Player | None:
39
+ players = self.game.players.alive().exclude(self)
40
+ await self.send(
41
+ "💫请选择需要射杀的玩家:\n"
42
+ + players.show()
43
+ + "\n\n🔫发送编号选择玩家"
44
+ + "\n❌发送 “/stop” 取消技能"
45
+ )
46
+
47
+ while True:
48
+ text = await self.receive_text()
49
+ if text == "/stop":
50
+ await self.send("ℹ️已取消技能")
51
+ return None
52
+ index = check_index(text, len(players))
53
+ if index is not None:
54
+ selected = index - 1
55
+ break
56
+ await self.send("⚠️输入错误: 请发送编号选择玩家")
57
+
58
+ await self.send(f"🎯选择射杀的玩家: {players[selected].name}")
59
+ return players[selected]
@@ -0,0 +1,7 @@
1
+ from ..constant import Role, RoleGroup
2
+ from .player import Player, register_role
3
+
4
+
5
+ @register_role(Role.Civilian, RoleGroup.GoodGuy)
6
+ class Civilian(Player):
7
+ pass
@@ -0,0 +1,37 @@
1
+ from nonebot_plugin_alconna.uniseg import UniMessage
2
+ from typing_extensions import override
3
+
4
+ from ..constant import Role, RoleGroup
5
+ from ..utils import check_index
6
+ from .player import Player, register_role
7
+
8
+
9
+ @register_role(Role.Guard, RoleGroup.GoodGuy)
10
+ class Guard(Player):
11
+ @override
12
+ async def interact(self) -> None:
13
+ players = self.game.players.alive()
14
+ await self.send(
15
+ UniMessage.text("💫请选择需要保护的玩家:\n")
16
+ .text(players.show())
17
+ .text("\n\n🛡️发送编号选择玩家")
18
+ .text("\n❌发送 “/stop” 结束回合")
19
+ )
20
+
21
+ while True:
22
+ text = await self.receive_text()
23
+ if text == "/stop":
24
+ await self.send("ℹ️你选择了取消,回合结束")
25
+ return
26
+ index = check_index(text, len(players))
27
+ if index is not None:
28
+ selected = index - 1
29
+ if players[selected] is self.selected:
30
+ await self.send("⚠️守卫不能连续两晚保护同一目标,请重新选择")
31
+ continue
32
+ break
33
+ await self.send("⚠️输入错误: 请发送编号选择玩家")
34
+
35
+ self.selected = players[selected]
36
+ self.game.state.protected.add(self.selected)
37
+ await self.send(f"✅本回合保护的玩家: {self.selected.name}")
@@ -0,0 +1,8 @@
1
+ from ..constant import Role, RoleGroup
2
+ from .can_shoot import CanShoot
3
+ from .player import Player, register_role
4
+
5
+
6
+ @register_role(Role.Hunter, RoleGroup.GoodGuy)
7
+ class Hunter(CanShoot, Player):
8
+ pass
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from nonebot_plugin_alconna.uniseg import UniMessage
6
+ from typing_extensions import override
7
+
8
+ from ..constant import KillReason, Role, RoleGroup
9
+ from .player import Player, register_role
10
+
11
+ if TYPE_CHECKING:
12
+ from ..player_set import PlayerSet
13
+
14
+
15
+ @register_role(Role.Idiot, RoleGroup.GoodGuy)
16
+ class Idiot(Player):
17
+ voted: bool = False
18
+
19
+ @override
20
+ async def notify_role(self) -> None:
21
+ await super().notify_role()
22
+ await self.send(
23
+ "作为白痴,你可以在首次被投票放逐时免疫放逐,但在之后的投票中无法继续投票"
24
+ )
25
+
26
+ @override
27
+ async def kill(self, reason: KillReason, *killers: Player) -> bool:
28
+ if reason == KillReason.Vote and not self.voted:
29
+ self.voted = True
30
+ await self.game.send(
31
+ UniMessage.text("⚙️玩家")
32
+ .at(self.user_id)
33
+ .text(" 的身份是白痴\n")
34
+ .text("免疫本次投票放逐,且接下来无法参与投票"),
35
+ )
36
+ return False
37
+ return await super().kill(reason, *killers)
38
+
39
+ @override
40
+ async def vote(self, players: PlayerSet) -> tuple[Player, Player] | None:
41
+ if self.voted:
42
+ await self.send("ℹ️你已经发动过白痴身份的技能,无法参与本次投票")
43
+ return None
44
+ return await super().vote(players)
@@ -0,0 +1,21 @@
1
+ from typing_extensions import override
2
+
3
+ from ..constant import GameStatus, KillReason, Role, RoleGroup
4
+ from ..exception import GameFinished
5
+ from .player import Player, register_role
6
+
7
+
8
+ @register_role(Role.Joker, RoleGroup.Others)
9
+ class Joker(Player):
10
+ @override
11
+ async def notify_role(self) -> None:
12
+ await super().notify_role()
13
+ await self.send("⚙️你的胜利条件: 被投票放逐")
14
+
15
+ @override
16
+ async def kill(self, reason: KillReason, *killers: Player) -> bool:
17
+ await super().kill(reason, *killers)
18
+ if reason == KillReason.Vote:
19
+ self.game.killed_players.append(self)
20
+ raise GameFinished(GameStatus.Joker)
21
+ return True
@@ -0,0 +1,161 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import functools
5
+ import weakref
6
+ from dataclasses import dataclass
7
+ from typing import TYPE_CHECKING, ClassVar, Final, TypeVar, final
8
+
9
+ from nonebot.log import logger
10
+ from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage
11
+
12
+ from ..constant import KillReason, Role, RoleGroup, role_emoji, role_name_conv
13
+ from ..utils import InputStore, check_index
14
+
15
+ if TYPE_CHECKING:
16
+ from collections.abc import Callable
17
+
18
+ from nonebot.adapters import Bot
19
+
20
+ from ..game import Game
21
+ from ..player_set import PlayerSet
22
+
23
+
24
+ P = TypeVar("P", bound=type["Player"])
25
+ PLAYER_CLASS: dict[Role, type[Player]] = {}
26
+
27
+
28
+ @dataclass
29
+ class KillInfo:
30
+ reason: KillReason
31
+ killers: PlayerSet
32
+
33
+
34
+ class Player:
35
+ role: ClassVar[Role]
36
+ role_group: ClassVar[RoleGroup]
37
+
38
+ bot: Final[Bot]
39
+ _game_ref: Final[weakref.ReferenceType[Game]]
40
+ user: Final[Target]
41
+ name: Final[str]
42
+ alive: bool = True
43
+ killed: Final[asyncio.Event]
44
+ kill_info: KillInfo | None = None
45
+ selected: Player | None = None
46
+
47
+ @final
48
+ def __init__(self, bot: Bot, game: Game, user_id: str, name: str) -> None:
49
+ self.bot = bot
50
+ self._game_ref = weakref.ref(game)
51
+ self.user = Target(
52
+ user_id,
53
+ private=True,
54
+ self_id=bot.self_id,
55
+ adapter=bot.adapter.get_name(),
56
+ )
57
+ self.name = name
58
+ self.killed = asyncio.Event()
59
+
60
+ @final
61
+ @classmethod
62
+ def new(cls, role: Role, bot: Bot, game: Game, user_id: str, name: str) -> Player:
63
+ if role not in PLAYER_CLASS:
64
+ raise ValueError(f"Unexpected role: {role!r}")
65
+
66
+ return PLAYER_CLASS[role](bot, game, user_id, name)
67
+
68
+ def __repr__(self) -> str:
69
+ return f"<Player {self.role_name}: user={self.name!r} alive={self.alive}>"
70
+
71
+ @property
72
+ def game(self) -> Game:
73
+ if game := self._game_ref():
74
+ return game
75
+ raise ValueError("Game not exist")
76
+
77
+ @functools.cached_property
78
+ def user_id(self) -> str:
79
+ return self.user.id
80
+
81
+ @functools.cached_property
82
+ def role_name(self) -> str:
83
+ return role_name_conv[self.role]
84
+
85
+ @final
86
+ def _log(self, text: str) -> None:
87
+ text = text.replace("\n", "\\n")
88
+ logger.opt(colors=True).info(
89
+ f"<b><e>{self.game.group.id}</e></b> | "
90
+ f"[<b><m>{self.role_name}</m></b>] "
91
+ f"<y>{self.name}</y>(<b><e>{self.user_id}</e></b>) | "
92
+ f"{text}",
93
+ )
94
+
95
+ @final
96
+ async def send(self, message: str | UniMessage) -> Receipt:
97
+ if isinstance(message, str):
98
+ message = UniMessage.text(message)
99
+
100
+ self._log(f"<g>Send</g> | {message}")
101
+ return await message.send(target=self.user, bot=self.bot)
102
+
103
+ @final
104
+ async def receive(self, prompt: str | UniMessage | None = None) -> UniMessage:
105
+ if prompt:
106
+ await self.send(prompt)
107
+
108
+ result = await InputStore.fetch(self.user.id)
109
+ self._log(f"<y>Recv</y> | {result}")
110
+ return result
111
+
112
+ @final
113
+ async def receive_text(self) -> str:
114
+ return (await self.receive()).extract_plain_text()
115
+
116
+ async def interact(self) -> None:
117
+ return
118
+
119
+ async def notify_role(self) -> None:
120
+ await self.send(f"⚙️你的身份: {role_emoji[self.role]}{self.role_name}")
121
+
122
+ async def kill(self, reason: KillReason, *killers: Player) -> bool:
123
+ from ..player_set import PlayerSet
124
+
125
+ self.alive = False
126
+ self.kill_info = KillInfo(reason=reason, killers=PlayerSet(killers))
127
+ return True
128
+
129
+ async def post_kill(self) -> None:
130
+ self.killed.set()
131
+
132
+ async def vote(self, players: PlayerSet) -> tuple[Player, Player] | None:
133
+ await self.send(
134
+ f"💫请选择需要投票的玩家:\n{players.show()}"
135
+ "\n\n🗳️发送编号选择玩家\n❌发送 “/stop” 弃票"
136
+ )
137
+
138
+ while True:
139
+ text = await self.receive_text()
140
+ if text == "/stop":
141
+ await self.send("⚠️你选择了弃票")
142
+ return None
143
+ index = check_index(text, len(players))
144
+ if index is not None:
145
+ selected = index - 1
146
+ break
147
+ await self.send("⚠️输入错误: 请发送编号选择玩家")
148
+
149
+ player = players[selected]
150
+ await self.send(f"🔨投票的玩家: {player.name}")
151
+ return self, player
152
+
153
+
154
+ def register_role(role: Role, role_group: RoleGroup, /) -> Callable[[P], P]:
155
+ def decorator(cls: P, /) -> P:
156
+ cls.role = role
157
+ cls.role_group = role_group
158
+ PLAYER_CLASS[role] = cls
159
+ return cls
160
+
161
+ return decorator
@@ -0,0 +1,30 @@
1
+ from nonebot_plugin_alconna.uniseg import UniMessage
2
+ from typing_extensions import override
3
+
4
+ from ..constant import Role, RoleGroup
5
+ from ..utils import check_index
6
+ from .player import Player, register_role
7
+
8
+
9
+ @register_role(Role.Prophet, RoleGroup.GoodGuy)
10
+ class Prophet(Player):
11
+ @override
12
+ async def interact(self) -> None:
13
+ players = self.game.players.alive().exclude(self)
14
+ await self.send(
15
+ UniMessage.text("💫请选择需要查验身份的玩家:\n")
16
+ .text(players.show())
17
+ .text("\n\n🔮发送编号选择玩家")
18
+ )
19
+
20
+ while True:
21
+ text = await self.receive_text()
22
+ index = check_index(text, len(players))
23
+ if index is not None:
24
+ selected = index - 1
25
+ break
26
+ await self.send("⚠️输入错误: 请发送编号选择玩家")
27
+
28
+ player = players[selected]
29
+ result = "狼人" if player.role_group == RoleGroup.Werewolf else "好人"
30
+ await self.send(f"✏️玩家 {player.name} 的阵营是『{result}』")
@@ -0,0 +1,67 @@
1
+ import asyncio
2
+
3
+ from nonebot_plugin_alconna.uniseg import UniMessage
4
+ from typing_extensions import override
5
+
6
+ from ..constant import Role, RoleGroup
7
+ from ..utils import check_index
8
+ from .player import Player, register_role
9
+
10
+
11
+ @register_role(Role.Werewolf, RoleGroup.Werewolf)
12
+ class Werewolf(Player):
13
+ @override
14
+ async def notify_role(self) -> None:
15
+ await super().notify_role()
16
+ partners = self.game.players.alive().select(RoleGroup.Werewolf).exclude(self)
17
+ if partners:
18
+ await self.send(
19
+ "🐺你的队友:\n"
20
+ + "\n".join(f" {p.role_name}: {p.name}" for p in partners)
21
+ )
22
+
23
+ @override
24
+ async def interact(self) -> None:
25
+ players = self.game.players.alive()
26
+ partners = players.select(RoleGroup.Werewolf).exclude(self)
27
+
28
+ # 避免阻塞
29
+ def broadcast(msg: str | UniMessage) -> asyncio.Task[None]:
30
+ return asyncio.create_task(partners.broadcast(msg))
31
+
32
+ msg = UniMessage()
33
+ if partners:
34
+ msg = (
35
+ msg.text("🐺你的队友:\n")
36
+ .text("\n".join(f" {p.role_name}: {p.name}" for p in partners))
37
+ .text("\n所有私聊消息将被转发至队友\n\n")
38
+ )
39
+ await self.send(
40
+ msg.text("💫请选择今晚的目标:\n")
41
+ .text(players.show())
42
+ .text("\n\n🔪发送编号选择玩家")
43
+ .text("\n❌发送 “/stop” 结束回合")
44
+ .text("\n\n⚠️意见未统一将空刀")
45
+ )
46
+
47
+ selected = None
48
+ finished = False
49
+ while selected is None or not finished:
50
+ input_msg = await self.receive()
51
+ text = input_msg.extract_plain_text()
52
+ index = check_index(text, len(players))
53
+ if index is not None:
54
+ selected = index - 1
55
+ msg = f"当前选择玩家: {players[selected].name}"
56
+ await self.send(f"🎯{msg}\n发送 “/stop” 结束回合")
57
+ broadcast(f"📝队友 {self.name} {msg}")
58
+ if text == "/stop":
59
+ if selected is not None:
60
+ finished = True
61
+ await self.send("✅你已结束当前回合")
62
+ broadcast(f"📝队友 {self.name} 结束当前回合")
63
+ else:
64
+ await self.send("⚠️当前未选择玩家,无法结束回合")
65
+ broadcast(UniMessage.text(f"💬队友 {self.name}:\n") + input_msg)
66
+
67
+ self.selected = players[selected]
@@ -0,0 +1,72 @@
1
+ from nonebot_plugin_alconna.uniseg import UniMessage
2
+ from typing_extensions import override
3
+
4
+ from ..constant import Role, RoleGroup
5
+ from ..utils import check_index
6
+ from .player import Player, register_role
7
+
8
+
9
+ @register_role(Role.Witch, RoleGroup.GoodGuy)
10
+ class Witch(Player):
11
+ antidote: int = 1
12
+ poison: int = 1
13
+
14
+ async def handle_killed(self) -> bool:
15
+ msg = UniMessage()
16
+ if (killed := self.game.state.killed) is not None:
17
+ msg.text(f"🔪今晚 {killed.name} 被刀了\n\n")
18
+ else:
19
+ await self.send("ℹ️今晚没有人被刀")
20
+ return False
21
+
22
+ if not self.antidote:
23
+ await self.send(msg.text("⚙️你已经用过解药了"))
24
+ return False
25
+
26
+ await self.send(msg.text("✏️使用解药请发送 “1”\n❌不使用解药请发送 “/stop”"))
27
+
28
+ while True:
29
+ text = await self.receive_text()
30
+ if text == "1":
31
+ self.antidote = 0
32
+ self.selected = killed
33
+ self.game.state.antidote.add(killed)
34
+ await self.send(f"✅你对 {killed.name} 使用了解药,回合结束")
35
+ return True
36
+ if text == "/stop":
37
+ return False
38
+ await self.send("⚠️输入错误: 请输入 “1” 或 “/stop”")
39
+
40
+ @override
41
+ async def interact(self) -> None:
42
+ if await self.handle_killed():
43
+ return
44
+
45
+ if not self.poison:
46
+ await self.send("⚙️你没有可以使用的药水,回合结束")
47
+ return
48
+
49
+ players = self.game.players.alive()
50
+ await self.send(
51
+ UniMessage.text("💫你有一瓶毒药\n")
52
+ .text("玩家列表:\n")
53
+ .text(players.show())
54
+ .text("\n\n🧪发送玩家编号使用毒药")
55
+ .text("\n❌发送 “/stop” 结束回合(不使用药水)")
56
+ )
57
+
58
+ while True:
59
+ text = await self.receive_text()
60
+ index = check_index(text, len(players))
61
+ if index is not None:
62
+ selected = index - 1
63
+ break
64
+ if text == "/stop":
65
+ await self.send("ℹ️你选择不使用毒药,回合结束")
66
+ return
67
+ await self.send("⚠️输入错误: 请发送玩家编号或 “/stop”")
68
+
69
+ self.poison = 0
70
+ self.selected = players[selected]
71
+ self.game.state.poison.add(self)
72
+ await self.send(f"✅当前回合选择对玩家 {self.selected.name} 使用毒药\n回合结束")
@@ -0,0 +1,14 @@
1
+ from typing_extensions import override
2
+
3
+ from ..constant import Role, RoleGroup
4
+ from .can_shoot import CanShoot
5
+ from .player import register_role
6
+ from .werewolf import Werewolf
7
+
8
+
9
+ @register_role(Role.WolfKing, RoleGroup.Werewolf)
10
+ class WolfKing(CanShoot, Werewolf):
11
+ @override
12
+ async def notify_role(self) -> None:
13
+ await super().notify_role()
14
+ await self.send("⚙️作为狼王,你可以在死后射杀一名玩家")