diff --git a/Content.Pirate.Client/_JustDecor/MartialArts/ComboHelperUIController.cs b/Content.Pirate.Client/_JustDecor/MartialArts/ComboHelperUIController.cs new file mode 100644 index 000000000000..ff2a1dd83e0f --- /dev/null +++ b/Content.Pirate.Client/_JustDecor/MartialArts/ComboHelperUIController.cs @@ -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; + +/// +/// UI контролер для керування ComboHelperWidget. +/// Слухає зміни стану компонента та оновлює віджет відповідно. +/// +public sealed class ComboHelperUIController : UIController, IOnStateEntered +{ + [Dependency] private readonly IPlayerManager _player = default!; + + private ComboHelperWidget? Widget => UIManager.GetActiveUIWidgetOrNull(); + + public override void Initialize() + { + base.Initialize(); + + var gameplayLoad = UIManager.GetUIController(); + gameplayLoad.OnScreenLoad += OnScreenLoad; + gameplayLoad.OnScreenUnload += OnScreenUnload; + + // Підписуємось на події компонента + EntityManager.EventBus.SubscribeLocalEvent(OnHandleState); + EntityManager.EventBus.SubscribeLocalEvent(OnComponentAdd); + EntityManager.EventBus.SubscribeLocalEvent(OnComponentRemove); + + // LocalPlayerAttachedEvent - це глобальна подія + SubscribeLocalEvent(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(player, out var component)) + { + RemoveWidget(); + return; + } + + var screen = UIManager.ActiveScreen; + if (screen == null) + { + RemoveWidget(); + return; + } + + if (!screen.TryGetWidget(out var widget)) + widget = screen.GetOrAddWidget(); + if (widget == null) + return; + + widget.SetPositionLast(); + widget.UpdateFromComponent(component); + } + + private void RemoveWidget() + { + var screen = UIManager.ActiveScreen; + if (screen == null) + return; + + if (screen.TryGetWidget(out _)) + { + screen.RemoveWidget(); + } + } +} diff --git a/Content.Pirate.Client/_JustDecor/MartialArts/ComboHelperWidget.cs b/Content.Pirate.Client/_JustDecor/MartialArts/ComboHelperWidget.cs new file mode 100644 index 000000000000..028f020c0eff --- /dev/null +++ b/Content.Pirate.Client/_JustDecor/MartialArts/ComboHelperWidget.cs @@ -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(); + + 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 GetDefaultIcons(List attacks) + { + var icons = new List(); + 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; + } +} diff --git a/Content.Pirate.Shared/_JustDecor/MartialArts/Components/ComboHelperComponent.cs b/Content.Pirate.Shared/_JustDecor/MartialArts/Components/ComboHelperComponent.cs new file mode 100644 index 000000000000..c2251ac2e223 --- /dev/null +++ b/Content.Pirate.Shared/_JustDecor/MartialArts/Components/ComboHelperComponent.cs @@ -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? 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 Combos = new(); + + /// + /// Texture for the background of the helper. + /// + [DataField("background")] + public SpriteSpecifier? Background; +} + +[DataDefinition] +public sealed partial class ComboHelperEntry +{ + [DataField("comboId", required: true)] + public ProtoId ComboId = default!; + + [DataField("name")] + public string? Name; + + [DataField("icons")] + public List? Icons; +} diff --git a/Content.Pirate.Shared/_JustDecor/MartialArts/Events/CqcComboHelperEvents.cs b/Content.Pirate.Shared/_JustDecor/MartialArts/Events/CqcComboHelperEvents.cs new file mode 100644 index 000000000000..7341073459c5 --- /dev/null +++ b/Content.Pirate.Shared/_JustDecor/MartialArts/Events/CqcComboHelperEvents.cs @@ -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 +{ +} diff --git a/Content.Pirate.Shared/_JustDecor/MartialArts/Systems/ComboHelperIntegrationSystem.cs b/Content.Pirate.Shared/_JustDecor/MartialArts/Systems/ComboHelperIntegrationSystem.cs new file mode 100644 index 000000000000..a480ec577061 --- /dev/null +++ b/Content.Pirate.Shared/_JustDecor/MartialArts/Systems/ComboHelperIntegrationSystem.cs @@ -0,0 +1,94 @@ +using Content.Pirate.Shared._JustDecor.MartialArts.Components; +using Content.Goobstation.Shared.MartialArts.Components; +using Robust.Shared.Prototypes; + +namespace Content.Pirate.Shared._JustDecor.MartialArts.Systems; + +/// +/// Допоміжна система для інтеграції ComboHelper з різними martial arts системами. +/// Надає методи для управління ComboHelper компонентом з інших систем. +/// +public sealed class ComboHelperIntegrationSystem : EntitySystem +{ + [Dependency] private readonly IPrototypeManager _proto = default!; + + /// + /// Додає ComboHelper компонент до entity та налаштовує його з заданим прототипом. + /// + public bool TryAddComboHelper(EntityUid uid, string helperPrototypeId, bool enabled = true) + { + // Перевіряємо, чи існує прототип + if (!_proto.HasIndex(helperPrototypeId)) + { + Logger.WarningS("ComboHelper", $"Tried to add ComboHelper with non-existent prototype: {helperPrototypeId}"); + return false; + } + + // Додаємо або отримуємо існуючий компонент + var helper = EnsureComp(uid); + helper.Prototype = helperPrototypeId; + helper.Enabled = enabled; + + Logger.DebugS("ComboHelper", $"Added ComboHelper to {uid} with prototype {helperPrototypeId}"); + Dirty(uid, helper); + + return true; + } + + /// + /// Видаляє ComboHelper компонент з entity. + /// + public void RemoveComboHelper(EntityUid uid) + { + if (RemComp(uid)) + { + Logger.DebugS("ComboHelper", $"Removed ComboHelper from {uid}"); + } + } + + /// + /// Оновлює прототип helper для існуючого ComboHelper компонента. + /// + public bool TryUpdateHelperPrototype(EntityUid uid, string newPrototypeId, ComboHelperComponent? component = null) + { + if (!Resolve(uid, ref component, false)) + { + Logger.WarningS("ComboHelper", $"Tried to update helper prototype on {uid} without ComboHelperComponent"); + return false; + } + + if (!_proto.HasIndex(newPrototypeId)) + { + Logger.WarningS("ComboHelper", $"Tried to update to non-existent prototype: {newPrototypeId}"); + return false; + } + + component.Prototype = newPrototypeId; + Logger.DebugS("ComboHelper", $"Updated helper prototype for {uid} to {newPrototypeId}"); + + Dirty(uid, component); + return true; + } + + /// + /// Перевіряє, чи має entity активний ComboHelper. + /// + public bool HasActiveHelper(EntityUid uid, ComboHelperComponent? component = null) + { + if (!Resolve(uid, ref component, false)) + return false; + + return component.Enabled && component.Prototype != null; + } + + /// + /// Отримує ID прототипу helper для entity, якщо він існує. + /// + public string? GetHelperPrototypeId(EntityUid uid, ComboHelperComponent? component = null) + { + if (!Resolve(uid, ref component, false)) + return null; + + return component.Prototype; + } +} \ No newline at end of file diff --git a/Content.Pirate.Shared/_JustDecor/MartialArts/Systems/ComboHelperSystem.cs b/Content.Pirate.Shared/_JustDecor/MartialArts/Systems/ComboHelperSystem.cs new file mode 100644 index 000000000000..80e1c0f32599 --- /dev/null +++ b/Content.Pirate.Shared/_JustDecor/MartialArts/Systems/ComboHelperSystem.cs @@ -0,0 +1,94 @@ +using Content.Shared.Actions; +using Content.Pirate.Shared._JustDecor.MartialArts.Components; +using Content.Pirate.Shared._JustDecor.MartialArts.Events; +using Robust.Shared.GameStates; +using Robust.Shared.Network; + +namespace Content.Pirate.Shared._JustDecor.MartialArts.Systems; + +/// +/// Shared система для керування ComboHelper компонентом. +/// Відповідає за toggle функціональність та синхронізацію стану. +/// +public sealed class ComboHelperSystem : EntitySystem +{ + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly INetManager _netManager = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnShutdown); + SubscribeLocalEvent(OnToggle); + } + + private void OnStartup(EntityUid uid, ComboHelperComponent component, ComponentStartup args) + { + + // Додаємо action для toggle якщо його немає + if (component.ToggleAction == null) + { + _actions.AddAction(uid, ref component.ToggleAction, "ActionCqcComboHelperToggle"); + Dirty(uid, component); + } + } + + private void OnShutdown(EntityUid uid, ComboHelperComponent component, ComponentShutdown args) + { + + // Видаляємо action при видаленні компонента + if (component.ToggleAction != null) + { + _actions.RemoveAction(uid, component.ToggleAction); + } + } + + private void OnToggle(EntityUid uid, ComboHelperComponent component, CqcComboHelperToggleEvent args) + { + if (!_netManager.IsServer) + return; + + component.Enabled = !component.Enabled; + + Dirty(uid, component); + args.Handled = true; + } + + /// + /// Встановлює прототип helper для entity. + /// Використовується зовнішніми системами (наприклад, martial arts системами). + /// + public void SetHelperPrototype(EntityUid uid, string prototypeId, ComboHelperComponent? component = null) + { + if (!Resolve(uid, ref component, false)) + { + Logger.WarningS("ComboHelper", $"Tried to set helper prototype on {uid} without ComboHelperComponent"); + return; + } + + component.Prototype = prototypeId; + + Dirty(uid, component); + } + + /// + /// Вмикає або вимикає helper для entity. + /// + public void SetEnabled(EntityUid uid, bool enabled, ComboHelperComponent? component = null) + { + if (!Resolve(uid, ref component, false)) + { + Logger.WarningS("ComboHelper", $"Tried to set enabled state on {uid} without ComboHelperComponent"); + return; + } + + if (component.Enabled == enabled) + return; + + component.Enabled = enabled; + + Dirty(uid, component); + } +} diff --git a/Resources/Locale/uk-UA/_Pirate/Clothing/slot-blocker.ftl b/Resources/Locale/uk-UA/_Pirate/Clothing/slot-blocker.ftl index 28e23344aad3..139597f9cb07 100644 --- a/Resources/Locale/uk-UA/_Pirate/Clothing/slot-blocker.ftl +++ b/Resources/Locale/uk-UA/_Pirate/Clothing/slot-blocker.ftl @@ -1,24 +1,2 @@ -slot-blocker-blocked-generic = Ви повинні зняти {THE($blocker)}, щоб зробити це! -slot-blocker-blocked-equipped = Ви повинні зняти {THE($blocker)}, щоб одягнути це! -slot-blocker-blocked-unequipped = Ви повинні зняти {THE($blocker)}, щоб зняти це! -slot-blocker-examine-blocks = Це може блокувати наступні слоти при екіпіруванні: [bold]{$slots}[/bold] -slot-blocker-examine-blocked-by = Це може бути заблоковано наступними слотами: [bold]{$slots}[/bold] -# Named generic slot flags. Add more if more get added, but do not add slot flags that are not supposed to be named (e.g. WITHOUT_POCKET) -# We do not use the names from InventoryTemplate because those character-specific and they suck. -slot-name-HEAD = голова -slot-name-EYES = очі -slot-name-EARS = вуха -slot-name-MASK = маска -slot-name-OUTERCLOTHING = верхній одяг -slot-name-INNERCLOTHING = нижній одяг -slot-name-NECK = шия -slot-name-BACK = спина -slot-name-BELT = пояс -slot-name-GLOVES = руки -slot-name-IDCARD = КПК -slot-name-POCKET = кишені -slot-name-LEGS = ноги -slot-name-FEET = стопи -slot-name-SUITSTORAGE = зберігання костюма diff --git a/Resources/Prototypes/_Pirate/_JustDecor/MartialArts/cqc_helper.yml b/Resources/Prototypes/_Pirate/_JustDecor/MartialArts/cqc_helper.yml new file mode 100644 index 000000000000..11f49803f717 --- /dev/null +++ b/Resources/Prototypes/_Pirate/_JustDecor/MartialArts/cqc_helper.yml @@ -0,0 +1,13 @@ +- type: cqcComboHelper + id: BigBosCqcHelper + combos: + - comboId: BigBosCQCTakedown + - comboId: BigBosCQCDisarm + - comboId: BigBosCQCThrow + - comboId: BigBosCQCChoke + - comboId: BigBosCQCChain + - comboId: BigBosCQCCounter + - comboId: BigBosCQCInterrogation + - comboId: BigBosCQCStealthTakedown + - comboId: BigBosCQCRush + - comboId: BigBosCQCFinisher