Delta-v/Content.Client/_DV/Traits/UI/TraitEntry.xaml.cs

261 lines
8.3 KiB
C#

using Content.Shared._DV.Traits;
using Content.Shared._DV.Traits.Conditions;
using Content.Shared.Humanoid.Prototypes;
using Content.Shared.Roles;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
namespace Content.Client._DV.Traits.UI;
[GenerateTypedNameReferences]
public sealed partial class TraitEntry : PanelContainer
{
[Dependency] private readonly IPrototypeManager _prototype = default!;
[Dependency] private readonly ILocalizationManager _loc = default!;
public event Action<bool>? OnToggled;
public bool IsSelected => TraitCheckbox.Pressed;
public readonly int TraitCost;
public bool MeetsConditions { get; private set; } = true;
private readonly TraitPrototype _trait;
private bool _isUpdating;
private readonly List<string> _failedConditionTooltips = new();
public TraitEntry(TraitPrototype trait)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
_trait = trait;
TraitCost = trait.Cost;
// Enable mouse events so tooltips work
MouseFilter = MouseFilterMode.Pass;
TraitNameLabel.Text = Loc.GetString(trait.Name);
TraitDescriptionLabel.SetMessage(Loc.GetString(trait.Description));
// Format cost display
var costPrefix = trait.Cost > 0 ? "+" : "";
var costColor = trait.Cost > 0 ? "#ff6b6b" : trait.Cost < 0 ? "#6bff6b" : "#888888";
TraitCostLabel.Text = $"{costPrefix}{trait.Cost}";
TraitCostLabel.ModulateSelfOverride = Color.FromHex(costColor);
TraitCheckbox.OnToggled += OnCheckboxToggled;
// Build condition tooltips
UpdateConditionTooltips();
}
private void UpdateConditionTooltips()
{
var tooltips = new List<string>();
foreach (var condition in _trait.Conditions)
{
var tooltip = condition.GetTooltip(_prototype, _loc);
if (!string.IsNullOrEmpty(tooltip))
tooltips.Add(tooltip);
}
if (tooltips.Count > 0)
{
var tooltipText = Loc.GetString("trait-conditions-tooltip",
("requirements", string.Join("\n", tooltips)));
TooltipSupplier = _ => CreateMarkupTooltip(tooltipText);
}
else
TooltipSupplier = null;
}
/// <summary>
/// Creates a tooltip control that properly parses markup.
/// </summary>
private static Tooltip CreateMarkupTooltip(string markupText)
{
var tooltip = new Tooltip();
// Parse the markup into a FormattedMessage
tooltip.SetMessage(FormattedMessage.FromMarkupOrThrow(markupText));
return tooltip;
}
/// <summary>
/// Updates whether conditions are met based on current job/species.
/// </summary>
public void UpdateConditionsMet(ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId)
{
_failedConditionTooltips.Clear();
MeetsConditions = true;
foreach (var condition in _trait.Conditions)
{
var result = condition switch
{
IsSpeciesCondition speciesCond => CheckSpeciesCondition(speciesCond, speciesId),
HasJobCondition jobCond => CheckJobCondition(jobCond, jobId),
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
HasCompCondition compCond => !compCond.Invert, // can't check in lobby but screws with the inversion logic
AnyOfCondition anyOfCond => CheckAnyOfCondition(anyOfCond, jobId, speciesId),
_ => true,
};
// Apply inversion
result ^= condition.Invert;
if (!result)
{
MeetsConditions = false;
var tooltip = condition.GetTooltip(_prototype, _loc);
if (!string.IsNullOrEmpty(tooltip))
_failedConditionTooltips.Add(tooltip);
}
}
UpdateDisabledState();
}
private bool CheckSpeciesCondition(IsSpeciesCondition condition, ProtoId<SpeciesPrototype>? speciesId)
{
if (!_prototype.TryIndex(speciesId, out var species))
return false;
return species == condition.Species;
}
private bool CheckJobCondition(HasJobCondition condition, ProtoId<JobPrototype>? jobId)
{
if (!_prototype.TryIndex(jobId, out var job))
return false;
return job == condition.Job;
}
private bool CheckDepartmentCondition(InDepartmentCondition condition, ProtoId<JobPrototype>? jobId)
{
if (!_prototype.TryIndex(jobId, out var job))
return false;
if (!_prototype.TryIndex(condition.Department, out var department))
return false;
return department.Roles.Contains(job);
}
private bool CheckAnyOfCondition(AnyOfCondition condition, ProtoId<JobPrototype>? jobId, ProtoId<SpeciesPrototype>? speciesId)
{
if (condition.Conditions.Count == 0)
return false;
// Return true if ANY child condition evaluates to true
foreach (var childCondition in condition.Conditions)
{
var result = childCondition switch
{
IsSpeciesCondition speciesCond => CheckSpeciesCondition(speciesCond, speciesId),
HasJobCondition jobCond => CheckJobCondition(jobCond, jobId),
InDepartmentCondition deptCond => CheckDepartmentCondition(deptCond, jobId),
HasCompCondition compCond => !compCond.Invert, // can't check in lobby
AnyOfCondition nestedAnyOf => CheckAnyOfCondition(nestedAnyOf, jobId, speciesId), // Recursive!
_ => true,
};
// Apply child's inversion
result ^= childCondition.Invert;
// If any child passes, the AnyOf passes
if (result)
return true;
}
// None of the children passed
return false;
}
private void UpdateDisabledState()
{
if (!MeetsConditions)
{
// Hide checkbox, show lock icon
TraitCheckbox.Visible = false;
LockIcon.Visible = true;
// Deselect if conditions no longer met
if (TraitCheckbox.Pressed)
{
_isUpdating = true;
TraitCheckbox.Pressed = false;
UpdateSelectedStyle();
_isUpdating = false;
OnToggled?.Invoke(false);
}
// Add disabled styling
AddStyleClass("TraitsEntryDisabled");
// Update tooltip to show failed conditions
if (_failedConditionTooltips.Count > 0)
{
var tooltipText = Loc.GetString("trait-conditions-not-met-tooltip",
("requirements", string.Join("\n", _failedConditionTooltips)));
TooltipSupplier = _ => CreateMarkupTooltip(tooltipText);
}
}
else
{
// Show checkbox, hide lock icon
TraitCheckbox.Visible = true;
LockIcon.Visible = false;
// Remove disabled styling - stylesheet restores normal colors
RemoveStyleClass("TraitsEntryDisabled");
// Reset to normal tooltips
UpdateConditionTooltips();
}
}
private void OnCheckboxToggled(BaseButton.ButtonToggledEventArgs args)
{
if (_isUpdating)
return;
if (!MeetsConditions)
{
// This shouldn't happen since checkbox is hidden, but just in case
_isUpdating = true;
TraitCheckbox.Pressed = false;
_isUpdating = false;
return;
}
UpdateSelectedStyle();
OnToggled?.Invoke(args.Pressed);
}
public void SetSelected(bool selected)
{
_isUpdating = true;
TraitCheckbox.Pressed = selected && MeetsConditions;
UpdateSelectedStyle();
_isUpdating = false;
}
private void UpdateSelectedStyle()
{
if (TraitCheckbox.Pressed)
AddStyleClass("TraitsEntrySelected");
else
RemoveStyleClass("TraitsEntrySelected");
}
}