예제 #1
0
        public bool CheckEnemySpriteFitInBank(List<EnemyType> currentSprites, EnemyType spriteToAdd)
        {
            List<int> currentRows = new List<int>();
            List<int> currentAddresses = new List<int>();

            foreach (EnemyType e in currentSprites)
            {
                // Return false if enemy is already in the list
                if (spriteToAdd.ID == e.ID)
                {
                    return false;
                }

                // Return false if the room restricts changing

                // Add the candidate enemy's sprite bank rows and pattern table addresses to their owns lists
                for (int i = 0; i < e.SpriteBankRows.Count; i++)
                {
                    currentRows.Add(e.SpriteBankRows[i]);
                }
                for (int i = 0; i < e.PatternTableAddresses.Count; i++)
                {
                    currentAddresses.Add(e.PatternTableAddresses[i]);
                }
            }

            for (int i = 0; i < currentRows.Count; i++)
            {
                for (int j = 0; j < spriteToAdd.SpriteBankRows.Count; j++)
                {
                    if (currentRows[i] == spriteToAdd.SpriteBankRows[j])
                    {
                        if (currentAddresses[i * 2] == spriteToAdd.PatternTableAddresses[j * 2] &&
                            currentAddresses[i * 2 + 1] == spriteToAdd.PatternTableAddresses[j * 2 + 1])
                        {
                            // This enemy contains the same pattern table address as the one in the list, add it
                        }
                        else
                        {
                            return false;
                        }
                    }
                }
            }

            return true;
        }
예제 #2
0
        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
        }
예제 #3
0
        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);
        }