+++ /dev/null
-import PropTypes from 'prop-types';
-import React from 'react';
-import { useTranslation } from 'react-i18next';
-
-import {
- addDungeonCheck,
- aggregateDungeonStatus,
- aggregateLocationStatus,
- clearAll,
- completeDungeonChecks,
- countRemainingLocations,
- getDungeonClearedItems,
- getDungeonRemainingItems,
- hasDungeonBoss,
- hasDungeonPrize,
- isDungeonCleared,
- removeDungeonCheck,
- resetDungeonChecks,
- setBossDefeated,
- setPrizeAcquired,
- unclearAll,
-} from '../../helpers/tracker';
-import { useTracker } from '../../hooks/tracker';
-
-const LW_DUNGEONS = [
- {
- id: 'hc',
- x: 0.5,
- y: 0.5,
- },
- {
- id: 'ct',
- x: 0.5,
- y: 0.4,
- },
- {
- id: 'ep',
- x: 0.95,
- y: 0.42,
- },
- {
- id: 'dp',
- x: 0.075,
- y: 0.8,
- },
- {
- id: 'th',
- x: 0.56,
- y: 0.05,
- },
-];
-
-const LW_LOCATIONS = [
- {
- id: 'aginah',
- checks: [
- 'aginah',
- ],
- x: 0.2,
- y: 0.83,
- },
- {
- id: 'blinds-hut',
- checks: [
- 'blinds-hut-top',
- 'blinds-hut-left',
- 'blinds-hut-right',
- 'blinds-hut-far-left',
- 'blinds-hut-far-right',
- ],
- x: 0.14,
- y: 0.42,
- },
- {
- id: 'bombos-tablet',
- checks: [
- 'bombos-tablet',
- ],
- x: 0.225,
- y: 0.925,
- },
- {
- id: 'bonk-rocks',
- checks: [
- 'bonk-rocks',
- ],
- x: 0.4,
- y: 0.3,
- },
- {
- id: 'bottle-vendor',
- checks: [
- 'bottle-vendor',
- ],
- x: 0.1,
- y: 0.475,
- },
- {
- id: 'cave-45',
- checks: [
- 'cave-45',
- ],
- x: 0.27,
- y: 0.83,
- },
- {
- id: 'checkerboard',
- checks: [
- 'checkerboard',
- ],
- x: 0.18,
- y: 0.78,
- },
- {
- id: 'chicken-house',
- checks: [
- 'chicken-house',
- ],
- x: 0.1,
- y: 0.53,
- },
- {
- id: 'dam',
- checks: [
- 'flooded-chest',
- 'sunken-treasure',
- ],
- x: 0.4675,
- y: 0.9375,
- },
- {
- id: 'desert-ledge',
- checks: [
- 'desert-ledge',
- ],
- x: 0.025,
- y: 0.9,
- },
- {
- id: 'ether-tablet',
- checks: [
- 'ether-tablet',
- ],
- x: 0.425,
- y: 0.025,
- },
- {
- id: 'floating-island',
- checks: [
- 'floating-island',
- ],
- x: 0.8,
- y: 0.025,
- },
- {
- id: 'flute-spot',
- checks: [
- 'flute-spot',
- ],
- x: 0.3,
- y: 0.675,
- },
- {
- id: 'graveyard-ledge',
- checks: [
- 'graveyard-ledge',
- ],
- x: 0.57,
- y: 0.28,
- },
- {
- id: 'hobo',
- checks: [
- 'hobo',
- ],
- x: 0.7,
- y: 0.7,
- },
- {
- id: 'ice-rod-cave',
- checks: [
- 'ice-rod-cave',
- ],
- x: 0.9,
- y: 0.76,
- },
- {
- id: 'kak-well',
- checks: [
- 'kak-well-top',
- 'kak-well-left',
- 'kak-well-mid',
- 'kak-well-right',
- 'kak-well-bottom',
- ],
- x: 0.04,
- y: 0.425,
- },
- {
- id: 'kings-tomb',
- checks: [
- 'kings-tomb',
- ],
- x: 0.62,
- y: 0.3,
- },
- {
- id: 'lake-hylia-island',
- checks: [
- 'lake-hylia-island',
- ],
- x: 0.725,
- y: 0.8375,
- },
- {
- id: 'library',
- checks: [
- 'library',
- ],
- x: 0.15,
- y: 0.65,
- },
- {
- id: 'links-house',
- checks: [
- 'links-house',
- ],
- x: 0.55,
- y: 0.6875,
- },
- {
- id: 'lost-woods-hideout',
- checks: [
- 'lost-woods-hideout',
- ],
- x: 0.19,
- y: 0.14,
- },
- {
- id: 'lumberjack',
- checks: [
- 'lumberjack',
- ],
- x: 0.3,
- y: 0.07,
- },
- {
- id: 'magic-bat',
- checks: [
- 'magic-bat',
- ],
- x: 0.325,
- y: 0.55,
- },
- {
- id: 'mimic-cave',
- checks: [
- 'mimic-cave',
- ],
- x: 0.85,
- y: 0.1,
- },
- {
- id: 'mini-moldorm-cave',
- checks: [
- 'mini-moldorm-left',
- 'mini-moldorm-right',
- 'mini-moldorm-far-left',
- 'mini-moldorm-far-right',
- 'mini-moldorm-npc',
- ],
- x: 0.65,
- y: 0.95,
- },
- {
- id: 'mushroom-spot',
- checks: [
- 'mushroom-spot',
- ],
- x: 0.125,
- y: 0.08,
- },
- {
- id: 'old-man',
- checks: [
- 'old-man',
- ],
- x: 0.405,
- y: 0.195,
- },
- {
- id: 'paradox-cave',
- checks: [
- 'paradox-lower-far-left',
- 'paradox-lower-left',
- 'paradox-lower-right',
- 'paradox-lower-far-right',
- 'paradox-lower-mid',
- 'paradox-upper-left',
- 'paradox-upper-right',
- ],
- x: 0.85,
- y: 0.2,
- },
- {
- id: 'pedestal',
- checks: [
- 'pedestal',
- ],
- x: 0.03,
- y: 0.05,
- },
- {
- id: 'potion-shop',
- checks: [
- 'potion-shop',
- ],
- x: 0.8,
- y: 0.325,
- },
- {
- id: 'race-game',
- checks: [
- 'race-game',
- ],
- x: 0.025,
- y: 0.7,
- },
- {
- id: 'saha',
- checks: [
- 'saha',
- ],
- x: 0.815,
- y: 0.465,
- },
- {
- id: 'saha-hut',
- checks: [
- 'saha-left',
- 'saha-mid',
- 'saha-right',
- ],
- x: 0.815,
- y: 0.42,
- },
- {
- id: 'sick-kid',
- checks: [
- 'sick-kid',
- ],
- x: 0.155,
- y: 0.525,
- },
- {
- id: 'uncle',
- checks: [
- 'uncle',
- 'secret-passage',
- ],
- x: 0.6,
- y: 0.4,
- },
- {
- id: 'spec-rock',
- checks: [
- 'spec-rock',
- ],
- x: 0.48,
- y: 0.09,
- },
- {
- id: 'spec-rock-cave',
- checks: [
- 'spec-rock-cave',
- ],
- x: 0.48,
- y: 0.14,
- },
- {
- id: 'spiral-cave',
- checks: [
- 'spiral-cave',
- ],
- x: 0.8,
- y: 0.1,
- },
- {
- id: 'tavern',
- checks: [
- 'tavern',
- ],
- x: 0.16,
- y: 0.58,
- },
- {
- id: 'waterfall-fairy',
- checks: [
- 'waterfall-fairy-left',
- 'waterfall-fairy-right',
- ],
- x: 0.9,
- y: 0.15,
- },
- {
- id: 'zora',
- checks: [
- 'zora',
- ],
- x: 0.975,
- y: 0.12,
- },
- {
- id: 'zora-ledge',
- checks: [
- 'zora-ledge',
- ],
- x: 0.975,
- y: 0.165,
- },
-];
-
-const DW_DUNGEONS = [
- {
- id: 'pd',
- x: 0.95,
- y: 0.42,
- },
- {
- id: 'sp',
- x: 0.4675,
- y: 0.9375,
- },
- {
- id: 'sw',
- x: 0.05,
- y: 0.05,
- },
- {
- id: 'tt',
- x: 0.125,
- y: 0.475,
- },
- {
- id: 'ip',
- x: 0.7975,
- y: 0.86,
- },
- {
- id: 'mm',
- x: 0.12,
- y: 0.82,
- },
- {
- id: 'tr',
- x: 0.94,
- y: 0.06,
- },
- {
- id: 'gt',
- x: 0.56,
- y: 0.05,
- },
-];
-
-const DW_LOCATIONS = [
- {
- id: 'blacksmith',
- checks: [
- 'blacksmith',
- ],
- x: 0.15,
- y: 0.65,
- },
- {
- id: 'brewery',
- checks: [
- 'brewery',
- ],
- x: 0.1,
- y: 0.6,
- },
- {
- id: 'bumper-cave',
- checks: [
- 'bumper-cave',
- ],
- x: 0.325,
- y: 0.15,
- },
- {
- id: 'c-house',
- checks: [
- 'c-house',
- ],
- x: 0.2,
- y: 0.5,
- },
- {
- id: 'catfish',
- checks: [
- 'catfish',
- ],
- x: 0.9,
- y: 0.175,
- },
- {
- id: 'chest-game',
- checks: [
- 'chest-game',
- ],
- x: 0.05,
- y: 0.45,
- },
- {
- id: 'digging-game',
- checks: [
- 'digging-game',
- ],
- x: 0.05,
- y: 0.7,
- },
- {
- id: 'hammer-pegs',
- checks: [
- 'hammer-pegs',
- ],
- x: 0.3125,
- y: 0.6,
- },
- {
- id: 'hookshot-cave',
- checks: [
- 'hookshot-cave-tl',
- 'hookshot-cave-tr',
- 'hookshot-cave-bl',
- ],
- x: 0.85,
- y: 0.02,
- },
- {
- id: 'hookshot-cave-bonk',
- checks: [
- 'hookshot-cave-br',
- ],
- x: 0.85,
- y: 0.065,
- },
- {
- id: 'hype-cave',
- checks: [
- 'hype-cave-top',
- 'hype-cave-left',
- 'hype-cave-right',
- 'hype-cave-bottom',
- 'hype-cave-npc',
- ],
- x: 0.6,
- y: 0.75,
- },
- {
- id: 'mire-shed',
- checks: [
- 'mire-shed-left',
- 'mire-shed-right',
- ],
- x: 0.04,
- y: 0.8,
- },
- {
- id: 'purple-chest',
- checks: [
- 'purple-chest',
- ],
- x: 0.3125,
- y: 0.525,
- },
- {
- id: 'pyramid',
- checks: [
- 'pyramid',
- ],
- x: 0.575,
- y: 0.45,
- },
- {
- id: 'pyramid-fairy',
- checks: [
- 'pyramid-fairy-left',
- 'pyramid-fairy-right',
- ],
- x: 0.45,
- y: 0.5,
- },
- {
- id: 'spike-cave',
- checks: [
- 'spike-cave',
- ],
- x: 0.575,
- y: 0.15,
- },
- {
- id: 'stumpy',
- checks: [
- 'stumpy',
- ],
- x: 0.3125,
- y: 0.6875,
- },
- {
- id: 'super-bunny',
- checks: [
- 'super-bunny-top',
- 'super-bunny-bottom',
- ],
- x: 0.85,
- y: 0.15,
- },
-];
-
-const Location = ({ number, l, size }) => {
- const { t } = useTranslation();
-
- const classNames = ['location', `status-${l.status}`];
- if (size) {
- classNames.push(`size-${size}`);
- }
- if (l.handlePrimary) {
- classNames.push('clickable');
- }
-
- return <g
- className={classNames.join(' ')}
- onClick={(e) => {
- l.handlePrimary();
- e.preventDefault();
- e.stopPropagation();
- }}
- onContextMenu={(e) => {
- l.handleSecondary();
- e.preventDefault();
- e.stopPropagation();
- }}
- transform={`translate(${l.x} ${l.y})`}
- >
- <title>{t(`tracker.location.${l.id}`)}</title>
- <rect className="box" x="0" y="0" />
- {number && l.remaining ?
- <text className="text" x="0" y="0">{l.remaining}</text>
- : null}
- </g>;
-};
-
-Location.propTypes = {
- number: PropTypes.bool,
- l: PropTypes.shape({
- id: PropTypes.string,
- x: PropTypes.number,
- y: PropTypes.number,
- number: PropTypes.number,
- remaining: PropTypes.number,
- status: PropTypes.string,
- handlePrimary: PropTypes.func,
- handleSecondary: PropTypes.func,
- }),
- size: PropTypes.string,
-};
-
-const makeBackground = (src, level) => {
- const amount = Math.pow(2, Math.max(0, level - 8));
- const size = 1 / amount;
- const tiles = [];
- for (let y = 0; y < amount; ++y) {
- for (let x = 0; x < amount; ++x) {
- tiles.push(<image
- key={`${x}-${y}`}
- x={x * size}
- y={y * size}
- width={size * 1.002}
- height={size * 1.002}
- href={`/media/alttp/map/${src}/${level}/${x}_${y}.png`}
- />);
- }
- }
- return tiles;
-};
-
-const Map = () => {
- const { dungeons, logic, setManualState, state } = useTracker();
-
- const mapDungeon = React.useCallback(dungeon => {
- const definition = dungeons.find(d => d.id === dungeon.id);
- const remaining = getDungeonRemainingItems(state, definition);
- const status = aggregateDungeonStatus(definition, logic, state);
- return {
- ...dungeon,
- status,
- remaining,
- handlePrimary: () => {
- if (getDungeonRemainingItems(state, definition)) {
- setManualState(addDungeonCheck(definition));
- } else if (
- !hasDungeonBoss(state, definition) || !hasDungeonPrize(state, definition)
- ) {
- if (definition.boss) {
- setManualState(setBossDefeated(definition, true));
- }
- if (definition.prize) {
- setManualState(setPrizeAcquired(definition, true));
- }
- } else {
- setManualState(resetDungeonChecks(definition));
- if (definition.boss) {
- setManualState(setBossDefeated(definition, false));
- }
- if (definition.prize) {
- setManualState(setPrizeAcquired(definition, false));
- }
- }
- },
- handleSecondary: () => {
- if (isDungeonCleared(state, definition)) {
- if (definition.items) {
- setManualState(removeDungeonCheck(definition));
- }
- if (definition.boss) {
- setManualState(setBossDefeated(definition, false));
- }
- if (definition.prize) {
- setManualState(setPrizeAcquired(definition, false));
- }
- } else if (getDungeonClearedItems(state, definition)) {
- setManualState(removeDungeonCheck(definition));
- } else {
- setManualState(completeDungeonChecks(definition));
- if (definition.boss) {
- setManualState(setBossDefeated(definition, true));
- }
- if (definition.prize) {
- setManualState(setPrizeAcquired(definition, true));
- }
- }
- },
- };
- }, [dungeons, logic, setManualState, state]);
-
- const mapLocation = React.useCallback(loc => {
- const remaining = countRemainingLocations(state, loc.checks);
- const status = aggregateLocationStatus(loc.checks, logic, state);
- return {
- ...loc,
- remaining,
- status,
- handlePrimary: () => {
- if (remaining) {
- setManualState(clearAll(loc.checks));
- } else {
- setManualState(unclearAll(loc.checks));
- }
- },
- handleSecondary: () => {
- if (remaining) {
- setManualState(clearAll(loc.checks));
- } else {
- setManualState(unclearAll(loc.checks));
- }
- },
- };
- }, [logic, setManualState, state]);
-
- const lwDungeons = React.useMemo(() => LW_DUNGEONS.map(mapDungeon), [mapDungeon]);
- const lwLocations = React.useMemo(() => LW_LOCATIONS.map(mapLocation), [mapLocation]);
-
- const dwDungeons = React.useMemo(() => DW_DUNGEONS.map(mapDungeon), [mapDungeon]);
- const dwLocations = React.useMemo(() => DW_LOCATIONS.map(mapLocation), [mapLocation]);
-
- return <div className="tracker-map">
- <svg
- xmlns="http://www.w3.org/2000/svg"
- className="canvas"
- width="2"
- height="1"
- viewBox="0 0 2 1"
- onContextMenu={(e) => {
- e.preventDefault();
- e.stopPropagation();
- }}
- >
- <g className="light-world">
- <g className="background">
- {makeBackground('lw_files', 10)}
- </g>
- <g className="locations">
- {lwLocations.map(l =>
- <Location key={l.id} l={l} />
- )}
- {lwDungeons.map(l =>
- <Location key={l.id} number l={l} size="lg" />
- )}
- </g>
- </g>
- <g className="dark-world" transform="translate(1 0)">
- <g className="background">
- {makeBackground('dw_files', 10)}
- </g>
- <g className="locations">
- {dwLocations.map(l =>
- <Location key={l.id} l={l} />
- )}
- {dwDungeons.map(l =>
- <Location key={l.id} number l={l} size="lg" />
- )}
- </g>
- </g>
- </svg>
- </div>;
-};
-
-export default Map;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import {
+ addDungeonCheck,
+ aggregateDungeonStatus,
+ aggregateLocationStatus,
+ clearAll,
+ completeDungeonChecks,
+ countRemainingLocations,
+ getDungeonClearedItems,
+ getDungeonRemainingItems,
+ hasDungeonBoss,
+ hasDungeonPrize,
+ isDungeonCleared,
+ removeDungeonCheck,
+ resetDungeonChecks,
+ setBossDefeated,
+ setPrizeAcquired,
+ unclearAll,
+} from '../../../helpers/tracker';
+import { useTracker } from '../../../hooks/tracker';
+
+const LW_DUNGEONS = [
+ {
+ id: 'hc',
+ x: 0.5,
+ y: 0.5,
+ },
+ {
+ id: 'ct',
+ x: 0.5,
+ y: 0.4,
+ },
+ {
+ id: 'ep',
+ x: 0.95,
+ y: 0.42,
+ },
+ {
+ id: 'dp',
+ x: 0.075,
+ y: 0.8,
+ },
+ {
+ id: 'th',
+ x: 0.56,
+ y: 0.05,
+ },
+];
+
+const LW_LOCATIONS = [
+ {
+ id: 'aginah',
+ checks: [
+ 'aginah',
+ ],
+ x: 0.2,
+ y: 0.83,
+ },
+ {
+ id: 'blinds-hut',
+ checks: [
+ 'blinds-hut-top',
+ 'blinds-hut-left',
+ 'blinds-hut-right',
+ 'blinds-hut-far-left',
+ 'blinds-hut-far-right',
+ ],
+ x: 0.14,
+ y: 0.42,
+ },
+ {
+ id: 'bombos-tablet',
+ checks: [
+ 'bombos-tablet',
+ ],
+ x: 0.225,
+ y: 0.925,
+ },
+ {
+ id: 'bonk-rocks',
+ checks: [
+ 'bonk-rocks',
+ ],
+ x: 0.4,
+ y: 0.3,
+ },
+ {
+ id: 'bottle-vendor',
+ checks: [
+ 'bottle-vendor',
+ ],
+ x: 0.1,
+ y: 0.475,
+ },
+ {
+ id: 'cave-45',
+ checks: [
+ 'cave-45',
+ ],
+ x: 0.27,
+ y: 0.83,
+ },
+ {
+ id: 'checkerboard',
+ checks: [
+ 'checkerboard',
+ ],
+ x: 0.18,
+ y: 0.78,
+ },
+ {
+ id: 'chicken-house',
+ checks: [
+ 'chicken-house',
+ ],
+ x: 0.1,
+ y: 0.53,
+ },
+ {
+ id: 'dam',
+ checks: [
+ 'flooded-chest',
+ 'sunken-treasure',
+ ],
+ x: 0.4675,
+ y: 0.9375,
+ },
+ {
+ id: 'desert-ledge',
+ checks: [
+ 'desert-ledge',
+ ],
+ x: 0.025,
+ y: 0.9,
+ },
+ {
+ id: 'ether-tablet',
+ checks: [
+ 'ether-tablet',
+ ],
+ x: 0.425,
+ y: 0.025,
+ },
+ {
+ id: 'floating-island',
+ checks: [
+ 'floating-island',
+ ],
+ x: 0.8,
+ y: 0.025,
+ },
+ {
+ id: 'flute-spot',
+ checks: [
+ 'flute-spot',
+ ],
+ x: 0.3,
+ y: 0.675,
+ },
+ {
+ id: 'graveyard-ledge',
+ checks: [
+ 'graveyard-ledge',
+ ],
+ x: 0.57,
+ y: 0.28,
+ },
+ {
+ id: 'hobo',
+ checks: [
+ 'hobo',
+ ],
+ x: 0.7,
+ y: 0.7,
+ },
+ {
+ id: 'ice-rod-cave',
+ checks: [
+ 'ice-rod-cave',
+ ],
+ x: 0.9,
+ y: 0.76,
+ },
+ {
+ id: 'kak-well',
+ checks: [
+ 'kak-well-top',
+ 'kak-well-left',
+ 'kak-well-mid',
+ 'kak-well-right',
+ 'kak-well-bottom',
+ ],
+ x: 0.04,
+ y: 0.425,
+ },
+ {
+ id: 'kings-tomb',
+ checks: [
+ 'kings-tomb',
+ ],
+ x: 0.62,
+ y: 0.3,
+ },
+ {
+ id: 'lake-hylia-island',
+ checks: [
+ 'lake-hylia-island',
+ ],
+ x: 0.725,
+ y: 0.8375,
+ },
+ {
+ id: 'library',
+ checks: [
+ 'library',
+ ],
+ x: 0.15,
+ y: 0.65,
+ },
+ {
+ id: 'links-house',
+ checks: [
+ 'links-house',
+ ],
+ x: 0.55,
+ y: 0.6875,
+ },
+ {
+ id: 'lost-woods-hideout',
+ checks: [
+ 'lost-woods-hideout',
+ ],
+ x: 0.19,
+ y: 0.14,
+ },
+ {
+ id: 'lumberjack',
+ checks: [
+ 'lumberjack',
+ ],
+ x: 0.3,
+ y: 0.07,
+ },
+ {
+ id: 'magic-bat',
+ checks: [
+ 'magic-bat',
+ ],
+ x: 0.325,
+ y: 0.55,
+ },
+ {
+ id: 'mimic-cave',
+ checks: [
+ 'mimic-cave',
+ ],
+ x: 0.85,
+ y: 0.1,
+ },
+ {
+ id: 'mini-moldorm-cave',
+ checks: [
+ 'mini-moldorm-left',
+ 'mini-moldorm-right',
+ 'mini-moldorm-far-left',
+ 'mini-moldorm-far-right',
+ 'mini-moldorm-npc',
+ ],
+ x: 0.65,
+ y: 0.95,
+ },
+ {
+ id: 'mushroom-spot',
+ checks: [
+ 'mushroom-spot',
+ ],
+ x: 0.125,
+ y: 0.08,
+ },
+ {
+ id: 'old-man',
+ checks: [
+ 'old-man',
+ ],
+ x: 0.405,
+ y: 0.195,
+ },
+ {
+ id: 'paradox-cave',
+ checks: [
+ 'paradox-lower-far-left',
+ 'paradox-lower-left',
+ 'paradox-lower-right',
+ 'paradox-lower-far-right',
+ 'paradox-lower-mid',
+ 'paradox-upper-left',
+ 'paradox-upper-right',
+ ],
+ x: 0.85,
+ y: 0.2,
+ },
+ {
+ id: 'pedestal',
+ checks: [
+ 'pedestal',
+ ],
+ x: 0.03,
+ y: 0.05,
+ },
+ {
+ id: 'potion-shop',
+ checks: [
+ 'potion-shop',
+ ],
+ x: 0.8,
+ y: 0.325,
+ },
+ {
+ id: 'race-game',
+ checks: [
+ 'race-game',
+ ],
+ x: 0.025,
+ y: 0.7,
+ },
+ {
+ id: 'saha',
+ checks: [
+ 'saha',
+ ],
+ x: 0.815,
+ y: 0.465,
+ },
+ {
+ id: 'saha-hut',
+ checks: [
+ 'saha-left',
+ 'saha-mid',
+ 'saha-right',
+ ],
+ x: 0.815,
+ y: 0.42,
+ },
+ {
+ id: 'sick-kid',
+ checks: [
+ 'sick-kid',
+ ],
+ x: 0.155,
+ y: 0.525,
+ },
+ {
+ id: 'uncle',
+ checks: [
+ 'uncle',
+ 'secret-passage',
+ ],
+ x: 0.6,
+ y: 0.4,
+ },
+ {
+ id: 'spec-rock',
+ checks: [
+ 'spec-rock',
+ ],
+ x: 0.48,
+ y: 0.09,
+ },
+ {
+ id: 'spec-rock-cave',
+ checks: [
+ 'spec-rock-cave',
+ ],
+ x: 0.48,
+ y: 0.14,
+ },
+ {
+ id: 'spiral-cave',
+ checks: [
+ 'spiral-cave',
+ ],
+ x: 0.8,
+ y: 0.1,
+ },
+ {
+ id: 'tavern',
+ checks: [
+ 'tavern',
+ ],
+ x: 0.16,
+ y: 0.58,
+ },
+ {
+ id: 'waterfall-fairy',
+ checks: [
+ 'waterfall-fairy-left',
+ 'waterfall-fairy-right',
+ ],
+ x: 0.9,
+ y: 0.15,
+ },
+ {
+ id: 'zora',
+ checks: [
+ 'zora',
+ ],
+ x: 0.975,
+ y: 0.12,
+ },
+ {
+ id: 'zora-ledge',
+ checks: [
+ 'zora-ledge',
+ ],
+ x: 0.975,
+ y: 0.165,
+ },
+];
+
+const DW_DUNGEONS = [
+ {
+ id: 'pd',
+ x: 0.95,
+ y: 0.42,
+ },
+ {
+ id: 'sp',
+ x: 0.4675,
+ y: 0.9375,
+ },
+ {
+ id: 'sw',
+ x: 0.05,
+ y: 0.05,
+ },
+ {
+ id: 'tt',
+ x: 0.125,
+ y: 0.475,
+ },
+ {
+ id: 'ip',
+ x: 0.7975,
+ y: 0.86,
+ },
+ {
+ id: 'mm',
+ x: 0.12,
+ y: 0.82,
+ },
+ {
+ id: 'tr',
+ x: 0.94,
+ y: 0.06,
+ },
+ {
+ id: 'gt',
+ x: 0.56,
+ y: 0.05,
+ },
+];
+
+const DW_LOCATIONS = [
+ {
+ id: 'blacksmith',
+ checks: [
+ 'blacksmith',
+ ],
+ x: 0.15,
+ y: 0.65,
+ },
+ {
+ id: 'brewery',
+ checks: [
+ 'brewery',
+ ],
+ x: 0.1,
+ y: 0.6,
+ },
+ {
+ id: 'bumper-cave',
+ checks: [
+ 'bumper-cave',
+ ],
+ x: 0.325,
+ y: 0.15,
+ },
+ {
+ id: 'c-house',
+ checks: [
+ 'c-house',
+ ],
+ x: 0.2,
+ y: 0.5,
+ },
+ {
+ id: 'catfish',
+ checks: [
+ 'catfish',
+ ],
+ x: 0.9,
+ y: 0.175,
+ },
+ {
+ id: 'chest-game',
+ checks: [
+ 'chest-game',
+ ],
+ x: 0.05,
+ y: 0.45,
+ },
+ {
+ id: 'digging-game',
+ checks: [
+ 'digging-game',
+ ],
+ x: 0.05,
+ y: 0.7,
+ },
+ {
+ id: 'hammer-pegs',
+ checks: [
+ 'hammer-pegs',
+ ],
+ x: 0.3125,
+ y: 0.6,
+ },
+ {
+ id: 'hookshot-cave',
+ checks: [
+ 'hookshot-cave-tl',
+ 'hookshot-cave-tr',
+ 'hookshot-cave-bl',
+ ],
+ x: 0.85,
+ y: 0.02,
+ },
+ {
+ id: 'hookshot-cave-bonk',
+ checks: [
+ 'hookshot-cave-br',
+ ],
+ x: 0.85,
+ y: 0.065,
+ },
+ {
+ id: 'hype-cave',
+ checks: [
+ 'hype-cave-top',
+ 'hype-cave-left',
+ 'hype-cave-right',
+ 'hype-cave-bottom',
+ 'hype-cave-npc',
+ ],
+ x: 0.6,
+ y: 0.75,
+ },
+ {
+ id: 'mire-shed',
+ checks: [
+ 'mire-shed-left',
+ 'mire-shed-right',
+ ],
+ x: 0.04,
+ y: 0.8,
+ },
+ {
+ id: 'purple-chest',
+ checks: [
+ 'purple-chest',
+ ],
+ x: 0.3125,
+ y: 0.525,
+ },
+ {
+ id: 'pyramid',
+ checks: [
+ 'pyramid',
+ ],
+ x: 0.575,
+ y: 0.45,
+ },
+ {
+ id: 'pyramid-fairy',
+ checks: [
+ 'pyramid-fairy-left',
+ 'pyramid-fairy-right',
+ ],
+ x: 0.45,
+ y: 0.5,
+ },
+ {
+ id: 'spike-cave',
+ checks: [
+ 'spike-cave',
+ ],
+ x: 0.575,
+ y: 0.15,
+ },
+ {
+ id: 'stumpy',
+ checks: [
+ 'stumpy',
+ ],
+ x: 0.3125,
+ y: 0.6875,
+ },
+ {
+ id: 'super-bunny',
+ checks: [
+ 'super-bunny-top',
+ 'super-bunny-bottom',
+ ],
+ x: 0.85,
+ y: 0.15,
+ },
+];
+
+const Location = ({ number, l, size }) => {
+ const { t } = useTranslation();
+
+ const classNames = ['location', `status-${l.status}`];
+ if (size) {
+ classNames.push(`size-${size}`);
+ }
+ if (l.handlePrimary) {
+ classNames.push('clickable');
+ }
+
+ return <g
+ className={classNames.join(' ')}
+ onClick={(e) => {
+ l.handlePrimary();
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ onContextMenu={(e) => {
+ l.handleSecondary();
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ transform={`translate(${l.x} ${l.y})`}
+ >
+ <title>{t(`tracker.location.${l.id}`)}</title>
+ <rect className="box" x="0" y="0" />
+ {number && l.remaining ?
+ <text className="text" x="0" y="0">{l.remaining}</text>
+ : null}
+ </g>;
+};
+
+Location.propTypes = {
+ number: PropTypes.bool,
+ l: PropTypes.shape({
+ id: PropTypes.string,
+ x: PropTypes.number,
+ y: PropTypes.number,
+ number: PropTypes.number,
+ remaining: PropTypes.number,
+ status: PropTypes.string,
+ handlePrimary: PropTypes.func,
+ handleSecondary: PropTypes.func,
+ }),
+ size: PropTypes.string,
+};
+
+const makeBackground = (src, level) => {
+ const amount = Math.pow(2, Math.max(0, level - 8));
+ const size = 1 / amount;
+ const tiles = [];
+ for (let y = 0; y < amount; ++y) {
+ for (let x = 0; x < amount; ++x) {
+ tiles.push(<image
+ key={`${x}-${y}`}
+ x={x * size}
+ y={y * size}
+ width={size * 1.002}
+ height={size * 1.002}
+ href={`/media/alttp/map/${src}/${level}/${x}_${y}.png`}
+ />);
+ }
+ }
+ return tiles;
+};
+
+const Overworld = () => {
+ const { dungeons, logic, setManualState, state } = useTracker();
+
+ const mapDungeon = React.useCallback(dungeon => {
+ const definition = dungeons.find(d => d.id === dungeon.id);
+ const remaining = getDungeonRemainingItems(state, definition);
+ const status = aggregateDungeonStatus(definition, logic, state);
+ return {
+ ...dungeon,
+ status,
+ remaining,
+ handlePrimary: () => {
+ if (getDungeonRemainingItems(state, definition)) {
+ setManualState(addDungeonCheck(definition));
+ } else if (
+ !hasDungeonBoss(state, definition) || !hasDungeonPrize(state, definition)
+ ) {
+ if (definition.boss) {
+ setManualState(setBossDefeated(definition, true));
+ }
+ if (definition.prize) {
+ setManualState(setPrizeAcquired(definition, true));
+ }
+ } else {
+ setManualState(resetDungeonChecks(definition));
+ if (definition.boss) {
+ setManualState(setBossDefeated(definition, false));
+ }
+ if (definition.prize) {
+ setManualState(setPrizeAcquired(definition, false));
+ }
+ }
+ },
+ handleSecondary: () => {
+ if (isDungeonCleared(state, definition)) {
+ if (definition.items) {
+ setManualState(removeDungeonCheck(definition));
+ }
+ if (definition.boss) {
+ setManualState(setBossDefeated(definition, false));
+ }
+ if (definition.prize) {
+ setManualState(setPrizeAcquired(definition, false));
+ }
+ } else if (getDungeonClearedItems(state, definition)) {
+ setManualState(removeDungeonCheck(definition));
+ } else {
+ setManualState(completeDungeonChecks(definition));
+ if (definition.boss) {
+ setManualState(setBossDefeated(definition, true));
+ }
+ if (definition.prize) {
+ setManualState(setPrizeAcquired(definition, true));
+ }
+ }
+ },
+ };
+ }, [dungeons, logic, setManualState, state]);
+
+ const mapLocation = React.useCallback(loc => {
+ const remaining = countRemainingLocations(state, loc.checks);
+ const status = aggregateLocationStatus(loc.checks, logic, state);
+ return {
+ ...loc,
+ remaining,
+ status,
+ handlePrimary: () => {
+ if (remaining) {
+ setManualState(clearAll(loc.checks));
+ } else {
+ setManualState(unclearAll(loc.checks));
+ }
+ },
+ handleSecondary: () => {
+ if (remaining) {
+ setManualState(clearAll(loc.checks));
+ } else {
+ setManualState(unclearAll(loc.checks));
+ }
+ },
+ };
+ }, [logic, setManualState, state]);
+
+ const lwDungeons = React.useMemo(() => LW_DUNGEONS.map(mapDungeon), [mapDungeon]);
+ const lwLocations = React.useMemo(() => LW_LOCATIONS.map(mapLocation), [mapLocation]);
+
+ const dwDungeons = React.useMemo(() => DW_DUNGEONS.map(mapDungeon), [mapDungeon]);
+ const dwLocations = React.useMemo(() => DW_LOCATIONS.map(mapLocation), [mapLocation]);
+
+ return <svg
+ xmlns="http://www.w3.org/2000/svg"
+ className="canvas"
+ width="2"
+ height="1"
+ viewBox="0 0 2 1"
+ onContextMenu={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ >
+ <g className="light-world">
+ <g className="background">
+ {makeBackground('lw_files', 10)}
+ </g>
+ <g className="locations">
+ {lwLocations.map(l =>
+ <Location key={l.id} l={l} />
+ )}
+ {lwDungeons.map(l =>
+ <Location key={l.id} number l={l} size="lg" />
+ )}
+ </g>
+ </g>
+ <g className="dark-world" transform="translate(1 0)">
+ <g className="background">
+ {makeBackground('dw_files', 10)}
+ </g>
+ <g className="locations">
+ {dwLocations.map(l =>
+ <Location key={l.id} l={l} />
+ )}
+ {dwDungeons.map(l =>
+ <Location key={l.id} number l={l} size="lg" />
+ )}
+ </g>
+ </g>
+ </svg>;
+};
+
+export default Overworld;
--- /dev/null
+import React from 'react';
+
+import Overworld from './Overworld';
+
+const Map = () => {
+ return <div className="tracker-map">
+ <Overworld />
+ </div>;
+};
+
+export default Map;