1 import PropTypes from 'prop-types';
2 import React from 'react';
3 import { useTranslation } from 'react-i18next';
12 } from '../../helpers/tracker';
13 import { useTracker } from '../../hooks/tracker';
43 const LW_LOCATIONS = [
58 'blinds-hut-far-left',
59 'blinds-hut-far-right',
138 id: 'floating-island',
154 id: 'graveyard-ledge',
198 id: 'lake-hylia-island',
222 id: 'lost-woods-hideout',
224 'lost-woods-hideout',
254 id: 'mini-moldorm-cave',
257 'mini-moldorm-right',
258 'mini-moldorm-far-left',
259 'mini-moldorm-far-right',
284 'paradox-lower-far-left',
285 'paradox-lower-left',
286 'paradox-lower-right',
287 'paradox-lower-far-right',
289 'paradox-upper-left',
290 'paradox-upper-right',
363 id: 'spec-rock-cave',
387 id: 'waterfall-fairy',
389 'waterfall-fairy-left',
390 'waterfall-fairy-right',
413 const DW_DUNGEONS = [
456 const DW_LOCATIONS = [
532 id: 'hookshot-cave-bonk',
579 'pyramid-fairy-left',
580 'pyramid-fairy-right',
605 'super-bunny-bottom',
612 const Location = ({ number, l, size }) => {
613 const { t } = useTranslation();
615 const classNames = ['location', `status-${l.status}`];
617 classNames.push(`size-${size}`);
619 if (l.handlePrimary) {
620 classNames.push('clickable');
624 className={classNames.join(' ')}
630 onContextMenu={(e) => {
635 transform={`translate(${l.x} ${l.y})`}
637 <title>{t(`tracker.location.${l.id}`)}</title>
638 <rect className="box" x="0" y="0" />
639 {number && l.cleared < l.total ?
640 <text className="text" x="0" y="0">{Math.max(0, l.total - l.cleared)}</text>
645 Location.propTypes = {
646 number: PropTypes.bool,
648 id: PropTypes.string,
651 cleared: PropTypes.number,
652 total: PropTypes.number,
653 status: PropTypes.string,
654 handlePrimary: PropTypes.func,
655 handleSecondary: PropTypes.func,
657 size: PropTypes.string,
661 const { dungeons, setState, state } = useTracker();
663 const mapDungeon = React.useCallback(dungeon => {
664 const definition = dungeons.find(d => d.id === dungeon.id);
665 const cleared = state[`${dungeon.id}-checks`] || 0;
666 const total = definition.items;
667 let status = 'available';
668 if (cleared === total) {
669 if (['ct', 'gt'].includes(dungeon.id)) {
670 if (hasDungeonBoss(state, dungeon)) {
682 handlePrimary: () => {
683 if (['ct', 'gt'].includes(dungeon.id) && cleared === total) {
684 if (hasDungeonBoss(state, dungeon)) {
688 [`${dungeon.id}-checks`]: 0,
689 [`${dungeon.id}-boss-defeated`]: false,
692 setState(toggleBoolean(`${dungeon.id}-boss-defeated`));
695 setState(increment(`${dungeon.id}-checks`, total));
698 handleSecondary: () => {
699 if (['ct', 'gt'].includes(dungeon.id) &&
700 (hasDungeonBoss(state, dungeon) || !cleared)
702 if (hasDungeonBoss(state, dungeon)) {
703 setState(toggleBoolean(`${dungeon.id}-boss-defeated`));
707 [`${dungeon.id}-checks`]: total,
708 [`${dungeon.id}-boss-defeated`]: true,
712 setState(decrement(`${dungeon.id}-checks`, total));
716 }, [dungeons, setState, state]);
718 const mapLocation = React.useCallback(loc => {
719 const cleared = loc.checks.reduce((acc, cur) => state[cur] ? acc + 1 : acc, 0);
720 const total = loc.checks.length;
721 let status = 'available';
722 if (cleared === total) {
730 handlePrimary: () => {
731 if (cleared < total) {
732 setState(clearAll(loc.checks));
734 setState(unclearAll(loc.checks));
737 handleSecondary: () => {
738 if (cleared < total) {
739 setState(clearAll(loc.checks));
741 setState(unclearAll(loc.checks));
745 }, [setState, state]);
747 const lwDungeons = React.useMemo(() => LW_DUNGEONS.map(mapDungeon), [mapDungeon]);
748 const lwLocations = React.useMemo(() => LW_LOCATIONS.map(mapLocation), [mapLocation]);
750 const dwDungeons = React.useMemo(() => DW_DUNGEONS.map(mapDungeon), [mapDungeon]);
751 const dwLocations = React.useMemo(() => DW_LOCATIONS.map(mapLocation), [mapLocation]);
753 return <div className="tracker-map">
755 xmlns="http://www.w3.org/2000/svg"
760 onContextMenu={(e) => {
765 <g className="light-world">
766 <g className="background">
772 href="/media/alttp/map/lw_files/9/0_0.png"
779 href="/media/alttp/map/lw_files/9/1_0.png"
786 href="/media/alttp/map/lw_files/9/0_1.png"
793 href="/media/alttp/map/lw_files/9/1_1.png"
796 <g className="locations">
797 {lwLocations.map(l =>
798 <Location key={l.id} l={l} />
801 <Location key={l.id} number l={l} size="lg" />
805 <g className="dark-world" transform="translate(1 0)">
806 <g className="background">
812 href="/media/alttp/map/dw_files/9/0_0.png"
819 href="/media/alttp/map/dw_files/9/1_0.png"
826 href="/media/alttp/map/dw_files/9/0_1.png"
833 href="/media/alttp/map/dw_files/9/1_1.png"
836 <g className="locations">
837 {dwLocations.map(l =>
838 <Location key={l.id} l={l} />
841 <Location key={l.id} number l={l} size="lg" />