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,
660 const makeBackground = (src, level) => {
661 const amount = Math.pow(2, Math.max(0, level - 8));
662 const size = 1 / amount;
664 for (let y = 0; y < amount; ++y) {
665 for (let x = 0; x < amount; ++x) {
671 height={size * 1.002}
672 href={`/media/alttp/map/${src}/${level}/${x}_${y}.png`}
680 const { dungeons, setState, state } = useTracker();
682 const mapDungeon = React.useCallback(dungeon => {
683 const definition = dungeons.find(d => d.id === dungeon.id);
684 const cleared = state[`${dungeon.id}-checks`] || 0;
685 const total = definition.items;
686 let status = 'available';
687 if (cleared === total) {
688 if (['ct', 'gt'].includes(dungeon.id)) {
689 if (hasDungeonBoss(state, dungeon)) {
701 handlePrimary: () => {
702 if (['ct', 'gt'].includes(dungeon.id) && cleared === total) {
703 if (hasDungeonBoss(state, dungeon)) {
707 [`${dungeon.id}-checks`]: 0,
708 [`${dungeon.id}-boss-defeated`]: false,
711 setState(toggleBoolean(`${dungeon.id}-boss-defeated`));
714 setState(increment(`${dungeon.id}-checks`, total));
717 handleSecondary: () => {
718 if (['ct', 'gt'].includes(dungeon.id) &&
719 (hasDungeonBoss(state, dungeon) || !cleared)
721 if (hasDungeonBoss(state, dungeon)) {
722 setState(toggleBoolean(`${dungeon.id}-boss-defeated`));
726 [`${dungeon.id}-checks`]: total,
727 [`${dungeon.id}-boss-defeated`]: true,
731 setState(decrement(`${dungeon.id}-checks`, total));
735 }, [dungeons, setState, state]);
737 const mapLocation = React.useCallback(loc => {
738 const cleared = loc.checks.reduce((acc, cur) => state[cur] ? acc + 1 : acc, 0);
739 const total = loc.checks.length;
740 let status = 'available';
741 if (cleared === total) {
749 handlePrimary: () => {
750 if (cleared < total) {
751 setState(clearAll(loc.checks));
753 setState(unclearAll(loc.checks));
756 handleSecondary: () => {
757 if (cleared < total) {
758 setState(clearAll(loc.checks));
760 setState(unclearAll(loc.checks));
764 }, [setState, state]);
766 const lwDungeons = React.useMemo(() => LW_DUNGEONS.map(mapDungeon), [mapDungeon]);
767 const lwLocations = React.useMemo(() => LW_LOCATIONS.map(mapLocation), [mapLocation]);
769 const dwDungeons = React.useMemo(() => DW_DUNGEONS.map(mapDungeon), [mapDungeon]);
770 const dwLocations = React.useMemo(() => DW_LOCATIONS.map(mapLocation), [mapLocation]);
772 return <div className="tracker-map">
774 xmlns="http://www.w3.org/2000/svg"
779 onContextMenu={(e) => {
784 <g className="light-world">
785 <g className="background">
786 {makeBackground('lw_files', 10)}
788 <g className="locations">
789 {lwLocations.map(l =>
790 <Location key={l.id} l={l} />
793 <Location key={l.id} number l={l} size="lg" />
797 <g className="dark-world" transform="translate(1 0)">
798 <g className="background">
799 {makeBackground('dw_files', 10)}
801 <g className="locations">
802 {dwLocations.map(l =>
803 <Location key={l.id} l={l} />
806 <Location key={l.id} number l={l} size="lg" />