Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md). All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
namespace Theriapolis.Core.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// Owns live actors. Phase 4 only ever holds the single player actor; this
|
||||
/// class exists so Phase 5/6 can add NPCs without further architectural change.
|
||||
/// </summary>
|
||||
public sealed class ActorManager
|
||||
{
|
||||
private readonly List<Actor> _actors = new();
|
||||
private int _nextId = 1;
|
||||
|
||||
public IReadOnlyList<Actor> All => _actors;
|
||||
|
||||
public PlayerActor? Player { get; private set; }
|
||||
|
||||
/// <summary>Creates the player actor. Idempotent on repeat calls — returns the existing player.</summary>
|
||||
public PlayerActor SpawnPlayer(Util.Vec2 worldPixelPos)
|
||||
{
|
||||
if (Player is not null) return Player;
|
||||
var p = new PlayerActor
|
||||
{
|
||||
Id = _nextId++,
|
||||
Position = worldPixelPos,
|
||||
Allegiance = Rules.Character.Allegiance.Player,
|
||||
};
|
||||
_actors.Add(p);
|
||||
Player = p;
|
||||
return p;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase 5 overload: spawn the player and attach a freshly-built character
|
||||
/// in one step. The character carries the player's name into the actor.
|
||||
/// </summary>
|
||||
public PlayerActor SpawnPlayer(Util.Vec2 worldPixelPos, Rules.Character.Character character)
|
||||
{
|
||||
var p = SpawnPlayer(worldPixelPos);
|
||||
p.Character = character;
|
||||
// The character record's identity (name) wins over the default actor name.
|
||||
// Other fields (HP, abilities, inventory) live exclusively on the Character.
|
||||
return p;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restore a player actor from a save. Does not allocate a new id — the
|
||||
/// saved id is restored as-is so cross-save references stay stable.
|
||||
/// </summary>
|
||||
public PlayerActor RestorePlayer(PlayerActorState state)
|
||||
{
|
||||
var p = new PlayerActor { Id = state.Id };
|
||||
p.RestoreState(state);
|
||||
_actors.Add(p);
|
||||
Player = p;
|
||||
if (state.Id >= _nextId) _nextId = state.Id + 1;
|
||||
return p;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase 5 M5: spawn an NPC at the given world-pixel position. The caller
|
||||
/// chooses the template (including any DangerZone-driven per-zone selection).
|
||||
/// Returns the spawned actor with a freshly-allocated id.
|
||||
/// </summary>
|
||||
public NpcActor SpawnNpc(
|
||||
Data.NpcTemplateDef template,
|
||||
Util.Vec2 worldPixelPos,
|
||||
Tactical.ChunkCoord? sourceChunk = null,
|
||||
int? sourceSpawnIndex = null)
|
||||
{
|
||||
var npc = new NpcActor(template)
|
||||
{
|
||||
Id = _nextId++,
|
||||
Position = worldPixelPos,
|
||||
SourceChunk = sourceChunk,
|
||||
SourceSpawnIndex = sourceSpawnIndex,
|
||||
};
|
||||
_actors.Add(npc);
|
||||
return npc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase 6 M1 — adopt a pre-constructed NpcActor (e.g. a resident built
|
||||
/// by <see cref="Rules.Combat.ResidentInstantiator"/> with the resident
|
||||
/// template constructor). Assigns an id and registers in the actor list.
|
||||
/// </summary>
|
||||
public NpcActor SpawnNpc(NpcActor pre)
|
||||
{
|
||||
if (pre.Id <= 0)
|
||||
{
|
||||
// Use reflection-free init via a fresh actor copy. NpcActor.Id is
|
||||
// init-only, but the only place we hit this path is the
|
||||
// resident-instantiator which constructs `pre` with Id = -1
|
||||
// expecting us to assign. Build the real one here.
|
||||
NpcActor adopted = pre.Resident is not null
|
||||
? new NpcActor(pre.Resident)
|
||||
{
|
||||
Id = _nextId++,
|
||||
Position = pre.Position,
|
||||
SourceChunk = pre.SourceChunk,
|
||||
SourceSpawnIndex = pre.SourceSpawnIndex,
|
||||
RoleTag = pre.RoleTag,
|
||||
HomeSettlementId = pre.HomeSettlementId,
|
||||
}
|
||||
: new NpcActor(pre.Template!)
|
||||
{
|
||||
Id = _nextId++,
|
||||
Position = pre.Position,
|
||||
SourceChunk = pre.SourceChunk,
|
||||
SourceSpawnIndex = pre.SourceSpawnIndex,
|
||||
RoleTag = pre.RoleTag,
|
||||
HomeSettlementId = pre.HomeSettlementId,
|
||||
};
|
||||
_actors.Add(adopted);
|
||||
return adopted;
|
||||
}
|
||||
_actors.Add(pre);
|
||||
if (pre.Id >= _nextId) _nextId = pre.Id + 1;
|
||||
return pre;
|
||||
}
|
||||
|
||||
/// <summary>Remove an actor by id. Returns true if it was found.</summary>
|
||||
public bool RemoveActor(int id)
|
||||
{
|
||||
for (int i = 0; i < _actors.Count; i++)
|
||||
{
|
||||
if (_actors[i].Id == id) { _actors.RemoveAt(i); return true; }
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Returns every NPC currently spawned.</summary>
|
||||
public IEnumerable<NpcActor> Npcs
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var a in _actors) if (a is NpcActor npc) yield return npc;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Find a live NPC originating from a specific chunk + spawn index.</summary>
|
||||
public NpcActor? FindNpcBySource(Tactical.ChunkCoord chunk, int spawnIndex)
|
||||
{
|
||||
foreach (var a in _actors)
|
||||
if (a is NpcActor npc &&
|
||||
npc.SourceChunk.HasValue && npc.SourceChunk.Value.Equals(chunk) &&
|
||||
npc.SourceSpawnIndex == spawnIndex)
|
||||
return npc;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user