]> git.localhorst.tv Git - alttp.git/commitdiff
add a map to the tracker
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Sun, 24 Mar 2024 18:26:18 +0000 (19:26 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Sun, 24 Mar 2024 18:26:18 +0000 (19:26 +0100)
resources/js/components/tracker/Map.js [new file with mode: 0644]
resources/js/components/tracker/index.js
resources/sass/tracker.scss

diff --git a/resources/js/components/tracker/Map.js b/resources/js/components/tracker/Map.js
new file mode 100644 (file)
index 0000000..d8e7636
--- /dev/null
@@ -0,0 +1,767 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import { useTracker } from '../../hooks/tracker';
+
+const LW_DUNGEONS = [
+       {
+               id: 'hc',
+               x: 0.5,
+               y: 0.5,
+       },
+       {
+               id: 'ct',
+               x: 0.5,
+               y: 0.4,
+       },
+       {
+               id: 'ep',
+               x: 0.95,
+               y: 0.42,
+       },
+       {
+               id: 'dp',
+               x: 0.075,
+               y: 0.8,
+       },
+       {
+               id: 'th',
+               x: 0.56,
+               y: 0.05,
+       },
+];
+
+const LW_LOCATIONS = [
+       {
+               id: 'aginah',
+               checks: [
+                       'aginah',
+               ],
+               x: 0.2,
+               y: 0.83,
+       },
+       {
+               id: 'blinds-hut',
+               checks: [
+                       'blinds-hut-top',
+                       'blinds-hut-left',
+                       'blinds-hut-right',
+                       'blinds-hut-far-left',
+                       'blinds-hut-far-right',
+               ],
+               x: 0.14,
+               y: 0.42,
+       },
+       {
+               id: 'bombos-tablet',
+               checks: [
+                       'bombos-tablet',
+               ],
+               x: 0.225,
+               y: 0.925,
+       },
+       {
+               id: 'bonk-rocks',
+               checks: [
+                       'bonk-rocks',
+               ],
+               x: 0.4,
+               y: 0.3,
+       },
+       {
+               id: 'bottle-vendor',
+               checks: [
+                       'bottle-vendor',
+               ],
+               x: 0.1,
+               y: 0.475,
+       },
+       {
+               id: 'cave-45',
+               checks: [
+                       'cave-45',
+               ],
+               x: 0.27,
+               y: 0.83,
+       },
+       {
+               id: 'checkerboard',
+               checks: [
+                       'checkerboard',
+               ],
+               x: 0.18,
+               y: 0.78,
+       },
+       {
+               id: 'chicken-house',
+               checks: [
+                       'chicken-house',
+               ],
+               x: 0.1,
+               y: 0.53,
+       },
+       {
+               id: 'dam',
+               checks: [
+                       'flooded-chest',
+                       'sunken-treasure',
+               ],
+               x: 0.4675,
+               y: 0.9375,
+       },
+       {
+               id: 'desert-ledge',
+               checks: [
+                       'desert-ledge',
+               ],
+               x: 0.025,
+               y: 0.9,
+       },
+       {
+               id: 'ether-tablet',
+               checks: [
+                       'ether-tablet',
+               ],
+               x: 0.425,
+               y: 0.025,
+       },
+       {
+               id: 'floating-island',
+               checks: [
+                       'floating-island',
+               ],
+               x: 0.8,
+               y: 0.025,
+       },
+       {
+               id: 'flute-spot',
+               checks: [
+                       'flute-spot',
+               ],
+               x: 0.3,
+               y: 0.675,
+       },
+       {
+               id: 'graveyard-ledge',
+               checks: [
+                       'graveyard-ledge',
+               ],
+               x: 0.57,
+               y: 0.28,
+       },
+       {
+               id: 'hobo',
+               checks: [
+                       'hobo',
+               ],
+               x: 0.7,
+               y: 0.7,
+       },
+       {
+               id: 'ice-rod-cave',
+               checks: [
+                       'ice-rod-cave',
+               ],
+               x: 0.9,
+               y: 0.76,
+       },
+       {
+               id: 'kak-well',
+               checks: [
+                       'kak-well-top',
+                       'kak-well-left',
+                       'kak-well-mid',
+                       'kak-well-right',
+                       'kak-well-bottom',
+               ],
+               x: 0.04,
+               y: 0.425,
+       },
+       {
+               id: 'kings-tomb',
+               checks: [
+                       'kings-tomb',
+               ],
+               x: 0.62,
+               y: 0.3,
+       },
+       {
+               id: 'lake-hylia-island',
+               checks: [
+                       'lake-hylia-island',
+               ],
+               x: 0.725,
+               y: 0.8375,
+       },
+       {
+               id: 'library',
+               checks: [
+                       'library',
+               ],
+               x: 0.15,
+               y: 0.65,
+       },
+       {
+               id: 'links-house',
+               checks: [
+                       'links-house',
+               ],
+               x: 0.55,
+               y: 0.6875,
+       },
+       {
+               id: 'lost-woods-hideout',
+               checks: [
+                       'lost-woods-hideout',
+               ],
+               x: 0.19,
+               y: 0.14,
+       },
+       {
+               id: 'lumberjack',
+               checks: [
+                       'lumberjack',
+               ],
+               x: 0.3,
+               y: 0.07,
+       },
+       {
+               id: 'magic-bat',
+               checks: [
+                       'magic-bat',
+               ],
+               x: 0.325,
+               y: 0.55,
+       },
+       {
+               id: 'mimic-cave',
+               checks: [
+                       'mimic-cave',
+               ],
+               x: 0.85,
+               y: 0.1,
+       },
+       {
+               id: 'mini-moldorm-cave',
+               checks: [
+                       'mini-moldorm-left',
+                       'mini-moldorm-right',
+                       'mini-moldorm-far-left',
+                       'mini-moldorm-far-right',
+                       'mini-moldorm-npc',
+               ],
+               x: 0.65,
+               y: 0.95,
+       },
+       {
+               id: 'mushroom-spot',
+               checks: [
+                       'mushroom-spot',
+               ],
+               x: 0.125,
+               y: 0.08,
+       },
+       {
+               id: 'old-man',
+               checks: [
+                       'old-man',
+               ],
+               x: 0.405,
+               y: 0.195,
+       },
+       {
+               id: 'paradox-cave',
+               checks: [
+                       'paradox-lower-far-left',
+                       'paradox-lower-left',
+                       'paradox-lower-right',
+                       'paradox-lower-far-right',
+                       'paradox-lower-mid',
+                       'paradox-upper-left',
+                       'paradox-upper-right',
+               ],
+               x: 0.85,
+               y: 0.2,
+       },
+       {
+               id: 'pedestal',
+               checks: [
+                       'pedestal',
+               ],
+               x: 0.03,
+               y: 0.05,
+       },
+       {
+               id: 'potion-shop',
+               checks: [
+                       'potion-shop',
+               ],
+               x: 0.8,
+               y: 0.325,
+       },
+       {
+               id: 'race-game',
+               checks: [
+                       'race-game',
+               ],
+               x: 0.025,
+               y: 0.7,
+       },
+       {
+               id: 'saha',
+               checks: [
+                       'saha',
+               ],
+               x: 0.815,
+               y: 0.42,
+       },
+       {
+               id: 'saha-hut',
+               checks: [
+                       'saha-left',
+                       'saha-mid',
+                       'saha-right',
+               ],
+               x: 0.815,
+               y: 0.465,
+       },
+       {
+               id: 'sick-kid',
+               checks: [
+                       'sick-kid',
+               ],
+               x: 0.155,
+               y: 0.525,
+       },
+       {
+               id: 'uncle',
+               checks: [
+                       'uncle',
+                       'secret-passage',
+               ],
+               x: 0.6,
+               y: 0.4,
+       },
+       {
+               id: 'spec-rock',
+               checks: [
+                       'spec-rock',
+               ],
+               x: 0.48,
+               y: 0.09,
+       },
+       {
+               id: 'spec-rock-cave',
+               checks: [
+                       'spec-rock-cave',
+               ],
+               x: 0.48,
+               y: 0.14,
+       },
+       {
+               id: 'spiral-cave',
+               checks: [
+                       'spiral-cave',
+               ],
+               x: 0.8,
+               y: 0.1,
+       },
+       {
+               id: 'tavern',
+               checks: [
+                       'tavern',
+               ],
+               x: 0.16,
+               y: 0.58,
+       },
+       {
+               id: 'waterfall-fairy',
+               checks: [
+                       'waterfall-fairy-left',
+                       'waterfall-fairy-right',
+               ],
+               x: 0.9,
+               y: 0.15,
+       },
+       {
+               id: 'zora',
+               checks: [
+                       'zora',
+               ],
+               x: 0.975,
+               y: 0.12,
+       },
+       {
+               id: 'zora-ledge',
+               checks: [
+                       'zora-ledge',
+               ],
+               x: 0.975,
+               y: 0.165,
+       },
+];
+
+const DW_DUNGEONS = [
+       {
+               id: 'pd',
+               x: 0.95,
+               y: 0.42,
+       },
+       {
+               id: 'sp',
+               x: 0.4675,
+               y: 0.9375,
+       },
+       {
+               id: 'sw',
+               x: 0.05,
+               y: 0.05,
+       },
+       {
+               id: 'tt',
+               x: 0.125,
+               y: 0.475,
+       },
+       {
+               id: 'ip',
+               x: 0.7975,
+               y: 0.86,
+       },
+       {
+               id: 'mm',
+               x: 0.12,
+               y: 0.82,
+       },
+       {
+               id: 'tr',
+               x: 0.94,
+               y: 0.06,
+       },
+       {
+               id: 'gt',
+               x: 0.56,
+               y: 0.05,
+       },
+];
+
+const DW_LOCATIONS = [
+       {
+               id: 'blacksmith',
+               checks: [
+                       'blacksmith',
+               ],
+               x: 0.15,
+               y: 0.65,
+       },
+       {
+               id: 'brewery',
+               checks: [
+                       'brewery',
+               ],
+               x: 0.1,
+               y: 0.6,
+       },
+       {
+               id: 'bumper-cave',
+               checks: [
+                       'bumper-cave',
+               ],
+               x: 0.325,
+               y: 0.15,
+       },
+       {
+               id: 'c-house',
+               checks: [
+                       'c-house',
+               ],
+               x: 0.2,
+               y: 0.5,
+       },
+       {
+               id: 'catfish',
+               checks: [
+                       'catfish',
+               ],
+               x: 0.9,
+               y: 0.175,
+       },
+       {
+               id: 'chest-game',
+               checks: [
+                       'chest-game',
+               ],
+               x: 0.05,
+               y: 0.45,
+       },
+       {
+               id: 'digging-game',
+               checks: [
+                       'digging-game',
+               ],
+               x: 0.05,
+               y: 0.7,
+       },
+       {
+               id: 'hammer-pegs',
+               checks: [
+                       'hammer-pegs',
+               ],
+               x: 0.3125,
+               y: 0.6,
+       },
+       {
+               id: 'hookshot-cave',
+               checks: [
+                       'hookshot-cave-tl',
+                       'hookshot-cave-tr',
+                       'hookshot-cave-bl',
+               ],
+               x: 0.85,
+               y: 0.02,
+       },
+       {
+               id: 'hookshot-cave-bonk',
+               checks: [
+                       'hookshot-cave-br',
+               ],
+               x: 0.85,
+               y: 0.065,
+       },
+       {
+               id: 'hype-cave',
+               checks: [
+                       'hype-cave-top',
+                       'hype-cave-left',
+                       'hype-cave-right',
+                       'hype-cave-bottom',
+                       'hype-cave-npc',
+               ],
+               x: 0.6,
+               y: 0.75,
+       },
+       {
+               id: 'mire-shed',
+               checks: [
+                       'mire-shed-left',
+                       'mire-shed-right',
+               ],
+               x: 0.04,
+               y: 0.8,
+       },
+       {
+               id: 'purple-chest',
+               checks: [
+                       'purple-chest',
+               ],
+               x: 0.3125,
+               y: 0.525,
+       },
+       {
+               id: 'pyramid',
+               checks: [
+                       'pyramid',
+               ],
+               x: 0.575,
+               y: 0.45,
+       },
+       {
+               id: 'pyramid-fairy',
+               checks: [
+                       'pyramid-fairy-left',
+                       'pyramid-fairy-right',
+               ],
+               x: 0.45,
+               y: 0.5,
+       },
+       {
+               id: 'spike-cave',
+               checks: [
+                       'spike-cave',
+               ],
+               x: 0.575,
+               y: 0.15,
+       },
+       {
+               id: 'stumpy',
+               checks: [
+                       'stumpy',
+               ],
+               x: 0.3125,
+               y: 0.6875,
+       },
+       {
+               id: 'super-bunny',
+               checks: [
+                       'super-bunny-top',
+                       'super-bunny-bottom',
+               ],
+               x: 0.85,
+               y: 0.15,
+       },
+];
+
+const Location = ({ l, size }) => {
+       const { t } = useTranslation();
+
+       const classNames = ['location', `status-${l.status}`];
+       if (size) {
+               classNames.push(`size-${size}`);
+       }
+
+       return <rect className={classNames.join(' ')} x={l.x} y={l.y}>
+               <title>{t(`tracker.location.${l.id}`)}</title>
+       </rect>;
+};
+
+Location.propTypes = {
+       l: PropTypes.shape({
+               id: PropTypes.string,
+               x: PropTypes.number,
+               y: PropTypes.number,
+               cleared: PropTypes.number,
+               total: PropTypes.number,
+               status: PropTypes.string,
+       }),
+       size: PropTypes.string,
+};
+
+const Map = () => {
+       const { dungeons, 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';
+                       }
+               }
+               return {
+                       ...dungeon,
+                       status,
+                       cleared,
+                       total,
+               };
+       }, [dungeons, 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';
+               }
+               return {
+                       ...loc,
+                       cleared,
+                       total,
+                       status,
+               };
+       }, [state]);
+
+       const lwDungeons = React.useMemo(() => LW_DUNGEONS.map(mapDungeon), [mapDungeon]);
+       const lwLocations = React.useMemo(() => LW_LOCATIONS.map(mapLocation), [mapLocation]);
+
+       const dwDungeons = React.useMemo(() => DW_DUNGEONS.map(mapDungeon), [mapDungeon]);
+       const dwLocations = React.useMemo(() => DW_LOCATIONS.map(mapLocation), [mapLocation]);
+
+       return <div className="tracker-map">
+               <svg
+                       xmlns="http://www.w3.org/2000/svg"
+                       className="canvas"
+                       width="2"
+                       height="1"
+                       viewBox="0 0 2 1"
+               >
+                       <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"
+                                       />
+                               </g>
+                               <g className="locations">
+                                       {lwLocations.map(l =>
+                                               <Location key={l.id} l={l} />
+                                       )}
+                                       {lwDungeons.map(l =>
+                                               <Location key={l.id} 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"
+                                       />
+                               </g>
+                               <g className="locations">
+                                       {dwLocations.map(l =>
+                                               <Location key={l.id} l={l} />
+                                       )}
+                                       {dwDungeons.map(l =>
+                                               <Location key={l.id} l={l} size="lg" />
+                                       )}
+                               </g>
+                       </g>
+               </svg>
+       </div>;
+};
+
+export default Map;
index 5a505b51ba1777c5a6f8ebe150ca98d3a0537396..0dd7cc5ed95d6d3fa7443c905c78b6a4cd2c689f 100644 (file)
@@ -3,14 +3,22 @@ import React from 'react';
 import Dungeons from './Dungeons';
 import Equipment from './Equipment';
 import Items from './Items';
+import Map from './Map';
 import Toolbar from './Toolbar';
 
 const Tracker = () => {
        return <div className="tracker">
                <Toolbar />
-               <Items />
-               <Equipment />
-               <Dungeons />
+               <div className="d-flex">
+                       <div>
+                               <Items />
+                               <Equipment />
+                               <Dungeons />
+                       </div>
+                       <div>
+                               <Map />
+                       </div>
+               </div>
        </div>;
 };
 
index 2e75301da33bee88a795ac67666ccd5d18fce0fe..f1b745a0a24c97617f4a8e0a2afd52b2b9871f3b 100644 (file)
                        line-height: 1;
                }
        }
+       .tracker-map {
+               .canvas {
+                       width: 50em;
+                       height: auto;
+                       .location {
+                               width: 0.04px;
+                               height: 0.04px;
+                               fill: green;
+                               stroke: black;
+                               stroke-width: 0.003px;
+                               transform: translate(-0.02px, -0.02px);
+                               &.status-cleared {
+                                       fill: grey;
+                                       opacity: 0.4;
+                               }
+                               &.size-lg {
+                                       width: 0.08px;
+                                       height: 0.08px;
+                                       transform: translate(-0.04px, -0.04px);
+                               }
+                       }
+               }
+       }
        .toggle-icon {
                &.inactive {
                        opacity: .5;