private List <EnemyType> GenerateEnemyCombinations(SpriteBankRoomGroup sbrg, Random r) { // Create a random enemy set List <EnemyType> NewEnemies = new List <EnemyType>(); List <EnemyType> PotentialEnemies = new List <EnemyType>(); bool done = false; bool hasActivator = false; while (!done) { foreach (EnemyType en in EnemyTypes) { // 1. Skip enemies that have exceeded the type's maximum // 2. Reduce the overall chance of certain types appearing by randomly skipping them // 3. Limit a room set to having at most one Activator enemy type double chance = 0.0; switch (en.ID) { case EEnemyID.Pipi_Activator: if (numPipis >= MAX_PIPIS) { continue; } chance = r.NextDouble(); if (chance > CHANCE_PIPI) { continue; } break; case EEnemyID.Mole_Activator: if (numMoles >= MAX_MOLES) { continue; } chance = r.NextDouble(); if (chance > CHANCE_MOLE) { continue; } break; case EEnemyID.M445_Activator: if (numM445s > MAX_M445S) { continue; } chance = r.NextDouble(); if (chance > CHANCE_M445) { continue; } break; case EEnemyID.Telly: chance = r.NextDouble(); if (chance > CHANCE_TELLY) { continue; } break; case EEnemyID.Springer: chance = r.NextDouble(); if (chance > CHANCE_SPRINGER) { continue; } break; default: break; } // Skip any additional activator enemies if (en.IsActivator && hasActivator) { continue; } // Reject certain enemy types for certain stages or rooms switch (sbrg.Stage) { case EStageID.HeatW1: // Moles don't display correctly in Heat or Wily 1. Also too annoying in Heat Yoku room. if (en.ID == EEnemyID.Mole_Activator) { continue; } // Reject Pipis appearing in Yoku block room if (en.ID == EEnemyID.Pipi_Activator && sbrg.ContainsRoom(2)) { continue; } // Reject M445s appearing in Yoku block room if (en.ID == EEnemyID.M445_Activator && sbrg.ContainsRoom(2)) { continue; } // Press doesn't display correctly in Wily 1 if (en.ID == EEnemyID.Press && sbrg.Rooms.Last().RoomNum >= 7) { continue; } break; case EStageID.AirW2: // Moles don't display correctly in Heat if (en.ID == EEnemyID.Mole_Activator && sbrg.Rooms[0].RoomNum < 7) { continue; } break; case EStageID.WoodW3: // Moles and Press don't display in Wood outside room if (en.ID == EEnemyID.Mole_Activator && sbrg.ContainsRoom(7)) { continue; } if (en.ID == EEnemyID.Press && sbrg.ContainsRoom(7)) { continue; } // Don't spawn Springer, Blocky, or Press underwater if (en.ID == EEnemyID.Springer && sbrg.ContainsRoom(0x11)) { continue; } if (en.ID == EEnemyID.Blocky && sbrg.ContainsRoom(0x11)) { continue; } if (en.ID == EEnemyID.Press && sbrg.ContainsRoom(0x11)) { continue; } break; case EStageID.BubbleW4: // Moles don't display correctly in Bubble if (en.ID == EEnemyID.Mole_Activator && sbrg.Rooms[0].RoomNum < 9) { continue; } // Press doesn't display correctly in Bubble if (en.ID == EEnemyID.Press && sbrg.Rooms[0].RoomNum < 9) { continue; } // Don't spawn Springer or Blocky underwater if (en.ID == EEnemyID.Springer && (sbrg.ContainsRoom(3) || sbrg.ContainsRoom(4))) { continue; } if (en.ID == EEnemyID.Blocky && (sbrg.ContainsRoom(3) || sbrg.ContainsRoom(4))) { continue; } break; case EStageID.Clash: // Mole bad GFX if (en.ID == EEnemyID.Mole_Activator) { continue; } // Press bad GFX if (en.ID == EEnemyID.Press) { continue; } break; default: break; } // If room has sprite restrictions, check if this enemy's sprite can be used // (i.e. certain rooms must use certain rows on the sprite table to draw mandatory objects or effects if (sbrg.IsSpriteRestricted) { // Check if this enemy uses the restricted row in the sprite bank List <int> commonRows = en.SpriteBankRows.Intersect(sbrg.SpriteBankRowsRestriction).ToList(); if (commonRows.Count != 0) { bool reject = false; for (int i = 0; i < en.SpriteBankRows.Count; i++) { int enemyRow = en.SpriteBankRows[i]; int indexOfRow = sbrg.SpriteBankRowsRestriction.IndexOf(enemyRow); // For a restricted sprite bank row, see if enemy uses the same sprite pattern if (indexOfRow > -1) { if (en.PatternTableAddresses[i * 2] == sbrg.PatternTableAddressesRestriction[indexOfRow * 2] && en.PatternTableAddresses[i * 2 + 1] == sbrg.PatternTableAddressesRestriction[indexOfRow * 2 + 1]) { // Enemy and the restricted sprite use the same pattern table, allow it // (do nothing) } else { // Enemy draws with this row, but using a different set of graphics. reject it. reject = true; break; } } } if (reject) { continue; } } } // Check if this enemy would fit in the sprite bank, given other new enemies already added if (CheckEnemySpriteFitInBank(NewEnemies, en)) { // Add enemy to set of possible enemies to place in PotentialEnemies.Add(en); } } // Unable to add any more enemies, done if (PotentialEnemies.Count == 0) { done = true; } else { // Choose a new enemy to add to the set from all possible new enemies to add EnemyType newEnemy = PotentialEnemies[r.Next(PotentialEnemies.Count)]; NewEnemies.Add(newEnemy); PotentialEnemies.Clear(); // Increase total count of certain enemy types to limit their appearance later switch (newEnemy.ID) { case EEnemyID.Pipi_Activator: numPipis++; break; case EEnemyID.Mole_Activator: numMoles++; break; case EEnemyID.M445_Activator: numM445s++; break; default: break; } // Flag the new enemy set as having an activator so that no more will be added if (newEnemy.IsActivator) { hasActivator = true; } } } return(NewEnemies); }
private List<EnemyType> GenerateEnemyCombinations(SpriteBankRoomGroup sbrg, Random r) { // Create a random enemy set List<EnemyType> NewEnemies = new List<EnemyType>(); List<EnemyType> PotentialEnemies = new List<EnemyType>(); bool done = false; bool hasActivator = false; while (!done) { foreach (EnemyType en in EnemyTypes) { // 1. Skip enemies that have exceeded the type's maximum // 2. Reduce the overall chance of certain types appearing by randomly skipping them // 3. Limit a room set to having at most one Activator enemy type double chance = 0.0; switch (en.ID) { case EEnemyID.Pipi_Activator: if (numPipis >= MAX_PIPIS) continue; chance = r.NextDouble(); if (chance > CHANCE_PIPI) continue; break; case EEnemyID.Mole_Activator: if (numMoles >= MAX_MOLES) continue; chance = r.NextDouble(); if (chance > CHANCE_MOLE) continue; break; case EEnemyID.Telly: chance = r.NextDouble(); if (chance > CHANCE_TELLY) continue; break; case EEnemyID.Springer: chance = r.NextDouble(); if (chance > CHANCE_SPRINGER) continue; break; default: break; } // Skip any additional activator enemies if (en.IsActivator && hasActivator) { continue; } // Reject certain enemy types for certain stages or rooms switch (sbrg.Stage) { case EStageID.HeatW1: // Moles don't display correctly in Heat or Wily 1. Also too annoying in Heat Yoku room. if (en.ID == EEnemyID.Mole_Activator) continue; // Reject Pipis appearing in Yoku block room if (en.ID == EEnemyID.Pipi_Activator && sbrg.ContainsRoom(2)) continue; // Press doesn't display correctly in Wily 1 if (en.ID == EEnemyID.Press && sbrg.Rooms.Last().RoomNum >= 7) continue; break; case EStageID.AirW2: // Moles don't display correctly in Heat if (en.ID == EEnemyID.Mole_Activator && sbrg.Rooms[0].RoomNum < 7) continue; break; case EStageID.WoodW3: // Moles and Press don't display in Wood outside room if (en.ID == EEnemyID.Mole_Activator && sbrg.ContainsRoom(7)) continue; if (en.ID == EEnemyID.Press && sbrg.ContainsRoom(7)) continue; // Don't spawn Springer, Blocky, or Press underwater if (en.ID == EEnemyID.Springer && sbrg.ContainsRoom(11)) continue; if (en.ID == EEnemyID.Blocky && sbrg.ContainsRoom(11)) continue; if (en.ID == EEnemyID.Press && sbrg.ContainsRoom(11)) continue; break; case EStageID.BubbleW4: // Moles don't display correctly in Bubble if (en.ID == EEnemyID.Mole_Activator && sbrg.Rooms[0].RoomNum < 9) continue; // Press doesn't display correctly in Bubble if (en.ID == EEnemyID.Press && sbrg.Rooms[0].RoomNum < 9) continue; // Don't spawn Springer or Blocky underwater if (en.ID == EEnemyID.Springer && (sbrg.ContainsRoom(3) || sbrg.ContainsRoom(4))) continue; if (en.ID == EEnemyID.Blocky && (sbrg.ContainsRoom(3) || sbrg.ContainsRoom(4))) continue; break; case EStageID.Clash: // Mole bad GFX if (en.ID == EEnemyID.Mole_Activator) continue; // Press bad GFX if (en.ID == EEnemyID.Press) continue; break; default: break; } // If room has sprite restrictions, check if this enemy's sprite can be used // (i.e. certain rooms must use certain rows on the sprite table to draw mandatory objects or effects if (sbrg.IsSpriteRestricted) { // Check if this enemy uses the restricted row in the sprite bank List<int> commonRows = en.SpriteBankRows.Intersect(sbrg.SpriteBankRowsRestriction).ToList(); if (commonRows.Count != 0) { bool reject = false; for (int i = 0; i < en.SpriteBankRows.Count; i++) { int enemyRow = en.SpriteBankRows[i]; int indexOfRow = sbrg.SpriteBankRowsRestriction.IndexOf(enemyRow); // For a restricted sprite bank row, see if enemy uses the same sprite pattern if (indexOfRow > -1) { if (en.PatternTableAddresses[i * 2] == sbrg.PatternTableAddressesRestriction[indexOfRow * 2] && en.PatternTableAddresses[i * 2 + 1] == sbrg.PatternTableAddressesRestriction[indexOfRow * 2 + 1]) { // Enemy and the restricted sprite use the same pattern table, allow it // (do nothing) } else { // Enemy draws with this row, but using a different set of graphics. reject it. reject = true; break; } } } if (reject) { continue; } } } // Check if this enemy would fit in the sprite bank, given other new enemies already added if (CheckEnemySpriteFitInBank(NewEnemies, en)) { // Add enemy to set of possible enemies to place in PotentialEnemies.Add(en); } } // Unable to add any more enemies, done if (PotentialEnemies.Count == 0) { done = true; } else { // Choose a new enemy to add to the set from all possible new enemies to add EnemyType newEnemy = PotentialEnemies[r.Next(PotentialEnemies.Count)]; NewEnemies.Add(newEnemy); PotentialEnemies.Clear(); // Increase total count of certain enemy types to limit their appearance later switch (newEnemy.ID) { case EEnemyID.Pipi_Activator: numPipis++; break; case EEnemyID.Mole_Activator: numMoles++; break; default: break; } // Flag the new enemy set as having an activator so that no more will be added if (newEnemy.IsActivator) { hasActivator = true; } } } return NewEnemies; }