Skip to content

Commit

Permalink
Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
jaller94 committed Jan 14, 2025
1 parent 6c96dba commit f822add
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -128,3 +128,5 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

/tracks
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# ImmoRally Track Renderer

A command line tool to turn ImmoRally tracks into SVG graphics.

```sh
bun install
export STEAM_FOLDER=~/.local/share/Steam
bun run src/index.ts
```
Binary file added bun.lockb
Binary file not shown.
16 changes: 16 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "immorally-track-renderer",
"license": "MIT",
"type": "module",
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5.7.2"
},
"dependencies": {
"@types/node": "^22.10.1",
"xmlbuilder2": "^3.1.1",
"zod": "^3.23.8"
}
}
92 changes: 92 additions & 0 deletions src/convert.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { create } from 'xmlbuilder2';

type Tile = {
shape: [number, number][],
type: string,
typeData?: string,
};

const tilesRegExp = /tile.*?\/tile/smg;
const typeRegExp = /type\n(.?)\n/;
const typeDataRegExp = /typeData\n(.?)\n/;
const shapeRegExp = /shape\n.*?\/shape/smg;
const vertRegExp = /vert\n(.*)\n(.*)\n\/vert/mg;

const aToJson = (input: string): Tile[] => {
const tiles: Tile[] = [];
const tileMatches = input.replace(/\r\n/g, '\n').matchAll(tilesRegExp);
for (const tileMatch of tileMatches) {
const shapeMatches = tileMatch[0].matchAll(shapeRegExp);
for (const shapeMatch of shapeMatches) {
const vertMatches = shapeMatch[0].matchAll(vertRegExp);
const typeMatch = tileMatch[0].match(typeRegExp);
if (!typeMatch) {
throw Error('No type');
}
const tile: Tile = {
type: typeMatch[1],
shape: [],
};
const typeDataMatch = tileMatch[0].match(typeDataRegExp);
if (typeDataMatch) {
tile.typeData = typeDataMatch[1];
}
for (const vertMatch of vertMatches) {
const vert: [number, number] = [Number.parseFloat(vertMatch[1]), Number.parseFloat(vertMatch[2])];
tile.shape.push(vert);
}
tiles.push(tile);
}
}
tiles.sort((a, b) => a.type.localeCompare(b.type));
return tiles;
}

const getColor = (type: string, typeData?: string): string => {
if (type === '0') {
return '#011';
}
if (type === '1') {
if (typeData === '1') {
return '#00f';
}
return '#0ff';
}
if (type === '2') {
return '#f90';
}
if (type === '3') {
return '#f00';
}
// Otherwise, use a debug color to easily spot unsupported types.
return 'pink';
}

export const convertImmoRallyTrackSlideToSvg = (track: string): string => {
// Create the svg document
const root = create({ version: '1.0' })
.ele('svg', { width: 1800, height: 1170, version: '1.0', 'xmlns': 'http://www.w3.org/2000/svg' });
const elements = root;

const trackRoot = aToJson(track);

elements.ele('rect', {
x: 0,
y: 0,
width: 1800,
height: 1170,
fill: 'black',
});

for (const tile of trackRoot) {
const color = getColor(tile.type, tile.typeData);
const points = tile.shape.map(vert => `${vert[0]}, ${vert[1]}`).join(' ') + ` ${tile.shape[0][0]}, ${tile.shape[0][1]}`;
elements.ele('polyline', {
points,
fill: 'none',
stroke: color ?? 'pink',
'stroke-width': 2,
});
}
return root.end({ prettyPrint: false });
};
38 changes: 38 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import fs from 'node:fs/promises';
import * as path from 'node:path';
import process from 'node:process';
import { homedir } from 'node:os';
import { convertImmoRallyTrackSlideToSvg } from './convert.ts';

// const inputPath = process.argv[2];
// const outputPath = process.argv[2] + '.svg';
// try {
// const data = await fs.readFile(inputPath, 'utf8');
// const output = convertImmoRallyTrackSlideToSvg(data);
// await fs.writeFile(outputPath, output, 'utf8');
// } catch (err) {
// console.error(err);
// process.exit(1);
// }

// STEAM_FOLDER is specific to Linux.
const STEAM_FOLDER = process.env.STEAM_FOLDER ?? path.join(homedir(), '.local/share/Steam');
const INPUT_DIR = process.env.INPUT_FOLDER ?? path.join(STEAM_FOLDER, 'steamapps/common/ImmoRally/irdata/tracks/');

await fs.mkdir('./tracks', {recursive: true});

for (const file of await fs.readdir(INPUT_DIR)) {
if (!file.endsWith('.track')) {
continue;
}
const inputPath = path.join(INPUT_DIR, file);
const outputPath = path.join('./tracks/', file + '.svg');
try {
const data = await fs.readFile(inputPath, 'utf8');
const output = convertImmoRallyTrackSlideToSvg(data);
await fs.writeFile(outputPath, output, 'utf8');
} catch (err) {
console.error(err);
process.exit(1);
}
}

0 comments on commit f822add

Please sign in to comment.