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

@ -63,6 +63,7 @@ public class ActionGameManager : MonoBehaviour
private (LockWeapon targetLock, PlaceUIElementWorldspace indicator, Rotate spin)[] _lockingIndicators;
public List<Ship> PlayerShips { get; } = new List<Ship>();
public EquippedDockingBay DockingBay { get; private set; }
public Entity DockedEntity { get; private set; }
public Ship CurrentShip { get; set; }
public DatabaseCache ItemData { get; private set; }
@ -348,8 +349,9 @@ public class ActionGameManager : MonoBehaviour
station.Active = true;
var reactorData = ItemData.GetAll<GearData>().First(x => x.HardpointType == HardpointType.Reactor && x.Shape.Height == 2);
var radiatorData = ItemData.GetAll<GearData>().First(x => x.Name == "Long Radiator");
var autocannonData = ItemData.GetAll<GearData>().First(x => x.Name == "Autocannon");
var ammoData = ItemData.GetAll<SimpleCommodityData>().First(x => x.Name == "AC2 Ammo");
var ammoData = ItemData.GetAll<SimpleCommodityData>().First(x => x.Name == "Autocannon Ammo");
var turretType = ItemData.GetAll<HullData>().First(x=>x.HullType==HullType.Turret);
var controlModuleData = ItemData.GetAll<GearData>().First(x => x.Name == "Murder Module");
@ -364,65 +366,31 @@ public class ActionGameManager : MonoBehaviour
var turretOrbit = Zone.CreateOrbit(turretParentOrbit, turretPos);
var turret = new OrbitalEntity(ItemManager, Zone, ItemManager.CreateInstance(turretType, 0, 1) as EquippableItem, turretOrbit.ID);
turret.TryEquip(ItemManager.CreateInstance(reactorData, .5f, 1) as EquippableItem);
turret.TryEquip(ItemManager.CreateInstance(radiatorData, .5f, 1) as EquippableItem);
turret.TryEquip(ItemManager.CreateInstance(radiatorData, .5f, 1) as EquippableItem);
turret.TryEquip(ItemManager.CreateInstance(controlModuleData, .5f, 1) as EquippableItem);
turret.TryEquip(ItemManager.CreateInstance(autocannonData, .5f, 1) as EquippableItem);
turret.TryEquip(ItemManager.CreateInstance(autocannonData, .5f, 1) as EquippableItem);
turret.TryEquip(ItemManager.CreateInstance(cargo1Data, .5f, 1) as EquippableItem);
turret.CargoBays.First().TryStore(ItemManager.CreateInstance(ammoData, 400));
foreach (var v in turretType.Shape.Coordinates)
turret.HullConductivity[v.x, v.y] = bool2(true);
turret.Active = true;
Zone.Entities.Add(turret);
}
var hullType = ItemData.GetAll<HullData>().First(x=>x.Name == "Djinni");
var shipHull = ItemManager.CreateInstance(hullType, 0, 1) as EquippableItem;
var ship = new Ship(ItemManager, Zone, shipHull);
PlayerShips.Add(ship);
CurrentShip = ship;
ship.ArmorDamage.Subscribe(hit =>
{
var direction = _directions.MaxBy(d => dot(d.direction, normalize(hit.pos - hullType.Shape.CenterOfMass)));
EventLog.LogMessage(
$"{direction.name} armor hit for {hit.damage} damage, currently {((int) (ship.Armor[hit.pos.x, hit.pos.y] / hullType.Armor * 100)).ToString()}%",
Settings.ArmorHitColor);
});
ship.HullDamage.Subscribe(hit => EventLog.LogMessage($"Hull Structure hit for {hit} damage"));
ship.ItemDamage.Subscribe(hit =>
{
var data = ItemManager.GetData(hit.item.EquippableItem);
EventLog.LogMessage(
$"{hit.item.EquippableItem.Name} hit for {hit.damage} damage, currently {((int) (hit.item.EquippableItem.Durability / data.Durability * 100)).ToString()}%",
Settings.GearHitColor);
});
ship.HardpointDamage.Subscribe(hit => EventLog.LogMessage(
$"{Enum.GetName(typeof(HardpointType), hit.hardpoint.Type)} hardpoint hit for {hit.damage} damage, currently {((int) (ship.HardpointArmor[hit.hardpoint] / hit.hardpoint.Armor * 100)).ToString()}%",
Settings.HardpointHitColor));
var thrusterData = ItemData.GetAll<GearData>().First(x => x.HardpointType == HardpointType.Thruster && x.Shape.Height == 1);
for (int i = 0; i < 8; i++)
{
ship.TryEquip(ItemManager.CreateInstance(thrusterData, .5f, 1) as EquippableItem);
//dockingCargo.TryStore(ItemManager.CreateInstance(thrusterData, .5f, 1));
}
ship.TryEquip(ItemManager.CreateInstance(reactorData, .5f, 1) as EquippableItem);
var cockpitData = ItemData.GetAll<GearData>().First(x => x.Behaviors.Any(b=>b is CockpitData));
ship.TryEquip(ItemManager.CreateInstance(cockpitData, .5f, 1) as EquippableItem);
ship.TryEquip(ItemManager.CreateInstance(autocannonData, .5f, 1) as EquippableItem);
ship.TryEquip(ItemManager.CreateInstance(autocannonData, .5f, 1) as EquippableItem);
var lrmmData = ItemData.GetAll<GearData>().First(x => x.Name == "LRMM72");
ship.TryEquip(ItemManager.CreateInstance(lrmmData, .5f, 1) as EquippableItem);
ship.TryEquip(ItemManager.CreateInstance(lrmmData, .5f, 1) as EquippableItem);
var cargoData = ItemData.GetAll<CargoBayData>().First(x => x.Name == "Cargo Bay 4x4");
ship.TryEquip(ItemManager.CreateInstance(cargoData, .5f, 1) as EquippableItem);
ship.CargoBays.First().TryStore(ItemManager.CreateInstance(ammoData, 200));
//dockingCargo.TryStore(ItemManager.CreateInstance(reactorData, .5f, 1));
Dock();
DockedEntity = station;
DockingBay = station.DockingBays.First();
DockCamera.enabled = true;
FollowCamera.enabled = false;
DockCamera.Follow = SectorRenderer.EntityInstances[station].Transform;
var parentOrbit = Zone.Orbits[station.OrbitData].Data.Parent;
var parentPlanet = SectorRenderer.Planets[Zone.Planets.FirstOrDefault(p => p.Value.Orbit == parentOrbit).Key];
DockCamera.LookAt = parentPlanet.Body.transform;
Menu.ShowTab(MenuTab.Inventory);
Cursor.lockState = CursorLockMode.None;
_input.Player.Disable();
GameplayUI.SetActive(false);
// MapButton.onClick.AddListener(() =>
// {
@ -446,6 +414,7 @@ public class ActionGameManager : MonoBehaviour
var bay = entity.TryDock(CurrentShip);
if (bay != null)
{
DockedEntity = entity;
DockingBay = bay;
DockCamera.enabled = true;
FollowCamera.enabled = false;
@ -490,6 +459,7 @@ public class ActionGameManager : MonoBehaviour
else if (CurrentShip.Parent.TryUndock(CurrentShip))
{
Menu.gameObject.SetActive(false);
DockedEntity = null;
DockingBay = null;
CurrentShip.Active = true;
//_shipInput = new ShipInput(_input.Player, _currentShip);
@ -515,7 +485,8 @@ public class ActionGameManager : MonoBehaviour
foreach (var group in _articulationGroups)
group.crosshair.gameObject.SetActive(true);
_lockingIndicators = CurrentShip.GetBehaviors<LockWeapon>().Select(x =>
_lockingIndicators = CurrentShip.GetBehaviors<LockWeapon>()
.Select(x =>
{
var i = LockIndicator.Instantiate<PlaceUIElementWorldspace>();
return (x, i, i.GetComponent<Rotate>());
@ -574,15 +545,18 @@ public class ActionGameManager : MonoBehaviour
public IEnumerable<Ship> AvailableShips()
{
if (CurrentShip.Parent != null)
if (CurrentShip != null)
{
foreach (var ship in PlayerShips)
if (CurrentShip.Parent != null)
{
if (CurrentShip.Parent.Children.Contains(ship)) yield return ship;
foreach (var ship in PlayerShips)
{
if (CurrentShip.Parent.Children.Contains(ship)) yield return ship;
}
}
else
yield return CurrentShip;
}
else
yield return CurrentShip;
}
void Update()
@ -591,7 +565,7 @@ public class ActionGameManager : MonoBehaviour
{
_time += Time.deltaTime;
ItemManager.Time = Time.time;
if(CurrentShip.Parent==null)
if(CurrentShip !=null && CurrentShip.Parent==null)
{
var look = _input.Player.Look.ReadValue<Vector2>();
_shipYawPitch = float2(_shipYawPitch.x + look.x * Sensitivity.x, clamp(_shipYawPitch.y + look.y * Sensitivity.y, -.45f * PI, .45f * PI));

View file

@ -9,6 +9,7 @@ using float2 = Unity.Mathematics.float2;
public class HullCollider : MonoBehaviour
{
public Subject<HullHitEventArgs> Hit = new Subject<HullHitEventArgs>();
public Subject<HullSplashEventArgs> Splash = new Subject<HullSplashEventArgs>();
public Entity Entity { get; set; }
@ -26,6 +27,17 @@ public class HullCollider : MonoBehaviour
});
}
public void SendSplash(float damage, DamageType damageType, Entity source, Vector3 direction)
{
Splash.OnNext(new HullSplashEventArgs
{
Damage = damage,
DamageType = damageType,
Source = source,
Direction = direction
});
}
public class HullHitEventArgs
{
public float Damage;
@ -36,4 +48,12 @@ public class HullCollider : MonoBehaviour
public RaycastHit Hit;
public float3 Direction;
}
public class HullSplashEventArgs
{
public float Damage;
public DamageType DamageType;
public Entity Source;
public float3 Direction;
}
}

View file

@ -1,8 +1,8 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using static Unity.Mathematics.noise;
using Random = UnityEngine.Random;
@ -12,6 +12,12 @@ public class GuidedProjectile : MonoBehaviour
{
public Prototype HitEffect;
public Prototype ChildProjectile;
public Prototype AirburstProjectile;
public int AirburstCount;
public float AirburstDirectionality;
public float AirburstDistance;
public float AirburstRange;
public float AirburstVelocity;
public int Children;
public float SplitTime;
public float SplitSeparationForwardness;
@ -26,13 +32,13 @@ public class GuidedProjectile : MonoBehaviour
public float TopSpeed;
public Transform Source;
public Transform Target;
public Func<Vector3> TargetPosition;
private float _phase;
private float _prevDist;
private bool _active;
private bool _alive;
private List<GuidedProjectile> _children = new List<GuidedProjectile>();
public float3 StartPosition { get; set; }
public float Range { get; set; }
public Vector3 Velocity { get; set; }
@ -63,18 +69,20 @@ public class GuidedProjectile : MonoBehaviour
if (_active)
{
if (!Target)
if (TargetPosition == null && !Target)
{
StartCoroutine(FadeOut());
return;
}
var diff = Target.position - transform.position;
var targetPosition = TargetPosition?.Invoke() ?? Target.position;
var diff = targetPosition - transform.position;
var targetDist = diff.magnitude;
var position = (float3) t.position;
var sourceDist = length(StartPosition.xz - position.xz);
if (sourceDist > Range || _prevDist < targetDist)
if (sourceDist > Range || dot(diff,Velocity) < 0 || AirburstCount > 0 && targetDist < AirburstDistance)
{
StartCoroutine(FadeOut());
if (HitEffect != null)
@ -82,6 +90,27 @@ public class GuidedProjectile : MonoBehaviour
var ht = HitEffect.Instantiate<Transform>();
ht.position = t.position;
}
if (AirburstCount > 0 && AirburstProjectile)
{
var targetDiff = Target.position - transform.position;
if (targetDiff.magnitude < AirburstRange)
{
for (int i = 0; i < AirburstCount; i++)
{
var child = AirburstProjectile.Instantiate<Projectile>();
child.StartPosition = child.transform.position = t.position;
var randomDirection = Random.onUnitSphere;
child.Velocity = Vector3.Lerp(randomDirection, targetDiff.normalized, AirburstDirectionality).normalized * AirburstVelocity;
child.Range = AirburstRange;
child.Damage = Damage / AirburstCount;
child.Penetration = Penetration;
child.Spread = Spread;
child.DamageType = DamageType;
child.SourceEntity = SourceEntity;
}
}
}
return;
}
_prevDist = targetDist;
@ -99,7 +128,6 @@ public class GuidedProjectile : MonoBehaviour
var child = ChildProjectile.Instantiate<GuidedProjectile>();
child.transform.position = t.position;
child.StartPosition = StartPosition;
_children.Add(child);
var randomDirection = normalize(Random.insideUnitCircle);
var perpendicularRandom = randomDirection.x * right + randomDirection.y * up;
child.Velocity = normalize(lerp(perpendicularRandom, dir, SplitSeparationForwardness)) * length(Velocity) * SplitSeparationVelocity;
@ -108,14 +136,14 @@ public class GuidedProjectile : MonoBehaviour
child.Penetration = Penetration;
child.Spread = Spread;
child.DamageType = DamageType;
child.Source = Source;
child.Target = Target;
child.SourceEntity = SourceEntity;
child.GuidanceCurve = GuidanceCurve;
child.LiftCurve = LiftCurve;
child.ThrustCurve = ThrustCurve;
child.Thrust = Thrust;
child.TopSpeed = TopSpeed;
child.Source = Source;
child.Target = Target;
child.SourceEntity = SourceEntity;
child.Frequency = Frequency;
child.Thrust = Thrust;
}
@ -189,14 +217,8 @@ public class GuidedProjectile : MonoBehaviour
Particles.Stop(true, ParticleSystemStopBehavior.StopEmitting);
var startTime = Time.time;
var lifetime = Particles.main.startLifetime.constant;
while (Time.time - startTime < lifetime || _children.Count > 0)
while (Time.time - startTime < lifetime)
{
for (var index = 0; index < _children.Count; index++)
{
if (!_children[index].gameObject.activeSelf)
_children.RemoveAt(index--);
}
yield return null;
}
GetComponent<Prototype>().ReturnToPool();

View file

@ -1,6 +1,8 @@
using System.Collections;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using static Unity.Mathematics.math;
public class GuidedProjectileManager : InstantWeaponEffectManager
{
@ -30,6 +32,29 @@ public class GuidedProjectileManager : InstantWeaponEffectManager
p.Thrust = source.Entity.ItemManager.Evaluate(launcher.Thrust, item.EquippableItem, source.Entity);
p.TopSpeed = source.Entity.ItemManager.Evaluate(launcher.MissileVelocity, item.EquippableItem, source.Entity);
}
else if(weapon.Data is GuidedWeaponData guidance)
{
var p = ProjectilePrototype.Instantiate<GuidedProjectile>();
p.Source = source.Transform;
p.SourceEntity = source.Entity;
p.Target = target?.Transform;
p.Frequency = guidance.DodgeFrequency;
var hp = source.Entity.Hardpoints[item.Position.x, item.Position.y];
var barrel = source.GetBarrel(hp);
p.StartPosition = p.transform.position = barrel.position;
p.Damage = weapon.Damage;
p.Range = weapon.Range;
p.Penetration = weapon.Penetration;
p.Spread = weapon.DamageSpread;
p.DamageType = weapon.WeaponData.DamageType;
p.GuidanceCurve = guidance.GuidanceCurve.ToCurve();
p.LiftCurve = guidance.LiftCurve.ToCurve();
p.ThrustCurve = guidance.ThrustCurve.ToCurve();
p.Velocity = barrel.forward * weapon.Velocity;
p.Thrust = source.Entity.ItemManager.Evaluate(guidance.Thrust, item.EquippableItem, source.Entity);
p.TopSpeed = source.Entity.ItemManager.Evaluate(guidance.MissileVelocity, item.EquippableItem, source.Entity);
p.TargetPosition = () => source.Entity.Position + length( (float3)source.LookAtPoint.position - source.Entity.Position) * source.Entity.LookDirection;
}
else Debug.LogError($"Weapon {item.EquippableItem.Name} linked to {name} effect, but is not a Launcher!");
}
}

View file

@ -4,13 +4,20 @@ using System.Collections.Generic;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using UnityEngine;
using Random = UnityEngine.Random;
public class Projectile : MonoBehaviour
{
public TrailRenderer Trail;
public float Gravity;
public float Drag = .1f;
public Prototype HitEffect;
public float AirburstDistance;
public float AirburstRange;
public float AirburstVelocity;
public float DirectHitDamageMultiplier = 1;
private bool _alive;
public Zone Zone { get; set; }
@ -45,6 +52,7 @@ public class Projectile : MonoBehaviour
var t = transform;
var position = t.position;
Velocity += Vector3.up * (Gravity * Time.deltaTime);
Velocity *= max(0, 1 - Drag * Time.deltaTime);
var forward = Velocity.normalized;
t.forward = forward;
var ray = new Ray(position, Velocity);
@ -55,7 +63,7 @@ public class Projectile : MonoBehaviour
{
if (hull.Entity != SourceEntity)
{
hull.SendHit(Damage, Penetration, Spread, DamageType, SourceEntity, hit, forward);
hull.SendHit(Damage*DirectHitDamageMultiplier, Penetration, Spread, DamageType, SourceEntity, hit, forward);
transform.position = hit.point;
StartCoroutine(Kill());
}
@ -76,8 +84,23 @@ public class Projectile : MonoBehaviour
}
transform.position += Velocity * Time.deltaTime;
if((transform.position - StartPosition).magnitude > Range)
var distanceTraveled = (transform.position - StartPosition).magnitude;
if(distanceTraveled > Range)
StartCoroutine(Kill());
if (AirburstDistance > 1 && distanceTraveled > AirburstDistance)
{
StartCoroutine(Kill());
var ht = HitEffect.Instantiate<Transform>();
ht.position = t.position;
foreach (var collider in Physics.OverlapSphere(t.position, AirburstRange, 1))
{
var hull = collider.GetComponent<HullCollider>();
if (hull)
{
hull.SendSplash(Damage, DamageType, SourceEntity, (collider.transform.position - t.position).normalized);
}
}
}
}
}

View file

@ -1,6 +1,9 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using Random = UnityEngine.Random;
public class ProjectileManager : InstantWeaponEffectManager
{
@ -27,6 +30,7 @@ public class ProjectileManager : InstantWeaponEffectManager
p.Spread = weapon.DamageSpread;
p.DamageType = weapon.WeaponData.DamageType;
p.Zone = source.Entity.Zone;
p.AirburstDistance = target != null ? length(source.Entity.Position - target.Entity.Position) : (weapon.Range * .75f);
p.Trail.Clear();
}
}