移除行动点系统,倒计时本地走秒,字体优化
为什么:游戏的时间(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:
1 parent
92b1a628b1
commit
15d290a60a
18 files changed
+82
-179
No files matched your search
+2
-4
@@ -115,8 +115,6 @@ BRIEF_KEYS = (
|
||||
"level",
|
||||
"exp",
|
||||
"exp_needed",
|
||||
"ap",
|
||||
"max_ap",
|
||||
"feed",
|
||||
"plot_count",
|
||||
"max_plots",
|
||||
@@ -158,8 +156,8 @@ def rules_text() -> str:
|
||||
"==========================================\n"
|
||||
f"1. 时间:每个牧场每 {config.DEFAULT_TICK_SECONDS / 60:.0f} 分钟"
|
||||
f"({config.DEFAULT_TICK_SECONDS:.0f} 秒)自动推进一天,可配置。"
|
||||
f"每天开始时行动点恢复为 {config.START_AP} 点,作物生长、牲畜产出、市场价格都在此刻结算。\n"
|
||||
f"2. 行动点:播种/浇水/喂食各消耗 1 点,收获与收取产出免费。行动点用完只能等第二天——"
|
||||
f"作物生长、牲畜产出、市场价格都在此刻结算。\n"
|
||||
"2. 节奏:动作没有行动点/体力限制,想种就种——稀缺的只有时间本身。"
|
||||
"时间只能自然流动,没有任何「手动推进一天」的接口,别白费请求。\n"
|
||||
f"3. 天气与水分:每天随机一种天气(晴/多云/雨/干旱/暴风雨)。作物每天按天气蒸发"
|
||||
f" 0~2 点水分(上限 {config.WATER_CAPACITY}),水分为 0 时停止生长;"
|
||||
|
||||
+22
-9
@@ -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", () => {
|
||||
|
||||
Reference in new issue
Block a user