Quantum Spin Inverter (#23756)

This commit is contained in:
Nemanja
2024-01-09 02:37:55 -05:00
committed by GitHub
parent f2647ac8d4
commit fdd18c9ae4
14 changed files with 417 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
using Content.Shared.Teleportation.Systems;
using Content.Shared.Whitelist;
using Robust.Shared.Audio;
using Robust.Shared.GameStates;
using Robust.Shared.Serialization;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
namespace Content.Shared.Teleportation.Components;
/// <summary>
/// This is used for an entity that, when linked to another valid entity, allows the two to swap positions,
/// additionally swapping the positions of the parents.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[Access(typeof(SwapTeleporterSystem))]
public sealed partial class SwapTeleporterComponent : Component
{
/// <summary>
/// The other SwapTeleporterComponent that this one is linked to
/// </summary>
[DataField, AutoNetworkedField]
public EntityUid? LinkedEnt;
/// <summary>
/// the time at which <see cref="TeleportDelay"/> ends and the teleportation occurs
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public TimeSpan? TeleportTime;
/// <summary>
/// Delay after starting the teleport and it occuring.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan TeleportDelay = TimeSpan.FromSeconds(2.5f);
/// <summary>
/// The time at which <see cref="Cooldown"/> ends and teleportation can occur again.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
public TimeSpan NextTeleportUse;
/// <summary>
/// A minimum waiting period inbetween teleports.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite)]
public TimeSpan Cooldown = TimeSpan.FromMinutes(5);
/// <summary>
/// Sound played when teleportation begins
/// </summary>
[DataField]
public SoundSpecifier? TeleportSound = new SoundPathSpecifier("/Audio/Weapons/flash.ogg");
/// <summary>
/// A whitelist for what entities are valid for <see cref="LinkedEnt"/>.
/// </summary>
[DataField]
public EntityWhitelist TeleporterWhitelist = new();
}
[Serializable, NetSerializable]
public enum SwapTeleporterVisuals : byte
{
Linked
}

View File

@@ -0,0 +1,246 @@
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Interaction;
using Content.Shared.Popups;
using Content.Shared.Teleportation.Components;
using Content.Shared.Verbs;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map.Components;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Timing;
namespace Content.Shared.Teleportation.Systems;
/// <summary>
/// This handles <see cref="SwapTeleporterComponent"/>
/// </summary>
public sealed class SwapTeleporterSystem : EntitySystem
{
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
private EntityQuery<TransformComponent> _xformQuery;
/// <inheritdoc/>
public override void Initialize()
{
SubscribeLocalEvent<SwapTeleporterComponent, AfterInteractEvent>(OnInteract);
SubscribeLocalEvent<SwapTeleporterComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAltVerb);
SubscribeLocalEvent<SwapTeleporterComponent, ActivateInWorldEvent>(OnActivateInWorld);
SubscribeLocalEvent<SwapTeleporterComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<SwapTeleporterComponent, EntityUnpausedEvent>(OnUnpaused);
SubscribeLocalEvent<SwapTeleporterComponent, ComponentShutdown>(OnShutdown);
_xformQuery = GetEntityQuery<TransformComponent>();
}
private void OnInteract(Entity<SwapTeleporterComponent> ent, ref AfterInteractEvent args)
{
var (uid, comp) = ent;
if (args.Target == null)
return;
var target = args.Target.Value;
if (!TryComp<SwapTeleporterComponent>(target, out var targetComp))
return;
if (!comp.TeleporterWhitelist.IsValid(target, EntityManager) ||
!targetComp.TeleporterWhitelist.IsValid(uid, EntityManager))
{
return;
}
if (comp.LinkedEnt != null)
{
_popup.PopupClient(Loc.GetString("swap-teleporter-popup-link-fail-already"), uid, args.User);
return;
}
if (targetComp.LinkedEnt != null)
{
_popup.PopupClient(Loc.GetString("swap-teleporter-popup-link-fail-already-other"), uid, args.User);
return;
}
comp.LinkedEnt = target;
targetComp.LinkedEnt = uid;
Dirty(uid, comp);
Dirty(target, targetComp);
_appearance.SetData(uid, SwapTeleporterVisuals.Linked, true);
_appearance.SetData(target, SwapTeleporterVisuals.Linked, true);
_popup.PopupClient(Loc.GetString("swap-teleporter-popup-link-create"), uid, args.User);
}
private void OnGetAltVerb(Entity<SwapTeleporterComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
{
var (uid, comp) = ent;
if (!args.CanAccess || !args.CanInteract || args.Hands == null || comp.TeleportTime != null)
return;
if (!TryComp<SwapTeleporterComponent>(comp.LinkedEnt, out var otherComp) || otherComp.TeleportTime != null)
return;
var user = args.User;
args.Verbs.Add(new AlternativeVerb
{
Text = Loc.GetString("swap-teleporter-verb-destroy-link"),
Priority = 1,
Act = () =>
{
DestroyLink((uid, comp), user);
}
});
}
private void OnActivateInWorld(Entity<SwapTeleporterComponent> ent, ref ActivateInWorldEvent args)
{
var (uid, comp) = ent;
var user = args.User;
if (comp.TeleportTime != null)
return;
if (comp.LinkedEnt == null)
{
_popup.PopupClient(Loc.GetString("swap-teleporter-popup-teleport-cancel-link"), ent, user);
return;
}
// don't allow teleporting to happen if the linked one is already teleporting
if (!TryComp<SwapTeleporterComponent>(comp.LinkedEnt, out var otherComp)
|| otherComp.TeleportTime != null)
{
return;
}
if (_timing.CurTime < comp.NextTeleportUse)
{
_popup.PopupClient(Loc.GetString("swap-teleporter-popup-teleport-cancel-time"), ent, user);
return;
}
_audio.PlayPredicted(comp.TeleportSound, uid, user);
_audio.PlayPredicted(otherComp.TeleportSound, comp.LinkedEnt.Value, user);
comp.NextTeleportUse = _timing.CurTime + comp.Cooldown;
comp.TeleportTime = _timing.CurTime + comp.TeleportDelay;
Dirty(uid, comp);
}
public void DoTeleport(Entity<SwapTeleporterComponent, TransformComponent> ent)
{
var (uid, comp, xform) = ent;
comp.TeleportTime = null;
Dirty(uid, comp);
if (comp.LinkedEnt is not { } linkedEnt)
{
return;
}
var teleEnt = GetTeleportingEntity((uid, xform));
var teleEntXform = Transform(teleEnt);
var otherTeleEnt = GetTeleportingEntity((linkedEnt, Transform(linkedEnt)));
var otherTeleEntXform = Transform(otherTeleEnt);
_popup.PopupEntity(Loc.GetString("swap-teleporter-popup-teleport-other",
("entity", Identity.Entity(linkedEnt, EntityManager))),
otherTeleEnt,
otherTeleEnt,
PopupType.MediumCaution);
var pos = teleEntXform.Coordinates;
var otherPos = otherTeleEntXform.Coordinates;
_transform.SetCoordinates(teleEnt, otherPos);
_transform.SetCoordinates(otherTeleEnt, pos);
}
/// <remarks>
/// HYAH -link
/// </remarks>
public void DestroyLink(Entity<SwapTeleporterComponent?> ent, EntityUid? user)
{
if (!Resolve(ent, ref ent.Comp, false))
return;
var linkedNullable = ent.Comp.LinkedEnt;
ent.Comp.LinkedEnt = null;
ent.Comp.TeleportTime = null;
_appearance.SetData(ent, SwapTeleporterVisuals.Linked, false);
Dirty(ent, ent.Comp);
if (user != null)
_popup.PopupClient(Loc.GetString("swap-teleporter-popup-link-destroyed"), ent, user.Value);
else
_popup.PopupEntity(Loc.GetString("swap-teleporter-popup-link-destroyed"), ent);
if (linkedNullable is {} linked)
DestroyLink(linked, user); // the linked one is shown globally
}
private EntityUid GetTeleportingEntity(Entity<TransformComponent> ent)
{
var parent = ent.Comp.ParentUid;
if (HasComp<MapGridComponent>(parent) || HasComp<MapComponent>(parent))
return ent;
if (!_xformQuery.TryGetComponent(parent, out var parentXform) || parentXform.Anchored)
return ent;
if (!TryComp<PhysicsComponent>(parent, out var body) || body.BodyType == BodyType.Static)
return ent;
return GetTeleportingEntity((parent, parentXform));
}
private void OnExamined(Entity<SwapTeleporterComponent> ent, ref ExaminedEvent args)
{
var (_, comp) = ent;
using (args.PushGroup(nameof(SwapTeleporterComponent)))
{
var locale = comp.LinkedEnt == null
? "swap-teleporter-examine-link-absent"
: "swap-teleporter-examine-link-present";
args.PushMarkup(Loc.GetString(locale));
if (_timing.CurTime < comp.NextTeleportUse)
{
args.PushMarkup(Loc.GetString("swap-teleporter-examine-time-remaining",
("second", (int) ((comp.NextTeleportUse - _timing.CurTime).TotalSeconds + 0.5f))));
}
}
}
private void OnUnpaused(Entity<SwapTeleporterComponent> ent, ref EntityUnpausedEvent args)
{
ent.Comp.NextTeleportUse += args.PausedTime;
}
private void OnShutdown(Entity<SwapTeleporterComponent> ent, ref ComponentShutdown args)
{
DestroyLink((ent, ent), null);
}
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<SwapTeleporterComponent, TransformComponent>();
while (query.MoveNext(out var uid, out var comp, out var xform))
{
if (comp.TeleportTime == null)
continue;
if (_timing.CurTime < comp.TeleportTime)
continue;
DoTeleport((uid, comp, xform));
}
}
}

View File

@@ -0,0 +1,17 @@
swap-teleporter-popup-link-create = Quantum link established!
swap-teleporter-popup-link-fail-already = Quantum link failed! Link already present on device.
swap-teleporter-popup-link-fail-already-other = Quantum link failed! Link already present on secondary device.
swap-teleporter-popup-link-destroyed = Quantum link destroyed!
swap-teleporter-popup-teleport-cancel-time = It's still recharging!
swap-teleporter-popup-teleport-cancel-link = It's not linked with another device!
swap-teleporter-popup-teleport-other = {CAPITALIZE(THE($entity))} activates, and you find yourself somewhere else.
swap-teleporter-verb-destroy-link = Destroy Quantum Link
swap-teleporter-examine-link-present = [color=forestgreen]It is linked to another device.[/color] Alt-Click to break the quantum link.
swap-teleporter-examine-link-absent = [color=yellow]It is not currently linked.[/color] Use on another device to establish a quantum link.
swap-teleporter-examine-time-remaining = Time left to recharge: [color=purple]{$second} second{$second ->
[one].
*[other]s.
}[/color]

View File

@@ -56,6 +56,7 @@ research-technology-anomaly-harnessing = Anomaly Core Harnessing
research-technology-grappling = Grappling
research-technology-abnormal-artifact-manipulation = Abnormal Artifact Manipulation
research-technology-gravity-manipulation = Gravity Manipulation
research-technology-quantum-leaping = Quantum Leaping
research-technology-advanced-anomaly-research = Advanced Anomaly Research
research-technology-rped = Rapid Part Exchange
research-technology-super-parts = Super Parts

View File

@@ -0,0 +1,27 @@
- type: entity
parent: BaseItem
id: DeviceQuantumSpinInverter
name: quantum spin inverter
description: An experimental device that is able to swap the locations of two entities by switching their particles' spin values. Must be linked to another device to function.
components:
- type: Sprite
sprite: Objects/Devices/swapper.rsi
layers:
- state: icon
map: ["base"]
- type: Item
size: Small
- type: Appearance
- type: SwapTeleporter
teleporterWhitelist:
tags:
- QuantumSpinInverter
- type: GenericVisualizer
visuals:
enum.SwapTeleporterVisuals.Linked:
base:
True: { state: linked }
False: { state: icon }
- type: Tag
tags:
- QuantumSpinInverter

View File

@@ -300,6 +300,7 @@
- FauxTileAstroGrass
- FauxTileAstroIce
- OreBagOfHolding
- DeviceQuantumSpinInverter
- type: EmagLatheRecipes
emagDynamicRecipes:
- ExplosivePayload

View File

@@ -188,6 +188,15 @@
Glass: 400
Silver: 200
- type: latheRecipe
id: DeviceQuantumSpinInverter
result: DeviceQuantumSpinInverter
completetime: 5
materials:
Steel: 700
Glass: 100
Uranium: 100
- type: latheRecipe
id: WeaponProtoKineticAccelerator
result: WeaponProtoKineticAccelerator

View File

@@ -152,3 +152,15 @@
recipeUnlocks:
- WeaponForceGun
- WeaponTetherGun
- type: technology
id: QuantumLeaping
name: research-technology-quantum-leaping
icon:
sprite: Objects/Devices/swapper.rsi
state: icon
discipline: Experimental
tier: 3
cost: 10000
recipeUnlocks:
- DeviceQuantumSpinInverter

View File

@@ -931,6 +931,9 @@
- type: Tag
id: ProximitySensor
- type: Tag
id: QuantumSpinInverter
- type: Tag
id: Radio

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 359 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

View File

@@ -0,0 +1,36 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "tgstation at https://github.com/tgstation/tgstation/commit/71a1fee2f13730adee5302d34bfa0f0262314d63",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "linked",
"delays": [
[
0.1,
0.1,
0.1,
0.1,
0.1,
0.1,
0.1
]
]
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}