Aetheria/Assets/Scripts/ServerShared/Ship.cs

207 lines
8.9 KiB
C#
Raw Normal View History

/* 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
{
public override float LayerHeight => ItemManager.GameplaySettings.ShipLayerHeight;
// [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;
2021-01-05 02:56:17 +00:00
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();
2021-03-05 14:31:18 +00:00
private bool _exitingWormhole = false;
private bool _enteringWormhole = false;
private float _wormholeAnimationProgress;
private float2 _wormholeEntryPosition;
private float2 _wormholeEntryDirection;
private float2 _wormholePosition;
private float2 _wormholeExitVelocity;
2021-03-05 14:31:18 +00:00
public bool WormholeAnimationInProgress => _enteringWormhole || _exitingWormhole;
2021-03-05 14:31:18 +00:00
public event Action OnExitedWormhole;
public event Action OnEnteredWormhole;
public quaternion Rotation { get; private set; }
2026-06-13 04:56:50 +01:00
public void ExitWormhole(float2 wormholePosition, float2 exitVelocity)
2021-03-05 14:31:18 +00:00
{
_exitingWormhole = true;
_wormholeAnimationProgress = 0;
_wormholePosition = wormholePosition;
_wormholeExitVelocity = exitVelocity;
Direction = normalize(exitVelocity);
2021-03-05 14:31:18 +00:00
}
public void EnterWormhole(float2 wormholePosition)
2021-03-05 14:31:18 +00:00
{
Mining Cut 3: a chunk is a target, detected by reflected light - Entity.Target is one slot of TargetRef, read-only from outside (IReadOnlyReactiveProperty); Entity.SetTarget is its one writer. A chunk is taken only while the entity can see it, so a rock in darkness cannot be picked. Every handler, agent, turret and test writes through SetTarget. - Entity.ChunkInfo (Q14 A): the value the per-tick sensor rule settles at, computed on demand through Sensor.PassiveRate/Sensor.Gain -- sum of the active sensors' passive rates over (sensors x TargetInfoDecay), capped at 1. Stored nowhere; no per-tick loop over chunks. ChunkVisible is the detection threshold through the chunk path. - The lost-track rule reads through the chunk path: Activate's reconciliation and a per-tick check of the one slot drop a chunk target that has gone dark. - Entity.VisibleChunksInReach (Q13 A): visible chunks within the longest range among active weapons -- the one reach rule for cycling and programmatic picks. - Entity-only readers take .Entity: TargetedBy/TargetedByCount, target items, the stance gate (a chunk has no stance), lock (Q12 A: never on a chunk), turrets, Combat, Minion, FireControl (a chunk fires untargeted until Cut 4). TargetRange and the arc gate read a chunk's planar position. - ActionGameManager: the reticle and next/previous offer visible ships and visible chunks in reach; nearest stays enemies-only; the target panel, indicator and fire-control debug take TargetRef. EntityInstance reads .Entity. - MiningCut3Tests pin parity, darkness, monotone brightness, wear and break, reach, the chunk query, target survival and loss, TargetRef equality, lock, and field-kind assignment at generation and first load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:56:39 +02:00
SetTarget(TargetRef.None);
2021-03-05 14:31:18 +00:00
_wormholeAnimationProgress = 0;
_enteringWormhole = true;
_wormholeEntryPosition = Position.xz;
_wormholeEntryDirection = normalize(_wormholeEntryPosition-wormholePosition);
2021-03-05 14:31:18 +00:00
_wormholePosition = wormholePosition;
}
public override void Activate()
2021-01-05 02:56:17 +00:00
{
base.Activate();
2021-01-05 02:56:17 +00:00
_allThrusters = GetBehaviors<Thruster>().ToArray();
_columns = new float3[_allThrusters.Length];
_throttles = new float[_allThrusters.Length];
2021-01-05 02:56:17 +00:00
}
public Ship(ItemManager itemManager, Zone zone, EquippableItem hull, EntitySettings settings) : base(itemManager, zone, hull, settings)
{
}
2021-03-05 14:31:18 +00:00
#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);
}
2021-03-05 14:31:18 +00:00
#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;
Velocity += Zone.GetForce(Position.xz) * delta;
var normal = Zone.GetNormal(Position.xz);
var shipRight = Direction.Rotate(ItemRotation.Clockwise);
var forward = cross(float3(shipRight.x, 0, shipRight.y), normal);
Rotation = quaternion.LookRotation(forward, normal);
base.Update(delta);
2021-03-05 14:31:18 +00:00
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));
2021-03-05 14:31:18 +00:00
}
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));
2021-03-05 14:31:18 +00:00
}
Position.y = Position.y - lerp(ItemManager.GameplaySettings.WormholeDepth, 0, _wormholeAnimationProgress);
2021-03-05 14:31:18 +00:00
}
else
{
_exitingWormhole = false;
OnExitedWormhole?.Invoke();
OnExitedWormhole = null;
Velocity = _wormholeExitVelocity;
2021-03-05 14:31:18 +00:00
}
}
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));
2021-03-05 14:31:18 +00:00
}
else
{
Position.xz = _wormholePosition;
Rotation = quaternion.LookRotation(float3(0, -1, 0),
float3(-_wormholeEntryDirection.x, 0, -_wormholeEntryDirection.y));
2021-03-05 14:31:18 +00:00
}
Position.y = Position.y - lerp(0, ItemManager.GameplaySettings.WormholeDepth, _wormholeAnimationProgress);
2021-03-05 14:31:18 +00:00
}
else
{
_enteringWormhole = false;
OnEnteredWormhole?.Invoke();
OnEnteredWormhole = null;
}
}
}
}