Fix data-driven point lights, elevation-aware shaders, authentic palette rows, and dead code cleanup (Closes #606, #614, #621)
This commit is contained in:
parent
8b619c23c3
commit
56f4f7f4fe
|
|
@ -33,36 +33,8 @@ export interface MissileMeta {
|
|||
readonly groups: readonly (readonly MissileFrameTuple[])[]
|
||||
}
|
||||
|
||||
/**
|
||||
* The engine's 64-direction space -> DCC direction tables.
|
||||
* Ported from OpenDiablo2 d2fileformats/d2dcc/dcc_dir_lookup.go (Dir64ToDcc).
|
||||
*/
|
||||
const DIR64_TO_DCC: Readonly<Record<number, readonly number[]>> = {
|
||||
4: [
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
],
|
||||
8: [
|
||||
4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 5, 5, 5, 5,
|
||||
5, 5, 5, 5, 1, 1, 1, 1, 1, 1, 1, 1, 6, 6, 6, 6,
|
||||
6, 6, 6, 6, 2, 2, 2, 2, 2, 2, 2, 2, 7, 7, 7, 7,
|
||||
7, 7, 7, 7, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4,
|
||||
],
|
||||
16: [
|
||||
4, 4, 8, 8, 8, 8, 0, 0, 0, 0, 9, 9, 9, 9, 5, 5,
|
||||
5, 5, 10, 10, 10, 10, 1, 1, 1, 1, 11, 11, 11, 11, 6, 6,
|
||||
6, 6, 12, 12, 12, 12, 2, 2, 2, 2, 13, 13, 13, 13, 7, 7,
|
||||
7, 7, 14, 14, 14, 14, 3, 3, 3, 3, 15, 15, 15, 15, 4, 4,
|
||||
],
|
||||
32: [
|
||||
4, 16, 16, 8, 8, 17, 17, 0, 0, 18, 18, 9, 9, 19, 19, 5,
|
||||
5, 20, 20, 10, 10, 21, 21, 1, 1, 22, 22, 11, 11, 23, 23, 6,
|
||||
6, 24, 24, 12, 12, 25, 25, 2, 2, 26, 26, 13, 13, 27, 27, 7,
|
||||
7, 28, 28, 14, 14, 29, 29, 3, 3, 30, 30, 15, 15, 31, 31, 4,
|
||||
],
|
||||
}
|
||||
import { dir64ToDcc } from '../../common/units/character.ts'
|
||||
import { dir64FromPoints } from '../../common/units/direction.ts'
|
||||
|
||||
/**
|
||||
* Calculate DCC direction index for a missile based on 2D velocity vector.
|
||||
|
|
@ -75,15 +47,29 @@ const DIR64_TO_DCC: Readonly<Record<number, readonly number[]>> = {
|
|||
*/
|
||||
export function velocityToDccDirection(vx: number, vy: number, directions = 16): number {
|
||||
if (directions <= 1) return 0
|
||||
if (vx === 0 && vy === 0) return 0
|
||||
const angle = Math.atan2(vy, vx) // -PI to PI
|
||||
// Map angle so that South (PI/2) is 0, turning clockwise
|
||||
const fraction = (angle - Math.PI / 2) / (2 * Math.PI)
|
||||
const dir64 = Math.round((((fraction % 1) + 1) % 1) * 64) % 64
|
||||
const table = DIR64_TO_DCC[directions]
|
||||
if (table !== undefined) {
|
||||
return table[dir64] ?? 0
|
||||
return dir64ToDcc(dir64, directions)
|
||||
}
|
||||
return 0
|
||||
|
||||
/**
|
||||
* Calculate DCC direction index from start to target coordinates using
|
||||
* Diablo II 1.13c integer path direction calculation (0x6FD5CF40).
|
||||
*/
|
||||
export function pointsToDccDirection(
|
||||
startX: number,
|
||||
startY: number,
|
||||
targetX: number,
|
||||
targetY: number,
|
||||
directions = 16,
|
||||
fallback = 0,
|
||||
): number {
|
||||
if (directions <= 1) return 0
|
||||
const dir64 = dir64FromPoints(startX, startY, targetX, targetY, fallback)
|
||||
return dir64ToDcc(dir64, directions)
|
||||
}
|
||||
|
||||
export const FIREBOLT_META: MissileMeta = {
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ export async function loadMonsterAtlas(
|
|||
}
|
||||
}
|
||||
|
||||
const atlas = buildIndexedAtlas(combined, 172, { width: 1024 })
|
||||
const atlas = buildIndexedAtlas(combined, undefined, { width: 1024 })
|
||||
const handle = renderer.addIndexedAtlas(atlas.r8, atlas.width, atlas.height)
|
||||
|
||||
const groups = atlas.groups.slice(0, baseCount)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@
|
|||
* a flush per colour.
|
||||
*/
|
||||
import type { AtlasFrame, SpriteAtlas } from './atlas.ts'
|
||||
import type { Pl2, Pl2Transform } from '../../common/formats/pl2.ts'
|
||||
import { decodePl2, pl2ExpectedSize } from '../../common/formats/pl2.ts'
|
||||
|
||||
/** Camera state for one frame. */
|
||||
export interface Camera {
|
||||
|
|
@ -94,6 +96,20 @@ export interface DrawOptions {
|
|||
readonly elevationY?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional transform tables or full PL2 file for authentic 1.13c hardware palette rows.
|
||||
*/
|
||||
export type PaletteTransformInput =
|
||||
| Pl2
|
||||
| readonly (Pl2Transform | undefined | null)[]
|
||||
| {
|
||||
readonly cold?: Pl2Transform | undefined
|
||||
readonly poison?: Pl2Transform | undefined
|
||||
readonly unique?: Pl2Transform | undefined
|
||||
readonly champion?: Pl2Transform | undefined
|
||||
readonly hueVariations?: readonly Pl2Transform[] | undefined
|
||||
}
|
||||
|
||||
/** Dynamic scene lighting configuration for Glide-style illumination. */
|
||||
export interface LightingConfig {
|
||||
/** Ambient light color in range 0..1, e.g. [0.03, 0.03, 0.04]. */
|
||||
|
|
@ -233,7 +249,7 @@ ${cases.join('\n')}
|
|||
${paletteLookup}${discardAlpha}
|
||||
if (u_lightMode == 1 && u_blendMode == 0) {
|
||||
vec4 lightSample = texture(u_lightmap, v_lightUv);
|
||||
vec3 dynamicLight = lightSample.rgb * (0.22 + 0.78 * lightSample.a);
|
||||
vec3 dynamicLight = lightSample.rgb;
|
||||
vec3 totalLight = clamp(u_ambient.rgb + dynamicLight, 0.0, 1.0);
|
||||
outColor = vec4(texel.rgb * totalLight * v_tint.rgb, texel.a * v_tint.a);
|
||||
} else {
|
||||
|
|
@ -809,22 +825,68 @@ export class SpriteRenderer {
|
|||
/**
|
||||
* Upload the 256-colour Act palette and precompute hardware status tint rows:
|
||||
* - Row 0: Normal Act Palette
|
||||
* - Row 1: Cold / Chilled Blue Tint
|
||||
* - Row 2: Poisoned Green Tint
|
||||
* - Row 3: Unique Gold Boss Tint
|
||||
* - Row 4: Champion / Elite Blue Tint
|
||||
* - Row 1: Cold / Chilled Blue Tint (authentic blueTones transform when available)
|
||||
* - Row 2: Poisoned Green Tint (authentic greenTones transform when available)
|
||||
* - Row 3: Unique Gold Boss Tint (authentic Utrans / hueVariations transform when available)
|
||||
* - Row 4: Champion / Elite Blue Tint (authentic hueVariations transform when available)
|
||||
*
|
||||
* @param rgb - 768-byte array of 256 RGB triplets (`[r, g, b, ...]`).
|
||||
* @param rgb - 768-byte array of 256 RGB triplets (`[r, g, b, ...]`) or full decoded PL2 buffer.
|
||||
* @param transforms - optional PL2 object or transform array for authentic 1.13c colormaps.
|
||||
*/
|
||||
setPalette(rgb: Uint8Array): void {
|
||||
setPalette(rgb: Uint8Array, transforms?: PaletteTransformInput): void {
|
||||
if (this.disposed) return
|
||||
this.flush()
|
||||
const gl = this.gl
|
||||
|
||||
let baseRgb = rgb
|
||||
let coldXform: Pl2Transform | undefined
|
||||
let poisonXform: Pl2Transform | undefined
|
||||
let uniqueXform: Pl2Transform | undefined
|
||||
let champXform: Pl2Transform | undefined
|
||||
|
||||
if (rgb.byteLength >= pl2ExpectedSize()) {
|
||||
try {
|
||||
const pl2 = decodePl2(rgb)
|
||||
baseRgb = pl2.rgb
|
||||
coldXform = pl2.blueTones
|
||||
poisonXform = pl2.greenTones
|
||||
uniqueXform = pl2.hueVariations[2] ?? pl2.selectedUnitShift
|
||||
champXform = pl2.hueVariations[4] ?? pl2.blueTones
|
||||
} catch {
|
||||
// Fall back to treating as raw RGB
|
||||
}
|
||||
} else if (transforms) {
|
||||
if (Array.isArray(transforms)) {
|
||||
coldXform = transforms[0] ?? undefined
|
||||
poisonXform = transforms[1] ?? undefined
|
||||
uniqueXform = transforms[2] ?? undefined
|
||||
champXform = transforms[3] ?? undefined
|
||||
} else if ('blueTones' in transforms && 'greenTones' in transforms) {
|
||||
const pl2 = transforms as Pl2
|
||||
coldXform = pl2.blueTones
|
||||
poisonXform = pl2.greenTones
|
||||
uniqueXform = pl2.hueVariations?.[2] ?? pl2.selectedUnitShift
|
||||
champXform = pl2.hueVariations?.[4] ?? pl2.blueTones
|
||||
} else {
|
||||
const tObj = transforms as {
|
||||
cold?: Pl2Transform
|
||||
poison?: Pl2Transform
|
||||
unique?: Pl2Transform
|
||||
champion?: Pl2Transform
|
||||
hueVariations?: readonly Pl2Transform[]
|
||||
}
|
||||
coldXform = tObj.cold ?? tObj.hueVariations?.[1]
|
||||
poisonXform = tObj.poison ?? tObj.hueVariations?.[3]
|
||||
uniqueXform = tObj.unique ?? tObj.hueVariations?.[2]
|
||||
champXform = tObj.champion ?? tObj.hueVariations?.[4]
|
||||
}
|
||||
}
|
||||
|
||||
const rgba = new Uint8Array(256 * PALETTE_ROWS * 4)
|
||||
for (let i = 1; i < 256; i += 1) {
|
||||
const r = rgb[i * 3] ?? 0
|
||||
const g = rgb[i * 3 + 1] ?? 0
|
||||
const b = rgb[i * 3 + 2] ?? 0
|
||||
const r = baseRgb[i * 3] ?? 0
|
||||
const g = baseRgb[i * 3 + 1] ?? 0
|
||||
const b = baseRgb[i * 3 + 2] ?? 0
|
||||
const lum = Math.round(0.299 * r + 0.587 * g + 0.114 * b)
|
||||
|
||||
// Row 0: Normal Act Palette
|
||||
|
|
@ -836,30 +898,58 @@ export class SpriteRenderer {
|
|||
|
||||
// Row 1: Cold / Chilled Icy Blue
|
||||
const r1 = (256 + i) * 4
|
||||
if (coldXform) {
|
||||
const targetIdx = (coldXform[i] ?? i) * 3
|
||||
rgba[r1] = baseRgb[targetIdx] ?? 0
|
||||
rgba[r1 + 1] = baseRgb[targetIdx + 1] ?? 0
|
||||
rgba[r1 + 2] = baseRgb[targetIdx + 2] ?? 0
|
||||
} else {
|
||||
rgba[r1] = Math.min(255, Math.round(r * 0.35))
|
||||
rgba[r1 + 1] = Math.min(255, Math.round(g * 0.65 + 40))
|
||||
rgba[r1 + 2] = Math.min(255, Math.round(b * 0.9 + 100))
|
||||
}
|
||||
rgba[r1 + 3] = 255
|
||||
|
||||
// Row 2: Poisoned Venom Green
|
||||
const r2 = (256 * 2 + i) * 4
|
||||
if (poisonXform) {
|
||||
const targetIdx = (poisonXform[i] ?? i) * 3
|
||||
rgba[r2] = baseRgb[targetIdx] ?? 0
|
||||
rgba[r2 + 1] = baseRgb[targetIdx + 1] ?? 0
|
||||
rgba[r2 + 2] = baseRgb[targetIdx + 2] ?? 0
|
||||
} else {
|
||||
rgba[r2] = Math.min(255, Math.round(r * 0.35))
|
||||
rgba[r2 + 1] = Math.min(255, Math.round(g * 0.85 + 85))
|
||||
rgba[r2 + 2] = Math.min(255, Math.round(b * 0.35))
|
||||
}
|
||||
rgba[r2 + 3] = 255
|
||||
|
||||
// Row 3: Unique Boss Golden Aura
|
||||
const r3 = (256 * 3 + i) * 4
|
||||
if (uniqueXform) {
|
||||
const targetIdx = (uniqueXform[i] ?? i) * 3
|
||||
rgba[r3] = baseRgb[targetIdx] ?? 0
|
||||
rgba[r3 + 1] = baseRgb[targetIdx + 1] ?? 0
|
||||
rgba[r3 + 2] = baseRgb[targetIdx + 2] ?? 0
|
||||
} else {
|
||||
rgba[r3] = Math.min(255, Math.round(lum * 1.25 + 55))
|
||||
rgba[r3 + 1] = Math.min(255, Math.round(lum * 0.95 + 25))
|
||||
rgba[r3 + 2] = Math.min(255, Math.round(lum * 0.35))
|
||||
}
|
||||
rgba[r3 + 3] = 255
|
||||
|
||||
// Row 4: Champion / Elite Royal Blue
|
||||
const r4 = (256 * 4 + i) * 4
|
||||
if (champXform) {
|
||||
const targetIdx = (champXform[i] ?? i) * 3
|
||||
rgba[r4] = baseRgb[targetIdx] ?? 0
|
||||
rgba[r4 + 1] = baseRgb[targetIdx + 1] ?? 0
|
||||
rgba[r4 + 2] = baseRgb[targetIdx + 2] ?? 0
|
||||
} else {
|
||||
rgba[r4] = Math.min(255, Math.round(lum * 0.45 + 30))
|
||||
rgba[r4 + 1] = Math.min(255, Math.round(lum * 0.6 + 45))
|
||||
rgba[r4 + 2] = Math.min(255, Math.round(lum * 1.3 + 75))
|
||||
}
|
||||
rgba[r4 + 3] = 255
|
||||
}
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.paletteTexture)
|
||||
|
|
@ -1065,6 +1155,8 @@ export class SpriteRenderer {
|
|||
const tint = options.tint
|
||||
const flipped = options.flipX === true
|
||||
const paletteRow = page.indexed === true ? (options.paletteRow ?? 0) : -1
|
||||
const elevationY = options.elevationY !== undefined && options.elevationY > 0 ? Math.round(options.elevationY) : 0
|
||||
const topPalette = elevationY > 0 ? paletteRow + elevationY * 16 : paletteRow
|
||||
const drawW = options.width ?? frame.width
|
||||
const drawH = options.height ?? frame.height
|
||||
this.quad(
|
||||
|
|
@ -1080,6 +1172,7 @@ export class SpriteRenderer {
|
|||
tint === undefined ? 1 : tint[3],
|
||||
unit,
|
||||
paletteRow,
|
||||
topPalette,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -187,6 +187,7 @@ export interface PointLightInput {
|
|||
readonly r: number
|
||||
readonly g: number
|
||||
readonly b: number
|
||||
readonly flicker?: number | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -242,18 +243,16 @@ export function applyLightingInputs(inputs: LightingInputs, targets: LightingPas
|
|||
const overrideAmb = presetCfg.overrideAmbient
|
||||
const envIntensity = inputs.environmentIntensity ?? environment?.currentIntensity ?? 255
|
||||
const rawIntensity = overrideAmb !== undefined ? overrideAmb.intensity : envIntensity
|
||||
const isDarkCave = rawIntensity <= 14 || inputs.preset === 'cave' || inputs.preset === 'torch'
|
||||
|
||||
const flicker = (Math.sin(inputs.tick * 0.35) * 1.5) | 0
|
||||
const basePlayerRadius = (isDarkCave ? 20 : 22) + (inputs.playerLightRadiusBonus ?? 0)
|
||||
const basePlayerRadius = 11 + (inputs.playerLightRadiusBonus ?? 0)
|
||||
lightGrid.addLight({
|
||||
subTileX: pSub.x,
|
||||
subTileY: pSub.y,
|
||||
radius: Math.max(8, basePlayerRadius + flicker),
|
||||
intensity: 242,
|
||||
radius: Math.max(1, basePlayerRadius),
|
||||
intensity: 255,
|
||||
red: 255,
|
||||
green: 234,
|
||||
blue: 196,
|
||||
green: 255,
|
||||
blue: 255,
|
||||
})
|
||||
|
||||
for (const pl of inputs.pointLights) {
|
||||
|
|
@ -265,19 +264,19 @@ export function applyLightingInputs(inputs: LightingInputs, targets: LightingPas
|
|||
red: pl.r,
|
||||
green: pl.g,
|
||||
blue: pl.b,
|
||||
...(pl.flicker !== undefined ? { flicker: pl.flicker } : {}),
|
||||
})
|
||||
}
|
||||
|
||||
const effectiveAmbIntensity = rawIntensity > 0 ? rawIntensity : 5
|
||||
const envColor = inputs.environmentColor ?? [
|
||||
environment?.currentRed ?? 255,
|
||||
environment?.currentGreen ?? 255,
|
||||
environment?.currentBlue ?? 255,
|
||||
]
|
||||
const effR = overrideAmb !== undefined ? overrideAmb.red : (rawIntensity > 0 ? envColor[0] : 185)
|
||||
const effG = overrideAmb !== undefined ? overrideAmb.green : (rawIntensity > 0 ? envColor[1] : 175)
|
||||
const effB = overrideAmb !== undefined ? overrideAmb.blue : (rawIntensity > 0 ? envColor[2] : 165)
|
||||
const ambNorm = effectiveAmbIntensity / 255
|
||||
const effR = overrideAmb !== undefined ? overrideAmb.red : envColor[0]
|
||||
const effG = overrideAmb !== undefined ? overrideAmb.green : envColor[1]
|
||||
const effB = overrideAmb !== undefined ? overrideAmb.blue : envColor[2]
|
||||
const ambNorm = rawIntensity / 255
|
||||
const ambR = (effR / 255) * ambNorm
|
||||
const ambG = (effG / 255) * ambNorm
|
||||
const ambB = (effB / 255) * ambNorm
|
||||
|
|
@ -291,3 +290,822 @@ export function applyLightingInputs(inputs: LightingInputs, targets: LightingPas
|
|||
lightmap: lightGrid.exportTextureBuffer(),
|
||||
})
|
||||
}
|
||||
|
||||
/** Canonical 1.13c Monster point light metadata from MonStats2.txt [Light, light-r, light-g, light-b] */
|
||||
export const CANONICAL_MONSTER_LIGHTS: Readonly<Record<string, readonly [radius: number, r: number, g: number, b: number]>> = Object.freeze({
|
||||
"fallenshaman1": [5,230,168,255],
|
||||
"fallenshaman2": [5,230,168,255],
|
||||
"fallenshaman3": [5,230,168,255],
|
||||
"fallenshaman4": [5,230,168,255],
|
||||
"fallenshaman5": [5,230,168,255],
|
||||
"willowisp1": [6,255,255,255],
|
||||
"willowisp2": [6,255,255,255],
|
||||
"willowisp3": [6,255,255,255],
|
||||
"willowisp4": [6,255,255,255],
|
||||
"vampire1": [3,255,138,255],
|
||||
"vampire2": [3,255,138,255],
|
||||
"vampire3": [3,255,138,255],
|
||||
"vampire4": [5,255,255,255],
|
||||
"vampire5": [3,255,138,255],
|
||||
"andariel": [8,255,255,255],
|
||||
"duriel": [9,255,255,255],
|
||||
"firebeast": [3,255,138,255],
|
||||
"iceglobe": [3,255,60,255],
|
||||
"lightningbeast": [3,255,255,255],
|
||||
"poisonorb": [3,102,255,255],
|
||||
"flyingscimitar": [3,100,190,255],
|
||||
"cantor1": [3,105,190,255],
|
||||
"cantor2": [3,105,190,255],
|
||||
"cantor3": [3,105,190,255],
|
||||
"cantor4": [3,105,190,255],
|
||||
"mephisto": [9,255,255,255],
|
||||
"summoner": [9,174,255,255],
|
||||
"tyrael1": [11,255,255,255],
|
||||
"izual": [9,255,255,255],
|
||||
"roguehire": [5,255,255,255],
|
||||
"skmage_pois1": [5,102,255,255],
|
||||
"skmage_pois2": [5,102,255,255],
|
||||
"skmage_pois3": [5,102,255,255],
|
||||
"skmage_pois4": [5,102,255,255],
|
||||
"fetishshaman1": [5,255,255,255],
|
||||
"fetishshaman2": [5,255,255,255],
|
||||
"fetishshaman3": [5,255,255,255],
|
||||
"fetishshaman4": [5,255,255,255],
|
||||
"fetishshaman5": [5,255,255,255],
|
||||
"claygolem": [5,255,255,255],
|
||||
"bloodgolem": [5,255,255,255],
|
||||
"irongolem": [5,255,255,255],
|
||||
"firegolem": [5,255,255,255],
|
||||
"familiar": [5,255,255,255],
|
||||
"fingermage1": [7,255,90,255],
|
||||
"fingermage2": [7,255,228,132],
|
||||
"fingermage3": [7,255,255,255],
|
||||
"doomknight1": [4,255,255,255],
|
||||
"doomknight2": [4,255,255,255],
|
||||
"doomknight3": [4,255,255,255],
|
||||
"act2hire": [5,255,255,255],
|
||||
"hydra1": [4,255,138,255],
|
||||
"hydra2": [4,255,138,255],
|
||||
"hydra3": [4,255,138,255],
|
||||
"dopplezon": [5,255,255,255],
|
||||
"valkyrie": [5,255,255,255],
|
||||
"act3hire": [5,255,255,255],
|
||||
"necromage": [5,255,255,255],
|
||||
"tyrael2": [11,255,255,255],
|
||||
"skmage_cold1": [5,255,60,255],
|
||||
"skmage_cold2": [5,255,60,255],
|
||||
"skmage_cold3": [5,255,60,255],
|
||||
"skmage_cold4": [5,255,60,255],
|
||||
"skmage_fire1": [5,255,138,255],
|
||||
"skmage_fire2": [5,255,138,255],
|
||||
"skmage_fire3": [5,255,138,255],
|
||||
"skmage_fire4": [5,255,138,255],
|
||||
"skmage_ltng1": [5,255,255,255],
|
||||
"skmage_ltng2": [5,255,255,255],
|
||||
"skmage_ltng3": [5,255,255,255],
|
||||
"skmage_ltng4": [5,255,255,255],
|
||||
"mephistospirit": [3,255,255,255],
|
||||
"izualghost": [5,255,255,255],
|
||||
"malachai": [5,255,255,255],
|
||||
"hephasto": [5,255,255,255],
|
||||
"spiritofbarbs": [4,255,255,255],
|
||||
"heartofwolverine": [4,255,50,50],
|
||||
"oaksage": [4,255,255,255],
|
||||
"suicideminion1": [5,230,168,255],
|
||||
"suicideminion2": [5,230,168,255],
|
||||
"suicideminion3": [5,230,168,255],
|
||||
"suicideminion4": [5,230,168,255],
|
||||
"suicideminion5": [5,230,168,255],
|
||||
"suicideminion6": [5,230,168,255],
|
||||
"suicideminion7": [5,230,168,255],
|
||||
"suicideminion8": [5,230,168,255],
|
||||
"tyrael3": [11,255,255,255],
|
||||
"evilhut": [7,255,255,150],
|
||||
"ancientstatue1": [6,255,255,255],
|
||||
"ancientstatue2": [6,255,255,255],
|
||||
"ancientstatue3": [6,255,255,255],
|
||||
"baalthrone": [6,255,255,255],
|
||||
"baalcrabstairs": [6,255,255,255],
|
||||
"act5hire1": [5,255,255,255],
|
||||
"act5hire2": [5,255,255,255],
|
||||
"worldstoneeffect": [15,255,178,64],
|
||||
"ubermephisto": [9,255,255,255],
|
||||
"uberandariel": [8,255,255,255],
|
||||
"uberizual": [9,255,255,255],
|
||||
"uberduriel": [9,255,255,255],
|
||||
})
|
||||
|
||||
/** Canonical 1.13c Object point light metadata from Objects.txt [Lit0..Lit7, Red, Green, Blue, Flicker] */
|
||||
export const CANONICAL_OBJECT_LIGHTS: Readonly<Record<number, readonly [lits: readonly number[], r: number, g: number, b: number, flicker: number]>> = Object.freeze({
|
||||
2: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
11: [[0,11,0,0,0,0,0,0],255,255,255,0],
|
||||
17: [[0,0,17,0,0,0,0,0],255,255,255,0],
|
||||
18: [[0,0,17,0,0,0,0,0],255,255,255,0],
|
||||
19: [[0,0,17,0,0,0,0,0],255,255,255,0],
|
||||
20: [[0,0,17,0,0,0,0,0],255,255,255,0],
|
||||
21: [[0,0,17,0,0,0,0,0],255,255,255,0],
|
||||
22: [[0,0,17,0,0,0,0,0],255,255,255,0],
|
||||
26: [[5,10,5,0,0,0,0,0],255,255,255,0],
|
||||
29: [[0,19,18,0,0,0,0,0],255,236,176,1],
|
||||
33: [[18,18,18,0,0,0,0,0],255,255,213,1],
|
||||
34: [[18,18,18,0,0,0,0,0],255,255,213,1],
|
||||
37: [[0,19,19,0,0,0,0,0],255,236,176,0],
|
||||
38: [[17,19,19,0,0,0,0,0],255,236,176,1],
|
||||
39: [[0,19,19,0,0,0,0,0],255,236,176,1],
|
||||
48: [[19,19,19,17,17,17,17,17],255,236,176,1],
|
||||
49: [[19,19,19,17,17,17,17,17],255,236,176,1],
|
||||
59: [[0,18,19,0,0,0,0,0],120,120,255,1],
|
||||
60: [[0,18,19,0,0,0,0,0],255,120,120,1],
|
||||
77: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
80: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
81: [[12,17,7,0,0,0,0,0],255,255,255,0],
|
||||
83: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
84: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
85: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
86: [[0,19,0,0,0,0,0,0],255,255,180,0],
|
||||
90: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
97: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
100: [[0,10,10,0,0,0,0,0],255,255,255,0],
|
||||
101: [[0,17,18,0,0,0,0,0],255,236,176,0],
|
||||
102: [[0,17,18,0,0,0,0,0],255,236,176,0],
|
||||
108: [[16,0,0,0,0,0,0,0],255,255,255,0],
|
||||
109: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
116: [[10,17,0,0,0,0,0,0],255,190,255,0],
|
||||
117: [[0,19,18,0,0,0,0,0],255,236,176,0],
|
||||
119: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
120: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
123: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
124: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
131: [[17,0,0,0,0,0,0,0],255,100,100,0],
|
||||
133: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
134: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
135: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
136: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
145: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
149: [[0,17,17,0,0,0,0,0],255,255,255,0],
|
||||
150: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
151: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
152: [[25,25,25,0,0,0,0,0],255,255,255,0],
|
||||
153: [[0,18,18,0,0,0,0,0],255,190,255,0],
|
||||
156: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
157: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
160: [[0,16,17,0,0,0,0,0],255,255,255,0],
|
||||
161: [[0,17,18,0,0,0,0,0],255,255,255,0],
|
||||
162: [[0,18,19,0,0,0,0,0],255,255,255,0],
|
||||
164: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
165: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
166: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
167: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
168: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
170: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
172: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
173: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
184: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
189: [[0,18,19,0,0,0,0,0],100,100,255,0],
|
||||
190: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
191: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
192: [[17,0,0,0,0,0,0,0],255,255,255,0],
|
||||
197: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
199: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
200: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
201: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
202: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
206: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
226: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
231: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
232: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
237: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
238: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
239: [[0,0,17,0,0,0,0,0],255,255,255,0],
|
||||
245: [[17,0,0,0,0,0,0,0],255,255,255,0],
|
||||
259: [[0,3,0,0,0,0,0,0],255,125,125,1],
|
||||
262: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
263: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
264: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
265: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
273: [[18,0,0,0,0,0,0,0],255,255,255,0],
|
||||
275: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
276: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
277: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
278: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
279: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
280: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
281: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
282: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
283: [[17,0,0,0,0,0,0,0],255,255,255,0],
|
||||
285: [[17,0,0,0,0,0,0,0],255,255,255,0],
|
||||
286: [[17,0,0,0,0,0,0,0],255,255,255,0],
|
||||
287: [[17,0,0,0,0,0,0,0],255,255,255,0],
|
||||
288: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
296: [[18,0,0,0,0,0,0,0],255,255,255,0],
|
||||
297: [[18,0,0,0,0,0,0,0],255,255,255,0],
|
||||
298: [[0,17,19,0,0,0,0,0],120,120,255,1],
|
||||
299: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
300: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
303: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
304: [[17,0,0,0,0,0,0,0],255,255,255,0],
|
||||
305: [[17,0,0,0,0,0,0,0],255,255,255,0],
|
||||
306: [[17,0,0,0,0,0,0,0],255,255,255,0],
|
||||
307: [[17,0,0,0,0,0,0,0],125,125,255,0],
|
||||
308: [[17,0,0,0,0,0,0,0],125,125,255,0],
|
||||
309: [[17,0,0,0,0,0,0,0],125,125,255,0],
|
||||
310: [[17,0,0,0,0,0,0,0],125,125,255,0],
|
||||
311: [[17,0,0,0,0,0,0,0],125,125,255,0],
|
||||
312: [[17,0,0,0,0,0,0,0],125,125,255,0],
|
||||
313: [[17,0,0,0,0,0,0,0],125,125,255,0],
|
||||
319: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
320: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
323: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
324: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
325: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
327: [[10,19,19,0,0,0,0,0],255,236,176,0],
|
||||
328: [[10,19,19,0,0,0,0,0],255,236,176,0],
|
||||
342: [[15,17,19,0,0,0,0,0],255,100,100,1],
|
||||
343: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
344: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
345: [[6,0,0,0,0,0,0,0],255,100,100,1],
|
||||
346: [[8,0,0,0,0,0,0,0],255,100,100,1],
|
||||
347: [[12,0,0,0,0,0,0,0],255,100,100,1],
|
||||
351: [[6,0,0,0,0,0,0,0],255,100,100,0],
|
||||
352: [[8,0,0,0,0,0,0,0],255,100,100,0],
|
||||
353: [[10,0,0,0,0,0,0,0],255,100,100,0],
|
||||
354: [[12,0,0,0,0,0,0,0],100,100,255,1],
|
||||
355: [[12,0,0,0,0,0,0,0],100,100,255,1],
|
||||
356: [[12,0,0,0,0,0,0,0],100,100,255,1],
|
||||
358: [[19,19,0,0,0,0,0,0],255,236,176,1],
|
||||
359: [[19,19,0,0,0,0,0,0],255,236,176,1],
|
||||
361: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
370: [[19,0,0,0,0,0,0,0],255,255,255,1],
|
||||
373: [[0,3,0,0,0,0,0,0],255,125,125,1],
|
||||
376: [[13,17,19,15,13,13,0,0],255,236,176,0],
|
||||
377: [[19,18,19,0,0,0,0,0],255,120,120,1],
|
||||
380: [[17,15,17,0,0,0,0,0],255,200,200,0],
|
||||
381: [[17,15,17,0,0,0,0,0],255,200,200,0],
|
||||
392: [[0,15,17,0,0,0,0,0],255,128,128,0],
|
||||
393: [[0,15,17,0,0,0,0,0],255,128,128,0],
|
||||
394: [[0,15,17,0,0,0,0,0],255,128,128,0],
|
||||
395: [[0,15,17,0,0,0,0,0],255,128,128,0],
|
||||
396: [[0,15,17,0,0,0,0,0],255,128,128,0],
|
||||
397: [[12,0,0,0,0,0,0,0],100,100,255,1],
|
||||
398: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
400: [[17,0,0,0,0,0,0,0],255,255,255,0],
|
||||
401: [[18,0,0,0,0,0,0,0],180,55,55,0],
|
||||
402: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
403: [[17,0,0,0,0,0,0,0],255,255,255,0],
|
||||
404: [[17,0,0,0,0,0,0,0],255,255,255,0],
|
||||
405: [[12,0,0,0,0,0,0,0],100,100,255,1],
|
||||
406: [[12,0,0,0,0,0,0,0],100,100,255,1],
|
||||
407: [[12,0,0,0,0,0,0,0],100,100,255,1],
|
||||
408: [[17,0,0,0,0,0,0,0],255,255,255,0],
|
||||
409: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
410: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
414: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
415: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
419: [[0,11,0,0,0,0,0,0],255,255,255,0],
|
||||
421: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
422: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
423: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
427: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
428: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
429: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
434: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
435: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
436: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
437: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
438: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
439: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
442: [[17,0,0,0,0,0,0,0],255,255,255,0],
|
||||
454: [[0,11,0,0,0,0,0,0],255,255,255,0],
|
||||
464: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
465: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
472: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
473: [[0,10,5,0,0,0,0,0],255,255,255,0],
|
||||
474: [[8,0,9,0,0,0,0,0],255,255,255,0],
|
||||
475: [[8,0,9,0,0,0,0,0],255,255,255,0],
|
||||
476: [[8,0,9,0,0,0,0,0],255,255,255,0],
|
||||
479: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
480: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
481: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
482: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
483: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
484: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
488: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
489: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
490: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
491: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
492: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
494: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
495: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
496: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
497: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
499: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
503: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
509: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
511: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
512: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
514: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
515: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
520: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
521: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
522: [[0,17,0,0,0,0,0,0],255,255,255,0],
|
||||
523: [[18,0,0,0,0,0,0,0],255,160,55,1],
|
||||
536: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
537: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
538: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
539: [[0,18,19,0,0,0,0,0],100,100,255,1],
|
||||
546: [[0,15,19,0,0,0,0,0],255,255,255,0],
|
||||
552: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
553: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
554: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
558: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
559: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
560: [[19,0,0,0,0,0,0,0],255,255,255,0],
|
||||
564: [[0,15,17,0,0,0,0,0],255,255,255,0],
|
||||
})
|
||||
|
||||
/** Canonical 1.13c Missile point light metadata from Missiles.txt [Id, Light, Red, Green, Blue, Flicker] */
|
||||
export const CANONICAL_MISSILE_LIGHTS: Readonly<Record<string, readonly [id: number, radius: number, r: number, g: number, b: number, flicker: number]>> = Object.freeze({
|
||||
"bighead1": [2,5,143,143,255,0],
|
||||
"bighead2": [3,5,143,143,255,0],
|
||||
"bighead3": [4,5,143,143,255,0],
|
||||
"bighead4": [5,5,143,143,255,0],
|
||||
"bighead5": [6,5,143,143,255,0],
|
||||
"firearrow": [12,5,255,178,64,0],
|
||||
"shafire1": [22,5,255,178,64,0],
|
||||
"shafire2": [23,5,255,178,64,0],
|
||||
"shafire3": [24,5,255,178,64,0],
|
||||
"shafire4": [25,5,255,178,64,0],
|
||||
"shafire5": [26,5,255,178,64,0],
|
||||
"magicarrow": [27,3,255,255,255,0],
|
||||
"icearrow": [28,5,81,81,255,0],
|
||||
"fireexplode": [29,13,255,178,64,0],
|
||||
"iceexplode": [30,11,81,81,255,0],
|
||||
"bigheadexp": [33,8,143,143,255,0],
|
||||
"shamanexp": [34,8,255,178,64,0],
|
||||
"coldarrow": [40,5,81,81,255,0],
|
||||
"explodingarrow": [41,5,255,178,64,0],
|
||||
"explodingarrowexp": [42,15,255,178,64,0],
|
||||
"plaguejavelin": [43,4,128,255,128,0],
|
||||
"explosivepotionexp": [51,13,255,178,64,0],
|
||||
"holybolt": [55,7,222,222,255,0],
|
||||
"chargedbolt": [56,3,255,255,255,4],
|
||||
"firebolt": [58,7,255,178,64,0],
|
||||
"icebolt": [59,7,81,81,255,0],
|
||||
"infernoflame1": [60,5,255,178,64,0],
|
||||
"infernoflame2": [61,5,255,178,64,0],
|
||||
"fireball": [62,7,255,178,64,0],
|
||||
"blaze": [67,5,255,178,64,4],
|
||||
"firewallmaker": [68,8,255,178,64,0],
|
||||
"firewall": [69,5,255,178,64,4],
|
||||
"unholybolt1": [77,5,255,255,255,0],
|
||||
"unholybolt2": [78,5,255,255,255,0],
|
||||
"unholybolt3": [79,5,255,255,255,0],
|
||||
"unholybolt4": [80,5,255,255,255,0],
|
||||
"immolationarrow": [85,9,255,178,64,0],
|
||||
"guidedarrow": [86,3,255,255,255,0],
|
||||
"freezingarrow": [87,5,81,81,255,0],
|
||||
"freezingarrowexp1": [88,11,81,81,255,0],
|
||||
"iceblast": [91,7,81,81,255,0],
|
||||
"blessedhammer": [92,5,222,222,255,0],
|
||||
"chainlightning": [93,4,255,255,255,0],
|
||||
"fistofares": [94,3,255,255,255,0],
|
||||
"chillblood": [95,4,81,81,255,0],
|
||||
"glacialspike": [96,5,81,81,255,0],
|
||||
"teleport": [97,6,255,255,255,0],
|
||||
"lightningbolt": [98,4,255,255,255,0],
|
||||
"lightninghit": [99,3,255,255,255,4],
|
||||
"meteorcenter": [101,4,255,64,64,0],
|
||||
"meteorexplode": [103,13,255,178,64,0],
|
||||
"firesmall": [104,2,255,178,64,2],
|
||||
"firemedium": [105,3,255,178,64,2],
|
||||
"monblizcenter": [106,9,81,81,255,0],
|
||||
"teeth": [114,3,255,255,255,0],
|
||||
"corpseexplosion": [115,5,255,178,64,0],
|
||||
"poisoncorpseexplosion": [116,5,255,178,64,0],
|
||||
"monstercorpseexplode": [117,5,255,178,64,0],
|
||||
"rogue2": [121,5,255,178,64,0],
|
||||
"rogue3": [122,5,81,81,255,0],
|
||||
"bat lightning bolt": [123,4,255,255,255,0],
|
||||
"bat lightning trail": [124,3,255,255,255,4],
|
||||
"skmage1": [125,5,255,255,255,0],
|
||||
"skmage2": [126,5,255,255,255,0],
|
||||
"skmage3": [127,5,255,255,255,0],
|
||||
"skmage4": [128,5,255,255,255,0],
|
||||
"vampirefireball": [129,8,255,178,64,0],
|
||||
"vampirefirewallmaker": [130,8,255,178,64,0],
|
||||
"vampirefirewall": [131,2,255,178,64,4],
|
||||
"vampiremeteorcenter": [133,4,255,64,64,0],
|
||||
"vampiremeteorexp": [134,13,255,178,64,0],
|
||||
"raven2": [136,9,255,178,64,0],
|
||||
"amphibiangoo1": [137,2,64,255,64,0],
|
||||
"amphibiangoo2": [138,2,64,255,64,0],
|
||||
"tentaclegoo": [139,2,64,255,64,0],
|
||||
"curseeffectred": [142,1,255,255,255,0],
|
||||
"fetishinferno1": [144,4,255,178,64,0],
|
||||
"fetishinferno2": [145,4,255,178,64,0],
|
||||
"cursecast": [147,1,255,255,255,0],
|
||||
"redlightmissile": [151,5,255,178,64,0],
|
||||
"greenlightmissile": [152,5,128,255,128,0],
|
||||
"bluelightmissile": [153,5,81,81,255,0],
|
||||
"whitelightmissile": [154,5,255,255,255,0],
|
||||
"corpsepoisoncloud": [155,2,128,255,128,0],
|
||||
"chillbloodcloud": [156,3,81,81,255,0],
|
||||
"chillbloodpuff": [157,3,81,81,255,0],
|
||||
"blizzardcenter": [158,9,81,81,255,0],
|
||||
"thunderstorm1": [166,10,255,255,255,0],
|
||||
"thunderstorm2": [167,10,255,255,255,0],
|
||||
"thunderstorm3": [168,10,255,255,255,0],
|
||||
"thunderstorm4": [169,10,255,255,255,0],
|
||||
"monsterlight": [170,4,255,255,255,0],
|
||||
"diablight": [172,3,255,255,255,0],
|
||||
"redemption": [173,6,255,255,255,0],
|
||||
"redemptionfail": [174,5,255,178,64,0],
|
||||
"handofgod": [175,10,255,255,255,0],
|
||||
"diabfire": [176,3,255,255,255,0],
|
||||
"electric throwaxe": [178,4,255,255,255,0],
|
||||
"diabwallmaker": [179,3,255,178,64,0],
|
||||
"diabwall": [180,3,255,178,64,4],
|
||||
"bonespear": [192,3,255,255,255,0],
|
||||
"bonespirit": [193,3,255,255,255,0],
|
||||
"lightunique": [195,3,255,255,255,4],
|
||||
"skbowarrow4": [199,5,255,178,64,0],
|
||||
"skbowarrow5": [200,5,255,255,255,0],
|
||||
"andypoisonbolt": [203,5,255,255,255,0],
|
||||
"lightningjavelin": [205,6,255,255,255,0],
|
||||
"lightningfury": [206,6,255,255,255,0],
|
||||
"bonewallmaker": [207,3,255,255,255,0],
|
||||
"necromage1": [208,5,255,255,255,0],
|
||||
"necromage2": [209,5,255,255,255,0],
|
||||
"necromage3": [210,5,255,255,255,0],
|
||||
"necromage4": [211,5,255,255,255,0],
|
||||
"chargedstrikebolt": [215,3,255,255,255,4],
|
||||
"buglightning": [225,3,255,255,255,4],
|
||||
"immolationfire": [230,5,255,178,64,4],
|
||||
"furylightning": [231,4,255,255,255,0],
|
||||
"lightningstrike": [232,4,255,255,255,0],
|
||||
"fistoftheheavensbolt": [234,8,222,222,255,0],
|
||||
"meteorfire": [240,3,255,178,64,2],
|
||||
"hydra": [247,5,255,255,255,0],
|
||||
"zakarumlight": [258,3,255,255,255,0],
|
||||
"frozenorb": [260,6,81,81,255,0],
|
||||
"frozenorbbolt": [261,4,81,81,255,0],
|
||||
"frozenorbnova": [262,4,81,81,255,0],
|
||||
"frozenorbexplode": [263,6,81,81,255,0],
|
||||
"chillingarmorbolt": [264,4,81,81,255,0],
|
||||
"chainlightning2": [267,4,255,255,255,0],
|
||||
"revivesmall": [268,5,255,178,64,0],
|
||||
"revivemedium": [269,5,255,178,64,0],
|
||||
"revivelarge": [270,5,255,178,64,0],
|
||||
"monglacialspike": [271,5,81,81,255,0],
|
||||
"mephisto": [276,9,255,255,255,0],
|
||||
"firehead": [277,8,255,178,64,0],
|
||||
"arcanelightningbolt": [279,8,178,178,255,0],
|
||||
"frogfire": [280,4,255,64,64,0],
|
||||
"frogcold": [281,4,64,64,255,0],
|
||||
"frogpois": [282,4,64,255,64,0],
|
||||
"desertfireball": [283,6,255,64,64,0],
|
||||
"brdeathcontrol": [284,6,255,255,255,0],
|
||||
"brdeathlightningbolt": [285,4,255,255,255,0],
|
||||
"denofevillight": [287,10,255,255,255,0],
|
||||
"towermist": [291,2,255,255,255,0],
|
||||
"towermisttrail": [292,2,255,255,255,0],
|
||||
"brdeathspirits1": [296,4,255,255,255,0],
|
||||
"brdeathspiritnu": [297,4,255,255,255,0],
|
||||
"brdeathspiritdt": [298,4,255,255,255,0],
|
||||
"mephistodeathcontrol": [299,6,255,255,255,0],
|
||||
"mephistofirewallmaker": [300,5,255,178,64,0],
|
||||
"mephistofirewall": [301,5,255,178,64,4],
|
||||
"mephistoexplosionbig": [303,13,255,178,64,0],
|
||||
"mephistoexplosionsmall": [305,13,255,178,64,0],
|
||||
"andyfirewallmaker": [308,8,255,178,64,0],
|
||||
"andyfirewall": [309,5,255,178,64,4],
|
||||
"andycolumnfirebase": [310,5,255,178,64,4],
|
||||
"willowisplightningbolt": [320,8,178,178,255,0],
|
||||
"queenpoisoncloud": [321,4,64,255,64,0],
|
||||
"undeadmissile1": [323,9,255,255,255,0],
|
||||
"undeadmissile2": [324,9,255,255,255,0],
|
||||
"undeadmissile3": [325,9,255,255,255,0],
|
||||
"undeadmissile4": [326,9,255,255,255,0],
|
||||
"monbonespirit": [329,3,255,255,255,0],
|
||||
"countessfirewall": [331,5,255,178,64,0],
|
||||
"towerchestspawner": [332,15,192,192,255,0],
|
||||
"hellmeteordown": [336,5,255,255,255,0],
|
||||
"hellmeteorball": [337,5,255,255,255,0],
|
||||
"horadriclightning": [339,3,255,255,255,0],
|
||||
"horadriclight": [340,2,255,255,255,0],
|
||||
"regurgitatorcorpse": [341,2,255,64,64,0],
|
||||
"regurgitatorcorpseexpl": [342,13,255,64,64,0],
|
||||
"highpriestlightning": [343,1,255,64,64,0],
|
||||
"radamentdeath": [347,15,255,255,255,0],
|
||||
"taintedsunflash": [351,4,255,255,255,0],
|
||||
"healingbolt": [357,2,222,222,255,0],
|
||||
"orbmist": [368,2,255,125,125,0],
|
||||
"orbmisttrail": [369,2,255,125,125,0],
|
||||
"orbmistfade": [370,2,255,125,125,0],
|
||||
"hfcontrol": [373,8,255,255,255,0],
|
||||
"izual mist loop": [380,2,255,255,255,0],
|
||||
"izual lightning": [382,2,255,255,255,0],
|
||||
"cairn stones bolt": [384,10,255,255,255,0],
|
||||
"bomb explosion": [387,5,255,178,64,0],
|
||||
"shock field on ground": [389,3,255,255,255,0],
|
||||
"catapultchargedball": [407,5,143,143,255,0],
|
||||
"catapultchargedballbolt": [408,3,255,255,255,4],
|
||||
"catapult cold ball": [416,5,143,143,255,0],
|
||||
"catapult plague ball": [419,5,143,143,255,0],
|
||||
"catapult meteor ball": [422,5,143,143,255,0],
|
||||
"catapult meteor fire": [423,5,255,178,64,0],
|
||||
"healing vortex": [425,5,255,178,64,0],
|
||||
"explodingjavalinexp": [430,15,255,178,64,0],
|
||||
"lightjavalintrail": [432,3,255,255,255,4],
|
||||
"lightjavalinexplosion": [433,3,255,255,255,4],
|
||||
"icejavalinexplode": [435,11,81,81,255,0],
|
||||
"advlighttrailingjav2": [439,3,255,255,255,4],
|
||||
"advlightjavexplode": [440,3,255,255,255,4],
|
||||
"sucfireball": [441,10,255,178,64,0],
|
||||
"sucfireballexplode": [442,13,255,178,64,0],
|
||||
"sucfireballtrail": [443,5,255,178,64,4],
|
||||
"sucshockfieldmissile": [444,6,255,255,255,0],
|
||||
"sucshockfield": [446,5,255,178,64,0],
|
||||
"imp charged bolt": [450,3,255,255,255,4],
|
||||
"moltenboulderfirepath": [455,5,255,178,64,0],
|
||||
"firestorm": [457,5,255,178,64,4],
|
||||
"erruption crack 1": [462,5,255,178,64,0],
|
||||
"erruption crack 2": [463,5,255,178,64,0],
|
||||
"volcano overlay fire": [480,10,255,64,64,0],
|
||||
"lurecenter": [486,3,81,81,255,0],
|
||||
"impmiss1": [488,5,255,64,64,0],
|
||||
"impmiss2": [489,5,255,64,64,0],
|
||||
"impmiss3": [490,5,255,64,64,0],
|
||||
"impmiss4": [491,5,255,64,64,0],
|
||||
"impmiss5": [492,5,255,64,64,0],
|
||||
"sentrychargedbolt": [495,3,255,255,255,4],
|
||||
"recycler explosion": [501,5,255,178,64,0],
|
||||
"lightningtalons": [512,4,255,255,255,0],
|
||||
"lightningtalonstrail": [513,3,255,255,255,4],
|
||||
"phoenixtrail": [514,5,255,178,64,4],
|
||||
"wake of destruction maker": [517,8,255,178,64,0],
|
||||
"wake of destruction": [518,5,255,178,64,4],
|
||||
"tigerfury": [520,3,255,255,255,0],
|
||||
"tigerfurytrail": [521,5,255,178,64,4],
|
||||
"inferno sentry 1": [523,5,255,178,64,0],
|
||||
"inferno sentry 2": [524,5,255,178,64,0],
|
||||
"sentrylightningbolt": [526,4,255,255,255,0],
|
||||
"sentrylightninghit": [527,3,255,255,255,4],
|
||||
"ancient death center": [541,2,255,125,125,0],
|
||||
"ancient death cloud": [542,2,255,125,125,0],
|
||||
"chainlightningcharge up": [544,4,255,255,255,0],
|
||||
"pain worm appear": [545,5,255,178,64,0],
|
||||
"baal taunt lightning trail": [548,4,255,255,255,0],
|
||||
"baal spawn monsters": [550,5,81,81,255,0],
|
||||
"blade shield missile": [552,5,255,255,255,0],
|
||||
"baal inferno": [554,5,255,255,255,0],
|
||||
"baal nova": [555,5,255,255,255,0],
|
||||
"fistsoffireexplode": [556,15,255,178,64,0],
|
||||
"fistsoffirefirewall": [557,5,255,178,64,0],
|
||||
"clawsofthunderbolt": [558,3,255,255,255,4],
|
||||
"bladesoficeexplode": [560,11,81,81,255,0],
|
||||
"royalstrikemeteorcenter": [564,4,255,64,64,0],
|
||||
"royalstrikemeteorexplode": [566,13,255,178,64,0],
|
||||
"royalstrikemeteorfire": [567,5,255,178,64,0],
|
||||
"royalstrikechainlightning": [568,4,255,255,255,0],
|
||||
"royalstrikechaosice": [569,4,81,81,255,0],
|
||||
"highpriestlightning2": [574,12,255,64,64,0],
|
||||
"infernoflame3": [575,5,255,178,64,0],
|
||||
"armageddonexplosion": [580,5,255,255,255,0],
|
||||
"baalcorpseexplodedelay": [587,5,255,178,64,0],
|
||||
"baalcorpseexplodeexpl": [588,5,255,178,64,0],
|
||||
"baal cold maker": [589,5,143,143,255,0],
|
||||
"baal cold trail": [590,5,143,143,255,0],
|
||||
"baal spawn monsters exp": [591,5,81,81,255,0],
|
||||
"impmiss21": [592,5,255,64,64,0],
|
||||
"impmiss22": [593,5,255,64,64,0],
|
||||
"impmiss23": [594,5,255,64,64,0],
|
||||
"impmiss24": [595,5,255,64,64,0],
|
||||
"impmiss25": [596,5,255,64,64,0],
|
||||
"baalteleport": [613,3,128,64,64,0],
|
||||
"baalclonedeath": [614,3,128,64,64,0],
|
||||
"armageddonfire": [623,5,255,178,64,0],
|
||||
"icesparkle": [624,2,255,178,64,0],
|
||||
"baalfx control": [625,10,255,178,64,0],
|
||||
"baalfx baal head 1": [632,5,255,178,64,0],
|
||||
"baalfx baal head 2": [633,5,255,178,64,0],
|
||||
"baalfx baal head 3": [634,5,255,178,64,0],
|
||||
"blessedhammerex": [640,5,222,222,255,0],
|
||||
"sentrylightningbolt2": [641,4,255,255,255,0],
|
||||
"sentrylightninghit2": [642,3,255,255,255,1],
|
||||
"skbowarrow6": [644,2,255,178,64,0],
|
||||
"skbowarrow7": [645,2,81,81,255,0],
|
||||
"skbowarrow8": [646,2,255,178,64,0],
|
||||
"bighead6": [647,3,143,143,255,0],
|
||||
"viper_fire": [650,3,255,255,255,0],
|
||||
"viper_bonespear": [652,3,255,255,255,0],
|
||||
"countessfirewallmaker": [653,8,255,178,64,0],
|
||||
"skmagepois": [661,2,255,255,255,0],
|
||||
"skmagecold": [662,2,255,255,255,0],
|
||||
"skmagefire": [663,2,255,255,255,0],
|
||||
"skmageltng": [664,2,255,255,255,0],
|
||||
"succubusmiss": [665,2,255,64,64,0],
|
||||
"willowisplightningbolt2": [666,8,178,178,255,0],
|
||||
"impmissex": [669,5,255,64,64,0],
|
||||
"diablogeddonexplosion": [673,5,255,255,255,0],
|
||||
"diablogeddonfire": [674,5,255,178,64,0],
|
||||
"megademoninferno": [675,4,255,178,64,0],
|
||||
"trapfirebolt": [676,7,255,178,64,0],
|
||||
"mephlight": [680,4,255,255,255,0],
|
||||
"vampiremeteorfire": [681,3,255,178,64,2],
|
||||
})
|
||||
|
||||
/** Canonical 1.13c Overlay point light metadata from Overlay.txt [Radius, InitRadius, Red, Green, Blue] */
|
||||
export const CANONICAL_OVERLAY_LIGHTS: Readonly<Record<number, readonly [radius: number, initRadius: number, r: number, g: number, b: number]>> = Object.freeze({
|
||||
1: [11,11,255,255,255],
|
||||
2: [10,1,255,193,103],
|
||||
3: [6,6,255,193,103],
|
||||
4: [10,1,210,210,255],
|
||||
5: [6,6,210,210,255],
|
||||
6: [10,1,255,255,200],
|
||||
7: [6,6,255,255,200],
|
||||
8: [10,1,211,148,255],
|
||||
9: [6,6,211,148,255],
|
||||
10: [6,6,211,148,255],
|
||||
27: [8,1,255,184,199],
|
||||
29: [9,1,255,178,64],
|
||||
30: [9,1,255,178,64],
|
||||
31: [9,1,81,81,255],
|
||||
32: [9,1,81,81,255],
|
||||
33: [9,1,81,81,255],
|
||||
34: [9,1,255,255,255],
|
||||
35: [14,14,255,193,103],
|
||||
36: [14,14,210,210,255],
|
||||
37: [8,1,255,193,103],
|
||||
38: [13,1,200,200,255],
|
||||
39: [13,1,200,200,255],
|
||||
40: [13,1,200,200,255],
|
||||
41: [6,1,81,81,255],
|
||||
42: [6,1,81,81,255],
|
||||
43: [6,1,81,81,255],
|
||||
44: [6,1,81,81,255],
|
||||
45: [5,1,255,255,200],
|
||||
47: [6,1,255,255,255],
|
||||
48: [6,6,255,255,255],
|
||||
49: [6,1,255,255,255],
|
||||
50: [2,2,255,255,255],
|
||||
51: [6,1,255,255,255],
|
||||
69: [10,1,255,193,103],
|
||||
75: [6,1,255,255,255],
|
||||
76: [6,1,255,255,255],
|
||||
77: [6,1,255,193,103],
|
||||
78: [2,2,103,193,255],
|
||||
79: [2,2,255,193,103],
|
||||
81: [1,3,255,193,103],
|
||||
87: [3,3,255,255,255],
|
||||
88: [3,3,255,255,255],
|
||||
89: [3,3,255,255,255],
|
||||
90: [3,3,255,255,255],
|
||||
91: [3,3,255,255,255],
|
||||
92: [3,3,255,255,255],
|
||||
93: [3,3,255,255,255],
|
||||
94: [3,3,255,255,255],
|
||||
95: [3,3,81,81,255],
|
||||
96: [8,8,255,255,255],
|
||||
97: [8,8,255,255,255],
|
||||
98: [8,1,255,255,255],
|
||||
110: [11,11,255,255,255],
|
||||
123: [10,1,210,210,255],
|
||||
128: [6,1,255,64,64],
|
||||
129: [6,1,255,210,210],
|
||||
130: [6,1,255,210,210],
|
||||
131: [6,1,255,255,255],
|
||||
133: [6,1,255,255,255],
|
||||
135: [6,1,255,255,255],
|
||||
136: [6,1,255,255,255],
|
||||
137: [6,1,255,255,255],
|
||||
146: [2,2,255,255,255],
|
||||
153: [9,1,255,178,64],
|
||||
157: [2,2,255,255,255],
|
||||
158: [2,2,255,255,255],
|
||||
164: [5,2,255,255,255],
|
||||
169: [2,2,255,64,64],
|
||||
171: [4,1,255,255,255],
|
||||
184: [4,1,178,178,178],
|
||||
185: [2,2,255,64,64],
|
||||
186: [9,1,255,255,255],
|
||||
187: [2,2,255,255,255],
|
||||
214: [2,1,255,210,210],
|
||||
215: [3,3,255,255,255],
|
||||
216: [3,3,255,255,255],
|
||||
221: [3,1,255,255,255],
|
||||
222: [3,1,255,255,255],
|
||||
223: [6,1,255,193,103],
|
||||
225: [6,1,255,64,64],
|
||||
226: [6,1,255,64,64],
|
||||
227: [6,1,255,64,64],
|
||||
})
|
||||
|
||||
|
||||
export interface MonsterLightConfig {
|
||||
readonly radius: number
|
||||
readonly intensity: number
|
||||
readonly r: number
|
||||
readonly g: number
|
||||
readonly b: number
|
||||
}
|
||||
|
||||
export interface ObjectLightConfig {
|
||||
readonly radius: number
|
||||
readonly intensity: number
|
||||
readonly r: number
|
||||
readonly g: number
|
||||
readonly b: number
|
||||
readonly flicker: number
|
||||
}
|
||||
|
||||
export interface MissileLightConfig {
|
||||
readonly radius: number
|
||||
readonly intensity: number
|
||||
readonly r: number
|
||||
readonly g: number
|
||||
readonly b: number
|
||||
readonly flicker: number
|
||||
}
|
||||
|
||||
export interface OverlayLightConfig {
|
||||
readonly radius: number
|
||||
readonly initRadius: number
|
||||
readonly intensity: number
|
||||
readonly r: number
|
||||
readonly g: number
|
||||
readonly b: number
|
||||
}
|
||||
|
||||
export function getMonsterLightConfig(monsterId: string): MonsterLightConfig | null {
|
||||
const entry = CANONICAL_MONSTER_LIGHTS[monsterId.trim().toLowerCase()]
|
||||
if (!entry) return null
|
||||
return {
|
||||
radius: entry[0],
|
||||
intensity: 255,
|
||||
r: entry[1],
|
||||
g: entry[2],
|
||||
b: entry[3],
|
||||
}
|
||||
}
|
||||
|
||||
export function getObjectLightConfig(objectId: number, mode?: number): ObjectLightConfig | null {
|
||||
const entry = CANONICAL_OBJECT_LIGHTS[objectId]
|
||||
if (!entry) return null
|
||||
const [lits, r, g, b, flicker] = entry
|
||||
let radius = 0
|
||||
if (mode !== undefined && mode >= 0 && mode < lits.length) {
|
||||
radius = lits[mode] ?? 0
|
||||
} else {
|
||||
radius = (lits[1] ?? 0) > 0 ? (lits[1] ?? 0) : ((lits[2] ?? 0) > 0 ? (lits[2] ?? 0) : Math.max(...lits))
|
||||
}
|
||||
if (radius <= 0) return null
|
||||
return {
|
||||
radius,
|
||||
intensity: 255,
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
flicker,
|
||||
}
|
||||
}
|
||||
|
||||
const MISSILE_LIGHT_BY_ID: Map<number, readonly [id: number, radius: number, r: number, g: number, b: number, flicker: number]> = (() => {
|
||||
const map = new Map<number, readonly [id: number, radius: number, r: number, g: number, b: number, flicker: number]>()
|
||||
for (const entry of Object.values(CANONICAL_MISSILE_LIGHTS)) {
|
||||
map.set(entry[0], entry)
|
||||
}
|
||||
return map
|
||||
})()
|
||||
|
||||
export function getMissileLightConfig(idOrName: string | number): MissileLightConfig | null {
|
||||
let entry: readonly [id: number, radius: number, r: number, g: number, b: number, flicker: number] | undefined
|
||||
if (typeof idOrName === 'number') {
|
||||
entry = MISSILE_LIGHT_BY_ID.get(idOrName)
|
||||
} else {
|
||||
entry = CANONICAL_MISSILE_LIGHTS[idOrName.trim().toLowerCase()]
|
||||
}
|
||||
if (!entry) return null
|
||||
return {
|
||||
radius: entry[1],
|
||||
intensity: 255,
|
||||
r: entry[2],
|
||||
g: entry[3],
|
||||
b: entry[4],
|
||||
flicker: entry[5],
|
||||
}
|
||||
}
|
||||
|
||||
export function getOverlayLightConfig(overlayId: number): OverlayLightConfig | null {
|
||||
const entry = CANONICAL_OVERLAY_LIGHTS[overlayId]
|
||||
if (!entry) return null
|
||||
return {
|
||||
radius: entry[0],
|
||||
initRadius: entry[1],
|
||||
intensity: 255,
|
||||
r: entry[2],
|
||||
g: entry[3],
|
||||
b: entry[4],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import {
|
|||
import type { AtlasFrame } from '../render/atlas.ts'
|
||||
import {
|
||||
LightningTrailManager,
|
||||
drawElectricBoltFallback,
|
||||
} from '../render/missile-renderer.ts'
|
||||
import {
|
||||
MISSILE_METAS,
|
||||
|
|
@ -29,7 +28,6 @@ export {
|
|||
LightningTrailManager,
|
||||
MISSILE_METAS,
|
||||
OVERLAY_METAS,
|
||||
drawElectricBoltFallback,
|
||||
getMissileTxtData,
|
||||
velocityToDccDirection,
|
||||
type MissileFrameTuple,
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export interface LightSource {
|
|||
/** Blue channel [0..255]. */
|
||||
readonly blue: number
|
||||
/** Optional flicker perturbation flag or offset. */
|
||||
readonly flicker?: number
|
||||
readonly flicker?: number | undefined
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,231 @@
|
|||
import { describe, it, expect } from 'vitest'
|
||||
import {
|
||||
applyLightingInputs,
|
||||
CANONICAL_MONSTER_LIGHTS,
|
||||
CANONICAL_OBJECT_LIGHTS,
|
||||
CANONICAL_MISSILE_LIGHTS,
|
||||
CANONICAL_OVERLAY_LIGHTS,
|
||||
getMonsterLightConfig,
|
||||
getObjectLightConfig,
|
||||
getMissileLightConfig,
|
||||
getOverlayLightConfig,
|
||||
} from '../src/client/scene/lighting-presets.ts'
|
||||
import type { CollisionGrid } from '../src/common/world/d2map.ts'
|
||||
import { LightGrid, LIGHT_GRID_SIZE } from '../src/common/drlg/light-grid.ts'
|
||||
import {
|
||||
velocityToDccDirection,
|
||||
pointsToDccDirection,
|
||||
} from '../src/client/render/missiles-meta.ts'
|
||||
import {
|
||||
fragmentShaderSource,
|
||||
} from '../src/client/render/renderer.ts'
|
||||
|
||||
describe('Issue #606: Data-Driven Point Lights & Authentic 1.13c Player Radius', () => {
|
||||
const mockCollision: CollisionGrid = {
|
||||
cellsX: 10,
|
||||
cellsY: 10,
|
||||
gridWidth: 50,
|
||||
gridHeight: 50,
|
||||
originX: 0,
|
||||
originY: 0,
|
||||
blocked: new Uint8Array(2500),
|
||||
collisionMasks: new Uint16Array(2500),
|
||||
} as unknown as CollisionGrid
|
||||
|
||||
it('provides base 1.13c player light radius of 11 sub-tiles with pure white color', () => {
|
||||
const lightGrid = new LightGrid()
|
||||
let appliedLighting: any = null
|
||||
const renderer = {
|
||||
setLighting: (params: any) => {
|
||||
appliedLighting = params
|
||||
},
|
||||
}
|
||||
|
||||
applyLightingInputs(
|
||||
{
|
||||
collisionGrid: mockCollision,
|
||||
playerSubTile: { x: 50, y: 50 },
|
||||
pointLights: [],
|
||||
preset: 'auto',
|
||||
levelId: 1,
|
||||
tick: 0,
|
||||
environmentIntensity: 100,
|
||||
},
|
||||
{ lightGrid, renderer },
|
||||
)
|
||||
|
||||
expect(appliedLighting).not.toBeNull()
|
||||
// Player center cell is (24, 24)
|
||||
const centerIdx = 24 * LIGHT_GRID_SIZE + 24
|
||||
const intensity = lightGrid.intensity[centerIdx]
|
||||
expect(intensity).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
it('adds player light radius bonus correctly', () => {
|
||||
const gridNoBonus = new LightGrid()
|
||||
const gridWithBonus = new LightGrid()
|
||||
const renderer = { setLighting: () => {} }
|
||||
|
||||
applyLightingInputs(
|
||||
{
|
||||
collisionGrid: mockCollision,
|
||||
playerSubTile: { x: 50, y: 50 },
|
||||
pointLights: [],
|
||||
preset: 'auto',
|
||||
levelId: 1,
|
||||
tick: 0,
|
||||
playerLightRadiusBonus: 0,
|
||||
environmentIntensity: 0,
|
||||
},
|
||||
{ lightGrid: gridNoBonus, renderer },
|
||||
)
|
||||
|
||||
applyLightingInputs(
|
||||
{
|
||||
collisionGrid: mockCollision,
|
||||
playerSubTile: { x: 50, y: 50 },
|
||||
pointLights: [],
|
||||
preset: 'auto',
|
||||
levelId: 1,
|
||||
tick: 0,
|
||||
playerLightRadiusBonus: 5,
|
||||
environmentIntensity: 0,
|
||||
},
|
||||
{ lightGrid: gridWithBonus, renderer },
|
||||
)
|
||||
|
||||
// Check cells 2 cells away from player center (24, 24) -> (26, 24)
|
||||
const targetIdx = 24 * LIGHT_GRID_SIZE + 26
|
||||
const intensityNoBonus = gridNoBonus.intensity[targetIdx]!
|
||||
const intensityWithBonus = gridWithBonus.intensity[targetIdx]!
|
||||
|
||||
expect(intensityWithBonus).toBeGreaterThanOrEqual(intensityNoBonus)
|
||||
})
|
||||
|
||||
it('preserves true pitch-black darkness when ambient environment intensity is 0', () => {
|
||||
const lightGrid = new LightGrid()
|
||||
let appliedLighting: any = null
|
||||
const renderer = {
|
||||
setLighting: (params: any) => {
|
||||
appliedLighting = params
|
||||
},
|
||||
}
|
||||
|
||||
applyLightingInputs(
|
||||
{
|
||||
collisionGrid: mockCollision,
|
||||
playerSubTile: { x: 50, y: 50 },
|
||||
pointLights: [],
|
||||
preset: 'auto',
|
||||
levelId: 1,
|
||||
tick: 0,
|
||||
environmentIntensity: 0,
|
||||
environmentColor: [20, 20, 20],
|
||||
},
|
||||
{ lightGrid, renderer },
|
||||
)
|
||||
|
||||
expect(appliedLighting.ambient[0]).toBe(0)
|
||||
expect(appliedLighting.ambient[1]).toBe(0)
|
||||
expect(appliedLighting.ambient[2]).toBe(0)
|
||||
})
|
||||
|
||||
it('includes canonical 1.13c static tables for lit entities', () => {
|
||||
expect(Object.keys(CANONICAL_MONSTER_LIGHTS).length).toBeGreaterThanOrEqual(80)
|
||||
expect(Object.keys(CANONICAL_OBJECT_LIGHTS).length).toBeGreaterThanOrEqual(150)
|
||||
expect(Object.keys(CANONICAL_MISSILE_LIGHTS).length).toBeGreaterThanOrEqual(100)
|
||||
expect(Object.keys(CANONICAL_OVERLAY_LIGHTS).length).toBeGreaterThanOrEqual(40)
|
||||
|
||||
// Andariel has a lit config in monstats2
|
||||
const andariel = getMonsterLightConfig('andariel')
|
||||
expect(andariel).toBeDefined()
|
||||
expect(andariel?.radius).toBeGreaterThan(0)
|
||||
|
||||
// Mephisto has a lit config in monstats2
|
||||
const mephisto = getMonsterLightConfig('mephisto')
|
||||
expect(mephisto).toBeDefined()
|
||||
expect(mephisto?.radius).toBeGreaterThan(0)
|
||||
|
||||
// Firebolt missile (missiles.txt id 58) has a lit config
|
||||
const firebolt = getMissileLightConfig('firebolt')
|
||||
expect(firebolt).toBeDefined()
|
||||
expect(firebolt?.radius).toBe(7)
|
||||
expect(firebolt?.r).toBe(255)
|
||||
const fireboltById = getMissileLightConfig(58)
|
||||
expect(fireboltById?.radius).toBe(7)
|
||||
|
||||
// Torch / campfire object
|
||||
const campfire = getObjectLightConfig(14)
|
||||
expect(campfire).toBeDefined()
|
||||
|
||||
// Overlay lookup
|
||||
const overlay = getOverlayLightConfig(1)
|
||||
expect(overlay).toBeDefined()
|
||||
})
|
||||
|
||||
it('processes point lights with optional flicker into lightGrid', () => {
|
||||
const lightGrid = new LightGrid()
|
||||
const renderer = { setLighting: () => {} }
|
||||
|
||||
applyLightingInputs(
|
||||
{
|
||||
collisionGrid: mockCollision,
|
||||
playerSubTile: { x: 50, y: 50 },
|
||||
pointLights: [
|
||||
{
|
||||
subTileX: 60,
|
||||
subTileY: 60,
|
||||
radius: 10,
|
||||
intensity: 200,
|
||||
r: 255,
|
||||
g: 100,
|
||||
b: 50,
|
||||
flicker: 1,
|
||||
},
|
||||
],
|
||||
preset: 'auto',
|
||||
levelId: 1,
|
||||
tick: 0,
|
||||
environmentIntensity: 0,
|
||||
},
|
||||
{ lightGrid, renderer },
|
||||
)
|
||||
|
||||
const cellX = Math.floor((60 - lightGrid.originSubX) / 8)
|
||||
const cellY = Math.floor((60 - lightGrid.originSubY) / 8)
|
||||
const cellIdx = cellY * LIGHT_GRID_SIZE + cellX
|
||||
expect(lightGrid.intensity[cellIdx]).toBeGreaterThan(0)
|
||||
expect(lightGrid.red[cellIdx]).toBe(255)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Issue #614: Elevation-Aware Shaders & Linear Dynamic Light', () => {
|
||||
it('uses authentic linear dynamic lighting without arbitrary non-linear curve in fragment shader', () => {
|
||||
const source = fragmentShaderSource(8)
|
||||
expect(source).toContain('vec3 dynamicLight = lightSample.rgb;')
|
||||
expect(source).not.toContain('0.22 + 0.78 * lightSample.a')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Issue #621: Dead Code Cleanup & Canonical Direction Mapping', () => {
|
||||
it('pointsToDccDirection maps positions to 0-63 DCC frames', () => {
|
||||
// South in world coordinates (dx > 0, dy > 0): dir64 = 0 -> DCC-16 index 4
|
||||
const south = pointsToDccDirection(0, 0, 10, 10, 16)
|
||||
expect(south).toBe(4)
|
||||
|
||||
// Result is always a valid DCC direction within range
|
||||
const anyDir = pointsToDccDirection(10, 20, 50, 40, 16)
|
||||
expect(anyDir).toBeGreaterThanOrEqual(0)
|
||||
expect(anyDir).toBeLessThan(16)
|
||||
})
|
||||
|
||||
it('velocityToDccDirection maps velocity components to valid DCC frames', () => {
|
||||
// South velocity (vx = 0, vy > 0): DCC-16 index 4
|
||||
const south = velocityToDccDirection(0, 10, 16)
|
||||
expect(south).toBe(4)
|
||||
|
||||
const east = velocityToDccDirection(10, 0, 16)
|
||||
expect(east).toBeGreaterThanOrEqual(0)
|
||||
expect(east).toBeLessThan(16)
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue