Entity.HardpointTransforms was written only by Unity (EntityInstance.cs), so the simulation's notion of where a gun points was a readback from a renderer that never runs headless -- Combat.cs threw KeyNotFoundException on the first combat tick outside Unity. Deletes the field, its writer, and the readback branch in Behavior.Direction, leaving the item-rotation path (the same convention reaction thrusters use, R6) as the only source of mount direction. Adds FireControl (ServerShared), a static class owning whether a weapon bears on a point: MountDirection (planar, from ItemRotation), ArcFor (the per-hardpoint HardpointData.FiringArc override, falling back to the new GameplaySettings.FiringArc default of 120), and InArc (the planar dot-cone test, R7 -- target height never enters it, and an arc of 360+ passes unconditionally so a turret hardpoint needs no new type, just a number). Combat.cs and TurretController.cs now gate AI fire through InArc instead of the old hardcoded 0.99 dot-product test against a live Unity transform. Settings.cs drops AgentFiringMinDot, which was declared and read by nothing. docs/fire-control-cut.md, Cut 1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
199 lines
No EOL
6.3 KiB
C#
199 lines
No EOL
6.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;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using MessagePack;
|
|
using Newtonsoft.Json;
|
|
using CultMath;
|
|
using static CultMath.math;
|
|
|
|
public abstract class Behavior
|
|
{
|
|
public BehaviorData Data { get; }
|
|
public EquippedItem Item { get; }
|
|
private ConsumableItemEffect Consumable { get; }
|
|
protected ItemManager ItemManager { get; }
|
|
|
|
protected Entity Entity => Item?.Entity ?? Consumable.Entity;
|
|
public float Temperature => Item?.Temperature ?? Consumable.Entity.MaxTemp;
|
|
|
|
public float3 Direction
|
|
{
|
|
get
|
|
{
|
|
if(Item != null)
|
|
{
|
|
var itemDirection = Entity.Direction.Rotate(Item.EquippableItem.Rotation);
|
|
return float3(itemDirection.x, 0, itemDirection.y);
|
|
}
|
|
|
|
return float3(Entity.Direction.x, 0, Entity.Direction.y);
|
|
}
|
|
}
|
|
|
|
protected Behavior(BehaviorData data, EquippedItem item)
|
|
{
|
|
Data = data;
|
|
Item = item;
|
|
ItemManager = Item.ItemManager;
|
|
}
|
|
|
|
protected Behavior(BehaviorData data, ConsumableItemEffect consumable)
|
|
{
|
|
Data = data;
|
|
Consumable = consumable;
|
|
ItemManager = consumable.Entity.ItemManager;
|
|
}
|
|
|
|
public float Evaluate(PerformanceStat stat) => Item?.Evaluate(stat) ?? Consumable.Evaluate(stat);
|
|
|
|
// Nominal-request ruling (docs/stats-and-power-cut.md, operator ruling 2026-09-19): the read a PowerRequest
|
|
// implementation uses for a stat named in StatValidation.PowerRequestFields, in place of Evaluate above. A
|
|
// consumable-hosted instance (Item == null) never reaches PowerBus.Step at all (see e.g. EnergyDraw.Execute's
|
|
// own comment), and ConsumableItemEffect.PowerSupplyFactor is already the identity (1) unconditionally, so
|
|
// Consumable.Evaluate is already nominal with respect to power and needs no separate context.
|
|
public float EvaluateNominalPower(PerformanceStat stat) => Item?.EvaluateNominalPower(stat) ?? Consumable.Evaluate(stat);
|
|
protected void AddHeat(float heat) => Item?.AddHeat(heat); // TODO: Heat for Consumables
|
|
|
|
protected void CauseDamage(float damage)
|
|
{
|
|
if (Item != null)
|
|
{
|
|
Item.EquippableItem.Durability -= damage;
|
|
Entity.ItemDamage.OnNext((Item, damage));
|
|
}
|
|
else
|
|
{
|
|
Consumable.Entity.Hull.Durability -= damage;
|
|
Consumable.Entity.HullDamage.OnNext(damage);
|
|
}
|
|
}
|
|
|
|
protected void CauseWearDamage(float multiplier)
|
|
{
|
|
if (Item != null)
|
|
{
|
|
CauseDamage(Item.Wear * multiplier);
|
|
}
|
|
}
|
|
|
|
public virtual bool Execute(float dt)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public interface IActivatedBehavior
|
|
{
|
|
void Activate();
|
|
void Deactivate();
|
|
}
|
|
|
|
public interface IAnalogBehavior
|
|
{
|
|
float Axis { get; set; }
|
|
}
|
|
|
|
public interface IEventBehavior
|
|
{
|
|
void ResetEvents();
|
|
}
|
|
|
|
public interface IInitializableBehavior
|
|
{
|
|
void Initialize();
|
|
}
|
|
|
|
public interface IInteractiveBehavior
|
|
{
|
|
bool Exposed { get; }
|
|
}
|
|
|
|
public interface IAlwaysUpdatedBehavior
|
|
{
|
|
void Update(float delta);
|
|
}
|
|
|
|
public interface IProgressBehavior
|
|
{
|
|
float Progress { get; }
|
|
}
|
|
|
|
public interface IPopulationAssignment
|
|
{
|
|
int AssignedPopulation { get; set; }
|
|
}
|
|
|
|
// Cut 3 (docs/stats-and-power-cut.md §1.2): a behaviour that wants to draw from its entity's PowerBus declares a
|
|
// request instead of spending energy itself. PowerBus.Step calls this on every consumer once per tick, before
|
|
// any behaviour's Execute runs, so PowerRequest must be computable from state that is already current at that
|
|
// point -- a resolved stat and, where the map calls for one, a behaviour-supplied scalar such as Thruster's
|
|
// throttle axis (set externally before the tick) -- never from work Execute itself would otherwise do. The
|
|
// behaviour reads back how much of its own request was granted from Item.PowerSupply during its own Execute.
|
|
//
|
|
// Cut 5 (docs/stats-and-power-cut.md §1.3): every kind of consumer also names the priority tier (PowerTiers.cs)
|
|
// it defaults to. EquippedItem reads this exactly once, to seed EquippableItem.PowerTier the first time an item
|
|
// without a player-chosen tier gets equipped -- after that the stored field is what PowerBus reads, never this.
|
|
public interface IPowerConsumer
|
|
{
|
|
float PowerRequest(float dt);
|
|
int DefaultPowerTier { get; }
|
|
}
|
|
|
|
[Inspectable,
|
|
Union(0, typeof(GuidedWeaponData)),
|
|
Union(1, typeof(LauncherData)),
|
|
Union(2, typeof(ReactorData)),
|
|
Union(3, typeof(RadiatorData)),
|
|
Union(4, typeof(StatModifierData)),
|
|
Union(5, typeof(SensorData)),
|
|
Union(6, typeof(ReflectorData)),
|
|
Union(7, typeof(ShieldData)),
|
|
Union(8, typeof(ThrusterData)),
|
|
Union(9, typeof(WearData)),
|
|
Union(10, typeof(VelocityConversionData)),
|
|
Union(11, typeof(VelocityLimitData)),
|
|
Union(12, typeof(AetherDriveData)),
|
|
// Union(14, typeof(TowingControllerData)),
|
|
Union(15, typeof(CooldownData)),
|
|
Union(16, typeof(HeatData)),
|
|
// Union(17, typeof(HitscanData)),
|
|
Union(18, typeof(ItemUsageData)),
|
|
//Union(19, typeof(RadianceData)),
|
|
Union(20, typeof(SwitchData)),
|
|
Union(21, typeof(TriggerData)),
|
|
Union(22, typeof(VisibilityData)),
|
|
Union(23, typeof(ThermotoggleData)),
|
|
Union(24, typeof(EnergyDrawData)),
|
|
// Union(25, typeof(MiningControllerData)),
|
|
Union(26, typeof(MiningToolData)),
|
|
// Union(27, typeof(SurveyControllerData)),
|
|
Union(28, typeof(ResourceScannerData)),
|
|
// Union(29, typeof(WanderControllerData)),
|
|
// Union(30, typeof(HaulingControllerData)),
|
|
Union(31, typeof(CapacitorData)),
|
|
Union(32, typeof(CockpitData)),
|
|
Union(33, typeof(HeatStorageData)),
|
|
Union(34, typeof(TurretControllerData)),
|
|
Union(35, typeof(InstantWeaponData)),
|
|
Union(36, typeof(ConstantWeaponData)),
|
|
Union(37, typeof(ChargedWeaponData)),
|
|
Union(38, typeof(AutoWeaponData)),
|
|
JsonObject(MemberSerialization.OptIn)]
|
|
public abstract class BehaviorData
|
|
{
|
|
[Inspectable, JsonProperty("group"), Key(0)]
|
|
public int Group;
|
|
|
|
public abstract Behavior CreateInstance(EquippedItem item);
|
|
public abstract Behavior CreateInstance(ConsumableItemEffect consumable);
|
|
|
|
public override string ToString()
|
|
{
|
|
return base.ToString().FormatTypeName();
|
|
}
|
|
} |