Exemple #1
0
        //offset lookups
        //0 - hit
        //1 - dmg
        //2 - crit
        //3 - weapon spell index
        //4 - elemental weaknesses
        //5 - type weaknesses
        //6 - weapon type sprite
        //7 - weapon sprite palette color

        public void RandomWeaponBonus(MT19337 rng, int min, int max, bool excludeMasa)
        {
            //get base stats
            Weapon currentWeapon;

            for (int i = 0; i < WeaponCount; i++)
            {
                if (i != 39 || !excludeMasa)
                {
                    currentWeapon = new Weapon(i, this);
                    int bonus = rng.Between(min, max);
                    if (bonus != 0)
                    {
                        //adjust stats
                        //clamp to 1 dmg min, 0 hit min, 50 hit maximum
                        currentWeapon.HitBonus = (byte)Math.Max(0, (int)(currentWeapon.HitBonus + (3 * bonus)));
                        currentWeapon.HitBonus = (byte)Math.Min(50, (int)(currentWeapon.HitBonus));
                        currentWeapon.Damage   = (byte)Math.Max(1, (int)currentWeapon.Damage + (2 * bonus));
                        currentWeapon.Crit     = (byte)Math.Max(1, (int)currentWeapon.Crit + (3 * 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 = currentWeapon.NameBytes[6] > 200 && currentWeapon.NameBytes[6] != 255 ? 5 : 6;
                        for (int j = 0; j < bonusBytes.Length - 1; j++)
                        {
                            currentWeapon.NameBytes[iconIndex - j] = bonusBytes[bonusBytes.Length - 2 - j];
                        }

                        currentWeapon.writeWeaponMemory(this);
                    }
                }
            }
        }
Exemple #2
0
        public void FunEnemyNames(bool teamSteak)
        {
            var enemyText = ReadText(EnemyTextPointerOffset, EnemyTextPointerBase, EnemyCount);

            enemyText[1]  = FF1Text.TextToBytes("GrUMP", useDTE: false);
            enemyText[2]  = FF1Text.TextToBytes("RURURU", useDTE: false);        // +2
            enemyText[3]  = FF1Text.TextToBytes("GrrrWOLF", useDTE: false);      // +2
            enemyText[28] = FF1Text.TextToBytes("GeORGE", useDTE: false);
            enemyText[30] = FF1Text.TextToBytes("R.SNEK", useDTE: false);        // +3
            enemyText[31] = FF1Text.TextToBytes("GrSNEK", useDTE: false);        // +1
            enemyText[32] = FF1Text.TextToBytes("SeaSNEK", useDTE: false);       // -1
            enemyText[40] = FF1Text.TextToBytes("iMAGE", useDTE: false);
            enemyText[56] = FF1Text.TextToBytes("EXPEDE", useDTE: false);        // +2
            enemyText[66] = FF1Text.TextToBytes("White D", useDTE: false);
            enemyText[72] = FF1Text.TextToBytes("MtlSLIME", useDTE: false);      // +3
            if (teamSteak)
            {
                enemyText[85] = FF1Text.TextToBytes("STEAK", useDTE: false);        // +1
                enemyText[86] = FF1Text.TextToBytes("T.BONE", useDTE: false);       // +1
            }
            enemyText[92]  = FF1Text.TextToBytes("NACHO", useDTE: false);           // -1
            enemyText[106] = FF1Text.TextToBytes("Green D", useDTE: false);         // +2
            enemyText[111] = FF1Text.TextToBytes("OKAYMAN", useDTE: false);         // +1

            // Moving IMP and GrIMP gives me another 10 bytes, for a total of 19 extra bytes, of which I'm using 16.
            var enemyTextPart1 = enemyText.Take(2).ToArray();
            var enemyTextPart2 = enemyText.Skip(2).ToArray();

            WriteText(enemyTextPart1, EnemyTextPointerOffset, EnemyTextPointerBase, 0x2CFEC);
            WriteText(enemyTextPart2, EnemyTextPointerOffset + 4, EnemyTextPointerBase, EnemyTextOffset);
        }
Exemple #3
0
        //sample function for creating new weapons
        public void ExpandWeapon()
        {
            Weapon flameChucks = new Weapon(0, FF1Text.TextToBytes("Flame"), WeaponIcon.CHUCK, 20, 26, 10, 0, (byte)Element.FIRE, 0, WeaponSprite.CHUCK, 0x25);

            flameChucks.setClassUsability((ushort)(EquipPermission.BlackBelt | EquipPermission.Master | EquipPermission.Ninja));
            flameChucks.writeWeaponMemory(this);
        }
        public void RandomArmorBonus(MT19337 rng, int min, int max, bool cleanNames)
        {
            //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);

                    // Adjusts blursed armor names to be more understandable
                    if (cleanNames)
                    {
                        if (currentArmor.Name[0..6] == "Copper")
Exemple #5
0
        public void FixSpellBugs()
        {
            Put(0x33A4E, Blob.FromHex("F017EA"));         // LOCK routine
            Put(0x3029C, Blob.FromHex("0E"));             // LOK2 spell effect
            Put(0x302F9, Blob.FromHex("18"));             // HEL2 effectivity

            // TMPR and SABR
            // Remove jump to PrepareEnemyMagAttack
            Put(0x334F4, Blob.FromHex("EAEAEA"));
            // Replace PrepareEnemyMagAttack with loading defender strength and hit%
            Put(0x33730, Blob.FromHex("A006B1908D7268A009B1908D8268A005B1908D846860"));
            // Replace end of PreparePlayerMagAttack with saving defender strength and hit%
            Put(0x3369E, Blob.FromHex("60A007AD85689190A009AD82689190A005AD8468919060"));
            // Call new loading code from BtlMag_LoadPlayerDefenderStats
            Put(0x33661, Blob.FromHex("2030B7EAEAEAEA"));
            // Call new saving code from BtlMag_SavePlayerDefenderStats
            Put(0x337C5, Blob.FromHex("209FB6EAEAEAEA"));
            // SABR's hit% bonus
            Put(0x30390, Blob.FromHex("0A"));

            TameExitAndWarpBoss();

            // Cure 4 fix, see 0E_AF7C_Cure4.asm
            Put(0x3AF80, Blob.FromHex("36"));
            Put(0x3AF8F, Blob.FromHex("2AC902F026A564C900F0062061B54CACAFBD0D619D0B61BD0C619D0A612000B4A665DE00632013B64C97AE2026DB4C7CAF0000000000000000000000002000B4A92B202BB9A9008D64004C7CAF"));
            Put(0x3AF13, Blob.FromHex("CC"));             // update address for Cure4 routine

            // Better Slow battle message
            Put(0x6C11F, FF1Text.TextToBytes("Attack rate down", false, FF1Text.Delimiter.Null));
        }
Exemple #6
0
        private ushort ChangeMenuText(ushort p, string text)
        {
            var textblob = FF1Text.TextToBytes(text);

            rom.PutInBank(0x0E, p, textblob);

            return((ushort)(p + textblob.Length));
        }
Exemple #7
0
        public void EnableShardHunt(MT19337 rng, int goal, 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;
            }

            string shardName = ShardNames.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 check for shards rather than ORBs.
            Put(0x39502, Blob.FromHex($"AD3560C9{hexCount}300CA0CA209690E67DE67DA51160A51260"));
            Put(0x7CDB3, Blob.FromHex("08CE"));

            // 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!"));
        }
Exemple #8
0
        private void AddLutePlateToChaosFloor(List <Map> maps)
        {
            // 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);

            // 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
        }
Exemple #9
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, bool VanillaStartingGold, 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;
            }
            prices[(int)Item.WhiteShirt] = (ushort)(questItemPrice / 2);
            prices[(int)Item.BlackShirt] = (ushort)(questItemPrice / 2);
            prices[(int)Item.Ribbon]     = 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);
                }
            }
            if (!VanillaStartingGold)
            {
                var startingGold = BitConverter.ToUInt16(Get(StartingGoldOffset, 2), 0);

                startingGold = (ushort)Min(Scale(startingGold / multiplier, scale, 1, rng), 0xFFFF);

                Put(StartingGoldOffset, BitConverter.GetBytes(startingGold));
            }
        }
Exemple #10
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[16 - flagBytes.Length];

            for (int i = 0; i < padding.Length; i++)
            {
                padding[i] = 0xFF;
            }

            Put(CopyrightOffset1, seedBytes);
            Put(CopyrightOffset2, padding + flagBytes);
        }
Exemple #11
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));
        }
Exemple #12
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));
        }
Exemple #13
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);
        }
Exemple #14
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);
            }
        }
Exemple #15
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);
        }
Exemple #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(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);
            }
            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 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);
            }
        }
Exemple #18
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);
                }
            }
        }
        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));
        }
Exemple #20
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);
        }
Exemple #21
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();
        }
Exemple #22
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);
        }
Exemple #23
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"));
        }
Exemple #24
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.
            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"));

            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.
            talkroutines.Replace(newTalkRoutines.Talk_BlackOrb, Blob.FromHex($"AD3560C9{goal:X2}300CA0CA209690E67DE67DA57160A57260"));
            Put(0x7CDB3, Blob.FromHex("08CE"));

            // 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!" },
            });
        }
Exemple #25
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);
        }
Exemple #26
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));
        }
Exemple #27
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"));
        }
Exemple #28
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));
            }
        }
Exemple #29
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();
        }
        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;
        }