移除行动点系统,倒计时本地走秒,字体优化

为什么:游戏的时间(40 分钟一天、只能自然流动)本身就是唯一的行动节奏
阀门,再叠一层"每天 10 点行动点"纯属重复惩罚——浇水还要体力既不拟真
也不好玩。删掉之后,稀缺的只剩时间本身,放置玩法的定位反而更纯粹。

改动范围(引擎到界面连根拔):
- config 删 START_AP/AP_COST;models 删 ap 字段与 max_ap,存档 payload
  里的旧 ap 键被 from_dict 自然忽略;
- engine 删 _spend_ap 与全部 8 个调用点、每日恢复逻辑与日志;
  available_actions 不再按行动点筛,cost 只剩金币;
- storage 建表去掉 ap 列,老库在 connect() 里 ALTER TABLE DROP COLUMN
  无痛迁移(实测 saves/farm.db 已自动迁完);
- bot 删 AP 分支(无 AP 后 available_actions 恒可用);client/TUI/CLI/
  README/帮助文本同步清理。

网页端顺带两件事:
- "下一天"倒计时改为客户端本地走秒——SSE 只在有变化时推送,以前倒计时
  只在推送瞬间跳变,现在每秒都在动(记基点 + 1s 定时器补齐);
- 字体优化:页面字体栈补齐 MiSans/HarmonyOS Sans/PingFang,正文字号
  14→15,统计数字 tabular-nums;canvas 小字统一字体栈并加粗提对比度。

测试适配:删 2 个 AP 专属用例(无 AP 可测)、改名/收敛 5 个,
532 项全绿(skip 39),ruff/pyright 干净。真机验证:老库迁移、
统计条无行动点、倒计时逐秒递减(39:04→39:02)、动作不再报 no_ap。
This commit is contained in:
snowy-yang committed 2026-09-25 22:44:08 +08:00
1 parent 92b1a628b1
commit 15d290a60a
18 files changed
+82 -179

No files matched your search

+22 -9
View File
@@ -27,7 +27,8 @@ DASHBOARD_HTML = r"""<!DOCTYPE html>
* { box-sizing: border-box; }
body {
margin: 0; background: var(--bg); color: var(--ink);
font: 14px/1.6 "Segoe UI", "Microsoft YaHei", system-ui, sans-serif;
font: 15px/1.6 "Segoe UI Variable Text", "Segoe UI", "MiSans",
"HarmonyOS Sans SC", "Microsoft YaHei", "PingFang SC", system-ui, sans-serif;
}
header {
display: flex; flex-wrap: wrap; gap: 12px; align-items: center;
@@ -53,7 +54,7 @@ DASHBOARD_HTML = r"""<!DOCTYPE html>
main { padding: 16px 20px 40px; max-width: 1180px; margin: 0 auto; }
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(118px, 1fr)); gap: 10px; }
.stat { background: var(--card); border: 1px solid var(--line); border-radius: 12px; padding: 10px 12px; }
.stat b { display: block; font-size: 18px; }
.stat b { display: block; font-size: 18px; font-variant-numeric: tabular-nums; }
.stat small { color: var(--dim); }
.cols { display: grid; grid-template-columns: 1.35fr 1fr; gap: 14px; margin-top: 14px; }
@media (max-width: 900px) { .cols { grid-template-columns: 1fr; } }
@@ -206,6 +207,7 @@ DASHBOARD_HTML = r"""<!DOCTYPE html>
<script>
const $ = (id) => document.getElementById(id);
let es = null, state = null, catalog = null, pollTimer = null, me = null;
let tickLeft = null, tickStamp = 0;
const cropNames = {
wheat: "小麦", carrot: "胡萝卜", tomato: "番茄",
@@ -361,12 +363,16 @@ function render() {
["第几天", s.day],
["天气", (s.weather_name || "—") + " → " + (s.forecast_name || "?")],
["金币", s.gold], ["等级", "Lv." + s.level + " (" + s.exp + "/" + s.exp_needed + ")"],
["行动点", s.ap + "/" + s.max_ap], ["饲料", s.feed],
["饲料", s.feed],
["仓储", (s.storage_used || 0) + "/" + (s.granary_capacity || 0)],
["净资产", Math.round(s.net_worth)],
["在线", s.online !== undefined ? s.online : "—"],
["下一天", fmtLeft(s.seconds_to_tick)]
].map((p) => '<div class="stat"><small>' + p[0] + '</small><b>' + p[1] + '</b></div>').join("");
["下一天", fmtLeft(s.seconds_to_tick), "tickcell"]
].map((p) => '<div class="stat"><small>' + p[0] + '</small><b' +
(p[2] ? ' id="' + p[2] + '"' : '') + '>' + p[1] + '</b></div>').join("");
/* 倒计时基点:SSE 只在有变化时推送,中间的每一秒由本地定时器补齐 */
tickLeft = (s.seconds_to_tick === undefined || s.seconds_to_tick === null) ? null : s.seconds_to_tick;
tickStamp = Date.now();
/* 农田 */
$("plots").innerHTML = s.plots.map((p) => {
@@ -629,7 +635,7 @@ function mapDrawTile(p, x, y, ts) {
c.restore();
} else {
c.fillStyle = "rgba(60,40,20,.5)";
c.font = '11px "Segoe UI",sans-serif';
c.font = '600 11px "Segoe UI","Microsoft YaHei",sans-serif';
c.textAlign = "center"; c.textBaseline = "middle";
c.fillText("空地", x + ts / 2, y + ts / 2);
}
@@ -648,7 +654,7 @@ function mapDrawTile(p, x, y, ts) {
mapRoundRect(x - 1.5, y - 1.5, ts + 3, ts + 3, 8); c.stroke();
}
c.fillStyle = "rgba(255,255,255,.75)";
c.font = '10px "Segoe UI",sans-serif';
c.font = '600 10px "Segoe UI",sans-serif';
c.textAlign = "left"; c.textBaseline = "top";
c.fillText(String(p.position), x + 4, y + 3);
}
@@ -707,7 +713,7 @@ function mapDraw(now) {
c.fillRect(pen.x + (pen.w / posts) * i - 2, pen.y + pen.h - 5, 4, 10);
}
c.fillStyle = "rgba(60,45,25,.75)";
c.font = '11px "Segoe UI","Microsoft YaHei",sans-serif';
c.font = '600 11px "Segoe UI","Microsoft YaHei",sans-serif';
c.textAlign = "center"; c.textBaseline = "top";
c.fillText("牧场", pen.x + pen.w / 2, pen.y + 3);
(MAP.walkers || []).forEach((wd) => {
@@ -746,7 +752,7 @@ function mapDraw(now) {
c.font = '22px "Segoe UI Emoji","Noto Color Emoji",sans-serif';
c.textAlign = "center"; c.textBaseline = "top";
c.fillText(info[0], cx, y0 + 4);
c.fillStyle = "#3a4a33"; c.font = '10px "Segoe UI","Microsoft YaHei",sans-serif';
c.fillStyle = "#3a4a33"; c.font = '600 11px "Segoe UI","Microsoft YaHei",sans-serif';
c.fillText(kv[1] > 1 ? info[1] + " ×" + kv[1] : info[1], cx, y0 + 32);
});
}
@@ -943,6 +949,13 @@ $("logout").addEventListener("click", async () => {
applyIdentity();
$("loginname").focus();
});
/* 倒计时本地走秒:状态只在动作/推送时到达,中间的每一秒自己补 */
setInterval(() => {
if (tickLeft === null) return;
const el = $("tickcell");
if (el) el.textContent = fmtLeft(Math.max(0, tickLeft - (Date.now() - tickStamp) / 1000));
}, 1000);
/* ---- 启动 ---- */
$("token").value = lsGet("sf.token") || "";
$("token").addEventListener("change", () => {