Adding new tower types and concept of tower deck

This commit is contained in:
Matt F 2026-06-23 21:55:12 -07:00
parent d1930a6b00
commit 117c77b4a8
16 changed files with 463 additions and 19 deletions

View file

@ -71,12 +71,17 @@ namespace TD.Gameplay
"worst-case 90/second (9 players × 10 placements/second).")]
[SerializeField] private int requestsPerFrame = 3;
[Tooltip("Tower definitions available in this match, indexed by TowerTypeId. " +
"Populate this with every TowerDefinition asset the current race roster " +
"contains. Index 0 is reserved; valid IDs start at 1. " +
"(Temporary: will be driven by RaceDefinition once Path E is complete.)")]
[Tooltip("Tower CATALOG for this match, indexed by TowerTypeId. This is the full " +
"pool of towers that can exist (be drafted) this match — NOT what any one " +
"player can build. Per-player buildable sets live in PlayerTowerDeck. " +
"Index 0 is reserved; valid IDs start at 1.")]
[SerializeField] private TowerDefinition[] towerDefinitions = new TowerDefinition[0];
[Tooltip("The starter towers every player begins the match with. Each must also " +
"appear in the catalog above (that's where its TowerTypeId comes from). " +
"Resolved to TypeIds and granted to each PlayerTowerDeck at match start.")]
[SerializeField] private TowerDefinition[] startingDeck = new TowerDefinition[0];
// ----- Internal request queue -------------------------------------
private struct PlacementRequest
@ -188,6 +193,16 @@ namespace TD.Gameplay
return;
}
// Deck membership — the requesting player must have unlocked this tower.
// Authoritative gate: a modded client cannot place a tower it hasn't drafted,
// even though every tower exists in the shared catalog.
var deck = PlayerTowerDeck.GetForClient(req.SenderClientId);
if (deck == null || !deck.Contains(req.TowerTypeId))
{
Reject(req, PlacementRejectionReason.TowerNotInDeck);
return;
}
var loader = LevelLoader.Instance;
if (loader == null || !loader.IsLoaded)
{
@ -652,6 +667,46 @@ namespace TD.Gameplay
}
}
/// <summary>
/// Reverse lookup: returns the TowerTypeId (catalog index) for a given
/// definition asset, or false if it isn't in the catalog. Used to resolve the
/// <see cref="startingDeck"/> assets into the TypeIds a <see cref="PlayerTowerDeck"/>
/// stores.
/// </summary>
public bool TryGetTypeId(TowerDefinition def, out int typeId)
{
typeId = 0;
if (def == null) return false;
for (int i = 1; i < towerDefinitions.Length; i++)
{
if (towerDefinitions[i] == def) { typeId = i; return true; }
}
return false;
}
/// <summary>
/// Resolves the configured <see cref="startingDeck"/> assets to their catalog
/// TypeIds. Logs an error for any starter tower missing from the catalog (it
/// would have no TypeId and couldn't be placed). Called once at match start.
/// </summary>
public List<int> GetStartingDeckTypeIds()
{
var ids = new List<int>();
if (startingDeck == null) return ids;
foreach (var def in startingDeck)
{
if (def == null) continue;
if (TryGetTypeId(def, out int id))
ids.Add(id);
else
Debug.LogError($"[TowerPlacementManager] Starting-deck tower '{def.name}' " +
$"is not in the towerDefinitions catalog. Add it to the catalog " +
$"so it has a TowerTypeId, or it can't be granted/placed.");
}
return ids;
}
private static PlayerSlot ClientIdToPlayerSlot(ulong clientId)
=> PlayerMatchState.SlotForClient(clientId);
@ -707,6 +762,10 @@ namespace TD.Gameplay
/// <summary>The requested tower type ID is not in the server's definition list.</summary>
InvalidTowerType,
/// <summary>The requested tower exists in the catalog but the placing player has
/// not unlocked it in their deck. Draft it first.</summary>
TowerNotInDeck,
/// <summary>An unexpected server-side error occurred (e.g., LevelLoader not loaded,
/// client not mapped to a PlayerSlot). Check server logs.</summary>
ServerError,