diablo2-web/tests/wilderness-acts.test.ts

456 lines
16 KiB
TypeScript

import { describe, expect, test } from 'vitest'
import {
generateWilderness,
getPerimeterOpenings,
UNIMPLEMENTED_PASSES,
} from '../src/game/wilderness.ts'
import type { WildernessPiece } from '../src/game/wilderness.ts'
import type {
Act2TopographyStats,
Act3TopographyStats,
Act4TopographyStats,
Act5TopographyStats,
} from '../src/game/wilderness-acts.ts'
import type { Ds1, Ds1Cell } from '../src/formats/ds1.ts'
function makeMockDs1(
width: number,
height: number,
hasGapInWalls: boolean = false,
floorStyle: number = 1,
wallStyle: number = 1,
): Ds1 {
const cells: Ds1Cell[][] = []
for (let y = 0; y < height; y += 1) {
const row: Ds1Cell[] = []
for (let x = 0; x < width; x += 1) {
const isGap = hasGapInWalls && x === Math.floor(width / 2)
row.push({
walls: isGap ? [] : [{ prop1: 2, sequence: 0, style: wallStyle, type: 0, unknown1: 0, unknown2: 0, hidden: false }],
floors: [{ prop1: 2, sequence: 0, style: floorStyle, unknown1: 0, unknown2: 0, hidden: false }],
shadows: [],
substitutions: [],
})
}
cells.push(row)
}
return {
version: 18,
width,
height,
act: 1,
substitutionType: 0,
wallLayers: 1,
floorLayers: 1,
cells,
objects: [],
npcPathOffset: null,
}
}
function makeMockBorderPieces(actPrefix: string): WildernessPiece[] {
const borderDs1 = makeMockDs1(8, 8, false)
const openBorderDs1 = makeMockDs1(8, 8, true)
return [
{ name: `${actPrefix} - Border 1`, border: true, levels: [borderDs1, borderDs1, borderDs1, openBorderDs1] },
{ name: `${actPrefix} - Border 2`, border: true, levels: [borderDs1] },
{ name: `${actPrefix} - Border 3`, border: true, levels: [borderDs1] },
{ name: `${actPrefix} - Border 4`, border: true, levels: [borderDs1, borderDs1, borderDs1, openBorderDs1] },
{ name: `${actPrefix} - Border 5`, border: true, levels: [borderDs1] },
{ name: `${actPrefix} - Border 6`, border: true, levels: [borderDs1] },
{ name: `${actPrefix} - Border 7`, border: true, levels: [borderDs1] },
{ name: `${actPrefix} - Border 8`, border: true, levels: [borderDs1] },
]
}
describe('Wilderness Outdoor Initializers for Acts 2-5 (Issue #51)', () => {
test('UNIMPLEMENTED_PASSES has 5 outdoor passes removed', () => {
expect(UNIMPLEMENTED_PASSES).not.toContain('DRLGOUTDESR_InitAct2OutdoorLevel')
expect(UNIMPLEMENTED_PASSES).not.toContain('DRLGOUTPLACE_InitAct3OutdoorLevel')
expect(UNIMPLEMENTED_PASSES).not.toContain('DRLGOUTDOORS_InitAct4OutdoorLevel')
expect(UNIMPLEMENTED_PASSES).not.toContain('DRLGOUTSIEGE_InitAct5OutdoorLevel')
expect(UNIMPLEMENTED_PASSES).not.toContain('DRLG_GenerateJungles')
expect(UNIMPLEMENTED_PASSES).toEqual([
'DRLGVER_CreateVertices',
'DRLGOUTPLACE_CreateLevelConnections',
'DRLGOUTDOORS_SpawnAct3Mephisto',
])
})
describe('Act 2: DRLGOUTDESR_InitAct2OutdoorLevel', () => {
test('Rocky Waste (41) generates sand caravan trails and canyon ridge walls', () => {
const pieces = makeMockBorderPieces('Act 2')
const result = generateWilderness({
levelId: 41,
levelName: 'Rocky Waste',
levelTypeName: 'Act 2 - Desert',
sizeX: 80,
sizeY: 80,
subType: 0,
subTheme: 0,
seed: 41001,
pieces,
substitutions: [],
})
expect(result.stats.act2Topography).toBeDefined()
const topo = result.stats.act2Topography as Act2TopographyStats
expect(topo.sandTrailCells).toBeGreaterThan(50)
expect(topo.canyonWalls).toBeGreaterThan(20)
let foundSandTrail = false
let foundCanyonWall = false
for (let y = 0; y < result.level.height; y += 1) {
for (let x = 0; x < result.level.width; x += 1) {
const cell = result.level.cells[y]![x]!
if (cell.floors.some(f => f.style === 5 && f.prop1 === 194)) foundSandTrail = true
if (cell.walls.some(w => w.style === 1 && w.prop1 === 129)) foundCanyonWall = true
}
}
expect(foundSandTrail).toBe(true)
expect(foundCanyonWall).toBe(true)
})
test('Far Oasis (43) generates lush water oasis pools', () => {
const pieces = makeMockBorderPieces('Act 2')
const result = generateWilderness({
levelId: 43,
levelName: 'Far Oasis',
levelTypeName: 'Act 2 - Desert',
sizeX: 80,
sizeY: 80,
subType: 0,
subTheme: 0,
seed: 43001,
pieces,
substitutions: [],
})
expect(result.stats.act2Topography).toBeDefined()
const topo = result.stats.act2Topography as Act2TopographyStats
expect(topo.oasisPoolCells).toBeGreaterThan(15)
let foundOasisWater = false
for (let y = 0; y < result.level.height; y += 1) {
for (let x = 0; x < result.level.width; x += 1) {
const cell = result.level.cells[y]![x]!
if (cell.floors.some(f => f.style === 2 && f.prop1 === 2)) foundOasisWater = true
}
}
expect(foundOasisWater).toBe(true)
})
test('Valley of Snakes (45) creates narrow canyon gorge', () => {
const pieces = makeMockBorderPieces('Act 2')
const result = generateWilderness({
levelId: 45,
levelName: 'Valley of Snakes',
levelTypeName: 'Act 2 - Desert',
sizeX: 80,
sizeY: 80,
subType: 0,
subTheme: 0,
seed: 45001,
pieces,
substitutions: [],
})
expect(result.stats.act2Topography).toBeDefined()
const topo = result.stats.act2Topography as Act2TopographyStats
expect(topo.canyonWalls).toBeGreaterThan(30)
})
test('Canyon of the Magi (46) places 7 Tal Rasha tombs, central dais, and has 0 perimeter openings', () => {
const pieces = makeMockBorderPieces('Act 2')
expect(getPerimeterOpenings(46, 10, 10).size).toBe(0)
const result = generateWilderness({
levelId: 46,
levelName: 'Canyon of the Magi',
levelTypeName: 'Act 2 - Desert',
sizeX: 80,
sizeY: 80,
subType: 0,
subTheme: 0,
seed: 46001,
pieces,
substitutions: [],
})
expect(result.stats.act2Topography).toBeDefined()
const topo = result.stats.act2Topography as Act2TopographyStats
expect(topo.tombEntrances).toBe(7)
// Verify central Arcane Sanctuary warp platform (portal/dais)
let foundCentralDais = false
for (let y = 35; y <= 45; y += 1) {
for (let x = 35; x <= 45; x += 1) {
const cell = result.level.cells[y]![x]!
if (cell.floors.some(f => f.style === 3)) foundCentralDais = true
}
}
expect(foundCentralDais).toBe(true)
})
})
describe('Act 3: DRLGOUTPLACE_InitAct3OutdoorLevel & DRLG_GenerateJungles', () => {
test('Jungles (76 Spider Forest) generate river channel, wooden river bridges, and jungle paths', () => {
const tailDs1 = makeMockDs1(64, 32)
const headDs1 = makeMockDs1(64, 32)
const clearingDs1 = makeMockDs1(32, 32)
const riverDs1 = makeMockDs1(32, 32)
const pieces: WildernessPiece[] = [
{ name: 'Act 3 - Jungle Tail', border: false, levels: [tailDs1] },
{ name: 'Act 3 - Jungle Head', border: false, levels: [headDs1] },
{ name: 'Act 3 - Clearing Webby E', border: false, levels: [clearingDs1] },
{ name: 'Act 3 - Jungle NS W', border: false, levels: [riverDs1] },
]
const result = generateWilderness({
levelId: 76,
levelName: 'Spider Forest',
levelTypeName: 'Act 3 - Jungle',
sizeX: 64,
sizeY: 192,
subType: 0,
subTheme: 0,
seed: 76001,
pieces,
substitutions: [],
})
expect(result.stats.act3Topography).toBeDefined()
const topo = result.stats.act3Topography as Act3TopographyStats
expect(topo.riverBridges).toBeGreaterThanOrEqual(2)
expect(topo.junglePathCells).toBeGreaterThan(20)
// Verify wooden river bridge planks (style 4, prop1 2) spanning across East channel
let foundBridgePlank = false
for (let y = 70; y <= 90; y += 1) {
for (let x = 32; x < 60; x += 1) {
const cell = result.level.cells[y]![x]!
if (cell.floors.some(f => f.style === 4 && f.prop1 === 2)) foundBridgePlank = true
}
}
expect(foundBridgePlank).toBe(true)
})
test('Kurast Causeway (82) stamps causeway with central walkway, balustrades, and canal water', () => {
const groundDs1 = makeMockDs1(48, 16)
const pieces: WildernessPiece[] = [
{ name: 'Act 3 - Ground', border: false, levels: [groundDs1] },
]
const result = generateWilderness({
levelId: 82,
levelName: 'Kurast Causeway',
levelTypeName: 'Act 3 - Kurast',
sizeX: 48,
sizeY: 16,
subType: 0,
subTheme: 0,
seed: 82001,
pieces,
substitutions: [],
})
expect(result.stats.act3Topography).toBeDefined()
const topo = result.stats.act3Topography as Act3TopographyStats
expect(topo.causewayCells).toBeGreaterThan(50)
expect(topo.canalCells).toBeGreaterThan(100)
// Verify central walkway (style 5), stone balustrade walls (style 1), and canal water (style 2)
let foundWalkway = false
let foundBalustrade = false
let foundCanalWater = false
for (let y = 0; y < result.level.height; y += 1) {
for (let x = 0; x < result.level.width; x += 1) {
const cell = result.level.cells[y]![x]!
if (cell.floors.some(f => f.style === 5 && f.prop1 === 194)) foundWalkway = true
if (cell.walls.some(w => w.style === 1 && w.prop1 === 129)) foundBalustrade = true
if (cell.floors.some(f => f.style === 2 && f.prop1 === 2)) foundCanalWater = true
}
}
expect(foundWalkway).toBe(true)
expect(foundBalustrade).toBe(true)
expect(foundCanalWater).toBe(true)
})
test('Kurast Causeway (82) preserves preset when Act 3 - Bridge is provided', () => {
const bridgeDs1 = makeMockDs1(48, 16, false, 29, 31)
const pieces: WildernessPiece[] = [
{ name: 'Act 3 - Bridge', border: false, levels: [bridgeDs1] },
]
const result = generateWilderness({
levelId: 82,
levelName: 'Kurast Causeway',
levelTypeName: 'Act 3 - Kurast',
sizeX: 48,
sizeY: 16,
subType: 0,
subTheme: 0,
seed: 82001,
pieces,
substitutions: [],
})
let foundPresetFloor = false
let foundPresetWall = false
for (let y = 0; y < result.level.height; y += 1) {
for (let x = 0; x < result.level.width; x += 1) {
const cell = result.level.cells[y]![x]!
if (cell.floors.some(f => f.style === 29)) foundPresetFloor = true
if (cell.walls.some(w => w.style === 31)) foundPresetWall = true
}
}
expect(foundPresetFloor).toBe(true)
expect(foundPresetWall).toBe(true)
})
test('Kurast Bazaar (79) generates paved stone avenues, canals, and stone bridges', () => {
const pieces = makeMockBorderPieces('Act 3')
const result = generateWilderness({
levelId: 79,
levelName: 'Kurast Bazaar',
levelTypeName: 'Act 3 - Kurast',
sizeX: 80,
sizeY: 80,
subType: 0,
subTheme: 0,
seed: 79001,
pieces,
substitutions: [],
})
expect(result.stats.act3Topography).toBeDefined()
const topo = result.stats.act3Topography as Act3TopographyStats
expect(topo.causewayCells).toBeGreaterThan(50)
expect(topo.canalCells).toBeGreaterThan(30)
expect(topo.riverBridges).toBeGreaterThanOrEqual(1)
})
})
describe('Act 4: DRLGOUTDOORS_InitAct4OutdoorLevel', () => {
test('Outer Steppes (104) generates lava chasms, basalt lips, and rocky bridge causeways', () => {
const pieces = makeMockBorderPieces('Act 4')
const result = generateWilderness({
levelId: 104,
levelName: 'Outer Steppes',
levelTypeName: 'Act 4 - Mesa',
sizeX: 80,
sizeY: 80,
subType: 0,
subTheme: 0,
seed: 104001,
pieces,
substitutions: [],
})
expect(result.stats.act4Topography).toBeDefined()
const topo = result.stats.act4Topography as Act4TopographyStats
expect(topo.lavaChasmCells).toBeGreaterThan(100)
expect(topo.rockyBridges).toBeGreaterThan(5)
// Verify boiling molten lava (style 20, sequence 3, prop1 2) and rocky bridges (style 10, sequence 0, prop1 2)
let foundLava = false
let foundRockyBridge = false
for (let y = 0; y < result.level.height; y += 1) {
for (let x = 0; x < result.level.width; x += 1) {
const cell = result.level.cells[y]![x]!
if (cell.floors.some(f => f.style === 20 && f.sequence === 3 && f.prop1 === 2)) foundLava = true
if (cell.floors.some(f => f.style === 10 && f.sequence === 0 && f.prop1 === 2)) foundRockyBridge = true
}
}
expect(foundLava).toBe(true)
expect(foundRockyBridge).toBe(true)
})
test('Plains of Despair (105) creates Izual prison plateau', () => {
const pieces = makeMockBorderPieces('Act 4')
const result = generateWilderness({
levelId: 105,
levelName: 'Plains of Despair',
levelTypeName: 'Act 4 - Mesa',
sizeX: 80,
sizeY: 80,
subType: 0,
subTheme: 0,
seed: 105001,
pieces,
substitutions: [],
})
expect(result.stats.act4Topography).toBeDefined()
const topo = result.stats.act4Topography as Act4TopographyStats
expect(topo.plateauCells).toBeGreaterThan(20)
})
})
describe('Act 5: DRLGOUTSIEGE_InitAct5OutdoorLevel', () => {
test('Frigid Highlands (111) generates transverse siege barricades, choke-point gates, and trenches', () => {
const pieces = makeMockBorderPieces('Act 5')
const result = generateWilderness({
levelId: 111,
levelName: 'Frigid Highlands',
levelTypeName: 'Act 5 - Barricade',
sizeX: 80,
sizeY: 80,
subType: 0,
subTheme: 0,
seed: 111001,
pieces,
substitutions: [],
})
expect(result.stats.act5Topography).toBeDefined()
const topo = result.stats.act5Topography as Act5TopographyStats
expect(topo.barricadeWalls).toBeGreaterThan(40)
expect(topo.trenchCells).toBeGreaterThan(30)
// Verify spiked palisade walls (style 2, prop1 129) and trench floors (style 5, prop1 194)
let foundBarricadeWall = false
let foundTrench = false
for (let y = 0; y < result.level.height; y += 1) {
for (let x = 0; x < result.level.width; x += 1) {
const cell = result.level.cells[y]![x]!
if (cell.walls.some(w => w.style === 2 && w.prop1 === 129)) foundBarricadeWall = true
if (cell.floors.some(f => f.style === 5 && f.prop1 === 194)) foundTrench = true
}
}
expect(foundBarricadeWall).toBe(true)
expect(foundTrench).toBe(true)
})
test('Arreat Plateau (112) generates snow crater frozen lakes with ice floors', () => {
const pieces = makeMockBorderPieces('Act 5')
const result = generateWilderness({
levelId: 112,
levelName: 'Arreat Plateau',
levelTypeName: 'Act 5 - Barricade',
sizeX: 80,
sizeY: 80,
subType: 0,
subTheme: 0,
seed: 112001,
pieces,
substitutions: [],
})
expect(result.stats.act5Topography).toBeDefined()
const topo = result.stats.act5Topography as Act5TopographyStats
expect(topo.frozenLakeCells).toBeGreaterThan(20)
let foundIceFloor = false
for (let y = 0; y < result.level.height; y += 1) {
for (let x = 0; x < result.level.width; x += 1) {
const cell = result.level.cells[y]![x]!
if (cell.floors.some(f => f.style === 2 && f.sequence === 0 && f.prop1 === 2)) foundIceFloor = true
}
}
expect(foundIceFloor).toBe(true)
})
})
})