diff --git a/konabot/docs/user/celeste.txt b/konabot/docs/user/celeste.txt new file mode 100644 index 0000000..ef59bca --- /dev/null +++ b/konabot/docs/user/celeste.txt @@ -0,0 +1,38 @@ +# Celeste + +爬山小游戏,移植自 Ccleste,是 Celeste Classic(即 PICO-8)版。 + +使用 `wasdxc` 和数字进行操作。 + +## 操作说明 + +`wsad` 是上下左右摇杆方向,或者是方向键。`c` 是跳跃键,`x` 是冲刺键。 + +使用空格分隔每一个操作,每个操作持续一帧。如果后面跟着数字,则持续那么多帧。 + +### 例子 1 + +``` +xc 180 +``` + +按下 xc 一帧,然后空置 180 帧。 + +### 例子 2 + +``` +d10 cd d10 xdw d20 +``` + +向右走 10 帧,向右跳一帧,再继续按下右 10 帧,按下向右上冲刺一帧,再按下右 20 帧。 + +## 指令使用说明 + +直接说 `celeste` 会开启一个新的游戏。但是,你需要在后面跟有操作,才能够渲染 gif 图出来。 + +一个常见的开始操作是直接发送 `celeste xc 130`,即按下 xc 两个按键触发 PICO 版的开始游戏,然后等待 130 秒动画播放完毕。 + +对于一个已经存在而且时间不是非常久远的 gif 图,只要是由 bot 自己发送出来的,就可以在它的基础上继续游戏。回复这条消息,可以继续游戏。 + +一种很常见的技巧是回复一个已经存在的 gif 图 `celeste 1`,此时会空操作一帧并且渲染画面。你可以用这种方法查看一个 gif 图的游戏目前的状态。 + diff --git a/konabot/plugins/celeste_classic/__init__.py b/konabot/plugins/celeste_classic/__init__.py index c00ae89..e55dae9 100644 --- a/konabot/plugins/celeste_classic/__init__.py +++ b/konabot/plugins/celeste_classic/__init__.py @@ -3,7 +3,7 @@ import subprocess import tempfile from typing import Any from loguru import logger -from nonebot import on_command +from nonebot import on_message from pydantic import BaseModel from nonebot.adapters import Event, Bot @@ -43,15 +43,11 @@ celeste_status = DataManager(CelesteStatus, DATA_PATH / "celeste-status.json") # ↓ 这里的 Type Hinting 是为了能 fit 进去 set[str | tuple[str, ...]] -aliases: set[Any] = {"蔚蓝", "爬山", "鳌太线"} +aliases: set[Any] = {"celeste", "蔚蓝", "爬山", "鳌太线"} ALLOW_CHARS = "wasdxc0123456789 \t\n\r" -cmd = on_command(cmd="celeste", aliases=aliases) - - -@cmd.handle() -async def _(msg: UniMsg, evt: Event, bot: Bot): +async def get_prev(evt: Event, bot: Bot) -> str | None: prev = None if isinstance(evt, OB11MessageEvent): if evt.reply is not None: @@ -61,8 +57,30 @@ async def _(msg: UniMsg, evt: Event, bot: Bot): if seg.type == 'reply': msgid = seg.get('id') prev = f"QQ:{bot.self_id}:" + str(msgid) + if prev is not None: + async with celeste_status.get_data() as data: + prev = data.records.get(prev) + return prev - actions = msg.extract_plain_text().strip().removeprefix("celeste") + +async def match_celeste(evt: Event, bot: Bot, msg: UniMsg) -> bool: + prev = await get_prev(evt, bot) + text = msg.extract_plain_text().strip() + if any(text.startswith(a) for a in aliases): + return True + if prev is not None: + return True + return False + + +# cmd = on_command(cmd="celeste", aliases=aliases) +cmd = on_message(rule=match_celeste) + + +@cmd.handle() +async def _(msg: UniMsg, evt: Event, bot: Bot): + prev = await get_prev(evt, bot) + actions = msg.extract_plain_text().strip() for alias in aliases: actions = actions.removeprefix(alias) actions = actions.strip() @@ -71,10 +89,6 @@ async def _(msg: UniMsg, evt: Event, bot: Bot): if any((c not in ALLOW_CHARS) for c in actions): return - if prev is not None: - async with celeste_status.get_data() as data: - prev = data.records.get(prev) - await ensure_artifact(arti_ccleste_wrap_linux) await ensure_artifact(arti_ccleste_wrap_windows)