nonebot-plugin-werewolf 1.1.8__py3-none-any.whl → 1.1.9__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 +1 -0
- nonebot_plugin_werewolf/constant.py +6 -1
- nonebot_plugin_werewolf/game.py +61 -50
- nonebot_plugin_werewolf/matchers/edit_behavior.py +14 -2
- nonebot_plugin_werewolf/matchers/poke/chronocat_poke.py +2 -2
- nonebot_plugin_werewolf/matchers/poke/ob11_poke.py +2 -2
- nonebot_plugin_werewolf/matchers/start_game.py +31 -30
- nonebot_plugin_werewolf/models.py +15 -3
- nonebot_plugin_werewolf/{players/player.py → player.py} +148 -70
- nonebot_plugin_werewolf/player_set.py +39 -23
- nonebot_plugin_werewolf/players/__init__.py +0 -1
- nonebot_plugin_werewolf/players/civilian.py +3 -3
- nonebot_plugin_werewolf/players/guard.py +20 -15
- nonebot_plugin_werewolf/players/hunter.py +6 -5
- nonebot_plugin_werewolf/players/idiot.py +25 -17
- nonebot_plugin_werewolf/players/jester.py +22 -17
- nonebot_plugin_werewolf/players/prophet.py +13 -8
- nonebot_plugin_werewolf/players/{can_shoot.py → shooter.py} +10 -10
- nonebot_plugin_werewolf/players/werewolf.py +69 -45
- nonebot_plugin_werewolf/players/witch.py +30 -23
- nonebot_plugin_werewolf/players/wolfking.py +14 -8
- nonebot_plugin_werewolf/utils.py +6 -6
- {nonebot_plugin_werewolf-1.1.8.dist-info → nonebot_plugin_werewolf-1.1.9.dist-info}/METADATA +9 -2
- nonebot_plugin_werewolf-1.1.9.dist-info/RECORD +35 -0
- {nonebot_plugin_werewolf-1.1.8.dist-info → nonebot_plugin_werewolf-1.1.9.dist-info}/WHEEL +1 -1
- nonebot_plugin_werewolf-1.1.8.dist-info/RECORD +0 -35
- {nonebot_plugin_werewolf-1.1.8.dist-info → nonebot_plugin_werewolf-1.1.9.dist-info/licenses}/LICENSE +0 -0
- {nonebot_plugin_werewolf-1.1.8.dist-info → nonebot_plugin_werewolf-1.1.9.dist-info}/top_level.txt +0 -0
@@ -1,19 +1,19 @@
|
|
1
1
|
import functools
|
2
2
|
import weakref
|
3
|
-
from
|
4
|
-
from typing import TYPE_CHECKING, ClassVar, Final, TypeVar, final
|
3
|
+
from types import EllipsisType
|
4
|
+
from typing import TYPE_CHECKING, ClassVar, Final, Generic, TypeVar, final
|
5
|
+
from typing_extensions import Self, override
|
5
6
|
|
6
7
|
import anyio
|
7
8
|
import nonebot
|
8
9
|
from nonebot.adapters import Bot
|
9
10
|
from nonebot.utils import escape_tag
|
10
11
|
from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage
|
11
|
-
from nonebot_plugin_uninfo import SceneType
|
12
|
+
from nonebot_plugin_uninfo import Interface, SceneType
|
12
13
|
|
13
|
-
from
|
14
|
-
from
|
15
|
-
from
|
16
|
-
from ..utils import (
|
14
|
+
from .constant import ROLE_EMOJI, ROLE_NAME_CONV, STOP_COMMAND, stop_command_prompt
|
15
|
+
from .models import KillInfo, KillReason, Role, RoleGroup
|
16
|
+
from .utils import (
|
17
17
|
InputStore,
|
18
18
|
SendHandler,
|
19
19
|
add_players_button,
|
@@ -23,15 +23,79 @@ from ..utils import (
|
|
23
23
|
)
|
24
24
|
|
25
25
|
if TYPE_CHECKING:
|
26
|
-
from
|
27
|
-
from
|
26
|
+
from .game import Game
|
27
|
+
from .player_set import PlayerSet
|
28
28
|
|
29
29
|
|
30
|
-
_P = TypeVar("_P", bound=type["Player"])
|
31
|
-
|
32
30
|
logger = nonebot.logger.opt(colors=True)
|
33
31
|
|
34
32
|
|
33
|
+
_P = TypeVar("_P", bound="Player")
|
34
|
+
_T = TypeVar("_T")
|
35
|
+
|
36
|
+
|
37
|
+
class ActionProvider(Generic[_P]):
|
38
|
+
p: _P
|
39
|
+
|
40
|
+
@final
|
41
|
+
def __init__(self, player: _P, /) -> None:
|
42
|
+
self.p = player
|
43
|
+
|
44
|
+
class proxy(Generic[_T]): # noqa: N801
|
45
|
+
def __init__(
|
46
|
+
self, _: type[_T] | None = None, /, *, readonly: bool = False
|
47
|
+
) -> None:
|
48
|
+
self.readonly = readonly
|
49
|
+
|
50
|
+
def __set_name__(self, owner: type["ActionProvider"], name: str) -> None:
|
51
|
+
self.name = name
|
52
|
+
|
53
|
+
def __get__(self, obj: "ActionProvider", objtype: type) -> _T:
|
54
|
+
return getattr(obj.p, self.name)
|
55
|
+
|
56
|
+
def __set__(self, obj: "ActionProvider", value: _T) -> None:
|
57
|
+
if self.readonly:
|
58
|
+
raise AttributeError(f"readonly attribute {self.name}")
|
59
|
+
setattr(obj.p, self.name, value)
|
60
|
+
|
61
|
+
name = proxy[str](readonly=True)
|
62
|
+
user_id = proxy[str](readonly=True)
|
63
|
+
game = proxy["Game"](readonly=True)
|
64
|
+
selected = proxy["Player | None"]()
|
65
|
+
|
66
|
+
|
67
|
+
class InteractProvider(ActionProvider[_P], Generic[_P]):
|
68
|
+
async def before(self) -> None: ...
|
69
|
+
async def interact(self) -> None: ...
|
70
|
+
async def after(self) -> None: ...
|
71
|
+
|
72
|
+
|
73
|
+
class KillProvider(ActionProvider[_P], Generic[_P]):
|
74
|
+
alive = ActionProvider.proxy[bool]()
|
75
|
+
kill_info = ActionProvider.proxy[KillInfo | None]()
|
76
|
+
|
77
|
+
async def kill(self, reason: KillReason, *killers: "Player") -> KillInfo | None:
|
78
|
+
if self.alive:
|
79
|
+
self.alive = False
|
80
|
+
self.kill_info = KillInfo(reason=reason, killers=[p.name for p in killers])
|
81
|
+
return self.kill_info
|
82
|
+
|
83
|
+
async def post_kill(self) -> None: ...
|
84
|
+
|
85
|
+
|
86
|
+
class NotifyProvider(ActionProvider[_P], Generic[_P]):
|
87
|
+
role = ActionProvider.proxy[Role]()
|
88
|
+
role_group = ActionProvider.proxy[RoleGroup]()
|
89
|
+
role_name = ActionProvider.proxy[str]()
|
90
|
+
|
91
|
+
def message(self, message: UniMessage) -> UniMessage:
|
92
|
+
return message
|
93
|
+
|
94
|
+
async def notify(self) -> None:
|
95
|
+
msg = UniMessage.text(f"⚙️你的身份: {ROLE_EMOJI[self.role]}{self.role_name}\n")
|
96
|
+
await self.p.send(self.message(msg))
|
97
|
+
|
98
|
+
|
35
99
|
class _SendHandler(SendHandler[str | None]):
|
36
100
|
def solve_msg(
|
37
101
|
self,
|
@@ -44,9 +108,13 @@ class _SendHandler(SendHandler[str | None]):
|
|
44
108
|
|
45
109
|
|
46
110
|
class Player:
|
47
|
-
|
111
|
+
_player_class: ClassVar[dict[Role, type["Player"]]] = {}
|
112
|
+
|
48
113
|
role: ClassVar[Role]
|
49
114
|
role_group: ClassVar[RoleGroup]
|
115
|
+
interact_provider: ClassVar[type[InteractProvider[Self]] | None]
|
116
|
+
kill_provider: ClassVar[type[KillProvider[Self]]]
|
117
|
+
notify_provider: ClassVar[type[NotifyProvider[Self]]]
|
50
118
|
|
51
119
|
bot: Final[Bot]
|
52
120
|
alive: bool = True
|
@@ -55,13 +123,8 @@ class Player:
|
|
55
123
|
selected: "Player | None" = None
|
56
124
|
|
57
125
|
@final
|
58
|
-
def __init__(self, bot: Bot, game: "Game",
|
59
|
-
self.__user =
|
60
|
-
user_id,
|
61
|
-
private=True,
|
62
|
-
self_id=bot.self_id,
|
63
|
-
adapter=bot.adapter.get_name(),
|
64
|
-
)
|
126
|
+
def __init__(self, bot: Bot, game: "Game", user: Target) -> None:
|
127
|
+
self.__user = user
|
65
128
|
self.__game_ref = weakref.ref(game)
|
66
129
|
self.bot = bot
|
67
130
|
self.killed = anyio.Event()
|
@@ -69,51 +132,75 @@ class Player:
|
|
69
132
|
self._send_handler = _SendHandler()
|
70
133
|
self._send_handler.update(self.__user, bot)
|
71
134
|
|
72
|
-
@
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
cls.
|
78
|
-
|
79
|
-
|
80
|
-
|
135
|
+
@final
|
136
|
+
@override
|
137
|
+
def __init_subclass__(cls) -> None:
|
138
|
+
super().__init_subclass__()
|
139
|
+
if hasattr(cls, "role") and hasattr(cls, "role_group"):
|
140
|
+
cls._player_class[cls.role] = cls
|
141
|
+
if not hasattr(cls, "interact_provider"):
|
142
|
+
cls.interact_provider = None
|
143
|
+
if not hasattr(cls, "kill_provider"):
|
144
|
+
cls.kill_provider = KillProvider
|
145
|
+
if not hasattr(cls, "notify_provider"):
|
146
|
+
cls.notify_provider = NotifyProvider
|
81
147
|
|
82
148
|
@final
|
83
149
|
@classmethod
|
84
|
-
def new(
|
85
|
-
|
150
|
+
async def new(
|
151
|
+
cls,
|
152
|
+
role: Role,
|
153
|
+
bot: Bot,
|
154
|
+
game: "Game",
|
155
|
+
user_id: str,
|
156
|
+
interface: Interface,
|
157
|
+
) -> "Player":
|
158
|
+
if role not in cls._player_class:
|
86
159
|
raise ValueError(f"Unexpected role: {role!r}")
|
87
160
|
|
88
|
-
|
161
|
+
user = Target(
|
162
|
+
user_id,
|
163
|
+
private=True,
|
164
|
+
self_id=game.group.self_id,
|
165
|
+
scope=game.group.scope,
|
166
|
+
adapter=game.group.adapter,
|
167
|
+
extra=game.group.extra,
|
168
|
+
)
|
169
|
+
self = cls._player_class[role](bot, game, user)
|
170
|
+
await self._fetch_member(interface)
|
171
|
+
return self
|
89
172
|
|
90
173
|
def __repr__(self) -> str:
|
91
174
|
return f"<Player {self.role_name}: user={self.user_id!r} alive={self.alive}>"
|
92
175
|
|
176
|
+
@final
|
93
177
|
@property
|
94
178
|
def game(self) -> "Game":
|
95
179
|
if game := self.__game_ref():
|
96
180
|
return game
|
97
181
|
raise ValueError("Game not exist")
|
98
182
|
|
183
|
+
@final
|
99
184
|
@functools.cached_property
|
100
185
|
def user_id(self) -> str:
|
101
186
|
return self.__user.id
|
102
187
|
|
188
|
+
@final
|
103
189
|
@functools.cached_property
|
104
190
|
def role_name(self) -> str:
|
105
191
|
return ROLE_NAME_CONV[self.role]
|
106
192
|
|
107
|
-
|
108
|
-
|
193
|
+
@final
|
194
|
+
async def _fetch_member(self, interface: Interface) -> None:
|
195
|
+
member = await interface.get_member(
|
109
196
|
SceneType.GROUP,
|
110
|
-
self.game.
|
197
|
+
self.game.group_id,
|
111
198
|
self.user_id,
|
112
199
|
)
|
113
200
|
if member is None:
|
114
|
-
member = await
|
201
|
+
member = await interface.get_member(
|
115
202
|
SceneType.GUILD,
|
116
|
-
self.game.
|
203
|
+
self.game.group_id,
|
117
204
|
self.user_id,
|
118
205
|
)
|
119
206
|
|
@@ -134,12 +221,10 @@ class Player:
|
|
134
221
|
@final
|
135
222
|
@property
|
136
223
|
def colored_name(self) -> str:
|
137
|
-
name = escape_tag(self.user_id)
|
224
|
+
name = f"<b><e>{escape_tag(self.user_id)}</e></b>"
|
138
225
|
|
139
|
-
if
|
140
|
-
name = f"<
|
141
|
-
else:
|
142
|
-
name = f"<y>{nick}</y>(<b><e>{name}</e></b>)"
|
226
|
+
if (nick := self._member_nick) is not None:
|
227
|
+
name = f"<y>{nick}</y>({name})"
|
143
228
|
|
144
229
|
if self._member is not None and self._member.user.avatar is not None:
|
145
230
|
name = link(name, self._member.user.avatar)
|
@@ -183,47 +268,40 @@ class Player:
|
|
183
268
|
|
184
269
|
@property
|
185
270
|
def interact_timeout(self) -> float:
|
186
|
-
return
|
187
|
-
|
188
|
-
async def _before_interact(self) -> None:
|
189
|
-
return
|
190
|
-
|
191
|
-
async def _interact(self) -> None:
|
192
|
-
return
|
193
|
-
|
194
|
-
async def _after_interact(self) -> None:
|
195
|
-
return
|
271
|
+
return self.game.behavior.timeout.interact
|
196
272
|
|
273
|
+
@final
|
197
274
|
async def interact(self) -> None:
|
198
|
-
if
|
275
|
+
if self.interact_provider is None:
|
199
276
|
await self.send("ℹ️请等待其他玩家结束交互...")
|
200
277
|
return
|
201
278
|
|
202
|
-
|
279
|
+
provider = self.interact_provider(self)
|
280
|
+
|
281
|
+
await provider.before()
|
203
282
|
|
204
283
|
timeout = self.interact_timeout
|
205
284
|
await self.send(f"✏️{self.role_name}交互开始,限时 {timeout / 60:.2f} 分钟")
|
206
285
|
|
207
286
|
try:
|
208
287
|
with anyio.fail_after(timeout):
|
209
|
-
await
|
288
|
+
await provider.interact()
|
210
289
|
except TimeoutError:
|
211
290
|
logger.debug(f"{self.role_name}交互超时 (<y>{timeout}</y>s)")
|
212
291
|
await self.send(f"⚠️{self.role_name}交互超时")
|
213
292
|
|
214
|
-
await
|
293
|
+
await provider.after()
|
215
294
|
|
216
295
|
async def notify_role(self) -> None:
|
217
|
-
await self.
|
218
|
-
await self.send(f"⚙️你的身份: {ROLE_EMOJI[self.role]}{self.role_name}")
|
296
|
+
await self.notify_provider(self).notify()
|
219
297
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
self.kill_info = KillInfo(reason=reason, killers=[p.name for p in killers])
|
224
|
-
return True
|
298
|
+
@final
|
299
|
+
async def kill(self, reason: KillReason, *killers: "Player") -> KillInfo | None:
|
300
|
+
return await self.kill_provider(self).kill(reason, *killers)
|
225
301
|
|
302
|
+
@final
|
226
303
|
async def post_kill(self) -> None:
|
304
|
+
await self.kill_provider(self).post_kill()
|
227
305
|
self.killed.set()
|
228
306
|
|
229
307
|
async def vote(self, players: "PlayerSet") -> "Player | None":
|
@@ -238,8 +316,8 @@ class Player:
|
|
238
316
|
)
|
239
317
|
|
240
318
|
try:
|
241
|
-
with anyio.fail_after(
|
242
|
-
selected = await self.
|
319
|
+
with anyio.fail_after(self.game.behavior.timeout.vote):
|
320
|
+
selected = await self.select_player(
|
243
321
|
players,
|
244
322
|
on_stop="⚠️你选择了弃票",
|
245
323
|
on_index_error="⚠️输入错误: 请发送编号选择玩家",
|
@@ -255,15 +333,16 @@ class Player:
|
|
255
333
|
async def _check_selected(self, player: "Player") -> "Player | None":
|
256
334
|
return player
|
257
335
|
|
258
|
-
|
336
|
+
@final
|
337
|
+
async def select_player(
|
259
338
|
self,
|
260
339
|
players: "PlayerSet",
|
261
340
|
*,
|
262
|
-
on_stop: str | None =
|
341
|
+
on_stop: str | EllipsisType | None = ...,
|
263
342
|
on_index_error: str | None = None,
|
264
343
|
stop_btn_label: str | None = None,
|
265
344
|
) -> "Player | None":
|
266
|
-
on_stop = on_stop
|
345
|
+
on_stop = on_stop if on_stop is not None else "ℹ️你选择了取消,回合结束"
|
267
346
|
on_index_error = (
|
268
347
|
on_index_error or f"⚠️输入错误: 请发送玩家编号或 “{stop_command_prompt()}”"
|
269
348
|
)
|
@@ -272,11 +351,10 @@ class Player:
|
|
272
351
|
while selected is None:
|
273
352
|
text = await self.receive_text()
|
274
353
|
if text == STOP_COMMAND:
|
275
|
-
if on_stop is not
|
354
|
+
if on_stop is not ...:
|
276
355
|
await self.send(on_stop)
|
277
356
|
return None
|
278
|
-
index
|
279
|
-
if index is None:
|
357
|
+
if (index := check_index(text, players.size)) is None:
|
280
358
|
await self.send(
|
281
359
|
on_index_error,
|
282
360
|
stop_btn_label=stop_btn_label,
|
@@ -1,38 +1,47 @@
|
|
1
1
|
import functools
|
2
|
+
import random
|
3
|
+
from collections.abc import Iterable
|
4
|
+
from typing_extensions import Self
|
2
5
|
|
3
6
|
import anyio
|
4
7
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
5
8
|
|
6
9
|
from .models import Role, RoleGroup
|
7
|
-
from .
|
10
|
+
from .player import Player
|
8
11
|
|
9
12
|
|
10
13
|
class PlayerSet(set[Player]):
|
14
|
+
__slots__ = ("__dict__",) # for cached_property `sorted`
|
15
|
+
|
11
16
|
@property
|
12
17
|
def size(self) -> int:
|
13
18
|
return len(self)
|
14
19
|
|
15
|
-
|
16
|
-
|
20
|
+
@classmethod
|
21
|
+
def from_(cls, iterable: Iterable[Player], /) -> Self:
|
22
|
+
return cls(iterable)
|
23
|
+
|
24
|
+
def alive(self) -> Self:
|
25
|
+
return self.from_(p for p in self if p.alive)
|
17
26
|
|
18
|
-
def dead(self) ->
|
19
|
-
return
|
27
|
+
def dead(self) -> Self:
|
28
|
+
return self.from_(p for p in self if not p.alive)
|
20
29
|
|
21
|
-
def killed(self) ->
|
22
|
-
return
|
30
|
+
def killed(self) -> Self:
|
31
|
+
return self.from_(p for p in self if p.killed.is_set())
|
23
32
|
|
24
|
-
def include(self, *types: Player | Role | RoleGroup) ->
|
25
|
-
return
|
33
|
+
def include(self, *types: Player | Role | RoleGroup) -> Self:
|
34
|
+
return self.from_(
|
26
35
|
player
|
27
36
|
for player in self
|
28
37
|
if (player in types or player.role in types or player.role_group in types)
|
29
38
|
)
|
30
39
|
|
31
|
-
def select(self, *types: Player | Role | RoleGroup) ->
|
40
|
+
def select(self, *types: Player | Role | RoleGroup) -> Self:
|
32
41
|
return self.include(*types)
|
33
42
|
|
34
|
-
def exclude(self, *types: Player | Role | RoleGroup) ->
|
35
|
-
return
|
43
|
+
def exclude(self, *types: Player | Role | RoleGroup) -> Self:
|
44
|
+
return self.from_(
|
36
45
|
player
|
37
46
|
for player in self
|
38
47
|
if (
|
@@ -42,13 +51,19 @@ class PlayerSet(set[Player]):
|
|
42
51
|
)
|
43
52
|
)
|
44
53
|
|
45
|
-
def player_selected(self) ->
|
46
|
-
return
|
54
|
+
def player_selected(self) -> Self:
|
55
|
+
return self.from_(p.selected for p in self.alive() if (p.selected is not None))
|
47
56
|
|
48
57
|
@functools.cached_property
|
49
58
|
def sorted(self) -> list[Player]:
|
50
59
|
return sorted(self, key=lambda p: p.user_id)
|
51
60
|
|
61
|
+
@property
|
62
|
+
def shuffled(self) -> list[Player]:
|
63
|
+
players = self.sorted.copy()
|
64
|
+
random.shuffle(players)
|
65
|
+
return players
|
66
|
+
|
52
67
|
async def vote(self) -> dict[Player, list[Player]]:
|
53
68
|
players = self.alive()
|
54
69
|
result: dict[Player, list[Player]] = {}
|
@@ -56,7 +71,7 @@ class PlayerSet(set[Player]):
|
|
56
71
|
async def _vote(player: Player) -> None:
|
57
72
|
vote = await player.vote(players)
|
58
73
|
if vote is not None:
|
59
|
-
result
|
74
|
+
result.setdefault(vote, []).append(player)
|
60
75
|
|
61
76
|
async with anyio.create_task_group() as tg:
|
62
77
|
for p in players:
|
@@ -68,16 +83,17 @@ class PlayerSet(set[Player]):
|
|
68
83
|
if not self:
|
69
84
|
return
|
70
85
|
|
86
|
+
send = functools.partial(
|
87
|
+
Player.send,
|
88
|
+
message=message,
|
89
|
+
stop_btn_label=None,
|
90
|
+
select_players=None,
|
91
|
+
skip_handler=True,
|
92
|
+
)
|
93
|
+
|
71
94
|
async with anyio.create_task_group() as tg:
|
72
95
|
for p in self:
|
73
|
-
|
74
|
-
p.send,
|
75
|
-
message=message,
|
76
|
-
stop_btn_label=None,
|
77
|
-
select_players=None,
|
78
|
-
skip_handler=True,
|
79
|
-
)
|
80
|
-
tg.start_soon(func)
|
96
|
+
tg.start_soon(send, p)
|
81
97
|
|
82
98
|
def show(self) -> str:
|
83
99
|
return "\n".join(f"{i}. {p.name}" for i, p in enumerate(self.sorted, 1))
|
@@ -3,7 +3,6 @@ from .guard import Guard as Guard
|
|
3
3
|
from .hunter import Hunter as Hunter
|
4
4
|
from .idiot import Idiot as Idiot
|
5
5
|
from .jester import Jester as Jester
|
6
|
-
from .player import Player as Player
|
7
6
|
from .prophet import Prophet as Prophet
|
8
7
|
from .werewolf import Werewolf as Werewolf
|
9
8
|
from .witch import Witch as Witch
|
@@ -2,23 +2,14 @@ from typing_extensions import override
|
|
2
2
|
|
3
3
|
from ..constant import stop_command_prompt
|
4
4
|
from ..models import GameState, Role, RoleGroup
|
5
|
-
from
|
5
|
+
from ..player import InteractProvider, Player
|
6
6
|
|
7
7
|
|
8
|
-
|
9
|
-
class Guard(Player):
|
10
|
-
@override
|
11
|
-
async def _check_selected(self, player: Player) -> Player | None:
|
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
|
-
|
8
|
+
class GuardInteractProvider(InteractProvider["Guard"]):
|
18
9
|
@override
|
19
|
-
async def
|
10
|
+
async def interact(self) -> None:
|
20
11
|
players = self.game.players.alive()
|
21
|
-
await self.send(
|
12
|
+
await self.p.send(
|
22
13
|
"💫请选择需要保护的玩家:\n"
|
23
14
|
f"{players.show()}\n\n"
|
24
15
|
"🛡️发送编号选择玩家\n"
|
@@ -27,7 +18,21 @@ class Guard(Player):
|
|
27
18
|
select_players=players,
|
28
19
|
)
|
29
20
|
|
30
|
-
self.selected = await self.
|
21
|
+
self.selected = await self.p.select_player(players, stop_btn_label="结束回合")
|
31
22
|
if self.selected:
|
32
23
|
self.game.state.protected.add(self.selected)
|
33
|
-
await self.send(f"✅本回合保护的玩家: {self.selected.name}")
|
24
|
+
await self.p.send(f"✅本回合保护的玩家: {self.selected.name}")
|
25
|
+
|
26
|
+
|
27
|
+
class Guard(Player):
|
28
|
+
role = Role.GUARD
|
29
|
+
role_group = RoleGroup.GOODGUY
|
30
|
+
interact_provider = GuardInteractProvider
|
31
|
+
|
32
|
+
@override
|
33
|
+
async def _check_selected(self, player: Player) -> Player | None:
|
34
|
+
if self.game.state.state == GameState.State.NIGHT and player is self.selected:
|
35
|
+
await self.send("⚠️守卫不能连续两晚保护同一目标,请重新选择")
|
36
|
+
return None
|
37
|
+
|
38
|
+
return player
|
@@ -1,8 +1,9 @@
|
|
1
1
|
from ..models import Role, RoleGroup
|
2
|
-
from
|
3
|
-
from .
|
2
|
+
from ..player import Player
|
3
|
+
from .shooter import ShooterKillProvider
|
4
4
|
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
class Hunter(Player):
|
7
|
+
role = Role.HUNTER
|
8
|
+
role_group = RoleGroup.GOODGUY
|
9
|
+
kill_provider = ShooterKillProvider
|
@@ -1,30 +1,20 @@
|
|
1
|
-
from __future__ import annotations
|
2
|
-
|
3
1
|
from typing import TYPE_CHECKING
|
4
2
|
from typing_extensions import override
|
5
3
|
|
6
4
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
7
5
|
|
8
|
-
from ..models import KillReason, Role, RoleGroup
|
9
|
-
from
|
6
|
+
from ..models import KillInfo, KillReason, Role, RoleGroup
|
7
|
+
from ..player import KillProvider, NotifyProvider, Player
|
10
8
|
|
11
9
|
if TYPE_CHECKING:
|
12
10
|
from ..player_set import PlayerSet
|
13
11
|
|
14
12
|
|
15
|
-
|
16
|
-
|
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
|
-
)
|
13
|
+
class IdiotKillProvider(KillProvider["Idiot"]):
|
14
|
+
voted = KillProvider.proxy(bool)
|
25
15
|
|
26
16
|
@override
|
27
|
-
async def kill(self, reason: KillReason, *killers: Player) ->
|
17
|
+
async def kill(self, reason: KillReason, *killers: Player) -> KillInfo | None:
|
28
18
|
if reason == KillReason.VOTE and not self.voted:
|
29
19
|
self.voted = True
|
30
20
|
await self.game.send(
|
@@ -33,11 +23,29 @@ class Idiot(Player):
|
|
33
23
|
.text(" 的身份是白痴\n")
|
34
24
|
.text("免疫本次投票放逐,且接下来无法参与投票"),
|
35
25
|
)
|
36
|
-
return
|
26
|
+
return None
|
27
|
+
|
37
28
|
return await super().kill(reason, *killers)
|
38
29
|
|
30
|
+
|
31
|
+
class IdiotNotifyProvider(NotifyProvider["Idiot"]):
|
32
|
+
@override
|
33
|
+
def message(self, message: UniMessage) -> UniMessage:
|
34
|
+
return message.text(
|
35
|
+
"作为白痴,你可以在首次被投票放逐时免疫放逐,但在之后的投票中无法继续投票"
|
36
|
+
)
|
37
|
+
|
38
|
+
|
39
|
+
class Idiot(Player):
|
40
|
+
role = Role.IDIOT
|
41
|
+
role_group = RoleGroup.GOODGUY
|
42
|
+
kill_provider = IdiotKillProvider
|
43
|
+
notify_provider = IdiotNotifyProvider
|
44
|
+
|
45
|
+
voted: bool = False
|
46
|
+
|
39
47
|
@override
|
40
|
-
async def vote(self, players: PlayerSet) -> Player | None:
|
48
|
+
async def vote(self, players: "PlayerSet") -> Player | None:
|
41
49
|
if self.voted:
|
42
50
|
await self.send("ℹ️你已经发动过白痴身份的技能,无法参与本次投票")
|
43
51
|
return None
|
@@ -1,24 +1,29 @@
|
|
1
|
-
from typing import TYPE_CHECKING
|
2
1
|
from typing_extensions import override
|
3
2
|
|
3
|
+
from nonebot_plugin_alconna import UniMessage
|
4
|
+
|
4
5
|
from ..exception import GameFinished
|
5
|
-
from ..models import GameStatus, KillReason, Role, RoleGroup
|
6
|
-
from
|
6
|
+
from ..models import GameStatus, KillInfo, KillReason, Role, RoleGroup
|
7
|
+
from ..player import KillProvider, NotifyProvider, Player
|
7
8
|
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
class JesterKillProvider(KillProvider["Jester"]):
|
11
|
+
async def kill(self, reason: KillReason, *killers: Player) -> KillInfo | None:
|
12
|
+
kill_info = await super().kill(reason, *killers)
|
13
|
+
if kill_info is not None and reason == KillReason.VOTE:
|
14
|
+
self.game.killed_players.append((self.name, kill_info))
|
15
|
+
raise GameFinished(GameStatus.JESTER)
|
16
|
+
return kill_info
|
17
|
+
|
15
18
|
|
19
|
+
class JesterNotifyProvider(NotifyProvider["Jester"]):
|
16
20
|
@override
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
21
|
+
def message(self, message: UniMessage) -> UniMessage:
|
22
|
+
return message.text("⚙️你的胜利条件: 被投票放逐")
|
23
|
+
|
24
|
+
|
25
|
+
class Jester(Player):
|
26
|
+
role = Role.JESTER
|
27
|
+
role_group = RoleGroup.OTHERS
|
28
|
+
kill_provider = JesterKillProvider
|
29
|
+
notify_provider = JesterNotifyProvider
|