10 import Logic from './logic';
12 export const BOOLEAN_STATES = [
45 export const INTEGER_STATES = [
57 export const INITIAL = {
61 export const BOTTLE_CONTENTS = [
72 export const BOSSES = [
85 export const CONFIG = {
88 showMap: 'situational',
89 showCompass: 'situational',
99 export const DUNGEONS = [
112 offset: DUNGEON_IDS.HC,
113 mask: DUNGEON_MASKS.HC,
136 offset: DUNGEON_IDS.CT,
137 mask: DUNGEON_MASKS.CT,
154 offset: DUNGEON_IDS.GT,
155 mask: DUNGEON_MASKS.GT,
199 offset: DUNGEON_IDS.EP,
200 mask: DUNGEON_MASKS.EP,
218 bosses: ['lanmolas'],
223 offset: DUNGEON_IDS.DP,
224 mask: DUNGEON_MASKS.DP,
247 offset: DUNGEON_IDS.TH,
248 mask: DUNGEON_MASKS.TH,
270 offset: DUNGEON_IDS.PD,
271 mask: DUNGEON_MASKS.PD,
274 'pd-stalfos-basement',
282 'pd-harmless-hellway',
301 offset: DUNGEON_IDS.SP,
302 mask: DUNGEON_MASKS.SP,
328 offset: DUNGEON_IDS.SW,
329 mask: DUNGEON_MASKS.SW,
353 offset: DUNGEON_IDS.TT,
354 mask: DUNGEON_MASKS.TT,
374 bosses: ['kholdstare'],
378 offset: DUNGEON_IDS.IP,
379 mask: DUNGEON_MASKS.IP,
399 bosses: ['vitreous'],
403 offset: DUNGEON_IDS.MM,
404 mask: DUNGEON_MASKS.MM,
428 offset: DUNGEON_IDS.TR,
429 mask: DUNGEON_MASKS.TR,
438 'tr-laser-bridge-top',
439 'tr-laser-bridge-left',
440 'tr-laser-bridge-right',
441 'tr-laser-bridge-bottom',
447 export const OVERWORLD_LOCATIONS = [
489 id: 'floating-island',
504 id: 'lake-hylia-island',
574 id: 'sunken-treasure',
595 export const UNDERWORLD_LOCATIONS = [
602 id: 'blinds-hut-top',
607 id: 'blinds-hut-left',
612 id: 'blinds-hut-right',
617 id: 'blinds-hut-far-left',
622 id: 'blinds-hut-far-right',
686 id: 'dp-big-key-chest',
692 id: 'dp-compass-chest',
716 id: 'ep-big-key-chest',
728 id: 'ep-compass-chest',
745 id: 'graveyard-ledge',
894 id: 'gt-helma-right',
900 id: 'gt-pre-moldorm',
906 id: 'gt-post-moldorm',
935 id: 'hookshot-cave-br',
940 id: 'hookshot-cave-tr',
945 id: 'hookshot-cave-tl',
950 id: 'hookshot-cave-bl',
960 id: 'hype-cave-left',
965 id: 'hype-cave-right',
970 id: 'hype-cave-bottom',
985 id: 'ip-compass-chest',
991 id: 'ip-big-key-chest',
1003 id: 'ip-spike-chest',
1009 id: 'ip-freezor-chest',
1032 id: 'kak-well-left',
1042 id: 'kak-well-right',
1047 id: 'kak-well-bottom',
1062 id: 'lost-woods-hideout',
1077 id: 'mini-moldorm-far-left',
1082 id: 'mini-moldorm-left',
1087 id: 'mini-moldorm-right',
1092 id: 'mini-moldorm-far-right',
1097 id: 'mini-moldorm-npc',
1102 id: 'mm-bridge-chest',
1107 id: 'mm-spike-chest',
1112 id: 'mm-lobby-chest',
1117 id: 'mm-compass-chest',
1122 id: 'mm-big-key-chest',
1137 id: 'mire-shed-left',
1142 id: 'mire-shed-right',
1147 id: 'paradox-lower-far-left',
1152 id: 'paradox-lower-left',
1157 id: 'paradox-lower-right',
1162 id: 'paradox-lower-far-right',
1167 id: 'paradox-lower-mid',
1172 id: 'paradox-upper-left',
1177 id: 'paradox-upper-right',
1182 id: 'pd-shooter-room',
1187 id: 'pd-stalfos-basement',
1192 id: 'pd-big-key-chest',
1197 id: 'pd-arena-bridge',
1202 id: 'pd-arena-ledge',
1217 id: 'pd-compass-chest',
1222 id: 'pd-harmless-hellway',
1232 id: 'pd-maze-bottom',
1237 id: 'pd-basement-left',
1242 id: 'pd-basement-right',
1247 id: 'pyramid-fairy-left',
1252 id: 'pyramid-fairy-right',
1278 id: 'secret-passage',
1319 id: 'sp-compass-chest',
1325 id: 'sp-west-chest',
1331 id: 'sp-big-key-chest',
1337 id: 'sp-flooded-left',
1343 id: 'sp-flooded-right',
1355 id: 'spec-rock-cave',
1370 id: 'super-bunny-top',
1375 id: 'super-bunny-bottom',
1392 id: 'sw-compass-chest',
1398 id: 'sw-big-key-chest',
1404 id: 'sw-pot-prison',
1410 id: 'sw-pinball-room',
1416 id: 'sw-bridge-chest',
1427 id: 'th-basement-cage',
1433 id: 'th-big-key-chest',
1451 id: 'th-compass-chest',
1457 id: 'tr-roller-left',
1463 id: 'tr-roller-right',
1469 id: 'tr-compass-chest',
1481 id: 'tr-big-key-chest',
1493 id: 'tr-crysta-roller',
1499 id: 'tr-laser-bridge-top',
1505 id: 'tr-laser-bridge-left',
1511 id: 'tr-laser-bridge-right',
1517 id: 'tr-laser-bridge-bottom',
1529 id: 'tt-big-key-chest',
1535 id: 'tt-ambush-chest',
1541 id: 'tt-compass-chest',
1565 id: 'waterfall-fairy-left',
1570 id: 'waterfall-fairy-right',
1576 export const applyLogic = (config, dungeons, state) => {
1577 const logic = Logic[config.worldState];
1579 for (const name in logic) {
1580 map[name] = logic[name](config, dungeons, state);
1585 export const shouldShowDungeonItem = (config, which) => {
1586 const show = config[`show${which}`] || 'always';
1587 const wild = config[`wild${which}`] || false;
1599 export const toggleBoolean = name => state => ({
1601 [name]: !state[name],
1604 export const increment = (name, max, skipZero) => state => {
1605 let newValue = ((state[name] || 0) + 1) % (max + 1);
1606 if (skipZero && !newValue) {
1615 export const decrement = (name, max, skipZero) => state => {
1616 let newValue = ((state[name] || 0) + max) % (max + 1);
1617 if (skipZero && !newValue) {
1626 export const highestActive = (state, names) => {
1627 for (let i = names.length; i >= 0; --i) {
1628 if (state[names[i]]) {
1635 export const clearAll = names => state => {
1636 const changes = names.reduce((acc, cur) => ({ ...acc, [cur]: true }), {});
1637 return { ...state, ...changes };
1640 export const unclearAll = names => state => {
1641 const changes = names.reduce((acc, cur) => ({ ...acc, [cur]: false }), {});
1642 return { ...state, ...changes };
1645 export const countClearedLocations = (state, locations) =>
1646 locations.reduce((acc, cur) => state[cur] ? acc + 1 : acc, 0);
1648 export const hasClearedLocations = (state, locations) =>
1649 countClearedLocations(state, locations) === locations.length;
1651 export const getLocationStatus = (name, logic, state) => {
1652 if (state[name]) return 'cleared';
1653 if (logic[name]) return logic[name];
1654 return logic.fallback;
1657 export const getCombinedStatus = statuses => {
1658 if (statuses.filter(s => s === 'cleared').length === statuses.length) {
1661 if (statuses.filter(s => ['available', 'cleared'].includes(s)).length === statuses.length) {
1664 if (statuses.filter(s => ['unavailable', 'cleared'].includes(s)).length === statuses.length) {
1665 return 'unavailable';
1670 export const aggregateLocationStatus = (names, logic, state) => {
1671 const statuses = names.map(name => getLocationStatus(name, logic, state));
1672 return getCombinedStatus(statuses);
1675 export const countRemainingLocations = (state, locations) =>
1676 locations.reduce((acc, cur) => state[cur] ? acc : acc + 1, 0);
1678 export const getGanonCrystals = (state) => state['ganon-crystals'];
1680 export const getGTCrystals = (state) => state['gt-crystals'];
1682 export const getGTBoss = (state, which) => state[`gt-${which}-boss`];
1684 export const hasDungeonBoss = (state, dungeon) =>
1685 !dungeon.boss || !!state[`${dungeon.id}-boss-defeated`];
1687 export const getDungeonBoss = (state, dungeon) =>
1688 state[`${dungeon.id}-boss`] || dungeon.boss || null;
1690 export const hasDungeonPrize = (state, dungeon) =>
1691 !dungeon.prize || !!state[`${dungeon.id}-prize-acquired`];
1693 export const getDungeonPrize = (state, dungeon) => state[`${dungeon.id}-prize`] || null;
1695 export const getDungeonClearedItems = (state, dungeon) => state[`${dungeon.id}-checks`] || 0;
1697 export const getDungeonRemainingItems = (state, dungeon) =>
1698 Math.max(0, dungeon.items - getDungeonClearedItems(state, dungeon));
1700 export const getDungeonAcquiredSKs = (state, dungeon) => state[`${dungeon.id}-small-key`] || 0;
1702 export const isDungeonCleared = (state, dungeon) => {
1703 const hasItems = !getDungeonRemainingItems(state, dungeon);
1704 const hasBoss = hasDungeonBoss(state, dungeon);
1705 const hasPrize = hasDungeonPrize(state, dungeon);
1706 return hasItems && hasBoss && hasPrize;
1709 export const aggregateDungeonStatus = (dungeon, logic, state) => {
1710 if (isDungeonCleared(state, dungeon)) {
1713 if (logic[dungeon.id] === 'unavailable') {
1714 return 'unavailable';
1716 const checks = [...dungeon.checks];
1717 if (['ct', 'gt'].includes(dungeon.id)) {
1718 checks.push(`${dungeon.id}-boss-killable`);
1720 const statuses = checks.map(name => getLocationStatus(name, logic, state));
1721 return getCombinedStatus(statuses);
1724 export const toggleBossDefeated = dungeon => toggleBoolean(`${dungeon.id}-boss-defeated`);
1726 export const setBossDefeated = (dungeon, defeated) =>
1727 state => ({ ...state, [`${dungeon.id}-boss-defeated`]: !!defeated });
1729 export const togglePrizeAcquired = dungeon => toggleBoolean(`${dungeon.id}-prize-acquired`);
1731 export const setPrizeAcquired = (dungeon, acquired) =>
1732 state => ({ ...state, [`${dungeon.id}-prize-acquired`]: !!acquired });
1734 export const addDungeonCheck = dungeon => increment(`${dungeon.id}-checks`, dungeon.items);
1736 export const removeDungeonCheck = dungeon => decrement(`${dungeon.id}-checks`, dungeon.items);
1738 export const resetDungeonChecks = dungeon => state => ({ ...state, [`${dungeon.id}-checks`]: 0 });
1740 export const completeDungeonChecks = dungeon =>
1741 state => ({ ...state, [`${dungeon.id}-checks`]: dungeon.items });
1743 export const makeEmptyState = () => {
1745 BOOLEAN_STATES.forEach(p => {
1746 state[p] = INITIAL[p] || false;
1748 INTEGER_STATES.forEach(p => {
1749 state[p] = INITIAL[p] || 0;
1751 DUNGEONS.forEach(dungeon => {
1752 state[`${dungeon.id}-map`] = false;
1753 state[`${dungeon.id}-compass`] = false;
1754 state[`${dungeon.id}-small-key`] = 0;
1755 state[`${dungeon.id}-big-key`] = false;
1756 state[`${dungeon.id}-checks`] = 0;
1758 state[`${dungeon.id}-boss`] = dungeon.boss;
1759 state[`${dungeon.id}-boss-defeated`] = false;
1761 if (dungeon.prize) {
1762 state[`${dungeon.id}-prize`] = 'crystal';
1763 state[`${dungeon.id}-prize-acquired`] = false;
1765 if (dungeon.id === 'gt') {
1766 state['gt-bot-boss'] = 'armos';
1767 state['gt-mid-boss'] = 'lanmolas';
1768 state['gt-top-boss'] = 'moldorm';
1771 OVERWORLD_LOCATIONS.forEach(location => {
1772 state[location.id] = false;
1774 UNDERWORLD_LOCATIONS.forEach(location => {
1775 state[location.id] = false;
1777 state['mm-medallion'] = null;
1778 state['tr-medallion'] = null;
1779 state['gt-crystals'] = 7;
1780 state['ganon-crystals'] = 7;
1784 const collectInventory = (state, data, prizeMap) => {
1785 state.bow = !!(data[INV_ADDR.RANDO_BOW] & 0x80);
1786 state.silvers = (data[INV_ADDR.RANDO_BOW] & 0xC0) == 0xC0;
1787 state['bowless-silvers'] = (data[INV_ADDR.RANDO_BOW] & 0xC0) == 0x40;
1788 state['blue-boomerang'] = !!(data[INV_ADDR.RANDO_BOOM] & 0x40);
1789 state['red-boomerang'] = !!(data[INV_ADDR.RANDO_BOOM] & 0x80);
1790 state.hookshot = !!data[INV_ADDR.HOOK];
1791 state.bomb = data[INV_ADDR.BOMB];
1792 state.mushroom = !!(data[INV_ADDR.RANDO_POWDER] & 0x20);
1793 state.powder = !!(data[INV_ADDR.RANDO_POWDER] & 0x10);
1794 state['fire-rod'] = !!data[INV_ADDR.FROD];
1795 state['ice-rod'] = !!data[INV_ADDR.IROD];
1796 state.bombos = !!data[INV_ADDR.BOMBOS];
1797 state.ether = !!data[INV_ADDR.ETHER];
1798 state.quake = !!data[INV_ADDR.QUAKE];
1799 state.lamp = !!data[INV_ADDR.LAMP];
1800 state.hammer = !!data[INV_ADDR.HAMMER];
1801 state.shovel = !!(data[INV_ADDR.RANDO_FLUTE] & 0x04);
1802 state.flute = !!(data[INV_ADDR.RANDO_FLUTE] & 0x03);
1803 state.duck = !!(data[INV_ADDR.RANDO_FLUTE] & 0x01);
1804 state.bugnet = !!data[INV_ADDR.BUGNET];
1805 state.book = !!data[INV_ADDR.BOOK];
1806 state['bottle-1'] = data[INV_ADDR.BOTTLE_1];
1807 state['bottle-2'] = data[INV_ADDR.BOTTLE_2];
1808 state['bottle-3'] = data[INV_ADDR.BOTTLE_3];
1809 state['bottle-4'] = data[INV_ADDR.BOTTLE_4];
1810 state.somaria = !!data[INV_ADDR.SOMARIA];
1811 state.byrna = !!data[INV_ADDR.BYRNA];
1812 state.cape = !!data[INV_ADDR.CAPE];
1813 state.mirror = !!data[INV_ADDR.MIRROR];
1814 state.lift = data[INV_ADDR.GLOVE];
1815 state.boots = !!data[INV_ADDR.BOOTS];
1816 state.flippers = !!data[INV_ADDR.FLIPPERS];
1817 state.moonpearl = !!data[INV_ADDR.MOONPEARL];
1818 state.sword = data[INV_ADDR.SWORD];
1819 state.shield = data[INV_ADDR.SHIELD];
1820 state.mail = data[INV_ADDR.ARMOR] + 1;
1821 state['heart-piece'] = data[INV_ADDR.HEART_PIECE];
1822 state['half-magic'] = data[INV_ADDR.MAGIC_USE] > 0;
1823 state['quarter-magic'] = data[INV_ADDR.MAGIC_USE] > 1;
1824 const map = getShort(data, INV_ADDR.MAP);
1825 const compass = getShort(data, INV_ADDR.COMPASS);
1826 const bigKey = getShort(data, INV_ADDR.BIG_KEY);
1827 DUNGEONS.forEach(dungeon => {
1828 state[`${dungeon.id}-map`] = !!(map & dungeon.mask);
1829 state[`${dungeon.id}-compass`] = !!(compass & dungeon.mask);
1830 state[`${dungeon.id}-small-key`] = data[INV_ADDR.RANDO_KEY_START + dungeon.offset];
1831 state[`${dungeon.id}-big-key`] = !!(bigKey & dungeon.mask);
1832 if (dungeon.prize) {
1833 const isCrystal = prizeMap[dungeon.offset].isCrystal;
1834 const prizeFlags = data[isCrystal ? INV_ADDR.CRYSTALS : INV_ADDR.PENDANTS];
1835 state[`${dungeon.id}-prize-acquired`] = !!(prizeFlags & prizeMap[dungeon.offset].mask);
1840 const collectOverworld = (state, data) => {
1841 OVERWORLD_LOCATIONS.forEach(location => {
1842 state[location.id] = !!(data[location.address] & location.mask);
1846 const collectUnderworld = (state, data) => {
1847 UNDERWORLD_LOCATIONS.forEach(location => {
1848 state[location.id] = isChestOpen(data, location.room, location.chest);
1850 DUNGEONS.forEach(dungeon => {
1851 state[`${dungeon.id}-boss-defeated`] = isBossDefeated(data, dungeon.bossRoom);
1855 export const computeState = (config, data, prizeMap) => {
1857 collectInventory(state, data.slice(SRAM_ADDR.INV_START), prizeMap);
1858 collectOverworld(state, data);
1859 collectUnderworld(state, data.slice(SRAM_ADDR.ROOM_DATA_START));
1860 const amounts = getDungeonAmounts(config, state);
1861 DUNGEONS.forEach(dungeon => {
1862 state[`${dungeon.id}-checks`] = amounts[dungeon.id];
1867 const getDungeonAmounts = (config, state) => {
1869 DUNGEONS.forEach(dungeon => {
1871 let total = dungeon.checks.length;
1872 dungeon.checks.forEach(check => {
1877 if (!config.wildMap && state[`${dungeon.id}-map`]) {
1881 if (!config.wildCompass && state[`${dungeon.id}-compass`]) {
1885 if (!config.wildSmall) {
1886 amount -= Math.min(state[`${dungeon.id}-small-key`], dungeon.sk);
1887 total -= dungeon.sk;
1889 if (!config.wildBig && !dungeon.dropBk && state[`${dungeon.id}-big-key`]) {
1893 amounts[dungeon.id] = Math.min(total, amount);
1898 export const mergeStates = (autoState, manualState) => {
1899 const next = { ...autoState };
1900 BOOLEAN_STATES.forEach(name => {
1901 if (manualState[name]) {
1905 INTEGER_STATES.forEach(name => {
1906 next[name] = Math.max(autoState[name] || 0, manualState[name] || 0);
1908 DUNGEONS.forEach(dungeon => {
1909 next[`${dungeon.id}-small-key`] += manualState[`${dungeon.id}-small-key`] || 0;
1910 next[`${dungeon.id}-checks`] += manualState[`${dungeon.id}-checks`] || 0;
1911 if (manualState[`${dungeon.id}-big-key`]) {
1912 next[`${dungeon.id}-big-key`] = true;
1914 if (manualState[`${dungeon.id}-compass`]) {
1915 next[`${dungeon.id}-compass`] = true;
1917 if (manualState[`${dungeon.id}-map`]) {
1918 next[`${dungeon.id}-map`] = true;
1920 if (manualState[`${dungeon.id}-boss-defeated`]) {
1921 next[`${dungeon.id}-boss-defeated`] = true;
1923 if (manualState[`${dungeon.id}-prize`] &&
1924 manualState[`${dungeon.id}-prize`] !== 'crystal'
1926 next[`${dungeon.id}-prize`] = manualState[`${dungeon.id}-prize`];
1927 } else if (!next[`${dungeon.id}-prize`]) {
1928 next[`${dungeon.id}-prize`] = 'crystal';
1930 if (manualState[`${dungeon.id}-prize-acquired`]) {
1931 next[`${dungeon.id}-prize-acquired`] = true;
1934 OVERWORLD_LOCATIONS.forEach(loc => {
1935 if (manualState[loc.id]) {
1936 next[loc.id] = true;
1939 UNDERWORLD_LOCATIONS.forEach(loc => {
1940 if (manualState[loc.id]) {
1941 next[loc.id] = true;
1945 next['bottle-1'] = autoState['bottle-1'] || manualState['bottle-1'] || 0;
1946 next['bottle-2'] = autoState['bottle-2'] || manualState['bottle-2'] || 0;
1947 next['bottle-3'] = autoState['bottle-3'] || manualState['bottle-3'] || 0;
1948 next['bottle-4'] = autoState['bottle-4'] || manualState['bottle-4'] || 0;
1950 next['mm-medallion'] = manualState['mm-medallion'];
1951 next['tr-medallion'] = manualState['tr-medallion'];
1952 next['gt-crystals'] = manualState['gt-crystals'];
1953 next['ganon-crystals'] = manualState['ganon-crystals'];
1954 next['gt-bot-boss'] = manualState['gt-bot-boss'];
1955 next['gt-mid-boss'] = manualState['gt-mid-boss'];
1956 next['gt-top-boss'] = manualState['gt-top-boss'];
1957 //console.log(next);