]> git.localhorst.tv Git - alttp.git/blobdiff - resources/js/components/tracker/Map.js
update dunka/muffins tracker
[alttp.git] / resources / js / components / tracker / Map.js
index d8e76364fb864a0f09def8cd0e1423a24ad30f83..3ee4b2f81458ab4eeb6190273623467406788bc1 100644 (file)
@@ -2,6 +2,24 @@ import PropTypes from 'prop-types';
 import React from 'react';
 import { useTranslation } from 'react-i18next';
 
+import {
+       addDungeonCheck,
+       aggregateDungeonStatus,
+       aggregateLocationStatus,
+       clearAll,
+       completeDungeonChecks,
+       countRemainingLocations,
+       getDungeonClearedItems,
+       getDungeonRemainingItems,
+       hasDungeonBoss,
+       hasDungeonPrize,
+       isDungeonCleared,
+       removeDungeonCheck,
+       resetDungeonChecks,
+       setBossDefeated,
+       setPrizeAcquired,
+       unclearAll,
+} from '../../helpers/tracker';
 import { useTracker } from '../../hooks/tracker';
 
 const LW_DUNGEONS = [
@@ -314,7 +332,7 @@ const LW_LOCATIONS = [
                        'saha',
                ],
                x: 0.815,
-               y: 0.42,
+               y: 0.465,
        },
        {
                id: 'saha-hut',
@@ -324,7 +342,7 @@ const LW_LOCATIONS = [
                        'saha-right',
                ],
                x: 0.815,
-               y: 0.465,
+               y: 0.42,
        },
        {
                id: 'sick-kid',
@@ -601,70 +619,155 @@ const DW_LOCATIONS = [
        },
 ];
 
-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, logic, 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;
-               let status = 'available';
-               if (cleared === total) {
-                       if (['ct', 'gt'].includes(dungeon.id)) {
-                               if (state[`${dungeon.id}-boss-defeated`]) {
-                                       status = 'cleared';
-                               }
-                       } else {
-                               status = 'cleared';
-                       }
-               }
+               const remaining = getDungeonRemainingItems(state, definition);
+               const status = aggregateDungeonStatus(definition, logic, state);
                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, logic, 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;
-               let status = 'available';
-               if (cleared === total) {
-                       status = 'cleared';
-               }
+               const remaining = countRemainingLocations(state, loc.checks);
+               const status = aggregateLocationStatus(loc.checks, logic, state);
                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]);
+       }, [logic, setManualState, state]);
 
        const lwDungeons = React.useMemo(() => LW_DUNGEONS.map(mapDungeon), [mapDungeon]);
        const lwLocations = React.useMemo(() => LW_LOCATIONS.map(mapLocation), [mapLocation]);
@@ -679,84 +782,34 @@ const Map = () => {
                        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>