diff --git a/package-lock.json b/package-lock.json index 41cd183..0798e0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "jszip": "^3.10.1", "nax-ccuilib": "github:conorlawton/nax-ccuilib", "prettier": "3.2.4", + "semver": "^7.6.0", "typescript": "^5.3.3", "ultimate-crosscode-typedefs": "github:krypciak/ultimate-crosscode-typedefs" } @@ -1516,9 +1517,10 @@ "dev": true }, "node_modules/semver": { - "version": "7.5.4", + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", "dev": true, - "license": "ISC", "dependencies": { "lru-cache": "^6.0.0" }, diff --git a/package.json b/package.json index c16ea5c..ee3258e 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "nax-ccuilib": "github:conorlawton/nax-ccuilib", "prettier": "3.2.4", "typescript": "^5.3.3", - "ultimate-crosscode-typedefs": "github:krypciak/ultimate-crosscode-typedefs" + "ultimate-crosscode-typedefs": "github:krypciak/ultimate-crosscode-typedefs", + "semver": "^7.6.0" } } diff --git a/src/gui/list-entry.ts b/src/gui/list-entry.ts index 1b1c9ba..9ea99fd 100644 --- a/src/gui/list-entry.ts +++ b/src/gui/list-entry.ts @@ -2,7 +2,7 @@ import { ModEntry } from '../types' import { FileCache } from '../cache' import { InstallQueue } from '../install-queue' import './list-entry-highlight' -import { InstalledMods } from '../installed-mod-manager' +import { InstalledMods } from '../local-mods' import { MOD_MENU_TAB_INDEXES } from './list' declare global { @@ -23,7 +23,7 @@ declare global { modEntryActionButtons: sc.ButtonGui.Type & { ninepatch: ig.NinePatch } iconGui: ig.ImageGui - stripModName(this: this, name: string): string + getModName(this: this): string onButtonPress(this: this): void setTextGreen(this: this): void setTextRed(this: this): void @@ -133,17 +133,25 @@ sc.ModListEntry = ig.FocusGui.extend({ this.addChildGui(this.starCount) } }, - stripModName(name) { - return name.replace(/\\c\[\d]/g, '') + getModName() { + let name = this.mod.name.replace(/\\c\[\d]/g, '') + let icon: string + if (this.mod.database == 'LOCAL') { + icon = 'lore-others' + } else { + icon = 'quest' + } + name = `\\i[${icon}]${name}` + return name }, setTextGreen() { - this.nameText.setText(`\\c[2]${this.stripModName(this.mod.name)}\\c[0]`) + this.nameText.setText(`\\c[2]${this.getModName()}\\c[0]`) }, setTextRed() { - this.nameText.setText(`\\c[1]${this.stripModName(this.mod.name)}\\c[0]`) + this.nameText.setText(`\\c[1]${this.getModName()}\\c[0]`) }, setTextWhite() { - this.nameText.setText(this.stripModName(this.mod.name)) + this.nameText.setText(this.getModName()) }, updateDrawables(root) { if (this.modList.hook.currentStateName != 'HIDDEN') { diff --git a/src/gui/list.ts b/src/gui/list.ts index 83814c7..34fe601 100644 --- a/src/gui/list.ts +++ b/src/gui/list.ts @@ -1,9 +1,9 @@ import { ModEntry } from '../types' -import { databases } from '../moddb' +import { ModDB } from '../moddb' import { Fliters, createFuzzyFilteredModList } from '../filters' import { InstallQueue } from '../install-queue' import './list-entry' -import { InstalledMods } from '../installed-mod-manager' +import { InstalledMods } from '../local-mods' declare global { namespace sc { @@ -77,11 +77,11 @@ sc.ModMenuList = sc.ListTabbedPane.extend({ this.addTab(this.tabz[i].name, i, {}) } - for (const dbName in databases) { - const db = databases[dbName] + for (const dbName in ModDB.databases) { + const db = ModDB.databases[dbName] if (db.active) { this.mods[dbName] = [] - db.getMods(dbName, mods => this.setMods(mods, dbName)) + db.getMods(mods => this.setMods(mods, dbName)) } } }, diff --git a/src/installed-mod-manager.ts b/src/local-mods.ts similarity index 88% rename from src/installed-mod-manager.ts rename to src/local-mods.ts index 93de47f..369a46e 100644 --- a/src/installed-mod-manager.ts +++ b/src/local-mods.ts @@ -2,6 +2,7 @@ import { Mod } from 'ultimate-crosscode-typedefs/modloader/mod' import { FileCache } from './cache' import ModManager from './plugin' import { ModEntryLocal } from './types' +import { ModDB } from './moddb' type CCL2Mod = { baseDirectory: string @@ -26,11 +27,16 @@ export class InstalledMods { static getAll() { if (this.cache) return this.cache + let all: ModEntryLocal[] if (ModManager.mod.isCCL3) { - return [...modloader.installedMods].map(e => this.convertCCL3Mod(e[1])) + all = [...modloader.installedMods].map(e => this.convertCCL3Mod(e[1])) } else { - return (this.cache = [...window.activeMods.map(this.convertCCL2Mod), ...window.inactiveMods.map(this.convertCCL2Mod)]) + all = this.cache = [...window.activeMods.map(this.convertCCL2Mod), ...window.inactiveMods.map(this.convertCCL2Mod)] } + for (const mod of all) { + ModDB.resolveLocalModOrigin(mod) + } + return all } static getActive(): ModEntryLocal[] { diff --git a/src/moddb.ts b/src/moddb.ts index ef8ea38..27be636 100644 --- a/src/moddb.ts +++ b/src/moddb.ts @@ -1,12 +1,40 @@ -const fs: typeof import('fs') = (0, eval)("require('fs')") -const path: typeof import('path') = (0, eval)("require('path')") +// const fs: typeof import('fs') = (0, eval)("require('fs')") +// const path: typeof import('path') = (0, eval)("require('path')") -import jszip from 'jszip' -import { ModEntryServer, ModID, NPDatabase } from './types' +// import jszip from 'jszip' +import semver from 'semver' +import { ModEntryLocal, ModEntryServer, ModID, NPDatabase } from './types' import { FileCache } from './cache' export class ModDB { + static databases: Record = { + krypek: new ModDB('krypek', 'https://raw.githubusercontent.com/krypciak/CCModDB/ccmodjson'), + } + + static async resolveLocalModOrigin(mod: ModEntryLocal) { + const matches: ModEntryServer[] = [] + for (const dbName in this.databases) { + const moddb = this.databases[dbName] + let modRecord = moddb.modRecord + if (!modRecord) { + await moddb.getMods(() => {}) + modRecord = moddb.modRecord + } + if (!modRecord) throw new Error('wat?') + + const dbMod = modRecord[mod.id] + if (dbMod) matches.push(dbMod) + } + if (matches.length == 0) return + if (matches.length > 1) { + // TODO match acual mod version not the highest + matches[0] = matches.reduce((highestVerMod, currMod) => (semver.gt(currMod.version, highestVerMod.version) ? currMod : highestVerMod)) + } + mod.database = matches[0].database + } + database!: NPDatabase + modRecord!: Record constructor( public name: string, @@ -16,13 +44,13 @@ export class ModDB { FileCache.addDatabase(name, url) } - private createModEntriesFromDatabase(databaseName: string): ModEntryServer[] { - const result: ModEntryServer[] = [] + private createModEntriesFromDatabase(databaseName: string) { + this.modRecord = {} for (const [name, data] of Object.entries(this.database)) { if (typeof data === 'string') continue const meta = data.metadata const ccmod = data.metadataCCMod - result.push({ + this.modRecord[name] = { database: databaseName, isLocal: false, id: name, @@ -32,20 +60,20 @@ export class ModDB { isLegacy: !ccmod, hasIcon: ccmod?.icons ? !!ccmod.icons['24'] : false, stars: data.stars, - }) + } } - return result } - async getMods(databaseName: string, callback: (mods: ModEntryServer[]) => void): Promise { + async getMods(callback: (mods: ModEntryServer[]) => void): Promise { const create = (database: NPDatabase) => { this.database = database - const result = this.createModEntriesFromDatabase(databaseName) - callback(result) + this.createModEntriesFromDatabase(this.name) + callback(Object.values(this.modRecord)) } - await FileCache.getDatabase(databaseName, create) + await FileCache.getDatabase(this.name, create) } + /* async downloadMod(id: ModID) { const pkg = await this.getMod(id) @@ -84,7 +112,6 @@ export class ModDB { try { await fs.promises.mkdir(path.dirname(filepath), { recursive: true }) } catch { - /* Directory already exists */ } await fs.promises.writeFile(filepath, data) }) @@ -99,8 +126,5 @@ export class ModDB { if (!newData) throw new Error('Could not find name') return newData } -} - -export const databases: Record = { - krypek: new ModDB('krypek', 'https://raw.githubusercontent.com/krypciak/CCModDB/ccmodjson'), + */ } diff --git a/src/types.d.ts b/src/types.d.ts index 81ac535..8170c87 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -99,6 +99,7 @@ export type NPDatabasePackageInstallation = { ) interface ModEntryBase { + database: string id: ModID name: string description?: string @@ -108,11 +109,9 @@ interface ModEntryBase { stars?: number } export interface ModEntryServer extends ModEntryBase { - database: string isLocal: false } export interface ModEntryLocal extends ModEntryBase { - database: 'LOCAL' isLocal: true active: boolean iconConfig: ModImageConfig