TurtleWoW compat: restore OptionsFrameSliders, resolve races 9/10, append .blp to bare M2 textures, nil-safe wipe, explode + addon delimiters, CONTINENTS_LENGTH
This commit is contained in:
@@ -1212,6 +1212,22 @@
|
||||
-- UIOptionsFrame.lua:193-227 re-applies saved uvars, so this window has no load ladder.
|
||||
|
||||
-- OptionsFrameSliders is the stock table (OptionsFrame.lua:24-34), which addons write.
|
||||
-- Deviation: TurtleWoW's patch-4 replaces OptionsFrame.lua with a 2.0-era file that never
|
||||
-- defines it, so the rows below restore the 1.12 table verbatim — `OPTIONS_FARCLIP_MIN/MAX`
|
||||
-- resolved to their 177/777 values (they live in the 1.12 OptionsFrame.lua:1-2, not in
|
||||
-- GlobalStrings, so a replacement loses them) — for the graphics rows and any addon.
|
||||
OptionsFrameSliders = {
|
||||
{ text = UI_SCALE, func = "uiscale", minValue = 0.64, maxValue = 1.0, valueStep = 0.01, tooltipText = OPTION_TOOLTIP_UI_SCALE },
|
||||
{ text = FARCLIP, func = "farclip", minValue = 177, maxValue = 777, valueStep = 60, tooltipText = OPTION_TOOLTIP_FARCLIP },
|
||||
{ text = ENVIRONMENT_DETAIL, func = "WorldDetail", minValue = 0, maxValue = 2, valueStep = 1, tooltipText = OPTION_TOOLTIP_ENVIRONMENT_DETAIL },
|
||||
{ text = TERRAIN_MIP, func = "TerrainMip", minValue = 0, maxValue = 1, valueStep = 1, tooltipText = OPTION_TOOLTIP_TERRAIN_TEXTURE, restartClient = 1, tooltipRequirement = OPTION_RESTART_REQUIREMENT },
|
||||
{ text = TEXTURE_DETAIL, func = "BaseMip", minValue = 0, maxValue = 1, valueStep = 1, tooltipText = OPTION_TOOLTIP_TEXTURE_DETAIL },
|
||||
{ text = GAMMA, func = "Gamma", cvar1 = "gamma", minValue = -0.5, maxValue = 0.5, valueStep = 0.1, tooltipText = OPTION_TOOLTIP_GAMMA },
|
||||
{ text = ANISOTROPIC, func = "anisotropic", minValue = 1, maxValue = 4, valueStep = 1, tooltipText = OPTION_TOOLTIP_ANISOTROPIC, restartClient = 1, tooltipRequirement = OPTION_RESTART_REQUIREMENT },
|
||||
{ text = SPELL_DETAIL, func = "spellEffectLevel", minValue = 0, maxValue = 2, valueStep = 1, tooltipText = OPTION_TOOLTIP_SPELL_DETAIL },
|
||||
{ text = WEATHER_DETAIL, func = "weatherDensity", minValue = 0, maxValue = 3, valueStep = 1, tooltipText = OPTION_TOOLTIP_WEATHER_DETAIL },
|
||||
}
|
||||
|
||||
-- Holes at 4, 5, 7 and 8, so the OnShow loop uses the pairs form: ipairs would stop at 3.
|
||||
BENILLA_GRAPHICS_SLIDER_ROWS = {
|
||||
"RowUiScale", "RowFarclip", "RowWorldDetail",
|
||||
|
||||
@@ -476,6 +476,13 @@ fn fire_unit_combat(
|
||||
|
||||
/// The race id to `UnitRace`'s display name and `raceFile` token (`"Scourge"`, `"NightElf"`); the
|
||||
/// display name is also what `$R`/`$r` expand to.
|
||||
///
|
||||
/// Deviation: races 9 (Goblin) and 10 (High Elf) are TurtleWoW's additions, absent from stock
|
||||
/// 1.12's eight; they resolve because the reference reads `ChrRaces.dbc` at call time and
|
||||
/// TurtleWoW's patched copy carries the rows, so a 1.12 client on that chain answers them too.
|
||||
/// Without the rows `UnitRace("player")` answers nil and TurtleWoW's `PaperDollFrame_SetLevel`
|
||||
/// raises in its three-spec `PLAYER_LEVEL` format. (The same justification as the `wipe`/`trim`/
|
||||
/// `strsplit` additions in the UI stdlib.)
|
||||
pub(crate) fn race_names(race: u8) -> Option<(&'static str, &'static str)> {
|
||||
Some(match race {
|
||||
1 => ("Human", "Human"),
|
||||
@@ -486,6 +493,8 @@ pub(crate) fn race_names(race: u8) -> Option<(&'static str, &'static str)> {
|
||||
6 => ("Tauren", "Tauren"),
|
||||
7 => ("Gnome", "Gnome"),
|
||||
8 => ("Troll", "Troll"),
|
||||
9 => ("Goblin", "Goblin"),
|
||||
10 => ("High Elf", "High Elf"),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -1614,6 +1623,25 @@ fn combo_edge(last: Option<(u8, u64)>, now: (u8, u64)) -> Option<bool> {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// TurtleWoW's added races resolve like the shipped eight: `UnitRace("player")` would return
|
||||
/// nil without them, and its `PaperDollFrame_SetLevel` (`PLAYER_LEVEL = "Level %d %s %s"`)
|
||||
/// raises on a nil race in `string.format`.
|
||||
#[test]
|
||||
fn turtlewow_races_nine_and_ten_resolve() {
|
||||
assert_eq!(
|
||||
race_names(9),
|
||||
Some(("Goblin", "Goblin")),
|
||||
"race 9 is TurtleWoW's Goblin"
|
||||
);
|
||||
assert_eq!(
|
||||
race_names(10),
|
||||
Some(("High Elf", "High Elf")),
|
||||
"race 10 is TurtleWoW's High Elf"
|
||||
);
|
||||
assert_eq!(race_names(0), None, "race 0 has no ChrRaces row");
|
||||
assert_eq!(race_names(11), None, "race 11 has no ChrRaces row");
|
||||
}
|
||||
|
||||
/// The team digit comes off the race byte, whatever template sits beside it (a GM's 35).
|
||||
#[test]
|
||||
fn the_team_digit_comes_off_the_race_byte_not_the_faction_template() {
|
||||
|
||||
@@ -131,6 +131,16 @@ const SAMPLER_MARKER: char = '@';
|
||||
/// the extension (`…/leaves01@cc.blp`), which must stay `.blp` to pick the loader.
|
||||
pub fn texture_url(internal: &str, wrap: (bool, bool)) -> String {
|
||||
let path = internal.replace('\\', "/").to_ascii_lowercase();
|
||||
// The reference's texture create appends `.blp` to a filename that lacks an extension
|
||||
// (`CImageManager::CreateTexture`), and TurtleWoW's M2s ship some texture names bare
|
||||
// (`redtreecanopy_leaves01`, whose `.blp` the chain holds); without the extension the archive
|
||||
// lookup misses and Bevy cannot pick the BLP loader.
|
||||
let file = path.rsplit('/').next().unwrap_or(&path);
|
||||
let path = if file.contains('.') {
|
||||
path
|
||||
} else {
|
||||
format!("{path}.blp")
|
||||
};
|
||||
if wrap == (true, true) {
|
||||
return format!("mpq://{path}");
|
||||
}
|
||||
@@ -257,6 +267,24 @@ mod tests {
|
||||
assert_eq!(strip_sampler_marker("world/foo.blp"), None);
|
||||
assert_eq!(strip_sampler_marker("world/foo@bar.blp"), None);
|
||||
assert_eq!(sampler_mode_of("world/foo@bar.blp"), (true, true));
|
||||
|
||||
// A TurtleWoW M2 names some textures bare; the reference appends `.blp`, and the marker
|
||||
// must still land before the appended extension so the loader picks it.
|
||||
let bare = texture_url(
|
||||
"World\\Azeroth\\Redridge\\PassiveDoodads\\Trees\\RedTreeCanopy_Leaves01",
|
||||
(true, true),
|
||||
);
|
||||
assert_eq!(
|
||||
bare,
|
||||
"mpq://world/azeroth/redridge/passivedoodads/trees/redtreecanopy_leaves01.blp"
|
||||
);
|
||||
assert_eq!(
|
||||
texture_url(
|
||||
"World\\Azeroth\\Redridge\\PassiveDoodads\\Trees\\RedTreeCanopy_Base02",
|
||||
(false, false),
|
||||
),
|
||||
"mpq://world/azeroth/redridge/passivedoodads/trees/redtreecanopy_base02@cc.blp"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -286,8 +286,8 @@ __benilla_game_hour = 0
|
||||
__benilla_game_minute = 0
|
||||
function GetGameTime() return __benilla_game_hour, __benilla_game_minute end
|
||||
|
||||
-- ── Beyond 1.12: wipe / strsplit / strjoin / trim ─────────────────────────────────────────────
|
||||
-- Four names 1.12 does NOT have: absent from `reference/1.12-globals.tsv`, absent from the stock
|
||||
-- ── Beyond 1.12: wipe / strsplit / strjoin / trim / explode ───────────────────────────────────
|
||||
-- Names 1.12 does NOT have: absent from `reference/1.12-globals.tsv`, absent from the stock
|
||||
-- chain (no stock `Interface\FrameXML` file calls any of them — `UIParent.lua`, `ChatFrame.lua`,
|
||||
-- `MainMenuBar.lua` and `FriendsFrame.lua` read clean), and absent from every registrar table. They
|
||||
-- are here anyway because a MODIFIED 1.12 UI ships them: TurtleWoW's `patch-4.mpq` replaces
|
||||
@@ -303,15 +303,52 @@ function GetGameTime() return __benilla_game_hour, __benilla_game_minute end
|
||||
--
|
||||
-- This is a deviation from the reference, kept because 1.12's own UI cannot load without it. The
|
||||
-- shapes are 2.0's, not 1.12's, and each is scoped to what the reference-corpus callers use:
|
||||
-- `wipe` is single-argument (no corpus caller passes a key), and `strsplit` splits on PLAIN text.
|
||||
-- `wipe` is single-argument (no corpus caller passes a key) and no-ops on nil — TurtleWoW's
|
||||
-- `Blizzard_RaidUI.lua:77` wipes `RAID_SUBGROUP_LISTS` entries that may never be populated, and
|
||||
-- `strsplit` splits on PLAIN text.
|
||||
do
|
||||
local _find, _sub, _insert, _concat = string.find, string.sub, table.insert, table.concat
|
||||
local _find, _sub, _insert, _concat, _len = string.find, string.sub, table.insert, table.concat, string.len
|
||||
local _gsub = string.gsub
|
||||
|
||||
function wipe(t)
|
||||
if t == nil then return end
|
||||
for k in pairs(t) do t[k] = nil end
|
||||
end
|
||||
|
||||
-- `explode(subject, sep [, out])`: 2.0-era split into a TABLE, unlike `strsplit`'s vararg
|
||||
-- shape, appending into `out` when given. TurtleWoW's `HonorFrame.lua:46` parses its
|
||||
-- `TW_HONOR` addon message with `explode(arg2, ADDON_MSG_FIELD_DELIMITER)` and its
|
||||
-- `WorldStateFrame.lua:233-245` reuses the same `out` tables across calls. A nil subject or
|
||||
-- separator comes back whole, so a missing delimiter global cannot take the UI down.
|
||||
function explode(str, sep, out)
|
||||
out = out or {}
|
||||
if str == nil then return out end
|
||||
if sep == nil or sep == "" then
|
||||
_insert(out, str)
|
||||
return out
|
||||
end
|
||||
local pos = 1
|
||||
local sep_len = _len(sep)
|
||||
while 1 do
|
||||
local e = _find(str, sep, pos, true)
|
||||
if e == nil then
|
||||
_insert(out, _sub(str, pos))
|
||||
break
|
||||
end
|
||||
_insert(out, _sub(str, pos, e - 1))
|
||||
pos = e + sep_len
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
-- TurtleWoW's addon-message field delimiters (`TW_HONOR`, `TW_BG`), named where its
|
||||
-- `HonorFrame.lua:46` and `WorldStateFrame.lua:331-335` split addon payloads. The values are
|
||||
-- the same `;`/`:`/`|` its `WorldStateFrame.lua:233-245` writes literally for the fields,
|
||||
-- arrays and subfields of the same protocol.
|
||||
ADDON_MSG_FIELD_DELIMITER = ";"
|
||||
ADDON_MSG_ARRAY_DELIMITER = ":"
|
||||
ADDON_MSG_SUBFIELD_DELIMITER = "|"
|
||||
|
||||
function strsplit(delimiter, subject)
|
||||
-- 5.0's `find` answers an empty plain pattern as `(from, from - 1)`, a zero-width match that
|
||||
-- never advances `from`; looping on it fills the table until it overflows. There is nothing
|
||||
|
||||
@@ -43,6 +43,12 @@ fn allowed_beyond_1_12() -> HashSet<&'static str> {
|
||||
"strsplit",
|
||||
"strjoin",
|
||||
"trim",
|
||||
// TurtleWoW's addon-message split and delimiters: its `HonorFrame.lua:46` and
|
||||
// `WorldStateFrame.lua:233-335` parse the `TW_HONOR`/`TW_BG` addon payloads with them.
|
||||
"explode",
|
||||
"ADDON_MSG_FIELD_DELIMITER",
|
||||
"ADDON_MSG_ARRAY_DELIMITER",
|
||||
"ADDON_MSG_SUBFIELD_DELIMITER",
|
||||
// ── WoW API past 1.12 ──
|
||||
// Only `SubmitChatInput` is called from our UI files (`ScriptLogFrame.xml`'s slash
|
||||
// commands); each of the rest awaits its 1.12 equivalent, removal or a reason.
|
||||
|
||||
@@ -348,11 +348,22 @@ fn revealed_overlays(state: &WorldMapState) -> Vec<&WorldMapOverlayView> {
|
||||
impl super::UiScript {
|
||||
/// Pushes the catalog at startup, queueing `WORLD_MAP_UPDATE` so an open map repaints.
|
||||
pub fn set_world_map_catalog(&mut self, continents: Vec<WorldMapContinentView>) {
|
||||
let mut model = self.model_mut();
|
||||
model.worldmap.continents = continents;
|
||||
model
|
||||
.pending_events
|
||||
.push(("WORLD_MAP_UPDATE".to_string(), Vec::new()));
|
||||
let n = continents.len();
|
||||
{
|
||||
let mut model = self.model_mut();
|
||||
model.worldmap.continents = continents;
|
||||
model
|
||||
.pending_events
|
||||
.push(("WORLD_MAP_UPDATE".to_string(), Vec::new()));
|
||||
}
|
||||
// TurtleWoW's WorldMapFrame reads `CONTINENTS_LENGTH` as the continent dropdown's length
|
||||
// (line 457), so it must be set before the WORLD_MAP_UPDATE above runs the dropdown's
|
||||
// OnEvent — a global its patch-4 WorldMapFrame.lua expects that the stock client never
|
||||
// shipped.
|
||||
self.lua
|
||||
.globals()
|
||||
.set("CONTINENTS_LENGTH", n as i64)
|
||||
.expect("CONTINENTS_LENGTH is a number");
|
||||
}
|
||||
|
||||
/// Pushes the orphan list beside the catalog, both filled in one walk (`0x4a5d00`).
|
||||
|
||||
Reference in New Issue
Block a user