nonebot-plugin-werewolf 1.1.8__py3-none-any.whl → 1.1.10__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 +73 -64
- nonebot_plugin_werewolf/matchers/edit_behavior.py +19 -3
- 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} +149 -72
- 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 +18 -7
- {nonebot_plugin_werewolf-1.1.8.dist-info → nonebot_plugin_werewolf-1.1.10.dist-info}/METADATA +14 -2
- nonebot_plugin_werewolf-1.1.10.dist-info/RECORD +35 -0
- {nonebot_plugin_werewolf-1.1.8.dist-info → nonebot_plugin_werewolf-1.1.10.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.10.dist-info/licenses}/LICENSE +0 -0
- {nonebot_plugin_werewolf-1.1.8.dist-info → nonebot_plugin_werewolf-1.1.10.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,65 +123,83 @@ 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()
|
68
131
|
self._member = None
|
69
|
-
self._send_handler = _SendHandler()
|
70
|
-
self._send_handler.update(self.__user, bot)
|
132
|
+
self._send_handler = _SendHandler(self.__user, bot)
|
71
133
|
|
72
|
-
@
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
cls.
|
78
|
-
|
79
|
-
|
80
|
-
|
134
|
+
@final
|
135
|
+
@override
|
136
|
+
def __init_subclass__(cls) -> None:
|
137
|
+
super().__init_subclass__()
|
138
|
+
if hasattr(cls, "role") and hasattr(cls, "role_group"):
|
139
|
+
cls._player_class[cls.role] = cls
|
140
|
+
if not hasattr(cls, "interact_provider"):
|
141
|
+
cls.interact_provider = None
|
142
|
+
if not hasattr(cls, "kill_provider"):
|
143
|
+
cls.kill_provider = KillProvider
|
144
|
+
if not hasattr(cls, "notify_provider"):
|
145
|
+
cls.notify_provider = NotifyProvider
|
81
146
|
|
82
147
|
@final
|
83
148
|
@classmethod
|
84
|
-
def new(
|
85
|
-
|
149
|
+
async def new(
|
150
|
+
cls,
|
151
|
+
role: Role,
|
152
|
+
bot: Bot,
|
153
|
+
game: "Game",
|
154
|
+
user_id: str,
|
155
|
+
interface: Interface,
|
156
|
+
) -> "Player":
|
157
|
+
if role not in cls._player_class:
|
86
158
|
raise ValueError(f"Unexpected role: {role!r}")
|
87
159
|
|
88
|
-
|
160
|
+
user = Target(
|
161
|
+
user_id,
|
162
|
+
private=True,
|
163
|
+
self_id=game.group.self_id,
|
164
|
+
scope=game.group.scope,
|
165
|
+
adapter=game.group.adapter,
|
166
|
+
extra=game.group.extra,
|
167
|
+
)
|
168
|
+
self = cls._player_class[role](bot, game, user)
|
169
|
+
await self._fetch_member(interface)
|
170
|
+
return self
|
89
171
|
|
90
172
|
def __repr__(self) -> str:
|
91
173
|
return f"<Player {self.role_name}: user={self.user_id!r} alive={self.alive}>"
|
92
174
|
|
175
|
+
@final
|
93
176
|
@property
|
94
177
|
def game(self) -> "Game":
|
95
178
|
if game := self.__game_ref():
|
96
179
|
return game
|
97
180
|
raise ValueError("Game not exist")
|
98
181
|
|
182
|
+
@final
|
99
183
|
@functools.cached_property
|
100
184
|
def user_id(self) -> str:
|
101
185
|
return self.__user.id
|
102
186
|
|
187
|
+
@final
|
103
188
|
@functools.cached_property
|
104
189
|
def role_name(self) -> str:
|
105
190
|
return ROLE_NAME_CONV[self.role]
|
106
191
|
|
107
|
-
|
108
|
-
|
192
|
+
@final
|
193
|
+
async def _fetch_member(self, interface: Interface) -> None:
|
194
|
+
member = await interface.get_member(
|
109
195
|
SceneType.GROUP,
|
110
|
-
self.game.
|
196
|
+
self.game.group_id,
|
111
197
|
self.user_id,
|
112
198
|
)
|
113
199
|
if member is None:
|
114
|
-
member = await
|
200
|
+
member = await interface.get_member(
|
115
201
|
SceneType.GUILD,
|
116
|
-
self.game.
|
202
|
+
self.game.group_id,
|
117
203
|
self.user_id,
|
118
204
|
)
|
119
205
|
|
@@ -134,12 +220,10 @@ class Player:
|
|
134
220
|
@final
|
135
221
|
@property
|
136
222
|
def colored_name(self) -> str:
|
137
|
-
name = escape_tag(self.user_id)
|
223
|
+
name = f"<b><e>{escape_tag(self.user_id)}</e></b>"
|
138
224
|
|
139
|
-
if
|
140
|
-
name = f"<
|
141
|
-
else:
|
142
|
-
name = f"<y>{nick}</y>(<b><e>{name}</e></b>)"
|
225
|
+
if (nick := self._member_nick) is not None:
|
226
|
+
name = f"<y>{nick}</y>({name})"
|
143
227
|
|
144
228
|
if self._member is not None and self._member.user.avatar is not None:
|
145
229
|
name = link(name, self._member.user.avatar)
|
@@ -183,47 +267,40 @@ class Player:
|
|
183
267
|
|
184
268
|
@property
|
185
269
|
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
|
270
|
+
return self.game.behavior.timeout.interact
|
196
271
|
|
272
|
+
@final
|
197
273
|
async def interact(self) -> None:
|
198
|
-
if
|
274
|
+
if self.interact_provider is None:
|
199
275
|
await self.send("ℹ️请等待其他玩家结束交互...")
|
200
276
|
return
|
201
277
|
|
202
|
-
|
278
|
+
provider = self.interact_provider(self)
|
279
|
+
|
280
|
+
await provider.before()
|
203
281
|
|
204
282
|
timeout = self.interact_timeout
|
205
283
|
await self.send(f"✏️{self.role_name}交互开始,限时 {timeout / 60:.2f} 分钟")
|
206
284
|
|
207
285
|
try:
|
208
286
|
with anyio.fail_after(timeout):
|
209
|
-
await
|
287
|
+
await provider.interact()
|
210
288
|
except TimeoutError:
|
211
289
|
logger.debug(f"{self.role_name}交互超时 (<y>{timeout}</y>s)")
|
212
290
|
await self.send(f"⚠️{self.role_name}交互超时")
|
213
291
|
|
214
|
-
await
|
292
|
+
await provider.after()
|
215
293
|
|
216
294
|
async def notify_role(self) -> None:
|
217
|
-
await self.
|
218
|
-
await self.send(f"⚙️你的身份: {ROLE_EMOJI[self.role]}{self.role_name}")
|
295
|
+
await self.notify_provider(self).notify()
|
219
296
|
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
self.kill_info = KillInfo(reason=reason, killers=[p.name for p in killers])
|
224
|
-
return True
|
297
|
+
@final
|
298
|
+
async def kill(self, reason: KillReason, *killers: "Player") -> KillInfo | None:
|
299
|
+
return await self.kill_provider(self).kill(reason, *killers)
|
225
300
|
|
301
|
+
@final
|
226
302
|
async def post_kill(self) -> None:
|
303
|
+
await self.kill_provider(self).post_kill()
|
227
304
|
self.killed.set()
|
228
305
|
|
229
306
|
async def vote(self, players: "PlayerSet") -> "Player | None":
|
@@ -238,8 +315,8 @@ class Player:
|
|
238
315
|
)
|
239
316
|
|
240
317
|
try:
|
241
|
-
with anyio.fail_after(
|
242
|
-
selected = await self.
|
318
|
+
with anyio.fail_after(self.game.behavior.timeout.vote):
|
319
|
+
selected = await self.select_player(
|
243
320
|
players,
|
244
321
|
on_stop="⚠️你选择了弃票",
|
245
322
|
on_index_error="⚠️输入错误: 请发送编号选择玩家",
|
@@ -255,15 +332,16 @@ class Player:
|
|
255
332
|
async def _check_selected(self, player: "Player") -> "Player | None":
|
256
333
|
return player
|
257
334
|
|
258
|
-
|
335
|
+
@final
|
336
|
+
async def select_player(
|
259
337
|
self,
|
260
338
|
players: "PlayerSet",
|
261
339
|
*,
|
262
|
-
on_stop: str | None =
|
340
|
+
on_stop: str | EllipsisType | None = ...,
|
263
341
|
on_index_error: str | None = None,
|
264
342
|
stop_btn_label: str | None = None,
|
265
343
|
) -> "Player | None":
|
266
|
-
on_stop = on_stop
|
344
|
+
on_stop = on_stop if on_stop is not None else "ℹ️你选择了取消,回合结束"
|
267
345
|
on_index_error = (
|
268
346
|
on_index_error or f"⚠️输入错误: 请发送玩家编号或 “{stop_command_prompt()}”"
|
269
347
|
)
|
@@ -272,11 +350,10 @@ class Player:
|
|
272
350
|
while selected is None:
|
273
351
|
text = await self.receive_text()
|
274
352
|
if text == STOP_COMMAND:
|
275
|
-
if on_stop is not
|
353
|
+
if on_stop is not ...:
|
276
354
|
await self.send(on_stop)
|
277
355
|
return None
|
278
|
-
index
|
279
|
-
if index is None:
|
356
|
+
if (index := check_index(text, players.size)) is None:
|
280
357
|
await self.send(
|
281
358
|
on_index_error,
|
282
359
|
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
|