Roster shows who's online
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f735831a1e
commit
718e440e2b
4 changed files with 19 additions and 5 deletions
1
app.py
1
app.py
|
|
@ -217,6 +217,7 @@ def roster(world: dict, now: float) -> list[dict]:
|
||||||
"party": (player["action"] or {}).get("party"),
|
"party": (player["action"] or {}).get("party"),
|
||||||
"garrison": player["garrison"], "donated": player["donated"],
|
"garrison": player["garrison"], "donated": player["donated"],
|
||||||
"look": game.look_view(player),
|
"look": game.look_view(player),
|
||||||
|
"seen": int(player["last_active"]),
|
||||||
})
|
})
|
||||||
return sorted(people, key=lambda p: -p["donated"])
|
return sorted(people, key=lambda p: -p["donated"])
|
||||||
|
|
||||||
|
|
|
||||||
1
game.py
1
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.
|
# line here (new / balance / fix), and a new release a new entry on top.
|
||||||
PATCH_NOTES = [
|
PATCH_NOTES = [
|
||||||
{"date": "2026-10-26", "title": "Fittings", "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", "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)."),
|
("new", "Construction › Fittings: ✓ Fitted on the ones the town already has (✓ Paused while the building is damaged)."),
|
||||||
]},
|
]},
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,11 @@ const serverNow = () => Date.now() / 1000 + skew;
|
||||||
const item = (id) => C.items[id] || { name: id, icon: "❔", value: 0 };
|
const item = (id) => C.items[id] || { name: id, icon: "❔", value: 0 };
|
||||||
const skillOf = (id) => C.skills.find((s) => s.id === id);
|
const skillOf = (id) => C.skills.find((s) => s.id === id);
|
||||||
const playerName = (uid) => S.players.find((p) => p.id === uid)?.name || uid;
|
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) ? `<span class="online" title="Online"></span>` : "";
|
||||||
|
const lastSeen = (p) => online(p) ? "" : ` · seen ${ago(p.seen)}`;
|
||||||
|
|
||||||
function remember(key, value) {
|
function remember(key, value) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -713,7 +718,7 @@ function sidebar() {
|
||||||
${nav("council", "📜", "Council & threats", "", raid ? `<span class="alert"></span>` : "", raid ? "A threat is coming" : "")}
|
${nav("council", "📜", "Council & threats", "", raid ? `<span class="alert"></span>` : "", raid ? "A threat is coming" : "")}
|
||||||
${nav("orders", ORDER_ICON(), "Work orders", "", takeable ? `<span class="badge">${takeable}</span>` : "", takeable ? `${takeable} for you` : "")}
|
${nav("orders", ORDER_ICON(), "Work orders", "", takeable ? `<span class="badge">${takeable}</span>` : "", takeable ? `${takeable} for you` : "")}
|
||||||
${nav("board", "💬", "Town board")}
|
${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() ? `<span class="badge">new</span>` : "", notesUnread() ? `What's new: ${C.patch_notes[0].title}` : "")}
|
${nav("notes", "📰", "Patch notes", "", notesUnread() ? `<span class="badge">new</span>` : "", notesUnread() ? `What's new: ${C.patch_notes[0].title}` : "")}
|
||||||
<div class="navhead">You</div>
|
<div class="navhead">You</div>
|
||||||
${nav("character", cls.icon, "Character", `${skillOf("combat").icon} ${me.levels.combat}`, "", `Combat level ${me.levels.combat}`)}
|
${nav("character", cls.icon, "Character", `${skillOf("combat").icon} ${me.levels.combat}`, "", `Combat level ${me.levels.combat}`)}
|
||||||
|
|
@ -2420,12 +2425,18 @@ function joinButton(p) {
|
||||||
return ` <button class="btn sm primary" data-join="${esc(p.id)}" ${why ? `disabled title="${esc(why)}"` : ""}>⚔️ Join</button>`;
|
return ` <button class="btn sm primary" data-join="${esc(p.id)}" ${why ? `disabled title="${esc(why)}"` : ""}>⚔️ Join</button>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 ? `<span class="dot"></span>` : "", `${on} of ${S.players.length} online`);
|
||||||
|
}
|
||||||
|
|
||||||
function roster() {
|
function roster() {
|
||||||
return `<div class="grid wide">${S.players.map((p) => {
|
const ordered = [...S.players].sort((a, b) => online(b) - online(a)); // stable: by donations within each
|
||||||
|
return `<div class="grid wide">${ordered.map((p) => {
|
||||||
const cls = C.classes[p.cls];
|
const cls = C.classes[p.cls];
|
||||||
const levels = C.skills.filter((s) => !s.class || s.class === p.cls).map((s) => `<div class="lvl ${p.levels[s.id] <= 1 ? "low" : ""}" title="${s.name} level ${p.levels[s.id]}"><span class="ico">${s.icon}</span><span class="nm">${s.name}</span><b>${p.levels[s.id]}</b></div>`).join("");
|
const levels = C.skills.filter((s) => !s.class || s.class === p.cls).map((s) => `<div class="lvl ${p.levels[s.id] <= 1 ? "low" : ""}" title="${s.name} level ${p.levels[s.id]}"><span class="ico">${s.icon}</span><span class="nm">${s.name}</span><b>${p.levels[s.id]}</b></div>`).join("");
|
||||||
return `<div class="panel person"><div class="top"><span class="ico">${cls.icon}</span><div>
|
return `<div class="panel person"><div class="top"><span class="ico">${cls.icon}</span><div>
|
||||||
${titled(p)} <span class="muted small">${cls.name} · total level ${p.total} · ${esc(p.look.home)}</span>
|
${presence(p)}${titled(p)} <span class="muted small">${cls.name} · total level ${p.total} · ${esc(p.look.home)}${lastSeen(p)}</span>
|
||||||
<div class="small">${p.doing ? `${skillOf(p.skill).icon} ${esc(p.doing)}` : `<span class="muted">Idle</span>`}${p.garrison ? ` · <span class="tag warn">On guard</span>` : ""}</div>
|
<div class="small">${p.doing ? `${skillOf(p.skill).icon} ${esc(p.doing)}` : `<span class="muted">Idle</span>`}${p.garrison ? ` · <span class="tag warn">On guard</span>` : ""}</div>
|
||||||
<div class="small favour">${FAVOUR} ${fmt(p.donated)} given to the city</div></div></div>
|
<div class="small favour">${FAVOUR} ${fmt(p.donated)} given to the city</div></div></div>
|
||||||
<div class="levels">${levels}</div>
|
<div class="levels">${levels}</div>
|
||||||
|
|
@ -2512,8 +2523,8 @@ function characterSheet(uid) {
|
||||||
<div class="panel sheet-head">
|
<div class="panel sheet-head">
|
||||||
<div class="big">${cls.icon}</div>
|
<div class="big">${cls.icon}</div>
|
||||||
<div class="grow">
|
<div class="grow">
|
||||||
<h2>${esc(p.name)}</h2>
|
<h2>${presence(p)}${esc(p.name)}</h2>
|
||||||
<div class="muted">${cls.name} · total level ${total} · ${days === 1 ? "first day" : `${days} days`} in the city</div>
|
<div class="muted">${cls.name} · total level ${total} · ${days === 1 ? "first day" : `${days} days`} in the city${lastSeen(p)}</div>
|
||||||
<div class="small" style="margin-top:6px">${p.doing ? `${skillOf(p.skill).icon} ${esc(p.doing)}` : `<span class="muted">Idle</span>`}${p.garrison ? ` · <span class="tag warn">On guard</span>` : ""}</div>
|
<div class="small" style="margin-top:6px">${p.doing ? `${skillOf(p.skill).icon} ${esc(p.doing)}` : `<span class="muted">Idle</span>`}${p.garrison ? ` · <span class="tag warn">On guard</span>` : ""}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,7 @@ a { color: var(--xp); }
|
||||||
.nav .label { flex: 1; }
|
.nav .label { flex: 1; }
|
||||||
.nav .meta { color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; }
|
.nav .meta { color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; }
|
||||||
.nav .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--good); box-shadow: 0 0 6px var(--good); }
|
.nav .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--good); box-shadow: 0 0 6px var(--good); }
|
||||||
|
.online { display: inline-block; width: 8px; height: 8px; margin-right: 6px; vertical-align: middle; border-radius: 50%; background: var(--good); box-shadow: 0 0 6px var(--good); }
|
||||||
.nav .alert { width: 8px; height: 8px; border-radius: 50%; background: var(--bad); }
|
.nav .alert { width: 8px; height: 8px; border-radius: 50%; background: var(--bad); }
|
||||||
|
|
||||||
.devbar {
|
.devbar {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue