Major pipeline changes to map building, introducing 3player map, and adding Shared build mode to level authoring options
This commit is contained in:
parent
749c9203de
commit
2b1aee3e03
37 changed files with 5418 additions and 30 deletions
|
|
@ -217,8 +217,10 @@ namespace TD.Gameplay
|
|||
footprint.Add(tile);
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// Check 1: Ownership
|
||||
// Every footprint tile must be owned by the placing player's zone.
|
||||
// Check 1: Build permission
|
||||
// On an OwnerOnly map every footprint tile must be owned by the placing
|
||||
// player's zone; on a Shared map any zone's tile will do. LevelLoader owns
|
||||
// that rule so the client's ghost preview can't disagree with us.
|
||||
// ------------------------------------------------------------------
|
||||
PlayerSlot placingSlot = ClientIdToPlayerSlot(req.SenderClientId);
|
||||
if (placingSlot == PlayerSlot.None)
|
||||
|
|
@ -229,7 +231,7 @@ namespace TD.Gameplay
|
|||
|
||||
foreach (var tile in footprint)
|
||||
{
|
||||
if (loader.GetOwner(tile) != placingSlot)
|
||||
if (!loader.HasBuildPermission(tile, placingSlot))
|
||||
{
|
||||
Reject(req, PlacementRejectionReason.WrongOwner);
|
||||
return;
|
||||
|
|
@ -533,37 +535,67 @@ namespace TD.Gameplay
|
|||
// ----- Path-validity BFS ------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if every spawner in <paramref name="placingSlot"/>'s zone can still
|
||||
/// reach an exit tile (any leak exit tile OR any goal tile) via walkable tiles,
|
||||
/// given the current state of <c>LevelLoader</c>'s runtime walkability grid.
|
||||
/// Returns true if the map's mazes are still solvable with the candidate footprint in
|
||||
/// place — every spawner can still reach an exit tile (any leak exit from its own zone,
|
||||
/// OR any goal tile) via walkable tiles, given <c>LevelLoader</c>'s runtime grid.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Mirrors bake-time P5-4. Runs a BFS per spawner against the runtime walkability
|
||||
/// grid. Reuses <see cref="bfsQueue"/> and <see cref="bfsVisited"/> scratch
|
||||
/// collections (cleared between BFS runs) to avoid GC allocation per call.
|
||||
/// <para>Mirrors bake-time P5-4. Reuses <see cref="bfsQueue"/> and <see cref="bfsVisited"/>
|
||||
/// scratch collections (cleared between BFS runs) to avoid GC allocation per call.</para>
|
||||
///
|
||||
/// <para><b>Scope depends on the map's build rule.</b> Under
|
||||
/// <see cref="BuildAccess.OwnerOnly"/> a player can only build inside their own zone, so
|
||||
/// their footprint can only ever strand their OWN spawners — checking
|
||||
/// <paramref name="placingSlot"/>'s zone alone is both sufficient and cheapest. Under
|
||||
/// <see cref="BuildAccess.Shared"/> anyone can wall off anyone's lane, so every zone has to
|
||||
/// be checked or a player could strand a teammate's spawner and leave its enemies with no
|
||||
/// route at all.</para>
|
||||
/// </remarks>
|
||||
private bool CheckPathValidity(LevelLoader loader, PlayerSlot slot,
|
||||
private bool CheckPathValidity(LevelLoader loader, PlayerSlot placingSlot,
|
||||
HashSet<Vector2Int> virtualBlocked = null)
|
||||
{
|
||||
var levelData = loader.LevelData;
|
||||
if (levelData?.PlayerZones == null) return true;
|
||||
|
||||
if (loader.BuildAccess == BuildAccess.Shared)
|
||||
{
|
||||
foreach (var zone in levelData.PlayerZones)
|
||||
{
|
||||
if (!ZoneSpawnersCanReachExit(loader, levelData, zone, virtualBlocked))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Find the PlayerZoneData for this slot.
|
||||
PlayerZoneData zoneData = null;
|
||||
foreach (var zone in levelData.PlayerZones)
|
||||
{
|
||||
if (zone.Owner == slot) { zoneData = zone; break; }
|
||||
if (zone.Owner == placingSlot)
|
||||
return ZoneSpawnersCanReachExit(loader, levelData, zone, virtualBlocked);
|
||||
}
|
||||
if (zoneData == null || zoneData.Spawners == null || zoneData.Spawners.Length == 0)
|
||||
|
||||
// Placing player has no zone — nothing of theirs to strand.
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the reachability BFS for every spawner in one zone. Returns true if they can all
|
||||
/// still reach an exit (or the zone has no spawners to strand).
|
||||
/// </summary>
|
||||
private bool ZoneSpawnersCanReachExit(LevelLoader loader, LevelData levelData,
|
||||
PlayerZoneData zoneData,
|
||||
HashSet<Vector2Int> virtualBlocked)
|
||||
{
|
||||
if (zoneData?.Spawners == null || zoneData.Spawners.Length == 0)
|
||||
{
|
||||
// No spawners means nothing to block — treat as valid.
|
||||
return true;
|
||||
}
|
||||
|
||||
// Build the exit tile set: union of all leak exit tiles and all goal tiles.
|
||||
var exitTiles = BuildExitTileSet(levelData, slot);
|
||||
// Build the exit tile set: this zone's leak exits, plus all goal tiles.
|
||||
var exitTiles = BuildExitTileSet(levelData, zoneData.Owner);
|
||||
if (exitTiles.Count == 0)
|
||||
{
|
||||
Debug.LogWarning($"[TowerPlacementManager] Zone {slot} has no exit tiles. " +
|
||||
Debug.LogWarning($"[TowerPlacementManager] Zone {zoneData.Owner} has no exit tiles. " +
|
||||
$"This should have been caught at bake time (P5-8).");
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue