]> git.localhorst.tv Git - alttp.git/blob - resources/js/components/tracker/Map.js
520ad938ab2faaafb61fd3774626af442104aa5b
[alttp.git] / resources / js / components / tracker / Map.js
1 import PropTypes from 'prop-types';
2 import React from 'react';
3 import { useTranslation } from 'react-i18next';
4
5 import {
6         clearAll,
7         decrement,
8         hasDungeonBoss,
9         increment,
10         toggleBoolean,
11         unclearAll,
12 } from '../../helpers/tracker';
13 import { useTracker } from '../../hooks/tracker';
14
15 const LW_DUNGEONS = [
16         {
17                 id: 'hc',
18                 x: 0.5,
19                 y: 0.5,
20         },
21         {
22                 id: 'ct',
23                 x: 0.5,
24                 y: 0.4,
25         },
26         {
27                 id: 'ep',
28                 x: 0.95,
29                 y: 0.42,
30         },
31         {
32                 id: 'dp',
33                 x: 0.075,
34                 y: 0.8,
35         },
36         {
37                 id: 'th',
38                 x: 0.56,
39                 y: 0.05,
40         },
41 ];
42
43 const LW_LOCATIONS = [
44         {
45                 id: 'aginah',
46                 checks: [
47                         'aginah',
48                 ],
49                 x: 0.2,
50                 y: 0.83,
51         },
52         {
53                 id: 'blinds-hut',
54                 checks: [
55                         'blinds-hut-top',
56                         'blinds-hut-left',
57                         'blinds-hut-right',
58                         'blinds-hut-far-left',
59                         'blinds-hut-far-right',
60                 ],
61                 x: 0.14,
62                 y: 0.42,
63         },
64         {
65                 id: 'bombos-tablet',
66                 checks: [
67                         'bombos-tablet',
68                 ],
69                 x: 0.225,
70                 y: 0.925,
71         },
72         {
73                 id: 'bonk-rocks',
74                 checks: [
75                         'bonk-rocks',
76                 ],
77                 x: 0.4,
78                 y: 0.3,
79         },
80         {
81                 id: 'bottle-vendor',
82                 checks: [
83                         'bottle-vendor',
84                 ],
85                 x: 0.1,
86                 y: 0.475,
87         },
88         {
89                 id: 'cave-45',
90                 checks: [
91                         'cave-45',
92                 ],
93                 x: 0.27,
94                 y: 0.83,
95         },
96         {
97                 id: 'checkerboard',
98                 checks: [
99                         'checkerboard',
100                 ],
101                 x: 0.18,
102                 y: 0.78,
103         },
104         {
105                 id: 'chicken-house',
106                 checks: [
107                         'chicken-house',
108                 ],
109                 x: 0.1,
110                 y: 0.53,
111         },
112         {
113                 id: 'dam',
114                 checks: [
115                         'flooded-chest',
116                         'sunken-treasure',
117                 ],
118                 x: 0.4675,
119                 y: 0.9375,
120         },
121         {
122                 id: 'desert-ledge',
123                 checks: [
124                         'desert-ledge',
125                 ],
126                 x: 0.025,
127                 y: 0.9,
128         },
129         {
130                 id: 'ether-tablet',
131                 checks: [
132                         'ether-tablet',
133                 ],
134                 x: 0.425,
135                 y: 0.025,
136         },
137         {
138                 id: 'floating-island',
139                 checks: [
140                         'floating-island',
141                 ],
142                 x: 0.8,
143                 y: 0.025,
144         },
145         {
146                 id: 'flute-spot',
147                 checks: [
148                         'flute-spot',
149                 ],
150                 x: 0.3,
151                 y: 0.675,
152         },
153         {
154                 id: 'graveyard-ledge',
155                 checks: [
156                         'graveyard-ledge',
157                 ],
158                 x: 0.57,
159                 y: 0.28,
160         },
161         {
162                 id: 'hobo',
163                 checks: [
164                         'hobo',
165                 ],
166                 x: 0.7,
167                 y: 0.7,
168         },
169         {
170                 id: 'ice-rod-cave',
171                 checks: [
172                         'ice-rod-cave',
173                 ],
174                 x: 0.9,
175                 y: 0.76,
176         },
177         {
178                 id: 'kak-well',
179                 checks: [
180                         'kak-well-top',
181                         'kak-well-left',
182                         'kak-well-mid',
183                         'kak-well-right',
184                         'kak-well-bottom',
185                 ],
186                 x: 0.04,
187                 y: 0.425,
188         },
189         {
190                 id: 'kings-tomb',
191                 checks: [
192                         'kings-tomb',
193                 ],
194                 x: 0.62,
195                 y: 0.3,
196         },
197         {
198                 id: 'lake-hylia-island',
199                 checks: [
200                         'lake-hylia-island',
201                 ],
202                 x: 0.725,
203                 y: 0.8375,
204         },
205         {
206                 id: 'library',
207                 checks: [
208                         'library',
209                 ],
210                 x: 0.15,
211                 y: 0.65,
212         },
213         {
214                 id: 'links-house',
215                 checks: [
216                         'links-house',
217                 ],
218                 x: 0.55,
219                 y: 0.6875,
220         },
221         {
222                 id: 'lost-woods-hideout',
223                 checks: [
224                         'lost-woods-hideout',
225                 ],
226                 x: 0.19,
227                 y: 0.14,
228         },
229         {
230                 id: 'lumberjack',
231                 checks: [
232                         'lumberjack',
233                 ],
234                 x: 0.3,
235                 y: 0.07,
236         },
237         {
238                 id: 'magic-bat',
239                 checks: [
240                         'magic-bat',
241                 ],
242                 x: 0.325,
243                 y: 0.55,
244         },
245         {
246                 id: 'mimic-cave',
247                 checks: [
248                         'mimic-cave',
249                 ],
250                 x: 0.85,
251                 y: 0.1,
252         },
253         {
254                 id: 'mini-moldorm-cave',
255                 checks: [
256                         'mini-moldorm-left',
257                         'mini-moldorm-right',
258                         'mini-moldorm-far-left',
259                         'mini-moldorm-far-right',
260                         'mini-moldorm-npc',
261                 ],
262                 x: 0.65,
263                 y: 0.95,
264         },
265         {
266                 id: 'mushroom-spot',
267                 checks: [
268                         'mushroom-spot',
269                 ],
270                 x: 0.125,
271                 y: 0.08,
272         },
273         {
274                 id: 'old-man',
275                 checks: [
276                         'old-man',
277                 ],
278                 x: 0.405,
279                 y: 0.195,
280         },
281         {
282                 id: 'paradox-cave',
283                 checks: [
284                         'paradox-lower-far-left',
285                         'paradox-lower-left',
286                         'paradox-lower-right',
287                         'paradox-lower-far-right',
288                         'paradox-lower-mid',
289                         'paradox-upper-left',
290                         'paradox-upper-right',
291                 ],
292                 x: 0.85,
293                 y: 0.2,
294         },
295         {
296                 id: 'pedestal',
297                 checks: [
298                         'pedestal',
299                 ],
300                 x: 0.03,
301                 y: 0.05,
302         },
303         {
304                 id: 'potion-shop',
305                 checks: [
306                         'potion-shop',
307                 ],
308                 x: 0.8,
309                 y: 0.325,
310         },
311         {
312                 id: 'race-game',
313                 checks: [
314                         'race-game',
315                 ],
316                 x: 0.025,
317                 y: 0.7,
318         },
319         {
320                 id: 'saha',
321                 checks: [
322                         'saha',
323                 ],
324                 x: 0.815,
325                 y: 0.465,
326         },
327         {
328                 id: 'saha-hut',
329                 checks: [
330                         'saha-left',
331                         'saha-mid',
332                         'saha-right',
333                 ],
334                 x: 0.815,
335                 y: 0.42,
336         },
337         {
338                 id: 'sick-kid',
339                 checks: [
340                         'sick-kid',
341                 ],
342                 x: 0.155,
343                 y: 0.525,
344         },
345         {
346                 id: 'uncle',
347                 checks: [
348                         'uncle',
349                         'secret-passage',
350                 ],
351                 x: 0.6,
352                 y: 0.4,
353         },
354         {
355                 id: 'spec-rock',
356                 checks: [
357                         'spec-rock',
358                 ],
359                 x: 0.48,
360                 y: 0.09,
361         },
362         {
363                 id: 'spec-rock-cave',
364                 checks: [
365                         'spec-rock-cave',
366                 ],
367                 x: 0.48,
368                 y: 0.14,
369         },
370         {
371                 id: 'spiral-cave',
372                 checks: [
373                         'spiral-cave',
374                 ],
375                 x: 0.8,
376                 y: 0.1,
377         },
378         {
379                 id: 'tavern',
380                 checks: [
381                         'tavern',
382                 ],
383                 x: 0.16,
384                 y: 0.58,
385         },
386         {
387                 id: 'waterfall-fairy',
388                 checks: [
389                         'waterfall-fairy-left',
390                         'waterfall-fairy-right',
391                 ],
392                 x: 0.9,
393                 y: 0.15,
394         },
395         {
396                 id: 'zora',
397                 checks: [
398                         'zora',
399                 ],
400                 x: 0.975,
401                 y: 0.12,
402         },
403         {
404                 id: 'zora-ledge',
405                 checks: [
406                         'zora-ledge',
407                 ],
408                 x: 0.975,
409                 y: 0.165,
410         },
411 ];
412
413 const DW_DUNGEONS = [
414         {
415                 id: 'pd',
416                 x: 0.95,
417                 y: 0.42,
418         },
419         {
420                 id: 'sp',
421                 x: 0.4675,
422                 y: 0.9375,
423         },
424         {
425                 id: 'sw',
426                 x: 0.05,
427                 y: 0.05,
428         },
429         {
430                 id: 'tt',
431                 x: 0.125,
432                 y: 0.475,
433         },
434         {
435                 id: 'ip',
436                 x: 0.7975,
437                 y: 0.86,
438         },
439         {
440                 id: 'mm',
441                 x: 0.12,
442                 y: 0.82,
443         },
444         {
445                 id: 'tr',
446                 x: 0.94,
447                 y: 0.06,
448         },
449         {
450                 id: 'gt',
451                 x: 0.56,
452                 y: 0.05,
453         },
454 ];
455
456 const DW_LOCATIONS = [
457         {
458                 id: 'blacksmith',
459                 checks: [
460                         'blacksmith',
461                 ],
462                 x: 0.15,
463                 y: 0.65,
464         },
465         {
466                 id: 'brewery',
467                 checks: [
468                         'brewery',
469                 ],
470                 x: 0.1,
471                 y: 0.6,
472         },
473         {
474                 id: 'bumper-cave',
475                 checks: [
476                         'bumper-cave',
477                 ],
478                 x: 0.325,
479                 y: 0.15,
480         },
481         {
482                 id: 'c-house',
483                 checks: [
484                         'c-house',
485                 ],
486                 x: 0.2,
487                 y: 0.5,
488         },
489         {
490                 id: 'catfish',
491                 checks: [
492                         'catfish',
493                 ],
494                 x: 0.9,
495                 y: 0.175,
496         },
497         {
498                 id: 'chest-game',
499                 checks: [
500                         'chest-game',
501                 ],
502                 x: 0.05,
503                 y: 0.45,
504         },
505         {
506                 id: 'digging-game',
507                 checks: [
508                         'digging-game',
509                 ],
510                 x: 0.05,
511                 y: 0.7,
512         },
513         {
514                 id: 'hammer-pegs',
515                 checks: [
516                         'hammer-pegs',
517                 ],
518                 x: 0.3125,
519                 y: 0.6,
520         },
521         {
522                 id: 'hookshot-cave',
523                 checks: [
524                         'hookshot-cave-tl',
525                         'hookshot-cave-tr',
526                         'hookshot-cave-bl',
527                 ],
528                 x: 0.85,
529                 y: 0.02,
530         },
531         {
532                 id: 'hookshot-cave-bonk',
533                 checks: [
534                         'hookshot-cave-br',
535                 ],
536                 x: 0.85,
537                 y: 0.065,
538         },
539         {
540                 id: 'hype-cave',
541                 checks: [
542                         'hype-cave-top',
543                         'hype-cave-left',
544                         'hype-cave-right',
545                         'hype-cave-bottom',
546                         'hype-cave-npc',
547                 ],
548                 x: 0.6,
549                 y: 0.75,
550         },
551         {
552                 id: 'mire-shed',
553                 checks: [
554                         'mire-shed-left',
555                         'mire-shed-right',
556                 ],
557                 x: 0.04,
558                 y: 0.8,
559         },
560         {
561                 id: 'purple-chest',
562                 checks: [
563                         'purple-chest',
564                 ],
565                 x: 0.3125,
566                 y: 0.525,
567         },
568         {
569                 id: 'pyramid',
570                 checks: [
571                         'pyramid',
572                 ],
573                 x: 0.575,
574                 y: 0.45,
575         },
576         {
577                 id: 'pyramid-fairy',
578                 checks: [
579                         'pyramid-fairy-left',
580                         'pyramid-fairy-right',
581                 ],
582                 x: 0.45,
583                 y: 0.5,
584         },
585         {
586                 id: 'spike-cave',
587                 checks: [
588                         'spike-cave',
589                 ],
590                 x: 0.575,
591                 y: 0.15,
592         },
593         {
594                 id: 'stumpy',
595                 checks: [
596                         'stumpy',
597                 ],
598                 x: 0.3125,
599                 y: 0.6875,
600         },
601         {
602                 id: 'super-bunny',
603                 checks: [
604                         'super-bunny-top',
605                         'super-bunny-bottom',
606                 ],
607                 x: 0.85,
608                 y: 0.15,
609         },
610 ];
611
612 const Location = ({ number, l, size }) => {
613         const { t } = useTranslation();
614
615         const classNames = ['location', `status-${l.status}`];
616         if (size) {
617                 classNames.push(`size-${size}`);
618         }
619         if (l.handlePrimary) {
620                 classNames.push('clickable');
621         }
622
623         return <g
624                 className={classNames.join(' ')}
625                 onClick={(e) => {
626                         l.handlePrimary();
627                         e.preventDefault();
628                         e.stopPropagation();
629                 }}
630                 onContextMenu={(e) => {
631                         l.handleSecondary();
632                         e.preventDefault();
633                         e.stopPropagation();
634                 }}
635                 transform={`translate(${l.x} ${l.y})`}
636         >
637                 <title>{t(`tracker.location.${l.id}`)}</title>
638                 <rect className="box" x="0" y="0" />
639                 {number && l.cleared < l.total ?
640                         <text className="text" x="0" y="0">{Math.max(0, l.total - l.cleared)}</text>
641                 : null}
642         </g>;
643 };
644
645 Location.propTypes = {
646         number: PropTypes.bool,
647         l: PropTypes.shape({
648                 id: PropTypes.string,
649                 x: PropTypes.number,
650                 y: PropTypes.number,
651                 cleared: PropTypes.number,
652                 total: PropTypes.number,
653                 status: PropTypes.string,
654                 handlePrimary: PropTypes.func,
655                 handleSecondary: PropTypes.func,
656         }),
657         size: PropTypes.string,
658 };
659
660 const Map = () => {
661         const { dungeons, setState, state } = useTracker();
662
663         const mapDungeon = React.useCallback(dungeon => {
664                 const definition = dungeons.find(d => d.id === dungeon.id);
665                 const cleared = state[`${dungeon.id}-checks`] || 0;
666                 const total = definition.items;
667                 let status = 'available';
668                 if (cleared === total) {
669                         if (['ct', 'gt'].includes(dungeon.id)) {
670                                 if (hasDungeonBoss(state, dungeon)) {
671                                         status = 'cleared';
672                                 }
673                         } else {
674                                 status = 'cleared';
675                         }
676                 }
677                 return {
678                         ...dungeon,
679                         status,
680                         cleared,
681                         total,
682                         handlePrimary: () => {
683                                 if (['ct', 'gt'].includes(dungeon.id) && cleared === total) {
684                                         if (hasDungeonBoss(state, dungeon)) {
685                                                 // reset
686                                                 setState(s => ({
687                                                         ...s,
688                                                         [`${dungeon.id}-checks`]: 0,
689                                                         [`${dungeon.id}-boss-defeated`]: false,
690                                                 }));
691                                         } else {
692                                                 setState(toggleBoolean(`${dungeon.id}-boss-defeated`));
693                                         }
694                                 } else {
695                                         setState(increment(`${dungeon.id}-checks`, total));
696                                 }
697                         },
698                         handleSecondary: () => {
699                                 if (['ct', 'gt'].includes(dungeon.id) &&
700                                         (hasDungeonBoss(state, dungeon) || !cleared)
701                                 ) {
702                                         if (hasDungeonBoss(state, dungeon)) {
703                                                 setState(toggleBoolean(`${dungeon.id}-boss-defeated`));
704                                         } else {
705                                                 setState(s => ({
706                                                         ...s,
707                                                         [`${dungeon.id}-checks`]: total,
708                                                         [`${dungeon.id}-boss-defeated`]: true,
709                                                 }));
710                                         }
711                                 } else {
712                                         setState(decrement(`${dungeon.id}-checks`, total));
713                                 }
714                         },
715                 };
716         }, [dungeons, setState, state]);
717
718         const mapLocation = React.useCallback(loc => {
719                 const cleared = loc.checks.reduce((acc, cur) => state[cur] ? acc + 1 : acc, 0);
720                 const total = loc.checks.length;
721                 let status = 'available';
722                 if (cleared === total) {
723                         status = 'cleared';
724                 }
725                 return {
726                         ...loc,
727                         cleared,
728                         total,
729                         status,
730                         handlePrimary: () => {
731                                 if (cleared < total) {
732                                         setState(clearAll(loc.checks));
733                                 } else {
734                                         setState(unclearAll(loc.checks));
735                                 }
736                         },
737                         handleSecondary: () => {
738                                 if (cleared < total) {
739                                         setState(clearAll(loc.checks));
740                                 } else {
741                                         setState(unclearAll(loc.checks));
742                                 }
743                         },
744                 };
745         }, [setState, state]);
746
747         const lwDungeons = React.useMemo(() => LW_DUNGEONS.map(mapDungeon), [mapDungeon]);
748         const lwLocations = React.useMemo(() => LW_LOCATIONS.map(mapLocation), [mapLocation]);
749
750         const dwDungeons = React.useMemo(() => DW_DUNGEONS.map(mapDungeon), [mapDungeon]);
751         const dwLocations = React.useMemo(() => DW_LOCATIONS.map(mapLocation), [mapLocation]);
752
753         return <div className="tracker-map">
754                 <svg
755                         xmlns="http://www.w3.org/2000/svg"
756                         className="canvas"
757                         width="2"
758                         height="1"
759                         viewBox="0 0 2 1"
760                         onContextMenu={(e) => {
761                                 e.preventDefault();
762                                 e.stopPropagation();
763                         }}
764                 >
765                         <g className="light-world">
766                                 <g className="background">
767                                         <image
768                                                 x="0"
769                                                 y="0"
770                                                 width="0.5"
771                                                 height="0.5"
772                                                 href="/media/alttp/map/lw_files/9/0_0.png"
773                                         />
774                                         <image
775                                                 x="0.5"
776                                                 y="0"
777                                                 width="0.5"
778                                                 height="0.5"
779                                                 href="/media/alttp/map/lw_files/9/1_0.png"
780                                         />
781                                         <image
782                                                 x="0"
783                                                 y="0.5"
784                                                 width="0.5"
785                                                 height="0.5"
786                                                 href="/media/alttp/map/lw_files/9/0_1.png"
787                                         />
788                                         <image
789                                                 x="0.5"
790                                                 y="0.5"
791                                                 width="0.5"
792                                                 height="0.5"
793                                                 href="/media/alttp/map/lw_files/9/1_1.png"
794                                         />
795                                 </g>
796                                 <g className="locations">
797                                         {lwLocations.map(l =>
798                                                 <Location key={l.id} l={l} />
799                                         )}
800                                         {lwDungeons.map(l =>
801                                                 <Location key={l.id} number l={l} size="lg" />
802                                         )}
803                                 </g>
804                         </g>
805                         <g className="dark-world" transform="translate(1 0)">
806                                 <g className="background">
807                                         <image
808                                                 x="0"
809                                                 y="0"
810                                                 width="0.5"
811                                                 height="0.5"
812                                                 href="/media/alttp/map/dw_files/9/0_0.png"
813                                         />
814                                         <image
815                                                 x="0.5"
816                                                 y="0"
817                                                 width="0.5"
818                                                 height="0.5"
819                                                 href="/media/alttp/map/dw_files/9/1_0.png"
820                                         />
821                                         <image
822                                                 x="0"
823                                                 y="0.5"
824                                                 width="0.5"
825                                                 height="0.5"
826                                                 href="/media/alttp/map/dw_files/9/0_1.png"
827                                         />
828                                         <image
829                                                 x="0.5"
830                                                 y="0.5"
831                                                 width="0.5"
832                                                 height="0.5"
833                                                 href="/media/alttp/map/dw_files/9/1_1.png"
834                                         />
835                                 </g>
836                                 <g className="locations">
837                                         {dwLocations.map(l =>
838                                                 <Location key={l.id} l={l} />
839                                         )}
840                                         {dwDungeons.map(l =>
841                                                 <Location key={l.id} number l={l} size="lg" />
842                                         )}
843                                 </g>
844                         </g>
845                 </svg>
846         </div>;
847 };
848
849 export default Map;