2021-01-25 05:53:36 +00:00
|
|
|
using System;
|
|
|
|
|
using System.Collections;
|
|
|
|
|
using System.Collections.Generic;
|
2026-08-10 14:57:50 +01:00
|
|
|
using MessagePack;
|
|
|
|
|
using Newtonsoft.Json;
|
2026-09-14 17:39:12 +02:00
|
|
|
using CultMath;
|
|
|
|
|
using static CultMath.math;
|
2026-08-10 14:57:50 +01:00
|
|
|
|
|
|
|
|
[Inspectable, MessagePackObject, JsonObject(MemberSerialization.OptIn), RuntimeInspectable]
|
|
|
|
|
public class InstantWeaponData : WeaponData
|
|
|
|
|
{
|
|
|
|
|
[Inspectable, JsonProperty("count"), Key(17)]
|
|
|
|
|
public PerformanceStat Count = new PerformanceStat();
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("burstTime"), Key(18)]
|
|
|
|
|
public PerformanceStat BurstTime = new PerformanceStat();
|
|
|
|
|
|
|
|
|
|
[Inspectable, JsonProperty("cooldown"), Key(19), RuntimeInspectable]
|
|
|
|
|
public PerformanceStat Cooldown = new PerformanceStat();
|
|
|
|
|
|
Move the ServerShared data model onto CultLib's CultCache
Cut 8b. Aetheria.Shared builds headless against GameCult.Caching and
GameCult.Caching.MessagePack at the pinned CultLib; the pin guard also
checks the Caching project exists.
Documents: [CultDocument("aetheria.<type>", "1")] with [MessagePackObject]
and integer [Key]s on the Q7 concrete types (the eight ItemData leaves,
Faction, FactionProductData, PersonalityAttribute, NameFile, OrbitData, the
four BodyData leaves, SavedZone, SavedGame, PlayerSettings, InputLayout).
SavedGame and PlayerSettings are [CultGlobal]; Name members are [CultName].
ItemData, CraftedItemData, EquippableItemData, BodyData and AgentTask lose
[MessagePackObject]; BodyData and AgentTask lose their [Union]s. InputLayout
moves into ServerShared so AetheriaStores can route it.
References: Guid and DatabaseLink<T> members become CultRecordRef<T>;
Guid-keyed maps become ref-keyed with [CultReference(..., many: true)];
ZonePack holds refs to orbit and body records. Runtime maps (Zone orbits,
bodies and belts, cargo ItemsOfType, agent targets) key by CultRecordKey.
Identity is the record key: ID comparisons become reference or key
comparisons. StatModifierData.RequireBehavior is a simple type name.
AetheriaStores.Open composes the catalog (read-only unless writable), run
and player stores, one home per type. CultRecordRefs carries IsSet, a
null-safe Get(ref), RefOf (key from TryGetHandle) and Upsert.
ItemManager.GetData is the one item resolution path. MathResolver is
declared by [assembly: CultCacheFormatterResolver].
Inspector attributes: text, range and asset path map onto CultLib's
CultInspector attributes (asset path only on string members); every
replaced Aetheria attribute keeps [Inspectable] for the runtime panel.
Builds: Aetheria.Shared. Does not build: AetherDb and the Unity project,
which follow.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:00:42 +02:00
|
|
|
[Inspectable, JsonProperty("ammoInterval"), Key(20)]
|
2026-08-10 14:57:50 +01:00
|
|
|
public bool SingleAmmoBurst;
|
|
|
|
|
|
|
|
|
|
public override Behavior CreateInstance(EquippedItem item)
|
|
|
|
|
{
|
|
|
|
|
return new InstantWeapon(this, item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override Behavior CreateInstance(ConsumableItemEffect item)
|
|
|
|
|
{
|
|
|
|
|
return new InstantWeapon(this, item);
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-01-25 05:53:36 +00:00
|
|
|
|
2021-03-21 01:55:25 +00:00
|
|
|
public class InstantWeapon : Weapon, IProgressBehavior, IEventBehavior
|
2021-01-25 05:53:36 +00:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
private InstantWeaponData _data;
|
2021-01-25 05:53:36 +00:00
|
|
|
|
|
|
|
|
protected int _burstRemaining;
|
|
|
|
|
private float _burstTimer;
|
|
|
|
|
private float _burstInterval;
|
|
|
|
|
protected float _cooldown; // Normalized
|
2021-02-01 08:13:14 +00:00
|
|
|
private int _ammo = 0;
|
2021-01-30 01:02:11 +00:00
|
|
|
protected bool _coolingDown;
|
2021-02-26 19:01:47 +00:00
|
|
|
|
2021-01-31 03:52:32 +00:00
|
|
|
public float BurstCount { get; protected set; }
|
|
|
|
|
public float BurstTime { get; protected set; }
|
|
|
|
|
public float Cooldown { get; protected set; }
|
2021-02-26 19:01:47 +00:00
|
|
|
public virtual bool CanFire
|
|
|
|
|
{
|
|
|
|
|
get => !_coolingDown;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override float DamagePerSecond => Damage / Cooldown;
|
|
|
|
|
public override float RangeDamagePerSecond(float range)
|
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
return Damage * _data.DamageCurve.Evaluate(saturate(unlerp(MinRange, Range, range))) / Cooldown;
|
2021-02-26 19:01:47 +00:00
|
|
|
}
|
2021-01-25 05:53:36 +00:00
|
|
|
|
|
|
|
|
public override int Ammo
|
|
|
|
|
{
|
|
|
|
|
get => _ammo;
|
|
|
|
|
}
|
|
|
|
|
public virtual float Progress => saturate(_cooldown);
|
|
|
|
|
|
|
|
|
|
public event Action OnReloadBegin;
|
|
|
|
|
public event Action OnReloadComplete;
|
|
|
|
|
public event Action OnCooldownComplete;
|
|
|
|
|
public event Action OnFire;
|
|
|
|
|
|
2021-03-21 01:55:25 +00:00
|
|
|
public virtual void ResetEvents()
|
2021-01-31 03:52:32 +00:00
|
|
|
{
|
|
|
|
|
OnReloadBegin = null;
|
|
|
|
|
OnReloadComplete = null;
|
|
|
|
|
OnCooldownComplete = null;
|
|
|
|
|
OnFire = null;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
public InstantWeapon(InstantWeaponData data, EquippedItem item) : base(data, item)
|
2026-06-15 20:12:34 +01:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_data = data;
|
|
|
|
|
_ammo = data.MagazineSize;
|
2026-06-15 20:12:34 +01:00
|
|
|
}
|
|
|
|
|
|
2026-08-10 14:57:50 +01:00
|
|
|
public InstantWeapon(InstantWeaponData data, ConsumableItemEffect item) : base(data, item)
|
2021-01-25 05:53:36 +00:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_data = data;
|
|
|
|
|
_ammo = data.MagazineSize;
|
2021-04-18 05:13:20 +01:00
|
|
|
}
|
|
|
|
|
|
2021-01-25 05:53:36 +00:00
|
|
|
protected void Trigger()
|
|
|
|
|
{
|
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
|
|
|
// Safed: shooter has a target and hasn't declared hostility toward it.
|
|
|
|
|
if (!StanceAllowsFire) return;
|
|
|
|
|
|
2021-03-22 06:10:00 +00:00
|
|
|
// If 1 ammo is consumed per burst, perform ammo and energy consumption here
|
2021-02-01 08:13:14 +00:00
|
|
|
// UseAmmo returns false when triggering reload; cancel firing if that is the case
|
2026-08-10 14:57:50 +01:00
|
|
|
if(_data.SingleAmmoBurst && (!Entity.TryConsumeEnergy(Energy) || !UseAmmo())) return;
|
|
|
|
|
|
2021-01-31 03:52:32 +00:00
|
|
|
_burstRemaining = (int) BurstCount;
|
|
|
|
|
_burstInterval = BurstTime / _burstRemaining;
|
2021-01-25 05:53:36 +00:00
|
|
|
_burstTimer = 0;
|
|
|
|
|
_cooldown = 1;
|
|
|
|
|
_coolingDown = true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-31 03:52:32 +00:00
|
|
|
protected override void UpdateStats()
|
|
|
|
|
{
|
|
|
|
|
base.UpdateStats();
|
2026-08-10 14:57:50 +01:00
|
|
|
BurstCount = Evaluate(_data.Count);
|
|
|
|
|
BurstTime = Evaluate(_data.BurstTime);
|
|
|
|
|
Cooldown = Evaluate(_data.Cooldown);
|
2021-02-01 08:13:14 +00:00
|
|
|
|
|
|
|
|
Damage /= (int) BurstCount;
|
|
|
|
|
Heat /= (int) BurstCount;
|
|
|
|
|
Energy /= (int) BurstCount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool UseAmmo()
|
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
if (_data.MagazineSize <= 1) return true;
|
|
|
|
|
|
2021-03-22 06:10:00 +00:00
|
|
|
if (_ammo > 0)
|
2021-02-01 08:13:14 +00:00
|
|
|
{
|
2021-03-22 06:10:00 +00:00
|
|
|
_ammo--;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
2021-03-22 06:10:00 +00:00
|
|
|
var hasAmmo = true;
|
Move the ServerShared data model onto CultLib's CultCache
Cut 8b. Aetheria.Shared builds headless against GameCult.Caching and
GameCult.Caching.MessagePack at the pinned CultLib; the pin guard also
checks the Caching project exists.
Documents: [CultDocument("aetheria.<type>", "1")] with [MessagePackObject]
and integer [Key]s on the Q7 concrete types (the eight ItemData leaves,
Faction, FactionProductData, PersonalityAttribute, NameFile, OrbitData, the
four BodyData leaves, SavedZone, SavedGame, PlayerSettings, InputLayout).
SavedGame and PlayerSettings are [CultGlobal]; Name members are [CultName].
ItemData, CraftedItemData, EquippableItemData, BodyData and AgentTask lose
[MessagePackObject]; BodyData and AgentTask lose their [Union]s. InputLayout
moves into ServerShared so AetheriaStores can route it.
References: Guid and DatabaseLink<T> members become CultRecordRef<T>;
Guid-keyed maps become ref-keyed with [CultReference(..., many: true)];
ZonePack holds refs to orbit and body records. Runtime maps (Zone orbits,
bodies and belts, cargo ItemsOfType, agent targets) key by CultRecordKey.
Identity is the record key: ID comparisons become reference or key
comparisons. StatModifierData.RequireBehavior is a simple type name.
AetheriaStores.Open composes the catalog (read-only unless writable), run
and player stores, one home per type. CultRecordRefs carries IsSet, a
null-safe Get(ref), RefOf (key from TryGetHandle) and Upsert.
ItemManager.GetData is the one item resolution path. MathResolver is
declared by [assembly: CultCacheFormatterResolver].
Inspector attributes: text, range and asset path map onto CultLib's
CultInspector attributes (asset path only on string members); every
replaced Aetheria attribute keeps [Inspectable] for the runtime panel.
Builds: Aetheria.Shared. Does not build: AetherDb and the Unity project,
which follow.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:00:42 +02:00
|
|
|
if (_data.AmmoType.IsSet())
|
2021-03-22 06:10:00 +00:00
|
|
|
{
|
Move the ServerShared data model onto CultLib's CultCache
Cut 8b. Aetheria.Shared builds headless against GameCult.Caching and
GameCult.Caching.MessagePack at the pinned CultLib; the pin guard also
checks the Caching project exists.
Documents: [CultDocument("aetheria.<type>", "1")] with [MessagePackObject]
and integer [Key]s on the Q7 concrete types (the eight ItemData leaves,
Faction, FactionProductData, PersonalityAttribute, NameFile, OrbitData, the
four BodyData leaves, SavedZone, SavedGame, PlayerSettings, InputLayout).
SavedGame and PlayerSettings are [CultGlobal]; Name members are [CultName].
ItemData, CraftedItemData, EquippableItemData, BodyData and AgentTask lose
[MessagePackObject]; BodyData and AgentTask lose their [Union]s. InputLayout
moves into ServerShared so AetheriaStores can route it.
References: Guid and DatabaseLink<T> members become CultRecordRef<T>;
Guid-keyed maps become ref-keyed with [CultReference(..., many: true)];
ZonePack holds refs to orbit and body records. Runtime maps (Zone orbits,
bodies and belts, cargo ItemsOfType, agent targets) key by CultRecordKey.
Identity is the record key: ID comparisons become reference or key
comparisons. StatModifierData.RequireBehavior is a simple type name.
AetheriaStores.Open composes the catalog (read-only unless writable), run
and player stores, one home per type. CultRecordRefs carries IsSet, a
null-safe Get(ref), RefOf (key from TryGetHandle) and Upsert.
ItemManager.GetData is the one item resolution path. MathResolver is
declared by [assembly: CultCacheFormatterResolver].
Inspector attributes: text, range and asset path map onto CultLib's
CultInspector attributes (asset path only on string members); every
replaced Aetheria attribute keeps [Inspectable] for the runtime panel.
Builds: Aetheria.Shared. Does not build: AetherDb and the Unity project,
which follow.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:00:42 +02:00
|
|
|
var cargo = Entity.FindItemInCargo(_data.AmmoType.Key);
|
2021-03-22 06:10:00 +00:00
|
|
|
if (cargo != null)
|
2021-03-22 05:22:31 +00:00
|
|
|
{
|
Move the ServerShared data model onto CultLib's CultCache
Cut 8b. Aetheria.Shared builds headless against GameCult.Caching and
GameCult.Caching.MessagePack at the pinned CultLib; the pin guard also
checks the Caching project exists.
Documents: [CultDocument("aetheria.<type>", "1")] with [MessagePackObject]
and integer [Key]s on the Q7 concrete types (the eight ItemData leaves,
Faction, FactionProductData, PersonalityAttribute, NameFile, OrbitData, the
four BodyData leaves, SavedZone, SavedGame, PlayerSettings, InputLayout).
SavedGame and PlayerSettings are [CultGlobal]; Name members are [CultName].
ItemData, CraftedItemData, EquippableItemData, BodyData and AgentTask lose
[MessagePackObject]; BodyData and AgentTask lose their [Union]s. InputLayout
moves into ServerShared so AetheriaStores can route it.
References: Guid and DatabaseLink<T> members become CultRecordRef<T>;
Guid-keyed maps become ref-keyed with [CultReference(..., many: true)];
ZonePack holds refs to orbit and body records. Runtime maps (Zone orbits,
bodies and belts, cargo ItemsOfType, agent targets) key by CultRecordKey.
Identity is the record key: ID comparisons become reference or key
comparisons. StatModifierData.RequireBehavior is a simple type name.
AetheriaStores.Open composes the catalog (read-only unless writable), run
and player stores, one home per type. CultRecordRefs carries IsSet, a
null-safe Get(ref), RefOf (key from TryGetHandle) and Upsert.
ItemManager.GetData is the one item resolution path. MathResolver is
declared by [assembly: CultCacheFormatterResolver].
Inspector attributes: text, range and asset path map onto CultLib's
CultInspector attributes (asset path only on string members); every
replaced Aetheria attribute keeps [Inspectable] for the runtime panel.
Builds: Aetheria.Shared. Does not build: AetherDb and the Unity project,
which follow.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 19:00:42 +02:00
|
|
|
var item = cargo.ItemsOfType[_data.AmmoType.Key][0];
|
2021-03-22 06:10:00 +00:00
|
|
|
if (item is SimpleCommodity simpleCommodity)
|
|
|
|
|
cargo.Remove(simpleCommodity, 1);
|
2021-02-01 08:13:14 +00:00
|
|
|
}
|
2021-03-22 06:10:00 +00:00
|
|
|
else hasAmmo = false;
|
|
|
|
|
}
|
|
|
|
|
if(hasAmmo)
|
|
|
|
|
{
|
|
|
|
|
OnReloadBegin?.Invoke();
|
|
|
|
|
_cooldown = 1;
|
|
|
|
|
_coolingDown = true;
|
|
|
|
|
_firing = false;
|
2021-02-01 08:13:14 +00:00
|
|
|
}
|
2021-03-22 06:10:00 +00:00
|
|
|
_burstRemaining = 0;
|
|
|
|
|
return false;
|
2021-02-01 08:13:14 +00:00
|
|
|
|
2021-01-31 03:52:32 +00:00
|
|
|
}
|
|
|
|
|
|
2021-04-14 03:29:14 +01:00
|
|
|
public override bool Execute(float dt)
|
2021-01-25 05:53:36 +00:00
|
|
|
{
|
2021-04-14 03:29:14 +01:00
|
|
|
base.Execute(dt);
|
2021-01-25 05:53:36 +00:00
|
|
|
if (_coolingDown)
|
|
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_cooldown -= dt / (_data.MagazineSize > 0 && _ammo == 0 ? _data.ReloadTime : Cooldown);
|
2021-01-25 05:53:36 +00:00
|
|
|
if (_cooldown < 0)
|
|
|
|
|
{
|
|
|
|
|
_coolingDown = false;
|
2026-08-10 14:57:50 +01:00
|
|
|
if (_data.MagazineSize > 0 && _ammo == 0)
|
2021-01-25 05:53:36 +00:00
|
|
|
{
|
2026-08-10 14:57:50 +01:00
|
|
|
_ammo = _data.MagazineSize;
|
2021-01-25 05:53:36 +00:00
|
|
|
OnReloadComplete?.Invoke();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
OnCooldownComplete?.Invoke();
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-04-30 06:39:57 +01:00
|
|
|
|
|
|
|
|
var firedThisFrame = false;
|
2021-04-14 03:29:14 +01:00
|
|
|
_burstTimer += dt;
|
2021-01-25 05:53:36 +00:00
|
|
|
while (_burstRemaining > 0 && _burstTimer > 0)
|
|
|
|
|
{
|
2021-03-22 06:10:00 +00:00
|
|
|
// If multiple ammo is consumed per burst, perform ammo and energy consumption here
|
|
|
|
|
// UseAmmo returns false when triggering reload; cancel firing if that is the case
|
2026-08-10 14:57:50 +01:00
|
|
|
if (!_data.SingleAmmoBurst && (!Entity.TryConsumeEnergy(Energy) || !UseAmmo()))
|
2021-03-02 23:02:19 +00:00
|
|
|
{
|
|
|
|
|
_burstRemaining = 0;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
|
2021-01-25 05:53:36 +00:00
|
|
|
_burstRemaining--;
|
|
|
|
|
_burstTimer -= _burstInterval;
|
|
|
|
|
OnFire?.Invoke();
|
2021-04-30 06:39:57 +01:00
|
|
|
if(!firedThisFrame)
|
|
|
|
|
{
|
|
|
|
|
Item.FireAudioEvent(WeaponAudioEvent.Fire);
|
|
|
|
|
firedThisFrame = true;
|
|
|
|
|
}
|
2021-04-18 05:13:20 +01:00
|
|
|
CauseWearDamage(1);
|
|
|
|
|
AddHeat(Heat);
|
|
|
|
|
Entity.VisibilitySources[this] = Visibility;
|
2021-01-25 05:53:36 +00:00
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Activate()
|
|
|
|
|
{
|
2021-04-07 01:27:35 +01:00
|
|
|
if(CanFire)
|
|
|
|
|
Trigger();
|
|
|
|
|
base.Activate();
|
2021-01-25 05:53:36 +00:00
|
|
|
}
|
2026-08-10 14:57:50 +01:00
|
|
|
}
|