protected void ChangeCrash(Patch in_Patch, ISeed in_Seed) { //0x03D4AD - C x-speed on shoot (04) (do 2-7) Int32 xVel = in_Seed.NextInt32(0x06) + 0x02; in_Patch.Add(0x03D4AD, (Byte)xVel, "(C) | X-Velocity (Integer)"); //0x03D4D7 - C y-speed integer for explosion (up only) (0) // TODO: Figure how this works more to apply in all directions // For now, 25% chance to make this move upward at 2px/fr Int32 yVelExplode = 0x00; Double rTestYVelExplode = in_Seed.NextDouble(); if (rTestYVelExplode > 0.75) { yVelExplode = in_Seed.NextInt32(2) + 0x01; } in_Patch.Add(0x03D4D7, (Byte)yVelExplode, "(C) | X-Velocity (Explosion)"); //0x03DB99 - C ammo per shot (04) (do 1-3) Int32 ammoUse = in_Seed.NextInt32(0x03) + 0x01; in_Patch.Add(0x03DB99, (Byte)ammoUse, "(C) | Ammo Usage"); AmmoUsage.Add(ammoUse); // 0x03DB9F - C explosion type? (02) // Change to 03 to "single explosion" type. Most other values break the game. // For now, 50% chance to change Int32 multiExplode = 0x02; Double rTestMultiExplode = in_Seed.NextDouble(); if (rTestMultiExplode > 0.50) { multiExplode = 0x03; } in_Patch.Add(0x03DB9F, (Byte)multiExplode, "(C) | Explosion Type"); //0x03DBA6 - C shoot sound effect (24) ESoundID sound = this.GetRandomSound(in_Seed); in_Patch.Add(0x03DBA6, (Byte)sound, "(C) | Sound Shoot"); //0x03E089 - C attach sound effect (2E) sound = this.GetRandomSound(in_Seed); in_Patch.Add(0x03E089, (Byte)sound, "(C) | Sound Attach"); //0x03E09C - C delay before explosion (7E) (do 01 to C0) Int32 delayExplosion = in_Seed.NextInt32(0xBF) + 0x01; in_Patch.Add(0x03E09C, (Byte)delayExplosion, "(C) | Explode Delay"); //0x03E0DA - C explode sound effect sound = this.GetRandomSound(in_Seed); in_Patch.Add(0x03E0DA, (Byte)sound, "(C) | Sound Explode"); }
/// <summary> /// Get a random unique sound for a weapon to use /// </summary> /// <param name="r"></param> /// <returns>Sound ID Byte</returns> private ESoundID GetRandomSound(ISeed in_Seed) { Int32 i = in_Seed.NextInt32(sounds.Count); ESoundID sound = sounds.ElementAt(i); sounds.RemoveAt(i); // Pick a random charge level if charge sound is chosen if (sound == ESoundID.WeaponH_Charge0) { i = in_Seed.NextInt32(3); sound = ESoundID.WeaponH_Charge0 + i; } return(sound); }
public void RandomizeAndWrite(Patch in_Patch, ISeed in_Seed, Int32 setNumber) { this.Index = in_Seed.NextInt32(ColorBytes.Count); for (Int32 i = 0; i < this.addresses.Length; i++) { in_Patch.Add( this.addresses[i], (Byte)this.ColorBytes[this.Index][i], String.Format("Color Set {0} (Index Chosen: {1}) Value #{2}", setNumber, Index, i)); } }
protected void ChangeItem1(Patch in_Patch, ISeed in_Seed) { Int32 rInt; //0x03E1A0(0F:E190) - Item 1 Update Subroutine //0x03E1AC - Delay before Item 1 starts flashing 0xBB (make 0x00 for infinite) (do from 0x30 to 0xFF) rInt = in_Seed.NextInt32(0xFF - 0x30 + 1) + 0x30; in_Patch.Add(0x03E1AC, (Byte)rInt, "(1) | Delay Before Flashing"); //0x03E1BF - Delay before Item 1 disappears after flashing 0x3E (make 0x00 for infinite) (do from 0x20 to 0x70) rInt = in_Seed.NextInt32(0x70 - 0x20 + 1) + 0x20; in_Patch.Add(0x03E1BF, (Byte)rInt, "(1) | Delay Before Despawn"); //0x03E1E2 - y-pos offset for Mega Man once standing on Item 1(0x04) //0x03E1E7 - width of Item 1 surface for Mega Man to stand on(0x14) //0x03E1F0 - Distance above Item to check for despawning(0x1D) //0x03D4C2 - y-vel fraction(0x41) Int32[] rXVelFracs = new Int32[] { 0x00, 0x20, 0x41, 0x50 }; rInt = in_Seed.NextInt32(rXVelFracs.Length); in_Patch.Add(0x03D4C2, (Byte)rXVelFracs[rInt], "(1) | Y-Velocity (Fraction)"); }
/* * private void ReadLevelComponentJSON(Patch p, Random r) * { * ComponentManager component = JsonConvert.DeserializeObject<ComponentManager>(Properties.Resources.level_components); * * foreach (var levelComponent in component.LevelComponents) * { * // Get a random variation * var variation = levelComponent.Variations[r.Next(levelComponent.Variations.Count)]; * * // Add patch for each element of the tsamap * Int32 startAddress = Convert.ToInt32(levelComponent.StartAddress, 16); * for (Int32 i = 0; i < variation.TsaMap.Length; i++) * { * // Parse hex String * Byte tsaVal = Convert.ToByte(variation.TsaMap[i], 16); * * p.Add(startAddress + i, tsaVal, $"Tilemap data for {levelComponent.Name} variation \"{variation.Name}\""); * } * } * * Console.WriteLine(component.LevelComponents); * } */ private static void ChangeW4FloorsBeforeSpikes(Patch in_Patch, ISeed in_Seed) { // Choose 2 of the 5 32x32 tiles to be fake Int32 tileA = in_Seed.NextInt32(5); Int32 tileB = in_Seed.NextInt32(4); // Make sure 2nd tile chosen is different if (tileB == tileA) { tileB++; } for (Int32 i = 0; i < 5; i++) { if (i == tileA || i == tileB) { in_Patch.Add(0x00CB5C + i * 8, 0x94, String.Format("Wily 4 Room 4 Tile {0} (fake)", i)); } else { in_Patch.Add(0x00CB5C + i * 8, 0x85, String.Format("Wily 4 Room 4 Tile {0} (solid)", i)); } } }
private static void ChangeW4FloorsSpikePit(Patch in_Patch, ISeed in_Seed) { // 5 tiles, but since two adjacent must construct a gap, 4 possible gaps. Choose 1 random gap. Int32 gap = in_Seed.NextInt32(4); for (Int32 i = 0; i < 4; i++) { if (i == gap) { in_Patch.Add(0x00CB9A + i * 8, 0x9B, String.Format("Wily 4 Room 5 Tile {0} (gap on right)", i)); in_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 { in_Patch.Add(0x00CB9A + i * 8, 0x9D, String.Format("Wily 4 Room 5 Tile {0} (solid)", i)); } } }
protected void ChangeMetal(Patch in_Patch, ISeed in_Seed) { //0x03DBB6 - M max shots (04) (change to 0x02-0x05, or 1-4) Int32 maxShots = in_Seed.NextInt32(0x04) + 0x02; in_Patch.Add(0x03DBB6, (Byte)maxShots, "(M) | Max Shots"); //0x03DBC9 - M sound effect(23) ESoundID sound = this.GetRandomSound(in_Seed); in_Patch.Add(0x03DBC9, (Byte)sound, "(M) | Sound"); //0x03DBD2 - M shots per ammo tick(04) (change to 1-5) Int32 magSize = in_Seed.NextInt32(0x05) + 0x01; in_Patch.Add(0x03DBD2, (Byte)magSize, "(M) | Shots Per Ammo Tick"); AmmoUsage.Add(1d / magSize); // Speeds. Change each to be 2-7. Diagonal will be half each, rounded up. Int32 velX = in_Seed.NextInt32(0x06) + 0x02; Int32 velY = in_Seed.NextInt32(0x06) + 0x02; Int32 halfY = (Int32)Math.Ceiling((Double)velY / 2d); Int32 halfX = (Int32)Math.Ceiling((Double)velX / 2d); //0x03DC12 - M y - speed, holding up(04) in_Patch.Add(0x03DC12, (Byte)velY, "(M) | Y-Velocity Up"); //0x03DC31 - M x - speed, no direction(04) in_Patch.Add(0x03DC31, (Byte)velX, "(M) | X-Velocity Neutral"); //0x03DC35 - M x - speed, holding left(04) in_Patch.Add(0x03DC35, (Byte)velX, "(M) | X-Velocity Left"); //0x03DC39 - M x - speed, holding right(04) in_Patch.Add(0x03DC39, (Byte)velX, "(M) | X-Velocity Right"); //0x03DC13 - M y - speed, holding down(FC) in_Patch.Add(0x03DC13, (Byte)(0x00 - (Byte)velY), "(M) | Y-Velocity Down"); //0x03DC16 - M y - speed, holding up + left(02) in_Patch.Add(0x03DC16, (Byte)halfY, "(M) | Y-Velocity Up+Left"); //0x03DC17 - M y - speed, holding down + left(FD) in_Patch.Add(0x03DC17, (Byte)(0x00 - (Byte)halfY), "(M) | Y-Velocity Down+Left"); //0x03DC1A - M y - speed, holding up + right(02) in_Patch.Add(0x03DC1A, (Byte)halfY, "(M) | Y-Velocity Up+Right"); //0x03DC1B - M y - speed, holding down + right(FD) in_Patch.Add(0x03DC1B, (Byte)(0x00 - (Byte)halfY), "(M) | Y-Velocity Down+Right"); //0x03DC36 - M x - speed, holding up + left(02) in_Patch.Add(0x03DC36, (Byte)halfX, "(M) | X-Velocity Up+Left"); //0x03DC37 - M x - speed, holding down + left(02) in_Patch.Add(0x03DC37, (Byte)halfX, "(M) | X-Velocity Down+Left"); //0x03DC3A - M x - speed, holding up + right(02) in_Patch.Add(0x03DC3A, (Byte)halfX, "(M) | X-Velocity Up+Right"); //0x03DC3B - M x - speed, holding down + right(02) in_Patch.Add(0x03DC3B, (Byte)halfX, "(M) | X-Velocity Down+Right"); }
protected void ChangeFlash(Patch in_Patch, ISeed in_Seed) { //0x03DC59 - F sound (21) ESoundID sound = this.GetRandomSound(in_Seed); in_Patch.Add(0x03DC59, (Byte)sound, "(F) | Sound"); //0x03E172 - F custom subroutine for reusable weapon // 75% chance to occur Double rTestFChange = in_Seed.NextDouble(); if (rTestFChange > 0.25) { // 0x03E175 - New ammo-usage address. Byte[] ammos = new Byte[] { 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; Int32 rIndex = in_Seed.NextInt32(7); Byte ammo = ammos[rIndex]; // Change ammo-tick subroutine into one that resumes time Byte[] sub = new Byte[] { 0xA5, 0xA1, // LDA ammoFlash = #$0A 0xE9, ammo, // SBC {ammo use} 0x85, 0xA1, // STA ammoFlash = #$0A 0x5E, 0x20, 0x04, // LSR megaXDir, X @ megaXDir = #$C0 0xA9, 0x00, // LDA #$00 0x85, 0xAA, // STA $00AA = #$00 0x85, 0x50, // STA $0050 = #$00 ; we erased LDA #$01, but doesn't seem to hurt }; for (Int32 i = 0; i < sub.Length; i++) { in_Patch.Add(0x03E172 + i, sub[i], "(F) | Reusable Time-Stopper Subroutine"); } // 0x03E16E and 0x03D49D is the new duration, in frames. They must both be the same value. Int32 duration = 0x01; switch (ammo) { case 0x02: duration = in_Seed.NextInt32(70) + 20; break; case 0x03: duration = in_Seed.NextInt32(60) + 40; break; case 0x04: duration = in_Seed.NextInt32(60) + 60; break; case 0x05: duration = in_Seed.NextInt32(60) + 80; break; case 0x06: duration = in_Seed.NextInt32(60) + 100; break; case 0x07: duration = in_Seed.NextInt32(85) + 120; break; case 0x08: duration = in_Seed.NextInt32(115) + 140; break; default: break; } in_Patch.Add(0x03E16E, (Byte)duration, "(F) | Freeze Duration (1)"); in_Patch.Add(0x03D49D, (Byte)duration, "(F) | Freeze Duration (2)"); // Finally, a fix is needed to prevent ammo underflow // 0x03DC41 - WpnMove_FStart sub = new Byte[] { 0xA2, 0x02, // LDX #$02 0xAD, 0x22, 0x04, // LDA $0422 = #$61 0x30, 0x1E, // BMI WpnMove_Done 0xA5, 0xA1, // LDA ammoFlash = #$02 0xC9, 0x07, // CMP #$07 0x90, 0x18, // BCC WpnMove_Done }; for (Int32 i = 0; i < sub.Length; i++) { in_Patch.Add(0x03DC41 + i, sub[i], "(F) | Reusable Time-Stopper Ammo-Underflow Fix"); } } else { // Standard Time Stopper, but modify the tick frequency // Default 0x0F. For 28 ticks, that's 7 seconds. Modify to be 4-10 seconds, which is about 0x09 to 0x16 Int32 tickDelay = in_Seed.NextInt32(0x13) + 9; in_Patch.Add(0x03E16E, (Byte)tickDelay, "(F) | Time-Stopper Ammo Tick Delay (1)"); in_Patch.Add(0x03D49D, (Byte)tickDelay, "(F) | Time-Stopper Ammo Tick Delay (2)"); } }
protected void ChangeQuick(Patch in_Patch, ISeed in_Seed) { // Q autofire delay, default 0x0B // Do from 0x05 to 0x12 Int32 autoFireDelay = in_Seed.NextInt32(0x0D) + 0x05; in_Patch.Add(0x03DB54, (Byte)autoFireDelay, "(Q) | Autofire Delay"); // Q max shots, default 0x05 // Do from 0x03 to 0x07(2 to 6 shots) Int32 maxShots = in_Seed.NextInt32(0x04) + 0x03; in_Patch.Add(0x03DB5C, (Byte)maxShots, "(Q) | Max Shots"); // 0x03DB6F - Q sound effect ESoundID sound = this.GetRandomSound(in_Seed); in_Patch.Add(0x03DB6F, (Byte)sound, "(Q) | Sound"); // Q shots per ammo tick, default 0x08 // Do from 0x04 to 0x0A ? Int32 magSize = in_Seed.NextInt32(0x06) + 0x04; in_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 ? Int32 distance = in_Seed.NextInt32(0x16) + 0x0A; in_Patch.Add(0x03DFE2, (Byte)distance, "(Q) | Travel Distance"); // Q behavior, initial angle, default 0x4B // Do from 0x00 to 0x60 ? Int32 angle1 = in_Seed.NextInt32(0x60); in_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) Int32[] angle2s = new Int32[] { 0x40, 0x80, 0x00, 0x03 }; Int32 rIndex = in_Seed.NextInt32(angle2s.Length); Int32 angle2 = angle2s[rIndex]; in_Patch.Add(0x03DFFF, (Byte)angle2, "(Q) | Secondary Angle"); // Q behavior, time before disappearing on return, default 0x23 // Do from 0x1E to 0x30 Int32 despawnDelay = in_Seed.NextInt32(0x12) + 0x1E; in_Patch.Add(0x03E007, (Byte)despawnDelay, "(Q) | Despawn Delay"); // Q behavior, return angle, default 0x4B // Do from 0x00 to 0x90 Int32 angle3 = in_Seed.NextInt32(0x90); in_Patch.Add(0x03E013, (Byte)angle3, "(Q) | Return Angle"); //0x03E01B - Q behavior, weird, default 0x00 // Change to 0x01 for interesting effects }
protected void ChangeBubble(Patch in_Patch, ISeed in_Seed) { //0x03D4AB - B x - speed on shoot (0x01) (do 1-3) Int32 xVelShoot = in_Seed.NextInt32(0x03) + 0x01; in_Patch.Add(0x03D4AB, (Byte)xVelShoot, "(B) | X-Velocity Shoot (Integer)"); //0x03D4CF - B y - speed on shoot(0x02) (do 0-6) Int32 yVelShoot = in_Seed.NextInt32(0x06); in_Patch.Add(0x03D4CF, (Byte)yVelShoot, "(B) | Y-Velocity Shoot (Integer)"); //0x03DB21 - B max shots (0x03) (do 2-5, i.e. 1-4 total projectiles) // Valid from 0x02 - 0x0F. Lags a bunch >= 0x06. Int32 maxShots = in_Seed.NextInt32(0x04) + 0x02; in_Patch.Add(0x03DB21, (Byte)maxShots, "(B) | Max Shots"); //0x03DB2F - B weapon type(0x04) // Don't do //0x03DB34 - B sound effect ESoundID sound = this.GetRandomSound(in_Seed); in_Patch.Add(0x03DB34, (Byte)sound, "(B) | Sound"); //0x03DB3D - B shots per ammo tick (0x02) (do 1-4) Int32 magSize = in_Seed.NextInt32(0x04) + 0x01; in_Patch.Add(0x03DB3D, (Byte)magSize, "(B) | Shots Per Ammo Tick"); AmmoUsage.Add(1d / (Double)magSize); //0x03DFA4 - B y - pos to embed in surface(0xFF) // Dumb //0x03DFA9 - B x - speed on surface (0x02) // 0x01 - 0x04 ? Int32 xVelRoll = in_Seed.NextInt32(0x04) + 0x01; in_Patch.Add(0x03DFA9, (Byte)xVelRoll, "(B) | X-Velocity Surface (Integer)"); //0x03DFC0 - B x - speed after falling from ledge (0x00) // Make 50% chance to be 0, or 1-5 Int32 xVelFall = 0x00; Double rTestXFallSpeed = in_Seed.NextDouble(); if (rTestXFallSpeed > 0.5) { xVelFall = in_Seed.NextInt32(0x05) + 0x01; } in_Patch.Add(0x03DFC0, (Byte)xVelFall, "(B) | X-Velocity Fall (Integer)"); //0x03DFC8 - B y - speed after falling(0xFE) // Either 0xFA - 0xFF or 0x01 - 0x06 Int32[] yFallVels = new Int32[] { 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 }; Int32 rIndex = in_Seed.NextInt32(yFallVels.Length); Int32 yFallVel = yFallVels[rIndex]; in_Patch.Add(0x03DFC8, (Byte)yFallVel, "(B) | Y-Velocity Fall (Integer)"); }
protected void ChangeWood(Patch in_Patch, ISeed in_Seed) { //0x03DEDA - W deploy time (0C) // Can change from 06 to 12 // Note: Shield glitches on odd numbers. Use evens only. Int32[] deployDelays = new Int32[] { 0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x10, 0x14, 0x1C, 0x22 }; Int32 rIndex = in_Seed.NextInt32(deployDelays.Length); Int32 deployDelay = deployDelays[rIndex]; in_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 = this.GetRandomSound(in_Seed); in_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 = in_Seed.NextDouble(); if (rTestReverseX > 0.5) { in_Patch.Add(0x03DF50, 0x29, "(W) | Reverse X-Direction Code AND"); in_Patch.Add(0x03DF51, 0x40, "(W) | Reverse X-Direction Code #$40"); in_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) Int32 launchVel = in_Seed.NextInt32(0x06) + 0x02; in_Patch.Add(0x03DF59, (Byte)launchVel, "(W) | Launch X-Velocity Integer"); //0x03DF64 - W launch y - direction(10) // Change to 0x20 to reverse, 50% chance Int32 reverseY = 0x10; Double rTestReverseY = in_Seed.NextDouble(); if (rTestReverseY > 0.5) { reverseY = 0x20; } in_Patch.Add(0x03DF64, (Byte)reverseY, "(W) | Launch Y-Direction"); //0x03DF72 - W ammo usage (3) (do from 1 to 3) Int32 ammoUse = in_Seed.NextInt32(0x03) + 0x01; in_Patch.Add(0x03DF72, (Byte)ammoUse, "(W) | Ammo Usage"); AmmoUsage.Add(ammoUse); //0x03DF7D - W y - speed(04) in_Patch.Add(0x03DF7D, (Byte)launchVel, "(W) | Launch Y-Velocity Integer"); }
protected void ChangeAir(Patch in_Patch, ISeed in_Seed) { //0x03DAD6 - A num projectiles, default 0x04 // Values 0x02 and 0x03 work, but larger values behave strangely Int32 numProjectiles = 0x04; Double rTestNumProjectiles = in_Seed.NextDouble(); if (rTestNumProjectiles > 0.80) { numProjectiles = 0x03; } else if (rTestNumProjectiles > 0.60) { numProjectiles = 0x02; } in_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 = this.GetRandomSound(in_Seed); in_Patch.Add(0x03DAE6, (Byte)sound, "(A) | Sound"); //0x03DAEE - A ammo used(0x02) Int32 ammoUse = in_Seed.NextInt32(0x02) + 0x01; in_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 Int32 yAccFrac = in_Seed.NextInt32(0x17) + 0x02; if (yAccFrac > 0x10) { // Double the addition of any acceleration value chosen above 0x10 yAccFrac += (yAccFrac - 0x10) * 2; } in_Patch.Add(0x03DE6E, (Byte)yAccFrac, "(A) | Y-Acceleration (fraction)"); //0x03DE76 - A projectile y-acceleration integer(00) // 15% chance to be significantly faster Int32 yAccInt = 0x00; Double rYAcc = in_Seed.NextDouble(); if (rYAcc > 0.85) { yAccInt = 0x01; } in_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 (Int32 i = 0; i < 3; i++) { Int32 xFracSpeed = in_Seed.NextInt32(0xFF) + 0x01; in_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) Int32[] xIntSpeeds = new Int32[] { 0x00, 0x01, 0x02, 0x04, 0x06 }; Int32 rIndex = 0; Int32 xIntSpeed = 0; for (Int32 i = 0; i < 3; i++) { rIndex = in_Seed.NextInt32(xIntSpeeds.Length); xIntSpeed = xIntSpeeds[rIndex]; in_Patch.Add(0x03DE81 + i, (Byte)xIntSpeed, String.Format("(A) | Projectile {0} X-Velocity (integer)", i + 1)); } }
protected void ChangeHeat(Patch in_Patch, ISeed in_Seed) { //0x03DE55 - H L1 Ammo use(01) //0x03DE56 - H L2 Ammo use(06) //0x03DE57 - H L3 Ammo use(0A) // 90% chance for L1 to be free, else cost 1 ammo // L2 ammo cost = L1 ammo cost // L3 will cost 1-4 ammo Double rTestL1Ammo = in_Seed.NextDouble(); Byte L1Ammo = (rTestL1Ammo > 0.1) ? (Byte)0 : (Byte)1; in_Patch.Add(0x03DE55, L1Ammo, $"(H) | Shot L1 Ammo Cost: {L1Ammo}"); in_Patch.Add(0x03DE56, L1Ammo, $"(H) | Shot L2 Ammo Cost: {L1Ammo}"); Byte[] bytes = new Byte[] { 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, }; Int32 rInt = in_Seed.NextInt32(bytes.Length); in_Patch.Add(0x03DE57, bytes[rInt], $"(H) | Shot L3 Ammo Cost: {bytes[rInt]}"); AmmoUsage.Add(bytes[rInt]); // Charge Behavior // 20% to have old charge behavior, 40% to skip 1 level, 40% to shoot L3 immediately Double rTestChargeType = in_Seed.NextDouble(); //0x03DD66 - H change these 4 bytes to 0xEA to skip L2 charge if (rTestChargeType < 0.8) { for (Int32 i = 0; i < 4; i++) { in_Patch.Add(0x03DD66 + i, 0xEA, $"(H) | Skip L2 Charge"); } } //0x03DD62 - H after doing the previous, change these 2 bytes to 0xEA to skip charge altogether if (rTestChargeType < 0.4) { in_Patch.Add(0x03DD62, 0xEA, $"(H) | Skip L1 Charge"); in_Patch.Add(0x03DD63, 0xEA, $"(H) | Skip L1 Charge"); //0x03DD95 - H change this from 0x0D to 0x14 to fire the charge on press instead of on release in_Patch.Add(0x03DD95, 0x14, $"(H) | Fire Shot On Press"); } // Charge delay for L2 will be between 0x10 and 0x80 frames //0x03DD61 - H L2 charge delay (0x7D) Int32 chargeDelayL2 = in_Seed.NextInt32(0x70) + 0x09; in_Patch.Add(0x03DD61, (Byte)chargeDelayL2, "(H) | L2 Charge Delay"); // Charge delay for L3 will L2 plus a value between 0x10 and 0x40 frames //0x03DD67 - H L3 charge delay(0xBB) Int32 chargeDelayL3 = in_Seed.NextInt32(0x30) + 0x09 + chargeDelayL2; if (rTestChargeType >= 0.8) { in_Patch.Add(0x03DD67, (Byte)chargeDelayL3, "(H) | L3 Charge Delay"); } //0x03DDEC - H shot sound effect(38) ESoundID sound = this.GetRandomSound(in_Seed); in_Patch.Add(0x03DDEC, (Byte)sound, "(H) | Shot Sound"); //0x03DDF1 - H x - speed (04, all levels) // Do from 02 to 08 Int32 xVel = in_Seed.NextInt32(0x07) + 0x02; in_Patch.Add(0x03DDF1, (Byte)xVel, "(H) | X-Velocity"); //0x03DE45 - H charge sound 1(35) Unused //0x03DE46 - H charge sound 2(35) sound = this.GetRandomSound(in_Seed); in_Patch.Add(0x03DE46, (Byte)sound, "(H) | L1 Sound"); //0x03DE47 - H charge sound 3(36) sound = this.GetRandomSound(in_Seed); in_Patch.Add(0x03DE47, (Byte)sound, "(H) | L2 Sound"); //0x03DE48 - H charge sound 4(37) sound = this.GetRandomSound(in_Seed); in_Patch.Add(0x03DE48, (Byte)sound, "(H) | L3 Sound"); }
/// <summary> /// /// </summary> /// <param name="p"></param> /// <param name="r"></param> public void ImportMusic(Patch in_Patch, ISeed in_Seed) { List <Song> songs = new List <Song>(); List <Song> stageSongs = new List <Song>(); SoundTrackSet soundTrackSet = Properties.Resources.SoundTrackConfiguration.Deserialize <SoundTrackSet>(); // Read songs from file, parse and add to list foreach (SoundTrack soundTrack in soundTrackSet) { if (true == soundTrack.Enabled) { songs.Add(new Song(soundTrack.Title, soundTrack.StartAddress, soundTrack.TrackData)); } } debug.AppendLine($"{songs.Count} stage songs loaded."); // Create a shuffled list of songs Boolean checkBytes = true; const Int32 numTracks = 11; while (checkBytes) { // Shuffle and get first 11 songs songs = in_Seed.Shuffle(songs).ToList(); stageSongs = songs.GetRange(0, numTracks); // Count bytes Int32 totalBytes = 0; Int32 i = 0; foreach (Song song in stageSongs) { totalBytes += song.SongHeader.Count; totalBytes += song.SongData.Count; i++; } // Break if within limit (Redo shuffle if over limit) // DEBUG DEBUG if (totalBytes <= StageSongsSize) { checkBytes = false; debug.AppendLine($"{numTracks} songs selected. {totalBytes} bytes used out of {StageSongsSize} limit."); } else { debug.AppendLine($"{numTracks} songs selected. {totalBytes} bytes, greater than {StageSongsSize} limit. Reshuffled songs."); } } // Write the songs and song info Int32 songStartRom = 0x030AE6; Int32 TblPtrOffset = 0x30A60; // Start of address pairs which point to song locations for (Int32 k = 0; k < stageSongs.Count; k++) { Song song = stageSongs[k]; // Calculate start addresses from song lengths and write to address table Int32 addressTwoBytes = songStartRom - 0x30010 + 0x8000; String addressHex = addressTwoBytes.ToString("X"); song.SongStartPtr1stByte = Byte.Parse(addressHex.Substring(0, 2), NumberStyles.HexNumber); song.SongStartPtr2ndByte = Byte.Parse(addressHex.Substring(2, 2), NumberStyles.HexNumber); if (k >= 10) // Must use a different location for the 11th track; use Stage Select at 0x30A78 { in_Patch.Add(0x30A78, song.SongStartPtr2ndByte, $"Song {k} Pointer Offset Byte 1"); in_Patch.Add(0x30A79, song.SongStartPtr1stByte, $"Song {k} Pointer Offset Byte 2"); Byte songIndex = 0x0C; // Wily 5 will play the song at the Stage Select address in_Patch.Add(0x0381EC, songIndex, $"Wily 5 Song"); debug.AppendLine($"{Enum.GetName(typeof(EMusicID), (EMusicID)songIndex)} stage song: {song.SongName}, {song.OriginalStartAddress}"); } else { in_Patch.Add(TblPtrOffset++, song.SongStartPtr2ndByte, $"Song {k} Pointer Offset Byte 1"); in_Patch.Add(TblPtrOffset++, song.SongStartPtr1stByte, $"Song {k} Pointer Offset Byte 2"); debug.AppendLine($"{Enum.GetName(typeof(EMusicID), (EMusicID)k)} stage song: {song.SongName}, {song.OriginalStartAddress}"); } // Header: Calculate 4 channel addresses and vibrato address Byte origChannel1ByteSmall = song.SongHeader[1]; Byte origChannel1ByteLarge = song.SongHeader[2]; Int32 origChannel1Offset = origChannel1ByteSmall + (origChannel1ByteLarge * 256); Int32 relChannel1Offset = origChannel1Offset - song.OriginalStartAddressInt; Int32 newChannel1Offset = addressTwoBytes + relChannel1Offset; song.SongHeader[1] = (Byte)(newChannel1Offset % 256); song.SongHeader[2] = (Byte)(newChannel1Offset / 256); Byte origChannel2ByteSmall = song.SongHeader[3]; Byte origChannel2ByteLarge = song.SongHeader[4]; Int32 origChannel2Offset = origChannel2ByteSmall + (origChannel2ByteLarge * 256); Int32 relChannel2Offset = origChannel2Offset - song.OriginalStartAddressInt; Int32 newChannel2Offset = addressTwoBytes + relChannel2Offset; song.SongHeader[3] = (Byte)(newChannel2Offset % 256); song.SongHeader[4] = (Byte)(newChannel2Offset / 256); Byte origChannel3ByteSmall = song.SongHeader[5]; Byte origChannel3ByteLarge = song.SongHeader[6]; Int32 origChannel3Offset = origChannel3ByteSmall + (origChannel3ByteLarge * 256); Int32 relChannel3Offset = origChannel3Offset - song.OriginalStartAddressInt; Int32 newChannel3Offset = addressTwoBytes + relChannel3Offset; song.SongHeader[5] = (Byte)(newChannel3Offset % 256); song.SongHeader[6] = (Byte)(newChannel3Offset / 256); Byte origChannel4ByteSmall = song.SongHeader[7]; Byte origChannel4ByteLarge = song.SongHeader[8]; if (origChannel4ByteSmall > 0 || origChannel4ByteLarge > 0) { Int32 origChannel4Offset = origChannel4ByteSmall + (origChannel4ByteLarge * 256); Int32 relChannel4Offset = origChannel4Offset - song.OriginalStartAddressInt; Int32 newChannel4Offset = addressTwoBytes + relChannel4Offset; song.SongHeader[7] = (Byte)(newChannel4Offset % 256); song.SongHeader[8] = (Byte)(newChannel4Offset / 256); if (relChannel4Offset > song.TotalLength || relChannel4Offset < 0) { debug.AppendLine($"WARNING: Song {song.SongName} channel 4 points to a shared location."); } } Byte origVibratoByteSmall = song.SongHeader[9]; Byte origVibratoByteLarge = song.SongHeader[10]; Int32 origVibratoOffset = origVibratoByteSmall + (origVibratoByteLarge * 256); Int32 relVibratoOffset = origVibratoOffset - song.OriginalStartAddressInt; Int32 newVibratoOffset = addressTwoBytes + relVibratoOffset; song.SongHeader[9] = (Byte)(newVibratoOffset % 256); song.SongHeader[10] = (Byte)(newVibratoOffset / 256); if (relChannel1Offset > song.TotalLength || relChannel1Offset < 0) { debug.AppendLine($"WARNING: Song {song.SongName} channel 1 points to a shared location."); } if (relChannel2Offset > song.TotalLength || relChannel2Offset < 0) { debug.AppendLine($"WARNING: Song {song.SongName} channel 2 points to a shared location."); } if (relChannel3Offset > song.TotalLength || relChannel3Offset < 0) { debug.AppendLine($"WARNING: Song {song.SongName} channel 3 points to a shared location."); } if (relVibratoOffset > song.TotalLength || relVibratoOffset < 0) { debug.AppendLine($"WARNING: Song {song.SongName} vibrato points to a shared location."); } // Write song header foreach (Byte b in song.SongHeader) { in_Patch.Add(songStartRom, b, $"Song Header Byte for {song.SongName}"); songStartRom++; } // Song Data: Traverse stream and change loop pointers for (Int32 i = 0; i < song.SongData.Count; i++) { // Do not parse loop pointers for vibrato // TODO: Check the length of the vibrato String, or even better, use separate lists for each channel! if (i >= song.VibratoIndex && i < song.VibratoLength) { continue; } Byte b0 = song.SongData[i]; // Bisqwit is awesome. // http://www.romhacking.net/forum/index.php?topic=16383.0 // http://bisqwit.iki.fi/jutut/megamansource/mm2music.txt switch (b0) { // Two-Byte encoding $00 n. Song speed is set as n // frames per tick. case 0x00: { i += 1; break; } // Two-Byte encoding $01 n. Adjusts vibrato parameters // by n. Affects all following notes. case 0x01: { i += 1; break; } // Two-Byte encoding $02 n. Selects duty cycle settings. // Valid values for n: $00,$40,$80,$C0. Only applicable // for squarewave channels. case 0x02: { i += 1; break; } // Two-Byte encoding $03 n. Selects volume and envelope // settings. Value n is passed directly to the soundchip; // Affects all following notes. case 0x03: { i += 1; break; } // Four-Byte encoding $04 n w. Ends a loop. If n=0, loop is // infinite. Otherwise the marked section plays for n+1 times. // w is a 16-bit pointer to the beginning of the loop. // Finite loops cannot be nested. case 0x04: { Byte origLoopPtrSmall = song.SongData[i + 2]; Byte origLoopPtrLarge = song.SongData[i + 3]; // Get the loop destination pointer by converting the two bytes to a 16-bit Int32 Int32 origLoopOffset = origLoopPtrSmall + (origLoopPtrLarge * 256); // Find index of destination of the loop with respect to the start of the song Int32 relLoopOffset = origLoopOffset - song.OriginalStartAddressInt; // Make new loop destination with respect to the new starting location of this song Int32 newLoopOffset = addressTwoBytes + relLoopOffset; // Put new hex bytes back into song data array song.SongData[i + 2] = (Byte)(newLoopOffset % 256); song.SongData[i + 3] = (Byte)(newLoopOffset / 256); // Validation check when testing out newly ripped songs to make sure I didn't miss any loops if (relLoopOffset > song.TotalLength || relLoopOffset < 0) { debug.AppendLine($"WARNING: Song {song.SongName} has external loop point."); } i += 3; break; } // Two-Byte encoding $05 n. Sets note base to n. Value // n is added to the note index for any notes // (excluding pauses) played on this channel from now. case 0x05: { i += 1; break; } // One-Byte encoding $06. Dotted note: The next note will // be played 50% longer than otherwise, i.e. 3/2 of its // stated duration. case 0x06: { break; } // Three-Byte encoding $07 n m. Sets volume curve settings. // Byte n controls the attack, and Byte m controls the decay. // Affects all following notes. case 0x07: { i += 2; break; } // Two-Byte encoding $08 n. Select vibrato entry n from the // vibrato table referred to by the song header. Affects all // following notes. case 0x08: { i += 1; break; } // One-Byte encoding $09. Ends the track. Can be omitted if // the track ends in an infinite loop instead. case 0x09: { break; } // One - Byte encoding $20 + n.Note delay(n = 0 - 7): // Delays the next note by n ticks, without affecting // its overall timing. (I.e.plays silence for the // first n ticks of the note.) // // One - Byte encoding $30.Triplet: // The next note will be played at 2 / 3 of its // stated duration. // // One - Byte encoding: // m * 0x20 + n.Play note(m = 2..7).If n = 0, plays pause. // Otherwise, plays note n(note base is added to n). The // lowest note that can be played is C - 0(n + base = 0). // Note or pause length is 2m−1 ticks, possibly altered by // the triplet / dotted modifiers.The next event will be // read only after this note/pause is done playing. default: { break; } } } // Write song data foreach (Byte b in song.SongData) { in_Patch.Add(songStartRom, b, $"Song Data Byte for {song.SongName}"); songStartRom++; } } // Play a random song during wily 6 Int32 w6song = in_Seed.NextInt32(11); if (w6song == 10) { // Wily 5 song was saved to 0x0C, don't use 0x0A in_Patch.Add(0x0381ED, 0x0C, $"Random Wily 6 Song: Song #{w6song}, {stageSongs[w6song].SongName}"); } else { in_Patch.Add(0x0381ED, (Byte)w6song, $"Random Wily 6 Song: Song #{w6song}, {stageSongs[w6song].SongName}"); } debug.AppendLine($"Wily 6 stage song: {stageSongs[w6song].SongName} (#{w6song})"); // Play a random stage song during the credits Song creditsSong = in_Seed.NextElement(stageSongs); in_Patch.Add(0x30A88, creditsSong.SongStartPtr2ndByte, $"Song Credits 2 Byte 0 ({creditsSong.SongName})"); in_Patch.Add(0x30A89, creditsSong.SongStartPtr1stByte, $"Song Credits 2 Byte 1 ({creditsSong.SongName})"); debug.AppendLine($"Credits song: {creditsSong.SongName}"); }
/// <summary> /// TODO /// </summary> private void RandomizeWilyUJ(Patch in_Patch, ISeed in_Seed) { // 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 = in_Seed.NextDouble(); Byte busterDmg = 0x00; if (rBuster > 0.75) { busterDmg = 0x01; } in_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); Int32 rInt = in_Seed.NextInt32(dragon.Count); bossWeak1 = dragon[rInt]; dragon.RemoveAt(rInt); rInt = in_Seed.NextInt32(dragon.Count); bossWeak2 = dragon[rInt]; // For each weapon, apply the weaknesses and immunities for (Int32 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) { Int32 tryDamage = (Int32)RWeaponBehavior.AmmoUsage[i + 1] - 0x01; damage = (tryDamage < 2) ? (Byte)0x02 : (Byte)tryDamage; } in_Patch.Add(weapon + EDmgVsBoss.Offset.Dragon, damage, String.Format("{0} Damage to Dragon", weapon.WeaponName)); WilyWeaknesses[0, i + 1] = damage; } // Dragon immune else { in_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 = in_Seed.NextDouble(); busterDmg = 0x00; if (rBuster > 0.75) { busterDmg = (Byte)(in_Seed.NextInt32(5) + 3); } in_Patch.Add(EDmgVsEnemy.DamageP + EDmgVsEnemy.Offset.PicopicoKun, busterDmg, String.Format("Buster Damage to Picopico-Kun")); WilyWeaknesses[1, 0] = busterDmg; // Deal ammoUse x 10 for the main weakness // Deal ammoUse x 6 for another // Deal ammoUse x 3 for another List <EDmgVsEnemy> pico = new List <EDmgVsEnemy>(dmgPtrEnemies); rInt = in_Seed.NextInt32(pico.Count); enemyWeak1 = pico[rInt]; pico.RemoveAt(rInt); rInt = in_Seed.NextInt32(pico.Count); enemyWeak2 = pico[rInt]; pico.RemoveAt(rInt); rInt = in_Seed.NextInt32(pico.Count); enemyWeak3 = pico[rInt]; for (Int32 i = 0; i < dmgPtrEnemies.Count; i++) { EDmgVsEnemy weapon = dmgPtrEnemies[i]; Byte damage = 0x00; Char level = ' '; if (weapon == enemyWeak1) { damage = (Byte)(RWeaponBehavior.AmmoUsage[i + 1] * 10); if (damage < 2) { damage = 3; } level = '^'; } else if (weapon == enemyWeak2) { damage = (Byte)(RWeaponBehavior.AmmoUsage[i + 1] * 6); if (damage < 2) { damage = 2; } level = '*'; } else if (weapon == enemyWeak3) { damage = (Byte)(RWeaponBehavior.AmmoUsage[i + 1] * 3); 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; } in_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 = in_Seed.NextDouble(); busterDmg = 0x00; if (rBuster > 0.75) { busterDmg = 0x01; } in_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 = in_Seed.NextInt32(guts.Count); bossWeak1 = guts[rInt]; guts.RemoveAt(rInt); rInt = in_Seed.NextInt32(guts.Count); bossWeak2 = guts[rInt]; for (Int32 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) { Int32 tryDamage = (Int32)RWeaponBehavior.AmmoUsage[i + 1] - 0x01; damage = (tryDamage < 2) ? (Byte)0x02 : (Byte)tryDamage; } in_Patch.Add(weapon + EDmgVsBoss.Offset.Guts, damage, String.Format("{0} Damage to Guts Tank", weapon.WeaponName)); WilyWeaknesses[2, i + 1] = damage; } // Guts immune else { in_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 Int32 rBarrierWeakness = in_Seed.NextInt32(dmgBarrierList.Count); EDmgVsEnemy wpnBarrier = dmgBarrierList[rBarrierWeakness]; // Scale damage to be slightly more capable than killing 5 barriers at full ammo Int32 dmgW4 = 0x01; if (wpnBarrier != EDmgVsEnemy.DamageP) { Int32 totalShots = (Int32)(28 / RWeaponBehavior.GetAmmoUsage(wpnBarrier)); Int32 numHitsPerBarrier = (Int32)(totalShots / 5); if (numHitsPerBarrier > 1) { numHitsPerBarrier--; } if (numHitsPerBarrier > 8) { numHitsPerBarrier = 8; } dmgW4 = (Int32)Math.Ceiling(20d / numHitsPerBarrier); } for (Int32 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; } in_Patch.Add(wpn.Address + EDmgVsEnemy.Offset.ClashBarrier_W4, damage, String.Format("{0} Damage to Clash Barrier 1", wpn.WeaponName)); in_Patch.Add(wpn.Address + EDmgVsEnemy.Offset.ClashBarrier_Other, damage, String.Format("{0} Damage to Clash Barrier 2", wpn.WeaponName)); } // Remove Barrier weakness from list first (therefore, different Buebeam weakness) dmgBarrierList.RemoveAt(rBarrierWeakness); // Get Buebeam weakness rInt = in_Seed.NextInt32(dmgBarrierList.Count); EDmgVsEnemy wpnBuebeam = dmgBarrierList[rInt]; // Add Barrier weakness back to list for counting later dmgBarrierList.Insert(rBarrierWeakness, wpnBarrier); // Scale damage to be slightly more capable than killing 5 buebeams at full ammo dmgW4 = 0x01; if (wpnBuebeam != EDmgVsEnemy.DamageP) { Int32 totalShots = (Int32)(28 / RWeaponBehavior.GetAmmoUsage(wpnBuebeam)); Int32 numHitsPerBuebeam = (Int32)(totalShots / 5); if (numHitsPerBuebeam > 1) { numHitsPerBuebeam--; } if (numHitsPerBuebeam > 8) { numHitsPerBuebeam = 8; } dmgW4 = (Int32)Math.Ceiling(20d / numHitsPerBuebeam); } // Add Buebeam damage values to patch, as well as array for use by Text and other modules later for (Int32 i = 0; i < dmgBarrierList.Count; i++) { Byte damage = (Byte)dmgW4; EDmgVsEnemy wpn = dmgBarrierList[i]; if (wpn != wpnBuebeam) { damage = 0; } in_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 = in_Seed.NextDouble(); busterDmg = 0x00; if (rBuster > 0.25) { busterDmg = 0x01; } in_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 = in_Seed.NextInt32(machine.Count); bossWeak1 = machine[rInt]; machine.RemoveAt(rInt); rInt = in_Seed.NextInt32(machine.Count); bossWeak2 = machine[rInt]; machine.RemoveAt(rInt); rInt = in_Seed.NextInt32(machine.Count); bossWeak3 = machine[rInt]; machine.RemoveAt(rInt); rInt = in_Seed.NextInt32(machine.Count); bossWeak4 = machine[rInt]; for (Int32 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]; } in_Patch.Add(weapon + EDmgVsBoss.Offset.Machine, damage, String.Format("{0} Damage to Wily Machine", weapon.WeaponName)); WilyWeaknesses[4, i + 1] = damage; } // Machine immune else { in_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) { in_Patch.Add(0x02DA2E, wIndex, String.Format("Wily Machine Phase 1 Resistance 1 ({0})", weapon.WeaponName)); } if (weapon == bossWeak2) { in_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) { in_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); Int32 rWeaponIndex = in_Seed.NextInt32(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 (Int32 i = 0; i < alienWeapons.Count; i++) { EDmgVsBoss weapon = alienWeapons[i]; if (i == rWeaponIndex) { in_Patch.Add(weapon + EDmgVsBoss.Offset.Alien, alienDamage, String.Format("{0} Damage to Alien", weapon.WeaponName)); WilyWeaknesses[5, i] = alienDamage; } else { in_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 (Int32 i = 0; i < WilyWeaknesses.GetLength(0); i++) { for (Int32 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.Append(Environment.NewLine); } // End method RandomizeWilyUJ