snowy-yang
|
cb4c1e7800
|
feat(engine): 四座自动化设施——喷灌 / 收割机 / 售货机 / 磨坊
为什么:自动化建筑只有水井和喂食器,中期以后每天的操作只剩浇水喂食
的重复劳动。新设施把"替代行动点动作"的思路推到完整流水线:
浇水(井/喷灌)→ 收割机收熟地 → 磨坊把陈粮变饲料 → 喂食器喂牲口
→ 售货机趁高价清库存,人不在也能持续产出。
- config.BUILDINGS 注册 4 座:喷灌系统 2800 金(全量浇水)、
自动收割机 900 金 ×3(每天各收 2 块熟地,刻意不给经验——机器收的
不算劳动,给手动玩法留价值)、自动售货机 1200 金(市价 > 基准价才
出手、每天上限 5 份、越贵越先卖)、饲料磨坊 500 金(1 份最便宜的
作物磨 3 份饲料)。
- engine._run_buildings 扩成六段流水线,顺序即依赖:磨坊排在喂食器前,
早上磨的饲料当天就能喂上;收割机遇满仓停摆、作物留地(同喂食器
缺料停摆哲学——自动系统绝不硬来,更绝不自动花钱)。
- webui / build 动作 / catalog 全部由 config.BUILDINGS 驱动,零改动。
- 测试 +6(喷灌全量浇水、收割入库且无经验、满仓停摆留作物、磨坊挑
最便宜、磨坊→喂食器同日链路、售货机阈值与配额)。修复断言时确认
两个既有行为:引擎自带 START_FEED=5;advance_day 末尾会把动物 fed
复位("喂到了"的证据是 pending 累计而非 fed 本身)。
- 门禁:534 项 OK(skip 39)、ruff/pyright 全绿。
|
2026-09-25 21:05:27 +08:00 |
|
snowy-yang
|
2676927c1a
|
fix(webui): 中文用户名登录后所有写操作静默失败
为什么:headers() 把用户名塞进 X-Farm-Id 请求头,而 HTTP 头只允许
Latin-1 字符——中文用户名(如"阳")会让 fetch 在发送前直接抛
TypeError(String contains non ISO-8859-1 code point)。登录时
me 还是 null(头为 ASCII 的 default)所以能登录成功,登录之后
me 变成中文,所有走 api() 的写操作(播种/浇水/收获/卖出)全部
静默失败;页面靠 SSE 推送照常渲染,看起来一切正常,用户表现为
"点播种没反应"。
- headers():非 ASCII 的牧场名/令牌不再塞头。登录态本来就由
Cookie 鉴权,X-Farm-Id 只是匿名脚本模式的备用通道;ASCII 名
(default、bot 等)行为不变。
- api():捕获 fetch 网络异常并 toast 提示。此前 Promise 拒绝
无人接住,这类故障完全不可见,正是本 bug 难排查的原因。
- localStorage 读写加 try/catch 兜底:sandbox iframe 若不带
allow-same-origin,裸访问即抛 SecurityError,会炸掉整个启动
脚本(启动段就有三处裸调用)。
验证:headless Chrome 真机复现 → 修复 → UI 全流程回归(登录
"阳" → 点播种 → 选小麦 → toast 与地块状态正确更新);ruff /
pyright / 528 项测试全绿。
|
2026-09-23 22:28:40 +08:00 |
|
snowy-yang
|
698a5e497a
|
feat: 用户名登录——一账号一个牧场,登录后改 URL 也进不了别人的档
多用户之前是「知道牧场名就能玩」:?farm=boss 直接进别人家里。
现在网页端用用户名登录(用户选择:不设密码、一账号一个牧场,
用户名即身份——互信小圈子模型,防的是随手改 URL,不是蓄意攻击)。
* 新增 farm/server/auth.py:HMAC-SHA256 签名的 HttpOnly Cookie
(sf_auth,30 天,恒时比较防篡改)。密钥存 <save_dir>/secret.key,
重启不掉线,也不用建表存 token。
* app.py 三个认 ?farm= 的地方(session_of / SSE / POST /api/farms)
统一规则:有 Cookie 强制本人的牧场(?farm=/X-Farm-Id 被忽略),
无 Cookie 保持旧语义——bot/TUI/CLI 脚本零改动照常工作。
* 新路由:POST /api/login(全局 token 照常把它挡在门外)、
POST /api/logout、GET /api/me(刻意不 guard:首屏只问"登录没")。
* 网页:移除牧场名输入框,改为登录遮罩 + 用户徽章 + 退出按钮;
首屏先 whoami() 再决定连接还是弹登录。
测试 +13:Cookie 伪造/过期/跨密钥/垃圾串/非法用户名/密钥跨重启复用/
session_of 优先级(starlette Request 用 scope dict 直接构造,不需要
httpx)9 项纯逻辑档 + 4 项 TestClient 档。528 项 OK(skip 39)。
真机验收 16/16:登录/HttpOnly/?farm= 被忽略/动作落自己牧场/篡改
退回未登录/退出/非法名 400;跨重启 Cookie 仍有效且进度保留。
|
2026-09-23 21:20:25 +08:00 |
|
snowy-yang
|
d1692d7b10
|
docs: 记忆与验收技能跟上——存档改动必须真机验自动落盘
单元测试抓不住 autosave 失效(dirty 被高频清零这件事测试里复现
不了真机时序),所以技能里加一条:动了存档/会话层,必须 0 客户端
在线做一个动作、等 60s、直接查库那一行有没有更新。
|
2026-09-23 20:54:13 +08:00 |
|
snowy-yang
|
ea346be484
|
fix: 自动存档几乎永不触发的真 bug——把 dirty 和 unsaved 拆开
顺着验收网页修复时发现:default 牧场根本没写进库,内存里每次
重启都是新档。根因是 session.dirty 一个标志位伺二主:
* Publisher 推送循环每 ~250ms 就把 dirty 清掉(无人在线时直接清,
"没人在看就不做序列化,省 CPU");
* autosave() 每 60s 才看一眼 dirty。
dirty=True 的窗口只有约 250ms,自动存档撞上它的概率约 0.4%——
等于永不存盘。进度只在手动存档/优雅停机时落盘,进程被强杀全丢。
修法:新增 session.unsaved 专管「有变化没落盘」,_on_change / load /
touch / advance_day 置位,只有成功的 save() 清位;autosave() 改看
unsaved;dirty 继续只管推送节流,hub.py 的推送循环一字未动。
两只旧测试直接操纵 dirty 驱动 autosave,固化了被修掉的耦合,改成
unsaved 表达意图;新增回归测试:推送循环清了 dirty 也要照常落盘、
没有新变化不重复写库。
真机验证:0 客户端在线纯 HTTP 种一块地,70 秒内库行更新(金币
120→112,种子钱都对得上)。515 项测试 OK。
|
2026-09-23 20:54:12 +08:00 |
|
snowy-yang
|
305ef0dcbe
|
fix: 网页播种/重置在嵌入预览里没反应——弃用 window.prompt/confirm
播种按钮原来弹 window.prompt 选作物,重置弹 confirm——这俩在
sandbox iframe(内置预览面板)里会被静默屏蔽:不弹窗、直接返回
null,表现为"点了没反应"。普通浏览器没事,所以一直没暴露。
改成页面内交互:播种弹 #pickmask 弹层,列出未锁定作物的按钮 +
取消,点遮罩关闭;重置改两步确认,第一次点变"再点一次确认",
3 秒不点自动还原。
|
2026-09-23 20:54:11 +08:00 |
|
snowy-yang
|
cb292f9f53
|
docs: 验收技能跟上 SQLite 存档——预置存档改塞库行,加了别动共享端口的提醒
|
2026-09-19 19:11:27 +08:00 |
|
snowy-yang
|
bc3ae96a38
|
refactor: 存档层从「一牧场一个 JSON」换成 SQLite(单库单表)
`saves/farm.db` 一张 farms 表,farm_id 主键,**一个牧场一行**。
接口签名不变(save_state / load_state / delete_save 只多了 farm_id 参数),
调用方只改了路径语义。
设计取舍:标量列 + 整份 payload。
* day/gold/level/ap/weather/forecast/net_worth 单独成列——外部工具或 AI
一条 SELECT 就能排榜、画曲线,不必先把每份存档读进内存;
* 完整状态仍整份存 payload JSON:游戏数据隔三差五长新字段,规范化成
几十张表意味着每加字段写一次迁移,而存档只做整体读、整体写,
所以结构化列管可查询,完整性交给 JSON——加字段不用改表结构。
崩溃安全升级:写入包在事务里。原来的"临时文件 + os.replace"能挡进程
崩溃,挡不住写到一半掉电;SQLite 的日志能。读取失败照旧降级开新档。
向后兼容:SAVE_VERSION=2 / MIN_SAVE_VERSION=1 不变。老 JSON 存档由
migrate_json_saves() 在服务器启动时自动导入:同名已存在就跳过(宁可不
迁不覆盖),导入成功原文件改名 *.json.migrated 留底,先写库再改名
保证幂等;坏档一律跳过——迁移不许有能力阻止服务器启动。
异常边界:StorageError = sqlite3.Error,刻意不是 OSError 子类——
调用方原来的 except OSError 全部跟着改,否则写盘失败会穿透出去。
性能实测:PRAGMA journal_mode=WAL 是持久属性,建库时设一次就够,
每次连接重设白花 2.2ms;save_state 约 6.9ms/次,大头是 synchronous=FULL
的 fsync——不省,存档的价值就是不丢档。bench 热路径无回归。
顺手修一个真 bug:connect() 建表失败时连接会泄漏,Windows 上句柄没关
就删不掉文件,而调用方正是靠"读失败→开新档"容错的。补 close。
测试:StorageTestCase 重写(往返/版本/损坏降级/多牧场隔离/迁移),
test_server 断言 *.json 的用例改查库。513 项全过,ruff/pyright 全绿。
端到端:预置 v1/v2 老档起真服务器验 22 项——自动迁移、字段还原、
list_farms() 排榜、重启后进度分毫不差,全过。
|
2026-09-19 19:04:30 +08:00 |
|
snowy-yang
|
6268283fc6
|
chore: 存一个「改完怎么验收怎么交付」的技能,省得下次重新踩
本次踩到两个纯靠猜出不来的坑,都写进技能里了:
* `RemoteFarm.snapshot()` 是本地缓存不发请求,非实时脚本轮询会一直读到同一份
旧数据——服务器在推进却看起来像卡死,排查花了十几分钟。
* `git push` 报远端 `Failed to authenticate user` 其实是本地 GCM 拒绝交互
(`Cannot prompt because user interactivity has been disabled`),
加 `GCM_INTERACTIVE=always` 就好;再不行 `GCM_TRACE=1` 看日志。
验证推送成功要看 `git ls-remote`,`git status -sb` 的 `[gone]` 是沙箱
丢 `.git/refs/remotes/**` 写入导致的假象。
技能放在 .workbuddy/skills/simplefarm-verify-and-ship/,覆盖四步交付流程:
门禁 → 真机端到端验收(预置存档 + 后台任务起服务器)→ 写记忆 → 提交推送。
|
2026-09-19 17:06:15 +08:00 |
|
snowy-yang
|
cf24cb43f1
|
feat: 加天气 / 谷仓 / 建筑三套系统,收获改为入库 + 择时卖出
动手前先修掉了两处「死机制」,新增的三个系统各自激活一个:
* 天气:小麦 2 天成熟、初始水分 2、每天只掉 1 点,等于永远不需要浇水。
现在水分消耗由天气决定(晴 1 / 多云 0 / 干旱 2,雨天反补 3),而
`state.forecast` 把明天的天气提前告诉你——「今晚浇不浇」成了能算的决策。
5 种天气、权重合计 100、同种不连出;暴风雨会毁未熟作物,温室免疫。
* 谷仓:市场波动原本纯装饰(收获当场按市价变现,没有择时空间)。现在收获与
收取只进 `state.inventory`,靠新增的 `sell` 换金币,价格波动才真正参与决策。
仓满时 `_store()` 抛 `granary_full` 且不改状态,作物原样留在地里。
* 建筑:新增 `build`,买的是「以后不用天天手动做」——水井自动浇干地、
喂食器自动喂食(只吃库存饲料,绝不自动花钱)、温室、谷仓扩容。
每天在 `advance_day` 里先于生长执行,同类越造越贵(×1.6)。
存档升到 `SAVE_VERSION=2`,配 `MIN_SAVE_VERSION=1` 向后兼容,旧档缺的新字段
一律走默认值,不作废玩家手里的档。`advance_day(weather=...)` 是留给测试的
确定性入口。
顺带修两个真 bug:
* `daemon.py` 三处 `makedirs(os.path.dirname(path))`:`--pid-file pid.txt`
这种只给文件名的用法会让 dirname 变成空串,启动阶段直接崩。抽出
`ensure_parent_dir()` 兜底。
* `RemoteFarm.snapshot()` 是本地缓存,非实时客户端轮询会一直读到旧数据,
看起来像服务器卡死。补 docstring 指明要自己调 `refresh()`。
质量门禁:ruff format/check 全绿、pyright 0 错误、503 项测试 OK(skip 35)、
bench 无回归(brief 19.5µs / snapshot 48.5µs / POST full=1 400µs)。
另起真实服务器(--tick 3 + 预置存档)跑了 38 项端到端验收,全过。
|
2026-09-19 17:02:33 +08:00 |
|
snowy-yang
|
a92c29ead2
|
docs: 记录发布到 gitea.4i.hk 的仓库地址与推送认证方式
远端仓库:https://gitea.4i.hk/hhhge/SimpleFarm(public,main)。
关键坑:非交互 shell 里必须 GCM_INTERACTIVE=always,否则 GCM 会因为「用户交互被禁用」
直接拒绝,git 报 could not read Username / Failed to authenticate user,看起来像认证配错了。
另记:tea 的 OAuth token 能建仓、能调 API、能加 SSH 公钥,但不能签发访问令牌
(POST /users/{u}/tokens 需要 basic auth)。
|
2026-09-19 13:33:48 +08:00 |
|
snowy-yang
|
450655bebf
|
chore: 补记本机两个仓库/环境坑(Git Bash 缺 cat、.gitignore 的 /_* 规则)
提交多行 message 时用 git commit -F <文件>,不要用 $(cat <<EOF) —— 本机没装 cat,
git 会因此认为消息为空并 abort。
根目录以 _ 开头的文件被 .gitignore 的 /_* 静默忽略:临时脚本因此不会污染 git status,
但放错名字的正经文件也会被吞掉。
|
2026-09-19 12:56:07 +08:00 |
|
snowy-yang
|
f8e093c98f
|
feat: 实时通道改 SSE、服务器换 uvicorn+zttp、节奏改 40 分钟一天,并重建测试与优化热路径
三件事凑成一次不兼容变更:WebSocket 通道、Hypercorn 服务器、「手动推进一天」都消失了。
实时通道:WebSocket -> SSE
- hub 改为 SSE 订阅池,GET /api/stream 单向推送 welcome/state/day/notice/bye,
250ms 节流 + 15s 心跳注释;写操作一律走 HTTP POST /api/actions,不再有 WS 回执。
- 停机三处一起修才干净:close_all() 发 bye + timeout_graceful_shutdown 兜底 +
客户端 stop() 直接 shutdown 底层 socket(光 close() 会等读线程的锁,最长卡到下次心跳)。
- 网页版改 EventSource + fetch;tools/ws_demo.py 换成 tools/sse_demo.py。
服务器:Hypercorn -> uvicorn + zttp 的 HTTP/2
- 同一端口同时说 HTTP/1.1 与 HTTP/2。uvicorn 0.53 不再内置 h2,必须装 zttp 并显式
http="zttp",否则 Config.load() 直接 raise。
- 信号接管改成子类覆盖 Server.handle_exit(0.53 没有 install_signal_handlers 可覆盖),
不调 super()、不写 _captured_signals,退出时不重放信号;第二次信号强制退出。
- 入口启动前用 daemon.port_busy() 自检端口:Windows 上 SO_REUSEADDR 会让第二个实例
「绑上」已监听端口并抢走新连接,所以判断「能不能连上」而不是「能不能 bind」。
- 修掉 clear_pid_file 的 Windows 真 bug:文件还开着就 os.remove 会失败且被
except OSError 吞掉,导致每次正常退出都留 stale pid 文件。
节奏:一天 15 秒 -> 40 分钟,并整体移除「休息」
- DEFAULT_TICK_SECONDS=2400,MAX_TICK_SECONDS 放宽到 86400(旧上限会把 40 分钟夹回去)。
- engine.sleep / SLEEP_COOLDOWN / /api/sleep / TUI 的 [N]休息 / 网页版按钮全部删掉,
时间只能自然流动;机器人改为 wait_for_next_day(timeout),CLI 加 bot --wait。
- 时长显示统一走 models.format_duration(),不再出现 2399.6s 这种写法。
键盘:修复方向键/空格/Tab/ESC 全部静默失效
- keys.py 的具名键统一小写并成为唯一来源,tui.py 一律引用 keys.XXX 而不手写字符串
(历史 bug 就是 Windows 侧产大写 UP、TUI 里写小写 up,字母键正常所以很难发现)。
- 顺带修 POSIX 路径一次 os.read 只取首字节、其余按键静默丢弃的老毛病。
测试:重建 500 项
- test_engine 110 / test_actions 35 / test_server 131(skip 35) / test_tui_keys 75 /
test_bot 36 / test_client 113。未安装的第三方依赖用 skipUnless 自动跳过,其余照跑,
所以是全绿而不是靠跳过伪装。
- 被测试逼出两处真隐患并修掉:RemoteFarm._normalize() 必须「先补 scheme 再削尾斜杠」
(顺序反了畸形端点会溜到系统代理、报一个和真实原因无关的 502);StateView 空视图要
预置 plots/animals,否则连不上服务器时读 state.plots 直接 AttributeError。
优化:先量后改,依据与前后数字都在 tools/bench.py
- 新增 engine.brief_source():brief/summary/动作回执只要十几个标量,不再为它们序列化
整局;to_dict 里 net_worth 只算一次。brief -76%、summary -77%、leaderboard(32) -78%、
GET /api/world -31%、POST /api/actions?full=1 -16%。
- 拆掉三个巨型函数:create_app 475->32 行(_Services + _register_*)、
build_parser 与 available_actions 分组、__main__.main 160->7 行。
- 消除重复:等级门槛 5 处 -> _is_locked/_require_level/_newly_unlocked;hub 的
broadcast/broadcast_all/close_all -> _push_many;daemon.probe/world -> _get_json;
main.py 的 _add_endpoint_args/_leaderboard_lines。
- tools/bench.py 作为长期性能守卫(含调用计数器),谁把轻量路径改回 snapshot() 会立刻报警。
门禁:ruff format/check 全绿、pyright 0 错误、500 项测试 OK(skip 35)。
|
2026-09-19 12:55:45 +08:00 |
|
snowy-yang
|
47dd818360
|
docs: 记录规则生效的探针反证过程
复核 ruff/pyright 规则是否真的被加载:排除所有可能覆盖 pyproject 的配置文件,并用故意违规的探针文件确认精确命中。
新知识:X | None 的规则码在新版 ruff 是 UP045 而非 UP007;SIM105 对 try 体含 return 的情况有意不报,所以'没报出来'不等于'规则没启用'。
|
2026-09-18 21:32:46 +08:00 |
|
snowy-yang
|
52004571c8
|
docs: 记录工程化改造(uv + ruff + pyright)的决策与踩过的坑
日志补记下半场:三件套的落地细节、行宽为何定 100、PTH 为何不开、pyright 两个关键开关;新增三条可复用环境坑(write_text 会把 LF 变 CRLF、同文件多次 Edit 互相覆盖、git status 与 git diff 不一致的判别)。
项目长期约定同步更新:运行方式改为 uv 单一可信来源,新增代码风格约定与质量门禁命令。
|
2026-09-18 21:28:20 +08:00 |
|
snowy-yang
|
c414db4217
|
fix: 用户可见的操作提示统一为 uv run 前缀
项目由 uv 管理,依赖装在 .venv。此前各处的提示语仍写作 `python main.py ...`,用户若在未激活虚拟环境时照抄,会因缺少 fastapi 而失败——这是正确性问题,不只是措辞。
涉及:main.py(12 行,含 prog 与各条 hint)、ai_player.py(5 行)、farm/client.py、farm/server/__main__.py(3 行,含 prog)、farm/server/__init__.py、farm/server/daemon.py、README(4 处示例与目录树注释)。
刻意保留 main.py:26 与 README:49 的「没装 uv?」兜底说明,那里 `python main.py` 是正确写法。
验收:ruff 全通过 / format 26 文件无改动 / pyright 0 错误 / 97 项测试通过。
|
2026-09-18 21:25:52 +08:00 |
|
snowy-yang
|
d7a7107b0a
|
chore: 引入 pyright 类型检查清零 + README 工程化章节
pyright(basic 模式)从 15 errors / 10 warnings 降到 0/0:
- pyproject: pythonPlatform="All"(termios/msvcrt 双平台分支不再误报),
reportUnsupportedDunderAll=false(__init__ 惰性导出属正常写法)
- keys.py: 捕获 sys.stdin 局部变量 + 提前返回,消除 fileno() 的 None 收窄告警
- server/app.py: lifespan 改用 @asynccontextmanager 并标注 AsyncIterator[None]
- server/actions.py: 显式标注 result: dict[str, Any],消除 isinstance 冗余报错
- server/__main__.py: 用 importlib.util.find_spec 探测 websockets
- tests/test_server.py: 同上,uvicorn 探测改为 find_spec
- tools/preview_tui.py: TYPE_CHECKING 下引入 RemoteFarm,修正返回类型标注
- farm/tui.py: 取消未使用变量,统一 round/取整写法
其他:
- README: 新增第 11 节「工程化:uv + ruff + pyright」,快速开始全部改用
uv sync / uv run;原第 11 节起顺延为 12
- requirements.txt: 由 uv export --no-dev --no-hashes 重新生成(锁定版本)
- 清理临时日志文件
验收:ruff check 全通过 / ruff format 26 文件无改动 / pyright 0 错误 /
unittest 97 项全部通过
|
2026-09-18 21:24:30 +08:00 |
|
snowy-yang
|
fcd4cb4649
|
style: 引入 ruff,474 项告警清零
配置见 pyproject.toml:
- line-length=100:贴着现有代码定。若用默认的 88 会重排 122 行,
噪音淹没真正有价值的修复。
- 启用 E/W/F/I/UP/B/C4/SIM/RET/PERF/RUF;**不**启用 PTH。因为本项目的路径
多是"字符串身份"——要塞进 subprocess 命令行、写进 JSON 存档、打印给用户,
全换 pathlib 只是包一层再拆一层,换来 70 处纯风格改动。
- 忽略 RUF001/002/003:中文全角标点是正确写法,不是"歧义字符"。
- 忽略 PERF203:剩下两处都是"逐项容错"语义(逐个牧场存档要立刻上报失败、
收获全部要跳过失败地块)。把 try 提到循环外会毁掉"一项失败不影响其它项"
这个行为,是错的优化。
自动修复 358 项:Dict/List/Optional 换成 PEP 585/604 写法、导入排序、
失效的 noqa、空占位 f-string 等。
手工修复的(这些是真问题,不是风格偏好):
- B904 x9:except 中 raise 补异常链。刻意区分——网络/IO 失败用 from exc 保留
根因;参数解析失败用 from None,因为底层 ValueError 对玩家只是噪音。
- RUF012 x2:StateView 的 plots/animals 原本是**类级共享可变列表**,
改成在 __init__ 里初始化(apply({}) 会提前返回,所以必须显式摆好)。
- F841:TUI 里一个赋值后再没用过的死变量。
- SIM105 x11:try/except/pass 统一改成 contextlib.suppress。
- SIM115:日志句柄改用 with。Popen 已把 fd 复制给子进程,父进程可安全关闭。
- E501 x7:拆超长行;main.py 那处顺手提取变量,避免超长嵌套表达式。
- E741:把 l/r 改成 left_line/right_line。
- 其余:RUF046 / PERF401 / RET504 / RUF005 / UP031 / B007。
ruff format 重排 24 个文件。
验证:97 项测试全绿。
|
2026-09-18 21:18:22 +08:00 |
|
snowy-yang
|
c7c8a6308a
|
chore: 统一行尾为 LF,清理临时文件
新增 .gitattributes 把文本文件钉死为 LF:否则 git 在 Windows 的 autocrlf
会把仓库的 LF 转成工作区 CRLF,与 ruff format 的 line-ending=auto 互相打架,
每次格式化都在改行尾。
|
2026-09-18 21:13:01 +08:00 |
|
snowy-yang
|
a7725f8812
|
chore: 工程化改造前的基线快照
在引入 uv 依赖管理、ruff 静态检查与格式化、pyright 类型检查之前,
先把当前可正常运行的状态固定下来,作为后续大规模自动改动的回滚点。
此时状态:97 项测试全绿,HTTP + WebSocket 多牧场服务器可用。
|
2026-09-18 21:12:40 +08:00 |
|