refactor(acts): unify ACT_TOWNS and townLevelForAct single source of truth
TAG=agy CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
This commit is contained in:
parent
27e0db38c8
commit
a3a3e44838
|
|
@ -30,7 +30,7 @@ import { join } from 'node:path'
|
|||
import { MpqArchive } from '../src/mpq/archive.ts'
|
||||
import { fileSource } from '../src/mpq/file-source.ts'
|
||||
import { MountedArchives } from '../src/mpq/mount.ts'
|
||||
import { cell, loadActTables, parseTable, resolveLevel, resolveLevelLibraries, tileMemberPath } from '../src/game/acts.ts'
|
||||
import { cell, loadActTables, parseTable, resolveLevel, resolveLevelLibraries, tileMemberPath, ACT_TOWNS } from '../src/game/acts.ts'
|
||||
import type { ActTables, LevelInfo, D2Table } from '../src/game/acts.ts'
|
||||
import { decodeDs1 } from '../src/formats/ds1.ts'
|
||||
import type { Ds1 } from '../src/formats/ds1.ts'
|
||||
|
|
@ -366,9 +366,6 @@ const monstersTable = {
|
|||
*/
|
||||
const ACT_LAYOUT_SEED = 0x5eed_2000
|
||||
|
||||
/** Where each act's town is, for working out which way is "up" in a dungeon. */
|
||||
const ACT_TOWNS: readonly number[] = [1, 40, 75, 103, 109]
|
||||
|
||||
const worldGraph: WorldGraph = assignGateSides(
|
||||
buildWorldGraph(parseLevelRows(tables.levels)),
|
||||
ACT_LAYOUT_SEED,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import { readFileSync } from 'node:fs'
|
|||
import { MountedArchives } from '../src/mpq/mount.ts'
|
||||
import { MpqArchive } from '../src/mpq/archive.ts'
|
||||
import { fileSource } from '../src/mpq/file-source.ts'
|
||||
import { loadActTables } from '../src/game/acts.ts'
|
||||
import { loadActTables, ACT_TOWNS } from '../src/game/acts.ts'
|
||||
import {
|
||||
parseLevelRows,
|
||||
buildWorldGraph,
|
||||
|
|
@ -56,8 +56,6 @@ const dir = process.argv[2] ?? 'samples/d2'
|
|||
const MOUNTS = ['d2data.mpq', 'd2exp.mpq', 'Patch_D2.mpq']
|
||||
/** The baked asset packs. */
|
||||
const PACK_INDEX = 'samples/d2-packs/index.json'
|
||||
/** The town of each act, keyed by the 0-based `Act` column. */
|
||||
const ACT_TOWNS: readonly number[] = [1, 40, 75, 103, 109]
|
||||
/** Waypoint ids run 0..38 with no gaps. */
|
||||
const WAYPOINT_COUNT = 39
|
||||
/** How many seeds to exercise the side solver with. */
|
||||
|
|
|
|||
|
|
@ -183,6 +183,28 @@ export interface LevelInfo {
|
|||
*/
|
||||
export type ActTown = LevelInfo
|
||||
|
||||
/**
|
||||
* Town level IDs for Acts 1..5 in order:
|
||||
* Act 1: Rogue Encampment (1)
|
||||
* Act 2: Lut Gholein (40)
|
||||
* Act 3: Kurast Docks (75)
|
||||
* Act 4: The Pandemonium Fortress (103)
|
||||
* Act 5: Harrogath (109)
|
||||
*/
|
||||
export const ACT_TOWNS: readonly number[] = [1, 40, 75, 103, 109]
|
||||
|
||||
/**
|
||||
* Returns the town level ID for an act (1..5).
|
||||
* Defaults to Act 5 Harrogath (109) for acts >= 5, or Act 1 Rogue Encampment (1) for acts <= 1.
|
||||
*/
|
||||
export function townLevelForAct(act: number): number {
|
||||
if (act >= 1 && act <= 5) {
|
||||
return ACT_TOWNS[act - 1]!
|
||||
}
|
||||
if (act <= 1) return ACT_TOWNS[0]!
|
||||
return ACT_TOWNS[4]!
|
||||
}
|
||||
|
||||
/** Turn a table-relative tile path into a member name. */
|
||||
export function tileMemberPath(relative: string): string {
|
||||
return `${TILES}${relative.replaceAll('/', '\\')}`
|
||||
|
|
|
|||
|
|
@ -213,27 +213,4 @@ export class TownPortalSlot {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The town for an act.
|
||||
*
|
||||
* Hard-coded because `Levels.txt` does not mark towns: the Rogue Encampment's
|
||||
* row looks like any other preset. The ids are stable across every version of
|
||||
* the game.
|
||||
*
|
||||
* @param act - the act, 1..5.
|
||||
* @returns the town's level id.
|
||||
*/
|
||||
export function townLevelForAct(act: number): number {
|
||||
switch (act) {
|
||||
case 1:
|
||||
return 1
|
||||
case 2:
|
||||
return 40
|
||||
case 3:
|
||||
return 75
|
||||
case 4:
|
||||
return 103
|
||||
default:
|
||||
return 109
|
||||
}
|
||||
}
|
||||
export { ACT_TOWNS, townLevelForAct } from './acts.ts'
|
||||
|
|
|
|||
|
|
@ -4941,16 +4941,7 @@ export function generateWilderness(request: WildernessRequest): WildernessResult
|
|||
let floorLayers = 1
|
||||
let substitutionType = 0
|
||||
let version = 0
|
||||
let inferredAct = 1
|
||||
if (request.levelId >= 40 && request.levelId < 75) inferredAct = 2
|
||||
else if (request.levelId >= 75 && request.levelId < 103) inferredAct = 3
|
||||
else if (request.levelId >= 103 && request.levelId < 109) inferredAct = 4
|
||||
else if (request.levelId >= 109) inferredAct = 5
|
||||
else if (/Act 2/i.test(request.levelTypeName)) inferredAct = 2
|
||||
else if (/Act 3/i.test(request.levelTypeName)) inferredAct = 3
|
||||
else if (/Act 4/i.test(request.levelTypeName)) inferredAct = 4
|
||||
else if (/Act 5/i.test(request.levelTypeName)) inferredAct = 5
|
||||
|
||||
const inferredAct = actOfLevel(request.levelId, request.levelTypeName)
|
||||
let act = inferredAct
|
||||
const allLevels: Ds1[] = []
|
||||
for (const piece of request.pieces) for (const level of piece.levels) allLevels.push(level)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import type { D2Table } from '../src/game/acts.ts'
|
||||
import { ACT_TOWNS, townLevelForAct } from '../src/game/acts.ts'
|
||||
import { actOfLevel } from '../src/game/wilderness.ts'
|
||||
import {
|
||||
parseLevelRows,
|
||||
buildWorldGraph,
|
||||
|
|
@ -444,3 +446,37 @@ describe('seamArrivalSpot', () => {
|
|||
expect(Math.abs(spot.y - 0)).toBeGreaterThan(SEAM_TRIGGER_SUBTILES)
|
||||
})
|
||||
})
|
||||
|
||||
describe('ACT_TOWNS and townLevelForAct single source of truth', () => {
|
||||
it('defines the correct canonical town level IDs for Acts 1..5', () => {
|
||||
expect(ACT_TOWNS).toEqual([1, 40, 75, 103, 109])
|
||||
})
|
||||
|
||||
it('resolves townLevelForAct for all 5 acts correctly', () => {
|
||||
expect(townLevelForAct(1)).toBe(1)
|
||||
expect(townLevelForAct(2)).toBe(40)
|
||||
expect(townLevelForAct(3)).toBe(75)
|
||||
expect(townLevelForAct(4)).toBe(103)
|
||||
expect(townLevelForAct(5)).toBe(109)
|
||||
})
|
||||
|
||||
it('handles boundary / out-of-range acts gracefully', () => {
|
||||
expect(townLevelForAct(0)).toBe(1)
|
||||
expect(townLevelForAct(-5)).toBe(1)
|
||||
expect(townLevelForAct(6)).toBe(109)
|
||||
expect(townLevelForAct(99)).toBe(109)
|
||||
})
|
||||
|
||||
it('actOfLevel correctly partitions level IDs matching ACT_TOWNS', () => {
|
||||
expect(actOfLevel(1)).toBe(1)
|
||||
expect(actOfLevel(39)).toBe(1)
|
||||
expect(actOfLevel(40)).toBe(2)
|
||||
expect(actOfLevel(74)).toBe(2)
|
||||
expect(actOfLevel(75)).toBe(3)
|
||||
expect(actOfLevel(102)).toBe(3)
|
||||
expect(actOfLevel(103)).toBe(4)
|
||||
expect(actOfLevel(108)).toBe(4)
|
||||
expect(actOfLevel(109)).toBe(5)
|
||||
expect(actOfLevel(136)).toBe(5)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in New Issue