initial commit

This commit is contained in:
Kamal Tufekcic 2026-07-05 12:14:39 +03:00
commit 2d966b8198
28 changed files with 2901 additions and 0 deletions

77
Services/Fmt.cs Normal file
View file

@ -0,0 +1,77 @@
using System.Text.Json;
namespace CsWeb.Services
{
// View helpers: tiny, dependency-free formatting shared by the pages. Content-only — no markup decisions here.
public static class Fmt
{
public static string ModeName(string? mode) => mode?.ToLowerInvariant() switch
{
"tdm" => "TDM",
"gungame" => "Gun Game",
"survival" => "Survival",
_ => mode ?? "?",
};
// Matches the plugin's win-banner format (m:ss.fff).
public static string RunTime(long ms) => $"{ms / 60000}:{ms / 1000 % 60:D2}.{ms % 1000:D3}";
public static string Age<T>(Cached<T> c) where T : class =>
c.Data is null ? "no data yet"
: c.Online ? "live"
: $"last seen {(int)c.Age.TotalSeconds}s ago";
// One-line mode summary for a server card, from the status payload's mode-specific Extra block.
public static string ExtraLine(StatusPayload s)
{
var e = s.Extra;
if (e.ValueKind != JsonValueKind.Object) return "";
return s.Mode switch
{
"survival" when e.TryGetProperty("RunActive", out var run) && run.GetBoolean()
&& e.TryGetProperty("Wave", out var w) && e.TryGetProperty("WaveCount", out var wc)
=> $"Wave {w.GetInt32()}/{wc.GetInt32()}",
"survival" => "Between runs",
"gungame" when e.TryGetProperty("Ladder", out var l) => $"{l.GetArrayLength()}-rung ladder",
"tdm" when e.TryGetProperty("KillGoal", out var kg) => $"First to {kg.GetInt32()} kills",
_ => "",
};
}
// A player's current GG weapon: their rung indexed into the ladder the GG driver ships in Extra.
public static string? RungWeapon(StatusPayload s, HumanRow h)
{
if (s.Mode != "gungame" || s.Extra.ValueKind != JsonValueKind.Object) return null;
if (!s.Extra.TryGetProperty("Ladder", out var l) || l.ValueKind != JsonValueKind.Array) return null;
int n = l.GetArrayLength();
if (n == 0) return null;
return l[Math.Clamp(h.Rung, 0, n - 1)].GetString();
}
// Best-effort display for raw weapon designer names in balance payloads (the site has no engine name table;
// status payloads carry pre-rendered display names, this is only for the guides' config-derived lists).
public static string PrettyWeapon(string w) =>
w.StartsWith("weapon_", StringComparison.Ordinal) ? w[7..].Replace('_', ' ').ToUpperInvariant() : w;
public static int IntOf(JsonElement obj, string prop, int fallback = 0) =>
obj.ValueKind == JsonValueKind.Object && obj.TryGetProperty(prop, out var v) && v.TryGetInt32(out int i) ? i : fallback;
public static double NumOf(JsonElement obj, string prop, double fallback = 0) =>
obj.ValueKind == JsonValueKind.Object && obj.TryGetProperty(prop, out var v) && v.ValueKind == JsonValueKind.Number ? v.GetDouble() : fallback;
public static bool BoolOf(JsonElement obj, string prop, bool fallback = false) =>
obj.ValueKind == JsonValueKind.Object && obj.TryGetProperty(prop, out var v) && (v.ValueKind == JsonValueKind.True || v.ValueKind == JsonValueKind.False) ? v.GetBoolean() : fallback;
public static string StrOf(JsonElement obj, string prop, string fallback = "") =>
obj.ValueKind == JsonValueKind.Object && obj.TryGetProperty(prop, out var v) && v.ValueKind == JsonValueKind.String ? v.GetString() ?? fallback : fallback;
// Survival card Detail strings are FORMAT TEMPLATES ("{0} = level x PerPick" per the card defs) that the in-game
// draft fills at render time. The guide shows the per-pick line, so format with one pick's worth.
public static string CardDetail(string template, double perPick)
{
if (string.IsNullOrEmpty(template)) return "";
try { return string.Format(System.Globalization.CultureInfo.InvariantCulture, template, perPick); }
catch (FormatException) { return template; } // malformed template -> show raw rather than nothing
}
}
}