Skip to content

Commit

Permalink
Sentry turrets - Part 2: Basic prototype (#35031)
Browse files Browse the repository at this point in the history
  • Loading branch information
chromiumboy authored Feb 23, 2025
1 parent b742afb commit 066c773
Show file tree
Hide file tree
Showing 8 changed files with 500 additions and 263 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Content.Server.Power.EntitySystems;
using Content.Shared.Power.EntitySystems;

namespace Content.Server.Power.Components;

/// <summary>
/// Attached to APC powered entities that possess a rechargeable internal battery.
/// If external power is interrupted, the entity will draw power from this battery instead.
/// Requires <see cref="ApcPowerReceiverComponent"/> and <see cref="BatteryComponent"/> to function.
/// </summary>
[RegisterComponent]
[Access([typeof(PowerNetSystem), typeof(SharedPowerReceiverSystem)])]
public sealed partial class ApcPowerReceiverBatteryComponent : Component
{
/// <summary>
/// Indicates whether power is currently being drawn from the battery.
/// </summary>
[DataField]
public bool Enabled = false;

/// <summary>
/// The passive load the entity places on the APC power network.
/// If not connected to an active APC power network, this amount
/// of power is drained from the battery every second.
/// </summary>
[DataField]
public float IdleLoad { get; set; } = 5f;

/// <summary>
/// Determines how much battery charge the entity's battery gains
/// per second when connected to an active APC power network.
/// </summary>
[DataField]
public float BatteryRechargeRate { get; set; } = 50f;

/// <summary>
/// While the battery is being recharged, the load this entity places on the APC
/// power network is increased by the <see cref="BatteryRechargeRate"/> multiplied
/// by this factor.
/// </summary>
[DataField]
public float BatteryRechargeEfficiency { get; set; } = 1f;
}

/// <summary>
/// Raised whenever an ApcPowerReceiverBattery starts / stops discharging
/// </summary>
[ByRefEvent]
public readonly record struct ApcPowerReceiverBatteryChangedEvent(bool Enabled);
43 changes: 41 additions & 2 deletions Content.Server/Power/EntitySystems/PowerNetSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public sealed class PowerNetSystem : EntitySystem
[Dependency] private readonly PowerNetConnectorSystem _powerNetConnector = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IParallelManager _parMan = default!;
[Dependency] private readonly BatterySystem _battery = default!;

private readonly PowerState _powerState = new();
private readonly HashSet<PowerNet> _powerNetReconnectQueue = new();
Expand Down Expand Up @@ -278,7 +279,7 @@ public override void Update(float frameTime)
// Send events where necessary.
// TODO: Instead of querying ALL power components every tick, and then checking if an event needs to be
// raised, should probably assemble a list of entity Uids during the actual solver steps.
UpdateApcPowerReceiver();
UpdateApcPowerReceiver(frameTime);
UpdatePowerConsumer();
UpdateNetworkBattery();
}
Expand Down Expand Up @@ -306,10 +307,12 @@ private void ReconnectNetworks()
_powerNetReconnectQueue.Clear();
}

private void UpdateApcPowerReceiver()
private void UpdateApcPowerReceiver(float frameTime)
{
var appearanceQuery = GetEntityQuery<AppearanceComponent>();
var metaQuery = GetEntityQuery<MetaDataComponent>();
var apcBatteryQuery = GetEntityQuery<ApcPowerReceiverBatteryComponent>();

var enumerator = AllEntityQuery<ApcPowerReceiverComponent>();
while (enumerator.MoveNext(out var uid, out var apcReceiver))
{
Expand All @@ -318,6 +321,42 @@ private void UpdateApcPowerReceiver()
|| MathHelper.CloseToPercent(apcReceiver.NetworkLoad.ReceivingPower,
apcReceiver.Load));

// Check if the entity has an internal battery
if (apcBatteryQuery.TryComp(uid, out var apcBattery) && TryComp<BatteryComponent>(uid, out var battery))
{
apcReceiver.Load = apcBattery.IdleLoad;

// Try to draw power from the battery if there isn't sufficient external power
var requireBattery = !powered && !apcReceiver.PowerDisabled;

if (requireBattery)
{
_battery.SetCharge(uid, battery.CurrentCharge - apcBattery.IdleLoad * frameTime, battery);
}

// Otherwise try to charge the battery
else if (powered && !_battery.IsFull(uid, battery))
{
apcReceiver.Load += apcBattery.BatteryRechargeRate * apcBattery.BatteryRechargeEfficiency;
_battery.SetCharge(uid, battery.CurrentCharge + apcBattery.BatteryRechargeRate * frameTime, battery);
}

// Enable / disable the battery if the state changed
var enableBattery = requireBattery && battery.CurrentCharge > 0;

if (apcBattery.Enabled != enableBattery)
{
apcBattery.Enabled = enableBattery;

var apcBatteryEv = new ApcPowerReceiverBatteryChangedEvent(enableBattery);
RaiseLocalEvent(uid, ref apcBatteryEv);

_appearance.SetData(uid, PowerDeviceVisuals.BatteryPowered, enableBattery);
}

powered |= enableBattery;
}

// If new value is the same as the old, then exit
if (!apcReceiver.Recalculate && apcReceiver.Powered == powered)
continue;
Expand Down
3 changes: 2 additions & 1 deletion Content.Shared/Power/SharedPowerDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Content.Shared.Power
public enum PowerDeviceVisuals : byte
{
VisualState,
Powered
Powered,
BatteryPowered
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,34 @@
tags:
- HideContextMenu
- type: AnimationPlayer

- type: entity
parent: MuzzleFlashEffect
id: MuzzleFlashEffectOmnilaser
categories: [ HideSpawnMenu ]
components:
- type: Sprite
drawdepth: BelowMobs
offset: 0.15, 0
layers:
- shader: unshaded
map: ["enum.EffectLayers.Unshaded"]
sprite: Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi
state: omnilaser_flash

- type: entity
parent: MuzzleFlashEffect
id: MuzzleFlashEffectHeavyLaser
categories: [ HideSpawnMenu ]
components:
- type: Sprite
drawdepth: BelowMobs
offset: 0.15, 0
layers:
- shader: unshaded
map: ["enum.EffectLayers.Unshaded"]
sprite: Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi
state: heavylaser_flash

# One bullet to bring them all into the darkness and bind them
- type: entity
Expand Down Expand Up @@ -949,6 +977,81 @@
collection: WeakHit
forceSound: true

- type: entity
name : energy bolt
id: BulletEnergyTurretBase
parent: BaseBullet
categories: [ HideSpawnMenu ]
components:
- type: Reflective
reflective:
- Energy
- type: FlyBySound
sound:
collection: EnergyMiss
params:
volume: 5
- type: Sprite
sprite: Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi
layers:
- state: heavylaser
shader: unshaded
- type: Physics
- type: Fixtures
fixtures:
projectile:
shape:
!type:PhysShapeAabb
bounds: "-0.15,-0.3,0.15,0.3"
hard: false
mask:
- Opaque
fly-by: *flybyfixture
- type: Ammo

- type: entity
name : laser bolt
id: BulletEnergyTurretLaser
parent: BulletEnergyTurretBase
categories: [ HideSpawnMenu ]
components:
- type: Ammo
muzzleFlash: MuzzleFlashEffectHeavyLaser
- type: Sprite
sprite: Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi
layers:
- state: heavylaser
shader: unshaded
- type: Projectile
impactEffect: BulletImpactEffectOrangeDisabler
damage:
types:
Heat: 28

- type: entity
name : disabler bolt
id: BulletEnergyTurretDisabler
parent: BulletEnergyTurretBase
categories: [ HideSpawnMenu ]
components:
- type: Ammo
muzzleFlash: MuzzleFlashEffectOmnilaser
- type: Sprite
sprite: Objects/Weapons/Guns/Projectiles/projectiles_tg.rsi
layers:
- state: omnilaser
shader: unshaded
- type: StaminaDamageOnCollide
damage: 30
- type: Projectile
impactEffect: BulletImpactEffectDisabler
damage:
types:
Heat: 0
soundHit:
collection: WeakHit
forceSound: true

- type: entity
name: tesla gun lightning
id: TeslaGunBullet
Expand Down Expand Up @@ -1051,4 +1154,4 @@
- type: ProjectileSpread
proto: BulletDisablerSmg
count: 3 #bit stronger than a disabler if you hit your shots you goober, still not a 2 hit stun though
spread: 9
spread: 9
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
- type: entity
parent: [BaseWeaponBallisticTurret, BaseSyndicateContraband]
id: WeaponTurretSyndicate
suffix: Syndicate
components:
- type: NpcFactionMember
factions:
- Syndicate

- type: entity
parent: BaseWeaponBallisticTurret
id: WeaponTurretSyndicateDisposable
name: disposable ballistic turret
suffix: Syndicate, Disposable
components:
- type: NpcFactionMember
factions:
- Syndicate
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 600
behaviors:
- !type:DoActsBehavior
acts: [ "Destruction" ]
- trigger:
!type:DamageTrigger
damage: 100
behaviors:
- !type:DoActsBehavior
acts: [ "Destruction" ]
- !type:TriggerBehavior
- type: Gun
fireRate: 2
selectedMode: FullAuto
availableModes:
- FullAuto
soundGunshot: /Audio/Weapons/Guns/Gunshots/gun_sentry.ogg
- type: BallisticAmmoProvider
proto: CartridgePistol
capacity: 50
- type: Construction
deconstructionTarget: null
graph: WeaponTurretSyndicateDisposable
node: disposableTurret
- type: Repairable
qualityNeeded: "Anchoring"
doAfterDelay: 3
- type: TriggerWhenEmpty
- type: ExplodeOnTrigger
- type: Explosive
explosionType: Default
maxIntensity: 10
intensitySlope: 1.5
totalIntensity: 30
canCreateVacuum: false

- type: entity
parent: BaseWeaponBallisticTurret
id: WeaponTurretNanoTrasen
suffix: NanoTrasen
components:
- type: NpcFactionMember
factions:
- NanoTrasen

- type: entity
parent: BaseWeaponBallisticTurret
id: WeaponTurretHostile
suffix: Hostile
components:
- type: NpcFactionMember
factions:
- SimpleHostile

- type: entity
parent: BaseWeaponBallisticTurret
id: WeaponTurretAllHostile
suffix: All hostile
components:
- type: NpcFactionMember
factions:
- AllHostile

- type: entity
parent: BaseWeaponBallisticTurret
id: WeaponTurretXeno
name: xeno turret
suffix: Xeno
description: Shoots 9mm acid projectiles.
components:
- type: NpcFactionMember
factions:
- Xeno
- type: Sprite
sprite: Objects/Weapons/Guns/Turrets/xenoturret.rsi
noRot: true
layers:
- state: acid_turret
- type: BallisticAmmoProvider
proto: BulletAcid
capacity: 500
- type: Gun
fireRate: 1
selectedMode: FullAuto
soundGunshot: /Audio/Weapons/Xeno/alien_spitacid.ogg
- type: HTN
rootTask:
task: TurretCompound
blackboard:
SoundTargetInLOS: !type:SoundPathSpecifier
path: /Audio/Animals/snake_hiss.ogg
- type: Damageable
damageContainer: Biological
- type: Destructible
thresholds:
- trigger:
!type:DamageTrigger
damage: 100
behaviors:
- !type:DoActsBehavior
acts: [ "Destruction" ]
- !type:PlaySoundBehavior
sound:
path: /Audio/Effects/gib1.ogg
- !type:SpawnEntitiesBehavior
spawn:
FoodMeatXeno:
min: 3
max: 5
- type: InteractionPopup
interactDelay: 1.0
successChance: 0.8
interactSuccessString: petting-success-generic
interactFailureString: petting-failure-generic
interactSuccessSound:
path: /Audio/Animals/snake_hiss.ogg
Loading

0 comments on commit 066c773

Please sign in to comment.