Skip to content
Draft
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using Content.Client.Gameplay;
using Content.Client.UserInterface.Systems.Gameplay;
using Content.Pirate.Shared._JustDecor.MartialArts.Components;
using Robust.Shared.Player;
using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controllers;
using Robust.Shared.GameObjects;

namespace Content.Pirate.Client._JustDecor.MartialArts;

/// <summary>
/// UI контролер для керування ComboHelperWidget.
/// Слухає зміни стану компонента та оновлює віджет відповідно.
/// </summary>
public sealed class ComboHelperUIController : UIController, IOnStateEntered<GameplayState>
{
[Dependency] private readonly IPlayerManager _player = default!;

private ComboHelperWidget? Widget => UIManager.GetActiveUIWidgetOrNull<ComboHelperWidget>();

public override void Initialize()
{
base.Initialize();

var gameplayLoad = UIManager.GetUIController<GameplayStateLoadController>();
gameplayLoad.OnScreenLoad += OnScreenLoad;
gameplayLoad.OnScreenUnload += OnScreenUnload;

// Підписуємось на події компонента
EntityManager.EventBus.SubscribeLocalEvent<ComboHelperComponent, AfterAutoHandleStateEvent>(OnHandleState);
EntityManager.EventBus.SubscribeLocalEvent<ComboHelperComponent, ComponentAdd>(OnComponentAdd);
EntityManager.EventBus.SubscribeLocalEvent<ComboHelperComponent, ComponentRemove>(OnComponentRemove);

// LocalPlayerAttachedEvent - це глобальна подія
SubscribeLocalEvent<LocalPlayerAttachedEvent>(OnPlayerAttached);
}

private void OnComponentAdd(EntityUid uid, ComboHelperComponent component, ComponentAdd args)
{
if (uid != _player.LocalEntity)
return;

UpdateWidget();
}

private void OnComponentRemove(EntityUid uid, ComboHelperComponent component, ComponentRemove args)
{
if (uid != _player.LocalEntity)
return;


// При видаленні компонента - видаляємо віджет
RemoveWidget();
}

private void OnPlayerAttached(LocalPlayerAttachedEvent ev)
{
UpdateWidget();
}

private void OnHandleState(EntityUid uid, ComboHelperComponent component, ref AfterAutoHandleStateEvent args)
{
if (uid != _player.LocalEntity)
return;

UpdateWidget();
}

private void OnScreenLoad()
{
UpdateWidget();
}

private void OnScreenUnload()
{
RemoveWidget();
}

public void OnStateEntered(GameplayState state)
{
UpdateWidget();
}

private void UpdateWidget()
{
if (_player.LocalEntity is not { } player)
{
RemoveWidget();
return;
}

if (!EntityManager.TryGetComponent<ComboHelperComponent>(player, out var component))
{
RemoveWidget();
return;
}

var screen = UIManager.ActiveScreen;
if (screen == null)
{
RemoveWidget();
return;
}

if (!screen.TryGetWidget<ComboHelperWidget>(out var widget))
widget = screen.GetOrAddWidget<ComboHelperWidget>();
if (widget == null)
return;

widget.SetPositionLast();
widget.UpdateFromComponent(component);
}

private void RemoveWidget()
{
var screen = UIManager.ActiveScreen;
if (screen == null)
return;

if (screen.TryGetWidget<ComboHelperWidget>(out _))
{
screen.RemoveWidget<ComboHelperWidget>();
}
}
}
177 changes: 177 additions & 0 deletions Content.Pirate.Client/_JustDecor/MartialArts/ComboHelperWidget.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
using Content.Client.UserInterface.Controls;
using Content.Pirate.Shared._JustDecor.MartialArts.Components;
using Content.Goobstation.Common.MartialArts;
using Robust.Client.Graphics;
using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using System.Numerics;

namespace Content.Pirate.Client._JustDecor.MartialArts;

public sealed class ComboHelperWidget : UIWidget
{
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly IEntityManager _entManager = default!;

private SpriteSystem _spriteSystem = default!;
private readonly BoxContainer _container;

public ComboHelperWidget()
{
IoCManager.InjectDependencies(this);
_spriteSystem = _entManager.System<SpriteSystem>();

LayoutContainer.SetAnchorAndMarginPreset(this, LayoutContainer.LayoutPreset.CenterBottom, margin: 5);

// Встановлюємо віджет на весь екран, але він ігнорує миші
MouseFilter = MouseFilterMode.Ignore;

// Контейнер для рядків комбо
_container = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Bottom,
Margin = new Thickness(0), // Відступ від нижнього правого кута
SeparationOverride = 5,
MouseFilter = MouseFilterMode.Ignore
};

AddChild(_container);
}

public void UpdateFromComponent(ComboHelperComponent component)
{
// Очищуємо попередній контент
_container.DisposeAllChildren();

// Якщо віджет вимкнено або немає прототипу - ховаємо
if (!component.Enabled || component.Prototype == null)
{
Visible = false;
return;
}

// Намагаємося отримати прототип helper
if (!_proto.TryIndex(component.Prototype.Value, out CqcComboHelperPrototype? helperProto))
{
Visible = false;
return;
}

// Якщо немає комбо - ховаємо
if (helperProto.Combos.Count == 0)
{
Visible = false;
return;
}

Visible = true;

// Створюємо UI для кожного комбо
foreach (var entry in helperProto.Combos)
{

if (!_proto.TryIndex(entry.ComboId, out ComboPrototype? comboProto))
{
continue;
}

CreateComboRow(entry, comboProto);
}
}

private void CreateComboRow(ComboHelperEntry entry, ComboPrototype comboProto)
{
// Створюємо горизонтальний рядок для комбо
var row = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
SeparationOverride = 2,
HorizontalAlignment = HAlignment.Center,
MouseFilter = MouseFilterMode.Ignore
};

// Додаємо назву комбо
var labelText = string.IsNullOrEmpty(entry.Name)
? Loc.GetString(comboProto.Name)
: Loc.GetString(entry.Name);

var label = new Label
{
Text = labelText,
Margin = new Thickness(0, 0, 5, 0),
VerticalAlignment = VAlignment.Center,
MouseFilter = MouseFilterMode.Ignore
};
row.AddChild(label);

// Використовуємо кастомні іконки якщо є, інакше генеруємо з типів атак
var icons = entry.Icons;
if (icons == null || icons.Count == 0)
{
icons = GetDefaultIcons(comboProto.AttackTypes);
}

// Додаємо іконки
foreach (var icon in icons)
{
var texture = TryGetTexture(icon);
if (texture == null)
{
continue;
}

var rect = new TextureRect
{
TextureScale = new Vector2(0.5f, 0.5f),
Stretch = TextureRect.StretchMode.KeepAspectCentered,
MinSize = new Vector2(24, 24),
Texture = texture,
MouseFilter = MouseFilterMode.Ignore
};
row.AddChild(rect);
}

_container.AddChild(row);
}

private Texture? TryGetTexture(SpriteSpecifier icon)
{
try
{
return _spriteSystem.Frame0(icon);
}
catch (Exception ex)
{
Logger.ErrorS("ComboHelper", $"Failed to load texture: {ex.Message}");
return null;
}
}

private List<SpriteSpecifier> GetDefaultIcons(List<ComboAttackType> attacks)
{
var icons = new List<SpriteSpecifier>();
var basePath = new ResPath("/Textures/_Goobstation/Interface/Misc/intents.rsi");

foreach (var attack in attacks)
{
var state = attack switch
{
ComboAttackType.Grab => "grab",
ComboAttackType.Disarm => "disarm",
ComboAttackType.Harm => "harm",
_ => "help"
};

icons.Add(new SpriteSpecifier.Rsi(basePath, state));
}

return icons;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Utility;
using Robust.Shared.GameStates;
using Content.Goobstation.Common.MartialArts;

namespace Content.Pirate.Shared._JustDecor.MartialArts.Components;

[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)]
public sealed partial class ComboHelperComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("prototype"), AutoNetworkedField]
public ProtoId<CqcComboHelperPrototype>? Prototype;

[ViewVariables(VVAccess.ReadWrite), DataField("enabled"), AutoNetworkedField]
public bool Enabled = true;

[DataField("toggleAction")]
public EntityUid? ToggleAction;
}

[Prototype("cqcComboHelper")]
public sealed partial class CqcComboHelperPrototype : IPrototype
{
[IdDataField] public string ID { get; private set; } = default!;

[DataField("combos")]
public List<ComboHelperEntry> Combos = new();

/// <summary>
/// Texture for the background of the helper.
/// </summary>
[DataField("background")]
public SpriteSpecifier? Background;
}

[DataDefinition]
public sealed partial class ComboHelperEntry
{
[DataField("comboId", required: true)]
public ProtoId<ComboPrototype> ComboId = default!;

[DataField("name")]
public string? Name;

[DataField("icons")]
public List<SpriteSpecifier>? Icons;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using Content.Shared.Actions;
using Robust.Shared.Serialization;

namespace Content.Pirate.Shared._JustDecor.MartialArts.Events;

public sealed partial class CqcComboHelperToggleEvent : InstantActionEvent
{
}
Loading