diff --git a/app.py b/app.py index ea42487..6cf6dc6 100644 --- a/app.py +++ b/app.py @@ -217,6 +217,7 @@ def roster(world: dict, now: float) -> list[dict]: "party": (player["action"] or {}).get("party"), "garrison": player["garrison"], "donated": player["donated"], "look": game.look_view(player), + "seen": int(player["last_active"]), }) return sorted(people, key=lambda p: -p["donated"]) diff --git a/game.py b/game.py index 1fa80af..86cd565 100644 --- a/game.py +++ b/game.py @@ -6084,6 +6084,7 @@ ORDER_RECIPES, ORDER_RAW = _order_sources() # line here (new / balance / fix), and a new release a new entry on top. PATCH_NOTES = [ {"date": "2026-10-26", "title": "Fittings", "notes": [ + ("new", "Guild Roster shows player activity."), ("new", "Make X: \"Make one\" now takes a count (1 by default, up to 1,000), then you go back to what you were doing."), ("new", "Construction › Fittings: ✓ Fitted on the ones the town already has (✓ Paused while the building is damaged)."), ]}, diff --git a/static/app.js b/static/app.js index e7e5c48..81c54d3 100644 --- a/static/app.js +++ b/static/app.js @@ -58,6 +58,11 @@ const serverNow = () => Date.now() / 1000 + skew; const item = (id) => C.items[id] || { name: id, icon: "❔", value: 0 }; const skillOf = (id) => C.skills.find((s) => s.id === id); const playerName = (uid) => S.players.find((p) => p.id === uid)?.name || uid; +// An open tab polls every 10s and a hidden one stops, so a quiet minute means they've gone. +const ONLINE_S = 45; +const online = (p) => p.id === S.uid || serverNow() - p.seen < ONLINE_S; +const presence = (p) => online(p) ? `` : ""; +const lastSeen = (p) => online(p) ? "" : ` · seen ${ago(p.seen)}`; function remember(key, value) { try { @@ -713,7 +718,7 @@ function sidebar() { ${nav("council", "📜", "Council & threats", "", raid ? `` : "", raid ? "A threat is coming" : "")} ${nav("orders", ORDER_ICON(), "Work orders", "", takeable ? `${takeable}` : "", takeable ? `${takeable} for you` : "")} ${nav("board", "💬", "Town board")} - ${nav("roster", "👥", "Guild roster", `${S.players.length}`, "", `${S.players.length} ${S.players.length === 1 ? "adventurer" : "adventurers"}`)} + ${rosterNav()} ${nav("notes", "📰", "Patch notes", "", notesUnread() ? `new` : "", notesUnread() ? `What's new: ${C.patch_notes[0].title}` : "")}
${nav("character", cls.icon, "Character", `${skillOf("combat").icon} ${me.levels.combat}`, "", `Combat level ${me.levels.combat}`)} @@ -2420,12 +2425,18 @@ function joinButton(p) { return ` `; } +function rosterNav() { + const on = S.players.filter(online).length, others = S.players.some((p) => p.id !== S.uid && online(p)); + return nav("roster", "👥", "Guild roster", `${on}/${S.players.length}`, others ? `` : "", `${on} of ${S.players.length} online`); +} + function roster() { - return `