fish-station/Content.Server/Shuttles/Systems/ShuttleSystem.FasterThanLight.cs
Vigers Ray 939076ff99 Merge remote-tracking branch 'space-wizards/master'
# Conflicts:
#	.github/CODEOWNERS
#	.github/workflows/publish.yml
#	Content.Client/Administration/UI/BanPanel/BanPanel.xaml
#	Content.Client/Administration/UI/BanPanel/BanPanel.xaml.cs
#	Content.Client/Administration/UI/Tabs/PlayerTab/PlayerTabEntry.xaml.cs
#	Content.Client/Entry/EntryPoint.cs
#	Content.Client/Hands/Systems/HandsSystem.cs
#	Content.Client/Humanoid/HumanoidAppearanceSystem.cs
#	Content.Client/Humanoid/SingleMarkingPicker.xaml.cs
#	Content.Client/IoC/ClientContentIoC.cs
#	Content.Client/Lobby/LobbyState.cs
#	Content.Client/Lobby/UI/HumanoidProfileEditor.xaml.cs
#	Content.Client/Lobby/UI/Loadouts/LoadoutGroupContainer.xaml.cs
#	Content.Client/Lobby/UI/LobbyGui.xaml
#	Content.Client/NetworkConfigurator/NetworkConfiguratorConfigurationMenu.xaml
#	Content.Client/Options/UI/EscapeMenu.xaml
#	Content.Client/Stealth/StealthSystem.cs
#	Content.Client/SurveillanceCamera/UI/SurveillanceCameraMonitorWindow.xaml.cs
#	Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs
#	Content.Client/UserInterface/Systems/Emotes/EmotesUIController.cs
#	Content.Client/Weapons/Ranged/Systems/GunSystem.cs
#	Content.IntegrationTests/Tests/Commands/SuicideCommandTests.cs
#	Content.IntegrationTests/Tests/Damageable/DamageableTest.cs
#	Content.IntegrationTests/Tests/PostMapInitTest.cs
#	Content.Server/Administration/Commands/AdminWhoCommand.cs
#	Content.Server/Administration/Commands/PardonCommand.cs
#	Content.Server/Administration/Commands/RoleUnbanCommand.cs
#	Content.Server/Administration/Commands/SetOutfitCommand.cs
#	Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs
#	Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
#	Content.Server/AlertLevel/Commands/SetAlertLevelCommand.cs
#	Content.Server/Body/Components/BloodstreamComponent.cs
#	Content.Server/Body/Systems/BloodstreamSystem.cs
#	Content.Server/Body/Systems/MetabolizerSystem.cs
#	Content.Server/Body/Systems/RespiratorSystem.cs
#	Content.Server/Chat/Managers/ChatSanitizationManager.cs
#	Content.Server/Chat/Systems/ChatSystem.Emote.cs
#	Content.Server/Chat/Systems/ChatSystem.cs
#	Content.Server/CrewManifest/CrewManifestSystem.cs
#	Content.Server/Emp/EmpSystem.cs
#	Content.Server/Explosion/EntitySystems/TriggerSystem.cs
#	Content.Server/Fax/FaxSystem.cs
#	Content.Server/Fluids/EntitySystems/AbsorbentSystem.cs
#	Content.Server/GameTicking/Commands/ForceMapCommand.cs
#	Content.Server/GameTicking/Commands/ForcePresetCommand.cs
#	Content.Server/GameTicking/Presets/GamePresetPrototype.cs
#	Content.Server/GameTicking/Rules/ChangelingRuleSystem.cs
#	Content.Server/GameTicking/Rules/Components/ChangelingRuleComponent.cs
#	Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs
#	Content.Server/Ghost/Roles/GhostRoleSystem.cs
#	Content.Server/Implants/Components/ScramImplantComponent.cs
#	Content.Server/Implants/SubdermalImplantSystem.cs
#	Content.Server/Materials/ProduceMaterialExtractorSystem.cs
#	Content.Server/Medical/HealthAnalyzerSystem.cs
#	Content.Server/NPC/Systems/NPCCombatSystem.Ranged.cs
#	Content.Server/NPC/Systems/NPCSystem.cs
#	Content.Server/Polymorph/Systems/PolymorphSystem.cs
#	Content.Server/Radio/EntitySystems/HeadsetSystem.cs
#	Content.Server/Repairable/RepairableSystem.cs
#	Content.Server/Revenant/EntitySystems/RevenantSystem.cs
#	Content.Server/RoundEnd/RoundEndSystem.cs
#	Content.Server/Shuttles/Systems/EmergencyShuttleSystem.cs
#	Content.Server/Speech/EntitySystems/FrenchAccentSystem.cs
#	Content.Server/Speech/EntitySystems/FrontalLispSystem.cs
#	Content.Server/Speech/EntitySystems/LizardAccentSystem.cs
#	Content.Server/Speech/EntitySystems/MothAccentSystem.cs
#	Content.Server/Speech/EntitySystems/OwOAccentSystem.cs
#	Content.Server/Station/Systems/StationSpawningSystem.cs
#	Content.Server/StationEvents/Events/MeteorSwarmSystem.cs
#	Content.Server/Stunnable/Components/StunOnCollideComponent.cs
#	Content.Server/SurveillanceCamera/Components/SurveillanceCameraRouterComponent.cs
#	Content.Server/Weapons/Ranged/Systems/GunSystem.Cartridges.cs
#	Content.Server/Zombies/ZombieSystem.Transform.cs
#	Content.Server/Zombies/ZombieSystem.cs
#	Content.Shared/Actions/SharedActionsSystem.cs
#	Content.Shared/Bed/SharedBedSystem.cs
#	Content.Shared/Blocking/BlockingSystem.cs
#	Content.Shared/Body/Systems/SharedBodySystem.Parts.cs
#	Content.Shared/Chemistry/Components/SolutionRegenerationComponent.cs
#	Content.Shared/Chemistry/EntitySystems/HypospraySystem.cs
#	Content.Shared/Construction/Conditions/TileNotBlocked.cs
#	Content.Shared/Cuffs/SharedCuffableSystem.cs
#	Content.Shared/Doors/Systems/SharedDoorSystem.cs
#	Content.Shared/Emoting/EmoteAttemptEvent.cs
#	Content.Shared/Eye/Blinding/Systems/BlindableSystem.cs
#	Content.Shared/Eye/VisibilityFlags.cs
#	Content.Shared/Hands/EntitySystems/SharedHandsSystem.Pickup.cs
#	Content.Shared/Humanoid/HumanoidCharacterAppearance.cs
#	Content.Shared/Interaction/SmartEquipSystem.cs
#	Content.Shared/Inventory/InventorySystem.Relay.cs
#	Content.Shared/Medical/Cryogenics/SharedCryoPodSystem.cs
#	Content.Shared/Medical/Healing/HealingSystem.cs
#	Content.Shared/Medical/HealingComponent.cs
#	Content.Shared/Nutrition/EntitySystems/FoodSystem.cs
#	Content.Shared/Slippery/SlipperySystem.cs
#	Content.Shared/SprayPainter/SharedSprayPainterSystem.cs
#	Content.Shared/Standing/StandingStateComponent.cs
#	Content.Shared/Standing/StandingStateSystem.cs
#	Content.Shared/Station/SharedStationSpawningSystem.cs
#	Content.Shared/Stunnable/SharedStunSystem.cs
#	Content.Shared/SurveillanceCamera/SharedSurveillanceCameraMonitorSystem.cs
#	Content.Shared/VendingMachines/VendingMachineComponent.cs
#	Content.Shared/Warps/WarpPointSystem.cs
#	Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs
#	Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs
#	Content.Shared/Wieldable/SharedWieldableSystem.cs
#	Resources/Audio/Ambience/Antag/attributions.yml
#	Resources/Audio/Ambience/Antag/changeling_start.ogg
#	Resources/Audio/Effects/attributions.yml
#	Resources/Audio/Lobby/attributions.yml
#	Resources/Audio/Machines/attributions.yml
#	Resources/Audio/Weapons/Guns/Gunshots/attributions.yml
#	Resources/Locale/en-US/_strings/administration/antag.ftl
#	Resources/Locale/en-US/_strings/administration/commands/camera.ftl
#	Resources/Locale/en-US/_strings/administration/commands/dsay-command.ftl
#	Resources/Locale/en-US/_strings/administration/commands/follow-command.ftl
#	Resources/Locale/en-US/_strings/administration/smites.ftl
#	Resources/Locale/en-US/_strings/administration/ui/admin-camera-window.ftl
#	Resources/Locale/en-US/_strings/administration/ui/role-bans.ftl
#	Resources/Locale/en-US/_strings/alerts/alerts.ftl
#	Resources/Locale/en-US/_strings/atmos/gas-pressure-regulator-system.ftl
#	Resources/Locale/en-US/_strings/bonk/components/bonkable-component.ftl
#	Resources/Locale/en-US/_strings/commands/add-body-part-command.ftl
#	Resources/Locale/en-US/_strings/commands/admin-logs-command.ftl
#	Resources/Locale/en-US/_strings/commands/admin-who-command.ftl
#	Resources/Locale/en-US/_strings/commands/asay-command.ftl
#	Resources/Locale/en-US/_strings/commands/crew-manifest-command.ftl
#	Resources/Locale/en-US/_strings/commands/deadmin-command.ftl
#	Resources/Locale/en-US/_strings/commands/delete-component-command.ftl
#	Resources/Locale/en-US/_strings/commands/destroy-mechanism-command.ftl
#	Resources/Locale/en-US/_strings/commands/dirty-command.ftl
#	Resources/Locale/en-US/_strings/commands/dsay-command.ftl
#	Resources/Locale/en-US/_strings/commands/dump-reagent-guide-text-command.ftl
#	Resources/Locale/en-US/_strings/commands/eye-commands.ftl
#	Resources/Locale/en-US/_strings/commands/follow-command.ftl
#	Resources/Locale/en-US/_strings/commands/force-map-command.ftl
#	Resources/Locale/en-US/_strings/commands/force-preset-command.ftl
#	Resources/Locale/en-US/_strings/commands/ghost-kick-command.ftl
#	Resources/Locale/en-US/_strings/commands/ghost-visibility-commands.ftl
#	Resources/Locale/en-US/_strings/commands/go-lobby-command.ftl
#	Resources/Locale/en-US/_strings/commands/grid-drag-command.ftl
#	Resources/Locale/en-US/_strings/commands/is-afk-command.ftl
#	Resources/Locale/en-US/_strings/commands/makesentient-command.ftl
#	Resources/Locale/en-US/_strings/commands/map-commands.ftl
#	Resources/Locale/en-US/_strings/commands/mapping-client-side-setup-command.ftl
#	Resources/Locale/en-US/_strings/commands/mappingclientsidesetup-command.ftl
#	Resources/Locale/en-US/_strings/commands/me-command.ftl
#	Resources/Locale/en-US/_strings/commands/mind-info-command.ftl
#	Resources/Locale/en-US/_strings/commands/node-vis-commands.ftl
#	Resources/Locale/en-US/_strings/commands/npc-command.ftl
#	Resources/Locale/en-US/_strings/commands/nukecodes-command.ftl
#	Resources/Locale/en-US/_strings/commands/ooc-command.ftl
#	Resources/Locale/en-US/_strings/commands/pardon-command.ftl
#	Resources/Locale/en-US/_strings/commands/powerstat-command.ftl
#	Resources/Locale/en-US/_strings/commands/promote-host-command.ftl
#	Resources/Locale/en-US/_strings/commands/radiation-command.ftl
#	Resources/Locale/en-US/_strings/commands/readmin-command.ftl
#	Resources/Locale/en-US/_strings/commands/remove-extra-components-command.ftl
#	Resources/Locale/en-US/_strings/commands/role-commands.ftl
#	Resources/Locale/en-US/_strings/commands/say-command.ftl
#	Resources/Locale/en-US/_strings/commands/set-battery-percent-command.ftl
#	Resources/Locale/en-US/_strings/commands/show-ambient-command.ftl
#	Resources/Locale/en-US/_strings/commands/show-gun-spread-command.ftl
#	Resources/Locale/en-US/_strings/commands/show-htn-command.ftl
#	Resources/Locale/en-US/_strings/commands/showrules-command.ftl
#	Resources/Locale/en-US/_strings/commands/spawn-entity-list-command.ftl
#	Resources/Locale/en-US/_strings/commands/start-singularity-command.ftl
#	Resources/Locale/en-US/_strings/commands/tile-pry-command.ftl
#	Resources/Locale/en-US/_strings/commands/toggle-decals-command.ftl
#	Resources/Locale/en-US/_strings/commands/toggle-disallow-late-join-command.ftl
#	Resources/Locale/en-US/_strings/commands/unlocknode-command.ftl
#	Resources/Locale/en-US/_strings/commands/whisper-command.ftl
#	Resources/Locale/en-US/_strings/components/gas-pressure-regulator-component.ftl
#	Resources/Locale/en-US/_strings/engineer-painter/engineer-painter.ftl
#	Resources/Locale/en-US/_strings/game-ticking/forcemap-command.ftl
#	Resources/Locale/en-US/_strings/interaction/interaction-popup-component.ftl
#	Resources/Locale/en-US/_strings/markings/vox_tattoos.ftl
#	Resources/Locale/en-US/_strings/materials/material-extractor.ftl
#	Resources/Locale/en-US/_strings/movement/eye.ftl
#	Resources/Locale/en-US/_strings/nutrition/components/food-component.ftl
#	Resources/Locale/en-US/_strings/nutrition/components/ingestion-system.ftl
#	Resources/Locale/en-US/_strings/nutrition/components/sealable-component.ftl
#	Resources/Locale/en-US/_strings/radiation/radiation-command.ftl
#	Resources/Locale/en-US/_strings/reagents/meta/physical-desc.ftl
#	Resources/Locale/en-US/_strings/research/technologies.ftl
#	Resources/Locale/en-US/_strings/seeds/seeds.ftl
#	Resources/Locale/en-US/_strings/weapons/grenades/timer-trigger.ftl
#	Resources/Locale/en-US/_strings/weapons/grenades/voice-trigger.ftl
#	Resources/Locale/en-US/commands/mapping-client-side-setup-command.ftl
#	Resources/Locale/en-US/recipes/tags.ftl
#	Resources/Maps/_Sunrise/Station/bagel.yml
#	Resources/Maps/_Sunrise/Station/barratry.yml
#	Resources/Maps/_Sunrise/Station/box.yml
#	Resources/Maps/_Sunrise/Station/fland.yml
#	Resources/Maps/_Sunrise/Station/marathon.yml
#	Resources/Maps/_Sunrise/Station/oasis.yml
#	Resources/Maps/_Sunrise/Station/packed.yml
#	Resources/Maps/_Sunrise/Station/plasma.yml
#	Resources/Maps/amber.yml
#	Resources/Maps/centcomm.yml
#	Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/security.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/service.yml
#	Resources/Prototypes/Catalog/Fills/Lockers/suit_storage.yml
#	Resources/Prototypes/Catalog/VendingMachines/Inventories/coffee.yml
#	Resources/Prototypes/Catalog/VendingMachines/Inventories/sustenance.yml
#	Resources/Prototypes/Catalog/VendingMachines/Inventories/winterdrobe.yml
#	Resources/Prototypes/Entities/Clothing/Belt/belts.yml
#	Resources/Prototypes/Entities/Clothing/Eyes/glasses.yml
#	Resources/Prototypes/Entities/Clothing/Eyes/hud.yml
#	Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml
#	Resources/Prototypes/Entities/Clothing/Head/hats.yml
#	Resources/Prototypes/Entities/Clothing/Head/helmets.yml
#	Resources/Prototypes/Entities/Clothing/Head/hoods.yml
#	Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml
#	Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml
#	Resources/Prototypes/Entities/Clothing/OuterClothing/softsuits.yml
#	Resources/Prototypes/Entities/Clothing/OuterClothing/suits.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/carp.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/pets.yml
#	Resources/Prototypes/Entities/Mobs/NPCs/revenant.yml
#	Resources/Prototypes/Entities/Mobs/Player/admin_ghost.yml
#	Resources/Prototypes/Entities/Mobs/Player/clone.yml
#	Resources/Prototypes/Entities/Mobs/Species/base.yml
#	Resources/Prototypes/Entities/Mobs/Species/dwarf.yml
#	Resources/Prototypes/Entities/Mobs/base.yml
#	Resources/Prototypes/Entities/Objects/Fun/pai.yml
#	Resources/Prototypes/Entities/Objects/Fun/toys.yml
#	Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml
#	Resources/Prototypes/Entities/Objects/Shields/shields.yml
#	Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml
#	Resources/Prototypes/Entities/Objects/Specific/Medical/hypospray.yml
#	Resources/Prototypes/Entities/Objects/Tools/access_configurator.yml
#	Resources/Prototypes/Entities/Objects/Tools/matches.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Bombs/plastic.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Bombs/spider.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/caseless_rifle.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/light_rifle.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Magazines/magnum.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Ammunition/Projectiles/grenade.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Battery/battery_guns.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Guns/Shotguns/shotguns.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Melee/mining.yml
#	Resources/Prototypes/Entities/Objects/Weapons/Throwable/grenades.yml
#	Resources/Prototypes/Entities/Structures/Doors/Airlocks/airlocks.yml
#	Resources/Prototypes/Entities/Structures/Doors/airlock_groups.yml
#	Resources/Prototypes/Entities/Structures/Furniture/beds.yml
#	Resources/Prototypes/Entities/Structures/Machines/vending_machines.yml
#	Resources/Prototypes/Entities/Structures/Power/debug_power.yml
#	Resources/Prototypes/Entities/Structures/Storage/Crates/crates.yml
#	Resources/Prototypes/Entities/Structures/Wallmounts/Misc/mirror.yml
#	Resources/Prototypes/GameRules/subgamemodes.yml
#	Resources/Prototypes/Reagents/Consumable/Drink/drinks.yml
#	Resources/Prototypes/Reagents/Consumable/Drink/soda.yml
#	Resources/Prototypes/Reagents/medicine.yml
#	Resources/Prototypes/Recipes/Lathes/Packs/tiles.yml
#	Resources/Prototypes/Recipes/Lathes/categories.yml
#	Resources/Prototypes/Recipes/Reactions/chemicals.yml
#	Resources/Prototypes/Roles/Antags/changeling.yml
#	Resources/Prototypes/name_identifier_groups.yml
#	Resources/Prototypes/tool_qualities.yml
#	Resources/ServerInfo/Guidebook/Antagonist/Zombies.xml
#	Resources/ServerInfo/Guidebook/Cargo/CargoBounties.xml
#	Resources/ServerInfo/Guidebook/Command.xml
#	Resources/ServerInfo/Guidebook/Glossary.xml
#	Resources/ServerInfo/Guidebook/NewPlayer/YourFirstCharacter.xml
#	Resources/ServerInfo/Guidebook/Science/Robotics.xml
#	Resources/ServerInfo/Guidebook/Science/ScannersAndVessels.xml
#	Resources/ServerInfo/Guidebook/Science/Science.xml
#	Resources/ServerInfo/Guidebook/SpaceStation14.xml
#	Resources/ServerInfo/Guidebook/Survival.xml
#	Resources/Textures/Clothing/Head/Hardsuits/atmospherics.rsi/meta.json
#	Resources/Textures/Clothing/Head/Hardsuits/capspace.rsi/meta.json
#	Resources/Textures/Clothing/Head/Hardsuits/cybersun.rsi/meta.json
#	Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/meta.json
#	Resources/Textures/Clothing/Head/Hardsuits/engineering-white.rsi/on-equipped-HELMET.png
#	Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/icon.png
#	Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/meta.json
#	Resources/Textures/Clothing/Head/Hardsuits/engineering.rsi/on-equipped-HELMET.png
#	Resources/Textures/Clothing/Head/Hardsuits/medical.rsi/meta.json
#	Resources/Textures/Clothing/Head/Hardsuits/rd.rsi/meta.json
#	Resources/Textures/Clothing/Head/Hardsuits/salvage.rsi/meta.json
#	Resources/Textures/Clothing/Head/Hardsuits/security.rsi/meta.json
#	Resources/Textures/Clothing/Head/Hardsuits/spatiohelm.rsi/meta.json
#	Resources/Textures/Clothing/Head/Hardsuits/syndicate.rsi/meta.json
#	Resources/Textures/Clothing/Head/Hardsuits/syndiecommander.rsi/meta.json
#	Resources/Textures/Clothing/Head/Hardsuits/syndieelite.rsi/meta.json
#	Resources/Textures/Clothing/Head/Hardsuits/wizard.rsi/meta.json
#	Resources/Textures/Clothing/Head/Helmets/atmos_firehelmet.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Armor/captain_carapace.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Armor/security.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/atmospherics.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/capspace.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/cybersun.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering-white.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/engineering.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/medical.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/paramed.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/rd.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/salvage.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/security.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/spatio.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/syndicate.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/syndiecommander.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/syndieelite.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Hardsuits/wizard.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Suits/atmos_firesuit.rsi/meta.json
#	Resources/Textures/Clothing/OuterClothing/Suits/eva.rsi/meta.json
#	Resources/Textures/Interface/Misc/job_icons.rsi/meta.json
#	Resources/Textures/Objects/Misc/Lights/lampgreen.rsi/meta.json
#	Resources/Textures/Objects/Specific/Janitorial/janitorial_cart.rsi/meta.json
#	Resources/Textures/Objects/Tools/spray_painter.rsi/meta.json
#	Resources/Textures/Objects/Weapons/Melee/baseball_bat.rsi/meta.json
#	Resources/engineCommandPerms.yml
#	Resources/keybinds.yml
#	Resources/migration.yml
2025-08-10 01:49:23 +03:00

1089 lines
40 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Numerics;
using Content.Server.Shuttles.Components;
using Content.Server.Shuttles.Events;
using Content.Server.Station.Events;
using Content.Shared.Atmos.Components;
using Content.Shared.Body.Components;
using Content.Shared.CCVar;
using Content.Shared.Database;
using Content.Shared.Ghost;
using Content.Shared.Maps;
using Content.Shared.Parallax;
using Content.Shared.Shuttles.Components;
using Content.Shared.Shuttles.Systems;
using Content.Shared.StatusEffect;
using Content.Shared.Timing;
using Content.Shared.Whitelist;
using JetBrains.Annotations;
using Robust.Shared.Audio;
using Robust.Shared.Audio.Components;
using Robust.Shared.Collections;
using Robust.Shared.Map;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Player;
using Robust.Shared.Utility;
using FTLMapComponent = Content.Shared.Shuttles.Components.FTLMapComponent;
namespace Content.Server.Shuttles.Systems;
public sealed partial class ShuttleSystem
{
/*
* This is a way to move a shuttle from one location to another, via an intermediate map for fanciness.
*/
private readonly SoundSpecifier _startupSound = new SoundPathSpecifier("/Audio/Effects/Shuttle/hyperspace_begin.ogg")
{
Params = AudioParams.Default.WithVolume(-5f),
};
private readonly SoundSpecifier _arrivalSound = new SoundPathSpecifier("/Audio/Effects/Shuttle/hyperspace_end.ogg")
{
Params = AudioParams.Default.WithVolume(-5f),
};
public float DefaultStartupTime;
public float DefaultTravelTime;
public float DefaultArrivalTime;
private float FTLCooldown;
public float FTLMassLimit;
private TimeSpan _hyperspaceKnockdownTime = TimeSpan.FromSeconds(5);
private const float _ftlThrowForce = 20.0f;
/// <summary>
/// Left-side of the station we're allowed to use
/// </summary>
private float _index;
/// <summary>
/// Space between grids within hyperspace.
/// </summary>
private const float Buffer = 5f;
/// <summary>
/// How many times we try to proximity warp close to something before falling back to map-wideAABB.
/// </summary>
private const int FTLProximityIterations = 5;
private readonly HashSet<EntityUid> _lookupEnts = new();
private readonly HashSet<EntityUid> _immuneEnts = new();
private readonly HashSet<Entity<NoFTLComponent>> _noFtls = new();
private EntityQuery<BodyComponent> _bodyQuery;
private EntityQuery<FTLSmashImmuneComponent> _immuneQuery;
private EntityQuery<StatusEffectsComponent> _statusQuery;
private EntityQuery<MovedByPressureComponent> _movedByPressureQuery;
private void InitializeFTL()
{
SubscribeLocalEvent<StationPostInitEvent>(OnStationPostInit);
SubscribeLocalEvent<FTLComponent, ComponentShutdown>(OnFtlShutdown);
_bodyQuery = GetEntityQuery<BodyComponent>();
_immuneQuery = GetEntityQuery<FTLSmashImmuneComponent>();
_statusQuery = GetEntityQuery<StatusEffectsComponent>();
_movedByPressureQuery = GetEntityQuery<MovedByPressureComponent>();
_cfg.OnValueChanged(CCVars.FTLStartupTime, time => DefaultStartupTime = time, true);
_cfg.OnValueChanged(CCVars.FTLTravelTime, time => DefaultTravelTime = time, true);
_cfg.OnValueChanged(CCVars.FTLArrivalTime, time => DefaultArrivalTime = time, true);
_cfg.OnValueChanged(CCVars.FTLCooldown, time => FTLCooldown = time, true);
_cfg.OnValueChanged(CCVars.FTLMassLimit, time => FTLMassLimit = time, true);
_cfg.OnValueChanged(CCVars.HyperspaceKnockdownTime, time => _hyperspaceKnockdownTime = TimeSpan.FromSeconds(time), true);
}
private void OnFtlShutdown(Entity<FTLComponent> ent, ref ComponentShutdown args)
{
QueueDel(ent.Comp.VisualizerEntity);
ent.Comp.VisualizerEntity = null;
}
private void OnStationPostInit(ref StationPostInitEvent ev)
{
// Add all grid maps as ftl destinations that anyone can FTL to.
foreach (var gridUid in ev.Station.Comp.Grids)
{
var gridXform = _xformQuery.GetComponent(gridUid);
if (gridXform.MapUid == null)
{
continue;
}
TryAddFTLDestination(gridXform.MapID, true, false, false, out _);
}
}
/// <summary>
/// Ensures the FTL map exists and returns it.
/// </summary>
public EntityUid EnsureFTLMap()
{
var query = AllEntityQuery<FTLMapComponent>();
while (query.MoveNext(out var uid, out _))
{
return uid;
}
var mapUid = _mapSystem.CreateMap(out var mapId);
var ftlMap = AddComp<FTLMapComponent>(mapUid);
_metadata.SetEntityName(mapUid, "FTL");
Log.Debug($"Setup hyperspace map at {mapUid}");
DebugTools.Assert(!_mapSystem.IsPaused(mapId));
var parallax = EnsureComp<ParallaxComponent>(mapUid);
parallax.Parallax = ftlMap.Parallax;
return mapUid;
}
public StartEndTime GetStateTime(FTLComponent component)
{
var state = component.State;
switch (state)
{
case FTLState.Starting:
case FTLState.Travelling:
case FTLState.Arriving:
case FTLState.Cooldown:
return component.StateTime;
case FTLState.Available:
return default;
default:
throw new NotImplementedException();
}
}
/// <summary>
/// Updates the whitelist for this FTL destination.
/// </summary>
/// <param name="entity"></param>
/// <param name="whitelist"></param>
public void SetFTLWhitelist(Entity<FTLDestinationComponent?> entity, EntityWhitelist? whitelist)
{
if (!Resolve(entity, ref entity.Comp))
return;
if (entity.Comp.Whitelist == whitelist)
return;
entity.Comp.Whitelist = whitelist;
_console.RefreshShuttleConsoles();
Dirty(entity);
}
/// <summary>
/// Adds the target map as available for FTL.
/// </summary>
public bool TryAddFTLDestination(MapId mapId, bool enabled, [NotNullWhen(true)] out FTLDestinationComponent? component)
{
return TryAddFTLDestination(mapId, enabled, true, false, out component);
}
public bool TryAddFTLDestination(MapId mapId, bool enabled, bool requireDisk, bool beaconsOnly, [NotNullWhen(true)] out FTLDestinationComponent? component)
{
var mapUid = _mapSystem.GetMapOrInvalid(mapId);
component = null;
if (!Exists(mapUid))
return false;
component = EnsureComp<FTLDestinationComponent>(mapUid);
if (component.Enabled == enabled && component.RequireCoordinateDisk == requireDisk && component.BeaconsOnly == beaconsOnly)
return true;
component.Enabled = enabled;
component.RequireCoordinateDisk = requireDisk;
component.BeaconsOnly = beaconsOnly;
_console.RefreshShuttleConsoles();
Dirty(mapUid, component);
return true;
}
[PublicAPI]
public void RemoveFTLDestination(EntityUid uid)
{
if (!RemComp<FTLDestinationComponent>(uid))
return;
_console.RefreshShuttleConsoles();
}
/// <summary>
/// Returns true if the grid can FTL. Used to block protected shuttles like the emergency shuttle.
/// </summary>
public bool CanFTL(EntityUid shuttleUid, [NotNullWhen(false)] out string? reason)
{
// Currently in FTL already
if (HasComp<FTLComponent>(shuttleUid))
{
reason = Loc.GetString("shuttle-console-in-ftl");
return false;
}
if (TryComp<PhysicsComponent>(shuttleUid, out var shuttlePhysics))
{
// Too large to FTL
if (FTLMassLimit > 0 && shuttlePhysics.Mass > FTLMassLimit)
{
reason = Loc.GetString("shuttle-console-mass");
return false;
}
}
if (HasComp<PreventPilotComponent>(shuttleUid) || HasComp<PreventFTLComponent>(shuttleUid))
{
reason = Loc.GetString("shuttle-console-prevent");
return false;
}
var ev = new ConsoleFTLAttemptEvent(shuttleUid, false, string.Empty);
RaiseLocalEvent(shuttleUid, ref ev, true);
if (ev.Cancelled)
{
reason = ev.Reason;
return false;
}
reason = null;
return true;
}
/// <summary>
/// Moves a shuttle from its current position to the target one without any checks. Goes through the hyperspace map while the timer is running.
/// </summary>
public void FTLToCoordinates(
EntityUid shuttleUid,
ShuttleComponent component,
EntityCoordinates coordinates,
Angle angle,
float? startupTime = null,
float? hyperspaceTime = null,
string? priorityTag = null)
{
if (!TrySetupFTL(shuttleUid, component, out var hyperspace))
return;
startupTime ??= DefaultStartupTime;
hyperspaceTime ??= DefaultTravelTime;
hyperspace.StartupTime = startupTime.Value;
hyperspace.TravelTime = hyperspaceTime.Value;
hyperspace.StateTime = StartEndTime.FromStartDuration(
_gameTiming.CurTime,
TimeSpan.FromSeconds(hyperspace.StartupTime));
hyperspace.TargetCoordinates = coordinates;
hyperspace.TargetAngle = angle;
hyperspace.PriorityTag = priorityTag;
_console.RefreshShuttleConsoles(shuttleUid);
var mapId = _transform.GetMapId(coordinates);
var mapUid = _mapSystem.GetMap(mapId);
var ev = new FTLRequestEvent(mapUid);
RaiseLocalEvent(shuttleUid, ref ev, true);
}
/// <summary>
/// Moves a shuttle from its current position to docked on the target one.
/// If no docks are free when FTLing it will arrive in proximity
/// </summary>
public void FTLToDock(
EntityUid shuttleUid,
ShuttleComponent component,
EntityUid target,
float? startupTime = null,
float? hyperspaceTime = null,
string? priorityTag = null,
bool ignored = false, // Sunrise-Edit
bool deletedTrash = false) // Sunrise-Edit
{
if (!TrySetupFTL(shuttleUid, component, out var hyperspace))
return;
startupTime ??= DefaultStartupTime;
hyperspaceTime ??= DefaultTravelTime;
var config = _dockSystem.GetDockingConfig(shuttleUid, target, priorityTag, ignored);
hyperspace.StartupTime = startupTime.Value;
hyperspace.TravelTime = hyperspaceTime.Value;
hyperspace.StateTime = StartEndTime.FromStartDuration(
_gameTiming.CurTime,
TimeSpan.FromSeconds(hyperspace.StartupTime));
hyperspace.PriorityTag = priorityTag;
hyperspace.Ignored = ignored; // Sunrise-Edit
hyperspace.DeleteTrash = deletedTrash; // Sunrise-Edit
_console.RefreshShuttleConsoles(shuttleUid);
// Valid dock for now time so just use that as the target.
if (config != null)
{
hyperspace.TargetCoordinates = config.Coordinates;
hyperspace.TargetAngle = config.Angle;
}
else if (TryGetFTLProximity(shuttleUid, new EntityCoordinates(target, Vector2.Zero), out var coords, out var targAngle))
{
hyperspace.TargetCoordinates = coords;
hyperspace.TargetAngle = targAngle;
}
else
{
// FTL back to its own position.
hyperspace.TargetCoordinates = Transform(shuttleUid).Coordinates;
Log.Error($"Unable to FTL grid {ToPrettyString(shuttleUid)} to target properly?");
}
}
private bool TrySetupFTL(EntityUid uid, ShuttleComponent shuttle, [NotNullWhen(true)] out FTLComponent? component)
{
component = null;
if (HasComp<FTLComponent>(uid))
{
Log.Warning($"Tried queuing {ToPrettyString(uid)} which already has {nameof(FTLComponent)}?");
return false;
}
_thruster.DisableLinearThrusters(shuttle);
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.North);
_thruster.SetAngularThrust(shuttle, false);
_dockSystem.UndockDocks(uid);
component = AddComp<FTLComponent>(uid);
component.State = FTLState.Starting;
var audio = _audio.PlayPvs(_startupSound, uid);
_audio.SetGridAudio(audio);
component.StartupStream = audio?.Entity;
// Make sure the map is setup before we leave to avoid pop-in (e.g. parallax).
EnsureFTLMap();
return true;
}
/// <summary>
/// Transitions shuttle to FTL map.
/// </summary>
private void UpdateFTLStarting(Entity<FTLComponent, ShuttleComponent> entity)
{
var uid = entity.Owner;
var comp = entity.Comp1;
var xform = _xformQuery.GetComponent(entity);
// Sunrise-Edit
//DoTheDinosaur(xform);
comp.State = FTLState.Travelling;
var fromMapUid = xform.MapUid;
var fromMatrix = _transform.GetWorldMatrix(xform);
var fromRotation = _transform.GetWorldRotation(xform);
var grid = Comp<MapGridComponent>(uid);
var width = grid.LocalAABB.Width;
var ftlMap = EnsureFTLMap();
var body = _physicsQuery.GetComponent(entity);
var shuttleCenter = grid.LocalAABB.Center;
// Leave audio at the old spot
// Just so we don't clip
if (fromMapUid != null && TryComp(comp.StartupStream, out AudioComponent? startupAudio))
{
var clippedAudio = _audio.PlayStatic(_startupSound, Filter.Broadcast(),
new EntityCoordinates(fromMapUid.Value, _mapSystem.GetGridPosition(entity.Owner)), true, startupAudio.Params);
_audio.SetPlaybackPosition(clippedAudio, entity.Comp1.StartupTime);
if (clippedAudio != null)
clippedAudio.Value.Component.Flags |= AudioFlags.NoOcclusion;
}
// Offset the start by buffer range just to avoid overlap.
var ftlStart = new EntityCoordinates(ftlMap, new Vector2(_index + width / 2f, 0f) - shuttleCenter);
// Store the matrix for the grid prior to movement. This means any entities we need to leave behind we can make sure their positions are updated.
// Setting the entity to map directly may run grid traversal (at least at time of writing this).
var oldMapUid = xform.MapUid;
var oldGridMatrix = _transform.GetWorldMatrix(xform);
_transform.SetCoordinates(entity.Owner, ftlStart);
LeaveNoFTLBehind((entity.Owner, xform), oldGridMatrix, oldMapUid);
// Reset rotation so they always face the same direction.
xform.LocalRotation = Angle.Zero;
_index += width + Buffer;
comp.StateTime = StartEndTime.FromCurTime(_gameTiming, comp.TravelTime - DefaultArrivalTime);
Enable(uid, component: body);
_physics.SetLinearVelocity(uid, new Vector2(0f, 20f), body: body);
_physics.SetAngularVelocity(uid, 0f, body: body);
_dockSystem.SetDockBolts(uid, true);
_console.RefreshShuttleConsoles(uid);
var ev = new FTLStartedEvent(uid, comp.TargetCoordinates, fromMapUid, fromMatrix, fromRotation);
RaiseLocalEvent(uid, ref ev, true);
// Audio
var wowdio = _audio.PlayPvs(comp.TravelSound, uid);
comp.TravelStream = wowdio?.Entity;
_audio.SetGridAudio(wowdio);
// Sunrise-Start
DoTheDinosaur(xform, Direction.South.ToVec());
// Sunrise-End
}
/// <summary>
/// Shuttle arriving.
/// </summary>
private void UpdateFTLTravelling(Entity<FTLComponent, ShuttleComponent> entity)
{
var shuttle = entity.Comp2;
var comp = entity.Comp1;
comp.StateTime = StartEndTime.FromCurTime(_gameTiming, DefaultArrivalTime);
comp.State = FTLState.Arriving;
// Sunrise-Start
// Почему оно здесь а не в UpdateFTLArriving?
// Ну потому что когда шаттл выходит из фтл он телепортируется
// к докам и меняет угол поворота. А вот брошеная сущность все еще летит
// в том направлении когда шаттл был в фтл зоне,
// как итог полет будет не назад или вперед а в бок.
var xform = _xformQuery.GetComponent(entity.Owner);
DoTheDinosaur(xform, Direction.North.ToVec());
// Sunrise-End
if (entity.Comp1.VisualizerProto != null)
{
comp.VisualizerEntity = SpawnAttachedTo(entity.Comp1.VisualizerProto, entity.Comp1.TargetCoordinates);
DebugTools.Assert(Transform(comp.VisualizerEntity.Value).ParentUid == entity.Comp1.TargetCoordinates.EntityId);
var visuals = Comp<FtlVisualizerComponent>(comp.VisualizerEntity.Value);
visuals.Grid = entity.Owner;
Dirty(comp.VisualizerEntity.Value, visuals);
_transform.SetLocalRotation(comp.VisualizerEntity.Value, entity.Comp1.TargetAngle);
_pvs.AddGlobalOverride(comp.VisualizerEntity.Value);
}
_thruster.DisableLinearThrusters(shuttle);
_thruster.EnableLinearThrustDirection(shuttle, DirectionFlag.South);
_console.RefreshShuttleConsoles(entity.Owner);
}
/// <summary>
/// Shuttle arrived.
/// </summary>
private void UpdateFTLArriving(Entity<FTLComponent, ShuttleComponent> entity)
{
var uid = entity.Owner;
var xform = _xformQuery.GetComponent(uid);
var body = _physicsQuery.GetComponent(uid);
var comp = entity.Comp1;
// Sunrise-Edit
//DoTheDinosaur(xform);
_dockSystem.SetDockBolts(entity, false);
_physics.SetLinearVelocity(uid, Vector2.Zero, body: body);
_physics.SetAngularVelocity(uid, 0f, body: body);
var target = entity.Comp1.TargetCoordinates;
MapId mapId;
QueueDel(entity.Comp1.VisualizerEntity);
entity.Comp1.VisualizerEntity = null;
if (!Exists(entity.Comp1.TargetCoordinates.EntityId))
{
// Uhh good luck
// Pick earliest map?
var maps = EntityQuery<MapComponent>().Select(o => o.MapId).ToList();
var map = maps.Min(o => o.GetHashCode());
mapId = new MapId(map);
TryFTLProximity(uid, _mapSystem.GetMap(mapId));
}
// Docking FTL
else if (HasComp<MapGridComponent>(target.EntityId) &&
!HasComp<MapComponent>(target.EntityId))
{
var config = _dockSystem.GetDockingConfigAt(uid, target.EntityId, target, entity.Comp1.TargetAngle,
entity.Comp1.Ignored, priorityTag: entity.Comp1.PriorityTag); // Sunrise-Edit
var mapCoordinates = _transform.ToMapCoordinates(target);
// Couldn't dock somehow so just fallback to regular position FTL.
if (config == null)
{
TryFTLProximity(uid, target.EntityId);
}
else
{
FTLDock((uid, xform), config, entity.Comp1.DeleteTrash);
}
mapId = mapCoordinates.MapId;
}
// Position ftl
else
{
// TODO: This should now use tryftlproximity
mapId = _transform.GetMapId(target);
_transform.SetCoordinates(uid, xform, target, rotation: entity.Comp1.TargetAngle);
}
if (_physicsQuery.TryGetComponent(uid, out body))
{
_physics.SetLinearVelocity(uid, Vector2.Zero, body: body);
_physics.SetAngularVelocity(uid, 0f, body: body);
// Disable shuttle if it's on a planet; unfortunately can't do this in parent change messages due
// to event ordering and awake body shenanigans (at least for now).
if (HasComp<MapGridComponent>(xform.MapUid))
{
Disable(uid, component: body);
}
else
{
Enable(uid, component: body, shuttle: entity.Comp2);
}
}
_thruster.DisableLinearThrusters(entity.Comp2);
comp.TravelStream = _audio.Stop(comp.TravelStream);
var audio = _audio.PlayPvs(_arrivalSound, uid);
_audio.SetGridAudio(audio);
if (TryComp<FTLDestinationComponent>(uid, out var dest))
{
dest.Enabled = true;
}
comp.State = FTLState.Cooldown;
comp.StateTime = StartEndTime.FromCurTime(_gameTiming, FTLCooldown);
_console.RefreshShuttleConsoles(uid);
_mapSystem.SetPaused(mapId, false);
Smimsh(uid, xform: xform);
var ftlEvent = new FTLCompletedEvent(uid, _mapSystem.GetMap(mapId));
RaiseLocalEvent(uid, ref ftlEvent, true);
}
private void UpdateFTLCooldown(Entity<FTLComponent, ShuttleComponent> entity)
{
RemCompDeferred<FTLComponent>(entity);
_console.RefreshShuttleConsoles(entity);
}
private void UpdateHyperspace()
{
var curTime = _gameTiming.CurTime;
var query = EntityQueryEnumerator<FTLComponent, ShuttleComponent>();
while (query.MoveNext(out var uid, out var comp, out var shuttle))
{
if (curTime < comp.StateTime.End)
continue;
var entity = (uid, comp, shuttle);
switch (comp.State)
{
// Startup time has elapsed and in hyperspace.
case FTLState.Starting:
UpdateFTLStarting(entity);
break;
// Arriving, play effects
case FTLState.Travelling:
UpdateFTLTravelling(entity);
break;
// Arrived
case FTLState.Arriving:
UpdateFTLArriving(entity);
break;
case FTLState.Cooldown:
UpdateFTLCooldown(entity);
break;
default:
Log.Error($"Found invalid FTL state {comp.State} for {uid}");
RemCompDeferred<FTLComponent>(uid);
break;
}
}
}
private float GetSoundRange(EntityUid uid)
{
if (!TryComp<MapGridComponent>(uid, out var grid))
return 4f;
return MathF.Max(grid.LocalAABB.Width, grid.LocalAABB.Height) + 12.5f;
}
/// <summary>
/// Puts everyone unbuckled on the floor, paralyzed.
/// </summary>
private void DoTheDinosaur(TransformComponent xform, Vector2 throwDirection) // Sunrise-Edit
{
// Get enumeration exceptions from people dropping things if we just paralyze as we go
var toKnock = new ValueList<EntityUid>();
KnockOverKids(xform, ref toKnock);
TryComp<MapGridComponent>(xform.GridUid, out var grid);
if (TryComp<PhysicsComponent>(xform.GridUid, out var shuttleBody))
{
foreach (var child in toKnock)
{
_stuns.TryUpdateParalyzeDuration(child, _hyperspaceKnockdownTime);
// Sunrise-Start
if (_physicsQuery.TryGetComponent(child, out var physics))
{
if ((physics.BodyType & BodyType.Static) != 0)
continue;
_throwing.TryThrow(child,
throwDirection * _ftlThrowForce,
physics,
Transform(child),
_projQuery,
_ftlThrowForce,
playSound: false);
}
// Sunrise-End
// If the guy we knocked down is on a spaced tile, throw them too
if (grid != null)
TossIfSpaced((xform.GridUid.Value, grid, shuttleBody), child);
}
}
}
private void LeaveNoFTLBehind(Entity<TransformComponent> grid, Matrix3x2 oldGridMatrix, EntityUid? oldMapUid)
{
if (oldMapUid == null)
return;
_noFtls.Clear();
var oldGridRotation = oldGridMatrix.Rotation();
_lookup.GetGridEntities(grid.Owner, _noFtls);
foreach (var childUid in _noFtls)
{
if (!_xformQuery.TryComp(childUid, out var childXform))
continue;
// If we're not parented directly to the grid the matrix may be wrong.
var relative = _physics.GetRelativePhysicsTransform(childUid.Owner, (grid.Owner, grid.Comp));
_transform.SetCoordinates(
childUid,
childXform,
new EntityCoordinates(oldMapUid.Value,
Vector2.Transform(relative.Position, oldGridMatrix)), rotation: relative.Quaternion2D.Angle + oldGridRotation);
}
}
private void KnockOverKids(TransformComponent xform, ref ValueList<EntityUid> toKnock)
{
// Not recursive because probably not necessary? If we need it to be that's why this method is separate.
var childEnumerator = xform.ChildEnumerator;
while (childEnumerator.MoveNext(out var child))
{
if (!_buckleQuery.TryGetComponent(child, out var buckle) || buckle.Buckled)
continue;
// Sunrise-Start
if (_movedByPressureQuery.TryComp(child, out var moved) && !moved.Enabled)
continue;
// Sunrise-End
toKnock.Add(child);
}
}
/// <summary>
/// Throws people who are standing on a spaced tile, tries to throw them towards a neighbouring space tile
/// </summary>
private void TossIfSpaced(Entity<MapGridComponent, PhysicsComponent> shuttleEntity, EntityUid tossed)
{
var shuttleGrid = shuttleEntity.Comp1;
var shuttleBody = shuttleEntity.Comp2;
if (!_xformQuery.TryGetComponent(tossed, out var childXform))
return;
// only toss if its on lattice/space
var tile = _mapSystem.GetTileRef(shuttleEntity, shuttleGrid, childXform.Coordinates);
if (!_turf.IsSpace(tile))
return;
var throwDirection = childXform.LocalPosition - shuttleBody.LocalCenter;
if (throwDirection == Vector2.Zero)
return;
_throwing.TryThrow(tossed, throwDirection.Normalized() * 10.0f, 50.0f);
}
/// <summary>
/// Tries to dock with the target grid, otherwise falls back to proximity.
/// This bypasses FTL travel time.
/// </summary>
public bool TryFTLDock(
EntityUid shuttleUid,
ShuttleComponent component,
EntityUid targetUid,
string? priorityTag = null,
bool ignored = false, // Sunrise-Edit
bool deletedTrash = false) // Sunrise-Edit
{
return TryFTLDock(shuttleUid, component, targetUid, out _, priorityTag, ignored, deletedTrash);
}
/// <summary>
/// Tries to dock with the target grid, otherwise falls back to proximity.
/// This bypasses FTL travel time.
/// </summary>
public bool TryFTLDock(
EntityUid shuttleUid,
ShuttleComponent component,
EntityUid targetUid,
[NotNullWhen(true)] out DockingConfig? config,
string? priorityTag = null,
bool ignored = false, // Sunrise-Edit
bool deletedTrash = false) // Sunrise-Edit
{
config = null;
if (!_xformQuery.TryGetComponent(shuttleUid, out var shuttleXform) ||
!_xformQuery.TryGetComponent(targetUid, out var targetXform) ||
targetXform.MapUid == null ||
!targetXform.MapUid.Value.IsValid())
{
return false;
}
config = _dockSystem.GetDockingConfig(shuttleUid, targetUid, priorityTag, ignored); // Sunrise-Edit
if (config != null)
{
FTLDock((shuttleUid, shuttleXform), config, deletedTrash);
return true;
}
TryFTLProximity(shuttleUid, targetUid, shuttleXform, targetXform);
return false;
}
/// <summary>
/// Forces an FTL dock.
/// </summary>
public void FTLDock(Entity<TransformComponent> shuttle, DockingConfig config, bool deletedTrash = false)
{
// Set position
var mapCoordinates = _transform.ToMapCoordinates(config.Coordinates);
var mapUid = _mapSystem.GetMap(mapCoordinates.MapId);
_transform.SetCoordinates(shuttle.Owner, shuttle.Comp, new EntityCoordinates(mapUid, mapCoordinates.Position), rotation: config.Angle + _transform.GetWorldRotation(config.Coordinates.EntityId));
// Connect everything
foreach (var (dockAUid, dockBUid, dockA, dockB) in config.Docks)
{
_dockSystem.Dock((dockAUid, dockA), (dockBUid, dockB));
}
// Sunrise-Start
if (deletedTrash &&
TryComp<FixturesComponent>(shuttle.Owner, out var fixtures) &&
TryComp<MapGridComponent>(shuttle.Owner, out var shuttleGrid))
{
var xform = Transform(shuttle.Owner);
var transform = _physics.GetPhysicsTransform(shuttle.Owner, xform);
foreach (var fixture in fixtures.Fixtures.Values)
{
if (!fixture.Hard)
continue;
var aabb = fixture.Shape.ComputeAABB(transform, 0);
aabb = aabb.Translated(-shuttleGrid.TileSizeHalfVector);
var grids = new List<Entity<MapGridComponent>>();
_mapManager.FindGridsIntersecting(shuttle.Comp.MapID, aabb, ref grids, includeMap: false);
foreach (var grid in grids)
{
if (grid.Owner == config.TargetGrid || grid.Owner == shuttle.Owner)
continue;
QueueDel(grid);
}
}
}
// Sunrise-End
}
/// <summary>
/// Tries to get the target position to FTL near the target coordinates.
/// If the target coordinates have a mapgrid then will try to offset the AABB.
/// </summary>
/// <param name="minOffset">Min offset for the final FTL.</param>
/// <param name="maxOffset">Max offset for the final FTL from the box we spawn.</param>
private bool TryGetFTLProximity(
EntityUid shuttleUid,
EntityCoordinates targetCoordinates,
out EntityCoordinates coordinates, out Angle angle,
float minOffset = 0f, float maxOffset = 64f,
TransformComponent? xform = null, TransformComponent? targetXform = null)
{
DebugTools.Assert(minOffset < maxOffset);
coordinates = EntityCoordinates.Invalid;
angle = Angle.Zero;
if (!Resolve(targetCoordinates.EntityId, ref targetXform) ||
targetXform.MapUid == null ||
!targetXform.MapUid.Value.IsValid() ||
!Resolve(shuttleUid, ref xform))
{
return false;
}
// We essentially expand the Box2 of the target area until nothing else is added then we know it's valid.
// Can't just get an AABB of every grid as we may spawn very far away.
var nearbyGrids = new HashSet<EntityUid>();
var shuttleAABB = Comp<MapGridComponent>(shuttleUid).LocalAABB;
// Start with small point.
// If our target pos is offset we mot even intersect our target's AABB so we don't include it.
var targetLocalAABB = Box2.CenteredAround(targetCoordinates.Position, Vector2.One);
// How much we expand the target AABB be.
// We half it because we only need the width / height in each direction if it's placed at a particular spot.
var expansionAmount = MathF.Max(shuttleAABB.Width / 2f, shuttleAABB.Height / 2f);
// Expand the starter AABB so we have something to query to start with.
var targetAABB = _transform.GetWorldMatrix(targetXform)
.TransformBox(targetLocalAABB)
.Enlarged(expansionAmount);
var iteration = 0;
var lastCount = nearbyGrids.Count;
var mapId = targetXform.MapID;
var grids = new List<Entity<MapGridComponent>>();
while (iteration < FTLProximityIterations)
{
grids.Clear();
// We pass in an expanded offset here so we can safely do a random offset later.
// We don't include this in the actual targetAABB because then we would be double-expanding it.
// Once in this loop, then again when placing the shuttle later.
// Note that targetAABB already has expansionAmount factored in already.
_mapManager.FindGridsIntersecting(mapId, targetAABB.Enlarged(maxOffset), ref grids);
foreach (var grid in grids)
{
if (!nearbyGrids.Add(grid))
continue;
// Include the other grid's AABB (expanded by ours) as well.
targetAABB = targetAABB.Union(
_transform.GetWorldMatrix(grid)
.TransformBox(Comp<MapGridComponent>(grid).LocalAABB.Enlarged(expansionAmount)));
}
// Can do proximity
if (nearbyGrids.Count == lastCount)
{
break;
}
iteration++;
lastCount = nearbyGrids.Count;
// Mishap moment, dense asteroid field or whatever
if (iteration != FTLProximityIterations)
continue;
var query = AllEntityQuery<MapGridComponent>();
while (query.MoveNext(out var uid, out var grid))
{
// Don't add anymore as it is irrelevant, but that doesn't mean we need to re-do existing work.
if (nearbyGrids.Contains(uid))
continue;
targetAABB = targetAABB.Union(
_transform.GetWorldMatrix(uid)
.TransformBox(Comp<MapGridComponent>(uid).LocalAABB.Enlarged(expansionAmount)));
}
break;
}
// Now we have a targetAABB. This has already been expanded to account for our fat ass.
Vector2 spawnPos;
if (TryComp<PhysicsComponent>(shuttleUid, out var shuttleBody))
{
_physics.SetLinearVelocity(shuttleUid, Vector2.Zero, body: shuttleBody);
_physics.SetAngularVelocity(shuttleUid, 0f, body: shuttleBody);
}
// TODO: This should prefer the position's angle instead.
// TODO: This is pretty crude for multiple landings.
if (nearbyGrids.Count > 1 || !HasComp<MapComponent>(targetXform.GridUid))
{
// Pick a random angle
var offsetAngle = _random.NextAngle();
// Our valid spawn positions are <targetAABB width / height + offset> away.
var minRadius = MathF.Max(targetAABB.Width / 2f, targetAABB.Height / 2f);
spawnPos = targetAABB.Center + offsetAngle.RotateVec(new Vector2(_random.NextFloat(minRadius + minOffset, minRadius + maxOffset), 0f));
}
else if (shuttleBody != null)
{
(spawnPos, angle) = _transform.GetWorldPositionRotation(targetXform);
}
else
{
spawnPos = _transform.GetWorldPosition(targetXform);
}
var offset = Vector2.Zero;
// Offset it because transform does not correspond to AABB position.
if (TryComp(shuttleUid, out MapGridComponent? shuttleGrid))
{
offset = -shuttleGrid.LocalAABB.Center;
}
if (!HasComp<MapComponent>(targetXform.GridUid))
{
angle = _random.NextAngle();
}
else
{
angle = Angle.Zero;
}
// Rotate our localcenter around so we spawn exactly where we "think" we should (center of grid on the dot).
var transform = new Transform(spawnPos, angle);
spawnPos = Robust.Shared.Physics.Transform.Mul(transform, offset);
coordinates = new EntityCoordinates(targetXform.MapUid.Value, spawnPos - offset);
return true;
}
/// <summary>
/// Tries to arrive nearby without overlapping with other grids.
/// </summary>
public bool TryFTLProximity(EntityUid shuttleUid, EntityUid targetUid, TransformComponent? xform = null, TransformComponent? targetXform = null)
{
if (!Resolve(targetUid, ref targetXform) ||
targetXform.MapUid == null ||
!targetXform.MapUid.Value.IsValid() ||
!Resolve(shuttleUid, ref xform))
{
return false;
}
if (!TryGetFTLProximity(shuttleUid, new EntityCoordinates(targetUid, Vector2.Zero), out var coords, out var angle, xform: xform, targetXform: targetXform))
return false;
_transform.SetCoordinates(shuttleUid, xform, coords, rotation: angle);
return true;
}
/// <summary>
/// Tries to FTL to the target coordinates; will move nearby if not possible.
/// </summary>
public bool TryFTLProximity(Entity<TransformComponent?> shuttle, EntityCoordinates targetCoordinates)
{
if (!Resolve(shuttle.Owner, ref shuttle.Comp) ||
_transform.GetMap(targetCoordinates)?.IsValid() != true)
{
return false;
}
if (!TryGetFTLProximity(shuttle, targetCoordinates, out var coords, out var angle))
return false;
_transform.SetCoordinates(shuttle, shuttle.Comp, coords, rotation: angle);
return true;
}
/// <summary>
/// Flattens / deletes everything under the grid upon FTL.
/// </summary>
private void Smimsh(EntityUid uid, FixturesComponent? manager = null, MapGridComponent? grid = null, TransformComponent? xform = null)
{
if (!Resolve(uid, ref manager, ref grid, ref xform) || xform.MapUid == null)
return;
if (!TryComp(xform.MapUid, out BroadphaseComponent? lookup))
return;
// Flatten anything not parented to a grid.
var transform = _physics.GetRelativePhysicsTransform((uid, xform), xform.MapUid.Value);
var aabbs = new List<Box2>(manager.Fixtures.Count);
var tileSet = new List<(Vector2i, Tile)>();
foreach (var fixture in manager.Fixtures.Values)
{
if (!fixture.Hard)
continue;
var aabb = fixture.Shape.ComputeAABB(transform, 0);
// Shift it slightly
// Create a small border around it.
aabb = aabb.Enlarged(0.2f);
aabbs.Add(aabb);
// Handle clearing biome stuff as relevant.
tileSet.Clear();
_biomes.ReserveTiles(xform.MapUid.Value, aabb, tileSet);
_lookupEnts.Clear();
_immuneEnts.Clear();
// TODO: Ideally we'd query first BEFORE moving grid but needs adjustments above.
_lookup.GetLocalEntitiesIntersecting(xform.MapUid.Value, fixture.Shape, transform, _lookupEnts, flags: LookupFlags.Uncontained, lookup: lookup);
foreach (var ent in _lookupEnts)
{
if (ent == uid || _immuneEnts.Contains(ent))
{
continue;
}
// If it's on our grid ignore it.
if (!_xformQuery.TryComp(ent, out var childXform) || childXform.GridUid == uid)
{
continue;
}
// If it has the FTLSmashImmuneComponent ignore it.
if (_immuneQuery.HasComponent(ent))
{
continue;
}
if (_bodyQuery.TryGetComponent(ent, out var mob))
{
_logger.Add(LogType.Gib, LogImpact.Extreme, $"{ToPrettyString(ent):player} got gibbed by the shuttle" +
$" {ToPrettyString(uid)} arriving from FTL at {xform.Coordinates:coordinates}");
var gibs = _bobby.GibBody(ent, body: mob);
_immuneEnts.UnionWith(gibs);
continue;
}
QueueDel(ent);
}
}
var ev = new ShuttleFlattenEvent(xform.MapUid.Value, aabbs);
RaiseLocalEvent(ref ev);
}
}