nonebot-plugin-werewolf 1.1.2__py3-none-any.whl → 1.1.5__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 +9 -4
- nonebot_plugin_werewolf/config.py +11 -15
- nonebot_plugin_werewolf/constant.py +40 -3
- nonebot_plugin_werewolf/game.py +219 -181
- nonebot_plugin_werewolf/matchers/__init__.py +2 -0
- nonebot_plugin_werewolf/matchers/depends.py +38 -0
- nonebot_plugin_werewolf/matchers/message_in_game.py +25 -0
- nonebot_plugin_werewolf/matchers/poke/__init__.py +8 -0
- nonebot_plugin_werewolf/matchers/poke/chronocat_poke.py +117 -0
- nonebot_plugin_werewolf/matchers/poke/ob11_poke.py +80 -0
- nonebot_plugin_werewolf/matchers/start_game.py +202 -0
- nonebot_plugin_werewolf/player_set.py +37 -36
- nonebot_plugin_werewolf/players/__init__.py +10 -0
- nonebot_plugin_werewolf/players/can_shoot.py +53 -0
- nonebot_plugin_werewolf/players/civilian.py +7 -0
- nonebot_plugin_werewolf/players/guard.py +30 -0
- nonebot_plugin_werewolf/players/hunter.py +8 -0
- nonebot_plugin_werewolf/players/idiot.py +44 -0
- nonebot_plugin_werewolf/players/joker.py +21 -0
- nonebot_plugin_werewolf/players/player.py +233 -0
- nonebot_plugin_werewolf/players/prophet.py +22 -0
- nonebot_plugin_werewolf/players/werewolf.py +89 -0
- nonebot_plugin_werewolf/players/witch.py +66 -0
- nonebot_plugin_werewolf/players/wolfking.py +14 -0
- nonebot_plugin_werewolf/utils.py +58 -173
- {nonebot_plugin_werewolf-1.1.2.dist-info → nonebot_plugin_werewolf-1.1.5.dist-info}/METADATA +24 -4
- nonebot_plugin_werewolf-1.1.5.dist-info/RECORD +31 -0
- {nonebot_plugin_werewolf-1.1.2.dist-info → nonebot_plugin_werewolf-1.1.5.dist-info}/WHEEL +1 -1
- nonebot_plugin_werewolf/_timeout.py +0 -110
- nonebot_plugin_werewolf/matchers.py +0 -62
- nonebot_plugin_werewolf/ob11_ext.py +0 -72
- nonebot_plugin_werewolf/player.py +0 -462
- nonebot_plugin_werewolf-1.1.2.dist-info/RECORD +0 -16
- {nonebot_plugin_werewolf-1.1.2.dist-info → nonebot_plugin_werewolf-1.1.5.dist-info}/LICENSE +0 -0
- {nonebot_plugin_werewolf-1.1.2.dist-info → nonebot_plugin_werewolf-1.1.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
import itertools
|
2
|
+
|
3
|
+
from nonebot.adapters import Bot, Event
|
4
|
+
from nonebot_plugin_alconna import MsgTarget
|
5
|
+
|
6
|
+
from ..game import Game
|
7
|
+
|
8
|
+
|
9
|
+
def user_in_game(self_id: str, user_id: str, group_id: str | None) -> bool:
|
10
|
+
if group_id is None:
|
11
|
+
return any(
|
12
|
+
self_id == p.bot.self_id and user_id == p.user_id
|
13
|
+
for p in itertools.chain(*[g.players for g in Game.running_games])
|
14
|
+
)
|
15
|
+
|
16
|
+
def check(game: Game) -> bool:
|
17
|
+
return self_id == game.group.self_id and group_id == game.group.id
|
18
|
+
|
19
|
+
if game := next(filter(check, Game.running_games), None):
|
20
|
+
return any(user_id == player.user_id for player in game.players)
|
21
|
+
|
22
|
+
return False
|
23
|
+
|
24
|
+
|
25
|
+
async def rule_in_game(bot: Bot, event: Event, target: MsgTarget) -> bool:
|
26
|
+
if not Game.running_games:
|
27
|
+
return False
|
28
|
+
if target.private:
|
29
|
+
return user_in_game(bot.self_id, target.id, None)
|
30
|
+
return user_in_game(bot.self_id, event.get_user_id(), target.id)
|
31
|
+
|
32
|
+
|
33
|
+
async def rule_not_in_game(bot: Bot, event: Event, target: MsgTarget) -> bool:
|
34
|
+
return not await rule_in_game(bot, event, target)
|
35
|
+
|
36
|
+
|
37
|
+
async def is_group(target: MsgTarget) -> bool:
|
38
|
+
return not target.private
|
@@ -0,0 +1,25 @@
|
|
1
|
+
from nonebot import on_command, on_message
|
2
|
+
from nonebot.adapters import Event
|
3
|
+
from nonebot_plugin_alconna import MsgTarget, UniMessage, UniMsg
|
4
|
+
|
5
|
+
from ..constant import STOP_COMMAND
|
6
|
+
from ..utils import InputStore
|
7
|
+
from .depends import rule_in_game
|
8
|
+
|
9
|
+
message_in_game = on_message(rule=rule_in_game, priority=10)
|
10
|
+
|
11
|
+
|
12
|
+
@message_in_game.handle()
|
13
|
+
async def handle_input(event: Event, target: MsgTarget, msg: UniMsg) -> None:
|
14
|
+
if target.private:
|
15
|
+
InputStore.put(msg, target.id)
|
16
|
+
else:
|
17
|
+
InputStore.put(msg, event.get_user_id(), target.id)
|
18
|
+
|
19
|
+
|
20
|
+
stopcmd = on_command("stop", rule=rule_in_game, block=True)
|
21
|
+
|
22
|
+
|
23
|
+
@stopcmd.handle()
|
24
|
+
async def handle_stopcmd(event: Event, target: MsgTarget) -> None:
|
25
|
+
await handle_input(event=event, target=target, msg=UniMessage.text(STOP_COMMAND))
|
@@ -0,0 +1,117 @@
|
|
1
|
+
import contextlib
|
2
|
+
|
3
|
+
from nonebot import on_message
|
4
|
+
from nonebot.internal.matcher import current_event
|
5
|
+
from nonebot_plugin_alconna import MsgTarget, UniMessage
|
6
|
+
|
7
|
+
from ...config import config
|
8
|
+
from ...constant import STOP_COMMAND
|
9
|
+
from ...game import Game
|
10
|
+
from ...utils import InputStore
|
11
|
+
from ..depends import user_in_game
|
12
|
+
|
13
|
+
|
14
|
+
def chronocat_poke_enabled() -> bool:
|
15
|
+
return False
|
16
|
+
|
17
|
+
|
18
|
+
with contextlib.suppress(ImportError):
|
19
|
+
from nonebot.adapters.satori import Bot
|
20
|
+
from nonebot.adapters.satori.event import (
|
21
|
+
MessageCreatedEvent,
|
22
|
+
PublicMessageCreatedEvent,
|
23
|
+
)
|
24
|
+
|
25
|
+
def extract_poke_tome(event: MessageCreatedEvent) -> str | None:
|
26
|
+
if event.login and event.login.platform and event.login.platform != "chronocat":
|
27
|
+
return None
|
28
|
+
|
29
|
+
poke = event.get_message().include("chronocat:poke")
|
30
|
+
if not poke:
|
31
|
+
return None
|
32
|
+
|
33
|
+
gen = (
|
34
|
+
seg.data["operatorId"]
|
35
|
+
for seg in poke
|
36
|
+
if seg.data["userId"] == event.self_id
|
37
|
+
)
|
38
|
+
return next(gen, None)
|
39
|
+
|
40
|
+
def extract_user_group(event: MessageCreatedEvent) -> tuple[str, str | None]:
|
41
|
+
user_id = event.get_user_id()
|
42
|
+
group_id = None
|
43
|
+
if isinstance(event, PublicMessageCreatedEvent):
|
44
|
+
group_id = (event.guild and event.guild.id) or event.channel.id
|
45
|
+
return user_id, group_id
|
46
|
+
|
47
|
+
# 游戏内戳一戳等效 "stop" 命令
|
48
|
+
async def _rule_poke_stop(bot: Bot, event: MessageCreatedEvent) -> bool:
|
49
|
+
if not config.enable_poke:
|
50
|
+
return False
|
51
|
+
return extract_poke_tome(event) is not None and (
|
52
|
+
user_in_game(bot.self_id, *extract_user_group(event))
|
53
|
+
)
|
54
|
+
|
55
|
+
@on_message(rule=_rule_poke_stop).handle()
|
56
|
+
async def handle_poke_stop(event: MessageCreatedEvent) -> None:
|
57
|
+
InputStore.put(
|
58
|
+
UniMessage.text(STOP_COMMAND),
|
59
|
+
extract_poke_tome(event) or event.get_user_id(),
|
60
|
+
extract_user_group(event)[1],
|
61
|
+
)
|
62
|
+
|
63
|
+
# 准备阶段戳一戳等效加入游戏
|
64
|
+
async def _rule_poke_join(
|
65
|
+
bot: Bot,
|
66
|
+
event: PublicMessageCreatedEvent,
|
67
|
+
target: MsgTarget,
|
68
|
+
) -> bool:
|
69
|
+
if not config.enable_poke:
|
70
|
+
return False
|
71
|
+
|
72
|
+
return (
|
73
|
+
(user_id := extract_poke_tome(event)) is not None
|
74
|
+
and not user_in_game(
|
75
|
+
self_id=bot.self_id,
|
76
|
+
user_id=user_id,
|
77
|
+
group_id=(event.guild and event.guild.id) or event.channel.id,
|
78
|
+
)
|
79
|
+
and any(target.verify(group) for group in Game.starting_games)
|
80
|
+
)
|
81
|
+
|
82
|
+
@on_message(rule=_rule_poke_join).handle()
|
83
|
+
async def handle_poke_join(
|
84
|
+
bot: Bot,
|
85
|
+
event: PublicMessageCreatedEvent,
|
86
|
+
target: MsgTarget,
|
87
|
+
) -> None:
|
88
|
+
user_id = extract_poke_tome(event) or event.get_user_id()
|
89
|
+
players = next(p for g, p in Game.starting_games.items() if target.verify(g))
|
90
|
+
|
91
|
+
if user_id not in players:
|
92
|
+
# XXX:
|
93
|
+
# 截止 chronocat v0.2.19
|
94
|
+
# 通过 guild.member.get / user.get 获取的用户信息均不包含用户名
|
95
|
+
# 跳过用户名获取, 使用用户 ID 代替
|
96
|
+
#
|
97
|
+
# member = await bot.guild_member_get(
|
98
|
+
# guild_id=(event.guild and event.guild.id) or event.channel.id,
|
99
|
+
# user_id=user_id,
|
100
|
+
# )
|
101
|
+
# name = member.nick or (
|
102
|
+
# member.user and (member.user.nick or member.user.name)
|
103
|
+
# )
|
104
|
+
# if name is None:
|
105
|
+
# user = await bot.user_get(user_id=user_id)
|
106
|
+
# name = user.nick or user.name
|
107
|
+
# players[user_id] = name or user_id
|
108
|
+
|
109
|
+
players[user_id] = user_id
|
110
|
+
await UniMessage.at(user_id).text("\n✅成功加入游戏").send(target, bot)
|
111
|
+
|
112
|
+
def chronocat_poke_enabled() -> bool:
|
113
|
+
if not config.enable_poke:
|
114
|
+
return False
|
115
|
+
|
116
|
+
event = current_event.get()
|
117
|
+
return isinstance(event, MessageCreatedEvent) and event.platform == "chronocat"
|
@@ -0,0 +1,80 @@
|
|
1
|
+
import contextlib
|
2
|
+
|
3
|
+
from nonebot import on_notice
|
4
|
+
from nonebot.internal.matcher import current_bot
|
5
|
+
from nonebot_plugin_alconna import MsgTarget, UniMessage
|
6
|
+
|
7
|
+
from ...config import config
|
8
|
+
from ...constant import STOP_COMMAND
|
9
|
+
from ...game import Game
|
10
|
+
from ...utils import InputStore
|
11
|
+
from ..depends import user_in_game
|
12
|
+
|
13
|
+
|
14
|
+
def ob11_poke_enabled() -> bool:
|
15
|
+
return False
|
16
|
+
|
17
|
+
|
18
|
+
with contextlib.suppress(ImportError):
|
19
|
+
from nonebot.adapters.onebot.v11 import Bot
|
20
|
+
from nonebot.adapters.onebot.v11.event import PokeNotifyEvent
|
21
|
+
|
22
|
+
# 游戏内戳一戳等效 "stop" 命令
|
23
|
+
async def _rule_poke_stop(bot: Bot, event: PokeNotifyEvent) -> bool:
|
24
|
+
if not config.enable_poke:
|
25
|
+
return False
|
26
|
+
|
27
|
+
user_id = str(event.user_id)
|
28
|
+
group_id = str(event.group_id) if event.group_id is not None else None
|
29
|
+
return (event.target_id == event.self_id) and user_in_game(
|
30
|
+
bot.self_id, user_id, group_id
|
31
|
+
)
|
32
|
+
|
33
|
+
@on_notice(rule=_rule_poke_stop).handle()
|
34
|
+
async def handle_poke_stop(event: PokeNotifyEvent) -> None:
|
35
|
+
InputStore.put(
|
36
|
+
msg=UniMessage.text(STOP_COMMAND),
|
37
|
+
user_id=str(event.user_id),
|
38
|
+
group_id=str(event.group_id) if event.group_id is not None else None,
|
39
|
+
)
|
40
|
+
|
41
|
+
# 准备阶段戳一戳等效加入游戏
|
42
|
+
async def _rule_poke_join(
|
43
|
+
bot: Bot, event: PokeNotifyEvent, target: MsgTarget
|
44
|
+
) -> bool:
|
45
|
+
if not config.enable_poke or event.group_id is None:
|
46
|
+
return False
|
47
|
+
|
48
|
+
user_id = str(event.user_id)
|
49
|
+
group_id = str(event.group_id)
|
50
|
+
return (
|
51
|
+
(event.target_id == event.self_id)
|
52
|
+
and not user_in_game(bot.self_id, user_id, group_id)
|
53
|
+
and any(target.verify(group) for group in Game.starting_games)
|
54
|
+
)
|
55
|
+
|
56
|
+
@on_notice(rule=_rule_poke_join).handle()
|
57
|
+
async def handle_poke_join(
|
58
|
+
bot: Bot,
|
59
|
+
event: PokeNotifyEvent,
|
60
|
+
target: MsgTarget,
|
61
|
+
) -> None:
|
62
|
+
user_id = event.get_user_id()
|
63
|
+
players = next(p for g, p in Game.starting_games.items() if target.verify(g))
|
64
|
+
|
65
|
+
if event.group_id is None or user_id in players:
|
66
|
+
return
|
67
|
+
|
68
|
+
member_info = await bot.get_group_member_info(
|
69
|
+
group_id=event.group_id,
|
70
|
+
user_id=event.user_id,
|
71
|
+
no_cache=True,
|
72
|
+
)
|
73
|
+
players[user_id] = member_info["card"] or member_info["nickname"] or user_id
|
74
|
+
await UniMessage.at(user_id).text("\n✅成功加入游戏").send(target, bot)
|
75
|
+
|
76
|
+
def ob11_poke_enabled() -> bool:
|
77
|
+
if not config.enable_poke:
|
78
|
+
return False
|
79
|
+
|
80
|
+
return isinstance(current_bot.get(), Bot)
|
@@ -0,0 +1,202 @@
|
|
1
|
+
import re
|
2
|
+
|
3
|
+
import anyio
|
4
|
+
import nonebot
|
5
|
+
import nonebot_plugin_waiter as waiter
|
6
|
+
from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream
|
7
|
+
from nonebot import on_command
|
8
|
+
from nonebot.adapters import Bot, Event
|
9
|
+
from nonebot.rule import to_me
|
10
|
+
from nonebot.utils import escape_tag
|
11
|
+
from nonebot_plugin_alconna import MsgTarget, Target, UniMessage, UniMsg
|
12
|
+
from nonebot_plugin_uninfo import QryItrface, Uninfo
|
13
|
+
|
14
|
+
from ..config import config
|
15
|
+
from ..constant import STOP_COMMAND_PROMPT
|
16
|
+
from ..game import Game
|
17
|
+
from ..utils import extract_session_member_nick
|
18
|
+
from .depends import rule_not_in_game
|
19
|
+
from .poke import poke_enabled
|
20
|
+
|
21
|
+
start_game = on_command(
|
22
|
+
"werewolf",
|
23
|
+
rule=to_me() & rule_not_in_game,
|
24
|
+
aliases={"狼人杀"},
|
25
|
+
)
|
26
|
+
|
27
|
+
|
28
|
+
@start_game.handle()
|
29
|
+
async def handle_start_warning(target: MsgTarget) -> None:
|
30
|
+
if target.private:
|
31
|
+
await UniMessage("⚠️请在群组中创建新游戏").finish(reply_to=True)
|
32
|
+
|
33
|
+
|
34
|
+
async def _prepare_receive(
|
35
|
+
stream: MemoryObjectSendStream[tuple[Event, str, str]],
|
36
|
+
event: Event,
|
37
|
+
group: Target,
|
38
|
+
) -> None:
|
39
|
+
async def same_group(target: MsgTarget) -> bool:
|
40
|
+
return group.verify(target)
|
41
|
+
|
42
|
+
@waiter.waiter(
|
43
|
+
waits=[event.get_type()],
|
44
|
+
keep_session=False,
|
45
|
+
rule=to_me() & same_group & rule_not_in_game,
|
46
|
+
)
|
47
|
+
def wait(event: Event, msg: UniMsg, session: Uninfo) -> tuple[Event, str, str]:
|
48
|
+
text = msg.extract_plain_text().strip()
|
49
|
+
name = extract_session_member_nick(session) or event.get_user_id()
|
50
|
+
return (event, text, name)
|
51
|
+
|
52
|
+
async for evt, text, name in wait(default=(None, "", "")):
|
53
|
+
if evt is None:
|
54
|
+
continue
|
55
|
+
await stream.send((evt, text, re.sub(r"[\u2066-\u2069]", "", name)))
|
56
|
+
|
57
|
+
|
58
|
+
async def _prepare_handle(
|
59
|
+
stream: MemoryObjectReceiveStream[tuple[Event, str, str]],
|
60
|
+
players: dict[str, str],
|
61
|
+
admin_id: str,
|
62
|
+
finished: anyio.Event,
|
63
|
+
) -> None:
|
64
|
+
logger = nonebot.logger.opt(colors=True)
|
65
|
+
|
66
|
+
async def send(msg: str, /) -> None:
|
67
|
+
await UniMessage.text(msg).send(target=event, reply_to=True)
|
68
|
+
|
69
|
+
while True:
|
70
|
+
event, text, name = await stream.receive()
|
71
|
+
user_id = event.get_user_id()
|
72
|
+
colored = f"<y>{escape_tag(name)}</y>(<c>{escape_tag(user_id)}</c>)"
|
73
|
+
|
74
|
+
# 更新用户名
|
75
|
+
# 当用户通过 chronoca:poke 加入游戏时, 插件无法获取用户名, 原字典值为用户ID
|
76
|
+
if user_id in players and players.get(user_id) != name:
|
77
|
+
logger.debug(f"更新玩家显示名称: {colored}")
|
78
|
+
players[user_id] = name
|
79
|
+
|
80
|
+
match (text, user_id == admin_id):
|
81
|
+
case ("开始游戏", True):
|
82
|
+
player_num = len(players)
|
83
|
+
role_preset = config.get_role_preset()
|
84
|
+
if player_num < min(role_preset):
|
85
|
+
await send(
|
86
|
+
f"⚠️游戏至少需要 {min(role_preset)} 人, "
|
87
|
+
f"当前已有 {player_num} 人"
|
88
|
+
)
|
89
|
+
elif player_num > max(role_preset):
|
90
|
+
await send(
|
91
|
+
f"⚠️游戏最多需要 {max(role_preset)} 人, "
|
92
|
+
f"当前已有 {player_num} 人"
|
93
|
+
)
|
94
|
+
elif player_num not in role_preset:
|
95
|
+
await send(f"⚠️不存在总人数为 {player_num} 的预设, 无法开始游戏")
|
96
|
+
else:
|
97
|
+
await send("✏️游戏即将开始...")
|
98
|
+
logger.info(f"游戏发起者 {colored} 开始游戏")
|
99
|
+
finished.set()
|
100
|
+
players["#$start_game$#"] = user_id
|
101
|
+
return
|
102
|
+
|
103
|
+
case ("开始游戏", False):
|
104
|
+
await send("⚠️只有游戏发起者可以开始游戏")
|
105
|
+
|
106
|
+
case ("结束游戏", True):
|
107
|
+
logger.info(f"游戏发起者 {colored} 结束游戏")
|
108
|
+
await send("ℹ️已结束当前游戏")
|
109
|
+
finished.set()
|
110
|
+
return
|
111
|
+
|
112
|
+
case ("结束游戏", False):
|
113
|
+
await send("⚠️只有游戏发起者可以结束游戏")
|
114
|
+
|
115
|
+
case ("加入游戏", True):
|
116
|
+
await send("ℹ️游戏发起者已经加入游戏了")
|
117
|
+
|
118
|
+
case ("加入游戏", False):
|
119
|
+
if user_id not in players:
|
120
|
+
players[user_id] = name
|
121
|
+
logger.info(f"玩家 {colored} 加入游戏")
|
122
|
+
await send("✅成功加入游戏")
|
123
|
+
else:
|
124
|
+
await send("ℹ️你已经加入游戏了")
|
125
|
+
|
126
|
+
case ("退出游戏", True):
|
127
|
+
await send("ℹ️游戏发起者无法退出游戏")
|
128
|
+
|
129
|
+
case ("退出游戏", False):
|
130
|
+
if user_id in players:
|
131
|
+
del players[user_id]
|
132
|
+
logger.info(f"玩家 {colored} 退出游戏")
|
133
|
+
await send("✅成功退出游戏")
|
134
|
+
else:
|
135
|
+
await send("ℹ️你还没有加入游戏")
|
136
|
+
|
137
|
+
case ("当前玩家", _):
|
138
|
+
await send(
|
139
|
+
"✨当前玩家:\n"
|
140
|
+
+ "\n".join(
|
141
|
+
f"{idx}. {players[user_id]}"
|
142
|
+
for idx, user_id in enumerate(players, 1)
|
143
|
+
)
|
144
|
+
)
|
145
|
+
|
146
|
+
|
147
|
+
async def prepare_game(event: Event, players: dict[str, str]) -> None:
|
148
|
+
admin_id = event.get_user_id()
|
149
|
+
group = UniMessage.get_target(event)
|
150
|
+
Game.starting_games[group] = players
|
151
|
+
|
152
|
+
finished = anyio.Event()
|
153
|
+
send, recv = anyio.create_memory_object_stream[tuple[Event, str, str]]()
|
154
|
+
|
155
|
+
async def _handle_cancel() -> None:
|
156
|
+
await finished.wait()
|
157
|
+
tg.cancel_scope.cancel()
|
158
|
+
|
159
|
+
try:
|
160
|
+
async with anyio.create_task_group() as tg:
|
161
|
+
tg.start_soon(_handle_cancel)
|
162
|
+
tg.start_soon(_prepare_receive, send, event, group)
|
163
|
+
tg.start_soon(_prepare_handle, recv, players, admin_id, finished)
|
164
|
+
except Exception as err:
|
165
|
+
await UniMessage(f"狼人杀准备阶段出现未知错误: {err!r}").send()
|
166
|
+
|
167
|
+
del Game.starting_games[group]
|
168
|
+
if players.pop("#$start_game$#", None) != admin_id:
|
169
|
+
await start_game.finish()
|
170
|
+
|
171
|
+
|
172
|
+
@start_game.handle()
|
173
|
+
async def handle_start(
|
174
|
+
bot: Bot,
|
175
|
+
event: Event,
|
176
|
+
target: MsgTarget,
|
177
|
+
session: Uninfo,
|
178
|
+
interface: QryItrface,
|
179
|
+
) -> None:
|
180
|
+
admin_id = event.get_user_id()
|
181
|
+
msg = (
|
182
|
+
UniMessage.text("🎉成功创建游戏\n\n")
|
183
|
+
.text(" 玩家请 @我 发送 “加入游戏”、“退出游戏”\n")
|
184
|
+
.text(" 玩家 @我 发送 “当前玩家” 可查看玩家列表\n")
|
185
|
+
.text(" 游戏发起者 @我 发送 “结束游戏” 可结束当前游戏\n")
|
186
|
+
.text(" 玩家均加入后,游戏发起者请 @我 发送 “开始游戏”\n")
|
187
|
+
)
|
188
|
+
if poke_enabled():
|
189
|
+
msg.text(f"\n可使用戳一戳代替游戏交互中的 “{STOP_COMMAND_PROMPT}” 命令\n")
|
190
|
+
await msg.text("\n游戏准备阶段限时5分钟,超时将自动结束").send(reply_to=True)
|
191
|
+
|
192
|
+
admin_name = extract_session_member_nick(session) or admin_id
|
193
|
+
players = {admin_id: admin_name}
|
194
|
+
|
195
|
+
try:
|
196
|
+
with anyio.fail_after(5 * 60):
|
197
|
+
await prepare_game(event, players)
|
198
|
+
except TimeoutError:
|
199
|
+
await UniMessage.text("⚠️游戏准备超时,已自动结束").finish()
|
200
|
+
|
201
|
+
game = Game(bot, target, set(players), interface)
|
202
|
+
await game.start()
|
@@ -1,15 +1,10 @@
|
|
1
|
-
|
1
|
+
import functools
|
2
2
|
|
3
|
-
import
|
4
|
-
from
|
3
|
+
import anyio
|
4
|
+
from nonebot_plugin_alconna.uniseg import UniMessage
|
5
5
|
|
6
|
-
from .
|
7
|
-
from .
|
8
|
-
|
9
|
-
if TYPE_CHECKING:
|
10
|
-
from nonebot_plugin_alconna.uniseg import UniMessage
|
11
|
-
|
12
|
-
from .constant import Role, RoleGroup
|
6
|
+
from .constant import Role, RoleGroup
|
7
|
+
from .players import Player
|
13
8
|
|
14
9
|
|
15
10
|
class PlayerSet(set[Player]):
|
@@ -17,26 +12,26 @@ class PlayerSet(set[Player]):
|
|
17
12
|
def size(self) -> int:
|
18
13
|
return len(self)
|
19
14
|
|
20
|
-
def alive(self) -> PlayerSet:
|
15
|
+
def alive(self) -> "PlayerSet":
|
21
16
|
return PlayerSet(p for p in self if p.alive)
|
22
17
|
|
23
|
-
def dead(self) -> PlayerSet:
|
18
|
+
def dead(self) -> "PlayerSet":
|
24
19
|
return PlayerSet(p for p in self if not p.alive)
|
25
20
|
|
26
|
-
def killed(self) -> PlayerSet:
|
21
|
+
def killed(self) -> "PlayerSet":
|
27
22
|
return PlayerSet(p for p in self if p.killed.is_set())
|
28
23
|
|
29
|
-
def include(self, *types: Player | Role | RoleGroup) -> PlayerSet:
|
24
|
+
def include(self, *types: Player | Role | RoleGroup) -> "PlayerSet":
|
30
25
|
return PlayerSet(
|
31
26
|
player
|
32
27
|
for player in self
|
33
28
|
if (player in types or player.role in types or player.role_group in types)
|
34
29
|
)
|
35
30
|
|
36
|
-
def select(self, *types: Player | Role | RoleGroup) -> PlayerSet:
|
31
|
+
def select(self, *types: Player | Role | RoleGroup) -> "PlayerSet":
|
37
32
|
return self.include(*types)
|
38
33
|
|
39
|
-
def exclude(self, *types: Player | Role | RoleGroup) -> PlayerSet:
|
34
|
+
def exclude(self, *types: Player | Role | RoleGroup) -> "PlayerSet":
|
40
35
|
return PlayerSet(
|
41
36
|
player
|
42
37
|
for player in self
|
@@ -47,37 +42,43 @@ class PlayerSet(set[Player]):
|
|
47
42
|
)
|
48
43
|
)
|
49
44
|
|
50
|
-
def player_selected(self) -> PlayerSet:
|
45
|
+
def player_selected(self) -> "PlayerSet":
|
51
46
|
return PlayerSet(p.selected for p in self.alive() if p.selected is not None)
|
52
47
|
|
48
|
+
@functools.cached_property
|
53
49
|
def sorted(self) -> list[Player]:
|
54
50
|
return sorted(self, key=lambda p: p.user_id)
|
55
51
|
|
56
|
-
async def interact(self
|
57
|
-
async with
|
58
|
-
|
59
|
-
|
60
|
-
async def vote(self, timeout_secs: float = 60) -> dict[Player, list[Player]]:
|
61
|
-
async def vote(player: Player) -> tuple[Player, Player] | None:
|
62
|
-
try:
|
63
|
-
async with timeout(timeout_secs):
|
64
|
-
return await player.vote(self)
|
65
|
-
except TimeoutError:
|
66
|
-
await player.send("投票超时,将视为弃票")
|
67
|
-
return None
|
52
|
+
async def interact(self) -> None:
|
53
|
+
async with anyio.create_task_group() as tg:
|
54
|
+
for p in self.alive():
|
55
|
+
tg.start_soon(p.interact)
|
68
56
|
|
57
|
+
async def vote(self) -> dict[Player, list[Player]]:
|
58
|
+
players = self.alive()
|
69
59
|
result: dict[Player, list[Player]] = {}
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
60
|
+
|
61
|
+
async def _vote(player: Player) -> None:
|
62
|
+
vote = await player.vote(players)
|
63
|
+
if vote is not None:
|
64
|
+
result[vote] = [*result.get(vote, []), player]
|
65
|
+
|
66
|
+
async with anyio.create_task_group() as tg:
|
67
|
+
for p in players:
|
68
|
+
tg.start_soon(_vote, p)
|
69
|
+
|
74
70
|
return result
|
75
71
|
|
76
72
|
async def broadcast(self, message: str | UniMessage) -> None:
|
77
|
-
|
73
|
+
if not self:
|
74
|
+
return
|
75
|
+
|
76
|
+
async with anyio.create_task_group() as tg:
|
77
|
+
for p in self:
|
78
|
+
tg.start_soon(p.send, message)
|
78
79
|
|
79
80
|
def show(self) -> str:
|
80
|
-
return "\n".join(f"{i}. {p.name}" for i, p in enumerate(self.sorted
|
81
|
+
return "\n".join(f"{i}. {p.name}" for i, p in enumerate(self.sorted, 1))
|
81
82
|
|
82
83
|
def __getitem__(self, __index: int) -> Player:
|
83
|
-
return self.sorted
|
84
|
+
return self.sorted[__index]
|
@@ -0,0 +1,10 @@
|
|
1
|
+
from .civilian import Civilian as Civilian
|
2
|
+
from .guard import Guard as Guard
|
3
|
+
from .hunter import Hunter as Hunter
|
4
|
+
from .idiot import Idiot as Idiot
|
5
|
+
from .joker import Joker as Joker
|
6
|
+
from .player import Player as Player
|
7
|
+
from .prophet import Prophet as Prophet
|
8
|
+
from .werewolf import Werewolf as Werewolf
|
9
|
+
from .witch import Witch as Witch
|
10
|
+
from .wolfking import WolfKing as WolfKing
|
@@ -0,0 +1,53 @@
|
|
1
|
+
from nonebot_plugin_alconna.uniseg import UniMessage
|
2
|
+
from typing_extensions import override
|
3
|
+
|
4
|
+
from ..constant import STOP_COMMAND_PROMPT, KillReason
|
5
|
+
from .player import Player
|
6
|
+
|
7
|
+
|
8
|
+
class CanShoot(Player):
|
9
|
+
@override
|
10
|
+
async def post_kill(self) -> None:
|
11
|
+
if self.kill_info and self.kill_info.reason == KillReason.Poison:
|
12
|
+
await self.send("⚠️你昨晚被女巫毒杀,无法使用技能")
|
13
|
+
return await super().post_kill()
|
14
|
+
|
15
|
+
await self.game.send(
|
16
|
+
UniMessage.text(f"🕵️{self.role_name} ")
|
17
|
+
.at(self.user_id)
|
18
|
+
.text(f" 死了\n请{self.role_name}决定击杀目标...")
|
19
|
+
)
|
20
|
+
|
21
|
+
self.game.state.shoot = None
|
22
|
+
shoot = await self.shoot()
|
23
|
+
|
24
|
+
if shoot is not None:
|
25
|
+
self.game.state.shoot = self
|
26
|
+
await self.send(
|
27
|
+
UniMessage.text(f"🔫{self.role_name} ")
|
28
|
+
.at(self.user_id)
|
29
|
+
.text(" 射杀了玩家 ")
|
30
|
+
.at(shoot.user_id)
|
31
|
+
)
|
32
|
+
await shoot.kill(KillReason.Shoot, self)
|
33
|
+
self.selected = shoot
|
34
|
+
else:
|
35
|
+
await self.send(f"ℹ️{self.role_name}选择了取消技能")
|
36
|
+
return await super().post_kill()
|
37
|
+
|
38
|
+
async def shoot(self) -> Player | None:
|
39
|
+
players = self.game.players.alive().exclude(self)
|
40
|
+
await self.send(
|
41
|
+
"💫请选择需要射杀的玩家:\n"
|
42
|
+
+ players.show()
|
43
|
+
+ "\n\n🔫发送编号选择玩家"
|
44
|
+
+ f"\n❌发送 “{STOP_COMMAND_PROMPT}” 取消技能"
|
45
|
+
)
|
46
|
+
|
47
|
+
if selected := await self._select_player(
|
48
|
+
players,
|
49
|
+
on_stop="ℹ️已取消技能,回合结束",
|
50
|
+
):
|
51
|
+
await self.send(f"🎯选择射杀的玩家: {selected.name}")
|
52
|
+
|
53
|
+
return selected
|