Add mapping editor (#23427)
* Add mapping editor (#757) * Remove mapping actions, never again * Cleanup actions system * Jarvis, remove all references to CM14 * Fix InventoryUIController crashing when an InventoryGui is not found * Rename mapping1 to mapping * Clean up context calls * Add doc comments * Add delegate for hiding decals in the mapping screen * Jarvis mission failed * a * Add test * Fix not flushing save stream in mapping manager * change * Fix verbs * fixes * localise --------- Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com> Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com> Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
This commit is contained in:
@@ -293,7 +293,7 @@ namespace Content.Client.Actions
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true);
|
var action = _serialization.Read<BaseActionComponent>(actionNode, notNullableOverride: true);
|
||||||
var actionId = Spawn(null);
|
var actionId = Spawn();
|
||||||
AddComp(actionId, action);
|
AddComp(actionId, action);
|
||||||
AddActionDirect(user, actionId);
|
AddActionDirect(user, actionId);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Content.Client.Actions;
|
||||||
using Content.Client.Actions;
|
using Content.Client.Actions;
|
||||||
using Content.Client.Mapping;
|
using Content.Client.Mapping;
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
@@ -61,27 +62,3 @@ public sealed class LoadActionsCommand : LocalizedCommands
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[AnyCommand]
|
|
||||||
public sealed class LoadMappingActionsCommand : LocalizedCommands
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
|
||||||
|
|
||||||
public const string CommandName = "loadmapacts";
|
|
||||||
|
|
||||||
public override string Command => CommandName;
|
|
||||||
|
|
||||||
public override string Help => LocalizationManager.GetString($"cmd-{Command}-help", ("command", Command));
|
|
||||||
|
|
||||||
public override void Execute(IConsoleShell shell, string argStr, string[] args)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_entitySystemManager.GetEntitySystem<MappingSystem>().LoadMappingActions();
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
shell.WriteError(LocalizationManager.GetString($"cmd-{Command}-error"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
using Content.Client.Mapping;
|
||||||
using Content.Client.Markers;
|
using Content.Client.Markers;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.State;
|
||||||
using Robust.Shared.Console;
|
using Robust.Shared.Console;
|
||||||
|
|
||||||
namespace Content.Client.Commands;
|
namespace Content.Client.Commands;
|
||||||
@@ -10,6 +12,7 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
[Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
|
||||||
[Dependency] private readonly ILightManager _lightManager = default!;
|
[Dependency] private readonly ILightManager _lightManager = default!;
|
||||||
|
[Dependency] private readonly IStateManager _stateManager = default!;
|
||||||
|
|
||||||
public override string Command => "mappingclientsidesetup";
|
public override string Command => "mappingclientsidesetup";
|
||||||
|
|
||||||
@@ -21,8 +24,8 @@ internal sealed class MappingClientSideSetupCommand : LocalizedCommands
|
|||||||
{
|
{
|
||||||
_entitySystemManager.GetEntitySystem<MarkerSystem>().MarkersVisible = true;
|
_entitySystemManager.GetEntitySystem<MarkerSystem>().MarkersVisible = true;
|
||||||
_lightManager.Enabled = false;
|
_lightManager.Enabled = false;
|
||||||
shell.ExecuteCommand(ShowSubFloorForever.CommandName);
|
shell.ExecuteCommand("showsubfloorforever");
|
||||||
shell.ExecuteCommand(LoadMappingActionsCommand.CommandName);
|
_stateManager.RequestStateChange<MappingState>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Numerics;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using Content.Client.CombatMode;
|
using Content.Client.CombatMode;
|
||||||
using Content.Client.Gameplay;
|
using Content.Client.Gameplay;
|
||||||
|
using Content.Client.Mapping;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controllers;
|
using Robust.Client.UserInterface.Controllers;
|
||||||
using Timer = Robust.Shared.Timing.Timer;
|
using Timer = Robust.Shared.Timing.Timer;
|
||||||
@@ -16,7 +17,7 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This largely involves setting up timers to open and close sub-menus when hovering over other menu elements.
|
/// This largely involves setting up timers to open and close sub-menus when hovering over other menu elements.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public sealed class ContextMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CombatModeSystem>
|
public sealed class ContextMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>, IOnSystemChanged<CombatModeSystem>, IOnStateEntered<MappingState>, IOnStateExited<MappingState>
|
||||||
{
|
{
|
||||||
public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2);
|
public static readonly TimeSpan HoverDelay = TimeSpan.FromSeconds(0.2);
|
||||||
|
|
||||||
@@ -42,18 +43,51 @@ namespace Content.Client.ContextMenu.UI
|
|||||||
public Action<ContextMenuElement>? OnSubMenuOpened;
|
public Action<ContextMenuElement>? OnSubMenuOpened;
|
||||||
public Action<ContextMenuElement, GUIBoundKeyEventArgs>? OnContextKeyEvent;
|
public Action<ContextMenuElement, GUIBoundKeyEventArgs>? OnContextKeyEvent;
|
||||||
|
|
||||||
|
private bool _setup;
|
||||||
|
|
||||||
public void OnStateEntered(GameplayState state)
|
public void OnStateEntered(GameplayState state)
|
||||||
{
|
{
|
||||||
|
Setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnStateExited(GameplayState state)
|
||||||
|
{
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnStateEntered(MappingState state)
|
||||||
|
{
|
||||||
|
Setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnStateExited(MappingState state)
|
||||||
|
{
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
if (_setup)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_setup = true;
|
||||||
|
|
||||||
RootMenu = new(this, null);
|
RootMenu = new(this, null);
|
||||||
RootMenu.OnPopupHide += Close;
|
RootMenu.OnPopupHide += Close;
|
||||||
Menus.Push(RootMenu);
|
Menus.Push(RootMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnStateExited(GameplayState state)
|
public void Shutdown()
|
||||||
{
|
{
|
||||||
|
if (!_setup)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_setup = false;
|
||||||
|
|
||||||
Close();
|
Close();
|
||||||
RootMenu.OnPopupHide -= Close;
|
RootMenu.OnPopupHide -= Close;
|
||||||
RootMenu.Dispose();
|
RootMenu.Dispose();
|
||||||
|
RootMenu = default!;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Robust.Client.Graphics;
|
|||||||
using Robust.Client.Input;
|
using Robust.Client.Input;
|
||||||
using Robust.Shared.Enums;
|
using Robust.Shared.Enums;
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
namespace Content.Client.Decals.Overlays;
|
namespace Content.Client.Decals.Overlays;
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ public sealed class DecalPlacementOverlay : Overlay
|
|||||||
private readonly SharedTransformSystem _transform;
|
private readonly SharedTransformSystem _transform;
|
||||||
private readonly SpriteSystem _sprite;
|
private readonly SpriteSystem _sprite;
|
||||||
|
|
||||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
public override OverlaySpace Space => OverlaySpace.WorldSpaceEntities;
|
||||||
|
|
||||||
public DecalPlacementOverlay(DecalPlacementSystem placement, SharedTransformSystem transform, SpriteSystem sprite)
|
public DecalPlacementOverlay(DecalPlacementSystem placement, SharedTransformSystem transform, SpriteSystem sprite)
|
||||||
{
|
{
|
||||||
@@ -24,6 +25,7 @@ public sealed class DecalPlacementOverlay : Overlay
|
|||||||
_placement = placement;
|
_placement = placement;
|
||||||
_transform = transform;
|
_transform = transform;
|
||||||
_sprite = sprite;
|
_sprite = sprite;
|
||||||
|
ZIndex = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Draw(in OverlayDrawArgs args)
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
@@ -55,7 +57,7 @@ public sealed class DecalPlacementOverlay : Overlay
|
|||||||
|
|
||||||
if (snap)
|
if (snap)
|
||||||
{
|
{
|
||||||
localPos = (Vector2) localPos.Floored() + grid.TileSizeHalfVector;
|
localPos = localPos.Floored() + grid.TileSizeHalfVector;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nothing uses snap cardinals so probably don't need preview?
|
// Nothing uses snap cardinals so probably don't need preview?
|
||||||
|
|||||||
@@ -4,23 +4,23 @@ using Content.Client.Chat.Managers;
|
|||||||
using Content.Client.Clickable;
|
using Content.Client.Clickable;
|
||||||
using Content.Client.DebugMon;
|
using Content.Client.DebugMon;
|
||||||
using Content.Client.Eui;
|
using Content.Client.Eui;
|
||||||
|
using Content.Client.Fullscreen;
|
||||||
using Content.Client.GhostKick;
|
using Content.Client.GhostKick;
|
||||||
|
using Content.Client.Guidebook;
|
||||||
using Content.Client.Launcher;
|
using Content.Client.Launcher;
|
||||||
|
using Content.Client.Mapping;
|
||||||
using Content.Client.Parallax.Managers;
|
using Content.Client.Parallax.Managers;
|
||||||
using Content.Client.Players.PlayTimeTracking;
|
using Content.Client.Players.PlayTimeTracking;
|
||||||
|
using Content.Client.Replay;
|
||||||
using Content.Client.Screenshot;
|
using Content.Client.Screenshot;
|
||||||
using Content.Client.Fullscreen;
|
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
using Content.Client.Viewport;
|
using Content.Client.Viewport;
|
||||||
using Content.Client.Voting;
|
using Content.Client.Voting;
|
||||||
using Content.Shared.Administration.Logs;
|
using Content.Shared.Administration.Logs;
|
||||||
using Content.Client.Guidebook;
|
|
||||||
using Content.Client.Lobby;
|
using Content.Client.Lobby;
|
||||||
using Content.Client.Replay;
|
|
||||||
using Content.Shared.Administration.Managers;
|
using Content.Shared.Administration.Managers;
|
||||||
using Content.Shared.Players.PlayTimeTracking;
|
using Content.Shared.Players.PlayTimeTracking;
|
||||||
|
|
||||||
|
|
||||||
namespace Content.Client.IoC
|
namespace Content.Client.IoC
|
||||||
{
|
{
|
||||||
internal static class ClientContentIoC
|
internal static class ClientContentIoC
|
||||||
@@ -49,6 +49,7 @@ namespace Content.Client.IoC
|
|||||||
collection.Register<DocumentParsingManager>();
|
collection.Register<DocumentParsingManager>();
|
||||||
collection.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
|
collection.Register<ContentReplayPlaybackManager, ContentReplayPlaybackManager>();
|
||||||
collection.Register<ISharedPlaytimeManager, JobRequirementsManager>();
|
collection.Register<ISharedPlaytimeManager, JobRequirementsManager>();
|
||||||
|
collection.Register<MappingManager>();
|
||||||
collection.Register<DebugMonitorManager>();
|
collection.Register<DebugMonitorManager>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
Content.Client/Mapping/MappingActionsButton.xaml
Normal file
8
Content.Client/Mapping/MappingActionsButton.xaml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<mapping:MappingActionsButton
|
||||||
|
xmlns="https://spacestation14.io"
|
||||||
|
xmlns:mapping="clr-namespace:Content.Client.Mapping"
|
||||||
|
StyleClasses="ButtonSquare" ToggleMode="True" SetSize="32 32" Margin="0 0 5 0"
|
||||||
|
TooltipDelay="0">
|
||||||
|
<TextureRect Name="Texture" Access="Public" Stretch="Scale" SetSize="16 16"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||||
|
</mapping:MappingActionsButton>
|
||||||
15
Content.Client/Mapping/MappingActionsButton.xaml.cs
Normal file
15
Content.Client/Mapping/MappingActionsButton.xaml.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client.Mapping;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class MappingActionsButton : Button
|
||||||
|
{
|
||||||
|
public MappingActionsButton()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
4
Content.Client/Mapping/MappingDoNotMeasure.xaml
Normal file
4
Content.Client/Mapping/MappingDoNotMeasure.xaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<mapping:MappingDoNotMeasure
|
||||||
|
xmlns="https://spacestation14.io"
|
||||||
|
xmlns:mapping="clr-namespace:Content.Client.Mapping">
|
||||||
|
</mapping:MappingDoNotMeasure>
|
||||||
21
Content.Client/Mapping/MappingDoNotMeasure.xaml.cs
Normal file
21
Content.Client/Mapping/MappingDoNotMeasure.xaml.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client.Mapping;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class MappingDoNotMeasure : Control
|
||||||
|
{
|
||||||
|
public MappingDoNotMeasure()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Vector2 MeasureOverride(Vector2 availableSize)
|
||||||
|
{
|
||||||
|
return Vector2.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
69
Content.Client/Mapping/MappingManager.cs
Normal file
69
Content.Client/Mapping/MappingManager.cs
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Content.Shared.Mapping;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Client.Mapping;
|
||||||
|
|
||||||
|
public sealed class MappingManager : IPostInjectInit
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IFileDialogManager _file = default!;
|
||||||
|
[Dependency] private readonly IClientNetManager _net = default!;
|
||||||
|
|
||||||
|
private Stream? _saveStream;
|
||||||
|
private MappingMapDataMessage? _mapData;
|
||||||
|
|
||||||
|
public void PostInject()
|
||||||
|
{
|
||||||
|
_net.RegisterNetMessage<MappingSaveMapMessage>();
|
||||||
|
_net.RegisterNetMessage<MappingSaveMapErrorMessage>(OnSaveError);
|
||||||
|
_net.RegisterNetMessage<MappingMapDataMessage>(OnMapData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSaveError(MappingSaveMapErrorMessage message)
|
||||||
|
{
|
||||||
|
_saveStream?.DisposeAsync();
|
||||||
|
_saveStream = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void OnMapData(MappingMapDataMessage message)
|
||||||
|
{
|
||||||
|
if (_saveStream == null)
|
||||||
|
{
|
||||||
|
_mapData = message;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await _saveStream.WriteAsync(Encoding.ASCII.GetBytes(message.Yml));
|
||||||
|
await _saveStream.DisposeAsync();
|
||||||
|
|
||||||
|
_saveStream = null;
|
||||||
|
_mapData = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SaveMap()
|
||||||
|
{
|
||||||
|
if (_saveStream != null)
|
||||||
|
await _saveStream.DisposeAsync();
|
||||||
|
|
||||||
|
var request = new MappingSaveMapMessage();
|
||||||
|
_net.ClientSendMessage(request);
|
||||||
|
|
||||||
|
var path = await _file.SaveFile();
|
||||||
|
if (path is not { fileStream: var stream })
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (_mapData != null)
|
||||||
|
{
|
||||||
|
await stream.WriteAsync(Encoding.ASCII.GetBytes(_mapData.Yml));
|
||||||
|
_mapData = null;
|
||||||
|
await stream.FlushAsync();
|
||||||
|
await stream.DisposeAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_saveStream = stream;
|
||||||
|
}
|
||||||
|
}
|
||||||
84
Content.Client/Mapping/MappingOverlay.cs
Normal file
84
Content.Client/Mapping/MappingOverlay.cs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.Input;
|
||||||
|
using Robust.Client.Player;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Shared.Enums;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using static Content.Client.Mapping.MappingState;
|
||||||
|
|
||||||
|
namespace Content.Client.Mapping;
|
||||||
|
|
||||||
|
public sealed class MappingOverlay : Overlay
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entities = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _player = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypes = default!;
|
||||||
|
|
||||||
|
// 1 off in case something else uses these colors since we use them to compare
|
||||||
|
private static readonly Color PickColor = new(1, 255, 0);
|
||||||
|
private static readonly Color DeleteColor = new(255, 1, 0);
|
||||||
|
|
||||||
|
private readonly Dictionary<EntityUid, Color> _oldColors = new();
|
||||||
|
|
||||||
|
private readonly MappingState _state;
|
||||||
|
private readonly ShaderInstance _shader;
|
||||||
|
|
||||||
|
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||||
|
|
||||||
|
public MappingOverlay(MappingState state)
|
||||||
|
{
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
_state = state;
|
||||||
|
_shader = _prototypes.Index<ShaderPrototype>("unshaded").Instance();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Draw(in OverlayDrawArgs args)
|
||||||
|
{
|
||||||
|
foreach (var (id, color) in _oldColors)
|
||||||
|
{
|
||||||
|
if (!_entities.TryGetComponent(id, out SpriteComponent? sprite))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (sprite.Color == DeleteColor || sprite.Color == PickColor)
|
||||||
|
sprite.Color = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
_oldColors.Clear();
|
||||||
|
|
||||||
|
if (_player.LocalEntity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var handle = args.WorldHandle;
|
||||||
|
handle.UseShader(_shader);
|
||||||
|
|
||||||
|
switch (_state.State)
|
||||||
|
{
|
||||||
|
case CursorState.Pick:
|
||||||
|
{
|
||||||
|
if (_state.GetHoveredEntity() is { } entity &&
|
||||||
|
_entities.TryGetComponent(entity, out SpriteComponent? sprite))
|
||||||
|
{
|
||||||
|
_oldColors[entity] = sprite.Color;
|
||||||
|
sprite.Color = PickColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CursorState.Delete:
|
||||||
|
{
|
||||||
|
if (_state.GetHoveredEntity() is { } entity &&
|
||||||
|
_entities.TryGetComponent(entity, out SpriteComponent? sprite))
|
||||||
|
{
|
||||||
|
_oldColors[entity] = sprite.Color;
|
||||||
|
sprite.Color = DeleteColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handle.UseShader(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
Content.Client/Mapping/MappingPrototype.cs
Normal file
39
Content.Client/Mapping/MappingPrototype.cs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
using Content.Shared.Decals;
|
||||||
|
using Content.Shared.Maps;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client.Mapping;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to represent a button's data in the mapping editor.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class MappingPrototype
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The prototype instance, if any.
|
||||||
|
/// Can be one of <see cref="EntityPrototype"/>, <see cref="ContentTileDefinition"/> or <see cref="DecalPrototype"/>
|
||||||
|
/// If null, this is a top-level button (such as Entities, Tiles or Decals)
|
||||||
|
/// </summary>
|
||||||
|
public readonly IPrototype? Prototype;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The text to display on the UI for this button.
|
||||||
|
/// </summary>
|
||||||
|
public readonly string Name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Which other prototypes (buttons) this one is nested inside of.
|
||||||
|
/// </summary>
|
||||||
|
public List<MappingPrototype>? Parents;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Which other prototypes (buttons) are nested inside this one.
|
||||||
|
/// </summary>
|
||||||
|
public List<MappingPrototype>? Children;
|
||||||
|
|
||||||
|
public MappingPrototype(IPrototype? prototype, string name)
|
||||||
|
{
|
||||||
|
Prototype = prototype;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Content.Client/Mapping/MappingPrototypeList.xaml
Normal file
21
Content.Client/Mapping/MappingPrototypeList.xaml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<mapping:MappingPrototypeList
|
||||||
|
xmlns="https://spacestation14.io"
|
||||||
|
xmlns:mapping="clr-namespace:Content.Client.Mapping">
|
||||||
|
<BoxContainer Orientation="Vertical">
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<Button Name="CollapseAllButton" Access="Public" Text="-" SetSize="48 48"
|
||||||
|
StyleClasses="ButtonSquare" ToolTip="Collapse All" TooltipDelay="0" />
|
||||||
|
<LineEdit Name="SearchBar" SetHeight="48" HorizontalExpand="True" Access="Public" />
|
||||||
|
<Button Name="ClearSearchButton" Access="Public" Text="X" SetSize="48 48"
|
||||||
|
StyleClasses="ButtonSquare" />
|
||||||
|
</BoxContainer>
|
||||||
|
<ScrollContainer Name="ScrollContainer" Access="Public" VerticalExpand="True"
|
||||||
|
ReserveScrollbarSpace="True">
|
||||||
|
<BoxContainer Name="PrototypeList" Access="Public" Orientation="Vertical" />
|
||||||
|
<PrototypeListContainer Name="SearchList" Access="Public" Visible="False" />
|
||||||
|
</ScrollContainer>
|
||||||
|
<mapping:MappingDoNotMeasure Visible="False">
|
||||||
|
<mapping:MappingSpawnButton Name="MeasureButton" Access="Public" />
|
||||||
|
</mapping:MappingDoNotMeasure>
|
||||||
|
</BoxContainer>
|
||||||
|
</mapping:MappingPrototypeList>
|
||||||
170
Content.Client/Mapping/MappingPrototypeList.xaml.cs
Normal file
170
Content.Client/Mapping/MappingPrototypeList.xaml.cs
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
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.Prototypes;
|
||||||
|
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||||
|
|
||||||
|
namespace Content.Client.Mapping;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class MappingPrototypeList : Control
|
||||||
|
{
|
||||||
|
private (int start, int end) _lastIndices;
|
||||||
|
private readonly List<MappingPrototype> _prototypes = new();
|
||||||
|
private readonly List<Texture> _insertTextures = new();
|
||||||
|
private readonly List<MappingPrototype> _search = new();
|
||||||
|
|
||||||
|
public MappingSpawnButton? Selected;
|
||||||
|
public Action<IPrototype, List<Texture>>? GetPrototypeData;
|
||||||
|
public event Action<MappingSpawnButton, IPrototype?>? SelectionChanged;
|
||||||
|
public event Action<MappingSpawnButton, ButtonToggledEventArgs>? CollapseToggled;
|
||||||
|
|
||||||
|
public MappingPrototypeList()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
MeasureButton.Measure(Vector2Helpers.Infinity);
|
||||||
|
|
||||||
|
ScrollContainer.OnScrolled += UpdateSearch;
|
||||||
|
OnResized += UpdateSearch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateVisible(List<MappingPrototype> prototypes)
|
||||||
|
{
|
||||||
|
_prototypes.Clear();
|
||||||
|
|
||||||
|
PrototypeList.DisposeAllChildren();
|
||||||
|
|
||||||
|
_prototypes.AddRange(prototypes);
|
||||||
|
|
||||||
|
Selected = null;
|
||||||
|
ScrollContainer.SetScrollValue(new Vector2(0, 0));
|
||||||
|
|
||||||
|
foreach (var prototype in _prototypes)
|
||||||
|
{
|
||||||
|
Insert(PrototypeList, prototype, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MappingSpawnButton Insert(Container list, MappingPrototype mapping, bool includeChildren)
|
||||||
|
{
|
||||||
|
var prototype = mapping.Prototype;
|
||||||
|
|
||||||
|
_insertTextures.Clear();
|
||||||
|
|
||||||
|
if (prototype != null)
|
||||||
|
GetPrototypeData?.Invoke(prototype, _insertTextures);
|
||||||
|
|
||||||
|
var button = new MappingSpawnButton { Prototype = mapping };
|
||||||
|
button.Label.Text = mapping.Name;
|
||||||
|
|
||||||
|
if (_insertTextures.Count > 0)
|
||||||
|
{
|
||||||
|
button.Texture.Textures.AddRange(_insertTextures);
|
||||||
|
button.Texture.InvalidateMeasure();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
button.Texture.Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prototype != null && button.Prototype == Selected?.Prototype)
|
||||||
|
{
|
||||||
|
Selected = button;
|
||||||
|
button.Button.Pressed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.AddChild(button);
|
||||||
|
|
||||||
|
button.Button.OnToggled += _ => SelectionChanged?.Invoke(button, prototype);
|
||||||
|
|
||||||
|
if (includeChildren && mapping.Children?.Count > 0)
|
||||||
|
{
|
||||||
|
button.CollapseButton.Visible = true;
|
||||||
|
button.CollapseButton.OnToggled += args => CollapseToggled?.Invoke(button, args);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
button.CollapseButtonWrapper.Visible = false;
|
||||||
|
button.CollapseButton.Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Search(List<MappingPrototype> prototypes)
|
||||||
|
{
|
||||||
|
_search.Clear();
|
||||||
|
SearchList.DisposeAllChildren();
|
||||||
|
_lastIndices = (0, -1);
|
||||||
|
|
||||||
|
_search.AddRange(prototypes);
|
||||||
|
SearchList.TotalItemCount = _search.Count;
|
||||||
|
ScrollContainer.SetScrollValue(new Vector2(0, 0));
|
||||||
|
|
||||||
|
UpdateSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Constructs a virtual list where not all buttons exist at one time, since there may be thousands of them.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateSearch()
|
||||||
|
{
|
||||||
|
if (!SearchList.Visible)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var height = MeasureButton.DesiredSize.Y + PrototypeListContainer.Separation;
|
||||||
|
var offset = Math.Max(-SearchList.Position.Y, 0);
|
||||||
|
var startIndex = (int) Math.Floor(offset / height);
|
||||||
|
SearchList.ItemOffset = startIndex;
|
||||||
|
|
||||||
|
var (prevStart, prevEnd) = _lastIndices;
|
||||||
|
var endIndex = startIndex - 1;
|
||||||
|
var spaceUsed = -height;
|
||||||
|
|
||||||
|
// calculate how far down we are scrolled
|
||||||
|
while (spaceUsed < SearchList.Parent!.Height)
|
||||||
|
{
|
||||||
|
spaceUsed += height;
|
||||||
|
endIndex += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
endIndex = Math.Min(endIndex, _search.Count - 1);
|
||||||
|
|
||||||
|
// nothing changed in terms of which buttons are visible now and before
|
||||||
|
if (endIndex == prevEnd && startIndex == prevStart)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_lastIndices = (startIndex, endIndex);
|
||||||
|
|
||||||
|
// remove previously seen but now unseen buttons from the top
|
||||||
|
for (var i = prevStart; i < startIndex && i <= prevEnd; i++)
|
||||||
|
{
|
||||||
|
var control = SearchList.GetChild(0);
|
||||||
|
SearchList.RemoveChild(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove previously seen but now unseen buttons from the bottom
|
||||||
|
for (var i = prevEnd; i > endIndex && i >= prevStart; i--)
|
||||||
|
{
|
||||||
|
var control = SearchList.GetChild(SearchList.ChildCount - 1);
|
||||||
|
SearchList.RemoveChild(control);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert buttons that can now be seen, from the start
|
||||||
|
for (var i = Math.Min(prevStart - 1, endIndex); i >= startIndex; i--)
|
||||||
|
{
|
||||||
|
Insert(SearchList, _search[i], false).SetPositionInParent(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// insert buttons that can now be seen, from the end
|
||||||
|
for (var i = Math.Max(prevEnd + 1, startIndex); i <= endIndex; i++)
|
||||||
|
{
|
||||||
|
Insert(SearchList, _search[i], false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
Content.Client/Mapping/MappingScreen.xaml
Normal file
85
Content.Client/Mapping/MappingScreen.xaml
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<mapping:MappingScreen
|
||||||
|
xmlns="https://spacestation14.io"
|
||||||
|
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
|
||||||
|
xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Chat.Widgets"
|
||||||
|
xmlns:hotbar="clr-namespace:Content.Client.UserInterface.Systems.Hotbar.Widgets"
|
||||||
|
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||||
|
xmlns:mapping="clr-namespace:Content.Client.Mapping"
|
||||||
|
VerticalExpand="False"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
HorizontalAlignment="Center">
|
||||||
|
<controls:RecordedSplitContainer Name="ScreenContainer" HorizontalExpand="True"
|
||||||
|
VerticalExpand="True" SplitWidth="0"
|
||||||
|
StretchDirection="TopLeft">
|
||||||
|
<BoxContainer Orientation="Vertical" VerticalExpand="True" Name="SpawnContainer" MinWidth="200" SetWidth="600">
|
||||||
|
<mapping:MappingPrototypeList Name="Prototypes" Access="Public" VerticalExpand="True" />
|
||||||
|
<BoxContainer Name="DecalContainer" Access="Public" Orientation="Horizontal"
|
||||||
|
Visible="False">
|
||||||
|
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||||
|
<ColorSelectorSliders Name="DecalColorPicker" IsAlphaVisible="True" />
|
||||||
|
<Button Name="DecalPickerOpen" Text="{Loc decal-placer-window-palette}"
|
||||||
|
StyleClasses="ButtonSquare" />
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer Orientation="Vertical" HorizontalExpand="True">
|
||||||
|
<CheckBox Name="DecalEnableAuto" Margin="0 0 0 10"
|
||||||
|
Text="{Loc decal-placer-window-enable-auto}" />
|
||||||
|
<CheckBox Name="DecalEnableSnap"
|
||||||
|
Text="{Loc decal-placer-window-enable-snap}" />
|
||||||
|
<CheckBox Name="DecalEnableCleanable"
|
||||||
|
Text="{Loc decal-placer-window-enable-cleanable}" />
|
||||||
|
<BoxContainer Name="DecalSpinBoxContainer" Orientation="Horizontal">
|
||||||
|
<Label Text="{Loc decal-placer-window-rotation}" Margin="0 0 0 1" />
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<Label Text="{Loc decal-placer-window-zindex}" Margin="0 0 0 1" />
|
||||||
|
<SpinBox Name="DecalZIndexSpinBox" HorizontalExpand="True" />
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer Name="EntityContainer" Access="Public" Orientation="Horizontal"
|
||||||
|
Visible="False">
|
||||||
|
<Button Name="EntityReplaceButton" Access="Public" ToggleMode="True"
|
||||||
|
SetHeight="48"
|
||||||
|
StyleClasses="ButtonSquare" Text="{Loc 'mapping-replace'}" HorizontalExpand="True" />
|
||||||
|
<OptionButton Name="EntityPlacementMode" Access="Public"
|
||||||
|
SetHeight="48"
|
||||||
|
StyleClasses="ButtonSquare" TooltipDelay="0"
|
||||||
|
ToolTip="{Loc entity-spawn-window-override-menu-tooltip}"
|
||||||
|
HorizontalExpand="True" />
|
||||||
|
</BoxContainer>
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<Button Name="EraseEntityButton" Access="Public" HorizontalExpand="True"
|
||||||
|
SetHeight="48"
|
||||||
|
ToggleMode="True" Text="{Loc 'mapping-erase-entity'}" StyleClasses="ButtonSquare" />
|
||||||
|
<Button Name="EraseDecalButton" Access="Public" HorizontalExpand="True"
|
||||||
|
SetHeight="48"
|
||||||
|
ToggleMode="True" Text="{Loc 'mapping-erase-decal'}" StyleClasses="ButtonSquare" />
|
||||||
|
</BoxContainer>
|
||||||
|
<widgets:ChatBox Visible="False" />
|
||||||
|
</BoxContainer>
|
||||||
|
<LayoutContainer Name="ViewportContainer" HorizontalExpand="True" VerticalExpand="True">
|
||||||
|
<controls:MainViewport Name="MainViewport"/>
|
||||||
|
<hotbar:HotbarGui Name="Hotbar" />
|
||||||
|
<PanelContainer Name="Actions" VerticalExpand="True" HorizontalExpand="True"
|
||||||
|
MaxHeight="48">
|
||||||
|
<PanelContainer.PanelOverride>
|
||||||
|
<graphics:StyleBoxFlat BackgroundColor="#222222AA" />
|
||||||
|
</PanelContainer.PanelOverride>
|
||||||
|
<BoxContainer Orientation="Horizontal" Margin="15 10">
|
||||||
|
<mapping:MappingActionsButton
|
||||||
|
Name="Add" Access="Public" Disabled="True" ToolTip="" Visible="False" />
|
||||||
|
<mapping:MappingActionsButton Name="Fill" Access="Public"
|
||||||
|
ToolTip="" Visible="False" />
|
||||||
|
<mapping:MappingActionsButton Name="Grab" Access="Public"
|
||||||
|
ToolTip="" Visible="False" />
|
||||||
|
<mapping:MappingActionsButton Name="Move" Access="Public"
|
||||||
|
ToolTip="" Visible="False" />
|
||||||
|
<mapping:MappingActionsButton Name="Pick" Access="Public"
|
||||||
|
ToolTip="Pick (Hold 5)" />
|
||||||
|
<mapping:MappingActionsButton Name="Delete" Access="Public"
|
||||||
|
ToolTip="Delete (Hold 6)" />
|
||||||
|
</BoxContainer>
|
||||||
|
</PanelContainer>
|
||||||
|
</LayoutContainer>
|
||||||
|
</controls:RecordedSplitContainer>
|
||||||
|
</mapping:MappingScreen>
|
||||||
197
Content.Client/Mapping/MappingScreen.xaml.cs
Normal file
197
Content.Client/Mapping/MappingScreen.xaml.cs
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Content.Client.Decals;
|
||||||
|
using Content.Client.Decals.UI;
|
||||||
|
using Content.Client.UserInterface.Screens;
|
||||||
|
using Content.Client.UserInterface.Systems.Chat.Widgets;
|
||||||
|
using Content.Shared.Decals;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using static Robust.Client.UserInterface.Controls.BaseButton;
|
||||||
|
|
||||||
|
namespace Content.Client.Mapping;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class MappingScreen : InGameScreen
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||||
|
|
||||||
|
public DecalPlacementSystem DecalSystem = default!;
|
||||||
|
|
||||||
|
private PaletteColorPicker? _picker;
|
||||||
|
|
||||||
|
private ProtoId<DecalPrototype>? _id;
|
||||||
|
private Color _decalColor = Color.White;
|
||||||
|
private float _decalRotation;
|
||||||
|
private bool _decalSnap;
|
||||||
|
private int _decalZIndex;
|
||||||
|
private bool _decalCleanable;
|
||||||
|
|
||||||
|
private bool _decalAuto;
|
||||||
|
|
||||||
|
public override ChatBox ChatBox => GetWidget<ChatBox>()!;
|
||||||
|
|
||||||
|
public event Func<MappingSpawnButton, bool>? IsDecalVisible;
|
||||||
|
|
||||||
|
public MappingScreen()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
AutoscaleMaxResolution = new Vector2i(1080, 770);
|
||||||
|
|
||||||
|
SetAnchorPreset(ScreenContainer, LayoutPreset.Wide);
|
||||||
|
SetAnchorPreset(ViewportContainer, LayoutPreset.Wide);
|
||||||
|
SetAnchorPreset(SpawnContainer, LayoutPreset.Wide);
|
||||||
|
SetAnchorPreset(MainViewport, LayoutPreset.Wide);
|
||||||
|
SetAnchorAndMarginPreset(Hotbar, LayoutPreset.BottomWide, margin: 5);
|
||||||
|
SetAnchorAndMarginPreset(Actions, LayoutPreset.TopWide, margin: 5);
|
||||||
|
|
||||||
|
ScreenContainer.OnSplitResizeFinished += () =>
|
||||||
|
OnChatResized?.Invoke(new Vector2(ScreenContainer.SplitFraction, 0));
|
||||||
|
|
||||||
|
var rotationSpinBox = new FloatSpinBox(90.0f, 0)
|
||||||
|
{
|
||||||
|
HorizontalExpand = true
|
||||||
|
};
|
||||||
|
DecalSpinBoxContainer.AddChild(rotationSpinBox);
|
||||||
|
|
||||||
|
DecalColorPicker.OnColorChanged += OnDecalColorPicked;
|
||||||
|
DecalPickerOpen.OnPressed += OnDecalPickerOpenPressed;
|
||||||
|
rotationSpinBox.OnValueChanged += args =>
|
||||||
|
{
|
||||||
|
_decalRotation = args.Value;
|
||||||
|
UpdateDecal();
|
||||||
|
};
|
||||||
|
DecalEnableAuto.OnToggled += args =>
|
||||||
|
{
|
||||||
|
_decalAuto = args.Pressed;
|
||||||
|
if (_id is { } id)
|
||||||
|
SelectDecal(id);
|
||||||
|
};
|
||||||
|
DecalEnableSnap.OnToggled += args =>
|
||||||
|
{
|
||||||
|
_decalSnap = args.Pressed;
|
||||||
|
UpdateDecal();
|
||||||
|
};
|
||||||
|
DecalEnableCleanable.OnToggled += args =>
|
||||||
|
{
|
||||||
|
_decalCleanable = args.Pressed;
|
||||||
|
UpdateDecal();
|
||||||
|
};
|
||||||
|
DecalZIndexSpinBox.ValueChanged += args =>
|
||||||
|
{
|
||||||
|
_decalZIndex = args.Value;
|
||||||
|
UpdateDecal();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var i = 0; i < EntitySpawnWindow.InitOpts.Length; i++)
|
||||||
|
{
|
||||||
|
EntityPlacementMode.AddItem(EntitySpawnWindow.InitOpts[i], i);
|
||||||
|
}
|
||||||
|
|
||||||
|
Pick.Texture.TexturePath = "/Textures/Interface/eyedropper.svg.png";
|
||||||
|
Delete.Texture.TexturePath = "/Textures/Interface/eraser.svg.png";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDecalColorPicked(Color color)
|
||||||
|
{
|
||||||
|
_decalColor = color;
|
||||||
|
DecalColorPicker.Color = color;
|
||||||
|
UpdateDecal();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDecalPickerOpenPressed(ButtonEventArgs obj)
|
||||||
|
{
|
||||||
|
if (_picker == null)
|
||||||
|
{
|
||||||
|
_picker = new PaletteColorPicker();
|
||||||
|
_picker.OpenToLeft();
|
||||||
|
_picker.PaletteList.OnItemSelected += args =>
|
||||||
|
{
|
||||||
|
var color = ((Color?) args.ItemList.GetSelected().First().Metadata)!.Value;
|
||||||
|
OnDecalColorPicked(color);
|
||||||
|
};
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_picker.IsOpen)
|
||||||
|
_picker.Close();
|
||||||
|
else
|
||||||
|
_picker.Open();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateDecal()
|
||||||
|
{
|
||||||
|
if (_id is not { } id)
|
||||||
|
return;
|
||||||
|
|
||||||
|
DecalSystem.UpdateDecalInfo(id, _decalColor, _decalRotation, _decalSnap, _decalZIndex, _decalCleanable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SelectDecal(string decalId)
|
||||||
|
{
|
||||||
|
if (!_prototype.TryIndex<DecalPrototype>(decalId, out var decal))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_id = decalId;
|
||||||
|
|
||||||
|
if (_decalAuto)
|
||||||
|
{
|
||||||
|
_decalColor = Color.White;
|
||||||
|
_decalCleanable = decal.DefaultCleanable;
|
||||||
|
_decalSnap = decal.DefaultSnap;
|
||||||
|
|
||||||
|
DecalColorPicker.Color = _decalColor;
|
||||||
|
DecalEnableCleanable.Pressed = _decalCleanable;
|
||||||
|
DecalEnableSnap.Pressed = _decalSnap;
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateDecal();
|
||||||
|
RefreshList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshList()
|
||||||
|
{
|
||||||
|
foreach (var control in Prototypes.Children)
|
||||||
|
{
|
||||||
|
if (control is not MappingSpawnButton button ||
|
||||||
|
button.Prototype?.Prototype is not DecalPrototype)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var child in button.Children)
|
||||||
|
{
|
||||||
|
if (child is not MappingSpawnButton { Prototype.Prototype: DecalPrototype } childButton)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
childButton.Texture.Modulate = _decalColor;
|
||||||
|
childButton.Visible = IsDecalVisible?.Invoke(childButton) ?? true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetChatSize(Vector2 size)
|
||||||
|
{
|
||||||
|
ScreenContainer.DesiredSplitCenter = size.X;
|
||||||
|
ScreenContainer.ResizeMode = SplitContainer.SplitResizeMode.RespectChildrenMinSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnPressActionsExcept(Control except)
|
||||||
|
{
|
||||||
|
Add.Pressed = Add == except;
|
||||||
|
Fill.Pressed = Fill == except;
|
||||||
|
Grab.Pressed = Grab == except;
|
||||||
|
Move.Pressed = Move == except;
|
||||||
|
Pick.Pressed = Pick == except;
|
||||||
|
Delete.Pressed = Delete == except;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
Content.Client/Mapping/MappingSpawnButton.xaml
Normal file
26
Content.Client/Mapping/MappingSpawnButton.xaml
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<mapping:MappingSpawnButton
|
||||||
|
xmlns="https://spacestation14.io"
|
||||||
|
xmlns:mapping="clr-namespace:Content.Client.Mapping">
|
||||||
|
<BoxContainer Orientation="Vertical">
|
||||||
|
<Control>
|
||||||
|
<Button Name="Button" Access="Public" ToggleMode="True" StyleClasses="ButtonSquare" />
|
||||||
|
<BoxContainer Orientation="Horizontal">
|
||||||
|
<LayeredTextureRect Name="Texture" Access="Public" MinSize="48 48"
|
||||||
|
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||||
|
Stretch="KeepAspectCentered" CanShrink="True" />
|
||||||
|
<Control SetSize="48 48" Access="Public" Name="CollapseButtonWrapper">
|
||||||
|
<Button Name="CollapseButton" Access="Public" Text="▶"
|
||||||
|
ToggleMode="True" StyleClasses="ButtonSquare" SetSize="48 48" />
|
||||||
|
</Control>
|
||||||
|
<Label Name="Label" Access="Public"
|
||||||
|
VAlign="Center"
|
||||||
|
VerticalExpand="True"
|
||||||
|
MinHeight="48"
|
||||||
|
Margin="5 0"
|
||||||
|
HorizontalExpand="True" ClipText="True" />
|
||||||
|
</BoxContainer>
|
||||||
|
</Control>
|
||||||
|
<BoxContainer Name="ChildrenPrototypes" Access="Public" Orientation="Vertical"
|
||||||
|
Margin="24 0 0 0" />
|
||||||
|
</BoxContainer>
|
||||||
|
</mapping:MappingSpawnButton>
|
||||||
16
Content.Client/Mapping/MappingSpawnButton.xaml.cs
Normal file
16
Content.Client/Mapping/MappingSpawnButton.xaml.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client.Mapping;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class MappingSpawnButton : Control
|
||||||
|
{
|
||||||
|
public MappingPrototype? Prototype;
|
||||||
|
|
||||||
|
public MappingSpawnButton()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
936
Content.Client/Mapping/MappingState.cs
Normal file
936
Content.Client/Mapping/MappingState.cs
Normal file
@@ -0,0 +1,936 @@
|
|||||||
|
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 = args.Coordinates.ToMap(_entityManager, _transform);
|
||||||
|
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(SpriteComponent.GetPrototypeTextures(entity, _resources).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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ public sealed partial class MappingSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IPlacementManager _placementMan = default!;
|
[Dependency] private readonly IPlacementManager _placementMan = default!;
|
||||||
[Dependency] private readonly ITileDefinitionManager _tileMan = default!;
|
[Dependency] private readonly ITileDefinitionManager _tileMan = default!;
|
||||||
[Dependency] private readonly ActionsSystem _actionsSystem = default!;
|
|
||||||
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
[Dependency] private readonly MetaDataSystem _metaData = default!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -26,8 +25,6 @@ public sealed partial class MappingSystem : EntitySystem
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly SpriteSpecifier _deleteIcon = new Texture(new ("Interface/VerbIcons/delete.svg.192dpi.png"));
|
private readonly SpriteSpecifier _deleteIcon = new Texture(new ("Interface/VerbIcons/delete.svg.192dpi.png"));
|
||||||
|
|
||||||
public string DefaultMappingActions = "/mapping_actions.yml";
|
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
@@ -36,11 +33,6 @@ public sealed partial class MappingSystem : EntitySystem
|
|||||||
SubscribeLocalEvent<StartPlacementActionEvent>(OnStartPlacementAction);
|
SubscribeLocalEvent<StartPlacementActionEvent>(OnStartPlacementAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadMappingActions()
|
|
||||||
{
|
|
||||||
_actionsSystem.LoadActionAssignments(DefaultMappingActions, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This checks if the placement manager is currently active, and attempts to copy the placement information for
|
/// This checks if the placement manager is currently active, and attempts to copy the placement information for
|
||||||
/// some entity or tile into an action. This is somewhat janky, but it seem to work well enough. Though I'd
|
/// some entity or tile into an action. This is somewhat janky, but it seem to work well enough. Though I'd
|
||||||
|
|||||||
@@ -736,7 +736,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged<GameplayS
|
|||||||
|
|
||||||
private void LoadGui()
|
private void LoadGui()
|
||||||
{
|
{
|
||||||
DebugTools.Assert(_window == null);
|
UnloadGui();
|
||||||
_window = UIManager.CreateWindow<ActionsWindow>();
|
_window = UIManager.CreateWindow<ActionsWindow>();
|
||||||
LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop);
|
LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Numerics;
|
|||||||
using Content.Client.CombatMode;
|
using Content.Client.CombatMode;
|
||||||
using Content.Client.ContextMenu.UI;
|
using Content.Client.ContextMenu.UI;
|
||||||
using Content.Client.Gameplay;
|
using Content.Client.Gameplay;
|
||||||
|
using Content.Client.Mapping;
|
||||||
using Content.Shared.Input;
|
using Content.Shared.Input;
|
||||||
using Content.Shared.Verbs;
|
using Content.Shared.Verbs;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
@@ -22,7 +23,9 @@ namespace Content.Client.Verbs.UI
|
|||||||
/// open a verb menu for a given entity, add verbs to it, and add server-verbs when the server response is
|
/// open a verb menu for a given entity, add verbs to it, and add server-verbs when the server response is
|
||||||
/// received.
|
/// received.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public sealed class VerbMenuUIController : UIController, IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>
|
public sealed class VerbMenuUIController : UIController,
|
||||||
|
IOnStateEntered<GameplayState>, IOnStateExited<GameplayState>,
|
||||||
|
IOnStateEntered<MappingState>, IOnStateExited<MappingState>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
[Dependency] private readonly ContextMenuUIController _context = default!;
|
[Dependency] private readonly ContextMenuUIController _context = default!;
|
||||||
@@ -44,7 +47,6 @@ namespace Content.Client.Verbs.UI
|
|||||||
{
|
{
|
||||||
_context.OnContextKeyEvent += OnKeyBindDown;
|
_context.OnContextKeyEvent += OnKeyBindDown;
|
||||||
_context.OnContextClosed += Close;
|
_context.OnContextClosed += Close;
|
||||||
_verbSystem.OnVerbsResponse += HandleVerbsResponse;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnStateExited(GameplayState state)
|
public void OnStateExited(GameplayState state)
|
||||||
@@ -56,6 +58,17 @@ namespace Content.Client.Verbs.UI
|
|||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void OnStateEntered(MappingState state)
|
||||||
|
{
|
||||||
|
_verbSystem.OnVerbsResponse += HandleVerbsResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnStateExited(MappingState state)
|
||||||
|
{
|
||||||
|
if (_verbSystem != null)
|
||||||
|
_verbSystem.OnVerbsResponse -= HandleVerbsResponse;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Open a verb menu and fill it with verbs applicable to the given target entity.
|
/// Open a verb menu and fill it with verbs applicable to the given target entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
41
Content.IntegrationTests/Tests/MappingEditorTest.cs
Normal file
41
Content.IntegrationTests/Tests/MappingEditorTest.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using Content.Client.Gameplay;
|
||||||
|
using Content.Client.Mapping;
|
||||||
|
using Robust.Client.State;
|
||||||
|
|
||||||
|
namespace Content.IntegrationTests.Tests;
|
||||||
|
|
||||||
|
[TestFixture]
|
||||||
|
public sealed class MappingEditorTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public async Task StopHardCodingWidgetsJesusChristTest()
|
||||||
|
{
|
||||||
|
await using var pair = await PoolManager.GetServerClient(new PoolSettings
|
||||||
|
{
|
||||||
|
Connected = true
|
||||||
|
});
|
||||||
|
var client = pair.Client;
|
||||||
|
var state = client.ResolveDependency<IStateManager>();
|
||||||
|
|
||||||
|
await client.WaitPost(() =>
|
||||||
|
{
|
||||||
|
Assert.DoesNotThrow(() =>
|
||||||
|
{
|
||||||
|
state.RequestStateChange<MappingState>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// arbitrary short time
|
||||||
|
await client.WaitRunTicks(30);
|
||||||
|
|
||||||
|
await client.WaitPost(() =>
|
||||||
|
{
|
||||||
|
Assert.DoesNotThrow(() =>
|
||||||
|
{
|
||||||
|
state.RequestStateChange<GameplayState>();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await pair.CleanReturnAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ using Content.Server.Discord;
|
|||||||
using Content.Server.EUI;
|
using Content.Server.EUI;
|
||||||
using Content.Server.GhostKick;
|
using Content.Server.GhostKick;
|
||||||
using Content.Server.Info;
|
using Content.Server.Info;
|
||||||
|
using Content.Server.Mapping;
|
||||||
using Content.Server.Maps;
|
using Content.Server.Maps;
|
||||||
using Content.Server.MoMMI;
|
using Content.Server.MoMMI;
|
||||||
using Content.Server.NodeContainer.NodeGroups;
|
using Content.Server.NodeContainer.NodeGroups;
|
||||||
@@ -66,6 +67,7 @@ namespace Content.Server.IoC
|
|||||||
IoCManager.Register<ServerApi>();
|
IoCManager.Register<ServerApi>();
|
||||||
IoCManager.Register<JobWhitelistManager>();
|
IoCManager.Register<JobWhitelistManager>();
|
||||||
IoCManager.Register<PlayerRateLimitManager>();
|
IoCManager.Register<PlayerRateLimitManager>();
|
||||||
|
IoCManager.Register<MappingManager>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
76
Content.Server/Mapping/MappingManager.cs
Normal file
76
Content.Server/Mapping/MappingManager.cs
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
using System.IO;
|
||||||
|
using Content.Server.Administration.Managers;
|
||||||
|
using Content.Shared.Administration;
|
||||||
|
using Content.Shared.Mapping;
|
||||||
|
using Robust.Server.GameObjects;
|
||||||
|
using Robust.Server.Player;
|
||||||
|
using Robust.Shared.Map;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
using YamlDotNet.Core;
|
||||||
|
using YamlDotNet.RepresentationModel;
|
||||||
|
|
||||||
|
namespace Content.Server.Mapping;
|
||||||
|
|
||||||
|
public sealed class MappingManager : IPostInjectInit
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IAdminManager _admin = default!;
|
||||||
|
[Dependency] private readonly ILogManager _log = default!;
|
||||||
|
[Dependency] private readonly IMapManager _map = default!;
|
||||||
|
[Dependency] private readonly IServerNetManager _net = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _players = default!;
|
||||||
|
[Dependency] private readonly IEntitySystemManager _systems = default!;
|
||||||
|
|
||||||
|
private ISawmill _sawmill = default!;
|
||||||
|
private ZStdCompressionContext _zstd = default!;
|
||||||
|
|
||||||
|
public void PostInject()
|
||||||
|
{
|
||||||
|
#if !FULL_RELEASE
|
||||||
|
_net.RegisterNetMessage<MappingSaveMapMessage>(OnMappingSaveMap);
|
||||||
|
_net.RegisterNetMessage<MappingSaveMapErrorMessage>();
|
||||||
|
_net.RegisterNetMessage<MappingMapDataMessage>();
|
||||||
|
|
||||||
|
_sawmill = _log.GetSawmill("mapping");
|
||||||
|
_zstd = new ZStdCompressionContext();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnMappingSaveMap(MappingSaveMapMessage message)
|
||||||
|
{
|
||||||
|
#if !FULL_RELEASE
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!_players.TryGetSessionByChannel(message.MsgChannel, out var session) ||
|
||||||
|
!_admin.IsAdmin(session, true) ||
|
||||||
|
!_admin.HasAdminFlag(session, AdminFlags.Host) ||
|
||||||
|
session.AttachedEntity is not { } player)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mapId = _systems.GetEntitySystem<TransformSystem>().GetMapCoordinates(player).MapId;
|
||||||
|
var mapEntity = _map.GetMapEntityIdOrThrow(mapId);
|
||||||
|
var data = _systems.GetEntitySystem<MapLoaderSystem>().GetSaveData(mapEntity);
|
||||||
|
var document = new YamlDocument(data.ToYaml());
|
||||||
|
var stream = new YamlStream { document };
|
||||||
|
var writer = new StringWriter();
|
||||||
|
stream.Save(new YamlMappingFix(new Emitter(writer)), false);
|
||||||
|
|
||||||
|
var msg = new MappingMapDataMessage()
|
||||||
|
{
|
||||||
|
Context = _zstd,
|
||||||
|
Yml = writer.ToString()
|
||||||
|
};
|
||||||
|
_net.ServerSendMessage(msg, message.MsgChannel);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_sawmill.Error($"Error saving map in mapping mode:\n{e}");
|
||||||
|
var msg = new MappingSaveMapErrorMessage();
|
||||||
|
_net.ServerSendMessage(msg, message.MsgChannel);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
|
||||||
using Robust.Shared.Utility;
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
namespace Content.Shared.Decals
|
namespace Content.Shared.Decals
|
||||||
{
|
{
|
||||||
[Prototype("decal")]
|
[Prototype("decal")]
|
||||||
public sealed partial class DecalPrototype : IPrototype
|
public sealed partial class DecalPrototype : IPrototype, IInheritingPrototype
|
||||||
{
|
{
|
||||||
[IdDataField] public string ID { get; } = null!;
|
[IdDataField] public string ID { get; } = null!;
|
||||||
[DataField("sprite")] public SpriteSpecifier Sprite { get; private set; } = SpriteSpecifier.Invalid;
|
[DataField("sprite")] public SpriteSpecifier Sprite { get; private set; } = SpriteSpecifier.Invalid;
|
||||||
@@ -33,5 +34,13 @@ namespace Content.Shared.Decals
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField]
|
[DataField]
|
||||||
public bool DefaultSnap = true;
|
public bool DefaultSnap = true;
|
||||||
|
|
||||||
|
[ParentDataField(typeof(AbstractPrototypeIdArraySerializer<DecalPrototype>))]
|
||||||
|
public string[]? Parents { get; }
|
||||||
|
|
||||||
|
[NeverPushInheritance]
|
||||||
|
[AbstractDataField]
|
||||||
|
public bool Abstract { get; }
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,5 +104,14 @@ namespace Content.Shared.Input
|
|||||||
public static readonly BoundKeyFunction EditorCopyObject = "EditorCopyObject";
|
public static readonly BoundKeyFunction EditorCopyObject = "EditorCopyObject";
|
||||||
public static readonly BoundKeyFunction EditorFlipObject = "EditorFlipObject";
|
public static readonly BoundKeyFunction EditorFlipObject = "EditorFlipObject";
|
||||||
public static readonly BoundKeyFunction InspectEntity = "InspectEntity";
|
public static readonly BoundKeyFunction InspectEntity = "InspectEntity";
|
||||||
|
|
||||||
|
public static readonly BoundKeyFunction MappingUnselect = "MappingUnselect";
|
||||||
|
public static readonly BoundKeyFunction SaveMap = "SaveMap";
|
||||||
|
public static readonly BoundKeyFunction MappingEnablePick = "MappingEnablePick";
|
||||||
|
public static readonly BoundKeyFunction MappingEnableDelete = "MappingEnableDelete";
|
||||||
|
public static readonly BoundKeyFunction MappingPick = "MappingPick";
|
||||||
|
public static readonly BoundKeyFunction MappingRemoveDecal = "MappingRemoveDecal";
|
||||||
|
public static readonly BoundKeyFunction MappingCancelEraseDecal = "MappingCancelEraseDecal";
|
||||||
|
public static readonly BoundKeyFunction MappingOpenContextMenu = "MappingOpenContextMenu";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
Content.Shared/Mapping/MappingMapDataMessage.cs
Normal file
46
Content.Shared/Mapping/MappingMapDataMessage.cs
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
using System.IO;
|
||||||
|
using Lidgren.Network;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Shared.Mapping;
|
||||||
|
|
||||||
|
public sealed class MappingMapDataMessage : NetMessage
|
||||||
|
{
|
||||||
|
public override MsgGroups MsgGroup => MsgGroups.Command;
|
||||||
|
public override NetDeliveryMethod DeliveryMethod => NetDeliveryMethod.ReliableUnordered;
|
||||||
|
|
||||||
|
public ZStdCompressionContext Context = default!;
|
||||||
|
public string Yml = default!;
|
||||||
|
|
||||||
|
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||||
|
{
|
||||||
|
MsgSize = buffer.LengthBytes;
|
||||||
|
|
||||||
|
var uncompressedLength = buffer.ReadVariableInt32();
|
||||||
|
var compressedLength = buffer.ReadVariableInt32();
|
||||||
|
var stream = new MemoryStream(compressedLength);
|
||||||
|
buffer.ReadAlignedMemory(stream, compressedLength);
|
||||||
|
using var decompress = new ZStdDecompressStream(stream);
|
||||||
|
using var decompressed = new MemoryStream(uncompressedLength);
|
||||||
|
|
||||||
|
decompress.CopyTo(decompressed, uncompressedLength);
|
||||||
|
decompressed.Position = 0;
|
||||||
|
serializer.DeserializeDirect(decompressed, out Yml);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||||
|
{
|
||||||
|
var stream = new MemoryStream();
|
||||||
|
serializer.SerializeDirect(stream, Yml);
|
||||||
|
buffer.WriteVariableInt32((int) stream.Length);
|
||||||
|
|
||||||
|
stream.Position = 0;
|
||||||
|
var buf = new byte[ZStd.CompressBound((int) stream.Length)];
|
||||||
|
var length = Context.Compress2(buf, stream.AsSpan());
|
||||||
|
|
||||||
|
buffer.WriteVariableInt32(length);
|
||||||
|
buffer.Write(buf.AsSpan(0, length));
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Content.Shared/Mapping/MappingSaveMapErrorMessage.cs
Normal file
19
Content.Shared/Mapping/MappingSaveMapErrorMessage.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Lidgren.Network;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Mapping;
|
||||||
|
|
||||||
|
public sealed class MappingSaveMapErrorMessage : NetMessage
|
||||||
|
{
|
||||||
|
public override MsgGroups MsgGroup => MsgGroups.Command;
|
||||||
|
public override NetDeliveryMethod DeliveryMethod => NetDeliveryMethod.ReliableUnordered;
|
||||||
|
|
||||||
|
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Content.Shared/Mapping/MappingSaveMapMessage.cs
Normal file
19
Content.Shared/Mapping/MappingSaveMapMessage.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using Lidgren.Network;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Serialization;
|
||||||
|
|
||||||
|
namespace Content.Shared.Mapping;
|
||||||
|
|
||||||
|
public sealed class MappingSaveMapMessage : NetMessage
|
||||||
|
{
|
||||||
|
public override MsgGroups MsgGroup => MsgGroups.Command;
|
||||||
|
public override NetDeliveryMethod DeliveryMethod => NetDeliveryMethod.ReliableUnordered;
|
||||||
|
|
||||||
|
public override void ReadFromBuffer(NetIncomingMessage buffer, IRobustSerializer serializer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serializer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
7
Resources/Locale/en-US/mapping/editor.ftl
Normal file
7
Resources/Locale/en-US/mapping/editor.ftl
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mapping-entities = Entities
|
||||||
|
mapping-tiles = Tiles
|
||||||
|
mapping-decals = Decals
|
||||||
|
|
||||||
|
mapping-replace = Replace
|
||||||
|
mapping-erase-entity = Erase Entity
|
||||||
|
mapping-erase-decal = Erase Decal
|
||||||
3
Resources/Textures/Interface/eraser.svg
Normal file
3
Resources/Textures/Interface/eraser.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eraser" viewBox="0 0 16 16">
|
||||||
|
<path d="M8.086 2.207a2 2 0 0 1 2.828 0l3.879 3.879a2 2 0 0 1 0 2.828l-5.5 5.5A2 2 0 0 1 7.879 15H5.12a2 2 0 0 1-1.414-.586l-2.5-2.5a2 2 0 0 1 0-2.828l6.879-6.879zm2.121.707a1 1 0 0 0-1.414 0L4.16 7.547l5.293 5.293 4.633-4.633a1 1 0 0 0 0-1.414l-3.879-3.879zM8.746 13.547 3.453 8.254 1.914 9.793a1 1 0 0 0 0 1.414l2.5 2.5a1 1 0 0 0 .707.293H7.88a1 1 0 0 0 .707-.293l.16-.16z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 511 B |
BIN
Resources/Textures/Interface/eraser.svg.png
Normal file
BIN
Resources/Textures/Interface/eraser.svg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
3
Resources/Textures/Interface/eyedropper.svg
Normal file
3
Resources/Textures/Interface/eyedropper.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eyedropper" viewBox="0 0 16 16">
|
||||||
|
<path d="M13.354.646a1.207 1.207 0 0 0-1.708 0L8.5 3.793l-.646-.647a.5.5 0 1 0-.708.708L8.293 5l-7.147 7.146A.5.5 0 0 0 1 12.5v1.793l-.854.853a.5.5 0 1 0 .708.707L1.707 15H3.5a.5.5 0 0 0 .354-.146L11 7.707l1.146 1.147a.5.5 0 0 0 .708-.708l-.647-.646 3.147-3.146a1.207 1.207 0 0 0 0-1.708l-2-2zM2 12.707l7-7L10.293 7l-7 7H2z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 464 B |
BIN
Resources/Textures/Interface/eyedropper.svg.png
Normal file
BIN
Resources/Textures/Interface/eyedropper.svg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
@@ -540,3 +540,33 @@ binds:
|
|||||||
- function: Hotbar9
|
- function: Hotbar9
|
||||||
type: State
|
type: State
|
||||||
key: Num9
|
key: Num9
|
||||||
|
- function: MappingUnselect
|
||||||
|
type: State
|
||||||
|
key: MouseRight
|
||||||
|
canFocus: true
|
||||||
|
- function: SaveMap
|
||||||
|
type: State
|
||||||
|
key: S
|
||||||
|
mod1: Control
|
||||||
|
- function: MappingEnablePick
|
||||||
|
type: State
|
||||||
|
key: Num5
|
||||||
|
- function: MappingEnableDelete
|
||||||
|
type: State
|
||||||
|
key: Num6
|
||||||
|
- function: MappingPick
|
||||||
|
type: State
|
||||||
|
key: MouseLeft
|
||||||
|
canFocus: true
|
||||||
|
- function: MappingRemoveDecal
|
||||||
|
type: State
|
||||||
|
key: MouseLeft
|
||||||
|
canFocus: true
|
||||||
|
- function: MappingCancelEraseDecal
|
||||||
|
type: State
|
||||||
|
key: MouseRight
|
||||||
|
canFocus: true
|
||||||
|
- function: MappingOpenContextMenu
|
||||||
|
type: State
|
||||||
|
key: MouseRight
|
||||||
|
canFocus: true
|
||||||
|
|||||||
Reference in New Issue
Block a user