Files
tbd-station-14/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.Battery.cs
beck-thompson 492a1aa9c3 Hitscans are now entities (#38035)
* Hitscans are now entities

* Cleanup

* Cleanup

* Silly mistakes but stop sign testing helps :)

* Address most of the review

* Reviews

* perry :(

* Final reviews

* Add comments

* Split event up

* better comment

* cleanup
2025-10-18 05:42:08 +00:00

176 lines
6.8 KiB
C#

using Content.Shared.Damage;
using Content.Shared.Damage.Events;
using Content.Shared.Examine;
using Content.Shared.Projectiles;
using Content.Shared.Weapons.Hitscan.Components;
using Content.Shared.Weapons.Ranged.Components;
using Content.Shared.Weapons.Ranged.Events;
using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
namespace Content.Shared.Weapons.Ranged.Systems;
public abstract partial class SharedGunSystem
{
protected virtual void InitializeBattery()
{
// Trying to dump comp references hence the below
// Hitscan
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, ComponentGetState>(OnBatteryGetState);
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, ComponentHandleState>(OnBatteryHandleState);
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, TakeAmmoEvent>(OnBatteryTakeAmmo);
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, GetAmmoCountEvent>(OnBatteryAmmoCount);
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, ExaminedEvent>(OnBatteryExamine);
SubscribeLocalEvent<HitscanBatteryAmmoProviderComponent, DamageExamineEvent>(OnBatteryDamageExamine);
// Projectile
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, ComponentGetState>(OnBatteryGetState);
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, ComponentHandleState>(OnBatteryHandleState);
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, TakeAmmoEvent>(OnBatteryTakeAmmo);
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, GetAmmoCountEvent>(OnBatteryAmmoCount);
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, ExaminedEvent>(OnBatteryExamine);
SubscribeLocalEvent<ProjectileBatteryAmmoProviderComponent, DamageExamineEvent>(OnBatteryDamageExamine);
}
private void OnBatteryHandleState(EntityUid uid, BatteryAmmoProviderComponent component, ref ComponentHandleState args)
{
if (args.Current is not BatteryAmmoProviderComponentState state)
return;
component.Shots = state.Shots;
component.Capacity = state.MaxShots;
component.FireCost = state.FireCost;
UpdateAmmoCount(uid, prediction: false);
}
private void OnBatteryGetState(EntityUid uid, BatteryAmmoProviderComponent component, ref ComponentGetState args)
{
args.State = new BatteryAmmoProviderComponentState()
{
Shots = component.Shots,
MaxShots = component.Capacity,
FireCost = component.FireCost,
};
}
private void OnBatteryExamine(EntityUid uid, BatteryAmmoProviderComponent component, ExaminedEvent args)
{
args.PushMarkup(Loc.GetString("gun-battery-examine", ("color", AmmoExamineColor), ("count", component.Shots)));
}
private void OnBatteryDamageExamine<T>(Entity<T> entity, ref DamageExamineEvent args) where T : BatteryAmmoProviderComponent
{
var damageSpec = GetDamage(entity.Comp);
if (damageSpec == null)
return;
var damageType = entity.Comp switch
{
HitscanBatteryAmmoProviderComponent => Loc.GetString("damage-hitscan"),
ProjectileBatteryAmmoProviderComponent => Loc.GetString("damage-projectile"),
_ => throw new ArgumentOutOfRangeException(),
};
_damageExamine.AddDamageExamine(args.Message, Damageable.ApplyUniversalAllModifiers(damageSpec), damageType);
}
private DamageSpecifier? GetDamage(BatteryAmmoProviderComponent component)
{
if (component is ProjectileBatteryAmmoProviderComponent battery)
{
if (ProtoManager.Index<EntityPrototype>(battery.Prototype).Components
.TryGetValue(Factory.GetComponentName<ProjectileComponent>(), out var projectile))
{
var p = (ProjectileComponent) projectile.Component;
if (!p.Damage.Empty)
{
return p.Damage * Damageable.UniversalProjectileDamageModifier;
}
}
return null;
}
if (component is HitscanBatteryAmmoProviderComponent hitscan)
{
var dmg = ProtoManager.Index(hitscan.HitscanEntityProto);
if (!dmg.TryGetComponent<HitscanBasicDamageComponent>(out var basicDamageComp, Factory))
return null;
return basicDamageComp.Damage * Damageable.UniversalHitscanDamageModifier;
}
return null;
}
private void OnBatteryTakeAmmo(EntityUid uid, BatteryAmmoProviderComponent component, TakeAmmoEvent args)
{
var shots = Math.Min(args.Shots, component.Shots);
// Don't dirty if it's an empty fire.
if (shots == 0)
return;
for (var i = 0; i < shots; i++)
{
args.Ammo.Add(GetShootable(component, args.Coordinates));
component.Shots--;
}
TakeCharge((uid, component));
UpdateBatteryAppearance(uid, component);
Dirty(uid, component);
}
private void OnBatteryAmmoCount(EntityUid uid, BatteryAmmoProviderComponent component, ref GetAmmoCountEvent args)
{
args.Count = component.Shots;
args.Capacity = component.Capacity;
}
/// <summary>
/// Update the battery (server-only) whenever fired.
/// </summary>
protected virtual void TakeCharge(Entity<BatteryAmmoProviderComponent> entity)
{
UpdateAmmoCount(entity, prediction: false);
}
protected void UpdateBatteryAppearance(EntityUid uid, BatteryAmmoProviderComponent component)
{
if (!TryComp<AppearanceComponent>(uid, out var appearance))
return;
Appearance.SetData(uid, AmmoVisuals.HasAmmo, component.Shots != 0, appearance);
Appearance.SetData(uid, AmmoVisuals.AmmoCount, component.Shots, appearance);
Appearance.SetData(uid, AmmoVisuals.AmmoMax, component.Capacity, appearance);
}
private (EntityUid? Entity, IShootable) GetShootable(BatteryAmmoProviderComponent component, EntityCoordinates coordinates)
{
switch (component)
{
case ProjectileBatteryAmmoProviderComponent proj:
var ent = Spawn(proj.Prototype, coordinates);
return (ent, EnsureShootable(ent));
case HitscanBatteryAmmoProviderComponent hitscan:
var hitscanEnt = Spawn(hitscan.HitscanEntityProto);
return (hitscanEnt, EnsureShootable(hitscanEnt));
default:
throw new ArgumentOutOfRangeException();
}
}
[Serializable, NetSerializable]
private sealed class BatteryAmmoProviderComponentState : ComponentState
{
public int Shots;
public int MaxShots;
public float FireCost;
}
}