using Godot;
using MegaCrit.Sts2.Core.Combat;
using MegaCrit.Sts2.Core.Entities.Players;
using MegaCrit.Sts2.Core.Nodes.Combat;
using System;
using System.Linq;
namespace VulnerabilityIndicator.Scripts.UI;
///
/// UI indicator shown on the combat UI that tracks vulnerability
/// card status for each player in multiplayer.
/// Attaches to NCombatUi and positions indicators near each player's panel.
///
public partial class VulnerabilityPlayerIndicator : Control
{
private VBoxContainer? _container;
private VulnerabilityChecker? _checker;
private NCombatUi? _combatUi;
private readonly Dictionary _playerIcons = new();
private const string IconPath = "res://images/icons/vuln_indicator_icon.png";
public override void _Ready()
{
_container = new VBoxContainer
{
SizeFlagsHorizontal = Control.SizeFlags.ShrinkBegin,
SizeFlagsVertical = Control.SizeFlags.Fill,
};
AddChild(_container);
Visible = false;
}
public override void _Process(double delta)
{
if (_checker == null || _combatUi == null) return;
bool anyVisible = false;
foreach (var kvp in _playerIcons)
{
var icon = kvp.Value;
bool hasVuln = _checker.PlayerHasVulnerabilityCard(kvp.Key);
icon.Visible = hasVuln;
if (hasVuln)
anyVisible = true;
}
Visible = anyVisible;
}
///
/// Called by the node attachment system when the parent NCombatUi is ready.
///
public void Bind(NCombatUi parent)
{
_combatUi = parent;
_checker = VulnerabilityChecker.Instance;
if (_checker == null || _container == null)
{
Visible = false;
return;
}
// Create an indicator for each player
foreach (var player in _checker.GetAllPlayers())
{
var icon = new TextureRect
{
Texture = (Texture2D?)LoadIcon(),
Size = new Vector2(20f, 20f),
StretchMode = TextureRect.StretchModeEnum.Keep,
Visible = false,
CustomMinimumSize = new Vector2(22f, 22f),
};
// Try to get a player name for the tooltip
var playerName = GetPlayerName(player);
icon.TooltipText = $"{playerName} has a vulnerability card";
_container.AddChild(icon);
_playerIcons[player] = icon;
}
}
private string GetPlayerName(Player player)
{
try
{
var playerType = player.GetType();
foreach (var propName in new[] { "Name", "DisplayName", "PlayerName", "NetName" })
{
var prop = playerType.GetProperty(propName,
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
if (prop != null)
{
var value = prop.GetValue(player);
if (value is string name && !string.IsNullOrEmpty(name))
return name;
}
}
}
catch { }
return "Player";
}
private CombatState? GetCombatState()
{
if (_combatUi == null) return null;
try
{
var type = _combatUi.GetType();
foreach (var propName in new[] { "CombatState", "Combat", "State", "GameState" })
{
var prop = type.GetProperty(propName,
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance);
if (prop != null)
{
var value = prop.GetValue(_combatUi);
if (value is CombatState cs)
return cs;
}
}
}
catch { }
return null;
}
private Texture2D? LoadIcon()
{
try
{
var tex = GD.Load(IconPath);
if (tex != null) return tex;
}
catch { }
return GenerateVulnerabilityIcon();
}
///
/// Generates a simple vulnerability icon programmatically.
///
private static Texture2D GenerateVulnerabilityIcon()
{
var size = 32;
var img = Image.Create(size, size, false, Image.Format.Rgba8);
img.Fill(new Color(0f, 0f, 0f, 0f));
var fill = new Color(0.8f, 0.15f, 0.15f, 0.9f);
// Draw shield polygon
var shieldPoints = new (int X, int Y)[]
{
(size / 2, 1),
(size - 3, size / 4),
(size - 3, size / 2),
(size / 2, size - 2),
(3, size / 2),
(3, size / 4),
};
var minX = shieldPoints.Min(p => p.X);
var maxX = shieldPoints.Max(p => p.X);
var minY = shieldPoints.Min(p => p.Y);
var maxY = shieldPoints.Max(p => p.Y);
for (int y = minY; y <= maxY; y++)
{
for (int x = minX; x <= maxX; x++)
{
if (PointInPolygon(x, y, shieldPoints))
img.SetPixel(x, y, fill);
}
}
// Lightning crack
var crack = new Color(1f, 0.9f, 0.1f, 1f);
var crackPoints = new (int X, int Y)[]
{
(size / 2 + 1, 4),
(size / 2 + 3, 10),
(size / 2 - 1, 12),
(size / 2 + 2, 13),
(size / 2 - 1, 18),
(size / 2 + 1, 28),
};
for (int i = 0; i < crackPoints.Length - 1; i++)
DrawLineThick(img, crack, crackPoints[i], crackPoints[i + 1], 2);
var texture = ImageTexture.CreateFromImage(img);
return texture;
}
private static bool PointInPolygon(int px, int py, (int X, int Y)[] polygon)
{
bool inside = false;
for (int i = 0, j = polygon.Length - 1; i < polygon.Length; j = i++)
{
var pi = polygon[i];
var pj = polygon[j];
if (((pi.Y > py) != (pj.Y > py)) &&
(px < (pj.X - pi.X) * (py - pi.Y) / (float)(pj.Y - pi.Y) + pi.X))
inside = !inside;
}
return inside;
}
private static void DrawLineThick(Image img, Color color, (int X, int Y) from, (int X, int Y) to, int thickness)
{
for (int tx = -thickness; tx <= thickness; tx++)
{
for (int ty = -thickness; ty <= thickness; ty++)
{
DrawLine(img, color, (from.X + tx, from.Y + ty), (to.X + tx, to.Y + ty));
}
}
}
private static void DrawLine(Image img, Color color, (int X, int Y) from, (int X, int Y) to)
{
int dx = Math.Abs(to.X - from.X);
int dy = Math.Abs(to.Y - from.Y);
int sx = from.X < to.X ? 1 : -1;
int sy = from.Y < to.Y ? 1 : -1;
int err = dx - dy;
int x = from.X;
int y = from.Y;
while (true)
{
if (x >= 0 && x < img.GetWidth() && y >= 0 && y < img.GetHeight())
img.SetPixel(x, y, color);
if (x == to.X && y == to.Y) break;
int e2 = 2 * err;
if (e2 > -dy) { err -= dy; x += sx; }
if (e2 < dx) { err += dx; y += sy; }
}
}
}