nonebot-plugin-werewolf 1.1.3__py3-none-any.whl → 1.1.6__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 +3 -1
- nonebot_plugin_werewolf/config.py +18 -55
- nonebot_plugin_werewolf/constant.py +20 -58
- nonebot_plugin_werewolf/exception.py +1 -1
- nonebot_plugin_werewolf/game.py +286 -245
- nonebot_plugin_werewolf/matchers/__init__.py +2 -0
- nonebot_plugin_werewolf/matchers/depends.py +50 -0
- nonebot_plugin_werewolf/matchers/edit_preset.py +263 -0
- nonebot_plugin_werewolf/matchers/message_in_game.py +18 -3
- nonebot_plugin_werewolf/matchers/poke/__init__.py +8 -0
- nonebot_plugin_werewolf/matchers/poke/chronocat_poke.py +117 -0
- nonebot_plugin_werewolf/matchers/{ob11_ext.py → poke/ob11_poke.py} +21 -19
- nonebot_plugin_werewolf/matchers/start_game.py +266 -28
- nonebot_plugin_werewolf/matchers/superuser_ops.py +24 -0
- nonebot_plugin_werewolf/models.py +73 -0
- nonebot_plugin_werewolf/player_set.py +33 -34
- nonebot_plugin_werewolf/players/can_shoot.py +15 -20
- nonebot_plugin_werewolf/players/civilian.py +3 -3
- nonebot_plugin_werewolf/players/guard.py +16 -22
- nonebot_plugin_werewolf/players/hunter.py +3 -3
- nonebot_plugin_werewolf/players/idiot.py +4 -4
- nonebot_plugin_werewolf/players/joker.py +8 -4
- nonebot_plugin_werewolf/players/player.py +133 -70
- nonebot_plugin_werewolf/players/prophet.py +8 -15
- nonebot_plugin_werewolf/players/werewolf.py +54 -30
- nonebot_plugin_werewolf/players/witch.py +33 -38
- nonebot_plugin_werewolf/players/wolfking.py +3 -3
- nonebot_plugin_werewolf/utils.py +109 -179
- {nonebot_plugin_werewolf-1.1.3.dist-info → nonebot_plugin_werewolf-1.1.6.dist-info}/METADATA +78 -66
- nonebot_plugin_werewolf-1.1.6.dist-info/RECORD +34 -0
- {nonebot_plugin_werewolf-1.1.3.dist-info → nonebot_plugin_werewolf-1.1.6.dist-info}/WHEEL +1 -1
- nonebot_plugin_werewolf/_timeout.py +0 -110
- nonebot_plugin_werewolf-1.1.3.dist-info/RECORD +0 -29
- {nonebot_plugin_werewolf-1.1.3.dist-info → nonebot_plugin_werewolf-1.1.6.dist-info}/LICENSE +0 -0
- {nonebot_plugin_werewolf-1.1.3.dist-info → nonebot_plugin_werewolf-1.1.6.dist-info}/top_level.txt +0 -0
@@ -5,14 +5,14 @@ from typing import TYPE_CHECKING
|
|
5
5
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
6
6
|
from typing_extensions import override
|
7
7
|
|
8
|
-
from ..
|
9
|
-
from .player import Player
|
8
|
+
from ..models import KillReason, Role, RoleGroup
|
9
|
+
from .player import Player
|
10
10
|
|
11
11
|
if TYPE_CHECKING:
|
12
12
|
from ..player_set import PlayerSet
|
13
13
|
|
14
14
|
|
15
|
-
@register_role(Role.Idiot, RoleGroup.GoodGuy)
|
15
|
+
@Player.register_role(Role.Idiot, RoleGroup.GoodGuy)
|
16
16
|
class Idiot(Player):
|
17
17
|
voted: bool = False
|
18
18
|
|
@@ -37,7 +37,7 @@ class Idiot(Player):
|
|
37
37
|
return await super().kill(reason, *killers)
|
38
38
|
|
39
39
|
@override
|
40
|
-
async def vote(self, players: PlayerSet) ->
|
40
|
+
async def vote(self, players: PlayerSet) -> Player | None:
|
41
41
|
if self.voted:
|
42
42
|
await self.send("ℹ️你已经发动过白痴身份的技能,无法参与本次投票")
|
43
43
|
return None
|
@@ -1,11 +1,13 @@
|
|
1
|
+
from typing import TYPE_CHECKING
|
2
|
+
|
1
3
|
from typing_extensions import override
|
2
4
|
|
3
|
-
from ..constant import GameStatus, KillReason, Role, RoleGroup
|
4
5
|
from ..exception import GameFinished
|
5
|
-
from
|
6
|
+
from ..models import GameStatus, KillReason, Role, RoleGroup
|
7
|
+
from .player import Player
|
6
8
|
|
7
9
|
|
8
|
-
@register_role(Role.Joker, RoleGroup.Others)
|
10
|
+
@Player.register_role(Role.Joker, RoleGroup.Others)
|
9
11
|
class Joker(Player):
|
10
12
|
@override
|
11
13
|
async def notify_role(self) -> None:
|
@@ -16,6 +18,8 @@ class Joker(Player):
|
|
16
18
|
async def kill(self, reason: KillReason, *killers: Player) -> bool:
|
17
19
|
await super().kill(reason, *killers)
|
18
20
|
if reason == KillReason.Vote:
|
19
|
-
|
21
|
+
if TYPE_CHECKING:
|
22
|
+
assert self.kill_info is not None
|
23
|
+
self.game.killed_players.append((self.name, self.kill_info))
|
20
24
|
raise GameFinished(GameStatus.Joker)
|
21
25
|
return True
|
@@ -1,95 +1,137 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
|
-
import asyncio
|
4
1
|
import functools
|
5
2
|
import weakref
|
6
|
-
from
|
3
|
+
from collections.abc import Callable
|
7
4
|
from typing import TYPE_CHECKING, ClassVar, Final, TypeVar, final
|
8
5
|
|
6
|
+
import anyio
|
7
|
+
from nonebot.adapters import Bot
|
9
8
|
from nonebot.log import logger
|
9
|
+
from nonebot.utils import escape_tag
|
10
10
|
from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage
|
11
|
+
from nonebot_plugin_uninfo import SceneType
|
11
12
|
|
12
|
-
from ..constant import
|
13
|
-
from ..
|
13
|
+
from ..constant import STOP_COMMAND, STOP_COMMAND_PROMPT, role_emoji, role_name_conv
|
14
|
+
from ..models import KillInfo, KillReason, Role, RoleGroup
|
15
|
+
from ..utils import InputStore, check_index, link
|
14
16
|
|
15
17
|
if TYPE_CHECKING:
|
16
|
-
from collections.abc import Callable
|
17
|
-
|
18
|
-
from nonebot.adapters import Bot
|
19
|
-
|
20
18
|
from ..game import Game
|
21
19
|
from ..player_set import PlayerSet
|
22
20
|
|
23
21
|
|
24
|
-
|
25
|
-
PLAYER_CLASS: dict[Role, type[Player]] = {}
|
26
|
-
|
27
|
-
|
28
|
-
@dataclass
|
29
|
-
class KillInfo:
|
30
|
-
reason: KillReason
|
31
|
-
killers: PlayerSet
|
22
|
+
_P = TypeVar("_P", bound=type["Player"])
|
32
23
|
|
33
24
|
|
34
25
|
class Player:
|
26
|
+
__player_class: ClassVar[dict[Role, type["Player"]]] = {}
|
35
27
|
role: ClassVar[Role]
|
36
28
|
role_group: ClassVar[RoleGroup]
|
37
29
|
|
38
30
|
bot: Final[Bot]
|
39
|
-
_game_ref: Final[weakref.ReferenceType[Game]]
|
40
|
-
user: Final[Target]
|
41
|
-
name: Final[str]
|
42
31
|
alive: bool = True
|
43
|
-
killed: Final[
|
32
|
+
killed: Final[anyio.Event]
|
44
33
|
kill_info: KillInfo | None = None
|
45
|
-
selected: Player | None = None
|
34
|
+
selected: "Player | None" = None
|
46
35
|
|
47
36
|
@final
|
48
|
-
def __init__(self, bot: Bot, game: Game, user_id: str
|
49
|
-
self.
|
50
|
-
self._game_ref = weakref.ref(game)
|
51
|
-
self.user = Target(
|
37
|
+
def __init__(self, bot: Bot, game: "Game", user_id: str) -> None:
|
38
|
+
self.__user = Target(
|
52
39
|
user_id,
|
53
40
|
private=True,
|
54
41
|
self_id=bot.self_id,
|
55
42
|
adapter=bot.adapter.get_name(),
|
56
43
|
)
|
57
|
-
self.
|
58
|
-
self.
|
44
|
+
self.__game_ref = weakref.ref(game)
|
45
|
+
self.bot = bot
|
46
|
+
self.killed = anyio.Event()
|
47
|
+
self._member = None
|
48
|
+
|
49
|
+
@classmethod
|
50
|
+
def register_role(cls, role: Role, role_group: RoleGroup, /) -> Callable[[_P], _P]:
|
51
|
+
def decorator(c: _P, /) -> _P:
|
52
|
+
c.role = role
|
53
|
+
c.role_group = role_group
|
54
|
+
cls.__player_class[role] = c
|
55
|
+
return c
|
56
|
+
|
57
|
+
return decorator
|
59
58
|
|
60
59
|
@final
|
61
60
|
@classmethod
|
62
|
-
def new(cls, role: Role, bot: Bot, game: Game, user_id: str
|
63
|
-
if role not in
|
61
|
+
def new(cls, role: Role, bot: Bot, game: "Game", user_id: str) -> "Player":
|
62
|
+
if role not in cls.__player_class:
|
64
63
|
raise ValueError(f"Unexpected role: {role!r}")
|
65
64
|
|
66
|
-
return
|
65
|
+
return cls.__player_class[role](bot, game, user_id)
|
67
66
|
|
68
67
|
def __repr__(self) -> str:
|
69
|
-
return
|
68
|
+
return (
|
69
|
+
f"<Player {self.role_name}: user={self.user_id!r} " f"alive={self.alive}>"
|
70
|
+
)
|
70
71
|
|
71
72
|
@property
|
72
|
-
def game(self) -> Game:
|
73
|
-
if game := self.
|
73
|
+
def game(self) -> "Game":
|
74
|
+
if game := self.__game_ref():
|
74
75
|
return game
|
75
76
|
raise ValueError("Game not exist")
|
76
77
|
|
77
78
|
@functools.cached_property
|
78
79
|
def user_id(self) -> str:
|
79
|
-
return self.
|
80
|
+
return self.__user.id
|
80
81
|
|
81
82
|
@functools.cached_property
|
82
83
|
def role_name(self) -> str:
|
83
84
|
return role_name_conv[self.role]
|
84
85
|
|
86
|
+
async def _fetch_member(self) -> None:
|
87
|
+
member = await self.game.interface.get_member(
|
88
|
+
SceneType.GROUP,
|
89
|
+
self.game.group.id,
|
90
|
+
self.user_id,
|
91
|
+
)
|
92
|
+
if member is None:
|
93
|
+
member = await self.game.interface.get_member(
|
94
|
+
SceneType.GUILD,
|
95
|
+
self.game.group.id,
|
96
|
+
self.user_id,
|
97
|
+
)
|
98
|
+
|
99
|
+
self._member = member
|
100
|
+
|
101
|
+
@final
|
102
|
+
@property
|
103
|
+
def _member_nick(self) -> str | None:
|
104
|
+
return self._member and (
|
105
|
+
self._member.nick or self._member.user.nick or self._member.user.name
|
106
|
+
)
|
107
|
+
|
108
|
+
@final
|
109
|
+
@property
|
110
|
+
def name(self) -> str:
|
111
|
+
return self._member_nick or self.user_id
|
112
|
+
|
113
|
+
@final
|
114
|
+
@property
|
115
|
+
def colored_name(self) -> str:
|
116
|
+
name = escape_tag(self.user_id)
|
117
|
+
|
118
|
+
if self._member is None or (nick := self._member_nick) is None:
|
119
|
+
name = f"<b><e>{name}</e></b>"
|
120
|
+
else:
|
121
|
+
name = f"<y>{nick}</y>(<b><e>{name}</e></b>)"
|
122
|
+
|
123
|
+
if self._member is not None and self._member.user.avatar is not None:
|
124
|
+
name = link(name, self._member.user.avatar)
|
125
|
+
|
126
|
+
return name
|
127
|
+
|
85
128
|
@final
|
86
129
|
def _log(self, text: str) -> None:
|
87
130
|
text = text.replace("\n", "\\n")
|
88
131
|
logger.opt(colors=True).info(
|
89
|
-
f"
|
132
|
+
f"{self.game.colored_name} | "
|
90
133
|
f"[<b><m>{self.role_name}</m></b>] "
|
91
|
-
f"
|
92
|
-
f"{text}",
|
134
|
+
f"{self.colored_name} | {text}",
|
93
135
|
)
|
94
136
|
|
95
137
|
@final
|
@@ -97,16 +139,16 @@ class Player:
|
|
97
139
|
if isinstance(message, str):
|
98
140
|
message = UniMessage.text(message)
|
99
141
|
|
100
|
-
self._log(f"<g>Send</g> | {message}")
|
101
|
-
return await message.send(target=self.
|
142
|
+
self._log(f"<g>Send</g> | {escape_tag(str(message))}")
|
143
|
+
return await message.send(target=self.__user, bot=self.bot)
|
102
144
|
|
103
145
|
@final
|
104
146
|
async def receive(self, prompt: str | UniMessage | None = None) -> UniMessage:
|
105
147
|
if prompt:
|
106
148
|
await self.send(prompt)
|
107
149
|
|
108
|
-
result = await InputStore.fetch(self.
|
109
|
-
self._log(f"<y>Recv</y> | {result}")
|
150
|
+
result = await InputStore.fetch(self.user_id)
|
151
|
+
self._log(f"<y>Recv</y> | {escape_tag(str(result))}")
|
110
152
|
return result
|
111
153
|
|
112
154
|
@final
|
@@ -117,45 +159,66 @@ class Player:
|
|
117
159
|
return
|
118
160
|
|
119
161
|
async def notify_role(self) -> None:
|
162
|
+
await self._fetch_member()
|
120
163
|
await self.send(f"⚙️你的身份: {role_emoji[self.role]}{self.role_name}")
|
121
164
|
|
122
|
-
async def kill(self, reason: KillReason, *killers: Player) -> bool:
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
self.kill_info = KillInfo(reason=reason, killers=PlayerSet(killers))
|
165
|
+
async def kill(self, reason: KillReason, *killers: "Player") -> bool:
|
166
|
+
if self.alive:
|
167
|
+
self.alive = False
|
168
|
+
self.kill_info = KillInfo(reason=reason, killers=[p.name for p in killers])
|
127
169
|
return True
|
128
170
|
|
129
171
|
async def post_kill(self) -> None:
|
130
172
|
self.killed.set()
|
131
173
|
|
132
|
-
async def vote(self, players: PlayerSet) ->
|
174
|
+
async def vote(self, players: "PlayerSet") -> "Player | None":
|
133
175
|
await self.send(
|
134
176
|
f"💫请选择需要投票的玩家:\n{players.show()}"
|
135
|
-
"\n\n🗳️发送编号选择玩家\n❌发送
|
177
|
+
f"\n\n🗳️发送编号选择玩家\n❌发送 “{STOP_COMMAND_PROMPT}” 弃票"
|
178
|
+
f"\n\n限时1分钟,超时将视为弃票"
|
179
|
+
)
|
180
|
+
|
181
|
+
try:
|
182
|
+
with anyio.fail_after(60):
|
183
|
+
selected = await self._select_player(
|
184
|
+
players,
|
185
|
+
on_stop="⚠️你选择了弃票",
|
186
|
+
on_index_error="⚠️输入错误: 请发送编号选择玩家",
|
187
|
+
)
|
188
|
+
except TimeoutError:
|
189
|
+
selected = None
|
190
|
+
await self.send("⚠️投票超时,将视为弃票")
|
191
|
+
|
192
|
+
if selected is not None:
|
193
|
+
await self.send(f"🔨投票的玩家: {selected.name}")
|
194
|
+
return selected
|
195
|
+
|
196
|
+
async def _check_selected(self, player: "Player") -> "Player | None":
|
197
|
+
return player
|
198
|
+
|
199
|
+
async def _select_player(
|
200
|
+
self,
|
201
|
+
players: "PlayerSet",
|
202
|
+
*,
|
203
|
+
on_stop: str | None = None,
|
204
|
+
on_index_error: str | None = None,
|
205
|
+
) -> "Player | None":
|
206
|
+
on_stop = on_stop or "ℹ️你选择了取消,回合结束"
|
207
|
+
on_index_error = (
|
208
|
+
on_index_error or f"⚠️输入错误: 请发送玩家编号或 “{STOP_COMMAND_PROMPT}”"
|
136
209
|
)
|
210
|
+
selected = None
|
137
211
|
|
138
|
-
while
|
212
|
+
while selected is None:
|
139
213
|
text = await self.receive_text()
|
140
|
-
if text ==
|
141
|
-
|
214
|
+
if text == STOP_COMMAND:
|
215
|
+
if on_stop is not None:
|
216
|
+
await self.send(on_stop)
|
142
217
|
return None
|
143
218
|
index = check_index(text, len(players))
|
144
|
-
if index is
|
145
|
-
|
146
|
-
|
147
|
-
await self.
|
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
|
219
|
+
if index is None:
|
220
|
+
await self.send(on_index_error)
|
221
|
+
continue
|
222
|
+
selected = await self._check_selected(players[index - 1])
|
160
223
|
|
161
|
-
|
224
|
+
return selected
|
@@ -1,12 +1,12 @@
|
|
1
1
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
2
2
|
from typing_extensions import override
|
3
3
|
|
4
|
-
from ..constant import
|
5
|
-
from ..
|
6
|
-
from .player import Player
|
4
|
+
from ..constant import STOP_COMMAND_PROMPT
|
5
|
+
from ..models import Role, RoleGroup
|
6
|
+
from .player import Player
|
7
7
|
|
8
8
|
|
9
|
-
@register_role(Role.Prophet, RoleGroup.GoodGuy)
|
9
|
+
@Player.register_role(Role.Prophet, RoleGroup.GoodGuy)
|
10
10
|
class Prophet(Player):
|
11
11
|
@override
|
12
12
|
async def interact(self) -> None:
|
@@ -15,16 +15,9 @@ class Prophet(Player):
|
|
15
15
|
UniMessage.text("💫请选择需要查验身份的玩家:\n")
|
16
16
|
.text(players.show())
|
17
17
|
.text("\n\n🔮发送编号选择玩家")
|
18
|
+
.text(f"\n❌发送 “{STOP_COMMAND_PROMPT}” 结束回合(不查验身份)")
|
18
19
|
)
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
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}』")
|
21
|
+
if selected := await self._select_player(players):
|
22
|
+
result = "狼人" if selected.role_group == RoleGroup.Werewolf else "好人"
|
23
|
+
await self.send(f"✏️玩家 {selected.name} 的阵营是『{result}』")
|
@@ -1,14 +1,19 @@
|
|
1
|
-
import
|
1
|
+
from typing import TYPE_CHECKING
|
2
2
|
|
3
|
+
import anyio
|
3
4
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
4
5
|
from typing_extensions import override
|
5
6
|
|
6
|
-
from ..constant import
|
7
|
-
from ..
|
8
|
-
from
|
7
|
+
from ..constant import STOP_COMMAND, STOP_COMMAND_PROMPT
|
8
|
+
from ..models import Role, RoleGroup
|
9
|
+
from ..utils import ObjectStream, check_index
|
10
|
+
from .player import Player
|
9
11
|
|
12
|
+
if TYPE_CHECKING:
|
13
|
+
from ..player_set import PlayerSet
|
10
14
|
|
11
|
-
|
15
|
+
|
16
|
+
@Player.register_role(Role.Werewolf, RoleGroup.Werewolf)
|
12
17
|
class Werewolf(Player):
|
13
18
|
@override
|
14
19
|
async def notify_role(self) -> None:
|
@@ -20,15 +25,50 @@ class Werewolf(Player):
|
|
20
25
|
+ "\n".join(f" {p.role_name}: {p.name}" for p in partners)
|
21
26
|
)
|
22
27
|
|
28
|
+
async def _handle_interact(
|
29
|
+
self,
|
30
|
+
players: "PlayerSet",
|
31
|
+
stream: ObjectStream[str | UniMessage],
|
32
|
+
) -> None:
|
33
|
+
self.selected = None
|
34
|
+
|
35
|
+
while True:
|
36
|
+
input_msg = await self.receive()
|
37
|
+
text = input_msg.extract_plain_text()
|
38
|
+
index = check_index(text, len(players))
|
39
|
+
if index is not None:
|
40
|
+
self.selected = players[index - 1]
|
41
|
+
msg = f"当前选择玩家: {self.selected.name}"
|
42
|
+
await self.send(f"🎯{msg}\n发送 “{STOP_COMMAND_PROMPT}” 结束回合")
|
43
|
+
await stream.send(f"📝队友 {self.name} {msg}")
|
44
|
+
if text == STOP_COMMAND:
|
45
|
+
if self.selected is not None:
|
46
|
+
await self.send("✅你已结束当前回合")
|
47
|
+
await stream.send(f"📝队友 {self.name} 结束当前回合")
|
48
|
+
stream.close()
|
49
|
+
return
|
50
|
+
await self.send("⚠️当前未选择玩家,无法结束回合")
|
51
|
+
else:
|
52
|
+
await stream.send(UniMessage.text(f"💬队友 {self.name}:\n") + input_msg)
|
53
|
+
|
54
|
+
async def _handle_broadcast(
|
55
|
+
self,
|
56
|
+
partners: "PlayerSet",
|
57
|
+
stream: ObjectStream[str | UniMessage],
|
58
|
+
) -> None:
|
59
|
+
while not stream.closed:
|
60
|
+
try:
|
61
|
+
message = await stream.recv()
|
62
|
+
except anyio.EndOfStream:
|
63
|
+
return
|
64
|
+
|
65
|
+
await partners.broadcast(message)
|
66
|
+
|
23
67
|
@override
|
24
68
|
async def interact(self) -> None:
|
25
69
|
players = self.game.players.alive()
|
26
70
|
partners = players.select(RoleGroup.Werewolf).exclude(self)
|
27
71
|
|
28
|
-
# 避免阻塞
|
29
|
-
def broadcast(msg: str | UniMessage) -> asyncio.Task[None]:
|
30
|
-
return asyncio.create_task(partners.broadcast(msg))
|
31
|
-
|
32
72
|
msg = UniMessage()
|
33
73
|
if partners:
|
34
74
|
msg = (
|
@@ -40,28 +80,12 @@ class Werewolf(Player):
|
|
40
80
|
msg.text("💫请选择今晚的目标:\n")
|
41
81
|
.text(players.show())
|
42
82
|
.text("\n\n🔪发送编号选择玩家")
|
43
|
-
.text("\n❌发送
|
83
|
+
.text(f"\n❌发送 “{STOP_COMMAND_PROMPT}” 结束回合")
|
44
84
|
.text("\n\n⚠️意见未统一将空刀")
|
45
85
|
)
|
46
86
|
|
47
|
-
|
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)
|
87
|
+
stream = ObjectStream[str | UniMessage](8)
|
66
88
|
|
67
|
-
|
89
|
+
async with anyio.create_task_group() as tg:
|
90
|
+
tg.start_soon(self._handle_interact, players, stream)
|
91
|
+
tg.start_soon(self._handle_broadcast, partners, stream)
|
@@ -1,41 +1,43 @@
|
|
1
1
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
2
2
|
from typing_extensions import override
|
3
3
|
|
4
|
-
from ..constant import
|
5
|
-
from ..
|
6
|
-
from
|
4
|
+
from ..constant import STOP_COMMAND_PROMPT
|
5
|
+
from ..models import Role, RoleGroup
|
6
|
+
from ..utils import as_player_set
|
7
|
+
from .player import Player
|
7
8
|
|
8
9
|
|
9
|
-
@register_role(Role.Witch, RoleGroup.GoodGuy)
|
10
|
+
@Player.register_role(Role.Witch, RoleGroup.GoodGuy)
|
10
11
|
class Witch(Player):
|
11
|
-
antidote:
|
12
|
-
poison:
|
12
|
+
antidote: bool = True
|
13
|
+
poison: bool = True
|
13
14
|
|
14
15
|
async def handle_killed(self) -> bool:
|
15
|
-
|
16
|
-
if (killed := self.game.state.killed) is not None:
|
17
|
-
msg.text(f"🔪今晚 {killed.name} 被刀了\n\n")
|
18
|
-
else:
|
16
|
+
if (killed := self.game.state.killed) is None:
|
19
17
|
await self.send("ℹ️今晚没有人被刀")
|
20
18
|
return False
|
21
19
|
|
20
|
+
msg = UniMessage.text(f"🔪今晚 {killed.name} 被刀了\n\n")
|
21
|
+
|
22
22
|
if not self.antidote:
|
23
23
|
await self.send(msg.text("⚙️你已经用过解药了"))
|
24
24
|
return False
|
25
25
|
|
26
|
-
|
26
|
+
msg.text(f"✏️使用解药请发送 “1”\n❌不使用解药请发送 “{STOP_COMMAND_PROMPT}”")
|
27
|
+
await self.send(msg)
|
28
|
+
|
29
|
+
if not await self._select_player(
|
30
|
+
as_player_set(killed),
|
31
|
+
on_stop=f"ℹ️你选择不对 {killed.name} 使用解药",
|
32
|
+
on_index_error=f"⚠️输入错误: 请输入 “1” 或 “{STOP_COMMAND_PROMPT}”",
|
33
|
+
):
|
34
|
+
return False
|
27
35
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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”")
|
36
|
+
self.antidote = False
|
37
|
+
self.selected = killed
|
38
|
+
self.game.state.antidote.add(killed)
|
39
|
+
await self.send(f"✅你对 {killed.name} 使用了解药,回合结束")
|
40
|
+
return True
|
39
41
|
|
40
42
|
@override
|
41
43
|
async def interact(self) -> None:
|
@@ -52,21 +54,14 @@ class Witch(Player):
|
|
52
54
|
.text("玩家列表:\n")
|
53
55
|
.text(players.show())
|
54
56
|
.text("\n\n🧪发送玩家编号使用毒药")
|
55
|
-
.text("\n❌发送
|
57
|
+
.text(f"\n❌发送 “{STOP_COMMAND_PROMPT}” 结束回合(不使用药水)")
|
56
58
|
)
|
57
59
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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回合结束")
|
60
|
+
if selected := await self._select_player(
|
61
|
+
players,
|
62
|
+
on_stop="ℹ️你选择不使用毒药,回合结束",
|
63
|
+
):
|
64
|
+
self.poison = False
|
65
|
+
self.selected = selected
|
66
|
+
self.game.state.poison.add(self)
|
67
|
+
await self.send(f"✅当前回合选择对玩家 {selected.name} 使用毒药\n回合结束")
|
@@ -1,12 +1,12 @@
|
|
1
1
|
from typing_extensions import override
|
2
2
|
|
3
|
-
from ..
|
3
|
+
from ..models import Role, RoleGroup
|
4
4
|
from .can_shoot import CanShoot
|
5
|
-
from .player import
|
5
|
+
from .player import Player
|
6
6
|
from .werewolf import Werewolf
|
7
7
|
|
8
8
|
|
9
|
-
@register_role(Role.WolfKing, RoleGroup.Werewolf)
|
9
|
+
@Player.register_role(Role.WolfKing, RoleGroup.Werewolf)
|
10
10
|
class WolfKing(CanShoot, Werewolf):
|
11
11
|
@override
|
12
12
|
async def notify_role(self) -> None:
|