261 lines
8.3 KiB
C#
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");
|
|
}
|
|
}
|