Ejemplo n.º 1
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!"));
        }
Ejemplo n.º 2
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);
        }
Ejemplo n.º 3
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!" },
            });
        }
Ejemplo n.º 4
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);
        }
Ejemplo n.º 5
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);
        }