convert game playing..
This commit is contained in:
@@ -44,6 +44,7 @@ namespace BumpCombat.Spawning
|
||||
// current elite is still alive and the event must wait in the queue.
|
||||
private int nextEliteScheduleOrdinal;
|
||||
private int nextEliteSpawnOrdinal;
|
||||
private static int nextEncounterGroupId;
|
||||
private const int SummonSpawnAngleSteps = 32;
|
||||
|
||||
private static RunConstants Tuning => GameplayConstants.Current.Run;
|
||||
@@ -148,6 +149,477 @@ namespace BumpCombat.Spawning
|
||||
return HasTimedEventEnemy(timedEvent);
|
||||
}
|
||||
|
||||
public void SpawnZoneEncounter(BumpCombat.Stage.ZoneInstance zone)
|
||||
{
|
||||
if (zone == null) return;
|
||||
EnsureTuningLoaded();
|
||||
Vector2 center = zone.ZoneCenter;
|
||||
Vector2 extents = zone.PlayableHalfExtents;
|
||||
BumpCombat.Stage.RoomPreset preset = zone.Preset;
|
||||
|
||||
if (zone.NodeType == BumpCombat.Stage.StageNodeType.Elite)
|
||||
{
|
||||
Vector2 spawnPos = center;
|
||||
if (preset != null && preset.SpawnPoints.Count > 0)
|
||||
{
|
||||
var pt = preset.SpawnPoints.Find(p => p.Role == BumpCombat.Stage.SpawnRole.Elite);
|
||||
if (pt.Role == BumpCombat.Stage.SpawnRole.Elite)
|
||||
{
|
||||
spawnPos = preset.CellToWorldPosition(center, pt.CellPosition);
|
||||
}
|
||||
}
|
||||
|
||||
RunEventEnemyTuning tuning = GetEliteTuning(0);
|
||||
EnemyController prefab = GetEventPrefab(RunTimedEvent.Elite, tuning.PrefabKind);
|
||||
if (prefab != null)
|
||||
{
|
||||
EnemyController enemy = Instantiate(prefab, spawnPos, Quaternion.identity);
|
||||
enemy.name = "Stage 1 Elite";
|
||||
enemy.ConfigureRunEventEnemy(RunTimedEvent.Elite, tuning);
|
||||
enemy.OnDied += HandleEventEnemyDied;
|
||||
GetEventList(RunTimedEvent.Elite).Add(enemy);
|
||||
zone.RegisterSpawnedEnemy(enemy);
|
||||
OnEventEnemySpawned?.Invoke(RunTimedEvent.Elite, enemy);
|
||||
}
|
||||
|
||||
SpawnPresetSupportMinions(zone, center, preset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (zone.NodeType == BumpCombat.Stage.StageNodeType.MidBoss)
|
||||
{
|
||||
Vector2 spawnPos = center;
|
||||
if (preset != null && preset.SpawnPoints.Count > 0)
|
||||
{
|
||||
var pt = preset.SpawnPoints.Find(p => p.Role == BumpCombat.Stage.SpawnRole.MidBoss);
|
||||
if (pt.Role == BumpCombat.Stage.SpawnRole.MidBoss)
|
||||
{
|
||||
spawnPos = preset.CellToWorldPosition(center, pt.CellPosition);
|
||||
}
|
||||
}
|
||||
|
||||
RunEventEnemyTuning tuning = GetEventEnemyTuning(RunTimedEvent.MidBoss);
|
||||
EnemyController prefab = GetEventPrefab(RunTimedEvent.MidBoss, tuning.PrefabKind);
|
||||
if (prefab != null)
|
||||
{
|
||||
EnemyController enemy = Instantiate(prefab, spawnPos, Quaternion.identity);
|
||||
enemy.name = "Stage 2 MidBoss";
|
||||
enemy.ConfigureRunEventEnemy(RunTimedEvent.MidBoss, tuning);
|
||||
enemy.OnDied += HandleEventEnemyDied;
|
||||
GetEventList(RunTimedEvent.MidBoss).Add(enemy);
|
||||
zone.RegisterSpawnedEnemy(enemy);
|
||||
OnEventEnemySpawned?.Invoke(RunTimedEvent.MidBoss, enemy);
|
||||
}
|
||||
|
||||
SpawnPresetSupportMinions(zone, center, preset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (zone.NodeType == BumpCombat.Stage.StageNodeType.FinalBoss)
|
||||
{
|
||||
Vector2 spawnPos = center;
|
||||
if (preset != null && preset.SpawnPoints.Count > 0)
|
||||
{
|
||||
var pt = preset.SpawnPoints.Find(p => p.Role == BumpCombat.Stage.SpawnRole.FinalBoss);
|
||||
if (pt.Role == BumpCombat.Stage.SpawnRole.FinalBoss)
|
||||
{
|
||||
spawnPos = preset.CellToWorldPosition(center, pt.CellPosition);
|
||||
}
|
||||
}
|
||||
|
||||
RunEventEnemyTuning tuning = GetEventEnemyTuning(RunTimedEvent.FinalBoss);
|
||||
EnemyController prefab = GetEventPrefab(RunTimedEvent.FinalBoss, tuning.PrefabKind);
|
||||
if (prefab != null)
|
||||
{
|
||||
EnemyController enemy = Instantiate(prefab, spawnPos, Quaternion.identity);
|
||||
enemy.name = "Stage 3 FinalBoss";
|
||||
enemy.ConfigureRunEventEnemy(RunTimedEvent.FinalBoss, tuning);
|
||||
enemy.OnDied += HandleEventEnemyDied;
|
||||
GetEventList(RunTimedEvent.FinalBoss).Add(enemy);
|
||||
zone.RegisterSpawnedEnemy(enemy);
|
||||
OnEventEnemySpawned?.Invoke(RunTimedEvent.FinalBoss, enemy);
|
||||
}
|
||||
|
||||
SpawnPresetSupportMinions(zone, center, preset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (zone.NodeType == BumpCombat.Stage.StageNodeType.Combat || zone.NodeType == BumpCombat.Stage.StageNodeType.Fork)
|
||||
{
|
||||
if (preset != null && preset.EncounterPool != null && preset.EncounterPool.Count > 0)
|
||||
{
|
||||
SpawnPresetEncounterPool(zone, center, preset);
|
||||
return;
|
||||
}
|
||||
|
||||
if (preset != null && preset.SpawnPoints.Count > 0)
|
||||
{
|
||||
SpawnPresetAnchorsFallback(zone, center, preset);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: Random area spawn if no preset anchors
|
||||
if (enemyPrefabs == null || enemyPrefabs.Length == 0) return;
|
||||
int count = UnityEngine.Random.Range(3, 6);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
EnemyRole role = (i % 2 == 0) ? EnemyRole.Normal : EnemyRole.Crowd;
|
||||
EnemyController prefab = GetRandomPrefab(role) ?? enemyPrefabs[0];
|
||||
Vector2 offset = new Vector2(
|
||||
UnityEngine.Random.Range(-extents.x * 0.6f, extents.x * 0.6f),
|
||||
UnityEngine.Random.Range(-extents.y * 0.6f, extents.y * 0.6f));
|
||||
EnemyController enemy = Instantiate(prefab, center + offset, Quaternion.identity);
|
||||
enemy.transform.SetParent(zone.transform, true);
|
||||
zone.RegisterSpawnedEnemy(enemy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnPresetEncounterPool(BumpCombat.Stage.ZoneInstance zone, Vector2 center, BumpCombat.Stage.RoomPreset preset)
|
||||
{
|
||||
if (preset.EncounterPool == null || preset.EncounterPool.Count == 0)
|
||||
{
|
||||
SpawnPresetAnchorsFallback(zone, center, preset);
|
||||
return;
|
||||
}
|
||||
|
||||
// Select encounter (default Encounter A for S1-A first verification)
|
||||
BumpCombat.Stage.RoomEncounter encounter = preset.EncounterPool[0];
|
||||
if (encounter.Items == null || encounter.Items.Count == 0)
|
||||
{
|
||||
SpawnPresetAnchorsFallback(zone, center, preset);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerController player = FindAnyObjectByType<PlayerController>();
|
||||
Vector2 playerPos = (player != null)
|
||||
? (Vector2)player.transform.position
|
||||
: zone.GetEntryPosition(BumpCombat.Stage.StageDirection.West);
|
||||
|
||||
List<Vector2> occupiedPositions = new();
|
||||
|
||||
// Sort items so NormalMelee / Ranged claim authored anchors first before Crowd
|
||||
List<BumpCombat.Stage.RoomEncounterItem> sortedItems = new(encounter.Items);
|
||||
sortedItems.Sort((a, b) =>
|
||||
{
|
||||
bool aCrowd = a.EnemyKind.Equals("Bat", StringComparison.OrdinalIgnoreCase) || a.EnemyKind.Equals("Slime", StringComparison.OrdinalIgnoreCase);
|
||||
bool bCrowd = b.EnemyKind.Equals("Bat", StringComparison.OrdinalIgnoreCase) || b.EnemyKind.Equals("Slime", StringComparison.OrdinalIgnoreCase);
|
||||
return aCrowd.CompareTo(bCrowd);
|
||||
});
|
||||
|
||||
foreach (var item in sortedItems)
|
||||
{
|
||||
EnemyController prefab = ResolvePrefabForKind(item.EnemyKind);
|
||||
if (prefab == null) continue;
|
||||
|
||||
bool isFast = item.EnemyKind.Equals("Bat", StringComparison.OrdinalIgnoreCase);
|
||||
bool isRanged = item.EnemyKind.Contains("Archer", StringComparison.OrdinalIgnoreCase);
|
||||
float requiredSafeDist = isRanged ? 7.0f : (isFast ? 5.0f : 4.0f);
|
||||
|
||||
int remaining = item.Count;
|
||||
while (remaining > 0)
|
||||
{
|
||||
int groupSize = Mathf.Min(
|
||||
SelectEncounterGroupSize(preset.StageIndex, UnityEngine.Random.value),
|
||||
remaining);
|
||||
nextEncounterGroupId = nextEncounterGroupId == int.MaxValue
|
||||
? 1
|
||||
: nextEncounterGroupId + 1;
|
||||
int groupId = nextEncounterGroupId;
|
||||
Vector2 groupAnchor = Vector2.zero;
|
||||
|
||||
for (int memberIndex = 0; memberIndex < groupSize; memberIndex++)
|
||||
{
|
||||
Vector2 spawnWorldPos = memberIndex == 0
|
||||
? FindSafeSpawnPosition(
|
||||
preset,
|
||||
center,
|
||||
playerPos,
|
||||
item.EnemyKind,
|
||||
requiredSafeDist,
|
||||
occupiedPositions)
|
||||
: FindSafeGroupSpawnPosition(
|
||||
preset,
|
||||
center,
|
||||
playerPos,
|
||||
requiredSafeDist,
|
||||
groupAnchor,
|
||||
occupiedPositions);
|
||||
|
||||
if (memberIndex == 0)
|
||||
{
|
||||
groupAnchor = spawnWorldPos;
|
||||
}
|
||||
|
||||
EnemyController enemy = Instantiate(prefab, spawnWorldPos, Quaternion.identity);
|
||||
enemy.name = $"{item.EnemyKind}_{occupiedPositions.Count + 1}";
|
||||
enemy.transform.SetParent(zone.transform, true);
|
||||
enemy.ConfigureSightRangeMultiplier(
|
||||
GameplayConstants.Current.Run.StageEncounterSightRangeMultiplier);
|
||||
enemy.ConfigureEncounterGroup(groupId, groupAnchor);
|
||||
zone.RegisterSpawnedEnemy(enemy);
|
||||
occupiedPositions.Add(spawnWorldPos);
|
||||
}
|
||||
|
||||
remaining -= groupSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int SelectEncounterGroupSize(int stageIndex, float roll)
|
||||
{
|
||||
roll = Mathf.Clamp01(roll);
|
||||
if (stageIndex <= 1)
|
||||
{
|
||||
if (roll < 0.55f) return 1;
|
||||
return roll < 0.90f ? 2 : 3;
|
||||
}
|
||||
if (stageIndex == 2)
|
||||
{
|
||||
if (roll < 0.35f) return 1;
|
||||
return roll < 0.80f ? 2 : 3;
|
||||
}
|
||||
|
||||
if (roll < 0.20f) return 1;
|
||||
return roll < 0.70f ? 2 : 3;
|
||||
}
|
||||
|
||||
private Vector2 FindSafeGroupSpawnPosition(
|
||||
BumpCombat.Stage.RoomPreset preset,
|
||||
Vector2 zoneCenter,
|
||||
Vector2 playerPos,
|
||||
float safeDist,
|
||||
Vector2 groupAnchor,
|
||||
List<Vector2> occupied)
|
||||
{
|
||||
EnemyConstants tuning = GameplayConstants.Current.Enemies;
|
||||
Vector2 best = groupAnchor;
|
||||
float bestDistance = float.MaxValue;
|
||||
|
||||
for (int cy = 0; cy < preset.GridHeight; cy++)
|
||||
{
|
||||
for (int cx = 0; cx < preset.GridWidth; cx++)
|
||||
{
|
||||
if (preset.GridMap[cx, cy] != BumpCombat.Stage.PresetCellType.Walkable)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Vector2 candidate = preset.CellToWorldPosition(
|
||||
zoneCenter,
|
||||
new Vector2Int(cx, cy));
|
||||
float groupDistance = Vector2.Distance(groupAnchor, candidate);
|
||||
if (groupDistance < tuning.EncounterGroupMinSpacing
|
||||
|| groupDistance > tuning.EncounterGroupMaxSpacing
|
||||
|| Vector2.Distance(playerPos, candidate) < safeDist
|
||||
|| !IsSeparatedFromOccupied(candidate, occupied, 0.8f))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
float targetDistance = Mathf.Abs(
|
||||
groupDistance
|
||||
- (tuning.EncounterGroupMinSpacing + tuning.EncounterGroupMaxSpacing) * 0.5f);
|
||||
if (targetDistance < bestDistance)
|
||||
{
|
||||
bestDistance = targetDistance;
|
||||
best = candidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestDistance < float.MaxValue
|
||||
? best
|
||||
: FindSafeSpawnPosition(
|
||||
preset,
|
||||
zoneCenter,
|
||||
playerPos,
|
||||
string.Empty,
|
||||
safeDist,
|
||||
occupied);
|
||||
}
|
||||
|
||||
private Vector2 FindSafeSpawnPosition(BumpCombat.Stage.RoomPreset preset, Vector2 zoneCenter, Vector2 playerPos,
|
||||
string enemyKind, float safeDist, List<Vector2> occupied)
|
||||
{
|
||||
BumpCombat.Stage.SpawnRole targetRole = BumpCombat.Stage.SpawnRole.NormalMelee;
|
||||
if (enemyKind.Equals("Bat", StringComparison.OrdinalIgnoreCase) || enemyKind.Equals("Slime", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
targetRole = BumpCombat.Stage.SpawnRole.Crowd;
|
||||
}
|
||||
else if (enemyKind.Contains("Archer", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
targetRole = BumpCombat.Stage.SpawnRole.Ranged;
|
||||
}
|
||||
|
||||
// 1. Check matching preset anchors first
|
||||
for (int i = 0; i < preset.SpawnPoints.Count; i++)
|
||||
{
|
||||
var pt = preset.SpawnPoints[i];
|
||||
if (pt.Role != targetRole) continue;
|
||||
|
||||
Vector2 wPos = preset.CellToWorldPosition(zoneCenter, pt.CellPosition);
|
||||
if (Vector2.Distance(playerPos, wPos) >= safeDist && IsSeparatedFromOccupied(wPos, occupied, 0.8f))
|
||||
{
|
||||
return wPos;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Search spiral from anchor or center in Walkable cells
|
||||
Vector2Int originCell = (targetRole == BumpCombat.Stage.SpawnRole.Crowd && preset.SpawnPoints.Count > 0)
|
||||
? preset.SpawnPoints.Find(p => p.Role == BumpCombat.Stage.SpawnRole.Crowd).CellPosition
|
||||
: new Vector2Int(preset.GridWidth / 2, preset.GridHeight / 2);
|
||||
|
||||
if (originCell.x < 0 || originCell.x >= preset.GridWidth) originCell = new Vector2Int(preset.GridWidth / 2, preset.GridHeight / 2);
|
||||
|
||||
int maxRadius = Mathf.Max(preset.GridWidth, preset.GridHeight);
|
||||
for (int r = 0; r <= maxRadius; r++)
|
||||
{
|
||||
for (int dy = -r; dy <= r; dy++)
|
||||
{
|
||||
for (int dx = -r; dx <= r; dx++)
|
||||
{
|
||||
if (Mathf.Abs(dx) != r && Mathf.Abs(dy) != r) continue;
|
||||
|
||||
int cx = originCell.x + dx;
|
||||
int cy = originCell.y + dy;
|
||||
if (cx < 0 || cx >= preset.GridWidth || cy < 0 || cy >= preset.GridHeight) continue;
|
||||
|
||||
if (preset.GridMap[cx, cy] != BumpCombat.Stage.PresetCellType.Walkable) continue;
|
||||
|
||||
Vector2 candidatePos = preset.CellToWorldPosition(zoneCenter, new Vector2Int(cx, cy));
|
||||
if (Vector2.Distance(playerPos, candidatePos) >= safeDist && IsSeparatedFromOccupied(candidatePos, occupied, 0.8f))
|
||||
{
|
||||
return candidatePos;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: farthest walkable cell from player
|
||||
Vector2 farthestPos = zoneCenter;
|
||||
float maxDist = -1f;
|
||||
for (int cy = 0; cy < preset.GridHeight; cy++)
|
||||
{
|
||||
for (int cx = 0; cx < preset.GridWidth; cx++)
|
||||
{
|
||||
if (preset.GridMap[cx, cy] != BumpCombat.Stage.PresetCellType.Walkable) continue;
|
||||
Vector2 p = preset.CellToWorldPosition(zoneCenter, new Vector2Int(cx, cy));
|
||||
float d = Vector2.Distance(playerPos, p);
|
||||
if (d > maxDist && IsSeparatedFromOccupied(p, occupied, 0.6f))
|
||||
{
|
||||
maxDist = d;
|
||||
farthestPos = p;
|
||||
}
|
||||
}
|
||||
}
|
||||
return farthestPos;
|
||||
}
|
||||
|
||||
private static bool IsSeparatedFromOccupied(Vector2 pos, List<Vector2> occupied, float minSeparation)
|
||||
{
|
||||
for (int i = 0; i < occupied.Count; i++)
|
||||
{
|
||||
if (Vector2.Distance(pos, occupied[i]) < minSeparation)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SpawnPresetAnchorsFallback(BumpCombat.Stage.ZoneInstance zone, Vector2 center, BumpCombat.Stage.RoomPreset preset)
|
||||
{
|
||||
foreach (var spawnPoint in preset.SpawnPoints)
|
||||
{
|
||||
if (spawnPoint.Role == BumpCombat.Stage.SpawnRole.Elite ||
|
||||
spawnPoint.Role == BumpCombat.Stage.SpawnRole.MidBoss ||
|
||||
spawnPoint.Role == BumpCombat.Stage.SpawnRole.FinalBoss)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
EnemyController prefab = ResolvePrefabForRole(spawnPoint.Role);
|
||||
if (prefab != null)
|
||||
{
|
||||
Vector2 worldPos = preset.CellToWorldPosition(center, spawnPoint.CellPosition);
|
||||
EnemyController enemy = Instantiate(prefab, worldPos, Quaternion.identity);
|
||||
enemy.transform.SetParent(zone.transform, true);
|
||||
zone.RegisterSpawnedEnemy(enemy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private EnemyController ResolvePrefabForKind(string kind)
|
||||
{
|
||||
if (string.IsNullOrEmpty(kind)) return null;
|
||||
|
||||
if (Enum.TryParse<EnemyKind>(kind, true, out EnemyKind parsedKind))
|
||||
{
|
||||
EnemyController prefab = GetPrefab(parsedKind);
|
||||
if (prefab != null) return prefab;
|
||||
}
|
||||
|
||||
string lower = kind.ToLowerInvariant();
|
||||
if (lower.Contains("bat")) return GetPrefab(EnemyKind.Bat) ?? GetRandomPrefab(EnemyRole.Crowd);
|
||||
if (lower.Contains("archer")) return GetPrefab(EnemyKind.SkeletonArcher) ?? GetRandomPrefab(EnemyRole.Normal);
|
||||
if (lower.Contains("skeleton")) return GetPrefab(EnemyKind.Skeleton) ?? GetRandomPrefab(EnemyRole.Normal);
|
||||
if (lower.Contains("slime")) return GetPrefab(EnemyKind.Slime) ?? GetRandomPrefab(EnemyRole.Crowd);
|
||||
if (lower.Contains("golem")) return GetPrefab(EnemyKind.NecroGolem) ?? GetRandomPrefab(EnemyRole.Normal);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void SpawnPresetSupportMinions(BumpCombat.Stage.ZoneInstance zone, Vector2 center, BumpCombat.Stage.RoomPreset preset)
|
||||
{
|
||||
if (preset == null || preset.SpawnPoints.Count == 0) return;
|
||||
|
||||
foreach (var pt in preset.SpawnPoints)
|
||||
{
|
||||
if (pt.Role == BumpCombat.Stage.SpawnRole.Elite ||
|
||||
pt.Role == BumpCombat.Stage.SpawnRole.MidBoss ||
|
||||
pt.Role == BumpCombat.Stage.SpawnRole.FinalBoss)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
EnemyController prefab = ResolvePrefabForRole(pt.Role);
|
||||
if (prefab != null)
|
||||
{
|
||||
Vector2 worldPos = preset.CellToWorldPosition(center, pt.CellPosition);
|
||||
EnemyController minion = Instantiate(prefab, worldPos, Quaternion.identity);
|
||||
zone.RegisterSpawnedEnemy(minion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private EnemyController ResolvePrefabForRole(BumpCombat.Stage.SpawnRole role)
|
||||
{
|
||||
switch (role)
|
||||
{
|
||||
case BumpCombat.Stage.SpawnRole.Ranged:
|
||||
return GetPrefab(EnemyKind.SkeletonArcher)
|
||||
?? GetPrefab(EnemyKind.Warlock)
|
||||
?? GetPrefab(EnemyKind.Necrofire)
|
||||
?? GetRandomPrefab(EnemyRole.Normal)
|
||||
?? (enemyPrefabs != null && enemyPrefabs.Length > 0 ? enemyPrefabs[0] : null);
|
||||
|
||||
case BumpCombat.Stage.SpawnRole.Crowd:
|
||||
return GetPrefab(EnemyKind.Bat)
|
||||
?? GetPrefab(EnemyKind.Slime)
|
||||
?? GetRandomPrefab(EnemyRole.Crowd)
|
||||
?? (enemyPrefabs != null && enemyPrefabs.Length > 0 ? enemyPrefabs[0] : null);
|
||||
|
||||
case BumpCombat.Stage.SpawnRole.NormalMelee:
|
||||
default:
|
||||
return GetPrefab(EnemyKind.Skeleton)
|
||||
?? GetPrefab(EnemyKind.Lancer)
|
||||
?? GetPrefab(EnemyKind.GreatswordSkeleton)
|
||||
?? GetRandomPrefab(EnemyRole.Normal)
|
||||
?? (enemyPrefabs != null && enemyPrefabs.Length > 0 ? enemyPrefabs[0] : null);
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (RunManager.Instance != null)
|
||||
@@ -175,6 +647,11 @@ namespace BumpCombat.Spawning
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (BumpCombat.Stage.StageManager.IsStageModeActive
|
||||
|| RunManager.Instance?.IsDefensePrototypeRun == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
TrySpawnPendingEvents();
|
||||
float elapsedTime = RunManager.Instance?.ProgressionTime ?? 0f;
|
||||
bool productionRun = RunManager.Instance?.IsProductionRun == true;
|
||||
@@ -732,11 +1209,21 @@ namespace BumpCombat.Spawning
|
||||
if (role == RunTimedEvent.MidBoss)
|
||||
{
|
||||
midBossDefeated = true;
|
||||
RunManager.Instance?.UnlockArtifactCharge();
|
||||
BumpCombat.Stage.StageManager.Instance?.HandleMajorBossDefeated(
|
||||
BumpCombat.Stage.StageNodeType.MidBoss,
|
||||
enemy.GetComponentInParent<BumpCombat.Stage.ZoneInstance>());
|
||||
}
|
||||
else if (role == RunTimedEvent.Elite)
|
||||
{
|
||||
RunManager.Instance?.UnlockGuard();
|
||||
BumpCombat.Stage.StageManager.Instance?.HandleMajorBossDefeated(
|
||||
BumpCombat.Stage.StageNodeType.Elite,
|
||||
enemy.GetComponentInParent<BumpCombat.Stage.ZoneInstance>());
|
||||
}
|
||||
else if (role == RunTimedEvent.FinalBoss)
|
||||
{
|
||||
BumpCombat.Stage.StageManager.Instance?.HandleMajorBossDefeated(
|
||||
BumpCombat.Stage.StageNodeType.FinalBoss,
|
||||
enemy.GetComponentInParent<BumpCombat.Stage.ZoneInstance>());
|
||||
}
|
||||
OnEventEnemyDefeated?.Invoke(role);
|
||||
|
||||
@@ -837,6 +1324,18 @@ namespace BumpCombat.Spawning
|
||||
return null;
|
||||
}
|
||||
|
||||
public EnemyController SpawnDefenseSkeleton(Vector2 position)
|
||||
{
|
||||
EnsureTuningLoaded();
|
||||
EnemyController prefab = GetPrefab(EnemyKind.Skeleton);
|
||||
if (prefab == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Instantiate(prefab, position, Quaternion.identity);
|
||||
}
|
||||
|
||||
private EnemyController GetPrefab(EnemyKind kind)
|
||||
{
|
||||
if (enemyPrefabs == null)
|
||||
@@ -1254,11 +1753,20 @@ namespace BumpCombat.Spawning
|
||||
return false;
|
||||
}
|
||||
|
||||
EnemyRole role = crowd ? EnemyRole.Crowd : EnemyRole.Normal;
|
||||
EnemyController[] candidates = new EnemyController[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
candidates[i] = GetRandomPrefab(role);
|
||||
if (crowd)
|
||||
{
|
||||
EnemyKind preferredKind = i % 2 == 0 ? EnemyKind.Bat : EnemyKind.Slime;
|
||||
EnemyKind fallbackKind = preferredKind == EnemyKind.Bat ? EnemyKind.Slime : EnemyKind.Bat;
|
||||
candidates[i] = GetPrefab(preferredKind) ?? GetPrefab(fallbackKind);
|
||||
}
|
||||
else
|
||||
{
|
||||
candidates[i] = GetRandomPrefab(EnemyRole.Normal);
|
||||
}
|
||||
|
||||
if (candidates[i] == null)
|
||||
{
|
||||
return false;
|
||||
@@ -1405,6 +1913,14 @@ namespace BumpCombat.Spawning
|
||||
{
|
||||
summonedEnemies.Add(enemy);
|
||||
enemy.OnDied += HandleSummonedEnemyDied;
|
||||
|
||||
EnemyController owner = enemy.SummonOwner;
|
||||
BumpCombat.Stage.ZoneInstance ownerZone =
|
||||
owner != null ? owner.GetComponentInParent<BumpCombat.Stage.ZoneInstance>() : null;
|
||||
if (ownerZone != null)
|
||||
{
|
||||
ownerZone.RegisterSpawnedEnemy(enemy);
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSummonedEnemyDied(EnemyController enemy)
|
||||
|
||||
Reference in New Issue
Block a user