import React from 'react';
import { useTranslation } from 'react-i18next';
+import {
+ addDungeonCheck,
+ clearAll,
+ completeDungeonChecks,
+ countRemainingLocations,
+ getDungeonClearedItems,
+ getDungeonRemainingItems,
+ hasClearedLocations,
+ hasDungeonBoss,
+ hasDungeonPrize,
+ isDungeonCleared,
+ removeDungeonCheck,
+ resetDungeonChecks,
+ setBossDefeated,
+ setPrizeAcquired,
+ unclearAll,
+} from '../../helpers/tracker';
import { useTracker } from '../../hooks/tracker';
const LW_DUNGEONS = [
'saha',
],
x: 0.815,
- y: 0.42,
+ y: 0.465,
},
{
id: 'saha-hut',
'saha-right',
],
x: 0.815,
- y: 0.465,
+ y: 0.42,
},
{
id: 'sick-kid',
},
];
-const Location = ({ l, size }) => {
+const Location = ({ number, l, size }) => {
const { t } = useTranslation();
const classNames = ['location', `status-${l.status}`];
if (size) {
classNames.push(`size-${size}`);
}
+ if (l.handlePrimary) {
+ classNames.push('clickable');
+ }
- return <rect className={classNames.join(' ')} x={l.x} y={l.y}>
+ return <g
+ className={classNames.join(' ')}
+ onClick={(e) => {
+ l.handlePrimary();
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ onContextMenu={(e) => {
+ l.handleSecondary();
+ e.preventDefault();
+ e.stopPropagation();
+ }}
+ transform={`translate(${l.x} ${l.y})`}
+ >
<title>{t(`tracker.location.${l.id}`)}</title>
- </rect>;
+ <rect className="box" x="0" y="0" />
+ {number && l.remaining ?
+ <text className="text" x="0" y="0">{l.remaining}</text>
+ : null}
+ </g>;
};
Location.propTypes = {
+ number: PropTypes.bool,
l: PropTypes.shape({
id: PropTypes.string,
x: PropTypes.number,
y: PropTypes.number,
- cleared: PropTypes.number,
- total: PropTypes.number,
+ number: PropTypes.number,
+ remaining: PropTypes.number,
status: PropTypes.string,
+ handlePrimary: PropTypes.func,
+ handleSecondary: PropTypes.func,
}),
size: PropTypes.string,
};
+const makeBackground = (src, level) => {
+ const amount = Math.pow(2, Math.max(0, level - 8));
+ const size = 1 / amount;
+ const tiles = [];
+ for (let y = 0; y < amount; ++y) {
+ for (let x = 0; x < amount; ++x) {
+ tiles.push(<image
+ key={`${x}-${y}`}
+ x={x * size}
+ y={y * size}
+ width={size * 1.002}
+ height={size * 1.002}
+ href={`/media/alttp/map/${src}/${level}/${x}_${y}.png`}
+ />);
+ }
+ }
+ return tiles;
+};
+
const Map = () => {
- const { dungeons, state } = useTracker();
+ const { dungeons, setManualState, state } = useTracker();
const mapDungeon = React.useCallback(dungeon => {
const definition = dungeons.find(d => d.id === dungeon.id);
- const cleared = state[`${dungeon.id}-checks`] || 0;
- const total = definition.items;
+ const remaining = getDungeonRemainingItems(state, definition);
let status = 'available';
- if (cleared === total) {
- if (['ct', 'gt'].includes(dungeon.id)) {
- if (state[`${dungeon.id}-boss-defeated`]) {
- status = 'cleared';
- }
- } else {
- status = 'cleared';
- }
+ if (isDungeonCleared(state, definition)) {
+ status = 'cleared';
}
return {
...dungeon,
status,
- cleared,
- total,
+ remaining,
+ handlePrimary: () => {
+ if (getDungeonRemainingItems(state, definition)) {
+ setManualState(addDungeonCheck(definition));
+ } else if (
+ !hasDungeonBoss(state, definition) || !hasDungeonPrize(state, definition)
+ ) {
+ if (definition.boss) {
+ setManualState(setBossDefeated(definition, true));
+ }
+ if (definition.prize) {
+ setManualState(setPrizeAcquired(definition, true));
+ }
+ } else {
+ setManualState(resetDungeonChecks(definition));
+ if (definition.boss) {
+ setManualState(setBossDefeated(definition, false));
+ }
+ if (definition.prize) {
+ setManualState(setPrizeAcquired(definition, false));
+ }
+ }
+ },
+ handleSecondary: () => {
+ if (isDungeonCleared(state, definition)) {
+ if (definition.items) {
+ setManualState(removeDungeonCheck(definition));
+ }
+ if (definition.boss) {
+ setManualState(setBossDefeated(definition, false));
+ }
+ if (definition.prize) {
+ setManualState(setPrizeAcquired(definition, false));
+ }
+ } else if (getDungeonClearedItems(state, definition)) {
+ setManualState(removeDungeonCheck(definition));
+ } else {
+ setManualState(completeDungeonChecks(definition));
+ if (definition.boss) {
+ setManualState(setBossDefeated(definition, true));
+ }
+ if (definition.prize) {
+ setManualState(setPrizeAcquired(definition, true));
+ }
+ }
+ },
};
- }, [dungeons, state]);
+ }, [dungeons, setManualState, state]);
const mapLocation = React.useCallback(loc => {
- const cleared = loc.checks.reduce((acc, cur) => state[cur] ? acc + 1 : acc, 0);
- const total = loc.checks.length;
+ const remaining = countRemainingLocations(state, loc.checks);
let status = 'available';
- if (cleared === total) {
+ if (hasClearedLocations(state, loc.checks)) {
status = 'cleared';
}
return {
...loc,
- cleared,
- total,
+ remaining,
status,
+ handlePrimary: () => {
+ if (remaining) {
+ setManualState(clearAll(loc.checks));
+ } else {
+ setManualState(unclearAll(loc.checks));
+ }
+ },
+ handleSecondary: () => {
+ if (remaining) {
+ setManualState(clearAll(loc.checks));
+ } else {
+ setManualState(unclearAll(loc.checks));
+ }
+ },
};
- }, [state]);
+ }, [setManualState, state]);
const lwDungeons = React.useMemo(() => LW_DUNGEONS.map(mapDungeon), [mapDungeon]);
const lwLocations = React.useMemo(() => LW_LOCATIONS.map(mapLocation), [mapLocation]);
width="2"
height="1"
viewBox="0 0 2 1"
+ onContextMenu={(e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ }}
>
<g className="light-world">
<g className="background">
- <image
- x="0"
- y="0"
- width="0.5"
- height="0.5"
- href="/media/alttp/map/lw_files/9/0_0.png"
- />
- <image
- x="0.5"
- y="0"
- width="0.5"
- height="0.5"
- href="/media/alttp/map/lw_files/9/1_0.png"
- />
- <image
- x="0"
- y="0.5"
- width="0.5"
- height="0.5"
- href="/media/alttp/map/lw_files/9/0_1.png"
- />
- <image
- x="0.5"
- y="0.5"
- width="0.5"
- height="0.5"
- href="/media/alttp/map/lw_files/9/1_1.png"
- />
+ {makeBackground('lw_files', 10)}
</g>
<g className="locations">
{lwLocations.map(l =>
<Location key={l.id} l={l} />
)}
{lwDungeons.map(l =>
- <Location key={l.id} l={l} size="lg" />
+ <Location key={l.id} number l={l} size="lg" />
)}
</g>
</g>
<g className="dark-world" transform="translate(1 0)">
<g className="background">
- <image
- x="0"
- y="0"
- width="0.5"
- height="0.5"
- href="/media/alttp/map/dw_files/9/0_0.png"
- />
- <image
- x="0.5"
- y="0"
- width="0.5"
- height="0.5"
- href="/media/alttp/map/dw_files/9/1_0.png"
- />
- <image
- x="0"
- y="0.5"
- width="0.5"
- height="0.5"
- href="/media/alttp/map/dw_files/9/0_1.png"
- />
- <image
- x="0.5"
- y="0.5"
- width="0.5"
- height="0.5"
- href="/media/alttp/map/dw_files/9/1_1.png"
- />
+ {makeBackground('dw_files', 10)}
</g>
<g className="locations">
{dwLocations.map(l =>
<Location key={l.id} l={l} />
)}
{dwDungeons.map(l =>
- <Location key={l.id} l={l} size="lg" />
+ <Location key={l.id} number l={l} size="lg" />
)}
</g>
</g>