Damage System Rewrite

New damage system, much easier to understand
Splash damage support
Hardpoint armor consolidated with hull armor
Minimap shows view cone
New industry standard palette for temperature display
Temperature range display contains palette
Restoring loadouts now ignores temperature and armor
Items can ignore thermal mass when adding heat
Burst weapons can consume single ammo (mainly shotguns)
New missile type that follows crosshairs
New airburst projectile type that deals splash damage
Auto Shotgun and Charged Shotgun
This commit is contained in:
Reinier Vens 2021-02-01 08:13:14 +00:00
commit 3a9ee2ccf7
55 changed files with 14253 additions and 2206 deletions

View file

@ -88,7 +88,7 @@ public abstract class PersistentBehaviorData
}
[InspectableField,
// Union(0, typeof(ProjectileWeaponData)),
Union(0, typeof(GuidedWeaponData)),
Union(1, typeof(LauncherData)),
Union(2, typeof(ReactorData)),
Union(3, typeof(RadiatorData)),

View file

@ -9,38 +9,41 @@ using static Unity.Mathematics.math;
[InspectableField, MessagePackObject, JsonObject(MemberSerialization.OptIn), RuntimeInspectable]
public class ChargedWeaponData : InstantWeaponData
{
[InspectableField, JsonProperty("chargeTime"), Key(18), RuntimeInspectable]
[InspectableField, JsonProperty("chargeTime"), Key(19), RuntimeInspectable]
public PerformanceStat ChargeTime = new PerformanceStat();
[InspectableField, JsonProperty("chargeEnergy"), Key(19), RuntimeInspectable]
[InspectableField, JsonProperty("chargeEnergy"), Key(20), RuntimeInspectable]
public PerformanceStat ChargeEnergy = new PerformanceStat();
[InspectableField, JsonProperty("chargeHeat"), Key(20), RuntimeInspectable]
[InspectableField, JsonProperty("chargeHeat"), Key(21), RuntimeInspectable]
public PerformanceStat ChargeHeat = new PerformanceStat();
[InspectableField, JsonProperty("canFireEarly"), Key(21)]
[InspectableField, JsonProperty("canFireEarly"), Key(22)]
public bool CanFireEarly;
[InspectableField, JsonProperty("failureCharge"), Key(22)]
[InspectableField, JsonProperty("failureCharge"), Key(23)]
public float FailureCharge;
[InspectableField, JsonProperty("failureDamage"), Key(23)]
[InspectableField, JsonProperty("failureDamage"), Key(24)]
public float FailureDamage = 1;
[InspectableField, JsonProperty("chargeDamage"), Key(24)]
public float ChargeDamage = 1;
[InspectableField, JsonProperty("chargeDamage"), Key(25)]
public float ChargeFiringDamageMultiplier = 1;
[InspectableField, JsonProperty("chargeSpread"), Key(25)]
public float ChargeSpread = 1;
[InspectableField, JsonProperty("chargeSpread"), Key(26)]
public float ChargeFiringSpreadMultiplier = 1;
[InspectableField, JsonProperty("chargeBurstCount"), Key(26)]
public float ChargeBurstCount = 1;
[InspectableField, JsonProperty("chargeBurstCount"), Key(27)]
public float ChargeFiringBurstCountMultiplier = 1;
[InspectableField, JsonProperty("chargeVisibility"), Key(27)]
public float ChargeVisibility = 1;
[InspectableField, JsonProperty("chargeVisibility"), Key(28)]
public float ChargeFiringVisibilityMultiplier = 1;
[InspectableField, JsonProperty("chargeVelocity"), Key(28)]
public float ChargeVelocity = 1;
[InspectableField, JsonProperty("chargeVelocity"), Key(29)]
public float ChargeFiringVelocityMultiplier = 1;
[InspectableField, JsonProperty("chargeHeatMul"), Key(30)]
public float ChargeFiringHeatMultiplier = 1;
public override IBehavior CreateInstance(ItemManager context, Entity entity, EquippedItem item)
{
@ -91,11 +94,12 @@ public class ChargedWeapon : InstantWeapon
ChargeTime = Context.Evaluate(_data.ChargeTime, Item.EquippableItem, Entity);
ChargeEnergy = Context.Evaluate(_data.ChargeEnergy, Item.EquippableItem, Entity);
ChargeHeat = Context.Evaluate(_data.ChargeHeat, Item.EquippableItem, Entity);
Damage *= lerp(1, _data.ChargeDamage, saturate(_charge));
Spread *= lerp(1, _data.ChargeSpread, saturate(_charge));
BurstCount *= lerp(1, _data.ChargeBurstCount, saturate(_charge));
Visibility *= lerp(1, _data.ChargeVisibility, saturate(_charge));
Velocity *= lerp(1, _data.ChargeVelocity, saturate(_charge));
Damage *= lerp(1, _data.ChargeFiringDamageMultiplier, saturate(_charge));
Heat *= lerp(1, _data.ChargeFiringHeatMultiplier, saturate(_charge));
Spread *= lerp(1, _data.ChargeFiringSpreadMultiplier, saturate(_charge));
BurstCount *= lerp(1, _data.ChargeFiringBurstCountMultiplier, saturate(_charge));
Visibility *= lerp(1, _data.ChargeFiringVisibilityMultiplier, saturate(_charge));
Velocity *= lerp(1, _data.ChargeFiringVelocityMultiplier, saturate(_charge));
}
public override bool Execute(float delta)

View file

@ -17,6 +17,9 @@ public class InstantWeaponData : WeaponData
[InspectableField, JsonProperty("cooldown"), Key(17), RuntimeInspectable]
public PerformanceStat Cooldown = new PerformanceStat();
[InspectablePrefab, JsonProperty("ammoInterval"), Key(18)]
public bool SingleAmmoBurst;
public override IBehavior CreateInstance(ItemManager context, Entity entity, EquippedItem item)
{
@ -32,7 +35,7 @@ public class InstantWeapon : Weapon, IProgressBehavior
private float _burstTimer;
private float _burstInterval;
protected float _cooldown; // Normalized
private int _ammo = 1;
private int _ammo = 0;
protected bool _coolingDown;
public float BurstCount { get; protected set; }
@ -65,6 +68,10 @@ public class InstantWeapon : Weapon, IProgressBehavior
protected void Trigger()
{
// If 1 ammo is consumed per burst, perform ammo consumption here
// UseAmmo returns false when triggering reload; cancel firing if that is the case
if(_data.SingleAmmoBurst && !UseAmmo()) return;
_burstRemaining = (int) BurstCount;
_burstInterval = BurstTime / _burstRemaining;
_burstTimer = 0;
@ -78,6 +85,40 @@ public class InstantWeapon : Weapon, IProgressBehavior
BurstCount = Context.Evaluate(_data.Count, Item.EquippableItem, Entity);
BurstTime = Context.Evaluate(_data.BurstTime, Item.EquippableItem, Entity);
Cooldown = Context.Evaluate(_data.Cooldown, Item.EquippableItem, Entity);
Damage /= (int) BurstCount;
Heat /= (int) BurstCount;
Energy /= (int) BurstCount;
}
private bool UseAmmo()
{
if (_data.AmmoType != Guid.Empty)
{
if (_data.MagazineSize > 1 && _ammo > 0) _ammo--;
else
{
var cargo = Entity.FindItemInCargo(_data.AmmoType);
if (cargo != null)
{
var item = cargo.ItemsOfType[_data.AmmoType][0];
if (item is SimpleCommodity simpleCommodity)
cargo.Remove(simpleCommodity, 1);
if(_data.MagazineSize > 1)
{
OnReloadBegin?.Invoke();
_cooldown = 1;
_coolingDown = true;
_firing = false;
}
}
_burstRemaining = 0;
return false;
}
}
return true;
}
public override bool Execute(float delta)
@ -102,30 +143,9 @@ public class InstantWeapon : Weapon, IProgressBehavior
_burstTimer += delta;
while (_burstRemaining > 0 && _burstTimer > 0)
{
if (_data.AmmoType != Guid.Empty)
{
if (_data.MagazineSize > 1 && _ammo > 0) _ammo--;
else
{
var cargo = Entity.FindItemInCargo(_data.AmmoType);
if (cargo != null)
{
var item = cargo.ItemsOfType[_data.AmmoType][0];
if (item is SimpleCommodity simpleCommodity)
cargo.Remove(simpleCommodity, 1);
if(_data.MagazineSize > 1)
{
OnReloadBegin?.Invoke();
_cooldown = 1;
_coolingDown = true;
_firing = false;
}
}
_burstRemaining = 0;
return false;
}
}
// If 1 ammo is consumed per shot in the burst, perform ammo consumption here
// UseAmmo returns false when triggering reload; cancel firing if that is the case
if (!_data.SingleAmmoBurst && !UseAmmo()) return false;
_burstRemaining--;
_burstTimer -= _burstInterval;

View file

@ -9,26 +9,53 @@ using Unity.Mathematics;
[InspectableField, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class LauncherData : LockWeaponData
{
[InspectableAnimationCurve, JsonProperty("guidance"), Key(23)]
[InspectableAnimationCurve, JsonProperty("guidance"), Key(24)]
public float4[] GuidanceCurve;
[InspectableAnimationCurve, JsonProperty("thrustCurve"), Key(24)]
[InspectableAnimationCurve, JsonProperty("thrustCurve"), Key(25)]
public float4[] ThrustCurve;
[InspectableAnimationCurve, JsonProperty("liftCurve"), Key(25)]
[InspectableAnimationCurve, JsonProperty("liftCurve"), Key(26)]
public float4[] LiftCurve;
[InspectableField, JsonProperty("thrust"), Key(26)]
[InspectableField, JsonProperty("thrust"), Key(27)]
public PerformanceStat Thrust = new PerformanceStat();
[InspectableField, JsonProperty("frequency"), Key(27)]
[InspectableField, JsonProperty("frequency"), Key(28)]
public float DodgeFrequency;
[InspectableField, JsonProperty("missileSpeed"), Key(28), RuntimeInspectable]
[InspectableField, JsonProperty("missileSpeed"), Key(29), RuntimeInspectable]
public PerformanceStat MissileVelocity = new PerformanceStat();
public override IBehavior CreateInstance(ItemManager context, Entity entity, EquippedItem item)
{
return new LockWeapon(context, this, entity, item);
}
}
[InspectableField, MessagePackObject, JsonObject(MemberSerialization.OptIn)]
public class GuidedWeaponData : InstantWeaponData
{
[InspectableAnimationCurve, JsonProperty("guidance"), Key(24)]
public float4[] GuidanceCurve;
[InspectableAnimationCurve, JsonProperty("thrustCurve"), Key(25)]
public float4[] ThrustCurve;
[InspectableAnimationCurve, JsonProperty("liftCurve"), Key(26)]
public float4[] LiftCurve;
[InspectableField, JsonProperty("thrust"), Key(27)]
public PerformanceStat Thrust = new PerformanceStat();
[InspectableField, JsonProperty("frequency"), Key(28)]
public float DodgeFrequency;
[InspectableField, JsonProperty("missileSpeed"), Key(29), RuntimeInspectable]
public PerformanceStat MissileVelocity = new PerformanceStat();
public override IBehavior CreateInstance(ItemManager context, Entity entity, EquippedItem item)
{
return new InstantWeapon(context, this, entity, item);
}
}

View file

@ -9,19 +9,19 @@ using static Unity.Mathematics.math;
[InspectableField, MessagePackObject, JsonObject(MemberSerialization.OptIn), RuntimeInspectable]
public class LockWeaponData : InstantWeaponData
{
[InspectableField, JsonProperty("speed"), Key(18), RuntimeInspectable]
[InspectableField, JsonProperty("speed"), Key(19), RuntimeInspectable]
public PerformanceStat LockSpeed = new PerformanceStat();
[InspectableField, JsonProperty("sensorImpact"), Key(19)]
[InspectableField, JsonProperty("sensorImpact"), Key(20)]
public PerformanceStat SensorImpact = new PerformanceStat();
[InspectableField, JsonProperty("threshold"), Key(20), RuntimeInspectable]
[InspectableField, JsonProperty("threshold"), Key(21), RuntimeInspectable]
public PerformanceStat LockAngle = new PerformanceStat();
[InspectableField, JsonProperty("directionImpact"), Key(21)]
[InspectableField, JsonProperty("directionImpact"), Key(22)]
public PerformanceStat DirectionImpact = new PerformanceStat();
[InspectableField, JsonProperty("decay"), Key(22)]
[InspectableField, JsonProperty("decay"), Key(23)]
public PerformanceStat Decay = new PerformanceStat();
public override IBehavior CreateInstance(ItemManager context, Entity entity, EquippedItem item)

View file

@ -43,7 +43,7 @@ public class Radiator : IBehavior
{
Emissivity = Context.Evaluate(_data.Emissivity, Item.EquippableItem, Entity);
var rad = pow(Item.Temperature, Context.GameplaySettings.HeatRadiationExponent) * Context.GameplaySettings.HeatRadiationMultiplier * Emissivity;
Item.AddHeat(-rad * delta * Context.GetThermalMass(Item.EquippableItem));
Item.AddHeat(-rad * delta);
Entity.VisibilitySources[this] = rad;
return true;
}

View file

@ -52,7 +52,6 @@ public abstract class Entity
public readonly Dictionary<object, float> VisibilitySources = new Dictionary<object, float>();
public readonly Dictionary<HardpointData, (float3 position, float3 direction)> HardpointTransforms =
new Dictionary<HardpointData, (float3 position, float3 direction)>();
public Dictionary<HardpointData, float> HardpointArmor;
public readonly (List<IActivatedBehavior> behaviors, List<EquippedItem> items)[] TriggerGroups;
@ -61,6 +60,7 @@ public abstract class Entity
public EquippedItem[,] GearOccupancy;
public HardpointData[,] Hardpoints;
public float[,] Armor;
public float[,] MaxArmor;
private EquippedItem[] _orderedEquipment;
protected bool _active;
@ -72,7 +72,6 @@ public abstract class Entity
public float Mass { get; private set; }
public float Visibility => VisibilitySources.Values.Sum();
public Subject<(HardpointData hardpoint, float damage)> HardpointDamage = new Subject<(HardpointData, float)>();
public Subject<(int2 pos, float damage)> ArmorDamage = new Subject<(int2, float)>();
public Subject<(EquippedItem item, float damage)> ItemDamage = new Subject<(EquippedItem, float)>();
public Subject<float> HullDamage = new Subject<float>();
@ -115,13 +114,11 @@ public abstract class Entity
ItemDestroyed = ItemDamage.Where(x => x.item.EquippableItem.Durability < .01f).Select(x=>x.item);
HullArmorDepleted = ArmorDamage.Where(x => Armor[x.pos.x, x.pos.y] < .01f).Select(x => x.pos);
HardpointArmorDepleted = HardpointDamage.Where(x => HardpointArmor[x.hardpoint] < .01f).Select(x => x.hardpoint);
}
private void MapEntity()
{
var hullData = ItemManager.GetData(Hull) as HullData;
HardpointArmor = hullData.Hardpoints.ToDictionary(hp => hp, hp => hp.Armor);
Equipment.Add(new EquippedItem(ItemManager, Hull, int2.zero, this));
Mass = hullData.Mass;
Temperature = new float[hullData.Shape.Width, hullData.Shape.Height];
@ -129,15 +126,7 @@ public abstract class Entity
HullConductivity = new bool2[hullData.Shape.Width,hullData.Shape.Height];
ThermalMass = new float[hullData.Shape.Width, hullData.Shape.Height];
Armor = new float[hullData.Shape.Width, hullData.Shape.Height];
var cellCount = hullData.Shape.Coordinates.Length;
foreach (var v in hullData.Shape.Coordinates)
{
if (!hullData.InteriorCells[v])
Armor[v.x, v.y] = hullData.Armor;
Temperature[v.x, v.y] = 280;
ThermalMass[v.x, v.y] = hullData.Mass * hullData.SpecificHeat / cellCount;
}
GearOccupancy = new EquippedItem[hullData.Shape.Width, hullData.Shape.Height];
MaxArmor = new float[hullData.Shape.Width, hullData.Shape.Height];
Hardpoints = new HardpointData[hullData.Shape.Width, hullData.Shape.Height];
foreach (var hardpoint in hullData.Hardpoints)
{
@ -147,11 +136,28 @@ public abstract class Entity
Hardpoints[hullCoord.x, hullCoord.y] = hardpoint;
}
}
var cellCount = hullData.Shape.Coordinates.Length;
foreach (var v in hullData.Shape.Coordinates)
{
Armor[v.x, v.y] = hullData.Armor;
MaxArmor[v.x, v.y] = hullData.Armor;
if (Hardpoints[v.x, v.y] != null)
{
Armor[v.x, v.y] += Hardpoints[v.x, v.y].Armor;
MaxArmor[v.x, v.y] += Hardpoints[v.x, v.y].Armor;
}
Temperature[v.x, v.y] = 280;
ThermalMass[v.x, v.y] = hullData.Mass * hullData.SpecificHeat / cellCount;
}
GearOccupancy = new EquippedItem[hullData.Shape.Width, hullData.Shape.Height];
}
public void AddHeat(int2 position, float heat)
public void AddHeat(int2 position, float heat, bool ignoreThermalMass = false)
{
Temperature[position.x, position.y] += heat / ThermalMass[position.x, position.y];
if (ignoreThermalMass)
Temperature[position.x, position.y] += heat;
else
Temperature[position.x, position.y] += heat / ThermalMass[position.x, position.y];
}
public int CountItemsInCargo(Guid itemDataID)
@ -750,10 +756,10 @@ public class EquippedItem
}
}
public void AddHeat(float heat)
public void AddHeat(float heat, bool ignoreThermalMass = false)
{
foreach(var hullCoord in InsetShape.Coordinates)
_entity.AddHeat(hullCoord, heat / InsetShape.Coordinates.Length);
_entity.AddHeat(hullCoord, heat / InsetShape.Coordinates.Length, ignoreThermalMass);
}
public void Update(float delta)

View file

@ -36,9 +36,6 @@ public class EntityPack
[Key(8)]
public bool2[,] Conductivity;
[Key(9)]
public Dictionary<int, float> HardpointArmor;
[Key(10)]
public int[] DockingBayAssignments;
@ -79,7 +76,6 @@ public class EntityPack
pack.Temperature = entity.Temperature;
pack.Conductivity = entity.HullConductivity;
var hullData = entity.ItemManager.GetData(entity.Hull) as HullData;
pack.HardpointArmor = entity.HardpointArmor.ToDictionary(x => hullData.Hardpoints.IndexOf(x.Key), x => x.Value);
pack.Children = entity.Children.Select(Pack).ToArray();
return pack;
}
@ -135,11 +131,16 @@ public class EntityPack
.Zip(pack.PersistedBehaviors[item.Position], (behavior, data) => new{behavior, data})))
persistentBehaviorData.behavior.Restore(persistentBehaviorData.data);
entity.Temperature = pack.Temperature;
entity.Armor = pack.Armor;
var hullData = entity.ItemManager.GetData(entity.Hull) as HullData;
entity.HullConductivity = pack.Conductivity;
entity.HardpointArmor = pack.HardpointArmor.ToDictionary(x => hullData.Hardpoints[x.Key], x => x.Value);
if(!instantiate)
entity.Temperature = pack.Temperature;
if(!instantiate)
entity.Armor = pack.Armor;
var hullData = itemManager.GetData(entity.Hull);
foreach(var v in hullData.Shape.Coordinates)
entity.HullConductivity[v.x,v.y] = pack.Conductivity[v.x,v.y];
}
public int Price(ItemManager itemManager)

View file

@ -60,21 +60,26 @@ public class Ship : Entity
foreach (var thruster in _leftThrusters) thruster.Axis += -MovementDirection.x;
foreach (var thruster in _forwardThrusters) thruster.Axis += MovementDirection.y;
foreach (var thruster in _reverseThrusters) thruster.Axis += -MovementDirection.y;
var deltaRot = dot(normalize(LookDirection.xz), normalize(Direction).Rotate(ItemRotation.Clockwise));
if (abs(deltaRot) < .0001) deltaRot = 0;
deltaRot = pow(abs(deltaRot), .5f) * sign(deltaRot);
var look = normalize(LookDirection.xz);
var deltaRot = dot(look, normalize(Direction).Rotate(ItemRotation.Clockwise));
if (abs(deltaRot) < .01f)
{
deltaRot = 0;
Direction = lerp(Direction, look, min(delta, 1));
}
deltaRot = pow(abs(deltaRot), .75f) * sign(deltaRot);
foreach (var thruster in _clockwiseThrusters) thruster.Axis += deltaRot;
foreach (var thruster in _counterClockwiseThrusters) thruster.Axis += -deltaRot;
}
var normal = Zone.GetNormal(Position.xz);
var f = new float2(normal.x, normal.z);
var fl = lengthsq(f);
if (fl > .001)
var force = new float2(normal.x, normal.z);
var forceMagnitude = lengthsq(force);
if (forceMagnitude > .001f)
{
var fa = 1 / (1 - fl) - 1;
Velocity += normalize(f) * Zone.Settings.GravityStrength * fa;
var fa = 1 / (1 - forceMagnitude) - 1;
Velocity += normalize(force) * Zone.Settings.GravityStrength * fa;
}
var shipRight = Direction.Rotate(ItemRotation.Clockwise);
var forward = cross(float3(shipRight.x, 0, shipRight.y), normal);