10 import Logic from './logic';
12 export const BOOLEAN_STATES = [
45 export const INTEGER_STATES = [
54 export const INITIAL = {
58 export const BOSSES = [
71 export const CONFIG = {
74 showMap: 'situational',
75 showCompass: 'situational',
85 export const DUNGEONS = [
98 offset: DUNGEON_IDS.HC,
99 mask: DUNGEON_MASKS.HC,
122 offset: DUNGEON_IDS.CT,
123 mask: DUNGEON_MASKS.CT,
140 offset: DUNGEON_IDS.GT,
141 mask: DUNGEON_MASKS.GT,
185 offset: DUNGEON_IDS.EP,
186 mask: DUNGEON_MASKS.EP,
204 bosses: ['lanmolas'],
209 offset: DUNGEON_IDS.DP,
210 mask: DUNGEON_MASKS.DP,
233 offset: DUNGEON_IDS.TH,
234 mask: DUNGEON_MASKS.TH,
256 offset: DUNGEON_IDS.PD,
257 mask: DUNGEON_MASKS.PD,
260 'pd-stalfos-basement',
268 'pd-harmless-hellway',
287 offset: DUNGEON_IDS.SP,
288 mask: DUNGEON_MASKS.SP,
314 offset: DUNGEON_IDS.SW,
315 mask: DUNGEON_MASKS.SW,
339 offset: DUNGEON_IDS.TT,
340 mask: DUNGEON_MASKS.TT,
360 bosses: ['kholdstare'],
364 offset: DUNGEON_IDS.IP,
365 mask: DUNGEON_MASKS.IP,
385 bosses: ['vitreous'],
389 offset: DUNGEON_IDS.MM,
390 mask: DUNGEON_MASKS.MM,
414 offset: DUNGEON_IDS.TR,
415 mask: DUNGEON_MASKS.TR,
424 'tr-laser-bridge-top',
425 'tr-laser-bridge-left',
426 'tr-laser-bridge-right',
427 'tr-laser-bridge-bottom',
433 export const OVERWORLD_LOCATIONS = [
475 id: 'floating-island',
490 id: 'lake-hylia-island',
560 id: 'sunken-treasure',
581 export const UNDERWORLD_LOCATIONS = [
588 id: 'blinds-hut-top',
593 id: 'blinds-hut-left',
598 id: 'blinds-hut-right',
603 id: 'blinds-hut-far-left',
608 id: 'blinds-hut-far-right',
672 id: 'dp-big-key-chest',
678 id: 'dp-compass-chest',
702 id: 'ep-big-key-chest',
714 id: 'ep-compass-chest',
731 id: 'graveyard-ledge',
880 id: 'gt-helma-right',
886 id: 'gt-pre-moldorm',
892 id: 'gt-post-moldorm',
921 id: 'hookshot-cave-br',
926 id: 'hookshot-cave-tr',
931 id: 'hookshot-cave-tl',
936 id: 'hookshot-cave-bl',
946 id: 'hype-cave-left',
951 id: 'hype-cave-right',
956 id: 'hype-cave-bottom',
971 id: 'ip-compass-chest',
977 id: 'ip-big-key-chest',
989 id: 'ip-spike-chest',
995 id: 'ip-freezor-chest',
1018 id: 'kak-well-left',
1028 id: 'kak-well-right',
1033 id: 'kak-well-bottom',
1048 id: 'lost-woods-hideout',
1063 id: 'mini-moldorm-far-left',
1068 id: 'mini-moldorm-left',
1073 id: 'mini-moldorm-right',
1078 id: 'mini-moldorm-far-right',
1083 id: 'mini-moldorm-npc',
1088 id: 'mm-bridge-chest',
1093 id: 'mm-spike-chest',
1098 id: 'mm-lobby-chest',
1103 id: 'mm-compass-chest',
1108 id: 'mm-big-key-chest',
1123 id: 'mire-shed-left',
1128 id: 'mire-shed-right',
1133 id: 'paradox-lower-far-left',
1138 id: 'paradox-lower-left',
1143 id: 'paradox-lower-right',
1148 id: 'paradox-lower-far-right',
1153 id: 'paradox-lower-mid',
1158 id: 'paradox-upper-left',
1163 id: 'paradox-upper-right',
1168 id: 'pd-shooter-room',
1173 id: 'pd-stalfos-basement',
1178 id: 'pd-big-key-chest',
1183 id: 'pd-arena-bridge',
1188 id: 'pd-arena-ledge',
1203 id: 'pd-compass-chest',
1208 id: 'pd-harmless-hellway',
1218 id: 'pd-maze-bottom',
1223 id: 'pd-basement-left',
1228 id: 'pd-basement-right',
1233 id: 'pyramid-fairy-left',
1238 id: 'pyramid-fairy-right',
1264 id: 'secret-passage',
1305 id: 'sp-compass-chest',
1311 id: 'sp-west-chest',
1317 id: 'sp-big-key-chest',
1323 id: 'sp-flooded-left',
1329 id: 'sp-flooded-right',
1341 id: 'spec-rock-cave',
1356 id: 'super-bunny-top',
1361 id: 'super-bunny-bottom',
1378 id: 'sw-compass-chest',
1384 id: 'sw-big-key-chest',
1390 id: 'sw-pot-prison',
1396 id: 'sw-pinball-room',
1402 id: 'sw-bridge-chest',
1413 id: 'th-basement-cage',
1419 id: 'th-big-key-chest',
1437 id: 'th-compass-chest',
1443 id: 'tr-roller-left',
1449 id: 'tr-roller-right',
1455 id: 'tr-compass-chest',
1467 id: 'tr-big-key-chest',
1479 id: 'tr-crysta-roller',
1485 id: 'tr-laser-bridge-top',
1491 id: 'tr-laser-bridge-left',
1497 id: 'tr-laser-bridge-right',
1503 id: 'tr-laser-bridge-bottom',
1515 id: 'tt-big-key-chest',
1521 id: 'tt-ambush-chest',
1527 id: 'tt-compass-chest',
1551 id: 'waterfall-fairy-left',
1556 id: 'waterfall-fairy-right',
1562 export const applyLogic = (config, dungeons, state) => {
1563 const logic = Logic[config.worldState];
1565 for (const name in logic) {
1566 map[name] = logic[name](config, dungeons, state);
1571 export const shouldShowDungeonItem = (config, which) => {
1572 const show = config[`show${which}`] || 'always';
1573 const wild = config[`wild${which}`] || false;
1585 export const toggleBoolean = name => state => ({
1587 [name]: !state[name],
1590 export const increment = (name, max, skipZero) => state => {
1591 let newValue = ((state[name] || 0) + 1) % (max + 1);
1592 if (skipZero && !newValue) {
1601 export const decrement = (name, max, skipZero) => state => {
1602 let newValue = ((state[name] || 0) + max) % (max + 1);
1603 if (skipZero && !newValue) {
1612 export const highestActive = (state, names) => {
1613 for (let i = names.length; i >= 0; --i) {
1614 if (state[names[i]]) {
1621 export const clearAll = names => state => {
1622 const changes = names.reduce((acc, cur) => ({ ...acc, [cur]: true }), {});
1623 return { ...state, ...changes };
1626 export const unclearAll = names => state => {
1627 const changes = names.reduce((acc, cur) => ({ ...acc, [cur]: false }), {});
1628 return { ...state, ...changes };
1631 export const countClearedLocations = (state, locations) =>
1632 locations.reduce((acc, cur) => state[cur] ? acc + 1 : acc, 0);
1634 export const hasClearedLocations = (state, locations) =>
1635 countClearedLocations(state, locations) === locations.length;
1637 export const getLocationStatus = (name, logic, state) => {
1638 if (state[name]) return 'cleared';
1639 if (logic[name]) return logic[name];
1640 return logic.fallback;
1643 export const getCombinedStatus = statuses => {
1644 if (statuses.filter(s => s === 'cleared').length === statuses.length) {
1647 if (statuses.filter(s => ['available', 'cleared'].includes(s)).length === statuses.length) {
1650 if (statuses.filter(s => ['unavailable', 'cleared'].includes(s)).length === statuses.length) {
1651 return 'unavailable';
1656 export const aggregateLocationStatus = (names, logic, state) => {
1657 const statuses = names.map(name => getLocationStatus(name, logic, state));
1658 return getCombinedStatus(statuses);
1661 export const countRemainingLocations = (state, locations) =>
1662 locations.reduce((acc, cur) => state[cur] ? acc : acc + 1, 0);
1664 export const getGanonCrystals = (state) => state['ganon-crystals'];
1666 export const getGTCrystals = (state) => state['gt-crystals'];
1668 export const getGTBoss = (state, which) => state[`gt-${which}-boss`];
1670 export const hasDungeonBoss = (state, dungeon) =>
1671 !dungeon.boss || !!state[`${dungeon.id}-boss-defeated`];
1673 export const getDungeonBoss = (state, dungeon) =>
1674 state[`${dungeon.id}-boss`] || dungeon.boss || null;
1676 export const hasDungeonPrize = (state, dungeon) =>
1677 !dungeon.prize || !!state[`${dungeon.id}-prize-acquired`];
1679 export const getDungeonPrize = (state, dungeon) => state[`${dungeon.id}-prize`] || null;
1681 export const getDungeonClearedItems = (state, dungeon) => state[`${dungeon.id}-checks`] || 0;
1683 export const getDungeonRemainingItems = (state, dungeon) =>
1684 Math.max(0, dungeon.items - getDungeonClearedItems(state, dungeon));
1686 export const getDungeonAcquiredSKs = (state, dungeon) => state[`${dungeon.id}-small-key`] || 0;
1688 export const isDungeonCleared = (state, dungeon) => {
1689 const hasItems = !getDungeonRemainingItems(state, dungeon);
1690 const hasBoss = hasDungeonBoss(state, dungeon);
1691 const hasPrize = hasDungeonPrize(state, dungeon);
1692 return hasItems && hasBoss && hasPrize;
1695 export const aggregateDungeonStatus = (dungeon, logic, state) => {
1696 if (isDungeonCleared(state, dungeon)) {
1699 if (logic[dungeon.id] === 'unavailable') {
1700 return 'unavailable';
1702 const checks = [...dungeon.checks];
1703 if (['ct', 'gt'].includes(dungeon.id)) {
1704 checks.push(`${dungeon.id}-boss-killable`);
1706 const statuses = checks.map(name => getLocationStatus(name, logic, state));
1707 return getCombinedStatus(statuses);
1710 export const toggleBossDefeated = dungeon => toggleBoolean(`${dungeon.id}-boss-defeated`);
1712 export const setBossDefeated = (dungeon, defeated) =>
1713 state => ({ ...state, [`${dungeon.id}-boss-defeated`]: !!defeated });
1715 export const togglePrizeAcquired = dungeon => toggleBoolean(`${dungeon.id}-prize-acquired`);
1717 export const setPrizeAcquired = (dungeon, acquired) =>
1718 state => ({ ...state, [`${dungeon.id}-prize-acquired`]: !!acquired });
1720 export const addDungeonCheck = dungeon => increment(`${dungeon.id}-checks`, dungeon.items);
1722 export const removeDungeonCheck = dungeon => decrement(`${dungeon.id}-checks`, dungeon.items);
1724 export const resetDungeonChecks = dungeon => state => ({ ...state, [`${dungeon.id}-checks`]: 0 });
1726 export const completeDungeonChecks = dungeon =>
1727 state => ({ ...state, [`${dungeon.id}-checks`]: dungeon.items });
1729 export const makeEmptyState = () => {
1731 BOOLEAN_STATES.forEach(p => {
1732 state[p] = INITIAL[p] || false;
1734 INTEGER_STATES.forEach(p => {
1735 state[p] = INITIAL[p] || 0;
1737 DUNGEONS.forEach(dungeon => {
1738 state[`${dungeon.id}-map`] = false;
1739 state[`${dungeon.id}-compass`] = false;
1740 state[`${dungeon.id}-small-key`] = 0;
1741 state[`${dungeon.id}-big-key`] = false;
1742 state[`${dungeon.id}-checks`] = 0;
1744 state[`${dungeon.id}-boss`] = dungeon.boss;
1745 state[`${dungeon.id}-boss-defeated`] = false;
1747 if (dungeon.prize) {
1748 state[`${dungeon.id}-prize`] = 'crystal';
1749 state[`${dungeon.id}-prize-acquired`] = false;
1751 if (dungeon.id === 'gt') {
1752 state['gt-bot-boss'] = 'armos';
1753 state['gt-mid-boss'] = 'lanmolas';
1754 state['gt-top-boss'] = 'moldorm';
1757 OVERWORLD_LOCATIONS.forEach(location => {
1758 state[location.id] = false;
1760 UNDERWORLD_LOCATIONS.forEach(location => {
1761 state[location.id] = false;
1763 state['mm-medallion'] = null;
1764 state['tr-medallion'] = null;
1765 state['gt-crystals'] = 7;
1766 state['ganon-crystals'] = 7;
1770 const collectInventory = (state, data, prizeMap) => {
1771 state.bow = !!(data[INV_ADDR.RANDO_BOW] & 0x80);
1772 state.silvers = (data[INV_ADDR.RANDO_BOW] & 0xC0) == 0xC0;
1773 state['bowless-silvers'] = (data[INV_ADDR.RANDO_BOW] & 0xC0) == 0x40;
1774 state['blue-boomerang'] = !!(data[INV_ADDR.RANDO_BOOM] & 0x40);
1775 state['red-boomerang'] = !!(data[INV_ADDR.RANDO_BOOM] & 0x80);
1776 state.hookshot = !!data[INV_ADDR.HOOK];
1777 state.bomb = data[INV_ADDR.BOMB];
1778 state.mushroom = !!(data[INV_ADDR.RANDO_POWDER] & 0x20);
1779 state.powder = !!(data[INV_ADDR.RANDO_POWDER] & 0x10);
1780 state['fire-rod'] = !!data[INV_ADDR.FROD];
1781 state['ice-rod'] = !!data[INV_ADDR.IROD];
1782 state.bombos = !!data[INV_ADDR.BOMBOS];
1783 state.ether = !!data[INV_ADDR.ETHER];
1784 state.quake = !!data[INV_ADDR.QUAKE];
1785 state.lamp = !!data[INV_ADDR.LAMP];
1786 state.hammer = !!data[INV_ADDR.HAMMER];
1787 state.shovel = !!(data[INV_ADDR.RANDO_FLUTE] & 0x04);
1788 state.flute = !!(data[INV_ADDR.RANDO_FLUTE] & 0x03);
1789 state.duck = !!(data[INV_ADDR.RANDO_FLUTE] & 0x01);
1790 state.bugnet = !!data[INV_ADDR.BUGNET];
1791 state.book = !!data[INV_ADDR.BOOK];
1793 if (data[INV_ADDR.BOTTLE_1]) {
1796 if (data[INV_ADDR.BOTTLE_2]) {
1799 if (data[INV_ADDR.BOTTLE_3]) {
1802 if (data[INV_ADDR.BOTTLE_4]) {
1805 state.somaria = !!data[INV_ADDR.SOMARIA];
1806 state.byrna = !!data[INV_ADDR.BYRNA];
1807 state.cape = !!data[INV_ADDR.CAPE];
1808 state.mirror = !!data[INV_ADDR.MIRROR];
1809 state.lift = data[INV_ADDR.GLOVE];
1810 state.boots = !!data[INV_ADDR.BOOTS];
1811 state.flippers = !!data[INV_ADDR.FLIPPERS];
1812 state.moonpearl = !!data[INV_ADDR.MOONPEARL];
1813 state.sword = data[INV_ADDR.SWORD];
1814 state.shield = data[INV_ADDR.SHIELD];
1815 state.mail = data[INV_ADDR.ARMOR] + 1;
1816 state['heart-piece'] = data[INV_ADDR.HEART_PIECE];
1817 state['half-magic'] = data[INV_ADDR.MAGIC_USE] > 0;
1818 state['quarter-magic'] = data[INV_ADDR.MAGIC_USE] > 1;
1819 const map = getShort(data, INV_ADDR.MAP);
1820 const compass = getShort(data, INV_ADDR.COMPASS);
1821 const bigKey = getShort(data, INV_ADDR.BIG_KEY);
1822 DUNGEONS.forEach(dungeon => {
1823 state[`${dungeon.id}-map`] = !!(map & dungeon.mask);
1824 state[`${dungeon.id}-compass`] = !!(compass & dungeon.mask);
1825 state[`${dungeon.id}-small-key`] = data[INV_ADDR.RANDO_KEY_START + dungeon.offset];
1826 state[`${dungeon.id}-big-key`] = !!(bigKey & dungeon.mask);
1827 if (dungeon.prize) {
1828 const isCrystal = prizeMap[dungeon.offset].isCrystal;
1829 const prizeFlags = data[isCrystal ? INV_ADDR.CRYSTALS : INV_ADDR.PENDANTS];
1830 state[`${dungeon.id}-prize-acquired`] = !!(prizeFlags & prizeMap[dungeon.offset].mask);
1835 const collectOverworld = (state, data) => {
1836 OVERWORLD_LOCATIONS.forEach(location => {
1837 state[location.id] = !!(data[location.address] & location.mask);
1841 const collectUnderworld = (state, data) => {
1842 UNDERWORLD_LOCATIONS.forEach(location => {
1843 state[location.id] = isChestOpen(data, location.room, location.chest);
1845 DUNGEONS.forEach(dungeon => {
1846 state[`${dungeon.id}-boss-defeated`] = isBossDefeated(data, dungeon.bossRoom);
1850 export const computeState = (config, data, prizeMap) => {
1852 collectInventory(state, data.slice(SRAM_ADDR.INV_START), prizeMap);
1853 collectOverworld(state, data);
1854 collectUnderworld(state, data.slice(SRAM_ADDR.ROOM_DATA_START));
1855 const amounts = getDungeonAmounts(config, state);
1856 DUNGEONS.forEach(dungeon => {
1857 state[`${dungeon.id}-checks`] = amounts[dungeon.id];
1862 const getDungeonAmounts = (config, state) => {
1864 DUNGEONS.forEach(dungeon => {
1866 let total = dungeon.checks.length;
1867 dungeon.checks.forEach(check => {
1872 if (!config.wildMap && state[`${dungeon.id}-map`]) {
1876 if (!config.wildCompass && state[`${dungeon.id}-compass`]) {
1880 if (!config.wildSmall) {
1881 amount -= Math.min(state[`${dungeon.id}-small-key`], dungeon.sk);
1882 total -= dungeon.sk;
1884 if (!config.wildBig && !dungeon.dropBk && state[`${dungeon.id}-big-key`]) {
1888 amounts[dungeon.id] = Math.min(total, amount);
1893 export const mergeStates = (autoState, manualState) => {
1894 const next = { ...autoState };
1895 BOOLEAN_STATES.forEach(name => {
1896 if (manualState[name]) {
1900 INTEGER_STATES.forEach(name => {
1901 next[name] = Math.max(autoState[name] || 0, manualState[name] || 0);
1903 DUNGEONS.forEach(dungeon => {
1904 next[`${dungeon.id}-small-key`] += manualState[`${dungeon.id}-small-key`] || 0;
1905 next[`${dungeon.id}-checks`] += manualState[`${dungeon.id}-checks`] || 0;
1906 if (manualState[`${dungeon.id}-big-key`]) {
1907 next[`${dungeon.id}-big-key`] = true;
1909 if (manualState[`${dungeon.id}-compass`]) {
1910 next[`${dungeon.id}-compass`] = true;
1912 if (manualState[`${dungeon.id}-map`]) {
1913 next[`${dungeon.id}-map`] = true;
1915 if (manualState[`${dungeon.id}-boss-defeated`]) {
1916 next[`${dungeon.id}-boss-defeated`] = true;
1918 if (manualState[`${dungeon.id}-prize`] &&
1919 manualState[`${dungeon.id}-prize`] !== 'crystal'
1921 next[`${dungeon.id}-prize`] = manualState[`${dungeon.id}-prize`];
1922 } else if (!next[`${dungeon.id}-prize`]) {
1923 next[`${dungeon.id}-prize`] = 'crystal';
1925 if (manualState[`${dungeon.id}-prize-acquired`]) {
1926 next[`${dungeon.id}-prize-acquired`] = true;
1929 OVERWORLD_LOCATIONS.forEach(loc => {
1930 if (manualState[loc.id]) {
1931 next[loc.id] = true;
1934 UNDERWORLD_LOCATIONS.forEach(loc => {
1935 if (manualState[loc.id]) {
1936 next[loc.id] = true;
1939 next['mm-medallion'] = manualState['mm-medallion'];
1940 next['tr-medallion'] = manualState['tr-medallion'];
1941 next['gt-crystals'] = manualState['gt-crystals'];
1942 next['ganon-crystals'] = manualState['ganon-crystals'];
1943 next['gt-bot-boss'] = manualState['gt-bot-boss'];
1944 next['gt-mid-boss'] = manualState['gt-mid-boss'];
1945 next['gt-top-boss'] = manualState['gt-top-boss'];
1946 //console.log(next);