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 getConfigValue = (config, name, fallback) =>
1577 Object.prototype.hasOwnProperty.call(config, name) ? config[name] : fallback;
1579 export const configureDungeons = config => DUNGEONS.map(dungeon => {
1580 const newDungeon = JSON.parse(JSON.stringify(dungeon));
1581 if (config.wildMap && dungeon.map) {
1584 if (config.wildCompass && dungeon.compass) {
1587 if (config.wildSmall) {
1588 newDungeon.items += dungeon.sk;
1590 if (config.wildBig && dungeon.bk && !dungeon.dropBk) {
1594 newDungeon.bosses = config.bossShuffle ? BOSSES : [dungeon.boss];
1599 export const applyLogic = (config, dungeons, state) => {
1600 const logic = Logic[config.worldState];
1602 for (const name in logic) {
1604 map[name] = logic[name](config, dungeons, state);
1606 console.error('error evaluating', name, e);
1612 export const shouldShowDungeonItem = (config, which) => {
1613 const show = config[`show${which}`] || 'always';
1614 const wild = config[`wild${which}`] || false;
1626 export const toggleBoolean = name => state => ({
1628 [name]: !state[name],
1631 export const increment = (name, max, skipZero) => state => {
1632 let newValue = ((state[name] || 0) + 1) % (max + 1);
1633 if (skipZero && !newValue) {
1642 export const decrement = (name, max, skipZero) => state => {
1643 let newValue = ((state[name] || 0) + max) % (max + 1);
1644 if (skipZero && !newValue) {
1653 export const highestActive = (state, names) => {
1654 for (let i = names.length; i >= 0; --i) {
1655 if (state[names[i]]) {
1662 export const clearAll = names => state => {
1663 const changes = names.reduce((acc, cur) => ({ ...acc, [cur]: true }), {});
1664 return { ...state, ...changes };
1667 export const unclearAll = names => state => {
1668 const changes = names.reduce((acc, cur) => ({ ...acc, [cur]: false }), {});
1669 return { ...state, ...changes };
1672 export const countClearedLocations = (state, locations) =>
1673 locations.reduce((acc, cur) => state[cur] ? acc + 1 : acc, 0);
1675 export const hasClearedLocations = (state, locations) =>
1676 countClearedLocations(state, locations) === locations.length;
1678 export const getLocationStatus = (name, logic, state) => {
1679 if (state[name]) return 'cleared';
1680 if (logic[name]) return logic[name];
1681 return logic.fallback;
1684 export const getCombinedStatus = statuses => {
1685 if (statuses.filter(s => s === 'cleared').length === statuses.length) {
1688 if (statuses.filter(s => ['available', 'cleared'].includes(s)).length === statuses.length) {
1691 if (statuses.filter(s => ['unavailable', 'cleared'].includes(s)).length === statuses.length) {
1692 return 'unavailable';
1697 export const aggregateLocationStatus = (names, logic, state) => {
1698 const statuses = names.map(name => getLocationStatus(name, logic, state));
1699 return getCombinedStatus(statuses);
1702 export const countRemainingLocations = (state, locations) =>
1703 locations.reduce((acc, cur) => state[cur] ? acc : acc + 1, 0);
1705 export const getGanonCrystals = (config) => getConfigValue(config, 'ganon-crystals', 7);
1707 export const getGTCrystals = (config) => getConfigValue(config, 'gt-crystals', 7);
1709 export const getGTBoss = (state, which) => state[`gt-${which}-boss`];
1711 export const hasDungeonBoss = (state, dungeon) =>
1712 !dungeon.boss || !!state[`${dungeon.id}-boss-defeated`];
1714 export const getDungeonBoss = (state, dungeon) =>
1715 dungeon.bosses.length > 1
1716 ? state[`${dungeon.id}-boss`] || dungeon.boss || null
1717 : dungeon.bosses[0];
1719 export const hasDungeonPrize = (state, dungeon) =>
1720 !dungeon.prize || !!state[`${dungeon.id}-prize-acquired`];
1722 export const getDungeonPrize = (state, dungeon) => state[`${dungeon.id}-prize`] || null;
1724 export const getDungeonClearedItems = (state, dungeon) => state[`${dungeon.id}-checks`] || 0;
1726 export const getDungeonRemainingItems = (state, dungeon) =>
1727 Math.max(0, dungeon.items - getDungeonClearedItems(state, dungeon));
1729 export const getDungeonAcquiredSKs = (state, dungeon) => state[`${dungeon.id}-small-key`] || 0;
1731 export const isDungeonCleared = (state, dungeon) => {
1732 const hasItems = !getDungeonRemainingItems(state, dungeon);
1733 const hasBoss = hasDungeonBoss(state, dungeon);
1734 const hasPrize = hasDungeonPrize(state, dungeon);
1735 return hasItems && hasBoss && hasPrize;
1738 export const aggregateDungeonStatus = (dungeon, logic, state) => {
1739 if (isDungeonCleared(state, dungeon)) {
1742 if (logic[dungeon.id] === 'unavailable') {
1743 return 'unavailable';
1745 const checks = [...dungeon.checks];
1746 if (['ct', 'gt'].includes(dungeon.id)) {
1747 checks.push(`${dungeon.id}-boss-killable`);
1749 const statuses = checks.map(name => getLocationStatus(name, logic, state));
1750 return getCombinedStatus(statuses);
1753 export const toggleBossDefeated = dungeon => toggleBoolean(`${dungeon.id}-boss-defeated`);
1755 export const setBossDefeated = (dungeon, defeated) =>
1756 state => ({ ...state, [`${dungeon.id}-boss-defeated`]: !!defeated });
1758 export const togglePrizeAcquired = dungeon => toggleBoolean(`${dungeon.id}-prize-acquired`);
1760 export const setPrizeAcquired = (dungeon, acquired) =>
1761 state => ({ ...state, [`${dungeon.id}-prize-acquired`]: !!acquired });
1763 export const addDungeonCheck = dungeon => increment(`${dungeon.id}-checks`, dungeon.items);
1765 export const removeDungeonCheck = dungeon => decrement(`${dungeon.id}-checks`, dungeon.items);
1767 export const resetDungeonChecks = dungeon => state => ({ ...state, [`${dungeon.id}-checks`]: 0 });
1769 export const completeDungeonChecks = dungeon =>
1770 state => ({ ...state, [`${dungeon.id}-checks`]: dungeon.items });
1772 export const makeEmptyState = () => {
1774 BOOLEAN_STATES.forEach(p => {
1775 state[p] = INITIAL[p] || false;
1777 INTEGER_STATES.forEach(p => {
1778 state[p] = INITIAL[p] || 0;
1780 DUNGEONS.forEach(dungeon => {
1781 state[`${dungeon.id}-map`] = false;
1782 state[`${dungeon.id}-compass`] = false;
1783 state[`${dungeon.id}-small-key`] = 0;
1784 state[`${dungeon.id}-big-key`] = false;
1785 state[`${dungeon.id}-checks`] = 0;
1787 state[`${dungeon.id}-boss`] = dungeon.boss;
1788 state[`${dungeon.id}-boss-defeated`] = false;
1790 if (dungeon.prize) {
1791 state[`${dungeon.id}-prize`] = 'crystal';
1792 state[`${dungeon.id}-prize-acquired`] = false;
1794 if (dungeon.id === 'gt') {
1795 state['gt-bot-boss'] = 'armos';
1796 state['gt-mid-boss'] = 'lanmolas';
1797 state['gt-top-boss'] = 'moldorm';
1800 OVERWORLD_LOCATIONS.forEach(location => {
1801 state[location.id] = false;
1803 UNDERWORLD_LOCATIONS.forEach(location => {
1804 state[location.id] = false;
1806 state['mm-medallion'] = null;
1807 state['tr-medallion'] = null;
1811 const collectInventory = (state, data, prizeMap) => {
1812 state.bow = !!(data[INV_ADDR.RANDO_BOW] & 0x80);
1813 state.silvers = (data[INV_ADDR.RANDO_BOW] & 0xC0) == 0xC0;
1814 state['bowless-silvers'] = (data[INV_ADDR.RANDO_BOW] & 0xC0) == 0x40;
1815 state['blue-boomerang'] = !!(data[INV_ADDR.RANDO_BOOM] & 0x40);
1816 state['red-boomerang'] = !!(data[INV_ADDR.RANDO_BOOM] & 0x80);
1817 state.hookshot = !!data[INV_ADDR.HOOK];
1818 state.bomb = data[INV_ADDR.BOMB];
1819 state.mushroom = !!(data[INV_ADDR.RANDO_POWDER] & 0x20);
1820 state.powder = !!(data[INV_ADDR.RANDO_POWDER] & 0x10);
1821 state['fire-rod'] = !!data[INV_ADDR.FROD];
1822 state['ice-rod'] = !!data[INV_ADDR.IROD];
1823 state.bombos = !!data[INV_ADDR.BOMBOS];
1824 state.ether = !!data[INV_ADDR.ETHER];
1825 state.quake = !!data[INV_ADDR.QUAKE];
1826 state.lamp = !!data[INV_ADDR.LAMP];
1827 state.hammer = !!data[INV_ADDR.HAMMER];
1828 state.shovel = !!(data[INV_ADDR.RANDO_FLUTE] & 0x04);
1829 state.flute = !!(data[INV_ADDR.RANDO_FLUTE] & 0x03);
1830 state.duck = !!(data[INV_ADDR.RANDO_FLUTE] & 0x01);
1831 state.bugnet = !!data[INV_ADDR.BUGNET];
1832 state.book = !!data[INV_ADDR.BOOK];
1833 state['bottle-1'] = data[INV_ADDR.BOTTLE_1];
1834 state['bottle-2'] = data[INV_ADDR.BOTTLE_2];
1835 state['bottle-3'] = data[INV_ADDR.BOTTLE_3];
1836 state['bottle-4'] = data[INV_ADDR.BOTTLE_4];
1837 state.somaria = !!data[INV_ADDR.SOMARIA];
1838 state.byrna = !!data[INV_ADDR.BYRNA];
1839 state.cape = !!data[INV_ADDR.CAPE];
1840 state.mirror = !!data[INV_ADDR.MIRROR];
1841 state.lift = data[INV_ADDR.GLOVE];
1842 state.boots = !!data[INV_ADDR.BOOTS];
1843 state.flippers = !!data[INV_ADDR.FLIPPERS];
1844 state.moonpearl = !!data[INV_ADDR.MOONPEARL];
1845 state.sword = data[INV_ADDR.SWORD];
1846 state.shield = data[INV_ADDR.SHIELD];
1847 state.mail = data[INV_ADDR.ARMOR] + 1;
1848 state['heart-piece'] = data[INV_ADDR.HEART_PIECE];
1849 state['half-magic'] = data[INV_ADDR.MAGIC_USE] > 0;
1850 state['quarter-magic'] = data[INV_ADDR.MAGIC_USE] > 1;
1851 const map = getShort(data, INV_ADDR.MAP);
1852 const compass = getShort(data, INV_ADDR.COMPASS);
1853 const bigKey = getShort(data, INV_ADDR.BIG_KEY);
1854 DUNGEONS.forEach(dungeon => {
1855 state[`${dungeon.id}-map`] = !!(map & dungeon.mask);
1856 state[`${dungeon.id}-compass`] = !!(compass & dungeon.mask);
1857 state[`${dungeon.id}-small-key`] = data[INV_ADDR.RANDO_KEY_START + dungeon.offset];
1858 state[`${dungeon.id}-big-key`] = !!(bigKey & dungeon.mask);
1859 if (dungeon.prize) {
1860 const isCrystal = prizeMap[dungeon.offset].isCrystal;
1861 const prizeFlags = data[isCrystal ? INV_ADDR.CRYSTALS : INV_ADDR.PENDANTS];
1862 state[`${dungeon.id}-prize-acquired`] = !!(prizeFlags & prizeMap[dungeon.offset].mask);
1867 const collectOverworld = (state, data) => {
1868 OVERWORLD_LOCATIONS.forEach(location => {
1869 state[location.id] = !!(data[location.address] & location.mask);
1873 const collectUnderworld = (state, data) => {
1874 UNDERWORLD_LOCATIONS.forEach(location => {
1875 state[location.id] = isChestOpen(data, location.room, location.chest);
1877 DUNGEONS.forEach(dungeon => {
1878 state[`${dungeon.id}-boss-defeated`] = isBossDefeated(data, dungeon.bossRoom);
1882 const getDungeonAmounts = (config, state) => {
1884 DUNGEONS.forEach(dungeon => {
1886 let total = dungeon.checks.length;
1887 dungeon.checks.forEach(check => {
1892 if (!config.wildMap && state[`${dungeon.id}-map`]) {
1896 if (!config.wildCompass && state[`${dungeon.id}-compass`]) {
1900 if (!config.wildSmall) {
1901 amount -= Math.min(state[`${dungeon.id}-small-key`], dungeon.sk);
1902 total -= dungeon.sk;
1904 if (!config.wildBig && !dungeon.dropBk && state[`${dungeon.id}-big-key`]) {
1908 amounts[dungeon.id] = Math.min(total, amount);
1913 export const computeState = (config, data, prizeMap) => {
1915 collectInventory(state, data.slice(SRAM_ADDR.INV_START), prizeMap);
1916 collectOverworld(state, data);
1917 collectUnderworld(state, data.slice(SRAM_ADDR.ROOM_DATA_START));
1918 const amounts = getDungeonAmounts(config, state);
1919 DUNGEONS.forEach(dungeon => {
1920 state[`${dungeon.id}-checks`] = amounts[dungeon.id];
1925 export const mergeStates = (autoState, manualState) => {
1926 const next = { ...autoState };
1927 BOOLEAN_STATES.forEach(name => {
1928 if (manualState[name]) {
1932 INTEGER_STATES.forEach(name => {
1933 next[name] = Math.max(autoState[name] || 0, manualState[name] || 0);
1935 DUNGEONS.forEach(dungeon => {
1936 next[`${dungeon.id}-small-key`] += manualState[`${dungeon.id}-small-key`] || 0;
1937 next[`${dungeon.id}-checks`] += manualState[`${dungeon.id}-checks`] || 0;
1938 if (manualState[`${dungeon.id}-big-key`]) {
1939 next[`${dungeon.id}-big-key`] = true;
1941 if (manualState[`${dungeon.id}-compass`]) {
1942 next[`${dungeon.id}-compass`] = true;
1944 if (manualState[`${dungeon.id}-map`]) {
1945 next[`${dungeon.id}-map`] = true;
1947 if (manualState[`${dungeon.id}-boss`]) {
1948 next[`${dungeon.id}-boss`] = manualState[`${dungeon.id}-boss`];
1950 if (manualState[`${dungeon.id}-boss-defeated`]) {
1951 next[`${dungeon.id}-boss-defeated`] = true;
1953 if (manualState[`${dungeon.id}-prize`] &&
1954 manualState[`${dungeon.id}-prize`] !== 'crystal'
1956 next[`${dungeon.id}-prize`] = manualState[`${dungeon.id}-prize`];
1957 } else if (!next[`${dungeon.id}-prize`]) {
1958 next[`${dungeon.id}-prize`] = 'crystal';
1960 if (manualState[`${dungeon.id}-prize-acquired`]) {
1961 next[`${dungeon.id}-prize-acquired`] = true;
1964 OVERWORLD_LOCATIONS.forEach(loc => {
1965 if (manualState[loc.id]) {
1966 next[loc.id] = true;
1969 UNDERWORLD_LOCATIONS.forEach(loc => {
1970 if (manualState[loc.id]) {
1971 next[loc.id] = true;
1975 next['bottle-1'] = autoState['bottle-1'] || manualState['bottle-1'] || 0;
1976 next['bottle-2'] = autoState['bottle-2'] || manualState['bottle-2'] || 0;
1977 next['bottle-3'] = autoState['bottle-3'] || manualState['bottle-3'] || 0;
1978 next['bottle-4'] = autoState['bottle-4'] || manualState['bottle-4'] || 0;
1980 next['mm-medallion'] = manualState['mm-medallion'];
1981 next['tr-medallion'] = manualState['tr-medallion'];
1982 next['gt-crystals'] = manualState['gt-crystals'];
1983 next['ganon-crystals'] = manualState['ganon-crystals'];
1984 next['gt-bot-boss'] = manualState['gt-bot-boss'];
1985 next['gt-mid-boss'] = manualState['gt-mid-boss'];
1986 next['gt-top-boss'] = manualState['gt-top-boss'];
1987 //console.log(next);