Android build v1.1.15
This commit is contained in:
+101
-1
@@ -1,4 +1,4 @@
|
||||
import { createReadStream, existsSync, mkdirSync, statSync, writeFileSync } from 'node:fs'
|
||||
import { createReadStream, existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs'
|
||||
import { createServer } from 'node:http'
|
||||
import { randomBytes } from 'node:crypto'
|
||||
import { extname, resolve, sep } from 'node:path'
|
||||
@@ -10,6 +10,7 @@ const port = Number(process.env.ADMIN_PORT ?? 4174)
|
||||
const databasePath = fileURLToPath(new URL('../data/game.db', import.meta.url))
|
||||
const distPath = fileURLToPath(new URL('../dist', import.meta.url))
|
||||
const adminOverridesPath = fileURLToPath(new URL('../db/admin-overrides.sql', import.meta.url))
|
||||
const iwt2BalanceOverridesPath = fileURLToPath(new URL('../src/modes/iwt2/content/balanceOverrides.ts', import.meta.url))
|
||||
const bossImageDirectory = fileURLToPath(new URL('../data/uploads/bosses/', import.meta.url))
|
||||
const itemImageDirectory = fileURLToPath(new URL('../data/uploads/items/', import.meta.url))
|
||||
const dungeonImageDirectory = fileURLToPath(new URL('../data/uploads/dungeons/', import.meta.url))
|
||||
@@ -248,6 +249,89 @@ function saveDungeonImage(database, dungeonId, payload) {
|
||||
return imageUrl
|
||||
}
|
||||
|
||||
function readIwt2BalanceOverrides() {
|
||||
if (!existsSync(iwt2BalanceOverridesPath)) return { bosses: {} }
|
||||
const source = readFileSync(iwt2BalanceOverridesPath, 'utf8')
|
||||
const exportIndex = source.indexOf('IWT2_BALANCE_OVERRIDES')
|
||||
const objectStart = source.indexOf('{', exportIndex)
|
||||
const objectEnd = source.lastIndexOf('}')
|
||||
if (exportIndex < 0 || objectStart < 0 || objectEnd <= objectStart) return { bosses: {} }
|
||||
try {
|
||||
const parsed = JSON.parse(source.slice(objectStart, objectEnd + 1))
|
||||
return sanitizeIwt2BalanceOverrides(parsed)
|
||||
} catch {
|
||||
return { bosses: {} }
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeIwt2BalanceOverrides(value) {
|
||||
const overrides = { bosses: {} }
|
||||
const bosses = value && typeof value === 'object' && value.bosses && typeof value.bosses === 'object'
|
||||
? value.bosses
|
||||
: {}
|
||||
for (const bossId of ['bulldrome', 'yian-kut-ku']) {
|
||||
const boss = bosses[bossId]
|
||||
if (!boss || typeof boss !== 'object') continue
|
||||
const nextBoss = {}
|
||||
if (Number.isFinite(Number(boss.maxHealth))) nextBoss.maxHealth = Math.max(1, Math.round(Number(boss.maxHealth)))
|
||||
if (bossId === 'yian-kut-ku' && Number.isFinite(Number(boss.birdHealth))) {
|
||||
nextBoss.birdHealth = Math.max(1, Math.round(Number(boss.birdHealth)))
|
||||
}
|
||||
if (Object.keys(nextBoss).length > 0) overrides.bosses[bossId] = nextBoss
|
||||
}
|
||||
return overrides
|
||||
}
|
||||
|
||||
function writeIwt2BalanceOverrides(overrides) {
|
||||
const source = [
|
||||
"import type { Iwt2BalanceOverrides } from './bosses'",
|
||||
'',
|
||||
'// Generated by local admin panel. Commit this file with intended IWT2 balance changes.',
|
||||
`export const IWT2_BALANCE_OVERRIDES: Iwt2BalanceOverrides = ${JSON.stringify(sanitizeIwt2BalanceOverrides(overrides), null, 2)}`,
|
||||
'',
|
||||
].join('\n')
|
||||
writeFileSync(iwt2BalanceOverridesPath, source, { mode: 0o644 })
|
||||
}
|
||||
|
||||
function updateIwt2EntityHp(entityId, payload) {
|
||||
const maxHealth = Number(payload.maxHealth)
|
||||
if (!Number.isFinite(maxHealth) || maxHealth <= 0) throw new Error('HP must be greater than zero.')
|
||||
const roundedHealth = Math.round(maxHealth)
|
||||
const overrides = readIwt2BalanceOverrides()
|
||||
const bosses = { ...(overrides.bosses ?? {}) }
|
||||
if (entityId === 'bulldrome' || entityId === 'yian-kut-ku') {
|
||||
bosses[entityId] = {
|
||||
...(bosses[entityId] ?? {}),
|
||||
maxHealth: roundedHealth,
|
||||
}
|
||||
} else if (entityId === 'yian-kut-ku:yian-bird' || entityId === 'yian-bird') {
|
||||
bosses['yian-kut-ku'] = {
|
||||
...(bosses['yian-kut-ku'] ?? {}),
|
||||
birdHealth: roundedHealth,
|
||||
}
|
||||
} else {
|
||||
throw new Error('IWT2 entity not found.')
|
||||
}
|
||||
const nextOverrides = sanitizeIwt2BalanceOverrides({ bosses })
|
||||
writeIwt2BalanceOverrides(nextOverrides)
|
||||
return nextOverrides
|
||||
}
|
||||
|
||||
function listIwt2BossAssets() {
|
||||
if (!existsSync(bossImageDirectory)) return []
|
||||
return readdirSync(bossImageDirectory, { withFileTypes: true })
|
||||
.filter((entry) => entry.isFile() && bossImageContentTypes[extname(entry.name).toLowerCase()])
|
||||
.map((entry) => {
|
||||
const imagePath = resolve(bossImageDirectory, entry.name)
|
||||
return {
|
||||
filename: entry.name,
|
||||
size: statSync(imagePath).size,
|
||||
url: `/api/boss-images/${entry.name}`,
|
||||
}
|
||||
})
|
||||
.sort((a, b) => a.filename.localeCompare(b.filename))
|
||||
}
|
||||
|
||||
function sendFile(response, filePath) {
|
||||
const contentTypes = {
|
||||
'.css': 'text/css; charset=utf-8',
|
||||
@@ -308,6 +392,22 @@ const server = createServer(async (request, response) => {
|
||||
return
|
||||
}
|
||||
|
||||
if (request.url === '/api/admin/iwt2/data' && request.method === 'GET') {
|
||||
sendJson(response, 200, {
|
||||
balanceOverrides: readIwt2BalanceOverrides(),
|
||||
bossAssets: listIwt2BossAssets(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const iwt2HpMatch = request.url.match(/^\/api\/admin\/iwt2\/entities\/([^/]+)\/hp$/)
|
||||
if (iwt2HpMatch && request.method === 'PUT') {
|
||||
const payload = await readJson(request)
|
||||
const balanceOverrides = updateIwt2EntityHp(decodeURIComponent(iwt2HpMatch[1]), payload)
|
||||
sendJson(response, 200, { ok: true, balanceOverrides })
|
||||
return
|
||||
}
|
||||
|
||||
if (!existsSync(databasePath)) {
|
||||
sendJson(response, 503, { error: 'Database missing. Run npm run db:init.' })
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user