the world redraws on resize, and terrain takes its DXT blocks
- the world froze on every resize or graphics change: the backdrop was resized by writing a fresh image through the existing handle, and a mutated `Image` is a whole new GPU texture while a material's bind group is captured once and never re-captured — so the camera drew into a texture the UI never sampled, and the last frame stretched over the window forever; a resized world target is now a NEW asset (publish, retire the old one, and the UI pass forgets materials for removed images so a window drag does not leak a 46 MB target per frame), and a resized capture now matches the native render bit for bit - terrain tile layer arrays take the DXT blocks when the tile's whole layer set allows it — one array has one format, so eligibility is the tile's, not a texture's — and the install's own census says narrow is enough: 87 % of Azeroth's tiles and 92 % of Kalimdor's pass, layer bytes 1.57 GB → 0.63 and 2.23 → 0.76; the decode-tax comment is corrected on the way past — decoding costs 8x DXT1's bytes and 4x DXT3/DXT5's, not 4x and 2x - Environment Detail is the reference's slider, not a dropdown — row 27 of 1.12's own slider table, with the readout keeping the stop names because a bare "1" means nothing to a player — and the shipped ground cover comes off High to Medium: the x3 clutter density had been matched to a configured install, and Medium is the nearest stop no sparser than what a fresh install draws on capable hardware - Display Mode is modern Classic's two-entry dropdown — Windowed (Fullscreen) / Windowed, a boolean at Blizzard's own source, where the three-mode list is pre-8.0.1 folklore — riding `gxWindow` so saved configs keep meaning, with the label-against-value polarity pinned by the row's first test; the Multisampling row comes off the Graphics page for now (the CVar, the device clamp and the bindings all stay): with alpha-to-coverage set nowhere, MSAA touches polygon silhouettes only, and render scale on the same page measures four times the visual change Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
//! select/hover wash mechanism, the working era search; 0985: the provenance split those
|
||||
//! cite; 0989: the directed cuts — steppers and the corner X gone, the whole bar live via
|
||||
//! the engine's track-press law, the search box at the era's verbatim seat; 0992: the
|
||||
//! dropdown row shape on the 1.12 kit — Environment Detail — and the Nameplates page's
|
||||
//! dropdown row shape on the 1.12 kit — Camera Following Style — and the Nameplates page's
|
||||
//! three UnitName* rows; 1476: the ground dim — a black fill over the 0.6 tile, seated clear
|
||||
//! of the rope's ink, because a 60% veil is not a page you can read over bright terrain).
|
||||
//!
|
||||
@@ -1015,6 +1015,67 @@ fn the_graphics_page_reads_the_cvar_table_on_select() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The **Display Mode** row (1650) — modern Classic's own control, which 1627 had already given us
|
||||
/// the two states for. Blizzard's `Blizzard_SettingsDefinitions_Shared/Graphics.lua` (identical on
|
||||
/// live, classic and classic_era) builds a boolean proxy as a dropdown with exactly two entries,
|
||||
/// `VIDEO_OPTIONS_WINDOWED_FULLSCREEN` and `VIDEO_OPTIONS_WINDOWED`, and no Fullscreen entry at all.
|
||||
///
|
||||
/// **The polarity is the thing worth pinning.** `gxWindow` is 1.12's CVar and keeps 1.12's sense —
|
||||
/// `"1"` is WINDOWED — so the FIRST entry, the borderless one this client defaults to, is the CVar's
|
||||
/// `"0"`. A row whose label and value run opposite ways is exactly the kind that gets silently
|
||||
/// inverted by a later edit, and until this test there was no coverage of the row at all.
|
||||
#[test]
|
||||
fn the_display_mode_dropdown_maps_its_entries_to_the_gx_window_polarity() {
|
||||
const ROW: &str = "OptionsFrameContainerBodyGraphicsRowDisplayMode";
|
||||
let mut s = audio_harness();
|
||||
s.set_cvar_host("gxWindow", "0");
|
||||
let mut s = harness_on(s);
|
||||
s.run("ShowUIPanel(OptionsFrame)").unwrap();
|
||||
s.run("OptionsFrameCategoryListRowGraphics:Click()")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>(&format!("return {ROW}Label:GetText()"))
|
||||
.unwrap(),
|
||||
"Display Mode"
|
||||
);
|
||||
// "0" is NOT windowed, which 1627 redefined as the borderless fullscreen window.
|
||||
assert_eq!(
|
||||
s.eval::<String>(&format!("return {ROW}DropdownText:GetText()"))
|
||||
.unwrap(),
|
||||
"Windowed (Fullscreen)"
|
||||
);
|
||||
assert!(
|
||||
s.take_cvar_changes().is_empty(),
|
||||
"reading the table on select must not write it back"
|
||||
);
|
||||
|
||||
// Two entries, and only two — the modern list has no Fullscreen row, because 8.0.1 removed
|
||||
// the exclusive mode those clients had and 1627 ships none for its own platform reasons.
|
||||
s.run(&format!("{ROW}DropdownButton:Click()")).unwrap();
|
||||
assert!(s.eval::<bool>("return DropDownList1:IsVisible()").unwrap());
|
||||
assert_eq!(
|
||||
s.eval::<f64>("return DropDownList1.numButtons").unwrap(),
|
||||
2.0
|
||||
);
|
||||
assert!(s
|
||||
.eval::<bool>("return DropDownList1Button1Check:IsVisible()")
|
||||
.unwrap());
|
||||
|
||||
// Picking Windowed writes the CVar's "1", closes the list, and repaints the capsule.
|
||||
s.run("DropDownList1Button2:Click()").unwrap();
|
||||
assert_eq!(
|
||||
s.take_cvar_changes(),
|
||||
vec![("gxWindow".to_string(), "1".to_string())]
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>(&format!("return {ROW}DropdownText:GetText()"))
|
||||
.unwrap(),
|
||||
"Windowed"
|
||||
);
|
||||
assert!(!s.eval::<bool>("return DropDownList1:IsVisible()").unwrap());
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// **Vertical Sync** (1394) — the Graphics page's third row, and the first option in this window
|
||||
/// that reaches the *window* rather than a gameplay or UI knob. It is 1.12's own Video Options
|
||||
/// checkbox 5 (`gxVSync`), which lived on the perf HUD as a dev checkbox until it turned out to be
|
||||
@@ -1065,13 +1126,13 @@ fn the_vertical_sync_row_reads_and_writes_the_present_mode_cvar() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The dropdown row (0992, the first): the capsule reads the CVar on select (text from the
|
||||
/// row's own entries), the capsule button opens the shared list at the OWNER's effective scale
|
||||
/// (the kit's 0992 uiScale correction — the window rides SetScale) with the current value's
|
||||
/// entry checked, and an entry click writes the CVar, repaints the capsule, and closes the
|
||||
/// list on the kit's own law.
|
||||
/// Environment Detail is the reference's own slider (1649): 0..2 step 1 over `WorldDetail`, with
|
||||
/// 0992's Low/Medium/High names kept in the readout seat — a groove whose readout says "1" tells a
|
||||
/// player nothing. Dragging writes the CVar; a value from outside the range shows the nearest stop
|
||||
/// and writes nothing back (0959's out-of-range law).
|
||||
#[test]
|
||||
fn the_world_detail_dropdown_writes_the_cvar_and_the_capsule_follows() {
|
||||
fn the_world_detail_slider_writes_the_cvar_and_the_readout_names_its_stop() {
|
||||
const ROW: &str = "OptionsFrameContainerBodyGraphicsRowWorldDetail";
|
||||
let mut s = audio_harness();
|
||||
s.set_cvar_host("WorldDetail", "0");
|
||||
let mut s = harness_on(s);
|
||||
@@ -1079,68 +1140,57 @@ fn the_world_detail_dropdown_writes_the_cvar_and_the_capsule_follows() {
|
||||
s.run("OptionsFrameCategoryListRowGraphics:Click()")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>(
|
||||
"return OptionsFrameContainerBodyGraphicsRowWorldDetailDropdownText:GetText()"
|
||||
)
|
||||
.unwrap(),
|
||||
"Low"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return OptionsFrameContainerBodyGraphicsRowWorldDetailLabel:GetText()")
|
||||
s.eval::<String>(&format!("return {ROW}Label:GetText()"))
|
||||
.unwrap(),
|
||||
"Environment Detail"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>(&format!("return {ROW}ControlValue:GetText()"))
|
||||
.unwrap(),
|
||||
"Low"
|
||||
);
|
||||
assert!(
|
||||
s.take_cvar_changes().is_empty(),
|
||||
"reading the table on select must not write it back"
|
||||
);
|
||||
|
||||
// The capsule button opens the list: three entries, the stored value's row checked, and
|
||||
// the shared list wearing the window's effective scale (0.78 era scale, checkFit capped).
|
||||
s.run("OptionsFrameContainerBodyGraphicsRowWorldDetailDropdownButton:Click()")
|
||||
.unwrap();
|
||||
assert!(s.eval::<bool>("return DropDownList1:IsVisible()").unwrap());
|
||||
assert_eq!(
|
||||
s.eval::<f64>("return DropDownList1.numButtons").unwrap(),
|
||||
3.0
|
||||
);
|
||||
// The reference's grid, verbatim: three stops, one apart (OptionsFrame.lua l.27).
|
||||
assert!(s
|
||||
.eval::<bool>("return DropDownList1Button1Check:IsVisible()")
|
||||
.unwrap());
|
||||
assert!(!s
|
||||
.eval::<bool>("return DropDownList1Button3Check:IsVisible()")
|
||||
.unwrap());
|
||||
assert!(s
|
||||
.eval::<bool>(
|
||||
"return math.abs(DropDownList1:GetScale() - OptionsFrame:GetScale()) < 0.0001"
|
||||
)
|
||||
.eval::<bool>(&format!(
|
||||
"local lo, hi = {ROW}ControlSlider:GetMinMaxValues() \
|
||||
return lo == 0 and hi == 2 and {ROW}ControlSlider:GetValueStep() == 1"
|
||||
))
|
||||
.unwrap());
|
||||
|
||||
// Clicking High: the write queues, the capsule repaints, the list closes.
|
||||
s.run("DropDownList1Button3:Click()").unwrap();
|
||||
// A drag to the top stop: the write queues and the readout names it.
|
||||
s.run(&format!("{ROW}ControlSlider:SetValue(2)")).unwrap();
|
||||
assert_eq!(
|
||||
s.take_cvar_changes(),
|
||||
vec![("WorldDetail".to_string(), "2".to_string())]
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>(
|
||||
"return OptionsFrameContainerBodyGraphicsRowWorldDetailDropdownText:GetText()"
|
||||
)
|
||||
.unwrap(),
|
||||
s.eval::<String>(&format!("return {ROW}ControlValue:GetText()"))
|
||||
.unwrap(),
|
||||
"High"
|
||||
);
|
||||
assert!(!s.eval::<bool>("return DropDownList1:IsVisible()").unwrap());
|
||||
|
||||
// An off-grid value (an env A/B: the hermetic capture's clutter-off session seeds "-1")
|
||||
// Off-grid input snaps to the stop grid (era obeyStepOnDrag), naming the stop it landed on.
|
||||
s.run(&format!("{ROW}ControlSlider:SetValue(0.6)")).unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>(&format!("return {ROW}ControlValue:GetText()"))
|
||||
.unwrap(),
|
||||
"Medium"
|
||||
);
|
||||
|
||||
// An out-of-range value (an env A/B: the hermetic capture's clutter-off session seeds "-1")
|
||||
// displays the NEAREST stop — 0959's out-of-range law — and writes nothing back.
|
||||
s.set_cvar_host("WorldDetail", "-1");
|
||||
s.take_cvar_changes();
|
||||
s.run("OptionsFrameCategoryListRowAudio:Click(); OptionsFrameCategoryListRowGraphics:Click()")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<String>(
|
||||
"return OptionsFrameContainerBodyGraphicsRowWorldDetailDropdownText:GetText()"
|
||||
)
|
||||
.unwrap(),
|
||||
s.eval::<String>(&format!("return {ROW}ControlValue:GetText()"))
|
||||
.unwrap(),
|
||||
"Low"
|
||||
);
|
||||
assert!(
|
||||
@@ -1150,92 +1200,6 @@ fn the_world_detail_dropdown_writes_the_cvar_and_the_capsule_follows() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The Graphics page's **Multisampling** row (decision 1631): its stops are enumerated from the
|
||||
/// DEVICE, not a fixed ladder, and picking one writes the latched `gxMultisample`.
|
||||
///
|
||||
/// The device list is seeded Apple-shaped — 1x/2x/4x and no 8x — because that is what this
|
||||
/// project's own machine reports, and because a fixed ladder offering 8x there is precisely the
|
||||
/// crash 1631 exists to prevent. A machine with 8x would show four rows off the same code.
|
||||
#[test]
|
||||
fn the_multisampling_row_offers_exactly_what_the_device_supports() {
|
||||
// No explicit `register_cvars` here: `audio_harness` registers `cvars::REGISTERED` whole, and
|
||||
// the three multisample rows live in it. That is deliberate — if they are ever dropped from
|
||||
// the registry this test fails rather than papering over it with its own registration.
|
||||
let mut s = audio_harness();
|
||||
s.set_multisample_formats(vec![
|
||||
benilla_ui::script::MultisampleFormat {
|
||||
color_bits: 32,
|
||||
depth_bits: 32,
|
||||
samples: 1,
|
||||
},
|
||||
benilla_ui::script::MultisampleFormat {
|
||||
color_bits: 32,
|
||||
depth_bits: 32,
|
||||
samples: 2,
|
||||
},
|
||||
benilla_ui::script::MultisampleFormat {
|
||||
color_bits: 32,
|
||||
depth_bits: 32,
|
||||
samples: 4,
|
||||
},
|
||||
]);
|
||||
let mut s = harness_on(s);
|
||||
s.run("ShowUIPanel(OptionsFrame)").unwrap();
|
||||
s.run("OptionsFrameCategoryListRowGraphics:Click()")
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
s.eval::<String>("return OptionsFrameContainerBodyGraphicsRowMultisampleLabel:GetText()")
|
||||
.unwrap(),
|
||||
"Multisampling",
|
||||
"the reference's own MULTISAMPLE global"
|
||||
);
|
||||
// "1" is the registered default and means NO multisampling, so the first stop reads "Off"
|
||||
// rather than "1x" (1635). The label carries the noun; the capsule carries the value.
|
||||
assert_eq!(
|
||||
s.eval::<String>(
|
||||
"return OptionsFrameContainerBodyGraphicsRowMultisampleDropdownText:GetText()"
|
||||
)
|
||||
.unwrap(),
|
||||
"Off",
|
||||
"1 is the encoding of NO multisampling, not one sample of it"
|
||||
);
|
||||
assert!(
|
||||
s.take_cvar_changes().is_empty(),
|
||||
"reading the table on select must not write it back"
|
||||
);
|
||||
|
||||
// Three stops, because the device offered three. This is the assertion that would fail if the
|
||||
// row ever went back to a hardcoded ladder.
|
||||
s.run("OptionsFrameContainerBodyGraphicsRowMultisampleDropdownButton:Click()")
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
s.eval::<f64>("return DropDownList1.numButtons").unwrap(),
|
||||
3.0
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>("return DropDownList1Button3:GetText()")
|
||||
.unwrap(),
|
||||
"4x"
|
||||
);
|
||||
|
||||
// Picking 4x writes the CVar — live and persisted, even though the picture waits for the next
|
||||
// launch (the 1629 latch; the camera takes its sample count at spawn).
|
||||
s.run("DropDownList1Button3:Click()").unwrap();
|
||||
assert_eq!(
|
||||
s.take_cvar_changes(),
|
||||
vec![("gxMultisample".to_string(), "4".to_string())]
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>(
|
||||
"return OptionsFrameContainerBodyGraphicsRowMultisampleDropdownText:GetText()"
|
||||
)
|
||||
.unwrap(),
|
||||
"4x"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The Nameplates page (0992 — live at last): the three 1.12 UnitName* checkbox rows read the
|
||||
/// table on select and write their flags on the interface panel's own click kit (checked →
|
||||
/// CheckBoxOn; these rows carry no soundQuirk).
|
||||
@@ -1391,9 +1355,9 @@ fn the_ui_scale_slider_defers_to_the_apply_button() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// **Render Scale** (decision 1639) — benilla's own Graphics row, seated directly above
|
||||
/// Multisampling because above 100 % it *is* antialiasing, on a client whose `gxMultisample`
|
||||
/// ships off (1629).
|
||||
/// **Render Scale** (decision 1639) — benilla's own Graphics row, and since 1648 the page's ONLY
|
||||
/// antialiasing control: the reference's own Multisampling row was pulled on the director's call,
|
||||
/// leaving `gxMultisample` reachable as a CVar and `$WOW_MSAA` but off the page.
|
||||
///
|
||||
/// Three things this pins, each of which was a real choice:
|
||||
///
|
||||
@@ -2133,18 +2097,20 @@ fn every_row_tooltip_key_resolves_in_the_real_global_strings() {
|
||||
untipped.push(row.to_string());
|
||||
continue;
|
||||
}
|
||||
// The one deliberate exception (decision 1639). Render Scale has no 1.12 counterpart
|
||||
// and so no `OPTION_TOOLTIP_*` to resolve, but it is the row that most needs a
|
||||
// description — its first reviewer said outright they could not tell what it did. It
|
||||
// carries one under a `BENILLA_` prefix so the reference's namespace stays the
|
||||
// reference's, which is exactly what this guard is here to protect. Everything the
|
||||
// guard was built to catch — an invented or typo'd `OPTION_TOOLTIP_` key that silently
|
||||
// resolves to nothing — is untouched by the carve-out.
|
||||
if key == "BENILLA_TOOLTIP_RENDER_SCALE" {
|
||||
assert_eq!(
|
||||
row, "GraphicsRowRenderScale",
|
||||
"{row}: not this row's string"
|
||||
);
|
||||
// The deliberate exceptions (1639 Render Scale, 1650 Display Mode). Neither has a 1.12
|
||||
// counterpart whose `OPTION_TOOLTIP_*` could be resolved — Render Scale has no era row at
|
||||
// all, and Display Mode's era row was a CHECKBOX whose string says "Check to…" — and both
|
||||
// are rows a player needs a description for. Each carries one under a `BENILLA_` prefix so
|
||||
// the reference's namespace stays the reference's, which is exactly what this guard is
|
||||
// here to protect. Everything the guard was built to catch — an invented or typo'd
|
||||
// `OPTION_TOOLTIP_` key that silently resolves to nothing — is untouched: the pairing below
|
||||
// is exact, so a `BENILLA_` key on the wrong row still fails.
|
||||
const BENILLA_OWNED: &[(&str, &str)] = &[
|
||||
("BENILLA_TOOLTIP_RENDER_SCALE", "GraphicsRowRenderScale"),
|
||||
("BENILLA_TOOLTIP_DISPLAY_MODE", "GraphicsRowDisplayMode"),
|
||||
];
|
||||
if let Some((_, want_row)) = BENILLA_OWNED.iter().find(|(k, _)| *k == key) {
|
||||
assert_eq!(row, *want_row, "{row}: not this row's string");
|
||||
let text: String = s.eval(&format!("return {key}")).unwrap();
|
||||
assert!(!text.is_empty(), "{row}: {key} resolves to nothing");
|
||||
checked += 1;
|
||||
@@ -2167,8 +2133,8 @@ fn every_row_tooltip_key_resolves_in_the_real_global_strings() {
|
||||
}
|
||||
// 27 CVar rows (the Chat page's two bubble switches are 1139's and its Detailed Loot
|
||||
// Information + Guild Member Alert are 1589's; Status Bar Text, Mouse Sensitivity
|
||||
// and Max Camera Distance 1140's; Graphics' Vertical Sync is 1394's, its Windowed Mode
|
||||
// 1627's and its Multisampling 1632's; Camera Following Style
|
||||
// and Max Camera Distance 1140's; Graphics' Vertical Sync is 1394's, its Display Mode
|
||||
// 1627's (a dropdown since 1650) and its Multisampling 1632's; Camera Following Style
|
||||
// 1493's; Terrain Distance 1513's) + the Combat page's 14 saved-variable rows (1134) + the Interface page's 6 (3 from
|
||||
// 1136, Buff Durations 1139, the target-of-target pair 1576), the Action Bars page's 2 (the
|
||||
// lock 1136, Always Show
|
||||
@@ -2178,9 +2144,9 @@ fn every_row_tooltip_key_resolves_in_the_real_global_strings() {
|
||||
// bar as the other two. Camera Following Style is counted on the key it wears at rest (Smart's
|
||||
// OPTION_TOOLTIP_CAMERA1) and Show When on its own (Always's OPTION_TOOLTIP_TARGETOFTARGET5);
|
||||
// their other entries ride the same census as the selection moves.
|
||||
// 55 of the 56 are 1.12's own; the 56th is Render Scale, whose description is benilla's
|
||||
// (1639) and whose carve-out is above.
|
||||
assert_eq!(checked, 56, "every tipped row carries a live key");
|
||||
// 53 of the 55 are 1.12's own; the other two are Render Scale (1639) and Display Mode
|
||||
// (1650), whose descriptions are benilla's and whose carve-out is above.
|
||||
assert_eq!(checked, 55, "every tipped row carries a live key");
|
||||
assert_eq!(
|
||||
untipped,
|
||||
vec![
|
||||
@@ -2278,16 +2244,16 @@ fn every_flavor_of_row_raises_its_plate_from_the_page_it_lives_on() {
|
||||
}
|
||||
// 27 of the 28 CVar rows (the Chat page's two bubble switches are 1139's and its Detailed
|
||||
// Loot Information + Guild Member Alert 1589's; Status Bar Text, Mouse Sensitivity and
|
||||
// Max Camera Distance 1140's; Vertical Sync 1394's, Windowed Mode 1627's and Multisampling
|
||||
// Max Camera Distance 1140's; Vertical Sync 1394's, Display Mode 1627's and Multisampling
|
||||
// 1632's; Camera Following
|
||||
// Style 1493's; Terrain Distance 1513's), plus the Combat page's 14 saved-variable rows (1134), the Interface
|
||||
// page's 6 (1136, + Buff Durations 1139, + the target-of-target pair 1576), Action Bars' 2
|
||||
// (the lock 1136, Always Show
|
||||
// ActionBars 1500), the Chat page's 1 (Remove Chat Hover Delay, 1589) and 6 API rows (Show
|
||||
// Cloak / Show Helm, 1472; the four multibar switches, 1500).
|
||||
// …plus the Graphics page's Render Scale (1639), the one row whose description is
|
||||
// benilla's own rather than a 1.12 GlobalString — see the tooltip-key guard above.
|
||||
assert_eq!(raised, 56, "every row but Auto Loot raises a description");
|
||||
// …plus the Graphics page's Render Scale (1639) and Display Mode (1650), the two rows whose
|
||||
// descriptions are benilla's own rather than 1.12 GlobalStrings — see the guard above.
|
||||
assert_eq!(raised, 55, "every row but Auto Loot raises a description");
|
||||
}
|
||||
|
||||
/// The **Combat page** (decision 1134) — the first rows in this window whose store is a
|
||||
|
||||
Reference in New Issue
Block a user