public void WriteSeedAndFlags(string version, string seed, string flags) { var seedBytes = FF1Text.TextToBytes($"{version} {seed}", useDTE: false); var flagBytes = FF1Text.TextToBytes($"{flags}", useDTE: false); var padding = new byte[15 - flagBytes.Length]; for (int i = 0; i < padding.Length; i++) { padding[i] = 0xFF; } Put(CopyrightOffset1, seedBytes); Put(CopyrightOffset2, padding + flagBytes); }
public string TranslateItem(Item item) { switch (item) { case Item.Ship: return(FF1Text.BytesToText(rom.Get(0x2B5D0, 4))); case Item.Bridge: return(FF1Text.BytesToText(rom.Get(0x2B5D0 + 16, 6))); case Item.Canal: return(FF1Text.BytesToText(rom.Get(0x2B5D0 + 24, 5))); case Item.Canoe: return(FF1Text.BytesToText(rom.Get(0x2B5D0 + 36, 5))); default: return(itemnames[(int)item].Replace(" ", "")); } }
public void WriteSeedAndFlags(string version, string seed, string flags) { // Replace most of the old copyright string printing with a JSR to a LongJump Put(0x38486, Blob.FromHex("20FCFE60")); // DrawSeedAndFlags LongJump PutInBank(0x1F, 0xFEFC, CreateLongJumpTableEntry(0x0F, 0x8960)); // Put the new string data in a known location. PutInBank(0x0F, 0x8900, Blob.Concat(new Blob[] { FF1Text.TextToCopyrightLine("Final Fantasy Randomizer " + version), FF1Text.TextToCopyrightLine("Seed " + seed), FF1Text.TextToCopyrightLine(flags), })); }
// Scale is the geometric scale factor used with RNG. Multiplier is where we make everything cheaper // instead of enemies giving more gold, so we don't overflow. public void ScalePrices(double scale, double multiplier, Blob[] text, MT19337 rng) { var prices = Get(PriceOffset, PriceSize * PriceCount).ToUShorts(); for (int i = 0; i < prices.Length; i++) { prices[i] = (ushort)Min(Scale(prices[i] / multiplier, scale, 1, rng), 0xFFFF); } var questItemPrice = prices[(int)Item.Bottle]; for (var i = 0; i < (int)Item.Tent; i++) { prices[i] = questItemPrice; } // Crystal can block Ship in early game where 50000 G would be too expensive prices[(int)Item.Crystal] = (ushort)(prices[(int)Item.Crystal] / 8); Put(PriceOffset, Blob.FromUShorts(prices)); for (int i = GoldItemOffset; i < GoldItemOffset + GoldItemCount; i++) { text[i] = FF1Text.TextToBytes(prices[i].ToString() + " G"); } var pointers = Get(ShopPointerOffset, ShopPointerCount * ShopPointerSize).ToUShorts(); RepackShops(pointers); for (int i = (int)ShopType.Clinic; i < (int)ShopType.Inn + ShopSectionSize; i++) { if (pointers[i] != ShopNullPointer) { var priceBytes = Get(ShopPointerBase + pointers[i], 2); var priceValue = BitConverter.ToUInt16(priceBytes, 0); priceValue = (ushort)Scale(priceValue / multiplier, scale, 1, rng); priceBytes = BitConverter.GetBytes(priceValue); Put(ShopPointerBase + pointers[i], priceBytes); } } var startingGold = BitConverter.ToUInt16(Get(StartingGoldOffset, 2), 0); startingGold = (ushort)Min(Scale(startingGold / multiplier, scale, 1, rng), 0xFFFF); Put(StartingGoldOffset, BitConverter.GetBytes(startingGold)); }
public void ChangeUnrunnableRunToWait() { // See Unrunnable.asm // Replace DrawCommandMenu with a cross page jump to a replacement that swaps RUN for WAIT if the battle is unrunnable. // The last 5 bytes here are the null terminated WAIT string (stashed in some leftover space of the original subroutine) Put(0x7F700, Blob.FromHex("ADFC6048A90F2003FE204087682003FE4C48F6A08A929D00")); // Replace some useless code with a special handler for unrunnables that prints a different message. // We then update the unrunnable branch to point here instead of the generic Can't Run handler // See Disch's comments here: Battle_PlayerTryRun [$A3D8 :: 0x323E8] Put(0x32409, Blob.FromHex("189005A9064C07AAEAEAEAEAEAEAEA")); Data[0x323EB] = 0x20; // new delta to special unrunnable message handler // The above code uses battle message $06 which is the unused Sight Recovered string // Let's overwrite that string with something more appropriate for the WAIT command Put(0x2CC71, FF1Text.TextToBytes("W A I T", false)); }
public void EnableOrbHunt(MT19337 rng) { // Replace unused CANOE string and EarthOrb pointer with whatever we're calling the scavenged item. Put(0x2B981, FF1Text.TextToBytes("SHARD ", false, FF1Text.Delimiter.Null)); Data[0x2B72A] = 0x81; // Now actually print shards when printing items, and print them with quantity Data[0x7EF49] = 0x15; Data[0x7EF91] = 0x15; // Replace the upper two tiles of the unlit orb with an empty and found shard. // These are at tile address $76 and $77 respectively. Put(0x37760, Blob.FromHex("001C22414141221CFFE3DDBEBEBEDDE3001C3E7F7F7F3E1CFFFFE3CFDFDFFFFF")); // Hard code the total number of orbs and where we start depending on how many are needed int goal = rng.Between(16, 24); String hexCount = goal.ToString("X2"); String ppuLowByte = goal <= 24 ? "63" : "43"; // Fancy orb drawing code, see 0E_B8D7_DrawOrbBox.asm Put(0x3B87D, Blob.FromHex($"A9{ppuLowByte}8511A977A00048AD0220A9208D0620A51118692085118D0620900DAD0220A9218D0620A9038D062068A200CC3560D002A976C0{hexCount}D001608D0720C8E8E006D0EB1890C3")); // Black Orb Override to jump to the final floor. This allows us to give out some last minute loot and // and make the repeated attempts the final battle strategy take a little longer due to some walking. Put(0x39502, Blob.FromHex($"AD3560C9{hexCount}300CA0CA209690E67DE67DA51160A51260")); Put(0x7CDB3, Blob.FromHex("08CE")); Data[0x00D80] = 0x80; // Map edits Data[0x02D01] = 0x0F; Data[0x02D41] = 0x03; Data[0x02D81] = 0x3B; // ToFR Map Hacks Put(0x1A899, Blob.FromHex("0C00810502BF38000462615D5E5F0402BF3334B0020004638405600402B00235BF2D3430B10303840905B1033035BF2A34B1033330068409083032B10335BF2834B10233B00231300684021004118402083031B00232B10235BF2732313330B104300687050830B10430323133BF2634B10233B10538B00336B00338B10532B10235BF2532313330B1053831B8023AB802")); // A little narrative overhaul. Put(0x289B2, FF1Text.TextToBytes("The SHARDS coalesce to\nrestore the Black ORB.\n\nBrave Light Warriors....\nDestroy the Evil within!")); // Black Orb Text Put(0x28CF8, FF1Text.TextToBytes("Ah, the Light Warriors!\n\nSo you have collected\nthe SHARDS and restored\nthe BLACK ORB.")); Put(0x28D57, FF1Text.TextToBytes("Thus you've travelled\n2000 years into the past\nto try to stop me?\n\nStep forward then,\nto your peril!")); Put(0x28DAF, FF1Text.TextToBytes("Oh, Light Warriors!\nSuch arrogant bravery.\n\nLet us see whom history\nremembers. En Garde!")); // This ugliness picks a random FinalFormation transformFinalFormation((FinalFormation)rng.Between(0, Enum.GetValues(typeof(FinalFormation)).Length - 1)); }
//sample function for creating new armor public void ExpandArmor() { Armor platinumBracelet = new Armor(12, FF1Text.TextToBytes("Plat"), ArmorIcon.BRACELET, 1, 42, 0, 0); platinumBracelet.setClassUsability((ushort)( EquipPermission.BlackBelt | EquipPermission.BlackMage | EquipPermission.BlackWizard | EquipPermission.Fighter | EquipPermission.Knight | EquipPermission.Master | EquipPermission.Ninja | EquipPermission.RedMage | EquipPermission.RedWizard | EquipPermission.Thief | EquipPermission.WhiteMage | EquipPermission.WhiteWizard)); platinumBracelet.writeArmorMemory(this); }
}; // Warp, Soft, Exit, Life, Life 2 public void ShuffleItemMagic(MT19337 rng) { CastableItemTargeting(); // make items able to target a single enemy or party member List <Blob> spellNames = Get(SpellNamesOffset, SpellNamesSize * SpellNamesCount).Chunk(SpellNamesSize); var Spells = spellNames.Select((blob, i) => new MagicSpell // creat a list of all spells { Data = null, Index = (byte)i, Name = FF1Text.TextToBytes(FF1Text.BytesToText(blob).PadRight(6), false, FF1Text.Delimiter.Empty), }).ToList(); Spells.RemoveAll(spell => SpellsToRemove.Contains(spell.Index)); // Remove the spells specified in SpellsToRemove Spells.Shuffle(rng); // Shuffle all spells remaining, then assign to each item that can cast a spell foreach (var item in Spells.Zip(ItemLists.AllMagicItem, (s, i) => new { Spell = s, Item = i })) { WriteItemSpellData(item.Spell, item.Item); } }
public void WriteSeedAndFlags(string version, string seed, string flags) { var seedBytes = FF1Text.TextToBytes($"{version} {seed}", useDTE: false); var flagHashText = Convert.ToBase64String(BitConverter.GetBytes(flags.GetHashCode())); flagHashText = flagHashText.TrimEnd('='); flagHashText = flagHashText.Replace('+', '-'); flagHashText = flagHashText.Replace('/', '!'); var flagBytes = FF1Text.TextToBytes($"{flagHashText}", useDTE: false); flagBytes = flagBytes.SubBlob(0, Math.Min(15, flagBytes.Length)); var padding = new byte[16 - flagBytes.Length]; for (int i = 0; i < padding.Length; i++) { padding[i] = 0xFF; } Put(CopyrightOffset1, seedBytes); Put(CopyrightOffset2, padding + flagBytes); }
public void LoadIntro(Stream stream) { var introText = new List <string>(); using (StreamReader reader = new StreamReader(stream)) { while (true) { var line = reader.ReadLine(); if (line == null) { break; } introText.Add(line.TrimEnd()); } } Blob intro = FF1Text.TextToStory(introText.ToArray()); Console.WriteLine(intro.Length); System.Diagnostics.Debug.Assert(intro.Length <= 208); Put(0x37F20, intro); }
public void ShuffleItemMagic(MT19337 rng) { CastableItemTargeting(); // make items able to target a single enemy or party member // Reusing MagicSpell from Magic.cs List <byte> SpellIndex = new List <byte>(); var Spells = SpellNames.Select((text, i) => new MagicSpell // creat a list of all spells { Data = Blob.FromInts(new int[1] { i + 1 }), // spells are 1 based Index = (byte)i, Name = FF1Text.TextToBytes(text), }).ToList(); Spells.RemoveAll(spell => SpellsToRemove.Contains(spell.Index)); // Remove the spells specified in SpellsToRemove Spells.Shuffle(rng); // Shuffle all spells remaining, then assign to each item that can cast a spell foreach (var item in Spells.Zip(ItemLists.AllMagicItem, (s, i) => new { Spell = s, Item = i })) { WriteItemSpellData(item.Spell, item.Item); } }
public void RandomArmorBonus(MT19337 rng, int min, int max) { //get base stats Armor currentArmor; for (int i = 0; i < ArmorCount; i++) { currentArmor = new Armor(i, this); int bonus = rng.Between(min, max); if (bonus != 0) { //body armor(indexes 0 - 15) +2/-2 +2/-2 weight //shield(indexes 16 - 24) +1/-1 +1/-1 weight //helm(indexes 25 - 31) +1/-1 +1/-1 weight //hand(indexes 32 - 39) +1/-1 +1/-1 weight int armorTypeAbsorbBonus = i < 16 ? 2 : 1; int armorTypeWeightBonus = i < 16 ? 2 : 1; //adjust stats //clamp minimums to 0 weight, and 1 absorb currentArmor.Weight = (byte)Math.Max(0, currentArmor.Weight - (armorTypeWeightBonus * bonus)); currentArmor.Absorb = (byte)Math.Max(1, currentArmor.Absorb + (armorTypeAbsorbBonus * bonus)); //change last two non icon characters to -/+bonus string bonusString = string.Format((bonus > 0) ? "+{0}" : "{0}", bonus.ToString()); byte[] bonusBytes = FF1Text.TextToBytes(bonusString); int iconIndex = currentArmor.NameBytes[6] > 200 && currentArmor.NameBytes[6] != 255 ? 5 : 6; for (int j = 0; j < bonusBytes.Length - 1; j++) { currentArmor.NameBytes[iconIndex - j] = bonusBytes[bonusBytes.Length - 2 - j]; } currentArmor.writeArmorMemory(this); } } }
public void WriteSeedAndFlags(string version, string seed, string flags) { // Replace most of the old copyright string printing with a JSR to a LongJump Put(0x38486, Blob.FromHex("20FCFE60")); // DrawSeedAndFlags LongJump PutInBank(0x1F, 0xFEFC, CreateLongJumpTableEntry(0x0F, 0x8980)); var sha = File.Exists("version.txt") ? File.ReadAllText("version.txt").Trim() : "development"; Blob hash; if (sha == "development") { hash = FF1Text.TextToCopyrightLine("DEVELOPMENT VERSION"); } else { var hasher = SHA256.Create(); hash = hasher.ComputeHash(Encoding.ASCII.GetBytes($"{seed}_{flags}_{sha}")); var hashpart = BitConverter.ToUInt64(hash, 0); hash = Blob.FromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); for (int i = 13; i < 19; i++) { // 0xD4 through 0xDF are good symbols to use. hash[i] = (byte)(0xD4 + hashpart % 12); hashpart /= 12; } } // Put the new string data in a known location. PutInBank(0x0F, 0x8900, Blob.Concat( FF1Text.TextToCopyrightLine("Final Fantasy Randomizer " + version), FF1Text.TextToCopyrightLine("Seed " + seed), FF1Text.TextToCopyrightLine(flags), hash)); }
public void WriteText(string[] texts, int pointerOffset, int pointerBase, int textOffset, List <int> skipThese) { int offset = textOffset; var pointers = new ushort[texts.Length]; for (int i = 0; i < texts.Length; i++) { if (skipThese.Contains(i)) { // Don't write a blob, and point to the null-terminator at the end of the previous string. pointers[i] = (ushort)(offset - pointerBase - 1); } else { var blob = FF1Text.TextToBytes(texts[i], useDTE: false); Put(offset, blob); pointers[i] = (ushort)(offset - pointerBase); offset += blob.Length; } } Put(pointerOffset, Blob.FromUShorts(pointers)); }
private void WriteItemSpellData(MagicSpell Spell, Item item) { // Set the spell an item casts var offset = WeaponOffset + 0x8 * Math.Min((byte)item - WeaponStart, ArmorStart - WeaponStart) + 0x4 * Math.Max(0, (byte)item - ArmorStart) + MagicBitOffset; Data[offset] = (byte)(Spell.Index + 1); // Setup the text of the item's name to include the spell name. offset = GearTextOffset + ((byte)item > (byte)Item.Ribbon ? 1 : 0) + GearTextSize * ((byte)item - WeaponStart); if (Get(offset, 1)[0] > 200) { offset++; // If the first byte is in the icon range, bump the pointer to overwrite after it. } else if (Get(offset + 6, 1)[0] <= 200) { Data[offset + 6] = 0xFF; // Erase final non-icon characters from name. } // Fix up the name of the spell so it works as part of an item name. var fixedSpellName = FF1Text.TextToBytes(FF1Text.BytesToText(Spell.Name).PadRight(6), false, FF1Text.Delimiter.Empty); Debug.Assert(fixedSpellName.Length == 6); Put(offset, fixedSpellName); }
// Scale is the geometric scale factor used with RNG. Multiplier is where we make everything cheaper // instead of enemies giving more gold, so we don't overflow. public void ScalePrices(IScaleFlags flags, Blob[] text, MT19337 rng, bool increaseOnly, ItemShopSlot shopItemLocation) { var scale = flags.PriceScaleFactor; var multiplier = flags.ExpMultiplier; var prices = Get(PriceOffset, PriceSize * PriceCount).ToUShorts(); for (int i = 0; i < prices.Length; i++) { var newPrice = Scale(prices[i] / multiplier, scale, 1, rng, increaseOnly); prices[i] = (ushort)(flags.WrapPriceOverflow ? ((newPrice - 1) % 0xFFFF) + 1 : Min(newPrice, 0xFFFF)); } var questItemPrice = prices[(int)Item.Bottle]; // If we don't do this before checking for the item shop location factor, Ribbons and Shirts will end up being really cheap // This realistically doesn't matter without Shop Wares shuffle on because nobody wants to sell Ribbons/Shirts, but if it is... prices[(int)Item.WhiteShirt] = (ushort)(questItemPrice / 2); prices[(int)Item.BlackShirt] = (ushort)(questItemPrice / 2); prices[(int)Item.Ribbon] = questItemPrice; var itemShopFactor = new Dictionary <MapLocation, int>() { { MapLocation.Coneria, 8 }, { MapLocation.Pravoka, 2 } }; if (itemShopFactor.TryGetValue(shopItemLocation.MapLocation, out int divisor)) { questItemPrice = (ushort)(prices[(int)Item.Bottle] / divisor); } for (var i = 0; i < (int)Item.Tent; i++) { prices[i] = questItemPrice; } Put(PriceOffset, Blob.FromUShorts(prices)); for (int i = GoldItemOffset; i < GoldItemOffset + GoldItemCount; i++) { text[i] = FF1Text.TextToBytes(prices[i].ToString() + " G"); } var pointers = Get(ShopPointerOffset, ShopPointerCount * ShopPointerSize).ToUShorts(); RepackShops(pointers); for (int i = (int)ShopType.Clinic; i < (int)ShopType.Inn + ShopSectionSize; i++) { if (pointers[i] != ShopNullPointer) { var priceBytes = Get(ShopPointerBase + pointers[i], 2); var priceValue = BitConverter.ToUInt16(priceBytes, 0); priceValue = (ushort)Scale(priceValue / multiplier, scale, 1, rng, increaseOnly); priceBytes = BitConverter.GetBytes(priceValue); Put(ShopPointerBase + pointers[i], priceBytes); } } if (flags.StartingGold) { var startingGold = BitConverter.ToUInt16(Get(StartingGoldOffset, 2), 0); startingGold = (ushort)Min(Scale(startingGold / multiplier, scale, 1, rng, increaseOnly), 0xFFFF); Put(StartingGoldOffset, BitConverter.GetBytes(startingGold)); } }
public void RollCredits() { // Wallpaper over the JSR to the NASIR CRC to circumvent their neolithic DRM. Put(0x3CF34, Blob.FromHex("EAEAEA")); // Actual Credits. Each string[] is a page. Each "" skips a line, duh. // The lines have zero padding on all sides, and 16 usable characters in length. // Don't worry about the inefficiency of spaces as they are all trimmed and the // leading spaces are used to increment the PPU ptr precisely to save ROM space. List <string[]> texts = new List <string[]>(); texts.Add(new string[] { "", "", " Final Fantasy ", "", "", " Randomizer ", }); texts.Add(new string[] { "", "", " Programmed ", " By ", "", "E N T R O P E R " }); texts.Add(new string[] { "", " Development ", "", "", " Entroper", " MeridianBC", " tartopan", " nitz", }); texts.Add(new string[] { " Special Thanks ", "", "fcoughlin, Disch", "Paulygon, anomie", "Derangedsquirrel", "AstralEsper, and", "", " The Entire FFR ", " Community ", }); // Accumulate all our Credits pages before we set up the string pointer array. List <Blob> pages = new List <Blob>(); foreach (string[] text in texts) { pages.Add(FF1Text.TextToCredits(text)); } // Clobber the number of pages to render before we insert in the pointers. Data[0x37873] = (byte)pages.Count(); // The first pointer is immediately after the pointer table. List <ushort> ptrs = new List <ushort>(); ptrs.Add((ushort)(0xBB00 + pages.Count() * 2)); for (int i = 1; i < pages.Count(); ++i) { ptrs.Add((ushort)(ptrs.Last() + pages[i - 1].Length)); } // Collect it into one blob and blit it. pages.Insert(0, Blob.FromUShorts(ptrs.ToArray())); Blob credits = Blob.Concat(pages); System.Diagnostics.Debug.Assert(credits.Length <= 0x0100, "Credits too large!"); Put(0x37B00, credits); }
public void ShopUpgrade(Flags flags, Preferences preferences) { // Modify DrawShopPartySprites to use new DrawOBSprite routines, see 0E_9500_ShopUpgrade.asm PutInBank(0x0E, 0xAA04, Blob.FromHex("205795")); PutInBank(0x0E, 0xAA0D, Blob.FromHex("205795")); PutInBank(0x0E, 0xAA16, Blob.FromHex("205795")); PutInBank(0x0E, 0xAA23, Blob.FromHex("4C5795")); // Extra routines for Shops, plus equip and magic info, see 0E_9500_ShopUpgrade.asm // Insert new routines in Common Shop Loop PutInBank(0x0E, 0xA931, Blob.FromHex("200095")); PutInBank(0x0E, 0x9500, Blob.FromHex("A564C928F038A566C904B035C902B017A20020D495A24020D495A28020D495A2C020D4954C4195A200208B95A240208B95A280208B95A2C0208B954C41952047952027A74C2696A9008DD66A8DDA6A8DDE6A8DE26A6060AA4A8510BD0061A8B9A4EC8511BD0161F011C901F0E9C903F004A9038511A9144C8395A5104A4A4AAABDD66A18651085104C24EC8A8515BD00610AAABD00AD8510BD01AD8511A662BD000338E9B0851229078513A5124A4A4AA8B1108514A613BD38AC2514F005A9004CC595A90E8510A5154A4A4A4AAAA5109DD66A608A8515BD00610AAABDB9BC8512BDBABC8513A662BD000338C944B01638E91C0AAABD50BF25128510BD51BF251305104C1896E9440AAABDA0BF25128510BDA1BF25130510C9019005A9004CC595A90E4CC595A522F033A564C928F02DA662BD00038514205E962027A7A520C561F0F7A9008522204D964C46E1A9018538A9128539A90E853CA90A853D60204D96A90E85572063E0A53E8512A53F8513A514380AAAB00DBD0093853EBD0193853F4C8E96BD0094853EBD0194853FA9118557A90E85582036DEA512853EA513853FA900852260")); if (flags.ChestInfo && !preferences.RenounceChestInfo) { // Shorten TreasureChest Dialog InsertDialogs(320, "You found.. #"); InsertDialogs(321, "Can't hold.. #"); PutInBank(0x1F, 0xD536, Blob.FromHex("68482064DB4CD0DD")); PutInBank(0x1F, 0xDDD0, Blob.FromHex("A9B648A9FF48A9114C03FE8A20DA8760")); PutInBank(0x11, 0xB700, Blob.FromHex("A911855768C9F1D004A905D003ADFB602010B8A545D00160A561C91CB00160C96C900160482089C6A9008561A639E88AC91E9002E91E8539A9068D0080A91C8D0180680AAAB00DBD0093853EBD0193853F4C5EB7BD0094853EBD0194853FA9068D0080A9228D0180A200A003B13EC8C902F05AC914F00B9D006BE8C900D0ED4C05B8A53E48A53F489848B13E0AA8B00DB9009A853EB9019A853F4CA7B7B9009B853EB9019B853FA000B13EC8C900F0079D006BE84CA9B7A9068D0080A9228D018068A8C868853F68853E4C6CB7A53E48A53F489848B13E0AA8A9068D0080A9158D0180B010B90097853EB9019738E920853F4CA7B7B90098853EB9019838E920853F4CA7B7A900853EA96B853F4C8ADBA23F8E0620A20E8E06208D0720A23F8E0620A2008E06208E06208E062020A1CC60")); if (preferences.RenounceCantHoldRed) { PutInBank(0x11, 0xB707, Blob.FromHex("EAEAEAEAEAEA")); } if (flags.ExtConsumableSet != ExtConsumableSet.None) { PutInBank(0x11, 0xB71B, Blob.FromHex("20")); } } // Patch in the equip menu loop to add gear info PutInBank(0x0E, 0xBB8F, Blob.FromHex("4CE090EA")); // Patch in the magic menu loop to add spell info PutInBank(0x0E, 0xAECD, Blob.FromHex("4C2691EA")); // the UpgradedEquipMenu and UpgradedMagicMenu code that the above patches jump to PutInBank(0x0E, 0x90E0, Blob.FromHex("A525D007A522D0044C93BB60A662BD0003F030297FA466C018D005691A4C029169428514203CC4205E9620F9BCA520C561F0F7A9008D0120853720F3BD2083B720DAEC4C93BBA525D007A522D0044CD1AE60A9018537A5664A6A6A0562AA0A2938187D00631869AF8514205E962080B72025B6A9008D01208537857F20029CA56248206DBA688562A90720EFB8A9292059B92080B74CD1AE")); // Modify DrawComplexString, this sets control code 14-19 to use a new words table in bank 11 // could be used to move some stuff in items name table and make some space // see 1F_DEBC_DrawComplexString.asm PutInBank(0x1F, 0xDEBC, Blob.FromHex("C910B005A2204C83DEC914B07B")); // Change branching to enable CC14 PutInBank(0x1F, 0xDF44, Blob.FromHex("4CCEDF")); // Jump to routine because we're too far, put in unused char weapons CC PutInBank(0x1F, 0xDFCE, Blob.FromHex("A91185572003FE4CA099")); // Routine, put in unused char weapons routine // Routine to load the right word from the new words table PutInBank(0x11, 0x99A0, Blob.FromHex("A91185578558B13EE63ED002E63F203EE00AAAB00BBD009A853EBD019A4CC899BD009B853EBD019B853F2045DEA90E85584C4EE0")); const int weaponOffset = 0x1C; // $28 entries const int armorOffset = 0x44; // $28 entries const int spellOffset = 0xB0; // $40 entries var weaponsData = new List <Weapon>(); var armorsData = new List <Armor>(); for (int i = 0; i < WeaponCount; i++) { weaponsData.Add(new Weapon(i, this)); } for (int i = 0; i < ArmorCount; i++) { armorsData.Add(new Armor(i, this)); } var spellsData = GetSpells(); // 12 char per row, 5 rows var descriptionsList = new List <string>(); for (int i = 0; i < weaponOffset; i++) { descriptionsList.Add(""); } // Insert the new words table int offsetWordsPointers = 0x9A00; int offsetWords = 0x9B00; var pointersWords = new ushort[shopInfoWordsList.Count()]; Blob generatedWords = Blob.FromHex(""); for (int i = 0; i < shopInfoWordsList.Count(); i++) { var blob = FF1Text.TextToBytes(shopInfoWordsList[i], useDTE: true); generatedWords += blob; pointersWords[i] = (ushort)(offsetWords); offsetWords += blob.Length; } PutInBank(0x11, 0x9B00, generatedWords); PutInBank(0x11, offsetWordsPointers, Blob.FromUShorts(pointersWords)); // Build the info boxes for (int i = weaponOffset; i < armorOffset; i++) { descriptionsList.Add("\n" + GenerateWeaponDescription(i - weaponOffset, preferences.ShopInfoIcons)); } for (int i = armorOffset; i < (armorOffset + 0x28); i++) { descriptionsList.Add("\n" + GenerateArmorDescription(i - armorOffset, preferences.ShopInfoIcons)); } for (int i = (armorOffset + 0x28); i < spellOffset; i++) { descriptionsList.Add(""); } for (int i = spellOffset; i < spellOffset + 0x40; i++) { descriptionsList.Add("" + GenerateSpellDescription(i, spellsData[i - spellOffset].Data, preferences.ShopInfoIcons)); } // Convert all dialogues to bytes int offset = 0xA000; var pointers = new ushort[descriptionsList.Count()]; Blob generatedText = Blob.FromHex(""); for (int i = 0; i < descriptionsList.Count(); i++) { var blob = new byte[] { 0x02, (byte)i } +FF1Text.TextToBytesInfo(descriptionsList[i], useDTE: true); if (blob.Length <= 3) { blob = new byte[0]; } generatedText += blob; pointers[i] = (ushort)(offset); offset += blob.Length; } // Check if dialogs are too long if (generatedText.Length > 0x1400) { throw new Exception("ShopInfo text size maximum exceeded."); } // Insert dialogs PutInBank(0x11, 0xA000, generatedText); PutInBank(0x0E, 0x9300, Blob.FromUShorts(pointers)); }
private void ExtraTrackingAndInitCode(Flags flags) { // Expanded game init code, does several things: // - Encounter table emu/hardware fix // - track hard/soft resets // - initialize tracking variables if no game is saved PutInBank(0x0F, 0x8000, Blob.FromHex("A9008D00208D012085FEA90885FF85FDA51BC901D00160A901851BA94DC5F9F008A9FF85F585F685F7182088C8B049A94DC5F918F013ADA36469018DA364ADA46469008DA464189010ADA56469018DA564ADA66469008DA664A9008DFD64A200187D00647D00657D00667D0067E8D0F149FF8DFD64189010A2A0A9009D00609D0064E8D0F7EEFB64ADFB648DFB6060")); Put(0x7C012, Blob.FromHex("A90F2003FE200080EAEAEAEAEAEAEAEA")); // Move controller handling out of bank 1F // This bit of code is also altered to allow a hard reset using Up+A on controller 2 PutInBank(0x0F, 0x8200, Blob.FromHex("20108220008360")); PutInBank(0x0F, 0x8210, Blob.FromHex("A9018D1640A9008D1640A208AD16402903C9012620AD17402903C901261ECAD0EBA51EC988F008C948F001604C2EFE20A8FE20A8FE20A8FEA2FF9AA900851E9500CAD0FBA6004C12C0")); PutInBank(0x0F, 0x8300, Blob.FromHex("A5202903F002A2038611A520290CF0058A090C8511A52045212511452185214520AA2910F00EA5202910F002E623A521491085218A2920F00EA5202920F002E622A521492085218A2940F00EA5202940F002E625A521494085218A2980F00EA5202980F002E624A5214980852160")); PutInBank(0x1F, 0xD7C2, CreateLongJumpTableEntry(0x0F, 0x8200)); // Battles use 2 separate and independent controller handlers for a total of 3 (because why not), so we patch these to respond to Up+A also PutInBank(0x0F, 0x8580, Blob.FromHex("A0018C1640888C1640A008AD16404AB0014A6EB368AD17402903C901261E88D0EAA51EC988F00BC948F004ADB368604C2EFE20A8FE20A8FE20A8FEA2FF9AA900851E9500CAD0FBA6004C12C0")); PutInBank(0x1F, 0xD828, CreateLongJumpTableEntry(0x0F, 0x8580)); // PutInBank(0x0B, 0x9A06, Blob.FromHex("4C28D8")); Included in bank 1B changes PutInBank(0x0C, 0x97C7, Blob.FromHex("2027F22028D82029ABADB36860")); // Put LongJump routine 6 bytes after UpdateJoy used to be PutInBank(0x1F, 0xD7C8, Blob.FromHex("85E99885EA6885EB6885ECA001B1EB85EDC8B1EB85EEC8ADFC6085E8B1EB2003FEA9D748A9F548A5E9A4EA6CED0085E9A5E82003FEA5E960")); // LongJump entries can start at 0xD800 and must stop before 0xD850 (at which point additional space will need to be freed to make room) // Patches for various tracking variables follow: // Pedometer + chests opened PutInBank(0x0F, 0x8100, Blob.FromHex("18A532D027A52D2901F006A550D01DF00398D018ADA06069018DA060ADA16069008DA160ADA26069008DA260A52F8530A9FF8518A200A000BD00622904F001C8E8D0F5988DB7606060")); Put(0x7D023, Blob.FromHex("A90F2003FE200081")); // Count number of battles PutInBank(0x0F, 0x8400, Blob.FromHex("18ADA76069018DA7609003EEA86020A8FE60")); PutInBank(0x1F, 0xD800, CreateLongJumpTableEntry(0x0F, 0x8400)); PutInBank(0x1F, 0xF28D, Blob.FromHex("2000D8")); // Ambushes / Strike First PutInBank(0x0F, 0x8420, Blob.FromHex("AD5668C90B9015C95A901F18ADAB6069018DAB609014EEAC6018900E18ADA96069018DA9609003EEAA60AC5668AE576860")); PutInBank(0x1F, 0xD806, CreateLongJumpTableEntry(0x0F, 0x8420)); Put(0x313FB, Blob.FromHex("eaeaea2006D8")); // Runs PutInBank(0x0F, 0x8480, Blob.FromHex("AD5868F00E18ADAD6069018DAD609003EEAE60AD586860")); PutInBank(0x1F, 0xD81C, CreateLongJumpTableEntry(0x0F, 0x8480)); Put(0x32418, Blob.FromHex("201CD8")); // Physical Damage PutInBank(0x0F, 0x84B0, Blob.FromHex("8E7D68AD8768F01DADB2606D82688DB260ADB3606D83688DB360ADB46069008DB46018901AADAF606D82688DAF60ADB0606D83688DB060ADB16069008DB160AE7D6860")); PutInBank(0x1F, 0xD822, CreateLongJumpTableEntry(0x0F, 0x84B0)); Put(0x32968, Blob.FromHex("2022D8")); // Magic Damage PutInBank(0x0F, 0x8500, Blob.FromHex("AD8A6C2980F01CADB2606D58688DB260ADB3606D59688DB360ADB46069008DB460901AADAF606D58688DAF60ADB0606D59688DB060ADB16069008DB160A912A212A00160")); PutInBank(0x1F, 0xD83A, CreateLongJumpTableEntry(0x0F, 0x8500)); PutInBank(0x0C, 0xB8ED, Blob.FromHex("203AD8eaeaea")); // Party Wipes PutInBank(0x0F, 0x85D0, Blob.FromHex("EEB564A9008DFD64A200187D00647D00657D00667D0067E8D0F149FF8DFD64A952854B60")); PutInBank(0x1F, 0xD82E, CreateLongJumpTableEntry(0x0F, 0x85D0)); //PutInBank(0x0B, 0x9AF5, Blob.FromHex("202ED8EAEA")); included in 1B changes // "Nothing Here"s PutInBank(0x0F, 0x8600, Blob.FromHex("A54429C2D005A545F00360A900EEB66060")); PutInBank(0x1F, 0xD834, CreateLongJumpTableEntry(0x0F, 0x8600)); PutInBank(0x1F, 0xCBF3, Blob.FromHex("4C34D8")); // Add select button handler on game start menu to change color PutInBank(0x0F, 0x8620, Blob.FromHex("203CC4A662A9488540ADFB60D003EEFB60A522F022EEFB60ADFB60C90D300EF007A9018DFB60D005A90F8DFB60A90085222029EBA90060A90160")); PutInBank(0x1F, 0xD840, CreateLongJumpTableEntry(0x0F, 0x8620)); Put(0x3A1B5, Blob.FromHex("2040D8D0034C56A1EA")); // Move Most of LoadBorderPalette_Blue out of the way to do a dynamic version. PutInBank(0x0F, 0x8700, Blob.FromHex("988DCE038DEE03A90F8DCC03A9008DCD03A9308DCF0360")); // Move DrawCommandMenu out of Bank F so we can add no Escape to it PutInBank(0x0F, 0x8740, Blob.FromHex("A000A200B91BFA9D9E6AE8C01BD015AD916D2903F00EA9139D9E6AE8C8A9F79D9E6AE8C8E005D0052090F6A200C8C01ED0D260")); // Create a clone of IsOnBridge that checks the canal too. PutInBank(0x0F, 0x8780, Blob.FromHex("AD0860F014A512CD0960D00DA513CD0A60D006A90085451860A512CD0D60D00DA513CD0E60D006A900854518603860")); // BB Absorb fix. //PutInBank(0x0F, 0x8800, Blob.FromHex("A000B186C902F005C908F00160A018B186301BC8B1863016C8B1863011C8B186300CA026B1861869010AA0209186A01CB186301AC8B1863015C8B1863010C8B186300BA026B186186901A022918660")); // Copyright overhaul, see 0F_8960_DrawSeedAndFlags.asm PutInBank(0x0F, 0x8980, Blob.FromHex("A9238D0620A9208D0620A200BD00898D0720E8E060D0F560")); // Fast Battle Boxes PutInBank(0x0F, 0x8A00, Blob.FromHex("A940858AA922858BA91E8588A969858960")); PutInBank(0x0F, 0x8A20, Blob.FromHex($"A9{BattleBoxDrawInRows}8DB96820A1F420E8F4A5881869208588A58969008589A58A186920858AA58B6900858BCEB968D0DE60")); // Fast Battle Boxes Undraw (Similar... yet different!) PutInBank(0x0F, 0x8A80, Blob.FromHex("A9A0858AA923858BA97E8588A96A858960")); PutInBank(0x0F, 0x8AA0, Blob.FromHex($"A9{BattleBoxUndrawRows}8DB96820A1F420E8F4A58838E9208588A589E9008589A58A38E920858AA58BE900858BCEB968D0DE60")); // Softlock fix Put(0x7C956, Blob.FromHex("A90F2003FE4C008B")); PutInBank(0x0F, 0x8B00, Blob.FromHex("BAE030B01E8A8D1001A9F4AAA9FBA8BD0001990001CA88E010D0F4AD1001186907AA9AA52948A52A48A50D48A54848A549484C65C9")); // Change INT to MDEF in the Status screen Put(0x388F5, Blob.FromHex("968D8E8F")); Data[0x38DED] = 0x25; //Key Items + Progressive Scaling if (flags.ProgressiveScaleMode == ProgressiveScaleMode.OrbProgressiveSlow || flags.ProgressiveScaleMode == ProgressiveScaleMode.OrbProgressiveMedium || flags.ProgressiveScaleMode == ProgressiveScaleMode.OrbProgressiveFast || flags.ProgressiveScaleMode == ProgressiveScaleMode.OrbProgressiveVFast) { if (flags.ShardHunt) { PutInBank(0x0F, 0x9000, Blob.FromHex("AD35608DB86060")); } else { PutInBank(0x0F, 0x9000, Blob.FromHex("A200AD3160F001E8AD3260F001E8AD3360F001E8AD3460F001E88EB86060")); } } else { PutInBank(0x0F, 0x9000, Blob.FromHex("A200AD2160F001E8AD2260F001E8AD2560F001E8AD2A60F001E8AD2B60F001E8AD2C60F001E8AD2E60F001E8AD3060F001E8AD0060F001E8AD1260F001E8AD0460F001E8AD0860F001E8AD0C60D001E8AD2360D007AD0A622902F001E8AD2460D007AD05622902F001E8AD2660D007AD08622902F001E8AD2760D007AD09622902F001E8AD2860D007AD0B622902F001E8AD2960D007AD14622901D001E8AD2D60D007AD0E622902F001E8AD2F60D007AD13622903F001E88EB86060")); } PutInBank(0x1F, 0xCFCB, CreateLongJumpTableEntry(0x0F, 0x9100)); //Division routine PutInBank(0x0F, 0x90C0, Blob.FromHex("8A48A9008513A210261026112613A513C5129004E512851326102611CAD0EDA513851268AA60")); // Progressive scaling also writes to 0x9100 approaching 200 bytes, begin next Put at 0x9200. // Replace Overworld to Floor and Floor to Floor teleport code to JSR out to 0x9200 to set X / Y AND inroom from unused high bit of X. PutInBank(0x1F, 0xC1E2, Blob.FromHex("A9002003FEA545293FAABD00AC8510BD20AC8511BD40AC8548AABDC0AC8549A90F2003FE200092EAEAEAEAEAEA")); PutInBank(0x1F, 0xC968, Blob.FromHex("A9002003FEA645BD00AD8510BD40AD8511BD80AD8548AABDC0AC8549A90F2003FE200092EAEA")); PutInBank(0x0F, 0x9200, Blob.FromHex("A200A5100A9002A2814A38E907293F8529A5110A9002860D4A38E907293F852A60")); // Critical hit display for number of hits PutInBank(0x0F, 0x9280, FF1Text.TextToBytes("Critical hit!!", false)); PutInBank(0x0F, 0x9290, FF1Text.TextToBytes(" Critical hits!", false)); PutInBank(0x0F, 0x92A0, Blob.FromHex("AD6B68C901F01EA2019D3A6BA9118D3A6BA900E89D3A6BA0FFC8E8B990929D3A6BD0F6F00EA2FFA0FFC8E8B980929D3A6BD0F6A23AA06BA904201CF7EEF86A60")); // Enable 3 palettes in battle PutInBank(0x1F, 0xFDF1, CreateLongJumpTableEntry(0x0F, 0x9380)); PutInBank(0x0F, 0x9380, Blob.FromHex("ADD16A2910F00BA020B9336D99866B88D0F7ADD16A290F8DD16A20A1F4AD0220A9028D1440A93F8D0620A9008D0620A000B9876B8D0720C8C020D0F5A93F8D0620A9008D06208D06208D062060")); }
public void ShortenToFR(List <Map> maps, bool includeRefightTiles, bool refightAll, bool addExitTile, bool addLutePlate, MT19337 rng) { // Black Orb tile Warp destination change straight to an edit Chaos floor with all the ToFR Chests. Data[0x00D80] = 0x80; // Map edits Data[0x02D01] = 0x0F; Data[0x02D41] = 0x03; Data[0x02D81] = 0x3B; // ToFR Map Hack List <Blob> landingArea = new List <Blob> { Blob.FromHex("3F3F000101010101023F3F"), Blob.FromHex("3F00045D5E5F606104023F"), Blob.FromHex("0004620404040404630402"), Blob.FromHex("0304040404040404040405"), Blob.FromHex("0604040404040404040408"), Blob.FromHex("3006040410041104040830"), Blob.FromHex("3130060707070707083031"), Blob.FromHex("3131303030363030303131"), Blob.FromHex("31383831383A3831383831"), }; if (includeRefightTiles) { var battles = new List <byte> { 0x57, 0x58, 0x59, 0x5A }; if (refightAll) { landingArea.Add(Blob.FromHex($"31{battles[3]:X2}{battles[2]:X2}{battles[1]:X2}{battles[0]:X2}31{battles[0]:X2}{battles[1]:X2}{battles[2]:X2}{battles[3]:X2}31")); } else { battles.Shuffle(rng); landingArea.Add(Blob.FromHex($"31{battles[0]:X2}3131{battles[1]:X2}31{battles[2]:X2}3131{battles[3]:X2}31")); } } maps[(int)MapId.TempleOfFiendsRevisitedChaos].Put((0x0A, 0x00), landingArea.ToArray()); if (addExitTile) { // add warp portal to alternate map, allowing player to Exit ToFR maps[(byte)MapId.TempleOfFiendsRevisitedChaos][3, 15] = (byte)Tile.PortalWarp; } if (addLutePlate) { // add lute plate (can't use mapNpcIndex 0-2, those belong to Garland) SetNpc(MapId.TempleOfFiendsRevisitedChaos, mapNpcIndex: 3, ObjectId.LutePlate, 15, 5, inRoom: true, stationary: true); // set walkable black tiles near lute plate to "lute usable" tiles for (int y = 2; y <= 4; y++) { for (int x = 13; x <= 17; x++) { if (maps[(byte)MapId.TempleOfFiendsRevisitedChaos][y, x].Equals((byte)0x04)) { maps[(byte)MapId.TempleOfFiendsRevisitedChaos][y, x] = (byte)0x20; } } } // add statue decorations maps[(byte)MapId.TempleOfFiendsRevisitedChaos][6, 14] = (byte)0x10; maps[(byte)MapId.TempleOfFiendsRevisitedChaos][6, 16] = (byte)0x11; // replace "The tune plays,\nrevealing a stairway." text (0x385BA) originally "9DAB1AB7B8B11AB3AFA4BCB6BF05B5A8B92BAF1FAA2024B7A4ACB55DBCC000" Put(0x385BA, FF1Text.TextToBytes("The tune plays,\nopening the pathway.", useDTE: true)); // make lute plate a single color MakeGarlandsBorderTransparent(); // so lute plate change doesn't conflict with Garland, he'll look the same on a black background Put(0x02B2D, Blob.FromHex("27")); // change bottom lute plate palette } }
public void EnableShardHunt(MT19337 rng, TalkRoutines talkroutines, ShardCount count) { int goal = 16; switch (count) { case ShardCount.Count16: goal = 16; break; case ShardCount.Count20: goal = 20; break; case ShardCount.Count24: goal = 24; break; case ShardCount.Count28: goal = 28; break; case ShardCount.Count32: goal = 32; break; case ShardCount.Count36: goal = 36; break; case ShardCount.Range16_24: goal = rng.Between(16, 24); break; case ShardCount.Range24_32: goal = rng.Between(24, 32); break; case ShardCount.Range16_36: goal = rng.Between(16, 36); break; } string shardName = ShardNames.PickRandom(rng); // Replace unused CANOE string and EarthOrb pointer with whatever we're calling the scavenged item. ItemsText[(int)Item.Shard] = shardName; // Replace the upper two tiles of the unlit orb with an empty and found shard. // These are at tile address $76 and $77 respectively. Put(0x37760, Blob.FromHex("001C22414141221CFFE3DDBEBEBEDDE3001C3E7F7F7F3E1CFFFFE3CFDFDFFFFF")); int ppu = 0x2043; ppu = ppu + (goal <= 24 ? 0x20 : 0x00); // Fancy shard drawing code, see 0E_B8D7_DrawShardBox.asm Put(0x3B87D, Blob.FromHex($"A9{ppu & 0xFF:X2}8511A9{(ppu & 0xFF00) >> 8:X2}8512A977A00048AD0220A5128D0620A51118692085118D0620900DAD0220E612A5128D0620A5118D062068A200CC3560D002A976C0{goal:X2}D001608D0720C8E8E006D0EB1890C1")); // Black Orb Override to check for shards rather than ORBs. BlackOrbChecksShardsCountFor(goal, talkroutines); // A little narrative overhaul. Blob intro = FF1Text.TextToStory(new string[] { "The Time Loop has reopened!", "", "The ORBS have been smashed!", "", "", "", $"The resulting {shardName}S were", "", "stolen and scattered around", "", "the world to distract while", "", "this new evil incubates....", "", "", "", "But The Light Warriors return!", "", $"They will need {goal} {shardName}S", "", "to restore the BLACK ORB and", "", "confront this new malevolence.", }); System.Diagnostics.Debug.Assert(intro.Length <= 208); Put(0x37F20, intro); InsertDialogs(new Dictionary <int, string>() { { 0x21, $"The {shardName}S coalesce to\nrestore the Black ORB.\n\nBrave Light Warriors....\nDestroy the Evil within!" }, // Black Orb Text { 0x2E, $"Ah, the Light Warriors!\n\nSo you have collected\nthe {shardName}S and restored\nthe BLACK ORB." }, { 0x2F, "Thus you've travelled\n2000 years into the past\nto try to stop me?\n\nStep forward then,\nto your peril!" }, { 0x30, "Oh, Light Warriors!\nSuch arrogant bravery.\n\nLet us see whom history\nremembers. En Garde!" }, }); }
public void Randomize(Blob seed, Flags flags) { var rng = new MT19337(BitConverter.ToUInt32(seed, 0)); UpgradeToMMC3(); EasterEggs(); DynamicWindowColor(); PermanentCaravan(); var map = new OverworldMap(this, flags); var shopItemLocation = ItemLocations.CaravanItemShop1; if (flags.ModernBattlefield) { SetBattleUI(true); } if (flags.TitansTrove) { EnableTitansTrove(); } // This has to be done before we shuffle spell levels. if (flags.SpellBugs) { FixSpellBugs(); } if (flags.Shops) { shopItemLocation = ShuffleShops(rng, flags.EnemyStatusAttacks); } if (flags.Treasures || flags.NPCItems || flags.NPCFetchItems) { var incentivesData = new IncentiveData(rng, flags, map.MapLocationRequirements); ShuffleTreasures(rng, flags, incentivesData, shopItemLocation, map.MapLocationRequirements); } if (flags.MagicShops) { ShuffleMagicShops(rng); } if (flags.MagicLevels) { ShuffleMagicLevels(rng, flags.MagicPermissions); } if (flags.Rng) { ShuffleRng(rng); } if (flags.EnemyScripts) { ShuffleEnemyScripts(rng); } if (flags.EnemySkillsSpells) { ShuffleEnemySkillsSpells(rng); } if (flags.EnemyStatusAttacks) { ShuffleEnemyStatusAttacks(rng); } if (flags.OrdealsPillars) { ShuffleOrdeals(rng); } if (flags.CrownlessOrdeals) { EnableEarlyOrdeals(); } if (flags.KeylessToFR) { EnableKeylessToFR(); } if (flags.EarlySarda && !flags.NPCItems) { EnableEarlySarda(); } if (flags.EarlySage && !flags.NPCItems) { EnableEarlySage(); } if (flags.FreeBridge) { EnableFreeBridge(); } if (flags.FreeAirship) { EnableFreeAirship(); } if (flags.FreeOrbs) { EnableFreeOrbs(); } if (flags.NoPartyShuffle) { DisablePartyShuffle(); } if (flags.SpeedHacks) { EnableSpeedHacks(); } if (flags.IdentifyTreasures) { EnableIdentifyTreasures(); } if (flags.Dash) { EnableDash(); } if (flags.BuyTen) { EnableBuyTen(); } if (flags.EasyMode) { EnableEasyMode(); } if (flags.HouseMPRestoration) { FixHouse(); } if (flags.WeaponStats) { FixWeaponStats(); } if (flags.ChanceToRun) { FixChanceToRun(); } if (flags.EnemyStatusAttackBug) { FixEnemyStatusAttackBug(); } if (flags.FunEnemyNames) { FunEnemyNames(flags.TeamSteak); } var itemText = ReadText(ItemTextPointerOffset, ItemTextPointerBase, ItemTextPointerCount); itemText[99] = FF1Text.TextToBytes("Ribbon ", useDTE: false); ExpGoldBoost(flags.ExpBonus, flags.ExpMultiplier); ScalePrices(flags.PriceScaleFactor, flags.ExpMultiplier, itemText, rng); map.ApplyMapEdits(); WriteText(itemText, ItemTextPointerOffset, ItemTextPointerBase, ItemTextOffset, UnusedGoldItems); if (flags.EnemyScaleFactor > 1) { ScaleEnemyStats(flags.EnemyScaleFactor, rng); } if (flags.ForcedPartyMembers > 0) { PartyRandomize(rng, flags.ForcedPartyMembers); } if (flags.MapCanalBridge) { EnableCanalBridge(); } // We have to do "fun" stuff last because it alters the RNG state. RollCredits(rng); if (flags.PaletteSwap) { PaletteSwap(rng); } if (flags.TeamSteak) { TeamSteak(); } if (flags.Music != MusicShuffle.None) { ShuffleMusic(flags.Music, rng); } WriteSeedAndFlags(Version, seed.ToHex(), Flags.EncodeFlagsText(flags)); ExtraTrackingAndInitCode(); }
public void EnableShardHunt(MT19337 rng, int goal, List <Map> maps, bool npcShuffleEnabled) { if (goal < 1 || goal > 30) { throw new ArgumentOutOfRangeException(); } if (!npcShuffleEnabled) { // NPC Shuffle fixes OpenTreasureChest to not play the fanfare for Shards differently System.Diagnostics.Debug.Assert(Data[0x7DDA0] == (byte)Item.Tent); Data[0x7DDA0] = (byte)Item.Shard; } var shardName = new List <string> { "SHARD", "JEWEL", "PIECE", "CHUNK", "PRISM", "STONE", "SLICE", "WEDGE", "BIGGS", "SLIVR", "ORBLT", "ESPER", "FORCE", }.PickRandom(rng); // Replace unused CANOE string and EarthOrb pointer with whatever we're calling the scavenged item. Put(0x2B981, FF1Text.TextToBytes($"{shardName} ", false, FF1Text.Delimiter.Null)); Data[0x2B72A] = 0x81; // Replace the upper two tiles of the unlit orb with an empty and found shard. // These are at tile address $76 and $77 respectively. Put(0x37760, Blob.FromHex("001C22414141221CFFE3DDBEBEBEDDE3001C3E7F7F7F3E1CFFFFE3CFDFDFFFFF")); String hexCount = goal.ToString("X2"); String ppuLowByte = goal <= 24 ? "63" : "43"; // Fancy shard drawing code, see 0E_B8D7_DrawShardBox.asm Put(0x3B87D, Blob.FromHex($"A9{ppuLowByte}8511A977A00048AD0220A9208D0620A51118692085118D0620900DAD0220A9218D0620A9038D062068A200CC3560D002A976C0{hexCount}D001608D0720C8E8E006D0EB1890C3")); // Black Orb Override to jump to the final floor. This allows us to give out some last minute loot and // and make the repeated attempts the final battle strategy take a little longer due to some walking. Put(0x39502, Blob.FromHex($"AD3560C9{hexCount}300CA0CA209690E67DE67DA51160A51260")); Put(0x7CDB3, Blob.FromHex("08CE")); Data[0x00D80] = 0x80; // Map edits Data[0x02D01] = 0x0F; Data[0x02D41] = 0x03; Data[0x02D81] = 0x3B; // ToFR Map Hack Blob[] landingArea = { Blob.FromHex("3F3F000101010101023F3F"), Blob.FromHex("3F00045D5E5F606104023F"), Blob.FromHex("0004620404040404630402"), Blob.FromHex("0304040404040404040405"), Blob.FromHex("0604040404040404040408"), Blob.FromHex("3006040410041104040830"), Blob.FromHex("3130060707070707083031"), Blob.FromHex("3131303030363030303131"), Blob.FromHex("31383831383A3831383831"), }; maps[59].Put(0x00, 0x0A, landingArea); // A little narrative overhaul. Blob intro = FF1Text.TextToStory(new string[] { "The Time Loop has reopened!", "", "The ORBS have been smashed!", "", "", "", $"The resulting {shardName}S were", "", "stolen and scattered around", "", "the world to distract while", "", "this new evil incubates....", "", "", "", "But The Light Warriors return!", "", $"They will need {goal} {shardName}S", "", "to restore the BLACK ORB and", "", "confront this new malevolence.", }); System.Diagnostics.Debug.Assert(intro.Length <= 208); Put(0x37F20, intro); Put(0x289B2, FF1Text.TextToBytes($"The {shardName}S coalesce to\nrestore the Black ORB.\n\nBrave Light Warriors....\nDestroy the Evil within!")); // Black Orb Text Put(0x28CF8, FF1Text.TextToBytes($"Ah, the Light Warriors!\n\nSo you have collected\nthe {shardName}S and restored\nthe BLACK ORB.")); Put(0x28D57, FF1Text.TextToBytes("Thus you've travelled\n2000 years into the past\nto try to stop me?\n\nStep forward then,\nto your peril!")); Put(0x28DAF, FF1Text.TextToBytes("Oh, Light Warriors!\nSuch arrogant bravery.\n\nLet us see whom history\nremembers. En Garde!")); // Scale up the Fundead enemies in case we end up with them. They're too weak otherwise. ScaleSingleEnemyStats(0x78, 1.4); ScaleSingleEnemyStats(0x33, 1.4); }
public void TitanSnack(TitanSnack snack, NPCdata npcdata, MT19337 rng) { if (snack == FF1Lib.TitanSnack.Ruby) { return; } var snackOptions = new List <string>(); // { "NEWRUBY(max 7 characters);NEWRUBYPLURALIZED(max 8 characters);IS/ARE(relating to plural form);DESCRIPTOR(max 6 characters);ONOMATOPOEIA(max 6 chars, how ingestion sounds)" } var mineralSnacks = new List <string> { "DIAMOND;DIAMONDS;ARE;SWEET;CRUNCH", "GEODE;GEODES;ARE;SWEET;CRUNCH", "COAL;COAL;IS;SMOKY;CRUNCH", "PEARL;PEARLS;ARE;SWEET;CRUNCH", "FOSSIL;FOSSILS;ARE;SWEET;CRUNCH", "EMERALD;EMERALDS;ARE;SWEET;CRUNCH", "TOPAZ;TOPAZ;IS;SWEET;CRUNCH", "QUARTZ;QUARTZ;IS;SWEET;CRUNCH", "ONYX;ONYXES;ARE;SWEET;CRUNCH", "MARBLE;MARBLE;IS;SWEET;CRUNCH", "AMETHST;AMETHST;IS;SWEET;CRUNCH", "JADE;JADES;ARE;SWEET;CRUNCH", "SAPHIRE;SAPHIRE;IS;SWEET;CRUNCH", "GRANITE;GRANITE;IS;SWEET;CRUNCH", "OBSDIAN;OBSDIAN;IS;SWEET;CRUNCH", "CONCRET;CONCRET;IS;SALTY;CRUNCH", "ASPHALT;ASPHALT;IS;SALTY;CRUNCH", "PUMICE;PUMICE;IS;SWEET;CRUNCH", "LIMESTN;LIMESTN;IS;SOUR;CRUNCH", "SNDSTON;SNDSTON;IS;SALTY;CRUNCH", "MYTHRL;MYTHRL;IS;SWEET;CRUNCH" }; var junkFoodSnacks = new List <string> { "DANISH;DANISHES;ARE;SWEET;MUNCH", "HOT DOG;HOT DOGS;ARE;GREAT;MUNCH", "TACO;TACOS;ARE;GREAT;MUNCH", "SUB;SUBS;ARE;GREAT;MUNCH", "PIZZA;PIZZA;IS;YUMMY;MUNCH", "BURGER;BURGERS;ARE;YUMMY;MUNCH", "EGGROLL;EGGROLLS;ARE;YUMMY;MUNCH", "BISCUIT;BISCUITS;ARE;YUMMY;MUNCH", "WAFFLE;WAFFLES;ARE;YUMMY;MUNCH", "CAKE;CAKE;IS;SWEET;MUNCH", "PIE;PIE;IS;SWEET;MUNCH", "DONUT;DONUTS;ARE;SWEET;MUNCH", "FRIES;FRIES;ARE;SALTY;MUNCH", "CHIPS;CHIPS;ARE;SALTY;CRUNCH", "CANDY;CANDY;IS;SWEET;MUNCH", "PANCAKE;PANCAKES;ARE;SWEET;MUNCH", "ICE CRM;ICE CRM;IS;CREAMY;MUNCH", "PUDDING;PUDDING;IS;YUMMY;MUNCH", "BROWNIE;BROWNIES;ARE;SWEET;MUNCH", "CRAYON;CRAYONS;ARE;WEIRD;MUNCH", "GLUE;GLUE;IS;WEIRD;MUNCH", "PASTE;PASTE;IS;WEIRD;MUNCH", "LASAGNA;LASAGNA;IS;YUMMY;MUNCH", "POUTINE;POUTINE;IS;GREAT;MUNCH", "PASTA;PASTA;IS;YUMMY;MUNCH", "RAMEN;RAMEN;IS;GREAT;MUNCH", "STEAK;STEAK;IS;GREAT;MUNCH", "NACHOS;NACHOS;ARE;SALTY;CRUNCH", "BACON;BACON;IS;SALTY;MUNCH", "MUTTON;MUTTON;IS;GREAT;MUNCH", "BAGEL;BAGELS;ARE;GREAT;MUNCH", "CHEESE;CHEESE;IS;GREAT;MUNCH", "POPCORN;POPCORN;IS;SALTY;MUNCH", "CHICKEN;CHICKEN;IS;GREAT;MUNCH", "BEEF;BEEF;IS;GREAT;MUNCH", "HAM;HAM;IS;GREAT;MUNCH", "BOLOGNA;BOLOGNA;IS;GREAT;MUNCH", "HOAGIE;HOAGIES;ARE;GREAT;MUNCH", "FILET;FILET;IS;DIVINE;MUNCH", "LOBSTER;LOBSTER;IS;DIVINE;MUNCH" }; var healthySnacks = new List <string> { "EDAMAME;EDAMAME;IS;SALTY;MUNCH", "SALAD;SALAD;IS;GREAT;MUNCH", "APPLE;APPLES;ARE;SWEET;CRUNCH", "PEAR;PEARS;ARE;SWEET;MUNCH", "MELON;MELONS;ARE;SWEET;MUNCH", "ORANGE;ORANGES;ARE;SWEET;MUNCH", "LEMON;LEMONS;ARE;SOUR;MUNCH", "YOGURT;YOGURT;IS;GREAT;MUNCH", "GRANOLA;GRANOLA;IS;GREAT;CRUNCH", "SPINACH;SPINACH;IS;YUMMY;MUNCH", "EGG;EGGS;ARE;YUMMY;MUNCH", "GRAPES;GRAPES;ARE;YUMMY;MUNCH", "OATMEAL;OATMEAL;IS;GREAT;MUNCH", "TOFU;TOFU;IS;WEIRD;MUNCH", "CABBAGE;CABBAGE;IS;FRESH;MUNCH", "LETTUCE;LETTUCE;IS;FRESH;MUNCH", "TOMATO;TOMATOES;ARE;YUMMY;MUNCH", "SUSHI;SUSHI;IS;FISHY;MUNCH", "TUNA;TUNA;IS;FISHY;MUNCH", "SALMON;SALMON;IS;FISHY;MUNCH", "FISH;FISH;IS;FRESH;MUNCH", "BEANS;BEANS;ARE;YUMMY;MUNCH", "CEREAL;CEREAL;IS;GREAT;MUNCH", "PRETZEL;PRETZELS;ARE;SALTY;MUNCH", "EGGSALD;EGGSALAD;IS;GREAT;MUNCH", "RICE;RICE;IS;PLAIN;MUNCH", "CAVIAR;CAVIAR;IS;DIVINE;MUNCH" }; var beverages = new List <string> { "BEER;BEER;IS;SMOOTH;GULP", "WINE;WINE;IS;RICH;GULP", "TEA;TEA;IS;FRESH;GULP", "COFFEE;COFFEE;IS;FRESH;GULP", "COLA;COLA;IS;SWEET;GULP", "COCOA;COCOA;IS;SWEET;GULP", "ICEDTEA;ICEDTEA;IS;SWEET;GULP", "LMONADE;LEMONADE;IS;SWEET;GULP", "MILK;MILK;IS;GREAT;GULP", "LATTE;LATTES;ARE;CREAMY;GULP", "WATER;WATER;IS;FRESH;GULP", "TEQUILA;TEQUILA;IS;SMOOTH;GULP" }; switch (snack) { case FF1Lib.TitanSnack.Minerals: snackOptions = mineralSnacks; break; case FF1Lib.TitanSnack.Junk: snackOptions = junkFoodSnacks; break; case FF1Lib.TitanSnack.Healthy: snackOptions = healthySnacks; break; case FF1Lib.TitanSnack.Beverages: snackOptions = beverages; break; case FF1Lib.TitanSnack.All: // combine all lists together foreach (string mineral in mineralSnacks) { snackOptions.Add(mineral); } foreach (string junkFood in junkFoodSnacks) { snackOptions.Add(junkFood); } foreach (string healthySnack in healthySnacks) { snackOptions.Add(healthySnack); } foreach (string beverage in beverages) { snackOptions.Add(beverage); } break; default: return; } var dialogs = ReadText(dialogsPointerOffset, dialogsPointerBase, dialogsPointerCount); var randomRuby = snackOptions.PickRandom(rng); var newRubyItemDescription = "A tasty treat."; // Replaces "A large red stone." (can't be too long else it'll overwrite next phrase: "The plate shatters,") var newTitanCraving = "is hungry."; // replaces "eat gems." (can't be too long or will appear outside window) if (beverages.Contains(randomRuby)) { newRubyItemDescription = "A tasty drink."; newTitanCraving = "is thirsty."; } else if (mineralSnacks.Contains(randomRuby)) { newRubyItemDescription = "Feels heavy."; } // replace "A red stone." item description (0x38671) originally "8AFFAF2FAA1A23A724285AC000" Put(0x38671, FF1Text.TextToBytes(newRubyItemDescription, useDTE: true)); // phrase parts var newRubyContent = randomRuby.Split(";"); var newRuby = newRubyContent[0]; var newRubyPluralized = newRubyContent[1]; var newRubySubjectVerbAgreement = newRubyContent[2]; var newRubyTastes = newRubyContent[3]; var newRubyOnomatopoeia = newRubyContent[4]; var newRubyArticle = ""; // newRubySubjectVerbAgreement; if (newRubySubjectVerbAgreement == "ARE") { if (newRuby[0] == 'A' || newRuby[0] == 'E' || newRuby[0] == 'I' || newRuby[0] == 'O' || newRuby[0] == 'U') { newRubyArticle = "an "; } else { newRubyArticle = "a "; } } // handle extra dialogues that might contain the RUBY if the NPChints flag is enabled var dialogsUpdate = SubstituteKeyItemInExtraNPCDialogues("RUBY", newRuby, dialogs); // begin substitute phrase parts var titanDeepDungeon = dialogs[0x29].Split(new string[] { "a RUBY" }, System.StringSplitOptions.RemoveEmptyEntries); var titanDialogue = dialogs[0x2A].Split(new string[] { "RUBY", "Crunch, crunch, crunch,", "sweet", "Rubies are" }, System.StringSplitOptions.RemoveEmptyEntries); var melmondManDialogue = dialogs[0x7B].Split(new string[] { "eats gems.", "RUBIES" }, System.StringSplitOptions.RemoveEmptyEntries); // Bring me a {newRuby} if you // wish to skip to floor 22. if (titanDeepDungeon.Length > 1) { dialogsUpdate.Add(0x29, titanDeepDungeon[0] + newRubyArticle + newRuby + titanDeepDungeon[1]); } // If you want pass, give // me the {newRuby}.. // {Onomatopoeia}, {onomatopoeia}, {onomatopoeia}, // mmm, it tastes so {newRubyTastes}. // {newRubyPluralized} {newRubySubjectVerbAgreement} my favorite. if (titanDialogue.Length > 3) { dialogsUpdate.Add(0x2A, titanDialogue[0] + newRuby + titanDialogue[1] + CapitalizeFirstLowercaseRest(newRubyOnomatopoeia) + ", " + newRubyOnomatopoeia.ToLower() + ", " + newRubyOnomatopoeia.ToLower() + "," + titanDialogue[2] + newRubyTastes.ToLower() + titanDialogue[3] + CapitalizeFirstLowercaseRest(newRubyPluralized) + " " + newRubySubjectVerbAgreement.ToLower() + titanDialogue[4]); } else if (titanDialogue.Length > 1) { // handle Shuffle Astos, alternate Titan dialog "If you want pass, give\nme the RUBY..\nHa, it mine! Now, you in\ntrouble. Me am Astos,\nKing of the Titans!" dialogsUpdate.Add(0x2A, titanDialogue[0] + newRuby + titanDialogue[1]); } // The Titan who lives in // the tunnel {newTitanCraving} // He loves {newRubyPluralized}. if (melmondManDialogue.Length > 2) { dialogsUpdate.Add(0x7B, melmondManDialogue[0] + newTitanCraving + melmondManDialogue[1] + newRubyPluralized + melmondManDialogue[2]); } // end substitute phrase parts if (dialogsUpdate.Count > 0) { InsertDialogs(dialogsUpdate); } // substitute key item ItemsText[(int)Item.Ruby] = newRuby; }
public void Randomize(Blob seed, Flags flags) { var rng = new MT19337(BitConverter.ToUInt32(seed, 0)); EasterEggs(); RollCredits(); // This has to be done before we shuffle spell levels. if (flags.SpellBugs) { FixSpellBugs(); } if (flags.Treasures) { ShuffleTreasures(rng, flags.EarlyCanoe, flags.EarlyOrdeals, flags.IncentivizeIceCave, flags.IncentivizeOrdeals); } if (flags.Shops) { ShuffleShops(rng, flags.EnemyStatusAttacks); } if (flags.MagicShops) { ShuffleMagicShops(rng); } if (flags.MagicLevels) { ShuffleMagicLevels(rng, flags.MagicPermissions); } if (flags.Rng) { ShuffleRng(rng); } if (flags.EnemyScripts) { ShuffleEnemyScripts(rng); } if (flags.EnemySkillsSpells) { ShuffleEnemySkillsSpells(rng); } if (flags.EnemyStatusAttacks) { ShuffleEnemyStatusAttacks(rng); } if (flags.Ordeals) { ShuffleOrdeals(rng); } if (flags.EarlyOrdeals) { EnableEarlyOrdeals(); } if (flags.EarlyRod) { EnableEarlyRod(); } if (flags.EarlyCanoe) { EnableEarlyCanoe(); } if (flags.EarlyBridge) { EnableEarlyBridge(); } if (flags.NoPartyShuffle) { DisablePartyShuffle(); } if (flags.SpeedHacks) { EnableSpeedHacks(); } if (flags.IdentifyTreasures) { EnableIdentifyTreasures(); } if (flags.Dash) { EnableDash(); } if (flags.BuyTen) { EnableBuyTen(); } if (flags.HouseMPRestoration) { FixHouse(); } if (flags.WeaponStats) { FixWeaponStats(); } if (flags.ChanceToRun) { FixChanceToRun(); } if (flags.EnemyStatusAttackBug) { FixEnemyStatusAttackBug(); } if (flags.FunEnemyNames) { FunEnemyNames(flags.TeamSteak); } var itemText = ReadText(ItemTextPointerOffset, ItemTextPointerBase, ItemTextPointerCount); itemText[99] = FF1Text.TextToBytes("Ribbon ", useDTE: false); ExpGoldBoost(flags.ExpBonus, flags.ExpMultiplier); ScalePrices(flags.PriceScaleFactor, flags.ExpMultiplier, itemText, rng); WriteText(itemText, ItemTextPointerOffset, ItemTextPointerBase, ItemTextOffset); if (flags.EnemyScaleFactor > 1) { ScaleEnemyStats(flags.EnemyScaleFactor, rng); } // We have to do "fun" stuff last because it alters the RNG state. if (flags.PaletteSwap) { PaletteSwap(rng); } if (flags.TeamSteak) { TeamSteak(); } if (flags.Music != MusicShuffle.None) { ShuffleMusic(flags.Music, rng); } if (flags.ShuffleLeader) { ShuffleLeader(rng); } WriteSeedAndFlags(Version, seed.ToHex(), EncodeFlagsText(flags)); //MMC3 conversion UpgradeToMMC3(); //Encounter table emu/hardware fix + track hard/soft resets this.PutInBank(0x0F, 0x8000, Blob.FromHex("A9008D00208D012085FEA90885FF85FDA51BC901D00160A901851BA94DC5F9F008A9FF85F585F685F7182088C8B046A94DC5F918F013AD176469018D1764AD186469008D1864189010AD196469018D1964AD1A6469008D1A64A9008DFD64A200187D00647D00657D00667D0067E8D0F149FF8DFD6460")); Put(0x7C012, Blob.FromHex("A90F2003FE200080EAEAEAEAEAEAEAEA")); }
private void SetupStoryPages(MT19337 rng) { // Setup DrawComplexString hijack for a particular escape sequence. See Credits.asm. Put(0x7DFA8, Blob.FromHex("A000A200B13EE63ED002E63F9510E8E003D0F18C1D608C1E60B111991C60C8C410D0F64C45DE")); List <Blob> pages = new List <Blob>(); BridgeStory.ForEach(story => pages.Add(FF1Text.TextToStory(story))); // An unused escape code from DrawComplexString is overridden allowing the following: // 1010 XX ADDR, Where 1010 enters the escape sequence, the next byte the the size of // the integer to print, and the next two bytes as a pointer to it. It is then copied // over the gold value, so 04 will follow as the next escape sequence to print gold. /* * Tracked Stats: * Steps: $60A0 (24-bit) * Hard Resets: $64A3 (16-bit) * Soft Resets: $64A5 (16-bit) * Battles: $60A7 (16-bit) * Ambushes: $60A9 (16-bit) * Strike Firsts: $60AB (16-bit) * Close Calls...: $60AD (16-bit) * Damage Dealt: $60AF (24-bit) * Damage Taken: $60B2 (24-bit) * Perished: $64B5 (8-bit) * "Nothing Here": $60B6 (8-bit) * Chests Opened: $60B7 (8-bit) * Can't Hold: $60B9 (8-bit) */ Blob[] movementStats = { FF1Text.TextToBytes("Movement Stats", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes("", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes("Steps ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101003A0600405"), FF1Text.TextToBytes("Resets ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101002A5640405"), FF1Text.TextToBytes("Power off ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101002A3640405"), FF1Text.TextToBytes("Nthng Here", true, FF1Text.Delimiter.Empty),Blob.FromHex("101001B6600405"), FF1Text.TextToBytes("Can't Hold", true, FF1Text.Delimiter.Empty),Blob.FromHex("101001B9600405"), FF1Text.TextToBytes("Chests Opened", true, FF1Text.Delimiter.Line), Blob.FromHex("FFFFFF101001B76004"), FF1Text.TextToBytes(" of 241", true, FF1Text.Delimiter.Null), }; Blob[] battleResults = { FF1Text.TextToBytes("Battle Results", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes("", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes("Battles ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101002A7600405"), FF1Text.TextToBytes("Ambushes ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101002A9600405"), FF1Text.TextToBytes("Struck 1st", true, FF1Text.Delimiter.Empty),Blob.FromHex("101002AB600405"), FF1Text.TextToBytes("Close call", true, FF1Text.Delimiter.Empty),Blob.FromHex("101002AD600405"), FF1Text.TextToBytes("Perished ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101001B5640400"), }; Blob[] combatStats = { FF1Text.TextToBytes("Combat Stats", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes("", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes("Damage Dealt", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes(" ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101003AF6004919901"), FF1Text.TextToBytes("Damage Taken", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes(" ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101003B26004919900"), }; pages.Add(FF1Text.TextToStory(VictoryMessages[rng.Between(0, VictoryMessages.Count - 1)])); pages.Add(Blob.Concat(movementStats)); pages.Add(Blob.Concat(battleResults)); pages.Add(Blob.Concat(combatStats)); ThankYous.ForEach(page => pages.Add(FF1Text.TextToStory(page))); Blob storyText = PackageTextBlob(pages, 0xA800); System.Diagnostics.Debug.Assert(storyText.Length <= 0x0500, "Story text too large!"); Put(0x36800, storyText); Data[0x36E00] = (byte)(BridgeStory.Count); Data[0x36E01] = (byte)(pages.Count - 1); }
public void Randomize(Blob seed, Flags flags) { var rng = new MT19337(BitConverter.ToUInt32(seed, 0)); UpgradeToMMC3(); EasterEggs(); DynamicWindowColor(); if (flags.ModernBattlefield) { SetBattleUI(true); } // This has to be done before we shuffle spell levels. if (flags.SpellBugs) { FixSpellBugs(); } if (flags.Treasures) { ShuffleTreasures(rng, flags.EarlyCanoe, flags.EarlyOrdeals, flags.IncentivizeIceCave, flags.IncentivizeOrdeals); } if (flags.Shops) { ShuffleShops(rng, flags.EnemyStatusAttacks); } if (flags.MagicShops) { ShuffleMagicShops(rng); } if (flags.MagicLevels) { ShuffleMagicLevels(rng, flags.MagicPermissions); } if (flags.Rng) { ShuffleRng(rng); } if (flags.EnemyScripts) { ShuffleEnemyScripts(rng); } if (flags.EnemySkillsSpells) { ShuffleEnemySkillsSpells(rng); } if (flags.EnemyStatusAttacks) { ShuffleEnemyStatusAttacks(rng); } if (flags.Ordeals) { ShuffleOrdeals(rng); } if (flags.EarlyOrdeals) { EnableEarlyOrdeals(); } if (flags.EarlyRod) { EnableEarlyRod(); } if (flags.EarlyCanoe) { EnableEarlyCanoe(); } if (flags.EarlyBridge) { EnableEarlyBridge(); } if (flags.NoPartyShuffle) { DisablePartyShuffle(); } if (flags.SpeedHacks) { EnableSpeedHacks(); } if (flags.IdentifyTreasures) { EnableIdentifyTreasures(); } if (flags.Dash) { EnableDash(); } if (flags.BuyTen) { EnableBuyTen(); } if (flags.HouseMPRestoration) { FixHouse(); } if (flags.WeaponStats) { FixWeaponStats(); } if (flags.ChanceToRun) { FixChanceToRun(); } if (flags.EnemyStatusAttackBug) { FixEnemyStatusAttackBug(); } if (flags.FunEnemyNames) { FunEnemyNames(flags.TeamSteak); } var itemText = ReadText(ItemTextPointerOffset, ItemTextPointerBase, ItemTextPointerCount); itemText[99] = FF1Text.TextToBytes("Ribbon ", useDTE: false); ExpGoldBoost(flags.ExpBonus, flags.ExpMultiplier); ScalePrices(flags.PriceScaleFactor, flags.ExpMultiplier, itemText, rng); WriteText(itemText, ItemTextPointerOffset, ItemTextPointerBase, ItemTextOffset, UnusedGoldItems); if (flags.EnemyScaleFactor > 1) { ScaleEnemyStats(flags.EnemyScaleFactor, rng); } if (flags.ForcedPartyMembers > 0) { PartyRandomize(rng, flags.ForcedPartyMembers); } // We have to do "fun" stuff last because it alters the RNG state. RollCredits(rng); if (flags.PaletteSwap) { PaletteSwap(rng); } if (flags.TeamSteak) { TeamSteak(); } if (flags.Music != MusicShuffle.None) { ShuffleMusic(flags.Music, rng); } WriteSeedAndFlags(Version, seed.ToHex(), EncodeFlagsText(flags)); ExtraTrackingAndInitCode(); }
private void SetupBridgeCredits() { // Wallpaper over the JSR to the NASIR CRC to circumvent their neolithic DRM. Put(0x7CF34, Blob.FromHex("EAEAEA")); // Actual Credits. Each string[] is a page. Each "" skips a line, duh. // The lines have zero padding on all sides, and 16 usable characters in length. // Don't worry about the inefficiency of spaces as they are all trimmed and the // leading spaces are used to increment the PPU ptr precisely to save ROM space. List <string[]> texts = new List <string[]> { new [] { "", "", " Final Fantasy", "", "", " Randomizer", }, new [] { "", "Lead Development", "", " Entroper", " MeridianBC", " nitz", " Septimus", " tartopan", }, new [] { "", " Contributors", "", " artea", " drcatdoctor", " leggystarscream", " nic0lette", " splitpunched", }, new [] { "", "", " Programmed ", " By ", "", "E N T R O P E R " }, }; // Accumulate all our Credits pages before we set up the string pointer array. List <Blob> pages = new List <Blob>(); texts.ForEach(text => pages.Add(FF1Text.TextToCredits(text))); // Clobber the number of pages to render before we insert in the pointers. Data[0x37873] = (byte)pages.Count; Blob credits = PackageTextBlob(pages, 0xBB00); System.Diagnostics.Debug.Assert(credits.Length <= 0x0100, "Credits too large: " + credits.Length); Put(0x37B00, credits); }