예제 #1
0
        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);
        }
예제 #2
0
        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(" ", ""));
            }
        }
예제 #3
0
        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),
            }));
        }
예제 #4
0
        // 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));
        }
예제 #5
0
        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));
        }
예제 #6
0
        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));
        }
예제 #7
0
        //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);
        }
예제 #8
0
        };                                                                                          // 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);
            }
        }
예제 #9
0
        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);
        }
예제 #10
0
        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);
        }
예제 #11
0
        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);
            }
        }
예제 #12
0
        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);
                }
            }
        }
예제 #13
0
        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));
        }
예제 #14
0
        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));
        }
예제 #15
0
        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);
        }
예제 #16
0
        // 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));
            }
        }
예제 #17
0
        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);
        }
예제 #18
0
        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));
        }
예제 #19
0
        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"));
        }
예제 #20
0
        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
            }
        }
예제 #21
0
        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!" },
            });
        }
예제 #22
0
        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();
        }
예제 #23
0
        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);
        }
예제 #24
0
        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;
        }
예제 #25
0
        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"));
        }
예제 #26
0
        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);
        }
예제 #27
0
        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();
        }
예제 #28
0
        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);
        }