Deletes the Unity physics hit path (HullCollider.Hit/Splash, and the raycast/shield/SendHit blocks in Projectile, GuidedProjectile, Laser and Lightning) and replaces it with a single owner in the same cut: FireControl now decides whether a shot hits, Zone owns the pending-shot lifecycle, and Entity.DamageSchematic/ApplyHit (moved verbatim from EntityInstance) apply the result. Presentation subscribes to Zone.ShotCommitted/ShotResolved and applies nothing. FireControl.HitProbability is the pure targeting solution (Accuracy * pSensor * pSpread, gated by detection/range/lock/arc). FireControl.Fire freezes the weapon's payload and the shooter's resolved targeting stats at the instant the trigger is pulled (Q6), computes the predicted intercept through the one shared PredictedIntercept function (CombatState now calls the same function instead of duplicating first_order_intercept), and queues a PendingShot. FireControl.Step commits each shot's outcome at ArrivalTime - CommitHorizon -- deviation from the predicted intercept, measured once, live, at that instant -- and applies it at ArrivalTime. A shot whose flight is shorter than the horizon commits and resolves in the same tick it fires. Rolls draw from the shooter's own ItemManager.Random (Q5, seeded and reproducible); an out-of-arc or otherwise gated shot draws nothing. Player fire is arc-gated through the same predicate AI and turret fire use (Weapon.ArcAllowsFire, checked in InstantWeapon.Trigger -- the one point every shooter's trigger passes through), closing Q2. Adds GameplaySettings.CommitHorizon, SchematicCellSize and AgentMinHitProbability (first guesses; the headless suite is the tuning harness, operator play smoke is the arbiter). FORK reported, not worked around silently: HullCollider.SendHit/SendSplash cannot be deleted yet without breaking ConstantLaser.cs, ConstantLightning.cs, ConstantParticleWeapon.cs, HitscanEffect.cs and Mine.cs, which still call them from their own live Physics queries -- that's Cut 4's migration. They fire into the void today: nothing subscribes to Hit/Splash any more for the four weapon kinds this cut owns. tests/Aetheria.Shared.Tests/FireAuthorityTests.cs pins the roll's seeding, the gate-consumes-no-draw rule, resolve-on-arrival, the commit horizon and its evasion window, short-flight degradation, the frozen-payload snapshot, aimed hits, the item-then-hull damage order, the planar penetration march and the shield-absorbs branch. FireControlTests.cs and InputCapacitorTests.cs are updated for the new AI gate and OnFire(int) signature. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
523 lines
19 KiB
C#
523 lines
19 KiB
C#
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using GameCult.Caching;
|
|
using UniRx;
|
|
using CultMath;
|
|
using static CultMath.math;
|
|
using float2 = CultMath.float2;
|
|
using Random = CultMath.Random;
|
|
|
|
public class Zone
|
|
{
|
|
public Action<string> Log;
|
|
public ReactiveCollection<Entity> Entities = new ReactiveCollection<Entity>();
|
|
public Dictionary<CultRecordKey, BodyData> Planets = new Dictionary<CultRecordKey, BodyData>();
|
|
public Dictionary<CultRecordKey, Planet> PlanetInstances = new Dictionary<CultRecordKey, Planet>();
|
|
|
|
public Dictionary<CultRecordKey, Orbit> Orbits = new Dictionary<CultRecordKey, Orbit>();
|
|
public Dictionary<CultRecordKey, AsteroidBelt> AsteroidBelts = new Dictionary<CultRecordKey, AsteroidBelt>();
|
|
public PlanetSettings Settings;
|
|
|
|
private HashSet<CultRecordKey> _updatedOrbits = new HashSet<CultRecordKey>();
|
|
|
|
private ItemManager _itemManager;
|
|
private double _time;
|
|
private Random _random;
|
|
public List<Agent> Agents = new List<Agent>();
|
|
|
|
// Cut 3 (docs/fire-control-cut.md, 0b table): Zone owns the pending-shot collection and the ShotId
|
|
// namespace; FireControl owns every transition a shot goes through. ShotCommitted publishes once a shot's
|
|
// outcome is decided (R4's commit horizon); ShotResolved republishes the same, unchanged outcome at
|
|
// arrival, right before the shot is removed. Presentations are the only readers of either.
|
|
public List<PendingShot> PendingShots = new List<PendingShot>();
|
|
public Subject<ShotOutcome> ShotCommitted = new Subject<ShotOutcome>();
|
|
public Subject<ShotOutcome> ShotResolved = new Subject<ShotOutcome>();
|
|
private int _nextShotId;
|
|
public int NextShotId() => ++_nextShotId;
|
|
|
|
private List<Task> BeltUpdates = new List<Task>();
|
|
|
|
public float Time
|
|
{
|
|
get => (float) _time;
|
|
}
|
|
public ZonePack Pack { get; }
|
|
public GalaxyZone GalaxyZone { get; }
|
|
public Galaxy Galaxy { get; }
|
|
|
|
public Zone(ItemManager itemManager, PlanetSettings settings, ZonePack pack, GalaxyZone galaxyZone, Galaxy galaxy)
|
|
{
|
|
_time = pack.Time;
|
|
GalaxyZone = galaxyZone;
|
|
Galaxy = galaxy;
|
|
Pack = pack;
|
|
_itemManager = itemManager;
|
|
Settings = settings;
|
|
_random = new Random(galaxyZone?.Name.StableHash() ?? 1337u);
|
|
var cache = itemManager.ItemData;
|
|
|
|
foreach (var orbit in pack.Orbits)
|
|
{
|
|
Orbits.Add(orbit.Key, new Orbit(Settings, cache.Get(orbit)));
|
|
}
|
|
|
|
foreach (var body in pack.Planets)
|
|
{
|
|
var planet = cache.Get(body);
|
|
Planets.Add(body.Key, planet);
|
|
switch (planet)
|
|
{
|
|
case AsteroidBeltData belt:
|
|
AsteroidBelts[body.Key] = new AsteroidBelt(belt);
|
|
break;
|
|
case SunData sun:
|
|
PlanetInstances.Add(body.Key, new Sun(settings, sun, Orbits[planet.Orbit.Key]));
|
|
break;
|
|
case GasGiantData gas:
|
|
PlanetInstances.Add(body.Key, new GasGiant(settings, gas, Orbits[planet.Orbit.Key]));
|
|
break;
|
|
default:
|
|
PlanetInstances.Add(body.Key, new Planet(settings, planet, Orbits[planet.Orbit.Key]));
|
|
break;
|
|
}
|
|
}
|
|
|
|
foreach (var entityPack in pack.Entities)
|
|
{
|
|
var entity = EntitySerializer.Unpack(_itemManager, this, entityPack);
|
|
Entities.Add(entity);
|
|
entity.Activate();
|
|
if (entity is Ship {IsPlayerShip: false} ship)
|
|
{
|
|
Agents.Add(CreateAgent(ship));
|
|
if (lengthsq(ship.Position) < 1)
|
|
ship.Position = _itemManager.Random.NextFloat3(float3(-pack.Radius * .5f), float3(pack.Radius * .5f));
|
|
}
|
|
}
|
|
|
|
// TODO: Associate planets with stored entities for planetary colonies
|
|
}
|
|
|
|
private Agent CreateAgent(Ship ship)
|
|
{
|
|
var agent = new Minion(ship);
|
|
var task = new PatrolOrbitsTask();
|
|
task.Circuit = Orbits.OrderBy(_ => _itemManager.Random.NextFloat()).Take(4).Select(x => x.Key).ToArray();
|
|
agent.Task = task;
|
|
return agent;
|
|
}
|
|
|
|
public ZonePack PackZone()
|
|
{
|
|
return new ZonePack
|
|
{
|
|
Radius = Pack.Radius,
|
|
Mass = Pack.Mass,
|
|
Entities = Entities.Select(EntitySerializer.Pack).ToList(),
|
|
Orbits = Orbits.Keys.Select(key => new CultRecordRef<OrbitData>(key)).ToList(),
|
|
Planets = Planets.Keys.Select(key => new CultRecordRef<BodyData>(key)).ToList(),
|
|
Time = _time
|
|
};
|
|
}
|
|
|
|
public void AddOrbit(OrbitData orbit)
|
|
{
|
|
Orbits.Add(_itemManager.ItemData.RefOf(orbit).Key, new Orbit(Settings, orbit));
|
|
}
|
|
|
|
public void Update(float deltaTime)
|
|
{
|
|
_time += deltaTime;
|
|
_updatedOrbits.Clear();
|
|
foreach (var t in BeltUpdates)
|
|
t.Wait();
|
|
BeltUpdates.Clear();
|
|
foreach (var orbit in Orbits)
|
|
{
|
|
orbit.Value.PreviousPosition = orbit.Value.Position;
|
|
orbit.Value.Position = GetOrbitPosition(orbit.Key);
|
|
orbit.Value.Velocity = (orbit.Value.Position - orbit.Value.PreviousPosition) / deltaTime;
|
|
}
|
|
|
|
foreach (var belt in AsteroidBelts)
|
|
{
|
|
Array.Copy(belt.Value.NewTransforms, belt.Value.Transforms, belt.Value.Transforms.Length);
|
|
belt.Value.OrbitPosition = belt.Value.NewOrbitPosition;
|
|
BeltUpdates.Add(Task.Run(() => UpdateAsteroidTransforms(belt.Key)));
|
|
}
|
|
|
|
foreach(var agent in Agents)
|
|
agent.Update(deltaTime);
|
|
|
|
foreach (var entity in Entities.ToArray()) entity.Update(deltaTime);
|
|
|
|
// Cut 3: after every entity has had its chance to fire this tick, age and resolve the shots that
|
|
// firing queued. A shot fired this tick with a flight time shorter than CommitHorizon commits and
|
|
// resolves in this same call.
|
|
FireControl.Step(this, deltaTime);
|
|
}
|
|
|
|
// Determine orbital position recursively, caching parent positions to avoid repeated calculations
|
|
public float2 GetOrbitPosition(CultRecordKey orbitID)
|
|
{
|
|
// Root orbit is fixed at origin
|
|
if(!orbitID.IsSet())
|
|
return float2.zero;
|
|
if (!Orbits.ContainsKey(orbitID))
|
|
{
|
|
Log?.Invoke("Requested orbit is not part of this zone!");
|
|
return float2.zero;
|
|
}
|
|
|
|
if (!_updatedOrbits.Contains(orbitID))
|
|
{
|
|
var orbit = Orbits[orbitID];
|
|
float2 pos = float2.zero;
|
|
if (orbit.Period > .01f)
|
|
{
|
|
var phase = (float) frac(_time / orbit.Period);
|
|
pos = OrbitData.Evaluate(frac(phase + orbit.Data.Phase)) * orbit.Data.Distance;
|
|
|
|
if (float.IsNaN(pos.x))
|
|
{
|
|
//_context.Log("Orbit position is NaN, something went very wrong!");
|
|
pos = float2.zero;
|
|
}
|
|
}
|
|
|
|
var parentPosition = !Orbits[orbitID].Data.Parent.IsSet()
|
|
? Orbits[orbitID].Data.FixedPosition :
|
|
GetOrbitPosition(orbit.Data.Parent.Key);
|
|
Orbits[orbitID].Position = parentPosition + pos;
|
|
_updatedOrbits.Add(orbitID);
|
|
}
|
|
|
|
return Orbits[orbitID].Position;
|
|
}
|
|
|
|
public float2 GetOrbitVelocity(CultRecordKey orbit)
|
|
{
|
|
if (Orbits.ContainsKey(orbit))
|
|
return Orbits[orbit].Velocity;
|
|
return float2.zero;
|
|
}
|
|
|
|
public int NearestAsteroid(CultRecordKey planetDataID, float2 position)
|
|
{
|
|
var beltData = Planets[planetDataID] as AsteroidBeltData;
|
|
|
|
var asteroidPositions = AsteroidBelts[planetDataID].Transforms;
|
|
|
|
int nearest = 0;
|
|
float nearestDistance = Single.MaxValue;
|
|
for (int i = 0; i < beltData.Asteroids.Length; i++)
|
|
{
|
|
var dist = lengthsq(asteroidPositions[i].xz - position);
|
|
if (AsteroidExists(planetDataID, i) && dist < nearestDistance)
|
|
{
|
|
nearest = i;
|
|
nearestDistance = dist;
|
|
}
|
|
}
|
|
|
|
return nearest;
|
|
}
|
|
|
|
public bool AsteroidExists(CultRecordKey planetDataID, int asteroid) => ((AsteroidBeltData) Planets[planetDataID]).Asteroids.Length > asteroid && asteroid >= 0;
|
|
|
|
private void UpdateAsteroidTransforms(CultRecordKey planetDataID)
|
|
{
|
|
var beltData = Planets[planetDataID] as AsteroidBeltData;
|
|
|
|
var belt = AsteroidBelts[planetDataID];
|
|
|
|
var orbitData = Orbits[beltData.Orbit.Key].Data;
|
|
belt.NewOrbitPosition = GetOrbitPosition(orbitData.Parent.Key);
|
|
for (var i = 0; i < beltData.Asteroids.Length; i++)
|
|
{
|
|
float size;
|
|
if(belt.RespawnTimers.ContainsKey(i)) size = 0;
|
|
else if (belt.Damage.ContainsKey(i))
|
|
{
|
|
var asteroidHitpoints = Settings.AsteroidHitpoints.Evaluate(beltData.Asteroids[i].Size);
|
|
var damage = (asteroidHitpoints - belt.Damage[i]) / asteroidHitpoints;
|
|
size = Settings.AsteroidSize.Evaluate(damage * beltData.Asteroids[i].Size);
|
|
}
|
|
else size = Settings.AsteroidSize.Evaluate(beltData.Asteroids[i].Size);
|
|
|
|
var rot = (float) (_time * beltData.Asteroids[i].RotationSpeed % (PI * 2));
|
|
var pos = OrbitData.Evaluate((float) frac(_time / Settings.OrbitPeriod.Evaluate(beltData.Asteroids[i].Distance) +
|
|
beltData.Asteroids[i].Phase)) * beltData.Asteroids[i].Distance + belt.NewOrbitPosition;
|
|
//belt.NewPositions[i] = float3(pos.x, GetHeight(pos) + Settings.AsteroidVerticalOffset, pos.y);
|
|
belt.NewTransforms[i] = float4(pos.x, pos.y, rot, size);
|
|
}
|
|
}
|
|
|
|
public OrbitData CreateOrbit(CultRecordKey parent, float2 position)
|
|
{
|
|
var parentPosition = GetOrbitPosition(parent);
|
|
var delta = position - parentPosition;
|
|
var distance = length(delta);
|
|
var period = Settings.OrbitPeriod.Evaluate(distance);
|
|
var phase = atan2(delta.y, delta.x) / (PI * 2);
|
|
var currentPhase = frac(_time / period);
|
|
var storedPhase = (float) frac(phase - currentPhase);
|
|
|
|
var orbit = new OrbitData
|
|
{
|
|
Distance = distance,
|
|
Parent = new CultRecordRef<OrbitData>(parent),
|
|
Phase = storedPhase
|
|
};
|
|
Orbits.Add(_itemManager.ItemData.Upsert(orbit).Key, new Orbit(Settings, orbit));
|
|
return orbit;
|
|
}
|
|
|
|
public void MineAsteroid(Entity miner, CultRecordKey asteroidBelt, int asteroid, float damage, float efficiency, float penetration)
|
|
{
|
|
var beltData = Planets[asteroidBelt] as AsteroidBeltData;
|
|
var belt = AsteroidBelts[asteroidBelt];
|
|
//var asteroidTransform = belt.Transforms[asteroid];
|
|
|
|
var size = beltData.Asteroids[asteroid].Size;
|
|
var asteroidHitpoints = Settings.AsteroidHitpoints.Evaluate(size);
|
|
|
|
if (!belt.Damage.ContainsKey(asteroid))
|
|
belt.Damage[asteroid] = 0;
|
|
belt.Damage[asteroid] = belt.Damage[asteroid] + damage;
|
|
|
|
if (!belt.MiningAccumulator.ContainsKey((miner, asteroid)))
|
|
belt.MiningAccumulator[(miner, asteroid)] = 0;
|
|
belt.MiningAccumulator[(miner, asteroid)] = belt.MiningAccumulator[(miner, asteroid)] + damage;
|
|
|
|
if (belt.Damage[asteroid] > asteroidHitpoints)
|
|
{
|
|
belt.RespawnTimers[asteroid] = Settings.AsteroidRespawnTime.Evaluate(size);
|
|
belt.Damage.Remove(asteroid);
|
|
belt.MiningAccumulator.Remove((miner, asteroid));
|
|
return;
|
|
}
|
|
|
|
var resourceCount = beltData.Resources.Sum(x => x.Value);
|
|
var resource = beltData.Resources.MaxBy(x => pow(x.Value, 1f / penetration) * _random.NextFloat());
|
|
if (efficiency * _random.NextFloat() * belt.MiningAccumulator[(miner, asteroid)] * resourceCount / Settings.MiningDifficulty > 1)
|
|
{
|
|
belt.MiningAccumulator.Remove((miner, asteroid));
|
|
// var newSimpleCommodity = new SimpleCommodity
|
|
// {
|
|
// Data = resource.Key,
|
|
// Quantity = 1
|
|
// };
|
|
// TODO: Drop item onto the Grid
|
|
//miner.AddCargo(newSimpleCommodity);
|
|
}
|
|
}
|
|
|
|
public SecurityLevel GetSecurityLevel(float2 pos)
|
|
{
|
|
if (GalaxyZone.Owner==null) return SecurityLevel.Open;
|
|
|
|
var security = SecurityLevel.Open;
|
|
foreach (var entity in Entities)
|
|
{
|
|
if (entity is OrbitalEntity orbitalEntity && orbitalEntity.SecurityRadius > 1 && entity.Faction == GalaxyZone.Owner)
|
|
{
|
|
if (orbitalEntity.SecurityLevel > security && length(orbitalEntity.Position.xz - pos) < orbitalEntity.SecurityRadius * Settings.SecureAreaRadiusMultiplier)
|
|
security = orbitalEntity.SecurityLevel;
|
|
}
|
|
}
|
|
|
|
return security;
|
|
}
|
|
|
|
public float GetHeight(float2 position)
|
|
{
|
|
var result = -PowerPulse(length(position)/(Pack.Radius*2), Settings.ZoneDepthExponent) * Settings.ZoneDepth;
|
|
foreach (var body in PlanetInstances.Values)
|
|
{
|
|
var p = position - body.Orbit.Position; //GetOrbitPosition(body.BodyData.Orbit)
|
|
var distSqr = lengthsq(p);
|
|
var gravityRadius = body.GravityWellRadius;
|
|
if (distSqr < gravityRadius*gravityRadius)
|
|
{
|
|
var depth = body.GravityWellDepth;
|
|
result -= PowerPulse(sqrt(distSqr) / gravityRadius, body.BodyData.GravityDepthExponent) * depth;
|
|
}
|
|
|
|
if (body is GasGiant gas)
|
|
{
|
|
var waveRadius = gas.GravityWavesRadius;
|
|
if(distSqr < waveRadius*waveRadius)
|
|
{
|
|
var depth = gas.GravityWavesDepth;
|
|
var frequency = Settings.WaveFrequency.Evaluate(body.BodyData.Mass);
|
|
var speed = gas.GravityWavesSpeed;
|
|
result -= RadialWaves(sqrt(distSqr) / waveRadius, 8, 1.25f, frequency, (float) (_time * speed)) * depth;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public float GetLight(float2 position)
|
|
{
|
|
var light = 0f;
|
|
foreach (var body in PlanetInstances.Values)
|
|
{
|
|
if (body is Sun sun)
|
|
{
|
|
var p = position - body.Orbit.Position;
|
|
var distSqr = lengthsq(p);
|
|
var lightRadius = sun.LightRadius;
|
|
if (distSqr < lightRadius * lightRadius)
|
|
{
|
|
light += PowerPulse(sqrt(distSqr) / lightRadius, 8);
|
|
}
|
|
}
|
|
}
|
|
|
|
return light;
|
|
}
|
|
|
|
public float2 GetForce(float2 position)
|
|
{
|
|
var normal = GetNormal(position);
|
|
var f = new float2(normal.x, normal.z);
|
|
return f * Settings.GravityStrength * lengthsq(f);
|
|
}
|
|
|
|
public static float PowerPulse(float x, float exponent)
|
|
{
|
|
x *= 2;
|
|
x = clamp(x, -1, 1);
|
|
return pow((x + 1) * (1 - x), exponent);
|
|
}
|
|
|
|
public static float RadialWaves(float x, float maskExponent, float sineExponent, float frequency, float phase)
|
|
{
|
|
//x *= 2;
|
|
return PowerPulse(x, maskExponent) * cos(pow(x*2, sineExponent) * frequency + phase);
|
|
}
|
|
|
|
public float3 GetNormal(float2 pos, float step = .1f, float mul = 1)
|
|
{
|
|
float hL = GetHeight(new float2(pos.x - step, pos.y)) * mul;
|
|
float hR = GetHeight(new float2(pos.x + step, pos.y)) * mul;
|
|
float hD = GetHeight(new float2(pos.x, pos.y - step)) * mul;
|
|
float hU = GetHeight(new float2(pos.x, pos.y + step)) * mul;
|
|
|
|
// Deduce terrain normal
|
|
float3 normal = new float3((hL - hR), (hD - hU), step*2);
|
|
return normalize(normal).xzy;
|
|
}
|
|
}
|
|
|
|
public class Planet
|
|
{
|
|
public Orbit Orbit;
|
|
protected readonly PlanetSettings Settings;
|
|
public BodyData BodyData;
|
|
public float GravityWellDepth;
|
|
public float GravityWellRadius;
|
|
public float BodyRadius;
|
|
|
|
public Planet(PlanetSettings settings, BodyData data, Orbit orbit)
|
|
{
|
|
Settings = settings;
|
|
Orbit = orbit;
|
|
BodyData = data;
|
|
CalculateProperties();
|
|
}
|
|
|
|
public void CalculateProperties()
|
|
{
|
|
BodyRadius = Settings.BodyRadius.Evaluate(BodyData.Mass) * BodyData.BodyRadiusMultiplier;
|
|
GravityWellRadius = Settings.GravityRadius.Evaluate(BodyData.Mass) * BodyData.GravityRadiusMultiplier;
|
|
GravityWellDepth = Settings.GravityDepth.Evaluate(BodyData.Mass) * BodyData.GravityDepthMultiplier;
|
|
}
|
|
}
|
|
|
|
public class GasGiant : Planet
|
|
{
|
|
public GasGiantData GasGiantData;
|
|
public float GravityWavesDepth;
|
|
public float GravityWavesRadius;
|
|
public float GravityWavesSpeed;
|
|
|
|
public GasGiant(PlanetSettings settings, GasGiantData data, Orbit orbit) : base(settings, data, orbit)
|
|
{
|
|
GasGiantData = data;
|
|
CalculateProperties();
|
|
}
|
|
|
|
public new void CalculateProperties()
|
|
{
|
|
base.CalculateProperties();
|
|
GravityWavesDepth = Settings.WaveDepth.Evaluate(BodyData.Mass) * GasGiantData.WaveDepthMultiplier;
|
|
GravityWavesRadius = Settings.WaveRadius.Evaluate(BodyData.Mass) * GasGiantData.WaveRadiusMultiplier;
|
|
GravityWavesSpeed = Settings.WaveSpeed.Evaluate(BodyData.Mass) * GasGiantData.WaveSpeedMultiplier;
|
|
}
|
|
}
|
|
|
|
public class Sun : GasGiant
|
|
{
|
|
public SunData SunData;
|
|
public float LightRadius;
|
|
|
|
public Sun(PlanetSettings settings, SunData data, Orbit orbit) : base(settings, data, orbit)
|
|
{
|
|
SunData = data;
|
|
CalculateProperties();
|
|
}
|
|
|
|
public new void CalculateProperties()
|
|
{
|
|
base.CalculateProperties();
|
|
LightRadius = Settings.LightRadius.Evaluate(BodyData.Mass) * SunData.LightRadiusMultiplier;
|
|
}
|
|
}
|
|
|
|
public class AsteroidBelt
|
|
{
|
|
public AsteroidBeltData Data;
|
|
public float4[] Transforms; // x, y, rotation, scale
|
|
public float4[] NewTransforms; // x, y, rotation, scale
|
|
public float Radius { get; }
|
|
public float2 OrbitPosition;
|
|
public float2 NewOrbitPosition;
|
|
public Dictionary<int, float> RespawnTimers = new Dictionary<int, float>();
|
|
public Dictionary<int, float> Damage = new Dictionary<int, float>();
|
|
public Dictionary<(Entity, int), float> MiningAccumulator = new Dictionary<(Entity, int), float>();
|
|
|
|
public AsteroidBelt(AsteroidBeltData data)
|
|
{
|
|
Data = data;
|
|
Transforms = new float4[data.Asteroids.Length];
|
|
NewTransforms = new float4[data.Asteroids.Length];
|
|
Radius = data.Asteroids.Max(a => a.Distance);
|
|
}
|
|
}
|
|
|
|
public class Orbit
|
|
{
|
|
public OrbitData Data { get; }
|
|
public float2 Velocity = float2.zero;
|
|
public float2 Position = float2.zero;
|
|
public float2 PreviousPosition = float2.zero;
|
|
public float Period;
|
|
|
|
public Orbit(PlanetSettings settings, OrbitData data)
|
|
{
|
|
Data = data;
|
|
Period = settings.OrbitPeriod.Evaluate(data.Distance);
|
|
}
|
|
}
|