mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
Hijack the Automated Trade Station objective (#42135)
* predict TradeStationComponent * hijack beacon first pass * add Objective uplink category * add HijackTradeStationCondition, objective, and uplink entry * add sprite and cleanups * fix + make cooldown sane * use TimeSpans and update logic accordingly + private state-related fields * remove temporary reward * tiny loc changes * buncha changes * Change success message, beacon physics properties, formatting --------- Co-authored-by: seanpimble <149889301+seanpimble@users.noreply.github.com> Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> Co-authored-by: SlamBamActionman <slambamactionman@gmail.com>
This commit is contained in:
parent
9504c1e0bb
commit
17ccf1dc70
22 changed files with 648 additions and 16 deletions
|
|
@ -1,10 +0,0 @@
|
|||
namespace Content.Server.Cargo.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Target for approved orders to spawn at.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed partial class TradeStationComponent : Component
|
||||
{
|
||||
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ using Content.Shared.Cargo.Components;
|
|||
using Content.Shared.Cargo.Events;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.HijackBeacon;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
|
|
@ -14,7 +15,7 @@ namespace Content.Server.Cargo.Systems;
|
|||
public sealed partial class CargoSystem
|
||||
{
|
||||
/*
|
||||
* Handles cargo shuttle / trade mechanics.
|
||||
* Handles automated trade station / trade mechanics.
|
||||
*/
|
||||
|
||||
private static readonly SoundPathSpecifier ApproveSound = new("/Audio/Effects/Cargo/ping.ogg");
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using Content.Shared.Access.Systems;
|
|||
using Content.Shared.Administration.Logs;
|
||||
using Content.Server.Radio.EntitySystems;
|
||||
using Content.Shared.Cargo;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Containers.ItemSlots;
|
||||
using Content.Shared.Mobs.Components;
|
||||
using Content.Shared.Paper;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
using Content.Server.Objectives.Systems;
|
||||
|
||||
namespace Content.Server.Objectives.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Objective condition that requires the player to hijack the trade station.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(HijackTradeStationConditionSystem))]
|
||||
public sealed partial class HijackTradeStationConditionComponent : Component
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
using Content.Server.Objectives.Components;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Mind;
|
||||
using Content.Shared.Objectives.Components;
|
||||
using NetCord;
|
||||
|
||||
namespace Content.Server.Objectives.Systems;
|
||||
|
||||
/// <summary>
|
||||
/// Handles the Hijack Trade Station objective.
|
||||
/// </summary>
|
||||
public sealed class HijackTradeStationConditionSystem : EntitySystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HijackTradeStationConditionComponent, ObjectiveGetProgressEvent>(OnGetProgress);
|
||||
}
|
||||
|
||||
private void OnGetProgress(Entity<HijackTradeStationConditionComponent> ent, ref ObjectiveGetProgressEvent args)
|
||||
{
|
||||
var enumerator = EntityQueryEnumerator<TradeStationComponent>();
|
||||
args.Progress = 0f;
|
||||
// If there's any hacked trade station, succeed.
|
||||
while (enumerator.MoveNext(out var comp))
|
||||
{
|
||||
if (!comp.Hacked)
|
||||
continue;
|
||||
|
||||
args.Progress = 1f;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Content.Shared/Cargo/Components/TradeStationComponent.cs
Normal file
17
Content.Shared/Cargo/Components/TradeStationComponent.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using Content.Shared.HijackBeacon;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.Cargo.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Target for approved orders to spawn at.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class TradeStationComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// The Trade Station's current hijack state. Modified by HijackBeaconSystem.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Hacked = false;
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Cargo.Prototypes;
|
||||
using Content.Shared.HijackBeacon;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
|
@ -17,6 +18,7 @@ public abstract class SharedCargoSystem : EntitySystem
|
|||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<StationBankAccountComponent, MapInitEvent>(OnMapInit);
|
||||
SubscribeLocalEvent<HijackBeaconSuccessEvent>(OnHijackSuccess);
|
||||
}
|
||||
|
||||
private void OnMapInit(Entity<StationBankAccountComponent> ent, ref MapInitEvent args)
|
||||
|
|
@ -25,6 +27,23 @@ public abstract class SharedCargoSystem : EntitySystem
|
|||
Dirty(ent);
|
||||
}
|
||||
|
||||
private void OnHijackSuccess(ref HijackBeaconSuccessEvent args)
|
||||
{
|
||||
var stationQuery = EntityQueryEnumerator<StationBankAccountComponent>();
|
||||
while (stationQuery.MoveNext(out var uid, out var comp))
|
||||
{
|
||||
foreach (var (account, cash) in comp.Accounts)
|
||||
{
|
||||
comp.Accounts[account] = cash - args.Fine;
|
||||
args.Total += args.Fine;
|
||||
}
|
||||
|
||||
var ev = new BankBalanceUpdatedEvent(uid, comp.Accounts);
|
||||
RaiseLocalEvent(uid, ref ev, true);
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For a given station, retrieves the balance in a specific account.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -235,12 +235,8 @@ public sealed partial class AnchorableSystem : EntitySystem
|
|||
// Log anchor attempt (server only)
|
||||
_adminLogger.Add(LogType.Anchor, LogImpact.Low, $"{ToPrettyString(userUid):user} is trying to anchor {ToPrettyString(uid):entity} to {transform.Coordinates:targetlocation}");
|
||||
|
||||
if (TryComp<PhysicsComponent>(uid, out var anchorBody) &&
|
||||
!TileFree(transform.Coordinates, anchorBody))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("anchorable-occupied"), uid, userUid);
|
||||
if (!CanAnchorAt(uid, transform.Coordinates, userUid))
|
||||
return;
|
||||
}
|
||||
|
||||
if (AnyUnstackable(uid, transform.Coordinates))
|
||||
{
|
||||
|
|
@ -285,6 +281,23 @@ public sealed partial class AnchorableSystem : EntitySystem
|
|||
return !attempt.Cancelled;
|
||||
}
|
||||
|
||||
public bool CanAnchorAt(Entity<PhysicsComponent?> entity, EntityUid? user = null)
|
||||
{
|
||||
return CanAnchorAt(entity, Transform(entity).Coordinates, user);
|
||||
}
|
||||
|
||||
public bool CanAnchorAt(Entity<PhysicsComponent?> entity, EntityCoordinates coordinates, EntityUid? user = null)
|
||||
{
|
||||
if (!Resolve(entity, ref entity.Comp))
|
||||
return true;
|
||||
|
||||
if (TileFree(coordinates, entity.Comp))
|
||||
return true;
|
||||
|
||||
_popup.PopupClient(Loc.GetString("anchorable-occupied"), entity, user);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if no hard anchored entities exist on the coordinate tile that would collide with the provided physics body.
|
||||
/// </summary>
|
||||
|
|
|
|||
16
Content.Shared/HijackBeacon/ActiveHijackBeaconComponent.cs
Normal file
16
Content.Shared/HijackBeacon/ActiveHijackBeaconComponent.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Shared.HijackBeacon;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for tracking a <see cref="HijackBeaconComponent"/> that is currently activated or on cooldown.
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class ActiveHijackBeaconComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Remaining time until the hijack is completed.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField, Access(typeof(HijackBeaconSystem))]
|
||||
public TimeSpan CompletionTime = TimeSpan.Zero;
|
||||
}
|
||||
53
Content.Shared/HijackBeacon/HijackBeaconComponent.cs
Normal file
53
Content.Shared/HijackBeacon/HijackBeaconComponent.cs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
using Content.Shared.DoAfter;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Prototypes;
|
||||
|
||||
namespace Content.Shared.HijackBeacon;
|
||||
|
||||
/// <summary>
|
||||
/// Component for hijack beacons, meant to be planted on the ATS to drain station funds.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Status and timer fields are private so the state machine is preserved.
|
||||
/// </remarks>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
|
||||
public sealed partial class HijackBeaconComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Current state of the beacon.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField, Access(typeof(HijackBeaconSystem))]
|
||||
public HijackBeaconStatus Status = HijackBeaconStatus.AwaitActivate;
|
||||
|
||||
/// <summary>
|
||||
/// How long it takes to deactivate the beacon.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField, Access(typeof(HijackBeaconSystem))]
|
||||
public TimeSpan DeactivationLength = TimeSpan.FromSeconds(5);
|
||||
|
||||
/// <summary>
|
||||
/// Remaining time until the hijack is completed.
|
||||
/// </summary>
|
||||
[DataField, Access(typeof(HijackBeaconSystem))]
|
||||
public TimeSpan RemainingTime = TimeSpan.FromSeconds(200);
|
||||
|
||||
/// <summary>
|
||||
/// Default amount of time before the beacon can be re-activated, if it is disarmed.
|
||||
/// </summary>
|
||||
[DataField, Access(typeof(HijackBeaconSystem))]
|
||||
public TimeSpan Cooldown = TimeSpan.FromSeconds(20);
|
||||
|
||||
/// <summary>
|
||||
/// Remaining cooldown time before the beacon can be reactivated.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField, Access(typeof(HijackBeaconSystem))]
|
||||
public TimeSpan CooldownTime = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// How much cash should be withdrawn from each department account?
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public int Fine = 5000;
|
||||
}
|
||||
353
Content.Shared/HijackBeacon/HijackBeaconSystem.cs
Normal file
353
Content.Shared/HijackBeacon/HijackBeaconSystem.cs
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Chat;
|
||||
using Content.Shared.Construction.Components;
|
||||
using Content.Shared.Construction.EntitySystems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.DoAfter;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Shared.HijackBeacon;
|
||||
|
||||
public sealed class HijackBeaconSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
||||
[Dependency] private readonly AnchorableSystem _anchor = default!;
|
||||
[Dependency] private readonly SharedChatSystem _chat = default!;
|
||||
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
||||
|
||||
public readonly SoundSpecifier AnnounceSound = new SoundPathSpecifier("/Audio/Misc/notice1.ogg");
|
||||
public readonly SoundSpecifier DeactivateSound = new SoundPathSpecifier("/Audio/Misc/notice2.ogg");
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<HijackBeaconComponent, GetVerbsEvent<AlternativeVerb>>(OnGetAltVerbs);
|
||||
SubscribeLocalEvent<HijackBeaconComponent, UnanchorAttemptEvent>(OnUnanchorAttempt);
|
||||
SubscribeLocalEvent<HijackBeaconComponent, AnchorStateChangedEvent>(OnAnchorChanged);
|
||||
SubscribeLocalEvent<HijackBeaconComponent, HijackBeaconDeactivateDoAfterEvent>(OnDeactivateDoAfter);
|
||||
SubscribeLocalEvent<HijackBeaconComponent, ExaminedEvent>(OnExaminedEvent);
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<ActiveHijackBeaconComponent, HijackBeaconComponent>();
|
||||
while (query.MoveNext(out var uid, out var active, out var comp))
|
||||
{
|
||||
switch (comp.Status)
|
||||
{
|
||||
case HijackBeaconStatus.Armed:
|
||||
if (_gameTiming.CurTime < active.CompletionTime)
|
||||
return;
|
||||
|
||||
HijackFinish((uid, comp));
|
||||
Dirty(uid, comp);
|
||||
break;
|
||||
case HijackBeaconStatus.Cooldown:
|
||||
if (comp.CooldownTime < _gameTiming.CurTime)
|
||||
{
|
||||
comp.Status = HijackBeaconStatus.AwaitActivate;
|
||||
RemCompDeferred<ActiveHijackBeaconComponent>(uid);
|
||||
Dirty(uid, comp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Event Subs
|
||||
|
||||
/// <summary>
|
||||
/// Deactivate beacon if it gets unanchored(via a bomb or something)
|
||||
/// </summary>
|
||||
private void OnAnchorChanged(Entity<HijackBeaconComponent> ent, ref AnchorStateChangedEvent args)
|
||||
{
|
||||
// Unanchoring the beacon deactivates it. This is to prevent people from bombing the tile the beacon is on and running away with it for a free activation.
|
||||
if (!args.Anchored && ent.Comp.Status == HijackBeaconStatus.Armed)
|
||||
DeactivateBeacon(ent);
|
||||
}
|
||||
|
||||
private void OnUnanchorAttempt(Entity<HijackBeaconComponent> entity, ref UnanchorAttemptEvent args)
|
||||
{
|
||||
if (entity.Comp.Status == HijackBeaconStatus.Armed)
|
||||
args.Cancel();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the activation and deactivation verbs.
|
||||
/// </summary>
|
||||
private void OnGetAltVerbs(Entity<HijackBeaconComponent> ent, ref GetVerbsEvent<AlternativeVerb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || args.Hands is null)
|
||||
return;
|
||||
|
||||
if (ent.Comp.Status == HijackBeaconStatus.AwaitActivate)
|
||||
{
|
||||
args.Verbs.Add(new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
ActivateBeacon(ent);
|
||||
},
|
||||
Text = Loc.GetString("hijack-beacon-verb-activate-text"),
|
||||
Message = Loc.GetString("hijack-beacon-verb-activate-message"),
|
||||
Disabled = ent.Comp.Status != HijackBeaconStatus.AwaitActivate || !CanActivate(ent),
|
||||
TextStyleClass = "InteractionVerb",
|
||||
Impact = LogImpact.High,
|
||||
});
|
||||
}
|
||||
|
||||
if (ent.Comp.Status == HijackBeaconStatus.Armed)
|
||||
{
|
||||
var user = args.User;
|
||||
|
||||
args.Verbs.Add(new()
|
||||
{
|
||||
Act = () =>
|
||||
{
|
||||
DeactivateBeaconDoAfter(ent, user);
|
||||
},
|
||||
Text = Loc.GetString("hijack-beacon-verb-deactivate-text"),
|
||||
Message = Loc.GetString("hijack-beacon-verb-deactivate-message"),
|
||||
TextStyleClass = "InteractionVerb",
|
||||
Impact = LogImpact.High,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When it's examined.
|
||||
/// </summary>
|
||||
private void OnExaminedEvent(Entity<HijackBeaconComponent> ent, ref ExaminedEvent args)
|
||||
{
|
||||
if (!args.IsInDetailsRange)
|
||||
return;
|
||||
|
||||
switch (ent.Comp.Status)
|
||||
{
|
||||
case HijackBeaconStatus.AwaitActivate:
|
||||
args.PushMarkup(Loc.GetString("hijack-beacon-examine-await-activate"));
|
||||
break;
|
||||
case HijackBeaconStatus.Armed:
|
||||
args.PushMarkup(Loc.GetString("defusable-examine-live",
|
||||
("name", ent),
|
||||
("time", GetRemainingTime(ent.Owner))));
|
||||
break;
|
||||
case HijackBeaconStatus.Cooldown:
|
||||
args.PushMarkup(Loc.GetString("hijack-beacon-examine-await-cooldown"));
|
||||
break;
|
||||
case HijackBeaconStatus.HijackComplete:
|
||||
args.PushMarkup(Loc.GetString("hijack-beacon-examine-await-hijack-complete"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// What happens when you deactivate the beacon.
|
||||
/// </summary>
|
||||
private void OnDeactivateDoAfter(Entity<HijackBeaconComponent> ent, ref HijackBeaconDeactivateDoAfterEvent args)
|
||||
{
|
||||
if (args.Handled || args.Cancelled)
|
||||
return;
|
||||
|
||||
DeactivateBeacon(ent);
|
||||
|
||||
args.Handled = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Arming the beacon. Should only occur if on the ATS.
|
||||
/// </summary>
|
||||
private void ActivateBeacon(Entity<HijackBeaconComponent> ent)
|
||||
{
|
||||
if (ent.Comp.Status != HijackBeaconStatus.AwaitActivate || !CanActivate(ent))
|
||||
return;
|
||||
|
||||
// Activate and start countdown.
|
||||
// Remaining time is adjusted by current time to simplify the update loop.
|
||||
EnsureComp<ActiveHijackBeaconComponent>(ent, out var activeComp);
|
||||
activeComp.CompletionTime = _gameTiming.CurTime + ent.Comp.RemainingTime;
|
||||
ent.Comp.Status = HijackBeaconStatus.Armed;
|
||||
|
||||
//global announcement
|
||||
var sender = Loc.GetString("hijack-beacon-announcement-sender");
|
||||
var message = Loc.GetString("hijack-beacon-announcement-activated", ("time", GetRemainingTime((ent.Owner, activeComp))));
|
||||
_chat.DispatchGlobalAnnouncement(message, sender, true, AnnounceSound, Color.Yellow);
|
||||
|
||||
//Anchor. Anchoring is tied to activation.
|
||||
Anchor(ent);
|
||||
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deactivates the beacon.
|
||||
/// </summary>
|
||||
private void DeactivateBeacon(Entity<HijackBeaconComponent> ent)
|
||||
{
|
||||
if (ent.Comp.Status != HijackBeaconStatus.Armed)
|
||||
return;
|
||||
|
||||
// Put beacon on cooldown
|
||||
ent.Comp.CooldownTime = ent.Comp.Cooldown + _gameTiming.CurTime;
|
||||
ent.Comp.Status = HijackBeaconStatus.Cooldown;
|
||||
|
||||
//global announcement
|
||||
var sender = Loc.GetString("hijack-beacon-announcement-sender");
|
||||
var message = Loc.GetString("hijack-beacon-announcement-deactivated");
|
||||
_chat.DispatchGlobalAnnouncement(message, sender, true, DeactivateSound, Color.Green);
|
||||
|
||||
// Unanchor. we want anchoring to be tied to activation here so we just call this.
|
||||
Unanchor(ent);
|
||||
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Complete the hijack. The beacon equivalent to a nuke detonation
|
||||
/// </summary>
|
||||
private void HijackFinish(Entity<HijackBeaconComponent> ent)
|
||||
{
|
||||
if (ent.Comp.Status != HijackBeaconStatus.Armed)
|
||||
return;
|
||||
|
||||
// Hijack is completed and can't be reattempted
|
||||
ent.Comp.Status = HijackBeaconStatus.HijackComplete;
|
||||
RemCompDeferred<ActiveHijackBeaconComponent>(ent);
|
||||
|
||||
var beaconXForm = Transform(ent);
|
||||
|
||||
// Ensure we are on the trade station still
|
||||
if (!TryComp<TradeStationComponent>(beaconXForm.GridUid, out var station))
|
||||
{
|
||||
Log.Warning($"Trade station hijack tried to succeed on non-trade station grid {beaconXForm.GridUid}!");
|
||||
DeactivateBeacon(ent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (station.Hacked)
|
||||
{
|
||||
Log.Warning("Hack succeeded on an already hacked trade station!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark the station as hacked.
|
||||
station.Hacked = true;
|
||||
Dirty(beaconXForm.GridUid.Value, station);
|
||||
|
||||
// Broadcast that the ATS has been hacked.
|
||||
var ev = new HijackBeaconSuccessEvent(ent.Comp.Fine);
|
||||
RaiseLocalEvent(ref ev);
|
||||
|
||||
//global announcement
|
||||
var sender = Loc.GetString("hijack-beacon-announcement-sender");
|
||||
var message = Loc.GetString("hijack-beacon-announcement-success", ("fine", ev.Total));
|
||||
_chat.DispatchGlobalAnnouncement(message, sender, true, AnnounceSound, Color.Red);
|
||||
|
||||
// Unanchoring must occur after updating the status, or it will disarm the beacon
|
||||
Unanchor(ent, beaconXForm);
|
||||
|
||||
Dirty(ent);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the deactivation doafter.
|
||||
/// </summary>
|
||||
private void DeactivateBeaconDoAfter(Entity<HijackBeaconComponent> beacon, EntityUid user)
|
||||
{
|
||||
var doAfter = new DoAfterArgs(EntityManager, user, beacon.Comp.DeactivationLength, new HijackBeaconDeactivateDoAfterEvent(), beacon)
|
||||
{
|
||||
BreakOnDamage = true,
|
||||
BreakOnMove = true,
|
||||
NeedHand = true,
|
||||
};
|
||||
|
||||
// (try to) start doafter
|
||||
_doAfter.TryStartDoAfter(doAfter);
|
||||
}
|
||||
|
||||
#region Helpers
|
||||
|
||||
/// <summary>
|
||||
/// Check if the beacon is on the Trade Station and if the Trade Station has not been hijacked already.
|
||||
/// </summary>
|
||||
private bool CanActivate(Entity<HijackBeaconComponent> ent)
|
||||
{
|
||||
return TryComp(Transform(ent).GridUid, out TradeStationComponent? tradeStation) && !tradeStation.Hacked && _anchor.CanAnchorAt(ent.Owner);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Anchoring helper
|
||||
/// </summary>
|
||||
private void Anchor(Entity<HijackBeaconComponent> ent, TransformComponent? beaconXForm = null)
|
||||
{
|
||||
beaconXForm ??= Transform(ent);
|
||||
|
||||
if (beaconXForm.Anchored || beaconXForm.GridUid == null)
|
||||
return;
|
||||
|
||||
_transform.AnchorEntity(ent, beaconXForm);
|
||||
_popup.PopupPredicted(Loc.GetString("hijack-beacon-popup-anchor"), ent, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unanchoring helper
|
||||
/// </summary>
|
||||
private void Unanchor(Entity<HijackBeaconComponent> ent, TransformComponent? beaconXForm = null)
|
||||
{
|
||||
beaconXForm ??= Transform(ent);
|
||||
|
||||
if (!beaconXForm.Anchored)
|
||||
return;
|
||||
|
||||
_transform.Unanchor(ent, beaconXForm);
|
||||
_popup.PopupPredicted(Loc.GetString("hijack-beacon-popup-unanchor"), ent, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns time values into usable values for announcements/examine messages
|
||||
/// </summary>
|
||||
private int GetRemainingTime(Entity<ActiveHijackBeaconComponent?> ent)
|
||||
{
|
||||
if (!Resolve(ent, ref ent.Comp))
|
||||
return 69420; // Mature error code
|
||||
|
||||
return (int) (ent.Comp.CompletionTime - _gameTiming.CurTime).TotalSeconds;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public enum HijackBeaconStatus : byte
|
||||
{
|
||||
AwaitActivate,
|
||||
Armed,
|
||||
Cooldown,
|
||||
HijackComplete
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DoAfter event raised when the hijack beacon is deactivated.
|
||||
/// </summary>
|
||||
[Serializable, NetSerializable]
|
||||
public sealed partial class HijackBeaconDeactivateDoAfterEvent : SimpleDoAfterEvent;
|
||||
|
||||
/// <summary>
|
||||
/// Event raised when the hijack beacon succeeds in hijacking the ATS.
|
||||
/// </summary>
|
||||
[ByRefEvent]
|
||||
public record struct HijackBeaconSuccessEvent(int Fine)
|
||||
{
|
||||
public int Total = 0;
|
||||
};
|
||||
16
Resources/Locale/en-US/hijack-beacon/hijack-beacon.ftl
Normal file
16
Resources/Locale/en-US/hijack-beacon/hijack-beacon.ftl
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
hijack-beacon-announcement-sender = Automated Trade Station
|
||||
hijack-beacon-announcement-activated = Attention! An Attempted Breach of the Automated Trade Station's firewall has been detected! Estimated {$time} seconds until firewall breach!
|
||||
hijack-beacon-announcement-deactivated = Firewall breach failed. Firewall integrity partially restored. Have a nice day!
|
||||
hijack-beacon-announcement-success = Successfully disengaged Automated Trade Station firewall. {$fine} spessos has been transferred from station funds to [%ERROR%]. Your trade station warranty is now void. This incident has been reported.
|
||||
|
||||
hijack-beacon-examine-await-activate = The beacon is [color=green]ready to activate[/color].
|
||||
hijack-beacon-examine-await-cooldown = The beacon is [color=red]on cooldown[/color].
|
||||
hijack-beacon-examine-await-hijack-complete = The beacon is [color=red]spent[/color].
|
||||
|
||||
hijack-beacon-popup-anchor = The beacon anchors itself into the ground!
|
||||
hijack-beacon-popup-unanchor = The beacon unanchors itself from the ground.
|
||||
|
||||
hijack-beacon-verb-activate-text = Activate
|
||||
hijack-beacon-verb-activate-message = The beacon can only be armed on the Automated Trade Station, on an unoccupied tile.
|
||||
hijack-beacon-verb-deactivate-text = Deactivate
|
||||
hijack-beacon-verb-deactivate-message = The beacon isn't going to deactivate itself, you know.
|
||||
|
|
@ -12,6 +12,7 @@ store-category-allies = Allies
|
|||
store-category-job = Job
|
||||
store-category-wearables = Wearables
|
||||
store-category-pointless = Pointless
|
||||
store-category-objective = Objective
|
||||
store-discounted-items = Discounts
|
||||
|
||||
# Revenant
|
||||
|
|
|
|||
|
|
@ -520,3 +520,7 @@ uplink-briefcase-gun-desc = An indistinct briefcase with a highly compact C-20K
|
|||
|
||||
uplink-energycrossbow-name = Mini Energy Crossbow
|
||||
uplink-energycrossbow-desc = The go-to sidearm of any operative who prefers their victims not to be moving. Fires regenerating toxic arrows that floors victims in an instant.
|
||||
|
||||
#Objective items
|
||||
uplink-hijack-beacon-name = Hijack Beacon
|
||||
uplink-hijack-beacon-desc = A syndicate-brand hijack beacon designed to get around the firewalls of Nanotrasen-brand Automated Trade Stations. They take 200 seconds to work and Trade Stations will announce they are being hacked, so prepare accordingly.
|
||||
|
|
|
|||
|
|
@ -2268,3 +2268,20 @@
|
|||
- !type:BuyerJobCondition
|
||||
whitelist:
|
||||
- Lawyer
|
||||
|
||||
#Objective items
|
||||
|
||||
- type: listing
|
||||
id: uplinkHijackBeacon
|
||||
name: uplink-hijack-beacon-name
|
||||
description: uplink-hijack-beacon-desc
|
||||
productEntity: HijackBeacon
|
||||
categories:
|
||||
- UplinkObjective
|
||||
conditions:
|
||||
- !type:ListingLimitedStockCondition
|
||||
stock: 1
|
||||
- !type:BuyerObjectiveWhitelistCondition
|
||||
whitelist:
|
||||
components:
|
||||
- HijackTradeStationCondition
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
- type: entity
|
||||
parent: [ BaseItem, BaseSyndicateContraband ]
|
||||
id: HijackBeacon
|
||||
name: hijack beacon
|
||||
description: A device that bypasses the firewall on Nanotrasen-brand Automated Trade Stations.
|
||||
components:
|
||||
- type: Transform
|
||||
anchored: false
|
||||
noRot: true
|
||||
- type: Physics
|
||||
bodyType: Dynamic
|
||||
- type: Anchorable
|
||||
- type: Item
|
||||
size: Normal
|
||||
- type: HijackBeacon
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
fix1:
|
||||
shape:
|
||||
!type:PhysShapeAabb
|
||||
bounds: "-0.3,-0.3,0.3,0.3"
|
||||
density: 190
|
||||
mask:
|
||||
- MachineMask
|
||||
layer:
|
||||
- MachineLayer
|
||||
- type: Clickable
|
||||
- type: InteractionOutline
|
||||
- type: Appearance
|
||||
- type: Sprite
|
||||
sprite: Objects/Tools/hijack_beacon.rsi
|
||||
noRot: true
|
||||
layers:
|
||||
- state: extraction_point
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
TraitorObjectiveGroupKill: 1
|
||||
TraitorObjectiveGroupState: 1 #As in, something about your character. Alive, dead, arrested, gained an ability...
|
||||
TraitorObjectiveGroupSocial: 1 #Involves helping/harming others without killing them or stealing their stuff
|
||||
TraitorObjectiveGroupOther: 1
|
||||
|
||||
- type: weightedRandom
|
||||
id: TraitorObjectiveGroupSteal
|
||||
|
|
@ -43,6 +44,13 @@
|
|||
# RandomTraitorAliveObjective: 1 - Removed because the objective was boring and didn't progress the round.
|
||||
RandomTraitorProgressObjective: 1
|
||||
|
||||
#misc objectives that don't fall other another category
|
||||
- type: weightedRandom
|
||||
id: TraitorObjectiveGroupOther
|
||||
weights:
|
||||
HijackTradeStationObjective: 1
|
||||
|
||||
|
||||
#Thief groups
|
||||
- type: weightedRandom
|
||||
id: ThiefObjectiveGroups
|
||||
|
|
|
|||
|
|
@ -352,3 +352,20 @@
|
|||
- type: StealCondition
|
||||
stealGroup: NukeDisk
|
||||
owner: objective-condition-steal-station
|
||||
|
||||
# other
|
||||
- type: entity
|
||||
parent: BaseTraitorObjective
|
||||
id: HijackTradeStationObjective
|
||||
name: Hijack the Automated Trade Station.
|
||||
description: Your uplink has been authorized one hijack beacon. Deploy it on the Automated Trade Station and defend it whilst it hijacks the trade station.
|
||||
components:
|
||||
- type: Objective
|
||||
difficulty: 3
|
||||
icon:
|
||||
sprite: Objects/Tools/hijack_beacon.rsi
|
||||
state: extraction_point
|
||||
- type: HijackTradeStationCondition
|
||||
- type: ObjectiveLimit
|
||||
# There is only one trade station so there should never be more than one of these objectives.
|
||||
limit: 1
|
||||
|
|
|
|||
|
|
@ -89,6 +89,11 @@
|
|||
name: store-category-pointless
|
||||
priority: 10
|
||||
|
||||
- type: storeCategory
|
||||
id: UplinkObjective
|
||||
name: store-category-objective
|
||||
priority: 11
|
||||
|
||||
#nukie delivery
|
||||
|
||||
- type: storeCategory
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
- UplinkWearables
|
||||
- UplinkJob
|
||||
- UplinkPointless
|
||||
- UplinkObjective
|
||||
currencyWhitelist:
|
||||
- Telecrystal
|
||||
balance:
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
20
Resources/Textures/Objects/Tools/hijack_beacon.rsi/meta.json
Normal file
20
Resources/Textures/Objects/Tools/hijack_beacon.rsi/meta.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"version": 1,
|
||||
"license": "CC-BY-SA-3.0",
|
||||
"copyright": "Taken from https://github.com/austation/austation/commit/e2a4fefd01e702f48d3d4cc8d6a2686d54d104fa and edited by TheShuEd. Recolored by alexalexmax",
|
||||
"size": {
|
||||
"x": 32,
|
||||
"y": 32
|
||||
},
|
||||
"states": [
|
||||
{
|
||||
"name": "extraction_point",
|
||||
"delays": [
|
||||
[
|
||||
0.5,
|
||||
0.5
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue