diff --git a/Content.Client/_VXS14/Mortar/BasicCalculatorEui.cs b/Content.Client/_VXS14/Mortar/BasicCalculatorEui.cs
new file mode 100644
index 00000000000..f5cee795414
--- /dev/null
+++ b/Content.Client/_VXS14/Mortar/BasicCalculatorEui.cs
@@ -0,0 +1,35 @@
+using Content.Client.Eui;
+using Content.Shared.Eui;
+using JetBrains.Annotations;
+
+namespace Content.Client._VXS14.Mortar;
+
+[UsedImplicitly]
+public sealed class BasicCalculatorEui : BaseEui
+{
+ private readonly BasicCalculatorWindow _window;
+
+ public BasicCalculatorEui()
+ {
+ _window = new BasicCalculatorWindow();
+ _window.OnClose += SendClosedMessage;
+ }
+
+ public override void Opened()
+ {
+ base.Opened();
+ _window.OpenCentered();
+ }
+
+ public override void Closed()
+ {
+ base.Closed();
+ _window.OnClose -= SendClosedMessage;
+ _window.Close();
+ }
+
+ private void SendClosedMessage()
+ {
+ SendMessage(new CloseEuiMessage());
+ }
+}
diff --git a/Content.Client/_VXS14/Mortar/BasicCalculatorWindow.xaml b/Content.Client/_VXS14/Mortar/BasicCalculatorWindow.xaml
new file mode 100644
index 00000000000..1d7b9363384
--- /dev/null
+++ b/Content.Client/_VXS14/Mortar/BasicCalculatorWindow.xaml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_VXS14/Mortar/BasicCalculatorWindow.xaml.cs b/Content.Client/_VXS14/Mortar/BasicCalculatorWindow.xaml.cs
new file mode 100644
index 00000000000..5c16839a0c2
--- /dev/null
+++ b/Content.Client/_VXS14/Mortar/BasicCalculatorWindow.xaml.cs
@@ -0,0 +1,196 @@
+using System.Globalization;
+using JetBrains.Annotations;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+using static Robust.Client.UserInterface.Controls.BaseButton;
+
+namespace Content.Client._VXS14.Mortar;
+
+[GenerateTypedNameReferences]
+[UsedImplicitly]
+public sealed partial class BasicCalculatorWindow : DefaultWindow
+{
+ private double? _accumulator;
+ private char? _pendingOp;
+ private bool _replaceDisplay = true;
+
+ public BasicCalculatorWindow()
+ {
+ RobustXamlLoader.Load(this);
+ Title = "Calculator";
+
+ WireDigit(Btn0, "0");
+ WireDigit(Btn1, "1");
+ WireDigit(Btn2, "2");
+ WireDigit(Btn3, "3");
+ WireDigit(Btn4, "4");
+ WireDigit(Btn5, "5");
+ WireDigit(Btn6, "6");
+ WireDigit(Btn7, "7");
+ WireDigit(Btn8, "8");
+ WireDigit(Btn9, "9");
+
+ BtnDot.OnPressed += _ => AddDot();
+ BtnBack.OnPressed += _ => Backspace();
+ BtnCe.OnPressed += _ => ClearEntry();
+ BtnC.OnPressed += _ => ClearAll();
+ BtnSign.OnPressed += _ => ToggleSign();
+
+ BtnAdd.OnPressed += _ => SetOperation('+');
+ BtnSub.OnPressed += _ => SetOperation('-');
+ BtnMul.OnPressed += _ => SetOperation('*');
+ BtnDiv.OnPressed += _ => SetOperation('/');
+ BtnEq.OnPressed += _ => EvaluatePending();
+
+ UpdateDisplay("0");
+ }
+
+ private void WireDigit(Button button, string value)
+ {
+ button.OnPressed += _ => AppendDigit(value);
+ }
+
+ private void AppendDigit(string digit)
+ {
+ string text = _replaceDisplay ? string.Empty : DisplayLabel?.Text ?? string.Empty;
+
+ if (text == "0")
+ text = string.Empty;
+
+ text += digit;
+ UpdateDisplay(text.Length == 0 ? "0" : text);
+ _replaceDisplay = false;
+ }
+
+ private void AddDot()
+ {
+ string text = _replaceDisplay ? "0" : DisplayLabel?.Text ?? "0";
+
+ if (!text.Contains('.'))
+ text += ".";
+
+ UpdateDisplay(text);
+ _replaceDisplay = false;
+ }
+
+ private void Backspace()
+ {
+ if (_replaceDisplay)
+ return;
+
+ string text = DisplayLabel?.Text ?? string.Empty;
+ if (text.Length <= 1 || (text.Length == 2 && text.StartsWith('-')))
+ {
+ UpdateDisplay("0");
+ _replaceDisplay = true;
+ return;
+ }
+
+ UpdateDisplay(text[..^1]);
+ }
+
+ private void ClearEntry()
+ {
+ UpdateDisplay("0");
+ _replaceDisplay = true;
+ }
+
+ private void ClearAll()
+ {
+ _accumulator = null;
+ _pendingOp = null;
+ if (ExpressionLabel != null)
+ ExpressionLabel.Text = string.Empty;
+ UpdateDisplay("0");
+ _replaceDisplay = true;
+ }
+
+ private void ToggleSign()
+ {
+ string display = DisplayLabel?.Text ?? "0";
+
+ if (display == "0")
+ return;
+
+ if (display.StartsWith('-'))
+ UpdateDisplay(display[1..]);
+ else
+ UpdateDisplay($"-{display}");
+ }
+
+ private void SetOperation(char operation)
+ {
+ if (!_replaceDisplay)
+ CommitDisplay();
+
+ _pendingOp = operation;
+ if (ExpressionLabel != null)
+ {
+ ExpressionLabel.Text = _accumulator.HasValue
+ ? $"{FormatNumber(_accumulator.Value)} {operation}"
+ : string.Empty;
+ }
+ _replaceDisplay = true;
+ }
+
+ private void EvaluatePending()
+ {
+ if (!_pendingOp.HasValue)
+ return;
+
+ if (!_replaceDisplay)
+ CommitDisplay();
+
+ _pendingOp = null;
+ if (ExpressionLabel != null)
+ ExpressionLabel.Text = string.Empty;
+ _replaceDisplay = true;
+ }
+
+ private void CommitDisplay()
+ {
+ string display = DisplayLabel?.Text ?? "0";
+ if (!double.TryParse(display, NumberStyles.Float, CultureInfo.InvariantCulture, out var current))
+ return;
+
+ if (!_accumulator.HasValue)
+ {
+ _accumulator = current;
+ UpdateDisplay(FormatNumber(_accumulator.Value));
+ return;
+ }
+
+ _accumulator = _pendingOp switch
+ {
+ '+' => _accumulator.Value + current,
+ '-' => _accumulator.Value - current,
+ '*' => _accumulator.Value * current,
+ '/' => Math.Abs(current) < 0.0000001 ? double.NaN : _accumulator.Value / current,
+ _ => current,
+ };
+
+ UpdateDisplay(double.IsNaN(_accumulator.Value) ? "Error" : FormatNumber(_accumulator.Value));
+
+ if (double.IsNaN(_accumulator.Value))
+ {
+ _accumulator = null;
+ _pendingOp = null;
+ if (ExpressionLabel != null)
+ ExpressionLabel.Text = string.Empty;
+ _replaceDisplay = true;
+ }
+ }
+
+ private static string FormatNumber(double value)
+ {
+ return value.ToString("0.########", CultureInfo.InvariantCulture);
+ }
+
+ private void UpdateDisplay(string text)
+ {
+ if (DisplayLabel != null)
+ DisplayLabel.Text = text;
+ }
+}
diff --git a/Content.Server/_VXS14/Mortar/System/BasicCalculatorEui.cs b/Content.Server/_VXS14/Mortar/System/BasicCalculatorEui.cs
new file mode 100644
index 00000000000..e13e06f3038
--- /dev/null
+++ b/Content.Server/_VXS14/Mortar/System/BasicCalculatorEui.cs
@@ -0,0 +1,15 @@
+using Content.Server.EUI;
+using JetBrains.Annotations;
+
+namespace Content.Server._VXS14.Mortar;
+
+[UsedImplicitly]
+public sealed class BasicCalculatorEui : BaseEui
+{
+ private readonly EntityUid _calculator;
+
+ public BasicCalculatorEui(EntityUid calculator)
+ {
+ _calculator = calculator;
+ }
+}
diff --git a/Content.Server/_VXS14/Mortar/System/BasicCalculatorSystem.cs b/Content.Server/_VXS14/Mortar/System/BasicCalculatorSystem.cs
new file mode 100644
index 00000000000..fdada9d0bc9
--- /dev/null
+++ b/Content.Server/_VXS14/Mortar/System/BasicCalculatorSystem.cs
@@ -0,0 +1,51 @@
+using Content.Server.EUI;
+using Content.Shared.Examine;
+using Content.Shared.Interaction.Events;
+using Content.Shared._VXS14.Mortar;
+using Content.Shared.Verbs;
+using Robust.Server.Player;
+
+namespace Content.Server._VXS14.Mortar;
+
+public sealed class BasicCalculatorSystem : EntitySystem
+{
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnUseInHand);
+ SubscribeLocalEvent>(OnGetExamineVerb);
+ }
+
+ private void OnUseInHand(EntityUid uid, BasicCalculatorComponent component, UseInHandEvent args)
+ {
+ if (args.Handled)
+ return;
+
+ OpenCalculator(uid, args.User);
+ args.Handled = true;
+ }
+
+ private void OnGetExamineVerb(EntityUid uid, BasicCalculatorComponent component, GetVerbsEvent args)
+ {
+ if (!args.CanInteract || !args.CanAccess)
+ return;
+
+ args.Verbs.Add(new ExamineVerb
+ {
+ Text = "Open calculator",
+ Act = () => OpenCalculator(uid, args.User),
+ });
+ }
+
+ private void OpenCalculator(EntityUid calculatorUid, EntityUid user)
+ {
+ if (!_playerManager.TryGetSessionByEntity(user, out var session))
+ return;
+
+ var eui = IoCManager.Resolve();
+ eui.OpenEui(new BasicCalculatorEui(calculatorUid), session);
+ }
+}
diff --git a/Content.Shared/_VXS14/Mortar/Component/BasicCalculatorComponent.cs b/Content.Shared/_VXS14/Mortar/Component/BasicCalculatorComponent.cs
new file mode 100644
index 00000000000..712ffe58451
--- /dev/null
+++ b/Content.Shared/_VXS14/Mortar/Component/BasicCalculatorComponent.cs
@@ -0,0 +1,8 @@
+using Robust.Shared.GameObjects;
+
+namespace Content.Shared._VXS14.Mortar;
+
+[RegisterComponent]
+public sealed partial class BasicCalculatorComponent : Component
+{
+}
diff --git a/Resources/Prototypes/_Battlefield14/Entities/Objects/Devices/mortar_calculator.yml b/Resources/Prototypes/_Battlefield14/Entities/Objects/Devices/mortar_calculator.yml
new file mode 100644
index 00000000000..a2a0cfaf5d4
--- /dev/null
+++ b/Resources/Prototypes/_Battlefield14/Entities/Objects/Devices/mortar_calculator.yml
@@ -0,0 +1,25 @@
+- type: entity
+ id: MortarCalculatorGreen
+ parent: BaseItem
+ name: calculator
+ description: A field calculator for quick math.
+ components:
+ - type: Sprite
+ sprite: _Battlefield14/Objects/Devices/calc.rsi
+ state: green
+ - type: Item
+ size: Small
+ - type: BasicCalculator
+
+- type: entity
+ id: MortarBallisticCalculator
+ parent: BaseItem
+ name: mortar ballistic calculator
+ description: A field calculator for mortar fire correction.
+ components:
+ - type: Sprite
+ sprite: _Battlefield14/Objects/Devices/calc.rsi
+ state: red
+ - type: Item
+ size: Small
+ - type: BasicCalculator
diff --git a/Resources/Textures/_Battlefield14/Objects/Devices/calc.rsi/green.png b/Resources/Textures/_Battlefield14/Objects/Devices/calc.rsi/green.png
new file mode 100644
index 00000000000..7a6ae280ffc
Binary files /dev/null and b/Resources/Textures/_Battlefield14/Objects/Devices/calc.rsi/green.png differ
diff --git a/Resources/Textures/_Battlefield14/Objects/Devices/calc.rsi/meta.json b/Resources/Textures/_Battlefield14/Objects/Devices/calc.rsi/meta.json
new file mode 100644
index 00000000000..fe3c71e95b0
--- /dev/null
+++ b/Resources/Textures/_Battlefield14/Objects/Devices/calc.rsi/meta.json
@@ -0,0 +1,17 @@
+{
+ "version": 1,
+ "license": "CC-BY-SA-4.0",
+ "copyright": "Made by huntdownthefreemanfan21 for Battlefield 14 project.",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "red"
+ },
+ {
+ "name": "green"
+ }
+ ]
+}
diff --git a/Resources/Textures/_Battlefield14/Objects/Devices/calc.rsi/red.png b/Resources/Textures/_Battlefield14/Objects/Devices/calc.rsi/red.png
new file mode 100644
index 00000000000..402aee5e54b
Binary files /dev/null and b/Resources/Textures/_Battlefield14/Objects/Devices/calc.rsi/red.png differ