diff --git a/Content.Server/Salvage/SalvageMagnetComponent.cs b/Content.Server/Salvage/SalvageMagnetComponent.cs index 4773cc22b2..96b338f2f3 100644 --- a/Content.Server/Salvage/SalvageMagnetComponent.cs +++ b/Content.Server/Salvage/SalvageMagnetComponent.cs @@ -11,13 +11,25 @@ namespace Content.Server.Salvage public sealed class SalvageMagnetComponent : Component { /// - /// Offset relative to magnet that salvage should spawn. - /// Keep in sync with marker sprite (if any???) + /// Offset relative to magnet used as centre of the placement circle. /// [ViewVariables(VVAccess.ReadWrite)] [DataField("offset")] public Vector2 Offset = Vector2.Zero; // TODO: Maybe specify a direction, and find the nearest edge of the magnets grid the salvage can fit at + /// + /// Minimum distance from the offset position that will be used as a salvage's spawnpoint. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("offsetRadiusMin")] + public float OffsetRadiusMin = 0f; + + /// + /// Maximum distance from the offset position that will be used as a salvage's spawnpoint. + /// + [ViewVariables(VVAccess.ReadWrite)] + [DataField("offsetRadiusMax")] + public float OffsetRadiusMax = 0f; /// /// The entity attached to the magnet diff --git a/Content.Server/Salvage/SalvageMapPrototype.cs b/Content.Server/Salvage/SalvageMapPrototype.cs index 19a717eac8..3893ac30f7 100644 --- a/Content.Server/Salvage/SalvageMapPrototype.cs +++ b/Content.Server/Salvage/SalvageMapPrototype.cs @@ -1,5 +1,6 @@ using Robust.Shared.Prototypes; using Robust.Shared.Utility; +using Robust.Shared.Maths; namespace Content.Server.Salvage { @@ -18,11 +19,11 @@ namespace Content.Server.Salvage public ResourcePath MapPath { get; } = default!; /// - /// Size *from 0,0* in units of the map (used to determine if it fits) + /// Map rectangle in world coordinates (to check if it fits) /// [ViewVariables] - [DataField("size", required: true)] - public float Size { get; } = 1.0f; // TODO: Find a way to figure out the size automatically + [DataField("bounds", required: true)] + public Box2 Bounds { get; } = Box2.UnitCentered; /// /// Name for admin use diff --git a/Content.Server/Salvage/SalvageRulerCommand.cs b/Content.Server/Salvage/SalvageRulerCommand.cs new file mode 100644 index 0000000000..184c1b9083 --- /dev/null +++ b/Content.Server/Salvage/SalvageRulerCommand.cs @@ -0,0 +1,70 @@ +using Content.Server.Preferences.Managers; +using Content.Server.Administration; +using Content.Shared.Administration; +using Content.Shared.Preferences; +using Content.Shared.Roles; +using Robust.Server.GameObjects; +using Robust.Server.Player; +using Robust.Shared.Console; +using Robust.Shared.Map; +using Robust.Shared.IoC; + +namespace Content.Server.Salvage; + +[AdminCommand(AdminFlags.Admin)] +sealed class SalvageRulerCommand : IConsoleCommand +{ + [Dependency] private readonly IEntityManager _entities = default!; + [Dependency] private readonly IMapManager _maps = default!; + + public string Command => "salvageruler"; + + public string Description => Loc.GetString("salvage-ruler-command-description"); + + public string Help => Loc.GetString("salvage-ruler-command-help-text", ("command",Command)); + + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (args.Length != 0) + { + shell.WriteError(Loc.GetString("shell-wrong-arguments-number")); + return; + } + + if (shell.Player is not IPlayerSession player) + { + shell.WriteError(Loc.GetString("shell-only-players-can-run-this-command")); + return; + } + + var entity = player.AttachedEntity; + + if (entity == null) + { + shell.WriteError(Loc.GetString("shell-must-be-attached-to-entity")); + return; + } + + var entityTransform = _entities.GetComponent(entity.Value); + var total = Box2.UnitCentered; + var first = true; + foreach (var mapGrid in _maps.GetAllMapGrids(entityTransform.MapID)) + { + var aabb = mapGrid.WorldAABB; + if (first) + { + total = aabb; + } + else + { + total = total.ExtendToContain(aabb.TopLeft); + total = total.ExtendToContain(aabb.TopRight); + total = total.ExtendToContain(aabb.BottomLeft); + total = total.ExtendToContain(aabb.BottomRight); + } + first = false; + } + shell.WriteLine(total.ToString()); + } +} + diff --git a/Content.Server/Salvage/SalvageSystem.cs b/Content.Server/Salvage/SalvageSystem.cs index 103ee1b810..5d3cd11a1c 100644 --- a/Content.Server/Salvage/SalvageSystem.cs +++ b/Content.Server/Salvage/SalvageSystem.cs @@ -37,6 +37,7 @@ namespace Content.Server.Salvage private static readonly TimeSpan HoldTime = TimeSpan.FromMinutes(4); private static readonly TimeSpan DetachingTime = TimeSpan.FromSeconds(30); private static readonly TimeSpan CooldownTime = TimeSpan.FromMinutes(1); + private static readonly int SalvageLocationPlaceAttempts = 16; // TODO: This is probably not compatible with multi-station private readonly Dictionary _salvageGridStates = new(); @@ -239,7 +240,6 @@ namespace Content.Server.Salvage private bool SpawnSalvage(SalvageMagnetComponent component) { TryGetSalvagePlacementLocation(component, out var spl, out var spAngle); - SalvageMapPrototype? map = null; var forcedSalvage = _configurationManager.GetCVar(CCVars.SalvageForced); List allSalvageMaps; @@ -250,28 +250,40 @@ namespace Content.Server.Salvage else { allSalvageMaps = new(); - if (_prototypeManager.TryIndex(forcedSalvage, out map)) + if (_prototypeManager.TryIndex(forcedSalvage, out var forcedMap)) { - allSalvageMaps.Add(map); + allSalvageMaps.Add(forcedMap); } else { Logger.ErrorS("c.s.salvage", $"Unable to get forced salvage map prototype {forcedSalvage}"); } } + + SalvageMapPrototype? map = null; + Vector2 spawnLocation = Vector2.Zero; + for (var i = 0; i < allSalvageMaps.Count; i++) { - map = _random.PickAndTake(allSalvageMaps); - var box2 = Box2.CenteredAround(spl.Position, new Vector2(map.Size * 2.0f, map.Size * 2.0f)); - var box2rot = new Box2Rotated(box2, spAngle, spl.Position); - - // This doesn't stop it from spawning on top of random things in space - // Might be better like this, ghosts could stop it before - if (_mapManager.FindGridsIntersecting(spl.MapId, box2rot).Any()) + SalvageMapPrototype attemptedMap = _random.PickAndTake(allSalvageMaps); + for (var attempt = 0; attempt < SalvageLocationPlaceAttempts; attempt++) { - map = null; + var randomRadius = _random.NextFloat(component.OffsetRadiusMin, component.OffsetRadiusMax); + var randomOffset = _random.NextAngle().ToWorldVec() * randomRadius; + spawnLocation = spl.Position + randomOffset; + + var box2 = Box2.CenteredAround(spawnLocation + attemptedMap.Bounds.Center, attemptedMap.Bounds.Size); + var box2rot = new Box2Rotated(box2, spAngle, spawnLocation); + + // This doesn't stop it from spawning on top of random things in space + // Might be better like this, ghosts could stop it before + if (!_mapManager.FindGridsIntersecting(spl.MapId, box2rot).Any()) + { + map = attemptedMap; + break; + } } - else + if (map != null) { break; } @@ -285,7 +297,7 @@ namespace Content.Server.Salvage var opts = new MapLoadOptions { - Offset = spl.Position + Offset = spawnLocation }; var (_, salvageEntityId) = _mapLoader.LoadGrid(spl.MapId, map.MapPath.ToString(), opts); diff --git a/Resources/Locale/en-US/salvage/salvage-ruler-command.ftl b/Resources/Locale/en-US/salvage/salvage-ruler-command.ftl new file mode 100644 index 0000000000..3a0b054b24 --- /dev/null +++ b/Resources/Locale/en-US/salvage/salvage-ruler-command.ftl @@ -0,0 +1,2 @@ +salvage-ruler-command-description = Measures grids on this map to get a total world AABB. Use for salvage bounds specifications. +salvage-ruler-command-help-text = Usage: {$command} diff --git a/Resources/Locale/en-US/shell.ftl b/Resources/Locale/en-US/shell.ftl index a5d0dd2be4..bb91ffb18e 100644 --- a/Resources/Locale/en-US/shell.ftl +++ b/Resources/Locale/en-US/shell.ftl @@ -8,6 +8,7 @@ shell-invalid-command = Invalid command. shell-invalid-command-specific = Invalid {$commandName} command. shell-cannot-run-command-from-server = You cannot run this command from the server. shell-only-players-can-run-this-command = Only players can run this command. +shell-must-be-attached-to-entity = You must be attached to an entity to run this command. ## Arguments diff --git a/Resources/Prototypes/Entities/Structures/Machines/salvage.yml b/Resources/Prototypes/Entities/Structures/Machines/salvage.yml index a860004295..3d010ac4cb 100644 --- a/Resources/Prototypes/Entities/Structures/Machines/salvage.yml +++ b/Resources/Prototypes/Entities/Structures/Machines/salvage.yml @@ -16,7 +16,9 @@ channels: - Supply - type: SalvageMagnet - offset: 0, -32 + offset: 0, 0 + offsetRadiusMin: 24 + offsetRadiusMax: 48 - type: ApcPowerReceiver powerLoad: 2500 # TODO change this to a HV power draw that really hits the grid hard WHEN active @@ -28,7 +30,9 @@ description: "Locates salvage." components: - type: SalvageMagnet - offset: 0, -12 + offset: 0, 0 + offsetRadiusMin: 12 + offsetRadiusMax: 48 - type: ApcPowerReceiver powerLoad: 1000 diff --git a/Resources/Prototypes/Maps/salvage.yml b/Resources/Prototypes/Maps/salvage.yml index c1f8f1111b..31b6c2ce71 100644 --- a/Resources/Prototypes/Maps/salvage.yml +++ b/Resources/Prototypes/Maps/salvage.yml @@ -1,5 +1,7 @@ -# ALL SALVAGE MAPS SHOULD BE SETUP SUCH THAT TILE REF -1,-1 IS THEIR OFFICIAL CENTRE, -# AND FOR EASE OF UNDERSTANDING, USE - pos: 0.5, 0.5 FOR SAVED POSITION. +# So the way things are done as of now is that Salvages are measured by world AABBs in their maps. +# Remember, first two coordinates should be the minimum X/Y, second two should be maximum. +# You can also use the salvageruler command to get these, once you're sure the transform's been reset. +# Ultimately, you should still be keeping the maps centred. # "Small"-class maps - Max size square: 7x7, indicated size: 3.5 @@ -7,37 +9,37 @@ id: small1 name: "Small / Engineering Storage 1" mapPath: /Maps/Salvage/small-1.yml - size: 3.5 + bounds: "-2.5,-3.5,3.5,3.5" - type: salvageMap id: small2 name: "Small / Gaming Nook 1" mapPath: /Maps/Salvage/small-2.yml - size: 3.5 + bounds: "-3.5,-3.5,3.5,3.5" - type: salvageMap id: small-ship-1 name: "Small / Ship 1 (Pill)" mapPath: /Maps/Salvage/small-ship-1.yml - size: 3.5 + bounds: "-1.5,-0.5,2.5,1.5" - type: salvageMap id: small3 name: "Small / Laundromat 1" mapPath: /Maps/Salvage/small-3.yml - size: 3.5 + bounds: "-3.5,-3.5,2.5,3.5" - type: salvageMap id: smallAISurveyDrone name: "Small / AI Survey Drone" mapPath: /Maps/Salvage/small-ai-survey-drone.yml - size: 3.5 + bounds: "-3.5,-3.5,3.5,3.5" - type: salvageMap id: small4 name: "Small / Bar Salvage" mapPath: /Maps/Salvage/small-4.yml - size: 3.5 + bounds: "-3.5,-3.5,3.5,3.5" # Small - Asteroids @@ -45,7 +47,7 @@ id: smallA1 name: "Small / Asteroid 1 Plasmafire" mapPath: /Maps/Salvage/small-a-1.yml - size: 3.5 + bounds: "-3.5,-3.5,3.5,3.5" # "Medium"-class maps - Max size square: 15x15, indicated size: 7.5 @@ -53,73 +55,73 @@ id: medium1 name: "Medium / Plasma-Trapped Cache 1" mapPath: /Maps/Salvage/medium-1.yml - size: 7.5 + bounds: "-7.5,-7.5,7.5,7.5" - type: salvageMap id: mediumvault1 name: "Medium / Vault 1" mapPath: /Maps/Salvage/medium-vault-1.yml - size: 7.5 + bounds: "-7.5,-7.5,7.5,7.5" - type: salvageMap id: mediumOrchestra name: "Medium / Silent Orchestra" mapPath: /Maps/Salvage/medium-silent-orchestra.yml - size: 7.5 + bounds: "-7.5,-7.5,7.5,7.5" - type: salvageMap id: mediumLibraryWreck name: "Medium / Abandoned Library" mapPath: /Maps/Salvage/medium-library.yml - size: 7.5 + bounds: "-8.5,-8.5,6.5,7.5" - type: salvageMap id: mediumCargoWreck name: "Medium / Cargo Department Wreck" mapPath: /Maps/Salvage/cargo-1.yml - size: 7.5 + bounds: "-5.5,-5.5,5.5,9.5" - type: salvageMap id: mediumPirateWreck name: "Medium / Pirate Barge Fragment" mapPath: /Maps/Salvage/medium-pirate.yml - size: 7.5 + bounds: "-3.5,-9.5,6.5,7.5" - type: salvageMap id: tickColony name: "Space Tick colony" mapPath: /Maps/Salvage/tick-colony.yml - size: 8.5 + bounds: "-6,-7,5,7" - type: salvageMap id: cargoDock name: "Asteroid Cargo Dock" mapPath: /Maps/Salvage/medium-dock.yml - size: 8.5 + bounds: "-7,-6,4,8" - type: salvageMap id: spaceWaffleHome name: "Waffle Home" mapPath: /Maps/Salvage/wh-salvage.yml - size: 12 + bounds: "-13,-12,14,11" - type: salvageMap id: mediumShuttleWreck name: "Medium / Ruined Emergency Shuttle" mapPath: /Maps/Salvage/medium-ruined-emergency-shuttle.yml - size: 8.5 # Over standard medium wreck size, may need future adjustment. + bounds: "-8.5,-8.5,8.5,8.5" - type: salvageMap id: mediumPetHospital name: "Medium / Pet and Bear Hospital" mapPath: /Maps/Salvage/medium-pet-hospital.yml - size: 8.5 + bounds: "-5.5,-8.5,5.5,8.5" - type: salvageMap id: mediumCrashedShuttle name: "Crashed Shuttle" mapPath: /Maps/Salvage/medium-crashed-shuttle.yml - size: 9.5 + bounds: "-7,-8,5,9" # """Large""" maps @@ -127,16 +129,17 @@ id: stationstation name: "StationStation" mapPath: /Maps/Salvage/stationstation.yml - size: 24 + bounds: "-17,-15,35,29" - type: salvageMap id: asteroidBase name: "Asteroid Base" mapPath: /Maps/Salvage/asteroid-base.yml - size: 16 + bounds: "-12,-13,15,11" - type: salvageMap id: ruinCargoBase name: "Ruined Cargo Storage" mapPath: /Maps/Salvage/ruin-cargo-salvage.yml - size: 22.5 + bounds: "-15,-13,22,14" +