Sentry turrets - Part 4: The sentry turret and its primary systems (#35123)
* Initial commit * Removed mention of StationAiTurretComponent (for now) * Prep for moving out of draft * Fixing merge conflict * Re-added new net frequencies to AI turrets * Removed turret control content * Removed unintended change * Final tweaks * Fixed incorrect file name * Improvement to fire mode handling * Addressed review comments * Updated how turret wire panel auto-closing is handled * Ranged NPCs no longer waste shots on stunned targets * Fixed bug in tracking broken state * Addressed review comments * Bug fix * Removed unnecessary event call
This commit is contained in:
121
Content.Client/Turrets/DeployableTurretSystem.cs
Normal file
121
Content.Client/Turrets/DeployableTurretSystem.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using Content.Client.Power;
|
||||
using Content.Shared.Turrets;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.Turrets;
|
||||
|
||||
public sealed partial class DeployableTurretSystem : SharedDeployableTurretSystem
|
||||
{
|
||||
[Dependency] private readonly AppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DeployableTurretComponent, ComponentInit>(OnComponentInit);
|
||||
SubscribeLocalEvent<DeployableTurretComponent, AnimationCompletedEvent>(OnAnimationCompleted);
|
||||
SubscribeLocalEvent<DeployableTurretComponent, AppearanceChangeEvent>(OnAppearanceChange);
|
||||
}
|
||||
|
||||
private void OnComponentInit(Entity<DeployableTurretComponent> ent, ref ComponentInit args)
|
||||
{
|
||||
ent.Comp.DeploymentAnimation = new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(ent.Comp.DeploymentLength),
|
||||
AnimationTracks = {
|
||||
new AnimationTrackSpriteFlick() {
|
||||
LayerKey = DeployableTurretVisuals.Turret,
|
||||
KeyFrames = {new AnimationTrackSpriteFlick.KeyFrame(ent.Comp.DeployingState, 0f)}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
ent.Comp.RetractionAnimation = new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(ent.Comp.RetractionLength),
|
||||
AnimationTracks = {
|
||||
new AnimationTrackSpriteFlick() {
|
||||
LayerKey = DeployableTurretVisuals.Turret,
|
||||
KeyFrames = {new AnimationTrackSpriteFlick.KeyFrame(ent.Comp.RetractingState, 0f)}
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(Entity<DeployableTurretComponent> ent, ref AnimationCompletedEvent args)
|
||||
{
|
||||
if (args.Key != DeployableTurretComponent.AnimationKey)
|
||||
return;
|
||||
|
||||
if (!TryComp<SpriteComponent>(ent, out var sprite))
|
||||
return;
|
||||
|
||||
if (!_appearance.TryGetData<DeployableTurretState>(ent, DeployableTurretVisuals.Turret, out var state))
|
||||
state = ent.Comp.VisualState;
|
||||
|
||||
// Convert to terminal state
|
||||
var targetState = state & DeployableTurretState.Deployed;
|
||||
|
||||
UpdateVisuals(ent, targetState, sprite, args.AnimationPlayer);
|
||||
}
|
||||
|
||||
private void OnAppearanceChange(Entity<DeployableTurretComponent> ent, ref AppearanceChangeEvent args)
|
||||
{
|
||||
if (args.Sprite == null)
|
||||
return;
|
||||
|
||||
if (!TryComp<AnimationPlayerComponent>(ent, out var animPlayer))
|
||||
return;
|
||||
|
||||
if (!_appearance.TryGetData<DeployableTurretState>(ent, DeployableTurretVisuals.Turret, out var state, args.Component))
|
||||
state = DeployableTurretState.Retracted;
|
||||
|
||||
UpdateVisuals(ent, state, args.Sprite, animPlayer);
|
||||
}
|
||||
|
||||
private void UpdateVisuals(Entity<DeployableTurretComponent> ent, DeployableTurretState state, SpriteComponent sprite, AnimationPlayerComponent? animPlayer = null)
|
||||
{
|
||||
if (!Resolve(ent, ref animPlayer))
|
||||
return;
|
||||
|
||||
if (_animation.HasRunningAnimation(ent, animPlayer, DeployableTurretComponent.AnimationKey))
|
||||
return;
|
||||
|
||||
if (state == ent.Comp.VisualState)
|
||||
return;
|
||||
|
||||
var targetState = state & DeployableTurretState.Deployed;
|
||||
var destinationState = ent.Comp.VisualState & DeployableTurretState.Deployed;
|
||||
|
||||
if (targetState != destinationState)
|
||||
targetState = targetState | DeployableTurretState.Retracting;
|
||||
|
||||
ent.Comp.VisualState = state;
|
||||
|
||||
// Toggle layer visibility
|
||||
sprite.LayerSetVisible(DeployableTurretVisuals.Weapon, (targetState & DeployableTurretState.Deployed) > 0);
|
||||
sprite.LayerSetVisible(PowerDeviceVisualLayers.Powered, HasAmmo(ent) && targetState == DeployableTurretState.Retracted);
|
||||
|
||||
// Change the visual state
|
||||
switch (targetState)
|
||||
{
|
||||
case DeployableTurretState.Deploying:
|
||||
_animation.Play((ent, animPlayer), (Animation)ent.Comp.DeploymentAnimation, DeployableTurretComponent.AnimationKey);
|
||||
break;
|
||||
|
||||
case DeployableTurretState.Retracting:
|
||||
_animation.Play((ent, animPlayer), (Animation)ent.Comp.RetractionAnimation, DeployableTurretComponent.AnimationKey);
|
||||
break;
|
||||
|
||||
case DeployableTurretState.Deployed:
|
||||
sprite.LayerSetState(DeployableTurretVisuals.Turret, ent.Comp.DeployedState);
|
||||
break;
|
||||
|
||||
case DeployableTurretState.Retracted:
|
||||
sprite.LayerSetState(DeployableTurretVisuals.Turret, ent.Comp.RetractedState);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,17 @@ namespace Content.Server.Destructible
|
||||
[RegisterComponent]
|
||||
public sealed partial class DestructibleComponent : Component
|
||||
{
|
||||
[DataField("thresholds")]
|
||||
/// <summary>
|
||||
/// A list of damage thresholds for the entity;
|
||||
/// includes their triggers and resultant behaviors
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public List<DamageThreshold> Thresholds = new();
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether the entity has passed a damage threshold that causes it to break
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public bool IsBroken = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,8 @@ namespace Content.Server.Destructible
|
||||
/// </summary>
|
||||
public void Execute(EntityUid uid, DestructibleComponent component, DamageChangedEvent args)
|
||||
{
|
||||
component.IsBroken = false;
|
||||
|
||||
foreach (var threshold in component.Thresholds)
|
||||
{
|
||||
if (threshold.Reached(args.Damageable, this))
|
||||
@@ -96,6 +98,12 @@ namespace Content.Server.Destructible
|
||||
threshold.Execute(uid, this, EntityManager, args.Origin);
|
||||
}
|
||||
|
||||
if (threshold.OldTriggered)
|
||||
{
|
||||
component.IsBroken |= threshold.Behaviors.Any(b => b is DoActsBehavior doActsBehavior &&
|
||||
(doActsBehavior.HasAct(ThresholdActs.Breakage) || doActsBehavior.HasAct(ThresholdActs.Destruction)));
|
||||
}
|
||||
|
||||
// if destruction behavior (or some other deletion effect) occurred, don't run other triggers.
|
||||
if (EntityManager.IsQueuedForDeletion(uid) || Deleted(uid))
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace Content.Server.NPC.Queries.Considerations;
|
||||
|
||||
/// <summary>
|
||||
/// Returns 1f if the target has the <see cref="StunnedComponent"/>
|
||||
/// </summary>
|
||||
public sealed partial class TargetIsStunnedCon : UtilityConsideration
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ using Content.Shared.Mobs.Systems;
|
||||
using Content.Shared.NPC.Systems;
|
||||
using Content.Shared.Nutrition.Components;
|
||||
using Content.Shared.Nutrition.EntitySystems;
|
||||
using Content.Shared.Stunnable;
|
||||
using Content.Shared.Tools.Systems;
|
||||
using Content.Shared.Turrets;
|
||||
using Content.Shared.Weapons.Melee;
|
||||
@@ -360,6 +361,10 @@ public sealed class NPCUtilitySystem : EntitySystem
|
||||
return 1f;
|
||||
return 0f;
|
||||
}
|
||||
case TargetIsStunnedCon:
|
||||
{
|
||||
return HasComp<StunnedComponent>(targetUid) ? 1f : 0f;
|
||||
}
|
||||
case TurretTargetingCon:
|
||||
{
|
||||
if (!TryComp<TurretTargetSettingsComponent>(owner, out var turretTargetSettings) ||
|
||||
|
||||
175
Content.Server/Turrets/DeployableTurretSystem.cs
Normal file
175
Content.Server/Turrets/DeployableTurretSystem.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using Content.Server.Destructible;
|
||||
using Content.Server.DeviceNetwork;
|
||||
using Content.Server.DeviceNetwork.Components;
|
||||
using Content.Server.DeviceNetwork.Systems;
|
||||
using Content.Server.NPC.HTN;
|
||||
using Content.Server.NPC.HTN.PrimitiveTasks.Operators.Combat.Ranged;
|
||||
using Content.Server.Power.Components;
|
||||
using Content.Server.Repairable;
|
||||
using Content.Shared.Destructible;
|
||||
using Content.Shared.DeviceNetwork;
|
||||
using Content.Shared.Power;
|
||||
using Content.Shared.Turrets;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Server.Turrets;
|
||||
|
||||
public sealed partial class DeployableTurretSystem : SharedDeployableTurretSystem
|
||||
{
|
||||
[Dependency] private readonly HTNSystem _htn = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly DeviceNetworkSystem _deviceNetwork = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DeployableTurretComponent, AmmoShotEvent>(OnAmmoShot);
|
||||
SubscribeLocalEvent<DeployableTurretComponent, ChargeChangedEvent>(OnChargeChanged);
|
||||
SubscribeLocalEvent<DeployableTurretComponent, PowerChangedEvent>(OnPowerChanged);
|
||||
SubscribeLocalEvent<DeployableTurretComponent, BreakageEventArgs>(OnBroken);
|
||||
SubscribeLocalEvent<DeployableTurretComponent, RepairedEvent>(OnRepaired);
|
||||
SubscribeLocalEvent<DeployableTurretComponent, BeforeBroadcastAttemptEvent>(OnBeforeBroadcast);
|
||||
}
|
||||
|
||||
private void OnAmmoShot(Entity<DeployableTurretComponent> ent, ref AmmoShotEvent args)
|
||||
{
|
||||
UpdateAmmoStatus(ent);
|
||||
}
|
||||
|
||||
private void OnChargeChanged(Entity<DeployableTurretComponent> ent, ref ChargeChangedEvent args)
|
||||
{
|
||||
UpdateAmmoStatus(ent);
|
||||
}
|
||||
|
||||
private void OnPowerChanged(Entity<DeployableTurretComponent> ent, ref PowerChangedEvent args)
|
||||
{
|
||||
UpdateAmmoStatus(ent);
|
||||
}
|
||||
|
||||
private void OnBroken(Entity<DeployableTurretComponent> ent, ref BreakageEventArgs args)
|
||||
{
|
||||
if (TryComp<AppearanceComponent>(ent, out var appearance))
|
||||
_appearance.SetData(ent, DeployableTurretVisuals.Broken, true, appearance);
|
||||
|
||||
SetState(ent, false);
|
||||
}
|
||||
|
||||
private void OnRepaired(Entity<DeployableTurretComponent> ent, ref RepairedEvent args)
|
||||
{
|
||||
if (TryComp<AppearanceComponent>(ent, out var appearance))
|
||||
_appearance.SetData(ent, DeployableTurretVisuals.Broken, false, appearance);
|
||||
}
|
||||
|
||||
private void OnBeforeBroadcast(Entity<DeployableTurretComponent> ent, ref BeforeBroadcastAttemptEvent args)
|
||||
{
|
||||
if (!TryComp<DeviceNetworkComponent>(ent, out var deviceNetwork))
|
||||
return;
|
||||
|
||||
var recipientDeviceNetworks = new HashSet<DeviceNetworkComponent>();
|
||||
|
||||
// Only broadcast to connected devices
|
||||
foreach (var recipient in deviceNetwork.DeviceLists)
|
||||
{
|
||||
if (!TryComp<DeviceNetworkComponent>(recipient, out var recipientDeviceNetwork))
|
||||
continue;
|
||||
|
||||
recipientDeviceNetworks.Add(recipientDeviceNetwork);
|
||||
}
|
||||
|
||||
if (recipientDeviceNetworks.Count > 0)
|
||||
args.ModifiedRecipients = recipientDeviceNetworks;
|
||||
}
|
||||
|
||||
private void SendStateUpdateToDeviceNetwork(Entity<DeployableTurretComponent> ent)
|
||||
{
|
||||
if (!TryComp<DeviceNetworkComponent>(ent, out var device))
|
||||
return;
|
||||
|
||||
var payload = new NetworkPayload
|
||||
{
|
||||
[DeviceNetworkConstants.Command] = DeviceNetworkConstants.CmdUpdatedState,
|
||||
[DeviceNetworkConstants.CmdUpdatedState] = GetTurretState(ent)
|
||||
};
|
||||
|
||||
_deviceNetwork.QueuePacket(ent, null, payload, device: device);
|
||||
}
|
||||
|
||||
protected override void SetState(Entity<DeployableTurretComponent> ent, bool enabled, EntityUid? user = null)
|
||||
{
|
||||
if (ent.Comp.Enabled == enabled)
|
||||
return;
|
||||
|
||||
base.SetState(ent, enabled, user);
|
||||
DirtyField(ent, ent.Comp, nameof(DeployableTurretComponent.Enabled));
|
||||
|
||||
// Determine how much time is remaining in the current animation and the one next in queue
|
||||
var animTimeRemaining = MathF.Max((float)(ent.Comp.AnimationCompletionTime - _timing.CurTime).TotalSeconds, 0f);
|
||||
var animTimeNext = ent.Comp.Enabled ? ent.Comp.DeploymentLength : ent.Comp.RetractionLength;
|
||||
|
||||
// End/restart any tasks the NPC was doing
|
||||
// Delay the resumption of any tasks based on the total animation length (plus a buffer)
|
||||
var planCooldown = animTimeRemaining + animTimeNext + 0.5f;
|
||||
|
||||
if (TryComp<HTNComponent>(ent, out var htn))
|
||||
_htn.SetHTNEnabled((ent, htn), ent.Comp.Enabled, planCooldown);
|
||||
|
||||
// Play audio
|
||||
_audio.PlayPvs(ent.Comp.Enabled ? ent.Comp.DeploymentSound : ent.Comp.RetractionSound, ent, new AudioParams { Volume = -10f });
|
||||
}
|
||||
|
||||
private void UpdateAmmoStatus(Entity<DeployableTurretComponent> ent)
|
||||
{
|
||||
if (!HasAmmo(ent))
|
||||
SetState(ent, false);
|
||||
}
|
||||
|
||||
private DeployableTurretState GetTurretState(Entity<DeployableTurretComponent> ent, DestructibleComponent? destructable = null, HTNComponent? htn = null)
|
||||
{
|
||||
Resolve(ent, ref destructable, ref htn);
|
||||
|
||||
if (destructable?.IsBroken == true)
|
||||
return DeployableTurretState.Broken;
|
||||
|
||||
if (htn == null || !HasAmmo(ent))
|
||||
return DeployableTurretState.Disabled;
|
||||
|
||||
if (htn.Plan?.CurrentTask.Operator is GunOperator)
|
||||
return DeployableTurretState.Firing;
|
||||
|
||||
if (ent.Comp.AnimationCompletionTime > _timing.CurTime)
|
||||
return ent.Comp.Enabled ? DeployableTurretState.Deploying : DeployableTurretState.Retracting;
|
||||
|
||||
return ent.Comp.Enabled ? DeployableTurretState.Deployed : DeployableTurretState.Retracted;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
base.Update(frameTime);
|
||||
|
||||
var query = EntityQueryEnumerator<DeployableTurretComponent, DestructibleComponent, HTNComponent>();
|
||||
while (query.MoveNext(out var uid, out var deployableTurret, out var destructible, out var htn))
|
||||
{
|
||||
// Check if the turret state has changed since the last update,
|
||||
// and if it has, inform the device network
|
||||
var ent = new Entity<DeployableTurretComponent>(uid, deployableTurret);
|
||||
var newState = GetTurretState(ent, destructible, htn);
|
||||
|
||||
if (newState != deployableTurret.CurrentState)
|
||||
{
|
||||
deployableTurret.CurrentState = newState;
|
||||
DirtyField(uid, deployableTurret, nameof(DeployableTurretComponent.CurrentState));
|
||||
|
||||
SendStateUpdateToDeviceNetwork(ent);
|
||||
|
||||
if (TryComp<AppearanceComponent>(ent, out var appearance))
|
||||
_appearance.SetData(ent, DeployableTurretVisuals.Turret, newState, appearance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,7 +296,7 @@ namespace Content.Shared.Damage
|
||||
DamageChanged(uid, component, new DamageSpecifier());
|
||||
}
|
||||
|
||||
public void SetDamageModifierSetId(EntityUid uid, string damageModifierSetId, DamageableComponent? comp = null)
|
||||
public void SetDamageModifierSetId(EntityUid uid, string? damageModifierSetId, DamageableComponent? comp = null)
|
||||
{
|
||||
if (!_damageableQuery.Resolve(uid, ref comp))
|
||||
return;
|
||||
|
||||
161
Content.Shared/Turrets/DeployableTurretComponent.cs
Normal file
161
Content.Shared/Turrets/DeployableTurretComponent.cs
Normal file
@@ -0,0 +1,161 @@
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.GameStates;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Content.Shared.Turrets;
|
||||
|
||||
/// <summary>
|
||||
/// Attached to turrets that can be toggled between an inactive and active state
|
||||
/// </summary>
|
||||
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(fieldDeltas: true), AutoGenerateComponentPause]
|
||||
[Access(typeof(SharedDeployableTurretSystem))]
|
||||
public sealed partial class DeployableTurretComponent : Component
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the turret is toggled 'on' or 'off'
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public bool Enabled = false;
|
||||
|
||||
/// <summary>
|
||||
/// The current state of the turret. Used to inform the device network.
|
||||
/// </summary>
|
||||
[DataField, AutoNetworkedField]
|
||||
public DeployableTurretState CurrentState = DeployableTurretState.Retracted;
|
||||
|
||||
/// <summary>
|
||||
/// The visual state of the turret. Used on the client-side.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public DeployableTurretState VisualState = DeployableTurretState.Retracted;
|
||||
|
||||
/// <summary>
|
||||
/// The physics fixture that will have its collisions disabled when the turret is retracted.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string? DeployedFixture = "turret";
|
||||
|
||||
/// <summary>
|
||||
/// When retracted, the following damage modifier set will be applied to the turret.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<DamageModifierSetPrototype>? RetractedDamageModifierSetId;
|
||||
|
||||
/// <summary>
|
||||
/// When deployed, the following damage modifier set will be applied to the turret.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public ProtoId<DamageModifierSetPrototype>? DeployedDamageModifierSetId;
|
||||
|
||||
#region: Sound data
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when denied access to the turret.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier AccessDeniedSound = new SoundPathSpecifier("/Audio/Machines/custom_deny.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when the turret deploys.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier DeploymentSound = new SoundPathSpecifier("/Audio/Machines/blastdoor.ogg");
|
||||
|
||||
/// <summary>
|
||||
/// Sound to play when the turret retracts.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public SoundSpecifier RetractionSound = new SoundPathSpecifier("/Audio/Machines/blastdoor.ogg");
|
||||
|
||||
#endregion
|
||||
|
||||
#region: Animation data
|
||||
|
||||
/// <summary>
|
||||
/// The length of the deployment animation (in seconds)
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float DeploymentLength = 1.19f;
|
||||
|
||||
/// <summary>
|
||||
/// The length of the retraction animation (in seconds)
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public float RetractionLength = 1.19f;
|
||||
|
||||
/// <summary>
|
||||
/// The time that the current animation should complete (in seconds)
|
||||
/// </summary>
|
||||
[DataField, AutoPausedField]
|
||||
public TimeSpan AnimationCompletionTime = TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// The animation used when turret activates
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public object DeploymentAnimation = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The animation used when turret deactivates
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public object RetractionAnimation = default!;
|
||||
|
||||
/// <summary>
|
||||
/// The key used to index the animation played when turning the turret on/off.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public const string AnimationKey = "deployable_turret_animation";
|
||||
|
||||
#endregion
|
||||
|
||||
#region: Visual state data
|
||||
|
||||
/// <summary>
|
||||
/// The visual state to use when the turret is deployed.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string DeployedState = "cover_open";
|
||||
|
||||
/// <summary>
|
||||
/// The visual state to use when the turret is not deployed.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string RetractedState = "cover_closed";
|
||||
|
||||
/// <summary>
|
||||
/// Used to build the deployment animation when the component is initialized.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string DeployingState = "cover_opening";
|
||||
|
||||
/// <summary>
|
||||
/// Used to build the retraction animation when the component is initialized.
|
||||
/// </summary>
|
||||
[DataField]
|
||||
public string RetractingState = "cover_closing";
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum DeployableTurretVisuals : byte
|
||||
{
|
||||
Turret,
|
||||
Weapon,
|
||||
Broken,
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum DeployableTurretState : byte
|
||||
{
|
||||
Retracted = 0,
|
||||
Deployed = (1 << 0),
|
||||
Retracting = (1 << 1),
|
||||
Deploying = (1 << 1) | Deployed,
|
||||
Firing = (1 << 2) | Deployed,
|
||||
Disabled = (1 << 3),
|
||||
Broken = (1 << 4),
|
||||
}
|
||||
167
Content.Shared/Turrets/SharedDeployableTurretSystem.cs
Normal file
167
Content.Shared/Turrets/SharedDeployableTurretSystem.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Timing;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Weapons.Ranged.Events;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Shared.Audio.Systems;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Physics.Systems;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Shared.Turrets;
|
||||
|
||||
public abstract partial class SharedDeployableTurretSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly SharedPopupSystem _popup = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
[Dependency] private readonly UseDelaySystem _useDelay = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessReader = default!;
|
||||
[Dependency] private readonly DamageableSystem _damageable = default!;
|
||||
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
||||
[Dependency] private readonly SharedWiresSystem _wires = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DeployableTurretComponent, ActivateInWorldEvent>(OnActivate);
|
||||
SubscribeLocalEvent<DeployableTurretComponent, AttemptChangePanelEvent>(OnAttemptChangeWirePanelWire);
|
||||
SubscribeLocalEvent<DeployableTurretComponent, GetVerbsEvent<Verb>>(OnGetVerb);
|
||||
}
|
||||
|
||||
private void OnGetVerb(Entity<DeployableTurretComponent> ent, ref GetVerbsEvent<Verb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || !args.CanComplexInteract)
|
||||
return;
|
||||
|
||||
if (!_accessReader.IsAllowed(args.User, ent))
|
||||
return;
|
||||
|
||||
var user = args.User;
|
||||
|
||||
var verb = new Verb
|
||||
{
|
||||
Priority = 1,
|
||||
Text = ent.Comp.Enabled ? Loc.GetString("deployable-turret-component-deactivate") : Loc.GetString("deployable-turret-component-activate"),
|
||||
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/VerbIcons/Spare/poweronoff.svg.192dpi.png")),
|
||||
Disabled = !HasAmmo(ent),
|
||||
Impact = LogImpact.Low,
|
||||
Act = () => { TryToggleState(ent, user); }
|
||||
};
|
||||
|
||||
args.Verbs.Add(verb);
|
||||
}
|
||||
|
||||
private void OnActivate(Entity<DeployableTurretComponent> ent, ref ActivateInWorldEvent args)
|
||||
{
|
||||
if (TryComp(ent, out UseDelayComponent? useDelay) && !_useDelay.TryResetDelay((ent, useDelay), true))
|
||||
return;
|
||||
|
||||
if (!_accessReader.IsAllowed(args.User, ent))
|
||||
{
|
||||
_popup.PopupClient(Loc.GetString("deployable-turret-component-access-denied"), ent, args.User);
|
||||
_audio.PlayPredicted(ent.Comp.AccessDeniedSound, ent, args.User);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
TryToggleState(ent, args.User);
|
||||
}
|
||||
|
||||
private void OnAttemptChangeWirePanelWire(Entity<DeployableTurretComponent> ent, ref AttemptChangePanelEvent args)
|
||||
{
|
||||
if (!ent.Comp.Enabled || args.Cancelled)
|
||||
return;
|
||||
|
||||
_popup.PopupClient(Loc.GetString("deployable-turret-component-cannot-access-wires"), ent, args.User);
|
||||
|
||||
args.Cancelled = true;
|
||||
}
|
||||
|
||||
public bool TryToggleState(Entity<DeployableTurretComponent> ent, EntityUid? user = null)
|
||||
{
|
||||
return TrySetState(ent, !ent.Comp.Enabled, user);
|
||||
}
|
||||
|
||||
public bool TrySetState(Entity<DeployableTurretComponent> ent, bool enabled, EntityUid? user = null)
|
||||
{
|
||||
if (enabled && ent.Comp.CurrentState == DeployableTurretState.Broken)
|
||||
{
|
||||
if (user != null)
|
||||
_popup.PopupClient(Loc.GetString("deployable-turret-component-is-broken"), ent, user.Value);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (enabled && !HasAmmo(ent))
|
||||
{
|
||||
if (user != null)
|
||||
_popup.PopupClient(Loc.GetString("deployable-turret-component-no-ammo"), ent, user.Value);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SetState(ent, enabled, user);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual void SetState(Entity<DeployableTurretComponent> ent, bool enabled, EntityUid? user = null)
|
||||
{
|
||||
if (ent.Comp.Enabled == enabled)
|
||||
return;
|
||||
|
||||
// Hide the wires panel UI on activation
|
||||
if (enabled && TryComp<WiresPanelComponent>(ent, out var wires) && wires.Open)
|
||||
{
|
||||
_wires.TogglePanel(ent, wires, false);
|
||||
_audio.PlayPredicted(wires.ScrewdriverCloseSound, ent, user);
|
||||
}
|
||||
|
||||
// Determine how much time is remaining in the current animation and the one next in queue
|
||||
// We track this so that when a turret is toggled on/off, we can wait for all queued animations
|
||||
// to end before the turret's HTN is reactivated
|
||||
var animTimeRemaining = MathF.Max((float)(ent.Comp.AnimationCompletionTime - _timing.CurTime).TotalSeconds, 0f);
|
||||
var animTimeNext = enabled ? ent.Comp.DeploymentLength : ent.Comp.RetractionLength;
|
||||
|
||||
ent.Comp.AnimationCompletionTime = _timing.CurTime + TimeSpan.FromSeconds(animTimeNext + animTimeRemaining);
|
||||
|
||||
// Change the turret's damage modifiers
|
||||
if (TryComp<DamageableComponent>(ent, out var damageable))
|
||||
{
|
||||
var damageSetID = enabled ? ent.Comp.DeployedDamageModifierSetId : ent.Comp.RetractedDamageModifierSetId;
|
||||
_damageable.SetDamageModifierSetId(ent, damageSetID, damageable);
|
||||
}
|
||||
|
||||
// Change the turret's fixtures
|
||||
if (ent.Comp.DeployedFixture != null &&
|
||||
TryComp(ent, out FixturesComponent? fixtures) &&
|
||||
fixtures.Fixtures.TryGetValue(ent.Comp.DeployedFixture, out var fixture))
|
||||
{
|
||||
_physics.SetHard(ent, fixture, enabled);
|
||||
}
|
||||
|
||||
// Play pop up message
|
||||
var msg = enabled ? "deployable-turret-component-activating" : "deployable-turret-component-deactivating";
|
||||
_popup.PopupClient(Loc.GetString(msg), ent, user);
|
||||
|
||||
// Update enabled state
|
||||
ent.Comp.Enabled = enabled;
|
||||
DirtyField(ent, ent.Comp, "Enabled");
|
||||
}
|
||||
|
||||
public bool HasAmmo(Entity<DeployableTurretComponent> ent)
|
||||
{
|
||||
var ammoCountEv = new GetAmmoCountEvent();
|
||||
RaiseLocalEvent(ent, ref ammoCountEv);
|
||||
|
||||
return ammoCountEv.Count > 0;
|
||||
}
|
||||
}
|
||||
@@ -43,3 +43,9 @@ public sealed partial class BatteryWeaponFireMode
|
||||
[DataField]
|
||||
public float FireCost = 100;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
public enum BatteryWeaponFireModeVisuals : byte
|
||||
{
|
||||
State
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using System.Linq;
|
||||
using Content.Shared.Access.Components;
|
||||
using Content.Shared.Access.Systems;
|
||||
using Content.Shared.Database;
|
||||
using Content.Shared.Examine;
|
||||
using Content.Shared.Interaction;
|
||||
using Content.Shared.Interaction.Events;
|
||||
using Content.Shared.Popups;
|
||||
using Content.Shared.Verbs;
|
||||
using Content.Shared.Weapons.Ranged.Components;
|
||||
@@ -14,12 +15,14 @@ public sealed class BatteryWeaponFireModesSystem : EntitySystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly SharedPopupSystem _popupSystem = default!;
|
||||
[Dependency] private readonly AccessReaderSystem _accessReaderSystem = default!;
|
||||
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<BatteryWeaponFireModesComponent, ActivateInWorldEvent>(OnInteractHandEvent);
|
||||
SubscribeLocalEvent<BatteryWeaponFireModesComponent, UseInHandEvent>(OnUseInHandEvent);
|
||||
SubscribeLocalEvent<BatteryWeaponFireModesComponent, GetVerbsEvent<Verb>>(OnGetVerb);
|
||||
SubscribeLocalEvent<BatteryWeaponFireModesComponent, ExaminedEvent>(OnExamined);
|
||||
}
|
||||
@@ -44,12 +47,15 @@ public sealed class BatteryWeaponFireModesSystem : EntitySystem
|
||||
|
||||
private void OnGetVerb(EntityUid uid, BatteryWeaponFireModesComponent component, GetVerbsEvent<Verb> args)
|
||||
{
|
||||
if (!args.CanAccess || !args.CanInteract || args.Hands == null)
|
||||
if (!args.CanAccess || !args.CanInteract || !args.CanComplexInteract)
|
||||
return;
|
||||
|
||||
if (component.FireModes.Count < 2)
|
||||
return;
|
||||
|
||||
if (!_accessReaderSystem.IsAllowed(args.User, uid))
|
||||
return;
|
||||
|
||||
for (var i = 0; i < component.FireModes.Count; i++)
|
||||
{
|
||||
var fireMode = component.FireModes[i];
|
||||
@@ -62,11 +68,11 @@ public sealed class BatteryWeaponFireModesSystem : EntitySystem
|
||||
Category = VerbCategory.SelectType,
|
||||
Text = entProto.Name,
|
||||
Disabled = i == component.CurrentFireMode,
|
||||
Impact = LogImpact.Low,
|
||||
Impact = LogImpact.Medium,
|
||||
DoContactInteraction = true,
|
||||
Act = () =>
|
||||
{
|
||||
SetFireMode(uid, component, index, args.User);
|
||||
TrySetFireMode(uid, component, index, args.User);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -74,24 +80,31 @@ public sealed class BatteryWeaponFireModesSystem : EntitySystem
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInteractHandEvent(EntityUid uid, BatteryWeaponFireModesComponent component, ActivateInWorldEvent args)
|
||||
private void OnUseInHandEvent(EntityUid uid, BatteryWeaponFireModesComponent component, UseInHandEvent args)
|
||||
{
|
||||
if (!args.Complex)
|
||||
return;
|
||||
|
||||
if (component.FireModes.Count < 2)
|
||||
return;
|
||||
|
||||
CycleFireMode(uid, component, args.User);
|
||||
TryCycleFireMode(uid, component, args.User);
|
||||
}
|
||||
|
||||
private void CycleFireMode(EntityUid uid, BatteryWeaponFireModesComponent component, EntityUid user)
|
||||
public void TryCycleFireMode(EntityUid uid, BatteryWeaponFireModesComponent component, EntityUid? user = null)
|
||||
{
|
||||
if (component.FireModes.Count < 2)
|
||||
return;
|
||||
|
||||
var index = (component.CurrentFireMode + 1) % component.FireModes.Count;
|
||||
TrySetFireMode(uid, component, index, user);
|
||||
}
|
||||
|
||||
public bool TrySetFireMode(EntityUid uid, BatteryWeaponFireModesComponent component, int index, EntityUid? user = null)
|
||||
{
|
||||
if (index < 0 || index >= component.FireModes.Count)
|
||||
return false;
|
||||
|
||||
if (user != null && !_accessReaderSystem.IsAllowed(user.Value, uid))
|
||||
return false;
|
||||
|
||||
SetFireMode(uid, component, index, user);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetFireMode(EntityUid uid, BatteryWeaponFireModesComponent component, int index, EntityUid? user = null)
|
||||
@@ -100,26 +113,30 @@ public sealed class BatteryWeaponFireModesSystem : EntitySystem
|
||||
component.CurrentFireMode = index;
|
||||
Dirty(uid, component);
|
||||
|
||||
if (_prototypeManager.TryIndex<EntityPrototype>(fireMode.Prototype, out var prototype))
|
||||
{
|
||||
if (TryComp<AppearanceComponent>(uid, out var appearance))
|
||||
_appearanceSystem.SetData(uid, BatteryWeaponFireModeVisuals.State, prototype.ID, appearance);
|
||||
|
||||
if (user != null)
|
||||
_popupSystem.PopupClient(Loc.GetString("gun-set-fire-mode", ("mode", prototype.Name)), uid, user.Value);
|
||||
}
|
||||
|
||||
if (TryComp(uid, out ProjectileBatteryAmmoProviderComponent? projectileBatteryAmmoProviderComponent))
|
||||
{
|
||||
if (!_prototypeManager.TryIndex<EntityPrototype>(fireMode.Prototype, out var prototype))
|
||||
return;
|
||||
|
||||
// TODO: Have this get the info directly from the batteryComponent when power is moved to shared.
|
||||
var OldFireCost = projectileBatteryAmmoProviderComponent.FireCost;
|
||||
projectileBatteryAmmoProviderComponent.Prototype = fireMode.Prototype;
|
||||
projectileBatteryAmmoProviderComponent.FireCost = fireMode.FireCost;
|
||||
|
||||
float FireCostDiff = (float)fireMode.FireCost / (float)OldFireCost;
|
||||
projectileBatteryAmmoProviderComponent.Shots = (int)Math.Round(projectileBatteryAmmoProviderComponent.Shots/FireCostDiff);
|
||||
projectileBatteryAmmoProviderComponent.Capacity = (int)Math.Round(projectileBatteryAmmoProviderComponent.Capacity/FireCostDiff);
|
||||
projectileBatteryAmmoProviderComponent.Shots = (int)Math.Round(projectileBatteryAmmoProviderComponent.Shots / FireCostDiff);
|
||||
projectileBatteryAmmoProviderComponent.Capacity = (int)Math.Round(projectileBatteryAmmoProviderComponent.Capacity / FireCostDiff);
|
||||
|
||||
Dirty(uid, projectileBatteryAmmoProviderComponent);
|
||||
|
||||
var updateClientAmmoEvent = new UpdateClientAmmoEvent();
|
||||
RaiseLocalEvent(uid, ref updateClientAmmoEvent);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
_popupSystem.PopupClient(Loc.GetString("gun-set-fire-mode", ("mode", prototype.Name)), uid, user.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,3 +8,5 @@ construction-insert-info-examine-name-instrument-string = string intrument
|
||||
construction-insert-info-examine-name-instrument-woodwind = woodwind instrument
|
||||
construction-insert-info-examine-name-knife = knife
|
||||
construction-insert-info-examine-name-utensil = utensil
|
||||
construction-insert-info-examine-name-laser-cannon = high power laser weapon
|
||||
construction-insert-info-examine-name-power-cell = power cell
|
||||
|
||||
@@ -9,6 +9,8 @@ device-frequency-prototype-name-fax = Fax
|
||||
device-frequency-prototype-name-basic-device = Basic Devices
|
||||
device-frequency-prototype-name-cyborg-control = Cyborg Control
|
||||
device-frequency-prototype-name-robotics-console = Robotics Console
|
||||
device-frequency-prototype-name-turret = Sentry Turret
|
||||
device-frequency-prototype-name-turret-control = Sentry Turret Control
|
||||
|
||||
## camera frequencies
|
||||
device-frequency-prototype-name-surveillance-camera-test = Subnet Test
|
||||
@@ -32,6 +34,7 @@ device-address-prefix-heater = HTR-
|
||||
device-address-prefix-freezer = FZR-
|
||||
device-address-prefix-volume-pump = VPP-
|
||||
device-address-prefix-smes = SMS-
|
||||
device-address-prefix-turret = TRT-
|
||||
|
||||
# PDAs and terminals
|
||||
device-address-prefix-console = CLS-
|
||||
|
||||
12
Resources/Locale/en-US/weapons/ranged/turrets.ftl
Normal file
12
Resources/Locale/en-US/weapons/ranged/turrets.ftl
Normal file
@@ -0,0 +1,12 @@
|
||||
# Deployable turret component
|
||||
deployable-turret-component-activating = Deploying...
|
||||
deployable-turret-component-deactivating = Deactivating...
|
||||
deployable-turret-component-activate = Activate
|
||||
deployable-turret-component-deactivate = Deactivate
|
||||
deployable-turret-component-access-denied = Access denied
|
||||
deployable-turret-component-no-ammo = Weapon systems depleted
|
||||
deployable-turret-component-is-broken = The turret is heavily damaged and must be repaired
|
||||
deployable-turret-component-cannot-access-wires = You can't reach the maintenance panel while the turret is active
|
||||
|
||||
# Turret notification for station AI
|
||||
station-ai-turret-is-attacking-warning = {CAPITALIZE($source)} has engaged a hostile target.
|
||||
@@ -43,6 +43,8 @@ wires-board-name-jukebox = Jukebox
|
||||
wires-board-name-computer = Computer
|
||||
wires-board-name-holopad = Holopad
|
||||
wires-board-name-barsign = Bar Sign
|
||||
wires-board-name-weapon-energy-turret = Sentry turret
|
||||
wires-board-name-turret-controls = Sentry turret control panel
|
||||
|
||||
# names that get displayed in the wire hacking hud & admin logs.
|
||||
|
||||
|
||||
@@ -87,6 +87,30 @@
|
||||
name: device-frequency-prototype-name-cyborg-control
|
||||
frequency: 1292
|
||||
|
||||
# Turret controllers send data to their turrets on this frequency
|
||||
- type: deviceFrequency
|
||||
id: TurretControl
|
||||
name: device-frequency-prototype-name-turret-control
|
||||
frequency: 2151
|
||||
|
||||
# Turrets send data to their controllers on this frequency
|
||||
- type: deviceFrequency
|
||||
id: Turret
|
||||
name: device-frequency-prototype-name-turret
|
||||
frequency: 2152
|
||||
|
||||
# AI turret controllers send data to their turrets on this frequency
|
||||
- type: deviceFrequency
|
||||
id: TurretControlAI
|
||||
name: device-frequency-prototype-name-turret-control
|
||||
frequency: 2153
|
||||
|
||||
# AI turrets send data to their controllers on this frequency
|
||||
- type: deviceFrequency
|
||||
id: TurretAI
|
||||
name: device-frequency-prototype-name-turret
|
||||
frequency: 2154
|
||||
|
||||
# This frequency will likely have a LARGE number of listening entities. Please don't broadcast on this frequency.
|
||||
- type: deviceFrequency
|
||||
id: SmartLight #used by powered lights.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
- type: entity
|
||||
id: WeaponEnergyTurretStationMachineCircuitboard
|
||||
parent: BaseMachineCircuitboard
|
||||
name: sentry turret machine board
|
||||
description: A machine printed circuit board for a sentry turret.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Misc/module.rsi
|
||||
state: security
|
||||
- type: MachineBoard
|
||||
prototype: WeaponEnergyTurretStation
|
||||
tagRequirements:
|
||||
TurretCompatibleWeapon:
|
||||
amount: 1
|
||||
defaultPrototype: WeaponLaserCannon
|
||||
examineName: construction-insert-info-examine-name-laser-cannon
|
||||
ProximitySensor:
|
||||
amount: 1
|
||||
defaultPrototype: ProximitySensor
|
||||
componentRequirements:
|
||||
PowerCell:
|
||||
amount: 1
|
||||
defaultPrototype: PowerCellMedium
|
||||
examineName: construction-insert-info-examine-name-power-cell
|
||||
|
||||
- type: entity
|
||||
id: WeaponEnergyTurretAIMachineCircuitboard
|
||||
parent: WeaponEnergyTurretStationMachineCircuitboard
|
||||
name: AI sentry turret machine board
|
||||
description: A machine printed circuit board for an AI sentry turret.
|
||||
components:
|
||||
- type: Sprite
|
||||
sprite: Objects/Misc/module.rsi
|
||||
state: command
|
||||
- type: MachineBoard
|
||||
prototype: WeaponEnergyTurretAI
|
||||
@@ -371,6 +371,9 @@
|
||||
- type: HitscanBatteryAmmoProvider
|
||||
proto: RedHeavyLaser
|
||||
fireCost: 100
|
||||
- type: Tag
|
||||
tags:
|
||||
- TurretCompatibleWeapon
|
||||
|
||||
- type: entity
|
||||
name: portable particle decelerator
|
||||
|
||||
@@ -127,9 +127,12 @@
|
||||
maxCharge: 2000
|
||||
startingCharge: 0
|
||||
- type: ApcPowerReceiverBattery
|
||||
idlePowerUse: 5
|
||||
idleLoad: 5
|
||||
batteryRechargeRate: 200
|
||||
batteryRechargeEfficiency: 1.225
|
||||
- type: ApcPowerReceiver
|
||||
powerLoad: 5
|
||||
- type: ExtensionCableReceiver
|
||||
- type: HTN
|
||||
rootTask:
|
||||
task: EnergyTurretCompound
|
||||
@@ -0,0 +1,182 @@
|
||||
- type: entity
|
||||
parent: [BaseWeaponEnergyTurret, ConstructibleMachine]
|
||||
id: WeaponEnergyTurretStation
|
||||
name: sentry turret
|
||||
description: A high-tech autonomous weapons system designed to keep unauthorized personnel out of sensitive areas.
|
||||
components:
|
||||
|
||||
# Physics
|
||||
- type: Fixtures
|
||||
fixtures:
|
||||
body:
|
||||
shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 0.45
|
||||
density: 60
|
||||
mask:
|
||||
- Impassable
|
||||
turret:
|
||||
shape:
|
||||
!type:PhysShapeCircle
|
||||
radius: 0.45
|
||||
density: 60
|
||||
mask:
|
||||
- MachineMask
|
||||
layer:
|
||||
- MachineLayer
|
||||
hard: false
|
||||
|
||||
# Sprites and appearance
|
||||
- type: Sprite
|
||||
sprite: Objects/Weapons/Guns/Turrets/sentry_turret.rsi
|
||||
drawdepth: FloorObjects
|
||||
granularLayersRendering: true
|
||||
layers:
|
||||
- state: support
|
||||
renderingStrategy: NoRotation
|
||||
- state: base_shadow
|
||||
map: [ "shadow" ]
|
||||
- state: base
|
||||
map: [ "base" ]
|
||||
- state: stun
|
||||
map: [ "enum.DeployableTurretVisuals.Weapon" ]
|
||||
shader: "unshaded"
|
||||
visible: false
|
||||
- state: cover_closed
|
||||
map: [ "enum.DeployableTurretVisuals.Turret" ]
|
||||
renderingStrategy: NoRotation
|
||||
- state: cover_light_on
|
||||
map: [ "enum.PowerDeviceVisualLayers.Powered" ]
|
||||
shader: "unshaded"
|
||||
renderingStrategy: NoRotation
|
||||
visible: false
|
||||
- state: panel
|
||||
map: [ "enum.WiresVisualLayers.MaintenancePanel" ]
|
||||
renderingStrategy: NoRotation
|
||||
visible: false
|
||||
- type: AnimationPlayer
|
||||
- type: Appearance
|
||||
- type: GenericVisualizer
|
||||
visuals:
|
||||
enum.BatteryWeaponFireModeVisuals.State:
|
||||
enum.DeployableTurretVisuals.Weapon:
|
||||
BulletEnergyTurretDisabler: { state: stun }
|
||||
BulletEnergyTurretLaser: { state: lethal }
|
||||
enum.DeployableTurretVisuals.Broken:
|
||||
base:
|
||||
True: { state: destroyed }
|
||||
False: { state: base }
|
||||
enum.WiresVisuals.MaintenancePanelState:
|
||||
enum.WiresVisualLayers.MaintenancePanel:
|
||||
True: { visible: false }
|
||||
False: { visible: true }
|
||||
|
||||
# HTN
|
||||
- type: HTN
|
||||
enabled: false
|
||||
|
||||
# Faction / control
|
||||
- type: StationAiWhitelist
|
||||
- type: NpcFactionMember
|
||||
factions:
|
||||
- AllHostile
|
||||
- type: AccessReader
|
||||
access: [["Security"]]
|
||||
|
||||
# Weapon systems
|
||||
- type: ProjectileBatteryAmmoProvider
|
||||
proto: BulletEnergyTurretDisabler
|
||||
fireCost: 100
|
||||
- type: BatteryWeaponFireModes
|
||||
fireModes:
|
||||
- proto: BulletEnergyTurretDisabler
|
||||
fireCost: 100
|
||||
- proto: BulletEnergyTurretLaser
|
||||
fireCost: 100
|
||||
- type: TurretTargetSettings
|
||||
exemptAccessLevels:
|
||||
- Security
|
||||
- Borg
|
||||
- BasicSilicon
|
||||
|
||||
# Defenses / destruction
|
||||
- type: DeployableTurret
|
||||
retractedDamageModifierSetId: Metallic
|
||||
deployedDamageModifierSetId: FlimsyMetallic
|
||||
- type: Damageable
|
||||
damageModifierSet: Metallic
|
||||
- type: Repairable
|
||||
doAfterDelay: 10
|
||||
allowSelfRepair: false
|
||||
- type: Destructible
|
||||
thresholds:
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 300
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound:
|
||||
collection: MetalBreak
|
||||
- !type:DoActsBehavior
|
||||
acts: [ "Breakage" ]
|
||||
- trigger:
|
||||
!type:DamageTrigger
|
||||
damage: 600
|
||||
behaviors:
|
||||
- !type:PlaySoundBehavior
|
||||
sound:
|
||||
collection: MetalBreak
|
||||
- !type:ChangeConstructionNodeBehavior
|
||||
node: machineFrame
|
||||
- !type:DoActsBehavior
|
||||
acts: ["Destruction"]
|
||||
|
||||
# Device network
|
||||
- type: DeviceNetwork
|
||||
deviceNetId: Wired
|
||||
receiveFrequencyId: TurretControl
|
||||
transmitFrequencyId: Turret
|
||||
sendBroadcastAttemptEvent: true
|
||||
prefix: device-address-prefix-turret
|
||||
examinableAddress: true
|
||||
- type: DeviceNetworkRequiresPower
|
||||
- type: WiredNetworkConnection
|
||||
|
||||
# Wires
|
||||
- type: UserInterface
|
||||
interfaces:
|
||||
enum.WiresUiKey.Key:
|
||||
type: WiresBoundUserInterface
|
||||
- type: WiresPanel
|
||||
- type: WiresVisuals
|
||||
- type: Wires
|
||||
boardName: wires-board-name-weapon-energy-turret
|
||||
layoutId: WeaponEnergyTurret
|
||||
- type: Lock
|
||||
locked: true
|
||||
unlockOnClick: false
|
||||
- type: LockedWiresPanel
|
||||
|
||||
# General properties
|
||||
- type: Machine
|
||||
board: WeaponEnergyTurretStationMachineCircuitboard
|
||||
- type: UseDelay
|
||||
delay: 1.2
|
||||
|
||||
- type: entity
|
||||
parent: WeaponEnergyTurretStation
|
||||
id: WeaponEnergyTurretAI
|
||||
name: AI sentry turret
|
||||
description: A high-tech autonomous weapons system under the direct control of a local artifical intelligence.
|
||||
components:
|
||||
- type: AccessReader
|
||||
access: [["StationAi"]]
|
||||
- type: TurretTargetSettings
|
||||
exemptAccessLevels:
|
||||
- Borg
|
||||
- BasicSilicon
|
||||
- type: Machine
|
||||
board: WeaponEnergyTurretAIMachineCircuitboard
|
||||
- type: DeviceNetwork
|
||||
receiveFrequencyId: TurretControlAI
|
||||
transmitFrequencyId: TurretAI
|
||||
@@ -190,6 +190,8 @@
|
||||
curve: !type:BoolCurve
|
||||
- !type:TargetIsCritCon
|
||||
curve: !type:InverseBoolCurve
|
||||
- !type:TargetIsStunnedCon
|
||||
curve: !type:InverseBoolCurve
|
||||
- !type:TurretTargetingCon
|
||||
curve: !type:BoolCurve
|
||||
- !type:TargetDistanceCon
|
||||
|
||||
@@ -205,3 +205,24 @@
|
||||
- !type:PowerWireAction
|
||||
- !type:AiInteractWireAction
|
||||
- !type:AccessWireAction
|
||||
|
||||
- type: wireLayout
|
||||
id: WeaponEnergyTurret
|
||||
dummyWires: 4
|
||||
wires:
|
||||
- !type:PowerWireAction
|
||||
- !type:PowerWireAction
|
||||
pulseTimeout: 15
|
||||
- !type:AiInteractWireAction
|
||||
- !type:AccessWireAction
|
||||
|
||||
- type: wireLayout
|
||||
id: TurretControls
|
||||
dummyWires: 2
|
||||
wires:
|
||||
- !type:PowerWireAction
|
||||
- !type:PowerWireAction
|
||||
pulseTimeout: 15
|
||||
- !type:AiInteractWireAction
|
||||
- !type:AccessWireAction
|
||||
|
||||
@@ -1318,6 +1318,12 @@
|
||||
- type: Tag
|
||||
id: Truncheon
|
||||
|
||||
- type: Tag
|
||||
id: TurretCompatibleWeapon # Used in the construction of sentry turrets
|
||||
|
||||
- type: Tag
|
||||
id: TurretControlElectronics # Used in the construction of sentry turret control panels
|
||||
|
||||
- type: Tag
|
||||
id: Unimplantable
|
||||
|
||||
|
||||
Reference in New Issue
Block a user