using System;
using System.Collections.Generic;
using Cthangover.Core.Utils;
namespace Cthangover.Core.Relationship
{
///
/// Container and factory for the party's recruit roster. The roster is owned
/// by the recruiting mod through the static instead of
/// living in the core RuntimeData, so the core knows nothing about
/// recruiting. Persistence is handled by the mod's RecruitingSaveable
/// (IModSaveable), and a new game clears the roster through the
/// RecruitingReset IResettable that RecruitingInitializer
/// registers with AppServices.
///
/// Provides ID-based lookup, duplicate-safe addition (auto-generates a unique
/// Guid if the requested ID collides, and rejects a second recruit of
/// the same enemy type), and removal that notifies
/// so behaviours can clean up and
/// detaches the recruit from the squad through .
/// initialises the recruit's Properties with a default
/// health of 10 via and immediately calls
/// RecruitBehaviourRegistry.OnConfigure, giving every registered
/// behaviour a chance to set up per-recruit state before the next tick fires.
///
public class RecruitingData
{
///
/// Session-wide roster. There is exactly one roster per playthrough —
/// starting a new game clears it, loading a save replaces its contents.
///
public static readonly RecruitingData Instance = new();
/// The current recruit roster.
public List Data { get; set; } = new();
/// True if a recruit with the given ID exists in the roster.
public bool HasID(string id)
{
return GetByID(id) != null;
}
/// Returns the recruit with the given ID, or null if not found.
public Recruit GetByID(string id)
{
foreach (var item in Data)
{
if (item.ID == id)
return item;
}
return null;
}
///
/// True if a recruit spawned from the given character template is already
/// in the roster. Recruitment allows only one instance per enemy type,
/// which also keeps the template id usable as a unique party key.
///
public bool HasCharacterID(string characterID)
{
return GetByCharacterID(characterID) != null;
}
/// Returns the recruit spawned from the given template, or null if not found.
public Recruit GetByCharacterID(string characterID)
{
foreach (var item in Data)
{
if (item.CharacterID == characterID)
return item;
}
return null;
}
///
/// Generates a unique ID: if is free,
/// returns it unchanged; otherwise appends a Guid suffix.
/// Loops until an unused ID is found (guaranteed because Guid
/// is unique enough for any practical roster size).
///
private string GenID(string id)
{
for (;;)
{
if (!HasID(id))
return id;
id = Guid.NewGuid().ToString();
}
}
///
/// Creates a new from the given
/// , assigns a unique instance ID,
/// initialises default health, appends to the roster, and fires
/// RecruitBehaviourRegistry.OnConfigure so behaviours
/// can set up per-recruit state.
///
/// Only one recruit per enemy type is allowed: a second attempt for an
/// already captured is ignored and the
/// existing entry is returned, so callers cannot create duplicates and
/// the template id remains a unique party card key.
///
public Recruit Add(string id, string characterID)
{
var existing = GetByCharacterID(characterID);
if (existing != null)
{
GameLogger.Log("RECRUIT",
$"Recruit '{characterID}' already captured, duplicate ignored", LogLevel.Warning);
return existing;
}
var data = new Recruit
{
ID = GenID(id),
CharacterID = characterID,
};
data.Properties.SetInt(Recruit.PROP_HEALTH, 10);
Data.Add(data);
RecruitBehaviourRegistry.Instance.OnConfigure(data);
return data;
}
///
/// Removes the recruit with the given ID from the roster.
/// Notifies
/// before deleting the entry so behaviours can persist state
/// or clean up companion nodes, then detaches the recruit from the
/// party and deletes its placeholder card via
/// , so no orphan party member
/// survives in the core save.
///
public void Remove(string id)
{
for (int i = Data.Count - 1; i >= 0; i--)
{
if (Data[i].ID == id)
{
RecruitBehaviourRegistry.Instance.OnRemove(Data[i]);
RecruitPartyBridge.Forget(Data[i]);
Data.RemoveAt(i);
return;
}
}
}
}
}