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

@ -330,31 +330,31 @@ public class DatabaseInspector : EditorWindow
field.SetValue(obj, list);
GUI.changed = true;
}
// else
// {
// var sorted = true;
// var order = int.MinValue;
// foreach(var element in list)
// {
// var elementType = element.GetType();
// var elementOrder = elementType.GetCustomAttribute<OrderAttribute>()?.Order ?? 0;
// if (elementOrder < order)
// sorted = false;
// order = elementOrder;
// }
//
// if (!sorted)
// {
// var sortedList = list
// .Cast<object>()
// .OrderBy(o => o.GetType().GetCustomAttribute<OrderAttribute>()?.Order ?? 0);
// list = (IList) Activator.CreateInstance(type);
// foreach (var o in sortedList)
// list.Add(o);
// field.SetValue(obj, list);
// GUI.changed = true;
// }
// }
else
{
var sorted = true;
var order = int.MinValue;
foreach(var element in list)
{
var elementType = element.GetType();
var elementOrder = elementType.GetCustomAttribute<OrderAttribute>()?.Order ?? 0;
if (elementOrder < order)
sorted = false;
order = elementOrder;
}
if (!sorted)
{
var sortedList = list
.Cast<object>()
.OrderBy(o => o.GetType().GetCustomAttribute<OrderAttribute>()?.Order ?? 0);
list = (IList) Activator.CreateInstance(type);
foreach (var o in sortedList)
list.Add(o);
field.SetValue(obj, list);
GUI.changed = true;
}
}
foreach (var o in list)
{
if (o == null)

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();
}
}

View file

@ -12,6 +12,7 @@ using UnityEngine;
using static Unity.Mathematics.math;
using Unity.Mathematics;
using UnityEngine.Serialization;
using float2 = Unity.Mathematics.float2;
using Object = UnityEngine.Object;
using Random = UnityEngine.Random;
@ -278,82 +279,39 @@ public class SectorRenderer : MonoBehaviour
}
}
void DamageSchematic(float damage, float spread, Shape hitShape)
void DamageSchematic(float damage, Shape hitShape)
{
// Spread indicates proportion of penetrating damage which is divided among neighboring schematic cells
var directDamage = damage * (1 - spread);
foreach (var v in hitShape.Coordinates)
hitShape[v] = hitShape[v] && hullData.Shape[v];
// Search all cells occupied by target cells for equipped gear
EquippedItem equippedGear = null;
foreach (var hullCoord in hitShape.Coordinates)
float hullDamage = 0;
var damagePerCell = damage / hitShape.Coordinates.Length;
foreach (var v in hitShape.Coordinates)
{
if (entity.GearOccupancy[hullCoord.x, hullCoord.y] != null)
var d = damagePerCell;
// Subtract surface damage from armor, passing on the remainder to the item and then to the hull
var prev = entity.Armor[v.x, v.y];
entity.Armor[v.x, v.y] = max(prev - d, 0);
entity.ArmorDamage.OnNext((v, d));
d = max(d - prev, 0);
if (d > 0.1f)
{
equippedGear = entity.GearOccupancy[hullCoord.x, hullCoord.y];
break;
}
}
float remainder = 0;
// Direct damage is divided between gear and thermal equipment (if present)
if (equippedGear != null)
{
remainder = max(directDamage / 2 - equippedGear.EquippableItem.Durability, 0);
equippedGear.EquippableItem.Durability = max(equippedGear.EquippableItem.Durability - directDamage / 2, 0);
entity.ItemDamage.OnNext((equippedGear, directDamage / 2));
}
else if (equippedGear != null)
{
remainder = max(directDamage - equippedGear.EquippableItem.Durability, 0);
equippedGear.EquippableItem.Durability = max(equippedGear.EquippableItem.Durability - directDamage, 0);
entity.ItemDamage.OnNext((equippedGear, directDamage));
}
else remainder = directDamage;
// Direct damage remaining after all items are destroyed damages the hull
var hullDamage = remainder;
// Spread damage is divided among components surrounding the hardpoint which has been hit
var spreadDamage = damage * spread;
if (spreadDamage > .01f)
{
// Find the cells surrounding the target cells
var surroundingCells = hitShape.Expand();
// Exclude cells contained by the original shape or not within the ship interior
foreach (var cell in surroundingCells.Coordinates.ToArray())
if (hitShape[cell] || !hullData.InteriorCells[cell])
surroundingCells[cell] = false;
if (surroundingCells.Coordinates.Length > 0)
{
// Find all equipment occupying surrounding cells
var surroundingEquipment = new List<EquippedItem>();
foreach (var hullCoord in surroundingCells.Coordinates)
var item = entity.GearOccupancy[v.x, v.y];
if (item != null)
{
if (entity.GearOccupancy[hullCoord.x, hullCoord.y] != null)
surroundingEquipment.Add(entity.GearOccupancy[hullCoord.x, hullCoord.y]);
prev = item.EquippableItem.Durability;
item.EquippableItem.Durability = max(prev - d, 0);
entity.ItemDamage.OnNext((item, d));
d = max(d - prev, 0);
}
// Divide spread damage among all surrounding cells
remainder = 0;
foreach (var e in surroundingEquipment)
{
remainder += max(spreadDamage / surroundingCells.Coordinates.Length - e.EquippableItem.Durability, 0);
e.EquippableItem.Durability = max(e.EquippableItem.Durability - spreadDamage / surroundingCells.Coordinates.Length, 0);
entity.ItemDamage.OnNext((e, spreadDamage / surroundingCells.Coordinates.Length));
}
// Remaining damage plus spread damage for empty cells is dealt to the hull
hullDamage += remainder + spreadDamage / surroundingCells.Coordinates.Length *
(surroundingCells.Coordinates.Length - surroundingEquipment.Count);
}
else hullDamage += spreadDamage;
hullDamage += d;
}
if(hullDamage > .01f)
if(hullDamage > .1f)
{
entity.Hull.Durability -= hullDamage;
entity.HullDamage.OnNext(hullDamage);
@ -362,99 +320,80 @@ public class SectorRenderer : MonoBehaviour
foreach (var collider in instance.Prefab.HullColliders)
{
collider.Splash.Subscribe(splash =>
{
var hitShape = new Shape(hullData.Shape.Width, hullData.Shape.Height);
foreach (var v in hullData.Shape.Coordinates)
{
var localHitDirection = instance.Prefab.transform.InverseTransformDirection(splash.Direction);
var direction = normalize(float2(localHitDirection.x, localHitDirection.z));
var cellDot = dot(normalize(v - hullData.Shape.CenterOfMass), direction);
if (cellDot < 0) hitShape[v] = true;
}
DamageSchematic(splash.Damage, hitShape);
});
collider.Hit.Subscribe(hit =>
{
var hardpointIndex = (int) hit.Hit.textureCoord.x;
var hardpointIndex = (int) hit.Hit.textureCoord.x - 1;
var hitShape = new Shape(hullData.Shape.Width, hullData.Shape.Height);
// U coordinate between 0-1 indicates a hit that didn't land directly on a hardpoint
if (hardpointIndex <= 0)
// Find the 2D position of the hit scaled to the schematic
float2 hitPos = float2.zero;
if (hardpointIndex < 0)
{
// Penetration indicates proportion of damage which passes through the hull armor
var surfaceDamage = hit.Damage * (1 - hit.Penetration);
// Find the 2D position of the hit relative to the entity's pivot point
var hitPos3 = instance.Transform.InverseTransformPoint(hit.Hit.point);
var hitDirection = normalize(float2(hitPos3.x, hitPos3.z));
hitPos = float2(hit.Hit.textureCoord.x * hullData.Shape.Width, hit.Hit.textureCoord.y * hullData.Shape.Height);
// Search all schematic border cells for the cell which is closest to the hit position
var hitCell = int2(-1);
var maxDot = -1f;
var distance = float.MaxValue;
foreach (var v in hullData.Shape.Coordinates)
{
if (!hullData.InteriorCells[v])
var cellDist = lengthsq(hitPos - v);
if (cellDist < distance)
{
// Take the dot product of the vector from the center to each border cell,
// and the vector from the center to the impact point
var cellDot = dot(normalize(v - hullData.Shape.CenterOfMass), hitDirection);
if (cellDot > maxDot)
{
maxDot = cellDot;
hitCell = v;
}
distance = cellDist;
hitCell = v;
}
}
// Extra damage remaining after depleting hull armor is added to penetrating damage
var remainder = max(surfaceDamage - entity.Armor[hitCell.x,hitCell.y], 0);
var penetratingDamage = remainder + hit.Damage * hit.Penetration;
// Subtract surface damage from hull armor
entity.Armor[hitCell.x,hitCell.y] = max(entity.Armor[hitCell.x,hitCell.y] - surfaceDamage, 0);
entity.ArmorDamage.OnNext((hitCell, surfaceDamage));
// Find the local 2D vector corresponding to the direction of the incoming hit
var localHitDirection = instance.Prefab.transform.InverseTransformDirection(hit.Direction);
var penetrationVector = normalize(float2(localHitDirection.x, localHitDirection.z));
// March a ray through the ship from the hit position until we reach an item or pass all the way through
var penetrationPoint = float2(hitCell);
var penetrationCell = hitCell;
while (hullData.Shape[penetrationCell] &&
entity.GearOccupancy[penetrationCell.x, penetrationCell.y] == null)
{
penetrationPoint += penetrationVector * .5f;
penetrationCell = int2(penetrationPoint);
}
// If we have passed all the way through the ship without touching an item, just damage the hull
if (!hullData.Shape[penetrationCell])
{
entity.Hull.Durability -= penetratingDamage;
entity.HullDamage.OnNext(penetratingDamage);
}
else
{
// Create a shape containing only the cell which the hit penetrated to
var hitShape = new Shape(hullData.Shape.Width, hullData.Shape.Height);
hitShape[penetrationCell] = true;
// Continue with the damage calculation, damaging the item and spreading some damage to the cells around it
DamageSchematic(penetratingDamage, hit.Spread, hitShape);
}
hitShape[hitCell] = true;
}
else
{
// Collider UV coordinates starting with 1 correspond to hardpoint index
var hardpoint = hullData.Hardpoints[hardpointIndex - 1];
// Penetration indicates proportion of damage which passes directly to the item
var surfaceDamage = hit.Damage * (1 - hit.Penetration);
// Extra damage remaining after depleting hardpoint armor is added to penetrating damage
var remainder = max(surfaceDamage - entity.HardpointArmor[hardpoint], 0);
// Subtract surface damage from hardpoint armor
entity.HardpointArmor[hardpoint] = max(entity.HardpointArmor[hardpoint] - surfaceDamage, 0);
entity.HardpointDamage.OnNext((hardpoint, surfaceDamage));
var penetratingDamage = remainder + hit.Damage * hit.Penetration;
var hardpoint = hullData.Hardpoints[hardpointIndex];
// Obtain the hull coordinates of all cells occupied by the hardpoint
var hardpointCells = hullData.Shape.Inset(hardpoint.Shape, hardpoint.Position);
// Continue with the damage calculation, damaging the hardpoint and spreading some damage to the cells around it
DamageSchematic(penetratingDamage, hit.Spread, hardpointCells);
hitPos = hardpointCells.CenterOfMass;
foreach (var v in hardpointCells.Coordinates)
hitShape[v] = true;
}
for (int i = 0; i < hit.Spread - 1; i++)
{
hitShape.Expand();
}
if (hit.Penetration > .5f)
{
// Find the local 2D vector corresponding to the direction of the incoming hit
var localHitDirection = instance.Prefab.transform.InverseTransformDirection(hit.Direction);
var penetrationVector = normalize(float2(localHitDirection.x, localHitDirection.z));
// March a ray through the ship from the hit position
var penetrationPoint = hitPos;
var penetrationDistance = 0;
while (penetrationDistance < hit.Penetration)
{
penetrationPoint += penetrationVector * .5f;
hitShape[int2(penetrationPoint)] = true;
}
}
DamageSchematic(hit.Damage, hitShape);
});
}

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);

View file

@ -61,7 +61,9 @@ public class InventoryMenu : MonoBehaviour
var cargo = GameManager.DockingBay ?? GameManager.CurrentShip.CargoBays.FirstOrDefault();
if (cargo!=null)
InventoryPanels[0].Display(cargo);
InventoryPanels[1].Display(GameManager.CurrentShip);
if(GameManager.CurrentShip != null)
InventoryPanels[1].Display(GameManager.CurrentShip);
else InventoryPanels[1].Clear();
}
private void OnDisable()

View file

@ -26,6 +26,7 @@ public class InventoryPanel : MonoBehaviour
public TextMeshProUGUI Title;
public TextMeshProUGUI MinTempLabel;
public TextMeshProUGUI MaxTempLabel;
public GameObject TemperatureRange;
public Button Dropdown;
public Button Current;
public Button Thermal;
@ -140,8 +141,8 @@ public class InventoryPanel : MonoBehaviour
(
$"{pack.Name} - {pack.Price(GameManager.ItemManager):n0}", () =>
{
var ship = EntityPack.Unpack(GameManager.ItemManager, GameManager.CurrentShip.Zone, pack, true);
ship.SetParent(GameManager.CurrentShip.Parent);
var ship = EntityPack.Unpack(GameManager.ItemManager, GameManager.Zone, pack, true);
ship.SetParent(GameManager.DockedEntity);
GameManager.PlayerShips.Add(ship);
GameManager.Credits -= pack.Price(GameManager.ItemManager);
GameManager.CurrentShip = ship;
@ -162,8 +163,8 @@ public class InventoryPanel : MonoBehaviour
{
if(MinTempLabel)
{
MinTempLabel.text = $"Min: {((int) (_displayedEntity.MinTemp - 273.15f)).ToString()}°C";
MaxTempLabel.text = $"Max: {((int) (_displayedEntity.MaxTemp - 273.15f)).ToString()}°C";
MinTempLabel.text = $"{((int) (_displayedEntity.MinTemp - 273.15f)).ToString()}°C";
MaxTempLabel.text = $"{((int) (_displayedEntity.MaxTemp - 273.15f)).ToString()}°C";
}
var hullData = GameManager.ItemManager.GetData(_displayedEntity.Hull) as HullData;
for(var x = 0; x < _temperatureTexture.width; x++)
@ -210,6 +211,14 @@ public class InventoryPanel : MonoBehaviour
_subscriptions.Clear();
if (Current)
Current.gameObject.SetActive(false);
if (Thermal)
Thermal.gameObject.SetActive(false);
if (TemperatureRange)
TemperatureRange.SetActive(false);
if(TemperatureDisplay)
TemperatureDisplay.gameObject.SetActive(false);
@ -227,11 +236,8 @@ public class InventoryPanel : MonoBehaviour
_displayedCargo = null;
_firstRect = null;
if(MinTempLabel)
{
MinTempLabel.gameObject.SetActive(true);
MaxTempLabel.gameObject.SetActive(true);
}
if (TemperatureRange)
TemperatureRange.SetActive(true);
if(Title)
Title.text = entity.Name;
@ -353,13 +359,6 @@ public class InventoryPanel : MonoBehaviour
StartCoroutine(Pulse(hitCells, HitType.Armor, _hitSequence++));
RefreshCells(hitCells);
}));
_subscriptions.Add(entity.HardpointDamage.Subscribe(hit =>
{
var hitCells = hit.hardpoint.Shape.Coordinates.Select(v => v + hit.hardpoint.Position).ToArray();
StartCoroutine(Pulse(hitCells, HitType.Armor, _hitSequence++));
RefreshCells(hitCells);
}));
}
private IEnumerator Pulse(int2[] cells, HitType hitType, int sequence)
@ -384,11 +383,13 @@ public class InventoryPanel : MonoBehaviour
var lerp = (Time.time - startTime) / CellHitPulseTime;
foreach(var cell in cells)
if(_cellAnimationSequence[cell] == sequence)
CellInstances[cell].Background.color = Color.Lerp(hitColor, CellBackgroundColor, lerp);
if(CellInstances.ContainsKey(cell))
CellInstances[cell].Background.color = Color.Lerp(hitColor, CellBackgroundColor, lerp);
yield return null;
}
foreach (var cell in cells)
CellInstances[cell].Background.color = CellBackgroundColor;
if(CellInstances.ContainsKey(cell))
CellInstances[cell].Background.color = CellBackgroundColor;
}
public void Display(EquippedCargoBay cargo)
@ -397,16 +398,6 @@ public class InventoryPanel : MonoBehaviour
_displayedCargo = cargo;
_displayedEntity = null;
if(TemperatureDisplay)
TemperatureDisplay.gameObject.SetActive(false);
MinTempLabel.gameObject.SetActive(false);
MaxTempLabel.gameObject.SetActive(false);
if (Current)
Current.gameObject.SetActive(false);
if (Thermal)
Thermal.gameObject.SetActive(false);
if(Title)
Title.text = cargo.Name;
@ -572,13 +563,11 @@ public class InventoryPanel : MonoBehaviour
if (_hud)
{
if (!interior && _displayedEntity.Armor[position.x, position.y] > .01f)
return Settings.ArmorGradient.Evaluate(_displayedEntity.Armor[position.x, position.y] / hullData.Armor);
else if (hardpoint != null && _displayedEntity.HardpointArmor[hardpoint] > .01f)
return Settings.ArmorGradient.Evaluate(_displayedEntity.HardpointArmor[hardpoint] / hardpoint.Armor);
if (_displayedEntity.Armor[position.x, position.y] > .01f)
return Settings.ArmorGradient.Evaluate(_displayedEntity.Armor[position.x, position.y] / _displayedEntity.MaxArmor[position.x, position.y]);
if (item != null) return Settings.DurabilityGradient.Evaluate(item.EquippableItem.Durability / _displayedEntity.ItemManager.GetData(item.EquippableItem).Durability);
return Color.white * .25f;
return float3(.25f).ToColor();
}
if (hardpoint == null)
@ -588,13 +577,13 @@ public class InventoryPanel : MonoBehaviour
if (item == null)
{
return Color.white * .25f;
return float3(.25f).ToColor();
}
if(highlight)
return Color.white;
return Color.white * .5f;
return float3(.5f).ToColor();
}
var tint = hardpoint.TintColor;

View file

@ -10,6 +10,7 @@ public class MinimapCameraTrack : MonoBehaviour
{
public Transform Camera;
public bool Modulo;
public bool Flip;
public float Offset;
private RectTransform _rect;
@ -22,6 +23,8 @@ public class MinimapCameraTrack : MonoBehaviour
{
var dir = Camera.forward.Flatland().normalized;
var deg = -Mathf.Atan2(dir.y, dir.x) * Mathf.Rad2Deg;
if (Flip)
deg *= -1;
if (Modulo)
deg = Mathf.Repeat(deg + 15, 30) - 15;
_rect.rotation = Quaternion.Euler(0,0,deg+Offset);

View file

@ -399,7 +399,8 @@ public class TradeMenu : MonoBehaviour
private void Buy(SimpleCommodityData data, int quantity)
{
int lots = quantity / data.MaxStack;
// Up-rounded integer division from https://stackoverflow.com/a/503201
int lots = (quantity - 1) / data.MaxStack + 1;
int remaining = quantity;
for (int i = 0; i < lots; i++)
{