diablo2-web/src/game/drlg/drlg-act-links.ts

1573 lines
56 KiB
TypeScript

/**
* D2Common act layout (level placement and level-to-level links): 1:1 port of
* D2MOO source/D2Common/src/Drlg/DrlgOutPlace.cpp and DrlgOutRoom.cpp (commit 5596f5c, MIT License,
* Copyright (c) 2020-2025 The Phrozen Keep community): DRLGOUTPLACE_CreateLevelConnections,
* DRLG_GenerateJungles, and link helpers.
*/
import { D2SEED_AdvanceInline, SEED_RollLimitedRandomNumber, SEED_RollRandomNumber, type D2SeedStrc } from '../d2-rng.ts'
import {
DRLG_GetDirectionFromCoordinates,
DRLG_GetDrlgWarpFromLevelId,
DRLG_GetLevel,
DRLG_SetWarpId,
} from './drlg-drlg.ts'
import {
LVLPREST_ACT3_CLEARING_WEBBY_E,
LVLPREST_ACT3_CLEARING_WEBBY_EW,
LVLPREST_ACT3_CLEARING_WEBBY_N,
LVLPREST_ACT3_CLEARING_WEBBY_NE,
LVLPREST_ACT3_CLEARING_WEBBY_NS,
LVLPREST_ACT3_CLEARING_WEBBY_NW,
LVLPREST_ACT3_CLEARING_WEBBY_S,
LVLPREST_ACT3_CLEARING_WEBBY_SE,
LVLPREST_ACT3_CLEARING_WEBBY_SW,
LVLPREST_ACT3_CLEARING_WEBBY_W,
LVLPREST_ACT3_JUNGLE_E_N,
LVLPREST_ACT3_JUNGLE_E_S,
LVLPREST_ACT3_JUNGLE_E_W,
LVLPREST_ACT3_JUNGLE_EW_N,
LVLPREST_ACT3_JUNGLE_EW_S,
LVLPREST_ACT3_JUNGLE_N_E,
LVLPREST_ACT3_JUNGLE_N_S,
LVLPREST_ACT3_JUNGLE_N_W,
LVLPREST_ACT3_JUNGLE_NE_S,
LVLPREST_ACT3_JUNGLE_NE_W,
LVLPREST_ACT3_JUNGLE_NEW_S,
LVLPREST_ACT3_JUNGLE_NS_E,
LVLPREST_ACT3_JUNGLE_NS_W,
LVLPREST_ACT3_JUNGLE_NSE_W,
LVLPREST_ACT3_JUNGLE_NSW_E,
LVLPREST_ACT3_JUNGLE_NW_E,
LVLPREST_ACT3_JUNGLE_NW_S,
LVLPREST_ACT3_JUNGLE_S_E,
LVLPREST_ACT3_JUNGLE_S_N,
LVLPREST_ACT3_JUNGLE_S_W,
LVLPREST_ACT3_JUNGLE_SE_N,
LVLPREST_ACT3_JUNGLE_SE_W,
LVLPREST_ACT3_JUNGLE_SEW_N,
LVLPREST_ACT3_JUNGLE_SW_E,
LVLPREST_ACT3_JUNGLE_SW_N,
LVLPREST_ACT3_JUNGLE_TAIL,
LVLPREST_ACT3_JUNGLE_W_E,
LVLPREST_ACT3_JUNGLE_W_N,
LVLPREST_ACT3_JUNGLE_W_S,
LVLPREST_ACT3_TOWN,
LVLPREST_NONE,
} from './drlg-ids.ts'
import {
DRLG_CheckNotOverlappingUsingManhattanDistance,
DRLG_CheckOverlappingWithOrthogonalMargin,
DRLGROOM_AddOrth,
DRLGROOM_GetVisArrayFromLevelId,
} from './drlg-room.ts'
import { DATATBLS_GetLevelDefRecord } from './drlg-tables.ts'
import {
ACT_I,
ACT_II,
ACT_III,
ACT_IV,
ACT_V,
type D2DrlgCoordStrc,
type D2DrlgLevelStrc,
type D2DrlgStrc,
DIRECTION_COUNT,
DIRECTION_NORTHEAST,
DIRECTION_NORTHWEST,
DIRECTION_SOUTHEAST,
DIRECTION_SOUTHWEST,
type DrlgEnv,
DRLGTYPE_OUTDOOR,
DRLGTYPE_PRESET,
idiv,
LEVEL_ARREATPLATEAU,
LEVEL_BLACKMARSH,
LEVEL_BLOODMOOR,
LEVEL_BLOODYFOOTHILLS,
LEVEL_BURIALGROUNDS,
LEVEL_CANYONOFTHEMAGI,
LEVEL_CHAOSSANCTUM,
LEVEL_CITYOFTHEDAMNED,
LEVEL_COLDPLAINS,
LEVEL_DARKWOOD,
LEVEL_DRYHILLS,
LEVEL_FAROASIS,
LEVEL_HARROGATH,
LEVEL_ID_ACT5_BARRICADE_1,
LEVEL_KURASTDOCKTOWN,
LEVEL_LOSTCITY,
LEVEL_LOWERKURAST,
LEVEL_LUTGHOLEIN,
LEVEL_MONASTERYGATE,
LEVEL_MOOMOOFARM,
LEVEL_OUTERCLOISTER,
LEVEL_OUTERSTEPPES,
LEVEL_PANDEMONIUMFINALE,
LEVEL_PANDEMONIUMRUN2,
LEVEL_PLAINSOFDESPAIR,
LEVEL_ROCKYWASTE,
LEVEL_ROGUEENCAMPMENT,
LEVEL_SPIDERFOREST,
LEVEL_STONYFIELD,
LEVEL_TAMOEHIGHLAND,
LEVEL_THEPANDEMONIUMFORTRESS,
LEVEL_TRAVINCAL,
LEVEL_TUNDRAWASTELANDS,
LEVEL_VALLEYOFSNAKES,
newCoord,
} from './drlg-types.ts'
import { DRLGWARP_GetWarpIdArrayFromLevelId } from './drlg-warp.ts'
/** D2DrlgLevelLinkDataStrc. `nRand` is the C union nRand[4][15] / nRand2[60]: nRand[k][i] = nRand[k * 15 + i]. */
interface D2DrlgLevelLinkDataStrc {
readonly env: DrlgEnv
pSeed: D2SeedStrc
pLevelCoord: D2DrlgCoordStrc[]
pLink: readonly D2DrlgLinkStrc[]
nRand: Int32Array
nIteration: number
nCurrentLevel: number
}
type Linker = (pLevelLinkData: D2DrlgLevelLinkDataStrc) => boolean
/** D2DrlgLinkStrc */
interface D2DrlgLinkStrc {
readonly pfLinker: Linker | null
readonly nLevel: number
readonly nLevelLink: number
readonly nLevelLinkEx: number
}
const R = (k: number, i: number): number => k * 15 + i
let dword_6FDEA6FC = 0
/** Pads a link table to its 15 static entries; C zero-initialises the unlisted ones. */
function linkTable(entries: D2DrlgLinkStrc[]): readonly D2DrlgLinkStrc[] {
const table = entries.slice()
while (table.length < 15) table.push({ pfLinker: null, nLevel: 0, nLevelLink: 0, nLevelLinkEx: 0 })
return table
}
//D2Common.0x6FD81330
function sub_6FD81330(d: D2DrlgLevelLinkDataStrc): boolean {
if (d.nRand[R(1, d.nIteration)] === -1) {
d.nRand[R(0, d.nIteration)] = -1
}
const pLevelDefBinRecord = DATATBLS_GetLevelDefRecord(d.env.tables, d.nCurrentLevel)
d.pLevelCoord[d.nIteration]!.nPosX = pLevelDefBinRecord.dwOffsetX
d.pLevelCoord[d.nIteration]!.nPosY = pLevelDefBinRecord.dwOffsetY
return true
}
//D2Common.0x6FD81380
function sub_6FD81380(d: D2DrlgLevelLinkDataStrc): boolean {
const it = d.nIteration
if (d.nRand[R(1, it)] === -1) {
d.nRand[R(1, it)] = SEED_RollRandomNumber(d.pSeed) & 3
d.nRand[R(0, it)] = d.nRand[R(1, it)]!
} else {
if ((d.nRand[R(0, it)]! + 1) % 4 === d.nRand[R(1, it)]) return false
d.nRand[R(0, it)] = (d.nRand[R(0, it)]! + 1) % 4
}
sub_6FD81430(d.pLevelCoord[d.pLink[it]!.nLevelLink]!, d.pLevelCoord[it]!, d.nRand[R(0, it)]!, 1)
return true
}
//D2Common.0x6FD81430
function sub_6FD81430(c1: D2DrlgCoordStrc, c2: D2DrlgCoordStrc, a3: number, a4: number): void {
switch (a3) {
case 0:
c2.nPosX = c1.nPosX
c2.nPosY = c1.nPosY + c1.nHeight
if (a4 === 1) c2.nPosX -= 16
break
case 1:
c2.nPosX = c1.nPosX - c2.nWidth
c2.nPosY = c1.nPosY
if (a4 === 1) c2.nPosY -= 16
else if (a4 === 2) c2.nPosY += 8
break
case 2:
c2.nPosX = c1.nPosX + c1.nWidth - c2.nWidth
c2.nPosY = c1.nPosY - c2.nHeight
if (a4 === 1) c2.nPosX += 16
break
case 3:
c2.nPosX = c1.nPosX + c1.nWidth
c2.nPosY = c1.nPosY + c1.nHeight - c2.nHeight
switch (a4) {
case 1:
c2.nPosY += 16
break
case 2:
c2.nPosY -= 8
break
case 3:
c2.nPosY += 8
break
default:
break
}
break
default:
return
}
}
//D2Common.0x6FD81530
function sub_6FD81530(d: D2DrlgLevelLinkDataStrc): boolean {
const it = d.nIteration
if (d.nRand[R(1, it)] === -1) {
d.nRand[R(1, it)] = SEED_RollRandomNumber(d.pSeed) & 7
d.nRand[R(0, it)] = d.nRand[R(1, it)]!
} else {
if ((d.nRand[R(0, it)]! + 1) % 8 === d.nRand[R(1, it)]) return false
d.nRand[R(0, it)] = (d.nRand[R(0, it)]! + 1) % 8
}
sub_6FD815E0(d.pLevelCoord[d.pLink[it]!.nLevelLink]!, d.pLevelCoord[it]!, d.nRand[R(0, it)]!, 1)
return true
}
//D2Common.0x6FD815E0
function sub_6FD815E0(c1: D2DrlgCoordStrc, c2: D2DrlgCoordStrc, a3: number, a4: number): void {
switch (a3) {
case 0:
c2.nPosX = c1.nPosX
c2.nPosY = c1.nPosY + c1.nHeight
if (a4 === 1) c2.nPosX -= idiv(c2.nWidth, 2) + 8
break
case 1:
c2.nPosX = c1.nPosX
c2.nPosY = c1.nPosY + c1.nHeight
if (a4 === 1) c2.nPosX += idiv(c2.nWidth, 2) + 8
break
case 2:
c2.nPosX = c1.nPosX - c2.nWidth
c2.nPosY = c1.nPosY
if (a4 === 1) c2.nPosY -= idiv(c2.nHeight, 2) + 8
break
case 3:
c2.nPosX = c1.nPosX - c2.nWidth
c2.nPosY = c1.nPosY
if (a4 === 1) c2.nPosY += idiv(c2.nHeight, 2) + 8
break
case 4:
c2.nPosX = c1.nPosX
c2.nPosY = c1.nPosY - c2.nHeight
if (a4 === 1) c2.nPosX -= idiv(c2.nWidth, 2) + 8
break
case 5:
c2.nPosX = c1.nPosX
c2.nPosY = c1.nPosY - c2.nHeight
if (a4 === 1) c2.nPosX += idiv(c2.nWidth, 2) + 8
break
case 6:
c2.nPosX = c1.nPosX + c1.nWidth
c2.nPosY = c1.nPosY
if (a4 === 1) c2.nPosY -= idiv(c2.nHeight, 2) + 8
break
case 7:
c2.nPosX = c1.nPosX + c1.nWidth
c2.nPosY = c1.nPosY
if (a4 === 1) c2.nPosY += idiv(c2.nHeight, 2) + 8
break
default:
return
}
}
//D2Common.0x6FD81720
function sub_6FD81720(d: D2DrlgLevelLinkDataStrc): boolean {
const it = d.nIteration
let nRand2 = 0
if (d.nRand[R(1, it)] === -1) {
d.nRand[R(1, it)] = SEED_RollRandomNumber(d.pSeed) & 3
d.nRand[R(0, it)] = d.nRand[R(1, it)]!
d.nRand[R(3, it)] = SEED_RollRandomNumber(d.pSeed) & 1
nRand2 = d.nRand[R(3, it)]!
} else {
const nRand0 = (d.nRand[R(2, it)]! + d.nRand[R(0, it)]!) % 4
nRand2 = (d.nRand[R(2, it)]! + 1) % 2
if (nRand0 === d.nRand[R(1, it)] && nRand2 === d.nRand[R(3, it)]) return false
d.nRand[R(0, it)] = nRand0
}
d.nRand[R(2, it)] = nRand2
if (d.nRand[R(2, it)] === 1) {
sub_6FD81430(d.pLevelCoord[d.pLink[it]!.nLevelLink]!, d.pLevelCoord[it]!, d.nRand[R(0, it)]!, 2)
} else {
sub_6FD81850(d.pLevelCoord[d.pLink[it]!.nLevelLink]!, d.pLevelCoord[it]!, d.nRand[R(0, it)]!, 2)
}
return true
}
//D2Common.0x6FD81850
function sub_6FD81850(c1: D2DrlgCoordStrc, c2: D2DrlgCoordStrc, a3: number, a4: number): void {
switch (a3) {
case 0:
c2.nPosX = c1.nPosX + c1.nWidth - c2.nWidth
c2.nPosY = c1.nPosY + c1.nHeight
if (a4 === 1) c2.nPosX += 16
break
case 1:
c2.nPosX = c1.nPosX - c2.nWidth
c2.nPosY = c1.nPosY + c1.nHeight - c2.nHeight
if (a4 === 1) c2.nPosY += 16
else if (a4 === 2) c2.nPosY -= 8
break
case 2:
c2.nPosX = c1.nPosX
c2.nPosY = c1.nPosY - c2.nHeight
if (a4 === 1) c2.nPosX -= 16
break
case 3:
c2.nPosX = c1.nPosX + c1.nWidth
c2.nPosY = c1.nPosY
switch (a4) {
case 1:
c2.nPosY -= 16
break
case 2:
c2.nPosY += 8
break
case 3:
c2.nPosY -= 8
break
default:
break
}
break
default:
return
}
}
//D2Common.0x6FD81950
function sub_6FD81950(d: D2DrlgLevelLinkDataStrc): boolean {
const it = d.nIteration
let nRand2 = 0
if (d.nRand[R(1, it)] === -1) {
d.nRand[R(1, it)] = SEED_RollRandomNumber(d.pSeed) & 3
d.nRand[R(0, it)] = d.nRand[R(1, it)]!
d.nRand[R(3, it)] = SEED_RollRandomNumber(d.pSeed) & 1
nRand2 = d.nRand[R(3, it)]!
} else {
const nRand0 = (d.nRand[R(2, it)]! + d.nRand[R(0, it)]!) % 4
nRand2 = (d.nRand[R(2, it)]! + 1) % 2
if (nRand0 === d.nRand[R(1, it)] && nRand2 === d.nRand[R(3, it)]) return false
d.nRand[R(0, it)] = nRand0
}
d.nRand[R(2, it)] = nRand2
d.pLevelCoord[it]!.nWidth = d.nRand[R(0, it)]! % 2 !== 0 ? 96 : 56
d.pLevelCoord[it]!.nHeight = d.nRand[R(0, it)]! % 2 !== 0 ? 56 : 96
if (d.nRand[R(2, it)] === 1) {
sub_6FD81430(d.pLevelCoord[d.pLink[it]!.nLevelLink]!, d.pLevelCoord[it]!, d.nRand[R(0, it)]!, 1)
} else {
sub_6FD81850(d.pLevelCoord[d.pLink[it]!.nLevelLink]!, d.pLevelCoord[it]!, d.nRand[R(0, it)]!, 1)
}
return true
}
//D2Common.0x6FD81AD0
function sub_6FD81AD0(d: D2DrlgLevelLinkDataStrc): boolean {
const it = d.nIteration
d.nRand[R(1, it)] = 0
d.nRand[R(0, it)] = d.nRand[R(1, it)]!
sub_6FD81430(d.pLevelCoord[d.pLink[it]!.nLevelLink]!, d.pLevelCoord[it]!, d.nRand[R(0, it)]!, 0)
return true
}
//D2Common.0x6FD81B30
function sub_6FD81B30(d: D2DrlgLevelLinkDataStrc): boolean {
const it = d.nIteration
if (d.nRand[R(1, it)] === -1) {
d.nRand[R(1, it)] = (SEED_RollRandomNumber(d.pSeed) & 1) + 1
d.nRand[R(0, it)] = d.nRand[R(1, it)]!
} else {
const nRand = 2 - (d.nRand[R(0, it)] !== 1 ? 1 : 0)
if (nRand === d.nRand[R(1, it)]) return false
d.nRand[R(0, it)] = nRand
}
if (d.nRand[R(0, it)] === 1) {
sub_6FD81430(d.pLevelCoord[d.pLink[it]!.nLevelLink]!, d.pLevelCoord[it]!, 1, 0)
} else {
sub_6FD81850(d.pLevelCoord[d.pLink[it]!.nLevelLink]!, d.pLevelCoord[it]!, d.nRand[R(0, it)]!, 0)
}
return true
}
//D2Common.0x6FD81BF0
function sub_6FD81BF0(d: D2DrlgLevelLinkDataStrc): boolean {
const it = d.nIteration
if (d.nRand[R(1, it)] === -1) {
d.nRand[R(1, it)] = SEED_RollRandomNumber(d.pSeed) & 7
d.nRand[R(0, it)] = d.nRand[R(1, it)]!
} else {
const nRand = (d.nRand[R(0, it)]! + 1) % 8
if (nRand === d.nRand[R(1, it)]) return false
d.nRand[R(0, it)] = nRand
}
sub_6FD815E0(d.pLevelCoord[d.pLink[it]!.nLevelLink]!, d.pLevelCoord[it]!, d.nRand[R(0, it)]!, 0)
return true
}
//D2Common.0x6FD81CA0
function sub_6FD81CA0(d: D2DrlgLevelLinkDataStrc): boolean {
const it = d.nIteration
d.nRand[R(1, it)] = 3
d.nRand[R(0, it)] = d.nRand[R(1, it)]!
if (!(SEED_RollRandomNumber(d.pSeed) & 1)) {
sub_6FD81850(d.pLevelCoord[d.pLink[it]!.nLevelLink]!, d.pLevelCoord[it]!, d.nRand[R(0, it)]!, 3)
dword_6FDEA6FC = 0x400000
} else {
sub_6FD81430(d.pLevelCoord[d.pLink[it]!.nLevelLink]!, d.pLevelCoord[it]!, d.nRand[R(0, it)]!, 3)
dword_6FDEA6FC = 0x800000
}
return true
}
//D2Common.0x6FD83EC0 (DrlgOutRoom.cpp)
function DRLGOUTROOM_LinkLevelsByLevelCoords(d: D2DrlgLevelLinkDataStrc): boolean {
const it = d.nIteration
const nRand = SEED_RollRandomNumber(d.pSeed) & 1
d.nRand[R(0, it)] = nRand
d.nRand[R(1, it)] = nRand
if (nRand === 0) {
d.pLevelCoord[it]!.nWidth = 64
d.pLevelCoord[it]!.nHeight = 160
} else if (nRand === 1) {
d.pLevelCoord[it]!.nWidth = 160
d.pLevelCoord[it]!.nHeight = 64
}
const pLinkCoord = d.pLevelCoord[d.pLink[it]!.nLevelLink]!
d.pLevelCoord[it]!.nPosX = pLinkCoord.nPosX - d.pLevelCoord[it]!.nWidth
d.pLevelCoord[it]!.nPosY = pLinkCoord.nHeight - d.pLevelCoord[it]!.nHeight + pLinkCoord.nPosY - 16
return true
}
//D2Common.0x6FD83F70 (DrlgOutRoom.cpp)
function DRLGOUTROOM_LinkLevelsByLevelDef(d: D2DrlgLevelLinkDataStrc): boolean {
const it = d.nIteration
const nRand = SEED_RollRandomNumber(d.pSeed) & 1
d.nRand[R(0, it)] = nRand
d.nRand[R(1, it)] = nRand
if (nRand === 0) {
d.pLevelCoord[it]!.nWidth = 64
d.pLevelCoord[it]!.nHeight = 160
} else if (nRand === 1) {
d.pLevelCoord[it]!.nWidth = 160
d.pLevelCoord[it]!.nHeight = 64
}
const pLevelDefBin = DATATBLS_GetLevelDefRecord(d.env.tables, d.nCurrentLevel)
d.pLevelCoord[it]!.nPosX = pLevelDefBin.dwOffsetX
d.pLevelCoord[it]!.nPosY = pLevelDefBin.dwOffsetY
return true
}
const pOffsetCoords_6FD84010: readonly (readonly [number, number])[] = [
[0, -160],
[-96, -64],
[-64, -96],
[-160, 0],
]
//D2Common.0x6FD84010 (DrlgOutRoom.cpp)
function DRLGOUTROOM_LinkLevelsByOffsetCoords(d: D2DrlgLevelLinkDataStrc): boolean {
const it = d.nIteration
if (d.nRand[R(1, it)] === -1) {
d.nRand[R(1, it)] = SEED_RollRandomNumber(d.pSeed) & 1
d.nRand[R(0, it)] = d.nRand[R(1, it)]!
} else {
const bRand0IsNull = d.nRand[R(0, it)] === 0 ? 1 : 0
if (bRand0IsNull === d.nRand[R(1, it)]) {
return false
}
d.nRand[R(0, it)] = bRand0IsNull
}
if (d.nRand[R(0, it)] === 0) {
d.pLevelCoord[it]!.nWidth = 64
d.pLevelCoord[it]!.nHeight = 160
} else if (d.nRand[R(0, it)] === 1) {
d.pLevelCoord[it]!.nWidth = 160
d.pLevelCoord[it]!.nHeight = 64
}
const nIndex = d.nRand[R(0, it)]! + 2 * d.nRand[R(0, d.pLink[it]!.nLevelLink)]!
const pLinkCoord = d.pLevelCoord[d.pLink[it]!.nLevelLink]!
d.pLevelCoord[it]!.nPosX = pLinkCoord.nPosX + pOffsetCoords_6FD84010[nIndex]![0]
d.pLevelCoord[it]!.nPosY = pLinkCoord.nPosY + pOffsetCoords_6FD84010[nIndex]![1]
return true
}
//D2Common.0x6FDCFE40
const gAct1WildernessDrlgLink = linkTable([
{ pfLinker: sub_6FD81330, nLevel: LEVEL_STONYFIELD, nLevelLink: -1, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81380, nLevel: LEVEL_COLDPLAINS, nLevelLink: 0, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81950, nLevel: LEVEL_BLOODMOOR, nLevelLink: 1, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81720, nLevel: LEVEL_ROGUEENCAMPMENT, nLevelLink: 2, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81380, nLevel: LEVEL_BURIALGROUNDS, nLevelLink: 1, nLevelLinkEx: -1 },
{ pfLinker: null, nLevel: 0, nLevelLink: -1, nLevelLinkEx: -1 },
])
//D2Common.0x6FDCFF30
const gAct1MonasteryDrlgLink = linkTable([
{ pfLinker: sub_6FD81330, nLevel: LEVEL_MOOMOOFARM, nLevelLink: -1, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81330, nLevel: LEVEL_MONASTERYGATE, nLevelLink: -1, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81AD0, nLevel: LEVEL_TAMOEHIGHLAND, nLevelLink: 1, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81380, nLevel: LEVEL_BLACKMARSH, nLevelLink: 2, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81380, nLevel: LEVEL_DARKWOOD, nLevelLink: 3, nLevelLinkEx: -1 },
{ pfLinker: null, nLevel: 0, nLevelLink: -1, nLevelLinkEx: -1 },
])
//D2Common.0x6FDD0020
const gAct2OutdoorDrlgLink = linkTable([
{ pfLinker: sub_6FD81330, nLevel: LEVEL_LUTGHOLEIN, nLevelLink: -1, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81B30, nLevel: LEVEL_ROCKYWASTE, nLevelLink: 0, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81530, nLevel: LEVEL_DRYHILLS, nLevelLink: 1, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81530, nLevel: LEVEL_FAROASIS, nLevelLink: 2, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81530, nLevel: LEVEL_LOSTCITY, nLevelLink: 3, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81BF0, nLevel: LEVEL_VALLEYOFSNAKES, nLevelLink: 4, nLevelLinkEx: -1 },
{ pfLinker: null, nLevel: 0, nLevelLink: -1, nLevelLinkEx: -1 },
])
//D2Common.0x6FDD0110
const gAct2CanyonDrlgLink = linkTable([
{ pfLinker: sub_6FD81330, nLevel: LEVEL_CANYONOFTHEMAGI, nLevelLink: -1, nLevelLinkEx: -1 },
{ pfLinker: null, nLevel: 0, nLevelLink: -1, nLevelLinkEx: -1 },
])
//1.11 : D2Common.0x6FDDC0B8
const gAct5UbersDrlgLink = linkTable([
{ pfLinker: sub_6FD81330, nLevel: LEVEL_PANDEMONIUMRUN2, nLevelLink: -1, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81330, nLevel: LEVEL_PANDEMONIUMFINALE, nLevelLink: -1, nLevelLinkEx: -1 },
{ pfLinker: null, nLevel: 0, nLevelLink: -1, nLevelLinkEx: -1 },
])
//D2Common.0x6FDD0200
const gAct4OutdoorDrlgLink = linkTable([
{ pfLinker: sub_6FD81330, nLevel: LEVEL_THEPANDEMONIUMFORTRESS, nLevelLink: -1, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81CA0, nLevel: LEVEL_OUTERSTEPPES, nLevelLink: 0, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81380, nLevel: LEVEL_PLAINSOFDESPAIR, nLevelLink: 1, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81380, nLevel: LEVEL_CITYOFTHEDAMNED, nLevelLink: 2, nLevelLinkEx: -1 },
{ pfLinker: null, nLevel: 0, nLevelLink: -1, nLevelLinkEx: -1 },
])
//D2Common.0x6FDD02F0
const gAct4ChaosSanctumDrlgLink = linkTable([
{ pfLinker: sub_6FD81330, nLevel: LEVEL_CHAOSSANCTUM, nLevelLink: -1, nLevelLinkEx: -1 },
{ pfLinker: null, nLevel: 0, nLevelLink: -1, nLevelLinkEx: -1 },
])
//1.10f: D2Common.0x6FDD03E0
const gAct5OutdoorDrlgLink = linkTable([
{ pfLinker: sub_6FD81330, nLevel: LEVEL_HARROGATH, nLevelLink: -1, nLevelLinkEx: -1 },
{ pfLinker: sub_6FD81330, nLevel: LEVEL_BLOODYFOOTHILLS, nLevelLink: 0, nLevelLinkEx: -1 },
{ pfLinker: DRLGOUTROOM_LinkLevelsByLevelCoords, nLevel: LEVEL_ID_ACT5_BARRICADE_1, nLevelLink: 1, nLevelLinkEx: -1 },
{ pfLinker: DRLGOUTROOM_LinkLevelsByOffsetCoords, nLevel: LEVEL_ARREATPLATEAU, nLevelLink: 2, nLevelLinkEx: -1 },
{ pfLinker: null, nLevel: 0, nLevelLink: -1, nLevelLinkEx: -1 },
])
//1.10f: D2Common.0x6FDD04D0
const gAct5TundraDrlgLink = linkTable([
{ pfLinker: DRLGOUTROOM_LinkLevelsByLevelDef, nLevel: LEVEL_TUNDRAWASTELANDS, nLevelLink: -1, nLevelLinkEx: -1 },
{ pfLinker: null, nLevel: 0, nLevelLink: -1, nLevelLinkEx: -1 },
])
//D2Common.0x6FD81D60 (1.10)
export function DRLGOUTPLACE_CreateLevelConnections(pDrlg: D2DrlgStrc, nAct: number): void {
switch (nAct) {
case ACT_I:
sub_6FD823C0(pDrlg, gAct1WildernessDrlgLink, sub_6FD82050, sub_6FD82360)
sub_6FD823C0(pDrlg, gAct1MonasteryDrlgLink, sub_6FD82130, sub_6FD82360)
sub_6FD82750(pDrlg, LEVEL_ROGUEENCAMPMENT, LEVEL_BURIALGROUNDS)
break
case ACT_II:
sub_6FD823C0(pDrlg, gAct2OutdoorDrlgLink, DRLGOUTPLACE_LinkAct2Outdoors, null)
sub_6FD823C0(pDrlg, gAct2CanyonDrlgLink, DRLGOUTPLACE_LinkAct2Canyon, null)
sub_6FD82750(pDrlg, LEVEL_LUTGHOLEIN, LEVEL_CANYONOFTHEMAGI)
break
case ACT_III: {
let pLevel = DRLG_GetLevel(pDrlg, LEVEL_KURASTDOCKTOWN)
const pTownLevelDef = DATATBLS_GetLevelDefRecord(pDrlg.env.tables, LEVEL_KURASTDOCKTOWN)
pLevel.pLevelCoords.nPosX = pTownLevelDef.dwOffsetX
pLevel.pLevelCoords.nPosY = pTownLevelDef.dwOffsetY
pLevel.pLevelCoords.nWidth = pTownLevelDef.dwSizeX[pDrlg.nDifficulty]!
pLevel.pLevelCoords.nHeight = pTownLevelDef.dwSizeY[pDrlg.nDifficulty]!
pLevel = DRLG_GenerateJungles(pLevel)
let nPosY = 0
for (let i = LEVEL_LOWERKURAST; i <= LEVEL_TRAVINCAL; i += 1) {
const pLevelDefBinRecord = DATATBLS_GetLevelDefRecord(pDrlg.env.tables, i)
const nSizeX = pLevelDefBinRecord.dwSizeX[pLevel.pDrlg.nDifficulty]!
const nSizeY = pLevelDefBinRecord.dwSizeY[pLevel.pDrlg.nDifficulty]!
nPosY -= nSizeY
const pAdjacentLevel = DRLG_GetLevel(pLevel.pDrlg, i)
pAdjacentLevel.pLevelCoords.nPosX =
idiv(pLevel.pLevelCoords.nWidth, 2) + pLevel.pLevelCoords.nPosX - idiv(nSizeX, 2)
pAdjacentLevel.pLevelCoords.nWidth = nSizeX
pAdjacentLevel.pLevelCoords.nPosY = nPosY + pLevel.pLevelCoords.nPosY
pAdjacentLevel.pLevelCoords.nHeight = nSizeY
}
sub_6FD826D0(pDrlg, LEVEL_KURASTDOCKTOWN, LEVEL_TRAVINCAL)
sub_6FD82750(pDrlg, LEVEL_KURASTDOCKTOWN, LEVEL_TRAVINCAL)
break
}
case ACT_IV: {
sub_6FD823C0(pDrlg, gAct4OutdoorDrlgLink, DRLGOUTPLACE_LinkAct4Outdoors, null)
sub_6FD823C0(pDrlg, gAct4ChaosSanctumDrlgLink, DRLGOUTPLACE_LinkAct4ChaosSanctum, null)
const pLevel = DRLG_GetLevel(pDrlg, LEVEL_OUTERSTEPPES)
pLevel.pOutdoors!.dwFlags = (pLevel.pOutdoors!.dwFlags | dword_6FDEA6FC) >>> 0
sub_6FD82750(pDrlg, LEVEL_THEPANDEMONIUMFORTRESS, LEVEL_CITYOFTHEDAMNED)
break
}
case ACT_V:
sub_6FD823C0(pDrlg, gAct5OutdoorDrlgLink, null, null)
sub_6FD823C0(pDrlg, gAct5TundraDrlgLink, null, null)
sub_6FD826D0(pDrlg, LEVEL_ID_ACT5_BARRICADE_1, LEVEL_ARREATPLATEAU)
sub_6FD82750(pDrlg, LEVEL_ID_ACT5_BARRICADE_1, LEVEL_ARREATPLATEAU)
sub_6FD826D0(pDrlg, LEVEL_BLOODYFOOTHILLS, LEVEL_ID_ACT5_BARRICADE_1)
sub_6FD826D0(pDrlg, LEVEL_HARROGATH, LEVEL_BLOODYFOOTHILLS)
sub_6FD823C0(pDrlg, gAct5UbersDrlgLink, DRLGOUTPLACE_LinkAct5Ubers, null)
break
default:
return
}
}
//D2Common.0x6FD82050
const dword_6FDD05C0 = [
1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1,
]
function sub_6FD82050(d: D2DrlgLevelLinkDataStrc, nIteration: number): boolean {
const nLevelLink = gAct1WildernessDrlgLink[nIteration]!.nLevelLink
for (let i = 0; i < nIteration; i += 1) {
if (i !== nLevelLink && !DRLG_CheckNotOverlappingUsingManhattanDistance(d.pLevelCoord[nIteration]!, d.pLevelCoord[i]!, 0)) {
return false
}
}
if (gAct1WildernessDrlgLink[nIteration]!.nLevel !== LEVEL_ROGUEENCAMPMENT) {
if (gAct1WildernessDrlgLink[nIteration]!.nLevel === LEVEL_BURIALGROUNDS) {
for (let i = 0; i < 15; i += 1) {
// nRand2[x] with x < 15 is nRand[0][x].
if (i !== nIteration && gAct1WildernessDrlgLink[i]!.nLevelLink === nLevelLink && d.nRand[nIteration] === d.nRand[i]) {
return false
}
}
}
return true
}
const nIndex =
d.nRand[R(0, nIteration)]! + 4 * (d.nRand[R(2, nIteration)]! + 2 * (d.nRand[R(0, nLevelLink)]! + 4 * d.nRand[R(2, nLevelLink)]!))
if (nIndex < 0 || nIndex >= dword_6FDD05C0.length) throw new RangeError(`sub_6FD82050: table index ${nIndex}`)
return dword_6FDD05C0[nIndex] !== 0
}
//D2Common.0x6FD82130
function sub_6FD82130(d: D2DrlgLevelLinkDataStrc, nIteration: number): boolean {
let nCounter = 0
while (nCounter < nIteration) {
if (
nCounter !== gAct1MonasteryDrlgLink[nIteration]!.nLevelLink &&
!DRLG_CheckNotOverlappingUsingManhattanDistance(d.pLevelCoord[nIteration]!, d.pLevelCoord[nCounter]!, 0)
) {
return false
}
nCounter += 1
}
let bResult = true
if (nIteration) {
d.pLevelCoord[0]!.nHeight += 200
d.pLevelCoord[0]!.nPosY -= 200
// nCounter == nIteration here.
bResult = DRLG_CheckNotOverlappingUsingManhattanDistance(d.pLevelCoord[0]!, d.pLevelCoord[nCounter]!, 0)
d.pLevelCoord[0]!.nHeight -= 200
d.pLevelCoord[0]!.nPosY += 200
}
return bResult
}
//D2Common.0x6FD821E0
function DRLGOUTPLACE_LinkAct2Outdoors(d: D2DrlgLevelLinkDataStrc, nIteration: number): boolean {
const nLevelLink = gAct2OutdoorDrlgLink[nIteration]!.nLevelLink
for (let i = 0; i < nIteration; i += 1) {
if (i !== nLevelLink && !DRLG_CheckNotOverlappingUsingManhattanDistance(d.pLevelCoord[nIteration]!, d.pLevelCoord[i]!, 0)) {
return false
}
}
return true
}
//D2Common.0x6FD82240
function DRLGOUTPLACE_LinkAct2Canyon(d: D2DrlgLevelLinkDataStrc, nIteration: number): boolean {
const nLevelLink = gAct2CanyonDrlgLink[nIteration]!.nLevelLink
for (let i = 0; i < nIteration; i += 1) {
if (i !== nLevelLink && !DRLG_CheckNotOverlappingUsingManhattanDistance(d.pLevelCoord[nIteration]!, d.pLevelCoord[i]!, 0)) {
return false
}
}
return true
}
//1.11 : D2Common.0x6FD6C560
function DRLGOUTPLACE_LinkAct5Ubers(d: D2DrlgLevelLinkDataStrc, nIteration: number): boolean {
const nLevelLink = gAct5UbersDrlgLink[nIteration]!.nLevelLink
for (let i = 0; i < nIteration; i += 1) {
if (i !== nLevelLink && !DRLG_CheckNotOverlappingUsingManhattanDistance(d.pLevelCoord[nIteration]!, d.pLevelCoord[i]!, 0)) {
return false
}
}
return true
}
//D2Common.0x6FD822A0
function DRLGOUTPLACE_LinkAct4Outdoors(d: D2DrlgLevelLinkDataStrc, nIteration: number): boolean {
const nLevelLink = gAct4OutdoorDrlgLink[nIteration]!.nLevelLink
for (let i = 0; i < nIteration; i += 1) {
if (i !== nLevelLink && !DRLG_CheckNotOverlappingUsingManhattanDistance(d.pLevelCoord[nIteration]!, d.pLevelCoord[i]!, 0)) {
return false
}
}
return true
}
//D2Common.0x6FD82300
function DRLGOUTPLACE_LinkAct4ChaosSanctum(d: D2DrlgLevelLinkDataStrc, nIteration: number): boolean {
const nLevelLink = gAct4ChaosSanctumDrlgLink[nIteration]!.nLevelLink
for (let i = 0; i < nIteration; i += 1) {
if (i !== nLevelLink && !DRLG_CheckNotOverlappingUsingManhattanDistance(d.pLevelCoord[nIteration]!, d.pLevelCoord[i]!, 0)) {
return false
}
}
return true
}
//D2Common.0x6FD82360
const stru_6FDD06C0: readonly (readonly [number, number, number, number, number, number])[] = [
// nLevelId, nExcludedLevel1, nExcludedLevel2, nRand, nNextRand, nFlags
[0, LEVEL_BLOODMOOR, LEVEL_COLDPLAINS, 1, 0, 0x04],
[0, LEVEL_BLOODMOOR, LEVEL_COLDPLAINS, 2, 3, 0x04],
[0, LEVEL_COLDPLAINS, LEVEL_BURIALGROUNDS, 2, 1, 0x08],
[0, LEVEL_COLDPLAINS, LEVEL_BURIALGROUNDS, 3, 0, 0x08],
[0, LEVEL_COLDPLAINS, LEVEL_BURIALGROUNDS, 1, 1, 0x10],
[0, LEVEL_COLDPLAINS, LEVEL_BURIALGROUNDS, 3, 3, 0x10],
[LEVEL_BLOODMOOR, 0, 0, 0, 0, 0x08],
[LEVEL_BLOODMOOR, 0, 0, 2, 2, 0x08],
[LEVEL_BLOODMOOR, 0, 0, 3, 0, 0x08],
[LEVEL_BLOODMOOR, 0, 0, 3, 2, 0x08],
[LEVEL_BLOODMOOR, 0, 0, 0, 1, 0x400],
[LEVEL_BLOODMOOR, 0, 0, 1, 1, 0x400],
[LEVEL_BLOODMOOR, 0, 0, 2, 1, 0x200],
[LEVEL_BLOODMOOR, 0, 0, 2, 2, 0x80],
[LEVEL_BLOODMOOR, 0, 0, 3, 2, 0x100],
]
function sub_6FD82360(pLevel: D2DrlgLevelStrc, nIteration: number, pRand: Int32Array): void {
if (pLevel.nDrlgType !== DRLGTYPE_OUTDOOR) return
for (const [nLevelId, nExcludedLevel1, nExcludedLevel2, nRand, nNextRand, nFlags] of stru_6FDD06C0) {
if (pLevel.nLevelId === nLevelId || !nLevelId) {
if (pLevel.nLevelId !== nExcludedLevel1 && pLevel.nLevelId !== nExcludedLevel2 && pRand[nIteration] === nRand) {
if (pRand[nIteration + 1] === nNextRand) {
pLevel.pOutdoors!.dwFlags = (pLevel.pOutdoors!.dwFlags | nFlags) >>> 0
}
}
}
}
}
/** The inlined `lSeed = nHighSeed + 1791398085i64 * nLowSeed` of sub_6FD823C0 (no SEED_* call, no trace). */
function advanceUntraced(pSeed: D2SeedStrc): void {
D2SEED_AdvanceInline(pSeed)
}
//D2Common.0x6FD823C0
function sub_6FD823C0(
pDrlg: D2DrlgStrc,
pDrlgLink: readonly D2DrlgLinkStrc[],
a3: ((d: D2DrlgLevelLinkDataStrc, nIteration: number) => boolean) | null,
a4: ((pLevel: D2DrlgLevelStrc, nIteration: number, pRand: Int32Array) => void) | null,
): void {
const pLevelLinkData: D2DrlgLevelLinkDataStrc = {
env: pDrlg.env,
pSeed: { lo: pDrlg.pSeed.lo, hi: pDrlg.pSeed.hi },
pLevelCoord: Array.from({ length: 15 }, () => newCoord()),
pLink: pDrlgLink,
nRand: new Int32Array(60).fill(-1),
nIteration: 0,
nCurrentLevel: 0,
}
for (let i = 0; i < 15 && pDrlgLink[i]!.nLevel; i += 1) {
const pLevelDefBinRecord = DATATBLS_GetLevelDefRecord(pDrlg.env.tables, pDrlgLink[i]!.nLevel)
pLevelLinkData.pLevelCoord[i]!.nWidth = pLevelDefBinRecord.dwSizeX[pDrlg.nDifficulty]!
pLevelLinkData.pLevelCoord[i]!.nHeight = pLevelDefBinRecord.dwSizeY[pDrlg.nDifficulty]!
}
let nCounter = 0
let nGuard = 0
while (pDrlgLink[nCounter]!.nLevel) {
if (++nGuard > 1_000_000) throw new Error('sub_6FD823C0: link solver does not terminate')
pLevelLinkData.nIteration = nCounter
pLevelLinkData.nCurrentLevel = pDrlgLink[nCounter]!.nLevel
if (pDrlgLink[nCounter]!.pfLinker!(pLevelLinkData)) {
if (!a3 || a3(pLevelLinkData, nCounter)) nCounter += 1
} else {
pLevelLinkData.nRand[R(0, nCounter)] = -1
pLevelLinkData.nRand[R(1, nCounter)] = -1
pLevelLinkData.nRand[R(2, nCounter)] = -1
pLevelLinkData.nRand[R(3, nCounter)] = -1
nCounter -= 1
if (nCounter < 0) throw new Error('sub_6FD823C0: backtracked past the first link (C reads pDrlgLink[-1])')
}
}
for (let i = 0; i < 15 && pDrlgLink[i]!.nLevel; i += 1) {
const nVis = pDrlgLink[i]!.nLevelLink !== -1 ? pDrlgLink[pDrlgLink[i]!.nLevelLink]!.nLevel : 0
const nVisEx = pDrlgLink[i]!.nLevelLinkEx !== -1 ? pDrlgLink[pDrlgLink[i]!.nLevelLinkEx]!.nLevel : 0
const pLevel = DRLG_GetLevel(pDrlg, pDrlgLink[i]!.nLevel)
pLevel.pLevelCoords.nPosX = pLevelLinkData.pLevelCoord[i]!.nPosX
pLevel.pLevelCoords.nPosY = pLevelLinkData.pLevelCoord[i]!.nPosY
pLevel.pLevelCoords.nWidth = pLevelLinkData.pLevelCoord[i]!.nWidth
pLevel.pLevelCoords.nHeight = pLevelLinkData.pLevelCoord[i]!.nHeight
if (pLevel.nDrlgType === DRLGTYPE_PRESET) {
if (pLevel.nLevelId === LEVEL_ROGUEENCAMPMENT) {
pLevel.pPreset!.nDirection = pLevelLinkData.nRand[R(0, i)]!
} else if (pLevel.nLevelId === LEVEL_LUTGHOLEIN) {
pLevel.pPreset!.nDirection = pLevelLinkData.nRand[R(0, i + 1)]!
}
}
if (pLevel.nLevelId === LEVEL_BLACKMARSH) {
const pPresetInfo = DRLG_GetLevel(pDrlg, LEVEL_OUTERCLOISTER).pPreset!
if (pLevelLinkData.nRand[R(0, i)] === 1) {
advanceUntraced(pDrlg.pSeed)
pPresetInfo.nDirection = 2 - ((pDrlg.pSeed.lo & 1) !== 0 ? 1 : 0)
} else if (pLevelLinkData.nRand[R(0, i)] === 3) {
advanceUntraced(pDrlg.pSeed)
pPresetInfo.nDirection = ~pDrlg.pSeed.lo & 1
}
}
if (a4) a4(pLevel, i, pLevelLinkData.nRand)
if (pDrlg.nAct !== ACT_V) {
if (nVis) {
const pDrlgWarp1 = DRLG_GetDrlgWarpFromLevelId(pDrlg, pDrlgLink[i]!.nLevel)
const pDrlgWarp2 = DRLG_GetDrlgWarpFromLevelId(pDrlg, nVis)
DRLG_SetWarpId(pDrlgWarp1, nVis, -1, -1)
DRLG_SetWarpId(pDrlgWarp2, pDrlgLink[i]!.nLevel, -1, -1)
}
if (nVisEx) {
const pDrlgWarp1 = DRLG_GetDrlgWarpFromLevelId(pDrlg, pDrlgLink[i]!.nLevel)
const pDrlgWarp2 = DRLG_GetDrlgWarpFromLevelId(pDrlg, nVisEx)
DRLG_SetWarpId(pDrlgWarp1, nVisEx, -1, -1)
DRLG_SetWarpId(pDrlgWarp2, pDrlgLink[i]!.nLevel, -1, -1)
}
}
}
}
//D2Common.0x6FD826D0
function sub_6FD826D0(pDrlg: D2DrlgStrc, nStartId: number, nEndId: number): void {
for (let i = nStartId; i <= nEndId; i += 1) {
const pLevel = DRLG_GetLevel(pDrlg, i)
const pDrlgWarp = DRLG_GetDrlgWarpFromLevelId(pDrlg, i)
for (let j = nStartId; j <= nEndId; j += 1) {
if (
i !== j &&
DRLG_CheckOverlappingWithOrthogonalMargin(pLevel.pLevelCoords, DRLG_GetLevel(pDrlg, j).pLevelCoords, -1)
) {
DRLG_SetWarpId(pDrlgWarp, j, -1, -1)
}
}
}
}
//D2Common.0x6FD82750
function sub_6FD82750(pDrlg: D2DrlgStrc, nStartId: number, nEndId: number): void {
for (let i = nStartId; i <= nEndId; i += 1) {
const pLevel = DRLG_GetLevel(pDrlg, i)
if (pLevel.nDrlgType === DRLGTYPE_OUTDOOR) {
const pVisArray = DRLGROOM_GetVisArrayFromLevelId(pDrlg, i)
const pWarpIdArray = DRLGWARP_GetWarpIdArrayFromLevelId(pDrlg, i)
for (let j = 0; j < 8; j += 1) {
if (pVisArray[j] && pWarpIdArray[j] === -1) {
const pWarpLevel = DRLG_GetLevel(pLevel.pDrlg, pVisArray[j]!)
pLevel.pOutdoors!.pRoomData = DRLGROOM_AddOrth(
pLevel.pOutdoors!.pRoomData,
pWarpLevel,
DRLG_GetDirectionFromCoordinates(pLevel.pLevelCoords, pWarpLevel.pLevelCoords),
pWarpLevel.nDrlgType === DRLGTYPE_PRESET,
)
}
}
}
}
}
// ---------------------------------------------------------------------------------------------
// Act III Jungle layout (DrlgOutPlace.cpp:1926-2616)
// ---------------------------------------------------------------------------------------------
const JUNGLE_MAX_ATTACH = 3
const JUNGLE_PRESET2_ATTACH_POINT = 2
const JUNGLE_FLAG_LEFT_ = 0b0001
const JUNGLE_FLAG_RIGHT_ = 0b0010
const JUNGLE_FLAG_BOTTOM_ = 0b0100
const JUNGLE_FLAG_TOP_ = 0b1000
interface D2JungleStrc {
pDrlgCoord: D2DrlgCoordStrc
field_10: number
nBranch: number
pBasedOnJungle: D2JungleStrc | null
pJungleBranches: (D2JungleStrc | null)[]
nPresetsBlocksX: number
nPresetsBlocksY: number
pJungleDefs: Int32Array | null
nJungleDefs: number
}
function newJungleStrc(): D2JungleStrc {
return {
pDrlgCoord: newCoord(),
field_10: 0,
nBranch: 0,
pBasedOnJungle: null,
pJungleBranches: [null, null, null],
nPresetsBlocksX: 0,
nPresetsBlocksY: 0,
pJungleDefs: null,
nJungleDefs: 0,
}
}
//D2Common.0x6FDD0828
const gJunglePresets: readonly number[] = [
LVLPREST_NONE, LVLPREST_ACT3_JUNGLE_W_E, LVLPREST_ACT3_JUNGLE_W_S, LVLPREST_ACT3_JUNGLE_W_N,
LVLPREST_ACT3_JUNGLE_E_W, LVLPREST_NONE, LVLPREST_ACT3_JUNGLE_E_S, LVLPREST_ACT3_JUNGLE_E_N,
LVLPREST_NONE, LVLPREST_NONE, LVLPREST_ACT3_JUNGLE_EW_S, LVLPREST_ACT3_JUNGLE_EW_N,
LVLPREST_ACT3_JUNGLE_S_W, LVLPREST_ACT3_JUNGLE_S_E, LVLPREST_NONE, LVLPREST_ACT3_JUNGLE_S_N,
LVLPREST_NONE, LVLPREST_ACT3_JUNGLE_SW_E, LVLPREST_NONE, LVLPREST_ACT3_JUNGLE_SW_N,
LVLPREST_ACT3_JUNGLE_SE_W, LVLPREST_NONE, LVLPREST_NONE, LVLPREST_ACT3_JUNGLE_SE_N,
LVLPREST_NONE, LVLPREST_NONE, LVLPREST_NONE, LVLPREST_ACT3_JUNGLE_SEW_N,
LVLPREST_ACT3_JUNGLE_N_W, LVLPREST_ACT3_JUNGLE_N_E, LVLPREST_ACT3_JUNGLE_N_S, LVLPREST_NONE,
LVLPREST_NONE, LVLPREST_ACT3_JUNGLE_NW_E, LVLPREST_ACT3_JUNGLE_NW_S, LVLPREST_NONE,
LVLPREST_ACT3_JUNGLE_NE_W, LVLPREST_NONE, LVLPREST_ACT3_JUNGLE_NE_S, LVLPREST_NONE,
LVLPREST_NONE, LVLPREST_NONE, LVLPREST_ACT3_JUNGLE_NEW_S, LVLPREST_NONE,
LVLPREST_ACT3_JUNGLE_NS_W, LVLPREST_ACT3_JUNGLE_NS_E, LVLPREST_NONE, LVLPREST_NONE,
LVLPREST_NONE, LVLPREST_ACT3_JUNGLE_NSW_E, LVLPREST_NONE, LVLPREST_NONE,
LVLPREST_ACT3_JUNGLE_NSE_W, LVLPREST_NONE, LVLPREST_NONE, LVLPREST_NONE,
LVLPREST_NONE, LVLPREST_NONE, LVLPREST_NONE, LVLPREST_NONE,
]
//D2Common.0x6FDD0924
const gSpiderForestPresets: readonly number[] = [
LVLPREST_NONE, LVLPREST_ACT3_CLEARING_WEBBY_W, LVLPREST_ACT3_CLEARING_WEBBY_E, LVLPREST_ACT3_CLEARING_WEBBY_EW,
LVLPREST_ACT3_CLEARING_WEBBY_S, LVLPREST_ACT3_CLEARING_WEBBY_SW, LVLPREST_ACT3_CLEARING_WEBBY_SE, LVLPREST_NONE,
LVLPREST_ACT3_CLEARING_WEBBY_N, LVLPREST_ACT3_CLEARING_WEBBY_NW, LVLPREST_ACT3_CLEARING_WEBBY_NE, LVLPREST_NONE,
LVLPREST_ACT3_CLEARING_WEBBY_NS, LVLPREST_NONE, LVLPREST_NONE, LVLPREST_NONE,
LVLPREST_NONE,
]
const nPresetBlocksDimensions = 32
function DRLG_GenerateJunglesAttachPoints(
pDrlg: D2DrlgStrc,
tJungles: D2JungleStrc[],
nMinX: number,
nMinY: number,
nSpiderForestLevelSizeX: number,
nSpiderForestLevelSizeY: number,
nPresetsWidth: number,
nPresetsHeight: number,
pPreset0: Int32Array,
pPreset1: Int32Array,
pPreset2: Int32Array,
pLevelPresetId: Int32Array,
): void {
pPreset0.fill(0, 0, nPresetsWidth * nPresetsHeight)
pPreset1.fill(0, 0, nPresetsWidth * nPresetsHeight)
pPreset2.fill(0, 0, nPresetsWidth * nPresetsHeight)
pLevelPresetId.fill(0, 0, nPresetsWidth * nPresetsHeight)
const nSpiderForestLevelPresetsBlocksSizeX = idiv(nSpiderForestLevelSizeX, nPresetBlocksDimensions)
const nSpiderForestLevelPresetsBlocksSizeY = idiv(nSpiderForestLevelSizeY, nPresetBlocksDimensions)
for (let nJungleIdx = 0; nJungleIdx < JUNGLE_MAX_ATTACH; nJungleIdx += 1) {
const tCurrentJungle = tJungles[nJungleIdx]!
const nJungleOffsetX = tCurrentJungle.pDrlgCoord.nPosX - nMinX
const nJungleOffsetY = tCurrentJungle.pDrlgCoord.nPosY - nMinY
tCurrentJungle.nPresetsBlocksX = (((nJungleOffsetX & 0x1f) + nJungleOffsetX) >> 5) + 1
tCurrentJungle.nPresetsBlocksY = (((nJungleOffsetY & 0x1f) + nJungleOffsetY) >> 5) + 1
tCurrentJungle.pJungleDefs = new Int32Array(
nSpiderForestLevelPresetsBlocksSizeX * nSpiderForestLevelPresetsBlocksSizeY,
)
let bLineStartWithOffset: boolean
if (nJungleIdx !== 0) {
bLineStartWithOffset = tCurrentJungle.field_10 % 2 !== 0
} else {
bLineStartWithOffset = SEED_RollLimitedRandomNumber(pDrlg.pSeed, 2) !== 0
}
let nJungleAttachPoints = 0
while (nJungleAttachPoints < 2) {
let nPresetBlockRowOffset = tCurrentJungle.nPresetsBlocksX + nPresetsWidth * tCurrentJungle.nPresetsBlocksY
for (let nPresetBlockY = 0; nPresetBlockY < nSpiderForestLevelPresetsBlocksSizeY; nPresetBlockY += 1) {
for (let nPresetBlockX = 0; nPresetBlockX < nSpiderForestLevelPresetsBlocksSizeX; nPresetBlockX += 1) {
const nPresetBlockIndex = nPresetBlockRowOffset + nPresetBlockX
pPreset0[nPresetBlockIndex] = nJungleIdx + 1
pPreset1[nPresetBlockIndex] = 0
pPreset2[nPresetBlockIndex] = 0
pLevelPresetId[nPresetBlockIndex] = 0
}
nPresetBlockRowOffset += nPresetsWidth
}
const nFirstPresetsBlockY = tCurrentJungle.nPresetsBlocksY
const nLastPresetsBlockY = nFirstPresetsBlockY + nSpiderForestLevelPresetsBlocksSizeY - 1
if (nJungleIdx !== 0) {
const nFirstPresetsBlockIndex = nLastPresetsBlockY * nPresetsWidth + tCurrentJungle.nPresetsBlocksX
pPreset2[nFirstPresetsBlockIndex + (bLineStartWithOffset ? 1 : 0)] = 1
pPreset2[nFirstPresetsBlockIndex + (!bLineStartWithOffset ? 1 : 0)] = JUNGLE_PRESET2_ATTACH_POINT
}
for (let nBranchIdx = 0; nBranchIdx < tCurrentJungle.nBranch; nBranchIdx += 1) {
const pJungleBranch = tCurrentJungle.pJungleBranches[nBranchIdx]!
let nPresetOffsetX = 0
let nPresetOffsetY = 0
switch (pJungleBranch.field_10) {
case 0:
break
case 1:
nPresetOffsetY = 3
break
case 2:
nPresetOffsetX = 1
nPresetOffsetY = 3
break
case 3:
nPresetOffsetY = 1
break
case 4:
nPresetOffsetX = 1
nPresetOffsetY = 1
break
default:
throw new Error(`DRLG_GenerateJunglesAttachPoints: unreachable field_10=${pJungleBranch.field_10}`)
}
pPreset2[
tCurrentJungle.nPresetsBlocksX +
nPresetOffsetX +
nPresetsWidth * (tCurrentJungle.nPresetsBlocksY + nPresetOffsetY)
] = 1
}
nJungleAttachPoints = nJungleIdx !== 0 ? 1 : 0
const nCurrentPresetsBlockX = tCurrentJungle.nPresetsBlocksX
let i = 20 * (5 * nJungleIdx + 5)
let nColumnSize = 0
for (let nCurrentPresetsBlockY = nLastPresetsBlockY; nCurrentPresetsBlockY >= nFirstPresetsBlockY; ) {
const nCurrentPresetsBlockRowOffset = nCurrentPresetsBlockY * nPresetsWidth
pPreset1[nCurrentPresetsBlockRowOffset + nCurrentPresetsBlockX + (bLineStartWithOffset ? 1 : 0)] = i
i += 1
if (
nColumnSize === 0 ||
nCurrentPresetsBlockY === nFirstPresetsBlockY ||
SEED_RollLimitedRandomNumber(pDrlg.pSeed, 3) !== 0
) {
nColumnSize += 1
if (nColumnSize >= 2 && nCurrentPresetsBlockY > 1) {
const nNextPresetIdx =
nCurrentPresetsBlockRowOffset + nCurrentPresetsBlockX + (!bLineStartWithOffset ? 1 : 0)
if (pPreset2[nNextPresetIdx] === 0) {
pPreset2[nNextPresetIdx] = JUNGLE_PRESET2_ATTACH_POINT
nJungleAttachPoints += 1
}
}
nCurrentPresetsBlockY -= 1
} else {
nColumnSize = 0
bLineStartWithOffset = !bLineStartWithOffset
}
}
}
while (nJungleAttachPoints > 3) {
let bFinished = false
let nAttachPointsFound = 0
const nAttachPointToRemove = SEED_RollLimitedRandomNumber(pDrlg.pSeed, nJungleAttachPoints) >>> 0
for (
let nCurrentPresetsBlockY = 0;
nCurrentPresetsBlockY < nSpiderForestLevelPresetsBlocksSizeY && !bFinished;
nCurrentPresetsBlockY += 1
) {
const nCurrentPresetsBlockOffset =
tCurrentJungle.nPresetsBlocksX +
(tCurrentJungle.nPresetsBlocksY + nCurrentPresetsBlockY) * nPresetsWidth
for (
let nCurrentPresetsBlockX = 0;
nCurrentPresetsBlockX < nSpiderForestLevelPresetsBlocksSizeX && !bFinished;
nCurrentPresetsBlockX += 1
) {
const nCurrentPresetBlockIdx = nCurrentPresetsBlockOffset + nCurrentPresetsBlockX
if (pPreset2[nCurrentPresetBlockIdx] === JUNGLE_PRESET2_ATTACH_POINT) {
if (nAttachPointsFound === nAttachPointToRemove) {
pPreset2[nCurrentPresetBlockIdx] = 0
bFinished = true
nJungleAttachPoints -= 1
}
nAttachPointsFound += 1
}
}
}
}
}
}
function DRLG_JungleComputeConnexity(
_pDrlg: D2DrlgStrc,
nPresetsWidth: number,
nPresetsHeight: number,
pPreset0: Int32Array,
pPreset1: Int32Array,
pPreset2: Int32Array,
pLevelPresetId: Int32Array,
): void {
const nMaxValue = 0x7fffffff
for (let nCurrentPresetY = 0; nCurrentPresetY < nPresetsHeight; nCurrentPresetY += 1) {
for (let nCurrentPresetX = 0; nCurrentPresetX < nPresetsWidth; nCurrentPresetX += 1) {
let nFlags = 0
let nLastDirPreset1Value = nMaxValue
const nCurrentPresetIndex = nCurrentPresetY * nPresetsWidth + nCurrentPresetX
const nRightPresetIndex = nCurrentPresetY * nPresetsWidth + nCurrentPresetX + 1
const nLeftPresetIndex = nCurrentPresetY * nPresetsWidth + nCurrentPresetX - 1
const nTopPresetIndex = (nCurrentPresetY - 1) * nPresetsWidth + nCurrentPresetX
const nBottomPresetIndex = (nCurrentPresetY + 1) * nPresetsWidth + nCurrentPresetX
if (pPreset2[nCurrentPresetIndex] === 1) {
const nPreset0CurrentValue = pPreset0[nCurrentPresetIndex]!
const UpdateAccordingToPreset1 = (nPresetIndex: number, nDirectionFlag: number): void => {
const nPreset1Value = pPreset1[nPresetIndex]!
if (
nPreset1Value !== 0 &&
nPreset1Value < nLastDirPreset1Value &&
pPreset0[nPresetIndex] === nPreset0CurrentValue
) {
nFlags = nDirectionFlag
nLastDirPreset1Value = nPreset1Value
}
}
UpdateAccordingToPreset1(nTopPresetIndex, JUNGLE_FLAG_TOP_)
UpdateAccordingToPreset1(nBottomPresetIndex, JUNGLE_FLAG_BOTTOM_)
UpdateAccordingToPreset1(nRightPresetIndex, JUNGLE_FLAG_RIGHT_)
UpdateAccordingToPreset1(nLeftPresetIndex, JUNGLE_FLAG_LEFT_)
if (nFlags & JUNGLE_FLAG_TOP_) pLevelPresetId[nTopPresetIndex]! |= JUNGLE_FLAG_BOTTOM_
if (nFlags & JUNGLE_FLAG_BOTTOM_) pLevelPresetId[nBottomPresetIndex]! |= JUNGLE_FLAG_TOP_
if (nFlags & JUNGLE_FLAG_RIGHT_) pLevelPresetId[nRightPresetIndex]! |= JUNGLE_FLAG_LEFT_
if (nFlags & JUNGLE_FLAG_LEFT_) pLevelPresetId[nLeftPresetIndex]! |= JUNGLE_FLAG_RIGHT_
if (pPreset2[nTopPresetIndex] === 1 && pPreset0[nTopPresetIndex] !== nPreset0CurrentValue) {
nFlags |= JUNGLE_FLAG_TOP_
}
if (pPreset2[nBottomPresetIndex] === 1 && pPreset0[nBottomPresetIndex] !== nPreset0CurrentValue) {
nFlags |= JUNGLE_FLAG_BOTTOM_
}
if (pPreset2[nRightPresetIndex] === 1 && pPreset0[nRightPresetIndex] !== nPreset0CurrentValue) {
nFlags |= JUNGLE_FLAG_RIGHT_
}
if (pPreset2[nLeftPresetIndex] === 1 && pPreset0[nLeftPresetIndex] !== nPreset0CurrentValue) {
nFlags |= JUNGLE_FLAG_LEFT_
}
}
const nPreset1CurrentValue = pPreset1[nCurrentPresetIndex]!
if (nPreset1CurrentValue) {
if (Math.abs(pPreset1[nTopPresetIndex]! - nPreset1CurrentValue) === 1) {
nFlags |= JUNGLE_FLAG_TOP_
}
if (Math.abs(pPreset1[nBottomPresetIndex]! - nPreset1CurrentValue) === 1) {
nFlags |= JUNGLE_FLAG_BOTTOM_
}
if (Math.abs(pPreset1[nRightPresetIndex]! - nPreset1CurrentValue) === 1) {
nFlags |= JUNGLE_FLAG_RIGHT_
}
if (Math.abs(pPreset1[nLeftPresetIndex]! - nPreset1CurrentValue) === 1) {
nFlags |= JUNGLE_FLAG_LEFT_
}
}
pLevelPresetId[nCurrentPresetIndex]! |= nFlags
}
}
}
function DRLG_JungleUpdateAttachPointsDirections(
pDrlg: D2DrlgStrc,
nPresetsWidth: number,
nPresetsHeight: number,
pPreset0: Int32Array,
pPreset2: Int32Array,
pLevelPresetId: Int32Array,
): boolean {
for (let nCurrentPresetY = 0; nCurrentPresetY < nPresetsHeight; nCurrentPresetY += 1) {
for (let nCurrentPresetX = 0; nCurrentPresetX < nPresetsWidth; nCurrentPresetX += 1) {
const nCurrentPresetIndex = nCurrentPresetY * nPresetsWidth + nCurrentPresetX
let nDirectionBase = SEED_RollLimitedRandomNumber(pDrlg.pSeed, DIRECTION_COUNT) | 0
if (pPreset2[nCurrentPresetIndex] === JUNGLE_PRESET2_ATTACH_POINT) {
const nRightPresetIndex = nCurrentPresetY * nPresetsWidth + nCurrentPresetX + 1
const nLeftPresetIndex = nCurrentPresetY * nPresetsWidth + nCurrentPresetX - 1
const nTopPresetIndex = (nCurrentPresetY - 1) * nPresetsWidth + nCurrentPresetX
const nBottomPresetIndex = (nCurrentPresetY + 1) * nPresetsWidth + nCurrentPresetX
const nPreset0CurrentValue = pPreset0[nCurrentPresetIndex]!
let nCurrentPresetLevelId = pLevelPresetId[nCurrentPresetIndex]!
const bHadNoPresetLeveld = nCurrentPresetLevelId === 0
let bLookAtNextDirection = true
const LookForDirection = (CheckDirectionFunction: (nPresetIndex: number, nFlagToAdd: number) => void): void => {
for (let i = 0; i < DIRECTION_COUNT && bLookAtNextDirection; i += 1) {
switch ((nDirectionBase + i) % DIRECTION_COUNT) {
case DIRECTION_SOUTHWEST:
CheckDirectionFunction(nTopPresetIndex, JUNGLE_FLAG_TOP_)
break
case DIRECTION_NORTHWEST:
CheckDirectionFunction(nBottomPresetIndex, JUNGLE_FLAG_BOTTOM_)
break
case DIRECTION_SOUTHEAST:
CheckDirectionFunction(nRightPresetIndex, JUNGLE_FLAG_RIGHT_)
break
case DIRECTION_NORTHEAST:
CheckDirectionFunction(nLeftPresetIndex, JUNGLE_FLAG_LEFT_)
break
default:
break
}
}
}
bLookAtNextDirection = true
LookForDirection((nPresetIndex, nFlagToAdd) => {
if (
pPreset0[nPresetIndex] === nPreset0CurrentValue &&
pLevelPresetId[nPresetIndex]! !== 0 &&
pLevelPresetId[nPresetIndex]! < 15
) {
nCurrentPresetLevelId |= nFlagToAdd << 4
bLookAtNextDirection = false
}
})
if (!nCurrentPresetLevelId) {
return false
}
if (bHadNoPresetLeveld) {
bLookAtNextDirection = true
LookForDirection((nPresetIndex, nFlagToAdd) => {
if (
pPreset0[nPresetIndex] !== nPreset0CurrentValue &&
pPreset2[nPresetIndex] === JUNGLE_PRESET2_ATTACH_POINT
) {
nCurrentPresetLevelId |= nFlagToAdd << 4
bLookAtNextDirection = false
}
})
bLookAtNextDirection = SEED_RollLimitedRandomNumber(pDrlg.pSeed, 2) !== 0 && bLookAtNextDirection
nDirectionBase = SEED_RollLimitedRandomNumber(pDrlg.pSeed, DIRECTION_COUNT) | 0
LookForDirection((nPresetIndex, nFlagToAdd) => {
if (
pLevelPresetId[nPresetIndex]! !== 0 &&
pLevelPresetId[nPresetIndex]! < 15 &&
(nCurrentPresetLevelId & (nFlagToAdd << 4)) === 0
) {
nCurrentPresetLevelId |= nFlagToAdd << 4
bLookAtNextDirection = false
}
})
}
if ((nCurrentPresetLevelId & (JUNGLE_FLAG_TOP_ << 4)) !== 0) {
pLevelPresetId[nTopPresetIndex]! |= JUNGLE_FLAG_BOTTOM_ << 4
}
if ((nCurrentPresetLevelId & (JUNGLE_FLAG_BOTTOM_ << 4)) !== 0) {
pLevelPresetId[nBottomPresetIndex]! |= JUNGLE_FLAG_TOP_ << 4
}
if ((nCurrentPresetLevelId & (JUNGLE_FLAG_RIGHT_ << 4)) !== 0) {
pLevelPresetId[nRightPresetIndex]! |= JUNGLE_FLAG_LEFT_ << 4
}
if ((nCurrentPresetLevelId & (JUNGLE_FLAG_LEFT_ << 4)) !== 0) {
pLevelPresetId[nLeftPresetIndex]! |= JUNGLE_FLAG_RIGHT_ << 4
}
pPreset2[nCurrentPresetIndex] = 0
pLevelPresetId[nCurrentPresetIndex]! |= nCurrentPresetLevelId
}
}
}
return true
}
function DRLG_JungleNormalizeLevelPresetId(nLevelPresetId: number): number {
const nbDirections = 16
let nPDef = idiv(nLevelPresetId, 1) % nbDirections
if (nPDef !== 0) {
if (nLevelPresetId < nbDirections) {
nPDef += LVLPREST_ACT3_TOWN
} else {
const nPresetDefinitionOffset = 4 * (nPDef - 1)
if ((nLevelPresetId & (JUNGLE_FLAG_LEFT_ << 4)) !== 0) nPDef = gJunglePresets[nPresetDefinitionOffset]!
if ((nLevelPresetId & (JUNGLE_FLAG_RIGHT_ << 4)) !== 0) nPDef = gJunglePresets[nPresetDefinitionOffset + 1]!
if ((nLevelPresetId & (JUNGLE_FLAG_BOTTOM_ << 4)) !== 0) nPDef = gJunglePresets[nPresetDefinitionOffset + 2]!
if ((nLevelPresetId & (JUNGLE_FLAG_TOP_ << 4)) !== 0) nPDef = gJunglePresets[nPresetDefinitionOffset + 3]!
}
} else if (nLevelPresetId >= nbDirections) {
nPDef = gSpiderForestPresets[nLevelPresetId >> 4]!
}
return nPDef
}
//D2Common.0x6FD82820
export function DRLG_GenerateJungles(pLevel: D2DrlgLevelStrc): D2DrlgLevelStrc {
const pDrlg = pLevel.pDrlg
const pSpiderForestLevelDef = DATATBLS_GetLevelDefRecord(pDrlg.env.tables, LEVEL_SPIDERFOREST)
const nDifficulty = pDrlg.nDifficulty
const nSpiderForestLevelSizeX = pSpiderForestLevelDef.dwSizeX[nDifficulty]!
const nSpiderForestLevelSizeY = pSpiderForestLevelDef.dwSizeY[nDifficulty]!
let nMinX = pLevel.pLevelCoords.nPosX
let nMaxX = nSpiderForestLevelSizeX + nMinX
const nMaxY = pLevel.pLevelCoords.nPosY
let nMinY = nMaxY - nSpiderForestLevelSizeY
const tJungles: D2JungleStrc[] = [newJungleStrc(), newJungleStrc(), newJungleStrc()]
sub_6FD83970(pLevel.pLevelCoords, tJungles[0]!, 0, nSpiderForestLevelSizeX, nSpiderForestLevelSizeY)
for (let nJungleAttachIdx = 1; nJungleAttachIdx < JUNGLE_MAX_ATTACH; nJungleAttachIdx += 1) {
const nBaseOn = SEED_RollLimitedRandomNumber(pDrlg.pSeed, nJungleAttachIdx) >>> 0
const pCurrentJungle = tJungles[nJungleAttachIdx]!
sub_6FD83970(
tJungles[nBaseOn]!.pDrlgCoord,
pCurrentJungle,
SEED_RollLimitedRandomNumber(pDrlg.pSeed, 5) | 0,
nSpiderForestLevelSizeX,
nSpiderForestLevelSizeY,
)
let nFirstOverlappingJungle = 0
for (nFirstOverlappingJungle = 0; nFirstOverlappingJungle < nJungleAttachIdx; nFirstOverlappingJungle += 1) {
const levelsOverlaps = !DRLG_CheckNotOverlappingUsingManhattanDistance(
tJungles[nFirstOverlappingJungle]!.pDrlgCoord,
pCurrentJungle.pDrlgCoord,
0,
)
if (levelsOverlaps) {
break
}
}
if (nFirstOverlappingJungle < nJungleAttachIdx) {
nJungleAttachIdx -= 1
continue
}
pCurrentJungle.pBasedOnJungle = tJungles[nBaseOn]!
tJungles[nBaseOn]!.pJungleBranches[tJungles[nBaseOn]!.nBranch] = pCurrentJungle
tJungles[nBaseOn]!.nBranch += 1
if (nMinX > pCurrentJungle.pDrlgCoord.nPosX) nMinX = pCurrentJungle.pDrlgCoord.nPosX
if (nMinY > pCurrentJungle.pDrlgCoord.nPosY) nMinY = pCurrentJungle.pDrlgCoord.nPosY
if (nMaxX < pCurrentJungle.pDrlgCoord.nWidth + pCurrentJungle.pDrlgCoord.nPosX) {
nMaxX = pCurrentJungle.pDrlgCoord.nWidth + pCurrentJungle.pDrlgCoord.nPosX
}
}
const nPresetsWidth = idiv(nMaxX - nMinX, nPresetBlocksDimensions) + 2
const nPresetsHeight = idiv(nMaxY - nMinY, nPresetBlocksDimensions) + 2
const nPresets = nPresetsWidth * nPresetsHeight
const pPreset0 = new Int32Array(nPresets)
const pPreset1 = new Int32Array(nPresets)
const pPreset2 = new Int32Array(nPresets)
const pLevelPresetId = new Int32Array(nPresets)
do {
DRLG_GenerateJunglesAttachPoints(
pDrlg,
tJungles,
nMinX,
nMinY,
nSpiderForestLevelSizeX,
nSpiderForestLevelSizeY,
nPresetsWidth,
nPresetsHeight,
pPreset0,
pPreset1,
pPreset2,
pLevelPresetId,
)
DRLG_JungleComputeConnexity(pDrlg, nPresetsWidth, nPresetsHeight, pPreset0, pPreset1, pPreset2, pLevelPresetId)
} while (!DRLG_JungleUpdateAttachPointsDirections(pDrlg, nPresetsWidth, nPresetsHeight, pPreset0, pPreset2, pLevelPresetId))
for (let nCurrentPresetY = 0; nCurrentPresetY < nPresetsHeight; nCurrentPresetY += 1) {
for (let nCurrentPresetX = 0; nCurrentPresetX < nPresetsWidth; nCurrentPresetX += 1) {
const nCurrentPresetIndex = nCurrentPresetY * nPresetsWidth + nCurrentPresetX
pLevelPresetId[nCurrentPresetIndex] = DRLG_JungleNormalizeLevelPresetId(pLevelPresetId[nCurrentPresetIndex]!)
}
}
const nSpiderForestLevelPresetsBlocksSizeX = idiv(nSpiderForestLevelSizeX, nPresetBlocksDimensions)
const nSpiderForestLevelPresetsBlocksSizeY = idiv(nSpiderForestLevelSizeY, nPresetBlocksDimensions)
for (let nJungleIdx = 0; nJungleIdx < JUNGLE_MAX_ATTACH; nJungleIdx += 1) {
const tCurrentJungle = tJungles[nJungleIdx]!
let nJungleDefsIndex = 0
for (let nBlockY = 0; nBlockY < nSpiderForestLevelPresetsBlocksSizeY; nBlockY += 1) {
for (let nBlockX = 0; nBlockX < nSpiderForestLevelPresetsBlocksSizeX; nBlockX += 1) {
const nCurrentPresetIndex =
nBlockX + tCurrentJungle.nPresetsBlocksX + nPresetsWidth * (nBlockY + tCurrentJungle.nPresetsBlocksY)
const nPrestId = pLevelPresetId[nCurrentPresetIndex]!
tCurrentJungle.pJungleDefs![nJungleDefsIndex] = nPrestId
if (nPrestId > LVLPREST_ACT3_JUNGLE_TAIL) {
tCurrentJungle.nJungleDefs += 1
}
nJungleDefsIndex += 1
}
}
}
tJungles.sort((lhs, rhs) => rhs.pDrlgCoord.nPosY - lhs.pDrlgCoord.nPosY)
let pJungleLevel: D2DrlgLevelStrc | null = null
for (let i = 0; i < JUNGLE_MAX_ATTACH; i += 1) {
pJungleLevel = DRLG_GetLevel(pDrlg, i + LEVEL_SPIDERFOREST)
pJungleLevel.pJungleDefs = tJungles[i]!.pJungleDefs
pJungleLevel.nJungleDefs = tJungles[i]!.nJungleDefs
pJungleLevel.pLevelCoords.nPosX = tJungles[i]!.pDrlgCoord.nPosX
pJungleLevel.pLevelCoords.nPosY = tJungles[i]!.pDrlgCoord.nPosY
pJungleLevel.pLevelCoords.nWidth = tJungles[i]!.pDrlgCoord.nWidth
pJungleLevel.pLevelCoords.nHeight = tJungles[i]!.pDrlgCoord.nHeight
}
return pJungleLevel!
}
//D2Common.0x6FD83970
function sub_6FD83970(
pDrlgCoord: D2DrlgCoordStrc,
pJungle: D2JungleStrc,
nRand: number,
nSizeX: number,
nSizeY: number,
): void {
let nX = 0
let nY = 0
switch (nRand) {
case 0:
nX = 0
nY = -nSizeY
break
case 1: {
nX = -nSizeX
const mul = Number(BigInt.asUintN(64, 0x55555555n * BigInt.asUintN(64, BigInt(nSizeY))) >> 32n) | 0
const nTemp = (mul - nSizeY) >> 1
nY = ((nTemp >>> 31) + nTemp) | 0
break
}
case 2: {
nX = nSizeX
const mul = Number(BigInt.asUintN(64, 0x55555555n * BigInt.asUintN(64, BigInt(nSizeY))) >> 32n) | 0
const nTemp = (mul - nSizeY) >> 1
nY = ((nTemp >>> 31) + nTemp) | 0
break
}
case 3: {
nX = -nSizeX
const nTemp = Number(BigInt.asUintN(64, 0xffffffff55555554n * BigInt.asUintN(64, BigInt(nSizeY))) >> 32n) | 0
nY = ((nTemp >>> 31) + nTemp) | 0
break
}
case 4: {
nX = nSizeX
const nTemp = Number(BigInt.asUintN(64, 0xffffffff55555554n * BigInt.asUintN(64, BigInt(nSizeY))) >> 32n) | 0
nY = ((nTemp >>> 31) + nTemp) | 0
break
}
default:
break
}
pJungle.pDrlgCoord.nPosX = nX + pDrlgCoord.nPosX
pJungle.pDrlgCoord.nHeight = nSizeY
pJungle.pDrlgCoord.nWidth = nSizeX
pJungle.pDrlgCoord.nPosY = nY + pDrlgCoord.nPosY
pJungle.field_10 = nRand
}