1 import PropTypes from 'prop-types';
2 import React from 'react';
3 import { useTranslation } from 'react-i18next';
7 aggregateDungeonStatus,
8 aggregateLocationStatus,
10 completeDungeonChecks,
11 countRemainingLocations,
12 getDungeonClearedItems,
13 getDungeonRemainingItems,
22 } from '../../../helpers/tracker';
23 import { useTracker } from '../../../hooks/tracker';
53 const LW_LOCATIONS = [
68 'blinds-hut-far-left',
69 'blinds-hut-far-right',
148 id: 'floating-island',
164 id: 'graveyard-ledge',
208 id: 'lake-hylia-island',
232 id: 'lost-woods-hideout',
234 'lost-woods-hideout',
264 id: 'mini-moldorm-cave',
267 'mini-moldorm-right',
268 'mini-moldorm-far-left',
269 'mini-moldorm-far-right',
294 'paradox-lower-far-left',
295 'paradox-lower-left',
296 'paradox-lower-right',
297 'paradox-lower-far-right',
299 'paradox-upper-left',
300 'paradox-upper-right',
373 id: 'spec-rock-cave',
397 id: 'waterfall-fairy',
399 'waterfall-fairy-left',
400 'waterfall-fairy-right',
423 const DW_DUNGEONS = [
466 const DW_LOCATIONS = [
542 id: 'hookshot-cave-bonk',
589 'pyramid-fairy-left',
590 'pyramid-fairy-right',
615 'super-bunny-bottom',
622 const Location = ({ number, l, size }) => {
623 const { t } = useTranslation();
625 const classNames = ['location', `status-${l.status}`];
627 classNames.push(`size-${size}`);
629 if (l.handlePrimary) {
630 classNames.push('clickable');
634 className={classNames.join(' ')}
640 onContextMenu={(e) => {
645 transform={`translate(${l.x} ${l.y})`}
647 <title>{t(`tracker.location.${l.id}`)}</title>
648 <rect className="box" x="0" y="0" />
649 {number && l.remaining ?
650 <text className="text" x="0" y="0">{l.remaining}</text>
655 Location.propTypes = {
656 number: PropTypes.bool,
658 id: PropTypes.string,
661 number: PropTypes.number,
662 remaining: PropTypes.number,
663 status: PropTypes.string,
664 handlePrimary: PropTypes.func,
665 handleSecondary: PropTypes.func,
667 size: PropTypes.string,
670 const makeBackground = (src, level) => {
671 const amount = Math.pow(2, Math.max(0, level - 8));
672 const size = 1 / amount;
674 for (let y = 0; y < amount; ++y) {
675 for (let x = 0; x < amount; ++x) {
681 height={size * 1.002}
682 href={`/media/alttp/map/${src}/${level}/${x}_${y}.png`}
689 const Overworld = () => {
690 const { dungeons, logic, setManualState, state } = useTracker();
692 const mapDungeon = React.useCallback(dungeon => {
693 const definition = dungeons.find(d => d.id === dungeon.id);
694 const remaining = getDungeonRemainingItems(state, definition);
695 const status = aggregateDungeonStatus(definition, logic, state);
700 handlePrimary: () => {
701 if (getDungeonRemainingItems(state, definition)) {
702 setManualState(addDungeonCheck(definition));
704 !hasDungeonBoss(state, definition) || !hasDungeonPrize(state, definition)
706 if (definition.boss) {
707 setManualState(setBossDefeated(definition, true));
709 if (definition.prize) {
710 setManualState(setPrizeAcquired(definition, true));
713 setManualState(resetDungeonChecks(definition));
714 if (definition.boss) {
715 setManualState(setBossDefeated(definition, false));
717 if (definition.prize) {
718 setManualState(setPrizeAcquired(definition, false));
722 handleSecondary: () => {
723 if (isDungeonCleared(state, definition)) {
724 if (definition.items) {
725 setManualState(removeDungeonCheck(definition));
727 if (definition.boss) {
728 setManualState(setBossDefeated(definition, false));
730 if (definition.prize) {
731 setManualState(setPrizeAcquired(definition, false));
733 } else if (getDungeonClearedItems(state, definition)) {
734 setManualState(removeDungeonCheck(definition));
736 setManualState(completeDungeonChecks(definition));
737 if (definition.boss) {
738 setManualState(setBossDefeated(definition, true));
740 if (definition.prize) {
741 setManualState(setPrizeAcquired(definition, true));
746 }, [dungeons, logic, setManualState, state]);
748 const mapLocation = React.useCallback(loc => {
749 const remaining = countRemainingLocations(state, loc.checks);
750 const status = aggregateLocationStatus(loc.checks, logic, state);
755 handlePrimary: () => {
757 setManualState(clearAll(loc.checks));
759 setManualState(unclearAll(loc.checks));
762 handleSecondary: () => {
764 setManualState(clearAll(loc.checks));
766 setManualState(unclearAll(loc.checks));
770 }, [logic, setManualState, state]);
772 const lwDungeons = React.useMemo(() => LW_DUNGEONS.map(mapDungeon), [mapDungeon]);
773 const lwLocations = React.useMemo(() => LW_LOCATIONS.map(mapLocation), [mapLocation]);
775 const dwDungeons = React.useMemo(() => DW_DUNGEONS.map(mapDungeon), [mapDungeon]);
776 const dwLocations = React.useMemo(() => DW_LOCATIONS.map(mapLocation), [mapLocation]);
779 xmlns="http://www.w3.org/2000/svg"
784 onContextMenu={(e) => {
789 <g className="light-world">
790 <g className="background">
791 {makeBackground('lw_files', 10)}
793 <g className="locations">
794 {lwLocations.map(l =>
795 <Location key={l.id} l={l} />
798 <Location key={l.id} number l={l} size="lg" />
802 <g className="dark-world" transform="translate(1 0)">
803 <g className="background">
804 {makeBackground('dw_files', 10)}
806 <g className="locations">
807 {dwLocations.map(l =>
808 <Location key={l.id} l={l} />
811 <Location key={l.id} number l={l} size="lg" />
818 export default Overworld;