public void Randomize(Patch patch, Random r) { // Create list of default teleporter position values List<byte[]> coords = new List<byte[]> { new byte[]{ 0x20, 0x3B }, // Teleporter X, Y (top-left) new byte[]{ 0x20, 0x7B }, new byte[]{ 0x20, 0xBB }, new byte[]{ 0x70, 0xBB }, new byte[]{ 0x90, 0xBB }, new byte[]{ 0xE0, 0x3B }, new byte[]{ 0xE0, 0x7B }, new byte[]{ 0xE0, 0xBB } }; // Randomize them coords.Shuffle(r); // Write the new x-coordinates for (int i = 0; i < coords.Count; i++) { byte[] location = coords[i]; patch.Add((int)(EMiscAddresses.WarpXCoordinateStartAddress + i), location[0], String.Format("Teleporter {0} X-Pos", i)); } // Write the new y-coordinates for (int i = 0; i < coords.Count; i++) { byte[] location = coords[i]; patch.Add((int)(EMiscAddresses.WarpYCoordinateStartAddress + i), location[1], String.Format("Teleporter {0} Y-Pos", i)); } // These values will be copied over to $04b0 (y) and $0470 (x), which will be checked // for in real time to determine where Mega will teleport to }
/// <summary> /// This method makes some preliminary modifications to the Mega Man 2 ROM to increase the enemy variety /// by changing the sprite banks used by certain rooms. /// </summary> public void ChangeRoomSpriteBankSlots(Patch p) { p.Add(0x00b444, 0x90, "Custom Sprite Bank: Wood 9th room: slot 3->? (0x90 special slot)"); p.Add(0x00b445, 0xa2, "Custom Sprite Bank: Wood 10th room: slot 3->? (0xa2 special slot)"); p.Add(0x00b446, 0x00, "Custom Sprite Bank: Wood 11th room: slot 3->0"); p.Add(0x01743e, 0x24, "Custom Sprite Bank: Flash 3rd room: slot 0->2"); p.Add(0x01f43d, 0x48, "Custom Sprite Bank: Clash 2nd room: slot 2->7"); }
private static void ChangeW4FloorsSpikePit(Patch Patch, Random r) { // 5 tiles, but since two adjacent must construct a gap, 4 possible gaps. Choose 1 random gap. int gap = r.Next(4); for (int i = 0; i < 4; i++) { if (i == gap) { Patch.Add(0x00CB9A + i * 8, 0x9B, String.Format("Wily 4 Room 5 Tile {0} (gap on right)", i)); Patch.Add(0x00CB9A + i * 8 + 8, 0x9C, String.Format("Wily 4 Room 5 Tile {0} (gap on left)", i)); ++i; // skip next tile since we just drew it } else { Patch.Add(0x00CB9A + i * 8, 0x9D, String.Format("Wily 4 Room 5 Tile {0} (solid)", i)); } } }
public void RandomizeAndWrite(Patch patch, Random rand, int setNumber) { Index = rand.Next(ColorBytes.Count); for (int i = 0; i < addresses.Length; i++) { patch.Add(addresses[i], (byte)ColorBytes[Index][i], String.Format("Color Set {0} (Index Chosen: {1}) Value #{2}", setNumber, Index, i)); } }
public void Randomize(Patch p, Random r) { List<EMusicID> newBGMOrder = new List<EMusicID>(); List<EMusicID> robos = new List<EMusicID>(); // Select 2 replacement tracks for the 2 extra instance of the boring W3/4/5 theme robos.Add(EMusicID.Flash); robos.Add(EMusicID.Heat); robos.Add(EMusicID.Air); robos.Add(EMusicID.Wood); robos.Add(EMusicID.Quick); robos.Add(EMusicID.Metal); robos.Add(EMusicID.Clash); robos.Add(EMusicID.Bubble); robos.Shuffle(r); newBGMOrder.Add(EMusicID.Flash); newBGMOrder.Add(EMusicID.Heat); newBGMOrder.Add(EMusicID.Air); newBGMOrder.Add(EMusicID.Wood); newBGMOrder.Add(EMusicID.Quick); newBGMOrder.Add(EMusicID.Metal); newBGMOrder.Add(EMusicID.Clash); newBGMOrder.Add(EMusicID.Bubble); newBGMOrder.Add(EMusicID.Wily12); newBGMOrder.Add(EMusicID.Wily12); // Wily 1/2 track will play twice newBGMOrder.Add(EMusicID.Wily345); // Wily 3/4/5 track only plays once newBGMOrder.Add(robos[0]); // Add extra Robot Master tracks to the set newBGMOrder.Add(robos[1]); // Randomize tracks newBGMOrder.Shuffle(r); // Start writing at Heatman BGM ID, both J and U // Loop through BGM addresses Heatman to Wily 5 (Wily 6 still silent) for (int i = 0; i < newBGMOrder.Count; i++) { EMusicID bgm = newBGMOrder[i]; p.Add(0x0381E0 + i, (byte)bgm, String.Format("BGM Stage {0}", i)); } // Finally, fix Wily 5 track when exiting a Teleporter to be the selected Wily 5 track instead of default p.Add(0x038489, (byte)newBGMOrder.Last(), "BGM Wily 5 Teleporter Exit Fix"); }
private static void ChangeW4FloorsBeforeSpikes(Patch Patch, Random r) { // Choose 2 of the 5 32x32 tiles to be fake int tileA = r.Next(5); int tileB = r.Next(4); // Make sure 2nd tile chosen is different if (tileB == tileA) tileB++; for (int i = 0; i < 5; i++) { if (i == tileA || i == tileB) { Patch.Add(0x00CB5C + i * 8, 0x94, String.Format("Wily 4 Room 4 Tile {0} (fake)", i)); } else { Patch.Add(0x00CB5C + i * 8, 0x85, String.Format("Wily 4 Room 4 Tile {0} (solid)", i)); } } }
public static void DrawTitleScreenChanges(Patch p, int seed) { // Draw version number System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly(); string version = assembly.GetName().Version.ToString(); for (int i = 0; i < version.Length; i++) { byte value = TitleChars.GetChar(version[i]).ID; p.Add(0x0373C7 + i, value, String.Format("Title Screen Version Number")); } // Draw seed string seedAlpha = SeedConvert.ConvertBase10To26(seed); for (int i = 0; i < seedAlpha.Length; i++) { char ch = seedAlpha.ElementAt(i); byte charIndex = (byte)(Convert.ToByte(ch) - Convert.ToByte('A')); // 'A' starts at C1 in the pattern table p.Add(0x037387 + i, (byte)(0xC1 + charIndex), String.Format("Title Screen Seed")); } }
/// <summary> /// Enabling Random Weapons or Random Stages will cause the wrong Robot Master portrait to /// be blacked out when a stage is completed. The game uses your acquired weapons to determine /// which portrait to black-out. This function changes the lookup table for x and y positions /// of portraits to black-out based on what was randomized. /// </summary> public static void FixPortraits(Patch Patch, bool is8StagesRandom, RStages randomStages, bool isWeaponGetRandom, RWeaponGet randomWeaponGet) { // Arrays of default values for X and Y of the black square that marks out each portrait // Index of arrays are stage order, e.g. Heat, Air, etc. byte[] portraitBG_y = new byte[] { 0x21, 0x20, 0x21, 0x20, 0x20, 0x22, 0x22, 0x22 }; byte[] portraitBG_x = new byte[] { 0x86, 0x8E, 0x96, 0x86, 0x96, 0x8E, 0x86, 0x96 }; // Adjusting the sprites is not necessary because the hacked portrait graphics ("?" images) // only use the background, and the sprites have been blacked out. Left in for reference. //byte[] portraitSprite_x = new byte[] { 0x3C, 0x0C, 0x4C, 0x00, 0x20, 0x84, 0x74, 0xA4 }; //byte[] portraitSprite_y = new byte[] { 0x10, 0x14, 0x28, 0x0C, 0x1C, 0x20, 0x10, 0x18 }; // Apply changes to portrait arrays based on shuffled stages if (is8StagesRandom) { randomStages.FixPortraits(ref portraitBG_x, ref portraitBG_y); } // Apply changes to portrait arrays based on shuffled weapons. Only need a standard "if" with no "else", // because the arrays must be permuted twice if both randomization settings are enabled. if (isWeaponGetRandom) { randomWeaponGet.FixPortraits(ref portraitBG_x, ref portraitBG_y); } for (int i = 0; i < 8; i++) { Patch.Add(0x034541 + i, portraitBG_y[i], String.Format("Stage Select Portrait {0} Y-Pos Fix", i + 1)); Patch.Add(0x034549 + i, portraitBG_x[i], String.Format("Stage Select Portrait {0} X-Pos Fix", i + 1)); // Changing this sprite table misplaces their positions by default. //stream.Position = 0x03460D + i; //stream.WriteByte(portraitSprite_y[i]); //stream.Position = 0x034615 + i; //stream.WriteByte(portraitSprite_x[i]); } }
/// <summary> /// Shuffle which Robot Master awards Items 1, 2, and 3. /// </summary> public void Randomize(Patch patch, Random r) { // 0x03C291 - Item # from Heat Man // 0x03C292 - Item # from Air Man // 0x03C293 - Item # from Wood Man // 0x03C294 - Item # from Bubble Man // 0x03C295 - Item # from Quick Man // 0x03C296 - Item # from Flash Man // 0x03C297 - Item # from Metal Man // 0x03C298 - Item # from Crash Man List<EItemNumber> newItemOrder = new List<EItemNumber>(); for (byte i = 0; i < 5; i++) newItemOrder.Add(EItemNumber.None); newItemOrder.Add(EItemNumber.One); newItemOrder.Add(EItemNumber.Two); newItemOrder.Add(EItemNumber.Three); newItemOrder.Shuffle(r); for (int i = 0; i < 8; i++) { patch.Add((int)EItemStageAddress.HeatMan + i, (byte)newItemOrder[i], String.Format("{0}man Item Get", ((EDmgVsBoss.Offset)i).ToString())); } }
protected void ChangeClash(Patch Patch, Random r) { int rInt; double rDbl; // Unused addresses //0x02CDAF - Jump velocity fraction? 0x44 double check // Clashman AI // Crash Man's routine for attack //0x02CCf2 - Walk x-vel fraction 0x47 rInt = r.Next(256); Patch.Add(0x02CCf2, (byte)rInt, "Clashman Walk X-Velocity Fraction"); //0x02CCF7 - Walk x-vel integer 0x01, do 0 to 3 rInt = r.Next(4); Patch.Add(0x02CCF7, (byte)rInt, "Clashman Walk X-Velocity Integer"); //0x02CD07 - Jump behavior 0x27. 0x17 = always jumping, any other value = doesn't react with a jump. // Give 25% chance for each unique behavior, and 50% for default. rDbl = r.NextDouble(); byte jumpType = 0x27; if (rDbl > 0.75) { jumpType = 0x17; } else if (rDbl > 0.5) { jumpType = 0x26; } Patch.Add(0x02CD07, jumpType, "Clashman Special Jump Behavior"); //0x02CD2A - Jump y-vel intger, 0x06, do from 0x02 to 0x0A rInt = r.Next(0x0A - 0x02 + 1) + 0x02; Patch.Add(0x02CD2A, (byte)rInt, "Clashman Jump Y-Velocity Integer"); //0x02CDD3 - Shot behavior, 0x5E, change to have him always shoot when jumping, 20% chance rDbl = r.NextDouble(); if (rDbl > 0.80) Patch.Add(0x02CDD3, 0x50, "Clashman Disable Single Shot"); //0x02CDEE - Clash Bomber velocity, 0x06, do from 2 to 8 rInt = r.Next(7) + 2; Patch.Add(0x02CD2A, (byte)rInt, "Clashman Clash Bomber X-Velocity"); }
protected void ChangeBubble(Patch Patch, Random r) { byte[] yVels; int rInt; // Bubbleman's AI //0x02C70B - Falling speed integer, 0xFF. yVels = new byte[] { 0xF0, 0xF4, 0xF8, 0xFC, 0xFF }; rInt = r.Next(yVels.Length); Patch.Add(0x02C70B, yVels[rInt], "Bubbleman Y-Velocity Falling"); //0x02C6D3 - Rising speed integer, 0x01. yVels = new byte[] { 0x01, 0x02, 0x03, 0x05 }; rInt = r.Next(yVels.Length); Patch.Add(0x02C6D3, yVels[rInt], "Bubbleman Y-Velocity Rising"); }
/// <summary> /// Shuffle the Robot Master stages. This shuffling will not be indicated by the Robot Master portraits. /// </summary> public void Randomize(Patch Patch, Random r) { // StageSelect Address Value // ----------------------------- // Bubble Man 0x034670 3 // Air Man 0x034671 1 // Quick Man 0x034672 4 // Wood Man 0x034673 2 // Crash Man 0x034674 7 // Flash Man 0x034675 5 // Metal Man 0x034676 6 // Heat Man 0x034677 0 StageSelect = new List<StageFromSelect>(); StageSelect.Add(new StageFromSelect() { PortraitName = "Bubble Man", PortraitAddress = ERMPortraitAddress.BubbleMan, PortraitDestinationOriginal = 3, PortraitDestinationNew = 3, StageClearAddress = ERMStageClearAddress.BubbleMan, StageClearDestinationOriginal = 8, StageClearDestinationNew = 8 }); StageSelect.Add(new StageFromSelect() { PortraitName = "Air Man", PortraitAddress = ERMPortraitAddress.AirMan, PortraitDestinationOriginal = 1, PortraitDestinationNew = 1, StageClearAddress = ERMStageClearAddress.AirMan, StageClearDestinationOriginal = 2, StageClearDestinationNew = 2 }); StageSelect.Add(new StageFromSelect() { PortraitName = "Quick Man", PortraitAddress = ERMPortraitAddress.QuickMan, PortraitDestinationOriginal = 4, PortraitDestinationNew = 4, StageClearAddress = ERMStageClearAddress.QuickMan, StageClearDestinationOriginal = 16, StageClearDestinationNew = 16 }); StageSelect.Add(new StageFromSelect() { PortraitName = "Wood Man", PortraitAddress = ERMPortraitAddress.WoodMan, PortraitDestinationOriginal = 2, PortraitDestinationNew = 2, StageClearAddress = ERMStageClearAddress.WoodMan, StageClearDestinationOriginal = 4, StageClearDestinationNew = 4 }); StageSelect.Add(new StageFromSelect() { PortraitName = "Clash Man", PortraitAddress = ERMPortraitAddress.CrashMan, PortraitDestinationOriginal = 7, PortraitDestinationNew = 7, StageClearAddress = ERMStageClearAddress.CrashMan, StageClearDestinationOriginal = 128, StageClearDestinationNew = 128 }); StageSelect.Add(new StageFromSelect() { PortraitName = "Flash Man", PortraitAddress = ERMPortraitAddress.FlashMan, PortraitDestinationOriginal = 5, PortraitDestinationNew = 5, StageClearAddress = ERMStageClearAddress.FlashMan, StageClearDestinationOriginal = 32, StageClearDestinationNew = 32 }); StageSelect.Add(new StageFromSelect() { PortraitName = "Metal Man", PortraitAddress = ERMPortraitAddress.MetalMan, PortraitDestinationOriginal = 6, PortraitDestinationNew = 6, StageClearAddress = ERMStageClearAddress.MetalMan, StageClearDestinationOriginal = 64, StageClearDestinationNew = 64 }); StageSelect.Add(new StageFromSelect() { PortraitName = "Heat Man", PortraitAddress = ERMPortraitAddress.HeatMan, PortraitDestinationOriginal = 0, PortraitDestinationNew = 0, // 4 = quick StageClearAddress = ERMStageClearAddress.HeatMan, StageClearDestinationOriginal = 1, StageClearDestinationNew = 1 }); List<byte> newStageOrder = new List<byte>(); for (byte i = 0; i < 8; i++) newStageOrder.Add(i); newStageOrder.Shuffle(r); for (int i = 0; i < 8; i++) { string portrait = StageSelect[i].PortraitName; StageSelect[i].PortraitDestinationNew = StageSelect[newStageOrder[i]].PortraitDestinationOriginal; } foreach (StageFromSelect stage in StageSelect) { Patch.Add((int)stage.PortraitAddress, (byte)stage.PortraitDestinationNew, String.Format("Stage Select {0} Destination", stage.PortraitName)); } }
protected void ChangeAir(Patch Patch, Random r) { //0x03DAD6 - A num projectiles, default 0x04 // Values 0x02 and 0x03 work, but larger values behave strangely int numProjectiles = 0x04; double rTestNumProjectiles = r.NextDouble(); if (rTestNumProjectiles > 0.80) numProjectiles = 0x03; else if (rTestNumProjectiles > 0.60) numProjectiles = 0x02; Patch.Add(0x03DAD6, (byte)numProjectiles, "(A) | Number of Projectiles"); //0x03DADA - A projectile type, default 0x02 // Can use this to change behavior completely!Buggy though. //0x03DAE6 - A sound effect (0x3F) ESoundID sound = GetRandomSound(r); Patch.Add(0x03DAE6, (byte)sound, "(A) | Sound"); //0x03DAEE - A ammo used(0x02) int ammoUse = r.Next(0x02) + 0x01; Patch.Add(0x03DAEE, (byte)ammoUse, "(A) | Ammo Use"); AmmoUsage.Add(ammoUse); //0x03DE6E - A projectile y-acceleration fraction(10) // Do 0x02 to 0x32, where values above 0x10 are less common int yAccFrac = r.Next(0x17) + 0x02; if (yAccFrac > 0x10) { // double the addition of any acceleration value chosen above 0x10 yAccFrac += (yAccFrac - 0x10) * 2; } Patch.Add(0x03DE6E, (byte)yAccFrac, "(A) | Y-Acceleration (fraction)"); //0x03DE76 - A projectile y-acceleration integer(00) // 15% chance to be significantly faster int yAccInt = 0x00; double rYAcc = r.NextDouble(); if (rYAcc > 0.85) yAccInt = 0x01; Patch.Add(0x03DE76, (byte)yAccInt, "(A) | Y-Acceleration (integer)"); //0x03DE7E - A x - speed fraction projectile 1(19) //0x03DE7F - A x - speed fraction projectile 2(99) //0x03DE80 - A x - speed fraction projectile 3(33) for (int i = 0; i < 3; i++) { int xFracSpeed = r.Next(0xFF) + 0x01; Patch.Add(0x03DE7E + i, (byte)xFracSpeed, String.Format("(A) | Projectile {0} X-Velocity (fraction)", i + 1)); } //0x03DE81 - A x - speed integer projectile 1(01) //0x03DE82 - A x - speed integer projectile 2(01) //0x03DE83 - A x - speed integer projectile 3(02) int[] xIntSpeeds = new int[] { 0x00, 0x01, 0x02, 0x04, 0x06 }; int rIndex = 0; int xIntSpeed = 0; for (int i = 0; i < 3; i++) { rIndex = r.Next(xIntSpeeds.Length); xIntSpeed = xIntSpeeds[rIndex]; Patch.Add(0x03DE81 + i, (byte)xIntSpeed, String.Format("(A) | Projectile {0} X-Velocity (integer)", i + 1)); } }
protected void ChangeQuick(Patch Patch, Random r) { int rInt; // Other addresses with potential: //0x02C872 - Projectile type, 0x59 // Quickman's AI //0x02C86E - Number of Boomerangs, 0x03, do from 1 - 0x0A rInt = r.Next(0x0B) + 0x01; Patch.Add(0x02C86E, (byte)rInt, "Quickman Number of Boomerangs"); //0x02C882 - Boomerang: delay before arc 0x25. 0 for no arc, or above like 0x35. do from 5 to 0x35. rInt = r.Next(0x35 - 0x05 + 1) + 0x05; Patch.Add(0x02C882, (byte)rInt, "Quickman Boomerang Delay 1"); //0x02C887 - Boomerang speed when appearing, 0x04, do from 0x01 to 0x07 rInt = r.Next(0x07 - 0x01 + 1) + 0x01; Patch.Add(0x02C887, (byte)rInt, "Quickman Boomerang Velocity Integer 1"); //0x03B726 - Boomerang speed secondary, 0x04, does this affect anything else? rInt = r.Next(0x07 - 0x01 + 1) + 0x01; Patch.Add(0x03B726, (byte)rInt, "Quickman Boomerang Velocity Integer 2"); // For all jumps, choose randomly from 0x02 to 0x0A //0x02C8A3 - Middle jump, 0x07 rInt = r.Next(0x0A - 0x02 + 1) + 0x02; Patch.Add(0x02C8A3, (byte)rInt, "Quickman Jump Height 1 Integer"); //0x02C8A4 - High jump, 0x08 rInt = r.Next(0x0A - 0x02 + 1) + 0x02; Patch.Add(0x02C8A4, (byte)rInt, "Quickman Jump Height 2 Integer"); //0x02C8A5 - Low jump, 0x04 rInt = r.Next(0x0A - 0x02 + 1) + 0x02; Patch.Add(0x02C8A5, (byte)rInt, "Quickman Jump Height 3 Integer"); //0x02C8E4 - Running time, 0x3E, do from 0x18 to 0x50 rInt = r.Next(0x50 - 0x18 + 1) + 0x18; Patch.Add(0x02C8E4, (byte)rInt, "Quickman Running Time"); //0x02C8DF - Running speed, 0x02, do from 0x05 to 0x01 rInt = r.Next(0x05 - 0x01 + 1) + 0x01; Patch.Add(0x02C8DF, (byte)rInt, "Quickman Running Velocity Integer"); }
protected void ChangeMetal(Patch Patch, Random r) { int rInt; double rDbl; // Unused addresses //0x02CC2D - Projectile type //0x02CC29 - Metal Blade sound effect 0x20 // Metalman AI //0x02CC3F - Speed of Metal blade 0x04, do 2 to 9 rInt = r.Next(8) + 2; Patch.Add(0x02CC3F, (byte)rInt, "Metalman Projectile Velocity Integer"); //0x02CC1D - Odd change to attack behavior, 0x06, only if different than 6. Give 25% chance. rDbl = r.NextDouble(); if (rDbl > 0.75) Patch.Add(0x02CC1D, 0x05, "Metalman Alternate Attack Behavior"); //0x02CBB5 - Jump Height 1 0x06, do from 03 - 07 ? higher than 7 bonks ceiling //0x02CBB6 - Jump Height 2 0x05 //0x02CBB7 - Jump Height 3 0x04 List<int> jumpHeight = new List<int>(){ 0x03, 0x04, 0x05, 0x06, 0x07 }; for (int i = 0; i < 3; i ++) { // Pick a height at random and remove from list to get 3 different heights rInt = r.Next(jumpHeight.Count); Patch.Add(0x02CBB5 + i, (byte)jumpHeight[rInt], String.Format("Metalman Jump {0} Y-Velocity Integer", i + 1)); jumpHeight.RemoveAt(rInt); } }
/// <summary> /// Identical to RandomWeaknesses() but using Mega Man 2 (U).nes offsets /// </summary> private void RandomizeU(Patch Patch, Random r) { // Chaos Mode Weaknesses if (IsChaos) { List<EDmgVsBoss> bossPrimaryWeaknessAddresses = EDmgVsBoss.GetTables(false, true); List<EDmgVsBoss> bossWeaknessShuffled = new List<EDmgVsBoss>(bossPrimaryWeaknessAddresses); bossWeaknessShuffled.Shuffle(r); // Preparation: Disable redundant Atomic Fire healing code // (Note that 0xFF in any weakness table is sufficient to heal a boss) Patch.Add(0x02E66D, 0xFF, "Atomic Fire Boss To Heal" ); // Normally "00" to indicate Heatman. // Select 2 robots to be weak against Buster int busterI1 = r.Next(8); int busterI2 = busterI1; while (busterI2 == busterI1) busterI2 = r.Next(8); // Foreach boss for (int i = 0; i < 8; i++) { // First, fill in special weapon tables with a 50% chance to block or do 1 damage for (int j = 0; j < bossPrimaryWeaknessAddresses.Count; j++) { double rTestImmune = r.NextDouble(); byte damage = 0; if (rTestImmune > 0.5) { if (bossPrimaryWeaknessAddresses[j] == EDmgVsBoss.U_DamageH) { // ...except for Atomic Fire, which will do some more damage damage = (byte)(RWeaponBehavior.AmmoUsage[1] / 2); } else if (bossPrimaryWeaknessAddresses[j] == EDmgVsBoss.U_DamageF) { damage = 0x00; } else { damage = 0x01; } } Patch.Add(bossPrimaryWeaknessAddresses[j] + i, damage, String.Format("{0} Damage to {1}", bossPrimaryWeaknessAddresses[j].WeaponName, (EDmgVsBoss.Offset)i)); BotWeaknesses[i, j + 1] = damage; } // Write the primary weakness for this boss byte dmgPrimary = GetRoboDamagePrimary(r, bossWeaknessShuffled[i]); Patch.Add(bossWeaknessShuffled[i] + i, dmgPrimary, String.Format("{0} Damage to {1} (Primary)", bossWeaknessShuffled[i].WeaponName, (EDmgVsBoss.Offset)i)); // Write the secondary weakness for this boss (next element in list) // Secondary weakness will either do 2 damage or 4 if it is Atomic Fire // Time Stopper cannot be a secondary weakness. Instead it will heal that boss. // As a result, one Robot Master will not have a secondary weakness int i2 = (i + 1 >= 8) ? 0 : i + 1; EDmgVsBoss weakWeap2 = bossWeaknessShuffled[i2]; //stream.Position = weakWeap2 + i; byte dmgSecondary = 0x02; if (weakWeap2 == EDmgVsBoss.U_DamageH) { dmgSecondary = 0x04; } else if (weakWeap2 == EDmgVsBoss.U_DamageF) { dmgSecondary = 0x00; //long prevStreamPos = stream.Position; //stream.Position = 0x02C08F; //stream.WriteByte((byte)i); //stream.Position = prevStreamPos; // Address in Time-Stopper code that normally heals Flashman, change to heal this boss instead Patch.Add(0x02C08F, (byte)i, String.Format("Time-Stopper Heals {0} (Special Code)", (EDmgVsBoss.Offset)i)); } Patch.Add(weakWeap2 + i, dmgSecondary, String.Format("{0} Damage to {1} (Secondary)", weakWeap2.WeaponName, (EDmgVsBoss.Offset)i)); //stream.WriteByte(dmgSecondary); // Add buster damage //stream.Position = EDmgVsBoss.U_DamageP + i; if (i == busterI1 || i == busterI2) { Patch.Add(EDmgVsBoss.U_DamageP + i, 0x02, String.Format("Buster Damage to {0}", (EDmgVsBoss.Offset)i)); BotWeaknesses[i, 0] = 0x02; } else { Patch.Add(EDmgVsBoss.U_DamageP + i, 0x01, String.Format("Buster Damage to {0}", (EDmgVsBoss.Offset)i)); BotWeaknesses[i, 0] = 0x01; } // Save info int weapIndexPrimary = GetWeaponIndexFromAddress(bossWeaknessShuffled[i]); BotWeaknesses[i, weapIndexPrimary] = dmgPrimary; int weapIndexSecondary = GetWeaponIndexFromAddress(weakWeap2); BotWeaknesses[i, weapIndexSecondary] = dmgSecondary; } debug.AppendLine("Robot Master Weaknesses:"); debug.AppendLine("P\tH\tA\tW\tB\tQ\tF\tM\tC:"); debug.AppendLine("--------------------------------------------"); for (int i = 0; i < 8; i++) { for (int j = 0; j < 9; j++) { debug.Append(String.Format("{0}\t", BotWeaknesses[i, j])); } debug.AppendLine("< " + ((EDmgVsBoss.Offset)i).ToString()); } debug.AppendLine(); } // Easy Mode Weaknesses else { List<WeaponTable> Weapons = new List<WeaponTable>(); Weapons.Add(new WeaponTable() { Name = "Buster", ID = 0, Address = EDmgVsBoss.U_DamageP, RobotMasters = new int[8] { 2, 2, 1, 1, 2, 2, 1, 1 } // Heat = 2, // Air = 2, // Wood = 1, // Bubble = 1, // Quick = 2, // Flash = 2, // Metal = 1, // Clash = 1, // Dragon = 1 // Byte Unused = 0 // Gutsdozer = 1 // Unused = 0 }); Weapons.Add(new WeaponTable() { Name = "Atomic Fire", ID = 1, Address = EDmgVsBoss.U_DamageH, // Note: These values only affect a fully charged shot. Partially charged shots use the Buster table. RobotMasters = new int[8] { 0xFF, 6, 0x0E, 0, 0x0A, 6, 4, 6 } // Dragon = 8 // Gutsdozer = 8 }); Weapons.Add(new WeaponTable() { Name = "Air Shooter", ID = 2, Address = EDmgVsBoss.U_DamageA, RobotMasters = new int[8] { 2, 0, 4, 0, 2, 0, 0, 0x0A } // Dragon = 0 // Gutsdozer = 0 }); Weapons.Add(new WeaponTable() { Name = "Leaf Shield", ID = 3, Address = EDmgVsBoss.U_DamageW, RobotMasters = new int[8] { 0, 8, 0xFF, 0, 0, 0, 0, 0 } // Dragon = 0 // Unused = 0 // Gutsdozer = 0 }); Weapons.Add(new WeaponTable() { Name = "Bubble Lead", ID = 4, Address = EDmgVsBoss.U_DamageB, RobotMasters = new int[8] { 6, 0, 0, 0xFF, 0, 2, 0, 1 } // Dragon = 0 // Unused = 0 // Gutsdozer = 1 }); Weapons.Add(new WeaponTable() { Name = "Quick Boomerang", ID = 5, Address = EDmgVsBoss.U_DamageQ, RobotMasters = new int[8] { 2, 2, 0, 2, 0, 0, 4, 1 } // Dragon = 1 // Unused = 0 // Gutsdozer = 2 }); Weapons.Add(new WeaponTable() { Name = "Time Stopper", ID = 6, Address = EDmgVsBoss.U_DamageF, // NOTE: These values affect damage per tick // NOTE: This table only has robot masters, no wily bosses RobotMasters = new int[8] { 0, 0, 0, 0, 1, 0, 0, 0 } }); Weapons.Add(new WeaponTable() { Name = "Metal Blade", ID = 7, Address = EDmgVsBoss.U_DamageM, RobotMasters = new int[8] { 1, 0, 2, 4, 0, 4, 0x0E, 0 } // Dragon = 0 // Unused = 0 // Gutsdozer = 0 }); Weapons.Add(new WeaponTable() { Name = "Clash Bomber", ID = 8, Address = EDmgVsBoss.U_DamageC, RobotMasters = new int[8] { 0xFF, 0, 2, 2, 4, 3, 0, 0 } // Dragon = 1 // Unused = 0 // Gutsdozer = 1 }); foreach (WeaponTable weapon in Weapons) { weapon.RobotMasters.Shuffle(r); } foreach (WeaponTable weapon in Weapons) { for (int i = 0; i < 8; i++) { Patch.Add(weapon.Address + i, (byte)weapon.RobotMasters[i], String.Format("Easy Weakness: {0} against {1}", weapon.Name, ((EDmgVsBoss.Offset)i).ToString() )); } } } }
/// <summary> /// TODO /// </summary> public static void SetBurstChaser(Patch p, bool jVersion) { p.Add(0x038147, 0x60, String.Format("READY Text Delay")); p.Add(0x038921, 0x03, String.Format("Mega Man Walk X-Velocity Integer")); p.Add(0x03892C, 0x00, String.Format("Mega Man Walk X-Velocity Fraction")); p.Add(0x038922, 0x03, String.Format("Mega Man Air X-Velocity Integer")); p.Add(0x03892D, 0x00, String.Format("Mega Man Air X-Velocity Fraction")); p.Add(0x0386EF, 0x01, String.Format("Mega Man Ladder Climb Up Integer")); p.Add(0x03872E, 0xFE, String.Format("Mega Man Ladder Climb Down Integer")); int address = (jVersion) ? 0x03D4A4 : 0x03D4A7; p.Add(address, 0x08, String.Format("Buster Projectile X-Velocity Integer")); }
public static void EnablePressDamage(Patch Patch) { Patch.Add(EDmgVsEnemy.DamageP + EDmgVsEnemy.Offset.Press, 0x01, "Buster Damage Against Press"); }
/// <summary> /// TODO /// </summary> public static void SetFastText(Patch p, bool jVersion) { int address = (jVersion) ? 0x037C51 : 0x037D4A; p.Add(address, 0x04, String.Format("Weapon Get Text Write Delay")); }
protected void ChangeFlash(Patch Patch, Random r) { int rInt; // Unused addresses //0x02CA71 - Projectile type 0x35 //0x02CA52 - "Length of time stopper / projectile frequency ?" // Flashman's AI //0x02C982 - Walk velocity integer 0x01, do from 0 to 3 rInt = r.Next(0x04); Patch.Add(0x02C982, (byte)rInt, "Flashman Walk Velocity Integer"); //0x02C97D - Walk velocity fraction 0x06, do 0x00-0xFF rInt = r.Next(256); Patch.Add(0x02C97D, (byte)rInt, "Flashman Walk Velocity Fraction"); //0x02C98B - Delay before time stopper 0xBB (187 frames). Do from 30 frames to 240 frames rInt = r.Next(211) + 30; Patch.Add(0x02C98B, (byte)rInt, "Flashman Delay Before Time Stopper"); //0x02CAC6 - Jump distance integer 0x00, do 0 to 3 // TODO do fraction also rInt = r.Next(4); Patch.Add(0x02CAC6, (byte)rInt, "Flashman Jump X-Velocity Integer"); //0x02CACE - Jump height 0x04, do 3 - 8 rInt = r.Next(6) + 3; Patch.Add(0x02CACE, (byte)rInt, "Flashman Jump Y-Velocity Integer"); //0x02CA81 - Projectile speed 0x08, do 2 - 0A rInt = r.Next(0x0A - 0x02 + 1) + 0x02; Patch.Add(0x02CA81, (byte)rInt, "Flashman Projectile Velocity Integer"); //0x02CA09 - Number of projectiles to shoot 0x06, do 3 - 0x10 rInt = r.Next(0x10 - 0x03 + 1) + 0x03; Patch.Add(0x02CA09, (byte)rInt, "Flashman Number of Projectiles"); }
protected void ChangeHeat(Patch Patch, Random r) { int rInt = 0; // Heatman AI 0x02C16E - 0x02C1FE // projectile y - distances //0x02C207 default 07, good from 03 - 08 //0x02C208 default 05, good from 04 - 07 //0x02C209 default 03, good from 03 - 05 rInt = r.Next(6) + 0x03; Patch.Add(0x02C207, (byte)rInt, "Heatman Projectile 1 Y-Distance"); rInt = r.Next(4) + 0x04; Patch.Add(0x02C208, (byte)rInt, "Heatman Projectile 2 Y-Distance"); rInt = r.Next(3) + 0x03; Patch.Add(0x02C209, (byte)rInt, "Heatman Projectile 3 Y-Distance"); // projectile x-distances, 0x3A 0x2E 0x1C // - The lower value, the faster speed. Different for each fireball. //0x02C20A - 1st value should be 0x47 to hit megaman, Or, from 0x30 to 0x80 //0x02C20B - 2nd value should be 0x2E to hit megaman. Or, from 0x22 to 0x40 //0x02C20C - 3rd value should be 0x17 to hit megaman, Or, from 0x10 to 0x30 rInt = r.Next(0x80 - 0x30 + 1) + 0x30; Patch.Add(0x02C20A, (byte)rInt, "Heatman Projectile 1 X-Distance"); rInt = r.Next(0x40 - 0x22 + 1) + 0x22; Patch.Add(0x02C20B, (byte)rInt, "Heatman Projectile 2 X-Distance"); rInt = r.Next(0x30 - 0x10 + 1) + 0x10; Patch.Add(0x02C20C, (byte)rInt, "Heatman Projectile 3 X-Distance"); // 30/60/90 frame delay //0x02C29D - Delay 1 0x1F //0x02C29E - Delay 2 0x3E //0x02C29F - Delay 3 0x5D // Choose delay interval from 10-40 frames rInt = r.Next(31) + 10; Patch.Add(0x02C29D, (byte)rInt, "Heatman Invuln Delay 1"); Patch.Add(0x02C29E, (byte)(rInt * 2), "Heatman Invuln Delay 2"); Patch.Add(0x02C29F, (byte)(rInt * 3), "Heatman Invuln Delay 3"); //0x02C253 - Charge velocity(0x04, 0x08 or more usually puts him on side of screen) rInt = r.Next(4) + 0x02; Patch.Add(0x02C253, (byte)rInt, "Heatman Charge Velocity"); }
/// <summary> /// TODO /// </summary> private void RandomizeWilyUJ(Patch Patch, Random r) { if (IsChaos) { // List of special weapon damage tables for enemies List<EDmgVsEnemy> dmgPtrEnemies = EDmgVsEnemy.GetTables(false); EDmgVsEnemy enemyWeak1; EDmgVsEnemy enemyWeak2; EDmgVsEnemy enemyWeak3; // List of special weapon damage tables for bosses (no flash or buster) List<EDmgVsBoss> dmgPtrBosses = EDmgVsBoss.GetTables(false, false); EDmgVsBoss bossWeak1; EDmgVsBoss bossWeak2; EDmgVsBoss bossWeak3; EDmgVsBoss bossWeak4; #region Dragon // Dragon // 25% chance to have a buster vulnerability double rBuster = r.NextDouble(); byte busterDmg = 0x00; if (rBuster > 0.75) busterDmg = 0x01; Patch.Add(EDmgVsBoss.U_DamageP + EDmgVsBoss.Offset.Dragon, busterDmg, "Buster Damage to Dragon"); WilyWeaknesses[0, 0] = busterDmg; // Choose 2 special weapon weaknesses List<EDmgVsBoss> dragon = new List<EDmgVsBoss>(dmgPtrBosses); int rInt = r.Next(dragon.Count); bossWeak1 = dragon[rInt]; dragon.RemoveAt(rInt); rInt = r.Next(dragon.Count); bossWeak2 = dragon[rInt]; // For each weapon, apply the weaknesses and immunities for (int i = 0; i < dmgPtrBosses.Count; i++) { EDmgVsBoss weapon = dmgPtrBosses[i]; // Dragon weak if (weapon == bossWeak1 || weapon == bossWeak2) { // Deal 1 damage with weapons that cost 1 or less ammo byte damage = 0x01; // Deal damage = ammoUsage - 1, minimum 2 damage if (RWeaponBehavior.AmmoUsage[i + 1] > 1) { int tryDamage = (int)RWeaponBehavior.AmmoUsage[i + 1] - 0x01; damage = (tryDamage < 2) ? (byte)0x02 : (byte)tryDamage; } Patch.Add(weapon + EDmgVsBoss.Offset.Dragon, damage, String.Format("{0} Damage to Dragon", weapon.WeaponName)); WilyWeaknesses[0, i + 1] = damage; } // Dragon immune else { Patch.Add(weapon + EDmgVsBoss.Offset.Dragon, 0x00, String.Format("{0} Damage to Dragon", weapon.WeaponName)); WilyWeaknesses[0, i + 1] = 0x00; } } #endregion #region Picopico-kun // Picopico-kun // 20 HP each // 25% chance for buster to deal 3-7 damage rBuster = r.NextDouble(); busterDmg = 0x00; if (rBuster > 0.75) { busterDmg = (byte)(r.Next(5) + 3); } Patch.Add(EDmgVsEnemy.DamageP + EDmgVsEnemy.Offset.PicopicoKun, busterDmg, String.Format("Buster Damage to Picopico-Kun")); WilyWeaknesses[1, 0] = busterDmg; // Deal ammoUse x 6 for the main weakness // Deal ammoUse x 2 for another // Deal ammoUse x 1 for another List<EDmgVsEnemy> pico = new List<EDmgVsEnemy>(dmgPtrEnemies); rInt = r.Next(pico.Count); enemyWeak1 = pico[rInt]; pico.RemoveAt(rInt); rInt = r.Next(pico.Count); enemyWeak2 = pico[rInt]; pico.RemoveAt(rInt); rInt = r.Next(pico.Count); enemyWeak3 = pico[rInt]; for (int i = 0; i < dmgPtrEnemies.Count; i++) { EDmgVsEnemy weapon = dmgPtrEnemies[i]; byte damage = 0x00; char level = ' '; // Pico weakness 1, deal ammoUse x8 damage if (weapon == enemyWeak1) { damage = (byte)(RWeaponBehavior.AmmoUsage[i + 1] * 10); if (damage < 2) damage = 3; level = '^'; } // weakness 2, deal ammoUse x5 damage else if (weapon == enemyWeak2) { damage = (byte)(RWeaponBehavior.AmmoUsage[i + 1] * 5); if (damage < 2) damage = 2; level = '*'; } // weakness 3, deal ammoUse x2 damage else if (weapon == enemyWeak3) { damage = (byte)(RWeaponBehavior.AmmoUsage[i + 1] * 2); if (damage < 2) damage = 2; } // If any weakness is Atomic Fire, deal 20 damage if (weapon == EDmgVsEnemy.DamageH && (enemyWeak1 == weapon || enemyWeak2 == weapon || enemyWeak3 == weapon)) { damage = 20; } // Bump up already high damage values to 20 if (damage >= 14) { damage = 20; } Patch.Add(weapon + EDmgVsEnemy.Offset.PicopicoKun, damage, String.Format("{0} Damage to Picopico-Kun{1}", weapon.WeaponName, level)); WilyWeaknesses[1, i + 1] = damage; WilyWeaknessInfo[1, i + 1] = level; } #endregion #region Guts // Guts // 25% chance to have a buster vulnerability rBuster = r.NextDouble(); busterDmg = 0x00; if (rBuster > 0.75) busterDmg = 0x01; Patch.Add(EDmgVsBoss.U_DamageP + EDmgVsBoss.Offset.Guts, busterDmg, String.Format("Buster Damage to Guts Tank")); WilyWeaknesses[2, 0] = busterDmg; // Choose 2 special weapon weaknesses List<EDmgVsBoss> guts = new List<EDmgVsBoss>(dmgPtrBosses); rInt = r.Next(guts.Count); bossWeak1 = guts[rInt]; guts.RemoveAt(rInt); rInt = r.Next(guts.Count); bossWeak2 = guts[rInt]; for (int i = 0; i < dmgPtrBosses.Count; i++) { EDmgVsBoss weapon = dmgPtrBosses[i]; // Guts weak if (weapon == bossWeak1 || weapon == bossWeak2) { // Deal 1 damage with weapons that cost 1 or less ammo byte damage = 0x01; // Deal damage = ammoUsage - 1, minimum 2 damage if (RWeaponBehavior.AmmoUsage[i + 1] > 1) { int tryDamage = (int)RWeaponBehavior.AmmoUsage[i + 1] - 0x01; damage = (tryDamage < 2) ? (byte)0x02 : (byte)tryDamage; } Patch.Add(weapon + EDmgVsBoss.Offset.Guts, damage, String.Format("{0} Damage to Guts Tank", weapon.WeaponName)); WilyWeaknesses[2, i + 1] = damage; } // Guts immune else { Patch.Add(weapon + EDmgVsBoss.Offset.Guts, 0x00, String.Format("{0} Damage to Guts Tank", weapon.WeaponName)); WilyWeaknesses[2, i + 1] = 0x00; } } #endregion #region Buebeam Trap // Buebeam // 5 Orbs + 3 Required Barriers (5 total barriers, 4 in speedrun route) // Choose a weakness for both Barriers and Orbs, scale damage to have plenty of energy // If the same weakness for both, scale damage further // If any weakness is Atomic Fire, ensure that there's enough ammo // Randomize Crash Barrier weakness List<EDmgVsEnemy> dmgBarrierList = EDmgVsEnemy.GetTables(true); // Remove Heat as possibility if it costs too much ammo if (RWeaponBehavior.AmmoUsage[1] > 5) { dmgBarrierList.RemoveAt(1); WilyWeaknesses[3, 1] = 0; } // Get Barrier weakness rInt = r.Next(dmgBarrierList.Count); EDmgVsEnemy wpnBarrier = dmgBarrierList[rInt]; // Scale damage to be slightly more capable than killing 5 barriers at full ammo int dmgW4 = 0x01; if (wpnBarrier != EDmgVsEnemy.DamageP) { int totalShots = (int)(28 / RWeaponBehavior.GetAmmoUsage(wpnBarrier)); int numHitsPerBarrier = (int)(totalShots / 5); if (numHitsPerBarrier > 1) numHitsPerBarrier--; if (numHitsPerBarrier > 8) numHitsPerBarrier = 8; dmgW4 = (int)Math.Ceiling(20d / numHitsPerBarrier); } for (int i = 0; i < dmgBarrierList.Count; i++) { // Deal damage with weakness, and 0 for everything else byte damage = (byte)dmgW4; EDmgVsEnemy wpn = dmgBarrierList[i]; if (wpn != wpnBarrier) { damage = 0; } Patch.Add(wpn.Address + EDmgVsEnemy.Offset.ClashBarrier_W4, damage, String.Format("{0} Damage to Clash Barrier 1", wpnBarrier.WeaponName)); Patch.Add(wpn.Address + EDmgVsEnemy.Offset.ClashBarrier_Other, damage, String.Format("{0} Damage to Clash Barrier 2", wpnBarrier.WeaponName)); } // Remove Barrier weakness from list (therefore, different Buebeam weakness) dmgBarrierList.Remove(wpnBarrier); // Get Buebeam weakness rInt = r.Next(dmgBarrierList.Count); EDmgVsEnemy wpnBuebeam = dmgBarrierList[rInt]; // Scale damage to be slightly more capable than killing 5 buebeams at full ammo dmgW4 = 0x01; if (wpnBuebeam != EDmgVsEnemy.DamageP) { int totalShots = (int)(28 / RWeaponBehavior.GetAmmoUsage(wpnBuebeam)); int numHitsPerBuebeam = (int)(totalShots / 5); if (numHitsPerBuebeam > 1) numHitsPerBuebeam--; if (numHitsPerBuebeam > 8) numHitsPerBuebeam = 8; dmgW4 = (int)Math.Ceiling(20d / numHitsPerBuebeam); } for (int i = 0; i < dmgBarrierList.Count; i++) { byte damage = (byte)dmgW4; EDmgVsEnemy wpn = dmgBarrierList[i]; if (wpn != wpnBuebeam) { damage = 0; } Patch.Add(wpn.Address + EDmgVsEnemy.Offset.Buebeam, damage, String.Format("{0} Damage to Buebeam Trap", wpnBuebeam.WeaponName)); // Add to damage table (skipping heat if necessary) if (RWeaponBehavior.AmmoUsage[1] > 5 && i >= 1) { WilyWeaknesses[3, i + 1] = damage; } else { WilyWeaknesses[3, i] = damage; } } #endregion #region Wily Machine // Machine // Will have 4 weaknesses and potentially a Buster weakness // Phase 1 will disable 2 of the weaknesses, taking no damage // Phase 2 will re-enable them, but disable 1 other weakness // Mega Man 2 behaves in a similar fashion, disabling Q and A in phase 1, but only disabling H in phase 2 // 75% chance to have a buster vulnerability rBuster = r.NextDouble(); busterDmg = 0x00; if (rBuster > 0.25) busterDmg = 0x01; Patch.Add(EDmgVsBoss.U_DamageP + EDmgVsBoss.Offset.Machine, busterDmg, String.Format("Buster Damage to Wily Machine")); WilyWeaknesses[4, 0] = busterDmg; // Choose 4 special weapon weaknesses List<EDmgVsBoss> machine = new List<EDmgVsBoss>(dmgPtrBosses); rInt = r.Next(machine.Count); bossWeak1 = machine[rInt]; machine.RemoveAt(rInt); rInt = r.Next(machine.Count); bossWeak2 = machine[rInt]; machine.RemoveAt(rInt); rInt = r.Next(machine.Count); bossWeak3 = machine[rInt]; machine.RemoveAt(rInt); rInt = r.Next(machine.Count); bossWeak4 = machine[rInt]; for (int i = 0; i < dmgPtrBosses.Count; i++) { EDmgVsBoss weapon = dmgPtrBosses[i]; // Machine weak if (weapon == bossWeak1 || weapon == bossWeak2 || weapon == bossWeak3 || weapon == bossWeak4) { // Deal 1 damage with weapons that cost 1 or less ammo byte damage = 0x01; // Deal damage = ammoUsage if (RWeaponBehavior.AmmoUsage[i + 1] > 1) { damage = (byte)RWeaponBehavior.AmmoUsage[i + 1]; } Patch.Add(weapon + EDmgVsBoss.Offset.Machine, damage, String.Format("{0} Damage to Wily Machine", weapon.WeaponName)); WilyWeaknesses[4, i + 1] = damage; } // Machine immune else { Patch.Add(weapon + EDmgVsBoss.Offset.Machine, 0x00, String.Format("{0} Damage to Wily Machine", weapon.WeaponName)); WilyWeaknesses[4, i + 1] = 0x00; } // Get index of this weapon out of all weapons 0-8; byte wIndex = (byte)(i + 1); if (weapon == EDmgVsBoss.ClashBomber || weapon == EDmgVsBoss.MetalBlade) wIndex++; // Disable weakness 1 and 2 on Wily Machine Phase 1 if (weapon == bossWeak1) { Patch.Add(0x02DA2E, wIndex, String.Format("Wily Machine Phase 1 Resistance 1 ({0})", weapon.WeaponName)); } if (weapon == bossWeak2) { Patch.Add(0x02DA32, wIndex, String.Format("Wily Machine Phase 1 Resistance 2 ({0})", weapon.WeaponName)); } // Disable weakness 3 on Wily Machine Phase 2 if (weapon == bossWeak3) { Patch.Add(0x02DA3A, wIndex, String.Format("Wily Machine Phase 2 Resistance ({0})", weapon.WeaponName)); } } #endregion #region Alien // Alien // Buster Heat Air Wood Bubble Quick Clash Metal byte alienDamage = 1; List<EDmgVsBoss> alienWeapons = EDmgVsBoss.GetTables(true, false); int rWeaponIndex = r.Next(alienWeapons.Count); // Deal two damage for 1-ammo weapons (or buster) if (RWeaponBehavior.AmmoUsage[rWeaponIndex] == 1) { alienDamage = 2; } // For 2+ ammo use weapons, deal 20% more than that in damage, rounded up else if (RWeaponBehavior.AmmoUsage[rWeaponIndex] > 1) { alienDamage = (byte)Math.Ceiling(RWeaponBehavior.AmmoUsage[rWeaponIndex] * 1.2); } // Apply weakness and erase others (flash will remain 0xFF) for (int i = 0; i < alienWeapons.Count; i++) { EDmgVsBoss weapon = alienWeapons[i]; if (i == rWeaponIndex) { Patch.Add(weapon + EDmgVsBoss.Offset.Alien, alienDamage, String.Format("{0} Damage to Alien", weapon.WeaponName)); WilyWeaknesses[5, i] = alienDamage; } else { Patch.Add(weapon + EDmgVsBoss.Offset.Alien, 0xFF, String.Format("{0} Damage to Alien", weapon.WeaponName)); WilyWeaknesses[5, i] = 0xFF; } } #endregion debug.AppendLine("Wily Boss Weaknesses:"); debug.AppendLine("P\tH\tA\tW\tB\tQ\tF\tM\tC:"); debug.AppendLine("--------------------------------------------"); for (int i = 0; i < WilyWeaknesses.GetLength(0); i++) { for (int j = 0; j < WilyWeaknesses.GetLength(1); j++) { debug.Append(String.Format("{0}\t", WilyWeaknesses[i, j])); if (j == 5) debug.Append("X\t"); // skip flash } string bossName = ""; switch (i) { case 0: bossName = "dragon"; break; case 1: bossName = "picopico-kun"; break; case 2: bossName = "guts"; break; case 3: bossName = "boobeam"; break; case 4: bossName = "machine"; break; case 5: bossName = "alien"; break; default: break; } debug.AppendLine("< " + bossName); } debug.AppendLine(); } // end if #region Easy Weakness else { // First address for damage (buster v heatman) int address = (RandomMM2.Settings.IsJapanese) ? (int)EDmgVsBoss.Buster : (int)EDmgVsBoss.U_DamageP; // Skip Time Stopper // Buster Air Wood Bubble Quick Clash Metal byte[] dragon = new byte[] { 1, 0, 0, 0, 1, 0, 1 }; byte[] guts = new byte[] { 1, 0, 0, 1, 2, 0, 1 }; byte[] machine = new byte[] { 1, 1, 0, 0, 1, 1, 4 }; byte[] alien = new byte[] { 0xff, 0xff, 0xff, 1, 0xff, 0xff, 0xff }; // TODO: Scale damage based on ammo count w/ weapon class instead of this hard-coded table // Buster Air Wood Bubble Quick Clash Metal //double[] ammoUsed = new double[] { 0, 2, 3, 0.5, 0.25, 4, 0.25 }; dragon.Shuffle(r); guts.Shuffle(r); machine.Shuffle(r); alien.Shuffle(r); int j = 0; for (int i = 0; i < 8; i++) // i = Buster plus 7 weapons, Time Stopper damage is located in another table (going to ignore it anyways) { //// Skip Atomic Fire //if (i == 1) continue; int posDragon = address + 14 * i + 8; Patch.Add(posDragon, dragon[j], String.Format("Easy Weakness: ? against Dragon")); Patch.Add(posDragon+2, guts[j], String.Format("Easy Weakness: ? against Guts")); Patch.Add(posDragon+4, machine[j], String.Format("Easy Weakness: ? against Wily Machine")); // Scale damage against alien if using a high ammo usage weapon if (alien[j] == 1) { if (RWeaponBehavior.AmmoUsage[j] >= 1) { alien[j] = (byte)((double)RWeaponBehavior.AmmoUsage[j] * 1.3); } } Patch.Add(posDragon+5, alien[j], String.Format("Easy Weakness: ? against Alien")); j++; } } #endregion }
protected void ChangeAir(Patch Patch, Random r) { int rInt = 0; double rDbl = 0; // Airman AI 0x02C2F3 - 0x02C50A // Create random Air Shooter patterns //0x02C393 - Tornado 0 Pattern 0 y-vel fraction //0x02C395 - Tornado 1 Pattern 0 y-vel frac //... //0x02C39A - Tornado 0 Pattern 1 y-vel frac //... ... //0x02C3B1 - Tornado 0 Pattern 0 y-vel integer //0x02C3CF - Tornado 0 Pattern 0 x-vel fraction //0x02C3ED - Tornado 0 Pattern 0 x-vel integer //0x02C40B - Tornado 0 Pattern 0 delay before stop const int A_tornadoTableLength = 0x1E; // Write y-vel fractions: 00-FF for (int i = 0; i < A_tornadoTableLength; i++) { rInt = r.Next(256); Patch.Add(0x02C393 + i, (byte)rInt, String.Format("Airman Tornado {0} Y-Vel Frac", i)); } // Write y-vel integers: FF-03, rare 04 for (int i = 0; i < A_tornadoTableLength; i++) { byte A_yVelInt = 0; byte[] A_yVelInts = new byte[] { 0xFF, 0x00, 0x01, 0x02, 0x03 }; rDbl = r.NextDouble(); if (rDbl > 0.9) { A_yVelInt = 0x04; } else { rInt = r.Next(A_yVelInts.Length); A_yVelInt = A_yVelInts[rInt]; } Patch.Add(0x02C3B1 + i, A_yVelInt, String.Format("Airman Tornado {0} Y-Vel Int", i)); } // Write x-vel fractions: 00-FF for (int i = 0; i < A_tornadoTableLength; i++) { rInt = r.Next(256); Patch.Add(0x02C3CF + i, (byte)rInt, String.Format("Airman Tornado {0} X-Vel Frac", i)); } // Write x-vel integers: 00-04, rare 04, common 03 for (int i = 0; i < A_tornadoTableLength; i++) { byte A_xVelInt = 0; byte[] A_xVelInts = new byte[] { 0x00, 0x01, 0x02 }; rDbl = r.NextDouble(); if (rDbl > 0.9) { A_xVelInt = 0x04; } else if (rDbl > 0.6) { A_xVelInt = 0x03; } else { rInt = r.Next(A_xVelInts.Length); A_xVelInt = A_xVelInts[rInt]; } Patch.Add(0x02C3ED + i, A_xVelInt, String.Format("Airman Tornado {0} X-Vel Int", i)); } // Write delays: 05-2A for (int i = 0; i < A_tornadoTableLength; i++) { rInt = r.Next(0x25) + 0x05; Patch.Add(0x02C40B + i, (byte)rInt, String.Format("Airman Tornado {0} Delay Time", i)); } // 0x02C30C - Num patterns before jumping 0x03 (do 1-4) rInt = r.Next(4) + 1; Patch.Add(0x02C30C, (byte)rInt, "Airman Patterns Before Jump"); //0x02C4DD - First Jump y-vel frac, 0xE6 //0x02C4DE - Second Jump y-vel frac, 0x76 //0x02C4E0 - First Jump y-vel int, 0x04 //0x02C4E1 - Second Jump y-vel int 0x07 //0x02C4E3 - First Jump x-vel frac, 0x39 //0x02C4E4 - Second Jump x-vel frac 0x9a //0x02C4E6 - First Jump x-vel int, 0x01 //0x02C4E7 - Second Jump x-vel int 0x01 // Pick x-vel integers for both jumps first. Must add up to 2 or 3. int rSum = r.Next(2) + 2; int jump1x = r.Next(rSum + 1); int jump2x = rSum - jump1x; Patch.Add(0x02C4E6, (byte)jump1x, "Airman X-Velocity Integer Jump 1"); Patch.Add(0x02C4E7, (byte)jump2x, "Airman X-Velocity Integer Jump 2"); // If a jump's x-int is 0, its corresponding y-int must be 6-7 // If a jump's x-int is 1, its corresponding y-int must be 4-7 // If a jump's x-int is 2, its corresponding y-int must be 3-5 // If a jump's x-int is 3, its corresponding y-int must be 2-4 int jump1y = AirmanGetJumpYVelocity(jump1x, r); int jump2y = AirmanGetJumpYVelocity(jump2x, r); Patch.Add(0x02C4E0, (byte)jump1y, "Airman Y-Velocity Integer Jump 1"); Patch.Add(0x02C4E1, (byte)jump2y, "Airman Y-Velocity Integer Jump 2"); // Random x and y-vel fractions for both jumps //stream.Position = 0x02C4DD; // 1st jump y-vel frac rInt = r.Next(0xF1); // If jump is 7 and fraction is > 0xF0, Airman gets stuck! Patch.Add(0x02C4DD, (byte)rInt, "Airman Y-Velocity Fraction Jump 1"); rInt = r.Next(0xF1); Patch.Add(0x02C4DE, (byte)rInt, "Airman Y-Velocity Fraction Jump 2"); rInt = r.Next(256); Patch.Add(0x02C4E3, (byte)rInt, "Airman X-Velocity Fraction Jump 1"); rInt = r.Next(256); Patch.Add(0x02C4E4, (byte)rInt, "Airman X-Velocity Fraction Jump 2"); }
private void RandomizeWeaponColors(Patch p, Random r) { // Create lists of possible colors to choose from and shuffle them List<byte> PossibleDarkColors = new List<byte>(); List<byte> PossibleLightColors = new List<byte>(); for (byte i = 0x01; i <= 0x0C; i++) { // Add first two rows of colors to dark list (except black/white/gray) PossibleDarkColors.Add(i); PossibleDarkColors.Add((byte)(i + 0x10)); // Add third and fourth rows to light list (except black/white/gray) PossibleLightColors.Add((byte)(i + 0x20)); PossibleLightColors.Add((byte)(i + 0x30)); } // Add black and dark-gray to dark list, white and light-gray to light list PossibleDarkColors.Add(0x0F); PossibleDarkColors.Add(0x00); PossibleLightColors.Add(0x10); PossibleLightColors.Add(0x20); // Randomize lists, and pick the first 9 and 8 elements to use as new colors PossibleDarkColors.Shuffle(r); PossibleLightColors.Shuffle(r); Queue<byte> DarkColors = new Queue<byte>(PossibleDarkColors.GetRange(0, 9)); Queue<byte> LightColors = new Queue<byte>(PossibleLightColors.GetRange(0, 8)); // Get starting address depending on game version int startAddress = (RandomMM2.Settings.IsJapanese) ? MegaManColorAddressJ : MegaManColorAddressU; // Change 8 robot master weapon colors for (int i = 0; i < 8; i++) { byte dark = DarkColors.Dequeue(); byte light = LightColors.Dequeue(); int pos = startAddress + 0x04 + i * 0x04; p.Add(pos, light, String.Format("{0} Weapon Color Light", ((EDmgVsBoss.Offset)i).ToString())); p.Add(pos+1, dark, String.Format("{0} Weapon Color Dark", ((EDmgVsBoss.Offset)i).ToString())); if (i == 0) { //0x03DE49 - H charge colors // 0F 15 - flash neutral color (15 = weapon color) // 31 15 - flash lv 1(outline only; keep 15 from weapon color) // 35 2C - flash lv 2 // 30 30 - flash lv 3 p.Add(0x03DE4A, dark, "Heat Weapon Charge Color 1"); p.Add(0x03DE4C, dark, "Heat Weapon Charge Color 2"); } } // Change 3 Item colors byte itemColor = DarkColors.Dequeue(); for (int i = 0; i < 3; i++) { p.Add(startAddress + 0x25 + i * 0x04, itemColor, String.Format("Item {0} Dark Color", i+1)); } }
protected void ChangeWood(Patch Patch, Random r) { int rInt = 0; double rDbl = 0.0; byte[] xVels; // Woodman AI // Some unused addresses for later: //0x02C567 - Falling leaf y-pos start, 0x20 //0x03DA34 - Leaf shield y-velocity while it's attached to woodman, lol. //0x02C537 - Delay between leaves 0x12. Do 0x06 to 0x20. rInt = r.Next(0x20 - 0x06 + 1) + 0x06; Patch.Add(0x02C537, (byte)rInt, "Woodman Leaf Spacing Delay"); //0x02C5DD - Jump height, 0x04. Do 0x03 to 0x08. rInt = r.Next(0x08 - 0x03 + 1) + 0x03; Patch.Add(0x02C5DD, (byte)rInt, "Woodman Jump Y-Velocity"); //0x02C5E2 - Jump distance, 0x01. Do 0x01 to 0x04. rInt = r.Next(0x04 - 0x01 + 1) + 0x01; Patch.Add(0x02C5E2, (byte)rInt, "Woodman Jump X-Velocity"); //0x02C5A9 - Shield launch speed, 0x04. Do 0x01 to 0x08. rInt = r.Next(0x08 - 0x01 + 1) + 0x01; Patch.Add(0x02C5A9, (byte)rInt, "Woodman Shield Launch X-Velocity"); //0x02C553 - Number of falling leaves, 0x03. Do 0x02 20% of the time. rDbl = r.NextDouble(); if (rDbl > 0.8) Patch.Add(0x02C553, 0x02, "Woodman Falling Leaf Quantity"); //0x02C576 - Falling leaf x-vel, 0x02. Do 0x01 or 0x02, but with a 10% chance for 0x00 and 10% for 0x03 xVels = new byte[] { 0x00, 0x03, 0x01,0x01,0x01,0x01, 0x02,0x02,0x02,0x02, }; rInt = r.Next(xVels.Length); Patch.Add(0x02C576, xVels[rInt], "Woodman Falling Leaf X-Velocity"); //0x03D8F6 - 0x02, change to 0x06 for an interesting leaf shield pattern 25% of the time rDbl = r.NextDouble(); if (rDbl > 0.66) { Patch.Add(0x03D8F6, 0x06, "Woodman Leaf Shield Pattern"); } //0x03B855 - Leaf fall speed(sort of ?) 0x20. // Decrease value to increase speed. At 0x40, it doesn't fall. // 20% of the time, change to a high number to instantly despawn leaves for a fast pattern. // Do from 0x00 to 0x24. Make less than 0x1A a lower chance. int yVel; rDbl = r.NextDouble(); if (rDbl > 0.8) { yVel = 0xA0; // Leaves go upwards } else { xVels = new byte[] { 0x00, 0x04, 0x08, 0x0C, 0x10, 0x14, 0x18, 0x1C, // Fall faster 0x1D, 0x1E, 0x20, 0x21, 0x22, 0x23, 0x24 // Fall slower }; rInt = r.Next(xVels.Length); yVel = xVels[rInt]; } Patch.Add(0x03B855, (byte)yVel, "Woodman Falling Leaf Y-Velocity"); }
protected void ChangeQuick(Patch Patch, Random r) { // Q autofire delay, default 0x0B // Do from 0x05 to 0x12 int autoFireDelay = r.Next(0x0D) + 0x05; Patch.Add(0x03DB54, (byte)autoFireDelay, "(Q) | Autofire Delay"); // Q max shots, default 0x05 // Do from 0x03 to 0x07(2 to 6 shots) int maxShots = r.Next(0x04) + 0x03; Patch.Add(0x03DB5C, (byte)maxShots, "(Q) | Max Shots"); // 0x03DB6F - Q sound effect ESoundID sound = GetRandomSound(r); Patch.Add(0x03DB6F, (byte)sound, "(Q) | Sound"); // Q shots per ammo tick, default 0x08 // Do from 0x04 to 0x0A ? int magSize = r.Next(0x06) + 0x04; Patch.Add(0x03DB78, (byte)magSize, "(Q) | Shots Per Ammo Tick"); AmmoUsage.Add(1d / (double)magSize); // Q behavior, distance, default 0x12 // Do from 0x0A to 0x20 ? int distance = r.Next(0x16) + 0x0A; Patch.Add(0x03DFE2, (byte)distance, "(Q) | Travel Distance"); // Q behavior, initial angle, default 0x4B // Do from 0x00 to 0x60 ? int angle1 = r.Next(0x60); Patch.Add(0x03DFEA, (byte)angle1, "(Q) | Initial Angle"); //0x03DFF2 - Q behavior, weird, default 0x00 // Don't use, but change to 0x01 for dumb effect // Q behavior, angle, default 0x40 // 0x40 - (GOOD)Normal // 0x80 - (GOOD / HARD) Disappears(doesn't return) // 0x00 - (GOOD)Sine wave // 0x03 - (GOOD)Float downwards(interesting behavior when changing other byte) // 0x04, 05 - Float downwards(short, not different enough from 03) // 0x06 - Float downwards(faster) int[] angle2s = new int[] { 0x40, 0x80, 0x00, 0x03 }; int rIndex = r.Next(angle2s.Length); int angle2 = angle2s[rIndex]; Patch.Add(0x03DFFF, (byte)angle2, "(Q) | Secondary Angle"); // Q behavior, time before disappearing on return, default 0x23 // Do from 0x1E to 0x30 int despawnDelay = r.Next(0x12) + 0x1E; Patch.Add(0x03E007, (byte)despawnDelay, "(Q) | Despawn Delay"); // Q behavior, return angle, default 0x4B // Do from 0x00 to 0x90 int angle3 = r.Next(0x90); Patch.Add(0x03E013, (byte)angle3, "(Q) | Return Angle"); //0x03E01B - Q behavior, weird, default 0x00 // Change to 0x01 for interesting effects }
public void Randomize(Patch p, Random r) { // Write in new weapon names for (int i = 0; i < 8; i++) { int offset = offsetAtomicFire + i * 0x10; string name = GetRandomName(r); char[] chars = name.ToCharArray(); for (int j = 0; j < MAX_CHARS; j++) { if (j < chars.Length) { byte b = Convert.ToByte(chars[j]); p.Add(offset + j, b, String.Format("Weapon Name {0} Char #{1}: {2}", ((EDmgVsBoss.Offset)i).Name, j, chars[j].ToString())); } else { p.Add(offset + j, Convert.ToByte('@'), String.Format("Weapon Name {0} Char #{1}: @", ((EDmgVsBoss.Offset)i).Name, j)); } } } // Erase "Boomerang" for now for (int i = 0; i < 10; i++) { p.Add(0x037f5e + i, Convert.ToByte('@'), String.Format("Quick Boomerang Name Erase Char #{0}: @", i)); } // Write in new weapon letters for (int i = 0; i < 8; i++) { int randLetter = 0x41 + r.Next(26); p.Add(offsetLetters + i, (byte)randLetter, String.Format("Weapon Get {0} Letter: {1}", ((EDmgVsBoss.Offset)i).Name, Convert.ToChar(randLetter).ToString())); } }
protected void ChangeWood(Patch Patch, Random r) { //0x03DEDA - W deploy time (0C) // Can change from 06 to 12 // Note: Shield glitches on odd numbers. Use evens only. int[] deployDelays = new int[] { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x10, 0x14, 0x1C, 0x22 }; int rIndex = r.Next(deployDelays.Length); int deployDelay = deployDelays[rIndex]; Patch.Add(0x03DEDA, (byte)deployDelay, "(W) | Deploy Delay"); //0x03DF0D - W spin animation? (01) //0x03DF1B - W delay between sounds(07) // TODO //0x03DF1F - W deploy sound effect ESoundID sound = GetRandomSound(r); Patch.Add(0x03DF1F, (byte)sound, "(W) | Deploy Sound"); //0x03DF41 - W which directions the shield is allowed to launch in (F0) // Can prevent launching left / right, up / down, etc //0x03DF4D - W launch directions (C0) // Don't use this one //0x03DF50 - W launch x - direction subroutine // 50% chance to have inverted x controls // Change "LSR AND #40" (4A 29 40) to simply "AND #40" to implement double rTestReverseX = r.NextDouble(); if (rTestReverseX > 0.5) { Patch.Add(0x03DF50, 0x29, "(W) | Reverse X-Direction Code AND"); Patch.Add(0x03DF51, 0x40, "(W) | Reverse X-Direction Code #$40"); Patch.Add(0x03DF52, 0xEA, "(W) | Reverse X-Direction Code NOP"); // (best thing i could find to simply "skip" a line) } //0x03DF59 - W x - speed(04) (do 0x02-0x08) int launchVel = r.Next(0x06) + 0x02; Patch.Add(0x03DF59, (byte)launchVel, "(W) | Launch X-Velocity Integer"); //0x03DF64 - W launch y - direction(10) // Change to 0x20 to reverse, 50% chance int reverseY = 0x10; double rTestReverseY = r.NextDouble(); if (rTestReverseY > 0.5) reverseY = 0x20; Patch.Add(0x03DF64, (byte)reverseY, "(W) | Launch Y-Direction"); //0x03DF72 - W ammo usage (3) (do from 1 to 3) int ammoUse = r.Next(0x03) + 0x01; Patch.Add(0x03DF72, (byte)ammoUse, "(W) | Ammo Usage"); AmmoUsage.Add(ammoUse); //0x03DF7D - W y - speed(04) Patch.Add(0x03DF7D, (byte)launchVel, "(W) | Launch Y-Velocity Integer"); }
private void RandomizeBossColors(Patch p, Random r) { //// Robot Master Color Palettes List<int> SolidColorSolo = new List<int> { 0x00B4EA, // Wood leaf color 0x29 0x01B4A1, // Metal blade color 0x30 }; List<int> SolidColorPair1Main = new List<int> { 0x01F4ED, // Clash red color 0x16 0x0174B7, // Flash blue color 0x12 0x0074B4, // Air projectile blue color 0x11 0x00B4ED, // Wood orange color 0x17 }; List<int> SolidColorPair1White = new List<int> { 0x01F4EC, // Clash white color 0x30 0x0174B6, // Flash white color 0x30 0x0074B3, // Air projectile white color 0x30 0x00B4EC, // Wood white color 0x36 }; List<int> SolidColorPair2Dark = new List<int> { 0x0034B4, // Heat projectile red color 0x15 0x0034B7, // Heat red color 0x15 0x0074B7, // Air blue color 0x11 0x0134C6, // Quick intro color 2 0x28 0x0134C9, // Quick red color 0x15 0x01B4A5, // Metal red color 0x15 0x00F4B7, // Bubble green color 0x19 }; List<int> SolidColorPair2Light = new List<int> { 0x0034B3, // Heat projectile yellow color 0x28 0x0034B6, // Heat yellow color 0x28 0x0074B6, // Air yellow color 0x28 0x0134C5, // Quick intro color 1 0x30 0x0134C8, // Quick yellow color 0x28 0x01B4A4, // Metal yellow color 0x28 0x00F4B6, // Bubble white & projectile color 0x30 }; // Colors for bosses with 1 solid color and 1 white List<byte> goodSolidColors = new List<byte>() { 0x0F,0x20,0x31,0x22,0x03,0x23,0x14,0x05,0x15,0x16,0x07,0x27,0x28,0x09,0x1A,0x2A,0x0B,0x2B,0x0C,0x1C, }; // Colors for bosses with a dark and a light color List<byte> goodDarkColors = new List<byte>() { 0x01,0x12,0x03,0x04,0x05,0x16,0x07,0x18,0x09,0x1A,0x0B,0x0C,0x0F,0x00, }; List<byte> goodLightColors = new List<byte>() { 0x21,0x32,0x23,0x34,0x15,0x26,0x27,0x28,0x29,0x3A,0x1B,0x2C,0x10,0x20, }; // Dark colors only List<byte> darkOnly = new List<byte>() { 0x0F,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0A,0x0B,0x0C }; // Medium colors only List<byte> mediumOnly = new List<byte>() { 0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C }; // Light colors only List<byte> lightOnly = new List<byte>() { 0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C }; int rColor = 0; for (int i = 0; i < SolidColorSolo.Count; i++) { rColor = r.Next(goodSolidColors.Count); p.Add(SolidColorSolo[i], goodSolidColors[rColor], String.Format("Robot Master Color")); } for (int i = 0; i < SolidColorPair1Main.Count; i++) { p.Add(SolidColorPair1Main[i], goodSolidColors[rColor], String.Format("Robot Master Color")); // Make 2nd color brighter. If already bright, make white. rColor = r.Next(goodSolidColors.Count); int lightColor = goodSolidColors[rColor] + 0x10; if (lightColor > 0x3C) { lightColor = 0x30; } p.Add(SolidColorPair1White[i], (byte)lightColor, String.Format("Robot Master Color")); } for (int i = 0; i < SolidColorPair2Dark.Count; i++) { rColor = r.Next(SolidColorPair2Dark.Count); p.Add(SolidColorPair2Dark[i], goodDarkColors[rColor], String.Format("Robot Master Color")); rColor = r.Next(SolidColorPair2Light.Count); p.Add(SolidColorPair2Light[i], goodLightColors[rColor], String.Format("Robot Master Color")); } // Wily Machine // choose main body color rColor = r.Next(darkOnly.Count); byte shade0 = darkOnly[rColor]; byte shade1 = (byte)(shade0 + 0x10); if (shade0 == 0x0F) shade1 = 0x00; // Dark gray up from black byte shade2 = (byte)(shade1 + 0x10); p.Add(0x02D7D5, shade2, String.Format("Wily Machine Light-Gold Color")); // 0x27 p.Add(0x02D7D2, shade1, String.Format("Wily Machine Gold 1 Color")); // 0x17 p.Add(0x02D7D6, shade1, String.Format("Wily Machine Gold 2 Color")); // 0x17 p.Add(0x02D7DA, shade1, String.Format("Wily Machine Gold 3 Color")); // 0x17 p.Add(0x02D7D7, shade0, String.Format("Wily Machine Dark Gold 1 Color")); // 0x07 p.Add(0x02D7DB, shade0, String.Format("Wily Machine Dark Gold 2 Color")); // 0x07 // choose front color rColor = r.Next(mediumOnly.Count); shade0 = mediumOnly[rColor]; shade1 = (byte)(shade0 + 0x20); p.Add(0x02D7D1, shade0, String.Format("Wily Machine Red 1 Color")); // 0x15 p.Add(0x02D7D9, shade0, String.Format("Wily Machine Red 2 Color")); // 0x15 p.Add(0x02D7D3, shade1, String.Format("Wily Machine Light Red 1 Color")); // 0x15 // Alien //0x02DC74(3 bytes) Alien Body, static 0x16 0x29 0x19 //0x02DC78(3 bytes) Alien Head, static 0x16 0x29 0x19 // Looks good as 4 separate color groups, should be easy. Save the animations for later. List<byte> mediumAndLight = new List<byte>(mediumOnly); mediumAndLight.AddRange(lightOnly); rColor = r.Next(mediumAndLight.Count); shade0 = mediumAndLight[rColor]; p.Add(0x02DC74, shade0, String.Format("Alien Body Solid Color")); rColor = r.Next(mediumAndLight.Count); shade1 = mediumAndLight[rColor]; p.Add(0x02DC78, shade1, String.Format("Alien Head Solid Color")); rColor = r.Next(mediumOnly.Count); shade0 = mediumOnly[rColor]; p.Add(0x02DC76, shade0, String.Format("Alien Body Dark Color")); shade1 = (byte)(shade0 + 0x10); p.Add(0x02DC75, shade1, String.Format("Alien Body Light Color")); rColor = r.Next(mediumOnly.Count); shade0 = mediumOnly[rColor]; p.Add(0x02DC7A, shade0, String.Format("Alien Body Dark Color")); shade1 = (byte)(shade0 + 0x10); p.Add(0x02DC79, shade1, String.Format("Alien Head Light Color")); }
/// <summary> /// Shuffle which Robot Master awards which weapon. /// </summary> public void Randomize(Patch Patch, Random r) { // StageBeat Address Value // ----------------------------- // Heat Man 0x03C289 1 // Air Man 0x03C28A 2 // Wood Man 0x03C28B 4 // Bubble Man 0x03C28C 8 // Quick Man 0x03C28D 16 // Flash Man 0x03C28E 32 // Metal Man 0x03C28F 64 // Crash Man 0x03C290 128 NewWeaponOrder.Shuffle(r); // Create table for which weapon is awarded by which robot master // This also affects which portrait is blacked out on the stage select // This also affects which teleporter deactivates after defeating a Wily 5 refight boss for (int i = 0; i < 8; i++) { Patch.Add((int)(ERMStageWeaponAddress.HeatMan + i), (byte)NewWeaponOrder[i], String.Format("{0}man Weapon Get", ((EDmgVsBoss.Offset)i).ToString())); } // Create a copy of the default weapon order table to be used by teleporter function // This is needed to fix teleporters breaking from the new weapon order. // Unused space at end of bank Patch.Add(0x03f310, (byte)ERMWeaponValueBit.HeatMan, "Custom Array of Default Weapon Order"); Patch.Add(0x03f311, (byte)ERMWeaponValueBit.AirMan, "Custom Array of Default Weapon Order"); Patch.Add(0x03f312, (byte)ERMWeaponValueBit.WoodMan, "Custom Array of Default Weapon Order"); Patch.Add(0x03f313, (byte)ERMWeaponValueBit.BubbleMan, "Custom Array of Default Weapon Order"); Patch.Add(0x03f314, (byte)ERMWeaponValueBit.QuickMan, "Custom Array of Default Weapon Order"); Patch.Add(0x03f315, (byte)ERMWeaponValueBit.FlashMan, "Custom Array of Default Weapon Order"); Patch.Add(0x03f316, (byte)ERMWeaponValueBit.MetalMan, "Custom Array of Default Weapon Order"); Patch.Add(0x03f317, (byte)ERMWeaponValueBit.CrashMan, "Custom Array of Default Weapon Order"); // Change function to call $f300 instead of $c279 when looking up defeated refight boss to // get our default weapon table, fixing the teleporter softlock Patch.Add(0x03843b, 0x00, "Teleporter Fix Custom Function Call Byte 1"); Patch.Add(0x03843c, 0xf3, "Teleporter Fix Custom Function Call Byte 2"); // Create table for which stage is selectable on the stage select screen (independent of it being blacked out) for (int i = 0; i < 8; i++) { Patch.Add((int)(ERMStageSelect.FirstStageInMemory + i), (byte)NewWeaponOrder[i], "Selectable Stage Fix for Random Weapon Get"); } }