Aetheria/Assets/Scripts/ServerShared/Behaviors/Weapon.cs
Meta Vens f88d1fc1b3 Mining Cut 3 fix: launchers do not count toward rock reach (Q12 A)
Weapon.CanMine is the one mining predicate; LockWeapon overrides it to
false. Entity.VisibleChunksInReach counts only weapons that can mine.

Tests: LaunchersDoNotCountTowardChunkReach (a launcher-only ship is offered
no rock; a gun of 200 beside a launcher of 400 is offered the near rock
only) and AnOfflineWeaponAddsNoReach (Soul F8: reach reads Active, not
Enabled).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 13:02:26 +02:00

158 lines
6 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 GameCult.Caching;
using System;
using System.Collections.Generic;
using System.Linq;
using MessagePack;
using Newtonsoft.Json;
using CultMath;
using static CultMath.math;
[Inspectable,
Union(0, typeof(InstantWeaponData)),
Union(1, typeof(LauncherData)),
Union(2, typeof(ConstantWeaponData)),
Union(3, typeof(ChargedWeaponData)),
RuntimeInspectable]
public abstract class WeaponData : BehaviorData
{
[Inspectable, JsonProperty("damageType"), Key(1), RuntimeInspectable]
public DamageType DamageType;
[Inspectable, JsonProperty("damage"), Key(2), RuntimeInspectable]
public PerformanceStat Damage = new PerformanceStat();
[Inspectable, CultInspectorRange(0, 1), JsonProperty("penetration"), Key(3), RuntimeInspectable]
public PerformanceStat Penetration = new PerformanceStat();
[Inspectable, CultInspectorRange(0, 1), JsonProperty("damageSpread"), Key(4)]
public PerformanceStat DamageSpread = new PerformanceStat();
[Inspectable, JsonProperty("minRange"), Key(5)]
public PerformanceStat MinRange = new PerformanceStat();
[Inspectable, JsonProperty("range"), Key(6)]
public PerformanceStat Range = new PerformanceStat();
[InspectableAnimationCurve, JsonProperty("damageCurve"), Key(7)]
public BezierCurve DamageCurve;
[Inspectable, CultInspectorAssetGuid, JsonProperty("effect"), Key(8)]
public string EffectPrefab;
[Inspectable, JsonProperty("energy"), Key(9), RuntimeInspectable]
public PerformanceStat Energy = new PerformanceStat();
[Inspectable, JsonProperty("heat"), Key(10), RuntimeInspectable]
public PerformanceStat Heat = new PerformanceStat();
[Inspectable, JsonProperty("visibility"), Key(11), RuntimeInspectable]
public PerformanceStat Visibility = new PerformanceStat();
[Inspectable, JsonProperty("ammo"), Key(12), RuntimeInspectable]
public CultRecordRef<SimpleCommodityData> AmmoType;
[Inspectable, JsonProperty("magSize"), Key(13)]
public int MagazineSize;
[Inspectable, JsonProperty("reloadTime"), Key(14)]
public float ReloadTime = 1;
[Inspectable, JsonProperty("spread"), Key(15)]
public PerformanceStat Spread = new PerformanceStat();
[Inspectable, JsonProperty("velocity"), Key(16)]
public PerformanceStat Velocity = new PerformanceStat();
}
public abstract class Weapon : Behavior, IActivatedBehavior
{
private WeaponData _data;
public abstract float DamagePerSecond { get; }
public abstract float RangeDamagePerSecond(float range);
public abstract int Ammo { get; }
public WeaponData WeaponData => _data;
public float Damage { get; protected set; }
public float Penetration { get; protected set; }
public float DamageSpread { get; protected set; }
public float MinRange { get; protected set; }
public float Range { get; protected set; }
public float Energy { get; protected set; }
public float Heat { get; protected set; }
public float Visibility { get; protected set; }
public float Spread { get; protected set; }
public float Velocity { get; protected set; }
// Mining Cut 3 fix (Q12 A, "launchers cannot mine"): whether this weapon can mine a chunk. The one mining
// predicate: chunk reach (Entity.VisibleChunksInReach) reads it, and so does anything that decides a weapon
// may work a chunk. A launcher overrides it.
public virtual bool CanMine => true;
protected bool _firing;
public bool Firing
{
get => _firing;
}
// Single shared fire gate for player and AI shooters alike: weapons are safed by the
// shooter's OWN declared stance toward its target, not the target's stance toward the shooter.
// With no target set, behaviour is unchanged (nothing to be safe about). Mining Cut 3: a chunk has no stance,
// so a weapon is never safed against one.
public bool StanceAllowsFire => Entity.Target.Value.Entity == null || Entity.IsHostileTo(Entity.Target.Value.Entity);
// Cut 3 (docs/fire-control-cut.md, Q2): player fire is arc-gated like everyone else's. This is the same
// predicate (FireControl.InArc) Combat.cs and TurretController.cs gate AI and turret fire with, read at
// the one point every shooter's trigger passes through (InstantWeapon.Trigger) -- not a parallel check, and
// not the AI's own AgentMinHitProbability heuristic, which is a "worth it" decision layered above this
// gate, not the gate itself. With no target set, behaviour is unchanged (nothing to bear on).
// Cut 5, 5.4 (Soul finding 11): the point-blank special case this used to carry moved into InArc itself --
// one bearing test, one owner, so every caller (not just this one) gets a point-blank shot that bears.
// FireControl owns the gate, including its one exemption: a fused weapon fires out of arc.
public bool ArcAllowsFire => FireControl.ArcPermitsFire(this, Entity);
public Weapon(WeaponData data, EquippedItem item) : base(data, item)
{
_data = data;
}
public Weapon(WeaponData data, ConsumableItemEffect item) : base(data, item)
{
_data = data;
}
protected virtual void UpdateStats()
{
Damage = Evaluate(_data.Damage);
Penetration = Evaluate(_data.Penetration);
DamageSpread = Evaluate(_data.DamageSpread);
MinRange = Evaluate(_data.MinRange);
Range = Evaluate(_data.Range);
Energy = Evaluate(_data.Energy);
Heat = Evaluate(_data.Heat);
Visibility = Evaluate(_data.Visibility);
Spread = Evaluate(_data.Spread);
Velocity = Evaluate(_data.Velocity);
}
public override bool Execute(float dt)
{
UpdateStats();
return true;
}
public virtual void Activate()
{
_firing = true;
}
public virtual void Deactivate()
{
_firing = false;
}
}