nonebot-plugin-werewolf 1.0.6__tar.gz → 1.0.7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: nonebot-plugin-werewolf
3
- Version: 1.0.6
3
+ Version: 1.0.7
4
4
  Summary: Default template for PDM package
5
5
  Author-Email: wyf7685 <wyf7685@163.com>
6
6
  License: MIT
@@ -9,7 +9,6 @@ Requires-Dist: nonebot2>=2.3.3
9
9
  Requires-Dist: nonebot-plugin-alconna>=0.52.1
10
10
  Requires-Dist: nonebot-plugin-userinfo>=0.2.6
11
11
  Requires-Dist: nonebot-plugin-waiter>=0.7.1
12
- Requires-Dist: StrEnum>=0.4.15
13
12
  Description-Content-Type: text/markdown
14
13
 
15
14
  <div align="center">
@@ -89,10 +88,14 @@ _✨ 简单的狼人杀插件 ✨_
89
88
 
90
89
  在 nonebot2 项目的`.env`文件中添加下表中的必填配置
91
90
 
92
- | 配置项 | 必填 | 默认值 | 说明 |
93
- | :-------------------------: | :--: | :----: | :-----------------------------------------------------------: |
94
- | `werewolf__enable_poke` | 否 | `True` | 是否使用戳一戳简化操作流程<br/>仅在 `OneBot V11` 适配器下生效 |
95
- | `werewolf__override_preset` | 否 | - | 覆写插件内置的职业预设 |
91
+ | 配置项 | 必填 | 默认值 | 说明 |
92
+ | :-----------------------------: | :--: | :----: | :-----------------------------------------------------------: |
93
+ | `werewolf__enable_poke` | 否 | `True` | 是否使用戳一戳简化操作流程<br/>仅在 `OneBot V11` 适配器下生效 |
94
+ | `werewolf__role_preset` | 否 | - | 覆写插件内置的职业预设 |
95
+ | `werewolf__werewolf_priority` | 否 | - | 自定义狼人职业优先级 |
96
+ | `werewolf__priesthood_proirity` | 否 | - | 自定义神职职业优先级 |
97
+
98
+ `werewolf__role_preset`, `werewolf__werewolf_priority`, `werewolf__priesthood_proirity` 的配置格式请参考 [`游戏内容`](#游戏内容) 部分
96
99
 
97
100
  ## 🎉 使用
98
101
 
@@ -150,15 +153,15 @@ _其他交互参考游戏内提示_
150
153
  | 11   | 3  | 5   | 3   |
151
154
  | 12   | 4  | 5   | 3   |
152
155
 
153
- 职业预设可以通过配置项 `werewolf__override_preset` 修改
156
+ 职业预设可以通过配置项 `werewolf__role_preset` 修改
154
157
 
155
158
  <details>
156
159
  <summary>示例</summary>
157
160
 
158
- 配置项 `werewolf__override_preset`
161
+ 配置项 `werewolf__role_preset`
159
162
 
160
163
  ```env
161
- werewolf__override_preset='
164
+ werewolf__role_preset='
162
165
  [
163
166
  [6, 1, 3, 2],
164
167
  [7, 2, 3, 2]
@@ -171,16 +174,63 @@ werewolf__override_preset='
171
174
  </details>
172
175
  <br/>
173
176
 
174
- 对于`狼人`和`神职`的职业分配,有如下优先级:
177
+ 对于`狼人`和`神职`的职业分配,默认有如下优先级:
175
178
 
176
179
  - `狼人`: `狼人`, `狼人`, `狼王`, `狼人`
177
- - `神职`: `预言家`, `女巫`, `猎人`, `守卫`, `白痴`
180
+ - `神职`: `女巫`, `预言家`, `猎人`, `守卫`, `白痴`
181
+
182
+ 职业分配优先级可以通过配置项 `werewolf__werewolf_priority` 和 `werewolf__priesthood_proirity` 修改
183
+
184
+ <details>
185
+ <summary>示例</summary>
186
+
187
+ #### 配置项 `werewolf__werewolf_priority`
188
+
189
+ ```env
190
+ werewolf__werewolf_priority=[1, 2, 1, 1]
191
+ ```
192
+
193
+ 上述配置中,`[1, 2, 1, 1]` 表示狼人的职业优先级为 `狼人`, `狼王`, `狼人`, `狼人`
194
+
195
+ #### 配置项 `werewolf__werewolf_priority`
196
+
197
+ ```env
198
+ werewolf__priesthood_proirity=[11, 12, 13, 14, 15]
199
+ ```
200
+
201
+ 上述配置中,`[11, 12, 13, 14, 15]` 表示神职的职业优先级为 `预言家`, `女巫`, `猎人`, `守卫`, `白痴`
202
+
203
+ #### 职业与数字的对应关系
204
+
205
+ 上述配置示例中有大量~~意义不明的~~数字, 其对应的是 [`这里`](./nonebot_plugin_werewolf/constant.py) 的枚举类 `Role` 的值
206
+
207
+ 以下列出目前的枚举值供参考
208
+
209
+ | 职业 | 枚举值 |
210
+ | -------- | ------ |
211
+ | `狼人` | `1` |
212
+ | `狼王` | `2` |
213
+ | `预言家` | `11` |
214
+ | `女巫` | `12` |
215
+ | `猎人` | `13` |
216
+ | `守卫` | `14` |
217
+ | `白痴` | `15` |
218
+ | `平民` | `0` |
219
+
220
+ </details>
178
221
 
179
222
  ## 📝 更新日志
180
223
 
181
224
  <details>
182
225
  <summary>更新日志</summary>
183
226
 
227
+ <!-- CHANGELOG -->
228
+
229
+ - 2024.09.04 v1.0.7
230
+
231
+ - 优先使用群名片作为玩家名
232
+ - 支持通过配置项修改职业分配优先级
233
+
184
234
  - 2024.09.03 v1.0.6
185
235
 
186
236
  - 修复预言家查验狼王返回好人的 bug
@@ -192,7 +242,7 @@ werewolf__override_preset='
192
242
 
193
243
  - 2024.08.31 v1.0.1
194
244
 
195
- - 允许通过配置项修改职业预设
245
+ - 支持通过配置项修改职业预设
196
246
 
197
247
  - 2024.08.31 v1.0.0
198
248
 
@@ -206,3 +256,4 @@ werewolf__override_preset='
206
256
  - [`nonebot/plugin-alconna`](https://github.com/nonebot/plugin-alconna): 跨平台的消息处理接口
207
257
  - [`noneplugin/nonebot-plugin-userinfo`](https://github.com/noneplugin/nonebot-plugin-userinfo): 用户信息获取
208
258
  - [`RF-Tar-Railt/nonebot-plugin-waiter`](https://github.com/RF-Tar-Railt/nonebot-plugin-waiter): 灵活获取用户输入
259
+ - `热心群友`: 协助测试插件
@@ -75,10 +75,14 @@ _✨ 简单的狼人杀插件 ✨_
75
75
 
76
76
  在 nonebot2 项目的`.env`文件中添加下表中的必填配置
77
77
 
78
- | 配置项 | 必填 | 默认值 | 说明 |
79
- | :-------------------------: | :--: | :----: | :-----------------------------------------------------------: |
80
- | `werewolf__enable_poke` | 否 | `True` | 是否使用戳一戳简化操作流程<br/>仅在 `OneBot V11` 适配器下生效 |
81
- | `werewolf__override_preset` | 否 | - | 覆写插件内置的职业预设 |
78
+ | 配置项 | 必填 | 默认值 | 说明 |
79
+ | :-----------------------------: | :--: | :----: | :-----------------------------------------------------------: |
80
+ | `werewolf__enable_poke` | 否 | `True` | 是否使用戳一戳简化操作流程<br/>仅在 `OneBot V11` 适配器下生效 |
81
+ | `werewolf__role_preset` | 否 | - | 覆写插件内置的职业预设 |
82
+ | `werewolf__werewolf_priority` | 否 | - | 自定义狼人职业优先级 |
83
+ | `werewolf__priesthood_proirity` | 否 | - | 自定义神职职业优先级 |
84
+
85
+ `werewolf__role_preset`, `werewolf__werewolf_priority`, `werewolf__priesthood_proirity` 的配置格式请参考 [`游戏内容`](#游戏内容) 部分
82
86
 
83
87
  ## 🎉 使用
84
88
 
@@ -136,15 +140,15 @@ _其他交互参考游戏内提示_
136
140
  | 11   | 3  | 5   | 3   |
137
141
  | 12   | 4  | 5   | 3   |
138
142
 
139
- 职业预设可以通过配置项 `werewolf__override_preset` 修改
143
+ 职业预设可以通过配置项 `werewolf__role_preset` 修改
140
144
 
141
145
  <details>
142
146
  <summary>示例</summary>
143
147
 
144
- 配置项 `werewolf__override_preset`
148
+ 配置项 `werewolf__role_preset`
145
149
 
146
150
  ```env
147
- werewolf__override_preset='
151
+ werewolf__role_preset='
148
152
  [
149
153
  [6, 1, 3, 2],
150
154
  [7, 2, 3, 2]
@@ -157,16 +161,63 @@ werewolf__override_preset='
157
161
  </details>
158
162
  <br/>
159
163
 
160
- 对于`狼人`和`神职`的职业分配,有如下优先级:
164
+ 对于`狼人`和`神职`的职业分配,默认有如下优先级:
161
165
 
162
166
  - `狼人`: `狼人`, `狼人`, `狼王`, `狼人`
163
- - `神职`: `预言家`, `女巫`, `猎人`, `守卫`, `白痴`
167
+ - `神职`: `女巫`, `预言家`, `猎人`, `守卫`, `白痴`
168
+
169
+ 职业分配优先级可以通过配置项 `werewolf__werewolf_priority` 和 `werewolf__priesthood_proirity` 修改
170
+
171
+ <details>
172
+ <summary>示例</summary>
173
+
174
+ #### 配置项 `werewolf__werewolf_priority`
175
+
176
+ ```env
177
+ werewolf__werewolf_priority=[1, 2, 1, 1]
178
+ ```
179
+
180
+ 上述配置中,`[1, 2, 1, 1]` 表示狼人的职业优先级为 `狼人`, `狼王`, `狼人`, `狼人`
181
+
182
+ #### 配置项 `werewolf__werewolf_priority`
183
+
184
+ ```env
185
+ werewolf__priesthood_proirity=[11, 12, 13, 14, 15]
186
+ ```
187
+
188
+ 上述配置中,`[11, 12, 13, 14, 15]` 表示神职的职业优先级为 `预言家`, `女巫`, `猎人`, `守卫`, `白痴`
189
+
190
+ #### 职业与数字的对应关系
191
+
192
+ 上述配置示例中有大量~~意义不明的~~数字, 其对应的是 [`这里`](./nonebot_plugin_werewolf/constant.py) 的枚举类 `Role` 的值
193
+
194
+ 以下列出目前的枚举值供参考
195
+
196
+ | 职业 | 枚举值 |
197
+ | -------- | ------ |
198
+ | `狼人` | `1` |
199
+ | `狼王` | `2` |
200
+ | `预言家` | `11` |
201
+ | `女巫` | `12` |
202
+ | `猎人` | `13` |
203
+ | `守卫` | `14` |
204
+ | `白痴` | `15` |
205
+ | `平民` | `0` |
206
+
207
+ </details>
164
208
 
165
209
  ## 📝 更新日志
166
210
 
167
211
  <details>
168
212
  <summary>更新日志</summary>
169
213
 
214
+ <!-- CHANGELOG -->
215
+
216
+ - 2024.09.04 v1.0.7
217
+
218
+ - 优先使用群名片作为玩家名
219
+ - 支持通过配置项修改职业分配优先级
220
+
170
221
  - 2024.09.03 v1.0.6
171
222
 
172
223
  - 修复预言家查验狼王返回好人的 bug
@@ -178,7 +229,7 @@ werewolf__override_preset='
178
229
 
179
230
  - 2024.08.31 v1.0.1
180
231
 
181
- - 允许通过配置项修改职业预设
232
+ - 支持通过配置项修改职业预设
182
233
 
183
234
  - 2024.08.31 v1.0.0
184
235
 
@@ -192,3 +243,4 @@ werewolf__override_preset='
192
243
  - [`nonebot/plugin-alconna`](https://github.com/nonebot/plugin-alconna): 跨平台的消息处理接口
193
244
  - [`noneplugin/nonebot-plugin-userinfo`](https://github.com/noneplugin/nonebot-plugin-userinfo): 用户信息获取
194
245
  - [`RF-Tar-Railt/nonebot-plugin-waiter`](https://github.com/RF-Tar-Railt/nonebot-plugin-waiter): 灵活获取用户输入
246
+ - `热心群友`: 协助测试插件
@@ -8,7 +8,7 @@ require("nonebot_plugin_waiter")
8
8
  from . import matchers as matchers
9
9
  from .config import Config
10
10
 
11
- __version__ = "1.0.6"
11
+ __version__ = "1.0.7"
12
12
  __plugin_meta__ = PluginMetadata(
13
13
  name="狼人杀",
14
14
  description="适用于 Nonebot2 的狼人杀插件",
@@ -1,10 +1,14 @@
1
1
  from nonebot import get_plugin_config
2
2
  from pydantic import BaseModel
3
3
 
4
+ from .constant import Role
5
+
4
6
 
5
7
  class PluginConfig(BaseModel):
6
8
  enable_poke: bool = True
7
- override_preset: list[tuple[int, int, int, int]] | None = None
9
+ role_preset: list[tuple[int, int, int, int]] | None = None
10
+ werewolf_priority: list[Role] | None = None
11
+ priesthood_proirity: list[Role] | None = None
8
12
 
9
13
 
10
14
  class Config(BaseModel):
@@ -0,0 +1,124 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from enum import Enum, auto
5
+ from typing import TYPE_CHECKING
6
+
7
+ if TYPE_CHECKING:
8
+ from .player import Player
9
+
10
+
11
+ class Role(Enum):
12
+ # 狼人
13
+ Werewolf = 1
14
+ WolfKing = 2
15
+
16
+ # 神职
17
+ Prophet = 11
18
+ Witch = 12
19
+ Hunter = 13
20
+ Guard = 14
21
+ Idiot = 15
22
+
23
+ # 平民
24
+ Civilian = 0
25
+
26
+
27
+ class RoleGroup(Enum):
28
+ Werewolf = auto()
29
+ GoodGuy = auto()
30
+
31
+
32
+ class KillReason(Enum):
33
+ Kill = auto()
34
+ Poison = auto()
35
+ Shoot = auto()
36
+ Vote = auto()
37
+
38
+
39
+ class GameStatus(Enum):
40
+ Good = auto()
41
+ Bad = auto()
42
+ Unset = auto()
43
+
44
+
45
+ @dataclass
46
+ class GameState:
47
+ day: int
48
+ killed: Player | None = None
49
+ shoot: tuple[Player, Player] | tuple[None, None] = (None, None)
50
+ protected: Player | None = None
51
+ potion: tuple[Player | None, tuple[bool, bool]] = (None, (False, False))
52
+
53
+
54
+ role_name_conv: dict[Role | RoleGroup, str] = {
55
+ Role.Werewolf: "狼人",
56
+ Role.WolfKing: "狼王",
57
+ Role.Prophet: "预言家",
58
+ Role.Witch: "女巫",
59
+ Role.Hunter: "猎人",
60
+ Role.Guard: "守卫",
61
+ Role.Idiot: "白痴",
62
+ Role.Civilian: "平民",
63
+ RoleGroup.Werewolf: "狼人",
64
+ RoleGroup.GoodGuy: "好人",
65
+ }
66
+
67
+ role_preset: dict[int, tuple[int, int, int]] = {
68
+ # 总人数: (狼, 神, 民)
69
+ 6: (1, 2, 3),
70
+ 7: (2, 2, 3),
71
+ 8: (2, 3, 3),
72
+ 9: (2, 4, 3),
73
+ 10: (3, 4, 3),
74
+ 11: (3, 5, 3),
75
+ 12: (4, 5, 3),
76
+ }
77
+
78
+ werewolf_priority: list[Role] = [
79
+ Role.Werewolf,
80
+ Role.Werewolf,
81
+ Role.WolfKing,
82
+ Role.Werewolf,
83
+ ]
84
+ priesthood_proirity: list[Role] = [
85
+ Role.Witch,
86
+ Role.Prophet,
87
+ Role.Hunter,
88
+ Role.Guard,
89
+ Role.Idiot,
90
+ ]
91
+
92
+
93
+ def __apply_config():
94
+ from .config import config
95
+
96
+ global role_preset, werewolf_priority, priesthood_proirity
97
+
98
+ if config.role_preset is not None:
99
+ for preset in config.role_preset:
100
+ if preset[0] != preset[1:]:
101
+ raise RuntimeError(
102
+ "配置项 `role_preset` 错误: "
103
+ f"预设总人数为 {preset[0]}, 实际总人数为 {sum(preset[1:])}"
104
+ )
105
+ role_preset |= {i[0]: i[1:] for i in config.role_preset}
106
+
107
+ if (priority := config.werewolf_priority) is not None:
108
+ min_length = max(i[0] for i in role_preset.values())
109
+ if len(priority) < min_length:
110
+ raise RuntimeError(
111
+ f"配置项 `werewolf_priority` 错误: 应至少为 {min_length} 项"
112
+ )
113
+ werewolf_priority = priority
114
+
115
+ if (priority := config.priesthood_proirity) is not None:
116
+ min_length = max(i[1] for i in role_preset.values())
117
+ if len(priority) < min_length:
118
+ raise RuntimeError(
119
+ f"配置项 `priesthood_proirity` 错误: 应至少为 {min_length} 项"
120
+ )
121
+ priesthood_proirity = priority
122
+
123
+
124
+ __apply_config()
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  import asyncio.timeouts
3
5
  import contextlib
@@ -8,30 +10,35 @@ from nonebot.adapters import Bot
8
10
  from nonebot.log import logger
9
11
  from nonebot_plugin_alconna import Target, UniMessage
10
12
 
11
- from .constant import GameState, GameStatus, KillReason, Role, RoleGroup, player_preset
13
+ from .constant import (
14
+ GameState,
15
+ GameStatus,
16
+ KillReason,
17
+ Role,
18
+ RoleGroup,
19
+ priesthood_proirity,
20
+ role_preset,
21
+ werewolf_priority,
22
+ )
12
23
  from .player import Player
13
24
  from .player_set import PlayerSet
14
25
  from .utils import InputStore
15
26
 
16
27
  starting_games: dict[str, dict[str, str]] = {}
17
- running_games: dict[str, tuple["Game", asyncio.Task[None], asyncio.Task[None]]] = {}
28
+ running_games: dict[str, Game] = {}
18
29
 
19
30
 
20
- def init_players(bot: Bot, game: "Game", players: dict[str, str]) -> PlayerSet:
21
- preset = player_preset.get(len(players))
31
+ def init_players(bot: Bot, game: Game, players: dict[str, str]) -> PlayerSet:
32
+ preset = role_preset.get(len(players))
22
33
  if preset is None:
23
34
  raise ValueError(
24
35
  f"玩家人数不符: "
25
- f"应为 {', '.join(map(str, player_preset))} 人, 传入{len(players)}人"
36
+ f"应为 {', '.join(map(str, role_preset))} 人, 传入{len(players)}人"
26
37
  )
27
38
 
28
39
  roles: list[Role] = []
29
- roles.extend(
30
- [Role.Werewolf, Role.Werewolf, Role.WolfKing, Role.Werewolf][: preset[0]]
31
- )
32
- roles.extend(
33
- [Role.Prophet, Role.Witch, Role.Hunter, Role.Guard, Role.Idiot][: preset[1]]
34
- )
40
+ roles.extend(werewolf_priority[: preset[0]])
41
+ roles.extend(priesthood_proirity[: preset[1]])
35
42
  roles.extend([Role.Civilian] * preset[2])
36
43
 
37
44
  r = random.Random(time.time())
@@ -130,7 +137,7 @@ class Game:
130
137
  return msg.strip()
131
138
 
132
139
  async def notify_player_role(self) -> None:
133
- preset = player_preset[len(self.players)]
140
+ preset = role_preset[len(self.players)]
134
141
  await asyncio.gather(
135
142
  self.send(
136
143
  self.at_all()
@@ -412,23 +419,32 @@ class Game:
412
419
  await self.send(f"玩家死亡报告:\n\n{self.show_killed_players()}")
413
420
 
414
421
  def start(self):
415
- task = asyncio.create_task(self.run())
422
+ event = asyncio.Event()
423
+ game_task = asyncio.create_task(self.run())
424
+ game_task.add_done_callback(lambda _: event.set())
416
425
  dead_channel = asyncio.create_task(self.run_dead_channel())
417
426
 
418
427
  async def daemon():
419
- while not task.done(): # noqa: ASYNC110
420
- await asyncio.sleep(1)
428
+ await event.wait()
421
429
 
422
430
  try:
423
- task.result()
431
+ game_task.result()
432
+ logger.info(f"{self.group.id} 的狼人杀游戏进程正常退出")
424
433
  except asyncio.CancelledError as err:
425
- logger.warning(f"狼人杀游戏进程被取消: {err}")
434
+ logger.warning(f"{self.group.id} 的狼人杀游戏进程被取消: {err}")
426
435
  except Exception as err:
427
- msg = f"狼人杀游戏进程出现错误: {err!r}"
436
+ msg = f"{self.group.id} 的狼人杀游戏进程出现错误: {err!r}"
428
437
  logger.opt(exception=err).error(msg)
429
438
  await self.send(msg)
430
439
  finally:
431
440
  dead_channel.cancel()
432
441
  running_games.pop(self.group.id, None)
433
442
 
434
- running_games[self.group.id] = (self, task, asyncio.create_task(daemon()))
443
+ def daemon_callback(task: asyncio.Task[None]):
444
+ if err := task.exception():
445
+ logger.opt(exception=err).error(
446
+ f"{self.group.id} 的狼人杀守护进程出现错误: {err!r}"
447
+ )
448
+
449
+ running_games[self.group.id] = self
450
+ asyncio.create_task(daemon()).add_done_callback(daemon_callback)
@@ -4,6 +4,7 @@ from typing import Annotated
4
4
 
5
5
  from nonebot import on_command, on_message
6
6
  from nonebot.adapters import Bot, Event
7
+ from nonebot.exception import FinishedException
7
8
  from nonebot.rule import to_me
8
9
  from nonebot_plugin_alconna import MsgTarget, UniMessage, UniMsg
9
10
  from nonebot_plugin_userinfo import EventUserInfo, UserInfo
@@ -56,6 +57,8 @@ async def handle_start(
56
57
  try:
57
58
  async with asyncio.timeouts.timeout(5 * 60):
58
59
  await prepare_game(event, players)
60
+ except FinishedException:
61
+ raise
59
62
  except TimeoutError:
60
63
  await UniMessage.text("游戏准备超时,已自动结束").finish()
61
64
  finally:
@@ -9,7 +9,7 @@ from typing_extensions import override
9
9
  from nonebot.adapters import Bot
10
10
  from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage
11
11
 
12
- from .constant import KillReason, Role, RoleGroup, role_name_conv,role_group_name_conv
12
+ from .constant import KillReason, Role, RoleGroup, role_name_conv
13
13
  from .utils import InputStore, check_index
14
14
 
15
15
  if TYPE_CHECKING:
@@ -21,9 +21,14 @@ P = TypeVar("P", bound=type["Player"])
21
21
  PLAYER_CLASS: dict[Role, type[Player]] = {}
22
22
 
23
23
 
24
- def register_role(cls: P) -> P:
25
- PLAYER_CLASS[cls.role] = cls
26
- return cls
24
+ def register_role(role: Role, role_group: RoleGroup, /):
25
+ def decorator(cls: P, /) -> P:
26
+ cls.role = role
27
+ cls.role_group = role_group
28
+ PLAYER_CLASS[role] = cls
29
+ return cls
30
+
31
+ return decorator
27
32
 
28
33
 
29
34
  @dataclass
@@ -190,11 +195,8 @@ class CanShoot(Player):
190
195
  return players[selected]
191
196
 
192
197
 
193
- @register_role
194
- class 狼人(Player):
195
- role: ClassVar[Role] = Role.Werewolf
196
- role_group: ClassVar[RoleGroup] = RoleGroup.Werewolf
197
-
198
+ @register_role(Role.Werewolf, RoleGroup.Werewolf)
199
+ class Werewolf(Player):
198
200
  @override
199
201
  async def notify_role(self) -> None:
200
202
  await super().notify_role()
@@ -252,17 +254,13 @@ class 狼人(Player):
252
254
  self.selected = players[selected]
253
255
 
254
256
 
255
- @register_role
256
- class 狼王(CanShoot, 狼人):
257
- role: ClassVar[Role] = Role.WolfKing
258
- role_group: ClassVar[RoleGroup] = RoleGroup.Werewolf
257
+ @register_role(Role.WolfKing, RoleGroup.Werewolf)
258
+ class WolfKing(CanShoot, Werewolf):
259
+ pass
259
260
 
260
261
 
261
- @register_role
262
- class 预言家(Player):
263
- role: ClassVar[Role] = Role.Prophet
264
- role_group: ClassVar[RoleGroup] = RoleGroup.GoodGuy
265
-
262
+ @register_role(Role.Prophet, RoleGroup.GoodGuy)
263
+ class Prophet(Player):
266
264
  @override
267
265
  async def interact(self) -> None:
268
266
  players = self.game.players.alive().exclude(self)
@@ -281,14 +279,12 @@ class 预言家(Player):
281
279
  await self.send("输入错误,请发送编号选择玩家")
282
280
 
283
281
  player = players[selected]
284
- result = role_group_name_conv[player.role_group]
282
+ result = role_name_conv[player.role_group]
285
283
  await self.send(f"玩家 {player.name} 的阵营是『{result}』")
286
284
 
287
285
 
288
- @register_role
289
- class 女巫(Player):
290
- role: ClassVar[Role] = Role.Witch
291
- role_group: ClassVar[RoleGroup] = RoleGroup.GoodGuy
286
+ @register_role(Role.Witch, RoleGroup.GoodGuy)
287
+ class Witch(Player):
292
288
  antidote: int = 1
293
289
  poison: int = 1
294
290
 
@@ -374,17 +370,13 @@ class 女巫(Player):
374
370
  await self.send(f"当前回合选择对玩家 {player.name} 使用毒药\n回合结束")
375
371
 
376
372
 
377
- @register_role
378
- class 猎人(CanShoot, Player):
379
- role: ClassVar[Role] = Role.Hunter
380
- role_group: ClassVar[RoleGroup] = RoleGroup.GoodGuy
381
-
373
+ @register_role(Role.Hunter, RoleGroup.GoodGuy)
374
+ class Hunter(CanShoot, Player):
375
+ pass
382
376
 
383
- @register_role
384
- class 守卫(Player):
385
- role: ClassVar[Role] = Role.Guard
386
- role_group: ClassVar[RoleGroup] = RoleGroup.GoodGuy
387
377
 
378
+ @register_role(Role.Guard, RoleGroup.GoodGuy)
379
+ class Guard(Player):
388
380
  @override
389
381
  async def interact(self) -> None:
390
382
  players = self.game.players.alive().exclude(self)
@@ -412,10 +404,8 @@ class 守卫(Player):
412
404
  await self.send(f"本回合保护的玩家: {self.selected.name}")
413
405
 
414
406
 
415
- @register_role
416
- class 白痴(Player):
417
- role: ClassVar[Role] = Role.Idiot
418
- role_group: ClassVar[RoleGroup] = RoleGroup.GoodGuy
407
+ @register_role(Role.Idiot, RoleGroup.GoodGuy)
408
+ class Idiot(Player):
419
409
  voted: bool = False
420
410
 
421
411
  @override
@@ -442,7 +432,6 @@ class 白痴(Player):
442
432
  return await super().vote(players)
443
433
 
444
434
 
445
- @register_role
446
- class 平民(Player):
447
- role: ClassVar[Role] = Role.Civilian
448
- role_group: ClassVar[RoleGroup] = RoleGroup.GoodGuy
435
+ @register_role(Role.Civilian, RoleGroup.GoodGuy)
436
+ class Civilian(Player):
437
+ pass
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import asyncio
2
4
  import asyncio.timeouts
3
5
 
@@ -12,23 +14,23 @@ class PlayerSet(set[Player]):
12
14
  def size(self) -> int:
13
15
  return len(self)
14
16
 
15
- def alive(self) -> "PlayerSet":
17
+ def alive(self) -> PlayerSet:
16
18
  return PlayerSet(p for p in self if p.alive)
17
19
 
18
- def dead(self) -> "PlayerSet":
20
+ def dead(self) -> PlayerSet:
19
21
  return PlayerSet(p for p in self if not p.alive)
20
22
 
21
- def include(self, *types: Player | Role | RoleGroup) -> "PlayerSet":
23
+ def include(self, *types: Player | Role | RoleGroup) -> PlayerSet:
22
24
  return PlayerSet(
23
25
  player
24
26
  for player in self
25
27
  if (player in types or player.role in types or player.role_group in types)
26
28
  )
27
29
 
28
- def select(self, *types: Player | Role | RoleGroup) -> "PlayerSet":
30
+ def select(self, *types: Player | Role | RoleGroup) -> PlayerSet:
29
31
  return self.include(*types)
30
32
 
31
- def exclude(self, *types: Player | Role | RoleGroup) -> "PlayerSet":
33
+ def exclude(self, *types: Player | Role | RoleGroup) -> PlayerSet:
32
34
  return PlayerSet(
33
35
  player
34
36
  for player in self
@@ -39,7 +41,7 @@ class PlayerSet(set[Player]):
39
41
  )
40
42
  )
41
43
 
42
- def player_selected(self) -> "PlayerSet":
44
+ def player_selected(self) -> PlayerSet:
43
45
  return PlayerSet(p.selected for p in self.alive() if p.selected is not None)
44
46
 
45
47
  def sorted(self) -> list[Player]:
@@ -1,5 +1,6 @@
1
1
  import asyncio
2
2
  import asyncio.timeouts
3
+ import re
3
4
  from collections import defaultdict
4
5
  from typing import Annotated, Any, ClassVar
5
6
 
@@ -9,7 +10,7 @@ from nonebot.rule import to_me
9
10
  from nonebot_plugin_alconna import MsgTarget, UniMessage, UniMsg
10
11
  from nonebot_plugin_userinfo import EventUserInfo, UserInfo
11
12
 
12
- from .constant import player_preset
13
+ from .constant import role_preset
13
14
 
14
15
 
15
16
  def check_index(text: str, arrlen: int) -> int | None:
@@ -47,7 +48,7 @@ def user_in_game(user_id: str, group_id: str | None) -> bool:
47
48
  if group_id is not None and group_id not in running_games:
48
49
  return False
49
50
  games = running_games.values() if group_id is None else [running_games[group_id]]
50
- for game, *_ in games:
51
+ for game in games:
51
52
  return any(user_id == player.user_id for player in game.players)
52
53
  return False
53
54
 
@@ -92,13 +93,18 @@ async def _prepare_game_receive(
92
93
  ) -> tuple[str, str, str]:
93
94
  return (
94
95
  event.get_user_id(),
95
- info.user_name if info is not None else event.get_user_id(),
96
+ (
97
+ (info.user_displayname or info.user_name)
98
+ if info is not None
99
+ else event.get_user_id()
100
+ ),
96
101
  msg.extract_plain_text().strip(),
97
102
  )
98
103
 
99
104
  async for user, name, text in wait(default=(None, "", "")):
100
105
  if user is None:
101
106
  continue
107
+ name = re.sub(r"[\u2066-\u2069]", "", name)
102
108
  await queue.put((user, name, text))
103
109
 
104
110
 
@@ -114,19 +120,19 @@ async def _prepare_game_handle(
114
120
  match (text, user == admin_id):
115
121
  case ("开始游戏", True):
116
122
  player_num = len(players)
117
- if player_num < min(player_preset):
123
+ if player_num < min(role_preset):
118
124
  await (
119
- msg.text(f"游戏至少需要 {min(player_preset)} 人, ")
125
+ msg.text(f"游戏至少需要 {min(role_preset)} 人, ")
120
126
  .text(f"当前已有 {player_num} 人")
121
127
  .send()
122
128
  )
123
- elif player_num > max(player_preset):
129
+ elif player_num > max(role_preset):
124
130
  await (
125
- msg.text(f"游戏最多需要 {max(player_preset)} 人, ")
131
+ msg.text(f"游戏最多需要 {max(role_preset)} 人, ")
126
132
  .text(f"当前已有 {player_num} 人")
127
133
  .send()
128
134
  )
129
- elif player_num not in player_preset:
135
+ elif player_num not in role_preset:
130
136
  await (
131
137
  msg.text(f"不存在总人数为 {player_num} 的预设, ")
132
138
  .text("无法开始游戏")
@@ -167,8 +173,8 @@ async def _prepare_game_handle(
167
173
 
168
174
  case ("当前玩家", _):
169
175
  msg.text("\n当前玩家:\n")
170
- for name in players.values():
171
- msg.text(f"\n{name}")
176
+ for idx, name in enumerate(players.values(), 1):
177
+ msg.text(f"\n{idx}. {name}")
172
178
  await msg.send()
173
179
 
174
180
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nonebot-plugin-werewolf"
3
- version = "1.0.6"
3
+ version = "1.0.7"
4
4
  description = "Default template for PDM package"
5
5
  authors = [
6
6
  { name = "wyf7685", email = "wyf7685@163.com" },
@@ -10,7 +10,6 @@ dependencies = [
10
10
  "nonebot-plugin-alconna>=0.52.1",
11
11
  "nonebot-plugin-userinfo>=0.2.6",
12
12
  "nonebot-plugin-waiter>=0.7.1",
13
- "StrEnum>=0.4.15",
14
13
  ]
15
14
  requires-python = ">=3.10"
16
15
  readme = "README.md"
@@ -27,6 +26,9 @@ build-backend = "pdm.backend"
27
26
  [tool.pdm]
28
27
  distribution = true
29
28
 
29
+ [tool.pdm.scripts]
30
+ updver = "pdm run python ./scripts/update_version.py"
31
+
30
32
  [tool.pdm.scripts.lint]
31
33
  composite = [
32
34
  "isort .",
@@ -97,3 +99,13 @@ pythonPlatform = "All"
97
99
  typeCheckingMode = "standard"
98
100
  reportShadowedImports = false
99
101
  disableBytesTypePromotions = true
102
+
103
+ [tool.nonebot]
104
+ adapters = [
105
+ { name = "OneBot V11", module_name = "nonebot.adapters.onebot.v11" },
106
+ ]
107
+ plugins = [
108
+ "nonebot_plugin_werewolf",
109
+ ]
110
+ plugin_dirs = []
111
+ builtin_plugins = []
@@ -1,88 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from dataclasses import dataclass
4
- from enum import Enum, auto
5
- from typing import TYPE_CHECKING
6
-
7
- from strenum import StrEnum
8
-
9
- from .config import config
10
-
11
- if TYPE_CHECKING:
12
- from .player import Player
13
-
14
-
15
- class Role(StrEnum):
16
- # 狼人
17
- Werewolf = auto()
18
- WolfKing = auto()
19
-
20
- # 神职
21
- Prophet = auto()
22
- Witch = auto()
23
- Hunter = auto()
24
- Guard = auto()
25
- Idiot = auto()
26
-
27
- # 平民
28
- Civilian = auto()
29
-
30
-
31
- class RoleGroup(StrEnum):
32
- Werewolf = auto()
33
- GoodGuy = auto()
34
-
35
-
36
- class KillReason(Enum):
37
- Kill = auto()
38
- Poison = auto()
39
- Shoot = auto()
40
- Vote = auto()
41
-
42
-
43
- class GameStatus(Enum):
44
- Good = auto()
45
- Bad = auto()
46
- Unset = auto()
47
-
48
-
49
- @dataclass
50
- class GameState:
51
- day: int
52
- killed: Player | None = None
53
- shoot: tuple[Player, Player] | tuple[None, None] = (None, None)
54
- protected: Player | None = None
55
- potion: tuple[Player | None, tuple[bool, bool]] = (None, (False, False))
56
-
57
-
58
- role_name_conv: dict[Role, str] = {
59
- Role.Werewolf: "狼人",
60
- Role.WolfKing: "狼王",
61
- Role.Prophet: "预言家",
62
- Role.Witch: "女巫",
63
- Role.Hunter: "猎人",
64
- Role.Guard: "守卫",
65
- Role.Idiot: "白痴",
66
- Role.Civilian: "平民",
67
- }
68
-
69
-
70
- role_group_name_conv: dict[RoleGroup, str] = {
71
- RoleGroup.Werewolf: "狼人",
72
- RoleGroup.GoodGuy: "好人",
73
- }
74
-
75
- player_preset: dict[int, tuple[int, int, int]] = {
76
- # 总人数: (狼, 神, 民)
77
- 6: (1, 2, 3),
78
- 7: (2, 2, 3),
79
- 8: (2, 3, 3),
80
- 9: (2, 4, 3),
81
- 10: (3, 4, 3),
82
- 11: (3, 5, 3),
83
- 12: (4, 5, 3),
84
- }
85
-
86
- if config.override_preset is not None:
87
- player_preset |= {i[0]: i[1:] for i in config.override_preset}
88
- player_preset |= {i[0]: i[1:] for i in config.override_preset}