fish-station/Content.Client/_Sunrise/InteractionsPanel/CustomInteractionEditor.xaml.cs
KaiserMaus 11cca765b8
Fix "ыыыы" prototype edition (#3514)
Co-authored-by: iertis <kanopus952@gmail.com>
Co-authored-by: Daniel | Orvex07 <d.b.orvex@gmail.com>
2025-12-27 18:23:10 +00:00

593 lines
18 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Linq;
using System.Numerics;
using Content.Client._Sunrise.InteractionsPanel.Models;
using Content.Shared._Sunrise.InteractionsPanel.Data.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Client.Graphics;
using Robust.Client.Utility;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Content.Client.Stylesheets;
namespace Content.Client._Sunrise.InteractionsPanel;
[GenerateTypedNameReferences]
public sealed partial class CustomInteractionEditor : DefaultWindow
{
#region Color Scheme
private static readonly Color PrimaryColor = new Color(0, 212, 255); // #00d4ff
private static readonly Color SecondaryColor = new Color(184, 230, 255); // #b8e6ff
private static readonly Color BackgroundDark = new Color(15, 20, 25); // #0f1419
private static readonly Color BackgroundLight = new Color(36, 51, 64); // #243340
private static readonly Color BackgroundHighlight = new Color(44, 68, 85); // #2c4455
private static readonly Color TextMuted = new Color(122, 157, 179); // #7a9db3
#endregion
#region Dependencies
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] protected readonly ILocalizationManager Loc = default!;
[Dependency] private readonly CustomInteractionService _customInteractionService = default!;
private readonly SharedAudioSystem _audio = default!;
#endregion
#region State
private CustomInteraction _interaction;
private Action<bool>? _onClose;
private readonly Dictionary<int, string> _categoryIds = new();
private readonly Dictionary<int, string> _iconIds = new();
private readonly Dictionary<int, string> _effectIds = new();
private readonly Dictionary<int, string> _soundIds = new();
private const int MaxNameLength = 64;
private const int MaxDescriptionLength = 256;
private const int MaxMessageLength = 128;
#endregion
#region Constructor
public CustomInteractionEditor(CustomInteraction? interaction = null)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
var iManager = IoCManager.Resolve<IEntityManager>();
_audio = iManager.EntitySysManager.GetEntitySystem<SharedAudioSystem>();
var isNewInteraction = interaction == null;
_interaction = interaction ?? new CustomInteraction
{
Id = string.Empty,
Name = string.Empty,
Description = string.Empty,
IconId = string.Empty,
CategoryId = string.Empty,
InteractionMessages = new List<string>(),
SoundIds = new List<string>(),
SpawnsEffect = false,
EffectChance = 0.5f,
EntityEffectId = string.Empty,
Cooldown = 5.0f
};
Title = isNewInteraction ? "Создание взаимодействия" : "Редактирование взаимодействия";
InitializeUI();
InitializeEvents();
LoadData();
}
#endregion
#region Initialization
private void InitializeUI()
{
EffectChanceSlider.OnValueChanged += OnEffectChanceChanged;
CooldownSlider.OnValueChanged += OnCooldownChanged;
AddMessageButton.OnPressed += OnAddMessagePressed;
AddSoundButton.OnPressed += OnAddSoundPressed;
SaveButton.OnPressed += OnSavePressed;
CancelButton.OnPressed += OnCancelPressed;
TestSoundButton.OnPressed += OnTestSoundPressed;
SpawnsEffectCheckBox.OnToggled += OnSpawnsEffectToggled;
CategoryOption.OnItemSelected += OnCategorySelected;
IconOption.OnItemSelected += OnIconSelected;
EffectOption.OnItemSelected += OnEffectSelected;
SoundOption.OnItemSelected += OnSoundSelected;
UpdateEffectChanceLabel(EffectChanceSlider.Value);
UpdateCooldownLabel(CooldownSlider.Value);
}
private void OnCategorySelected(OptionButton.ItemSelectedEventArgs args)
{
CategoryOption.SelectId(args.Id);
}
private void OnIconSelected(OptionButton.ItemSelectedEventArgs args)
{
IconOption.SelectId(args.Id);
UpdateIconPreview();
}
private void OnEffectSelected(OptionButton.ItemSelectedEventArgs args)
{
EffectOption.SelectId(args.Id);
}
private void OnSoundSelected(OptionButton.ItemSelectedEventArgs args)
{
SoundOption.SelectId(args.Id);
}
private void InitializeEvents()
{
SpawnsEffectCheckBox.OnToggled += _ => UpdateEffectControlsEnabled();
}
private void LoadData()
{
_categoryIds.Clear();
_iconIds.Clear();
_effectIds.Clear();
_soundIds.Clear();
CategoryOption.Clear();
int categoryId = 0;
CategoryOption.AddItem("Выберите категорию");
_categoryIds[categoryId] = string.Empty;
categoryId++;
foreach (var category in _prototypeManager.EnumeratePrototypes<InteractionCategoryPrototype>().OrderBy(c => Loc.GetString(c.Name)))
{
_categoryIds[categoryId] = category.ID;
CategoryOption.AddItem(Loc.GetString(category.Name), categoryId);
if (category.ID == _interaction.CategoryId)
{
CategoryOption.SelectId(categoryId);
}
categoryId++;
}
IconOption.Clear();
int iconId = 0;
IconOption.AddItem("Нет");
_iconIds[iconId] = string.Empty;
iconId++;
foreach (var icon in _prototypeManager.EnumeratePrototypes<InteractionIconPrototype>().OrderBy(i => Loc.GetString(i.Name)))
{
_iconIds[iconId] = icon.ID;
IconOption.AddItem(Loc.GetString(icon.Name), iconId);
if (icon.ID == _interaction.IconId)
{
IconOption.SelectId(iconId);
}
iconId++;
}
EffectOption.Clear();
int effectId = 0;
EffectOption.AddItem("Нет");
_effectIds[effectId] = string.Empty;
effectId++;
foreach (var effect in _prototypeManager.EnumeratePrototypes<InteractionEntityEffectPrototype>().OrderBy(e => Loc.GetString(e.Name)))
{
_effectIds[effectId] = effect.ID;
EffectOption.AddItem(Loc.GetString(effect.Name), effectId);
if (effect.ID == _interaction.EntityEffectId)
{
EffectOption.SelectId(effectId);
}
effectId++;
}
SoundOption.Clear();
int soundId = 0;
SoundOption.AddItem("Выберите звук");
_soundIds[soundId] = string.Empty;
soundId++;
foreach (var sound in _prototypeManager.EnumeratePrototypes<InteractionSoundPrototype>().OrderBy(s => Loc.GetString(s.Name)))
{
_soundIds[soundId] = sound.ID;
SoundOption.AddItem(Loc.GetString(sound.Name), soundId);
soundId++;
}
NameInput.Text = _interaction.Name;
DescriptionInput.Text = _interaction.Description;
SpawnsEffectCheckBox.Pressed = _interaction.SpawnsEffect;
EffectChanceSlider.Value = _interaction.EffectChance;
CooldownSlider.Value = _interaction.Cooldown;
MessagesContainer.DisposeAllChildren();
foreach (var message in _interaction.InteractionMessages)
{
AddMessageToList(message);
}
SoundsContainer.DisposeAllChildren();
foreach (var soundId2 in _interaction.SoundIds)
{
if (_prototypeManager.TryIndex<InteractionSoundPrototype>(soundId2, out var soundProto))
{
AddSoundToList(soundId2, soundProto.Name);
}
}
UpdateEffectControlsEnabled();
UpdateIconPreview();
}
#endregion
#region UI Updates
private void UpdateEffectChanceLabel(float value)
{
var percentage = (int)(value * 100);
EffectChanceLabel.Text = $"{percentage}%";
}
private void UpdateCooldownLabel(float value)
{
CooldownLabel.Text = $"{(int)value}с";
}
private void UpdateEffectControlsEnabled()
{
var enabled = SpawnsEffectCheckBox.Pressed;
EffectChanceSlider.Disabled = !enabled;
EffectOption.Disabled = !enabled;
EffectChanceLabel.Modulate = enabled ? Color.White : TextMuted;
}
private void UpdateIconPreview()
{
if (!_iconIds.TryGetValue(IconOption.SelectedId, out var iconId) || string.IsNullOrEmpty(iconId))
{
IconPreview.Texture = null;
return;
}
if (_prototypeManager.TryIndex<InteractionIconPrototype>(iconId, out var iconProto))
{
IconPreview.Texture = iconProto.Icon.Frame0();
}
}
#endregion
#region Event Handlers
private void OnEffectChanceChanged(Robust.Client.UserInterface.Controls.Range range)
{
UpdateEffectChanceLabel(range.Value);
}
private void OnCooldownChanged(Robust.Client.UserInterface.Controls.Range range)
{
UpdateCooldownLabel(range.Value);
}
private void OnSpawnsEffectToggled(BaseButton.ButtonToggledEventArgs args)
{
UpdateEffectControlsEnabled();
}
private void OnAddMessagePressed(BaseButton.ButtonEventArgs args)
{
var message = NewMessageInput.Text.Trim();
if (string.IsNullOrEmpty(message))
return;
if (message.Length > MaxMessageLength)
{
ShowError($"Сообщение слишком длинное (макс. {MaxMessageLength} символов)");
return;
}
_interaction.InteractionMessages.Add(message);
AddMessageToList(message);
NewMessageInput.Text = string.Empty;
}
private void OnAddSoundPressed(BaseButton.ButtonEventArgs args)
{
if (SoundOption.SelectedId == 0)
return;
if (!_soundIds.TryGetValue(SoundOption.SelectedId, out var selectedSoundId) || string.IsNullOrEmpty(selectedSoundId))
return;
if (_interaction.SoundIds.Contains(selectedSoundId))
return;
if (_prototypeManager.TryIndex<InteractionSoundPrototype>(selectedSoundId, out var soundProto))
{
_interaction.SoundIds.Add(selectedSoundId);
AddSoundToList(selectedSoundId, soundProto.Name);
}
}
private void OnTestSoundPressed(BaseButton.ButtonEventArgs args)
{
PlayTestSound();
}
private void OnSavePressed(BaseButton.ButtonEventArgs args)
{
if (string.IsNullOrEmpty(NameInput.Text.Trim()))
{
ShowError("Имя взаимодействия не может быть пустым");
return;
}
if (_interaction.Name.Length > MaxNameLength)
{
ShowError($"Имя слишком длинное (макс. {MaxNameLength} символов)");
return;
}
if (_interaction.Description.Length > MaxDescriptionLength)
{
ShowError($"Описание слишком длинное (макс. {MaxDescriptionLength} символов)");
return;
}
if (_interaction.InteractionMessages.Count == 0)
{
ShowError("Добавьте хотя бы одно сообщение взаимодействия");
return;
}
if (!_categoryIds.TryGetValue(CategoryOption.SelectedId, out var categoryId) || string.IsNullOrEmpty(categoryId))
{
ShowError("Выберите категорию");
return;
}
_interaction.Name = NameInput.Text.Trim();
_interaction.Description = DescriptionInput.Text.Trim();
_interaction.CategoryId = categoryId;
_iconIds.TryGetValue(IconOption.SelectedId, out var iconId);
_interaction.IconId = iconId ?? string.Empty;
_interaction.SpawnsEffect = SpawnsEffectCheckBox.Pressed;
_interaction.EffectChance = EffectChanceSlider.Value;
_effectIds.TryGetValue(EffectOption.SelectedId, out var effectId);
_interaction.EntityEffectId = effectId ?? string.Empty;
_interaction.Cooldown = CooldownSlider.Value;
_customInteractionService.SaveInteraction(_interaction);
_onClose?.Invoke(true);
Close();
}
private void OnCancelPressed(BaseButton.ButtonEventArgs args)
{
_onClose?.Invoke(false);
Close();
}
#endregion
#region Helper Methods
private void AddMessageToList(string message)
{
var panelContainer = new PanelContainer
{
Margin = new Thickness(0, 1, 0, 1),
HorizontalExpand = true,
MinHeight = 24
};
panelContainer.PanelOverride = new StyleBoxFlat
{
BackgroundColor = BackgroundHighlight,
BorderColor = BackgroundLight,
BorderThickness = new Thickness(1)
};
var container = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
Margin = new Thickness(6, 2)
};
var label = new Label
{
Text = message,
HorizontalExpand = true,
FontColorOverride = Color.White,
ClipText = true,
VerticalAlignment = VAlignment.Center,
Margin = new Thickness(0, 0, 4, 0)
};
var removeButton = new Button
{
Text = "✕",
StyleClasses = { StyleClass.ButtonSquare },
MinWidth = 22,
MinHeight = 22,
Label = { FontColorOverride = PrimaryColor}
};
removeButton.OnPressed += _ =>
{
_interaction.InteractionMessages.Remove(message);
panelContainer.Dispose();
};
container.AddChild(label);
container.AddChild(removeButton);
panelContainer.AddChild(container);
MessagesContainer.AddChild(panelContainer);
}
private void AddSoundToList(string soundId, string soundName)
{
var panelContainer = new PanelContainer
{
Margin = new Thickness(0, 1, 0, 1),
HorizontalExpand = true,
MinHeight = 24
};
panelContainer.PanelOverride = new StyleBoxFlat
{
BackgroundColor = BackgroundHighlight,
BorderColor = BackgroundLight,
BorderThickness = new Thickness(1)
};
var container = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
HorizontalExpand = true,
VerticalAlignment = VAlignment.Center,
Margin = new Thickness(6, 2)
};
var label = new Label
{
Text = soundName,
HorizontalExpand = true,
FontColorOverride = Color.White,
ClipText = true,
VerticalAlignment = VAlignment.Center,
Margin = new Thickness(0, 0, 4, 0)
};
var removeButton = new Button
{
Text = "✕",
StyleClasses = { StyleClass.ButtonSquare },
MinWidth = 22,
MinHeight = 22,
Label = { FontColorOverride = PrimaryColor}
};
removeButton.OnPressed += _ =>
{
_interaction.SoundIds.Remove(soundId);
panelContainer.Dispose();
};
container.AddChild(label);
container.AddChild(removeButton);
panelContainer.AddChild(container);
SoundsContainer.AddChild(panelContainer);
}
private void ShowError(string message)
{
var dialog = new DefaultWindow
{
Title = "Ошибка",
MinSize = new Vector2(300, 150),
SetSize = new Vector2(300, 150)
};
var dialogPanel = new PanelContainer
{
VerticalExpand = true,
HorizontalExpand = true
};
dialogPanel.PanelOverride = new StyleBoxFlat
{
BackgroundColor = BackgroundDark
};
var dialogVBox = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
Margin = new Thickness(16),
VerticalExpand = true
};
var errorLabel = new Label
{
Text = message,
HorizontalExpand = true,
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
Margin = new Thickness(0, 0, 0, 16),
FontColorOverride = SecondaryColor
};
var closeButton = new Button
{
Text = "ОК",
StyleClasses = { StyleClass.ButtonSquare },
HorizontalAlignment = HAlignment.Center,
MinWidth = 80,
MinHeight = 30
};
closeButton.OnPressed += _ => dialog.Close();
dialogVBox.AddChild(errorLabel);
dialogVBox.AddChild(closeButton);
dialogPanel.AddChild(dialogVBox);
dialog.AddChild(dialogPanel);
dialog.OpenCentered();
}
private void PlayTestSound()
{
if (!_soundIds.TryGetValue(SoundOption.SelectedId, out var selectedSoundId) || string.IsNullOrEmpty(selectedSoundId))
return;
if (_prototypeManager.TryIndex<InteractionSoundPrototype>(selectedSoundId, out var soundProto))
{
_audio.PlayGlobal(soundProto.Sound, Filter.Local(), false);
}
}
#endregion
#region Public Methods
public void SetCloseCallback(Action<bool> callback)
{
_onClose = callback;
}
#endregion
}