1174 lines
38 KiB
C#
1174 lines
38 KiB
C#
using System.Linq;
|
||
using System.Numerics;
|
||
using Content.Client._Sunrise.InteractionsPanel.Models;
|
||
using Content.Shared._Sunrise.InteractionsPanel.Data.Components;
|
||
using Content.Shared._Sunrise.InteractionsPanel.Data.Prototypes;
|
||
using Content.Shared._Sunrise.InteractionsPanel.Data.UI;
|
||
using Robust.Client.AutoGenerated;
|
||
using Robust.Client.GameObjects;
|
||
using Robust.Client.Graphics;
|
||
using Robust.Client.UserInterface;
|
||
using Robust.Client.UserInterface.Controls;
|
||
using Robust.Client.UserInterface.CustomControls;
|
||
using Robust.Client.UserInterface.XAML;
|
||
using Robust.Shared.Configuration;
|
||
using Robust.Shared.Prototypes;
|
||
using Robust.Shared.Random;
|
||
using Robust.Shared.Timing;
|
||
using Robust.Shared.Utility;
|
||
|
||
namespace Content.Client._Sunrise.InteractionsPanel;
|
||
|
||
[GenerateTypedNameReferences]
|
||
public sealed partial class InteractionsUIWindow : DefaultWindow
|
||
{
|
||
#region Dependencies
|
||
|
||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||
[Dependency] private readonly CustomInteractionService _customInteractionService = default!;
|
||
[Dependency] private readonly IRobustRandom _random = default!;
|
||
[Dependency] protected readonly ILocalizationManager Loc = default!;
|
||
|
||
private readonly SpriteSystem _spriteSystem;
|
||
|
||
#endregion
|
||
|
||
#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 BackgroundMedium = new Color(26, 35, 50); // #1a2332
|
||
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 ErrorColor = new Color(255, 69, 58); // #ff453a
|
||
private static readonly Color SuccessColor = new Color(48, 209, 88); // #30d158
|
||
private static readonly Color TextMuted = new Color(142, 142, 147); // #8e8e93
|
||
private static readonly Color FavoriteColor = new Color(255, 214, 10); // #ffd60a
|
||
private static readonly Color FavoriteHoverColor = new Color(255, 180, 0); // #ffb400
|
||
|
||
#endregion
|
||
|
||
#region Properties
|
||
|
||
private InteractionsWindowBoundUserInterface? _owner;
|
||
private List<string>? _currentInteractionIds;
|
||
private string _searchText = string.Empty;
|
||
private string _customSearchText = string.Empty;
|
||
private Dictionary<Button, string> _buttonInteractions = new();
|
||
private HashSet<string> _customInteractionIds = new();
|
||
private readonly HashSet<string> _openCategories = new();
|
||
private readonly HashSet<string> _favoriteInteractions = new();
|
||
|
||
#endregion
|
||
|
||
#region Initialization
|
||
|
||
public InteractionsUIWindow()
|
||
{
|
||
RobustXamlLoader.Load(this);
|
||
IoCManager.InjectDependencies(this);
|
||
|
||
_spriteSystem = _entityManager.System<SpriteSystem>();
|
||
|
||
MainTabContainer.SetTabTitle(0, "Интеракции");
|
||
MainTabContainer.SetTabTitle(1, "Кастом");
|
||
MainTabContainer.SetTabTitle(2, "Настройки");
|
||
|
||
SearchInput.OnTextChanged += OnSearchTextChanged;
|
||
|
||
CustomInteractionSearchInput.OnTextChanged += OnCustomInteractionSearchTextChanged;
|
||
NewCustomInteractionButton.OnPressed += OnNewCustomInteractionPressed;
|
||
|
||
LoadSavedInteractions();
|
||
LoadOpenCategories();
|
||
LoadFavoriteInteractions();
|
||
InitializeSettings();
|
||
}
|
||
|
||
private void InitializeSettings()
|
||
{
|
||
var currentVisibility = _cfg.GetCVar(InteractionsCVars.EmoteVisibility);
|
||
var currentExpand = _cfg.GetCVar(InteractionsCVars.Expand);
|
||
EmoteVisibilityCheckBox.Pressed = currentVisibility;
|
||
HideTopPanelCheckBox.Pressed = currentExpand;
|
||
ApplySettingsButton.OnPressed += OnApplySettings;
|
||
TopUserInfoBox.Visible = !currentExpand;
|
||
}
|
||
|
||
private void OnApplySettings(BaseButton.ButtonEventArgs args)
|
||
{
|
||
var emoteVisible = EmoteVisibilityCheckBox.Pressed;
|
||
var expand = HideTopPanelCheckBox.Pressed;
|
||
SetEmoteVisibility(emoteVisible);
|
||
SetExpanded(expand);
|
||
}
|
||
|
||
private void OnSearchTextChanged(LineEdit.LineEditEventArgs args)
|
||
{
|
||
_searchText = args.Text.ToLowerInvariant();
|
||
if (_currentInteractionIds != null)
|
||
{
|
||
_buttonInteractions.Clear();
|
||
_customInteractionIds.Clear();
|
||
PopulateCategories(_currentInteractionIds);
|
||
}
|
||
}
|
||
|
||
protected override void FrameUpdate(FrameEventArgs args)
|
||
{
|
||
base.FrameUpdate(args);
|
||
|
||
UpdateButtonsState();
|
||
}
|
||
|
||
public override void Close()
|
||
{
|
||
_cfg.SetCVar(InteractionsCVars.WindowPosX, (int)Position.X);
|
||
_cfg.SetCVar(InteractionsCVars.WindowPosY, (int)Position.Y);
|
||
base.Close();
|
||
}
|
||
|
||
public void SavePos()
|
||
{
|
||
_cfg.SetCVar(InteractionsCVars.WindowPosX, (int)Position.X);
|
||
_cfg.SetCVar(InteractionsCVars.WindowPosY, (int)Position.Y);
|
||
_cfg.SaveToFile();
|
||
}
|
||
|
||
private bool IsInteractionOnCooldown(string interactionId)
|
||
{
|
||
var userEntity = _owner?.Owner ?? default;
|
||
|
||
if (!_entityManager.TryGetComponent<InteractionsComponent>(userEntity, out var component))
|
||
return false;
|
||
|
||
if (!component.InteractionCooldowns.TryGetValue(interactionId, out var endTime))
|
||
return false;
|
||
|
||
return _gameTiming.CurTime < endTime;
|
||
}
|
||
|
||
private void UpdateButtonsState()
|
||
{
|
||
var userEntity = _owner?.Owner ?? default;
|
||
|
||
if (!_entityManager.TryGetComponent<InteractionsComponent>(userEntity, out var component))
|
||
return;
|
||
|
||
foreach (var (button, interactionId) in _buttonInteractions)
|
||
{
|
||
var isCustom = _customInteractionIds.Contains(interactionId);
|
||
|
||
var interactionName = "Unknown";
|
||
|
||
if (!isCustom)
|
||
{
|
||
if (_prototypeManager.TryIndex<InteractionPrototype>(interactionId, out var prototype))
|
||
interactionName = Loc.GetString(prototype.Name);
|
||
else
|
||
continue;
|
||
}
|
||
else
|
||
{
|
||
var customInteraction = _customInteractionService.GetInteraction(interactionId);
|
||
if (customInteraction != null)
|
||
{
|
||
interactionName = customInteraction.Name;
|
||
}
|
||
}
|
||
|
||
var isOnCooldown = IsInteractionOnCooldown(interactionId);
|
||
button.Disabled = isOnCooldown;
|
||
|
||
if (isOnCooldown && component.InteractionCooldowns.TryGetValue(interactionId, out var endTime))
|
||
{
|
||
var remainingTime = endTime - _gameTiming.CurTime;
|
||
if (remainingTime > TimeSpan.Zero)
|
||
{
|
||
var seconds = (int)Math.Ceiling(remainingTime.TotalSeconds);
|
||
button.Text = $"{interactionName} ({seconds}с)";
|
||
}
|
||
}
|
||
else
|
||
{
|
||
button.Text = interactionName;
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region State Management
|
||
|
||
public void UpdateState(NetEntity userEntity,
|
||
NetEntity targetEntity,
|
||
List<string> availableInteractionIds)
|
||
{
|
||
UpdateEntityInformation(userEntity, targetEntity);
|
||
_currentInteractionIds = availableInteractionIds;
|
||
_buttonInteractions.Clear();
|
||
_customInteractionIds.Clear();
|
||
PopulateCategories(availableInteractionIds);
|
||
}
|
||
|
||
private void UpdateEntityInformation(
|
||
NetEntity userEntity,
|
||
NetEntity targetEntity)
|
||
{
|
||
var selfTargeting = userEntity == targetEntity;
|
||
var nameUser = _entityManager.GetComponentOrNull<MetaDataComponent>(_entityManager.GetEntity(userEntity));
|
||
|
||
UserSpriteView.SetEntity(_entityManager.GetEntity(userEntity));
|
||
NameUser.Text = $"{nameUser?.EntityName}";
|
||
TargetContainer.Visible = !selfTargeting;
|
||
|
||
UserBoxShit.HorizontalAlignment = selfTargeting ? HAlignment.Center : HAlignment.Left;
|
||
|
||
if (!selfTargeting)
|
||
{
|
||
TargetSpriteView.SetEntity(_entityManager.GetEntity(targetEntity));
|
||
var nameTarget = _entityManager.GetComponentOrNull<MetaDataComponent>(_entityManager.GetEntity(targetEntity));
|
||
NameTarget.Text = $"{nameTarget?.EntityName}";
|
||
TargetSpriteView.InvalidateArrange();
|
||
TargetSpriteView.InvalidateMeasure();
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region UI Population
|
||
|
||
private void PopulateCategories(List<string> interactionIds)
|
||
{
|
||
CategoriesContainer.DisposeAllChildren();
|
||
|
||
var customInteractions = _customInteractionService.GetInteractions();
|
||
|
||
if (!string.IsNullOrEmpty(_searchText))
|
||
{
|
||
customInteractions = customInteractions
|
||
.Where(i => i.Name.ToLowerInvariant().Contains(_searchText) ||
|
||
i.Description.ToLowerInvariant().Contains(_searchText))
|
||
.ToList();
|
||
}
|
||
|
||
var categorizedInteractions = new Dictionary<string, (string Name, List<object> Interactions)>();
|
||
|
||
var favoriteInteractions = new List<object>();
|
||
|
||
foreach (var id in interactionIds)
|
||
{
|
||
if (!_prototypeManager.TryIndex<InteractionPrototype>(id, out var interaction))
|
||
continue;
|
||
|
||
if (!string.IsNullOrEmpty(_searchText))
|
||
{
|
||
var interactionName = interaction.Name.ToLowerInvariant();
|
||
var interactionDesc = interaction.Description != null ? interaction.Description.ToLowerInvariant() : "";
|
||
|
||
if (!interactionName.Contains(_searchText) && !interactionDesc.Contains(_searchText))
|
||
continue;
|
||
}
|
||
|
||
if (_favoriteInteractions.Contains(interaction.ID))
|
||
{
|
||
favoriteInteractions.Add(interaction);
|
||
}
|
||
|
||
var categoryId = interaction.Category.ToString();
|
||
|
||
if (!_prototypeManager.TryIndex(interaction.Category, out InteractionCategoryPrototype? category))
|
||
continue;
|
||
|
||
if (!categorizedInteractions.TryGetValue(categoryId, out _))
|
||
{
|
||
var categoryName = Loc.GetString(category.Name);
|
||
categorizedInteractions[categoryId] = (categoryName, new List<object>());
|
||
}
|
||
|
||
if (!_favoriteInteractions.Contains(interaction.ID))
|
||
{
|
||
categorizedInteractions[categoryId].Interactions.Add(interaction);
|
||
}
|
||
}
|
||
|
||
foreach (var customInteraction in customInteractions)
|
||
{
|
||
var categoryId = customInteraction.CategoryId;
|
||
|
||
if (string.IsNullOrEmpty(categoryId) || !_prototypeManager.TryIndex<InteractionCategoryPrototype>(categoryId, out _))
|
||
continue;
|
||
|
||
if (_favoriteInteractions.Contains(customInteraction.Id))
|
||
{
|
||
favoriteInteractions.Add(customInteraction);
|
||
}
|
||
|
||
if (!categorizedInteractions.TryGetValue(categoryId, out _))
|
||
{
|
||
var category = _prototypeManager.Index<InteractionCategoryPrototype>(categoryId);
|
||
categorizedInteractions[categoryId] = (Loc.GetString(category.Name), new List<object>());
|
||
}
|
||
|
||
if (!_favoriteInteractions.Contains(customInteraction.Id))
|
||
{
|
||
categorizedInteractions[categoryId].Interactions.Add(customInteraction);
|
||
}
|
||
|
||
_customInteractionIds.Add(customInteraction.Id);
|
||
}
|
||
|
||
if (favoriteInteractions.Count > 0)
|
||
{
|
||
var sortedFavorites = favoriteInteractions
|
||
.OrderBy(i =>
|
||
{
|
||
return i switch
|
||
{
|
||
InteractionPrototype proto => proto.Name,
|
||
CustomInteraction custom => custom.Name,
|
||
_ => string.Empty
|
||
};
|
||
})
|
||
.ToList();
|
||
|
||
var favoritesCollapsible = CreateCategoryCollapsible("⭐ Избранные", sortedFavorites);
|
||
CategoriesContainer.AddChild(favoritesCollapsible);
|
||
}
|
||
|
||
if (categorizedInteractions.Count == 0 && favoriteInteractions.Count == 0)
|
||
{
|
||
var noResultsLabel = new Label
|
||
{
|
||
Text = "Ничего не найдено",
|
||
HorizontalAlignment = HAlignment.Center,
|
||
VerticalAlignment = VAlignment.Center,
|
||
FontColorOverride = TextMuted,
|
||
Margin = new Thickness(0, 20, 0, 0)
|
||
};
|
||
|
||
CategoriesContainer.AddChild(noResultsLabel);
|
||
return;
|
||
}
|
||
|
||
var sortedCategories = categorizedInteractions
|
||
.OrderBy(kvp => kvp.Value.Name)
|
||
.ToList();
|
||
|
||
foreach (var (_, categoryData) in sortedCategories)
|
||
{
|
||
if (categoryData.Interactions.Count == 0)
|
||
continue;
|
||
|
||
var sortedInteractions = categoryData.Interactions
|
||
.OrderBy(i =>
|
||
{
|
||
return i switch
|
||
{
|
||
InteractionPrototype proto => proto.Name,
|
||
CustomInteraction custom => custom.Name,
|
||
_ => string.Empty
|
||
};
|
||
})
|
||
.ToList();
|
||
|
||
var collapsible = CreateCategoryCollapsible(categoryData.Name, sortedInteractions);
|
||
CategoriesContainer.AddChild(collapsible);
|
||
}
|
||
}
|
||
|
||
private Collapsible CreateCategoryCollapsible(string categoryName, List<object> interactions)
|
||
{
|
||
var heading = new CollapsibleHeading(categoryName)
|
||
{
|
||
ChevronMargin = new Thickness(6, 0, 10, 0),
|
||
MinHeight = 28,
|
||
Margin = new Thickness(0, 1, 0, 0)
|
||
};
|
||
|
||
heading.StyleBoxOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = BackgroundMedium,
|
||
BorderColor = categoryName.StartsWith("⭐") ? FavoriteColor : PrimaryColor,
|
||
BorderThickness = new Thickness(0, 0, 0, 1)
|
||
};
|
||
|
||
var body = new CollapsibleBody
|
||
{
|
||
Margin = new Thickness(0, 0, 0, 0)
|
||
};
|
||
|
||
var collapsible = new Collapsible(heading, body);
|
||
|
||
heading.OnPressed += _ =>
|
||
{
|
||
collapsible.BodyVisible = !collapsible.BodyVisible;
|
||
|
||
if (collapsible.BodyVisible)
|
||
_openCategories.Add(categoryName);
|
||
else
|
||
_openCategories.Remove(categoryName);
|
||
|
||
SaveOpenCategories();
|
||
};
|
||
|
||
var interactionsContainer = new BoxContainer
|
||
{
|
||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||
HorizontalExpand = true,
|
||
Margin = new Thickness(0, 1, 0, 2),
|
||
};
|
||
|
||
foreach (var interaction in interactions)
|
||
{
|
||
BoxContainer buttonBox;
|
||
if (interaction is InteractionPrototype standardInteraction)
|
||
{
|
||
buttonBox = CreateInteractionButton(standardInteraction);
|
||
}
|
||
else if (interaction is CustomInteraction customInteraction)
|
||
{
|
||
buttonBox = CreateCustomInteractionButton(customInteraction);
|
||
}
|
||
else
|
||
{
|
||
continue;
|
||
}
|
||
|
||
interactionsContainer.AddChild(buttonBox);
|
||
}
|
||
|
||
body.AddChild(interactionsContainer);
|
||
|
||
collapsible.Margin = new Thickness(0, 0, 0, 2);
|
||
collapsible.BodyVisible = _openCategories.Contains(categoryName) || !string.IsNullOrEmpty(_searchText);
|
||
|
||
return collapsible;
|
||
}
|
||
|
||
private BoxContainer CreateInteractionButton(InteractionPrototype interaction)
|
||
{
|
||
var buttonBox = new BoxContainer
|
||
{
|
||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||
HorizontalExpand = true,
|
||
Margin = new Thickness(0, 1, 0, 0),
|
||
VerticalAlignment = VAlignment.Center
|
||
};
|
||
|
||
var isOnCooldown = IsInteractionOnCooldown(interaction.ID);
|
||
|
||
var button = new Button
|
||
{
|
||
Text = interaction.Name,
|
||
ToolTip = interaction.Description,
|
||
HorizontalExpand = true,
|
||
MinHeight = 40,
|
||
MaxHeight = 45,
|
||
StyleClasses = { StyleClass.ButtonSquare },
|
||
Disabled = isOnCooldown
|
||
};
|
||
|
||
if (interaction.Icon.HasValue && _prototypeManager.TryIndex(interaction.Icon.Value, out InteractionIconPrototype? iconProto))
|
||
{
|
||
var iconRect = new TextureRect
|
||
{
|
||
TextureScale = new Vector2(1.0f, 1.0f),
|
||
Stretch = TextureRect.StretchMode.KeepAspectCentered,
|
||
Texture = _spriteSystem.Frame0(iconProto.Icon),
|
||
HorizontalAlignment = HAlignment.Left,
|
||
VerticalAlignment = VAlignment.Center,
|
||
Margin = new Thickness(10, 0, 10, 0),
|
||
// MinSize = new Vector2(64, 64),
|
||
// MaxSize = new Vector2(64, 64)
|
||
};
|
||
button.AddChild(iconRect);
|
||
}
|
||
|
||
if (isOnCooldown)
|
||
{
|
||
button.StyleBoxOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = new Color(BackgroundLight.R * 0.6f, BackgroundLight.G * 0.6f, BackgroundLight.B * 0.6f),
|
||
BorderColor = TextMuted,
|
||
BorderThickness = new Thickness(1)
|
||
};
|
||
}
|
||
else
|
||
{
|
||
button.StyleBoxOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = BackgroundLight,
|
||
BorderColor = PrimaryColor,
|
||
BorderThickness = new Thickness(1)
|
||
};
|
||
}
|
||
|
||
_buttonInteractions[button] = interaction.ID;
|
||
|
||
if (isOnCooldown)
|
||
{
|
||
var userEntity = _owner?.Owner ?? default;
|
||
if (_entityManager.TryGetComponent<InteractionsComponent>(userEntity, out var component) &&
|
||
component.InteractionCooldowns.TryGetValue(interaction.ID, out var endTime))
|
||
{
|
||
var remainingTime = endTime - _gameTiming.CurTime;
|
||
if (remainingTime > TimeSpan.Zero)
|
||
{
|
||
var seconds = (int)Math.Ceiling(remainingTime.TotalSeconds);
|
||
button.Text = $"{interaction.Name} ({seconds}с)";
|
||
}
|
||
}
|
||
}
|
||
|
||
button.OnPressed += _ =>
|
||
{
|
||
ExecuteInteraction(interaction.ID, false);
|
||
};
|
||
|
||
buttonBox.AddChild(button);
|
||
|
||
var favoriteButton = new Button
|
||
{
|
||
MinSize = new Vector2(32, 45),
|
||
MaxSize = new Vector2(32, 45),
|
||
StyleClasses = { StyleClass.ButtonSquare },
|
||
Margin = new Thickness(2, 0, 0, 0),
|
||
VerticalAlignment = VAlignment.Center
|
||
};
|
||
|
||
var isFavorite = _favoriteInteractions.Contains(interaction.ID);
|
||
|
||
favoriteButton.StyleBoxOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = isFavorite ? FavoriteColor : BackgroundLight,
|
||
BorderColor = isFavorite ? FavoriteColor : TextMuted,
|
||
BorderThickness = new Thickness(1)
|
||
};
|
||
|
||
var starIcon = new TextureRect
|
||
{
|
||
Texture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/examine-star.png"))),
|
||
TextureScale = new Vector2(0.8f, 0.8f),
|
||
Stretch = TextureRect.StretchMode.KeepAspectCentered,
|
||
HorizontalAlignment = HAlignment.Center,
|
||
VerticalAlignment = VAlignment.Center,
|
||
Modulate = isFavorite ? Color.White : TextMuted
|
||
};
|
||
|
||
favoriteButton.AddChild(starIcon);
|
||
|
||
favoriteButton.OnPressed += _ =>
|
||
{
|
||
ToggleFavorite(interaction.ID);
|
||
};
|
||
|
||
favoriteButton.OnMouseEntered += _ =>
|
||
{
|
||
favoriteButton.StyleBoxOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = isFavorite ? FavoriteHoverColor : BackgroundHighlight,
|
||
BorderColor = isFavorite ? FavoriteHoverColor : PrimaryColor,
|
||
BorderThickness = new Thickness(1)
|
||
};
|
||
};
|
||
|
||
favoriteButton.OnMouseExited += _ =>
|
||
{
|
||
favoriteButton.StyleBoxOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = isFavorite ? FavoriteColor : BackgroundLight,
|
||
BorderColor = isFavorite ? FavoriteColor : TextMuted,
|
||
BorderThickness = new Thickness(1)
|
||
};
|
||
};
|
||
|
||
buttonBox.AddChild(favoriteButton);
|
||
|
||
return buttonBox;
|
||
}
|
||
|
||
private BoxContainer CreateCustomInteractionButton(CustomInteraction interaction)
|
||
{
|
||
var buttonBox = new BoxContainer
|
||
{
|
||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||
HorizontalExpand = true,
|
||
Margin = new Thickness(0, 1, 0, 0),
|
||
VerticalAlignment = VAlignment.Center
|
||
};
|
||
|
||
var isOnCooldown = IsInteractionOnCooldown(interaction.Id);
|
||
|
||
var button = new Button
|
||
{
|
||
Text = interaction.Name,
|
||
ToolTip = interaction.Description,
|
||
HorizontalExpand = true,
|
||
MinHeight = 40,
|
||
MaxHeight = 45,
|
||
StyleClasses = { StyleClass.ButtonSquare },
|
||
Disabled = isOnCooldown,
|
||
};
|
||
|
||
if (!string.IsNullOrEmpty(interaction.IconId) &&
|
||
_prototypeManager.TryIndex<InteractionIconPrototype>(interaction.IconId, out var iconProto))
|
||
{
|
||
var iconRect = new TextureRect
|
||
{
|
||
TextureScale = new Vector2(1.0f, 1.0f),
|
||
Stretch = TextureRect.StretchMode.KeepAspectCentered,
|
||
Texture = _spriteSystem.Frame0(iconProto.Icon),
|
||
HorizontalAlignment = HAlignment.Left,
|
||
VerticalAlignment = VAlignment.Center,
|
||
Margin = new Thickness(10, 0, 10, 0),
|
||
// MinSize = new Vector2(64, 64),
|
||
// MaxSize = new Vector2(64, 64)
|
||
};
|
||
button.AddChild(iconRect);
|
||
}
|
||
|
||
if (isOnCooldown)
|
||
{
|
||
button.StyleBoxOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = new Color(BackgroundLight.R * 0.6f, BackgroundLight.G * 0.6f, BackgroundLight.B * 0.6f),
|
||
BorderColor = TextMuted,
|
||
BorderThickness = new Thickness(1)
|
||
};
|
||
}
|
||
else
|
||
{
|
||
button.StyleBoxOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = BackgroundLight,
|
||
BorderColor = SuccessColor,
|
||
BorderThickness = new Thickness(1)
|
||
};
|
||
}
|
||
|
||
_buttonInteractions[button] = interaction.Id;
|
||
|
||
if (isOnCooldown)
|
||
{
|
||
var userEntity = _owner?.Owner ?? default;
|
||
if (_entityManager.TryGetComponent<InteractionsComponent>(userEntity, out var component) &&
|
||
component.InteractionCooldowns.TryGetValue(interaction.Id, out var endTime))
|
||
{
|
||
var remainingTime = endTime - _gameTiming.CurTime;
|
||
if (remainingTime > TimeSpan.Zero)
|
||
{
|
||
var seconds = (int)Math.Ceiling(remainingTime.TotalSeconds);
|
||
button.Text = $"{interaction.Name} ({seconds}с)";
|
||
}
|
||
}
|
||
}
|
||
|
||
button.OnPressed += _ =>
|
||
{
|
||
ExecuteInteraction(interaction.Id, true);
|
||
};
|
||
|
||
buttonBox.AddChild(button);
|
||
|
||
var favoriteButton = new Button
|
||
{
|
||
MinSize = new Vector2(32, 44),
|
||
MaxSize = new Vector2(32, 44),
|
||
StyleClasses = { StyleClass.ButtonSquare },
|
||
Margin = new Thickness(2, 0, 0, 0),
|
||
VerticalAlignment = VAlignment.Center
|
||
};
|
||
|
||
var isFavorite = _favoriteInteractions.Contains(interaction.Id);
|
||
|
||
favoriteButton.StyleBoxOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = isFavorite ? FavoriteColor : BackgroundLight,
|
||
BorderColor = isFavorite ? FavoriteColor : TextMuted,
|
||
BorderThickness = new Thickness(1)
|
||
};
|
||
|
||
var starIcon = new TextureRect
|
||
{
|
||
Texture = _spriteSystem.Frame0(new SpriteSpecifier.Texture(new ResPath("/Textures/Interface/examine-star.png"))),
|
||
TextureScale = new Vector2(0.8f, 0.8f),
|
||
Stretch = TextureRect.StretchMode.KeepAspectCentered,
|
||
HorizontalAlignment = HAlignment.Center,
|
||
VerticalAlignment = VAlignment.Center,
|
||
Modulate = isFavorite ? Color.White : TextMuted
|
||
};
|
||
|
||
favoriteButton.AddChild(starIcon);
|
||
|
||
favoriteButton.OnPressed += _ =>
|
||
{
|
||
ToggleFavorite(interaction.Id);
|
||
};
|
||
|
||
favoriteButton.OnMouseEntered += _ =>
|
||
{
|
||
favoriteButton.StyleBoxOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = isFavorite ? FavoriteHoverColor : BackgroundHighlight,
|
||
BorderColor = isFavorite ? FavoriteHoverColor : PrimaryColor,
|
||
BorderThickness = new Thickness(1)
|
||
};
|
||
};
|
||
|
||
favoriteButton.OnMouseExited += _ =>
|
||
{
|
||
favoriteButton.StyleBoxOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = isFavorite ? FavoriteColor : BackgroundLight,
|
||
BorderColor = isFavorite ? FavoriteColor : TextMuted,
|
||
BorderThickness = new Thickness(1)
|
||
};
|
||
};
|
||
|
||
buttonBox.AddChild(favoriteButton);
|
||
|
||
return buttonBox;
|
||
}
|
||
|
||
private void ExecuteInteraction(string interactionId, bool isCustom)
|
||
{
|
||
if (!isCustom)
|
||
{
|
||
_owner?.SendBoundUserInterfaceMessage(new InteractionMessage(interactionId));
|
||
}
|
||
else
|
||
{
|
||
var customInteraction = _customInteractionService.GetInteraction(interactionId);
|
||
if (customInteraction == null)
|
||
return;
|
||
|
||
var message = customInteraction.InteractionMessages.Count > 0
|
||
? _random.Pick(customInteraction.InteractionMessages)
|
||
: "взаимодействует с";
|
||
|
||
string? soundId = null;
|
||
if (customInteraction.SoundIds.Count > 0)
|
||
soundId = _random.Pick(customInteraction.SoundIds);
|
||
|
||
var customData = new CustomInteractionData(
|
||
message,
|
||
soundId,
|
||
customInteraction.SpawnsEffect,
|
||
customInteraction.EffectChance,
|
||
customInteraction.EntityEffectId,
|
||
customInteraction.Cooldown
|
||
);
|
||
|
||
_owner?.SendBoundUserInterfaceMessage(new InteractionMessage(interactionId, customData));
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Favorites
|
||
|
||
private void ToggleFavorite(string interactionId)
|
||
{
|
||
if (_favoriteInteractions.Contains(interactionId))
|
||
{
|
||
_favoriteInteractions.Remove(interactionId);
|
||
}
|
||
else
|
||
{
|
||
_favoriteInteractions.Add(interactionId);
|
||
}
|
||
|
||
SaveFavoriteInteractions();
|
||
|
||
if (_currentInteractionIds != null)
|
||
{
|
||
_buttonInteractions.Clear();
|
||
_customInteractionIds.Clear();
|
||
PopulateCategories(_currentInteractionIds);
|
||
}
|
||
}
|
||
|
||
private void SaveFavoriteInteractions()
|
||
{
|
||
var joined = string.Join(",", _favoriteInteractions);
|
||
_cfg.SetCVar(InteractionsCVars.FavoriteInteractions, joined);
|
||
_cfg.SaveToFile();
|
||
}
|
||
|
||
private void LoadFavoriteInteractions()
|
||
{
|
||
_favoriteInteractions.Clear();
|
||
|
||
var saved = _cfg.GetCVar(InteractionsCVars.FavoriteInteractions);
|
||
if (!string.IsNullOrEmpty(saved))
|
||
{
|
||
foreach (var interactionId in saved.Split(','))
|
||
{
|
||
if (!string.IsNullOrEmpty(interactionId))
|
||
{
|
||
_favoriteInteractions.Add(interactionId);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region API
|
||
|
||
public void SetOwner(InteractionsWindowBoundUserInterface owner)
|
||
{
|
||
_owner = owner;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region CFG
|
||
|
||
private void SetEmoteVisibility(bool visible)
|
||
{
|
||
_cfg.SetCVar(InteractionsCVars.EmoteVisibility, visible);
|
||
_cfg.SaveToFile();
|
||
}
|
||
|
||
private void SetExpanded(bool expand)
|
||
{
|
||
_cfg.SetCVar(InteractionsCVars.Expand, expand);
|
||
_cfg.SaveToFile();
|
||
|
||
TopUserInfoBox.Visible = !expand;
|
||
}
|
||
|
||
private void SaveOpenCategories()
|
||
{
|
||
var joined = string.Join(",", _openCategories);
|
||
_cfg.SetCVar(InteractionsCVars.OpenInteractionCategories, joined);
|
||
_cfg.SaveToFile();
|
||
}
|
||
|
||
private void LoadOpenCategories()
|
||
{
|
||
_openCategories.Clear();
|
||
|
||
var saved = _cfg.GetCVar(InteractionsCVars.OpenInteractionCategories);
|
||
if (!string.IsNullOrEmpty(saved))
|
||
{
|
||
foreach (var cat in saved.Split(','))
|
||
{
|
||
_openCategories.Add(cat);
|
||
}
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Custom
|
||
|
||
private void OnCustomInteractionSearchTextChanged(LineEdit.LineEditEventArgs args)
|
||
{
|
||
_customSearchText = args.Text.ToLowerInvariant();
|
||
LoadSavedInteractions();
|
||
}
|
||
|
||
private void OnNewCustomInteractionPressed(BaseButton.ButtonEventArgs args)
|
||
{
|
||
var editor = new CustomInteractionEditor();
|
||
editor.SetCloseCallback(saved =>
|
||
{
|
||
if (saved)
|
||
{
|
||
LoadSavedInteractions();
|
||
if (_currentInteractionIds != null)
|
||
{
|
||
_buttonInteractions.Clear();
|
||
_customInteractionIds.Clear();
|
||
PopulateCategories(_currentInteractionIds);
|
||
}
|
||
}
|
||
});
|
||
editor.OpenCentered();
|
||
}
|
||
|
||
private void LoadSavedInteractions()
|
||
{
|
||
SavedInteractionsContainer.DisposeAllChildren();
|
||
|
||
var interactions = _customInteractionService.GetInteractions();
|
||
|
||
if (!string.IsNullOrEmpty(_customSearchText))
|
||
{
|
||
interactions = interactions
|
||
.Where(i => i.Name.ToLowerInvariant().Contains(_customSearchText) ||
|
||
i.Description.ToLowerInvariant().Contains(_customSearchText))
|
||
.ToList();
|
||
}
|
||
|
||
if (interactions.Count == 0)
|
||
{
|
||
var emptyLabel = new Label
|
||
{
|
||
Text = string.IsNullOrEmpty(_customSearchText)
|
||
? "У вас нет сохраненных взаимодействий"
|
||
: "Ничего не найдено",
|
||
HorizontalAlignment = HAlignment.Center,
|
||
VerticalAlignment = VAlignment.Center,
|
||
FontColorOverride = TextMuted,
|
||
Margin = new Thickness(0, 10, 0, 0)
|
||
};
|
||
|
||
SavedInteractionsContainer.AddChild(emptyLabel);
|
||
return;
|
||
}
|
||
|
||
foreach (var interaction in interactions)
|
||
{
|
||
SavedInteractionsContainer.AddChild(CreateSavedInteractionCard(interaction));
|
||
}
|
||
}
|
||
|
||
private Control CreateSavedInteractionCard(CustomInteraction interaction)
|
||
{
|
||
var card = new PanelContainer
|
||
{
|
||
Margin = new Thickness(0, 0, 0, 6)
|
||
};
|
||
|
||
card.PanelOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = BackgroundLight,
|
||
BorderColor = PrimaryColor,
|
||
BorderThickness = new Thickness(1)
|
||
};
|
||
|
||
var mainBox = new BoxContainer
|
||
{
|
||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||
Margin = new Thickness(6)
|
||
};
|
||
|
||
var headerBox = new BoxContainer
|
||
{
|
||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||
HorizontalExpand = true,
|
||
Margin = new Thickness(0, 0, 0, 4)
|
||
};
|
||
|
||
var titleLabel = new Label
|
||
{
|
||
Text = interaction.Name,
|
||
FontColorOverride = PrimaryColor,
|
||
HorizontalExpand = true
|
||
};
|
||
|
||
var editButton = new Button
|
||
{
|
||
Text = "Редактировать",
|
||
StyleClasses = { StyleClass.ButtonSquare },
|
||
Margin = new Thickness(0, 0, 4, 0)
|
||
};
|
||
|
||
editButton.StyleBoxOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = BackgroundHighlight,
|
||
BorderColor = SuccessColor,
|
||
BorderThickness = new Thickness(1)
|
||
};
|
||
|
||
var deleteButton = new Button
|
||
{
|
||
Text = "Удалить",
|
||
StyleClasses = { StyleClass.ButtonSquare },
|
||
};
|
||
|
||
deleteButton.StyleBoxOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = BackgroundHighlight,
|
||
BorderColor = ErrorColor,
|
||
BorderThickness = new Thickness(1)
|
||
};
|
||
|
||
editButton.OnPressed += _ => OnEditInteraction(interaction);
|
||
deleteButton.OnPressed += _ => OnDeleteInteraction(interaction);
|
||
|
||
headerBox.AddChild(titleLabel);
|
||
headerBox.AddChild(editButton);
|
||
headerBox.AddChild(deleteButton);
|
||
|
||
var descriptionLabel = new Label
|
||
{
|
||
Text = interaction.Description,
|
||
FontColorOverride = SecondaryColor,
|
||
Margin = new Thickness(0, 2, 0, 6)
|
||
};
|
||
|
||
var categoryBox = new BoxContainer
|
||
{
|
||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||
Margin = new Thickness(0, 2, 0, 0)
|
||
};
|
||
|
||
var categoryLabel = new Label
|
||
{
|
||
Text = $"Категория: {GetCategoryName(interaction.CategoryId)}",
|
||
FontColorOverride = new Color(208, 166, 92)
|
||
};
|
||
|
||
categoryBox.AddChild(categoryLabel);
|
||
|
||
mainBox.AddChild(headerBox);
|
||
mainBox.AddChild(descriptionLabel);
|
||
mainBox.AddChild(categoryBox);
|
||
|
||
card.AddChild(mainBox);
|
||
|
||
return card;
|
||
}
|
||
|
||
private string GetCategoryName(string categoryId)
|
||
{
|
||
if (string.IsNullOrEmpty(categoryId))
|
||
return "Не указана";
|
||
|
||
if (_prototypeManager.TryIndex<InteractionCategoryPrototype>(categoryId, out var category))
|
||
return Loc.GetString(category.Name);
|
||
|
||
return categoryId;
|
||
}
|
||
|
||
private void OnEditInteraction(CustomInteraction interaction)
|
||
{
|
||
var interactionCopy = new CustomInteraction
|
||
{
|
||
Id = interaction.Id,
|
||
Name = interaction.Name,
|
||
Description = interaction.Description,
|
||
IconId = interaction.IconId,
|
||
CategoryId = interaction.CategoryId,
|
||
InteractionMessages = new List<string>(interaction.InteractionMessages),
|
||
SoundIds = new List<string>(interaction.SoundIds),
|
||
SpawnsEffect = interaction.SpawnsEffect,
|
||
EffectChance = interaction.EffectChance,
|
||
EntityEffectId = interaction.EntityEffectId,
|
||
Cooldown = interaction.Cooldown
|
||
};
|
||
|
||
var editor = new CustomInteractionEditor(interactionCopy);
|
||
editor.SetCloseCallback((saved) =>
|
||
{
|
||
if (saved)
|
||
{
|
||
LoadSavedInteractions();
|
||
if (_currentInteractionIds != null)
|
||
{
|
||
_buttonInteractions.Clear();
|
||
_customInteractionIds.Clear();
|
||
PopulateCategories(_currentInteractionIds);
|
||
}
|
||
}
|
||
});
|
||
editor.OpenCentered();
|
||
}
|
||
|
||
private void OnDeleteInteraction(CustomInteraction interaction)
|
||
{
|
||
var confirmDialog = new DefaultWindow
|
||
{
|
||
Title = "Подтверждение",
|
||
MinSize = new Vector2(280, 140)
|
||
};
|
||
|
||
var dialogPanel = new PanelContainer
|
||
{
|
||
VerticalExpand = true,
|
||
HorizontalExpand = true
|
||
};
|
||
|
||
dialogPanel.PanelOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = BackgroundMedium
|
||
};
|
||
|
||
var dialogVBox = new BoxContainer
|
||
{
|
||
Orientation = BoxContainer.LayoutOrientation.Vertical,
|
||
Margin = new Thickness(8),
|
||
VerticalExpand = true
|
||
};
|
||
|
||
var questionLabel = new Label
|
||
{
|
||
Text = $"Удалить '{interaction.Name}'?",
|
||
HorizontalExpand = true,
|
||
HorizontalAlignment = HAlignment.Center,
|
||
Margin = new Thickness(0, 0, 0, 12),
|
||
FontColorOverride = PrimaryColor
|
||
};
|
||
|
||
var buttonsBox = new BoxContainer
|
||
{
|
||
Orientation = BoxContainer.LayoutOrientation.Horizontal,
|
||
HorizontalAlignment = HAlignment.Center,
|
||
VerticalAlignment = VAlignment.Bottom,
|
||
VerticalExpand = true,
|
||
Margin = new Thickness(0, 6, 0, 0)
|
||
};
|
||
|
||
var cancelButton = new Button
|
||
{
|
||
Text = "Отмена",
|
||
StyleClasses = { StyleClass.ButtonSquare },
|
||
Margin = new Thickness(0, 0, 4, 0)
|
||
};
|
||
|
||
cancelButton.StyleBoxOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = BackgroundLight,
|
||
BorderColor = SecondaryColor,
|
||
BorderThickness = new Thickness(1)
|
||
};
|
||
|
||
var confirmButton = new Button
|
||
{
|
||
Text = "Удалить",
|
||
StyleClasses = { StyleClass.ButtonSquare },
|
||
};
|
||
|
||
confirmButton.StyleBoxOverride = new StyleBoxFlat
|
||
{
|
||
BackgroundColor = BackgroundLight,
|
||
BorderColor = ErrorColor,
|
||
BorderThickness = new Thickness(1)
|
||
};
|
||
|
||
cancelButton.OnPressed += _ => confirmDialog.Close();
|
||
|
||
confirmButton.OnPressed += _ =>
|
||
{
|
||
_customInteractionService.RemoveInteraction(interaction.Id);
|
||
LoadSavedInteractions();
|
||
if (_currentInteractionIds != null)
|
||
{
|
||
_buttonInteractions.Clear();
|
||
_customInteractionIds.Clear();
|
||
PopulateCategories(_currentInteractionIds);
|
||
}
|
||
confirmDialog.Close();
|
||
};
|
||
|
||
buttonsBox.AddChild(cancelButton);
|
||
buttonsBox.AddChild(confirmButton);
|
||
|
||
dialogVBox.AddChild(questionLabel);
|
||
dialogVBox.AddChild(buttonsBox);
|
||
dialogPanel.AddChild(dialogVBox);
|
||
|
||
confirmDialog.AddChild(dialogPanel);
|
||
confirmDialog.OpenCentered();
|
||
}
|
||
|
||
#endregion
|
||
}
|