1 import PropTypes from 'prop-types';
2 import React from 'react';
3 import { useTranslation } from 'react-i18next';
13 } from '../../helpers/tracker';
14 import { useTracker } from '../../hooks/tracker';
44 const LW_LOCATIONS = [
59 'blinds-hut-far-left',
60 'blinds-hut-far-right',
139 id: 'floating-island',
155 id: 'graveyard-ledge',
199 id: 'lake-hylia-island',
223 id: 'lost-woods-hideout',
225 'lost-woods-hideout',
255 id: 'mini-moldorm-cave',
258 'mini-moldorm-right',
259 'mini-moldorm-far-left',
260 'mini-moldorm-far-right',
285 'paradox-lower-far-left',
286 'paradox-lower-left',
287 'paradox-lower-right',
288 'paradox-lower-far-right',
290 'paradox-upper-left',
291 'paradox-upper-right',
364 id: 'spec-rock-cave',
388 id: 'waterfall-fairy',
390 'waterfall-fairy-left',
391 'waterfall-fairy-right',
414 const DW_DUNGEONS = [
457 const DW_LOCATIONS = [
533 id: 'hookshot-cave-bonk',
580 'pyramid-fairy-left',
581 'pyramid-fairy-right',
606 'super-bunny-bottom',
613 const Location = ({ number, l, size }) => {
614 const { t } = useTranslation();
616 const classNames = ['location', `status-${l.status}`];
618 classNames.push(`size-${size}`);
620 if (l.handlePrimary) {
621 classNames.push('clickable');
625 className={classNames.join(' ')}
631 onContextMenu={(e) => {
636 transform={`translate(${l.x} ${l.y})`}
638 <title>{t(`tracker.location.${l.id}`)}</title>
639 <rect className="box" x="0" y="0" />
640 {number && l.cleared < l.total ?
641 <text className="text" x="0" y="0">{Math.max(0, l.total - l.cleared)}</text>
646 Location.propTypes = {
647 number: PropTypes.bool,
649 id: PropTypes.string,
652 cleared: PropTypes.number,
653 total: PropTypes.number,
654 status: PropTypes.string,
655 handlePrimary: PropTypes.func,
656 handleSecondary: PropTypes.func,
658 size: PropTypes.string,
661 const makeBackground = (src, level) => {
662 const amount = Math.pow(2, Math.max(0, level - 8));
663 const size = 1 / amount;
665 for (let y = 0; y < amount; ++y) {
666 for (let x = 0; x < amount; ++x) {
672 height={size * 1.002}
673 href={`/media/alttp/map/${src}/${level}/${x}_${y}.png`}
681 const { dungeons, setState, state } = useTracker();
683 const mapDungeon = React.useCallback(dungeon => {
684 const definition = dungeons.find(d => d.id === dungeon.id);
685 const cleared = state[`${dungeon.id}-checks`] || 0;
686 const total = definition.items;
687 let status = 'available';
688 if (isDungeonCleared(state, definition)) {
696 handlePrimary: () => {
697 if (['ct', 'gt'].includes(dungeon.id) && cleared === total) {
698 if (hasDungeonBoss(state, dungeon)) {
702 [`${dungeon.id}-checks`]: 0,
703 [`${dungeon.id}-boss-defeated`]: false,
706 setState(toggleBoolean(`${dungeon.id}-boss-defeated`));
709 setState(increment(`${dungeon.id}-checks`, total));
712 handleSecondary: () => {
713 if (['ct', 'gt'].includes(dungeon.id) &&
714 (hasDungeonBoss(state, dungeon) || !cleared)
716 if (hasDungeonBoss(state, dungeon)) {
717 setState(toggleBoolean(`${dungeon.id}-boss-defeated`));
721 [`${dungeon.id}-checks`]: total,
722 [`${dungeon.id}-boss-defeated`]: true,
726 setState(decrement(`${dungeon.id}-checks`, total));
730 }, [dungeons, setState, state]);
732 const mapLocation = React.useCallback(loc => {
733 const cleared = loc.checks.reduce((acc, cur) => state[cur] ? acc + 1 : acc, 0);
734 const total = loc.checks.length;
735 let status = 'available';
736 if (cleared === total) {
744 handlePrimary: () => {
745 if (cleared < total) {
746 setState(clearAll(loc.checks));
748 setState(unclearAll(loc.checks));
751 handleSecondary: () => {
752 if (cleared < total) {
753 setState(clearAll(loc.checks));
755 setState(unclearAll(loc.checks));
759 }, [setState, state]);
761 const lwDungeons = React.useMemo(() => LW_DUNGEONS.map(mapDungeon), [mapDungeon]);
762 const lwLocations = React.useMemo(() => LW_LOCATIONS.map(mapLocation), [mapLocation]);
764 const dwDungeons = React.useMemo(() => DW_DUNGEONS.map(mapDungeon), [mapDungeon]);
765 const dwLocations = React.useMemo(() => DW_LOCATIONS.map(mapLocation), [mapLocation]);
767 return <div className="tracker-map">
769 xmlns="http://www.w3.org/2000/svg"
774 onContextMenu={(e) => {
779 <g className="light-world">
780 <g className="background">
781 {makeBackground('lw_files', 10)}
783 <g className="locations">
784 {lwLocations.map(l =>
785 <Location key={l.id} l={l} />
788 <Location key={l.id} number l={l} size="lg" />
792 <g className="dark-world" transform="translate(1 0)">
793 <g className="background">
794 {makeBackground('dw_files', 10)}
796 <g className="locations">
797 {dwLocations.map(l =>
798 <Location key={l.id} l={l} />
801 <Location key={l.id} number l={l} size="lg" />