0.2.0: Server-Browser und Spiel-Ticket

- Seite Server: Liste der Website, Ping/Spieler direkt abgefragt, BEITRETEN und Direkt verbinden
- Holt beim Spielen ein Spiel-Ticket und schreibt es in BSO.json

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 18:10:55 +01:00
co-authored by Claude Opus 5.5
parent 077e39fa8a
commit fb8d6d6844
8 changed files with 174 additions and 7 deletions
+64 -1
View File
@@ -140,9 +140,12 @@ function showPage(name) {
document.querySelectorAll("#nav [data-page]").forEach((b) => b.classList.toggle("on", b.dataset.page === name));
const root = $("page");
root.replaceChildren(h("p", { class: "muted" }, "Laden …"));
({ home: renderHome, news: renderNews, settings: renderSettings })[name](root);
({ home: renderHome, servers: renderServers, news: renderNews, settings: renderSettings })[name](root);
}
// Anmeldung abgelaufen (z.B. beim Holen des Spiel-Tickets)
on("loggedOut", () => showAuth());
// Fortschritt (Downloads)
let progressBox = null;
on("progress", (m) => {
@@ -240,6 +243,66 @@ function serverCard(s) {
h("label", { class: "switch" }, h("span", {}, "Beim Start verbinden", h("small", {}, "sonst im Spiel mit F8")), auto));
}
// ---------------------------------------------------------------- Server-Browser
let serverList = [];
async function renderServers(root) {
const search = h("input", { placeholder: "Server suchen …", spellcheck: "false" });
const refresh = h("button", { class: "btn small", type: "button" }, "Aktualisieren");
const list = h("div", { class: "server-list" }, h("p", { class: "muted" }, "Server werden gesucht …"));
const direct = h("input", { placeholder: "Adresse, z.B. bso.example.de:7777", spellcheck: "false" });
const directJoin = h("button", { class: "btn small", type: "button" }, "Verbinden");
directJoin.addEventListener("click", () => join(directJoin, direct.value));
root.replaceChildren(
h("div", { class: "row", style: "justify-content:space-between;flex-wrap:wrap" }, h("h1", {}, "Server"),
h("div", { class: "row", style: "min-width:320px" }, search, refresh)),
list,
h("div", { class: "card" }, h("h3", {}, "Direkt verbinden"), h("div", { class: "row" }, direct, directJoin)));
const draw = () => {
const q = search.value.trim().toLowerCase();
const rows = serverList.filter((s) => !q || s.name.toLowerCase().includes(q) || s.address.toLowerCase().includes(q));
if (!rows.length) {
list.replaceChildren(h("p", { class: "muted" }, serverList.length ? "Kein Server passt zur Suche." : "Gerade ist kein öffentlicher Server online."));
return;
}
list.replaceChildren(...rows.map((s) => {
const b = h("button", { class: "btn primary small", type: "button" }, "BEITRETEN");
b.disabled = !s.compatible || s.players >= s.maxPlayers;
b.addEventListener("click", () => join(b, s.address));
const ping = s.ping == null ? h("span", { class: "muted" }, "–") : h("span", { class: s.ping < 80 ? "ok" : s.ping < 150 ? "warn" : "bad" }, s.ping + " ms");
return h("div", { class: "server" },
h("div", { class: "server-main" }, h("b", {}, s.name),
h("span", { class: "muted small" }, s.description || s.address)),
h("span", { class: "server-col" }, `${s.players}/${s.maxPlayers}`),
h("span", { class: "server-col" }, ping),
h("span", { class: "server-col muted small" }, s.compatible ? "v" + s.version : "andere Version"),
b);
}));
};
const load = async () => {
refresh.disabled = true;
try { serverList = await call("servers"); draw(); }
catch (e) { list.replaceChildren(h("p", { class: "error" }, e.message)); }
finally { refresh.disabled = false; }
};
search.addEventListener("input", draw);
refresh.addEventListener("click", load);
await load();
}
/** Beitreten: Server merken, Spiel starten, im Spiel automatisch verbinden. */
function join(button, address) {
if (!address.trim()) { toast("Adresse eingeben", true); return; }
busy(button, async () => {
await call("join", { address: address.trim() });
toast("Bus-Simulator 2012 startet und verbindet mit " + address.trim() + " …");
});
}
async function renderNews(root) {
let list;
try { list = await call("releases"); }