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';
25 const GENERIC_LW_DUNGEONS = [
49 ...GENERIC_LW_DUNGEONS,
57 const INVERTED_LW_DUNGEONS = [
58 ...GENERIC_LW_DUNGEONS,
66 const GENERIC_LW_LOCATIONS = [
81 'blinds-hut-far-left',
82 'blinds-hut-far-right',
161 id: 'floating-island',
177 id: 'graveyard-ledge',
221 id: 'lake-hylia-island',
237 id: 'lost-woods-hideout',
239 'lost-woods-hideout',
269 id: 'mini-moldorm-cave',
272 'mini-moldorm-right',
273 'mini-moldorm-far-left',
274 'mini-moldorm-far-right',
299 'paradox-lower-far-left',
300 'paradox-lower-left',
301 'paradox-lower-right',
302 'paradox-lower-far-right',
304 'paradox-upper-left',
305 'paradox-upper-right',
378 id: 'spec-rock-cave',
402 id: 'waterfall-fairy',
404 'waterfall-fairy-left',
405 'waterfall-fairy-right',
428 const LW_LOCATIONS = [
429 ...GENERIC_LW_LOCATIONS,
440 const INVERTED_LW_LOCATIONS = GENERIC_LW_LOCATIONS;
442 const GENERIC_DW_DUNGEONS = [
480 const DW_DUNGEONS = [
481 ...GENERIC_DW_DUNGEONS,
489 const INVERTED_DW_DUNGEONS = [
490 ...GENERIC_DW_DUNGEONS,
498 const GENERIC_DW_LOCATIONS = [
574 id: 'hookshot-cave-bonk',
621 'pyramid-fairy-left',
622 'pyramid-fairy-right',
647 'super-bunny-bottom',
654 const DW_LOCATIONS = GENERIC_DW_LOCATIONS;
656 const INVERTED_DW_LOCATIONS = [
657 ...GENERIC_DW_LOCATIONS,
668 const Location = ({ number, l, size }) => {
669 const { t } = useTranslation();
671 const classNames = ['location', `status-${l.status}`];
673 classNames.push(`size-${size}`);
675 if (l.handlePrimary) {
676 classNames.push('clickable');
680 className={classNames.join(' ')}
686 onContextMenu={(e) => {
691 transform={`translate(${l.x} ${l.y})`}
693 <title>{t(`tracker.location.${l.id}`)}</title>
694 <rect className="box" x="0" y="0" />
695 {number && l.remaining ?
696 <text className="text" x="0" y="0">{l.remaining}</text>
701 Location.propTypes = {
702 number: PropTypes.bool,
704 id: PropTypes.string,
707 number: PropTypes.number,
708 remaining: PropTypes.number,
709 status: PropTypes.string,
710 handlePrimary: PropTypes.func,
711 handleSecondary: PropTypes.func,
713 size: PropTypes.string,
716 const makeBackground = (src, level) => {
717 const amount = Math.pow(2, Math.max(0, level - 8));
718 const size = 1 / amount;
720 for (let y = 0; y < amount; ++y) {
721 for (let x = 0; x < amount; ++x) {
727 height={size * 1.002}
728 href={`/media/alttp/map/${src}/${level}/${x}_${y}.png`}
735 const Overworld = () => {
736 const { config, dungeons, logic, setManualState, state } = useTracker();
738 const mapDungeon = React.useCallback(dungeon => {
739 const definition = dungeons.find(d => d.id === dungeon.id);
740 const remaining = getDungeonRemainingItems(state, definition);
741 const status = aggregateDungeonStatus(definition, logic, state);
746 handlePrimary: () => {
747 if (getDungeonRemainingItems(state, definition)) {
748 setManualState(addDungeonCheck(definition));
750 !hasDungeonBoss(state, definition) || !hasDungeonPrize(state, definition)
752 if (definition.boss) {
753 setManualState(setBossDefeated(definition, true));
755 if (definition.prize) {
756 setManualState(setPrizeAcquired(definition, true));
759 setManualState(resetDungeonChecks(definition));
760 if (definition.boss) {
761 setManualState(setBossDefeated(definition, false));
763 if (definition.prize) {
764 setManualState(setPrizeAcquired(definition, false));
768 handleSecondary: () => {
769 if (isDungeonCleared(state, definition)) {
770 if (definition.items) {
771 setManualState(removeDungeonCheck(definition));
773 if (definition.boss) {
774 setManualState(setBossDefeated(definition, false));
776 if (definition.prize) {
777 setManualState(setPrizeAcquired(definition, false));
779 } else if (getDungeonClearedItems(state, definition)) {
780 setManualState(removeDungeonCheck(definition));
782 setManualState(completeDungeonChecks(definition));
783 if (definition.boss) {
784 setManualState(setBossDefeated(definition, true));
786 if (definition.prize) {
787 setManualState(setPrizeAcquired(definition, true));
792 }, [dungeons, logic, setManualState, state]);
794 const mapLocation = React.useCallback(loc => {
795 const remaining = countRemainingLocations(state, loc.checks);
796 const status = aggregateLocationStatus(loc.checks, logic, state);
801 handlePrimary: () => {
803 setManualState(clearAll(loc.checks));
805 setManualState(unclearAll(loc.checks));
808 handleSecondary: () => {
810 setManualState(clearAll(loc.checks));
812 setManualState(unclearAll(loc.checks));
816 }, [logic, setManualState, state]);
818 const lwDungeons = React.useMemo(() =>
819 (config.worldState === 'inverted' ? INVERTED_LW_DUNGEONS : LW_DUNGEONS)
822 const lwLocations = React.useMemo(() =>
823 (config.worldState === 'inverted' ? INVERTED_LW_LOCATIONS : LW_LOCATIONS)
827 const dwDungeons = React.useMemo(() =>
828 (config.worldState === 'inverted' ? INVERTED_DW_DUNGEONS : DW_DUNGEONS)
831 const dwLocations = React.useMemo(() =>
832 (config.worldState === 'inverted' ? INVERTED_DW_LOCATIONS : DW_LOCATIONS)
836 const layout = React.useMemo(() => {
837 if (config.mapLayout === 'vertical') {
840 dwTransform: 'translate(0 1)',
844 lwTransform: 'scale(0.5)',
845 dwTransform: 'scale(0.5) translate(1 0)',
851 <g className="light-world" transform={layout.lwTransform}>
852 <g className="background">
853 {makeBackground('lw_files', 10)}
855 <g className="locations">
856 {lwLocations.map(l =>
857 <Location key={l.id} l={l} />
860 <Location key={l.id} number l={l} size="lg" />
864 <g className="dark-world" transform={layout.dwTransform}>
865 <g className="background">
866 {makeBackground('dw_files', 10)}
868 <g className="locations">
869 {dwLocations.map(l =>
870 <Location key={l.id} l={l} />
873 <Location key={l.id} number l={l} size="lg" />
880 export default Overworld;