/// <summary> /// Read enemylist.csv to construct EnemyInstances. /// </summary> private void ReadEnemyInstancesFromFile() { string[] lines = Properties.Resources.enemylist.Split(new string[] { Environment.NewLine }, StringSplitOptions.None); foreach (string line in lines) { if (line.StartsWith("#")) { continue; // Ignore comment lines } string[] cols = line.Split(new char[] { ',' }); EnemyInstance enemy = new EnemyInstance( Convert.ToInt32(cols[0], 16), // Index Convert.ToInt32(cols[1], 16), // StageNum Convert.ToInt32(cols[2], 16), // RoomNum Convert.ToInt32(cols[3], 16), // ScreenNum Convert.ToBoolean(cols[4]), // IsActive Convert.ToInt32(cols[5], 16), // EnemyID Convert.ToInt32(cols[6], 16), // XPosOriginal Convert.ToInt32(cols[7], 16), // YPosOriginal Convert.ToInt32(cols[8], 16), // YPosAir Convert.ToInt32(cols[9], 16), // YPosGround Convert.ToBoolean(cols[10])); // FaceRight EnemyInstances.Add(enemy); } }
/// <summary> /// Read enemylist.csv to construct EnemyInstances. /// </summary> private void ReadEnemyInstancesFromFile() { EnemySet enemySet = Properties.Resources.EnemySet.Deserialize <EnemySet>(); foreach (Enemy enemy in enemySet) { EnemyInstance enemyInstance = new EnemyInstance( Convert.ToInt32(enemy.Index, 16), Convert.ToInt32(enemy.StageNumber, 16), Convert.ToInt32(enemy.RoomNumber, 16), Convert.ToInt32(enemy.ScreenNumber, 16), enemy.IsActive, Convert.ToInt32(enemy.EnemyId, 16), Convert.ToInt32(enemy.PositionX, 16), Convert.ToInt32(enemy.PositionY, 16), Convert.ToInt32(enemy.PositionAir, 16), Convert.ToInt32(enemy.PositionGround, 16), enemy.FaceRight); EnemyInstances.Add(enemyInstance); } }
private void Execute(Patch Patch, Random r) { foreach (SpriteBankRoomGroup sbrg in RoomGroups) { // Skip processing the room if every sprite bank row is taken if (sbrg.IsSpriteRestricted && sbrg.SpriteBankRowsRestriction.Count >= 6) { continue; } // Create valid random combination of enemies to place List <EnemyType> newEnemies = GenerateEnemyCombinations(sbrg, r); // No enemy can fit in this room for some reason, skip this room (GFX will be glitched) if (newEnemies.Count == 0) { continue; } // For each enemy ID (in the room, in the room-group), change to a random enemy from the new set for (int i = 0; i < sbrg.Rooms.Count; i++) { Room room = sbrg.Rooms[i]; for (int j = 0; j < room.EnemyInstances.Count; j++) { EnemyInstance instance = room.EnemyInstances[j]; int randomIndex = r.Next(newEnemies.Count); EnemyType newEnemyType = newEnemies[randomIndex]; byte newId = (byte)newEnemyType.ID; // When placing the last enemy, If room contains an activator, manually change the last spawn in the room to be its deactivator if (j == room.EnemyInstances.Count - 1) { EEnemyID?activator = room.GetActivatorIfOneHasBeenAdded(); if (activator != null) { newId = (byte)EnemyType.GetCorrespondingDeactivator((EEnemyID)activator); } // Also, if this last instance is an activator, try to replace it if (EnemyType.CheckIsActivator(newId)) { newId = TryReplaceActivator(newEnemies, newId); // Update the new enemy type because it may require different graphics if (!EnemyType.CheckIsDeactivator(newId)) { newEnemyType = newEnemies.Where(x => (byte)x.ID == newId).First(); } } } sbrg.NewEnemyTypes.Add(newEnemyType); // TODO: This all should be refactored. Use a hashtable of EnemyTypes and abolish "EnemyID". // If room contains only this one enemy and it is an activator // TODO: How does Clash stage work with the Pipis? They don't break normally. if ((room.EnemyInstances.Count == 1 && instance.HasNewActivator())) { // Try to replace it with a non-activator enemy //newId = TryReplaceActivator(newEnemies, newId); } // Last-minute adjustments to certain enemy spawns switch ((EEnemyID)newId) { case EEnemyID.Shrink: double randomSpawner = r.NextDouble(); if (randomSpawner < CHANCE_SHRINKSPAWNER) { newId = (byte)EEnemyID.Shrink_Spawner; } break; case EEnemyID.Shotman_Left: if (instance.IsFaceRight) { newId = (byte)EEnemyID.Shotman_Right; } break; default: break; } // Update object with new ID for future use room.EnemyInstances[j].EnemyID = newId; // Change the enemy ID in the ROM int IDposition = Stage0EnemyIDAddress + instance.StageNum * StageLength + instance.Offset; Patch.Add(IDposition, newId, $"{sbrg.Stage.ToString("G")} Stage Enemy #{instance.Offset} ID (Room {instance.RoomNum}) {((EEnemyID)instance.EnemyID).ToString("G")}"); // Change the enemy Y pos based on Air or Ground category int newY = newEnemyType.YAdjust; newY += (newEnemyType.IsYPosAir) ? instance.YAir : instance.YGround; IDposition = Stage0EnemyYAddress + instance.StageNum * StageLength + instance.Offset; Patch.Add(IDposition, (byte)newY, $"{sbrg.Stage.ToString("G")} Stage Enemy #{instance.Offset} Y (Room {instance.RoomNum}) {((EEnemyID)instance.EnemyID).ToString("G")}"); } } // Change sprite banks for the room foreach (EnemyType e in sbrg.NewEnemyTypes) { for (int i = 0; i < e.SpriteBankRows.Count; i++) { int rowInSlotAddress = sbrg.PatternAddressStart + e.SpriteBankRows[i] * 2; int patternTblPtr1 = e.PatternTableAddresses[2 * i]; int patternTblPtr2 = e.PatternTableAddresses[2 * i + 1]; Patch.Add(rowInSlotAddress, (byte)patternTblPtr1, $"{sbrg.Stage.ToString("G")} Stage Sprite Bank Slot ? Row {e.SpriteBankRows[i]} Indirect Address 1"); Patch.Add(rowInSlotAddress + 1, (byte)patternTblPtr2, $"{sbrg.Stage.ToString("G")} Stage Sprite Bank Slot ? Row {e.SpriteBankRows[i]} Indirect Address 2"); } } } // end foreach sbrg }
private void InitializeRooms() { // First, create a list of every room-group that refers to a specifc Sprite Bank Slot. // Heatman & Wily 1 stage enemies // NOTE: Can only use sprite banks 0-5 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.HeatW1, 0x003470, new int[] { 0, 12 })); // Bank 0 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.HeatW1, 0x003482, new int[] { 3, 8, 9, 10 })); // Bank 1 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.HeatW1, 0x003494, new int[] { 1, 2 }, // Bank 2 new int[] { 3 }, new byte[] { 0x97, 0x03 })); // Force Yoku blocks // Heat Bank 3 - Heat fight 0x0034A6 // Heat Bank 4 - Dragon fight RoomGroups.Add(new SpriteBankRoomGroup(EStageID.HeatW1, 0x0034ca, new int[] { 7 })); // Bank 5 // Airman & Wily 2 stage enemies RoomGroups.Add(new SpriteBankRoomGroup(EStageID.AirW2, 0x007470, new int[] { 0 })); // Bank 0 - Lightning Goro room RoomGroups.Add(new SpriteBankRoomGroup(EStageID.AirW2, 0x007482, new int[] { 2 })); // Bank 1 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.AirW2, 0x007494, new int[] { 1 })); // Bank 2 // Air Bank 3 - Air fight 0x0074A6 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.AirW2, 0x0074b8, new int[] { 5 })); // Bank 4 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.AirW2, 0x0074ca, new int[] { 7 })); // Bank 5 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.AirW2, 0x0074dc, new int[] { 9 })); // Bank 6 // Air Bank 7 - Picopico-kun fight // Woodman & Wily 3 stage enemies // NOTE: Access to sprite banks 0-7, plus extra banks 0x90 and 0xA2 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.WoodW3, 0x00b470, new int[] { 10, 22 })); // Bank 0; Moved Room 10 from bank 3 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.WoodW3, 0x00B482, new int[] { 1, 6 })); // Bank 1 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.WoodW3, 0x00B494, new int[] { 7 })); // Bank 2 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.WoodW3, 0x00B4A6, new int[] { 0 })); // Bank 3 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.WoodW3, 0x00B4B8, new int[] { 11 })); // Bank 4 // Rooms.Add(new EnemyRoom(EStageID.WoodW3, 0x00B4CA, new int[] { 2, 3, 4 })); // Bank 5 - Friender rooms // Wood Bank 6 - Wood fight 0x00B4DC // Wood Bank 7 - Gutsdozer fight RoomGroups.Add(new SpriteBankRoomGroup(EStageID.WoodW3, 0x00b500, new int[] { 8, 16 })); // Bank ? (0x90); Moved Room 8 from bank 3 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.WoodW3, 0x00b512, new int[] { 9, 17 })); // Bank ? (0xA2); Moved Room 9 from bank 3 // Bubbleman & Wily 4 stage enemies RoomGroups.Add(new SpriteBankRoomGroup(EStageID.BubbleW4, 0x00F470, new int[] { 0, 5 }, // Bank 0 new int[] { 2 }, new byte[] { 0x9D, 0x02 })); // Falling platform sprite RoomGroups.Add(new SpriteBankRoomGroup(EStageID.BubbleW4, 0x00F482, new int[] { 1, 2, 3 })); // Bank 1 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.BubbleW4, 0x00F494, new int[] { 4 }, // Bank 2 new int[] { 0, 1 }, new byte[] { 0x9E, 0x02, 0x9F, 0x02 })); // Shrimp sprites // Bubble Bank 3 - Bubbleman fight 0x00F4A6 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.BubbleW4, 0x00f4b8, new int[] { 9, 10, 13 })); // Bank 4 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.BubbleW4, 0x00f4ca, new int[] { 15, 17 }, // Bank 5 new int[] { 3 }, new byte[] { 0x95, 0x03 })); // Moving platform sprite RoomGroups.Add(new SpriteBankRoomGroup(EStageID.BubbleW4, 0x00f4dc, new int[] { 19 })); // Bank 6 // Quick // Quick Bank 0 - Used in empty room only RoomGroups.Add(new SpriteBankRoomGroup(EStageID.QuickW5, 0x013482, new int[] { 7 })); // Bank 1 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.QuickW5, 0x013494, new int[] { 15 })); // Bank 2 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.QuickW5, 0x0134A6, new int[] { 3, 4, 5, 8, 9, 10, 11, 12, 13, 14 })); // Bank 3 // Quick Bank 4 - Quick fight // 0x0134B8 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.QuickW5, 0x0134CA, new int[] { 1, 2 })); // Bank 5 // Quick Bank 6 - W5 Teleporters // Quick Bank 7 - Wily Machine // Flash RoomGroups.Add(new SpriteBankRoomGroup(EStageID.FlashW6, 0x017470, new int[] { 0, 3, 5 })); // Bank 0 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.FlashW6, 0x017482, new int[] { 1, 6, 7 })); // Bank 1 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.FlashW6, 0x017494, new int[] { 2, 4 })); // Bank 2; Moved room 2 from bank 0 // Flash Bank 3: Flashman fight 0x0174A6 // Flash Bank 4: W6 Alien fight // Flash Bank 5: Wily defeated cutscene? // Flash Bank 6: Droplets // Metal RoomGroups.Add(new SpriteBankRoomGroup(EStageID.Metal, 0x01B470, new int[] { 0, 1 })); RoomGroups.Add(new SpriteBankRoomGroup(EStageID.Metal, 0x01B482, new int[] { 2 })); // Metal fight 0x01B494 // Clash RoomGroups.Add(new SpriteBankRoomGroup(EStageID.Clash, 0x01f494, new int[] { 0, 3, 4, 5 }, new int[] { 3 }, new byte[] { 0x95, 0x03 })); // Moving platform sprites RoomGroups.Add(new SpriteBankRoomGroup(EStageID.Clash, 0x01f482, new int[] { 2, 8, 9 })); RoomGroups.Add(new SpriteBankRoomGroup(EStageID.Clash, 0x01f4a6, new int[] { 6, 7 })); RoomGroups.Add(new SpriteBankRoomGroup(EStageID.Clash, 0x01f470, new int[] { 10, 11, 12 })); RoomGroups.Add(new SpriteBankRoomGroup(EStageID.Clash, 0x01f4b8, new int[] { 1 })); // Slot 4, changed from empty room 13 to room 1 RoomGroups.Add(new SpriteBankRoomGroup(EStageID.Clash, 0x01f4ca, new int[] { 14 })); // Clash fight 0x01F4DC // Get copy of enemy spawn list to save time List <EnemyInstance> usedInstances = new List <EnemyInstance>(EnemyInstances); // First, loop back through entire list of room-groups. For each, loop through entire list of // enemies, match them and assign them to their room-group. Assigned enemies are removed from // the list, reducing the search time on each loop. For now, completely discard all enemy spawns // that are reserved (i.e. Yoku blocks in Heat, beams in Quick). foreach (SpriteBankRoomGroup sbrg in RoomGroups) { int stageNum = (int)sbrg.Stage; // Find index of first enemy instance in the stage int i = 0; for (i = 0; i < usedInstances.Count; i++) { if (usedInstances.ElementAt(i).StageNum == stageNum) { break; } } // Check each applicable room number for this room group for (int roomIndex = 0; roomIndex < sbrg.Rooms.Count; roomIndex++) { Room room = sbrg.Rooms[roomIndex]; // Find first occurrence of this room num in enemy list (starting at this stage) int j = 0; while (i + j < usedInstances.Count) { EnemyInstance checkEnemy = usedInstances.ElementAt(i + j); // Only add non-required enemy instances to the randomizer if (checkEnemy.IsActive) { // Enemy instance stage/room num match one described in a SpriteBankRoomGroup if (checkEnemy.RoomNum == room.RoomNum && checkEnemy.StageNum == stageNum) { // Add enemy to room group room.EnemyInstances.Add(checkEnemy); // Remove enemy from temporary list usedInstances.RemoveAt(i + j); } else { // Only inc j here since an element hasn't been removed j++; } } else { // Remove unrandomizable enemy from temporary list usedInstances.RemoveAt(i + j); } } // end while } // end foreach roomNum in sbrg } // end foreach sbrg }
/// <summary> /// Read enemylist.csv to construct EnemyInstances. /// </summary> private void ReadEnemyInstancesFromFile() { using (StreamReader sr = new StreamReader("enemylist.csv")) { while (!sr.EndOfStream) { string line = sr.ReadLine(); if (line.StartsWith("#")) continue; // Ignore comment lines string[] cols = line.Split(new char[] { ',' }); EnemyInstance enemy = new EnemyInstance( Convert.ToInt32(cols[0], 16), // Index Convert.ToInt32(cols[1], 16), // StageNum Convert.ToInt32(cols[2], 16), // RoomNum Convert.ToInt32(cols[3], 16), // ScreenNum Convert.ToBoolean(cols[4]), // IsActive Convert.ToInt32(cols[5], 16), // EnemyID Convert.ToInt32(cols[6], 16), // XPosOriginal Convert.ToInt32(cols[7], 16), // YPosOriginal Convert.ToInt32(cols[8], 16), // YPosAir Convert.ToInt32(cols[9], 16), // YPosGround Convert.ToBoolean(cols[10])); // FaceRight EnemyInstances.Add(enemy); } } }