nonebot-plugin-werewolf 1.0.3__tar.gz → 1.0.5__tar.gz
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-1.0.3 → nonebot_plugin_werewolf-1.0.5}/PKG-INFO +17 -7
- {nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/README.md +16 -6
- {nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/nonebot_plugin_werewolf/__init__.py +1 -1
- {nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/nonebot_plugin_werewolf/constant.py +8 -6
- {nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/nonebot_plugin_werewolf/game.py +79 -26
- {nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/nonebot_plugin_werewolf/matchers.py +1 -2
- {nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/nonebot_plugin_werewolf/player.py +23 -15
- {nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/nonebot_plugin_werewolf/player_set.py +2 -14
- {nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/nonebot_plugin_werewolf/utils.py +8 -4
- {nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/pyproject.toml +1 -1
- {nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/LICENSE +0 -0
- {nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/nonebot_plugin_werewolf/config.py +0 -0
- {nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/nonebot_plugin_werewolf/ob11_ext.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: nonebot-plugin-werewolf
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.5
|
4
4
|
Summary: Default template for PDM package
|
5
5
|
Author-Email: wyf7685 <wyf7685@163.com>
|
6
6
|
License: MIT
|
@@ -116,9 +116,18 @@ _✨ 简单的狼人杀插件 ✨_
|
|
116
116
|
|
117
117
|
### 指令表
|
118
118
|
|
119
|
-
| 指令 |
|
120
|
-
| :-----------------: |
|
121
|
-
| `werewolf`/`狼人杀` |
|
119
|
+
| 指令 | 权限 | 需要@ | 范围 | 说明 |
|
120
|
+
| :-----------------: | :--------: | :---: | :--: | :---------------------------------: |
|
121
|
+
| `werewolf`/`狼人杀` | 群员 | 是 | 群聊 | 发起游戏 (进入准备阶段) |
|
122
|
+
| `开始游戏` | 游戏发起者 | 是 | 群聊 | _[准备阶段]_ 游戏发起者开始游戏 |
|
123
|
+
| `结束游戏` | 游戏发起者 | 是 | 群聊 | _[准备阶段]_ 游戏发起者结束游戏 |
|
124
|
+
| `当前玩家` | 群员 | 是 | 群聊 | _[准备阶段]_ 列出参与游戏的玩家列表 |
|
125
|
+
| `加入游戏` | 群员 | 是 | 群聊 | _[准备阶段]_ 玩家加入游戏 |
|
126
|
+
| `退出游戏` | 群员 | 是 | 群聊 | _[准备阶段]_ 玩家退出游戏 |
|
127
|
+
|
128
|
+
_其他交互参考游戏内提示_
|
129
|
+
|
130
|
+
对于 `OneBot V11` 适配器, 启用配置项 `werewolf__enable_poke` 后, 可以使用戳一戳代替 _准备阶段_ 的 `加入游戏` 操作 和 游戏内的 `/stop` 命令
|
122
131
|
|
123
132
|
### 游戏内容
|
124
133
|
|
@@ -132,8 +141,8 @@ _✨ 简单的狼人杀插件 ✨_
|
|
132
141
|
|
133
142
|
| 总人数 | 狼人 | 神职 | 平民 |
|
134
143
|
| ------ | ---- | ---- | ---- |
|
135
|
-
| 6 | 1 |
|
136
|
-
| 7 | 2 |
|
144
|
+
| 6 | 1 | 2 | 3 |
|
145
|
+
| 7 | 2 | 2 | 3 |
|
137
146
|
| 8 | 2 | 3 | 3 |
|
138
147
|
| 9 | 2 | 4 | 3 |
|
139
148
|
| 10 | 3 | 4 | 3 |
|
@@ -171,9 +180,10 @@ werewolf__override_preset='
|
|
171
180
|
<details>
|
172
181
|
<summary>更新日志</summary>
|
173
182
|
|
174
|
-
- 2024.09.
|
183
|
+
- 2024.09.03 v1.0.5
|
175
184
|
|
176
185
|
- 优化玩家交互体验
|
186
|
+
- 添加游戏结束后死亡报告
|
177
187
|
|
178
188
|
- 2024.08.31 v1.0.1
|
179
189
|
|
@@ -103,9 +103,18 @@ _✨ 简单的狼人杀插件 ✨_
|
|
103
103
|
|
104
104
|
### 指令表
|
105
105
|
|
106
|
-
| 指令 |
|
107
|
-
| :-----------------: |
|
108
|
-
| `werewolf`/`狼人杀` |
|
106
|
+
| 指令 | 权限 | 需要@ | 范围 | 说明 |
|
107
|
+
| :-----------------: | :--------: | :---: | :--: | :---------------------------------: |
|
108
|
+
| `werewolf`/`狼人杀` | 群员 | 是 | 群聊 | 发起游戏 (进入准备阶段) |
|
109
|
+
| `开始游戏` | 游戏发起者 | 是 | 群聊 | _[准备阶段]_ 游戏发起者开始游戏 |
|
110
|
+
| `结束游戏` | 游戏发起者 | 是 | 群聊 | _[准备阶段]_ 游戏发起者结束游戏 |
|
111
|
+
| `当前玩家` | 群员 | 是 | 群聊 | _[准备阶段]_ 列出参与游戏的玩家列表 |
|
112
|
+
| `加入游戏` | 群员 | 是 | 群聊 | _[准备阶段]_ 玩家加入游戏 |
|
113
|
+
| `退出游戏` | 群员 | 是 | 群聊 | _[准备阶段]_ 玩家退出游戏 |
|
114
|
+
|
115
|
+
_其他交互参考游戏内提示_
|
116
|
+
|
117
|
+
对于 `OneBot V11` 适配器, 启用配置项 `werewolf__enable_poke` 后, 可以使用戳一戳代替 _准备阶段_ 的 `加入游戏` 操作 和 游戏内的 `/stop` 命令
|
109
118
|
|
110
119
|
### 游戏内容
|
111
120
|
|
@@ -119,8 +128,8 @@ _✨ 简单的狼人杀插件 ✨_
|
|
119
128
|
|
120
129
|
| 总人数 | 狼人 | 神职 | 平民 |
|
121
130
|
| ------ | ---- | ---- | ---- |
|
122
|
-
| 6 | 1 |
|
123
|
-
| 7 | 2 |
|
131
|
+
| 6 | 1 | 2 | 3 |
|
132
|
+
| 7 | 2 | 2 | 3 |
|
124
133
|
| 8 | 2 | 3 | 3 |
|
125
134
|
| 9 | 2 | 4 | 3 |
|
126
135
|
| 10 | 3 | 4 | 3 |
|
@@ -158,9 +167,10 @@ werewolf__override_preset='
|
|
158
167
|
<details>
|
159
168
|
<summary>更新日志</summary>
|
160
169
|
|
161
|
-
- 2024.09.
|
170
|
+
- 2024.09.03 v1.0.5
|
162
171
|
|
163
172
|
- 优化玩家交互体验
|
173
|
+
- 添加游戏结束后死亡报告
|
164
174
|
|
165
175
|
- 2024.08.31 v1.0.1
|
166
176
|
|
{nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/nonebot_plugin_werewolf/constant.py
RENAMED
@@ -1,3 +1,5 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
from dataclasses import dataclass
|
2
4
|
from enum import Enum, auto
|
3
5
|
from typing import TYPE_CHECKING
|
@@ -45,16 +47,16 @@ class GameStatus(Enum):
|
|
45
47
|
@dataclass
|
46
48
|
class GameState:
|
47
49
|
day: int
|
48
|
-
killed:
|
49
|
-
shoot: tuple[
|
50
|
-
protected:
|
51
|
-
potion: tuple[
|
50
|
+
killed: Player | None = None
|
51
|
+
shoot: tuple[Player, Player] | tuple[None, None] = (None, None)
|
52
|
+
protected: Player | None = None
|
53
|
+
potion: tuple[Player | None, tuple[bool, bool]] = (None, (False, False))
|
52
54
|
|
53
55
|
|
54
56
|
player_preset: dict[int, tuple[int, int, int]] = {
|
55
57
|
# 总人数: (狼, 神, 民)
|
56
|
-
6: (1,
|
57
|
-
7: (2,
|
58
|
+
6: (1, 2, 3),
|
59
|
+
7: (2, 2, 3),
|
58
60
|
8: (2, 3, 3),
|
59
61
|
9: (2, 4, 3),
|
60
62
|
10: (3, 4, 3),
|
{nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/nonebot_plugin_werewolf/game.py
RENAMED
@@ -1,16 +1,20 @@
|
|
1
1
|
import asyncio
|
2
2
|
import asyncio.timeouts
|
3
|
+
import contextlib
|
3
4
|
import random
|
5
|
+
import time
|
4
6
|
|
5
7
|
from nonebot.adapters import Bot
|
8
|
+
from nonebot.log import logger
|
6
9
|
from nonebot_plugin_alconna import Target, UniMessage
|
7
10
|
|
8
11
|
from .constant import GameState, GameStatus, KillReason, Role, RoleGroup, player_preset
|
9
12
|
from .player import Player
|
10
13
|
from .player_set import PlayerSet
|
14
|
+
from .utils import InputStore
|
11
15
|
|
12
16
|
starting_games: dict[str, dict[str, str]] = {}
|
13
|
-
running_games: dict[str, tuple["Game", asyncio.Task[None]]] = {}
|
17
|
+
running_games: dict[str, tuple["Game", asyncio.Task[None], asyncio.Task[None]]] = {}
|
14
18
|
|
15
19
|
|
16
20
|
def init_players(bot: Bot, game: "Game", players: dict[str, str]) -> PlayerSet:
|
@@ -26,7 +30,11 @@ def init_players(bot: Bot, game: "Game", players: dict[str, str]) -> PlayerSet:
|
|
26
30
|
roles.extend([Role.预言家, Role.女巫, Role.猎人, Role.守卫, Role.白痴][: preset[1]])
|
27
31
|
roles.extend([Role.平民] * preset[2])
|
28
32
|
|
29
|
-
random.
|
33
|
+
r = random.Random(time.time())
|
34
|
+
shuffled: list[Role] = []
|
35
|
+
for _ in range(len(players)):
|
36
|
+
idx = r.randint(0, len(roles) - 1)
|
37
|
+
shuffled.append(roles.pop(idx))
|
30
38
|
|
31
39
|
async def selector(target_: Target, b: Bot):
|
32
40
|
return target_.self_id == bot.self_id and b is bot
|
@@ -44,7 +52,7 @@ def init_players(bot: Bot, game: "Game", players: dict[str, str]) -> PlayerSet:
|
|
44
52
|
),
|
45
53
|
players[user_id],
|
46
54
|
)
|
47
|
-
for user_id, role in zip(players,
|
55
|
+
for user_id, role in zip(players, shuffled)
|
48
56
|
)
|
49
57
|
|
50
58
|
|
@@ -65,6 +73,7 @@ class Game:
|
|
65
73
|
self.group = group
|
66
74
|
self.players = init_players(bot, self, players)
|
67
75
|
self.state = GameState(0)
|
76
|
+
self.killed_players = []
|
68
77
|
|
69
78
|
async def send(self, message: str | UniMessage):
|
70
79
|
if isinstance(message, str):
|
@@ -111,7 +120,7 @@ class Game:
|
|
111
120
|
case KillReason.Shoot:
|
112
121
|
msg += " 射杀"
|
113
122
|
case KillReason.Vote:
|
114
|
-
msg += "
|
123
|
+
msg += " 票出"
|
115
124
|
msg += "\n\n"
|
116
125
|
|
117
126
|
return msg.strip()
|
@@ -136,7 +145,15 @@ class Game:
|
|
136
145
|
if isinstance(players, Player):
|
137
146
|
players = PlayerSet([players])
|
138
147
|
|
139
|
-
|
148
|
+
async def wait(p: Player):
|
149
|
+
while True:
|
150
|
+
msg = await InputStore.fetch(p.user_id, self.group.id)
|
151
|
+
if msg.extract_plain_text() == "/stop":
|
152
|
+
break
|
153
|
+
|
154
|
+
with contextlib.suppress(TimeoutError):
|
155
|
+
async with asyncio.timeouts.timeout(timeout_secs):
|
156
|
+
await asyncio.gather(*[wait(p) for p in players])
|
140
157
|
|
141
158
|
async def interact(
|
142
159
|
self,
|
@@ -215,11 +232,11 @@ class Game:
|
|
215
232
|
.text("限时1分钟, 发送 “/stop” 结束发言")
|
216
233
|
)
|
217
234
|
await self.wait_stop(shoot, 60)
|
235
|
+
self.state.shoot = (None, None)
|
218
236
|
await self.post_kill(shoot)
|
219
|
-
self.state.shoot = (None, None)
|
220
237
|
|
221
238
|
async def run_vote(self) -> None:
|
222
|
-
#
|
239
|
+
# 筛选当前存活玩家
|
223
240
|
players = self.players.alive()
|
224
241
|
|
225
242
|
# 被票玩家: [投票玩家]
|
@@ -231,9 +248,9 @@ class Game:
|
|
231
248
|
|
232
249
|
# 投票结果公示
|
233
250
|
msg = UniMessage.text("投票结果:\n")
|
234
|
-
for p, v in sorted(vote_result.items(), key=lambda x: x[1], reverse=True):
|
251
|
+
for p, v in sorted(vote_result.items(), key=lambda x: len(x[1]), reverse=True):
|
235
252
|
if p is not None:
|
236
|
-
msg.at(p.user_id).text(f": {v} 票\n")
|
253
|
+
msg.at(p.user_id).text(f": {len(v)} 票\n")
|
237
254
|
vote_reversed[len(v)] = [*vote_reversed.get(len(v), []), p]
|
238
255
|
if v := (len(players) - total_votes):
|
239
256
|
msg.text(f"弃票: {v} 票\n")
|
@@ -244,6 +261,11 @@ class Game:
|
|
244
261
|
await self.send("没有人被票出")
|
245
262
|
return
|
246
263
|
|
264
|
+
# 弃票大于最高票
|
265
|
+
if (len(players) - total_votes) >= max(vote_reversed.keys()):
|
266
|
+
await self.send("弃票数大于最高票数, 没有人被票出")
|
267
|
+
return
|
268
|
+
|
247
269
|
# 平票
|
248
270
|
if len(vs := vote_reversed[max(vote_reversed.keys())]) != 1:
|
249
271
|
await self.send(
|
@@ -270,6 +292,7 @@ class Game:
|
|
270
292
|
await self.post_kill(voted)
|
271
293
|
|
272
294
|
async def run_dead_channel(self) -> None:
|
295
|
+
loop = asyncio.get_event_loop()
|
273
296
|
queue: asyncio.Queue[tuple[Player, UniMessage]] = asyncio.Queue()
|
274
297
|
|
275
298
|
async def send():
|
@@ -279,20 +302,29 @@ class Game:
|
|
279
302
|
await self.players.dead().exclude(player).broadcast(msg)
|
280
303
|
|
281
304
|
async def recv(player: Player):
|
305
|
+
counter = 0
|
306
|
+
|
307
|
+
def decrease():
|
308
|
+
nonlocal counter
|
309
|
+
counter -= 1
|
310
|
+
|
282
311
|
while True:
|
283
312
|
if not player.killed:
|
284
313
|
await asyncio.sleep(1)
|
285
314
|
continue
|
286
315
|
msg = await player.receive()
|
287
|
-
|
316
|
+
counter += 1
|
317
|
+
if counter <= 10:
|
318
|
+
await queue.put((player, msg))
|
319
|
+
loop.call_later(60, decrease)
|
320
|
+
else:
|
321
|
+
await player.send("发言频率超过限制, 该消息被屏蔽")
|
288
322
|
|
289
323
|
await asyncio.gather(send(), *[recv(p) for p in self.players])
|
290
324
|
|
291
325
|
async def run(self) -> None:
|
292
326
|
# 告知玩家角色信息
|
293
327
|
await self.notify_player_role()
|
294
|
-
# 死者频道
|
295
|
-
dead_channel = asyncio.create_task(self.run_dead_channel())
|
296
328
|
# 天数记录 主要用于第一晚狼人击杀的遗言
|
297
329
|
day_count = 0
|
298
330
|
|
@@ -306,6 +338,7 @@ class Game:
|
|
306
338
|
await asyncio.gather(
|
307
339
|
self.select_killed(),
|
308
340
|
players.select(Role.女巫).broadcast("请等待狼人决定目标..."),
|
341
|
+
players.select(Role.平民).broadcast("请等待其他玩家结束交互..."),
|
309
342
|
self.interact(Role.预言家, 60),
|
310
343
|
self.interact(Role.守卫, 60),
|
311
344
|
)
|
@@ -331,30 +364,30 @@ class Game:
|
|
331
364
|
# 没有玩家死亡,平安夜
|
332
365
|
if not (dead := players.dead()):
|
333
366
|
await self.send(msg.text("昨晚是平安夜"))
|
334
|
-
#
|
367
|
+
# 有玩家死亡,公布死者名单
|
335
368
|
else:
|
336
|
-
# 公布死者名单
|
337
369
|
msg.text("昨晚的死者是:")
|
338
370
|
for p in dead.sorted():
|
339
371
|
msg.text("\n").at(p.user_id)
|
340
372
|
await self.send(msg)
|
341
|
-
await self.post_kill(dead)
|
342
|
-
|
343
|
-
# 判断游戏状态
|
344
|
-
if self.check_game_status() != GameStatus.Unset:
|
345
|
-
break
|
346
|
-
|
347
|
-
# 公示存活玩家
|
348
|
-
await self.send(f"当前存活玩家: \n\n{self.players.alive().show()}")
|
349
373
|
|
350
374
|
# 第一晚被狼人杀死的玩家发表遗言
|
351
375
|
if day_count == 1 and killed is not None and not killed.alive:
|
352
376
|
await self.send(
|
353
377
|
UniMessage.text("当前为第一天\n请被狼人杀死的 ")
|
354
378
|
.at(killed.user_id)
|
355
|
-
.text(" 发表遗言\n
|
379
|
+
.text(" 发表遗言\n")
|
380
|
+
.text("限时1分钟, 发送 “/stop” 结束发言")
|
356
381
|
)
|
357
382
|
await self.wait_stop(killed, 60)
|
383
|
+
await self.post_kill(dead)
|
384
|
+
|
385
|
+
# 判断游戏状态
|
386
|
+
if self.check_game_status() != GameStatus.Unset:
|
387
|
+
break
|
388
|
+
|
389
|
+
# 公示存活玩家
|
390
|
+
await self.send(f"当前存活玩家: \n\n{self.players.alive().show()}")
|
358
391
|
|
359
392
|
# 开始自由讨论
|
360
393
|
await self.send("接下来开始自由讨论\n限时2分钟, 全员发送 “/stop” 结束发言")
|
@@ -365,11 +398,31 @@ class Game:
|
|
365
398
|
await self.run_vote()
|
366
399
|
|
367
400
|
# 游戏结束
|
368
|
-
dead_channel.cancel()
|
369
401
|
winner = "好人" if self.check_game_status() == GameStatus.Good else "狼人"
|
370
402
|
msg = UniMessage.text(f"🎉游戏结束,{winner}获胜\n\n")
|
371
403
|
for p in sorted(self.players, key=lambda p: (p.role.value, p.user_id)):
|
372
404
|
msg.at(p.user_id).text(f": {p.role.name}\n")
|
373
|
-
msg.text(f"\n{self.show_killed_players()}")
|
374
405
|
await self.send(msg)
|
375
|
-
|
406
|
+
await self.send(f"玩家死亡报告:\n\n{self.show_killed_players()}")
|
407
|
+
|
408
|
+
def start(self):
|
409
|
+
task = asyncio.create_task(self.run())
|
410
|
+
dead_channel = asyncio.create_task(self.run_dead_channel())
|
411
|
+
|
412
|
+
async def daemon():
|
413
|
+
while not task.done(): # noqa: ASYNC110
|
414
|
+
await asyncio.sleep(1)
|
415
|
+
|
416
|
+
try:
|
417
|
+
task.result()
|
418
|
+
except asyncio.CancelledError as err:
|
419
|
+
logger.warning(f"狼人杀游戏进程被取消: {err}")
|
420
|
+
except Exception as err:
|
421
|
+
msg = f"狼人杀游戏进程出现错误: {err!r}"
|
422
|
+
logger.opt(exception=err).error(msg)
|
423
|
+
await self.send(msg)
|
424
|
+
finally:
|
425
|
+
dead_channel.cancel()
|
426
|
+
running_games.pop(self.group.id, None)
|
427
|
+
|
428
|
+
running_games[self.group.id] = (self, task, asyncio.create_task(daemon()))
|
{nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/nonebot_plugin_werewolf/player.py
RENAMED
@@ -1,3 +1,5 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import asyncio
|
2
4
|
import asyncio.timeouts
|
3
5
|
from dataclasses import dataclass
|
@@ -16,7 +18,7 @@ if TYPE_CHECKING:
|
|
16
18
|
|
17
19
|
|
18
20
|
P = TypeVar("P", bound=type["Player"])
|
19
|
-
PLAYER_CLASS: dict[Role, type[
|
21
|
+
PLAYER_CLASS: dict[Role, type[Player]] = {}
|
20
22
|
|
21
23
|
|
22
24
|
def register_role(cls: P) -> P:
|
@@ -27,7 +29,7 @@ def register_role(cls: P) -> P:
|
|
27
29
|
@dataclass
|
28
30
|
class KillInfo:
|
29
31
|
reason: KillReason
|
30
|
-
killers:
|
32
|
+
killers: PlayerSet
|
31
33
|
|
32
34
|
|
33
35
|
class Player:
|
@@ -35,15 +37,15 @@ class Player:
|
|
35
37
|
role_group: ClassVar[RoleGroup]
|
36
38
|
|
37
39
|
bot: Bot
|
38
|
-
game:
|
40
|
+
game: Game
|
39
41
|
user: Target
|
40
42
|
name: str
|
41
43
|
alive: bool = True
|
42
44
|
killed: bool = False
|
43
45
|
kill_info: KillInfo | None = None
|
44
|
-
selected:
|
46
|
+
selected: Player | None = None
|
45
47
|
|
46
|
-
def __init__(self, bot: Bot, game:
|
48
|
+
def __init__(self, bot: Bot, game: Game, user: Target, name: str) -> None:
|
47
49
|
self.bot = bot
|
48
50
|
self.game = game
|
49
51
|
self.user = user
|
@@ -54,10 +56,10 @@ class Player:
|
|
54
56
|
cls,
|
55
57
|
role: Role,
|
56
58
|
bot: Bot,
|
57
|
-
game:
|
59
|
+
game: Game,
|
58
60
|
user: Target,
|
59
61
|
name: str,
|
60
|
-
) ->
|
62
|
+
) -> Player:
|
61
63
|
if role not in PLAYER_CLASS:
|
62
64
|
raise ValueError(f"Unexpected role: {role!r}")
|
63
65
|
|
@@ -93,8 +95,10 @@ class Player:
|
|
93
95
|
async def kill(
|
94
96
|
self,
|
95
97
|
reason: KillReason,
|
96
|
-
*killers:
|
98
|
+
*killers: Player,
|
97
99
|
) -> bool:
|
100
|
+
from .player_set import PlayerSet
|
101
|
+
|
98
102
|
self.alive = False
|
99
103
|
self.kill_info = KillInfo(reason, PlayerSet(killers))
|
100
104
|
return True
|
@@ -102,15 +106,16 @@ class Player:
|
|
102
106
|
async def post_kill(self) -> None:
|
103
107
|
self.killed = True
|
104
108
|
|
105
|
-
async def vote(self, players:
|
109
|
+
async def vote(self, players: PlayerSet) -> tuple[Player, Player] | None:
|
106
110
|
await self.send(
|
107
111
|
f"请选择需要投票的玩家:\n{players.show()}"
|
108
112
|
"\n\n发送编号选择玩家\n发送 “/stop” 弃票"
|
109
113
|
)
|
110
114
|
|
111
115
|
while True:
|
112
|
-
text =
|
116
|
+
text = await self.receive_text()
|
113
117
|
if text == "/stop":
|
118
|
+
await self.send("你选择了弃票")
|
114
119
|
return None
|
115
120
|
index = check_index(text, len(players))
|
116
121
|
if index is not None:
|
@@ -185,8 +190,11 @@ class 狼人(Player):
|
|
185
190
|
async def notify_role(self) -> None:
|
186
191
|
await super().notify_role()
|
187
192
|
partners = self.game.players.alive().select(RoleGroup.狼人).exclude(self)
|
188
|
-
|
189
|
-
|
193
|
+
if partners:
|
194
|
+
await self.send(
|
195
|
+
"你的队友:\n"
|
196
|
+
+ "\n".join(f" {p.role.name}: {p.name}" for p in partners)
|
197
|
+
)
|
190
198
|
|
191
199
|
@override
|
192
200
|
async def interact(self) -> None:
|
@@ -221,7 +229,7 @@ class 狼人(Player):
|
|
221
229
|
if index is not None:
|
222
230
|
selected = index - 1
|
223
231
|
msg = f"当前选择玩家: {players[selected].name}"
|
224
|
-
await self.send(msg)
|
232
|
+
await self.send(f"{msg}\n发送 “/stop” 结束回合")
|
225
233
|
broadcast(f"队友 {self.name} {msg}")
|
226
234
|
if text == "/stop":
|
227
235
|
if selected is not None:
|
@@ -296,7 +304,7 @@ class 女巫(Player):
|
|
296
304
|
async def handle_killed(self) -> bool:
|
297
305
|
msg = UniMessage()
|
298
306
|
if (killed := self.game.state.killed) is not None:
|
299
|
-
msg.text(f"今晚 {killed} 被刀了\n\n")
|
307
|
+
msg.text(f"今晚 {killed.name} 被刀了\n\n")
|
300
308
|
else:
|
301
309
|
await self.send("今晚没有人被刀")
|
302
310
|
return False
|
@@ -418,7 +426,7 @@ class 白痴(Player):
|
|
418
426
|
return await super().kill(reason, *killers)
|
419
427
|
|
420
428
|
@override
|
421
|
-
async def vote(self, players:
|
429
|
+
async def vote(self, players: PlayerSet) -> tuple[Player, Player] | None:
|
422
430
|
if self.voted:
|
423
431
|
await self.send("你已经发动过白痴身份的技能,无法参与本次投票")
|
424
432
|
return None
|
@@ -1,12 +1,10 @@
|
|
1
1
|
import asyncio
|
2
2
|
import asyncio.timeouts
|
3
|
-
import contextlib
|
4
3
|
|
5
4
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
6
5
|
|
7
6
|
from .constant import Role, RoleGroup
|
8
7
|
from .player import Player
|
9
|
-
from .utils import InputStore
|
10
8
|
|
11
9
|
|
12
10
|
class PlayerSet(set[Player]):
|
@@ -52,12 +50,13 @@ class PlayerSet(set[Player]):
|
|
52
50
|
await asyncio.gather(*[p.interact() for p in self.alive()])
|
53
51
|
|
54
52
|
async def vote(self, timeout_secs: float = 60) -> dict[Player, list[Player]]:
|
55
|
-
async def vote(player: Player) ->
|
53
|
+
async def vote(player: Player) -> tuple[Player, Player] | None:
|
56
54
|
try:
|
57
55
|
async with asyncio.timeouts.timeout(timeout_secs):
|
58
56
|
return await player.vote(self)
|
59
57
|
except TimeoutError:
|
60
58
|
await player.send("投票超时,将视为弃票")
|
59
|
+
return None
|
61
60
|
|
62
61
|
result: dict[Player, list[Player]] = {}
|
63
62
|
for item in await asyncio.gather(*[vote(p) for p in self.alive()]):
|
@@ -69,17 +68,6 @@ class PlayerSet(set[Player]):
|
|
69
68
|
async def broadcast(self, message: str | UniMessage) -> None:
|
70
69
|
await asyncio.gather(*[p.send(message) for p in self])
|
71
70
|
|
72
|
-
async def wait_group_stop(self, group_id: str, timeout_secs: float) -> None:
|
73
|
-
async def wait(p: Player):
|
74
|
-
while True:
|
75
|
-
msg = await InputStore.fetch(p.user_id, group_id)
|
76
|
-
if msg.extract_plain_text() == "/stop":
|
77
|
-
break
|
78
|
-
|
79
|
-
with contextlib.suppress(TimeoutError):
|
80
|
-
async with asyncio.timeouts.timeout(timeout_secs):
|
81
|
-
await asyncio.gather(*[wait(p) for p in self])
|
82
|
-
|
83
71
|
def show(self) -> str:
|
84
72
|
return "\n".join(f"{i}. {p.name}" for i, p in enumerate(self.sorted(), 1))
|
85
73
|
|
{nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/nonebot_plugin_werewolf/utils.py
RENAMED
@@ -9,7 +9,7 @@ from nonebot.rule import to_me
|
|
9
9
|
from nonebot_plugin_alconna import MsgTarget, UniMessage, UniMsg
|
10
10
|
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
|
11
11
|
|
12
|
-
from .
|
12
|
+
from .constant import player_preset
|
13
13
|
|
14
14
|
|
15
15
|
def check_index(text: str, arrlen: int) -> int | None:
|
@@ -42,15 +42,19 @@ class InputStore:
|
|
42
42
|
|
43
43
|
|
44
44
|
def user_in_game(user_id: str, group_id: str | None) -> bool:
|
45
|
+
from .game import running_games
|
46
|
+
|
45
47
|
if group_id is not None and group_id not in running_games:
|
46
48
|
return False
|
47
49
|
games = running_games.values() if group_id is None else [running_games[group_id]]
|
48
|
-
for game, _ in games:
|
50
|
+
for game, *_ in games:
|
49
51
|
return any(user_id == player.user_id for player in game.players)
|
50
52
|
return False
|
51
53
|
|
52
54
|
|
53
55
|
async def rule_in_game(event: Event, target: MsgTarget) -> bool:
|
56
|
+
from .game import running_games
|
57
|
+
|
54
58
|
if not running_games:
|
55
59
|
return False
|
56
60
|
if target.private:
|
@@ -169,12 +173,12 @@ async def _prepare_game_handle(
|
|
169
173
|
|
170
174
|
|
171
175
|
async def prepare_game(event: Event, players: dict[str, str]) -> None:
|
172
|
-
queue = asyncio.Queue()
|
176
|
+
queue: asyncio.Queue[tuple[str, str, str]] = asyncio.Queue()
|
173
177
|
task_receive = asyncio.create_task(
|
174
178
|
_prepare_game_receive(
|
175
179
|
queue,
|
176
180
|
event,
|
177
|
-
UniMessage.get_target().id,
|
181
|
+
UniMessage.get_target(event).id,
|
178
182
|
)
|
179
183
|
)
|
180
184
|
|
File without changes
|
{nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/nonebot_plugin_werewolf/config.py
RENAMED
File without changes
|
{nonebot_plugin_werewolf-1.0.3 → nonebot_plugin_werewolf-1.0.5}/nonebot_plugin_werewolf/ob11_ext.py
RENAMED
File without changes
|