Files
benilla-octo/crates/benilla-formats/src/environmental_damage.rs
T
SamandClaude Fable 5 2ad0043523 benilla: initial public release
A from-scratch World of Warcraft 1.12.1 client in Rust + Bevy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:27:01 +07:00

106 lines
4.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! **EnvironmentalDamage.dbc** — the environmental-damage feedback table: damage type →
//! `SpellVisualKit` id, played on the victim when `SMSG_ENVIRONMENTALDAMAGELOG` arrives (the
//! fall-landing dust puff and its five siblings).
//!
//! The byte-verified client shape (wow-re `sound/scratch/uisound-tables.md`): init `0x603900`
//! zeroes a **6-slot table** (`[0xc4d8e4]`) and, for each record with `field1 < 6`, stores
//! `slot[field1] = field2` — so field 1 is the `EnvironmentalDamageType` enum (0 exhausted ·
//! 1 drowning · 2 fall · 3 lava · 4 slime · 5 fire; the wire's `damage_type`) and field 2 the
//! kit id. Sole reader `0x624fcc` (in the 0x1FC consequence method `0x624f30`) bounds the kit id
//! against `SpellVisualKit.dbc` and plays it. Build 5875 data: 0→871, 1→870, 2→**1066** (the
//! "DustCloud Land" kit), 3→1064, 4→1065, 5→1067.
use crate::Chain;
use anyhow::{Context, Result};
use benilla_dbc::{FieldType, Schema, SchemaField};
use crate::dbc::{parse, u32_at};
const ENVIRONMENTAL_DAMAGE: &str = "DBFilesClient\\EnvironmentalDamage.dbc";
/// The 6-slot damage-type → `SpellVisualKit` table (the client's `[0xc4d8e4]`).
pub struct EnvironmentalDamageTable {
kits: [u32; 6],
}
impl EnvironmentalDamageTable {
/// The `SpellVisualKit` id for a wire `damage_type` (0–5). `None` for an out-of-range type or
/// an empty slot — the client's `field1 < 6` init guard and zero-init respectively.
pub fn kit_id(&self, damage_type: u8) -> Option<u32> {
self.kits
.get(usize::from(damage_type))
.copied()
.filter(|&k| k != 0)
}
}
/// EnvironmentalDamage.dbc — 3 fields in build 5875: ID, the damage-type enum, the kit id.
fn schema() -> Schema {
let mut s = Schema::new("EnvironmentalDamage");
for name in ["ID", "DamageType", "VisualKit"] {
s.add_field(SchemaField::new(name, FieldType::UInt32));
}
s
}
/// Load the table from the patch chain, exactly as the client's init `0x603900` fills its 6-slot
/// store: zeroed slots, `slot[DamageType] = VisualKit` for each in-range record.
pub fn load_environmental_damage(chain: &mut Chain) -> Result<EnvironmentalDamageTable> {
let bytes = chain
.read_file(ENVIRONMENTAL_DAMAGE)
.with_context(|| format!("reading {ENVIRONMENTAL_DAMAGE}"))?;
table_from(&bytes)
}
/// The fill itself, split from the chain read so the golden test drives the identical path.
fn table_from(bytes: &[u8]) -> Result<EnvironmentalDamageTable> {
let rs = parse(bytes, schema(), "EnvironmentalDamage")?;
let mut kits = [0u32; 6];
for r in rs.records() {
if let (Some(ty), Some(kit)) = (u32_at(r, 1), u32_at(r, 2)) {
if let Some(slot) = kits.get_mut(ty as usize) {
*slot = kit;
}
}
}
Ok(EnvironmentalDamageTable { kits })
}
#[cfg(test)]
mod tests {
use super::*;
/// A hand-built WDBC with the real 5875 rows (verified via benilla-extract 2026-07-16) plus an
/// out-of-range type-9 row that must hit the `field1 < 6` init guard and load nowhere.
#[test]
fn table_loads_by_damage_type_with_the_range_guard() {
let rows: &[[u32; 3]] = &[
[1, 0, 871],
[2, 1, 870],
[3, 2, 1066],
[4, 3, 1064],
[5, 4, 1065],
[6, 5, 1067],
[7, 9, 999], // out of range: the client's `cmp edx,6; jae` skip
];
let mut dbc = Vec::new();
dbc.extend_from_slice(b"WDBC");
dbc.extend_from_slice(&(rows.len() as u32).to_le_bytes());
dbc.extend_from_slice(&3u32.to_le_bytes()); // fields
dbc.extend_from_slice(&12u32.to_le_bytes()); // record size
dbc.extend_from_slice(&1u32.to_le_bytes()); // string block
for row in rows {
for v in row {
dbc.extend_from_slice(&v.to_le_bytes());
}
}
dbc.push(0); // the string block
let table = table_from(&dbc).unwrap();
assert_eq!(table.kit_id(2), Some(1066), "fall → the DustCloud Land kit");
assert_eq!(table.kit_id(0), Some(871));
assert_eq!(table.kit_id(5), Some(1067));
assert_eq!(table.kit_id(9), None, "out-of-range type");
}
}