add bluespace shelter capsules (#2566)

* move mining_voucher.yml into Salvage folder

* add ShelterCapsuleComponent and system

* add shelter capsules

* add capsules to vendor and voucher

* :trollface:

* add admin logging and delete lava

* mv wire

* changes for namespace refactor

* remove dupe voucher

* add smoke when deploying

---------

Co-authored-by: deltanedas <@deltanedas:kde.org>
This commit is contained in:
deltanedas 2025-03-08 23:22:02 +00:00 committed by GitHub
parent b79e297a80
commit 49db46fa80
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 2415 additions and 14 deletions

View File

@ -0,0 +1,5 @@
using Content.Shared._DV.Salvage.Systems;
namespace Content.Client._DV.Salvage.Systems;
public sealed class ShelterCapsuleSystem : SharedShelterCapsuleSystem;

View File

@ -0,0 +1,76 @@
using Content.Server.Fluids.EntitySystems;
using Content.Server.Procedural;
using Content.Shared.Administration.Logs;
using Content.Shared.Chemistry.Components;
using Content.Shared.Database;
using Content.Shared._DV.Salvage.Components;
using Content.Shared._DV.Salvage.Systems;
using Content.Shared.Maps;
using Content.Shared.Physics;
using Robust.Shared.Map.Components;
using Robust.Shared.Maths;
using Robust.Shared.Prototypes;
using System.Numerics;
namespace Content.Server._DV.Salvage.Systems;
public sealed class ShelterCapsuleSystem : SharedShelterCapsuleSystem
{
[Dependency] private readonly DungeonSystem _dungeon = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly IPrototypeManager _proto = default!;
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default!;
[Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SmokeSystem _smoke = default!;
[Dependency] private readonly TurfSystem _turf = default!;
public EntProtoId SmokePrototype = "Smoke";
private HashSet<Entity<TransformComponent>> _entities = new();
protected override LocId? TrySpawnRoom(Entity<ShelterCapsuleComponent> ent)
{
var xform = Transform(ent);
if (xform.GridUid is not {} gridUid || !TryComp<MapGridComponent>(gridUid, out var grid))
return "shelter-capsule-error-space";
var gridXform = Transform(gridUid);
var center = _map.LocalToTile(gridUid, grid, xform.Coordinates);
var room = _proto.Index(ent.Comp.Room);
var origin = center - room.Size / 2;
// check that every tile it needs isn't blocked
var mask = CollisionGroup.MobMask;
for (int y = 0; y < room.Size.Y; y++)
{
for (int x = 0; x < room.Size.X; x++)
{
var pos = origin + new Vector2i(x, y);
var tile = _map.GetTileRef((gridUid, grid), pos);
if (tile.Tile.IsEmpty)
return "shelter-capsule-error-space";
if (_turf.IsTileBlocked(gridUid, pos, mask, grid, gridXform))
return "shelter-capsule-error-obstructed";
}
}
var user = ent.Comp.User;
_adminLogger.Add(LogType.Action, LogImpact.High, $"{ToPrettyString(user):user} expanded {ToPrettyString(ent):capsule} at {center} on {ToPrettyString(gridUid):grid}");
_dungeon.SpawnRoom(gridUid,
grid,
origin,
room,
new Random(),
null,
clearExisting: true); // already checked for mobs and structures here
var smoke = Spawn(SmokePrototype, xform.Coordinates);
var spreadAmount = (int) room.Size.Length * 2;
_smoke.StartSmoke(smoke, new Solution(), 3f, spreadAmount);
QueueDel(ent);
return null;
}
}

View File

@ -0,0 +1,39 @@
using Content.Shared._DV.Salvage.Systems;
using Content.Shared.Procedural;
using Robust.Shared.GameStates;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared._DV.Salvage.Components;
/// <summary>
/// Spawns a dungeon room after a delay when used and deletes itself.
/// </summary>
[RegisterComponent, NetworkedComponent, Access(typeof(SharedShelterCapsuleSystem))]
[AutoGenerateComponentPause]
public sealed partial class ShelterCapsuleComponent : Component
{
/// <summary>
/// The room to spawn.
/// </summary>
[DataField(required: true)]
public ProtoId<DungeonRoomPrototype> Room;
/// <summary>
/// How long to wait between using and spawning the room.
/// </summary>
[DataField]
public TimeSpan Delay = TimeSpan.FromSeconds(5);
/// <summary>
/// When to next spawn the room, also used to ignore activating multiple times.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField]
public TimeSpan? NextSpawn;
/// <summary>
/// The user of the capsule, used for logging.
/// </summary>
[DataField]
public EntityUid? User;
}

View File

@ -0,0 +1,66 @@
using Content.Shared._DV.Salvage.Components;
using Content.Shared.Interaction.Events;
using Content.Shared.Popups;
using Robust.Shared.Timing;
namespace Content.Shared._DV.Salvage.Systems;
/// <summary>
/// Handles interaction for shelter capsules.
/// Room spawning is done serverside.
/// </summary>
public abstract class SharedShelterCapsuleSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ShelterCapsuleComponent, UseInHandEvent>(OnUse);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var now = _timing.CurTime;
var query = EntityQueryEnumerator<ShelterCapsuleComponent>();
while (query.MoveNext(out var uid, out var comp))
{
if (comp.NextSpawn is not {} nextSpawn || now < nextSpawn)
continue;
comp.User = null;
comp.NextSpawn = null;
if (TrySpawnRoom((uid, comp)) is {} id)
{
var msg = Loc.GetString(id, ("capsule", uid));
_popup.PopupEntity(msg, uid, PopupType.LargeCaution);
}
}
}
/// <summary>
/// Spawn the room, returning a locale string for an error. It gets "capsule" passed.
/// </summary>
protected virtual LocId? TrySpawnRoom(Entity<ShelterCapsuleComponent> ent)
{
return null;
}
private void OnUse(Entity<ShelterCapsuleComponent> ent, ref UseInHandEvent args)
{
if (args.Handled || ent.Comp.NextSpawn != null)
return;
args.Handled = true;
var msg = Loc.GetString("shelter-capsule-warning", ("capsule", ent));
_popup.PopupPredicted(msg, ent, args.User, PopupType.LargeCaution);
ent.Comp.User = args.User;
ent.Comp.NextSpawn = _timing.CurTime + ent.Comp.Delay;
}
}

View File

@ -0,0 +1,3 @@
shelter-capsule-warning = {THE($capsule)} begins to shake. Stand back!
shelter-capsule-error-space = {THE($capsule)} needs ground to deploy on!
shelter-capsule-error-obstructed = {THE($capsule)} is obstructed, clear the area first!

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
# This is meant to be a smart fridge, but thats useless so
- type: entity
parent: CratePlastic
id: CrateSurvivalPodStorage
name: survival pod storage
suffix: Filled
components:
- type: EntityTableContainerFill
containers:
entity_storage: !type:NestedSelector
tableId: SurvivalPod
- type: entityTable
id: SurvivalPod
table: !type:AllSelector
children:
- id: FoodDonkpocketWarm
amount: !type:ConstantNumberSelector
value: 5
- !type:GroupSelector
children:
- id: d6Dice
- id: AcousticGuitarInstrument

View File

@ -16,7 +16,8 @@
# TODO: stabilizing serum for 400
- id: FultonBeacon
cost: 400
# TODO: bluespace shelter capsule for 400
- id: ShelterCapsule
cost: 400
- id: ClothingEyesGlassesGarMeson
cost: 500
- id: ClothingBeltSalvageWebbing
@ -49,7 +50,9 @@
# TODO: jump boots for 2500
- id: ClothingOuterHardsuitSalvage
cost: 3000
# TODO: luxury shelter capsule for 3k
# TODO: luxury elite bar capsule for 10k
- id: ShelterCapsuleLuxury
cost: 3000
- id: ShelterCapsuleBar
cost: 10000
# TODO: pka mods
# TODO: mining drone stuff

View File

@ -34,16 +34,16 @@
# - Resonator
# - FireExtinguisherMini
# TODO: bluespace shelter capsule so this isnt a scam
#- type: thiefBackpackSet
# id: MiningSurvival
# name: mining-voucher-survival-name
# description: mining-voucher-survival-description
# sprite:
# sprite: Clothing/Belt/salvagewebbing.rsi
# state: icon
# content:
# - ClothingBeltSalvageWebbing
- type: thiefBackpackSet
id: MiningSurvival
name: mining-voucher-survival-name
description: mining-voucher-survival-description
sprite:
sprite: Clothing/Belt/salvagewebbing.rsi
state: icon
content:
- ClothingBeltSalvageWebbing
- ShelterCapsule
# TODO: mining drone
#- type: thiefBackpackSet

View File

@ -23,6 +23,6 @@
- MiningCrusher
- MiningExtraction
#- MiningResonator
#- MiningSurvival
- MiningSurvival
#- MiningDrone
- MiningConscription

View File

@ -0,0 +1,63 @@
- type: entity
abstract: true
parent: BaseItem
id: BaseShelterCapsule
components:
- type: Sprite
sprite: _DV/Objects/Specific/Salvage/shelter_capsule.rsi
state: capsule
- type: Item
size: Tiny
- type: UseDelay
delay: 15 # avoid spamming popups when you know it will fail to spawn a room
- type: ShelterCapsule
- type: entity
parent: BaseShelterCapsule
id: ShelterCapsule
name: bluespace shelter capsule
description: An emergency shelter stored within a pocket of bluespace.
components:
- type: ShelterCapsule
room: EmergencyShelter
- type: entity
parent: BaseShelterCapsule
id: ShelterCapsuleLuxury
name: luxury bluespace shelter capsule
description: An exorbitantly expensive luxury suite stored within a pocket of bluespace.
components:
- type: ShelterCapsule
room: LuxuryShelter
- type: entity
parent: BaseShelterCapsule
id: ShelterCapsuleBar
name: luxury elite bar capsule
description: A luxury bar in a capsule. Bartender required and not included.
components:
- type: ShelterCapsule
room: EliteBarShelter
# Dungeon room prototypes
- type: dungeonRoom
id: EmergencyShelter
size: 5,5
atlas: /Maps/_DV/Nonstations/shelters.yml
offset: -2,-2 # grid is offset badly cba to fix it
ignoreTile: FloorShuttleOrange
- type: dungeonRoom
id: LuxuryShelter
size: 7,7
atlas: /Maps/_DV/Nonstations/shelters.yml
offset: 4,-2
ignoreTile: FloorShuttleOrange
- type: dungeonRoom
id: EliteBarShelter
size: 11,11
atlas: /Maps/_DV/Nonstations/shelters.yml
offset: 12,-2
ignoreTile: FloorShuttleOrange

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

View File

@ -0,0 +1,14 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Taken from tgstation at https://github.com/tgstation/tgstation/blob/8d20615ba100e7744792b42b6a6c7f4ea6314b3f/icons/obj/mining.dmi",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "capsule"
}
]
}