937 lines
31 KiB
C#
937 lines
31 KiB
C#
using System.Linq;
|
|
using System.Numerics;
|
|
using Content.Client.Administration.Managers;
|
|
using Content.Client.ContextMenu.UI;
|
|
using Content.Client.Decals;
|
|
using Content.Client.Gameplay;
|
|
using Content.Client.UserInterface.Controls;
|
|
using Content.Client.UserInterface.Systems.Gameplay;
|
|
using Content.Client.Verbs;
|
|
using Content.Shared.Administration;
|
|
using Content.Shared.Decals;
|
|
using Content.Shared.Input;
|
|
using Content.Shared.Maps;
|
|
using Robust.Client.GameObjects;
|
|
using Robust.Client.Graphics;
|
|
using Robust.Client.Input;
|
|
using Robust.Client.Placement;
|
|
using Robust.Client.ResourceManagement;
|
|
using Robust.Client.UserInterface;
|
|
using Robust.Client.UserInterface.CustomControls;
|
|
using Robust.Shared.Enums;
|
|
using Robust.Shared.Input.Binding;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Player;
|
|
using Robust.Shared.Prototypes;
|
|
using Robust.Shared.Serialization.Markdown.Sequence;
|
|
using Robust.Shared.Serialization.Markdown.Value;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Utility;
|
|
using static System.StringComparison;
|
|
using static Robust.Client.UserInterface.Controls.BaseButton;
|
|
using static Robust.Client.UserInterface.Controls.LineEdit;
|
|
using static Robust.Client.UserInterface.Controls.OptionButton;
|
|
using static Robust.Shared.Input.Binding.PointerInputCmdHandler;
|
|
|
|
namespace Content.Client.Mapping;
|
|
|
|
public sealed class MappingState : GameplayStateBase
|
|
{
|
|
[Dependency] private readonly IClientAdminManager _admin = default!;
|
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
|
[Dependency] private readonly IEntityNetworkManager _entityNetwork = default!;
|
|
[Dependency] private readonly IInputManager _input = default!;
|
|
[Dependency] private readonly ILogManager _log = default!;
|
|
[Dependency] private readonly IMapManager _mapMan = default!;
|
|
[Dependency] private readonly MappingManager _mapping = default!;
|
|
[Dependency] private readonly IOverlayManager _overlays = default!;
|
|
[Dependency] private readonly IPlacementManager _placement = default!;
|
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
|
[Dependency] private readonly IResourceCache _resources = default!;
|
|
[Dependency] private readonly IGameTiming _timing = default!;
|
|
|
|
private EntityMenuUIController _entityMenuController = default!;
|
|
|
|
private DecalPlacementSystem _decal = default!;
|
|
private SpriteSystem _sprite = default!;
|
|
private TransformSystem _transform = default!;
|
|
private VerbSystem _verbs = default!;
|
|
|
|
private readonly ISawmill _sawmill;
|
|
private readonly GameplayStateLoadController _loadController;
|
|
private bool _setup;
|
|
private readonly List<MappingPrototype> _allPrototypes = new();
|
|
private readonly Dictionary<IPrototype, MappingPrototype> _allPrototypesDict = new();
|
|
private readonly Dictionary<Type, Dictionary<string, MappingPrototype>> _idDict = new();
|
|
private readonly List<MappingPrototype> _prototypes = new();
|
|
private (TimeSpan At, MappingSpawnButton Button)? _lastClicked;
|
|
private Control? _scrollTo;
|
|
private bool _updatePlacement;
|
|
private bool _updateEraseDecal;
|
|
|
|
private MappingScreen Screen => (MappingScreen) UserInterfaceManager.ActiveScreen!;
|
|
private MainViewport Viewport => UserInterfaceManager.ActiveScreen!.GetWidget<MainViewport>()!;
|
|
|
|
public CursorState State { get; set; }
|
|
|
|
public MappingState()
|
|
{
|
|
IoCManager.InjectDependencies(this);
|
|
|
|
_sawmill = _log.GetSawmill("mapping");
|
|
_loadController = UserInterfaceManager.GetUIController<GameplayStateLoadController>();
|
|
}
|
|
|
|
protected override void Startup()
|
|
{
|
|
EnsureSetup();
|
|
base.Startup();
|
|
|
|
UserInterfaceManager.LoadScreen<MappingScreen>();
|
|
_loadController.LoadScreen();
|
|
|
|
var context = _input.Contexts.GetContext("common");
|
|
context.AddFunction(ContentKeyFunctions.MappingUnselect);
|
|
context.AddFunction(ContentKeyFunctions.SaveMap);
|
|
context.AddFunction(ContentKeyFunctions.MappingEnablePick);
|
|
context.AddFunction(ContentKeyFunctions.MappingEnableDelete);
|
|
context.AddFunction(ContentKeyFunctions.MappingPick);
|
|
context.AddFunction(ContentKeyFunctions.MappingRemoveDecal);
|
|
context.AddFunction(ContentKeyFunctions.MappingCancelEraseDecal);
|
|
context.AddFunction(ContentKeyFunctions.MappingOpenContextMenu);
|
|
|
|
Screen.DecalSystem = _decal;
|
|
Screen.Prototypes.SearchBar.OnTextChanged += OnSearch;
|
|
Screen.Prototypes.CollapseAllButton.OnPressed += OnCollapseAll;
|
|
Screen.Prototypes.ClearSearchButton.OnPressed += OnClearSearch;
|
|
Screen.Prototypes.GetPrototypeData += OnGetData;
|
|
Screen.Prototypes.SelectionChanged += OnSelected;
|
|
Screen.Prototypes.CollapseToggled += OnCollapseToggled;
|
|
Screen.Pick.OnPressed += OnPickPressed;
|
|
Screen.Delete.OnPressed += OnDeletePressed;
|
|
Screen.EntityReplaceButton.OnToggled += OnEntityReplacePressed;
|
|
Screen.EntityPlacementMode.OnItemSelected += OnEntityPlacementSelected;
|
|
Screen.EraseEntityButton.OnToggled += OnEraseEntityPressed;
|
|
Screen.EraseDecalButton.OnToggled += OnEraseDecalPressed;
|
|
_placement.PlacementChanged += OnPlacementChanged;
|
|
|
|
CommandBinds.Builder
|
|
.Bind(ContentKeyFunctions.MappingUnselect, new PointerInputCmdHandler(HandleMappingUnselect, outsidePrediction: true))
|
|
.Bind(ContentKeyFunctions.SaveMap, new PointerInputCmdHandler(HandleSaveMap, outsidePrediction: true))
|
|
.Bind(ContentKeyFunctions.MappingEnablePick, new PointerStateInputCmdHandler(HandleEnablePick, HandleDisablePick, outsidePrediction: true))
|
|
.Bind(ContentKeyFunctions.MappingEnableDelete, new PointerStateInputCmdHandler(HandleEnableDelete, HandleDisableDelete, outsidePrediction: true))
|
|
.Bind(ContentKeyFunctions.MappingPick, new PointerInputCmdHandler(HandlePick, outsidePrediction: true))
|
|
.Bind(ContentKeyFunctions.MappingRemoveDecal, new PointerInputCmdHandler(HandleEditorCancelPlace, outsidePrediction: true))
|
|
.Bind(ContentKeyFunctions.MappingCancelEraseDecal, new PointerInputCmdHandler(HandleCancelEraseDecal, outsidePrediction: true))
|
|
.Bind(ContentKeyFunctions.MappingOpenContextMenu, new PointerInputCmdHandler(HandleOpenContextMenu, outsidePrediction: true))
|
|
.Register<MappingState>();
|
|
|
|
_overlays.AddOverlay(new MappingOverlay(this));
|
|
|
|
_prototypeManager.PrototypesReloaded += OnPrototypesReloaded;
|
|
|
|
Screen.Prototypes.UpdateVisible(_prototypes);
|
|
}
|
|
|
|
private void OnPrototypesReloaded(PrototypesReloadedEventArgs obj)
|
|
{
|
|
if (!obj.WasModified<EntityPrototype>() &&
|
|
!obj.WasModified<ContentTileDefinition>() &&
|
|
!obj.WasModified<DecalPrototype>())
|
|
{
|
|
return;
|
|
}
|
|
|
|
ReloadPrototypes();
|
|
}
|
|
|
|
private bool HandleOpenContextMenu(in PointerInputCmdArgs args)
|
|
{
|
|
Deselect();
|
|
|
|
var coords = _transform.ToMapCoordinates(args.Coordinates);
|
|
if (_verbs.TryGetEntityMenuEntities(coords, out var entities))
|
|
_entityMenuController.OpenRootMenu(entities);
|
|
|
|
return true;
|
|
}
|
|
|
|
protected override void Shutdown()
|
|
{
|
|
CommandBinds.Unregister<MappingState>();
|
|
|
|
Screen.Prototypes.SearchBar.OnTextChanged -= OnSearch;
|
|
Screen.Prototypes.CollapseAllButton.OnPressed -= OnCollapseAll;
|
|
Screen.Prototypes.ClearSearchButton.OnPressed -= OnClearSearch;
|
|
Screen.Prototypes.GetPrototypeData -= OnGetData;
|
|
Screen.Prototypes.SelectionChanged -= OnSelected;
|
|
Screen.Prototypes.CollapseToggled -= OnCollapseToggled;
|
|
Screen.Pick.OnPressed -= OnPickPressed;
|
|
Screen.Delete.OnPressed -= OnDeletePressed;
|
|
Screen.EntityReplaceButton.OnToggled -= OnEntityReplacePressed;
|
|
Screen.EntityPlacementMode.OnItemSelected -= OnEntityPlacementSelected;
|
|
Screen.EraseEntityButton.OnToggled -= OnEraseEntityPressed;
|
|
Screen.EraseDecalButton.OnToggled -= OnEraseDecalPressed;
|
|
_placement.PlacementChanged -= OnPlacementChanged;
|
|
_prototypeManager.PrototypesReloaded -= OnPrototypesReloaded;
|
|
|
|
UserInterfaceManager.ClearWindows();
|
|
_loadController.UnloadScreen();
|
|
UserInterfaceManager.UnloadScreen();
|
|
|
|
var context = _input.Contexts.GetContext("common");
|
|
context.RemoveFunction(ContentKeyFunctions.MappingUnselect);
|
|
context.RemoveFunction(ContentKeyFunctions.SaveMap);
|
|
context.RemoveFunction(ContentKeyFunctions.MappingEnablePick);
|
|
context.RemoveFunction(ContentKeyFunctions.MappingEnableDelete);
|
|
context.RemoveFunction(ContentKeyFunctions.MappingPick);
|
|
context.RemoveFunction(ContentKeyFunctions.MappingRemoveDecal);
|
|
context.RemoveFunction(ContentKeyFunctions.MappingCancelEraseDecal);
|
|
context.RemoveFunction(ContentKeyFunctions.MappingOpenContextMenu);
|
|
|
|
_overlays.RemoveOverlay<MappingOverlay>();
|
|
|
|
base.Shutdown();
|
|
}
|
|
|
|
private void EnsureSetup()
|
|
{
|
|
if (_setup)
|
|
return;
|
|
|
|
_setup = true;
|
|
|
|
_entityMenuController = UserInterfaceManager.GetUIController<EntityMenuUIController>();
|
|
|
|
_decal = _entityManager.System<DecalPlacementSystem>();
|
|
_sprite = _entityManager.System<SpriteSystem>();
|
|
_transform = _entityManager.System<TransformSystem>();
|
|
_verbs = _entityManager.System<VerbSystem>();
|
|
ReloadPrototypes();
|
|
}
|
|
|
|
private void ReloadPrototypes()
|
|
{
|
|
var entities = new MappingPrototype(null, Loc.GetString("mapping-entities")) { Children = new List<MappingPrototype>() };
|
|
_prototypes.Add(entities);
|
|
|
|
var mappings = new Dictionary<string, MappingPrototype>();
|
|
foreach (var entity in _prototypeManager.EnumeratePrototypes<EntityPrototype>())
|
|
{
|
|
Register(entity, entity.ID, entities);
|
|
}
|
|
|
|
Sort(mappings, entities);
|
|
mappings.Clear();
|
|
|
|
var tiles = new MappingPrototype(null, Loc.GetString("mapping-tiles")) { Children = new List<MappingPrototype>() };
|
|
_prototypes.Add(tiles);
|
|
|
|
foreach (var tile in _prototypeManager.EnumeratePrototypes<ContentTileDefinition>())
|
|
{
|
|
Register(tile, tile.ID, tiles);
|
|
}
|
|
|
|
Sort(mappings, tiles);
|
|
mappings.Clear();
|
|
|
|
var decals = new MappingPrototype(null, Loc.GetString("mapping-decals")) { Children = new List<MappingPrototype>() };
|
|
_prototypes.Add(decals);
|
|
|
|
foreach (var decal in _prototypeManager.EnumeratePrototypes<DecalPrototype>())
|
|
{
|
|
Register(decal, decal.ID, decals);
|
|
}
|
|
|
|
Sort(mappings, decals);
|
|
mappings.Clear();
|
|
}
|
|
|
|
private void Sort(Dictionary<string, MappingPrototype> prototypes, MappingPrototype topLevel)
|
|
{
|
|
static int Compare(MappingPrototype a, MappingPrototype b)
|
|
{
|
|
return string.Compare(a.Name, b.Name, OrdinalIgnoreCase);
|
|
}
|
|
|
|
topLevel.Children ??= new List<MappingPrototype>();
|
|
|
|
foreach (var prototype in prototypes.Values)
|
|
{
|
|
if (prototype.Parents == null && prototype != topLevel)
|
|
{
|
|
prototype.Parents = new List<MappingPrototype> { topLevel };
|
|
topLevel.Children.Add(prototype);
|
|
}
|
|
|
|
prototype.Parents?.Sort(Compare);
|
|
prototype.Children?.Sort(Compare);
|
|
}
|
|
|
|
topLevel.Children.Sort(Compare);
|
|
}
|
|
|
|
private MappingPrototype? Register<T>(T? prototype, string id, MappingPrototype topLevel) where T : class, IPrototype, IInheritingPrototype
|
|
{
|
|
{
|
|
if (prototype == null &&
|
|
_prototypeManager.TryIndex(id, out prototype) &&
|
|
prototype is EntityPrototype entity)
|
|
{
|
|
if (entity.HideSpawnMenu || entity.Abstract)
|
|
prototype = null;
|
|
}
|
|
}
|
|
|
|
if (prototype == null)
|
|
{
|
|
if (!_prototypeManager.TryGetMapping(typeof(T), id, out var node))
|
|
{
|
|
_sawmill.Error($"No {nameof(T)} found with id {id}");
|
|
return null;
|
|
}
|
|
|
|
var ids = _idDict.GetOrNew(typeof(T));
|
|
if (ids.TryGetValue(id, out var mapping))
|
|
{
|
|
return mapping;
|
|
}
|
|
else
|
|
{
|
|
var name = node.TryGet("name", out ValueDataNode? nameNode)
|
|
? nameNode.Value
|
|
: id;
|
|
|
|
if (node.TryGet("suffix", out ValueDataNode? suffix))
|
|
name = $"{name} [{suffix.Value}]";
|
|
|
|
mapping = new MappingPrototype(prototype, name);
|
|
_allPrototypes.Add(mapping);
|
|
ids.Add(id, mapping);
|
|
|
|
if (node.TryGet("parent", out ValueDataNode? parentValue))
|
|
{
|
|
var parent = Register<T>(null, parentValue.Value, topLevel);
|
|
|
|
if (parent != null)
|
|
{
|
|
mapping.Parents ??= new List<MappingPrototype>();
|
|
mapping.Parents.Add(parent);
|
|
parent.Children ??= new List<MappingPrototype>();
|
|
parent.Children.Add(mapping);
|
|
}
|
|
}
|
|
else if (node.TryGet("parent", out SequenceDataNode? parentSequence))
|
|
{
|
|
foreach (var parentNode in parentSequence.Cast<ValueDataNode>())
|
|
{
|
|
var parent = Register<T>(null, parentNode.Value, topLevel);
|
|
|
|
if (parent != null)
|
|
{
|
|
mapping.Parents ??= new List<MappingPrototype>();
|
|
mapping.Parents.Add(parent);
|
|
parent.Children ??= new List<MappingPrototype>();
|
|
parent.Children.Add(mapping);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
topLevel.Children ??= new List<MappingPrototype>();
|
|
topLevel.Children.Add(mapping);
|
|
mapping.Parents ??= new List<MappingPrototype>();
|
|
mapping.Parents.Add(topLevel);
|
|
}
|
|
|
|
return mapping;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var ids = _idDict.GetOrNew(typeof(T));
|
|
if (ids.TryGetValue(id, out var mapping))
|
|
{
|
|
return mapping;
|
|
}
|
|
else
|
|
{
|
|
var entity = prototype as EntityPrototype;
|
|
var name = entity?.Name ?? prototype.ID;
|
|
|
|
if (!string.IsNullOrWhiteSpace(entity?.EditorSuffix))
|
|
name = $"{name} [{entity.EditorSuffix}]";
|
|
|
|
mapping = new MappingPrototype(prototype, name);
|
|
_allPrototypes.Add(mapping);
|
|
_allPrototypesDict.Add(prototype, mapping);
|
|
ids.Add(prototype.ID, mapping);
|
|
}
|
|
|
|
if (prototype.Parents == null)
|
|
{
|
|
topLevel.Children ??= new List<MappingPrototype>();
|
|
topLevel.Children.Add(mapping);
|
|
mapping.Parents ??= new List<MappingPrototype>();
|
|
mapping.Parents.Add(topLevel);
|
|
return mapping;
|
|
}
|
|
|
|
foreach (var parentId in prototype.Parents)
|
|
{
|
|
var parent = Register<T>(null, parentId, topLevel);
|
|
|
|
if (parent != null)
|
|
{
|
|
mapping.Parents ??= new List<MappingPrototype>();
|
|
mapping.Parents.Add(parent);
|
|
parent.Children ??= new List<MappingPrototype>();
|
|
parent.Children.Add(mapping);
|
|
}
|
|
}
|
|
|
|
return mapping;
|
|
}
|
|
}
|
|
|
|
private void OnPlacementChanged(object? sender, EventArgs e)
|
|
{
|
|
_updatePlacement = true;
|
|
}
|
|
|
|
protected override void OnKeyBindStateChanged(ViewportBoundKeyEventArgs args)
|
|
{
|
|
if (args.Viewport == null)
|
|
base.OnKeyBindStateChanged(new ViewportBoundKeyEventArgs(args.KeyEventArgs, Viewport.Viewport));
|
|
else
|
|
base.OnKeyBindStateChanged(args);
|
|
}
|
|
|
|
private void OnSearch(LineEditEventArgs args)
|
|
{
|
|
if (string.IsNullOrEmpty(args.Text))
|
|
{
|
|
Screen.Prototypes.PrototypeList.Visible = true;
|
|
Screen.Prototypes.SearchList.Visible = false;
|
|
return;
|
|
}
|
|
|
|
var matches = new List<MappingPrototype>();
|
|
foreach (var prototype in _allPrototypes)
|
|
{
|
|
if (prototype.Name.Contains(args.Text, OrdinalIgnoreCase))
|
|
matches.Add(prototype);
|
|
}
|
|
|
|
matches.Sort(static (a, b) => string.Compare(a.Name, b.Name, OrdinalIgnoreCase));
|
|
|
|
Screen.Prototypes.PrototypeList.Visible = false;
|
|
Screen.Prototypes.SearchList.Visible = true;
|
|
Screen.Prototypes.Search(matches);
|
|
}
|
|
|
|
private void OnCollapseAll(ButtonEventArgs args)
|
|
{
|
|
foreach (var child in Screen.Prototypes.PrototypeList.Children)
|
|
{
|
|
if (child is not MappingSpawnButton button)
|
|
continue;
|
|
|
|
Collapse(button);
|
|
}
|
|
|
|
Screen.Prototypes.ScrollContainer.SetScrollValue(new Vector2(0, 0));
|
|
}
|
|
|
|
private void OnClearSearch(ButtonEventArgs obj)
|
|
{
|
|
Screen.Prototypes.SearchBar.Text = string.Empty;
|
|
OnSearch(new LineEditEventArgs(Screen.Prototypes.SearchBar, string.Empty));
|
|
}
|
|
|
|
private void OnGetData(IPrototype prototype, List<Texture> textures)
|
|
{
|
|
switch (prototype)
|
|
{
|
|
case EntityPrototype entity:
|
|
textures.AddRange(_sprite.GetPrototypeTextures(entity).Select(t => t.Default));
|
|
break;
|
|
case DecalPrototype decal:
|
|
textures.Add(_sprite.Frame0(decal.Sprite));
|
|
break;
|
|
case ContentTileDefinition tile:
|
|
if (tile.Sprite?.ToString() is { } sprite)
|
|
textures.Add(_resources.GetResource<TextureResource>(sprite).Texture);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void OnSelected(MappingPrototype mapping)
|
|
{
|
|
if (mapping.Prototype == null)
|
|
return;
|
|
|
|
var chain = new Stack<MappingPrototype>();
|
|
chain.Push(mapping);
|
|
|
|
var parent = mapping.Parents?.FirstOrDefault();
|
|
while (parent != null)
|
|
{
|
|
chain.Push(parent);
|
|
parent = parent.Parents?.FirstOrDefault();
|
|
}
|
|
|
|
_lastClicked = null;
|
|
|
|
Control? last = null;
|
|
var children = Screen.Prototypes.PrototypeList.Children;
|
|
foreach (var prototype in chain)
|
|
{
|
|
foreach (var child in children)
|
|
{
|
|
if (child is MappingSpawnButton button &&
|
|
button.Prototype == prototype)
|
|
{
|
|
UnCollapse(button);
|
|
OnSelected(button, prototype.Prototype);
|
|
children = button.ChildrenPrototypes.Children;
|
|
last = child;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (last != null && Screen.Prototypes.PrototypeList.Visible)
|
|
_scrollTo = last;
|
|
}
|
|
|
|
private void OnSelected(MappingSpawnButton button, IPrototype? prototype)
|
|
{
|
|
var time = _timing.CurTime;
|
|
if (prototype is DecalPrototype)
|
|
Screen.SelectDecal(prototype.ID);
|
|
|
|
// Double-click functionality if it's collapsible.
|
|
if (_lastClicked is { } lastClicked &&
|
|
lastClicked.Button == button &&
|
|
lastClicked.At > time - TimeSpan.FromSeconds(0.333) &&
|
|
string.IsNullOrEmpty(Screen.Prototypes.SearchBar.Text) &&
|
|
button.CollapseButton.Visible)
|
|
{
|
|
button.CollapseButton.Pressed = !button.CollapseButton.Pressed;
|
|
ToggleCollapse(button);
|
|
button.Button.Pressed = true;
|
|
Screen.Prototypes.Selected = button;
|
|
_lastClicked = null;
|
|
return;
|
|
}
|
|
|
|
// Toggle if it's the same button (at least if we just unclicked it).
|
|
if (!button.Button.Pressed && button.Prototype?.Prototype != null && _lastClicked?.Button == button)
|
|
{
|
|
_lastClicked = null;
|
|
Deselect();
|
|
return;
|
|
}
|
|
|
|
_lastClicked = (time, button);
|
|
|
|
if (button.Prototype == null)
|
|
return;
|
|
|
|
if (Screen.Prototypes.Selected is { } oldButton &&
|
|
oldButton != button)
|
|
{
|
|
Deselect();
|
|
}
|
|
|
|
Screen.EntityContainer.Visible = false;
|
|
Screen.DecalContainer.Visible = false;
|
|
|
|
switch (prototype)
|
|
{
|
|
case EntityPrototype entity:
|
|
{
|
|
var placementId = Screen.EntityPlacementMode.SelectedId;
|
|
|
|
var placement = new PlacementInformation
|
|
{
|
|
PlacementOption = placementId > 0 ? EntitySpawnWindow.InitOpts[placementId] : entity.PlacementMode,
|
|
EntityType = entity.ID,
|
|
IsTile = false
|
|
};
|
|
|
|
Screen.EntityContainer.Visible = true;
|
|
_decal.SetActive(false);
|
|
_placement.BeginPlacing(placement);
|
|
break;
|
|
}
|
|
case DecalPrototype decal:
|
|
_placement.Clear();
|
|
|
|
_decal.SetActive(true);
|
|
_decal.UpdateDecalInfo(decal.ID, Color.White, 0, true, 0, false);
|
|
Screen.DecalContainer.Visible = true;
|
|
break;
|
|
case ContentTileDefinition tile:
|
|
{
|
|
var placement = new PlacementInformation
|
|
{
|
|
PlacementOption = "AlignTileAny",
|
|
TileType = tile.TileId,
|
|
IsTile = true
|
|
};
|
|
|
|
_decal.SetActive(false);
|
|
_placement.BeginPlacing(placement);
|
|
break;
|
|
}
|
|
default:
|
|
_placement.Clear();
|
|
break;
|
|
}
|
|
|
|
Screen.Prototypes.Selected = button;
|
|
|
|
button.Button.Pressed = true;
|
|
}
|
|
|
|
private void Deselect()
|
|
{
|
|
if (Screen.Prototypes.Selected is { } selected)
|
|
{
|
|
selected.Button.Pressed = false;
|
|
Screen.Prototypes.Selected = null;
|
|
|
|
if (selected.Prototype?.Prototype is DecalPrototype)
|
|
{
|
|
_decal.SetActive(false);
|
|
Screen.DecalContainer.Visible = false;
|
|
}
|
|
|
|
if (selected.Prototype?.Prototype is EntityPrototype)
|
|
{
|
|
_placement.Clear();
|
|
}
|
|
|
|
if (selected.Prototype?.Prototype is ContentTileDefinition)
|
|
{
|
|
_placement.Clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnCollapseToggled(MappingSpawnButton button, ButtonToggledEventArgs args)
|
|
{
|
|
ToggleCollapse(button);
|
|
}
|
|
|
|
private void OnPickPressed(ButtonEventArgs args)
|
|
{
|
|
if (args.Button.Pressed)
|
|
EnablePick();
|
|
else
|
|
DisablePick();
|
|
}
|
|
|
|
private void OnDeletePressed(ButtonEventArgs obj)
|
|
{
|
|
if (obj.Button.Pressed)
|
|
EnableDelete();
|
|
else
|
|
DisableDelete();
|
|
}
|
|
|
|
private void OnEntityReplacePressed(ButtonToggledEventArgs args)
|
|
{
|
|
_placement.Replacement = args.Pressed;
|
|
}
|
|
|
|
private void OnEntityPlacementSelected(ItemSelectedEventArgs args)
|
|
{
|
|
Screen.EntityPlacementMode.SelectId(args.Id);
|
|
|
|
if (_placement.CurrentMode != null)
|
|
{
|
|
var placement = new PlacementInformation
|
|
{
|
|
PlacementOption = EntitySpawnWindow.InitOpts[args.Id],
|
|
EntityType = _placement.CurrentPermission!.EntityType,
|
|
TileType = _placement.CurrentPermission.TileType,
|
|
Range = 2,
|
|
IsTile = _placement.CurrentPermission.IsTile,
|
|
};
|
|
|
|
_placement.BeginPlacing(placement);
|
|
}
|
|
}
|
|
|
|
private void OnEraseEntityPressed(ButtonEventArgs args)
|
|
{
|
|
if (args.Button.Pressed == _placement.Eraser)
|
|
return;
|
|
|
|
if (args.Button.Pressed)
|
|
EnableEraser();
|
|
else
|
|
DisableEraser();
|
|
}
|
|
|
|
private void OnEraseDecalPressed(ButtonToggledEventArgs args)
|
|
{
|
|
_placement.Clear();
|
|
Deselect();
|
|
Screen.EraseEntityButton.Pressed = false;
|
|
_updatePlacement = true;
|
|
_updateEraseDecal = args.Pressed;
|
|
}
|
|
|
|
private void EnableEraser()
|
|
{
|
|
if (_placement.Eraser)
|
|
return;
|
|
|
|
_placement.Clear();
|
|
_placement.ToggleEraser();
|
|
Screen.EntityPlacementMode.Disabled = true;
|
|
Screen.EraseDecalButton.Pressed = false;
|
|
Deselect();
|
|
}
|
|
|
|
private void DisableEraser()
|
|
{
|
|
if (!_placement.Eraser)
|
|
return;
|
|
|
|
_placement.ToggleEraser();
|
|
Screen.EntityPlacementMode.Disabled = false;
|
|
}
|
|
|
|
private void EnablePick()
|
|
{
|
|
Screen.UnPressActionsExcept(Screen.Pick);
|
|
State = CursorState.Pick;
|
|
}
|
|
|
|
private void DisablePick()
|
|
{
|
|
Screen.Pick.Pressed = false;
|
|
State = CursorState.None;
|
|
}
|
|
|
|
private void EnableDelete()
|
|
{
|
|
Screen.UnPressActionsExcept(Screen.Delete);
|
|
State = CursorState.Delete;
|
|
EnableEraser();
|
|
}
|
|
|
|
private void DisableDelete()
|
|
{
|
|
Screen.Delete.Pressed = false;
|
|
State = CursorState.None;
|
|
DisableEraser();
|
|
}
|
|
|
|
private bool HandleMappingUnselect(in PointerInputCmdArgs args)
|
|
{
|
|
if (Screen.Prototypes.Selected is not { Prototype.Prototype: DecalPrototype })
|
|
return false;
|
|
|
|
Deselect();
|
|
return true;
|
|
}
|
|
|
|
private bool HandleSaveMap(in PointerInputCmdArgs args)
|
|
{
|
|
#if FULL_RELEASE
|
|
return false;
|
|
#endif
|
|
if (!_admin.IsAdmin(true) || !_admin.HasFlag(AdminFlags.Host))
|
|
return false;
|
|
|
|
SaveMap();
|
|
return true;
|
|
}
|
|
|
|
private bool HandleEnablePick(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
|
{
|
|
EnablePick();
|
|
return true;
|
|
}
|
|
|
|
private bool HandleDisablePick(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
|
{
|
|
DisablePick();
|
|
return true;
|
|
}
|
|
|
|
private bool HandleEnableDelete(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
|
{
|
|
EnableDelete();
|
|
return true;
|
|
}
|
|
|
|
private bool HandleDisableDelete(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
|
{
|
|
DisableDelete();
|
|
return true;
|
|
}
|
|
|
|
private bool HandlePick(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
|
{
|
|
if (State != CursorState.Pick)
|
|
return false;
|
|
|
|
MappingPrototype? button = null;
|
|
|
|
// Try and get tile under it
|
|
// TODO: Separate mode for decals.
|
|
if (!uid.IsValid())
|
|
{
|
|
var mapPos = _transform.ToMapCoordinates(coords);
|
|
|
|
if (_mapMan.TryFindGridAt(mapPos, out var gridUid, out var grid) &&
|
|
_entityManager.System<SharedMapSystem>().TryGetTileRef(gridUid, grid, coords, out var tileRef) &&
|
|
_allPrototypesDict.TryGetValue(tileRef.GetContentTileDefinition(), out button))
|
|
{
|
|
OnSelected(button);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (button == null)
|
|
{
|
|
if (uid == EntityUid.Invalid ||
|
|
_entityManager.GetComponentOrNull<MetaDataComponent>(uid) is not { EntityPrototype: { } prototype } ||
|
|
!_allPrototypesDict.TryGetValue(prototype, out button))
|
|
{
|
|
// we always block other input handlers if pick mode is enabled
|
|
// this makes you not accidentally place something in space because you
|
|
// miss-clicked while holding down the pick hotkey
|
|
return true;
|
|
}
|
|
|
|
// Selected an entity
|
|
OnSelected(button);
|
|
|
|
// Match rotation
|
|
_placement.Direction = _entityManager.GetComponent<TransformComponent>(uid).LocalRotation.GetDir();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private bool HandleEditorCancelPlace(ICommonSession? session, EntityCoordinates coords, EntityUid uid)
|
|
{
|
|
if (!Screen.EraseDecalButton.Pressed)
|
|
return false;
|
|
|
|
_entityNetwork.SendSystemNetworkMessage(new RequestDecalRemovalEvent(_entityManager.GetNetCoordinates(coords)));
|
|
return true;
|
|
}
|
|
|
|
private bool HandleCancelEraseDecal(in PointerInputCmdArgs args)
|
|
{
|
|
if (!Screen.EraseDecalButton.Pressed)
|
|
return false;
|
|
|
|
Screen.EraseDecalButton.Pressed = false;
|
|
return true;
|
|
}
|
|
|
|
private async void SaveMap()
|
|
{
|
|
await _mapping.SaveMap();
|
|
}
|
|
|
|
private void ToggleCollapse(MappingSpawnButton button)
|
|
{
|
|
if (button.CollapseButton.Pressed)
|
|
{
|
|
if (button.Prototype?.Children != null)
|
|
{
|
|
foreach (var child in button.Prototype.Children)
|
|
{
|
|
Screen.Prototypes.Insert(button.ChildrenPrototypes, child, true);
|
|
}
|
|
}
|
|
|
|
button.CollapseButton.Label.Text = "▼";
|
|
}
|
|
else
|
|
{
|
|
button.ChildrenPrototypes.DisposeAllChildren();
|
|
button.CollapseButton.Label.Text = "▶";
|
|
}
|
|
}
|
|
|
|
private void Collapse(MappingSpawnButton button)
|
|
{
|
|
if (!button.CollapseButton.Pressed)
|
|
return;
|
|
|
|
button.CollapseButton.Pressed = false;
|
|
ToggleCollapse(button);
|
|
}
|
|
|
|
|
|
private void UnCollapse(MappingSpawnButton button)
|
|
{
|
|
if (button.CollapseButton.Pressed)
|
|
return;
|
|
|
|
button.CollapseButton.Pressed = true;
|
|
ToggleCollapse(button);
|
|
}
|
|
|
|
public EntityUid? GetHoveredEntity()
|
|
{
|
|
if (UserInterfaceManager.CurrentlyHovered is not IViewportControl viewport ||
|
|
_input.MouseScreenPosition is not { IsValid: true } position)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var mapPos = viewport.PixelToMap(position.Position);
|
|
return GetClickedEntity(mapPos);
|
|
}
|
|
|
|
public override void FrameUpdate(FrameEventArgs e)
|
|
{
|
|
if (_updatePlacement)
|
|
{
|
|
_updatePlacement = false;
|
|
|
|
if (!_placement.IsActive && _decal.GetActiveDecal().Decal == null)
|
|
Deselect();
|
|
|
|
Screen.EraseEntityButton.Pressed = _placement.Eraser;
|
|
Screen.EraseDecalButton.Pressed = _updateEraseDecal;
|
|
Screen.EntityPlacementMode.Disabled = _placement.Eraser;
|
|
}
|
|
|
|
if (_scrollTo is not { } scrollTo)
|
|
return;
|
|
|
|
// this is not ideal but we wait until the control's height is computed to use
|
|
// its position to scroll to
|
|
if (scrollTo.Height > 0 && Screen.Prototypes.PrototypeList.Visible)
|
|
{
|
|
var y = scrollTo.GlobalPosition.Y - Screen.Prototypes.ScrollContainer.Height / 2 + scrollTo.Height;
|
|
var scroll = Screen.Prototypes.ScrollContainer;
|
|
scroll.SetScrollValue(scroll.GetScrollValue() + new Vector2(0, y));
|
|
_scrollTo = null;
|
|
}
|
|
}
|
|
|
|
|
|
// TODO this doesn't handle pressing down multiple state hotkeys at the moment
|
|
public enum CursorState
|
|
{
|
|
None,
|
|
Pick,
|
|
Delete
|
|
}
|
|
}
|