11 export const BOOLEAN_STATES = [
44 export const INTEGER_STATES = [
53 export const INITIAL = {
57 export const BOSSES = [
70 export const CONFIG = {
71 showMap: 'situational',
72 showCompass: 'situational',
82 export const DUNGEONS = [
95 offset: DUNGEON_IDS.HC,
96 mask: DUNGEON_MASKS.HC,
119 offset: DUNGEON_IDS.CT,
120 mask: DUNGEON_MASKS.CT,
137 offset: DUNGEON_IDS.GT,
138 mask: DUNGEON_MASKS.GT,
182 offset: DUNGEON_IDS.EP,
183 mask: DUNGEON_MASKS.EP,
201 bosses: ['lanmolas'],
206 offset: DUNGEON_IDS.DP,
207 mask: DUNGEON_MASKS.DP,
230 offset: DUNGEON_IDS.TH,
231 mask: DUNGEON_MASKS.TH,
253 offset: DUNGEON_IDS.PD,
254 mask: DUNGEON_MASKS.PD,
257 'pd-stalfos-basement',
265 'pd-harmless-hellway',
284 offset: DUNGEON_IDS.SP,
285 mask: DUNGEON_MASKS.SP,
311 offset: DUNGEON_IDS.SW,
312 mask: DUNGEON_MASKS.SW,
336 offset: DUNGEON_IDS.TT,
337 mask: DUNGEON_MASKS.TT,
357 bosses: ['kholdstare'],
361 offset: DUNGEON_IDS.IP,
362 mask: DUNGEON_MASKS.IP,
382 bosses: ['vitreous'],
386 offset: DUNGEON_IDS.MM,
387 mask: DUNGEON_MASKS.MM,
411 offset: DUNGEON_IDS.TR,
412 mask: DUNGEON_MASKS.TR,
421 'tr-laser-bridge-top',
422 'tr-laser-bridge-left',
423 'tr-laser-bridge-right',
424 'tr-laser-bridge-bottom',
430 export const OVERWORLD_LOCATIONS = [
472 id: 'floating-island',
487 id: 'lake-hylia-island',
557 id: 'sunken-treasure',
578 export const UNDERWORLD_LOCATIONS = [
585 id: 'blinds-hut-top',
590 id: 'blinds-hut-left',
595 id: 'blinds-hut-right',
600 id: 'blinds-hut-far-left',
605 id: 'blinds-hut-far-right',
669 id: 'dp-big-key-chest',
675 id: 'dp-compass-chest',
699 id: 'ep-big-key-chest',
711 id: 'ep-compass-chest',
728 id: 'graveyard-ledge',
877 id: 'gt-helma-right',
883 id: 'gt-pre-moldorm',
889 id: 'gt-post-moldorm',
918 id: 'hookshot-cave-br',
923 id: 'hookshot-cave-tr',
928 id: 'hookshot-cave-tl',
933 id: 'hookshot-cave-bl',
943 id: 'hype-cave-left',
948 id: 'hype-cave-right',
953 id: 'hype-cave-bottom',
968 id: 'ip-compass-chest',
974 id: 'ip-big-key-chest',
986 id: 'ip-spike-chest',
992 id: 'ip-freezor-chest',
1015 id: 'kak-well-left',
1025 id: 'kak-well-right',
1030 id: 'kak-well-bottom',
1045 id: 'lost-woods-hideout',
1060 id: 'mini-moldorm-far-left',
1065 id: 'mini-moldorm-left',
1070 id: 'mini-moldorm-right',
1075 id: 'mini-moldorm-far-right',
1080 id: 'mini-moldorm-npc',
1085 id: 'mm-bridge-chest',
1090 id: 'mm-spike-chest',
1095 id: 'mm-lobby-chest',
1100 id: 'mm-compass-chest',
1105 id: 'mm-big-key-chest',
1120 id: 'mire-shed-left',
1125 id: 'mire-shed-right',
1130 id: 'paradox-lower-far-left',
1135 id: 'paradox-lower-left',
1140 id: 'paradox-lower-right',
1145 id: 'paradox-lower-far-right',
1150 id: 'paradox-lower-mid',
1155 id: 'paradox-upper-left',
1160 id: 'paradox-upper-right',
1165 id: 'pd-shooter-room',
1170 id: 'pd-stalfos-basement',
1175 id: 'pd-big-key-chest',
1180 id: 'pd-arena-bridge',
1185 id: 'pd-arena-ledge',
1200 id: 'pd-compass-chest',
1205 id: 'pd-harmless-hellway',
1215 id: 'pd-maze-bottom',
1220 id: 'pd-basement-left',
1225 id: 'pd-basement-right',
1230 id: 'pyramid-fairy-left',
1235 id: 'pyramid-fairy-right',
1261 id: 'secret-passage',
1302 id: 'sp-compass-chest',
1308 id: 'sp-west-chest',
1314 id: 'sp-big-key-chest',
1320 id: 'sp-flooded-left',
1326 id: 'sp-flooded-right',
1338 id: 'spec-rock-cave',
1353 id: 'super-bunny-top',
1358 id: 'super-bunny-bottom',
1375 id: 'sw-compass-chest',
1381 id: 'sw-big-key-chest',
1387 id: 'sw-pot-prison',
1393 id: 'sw-pinball-room',
1399 id: 'sw-bridge-chest',
1410 id: 'th-basement-cage',
1416 id: 'th-big-key-chest',
1434 id: 'th-compass-chest',
1440 id: 'tr-roller-left',
1446 id: 'tr-roller-right',
1452 id: 'tr-compass-chest',
1464 id: 'tr-big-key-chest',
1476 id: 'tr-crysta-roller',
1482 id: 'tr-laser-bridge-top',
1488 id: 'tr-laser-bridge-left',
1494 id: 'tr-laser-bridge-right',
1500 id: 'tr-laser-bridge-bottom',
1512 id: 'tt-big-key-chest',
1518 id: 'tt-ambush-chest',
1524 id: 'tt-compass-chest',
1548 id: 'waterfall-fairy-left',
1553 id: 'waterfall-fairy-right',
1559 export const shouldShowDungeonItem = (config, which) => {
1560 const show = config[`show${which}`] || 'always';
1561 const wild = config[`wild${which}`] || false;
1573 export const toggleBoolean = name => state => ({
1575 [name]: !state[name],
1578 export const increment = (name, max, skipZero) => state => {
1579 let newValue = ((state[name] || 0) + 1) % (max + 1);
1580 if (skipZero && !newValue) {
1589 export const decrement = (name, max, skipZero) => state => {
1590 let newValue = ((state[name] || 0) + max) % (max + 1);
1591 if (skipZero && !newValue) {
1600 export const highestActive = (state, names) => {
1601 for (let i = names.length; i >= 0; --i) {
1602 if (state[names[i]]) {
1609 export const clearAll = names => state => {
1610 const changes = names.reduce((acc, cur) => ({ ...acc, [cur]: true }), {});
1611 return { ...state, ...changes };
1614 export const unclearAll = names => state => {
1615 const changes = names.reduce((acc, cur) => ({ ...acc, [cur]: false }), {});
1616 return { ...state, ...changes };
1619 export const countClearedLocations = (state, locations) =>
1620 locations.reduce((acc, cur) => state[cur] ? acc + 1 : acc, 0);
1622 export const hasClearedLocations = (state, locations) =>
1623 countClearedLocations(state, locations) === locations.length;
1625 export const countRemainingLocations = (state, locations) =>
1626 locations.reduce((acc, cur) => state[cur] ? acc : acc + 1, 0);
1628 export const hasDungeonBoss = (state, dungeon) =>
1629 !dungeon.boss || !!state[`${dungeon.id}-boss-defeated`];
1631 export const getDungeonBoss = (state, dungeon) =>
1632 state[`${dungeon.id}-boss`] || dungeon.boss || null;
1634 export const hasDungeonPrize = (state, dungeon) =>
1635 !dungeon.prize || !!state[`${dungeon.id}-prize-acquired`];
1637 export const getDungeonPrize = (state, dungeon) => state[`${dungeon.id}-prize`] || null;
1639 export const getDungeonClearedItems = (state, dungeon) => state[`${dungeon.id}-checks`] || 0;
1641 export const getDungeonRemainingItems = (state, dungeon) =>
1642 Math.max(0, dungeon.items - getDungeonClearedItems(state, dungeon));
1644 export const getDungeonAcquiredSKs = (state, dungeon) => state[`${dungeon.id}-small-key`] || 0;
1646 export const isDungeonCleared = (state, dungeon) => {
1647 const hasItems = !getDungeonRemainingItems(state, dungeon);
1648 const hasBoss = hasDungeonBoss(state, dungeon);
1649 const hasPrize = hasDungeonPrize(state, dungeon);
1650 return hasItems && hasBoss && hasPrize;
1653 export const toggleBossDefeated = dungeon => toggleBoolean(`${dungeon.id}-boss-defeated`);
1655 export const setBossDefeated = (dungeon, defeated) =>
1656 state => ({ ...state, [`${dungeon.id}-boss-defeated`]: !!defeated });
1658 export const togglePrizeAcquired = dungeon => toggleBoolean(`${dungeon.id}-prize-acquired`);
1660 export const setPrizeAcquired = (dungeon, acquired) =>
1661 state => ({ ...state, [`${dungeon.id}-prize-acquired`]: !!acquired });
1663 export const addDungeonCheck = dungeon => increment(`${dungeon.id}-checks`, dungeon.items);
1665 export const removeDungeonCheck = dungeon => decrement(`${dungeon.id}-checks`, dungeon.items);
1667 export const resetDungeonChecks = dungeon => state => ({ ...state, [`${dungeon.id}-checks`]: 0 });
1669 export const completeDungeonChecks = dungeon =>
1670 state => ({ ...state, [`${dungeon.id}-checks`]: dungeon.items });
1672 export const makeEmptyState = () => {
1674 BOOLEAN_STATES.forEach(p => {
1675 state[p] = INITIAL[p] || false;
1677 INTEGER_STATES.forEach(p => {
1678 state[p] = INITIAL[p] || 0;
1680 DUNGEONS.forEach(dungeon => {
1681 state[`${dungeon.id}-map`] = false;
1682 state[`${dungeon.id}-compass`] = false;
1683 state[`${dungeon.id}-small-key`] = 0;
1684 state[`${dungeon.id}-big-key`] = false;
1685 state[`${dungeon.id}-checks`] = 0;
1687 state[`${dungeon.id}-boss`] = dungeon.boss;
1688 state[`${dungeon.id}-boss-defeated`] = false;
1690 if (dungeon.prize) {
1691 state[`${dungeon.id}-prize`] = 'crystal';
1692 state[`${dungeon.id}-prize-acquired`] = false;
1695 OVERWORLD_LOCATIONS.forEach(location => {
1696 state[location.id] = false;
1698 UNDERWORLD_LOCATIONS.forEach(location => {
1699 state[location.id] = false;
1701 state['mm-medallion'] = null;
1702 state['tr-medallion'] = null;
1706 const collectInventory = (state, data, prizeMap) => {
1707 state.bow = !!(data[INV_ADDR.RANDO_BOW] & 0x80);
1708 state.silvers = (data[INV_ADDR.RANDO_BOW] & 0xC0) == 0xC0;
1709 state['bowless-silvers'] = (data[INV_ADDR.RANDO_BOW] & 0xC0) == 0x40;
1710 state['blue-boomerang'] = !!(data[INV_ADDR.RANDO_BOOM] & 0x40);
1711 state['red-boomerang'] = !!(data[INV_ADDR.RANDO_BOOM] & 0x80);
1712 state.hookshot = !!data[INV_ADDR.HOOK];
1713 state.bomb = data[INV_ADDR.BOMB];
1714 state.mushroom = !!(data[INV_ADDR.RANDO_POWDER] & 0x20);
1715 state.powder = !!(data[INV_ADDR.RANDO_POWDER] & 0x10);
1716 state['fire-rod'] = !!data[INV_ADDR.FROD];
1717 state['ice-rod'] = !!data[INV_ADDR.IROD];
1718 state.bombos = !!data[INV_ADDR.BOMBOS];
1719 state.ether = !!data[INV_ADDR.ETHER];
1720 state.quake = !!data[INV_ADDR.QUAKE];
1721 state.lamp = !!data[INV_ADDR.LAMP];
1722 state.hammer = !!data[INV_ADDR.HAMMER];
1723 state.shovel = !!(data[INV_ADDR.RANDO_FLUTE] & 0x04);
1724 state.flute = !!(data[INV_ADDR.RANDO_FLUTE] & 0x03);
1725 state.duck = !!(data[INV_ADDR.RANDO_FLUTE] & 0x01);
1726 state.bugnet = !!data[INV_ADDR.BUGNET];
1727 state.book = !!data[INV_ADDR.BOOK];
1729 if (data[INV_ADDR.BOTTLE_1]) {
1732 if (data[INV_ADDR.BOTTLE_2]) {
1735 if (data[INV_ADDR.BOTTLE_3]) {
1738 if (data[INV_ADDR.BOTTLE_4]) {
1741 state.somaria = !!data[INV_ADDR.SOMARIA];
1742 state.byrna = !!data[INV_ADDR.BYRNA];
1743 state.cape = !!data[INV_ADDR.CAPE];
1744 state.mirror = !!data[INV_ADDR.MIRROR];
1745 state.lift = data[INV_ADDR.GLOVE];
1746 state.boots = !!data[INV_ADDR.BOOTS];
1747 state.flippers = !!data[INV_ADDR.FLIPPERS];
1748 state.moonpearl = !!data[INV_ADDR.MOONPEARL];
1749 state.sword = data[INV_ADDR.SWORD];
1750 state.shield = data[INV_ADDR.SHIELD];
1751 state.mail = data[INV_ADDR.ARMOR] + 1;
1752 state['heart-piece'] = data[INV_ADDR.HEART_PIECE];
1753 state['half-magic'] = data[INV_ADDR.MAGIC_USE] > 0;
1754 state['quarter-magic'] = data[INV_ADDR.MAGIC_USE] > 1;
1755 const map = getShort(data, INV_ADDR.MAP);
1756 const compass = getShort(data, INV_ADDR.COMPASS);
1757 const bigKey = getShort(data, INV_ADDR.BIG_KEY);
1758 DUNGEONS.forEach(dungeon => {
1759 state[`${dungeon.id}-map`] = !!(map & dungeon.mask);
1760 state[`${dungeon.id}-compass`] = !!(compass & dungeon.mask);
1761 state[`${dungeon.id}-small-key`] = data[INV_ADDR.RANDO_KEY_START + dungeon.offset];
1762 state[`${dungeon.id}-big-key`] = !!(bigKey & dungeon.mask);
1763 if (dungeon.prize) {
1764 const isCrystal = prizeMap[dungeon.offset].isCrystal;
1765 const prizeFlags = data[isCrystal ? INV_ADDR.CRYSTALS : INV_ADDR.PENDANTS];
1766 state[`${dungeon.id}-prize-acquired`] = !!(prizeFlags & prizeMap[dungeon.offset].mask);
1771 const collectOverworld = (state, data) => {
1772 OVERWORLD_LOCATIONS.forEach(location => {
1773 state[location.id] = !!(data[location.address] & location.mask);
1777 const collectUnderworld = (state, data) => {
1778 UNDERWORLD_LOCATIONS.forEach(location => {
1779 state[location.id] = isChestOpen(data, location.room, location.chest);
1781 DUNGEONS.forEach(dungeon => {
1782 state[`${dungeon.id}-boss-defeated`] = isBossDefeated(data, dungeon.bossRoom);
1786 export const computeState = (config, data, prizeMap) => {
1788 collectInventory(state, data.slice(SRAM_ADDR.INV_START), prizeMap);
1789 collectOverworld(state, data);
1790 collectUnderworld(state, data.slice(SRAM_ADDR.ROOM_DATA_START));
1791 const amounts = getDungeonAmounts(config, state);
1792 DUNGEONS.forEach(dungeon => {
1793 state[`${dungeon.id}-checks`] = amounts[dungeon.id];
1798 const getDungeonAmounts = (config, state) => {
1800 DUNGEONS.forEach(dungeon => {
1802 let total = dungeon.checks.length;
1803 dungeon.checks.forEach(check => {
1808 if (!config.wildMap && state[`${dungeon.id}-map`]) {
1812 if (!config.wildCompass && state[`${dungeon.id}-compass`]) {
1816 if (!config.wildSmall) {
1817 amount -= Math.min(state[`${dungeon.id}-small-key`], dungeon.sk);
1818 total -= dungeon.sk;
1820 if (!config.wildBig && !dungeon.dropBk && state[`${dungeon.id}-big-key`]) {
1824 amounts[dungeon.id] = Math.min(total, amount);
1829 export const mergeStates = (autoState, manualState) => {
1830 const next = { ...autoState };
1831 BOOLEAN_STATES.forEach(name => {
1832 if (manualState[name]) {
1836 INTEGER_STATES.forEach(name => {
1837 next[name] = Math.max(autoState[name] || 0, manualState[name] || 0);
1839 DUNGEONS.forEach(dungeon => {
1840 next[`${dungeon.id}-small-key`] += manualState[`${dungeon.id}-small-key`] || 0;
1841 next[`${dungeon.id}-checks`] += manualState[`${dungeon.id}-checks`] || 0;
1842 if (manualState[`${dungeon.id}-big-key`]) {
1843 next[`${dungeon.id}-big-key`] = true;
1845 if (manualState[`${dungeon.id}-compass`]) {
1846 next[`${dungeon.id}-compass`] = true;
1848 if (manualState[`${dungeon.id}-map`]) {
1849 next[`${dungeon.id}-map`] = true;
1851 if (manualState[`${dungeon.id}-boss-defeated`]) {
1852 next[`${dungeon.id}-boss-defeated`] = true;
1854 if (manualState[`${dungeon.id}-prize`] &&
1855 manualState[`${dungeon.id}-prize`] !== 'crystal'
1857 next[`${dungeon.id}-prize`] = manualState[`${dungeon.id}-prize`];
1858 } else if (!next[`${dungeon.id}-prize`]) {
1859 next[`${dungeon.id}-prize`] = 'crystal';
1861 if (manualState[`${dungeon.id}-prize-acquired`]) {
1862 next[`${dungeon.id}-prize-acquired`] = true;
1865 OVERWORLD_LOCATIONS.forEach(loc => {
1866 if (manualState[loc.id]) {
1867 next[loc.id] = true;
1870 UNDERWORLD_LOCATIONS.forEach(loc => {
1871 if (manualState[loc.id]) {
1872 next[loc.id] = true;
1875 next['mm-medallion'] = manualState['mm-medallion'];
1876 next['tr-medallion'] = manualState['tr-medallion'];
1877 //console.log(next);