WRAM_ADDR,
buildPrizeMap,
} from '../../helpers/alttp-ram';
-import { computeState, mergeStates } from '../../helpers/tracker';
+import { computeState } from '../../helpers/tracker';
import { useSNES } from '../../hooks/snes';
import { useTracker } from '../../hooks/tracker';
sock,
status,
} = useSNES();
- const { config, setState } = useTracker();
+ const { config, setAutoState } = useTracker();
const { t } = useTranslation();
const enable = React.useCallback(() => {
const saveStart = WRAM_ADDR.SAVE_DATA;
const saveSize = SRAM_ADDR.INV_END;
sock.current.readWRAM(saveStart, saveSize, (data) => {
- const computed = computeState(data, prizeMap);
- setState(s => mergeStates(config, s, computed));
+ const computed = computeState(config, data, prizeMap);
+ setAutoState(computed);
});
};
const fetchPrizes = () => {
return 'tracking';
}, [enabled, status]);
- return <div>
+ return <div className="auto-tracking">
{['disconnected', 'error', 'no-device'].includes(statusMsg) ?
<Icon.WARNING
className="me-2 text-warning"
import React from 'react';
-import CountDisplay from './CountDisplay';
import ToggleIcon from './ToggleIcon';
-import { useTracker } from '../../hooks/tracker';
const Equipment = () => {
- const { state } = useTracker();
-
return <div className="equipment">
<div className="item">
<ToggleIcon controller={ToggleIcon.simpleController} icons={['boots']} />
};
const Map = () => {
- const { dungeons, setState, state } = useTracker();
+ const { dungeons, setManualState, state } = useTracker();
const mapDungeon = React.useCallback(dungeon => {
const definition = dungeons.find(d => d.id === dungeon.id);
remaining,
handlePrimary: () => {
if (getDungeonRemainingItems(state, definition)) {
- setState(addDungeonCheck(definition));
+ setManualState(addDungeonCheck(definition));
} else if (
!hasDungeonBoss(state, definition) || !hasDungeonPrize(state, definition)
) {
if (definition.boss) {
- setState(setBossDefeated(definition, true));
+ setManualState(setBossDefeated(definition, true));
}
if (definition.prize) {
- setState(setPrizeAcquired(definition, true));
+ setManualState(setPrizeAcquired(definition, true));
}
} else {
- setState(resetDungeonChecks(definition));
+ setManualState(resetDungeonChecks(definition));
if (definition.boss) {
- setState(setBossDefeated(definition, false));
+ setManualState(setBossDefeated(definition, false));
}
if (definition.prize) {
- setState(setPrizeAcquired(definition, false));
+ setManualState(setPrizeAcquired(definition, false));
}
}
},
handleSecondary: () => {
if (isDungeonCleared(state, definition)) {
if (definition.items) {
- setState(removeDungeonCheck(definition));
+ setManualState(removeDungeonCheck(definition));
}
if (definition.boss) {
- setState(setBossDefeated(definition, false));
+ setManualState(setBossDefeated(definition, false));
}
if (definition.prize) {
- setState(setPrizeAcquired(definition, false));
+ setManualState(setPrizeAcquired(definition, false));
}
} else if (getDungeonClearedItems(state, definition)) {
- setState(removeDungeonCheck(definition));
+ setManualState(removeDungeonCheck(definition));
} else {
- setState(completeDungeonChecks(definition));
+ setManualState(completeDungeonChecks(definition));
if (definition.boss) {
- setState(setBossDefeated(definition, true));
+ setManualState(setBossDefeated(definition, true));
}
if (definition.prize) {
- setState(setPrizeAcquired(definition, true));
+ setManualState(setPrizeAcquired(definition, true));
}
}
},
};
- }, [dungeons, setState, state]);
+ }, [dungeons, setManualState, state]);
const mapLocation = React.useCallback(loc => {
const remaining = countRemainingLocations(state, loc.checks);
status,
handlePrimary: () => {
if (remaining) {
- setState(clearAll(loc.checks));
+ setManualState(clearAll(loc.checks));
} else {
- setState(unclearAll(loc.checks));
+ setManualState(unclearAll(loc.checks));
}
},
handleSecondary: () => {
if (remaining) {
- setState(clearAll(loc.checks));
+ setManualState(clearAll(loc.checks));
} else {
- setState(unclearAll(loc.checks));
+ setManualState(unclearAll(loc.checks));
}
},
};
- }, [setState, state]);
+ }, [setManualState, state]);
const lwDungeons = React.useMemo(() => LW_DUNGEONS.map(mapDungeon), [mapDungeon]);
const lwLocations = React.useMemo(() => LW_LOCATIONS.map(mapLocation), [mapLocation]);
import { useTracker } from '../../hooks/tracker';
const ToggleIcon = ({ controller, className, icons }) => {
- const { state, setState } = useTracker();
+ const { setManualState, state } = useTracker();
const activeController = controller || ToggleIcon.nullController;
const active = activeController.getActive(state, icons);
const defaultIcon = activeController.getDefault(state, icons);
return <span
className={classNames.join(' ')}
onClick={(e) => {
- activeController.handlePrimary(state, setState, icons);
+ activeController.handlePrimary(state, setManualState, icons);
e.preventDefault();
e.stopPropagation();
}}
onContextMenu={(e) => {
- activeController.handleSecondary(state, setState, icons);
+ activeController.handleSecondary(state, setManualState, icons);
e.preventDefault();
e.stopPropagation();
}}
<Toolbar />
<div className="d-flex">
<div>
- <Items />
- <Equipment />
+ <div className="inventory">
+ <Items />
+ <Equipment />
+ </div>
<Dungeons />
</div>
<div className="flex-fill">
});
};
-export const computeState = (data, prizeMap) => {
+export const computeState = (config, data, prizeMap) => {
const state = {};
collectInventory(state, data.slice(SRAM_ADDR.INV_START), prizeMap);
collectOverworld(state, data);
collectUnderworld(state, data.slice(SRAM_ADDR.ROOM_DATA_START));
+ const amounts = getDungeonAmounts(config, state);
+ DUNGEONS.forEach(dungeon => {
+ state[`${dungeon.id}-checks`] = amounts[dungeon.id];
+ });
return state;
};
return amounts;
};
-export const mergeStates = (config, cur, inc) => {
- const next = { ...cur, ...inc };
- const amounts = getDungeonAmounts(config, inc);
+export const mergeStates = (autoState, manualState) => {
+ const next = { ...autoState };
+ BOOLEAN_STATES.forEach(name => {
+ if (manualState[name]) {
+ next[name] = true;
+ }
+ });
+ INTEGER_STATES.forEach(name => {
+ next[name] = Math.max(autoState[name] || 0, manualState[name] || 0);
+ });
DUNGEONS.forEach(dungeon => {
- next[`${dungeon.id}-checks`] = amounts[dungeon.id];
+ next[`${dungeon.id}-small-key`] += manualState[`${dungeon.id}-small-key`] || 0;
+ next[`${dungeon.id}-checks`] += manualState[`${dungeon.id}-checks`] || 0;
+ if (manualState[`${dungeon.id}-big-key`]) {
+ next[`${dungeon.id}-big-key`] = true;
+ }
+ if (manualState[`${dungeon.id}-compass`]) {
+ next[`${dungeon.id}-compass`] = true;
+ }
+ if (manualState[`${dungeon.id}-map`]) {
+ next[`${dungeon.id}-map`] = true;
+ }
+ if (manualState[`${dungeon.id}-boss-defeated`]) {
+ next[`${dungeon.id}-boss-defeated`] = true;
+ }
+ if (manualState[`${dungeon.id}-prize`] &&
+ manualState[`${dungeon.id}-prize`] !== 'crystal'
+ ) {
+ next[`${dungeon.id}-prize`] = manualState[`${dungeon.id}-prize`];
+ } else if (!next[`${dungeon.id}-prize`]) {
+ next[`${dungeon.id}-prize`] = 'crystal';
+ }
+ if (manualState[`${dungeon.id}-prize-acquired`]) {
+ next[`${dungeon.id}-prize-acquired`] = true;
+ }
+ });
+ OVERWORLD_LOCATIONS.forEach(loc => {
+ if (manualState[loc.id]) {
+ next[loc.id] = true;
+ }
+ });
+ UNDERWORLD_LOCATIONS.forEach(loc => {
+ if (manualState[loc.id]) {
+ next[loc.id] = true;
+ }
});
+ next['mm-medallion'] = manualState['mm-medallion'];
+ next['tr-medallion'] = manualState['tr-medallion'];
//console.log(next);
return next;
};
import PropTypes from 'prop-types';
import React from 'react';
-import { CONFIG, DUNGEONS, makeEmptyState } from '../helpers/tracker';
+import { CONFIG, DUNGEONS, makeEmptyState, mergeStates } from '../helpers/tracker';
const context = React.createContext({});
export const TrackerProvider = ({ children }) => {
const [config, setConfig] = React.useState(CONFIG);
const [state, setState] = React.useState(makeEmptyState());
+ const [autoState, setAutoState] = React.useState(makeEmptyState());
+ const [manualState, setManualState] = React.useState(makeEmptyState());
const [dungeons, setDungeons] = React.useState(DUNGEONS);
React.useEffect(() => {
setDungeons(newDungeons);
}, [config]);
+ React.useEffect(() => {
+ setState(mergeStates(autoState, manualState));
+ }, [autoState, manualState]);
+
const value = React.useMemo(() => {
- return { config, setConfig, dungeons, setState, state };
+ return { config, setConfig, dungeons, setAutoState, setManualState, state };
}, [config, dungeons, state]);
return <context.Provider value={value}>
.tracker {
+ .auto-tracking {
+ .custom-toggle {
+ vertical-align: middle;
+ }
+ }
.count-display,
.med-display {
background: black;
}
}
}
+ .dungeons {
+ padding: 1ex;
+ }
.dungeon-ep,
.dungeon-pd {
margin-top: 1ex;
gap: 1ex;
padding: 1ex;
}
+ .inventory {
+ font-size: 110%;
+ }
.items {
display: grid;
grid-template-columns: 3em 3em 3em 3em 3em;