Adding a ton of funcitonality to the builder's movement and build queue
This commit is contained in:
parent
a63cce53e2
commit
f05734e19b
31 changed files with 3104 additions and 339 deletions
182
Assets/_Project/Scripts/Gameplay/BuildJob.cs
Normal file
182
Assets/_Project/Scripts/Gameplay/BuildJob.cs
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
// Assets/_Project/Scripts/Gameplay/BuildJob.cs
|
||||
using System;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
namespace TD.Gameplay
|
||||
{
|
||||
/// <summary>
|
||||
/// One entry in a <see cref="Builder"/>'s build queue. Replicated as part of a
|
||||
/// <see cref="NetworkList{T}"/> on the Builder so all clients can render queued
|
||||
/// ghosts and progress without per-job RPCs.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>Identity.</b> <see cref="JobId"/> is a server-assigned, monotonically
|
||||
/// increasing identifier so cancel/lookup operations can target a specific job
|
||||
/// regardless of its current index in the NetworkList. Index-based addressing
|
||||
/// breaks the moment any job is removed.</para>
|
||||
///
|
||||
/// <para><b>Stage transitions.</b> Jobs progress
|
||||
/// <see cref="BuildStage.Queued"/> → <see cref="BuildStage.Constructing"/> →
|
||||
/// (removed when complete). Only the head of the queue can transition to
|
||||
/// Constructing; tail jobs stay Queued until they reach the head.</para>
|
||||
///
|
||||
/// <para><b>Time fields.</b> <see cref="ConstructionStartServerTime"/> is set on
|
||||
/// the server using <c>NetworkManager.ServerTime.TimeAsFloat</c> at the moment
|
||||
/// construction begins. Clients compute current stage as
|
||||
/// <c>floor(elapsed / (BuildTime / 4))</c> against the same server time, so all
|
||||
/// peers see identical staging without per-stage RPC chatter.</para>
|
||||
///
|
||||
/// <para><b>INetworkSerializable.</b> Required for use in
|
||||
/// <see cref="NetworkList{T}"/>. Serializes only the minimum needed fields;
|
||||
/// derived data (footprint size, gold cost) is looked up from the
|
||||
/// TowerDefinition by <see cref="TowerTypeId"/> on each peer.</para>
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
public struct BuildJob : INetworkSerializable, IEquatable<BuildJob>
|
||||
{
|
||||
// ----- Persistent fields ------------------------------------------
|
||||
|
||||
/// <summary>Server-assigned unique ID. Stable across NetworkList reorderings.</summary>
|
||||
public ulong JobId;
|
||||
|
||||
/// <summary>Footprint anchor (SW corner, world-tile coords).</summary>
|
||||
public Vector2Int Anchor;
|
||||
|
||||
/// <summary>Index into <c>TowerPlacementManager.towerDefinitions[]</c>.</summary>
|
||||
public int TowerTypeId;
|
||||
|
||||
/// <summary>Current stage. See <see cref="BuildStage"/>.</summary>
|
||||
public BuildStage Stage;
|
||||
|
||||
/// <summary>
|
||||
/// Server time (seconds) at which the current Constructing run began.
|
||||
/// -1 while the job is Queued or Paused (no active timer running).
|
||||
/// Read by clients to compute the current visual stage locally as
|
||||
/// <c>(now - ConstructionStartServerTime) + AccumulatedConstructionTime</c>.
|
||||
/// </summary>
|
||||
public float ConstructionStartServerTime;
|
||||
|
||||
/// <summary>
|
||||
/// Construction time accumulated across previous Constructing runs.
|
||||
/// Used to preserve progress across pause/resume cycles.
|
||||
/// At pause, set to <c>elapsed_in_current_run + previous_accumulated</c>.
|
||||
/// At resume, <c>ConstructionStartServerTime</c> is reset to "now" and
|
||||
/// total progress is computed as
|
||||
/// <c>(now - ConstructionStartServerTime) + AccumulatedConstructionTime</c>.
|
||||
/// 0 for jobs that have never been paused.
|
||||
/// </summary>
|
||||
public float AccumulatedConstructionTime;
|
||||
|
||||
/// <summary>
|
||||
/// Gold the player paid when this job was queued. Used to refund on
|
||||
/// cancellation. Stored on the job (not looked up from the definition)
|
||||
/// so that a future "tower price change mid-match" mechanic refunds
|
||||
/// what was actually paid.
|
||||
/// </summary>
|
||||
public int GoldSpent;
|
||||
|
||||
// ----- Convenience constructors -----------------------------------
|
||||
|
||||
public static BuildJob CreateQueued(ulong jobId, Vector2Int anchor, int towerTypeId, int goldSpent)
|
||||
{
|
||||
return new BuildJob
|
||||
{
|
||||
JobId = jobId,
|
||||
Anchor = anchor,
|
||||
TowerTypeId = towerTypeId,
|
||||
Stage = BuildStage.Queued,
|
||||
ConstructionStartServerTime = -1f,
|
||||
AccumulatedConstructionTime = 0f,
|
||||
GoldSpent = goldSpent,
|
||||
};
|
||||
}
|
||||
|
||||
// ----- INetworkSerializable ---------------------------------------
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||
{
|
||||
serializer.SerializeValue(ref JobId);
|
||||
serializer.SerializeValue(ref Anchor);
|
||||
serializer.SerializeValue(ref TowerTypeId);
|
||||
|
||||
// BuildStage is a byte-backed enum; serialize as byte for forward-compat
|
||||
// and to avoid relying on default enum serialization assumptions.
|
||||
byte stageByte = (byte)Stage;
|
||||
serializer.SerializeValue(ref stageByte);
|
||||
Stage = (BuildStage)stageByte;
|
||||
|
||||
serializer.SerializeValue(ref ConstructionStartServerTime);
|
||||
serializer.SerializeValue(ref AccumulatedConstructionTime);
|
||||
serializer.SerializeValue(ref GoldSpent);
|
||||
}
|
||||
|
||||
// ----- IEquatable -------------------------------------------------
|
||||
//
|
||||
// NetworkList<T> requires IEquatable<T> in NGO 2.x. Critically, NGO's
|
||||
// indexer setter (jobs[i] = newValue) short-circuits the write when
|
||||
// Equals returns true — see NGO 2.7.0 changelog. If we only compared
|
||||
// by JobId, mutating Stage or ConstructionStartServerTime on a job and
|
||||
// writing it back would be silently dropped, leaving the list with the
|
||||
// old struct values forever. Compare every field that we ever mutate
|
||||
// after creation.
|
||||
//
|
||||
// GetHashCode uses JobId only because that's the unique identity and is
|
||||
// sufficient for hashing — the full-field comparison only happens on
|
||||
// hash collision (or where IEquatable bypasses the hash entirely,
|
||||
// which is the case in NetworkList's indexer setter).
|
||||
|
||||
public bool Equals(BuildJob other)
|
||||
{
|
||||
return JobId == other.JobId
|
||||
&& Anchor == other.Anchor
|
||||
&& TowerTypeId == other.TowerTypeId
|
||||
&& Stage == other.Stage
|
||||
&& ConstructionStartServerTime == other.ConstructionStartServerTime
|
||||
&& AccumulatedConstructionTime == other.AccumulatedConstructionTime
|
||||
&& GoldSpent == other.GoldSpent;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj) => obj is BuildJob other && Equals(other);
|
||||
|
||||
public override int GetHashCode() => JobId.GetHashCode();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lifecycle stage of a <see cref="BuildJob"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Backed by byte to keep the serialized payload small and to allow the byte
|
||||
/// round-trip in <see cref="BuildJob.NetworkSerialize{T}"/>.
|
||||
/// </remarks>
|
||||
public enum BuildStage : byte
|
||||
{
|
||||
/// <summary>
|
||||
/// Job is in the queue but the builder has not yet arrived at it.
|
||||
/// Tile is occupied (no other tower can be queued/placed there) but
|
||||
/// remains walkable — enemies pass through queued ghosts because the
|
||||
/// ghost represents intent, not a structure.
|
||||
/// </summary>
|
||||
Queued = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Builder has arrived and construction is in progress. Tile is
|
||||
/// occupied AND non-walkable — this is the moment the maze actually
|
||||
/// changes. Path re-validation runs at the transition into this stage.
|
||||
/// </summary>
|
||||
Constructing = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Construction was interrupted by the builder being moved away.
|
||||
/// Tile remains occupied and non-walkable (the half-built tower is
|
||||
/// still physical, still blocks enemies). The cube's Y-scale is frozen
|
||||
/// at the level it reached when paused. Resume returns to Constructing
|
||||
/// stage with previously-accumulated progress preserved.
|
||||
///
|
||||
/// While the head job is Paused, the queue contains exactly this one
|
||||
/// job — all other queued jobs are refunded and removed at the moment
|
||||
/// of pausing.
|
||||
/// </summary>
|
||||
Paused = 2,
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue