mirror of
https://github.com/DeltaV-Station/Delta-v.git
synced 2026-09-29 23:57:29 +00:00
* Fix the sensor monitoring console forcing a GC every 3 seconds (#38146) * Optimize sensor monitoring window graph drawing * Add shared static Vector2 pool for all GraphView instances * Address requested changes * remove lock * Update submodule to 264.0.0 (#38629) * Toy/Plushie Inhands and Wearables (#38514) * Fixed dependency injection and some other issues in a few places. * More issue fixes * patchfor broken NetworkConfiguratorLinkMenu (#38632) fix * Fix solutions flickering when transferring contents (#34838) * Use Solution clones when applying SolutionComponent states * Revert "Use Solution clones when applying SolutionComponent states" This reverts commit 013fd111cf92b22562e00f98a7aaa49bc4b4ed62. * Make Solution implement ICloneable and rename Clone method. * Copy CanReact value when cloning a Solution * Convert to IRobustCloneable * Fix typos in guidebook: Buisness → Business (#38636) * Fix typo in `MinorAntagonists.xml` Buisness → Business * Fix typo in `YourFirstCharacter.xml` Buisness → Business * Various Headphones Fixes and Tweaks (#38479) * sprites, wearables * neck * icon-on sprite * Cleanup prototype instantiation in `DamageTest` (#38639) Cleanup prototype instantiation in DamageTest * Cleanup prototype instantiation in `ExplosionSystem` (#38642) Cleanup prototype instantiation in ExplosionSystem * AddBodyPartCommand localization. (#38612) commit * fix ItemSlotsSystem debug assert (#38655) * Allow the Command & Super door remotes to use the access of their user. (Re-creation of PR due to changes to game balance) (#35536) * Added directional beacons (#38284) * Added directional beacons Signed-off-by: Nox38 <nebulousnox38@gmail.com> * Fixed names Signed-off-by: Nox38 <nebulousnox38@gmail.com> --------- Signed-off-by: Nox38 <nebulousnox38@gmail.com> * Power stat and nuke codes commands get some LEC love. (#38585) * commit * requested changes. * Dsay Dirty and Follow commands converted to LEC and localized. (#38666) * commit * whoopwhoopwhoop * Retro laser sprite fix (#38676) * Fixed everything except the icon Signed-off-by: Nox38 <nebulousnox38@gmail.com> * fixed icon Signed-off-by: Nox38 <nebulousnox38@gmail.com> --------- Signed-off-by: Nox38 <nebulousnox38@gmail.com> * fix water coolers (#38681) * Monochromacy typo fix (#38686) * fixes the typo * Fixed cloning looking for the trait, not the component, RE https://github.com/space-wizards/space-station-14/pull/38686#issuecomment-3025093504 * Apply suggestions from code review --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Validate `CloningSettingsPrototype`s (#38688) * Validate CloningSettingsPrototypes * Update Content.IntegrationTests/Tests/Cloning/CloningSettingsPrototypeTest.cs Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Check EventComponents too --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Add test of objective-related console commands (#36400) * Add test of objective add/list/remove commands * Not sure why we're validating test prototypes, but sure * We don't need a map * Improvements and fixups for New Status Effect API (#38660) * Predict healing and bloodstream (#38690) * initial commit * reapply 38126 * fix rootable * someone missed an important minus sign here * try this * fix * fix * reenable crit hits * cleanup * fix status time dirtying * fix * camelCase * Healing and bloodstream prediction fixes. * Resolving Wizard casting recall on nuke disk making it impossible to disarm (#38661) * Resolving Wizard Recall on Nuke disk making it impossible to disarm - Adding a DisarmBomb case to nuke status update loop - Changing a few methods and parameters to properly follow formatting standards - Updating some names to follow camelCase * Updating missed tag * Reverting DataField change Should prevent this preventative bugfix being a breaking change. * Switch HSV to the default colorspace for character customization (#38434) * Made HSV default for character editor * Adds/fixes comments to HSV defaulting * Added dropbox fix, potentially cursed * Revert "Added dropbox fix, potentially cursed" This reverts commit a709883366fbee813e839742125e70844672af29. --------- Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> * Fix: Don't deploy foldables when clicking on items inside containers (#38709) * Fix * Apply suggestions from code review --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Pressure Relief Valve (#36708) * initial system (this math is probably WRONG) * General code cleanup and OnExamined support (holy moly this code sucks) * UICode and related events foundation TODO: - Actually write the XAML UI and the underlying system - Un-shitcode the entire thing - Actually test everything... * Working UI code TODO: Make predicted, as this certainly isn't predicted. Even though I said it was. It isn't. * Remove one TODO for unshitcoding the examine code * Add reminder yea * Make predicted (defenitely isn't) (also defenitely isn't a copypaste from pressure pump code) * It's predicted! TODO: - Give it snazzy predicted visuals! - Have a different field for pressure entry, lest it gets bulldozed every UI update. * Improve gas pressure relief valve UI TODO: Reminder to reduce amount of dirties using deltafields * Implement DirtyField prediction * Entity<T> cleanup A lot of Entity<T> conversions and lukewarm cleanup. Also got caught copy pasting code in 4K UHD but it's not like you couldn't tell. * More cleanup and comments * Remove TODO comment on bulldozing window title * """refactoring""" - Move appearance out of shared and finally fix it. Pointless to predict appearance in this instance. - More Entity<T> conversions because I like them. - Move UI creation handling over entirely to the ActivatableUI system. - Fix a hardcoded locale string (why????). * Add visuals * Revert debugging variable replacememt yea * Revert skissue * Remove unused using directives and remove TODO * Localize, cleanup, document * Fix adminlogging discrepancy * Add ability to construct, add guidebook entry * Clear up comment * Add guidebook tooltip to valve * Convert GasPressureReliefValveBoundUserInterface declaration into primary constructor * Adds more input handling and adds autofill on open * Un-deepfry input validator shitcode Genuinely what was I smoking * improve visuals logic * Refactor again - Update math to the correct implementation - Moved code that could be re-used in the future into a helper method under AtmosphereSystem.Gases.cs * I'm sorry but I hate warnings * Remove unused using directive in AtmosphereSystem.Gases.cs * Review and cleanup * Lukewarm UI glossup * Maintainer for the upstream project btw * Remove redundant state sets and messy logic * Unduplicate valve updater code * Redo UI (im sorry Slarti) * run tests * Test refactored UI messaging * Second round of UI improvements - God please find a way to improve this system. Feels bad. * Update loop implementation * Further predict UI * Clear up SetToCurrentThreshold * cleanup * Update to master + pipe layers and bug fixes want to run tests * fixes * Deploy rename pipebomb * Documentation and requested changes * Rename the method that wiggled away * Undo rounding changes * Fix comment * Rename and cleanup * Apply suggestions from code review --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Fix TextLinkTag (#32203) * Adjust uplink buy button to be under item icon (#38596) * Adjusted uplink buy button to be under item icon * Put the discount subtext under the icon * Indent fixes, added margin --------- Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> * Scurrets - Audio Improvements (#38482) * Scurret audio tuning * Add new sfx * Update sneezing sfx * YAML support * Rename a folder * make telesci wreck easier (#37569) rel * Validate `ShaderPrototype` IDs (#38728) * Convert all shader prototype string literals to protoids in overlays * Convert more shader prototype literal strings to protoids * Convert ValidatePrototypeId to ProtoId * Later * [BUGFIX] Fixed revenant malfunction ability not working properly only MediBots and Stasis bed (#38664) * fixed * clean up * orks fix smart * review fix 1 * more requested changes * less cursed * more descriptive description * better wording * Tiny Tiny Cleanup of the EyeClosingSystem. (#38734) Update EyeClosingSystem.cs * Fix human skin tone distribution (#38701) * fix: Use PredictedQueueDel for gib spell (#38729) * fix: don't default to uncharged sprite state for cells (#38730) * Fix Hristov description - remove inaccurate technical specs (#38746) - Removes inaccurate 'armor piercing 14.5mm shells' reference - Replaces it with a more funny description, matching the style of the other snipers and guns - Keeps ammunition type '.60 anti-materiel ammo' specification - Fixes issue #38590 Co-authored-by: Arthur Fiorese de Andrade <aandrade@cmcxs.gov.br> * feat: allow mopping evaporating puddles (#38743) * Validate remaining `ProtoId` strings (#38747) Validate remaining ProtoId strings * Validate `ProtoId`s in tests (#38745) * Convert string literals to protoids in Content.Tests * Convert string literals to protoids or consts in Content.IntegrationTests * Fix linter failures Tricksy static using misled me * Cleanup warning in StomachSystem (#38748) you did not see this * Vox scars (#38592) * Added vox scars n'stuff, renamed vox_tattoos.ftl to just vox.ftl * Revert "Added vox scars n'stuff, renamed vox_tattoos.ftl to just vox.ftl" This reverts commit c73da55ba3b39ddf93b493aecd85604c54dd8a15. * locale key fix * Changed top surgery scar names to be more generalized * Adjusted face scars * Formatting fixes --------- Co-authored-by: TrixxedHeart <46364955+TrixxedBit@users.noreply.github.com> * UnlockNode command to LEC. (#38751) * commit * Update UnlockNodeCommand.cs * commit * move command locale to its own file. * Update Content.Server/Xenoarchaeology/Artifact/UnlockNodeCommand.cs --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Typofixes for figurine dialogue (#38737) * Typofixes for figurine dialogue * Forgot two * Janitor Tool: Wire Brush (#38667) * Wow! It's -brush- * spacing. * Update Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Update Resources/Prototypes/Entities/Objects/Specific/Janitorial/janitor.yml Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * fixed changes --------- Co-authored-by: slarticodefast <161409025+slarticodefast@users.noreply.github.com> * Minor escape menu UX improvements (#38650) * fix: spellbooks can have infinite charges (#38376) * fix: spellbooks can have infinite charges * refactor: indicate infinite spellbook charges with null Not sure if I like this much better...
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(_entityManager.System<TurfSystem>().GetContentTileDefinition(tileRef), 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
|
|
}
|
|
}
|