Part of #41: the comments of `benilla-formats` (the library, its extract tools, examples and tests) are rewritten to the rule in `docs/METHOD.md`, which is what the code does and the reference fact behind it, in a line. 19,654 comment lines become 7,264. Fixes #105: the WMO collision comments now name walking's box-query leaf `0x6bca50` and the camera and line-of-sight segment-query leaf `0x6bc700`, in `models/collision.rs`, `examples/wmo_bsp.rs` and `tests/chain.rs`. - **The mechanical strip.** The citations that were only a private record number go first, and each edit is shown to be a pure removal. - **Wrong facts corrected.** The rewrite corrects the comments that stated wrong facts. Among them: - A placed doodad's material alpha reads file slot 0 on its spawn clock, while the reference reads bone 0's armed sequence, re-rolled every play window. The old text said doodads are armed once at load. - The fog end is stored ×36 and scaled once at load (`0x6d6090`). It is not raw. - The MCSH bit picks a map doodad's sun scale, 1.0 lit or 0.5 shadowed. 2.5 belongs to an entity's light node. - `SpellRec` is the row as stored, column × 4, not a translated copy. - A global sequence's clock is anchored at the instance's attach time (`0x714352`). - Particle twinkle is a gated size multiplier (`0x7b2adc`). - The blob shadow sizes from the Stand box. - Data claims are checked against the install: the weapon-trail kits' parameters and alphas, the beam rows' noise, and the maps' `field_16`. - Dozens more turned up on the way: renamed or deleted items, docs sitting on the wrong item, stale counts and links. - **Deviation labels.** "Deviation:" now marks only a choice with a reason. An unbuilt behaviour says so plainly. - **Verification.** Every file strips to identical code, and `cargo fmt --check` is clean. Four second readers compared every file old against new, restored the facts the rewrite had dropped, and checked each new citation against the reference, the code and the shipped data. String literals are untouched; they are #92. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
174 lines
5.3 KiB
Rust
174 lines
5.3 KiB
Rust
//! `WorldMapContinent.dbc`: each continent's ADT tile bounds and its place on the world map sheet,
|
|
//! read by the builder `0x4a5d00` and the world-mode projections `0x4a72b0` and `0x4a7360`.
|
|
//! vmangos does not load it.
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use anyhow::{Context, Result};
|
|
use benilla_dbc::{FieldType, Schema, SchemaField};
|
|
|
|
use crate::dbc::{f32_at, parse, u32_at};
|
|
use crate::Chain;
|
|
|
|
const WORLD_MAP_CONTINENT: &str = "DBFilesClient\\WorldMapContinent.dbc";
|
|
|
|
/// One `WorldMapContinent.dbc` row.
|
|
#[derive(Clone, Debug)]
|
|
pub struct WorldMapContinent {
|
|
/// `Map.dbc` id: 0 Azeroth, 1 Kalimdor.
|
|
pub map_id: u32,
|
|
/// ADT tile column bounds, which `0x4a5d00` turns into the continent's sheet rect.
|
|
pub left_boundary: u32,
|
|
pub right_boundary: u32,
|
|
/// ADT tile row bounds.
|
|
pub top_boundary: u32,
|
|
pub bottom_boundary: u32,
|
|
/// World-sheet offsets, x on the u axis and y on v
|
|
/// (`0x4a7360`: `u = offset_x/62.625 + 0.5 - wy·k·scale`).
|
|
pub offset_x: f32,
|
|
pub offset_y: f32,
|
|
/// The world-sheet scale, 0.75 on both rows; there is no separate world zoom.
|
|
pub scale: f32,
|
|
/// The taxi map's rect in world units; no map projection reads it.
|
|
pub taxi_min: (f32, f32),
|
|
pub taxi_max: (f32, f32),
|
|
}
|
|
|
|
/// `WorldMapContinent.dbc` rows keyed by `MapID`.
|
|
pub struct WorldMapContinentCatalog {
|
|
by_map_id: HashMap<u32, WorldMapContinent>,
|
|
}
|
|
|
|
impl WorldMapContinentCatalog {
|
|
pub fn get(&self, map_id: u32) -> Option<&WorldMapContinent> {
|
|
self.by_map_id.get(&map_id)
|
|
}
|
|
|
|
pub fn len(&self) -> usize {
|
|
self.by_map_id.len()
|
|
}
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
self.by_map_id.is_empty()
|
|
}
|
|
}
|
|
|
|
fn schema() -> Schema {
|
|
let mut s = Schema::new("WorldMapContinent");
|
|
for name in [
|
|
"ID",
|
|
"MapID",
|
|
"LeftBoundary",
|
|
"RightBoundary",
|
|
"TopBoundary",
|
|
"BottomBoundary",
|
|
] {
|
|
s.add_field(SchemaField::new(name, FieldType::UInt32));
|
|
}
|
|
for name in [
|
|
"OffsetX", "OffsetY", "Scale", "TaxiMinX", "TaxiMinY", "TaxiMaxX", "TaxiMaxY",
|
|
] {
|
|
s.add_field(SchemaField::new(name, FieldType::Float32));
|
|
}
|
|
s
|
|
}
|
|
|
|
/// Read `WorldMapContinent.dbc` off the patch chain into a [`WorldMapContinentCatalog`].
|
|
pub fn load_world_map_continent_catalog(chain: &mut Chain) -> Result<WorldMapContinentCatalog> {
|
|
let bytes = chain
|
|
.read_file(WORLD_MAP_CONTINENT)
|
|
.context("reading WorldMapContinent.dbc")?;
|
|
let rs = parse(&bytes, schema(), "WorldMapContinent")?;
|
|
let mut by_map_id = HashMap::with_capacity(rs.records().len());
|
|
for r in rs.records() {
|
|
let Some(map_id) = u32_at(r, 1) else { continue };
|
|
let (
|
|
Some(left_boundary),
|
|
Some(right_boundary),
|
|
Some(top_boundary),
|
|
Some(bottom_boundary),
|
|
Some(offset_x),
|
|
Some(offset_y),
|
|
Some(scale),
|
|
Some(taxi_min_x),
|
|
Some(taxi_min_y),
|
|
Some(taxi_max_x),
|
|
Some(taxi_max_y),
|
|
) = (
|
|
u32_at(r, 2),
|
|
u32_at(r, 3),
|
|
u32_at(r, 4),
|
|
u32_at(r, 5),
|
|
f32_at(r, 6),
|
|
f32_at(r, 7),
|
|
f32_at(r, 8),
|
|
f32_at(r, 9),
|
|
f32_at(r, 10),
|
|
f32_at(r, 11),
|
|
f32_at(r, 12),
|
|
)
|
|
else {
|
|
continue;
|
|
};
|
|
by_map_id.insert(
|
|
map_id,
|
|
WorldMapContinent {
|
|
map_id,
|
|
left_boundary,
|
|
right_boundary,
|
|
top_boundary,
|
|
bottom_boundary,
|
|
offset_x,
|
|
offset_y,
|
|
scale,
|
|
taxi_min: (taxi_min_x, taxi_min_y),
|
|
taxi_max: (taxi_max_x, taxi_max_y),
|
|
},
|
|
);
|
|
}
|
|
Ok(WorldMapContinentCatalog { by_map_id })
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn real_world_map_continent_has_azeroth_and_kalimdor() {
|
|
let data = crate::wow_data_or_skip!();
|
|
let mut chain = crate::open_chain(&data).expect("open chain");
|
|
let cat = load_world_map_continent_catalog(&mut chain).expect("load WorldMapContinent");
|
|
assert_eq!(cat.len(), 2);
|
|
|
|
let azeroth = cat.get(0).expect("Azeroth (mapId 0)");
|
|
assert_eq!(
|
|
(
|
|
azeroth.left_boundary,
|
|
azeroth.right_boundary,
|
|
azeroth.top_boundary,
|
|
azeroth.bottom_boundary
|
|
),
|
|
(23, 47, 15, 61)
|
|
);
|
|
assert!((azeroth.offset_x - 14.5).abs() < 0.01);
|
|
assert!((azeroth.offset_y - (-7.0)).abs() < 0.01);
|
|
assert!((azeroth.scale - 0.75).abs() < 0.001);
|
|
assert!((azeroth.taxi_min.0 - (-15980.0)).abs() < 0.1);
|
|
assert!((azeroth.taxi_min.1 - (-11880.0)).abs() < 0.1);
|
|
assert!((azeroth.taxi_max.0 - 5817.0).abs() < 0.1);
|
|
assert!((azeroth.taxi_max.1 - 9917.0).abs() < 0.1);
|
|
|
|
let kalimdor = cat.get(1).expect("Kalimdor (mapId 1)");
|
|
assert_eq!(
|
|
(
|
|
kalimdor.left_boundary,
|
|
kalimdor.right_boundary,
|
|
kalimdor.top_boundary,
|
|
kalimdor.bottom_boundary
|
|
),
|
|
(23, 48, 9, 52)
|
|
);
|
|
assert!((kalimdor.scale - 0.75).abs() < 0.001);
|
|
}
|
|
}
|