diff --git a/FF1Blazorizer/Tabs/AdjustmentsTab.razor b/FF1Blazorizer/Tabs/AdjustmentsTab.razor
index cfde739d0..4e1f3a266 100644
--- a/FF1Blazorizer/Tabs/AdjustmentsTab.razor
+++ b/FF1Blazorizer/Tabs/AdjustmentsTab.razor
@@ -39,6 +39,7 @@
Conveniences
+
In-Game Tracker
No Party Shuffle
Speed Hacks
Faster Walking Speed
diff --git a/FF1Blazorizer/Tabs/QoLTab.razor b/FF1Blazorizer/Tabs/QoLTab.razor
index 0e3843943..e543edbc6 100644
--- a/FF1Blazorizer/Tabs/QoLTab.razor
+++ b/FF1Blazorizer/Tabs/QoLTab.razor
@@ -16,6 +16,7 @@
Uninterrupted Music
Lock Respond Rate
Respond Rate:
+
Overlay Orb Letters
Accessible Spell Names
Cleaner Blursed Names
Shop Information Icons
@@ -24,6 +25,7 @@
Quick Controller 2 Reset
Remove Unwanted Features:
+
Legacy Shard Display
Renounce Autosort
Renounce Chest Info
Renounce Can't Hold Red
diff --git a/FF1Lib/BankUsageRegister.md b/FF1Lib/BankUsageRegister.md
index 795b6d5bb..bb1d57b2f 100644
--- a/FF1Lib/BankUsageRegister.md
+++ b/FF1Lib/BankUsageRegister.md
@@ -181,7 +181,7 @@ ROM: 38000-3BFFF
0E 8DE4-8E10 Fix character stat rendering
OE 9079-90D9 Restore npc manip routines
0E 9273-92A3 Restore map object
-0E 94E0-94F0 Moved Magic Menu Finalize Ouput
+0E 94E0-94F0 Moved Magic Menu Finalize Output
0E 96B0-96F8 Item Jump Table
0E 9C54-9C7D Exit Boss
0E A360-A3AA Magic Shop Menu QoS
@@ -198,8 +198,7 @@ ROM: 3C000-3FFFF
0F 8D00-8D53 Autosort
0F 9100-91AC Progressive Scaling
0F B000-B300 Expanded Teleporter tables
-0F F100-F200 Encounter RNG Table
-0F FCF1-FDF1 Battle RNG Table
+
ROM: 40000-43FFF
10 8000-BFFF Dialogues (whole bank is reserved)
@@ -304,7 +303,7 @@ ROM: 74000-77FFF
ROM: 78000-7BFFF
1E 8000-85B0 Moving routines from bank 0E to 1E (PartyGen and menu stuff)
1E 806B-8070 Battlestep to SRAM Jump
-1E 85B0-85C1 Parry Permissions table
+1E 85B0-85C1 Party Permissions table
1E 8680-86A7 New Icons routine
1E 86E0-B9F1 Encounter Table True PRNG
1E 8800-8933 Class Info Window
diff --git a/FF1Lib/BlackOrb.cs b/FF1Lib/BlackOrb.cs
index 0309943cb..335f7e12c 100644
--- a/FF1Lib/BlackOrb.cs
+++ b/FF1Lib/BlackOrb.cs
@@ -30,7 +30,7 @@ private void BlackOrbMode(TalkRoutines talkRoutines, DialogueData dialogues, Fla
{
if (((bool)flags.Treasures) && flags.ShardHunt)
{
- EnableShardHunt(rng, TalkRoutines, Dialogues, flags.ShardCount, pref.randomShardNames, flags.SpookyFlag, funRng);
+ EnableShardHunt(rng, TalkRoutines, Dialogues, flags.ShardCount, pref.randomShardNames, flags.SpookyFlag, pref.LegacyShardDisplay, funRng);
}
if (!flags.ShardHunt && (flags.GameMode != GameModes.DeepDungeon))
@@ -46,14 +46,27 @@ private void BlackOrbMode(TalkRoutines talkRoutines, DialogueData dialogues, Fla
"JEWEL", "PIECE", "CHUNK", "PRISM", "STONE", "SLICE", "WEDGE", "BIGGS", "SLIVR", "ORBLT", "ESPER", "FORCE",
};
- public void addShardIcon(int bank, int address)
+ public async Task AddShardGraphics(int bank, int address, bool legacyShardDisplay, bool orbGraphicsInResourcePack)
{
- // Replace the upper two tiles of the unlit orb with an empty and found shard.
- // These are at tile address $76 and $77 respectively.
- PutInBank(bank, address, Blob.FromHex("001C22414141221CFFE3DDBEBEBEDDE3001C3E7F7F7F3E1CFFFFE3CFDFDFFFFF"));
+ Console.WriteLine("Entering AddShardGraphics");
+ if (legacyShardDisplay)
+ {
+ // Replace the upper two tiles of the unlit orb with an empty and found shard.
+ // These are at tile address $76 and $77 respectively.
+ PutInBank(bank, address + 0x760, Blob.FromHex("001C22414141221CFFE3DDBEBEBEDDE3001C3E7F7F7F3E1CFFFFE3CFDFDFFFFF"));
+ }
+ else if (!orbGraphicsInResourcePack)
+ {
+ Console.WriteLine("AddShardGraphics Step 2");
+ var assembly = System.Reflection.Assembly.GetExecutingAssembly();
+ var shardGraphicsFile = assembly.GetManifestResourceNames()
+ .Single(str => str.EndsWith("orbs_shards.png"));
+ var shardGraphicsStream = assembly.GetManifestResourceStream(shardGraphicsFile);
+ await SetCustomOrbGraphics(shardGraphicsStream, bank, address + 0x640);
+ }
}
- public void EnableShardHunt(MT19337 rng, TalkRoutines talkroutines, DialogueData dialogues, ShardCount count, bool RandomShardNames, bool skipFlavorText, MT19337 funRngSeed)
+ public void EnableShardHunt(MT19337 rng, TalkRoutines talkroutines, DialogueData dialogues, ShardCount count, bool RandomShardNames, bool skipFlavorText, bool LegacyShardDisplay, MT19337 funRngSeed)
{
int goal = 16;
switch (count) {
@@ -76,12 +89,24 @@ public void EnableShardHunt(MT19337 rng, TalkRoutines talkroutines, DialogueData
ItemsText[(int)Item.Shard] = shardName;
//addShardIcon(0xD, 0xB760);
+ if (LegacyShardDisplay)
+ {
+ int ppu = 0x2043;
+ ppu = ppu + (goal <= 24 ? 0x20 : 0x00);
- 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"));
+ }
+ else
+ {
+ byte[] ShardGoal = [(byte)goal, (byte)(goal/10 + 0x80), (byte)(goal%10 + 0x80)];
+ // New shard display
+ PutInBank(0x0E,0xBAA2,Blob.FromHex("01010A09"));
+ PutInBank(0x0E,0xB8A5,Blob.FromHex("A9A448A90248A91B4C03FEEAEAEAEAEAEAEA"));
+ PutInBank(0x1B,0xA400,ShardGoal);
+ PutInBank(0x1B,0xA403,Blob.FromHex("AD0220A9238D0620A9C98D0620A5178D0720A200A9639D106EE8A000AD3560C90AB00CA9FF9D106EE8AD35604C43A4C838E90AC90AB0F8489809809D106EE86809809D106EE8A97A9D106EE8AD01A49D106EE8AD02A49D106EA903853AA902853B20ABDCA200A9068510BD106EAC0220A4558C0620A4548C06208D0720E8E654C610D0E6A93FAC0220A0238C0620A0C08C06208D0720AD3560CD00A49012A9CFAC0220A0238C0620A0C18C06208D0720A90E4C03FE"));
- // 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);
diff --git a/FF1Lib/Classes/Lockpicking.cs b/FF1Lib/Classes/Lockpicking.cs
index 611e85b01..e5551e800 100644
--- a/FF1Lib/Classes/Lockpicking.cs
+++ b/FF1Lib/Classes/Lockpicking.cs
@@ -6,7 +6,8 @@ public void EnableLockpicking()
{
//put in the base hack: see 1B_9300_LockpickDoors.asm for more info
PutInBank(0x1F, 0xCE53, Blob.FromHex("AA9848A91B2003FE200093C00168A88AB0ED"));
- PutInBank(0x1B, 0x9300, Blob.FromHex("8A4A2903C902D059A2008645AE2560D050AE2661E009900BAE0061E001F042E007F03EAE6661E009900BAE4061E001F030E007F02CAEA661E009900BAE8061E001F01EE007F01AAEE661E009900BAEC061E001F00CE007F008A001AAA90E4C03FEA000AAA90E4C03FE"));
+ PutInBank(0x1B,0x9300,Blob.FromHex("8A4A29038D036EC902D026A2008645AE2560D01DA2008E046E204093A240204093A280204093A2C02040934901A84C3393A000AE036EA90E4C03FE00000E0107BD2661CD3D93900DBD0061CD3E93F009CD3F93F004A900F002A901A80D046E8D046E60"));
+ // PutInBank(0x1B, 0x9300, Blob.FromHex("8A4A2903C902D059A2008645AE2560D050AE2661E009900BAE0061E001F042E007F03EAE6661E009900BAE4061E001F030E007F02CAEA661E009900BAE8061E001F01EE007F01AAEE661E009900BAEC061E001F00CE007F008A001AAA90E4C03FEA000AAA90E4C03FE"));
}
public void SetLockpickingLevel(int requiredLevel)
@@ -14,11 +15,12 @@ public void SetLockpickingLevel(int requiredLevel)
//overlay the level requirement
if (requiredLevel > 0 && requiredLevel <= 50)
{
- //level is stored zero based
- PutInBank(0x1B, 0x9315, new byte[] { (byte)(requiredLevel - 1) });
- PutInBank(0x1B, 0x9327, new byte[] { (byte)(requiredLevel - 1) });
- PutInBank(0x1B, 0x9339, new byte[] { (byte)(requiredLevel - 1) });
- PutInBank(0x1B, 0x934B, new byte[] { (byte)(requiredLevel - 1) });
+ // //level is stored zero based
+ // PutInBank(0x1B, 0x9315, new byte[] { (byte)(requiredLevel - 1) });
+ // PutInBank(0x1B, 0x9327, new byte[] { (byte)(requiredLevel - 1) });
+ // PutInBank(0x1B, 0x9339, new byte[] { (byte)(requiredLevel - 1) });
+ // PutInBank(0x1B, 0x934B, new byte[] { (byte)(requiredLevel - 1) });
+ PutInBank(0x1B, 0x933D, (byte)(requiredLevel - 1));
}
}
@@ -26,15 +28,17 @@ public void SetLockpickingClass(int requiredClass)
{
if (requiredClass >= 0 && requiredClass < 6)
{
- PutInBank(0x1B, 0x9315 + 7, new byte[] { (byte)(requiredClass) });
- PutInBank(0x1B, 0x9327 + 7, new byte[] { (byte)(requiredClass) });
- PutInBank(0x1B, 0x9339 + 7, new byte[] { (byte)(requiredClass) });
- PutInBank(0x1B, 0x934B + 7, new byte[] { (byte)(requiredClass) });
+ // PutInBank(0x1B, 0x9315 + 7, new byte[] { (byte)(requiredClass) });
+ // PutInBank(0x1B, 0x9327 + 7, new byte[] { (byte)(requiredClass) });
+ // PutInBank(0x1B, 0x9339 + 7, new byte[] { (byte)(requiredClass) });
+ // PutInBank(0x1B, 0x934B + 7, new byte[] { (byte)(requiredClass) });
- PutInBank(0x1B, 0x9315 + 11, new byte[] { (byte)(requiredClass + 6) });
- PutInBank(0x1B, 0x9327 + 11, new byte[] { (byte)(requiredClass + 6) });
- PutInBank(0x1B, 0x9339 + 11, new byte[] { (byte)(requiredClass + 6) });
- PutInBank(0x1B, 0x934B + 11, new byte[] { (byte)(requiredClass + 6) });
+ // PutInBank(0x1B, 0x9315 + 11, new byte[] { (byte)(requiredClass + 6) });
+ // PutInBank(0x1B, 0x9327 + 11, new byte[] { (byte)(requiredClass + 6) });
+ // PutInBank(0x1B, 0x9339 + 11, new byte[] { (byte)(requiredClass + 6) });
+ // PutInBank(0x1B, 0x934B + 11, new byte[] { (byte)(requiredClass + 6) });
+ PutInBank(0x1B, 0x933E, (byte)requiredClass);
+ PutInBank(0x1B, 0x933F, (byte)(requiredClass + 6));
}
}
}
diff --git a/FF1Lib/FF1Lib.csproj b/FF1Lib/FF1Lib.csproj
index 42492201d..123c4ede8 100644
--- a/FF1Lib/FF1Lib.csproj
+++ b/FF1Lib/FF1Lib.csproj
@@ -65,6 +65,8 @@
+
+
diff --git a/FF1Lib/FF1Text.cs b/FF1Lib/FF1Text.cs
index 364648120..45c9624ed 100644
--- a/FF1Lib/FF1Text.cs
+++ b/FF1Lib/FF1Text.cs
@@ -561,7 +561,7 @@ public static Blob TextToCopyrightLine(string text)
}
// Loads custom icons
- public static void AddNewIcons(FF1Rom rom, Flags flags)
+ public static async Task AddNewIcons(FF1Rom rom, Flags flags, Preferences preferences, ResourcePackSettings resourcePackSettings)
{
// Icons have a 0x10 Control code indicating the next byte is pulled from the 0x00-0x7F tile reference instead of the 0x80-0xFF like regular fonts
@@ -578,8 +578,14 @@ public static void AddNewIcons(FF1Rom rom, Flags flags)
var tileset = rom.GetFromBank(0x0D, 0xB600, 0x0200); // Get font tileset
rom.PutInBank(0x12, 0x8800 + 0x600, tileset); // Put it in bank 12
- if (flags.ShardHunt)
- rom.addShardIcon(0x12, 0x8800 + 0x760); // If we're shard hunt, add the shard to tiles 0x76 and 0x77 because we missed em
+ if ((bool)flags.Treasures && flags.ShardHunt)
+ await rom.AddShardGraphics(0x12, 0x8800, preferences.LegacyShardDisplay, resourcePackSettings.OrbGraphics); // If we're in shard hunt, add the shard graphics.
+
+ if (flags.Tracker)
+ rom.AddTrackerIcons(flags);
+
+ if (preferences.OrbLetterOverlays)
+ rom.AddOrbLetterOverlays();
// Change where the ORBS are loaded from and to so we can piggyback our icons - Now we load 8 lines from Bank 12 into 0000 of the PPU
rom.PutInBank(0x1F, 0xEA9F, Blob.FromHex("2002EAA9122003FEA208A9008510A9888511A900"));
diff --git a/FF1Lib/Flags/Flags.cs b/FF1Lib/Flags/Flags.cs
index 0b46a7524..16db41ad6 100644
--- a/FF1Lib/Flags/Flags.cs
+++ b/FF1Lib/Flags/Flags.cs
@@ -434,6 +434,7 @@ public partial class Flags : IIncentiveFlags, IScaleFlags, IVictoryConditionFlag
public bool NPCSwatter { get; set; } = false;
public bool BattleMagicMenuWrapAround { get; set; } = false;
public bool MagicMenuSpellReordering { get; set; } = true;
+ public bool Tracker { get; set; } = true;
public bool InventoryAutosort { get; set; } = true;
public bool RepeatedPotionUse { get; set; } = true;
public bool AutoRetargeting { get; set; } = true;
diff --git a/FF1Lib/Flags/FlagsCompute.cs b/FF1Lib/Flags/FlagsCompute.cs
index ab5388ff7..1dc1821de 100644
--- a/FF1Lib/Flags/FlagsCompute.cs
+++ b/FF1Lib/Flags/FlagsCompute.cs
@@ -273,5 +273,8 @@ public LoosePlacementMode LoosePlacementMode
public bool IsAnythingLoose => (IncentivizedItemCountMax > IncentivizedLocationCountMin) || IncentivizeMainItems != true || IncentivizeFetchItems != true || (IncentivizeAirship != true && FreeAirship != true && NoFloater != true) || (IncentivizeCanoeItem != true && FreeCanoe != true) || (IncentivizeShipAndCanal != true && (FreeShip != true || FreeCanal != true)) || (IncentivizeBridgeItem != true && FreeBridge != true) || (IncentivizeTail != true && FreeTail != true && NoTail != true);
+
+ // changed in the randomizer, not in UI
+ public bool OrbGraphicsInResourcePack { get; set; } = false;
}
}
diff --git a/FF1Lib/Flags/Preferences.cs b/FF1Lib/Flags/Preferences.cs
index b7e1e9073..4bb27f305 100644
--- a/FF1Lib/Flags/Preferences.cs
+++ b/FF1Lib/Flags/Preferences.cs
@@ -33,10 +33,12 @@ public class Preferences
public bool randomShardNames { get; set; } = false;
public Fate HurrayDwarfFate { get; set; } = Fate.Spare;
public bool FunFountainText { get; set; } = false;
+ public bool LegacyShardDisplay { get; set; } = false;
public bool RenounceAutosort { get; set; } = false;
public bool RenounceChestInfo { get; set; } = false;
public bool RenounceCantHoldRed { get; set; } = false;
public bool AccessibleSpellNames { get; set; } = false;
+ public bool OrbLetterOverlays { get; set; } = false;
public bool CleanBlursedEquipmentNames { get; set; } = false;
public bool ShopInfoIcons { get; set; } = false;
public bool MagicShopMenuChange { get; set; } = false;
@@ -55,4 +57,10 @@ public class Preferences
public bool NegativeHarmony { get; set; } = false;
public bool MapDerp { get; set; } = false;
}
+
+ public class ResourcePackSettings
+ {
+
+ public bool OrbGraphics{ get; set; } = false;
+ }
}
diff --git a/FF1Lib/GlobalImprovements.cs b/FF1Lib/GlobalImprovements.cs
index 76abbbe2c..558344eb3 100644
--- a/FF1Lib/GlobalImprovements.cs
+++ b/FF1Lib/GlobalImprovements.cs
@@ -125,16 +125,21 @@ public void EnableBuyQuantity(bool archipelagoenabled)
Put(0x39E00, Blob.FromHex("ad0a0385104c668eae0c03bd2060186d0a03c9649001609d206060a903203baaa9018d0a03a520290f856120909f2032aa2043a7a525d056a524d05aa520290fc561f0ed8561c900f0e7c904f02fc908f01ac901f00ace0a03d0d0ee0a03d0cbee0a03c964d0c4ce0a03d0bfad0a0318690a8d0a03c96490b2a96310f5ad0a0338e90af0021002a9018d0a03109d38a90085248525601890f6"));
Put(0x39E99, Blob.FromHex("a90e205baaa5620a0a0a186916aabd00038d0c0320b9ecae0a03a9008d0b038d0e038d0f0318ad0b0365108d0b03ad0e0365118d0e03ad0f0369008d0f03b005caf00dd0e1a9ff8d0b038d0e038d0f03ad0f038512ad0e038511ad0b03851020429f2032aa60"));
Put(0x39EFF, Blob.FromHex("ad1e60cd0f03f0049016b016ad1d60cd0e03f004900ab00aad1c60cd0b03b00238601860ad1c6038ed0b038d1c60ad1d60ed0e038d1d60ad1e60ed0f038d1e604cefa74c8e8e"));
- Put(0x3A494, Blob.FromHex("201b9eb0e820999e20c2a8b0e0a562d0dc20ff9e9008a910205baa4c81a420089e9008a90c205baa4c81a420239fa913205baa4c81a4eaeaea"));
+ Put(0x3A494, Blob.FromHex("201b9eb0e820999e20c2a8b0e0a562d0dc20ff9e9008a910205baa4c81a420489f9008a90c205baa4c81a420239fa913205baa4c81a4eaeaea"));
PutInBank(0x0E, 0x9F90, Blob.FromHex("A5620A0A0A186916A8BE0003BC2060AD0A038CEF6A186DEF6AE963300EA963EDEF6AF0021002A9018D0A034C009E"));
-
- if (archipelagoenabled)
- {
- //Replace NewCheckforSpace with patch in 0E_9F48_ItemShopCheckForSpace.asm
- PutInBank(0x0E, 0xA4B2, Blob.FromHex("20489F"));
- PutInBank(0x0E, 0x9F48, Blob.FromHex("AE0C03E016900CBD2060186D0A03C964900D60A2FFBD006209029D006218609D20601860"));
- }
+
+
+
+ //Replace NewCheckforSpace with patch in 0E_9F48_ItemShopCheckForSpace.asm
+ /// in AP we want this asm routine to return before adding the item-shop key item to inventory, and let the client do that.
+ /// in normal seeds, we need to give the item instead, so we NOP out a return.
+
+ byte APReturnByte = archipelagoenabled? (byte)0x60 : (byte)0xEA;
+ // This is no longer needed -- added to the Blob string above. Do a search for 20489f to find it
+ //PutInBank(0x0E, 0xA4B2, Blob.FromHex("20489F"));
+ //PutInBank(0x0E, 0x9F48, Blob.FromHex($"AE0C03E016900CBD2060186D0A03C964900D60A0FFB90062090299006218{APReturnByte:X2}9D20601860"));
+ PutInBank(0x0E,0x9F48,Blob.FromHex($"AE0C03E016B00CA0FFB90062090299006218{APReturnByte:X2}BD2060186D0A03C9649001609D20601860"));
}
public void ChangeUnrunnableRunToWait()
diff --git a/FF1Lib/Hacks.cs b/FF1Lib/Hacks.cs
index 1e84a144f..223d1ce08 100644
--- a/FF1Lib/Hacks.cs
+++ b/FF1Lib/Hacks.cs
@@ -52,11 +52,12 @@ private void MiscHacks(Flags flags, MT19337 rng)
EnableCanalBridge((bool)flags.MapCanalBridge);
}
- public void DeepDungeonFloorIndicator()
+ public void DeepDungeonFloorIndicator(Flags flags, Preferences preferences)
{
+ byte FloorIndicatorDestY = (flags.ShardHunt && !preferences.LegacyShardDisplay) ? (byte)0x08 : (byte)0x02;
// Add Current Floor indicator above orbs, see 0E_9850_DrawOrbFloor.asm
PutInBank(0x0E, 0xB83D, Blob.FromHex("205098"));
- PutInBank(0x0E, 0x9850, Blob.FromHex("2078B8A548C93CB018C908900638E9074C7398A8B9A698855AB9AE98855B4C8398A900851020668EA000B13E855AC8B13E855BA97A855CA985855DA982855EA900855FA95A853EA900853FA902853BA904853A4C44B98C998E968C909895B2B5AFA8B5A4B1A8"));
+ PutInBank(0x0E, 0x9850, Blob.FromHex($"2078B8A548C93CB018C908900638E9074C7398A8B9A698855AB9AE98855B4C8398A900851020668EA000B13E855AC8B13E855BA97A855CA985855DA982855EA900855FA95A853EA900853FA9{FloorIndicatorDestY:X2}853BA904853A4C44B98C998E968C909895B2B5AFA8B5A4B1A8"));
// Extend Orb Box
PutInBank(0x0E, 0xBAA2, Blob.FromHex("02010809"));
}
diff --git a/FF1Lib/Randomize.cs b/FF1Lib/Randomize.cs
index 9ffa5a7e3..5ea61294d 100644
--- a/FF1Lib/Randomize.cs
+++ b/FF1Lib/Randomize.cs
@@ -53,6 +53,8 @@ public partial class FF1Rom : NesRom
private Blob SavedHash;
+ public ResourcePackSettings ResourcePackSettings;
+
public void LoadSharedDataTables()
{
ItemsText = new ItemNames(this);
@@ -107,7 +109,8 @@ public async Task
Randomize(Blob seed, Flags flags, Preferences preferenc
// data is read
// resource pack goes after map derp; Later could make this more efficient.
await this.LoadFunTiles(preferences, new MT19337(funRng.Next()));
- await this.LoadResourcePackPreROM(flags.ResourcePack, preferences);
+ ResourcePackSettings = new();
+ await this.LoadResourcePackPreROM(flags.ResourcePack, flags, preferences,ResourcePackSettings);
// Load Initial Data
@@ -167,7 +170,7 @@ public async Task Randomize(Blob seed, Flags flags, Preferences preferenc
Dialogues.TransferDialogues();
// Apply general fixes and hacks
- FF1Text.AddNewIcons(this, flags);
+ await FF1Text.AddNewIcons(this, flags, preferences, ResourcePackSettings);
Music.ShuffleMusic(this, preferences, new MT19337(funRng.Next()));
NewMusic = new NewMusic(this);
Bugfixes(flags);
@@ -188,7 +191,7 @@ public async Task Randomize(Blob seed, Flags flags, Preferences preferenc
await this.Progress("Generating Deep Dungeon's Floors...", 2);
DeepDungeon.Generate(rng, Overworld, EncounterRates, ZoneFormations, Dialogues);
- DeepDungeonFloorIndicator();
+ DeepDungeonFloorIndicator(flags,preferences);
warmMechFloor = (MapIndex)DeepDungeon.WarMechFloor;
await this.Progress("Generating Deep Dungeon's Floors... Done!");
@@ -411,7 +414,7 @@ public async Task Randomize(Blob seed, Flags flags, Preferences preferenc
RollCredits(rng);
-
+ InGameTracker(flags,unmodifiedFlags);
StatsTrackingHacks(flags, preferences);
if ((bool)flags.IsShipFree || flags.Archipelago) Overworld.SetShipLocation(255);
if (flags.TournamentSafe || preferences.CropScreen) ActivateCropScreen();
diff --git a/FF1Lib/ResourcePack.cs b/FF1Lib/ResourcePack.cs
index 145c43d69..55ac6ccb4 100644
--- a/FF1Lib/ResourcePack.cs
+++ b/FF1Lib/ResourcePack.cs
@@ -68,13 +68,14 @@ await SetCustomMapGraphics(maptileStream, 255, 4,
}
}
+
// split Resource Pack loading into two task sets: those which should be done pre ROM expansion
// and those which should be done after. This is a little slippery, but has mostly
// to do with the order in which things are done in the randomizer itself.
// In the future, there may need to be a third set, which applies changes after
// randomization.
- async Task LoadResourcePackPreROM(string resourcepack, Preferences preferences)
+ async Task LoadResourcePackPreROM(string resourcepack, Flags flags, Preferences preferences, ResourcePackSettings settings)
{
if (resourcepack == null)
{
@@ -156,6 +157,16 @@ await SetCustomMapGraphics(s, 128, 4,
}
}
+ var orbs = resourcePackArchive.GetEntry("orbs.png");
+ if (orbs != null)
+ {
+ using (var s = orbs.Open())
+ {
+ await SetCustomOrbGraphics(s, 0x0D, 0xB640);
+ }
+ settings.OrbGraphics = true;
+ }
+
}
@@ -244,6 +255,8 @@ async Task LoadResourcePackPostROM(string resourcepack, DialogueData dialogues,
}
}
+
+
var enemysprites = resourcePackArchive.GetEntry("enemies.png");
if (enemysprites != null)
{
diff --git a/FF1Lib/Sprites/CharacterSprites.cs b/FF1Lib/Sprites/CharacterSprites.cs
index f9fe0dd53..e2bfe45d2 100644
--- a/FF1Lib/Sprites/CharacterSprites.cs
+++ b/FF1Lib/Sprites/CharacterSprites.cs
@@ -25,6 +25,8 @@ public partial class FF1Rom : NesRom
const int MAPMANGRAPHIC_OFFSET = 0x9000;
const int VEHICLEGRAPHIC_OFFSET = 0x9D00;
+ const int ORBGRAPHIC_OFFSET = 0x37640; //0x37640 before rom expansion
+
const int NPCGRAPHIC_OFFSET = 0xA200;
const int MAPMAN_DOWN = 0;
@@ -75,6 +77,67 @@ bool makeMapmanPalette(List colors, Rgba32[] NESpalette,
return true;
}
+ bool makeLitOrbPalette(List colors, Rgba32[] NESpalette,
+ out List pal,
+ out Dictionary toIndex)
+ {
+ pal = new List();
+ toIndex = new Dictionary();
+ Rgba32 black = new Rgba32(0x00,0x00,0x00);
+
+ colors = OrderByLightness(colors);
+
+
+
+ colors.Reverse();
+ if (colors.Last() != black)
+ {
+ colors.Add(black);
+ }
+ for (int i = 0; i < colors.Count; i++)
+ {
+ if (colors[i].R <= 5 && colors[i].G <= 5 && colors[i].B >= 250)
+ {
+ // treat #0000FF as menu color.
+ toIndex[colors[i]] = 2;
+ continue;
+ }
+ else if (colors[i] == black)
+ {
+ toIndex[colors[i]] = 0;
+ continue;
+ }
+
+ byte selected = selectColor(colors[i], NESpalette);
+ int idx = pal.IndexOf(selected);
+ if (idx == -1)
+ {
+ // add 1 everything is going to get shifted
+ // when the tranparent entry is added
+ idx = pal.Count == 0? 1 : pal.Count+2;
+ pal.Add(selected);
+ }
+ toIndex[colors[i]] = (byte)idx;
+
+ }
+
+ pal.Insert(0, 0x0F); //black
+ pal.Insert(2, 0x01); //menu blue
+
+
+
+ if (pal.Count > 4)
+ {
+ return false;
+ }
+ while (pal.Count < 4)
+ {
+ pal.Add(0x0F);
+ }
+
+ return true;
+ }
+
bool makeBattlePalette(List colors, Rgba32[] NESpalette,
out List pal,
@@ -646,6 +709,98 @@ public void SetCustomGearIcons(Stream stream)
}
+ public async Task SetCustomOrbGraphics(Stream stream, int bank, int address, bool sync = false)
+ {
+ Image image = Image.Load(stream);
+ // lit orbs share a single palette; unlit orbs must match the border palette
+
+ var litOrbColors = new List();
+ var firstUnique = new Dictionary();
+ for (int y = 0; y < 32; y++)
+ {
+ for (int x = 0; x < 32; x++)
+ {
+ if (!litOrbColors.Contains(image[x, y]))
+ {
+ firstUnique[image[x, y]] = (x << 16 | y);
+ litOrbColors.Add(image[x, y]);
+ }
+ }
+ }
+
+ List litOrbPal;
+ Dictionary litOrbIndex;
+ if (!makeLitOrbPalette(litOrbColors, NESpalette, out litOrbPal, out litOrbIndex))
+ {
+ if (!sync)
+ {
+ await this.Progress($"WARNING: Failed importing orb sprites, too many unique colors (limit 2 unique colors, plus black and menu blue):",
+ 1 + litOrbPal.Count + litOrbIndex.Count);
+ for (int i = 1; i < litOrbPal.Count; i++)
+ {
+ await this.Progress($"WARNING: NES palette {i}: ${litOrbPal[i],2:X}");
+ }
+ foreach (var i in litOrbIndex)
+ {
+ int c = firstUnique[i.Key];
+ await this.Progress($"WARNING: RGB to index {i.Key}: {i.Value} first appears at {c >> 16}, {c & 0xFFFF}");
+ }
+ }
+ return;
+ }
+
+ int top;
+ int left;
+ byte[] tileTopLeft;
+ byte[] tileTopRight;
+ byte[] tileBottomLeft;
+ byte[] tileBottomRight;
+
+ for (int CurrentOrb = 0; CurrentOrb < 4; CurrentOrb++)
+ {
+
+ top = (CurrentOrb / 2) * 16;
+ left = (CurrentOrb % 2) * 16;
+
+ tileTopLeft = makeTile(image, top, left, litOrbIndex);
+ tileTopRight = makeTile(image, top, left + 8, litOrbIndex);
+
+ tileBottomLeft = makeTile(image, top + 8, left, litOrbIndex);
+ tileBottomRight = makeTile(image, top + 8, left + 8, litOrbIndex);
+
+ // rom sprites are arranged ship, airship, canoe
+ //int[] litOrb_map = {2,0,1};
+
+ PutInBank(bank, address + (CurrentOrb * 64) , EncodeForPPU(tileTopLeft));
+ PutInBank(bank, address + (CurrentOrb * 64) + (16 * 1), EncodeForPPU(tileTopRight));
+ PutInBank(bank, address + (CurrentOrb * 64) + (16 * 2), EncodeForPPU(tileBottomLeft));
+ PutInBank(bank, address + (CurrentOrb * 64) + (16 * 3), EncodeForPPU(tileBottomRight));
+ }
+
+ top = 32;
+ left = 0;
+ var shardTile = makeTile(image,top,left, litOrbIndex);
+ // this address is hardcoded
+ PutInBank(bank, address - 0x10, EncodeForPPU(shardTile));
+
+ int LutMenuPalettes = 0xAD78;
+ PutInBank(0x0E, LutMenuPalettes, litOrbPal.ToArray());
+
+ // Unlit orb
+ top = 32;
+ left = 16;
+ tileTopLeft = makeTileQuantize(image,top,left, MenuIndex);
+ tileTopRight = makeTileQuantize(image,top,left+8, MenuIndex);
+ tileBottomLeft = makeTileQuantize(image,top+8,left, MenuIndex);
+ tileBottomRight = makeTileQuantize(image,top+8,left+8, MenuIndex);
+
+ PutInBank(bank, address + 0x120, EncodeForPPU(tileTopLeft));
+ PutInBank(bank, address + 0x120 + (16*1), EncodeForPPU(tileTopRight));
+ PutInBank(bank, address + 0x120 + (16*2), EncodeForPPU(tileBottomLeft));
+ PutInBank(bank, address + 0x120 + (16*3), EncodeForPPU(tileBottomRight));
+ }
+
+
// These are terrible, but I need non-async functions for some goddamn reason
public void ImportMapmanSync(Image image, int cur_class, int top, int left, Rgba32[] NESpalette)
{
diff --git a/FF1Lib/Sprites/Sprites.cs b/FF1Lib/Sprites/Sprites.cs
index 40105e285..6981d0a70 100644
--- a/FF1Lib/Sprites/Sprites.cs
+++ b/FF1Lib/Sprites/Sprites.cs
@@ -142,6 +142,14 @@ How they are actually rendered on screen from an emulator depends on what palett
{ new Rgba32(0xbd, 0xbd, 0xbd), 3 } //0x10 light gray
};
+ public Dictionary MenuIndex = new Dictionary
+ {
+ { new Rgba32(0x00, 0x00, 0x00), 0}, //0x0f black
+ { new Rgba32(0x7b, 0x7b, 0x7b), 1}, //0x00 dark gray
+ { new Rgba32(0x00, 0x00, 0xff), 2}, //0x01 menu blue
+ { new Rgba32(0xff, 0xff, 0xff), 3} //0x30 white
+ };
+
public byte selectColor(Rgba32 px, Rgba32[] NESpalette)
{
diff --git a/FF1Lib/Tracker.cs b/FF1Lib/Tracker.cs
new file mode 100644
index 000000000..0fd88f6a3
--- /dev/null
+++ b/FF1Lib/Tracker.cs
@@ -0,0 +1,239 @@
+using RomUtilities;
+using System;
+using System.Collections.Generic;
+using System.IO.Compression;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SixLabors.ImageSharp;
+using SixLabors.ImageSharp.PixelFormats;
+
+namespace FF1Lib
+{
+ public enum TrackerIcon
+ {
+
+ Bridge = 0,
+ Canal = 1,
+ Ship = 2,
+ Canoe = 3,
+ Airship = 4,
+ Floater = 5,
+ Crown = 6,
+ Crystal = 7,
+ Herb = 8,
+ Nurse = 9,
+ Adamant = 10,
+ TNT = 11,
+ Ruby = 12,
+ Tail = 13,
+ Bottle = 14,
+ Fairy = 15,
+ Slab = 16,
+ TranslatedSlab = 17,
+ Rod = 18,
+ Lute = 19,
+ Key = 20,
+ Oxyale = 21,
+ Chime = 22,
+ Cube = 23,
+ Sara = 24,
+ King = 25,
+ Bikke = 26,
+ CrescentSage = 27,
+ Sarda = 28,
+ Robot = 29,
+ ShopItem = 30,
+ SprintShoes = 31,
+ Repel = 32,
+ Mark = 33,
+ Sigil = 34,
+ AirBoat = 35,
+ Boulder = 36,
+ Bahamut = 37,
+ EmptyCheckbox = 38,
+ FilledCheckbox = 39,
+
+ Shard = 42,
+ LockPicking = 43,
+ Hints = 44,
+ GoMode = 45
+ }
+ public partial class FF1Rom
+ {
+ const int TRACKER_ICON_BANK = 0x12;
+ const int TRACKER_ICON_OFFSET = 0x8810;
+ const int TRACKER_CHECKBOX_OFFSET = 0x8F40;
+
+ const int TRACKER_ORB_OFFSET = 0x8E70;
+
+
+ public void InGameTracker(Flags flags, Flags unmodifiedFlags)
+ {
+ if (!flags.Tracker)
+ {
+ return;
+ }
+
+
+ byte KingReq = (bool)flags.EarlyKing ? (byte)0x00 : (byte)ObjectId.Princess1;
+ byte SageReq = (bool)flags.EarlySage ? (byte)0x00 : (byte)Item.EarthOrb;
+ byte SardaReq = (bool)flags.EarlySarda ? (byte)0x00 : (byte)ObjectId.Vampire;
+ byte BahamutReq = (bool)flags.FightBahamut && (bool)flags.NoTail ? (byte)0x00 : (byte)Item.Tail;
+
+
+ byte[] NPCReqs = [KingReq,SageReq,SardaReq,BahamutReq];
+
+
+ // set the width of the "ITEM" box, which we'll use for the tracker:
+ PutInBank(0x0E, 0xBABE, 0x00); // 0xBABE!!
+ PutInBank(0x0E, 0xBAC0, 0x20);
+
+ // redirect "ITEM" box draw to our tracker
+ PutInBank(0x0E, 0xB12E, 0x07);
+ PutInBank(0x0E, 0xB91D,Blob.FromHex("20EFB8C63B4CD0A0EAEA"));
+ PutInBank(0x0E, 0xA0D0, Blob.FromHex("20ABDCA9A148A90348A91B4C03FE"));
+
+
+
+ PutInBank(0x1B, 0xA100, NPCReqs);
+
+ PutInBank(0x1B,0xA104,Blob.FromHex("A9FFA23B9D006ECA10FAA200AD0860F005A9019D006EE8AD0C60D005A9029D006EE8AD0060F005A9039D006EE8AD1260F005A9049D006EE8AD0460F004A905D007AD2B60F005A9069D006EE8AD2260F015A9079D006EA00720F7A29004A975D002A9749D1E6EE8A908851EA00AAD23602002A3A909851EA90A851FA00520F7A22A2901851DA006AD2460201DA3A90B851EA009AD27602002A3A90C851EA008AD26602002A3A01420FDA2B004A975D007AD2960F00AA9749D1E6EA90D9D006EE8A00E20F7A29004A975D00CAC03A1F005B92060F00AA9749D1E6EA90E9D006EE8A90F851EA910851FA01320FDA22A2901851DAD2F60201DA3A911851EA912851FA00B20F7A22A2901851DA00FAD2860201DA3AD2A60F015A9139D006EA01620FDA2B004A975D002A9749D1E6EE8AD2160F015A9149D006EA01720FDA2B004A975D002A9749D1E6EE8A015AD25602048A3A016AD30602048A3A017AD2C602048A3A018AD2E602048A3E8E8E8A919851EA01220FDA22A29012052A3A91A851EAC00A1D004A901D00620F7A22A2901A0012052A3A91B851EA03F20FDA22A2901A0042052A3A91C851EAC01A1D004A901D003B92060A0152052A3A91D851EAC02A1D004A901D00620F7A22A2901A00D2052A3A91E851EA901A0112052A3A91F9D006EA0FF20F7A29004A975D002A9749D1E6E4C6BA3B900624A4A60B900624A60851020F7A29004A975D006A510F00AA9749D1E6EA51E9D006EE860851020F7A29009A51F9D006EA975D016A51DF007A51F9D006ED009A510F00AA51E9D006EA9749D1E6EE860C900F004989D006EE860C900F013A51E9D006E20F7A29004A975D002A9749D1E6EE860A9008D0120A200A91E8510208BA3E63B20ABDCA91E8510AA208BA3A90E4C03FEBD006EAC0220A4558C0620A4548C06208D0720E8E654C610D0E660"));
+ }
+
+
+
+ public void AddOrbLetterOverlays()
+ {
+ byte[][] Overlays =
+ [
+ //Fire
+ [
+ 4,4,4,4,4,4,4,4,
+ 4,4,4,0,0,0,0,0,
+ 4,4,4,0,1,1,1,0,
+ 4,4,4,0,1,0,0,0,
+ 4,4,4,0,1,1,0,4,
+ 4,4,4,0,1,0,4,4,
+ 4,4,4,0,1,0,4,4,
+ 4,4,4,0,0,0,4,4
+ ],
+
+ //Water
+ [
+ 4,4,4,4,4,4,4,4,
+ 4,0,0,0,4,0,0,0,
+ 4,0,1,0,4,0,1,0,
+ 4,0,1,0,0,0,1,0,
+ 4,0,1,0,1,0,1,0,
+ 4,0,1,1,1,1,1,0,
+ 4,0,0,1,0,1,0,0,
+ 4,4,0,0,0,0,0,4
+ ],
+
+ //Air
+ [
+ 4,4,4,4,4,4,4,4,
+ 4,4,4,4,0,0,0,4,
+ 4,4,4,0,0,1,0,0,
+ 4,4,4,0,1,0,1,0,
+ 4,4,4,0,1,1,1,0,
+ 4,4,4,0,1,0,1,0,
+ 4,4,4,0,1,0,1,0,
+ 4,4,4,0,0,0,0,0
+ ],
+
+ //Earth
+ [
+ 4,4,4,4,4,4,4,4,
+ 4,4,4,0,0,0,0,0,
+ 4,4,4,0,1,1,1,0,
+ 4,4,4,0,1,0,0,0,
+ 4,4,4,0,1,1,0,4,
+ 4,4,4,0,1,0,0,0,
+ 4,4,4,0,1,1,1,0,
+ 4,4,4,0,0,0,0,0
+ ]
+ ];
+
+ Console.WriteLine("Doing Orb Overlays");
+ for (int i = 0; i < 4; i++)
+ {
+ int ThisOffset = TRACKER_ORB_OFFSET+i*0x40;
+ byte[] OrbTile = DecodePPU(GetFromBank(TRACKER_ICON_BANK, ThisOffset,0x10));
+ for (int j = 0; j < 64; j++)
+ {
+ byte OrbByte = OrbTile[j];
+ byte OverlayByte = Overlays[i][j];
+ OrbTile[j] = OverlayByte == 4 ? OrbByte : OverlayByte;
+ }
+ PutInBank(TRACKER_ICON_BANK, ThisOffset,EncodeForPPU(OrbTile));
+ }
+ }
+
+ public void AddTrackerIcons(Flags flags)
+ {
+
+ byte[] BlankTile = EncodeForPPU(
+ [
+ 3,3,3,3,3,3,3,3,
+ 3,3,3,3,3,3,3,3,
+ 3,3,3,3,3,3,3,3,
+ 3,3,3,3,3,3,3,3,
+ 3,3,3,3,3,3,3,3,
+ 3,3,3,3,3,3,3,3,
+ 3,3,3,3,3,3,3,3,
+ 3,3,3,3,3,3,3,3
+ ]
+ );
+
+
+
+ var assembly = System.Reflection.Assembly.GetExecutingAssembly();
+ var trackerIconFile = assembly.GetManifestResourceNames().
+ Single(str => str.EndsWith("tracker_icons.png"));
+ var trackerIconStream = assembly.GetManifestResourceStream(trackerIconFile);
+
+ Image trackerIconImage = Image.Load(trackerIconStream);
+
+ Dictionary TrackerIcons = new();
+
+ foreach (var icon in Enum.GetValues())
+ {
+ int left = 8 * ((int)icon % 6);
+ int top = 8 * ((int)icon / 6);
+
+ TrackerIcons[icon] = EncodeForPPU(makeTile(trackerIconImage,top,left,MenuIndex));
+ }
+
+ if (flags.NoOverworld)
+ {
+ TrackerIcons[TrackerIcon.Bridge] = BlankTile;
+ TrackerIcons[TrackerIcon.Ship] = BlankTile;
+ TrackerIcons[TrackerIcon.Canoe] = TrackerIcons[TrackerIcon.Mark];
+ TrackerIcons[TrackerIcon.Floater] = TrackerIcons[TrackerIcon.Sigil];
+ }
+ else if ((bool)flags.AirBoat)
+ {
+ TrackerIcons[TrackerIcon.Floater] = TrackerIcons[TrackerIcon.AirBoat];
+ TrackerIcons[TrackerIcon.Airship] = TrackerIcons[TrackerIcon.AirBoat];
+ }
+ if ((bool)flags.FightBahamut)
+ {
+ TrackerIcons[TrackerIcon.Tail] = TrackerIcons[TrackerIcon.Bahamut];
+ }
+
+ for (int i = 0; i<31; i++)
+ {
+ PutInBank(TRACKER_ICON_BANK, i*0x10 + TRACKER_ICON_OFFSET, TrackerIcons[(TrackerIcon)i]);
+ }
+
+ PutInBank(TRACKER_ICON_BANK, TRACKER_ICON_OFFSET + 0x200, TrackerIcons[TrackerIcon.Shard]);
+
+ // TODO add icons for Sprint Shoes, Repel
+
+ PutInBank(TRACKER_ICON_BANK, TRACKER_CHECKBOX_OFFSET, TrackerIcons[TrackerIcon.EmptyCheckbox]);
+ PutInBank(TRACKER_ICON_BANK, TRACKER_CHECKBOX_OFFSET + 0x10, TrackerIcons[TrackerIcon.FilledCheckbox]);
+ }
+ }
+}
diff --git a/FF1Lib/asm/0E_9F48_ItemShopCheckForSpace.asm b/FF1Lib/asm/0E_9F48_ItemShopCheckForSpace.asm
index 4d3419d08..1da208110 100644
--- a/FF1Lib/asm/0E_9F48_ItemShopCheckForSpace.asm
+++ b/FF1Lib/asm/0E_9F48_ItemShopCheckForSpace.asm
@@ -3,26 +3,55 @@
shop_quantity = $030A
+shop_curitem = $030C
+items = $6020
.org $9F48
-NewCheckForSpace: ; we need a slightly more complex formula for
+NewCheckForSpace:
LDX shop_curitem
- CPX #$16 ; CMP ItemId with Tent
- BCC :+
- LDA items, X
+ CPX #$16
+ BCS NormalItem
+ LDY #$FF
+ LDA game_flags,Y
+ ORA #GMFLG_EVENT
+ STA game_flags,Y
CLC
- ADC shop_quantity ; add the shop item quantity
+ NOP ; AP: RTS
+ NormalItem:
+ LDA items,X
+ CLC
+ ADC shop_quantity
CMP #$64
BCC SpaceAvailable
- RTS ; if we would have 100 or more of the item, return with carry set to indicate we have too many
- : LDX #$FF
- LDA game_flags, X ; get the game flags
- ORA #GMFLG_EVENT ; set the event bit
- STA game_flags, X ; and write back
- CLC
- RTS
-SpaceAvailable:
- STA items, X ; otherwise, add the items to the player's inventory and return with carry not set to indicate success
- CLC
- RTS
+ RTS
+ SpaceAvailable:
+ STA items,X
+ CLC
+ RTS
+ ;(34 bytes)
+
+; NewCheckForSpace: ; we need a slightly more complex formula for
+; LDX shop_curitem
+; CPX #$16 ; CMP ItemId with Tent
+; BCC :+
+; Quantity:
+; LDA items,X
+; CLC
+; ADC shop_quantity ; add the shop item quantity
+; CMP #$64
+; BCC SpaceAvailable
+; RTS ; if we would have 100 or more of the item, return with carry set to indicate we have too many
+; : LDY #$FF
+; LDA game_flags,Y ; get the game flags
+; ORA #GMFLG_EVENT ; set the event bit
+; STA game_flags,Y ; and write back
+; BNE Quantity ; AP: CLC
+; ; RTS
+; SpaceAvailable:
+; STA items, X ; otherwise, add the items to the player's inventory and return with carry not set to indicate success
+; CLC
+; RTS
+; ;(36 bytes)
+
+
diff --git a/FF1Lib/asm/1B_9300_LockpickDoors.asm b/FF1Lib/asm/1B_9300_LockpickDoors.asm
index 3bfb2c41b..8e72c7b59 100644
--- a/FF1Lib/asm/1B_9300_LockpickDoors.asm
+++ b/FF1Lib/asm/1B_9300_LockpickDoors.asm
@@ -1,9 +1,21 @@
-.include "Constants.inc"
+; UPDATED 06/06/26 for use with tracker
+
+.include "Constants.inc"
.include "variables.inc"
+door_bits = $6E03 ; unused expansion ram ($6E00 is used as a tmp gold value elsewhere)
+lockpicking_status = $6E04
+
+
SwapPRG = $FE03
SMMove_Norm_RTS = $CE52
+tileprop = $44
+item_mystickey = $6025
+BANK_MENUS = $0E
+TP_SPEC_DOOR = %00000010
+TP_SPEC_LOCKED = %00000100
+
;currently in the MENU_BANK
;this replacement could be a lot less bytes if we use a temporary memory to store A
;A gets overwriten by the bank swap method and we cant grab it from the stack without some heavy stack manipulation
@@ -24,71 +36,83 @@ BCS SMMove_Norm_RTS ;save a byte by just branching to a nearby rts instead of ha
;exact change replacing 18 bytes at CE53
;AA 98 48 A9 1B 20 03 FE 20 00 93 C0 01 68 A8 8A B0 ED
+;; new version
+;
;bank 1b
-.ORG $9300
-CheckDoorLocked:
- TXA
- LSR A ; downshift to get the door bits into the low 2 bits
- AND #(TP_SPEC_DOOR | TP_SPEC_LOCKED) >> 1 ; mask out the door bits
-
- CMP #TP_SPEC_LOCKED >> 1 ; see if the door is locked
- BNE DoorUnlocked ; if not.. open the door
- LDX #0 ; otherwise (door is locked)
- STX tileprop+1 ; erase the secondary attribute byte (prevent it from being a locked shop)
- LDX item_mystickey ; check to see if the player has the key
- BNE DoorUnlocked ; if they do, open the door
- ;all of this checking is rom space ineffecient but I don't know if we have available temp memory to do an index
- ;check to see if they have a thief/ninja in the party, and they're at or above level 10, the class and level are randomizable at rom creation
- LDX ch_level
- CPX #$09
- BCC Slot1UnderLeveled
- LDX ch_class
- CPX #$01
- BEQ DoorUnlocked
- CPX #$07
- BEQ DoorUnlocked
-
- Slot1UnderLeveled:
- LDX ch_level+$40
- CPX #$09
- BCC Slot2UnderLeveled
- LDX ch_class+$40
- CPX #$01
- BEQ DoorUnlocked
- CPX #$07
- BEQ DoorUnlocked
-
- Slot2UnderLeveled:
- LDX ch_level+$80
- CPX #$09
- BCC Slot3UnderLeveled
- LDX ch_class+$80
- CPX #$01
- BEQ DoorUnlocked
- CPX #$07
- BEQ DoorUnlocked
-
- Slot3UnderLeveled:
- LDX ch_level+$C0
- CPX #$09
- BCC Slot4UnderLeveled
- LDX ch_class+$C0
- CPX #$01
- BEQ DoorUnlocked
- CPX #$07
- BEQ DoorUnlocked
-
- Slot4UnderLeveled:
- LDY #$01
- TAX
- LDA #BANK_MENUS
- JMP SwapPRG
-
- DoorUnlocked:
- LDY #$00
- TAX
- LDA #BANK_MENUS
- JMP SwapPRG
-
-;105 bytes
-;8A 4A 29 03 C9 02 D0 59 A2 00 86 45 AE 25 60 D0 50 AE 26 61 E0 09 90 0B AE 00 61 E0 01 F0 42 E0 07 F0 3E AE 66 61 E0 09 90 0B AE 40 61 E0 01 F0 30 E0 07 F0 2C AE A6 61 E0 09 90 0B AE 80 61 E0 01 F0 1E E0 07 F0 1A AE E6 61 E0 09 90 0B AE C0 61 E0 01 F0 0C E0 07 F0 08 A0 01 AA A9 0E 4C 03 FE A0 00 AA A9 0E 4C 03 FE
+
+
+
+;; 35 bytes
+;;
+;; BD 26 61 CD 3D 93 90 0D BD 00 61 CD 3E 93 F0 09 CD 3F 93 F0 04 A9 00 F0 02 A9 01 A8 0D 04 6E 8D 04 6E 60
+
+
+;; old version
+;bank 1b
+; .ORG $9300
+; CheckDoorLocked:
+; TXA
+; LSR A ; downshift to get the door bits into the low 2 bits
+; AND #(TP_SPEC_DOOR | TP_SPEC_LOCKED) >> 1 ; mask out the door bits
+
+; CMP #TP_SPEC_LOCKED >> 1 ; see if the door is locked
+; BNE DoorUnlocked ; if not.. open the door
+; LDX #0 ; otherwise (door is locked)
+; STX tileprop+1 ; erase the secondary attribute byte (prevent it from being a locked shop)
+; LDX item_mystickey ; check to see if the player has the key
+; BNE DoorUnlocked ; if they do, open the door
+; ;all of this checking is rom space ineffecient but I don't know if we have available temp memory to do an index
+; ;check to see if they have a thief/ninja in the party, and they're at or above level 10, the class and level are randomizable at rom creation
+; LDX ch_level
+; CPX #$09
+; BCC Slot1UnderLeveled
+; LDX ch_class
+; CPX #$01
+; BEQ DoorUnlocked
+; CPX #$07
+; BEQ DoorUnlocked
+
+; Slot1UnderLeveled:
+; LDX ch_level+$40
+; CPX #$09
+; BCC Slot2UnderLeveled
+; LDX ch_class+$40
+; CPX #$01
+; BEQ DoorUnlocked
+; CPX #$07
+; BEQ DoorUnlocked
+
+; Slot2UnderLeveled:
+; LDX ch_level+$80
+; CPX #$09
+; BCC Slot3UnderLeveled
+; LDX ch_class+$80
+; CPX #$01
+; BEQ DoorUnlocked
+; CPX #$07
+; BEQ DoorUnlocked
+
+; Slot3UnderLeveled:
+; LDX ch_level+$C0
+; CPX #$09
+; BCC Slot4UnderLeveled
+; LDX ch_class+$C0
+; CPX #$01
+; BEQ DoorUnlocked
+; CPX #$07
+; BEQ DoorUnlocked
+
+; Slot4UnderLeveled:
+; LDY #$01
+; TAX
+; LDA #BANK_MENUS
+; JMP SwapPRG
+
+; DoorUnlocked:
+; LDY #$00
+; TAX
+; LDA #BANK_MENUS
+; JMP SwapPRG
+
+; ;105 bytes
+; ;8A 4A 29 03 C9 02 D0 59 A2 00 86 45 AE 25 60 D0 50 AE 26 61 E0 09 90 0B AE 00 61 E0 01 F0 42 E0 07 F0 3E AE 66 61 E0 09 90 0B AE 40 61 E0 01 F0 30 E0 07 F0 2C AE A6 61 E0 09 90 0B AE 80 61 E0 01 F0 1E E0 07 F0 1A AE E6 61 E0 09 90 0B AE C0 61 E0 01 F0 0C E0 07 F0 08 A0 01 AA A9 0E 4C 03 FE A0 00 AA A9 0E 4C 03 FE
diff --git a/FF1Lib/asm/1B_A100_Tracker.asm b/FF1Lib/asm/1B_A100_Tracker.asm
new file mode 100644
index 000000000..0c0e5adea
--- /dev/null
+++ b/FF1Lib/asm/1B_A100_Tracker.asm
@@ -0,0 +1,844 @@
+tmp = $10
+event_flag = tmp+$0D
+icon_id1 = tmp+$0E
+icon_id2 = tmp+$0F
+dest_x = $3A
+dest_y = $3B
+ppu_dest = $54
+icon_buf = $6E00
+unsram = $6000 ; $400 bytes
+ship_vis = unsram+$00
+airship_vis = unsram+$04
+bridge_vis = unsram+$08
+canal_vis = unsram+$0C
+has_canoe = unsram+$12 ; (not to be confused with item_canoe)
+
+items = unsram+$20
+
+item_lute = items+$01
+item_crown = items+$02
+item_crystal = items+$03
+item_herb = items+$04
+item_mystickey = items+$05
+item_tnt = items+$06
+item_adamant = items+$07
+item_slab = items+$08
+item_ruby = items+$09
+item_rod = items+$0A
+item_floater = items+$0B
+item_chime = items+$0C
+item_tail = items+$0D
+item_cube = items+$0E
+item_bottle = items+$0F
+item_oxyale = items+$10
+orb_earth = items+$11
+
+game_flags = unsram+$200
+
+
+
+
+DrawMainItemBox = $B8EF ; bank 0E
+
+CoordToNTAddr = $DCAB ; bank 1F
+MenuCondStall = $E12E ; bank 1F
+SwapPRG = $FE03 ; bnak 1F
+
+SOURCE_BANK = $0E
+DEST_BANK = $1B
+
+
+;;;;;;;;;;;;;;;;;;;;;;
+;; Tracker Icon Tiles
+;;;;;;;;;;;;;;;;;;;;;;
+BRIDGE = $01
+CANAL = $02
+SHIP = $03
+CANOE = $04
+MARK = $04
+AIRSHIP = $05
+FLOATER = $06
+SIGIL = $06
+CROWN = $07
+CRYSTAL = $08
+HERB = $09
+ELFDOC = $0A
+ADAMANT = $0B
+TNT = $0C
+RUBY = $0D
+TAIL = $0E
+BOTTLE = $0F
+FAIRY = $10
+SLAB = $11
+TRSLAB = $12
+ROD = $13
+LUTE = $14
+KEY = $15
+OXYALE = $16
+CHIME = $17
+CUBE = $18
+SARA = $19
+KING = $1A
+BIKKE = $1B
+SAGE = $1C
+SARDA = $1D
+ROBOT = $1E
+SHOP = $1F
+;SHOES = $1F
+;REPEL = $20
+
+EMPTYCH = $74
+FILLEDCH = $75
+BLANK = $FF
+
+OBJID_KING = $01 ; King of Coneria
+OBJID_GARLAND = $02 ; Garland (the first one, not ToFR)
+OBJID_PRINCESS_1 = $03 ; kidnapped princess (in ToF)
+OBJID_BIKKE = $04 ; Bikke the Pirate
+OBJID_ELFDOC = $05 ; Attending the Elf Prince (more of a nurse, let's be honest)
+OBJID_ELFPRINCE = $06 ; Elf Prince (sleeping man-beauty)
+OBJID_ASTOS = $07 ; Astos -- the dark king! omg scarey
+OBJID_NERRICK = $08 ; Nerrick -- the dwarf working on the canal
+OBJID_SMITH = $09 ; Smith, the dwarven blacksmith (no, he's not Watts)
+OBJID_MATOYA = $0A
+OBJID_UNNE = $0B ; you've never heard of him?
+OBJID_VAMPIRE = $0C ; Earth Cave's Vampire
+OBJID_SARDA = $0D
+OBJID_BAHAMUT = $0E ; Bahamut
+OBJID_LEFEIN = $0F ; Lefein chime guy
+OBJID_CUBEBOT = $11 ; waterfall robot
+OBJID_PRINCESS_2 = $12 ; rescued princess (in Coneria Castle)
+OBJID_FAIRY = $13 ; fairy that appears from the bottle
+OBJID_TITAN = $14 ; Titan in Titan's Tunnel
+OBJID_CANOESAGE = $15 ; sage you get canoe from in vanilla
+OBJID_RODPLATE = $16 ; plate that is removed with the Rod
+OBJID_LUTEPLATE = $17 ; plate that is removed with the Lute
+
+OBJID_SKYWAR_FIRST = $3A ; start of the 5 sky warriors
+OBJID_SKYWAR_LAST = OBJID_SKYWAR_FIRST+4 ; last of the 5 sky warriors
+
+OBJID_PIRATETERR_1 = $3F ; townspeople that were terrorized by the
+OBJID_PIRATETERR_2 = $40 ; pirates... they don't become visible until after
+OBJID_PIRATETERR_3 = $41 ; you beat Bikke and claim the ship
+
+OBJID_SHOPITEM = $FF ; a key item you buy from a shop
+
+
+.ORG $B12D ; bank $0E
+LDA #07 ;; load the box ID (dimensions must be changed from $08 to $1C at $BAC0 in bank $0E)
+JSR DrawItemTitleBox
+
+
+.ORG $B91D ; bank $0E
+DrawItemTitleBox:
+ JSR DrawMainItemBox ;; dest_x & dest_y now set
+ DEC dest_y ;; we need one row above the usual menu start location
+ JMP ItemTrackerRedirect
+ NOP
+ NOP
+ ; 10 bytes -- same as vanilla
+
+.ORG $A0D0 ; bank $0E
+
+ItemTrackerRedirect:
+ JSR CoordToNTAddr ;; convert dest_x & dest_y to ppu address and store in ppu_dest (2 bytes)
+ LDA #>(ItemTrackerInit-1)
+ PHA
+ LDA #<(ItemTrackerInit-1)
+ PHA
+ LDA #DEST_BANK
+ JMP SwapPRG
+ ; 14 bytes
+
+
+
+
+
+.ORG $A100 ; bank $1B
+
+
+;; written by the randomizer.
+;; check flag = #OBJID
+;; check item = item id (offset from items)
+;; noreq = #0
+;; order: KING SAGE SARDA BAHAMUT
+lut_NPCReqs:
+ .BYTE OBJID_PRINCESS_1 $11 OBJID_VAMPIRE $0D
+ ; (4 bytes)
+
+.ORG $A104
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; SUBROUTINES
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Tracker Logic begins here
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+lut_NPCReqs = $A100
+
+ItemTrackerInit:
+ LDA #BLANK
+ LDX #59
+ initloop: ; fill 60 icon_buf values with blank menu tile
+ STA icon_buf,X
+ DEX
+ BPL initloop
+
+ ; 13 bytes
+
+ ;; begin processing items immediately:
+
+ProcessItems:
+ LDX #$00 ; start at icon_buf[0]
+ ; 2 bytes
+
+;;; first 4 OW items could be a subroutine but it only saves 3 bytes -- 40 vs 37
+; CheckBridge:
+ LDA bridge_vis
+ BEQ NoBridge
+ LDA #BRIDGE
+ STA icon_buf,X
+ NoBridge:
+ INX
+ ; 11 bytes
+
+
+; CheckCanal:
+ LDA canal_vis
+ BNE NoCanal ; canal logic is flipped
+ LDA #CANAL
+ STA icon_buf,X
+ NoCanal:
+ INX
+ ; 11 bytes
+
+; CheckShip -- should work with AirBoat
+ LDA ship_vis
+ BEQ NoShip
+ LDA #SHIP
+ STA icon_buf,X
+ NoShip:
+ INX
+ ; 11 bytes
+
+; CheckCanoe:
+ LDA has_canoe
+ BEQ NoCanoe
+ LDA #CANOE
+ STA icon_buf,X
+ NoCanoe:
+ INX
+ ; 11 bytes
+
+; CheckFloaterAirship
+; With AirBoat on, the FLOATER and AIRSHIP icons are identical
+ LDA airship_vis
+ BEQ NoAirship
+ LDA #AIRSHIP
+ BNE GotAirship
+ NoAirship:
+ LDA item_floater
+ BEQ NoFloater
+ LDA #FLOATER
+ GotAirship:
+ STA icon_buf,X
+ NoFloater:
+ INX
+ ; 20 bytes
+
+; CheckCrown:
+ LDA item_crown
+ BEQ NoCrown
+ LDA #CROWN
+ STA icon_buf,X
+ LDY #OBJID_ASTOS
+ JSR CheckGameEventFlag
+ BCC NoAstos
+ LDA #FILLEDCH
+ BNE CrownCheckbox
+ NoAstos:
+ LDA #EMPTYCH
+ CrownCheckbox:
+ STA icon_buf+30,X
+ NoCrown:
+ INX
+ ; 27 bytes
+
+; CheckCrystal
+ LDA #CRYSTAL
+ STA icon_id1
+ LDY #OBJID_MATOYA
+ LDA item_crystal
+ JSR CheckTurnInItem
+ ; 12 bytes
+
+
+; CheckHerb:
+ LDA #HERB
+ STA icon_id1
+ LDA #ELFDOC
+ STA icon_id2
+ LDY #OBJID_ELFDOC
+ JSR CheckGameEventFlag
+ ROL
+ AND #1
+ STA event_flag
+ LDY #OBJID_ELFPRINCE
+ LDA item_herb
+ JSR CheckTwoPartTurnInItem
+ ; 26 bytes
+
+; CheckAdamant
+ LDA #ADAMANT
+ STA icon_id1
+ LDY #OBJID_SMITH
+ LDA item_adamant
+ JSR CheckTurnInItem
+ ; 12 bytes
+
+; CheckTNT
+ LDA #TNT
+ STA icon_id1
+ LDY #OBJID_NERRICK
+ LDA item_tnt
+ JSR CheckTurnInItem
+ ; 12 bytes
+
+; CheckRuby
+ LDY #OBJID_TITAN
+ JSR IsObjectVisible
+ BCS TitanStillThere
+ LDA #FILLEDCH
+ BNE RubyCheckbox
+ TitanStillThere:
+ LDA item_ruby
+ BEQ NoRuby
+ LDA #EMPTYCH
+ RubyCheckbox:
+ STA icon_buf+30,X
+ LDA #RUBY
+ STA icon_buf,X
+ NoRuby:
+ INX
+
+ ; 12 bytes
+
+; CheckTail
+ LDY #OBJID_BAHAMUT
+ JSR CheckGameEventFlag
+ BCC NoTailTurnIn
+ LDA #FILLEDCH
+ BNE TailCheckbox
+ NoTailTurnIn:
+ LDY lut_NPCReqs+3
+ BEQ NoBahamutReq
+ LDA items,Y
+ BEQ NoTail
+ NoBahamutReq:
+ LDA #EMPTYCH
+ TailCheckbox:
+ STA icon_buf+30,X
+ LDA #TAIL
+ STA icon_buf,X
+ NoTail:
+ INX
+
+; CheckBottle:
+ LDA #BOTTLE
+ STA icon_id1
+ LDA #FAIRY
+ STA icon_id2
+ LDY #OBJID_FAIRY
+ JSR IsObjectVisible
+ ROL
+ AND #1
+ STA event_flag
+ ; LDY #OBJID_FAIRY -- Y still set to fairy
+ LDA item_bottle
+ JSR CheckTwoPartTurnInItem
+ ; 24 bytes
+
+; CheckSlab:
+ LDA #SLAB
+ STA icon_id1
+ LDA #TRSLAB
+ STA icon_id2
+ LDY #OBJID_UNNE
+ JSR CheckGameEventFlag
+ ROL
+ AND #1
+ STA event_flag
+ LDY #OBJID_LEFEIN
+ LDA item_slab
+ JSR CheckTwoPartTurnInItem
+ ; 26 bytes
+
+
+;;; inlining these takes fewer bytes
+; CheckRod:
+ LDA item_rod
+ BEQ NoRod
+ LDA #ROD
+ STA icon_buf,X
+ LDY #OBJID_RODPLATE
+ JSR IsObjectVisible
+ BCS RodPlateNotCleared
+ LDA #FILLEDCH
+ BNE RodCheckbox
+ RodPlateNotCleared:
+ LDA #EMPTYCH
+ RodCheckbox:
+ STA icon_buf+30,X
+ NoRod:
+ INX
+
+; CheckLute:
+ LDA item_lute
+ BEQ NoLute
+ LDA #LUTE
+ STA icon_buf,X
+ LDY #OBJID_LUTEPLATE
+ JSR IsObjectVisible
+ BCS LutePlateNotCleared
+ LDA #FILLEDCH
+ BNE LuteCheckbox
+ LutePlateNotCleared:
+ LDA #EMPTYCH
+ LuteCheckbox:
+ STA icon_buf+30,X
+ NoLute:
+ INX
+
+
+; CheckKey:
+ LDY #KEY
+ LDA item_mystickey
+ JSR CheckPassiveItem
+ ; (8 bytes)
+
+ LDY #OXYALE
+ LDA item_oxyale
+ JSR CheckPassiveItem
+ ; (8 bytes)
+
+ LDY #CHIME
+ LDA item_chime
+ JSR CheckPassiveItem
+ ; (8 bytes)
+
+ LDY #CUBE
+ LDA item_cube
+ JSR CheckPassiveItem
+ ; (8 bytes)
+
+; TODO: Check Sprint Shoes and Repel
+ INX
+ INX
+ ; (2 bytes for now)
+
+ INX ; space to offset NPCs from Items
+
+; Check Princess
+ LDA #SARA
+ STA icon_id1
+
+ LDY #OBJID_PRINCESS_2
+ JSR IsObjectVisible
+ ROL
+ AND #1
+
+ JSR CheckNPC ;; Y still has Sara's OBJID
+ ; (15 bytes)
+
+
+; Check King
+
+ LDA #KING
+ STA icon_id1
+ LDY lut_NPCReqs
+ BNE KingReq
+ LDA #1
+ BNE KingID
+ KingReq:
+ JSR CheckGameEventFlag
+ ROL
+ AND #1
+ KingID:
+ LDY #OBJID_KING
+ JSR CheckNPC
+ ; (17 bytes)
+
+; Check Bikke
+ LDA #BIKKE
+ STA icon_id1
+ LDY #OBJID_PIRATETERR_1
+ JSR IsObjectVisible
+ ROL
+ AND #1
+ LDY #OBJID_BIKKE
+ JSR CheckNPC
+ ; (17 bytes)
+
+
+; Check Canoe Sage
+ LDA #SAGE
+ STA icon_id1
+ LDY lut_NPCReqs+1
+ BNE SageReq
+ LDA #1
+ BNE SageID
+ SageReq:
+ LDA items,Y
+ SageID:
+ LDY #OBJID_CANOESAGE
+ JSR CheckNPC
+ ; (11 bytes)
+
+
+; Check Sarda
+ LDA #SARDA
+ STA icon_id1
+ LDY lut_NPCReqs+2
+ BNE SardaReq
+ LDA #1
+ BNE SardaID
+ SardaReq:
+ JSR CheckGameEventFlag
+ ROL
+ AND #1
+ SardaID
+ LDY #OBJID_SARDA
+ JSR CheckNPC
+ ; (19 bytes)
+
+
+; Check Robot
+ LDA #ROBOT
+ STA icon_id1
+ ;; requirement logic ;;;;;;
+ LDA #1 ;;
+ ;; end requirement logic ;;
+ LDY #OBJID_CUBEBOT
+ JSR CheckNPC
+ ; (11 bytes)
+
+; Check ShopItem
+ LDA #SHOP
+ STA icon_buf,X
+ LDY #OBJID_SHOPITEM
+ JSR CheckGameEventFlag
+ BCC NoShopItem
+ LDA #FILLEDCH
+ BNE ShopItemCheckbox
+ NoShopItem:
+ LDA #EMPTYCH
+ ShopItemCheckbox:
+ STA icon_buf+30,X
+
+
+
+
+ JMP DrawIconsToItemMenu
+
+;;;;;; copied from bank $0E
+CheckGameEventFlag:
+ LDA game_flags,Y ; Get the game flags using Y as index
+ LSR A ; and shift the event flag into C
+ LSR A
+ RTS
+ ; 6 bytes
+
+IsObjectVisible:
+ LDA game_flags,Y ; get the game flags using object ID as index
+ LSR A ; shift object visibility flag into C
+ RTS ; and exit
+ ; 5 bytes
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; CheckTurnInItem
+;;
+;; Check item that is removed from
+;; inventory after turn-in
+;;
+;; applies to these items:
+;; crystal
+;; adamant
+;; tnt
+;;
+;; Not ruby -- Titan's event flag is not updated in talk routines
+;;
+;; in:
+;; A: item flag
+;; X: pointer to location in icon_buf
+;; Y: npc #OBJID
+;; icon_id1: icon to draw
+;; out:
+;; X: incremented icon_buf pointer
+;;
+;; setup: (this could be done more elegantly with a lut, but there aren't enough checks to worry about it)
+;; LDA #ITEMICON
+;; STA icon_id1
+;; LDY #OBJID
+;; LDA item flag
+;; JSR CheckTurnInItem
+;; (11 bytes per check)
+;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+CheckTurnInItem:
+ ; CMP #0 ; not strictly needed since setup should load the item flag just before entering
+ ; BEQ NoTurnInItem
+ ; LDA #EMPTYCH
+ ; BNE TurnInCheckbox
+ ; NoTurnInItem:
+ ; JSR CheckGameEventFlag
+ ; BCC NoTurnIn
+ ; LDA #FILLEDCH
+ ; TurnInCheckbox:
+ ; STA icon_buf+30,X
+ ; LDA icon_id1
+ ; STA icon_buf,X
+ ; NoTurnin:
+ ; INX
+ ; RTS
+ ; ; (25 bytes)
+ STA tmp
+ JSR CheckGameEventFlag
+ BCC NoTurnIn
+ LDA #FILLEDCH
+ BNE TurnInCheckbox
+ NoTurnIn:
+ LDA tmp
+ BEQ NoTurnInItem
+ LDA #EMPTYCH
+ TurnInCheckbox:
+ STA icon_buf+30,X
+ LDA icon_id1
+ STA icon_buf,X
+ NoTurnInItem:
+ INX
+ RTS
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; CheckTwoPartTurnInItem
+;; Check item that is removed from
+;; inventory after an event, and that
+;; then requires talking to an NPC to
+;; receive the item.
+;;
+;; applies to these:
+;; ITEM -> EVENT -> NPC
+;; herb -> talk to Elf Doc -> Elf Prince
+;; slab -> talk to Unne -> Lefein Guy
+;; bottle -> open bottle -> Fairy
+;;
+;; in:
+;; A: item flag
+;; X: pointer to location in icon_buf
+;; Y: final npc #OBJID
+;; event_flag: talked to Elf Doc, Talked to Unne, Released Fairy
+;; icon_id1: icon to draw if item held
+;; icon_id2: icon to draw if event has taken place
+;;
+;; out:
+;; X: incremented icon_buf pointer
+;;
+;; setup:
+;; LDA #ITEMICON1
+;; STA icon_id1
+;; LDA #ITEMICON2
+;; STA icon_id2
+;; LDY eventOBJID
+;; JSR CheckGameEventFlag OR IsObjectVisible
+;; ROL
+;; AND #1
+;; STA event_flag
+;; LDY #OBJID
+;; LDA item flag
+;; JSR CheckTwoPartTurnInItem
+;; (26 bytes per check)
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+CheckTwoPartTurnInItem:
+ STA tmp
+ JSR CheckGameEventFlag
+ BCC NoFinalTurnIn
+ LDA icon_id2
+ STA icon_buf,X
+ LDA #FILLEDCH
+ BNE TwoPartCheckbox
+ NoFinalTurnin:
+ LDA event_flag
+ BEQ NoInitialEvent
+ LDA icon_id2
+ STA icon_buf,X
+ BNE TwoPartEmptyCheckbox
+ NoInitialEvent:
+ LDA tmp
+ BEQ NoTwoPartItem
+ LDA icon_id1
+ STA icon_buf,X
+ TwoPartEmptyCheckbox:
+ LDA #EMPTYCH
+ TwoPartCheckbox:
+ STA icon_buf+30,X
+ NoTwoPartItem:
+ INX
+ RTS
+ ; CMP #0
+ ; BEQ NoTwoPartItem
+ ; LDA icon_id1
+ ; STA icon_buf,X
+ ; BNE NoFinalTurnIn
+ ; NoTwoPartItem:
+ ; LDA event_flag
+ ; BEQ NoTwoPartTurnIn
+ ; LDA icon_id2
+ ; STA icon_buf,X
+ ; JSR CheckGameEventFlag ; Y still set
+ ; BCC NoFinalTurnIn
+ ; LDA #FILLEDCH
+ ; BNE TwoPartCheckBox
+ ; NoFinalTurnIn:
+ ; LDA #EMPTYCH
+ ; TwoPartCheckBox:
+ ; STA icon_buf+30,X
+ ; NoTwoPartTurnIn:
+ ; INX
+ ; RTS
+ ; (36 bytes)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; CheckPassiveItem
+;;
+;; Check item that
+;; opens a path without turn-in
+;;
+;; applies to these items:
+;; key
+;; oxyale
+;; chime
+;; cube
+;;
+;; in:
+;; A: item flag
+;; X: pointer to icon_buf location
+;; Y: item icon
+;; out:
+;; X: incremented icon_buf pointer
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+CheckPassiveItem:
+ CMP #0
+ BEQ NoPassiveItem
+ TYA ; STY with X index only works on zero page
+ STA icon_buf,X
+ NoPassiveItem:
+ INX
+ RTS
+ ; (10 bytes)
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; CheckNPC
+;;
+;; Checks a Key Item NPC
+;;
+;; Applies to:
+;; Sara
+;; King
+;; Bikke
+;; Canoe Sage
+;; Sarda
+;; Robot
+;;
+;; in:
+;; A: requirement for NPC to give item
+;; X: pointer to icon_buf location
+;; Y: NPC id
+;; icon_id1: NPC icon
+;; out:
+;; X: incremented icon_buf pointer
+;;
+;; setup:
+;;
+;; LDA #ITEMICON
+;; STA icon_id1
+;; LDA ;; logic to determine whether requirement met
+;; LDY #OBJID
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+CheckNPC:
+ CMP #0
+ BEQ NoNPC
+ LDA icon_id1
+ STA icon_buf,X
+ JSR CheckGameEventFlag
+ BCC NoTalkNPC
+ LDA #FILLEDCH
+ BNE NPCCheckbox
+ NoTalkNPC:
+ LDA #EMPTYCH
+ NPCCheckbox:
+ STA icon_buf+30,x
+ NoNPC:
+ INX
+ RTS
+ ; (26 bytes)
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Drawing Routines
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+DrawIconsToItemMenu:
+ LDA #0
+ STA $2001
+ LDX #$00
+ LDA #30
+ STA tmp
+ JSR DrawIcons
+ INC dest_y
+ JSR CoordToNTAddr
+ LDA #30
+ STA tmp
+ TAX
+ JSR DrawIcons
+ LDA #SOURCE_BANK
+ JMP SwapPRG ;;; END ITEM MENU TRACKER
+ ; 29 bytes
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Draw Icons
+;;
+;; in: tmp = number of icons to draw
+;; X = index in icon_buf
+;; out: X = index in icon_buf we ended at -- can be reused
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+DrawIcons:
+ LDA icon_buf,X
+ LDY $2002
+ LDY ppu_dest+1
+ STY $2006
+ LDY ppu_dest
+ STY $2006
+ STA $2007
+ INX
+ INC ppu_dest
+ DEC tmp
+ BNE DrawIcons
+ RTS
+ ; 27 bytes
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+
+
+
diff --git a/FF1Lib/asm/1B_A400_NewDrawShardBox_MainMenuTracker.asm b/FF1Lib/asm/1B_A400_NewDrawShardBox_MainMenuTracker.asm
new file mode 100644
index 000000000..7f8bcce6e
--- /dev/null
+++ b/FF1Lib/asm/1B_A400_NewDrawShardBox_MainMenuTracker.asm
@@ -0,0 +1,190 @@
+tmp = $10
+event_flag = tmp+$0D
+icon_id1 = tmp+$0E
+icon_id2 = tmp+$0F
+dest_x = $3A
+dest_y = $3B
+ppu_dest = $54
+display_buf = $6E10
+unsram = $6000 ; $400 bytes
+ship_vis = unsram+$00
+airship_vis = unsram+$04
+bridge_vis = unsram+$08
+canal_vis = unsram+$0C
+has_canoe = unsram+$12 ; (not to be confused with item_canoe)
+
+items = unsram+$20
+
+item_lute = items+$01
+item_crown = items+$02
+item_crystal = items+$03
+item_herb = items+$04
+item_mystickey = items+$05
+item_tnt = items+$06
+item_adamant = items+$07
+item_slab = items+$08
+item_ruby = items+$09
+item_rod = items+$0A
+item_floater = items+$0B
+item_chime = items+$0C
+item_tail = items+$0D
+item_cube = items+$0E
+item_bottle = items+$0F
+item_oxyale = items+$10
+orb_earth = items+$11
+orb_fire = items+$12
+orb_water = items+$13
+orb_air = items+$14
+shards = items+$15
+
+game_flags = unsram+$200
+
+
+
+
+DrawMainItemBox = $B8EF ; bank 0E
+
+CoordToNTAddr = $DCAB ; bank 1F
+MenuCondStall = $E12E ; bank 1F
+SwapPRG = $FE03 ; bnak 1F
+
+SOURCE_BANK = $0E
+DEST_BANK = $1B
+
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; New DrawShardBox routine
+;;
+;; This has to work well with Deep Dungeon, which also makes changes to the orb box.
+;; In the Deep Dungeon asm, it calls the DrawOrbBox routine before making changes.
+;; Therefore, this needs to be called from within the DrawOrbBox routine itself.
+;; Randomizer needs to handle the dimensions of the box and the location of Deep Dungeon's
+;; floor
+DrawShardDisplay = $A403
+
+
+;; redirect from bank $0E to bank $1B
+.ORG $B8A5 ; bank $0E, in DrawOrbBox
+LDA #>(DrawShardDisplay-1)
+PHA
+LDA #<(DrawShardDisplay-1)
+PHA
+LDA #DEST_BANK
+JMP SwapPRG
+NOP
+NOP
+NOP
+NOP
+NOP
+NOP
+NOP
+
+ ; (18 bytes)
+
+.ORG $A400 ; bank $1B
+
+;written by randomizer
+; the number of shards needed, and the two number tiles associated with them
+
+lut_ShardGoal:
+.BYTE 24 $82 $84
+
+.ORG $A403
+;; stuff from $0E overwritten by the redirect above
+LDA $2002 ; reset PPU toggle
+LDA #>$23C9
+STA $2006
+LDA #<$23C9
+STA $2006 ; attribute byte at $23C9
+LDA tmp+7 ; load computed attribute byte
+STA $2007 ; and draw it
+
+;;; at this point, the orb box and orbs have been drawn, as well as the attributes bytes for lit orbs.
+
+;; now we assemble the display tiles to the buffer. Could use game's string routines, but those are overkill for this little
+;; bit. Printing 2-digit numbers is in bank $0E, but that's a minefield and it's easier to just do it here.
+LDX #0
+LDA #$63 ; the shard tile
+STA display_buf,X
+INX
+LDY #0
+LDA shards ;; load the current number of collected shards
+CMP #10
+BCS TwoDigits
+ LDA #$FF ; blank tile
+ STA display_buf,X
+ INX
+ LDA shards
+ JMP OnesPlace
+TwoDigits:
+ INY
+ SEC
+ SBC #10
+ CMP #10
+ BCS TwoDigits
+ PHA
+ TYA
+ ORA #$80
+ STA display_buf,X
+ INX
+ PLA
+OnesPlace:
+ ORA #$80
+ STA display_buf,X
+ INX
+ ;;; now the slash, and the tiles stored in the lut above
+ LDA #$7A ; slash
+ STA display_buf,X
+ INX
+ LDA lut_ShardGoal+1
+ STA display_buf,X
+ INX
+ LDA lut_ShardGoal+2
+ STA display_buf,X
+ ;; draw
+ LDA #$03
+ STA dest_x
+ LDA #$02
+ STA dest_y
+ JSR CoordToNTAddr
+ LDX #0
+ LDA #6 ; 6 tiles to draw
+ STA tmp
+DrawShardTiles:
+ LDA display_buf,X
+ LDY $2002 ; reset PPU toggle
+ LDY ppu_dest+1
+ STY $2006
+ LDY ppu_dest
+ STY $2006
+ STA $2007
+ INX
+ INC ppu_dest
+ DEC tmp
+ BNE DrawShardTiles
+; draw attribute bytes
+LDA #%00111111 ; lower right metatile has palette 0; all others have palette 3
+LDY $2002 ; reset PPU toggle
+LDY #>$23C0 ; enter address for this attribute byte
+STY $2006
+LDY #<$23C0
+STY $2006
+STA $2007
+;; check if we've met goal
+LDA shards
+CMP lut_ShardGoal
+BCC GoalNotMet ; if goal not met, we don't need to do anything. Otherwise...
+ LDA #%11001111 ; lower left metatile has palette 0; all others have palette 3
+ LDY $2002 ; reset PPU toggle
+ LDY #>$23C1 ; enter address for this attribute byte
+ STY $2006
+ LDY #<$23C1
+ STY $2006
+ STA $2007
+GoalNotMet:
+;; swap to bank $0E and return from JSR DrawOrbBox
+LDA #SOURCE_BANK
+JMP SwapPRG
+
+
diff --git a/FF1Lib/imagedata/icons/orbs_shards.png b/FF1Lib/imagedata/icons/orbs_shards.png
new file mode 100644
index 000000000..e2c259e71
Binary files /dev/null and b/FF1Lib/imagedata/icons/orbs_shards.png differ
diff --git a/FF1Lib/imagedata/icons/tracker_icons.png b/FF1Lib/imagedata/icons/tracker_icons.png
new file mode 100644
index 000000000..0df6a0fc6
Binary files /dev/null and b/FF1Lib/imagedata/icons/tracker_icons.png differ