├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── craco.config.js ├── package.json ├── public ├── .htaccess ├── favicon.ico ├── favicon.png ├── index.html ├── robots.txt └── static │ └── media │ └── .htaccess ├── screenshots └── lumbridge.png ├── scripts ├── cache │ ├── export-height-map.ts │ ├── export-textures.ts │ └── load-util.ts └── download-caches.js ├── src ├── components │ ├── renderer │ │ ├── RenderStats.ts │ │ ├── Renderer.ts │ │ └── RendererCanvas.tsx │ └── rs │ │ ├── loading │ │ ├── OsrsLoadingBar.css │ │ └── OsrsLoadingBar.tsx │ │ ├── menu │ │ ├── OsrsMenu.css │ │ └── OsrsMenu.tsx │ │ ├── minimap │ │ ├── MinimapContainer.css │ │ ├── MinimapContainer.tsx │ │ ├── MinimapImage.tsx │ │ ├── compass.png │ │ ├── minimap-black.png │ │ ├── minimap-frame.png │ │ ├── worldmap-icon-hover.png │ │ └── worldmap-icon.png │ │ ├── select │ │ ├── OsrsSelect.css │ │ └── OsrsSelect.tsx │ │ └── worldmap │ │ ├── WorldMap.css │ │ ├── WorldMap.tsx │ │ ├── WorldMapModal.css │ │ ├── WorldMapModal.tsx │ │ ├── close-button-hover.png │ │ ├── close-button.png │ │ ├── locations.json │ │ ├── zoom-in.png │ │ └── zoom-out.png ├── import-json.d.ts ├── index.css ├── index.tsx ├── java-random.d.ts ├── mapviewer │ ├── Caches.ts │ ├── Camera.ts │ ├── Frustum.ts │ ├── InputManager.ts │ ├── MapManager.ts │ ├── MapViewer.ts │ ├── MapViewerApp.tsx │ ├── MapViewerContainer.css │ ├── MapViewerContainer.tsx │ ├── MapViewerControls.tsx │ ├── MapViewerRenderer.ts │ ├── MapViewerRenderers.ts │ ├── buffer │ │ └── DataBuffer.ts │ ├── data │ │ ├── npc │ │ │ ├── NpcSpawn.ts │ │ │ ├── npc-spawns-2004.json │ │ │ ├── npc-spawns-2004.json.LICENSE.txt │ │ │ ├── npc-spawns-2009.json │ │ │ ├── npc-spawns-2009.json.LICENSE.txt │ │ │ └── npc-spawns-osrs.json │ │ └── obj │ │ │ ├── ObjSpawn.ts │ │ │ └── obj-spawns.json │ ├── webgl │ │ ├── AnimationFrames.ts │ │ ├── DrawRange.ts │ │ ├── InteractType.ts │ │ ├── Interactions.ts │ │ ├── WebGLMapSquare.ts │ │ ├── WebGLMapViewerRenderer.ts │ │ ├── buffer │ │ │ ├── ModelHashBuffer.ts │ │ │ ├── SceneBuffer.ts │ │ │ └── VertexBuffer.ts │ │ ├── loader │ │ │ ├── SdMapData.ts │ │ │ ├── SdMapDataLoader.ts │ │ │ └── SdMapLoaderInput.ts │ │ ├── loc │ │ │ ├── LocAnimated.ts │ │ │ ├── LocAnimatedData.ts │ │ │ ├── LocAnimatedGroup.ts │ │ │ ├── SceneLocEntity.ts │ │ │ └── SceneLocs.ts │ │ ├── npc │ │ │ ├── Npc.ts │ │ │ ├── NpcData.ts │ │ │ └── NpcSpawnGroup.ts │ │ └── shaders │ │ │ ├── ShaderUtil.ts │ │ │ ├── Shaders.ts │ │ │ ├── frame-fxaa.frag.glsl │ │ │ ├── frame-fxaa.vert.glsl │ │ │ ├── frame.frag.glsl │ │ │ ├── frame.vert.glsl │ │ │ ├── includes │ │ │ ├── branchless-logic.glsl │ │ │ ├── fog.glsl │ │ │ ├── fxaa │ │ │ │ ├── fxaa.glsl │ │ │ │ └── texcoords.glsl │ │ │ ├── height-map.glsl │ │ │ ├── hsl-to-rgb.glsl │ │ │ ├── material.glsl │ │ │ ├── multi-draw.glsl │ │ │ ├── scene-uniforms.glsl │ │ │ ├── unpack-float.glsl │ │ │ └── vertex.glsl │ │ │ ├── main.frag.glsl │ │ │ ├── main.vert.glsl │ │ │ └── npc.vert.glsl │ ├── webgpu │ │ ├── WebGPUMapViewerRenderer.ts │ │ └── shaders │ │ │ ├── fullscreenTexturedQuad.wgsl │ │ │ ├── red.frag.wgsl │ │ │ └── triangle.vert.wgsl │ └── worker │ │ ├── MinimapData.ts │ │ ├── RenderDataLoader.ts │ │ ├── RenderDataWorker.ts │ │ └── RenderDataWorkerPool.ts ├── media │ ├── RuneScape-Bold-12.ttf │ ├── RuneScape-Plain-11.ttf │ ├── RuneScape-Plain-12.ttf │ ├── interface-bg.png │ └── interface-border.png ├── picogl.d.ts ├── picogl │ └── PicoTexture.ts ├── react-app-env.d.ts ├── reportWebVitals.ts ├── rs │ ├── Client.ts │ ├── MathConstants.ts │ ├── MenuEntry.ts │ ├── cache │ │ ├── ApiType.ts │ │ ├── Archive.ts │ │ ├── ArchiveFile.ts │ │ ├── CacheFiles.ts │ │ ├── CacheIndex.ts │ │ ├── CacheInfo.ts │ │ ├── CacheSystem.ts │ │ ├── CacheType.ts │ │ ├── ConfigType.ts │ │ ├── Container.ts │ │ ├── IndexType.ts │ │ ├── loader │ │ │ ├── CacheLoaderFactory.ts │ │ │ ├── Dat2CacheLoaderFactory.ts │ │ │ ├── DatCacheLoaderFactory.ts │ │ │ └── LegacyCacheLoaderFactory.ts │ │ ├── ref │ │ │ ├── ArchiveFileReference.ts │ │ │ ├── ArchiveReference.ts │ │ │ └── ReferenceTable.ts │ │ └── store │ │ │ ├── CacheStore.ts │ │ │ ├── MemoryStore.ts │ │ │ ├── Sector.ts │ │ │ └── SectorCluster.ts │ ├── compression │ │ ├── Bzip2.ts │ │ ├── CompressionType.ts │ │ ├── Gzip.ts │ │ └── Gzip.web.ts │ ├── config │ │ ├── Type.ts │ │ ├── TypeLoader.ts │ │ ├── bastype │ │ │ ├── BasType.ts │ │ │ └── BasTypeLoader.ts │ │ ├── defaults │ │ │ ├── DefaultsGroup.ts │ │ │ └── GraphicsDefaults.ts │ │ ├── enumtype │ │ │ └── EnumType.ts │ │ ├── floortype │ │ │ ├── FloorType.ts │ │ │ ├── FloorTypeLoader.ts │ │ │ ├── OverlayFloorType.ts │ │ │ └── UnderlayFloorType.ts │ │ ├── idktype │ │ │ └── IdkType.ts │ │ ├── invtype │ │ │ └── InvType.ts │ │ ├── loctype │ │ │ ├── LocModelLoader.ts │ │ │ ├── LocModelType.ts │ │ │ ├── LocType.ts │ │ │ └── LocTypeLoader.ts │ │ ├── mapscenetype │ │ │ ├── MapSceneType.ts │ │ │ └── MapSceneTypeLoader.ts │ │ ├── meltype │ │ │ ├── MapElementType.ts │ │ │ └── MapElementTypeLoader.ts │ │ ├── npctype │ │ │ ├── NpcModelLoader.ts │ │ │ ├── NpcType.ts │ │ │ └── NpcTypeLoader.ts │ │ ├── objtype │ │ │ ├── ObjModelLoader.ts │ │ │ ├── ObjStackability.ts │ │ │ ├── ObjType.ts │ │ │ └── ObjTypeLoader.ts │ │ ├── paramtype │ │ │ └── ParamType.ts │ │ ├── questtype │ │ │ ├── QuestState.ts │ │ │ ├── QuestType.ts │ │ │ └── QuestTypeLoader.ts │ │ ├── seqtype │ │ │ ├── SeqType.ts │ │ │ └── SeqTypeLoader.ts │ │ ├── spotanimtype │ │ │ └── SpotAnimType.ts │ │ ├── structtype │ │ │ └── StructType.ts │ │ └── vartype │ │ │ ├── VarManager.ts │ │ │ ├── bit │ │ │ ├── VarBitType.ts │ │ │ └── VarBitTypeLoader.ts │ │ │ ├── client │ │ │ ├── VarClientIntType.ts │ │ │ └── VarClientStrType.ts │ │ │ └── player │ │ │ └── VarPlayerType.ts │ ├── crypto │ │ └── Xtea.ts │ ├── graphics │ │ ├── Rasterizer2D.ts │ │ └── Rasterizer3D.ts │ ├── io │ │ └── ByteBuffer.ts │ ├── map │ │ ├── MapFileIndex.ts │ │ ├── MapFileLoader.ts │ │ └── MapImageRenderer.ts │ ├── model │ │ ├── FaceNormal.ts │ │ ├── Model.ts │ │ ├── ModelData.ts │ │ ├── ModelLoader.ts │ │ ├── TextureMapper.ts │ │ ├── VertexNormal.ts │ │ ├── seq │ │ │ ├── SeqBase.ts │ │ │ ├── SeqBaseLoader.ts │ │ │ ├── SeqFrame.ts │ │ │ ├── SeqFrameLoader.ts │ │ │ ├── SeqFrameMap.ts │ │ │ └── SeqTransformType.ts │ │ └── skeletal │ │ │ ├── Curve.ts │ │ │ ├── CurveInterp.ts │ │ │ ├── CurveInterpType.ts │ │ │ ├── CurveType.ts │ │ │ ├── MatrixPool.ts │ │ │ ├── QuatPool.ts │ │ │ ├── SkeletalBase.ts │ │ │ ├── SkeletalBone.ts │ │ │ ├── SkeletalSeq.ts │ │ │ ├── SkeletalSeqLoader.ts │ │ │ └── SkeletalTransformType.ts │ ├── pathfinder │ │ ├── CollisionStrategy.ts │ │ ├── Pathfinder.ts │ │ ├── RouteStrategy.ts │ │ └── flag │ │ │ ├── CollisionFlag.ts │ │ │ └── DirectionFlag.ts │ ├── scene │ │ ├── CollisionMap.ts │ │ ├── FloorDecoration.ts │ │ ├── Loc.ts │ │ ├── Scene.ts │ │ ├── SceneBuilder.ts │ │ ├── SceneLoc.ts │ │ ├── SceneTile.ts │ │ ├── SceneTileModel.ts │ │ ├── Wall.ts │ │ ├── WallDecoration.ts │ │ └── entity │ │ │ ├── Entity.ts │ │ │ ├── EntityTag.ts │ │ │ └── LocEntity.ts │ ├── sprite │ │ ├── IndexedSprite.ts │ │ ├── SpriteLoader.ts │ │ └── SpritePixels.ts │ ├── texture │ │ ├── DatTextureLoader.ts │ │ ├── OldProceduralTextureLoader.ts │ │ ├── ProceduralTextureLoader.ts │ │ ├── SpriteTextureLoader.ts │ │ ├── TextureCombineMode.ts │ │ ├── TextureLoader.ts │ │ ├── TextureMaterial.ts │ │ └── procedural │ │ │ ├── ProceduralTexture.ts │ │ │ ├── TextureGenerator.ts │ │ │ ├── cache │ │ │ ├── ColourImageCache.ts │ │ │ └── MonochromeImageCache.ts │ │ │ └── operation │ │ │ ├── ArithmeticOperation.ts │ │ │ ├── BinaryOperation.ts │ │ │ ├── BlurOperation.ts │ │ │ ├── BricksOperation.ts │ │ │ ├── BrightnessOperation.ts │ │ │ ├── ClampOperation.ts │ │ │ ├── ColorEdgeDetectorOperation.ts │ │ │ ├── ColourStripOperation.ts │ │ │ ├── ConstantColourOperation.ts │ │ │ ├── ConstantMonochromeOperation.ts │ │ │ ├── CurveOperation.ts │ │ │ ├── DiagonalGradientOperation.ts │ │ │ ├── EmbossOperation.ts │ │ │ ├── GradientOperation.ts │ │ │ ├── GrayScaleOperation.ts │ │ │ ├── HerringboneOperation.ts │ │ │ ├── HorizontalGradientOperation.ts │ │ │ ├── HslOperation.ts │ │ │ ├── InvertOperation.ts │ │ │ ├── IrregularBricksOperation.ts │ │ │ ├── KaleidoscopeOperation.ts │ │ │ ├── LineNoiseOperation.ts │ │ │ ├── MandelbrotOperation.ts │ │ │ ├── MirrorOperation.ts │ │ │ ├── MixerOperation.ts │ │ │ ├── MonochromeEdgeDetectorOperation.ts │ │ │ ├── Operation37.ts │ │ │ ├── PerlinNoiseOperation.ts │ │ │ ├── PseudoRandomNoiseOperation.ts │ │ │ ├── RangeOperation.ts │ │ │ ├── RasterizerOperation.ts │ │ │ ├── SpriteSourceOperation.ts │ │ │ ├── SquareWaveformOperation.ts │ │ │ ├── TextureOperation.ts │ │ │ ├── TextureOperationFactory.ts │ │ │ ├── TextureSourceOperation.ts │ │ │ ├── TilingOperation.ts │ │ │ ├── TilingSpriteOperation.ts │ │ │ ├── TrigWarpOperation.ts │ │ │ ├── VerticalGradientOperation.ts │ │ │ ├── VoronoiNoiseOperation.ts │ │ │ └── WeaveOperation.ts │ └── util │ │ ├── ArrayUtils.ts │ │ ├── ColorUtil.ts │ │ ├── HeightCalc.ts │ │ └── StringUtil.ts ├── setupTests.ts ├── shaders.d.ts └── util │ ├── BytesUtil.ts │ ├── DeviceUtil.ts │ ├── FloatUtil.ts │ ├── Hasher.ts │ └── MathUtil.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | caches/ 26 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | # Deliberately minified file 26 | *.min.* -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 4, 3 | "printWidth": 100, 4 | "trailingComma": "all", 5 | "semi": true, 6 | "importOrder": ["^[./]"], 7 | "importOrderSeparation": true, 8 | "importOrderSortSpecifiers": true, 9 | "plugins": ["@trivago/prettier-plugin-sort-imports"] 10 | } 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2022-2023, dennisdev 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RuneScape Map Viewer 2 | 3 | [Website](https://osrs.world) | [Discord](https://discord.gg/WfEWPE5wUd) 4 | 5 | A webapp for exploring current and historical versions of RuneScape. 6 | 7 | Lumbridge 8 | 9 | ## Running locally 10 | 11 | ``` 12 | $ git clone https://github.com/dennisdev/rs-map-viewer.git 13 | $ cd rs-map-viewer 14 | $ yarn install 15 | $ yarn run download-caches 16 | $ yarn start 17 | ``` 18 | 19 | ## Credits 20 | 21 | - Jagex 22 | - [RuneLite](https://github.com/runelite/runelite) 23 | - [OpenRS2 Archive](https://archive.openrs2.org/) - Caches 24 | - [RuneScape Archive](https://rs-archive.github.io/) - Caches 25 | - [OSRS Wiki](https://oldschool.runescape.wiki/) - Item spawns 26 | - [2004scape](https://github.com/2004scape/Server) - Npc spawns 27 | - [2009scape](https://gitlab.com/2009scape/2009scape) - Npc spawns 28 | - [RuneStar](https://github.com/RuneStar/fonts) - Fonts 29 | - [Blurite](https://github.com/blurite/pathfinder) - Some pathfinding stuff 30 | - [RuneApps Model Viewer](https://github.com/skillbert/rsmv) - Some procedural texture stuff 31 | -------------------------------------------------------------------------------- /craco.config.js: -------------------------------------------------------------------------------- 1 | const { when, whenDev, addBeforeLoader, loaderByName } = require("@craco/craco"); 2 | 3 | const ThreadsPlugin = require("threads-plugin"); 4 | const JsonMinimizerPlugin = require("json-minimizer-webpack-plugin"); 5 | 6 | const express = require("express"); 7 | 8 | module.exports = { 9 | webpack: { 10 | configure: (webpackConfig) => { 11 | const glslLoader = { 12 | test: /\.(glsl|vs|fs)$/, 13 | loader: "ts-shader-loader", 14 | }; 15 | 16 | // Kind of a hack to get the glsl loader to work 17 | // https://github.com/dilanx/craco/issues/486 18 | for (const rule of webpackConfig.module.rules) { 19 | if (rule.oneOf) { 20 | rule.oneOf.unshift(glslLoader); 21 | break; 22 | } 23 | } 24 | 25 | webpackConfig.module.rules.push({ 26 | resourceQuery: /url/, 27 | type: "asset/resource", 28 | }); 29 | webpackConfig.module.rules.push({ 30 | resourceQuery: /source/, 31 | type: "asset/source", 32 | }); 33 | 34 | // addBeforeLoader(webpackConfig, loaderByName('file-loader'), glslLoader); 35 | 36 | webpackConfig.resolve.fallback = { 37 | fs: false, 38 | }; 39 | 40 | webpackConfig.resolve.extensions = [".web.js", ...webpackConfig.resolve.extensions]; 41 | 42 | webpackConfig.optimization.minimizer.push(new JsonMinimizerPlugin()); 43 | 44 | return webpackConfig; 45 | }, 46 | plugins: [new ThreadsPlugin()], 47 | }, 48 | devServer: { 49 | headers: { 50 | "Cross-Origin-Opener-Policy": "same-origin", 51 | "Cross-Origin-Embedder-Policy": "require-corp", 52 | }, 53 | client: { 54 | overlay: { 55 | errors: true, 56 | warnings: false, 57 | runtimeErrors: (error) => { 58 | if (error instanceof DOMException && error.name === "AbortError") { 59 | return false; 60 | } 61 | return true; 62 | }, 63 | }, 64 | }, 65 | setupMiddlewares: (middlewares, devServer) => { 66 | if (!devServer) { 67 | throw new Error("webpack-dev-server is not defined"); 68 | } 69 | 70 | devServer.app.use("/caches", express.static("caches")); 71 | 72 | return middlewares; 73 | }, 74 | }, 75 | }; 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rs-map-viewer", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@foxglove/wasm-bz2": "^0.1.1", 7 | "@testing-library/jest-dom": "^5.14.1", 8 | "@testing-library/react": "^13.0.0", 9 | "@testing-library/user-event": "^13.2.1", 10 | "@types/jest": "^27.0.1", 11 | "@types/node": "^16.7.13", 12 | "@types/react": "^18.0.0", 13 | "@types/react-dom": "^18.0.0", 14 | "@webgpu/types": "^0.1.40", 15 | "bzip2": "^0.1.1", 16 | "denque": "^2.1.0", 17 | "fast-memoize": "^2.5.2", 18 | "file-saver": "^2.0.5", 19 | "gl-matrix": "^3.4.3", 20 | "gzip-js": "^0.3.2", 21 | "java-random": "^0.4.0", 22 | "js-xxhash": "^2.0.0", 23 | "jszip": "^3.10.1", 24 | "leva": "^0.9.35", 25 | "picogl": "^0.17.9", 26 | "react": "^18.2.0", 27 | "react-dom": "^18.2.0", 28 | "react-joystick-component": "^6.2.1", 29 | "react-modal": "^3.16.1", 30 | "react-router-dom": "^6.14.2", 31 | "react-scripts": "5.0.1", 32 | "react-select": "^5.7.4", 33 | "threads": "^1.7.0", 34 | "typescript": "^5.1.6", 35 | "usehooks-ts": "^2.9.1", 36 | "wasm-gzip": "^1.0.1", 37 | "web-vitals": "^2.1.0", 38 | "webfontloader": "^1.6.28", 39 | "xxhash-wasm": "^1.0.2" 40 | }, 41 | "scripts": { 42 | "start": "craco start", 43 | "build": "craco build", 44 | "test": "craco test", 45 | "lint": "prettier --write \"./**/*.{js,ts,jsx,tsx,css,md}\"", 46 | "prepare": "husky install", 47 | "download-caches": "node scripts/download-caches.js", 48 | "export-textures": "npx tsx scripts/cache/export-textures.ts", 49 | "export-height-map": "npx tsx scripts/cache/export-height-map.ts" 50 | }, 51 | "eslintConfig": { 52 | "extends": [ 53 | "react-app", 54 | "react-app/jest" 55 | ] 56 | }, 57 | "browserslist": { 58 | "production": [ 59 | "chrome >= 67", 60 | "edge >= 79", 61 | "firefox >= 68", 62 | "opera >= 54", 63 | "safari >= 14" 64 | ], 65 | "development": [ 66 | "last 1 chrome version", 67 | "last 1 firefox version", 68 | "last 1 safari version" 69 | ] 70 | }, 71 | "husky": { 72 | "hooks": { 73 | "pre-commit": "lint-staged" 74 | } 75 | }, 76 | "lint-staged": { 77 | "*.{js,ts,jsx,tsx,css,md}": "prettier --write" 78 | }, 79 | "devDependencies": { 80 | "@craco/craco": "^7.1.0", 81 | "@trivago/prettier-plugin-sort-imports": "^4.3.0", 82 | "@types/file-saver": "^2.0.7", 83 | "@types/gzip-js": "^0.3.3", 84 | "@types/react-modal": "^3.16.0", 85 | "@types/webfontloader": "^1.6.35", 86 | "adm-zip": "^0.5.10", 87 | "husky": "^8.0.3", 88 | "json-minimizer-webpack-plugin": "^4.0.0", 89 | "lint-staged": "^13.2.3", 90 | "path": "^0.12.7", 91 | "prettier": "^3.0.0", 92 | "sharp": "^0.32.6", 93 | "threads-plugin": "^1.4.0", 94 | "ts-node": "^10.9.1", 95 | "ts-shader-loader": "^2.0.2" 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteBase / 3 | RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC] 4 | RewriteRule ^(.*)$ https://%1/$1 [R=301,L] 5 | 6 | 7 | Header append Cross-Origin-Opener-Policy: same-origin 8 | Header append Cross-Origin-Embedder-Policy: require-corp 9 | 10 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisdev/rs-map-viewer/dd102bcca1bf2cce445d075ddb867b3b129f3f3a/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisdev/rs-map-viewer/dd102bcca1bf2cce445d075ddb867b3b129f3f3a/public/favicon.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | OSRS Map Viewer 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/static/media/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | # AddEncoding br .wasm 3 | AddType application/wasm .wasm 4 | 5 | -------------------------------------------------------------------------------- /screenshots/lumbridge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisdev/rs-map-viewer/dd102bcca1bf2cce445d075ddb867b3b129f3f3a/screenshots/lumbridge.png -------------------------------------------------------------------------------- /scripts/cache/export-textures.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import sharp from "sharp"; 3 | 4 | import { CacheSystem } from "../../src/rs/cache/CacheSystem"; 5 | import { getCacheLoaderFactory } from "../../src/rs/cache/loader/CacheLoaderFactory"; 6 | import { loadCache, loadCacheInfos, loadCacheList } from "./load-util"; 7 | 8 | function saveArgbArrayToPng(pixels: Int32Array, width: number, height: number, outputPath: string) { 9 | // Convert ARGB to RGBA 10 | const rgbaPixels = new Uint8Array(pixels.length * 4); 11 | for (let i = 0; i < pixels.length; i++) { 12 | rgbaPixels[i * 4 + 0] = (pixels[i] >> 16) & 0xff; // R 13 | rgbaPixels[i * 4 + 1] = (pixels[i] >> 8) & 0xff; // G 14 | rgbaPixels[i * 4 + 2] = pixels[i] & 0xff; // B 15 | rgbaPixels[i * 4 + 3] = (pixels[i] >> 24) & 0xff; // A 16 | } 17 | 18 | // Convert to PNG using sharp 19 | sharp(rgbaPixels, { 20 | raw: { 21 | width: width, 22 | height: height, 23 | channels: 4, 24 | }, 25 | }) 26 | .toFile(outputPath) 27 | .catch((err) => console.error(err)); 28 | } 29 | 30 | const SIZE = 128; 31 | 32 | const caches = loadCacheInfos(); 33 | const cacheList = loadCacheList(caches); 34 | 35 | const cacheInfo = cacheList.latest; 36 | 37 | const loadedCache = loadCache(cacheInfo); 38 | 39 | const cacheSystem = CacheSystem.fromFiles(loadedCache.type, loadedCache.files); 40 | const cacheLoaderFactory = getCacheLoaderFactory(cacheInfo, cacheSystem); 41 | 42 | const textureLoader = cacheLoaderFactory.getTextureLoader(); 43 | 44 | fs.mkdirSync("./textures", { recursive: true }); 45 | 46 | for (const id of textureLoader.getTextureIds()) { 47 | const pixels = textureLoader.getPixelsArgb(id, SIZE, false, 1.0); 48 | 49 | const outputPath = `./textures/${id}.png`; 50 | saveArgbArrayToPng(pixels, SIZE, SIZE, outputPath); 51 | } 52 | -------------------------------------------------------------------------------- /scripts/cache/load-util.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | 3 | import { CacheList, LoadedCache, XteaMap } from "../../src/mapviewer/Caches"; 4 | import { CacheFiles } from "../../src/rs/cache/CacheFiles"; 5 | import { CacheInfo, getLatestCache } from "../../src/rs/cache/CacheInfo"; 6 | import { detectCacheType } from "../../src/rs/cache/CacheType"; 7 | 8 | export function loadCacheInfos(): CacheInfo[] { 9 | const json = fs.readFileSync("./caches/caches.json", "utf8"); 10 | return JSON.parse(json); 11 | } 12 | 13 | export function loadCacheList(caches: CacheInfo[]): CacheList { 14 | const latest = getLatestCache(caches); 15 | if (!latest) { 16 | throw new Error("No latest cache"); 17 | } 18 | return { 19 | caches, 20 | latest, 21 | }; 22 | } 23 | 24 | export function loadCacheFiles(cache: CacheInfo): CacheFiles { 25 | const cachePath = "./caches/" + cache.name + "/"; 26 | 27 | const files = new Map(); 28 | 29 | fs.readdirSync(cachePath).forEach((fileName: string) => { 30 | const buffer = fs.readFileSync(cachePath + fileName); 31 | 32 | const newBuffer = new ArrayBuffer(buffer.byteLength); 33 | const newView = new Uint8Array(newBuffer); 34 | for (let i = 0; i < buffer.byteLength; i++) { 35 | newView[i] = buffer[i]; 36 | } 37 | 38 | files.set(fileName, newBuffer); 39 | }); 40 | 41 | return new CacheFiles(files); 42 | } 43 | 44 | export function loadCache(info: CacheInfo): LoadedCache { 45 | const files = loadCacheFiles(info); 46 | const xteas = loadXteas(info); 47 | return { 48 | info, 49 | type: detectCacheType(info), 50 | files, 51 | xteas, 52 | }; 53 | } 54 | 55 | export function loadXteas(cache: CacheInfo): XteaMap { 56 | const cachePath = "./caches/" + cache.name + "/"; 57 | const json = fs.readFileSync(cachePath + "keys.json", "utf8"); 58 | const data: Record = JSON.parse(json); 59 | return new Map(Object.keys(data).map((key) => [parseInt(key), data[key]])); 60 | } 61 | -------------------------------------------------------------------------------- /src/components/renderer/RenderStats.ts: -------------------------------------------------------------------------------- 1 | export class RenderStats { 2 | frameCount: number = 0; 3 | 4 | frameTime: number = 0; 5 | frameTimeFps: number = 0; 6 | 7 | lastFrameTime: DOMHighResTimeStamp | undefined; 8 | 9 | frameTimeStart: number = 0; 10 | frameTimeJs: number = 0; 11 | 12 | getDeltaTime(time: DOMHighResTimeStamp): number { 13 | return time - (this.lastFrameTime ?? time); 14 | } 15 | 16 | update(time: DOMHighResTimeStamp) { 17 | this.frameTime = this.getDeltaTime(time); 18 | this.lastFrameTime = time; 19 | this.frameTimeStart = performance.now(); 20 | if (this.frameTime !== 0) { 21 | this.frameTimeFps = 1000 / this.frameTime; 22 | } 23 | } 24 | 25 | onFrameEnd() { 26 | this.frameCount++; 27 | if (this.lastFrameTime !== undefined) { 28 | this.frameTimeJs = performance.now() - this.frameTimeStart; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/components/renderer/Renderer.ts: -------------------------------------------------------------------------------- 1 | import { pixelRatio } from "../../util/DeviceUtil"; 2 | import { RenderStats } from "./RenderStats"; 3 | 4 | function resizeCanvas(canvas: HTMLCanvasElement) { 5 | const devicePixelRatio = pixelRatio; 6 | const width = canvas.offsetWidth * devicePixelRatio; 7 | const height = canvas.offsetHeight * devicePixelRatio; 8 | 9 | if (width !== canvas.width || height !== canvas.height) { 10 | canvas.width = width; 11 | canvas.height = height; 12 | return true; 13 | } 14 | 15 | return false; 16 | } 17 | 18 | export abstract class Renderer { 19 | canvas: HTMLCanvasElement; 20 | animationId: number | undefined; 21 | running: boolean = false; 22 | 23 | fpsLimit: number = 999; 24 | 25 | stats: RenderStats = new RenderStats(); 26 | 27 | constructor() { 28 | this.canvas = document.createElement("canvas"); 29 | this.canvas.style.width = "100%"; 30 | this.canvas.style.height = "100%"; 31 | this.canvas.tabIndex = 0; 32 | } 33 | 34 | abstract init(): Promise; 35 | 36 | abstract cleanUp(): void; 37 | 38 | start() { 39 | this.running = true; 40 | this.animationId = requestAnimationFrame(this.frameCallback); 41 | } 42 | 43 | stop() { 44 | this.running = false; 45 | if (this.animationId !== undefined) { 46 | cancelAnimationFrame(this.animationId); 47 | this.animationId = undefined; 48 | } 49 | this.cleanUp(); 50 | } 51 | 52 | onResize(width: number, height: number) {} 53 | 54 | frameCallback = (time: DOMHighResTimeStamp) => { 55 | try { 56 | const resized = resizeCanvas(this.canvas); 57 | if (resized) { 58 | this.onResize(this.canvas.width, this.canvas.height); 59 | } 60 | 61 | const deltaTime = this.stats.getDeltaTime(time); 62 | 63 | if (this.fpsLimit && deltaTime > 0) { 64 | const tolerance = 1; 65 | if (deltaTime < 1000 / this.fpsLimit - tolerance) { 66 | return; 67 | } 68 | } 69 | 70 | this.stats.update(time); 71 | 72 | this.render(time, deltaTime, resized); 73 | 74 | this.onFrameEnd(); 75 | } finally { 76 | if (this.running) { 77 | this.animationId = requestAnimationFrame(this.frameCallback); 78 | } 79 | } 80 | }; 81 | 82 | abstract render( 83 | time: DOMHighResTimeStamp, 84 | deltaTime: DOMHighResTimeStamp, 85 | resized: boolean, 86 | ): void; 87 | 88 | onFrameEnd() { 89 | this.stats.onFrameEnd(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/components/renderer/RendererCanvas.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | import { Renderer } from "./Renderer"; 4 | 5 | export interface RendererCanvasProps { 6 | renderer: Renderer; 7 | } 8 | 9 | export function RendererCanvas({ renderer }: RendererCanvasProps): JSX.Element { 10 | const divRef = useRef(null); 11 | 12 | useEffect(() => { 13 | if (!divRef.current) { 14 | return; 15 | } 16 | divRef.current.appendChild(renderer.canvas); 17 | 18 | renderer.init().then(() => { 19 | renderer.start(); 20 | }); 21 | 22 | return () => { 23 | renderer.stop(); 24 | divRef.current?.removeChild(renderer.canvas); 25 | }; 26 | }, [renderer]); 27 | 28 | return
; 29 | } 30 | -------------------------------------------------------------------------------- /src/components/rs/loading/OsrsLoadingBar.css: -------------------------------------------------------------------------------- 1 | .loading-bar { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | position: relative; 6 | width: 305px; 7 | height: 35px; 8 | font-family: ui-monospace, SFMono-Regular, Menlo, "Roboto Mono", monospace; 9 | /* font-family: Helvetica; */ 10 | font-size: 13px; 11 | font-weight: bold; 12 | color: white; 13 | background-color: black; 14 | box-sizing: border-box; 15 | border: solid #8c1111 1px; 16 | } 17 | 18 | .loading-bar-text { 19 | display: flex; 20 | z-index: 1; 21 | } 22 | 23 | .loading-bar-progress-container { 24 | position: absolute; 25 | width: 100%; 26 | height: 100%; 27 | } 28 | 29 | .loading-bar-progress { 30 | border: solid black 1px; 31 | box-sizing: border-box; 32 | background-color: #8c1111; 33 | /* width: 75%; */ 34 | height: 100%; 35 | } 36 | -------------------------------------------------------------------------------- /src/components/rs/loading/OsrsLoadingBar.tsx: -------------------------------------------------------------------------------- 1 | import "./OsrsLoadingBar.css"; 2 | 3 | interface OsrsLoadingBarProps { 4 | text: string; 5 | progress: number; 6 | } 7 | 8 | export function OsrsLoadingBar({ text, progress }: OsrsLoadingBarProps): JSX.Element { 9 | return ( 10 |
11 |
12 |
13 |
14 |
15 | {text} - {progress}% 16 |
17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/rs/menu/OsrsMenu.css: -------------------------------------------------------------------------------- 1 | .context-menu-container { 2 | position: absolute; 3 | top: 100px; 4 | left: 100px; 5 | font-family: "OSRS Bold", ui-monospace, SFMono-Regular, Menlo, "Roboto Mono", monospace; 6 | display: flex; 7 | white-space: nowrap; 8 | border: 10px solid transparent; 9 | } 10 | 11 | .context-menu-container.tooltip { 12 | pointer-events: none; 13 | border: 0; 14 | } 15 | 16 | .context-menu { 17 | cursor: default; 18 | font-size: 16px; 19 | font-smooth: never; 20 | /* font-weight: normal; */ 21 | /* filter: opacity(100%); */ 22 | text-shadow: 1px 1px 0 black; 23 | /* display: flex; */ 24 | border: solid #5d5447 1px; 25 | } 26 | 27 | .title { 28 | padding-left: 2px; 29 | padding-right: 6px; 30 | color: #5d5447; 31 | background-color: black; 32 | border: solid black 1px; 33 | } 34 | 35 | .line { 36 | width: 100%; 37 | border-top: solid #5d5447 1px; 38 | } 39 | 40 | .options { 41 | color: white; 42 | background-color: #5d5447; 43 | border: solid black 1px; 44 | } 45 | 46 | .tooltip .options { 47 | background-color: #5d5447b2; 48 | } 49 | 50 | .option { 51 | padding-left: 2px; 52 | padding-right: 6px; 53 | padding-bottom: 1px; 54 | } 55 | 56 | .option:hover .option-name { 57 | color: #ffff00; 58 | } 59 | 60 | .object-name { 61 | color: #00ffff; 62 | } 63 | 64 | .npc-name { 65 | color: #ffff00; 66 | } 67 | 68 | .npc-level { 69 | color: #c0ff00; 70 | } 71 | 72 | .item-name { 73 | color: #ff9040; 74 | } 75 | 76 | .target-id { 77 | color: #ff5d40; 78 | } 79 | -------------------------------------------------------------------------------- /src/components/rs/minimap/MinimapContainer.css: -------------------------------------------------------------------------------- 1 | .minimap-container { 2 | position: relative; 3 | width: 181px; 4 | height: 165px; 5 | overflow: hidden; 6 | } 7 | 8 | .minimap-container .compass { 9 | cursor: pointer; 10 | clip-path: circle(18px); 11 | position: absolute; 12 | left: -4px; 13 | top: -4px; 14 | } 15 | 16 | .minimap-container .worldmap-icon { 17 | cursor: pointer; 18 | position: absolute; 19 | left: 144px; 20 | top: 128px; 21 | width: 32px; 22 | height: 32px; 23 | background-image: url(./worldmap-icon.png); 24 | background-size: 32px 32px; 25 | } 26 | 27 | .minimap-container .worldmap-icon:hover { 28 | background-image: url(./worldmap-icon-hover.png); 29 | } 30 | 31 | .minimap-container .minimap { 32 | clip-path: circle(77px at 384px 384px); 33 | position: absolute; 34 | left: -285px; 35 | top: -301px; 36 | transform-origin: 384px 384px; 37 | background-color: black; 38 | } 39 | 40 | .minimap-images { 41 | position: relative; 42 | } 43 | 44 | .minimap-image { 45 | position: absolute; 46 | padding: 0; 47 | margin: 0; 48 | } 49 | -------------------------------------------------------------------------------- /src/components/rs/minimap/MinimapImage.tsx: -------------------------------------------------------------------------------- 1 | interface MinimapImageProps { 2 | src: string; 3 | 4 | left: number; 5 | top: number; 6 | } 7 | 8 | export function MinimapImage({ src, left, top }: MinimapImageProps) { 9 | return ( 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/components/rs/minimap/compass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisdev/rs-map-viewer/dd102bcca1bf2cce445d075ddb867b3b129f3f3a/src/components/rs/minimap/compass.png -------------------------------------------------------------------------------- /src/components/rs/minimap/minimap-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisdev/rs-map-viewer/dd102bcca1bf2cce445d075ddb867b3b129f3f3a/src/components/rs/minimap/minimap-black.png -------------------------------------------------------------------------------- /src/components/rs/minimap/minimap-frame.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisdev/rs-map-viewer/dd102bcca1bf2cce445d075ddb867b3b129f3f3a/src/components/rs/minimap/minimap-frame.png -------------------------------------------------------------------------------- /src/components/rs/minimap/worldmap-icon-hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisdev/rs-map-viewer/dd102bcca1bf2cce445d075ddb867b3b129f3f3a/src/components/rs/minimap/worldmap-icon-hover.png -------------------------------------------------------------------------------- /src/components/rs/minimap/worldmap-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dennisdev/rs-map-viewer/dd102bcca1bf2cce445d075ddb867b3b129f3f3a/src/components/rs/minimap/worldmap-icon.png -------------------------------------------------------------------------------- /src/components/rs/select/OsrsSelect.css: -------------------------------------------------------------------------------- 1 | .osrs-select-container { 2 | min-width: 200px; 3 | /* max-width: 140px; */ 4 | border: 1px solid; 5 | border-color: #0e0e0c; 6 | } 7 | 8 | .osrs-select-control { 9 | border: 1px solid !important; 10 | border-color: #474745 !important; 11 | border-radius: 0 !important; 12 | box-shadow: none !important; 13 | min-height: 0 !important; 14 | background-color: #3e3529 !important; 15 | } 16 | 17 | .osrs-select-value-container { 18 | padding: 0 !important; 19 | text-align: center !important; 20 | font-size: 16px; 21 | font-family: "OSRS Small" !important; 22 | text-shadow: 1px 1px 0 black; 23 | /* text-shadow: 1px 1px 0 black; */ 24 | } 25 | 26 | .osrs-select-input { 27 | margin: 0 !important; 28 | /* padding-top: 1px !important; */ 29 | /* padding-bottom: 1px !important; */ 30 | text-align: center !important; 31 | color: white !important; 32 | /* min-width: 100% !important; */ 33 | } 34 | 35 | .osrs-select-input input { 36 | /* min-width: 100% !important; */ 37 | text-align: center !important; 38 | text-shadow: 1px 1px 0 black; 39 | /* grid-area: a !important; */ 40 | } 41 | 42 | .osrs-select-single-value { 43 | text-align: center !important; 44 | color: #ff981f !important; 45 | } 46 | 47 | .osrs-select-placeholder { 48 | text-align: center; 49 | color: #9f9f9f !important; 50 | /* text-shadow: 1px 1px 0 black; */ 51 | } 52 | 53 | .osrs-select-menu { 54 | border: 1px solid #474745; 55 | outline: 1px solid #0e0e0c; 56 | border-radius: 0 !important; 57 | /* margin: 0 !important; */ 58 | } 59 | 60 | .osrs-select-menu-list { 61 | padding: 0 !important; 62 | margin: 0 !important; 63 | } 64 | 65 | .osrs-select-option { 66 | font-size: 16px; 67 | font-family: "OSRS Small" !important; 68 | text-shadow: 1px 1px 0 black; 69 | color: #ff981f !important; 70 | /* background-color: #3e3529; */ 71 | } 72 | 73 | .osrs-select-option:hover { 74 | background-color: #787169; 75 | } 76 | 77 | .osrs-select-no-options-message { 78 | font-size: 16px; 79 | font-family: "OSRS Small" !important; 80 | text-shadow: 1px 1px 0 black; 81 | color: #ff981f !important; 82 | } 83 | -------------------------------------------------------------------------------- /src/components/rs/select/OsrsSelect.tsx: -------------------------------------------------------------------------------- 1 | import { memo } from "react"; 2 | import Select, { GroupBase, OptionProps, Props, components, createFilter } from "react-select"; 3 | 4 | import "./OsrsSelect.css"; 5 | 6 | function OsrsSelectOption>({ 7 | children, 8 | ...props 9 | }: OptionProps) { 10 | const { onMouseMove, onMouseOver, ...rest } = props.innerProps; 11 | const newProps = { ...props, innerProps: rest }; 12 | return {children}; 13 | } 14 | 15 | export const OsrsSelect = memo(function OsrsSelect< 16 | Option, 17 | IsMulti extends boolean = false, 18 | Group extends GroupBase