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 mapLayout: 'horizontal',
89 showMap: 'situational',
90 showCompass: 'situational',
100 export const DUNGEONS = [
113 offset: DUNGEON_IDS.HC,
114 mask: DUNGEON_MASKS.HC,
137 offset: DUNGEON_IDS.CT,
138 mask: DUNGEON_MASKS.CT,
155 offset: DUNGEON_IDS.GT,
156 mask: DUNGEON_MASKS.GT,
200 offset: DUNGEON_IDS.EP,
201 mask: DUNGEON_MASKS.EP,
219 bosses: ['lanmolas'],
224 offset: DUNGEON_IDS.DP,
225 mask: DUNGEON_MASKS.DP,
248 offset: DUNGEON_IDS.TH,
249 mask: DUNGEON_MASKS.TH,
271 offset: DUNGEON_IDS.PD,
272 mask: DUNGEON_MASKS.PD,
275 'pd-stalfos-basement',
283 'pd-harmless-hellway',
302 offset: DUNGEON_IDS.SP,
303 mask: DUNGEON_MASKS.SP,
329 offset: DUNGEON_IDS.SW,
330 mask: DUNGEON_MASKS.SW,
354 offset: DUNGEON_IDS.TT,
355 mask: DUNGEON_MASKS.TT,
375 bosses: ['kholdstare'],
379 offset: DUNGEON_IDS.IP,
380 mask: DUNGEON_MASKS.IP,
400 bosses: ['vitreous'],
404 offset: DUNGEON_IDS.MM,
405 mask: DUNGEON_MASKS.MM,
429 offset: DUNGEON_IDS.TR,
430 mask: DUNGEON_MASKS.TR,
439 'tr-laser-bridge-top',
440 'tr-laser-bridge-left',
441 'tr-laser-bridge-right',
442 'tr-laser-bridge-bottom',
448 export const OVERWORLD_LOCATIONS = [
490 id: 'floating-island',
505 id: 'lake-hylia-island',
575 id: 'sunken-treasure',
596 export const UNDERWORLD_LOCATIONS = [
603 id: 'blinds-hut-top',
608 id: 'blinds-hut-left',
613 id: 'blinds-hut-right',
618 id: 'blinds-hut-far-left',
623 id: 'blinds-hut-far-right',
687 id: 'dp-big-key-chest',
693 id: 'dp-compass-chest',
717 id: 'ep-big-key-chest',
729 id: 'ep-compass-chest',
746 id: 'graveyard-ledge',
895 id: 'gt-helma-right',
901 id: 'gt-pre-moldorm',
907 id: 'gt-post-moldorm',
936 id: 'hookshot-cave-br',
941 id: 'hookshot-cave-tr',
946 id: 'hookshot-cave-tl',
951 id: 'hookshot-cave-bl',
961 id: 'hype-cave-left',
966 id: 'hype-cave-right',
971 id: 'hype-cave-bottom',
986 id: 'ip-compass-chest',
992 id: 'ip-big-key-chest',
1004 id: 'ip-spike-chest',
1010 id: 'ip-freezor-chest',
1033 id: 'kak-well-left',
1043 id: 'kak-well-right',
1048 id: 'kak-well-bottom',
1063 id: 'lost-woods-hideout',
1078 id: 'mini-moldorm-far-left',
1083 id: 'mini-moldorm-left',
1088 id: 'mini-moldorm-right',
1093 id: 'mini-moldorm-far-right',
1098 id: 'mini-moldorm-npc',
1103 id: 'mm-bridge-chest',
1108 id: 'mm-spike-chest',
1113 id: 'mm-lobby-chest',
1118 id: 'mm-compass-chest',
1123 id: 'mm-big-key-chest',
1138 id: 'mire-shed-left',
1143 id: 'mire-shed-right',
1148 id: 'paradox-lower-far-left',
1153 id: 'paradox-lower-left',
1158 id: 'paradox-lower-right',
1163 id: 'paradox-lower-far-right',
1168 id: 'paradox-lower-mid',
1173 id: 'paradox-upper-left',
1178 id: 'paradox-upper-right',
1183 id: 'pd-shooter-room',
1188 id: 'pd-stalfos-basement',
1193 id: 'pd-big-key-chest',
1198 id: 'pd-arena-bridge',
1203 id: 'pd-arena-ledge',
1218 id: 'pd-compass-chest',
1223 id: 'pd-harmless-hellway',
1233 id: 'pd-maze-bottom',
1238 id: 'pd-basement-left',
1243 id: 'pd-basement-right',
1248 id: 'pyramid-fairy-left',
1253 id: 'pyramid-fairy-right',
1279 id: 'secret-passage',
1320 id: 'sp-compass-chest',
1326 id: 'sp-west-chest',
1332 id: 'sp-big-key-chest',
1338 id: 'sp-flooded-left',
1344 id: 'sp-flooded-right',
1356 id: 'spec-rock-cave',
1371 id: 'super-bunny-top',
1376 id: 'super-bunny-bottom',
1393 id: 'sw-compass-chest',
1399 id: 'sw-big-key-chest',
1405 id: 'sw-pot-prison',
1411 id: 'sw-pinball-room',
1417 id: 'sw-bridge-chest',
1428 id: 'th-basement-cage',
1434 id: 'th-big-key-chest',
1452 id: 'th-compass-chest',
1458 id: 'tr-roller-left',
1464 id: 'tr-roller-right',
1470 id: 'tr-compass-chest',
1482 id: 'tr-big-key-chest',
1494 id: 'tr-crysta-roller',
1500 id: 'tr-laser-bridge-top',
1506 id: 'tr-laser-bridge-left',
1512 id: 'tr-laser-bridge-right',
1518 id: 'tr-laser-bridge-bottom',
1530 id: 'tt-big-key-chest',
1536 id: 'tt-ambush-chest',
1542 id: 'tt-compass-chest',
1566 id: 'waterfall-fairy-left',
1571 id: 'waterfall-fairy-right',
1577 export const getConfigValue = (config, name, fallback) =>
1578 Object.prototype.hasOwnProperty.call(config, name) ? config[name] : fallback;
1580 export const configureDungeons = config => DUNGEONS.map(dungeon => {
1581 const newDungeon = JSON.parse(JSON.stringify(dungeon));
1582 if (config.wildMap && dungeon.map) {
1585 if (config.wildCompass && dungeon.compass) {
1588 if (config.wildSmall) {
1589 newDungeon.items += dungeon.sk;
1591 if (config.wildBig && dungeon.bk && !dungeon.dropBk) {
1595 newDungeon.bosses = config.bossShuffle ? BOSSES : [dungeon.boss];
1600 export const applyLogic = (config, dungeons, state) => {
1601 const logic = Logic[config.worldState];
1603 for (const name in logic) {
1605 map[name] = logic[name](config, dungeons, state);
1607 console.error('error evaluating', name, e);
1613 export const shouldShowDungeonItem = (config, which) => {
1614 const show = config[`show${which}`] || 'always';
1615 const wild = config[`wild${which}`] || false;
1627 export const toggleBoolean = name => state => ({
1629 [name]: !state[name],
1632 export const increment = (name, max, skipZero) => state => {
1633 let newValue = ((state[name] || 0) + 1) % (max + 1);
1634 if (skipZero && !newValue) {
1643 export const decrement = (name, max, skipZero) => state => {
1644 let newValue = ((state[name] || 0) + max) % (max + 1);
1645 if (skipZero && !newValue) {
1654 export const highestActive = (state, names) => {
1655 for (let i = names.length; i >= 0; --i) {
1656 if (state[names[i]]) {
1663 export const clearAll = names => state => {
1664 const changes = names.reduce((acc, cur) => ({ ...acc, [cur]: true }), {});
1665 return { ...state, ...changes };
1668 export const unclearAll = names => state => {
1669 const changes = names.reduce((acc, cur) => ({ ...acc, [cur]: false }), {});
1670 return { ...state, ...changes };
1673 export const countClearedLocations = (state, locations) =>
1674 locations.reduce((acc, cur) => state[cur] ? acc + 1 : acc, 0);
1676 export const hasClearedLocations = (state, locations) =>
1677 countClearedLocations(state, locations) === locations.length;
1679 export const getLocationStatus = (name, logic, state) => {
1680 if (state[name]) return 'cleared';
1681 if (logic[name]) return logic[name];
1682 return logic.fallback;
1685 export const getCombinedStatus = statuses => {
1686 if (statuses.filter(s => s === 'cleared').length === statuses.length) {
1689 if (statuses.filter(s => ['available', 'cleared'].includes(s)).length === statuses.length) {
1692 if (statuses.filter(s => ['unavailable', 'cleared'].includes(s)).length === statuses.length) {
1693 return 'unavailable';
1698 export const aggregateLocationStatus = (names, logic, state) => {
1699 const statuses = names.map(name => getLocationStatus(name, logic, state));
1700 return getCombinedStatus(statuses);
1703 export const countRemainingLocations = (state, locations) =>
1704 locations.reduce((acc, cur) => state[cur] ? acc : acc + 1, 0);
1706 export const getGanonCrystals = (config) => getConfigValue(config, 'ganon-crystals', 7);
1708 export const getGTCrystals = (config) => getConfigValue(config, 'gt-crystals', 7);
1710 export const getGTBoss = (state, which) => state[`gt-${which}-boss`];
1712 export const hasDungeonBoss = (state, dungeon) =>
1713 !dungeon.boss || !!state[`${dungeon.id}-boss-defeated`];
1715 export const getDungeonBoss = (state, dungeon) =>
1716 dungeon.bosses.length > 1
1717 ? state[`${dungeon.id}-boss`] || dungeon.boss || null
1718 : dungeon.bosses[0];
1720 export const hasDungeonPrize = (state, dungeon) =>
1721 !dungeon.prize || !!state[`${dungeon.id}-prize-acquired`];
1723 export const getDungeonPrize = (state, dungeon) => state[`${dungeon.id}-prize`] || null;
1725 export const getDungeonClearedItems = (state, dungeon) => state[`${dungeon.id}-checks`] || 0;
1727 export const getDungeonRemainingItems = (state, dungeon) =>
1728 Math.max(0, dungeon.items - getDungeonClearedItems(state, dungeon));
1730 export const getDungeonAcquiredSKs = (state, dungeon) => state[`${dungeon.id}-small-key`] || 0;
1732 export const isDungeonCleared = (state, dungeon) => {
1733 const hasItems = !getDungeonRemainingItems(state, dungeon);
1734 const hasBoss = hasDungeonBoss(state, dungeon);
1735 const hasPrize = hasDungeonPrize(state, dungeon);
1736 return hasItems && hasBoss && hasPrize;
1739 export const aggregateDungeonStatus = (dungeon, logic, state) => {
1740 if (isDungeonCleared(state, dungeon)) {
1743 if (logic[dungeon.id] === 'unavailable') {
1744 return 'unavailable';
1746 const checks = [...dungeon.checks];
1747 if (['ct', 'gt'].includes(dungeon.id)) {
1748 checks.push(`${dungeon.id}-boss-killable`);
1750 const statuses = checks.map(name => getLocationStatus(name, logic, state));
1751 return getCombinedStatus(statuses);
1754 export const toggleBossDefeated = dungeon => toggleBoolean(`${dungeon.id}-boss-defeated`);
1756 export const setBossDefeated = (dungeon, defeated) =>
1757 state => ({ ...state, [`${dungeon.id}-boss-defeated`]: !!defeated });
1759 export const togglePrizeAcquired = dungeon => toggleBoolean(`${dungeon.id}-prize-acquired`);
1761 export const setPrizeAcquired = (dungeon, acquired) =>
1762 state => ({ ...state, [`${dungeon.id}-prize-acquired`]: !!acquired });
1764 export const addDungeonCheck = dungeon => increment(`${dungeon.id}-checks`, dungeon.items);
1766 export const removeDungeonCheck = dungeon => decrement(`${dungeon.id}-checks`, dungeon.items);
1768 export const resetDungeonChecks = dungeon => state => ({ ...state, [`${dungeon.id}-checks`]: 0 });
1770 export const completeDungeonChecks = dungeon =>
1771 state => ({ ...state, [`${dungeon.id}-checks`]: dungeon.items });
1773 export const makeEmptyState = () => {
1775 BOOLEAN_STATES.forEach(p => {
1776 state[p] = INITIAL[p] || false;
1778 INTEGER_STATES.forEach(p => {
1779 state[p] = INITIAL[p] || 0;
1781 DUNGEONS.forEach(dungeon => {
1782 state[`${dungeon.id}-map`] = false;
1783 state[`${dungeon.id}-compass`] = false;
1784 state[`${dungeon.id}-small-key`] = 0;
1785 state[`${dungeon.id}-big-key`] = false;
1786 state[`${dungeon.id}-checks`] = 0;
1788 state[`${dungeon.id}-boss`] = dungeon.boss;
1789 state[`${dungeon.id}-boss-defeated`] = false;
1791 if (dungeon.prize) {
1792 state[`${dungeon.id}-prize`] = 'crystal';
1793 state[`${dungeon.id}-prize-acquired`] = false;
1795 if (dungeon.id === 'gt') {
1796 state['gt-bot-boss'] = 'armos';
1797 state['gt-mid-boss'] = 'lanmolas';
1798 state['gt-top-boss'] = 'moldorm';
1801 OVERWORLD_LOCATIONS.forEach(location => {
1802 state[location.id] = false;
1804 UNDERWORLD_LOCATIONS.forEach(location => {
1805 state[location.id] = false;
1807 state['mm-medallion'] = null;
1808 state['tr-medallion'] = null;
1812 const collectInventory = (state, data, prizeMap) => {
1813 state.bow = !!(data[INV_ADDR.RANDO_BOW] & 0x80);
1814 state.silvers = (data[INV_ADDR.RANDO_BOW] & 0xC0) == 0xC0;
1815 state['bowless-silvers'] = (data[INV_ADDR.RANDO_BOW] & 0xC0) == 0x40;
1816 state['blue-boomerang'] = !!(data[INV_ADDR.RANDO_BOOM] & 0x40);
1817 state['red-boomerang'] = !!(data[INV_ADDR.RANDO_BOOM] & 0x80);
1818 state.hookshot = !!data[INV_ADDR.HOOK];
1819 state.bomb = data[INV_ADDR.BOMB];
1820 state.mushroom = !!(data[INV_ADDR.RANDO_POWDER] & 0x20);
1821 state.powder = !!(data[INV_ADDR.RANDO_POWDER] & 0x10);
1822 state['fire-rod'] = !!data[INV_ADDR.FROD];
1823 state['ice-rod'] = !!data[INV_ADDR.IROD];
1824 state.bombos = !!data[INV_ADDR.BOMBOS];
1825 state.ether = !!data[INV_ADDR.ETHER];
1826 state.quake = !!data[INV_ADDR.QUAKE];
1827 state.lamp = !!data[INV_ADDR.LAMP];
1828 state.hammer = !!data[INV_ADDR.HAMMER];
1829 state.shovel = !!(data[INV_ADDR.RANDO_FLUTE] & 0x04);
1830 state.flute = !!(data[INV_ADDR.RANDO_FLUTE] & 0x03);
1831 state.duck = !!(data[INV_ADDR.RANDO_FLUTE] & 0x01);
1832 state.bugnet = !!data[INV_ADDR.BUGNET];
1833 state.book = !!data[INV_ADDR.BOOK];
1834 state['bottle-1'] = data[INV_ADDR.BOTTLE_1];
1835 state['bottle-2'] = data[INV_ADDR.BOTTLE_2];
1836 state['bottle-3'] = data[INV_ADDR.BOTTLE_3];
1837 state['bottle-4'] = data[INV_ADDR.BOTTLE_4];
1838 state.somaria = !!data[INV_ADDR.SOMARIA];
1839 state.byrna = !!data[INV_ADDR.BYRNA];
1840 state.cape = !!data[INV_ADDR.CAPE];
1841 state.mirror = !!data[INV_ADDR.MIRROR];
1842 state.lift = data[INV_ADDR.GLOVE];
1843 state.boots = !!data[INV_ADDR.BOOTS];
1844 state.flippers = !!data[INV_ADDR.FLIPPERS];
1845 state.moonpearl = !!data[INV_ADDR.MOONPEARL];
1846 state.sword = data[INV_ADDR.SWORD];
1847 state.shield = data[INV_ADDR.SHIELD];
1848 state.mail = data[INV_ADDR.ARMOR] + 1;
1849 state['heart-piece'] = data[INV_ADDR.HEART_PIECE];
1850 state['half-magic'] = data[INV_ADDR.MAGIC_USE] > 0;
1851 state['quarter-magic'] = data[INV_ADDR.MAGIC_USE] > 1;
1852 const map = getShort(data, INV_ADDR.MAP);
1853 const compass = getShort(data, INV_ADDR.COMPASS);
1854 const bigKey = getShort(data, INV_ADDR.BIG_KEY);
1855 DUNGEONS.forEach(dungeon => {
1856 state[`${dungeon.id}-map`] = !!(map & dungeon.mask);
1857 state[`${dungeon.id}-compass`] = !!(compass & dungeon.mask);
1858 state[`${dungeon.id}-small-key`] = data[INV_ADDR.RANDO_KEY_START + dungeon.offset];
1859 state[`${dungeon.id}-big-key`] = !!(bigKey & dungeon.mask);
1860 if (dungeon.prize) {
1861 const isCrystal = prizeMap[dungeon.offset].isCrystal;
1862 const prizeFlags = data[isCrystal ? INV_ADDR.CRYSTALS : INV_ADDR.PENDANTS];
1863 state[`${dungeon.id}-prize-acquired`] = !!(prizeFlags & prizeMap[dungeon.offset].mask);
1868 const collectOverworld = (state, data) => {
1869 OVERWORLD_LOCATIONS.forEach(location => {
1870 state[location.id] = !!(data[location.address] & location.mask);
1874 const collectUnderworld = (state, data) => {
1875 UNDERWORLD_LOCATIONS.forEach(location => {
1876 state[location.id] = isChestOpen(data, location.room, location.chest);
1878 DUNGEONS.forEach(dungeon => {
1879 state[`${dungeon.id}-boss-defeated`] = isBossDefeated(data, dungeon.bossRoom);
1883 const getDungeonAmounts = (config, state) => {
1885 DUNGEONS.forEach(dungeon => {
1887 let total = dungeon.checks.length;
1888 dungeon.checks.forEach(check => {
1893 if (!config.wildMap && state[`${dungeon.id}-map`]) {
1897 if (!config.wildCompass && state[`${dungeon.id}-compass`]) {
1901 if (!config.wildSmall) {
1902 amount -= Math.min(state[`${dungeon.id}-small-key`], dungeon.sk);
1903 total -= dungeon.sk;
1905 if (!config.wildBig && !dungeon.dropBk && state[`${dungeon.id}-big-key`]) {
1909 amounts[dungeon.id] = Math.min(total, amount);
1914 export const computeState = (config, data, prizeMap) => {
1916 collectInventory(state, data.slice(SRAM_ADDR.INV_START), prizeMap);
1917 collectOverworld(state, data);
1918 collectUnderworld(state, data.slice(SRAM_ADDR.ROOM_DATA_START));
1919 const amounts = getDungeonAmounts(config, state);
1920 DUNGEONS.forEach(dungeon => {
1921 state[`${dungeon.id}-checks`] = amounts[dungeon.id];
1926 export const mergeStates = (autoState, manualState) => {
1927 const next = { ...autoState };
1928 BOOLEAN_STATES.forEach(name => {
1929 if (manualState[name]) {
1933 INTEGER_STATES.forEach(name => {
1934 next[name] = Math.max(autoState[name] || 0, manualState[name] || 0);
1936 DUNGEONS.forEach(dungeon => {
1937 next[`${dungeon.id}-small-key`] += manualState[`${dungeon.id}-small-key`] || 0;
1938 next[`${dungeon.id}-checks`] += manualState[`${dungeon.id}-checks`] || 0;
1939 if (manualState[`${dungeon.id}-big-key`]) {
1940 next[`${dungeon.id}-big-key`] = true;
1942 if (manualState[`${dungeon.id}-compass`]) {
1943 next[`${dungeon.id}-compass`] = true;
1945 if (manualState[`${dungeon.id}-map`]) {
1946 next[`${dungeon.id}-map`] = true;
1948 if (manualState[`${dungeon.id}-boss`]) {
1949 next[`${dungeon.id}-boss`] = manualState[`${dungeon.id}-boss`];
1951 if (manualState[`${dungeon.id}-boss-defeated`]) {
1952 next[`${dungeon.id}-boss-defeated`] = true;
1954 if (manualState[`${dungeon.id}-prize`] &&
1955 manualState[`${dungeon.id}-prize`] !== 'crystal'
1957 next[`${dungeon.id}-prize`] = manualState[`${dungeon.id}-prize`];
1958 } else if (!next[`${dungeon.id}-prize`]) {
1959 next[`${dungeon.id}-prize`] = 'crystal';
1961 if (manualState[`${dungeon.id}-prize-acquired`]) {
1962 next[`${dungeon.id}-prize-acquired`] = true;
1965 OVERWORLD_LOCATIONS.forEach(loc => {
1966 if (manualState[loc.id]) {
1967 next[loc.id] = true;
1970 UNDERWORLD_LOCATIONS.forEach(loc => {
1971 if (manualState[loc.id]) {
1972 next[loc.id] = true;
1976 next['bottle-1'] = autoState['bottle-1'] || manualState['bottle-1'] || 0;
1977 next['bottle-2'] = autoState['bottle-2'] || manualState['bottle-2'] || 0;
1978 next['bottle-3'] = autoState['bottle-3'] || manualState['bottle-3'] || 0;
1979 next['bottle-4'] = autoState['bottle-4'] || manualState['bottle-4'] || 0;
1981 next['mm-medallion'] = manualState['mm-medallion'];
1982 next['tr-medallion'] = manualState['tr-medallion'];
1983 next['gt-crystals'] = manualState['gt-crystals'];
1984 next['ganon-crystals'] = manualState['ganon-crystals'];
1985 next['gt-bot-boss'] = manualState['gt-bot-boss'];
1986 next['gt-mid-boss'] = manualState['gt-mid-boss'];
1987 next['gt-top-boss'] = manualState['gt-top-boss'];
1988 //console.log(next);