diablo2-web/src/game/drlg/drlg-preset.ts

1307 lines
47 KiB
TypeScript

/**
* D2Common DRLG presets: 1:1 port of D2MOO source/D2Common/src/Drlg/DrlgPreset.cpp (commit 5596f5c,
* MIT License, Copyright (c) 2020-2025 The Phrozen Keep community), the parts used by Act I.
*
* DS1 layers are parsed into Int32Arrays that are cached per file path, exactly like
* gpLevelFilesList_6FDEA700: the grids of every map/room using a file are views into the SAME
* arrays, so the in-place edits D2Common makes (DRLGPRESET_InitPresetRoomGrids ORs edge flags into
* the file layers) are shared the same way.
*
* D2_ALLOC_POOL memory is zero-filled, as in the native oracle (FOG_AllocPool -> calloc).
*/
import { SEED_RollLimitedRandomNumber, SEED_RollRandomNumber, type D2SeedStrc } from '../d2-rng.ts'
import { DRLGANIM_AllocAnimationTileGrids, DRLGANIM_TestLoadAnimatedRoomTiles } from './drlg-anim.ts'
import {
DATATBLS_GetLvlPrestTxtRecord,
DATATBLS_GetLvlPrestTxtRecordFromLevelId,
DATATBLS_GetMonPresetTxtActSection,
objectSubClass,
type DrlgTables,
} from './drlg-tables.ts'
import {
gObjPresetToObjectId,
gTileTypeMappingTable,
LVLPREST_ACT1_BRIDGE,
LVLPREST_ACT1_GRAVEYARD,
LVLPREST_ACT1_RIVER_LOWER,
LVLPREST_ACT1_RIVER_UPPER,
LVLPREST_ACT1_TOWN_1,
LVLPREST_ACT1_TOWN_1_TRANSITION_S,
LVLPREST_ACT1_TRISTRAM,
LVLPREST_ACT1_WILD_BORDER_1,
LVLPREST_ACT1_WILD_BORDER_4,
} from './drlg-ids.ts'
import {
ACT_I,
ACT_III,
ACT_V,
DRLGPRESETROOMFLAG_NONE,
DRLGPRESETROOMFLAG_SINGLE_ROOM,
DRLGROOMFLAG_AUTOMAP_REVEAL,
DRLGROOMFLAG_HAS_WARP_0,
DRLGROOMFLAG_POPULATION_ZERO,
DRLGROOMFLAG_PRESET_UNITS_ADDED,
DRLGSUBST_NONE,
DRLGSUBST_RANDOM,
DRLGTYPE_PRESET,
DRLG_MAX_FLOOR_LAYERS,
DRLG_MAX_WALL_LAYERS,
FLAG_OPERATION_OR,
IMODE_ONGROUND,
LEVEL_BLOODMOOR,
LEVEL_BURIALGROUNDS,
LEVEL_HARROGATH,
LEVEL_LUTGHOLEIN,
LEVEL_THEPANDEMONIUMFORTRESS,
MONMODE_NEUTRAL,
OBJMODE_NEUTRAL,
UNIT_ITEM,
UNIT_MONSTER,
UNIT_OBJECT,
newCoord,
newGrid,
newPresetRoom,
newPresetUnit,
type D2DrlgCoordStrc,
type D2DrlgFileStrc,
type D2DrlgGridStrc,
type D2DrlgLevelStrc,
type D2DrlgMapStrc,
type D2DrlgRoomStrc,
type D2MapAIPathPositionStrc,
type D2PresetUnitStrc,
type DrlgEnv,
} from './drlg-types.ts'
import { DRLG_SetLevelPositionAndSize, DUNGEON_GameTileToSubtileCoords } from './drlg-drlg.ts'
import {
DRLGGRID_AlterAllGridFlags,
DRLGGRID_AlterEdgeGridFlags,
DRLGGRID_AlterGridFlag,
DRLGGRID_AssignCellsOffsetsAndFlags,
DRLGGRID_FillGrid,
DRLGGRID_FillNewCellFlags,
DRLGGRID_GetGridEntry,
DRLGGRID_ResetGrid,
} from './drlg-grid.ts'
import {
DRLGROOM_AddRoomExToLevel,
DRLGROOM_AllocPresetUnit,
DRLGROOM_AllocRoomEx,
DRLGROOM_AreXYInsideCoordinates,
DRLGROOM_GetVisArrayFromLevelId,
} from './drlg-room.ts'
import { DRLGWARP_GetWarpDestinationFromArray } from './drlg-warp.ts'
// Used only inside DRLGPRESET_AddPresetRoomMapTiles (at run time), so these import cycles are harmless.
import { DRLGLOGIC_AllocCoordLists, DRLGLOGIC_InitializeDrlgCoordList } from './drlg-logic.ts'
import {
DRLGROOMTILE_AllocTileData,
DRLGROOMTILE_AllocTileGrid,
DRLGROOMTILE_CountAllTileTypes,
DRLGROOMTILE_CountWallWarpTiles,
DRLGROOMTILE_LoadInitRoomTiles,
PackedTile,
tileGridOf,
} from './drlg-roomtile.ts'
// D2C_UnitTypes and unit modes: defined in the leaf module drlg-types.ts (see the note there).
export {
IMODE_ONGROUND,
MONMODE_NEUTRAL,
OBJMODE_NEUTRAL,
UNIT_ITEM,
UNIT_MISSILE,
UNIT_MONSTER,
UNIT_OBJECT,
UNIT_PLAYER,
UNIT_TILE,
} from './drlg-types.ts'
// D2C_MonsterIds / D2C_SuperUniques (DataTbls/MonsterIds.h)
export const MONSTER_ACT2VENDOR1 = 204
export const MONSTER_ACT2VENDOR2 = 205
export const MONSTER_NAVI = 266
export const MONSTER_NATALYA = 297
export const MONSTER_COMPELLINGORB = 366
export const MONSTER_LIGHTNINGSPIRE = 371
export const MONSTER_FIRETOWER = 372
export const MONSTER_NIHLATHAK = 514
export const MONSTER_ANCIENTSTATUE1 = 537
export const MONSTER_ANCIENTSTATUE2 = 538
export const MONSTER_ANCIENTSTATUE3 = 539
export const SUPERUNIQUE_THE_TORMENTOR = 33
export const SUPERUNIQUE_TAINTBREEDER = 34
export const SUPERUNIQUE_RIFTWRAITH_THE_CANNIBAL = 35
// D2C_ObjectIds (DataTbls/ObjectsIds.h)
export const OBJECT_RIVER1 = 40
export const OBJECT_RIVER2 = 41
export const OBJECT_RIVER3 = 42
export const OBJECT_INVISIBLE_RIVER_SOUND1 = 65
export const OBJECT_FLOORTRAP = 196
export const OBJECT_TOMBFLOORTRAP = 261
export const OBJECT_NATALYA_START = 382
export const OBJECT_COMPELLING_ORB = 404
export const OBJECT_NIHLATHAK_START_IN_TOWN = 461
export const OBJECT_ANCIENTSTATUE3 = 474
export const OBJECT_ANCIENTSTATUE1 = 475
export const OBJECT_ANCIENTSTATUE2 = 476
export const OBJSUBCLASS_WAYPOINT = 0x40
// D2TileType (D2CMP.h)
export const TILETYPE_FLOOR = 0
export const TILETYPE_WALL_LEFT_EXIT = 10
export const TILETYPE_WALL_RIGHT_EXIT = 11
export const TILETYPE_SHADOW = 13
//D2Common.0x6FD859E0 (#11223)
export function DRLGPRESET_GetObjectIndexFromObjPreset(nAct: number, nUnitId: number): number {
if (nAct < 0 || nAct >= 5 || nUnitId < 0 || nUnitId >= 150) {
throw new RangeError(`DRLGPRESET_GetObjectIndexFromObjPreset(${nAct}, ${nUnitId}): outside dword_6FDE1180[5][150]`)
}
return gObjPresetToObjectId[nAct * 150 + nUnitId]!
}
//D2Common.0x6FD88850
export function DRLGPRESET_MapTileType(nId: number): number {
if (nId < 0 || nId >= gTileTypeMappingTable.length) throw new RangeError(`DRLGPRESET_MapTileType(${nId})`)
return gTileTypeMappingTable[nId]!
}
/** `int32_t* pData` walking over a DS1 image (unaligned little-endian reads, bounds-checked). */
class Ds1Cursor {
private readonly view: DataView
offset: number
constructor(private readonly bytes: Uint8Array, offset: number, private readonly path: string) {
this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
this.offset = offset
}
int32(): number {
if (this.offset + 4 > this.bytes.byteLength + 16) throw new RangeError(`${this.path}: read past the end of the DS1`)
const v = this.offset + 4 <= this.bytes.byteLength ? this.view.getInt32(this.offset, true) : 0
this.offset += 4
return v
}
skipInt32s(n: number): void {
if (n < 0 || this.offset + 4 * n > this.bytes.byteLength) throw new RangeError(`${this.path}: skip past the end of the DS1`)
this.offset += 4 * n
}
/** `pData = (int*)((char*)pData + strlen((char*)pData) + 1)` */
skipCString(): void {
let end = this.offset
while (end < this.bytes.byteLength && this.bytes[end] !== 0) end += 1
if (end >= this.bytes.byteLength) throw new RangeError(`${this.path}: unterminated string`)
this.offset = end + 1
}
/** `layer = pData; SkipInt32s(pData, nArea)`, as an owned Int32Array (the cached file layer). */
layer(nArea: number): Int32Array {
const out = new Int32Array(nArea)
for (let i = 0; i < nArea; i += 1) out[i] = this.int32()
return out
}
}
//D2Common.0x6FD85A10
export function DRLGPRESET_ParseDS1File(env: DrlgEnv, pDrlgFile: D2DrlgFileStrc, szFileName: string): void {
const t = env.tables
const bytes = env.data.readFile(szFileName)
if (bytes.byteLength < 12) throw new RangeError(`${szFileName}: truncated DS1 header`)
const header = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
const nVersion = header.getInt32(0, true)
pDrlgFile.nWidth = header.getInt32(4, true)
pDrlgFile.nHeight = header.getInt32(8, true)
const pData = new Ds1Cursor(bytes, 12, szFileName)
let nAct = ACT_I
if (nVersion >= 8) {
nAct = pData.int32()
if (nAct > ACT_V) nAct = ACT_V
}
pDrlgFile.nSubstMethod = 0
if (nVersion >= 10) pDrlgFile.nSubstMethod = pData.int32()
if (nVersion >= 3) {
const nStrings = pData.int32()
for (let i = 0; i < nStrings; i += 1) pData.skipCString()
}
const nArea = (pDrlgFile.nWidth + 1) * (pDrlgFile.nHeight + 1)
if (nArea <= 0) throw new RangeError(`${szFileName}: invalid size ${pDrlgFile.nWidth}x${pDrlgFile.nHeight}`)
if (nVersion >= 9 && nVersion < 14) pData.skipInt32s(2)
if (nVersion < 4) {
pDrlgFile.pWallLayer[0] = pData.layer(nArea)
pDrlgFile.nWallLayers = 1
pDrlgFile.pFloorLayer[0] = pData.layer(nArea)
pDrlgFile.pTileTypeLayer[0] = pData.layer(nArea)
pDrlgFile.pSubstGroupTags = pData.layer(nArea)
} else {
pDrlgFile.nWallLayers = pData.int32()
if (nVersion < 16) {
pDrlgFile.nFloorLayers = 1
} else {
pDrlgFile.nFloorLayers = pData.int32()
}
if (pDrlgFile.nWallLayers < 0 || pDrlgFile.nWallLayers > DRLG_MAX_WALL_LAYERS) {
throw new RangeError(`${szFileName}: ${pDrlgFile.nWallLayers} wall layers overflow pWallLayer[${DRLG_MAX_WALL_LAYERS}]`)
}
if (pDrlgFile.nFloorLayers < 0 || pDrlgFile.nFloorLayers > DRLG_MAX_FLOOR_LAYERS) {
throw new RangeError(`${szFileName}: ${pDrlgFile.nFloorLayers} floor layers overflow pFloorLayer[${DRLG_MAX_FLOOR_LAYERS}]`)
}
for (let i = 0; i < pDrlgFile.nWallLayers; i += 1) {
pDrlgFile.pWallLayer[i] = pData.layer(nArea)
pDrlgFile.pTileTypeLayer[i] = pData.layer(nArea)
}
for (let i = 0; i < pDrlgFile.nFloorLayers; i += 1) {
pDrlgFile.pFloorLayer[i] = pData.layer(nArea)
}
}
if (nVersion < 7) {
for (let j = 0; j < pDrlgFile.nWallLayers; j += 1) {
const pTileTypeLayer = pDrlgFile.pTileTypeLayer[j]!
for (let i = 0; i < nArea; i += 1) pTileTypeLayer[i] = DRLGPRESET_MapTileType(pTileTypeLayer[i]!)
}
}
pDrlgFile.pShadowLayer = pData.layer(nArea)
if (pDrlgFile.nSubstMethod > DRLGSUBST_NONE && pDrlgFile.nSubstMethod <= DRLGSUBST_RANDOM) {
pDrlgFile.pSubstGroupTags = pData.layer(nArea)
}
if (nVersion > 1) {
const nUnits = pData.int32()
for (let i = 0; i < nUnits; i += 1) {
let nUnitType = pData.int32()
let nUnitId = pData.int32()
let nMode = 0
switch (nUnitType) {
case UNIT_MONSTER:
nMode = MONMODE_NEUTRAL
if (nVersion > 4) {
const pSection = DATATBLS_GetMonPresetTxtActSection(t, nAct)
// D2_VERIFY(pMonPresetTxtSection && nUnitId < nMonPresetRecordsCount): the oracle fails on it.
if (!(pSection && nUnitId < pSection.count)) {
throw new Error(`${szFileName}: D2_VERIFY failed: MonPreset act ${nAct} has no record ${nUnitId}`)
}
if (nUnitId < 0) throw new RangeError(`${szFileName}: negative MonPreset index ${nUnitId}`)
const monPresetRecord = t.monPreset[pSection.start + nUnitId]!
nUnitId = monPresetRecord.wPlace
switch (monPresetRecord.nType) {
case 0:
nUnitId += t.nSuperUniquesTxtRecordCount
nUnitId += t.nMonStatsTxtRecordCount
break
case 1:
break
case 2:
nUnitId += t.nMonStatsTxtRecordCount
break
default:
nUnitId = -1
break
}
switch (nAct) {
case ACT_III:
switch (nUnitId) {
case MONSTER_NATALYA:
nUnitId = OBJECT_NATALYA_START
nUnitType = UNIT_OBJECT
nMode = OBJMODE_NEUTRAL
break
case MONSTER_COMPELLINGORB:
nUnitId = OBJECT_COMPELLING_ORB
nUnitType = UNIT_OBJECT
nMode = OBJMODE_NEUTRAL
break
default:
break
}
break
case ACT_V:
switch (nUnitId) {
case MONSTER_NIHLATHAK:
nUnitId = OBJECT_NIHLATHAK_START_IN_TOWN
nUnitType = UNIT_OBJECT
nMode = OBJMODE_NEUTRAL
break
case MONSTER_ANCIENTSTATUE1:
nUnitId = OBJECT_ANCIENTSTATUE2
nUnitType = UNIT_OBJECT
nMode = OBJMODE_NEUTRAL
break
case MONSTER_ANCIENTSTATUE2:
nUnitId = OBJECT_ANCIENTSTATUE1
nUnitType = UNIT_OBJECT
nMode = OBJMODE_NEUTRAL
break
case MONSTER_ANCIENTSTATUE3:
nUnitId = OBJECT_ANCIENTSTATUE3
nUnitType = UNIT_OBJECT
nMode = OBJMODE_NEUTRAL
break
default:
break
}
break
default:
break
}
}
break
case UNIT_OBJECT:
if (nVersion > 5) {
if (nUnitId >= 150) {
nUnitId -= 150
} else {
nUnitId = DRLGPRESET_GetObjectIndexFromObjPreset(nAct, nUnitId)
}
} else if (nUnitId === 573) {
nUnitId = -1
}
break
case UNIT_ITEM:
if (nVersion > 4) {
// DATATBLS_GetItemRecordFromItemCode is outside the DRLG; the oracle fails on it too.
throw new Error(`${szFileName}: item preset unit in a DS1 (DATATBLS_GetItemRecordFromItemCode is not ported)`)
}
nMode = IMODE_ONGROUND
break
default:
break
}
const nX = pData.int32()
const nY = pData.int32()
let nSpawnFlag = 0
if (nVersion > 5) nSpawnFlag = pData.int32()
if (nUnitId >= 0 && (nUnitType !== UNIT_MONSTER || nVersion > 4)) {
const pNewPresetUnit = DRLGROOM_AllocPresetUnit(null, nUnitType, nUnitId, nMode, nX, nY)
pNewPresetUnit.pNext = pDrlgFile.pPresetUnit
pDrlgFile.pPresetUnit = pNewPresetUnit
pNewPresetUnit.bSpawned |= nSpawnFlag
}
}
}
if (nVersion >= 12 && pDrlgFile.nSubstMethod > DRLGSUBST_NONE && pDrlgFile.nSubstMethod <= DRLGSUBST_RANDOM) {
if (nVersion >= 18) pData.skipInt32s(1)
pDrlgFile.nSubstGroups = pData.int32()
if (pDrlgFile.nSubstGroups < 0) throw new RangeError(`${szFileName}: ${pDrlgFile.nSubstGroups} substitution groups`)
pDrlgFile.pSubstGroups = []
for (let i = 0; i < pDrlgFile.nSubstGroups; i += 1) {
const tBox = newCoord()
tBox.nPosX = pData.int32()
tBox.nPosY = pData.int32()
tBox.nWidth = pData.int32()
tBox.nHeight = pData.int32()
// field_10 is never read from the file; D2_ALLOC_POOL memory is zero-filled (oracle calloc).
let field_14 = 0
if (nVersion >= 13) field_14 = pData.int32()
pDrlgFile.pSubstGroups.push({ tBox, field_10: 0, field_14 })
}
}
if (nVersion >= 14) {
const v59 = pData.int32()
for (let i = 0; i < v59; i += 1) {
const nNodes = pData.int32()
const nX = pData.int32()
const nY = pData.int32()
if (nNodes) {
// Find unit with given position
let pPresetUnit = pDrlgFile.pPresetUnit
while (pPresetUnit) {
if (pPresetUnit.nXpos === nX && pPresetUnit.nYpos === nY) break
pPresetUnit = pPresetUnit.pNext
}
if (pPresetUnit) {
if (nNodes < 0) throw new RangeError(`${szFileName}: ${nNodes} path nodes`)
const pPosition: D2MapAIPathPositionStrc[] = []
for (let j = 0; j < nNodes; j += 1) {
const nPosX = pData.int32()
const nPosY = pData.int32()
const nMapAIAction = nVersion < 15 ? 1 : pData.int32()
pPosition.push({ nX: nPosX, nY: nPosY, nMapAIAction })
}
pPresetUnit.pMapAI = pPosition
} else {
// Unit not found, skip path
pData.skipInt32s(2 * nNodes)
if (nVersion >= 15) pData.skipInt32s(nNodes)
}
}
}
}
}
function newDrlgFile(szPath: string): D2DrlgFileStrc {
return {
nSubstMethod: 0,
szPath,
nWidth: 0,
nHeight: 0,
nWallLayers: 0,
nFloorLayers: 0,
pTileTypeLayer: [null, null, null, null],
pWallLayer: [null, null, null, null],
pFloorLayer: [null, null],
pShadowLayer: null,
pSubstGroupTags: null,
nSubstGroups: 0,
pSubstGroups: [],
pPresetUnit: null,
}
}
//D2Common.0x6FD86050
// Returns the cached or newly parsed file (C: *ppDrlgFile). The cache key is the exact path
// string (strcmp), as in gpLevelFilesList_6FDEA700.
export function DRLGPRESET_LoadDrlgFile(env: DrlgEnv, szFile: string): D2DrlgFileStrc {
const cached = env.fileCache.get(szFile)
if (cached) return cached
const pFile = newDrlgFile(szFile)
env.fileCache.set(szFile, pFile)
DRLGPRESET_ParseDS1File(env, pFile, szFile)
return pFile
}
//D2Common.0x6FD86310
export function DRLGPRESET_CopyPresetUnit(pPresetUnit: D2PresetUnitStrc, nX: number, nY: number): D2PresetUnitStrc {
const pNewPresetUnit = newPresetUnit()
pNewPresetUnit.nUnitType = pPresetUnit.nUnitType
pNewPresetUnit.nIndex = pPresetUnit.nIndex
pNewPresetUnit.nMode = pPresetUnit.nMode
pNewPresetUnit.nXpos = nX + pPresetUnit.nXpos
pNewPresetUnit.nYpos = nY + pPresetUnit.nYpos
pNewPresetUnit.bSpawned = pPresetUnit.bSpawned
if (pPresetUnit.pMapAI) {
pNewPresetUnit.pMapAI = pPresetUnit.pMapAI.map(p => ({ nX: p.nX + nX, nY: p.nY + nY, nMapAIAction: p.nMapAIAction }))
}
return pNewPresetUnit
}
//D2Common.0x6FD86540
export function DRLGPRESET_AddPresetUnitToDrlgMap(t: DrlgTables, pDrlgMap: D2DrlgMapStrc, pSeed: D2SeedStrc): void {
const sub = DUNGEON_GameTileToSubtileCoords(pDrlgMap.pDrlgCoord.nPosX, pDrlgMap.pDrlgCoord.nPosY)
const nX = sub.nX
const nY = sub.nY
for (let pPresetUnit = pDrlgMap.pFile!.pPresetUnit; pPresetUnit; pPresetUnit = pPresetUnit.pNext) {
if (pPresetUnit.nUnitType === UNIT_MONSTER) {
let nIndex = pPresetUnit.nIndex
if (nIndex < t.nMonStatsTxtRecordCount) {
if (nIndex < 0 || nIndex >= t.nMonStatsTxtRecordCount) nIndex = -1
switch (nIndex) {
case MONSTER_ACT2VENDOR1:
case MONSTER_ACT2VENDOR2:
case MONSTER_LIGHTNINGSPIRE:
case MONSTER_FIRETOWER:
if ((SEED_RollRandomNumber(pSeed) >>> 0) % 3) continue
break
default:
break
}
} else if (pPresetUnit.nIndex - t.nMonStatsTxtRecordCount >= t.nSuperUniquesTxtRecordCount) {
const nPlace = pPresetUnit.nIndex - t.nMonStatsTxtRecordCount - t.nSuperUniquesTxtRecordCount
if (nPlace === SUPERUNIQUE_THE_TORMENTOR) {
if (!(SEED_RollRandomNumber(pSeed) & 3)) continue
} else if (nPlace === SUPERUNIQUE_TAINTBREEDER) {
if (!(SEED_RollRandomNumber(pSeed) & 1)) continue
} else if (nPlace === SUPERUNIQUE_RIFTWRAITH_THE_CANNIBAL) {
if (SEED_RollRandomNumber(pSeed) & 3) continue
}
}
} else if (pPresetUnit.nUnitType === UNIT_OBJECT) {
if (pPresetUnit.nIndex === OBJECT_FLOORTRAP || pPresetUnit.nIndex === OBJECT_TOMBFLOORTRAP) {
if (SEED_RollRandomNumber(pSeed) & 1) continue
} else if (pPresetUnit.nIndex === 581) {
if (!(SEED_RollRandomNumber(pSeed) & 3)) continue
}
}
const pNewPresetUnit = DRLGPRESET_CopyPresetUnit(pPresetUnit, nX, nY)
pNewPresetUnit.pNext = pDrlgMap.pPresetUnit
pDrlgMap.pPresetUnit = pNewPresetUnit
}
}
//D2Common.0x6FD86D80
export function DRLGPRESET_AllocPresetRoomData(pDrlgRoom: D2DrlgRoomStrc): void {
pDrlgRoom.pMaze = newPresetRoom()
}
//D2Common.0x6FD86DC0
export function DRLGPRESET_InitPresetRoomData(
pLevel: D2DrlgLevelStrc,
pDrlgMap: D2DrlgMapStrc,
pDrlgCoord: D2DrlgCoordStrc,
dwDT1Mask: number,
dwRoomFlags: number,
dwPresetFlags: number,
a7: D2DrlgGridStrc | null,
): D2DrlgRoomStrc {
const pDrlgRoom = DRLGROOM_AllocRoomEx(pLevel, DRLGTYPE_PRESET)
pDrlgRoom.dwDT1Mask = dwDT1Mask >>> 0
pDrlgRoom.dwFlags = (pDrlgRoom.dwFlags | dwRoomFlags) >>> 0
pDrlgRoom.pDrlgCoord.nPosX = pDrlgCoord.nPosX
pDrlgRoom.pDrlgCoord.nPosY = pDrlgCoord.nPosY
pDrlgRoom.pDrlgCoord.nWidth = pDrlgCoord.nWidth
pDrlgRoom.pDrlgCoord.nHeight = pDrlgCoord.nHeight
const pDrlgPresetRoom = pDrlgRoom.pMaze!
pDrlgPresetRoom.pMap = pDrlgMap
pDrlgPresetRoom.dwFlags = dwPresetFlags >>> 0
pDrlgPresetRoom.nLevelPrest = pDrlgMap.pLvlPrestTxtRecord.dwDef
pDrlgPresetRoom.pMazeGrid = a7
if (a7) a7.nHeight |= 1
if (!pDrlgPresetRoom.pMap!.pLvlPrestTxtRecord.dwPopulate) {
pDrlgRoom.dwFlags = (pDrlgRoom.dwFlags | DRLGROOMFLAG_POPULATION_ZERO) >>> 0
}
DRLGROOM_AddRoomExToLevel(pLevel, pDrlgRoom)
return pDrlgRoom
}
//D2Common.0x6FD87560
export function DRLGPRESET_BuildArea(pLevel: D2DrlgLevelStrc, pDrlgMap: D2DrlgMapStrc, nFlags: number, bSingleRoom: boolean): D2DrlgRoomStrc | null {
if (pDrlgMap.pLvlPrestTxtRecord.dwOutdoors) nFlags = (nFlags | 0x80000) >>> 0
// int nCellPos[1024]; int nCellFlags[256];
const nCellPos = new Int32Array(1024)
const nCellFlags = new Int32Array(256)
const nGridWidth = Math.trunc(pDrlgMap.pDrlgCoord.nWidth / 8) + 1
const nGridHeight = Math.trunc(pDrlgMap.pDrlgCoord.nHeight / 8) + 1
if (nGridHeight > 256) throw new RangeError(`DRLGPRESET_BuildArea: ${nGridHeight} rows overflow nCellFlags[256]`)
const tDrlgGrid = newGrid()
DRLGGRID_FillGrid(tDrlgGrid, nGridWidth, nGridHeight, nCellPos, 0, nCellFlags)
DRLGPRESET_BuildPresetArea(pLevel, tDrlgGrid, nFlags, pDrlgMap, bSingleRoom)
let pDrlgRoom: D2DrlgRoomStrc | null = null
if (bSingleRoom) {
pDrlgRoom = DRLGPRESET_InitPresetRoomData(
pLevel,
pDrlgMap,
pDrlgMap.pDrlgCoord,
pDrlgMap.pLvlPrestTxtRecord.dwDt1Mask,
DRLGGRID_GetGridEntry(tDrlgGrid, 0, 0),
DRLGPRESETROOMFLAG_SINGLE_ROOM,
null,
)
} else {
const nXEnd = pDrlgMap.pDrlgCoord.nPosX + pDrlgMap.pDrlgCoord.nWidth
const nYEnd = pDrlgMap.pDrlgCoord.nPosY + pDrlgMap.pDrlgCoord.nHeight
const tDrlgCoord = newCoord()
let nGridY = 0
for (let nY = pDrlgMap.pDrlgCoord.nPosY; nY < nYEnd; nY += 8) {
const nDeltaToEndY = nYEnd - nY
tDrlgCoord.nPosY = nY
let nGridX = 0
for (let nX = pDrlgMap.pDrlgCoord.nPosX; nX < nXEnd; nX += 8) {
const nDeltaToEndX = nXEnd - nX
tDrlgCoord.nPosX = nX
tDrlgCoord.nWidth = nDeltaToEndX
if (nDeltaToEndX >= 8) tDrlgCoord.nWidth = 8
tDrlgCoord.nHeight = nDeltaToEndY
if (nDeltaToEndY >= 8) tDrlgCoord.nHeight = 8
const nGridFlags = DRLGGRID_GetGridEntry(tDrlgGrid, nGridX, nGridY)
if (tDrlgCoord.nWidth && tDrlgCoord.nHeight) {
// C passes `(D2DrlgGridStrc*)(pDrlgMap->bHasInfo ? nGridFlags : 0)`: an int reinterpreted as a
// pointer. Only maze/jungle maps carry bHasInfo; reaching it here would be a porting error.
if (pDrlgMap.bHasInfo) throw new Error('DRLGPRESET_BuildArea: bHasInfo maps are not ported')
pDrlgRoom = DRLGPRESET_InitPresetRoomData(
pLevel,
pDrlgMap,
tDrlgCoord,
pDrlgMap.pLvlPrestTxtRecord.dwDt1Mask,
nGridFlags,
DRLGPRESETROOMFLAG_NONE,
null,
)
}
nGridX += 1
}
nGridY += 1
}
}
DRLGGRID_ResetGrid(tDrlgGrid)
return pDrlgRoom
}
//D2Common.0x6FD87760
export function DRLGPRESET_BuildPresetArea(
pLevel: D2DrlgLevelStrc,
pDrlgGrid: D2DrlgGridStrc,
nFlags: number,
pDrlgMap: D2DrlgMapStrc,
bSingleRoom: boolean,
): void {
const env = pLevel.pDrlg.env
const pVisArray = DRLGROOM_GetVisArrayFromLevelId(pLevel.pDrlg, pLevel.nLevelId)
for (let i = 0; i < 8; i += 1) {
if (pVisArray[i] && DRLGWARP_GetWarpDestinationFromArray(pLevel, i) === -1) {
nFlags = (nFlags | (DRLGROOMFLAG_HAS_WARP_0 << i)) >>> 0
}
}
DRLGGRID_AlterAllGridFlags(pDrlgGrid, nFlags, FLAG_OPERATION_OR)
const pRecord = pDrlgMap.pLvlPrestTxtRecord
const nPresetTxtRecordNumberPops = pRecord.dwPops >>> 0
let nProcessedPops = 0
if (!(pRecord.dwScan || nPresetTxtRecordNumberPops)) return
pDrlgMap.pFile = DRLGPRESET_LoadDrlgFile(env, pRecord.szFile[pDrlgMap.nPickedFile]!)
DRLGPRESET_AddPresetUnitToDrlgMap(env.tables, pDrlgMap, pLevel.pSeed)
const pFile = pDrlgMap.pFile
// FOG_DisplayWarning: the oracle's warning handler fails fast.
if (pFile.nWidth !== pDrlgMap.pDrlgCoord.nWidth) {
throw new Error(`${pFile.szPath}: ptRegion->ptFile->nSizeX == ptRegion->tCoords.nSizeTileX (${pFile.nWidth} != ${pDrlgMap.pDrlgCoord.nWidth})`)
}
if (pFile.nHeight !== pDrlgMap.pDrlgCoord.nHeight) {
throw new Error(`${pFile.szPath}: ptRegion->ptFile->nSizeY == ptRegion->tCoords.nSizeTileY (${pFile.nHeight} != ${pDrlgMap.pDrlgCoord.nHeight})`)
}
if (nPresetTxtRecordNumberPops) {
pDrlgMap.pPopsIndex = new Int32Array(nPresetTxtRecordNumberPops)
pDrlgMap.pPopsSubIndex = new Int32Array(nPresetTxtRecordNumberPops)
pDrlgMap.pPopsOrientation = new Int32Array(nPresetTxtRecordNumberPops)
pDrlgMap.pPopsLocation = Array.from({ length: nPresetTxtRecordNumberPops }, () => newCoord())
}
const pDrlgCoord = newCoord(0, 0, pFile.nWidth + 1, pFile.nHeight + 1)
if (pDrlgCoord.nHeight > 256) throw new RangeError(`${pFile.szPath}: ${pDrlgCoord.nHeight} rows overflow pCellFlags[256]`)
for (let i = 0; i < pFile.nWallLayers; i += 1) {
const pTileTypeGrid = newGrid()
const pDrlgGrid2 = newGrid()
DRLGGRID_AssignCellsOffsetsAndFlags(pTileTypeGrid, pFile.pTileTypeLayer[i]!, 0, pDrlgCoord, pFile.nWidth + 1, new Int32Array(256))
DRLGGRID_AssignCellsOffsetsAndFlags(pDrlgGrid2, pFile.pWallLayer[i]!, 0, pDrlgCoord, pFile.nWidth + 1, new Int32Array(256))
for (let nY = 0; nY < pFile.nHeight; nY += 1) {
for (let nX = 0; nX < pFile.nWidth; nX += 1) {
const nTileType = DRLGGRID_GetGridEntry(pTileTypeGrid, nX, nY)
const nGrid2Flags = DRLGGRID_GetGridEntry(pDrlgGrid2, nX, nY)
const nGrid2FlagsByte1 = (nGrid2Flags >>> 8) & 0xff
const nGrid2UpperBit = nGrid2Flags & 0x80000000
const nFlagsBits21_26 = (nGrid2Flags >>> 20) & 0x3f
if (nTileType === TILETYPE_WALL_RIGHT_EXIT || nTileType === TILETYPE_WALL_LEFT_EXIT) {
if (
pRecord.dwScan &&
nFlagsBits21_26 >= 0 &&
nFlagsBits21_26 <= 7 &&
(nGrid2FlagsByte1 === 0 || nGrid2FlagsByte1 === 4 || nGrid2UpperBit)
) {
if (bSingleRoom) {
DRLGGRID_AlterGridFlag(pDrlgGrid, 0, 0, 1 << (nFlagsBits21_26 + 4), FLAG_OPERATION_OR)
} else {
DRLGGRID_AlterGridFlag(pDrlgGrid, Math.trunc(nX / 8), Math.trunc(nY / 8), 1 << (nFlagsBits21_26 + 4), FLAG_OPERATION_OR)
}
}
if (nPresetTxtRecordNumberPops && nFlagsBits21_26 >= 8 && nFlagsBits21_26 <= 29) {
let nCounter = 0
while (nCounter < nProcessedPops) {
if (pDrlgMap.pPopsIndex![nCounter] === nFlagsBits21_26) {
pDrlgMap.pPopsLocation![nCounter]!.nWidth = nX
pDrlgMap.pPopsLocation![nCounter]!.nHeight = nY
break
}
nCounter += 1
}
if (nCounter === nProcessedPops) {
if (nProcessedPops >= nPresetTxtRecordNumberPops) {
throw new RangeError(`${pFile.szPath}: more pops than LvlPrest Pops=${nPresetTxtRecordNumberPops}`)
}
pDrlgMap.pPopsIndex![nProcessedPops] = nFlagsBits21_26
pDrlgMap.pPopsSubIndex![nProcessedPops] = nGrid2FlagsByte1
pDrlgMap.pPopsLocation![nProcessedPops]!.nPosX = nX
pDrlgMap.pPopsLocation![nProcessedPops]!.nPosY = nY
nProcessedPops += 1
}
}
if (pRecord.dwScan && nFlagsBits21_26 >= 30 && nFlagsBits21_26 <= 33) {
if (pLevel.nTileInfo >= pLevel.pTileInfo.length) {
throw new RangeError(`level ${pLevel.nLevelId}: pTileInfo[32] overflow`)
}
const pTileInfo = pLevel.pTileInfo[pLevel.nTileInfo]!
pTileInfo.nPosX = nX + pDrlgMap.pDrlgCoord.nPosX
pTileInfo.nPosY = nY + pDrlgMap.pDrlgCoord.nPosY
switch (nFlagsBits21_26) {
case 30:
pTileInfo.nTileIndex = nGrid2FlagsByte1
break
case 31:
pTileInfo.nTileIndex = nGrid2FlagsByte1 + 5
break
case 32:
pTileInfo.nTileIndex = 10
break
case 33:
pTileInfo.nTileIndex = 11
break
default:
break
}
pLevel.nTileInfo += 1
}
}
}
}
DRLGGRID_ResetGrid(pDrlgGrid2)
DRLGGRID_ResetGrid(pTileTypeGrid)
}
if (nPresetTxtRecordNumberPops) {
pDrlgMap.nPops = nProcessedPops
for (let i = 0; i < pDrlgMap.nPops; i += 1) {
const pPopsLocation = pDrlgMap.pPopsLocation![i]!
const nX1 = Math.min(pPopsLocation.nPosX, pPopsLocation.nWidth)
const nX2 = Math.max(pPopsLocation.nPosX, pPopsLocation.nWidth)
const nY1 = Math.min(pPopsLocation.nPosY, pPopsLocation.nHeight)
const nY2 = Math.max(pPopsLocation.nPosY, pPopsLocation.nHeight)
pPopsLocation.nPosX = nX1 + pDrlgMap.pDrlgCoord.nPosX
pPopsLocation.nPosY = nY1 + pDrlgMap.pDrlgCoord.nPosY
pPopsLocation.nWidth = nX2 - nX1 + 1
pPopsLocation.nHeight = nY2 - nY1 + 1
// (((BYTE4(n) & 3) + (int)n) >> 2) - 1 with BYTE4 of a zero-extended uint32 == 0.
pDrlgMap.pPopsIndex![i] = (pDrlgMap.pPopsIndex![i]! >> 2) - 1
}
}
if (pRecord.dwScan) {
for (let pPresetUnit = pFile.pPresetUnit; pPresetUnit; pPresetUnit = pPresetUnit.pNext) {
if (
pPresetUnit.nUnitType === UNIT_OBJECT &&
pPresetUnit.nIndex < 573 &&
objectSubClass(env.tables, pPresetUnit.nIndex) & OBJSUBCLASS_WAYPOINT
) {
if (bSingleRoom) {
DRLGGRID_AlterGridFlag(pDrlgGrid, 0, 0, 0x30000, FLAG_OPERATION_OR)
} else {
DRLGGRID_AlterGridFlag(
pDrlgGrid,
Math.trunc(Math.trunc(pPresetUnit.nXpos / 5) / 8),
Math.trunc(Math.trunc(pPresetUnit.nYpos / 5) / 8),
0x30000,
FLAG_OPERATION_OR,
)
}
}
}
}
}
//D2Common.0x6FD87E10
export function DRLGPRESET_SetPickedFileInDrlgMap(pDrlgMap: D2DrlgMapStrc, nPickedFile: number): void {
pDrlgMap.nPickedFile = nPickedFile
}
//D2Common.0x6FD87E20
export function DRLGPRESET_AllocDrlgMap(
pLevel: D2DrlgLevelStrc,
nLvlPrestId: number,
pDrlgCoord: D2DrlgCoordStrc,
pSeed: D2SeedStrc,
): D2DrlgMapStrc {
const pLvlPrestTxtRecord = DATATBLS_GetLvlPrestTxtRecord(pLevel.pDrlg.env.tables, nLvlPrestId)
const pDrlgMap: D2DrlgMapStrc = {
nLevelPrest: nLvlPrestId,
nPickedFile: 0,
pLvlPrestTxtRecord,
pFile: null,
pDrlgCoord: newCoord(),
bHasInfo: 0,
pMapGrid: newGrid(),
pPresetUnit: null,
bInited: 0,
nPops: 0,
pPopsIndex: null,
pPopsSubIndex: null,
pPopsOrientation: null,
pPopsLocation: null,
pNext: null,
}
pDrlgMap.nPickedFile = SEED_RollLimitedRandomNumber(pSeed, pLvlPrestTxtRecord.dwFiles)
pDrlgMap.pDrlgCoord.nPosX = pDrlgCoord.nPosX
pDrlgMap.pDrlgCoord.nPosY = pDrlgCoord.nPosY
if (pLvlPrestTxtRecord.nSizeX && pLvlPrestTxtRecord.nSizeY) {
pDrlgMap.pDrlgCoord.nWidth = pLvlPrestTxtRecord.nSizeX
pDrlgMap.pDrlgCoord.nHeight = pLvlPrestTxtRecord.nSizeY
} else {
pDrlgMap.pDrlgCoord.nWidth = pDrlgCoord.nWidth
pDrlgMap.pDrlgCoord.nHeight = pDrlgCoord.nHeight
}
pDrlgMap.bInited = 1
pDrlgMap.pNext = pLevel.pCurrentMap
pLevel.pCurrentMap = pDrlgMap
return pDrlgMap
}
//D2Common.0x6FD88610
export function DRLGPRESET_InitLevelData(pLevel: D2DrlgLevelStrc): void {
const pLvlPrestTxtRecord = DATATBLS_GetLvlPrestTxtRecordFromLevelId(pLevel.pDrlg.env.tables, pLevel.nLevelId)
if (!pLvlPrestTxtRecord) {
throw new Error(`level ${pLevel.nLevelId}: Level labeled as Preset, but no preset claims the level`)
}
pLevel.pPreset = { pDrlgMap: null, nDirection: 0 }
if (pLvlPrestTxtRecord.dwFiles) {
pLevel.pPreset.nDirection = SEED_RollLimitedRandomNumber(pLevel.pSeed, pLvlPrestTxtRecord.dwFiles)
} else {
pLevel.pPreset.nDirection = -1
}
DRLG_SetLevelPositionAndSize(pLevel.pDrlg, pLevel)
}
//D2Common.0x6FD886F0
// pfAutomap / pfTownAutomap are null on the server (and in the oracle), so the automap branches
// that initialise neighbouring levels never run.
export function DRLGPRESET_GenerateLevel(pLevel: D2DrlgLevelStrc): void {
const pLvlPrestTxtRecord = DATATBLS_GetLvlPrestTxtRecordFromLevelId(pLevel.pDrlg.env.tables, pLevel.nLevelId)
if (!pLvlPrestTxtRecord) throw new Error(`level ${pLevel.nLevelId}: no LvlPrest record`)
const pPreset = pLevel.pPreset!
pPreset.pDrlgMap = DRLGPRESET_AllocDrlgMap(pLevel, pLvlPrestTxtRecord.dwDef, pLevel.pLevelCoords, pLevel.pSeed)
if (pPreset.nDirection === -1) {
pPreset.nDirection = pPreset.pDrlgMap.nPickedFile
} else {
pPreset.pDrlgMap.nPickedFile = pPreset.nDirection
}
DRLGPRESET_BuildArea(pLevel, pPreset.pDrlgMap, 0, false)
if (pLevel.nLevelId === LEVEL_LUTGHOLEIN || pLevel.nLevelId === LEVEL_THEPANDEMONIUMFORTRESS || pLevel.nLevelId === LEVEL_HARROGATH) {
// pfTownAutomap is null.
} else {
// pfAutomap is null.
}
}
const nRiverBlockSizeInTilesX = 5
const nRiverBlockSizeInTilesY = 4
const DRLGROOMTILE_SUBTILES_SIZE = 5
function DRLGPRESET_AddPresetUnitToMap(pMazeMap: D2DrlgMapStrc, pNewPresetUnit: D2PresetUnitStrc, bSpawned: boolean): void {
pNewPresetUnit.pNext = pMazeMap.pPresetUnit
pMazeMap.pPresetUnit = pNewPresetUnit
pNewPresetUnit.bSpawned |= bSpawned ? 1 : 0
}
function DRLGPRESET_AllocateAndAddPresetUnitToMap(
pDrlgRoom: D2DrlgRoomStrc | null,
nUnitType: number,
nIndex: number,
nMode: number,
nX: number,
nY: number,
pMazeMap: D2DrlgMapStrc,
bSpawned: boolean,
): D2PresetUnitStrc {
const pPresetUnit = DRLGROOM_AllocPresetUnit(pDrlgRoom, nUnitType, nIndex, nMode, nX, nY)
DRLGPRESET_AddPresetUnitToMap(pMazeMap, pPresetUnit, bSpawned)
return pPresetUnit
}
//D2Common.0x6FD86AC0
export function DRLGPRESET_AddPresetRiverObjects(
pDrlgMap: D2DrlgMapStrc,
nOffsetX: number,
pDrlgGrid: D2DrlgGridStrc,
): void {
const nX = nOffsetX < 0 ? 0 : nOffsetX
for (let nY = 0; nY < pDrlgMap.pDrlgCoord.nHeight; nY += 1) {
const sub = DUNGEON_GameTileToSubtileCoords(nOffsetX + pDrlgMap.pDrlgCoord.nPosX, nY + pDrlgMap.pDrlgCoord.nPosY)
const nObjectX = sub.nX - nRiverBlockSizeInTilesX
const nObjectY = sub.nY
DRLGPRESET_AllocateAndAddPresetUnitToMap(null, UNIT_OBJECT, OBJECT_RIVER1, OBJMODE_NEUTRAL, nObjectX, nObjectY, pDrlgMap, true)
DRLGPRESET_AllocateAndAddPresetUnitToMap(null, UNIT_OBJECT, OBJECT_RIVER2, OBJMODE_NEUTRAL, nObjectX + 5, nObjectY, pDrlgMap, true)
DRLGPRESET_AllocateAndAddPresetUnitToMap(null, UNIT_OBJECT, OBJECT_RIVER2, OBJMODE_NEUTRAL, nObjectX + 10, nObjectY, pDrlgMap, true)
DRLGPRESET_AllocateAndAddPresetUnitToMap(null, UNIT_OBJECT, OBJECT_RIVER2, OBJMODE_NEUTRAL, nObjectX + 15, nObjectY, pDrlgMap, true)
DRLGPRESET_AllocateAndAddPresetUnitToMap(null, UNIT_OBJECT, OBJECT_RIVER3, OBJMODE_NEUTRAL, nObjectX + 20, nObjectY, pDrlgMap, true)
const nGridEntry = DRLGGRID_GetGridEntry(pDrlgGrid, nX, nY)
const nTileStyle = (nGridEntry >>> 20) & 0x3f
const nTileSequence = (nGridEntry >>> 8) & 0xff
if (nTileStyle === 4) {
switch (nTileSequence) {
case 0:
case 4:
case 8:
case 16:
case 29:
case 39:
nY += nRiverBlockSizeInTilesY - 1
break
default:
break
}
}
}
}
function DRLGPRESET_SpawnRiver(
pMazeMap: D2DrlgMapStrc,
tDrlgCoord: D2DrlgCoordStrc,
pDrlgGrid: D2DrlgGridStrc,
nOffsetX: number,
): void {
const startSub = DUNGEON_GameTileToSubtileCoords(tDrlgCoord.nPosX + nOffsetX + 1, tDrlgCoord.nPosY)
const endSub = DUNGEON_GameTileToSubtileCoords(0, tDrlgCoord.nPosY + pMazeMap.pFile!.nHeight)
const nSubtileX = startSub.nX
let nSubtileY = startSub.nY
const nEndSubtileY = endSub.nY
while (nSubtileY < nEndSubtileY) {
DRLGPRESET_AllocateAndAddPresetUnitToMap(
null,
UNIT_OBJECT,
OBJECT_INVISIBLE_RIVER_SOUND1,
OBJMODE_NEUTRAL,
nSubtileX,
nSubtileY,
pMazeMap,
true,
)
nSubtileY += DRLGROOMTILE_SUBTILES_SIZE
}
DRLGPRESET_AddPresetRiverObjects(pMazeMap, nOffsetX, pDrlgGrid)
}
//D2Common.0x6FD867A0
export function DRLGPRESET_SpawnHardcodedPresetUnits(pDrlgRoom: D2DrlgRoomStrc): void {
const env = pDrlgRoom.pLevel.pDrlg.env
const pMazeMap = pDrlgRoom.pMaze!.pMap!
if (!pMazeMap.pFile) {
pMazeMap.pFile = DRLGPRESET_LoadDrlgFile(env, pMazeMap.pLvlPrestTxtRecord.szFile[pMazeMap.nPickedFile]!)
DRLGPRESET_AddPresetUnitToDrlgMap(env.tables, pMazeMap, pDrlgRoom.pSeed)
}
if (pMazeMap.bInited === 1) {
pMazeMap.bInited = 0
const nLevelPrestId = pMazeMap.nLevelPrest
if (
nLevelPrestId >= LVLPREST_ACT1_WILD_BORDER_1 &&
nLevelPrestId <= LVLPREST_ACT1_WILD_BORDER_4 &&
pDrlgRoom.pLevel.nLevelId === LEVEL_BLOODMOOR &&
pMazeMap.nPickedFile === 3
) {
const sub = DUNGEON_GameTileToSubtileCoords(
pMazeMap.pDrlgCoord.nPosX + Math.trunc(pMazeMap.pDrlgCoord.nWidth / 2),
pMazeMap.pDrlgCoord.nPosY + Math.trunc(pMazeMap.pDrlgCoord.nHeight / 2),
)
const nClassId = MONSTER_NAVI < env.tables.nMonStatsTxtRecordCount ? MONSTER_NAVI : -1
DRLGPRESET_AllocateAndAddPresetUnitToMap(null, UNIT_MONSTER, nClassId, MONMODE_NEUTRAL, sub.nX, sub.nY, pMazeMap, false)
} else if (
nLevelPrestId === LVLPREST_ACT1_TOWN_1 ||
nLevelPrestId === LVLPREST_ACT1_TOWN_1_TRANSITION_S ||
(nLevelPrestId >= LVLPREST_ACT1_RIVER_UPPER && nLevelPrestId <= LVLPREST_ACT1_BRIDGE) ||
nLevelPrestId === LVLPREST_ACT1_TRISTRAM
) {
if (pDrlgRoom.dwFlags & DRLGROOMFLAG_AUTOMAP_REVEAL) {
const tDrlgCoord = newCoord(0, 0, pMazeMap.pDrlgCoord.nWidth, pMazeMap.pDrlgCoord.nHeight)
const tDrlgGrid = newGrid()
const nCellFlags = new Int32Array(256)
DRLGGRID_AssignCellsOffsetsAndFlags(
tDrlgGrid,
pMazeMap.pFile.pFloorLayer[0]!,
0,
tDrlgCoord,
pMazeMap.pDrlgCoord.nWidth + 1,
nCellFlags,
)
tDrlgCoord.nPosX = pMazeMap.pDrlgCoord.nPosX
tDrlgCoord.nPosY = pMazeMap.pDrlgCoord.nPosY
if (pMazeMap.pLvlPrestTxtRecord.dwDef === LVLPREST_ACT1_RIVER_LOWER) {
DRLGPRESET_SpawnRiver(pMazeMap, tDrlgCoord, tDrlgGrid, -1)
} else {
for (let nOffsetX = 0; nOffsetX < tDrlgCoord.nWidth; nOffsetX += 1) {
const nGridEntry = DRLGGRID_GetGridEntry(tDrlgGrid, nOffsetX, 0)
const nTileStyle = (nGridEntry >>> 20) & 0x3f
const nTileSequence = (nGridEntry >>> 8) & 0xff
if (nTileStyle === 2 && nTileSequence === 24) {
DRLGPRESET_SpawnRiver(pMazeMap, tDrlgCoord, tDrlgGrid, nOffsetX)
nOffsetX += nRiverBlockSizeInTilesX - 1
}
}
}
DRLGGRID_ResetGrid(tDrlgGrid)
}
}
}
pDrlgRoom.dwFlags = (pDrlgRoom.dwFlags | DRLGROOMFLAG_PRESET_UNITS_ADDED) >>> 0
}
//D2Common.0x6FD86E50
export function DRLGPRESET_InitPresetRoomGrids(pDrlgRoom: D2DrlgRoomStrc): void {
const pMaze = pDrlgRoom.pMaze!
const pMap = pMaze.pMap!
const pFile = pMap.pFile!
const pDrlgCoord = newCoord(
pDrlgRoom.pDrlgCoord.nPosX - pMap.pDrlgCoord.nPosX,
pDrlgRoom.pDrlgCoord.nPosY - pMap.pDrlgCoord.nPosY,
pDrlgRoom.pDrlgCoord.nWidth + 1,
pDrlgRoom.pDrlgCoord.nHeight + 1,
)
const nWidth = pMap.pDrlgCoord.nWidth + 1
for (let i = 0; i < pFile.nWallLayers; i += 1) {
DRLGGRID_FillNewCellFlags(pMaze.pWallGrid[i]!, pFile.pWallLayer[i]!, 0, pDrlgCoord, nWidth)
DRLGGRID_FillNewCellFlags(pMaze.pTileTypeGrid[i]!, pFile.pTileTypeLayer[i]!, 0, pDrlgCoord, nWidth)
}
if (pFile.nWallLayers) {
DRLGGRID_AlterEdgeGridFlags(pMaze.pWallGrid[0]!, 132, FLAG_OPERATION_OR)
}
for (let i = 1; i < pFile.nWallLayers; i += 1) {
DRLGGRID_AlterAllGridFlags(pMaze.pWallGrid[i]!, i << 18, FLAG_OPERATION_OR)
}
for (let i = 0; i < pFile.nFloorLayers; i += 1) {
DRLGGRID_FillNewCellFlags(pMaze.pFloorGrid[i]!, pFile.pFloorLayer[i]!, 0, pDrlgCoord, nWidth)
DRLGGRID_AlterAllGridFlags(pMaze.pFloorGrid[i]!, i << 18, FLAG_OPERATION_OR)
}
DRLGGRID_FillNewCellFlags(pMaze.pCellGrid, pFile.pShadowLayer!, 0, pDrlgCoord, nWidth)
for (let i = 0; i < pFile.nFloorLayers; i += 1) {
DRLGGRID_AlterEdgeGridFlags(pMaze.pFloorGrid[i]!, 132, FLAG_OPERATION_OR)
}
DRLGGRID_AlterEdgeGridFlags(pMaze.pCellGrid, 132, FLAG_OPERATION_OR)
const subPos = DUNGEON_GameTileToSubtileCoords(pDrlgRoom.pDrlgCoord.nPosX, pDrlgRoom.pDrlgCoord.nPosY)
const subSize = DUNGEON_GameTileToSubtileCoords(pDrlgRoom.pDrlgCoord.nWidth, pDrlgRoom.pDrlgCoord.nHeight)
pDrlgCoord.nPosX = subPos.nX
pDrlgCoord.nPosY = subPos.nY
pDrlgCoord.nWidth = subSize.nX
pDrlgCoord.nHeight = subSize.nY
let pPresetUnit = pMap.pPresetUnit
let pPrevious: D2PresetUnitStrc | null = null
while (pPresetUnit) {
if (DRLGROOM_AreXYInsideCoordinates(pDrlgCoord, pPresetUnit.nXpos, pPresetUnit.nYpos)) {
pPresetUnit.nXpos -= 5 * pDrlgRoom.pDrlgCoord.nPosX
pPresetUnit.nYpos -= 5 * pDrlgRoom.pDrlgCoord.nPosY
if (pPrevious) {
pPrevious.pNext = pPresetUnit.pNext
} else {
pMap.pPresetUnit = pPresetUnit.pNext
}
pPresetUnit.pNext = pDrlgRoom.pPresetUnits
pDrlgRoom.pPresetUnits = pPresetUnit
if (pPrevious) {
pPresetUnit = pPrevious.pNext
} else {
pPresetUnit = pMap.pPresetUnit
}
} else {
pPrevious = pPresetUnit
pPresetUnit = pPresetUnit.pNext
}
}
}
//D2Common.0x6FD87130
export function DRLGPRESET_AddPresetRoomMapTiles(pDrlgRoom: D2DrlgRoomStrc): void {
DRLGROOMTILE_AllocTileGrid(pDrlgRoom)
const pMaze = pDrlgRoom.pMaze!
const pMazeMap = pDrlgRoom.pMaze!.pMap!
const pFile = pMazeMap.pFile!
let bKillEdgeX = false
let bKillEdgeY = false
if (pMazeMap.pLvlPrestTxtRecord.dwKillEdge) {
bKillEdgeX =
((pDrlgRoom.pDrlgCoord.nPosX + pDrlgRoom.pDrlgCoord.nWidth) | 0) ===
((pMazeMap.pDrlgCoord.nPosX + pMazeMap.pDrlgCoord.nWidth) | 0)
bKillEdgeY =
((pDrlgRoom.pDrlgCoord.nPosY + pDrlgRoom.pDrlgCoord.nHeight) | 0) ===
((pMazeMap.pDrlgCoord.nPosY + pMazeMap.pDrlgCoord.nHeight) | 0)
}
const tTownOrGraveyardTileInfoGrid = newGrid()
const aCellOffsets = new Int32Array(256)
const aCellPosBuffer = new Int32Array(1024)
const tTownOrGraveyardTileTypeGrid = newGrid()
const aTownOrGraveyardCellPosOffsets = new Int32Array(256)
const aTownOrGraveyardCellPosBuffer = new Int32Array(1024)
const bTownOrGraveyard =
pDrlgRoom.pMaze!.nLevelPrest === LVLPREST_ACT1_TOWN_1 || pDrlgRoom.pMaze!.nLevelPrest === LVLPREST_ACT1_GRAVEYARD
if (bTownOrGraveyard) {
DRLGGRID_FillGrid(
tTownOrGraveyardTileTypeGrid,
(pDrlgRoom.pDrlgCoord.nWidth + 1) | 0,
(pDrlgRoom.pDrlgCoord.nHeight + 1) | 0,
aTownOrGraveyardCellPosBuffer,
0,
aTownOrGraveyardCellPosOffsets,
)
DRLGGRID_FillGrid(
tTownOrGraveyardTileInfoGrid,
(pDrlgRoom.pDrlgCoord.nWidth + 1) | 0,
(pDrlgRoom.pDrlgCoord.nHeight + 1) | 0,
aCellPosBuffer,
0,
aCellOffsets,
)
}
for (let i = 0; i < pFile.nFloorLayers; i += 1) {
const bCheckCoordinatesValidity = i === 0 && pMazeMap.pLvlPrestTxtRecord.dwFillBlanks !== 0
DRLGROOMTILE_CountAllTileTypes(pDrlgRoom, pMaze.pFloorGrid[i]!, bCheckCoordinatesValidity, bKillEdgeX, bKillEdgeY)
if (pMazeMap.pLvlPrestTxtRecord.dwAnimate) {
DRLGANIM_TestLoadAnimatedRoomTiles(pDrlgRoom, pMaze.pFloorGrid[i]!, null, 0, bKillEdgeX, bKillEdgeY)
}
}
for (let i = 0; i < pFile.nWallLayers; i += 1) {
DRLGROOMTILE_CountWallWarpTiles(pDrlgRoom, pMaze.pWallGrid[i]!, pMaze.pTileTypeGrid[i]!, bKillEdgeX, bKillEdgeY)
DRLGROOMTILE_CountAllTileTypes(pDrlgRoom, pMaze.pWallGrid[i]!, false, bKillEdgeX, bKillEdgeY)
if (pMazeMap.pLvlPrestTxtRecord.dwAnimate) {
DRLGANIM_TestLoadAnimatedRoomTiles(
pDrlgRoom,
pMaze.pWallGrid[i]!,
pMaze.pTileTypeGrid[i]!,
TILETYPE_FLOOR,
bKillEdgeX,
bKillEdgeY,
)
}
}
DRLGROOMTILE_CountAllTileTypes(pDrlgRoom, pMaze.pCellGrid, false, bKillEdgeX, bKillEdgeY)
if (pMazeMap.pLvlPrestTxtRecord.dwAnimate) {
DRLGANIM_TestLoadAnimatedRoomTiles(pDrlgRoom, pMaze.pCellGrid, null, TILETYPE_SHADOW, bKillEdgeX, bKillEdgeY)
}
if (bTownOrGraveyard) {
DRLGROOMTILE_CountAllTileTypes(pDrlgRoom, tTownOrGraveyardTileInfoGrid, false, bKillEdgeX, bKillEdgeY)
}
DRLGROOMTILE_AllocTileData(pDrlgRoom)
for (let i = 0; i < pFile.nFloorLayers; i += 1) {
const bCheckCoordinatesValidity = i === 0 && pMazeMap.pLvlPrestTxtRecord.dwFillBlanks !== 0
DRLGROOMTILE_LoadInitRoomTiles(pDrlgRoom, pMaze.pFloorGrid[i]!, null, bCheckCoordinatesValidity, bKillEdgeX, bKillEdgeY)
}
for (let i = 0; i < pFile.nWallLayers; i += 1) {
DRLGROOMTILE_LoadInitRoomTiles(pDrlgRoom, pMaze.pWallGrid[i]!, pMaze.pTileTypeGrid[i]!, false, bKillEdgeX, bKillEdgeY)
}
DRLGROOMTILE_LoadInitRoomTiles(pDrlgRoom, pMaze.pCellGrid, null, false, bKillEdgeX, bKillEdgeY)
if (bTownOrGraveyard) {
DRLGROOMTILE_LoadInitRoomTiles(pDrlgRoom, tTownOrGraveyardTileInfoGrid, tTownOrGraveyardTileTypeGrid, false, bKillEdgeX, bKillEdgeY)
}
if (pMazeMap.pLvlPrestTxtRecord.dwAnimate) {
DRLGANIM_AllocAnimationTileGrids(
pDrlgRoom,
pMazeMap.pLvlPrestTxtRecord.nAnimSpeed,
pMaze.pWallGrid,
pFile.nWallLayers,
pMaze.pFloorGrid,
pFile.nFloorLayers,
pMaze.pCellGrid,
)
}
const pTileGrid = tileGridOf(pDrlgRoom)
pTileGrid.pTiles.nWalls = pTileGrid.nWalls
pTileGrid.pTiles.nFloors = pTileGrid.nFloors
pTileGrid.pTiles.nRoofs = pTileGrid.nShadows
if (pMazeMap.pLvlPrestTxtRecord.dwLogicals) {
// C passes the first element of each grid array.
DRLGLOGIC_InitializeDrlgCoordList(pDrlgRoom, pMaze.pTileTypeGrid[0]!, pMaze.pFloorGrid[0]!, pMaze.pWallGrid[0]!)
} else {
DRLGLOGIC_AllocCoordLists(pDrlgRoom)
}
if (bTownOrGraveyard) {
DRLGGRID_ResetGrid(tTownOrGraveyardTileTypeGrid)
DRLGGRID_ResetGrid(tTownOrGraveyardTileInfoGrid)
}
if (pDrlgRoom.pLevel.nLevelId === LEVEL_BURIALGROUNDS && !pMaze.pTombStoneTiles) {
let nTombStoneTiles = 0
// D2_ALLOC_POOL(6 * sizeof(D2CoordStrc)): zero-filled like the oracle's FOG_AllocPool (calloc).
const pTombStoneTiles = [0, 1, 2, 3, 4, 5].map(() => ({ nX: 0, nY: 0 }))
pMaze.pTombStoneTiles = pTombStoneTiles
for (let i = 0; i < pDrlgRoom.pDrlgCoord.nHeight; i += 1) {
for (let j = 0; j < pDrlgRoom.pDrlgCoord.nWidth; j += 1) {
const nPackedTileInformation = DRLGGRID_GetGridEntry(pMaze.pWallGrid[0]!, j, i) >>> 0
if (
PackedTile.nTileStyle(nPackedTileInformation) === 10 &&
PackedTile.nTileSequence(nPackedTileInformation) >= 23 &&
PackedTile.nTileSequence(nPackedTileInformation) <= 27
) {
if (nTombStoneTiles < 6) {
const nX = (j + pDrlgRoom.pDrlgCoord.nPosX) | 0
const nY = (i + pDrlgRoom.pDrlgCoord.nPosY) | 0
const tSubtile = DUNGEON_GameTileToSubtileCoords(nX, nY)
pTombStoneTiles[nTombStoneTiles]!.nX = (tSubtile.nX + 2) | 0
pTombStoneTiles[nTombStoneTiles]!.nY = (tSubtile.nY + 2) | 0
nTombStoneTiles += 1
}
}
}
}
pMaze.nTombStoneTiles = nTombStoneTiles
}
}