Aetheria/Assets/Scripts/ServerShared/Behaviors/ConstantWeapon.cs
Meta Vens a351009956 Gate weapon fire on the shooter's own stance, shared by player and AI
Weapon.StanceAllowsFire is the one shared gate: true with no target, else the
shooter's own Entity.IsHostileTo(Target) -- not the target's stance toward the
shooter. It lives on the Weapon base class so every weapon subtype and every
caller (player input, TurretController, AI) goes through the same rule.

Wired at the two places weapons actually start dealing damage:
- InstantWeapon.Trigger (the base for InstantWeapon, AutoWeapon, ChargedWeapon
  and LockWeapon/Launcher, all of which route real firing through Trigger).
- ConstantWeapon.Execute's continuous-beam loop, which stops firing (and
  raises OnStopFiring) the moment the shooter's stance disallows it.

Also fixes LockWeapon's lock-building condition, which read the wrong
direction (target.IsHostileTo(shooter) instead of shooter.IsHostileTo(target))
-- the same backwards check as the HUD lock indicator, fixed alongside this in
ActionGameManager.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:00:59 +02:00

159 lines
No EOL
4.5 KiB
C#

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
using System;
using MessagePack;
using Newtonsoft.Json;
using CultMath;
using static CultMath.math;
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class ConstantWeaponData : WeaponData
{
[Inspectable, JsonProperty("ammoInterval"), Key(17)]
public float AmmoInterval = 1;
public override Behavior CreateInstance(EquippedItem item)
{
return new ConstantWeapon(this, item);
}
public override Behavior CreateInstance(ConsumableItemEffect item)
{
return new ConstantWeapon(this, item);
}
}
public class ConstantWeapon : Weapon, IProgressBehavior, IEventBehavior
{
private ConstantWeaponData _data;
private int _ammo = 1;
private float _ammoInterval;
private float _reload;
private bool _reloading;
public override int Ammo
{
get => _ammo;
}
public float Progress
{
get { return saturate(_reload); }
}
public override float DamagePerSecond => Damage;
public override float RangeDamagePerSecond(float range)
{
return Damage * _data.DamageCurve.Evaluate(saturate(unlerp(MinRange, Range, range)));
}
public event Action OnReloadBegin;
public event Action OnReloadComplete;
public event Action OnStartFiring;
public event Action OnStopFiring;
public void ResetEvents()
{
OnReloadBegin = null;
OnReloadComplete = null;
OnStartFiring = null;
OnStopFiring = null;
}
public ConstantWeapon(ConstantWeaponData data, EquippedItem item) : base(data, item)
{
_data = data;
}
public ConstantWeapon(ConstantWeaponData data, ConsumableItemEffect item) : base(data, item)
{
_data = data;
}
public override bool Execute(float dt)
{
base.Execute(dt);
if (_firing && !StanceAllowsFire)
{
// Safed: shooter has a target and hasn't declared hostility toward it.
_firing = false;
OnStopFiring?.Invoke();
return false;
}
if (_firing)
{
if (!Entity.TryConsumeEnergy(Evaluate(_data.Energy) * dt))
{
_firing = false;
OnStopFiring?.Invoke();
return false;
}
if (_data.AmmoType.IsSet())
{
if (_reloading)
{
_reload -= dt / _data.ReloadTime;
if (_reload < 0)
{
_reloading = false;
OnReloadComplete?.Invoke();
}
return false;
}
_ammoInterval -= dt / _data.AmmoInterval;
if (_ammoInterval < 0)
{
_ammoInterval = 1;
if (_data.MagazineSize > 1 && _ammo > 0) _ammo--;
else
{
var cargo = Entity.FindItemInCargo(_data.AmmoType.Key);
if (cargo != null)
{
var item = cargo.ItemsOfType[_data.AmmoType.Key][0];
if (item is SimpleCommodity simpleCommodity)
cargo.Remove(simpleCommodity, 1);
if(_data.MagazineSize > 1)
{
_reloading = true;
_reload = 1;
OnReloadBegin?.Invoke();
_firing = false;
OnStopFiring?.Invoke();
}
}
return false;
}
}
}
CauseWearDamage(dt);
AddHeat(Evaluate(_data.Heat) * dt);
Entity.VisibilitySources[this] = Evaluate(_data.Visibility);
}
return true;
}
public override void Activate()
{
if(!_firing && !_reloading)
{
_firing = true;
OnStartFiring?.Invoke();
}
}
public override void Deactivate()
{
if (_firing)
{
_firing = false;
OnStopFiring?.Invoke();
}
}
}