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!")); }
public void LoadIntro(Stream stream) { var introText = new List <string>(); using (StreamReader reader = new StreamReader(stream)) { while (true) { var line = reader.ReadLine(); if (line == null) { break; } introText.Add(line.TrimEnd()); } } Blob intro = FF1Text.TextToStory(introText.ToArray()); Console.WriteLine(intro.Length); System.Diagnostics.Debug.Assert(intro.Length <= 208); Put(0x37F20, intro); }
public void 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!" }, }); }
private void SetupStoryPages(MT19337 rng) { // Setup DrawComplexString hijack for a particular escape sequence. See Credits.asm. Put(0x7DFA8, Blob.FromHex("A000A200B13EE63ED002E63F9510E8E003D0F18C1D608C1E60B111991C60C8C410D0F64C45DE")); List <Blob> pages = new List <Blob>(); BridgeStory.ForEach(story => pages.Add(FF1Text.TextToStory(story))); // An unused escape code from DrawComplexString is overridden allowing the following: // 1010 XX ADDR, Where 1010 enters the escape sequence, the next byte the the size of // the integer to print, and the next two bytes as a pointer to it. It is then copied // over the gold value, so 04 will follow as the next escape sequence to print gold. /* * Tracked Stats: * Steps: $60A0 (24-bit) * Hard Resets: $64A3 (16-bit) * Soft Resets: $64A5 (16-bit) * Battles: $60A7 (16-bit) * Ambushes: $60A9 (16-bit) * Strike Firsts: $60AB (16-bit) * Close Calls...: $60AD (16-bit) * Damage Dealt: $60AF (24-bit) * Damage Taken: $60B2 (24-bit) * Perished: $64B5 (8-bit) * "Nothing Here": $60B6 (8-bit) * Chests Opened: $60B7 (8-bit) * Can't Hold: $60B9 (8-bit) */ Blob[] movementStats = { FF1Text.TextToBytes("Movement Stats", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes("", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes("Steps ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101003A0600405"), FF1Text.TextToBytes("Resets ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101002A5640405"), FF1Text.TextToBytes("Power off ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101002A3640405"), FF1Text.TextToBytes("Nthng Here", true, FF1Text.Delimiter.Empty),Blob.FromHex("101001B6600405"), FF1Text.TextToBytes("Can't Hold", true, FF1Text.Delimiter.Empty),Blob.FromHex("101001B9600405"), FF1Text.TextToBytes("Chests Opened", true, FF1Text.Delimiter.Line), Blob.FromHex("FFFFFF101001B76004"), FF1Text.TextToBytes(" of 241", true, FF1Text.Delimiter.Null), }; Blob[] battleResults = { FF1Text.TextToBytes("Battle Results", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes("", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes("Battles ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101002A7600405"), FF1Text.TextToBytes("Ambushes ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101002A9600405"), FF1Text.TextToBytes("Struck 1st", true, FF1Text.Delimiter.Empty),Blob.FromHex("101002AB600405"), FF1Text.TextToBytes("Close call", true, FF1Text.Delimiter.Empty),Blob.FromHex("101002AD600405"), FF1Text.TextToBytes("Perished ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101001B5640400"), }; Blob[] combatStats = { FF1Text.TextToBytes("Combat Stats", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes("", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes("Damage Dealt", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes(" ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101003AF6004919901"), FF1Text.TextToBytes("Damage Taken", true, FF1Text.Delimiter.Line), FF1Text.TextToBytes(" ", true, FF1Text.Delimiter.Empty),Blob.FromHex("101003B26004919900"), }; pages.Add(FF1Text.TextToStory(VictoryMessages[rng.Between(0, VictoryMessages.Count - 1)])); pages.Add(Blob.Concat(movementStats)); pages.Add(Blob.Concat(battleResults)); pages.Add(Blob.Concat(combatStats)); ThankYous.ForEach(page => pages.Add(FF1Text.TextToStory(page))); Blob storyText = PackageTextBlob(pages, 0xA800); System.Diagnostics.Debug.Assert(storyText.Length <= 0x0500, "Story text too large!"); Put(0x36800, storyText); Data[0x36E00] = (byte)(BridgeStory.Count); Data[0x36E01] = (byte)(pages.Count - 1); }
public void 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); }