Skip to content

Commit

Permalink
Merge pull request #278 from bcdrme/map-metadata-integration
Browse files Browse the repository at this point in the history
Integration of the map-metadata service
  • Loading branch information
bcdrme authored Nov 10, 2024
2 parents 4571ecf + 5a3fcd2 commit 95a6fee
Show file tree
Hide file tree
Showing 101 changed files with 2,205 additions and 4,847 deletions.
5 changes: 4 additions & 1 deletion forge.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ const config = {
},
rebuildConfig: {},
makers: [
new MakerSquirrel({}),
new MakerSquirrel({
authors: "BAR Team",
description: "Beyond All Reason Lobby",
}),
new MakerRpm({
options: {
mimeType: ["application/sdfz"],
Expand Down
1,655 changes: 317 additions & 1,338 deletions package-lock.json

Large diffs are not rendered by default.

40 changes: 16 additions & 24 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,14 @@
"node": "20.18.0"
},
"dependencies": {
"7zip-min": "^1.4.5",
"@extractus/feed-extractor": "^7.1.3",
"@iconify-icons/mdi": "^1.2.48",
"@iconify/json": "^2.2.264",
"@iconify/json": "^2.2.270",
"@iconify/vue": "^4.1.2",
"@octokit/rest": "^21.0.2",
"@pixi/unsafe-eval": "^7.4.2",
"@sinclair/typebox": "^0.33.17",
"@vueuse/core": "^11.1.0",
"7zip-min": "^1.4.5",
"@sinclair/typebox": "^0.33.21",
"@vueuse/core": "^11.2.0",
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"axios": "^1.7.7",
Expand All @@ -45,18 +44,12 @@
"flag-icons": "^7.2.3",
"glob-promise": "^6.0.7",
"howler": "^2.2.4",
"jimp": "^1.6.0",
"luaparse": "^0.3.1",
"marked": "^14.1.3",
"node-stream-zip": "^1.15.0",
"marked": "^15.0.0",
"pino": "^9.5.0",
"pino-pretty": "^11.3.0",
"pixi.js": "^8.5.2",
"pino-pretty": "^12.1.0",
"primeicons": "^6.0.1",
"primevue": "3.23.0",
"remove": "^0.1.5",
"tga": "^1.0.7",
"uuid": "^10.0.0",
"vue-i18n": "^10.0.4",
"vue-router": "^4.4.5"
},
Expand All @@ -77,26 +70,25 @@
"@types/howler": "^2.2.12",
"@types/luaparse": "^0.2.12",
"@types/marked": "^6.0.0",
"@types/node": "^20.17.1",
"@types/uuid": "^10.0.0",
"@types/node": "^20.17.6",
"@vitejs/plugin-vue": "^5.1.4",
"cross-env": "^7.0.3",
"electron": "^33.0.1",
"eslint": "^9.13.0",
"electron": "33.2.0",
"eslint": "^9.14.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-unused-imports": "^4.1.4",
"eslint-plugin-vue": "^9.29.1",
"globals": "^15.11.0",
"eslint-plugin-vue": "^9.30.0",
"globals": "^15.12.0",
"prettier": "^3.3.3",
"sass": "^1.80.4",
"sass": "^1.80.6",
"ts-node": "^10.9.2",
"type-fest": "^4.26.1",
"typescript": "^5.6.3",
"typescript-eslint": "^8.11.0",
"typescript-eslint": "^8.13.0",
"unplugin-vue-router": "^0.10.8",
"vite": "^5.4.10",
"vite-plugin-static-copy": "^2.0.0",
"vitest": "^2.1.3",
"vue-tsc": "^2.1.6"
"vite-plugin-static-copy": "^2.1.0",
"vitest": "^2.1.4",
"vue-tsc": "^2.1.10"
}
}
2 changes: 1 addition & 1 deletion src/main/content/engine/engine-version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ export interface EngineAI {
shortName: string;
version: string;
description: string;
options: LuaOptionSection[];
options?: LuaOptionSection[];
}
29 changes: 17 additions & 12 deletions src/main/content/game/game-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ export class GameContentAPI extends PrDownloaderAPI<GameVersion> {
for (const packageFile of packages) {
const packageMd5 = packageFile.replace(".sdp", "");
const gameVersion = this.packageGameVersionLookup[packageMd5];
const luaOptionSections = await this.getGameOptions(packageMd5);
const ais = await this.getAis(packageMd5);
if (gameVersion) {
this.installedVersions.push({ gameVersion, packageMd5 });
this.installedVersions.push({ gameVersion, packageMd5, luaOptionSections, ais });
}
}
this.sortVersions();
Expand Down Expand Up @@ -117,13 +119,16 @@ export class GameContentAPI extends PrDownloaderAPI<GameVersion> {
});
}

public async getGameOptions(version: string): Promise<LuaOptionSection[]> {
const gameVersion = this.installedVersions.find((installedVersion) => installedVersion.gameVersion === version);
// TODO: cache per session
const gameFiles = await this.getGameFiles(gameVersion.packageMd5, "modoptions.lua", true);
const gameOptionsLua = gameFiles[0].data;
// TODO maybe send ais as well
return parseLuaOptions(gameOptionsLua);
protected async getGameOptions(packageMd5: string): Promise<LuaOptionSection[]> {
const gameFiles = await this.getGameFiles(packageMd5, "modoptions.lua", true);
const modoptions = gameFiles[0].data;
return parseLuaOptions(modoptions);
}

protected async getAis(packageMd5: string): Promise<GameAI[]> {
const gameFiles = await this.getGameFiles(packageMd5, "luaai.lua", true);
const luaai = gameFiles[0].data;
return this.parseAis(luaai);
}

public async getScenarios(): Promise<Scenario[]> {
Expand Down Expand Up @@ -259,14 +264,13 @@ export class GameContentAPI extends PrDownloaderAPI<GameVersion> {
}

protected async addGame(gameVersion: string) {
//TODO reimplement ais lookup now that its no longer in the GameVersion object
// const luaAiFile = (await this.getGameFiles({ md5: packageMd5 }, "luaai.lua", true))[0];
// const ais = await this.parseAis(luaAiFile.data);
if (gameVersion === "byar:test") {
await this.scanPackagesDir();
} else {
const packageMd5 = this.gameVersionPackageLookup[gameVersion];
this.installedVersions.push({ gameVersion, packageMd5 });
const luaOptionSections = await this.getGameOptions(packageMd5);
const ais = await this.getAis(packageMd5);
this.installedVersions.push({ gameVersion, packageMd5, luaOptionSections, ais });
this.sortVersions();
}
}
Expand All @@ -277,6 +281,7 @@ export class GameContentAPI extends PrDownloaderAPI<GameVersion> {
for (const def of aiDefinitions) {
ais.push({
name: def.name,
shortName: def.name,
description: def.desc,
});
}
Expand Down
6 changes: 6 additions & 0 deletions src/main/content/game/game-version.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import { LuaOptionSection } from "@main/content/game/lua-options";

export type GameVersion = {
gameVersion: string;
packageMd5: string;
luaOptionSections: LuaOptionSection[];
ais: GameAI[];
};

export interface GameAI {
name: string;
shortName: string;
description: string;
options?: LuaOptionSection[];
}
25 changes: 25 additions & 0 deletions src/main/content/maps/box-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//Boxes
export function pointsToXYWH(points: { x: number; y: number }[]) {
if (points.length !== 2) {
throw new Error("pointsToXYWH expects exactly 2 points");
}
const xs = points.map((point) => point.x);
const ys = points.map((point) => point.y);
const x = Math.min(...xs);
const y = Math.min(...ys);
const w = Math.max(...xs) - x;
const h = Math.max(...ys) - y;
return { x, y, w, h };
}

export function spadsPointsToLTRBPercent(points: { x: number; y: number }[]) {
if (points.length !== 2) {
throw new Error("SpadsPointsToLTRBPercent expects exactly 2 points");
}
return {
left: points[0].x / 200,
top: points[0].y / 200,
right: points[1].x / 200,
bottom: points[1].y / 200,
};
}
114 changes: 24 additions & 90 deletions src/main/content/maps/map-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@ import * as path from "path";
import { MapData } from "@main/content/maps/map-data";
import { logger } from "@main/utils/logger";
import { Signal } from "$/jaz-ts-utils/signal";
import { delay } from "$/jaz-ts-utils/delay";
import { PrDownloaderAPI } from "@main/content/pr-downloader";
import { CONTENT_PATH } from "@main/config/app";
import { asyncParseMap } from "@main/content/maps/parse-map";
import chokidar from "chokidar";
import { UltraSimpleMapParser } from "$/map-parser/ultrasimple-map-parser";

Expand All @@ -17,11 +15,10 @@ const log = logger("map-content.ts");
* @todo replace queue method with syncMapCache function once prd returns map file name
*/
export class MapContentAPI extends PrDownloaderAPI<MapData> {
public mapNameFileNameLookup: { [scriptName: string]: string } = {};
public mapNameFileNameLookup: { [springName: string]: string } = {};
public fileNameMapNameLookup: { [fileName: string]: string } = {};

public readonly onMapCachingStarted: Signal<string> = new Signal();
public readonly onMapCached: Signal<MapData> = new Signal();
public readonly onMapAdded: Signal<string> = new Signal();
public readonly onMapDeleted: Signal<string> = new Signal();

protected readonly mapsDir = path.join(CONTENT_PATH, "maps");
Expand All @@ -31,7 +28,6 @@ export class MapContentAPI extends PrDownloaderAPI<MapData> {
public override async init() {
await fs.promises.mkdir(this.mapsDir, { recursive: true });
this.initLookupMaps();
this.startCacheMapConsumer();
this.startWatchingMapFolder();
return super.init();
}
Expand All @@ -41,18 +37,23 @@ export class MapContentAPI extends PrDownloaderAPI<MapData> {
const sd7filePaths = filePaths.filter((path) => path.endsWith(".sd7"));
log.debug(`Found ${sd7filePaths.length} maps`);
for (const filePath of sd7filePaths) {
const mapName = await this.getMapNameFromFile(filePath);
const fileName = path.basename(filePath);
this.mapNameFileNameLookup[mapName] = fileName;
this.fileNameMapNameLookup[fileName] = mapName;
try {
const mapName = await this.getMapNameFromFile(filePath);
const fileName = path.basename(filePath);
this.mapNameFileNameLookup[mapName] = fileName;
this.fileNameMapNameLookup[fileName] = mapName;
} catch (err) {
log.error(`File may be corrupted, removing ${filePath}: ${err}`);
fs.promises.rm(path.join(this.mapsDir, filePath));
}
}
log.info(`Found ${Object.keys(this.mapNameFileNameLookup).length} maps`);
}

protected async getMapNameFromFile(file: string) {
const ultraSimpleMapParser = new UltraSimpleMapParser();
const parsedMap = await ultraSimpleMapParser.parseMap(path.join(this.mapsDir, file));
return parsedMap.scriptName;
return parsedMap.springName;
}

protected startWatchingMapFolder() {
Expand All @@ -73,7 +74,7 @@ export class MapContentAPI extends PrDownloaderAPI<MapData> {
this.mapNameFileNameLookup[mapName] = filename;
this.fileNameMapNameLookup[filename] = mapName;
});
this.queueMapsToCache([filename]);
this.onMapAdded.dispatch(filename);
})
.on("unlink", (filepath) => {
if (!filepath.endsWith("sd7")) {
Expand All @@ -86,110 +87,43 @@ export class MapContentAPI extends PrDownloaderAPI<MapData> {
});
}

public isVersionInstalled(scriptName: string): boolean {
return this.mapNameFileNameLookup[scriptName] !== undefined;
public isVersionInstalled(springName: string): boolean {
return this.mapNameFileNameLookup[springName] !== undefined;
}

public async downloadMaps(scriptNames: string[]) {
return Promise.all(scriptNames.map((scriptName) => this.downloadMap(scriptName)));
public async downloadMaps(springNames: string[]) {
return Promise.all(springNames.map((springName) => this.downloadMap(springName)));
}

public async downloadMap(scriptName: string) {
if (this.isVersionInstalled(scriptName)) return;
if (this.currentDownloads.some((download) => download.name === scriptName)) {
public async downloadMap(springName: string) {
if (this.isVersionInstalled(springName)) return;
if (this.currentDownloads.some((download) => download.name === springName)) {
return await new Promise<void>((resolve) => {
this.onDownloadComplete.addOnce((mapData) => {
if (mapData.name === scriptName) {
if (mapData.name === springName) {
resolve();
}
});
});
}
const downloadInfo = await this.downloadContent("map", scriptName);
const downloadInfo = await this.downloadContent("map", springName);
this.onDownloadComplete.dispatch(downloadInfo);
}

public async attemptCacheErrorMaps() {
throw new Error("Method not implemented.");
}

//Method to sync the cache with the maps folder, if the folder doesnt have the map, download it. If the folder has a map that is not in the cache, cache it.
public async sync(maps: { scriptName: string; fileName: string }[]) {
const existingFiles = await this.scanFolderForMaps();
const mapsToDownload = maps.filter((map) => !existingFiles.includes(map.fileName));
mapsToDownload.forEach((map) => this.onMapDeleted.dispatch(map.fileName));
this.downloadMaps(mapsToDownload.map((map) => map.scriptName));
const mapFileNames = maps.map((map) => map.fileName);
const mapsToCache = existingFiles.filter((map) => !mapFileNames.includes(map));
this.queueMapsToCache(mapsToCache);
}

public async scanFolderForMaps() {
let mapFiles = await fs.promises.readdir(this.mapsDir);
mapFiles = mapFiles.filter((mapFile) => mapFile.endsWith("sd7"));
return mapFiles;
}

protected async queueMapsToCache(filenames?: string[]) {
let mapFiles = filenames;
if (!filenames) {
mapFiles = await this.scanFolderForMaps();
}
for (const mapFileToCache of mapFiles) {
this.mapCacheQueue.add(mapFileToCache);
this.onMapCachingStarted.dispatch(mapFileToCache);
}
}

public async uninstallVersion(version: MapData) {
const mapFile = path.join(this.mapsDir, version.fileName);
const mapFile = path.join(this.mapsDir, version.filename);
await fs.promises.rm(mapFile, { force: true, recursive: true });
log.debug(`Map removed: ${version.scriptName}`);
}

protected async startCacheMapConsumer() {
if (this.cachingMaps) {
log.warn("Don't call cacheMaps more than once");
return;
}
this.cachingMaps = true;

while (true) {
const [mapToCache] = this.mapCacheQueue;
if (mapToCache) {
await this.cacheMap(mapToCache);
} else {
await delay(500);
}
}
}

protected async cacheMap(mapFileName: string) {
try {
log.debug(`Caching: ${mapFileName}`);
console.time(`Cached: ${mapFileName}`);
const mapPath = path.join(this.mapsDir, mapFileName);
log.debug(`Parsing map asynchronously: ${mapFileName}`);
const mapData = await asyncParseMap(mapPath);
log.debug(`Parsed map: ${mapFileName}`);
this.onMapCached.dispatch(mapData);
console.timeEnd(`Cached: ${mapFileName}`);
} catch (err) {
log.error(`Error parsing map: ${mapFileName}`, err);
log.error(err);
//TODO emit error signal
}
this.mapCacheQueue.delete(mapFileName);
}

protected mapCached(mapName: string) {
return new Promise<MapData>((resolve) => {
this.onMapCached.addOnce((map) => {
if (map.scriptName === mapName) {
resolve(map);
}
});
});
log.debug(`Map removed: ${version.springName}`);
}
}

Expand Down
Loading

0 comments on commit 95a6fee

Please sign in to comment.