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 applyLogic = (config, dungeons, state) => {
1580 const logic = Logic[config.worldState];
1582 for (const name in logic) {
1584 map[name] = logic[name](config, dungeons, state);
1586 console.error('error evaluating', name, e);
1592 export const shouldShowDungeonItem = (config, which) => {
1593 const show = config[`show${which}`] || 'always';
1594 const wild = config[`wild${which}`] || false;
1606 export const toggleBoolean = name => state => ({
1608 [name]: !state[name],
1611 export const increment = (name, max, skipZero) => state => {
1612 let newValue = ((state[name] || 0) + 1) % (max + 1);
1613 if (skipZero && !newValue) {
1622 export const decrement = (name, max, skipZero) => state => {
1623 let newValue = ((state[name] || 0) + max) % (max + 1);
1624 if (skipZero && !newValue) {
1633 export const highestActive = (state, names) => {
1634 for (let i = names.length; i >= 0; --i) {
1635 if (state[names[i]]) {
1642 export const clearAll = names => state => {
1643 const changes = names.reduce((acc, cur) => ({ ...acc, [cur]: true }), {});
1644 return { ...state, ...changes };
1647 export const unclearAll = names => state => {
1648 const changes = names.reduce((acc, cur) => ({ ...acc, [cur]: false }), {});
1649 return { ...state, ...changes };
1652 export const countClearedLocations = (state, locations) =>
1653 locations.reduce((acc, cur) => state[cur] ? acc + 1 : acc, 0);
1655 export const hasClearedLocations = (state, locations) =>
1656 countClearedLocations(state, locations) === locations.length;
1658 export const getLocationStatus = (name, logic, state) => {
1659 if (state[name]) return 'cleared';
1660 if (logic[name]) return logic[name];
1661 return logic.fallback;
1664 export const getCombinedStatus = statuses => {
1665 if (statuses.filter(s => s === 'cleared').length === statuses.length) {
1668 if (statuses.filter(s => ['available', 'cleared'].includes(s)).length === statuses.length) {
1671 if (statuses.filter(s => ['unavailable', 'cleared'].includes(s)).length === statuses.length) {
1672 return 'unavailable';
1677 export const aggregateLocationStatus = (names, logic, state) => {
1678 const statuses = names.map(name => getLocationStatus(name, logic, state));
1679 return getCombinedStatus(statuses);
1682 export const countRemainingLocations = (state, locations) =>
1683 locations.reduce((acc, cur) => state[cur] ? acc : acc + 1, 0);
1685 export const getGanonCrystals = (config) => getConfigValue(config, 'ganon-crystals', 7);
1687 export const getGTCrystals = (config) => getConfigValue(config, 'gt-crystals', 7);
1689 export const getGTBoss = (state, which) => state[`gt-${which}-boss`];
1691 export const hasDungeonBoss = (state, dungeon) =>
1692 !dungeon.boss || !!state[`${dungeon.id}-boss-defeated`];
1694 export const getDungeonBoss = (state, dungeon) =>
1695 dungeon.bosses.length > 1
1696 ? state[`${dungeon.id}-boss`] || dungeon.boss || null
1697 : dungeon.bosses[0];
1699 export const hasDungeonPrize = (state, dungeon) =>
1700 !dungeon.prize || !!state[`${dungeon.id}-prize-acquired`];
1702 export const getDungeonPrize = (state, dungeon) => state[`${dungeon.id}-prize`] || null;
1704 export const getDungeonClearedItems = (state, dungeon) => state[`${dungeon.id}-checks`] || 0;
1706 export const getDungeonRemainingItems = (state, dungeon) =>
1707 Math.max(0, dungeon.items - getDungeonClearedItems(state, dungeon));
1709 export const getDungeonAcquiredSKs = (state, dungeon) => state[`${dungeon.id}-small-key`] || 0;
1711 export const isDungeonCleared = (state, dungeon) => {
1712 const hasItems = !getDungeonRemainingItems(state, dungeon);
1713 const hasBoss = hasDungeonBoss(state, dungeon);
1714 const hasPrize = hasDungeonPrize(state, dungeon);
1715 return hasItems && hasBoss && hasPrize;
1718 export const aggregateDungeonStatus = (dungeon, logic, state) => {
1719 if (isDungeonCleared(state, dungeon)) {
1722 if (logic[dungeon.id] === 'unavailable') {
1723 return 'unavailable';
1725 const checks = [...dungeon.checks];
1726 if (['ct', 'gt'].includes(dungeon.id)) {
1727 checks.push(`${dungeon.id}-boss-killable`);
1729 const statuses = checks.map(name => getLocationStatus(name, logic, state));
1730 return getCombinedStatus(statuses);
1733 export const toggleBossDefeated = dungeon => toggleBoolean(`${dungeon.id}-boss-defeated`);
1735 export const setBossDefeated = (dungeon, defeated) =>
1736 state => ({ ...state, [`${dungeon.id}-boss-defeated`]: !!defeated });
1738 export const togglePrizeAcquired = dungeon => toggleBoolean(`${dungeon.id}-prize-acquired`);
1740 export const setPrizeAcquired = (dungeon, acquired) =>
1741 state => ({ ...state, [`${dungeon.id}-prize-acquired`]: !!acquired });
1743 export const addDungeonCheck = dungeon => increment(`${dungeon.id}-checks`, dungeon.items);
1745 export const removeDungeonCheck = dungeon => decrement(`${dungeon.id}-checks`, dungeon.items);
1747 export const resetDungeonChecks = dungeon => state => ({ ...state, [`${dungeon.id}-checks`]: 0 });
1749 export const completeDungeonChecks = dungeon =>
1750 state => ({ ...state, [`${dungeon.id}-checks`]: dungeon.items });
1752 export const makeEmptyState = () => {
1754 BOOLEAN_STATES.forEach(p => {
1755 state[p] = INITIAL[p] || false;
1757 INTEGER_STATES.forEach(p => {
1758 state[p] = INITIAL[p] || 0;
1760 DUNGEONS.forEach(dungeon => {
1761 state[`${dungeon.id}-map`] = false;
1762 state[`${dungeon.id}-compass`] = false;
1763 state[`${dungeon.id}-small-key`] = 0;
1764 state[`${dungeon.id}-big-key`] = false;
1765 state[`${dungeon.id}-checks`] = 0;
1767 state[`${dungeon.id}-boss`] = dungeon.boss;
1768 state[`${dungeon.id}-boss-defeated`] = false;
1770 if (dungeon.prize) {
1771 state[`${dungeon.id}-prize`] = 'crystal';
1772 state[`${dungeon.id}-prize-acquired`] = false;
1774 if (dungeon.id === 'gt') {
1775 state['gt-bot-boss'] = 'armos';
1776 state['gt-mid-boss'] = 'lanmolas';
1777 state['gt-top-boss'] = 'moldorm';
1780 OVERWORLD_LOCATIONS.forEach(location => {
1781 state[location.id] = false;
1783 UNDERWORLD_LOCATIONS.forEach(location => {
1784 state[location.id] = false;
1786 state['mm-medallion'] = null;
1787 state['tr-medallion'] = null;
1791 const collectInventory = (state, data, prizeMap) => {
1792 state.bow = !!(data[INV_ADDR.RANDO_BOW] & 0x80);
1793 state.silvers = (data[INV_ADDR.RANDO_BOW] & 0xC0) == 0xC0;
1794 state['bowless-silvers'] = (data[INV_ADDR.RANDO_BOW] & 0xC0) == 0x40;
1795 state['blue-boomerang'] = !!(data[INV_ADDR.RANDO_BOOM] & 0x40);
1796 state['red-boomerang'] = !!(data[INV_ADDR.RANDO_BOOM] & 0x80);
1797 state.hookshot = !!data[INV_ADDR.HOOK];
1798 state.bomb = data[INV_ADDR.BOMB];
1799 state.mushroom = !!(data[INV_ADDR.RANDO_POWDER] & 0x20);
1800 state.powder = !!(data[INV_ADDR.RANDO_POWDER] & 0x10);
1801 state['fire-rod'] = !!data[INV_ADDR.FROD];
1802 state['ice-rod'] = !!data[INV_ADDR.IROD];
1803 state.bombos = !!data[INV_ADDR.BOMBOS];
1804 state.ether = !!data[INV_ADDR.ETHER];
1805 state.quake = !!data[INV_ADDR.QUAKE];
1806 state.lamp = !!data[INV_ADDR.LAMP];
1807 state.hammer = !!data[INV_ADDR.HAMMER];
1808 state.shovel = !!(data[INV_ADDR.RANDO_FLUTE] & 0x04);
1809 state.flute = !!(data[INV_ADDR.RANDO_FLUTE] & 0x03);
1810 state.duck = !!(data[INV_ADDR.RANDO_FLUTE] & 0x01);
1811 state.bugnet = !!data[INV_ADDR.BUGNET];
1812 state.book = !!data[INV_ADDR.BOOK];
1813 state['bottle-1'] = data[INV_ADDR.BOTTLE_1];
1814 state['bottle-2'] = data[INV_ADDR.BOTTLE_2];
1815 state['bottle-3'] = data[INV_ADDR.BOTTLE_3];
1816 state['bottle-4'] = data[INV_ADDR.BOTTLE_4];
1817 state.somaria = !!data[INV_ADDR.SOMARIA];
1818 state.byrna = !!data[INV_ADDR.BYRNA];
1819 state.cape = !!data[INV_ADDR.CAPE];
1820 state.mirror = !!data[INV_ADDR.MIRROR];
1821 state.lift = data[INV_ADDR.GLOVE];
1822 state.boots = !!data[INV_ADDR.BOOTS];
1823 state.flippers = !!data[INV_ADDR.FLIPPERS];
1824 state.moonpearl = !!data[INV_ADDR.MOONPEARL];
1825 state.sword = data[INV_ADDR.SWORD];
1826 state.shield = data[INV_ADDR.SHIELD];
1827 state.mail = data[INV_ADDR.ARMOR] + 1;
1828 state['heart-piece'] = data[INV_ADDR.HEART_PIECE];
1829 state['half-magic'] = data[INV_ADDR.MAGIC_USE] > 0;
1830 state['quarter-magic'] = data[INV_ADDR.MAGIC_USE] > 1;
1831 const map = getShort(data, INV_ADDR.MAP);
1832 const compass = getShort(data, INV_ADDR.COMPASS);
1833 const bigKey = getShort(data, INV_ADDR.BIG_KEY);
1834 DUNGEONS.forEach(dungeon => {
1835 state[`${dungeon.id}-map`] = !!(map & dungeon.mask);
1836 state[`${dungeon.id}-compass`] = !!(compass & dungeon.mask);
1837 state[`${dungeon.id}-small-key`] = data[INV_ADDR.RANDO_KEY_START + dungeon.offset];
1838 state[`${dungeon.id}-big-key`] = !!(bigKey & dungeon.mask);
1839 if (dungeon.prize) {
1840 const isCrystal = prizeMap[dungeon.offset].isCrystal;
1841 const prizeFlags = data[isCrystal ? INV_ADDR.CRYSTALS : INV_ADDR.PENDANTS];
1842 state[`${dungeon.id}-prize-acquired`] = !!(prizeFlags & prizeMap[dungeon.offset].mask);
1847 const collectOverworld = (state, data) => {
1848 OVERWORLD_LOCATIONS.forEach(location => {
1849 state[location.id] = !!(data[location.address] & location.mask);
1853 const collectUnderworld = (state, data) => {
1854 UNDERWORLD_LOCATIONS.forEach(location => {
1855 state[location.id] = isChestOpen(data, location.room, location.chest);
1857 DUNGEONS.forEach(dungeon => {
1858 state[`${dungeon.id}-boss-defeated`] = isBossDefeated(data, dungeon.bossRoom);
1862 export const computeState = (config, data, prizeMap) => {
1864 collectInventory(state, data.slice(SRAM_ADDR.INV_START), prizeMap);
1865 collectOverworld(state, data);
1866 collectUnderworld(state, data.slice(SRAM_ADDR.ROOM_DATA_START));
1867 const amounts = getDungeonAmounts(config, state);
1868 DUNGEONS.forEach(dungeon => {
1869 state[`${dungeon.id}-checks`] = amounts[dungeon.id];
1874 const getDungeonAmounts = (config, state) => {
1876 DUNGEONS.forEach(dungeon => {
1878 let total = dungeon.checks.length;
1879 dungeon.checks.forEach(check => {
1884 if (!config.wildMap && state[`${dungeon.id}-map`]) {
1888 if (!config.wildCompass && state[`${dungeon.id}-compass`]) {
1892 if (!config.wildSmall) {
1893 amount -= Math.min(state[`${dungeon.id}-small-key`], dungeon.sk);
1894 total -= dungeon.sk;
1896 if (!config.wildBig && !dungeon.dropBk && state[`${dungeon.id}-big-key`]) {
1900 amounts[dungeon.id] = Math.min(total, amount);
1905 export const mergeStates = (autoState, manualState) => {
1906 const next = { ...autoState };
1907 BOOLEAN_STATES.forEach(name => {
1908 if (manualState[name]) {
1912 INTEGER_STATES.forEach(name => {
1913 next[name] = Math.max(autoState[name] || 0, manualState[name] || 0);
1915 DUNGEONS.forEach(dungeon => {
1916 next[`${dungeon.id}-small-key`] += manualState[`${dungeon.id}-small-key`] || 0;
1917 next[`${dungeon.id}-checks`] += manualState[`${dungeon.id}-checks`] || 0;
1918 if (manualState[`${dungeon.id}-big-key`]) {
1919 next[`${dungeon.id}-big-key`] = true;
1921 if (manualState[`${dungeon.id}-compass`]) {
1922 next[`${dungeon.id}-compass`] = true;
1924 if (manualState[`${dungeon.id}-map`]) {
1925 next[`${dungeon.id}-map`] = true;
1927 if (manualState[`${dungeon.id}-boss`]) {
1928 next[`${dungeon.id}-boss`] = manualState[`${dungeon.id}-boss`];
1930 if (manualState[`${dungeon.id}-boss-defeated`]) {
1931 next[`${dungeon.id}-boss-defeated`] = true;
1933 if (manualState[`${dungeon.id}-prize`] &&
1934 manualState[`${dungeon.id}-prize`] !== 'crystal'
1936 next[`${dungeon.id}-prize`] = manualState[`${dungeon.id}-prize`];
1937 } else if (!next[`${dungeon.id}-prize`]) {
1938 next[`${dungeon.id}-prize`] = 'crystal';
1940 if (manualState[`${dungeon.id}-prize-acquired`]) {
1941 next[`${dungeon.id}-prize-acquired`] = true;
1944 OVERWORLD_LOCATIONS.forEach(loc => {
1945 if (manualState[loc.id]) {
1946 next[loc.id] = true;
1949 UNDERWORLD_LOCATIONS.forEach(loc => {
1950 if (manualState[loc.id]) {
1951 next[loc.id] = true;
1955 next['bottle-1'] = autoState['bottle-1'] || manualState['bottle-1'] || 0;
1956 next['bottle-2'] = autoState['bottle-2'] || manualState['bottle-2'] || 0;
1957 next['bottle-3'] = autoState['bottle-3'] || manualState['bottle-3'] || 0;
1958 next['bottle-4'] = autoState['bottle-4'] || manualState['bottle-4'] || 0;
1960 next['mm-medallion'] = manualState['mm-medallion'];
1961 next['tr-medallion'] = manualState['tr-medallion'];
1962 next['gt-crystals'] = manualState['gt-crystals'];
1963 next['ganon-crystals'] = manualState['ganon-crystals'];
1964 next['gt-bot-boss'] = manualState['gt-bot-boss'];
1965 next['gt-mid-boss'] = manualState['gt-mid-boss'];
1966 next['gt-top-boss'] = manualState['gt-top-boss'];
1967 //console.log(next);