From 0305b95db414d269756c724611ab8a43574d5a2e Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Tue, 2 Dec 2025 14:21:29 -0600
Subject: [PATCH 01/25] Moved Warper and Shipyard Localization
---
Resources/Locale/en-US/_Null/{ => machines}/warper.ftl | 0
Resources/Locale/en-US/_Null/{ => purchasable_grids}/shipyard.ftl | 0
2 files changed, 0 insertions(+), 0 deletions(-)
rename Resources/Locale/en-US/_Null/{ => machines}/warper.ftl (100%)
rename Resources/Locale/en-US/_Null/{ => purchasable_grids}/shipyard.ftl (100%)
diff --git a/Resources/Locale/en-US/_Null/warper.ftl b/Resources/Locale/en-US/_Null/machines/warper.ftl
similarity index 100%
rename from Resources/Locale/en-US/_Null/warper.ftl
rename to Resources/Locale/en-US/_Null/machines/warper.ftl
diff --git a/Resources/Locale/en-US/_Null/shipyard.ftl b/Resources/Locale/en-US/_Null/purchasable_grids/shipyard.ftl
similarity index 100%
rename from Resources/Locale/en-US/_Null/shipyard.ftl
rename to Resources/Locale/en-US/_Null/purchasable_grids/shipyard.ftl
From 3f4c7ee7b8460353b911bc69f2a16818146963a6 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Tue, 2 Dec 2025 21:38:24 -0600
Subject: [PATCH 02/25] Updated ShipyardDirectionSystem.cs
---
.../_Mono/Shipyard/ShipyardDirectionSystem.cs | 71 ++++++++++++++++---
1 file changed, 60 insertions(+), 11 deletions(-)
diff --git a/Content.Server/_Mono/Shipyard/ShipyardDirectionSystem.cs b/Content.Server/_Mono/Shipyard/ShipyardDirectionSystem.cs
index 73a8d74927d..38106e5bf63 100644
--- a/Content.Server/_Mono/Shipyard/ShipyardDirectionSystem.cs
+++ b/Content.Server/_Mono/Shipyard/ShipyardDirectionSystem.cs
@@ -2,6 +2,7 @@
using Content.Server.Chat.Managers;
using Content.Shared.Chat;
using Content.Shared.Localizations;
+using Robust.Server.GameObjects;
using Robust.Server.Player;
namespace Content.Server._Mono.Shipyard;
@@ -13,23 +14,35 @@ public sealed class ShipyardDirectionSystem : EntitySystem
{
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly TransformSystem _transform = default!;
///
- /// Sends a message to the player indicating the compass direction of their newly purchased ship
+ /// Made for Null Sector by LZ22. I left this in here because it felt fair, and appropriate. -LZ22
+ /// Sends a message to the player indicating the literal coordinates of their newly purchased hull.
///
- public void SendShipDirectionMessage(EntityUid player, EntityUid ship)
+ ///
+ ///
+ public void SendShipLocationMessage(EntityUid player, EntityUid hull)
{
- if (!TryComp(player, out var playerTransform) ||
- !TryComp(ship, out var shipTransform))
+ // Try to get player's and ship's locations.
+ if (!TryGetPositions(player, hull, out _, out var shipPos))
return;
- // Make sure both entities are on the same map
- if (playerTransform.MapID != shipTransform.MapID)
- return;
+ // Send message to player
+ var message = Loc.GetString(
+ "nullith-location-message", // Null Sector - For use with the Nullith. Localized vaguely to accomodate ships, if needed.
+ ("location", shipPos));
- // Get positions of both entities
- var playerPos = Transform(player).WorldPosition;
- var shipPos = Transform(ship).WorldPosition;
+ SendMessageToPlayer(player, message);
+ }
+
+ ///
+ /// Sends a message to the player indicating the compass direction of their newly purchased ship
+ ///
+ public void SendShipDirectionMessage(EntityUid player, EntityUid ship)
+ {
+ if (!TryGetPositions(player, ship, out var playerPos, out var shipPos))
+ return;
// Calculate direction vector
var direction = shipPos - playerPos;
@@ -47,12 +60,48 @@ public void SendShipDirectionMessage(EntityUid player, EntityUid ship)
("direction", directionName),
("distance", distance));
+ SendMessageToPlayer(player, message);
+ }
+
+ ///
+ /// [Null Sector] Sends a provided message to player.
+ ///
+ private void SendMessageToPlayer(EntityUid player, string message)
+ {
if (_playerManager.TryGetSessionByEntity(player, out var session))
{
- _chatManager.ChatMessageToOne(ChatChannel.Server, message, message, EntityUid.Invalid, false, session.Channel);
+ _chatManager.ChatMessageToOne(ChatChannel.Server,
+ message,
+ message,
+ EntityUid.Invalid,
+ false,
+ session.Channel);
}
}
+ ///
+ /// [Null Sector] Attempts to get the Player and Ship positions from provided Player and Ship Entities.
+ ///
+ /// Two Vector2 output-variables: player and ship positions.
+ private bool TryGetPositions(EntityUid player, EntityUid ship, out Vector2 playerPos, out Vector2 shipPos)
+ {
+ playerPos = Vector2.NaN;
+ shipPos = Vector2.NaN;
+ // Try to get player's and ship's transform components.
+ if (!EntityManager.TryGetComponent(player, out var playerTransform) ||
+ !EntityManager.TryGetComponent(ship, out var shipTransform))
+ return false;
+
+ // Make sure both entities are on the same map
+ if (playerTransform.MapID != shipTransform.MapID)
+ return false;
+
+ // Get positions of both entities
+ playerPos = _transform.GetWorldPosition(player);
+ shipPos = _transform.GetWorldPosition(ship);
+ return true;
+ }
+
//lua start
/////
///// Converts a direction vector to a compass direction
From 43607e2a7ce7e4cae223a35513d17473a4ed4adb Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Tue, 2 Dec 2025 22:39:16 -0600
Subject: [PATCH 03/25] Cleaned Frontier Shipyard Code w. Respect to
Post-Cleaning Monolith Ship Location Announcer Code
---
.../BUI/ShipyardConsoleBoundUserInterface.cs | 21 +-
.../Systems/ShipyardSystem.Consoles.cs | 367 ++++++++++++------
.../_NF/Shipyard/Systems/ShipyardSystem.cs | 2 +-
.../SharedShipyardConsoleComponent.cs | 13 +-
.../Components/ShipyardListingComponent.cs | 2 +-
.../Shipyard/Prototypes/VesselPrototype.cs | 6 +-
.../_NF/Shipyard/SharedShipyardSystem.cs | 8 +-
7 files changed, 280 insertions(+), 139 deletions(-)
diff --git a/Content.Client/_NF/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs b/Content.Client/_NF/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs
index f4c251223bd..202b162b600 100644
--- a/Content.Client/_NF/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs
+++ b/Content.Client/_NF/Shipyard/BUI/ShipyardConsoleBoundUserInterface.cs
@@ -1,15 +1,16 @@
using Content.Client._NF.Shipyard.UI;
-using Content.Shared.Containers.ItemSlots;
using Content.Shared._NF.Shipyard.BUI;
using Content.Shared._NF.Shipyard.Events;
+using Content.Shared.Containers.ItemSlots;
using static Robust.Client.UserInterface.Controls.BaseButton;
+#pragma warning disable CS0618 // Type or member is obsolete
namespace Content.Client._NF.Shipyard.BUI;
public sealed class ShipyardConsoleBoundUserInterface : BoundUserInterface
{
private ShipyardConsoleMenu? _menu;
- private ShipyardRulesPopup? _rulesWindow;
+ //private ShipyardRulesPopup? _rulesWindow;
public int Balance { get; private set; }
public int? ShipSellValue { get; private set; }
@@ -40,7 +41,10 @@ protected override void Open()
_menu.TargetIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent("ShipyardConsole-targetId"));
}
- private void Populate(List availablePrototypes, List unavailablePrototypes, bool freeListings, bool validId)
+ private void Populate(List availablePrototypes,
+ List unavailablePrototypes,
+ bool freeListings,
+ bool validId)
{
if (_menu == null)
return;
@@ -60,8 +64,11 @@ protected override void UpdateState(BoundUserInterfaceState state)
Balance = cState.Balance;
ShipSellValue = cState.ShipSellValue;
- var castState = (ShipyardConsoleInterfaceState) state;
- Populate(castState.ShipyardPrototypes.available, castState.ShipyardPrototypes.unavailable, castState.FreeListings, castState.IsTargetIdPresent);
+ var castState = (ShipyardConsoleInterfaceState)state;
+ Populate(castState.ShipyardPrototypes.available,
+ castState.ShipyardPrototypes.unavailable,
+ castState.FreeListings,
+ castState.IsTargetIdPresent);
_menu?.UpdateState(castState);
}
@@ -69,7 +76,8 @@ protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
- if (!disposing) return;
+ if (!disposing)
+ return;
_menu?.Dispose();
}
@@ -84,6 +92,7 @@ private void ApproveOrder(ButtonEventArgs args)
var vesselId = row.Vessel.ID;
SendMessage(new ShipyardConsolePurchaseMessage(vesselId));
}
+
private void SellShip(ButtonEventArgs args)
{
//reserved for a sanity check, but im not sure what since we check all the important stuffs on server already
diff --git a/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.Consoles.cs b/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.Consoles.cs
index 0e09c30e298..58143eed118 100644
--- a/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.Consoles.cs
+++ b/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.Consoles.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text.RegularExpressions;
using Content.Server._Mono.Shipyard;
@@ -34,7 +35,6 @@
using Content.Shared.Forensics.Components;
using Content.Shared.Ghost;
using Content.Shared.Mobs.Components;
-using Content.Shared.Mobs.Systems;
using Content.Shared.Preferences;
using Content.Shared.Radio;
using Content.Shared.Shuttles.Components;
@@ -48,7 +48,8 @@
namespace Content.Server._NF.Shipyard.Systems;
-public sealed partial class ShipyardSystem : SharedShipyardSystem
+[SuppressMessage("ReSharper", "InconsistentNaming")]
+public sealed partial class ShipyardSystem
{
[Dependency] private readonly AccessSystem _accessSystem = default!;
[Dependency] private readonly AccessReaderSystem _access = default!;
@@ -60,12 +61,11 @@ public sealed partial class ShipyardSystem : SharedShipyardSystem
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly BankSystem _bank = default!;
[Dependency] private readonly IdCardSystem _idSystem = default!;
- [Dependency] private readonly MobStateSystem _mobState = default!;
[Dependency] private readonly StationRecordsSystem _records = default!;
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly IAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
[Dependency] private readonly MindSystem _mind = default!;
- [Dependency] private readonly UserInterfaceSystem _userInterface = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
[Dependency] private readonly ShuttleRecordsSystem _shuttleRecordsSystem = default!;
[Dependency] private readonly ShuttleConsoleLockSystem _shuttleConsoleLock = default!;
@@ -74,10 +74,11 @@ public sealed partial class ShipyardSystem : SharedShipyardSystem
public void InitializeConsole()
{
-
}
- private void OnPurchaseMessage(EntityUid shipyardConsoleUid, ShipyardConsoleComponent component, ShipyardConsolePurchaseMessage args)
+ private void OnPurchaseMessage(EntityUid shipyardConsoleUid,
+ ShipyardConsoleComponent component,
+ ShipyardConsolePurchaseMessage args)
{
if (args.Actor is not { Valid: true } player)
return;
@@ -105,7 +106,8 @@ private void OnPurchaseMessage(EntityUid shipyardConsoleUid, ShipyardConsoleComp
return;
}
- if (TryComp(shipyardConsoleUid, out var accessReaderComponent) && !_access.IsAllowed(player, shipyardConsoleUid, accessReaderComponent))
+ if (TryComp(shipyardConsoleUid, out var accessReaderComponent) &&
+ !_access.IsAllowed(player, shipyardConsoleUid, accessReaderComponent))
{
ConsolePopup(player, Loc.GetString("comms-console-permission-denied"));
PlayDenySound(player, shipyardConsoleUid, component);
@@ -122,7 +124,9 @@ private void OnPurchaseMessage(EntityUid shipyardConsoleUid, ShipyardConsoleComp
if (!GetAvailableShuttles(shipyardConsoleUid, targetId: targetId).available.Contains(vessel.ID))
{
PlayDenySound(player, shipyardConsoleUid, component);
- _adminLogger.Add(LogType.Action, LogImpact.Medium, $"{ToPrettyString(player):player} tried to purchase a vessel that was never available.");
+ _adminLogger.Add(LogType.Action,
+ LogImpact.Medium,
+ $"{ToPrettyString(player):player} tried to purchase a vessel that was never available.");
return;
}
@@ -144,45 +148,17 @@ private void OnPurchaseMessage(EntityUid shipyardConsoleUid, ShipyardConsoleComp
return;
}
- // Keep track of whether or not a voucher was used.
- // TODO: voucher purchase should be done in a separate function.
+ // Keep track of whether a voucher was used.
var voucherUsed = false;
if (voucher is not null)
{
- if (voucher!.RedemptionsLeft <= 0)
- {
- ConsolePopup(player, Loc.GetString("shipyard-console-no-voucher-redemptions"));
- PlayDenySound(player, shipyardConsoleUid, component);
- if (voucher!.DestroyOnEmpty)
- {
- QueueDel(targetId);
- }
+ if (TryPurchaseWithVoucher(shipyardConsoleUid, component, args, voucher, player, targetId, ref voucherUsed))
return;
- }
- if (voucher!.ConsoleType != (ShipyardConsoleUiKey)args.UiKey)
- {
- ConsolePopup(player, Loc.GetString("shipyard-console-invalid-voucher-type"));
- PlayDenySound(player, shipyardConsoleUid, component);
- return;
- }
- voucher.RedemptionsLeft--;
- voucherUsed = true;
}
- else
+ else // If voucher is null, then clearly they are buying it normally. Check if they're poor, screw 'em!
{
- if (bank.Balance <= vessel.Price)
- {
- ConsolePopup(player, Loc.GetString("cargo-console-insufficient-funds", ("cost", vessel.Price)));
- PlayDenySound(player, shipyardConsoleUid, component);
- return;
- }
-
- if (!_bank.TryBankWithdraw(player, vessel.Price))
- {
- ConsolePopup(player, Loc.GetString("cargo-console-insufficient-funds", ("cost", vessel.Price)));
- PlayDenySound(player, shipyardConsoleUid, component);
+ if (UserIsPoor(shipyardConsoleUid, component, bank, vessel, player))
return;
- }
}
@@ -191,8 +167,9 @@ private void OnPurchaseMessage(EntityUid shipyardConsoleUid, ShipyardConsoleComp
PlayDenySound(player, shipyardConsoleUid, component);
return;
}
+
var shuttleUid = shuttleUidOut.Value;
- if (!_entityManager.TryGetComponent(shuttleUid, out var shuttle))
+ if (!_entityManager.TryGetComponent(shuttleUid, out _))
{
PlayDenySound(player, shipyardConsoleUid, component);
return;
@@ -211,10 +188,7 @@ private void OnPurchaseMessage(EntityUid shipyardConsoleUid, ShipyardConsoleComp
// setting up any stations if we have a matching game map prototype to allow late joins directly onto the vessel
if (_prototypeManager.TryIndex(vessel.ID, out var stationProto))
{
- List gridUids = new()
- {
- shuttleUid
- };
+ List gridUids = [shuttleUid];
shuttleStation = _station.InitializeNewStation(stationProto.Stations[vessel.ID], gridUids);
name = Name(shuttleStation.Value);
}
@@ -247,7 +221,10 @@ private void OnPurchaseMessage(EntityUid shipyardConsoleUid, ShipyardConsoleComp
_shuttleConsoleLock.SetShuttleId(consoleUid, shuttleUid.ToString(), lockComp);
// Log for debugging
- Log.Debug("Locked shuttle console {0} to shuttle {1} for deed holder {2}", consoleUid, shuttleUid, targetId);
+ Log.Debug("Locked shuttle console {0} to shuttle {1} for deed holder {2}",
+ consoleUid,
+ shuttleUid,
+ targetId);
}
// Null Sector: Disabled ShipAutoDelete
@@ -274,11 +251,11 @@ private void OnPurchaseMessage(EntityUid shipyardConsoleUid, ShipyardConsoleComp
var stationList = EntityQueryEnumerator();
if (TryComp(targetId, out var keyStorage)
- && shuttleStation != null
- && keyStorage.Key != null)
+ && shuttleStation != null
+ && keyStorage.Key != null)
{
bool recSuccess = false;
- while (stationList.MoveNext(out var stationUid, out var stationRecComp))
+ while (stationList.MoveNext(out _, out _))
{
if (!_records.TryGetRecord(keyStorage.Key.Value, out var record))
continue;
@@ -290,15 +267,27 @@ private void OnPurchaseMessage(EntityUid shipyardConsoleUid, ShipyardConsoleComp
}
if (!recSuccess &&
- _mind.TryGetMind(player, out var mindUid, out var mindComp)
- && _prefManager.GetPreferences(_mind.GetSession(mindComp)!.UserId).SelectedCharacter is HumanoidCharacterProfile profile)
+ _mind.TryGetMind(player, out _, out var mindComp)
+ && _prefManager.GetPreferences(_mind.GetSession(mindComp)!.UserId).SelectedCharacter is
+ HumanoidCharacterProfile profile)
{
TryComp(player, out var fingerprintComponent);
TryComp(player, out var dnaComponent);
TryComp(shuttleStation, out var stationRec);
- _records.CreateGeneralRecord(shuttleStation.Value, targetId, profile.Name, profile.Age, profile.Species, profile.Gender, $"Captain", fingerprintComponent!.Fingerprint, dnaComponent!.DNA, profile, stationRec!);
+ _records.CreateGeneralRecord(shuttleStation.Value,
+ targetId,
+ profile.Name,
+ profile.Age,
+ profile.Species,
+ profile.Gender,
+ $"Captain",
+ fingerprintComponent!.Fingerprint,
+ dnaComponent!.DNA,
+ profile,
+ stationRec!);
}
}
+
_records.Synchronize(shuttleStation!.Value);
_records.Synchronize(station);
@@ -312,7 +301,7 @@ private void OnPurchaseMessage(EntityUid shipyardConsoleUid, ShipyardConsoleComp
{
// Get the price of the ship
if (TryComp(targetId, out var deed))
- sellValue = (int)_pricing.AppraiseGrid((EntityUid)deed?.ShuttleUid!, LacksPreserveOnSaleComp);
+ sellValue = (int)_pricing.AppraiseGrid((EntityUid)deed.ShuttleUid!, LacksPreserveOnSaleComp);
// Adjust for taxes
sellValue = CalculateShipResaleValue((shipyardConsoleUid, component), sellValue);
@@ -322,14 +311,23 @@ private void OnPurchaseMessage(EntityUid shipyardConsoleUid, ShipyardConsoleComp
if (component.SecretShipyardChannel is { } secretChannel)
SendPurchaseMessage(shipyardConsoleUid, player, name, secretChannel, secret: true);
- // Mono
- Get().SendShipDirectionMessage(player, shuttleUid);
+ // Mono -> Null Sector [ fixed it for ya' :) ]
+ _entitySystemManager.GetEntitySystem()
+ .SendShipDirectionMessage(player, shuttleUid);
PlayConfirmSound(player, shipyardConsoleUid, component);
if (voucherUsed)
- _adminLogger.Add(LogType.ShipYardUsage, LogImpact.Low, $"{ToPrettyString(player):actor} used {ToPrettyString(targetId)} to purchase shuttle {ToPrettyString(shuttleUid)} with a voucher via {ToPrettyString(shipyardConsoleUid)}");
+ {
+ _adminLogger.Add(LogType.ShipYardUsage,
+ LogImpact.Low,
+ $"{ToPrettyString(player):actor} used {ToPrettyString(targetId)} to purchase shuttle {ToPrettyString(shuttleUid)} with a voucher via {ToPrettyString(shipyardConsoleUid)}");
+ }
else
- _adminLogger.Add(LogType.ShipYardUsage, LogImpact.Low, $"{ToPrettyString(player):actor} used {ToPrettyString(targetId)} to purchase shuttle {ToPrettyString(shuttleUid)} for {vessel.Price} credits via {ToPrettyString(shipyardConsoleUid)}");
+ {
+ _adminLogger.Add(LogType.ShipYardUsage,
+ LogImpact.Low,
+ $"{ToPrettyString(player):actor} used {ToPrettyString(targetId)} to purchase shuttle {ToPrettyString(shuttleUid)} for {vessel.Price} credits via {ToPrettyString(shipyardConsoleUid)}");
+ }
// Adding the record to the shuttle records system makes them eligible to be copied.
// Can be set on the component of the shipyard.
@@ -347,7 +345,69 @@ private void OnPurchaseMessage(EntityUid shipyardConsoleUid, ShipyardConsoleComp
);
}
- RefreshState(shipyardConsoleUid, bank.Balance, true, name, sellValue, targetId, (ShipyardConsoleUiKey)args.UiKey, voucherUsed);
+ RefreshState(shipyardConsoleUid,
+ bank.Balance,
+ true,
+ name,
+ sellValue,
+ targetId,
+ (ShipyardConsoleUiKey)args.UiKey,
+ voucherUsed);
+ }
+
+ private bool UserIsPoor(EntityUid shipyardConsoleUid,
+ ShipyardConsoleComponent component,
+ BankAccountComponent bank,
+ VesselPrototype vessel,
+ EntityUid player)
+ {
+ if (bank.Balance <= vessel.Price)
+ {
+ ConsolePopup(player, Loc.GetString("cargo-console-insufficient-funds", ("cost", vessel.Price)));
+ PlayDenySound(player, shipyardConsoleUid, component);
+ return true;
+ }
+
+ if (!_bank.TryBankWithdraw(player, vessel.Price))
+ {
+ ConsolePopup(player, Loc.GetString("cargo-console-insufficient-funds", ("cost", vessel.Price)));
+ PlayDenySound(player, shipyardConsoleUid, component);
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool TryPurchaseWithVoucher(EntityUid shipyardConsoleUid,
+ ShipyardConsoleComponent component,
+ ShipyardConsolePurchaseMessage args,
+ ShipyardVoucherComponent voucher,
+ EntityUid player,
+ EntityUid targetId,
+ ref bool voucherUsed)
+ {
+ if (voucher.RedemptionsLeft <= 0)
+ {
+ ConsolePopup(player, Loc.GetString("shipyard-console-no-voucher-redemptions"));
+ PlayDenySound(player, shipyardConsoleUid, component);
+ if (voucher.DestroyOnEmpty)
+ {
+ QueueDel(targetId);
+ }
+
+ return true;
+ }
+
+ if (voucher.ConsoleType != (ShipyardConsoleUiKey)args.UiKey)
+ {
+ ConsolePopup(player, Loc.GetString("shipyard-console-invalid-voucher-type"));
+ PlayDenySound(player, shipyardConsoleUid, component);
+ return true;
+ }
+
+ voucher.RedemptionsLeft--;
+ voucherUsed = true;
+ return false;
}
private void TryParseShuttleName(ShuttleDeedComponent deed, string name)
@@ -356,14 +416,14 @@ private void TryParseShuttleName(ShuttleDeedComponent deed, string name)
// This may cause problems but ONLY when renaming a ship. It will still display properly regardless of this.
var nameParts = name.Split(' ');
- var hasSuffix = nameParts.Length > 1 && nameParts.Last().Length < MaxSuffixLength && nameParts.Last().Contains('-');
+ var hasSuffix = nameParts.Length > 1 && nameParts.Last().Length < MaxSuffixLength &&
+ nameParts.Last().Contains('-');
deed.ShuttleNameSuffix = hasSuffix ? nameParts.Last() : null;
deed.ShuttleName = String.Join(" ", nameParts.SkipLast(hasSuffix ? 1 : 0));
}
public void OnSellMessage(EntityUid uid, ShipyardConsoleComponent component, ShipyardConsoleSellMessage args)
{
-
if (args.Actor is not { Valid: true } player)
return;
@@ -371,7 +431,7 @@ public void OnSellMessage(EntityUid uid, ShipyardConsoleComponent component, Shi
var targetId = component.TargetIdSlot.ContainerSlot?.ContainedEntity;
TryComp(targetId, out var idCard);
TryComp(targetId, out var voucher);
- if (targetId is not { Valid: true } || (idCard is null && voucher is null))
+ if (targetId is not { Valid: true } || idCard is null && voucher is null)
{
ConsolePopup(player, Loc.GetString("shipyard-console-no-idcard"));
PlayDenySound(player, uid, component);
@@ -418,7 +478,8 @@ public void OnSellMessage(EntityUid uid, ShipyardConsoleComponent component, Shi
// Check for shipyard blacklisting components
var disableSaleQuery = GetEntityQuery();
var xformQuery = GetEntityQuery();
- var disableSaleMsg = FindDisableShipyardSaleObjects(shuttleUid, (ShipyardConsoleUiKey)args.UiKey, disableSaleQuery, xformQuery);
+ var disableSaleMsg =
+ FindDisableShipyardSaleObjects(shuttleUid, (ShipyardConsoleUiKey)args.UiKey, disableSaleQuery, xformQuery);
if (disableSaleMsg != null)
{
ConsolePopup(player, Loc.GetString(disableSaleMsg));
@@ -435,15 +496,19 @@ public void OnSellMessage(EntityUid uid, ShipyardConsoleComponent component, Shi
ConsolePopup(player, Loc.GetString("shipyard-console-sale-not-docked"));
break;
case ShipyardSaleError.OrganicsAboard:
- ConsolePopup(player, Loc.GetString("shipyard-console-sale-organic-aboard", ("name", saleResult.OrganicName ?? "Somebody")));
+ ConsolePopup(player,
+ Loc.GetString("shipyard-console-sale-organic-aboard",
+ ("name", saleResult.OrganicName ?? "Somebody")));
break;
case ShipyardSaleError.InvalidShip:
ConsolePopup(player, Loc.GetString("shipyard-console-sale-invalid-ship"));
break;
default:
- ConsolePopup(player, Loc.GetString("shipyard-console-sale-unknown-reason", ("reason", saleResult.Error.ToString())));
+ ConsolePopup(player,
+ Loc.GetString("shipyard-console-sale-unknown-reason", ("reason", saleResult.Error.ToString())));
break;
}
+
PlayDenySound(player, uid, component);
return;
}
@@ -462,6 +527,7 @@ public void OnSellMessage(EntityUid uid, ShipyardConsoleComponent component, Shi
_bank.TrySectorDeposit(account, tax, LedgerEntryType.BlackMarketShipyardTax);
bill -= tax;
}
+
bill = int.Max(0, bill);
_bank.TryBankDeposit(player, bill);
@@ -473,17 +539,25 @@ public void OnSellMessage(EntityUid uid, ShipyardConsoleComponent component, Shi
if (component.SecretShipyardChannel is { } secretChannel)
SendSellMessage(uid, deed.ShuttleOwner!, name, secretChannel, player, secret: true);
- EntityUid? refreshId = targetId;
+ var refreshId = targetId;
if (voucherUsed)
- _adminLogger.Add(LogType.ShipYardUsage, LogImpact.Low, $"{ToPrettyString(player):actor} used {ToPrettyString(targetId)} to sell {shuttleName} (purchased with voucher) via {ToPrettyString(uid)}");
+ {
+ _adminLogger.Add(LogType.ShipYardUsage,
+ LogImpact.Low,
+ $"{ToPrettyString(player):actor} used {ToPrettyString(targetId)} to sell {shuttleName} (purchased with voucher) via {ToPrettyString(uid)}");
+ }
else
- _adminLogger.Add(LogType.ShipYardUsage, LogImpact.Low, $"{ToPrettyString(player):actor} used {ToPrettyString(targetId)} to sell {shuttleName} for {bill} credits via {ToPrettyString(uid)}");
+ {
+ _adminLogger.Add(LogType.ShipYardUsage,
+ LogImpact.Low,
+ $"{ToPrettyString(player):actor} used {ToPrettyString(targetId)} to sell {shuttleName} for {bill} credits via {ToPrettyString(uid)}");
+ }
// No uses on the voucher left, destroy it.
if (voucher != null
- && voucher!.RedemptionsLeft <= 0
- && voucher!.DestroyOnEmpty)
+ && voucher.RedemptionsLeft <= 0
+ && voucher.DestroyOnEmpty)
{
QueueDel(targetId);
refreshId = null;
@@ -514,9 +588,9 @@ private void OnConsoleUIOpened(EntityUid uid, ShipyardConsoleComponent component
if (TryComp(targetId, out var deed))
{
- if (Deleted(deed!.ShuttleUid))
+ if (Deleted(deed.ShuttleUid))
{
- RemComp(targetId!.Value);
+ RemComp(targetId.Value);
return;
}
}
@@ -526,12 +600,19 @@ private void OnConsoleUIOpened(EntityUid uid, ShipyardConsoleComponent component
int sellValue = 0;
if (deed?.ShuttleUid != null)
{
- sellValue = (int)_pricing.AppraiseGrid((EntityUid)(deed?.ShuttleUid!), LacksPreserveOnSaleComp);
+ sellValue = (int)_pricing.AppraiseGrid((EntityUid)deed.ShuttleUid!, LacksPreserveOnSaleComp);
sellValue = CalculateShipResaleValue((uid, component), sellValue);
}
var fullName = deed != null ? GetFullName(deed) : null;
- RefreshState(uid, bank.Balance, true, fullName, sellValue, targetId, (ShipyardConsoleUiKey)args.UiKey, voucherUsed);
+ RefreshState(uid,
+ bank.Balance,
+ true,
+ fullName,
+ sellValue,
+ targetId,
+ (ShipyardConsoleUiKey)args.UiKey,
+ voucherUsed);
}
private void ConsolePopup(EntityUid uid, string text)
@@ -545,27 +626,50 @@ private void SendPurchaseMessage(EntityUid uid, EntityUid player, string name, s
if (secret)
{
- _chat.TrySendInGameICMessage(uid, Loc.GetString("shipyard-console-docking-secret"), InGameICChatType.Speak, true);
+ _chat.TrySendInGameICMessage(uid,
+ Loc.GetString("shipyard-console-docking-secret"),
+ InGameICChatType.Speak,
+ true);
}
else
{
- _radio.SendRadioMessage(uid, Loc.GetString("shipyard-console-docking", ("owner", player), ("vessel", name)), channel, uid);
- _chat.TrySendInGameICMessage(uid, Loc.GetString("shipyard-console-docking", ("owner", player!), ("vessel", name)), InGameICChatType.Speak, true);
+ _radio.SendRadioMessage(uid,
+ Loc.GetString("shipyard-console-docking", ("owner", player), ("vessel", name)),
+ channel,
+ uid);
+ _chat.TrySendInGameICMessage(uid,
+ Loc.GetString("shipyard-console-docking", ("owner", player), ("vessel", name)),
+ InGameICChatType.Speak,
+ true);
}
}
- private void SendSellMessage(EntityUid uid, string? player, string name, string shipyardChannel, EntityUid seller, bool secret)
+ private void SendSellMessage(EntityUid uid,
+ string? player,
+ string name,
+ string shipyardChannel,
+ EntityUid seller,
+ bool secret)
{
var channel = _prototypeManager.Index(shipyardChannel);
if (secret)
{
- _chat.TrySendInGameICMessage(uid, Loc.GetString("shipyard-console-leaving-secret"), InGameICChatType.Speak, true);
+ _chat.TrySendInGameICMessage(uid,
+ Loc.GetString("shipyard-console-leaving-secret"),
+ InGameICChatType.Speak,
+ true);
}
else
{
- _radio.SendRadioMessage(uid, Loc.GetString("shipyard-console-leaving", ("owner", player!), ("vessel", name!), ("player", seller)), channel, uid);
- _chat.TrySendInGameICMessage(uid, Loc.GetString("shipyard-console-leaving", ("owner", player!), ("vessel", name!), ("player", seller)), InGameICChatType.Speak, true);
+ _radio.SendRadioMessage(uid,
+ Loc.GetString("shipyard-console-leaving", ("owner", player!), ("vessel", name), ("player", seller)),
+ channel,
+ uid);
+ _chat.TrySendInGameICMessage(uid,
+ Loc.GetString("shipyard-console-leaving", ("owner", player!), ("vessel", name), ("player", seller)),
+ InGameICChatType.Speak,
+ true);
}
}
@@ -605,9 +709,9 @@ private void OnItemSlotChanged(EntityUid uid, ShipyardConsoleComponent component
if (TryComp(targetId, out var deed))
{
- if (Deleted(deed!.ShuttleUid))
+ if (Deleted(deed.ShuttleUid))
{
- RemComp(targetId!.Value);
+ RemComp(targetId.Value);
continue;
}
}
@@ -630,7 +734,6 @@ private void OnItemSlotChanged(EntityUid uid, ShipyardConsoleComponent component
targetId,
(ShipyardConsoleUiKey)uiComp.Key,
voucherUsed);
-
}
}
@@ -641,7 +744,9 @@ private void OnItemSlotChanged(EntityUid uid, ShipyardConsoleComponent component
/// A query to get the MobState from an entity
/// A query to get the transform component of an entity
/// The name of the sapient being if one was found, null otherwise.
- public string? FoundOrganics(EntityUid uid, EntityQuery mobQuery, EntityQuery xformQuery)
+ public string? FoundOrganics(EntityUid uid,
+ EntityQuery mobQuery,
+ EntityQuery xformQuery)
{
var xform = xformQuery.GetComponent(uid);
var childEnumerator = xform.ChildEnumerator;
@@ -653,9 +758,9 @@ private void OnItemSlotChanged(EntityUid uid, ShipyardConsoleComponent component
continue;
// Check if we have a player entity that's either still around or alive and may come back
- if (_mind.TryGetMind(child, out var mind, out var mindComp)
+ if (_mind.TryGetMind(child, out _, out var mindComp)
&& (mindComp.Session != null
- || !_mind.IsCharacterDeadPhysically(mindComp)))
+ || !_mind.IsCharacterDeadPhysically(mindComp)))
{
return Name(child);
}
@@ -678,7 +783,10 @@ private void OnItemSlotChanged(EntityUid uid, ShipyardConsoleComponent component
/// A query to get any marked objects from an entity
/// A query to get the transform component of an entity
/// The reason that a shuttle should be blocked from sale, null otherwise.
- public string? FindDisableShipyardSaleObjects(EntityUid shuttle, ShipyardConsoleUiKey key, EntityQuery disableSaleQuery, EntityQuery xformQuery)
+ public string? FindDisableShipyardSaleObjects(EntityUid shuttle,
+ ShipyardConsoleUiKey key,
+ EntityQuery disableSaleQuery,
+ EntityQuery xformQuery)
{
var xform = xformQuery.GetComponent(shuttle);
var childEnumerator = xform.ChildEnumerator;
@@ -686,7 +794,7 @@ private void OnItemSlotChanged(EntityUid uid, ShipyardConsoleComponent component
while (childEnumerator.MoveNext(out var child))
{
if (disableSaleQuery.TryGetComponent(child, out var disableSale)
- && disableSale.BlockSale is true
+ && disableSale.BlockSale
&& !disableSale.AllowedShipyardTypes.Contains(key))
{
return disableSale.Reason ?? "shipyard-console-fallback-prevent-sale";
@@ -705,8 +813,10 @@ private struct IDShipAccesses
///
/// Returns all shuttle prototype IDs the given shipyard console can offer.
///
- public (List available, List unavailable) GetAvailableShuttles(EntityUid uid, ShipyardConsoleUiKey? key = null,
- ShipyardListingComponent? listing = null, EntityUid? targetId = null)
+ public (List available, List unavailable) GetAvailableShuttles(EntityUid uid,
+ ShipyardConsoleUiKey? key = null,
+ ShipyardListingComponent? listing = null,
+ EntityUid? targetId = null)
{
var available = new List();
var unavailable = new List();
@@ -714,7 +824,7 @@ private struct IDShipAccesses
if (key == null && TryComp(uid, out var ui))
{
// Try to find a ui key that is an instance of the shipyard console ui key
- foreach (var (k, v) in ui.Actors)
+ foreach (var (k, _) in ui.Actors)
{
if (k is ShipyardConsoleUiKey shipyardKey)
{
@@ -730,7 +840,7 @@ private struct IDShipAccesses
// Construct access set from input type (voucher or ID card)
IDShipAccesses accesses;
- bool initialHasAccess = true;
+ var initialHasAccess = true;
if (TryComp(targetId, out var voucher))
{
if (voucher.ConsoleType == key)
@@ -758,14 +868,12 @@ private struct IDShipAccesses
foreach (var vessel in _prototypeManager.EnumeratePrototypes())
{
- bool hasAccess = initialHasAccess;
+ var hasAccess = initialHasAccess;
// If the vessel needs access to be bought, check the user's access.
if (!string.IsNullOrEmpty(vessel.Access))
{
- hasAccess = false;
- // Check tags
- if (accesses.Tags.Contains(vessel.Access))
- hasAccess = true;
+ // Check tags. False by default, but can be set to true if Tags contain Access.
+ hasAccess = accesses.Tags.Contains(vessel.Access);
// Check each group if we haven't found access already.
if (!hasAccess)
@@ -773,7 +881,7 @@ private struct IDShipAccesses
foreach (var groupId in accesses.Groups)
{
var groupProto = _prototypeManager.Index(groupId);
- if (groupProto?.Tags.Contains(vessel.Access) ?? false)
+ if (groupProto.Tags.Contains(vessel.Access))
{
hasAccess = true;
break;
@@ -783,9 +891,10 @@ private struct IDShipAccesses
}
// Check that the listing contains the shuttle or that the shuttle is in the group that the console is looking for
- if (listing?.Shuttles.Contains(vessel.ID) ?? false ||
- key != null && key != ShipyardConsoleUiKey.Custom &&
- vessel.Group == key)
+ if (listing?.Shuttles.Contains(vessel.ID)
+ ?? key != null
+ && key != ShipyardConsoleUiKey.Custom
+ && vessel.Group == key)
{
if (hasAccess)
available.Add(vessel.ID);
@@ -797,7 +906,14 @@ private struct IDShipAccesses
return (available, unavailable);
}
- private void RefreshState(EntityUid uid, int balance, bool access, string? shipDeed, int shipSellValue, EntityUid? targetId, ShipyardConsoleUiKey uiKey, bool freeListings)
+ private void RefreshState(EntityUid uid,
+ int balance,
+ bool access,
+ string? shipDeed,
+ int shipSellValue,
+ EntityUid? targetId,
+ ShipyardConsoleUiKey uiKey,
+ bool freeListings)
{
var newState = new ShipyardConsoleInterfaceState(
balance,
@@ -805,7 +921,7 @@ private void RefreshState(EntityUid uid, int balance, bool access, string? shipD
shipDeed,
shipSellValue,
targetId.HasValue,
- ((byte)uiKey),
+ (byte)uiKey,
GetAvailableShuttles(uid, uiKey, targetId: targetId),
uiKey.ToString(),
freeListings,
@@ -815,7 +931,12 @@ private void RefreshState(EntityUid uid, int balance, bool access, string? shipD
}
#region Deed Assignment
- void AssignShuttleDeedProperties(ShuttleDeedComponent deed, EntityUid? shuttleUid, string? shuttleName, EntityUid? shuttleOwner, bool purchasedWithVoucher)
+
+ private void AssignShuttleDeedProperties(ShuttleDeedComponent deed,
+ EntityUid? shuttleUid,
+ string? shuttleName,
+ EntityUid? shuttleOwner,
+ bool purchasedWithVoucher)
{
deed.ShuttleUid = shuttleUid;
TryParseShuttleName(deed, shuttleName!);
@@ -835,30 +956,36 @@ private void OnInitDeedSpawner(EntityUid uid, StationDeedSpawnerComponent compon
if (xform.GridUid == null)
return;
- if (!TryComp(xform.GridUid.Value, out var shuttleDeed) || !TryComp(xform.GridUid.Value, out var shuttle) || !HasComp(xform.GridUid.Value) || shuttle == null || ShipyardMap == null)
+ if (!TryComp(xform.GridUid.Value, out var shuttleDeed) ||
+ !TryComp(xform.GridUid.Value, out _) ||
+ !HasComp(xform.GridUid.Value) || ShipyardMap == null)
return;
- var output = DeedRegex.Replace($"{shuttleDeed.ShuttleOwner}", ""); // Removes content inside parentheses along with parentheses and a preceding space
+ var output =
+ DeedRegex.Replace($"{shuttleDeed.ShuttleOwner}",
+ ""); // Removes content inside parentheses along with parentheses and a preceding space
_idSystem.TryChangeFullName(uid, output); // Update the card with owner name
var deedId = EnsureComp(uid);
- AssignShuttleDeedProperties(deedId, shuttleDeed.ShuttleUid, shuttleDeed.ShuttleName, shuttleDeed.DeedHolderCard, shuttleDeed.PurchasedWithVoucher);
+ AssignShuttleDeedProperties(deedId,
+ shuttleDeed.ShuttleUid,
+ shuttleDeed.ShuttleName,
+ shuttleDeed.DeedHolderCard,
+ shuttleDeed.PurchasedWithVoucher);
}
+
#endregion
#region Ship Pricing
+
// Calculates the sell rate of a given shipyard console
private float CalculateSellRate(Entity console)
{
if (!Resolve(console, ref console.Comp))
return 0.0f;
- var taxRate = 0.0f;
- foreach (var taxAccount in console.Comp.TaxAccounts)
- {
- taxRate += taxAccount.Value;
- }
- taxRate = 1.0f - taxRate; // Return the value minus the taxes
+ var taxRate = console.Comp.TaxAccounts.Sum(taxAccount => taxAccount.Value);
+ taxRate = 1.0f - taxRate; // Return the value minus the taxes
if (console.Comp.IgnoreBaseSaleRate)
return taxRate;
@@ -871,7 +998,7 @@ private int CalculateShipResaleValue(Entity console,
if (!Resolve(console, ref console.Comp))
return 0;
- int resaleValue = baseAppraisal;
+ var resaleValue = baseAppraisal;
if (!console.Comp.IgnoreBaseSaleRate)
resaleValue = (int)(_baseSaleRate * resaleValue);
@@ -880,20 +1007,24 @@ private int CalculateShipResaleValue(Entity console,
}
// Calculates total sales tax over all accounts.
- private int CalculateTotalSalesTax(ShipyardConsoleComponent component, int sellValue)
+ private static int CalculateTotalSalesTax(ShipyardConsoleComponent component, int sellValue)
{
int salesTax = 0;
- foreach (var (account, taxCoeff) in component.TaxAccounts)
- salesTax += CalculateSalesTax(sellValue, taxCoeff);
+ foreach (var (_, taxCoefficient) in component.TaxAccounts)
+ {
+ salesTax += CalculateSalesTax(sellValue, taxCoefficient);
+ }
+
return salesTax;
}
// Calculates sales tax for a particular account.
- private int CalculateSalesTax(int sellValue, float taxRate)
+ private static int CalculateSalesTax(int sellValue, float taxRate)
{
if (float.IsFinite(taxRate) && taxRate > 0f)
return (int)(sellValue * taxRate);
return 0;
}
+
#endregion Ship Pricing
}
diff --git a/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.cs b/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.cs
index ad3d43a6b4e..4cc6532dfb2 100644
--- a/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.cs
+++ b/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.cs
@@ -308,7 +308,7 @@ private void FindEntitiesToPreserve(EntityUid entity, ref List output
}
}
- // returns false if it has ShipyardPreserveOnSaleComponent, true otherwise
+ ///False if provided uId has ShipyardPreserveOnSaleComponent, and true if otherwise.
private bool LacksPreserveOnSaleComp(EntityUid uid)
{
return !TryComp(uid, out var comp) || comp.PreserveOnSale == false;
diff --git a/Content.Shared/_NF/Shipyard/Components/SharedShipyardConsoleComponent.cs b/Content.Shared/_NF/Shipyard/Components/SharedShipyardConsoleComponent.cs
index 05cc2e42115..c2a05c840e5 100644
--- a/Content.Shared/_NF/Shipyard/Components/SharedShipyardConsoleComponent.cs
+++ b/Content.Shared/_NF/Shipyard/Components/SharedShipyardConsoleComponent.cs
@@ -1,14 +1,15 @@
-using Robust.Shared.GameStates;
-using Robust.Shared.Audio;
+using Content.Shared._NF.Bank.Components;
+using Content.Shared._Null.Nullith;
+using Content.Shared.Access;
using Content.Shared.Containers.ItemSlots;
-using Robust.Shared.Prototypes;
using Content.Shared.Radio;
-using Content.Shared.Access;
-using Content.Shared._NF.Bank.Components;
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
namespace Content.Shared._NF.Shipyard.Components;
-[RegisterComponent, NetworkedComponent, Access(typeof(SharedShipyardSystem))]
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedNullithSystem))]
public sealed partial class ShipyardConsoleComponent : Component
{
public static string TargetIdCardSlotId = "ShipyardConsole-targetId";
diff --git a/Content.Shared/_NF/Shipyard/Components/ShipyardListingComponent.cs b/Content.Shared/_NF/Shipyard/Components/ShipyardListingComponent.cs
index 87eb0c2ed48..793c88dd8fd 100644
--- a/Content.Shared/_NF/Shipyard/Components/ShipyardListingComponent.cs
+++ b/Content.Shared/_NF/Shipyard/Components/ShipyardListingComponent.cs
@@ -13,5 +13,5 @@ public sealed partial class ShipyardListingComponent : Component
/// All VesselPrototype IDs that should be listed in this shipyard console.
///
[ViewVariables, DataField(customTypeSerializer: typeof(PrototypeIdListSerializer))]
- public List Shuttles = new();
+ public List Shuttles = [];
}
diff --git a/Content.Shared/_NF/Shipyard/Prototypes/VesselPrototype.cs b/Content.Shared/_NF/Shipyard/Prototypes/VesselPrototype.cs
index 73afcbf4bc3..88b2904f04f 100644
--- a/Content.Shared/_NF/Shipyard/Prototypes/VesselPrototype.cs
+++ b/Content.Shared/_NF/Shipyard/Prototypes/VesselPrototype.cs
@@ -50,16 +50,16 @@ public sealed class VesselPrototype : IPrototype, IInheritingPrototype
/// The purpose of the vessel. (e.g. Service, Cargo, Engineering etc.)
///
[DataField("class")]
- public List Classes = new();
+ public List Classes = [];
///
/// The engine type that powers the vessel. (e.g. AME, Plasma, Solar etc.)
///
[DataField("engine")]
- public List Engines = new();
+ public List Engines = [];
///
- /// The access required to buy the product. (e.g. Command, Mail, Bailiff, etc.)
+ /// The access required to buy the product. (e.g. Command, Mail, Bailiff, etc.)
///
[DataField]
public string Access = string.Empty;
diff --git a/Content.Shared/_NF/Shipyard/SharedShipyardSystem.cs b/Content.Shared/_NF/Shipyard/SharedShipyardSystem.cs
index 7763cfe5ef9..8a581d7fcaf 100644
--- a/Content.Shared/_NF/Shipyard/SharedShipyardSystem.cs
+++ b/Content.Shared/_NF/Shipyard/SharedShipyardSystem.cs
@@ -18,8 +18,8 @@ public enum ShipyardConsoleUiKey : byte
Sr,
Medical,
Service, // Null Sector
- // Add ships to this key if they are only available from mothership consoles. Shipyards using it are inherently empty and are populated using the ShipyardListingComponent.
- Custom
+ /// Add ships to this key if they are only available from mothership consoles. Shipyards using it are inherently empty and are populated using the ShipyardListingComponent.
+ Custom,
}
public abstract class SharedShipyardSystem : EntitySystem
@@ -37,8 +37,8 @@ public override void Initialize()
private void OnHandleState(EntityUid uid, ShipyardConsoleComponent component, ref ComponentHandleState args)
{
- if (args.Current is not ShipyardConsoleComponentState state) return;
-
+ if (args.Current is not ShipyardConsoleComponentState state)
+ return;
}
private void OnGetState(EntityUid uid, ShipyardConsoleComponent component, ref ComponentGetState args)
From 4069cec31d2e4e4b4f895c3cce5eb85a5b56866b Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Tue, 2 Dec 2025 22:43:30 -0600
Subject: [PATCH 04/25] Corrected Floof Reference Source Folder to "_Floof"
(For IceCream)
---
.../Objects/Consumable/Food/icecream.yml | 56 +++++++++---------
.../Food/icecream.rsi/bananasplit.png | Bin
.../Food/icecream.rsi/blooddrop.png | Bin
.../Consumable/Food/icecream.rsi/bowl.png | Bin
.../Consumable/Food/icecream.rsi/caramel.png | Bin
.../Consumable/Food/icecream.rsi/choco.png | Bin
.../Consumable/Food/icecream.rsi/coconut.png | Bin
.../Consumable/Food/icecream.rsi/coffee.png | Bin
.../Consumable/Food/icecream.rsi/cosmos.png | Bin
.../Consumable/Food/icecream.rsi/fill-1.png | Bin
.../Consumable/Food/icecream.rsi/fill-2.png | Bin
.../Consumable/Food/icecream.rsi/fill-3.png | Bin
.../Consumable/Food/icecream.rsi/fill-4.png | Bin
.../Consumable/Food/icecream.rsi/fill-5.png | Bin
.../Consumable/Food/icecream.rsi/gilded.png | Bin
.../Consumable/Food/icecream.rsi/greentea.png | Bin
.../Consumable/Food/icecream.rsi/hazelnut.png | Bin
.../Consumable/Food/icecream.rsi/lemon.png | Bin
.../Food/icecream.rsi/loversdelight.png | Bin
.../Consumable/Food/icecream.rsi/meta.json | 0
.../Food/icecream.rsi/mintchoco.png | Bin
.../Consumable/Food/icecream.rsi/napalm.png | Bin
.../Food/icecream.rsi/neapolitan.png | Bin
.../Consumable/Food/icecream.rsi/nebula.png | Bin
.../Consumable/Food/icecream.rsi/orange.png | Bin
.../Consumable/Food/icecream.rsi/plate.png | Bin
.../Consumable/Food/icecream.rsi/rainbow.png | Bin
.../Food/icecream.rsi/rockyroad.png | Bin
.../Food/icecream.rsi/saltedcaramel.png | Bin
.../Food/icecream.rsi/spaghetti.png | Bin
.../Food/icecream.rsi/starlight.png | Bin
.../Food/icecream.rsi/strawberry.png | Bin
.../Consumable/Food/icecream.rsi/vanilla.png | Bin
.../Consumable/Food/icecream.rsi/void.png | Bin
.../Consumable/Food/icecream.rsi/walnut.png | Bin
.../Food/icecream.rsi/watermelon.png | Bin
36 files changed, 28 insertions(+), 28 deletions(-)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/bananasplit.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/blooddrop.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/bowl.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/caramel.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/choco.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/coconut.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/coffee.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/cosmos.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/fill-1.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/fill-2.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/fill-3.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/fill-4.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/fill-5.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/gilded.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/greentea.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/hazelnut.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/lemon.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/loversdelight.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/meta.json (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/mintchoco.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/napalm.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/neapolitan.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/nebula.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/orange.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/plate.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/rainbow.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/rockyroad.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/saltedcaramel.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/spaghetti.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/starlight.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/strawberry.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/vanilla.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/void.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/walnut.png (100%)
rename Resources/Textures/{Floof => _Floof}/Objects/Consumable/Food/icecream.rsi/watermelon.png (100%)
diff --git a/Resources/Prototypes/Floof/Entities/Objects/Consumable/Food/icecream.yml b/Resources/Prototypes/Floof/Entities/Objects/Consumable/Food/icecream.yml
index e8889acaf72..8fa79f8df9f 100644
--- a/Resources/Prototypes/Floof/Entities/Objects/Consumable/Food/icecream.yml
+++ b/Resources/Prototypes/Floof/Entities/Objects/Consumable/Food/icecream.yml
@@ -16,7 +16,7 @@
- ReagentId: IceCream
Quantity: 30
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: bowl
- type: DamageOnLand
@@ -71,7 +71,7 @@
flavors:
- blooddrop
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: blooddrop
- type: SolutionContainerManager
@@ -97,7 +97,7 @@
- creamy
- caramel
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: caramel
- type: SolutionContainerManager
@@ -120,7 +120,7 @@
flavors:
- coffee
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: coffee
- type: SolutionContainerManager
@@ -144,7 +144,7 @@
- sweet
- tea
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: greentea
- type: SolutionContainerManager
@@ -168,7 +168,7 @@
- sweet
- sour
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: lemon
- type: SolutionContainerManager
@@ -192,7 +192,7 @@
- sweet
- orange
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: orange
- type: SolutionContainerManager
@@ -218,7 +218,7 @@
- grape
- sour
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: rainbow
- type: SolutionContainerManager
@@ -243,7 +243,7 @@
- caramel
- creamy
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: saltedcaramel
- type: SolutionContainerManager
@@ -269,7 +269,7 @@
- vanilla
- strawberry
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: spaghetti
- type: SolutionContainerManager
@@ -292,7 +292,7 @@
flavors:
- strawberry
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: strawberry
- type: SolutionContainerManager
@@ -318,7 +318,7 @@
- chocolate
- vanilla
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: bananasplit
- type: SolutionContainerManager
@@ -341,7 +341,7 @@
flavors:
- chocolate
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: choco
- type: SolutionContainerManager
@@ -364,7 +364,7 @@
flavors:
- coconut
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: coconut
- type: SolutionContainerManager
@@ -387,7 +387,7 @@
flavors:
- space time
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: cosmos
- type: SolutionContainerManager
@@ -412,7 +412,7 @@
- silver
- mining
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: gilded
- type: SolutionContainerManager
@@ -435,7 +435,7 @@
flavors:
- hazelnut
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: hazelnut
- type: SolutionContainerManager
@@ -460,7 +460,7 @@
- lust
- chocolate
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: loversdelight
- type: SolutionContainerManager
@@ -484,7 +484,7 @@
- chocolate
- mint
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: mintchoco
- type: SolutionContainerManager
@@ -507,7 +507,7 @@
flavors:
- star
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: napalm
- type: SolutionContainerManager
@@ -530,7 +530,7 @@
flavors:
- strawberry
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: nebula
- type: SolutionContainerManager
@@ -556,7 +556,7 @@
- chocolate
- vanilla
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: neapolitan
- type: SolutionContainerManager
@@ -581,7 +581,7 @@
- nuts
- chocolate
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: rockyroad
- type: SolutionContainerManager
@@ -604,7 +604,7 @@
flavors:
- strawberry
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: starlight
- type: SolutionContainerManager
@@ -627,7 +627,7 @@
flavors:
- vanilla
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: vanilla
- type: SolutionContainerManager
@@ -652,7 +652,7 @@
- emptiness
- despair
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: void
- type: SolutionContainerManager
@@ -675,7 +675,7 @@
flavors:
- walnut
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: walnut
- type: SolutionContainerManager
@@ -698,7 +698,7 @@
flavors:
- watermelon
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: watermelon
- type: SolutionContainerManager
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/bananasplit.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/bananasplit.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/bananasplit.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/bananasplit.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/blooddrop.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/blooddrop.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/blooddrop.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/blooddrop.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/bowl.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/bowl.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/bowl.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/bowl.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/caramel.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/caramel.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/caramel.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/caramel.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/choco.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/choco.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/choco.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/choco.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/coconut.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/coconut.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/coconut.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/coconut.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/coffee.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/coffee.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/coffee.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/coffee.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/cosmos.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/cosmos.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/cosmos.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/cosmos.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/fill-1.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/fill-1.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/fill-1.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/fill-1.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/fill-2.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/fill-2.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/fill-2.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/fill-2.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/fill-3.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/fill-3.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/fill-3.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/fill-3.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/fill-4.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/fill-4.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/fill-4.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/fill-4.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/fill-5.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/fill-5.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/fill-5.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/fill-5.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/gilded.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/gilded.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/gilded.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/gilded.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/greentea.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/greentea.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/greentea.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/greentea.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/hazelnut.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/hazelnut.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/hazelnut.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/hazelnut.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/lemon.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/lemon.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/lemon.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/lemon.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/loversdelight.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/loversdelight.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/loversdelight.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/loversdelight.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/meta.json b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/meta.json
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/meta.json
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/meta.json
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/mintchoco.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/mintchoco.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/mintchoco.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/mintchoco.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/napalm.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/napalm.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/napalm.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/napalm.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/neapolitan.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/neapolitan.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/neapolitan.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/neapolitan.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/nebula.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/nebula.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/nebula.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/nebula.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/orange.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/orange.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/orange.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/orange.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/plate.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/plate.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/plate.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/plate.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/rainbow.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/rainbow.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/rainbow.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/rainbow.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/rockyroad.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/rockyroad.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/rockyroad.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/rockyroad.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/saltedcaramel.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/saltedcaramel.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/saltedcaramel.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/saltedcaramel.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/spaghetti.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/spaghetti.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/spaghetti.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/spaghetti.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/starlight.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/starlight.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/starlight.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/starlight.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/strawberry.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/strawberry.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/strawberry.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/strawberry.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/vanilla.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/vanilla.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/vanilla.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/vanilla.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/void.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/void.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/void.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/void.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/walnut.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/walnut.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/walnut.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/walnut.png
diff --git a/Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/watermelon.png b/Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/watermelon.png
similarity index 100%
rename from Resources/Textures/Floof/Objects/Consumable/Food/icecream.rsi/watermelon.png
rename to Resources/Textures/_Floof/Objects/Consumable/Food/icecream.rsi/watermelon.png
From 4c32112d93c9c34a432d0180ab65c50da5be2436 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Tue, 2 Dec 2025 22:54:21 -0600
Subject: [PATCH 05/25] Added Niekroniak Names List
---
.../Prototypes/Datasets/Names/niekroniak.yml | 40 +++++++++++++++++++
1 file changed, 40 insertions(+)
create mode 100644 Resources/Prototypes/Datasets/Names/niekroniak.yml
diff --git a/Resources/Prototypes/Datasets/Names/niekroniak.yml b/Resources/Prototypes/Datasets/Names/niekroniak.yml
new file mode 100644
index 00000000000..0f46f11284e
--- /dev/null
+++ b/Resources/Prototypes/Datasets/Names/niekroniak.yml
@@ -0,0 +1,40 @@
+- type: dataset
+ id: NamesFirstNiekroniakLeader
+ values:
+ - Ensignate
+ - Lieutenant
+ - Subsidiary
+
+- type: dataset
+ id: NamesFirstNiekroniak
+ values:
+ - Anak
+ - Ryak
+ - Velak
+ - Ceasak
+
+- type: dataset
+ id: NamesMiddleNiekroniak
+ values:
+ - Sho'um
+ - Cemmia
+ - Henzia
+ - Humia
+ - Saria
+ - Kro'ia
+ - Nie'ia
+ - Leria
+ - Cemmak
+
+- type: dataset
+ id: NamesLastNiekroniak
+ values:
+ - Akh
+ - Cemmak
+ - Einak
+ - Henzak
+ - Shanak
+ - Shonak
+ - Pavak
+ - Varchak
+ - Zavak
\ No newline at end of file
From 8d8a0202c9ebf2ca3dfce8be80f60081fe1a1713 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Tue, 2 Dec 2025 23:02:16 -0600
Subject: [PATCH 06/25] Corrected PricingSystem.cs Commentation
---
Content.Server/Cargo/Systems/PricingSystem.cs | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/Content.Server/Cargo/Systems/PricingSystem.cs b/Content.Server/Cargo/Systems/PricingSystem.cs
index 72cbdd301c3..2d1909f41cd 100644
--- a/Content.Server/Cargo/Systems/PricingSystem.cs
+++ b/Content.Server/Cargo/Systems/PricingSystem.cs
@@ -46,6 +46,7 @@
using Robust.Shared.Map.Components;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
+
// Frontier
namespace Content.Server.Cargo.Systems;
@@ -312,7 +313,7 @@ public double GetPrice(EntityUid uid, bool includeContents = true)
// Begin Frontier - GetPrice variant that uses predicate
///
- /// Appraises an entity, returning its price. Respects predicate - an entity that is excluded will be removed from the
+ /// Appraises an entity, returning its price. Respects predicate - an entity that is excluded will be removed from the
///
/// The entity to appraise.
/// Whether to examine its contents.
@@ -482,7 +483,7 @@ private double GetVendPrice(EntityPrototype prototype)
// End of modified code
///
- /// Appraises a grid, this is mainly meant to be used by yarrs.
+ /// Appraises a grid. This is primarily for pirates.
///
/// The grid to appraise.
/// An optional predicate that controls whether or not the entity is counted toward the total.
From 00764bf35e8fb871d10bdfacad7182f74857d281 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Tue, 2 Dec 2025 23:40:02 -0600
Subject: [PATCH 07/25] Added Purchasable POIs to PointOfInterestSystem.cs
---
.../_NF/GameRule/PointOfInterestPrototype.cs | 2 +-
.../_NF/GameRule/PointOfInterestSystem.cs | 167 +++++++++++++-----
2 files changed, 126 insertions(+), 43 deletions(-)
diff --git a/Content.Server/_NF/GameRule/PointOfInterestPrototype.cs b/Content.Server/_NF/GameRule/PointOfInterestPrototype.cs
index 5dd7b8a9d07..ab4dbb143d0 100644
--- a/Content.Server/_NF/GameRule/PointOfInterestPrototype.cs
+++ b/Content.Server/_NF/GameRule/PointOfInterestPrototype.cs
@@ -29,7 +29,7 @@ public sealed partial class PointOfInterestPrototype : IPrototype, IInheritingPr
public string Name { get; private set; } = "";
///
- /// Should we set the warppoint name based on the grid name.
+ /// Should we set the warp point name based on the grid name.
///
[DataField]
public bool NameWarp { get; set; } = true;
diff --git a/Content.Server/_NF/GameRule/PointOfInterestSystem.cs b/Content.Server/_NF/GameRule/PointOfInterestSystem.cs
index f083bdbfb0d..67641093e77 100644
--- a/Content.Server/_NF/GameRule/PointOfInterestSystem.cs
+++ b/Content.Server/_NF/GameRule/PointOfInterestSystem.cs
@@ -1,17 +1,19 @@
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
+using Content.Server._NF.Station.Systems;
using Content.Server._NF.Trade;
using Content.Server.GameTicking;
using Content.Server.Maps;
using Content.Server.Station.Systems;
using Content.Shared._NF.CCVar;
+using Content.Shared._Null.Nullith;
using Content.Shared.GameTicking;
using Robust.Shared.Configuration;
+using Robust.Shared.EntitySerialization.Systems;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
-using Content.Server._NF.Station.Systems;
-using Robust.Shared.EntitySerialization.Systems;
namespace Content.Server._NF.GameRule;
@@ -19,6 +21,9 @@ namespace Content.Server._NF.GameRule;
/// This handles the dungeon and trading post spawning, as well as round end capitalism summary
///
//[Access(typeof(NfAdventureRuleSystem))]
+[SuppressMessage("ReSharper", "SuggestVarOrType_BuiltInTypes")]
+[SuppressMessage("ReSharper", "SuggestVarOrType_SimpleTypes")]
+[SuppressMessage("ReSharper", "InconsistentNaming")]
public sealed class PointOfInterestSystem : EntitySystem
{
[Dependency] private readonly IConfigurationManager _cfg = default!;
@@ -30,7 +35,7 @@ public sealed class PointOfInterestSystem : EntitySystem
[Dependency] private readonly StationRenameWarpsSystems _renameWarps = default!;
[Dependency] private readonly StationSystem _station = default!;
- private List _stationCoords = new();
+ private readonly List _stationCoords = [];
public override void Initialize()
{
@@ -49,13 +54,15 @@ private void AddStationCoordsToSet(Vector2 coords)
_stationCoords.Add(coords);
}
- public void GenerateDepots(MapId mapUid, List depotPrototypes, out List depotStations)
+ public void GenerateDepots(MapId mapUid,
+ List depotPrototypes,
+ out List depotStations)
{
//For depots, we want them to fill a circular type dystance formula to try to keep them as far apart as possible
//Therefore, we will be taking our range properties and treating them as magnitudes of a direction vector divided
//by the number of depots set in our corresponding cvar
- depotStations = new List();
+ depotStations = [];
var depotCount = _cfg.GetCVar(NFCCVars.CargoDepots);
var rotation = 2 * Math.PI / depotCount;
var rotationOffset = _random.NextAngle() / depotCount;
@@ -83,7 +90,8 @@ public void GenerateDepots(MapId mapUid, List depotPro
overrideName += $" {(char)('A' + i)}"; // " A" ... " Z"
else
overrideName += $" {i + 1}"; // " 27", " 28"...
- if (TrySpawnPoiGrid(mapUid, proto, offset, out var depotUid, overrideName: overrideName) && depotUid is { Valid: true } depot)
+ if (TrySpawnPoiGrid(mapUid, proto, offset, out var depotUid, overrideName: overrideName) &&
+ depotUid is { Valid: true } depot)
{
// Nasty jank: set up destination in the station.
var depotStation = _station.GetOwningStation(depot);
@@ -94,19 +102,22 @@ public void GenerateDepots(MapId mapUid, List depotPro
else
destComp.DestinationProto = "CargoOther";
}
+
depotStations.Add(depot);
AddStationCoordsToSet(offset); // adjust list of actual station coords
}
}
}
- public void GenerateMarkets(MapId mapUid, List marketPrototypes, out List marketStations)
+ public void GenerateMarkets(MapId mapUid,
+ List marketPrototypes,
+ out List marketStations)
{
//For market stations, we are going to allow for a bit of randomness and a different offset configuration. We dont
//want copies of this one, since these can be more themed and duplicate names, for instance, can make for a less
//ideal world
- marketStations = new List();
+ marketStations = [];
var marketCount = _cfg.GetCVar(NFCCVars.MarketStations);
_random.Shuffle(marketPrototypes);
int marketsAdded = 0;
@@ -135,13 +146,15 @@ public void GenerateMarkets(MapId mapUid, List marketP
}
}
- public void GenerateOptionals(MapId mapUid, List optionalPrototypes, out List optionalStations)
+ public void GenerateOptionals(MapId mapUid,
+ List optionalPrototypes,
+ out List optionalStations)
{
//Stations that do not have a defined grouping in their prototype get a default of "Optional" and get put into the
//generic random rotation of POIs. This should include traditional places like Tinnia's rest, the Science Lab, The Pit,
//and most RP places. This will essentially put them all into a pool to pull from, and still does not use the RNG function.
- optionalStations = new List();
+ optionalStations = [];
var optionalCount = _cfg.GetCVar(NFCCVars.OptionalStations);
_random.Shuffle(optionalPrototypes);
int optionalsAdded = 0;
@@ -169,14 +182,16 @@ public void GenerateOptionals(MapId mapUid, List optio
}
}
- public void GenerateRequireds(MapId mapUid, List requiredPrototypes, out List requiredStations)
+ public void GenerateRequireds(MapId mapUid,
+ List requiredPrototypes,
+ out List requiredStations)
{
//Stations are required are ones that are vital to function but otherwise still follow a generic random spawn logic
//Traditionally these would be stations like Expedition Lodge, NFSD station, Prison/Courthouse POI, etc.
- //There are no limit to these, and any prototype marked alwaysSpawn = true will get pulled out of any list that isnt Markets/Depots
+ //There are no limit to these, and any prototype marked alwaysSpawn = true will get pulled out of any list that isn't Markets/Depots
//And will always appear every time, and also will not be included in other optional/dynamic lists
- requiredStations = new List();
+ requiredStations = [];
if (_ticker.CurrentPreset is null)
return;
@@ -198,20 +213,23 @@ public void GenerateRequireds(MapId mapUid, List requi
}
}
- public void GenerateUniques(MapId mapUid, Dictionary> uniquePrototypes, out List uniqueStations)
+ public void GenerateUniques(MapId mapUid,
+ Dictionary> uniquePrototypes,
+ out List uniqueStations)
{
- //Unique locations are semi-dynamic groupings of POIs that rely each independantly on the SpawnChance per POI prototype
- //Since these are the remainder, and logically must have custom-designated groupings, we can then know to subdivide
- //our random pool into these found groups.
- //To do this with an equal distribution on a per-POI, per-round percentage basis, we are going to ensure a random
- //pick order of which we analyze our weighted chances to spawn, and if successful, remove every entry of that group
- //entirely.
+ // Unique locations are semi-dynamic groupings of POIs that rely each independently on the SpawnChance per POI prototype
+ // Since these are the remainder, and logically must have custom-designated groupings, we can then know to subdivide
+ // our random pool into these found groups.
+ // To do this with an equal distribution on a per-POI, per-round percentage basis, we are going to ensure a random
+ // pick order of which we analyze our weighted chances to spawn, and if successful, remove every entry of that group
+ // entirely.
- uniqueStations = new List();
+ uniqueStations = [];
if (_ticker.CurrentPreset is null)
return;
- var currentPreset = _ticker.CurrentPreset!.ID;
+
+ var currentPreset = _ticker.CurrentPreset.ID;
foreach (var prototypeList in uniquePrototypes.Values)
{
@@ -224,22 +242,94 @@ public void GenerateUniques(MapId mapUid, Dictionary
+ /// [Null Sector] For purchasable Points of Interest, and Locality's sake.
+ ///
+ public void GeneratePurchased(MapId mapUid, BuyablePoIPrototype proto)
+ {
+ /*
+ * For purchasable points of interest which may be in any number, and ignorant of the optional stations
+ * that are (un)naturally spawned elsewhere.
+ */
+
+ if (_ticker.CurrentPreset is null)
+ return;
+
+ // There is no game mode safety check. The safety check at this point is dictated by the presence of the object
+ // that permits one to purchase a POI.
+
+ var offset = GetRandomPOICoord(proto.MinimumDistance, proto.MaximumDistance);
+
+ if (!TrySpawnBoughtPoI(mapUid, proto, offset, out var optionalUid) && optionalUid is { Valid: true } _)
+ {
+ return;
+ }
+
+ AddStationCoordsToSet(offset);
+ return;
+
+ bool TrySpawnBoughtPoI(MapId map,
+ BuyablePoIPrototype boughtPoIProto,
+ Vector2 locOffset,
+ out EntityUid? gridUid,
+ string? overrideName = null)
+ {
+ gridUid = null;
+ if (!_map.TryLoadGrid(map,
+ boughtPoIProto.GridPath,
+ out var loadedGrid,
+ offset: locOffset,
+ rot: _random.NextAngle()))
+ return false;
+ gridUid = loadedGrid.Value;
+ List gridList = [loadedGrid.Value];
+
+ string stationName = string.IsNullOrEmpty(overrideName) ? boughtPoIProto.Name : overrideName;
+
+ EntityUid? stationUid = null;
+ if (_proto.TryIndex(boughtPoIProto.ID, out var stationProto))
+ {
+ stationUid = _station.InitializeNewStation(stationProto.Stations[boughtPoIProto.ID], gridList, stationName);
+ }
+
+ var meta = EnsureComp(loadedGrid.Value);
+ _meta.SetEntityName(loadedGrid.Value, stationName, meta);
+
+ EntityManager.AddComponents(loadedGrid.Value, boughtPoIProto.AddComponents);
+
+ // Rename warp points after set up if needed
+ if (boughtPoIProto.NameWarp)
+ {
+ bool? hideWarp = boughtPoIProto.HideWarp ? true : null;
+ if (stationUid != null)
+ _renameWarps.SyncWarpPointsToStation(stationUid.Value, forceAdminOnly: hideWarp);
+ else
+ _renameWarps.SyncWarpPointsToGrids(gridList, forceAdminOnly: hideWarp);
+ }
+
+ return true;
+ }
+ }
+
+ private bool TrySpawnPoiGrid(MapId mapUid,
+ PointOfInterestPrototype proto,
+ Vector2 offset,
+ out EntityUid? gridUid,
+ string? overrideName = null)
{
gridUid = null;
if (!_map.TryLoadGrid(mapUid, proto.GridPath, out var loadedGrid, offset: offset, rot: _random.NextAngle()))
@@ -274,20 +364,13 @@ private bool TrySpawnPoiGrid(MapId mapUid, PointOfInterestPrototype proto, Vecto
private Vector2 GetRandomPOICoord(float unscaledMinRange, float unscaledMaxRange)
{
int numRetries = int.Max(_cfg.GetCVar(NFCCVars.POIPlacementRetries), 0);
- float minDistance = float.Max(_cfg.GetCVar(NFCCVars.MinPOIDistance), 0); // Constant at the end to avoid NaN weirdness
+ float minDistance =
+ float.Max(_cfg.GetCVar(NFCCVars.MinPOIDistance), 0); // Constant at the end to avoid NaN weirdness
Vector2 coords = _random.NextVector2(unscaledMinRange, unscaledMaxRange);
for (int i = 0; i < numRetries; i++)
{
- bool positionIsValid = true;
- foreach (var station in _stationCoords)
- {
- if (Vector2.Distance(station, coords) < minDistance)
- {
- positionIsValid = false;
- break;
- }
- }
+ bool positionIsValid = _stationCoords.All(station => !(Vector2.Distance(station, coords) < minDistance));
// We have a valid position
if (positionIsValid)
From d8182be95f37d163d372d8cc20cdb73397d14613 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 00:18:04 -0600
Subject: [PATCH 08/25] Moved Null's Form of shipyard.ftl
---
.../en-US/_Null/{purchasable_grids => shipyarding}/shipyard.ftl | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename Resources/Locale/en-US/_Null/{purchasable_grids => shipyarding}/shipyard.ftl (100%)
diff --git a/Resources/Locale/en-US/_Null/purchasable_grids/shipyard.ftl b/Resources/Locale/en-US/_Null/shipyarding/shipyard.ftl
similarity index 100%
rename from Resources/Locale/en-US/_Null/purchasable_grids/shipyard.ftl
rename to Resources/Locale/en-US/_Null/shipyarding/shipyard.ftl
From 471d23bf36039abaf7c4f6fed76eb171f73e9213 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 00:19:43 -0600
Subject: [PATCH 09/25] Added Base Buyable POI Prototype
---
.../PointsOfInterest_Purchasable/base.yml | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 Resources/Prototypes/_Null/PointsOfInterest_Purchasable/base.yml
diff --git a/Resources/Prototypes/_Null/PointsOfInterest_Purchasable/base.yml b/Resources/Prototypes/_Null/PointsOfInterest_Purchasable/base.yml
new file mode 100644
index 00000000000..ea3e4932ee8
--- /dev/null
+++ b/Resources/Prototypes/_Null/PointsOfInterest_Purchasable/base.yml
@@ -0,0 +1,23 @@
+- type: buyablePoI
+ id: BaseBuyablePOI
+ abstract: true
+ price: 100000 # Default price, which should be changed per-case.
+ minimumDistance: 2000
+ maximumDistance: 4000
+ addComponents:
+ - type: IFF
+ color: "#ffa600"
+ readOnly: true
+ # Prevent gravity changes
+ - type: ForceAnchor
+
+- type: buyablePoI
+ id: BaseBuyableUnanchoredPOI
+ abstract: true
+ price: 100000 # Default price, which should be changed per-case.
+ minimumDistance: 2000
+ maximumDistance: 4000
+ addComponents:
+ - type: IFF
+ color: "#ffa600"
+ readOnly: true
\ No newline at end of file
From 31e750cb96c859db8dea8de4e47496e58244c3d8 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 00:20:01 -0600
Subject: [PATCH 10/25] Added Zombie Lab (Mors Research Centre) Buyable POI
---
.../Maps/_Null/POI_Purchasable/zombie_lab.yml | 3036 +++++++++++++++++
.../zombie_lab.yml | 62 +
2 files changed, 3098 insertions(+)
create mode 100644 Resources/Maps/_Null/POI_Purchasable/zombie_lab.yml
create mode 100644 Resources/Prototypes/_Null/PointsOfInterest_Purchasable/zombie_lab.yml
diff --git a/Resources/Maps/_Null/POI_Purchasable/zombie_lab.yml b/Resources/Maps/_Null/POI_Purchasable/zombie_lab.yml
new file mode 100644
index 00000000000..69d9c1a6934
--- /dev/null
+++ b/Resources/Maps/_Null/POI_Purchasable/zombie_lab.yml
@@ -0,0 +1,3036 @@
+meta:
+ format: 7
+ category: Grid
+ engineVersion: 260.2.0
+ forkId: ""
+ forkVersion: ""
+ time: 12/02/2025 23:02:05
+ entityCount: 461
+maps: []
+grids:
+- 1
+orphans:
+- 1
+nullspace: []
+tilemap:
+ 0: Space
+ 4: FloorSteel
+ 2: FloorWhite
+ 6: FloorWhiteMono
+ 5: FloorWhitePavement
+ 7: FloorWhitePavementVertical
+ 3: Lattice
+ 1: Plating
+entities:
+- proto: ""
+ entities:
+ - uid: 1
+ components:
+ - type: MetaData
+ name: Zombie Lab
+ - type: Transform
+ pos: -0.453125,-0.609375
+ parent: invalid
+ - type: MapGrid
+ chunks:
+ -1,0:
+ ind: -1,0
+ tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAACAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAgAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAEAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAMAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAwAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
+ version: 7
+ 0,0:
+ ind: 0,0
+ tiles: AgAAAAAAAAIAAAAAAAACAAAAAAAABAAAAAAAAAIAAAAAAAAFAAAAAAAAAgAAAAAAAAUAAAAAAAACAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAACAAAAAAAABgAAAAAAAAQAAAAAAAAHAAAAAAAABgAAAAAAAAcAAAAAAAAGAAAAAAAABwAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAABAAAAAAAAAwAAAAAAAAAAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAABAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAABAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAAAQAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAYAAAAAAAAGAAAAAAAABgAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAGAAAAAAAABgAAAAAAAAYAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAADAAAAAAAAAwAAAAAAAAAAAAAAAAABAAAAAAAABgAAAAAAAAYAAAAAAAAGAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAYAAAAAAAAGAAAAAAAABgAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
+ version: 7
+ -1,-1:
+ ind: -1,-1
+ tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAwAAAAAAAAMAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAwAAAAAAAAMAAAAAAAADAAAAAAAAAQAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAEAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAACAAAAAAAAAgAAAAAAAA==
+ version: 7
+ 0,-1:
+ ind: 0,-1
+ tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAQAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAEAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAABAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAQAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAwAAAAAAAAMAAAAAAAADAAAAAAAAAQAAAAAAAAEAAAAAAAAEAAAAAAAAAQAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAEAAAAAAAAFAAAAAAAAAgAAAAAAAAUAAAAAAAABAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAABAAAAAAAAAQAAAAAAAAQAAAAAAAAHAAAAAAAABgAAAAAAAAcAAAAAAAAGAAAAAAAABwAAAAAAAAQAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAgAAAAAAAAIAAAAAAAAEAAAAAAAAAgAAAAAAAAUAAAAAAAACAAAAAAAABQAAAAAAAAIAAAAAAAAFAAAAAAAABAAAAAAAAAQAAAAAAAABAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAIAAAAAAAAGAAAAAAAABAAAAAAAAAcAAAAAAAAGAAAAAAAABwAAAAAAAAYAAAAAAAAHAAAAAAAABAAAAAAAAAQAAAAAAAAEAAAAAAAABAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAA==
+ version: 7
+ - type: Broadphase
+ - type: Physics
+ bodyStatus: InAir
+ angularDamping: 0.05
+ linearDamping: 0.05
+ fixedRotation: False
+ bodyType: Dynamic
+ - type: Fixtures
+ fixtures: {}
+ - type: OccluderTree
+ - type: SpreaderGrid
+ - type: Shuttle
+ - type: GridPathfinding
+ - type: Gravity
+ gravityShakeSound: !type:SoundPathSpecifier
+ path: /Audio/Effects/alert.ogg
+ - type: DecalGrid
+ chunkCollection:
+ version: 2
+ nodes:
+ - node:
+ color: '#7A9CC7FF'
+ id: BrickTileSteelCornerNe
+ decals:
+ 28: 2,2
+ - node:
+ color: '#7AA46AFF'
+ id: BrickTileSteelCornerNe
+ decals:
+ 3: 8,1
+ - node:
+ color: '#B85A23FF'
+ id: BrickTileSteelCornerNe
+ decals:
+ 53: 12,1
+ 54: 11,2
+ - node:
+ color: '#7A9CC7FF'
+ id: BrickTileSteelCornerNw
+ decals:
+ 24: -1,2
+ 25: -2,1
+ - node:
+ color: '#7AA46AFF'
+ id: BrickTileSteelCornerNw
+ decals:
+ 4: 4,1
+ - node:
+ color: '#B85A23FF'
+ id: BrickTileSteelCornerNw
+ decals:
+ 52: 10,2
+ - node:
+ color: '#7A9CC7FF'
+ id: BrickTileSteelCornerSe
+ decals:
+ 29: 2,-2
+ - node:
+ color: '#7AA46AFF'
+ id: BrickTileSteelCornerSe
+ decals:
+ 1: 8,-3
+ 5: 7,-4
+ - node:
+ color: '#B85A23FF'
+ id: BrickTileSteelCornerSe
+ decals:
+ 51: 12,-1
+ - node:
+ color: '#7A9CC7FF'
+ id: BrickTileSteelCornerSw
+ decals:
+ 26: -2,-1
+ 27: -1,-2
+ - node:
+ color: '#7AA46AFF'
+ id: BrickTileSteelCornerSw
+ decals:
+ 6: 5,-4
+ 7: 4,-3
+ - node:
+ color: '#B85A23FF'
+ id: BrickTileSteelCornerSw
+ decals:
+ 55: 10,0
+ - node:
+ color: '#7A50C7FF'
+ id: BrickTileSteelEndE
+ decals:
+ 40: 8,3
+ - node:
+ color: '#7AA46AFF'
+ id: BrickTileSteelEndE
+ decals:
+ 2: 9,-2
+ - node:
+ color: '#B85A23FF'
+ id: BrickTileSteelEndS
+ decals:
+ 56: 11,-2
+ - node:
+ color: '#7A50C7FF'
+ id: BrickTileSteelEndW
+ decals:
+ 39: 4,3
+ - node:
+ color: '#7AA46AFF'
+ id: BrickTileSteelInnerNe
+ decals:
+ 11: 8,-2
+ - node:
+ color: '#B85A23FF'
+ id: BrickTileSteelInnerNe
+ decals:
+ 49: 11,1
+ - node:
+ color: '#7A9CC7FF'
+ id: BrickTileSteelInnerNw
+ decals:
+ 23: -1,1
+ - node:
+ color: '#7AA46AFF'
+ id: BrickTileSteelInnerSe
+ decals:
+ 8: 7,-3
+ 9: 8,-2
+ - node:
+ color: '#B85A23FF'
+ id: BrickTileSteelInnerSe
+ decals:
+ 58: 11,-1
+ - node:
+ color: '#7A9CC7FF'
+ id: BrickTileSteelInnerSw
+ decals:
+ 22: -1,-1
+ - node:
+ color: '#7AA46AFF'
+ id: BrickTileSteelInnerSw
+ decals:
+ 10: 5,-3
+ - node:
+ color: '#B85A23FF'
+ id: BrickTileSteelInnerSw
+ decals:
+ 48: 11,0
+ - node:
+ color: '#7A9CC7FF'
+ id: BrickTileSteelLineE
+ decals:
+ 30: 2,-1
+ 31: 2,0
+ 32: 2,1
+ - node:
+ color: '#7AA46AFF'
+ id: BrickTileSteelLineE
+ decals:
+ 12: 8,0
+ 13: 8,-1
+ - node:
+ color: '#B85A23FF'
+ id: BrickTileSteelLineE
+ decals:
+ 47: 12,0
+ - node:
+ color: '#7A50C7FF'
+ id: BrickTileSteelLineN
+ decals:
+ 41: 5,3
+ 42: 6,3
+ 43: 7,3
+ - node:
+ color: '#7A9CC7FF'
+ id: BrickTileSteelLineN
+ decals:
+ 36: 0,2
+ 37: 1,2
+ - node:
+ color: '#7AA46AFF'
+ id: BrickTileSteelLineN
+ decals:
+ 18: 5,1
+ 19: 6,1
+ 20: 7,1
+ - node:
+ color: '#7A50C7FF'
+ id: BrickTileSteelLineS
+ decals:
+ 44: 5,3
+ 45: 6,3
+ 46: 7,3
+ - node:
+ color: '#7A9CC7FF'
+ id: BrickTileSteelLineS
+ decals:
+ 33: 1,-2
+ 34: 0,-2
+ - node:
+ color: '#7AA46AFF'
+ id: BrickTileSteelLineS
+ decals:
+ 14: 6,-4
+ - node:
+ color: '#7A9CC7FF'
+ id: BrickTileSteelLineW
+ decals:
+ 35: -2,0
+ - node:
+ color: '#7AA46AFF'
+ id: BrickTileSteelLineW
+ decals:
+ 15: 4,-2
+ 16: 4,-1
+ 17: 4,0
+ - node:
+ color: '#B85A23FF'
+ id: BrickTileSteelLineW
+ decals:
+ 57: 11,-1
+ 59: 10,1
+ - node:
+ color: '#57AE23FF'
+ id: BrickTileWhiteBox
+ decals:
+ 64: 6,-5
+ 65: 6,-9
+ 66: 3,0
+ 67: 6,2
+ 68: 9,0
+ - node:
+ color: '#7A9CC7FF'
+ id: BrickTileWhiteBox
+ decals:
+ 38: 0,0
+ - node:
+ color: '#577323FF'
+ id: BrickTileWhiteEndN
+ decals:
+ 60: 6,-6
+ - node:
+ color: '#577323FF'
+ id: BrickTileWhiteEndS
+ decals:
+ 61: 6,-8
+ - node:
+ color: '#577323FF'
+ id: BrickTileWhiteLineE
+ decals:
+ 62: 6,-7
+ - node:
+ color: '#577323FF'
+ id: BrickTileWhiteLineW
+ decals:
+ 63: 6,-7
+ - node:
+ color: '#EFB34196'
+ id: Delivery
+ decals:
+ 0: 8,3
+ - type: GridAtmosphere
+ version: 2
+ data:
+ tiles:
+ -2,1:
+ 0: 8
+ -1,1:
+ 0: 3189
+ -1,0:
+ 1: 2252
+ -1,-1:
+ 1: 51200
+ 0: 49
+ 0,0:
+ 1: 1919
+ 0,1:
+ 0: 28928
+ 0,-1:
+ 1: 30464
+ 0: 1
+ 1,0:
+ 1: 62719
+ 1,-1:
+ 1: 65534
+ 1,1:
+ 1: 61152
+ 1,2:
+ 1: 14
+ 2,0:
+ 1: 7391
+ 2,-1:
+ 1: 39696
+ 3,0:
+ 1: 17
+ 0: 64
+ 3,-1:
+ 1: 4096
+ -2,-1:
+ 0: 17608
+ -1,-2:
+ 0: 61440
+ 0,-2:
+ 0: 61440
+ 1,-3:
+ 1: 16384
+ 1,-2:
+ 1: 17476
+ uniqueMixes:
+ - volume: 2500
+ immutable: True
+ moles:
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - volume: 2500
+ temperature: 293.15
+ moles:
+ - 21.824879
+ - 82.10312
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ chunkSize: 4
+ - type: GasTileOverlay
+ - type: IFF
+ flags: HideLabel
+ - type: RadiationGridResistance
+ - type: BecomesStation
+ id: POIZombie
+- proto: AirAlarm
+ entities:
+ - uid: 307
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 9.5,-0.5
+ parent: 1
+ - type: DeviceList
+ devices:
+ - 303
+ - 305
+ - 306
+ - 304
+ - 311
+ - 310
+ - 308
+ - 309
+- proto: AirlockEngineering
+ entities:
+ - uid: 144
+ components:
+ - type: Transform
+ pos: 9.5,0.5
+ parent: 1
+- proto: AirlockGlassShuttle
+ entities:
+ - uid: 32
+ components:
+ - type: Transform
+ pos: 6.5,-8.5
+ parent: 1
+- proto: AirlockVirology
+ entities:
+ - uid: 200
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 6.5,2.5
+ parent: 1
+- proto: AirlockVirologyGlass
+ entities:
+ - uid: 199
+ components:
+ - type: Transform
+ pos: 6.5,-4.5
+ parent: 1
+ - uid: 207
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 3.5,0.5
+ parent: 1
+- proto: APCBasic
+ entities:
+ - uid: 216
+ components:
+ - type: Transform
+ pos: 12.5,2.5
+ parent: 1
+- proto: APCHighCapacity
+ entities:
+ - uid: 233
+ components:
+ - type: Transform
+ pos: -1.5,2.5
+ parent: 1
+- proto: AtmosDeviceFanDirectional
+ entities:
+ - uid: 461
+ components:
+ - type: Transform
+ pos: 6.5,-8.5
+ parent: 1
+- proto: BlastDoor
+ entities:
+ - uid: 51
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 5.5,4.5
+ parent: 1
+ - uid: 52
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 6.5,4.5
+ parent: 1
+ - uid: 53
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 7.5,4.5
+ parent: 1
+- proto: ButtonFrameCautionSecurity
+ entities:
+ - uid: 58
+ components:
+ - type: Transform
+ pos: 4.5,4.5
+ parent: 1
+- proto: CableApcExtension
+ entities:
+ - uid: 217
+ components:
+ - type: Transform
+ pos: 12.5,2.5
+ parent: 1
+ - uid: 218
+ components:
+ - type: Transform
+ pos: 12.5,1.5
+ parent: 1
+ - uid: 219
+ components:
+ - type: Transform
+ pos: 12.5,0.5
+ parent: 1
+ - uid: 220
+ components:
+ - type: Transform
+ pos: 11.5,0.5
+ parent: 1
+ - uid: 221
+ components:
+ - type: Transform
+ pos: 10.5,0.5
+ parent: 1
+ - uid: 222
+ components:
+ - type: Transform
+ pos: 7.5,0.5
+ parent: 1
+ - uid: 223
+ components:
+ - type: Transform
+ pos: 8.5,0.5
+ parent: 1
+ - uid: 224
+ components:
+ - type: Transform
+ pos: 6.5,0.5
+ parent: 1
+ - uid: 225
+ components:
+ - type: Transform
+ pos: 9.5,0.5
+ parent: 1
+ - uid: 226
+ components:
+ - type: Transform
+ pos: 6.5,1.5
+ parent: 1
+ - uid: 227
+ components:
+ - type: Transform
+ pos: 6.5,2.5
+ parent: 1
+ - uid: 228
+ components:
+ - type: Transform
+ pos: 6.5,3.5
+ parent: 1
+ - uid: 229
+ components:
+ - type: Transform
+ pos: 6.5,4.5
+ parent: 1
+ - uid: 230
+ components:
+ - type: Transform
+ pos: 6.5,5.5
+ parent: 1
+ - uid: 231
+ components:
+ - type: Transform
+ pos: 6.5,6.5
+ parent: 1
+ - uid: 232
+ components:
+ - type: Transform
+ pos: 6.5,7.5
+ parent: 1
+ - uid: 234
+ components:
+ - type: Transform
+ pos: -1.5,2.5
+ parent: 1
+ - uid: 235
+ components:
+ - type: Transform
+ pos: -1.5,1.5
+ parent: 1
+ - uid: 236
+ components:
+ - type: Transform
+ pos: -1.5,0.5
+ parent: 1
+ - uid: 237
+ components:
+ - type: Transform
+ pos: -0.5,0.5
+ parent: 1
+ - uid: 238
+ components:
+ - type: Transform
+ pos: 0.5,0.5
+ parent: 1
+ - uid: 239
+ components:
+ - type: Transform
+ pos: 1.5,0.5
+ parent: 1
+ - uid: 240
+ components:
+ - type: Transform
+ pos: 2.5,0.5
+ parent: 1
+ - uid: 241
+ components:
+ - type: Transform
+ pos: -0.5,2.5
+ parent: 1
+ - uid: 242
+ components:
+ - type: Transform
+ pos: 0.5,2.5
+ parent: 1
+ - uid: 243
+ components:
+ - type: Transform
+ pos: 1.5,2.5
+ parent: 1
+ - uid: 244
+ components:
+ - type: Transform
+ pos: 2.5,2.5
+ parent: 1
+ - uid: 245
+ components:
+ - type: Transform
+ pos: -1.5,-0.5
+ parent: 1
+ - uid: 246
+ components:
+ - type: Transform
+ pos: -1.5,-1.5
+ parent: 1
+ - uid: 247
+ components:
+ - type: Transform
+ pos: -0.5,-1.5
+ parent: 1
+ - uid: 248
+ components:
+ - type: Transform
+ pos: 0.5,-1.5
+ parent: 1
+ - uid: 249
+ components:
+ - type: Transform
+ pos: 1.5,-1.5
+ parent: 1
+ - uid: 250
+ components:
+ - type: Transform
+ pos: 2.5,-1.5
+ parent: 1
+ - uid: 251
+ components:
+ - type: Transform
+ pos: 2.5,-0.5
+ parent: 1
+ - uid: 252
+ components:
+ - type: Transform
+ pos: 2.5,1.5
+ parent: 1
+- proto: CableHV
+ entities:
+ - uid: 60
+ components:
+ - type: Transform
+ pos: 6.5,8.5
+ parent: 1
+ - uid: 61
+ components:
+ - type: Transform
+ pos: 6.5,7.5
+ parent: 1
+ - uid: 62
+ components:
+ - type: Transform
+ pos: 6.5,6.5
+ parent: 1
+ - uid: 63
+ components:
+ - type: Transform
+ pos: 6.5,5.5
+ parent: 1
+ - uid: 64
+ components:
+ - type: Transform
+ pos: 7.5,5.5
+ parent: 1
+ - uid: 65
+ components:
+ - type: Transform
+ pos: 5.5,5.5
+ parent: 1
+ - uid: 67
+ components:
+ - type: Transform
+ pos: 7.5,4.5
+ parent: 1
+ - uid: 68
+ components:
+ - type: Transform
+ pos: 7.5,3.5
+ parent: 1
+ - uid: 69
+ components:
+ - type: Transform
+ pos: 8.5,3.5
+ parent: 1
+ - uid: 132
+ components:
+ - type: Transform
+ pos: 9.5,3.5
+ parent: 1
+ - uid: 133
+ components:
+ - type: Transform
+ pos: 10.5,3.5
+ parent: 1
+ - uid: 134
+ components:
+ - type: Transform
+ pos: 11.5,3.5
+ parent: 1
+ - uid: 135
+ components:
+ - type: Transform
+ pos: 12.5,3.5
+ parent: 1
+ - uid: 136
+ components:
+ - type: Transform
+ pos: 12.5,2.5
+ parent: 1
+ - uid: 137
+ components:
+ - type: Transform
+ pos: 13.5,2.5
+ parent: 1
+ - uid: 138
+ components:
+ - type: Transform
+ pos: 13.5,1.5
+ parent: 1
+ - uid: 139
+ components:
+ - type: Transform
+ pos: 13.5,0.5
+ parent: 1
+ - uid: 145
+ components:
+ - type: Transform
+ pos: 10.5,-2.5
+ parent: 1
+ - uid: 146
+ components:
+ - type: Transform
+ pos: 9.5,-2.5
+ parent: 1
+ - uid: 147
+ components:
+ - type: Transform
+ pos: 9.5,-3.5
+ parent: 1
+ - uid: 148
+ components:
+ - type: Transform
+ pos: 8.5,-3.5
+ parent: 1
+ - uid: 149
+ components:
+ - type: Transform
+ pos: 8.5,-4.5
+ parent: 1
+ - uid: 150
+ components:
+ - type: Transform
+ pos: 7.5,-4.5
+ parent: 1
+ - uid: 151
+ components:
+ - type: Transform
+ pos: 7.5,-5.5
+ parent: 1
+ - uid: 152
+ components:
+ - type: Transform
+ pos: 7.5,-6.5
+ parent: 1
+ - uid: 153
+ components:
+ - type: Transform
+ pos: 7.5,-7.5
+ parent: 1
+ - uid: 154
+ components:
+ - type: Transform
+ pos: 5.5,-7.5
+ parent: 1
+ - uid: 155
+ components:
+ - type: Transform
+ pos: 5.5,-6.5
+ parent: 1
+ - uid: 156
+ components:
+ - type: Transform
+ pos: 5.5,-5.5
+ parent: 1
+ - uid: 157
+ components:
+ - type: Transform
+ pos: 5.5,-4.5
+ parent: 1
+ - uid: 158
+ components:
+ - type: Transform
+ pos: 6.5,-4.5
+ parent: 1
+ - uid: 159
+ components:
+ - type: Transform
+ pos: 4.5,-4.5
+ parent: 1
+ - uid: 160
+ components:
+ - type: Transform
+ pos: 4.5,-3.5
+ parent: 1
+ - uid: 161
+ components:
+ - type: Transform
+ pos: 3.5,-3.5
+ parent: 1
+ - uid: 162
+ components:
+ - type: Transform
+ pos: 3.5,-2.5
+ parent: 1
+ - uid: 163
+ components:
+ - type: Transform
+ pos: 2.5,-2.5
+ parent: 1
+ - uid: 164
+ components:
+ - type: Transform
+ pos: 1.5,-2.5
+ parent: 1
+ - uid: 165
+ components:
+ - type: Transform
+ pos: 0.5,-2.5
+ parent: 1
+ - uid: 166
+ components:
+ - type: Transform
+ pos: -0.5,-2.5
+ parent: 1
+ - uid: 167
+ components:
+ - type: Transform
+ pos: -1.5,-2.5
+ parent: 1
+ - uid: 168
+ components:
+ - type: Transform
+ pos: -1.5,-1.5
+ parent: 1
+ - uid: 169
+ components:
+ - type: Transform
+ pos: -2.5,-1.5
+ parent: 1
+ - uid: 170
+ components:
+ - type: Transform
+ pos: -2.5,-0.5
+ parent: 1
+ - uid: 171
+ components:
+ - type: Transform
+ pos: -2.5,1.5
+ parent: 1
+ - uid: 172
+ components:
+ - type: Transform
+ pos: -2.5,2.5
+ parent: 1
+ - uid: 173
+ components:
+ - type: Transform
+ pos: -2.5,0.5
+ parent: 1
+ - uid: 174
+ components:
+ - type: Transform
+ pos: -1.5,2.5
+ parent: 1
+ - uid: 175
+ components:
+ - type: Transform
+ pos: -1.5,3.5
+ parent: 1
+ - uid: 176
+ components:
+ - type: Transform
+ pos: -0.5,3.5
+ parent: 1
+ - uid: 177
+ components:
+ - type: Transform
+ pos: 0.5,3.5
+ parent: 1
+ - uid: 178
+ components:
+ - type: Transform
+ pos: 1.5,3.5
+ parent: 1
+ - uid: 179
+ components:
+ - type: Transform
+ pos: 2.5,3.5
+ parent: 1
+ - uid: 180
+ components:
+ - type: Transform
+ pos: 3.5,3.5
+ parent: 1
+ - uid: 181
+ components:
+ - type: Transform
+ pos: 3.5,4.5
+ parent: 1
+ - uid: 182
+ components:
+ - type: Transform
+ pos: 4.5,4.5
+ parent: 1
+ - uid: 183
+ components:
+ - type: Transform
+ pos: 5.5,4.5
+ parent: 1
+ - uid: 184
+ components:
+ - type: Transform
+ pos: 6.5,4.5
+ parent: 1
+ - uid: 185
+ components:
+ - type: Transform
+ pos: 7.5,9.5
+ parent: 1
+ - uid: 186
+ components:
+ - type: Transform
+ pos: 6.5,9.5
+ parent: 1
+ - uid: 187
+ components:
+ - type: Transform
+ pos: 5.5,9.5
+ parent: 1
+ - uid: 188
+ components:
+ - type: Transform
+ pos: 4.5,6.5
+ parent: 1
+ - uid: 189
+ components:
+ - type: Transform
+ pos: 4.5,7.5
+ parent: 1
+ - uid: 190
+ components:
+ - type: Transform
+ pos: 4.5,8.5
+ parent: 1
+ - uid: 191
+ components:
+ - type: Transform
+ pos: 8.5,6.5
+ parent: 1
+ - uid: 192
+ components:
+ - type: Transform
+ pos: 8.5,7.5
+ parent: 1
+ - uid: 193
+ components:
+ - type: Transform
+ pos: 8.5,8.5
+ parent: 1
+ - uid: 194
+ components:
+ - type: Transform
+ pos: 8.5,9.5
+ parent: 1
+ - uid: 195
+ components:
+ - type: Transform
+ pos: 4.5,9.5
+ parent: 1
+ - uid: 196
+ components:
+ - type: Transform
+ pos: 4.5,5.5
+ parent: 1
+ - uid: 197
+ components:
+ - type: Transform
+ pos: 8.5,5.5
+ parent: 1
+ - uid: 273
+ components:
+ - type: Transform
+ pos: 11.5,-1.5
+ parent: 1
+ - uid: 274
+ components:
+ - type: Transform
+ pos: 11.5,-0.5
+ parent: 1
+ - uid: 275
+ components:
+ - type: Transform
+ pos: 12.5,-0.5
+ parent: 1
+ - uid: 391
+ components:
+ - type: Transform
+ pos: 5.5,-3.5
+ parent: 1
+ - uid: 405
+ components:
+ - type: Transform
+ pos: 3.5,-4.5
+ parent: 1
+ - uid: 406
+ components:
+ - type: Transform
+ pos: 2.5,-4.5
+ parent: 1
+ - uid: 407
+ components:
+ - type: Transform
+ pos: 1.5,-4.5
+ parent: 1
+ - uid: 408
+ components:
+ - type: Transform
+ pos: 0.5,-4.5
+ parent: 1
+ - uid: 409
+ components:
+ - type: Transform
+ pos: -0.5,-4.5
+ parent: 1
+ - uid: 410
+ components:
+ - type: Transform
+ pos: -2.5,-4.5
+ parent: 1
+ - uid: 411
+ components:
+ - type: Transform
+ pos: -3.5,-4.5
+ parent: 1
+ - uid: 412
+ components:
+ - type: Transform
+ pos: -1.5,-4.5
+ parent: 1
+ - uid: 413
+ components:
+ - type: Transform
+ pos: -3.5,-3.5
+ parent: 1
+ - uid: 414
+ components:
+ - type: Transform
+ pos: -4.5,-3.5
+ parent: 1
+ - uid: 415
+ components:
+ - type: Transform
+ pos: -4.5,-2.5
+ parent: 1
+ - uid: 416
+ components:
+ - type: Transform
+ pos: -5.5,-2.5
+ parent: 1
+ - uid: 417
+ components:
+ - type: Transform
+ pos: -5.5,-1.5
+ parent: 1
+ - uid: 418
+ components:
+ - type: Transform
+ pos: -5.5,-0.5
+ parent: 1
+ - uid: 419
+ components:
+ - type: Transform
+ pos: 0.5,-3.5
+ parent: 1
+ - uid: 420
+ components:
+ - type: Transform
+ pos: -2.5,5.5
+ parent: 1
+ - uid: 421
+ components:
+ - type: Transform
+ pos: -1.5,4.5
+ parent: 1
+ - uid: 422
+ components:
+ - type: Transform
+ pos: -1.5,5.5
+ parent: 1
+ - uid: 423
+ components:
+ - type: Transform
+ pos: -1.5,6.5
+ parent: 1
+ - uid: 424
+ components:
+ - type: Transform
+ pos: -3.5,5.5
+ parent: 1
+ - uid: 425
+ components:
+ - type: Transform
+ pos: -3.5,4.5
+ parent: 1
+ - uid: 426
+ components:
+ - type: Transform
+ pos: -4.5,4.5
+ parent: 1
+ - uid: 427
+ components:
+ - type: Transform
+ pos: 0.5,7.5
+ parent: 1
+ - uid: 428
+ components:
+ - type: Transform
+ pos: -0.5,6.5
+ parent: 1
+ - uid: 429
+ components:
+ - type: Transform
+ pos: 0.5,6.5
+ parent: 1
+ - uid: 430
+ components:
+ - type: Transform
+ pos: 1.5,7.5
+ parent: 1
+ - uid: 431
+ components:
+ - type: Transform
+ pos: 2.5,7.5
+ parent: 1
+- proto: CableMV
+ entities:
+ - uid: 253
+ components:
+ - type: Transform
+ pos: 12.5,2.5
+ parent: 1
+ - uid: 254
+ components:
+ - type: Transform
+ pos: 12.5,0.5
+ parent: 1
+ - uid: 255
+ components:
+ - type: Transform
+ pos: 12.5,-0.5
+ parent: 1
+ - uid: 256
+ components:
+ - type: Transform
+ pos: 12.5,1.5
+ parent: 1
+ - uid: 257
+ components:
+ - type: Transform
+ pos: 1.5,0.5
+ parent: 1
+ - uid: 258
+ components:
+ - type: Transform
+ pos: 0.5,0.5
+ parent: 1
+ - uid: 259
+ components:
+ - type: Transform
+ pos: -1.5,0.5
+ parent: 1
+ - uid: 260
+ components:
+ - type: Transform
+ pos: -1.5,1.5
+ parent: 1
+ - uid: 261
+ components:
+ - type: Transform
+ pos: 6.5,0.5
+ parent: 1
+ - uid: 262
+ components:
+ - type: Transform
+ pos: -0.5,0.5
+ parent: 1
+ - uid: 263
+ components:
+ - type: Transform
+ pos: 11.5,0.5
+ parent: 1
+ - uid: 264
+ components:
+ - type: Transform
+ pos: 10.5,0.5
+ parent: 1
+ - uid: 265
+ components:
+ - type: Transform
+ pos: 9.5,0.5
+ parent: 1
+ - uid: 266
+ components:
+ - type: Transform
+ pos: 8.5,0.5
+ parent: 1
+ - uid: 267
+ components:
+ - type: Transform
+ pos: 7.5,0.5
+ parent: 1
+ - uid: 268
+ components:
+ - type: Transform
+ pos: 5.5,0.5
+ parent: 1
+ - uid: 269
+ components:
+ - type: Transform
+ pos: 4.5,0.5
+ parent: 1
+ - uid: 270
+ components:
+ - type: Transform
+ pos: 3.5,0.5
+ parent: 1
+ - uid: 271
+ components:
+ - type: Transform
+ pos: 2.5,0.5
+ parent: 1
+ - uid: 272
+ components:
+ - type: Transform
+ pos: -1.5,2.5
+ parent: 1
+- proto: ChemistryBottleNecropporozidonosol
+ entities:
+ - uid: 369
+ components:
+ - type: Transform
+ pos: -1.6311691,1.6797215
+ parent: 1
+- proto: ClosetWallO2N2Filled
+ entities:
+ - uid: 363
+ components:
+ - type: Transform
+ pos: 5.5,2.5
+ parent: 1
+- proto: ClothingBackpackDuffelSurgeryAdvancedFilled
+ entities:
+ - uid: 345
+ components:
+ - type: Transform
+ pos: 1.4973125,2.6104722
+ parent: 1
+ - type: Visibility
+- proto: ClothingHeadHatSurgcapBlue
+ entities:
+ - uid: 355
+ components:
+ - type: Transform
+ pos: 0.46594512,2.7823472
+ parent: 1
+ - uid: 356
+ components:
+ - type: Transform
+ pos: 0.46594512,2.6573472
+ parent: 1
+ - uid: 357
+ components:
+ - type: Transform
+ pos: 0.46594512,2.4698472
+ parent: 1
+- proto: ClothingHeadHatSurgcapGreen
+ entities:
+ - uid: 352
+ components:
+ - type: Transform
+ pos: -0.065304875,2.7823472
+ parent: 1
+ - uid: 353
+ components:
+ - type: Transform
+ pos: -0.065304875,2.6417222
+ parent: 1
+ - uid: 354
+ components:
+ - type: Transform
+ pos: -0.049679875,2.4229722
+ parent: 1
+- proto: ClothingHeadHatSurgcapPurple
+ entities:
+ - uid: 349
+ components:
+ - type: Transform
+ pos: -0.5653049,2.7354722
+ parent: 1
+ - uid: 350
+ components:
+ - type: Transform
+ pos: -0.5653049,2.5792222
+ parent: 1
+ - uid: 351
+ components:
+ - type: Transform
+ pos: -0.5653049,2.3604722
+ parent: 1
+- proto: ComputerSolarControl
+ entities:
+ - uid: 390
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 5.5,-3.5
+ parent: 1
+ - type: ApcPowerReceiver
+ powerLoad: 5
+ - type: Battery
+ startingCharge: 0
+- proto: CurtainsBlack
+ entities:
+ - uid: 449
+ components:
+ - type: Transform
+ pos: 4.5,8.5
+ parent: 1
+ - uid: 450
+ components:
+ - type: Transform
+ pos: 4.5,6.5
+ parent: 1
+ - uid: 451
+ components:
+ - type: Transform
+ pos: 4.5,7.5
+ parent: 1
+ - uid: 452
+ components:
+ - type: Transform
+ pos: 5.5,9.5
+ parent: 1
+ - uid: 453
+ components:
+ - type: Transform
+ pos: 6.5,9.5
+ parent: 1
+ - uid: 454
+ components:
+ - type: Transform
+ pos: 7.5,9.5
+ parent: 1
+ - uid: 455
+ components:
+ - type: Transform
+ pos: 8.5,8.5
+ parent: 1
+ - uid: 456
+ components:
+ - type: Transform
+ pos: 8.5,6.5
+ parent: 1
+ - uid: 457
+ components:
+ - type: Transform
+ pos: 8.5,7.5
+ parent: 1
+- proto: DefibrillatorCabinetFilled
+ entities:
+ - uid: 447
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: -1.5,-1.5
+ parent: 1
+- proto: FaxMachineShip
+ entities:
+ - uid: 446
+ components:
+ - type: Transform
+ pos: 5.5,-1.5
+ parent: 1
+- proto: FenceMetalCorner
+ entities:
+ - uid: 18
+ components:
+ - type: Transform
+ pos: 6.5,5.5
+ parent: 1
+- proto: FenceMetalGate
+ entities:
+ - uid: 16
+ components:
+ - type: Transform
+ pos: 5.5,5.5
+ parent: 1
+ - uid: 17
+ components:
+ - type: Transform
+ pos: 7.5,5.5
+ parent: 1
+- proto: FenceMetalStraight
+ entities:
+ - uid: 19
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 6.5,5.5
+ parent: 1
+ - uid: 20
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 6.5,6.5
+ parent: 1
+ - uid: 21
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 6.5,7.5
+ parent: 1
+ - uid: 22
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 6.5,8.5
+ parent: 1
+- proto: FloorDrain
+ entities:
+ - uid: 365
+ components:
+ - type: Transform
+ pos: 0.5,0.5
+ parent: 1
+ - type: Fixtures
+ fixtures: {}
+- proto: FoodMothMothmallow
+ entities:
+ - uid: 302
+ components:
+ - type: Transform
+ pos: 5.518734,-0.3768854
+ parent: 1
+- proto: GasMixerOn
+ entities:
+ - uid: 291
+ components:
+ - type: Transform
+ pos: 11.5,1.5
+ parent: 1
+ - type: GasMixer
+ inletTwoConcentration: 0.29000002
+ inletOneConcentration: 0.71
+- proto: GasPassiveVent
+ entities:
+ - uid: 312
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 14.5,1.5
+ parent: 1
+ - uid: 314
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 3.5,-4.5
+ parent: 1
+- proto: GasPipeBend
+ entities:
+ - uid: 292
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 10.5,1.5
+ parent: 1
+ - uid: 315
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: 3.5,-3.5
+ parent: 1
+ - uid: 316
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 4.5,-3.5
+ parent: 1
+ - uid: 326
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 5.5,-0.5
+ parent: 1
+ - uid: 333
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 1.5,0.5
+ parent: 1
+ - uid: 334
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: 1.5,1.5
+ parent: 1
+ - uid: 338
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: 4.5,3.5
+ parent: 1
+ - uid: 339
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: 6.5,3.5
+ parent: 1
+- proto: GasPipeFourway
+ entities:
+ - uid: 327
+ components:
+ - type: Transform
+ pos: 4.5,-0.5
+ parent: 1
+- proto: GasPipeStraight
+ entities:
+ - uid: 313
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 13.5,1.5
+ parent: 1
+ - uid: 317
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 4.5,-2.5
+ parent: 1
+ - uid: 318
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: 8.5,0.5
+ parent: 1
+ - uid: 319
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: 9.5,0.5
+ parent: 1
+ - uid: 320
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: 10.5,0.5
+ parent: 1
+ - uid: 324
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 4.5,2.5
+ parent: 1
+ - uid: 325
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 4.5,1.5
+ parent: 1
+ - uid: 328
+ components:
+ - type: Transform
+ pos: 4.5,-1.5
+ parent: 1
+ - uid: 329
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 5.5,0.5
+ parent: 1
+ - uid: 330
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 4.5,0.5
+ parent: 1
+ - uid: 331
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 3.5,0.5
+ parent: 1
+ - uid: 332
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 2.5,0.5
+ parent: 1
+ - uid: 335
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: 3.5,-0.5
+ parent: 1
+ - uid: 336
+ components:
+ - type: Transform
+ pos: 4.5,0.5
+ parent: 1
+ - uid: 337
+ components:
+ - type: Transform
+ pos: 5.5,0.5
+ parent: 1
+ - uid: 340
+ components:
+ - type: Transform
+ pos: 6.5,2.5
+ parent: 1
+ - uid: 341
+ components:
+ - type: Transform
+ pos: 6.5,1.5
+ parent: 1
+- proto: GasPipeTJunction
+ entities:
+ - uid: 321
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 11.5,0.5
+ parent: 1
+ - uid: 322
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 7.5,0.5
+ parent: 1
+ - uid: 323
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 6.5,0.5
+ parent: 1
+- proto: GasPort
+ entities:
+ - uid: 289
+ components:
+ - type: Transform
+ pos: 10.5,2.5
+ parent: 1
+ - uid: 290
+ components:
+ - type: Transform
+ pos: 11.5,2.5
+ parent: 1
+- proto: GasVentPump
+ entities:
+ - uid: 303
+ components:
+ - type: Transform
+ pos: 7.5,1.5
+ parent: 1
+ - type: DeviceNetwork
+ deviceLists:
+ - 307
+ - uid: 304
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 2.5,1.5
+ parent: 1
+ - type: DeviceNetwork
+ deviceLists:
+ - 307
+ - uid: 305
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 7.5,3.5
+ parent: 1
+ - type: DeviceNetwork
+ deviceLists:
+ - 307
+ - uid: 306
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 12.5,0.5
+ parent: 1
+ - type: DeviceNetwork
+ deviceLists:
+ - 307
+- proto: GasVentScrubber
+ entities:
+ - uid: 308
+ components:
+ - type: Transform
+ pos: 5.5,1.5
+ parent: 1
+ - type: DeviceNetwork
+ deviceLists:
+ - 307
+ - uid: 309
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: 2.5,-0.5
+ parent: 1
+ - type: DeviceNetwork
+ deviceLists:
+ - 307
+ - uid: 310
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 5.5,3.5
+ parent: 1
+ - type: DeviceNetwork
+ deviceLists:
+ - 307
+ - uid: 311
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: 12.5,1.5
+ parent: 1
+ - type: DeviceNetwork
+ deviceLists:
+ - 307
+- proto: GeneratorRTG
+ entities:
+ - uid: 66
+ components:
+ - type: Transform
+ pos: 8.5,3.5
+ parent: 1
+- proto: GravityGeneratorMini
+ entities:
+ - uid: 444
+ components:
+ - type: Transform
+ pos: 12.5,0.5
+ parent: 1
+- proto: Grille
+ entities:
+ - uid: 23
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 4.5,6.5
+ parent: 1
+ - uid: 24
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 4.5,7.5
+ parent: 1
+ - uid: 25
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 4.5,8.5
+ parent: 1
+ - uid: 26
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 5.5,9.5
+ parent: 1
+ - uid: 27
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 6.5,9.5
+ parent: 1
+ - uid: 28
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 7.5,9.5
+ parent: 1
+ - uid: 29
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 8.5,8.5
+ parent: 1
+ - uid: 30
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 8.5,7.5
+ parent: 1
+ - uid: 31
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 8.5,6.5
+ parent: 1
+ - uid: 37
+ components:
+ - type: Transform
+ pos: 5.5,-7.5
+ parent: 1
+ - uid: 104
+ components:
+ - type: Transform
+ pos: 13.5,-0.5
+ parent: 1
+ - uid: 105
+ components:
+ - type: Transform
+ pos: 13.5,0.5
+ parent: 1
+ - uid: 106
+ components:
+ - type: Transform
+ pos: 13.5,1.5
+ parent: 1
+ - uid: 107
+ components:
+ - type: Transform
+ pos: 11.5,3.5
+ parent: 1
+ - uid: 108
+ components:
+ - type: Transform
+ pos: 11.5,-2.5
+ parent: 1
+ - uid: 109
+ components:
+ - type: Transform
+ pos: 9.5,-2.5
+ parent: 1
+ - uid: 110
+ components:
+ - type: Transform
+ pos: 9.5,-3.5
+ parent: 1
+ - uid: 111
+ components:
+ - type: Transform
+ pos: 8.5,-3.5
+ parent: 1
+ - uid: 112
+ components:
+ - type: Transform
+ pos: 8.5,-4.5
+ parent: 1
+ - uid: 113
+ components:
+ - type: Transform
+ pos: 4.5,-4.5
+ parent: 1
+ - uid: 114
+ components:
+ - type: Transform
+ pos: 4.5,-3.5
+ parent: 1
+ - uid: 115
+ components:
+ - type: Transform
+ pos: 3.5,-3.5
+ parent: 1
+ - uid: 116
+ components:
+ - type: Transform
+ pos: 3.5,-2.5
+ parent: 1
+ - uid: 117
+ components:
+ - type: Transform
+ pos: 1.5,-2.5
+ parent: 1
+ - uid: 118
+ components:
+ - type: Transform
+ pos: 0.5,-2.5
+ parent: 1
+ - uid: 119
+ components:
+ - type: Transform
+ pos: -0.5,-2.5
+ parent: 1
+ - uid: 120
+ components:
+ - type: Transform
+ pos: -2.5,-0.5
+ parent: 1
+ - uid: 121
+ components:
+ - type: Transform
+ pos: -2.5,0.5
+ parent: 1
+ - uid: 122
+ components:
+ - type: Transform
+ pos: -2.5,1.5
+ parent: 1
+ - uid: 123
+ components:
+ - type: Transform
+ pos: -0.5,3.5
+ parent: 1
+ - uid: 124
+ components:
+ - type: Transform
+ pos: 0.5,3.5
+ parent: 1
+ - uid: 125
+ components:
+ - type: Transform
+ pos: 1.5,3.5
+ parent: 1
+ - uid: 126
+ components:
+ - type: Transform
+ pos: 5.5,-6.5
+ parent: 1
+ - uid: 127
+ components:
+ - type: Transform
+ pos: 5.5,-5.5
+ parent: 1
+ - uid: 128
+ components:
+ - type: Transform
+ pos: 7.5,-7.5
+ parent: 1
+ - uid: 129
+ components:
+ - type: Transform
+ pos: 7.5,-6.5
+ parent: 1
+ - uid: 130
+ components:
+ - type: Transform
+ pos: 7.5,-5.5
+ parent: 1
+ - uid: 208
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 3.5,1.5
+ parent: 1
+ - uid: 209
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 3.5,-0.5
+ parent: 1
+ - uid: 210
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 3.5,-1.5
+ parent: 1
+- proto: hydroponicsTrayAnchored
+ entities:
+ - uid: 301
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 9.5,-1.5
+ parent: 1
+- proto: Left4ZedChemistryBottle
+ entities:
+ - uid: 370
+ components:
+ - type: Transform
+ pos: -1.4905441,1.4609715
+ parent: 1
+- proto: LockerMedicalFilled
+ entities:
+ - uid: 448
+ components:
+ - type: Transform
+ pos: 1.5,-1.5
+ parent: 1
+- proto: LockerWallMaterialsFuelPlasmaFilled2
+ entities:
+ - uid: 295
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 10.5,-0.5
+ parent: 1
+- proto: NFHolopadShip
+ entities:
+ - uid: 445
+ components:
+ - type: Transform
+ pos: 0.5,-1.5
+ parent: 1
+- proto: NitrogenCanister
+ entities:
+ - uid: 294
+ components:
+ - type: Transform
+ anchored: True
+ pos: 11.5,2.5
+ parent: 1
+ - type: Physics
+ bodyType: Static
+- proto: OmnizineChemistryBottle
+ entities:
+ - uid: 374
+ components:
+ - type: Transform
+ pos: -1.6660676,1.3515965
+ parent: 1
+- proto: OperatingTable
+ entities:
+ - uid: 364
+ components:
+ - type: Transform
+ pos: 0.5,0.5
+ parent: 1
+ - type: ContainerContainer
+ containers:
+ machine_board: !type:Container
+ ents: []
+ machine_parts: !type:Container
+ ents: []
+- proto: OxygenCanister
+ entities:
+ - uid: 293
+ components:
+ - type: Transform
+ anchored: True
+ pos: 10.5,2.5
+ parent: 1
+ - type: Physics
+ bodyType: Static
+- proto: Pill
+ entities:
+ - uid: 378
+ components:
+ - type: Transform
+ pos: -1.2050694,1.2099655
+ parent: 1
+- proto: PillAmbuzol
+ entities:
+ - uid: 376
+ components:
+ - type: Transform
+ pos: 7.008634,8.199874
+ parent: 1
+ - uid: 377
+ components:
+ - type: Transform
+ pos: -1.7710137,0.10927528
+ parent: 1
+- proto: PillCanisterRandom
+ entities:
+ - uid: 388
+ components:
+ - type: Transform
+ pos: -1.8557057,-0.38624913
+ parent: 1
+ - uid: 389
+ components:
+ - type: Transform
+ pos: -1.1213307,0.5512509
+ parent: 1
+- proto: PillCanisterTricordrazine
+ entities:
+ - uid: 371
+ components:
+ - type: Transform
+ pos: -1.2561691,1.7890965
+ parent: 1
+- proto: PillCharcoal
+ entities:
+ - uid: 379
+ components:
+ - type: Transform
+ pos: -1.3750893,0.10059047
+ parent: 1
+ - uid: 380
+ components:
+ - type: Transform
+ pos: -1.2500893,0.11621547
+ parent: 1
+ - uid: 381
+ components:
+ - type: Transform
+ pos: -1.3125893,0.20996547
+ parent: 1
+- proto: PillDylovene
+ entities:
+ - uid: 282
+ components:
+ - type: Transform
+ pos: 5.4471984,6.3821745
+ parent: 1
+ - uid: 382
+ components:
+ - type: Transform
+ pos: 0.986974,2.4480505
+ parent: 1
+ - uid: 383
+ components:
+ - type: Transform
+ pos: 0.877599,2.4480505
+ parent: 1
+ - uid: 384
+ components:
+ - type: Transform
+ pos: 0.955724,2.5886755
+ parent: 1
+ - uid: 386
+ components:
+ - type: Transform
+ pos: 7.6289797,8.7727995
+ parent: 1
+- proto: PillPotassiumIodide
+ entities:
+ - uid: 387
+ components:
+ - type: Transform
+ pos: 6.8321047,6.8196745
+ parent: 1
+- proto: PillRomerol
+ entities:
+ - uid: 375
+ components:
+ - type: MetaData
+ desc: This is not a suppository. You REALLY shouldn't eat this, given where you found it.
+ name: suspicious pill
+ - type: Transform
+ pos: 5.866243,8.723654
+ parent: 1
+- proto: PlastitaniumWindow
+ entities:
+ - uid: 7
+ components:
+ - type: Transform
+ pos: 4.5,6.5
+ parent: 1
+ - uid: 8
+ components:
+ - type: Transform
+ pos: 4.5,7.5
+ parent: 1
+ - uid: 9
+ components:
+ - type: Transform
+ pos: 4.5,8.5
+ parent: 1
+ - uid: 10
+ components:
+ - type: Transform
+ pos: 5.5,9.5
+ parent: 1
+ - uid: 11
+ components:
+ - type: Transform
+ pos: 7.5,9.5
+ parent: 1
+ - uid: 12
+ components:
+ - type: Transform
+ pos: 6.5,9.5
+ parent: 1
+ - uid: 13
+ components:
+ - type: Transform
+ pos: 8.5,8.5
+ parent: 1
+ - uid: 14
+ components:
+ - type: Transform
+ pos: 8.5,7.5
+ parent: 1
+ - uid: 15
+ components:
+ - type: Transform
+ pos: 8.5,6.5
+ parent: 1
+- proto: PortableGeneratorPacmanShuttle
+ entities:
+ - uid: 214
+ components:
+ - type: Transform
+ pos: 11.5,-1.5
+ parent: 1
+- proto: Poweredlight
+ entities:
+ - uid: 278
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: 11.5,-0.5
+ parent: 1
+ - uid: 342
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 10.5,0.5
+ parent: 1
+ - uid: 343
+ components:
+ - type: Transform
+ pos: 12.5,1.5
+ parent: 1
+ - uid: 344
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 11.5,2.5
+ parent: 1
+- proto: PoweredlightLED
+ entities:
+ - uid: 131
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: -1.5,-0.5
+ parent: 1
+ - uid: 279
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 2.5,2.5
+ parent: 1
+ - uid: 280
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: -0.5,2.5
+ parent: 1
+ - uid: 281
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: -0.5,-1.5
+ parent: 1
+ - uid: 283
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 2.5,-1.5
+ parent: 1
+ - uid: 284
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 5.5,-3.5
+ parent: 1
+ - uid: 285
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 7.5,-3.5
+ parent: 1
+ - uid: 286
+ components:
+ - type: Transform
+ pos: 5.5,1.5
+ parent: 1
+ - uid: 287
+ components:
+ - type: Transform
+ pos: 7.5,1.5
+ parent: 1
+ - uid: 288
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 8.5,-0.5
+ parent: 1
+- proto: PoweredlightSodium
+ entities:
+ - uid: 276
+ components:
+ - type: Transform
+ rot: -1.5707963267948966 rad
+ pos: 8.5,3.5
+ parent: 1
+ - uid: 277
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: 4.5,3.5
+ parent: 1
+- proto: PoweredSmallLight
+ entities:
+ - uid: 443
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: 6.5,-6.5
+ parent: 1
+- proto: PoweredStrobeRed
+ entities:
+ - uid: 198
+ components:
+ - type: Transform
+ pos: 6.5,8.5
+ parent: 1
+- proto: ShuttleWindow
+ entities:
+ - uid: 38
+ components:
+ - type: Transform
+ pos: 7.5,-6.5
+ parent: 1
+ - uid: 46
+ components:
+ - type: Transform
+ pos: 7.5,-7.5
+ parent: 1
+ - uid: 72
+ components:
+ - type: Transform
+ pos: 7.5,-5.5
+ parent: 1
+ - uid: 73
+ components:
+ - type: Transform
+ pos: 5.5,-7.5
+ parent: 1
+ - uid: 74
+ components:
+ - type: Transform
+ pos: 5.5,-6.5
+ parent: 1
+ - uid: 75
+ components:
+ - type: Transform
+ pos: 5.5,-5.5
+ parent: 1
+ - uid: 76
+ components:
+ - type: Transform
+ pos: 8.5,-4.5
+ parent: 1
+ - uid: 77
+ components:
+ - type: Transform
+ pos: 8.5,-3.5
+ parent: 1
+ - uid: 78
+ components:
+ - type: Transform
+ pos: 9.5,-3.5
+ parent: 1
+ - uid: 79
+ components:
+ - type: Transform
+ pos: 9.5,-2.5
+ parent: 1
+ - uid: 80
+ components:
+ - type: Transform
+ pos: 4.5,-4.5
+ parent: 1
+ - uid: 81
+ components:
+ - type: Transform
+ pos: 4.5,-3.5
+ parent: 1
+ - uid: 82
+ components:
+ - type: Transform
+ pos: 3.5,-3.5
+ parent: 1
+ - uid: 83
+ components:
+ - type: Transform
+ pos: 3.5,-2.5
+ parent: 1
+ - uid: 84
+ components:
+ - type: Transform
+ pos: 11.5,-2.5
+ parent: 1
+ - uid: 85
+ components:
+ - type: Transform
+ pos: 13.5,-0.5
+ parent: 1
+ - uid: 86
+ components:
+ - type: Transform
+ pos: 13.5,1.5
+ parent: 1
+ - uid: 87
+ components:
+ - type: Transform
+ pos: 13.5,0.5
+ parent: 1
+ - uid: 88
+ components:
+ - type: Transform
+ pos: 11.5,3.5
+ parent: 1
+ - uid: 89
+ components:
+ - type: Transform
+ pos: -0.5,3.5
+ parent: 1
+ - uid: 90
+ components:
+ - type: Transform
+ pos: 0.5,3.5
+ parent: 1
+ - uid: 91
+ components:
+ - type: Transform
+ pos: 1.5,3.5
+ parent: 1
+ - uid: 92
+ components:
+ - type: Transform
+ pos: 1.5,-2.5
+ parent: 1
+ - uid: 93
+ components:
+ - type: Transform
+ pos: 0.5,-2.5
+ parent: 1
+ - uid: 94
+ components:
+ - type: Transform
+ pos: -0.5,-2.5
+ parent: 1
+ - uid: 95
+ components:
+ - type: Transform
+ pos: -2.5,-0.5
+ parent: 1
+ - uid: 96
+ components:
+ - type: Transform
+ pos: -2.5,1.5
+ parent: 1
+ - uid: 97
+ components:
+ - type: Transform
+ pos: -2.5,0.5
+ parent: 1
+ - uid: 211
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 3.5,1.5
+ parent: 1
+ - uid: 212
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 3.5,-0.5
+ parent: 1
+ - uid: 213
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 3.5,-1.5
+ parent: 1
+- proto: SignalButtonDirectional
+ entities:
+ - uid: 59
+ components:
+ - type: Transform
+ pos: 4.5,4.5
+ parent: 1
+ - type: DeviceLinkSource
+ linkedPorts:
+ 51:
+ - - Pressed
+ - Toggle
+ 52:
+ - - Pressed
+ - Toggle
+ 53:
+ - - Pressed
+ - Toggle
+ 56:
+ - - Pressed
+ - Trigger
+ 57:
+ - - Pressed
+ - Trigger
+- proto: SignalSpawnerZombieInfectiouslos
+ entities:
+ - uid: 56
+ components:
+ - type: Transform
+ pos: 5.5,7.5
+ parent: 1
+ - type: DeviceLinkSink
+ ports:
+ - Trigger
+ - uid: 57
+ components:
+ - type: Transform
+ pos: 7.5,7.5
+ parent: 1
+ - type: DeviceLinkSink
+ ports:
+ - Trigger
+- proto: SignDanger
+ entities:
+ - uid: 297
+ components:
+ - type: Transform
+ pos: 8.5,4.5
+ parent: 1
+- proto: SignZomlab
+ entities:
+ - uid: 298
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 7.5,2.5
+ parent: 1
+- proto: SinkWide
+ entities:
+ - uid: 460
+ components:
+ - type: Transform
+ rot: 1.5707963267948966 rad
+ pos: -0.5,-1.5
+ parent: 1
+- proto: SolarPanel
+ entities:
+ - uid: 385
+ components:
+ - type: Transform
+ pos: -3.5,-3.5
+ parent: 1
+ - uid: 392
+ components:
+ - type: Transform
+ pos: 2.5,-4.5
+ parent: 1
+ - uid: 393
+ components:
+ - type: Transform
+ pos: 1.5,-4.5
+ parent: 1
+ - uid: 394
+ components:
+ - type: Transform
+ pos: 0.5,-4.5
+ parent: 1
+ - uid: 395
+ components:
+ - type: Transform
+ pos: -0.5,-4.5
+ parent: 1
+ - uid: 396
+ components:
+ - type: Transform
+ pos: -1.5,-4.5
+ parent: 1
+ - uid: 397
+ components:
+ - type: Transform
+ pos: -2.5,-4.5
+ parent: 1
+ - uid: 398
+ components:
+ - type: Transform
+ pos: -3.5,-4.5
+ parent: 1
+ - uid: 399
+ components:
+ - type: Transform
+ pos: -4.5,-3.5
+ parent: 1
+ - uid: 400
+ components:
+ - type: Transform
+ pos: -4.5,-2.5
+ parent: 1
+ - uid: 401
+ components:
+ - type: Transform
+ pos: -5.5,-2.5
+ parent: 1
+ - uid: 402
+ components:
+ - type: Transform
+ pos: -5.5,-0.5
+ parent: 1
+ - uid: 403
+ components:
+ - type: Transform
+ pos: -5.5,-1.5
+ parent: 1
+ - uid: 432
+ components:
+ - type: Transform
+ pos: -4.5,4.5
+ parent: 1
+ - uid: 433
+ components:
+ - type: Transform
+ pos: -3.5,4.5
+ parent: 1
+ - uid: 434
+ components:
+ - type: Transform
+ pos: -3.5,5.5
+ parent: 1
+ - uid: 435
+ components:
+ - type: Transform
+ pos: -2.5,5.5
+ parent: 1
+ - uid: 436
+ components:
+ - type: Transform
+ pos: -1.5,5.5
+ parent: 1
+ - uid: 437
+ components:
+ - type: Transform
+ pos: -1.5,6.5
+ parent: 1
+ - uid: 438
+ components:
+ - type: Transform
+ pos: -0.5,6.5
+ parent: 1
+ - uid: 439
+ components:
+ - type: Transform
+ pos: 0.5,6.5
+ parent: 1
+ - uid: 440
+ components:
+ - type: Transform
+ pos: 0.5,7.5
+ parent: 1
+ - uid: 441
+ components:
+ - type: Transform
+ pos: 1.5,7.5
+ parent: 1
+ - uid: 442
+ components:
+ - type: Transform
+ pos: 2.5,7.5
+ parent: 1
+- proto: SolarTracker
+ entities:
+ - uid: 404
+ components:
+ - type: Transform
+ pos: 0.5,-3.5
+ parent: 1
+- proto: SpawnDungeonClutterSyringe
+ entities:
+ - uid: 372
+ components:
+ - type: Transform
+ pos: -1.5217941,0.5859715
+ parent: 1
+ - uid: 373
+ components:
+ - type: Transform
+ pos: -1.5098176,-0.36715353
+ parent: 1
+- proto: SubstationBasic
+ entities:
+ - uid: 215
+ components:
+ - type: Transform
+ pos: 12.5,-0.5
+ parent: 1
+- proto: TableGlass
+ entities:
+ - uid: 299
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 5.5,-1.5
+ parent: 1
+ - uid: 300
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 5.5,-0.5
+ parent: 1
+ - uid: 346
+ components:
+ - type: Transform
+ pos: -0.5,2.5
+ parent: 1
+ - uid: 347
+ components:
+ - type: Transform
+ pos: 0.5,2.5
+ parent: 1
+ - uid: 348
+ components:
+ - type: Transform
+ pos: 1.5,2.5
+ parent: 1
+ - uid: 366
+ components:
+ - type: Transform
+ pos: -1.5,-0.5
+ parent: 1
+ - uid: 367
+ components:
+ - type: Transform
+ pos: -1.5,0.5
+ parent: 1
+ - uid: 368
+ components:
+ - type: Transform
+ pos: -1.5,1.5
+ parent: 1
+- proto: VendingMachineCigsPOI
+ entities:
+ - uid: 361
+ components:
+ - type: Transform
+ pos: 2.5,-1.5
+ parent: 1
+- proto: VendingMachineCiviMedPlus
+ entities:
+ - uid: 358
+ components:
+ - type: Transform
+ pos: 2.5,2.5
+ parent: 1
+- proto: VendingMachineNutri
+ entities:
+ - uid: 360
+ components:
+ - type: Transform
+ pos: 4.5,-2.5
+ parent: 1
+- proto: VendingMachineSeeds
+ entities:
+ - uid: 359
+ components:
+ - type: Transform
+ pos: 8.5,-2.5
+ parent: 1
+- proto: VendingMachineSustenance
+ entities:
+ - uid: 296
+ components:
+ - type: Transform
+ pos: 8.5,1.5
+ parent: 1
+- proto: VendingMachineViroDrobe
+ entities:
+ - uid: 362
+ components:
+ - type: Transform
+ pos: 4.5,1.5
+ parent: 1
+- proto: WallPlastitanium
+ entities:
+ - uid: 3
+ components:
+ - type: Transform
+ pos: 4.5,5.5
+ parent: 1
+ - uid: 4
+ components:
+ - type: Transform
+ pos: 4.5,9.5
+ parent: 1
+ - uid: 5
+ components:
+ - type: Transform
+ pos: 8.5,9.5
+ parent: 1
+ - uid: 6
+ components:
+ - type: Transform
+ pos: 8.5,5.5
+ parent: 1
+- proto: WallReinforced
+ entities:
+ - uid: 33
+ components:
+ - type: Transform
+ pos: 5.5,-8.5
+ parent: 1
+ - uid: 34
+ components:
+ - type: Transform
+ pos: 7.5,-8.5
+ parent: 1
+- proto: WallShuttle
+ entities:
+ - uid: 2
+ components:
+ - type: Transform
+ pos: 13.5,-1.5
+ parent: 1
+ - uid: 35
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 5.5,-4.5
+ parent: 1
+ - uid: 36
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 7.5,-4.5
+ parent: 1
+ - uid: 39
+ components:
+ - type: Transform
+ pos: -1.5,3.5
+ parent: 1
+ - uid: 40
+ components:
+ - type: Transform
+ pos: -2.5,2.5
+ parent: 1
+ - uid: 41
+ components:
+ - type: Transform
+ pos: -2.5,-1.5
+ parent: 1
+ - uid: 42
+ components:
+ - type: Transform
+ pos: -1.5,-2.5
+ parent: 1
+ - uid: 43
+ components:
+ - type: Transform
+ pos: 8.5,4.5
+ parent: 1
+ - uid: 44
+ components:
+ - type: Transform
+ pos: 3.5,4.5
+ parent: 1
+ - uid: 45
+ components:
+ - type: Transform
+ pos: 13.5,2.5
+ parent: 1
+ - uid: 47
+ components:
+ - type: Transform
+ pos: 12.5,3.5
+ parent: 1
+ - uid: 48
+ components:
+ - type: Transform
+ pos: 10.5,3.5
+ parent: 1
+ - uid: 49
+ components:
+ - type: Transform
+ pos: 10.5,-2.5
+ parent: 1
+ - uid: 50
+ components:
+ - type: Transform
+ pos: 12.5,-2.5
+ parent: 1
+ - uid: 54
+ components:
+ - type: Transform
+ pos: 9.5,4.5
+ parent: 1
+ - uid: 55
+ components:
+ - type: Transform
+ pos: 4.5,4.5
+ parent: 1
+ - uid: 70
+ components:
+ - type: Transform
+ pos: 2.5,3.5
+ parent: 1
+ - uid: 71
+ components:
+ - type: Transform
+ pos: 2.5,-2.5
+ parent: 1
+ - uid: 98
+ components:
+ - type: Transform
+ pos: -1.5,2.5
+ parent: 1
+ - uid: 99
+ components:
+ - type: Transform
+ pos: -1.5,-1.5
+ parent: 1
+ - uid: 100
+ components:
+ - type: Transform
+ pos: 12.5,2.5
+ parent: 1
+ - uid: 101
+ components:
+ - type: Transform
+ pos: 12.5,-1.5
+ parent: 1
+ - uid: 102
+ components:
+ - type: Transform
+ pos: 9.5,3.5
+ parent: 1
+ - uid: 103
+ components:
+ - type: Transform
+ pos: 3.5,3.5
+ parent: 1
+ - uid: 140
+ components:
+ - type: Transform
+ pos: 10.5,-1.5
+ parent: 1
+ - uid: 141
+ components:
+ - type: Transform
+ pos: 9.5,1.5
+ parent: 1
+ - uid: 142
+ components:
+ - type: Transform
+ pos: 9.5,-0.5
+ parent: 1
+ - uid: 143
+ components:
+ - type: Transform
+ pos: 10.5,-0.5
+ parent: 1
+ - uid: 201
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 3.5,2.5
+ parent: 1
+ - uid: 202
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 5.5,2.5
+ parent: 1
+ - uid: 203
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 4.5,2.5
+ parent: 1
+ - uid: 204
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 7.5,2.5
+ parent: 1
+ - uid: 205
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 8.5,2.5
+ parent: 1
+ - uid: 206
+ components:
+ - type: Transform
+ rot: 3.141592653589793 rad
+ pos: 9.5,2.5
+ parent: 1
+- proto: WarpPoint
+ entities:
+ - uid: 458
+ components:
+ - type: Transform
+ pos: 6.5,-0.5
+ parent: 1
+- proto: WaterCooler
+ entities:
+ - uid: 459
+ components:
+ - type: Transform
+ pos: 7.5,-3.5
+ parent: 1
+...
diff --git a/Resources/Prototypes/_Null/PointsOfInterest_Purchasable/zombie_lab.yml b/Resources/Prototypes/_Null/PointsOfInterest_Purchasable/zombie_lab.yml
new file mode 100644
index 00000000000..bd081ec2548
--- /dev/null
+++ b/Resources/Prototypes/_Null/PointsOfInterest_Purchasable/zombie_lab.yml
@@ -0,0 +1,62 @@
+# Author Info
+# GitHub: LukeZurg22
+# Discord: lukezurg22
+
+# Maintainer Info
+# GitHub: ???
+# Discord: ???
+#
+- type: buyablePoI
+ id: PoIZombie
+ parent: BaseBuyablePOI
+ name: Mors Research Centre
+ description: A research center revolved around necromantic activities
+ price: 85000
+ minimumDistance: 2000
+ maximumDistance: 3500
+ group: Monolith # Determines which variant of Nullith this will appear. Only one category remains, for now.
+ gridPath: /Maps/_Null/POI_Purchasable/zombie_lab.yml
+ guidebookPage: null
+ hideWarp: false
+ category: # See enum.PoIType or NSB
+ - Laboratory
+ addComponents:
+ - type: IFF
+ color: "#87ABBA"
+ #flags: [HideLabel, Hide]
+ readOnly: false
+ - type: SolarPoweredGrid
+ trackOnInit: true
+ doNotCull: true
+
+# NOTE: Guidebook entries, much like for ships, can be made. Still a work in progress as of 20251202.
+# - type: guideEntry
+ # id: BPoIZombieLab
+ # name: guide-entry-bpoi-zombie-lab
+ # text: "/ServerInfo/_Null/Guidebook/Shipyard/Coldcap.xml"
+
+# NOTE FROM LZ22: "I don't know if this is needed. Likely not, but it will be left here commented for reference in the meantime. If becomes irrelevant, feel free to remove.
+# - type: gameMap
+ # id: EventRepairRelay
+ # mapName: 'Mors Research Centre'
+ # mapPath: /Maps/_Null/POI_Purchasable/zombie_lab.yml
+ # minPlayers: 0
+ # stations:
+ # EventRepairRelay:
+ # stationProto: StandardFrontierStation
+ # components:
+ # - type: StationTransit
+ # routes:
+ # MinorPointsOfInterest: 55
+ # - type: StationNameSetup
+ # mapNameTemplate: 'Lark Repair Orbital'
+ # - type: ExtraStationInformation
+ # iconPath: /Textures/_Null/Miscellaneous/repair_icon-256x256.png
+ # stationSubtext: 'lark-orbital-name'
+ # stationDescription: 'lark-orbital-description'
+ # # - type: StationJobs
+ # # availableJobs:
+ # # Contractor: [ -1, -1 ]
+ # # Pilot: [ -1, -1 ]
+ # # tags:
+ # # - Lark
From 94f46a3fbee17fd4cca4a727d80fadec840c95a5 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 00:20:14 -0600
Subject: [PATCH 11/25] Added nullith.ftl
---
.../Locale/en-US/_Null/shipyarding/nullith.ftl | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
create mode 100644 Resources/Locale/en-US/_Null/shipyarding/nullith.ftl
diff --git a/Resources/Locale/en-US/_Null/shipyarding/nullith.ftl b/Resources/Locale/en-US/_Null/shipyarding/nullith.ftl
new file mode 100644
index 00000000000..4281ace7b84
--- /dev/null
+++ b/Resources/Locale/en-US/_Null/shipyarding/nullith.ftl
@@ -0,0 +1,18 @@
+# This file is for purchasable points of interest. This was made at a time where the
+# paradigm demanded only one instance of any particular PoI at a time.
+nullith-console-menu-title = Purchasable Points of Interest
+nullith-console-purchased = {$owner} has purchased the deed to {$location}.
+nullith-console-resale-warning-label = Once a location is purchased, it cannot be sold back! No refunds!
+nullith-location-message = Your purchase is located precisely at {$location}!
+
+# General Location Classifications
+nullith-console-class-All = All
+nullith-console-class-Scrapyard = Scrapyard
+nullith-console-class-Laboratory = Laboratory
+nullith-console-class-Warehouse = Warehouse
+nullith-console-class-Farm = Farm
+nullith-console-class-Hospital = Hospital
+nullith-console-class-Restaurant = Restaurant
+# Antagonist
+nullith-console-class-Hideout = Hideout
+nullith-console-class-Orbital = Orbital
From c423f85e552cbd755364723cfac2f0615bcbe7ea Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 00:54:50 -0600
Subject: [PATCH 12/25] Shipyard Information Locale and Output Cleanup
---
...nSystem.cs => ShipyardInformPurchaseLocationSystem.cs} | 8 ++++----
.../_NF/Shipyard/Systems/ShipyardSystem.Consoles.cs | 2 +-
Resources/Locale/en-US/_Null/shipyarding/nullith.ftl | 1 +
3 files changed, 6 insertions(+), 5 deletions(-)
rename Content.Server/_Mono/Shipyard/{ShipyardDirectionSystem.cs => ShipyardInformPurchaseLocationSystem.cs} (95%)
diff --git a/Content.Server/_Mono/Shipyard/ShipyardDirectionSystem.cs b/Content.Server/_Mono/Shipyard/ShipyardInformPurchaseLocationSystem.cs
similarity index 95%
rename from Content.Server/_Mono/Shipyard/ShipyardDirectionSystem.cs
rename to Content.Server/_Mono/Shipyard/ShipyardInformPurchaseLocationSystem.cs
index 38106e5bf63..6b0f4e0bb17 100644
--- a/Content.Server/_Mono/Shipyard/ShipyardDirectionSystem.cs
+++ b/Content.Server/_Mono/Shipyard/ShipyardInformPurchaseLocationSystem.cs
@@ -10,7 +10,7 @@ namespace Content.Server._Mono.Shipyard;
///
/// A system that tells players which direction their newly purchased ship is located
///
-public sealed class ShipyardDirectionSystem : EntitySystem
+public sealed class ShipyardInformPurchaseLocationSystem : EntitySystem
{
[Dependency] private readonly IChatManager _chatManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
@@ -21,11 +21,11 @@ public sealed class ShipyardDirectionSystem : EntitySystem
/// Sends a message to the player indicating the literal coordinates of their newly purchased hull.
///
///
- ///
- public void SendShipLocationMessage(EntityUid player, EntityUid hull)
+ ///
+ public void SendShipLocationMessage(EntityUid player, EntityUid purchase)
{
// Try to get player's and ship's locations.
- if (!TryGetPositions(player, hull, out _, out var shipPos))
+ if (!TryGetPositions(player, purchase, out _, out var shipPos))
return;
// Send message to player
diff --git a/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.Consoles.cs b/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.Consoles.cs
index 58143eed118..d41a7a2fe9d 100644
--- a/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.Consoles.cs
+++ b/Content.Server/_NF/Shipyard/Systems/ShipyardSystem.Consoles.cs
@@ -312,7 +312,7 @@ private void OnPurchaseMessage(EntityUid shipyardConsoleUid,
SendPurchaseMessage(shipyardConsoleUid, player, name, secretChannel, secret: true);
// Mono -> Null Sector [ fixed it for ya' :) ]
- _entitySystemManager.GetEntitySystem()
+ _entitySystemManager.GetEntitySystem()
.SendShipDirectionMessage(player, shuttleUid);
PlayConfirmSound(player, shipyardConsoleUid, component);
diff --git a/Resources/Locale/en-US/_Null/shipyarding/nullith.ftl b/Resources/Locale/en-US/_Null/shipyarding/nullith.ftl
index 4281ace7b84..6d4159d0ffa 100644
--- a/Resources/Locale/en-US/_Null/shipyarding/nullith.ftl
+++ b/Resources/Locale/en-US/_Null/shipyarding/nullith.ftl
@@ -2,6 +2,7 @@
# paradigm demanded only one instance of any particular PoI at a time.
nullith-console-menu-title = Purchasable Points of Interest
nullith-console-purchased = {$owner} has purchased the deed to {$location}.
+nullith-console-purchased-secret = A point of intrest has been purchased!
nullith-console-resale-warning-label = Once a location is purchased, it cannot be sold back! No refunds!
nullith-location-message = Your purchase is located precisely at {$location}!
From 3f8e63f50e8e8fdab3684248c2a2470b8835c53b Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 00:57:19 -0600
Subject: [PATCH 13/25] Reworked nullith.yml Monolith Base Type
---
.../_Null/Entities/Structures/nullith.yml | 29 ++++++++++++++++++-
1 file changed, 28 insertions(+), 1 deletion(-)
diff --git a/Resources/Prototypes/_Null/Entities/Structures/nullith.yml b/Resources/Prototypes/_Null/Entities/Structures/nullith.yml
index cb1c0264d7c..a48d8817bb5 100644
--- a/Resources/Prototypes/_Null/Entities/Structures/nullith.yml
+++ b/Resources/Prototypes/_Null/Entities/Structures/nullith.yml
@@ -31,9 +31,36 @@
color: "#FFFFFF"
radius: 12.0
energy: 7.5
+ - type: ActivatableUI
+ singleUser: true
+ key: enum.NullithConsoleUiKey.Monolith
+ - type: UserInterface
+ interfaces:
+ enum.NullithConsoleUiKey.Monolith:
+ type: NullithConsoleBoundUserInterface
+ - type: NullithConsole
+ targetIdSlot:
+ name: id-card-console-target-id
+ ejectSound: /Audio/Machines/id_swipe.ogg
+ insertSound: /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg
+ ejectOnBreak: true
+ swap: true
+ whitelist:
+ components:
+ - IdCard
+ - NullithVoucher
+ #newAccessLevels: [Captain] # Not providing any new access levels, for now as of 20251202.
+ - type: ItemSlots
+ - type: ContainerContainer
+ containers:
+ board: !type:Container
+ ShipyardConsole-targetId: !type:ContainerSlot
+ #- type: PurchasablePoIListing # Forces specific entries.
+ - type: Computer
+ board: Null # a null-type of board. Not the literal "Null" sector.
# WIP : Add power generation.
-# Null Sector : Monolith Implementations
+# Null Sector : Various Monolith Implementations
- type: entity
id: MonolithNullith
parent: Monolith
From 78411544a741e07363231cccf152e854999fa733 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 01:05:05 -0600
Subject: [PATCH 14/25] Added NonExistentCompanyName variable to
CompanyComponent.cs
---
Content.Shared/_Mono/Company/CompanyComponent.cs | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Content.Shared/_Mono/Company/CompanyComponent.cs b/Content.Shared/_Mono/Company/CompanyComponent.cs
index 8da2bfa4049..495e6bd216d 100644
--- a/Content.Shared/_Mono/Company/CompanyComponent.cs
+++ b/Content.Shared/_Mono/Company/CompanyComponent.cs
@@ -14,4 +14,6 @@ public sealed partial class CompanyComponent : Component
///
[DataField, AutoNetworkedField]
public string CompanyName = string.Empty;
+
+ public const string NonExistentCompanyName = "None";
}
From f3bcc0c60c609c616619935d374d88e268226314 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 02:15:08 -0600
Subject: [PATCH 15/25] Updated Mors Research Centre
---
.../_Null/PointsOfInterest_Purchasable/zombie_lab.yml | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/Resources/Prototypes/_Null/PointsOfInterest_Purchasable/zombie_lab.yml b/Resources/Prototypes/_Null/PointsOfInterest_Purchasable/zombie_lab.yml
index bd081ec2548..0c85c37a486 100644
--- a/Resources/Prototypes/_Null/PointsOfInterest_Purchasable/zombie_lab.yml
+++ b/Resources/Prototypes/_Null/PointsOfInterest_Purchasable/zombie_lab.yml
@@ -11,6 +11,7 @@
parent: BaseBuyablePOI
name: Mors Research Centre
description: A research center revolved around necromantic activities
+ title: poi-title-zombie
price: 85000
minimumDistance: 2000
maximumDistance: 3500
@@ -22,7 +23,7 @@
- Laboratory
addComponents:
- type: IFF
- color: "#87ABBA"
+ color: "#608250"
#flags: [HideLabel, Hide]
readOnly: false
- type: SolarPoweredGrid
From b7cd6cf6a242c51a4a204cf8138acfa3741925e2 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 13:59:24 -0600
Subject: [PATCH 16/25] Fixed Floof Reference in bowl.yml
---
.../Floof/Entities/Objects/Consumable/Food/Containers/bowl.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Resources/Prototypes/Floof/Entities/Objects/Consumable/Food/Containers/bowl.yml b/Resources/Prototypes/Floof/Entities/Objects/Consumable/Food/Containers/bowl.yml
index bcc875b06b2..e547f568e5a 100644
--- a/Resources/Prototypes/Floof/Entities/Objects/Consumable/Food/Containers/bowl.yml
+++ b/Resources/Prototypes/Floof/Entities/Objects/Consumable/Food/Containers/bowl.yml
@@ -12,7 +12,7 @@
food:
maxVol: 50 # enough to make cheese in.
- type: Sprite
- sprite: Floof/Objects/Consumable/Food/icecream.rsi
+ sprite: _Floof/Objects/Consumable/Food/icecream.rsi
layers:
- state: bowl
- map: ["enum.SolutionContainerLayers.Fill"]
From 532e81bdc8e62961156f3f148cff66834fb40310 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 13:59:52 -0600
Subject: [PATCH 17/25] Added Generic Company Stamp
---
.../_Null/Entities/Objects/Misc/papers.yml | 8 ++++++++
.../_Null/Entities/Objects/Misc/rubber_stamp.yml | 15 +++++++++++++++
.../Objects/Misc/bureaucracy.rsi/meta.json | 3 +++
.../Misc/bureaucracy.rsi/paper_stamp-company.png | Bin 0 -> 424 bytes
.../_Null/Objects/Misc/stamps.rsi/meta.json | 3 +++
.../Misc/stamps.rsi/stamp-company-generic.png | Bin 0 -> 529 bytes
6 files changed, 29 insertions(+)
create mode 100644 Resources/Prototypes/_Null/Entities/Objects/Misc/papers.yml
create mode 100644 Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_stamp-company.png
create mode 100644 Resources/Textures/_Null/Objects/Misc/stamps.rsi/stamp-company-generic.png
diff --git a/Resources/Prototypes/_Null/Entities/Objects/Misc/papers.yml b/Resources/Prototypes/_Null/Entities/Objects/Misc/papers.yml
new file mode 100644
index 00000000000..749bf390465
--- /dev/null
+++ b/Resources/Prototypes/_Null/Entities/Objects/Misc/papers.yml
@@ -0,0 +1,8 @@
+- type: entity
+ id: PaperMonolithDeed
+ name: nullith-deed-name
+ description: An affidavit of ownership proving an individual's claim to a location.
+ parent: Paper
+ components:
+ - type: Paper
+ content: nullith-console-deed
\ No newline at end of file
diff --git a/Resources/Prototypes/_Null/Entities/Objects/Misc/rubber_stamp.yml b/Resources/Prototypes/_Null/Entities/Objects/Misc/rubber_stamp.yml
index f7c6f38fa19..1acf6558693 100644
--- a/Resources/Prototypes/_Null/Entities/Objects/Misc/rubber_stamp.yml
+++ b/Resources/Prototypes/_Null/Entities/Objects/Misc/rubber_stamp.yml
@@ -1,3 +1,18 @@
+# Generic Stamps aren't implemented in a dynamic way. This is the next best thing until that is implemented.
+- type: entity
+ name: company stamp
+ parent: RubberStampBase # Frontier: remove BaseCommandContraband
+ id: RubberStampCompanyGeneric
+ categories: [ DoNotMap ]
+ components:
+ - type: Stamp
+ stampedName: null # Name is changed in code.
+ stampedColor: "#000000" # Black, color is changed in code.
+ stampState: "paper_stamp-company"
+ - type: Sprite
+ sprite: _Null/Objects/Misc/stamps.rsi
+ state: stamp-company-generic
+
- type: entity
name: nexwealth's rubber stamp
parent: RubberStampBase # Frontier: remove BaseCommandContraband
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json b/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json
index 577517e8b9a..39dfa28d758 100644
--- a/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json
+++ b/Resources/Textures/Objects/Misc/bureaucracy.rsi/meta.json
@@ -189,6 +189,9 @@
{
"name": "paper_stamp-cmo"
},
+ {
+ "name": "paper_stamp-company"
+ },
{
"name": "paper_stamp-deny"
},
diff --git a/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_stamp-company.png b/Resources/Textures/Objects/Misc/bureaucracy.rsi/paper_stamp-company.png
new file mode 100644
index 0000000000000000000000000000000000000000..1983a9e6d975a092a73da9b307e1c703ac857003
GIT binary patch
literal 424
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}Ea{HEjtmSN
z`?>!lvI6;>1s;*b3=DjSK$uZf!>a)(C{f}XQ4*Y=R#Ki=l*$m0n3-3i=jR%tV5(=R
zXPJ|C%MPe!TWW-7ny0500|$`9${@wa%D@O@c>%FBlnru?1|u_AoC(M_WMpCx0Mb!F
zoY~F-7S95*LEuvvBf|@31dV1X15j!LI|B<)rGb&L0pkLQsURC!7eGv!0%U^#6VO~H
zu*x7y3m^-s%h138B&)6XUERis9i%wi)5S3)!u{=qgPaEpI9d~Thn|Rt+tpfZ=`X%v
zqssSd&fHJ`t9bB8Q~=%Hus(P5{bj2jzPl8)`(4_%3irsmd+x6C&nHZpWfkT-G@yGywoOdSo>K
literal 0
HcmV?d00001
diff --git a/Resources/Textures/_Null/Objects/Misc/stamps.rsi/meta.json b/Resources/Textures/_Null/Objects/Misc/stamps.rsi/meta.json
index 8d08d5311aa..93e94129772 100644
--- a/Resources/Textures/_Null/Objects/Misc/stamps.rsi/meta.json
+++ b/Resources/Textures/_Null/Objects/Misc/stamps.rsi/meta.json
@@ -7,6 +7,9 @@
"y": 32
},
"states": [
+ {
+ "name": "stamp-company-generic"
+ },
{
"name": "stamp-nexwealth"
}
diff --git a/Resources/Textures/_Null/Objects/Misc/stamps.rsi/stamp-company-generic.png b/Resources/Textures/_Null/Objects/Misc/stamps.rsi/stamp-company-generic.png
new file mode 100644
index 0000000000000000000000000000000000000000..eec337cfab9762a4b26758711defedd21679bda8
GIT binary patch
literal 529
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyjKx9jP7LeL$-D$|SkfJR9T^xl
z_H+M9WCijK0(?ST<>ch7tgI$YnzV1SYP${vR5qhid=z1wdwor;B5Vh5y?#r-d36I2-~M
zK1P20Uw>Of=ZfduEpPIs%w$&Ru3J#c$MCzijP2Hn_>JMtN>0C{@3Ok*l{HFo&dj}(
zb-=En`GNFB<~hu{JV`scohL+`HnYF#;B~LxSakLlwmn~&a~WOqwtTbrBJ6Fp5M;ln
LtDnm{r-UW|E*g!~
literal 0
HcmV?d00001
From 41b4864e9c26d702dcc272e4d1b3606ab8391d6e Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 16:37:23 -0600
Subject: [PATCH 18/25] Added Buyable POI Locale
---
Content.Server/_NF/GameRule/PointOfInterestSystem.cs | 5 +++--
Resources/Locale/en-US/_Null/points_of_interest.ftl | 5 ++++-
2 files changed, 7 insertions(+), 3 deletions(-)
diff --git a/Content.Server/_NF/GameRule/PointOfInterestSystem.cs b/Content.Server/_NF/GameRule/PointOfInterestSystem.cs
index 67641093e77..ae13b63d871 100644
--- a/Content.Server/_NF/GameRule/PointOfInterestSystem.cs
+++ b/Content.Server/_NF/GameRule/PointOfInterestSystem.cs
@@ -259,8 +259,9 @@ public void GenerateUniques(MapId mapUid,
///
/// [Null Sector] For purchasable Points of Interest, and Locality's sake.
///
- public void GeneratePurchased(MapId mapUid, BuyablePoIPrototype proto)
+ public void GeneratePurchased(MapId mapUid, BuyablePoIPrototype proto, out EntityUid? poiEntityUid)
{
+ poiEntityUid = null;
/*
* For purchasable points of interest which may be in any number, and ignorant of the optional stations
* that are (un)naturally spawned elsewhere.
@@ -278,7 +279,7 @@ public void GeneratePurchased(MapId mapUid, BuyablePoIPrototype proto)
{
return;
}
-
+ poiEntityUid = optionalUid;
AddStationCoordsToSet(offset);
return;
diff --git a/Resources/Locale/en-US/_Null/points_of_interest.ftl b/Resources/Locale/en-US/_Null/points_of_interest.ftl
index 03842492930..836417cecba 100644
--- a/Resources/Locale/en-US/_Null/points_of_interest.ftl
+++ b/Resources/Locale/en-US/_Null/points_of_interest.ftl
@@ -1,2 +1,5 @@
lark-orbital-name = Lark Repair Orbital
-lark-orbital-description = A temporary repair orbital for Lark Station. It will be dismantled at a later time. (CAMPAIGN) Lark Station was found in ruins, and a repair team was called in. Some of the team are familiar; others, new faces. Regardless, the Lark Region was nearly destroyed in its infancy, and it remains to be seen if the repair efforts will be enough to keep it together.
\ No newline at end of file
+lark-orbital-description = A temporary repair orbital for Lark Station. It will be dismantled at a later time. (CAMPAIGN) Lark Station was found in ruins, and a repair team was called in. Some of the team are familiar; others, new faces. Regardless, the Lark Region was nearly destroyed in its infancy, and it remains to be seen if the repair efforts will be enough to keep it together.
+
+poi-title-blank = Landowner
+poi-title-zombie = Mors Centre Zombie Expert
From ffb849a0ac2131153ebb87a25ed5634a15c1584f Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 16:37:34 -0600
Subject: [PATCH 19/25] Added Deed and generic company stamp
---
Resources/Prototypes/_Null/Entities/Objects/Misc/papers.yml | 2 +-
.../Prototypes/_Null/Entities/Objects/Misc/rubber_stamp.yml | 5 +++--
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/Resources/Prototypes/_Null/Entities/Objects/Misc/papers.yml b/Resources/Prototypes/_Null/Entities/Objects/Misc/papers.yml
index 749bf390465..e246c2b692b 100644
--- a/Resources/Prototypes/_Null/Entities/Objects/Misc/papers.yml
+++ b/Resources/Prototypes/_Null/Entities/Objects/Misc/papers.yml
@@ -1,6 +1,6 @@
- type: entity
id: PaperMonolithDeed
- name: nullith-deed-name
+ name: deed
description: An affidavit of ownership proving an individual's claim to a location.
parent: Paper
components:
diff --git a/Resources/Prototypes/_Null/Entities/Objects/Misc/rubber_stamp.yml b/Resources/Prototypes/_Null/Entities/Objects/Misc/rubber_stamp.yml
index 1acf6558693..c09e9f48403 100644
--- a/Resources/Prototypes/_Null/Entities/Objects/Misc/rubber_stamp.yml
+++ b/Resources/Prototypes/_Null/Entities/Objects/Misc/rubber_stamp.yml
@@ -3,11 +3,12 @@
name: company stamp
parent: RubberStampBase # Frontier: remove BaseCommandContraband
id: RubberStampCompanyGeneric
+ # -> referenced in NullithSystem.cs
categories: [ DoNotMap ]
components:
- type: Stamp
- stampedName: null # Name is changed in code.
- stampedColor: "#000000" # Black, color is changed in code.
+ stampedName: stamp-component-stamped-name-default # Name is changed in code.
+ stampedColor: "#000000" # Black, color is changed in code.
stampState: "paper_stamp-company"
- type: Sprite
sprite: _Null/Objects/Misc/stamps.rsi
From 6b395e05b6a95fa62c0c193706b2bf35560259f9 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 17:12:14 -0600
Subject: [PATCH 20/25] Fixed Nullith Sprite Clipping with Spawned Items
---
Resources/Prototypes/_Null/Entities/Structures/nullith.yml | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Resources/Prototypes/_Null/Entities/Structures/nullith.yml b/Resources/Prototypes/_Null/Entities/Structures/nullith.yml
index a48d8817bb5..181846e265a 100644
--- a/Resources/Prototypes/_Null/Entities/Structures/nullith.yml
+++ b/Resources/Prototypes/_Null/Entities/Structures/nullith.yml
@@ -25,7 +25,7 @@
- type: Sprite
noRot: false
sprite: _Null/Objects/Fun/nullith.rsi
- drawdepth: Mobs
+ drawdepth: SmallMobs
- type: PointLight
enabled: true
color: "#FFFFFF"
@@ -41,8 +41,8 @@
- type: NullithConsole
targetIdSlot:
name: id-card-console-target-id
- ejectSound: /Audio/Machines/id_swipe.ogg
- insertSound: /Audio/Weapons/Guns/MagIn/batrifle_magin.ogg
+ ejectSound: /Audio/Items/bottle_clunk_2.ogg
+ insertSound: /Audio/Items/bottle_clunk.ogg
ejectOnBreak: true
swap: true
whitelist:
From adad42412571607921020cee6f41394850a6ed8d Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 17:12:34 -0600
Subject: [PATCH 21/25] Added Titles for other Points of Interest preemptively
---
Resources/Locale/en-US/_Null/points_of_interest.ftl | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/Resources/Locale/en-US/_Null/points_of_interest.ftl b/Resources/Locale/en-US/_Null/points_of_interest.ftl
index 836417cecba..2ca72cb58d9 100644
--- a/Resources/Locale/en-US/_Null/points_of_interest.ftl
+++ b/Resources/Locale/en-US/_Null/points_of_interest.ftl
@@ -2,4 +2,11 @@ lark-orbital-name = Lark Repair Orbital
lark-orbital-description = A temporary repair orbital for Lark Station. It will be dismantled at a later time. (CAMPAIGN) Lark Station was found in ruins, and a repair team was called in. Some of the team are familiar; others, new faces. Regardless, the Lark Region was nearly destroyed in its infancy, and it remains to be seen if the repair efforts will be enough to keep it together.
poi-title-blank = Landowner
-poi-title-zombie = Mors Centre Zombie Expert
+poi-title-zombie = Mors Centre Overseer
+
+# TODO
+poi-title-tinnias = Resthead Boss
+poi-title-bahamas = Grand Mama of the Bahamas
+poi-title-griftys = Finest Grifty's Employee
+poi-title-thepit = Pit Overboss
+poi-title-caseys = Casino Magnate
\ No newline at end of file
From 1c7289613cd191bcec1ec53f4e14c9984b3283d9 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 17:15:17 -0600
Subject: [PATCH 22/25] Added Deed to Localization
---
.../Locale/en-US/_Null/shipyarding/nullith.ftl | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/Resources/Locale/en-US/_Null/shipyarding/nullith.ftl b/Resources/Locale/en-US/_Null/shipyarding/nullith.ftl
index 6d4159d0ffa..45b3b7d87de 100644
--- a/Resources/Locale/en-US/_Null/shipyarding/nullith.ftl
+++ b/Resources/Locale/en-US/_Null/shipyarding/nullith.ftl
@@ -6,6 +6,20 @@ nullith-console-purchased-secret = A point of intrest has been purchased!
nullith-console-resale-warning-label = Once a location is purchased, it cannot be sold back! No refunds!
nullith-location-message = Your purchase is located precisely at {$location}!
+nullith-deed-company-default = Rogue Relay
+nullith-deed-name = deed to {$location}
+nullith-deed-provided-ship-success = Congratulations. Your deed has been sent to your ship, at the {$ship}.
+nullith-deed-provided-ship-failure = Could not find a ship to send the deed. Sending it here, instead.
+nullith-deed-company-exists = Your official sponsorship by {$company} is resound.
+nullith-deed-company-nonexistent = You are currently not sponsored by any official organization, but are reccommended to find one in the future.
+nullith-console-deed = [head=2]Affidavit of Ownership[/head]
+ To the esteemed {$player}, the Lark Relay humbly bestows upon you the title of {$title}.
+ {$sponsor} We at the Rogue Relay approve the affidavit of ownership. You are now the highest direct authority above the "{$location}", which now lies in your care at {$coordinates}.
+
+ Due to the turbulent nature of Null Sector property ownership in proximation to the Armadan homeworld, you are expected to relieve this property of your- and others' -presences in the event of an evacuation.
+
+ In addition, you are still subject to the Null Sector's current Common Law, and all that entails. You are not expected to return the property in pristine condition upon evacuation. Provided is an official stamp for any paperwork you may require concerning your title.
+
# General Location Classifications
nullith-console-class-All = All
nullith-console-class-Scrapyard = Scrapyard
From 73c8802687e146e92fce42ff2cc3f5642e159cfc Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 17:35:10 -0600
Subject: [PATCH 23/25] Updated nullith.yml
---
.../Prototypes/_Null/Entities/Structures/nullith.yml | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/Resources/Prototypes/_Null/Entities/Structures/nullith.yml b/Resources/Prototypes/_Null/Entities/Structures/nullith.yml
index 181846e265a..e2973150525 100644
--- a/Resources/Prototypes/_Null/Entities/Structures/nullith.yml
+++ b/Resources/Prototypes/_Null/Entities/Structures/nullith.yml
@@ -12,7 +12,7 @@
fix1:
shape:
!type:PhysShapeAabb
- bounds: "-0.5,-1,0.5,0.2" # Left, Down, Right, Up
+ bounds: "-1,-0.5,0.5,1" # Left, Down, Right, Up
mask:
- MachineMask
layer:
@@ -22,10 +22,11 @@
anchored: true
- type: Physics
bodyType: Static
- - type: Sprite
- noRot: false
+ - type: Sprite # Place w. Direction Facing "SOUTH" for best results.
+ noRot: true
sprite: _Null/Objects/Fun/nullith.rsi
- drawdepth: SmallMobs
+ drawdepth: Mobs
+ offset: -0.25, 0.50
- type: PointLight
enabled: true
color: "#FFFFFF"
From 92bdfb9b8a95c58490afd634c6dd3b744953c9d3 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 17:53:17 -0600
Subject: [PATCH 24/25] Added Buyable Points of Interest via Nullith Rework
---
.../BUI/NullithConsoleBoundUserInterface.cs | 81 ++
.../_Null/Nullith/UI/NullithConsoleMenu.xaml | 127 +++
.../Nullith/UI/NullithConsoleMenu.xaml.cs | 187 +++++
.../_Null/Nullith/UI/NullithRulesPopup.xaml | 20 +
.../Nullith/UI/NullithRulesPopup.xaml.cs | 26 +
Content.Client/_Null/Nullith/UI/PoIRow.xaml | 36 +
.../_Null/Nullith/UI/PoIRow.xaml.cs | 36 +
.../ShipyardInformPurchaseLocationSystem.cs | 16 +-
.../Components/NullithVoucherComponent.cs | 37 +
.../_Null/Systems/NullithSystem.Consoles.cs | 728 ++++++++++++++++++
Content.Server/_Null/Systems/NullithSystem.cs | 265 +++++++
.../_Mono/Company/CompanyComponent.cs | 7 +
.../SharedNullithConsoleComponent.cs | 60 ++
.../_Null/Nullith/BuyablePoIPrototype.cs | 137 ++++
.../Events/NullithConsolePurchaseMessage.cs | 17 +
.../Nullith/NullithConsoleInterfaceState.cs | 38 +
.../Nullith/PurchasablePoIListingComponent.cs | 16 +
.../_Null/Nullith/SharedNullithSystem.cs | 66 ++
18 files changed, 1894 insertions(+), 6 deletions(-)
create mode 100644 Content.Client/_Null/Nullith/BUI/NullithConsoleBoundUserInterface.cs
create mode 100644 Content.Client/_Null/Nullith/UI/NullithConsoleMenu.xaml
create mode 100644 Content.Client/_Null/Nullith/UI/NullithConsoleMenu.xaml.cs
create mode 100644 Content.Client/_Null/Nullith/UI/NullithRulesPopup.xaml
create mode 100644 Content.Client/_Null/Nullith/UI/NullithRulesPopup.xaml.cs
create mode 100644 Content.Client/_Null/Nullith/UI/PoIRow.xaml
create mode 100644 Content.Client/_Null/Nullith/UI/PoIRow.xaml.cs
create mode 100644 Content.Server/_Null/Components/NullithVoucherComponent.cs
create mode 100644 Content.Server/_Null/Systems/NullithSystem.Consoles.cs
create mode 100644 Content.Server/_Null/Systems/NullithSystem.cs
create mode 100644 Content.Shared/_Null/Components/SharedNullithConsoleComponent.cs
create mode 100644 Content.Shared/_Null/Nullith/BuyablePoIPrototype.cs
create mode 100644 Content.Shared/_Null/Nullith/Events/NullithConsolePurchaseMessage.cs
create mode 100644 Content.Shared/_Null/Nullith/NullithConsoleInterfaceState.cs
create mode 100644 Content.Shared/_Null/Nullith/PurchasablePoIListingComponent.cs
create mode 100644 Content.Shared/_Null/Nullith/SharedNullithSystem.cs
diff --git a/Content.Client/_Null/Nullith/BUI/NullithConsoleBoundUserInterface.cs b/Content.Client/_Null/Nullith/BUI/NullithConsoleBoundUserInterface.cs
new file mode 100644
index 00000000000..3f8ef32182e
--- /dev/null
+++ b/Content.Client/_Null/Nullith/BUI/NullithConsoleBoundUserInterface.cs
@@ -0,0 +1,81 @@
+using Content.Client._Null.Nullith.UI;
+using Content.Shared._Null.Nullith;
+using Content.Shared._Null.Nullith.Events;
+using Content.Shared.Containers.ItemSlots;
+using Robust.Client.UserInterface.Controls;
+
+#pragma warning disable CS0618 // Type or member is obsolete
+
+namespace Content.Client._Null.Nullith.BUI;
+
+///
+public sealed class NullithConsoleBoundUserInterface : BoundUserInterface
+{
+ private NullithConsoleMenu? _menu;
+
+ public int Balance { get; private set; }
+
+ public NullithConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+ _menu = new NullithConsoleMenu(this);
+ _menu.OpenCentered();
+ // The Shipyard Console may be used as reference for rules menu popup, if it needs implementation.
+ // Else-wise, it shall NOT here as it isn't needed as of 20251201.
+ _menu.OnClose += Close;
+ _menu.OnOrderApproved += ApproveOrder;
+ _menu.TargetIdButton.OnPressed += _ => SendMessage(new ItemSlotButtonPressedEvent("ShipyardConsole-targetId"));
+ }
+
+ private void Populate(List availablePrototypes,
+ List unavailablePrototypes,
+ bool freeListings,
+ bool validId)
+ {
+ if (_menu == null)
+ return;
+
+ _menu.PopulateLocations(availablePrototypes, unavailablePrototypes, freeListings, validId);
+ _menu.PopulateCategories(availablePrototypes, unavailablePrototypes);
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+
+ if (state is not NullithConsoleInterfaceState consoleState)
+ return;
+
+ Balance = consoleState.Balance;
+ Populate(
+ consoleState.ShipyardPrototypes.available,
+ consoleState.ShipyardPrototypes.unavailable,
+ consoleState.FreeListings,
+ consoleState.IsTargetIdPresent);
+ _menu?.UpdateState(consoleState);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (!disposing)
+ return;
+ _menu?.Dispose();
+ }
+
+ private void ApproveOrder(BaseButton.ButtonEventArgs args)
+ {
+ if (args.Button.Parent?.Parent is not PoIRow row || row.PoI == null)
+ {
+ return;
+ }
+
+ var pointOfInterestId = row.PoI.ID;
+ SendMessage(new NullithConsolePurchaseMessage(pointOfInterestId));
+ }
+}
diff --git a/Content.Client/_Null/Nullith/UI/NullithConsoleMenu.xaml b/Content.Client/_Null/Nullith/UI/NullithConsoleMenu.xaml
new file mode 100644
index 00000000000..4d7a09246a3
--- /dev/null
+++ b/Content.Client/_Null/Nullith/UI/NullithConsoleMenu.xaml
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_Null/Nullith/UI/NullithConsoleMenu.xaml.cs b/Content.Client/_Null/Nullith/UI/NullithConsoleMenu.xaml.cs
new file mode 100644
index 00000000000..bf99ae19d0b
--- /dev/null
+++ b/Content.Client/_Null/Nullith/UI/NullithConsoleMenu.xaml.cs
@@ -0,0 +1,187 @@
+using System.Linq;
+using Content.Client._Null.Nullith.BUI;
+using Content.Client.UserInterface.Controls;
+using Content.Shared._NF.Bank;
+using Content.Shared._Null.Nullith;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using static Robust.Client.UserInterface.Controls.BaseButton;
+
+namespace Content.Client._Null.Nullith.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class NullithConsoleMenu : FancyWindow
+{
+ [Dependency] private readonly IPrototypeManager _protoManager = default!;
+
+ public event Action? OnOrderApproved;
+ private readonly NullithConsoleBoundUserInterface _menu;
+ private readonly List _categoriesAsStrings = [];
+ private PoICategory? _category;
+
+ private List _lastAvailableProtos = [];
+ private List _lastUnavailableProtos = [];
+ private bool _freeListings = false;
+ private bool _validId = false;
+
+ public NullithConsoleMenu(NullithConsoleBoundUserInterface owner)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ _menu = owner;
+ Title = Loc.GetString("nullith-console-menu-title");
+ SearchBar.OnTextChanged += OnSearchBarTextChanged;
+ Categories.OnItemSelected += OnCategoryItemSelected;
+ }
+
+ private void OnCategoryItemSelected(OptionButton.ItemSelectedEventArgs args)
+ {
+ SetCategoryText(args.Id);
+ PopulateLocations(_lastAvailableProtos, _lastUnavailableProtos, _freeListings, _validId);
+ }
+
+ private void OnSearchBarTextChanged(LineEdit.LineEditEventArgs args)
+ {
+ PopulateLocations(_lastAvailableProtos, _lastUnavailableProtos, _freeListings, _validId);
+ }
+
+ private void SetCategoryText(int id)
+ {
+ _category = id == 0 ? null : _categoriesAsStrings[id];
+ Categories.SelectId(id);
+ }
+
+ ///
+ /// Populates the list of products that will actually be shown, using the current filters.
+ ///
+ public void PopulateLocations(List availablePrototypes,
+ List unavailablePrototypes,
+ bool free,
+ bool canPurchase)
+ {
+ PoIList.RemoveAllChildren();
+
+ var search = SearchBar.Text.Trim().ToLowerInvariant();
+
+ var newVessels = GetPoIPrototypesFromIds(availablePrototypes);
+ AddPoIsToControls(newVessels, search, free, canPurchase);
+
+ var newUnavailableVessels = GetPoIPrototypesFromIds(unavailablePrototypes);
+ AddPoIsToControls(newUnavailableVessels, search, free, false);
+
+ _lastAvailableProtos = availablePrototypes;
+ _lastUnavailableProtos = unavailablePrototypes;
+ }
+
+ ///
+ /// Given a set of prototype IDs, returns a corresponding set of prototypes, ordered by name.
+ ///
+ private List GetPoIPrototypesFromIds(IEnumerable protoIds)
+ {
+ var vesselList = protoIds
+ .Select(it => _protoManager.TryIndex(it, out var proto) ? proto : null)
+ .Where(it => it != null)
+ .ToList();
+
+ vesselList.Sort((x, y) =>
+ string.Compare(x!.Name, y!.Name, StringComparison.CurrentCultureIgnoreCase));
+ return vesselList;
+ }
+
+ ///
+ /// Adds all vessels in a given list of prototypes as PoIRows in the UI.
+ ///
+ private void AddPoIsToControls(IEnumerable vessels,
+ string search,
+ bool free,
+ bool canPurchase)
+ {
+ foreach (var prototype in vessels)
+ {
+ // Filter any ships
+ if (prototype == null)
+ continue;
+ if (_category != null && !prototype.PoICategories.Contains(_category.Value))
+ continue;
+ if (search.Length > 0 && !prototype.Name.Contains(search, StringComparison.InvariantCultureIgnoreCase))
+ continue;
+
+ var priceText = free
+ ? Loc.GetString("shipyard-console-menu-listing-free")
+ : BankSystemExtensions.ToSpesoString(prototype.Price);
+
+ var poIRowEntry = new PoIRow
+ {
+ PoI = prototype,
+ VesselName = { Text = prototype.Name },
+ Purchase = { Text = Loc.GetString("shipyard-console-purchase-available"), Disabled = !canPurchase },
+ Guidebook =
+ {
+ Disabled = prototype.GuidebookPage is null, TooltipDelay = 0.2f, ToolTip = prototype.Description
+ },
+ Price = { Text = priceText },
+ };
+ poIRowEntry.Purchase.OnPressed += args => { OnOrderApproved?.Invoke(args); };
+ PoIList.AddChild(poIRowEntry);
+ }
+ }
+
+ ///
+ /// Populates the list classes that will actually be shown, using the current filters.
+ ///
+ public void PopulateCategories(List availablePrototypes, List unavailablePrototypes)
+ {
+ _categoriesAsStrings.Clear();
+ Categories.Clear();
+
+ // Use (un)available prototypes in combination to get all category listings.
+ AddCategoriesFromPrototypes(availablePrototypes.Concat(unavailablePrototypes).Distinct());
+
+ _categoriesAsStrings.Sort();
+
+ // Add "All" category at the top of the list
+ _categoriesAsStrings.Insert(0, PoICategory.All);
+
+ foreach (var str in _categoriesAsStrings)
+ {
+ Categories.AddItem(Loc.GetString($"nullith-console-class-{str}"));
+ }
+ }
+
+ ///
+ /// Adds all ship classes from a list of vessel prototypes to the current control's list if they are missing.
+ ///
+ private void AddCategoriesFromPrototypes(IEnumerable prototypes)
+ {
+ foreach (var protoId in prototypes)
+ {
+ if (!_protoManager.TryIndex(protoId, out var prototype))
+ continue;
+
+ foreach (var category in prototype.PoICategories)
+ {
+ if (!_categoriesAsStrings.Contains(category) && category != PoICategory.All)
+ {
+ _categoriesAsStrings.Add(category);
+ }
+ }
+ }
+ }
+
+ public void UpdateState(NullithConsoleInterfaceState state)
+ {
+ BalanceLabel.Text = BankSystemExtensions.ToSpesoString(state.Balance);
+
+ //ShipAppraisalLabel.Text = $"{BankSystemExtensions.ToSpesoString(shipPrice)} ({state.SellRate * 100.0f:F1}%)";
+ TargetIdButton.Text = state.IsTargetIdPresent
+ ? Loc.GetString("id-card-console-window-eject-button")
+ : Loc.GetString("id-card-console-window-insert-button");
+ // TODO: Doesnt exist. Remove this later.// TODO: Doesnt exist. Remove this later.
+ //DeedTitle.Text = state.ShipDeedTitle ?? $"None";
+ _freeListings = state.FreeListings;
+ _validId = state.IsTargetIdPresent;
+ PopulateLocations(_lastAvailableProtos, _lastUnavailableProtos, _freeListings, _validId);
+ }
+}
diff --git a/Content.Client/_Null/Nullith/UI/NullithRulesPopup.xaml b/Content.Client/_Null/Nullith/UI/NullithRulesPopup.xaml
new file mode 100644
index 00000000000..13adf7e8c70
--- /dev/null
+++ b/Content.Client/_Null/Nullith/UI/NullithRulesPopup.xaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_Null/Nullith/UI/NullithRulesPopup.xaml.cs b/Content.Client/_Null/Nullith/UI/NullithRulesPopup.xaml.cs
new file mode 100644
index 00000000000..4be26a28512
--- /dev/null
+++ b/Content.Client/_Null/Nullith/UI/NullithRulesPopup.xaml.cs
@@ -0,0 +1,26 @@
+using Content.Client._Null.Nullith.BUI;
+using Content.Client.UserInterface.Controls;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client._Null.Nullith.UI;
+
+[GenerateTypedNameReferences]
+internal sealed partial class NullithRulesPopup : FancyWindow
+{
+ private readonly NullithConsoleBoundUserInterface _menu;
+
+ public NullithRulesPopup(NullithConsoleBoundUserInterface owner)
+ {
+ RobustXamlLoader.Load(this);
+ _menu = owner;
+ Title = Loc.GetString("shipyard-console-menu-title");
+ AcceptButton.OnPressed += OnRulesAccept;
+ }
+
+ private void OnRulesAccept(BaseButton.ButtonEventArgs args)
+ {
+ Close();
+ }
+}
diff --git a/Content.Client/_Null/Nullith/UI/PoIRow.xaml b/Content.Client/_Null/Nullith/UI/PoIRow.xaml
new file mode 100644
index 00000000000..759ce57fbab
--- /dev/null
+++ b/Content.Client/_Null/Nullith/UI/PoIRow.xaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/_Null/Nullith/UI/PoIRow.xaml.cs b/Content.Client/_Null/Nullith/UI/PoIRow.xaml.cs
new file mode 100644
index 00000000000..f52c8acaed7
--- /dev/null
+++ b/Content.Client/_Null/Nullith/UI/PoIRow.xaml.cs
@@ -0,0 +1,36 @@
+using System.Runtime.CompilerServices;
+using Content.Client.Guidebook;
+using Content.Shared._Null.Nullith;
+using Content.Shared.Guidebook;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client._Null.Nullith.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class PoIRow : PanelContainer
+{
+ [Robust.Shared.IoC.Dependency] private readonly IEntitySystemManager _entitySystem = default!;
+ private readonly GuidebookSystem _guidebook = default!;
+ public BuyablePoIPrototype? PoI;
+ public PoIRow()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ _guidebook = _entitySystem.GetEntitySystem();
+
+ Guidebook.OnPressed += LoadVesselGuidebook;
+ }
+
+ [MethodImpl(MethodImplOptions.NoOptimization)]
+ private void LoadVesselGuidebook(BaseButton.ButtonEventArgs args)
+ {
+ if (PoI?.GuidebookPage == null)
+ return;
+
+ var guidebookEntries = new List> { PoI.GuidebookPage.Value };
+ _guidebook.OpenHelp(guidebookEntries);
+ }
+}
diff --git a/Content.Server/_Mono/Shipyard/ShipyardInformPurchaseLocationSystem.cs b/Content.Server/_Mono/Shipyard/ShipyardInformPurchaseLocationSystem.cs
index 6b0f4e0bb17..1f456de6103 100644
--- a/Content.Server/_Mono/Shipyard/ShipyardInformPurchaseLocationSystem.cs
+++ b/Content.Server/_Mono/Shipyard/ShipyardInformPurchaseLocationSystem.cs
@@ -25,13 +25,13 @@ public sealed class ShipyardInformPurchaseLocationSystem : EntitySystem
public void SendShipLocationMessage(EntityUid player, EntityUid purchase)
{
// Try to get player's and ship's locations.
- if (!TryGetPositions(player, purchase, out _, out var shipPos))
+ if (!TryGetPositions(player, purchase, out _, out var shipPos, true))
return;
// Send message to player
- var message = Loc.GetString(
- "nullith-location-message", // Null Sector - For use with the Nullith. Localized vaguely to accomodate ships, if needed.
- ("location", shipPos));
+ // Null Sector - For use with the Nullith. Localized vaguely to accomodate ships, if needed.
+ var message = Loc.GetString("nullith-location-message",
+ ("location", $"{Math.Round(shipPos.X, 2)}, {Math.Round(shipPos.Y, 2)}"));
SendMessageToPlayer(player, message);
}
@@ -83,7 +83,11 @@ private void SendMessageToPlayer(EntityUid player, string message)
/// [Null Sector] Attempts to get the Player and Ship positions from provided Player and Ship Entities.
///
/// Two Vector2 output-variables: player and ship positions.
- private bool TryGetPositions(EntityUid player, EntityUid ship, out Vector2 playerPos, out Vector2 shipPos)
+ private bool TryGetPositions(EntityUid player,
+ EntityUid ship,
+ out Vector2 playerPos,
+ out Vector2 shipPos,
+ bool ignoreMap = false)
{
playerPos = Vector2.NaN;
shipPos = Vector2.NaN;
@@ -93,7 +97,7 @@ private bool TryGetPositions(EntityUid player, EntityUid ship, out Vector2 playe
return false;
// Make sure both entities are on the same map
- if (playerTransform.MapID != shipTransform.MapID)
+ if (!ignoreMap && playerTransform.MapID != shipTransform.MapID)
return false;
// Get positions of both entities
diff --git a/Content.Server/_Null/Components/NullithVoucherComponent.cs b/Content.Server/_Null/Components/NullithVoucherComponent.cs
new file mode 100644
index 00000000000..61fab490d9b
--- /dev/null
+++ b/Content.Server/_Null/Components/NullithVoucherComponent.cs
@@ -0,0 +1,37 @@
+using Content.Shared._Null.Nullith;
+using Content.Shared.Access;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server._Null.Components;
+
+[RegisterComponent, AutoGenerateComponentPause]
+public sealed partial class NullithVoucherComponent : Component
+{
+ ///
+ /// Number of redeemable ships that this voucher can still be used for. Decremented on purchase.
+ ///
+ [DataField]
+ public uint RedemptionsLeft = 1;
+
+ ///
+ /// If true, card will be destroyed when no redemptions are left. Checked at time of sale.
+ ///
+ [DataField]
+ public bool DestroyOnEmpty = false;
+
+ ///
+ /// Access tags and groups for shipyard access.
+ ///
+ [DataField]
+ public IReadOnlyCollection> Access { get; private set; } = Array.Empty>();
+
+ [DataField]
+ public IReadOnlyCollection> AccessGroups { get; private set; } = Array.Empty>();
+
+ ///
+ /// The type of console where this voucher can be used.
+ /// Should not be ShipyardConsoleUiKey.Custom. Note: currently cannot be used for mothership consoles.
+ ///
+ [DataField(required: true)]
+ public NullithConsoleUiKey MonolithType; // Who knows, perhaps one day certain Monolith types will permit certain locations.
+}
diff --git a/Content.Server/_Null/Systems/NullithSystem.Consoles.cs b/Content.Server/_Null/Systems/NullithSystem.Consoles.cs
new file mode 100644
index 00000000000..1f74569ede5
--- /dev/null
+++ b/Content.Server/_Null/Systems/NullithSystem.Consoles.cs
@@ -0,0 +1,728 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Server._Mono.Shipyard;
+using Content.Server._NF.Bank;
+using Content.Server._NF.Shipyard.Components;
+using Content.Server._NF.ShuttleRecords;
+using Content.Server._Null.Components;
+using Content.Server.Access.Systems;
+using Content.Server.Administration.Logs;
+using Content.Server.Chat.Managers;
+using Content.Server.Chat.Systems;
+using Content.Server.DeviceNetwork.Components;
+using Content.Server.Fax;
+using Content.Server.Popups;
+using Content.Server.Radio.EntitySystems;
+using Content.Server.StationEvents.Components;
+using Content.Shared._Mono.Company;
+using Content.Shared._NF.Bank.Components;
+using Content.Shared._NF.Shipyard.Components;
+using Content.Shared._Null.Components;
+using Content.Shared._Null.Nullith;
+using Content.Shared._Null.Nullith.Events;
+using Content.Shared.Access;
+using Content.Shared.Access.Components;
+using Content.Shared.Access.Systems;
+using Content.Shared.Chat;
+using Content.Shared.Database;
+using Content.Shared.Fax.Components;
+using Content.Shared.Paper;
+using Content.Shared.Radio;
+using Content.Shared.Shuttles.Components;
+using Content.Shared.UserInterface;
+using Robust.Server.GameObjects;
+using Robust.Server.Player;
+using Robust.Shared.Audio.Systems;
+using Robust.Shared.Containers;
+using Robust.Shared.Prototypes;
+
+namespace Content.Server._Null.Systems;
+
+[SuppressMessage("ReSharper", "InconsistentNaming")]
+[SuppressMessage("ReSharper", "ArrangeMethodOrOperatorBody")]
+public sealed partial class NullithSystem
+{
+ [Dependency] private readonly AccessReaderSystem _access = default!;
+ [Dependency] private readonly PopupSystem _popup = default!;
+ [Dependency] private readonly UserInterfaceSystem _ui = default!;
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+ [Dependency] private readonly RadioSystem _radio = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+ [Dependency] private readonly BankSystem _bank = default!;
+ [Dependency] private readonly IdCardSystem _idSystem = default!;
+ [Dependency] private readonly ChatSystem _chat = default!;
+ [Dependency] private readonly IAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly IEntitySystemManager _entitySystemManager = default!;
+ [Dependency] private readonly PaperSystem _stampSystem = default!;
+ [Dependency] private readonly FaxSystem _faxSystem = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IChatManager _chatManager = default!;
+ [Dependency] private readonly ShuttleRecordsSystem _shuttleRecordsSystem = default!;
+
+ public void InitializeConsole() { }
+
+ private void OnPurchaseMessage(EntityUid nullithConsoleUid,
+ NullithConsoleComponent nullithConsoleComponent,
+ NullithConsolePurchaseMessage args)
+ {
+ if (args.Actor is not { Valid: true } player)
+ return;
+
+ // Check if contained object is valid.
+ if (nullithConsoleComponent.TargetIdSlot.ContainerSlot?.ContainedEntity is not { Valid: true } buyerId)
+ {
+ ConsolePopup(player, Loc.GetString("shipyard-console-no-idcard"));
+ PlayDenySound(player, nullithConsoleUid, nullithConsoleComponent);
+ return;
+ }
+
+ // Check if contained object is an ID card or voucher.
+ TryComp(buyerId, out var idCard);
+ TryComp(buyerId, out var voucher);
+ if (idCard is null && voucher is null)
+ {
+ ConsolePopup(player, Loc.GetString("shipyard-console-no-idcard"));
+ PlayDenySound(player, nullithConsoleUid, nullithConsoleComponent);
+ return;
+ }
+
+ // Check if user making the purchase has legitimate accesses to the console.
+ if (TryComp(nullithConsoleUid, out var accessReaderComponent) &&
+ !_access.IsAllowed(player, nullithConsoleUid, accessReaderComponent))
+ {
+ ConsolePopup(player, Loc.GetString("comms-console-permission-denied"));
+ PlayDenySound(player, nullithConsoleUid, nullithConsoleComponent);
+ return;
+ }
+
+ if (!_prototypeManager.TryIndex(args.PointOfInterest, out var pointOfInterestPrototype))
+ {
+ ConsolePopup(player, Loc.GetString("shipyard-console-invalid-vessel", ("vessel", args.PointOfInterest)));
+ PlayDenySound(player, nullithConsoleUid, nullithConsoleComponent);
+ return;
+ }
+
+ if (!GetAvailableShuttles(nullithConsoleUid, targetId: buyerId).available.Contains(pointOfInterestPrototype.ID))
+ {
+ PlayDenySound(player, nullithConsoleUid, nullithConsoleComponent);
+ _adminLogger.Add(LogType.Action,
+ LogImpact.Medium,
+ $"{ToPrettyString(player):player} tried to purchase a vessel that was never available.");
+ return;
+ }
+
+ var name = pointOfInterestPrototype.Name;
+ if (pointOfInterestPrototype.Price <= 0)
+ return;
+
+ if (_station.GetOwningStation(nullithConsoleUid) is not { Valid: true } station)
+ {
+ ConsolePopup(player, Loc.GetString("shipyard-console-invalid-station"));
+ PlayDenySound(player, nullithConsoleUid, nullithConsoleComponent);
+ return;
+ }
+
+ if (!TryComp(player, out var bank))
+ {
+ ConsolePopup(player, Loc.GetString("shipyard-console-no-bank"));
+ PlayDenySound(player, nullithConsoleUid, nullithConsoleComponent);
+ return;
+ }
+
+ // Keep track of whether a voucher was used, or not.
+ var voucherUsed = false;
+ if (voucher is not null)
+ {
+ if (TryPurchaseWithVoucher(nullithConsoleUid,
+ nullithConsoleComponent,
+ args,
+ voucher,
+ player,
+ buyerId,
+ ref voucherUsed))
+ return;
+ }
+ else
+ {
+ if (UserIsPoor(nullithConsoleUid, nullithConsoleComponent, bank, pointOfInterestPrototype, player))
+ return; // Get bent. No Voucher will save you here.
+ }
+
+ // Attempt to purchase the Buyable Point of Interest. After this point, the remainder should be handling-
+ // -printing deeds or any secondary affects.
+ var nullithMap = _transform.GetMapId(nullithConsoleUid); // Get map the Monolith belongs to.
+ if (!TryPurchaseLocation((station, nullithMap), pointOfInterestPrototype, out var pointOfInterest_Unchecked))
+ {
+ PlayDenySound(player, nullithConsoleUid, nullithConsoleComponent);
+ return;
+ }
+
+ // The thing was purchased. Add this sucker to the Already-Purchased list!
+ AlreadyPurchasedPointsOfInterest.Add(pointOfInterestPrototype.ID);
+
+ // === === === === === === === === === === === === === === === === === === === === === === === === === === ===
+ // For absolute certainty the value of the Point of Interest is valid beyond this point.
+ var pointOfInterest = pointOfInterest_Unchecked.Value;
+
+ /* ===Commentary===
+ * As far as I am concerned, purchasable PoI's could be dedicated to certain organizations. However, the idea of
+ * players individually purchasing and changing their own bases of operations sounds awesome to me, and I do quite
+ * like the implications of dedicated organizational spaces without the context of constant fighting and "warfare".
+ * As such, I would like to keep this in, if only to make things more... territorially interesting—in a setting
+ * that prides itself on territorial disputes—for the Null Sector.
+ * Kindly,
+ * -LZ22
+ */
+ // Add company information to the location
+ var sponsor = Loc.GetString("nullith-deed-company-nonexistent");
+ if (TryComp(player, out var playerCompany) && // Player company component
+ !string.IsNullOrEmpty(playerCompany.CompanyName) && // The company isn't null or empty
+ !playerCompany.CompanyName.Equals(CompanyComponent
+ .NonExistentCompanyName)) // Just in case, it doesn't say "None"
+ {
+ // Assign the sponsor for the deed.
+ sponsor = Loc.GetString("nullith-deed-company-exists", ("company", playerCompany.CompanyName));
+ // Handle ship's company information.
+ var shipCompany = EnsureComp(pointOfInterest);
+ shipCompany.CompanyName = playerCompany.CompanyName;
+ Dirty(pointOfInterest, shipCompany);
+ }
+
+ #region Obsolete Code (just in case)
+
+ /* // Null Sector - Points of Interests are not shuttles. They tend not to move.
+ // Ensure that the vessel contains a shuttle component
+ if (!_entityManager.TryGetComponent(pointOfInterestID_Validated, out _))
+ {
+ PlayDenySound(player, shipyardConsoleUid, component);
+ return;
+ }*/
+
+
+ /*
+ // Null Sector - This spawns a spare copy of the POI, which is BAD. However it also handles station things, so if
+ // If there are stations, they must be set-up assuming there is a matching game-map prototype.
+ // This allows late-joins the ability to directly join onto the location, should it be available.
+ EntityUid? poiStation = null;
+ if (_prototypeManager.TryIndex(pointOfInterestPrototype.ID, out var stationProto))
+ {
+ List gridUids = [pointOfInterest];
+ poiStation = _station.InitializeNewStation(stationProto.Stations[pointOfInterestPrototype.ID], gridUids);
+ name = Name(poiStation.Value);
+ }*/
+
+ /*Null Sector -
+ * Access changes not really required. This should be improved later, of course, should certain locations have
+ * special buyer-only doors.
+ */
+ /*
+ // Assign accesses.
+ if (TryComp(buyerId, out var accessComponent))
+ {
+ var newAccess = accessComponent.Tags.ToList();
+ newAccess.AddRange(component.NewAccessLevels);
+ _accessSystem.TrySetTags(buyerId, newAccess, accessComponent);
+ }*/
+
+ // Null Sector - No IDCard-driven Deeds necessary here. But just in case, the code is left behind.
+ /*
+ var deedID = EnsureComp(buyerId);
+
+ AssignShuttleDeedProperties(deedID, pointOfInterestID_Validated, name, player, voucherUsed);
+ //deedID.DeedHolderCard = targetId;
+
+ var deedShuttle = EnsureComp(pointOfInterestID_Validated);
+ AssignShuttleDeedProperties(deedShuttle, pointOfInterestID_Validated, name, player, voucherUsed);
+ */
+
+ #endregion
+
+ EntityManager.AddComponents(pointOfInterest, pointOfInterestPrototype.AddComponents);
+
+ // Ensure cleanup on ship sale
+ EnsureComp(pointOfInterest);
+
+ CompletePurchase(nullithConsoleUid,
+ nullithConsoleComponent,
+ sponsor,
+ pointOfInterest,
+ player,
+ name,
+ voucherUsed,
+ buyerId,
+ pointOfInterestPrototype,
+ playerCompany);
+
+ RefreshState(nullithConsoleUid,
+ bank.Balance,
+ true,
+ name,
+ buyerId,
+ (NullithConsoleUiKey)args.UiKey,
+ voucherUsed);
+ }
+
+ ///
+ ///
+ ///
+ /// The monolith itself.
+ /// The NullithConsoleComponent belonging to the Monolith.
+ /// Localized deed string of the company sponsoring the player.
+ /// The ID of the PoI being purchased
+ /// The Player entity
+ /// The name of the location
+ /// Checks if a voucher was used to get it for free
+ /// The player's ID card (or voucher) itself
+ /// Prototype of the purchased POI
+ /// The company that the player belongs to.
+ private void CompletePurchase(EntityUid monolith,
+ NullithConsoleComponent component,
+ string sponsor,
+ EntityUid pointOfInterest,
+ EntityUid player,
+ string locationName,
+ bool voucherUsed,
+ EntityUid buyerID,
+ BuyablePoIPrototype poiProto,
+ CompanyComponent? company)
+ {
+ // Example: "Zombie Lab Landowner"; makes their title clean. Else, use the provided title localization.
+ // The title bestowed upon the player.
+ var title =
+ string.IsNullOrEmpty(poiProto.TitleLoc)
+ ? $"{poiProto.Name} {Loc.GetString("poi-title-blank")}"
+ : Loc.GetString(poiProto.TitleLoc);
+
+ // The complete and formalized deed that the player is provided.
+ var coords = _transform.GetWorldPosition(pointOfInterest);
+ var poiDeed = Loc.GetString(
+ "nullith-console-deed",
+ ("player", Name(player)), // Get player's name directly.
+ ("title", title),
+ ("sponsor", sponsor),
+ ("location", locationName),
+ ("coordinates", $"{Math.Round(coords.X,0)}, {Math.Round(coords.Y,0)}")); // Rounding for full coordinates.
+
+ #region Creating the Stamp Overlay for The Deed
+
+ var companyStamp = new StampDisplayInfo();
+ if (company is not null && company.CompanyName != CompanyComponent.NonExistentCompanyName)
+ {
+ // TODO: Companies do not have colours applied to them. For now, assigns stamp color to that of IFF.
+ if (TryComp(pointOfInterest, out var iffComponent))
+ companyStamp.StampedColor = iffComponent.Color;
+ companyStamp.StampedName = company.CompanyName;
+ }
+ // The player isn't part of a legitimate company.
+ else
+ {
+ companyStamp.StampedName = Loc.GetString("nullith-deed-company-default");
+ companyStamp.StampedColor = Color.Black;
+ }
+
+ #endregion
+
+ #region Creating the Deed
+
+ // Begin with attempting to find the fax machine.
+ EntityUid? destinationFax = null;
+ var faxMachines = EntityManager.EntityQueryEnumerator();
+ while (faxMachines.MoveNext(out var uid, out _, out _))
+ {
+ var faxGrid = _transform.GetGrid(uid);
+ if (faxGrid is null)
+ continue;
+ if (!TryComp(faxGrid.Value, out var shuttleDeed) ||
+ shuttleDeed.DeedHolderCard is null)
+ continue;
+ if (shuttleDeed.DeedHolderCard.Value.Id == buyerID.Id)
+ {
+ destinationFax = uid;
+ }
+ }
+
+ // If there is a place to fax the Deed to, fax it there.
+ string deedSpawnMessage;
+ if (destinationFax is not null)
+ {
+ deedSpawnMessage = Loc.GetString("nullith-deed-provided-ship-success", ("ship", destinationFax.Value));
+ var printout = new FaxPrintout(
+ poiDeed,
+ Loc.GetString("nullith-deed-name", ("location", Loc.GetString(poiProto.Name))),
+ null,
+ DeedPrototype,
+ DefaultStampState,
+ [companyStamp],
+ locked: true,
+ stampProtected: true);
+ _faxSystem.Receive(destinationFax.Value, printout);
+ }
+ // If the player doesn't have a valid ship, then just create it at the Monolith.
+ else
+ {
+ // Play a message and spawn the deed manually. Assumes the deed indeed includes a paper component.
+ // It better!
+ deedSpawnMessage = Loc.GetString("nullith-deed-provided-ship-failure");
+ var deed = EntityManager.SpawnAttachedTo(DeedPrototype, Transform(monolith).Coordinates);
+ var paperComponent = Comp(deed);
+ paperComponent.StampedBy = [companyStamp];
+ paperComponent.StampState = DefaultStampState;
+ paperComponent.EditingDisabled = true;
+ paperComponent.Content = poiDeed;
+ _metaData.SetEntityName(deed, Loc.GetString("nullith-deed-name", ("location", poiProto.Name))); // Set custom name for deed. "Deed to X"
+ }
+
+ // Send deed spawn message.
+ if (_playerManager.TryGetSessionByEntity(player, out var session))
+ {
+ _chatManager.ChatMessageToOne(ChatChannel.Server,
+ deedSpawnMessage,
+ deedSpawnMessage,
+ EntityUid.Invalid,
+ false,
+ session.Channel);
+ }
+
+ #endregion
+
+ #region Creating the Personalized Stamp
+
+ var personalizedStamp = EntityManager.SpawnAttachedTo(GenericStampPrototype, Transform(monolith).Coordinates);
+ var stampComponent = Comp(personalizedStamp);
+ stampComponent.StampedColor = TryComp(pointOfInterest, out var iff)
+ ? iff.Color // Uses IFF color for stamp. Very appropriate!
+ : Color.Black; // Defaults to black for personalized stamp.
+ stampComponent.StampedName = title; // Behold, the players awesome new custom title stamp!
+ //stampComponent.StampState // This is unchanged. The default paper_stamp-company is used for now, but perhaps
+ // in the future, dynamic stamps could allow some creative stamp types based on POI type.
+ // Changing the sprite referenced would also mean some unique on-paper visual stamps. For now though, this will do.
+ _metaData.SetEntityName(personalizedStamp, $"{title} stamp"); // Set custom name for stamp.
+
+ #endregion
+
+ SendPurchaseMessage(monolith, player, locationName, component.ShipyardChannel, secret: false);
+ if (component.SecretShipyardChannel is { } secretChannel)
+ SendPurchaseMessage(monolith, player, locationName, secretChannel, secret: true);
+
+ // Mono -> Null Sector [ fixed it for ya' :) ]
+ _entitySystemManager.GetEntitySystem()
+ .SendShipLocationMessage(player, pointOfInterest);
+
+ PlayConfirmSound(player, monolith, component);
+
+ #region Admin-Logging
+
+ if (voucherUsed)
+ {
+ _adminLogger.Add(LogType.ShipYardUsage,
+ LogImpact.Low,
+ $"{ToPrettyString(player):actor} used {ToPrettyString(buyerID)} to purchase POI {ToPrettyString(pointOfInterest)} with a voucher via {ToPrettyString(monolith)}");
+ }
+ else
+ {
+ _adminLogger.Add(LogType.ShipYardUsage,
+ LogImpact.Low,
+ $"{ToPrettyString(player):actor} used {ToPrettyString(buyerID)} to purchase POI {ToPrettyString(pointOfInterest)} for {poiProto.Price} credits via {ToPrettyString(monolith)}");
+ }
+
+ #endregion
+ }
+
+ private bool UserIsPoor(EntityUid shipyardConsoleUid,
+ NullithConsoleComponent component,
+ BankAccountComponent bank,
+ BuyablePoIPrototype pointOfInterest,
+ EntityUid player)
+ {
+ // User is too poor. WEAK! WEAK! WEAK! WEAK! WEAK! WEAK! WEAK! WEAK! WEAK! WEAK!
+ if (bank.Balance <= pointOfInterest.Price)
+ {
+ ConsolePopup(player,
+ Loc.GetString("cargo-console-insufficient-funds", ("cost", pointOfInterest.Price)));
+ PlayDenySound(player, shipyardConsoleUid, component);
+ return true;
+ }
+
+ // User cannot withdraw from bank, somehow. Likely too poor.
+ if (!_bank.TryBankWithdraw(player, pointOfInterest.Price))
+ {
+ ConsolePopup(player,
+ Loc.GetString("cargo-console-insufficient-funds", ("cost", pointOfInterest.Price)));
+ PlayDenySound(player, shipyardConsoleUid, component);
+ return true;
+ }
+
+ return false;
+ }
+
+ private bool TryPurchaseWithVoucher(EntityUid shipyardConsoleUid,
+ NullithConsoleComponent component,
+ NullithConsolePurchaseMessage args,
+ NullithVoucherComponent voucher,
+ EntityUid player,
+ EntityUid targetId,
+ ref bool voucherUsed)
+ {
+ if (voucher.RedemptionsLeft <= 0)
+ {
+ ConsolePopup(player, Loc.GetString("shipyard-console-no-voucher-redemptions"));
+ PlayDenySound(player, shipyardConsoleUid, component);
+ if (voucher.DestroyOnEmpty)
+ {
+ QueueDel(targetId);
+ }
+
+ return true;
+ }
+
+ if (voucher.MonolithType != (NullithConsoleUiKey)args.UiKey)
+ {
+ ConsolePopup(player, Loc.GetString("shipyard-console-invalid-voucher-type"));
+ PlayDenySound(player, shipyardConsoleUid, component);
+ return true;
+ }
+
+ voucher.RedemptionsLeft--;
+ voucherUsed = true;
+ return false;
+ }
+
+ private void OnConsoleUIOpened(EntityUid uid, NullithConsoleComponent component, BoundUIOpenedEvent args)
+ {
+ if (!component.Initialized)
+ return;
+
+ // Orthodox method. We need to update the UI when an ID is entered, but the UI needs to know the player-
+ // -character's bank account.
+ if (!TryComp(uid, out var uiComp) || uiComp.Key == null)
+ return;
+
+ // Ensure that the user is a real player. Else, give up.
+ if (args.Actor is not { Valid: true } player)
+ return;
+
+ // Make sure that the player has a bank account in order to use this thing.
+ if (!TryComp(player, out var bank))
+ return;
+
+ var targetId = component.TargetIdSlot.ContainerSlot?.ContainedEntity;
+
+ var voucherUsed = HasComp(targetId);
+
+ string? fullName = null;
+ RefreshState(uid,
+ bank.Balance,
+ true,
+ fullName,
+ targetId,
+ (NullithConsoleUiKey)args.UiKey,
+ voucherUsed);
+ }
+
+ private void ConsolePopup(EntityUid uid, string text) => _popup.PopupEntity(text, uid);
+
+ private void SendPurchaseMessage(EntityUid uid, EntityUid player, string name, string shipyardChannel, bool secret)
+ {
+ var channel = _prototypeManager.Index(shipyardChannel);
+
+ if (secret)
+ {
+ _chat.TrySendInGameICMessage(uid,
+ Loc.GetString("nullith-console-purchased-secret"),
+ InGameICChatType.Speak,
+ true);
+ }
+ else
+ {
+ _radio.SendRadioMessage(uid,
+ Loc.GetString("nullith-console-purchased", ("owner", player), ("location", name)),
+ channel,
+ uid);
+ _chat.TrySendInGameICMessage(uid,
+ Loc.GetString("nullith-console-purchased", ("owner", player), ("location", name)),
+ InGameICChatType.Speak,
+ true);
+ }
+ }
+
+ private void PlayDenySound(EntityUid playerUid, EntityUid consoleUid, NullithConsoleComponent component)
+ => _audio.PlayEntity(component.ErrorSound, playerUid, consoleUid);
+
+ private void PlayConfirmSound(EntityUid playerUid, EntityUid consoleUid, NullithConsoleComponent component)
+ => _audio.PlayEntity(component.ConfirmSound, playerUid, consoleUid);
+
+ private void OnItemSlotChanged(EntityUid uid, NullithConsoleComponent component, ContainerModifiedMessage args)
+ {
+ if (!component.Initialized)
+ return;
+
+ if (args.Container.ID != component.TargetIdSlot.ID)
+ return;
+
+ // kind of cursed. We need to update the UI when an Id is entered, but the UI needs to know the player characters bank account.
+ if (!TryComp(uid, out var uiComp) || uiComp.Key == null)
+ return;
+
+ var uiUsers = _ui.GetActors(uid, uiComp.Key);
+
+ foreach (var user in uiUsers)
+ {
+ if (user is not { Valid: true } player)
+ continue;
+
+ if (!TryComp(player, out var bank))
+ continue;
+
+ var targetId = component.TargetIdSlot.ContainerSlot?.ContainedEntity;
+
+ if (TryComp(targetId, out var deed))
+ {
+ if (Deleted(deed.ShuttleUid))
+ {
+ RemComp(targetId.Value);
+ continue;
+ }
+ }
+
+ var voucherUsed = HasComp(targetId);
+
+ var fullName = deed != null ? GetFullName(deed) : null;
+ RefreshState(uid,
+ bank.Balance,
+ true,
+ fullName,
+ targetId,
+ (NullithConsoleUiKey)uiComp.Key,
+ voucherUsed);
+ }
+ }
+
+ private struct IDShipAccesses
+ {
+ public IReadOnlyCollection> Tags;
+ public IReadOnlyCollection> Groups;
+ }
+
+ ///
+ /// Returns all shuttle prototype IDs the given shipyard console can offer.
+ ///
+ [SuppressMessage("ReSharper", "ConditionalAccessQualifierIsNonNullableAccordingToAPIContract")]
+ public (List available, List unavailable) GetAvailableShuttles(EntityUid uid,
+ NullithConsoleUiKey? key = null,
+ PurchasablePoIListingComponent? listing = null,
+ EntityUid? targetId = null)
+ {
+ var available = new List();
+ var unavailable = new List();
+
+ if (key == null && TryComp(uid, out var ui))
+ {
+ // Try to find UI key that is an instance of the shipyard console UI key.
+ foreach (var (k, _) in ui.Actors)
+ {
+ if (k is not NullithConsoleUiKey shipyardKey)
+ continue;
+ key = shipyardKey;
+ break;
+ }
+ }
+
+ // No listing provided, try to get the current one from the console being used as a default.
+ if (listing is null)
+ TryComp(uid, out listing);
+
+ // Construct access set from input type (voucher or ID card)
+ IDShipAccesses accesses;
+ var initialHasAccess = true;
+ if (TryComp(targetId, out var voucher))
+ {
+ if (voucher.MonolithType == key)
+ {
+ accesses.Tags = voucher.Access;
+ accesses.Groups = voucher.AccessGroups;
+ }
+ else
+ {
+ accesses.Tags = new HashSet>();
+ accesses.Groups = new HashSet>();
+ initialHasAccess = false;
+ }
+ }
+ else if (TryComp(targetId, out var accessComponent))
+ {
+ accesses.Tags = accessComponent.Tags;
+ accesses.Groups = accessComponent.Groups;
+ }
+ else
+ {
+ accesses.Tags = new HashSet>();
+ accesses.Groups = new HashSet>();
+ }
+
+ foreach (var pointOfInterest in _prototypeManager.EnumeratePrototypes())
+ {
+ var hasAccess = initialHasAccess;
+ // If the vessel needs access to be bought, check the user's access.
+ if (!string.IsNullOrEmpty(pointOfInterest.Access))
+ {
+ // Check tags. Naturally false by default, but any tags containing an access will flag this as true.
+ hasAccess = accesses.Tags.Contains(pointOfInterest.Access);
+
+ // Check each group if we haven't found access already.
+ if (!hasAccess)
+ {
+ foreach (var groupId in accesses.Groups)
+ {
+ var groupProto = _prototypeManager.Index(groupId);
+ if (groupProto?.Tags.Contains(pointOfInterest.Access) ?? false)
+ {
+ hasAccess = true;
+ break;
+ }
+ }
+ }
+ }
+
+ // A set of stringent requirements that ensures that a POI can be purchased, assuming all goes well:
+ // - Its key isn't custom, and its group is equal to the key provided. (Limits to certain UIs)
+ // - The POI is in a valid listing of Points of Interest and the key isn't null.
+ // - Inverting the "IsPurchased" to ensure that it CANNOT show if it's already bought.
+ // ~Kindly, LZ22
+ var keyIsPartOfGroup = key != NullithConsoleUiKey.Custom && pointOfInterest.Group == key;
+ var poiInPointsOfInterestList =
+ listing?.PointsOfInterest.Contains(pointOfInterest.ID) == true || listing == null;
+ var poiAlreadyPurchased = AlreadyPurchasedPointsOfInterest.Contains(pointOfInterest.ID);
+ var isValid = keyIsPartOfGroup && poiInPointsOfInterestList && !poiAlreadyPurchased;
+ if (!isValid)
+ continue;
+ if (hasAccess)
+ available.Add(pointOfInterest.ID);
+ else
+ unavailable.Add(pointOfInterest.ID);
+ }
+
+ return (available, unavailable);
+ }
+
+ private void RefreshState(EntityUid uid,
+ int balance,
+ bool access,
+ string? shipDeed,
+ EntityUid? targetId,
+ NullithConsoleUiKey uiKey,
+ bool freeListings)
+ {
+ var newState = new NullithConsoleInterfaceState(
+ balance,
+ access,
+ shipDeed,
+ targetId.HasValue,
+ ((byte)uiKey),
+ GetAvailableShuttles(uid, uiKey, targetId: targetId),
+ uiKey.ToString(),
+ freeListings,
+ 0);
+
+ _ui.SetUiState(uid, uiKey, newState);
+ }
+}
diff --git a/Content.Server/_Null/Systems/NullithSystem.cs b/Content.Server/_Null/Systems/NullithSystem.cs
new file mode 100644
index 00000000000..fbdf1002c4c
--- /dev/null
+++ b/Content.Server/_Null/Systems/NullithSystem.cs
@@ -0,0 +1,265 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Numerics;
+using Content.Server._NF.GameRule;
+using Content.Server.Cargo.Systems;
+using Content.Server.Station.Components;
+using Content.Server.Station.Systems;
+using Content.Shared._NF.CCVar;
+using Content.Shared._NF.Shipyard.Components;
+using Content.Shared._Null.Components;
+using Content.Shared._Null.Nullith;
+using Content.Shared._Null.Nullith.Events;
+using Content.Shared.GameTicking;
+using Robust.Server.GameObjects;
+using Robust.Shared.Configuration;
+using Robust.Shared.Containers;
+using Robust.Shared.EntitySerialization.Systems;
+using Robust.Shared.Map;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+using Robust.Shared.Utility;
+
+namespace Content.Server._Null.Systems;
+
+public sealed partial class NullithSystem : SharedNullithSystem
+{
+ [Dependency] private readonly IConfigurationManager _configManager = default!;
+ [Dependency] private readonly PricingSystem _pricing = default!;
+ [Dependency] private readonly StationSystem _station = default!;
+ [Dependency] private readonly MapLoaderSystem _mapLoader = default!;
+ [Dependency] private readonly MetaDataSystem _metaData = default!;
+ [Dependency] private readonly MapSystem _map = default!;
+ [Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly PointOfInterestSystem _poiSystem = default!;
+
+ ///
+ /// A list of Prototype IDs shared between all NullithConsoleComponents that prevents the same
+ /// Point of Interest from being purchased more than once.
+ ///
+ [ViewVariables, DataField(customTypeSerializer: typeof(PrototypeIdListSerializer))]
+ public static List AlreadyPurchasedPointsOfInterest = [];
+
+ #region Constant Prototype ID's
+
+ ///
+ /// The prototype of the paper that the monolith will spawn on purchase of a location.
+ ///
+ [ViewVariables(VVAccess.ReadOnly)]
+ public const string DeedPrototype = "PaperMonolithDeed";
+
+ ///
+ /// This is applied to both the player's customized stamp's visual, AND the deed's stamp visual.
+ ///
+ public const string DefaultStampState = "paper_stamp-company";
+
+ ///
+ /// Generic stamp that spawns for the user, which is customized to their hearts content based on the POI purchased.
+ ///
+ public const string GenericStampPrototype = "RubberStampCompanyGeneric";
+
+ #endregion
+
+ public MapId? ShipyardMap { get; private set; }
+ private float _shuttleIndex;
+ private const float ShuttleSpawnBuffer = 1f;
+ private ISawmill _sawmill = default!;
+ private bool _enabled;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ // FIXME: Load-bearing jank - game doesn't want to create a shipyard map at this point.
+ _enabled = _configManager.GetCVar(NFCCVars.Shipyard);
+ _configManager.OnValueChanged(NFCCVars.Shipyard,
+ SetShipyardEnabled); // NOTE: run immediately set to false, see comment above
+
+ _sawmill = Logger.GetSawmill("shipyard");
+
+ SubscribeLocalEvent(OnShipyardStartup);
+ SubscribeLocalEvent(OnConsoleUIOpened);
+ SubscribeLocalEvent(OnPurchaseMessage);
+ SubscribeLocalEvent(OnItemSlotChanged);
+ SubscribeLocalEvent(OnItemSlotChanged);
+ SubscribeLocalEvent(OnRoundRestart);
+ }
+
+ public override void Shutdown()
+ {
+ _configManager.UnsubValueChanged(NFCCVars.Shipyard, SetShipyardEnabled);
+ }
+
+ private void OnShipyardStartup(EntityUid uid, NullithConsoleComponent component, ComponentStartup args)
+ {
+ if (!_enabled)
+ return;
+ InitializeConsole();
+ }
+
+ private void OnRoundRestart(RoundRestartCleanupEvent ev)
+ {
+ CleanupShipyard();
+ }
+
+ private void SetShipyardEnabled(bool value)
+ {
+ if (_enabled == value)
+ return;
+
+ _enabled = value;
+
+ if (value)
+ SetupShipyardIfNeeded();
+ else
+ CleanupShipyard();
+ }
+
+ ///
+ /// Adds a ship to the shipyard, calculates its price, and attempts to ftl-dock it to the given station
+ ///
+ /// Item1 is the station the purchase was made. Item2 is the map the purchase took place.
+ /// The prototype of the PoI to load. Must be a grid file!
+ /// The EntityUid of the shuttle that was purchased
+ public bool TryPurchaseLocation(
+ (EntityUid, MapId) stationMapTuple,
+ BuyablePoIPrototype poiPrototype,
+ [NotNullWhen(true)] out EntityUid? purchasedLocation)
+ {
+ var gridPath = poiPrototype.GridPath;
+
+ if (!TryComp(stationMapTuple.Item1, out var stationData))
+ {
+ purchasedLocation = null;
+ return false;
+ }
+
+ var price = _pricing.AppraiseGrid(stationMapTuple.Item1);
+ var targetGrid = _station.GetLargestGrid(stationData);
+ if (targetGrid == null) // How are we even here with no station grid
+ {
+ QueueDel(stationMapTuple.Item1);
+ purchasedLocation = null;
+ return false;
+ }
+
+ _sawmill.Info($"PoI {gridPath} was purchased at {ToPrettyString(stationMapTuple.Item1)} for {price:f2}");
+
+ // Use the provided map and generate the POI!
+ _poiSystem.GeneratePurchased(stationMapTuple.Item2, poiPrototype, out var generatedPoI);
+ purchasedLocation = generatedPoI;
+ if (generatedPoI != null)
+ purchasedLocation = generatedPoI.Value;
+ else
+ return false;
+
+ return true;
+ }
+
+ ///
+ /// Loads a shuttle into the ShipyardMap from a file path
+ ///
+ /// The path to the grid file to load. Must be a grid file!
+ ///
+ /// Returns the EntityUid of the shuttle
+ private bool TryAddShuttle(ResPath shuttlePath, [NotNullWhen(true)] out EntityUid? shuttleGrid)
+ {
+ shuttleGrid = null;
+ SetupShipyardIfNeeded();
+ if (ShipyardMap == null)
+ return false;
+
+ if (!_mapLoader.TryLoadGrid(ShipyardMap.Value,
+ shuttlePath,
+ out var grid,
+ offset: new Vector2(500f + _shuttleIndex, 1f)))
+ {
+ _sawmill.Error($"Unable to spawn shuttle {shuttlePath}");
+ return false;
+ }
+
+ _shuttleIndex += grid.Value.Comp.LocalAABB.Width + ShuttleSpawnBuffer;
+
+ shuttleGrid = grid.Value.Owner;
+ return true;
+ }
+
+ ///False if provided uId has ShipyardPreserveOnSaleComponent, and true if otherwise.
+ private bool LacksPreserveOnSaleComp(EntityUid uid)
+ {
+ return !TryComp(uid, out var comp) || comp.PreserveOnSale == false;
+ }
+
+ private void CleanupShipyard()
+ {
+ if (ShipyardMap == null || !_map.MapExists(ShipyardMap.Value))
+ {
+ ShipyardMap = null;
+ return;
+ }
+
+ _map.DeleteMap(ShipyardMap.Value);
+ }
+
+ public void SetupShipyardIfNeeded()
+ {
+ if (ShipyardMap != null && _map.MapExists(ShipyardMap.Value))
+ return;
+
+ _map.CreateMap(out var shipyardMap);
+ ShipyardMap = shipyardMap;
+
+ _map.SetPaused(ShipyardMap.Value, false);
+ }
+
+ //
+ // Tries to rename a shuttle deed and update the respective components.
+ // Returns true if successful.
+ //
+ // Null name parts are promptly ignored.
+ //
+ public bool TryRenameShuttle(EntityUid uid, ShuttleDeedComponent? shuttleDeed, string? newName, string? newSuffix)
+ {
+ if (!Resolve(uid, ref shuttleDeed))
+ return false;
+
+ var shuttle = shuttleDeed.ShuttleUid;
+ if (shuttle != null
+ && _station.GetOwningStation(shuttle.Value) is { Valid: true } shuttleStation)
+ {
+ // Null Sector - No such thing as deeds, here!
+ /*shuttleDeed.ShuttleName = newName;
+ shuttleDeed.ShuttleNameSuffix = newSuffix;*/
+ Dirty(uid, shuttleDeed);
+
+ var fullName = GetFullName(shuttleDeed);
+ _station.RenameStation(shuttleStation, fullName, loud: false);
+ _metaData.SetEntityName(shuttle.Value, fullName);
+ _metaData.SetEntityName(shuttleStation, fullName);
+ }
+ else
+ {
+ _sawmill.Error($"Could not rename shuttle {ToPrettyString(shuttle):entity} to {newName}");
+ return false;
+ }
+
+ //TODO: move this to an event that others hook into.
+ if (TryGetNetEntity(shuttleDeed.ShuttleUid, out var shuttleNetEntity) &&
+ _shuttleRecordsSystem.TryGetRecord(shuttleNetEntity.Value, out var record))
+ {
+ record.Name = newName ?? "";
+ record.Suffix = newSuffix ?? "";
+ _shuttleRecordsSystem.TryUpdateRecord(record);
+ }
+
+ return true;
+ }
+
+ ///
+ /// Returns the full name of the shuttle component in the form of [prefix] [name] [suffix].
+ ///
+ public static string GetFullName(ShuttleDeedComponent comp)
+ {
+ string?[] parts = { comp.ShuttleName, comp.ShuttleNameSuffix };
+ return string.Join(' ', parts.Where(it => it != null));
+ }
+}
diff --git a/Content.Shared/_Mono/Company/CompanyComponent.cs b/Content.Shared/_Mono/Company/CompanyComponent.cs
index 495e6bd216d..fa7eb15c3f1 100644
--- a/Content.Shared/_Mono/Company/CompanyComponent.cs
+++ b/Content.Shared/_Mono/Company/CompanyComponent.cs
@@ -15,5 +15,12 @@ public sealed partial class CompanyComponent : Component
[DataField, AutoNetworkedField]
public string CompanyName = string.Empty;
+ // TODO: Make this useful for something. Chiefly Company stamps, such as the dynamic stamp in NullithSystem.Consoles.cs
+ ///
+ /// Assigns the Company's official color for use in stamping, and possible IFF changes. [UNUSED!!!]
+ ///
+ [DataField, AutoNetworkedField]
+ public Color CompanyColor;
+
public const string NonExistentCompanyName = "None";
}
diff --git a/Content.Shared/_Null/Components/SharedNullithConsoleComponent.cs b/Content.Shared/_Null/Components/SharedNullithConsoleComponent.cs
new file mode 100644
index 00000000000..3cb7edb19ae
--- /dev/null
+++ b/Content.Shared/_Null/Components/SharedNullithConsoleComponent.cs
@@ -0,0 +1,60 @@
+using Content.Shared._NF.Shipyard;
+using Content.Shared.Access;
+using Content.Shared.Containers.ItemSlots;
+using Content.Shared.Radio;
+using Robust.Shared.Audio;
+using Robust.Shared.GameStates;
+using Robust.Shared.Prototypes;
+
+namespace Content.Shared._Null.Components;
+
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedShipyardSystem))]
+public sealed partial class NullithConsoleComponent : Component
+{
+ ///
+ /// The ID of the Card Slot the user inserts their ID card into.
+ ///
+ public const string TargetIdCardSlotId = "ShipyardConsole-targetId";
+
+ [DataField("targetIdSlot")]
+ public ItemSlot TargetIdSlot = new();
+
+ [DataField("soundError")]
+ public SoundSpecifier ErrorSound =
+ new SoundPathSpecifier("/Audio/Effects/Cargo/buzz_sigh.ogg");
+
+ [DataField("soundConfirm")]
+ public SoundSpecifier ConfirmSound =
+ new SoundPathSpecifier("/Audio/Effects/Shuttle/hyperspace_end.ogg");
+
+ ///
+ /// The comms channel that announces the PoI purchase. The purchase is *always* announced
+ /// on this channel.
+ ///
+ [DataField("shipyardChannel")]
+ public ProtoId ShipyardChannel = "Traffic";
+
+ ///
+ /// A second comms channel that announces the ship purchase, with some information redacted.
+ ///
+ [DataField("secretShipyardChannel")]
+ public ProtoId? SecretShipyardChannel = null;
+
+ ///
+ /// Access levels to be added to the owner's ID card.
+ ///
+ [DataField]
+ public List> NewAccessLevels = [];
+
+ ///
+ /// Indicates that the deeds that come from this console can be copied and transferred.
+ ///
+ [DataField]
+ public bool CanTransferDeed = true;
+
+ ///
+ /// If true, the base sale rate is ignored before calculating taxes.
+ ///
+ [DataField]
+ public bool IgnoreBaseSaleRate;
+}
diff --git a/Content.Shared/_Null/Nullith/BuyablePoIPrototype.cs b/Content.Shared/_Null/Nullith/BuyablePoIPrototype.cs
new file mode 100644
index 00000000000..73cb88b15a6
--- /dev/null
+++ b/Content.Shared/_Null/Nullith/BuyablePoIPrototype.cs
@@ -0,0 +1,137 @@
+using Content.Shared._NF.Shipyard.Prototypes;
+using Content.Shared.Guidebook;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Array;
+using Robust.Shared.Utility;
+
+namespace Content.Shared._Null.Nullith;
+
+///
+/// Various "VesselX" enums are used here, which were made for Frontier's Vessel Shipyards now repurposed for points
+/// of Interest, which bears a very similar prototype to . However, Buyable PoI's are not
+/// limited to any particular "yard", because as of 20251202 they can only be purchased from one location anyway.
+///
+[Prototype]
+public sealed class BuyablePoIPrototype : IPrototype, IInheritingPrototype
+{
+ [IdDataField]
+ public string ID { get; } = default!;
+
+ [ParentDataField(typeof(AbstractPrototypeIdArraySerializer))]
+ public string[]? Parents { get; private set; }
+
+ [NeverPushInheritance]
+ [AbstractDataField]
+ public bool Abstract { get; private set; }
+
+ ///
+ /// The location's name.
+ ///
+ [DataField] public string Name = string.Empty;
+
+ ///
+ /// Short description of the location.
+ ///
+ [DataField] public string Description = string.Empty;
+
+ ///
+ /// The title that the user gains for being the buyer of the location.
+ ///
+ [DataField("title")]
+ public string? TitleLoc = string.Empty; // TODO: make this actually mean something.
+
+ ///
+ /// The price of the location
+ ///
+ [DataField(required: true)]
+ public int Price;
+
+ ///
+ /// The listing that the location should be in.
+ ///
+ [DataField(required: true)]
+ public NullithConsoleUiKey Group = NullithConsoleUiKey.Monolith;
+
+ ///
+ /// The purpose of the location. (e.g. Service, Cargo, Engineering etc.)
+ ///
+ [DataField("category")]
+ public List PoICategories = [];
+
+ ///
+ /// The access required to buy the product. (e.g. Command, Mail, Bailiff, etc.)
+ ///
+ [DataField]
+ public string Access = string.Empty;
+
+ ///
+ /// Relative directory path to the given point of interest, i.e. `/Maps/PointsOfInterest_Purchasable/your_grid.yml`
+ ///
+ [DataField(required: true)]
+ public ResPath GridPath = default!;
+
+ ///
+ /// Guidebook page associated with a point of interest
+ ///
+ [DataField]
+ public ProtoId? GuidebookPage = default!;
+
+ ///
+ /// The price markup of the location testing
+ ///
+ [DataField]
+ public float MinPriceMarkup = 1.05f;
+
+ #region Warp Point
+
+ ///
+ /// Should we set the warp point name based on the grid name?
+ ///
+ [DataField]
+ public bool NameWarp { get; set; } = true;
+
+ ///
+ /// If NameWarp is true, should the warp point be admin-only (hiding it for players)?
+ ///
+ [DataField]
+ public bool HideWarp { get; set; } = false;
+
+ #endregion
+
+ #region Spawn Distance
+
+ ///
+ /// Minimum range to spawn this POI at
+ ///
+ [DataField]
+ public int MinimumDistance { get; private set; } = 5000;
+
+ ///
+ /// Maximum range to spawn this POI at
+ ///
+ [DataField]
+ public int MaximumDistance { get; private set; } = 10000;
+
+ #endregion
+
+ ///
+ /// Components to be added to any spawned grids.
+ ///
+ [DataField]
+ [AlwaysPushInheritance]
+ public ComponentRegistry AddComponents { get; set; } = new();
+}
+
+public enum PoICategory : byte
+{
+ All, // Placeholder value to represent everything
+ Scrapyard,
+ Laboratory,
+ Warehouse,
+ Farm,
+ Hospital,
+ Restaurant,
+ // Antagonist PoIs
+ Hideout, // Defense Stations or Piratical Areas
+ Orbital, // Armadan Defense Stations
+}
diff --git a/Content.Shared/_Null/Nullith/Events/NullithConsolePurchaseMessage.cs b/Content.Shared/_Null/Nullith/Events/NullithConsolePurchaseMessage.cs
new file mode 100644
index 00000000000..dabbd1bc304
--- /dev/null
+++ b/Content.Shared/_Null/Nullith/Events/NullithConsolePurchaseMessage.cs
@@ -0,0 +1,17 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._Null.Nullith.Events;
+
+///
+/// Purchase a Vessel from the console
+///
+[Serializable, NetSerializable]
+public sealed class NullithConsolePurchaseMessage : BoundUserInterfaceMessage
+{
+ public string PointOfInterest; // prototype ID
+
+ public NullithConsolePurchaseMessage(string poi)
+ {
+ PointOfInterest = poi;
+ }
+}
diff --git a/Content.Shared/_Null/Nullith/NullithConsoleInterfaceState.cs b/Content.Shared/_Null/Nullith/NullithConsoleInterfaceState.cs
new file mode 100644
index 00000000000..bb063a3db63
--- /dev/null
+++ b/Content.Shared/_Null/Nullith/NullithConsoleInterfaceState.cs
@@ -0,0 +1,38 @@
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._Null.Nullith;
+
+[NetSerializable, Serializable]
+public sealed class NullithConsoleInterfaceState : BoundUserInterfaceState
+{
+ public int Balance;
+ public readonly bool AccessGranted;
+ public readonly string? PoIDeedTitle;
+ public readonly bool IsTargetIdPresent;
+ public readonly byte UiKey;
+
+ public readonly (List available, List unavailable) ShipyardPrototypes;
+ public readonly string ShipyardName;
+ public readonly bool FreeListings;
+
+ public NullithConsoleInterfaceState(
+ int balance,
+ bool accessGranted,
+ string? poIDeedTitle,
+ bool isTargetIdPresent,
+ byte uiKey,
+ (List available, List unavailable) shipyardPrototypes,
+ string shipyardName,
+ bool freeListings,
+ float sellRate)
+ {
+ Balance = balance;
+ AccessGranted = accessGranted;
+ PoIDeedTitle = poIDeedTitle;
+ IsTargetIdPresent = isTargetIdPresent;
+ UiKey = uiKey;
+ ShipyardPrototypes = shipyardPrototypes;
+ ShipyardName = shipyardName;
+ FreeListings = freeListings;
+ }
+}
diff --git a/Content.Shared/_Null/Nullith/PurchasablePoIListingComponent.cs b/Content.Shared/_Null/Nullith/PurchasablePoIListingComponent.cs
new file mode 100644
index 00000000000..e5e52668837
--- /dev/null
+++ b/Content.Shared/_Null/Nullith/PurchasablePoIListingComponent.cs
@@ -0,0 +1,16 @@
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.List;
+
+namespace Content.Shared._Null.Nullith;
+
+///
+/// When applied to a shipyard console, adds all specified shuttles to the list of sold shuttles.
+///
+[RegisterComponent]
+public sealed partial class PurchasablePoIListingComponent : Component
+{
+ ///
+ /// All VesselPrototype IDs that should be listed in this shipyard console.
+ ///
+ [ViewVariables, DataField(customTypeSerializer: typeof(PrototypeIdListSerializer))]
+ public List PointsOfInterest = [];
+}
diff --git a/Content.Shared/_Null/Nullith/SharedNullithSystem.cs b/Content.Shared/_Null/Nullith/SharedNullithSystem.cs
new file mode 100644
index 00000000000..15028a885fb
--- /dev/null
+++ b/Content.Shared/_Null/Nullith/SharedNullithSystem.cs
@@ -0,0 +1,66 @@
+using System.Diagnostics.CodeAnalysis;
+using Content.Shared._Null.Components;
+using Content.Shared.Containers.ItemSlots;
+using Robust.Shared.GameStates;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared._Null.Nullith;
+
+// Note: when adding a new ui key, don't forget to modify the dictionary in SharedShipyardSystem
+[NetSerializable, Serializable]
+public enum NullithConsoleUiKey : byte
+{
+ /// Represents purchasable Points of Interest in Nullith. NOT FOR SHIPS!
+ Monolith,
+ // TODO: various kinds of nullith exist. This could add exclusivity for certain kinds of PoIs per round.
+ /// Add ships to this key if they are only available from mothership consoles. Shipyards using it are inherently empty and are populated using the ShipyardListingComponent.
+ Custom,
+}
+
+public abstract class SharedNullithSystem : EntitySystem
+{
+ [Dependency] private readonly ItemSlotsSystem _itemSlotsSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnComponentInit);
+ SubscribeLocalEvent(OnComponentRemove);
+ SubscribeLocalEvent(OnGetState);
+ SubscribeLocalEvent(OnHandleState);
+ }
+
+ private void OnHandleState(EntityUid uid, NullithConsoleComponent component, ref ComponentHandleState args)
+ {
+ // if (args.Current is not NullithConsoleComponentState)
+ // return;
+ }
+
+ private void OnGetState(EntityUid uid, NullithConsoleComponent component, ref ComponentGetState args)
+ {
+
+ }
+
+ private void OnComponentInit(EntityUid uid, NullithConsoleComponent component, ComponentInit args)
+ {
+ _itemSlotsSystem.AddItemSlot(uid, NullithConsoleComponent.TargetIdCardSlotId, component.TargetIdSlot);
+ }
+
+ private void OnComponentRemove(EntityUid uid, NullithConsoleComponent component, ComponentRemove args)
+ {
+ _itemSlotsSystem.RemoveItemSlot(uid, component.TargetIdSlot);
+ }
+
+ [Serializable, NetSerializable]
+ [SuppressMessage("ReSharper", "NotAccessedField.Local")]
+ private sealed class NullithConsoleComponentState : ComponentState
+ {
+ public List AccessLevels;
+
+ public NullithConsoleComponentState(List accessLevels)
+ {
+ AccessLevels = accessLevels;
+ }
+ }
+
+}
From 6a5aa51a0e28f7ea67dc149996e73645cc8f1b03 Mon Sep 17 00:00:00 2001
From: LukeZurg22 <11780262+LukeZurg22@users.noreply.github.com>
Date: Wed, 3 Dec 2025 18:03:53 -0600
Subject: [PATCH 25/25] Added Guidebook Entry for Buyable POIs
---
.../en-US/_Null/guidebook/guides_other.ftl | 1 +
.../Prototypes/_Null/Guidebook/buyable_pois.yml | 5 +++++
.../{nf14.yml => null_sector_intro.yml} | 3 ++-
.../ServerInfo/_Null/Guidebook/BuyablePOIs.xml | 17 +++++++++++++++++
4 files changed, 25 insertions(+), 1 deletion(-)
create mode 100644 Resources/Prototypes/_Null/Guidebook/buyable_pois.yml
rename Resources/Prototypes/_Null/Guidebook/{nf14.yml => null_sector_intro.yml} (96%)
create mode 100644 Resources/ServerInfo/_Null/Guidebook/BuyablePOIs.xml
diff --git a/Resources/Locale/en-US/_Null/guidebook/guides_other.ftl b/Resources/Locale/en-US/_Null/guidebook/guides_other.ftl
index 9ab6a50e0b3..205c10ea4ce 100644
--- a/Resources/Locale/en-US/_Null/guidebook/guides_other.ftl
+++ b/Resources/Locale/en-US/_Null/guidebook/guides_other.ftl
@@ -3,3 +3,4 @@ guide-entry-null-rule-antag = Space for Self Antagonism
# Other
guide-entry-fentanyl = Fentanyl Production
+guide-entry-buyable-pois = Buyable POIs
\ No newline at end of file
diff --git a/Resources/Prototypes/_Null/Guidebook/buyable_pois.yml b/Resources/Prototypes/_Null/Guidebook/buyable_pois.yml
new file mode 100644
index 00000000000..00c8e2c3ed2
--- /dev/null
+++ b/Resources/Prototypes/_Null/Guidebook/buyable_pois.yml
@@ -0,0 +1,5 @@
+- type: guideEntry
+ id: BuyablePOIs
+ name: guide-entry-buyable-pois
+ text: "/ServerInfo/_Null/Guidebook/BuyablePOIs.xml"
+ filterEnabled: True
\ No newline at end of file
diff --git a/Resources/Prototypes/_Null/Guidebook/nf14.yml b/Resources/Prototypes/_Null/Guidebook/null_sector_intro.yml
similarity index 96%
rename from Resources/Prototypes/_Null/Guidebook/nf14.yml
rename to Resources/Prototypes/_Null/Guidebook/null_sector_intro.yml
index 4597910c9a1..2683552ec74 100644
--- a/Resources/Prototypes/_Null/Guidebook/nf14.yml
+++ b/Resources/Prototypes/_Null/Guidebook/null_sector_intro.yml
@@ -13,7 +13,8 @@
- Expeditions
- CargoHauling # TODO: separate this out along with salvage and mining into some NF_Cargo section
- Shipyard
- - WeaponsSystems # mono
+ - WeaponsSystems # Monolith
+ - BuyablePOIs
- type: guideEntry
id: Lore
diff --git a/Resources/ServerInfo/_Null/Guidebook/BuyablePOIs.xml b/Resources/ServerInfo/_Null/Guidebook/BuyablePOIs.xml
new file mode 100644
index 00000000000..234306c93b8
--- /dev/null
+++ b/Resources/ServerInfo/_Null/Guidebook/BuyablePOIs.xml
@@ -0,0 +1,17 @@
+
+ # Buyable POIs
+
+
+
+
+
+ This section covers the Buyable Points of Interest (BPoI’s) from the Monolith structure. This was written as of 20251202, meaning the actual content may change over time. Unlike standard PoI’s, BPoI’s must be deliberately purchased in order to appear in the map, and will not persist between rounds.
+
+ Once a BPoI is bought:
+ - 1. It cannot be sold.
+ - 2. A customized deed is either faxed to your ship, or spawned at the Monolith.
+ - 3. A customized stamp with your very own title is spawned at the monolith.
+
+ Note that it will not have the same protections as Primary Stations. Though server rules still apply, a good example would be that you can blow it up if nobody else is onboard.
+
+