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.
- nonebot_plugin_werewolf/__init__.py +1 -1
- nonebot_plugin_werewolf/config.py +76 -18
- nonebot_plugin_werewolf/constant.py +54 -46
- nonebot_plugin_werewolf/exception.py +2 -4
- nonebot_plugin_werewolf/game.py +193 -166
- nonebot_plugin_werewolf/matchers/__init__.py +1 -0
- nonebot_plugin_werewolf/matchers/depends.py +4 -4
- nonebot_plugin_werewolf/matchers/edit_behavior.py +205 -0
- nonebot_plugin_werewolf/matchers/edit_preset.py +11 -11
- nonebot_plugin_werewolf/matchers/message_in_game.py +3 -1
- nonebot_plugin_werewolf/matchers/poke/chronocat_poke.py +8 -5
- nonebot_plugin_werewolf/matchers/poke/ob11_poke.py +3 -3
- nonebot_plugin_werewolf/matchers/start_game.py +213 -175
- nonebot_plugin_werewolf/matchers/superuser_ops.py +3 -3
- nonebot_plugin_werewolf/models.py +31 -19
- nonebot_plugin_werewolf/player_set.py +10 -8
- nonebot_plugin_werewolf/players/__init__.py +1 -1
- nonebot_plugin_werewolf/players/can_shoot.py +15 -15
- nonebot_plugin_werewolf/players/civilian.py +1 -1
- nonebot_plugin_werewolf/players/guard.py +16 -14
- nonebot_plugin_werewolf/players/hunter.py +1 -1
- nonebot_plugin_werewolf/players/idiot.py +3 -3
- nonebot_plugin_werewolf/players/{joker.py → jester.py} +4 -5
- nonebot_plugin_werewolf/players/player.py +93 -29
- nonebot_plugin_werewolf/players/prophet.py +11 -10
- nonebot_plugin_werewolf/players/werewolf.py +63 -31
- nonebot_plugin_werewolf/players/witch.py +29 -12
- nonebot_plugin_werewolf/players/wolfking.py +1 -1
- nonebot_plugin_werewolf/utils.py +106 -7
- {nonebot_plugin_werewolf-1.1.6.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/METADATA +27 -20
- nonebot_plugin_werewolf-1.1.8.dist-info/RECORD +35 -0
- {nonebot_plugin_werewolf-1.1.6.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/WHEEL +1 -1
- nonebot_plugin_werewolf-1.1.6.dist-info/RECORD +0 -34
- {nonebot_plugin_werewolf-1.1.6.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/LICENSE +0 -0
- {nonebot_plugin_werewolf-1.1.6.dist-info → nonebot_plugin_werewolf-1.1.8.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,8 @@
|
|
1
|
-
from nonebot_plugin_alconna.uniseg import UniMessage
|
2
1
|
from typing_extensions import override
|
3
2
|
|
4
|
-
from
|
3
|
+
from nonebot_plugin_alconna.uniseg import UniMessage
|
4
|
+
|
5
|
+
from ..constant import stop_command_prompt
|
5
6
|
from ..models import KillReason
|
6
7
|
from .player import Player
|
7
8
|
|
@@ -9,7 +10,7 @@ from .player import Player
|
|
9
10
|
class CanShoot(Player):
|
10
11
|
@override
|
11
12
|
async def post_kill(self) -> None:
|
12
|
-
if self.kill_info and self.kill_info.reason == KillReason.
|
13
|
+
if self.kill_info and self.kill_info.reason == KillReason.POISON:
|
13
14
|
await self.send("⚠️你昨晚被女巫毒杀,无法使用技能")
|
14
15
|
return await super().post_kill()
|
15
16
|
|
@@ -21,33 +22,32 @@ class CanShoot(Player):
|
|
21
22
|
|
22
23
|
self.game.state.shoot = None
|
23
24
|
shoot = await self.shoot()
|
24
|
-
|
25
|
+
msg = UniMessage.text("玩家 ").at(self.user_id).text(" ")
|
25
26
|
if shoot is not None:
|
26
27
|
self.game.state.shoot = self
|
27
|
-
await self.send(
|
28
|
-
|
29
|
-
.at(self.user_id)
|
30
|
-
.text(" 射杀了玩家 ")
|
31
|
-
.at(shoot.user_id)
|
32
|
-
)
|
33
|
-
await shoot.kill(KillReason.Shoot, self)
|
28
|
+
await self.game.send("🔫" + msg.text("射杀了玩家 ").at(shoot.user_id))
|
29
|
+
await shoot.kill(KillReason.SHOOT, self)
|
34
30
|
self.selected = shoot
|
35
31
|
else:
|
36
|
-
await self.send(
|
32
|
+
await self.game.send("ℹ️" + msg.text("选择了取消技能"))
|
33
|
+
|
37
34
|
return await super().post_kill()
|
38
35
|
|
39
36
|
async def shoot(self) -> Player | None:
|
40
37
|
players = self.game.players.alive().exclude(self)
|
41
38
|
await self.send(
|
42
39
|
"💫请选择需要射杀的玩家:\n"
|
43
|
-
|
44
|
-
|
45
|
-
|
40
|
+
f"{players.show()}\n\n"
|
41
|
+
"🔫发送编号选择玩家\n"
|
42
|
+
f"❌发送 “{stop_command_prompt()}” 取消技能",
|
43
|
+
stop_btn_label="取消技能",
|
44
|
+
select_players=players,
|
46
45
|
)
|
47
46
|
|
48
47
|
if selected := await self._select_player(
|
49
48
|
players,
|
50
49
|
on_stop="ℹ️已取消技能,回合结束",
|
50
|
+
stop_btn_label="取消技能",
|
51
51
|
):
|
52
52
|
await self.send(f"🎯选择射杀的玩家: {selected.name}")
|
53
53
|
|
@@ -1,31 +1,33 @@
|
|
1
|
-
from nonebot_plugin_alconna.uniseg import UniMessage
|
2
1
|
from typing_extensions import override
|
3
2
|
|
4
|
-
from ..constant import
|
5
|
-
from ..models import Role, RoleGroup
|
3
|
+
from ..constant import stop_command_prompt
|
4
|
+
from ..models import GameState, Role, RoleGroup
|
6
5
|
from .player import Player
|
7
6
|
|
8
7
|
|
9
|
-
@Player.register_role(Role.
|
8
|
+
@Player.register_role(Role.GUARD, RoleGroup.GOODGUY)
|
10
9
|
class Guard(Player):
|
11
10
|
@override
|
12
11
|
async def _check_selected(self, player: Player) -> Player | None:
|
13
|
-
if player is
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
if self.game.state.state == GameState.State.NIGHT and player is self.selected:
|
13
|
+
await self.send("⚠️守卫不能连续两晚保护同一目标,请重新选择")
|
14
|
+
return None
|
15
|
+
|
16
|
+
return player
|
17
17
|
|
18
18
|
@override
|
19
|
-
async def
|
19
|
+
async def _interact(self) -> None:
|
20
20
|
players = self.game.players.alive()
|
21
21
|
await self.send(
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
"💫请选择需要保护的玩家:\n"
|
23
|
+
f"{players.show()}\n\n"
|
24
|
+
"🛡️发送编号选择玩家\n"
|
25
|
+
f"❌发送 “{stop_command_prompt()}” 结束回合",
|
26
|
+
stop_btn_label="结束回合",
|
27
|
+
select_players=players,
|
26
28
|
)
|
27
29
|
|
28
|
-
self.selected = await self._select_player(players)
|
30
|
+
self.selected = await self._select_player(players, stop_btn_label="结束回合")
|
29
31
|
if self.selected:
|
30
32
|
self.game.state.protected.add(self.selected)
|
31
33
|
await self.send(f"✅本回合保护的玩家: {self.selected.name}")
|
@@ -1,9 +1,9 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from typing import TYPE_CHECKING
|
4
|
+
from typing_extensions import override
|
4
5
|
|
5
6
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
6
|
-
from typing_extensions import override
|
7
7
|
|
8
8
|
from ..models import KillReason, Role, RoleGroup
|
9
9
|
from .player import Player
|
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
|
|
12
12
|
from ..player_set import PlayerSet
|
13
13
|
|
14
14
|
|
15
|
-
@Player.register_role(Role.
|
15
|
+
@Player.register_role(Role.IDIOT, RoleGroup.GOODGUY)
|
16
16
|
class Idiot(Player):
|
17
17
|
voted: bool = False
|
18
18
|
|
@@ -25,7 +25,7 @@ class Idiot(Player):
|
|
25
25
|
|
26
26
|
@override
|
27
27
|
async def kill(self, reason: KillReason, *killers: Player) -> bool:
|
28
|
-
if reason == KillReason.
|
28
|
+
if reason == KillReason.VOTE and not self.voted:
|
29
29
|
self.voted = True
|
30
30
|
await self.game.send(
|
31
31
|
UniMessage.text("⚙️玩家")
|
@@ -1,5 +1,4 @@
|
|
1
1
|
from typing import TYPE_CHECKING
|
2
|
-
|
3
2
|
from typing_extensions import override
|
4
3
|
|
5
4
|
from ..exception import GameFinished
|
@@ -7,8 +6,8 @@ from ..models import GameStatus, KillReason, Role, RoleGroup
|
|
7
6
|
from .player import Player
|
8
7
|
|
9
8
|
|
10
|
-
@Player.register_role(Role.
|
11
|
-
class
|
9
|
+
@Player.register_role(Role.JESTER, RoleGroup.OTHERS)
|
10
|
+
class Jester(Player):
|
12
11
|
@override
|
13
12
|
async def notify_role(self) -> None:
|
14
13
|
await super().notify_role()
|
@@ -17,9 +16,9 @@ class Joker(Player):
|
|
17
16
|
@override
|
18
17
|
async def kill(self, reason: KillReason, *killers: Player) -> bool:
|
19
18
|
await super().kill(reason, *killers)
|
20
|
-
if reason == KillReason.
|
19
|
+
if reason == KillReason.VOTE:
|
21
20
|
if TYPE_CHECKING:
|
22
21
|
assert self.kill_info is not None
|
23
22
|
self.game.killed_players.append((self.name, self.kill_info))
|
24
|
-
raise GameFinished(GameStatus.
|
23
|
+
raise GameFinished(GameStatus.JESTER)
|
25
24
|
return True
|
@@ -4,15 +4,23 @@ from collections.abc import Callable
|
|
4
4
|
from typing import TYPE_CHECKING, ClassVar, Final, TypeVar, final
|
5
5
|
|
6
6
|
import anyio
|
7
|
+
import nonebot
|
7
8
|
from nonebot.adapters import Bot
|
8
|
-
from nonebot.log import logger
|
9
9
|
from nonebot.utils import escape_tag
|
10
10
|
from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage
|
11
11
|
from nonebot_plugin_uninfo import SceneType
|
12
12
|
|
13
|
-
from ..
|
13
|
+
from ..config import GameBehavior
|
14
|
+
from ..constant import ROLE_EMOJI, ROLE_NAME_CONV, STOP_COMMAND, stop_command_prompt
|
14
15
|
from ..models import KillInfo, KillReason, Role, RoleGroup
|
15
|
-
from ..utils import
|
16
|
+
from ..utils import (
|
17
|
+
InputStore,
|
18
|
+
SendHandler,
|
19
|
+
add_players_button,
|
20
|
+
add_stop_button,
|
21
|
+
check_index,
|
22
|
+
link,
|
23
|
+
)
|
16
24
|
|
17
25
|
if TYPE_CHECKING:
|
18
26
|
from ..game import Game
|
@@ -21,6 +29,19 @@ if TYPE_CHECKING:
|
|
21
29
|
|
22
30
|
_P = TypeVar("_P", bound=type["Player"])
|
23
31
|
|
32
|
+
logger = nonebot.logger.opt(colors=True)
|
33
|
+
|
34
|
+
|
35
|
+
class _SendHandler(SendHandler[str | None]):
|
36
|
+
def solve_msg(
|
37
|
+
self,
|
38
|
+
msg: UniMessage,
|
39
|
+
stop_btn_label: str | None = None,
|
40
|
+
) -> UniMessage:
|
41
|
+
if stop_btn_label is not None:
|
42
|
+
msg = add_stop_button(msg, stop_btn_label)
|
43
|
+
return msg
|
44
|
+
|
24
45
|
|
25
46
|
class Player:
|
26
47
|
__player_class: ClassVar[dict[Role, type["Player"]]] = {}
|
@@ -45,6 +66,8 @@ class Player:
|
|
45
66
|
self.bot = bot
|
46
67
|
self.killed = anyio.Event()
|
47
68
|
self._member = None
|
69
|
+
self._send_handler = _SendHandler()
|
70
|
+
self._send_handler.update(self.__user, bot)
|
48
71
|
|
49
72
|
@classmethod
|
50
73
|
def register_role(cls, role: Role, role_group: RoleGroup, /) -> Callable[[_P], _P]:
|
@@ -65,9 +88,7 @@ class Player:
|
|
65
88
|
return cls.__player_class[role](bot, game, user_id)
|
66
89
|
|
67
90
|
def __repr__(self) -> str:
|
68
|
-
return
|
69
|
-
f"<Player {self.role_name}: user={self.user_id!r} " f"alive={self.alive}>"
|
70
|
-
)
|
91
|
+
return f"<Player {self.role_name}: user={self.user_id!r} alive={self.alive}>"
|
71
92
|
|
72
93
|
@property
|
73
94
|
def game(self) -> "Game":
|
@@ -81,7 +102,7 @@ class Player:
|
|
81
102
|
|
82
103
|
@functools.cached_property
|
83
104
|
def role_name(self) -> str:
|
84
|
-
return
|
105
|
+
return ROLE_NAME_CONV[self.role]
|
85
106
|
|
86
107
|
async def _fetch_member(self) -> None:
|
87
108
|
member = await self.game.interface.get_member(
|
@@ -126,41 +147,75 @@ class Player:
|
|
126
147
|
return name
|
127
148
|
|
128
149
|
@final
|
129
|
-
def
|
150
|
+
def log(self, text: str) -> None:
|
130
151
|
text = text.replace("\n", "\\n")
|
131
|
-
|
132
|
-
f"{self.game.colored_name} | "
|
133
|
-
f"[<b><m>{self.role_name}</m></b>] "
|
134
|
-
f"{self.colored_name} | {text}",
|
135
|
-
)
|
152
|
+
self.game.log(f"[<b><m>{self.role_name}</m></b>] {self.colored_name} | {text}")
|
136
153
|
|
137
154
|
@final
|
138
|
-
async def send(
|
155
|
+
async def send(
|
156
|
+
self,
|
157
|
+
message: str | UniMessage,
|
158
|
+
*,
|
159
|
+
stop_btn_label: str | None = None,
|
160
|
+
select_players: "PlayerSet | None" = None,
|
161
|
+
skip_handler: bool = False,
|
162
|
+
) -> Receipt:
|
139
163
|
if isinstance(message, str):
|
140
164
|
message = UniMessage.text(message)
|
141
165
|
|
142
|
-
self.
|
143
|
-
return await message.send(target=self.__user, bot=self.bot)
|
166
|
+
self.log(f"<g>Send</g> | {escape_tag(str(message))}")
|
144
167
|
|
145
|
-
|
146
|
-
|
147
|
-
if
|
148
|
-
await
|
168
|
+
if select_players:
|
169
|
+
message = add_players_button(message, select_players)
|
170
|
+
if skip_handler:
|
171
|
+
return await message.send(self.__user, self.bot)
|
172
|
+
return await self._send_handler.send(message, stop_btn_label)
|
149
173
|
|
174
|
+
@final
|
175
|
+
async def receive(self) -> UniMessage:
|
150
176
|
result = await InputStore.fetch(self.user_id)
|
151
|
-
self.
|
177
|
+
self.log(f"<y>Recv</y> | {escape_tag(str(result))}")
|
152
178
|
return result
|
153
179
|
|
154
180
|
@final
|
155
181
|
async def receive_text(self) -> str:
|
156
182
|
return (await self.receive()).extract_plain_text()
|
157
183
|
|
158
|
-
|
184
|
+
@property
|
185
|
+
def interact_timeout(self) -> float:
|
186
|
+
return GameBehavior.get().timeout.interact
|
187
|
+
|
188
|
+
async def _before_interact(self) -> None:
|
159
189
|
return
|
160
190
|
|
191
|
+
async def _interact(self) -> None:
|
192
|
+
return
|
193
|
+
|
194
|
+
async def _after_interact(self) -> None:
|
195
|
+
return
|
196
|
+
|
197
|
+
async def interact(self) -> None:
|
198
|
+
if not getattr(self._interact, "__override__", False):
|
199
|
+
await self.send("ℹ️请等待其他玩家结束交互...")
|
200
|
+
return
|
201
|
+
|
202
|
+
await self._before_interact()
|
203
|
+
|
204
|
+
timeout = self.interact_timeout
|
205
|
+
await self.send(f"✏️{self.role_name}交互开始,限时 {timeout / 60:.2f} 分钟")
|
206
|
+
|
207
|
+
try:
|
208
|
+
with anyio.fail_after(timeout):
|
209
|
+
await self._interact()
|
210
|
+
except TimeoutError:
|
211
|
+
logger.debug(f"{self.role_name}交互超时 (<y>{timeout}</y>s)")
|
212
|
+
await self.send(f"⚠️{self.role_name}交互超时")
|
213
|
+
|
214
|
+
await self._after_interact()
|
215
|
+
|
161
216
|
async def notify_role(self) -> None:
|
162
217
|
await self._fetch_member()
|
163
|
-
await self.send(f"⚙️你的身份: {
|
218
|
+
await self.send(f"⚙️你的身份: {ROLE_EMOJI[self.role]}{self.role_name}")
|
164
219
|
|
165
220
|
async def kill(self, reason: KillReason, *killers: "Player") -> bool:
|
166
221
|
if self.alive:
|
@@ -173,13 +228,17 @@ class Player:
|
|
173
228
|
|
174
229
|
async def vote(self, players: "PlayerSet") -> "Player | None":
|
175
230
|
await self.send(
|
176
|
-
f"💫请选择需要投票的玩家:\n
|
177
|
-
f"\n\n
|
178
|
-
|
231
|
+
f"💫请选择需要投票的玩家:\n"
|
232
|
+
f"{players.show()}\n\n"
|
233
|
+
"🗳️发送编号选择玩家\n"
|
234
|
+
f"❌发送 “{stop_command_prompt()}” 弃票\n\n"
|
235
|
+
"限时1分钟,超时将视为弃票",
|
236
|
+
stop_btn_label="弃票",
|
237
|
+
select_players=players,
|
179
238
|
)
|
180
239
|
|
181
240
|
try:
|
182
|
-
with anyio.fail_after(
|
241
|
+
with anyio.fail_after(GameBehavior.get().timeout.vote):
|
183
242
|
selected = await self._select_player(
|
184
243
|
players,
|
185
244
|
on_stop="⚠️你选择了弃票",
|
@@ -202,10 +261,11 @@ class Player:
|
|
202
261
|
*,
|
203
262
|
on_stop: str | None = None,
|
204
263
|
on_index_error: str | None = None,
|
264
|
+
stop_btn_label: str | None = None,
|
205
265
|
) -> "Player | None":
|
206
266
|
on_stop = on_stop or "ℹ️你选择了取消,回合结束"
|
207
267
|
on_index_error = (
|
208
|
-
on_index_error or f"⚠️输入错误: 请发送玩家编号或 “{
|
268
|
+
on_index_error or f"⚠️输入错误: 请发送玩家编号或 “{stop_command_prompt()}”"
|
209
269
|
)
|
210
270
|
selected = None
|
211
271
|
|
@@ -217,7 +277,11 @@ class Player:
|
|
217
277
|
return None
|
218
278
|
index = check_index(text, len(players))
|
219
279
|
if index is None:
|
220
|
-
await self.send(
|
280
|
+
await self.send(
|
281
|
+
on_index_error,
|
282
|
+
stop_btn_label=stop_btn_label,
|
283
|
+
select_players=players,
|
284
|
+
)
|
221
285
|
continue
|
222
286
|
selected = await self._check_selected(players[index - 1])
|
223
287
|
|
@@ -1,23 +1,24 @@
|
|
1
|
-
from nonebot_plugin_alconna.uniseg import UniMessage
|
2
1
|
from typing_extensions import override
|
3
2
|
|
4
|
-
from ..constant import
|
3
|
+
from ..constant import stop_command_prompt
|
5
4
|
from ..models import Role, RoleGroup
|
6
5
|
from .player import Player
|
7
6
|
|
8
7
|
|
9
|
-
@Player.register_role(Role.
|
8
|
+
@Player.register_role(Role.PROPHET, RoleGroup.GOODGUY)
|
10
9
|
class Prophet(Player):
|
11
10
|
@override
|
12
|
-
async def
|
11
|
+
async def _interact(self) -> None:
|
13
12
|
players = self.game.players.alive().exclude(self)
|
14
13
|
await self.send(
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
"💫请选择需要查验身份的玩家:\n"
|
15
|
+
f"{players.show()}\n\n"
|
16
|
+
"🔮发送编号选择玩家\n"
|
17
|
+
f"❌发送 “{stop_command_prompt()}” 结束回合(不查验身份)",
|
18
|
+
stop_btn_label="结束回合",
|
19
|
+
select_players=players,
|
19
20
|
)
|
20
21
|
|
21
|
-
if selected := await self._select_player(players):
|
22
|
-
result = "狼人" if selected.role_group == RoleGroup.
|
22
|
+
if selected := await self._select_player(players, stop_btn_label="结束回合"):
|
23
|
+
result = "狼人" if selected.role_group == RoleGroup.WEREWOLF else "好人"
|
23
24
|
await self.send(f"✏️玩家 {selected.name} 的阵营是『{result}』")
|
@@ -1,10 +1,13 @@
|
|
1
|
+
import secrets
|
1
2
|
from typing import TYPE_CHECKING
|
3
|
+
from typing_extensions import override
|
2
4
|
|
3
5
|
import anyio
|
6
|
+
import nonebot
|
4
7
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
5
|
-
from typing_extensions import override
|
6
8
|
|
7
|
-
from ..
|
9
|
+
from ..config import GameBehavior
|
10
|
+
from ..constant import STOP_COMMAND, stop_command_prompt
|
8
11
|
from ..models import Role, RoleGroup
|
9
12
|
from ..utils import ObjectStream, check_index
|
10
13
|
from .player import Player
|
@@ -12,24 +15,29 @@ from .player import Player
|
|
12
15
|
if TYPE_CHECKING:
|
13
16
|
from ..player_set import PlayerSet
|
14
17
|
|
18
|
+
logger = nonebot.logger.opt(colors=True)
|
19
|
+
|
15
20
|
|
16
|
-
@Player.register_role(Role.
|
21
|
+
@Player.register_role(Role.WEREWOLF, RoleGroup.WEREWOLF)
|
17
22
|
class Werewolf(Player):
|
23
|
+
stream: ObjectStream[str | UniMessage]
|
24
|
+
|
25
|
+
@property
|
26
|
+
@override
|
27
|
+
def interact_timeout(self) -> float:
|
28
|
+
return GameBehavior.get().timeout.werewolf
|
29
|
+
|
18
30
|
@override
|
19
31
|
async def notify_role(self) -> None:
|
20
32
|
await super().notify_role()
|
21
|
-
partners = self.game.players.alive().select(RoleGroup.
|
33
|
+
partners = self.game.players.alive().select(RoleGroup.WEREWOLF).exclude(self)
|
22
34
|
if partners:
|
23
35
|
await self.send(
|
24
36
|
"🐺你的队友:\n"
|
25
37
|
+ "\n".join(f" {p.role_name}: {p.name}" for p in partners)
|
26
38
|
)
|
27
39
|
|
28
|
-
async def _handle_interact(
|
29
|
-
self,
|
30
|
-
players: "PlayerSet",
|
31
|
-
stream: ObjectStream[str | UniMessage],
|
32
|
-
) -> None:
|
40
|
+
async def _handle_interact(self, players: "PlayerSet") -> None:
|
33
41
|
self.selected = None
|
34
42
|
|
35
43
|
while True:
|
@@ -39,35 +47,40 @@ class Werewolf(Player):
|
|
39
47
|
if index is not None:
|
40
48
|
self.selected = players[index - 1]
|
41
49
|
msg = f"当前选择玩家: {self.selected.name}"
|
42
|
-
await self.send(
|
43
|
-
|
50
|
+
await self.send(
|
51
|
+
f"🎯{msg}\n发送 “{stop_command_prompt()}” 结束回合",
|
52
|
+
stop_btn_label="结束回合",
|
53
|
+
select_players=players,
|
54
|
+
)
|
55
|
+
await self.stream.send(f"📝队友 {self.name} {msg}")
|
44
56
|
if text == STOP_COMMAND:
|
45
57
|
if self.selected is not None:
|
46
58
|
await self.send("✅你已结束当前回合")
|
47
|
-
await stream.send(f"📝队友 {self.name} 结束当前回合")
|
48
|
-
stream.close()
|
59
|
+
await self.stream.send(f"📝队友 {self.name} 结束当前回合")
|
60
|
+
self.stream.close()
|
49
61
|
return
|
50
|
-
await self.send(
|
62
|
+
await self.send(
|
63
|
+
"⚠️当前未选择玩家,无法结束回合",
|
64
|
+
select_players=players,
|
65
|
+
)
|
51
66
|
else:
|
52
|
-
await stream.send(
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
stream:
|
58
|
-
) -> None:
|
59
|
-
while not stream.closed:
|
67
|
+
await self.stream.send(
|
68
|
+
UniMessage.text(f"💬队友 {self.name}:\n") + input_msg
|
69
|
+
)
|
70
|
+
|
71
|
+
async def _handle_broadcast(self, partners: "PlayerSet") -> None:
|
72
|
+
while not self.stream.closed:
|
60
73
|
try:
|
61
|
-
message = await stream.recv()
|
74
|
+
message = await self.stream.recv()
|
62
75
|
except anyio.EndOfStream:
|
63
76
|
return
|
64
77
|
|
65
78
|
await partners.broadcast(message)
|
66
79
|
|
67
80
|
@override
|
68
|
-
async def
|
81
|
+
async def _interact(self) -> None:
|
69
82
|
players = self.game.players.alive()
|
70
|
-
partners = players.select(RoleGroup.
|
83
|
+
partners = players.select(RoleGroup.WEREWOLF).exclude(self)
|
71
84
|
|
72
85
|
msg = UniMessage()
|
73
86
|
if partners:
|
@@ -80,12 +93,31 @@ class Werewolf(Player):
|
|
80
93
|
msg.text("💫请选择今晚的目标:\n")
|
81
94
|
.text(players.show())
|
82
95
|
.text("\n\n🔪发送编号选择玩家")
|
83
|
-
.text(f"\n❌发送 “{
|
84
|
-
.text("\n\n⚠️意见未统一将空刀")
|
96
|
+
.text(f"\n❌发送 “{stop_command_prompt()}” 结束回合")
|
97
|
+
.text("\n\n⚠️意见未统一将空刀"),
|
98
|
+
select_players=players,
|
85
99
|
)
|
86
100
|
|
87
|
-
stream = ObjectStream[str | UniMessage](8)
|
101
|
+
self.stream = ObjectStream[str | UniMessage](8)
|
102
|
+
|
103
|
+
try:
|
104
|
+
async with anyio.create_task_group() as tg:
|
105
|
+
tg.start_soon(self._handle_interact, players)
|
106
|
+
tg.start_soon(self._handle_broadcast, partners)
|
107
|
+
finally:
|
108
|
+
del self.stream
|
109
|
+
|
110
|
+
@override
|
111
|
+
async def _after_interact(self) -> None:
|
112
|
+
state = self.game.state
|
113
|
+
if not state.werewolf_finished.is_set():
|
114
|
+
state.werewolf_finished.set()
|
115
|
+
w = self.game.players.alive().select(RoleGroup.WEREWOLF)
|
116
|
+
if (s := w.player_selected()).size == 1:
|
117
|
+
state.killed = s.pop()
|
118
|
+
await w.broadcast(f"🔪今晚选择的目标为: {state.killed.name}")
|
119
|
+
else:
|
120
|
+
await w.broadcast("⚠️狼人阵营意见未统一,此晚空刀")
|
88
121
|
|
89
|
-
|
90
|
-
|
91
|
-
tg.start_soon(self._handle_broadcast, partners, stream)
|
122
|
+
if not self.game.players.alive().select(Role.WITCH):
|
123
|
+
await anyio.sleep(5 + secrets.randbelow(15))
|
@@ -1,13 +1,17 @@
|
|
1
|
-
from nonebot_plugin_alconna.uniseg import UniMessage
|
2
1
|
from typing_extensions import override
|
3
2
|
|
4
|
-
|
3
|
+
import nonebot
|
4
|
+
from nonebot_plugin_alconna.uniseg import UniMessage
|
5
|
+
|
6
|
+
from ..constant import stop_command_prompt
|
5
7
|
from ..models import Role, RoleGroup
|
6
8
|
from ..utils import as_player_set
|
7
9
|
from .player import Player
|
8
10
|
|
11
|
+
logger = nonebot.logger.opt(colors=True)
|
12
|
+
|
9
13
|
|
10
|
-
@Player.register_role(Role.
|
14
|
+
@Player.register_role(Role.WITCH, RoleGroup.GOODGUY)
|
11
15
|
class Witch(Player):
|
12
16
|
antidote: bool = True
|
13
17
|
poison: bool = True
|
@@ -23,13 +27,18 @@ class Witch(Player):
|
|
23
27
|
await self.send(msg.text("⚙️你已经用过解药了"))
|
24
28
|
return False
|
25
29
|
|
26
|
-
msg.text(f"✏️使用解药请发送 “1”\n❌不使用解药请发送 “{
|
27
|
-
await self.send(
|
30
|
+
msg.text(f"✏️使用解药请发送 “1”\n❌不使用解药请发送 “{stop_command_prompt()}”")
|
31
|
+
await self.send(
|
32
|
+
msg,
|
33
|
+
stop_btn_label="不使用解药",
|
34
|
+
select_players=as_player_set(killed),
|
35
|
+
)
|
28
36
|
|
29
37
|
if not await self._select_player(
|
30
38
|
as_player_set(killed),
|
31
39
|
on_stop=f"ℹ️你选择不对 {killed.name} 使用解药",
|
32
|
-
on_index_error=f"⚠️输入错误: 请输入 “1” 或 “{
|
40
|
+
on_index_error=f"⚠️输入错误: 请输入 “1” 或 “{stop_command_prompt()}”",
|
41
|
+
stop_btn_label="不使用解药",
|
33
42
|
):
|
34
43
|
return False
|
35
44
|
|
@@ -40,7 +49,12 @@ class Witch(Player):
|
|
40
49
|
return True
|
41
50
|
|
42
51
|
@override
|
43
|
-
async def
|
52
|
+
async def _before_interact(self) -> None:
|
53
|
+
await self.send("ℹ️请等待狼人决定目标...")
|
54
|
+
await self.game.state.werewolf_finished.wait()
|
55
|
+
|
56
|
+
@override
|
57
|
+
async def _interact(self) -> None:
|
44
58
|
if await self.handle_killed():
|
45
59
|
return
|
46
60
|
|
@@ -50,16 +64,19 @@ class Witch(Player):
|
|
50
64
|
|
51
65
|
players = self.game.players.alive()
|
52
66
|
await self.send(
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
67
|
+
"💫你有一瓶毒药\n"
|
68
|
+
"玩家列表:\n"
|
69
|
+
f"{players.show()}\n\n"
|
70
|
+
"🧪发送玩家编号使用毒药\n"
|
71
|
+
f"❌发送 “{stop_command_prompt()}” 结束回合(不使用药水)",
|
72
|
+
stop_btn_label="结束回合",
|
73
|
+
select_players=players,
|
58
74
|
)
|
59
75
|
|
60
76
|
if selected := await self._select_player(
|
61
77
|
players,
|
62
78
|
on_stop="ℹ️你选择不使用毒药,回合结束",
|
79
|
+
stop_btn_label="结束回合",
|
63
80
|
):
|
64
81
|
self.poison = False
|
65
82
|
self.selected = selected
|