Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions client/i18n/strings.csv
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ editor.theme_parkinglot,"Parking Lot","Aparcamiento","Parkeringsplads"
editor.spawn,"Spawn","Aparición","Spawn"
editor.scale,"Size","Tamaño","Størrelse"
stats.repairs,"Repairs","Reparaciones","Reparationer"
stats.standing,"Standing","Clasificación","Stilling"
net.team_label,"Team {0}","Equipo {0}","Hold {0}"
stats.assists,"Assists","Asistencias","Assists"
editor.assets,"Assets","Recursos","Aktiver"
editor.search,"Search...","Buscar...","Søg..."
Expand Down
36 changes: 36 additions & 0 deletions client/src/GameLogic/AuthoredTeleporter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System.Collections.Generic;
using System.Numerics;
using TankGame.Domain;

namespace TankGame.GameLogic;

/// <summary>Builds the <see cref="Teleporter"/> for a map's authored pad links (a custom map's, or
/// Cliffs' cross-layer pair): each cell becomes a world-space pad on whatever elevation layer the
/// grid gives that cell — the layer is derived, never authored separately, so pad data stays plain
/// cells and cannot disagree with the map. Shared by the local and networked arenas: pads are static
/// map features every peer derives identically from the same resolved map, so nothing about them
/// travels on the wire. The returned pad list is in link order (each link's A then B), matching
/// <see cref="Teleporter.PadStatuses"/>, so views built in this order mirror state by index.</summary>
public static class AuthoredTeleporter
{
public static (Teleporter Teleporter, IReadOnlyList<TeleportPad> Pads) Build(
IReadOnlyList<TeleportPadLink> links, IWallGrid grid, float tileSize, Vector2 origin, float padRadius)
{
var pairs = new List<(TeleportPad, TeleportPad)>(links.Count);
var pads = new List<TeleportPad>(links.Count * 2);
foreach (var link in links)
{
var a = PadAt(link.AX, link.AY, grid, tileSize, origin);
var b = PadAt(link.BX, link.BY, grid, tileSize, origin);
pairs.Add((a, b));
pads.Add(a);
pads.Add(b);
}

return (new Teleporter(pairs, padRadius), pads);
}

private static TeleportPad PadAt(int x, int y, IWallGrid grid, float tileSize, Vector2 origin) => new(
new Vector2(origin.X + ((x + 0.5f) * tileSize), origin.Y + ((y + 0.5f) * tileSize)),
grid.LayerAt(x, y));
}
1 change: 1 addition & 0 deletions client/src/GameLogic/AuthoredTeleporter.cs.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://xetjssd4o6se
102 changes: 102 additions & 0 deletions client/src/GameLogic/NetMatchStats.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

namespace TankGame.GameLogic;

/// <summary>The networked match's stat book — the net victory screen's data source. No stats travel
/// on the wire: both roles feed the same per-tank hp stream (the host from its authoritative world
/// each tick, a guest from every snapshot's tank states), so damage taken, repairs and the final
/// standing all derive identically on every peer from observed hp deltas. Pure C#.</summary>
public sealed class NetMatchStats
{
/// <summary>One slot's running tally. Mutable bookkeeping the screen reads at match end.</summary>
public sealed class SlotTally
{
public byte Slot { get; init; }
public string Name { get; set; } = string.Empty;
public int Team { get; set; }
public int Hp { get; internal set; }
public bool Alive { get; internal set; } = true;
public int DamageTaken { get; internal set; }
public int Repairs { get; internal set; }

/// <summary>When this tank fell, as a monotonically increasing sequence — higher means it
/// outlived more of the field. Zero while alive.</summary>
internal int EliminatedAt { get; set; }

internal bool Baselined { get; set; }
}

private readonly Dictionary<byte, SlotTally> _bySlot = new();
private readonly List<SlotTally> _ordered = new(); // registration order = the roster's seat order
private int _eliminationSeq;

/// <summary>Every observed slot's tally, in registration order.</summary>
public IReadOnlyList<SlotTally> Tallies => _ordered;

/// <summary>Seats a slot with its roster name and team before play (both roles derive the same
/// roster, so both books carry the same rows).</summary>
public void Register(byte slot, string name, int team)
{
var tally = GetOrCreate(slot);
tally.Name = name;
tally.Team = team;
}

/// <summary>One hp sighting for a slot. The first sighting is the baseline; every later drop is
/// damage taken, every rise a repair, and the drop to zero stamps the elimination order.</summary>
public void Observe(byte slot, int hp)
{
var tally = GetOrCreate(slot);
if (!tally.Baselined)
{
tally.Baselined = true;
tally.Hp = hp;
tally.Alive = hp > 0;
return;
}

if (hp < tally.Hp)
{
tally.DamageTaken += tally.Hp - hp;
}
else if (hp > tally.Hp)
{
tally.Repairs += hp - tally.Hp;
}

tally.Hp = hp;
if (tally.Alive && hp <= 0)
{
tally.Alive = false;
tally.EliminatedAt = ++_eliminationSeq;
}
}

/// <summary>The final standing: survivors first (healthiest on top), then the fallen ranked by
/// how long they lasted — a later death places higher.</summary>
public IReadOnlyList<SlotTally> Standings()
=> _ordered
.OrderByDescending(t => t.Alive)
.ThenByDescending(t => t.Alive ? t.Hp : t.EliminatedAt)
.ToList();

private SlotTally GetOrCreate(byte slot)
{
if (!_bySlot.TryGetValue(slot, out var tally))
{
// A slot the roster never named (a stray snapshot) still gets a readable row.
tally = new SlotTally
{
Slot = slot,
Name = string.Format(CultureInfo.InvariantCulture, "Tank {0}", slot + 1),
Team = slot,
};
_bySlot[slot] = tally;
_ordered.Add(tally);
}

return tally;
}
}
1 change: 1 addition & 0 deletions client/src/GameLogic/NetMatchStats.cs.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://c1ob3bglya8yv
Loading
Loading