TargetingSystem is required interior Tool gear: Accuracy/Resolution/ Precision/Tracking are PerformanceStats reached through the usual Evaluate path, so lot quality, wear, heat and PowerSupply all apply (a starved system rolls worse, never switches off). FireControl.Accuracy/ Resolution/Precision fall back to GameplaySettings.UnaidedAccuracy (new, authored low per Q4) whenever an entity has no targeting system or its own is destroyed/disabled. FireControl.IsRevealed derives R5's reveal rule from EntityInfoGathered: non-hull equipment ranks hardpoint-mounted first, then by size, and reveals between TargetArmorInfoThreshold and TargetGearInfoThreshold -- the first code to read either setting. Entity.TargetItem/ TrySelectTargetItem/ResolvedTargetItem give both the player and AI one shared, revoke-on-decay aim-point slot with a single writer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
103 lines
5.3 KiB
C#
103 lines
5.3 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.Linq;
|
|
using CultMath;
|
|
using static CultMath.math;
|
|
|
|
// Cut 1 (docs/fire-control-cut.md): geometry -- whether a weapon bears on a point is decided here and
|
|
// nowhere else -- no prefab, transform, ArticulationPoint or renderer may influence it (R6, R7). Mount
|
|
// direction and arc are both derived per read from catalog data and the item's authored rotation; neither
|
|
// is ever stored on the entity.
|
|
// Cut 2 adds the reveal rule (R5) and the resolved targeting-system stats an entity fires with, both
|
|
// likewise derived on every read and never cached.
|
|
public static class FireControl
|
|
{
|
|
// The planar unit vector a hardpoint's weapon points along, derived solely from the item's authored
|
|
// rotation (R6, the same convention reaction thrusters already use) -- never from a live Unity
|
|
// transform. This is the item-rotation path Behavior.Direction now uses unconditionally, after the
|
|
// HardpointTransforms readback branch is deleted.
|
|
public static float3 MountDirection(EquippedItem item)
|
|
{
|
|
var itemDirection = item.Entity.Direction.Rotate(item.EquippableItem.Rotation);
|
|
return normalize(float3(itemDirection.x, 0, itemDirection.y));
|
|
}
|
|
|
|
// Full arc width in degrees: the per-hardpoint override when authored (nonzero), else
|
|
// GameplaySettings.FiringArc. Zero on the hardpoint means "use the default," not "no arc."
|
|
public static float ArcFor(EquippedItem item)
|
|
{
|
|
var hardpoint = item.Entity.Hardpoints[item.Position.x, item.Position.y];
|
|
var arc = hardpoint?.FiringArc ?? 0f;
|
|
return arc > 0 ? arc : item.ItemManager.GameplaySettings.FiringArc;
|
|
}
|
|
|
|
// Planar bearing test (R7: the simulation is 2D, the 3D is set dressing -- target height never enters
|
|
// this). An arc of 360 degrees or more passes unconditionally, which is how a turret hardpoint
|
|
// (authored FiringArc: 360, Q1) tracks all the way around without a second type.
|
|
public static bool InArc(EquippedItem weapon, float3 toTarget)
|
|
{
|
|
var arc = ArcFor(weapon);
|
|
if (arc >= 360f) return true;
|
|
var planarTarget = normalize(float3(toTarget.x, 0, toTarget.z));
|
|
return dot(MountDirection(weapon), planarTarget) >= cos(radians(arc / 2f));
|
|
}
|
|
|
|
// Cut 2, R5: whether `observer` has resolved `item` (which must belong to some entity in the zone) well
|
|
// enough to aim at it. Derived fresh from EntityInfoGathered on every call -- nothing caches a reveal
|
|
// result, so decay drops it the moment info falls back below tier, with no clearing loop anywhere.
|
|
//
|
|
// Ranking: the target's own non-hull equipment, hardpoint-mounted gear first (the big obvious things),
|
|
// then by size descending, ties broken by equipment order (OrderByDescending is a stable sort, so this
|
|
// needs no explicit tie-break key). Item i of N reveals once info crosses
|
|
// lerp(TargetArmorInfoThreshold, TargetGearInfoThreshold, i / max(1, N - 1)) -- the first item to reveal
|
|
// does so at the armor tier, the last at the gear tier.
|
|
public static bool IsRevealed(Entity observer, EquippedItem item)
|
|
{
|
|
var target = item.Entity;
|
|
var ranked = target.Equipment
|
|
.Where(e => e.Data.HardpointType != HardpointType.Hull)
|
|
.OrderByDescending(e => e.Data.HardpointType != HardpointType.Tool)
|
|
.ThenByDescending(e => e.Data.Shape.Coordinates.Length)
|
|
.ToList();
|
|
var index = ranked.IndexOf(item);
|
|
if (index < 0) return false; // the hull itself, or an item that is no longer on this target
|
|
|
|
var settings = observer.ItemManager.GameplaySettings;
|
|
var info = observer.EntityInfoGathered.TryGetValue(target, out var gathered) ? gathered : 0f;
|
|
var tier = lerp(settings.TargetArmorInfoThreshold, settings.TargetGearInfoThreshold,
|
|
(float) index / max(1, ranked.Count - 1));
|
|
return info >= tier;
|
|
}
|
|
|
|
// Cut 2, Q4: the stats an entity actually fires with. A working targeting system (equipped, online --
|
|
// not destroyed, not shut down) supplies its own resolved stats; anything else falls back to the
|
|
// authored unaided figures, which make aiming without one a last resort rather than an alternative.
|
|
// Cut 3's roll is the only planned reader.
|
|
public static float Accuracy(Entity entity)
|
|
{
|
|
var system = entity.GetBehavior<TargetingSystem>();
|
|
return system != null && system.Item.Active.Value
|
|
? system.Accuracy
|
|
: entity.ItemManager.GameplaySettings.UnaidedAccuracy;
|
|
}
|
|
|
|
public static float Resolution(Entity entity)
|
|
{
|
|
var system = entity.GetBehavior<TargetingSystem>();
|
|
return system != null && system.Item.Active.Value ? system.Resolution : 1f;
|
|
}
|
|
|
|
public static float Precision(Entity entity)
|
|
{
|
|
var system = entity.GetBehavior<TargetingSystem>();
|
|
return system != null && system.Item.Active.Value ? system.Precision : 0f;
|
|
}
|
|
|
|
public static float Tracking(Entity entity)
|
|
{
|
|
var system = entity.GetBehavior<TargetingSystem>();
|
|
return system != null && system.Item.Active.Value ? system.Tracking : 0f;
|
|
}
|
|
}
|