Skip to content

Commit

Permalink
feat: resolve minimap tile urls in map component (#59)
Browse files Browse the repository at this point in the history
  • Loading branch information
fallenoak authored Dec 2, 2023
1 parent 42975a6 commit e8b6a74
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 54 deletions.
1 change: 1 addition & 0 deletions .github/workflows/web-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
DEBUG: 'spelunker*'
PIPELINE_URI: 'http://localhost:3001/pipeline'
DATA_URI: 'http://localhost:3001/data'
MINIMAP_URI: 'http://localhost:3001/minimap'
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
Expand Down
17 changes: 17 additions & 0 deletions packages/spelunker-api/src/lib/minimap/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import express from 'express';
import tiles from './tiles.mjs';

const minimap = express();

minimap.get('/tiles/*', (req, res) => {
const directoryName = req.params[0];

if (!tiles[directoryName]) {
res.sendStatus(404);
return;
}

res.json(tiles[directoryName]);
});

export default minimap;
36 changes: 36 additions & 0 deletions packages/spelunker-api/src/lib/minimap/tiles.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import mpq from '../mpq/index.mjs';
import logger from '../utils/logger.mjs';

const log = logger('minimap');

log('generating tile index');

const file = mpq.files.get('textures\\Minimap\\md5translate.trs');
const source = file.data.toString();
file.close();

// tile directory -> tile name -> content path
const tiles = {};
let tileCount = 0;

for (const line of source.split(/\n|\r\n/)) {
if (line.startsWith('dir:') || line.trim().length === 0) {
continue;
}

const [tilePath, contentPath] = line.split(/\t+/);
const tilePathParts = tilePath.split(/[/\\]/);
const tileDirectory = tilePathParts.slice(0, -1).map((t) => t.toLowerCase()).join('/');
const tileName = tilePathParts.at(-1).split('.')[0].toLowerCase();

if (!tiles[tileDirectory]) {
tiles[tileDirectory] = {};
}

tiles[tileDirectory][tileName] = contentPath.toLowerCase();
tileCount++;
}

log(`tile index contains ${tileCount} tiles`);

export default tiles;
17 changes: 0 additions & 17 deletions packages/spelunker-api/src/lib/mpq/files/MinimapTiles.mjs

This file was deleted.

14 changes: 0 additions & 14 deletions packages/spelunker-api/src/lib/pipeline/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import BLP from 'blizzardry/lib/blp/index.js';
import express from 'express';
import pngjs from 'pngjs';

import minimapTiles from '../mpq/files/MinimapTiles.mjs';
import mpq from '../mpq/index.mjs';

const { PNG } = pngjs;
Expand All @@ -25,19 +24,6 @@ pipeline.param('file', (req, res, next, path) => {
}
});

// TODO: Client is currently required to convert X/Y to tile X/Y, perhaps the
// pipeline server should do this?
pipeline.get('/minimap/:mapName/:tx/:ty.blp.png', (req, res) => {
const { mapName, tx, ty } = req.params;
const index = `${mapName}\\map${tx}_${ty}`;
const tile = minimapTiles[index];
if (tile) {
res.redirect(`${req.baseUrl}/files/textures/minimap/${tile}.png`);
} else {
res.sendStatus(404);
}
});

pipeline.get('/files/:file(*.blp).png', (req, res) => {
BLP.from(req.file.data, (blp) => {
const mipmap = blp.largest;
Expand Down
3 changes: 3 additions & 0 deletions packages/spelunker-api/src/lib/server.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import cors from 'cors';
import express from 'express';

import pipeline from './pipeline/index.mjs';
import minimap from './minimap/index.mjs';
import rootValue from './graph/root.mjs';
import schema from './graph/schema/index.mjs';

Expand All @@ -19,6 +20,8 @@ server.use(cors({ origin }));
// TODO: Use separate pipeline server
server.use('/pipeline', pipeline);

server.use('/minimap', minimap);

const apollo = new ApolloServer({
schema,
rootValue,
Expand Down
101 changes: 78 additions & 23 deletions packages/spelunker-web/src/components/core/GameMap/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import { CRS, TileLayer, transformation } from 'leaflet';
import { MapContainer } from 'react-leaflet';
import { createLayerComponent } from '@react-leaflet/core';
Expand All @@ -10,6 +10,7 @@ import { Box } from '../';
import styles from './index.styl';

const CHUNK_SIZE = 33.3333;
const TILE_INDEX = {};
const zoom = 4;

// See: http://en.wikipedia.org/wiki/Coordinate_reference_system
Expand All @@ -18,24 +19,58 @@ const crs = Object.assign({}, CRS.Simple, {
infinity: false,
});

const loadTileIndex = async (tileDirectory) => {
// Check index cache
if (TILE_INDEX[tileDirectory]) {
return;
}

const indexResponse = await fetch(`${process.env.MINIMAP_URI}/tiles/${tileDirectory}`);
const tileIndex = await indexResponse.json();

// Cache index
TILE_INDEX[tileDirectory] = tileIndex;
};

class MinimapTileLayer extends TileLayer {
getTileUrl({ x, y }) {
const tx = 32 + x;
const ty = 32 + y;
const mapName = this._url;
return `${process.env.PIPELINE_URI}/minimap/${mapName}/${tx}/${ty}.blp.png`;

// TODO use real url
const unknownTileUrl = `${process.env.PIPELINE_URI}/files/textures/minimap/unknown_${tx}_${ty}`;

const tileDirectory = this._url;
if (!tileDirectory) {
return unknownTileUrl;
}

const tileIndex = TILE_INDEX[tileDirectory];
if (!tileIndex) {
return unknownTileUrl;
}

const contentPath = tileIndex[`map${tx}_${ty}`];
if (!contentPath) {
return unknownTileUrl;
}

return `${process.env.PIPELINE_URI}/files/textures/minimap/${contentPath}.png`;
}
}

const createMinimapLayer = (props, context) => {
const instance = new MinimapTileLayer(props.mapName, { ...props });
if (props.tileDirectory) {
instance.setUrl(props.tileDirectory);
}
return { instance, context };
};

const updateMinimapLayer = (instance, props, prevProps) => {
if (prevProps.mapName !== props.mapName) {
if (prevProps.tileDirectory !== props.tileDirectory) {
if (instance.setUrl) {
instance.setUrl(props.mapName);
instance.setUrl(props.tileDirectory);
}
}
};
Expand All @@ -61,26 +96,46 @@ const GameMap = (props) => {
},
} = props;

const tileDirectory = filename.toLowerCase();
const [loaded, setLoaded] = useState(false);

useEffect(() => {
const load = async (tileDirectory) => {
try {
await loadTileIndex(tileDirectory);
setLoaded(true);
} catch (error) {
setLoaded(false);
}
};

if (!loaded) {
load(tileDirectory);
}
}, [tileDirectory, loaded]);

return (
<Box className={styles.box}>
<MapContainer
attributionControl={false}
bounds={normalizeBounds(bounds || maxBounds)}
className={styles.map}
crs={crs}
maxBounds={normalizeBounds(maxBounds)}
maxBoundsViscosity={1.0}
>
<MinimapLayer
mapName={filename}
minZoom={zoom - 2}
maxZoom={zoom + 2}
minNativeZoom={zoom}
maxNativeZoom={zoom}
/>

{props.children}
</MapContainer>
{loaded && (
<MapContainer
attributionControl={false}
bounds={normalizeBounds(bounds || maxBounds)}
className={styles.map}
crs={crs}
maxBounds={normalizeBounds(maxBounds)}
maxBoundsViscosity={1.0}
>
<MinimapLayer
tileDirectory={tileDirectory}
minZoom={zoom - 2}
maxZoom={zoom + 2}
minNativeZoom={zoom}
maxNativeZoom={zoom}
/>

{props.children}
</MapContainer>
)}
</Box>
);
};
Expand Down
1 change: 1 addition & 0 deletions packages/spelunker-web/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module.exports = {
'DEBUG',
'PIPELINE_URI',
'DATA_URI',
'MINIMAP_URI',
]),
],
module: {
Expand Down

0 comments on commit e8b6a74

Please sign in to comment.