X-Git-Url: https://git.localhorst.tv/?a=blobdiff_plain;ds=sidebyside;f=resources%2Fjs%2Fcomponents%2Ftracker%2FMap%2FOverworld.js;fp=resources%2Fjs%2Fcomponents%2Ftracker%2FMap%2FOverworld.js;h=609dc34cd7c92fd282f566580a9f3a2cc99dc4c3;hb=6702d0ef2ec2a796a9aa6afdf58604800c4915a0;hp=0000000000000000000000000000000000000000;hpb=1b1f6865fb04dcec9e5c55b876cc9ff25706de23;p=alttp.git diff --git a/resources/js/components/tracker/Map/Overworld.js b/resources/js/components/tracker/Map/Overworld.js new file mode 100644 index 0000000..609dc34 --- /dev/null +++ b/resources/js/components/tracker/Map/Overworld.js @@ -0,0 +1,818 @@ +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 = [ + { + 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.465, + }, + { + id: 'saha-hut', + checks: [ + 'saha-left', + 'saha-mid', + 'saha-right', + ], + x: 0.815, + y: 0.42, + }, + { + 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 = ({ 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 { + l.handlePrimary(); + e.preventDefault(); + e.stopPropagation(); + }} + onContextMenu={(e) => { + l.handleSecondary(); + e.preventDefault(); + e.stopPropagation(); + }} + transform={`translate(${l.x} ${l.y})`} + > + {t(`tracker.location.${l.id}`)} + + {number && l.remaining ? + {l.remaining} + : null} + ; +}; + +Location.propTypes = { + number: PropTypes.bool, + l: PropTypes.shape({ + id: PropTypes.string, + x: PropTypes.number, + y: 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(); + } + } + return tiles; +}; + +const Overworld = () => { + const { dungeons, logic, setManualState, state } = useTracker(); + + const mapDungeon = React.useCallback(dungeon => { + const definition = dungeons.find(d => d.id === dungeon.id); + const remaining = getDungeonRemainingItems(state, definition); + const status = aggregateDungeonStatus(definition, logic, state); + return { + ...dungeon, + status, + 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, logic, setManualState, state]); + + const mapLocation = React.useCallback(loc => { + const remaining = countRemainingLocations(state, loc.checks); + const status = aggregateLocationStatus(loc.checks, logic, state); + return { + ...loc, + 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)); + } + }, + }; + }, [logic, setManualState, 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 { + e.preventDefault(); + e.stopPropagation(); + }} + > + + + {makeBackground('lw_files', 10)} + + + {lwLocations.map(l => + + )} + {lwDungeons.map(l => + + )} + + + + + {makeBackground('dw_files', 10)} + + + {dwLocations.map(l => + + )} + {dwDungeons.map(l => + + )} + + + ; +}; + +export default Overworld;