+import {
+ DUNGEON_IDS,
+ DUNGEON_MASKS,
+ INV_ADDR,
+ SRAM_ADDR,
+ getShort,
+ isBossDefeated,
+ isChestOpen,
+} from './alttp-ram';
+
+export const BOOLEAN_STATES = [
+ 'blue-boomerang',
+ 'bomb',
+ 'bombos',
+ 'bow',
+ 'bowless-silvers',
+ 'book',
+ 'boots',
+ 'bugnet',
+ 'byrna',
+ 'cape',
+ 'duck',
+ 'ether',
+ 'fire-rod',
+ 'flippers',
+ 'flute',
+ 'half-magic',
+ 'hammer',
+ 'hookshot',
+ 'ice-rod',
+ 'lamp',
+ 'mirror',
+ 'moonpearl',
+ 'mushroom',
+ 'powder',
+ 'quake',
+ 'quarter-magic',
+ 'red-boomerang',
+ 'shovel',
+ 'silvers',
+ 'somaria',
+];
+
+export const INTEGER_STATES = [
+ 'bottle',
+ 'heart-piece',
+ 'lift',
+ 'mail',
+ 'shield',
+ 'sword',
+];
+
+export const INITIAL = {
+ mail: 1,
+};
+
+export const BOSSES = [
+ 'armos',
+ 'lanmolas',
+ 'moldorm',
+ 'helma',
+ 'arrghus',
+ 'mothula',
+ 'blind',
+ 'kholdstare',
+ 'vitreous',
+ 'trinexx',
+];
+
+export const CONFIG = {
+ wildMap: false,
+ wildCompass: false,
+ wildSmall: false,
+ wildBig: false,
+ bossShuffle: false,
+};
+
+export const DUNGEONS = [
+ {
+ id: 'hc',
+ map: true,
+ compass: false,
+ sk: 1,
+ bk: true,
+ dropBk: true,
+ items: 6,
+ boss: null,
+ bosses: [],
+ bossRoom: 0x80,
+ prize: false,
+ offset: DUNGEON_IDS.HC,
+ mask: DUNGEON_MASKS.HC,
+ checks: [
+ 'dark-cross',
+ 'hc-map',
+ 'hc-boom',
+ 'hc-cell',
+ 'sanc',
+ 'sewers-left',
+ 'sewers-mid',
+ 'sewers-right',
+ ],
+ },
+ {
+ id: 'ct',
+ map: false,
+ compass: false,
+ sk: 2,
+ bk: false,
+ items: 0,
+ boss: 'aga',
+ bosses: ['aga'],
+ bossRoom: 0x20,
+ prize: false,
+ offset: DUNGEON_IDS.CT,
+ mask: DUNGEON_MASKS.CT,
+ checks: [
+ 'ct-1',
+ 'ct-2',
+ ],
+ },
+ {
+ id: 'gt',
+ map: true,
+ compass: true,
+ sk: 4,
+ bk: true,
+ items: 20,
+ boss: 'aga',
+ bosses: ['aga'],
+ bossRoom: 0x0D,
+ prize: false,
+ offset: DUNGEON_IDS.GT,
+ mask: DUNGEON_MASKS.GT,
+ checks: [
+ 'gt-hope-left',
+ 'gt-hope-right',
+ 'gt-tile-room',
+ 'gt-compass-tl',
+ 'gt-compass-tr',
+ 'gt-compass-bl',
+ 'gt-compass-br',
+ 'gt-torch',
+ 'gt-dm-tl',
+ 'gt-dm-tr',
+ 'gt-dm-bl',
+ 'gt-dm-br',
+ 'gt-map-chest',
+ 'gt-firesnake',
+ 'gt-rando-tl',
+ 'gt-rando-tr',
+ 'gt-rando-bl',
+ 'gt-rando-br',
+ 'gt-bobs-chest',
+ 'gt-ice-left',
+ 'gt-ice-mid',
+ 'gt-ice-right',
+ 'gt-big-chest',
+ 'gt-helma-left',
+ 'gt-helma-right',
+ 'gt-pre-moldorm',
+ 'gt-post-moldorm',
+ ],
+ },
+ {
+ id: 'ep',
+ map: true,
+ compass: true,
+ sk: 0,
+ bk: true,
+ items: 3,
+ boss: 'armos',
+ bosses: ['armos'],
+ bossRoom: 0xC8,
+ prize: true,
+ isPendant: true,
+ prizeMask: 0x04,
+ offset: DUNGEON_IDS.EP,
+ mask: DUNGEON_MASKS.EP,
+ checks: [
+ 'ep-cannonball',
+ 'ep-map-chest',
+ 'ep-compass-chest',
+ 'ep-big-chest',
+ 'ep-big-key-chest',
+ 'ep-boss-defeated',
+ ],
+ },
+ {
+ id: 'dp',
+ map: true,
+ compass: true,
+ sk: 1,
+ bk: true,
+ items: 2,
+ boss: 'lanmolas',
+ bosses: ['lanmolas'],
+ bossRoom: 0x33,
+ prize: true,
+ isPendant: true,
+ prizeMask: 0x02,
+ offset: DUNGEON_IDS.DP,
+ mask: DUNGEON_MASKS.DP,
+ checks: [
+ 'dp-torch',
+ 'dp-map-chest',
+ 'dp-big-chest',
+ 'dp-compass-chest',
+ 'dp-big-key-chest',
+ 'dp-boss-defeated',
+ ],
+ },
+ {
+ id: 'th',
+ map: true,
+ compass: true,
+ sk: 1,
+ bk: true,
+ items: 2,
+ boss: 'moldorm',
+ bosses: ['moldorm'],
+ bossRoom: 0x07,
+ prize: true,
+ isPendant: true,
+ prizeMask: 0x01,
+ offset: DUNGEON_IDS.TH,
+ mask: DUNGEON_MASKS.TH,
+ checks: [
+ 'th-basement-cage',
+ 'th-map-chest',
+ 'th-big-key-chest',
+ 'th-compass-chest',
+ 'th-big-chest',
+ 'th-boss-defeated',
+ ],
+ },
+ {
+ id: 'pd',
+ map: true,
+ compass: true,
+ sk: 6,
+ bk: true,
+ items: 5,
+ boss: 'helma',
+ bosses: ['helma'],
+ bossRoom: 0x5A,
+ prize: true,
+ prizeMask: 0x02,
+ offset: DUNGEON_IDS.PD,
+ mask: DUNGEON_MASKS.PD,
+ checks: [
+ 'pd-shooter-room',
+ 'pd-stalfos-basement',
+ 'pd-big-key-chest',
+ 'pd-arena-bridge',
+ 'pd-arena-ledge',
+ 'pd-map-chest',
+ 'pd-compass-chest',
+ 'pd-basement-left',
+ 'pd-basement-right',
+ 'pd-harmless-hellway',
+ 'pd-maze-top',
+ 'pd-maze-bottom',
+ 'pd-big-chest',
+ 'pd-boss-defeated',
+ ],
+ },
+ {
+ id: 'sp',
+ map: true,
+ compass: true,
+ sk: 1,
+ bk: true,
+ items: 6,
+ boss: 'arrghus',
+ bosses: ['arrghus'],
+ bossRoom: 0x06,
+ prize: true,
+ prizeMask: 0x10,
+ offset: DUNGEON_IDS.SP,
+ mask: DUNGEON_MASKS.SP,
+ checks: [
+ 'sp-lobby',
+ 'sp-map-chest',
+ 'sp-big-chest',
+ 'sp-compass-chest',
+ 'sp-west-chest',
+ 'sp-big-key-chest',
+ 'sp-flooded-left',
+ 'sp-flooded-right',
+ 'sp-waterfall',
+ 'sp-boss-defeated',
+ ],
+ },
+ {
+ id: 'sw',
+ map: true,
+ compass: true,
+ sk: 3,
+ bk: true,
+ items: 2,
+ boss: 'mothula',
+ bosses: ['mothula'],
+ bossRoom: 0x29,
+ prize: true,
+ prizeMask: 0x40,
+ offset: DUNGEON_IDS.SW,
+ mask: DUNGEON_MASKS.SW,
+ checks: [
+ 'sw-big-chest',
+ 'sw-map-chest',
+ 'sw-pot-prison',
+ 'sw-compass-chest',
+ 'sw-pinball-room',
+ 'sw-big-key-chest',
+ 'sw-bridge-chest',
+ 'sw-boss-defeated',
+ ],
+ },
+ {
+ id: 'tt',
+ map: true,
+ compass: true,
+ sk: 1,
+ bk: true,
+ items: 4,
+ boss: 'blind',
+ bosses: ['blind'],
+ bossRoom: 0xAC,
+ prize: true,
+ prizeMask: 0x20,
+ offset: DUNGEON_IDS.TT,
+ mask: DUNGEON_MASKS.TT,
+ checks: [
+ 'tt-map-chest',
+ 'tt-ambush-chest',
+ 'tt-compass-chest',
+ 'tt-big-key-chest',
+ 'tt-attic',
+ 'tt-cell',
+ 'tt-big-chest',
+ 'tt-boss-defeated',
+ ],
+ },
+ {
+ id: 'ip',
+ map: true,
+ compass: true,
+ sk: 2,
+ bk: true,
+ items: 3,
+ boss: 'kholdstare',
+ bosses: ['kholdstare'],
+ bossRoom: 0xDE,
+ prize: true,
+ prizeMask: 0x04,
+ offset: DUNGEON_IDS.IP,
+ mask: DUNGEON_MASKS.IP,
+ checks: [
+ 'ip-compass-chest',
+ 'ip-big-key-chest',
+ 'ip-map-chest',
+ 'ip-spike-chest',
+ 'ip-freezor-chest',
+ 'ip-big-chest',
+ 'ip-ice-t',
+ 'ip-boss-defeated',
+ ],
+ },
+ {
+ id: 'mm',
+ map: true,
+ compass: true,
+ sk: 3,
+ bk: true,
+ items: 2,
+ boss: 'vitreous',
+ bosses: ['vitreous'],
+ bossRoom: 0x90,
+ prize: true,
+ prizeMask: 0x01,
+ offset: DUNGEON_IDS.MM,
+ mask: DUNGEON_MASKS.MM,
+ checks: [
+ 'mm-bridge-chest',
+ 'mm-spike-chest',
+ 'mm-lobby-chest',
+ 'mm-compass-chest',
+ 'mm-big-key-chest',
+ 'mm-big-chest',
+ 'mm-map-chest',
+ 'mm-boss-defeated',
+ ],
+ },
+ {
+ id: 'tr',
+ map: true,
+ compass: true,
+ sk: 4,
+ bk: true,
+ items: 5,
+ boss: 'trinexx',
+ bosses: ['trinexx'],
+ bossRoom: 0xA4,
+ prize: true,
+ prizeMask: 0x08,
+ offset: DUNGEON_IDS.TR,
+ mask: DUNGEON_MASKS.TR,
+ checks: [
+ 'tr-roller-left',
+ 'tr-roller-right',
+ 'tr-compass-chest',
+ 'tr-chomps',
+ 'tr-big-key-chest',
+ 'tr-big-chest',
+ 'tr-crysta-roller',
+ 'tr-laser-bridge-top',
+ 'tr-laser-bridge-left',
+ 'tr-laser-bridge-right',
+ 'tr-laser-bridge-bottom',
+ 'tr-boss-defeated',
+ ],
+ },
+];
+
+export const OVERWORLD_LOCATIONS = [
+ {
+ id: 'blacksmith',
+ address: 0x411,
+ mask: 0x04,
+ },
+ {
+ id: 'bombos-tablet',
+ address: 0x411,
+ mask: 0x02,
+ },
+ {
+ id: 'bottle-vendor',
+ address: 0x3C9,
+ mask: 0x02,
+ },
+ {
+ id: 'bumper-cave',
+ address: 0x2CA,
+ mask: 0x40,
+ },
+ {
+ id: 'catfish',
+ address: 0x410,
+ mask: 0x20,
+ },
+ {
+ id: 'desert-ledge',
+ address: 0x2B0,
+ mask: 0x40,
+ },
+ {
+ id: 'digging-game',
+ address: 0x2E8,
+ mask: 0x40,
+ },
+ {
+ id: 'ether-tablet',
+ address: 0x411,
+ mask: 0x01,
+ },
+ {
+ id: 'floating-island',
+ address: 0x285,
+ mask: 0x40,
+ },
+ {
+ id: 'flute-spot',
+ address: 0x2AA,
+ mask: 0x40,
+ },
+ {
+ id: 'hobo',
+ address: 0x3C9,
+ mask: 0x01,
+ },
+ {
+ id: 'lake-hylia-island',
+ address: 0x2B5,
+ mask: 0x40,
+ },
+ {
+ id: 'library',
+ address: 0x410,
+ mask: 0x80,
+ },
+ {
+ id: 'magic-bat',
+ address: 0x411,
+ mask: 0x80,
+ },
+ {
+ id: 'mushroom-spot',
+ address: 0x411,
+ mask: 0x10,
+ },
+ {
+ id: 'old-man',
+ address: 0x410,
+ mask: 0x01,
+ },
+ {
+ id: 'pedestal',
+ address: 0x300,
+ mask: 0x40,
+ },
+ {
+ id: 'potion-shop',
+ address: 0x411,
+ mask: 0x20,
+ },
+ {
+ id: 'purple-chest',
+ address: 0x3C9,
+ mask: 0x10,
+ },
+ {
+ id: 'pyramid',
+ address: 0x2DB,
+ mask: 0x40,
+ },
+ {
+ id: 'race-game',
+ address: 0x2A8,
+ mask: 0x40,
+ },
+ {
+ id: 'saha',
+ address: 0x410,
+ mask: 0x10,
+ },
+ {
+ id: 'sick-kid',
+ address: 0x410,
+ mask: 0x04,
+ },
+ {
+ id: 'spec-rock',
+ address: 0x283,
+ mask: 0x40,
+ },
+ {
+ id: 'stumpy',
+ address: 0x410,
+ mask: 0x08,
+ },
+ {
+ id: 'sunken-treasure',
+ address: 0x2BB,
+ mask: 0x40,
+ },
+ {
+ id: 'uncle',
+ address: 0x3C6,
+ mask: 0x01,
+ },
+ {
+ id: 'zora',
+ address: 0x410,
+ mask: 0x02,
+ },
+ {
+ id: 'zora-ledge',
+ address: 0x301,
+ mask: 0x40,
+ },
+];
+
+export const UNDERWORLD_LOCATIONS = [
+ {
+ id: 'aginah',
+ room: 0x10A,
+ chest: 0,
+ },
+ {
+ id: 'blinds-hut-top',
+ room: 0x11D,
+ chest: 0,
+ },
+ {
+ id: 'blinds-hut-left',
+ room: 0x11D,
+ chest: 1,
+ },
+ {
+ id: 'blinds-hut-right',
+ room: 0x11D,
+ chest: 2,
+ },
+ {
+ id: 'blinds-hut-far-left',
+ room: 0x11D,
+ chest: 3,
+ },
+ {
+ id: 'blinds-hut-far-right',
+ room: 0x11D,
+ chest: 4,
+ },
+ {
+ id: 'bonk-rocks',
+ room: 0x124,
+ chest: 0,
+ },
+ {
+ id: 'brewery',
+ room: 0x106,
+ chest: 0,
+ },
+ {
+ id: 'c-house',
+ room: 0x11C,
+ chest: 0,
+ },
+ {
+ id: 'cave-45',
+ room: 0x11B,
+ chest: 6,
+ },
+ {
+ id: 'checkerboard',
+ room: 0x126,
+ chest: 5,
+ },
+ {
+ id: 'chest-game',
+ room: 0x106,
+ chest: 6,
+ },
+ {
+ id: 'chicken-house',
+ room: 0x108,
+ chest: 0,
+ },
+ {
+ id: 'ct-1',
+ area: 'ct',
+ room: 0xE0,
+ chest: 0,
+ },
+ {
+ id: 'ct-2',
+ area: 'ct',
+ room: 0xD0,
+ chest: 0,
+ },
+ {
+ id: 'dark-cross',
+ area: 'hc',
+ room: 0x32,
+ chest: 0,
+ },
+ {
+ id: 'dp-big-chest',
+ area: 'dp',
+ room: 0x73,
+ chest: 0,
+ },
+ {
+ id: 'dp-big-key-chest',
+ area: 'dp',
+ room: 0x75,
+ chest: 0,
+ },
+ {
+ id: 'dp-compass-chest',
+ area: 'dp',
+ room: 0x85,
+ chest: 0,
+ },
+ {
+ id: 'dp-map-chest',
+ area: 'dp',
+ room: 0x74,
+ chest: 0,
+ },
+ {
+ id: 'dp-torch',
+ area: 'dp',
+ room: 0x73,
+ chest: 6,
+ },
+ {
+ id: 'ep-big-chest',
+ area: 'ep',
+ room: 0xA9,
+ chest: 0,
+ },
+ {
+ id: 'ep-big-key-chest',
+ area: 'ep',
+ room: 0xB8,
+ chest: 0,
+ },
+ {
+ id: 'ep-cannonball',
+ area: 'ep',
+ room: 0xB9,
+ chest: 0,
+ },
+ {
+ id: 'ep-compass-chest',
+ area: 'ep',
+ room: 0xA8,
+ chest: 0,
+ },
+ {
+ id: 'ep-map-chest',
+ area: 'ep',
+ room: 0xAA,
+ chest: 0,
+ },
+ {
+ id: 'flooded-chest',
+ room: 0x10B,
+ chest: 0,
+ },
+ {
+ id: 'graveyard-ledge',
+ room: 0x11B,
+ chest: 5,
+ },
+ {
+ id: 'gt-hope-left',
+ area: 'gt',
+ room: 0x8C,
+ chest: 1,
+ },
+ {
+ id: 'gt-hope-right',
+ area: 'gt',
+ room: 0x8C,
+ chest: 2,
+ },
+ {
+ id: 'gt-tile-room',
+ area: 'gt',
+ room: 0x8D,
+ chest: 0,
+ },
+ {
+ id: 'gt-compass-tl',
+ area: 'gt',
+ room: 0x9D,
+ chest: 0,
+ },
+ {
+ id: 'gt-compass-tr',
+ area: 'gt',
+ room: 0x9D,
+ chest: 1,
+ },
+ {
+ id: 'gt-compass-bl',
+ area: 'gt',
+ room: 0x9D,
+ chest: 2,
+ },
+ {
+ id: 'gt-compass-br',
+ area: 'gt',
+ room: 0x9D,
+ chest: 3,
+ },
+ {
+ id: 'gt-torch',
+ area: 'gt',
+ room: 0x8C,
+ chest: 6,
+ },
+ {
+ id: 'gt-dm-tl',
+ area: 'gt',
+ room: 0x7B,
+ chest: 0,
+ },
+ {
+ id: 'gt-dm-tr',
+ area: 'gt',
+ room: 0x7B,
+ chest: 1,
+ },
+ {
+ id: 'gt-dm-bl',
+ area: 'gt',
+ room: 0x7B,
+ chest: 2,
+ },
+ {
+ id: 'gt-dm-br',
+ area: 'gt',
+ room: 0x7B,
+ chest: 3,
+ },
+ {
+ id: 'gt-map-chest',
+ area: 'gt',
+ room: 0x8B,
+ chest: 0,
+ },
+ {
+ id: 'gt-firesnake',
+ area: 'gt',
+ room: 0x7D,
+ chest: 0,
+ },
+ {
+ id: 'gt-rando-tl',
+ area: 'gt',
+ room: 0x7C,
+ chest: 0,
+ },
+ {
+ id: 'gt-rando-tr',
+ area: 'gt',
+ room: 0x7C,
+ chest: 1,
+ },
+ {
+ id: 'gt-rando-bl',
+ area: 'gt',
+ room: 0x7C,
+ chest: 2,
+ },
+ {
+ id: 'gt-rando-br',
+ area: 'gt',
+ room: 0x7C,
+ chest: 3,
+ },
+ {
+ id: 'gt-bobs-chest',
+ area: 'gt',
+ room: 0x8C,
+ chest: 3,
+ },
+ {
+ id: 'gt-ice-left',
+ area: 'gt',
+ room: 0x1C,
+ chest: 1,
+ },
+ {
+ id: 'gt-ice-mid',
+ area: 'gt',
+ room: 0x1C,
+ chest: 0,
+ },
+ {
+ id: 'gt-ice-right',
+ area: 'gt',
+ room: 0x1C,
+ chest: 2,
+ },
+ {
+ id: 'gt-big-chest',
+ area: 'gt',
+ room: 0x8C,
+ chest: 0,
+ },
+ {
+ id: 'gt-helma-left',
+ area: 'gt',
+ room: 0x3D,
+ chest: 0,
+ },
+ {
+ id: 'gt-helma-right',
+ area: 'gt',
+ room: 0x3D,
+ chest: 1,
+ },
+ {
+ id: 'gt-pre-moldorm',
+ area: 'gt',
+ room: 0x3D,
+ chest: 2,
+ },
+ {
+ id: 'gt-post-moldorm',
+ area: 'gt',
+ room: 0x4D,
+ chest: 0,
+ },
+ {
+ id: 'hammer-pegs',
+ room: 0x127,
+ chest: 6,
+ },
+ {
+ id: 'hc-boom',
+ area: 'hc',
+ room: 0x71,
+ chest: 0,
+ },
+ {
+ id: 'hc-cell',
+ area: 'hc',
+ room: 0x80,
+ chest: 0,
+ },
+ {
+ id: 'hc-map',
+ area: 'hc',
+ room: 0x72,
+ chest: 0,
+ },
+ {
+ id: 'hookshot-cave-br',
+ room: 0x3C,
+ chest: 3,
+ },
+ {
+ id: 'hookshot-cave-tr',
+ room: 0x3C,
+ chest: 0,
+ },
+ {
+ id: 'hookshot-cave-tl',
+ room: 0x3C,
+ chest: 1,
+ },
+ {
+ id: 'hookshot-cave-bl',
+ room: 0x3C,
+ chest: 2,
+ },
+ {
+ id: 'hype-cave-top',
+ room: 0x11E,
+ chest: 0,
+ },
+ {
+ id: 'hype-cave-left',
+ room: 0x11E,
+ chest: 1,
+ },
+ {
+ id: 'hype-cave-right',
+ room: 0x11E,
+ chest: 2,
+ },
+ {
+ id: 'hype-cave-bottom',
+ room: 0x11E,
+ chest: 4,
+ },
+ {
+ id: 'hype-cave-npc',
+ room: 0x11E,
+ chest: 6,
+ },
+ {
+ id: 'ice-rod-cave',
+ room: 0x120,
+ chest: 0,
+ },
+ {
+ id: 'ip-compass-chest',
+ area: 'ip',
+ room: 0x2E,
+ chest: 0,
+ },
+ {
+ id: 'ip-big-key-chest',
+ area: 'ip',
+ room: 0x1F,
+ chest: 0,
+ },
+ {
+ id: 'ip-map-chest',
+ area: 'ip',
+ room: 0x3F,
+ chest: 0,
+ },
+ {
+ id: 'ip-spike-chest',
+ area: 'ip',
+ room: 0x5F,
+ chest: 0,
+ },
+ {
+ id: 'ip-freezor-chest',
+ area: 'ip',
+ room: 0x7E,
+ chest: 0,
+ },
+ {
+ id: 'ip-big-chest',
+ area: 'ip',
+ room: 0x9E,
+ chest: 0,
+ },
+ {
+ id: 'ip-ice-t',
+ area: 'ip',
+ room: 0xAE,
+ chest: 0,
+ },
+ {
+ id: 'kak-well-top',
+ room: 0x2F,
+ chest: 0,
+ },
+ {
+ id: 'kak-well-left',
+ room: 0x2F,
+ chest: 1,
+ },
+ {
+ id: 'kak-well-mid',
+ room: 0x2F,
+ chest: 2,
+ },
+ {
+ id: 'kak-well-right',
+ room: 0x2F,
+ chest: 3,
+ },
+ {
+ id: 'kak-well-bottom',
+ room: 0x2F,
+ chest: 4,
+ },
+ {
+ id: 'kings-tomb',
+ room: 0x113,
+ chest: 0,
+ },
+ {
+ id: 'links-house',
+ room: 0x104,
+ chest: 0,
+ },
+ {
+ id: 'lost-woods-hideout',
+ room: 0xE1,
+ chest: 5,
+ },
+ {
+ id: 'lumberjack',
+ room: 0xE2,
+ chest: 5,
+ },
+ {
+ id: 'mimic-cave',
+ room: 0x10C,
+ chest: 0,
+ },
+ {
+ id: 'mini-moldorm-far-left',
+ room: 0x123,
+ chest: 0,
+ },
+ {
+ id: 'mini-moldorm-left',
+ room: 0x123,
+ chest: 1,
+ },
+ {
+ id: 'mini-moldorm-right',
+ room: 0x123,
+ chest: 2,
+ },
+ {
+ id: 'mini-moldorm-far-right',
+ room: 0x123,
+ chest: 3,
+ },
+ {
+ id: 'mini-moldorm-npc',
+ room: 0x123,
+ chest: 6,
+ },
+ {
+ id: 'mm-bridge-chest',
+ room: 0xA2,
+ chest: 0,
+ },
+ {
+ id: 'mm-spike-chest',
+ room: 0xB3,
+ chest: 0,
+ },
+ {
+ id: 'mm-lobby-chest',
+ room: 0xC2,
+ chest: 0,
+ },
+ {
+ id: 'mm-compass-chest',
+ room: 0xC1,
+ chest: 0,
+ },
+ {
+ id: 'mm-big-key-chest',
+ room: 0xD1,
+ chest: 0,
+ },
+ {
+ id: 'mm-big-chest',
+ room: 0xC3,
+ chest: 0,
+ },
+ {
+ id: 'mm-map-chest',
+ room: 0xC3,
+ chest: 1,
+ },
+ {
+ id: 'mire-shed-left',
+ room: 0x10D,
+ chest: 0,
+ },
+ {
+ id: 'mire-shed-right',
+ room: 0x10D,
+ chest: 1,
+ },
+ {
+ id: 'paradox-lower-far-left',
+ room: 0xEF,
+ chest: 0,
+ },
+ {
+ id: 'paradox-lower-left',
+ room: 0xEF,
+ chest: 1,
+ },
+ {
+ id: 'paradox-lower-right',
+ room: 0xEF,
+ chest: 2,
+ },
+ {
+ id: 'paradox-lower-far-right',
+ room: 0xEF,
+ chest: 4,
+ },
+ {
+ id: 'paradox-lower-mid',
+ room: 0xEF,
+ chest: 5,
+ },
+ {
+ id: 'paradox-upper-left',
+ room: 0xFF,
+ chest: 0,
+ },
+ {
+ id: 'paradox-upper-right',
+ room: 0xFF,
+ chest: 1,
+ },
+ {
+ id: 'pd-shooter-room',
+ room: 0x09,
+ chest: 0,
+ },
+ {
+ id: 'pd-stalfos-basement',
+ room: 0x0A,
+ chest: 0,
+ },
+ {
+ id: 'pd-big-key-chest',
+ room: 0x3A,
+ chest: 0,
+ },
+ {
+ id: 'pd-arena-bridge',
+ room: 0x2A,
+ chest: 1,
+ },
+ {
+ id: 'pd-arena-ledge',
+ room: 0x2A,
+ chest: 0,
+ },
+ {
+ id: 'pd-map-chest',
+ room: 0x2B,
+ chest: 0,
+ },
+ {
+ id: 'pd-big-chest',
+ room: 0x1A,
+ chest: 0,
+ },
+ {
+ id: 'pd-compass-chest',
+ room: 0x1A,
+ chest: 1,
+ },
+ {
+ id: 'pd-harmless-hellway',
+ room: 0x1A,
+ chest: 2,
+ },
+ {
+ id: 'pd-maze-top',
+ room: 0x19,
+ chest: 0,
+ },
+ {
+ id: 'pd-maze-bottom',
+ room: 0x19,
+ chest: 1,
+ },
+ {
+ id: 'pd-basement-left',
+ room: 0x6A,
+ chest: 0,
+ },
+ {
+ id: 'pd-basement-right',
+ room: 0x6A,
+ chest: 1,
+ },
+ {
+ id: 'pyramid-fairy-left',
+ room: 0x116,
+ chest: 0,
+ },
+ {
+ id: 'pyramid-fairy-right',
+ room: 0x116,
+ chest: 1,
+ },
+ {
+ id: 'saha-left',
+ room: 0x105,
+ chest: 0,
+ },
+ {
+ id: 'saha-mid',
+ room: 0x105,
+ chest: 1,
+ },
+ {
+ id: 'saha-right',
+ room: 0x105,
+ chest: 2,
+ },
+ {
+ id: 'sanc',
+ area: 'hc',
+ room: 0x12,
+ chest: 0,
+ },
+ {
+ id: 'secret-passage',
+ room: 0x55,
+ chest: 0,
+ },
+ {
+ id: 'sewers-left',
+ area: 'hc',
+ room: 0x11,
+ chest: 0,
+ },
+ {
+ id: 'sewers-mid',
+ area: 'hc',
+ room: 0x11,
+ chest: 1,
+ },
+ {
+ id: 'sewers-right',
+ area: 'hc',
+ room: 0x11,
+ chest: 2,
+ },
+ {
+ id: 'sp-lobby',
+ area: 'sp',
+ room: 0x28,
+ chest: 0,
+ },
+ {
+ id: 'sp-map-chest',
+ area: 'sp',
+ room: 0x37,
+ chest: 0,
+ },
+ {
+ id: 'sp-big-chest',
+ area: 'sp',
+ room: 0x36,
+ chest: 0,
+ },
+ {
+ id: 'sp-compass-chest',
+ area: 'sp',
+ room: 0x46,
+ chest: 0,
+ },
+ {
+ id: 'sp-west-chest',
+ area: 'sp',
+ room: 0x34,
+ chest: 0,
+ },
+ {
+ id: 'sp-big-key-chest',
+ area: 'sp',
+ room: 0x35,
+ chest: 0,
+ },
+ {
+ id: 'sp-flooded-left',
+ area: 'sp',
+ room: 0x76,
+ chest: 0,
+ },
+ {
+ id: 'sp-flooded-right',
+ area: 'sp',
+ room: 0x76,
+ chest: 1,
+ },
+ {
+ id: 'sp-waterfall',
+ area: 'sp',
+ room: 0x66,
+ chest: 0,
+ },
+ {
+ id: 'spec-rock-cave',
+ room: 0xEA,
+ chest: 6,
+ },
+ {
+ id: 'spike-cave',
+ room: 0x117,
+ chest: 0,
+ },
+ {
+ id: 'spiral-cave',
+ room: 0xFE,
+ chest: 0,
+ },
+ {
+ id: 'super-bunny-top',
+ room: 0xF8,
+ chest: 0,
+ },
+ {
+ id: 'super-bunny-bottom',
+ room: 0xF8,
+ chest: 0,
+ },
+ {
+ id: 'sw-big-chest',
+ area: 'sw',
+ room: 0x58,
+ chest: 0,
+ },
+ {
+ id: 'sw-map-chest',
+ area: 'sw',
+ room: 0x58,
+ chest: 1,
+ },
+ {
+ id: 'sw-compass-chest',
+ area: 'sw',
+ room: 0x67,
+ chest: 0,
+ },
+ {
+ id: 'sw-big-key-chest',
+ area: 'sw',
+ room: 0x57,
+ chest: 0,
+ },
+ {
+ id: 'sw-pot-prison',
+ area: 'sw',
+ room: 0x57,
+ chest: 1,
+ },
+ {
+ id: 'sw-pinball-room',
+ area: 'sw',
+ room: 0x68,
+ chest: 0,
+ },
+ {
+ id: 'sw-bridge-chest',
+ area: 'sw',
+ room: 0x59,
+ chest: 0,
+ },
+ {
+ id: 'tavern',
+ room: 0x103,
+ chest: 0,
+ },
+ {
+ id: 'th-basement-cage',
+ area: 'th',
+ room: 0x87,
+ chest: 6,
+ },
+ {
+ id: 'th-big-key-chest',
+ area: 'th',
+ room: 0x87,
+ chest: 0,
+ },
+ {
+ id: 'th-map-chest',
+ area: 'th',
+ room: 0x77,
+ chest: 0,
+ },
+ {
+ id: 'th-big-chest',
+ area: 'th',
+ room: 0x27,
+ chest: 0,
+ },
+ {
+ id: 'th-compass-chest',
+ area: 'th',
+ room: 0x27,
+ chest: 1,
+ },
+ {
+ id: 'tr-roller-left',
+ area: 'tr',
+ room: 0xB7,
+ chest: 0,
+ },
+ {
+ id: 'tr-roller-right',
+ area: 'tr',
+ room: 0xB7,
+ chest: 1,
+ },
+ {
+ id: 'tr-compass-chest',
+ area: 'tr',
+ room: 0xD6,
+ chest: 0,
+ },
+ {
+ id: 'tr-chomps',
+ area: 'tr',
+ room: 0xB6,
+ chest: 0,
+ },
+ {
+ id: 'tr-big-key-chest',
+ area: 'tr',
+ room: 0x14,
+ chest: 0,
+ },
+ {
+ id: 'tr-big-chest',
+ area: 'tr',
+ room: 0x24,
+ chest: 0,
+ },
+ {
+ id: 'tr-crysta-roller',
+ area: 'tr',
+ room: 0x04,
+ chest: 0,
+ },
+ {
+ id: 'tr-laser-bridge-top',
+ area: 'tr',
+ room: 0xD5,
+ chest: 0,
+ },
+ {
+ id: 'tr-laser-bridge-left',
+ area: 'tr',
+ room: 0xD5,
+ chest: 1,
+ },
+ {
+ id: 'tr-laser-bridge-right',
+ area: 'tr',
+ room: 0xD5,
+ chest: 2,
+ },
+ {
+ id: 'tr-laser-bridge-bottom',
+ area: 'tr',
+ room: 0xD5,
+ chest: 3,
+ },
+ {
+ id: 'tt-map-chest',
+ area: 'tt',
+ room: 0xDB,
+ chest: 0,
+ },
+ {
+ id: 'tt-big-key-chest',
+ area: 'tt',
+ room: 0xDB,
+ chest: 1,
+ },
+ {
+ id: 'tt-ambush-chest',
+ area: 'tt',
+ room: 0xCB,
+ chest: 0,
+ },
+ {
+ id: 'tt-compass-chest',
+ area: 'tt',
+ room: 0xDC,
+ chest: 0,
+ },
+ {
+ id: 'tt-attic',
+ area: 'tt',
+ room: 0x65,
+ chest: 0,
+ },
+ {
+ id: 'tt-cell',
+ area: 'tt',
+ room: 0x45,
+ chest: 0,
+ },
+ {
+ id: 'tt-big-chest',
+ area: 'tt',
+ room: 0x44,
+ chest: 0,
+ },
+ {
+ id: 'waterfall-fairy-left',
+ room: 0x114,
+ chest: 4,
+ },
+ {
+ id: 'waterfall-fairy-right',
+ room: 0x114,
+ chest: 5,
+ },
+];
+
+export const toggleBoolean = name => state => ({
+ ...state,
+ [name]: !state[name],
+});
+
+export const increment = (name, max, skipZero) => state => {
+ let newValue = ((state[name] || 0) + 1) % (max + 1);
+ if (skipZero && !newValue) {
+ newValue = 1;
+ }
+ return {
+ ...state,
+ [name]: newValue,
+ };
+};
+
+export const decrement = (name, max, skipZero) => state => {
+ let newValue = ((state[name] || 0) + max) % (max + 1);
+ if (skipZero && !newValue) {
+ newValue = max;
+ }
+ return {
+ ...state,
+ [name]: newValue,
+ };
+};
+
+export const highestActive = (state, names) => {
+ for (let i = names.length; i >= 0; --i) {
+ if (state[names[i]]) {
+ return names[i];
+ }
+ }
+ return null;
+};
+
+export const hasDungeonBoss = (state, dungeon) => !!state[`${dungeon.id}-boss-defeated`];
+
+export const getDungeonBoss = (state, dungeon) =>
+ state[`${dungeon.id}-boss`] || dungeon.boss || null;
+
+export const hasDungeonPrize = (state, dungeon) => !!state[`${dungeon.id}-prize-acquired`];
+
+export const getDungeonPrize = (state, dungeon) => state[`${dungeon.id}-prize`] || null;
+
+export const makeEmptyState = () => {
+ const state = {};
+ BOOLEAN_STATES.forEach(p => {
+ state[p] = INITIAL[p] || false;
+ });
+ INTEGER_STATES.forEach(p => {
+ state[p] = INITIAL[p] || 0;
+ });
+ DUNGEONS.forEach(dungeon => {
+ state[`${dungeon.id}-map`] = false;
+ state[`${dungeon.id}-compass`] = false;
+ state[`${dungeon.id}-small-key`] = 0;
+ state[`${dungeon.id}-big-key`] = false;
+ state[`${dungeon.id}-checks`] = 0;
+ if (dungeon.boss) {
+ state[`${dungeon.id}-boss`] = dungeon.boss;
+ state[`${dungeon.id}-boss-defeated`] = false;
+ }
+ if (dungeon.prize) {
+ state[`${dungeon.id}-prize`] = 'crystal';
+ state[`${dungeon.id}-prize-acquired`] = false;
+ }
+ });
+ return state;
+};
+
+const collectInventory = (state, data, prizeMap) => {
+ state.bow = !!(data[INV_ADDR.RANDO_BOW] & 0x80);
+ state.silvers = (data[INV_ADDR.RANDO_BOW] & 0xC0) == 0xC0;
+ state['bowless-silvers'] = (data[INV_ADDR.RANDO_BOW] & 0xC0) == 0x40;
+ state['blue-boomerang'] = !!(data[INV_ADDR.RANDO_BOOM] & 0x40);
+ state['red-boomerang'] = !!(data[INV_ADDR.RANDO_BOOM] & 0x80);
+ state.hookshot = !!data[INV_ADDR.HOOK];
+ state.bomb = data[INV_ADDR.BOMB];
+ state.mushroom = !!(data[INV_ADDR.RANDO_POWDER] & 0x20);
+ state.powder = !!(data[INV_ADDR.RANDO_POWDER] & 0x10);
+ state['fire-rod'] = !!data[INV_ADDR.FROD];
+ state['ice-rod'] = !!data[INV_ADDR.IROD];
+ state.bombos = !!data[INV_ADDR.BOMBOS];
+ state.ether = !!data[INV_ADDR.ETHER];
+ state.quake = !!data[INV_ADDR.QUAKE];
+ state.lamp = !!data[INV_ADDR.LAMP];
+ state.hammer = !!data[INV_ADDR.HAMMER];
+ state.shovel = !!(data[INV_ADDR.RANDO_FLUTE] & 0x04);
+ state.flute = !!(data[INV_ADDR.RANDO_FLUTE] & 0x03);
+ state.duck = !!(data[INV_ADDR.RANDO_FLUTE] & 0x01);
+ state.bugnet = !!data[INV_ADDR.BUGNET];
+ state.book = !!data[INV_ADDR.BOOK];
+ state.bottle = 0;
+ if (data[INV_ADDR.BOTTLE_1]) {
+ ++state.bottle;
+ }
+ if (data[INV_ADDR.BOTTLE_2]) {
+ ++state.bottle;
+ }
+ if (data[INV_ADDR.BOTTLE_3]) {
+ ++state.bottle;
+ }
+ if (data[INV_ADDR.BOTTLE_4]) {
+ ++state.bottle;
+ }
+ state.somaria = !!data[INV_ADDR.SOMARIA];
+ state.byrna = !!data[INV_ADDR.BYRNA];
+ state.cape = !!data[INV_ADDR.CAPE];
+ state.mirror = !!data[INV_ADDR.MIRROR];
+ state.lift = data[INV_ADDR.GLOVE];
+ state.boots = !!data[INV_ADDR.BOOTS];
+ state.flippers = !!data[INV_ADDR.FLIPPERS];
+ state.moonpearl = !!data[INV_ADDR.MOONPEARL];
+ state.sword = data[INV_ADDR.SWORD];
+ state.shield = data[INV_ADDR.SHIELD];
+ state.mail = data[INV_ADDR.ARMOR] + 1;
+ state['heart-piece'] = data[INV_ADDR.HEART_PIECE];
+ state['half-magic'] = data[INV_ADDR.MAGIC_USE] > 0;
+ state['quarter-magic'] = data[INV_ADDR.MAGIC_USE] > 1;
+ const map = getShort(data, INV_ADDR.MAP);
+ const compass = getShort(data, INV_ADDR.COMPASS);
+ const bigKey = getShort(data, INV_ADDR.BIG_KEY);
+ DUNGEONS.forEach(dungeon => {
+ state[`${dungeon.id}-map`] = !!(map & dungeon.mask);
+ state[`${dungeon.id}-compass`] = !!(compass & dungeon.mask);
+ state[`${dungeon.id}-small-key`] = data[INV_ADDR.RANDO_KEY_START + dungeon.offset];
+ state[`${dungeon.id}-big-key`] = !!(bigKey & dungeon.mask);
+ if (dungeon.prize) {
+ const isCrystal = prizeMap[dungeon.offset].isCrystal;
+ const prizeFlags = data[isCrystal ? INV_ADDR.CRYSTALS : INV_ADDR.PENDANTS];
+ state[`${dungeon.id}-prize-acquired`] = !!(prizeFlags & prizeMap[dungeon.offset].mask);
+ }
+ });
+};
+
+const collectOverworld = (state, data) => {
+ OVERWORLD_LOCATIONS.forEach(location => {
+ state[location.id] = !!(data[location.address] & location.mask);
+ });
+};
+
+const collectUnderworld = (state, data) => {
+ UNDERWORLD_LOCATIONS.forEach(location => {
+ state[location.id] = isChestOpen(data, location.room, location.chest);
+ });
+ DUNGEONS.forEach(dungeon => {
+ state[`${dungeon.id}-boss-defeated`] = isBossDefeated(data, dungeon.bossRoom);
+ });
+};
+
+export const computeState = (data, prizeMap) => {
+ const state = {};
+ collectInventory(state, data.slice(SRAM_ADDR.INV_START), prizeMap);
+ collectOverworld(state, data);
+ collectUnderworld(state, data.slice(SRAM_ADDR.ROOM_DATA_START));
+ return state;
+};
+
+const getDungeonAmounts = (config, state) => {
+ const amounts = {};
+ DUNGEONS.forEach(dungeon => {
+ let amount = 0;
+ let total = dungeon.checks.length;
+ dungeon.checks.forEach(check => {
+ if (state[check]) {
+ ++amount;
+ }
+ });
+ if (!config.wildMap && state[`${dungeon.id}-map`]) {
+ --amount;
+ --total;
+ }
+ if (!config.wildCompass && state[`${dungeon.id}-compass`]) {
+ --amount;
+ --total;
+ }
+ if (!config.wildSmall) {
+ amount -= Math.min(state[`${dungeon.id}-small-key`], dungeon.sk);
+ total -= dungeon.sk;
+ }
+ if (!config.wildBig && !dungeon.dropBk && state[`${dungeon.id}-big-key`]) {
+ --amount;
+ --total;
+ }
+ amounts[dungeon.id] = Math.min(total, amount);
+ });
+ return amounts;
+};
+
+export const mergeStates = (config, cur, inc) => {
+ const next = { ...cur, ...inc };
+ const amounts = getDungeonAmounts(config, inc);
+ DUNGEONS.forEach(dungeon => {
+ next[`${dungeon.id}-checks`] = amounts[dungeon.id];
+ });
+ //console.log(next);
+ return next;
+};