using System.Collections.Generic;
using Luneraria.Core;
namespace Luneraria.UI
{
///
/// Deterministic lane threat for one team (port of scripts/luneraria/ui/lune_lane_threat.gd),
/// read only from public state: building HP, the units on the battlefield (positions and HP,
/// which every client also sees) and the public announcements of this round. It never reads
/// drafts, gold, unannounced bookings or bot notes, so host and guest compute the same level.
/// Positions are projected onto the lane, because a network guest has positions but no lane
/// progress.
///
/// RUHIG nothing below applies
/// UMKÄMPFT a lane tower is below 85 %, an enemy attack champion is announced here, or
/// enemy units stand in the own half of the lane
/// UNTER DRUCK the Grenzwacht fell or is below 50 %, enemies attack the Grenzwacht, the enemy
/// has more than 1.5 times the own unit HP in the own half, or the enemy boss
/// is deployed on this lane
/// KRITISCH both lane towers fell (base open), the Bastion is below 50 % after the
/// Grenzwacht fell, enemies attack the Bastion, or the enemy boss comes while the
/// Grenzwacht is already down
///
public static class LuneLaneThreat
{
public const int Calm = 0;
public const int Contested = 1;
public const int Pressure = 2;
public const int Critical = 3;
public static readonly string[] Names = { "RUHIG", "UMKÄMPFT", "UNTER DRUCK", "KRITISCH" };
public static readonly string[] Glyphs = { "check", "melee", "warning", "warning" };
public sealed class Result
{
public int Level;
public string Name = "";
public readonly List Reasons = new List();
}
public sealed class Battle
{
public double Length;
public int OwnCount;
public int EnemyCount;
public double OwnHp;
public double EnemyHp;
/// Deepest enemy position measured from the own base along the lane (infinity without enemies).
public double EnemyDepth = double.PositiveInfinity;
public double OwnFront;
}
static void Building(LuneMatch state, string id, out bool alive, out double fraction)
{
double maxHp = state.Map.BuildingDef(id)?.MaxHp ?? 1.0;
if (state.Phase == Phase.Combat && state.Combat != null && state.Combat.Buildings.TryGetValue(id, out LuneUnit unit))
{
alive = unit.Alive;
fraction = alive ? GdMath.Clamp(unit.Hp / GdMath.Max(1.0, maxHp), 0.0, 1.0) : 0.0;
return;
}
double hp = state.BuildingHp.GetValueOrDefault(id, 0.0);
alive = hp > 0.0;
fraction = GdMath.Clamp(hp / GdMath.Max(1.0, maxHp), 0.0, 1.0);
}
static string Percent(double value)
{
return LuneUiTheme.Round(value * 100.0) + " %";
}
public static Result Evaluate(LuneMatch state, int team, string lane, ICollection hidden = null)
{
var result = new Result();
int level = Calm;
string prefix = team == 0 ? "blau" : "rot";
int enemy = 1 - team;
Building(state, prefix + "_" + lane + "_outer", out bool outerAlive, out double outer);
Building(state, prefix + "_" + lane + "_inner", out bool innerAlive, out double inner);
if (!outerAlive && !innerAlive)
{
level = Critical;
result.Reasons.Add("Grenzwacht und Bastion gefallen – der Weg zur Basis ist offen");
}
else if (!outerAlive)
{
level = Pressure;
result.Reasons.Add("Grenzwacht gefallen");
if (inner < .5)
{
level = Critical;
result.Reasons.Add("Bastion bei " + Percent(inner));
}
else if (inner < .85)
{
result.Reasons.Add("Bastion bei " + Percent(inner));
}
}
else if (outer < .5)
{
level = Pressure;
result.Reasons.Add("Grenzwacht bei " + Percent(outer));
}
else if (outer < .85)
{
level = Contested;
result.Reasons.Add("Grenzwacht bei " + Percent(outer));
}
foreach (Announcement entry in state.Announcements())
{
if (entry.Team != enemy || entry.Lane != lane)
continue;
if (entry.Kind == "boss")
{
level = System.Math.Max(level, !outerAlive ? Critical : Pressure);
result.Reasons.Add("Gegnerische Schlundkrone greift hier an");
}
else
{
level = System.Math.Max(level, Contested);
result.Reasons.Add("Angriffschampion " + LuneContent.Champions[entry.ChampionId].Name + " angekündigt");
}
}
if (state.Phase == Phase.Combat && state.Combat != null)
{
Battle battle = Battlefield(state, team, lane, hidden);
double length = battle.Length;
if (battle.EnemyCount > 0)
{
double deepest = battle.EnemyDepth;
double innerAt = length * state.Balance.InnerTowerFraction;
double outerAt = length * state.Balance.OuterTowerFraction;
double reach = state.Balance.TowerRange * .8;
if (deepest <= innerAt + reach)
{
level = Critical;
result.Reasons.Add("Gegner greifen die Bastion oder die Basis an");
}
else if (deepest <= outerAt + reach && outerAlive)
{
level = System.Math.Max(level, Pressure);
result.Reasons.Add("Gegner greifen die Grenzwacht an");
}
else if (deepest <= length * .5)
{
level = System.Math.Max(level, Contested);
result.Reasons.Add("Gegner in der eigenen Lanehälfte");
}
if (battle.EnemyHp > battle.OwnHp * 1.5 && deepest <= length * .5)
{
level = System.Math.Max(level, Pressure);
result.Reasons.Add(string.Format("Übermacht: {0} gegen {1} Einheiten", battle.EnemyCount, battle.OwnCount));
}
}
}
result.Level = level;
result.Name = Names[level];
return result;
}
///
/// Units of both sides on one lane: counts, HP sums and the deepest enemy position measured
/// from the own base along the lane (from positions only).
///
/// Units on the lane in combat; S3: enemies in the fog (hidden) are not counted.
public static Battle Battlefield(LuneMatch state, int team, string lane, ICollection hidden = null)
{
LanePath path = state.Map.LanePaths[lane];
var result = new Battle { Length = path.Length };
if (state.Combat == null)
return result;
foreach (LuneUnit unit in state.Combat.Units)
{
if (!unit.Alive || !unit.IsMobile() || unit.Zone != "lane" || unit.Lane != lane || unit.Team > 1)
continue;
if (hidden != null && hidden.Contains(unit.Id))
continue;
double s = path.Project(unit.Position);
double fromOwnBase = team == 0 ? s : path.Length - s;
if (unit.Team == team)
{
result.OwnCount++;
result.OwnHp += unit.Hp;
result.OwnFront = GdMath.Max(result.OwnFront, fromOwnBase);
}
else
{
result.EnemyCount++;
result.EnemyHp += unit.Hp;
result.EnemyDepth = GdMath.Min(result.EnemyDepth, fromOwnBase);
}
}
return result;
}
}
}