Builds not verified yet at this commit; verification follows on Yggdrasil. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
206 lines
8.9 KiB
C#
206 lines
8.9 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.Reflection;
|
|
using MessagePack;
|
|
using Newtonsoft.Json;
|
|
using CultMath;
|
|
using static CultMath.math;
|
|
using quaternion = CultMath.quaternion;
|
|
|
|
[MessagePackObject]
|
|
public class Ship : Entity
|
|
{
|
|
|
|
// [Key("bindings")] public Dictionary<KeyCode,Guid> Bindings = new Dictionary<KeyCode,Guid>();
|
|
//[IgnoreMember] public int HullHardpointCount;
|
|
|
|
// [IgnoreMember] public Dictionary<Targetable, float> Contacts = new Dictionary<Targetable, float>();
|
|
// [IgnoreMember] public Targetable Target;
|
|
public Entity HomeEntity;
|
|
public float2 MovementDirection;
|
|
// The one facing command, -1..1, positive clockwise. Written each tick by the pilot (player input or the
|
|
// ship's agent); a heading becomes a demand only through Steering.Toward. Runtime only, never packed.
|
|
public float Turn;
|
|
public bool IsPlayerShip;
|
|
|
|
private Thruster[] _allThrusters;
|
|
// One column per thruster (its effect per unit throttle, in the body frame) and the throttles the allocator answers with.
|
|
private float3[] _columns;
|
|
private float[] _throttles;
|
|
private readonly ThrustAllocator _allocator = new ThrustAllocator();
|
|
|
|
private bool _exitingWormhole = false;
|
|
private bool _enteringWormhole = false;
|
|
private float _wormholeAnimationProgress;
|
|
private float2 _wormholeEntryPosition;
|
|
private float2 _wormholeEntryDirection;
|
|
private float2 _wormholePosition;
|
|
private float2 _wormholeExitVelocity;
|
|
|
|
public bool WormholeAnimationInProgress => _enteringWormhole || _exitingWormhole;
|
|
|
|
public event Action OnExitedWormhole;
|
|
public event Action OnEnteredWormhole;
|
|
|
|
public quaternion Rotation { get; private set; }
|
|
|
|
public void ExitWormhole(float2 wormholePosition, float2 exitVelocity)
|
|
{
|
|
_exitingWormhole = true;
|
|
_wormholeAnimationProgress = 0;
|
|
_wormholePosition = wormholePosition;
|
|
_wormholeExitVelocity = exitVelocity;
|
|
Direction = normalize(exitVelocity);
|
|
}
|
|
|
|
public void EnterWormhole(float2 wormholePosition)
|
|
{
|
|
SetTarget(TargetRef.None);
|
|
_wormholeAnimationProgress = 0;
|
|
_enteringWormhole = true;
|
|
_wormholeEntryPosition = Position.xz;
|
|
_wormholeEntryDirection = normalize(_wormholeEntryPosition-wormholePosition);
|
|
_wormholePosition = wormholePosition;
|
|
}
|
|
|
|
public override void Activate()
|
|
{
|
|
base.Activate();
|
|
|
|
_allThrusters = GetBehaviors<Thruster>().ToArray();
|
|
_columns = new float3[_allThrusters.Length];
|
|
_throttles = new float[_allThrusters.Length];
|
|
}
|
|
|
|
public Ship(ItemManager itemManager, Zone zone, EquippableItem hull, EntitySettings settings) : base(itemManager, zone, hull, settings)
|
|
{
|
|
}
|
|
|
|
#region ThrustCalculation
|
|
|
|
// What this ship can do at this instant (see ManoeuvreEnvelope): the box extremes of the thrusters' columns at their
|
|
// live thrust (zero when unpowered or not Active), because it reports what the ship can do now. The allocator solves
|
|
// with Column(NominalThrust), the full-grant column, because a column promises what a thruster does at a full grant
|
|
// and the allocator must not reshape its answer as the grant moves. Same geometry (Thruster.Column), different thrust.
|
|
// Derived each update, never saved.
|
|
public ManoeuvreEnvelope Envelope { get; private set; }
|
|
|
|
private void RecalculateEnvelope()
|
|
{
|
|
float forward = 0, back = 0, left = 0, right = 0, clockwise = 0, counterClockwise = 0;
|
|
foreach (var thruster in _allThrusters)
|
|
{
|
|
var column = thruster.Column(thruster.Item != null && thruster.Item.PowerSupply > 1e-4f ? thruster.Thrust : 0f);
|
|
forward += max(column.y, 0f);
|
|
back += max(-column.y, 0f);
|
|
left += max(-column.x, 0f);
|
|
right += max(column.x, 0f);
|
|
clockwise += max(column.z, 0f);
|
|
counterClockwise += max(-column.z, 0f);
|
|
}
|
|
Envelope = new ManoeuvreEnvelope(forward, back, left, right, clockwise, counterClockwise);
|
|
}
|
|
|
|
#endregion
|
|
|
|
// Called by Entity.Update once the items' performance is current and before the power bus is stepped and the items
|
|
// execute: liveness, thrust and the throttles they decide are one phase, the one Execute acts on. The lock is a
|
|
// bound on the allocator's solve; intent reaches the allocator here and nowhere else.
|
|
protected override void UpdateThrottles()
|
|
{
|
|
if (_exitingWormhole || _enteringWormhole) return;
|
|
RecalculateEnvelope();
|
|
for (var i = 0; i < _allThrusters.Length; i++)
|
|
_columns[i] = _allThrusters[i].Column(_allThrusters[i].NominalThrust);
|
|
_allocator.Allocate(_columns, MovementDirection, Turn, _throttles, ThrottleLocked);
|
|
for (var i = 0; i < _allThrusters.Length; i++) _allThrusters[i].Axis = _throttles[i];
|
|
}
|
|
|
|
public override void Update(float delta)
|
|
{
|
|
var velocityMagnitude = length(Velocity);
|
|
if(velocityMagnitude > .01f)
|
|
Velocity = normalize(Velocity) * decay(velocityMagnitude, HullData.Drag, delta);
|
|
|
|
Position.xz += Velocity * delta;
|
|
|
|
var normal = Zone.GetNormal(Position.xz);
|
|
var force = new float2(normal.x, normal.z);
|
|
Velocity += Zone.GravityAcceleration(force, Zone.Settings.GravityStrength) * delta;
|
|
var shipRight = Direction.Rotate(ItemRotation.Clockwise);
|
|
var forward = cross(float3(shipRight.x, 0, shipRight.y), normal);
|
|
Rotation = quaternion.LookRotation(forward, normal);
|
|
|
|
base.Update(delta);
|
|
|
|
if (_exitingWormhole)
|
|
{
|
|
_wormholeAnimationProgress += delta / ItemManager.GameplaySettings.WormholeAnimationDuration;
|
|
if(_wormholeAnimationProgress < 1)
|
|
{
|
|
if (_wormholeAnimationProgress < ItemManager.GameplaySettings.WormholeExitCurveStart)
|
|
{
|
|
Position.xz = _wormholePosition;
|
|
Rotation = quaternion.LookRotation(float3(0, 1, 0), float3(-Direction.x, 0, -Direction.y));
|
|
}
|
|
else
|
|
{
|
|
var exitLerp = (_wormholeAnimationProgress - ItemManager.GameplaySettings.WormholeExitCurveStart) /
|
|
(1 - ItemManager.GameplaySettings.WormholeExitCurveStart);
|
|
exitLerp = smootherstep(exitLerp); // Square the interpolation variable to produce curve with zero slope at start
|
|
Position.xz = _wormholePosition + normalize(_wormholeExitVelocity) * exitLerp * ItemManager.GameplaySettings.WormholeExitRadius;
|
|
Rotation = quaternion.LookRotation(
|
|
lerp(float3(0, 1, 0), forward, exitLerp),
|
|
lerp(float3(-Direction.x, 0, -Direction.y), normal, exitLerp));
|
|
}
|
|
|
|
Position.y = Position.y - lerp(ItemManager.GameplaySettings.WormholeDepth, 0, _wormholeAnimationProgress);
|
|
}
|
|
else
|
|
{
|
|
_exitingWormhole = false;
|
|
OnExitedWormhole?.Invoke();
|
|
OnExitedWormhole = null;
|
|
Velocity = _wormholeExitVelocity;
|
|
}
|
|
}
|
|
|
|
if (_enteringWormhole)
|
|
{
|
|
_wormholeAnimationProgress += delta / ItemManager.GameplaySettings.WormholeAnimationDuration;
|
|
if(_wormholeAnimationProgress < 1)
|
|
{
|
|
if (_wormholeAnimationProgress < 1 - ItemManager.GameplaySettings.WormholeExitCurveStart)
|
|
{
|
|
var enterLerp = _wormholeAnimationProgress / (1 - ItemManager.GameplaySettings.WormholeExitCurveStart);
|
|
enterLerp = smootherstep(enterLerp); // Square the interpolation variable to produce curve with zero slope at vertical
|
|
Position.xz = lerp(_wormholeEntryPosition, _wormholePosition, enterLerp);
|
|
Rotation = quaternion.LookRotation(
|
|
lerp(forward, float3(0, -1, 0), enterLerp),
|
|
lerp(normal, float3(-_wormholeEntryDirection.x, 0, -_wormholeEntryDirection.y), enterLerp));
|
|
}
|
|
else
|
|
{
|
|
Position.xz = _wormholePosition;
|
|
Rotation = quaternion.LookRotation(float3(0, -1, 0),
|
|
float3(-_wormholeEntryDirection.x, 0, -_wormholeEntryDirection.y));
|
|
}
|
|
|
|
Position.y = Position.y - lerp(0, ItemManager.GameplaySettings.WormholeDepth, _wormholeAnimationProgress);
|
|
}
|
|
else
|
|
{
|
|
_enteringWormhole = false;
|
|
OnEnteredWormhole?.Invoke();
|
|
OnEnteredWormhole = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|