Aetheria/Assets/Scripts/ServerShared/Behaviors/Weapon.cs
Meta Vens 15723f4037 Mining Cut 3: a chunk is a target, detected by reflected light
- Entity.Target is one slot of TargetRef, read-only from outside
  (IReadOnlyReactiveProperty); Entity.SetTarget is its one writer. A chunk is
  taken only while the entity can see it, so a rock in darkness cannot be
  picked. Every handler, agent, turret and test writes through SetTarget.
- Entity.ChunkInfo (Q14 A): the value the per-tick sensor rule settles at,
  computed on demand through Sensor.PassiveRate/Sensor.Gain -- sum of the
  active sensors' passive rates over (sensors x TargetInfoDecay), capped at 1.
  Stored nowhere; no per-tick loop over chunks. ChunkVisible is the detection
  threshold through the chunk path.
- The lost-track rule reads through the chunk path: Activate's reconciliation
  and a per-tick check of the one slot drop a chunk target that has gone dark.
- Entity.VisibleChunksInReach (Q13 A): visible chunks within the longest range
  among active weapons -- the one reach rule for cycling and programmatic picks.
- Entity-only readers take .Entity: TargetedBy/TargetedByCount, target items,
  the stance gate (a chunk has no stance), lock (Q12 A: never on a chunk),
  turrets, Combat, Minion, FireControl (a chunk fires untargeted until Cut 4).
  TargetRange and the arc gate read a chunk's planar position.
- ActionGameManager: the reticle and next/previous offer visible ships and
  visible chunks in reach; nearest stays enemies-only; the target panel,
  indicator and fire-control debug take TargetRef. EntityInstance reads .Entity.
- MiningCut3Tests pin parity, darkness, monotone brightness, wear and break,
  reach, the chunk query, target survival and loss, TargetRef equality, lock,
  and field-kind assignment at generation and first load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:56:39 +02:00

153 lines
5.7 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; }
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;
}
}