Prechádzať zdrojové kódy

Merge branch 'master' of https://github.com/Civ13/Civ14

Taislin 6 mesiacov pred
rodič
commit
803e8f9533
48 zmenil súbory, kde vykonal 1359 pridanie a 2 odobranie
  1. 70 0
      Content.Client/_RMC14/Mortar/MortarBui.cs
  2. 70 0
      Content.Client/_RMC14/Mortar/MortarSystem.cs
  3. 33 0
      Content.Client/_RMC14/Mortar/MortarWindow.xaml
  4. 71 0
      Content.Client/_RMC14/Mortar/MortarWindow.xaml.cs
  5. 26 0
      Content.Client/_RMC14/UserInterface/UIExtensions.cs
  6. 2 2
      Content.Server/GameTicking/GameTicker.cs
  7. 84 0
      Content.Server/_RMC14/Mortar/MortarSystem.cs
  8. 13 0
      Content.Shared/_RMC14/Extensions/IntExtensions.cs
  9. 37 0
      Content.Shared/_RMC14/Mortar/ActiveMortarShellComponent.cs
  10. 7 0
      Content.Shared/_RMC14/Mortar/DeployMortarDoAfterEvent.cs
  11. 15 0
      Content.Shared/_RMC14/Mortar/DialMortarDoAfterEvent.cs
  12. 7 0
      Content.Shared/_RMC14/Mortar/LoadMortarShellDoAfterEvent.cs
  13. 89 0
      Content.Shared/_RMC14/Mortar/MortarComponent.cs
  14. 9 0
      Content.Shared/_RMC14/Mortar/MortarFiredEvent.cs
  15. 20 0
      Content.Shared/_RMC14/Mortar/MortarShellComponent.cs
  16. 6 0
      Content.Shared/_RMC14/Mortar/MortarShellLandEvent.cs
  17. 24 0
      Content.Shared/_RMC14/Mortar/MortarUI.cs
  18. 16 0
      Content.Shared/_RMC14/Mortar/MortarVisuals.cs
  19. 447 0
      Content.Shared/_RMC14/Mortar/SharedMortarSystem.cs
  20. 15 0
      Content.Shared/_RMC14/Mortar/TargetMortarDoAfterEvent.cs
  21. BIN
      Resources/Audio/_RMC/Weapons/gun_mortar_fire.ogg
  22. BIN
      Resources/Audio/_RMC/Weapons/gun_mortar_reload.ogg
  23. BIN
      Resources/Audio/_RMC/Weapons/gun_mortar_travel.ogg
  24. BIN
      Resources/Audio/_RMC/Weapons/gun_mortar_unpack.ogg
  25. BIN
      Resources/Audio/_RMC/Weapons/gun_orbital_travel.ogg
  26. 45 0
      Resources/Locale/en-US/_RMC/mortar.ftl
  27. 31 0
      Resources/Prototypes/Civ14/Entities/Objects/Explosives/Mortars/mortar_shells.yml
  28. 69 0
      Resources/Prototypes/Civ14/Entities/Objects/Explosives/Mortars/mortars.yml
  29. 43 0
      Resources/Prototypes/Civ14/Entities/Objects/Tools/navigation.yml
  30. BIN
      Resources/Textures/Civ14/Objects/Mortars/mortar.rsi/inhand-left.png
  31. BIN
      Resources/Textures/Civ14/Objects/Mortars/mortar.rsi/inhand-right.png
  32. 48 0
      Resources/Textures/Civ14/Objects/Mortars/mortar.rsi/meta.json
  33. BIN
      Resources/Textures/Civ14/Objects/Mortars/mortar.rsi/mortar_carry.png
  34. BIN
      Resources/Textures/Civ14/Objects/Mortars/mortar.rsi/mortar_deploy.png
  35. BIN
      Resources/Textures/Civ14/Objects/Mortars/mortar.rsi/mortar_fire.png
  36. BIN
      Resources/Textures/Civ14/Objects/Mortars/mortar_old.rsi/inhand-left.png
  37. BIN
      Resources/Textures/Civ14/Objects/Mortars/mortar_old.rsi/inhand-right.png
  38. 48 0
      Resources/Textures/Civ14/Objects/Mortars/mortar_old.rsi/meta.json
  39. BIN
      Resources/Textures/Civ14/Objects/Mortars/mortar_old.rsi/mortar_carry.png
  40. BIN
      Resources/Textures/Civ14/Objects/Mortars/mortar_old.rsi/mortar_deploy.png
  41. BIN
      Resources/Textures/Civ14/Objects/Mortars/mortar_old.rsi/mortar_fire.png
  42. 14 0
      Resources/Textures/Civ14/Objects/Mortars/shells.rsi/meta.json
  43. BIN
      Resources/Textures/Civ14/Objects/Mortars/shells.rsi/mortar_ammo (1).png
  44. BIN
      Resources/Textures/Civ14/Objects/Mortars/shells.rsi/mortar_ammo (2).png
  45. BIN
      Resources/Textures/Civ14/Objects/Mortars/shells.rsi/mortar_ammo (4).png
  46. BIN
      Resources/Textures/Civ14/Objects/Mortars/shells.rsi/mortar_ammo (5).png
  47. BIN
      Resources/Textures/Civ14/Objects/Mortars/shells.rsi/mortar_ammo_he.png
  48. BIN
      Resources/Textures/Civ14/Objects/Mortars/type89.rsi/type89.png

+ 70 - 0
Content.Client/_RMC14/Mortar/MortarBui.cs

@@ -0,0 +1,70 @@
+using Content.Shared._RMC14.Mortar;
+using JetBrains.Annotations;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+
+namespace Content.Client._RMC14.Mortar;
+
+[UsedImplicitly]
+public sealed class MortarBui(EntityUid owner, Enum uiKey) : BoundUserInterface(owner, uiKey)
+{
+    private MortarWindow? _window;
+
+    protected override void Open()
+    {
+        base.Open();
+        _window = this.CreateWindow<MortarWindow>();
+
+        Refresh();
+
+        static int Parse(FloatSpinBox spinBox)
+        {
+            return (int) spinBox.Value;
+        }
+
+        static void SetSpinBox(FloatSpinBox spinBox, int limit, int value)
+        {
+            spinBox.Value = value;
+            spinBox.OnValueChanged += args =>
+            {
+                var value = Math.Clamp(args.Value, -limit, limit);
+                spinBox.Value = value;
+            };
+        }
+
+        if (EntMan.TryGetComponent(Owner, out MortarComponent? mortar))
+        {
+            SetSpinBox(_window.TargetX, mortar.MaxTarget, mortar.Target.X);
+            SetSpinBox(_window.TargetY, mortar.MaxTarget, mortar.Target.Y);
+            SetSpinBox(_window.DialX, mortar.MaxDial, mortar.Dial.X);
+            SetSpinBox(_window.DialY, mortar.MaxDial, mortar.Dial.Y);
+            _window.SetTargetButton.OnPressed += _ =>
+                SendPredictedMessage(new MortarTargetBuiMsg((Parse(_window.TargetX), Parse(_window.TargetY))));
+
+            _window.SetOffsetButton.OnPressed += _ =>
+                SendPredictedMessage(new MortarDialBuiMsg((Parse(_window.DialX), Parse(_window.DialY))));
+        }
+
+    }
+
+    public void Refresh()
+    {
+        if (_window is not { IsOpen: true })
+            return;
+
+        if (!EntMan.TryGetComponent(Owner, out MortarComponent? mortar))
+            return;
+
+        static void SetValue(FloatSpinBox? spinBox, int value)
+        {
+            if (spinBox != null)
+                spinBox.Value = value;
+        }
+
+        SetValue(_window.TargetX, mortar.Target.X);
+        SetValue(_window.TargetY, mortar.Target.Y);
+        SetValue(_window.DialX, mortar.Dial.X);
+        SetValue(_window.DialY, mortar.Dial.Y);
+        _window.MaxDialLabel.Text = Loc.GetString("rmc-mortar-offset-max", ("max", mortar.MaxDial));
+    }
+}

+ 70 - 0
Content.Client/_RMC14/Mortar/MortarSystem.cs

@@ -0,0 +1,70 @@
+using Content.Shared._RMC14.Mortar;
+using Robust.Client.Animations;
+using Robust.Client.GameObjects;
+
+namespace Content.Client._RMC14.Mortar;
+
+public sealed class MortarSystem : SharedMortarSystem
+{
+    [Dependency] private readonly AnimationPlayerSystem _animation = default!;
+
+    private const string AnimationKey = "rmc_mortar_fire";
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeAllEvent<MortarFiredEvent>(OnMortarFiredEvent);
+        SubscribeLocalEvent<MortarComponent, AfterAutoHandleStateEvent>(OnMortarHandleState);
+    }
+
+    private void OnMortarFiredEvent(MortarFiredEvent ev)
+    {
+        if (!TryGetEntity(ev.Mortar, out var mortarId) ||
+            !TryComp(mortarId, out MortarComponent? mortar))
+        {
+            return;
+        }
+
+        if (_animation.HasRunningAnimation(mortarId.Value, AnimationKey))
+            return;
+
+        _animation.Play(mortarId.Value,
+            new Animation
+            {
+                Length = mortar.AnimationTime,
+                AnimationTracks =
+                {
+                    new AnimationTrackSpriteFlick
+                    {
+                        LayerKey = mortar.AnimationLayer,
+                        KeyFrames =
+                        {
+                            new AnimationTrackSpriteFlick.KeyFrame(mortar.AnimationState, 0f),
+                            new AnimationTrackSpriteFlick.KeyFrame(mortar.DeployedState, 0.3f),
+                        },
+                    },
+                },
+            },
+            AnimationKey);
+    }
+
+    private void OnMortarHandleState(Entity<MortarComponent> mortar, ref AfterAutoHandleStateEvent args)
+    {
+        try
+        {
+            if (!TryComp(mortar, out UserInterfaceComponent? ui))
+                return;
+
+            foreach (var open in ui.ClientOpenInterfaces.Values)
+            {
+                if (open is MortarBui bui)
+                    bui.Refresh();
+            }
+        }
+        catch (Exception e)
+        {
+            Log.Error($"Error refreshing {nameof(MortarBui)}:\n{e}");
+        }
+    }
+}

+ 33 - 0
Content.Client/_RMC14/Mortar/MortarWindow.xaml

@@ -0,0 +1,33 @@
+<controls:MortarWindow
+    xmlns="https://spacestation14.io"
+    xmlns:controls="clr-namespace:Content.Client._RMC14.Mortar"
+    xmlns:ui="clr-namespace:Content.Client._RMC14.UserInterface"
+    Title="{Loc 'rmc-mortar-interface'}">
+    <BoxContainer Orientation="Vertical">
+        <BoxContainer Orientation="Horizontal">
+            <BoxContainer Orientation="Vertical">
+                <BoxContainer Name="TargetXContainer" Access="Public" Orientation="Horizontal">
+                    <Label Text="{Loc 'rmc-mortar-target-x'}" Margin="0 0 5 0" />
+                </BoxContainer>
+                <BoxContainer Name="TargetYContainer" Access="Public" Orientation="Horizontal" Margin="0 5 0 0">
+                    <Label Text="{Loc 'rmc-mortar-target-y'}" Margin="0 0 5 0" />
+                </BoxContainer>
+                <Button Name="SetTargetButton" Access="Public" StyleClasses="OpenBoth"
+                        Text="{Loc 'rmc-mortar-target-set'}" Margin="0 5 0 0" />
+            </BoxContainer>
+        </BoxContainer>
+        <BoxContainer Orientation="Horizontal">
+            <BoxContainer Orientation="Vertical">
+                <Label Name="MaxDialLabel" Access="Public" VerticalAlignment="Top" Margin="0 5" />
+                <BoxContainer Name="DialXContainer" Access="Public" Orientation="Horizontal">
+                    <Label Text="{Loc 'rmc-mortar-offset-x'}" Margin="0 0 5 0" />
+                </BoxContainer>
+                <BoxContainer Name="DialYContainer" Access="Public" Orientation="Horizontal" Margin="0 5 0 0">
+                    <Label Text="{Loc 'rmc-mortar-offset-y'}" Margin="0 0 5 0" />
+                </BoxContainer>
+                <Button Name="SetOffsetButton" Access="Public" StyleClasses="OpenBoth"
+                        Text="{Loc 'rmc-mortar-offset-set'}" Margin="0 5 0 0" />
+            </BoxContainer>
+        </BoxContainer>
+    </BoxContainer>
+</controls:MortarWindow>

+ 71 - 0
Content.Client/_RMC14/Mortar/MortarWindow.xaml.cs

@@ -0,0 +1,71 @@
+using Content.Client._RMC14.UserInterface;
+using Content.Client.UserInterface.ControlExtensions;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Input;
+
+namespace Content.Client._RMC14.Mortar;
+
+[GenerateTypedNameReferences]
+public sealed partial class MortarWindow : DefaultWindow
+{
+    public readonly FloatSpinBox TargetX;
+    public readonly FloatSpinBox TargetY;
+    public readonly FloatSpinBox DialX;
+    public readonly FloatSpinBox DialY;
+
+    public MortarWindow()
+    {
+        RobustXamlLoader.Load(this);
+
+        static LineEdit? SpinBoxGetLineEdit(FloatSpinBox box)
+        {
+            foreach (var edit in box.GetControlOfType<LineEdit>())
+            {
+                return edit;
+            }
+
+            return null;
+        }
+
+        void SpinBoxTab(FloatSpinBox from, FloatSpinBox to)
+        {
+            if (SpinBoxGetLineEdit(from) is not { } fromEdit ||
+                SpinBoxGetLineEdit(to) is not { } toEdit)
+            {
+                return;
+            }
+
+            fromEdit.OnKeyBindDown += args =>
+            {
+                if (args.Function == EngineKeyFunctions.GuiTabNavigateNext)
+                    toEdit.GrabKeyboardFocus();
+            };
+
+            toEdit.OnKeyBindDown += args =>
+            {
+                if (args.Function == EngineKeyFunctions.GuiTabNavigatePrev)
+                    fromEdit.GrabKeyboardFocus();
+            };
+        }
+
+        TargetX = UIExtensions.CreateDialSpinBox();
+        TargetXContainer.AddChild(TargetX);
+
+        TargetY = UIExtensions.CreateDialSpinBox();
+        TargetYContainer.AddChild(TargetY);
+
+        DialX = UIExtensions.CreateDialSpinBox();
+        DialXContainer.AddChild(DialX);
+
+        DialY = UIExtensions.CreateDialSpinBox();
+        DialYContainer.AddChild(DialY);
+
+        SpinBoxTab(TargetX, TargetY);
+        SpinBoxTab(TargetY, DialX);
+        SpinBoxTab(DialX, DialY);
+        SpinBoxTab(DialY, TargetX);
+    }
+}

+ 26 - 0
Content.Client/_RMC14/UserInterface/UIExtensions.cs

@@ -0,0 +1,26 @@
+using Content.Client.UserInterface.ControlExtensions;
+using Robust.Client.GameObjects;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using static Robust.Client.UserInterface.Controls.FloatSpinBox;
+
+namespace Content.Client._RMC14.UserInterface;
+
+public static class UIExtensions
+{
+    public static FloatSpinBox CreateDialSpinBox(float value = default, Action<FloatSpinBoxEventArgs>? onValueChanged = null, bool buttons = true, int minWidth = 130)
+    {
+        var spinBox = new FloatSpinBox(1, 0) { MinWidth = minWidth };
+        spinBox.Value = value;
+        spinBox.OnValueChanged += onValueChanged;
+        if (!buttons)
+        {
+            foreach (var button in spinBox.GetControlOfType<Button>())
+            {
+                button.Visible = false;
+            }
+        }
+
+        return spinBox;
+    }
+}

+ 2 - 2
Content.Server/GameTicking/GameTicker.cs

@@ -88,8 +88,8 @@ public override void Initialize()
             InitializePlayer();
             InitializeLobbyBackground();
             InitializeGamePreset();
-            DebugTools.Assert(_prototypeManager.Index<JobPrototype>(FallbackOverflowJob).Name == FallbackOverflowJobName,
-                "Overflow role does not have the correct name!");
+            //DebugTools.Assert(_prototypeManager.Index<JobPrototype>(FallbackOverflowJob).Name == FallbackOverflowJobName,
+            //    "Overflow role does not have the correct name!");
             InitializeGameRules();
             InitializeReplays();
             _initialized = true;

+ 84 - 0
Content.Server/_RMC14/Mortar/MortarSystem.cs

@@ -0,0 +1,84 @@
+using System.Numerics;
+using Content.Server.Popups;
+using Content.Shared._RMC14.Mortar;
+using Robust.Server.Containers;
+using Robust.Shared.Map;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+using static Content.Shared.Popups.PopupType;
+
+namespace Content.Server._RMC14.Mortar;
+
+public sealed class MortarSystem : SharedMortarSystem
+{
+    [Dependency] private readonly ContainerSystem _container = default!;
+    [Dependency] private readonly PopupSystem _popup = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+
+    protected override bool CanLoadPopup(
+        Entity<MortarComponent> mortar,
+        Entity<MortarShellComponent> shell,
+        EntityUid user,
+        out TimeSpan travelTime,
+        out MapCoordinates coordinates)
+    {
+        travelTime = default;
+        coordinates = default;
+
+        if (!mortar.Comp.Deployed)
+        {
+            _popup.PopupEntity(Loc.GetString("rmc-mortar-not-deployed", ("mortar", mortar)), user, user, SmallCaution);
+            return false;
+        }
+
+        var time = _timing.CurTime;
+        if (time < mortar.Comp.LastFiredAt + mortar.Comp.FireDelay)
+        {
+            _popup.PopupEntity(Loc.GetString("rmc-mortar-fire-cooldown", ("mortar", mortar)), user, user, SmallCaution);
+            return false;
+        }
+
+        var target = mortar.Comp.Target + mortar.Comp.Offset + mortar.Comp.Dial;
+        if (target == Vector2i.Zero)
+        {
+            _popup.PopupEntity(Loc.GetString("rmc-mortar-not-aimed", ("mortar", mortar)), user, user, SmallCaution);
+            return false;
+        }
+
+        var mortarCoordinates = _transform.GetMapCoordinates(mortar);
+        coordinates = new MapCoordinates(Vector2.Zero, mortarCoordinates.MapId);
+
+        coordinates = coordinates.Offset(target);
+        travelTime = shell.Comp.TravelDelay;
+
+        if ((mortarCoordinates.Position - coordinates.Position).Length() < mortar.Comp.MinimumRange)
+        {
+            _popup.PopupEntity(Loc.GetString("rmc-mortar-target-too-close"), user, user, SmallCaution);
+            return false;
+        }
+
+        if ((mortarCoordinates.Position - coordinates.Position).Length() > mortar.Comp.MaximumRange)
+        {
+            _popup.PopupEntity(Loc.GetString("rmc-mortar-target-too-far"), user, user, SmallCaution);
+            return false;
+        }
+
+        if (mortar.Comp.FireRandomOffset is { Length: > 0 } fireRandomOffset)
+        {
+            var xDeviation = _random.Pick(fireRandomOffset);
+            var yDeviation = _random.Pick(fireRandomOffset);
+            coordinates = coordinates.Offset(new Vector2(xDeviation, yDeviation));
+        }
+
+        if (_container.TryGetContainer(mortar, mortar.Comp.ContainerId, out var container) &&
+            !_container.CanInsert(shell, container))
+        {
+            _popup.PopupClient(Loc.GetString("rmc-mortar-cant-insert", ("shell", shell), ("mortar", mortar)), user, user, SmallCaution);
+            return false;
+        }
+
+        return true;
+    }
+}

+ 13 - 0
Content.Shared/_RMC14/Extensions/IntExtensions.cs

@@ -0,0 +1,13 @@
+namespace Content.Shared._RMC14.Extensions;
+
+public static class IntExtensions
+{
+    public static void Cap(this ref int value, int at)
+    {
+        at = Math.Abs(at);
+        if (value > at)
+            value = at;
+        else if (value < -at)
+            value = -at;
+    }
+}

+ 37 - 0
Content.Shared/_RMC14/Mortar/ActiveMortarShellComponent.cs

@@ -0,0 +1,37 @@
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Map;
+
+namespace Content.Shared._RMC14.Mortar;
+
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedMortarSystem))]
+public sealed partial class ActiveMortarShellComponent : Component
+{
+    [DataField, AutoNetworkedField]
+    public EntityCoordinates Coordinates;
+
+    [DataField, AutoNetworkedField]
+    public TimeSpan WarnAt;
+
+    [DataField, AutoNetworkedField]
+    public bool Warned;
+
+    [DataField, AutoNetworkedField]
+    public float WarnRange = 15;
+
+    [DataField, AutoNetworkedField]
+    public SoundSpecifier? WarnSound = new SoundPathSpecifier("/Audio/_RMC/Weapons/gun_mortar_travel.ogg");
+
+    [DataField, AutoNetworkedField]
+    public TimeSpan ImpactWarnAt;
+
+    [DataField, AutoNetworkedField]
+    public bool ImpactWarned;
+
+    [DataField, AutoNetworkedField]
+    public float ImpactWarnRange = 10;
+
+    [DataField, AutoNetworkedField]
+    public TimeSpan LandAt;
+}

+ 7 - 0
Content.Shared/_RMC14/Mortar/DeployMortarDoAfterEvent.cs

@@ -0,0 +1,7 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._RMC14.Mortar;
+
+[Serializable, NetSerializable]
+public sealed partial class DeployMortarDoAfterEvent : SimpleDoAfterEvent;

+ 15 - 0
Content.Shared/_RMC14/Mortar/DialMortarDoAfterEvent.cs

@@ -0,0 +1,15 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._RMC14.Mortar;
+
+[Serializable, NetSerializable]
+public sealed partial class DialMortarDoAfterEvent : SimpleDoAfterEvent
+{
+    public readonly Vector2i Vector;
+
+    public DialMortarDoAfterEvent(Vector2i vector)
+    {
+        Vector = vector;
+    }
+}

+ 7 - 0
Content.Shared/_RMC14/Mortar/LoadMortarShellDoAfterEvent.cs

@@ -0,0 +1,7 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._RMC14.Mortar;
+
+[Serializable, NetSerializable]
+public sealed partial class LoadMortarShellDoAfterEvent : SimpleDoAfterEvent;

+ 89 - 0
Content.Shared/_RMC14/Mortar/MortarComponent.cs

@@ -0,0 +1,89 @@
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+
+namespace Content.Shared._RMC14.Mortar;
+
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState, AutoGenerateComponentPause]
+[Access(typeof(SharedMortarSystem))]
+public sealed partial class MortarComponent : Component
+{
+    [DataField, AutoNetworkedField]
+    public string ContainerId = "rmc_mortar_container";
+
+    [DataField, AutoNetworkedField]
+    public TimeSpan DeployDelay = TimeSpan.FromSeconds(4);
+
+    [DataField, AutoNetworkedField]
+    public TimeSpan TargetDelay = TimeSpan.FromSeconds(3);
+
+    [DataField, AutoNetworkedField]
+    public TimeSpan DialDelay = TimeSpan.FromSeconds(1);
+
+    [DataField, AutoNetworkedField]
+    public bool Deployed;
+
+    [DataField, AutoNetworkedField]
+    public Vector2i Target;
+
+    [DataField, AutoNetworkedField]
+    public Vector2i Offset;
+
+    [DataField, AutoNetworkedField]
+    public Vector2i Dial;
+
+    [DataField, AutoNetworkedField]
+    public TimeSpan FireDelay = TimeSpan.FromSeconds(4); // original was 9
+
+    [DataField, AutoNetworkedField]
+    public int TilesPerOffset = 20;
+
+    [DataField, AutoNetworkedField]
+    public int MaxTarget = 1000;
+
+    [DataField, AutoNetworkedField]
+    public int MaxDial = 10;
+
+    [DataField, AutoNetworkedField]
+    public int MinimumRange = 25;
+
+    [DataField, AutoNetworkedField]
+    public int MaximumRange = 75;
+
+    [DataField, AutoNetworkedField]
+    public string FixtureId = "mortar";
+
+    [DataField, AutoNetworkedField]
+    public TimeSpan AnimationTime = TimeSpan.FromSeconds(0.3);
+
+    [DataField, AutoNetworkedField]
+    public string AnimationLayer = "mortar";
+
+    [DataField, AutoNetworkedField]
+    public string AnimationState = "mortar_fire";
+
+    [DataField, AutoNetworkedField]
+    public string DeployedState = "mortar_deploy";
+
+    [DataField, AutoNetworkedField]
+    public SoundSpecifier? DeploySound = new SoundPathSpecifier("/Audio/_RMC/Weapons/gun_mortar_unpack.ogg");
+
+    [DataField, AutoNetworkedField]
+    public SoundSpecifier? ReloadSound = new SoundPathSpecifier("/Audio/_RMC/Weapons/gun_mortar_reload.ogg");
+
+    [DataField, AutoNetworkedField]
+    public SoundSpecifier? FireSound = new SoundPathSpecifier("/Audio/_RMC/Weapons/gun_mortar_fire.ogg", AudioParams.Default.AddVolume(4));
+
+    [DataField, AutoNetworkedField]
+    public TimeSpan? Cooldown;
+
+    [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField, AutoPausedField]
+    public TimeSpan LastFiredAt;
+
+    [DataField, AutoNetworkedField]
+    public EntProtoId Drop = "MortarKit";
+
+    [DataField, AutoNetworkedField]
+    public int[] FireRandomOffset = new[] { -2, 0, 0, 2 }; // used to be -1 0 0 1
+}

+ 9 - 0
Content.Shared/_RMC14/Mortar/MortarFiredEvent.cs

@@ -0,0 +1,9 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._RMC14.Mortar;
+
+[Serializable, NetSerializable]
+public sealed class MortarFiredEvent(NetEntity mortar) : EntityEventArgs
+{
+    public readonly NetEntity Mortar = mortar;
+}

+ 20 - 0
Content.Shared/_RMC14/Mortar/MortarShellComponent.cs

@@ -0,0 +1,20 @@
+using Robust.Shared.GameStates;
+
+namespace Content.Shared._RMC14.Mortar;
+
+[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
+[Access(typeof(SharedMortarSystem))]
+public sealed partial class MortarShellComponent : Component
+{
+    [DataField, AutoNetworkedField]
+    public TimeSpan LoadDelay = TimeSpan.FromSeconds(1.5);
+
+    [DataField, AutoNetworkedField]
+    public TimeSpan TravelDelay = TimeSpan.FromSeconds(8); //used to be 4.5
+
+    [DataField, AutoNetworkedField]
+    public TimeSpan ImpactWarningDelay = TimeSpan.FromSeconds(2.5);
+
+    [DataField, AutoNetworkedField]
+    public TimeSpan ImpactDelay = TimeSpan.FromSeconds(4.5);
+}

+ 6 - 0
Content.Shared/_RMC14/Mortar/MortarShellLandEvent.cs

@@ -0,0 +1,6 @@
+using Robust.Shared.Map;
+
+namespace Content.Shared._RMC14.Mortar;
+
+[ByRefEvent]
+public readonly record struct MortarShellLandEvent(EntityCoordinates Coordinates);

+ 24 - 0
Content.Shared/_RMC14/Mortar/MortarUI.cs

@@ -0,0 +1,24 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._RMC14.Mortar;
+
+[Serializable, NetSerializable]
+public enum MortarUiKey
+{
+    Key,
+}
+
+[Serializable, NetSerializable]
+public sealed class MortarTargetBuiMsg(Vector2i target) : BoundUserInterfaceMessage
+{
+    public Vector2i Target = target;
+}
+
+[Serializable, NetSerializable]
+public sealed class MortarDialBuiMsg(Vector2i target) : BoundUserInterfaceMessage
+{
+    public Vector2i Target = target;
+}
+
+[Serializable, NetSerializable]
+public sealed class MortarViewCamerasMsg : BoundUserInterfaceMessage;

+ 16 - 0
Content.Shared/_RMC14/Mortar/MortarVisuals.cs

@@ -0,0 +1,16 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._RMC14.Mortar;
+
+[Serializable, NetSerializable]
+public enum MortarVisuals
+{
+    Item,
+    Deployed,
+}
+
+[Serializable, NetSerializable]
+public enum MortarVisualLayers
+{
+    State,
+}

+ 447 - 0
Content.Shared/_RMC14/Mortar/SharedMortarSystem.cs

@@ -0,0 +1,447 @@
+using Content.Shared._RMC14.Extensions;
+using Content.Shared.Administration.Logs;
+using Content.Shared.Construction.Components;
+using Content.Shared.Coordinates;
+using Content.Shared.Damage;
+using Content.Shared.Destructible;
+using Content.Shared.DoAfter;
+using Content.Shared.Examine;
+using Content.Shared.Interaction;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Popups;
+using Content.Shared.UserInterface;
+using Content.Shared.Explosion.EntitySystems;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Containers;
+using Robust.Shared.Map;
+using Robust.Shared.Network;
+using Robust.Shared.Physics.Systems;
+using Robust.Shared.Player;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+
+namespace Content.Shared._RMC14.Mortar;
+
+public abstract class SharedMortarSystem : EntitySystem
+{
+    [Dependency] private readonly ISharedAdminLogManager _adminLogs = default!;
+    [Dependency] private readonly SharedAppearanceSystem _appearance = default!;
+    [Dependency] private readonly SharedAudioSystem _audio = default!;
+    [Dependency] private readonly SharedContainerSystem _container = default!;
+    [Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
+    [Dependency] private readonly FixtureSystem _fixture = default!;
+    [Dependency] private readonly MetaDataSystem _metaData = default!;
+    [Dependency] private readonly INetManager _net = default!;
+    [Dependency] private readonly SharedPhysicsSystem _physics = default!;
+    [Dependency] private readonly SharedPopupSystem _popup = default!;
+    [Dependency] private readonly ISharedPlayerManager _player = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly IGameTiming _timing = default!;
+    [Dependency] private readonly SharedTransformSystem _transform = default!;
+    [Dependency] private readonly SharedUserInterfaceSystem _ui = default!;
+    [Dependency] private readonly SharedExplosionSystem _explosion = default!;
+
+    private EntityQuery<TransformComponent> _transformQuery;
+
+    public override void Initialize()
+    {
+        _transformQuery = GetEntityQuery<TransformComponent>();
+
+        SubscribeLocalEvent<MortarComponent, UseInHandEvent>(OnMortarUseInHand, before: [typeof(ActivatableUISystem)]);
+        SubscribeLocalEvent<MortarComponent, DeployMortarDoAfterEvent>(OnMortarDeployDoAfter);
+        SubscribeLocalEvent<MortarComponent, TargetMortarDoAfterEvent>(OnMortarTargetDoAfter);
+        SubscribeLocalEvent<MortarComponent, DialMortarDoAfterEvent>(OnMortarDialDoAfter);
+        SubscribeLocalEvent<MortarComponent, InteractUsingEvent>(OnMortarInteractUsing);
+        SubscribeLocalEvent<MortarComponent, LoadMortarShellDoAfterEvent>(OnMortarLoadDoAfter);
+        SubscribeLocalEvent<MortarComponent, UnanchorAttemptEvent>(OnMortarUnanchorAttempt);
+        SubscribeLocalEvent<MortarComponent, AnchorStateChangedEvent>(OnMortarAnchorStateChanged);
+        SubscribeLocalEvent<MortarComponent, ExaminedEvent>(OnMortarExamined);
+        SubscribeLocalEvent<MortarComponent, ActivatableUIOpenAttemptEvent>(OnMortarActivatableUIOpenAttempt);
+        SubscribeLocalEvent<MortarComponent, CombatModeShouldHandInteractEvent>(OnMortarShouldInteract);
+        SubscribeLocalEvent<MortarComponent, DestructionEventArgs>(OnMortarDestruction);
+        SubscribeLocalEvent<MortarComponent, BeforeDamageChangedEvent>(OnMortarBeforeDamageChanged);
+
+        Subs.BuiEvents<MortarComponent>(MortarUiKey.Key,
+            subs =>
+            {
+                subs.Event<MortarTargetBuiMsg>(OnMortarTargetBui);
+                subs.Event<MortarDialBuiMsg>(OnMortarDialBui);
+            });
+    }
+
+    private void OnMortarBeforeDamageChanged(Entity<MortarComponent> ent, ref BeforeDamageChangedEvent args)
+    {
+        if (!ent.Comp.Deployed) // cannot destroy in item form
+            args.Cancelled = true;
+    }
+
+    private void OnMortarDestruction(Entity<MortarComponent> mortar, ref DestructionEventArgs args)
+    {
+        if (!mortar.Comp.Deployed || _net.IsClient)
+            return;
+
+        SpawnAtPosition(mortar.Comp.Drop, mortar.Owner.ToCoordinates());
+    }
+
+    private void OnMortarUseInHand(Entity<MortarComponent> mortar, ref UseInHandEvent args)
+    {
+        args.Handled = true;
+        DeployMortar(mortar, args.User);
+    }
+
+    private void OnMortarDeployDoAfter(Entity<MortarComponent> mortar, ref DeployMortarDoAfterEvent args)
+    {
+        var user = args.User;
+        if (args.Cancelled || args.Handled)
+            return;
+
+        args.Handled = true;
+        if (mortar.Comp.Deployed)
+            return;
+
+        if (!CanDeployPopup(mortar, user))
+            return;
+
+        mortar.Comp.Deployed = true;
+        Dirty(mortar);
+
+        if (_fixture.GetFixtureOrNull(mortar, mortar.Comp.FixtureId) is { } fixture)
+            _physics.SetHard(mortar, fixture, true);
+
+        _appearance.SetData(mortar, MortarVisualLayers.State, MortarVisuals.Deployed);
+
+        var xform = Transform(mortar);
+        var coordinates = _transform.GetMoverCoordinates(mortar, xform);
+        var rotation = Transform(user).LocalRotation.GetCardinalDir().ToAngle();
+        _transform.SetCoordinates(mortar, xform, coordinates, rotation);
+        _transform.AnchorEntity((mortar, xform));
+
+        _audio.PlayPredicted(mortar.Comp.DeploySound, mortar, user);
+    }
+
+    private void OnMortarTargetDoAfter(Entity<MortarComponent> mortar, ref TargetMortarDoAfterEvent args)
+    {
+        if (args.Cancelled || args.Handled)
+            return;
+
+        args.Handled = true;
+
+        var user = args.User;
+        var selfMsg = Loc.GetString("rmc-mortar-target-finish-self", ("mortar", mortar));
+        var othersMsg = Loc.GetString("rmc-mortar-target-finish-others", ("user", user), ("mortar", mortar));
+        _popup.PopupPredicted(selfMsg, othersMsg, user, user);
+        if (_net.IsClient)
+            return;
+
+        var target = args.Vector;
+        var position = _transform.GetMapCoordinates(mortar).Position;
+        var offset = target;
+        //if (_rmcPlanet.TryGetOffset(_transform.GetMapCoordinates(mortar.Owner), out var planetOffset))
+        //    offset -= planetOffset;
+        // RMC uses a system to offset based on set values per map
+
+        mortar.Comp.Target = target;
+
+        var tilesPer = mortar.Comp.TilesPerOffset;
+        var xOffset = (int) Math.Floor(Math.Abs(offset.X - position.X) / tilesPer);
+        var yOffset = (int) Math.Floor(Math.Abs(offset.Y - position.Y) / tilesPer);
+        mortar.Comp.Offset = (_random.Next(-xOffset, xOffset + 1), _random.Next(-yOffset, yOffset + 1));
+
+        Dirty(mortar);
+    }
+
+    private void OnMortarDialDoAfter(Entity<MortarComponent> mortar, ref DialMortarDoAfterEvent args)
+    {
+        if (args.Cancelled || args.Handled)
+            return;
+
+        args.Handled = true;
+
+        mortar.Comp.Dial = args.Vector;
+        Dirty(mortar);
+
+        var user = args.User;
+        var selfMsg = Loc.GetString("rmc-mortar-dial-finish-self", ("mortar", mortar));
+        var othersMsg = Loc.GetString("rmc-mortar-dial-finish-others", ("user", user), ("mortar", mortar));
+        _popup.PopupPredicted(selfMsg, othersMsg, user, user);
+    }
+
+    private void OnMortarInteractUsing(Entity<MortarComponent> mortar, ref InteractUsingEvent args)
+    {
+        var shellId = args.Used;
+        if (!TryComp(shellId, out MortarShellComponent? shell))
+            return;
+
+        args.Handled = true;
+        var user = args.User;
+
+        if (!CanLoadPopup(mortar, (shellId, shell), user, out _, out _))
+            return;
+
+        var ev = new LoadMortarShellDoAfterEvent();
+        var doAfter = new DoAfterArgs(EntityManager, user, shell.LoadDelay, ev, mortar, mortar, shellId)
+        {
+            BreakOnMove = true,
+            BreakOnHandChange = true,
+            //ForceVisible = true,
+        };
+
+        if (_doAfter.TryStartDoAfter(doAfter))
+        {
+            var selfMsg = Loc.GetString("rmc-mortar-shell-load-start-self", ("mortar", mortar), ("shell", shellId));
+            var othersMsg = Loc.GetString("rmc-mortar-shell-load-start-others",
+                ("user", user),
+                ("mortar", mortar),
+                ("shell", shellId));
+            _popup.PopupPredicted(selfMsg, othersMsg, mortar, user);
+
+            _audio.PlayPredicted(mortar.Comp.ReloadSound, mortar, user);
+        }
+    }
+
+    private void OnMortarLoadDoAfter(Entity<MortarComponent> mortar, ref LoadMortarShellDoAfterEvent args)
+    {
+        var user = args.User;
+        if (args.Cancelled || args.Handled || args.Used is not { } shellId)
+            return;
+
+        args.Handled = true;
+        if (_net.IsClient)
+            return;
+
+        if (!TryComp(shellId, out MortarShellComponent? shell))
+            return;
+
+        if (!mortar.Comp.Deployed)
+            return;
+
+        if (HasComp<ActiveMortarShellComponent>(shellId))
+            return;
+
+        if (!CanLoadPopup(mortar, (shellId, shell), user, out var travelTime, out var coordinates))
+            return;
+
+        var container = _container.EnsureContainer<Container>(mortar, mortar.Comp.ContainerId);
+        if (!_container.Insert(shellId, container))
+            return;
+
+        var time = _timing.CurTime;
+        mortar.Comp.LastFiredAt = time;
+
+        var active = new ActiveMortarShellComponent
+        {
+            Coordinates = _transform.ToCoordinates(coordinates),
+            WarnAt = time + travelTime,
+            ImpactWarnAt = time + travelTime + shell.ImpactWarningDelay,
+            LandAt = time + travelTime + shell.ImpactDelay,
+        };
+
+        AddComp(shellId, active, true);
+
+        var selfMsg = Loc.GetString("rmc-mortar-shell-load-finish-self", ("mortar", mortar), ("shell", shellId));
+        var othersMsg = Loc.GetString("rmc-mortar-shell-load-finish-others", ("user", user), ("mortar", mortar), ("shell", shellId));
+        _popup.PopupPredicted(selfMsg, othersMsg, user, user);
+
+        othersMsg = Loc.GetString("rmc-mortar-shell-fire", ("mortar", mortar));
+        _popup.PopupEntity(othersMsg, mortar, PopupType.MediumCaution);
+
+        var filter = Filter.Pvs(mortar);
+        _audio.PlayPvs(mortar.Comp.FireSound, mortar);
+
+        var ev = new MortarFiredEvent(GetNetEntity(mortar));
+        if (_net.IsServer)
+            RaiseNetworkEvent(ev, filter);
+    }
+
+    private void OnMortarUnanchorAttempt(Entity<MortarComponent> mortar, ref UnanchorAttemptEvent args)
+    {
+        if (args.Cancelled)
+            return;
+    }
+
+    private void OnMortarAnchorStateChanged(Entity<MortarComponent> mortar, ref AnchorStateChangedEvent args)
+    {
+        if (args.Anchored)
+            return;
+
+        mortar.Comp.Deployed = false;
+        Dirty(mortar);
+
+        if (_fixture.GetFixtureOrNull(mortar, mortar.Comp.FixtureId) is { } fixture)
+            _physics.SetHard(mortar, fixture, false);
+
+        _appearance.SetData(mortar, MortarVisualLayers.State, MortarVisuals.Item);
+    }
+
+    private void OnMortarExamined(Entity<MortarComponent> ent, ref ExaminedEvent args)
+    {
+        using (args.PushGroup(nameof(MortarComponent)))
+        {
+            args.PushMarkup(Loc.GetString("rmc-mortar-less-accurate-with-range"));
+        }
+    }
+
+    private void OnMortarActivatableUIOpenAttempt(Entity<MortarComponent> ent, ref ActivatableUIOpenAttemptEvent args)
+    {
+        if (args.Cancelled)
+            return;
+
+        if (!ent.Comp.Deployed)
+            args.Cancel();
+    }
+
+    private void OnMortarShouldInteract(Entity<MortarComponent> ent, ref CombatModeShouldHandInteractEvent args)
+    {
+        args.Cancelled = true;
+    }
+
+    private void OnMortarTargetBui(Entity<MortarComponent> mortar, ref MortarTargetBuiMsg args)
+    {
+        args.Target.X.Cap(mortar.Comp.MaxTarget);
+        args.Target.Y.Cap(mortar.Comp.MaxTarget);
+
+        var user = args.Actor;
+        var ev = new TargetMortarDoAfterEvent(args.Target);
+        var doAfter = new DoAfterArgs(EntityManager, user, mortar.Comp.TargetDelay, ev, mortar)
+        {
+            BreakOnMove = true,
+        };
+
+        if (_doAfter.TryStartDoAfter(doAfter))
+        {
+            var selfMsg = Loc.GetString("rmc-mortar-target-start-self", ("mortar", mortar));
+            var othersMsg = Loc.GetString("rmc-mortar-target-start-others", ("user", user), ("mortar", mortar));
+            _popup.PopupPredicted(selfMsg, othersMsg, user, user);
+        }
+    }
+
+    private void OnMortarDialBui(Entity<MortarComponent> mortar, ref MortarDialBuiMsg args)
+    {
+        args.Target.X.Cap(mortar.Comp.MaxDial);
+        args.Target.Y.Cap(mortar.Comp.MaxDial);
+
+        var user = args.Actor;
+        var ev = new DialMortarDoAfterEvent(args.Target);
+        var doAfter = new DoAfterArgs(EntityManager, user, mortar.Comp.TargetDelay, ev, mortar)
+        {
+            BreakOnMove = true,
+        };
+
+        if (_doAfter.TryStartDoAfter(doAfter))
+        {
+            var selfMsg = Loc.GetString("rmc-mortar-dial-start-self", ("mortar", mortar));
+            var othersMsg = Loc.GetString("rmc-mortar-dial-start-others", ("user", user), ("mortar", mortar));
+            _popup.PopupPredicted(selfMsg, othersMsg, user, user);
+        }
+    }
+
+    private void DeployMortar(Entity<MortarComponent> mortar, EntityUid user)
+    {
+        if (mortar.Comp.Deployed)
+            return;
+
+        if (!CanDeployPopup(mortar, user))
+            return;
+
+        var ev = new DeployMortarDoAfterEvent();
+        var args = new DoAfterArgs(EntityManager, user, mortar.Comp.DeployDelay, ev, mortar)
+        {
+            BreakOnMove = true,
+            BreakOnHandChange = true,
+        };
+
+        if (_doAfter.TryStartDoAfter(args))
+            _popup.PopupClient(Loc.GetString("rmc-mortar-deploy-start", ("mortar", mortar)), user, user);
+    }
+
+    private bool CanDeployPopup(Entity<MortarComponent> mortar, EntityUid user)
+    {
+        //if (!_area.CanMortarPlacement(user.ToCoordinates()))
+        //{
+        //    _popup.PopupClient(Loc.GetString("rmc-mortar-covered", ("mortar", mortar)), user, user, PopupType.SmallCaution);
+        //    return false;
+        //}
+        // Need a system to check for a roof
+
+        return true;
+    }
+
+    protected virtual bool CanLoadPopup(
+        Entity<MortarComponent> mortar,
+        Entity<MortarShellComponent> shell,
+        EntityUid user,
+        out TimeSpan travelTime,
+        out MapCoordinates coordinates)
+    {
+        travelTime = default;
+        coordinates = default;
+        return false;
+    }
+
+    public void PopupWarning(MapCoordinates coordinates, float range, LocId warning, LocId warningAbove)
+    {
+        foreach (var session in _player.NetworkedSessions)
+        {
+            if (session.AttachedEntity is not { } recipient ||
+                !_transformQuery.TryComp(recipient, out var xform) ||
+                xform.MapID != coordinates.MapId)
+            {
+                continue;
+            }
+
+            var sessionCoordinates = _transform.GetMapCoordinates(xform);
+            var distanceVec = (coordinates.Position - sessionCoordinates.Position);
+            var distance = distanceVec.Length();
+            if (distance > range)
+                continue;
+
+            var direction = distanceVec.GetDir().ToString().ToUpperInvariant();
+            var msg = distance < 1
+                ? Loc.GetString(warningAbove)
+                : Loc.GetString(warning, ("direction", direction));
+            _popup.PopupEntity(msg, recipient, recipient, PopupType.LargeCaution);
+        }
+    }
+
+    public override void Update(float frameTime)
+    {
+        if (_net.IsClient)
+            return;
+
+        var time = _timing.CurTime;
+        var shells = EntityQueryEnumerator<ActiveMortarShellComponent>();
+        while (shells.MoveNext(out var uid, out var active))
+        {
+            if (!active.Warned && time >= active.WarnAt)
+            {
+                active.Warned = true;
+                var coordinates = _transform.ToMapCoordinates(active.Coordinates);
+                //PopupWarning(coordinates,
+                //    active.WarnRange,
+                //    "rmc-mortar-shell-warning",
+                //    "rmc-mortar-shell-warning-above");
+                _audio.PlayPvs(active.WarnSound, active.Coordinates);
+            }
+
+            if (!active.ImpactWarned && time >= active.ImpactWarnAt)
+            {
+                active.ImpactWarned = true;
+                var coordinates = _transform.ToMapCoordinates(active.Coordinates);
+                //PopupWarning(coordinates,
+                //    active.WarnRange,
+                //    "rmc-mortar-shell-impact-warning",
+                //    "rmc-mortar-shell-impact-warning-above");
+            }
+
+            if (time >= active.LandAt)
+            {
+                _transform.SetCoordinates(uid, active.Coordinates);
+
+                var ev = new MortarShellLandEvent(active.Coordinates);
+                RaiseLocalEvent(uid, ref ev);
+
+                _explosion.TriggerExplosive(uid);
+            }
+        }
+    }
+}

+ 15 - 0
Content.Shared/_RMC14/Mortar/TargetMortarDoAfterEvent.cs

@@ -0,0 +1,15 @@
+using Content.Shared.DoAfter;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._RMC14.Mortar;
+
+[Serializable, NetSerializable]
+public sealed partial class TargetMortarDoAfterEvent : SimpleDoAfterEvent
+{
+    public readonly Vector2i Vector;
+
+    public TargetMortarDoAfterEvent(Vector2i vector)
+    {
+        Vector = vector;
+    }
+}

BIN
Resources/Audio/_RMC/Weapons/gun_mortar_fire.ogg


BIN
Resources/Audio/_RMC/Weapons/gun_mortar_reload.ogg


BIN
Resources/Audio/_RMC/Weapons/gun_mortar_travel.ogg


BIN
Resources/Audio/_RMC/Weapons/gun_mortar_unpack.ogg


BIN
Resources/Audio/_RMC/Weapons/gun_orbital_travel.ogg


+ 45 - 0
Resources/Locale/en-US/_RMC/mortar.ftl

@@ -0,0 +1,45 @@
+rmc-mortar-deploy-start = You start deploying {THE($mortar)}.
+rmc-mortar-shell-busy = Someone else is currently using {THE($mortar)}
+rmc-mortar-not-aimed = {CAPITALIZE(THE($mortar))} needs to be aimed first.
+rmc-mortar-covered = You probably shouldn't deploy {THE($mortar)} indoors.
+rmc-mortar-target-invalid = You cannot fire {THE($mortar)} to this target.
+rmc-mortar-target-not-area = This area is out of bounds!
+rmc-mortar-target-covered = You cannot hit the target. It is probably underground.
+rmc-mortar-bad-idea = You realize how bad of an idea this is and quickly stop.
+rmc-mortar-cant-insert = You can't put {THE($shell)} in {THE($mortar)}!
+rmc-mortar-not-deployed = You need to deploy {THE($mortar)} first!
+rmc-mortar-fire-cooldown = {CAPITALIZE(THE($mortar))} barrel is still steaming hot. Wait a few seconds and stop firing it.
+rmc-mortar-less-accurate-with-range = [color=red]It will be less accurate the further away the target is![/color]
+rmc-mortar-target-start-self = You start adjusting {THE($mortar)}'s firing angle and distance to match the new coordinates.
+rmc-mortar-target-start-others = {$user} starts adjusting {THE($mortar)}'s firing angle and distance.
+rmc-mortar-target-finish-self = You finish adjusting {THE($mortar)}'s firing angle and distance to match the new coordinates.
+rmc-mortar-target-finish-others = {$user} finishes adjusting {THE($mortar)}'s firing angle and distance.
+rmc-mortar-dial-start-self = You start dialing {THE($mortar)}'s firing angle and distance to match the new coordinates.
+rmc-mortar-dial-start-others = {$user} starts dialing {THE($mortar)}'s firing angle and distance.
+rmc-mortar-dial-finish-self = You finish dialing {THE($mortar)}'s firing angle and distance to match the new coordinates.
+rmc-mortar-dial-finish-others = {$user} finishes dialing {THE($mortar)}'s firing angle and distance.
+
+rmc-mortar-shell-load-start-self = You start loading {THE($shell)} into {THE($mortar)}.
+rmc-mortar-shell-load-start-others = {$user} starts loading {THE($shell)} into {THE($mortar)}.
+rmc-mortar-shell-load-finish-self = You load {THE($shell)} into {THE($mortar)}.
+rmc-mortar-shell-fire = {CAPITALIZE(THE($mortar))} fires!
+rmc-mortar-shell-warning = A SHELL IS COMING DOWN TO YOUR {$direction}
+rmc-mortar-shell-warning-above = A SHELL IS COMING DOWN RIGHT ABOVE YOU
+rmc-mortar-shell-impact-warning = A SHELL IS ABOUT TO IMPACT TO YOUR {$direction}
+rmc-mortar-shell-impact-warning-above = A SHELL IS ABOUT TO IMPACT RIGHT ABOVE YOU
+
+rmc-mortar-interface = Mortar Interface
+
+rmc-mortar-target-x = Target X:
+rmc-mortar-target-y = Target Y:
+rmc-mortar-target-set = Set Target
+rmc-mortar-target-too-close = You cannot aim at this coordinate, it is too close to your mortar.
+rmc-mortar-target-too-far = You cannot aim at this coordinate, it is too far from your mortar.
+
+rmc-mortar-offset-x = X Offset:
+rmc-mortar-offset-y = Y Offset:
+rmc-mortar-offset-set = Dial Offset
+rmc-mortar-offset-too-far = You cannot dial to this coordinate, it is too far away from the original target.
+rmc-mortar-offset-too-close = You cannot dial to this coordinate, it is too close to your mortar.
+rmc-mortar-offset-max = Max dial
+  offset: {$max}

+ 31 - 0
Resources/Prototypes/Civ14/Entities/Objects/Explosives/Mortars/mortar_shells.yml

@@ -0,0 +1,31 @@
+- type: entity
+  abstract: true
+  parent: BaseItem
+  id: MortarAmmoBase
+  name: base mortar shell
+  components:
+  - type: Sprite
+    sprite: Civ14/Objects/Mortars/shells.rsi
+    state: mortar_ammo_he
+  - type: Item
+    size: Huge
+  - type: MortarShell
+
+- type: entity
+  parent: MortarAmmoBase
+  id: MortarShellHE
+  name: high explosive mortar shell
+  description: A mortar shell packed with high explosive. Drop into a mortar tube to activate.
+  components:
+  - type: Item
+  - type: Sprite
+    sprite: Civ14/Objects/Mortars/shells.rsi
+    state: mortar_ammo_he
+  - type: MortarShell
+  - type: Explosive
+    explosionType: Default
+    maxIntensity: 10 # these values need tuning to im sure
+    intensitySlope: 3
+    totalIntensity: 120
+    canCreateVacuum: false
+    deleteAfterExplosion: true

+ 69 - 0
Resources/Prototypes/Civ14/Entities/Objects/Explosives/Mortars/mortars.yml

@@ -0,0 +1,69 @@
+- type: entity
+  parent: BaseItem
+  id: MortarKit
+  name: portable mortar
+  description: A support weapon capable of droping bombs on heads from a distance. This one seems rather modern.
+  components:
+  - type: Sprite
+    sprite: Civ14/Objects/Mortars/mortar.rsi
+    layers:
+    - map: [ "mortar" ]
+      state: mortar_carry
+  - type: Fixtures
+    fixtures:
+      fix1:
+        shape:
+          !type:PhysShapeAabb
+          bounds: "-0.25,-0.25,0.25,0.25"
+        density: 20
+        mask:
+        - ItemMask
+        restitution: 0.3
+        friction: 0.2
+      mortar:
+        shape:
+          !type:PhysShapeAabb
+          bounds: "-0.49,-0.49,0.49,0.49"
+        density: 20
+        layer:
+        - Impassable
+        mask:
+        - Impassable
+        hard: false
+  - type: CollisionWake
+    enabled: false
+  - type: Anchorable
+    flags:
+    - Unanchorable
+  - type: Item
+    size: Huge
+  - type: Mortar
+  - type: Damageable
+    damageContainer: Inorganic
+  - type: UserInterface
+    interfaces:
+      enum.MortarUiKey.Key:
+        type: MortarBui
+  - type: ActivatableUI
+    key: enum.MortarUiKey.Key
+  - type: Appearance
+  - type: GenericVisualizer
+    visuals:
+      enum.MortarVisualLayers.State:
+        mortar:
+          Item: { state: mortar_carry }
+          Deployed: { state: mortar_deploy }
+  - type: MeleeSound
+    soundGroups:
+      Brute:
+        path:
+          "/Audio/Effects/metal_break1.ogg"
+
+- type: entity
+  parent: MortarKit
+  id: MortarOld
+  name: old portable mortar
+  description: A support weapon capable of droping bombs on heads from a distance.
+  components:
+  - type: Sprite
+    sprite: Civ14/Objects/Mortars/mortar_old.rsi

+ 43 - 0
Resources/Prototypes/Civ14/Entities/Objects/Tools/navigation.yml

@@ -0,0 +1,43 @@
+- type: entity
+  name: compass
+  parent: BaseItem
+  id: Compass
+  description: A device to help navigate.
+  components:
+  - type: Sprite
+    sprite: Civ14/Objects/items.rsi
+    state: compass
+  - type: Item
+  - type: HandheldGPS
+  - type: Tag
+    tags:
+    - GPS
+
+- type: entity
+  name: map and compass
+  parent: Compass
+  id: CompassMap
+  description: A set of navigation tools.
+  components:
+  - type: Sprite
+    sprite: Civ14/Objects/items.rsi
+    state: compass_map
+
+- type: entity
+  name : modern map and compass
+  parent: CompassMap
+  id: ModernCompassMap
+  components:
+  - type: Sprite
+    sprite: Civ14/Objects/items.rsi
+    state: compass_map_modern
+
+- type: entity
+  name : digital map
+  parent: CompassMap
+  id: DigitalMap
+  description: A tablet with a map and navigation tools on it.
+  components:
+  - type: Sprite
+    sprite: Civ14/Objects/items.rsi
+    state: compass_modern

BIN
Resources/Textures/Civ14/Objects/Mortars/mortar.rsi/inhand-left.png


BIN
Resources/Textures/Civ14/Objects/Mortars/mortar.rsi/inhand-right.png


+ 48 - 0
Resources/Textures/Civ14/Objects/Mortars/mortar.rsi/meta.json

@@ -0,0 +1,48 @@
+{
+  "version": 1,
+  "license": "CC-BY-SA-3.0",
+  "copyright": "Taken from civ13 and edited by dallaszzz on github",
+  "size": {
+    "x": 32,
+    "y": 32
+  },
+  "states": [
+    {
+      "name": "mortar_deploy",
+      "directions": 4
+    },
+    {
+      "name": "mortar_fire",
+      "directions": 4,
+      "delays": [
+        [
+          0.1,
+          0.2
+        ],
+        [
+          0.1,
+          0.2
+        ],
+        [
+          0.1,
+          0.2
+        ],
+        [
+          0.1,
+          0.2
+        ]
+      ]
+    },
+    {
+      "name": "mortar_carry"
+    },
+    {
+      "name": "inhand-left",
+      "directions": 4
+    },
+    {
+      "name": "inhand-right",
+      "directions": 4
+    }
+  ]
+}

BIN
Resources/Textures/Civ14/Objects/Mortars/mortar.rsi/mortar_carry.png


BIN
Resources/Textures/Civ14/Objects/Mortars/mortar.rsi/mortar_deploy.png


BIN
Resources/Textures/Civ14/Objects/Mortars/mortar.rsi/mortar_fire.png


BIN
Resources/Textures/Civ14/Objects/Mortars/mortar_old.rsi/inhand-left.png


BIN
Resources/Textures/Civ14/Objects/Mortars/mortar_old.rsi/inhand-right.png


+ 48 - 0
Resources/Textures/Civ14/Objects/Mortars/mortar_old.rsi/meta.json

@@ -0,0 +1,48 @@
+{
+  "version": 1,
+  "license": "CC-BY-SA-3.0",
+  "copyright": "Taken from civ13 and edited by dallaszzz on github",
+  "size": {
+    "x": 32,
+    "y": 32
+  },
+  "states": [
+    {
+      "name": "mortar_deploy",
+      "directions": 4
+    },
+    {
+      "name": "mortar_fire",
+      "directions": 4,
+      "delays": [
+        [
+          0.1,
+          0.2
+        ],
+        [
+          0.1,
+          0.2
+        ],
+        [
+          0.1,
+          0.2
+        ],
+        [
+          0.1,
+          0.2
+        ]
+      ]
+    },
+    {
+      "name": "mortar_carry"
+    },
+    {
+      "name": "inhand-left",
+      "directions": 4
+    },
+    {
+      "name": "inhand-right",
+      "directions": 4
+    }
+  ]
+}

BIN
Resources/Textures/Civ14/Objects/Mortars/mortar_old.rsi/mortar_carry.png


BIN
Resources/Textures/Civ14/Objects/Mortars/mortar_old.rsi/mortar_deploy.png


BIN
Resources/Textures/Civ14/Objects/Mortars/mortar_old.rsi/mortar_fire.png


+ 14 - 0
Resources/Textures/Civ14/Objects/Mortars/shells.rsi/meta.json

@@ -0,0 +1,14 @@
+{
+  "version": 1,
+  "license": "CC-BY-SA-3.0",
+  "copyright": "Sprites taken from civ13",
+  "size": {
+    "x": 32,
+    "y": 32
+  },
+  "states": [
+    {
+      "name": "mortar_ammo_he"
+    }
+  ]
+}

BIN
Resources/Textures/Civ14/Objects/Mortars/shells.rsi/mortar_ammo (1).png


BIN
Resources/Textures/Civ14/Objects/Mortars/shells.rsi/mortar_ammo (2).png


BIN
Resources/Textures/Civ14/Objects/Mortars/shells.rsi/mortar_ammo (4).png


BIN
Resources/Textures/Civ14/Objects/Mortars/shells.rsi/mortar_ammo (5).png


BIN
Resources/Textures/Civ14/Objects/Mortars/shells.rsi/mortar_ammo_he.png


BIN
Resources/Textures/Civ14/Objects/Mortars/type89.rsi/type89.png