Fix external MFD and radar damage mappings

Install the validated J&J coordinates for 19 external display sets across 13 chassis. Update all affected texuv2/offset2 and texuv3/offset3 rows while preserving the 65-mech positional layout and coord.cpp CRLF encoding.

Add a reusable Pillow comparison generator, 38 red/green review maps, and a generated summary. The final audit reports 19 exact mappings, zero differences, zero input warnings, and seven display sets without complete supplied inputs.

Normalize four unambiguous measurement transcription issues: Behemoth MFD LT 1742 to 174, Behemoth Radar CT punctuation and blank S2, and Fafnir MFD LL punctuation.

Document the complete authoring and runtime workflow, including legacy tuple normalization, MFD/Radar scaling, odd-coordinate handling, canonical runtime names, pixel-versus-byte BMP comparison, validation checks, rebuild requirements, and the final installed-set inventory.

Compare every supplied exploded runtime BMP against Gameleap/mw4/hsh. All 19 are already pixel-identical, so no runtime art files are replaced.
This commit is contained in:
2026-08-07 20:46:32 -05:00
parent 13675c8cf4
commit 9755803949
45 changed files with 1010 additions and 42 deletions
@@ -3,7 +3,7 @@ RL 184,170,262,302
LA 12,106,126,174
RA 212,106,326,174
RT 176,86,226,174
LT 110,82,160,1742
LT 110,82,160,174
CT 140,96,198,224
CTR 0,0,0,0
HD 164,122,174,134
@@ -4,8 +4,8 @@ LA 8,26
RA 302,26
RT 294,130
LT 108,130
CT 202.242
CT 202,242
CTR 0,0
HD 230,174
S1 202,28
S2
S2 0,0
@@ -0,0 +1,82 @@
# J&J Coordinate Comparison Summary
Generated by `generate_comparison_maps.py`. Red is the current `coord.cpp` mapping; green is the provided J&J mapping.
`EXACT` means all eleven source rectangles and all eleven exploded offsets match numerically. `DIFFERENT` means at least one value differs; inspect both generated images.
| Mech | Display | Status | Source rectangles different | Exploded offsets different |
|---|---:|---:|---:|---:|
| Annihilator | MFD | **EXACT** | 0/11 | 0/11 |
| Annihilator | RADAR | **EXACT** | 0/11 | 0/11 |
| Argus | MFD | **EXACT** | 0/11 | 0/11 |
| AssassinII | MFD | **EXACT** | 0/11 | 0/11 |
| AssassinII | RADAR | **EXACT** | 0/11 | 0/11 |
| Avatar | MFD | **EXACT** | 0/11 | 0/11 |
| Avatar | RADAR | **EXACT** | 0/11 | 0/11 |
| Behemoth | MFD | **EXACT** | 0/11 | 0/11 |
| Behemoth | RADAR | **EXACT** | 0/11 | 0/11 |
| BlackHawk | MFD | **EXACT** | 0/11 | 0/11 |
| BlackHawk | RADAR | **EXACT** | 0/11 | 0/11 |
| Fafnir | MFD | **EXACT** | 0/11 | 0/11 |
| Flea | MFD | **EXACT** | 0/11 | 0/11 |
| Gladiator | MFD | **EXACT** | 0/11 | 0/11 |
| hellspawn | RADAR | **EXACT** | 0/11 | 0/11 |
| Kodiak | MFD | **EXACT** | 0/11 | 0/11 |
| Longbow | MFD | **EXACT** | 0/11 | 0/11 |
| Warhammer | MFD | **EXACT** | 0/11 | 0/11 |
| Warhammer | RADAR | **EXACT** | 0/11 | 0/11 |
## Missing comparison inputs
- `Argus` RADAR: no complete provided mapping/image set configured
- `Fafnir` RADAR: no complete provided mapping/image set configured
- `Flea` RADAR: no complete provided mapping/image set configured
- `Gladiator` RADAR: no complete provided mapping/image set configured
- `Kodiak` RADAR: no complete provided mapping/image set configured
- `Longbow` RADAR: no complete provided mapping/image set configured
- `hellspawn` MFD: no complete provided mapping/image set configured
## Input warnings
- None
## Generated files
- `Annihilator/MFD/annihilator_mfd_unexploded_coords_comparison.png`
- `Annihilator/MFD/annihilator_mfd_exploded_coords_comparison.png`
- `Annihilator/Radar/annihilator_radar_unexploded_coords_comparison.png`
- `Annihilator/Radar/annihilator_radar_exploded_coords_comparison.png`
- `Argus/MFD/argus_mfd_unexploded_coords_comparison.png`
- `Argus/MFD/argus_mfd_exploded_coords_comparison.png`
- `AssassinII/HUD/assian2_mfd_unexploded_coords_comparison.png`
- `AssassinII/HUD/assian2_mfd_exploded_coords_comparison.png`
- `AssassinII/Radar/assian2_radar_unexploded_coords_comparison.png`
- `AssassinII/Radar/assian2_radar_exploded_coords_comparison.png`
- `Avatar/MFD/avatar_mfd_unexploded_coords_comparison.png`
- `Avatar/MFD/avatar_mfd_exploded_coords_comparison.png`
- `Avatar/Radar/avatar_radar_unexploded_coords_comparison.png`
- `Avatar/Radar/avatar_radar_exploded_coords_comparison.png`
- `Behemoth/HUD/behemoth_mfd_unexploded_coords_comparison.png`
- `Behemoth/HUD/behemoth_mfd_exploded_coords_comparison.png`
- `Behemoth/Radar/behemoth_radar_unexploded_coords_comparison.png`
- `Behemoth/Radar/behemoth_radar_exploded_coords_comparison.png`
- `BlackHawk/MFD/blackhawk_mfd_unexploded_coords_comparison.png`
- `BlackHawk/MFD/blackhawk_mfd_exploded_coords_comparison.png`
- `BlackHawk/Radar/blackhawk_radar_unexploded_coords_comparison.png`
- `BlackHawk/Radar/blackhawk_radar_exploded_coords_comparison.png`
- `Fafnir/HUD/fafnir_mfd_unexploded_coords_comparison.png`
- `Fafnir/HUD/fafnir_mfd_exploded_coords_comparison.png`
- `Flea/MFD/flea_mfd_unexploded_coords_comparison.png`
- `Flea/MFD/flea_mfd_exploded_coords_comparison.png`
- `Gladiator/MFD/gladiator_mfd_unexploded_coords_comparison.png`
- `Gladiator/MFD/gladiator_mfd_exploded_coords_comparison.png`
- `hellspawn/radar/hellspawn_radar_unexploded_coords_comparison.png`
- `hellspawn/radar/hellspawn_radar_exploded_coords_comparison.png`
- `Kodiak/MFD/kodiak_mfd_unexploded_coords_comparison.png`
- `Kodiak/MFD/kodiak_mfd_exploded_coords_comparison.png`
- `Longbow/MFD/longbow_mfd_unexploded_coords_comparison.png`
- `Longbow/MFD/longbow_mfd_exploded_coords_comparison.png`
- `Warhammer/MFD/warhammer_mfd_unexploded_coords_comparison.png`
- `Warhammer/MFD/warhammer_mfd_exploded_coords_comparison.png`
- `Warhammer/Radar/warhammer_radar_unexploded_coords_comparison.png`
- `Warhammer/Radar/warhammer_radar_exploded_coords_comparison.png`
@@ -1,4 +1,4 @@
LL 86,142,162.326
LL 86,142,162,326
RL 178,142,254,326
LA 24,22,68,134
RA 272,22,316,134
@@ -0,0 +1,320 @@
#!/usr/bin/env python3
import re
from dataclasses import dataclass
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
ROOT = Path(__file__).resolve().parent
REPO = ROOT.parent
COORD = REPO / "Gameleap/code/CoreTech/Libraries/GameOS/coord.cpp"
ZONES = ("LL", "RL", "LA", "RA", "RT", "LT", "CT", "CTR", "HD", "S1", "S2")
COLORS = {"current": "#ff3030", "provided": "#20e060"}
@dataclass(frozen=True)
class Display:
folder: str
mech_id: int
kind: str
output_dir: str
slug: str
unexploded_text: str
exploded_text: str
unexploded_image: str | None
exploded_image: str
source_image: str | None = None
runtime_stem: str | None = None
DISPLAYS = (
Display("Annihilator", 0, "mfd", "MFD", "annihilator", "MFD/Annihilator Unexploded.txt", "MFD/Annihilator Exploded.txt", "MFD/annihilator_unexploded.bmp", "MFD/annihilator_Exploded.bmp"),
Display("Annihilator", 0, "radar", "Radar", "annihilator", "Radar/Annihilator Unexploded.txt", "Radar/Annihilator Exploded.txt", "Radar/annihilator_unexploded.bmp", "Radar/annihilator_exploded.bmp"),
Display("Argus", 4, "mfd", "MFD", "argus", "MFD/Argus Cords.txt", "MFD/Argus Cords.txt", "MFD/argus_unexpanded.bmp", "MFD/argus_explode.bmp"),
Display("AssassinII", 5, "mfd", "HUD", "assian2", "HUD/assassinII_HUD_unexploded.txt", "HUD/assassinII_hud_exploded.txt", None, "HUD/assian2_hud_exploded.bmp", "assian2_color.png", "assassin2"),
Display("AssassinII", 5, "radar", "Radar", "assian2", "Radar/assassinII_radar_unexploded.txt", "Radar/assassinII_radar_exploded.txt", "Radar/assian2_radar_unexploded.bmp", "Radar/assian2_radar_exploded.bmp", None, "assassin2"),
Display("Avatar", 7, "mfd", "MFD", "avatar", "MFD/Avatar Coords.txt", "MFD/Avatar Coords.txt", "MFD/avatar_unexploded.bmp", "MFD/avatar_exploded.bmp"),
Display("Avatar", 7, "radar", "Radar", "avatar", "Radar/Avatar Coords.txt", "Radar/Avatar Coords.txt", "Radar/avatar_unexploded.bmp", "Radar/avatar_exploded.bmp"),
Display("Behemoth", 11, "mfd", "HUD", "behemoth", "HUD/Behemoth_HUD_unexploded.txt", "HUD/Behemoth_HUD_exploded.txt", "HUD/Behemoth_HUD_unexploded.bmp", "HUD/Behemoth_HUD_exploded.bmp"),
Display("Behemoth", 11, "radar", "Radar", "behemoth", "Radar/Behemoth_unexploded.txt", "Radar/Behemoth_exploded.txt", "Radar/Behemoth_unexploded_radar.bmp", "Radar/Behemoth_radar_exploded.bmp"),
Display("BlackHawk", 13, "mfd", "MFD", "blackhawk", "MFD/Black Hawk.txt", "MFD/Black Hawk.txt", "MFD/blackhawk_unexploded.bmp", "MFD/blackhawk_exploded.bmp"),
Display("BlackHawk", 13, "radar", "Radar", "blackhawk", "Radar/Black Hawk.txt", "Radar/Black Hawk.txt", "Radar/blackhawk_unexploded.bmp", "Radar/blackhawk_exploded.bmp"),
Display("Fafnir", 27, "mfd", "HUD", "fafnir", "HUD/fafnir_unexploded.txt", "HUD/fafnir_exploded.txt", "HUD/fafnir_unexploded.bmp", "HUD/fafnir_exploded.bmp"),
Display("Flea", 28, "mfd", "MFD", "flea", "MFD/Flea unexploded.txt", "MFD/Flea exploded.txt", "MFD/flea_unexploded copy.bmp", "MFD/flea_exploded copy.bmp"),
Display("Gladiator", 29, "mfd", "MFD", "gladiator", "MFD/Gladiator unexploded.txt", "MFD/Gladiator Exploded.txt", "MFD/gladiator_unexploded copy.bmp", "MFD/gladiator_exploded copy.bmp"),
Display("hellspawn", 33, "radar", "radar", "hellspawn", "radar/hellspawn_radar_unexploded.txt", "radar/hellspawn_radar_exploded.txt", "radar/hellspawn_radar_unexploded.bmp", "radar/hellspawn_radar_exploded.bmp"),
Display("Kodiak", 37, "mfd", "MFD", "kodiak", "MFD/Kodiak Cords.txt", "MFD/Kodiak Cords.txt", "MFD/kodiak_unexpanded.bmp", "MFD/kodiak_Explode.bmp"),
Display("Longbow", 39, "mfd", "MFD", "longbow", "MFD/Lomgbow unexploded.txt", "MFD/Longbow exploded.txt", "MFD/longbow_unexploded copy.bmp", "MFD/longbow_exploded copy.bmp"),
Display("Warhammer", 62, "mfd", "MFD", "warhammer", "MFD/Warhammer unexploded.txt", "MFD/Warhammer exploded.txt", "MFD/warhammer_unexploded copy.bmp", "MFD/warhammer_exploded.bmp"),
Display("Warhammer", 62, "radar", "Radar", "warhammer", "Radar/Warhammer unexploded.txt", "Radar/Warhammer exploded.txt", "Radar/warhammer_unexploded copy.bmp", "Radar/warhammer_exploded.bmp"),
)
def parse_coord_arrays() -> dict[str, list[list[tuple[int, ...]]]]:
text = COORD.read_bytes().decode("ascii")
arrays = {}
for name in ("texuv2", "offset2", "texuv3", "offset3"):
match = re.search(rf"\b{name}\[.*?\]=\{{\r?\n(.*?)\r?\n\}};", text, re.S)
if not match:
raise RuntimeError(f"Could not find {name} in {COORD}")
rows = []
for line in match.group(1).splitlines():
if not line.lstrip().startswith("{{"):
continue
values = [
tuple(map(int, group.split(",")))
for group in re.findall(r"\{\s*([0-9 ]+(?:,\s*[0-9 ]+){1,3})\s*\}", line)
]
if len(values) != len(ZONES):
raise RuntimeError(f"Expected 11 zones in {name} row: {line}")
rows.append(values)
if len(rows) != 65:
raise RuntimeError(f"Expected 65 active rows in {name}, found {len(rows)}")
arrays[name] = rows
return arrays
def parse_measurements(path: Path, wanted_section: str, warnings: list[str]) -> dict[str, tuple[int, ...]]:
rows = {}
section = None
for line_number, raw in enumerate(path.read_text(errors="replace").splitlines(), 1):
stripped = raw.strip()
lower = stripped.lower()
zone_match = re.match(r"^(LL|RL|LA|RA|RT|LT|CT|CTR|HD|S1|S2)\b(.*)$", stripped, re.I)
if not zone_match:
if "unexploded" in lower or "unexpanded" in lower:
section = "unexploded"
elif "exploded" in lower or "explode" in lower:
section = "exploded"
continue
if section and section != wanted_section:
continue
zone = zone_match.group(1).upper()
remainder = zone_match.group(2)
numbers = tuple(map(int, re.findall(r"\d+", remainder)))
if "." in remainder:
warnings.append(f"{path.relative_to(ROOT)}:{line_number}: treated '.' as a coordinate separator")
if not numbers:
warnings.append(f"{path.relative_to(ROOT)}:{line_number}: blank {zone} value treated as zero")
rows[zone] = numbers
return rows
def normalize_measurements(display: Display, warnings: list[str]) -> tuple[list[tuple[int, ...]], list[tuple[int, ...]]]:
base = ROOT / display.folder
unexploded_path = base / display.unexploded_text
exploded_path = base / display.exploded_text
unexploded = parse_measurements(unexploded_path, "unexploded", warnings)
exploded = parse_measurements(exploded_path, "exploded", warnings)
unexploded_lengths = {len(value) for value in unexploded.values() if value and any(value)}
exploded_lengths = {len(value) for value in exploded.values() if value and any(value)}
if unexploded_lengths == {4} and exploded_lengths == {2}:
mode = "modern"
elif unexploded_lengths == {2} and exploded_lengths == {4}:
mode = "legacy"
else:
raise RuntimeError(
f"Cannot determine format for {display.folder} {display.kind}: "
f"unexploded lengths={unexploded_lengths}, exploded lengths={exploded_lengths}"
)
rectangles = []
offsets = []
for zone in ZONES:
un = unexploded.get(zone, ())
ex = exploded.get(zone, ())
if not un:
un = (0, 0, 0, 0) if mode == "modern" else (0, 0)
if not ex:
ex = (0, 0) if mode == "modern" else (0, 0, 0, 0)
expected_un = 4 if mode == "modern" else 2
expected_ex = 2 if mode == "modern" else 4
if un and not any(un):
un = (0,) * expected_un
if ex and not any(ex):
ex = (0,) * expected_ex
if len(un) != expected_un or len(ex) != expected_ex:
raise RuntimeError(f"Invalid {zone} values for {display.folder} {display.kind}: {un}, {ex}")
if mode == "modern":
rectangle = un
offset = ex
else:
width = ex[2] - ex[0]
height = ex[3] - ex[1]
rectangle = (un[0], un[1], un[0] + width, un[1] + height) if any(ex) else (0, 0, 0, 0)
offset = ex[:2]
rectangles.append(rectangle)
offsets.append(offset)
return rectangles, offsets
def load_unexploded(display: Display) -> Image.Image:
base = ROOT / display.folder
if display.source_image:
source = Image.open(base / display.source_image).convert("RGB")
scaled = source.resize((320, 320), Image.Resampling.LANCZOS)
prepared = Image.new("RGB", (340, 340), "black")
prepared.paste(scaled, (10, 10))
else:
prepared = Image.open(base / display.unexploded_image).convert("RGB")
if prepared.size == (512, 512):
return prepared
if prepared.width > 512 or prepared.height > 512:
raise RuntimeError(f"Unexpected unexploded size {prepared.size} for {display.folder} {display.kind}")
canvas = Image.new("RGB", (512, 512), "black")
canvas.paste(prepared, (0, 0))
return canvas
def component_boxes(rectangles: list[tuple[int, ...]], offsets: list[tuple[int, ...]]) -> list[tuple[int, ...]]:
boxes = []
for rectangle, offset in zip(rectangles, offsets):
if not any(rectangle) or not any(offset):
boxes.append((0, 0, 0, 0))
continue
width = rectangle[2] - rectangle[0]
height = rectangle[3] - rectangle[1]
boxes.append((offset[0], offset[1], offset[0] + width, offset[1] + height))
return boxes
def draw_legend(draw: ImageDraw.ImageDraw, font: ImageFont.ImageFont) -> None:
draw.rectangle((365, 12, 505, 67), fill="black", outline="white", width=1)
draw.line((377, 30, 402, 30), fill=COLORS["current"], width=3)
draw.text((410, 22), "current", fill=COLORS["current"], font=font)
draw.line((377, 51, 402, 51), fill=COLORS["provided"], width=3)
draw.text((410, 43), "provided", fill=COLORS["provided"], font=font)
def draw_comparison(
image: Image.Image,
current: list[tuple[int, ...]],
provided: list[tuple[int, ...]],
output: Path,
mark_origins: bool,
) -> None:
draw = ImageDraw.Draw(image)
font = ImageFont.load_default(size=14)
for label, boxes in (("current", current), ("provided", provided)):
color = COLORS[label]
for zone, box in zip(ZONES, boxes):
if not any(box):
continue
draw.rectangle(box, outline=color, width=3)
if mark_origins:
x, y = box[:2]
draw.line((x - 4, y, x + 5, y), fill=color, width=2)
draw.line((x, y - 4, x, y + 5), fill=color, width=2)
draw.text((box[0] + 3, box[1] + 2), zone, fill=color, font=font, stroke_width=2, stroke_fill="black")
draw_legend(draw, font)
output.parent.mkdir(parents=True, exist_ok=True)
image.save(output)
def validate_provided(
display: Display,
rectangles: list[tuple[int, ...]],
offsets: list[tuple[int, ...]],
warnings: list[str],
) -> None:
source_limit = 340 if display.kind == "mfd" else 512
for zone, rectangle, offset in zip(ZONES, rectangles, offsets):
if any(rectangle):
if rectangle[2] <= rectangle[0] or rectangle[3] <= rectangle[1]:
warnings.append(f"{display.folder} {display.kind} {zone}: non-positive source rectangle {rectangle}")
if min(rectangle) < 0 or max(rectangle) > source_limit:
warnings.append(f"{display.folder} {display.kind} {zone}: source rectangle outside 0..{source_limit}: {rectangle}")
if any(offset) and (offset[0] < 0 or offset[1] < 0 or offset[0] > 512 or offset[1] > 512):
warnings.append(f"{display.folder} {display.kind} {zone}: offset outside 512 canvas: {offset}")
def difference_count(left: list[tuple[int, ...]], right: list[tuple[int, ...]]) -> int:
return sum(a != b for a, b in zip(left, right))
def main() -> None:
arrays = parse_coord_arrays()
results = []
all_warnings = []
generated = []
for display in DISPLAYS:
warnings = []
provided_rectangles, provided_offsets = normalize_measurements(display, warnings)
validate_provided(display, provided_rectangles, provided_offsets, warnings)
current_rectangles = arrays["texuv2" if display.kind == "mfd" else "texuv3"][display.mech_id]
current_offsets = arrays["offset2" if display.kind == "mfd" else "offset3"][display.mech_id]
output_dir = ROOT / display.folder / display.output_dir
unexploded_output = output_dir / f"{display.slug}_{display.kind}_unexploded_coords_comparison.png"
exploded_output = output_dir / f"{display.slug}_{display.kind}_exploded_coords_comparison.png"
draw_comparison(
load_unexploded(display),
current_rectangles,
provided_rectangles,
unexploded_output,
mark_origins=False,
)
exploded_image = Image.open(ROOT / display.folder / display.exploded_image).convert("RGB")
draw_comparison(
exploded_image,
component_boxes(current_rectangles, current_offsets),
component_boxes(provided_rectangles, provided_offsets),
exploded_output,
mark_origins=True,
)
rectangle_differences = difference_count(current_rectangles, provided_rectangles)
offset_differences = difference_count(current_offsets, provided_offsets)
status = "EXACT" if rectangle_differences == 0 and offset_differences == 0 else "DIFFERENT"
results.append((display, status, rectangle_differences, offset_differences, warnings))
generated.extend((unexploded_output, exploded_output))
all_warnings.extend(warnings)
configured = {(display.folder.lower(), display.kind) for display in DISPLAYS}
missing = []
for folder in sorted(path.name for path in ROOT.iterdir() if path.is_dir()):
for kind in ("mfd", "radar"):
if (folder.lower(), kind) not in configured:
missing.append((folder, kind))
lines = [
"# J&J Coordinate Comparison Summary",
"",
"Generated by `generate_comparison_maps.py`. Red is the current `coord.cpp` mapping; green is the provided J&J mapping.",
"",
"`EXACT` means all eleven source rectangles and all eleven exploded offsets match numerically. `DIFFERENT` means at least one value differs; inspect both generated images.",
"",
"| Mech | Display | Status | Source rectangles different | Exploded offsets different |",
"|---|---:|---:|---:|---:|",
]
for display, status, rectangle_differences, offset_differences, _ in results:
lines.append(f"| {display.folder} | {display.kind.upper()} | **{status}** | {rectangle_differences}/11 | {offset_differences}/11 |")
lines.extend(("", "## Missing comparison inputs", ""))
for folder, kind in missing:
lines.append(f"- `{folder}` {kind.upper()}: no complete provided mapping/image set configured")
lines.extend(("", "## Input warnings", ""))
if all_warnings:
for warning in sorted(set(all_warnings)):
lines.append(f"- {warning}")
else:
lines.append("- None")
lines.extend(("", "## Generated files", ""))
for output in generated:
lines.append(f"- `{output.relative_to(ROOT)}`")
lines.append("")
(ROOT / "COMPARISON-SUMMARY.md").write_text("\n".join(lines))
exact = sum(status == "EXACT" for _, status, _, _, _ in results)
print(f"Generated {len(generated)} maps for {len(results)} display sets: {exact} exact, {len(results) - exact} different")
print(f"Warnings: {len(set(all_warnings))}; missing display sets: {len(missing)}")
if __name__ == "__main__":
main()
@@ -2,20 +2,20 @@
// MSL ADD MECH
// MFD
float texuv2[65][11][4]={
{{186,156,237,336},{ 96,156,147,336},{237, 24,294,168},{ 57, 24, 93,162},{ 99, 39,150,129},{186, 39,234,129},{312, 48,375,207},{ 0, 0, 0, 0},{159, 24,177, 39},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Annihilator
{{ 78,152,156,328},{182,152,262,328},{ 56, 72,128,164},{210, 72,284,164},{180, 64,208,200},{132, 64,160,200},{146, 10,192,198},{ 0, 0, 0, 0},{162, 32,176, 38},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Annihilator
{{195,171,258,351},{ 69,171,132,351},{264,168,309,240},{ 15,168, 60,240},{ 54, 39,141,162},{183, 39,270,162},{141, 51,183,207},{ 0, 0, 0, 0},{153, 15,171, 39},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Archer
{{250,150,328,330},{ 10,150, 90,330},{254, 86,332,136},{ 4, 86, 84,136},{102,252,158,310},{168,252,226,312},{128, 34,210,198},{ 0, 0, 0, 0},{154, 10,174, 24},{228, 8,308, 64},{ 28, 8,108, 64}}, // M_Arctic Wolf
{{254,142,324,334},{ 16,142, 86,334},{248, 56,326,108},{ 12, 56, 92,108},{ 94, 70,156,152},{182, 70,246,152},{132,166,208,292},{ 0, 0, 0, 0},{ 40, 24, 58, 30},{140,302,198,332},{122, 2,218, 60}}, // M_Ares
{{202,130,326,338},{ 6,130,130,338},{242, 14,334,120},{ 6, 18, 94,100},{100, 26,125,118},{198, 18,232,116},{134, 2,190,188},{ 0, 0, 0, 0},{150,220,174,226},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Argus
{{270,136,330,332},{ 10,136, 68,332},{240, 28,302,114},{ 38, 28, 92,114},{114, 12,218,116},{178,218,218,282},{148,128,192,182},{ 0, 0, 0, 0},{190,294,200,302},{140,230,164,278},{ 0, 0, 0, 0}}, // M_Assassin2
{{ 42,138,158,326},{176,138,292,336},{ 54, 30,124,106},{224, 24,306,124},{194, 38,222,100},{124, 38,142,100},{128, 14,210,186},{ 0, 0, 0, 0},{156, 48,178, 56},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Argus
{{ 98,142,148,328},{190,142,240,328},{ 56, 48,124,154},{216, 48,282,154},{180, 22,220,120},{120, 22,158,120},{144, 36,194,186},{ 0, 0, 0, 0},{158, 70,180, 86},{148, 10,190, 48},{ 0, 0, 0, 0}}, // M_Assassin2
{{174,153,225,345},{111,153,162,345},{219, 33,276,153},{ 60, 33,117,153},{126, 45,150,144},{186, 45,210,144},{ 6, 24, 54,198},{ 0, 0, 0, 0},{144, 9,162, 18},{291,111,309,129},{252,195,273,252}}, // M_Atlas
{{180,135,285,342},{ 60,135,165,342},{234, 84,291,132},{ 54, 84,111,132},{114, 27,150,126},{195, 27,231,126},{156, 21,189,135},{ 0, 0, 0, 0},{165, 3,180, 18},{297,129,378,168},{ 0, 0, 0, 0}}, // M_Avatar
{{ 60,108,104,250},{138,108,180,250},{ 40, 40, 74, 84},{168, 40,218, 88},{140, 28,168,104},{ 76, 28,102,104},{ 98, 6,146,138},{ 0, 0, 0, 0},{116, 16,124, 24},{112, 78,128, 94},{ 0, 0, 0, 0}}, // M_Avatar
{{186,156,237,336},{ 96,156,147,336},{237, 24,294,168},{ 57, 24, 93,162},{ 99, 39,150,129},{186, 39,234,129},{312, 48,375,207},{ 0, 0, 0, 0},{159, 24,177, 39},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Awesome
{{174,153,225,345},{111,153,162,345},{219, 33,276,153},{ 60, 33,117,153},{126, 45,150,144},{186, 45,210,144},{ 6, 24, 54,198},{ 0, 0, 0, 0},{144, 9,162, 18},{291,111,309,129},{252,195,273,252}}, // M_Battlemaster
{{174,153,225,345},{111,153,162,345},{219, 33,276,153},{ 60, 33,117,153},{126, 45,150,144},{186, 45,210,144},{ 6, 24, 54,198},{ 0, 0, 0, 0},{144, 9,162, 18},{291,111,309,129},{ 0, 0, 0, 0}}, // M_BattlemasterIIc
{{186,177,243,375},{102,177,159,375},{249, 27,324,162},{ 18, 18,102,162},{111, 51,147,141},{201, 51,237,141},{153, 18,192,174},{ 0, 0, 0, 0},{210, 30,231, 39},{ 21,213, 75,330},{ 0, 0, 0, 0}}, // M_Behemoth
{{ 76,170,152,302},{184,170,262,302},{ 12,106,126,174},{212,106,326,174},{176, 86,226,174},{110, 82,160,174},{140, 96,198,224},{ 0, 0, 0, 0},{164,122,174,134},{142, 38,194, 98},{ 0, 0, 0, 0}}, // M_Behemoth
{{186,177,243,375},{102,177,159,375},{249, 27,324,162},{ 18, 18,102,162},{111, 51,147,141},{201, 51,237,141},{153, 18,192,174},{ 0, 0, 0, 0},{210, 30,231, 39},{ 21,213, 75,330},{ 0, 0, 0, 0}}, // M_Behemothii
{{177,189,267,378},{ 69,189,159,378},{240, 78,288,171},{ 48, 78, 96,171},{102, 39,165,141},{171, 39,234,141},{309, 60,375,177},{ 0, 0, 0, 0},{156, 24,180, 33},{243, 18,300, 72},{ 36, 18, 93, 72}}, // M_Blackhawk
{{ 32, 82,140,294},{188, 82,294,294},{ 6, 12, 88,134},{236, 12,318,134},{176, 18,234,132},{ 92, 18,244, 36},{152, 6,172,132},{ 0, 0, 0, 0},{154, 38,168, 42},{110,112,142,138},{184,112,216,138}}, // M_Blackhawk
{{186,177,243,375},{102,177,159,375},{249, 27,324,162},{ 18, 18,102,162},{111, 51,147,141},{201, 51,237,141},{153, 18,192,174},{ 0, 0, 0, 0},{210, 30,231, 39},{ 21,213, 75,330},{ 0, 0, 0, 0}}, // M_Blackknight
{{256,122,336,334},{ 8,122, 88,334},{234, 24,282,100},{ 62, 14,108, 90},{108,162,152,220},{194,162,238,220},{118, 2,226,152},{ 0, 0, 0, 0},{164,208,180,222},{194,238,218,290},{126,238,152,290}}, // M_Blacklanner
{{262,160,324,336},{ 18,160, 80,336},{284, 74,328,148},{ 14, 74, 58,148},{ 62, 2,150,156},{150,204,238,326},{172, 10,248,190},{ 0, 0, 0, 0},{282, 36,300, 52},{102,200,130,236},{100,288,128,326}}, // M_Brigand
@@ -30,9 +30,9 @@ float texuv2[65][11][4]={
// {{250,174,300,336},{ 40,174, 90,336},{280, 2,322,110},{ 16, 2, 58,110},{ 62, 60,104,136},{234, 60,276,136},{110, 62,230,198},{ 0, 0, 0, 0},{156, 24,182, 34},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Dasher
{{191,142,265,337},{ 76,142,150,337},{266, 58,328,142},{ 12, 58, 75,141},{ 93, 56,150,129},{190, 56,246,129},{152, 60,188,198},{ 0, 0, 0, 0},{166, 42,177, 49},{184, 2,245, 55},{ 96, 2,157, 55}}, // M_Deimos
{{237,147,300,345},{ 84,147,147,345},{324, 78,372,147},{ 12, 72, 66,135},{ 81, 33,147,111},{237, 33,300,111},{162, 42,222,186},{ 0, 0, 0, 0},{177, 15,207, 30},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Dragon
{{250,140,332,336},{ 90,140,170,336},{260, 10,306,128},{110, 10,156,128},{ 2, 2, 88,140},{ 2,180, 86,318},{180,130,234,334},{ 0, 0, 0, 0},{200, 50,216, 60},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Fafnir - Skippy
{{210,110,296,336},{ 10,110, 96,336},{220, 20,226, 40},{100, 20,106, 42},{ 20, 20, 74, 94},{260, 20,314, 94},{140, 20,194,178},{ 0, 0, 0, 0},{130,200,160,216},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Flea - Skippy
{{270,160,328,330},{ 10,160, 68,330},{280, 10,328,108},{ 10, 10,108, 50},{ 70, 10,106,120},{230, 10,262,120},{130, 10,210,216},{ 0, 0, 0, 0},{160,260,184,268},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Gladiator
{{ 86,142,162,326},{178,142,254,326},{ 24, 22, 68,134},{272, 22,316,134},{188, 14,270,136},{ 68, 14,150,136},{124, 16,214,202},{ 0, 0, 0, 0},{162, 90,178,100},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Fafnir - Skippy
{{ 52,102,136,314},{182,102,270,314},{ 96, 60,102, 78},{216, 62,224, 80},{174, 14,224, 84},{ 92, 14,146, 82},{134, 22,186,154},{ 0, 0, 0, 0},{144, 44,174, 60},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Flea - Skippy
{{ 88,156,142,318},{170,156,226,318},{ 68, 52,108,144},{206, 52,250,144},{170, 32,212,138},{102, 32,144,138},{120, 4,196,196},{ 0, 0, 0, 0},{148, 58,168, 66},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Gladiator
{{210,164,286,334},{ 56,164,132,334},{278, 18,314,152},{ 24, 18, 66,144},{ 82, 32,124,132},{218, 28,262,136},{142, 16,200,190},{ 0, 0, 0, 0},{158,214,184,228},{160,254,190,280},{ 0, 0, 0, 0}}, // M_Grizzly
{{237,174,312,348},{ 60,174,135,348},{294, 57,342,165},{ 24, 57, 72,165},{ 81, 75,123,165},{240, 69,285,165},{153, 48,222,222},{ 0, 0, 0, 0},{174, 27,201, 33},{102, 24,141, 60},{ 0, 0, 0, 0}}, // M_Hauptmann
{{165,144,252,318},{ 69,144,156,318},{291, 90,342,141},{ 24, 90, 75,141},{ 93, 39,141,138},{231, 42,279,141},{282,174,327,291},{ 0, 0, 0, 0},{150, 18,171, 27},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Hellhound
@@ -40,9 +40,9 @@ float texuv2[65][11][4]={
{{255,180,315,372},{ 72,183,132,375},{312, 69,369,144},{ 9, 63, 69,147},{ 75, 27,156,138},{225, 24,300,135},{165, 15,216,198},{ 0, 0, 0, 0},{237, 9,270, 18},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Highlander
{{270,136,330,332},{ 10,136, 68,332},{240, 28,302,114},{ 38, 28, 92,114},{114, 12,218,116},{178,218,218,282},{148,128,192,182},{ 0, 0, 0, 0},{190,294,200,302},{140,230,164,278},{154,192,186,208}}, // M_HollanderII
{{210,165,276,336},{ 69,165,135,336},{327, 48,372,141},{ 6, 48, 51,141},{ 18,165, 54,228},{276, 15,312,147},{135, 27,204,198},{ 0, 0, 0, 0},{165, 9,183, 15},{ 66, 27,114, 90},{ 0, 0, 0, 0}}, // M_Hunchback
{{230,160,310,336},{ 22,160,102,336},{266, 62,336,156},{ 4, 62, 74,156},{ 86, 16,152, 88},{188, 16,254,138},{116,174,220,316},{ 0, 0, 0, 0},{276, 0,340, 60},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Kodiak - GhostHawk
{{ 80,152,154,314},{166,152,238,314},{ 36, 50,102,136},{218, 50,286,134},{180, 24,238,138},{ 82, 24,142,138},{114, 6,208,190},{ 0, 0, 0, 0},{146,118,168,124},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Kodiak - GhostHawk
{{192,156,243,342},{ 96,156,150,342},{231, 69,285,156},{ 60, 69,108,156},{114, 54,149,146},{195, 39,225,141},{150, 48,192,192},{ 0, 0, 0, 0},{162, 30,177, 42},{108, 21,146, 50},{ 0, 0, 0, 0}}, // M_Loki
{{270,138,332,332},{ 10,138, 72,332},{186,138,260,216},{ 82,138,158,216},{ 32, 12,100,104},{236, 12,304,104},{142, 18,198,122},{ 0, 0, 0, 0},{162,316,176,326},{134,238,206,292},{ 0, 0, 0, 0}}, // M_Longbow - GhostHawk
{{ 88,120,146,300},{172,120,230,300},{ 4, 38, 70,110},{248, 38,314,108},{186, 30,248,114},{ 72, 30,136,116},{134, 20,184,114},{ 0, 0, 0, 0},{152, 62,168, 68},{132,116,188,166},{ 0, 0, 0, 0}}, // M_Longbow - GhostHawk
{{177,189,267,378},{ 69,189,159,378},{240, 78,288,171},{ 48, 78, 96,171},{102, 39,165,141},{171, 39,234,141},{309, 60,375,177},{ 0, 0, 0, 0},{156, 24,180, 33},{243, 18,300, 72},{ 36, 18, 93, 72}}, // M_Madcat
{{201,159,294,333},{ 48,159,141,333},{225,102,285,153},{ 57,102,117,153},{120, 45,168,150},{174, 45,222,150},{141,198,201,327},{ 0, 0, 0, 0},{159, 27,183, 39},{ 57, 12,108, 75},{234, 12,285, 75}}, // M_Madcat_MKII
{{225,153,360,342},{ 24,153,159,342},{297, 30,378,129},{ 6, 30, 87,129},{123, 45,165,147},{219, 45,261,147},{168, 45,216,198},{ 0, 0, 0, 0},{174, 18,210, 30},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Masakari
@@ -65,7 +65,7 @@ float texuv2[65][11][4]={
{{207,156,288,342},{ 51,156,132,342},{264, 30,327,123},{ 18, 30, 75,147},{ 90, 27,138,147},{207, 27,249,147},{138, 33,207,141},{ 0, 0, 0, 0},{159, 6,183, 18},{138,144,201,216},{ 0, 0, 0, 0}}, // M_Uziel
{{218,138,280,334},{ 20,138, 82,334},{270, 8,328,134},{ 10, 8, 70,114},{ 88, 66,130,156},{172, 66,210,156},{124,166,176,304},{ 0, 0, 0, 0},{130, 4,210, 52},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Victor - GhostHawk
{{180,135,285,342},{ 60,135,165,342},{234, 84,291,132},{ 54, 84,111,132},{114, 27,150,126},{195, 27,231,126},{156, 21,189,135},{ 0, 0, 0, 0},{165, 3,180, 18},{297,129,378,168},{ 0, 0, 0, 0}}, // M_Vulture
{{177,189,267,378},{ 69,189,159,378},{240, 78,288,171},{ 48, 78, 96,171},{102, 39,165,141},{171, 39,234,141},{309, 60,375,177},{ 0, 0, 0, 0},{156, 24,180, 33},{243, 18,300, 72},{ 36, 18, 93, 72}}, // M_Warhammer
{{ 78,134,116,256},{142,134,180,256},{ 52, 46, 90,126},{168, 44,208,126},{146, 42,166,112},{ 90, 42,110,112},{110, 28,184,156},{ 0, 0, 0, 0},{118, 56,138, 64},{ 56, 4, 90, 48},{172, 24,202, 48}}, // M_Warhammer
{{198,165,237,348},{102,165,141,348},{270, 42,309,150},{ 24, 45, 75,159},{ 93, 63,123,141},{216, 63,246,141},{138, 24,198,213},{ 0, 0, 0, 0},{216, 42,240, 48},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Wolfhound
{{250,152,312,328},{ 26,152, 88,328},{186,234,228,294},{112,234,150,282},{ 20, 12, 96,116},{244, 12,318,116},{128, 12,210,190},{ 0, 0, 0, 0},{158,206,182,218},{ 0, 0, 0, 0},{ 0, 0, 0, 0}} // M_Zeus
};
@@ -73,20 +73,20 @@ float texuv2[65][11][4]={
// MSL ADD MECH
// MFD
int offset2[65][11][2]={
{{186,151},{ 96,151},{234, 19},{ 60, 19},{ 99, 34},{186, 34},{138, 43},{ 0, 0},{159, 55},{ 0, 0},{ 0, 0}}, // M_Annihilator
{{ 0,148},{260,148},{ 6, 40},{260, 40},{212, 80},{100, 80},{146, 12},{ 0, 0},{162,236},{ 0, 0},{ 0, 0}}, // M_Annihilator
{{186,153},{ 99,153},{252,102},{ 51,102},{ 66, 21},{195, 21},{153, 33},{ 0, 0},{165, 51},{ 0, 0},{ 0, 0}}, // M_Archer
{{210,156},{ 50,156},{224, 92},{ 34, 92},{ 82, 68},{198, 68},{128, 40},{ 0, 0},{154, 46},{198, 14},{ 58, 14}}, // M_Arctic Wolf
{{184,148},{ 86,148},{248, 62},{ 12, 62},{ 94, 56},{182, 56},{132, 62},{ 0, 0},{160, 70},{140,128},{122, 8}}, // M_Ares
{{172,130},{ 26,130},{222, 14},{ 26, 18},{110, 26},{188, 18},{134, 2},{ 0, 0},{150, 40},{ 0, 0},{ 0, 0}}, // M_Argus
{{190,126},{ 90,126},{220, 38},{ 68, 38},{114, 12},{178, 8},{148,118},{ 0, 0},{190, 24},{140, 20},{ 0, 0}}, // M_Assassin2
{{ 4,138},{216,138},{ 12, 14},{242, 14},{174,200},{140,200},{126, 14},{ 0, 0},{156,294},{ 0, 0},{ 0, 0}}, // M_Argus
{{ 6,148},{282,148},{ 6, 10},{266, 10},{206, 22},{ 98, 22},{144,180},{ 0, 0},{158,140},{148, 10},{ 0, 0}}, // M_Assassin2
{{174,143},{111,143},{216, 23},{ 63, 23},{126, 35},{186, 35},{147, 14},{ 0, 0},{144, 29},{117,101},{207,140}}, // M_Atlas
{{180,135},{ 60,135},{234, 84},{ 54, 84},{114, 27},{195, 27},{156, 21},{ 0, 0},{165, 12},{132,129},{ 0, 0}}, // M_Avatar
{{ 12,174},{198,174},{ 12, 42},{198, 42},{158, 42},{ 60, 42},{ 96, 42},{ 0, 0},{120,202},{116,238},{ 0, 0}}, // M_Avatar
{{186,151},{ 96,151},{234, 19},{ 60, 19},{ 99, 34},{186, 34},{138, 43},{ 0, 0},{159, 55},{ 0, 0},{ 0, 0}}, // M_Awesome
{{174,143},{111,143},{216, 23},{ 63, 23},{126, 35},{186, 35},{147, 14},{ 0, 0},{144, 29},{117,101},{207,140}}, // M_Battlemaster
{{174,143},{111,143},{216, 23},{ 63, 23},{126, 35},{186, 35},{147, 14},{ 0, 0},{144, 29},{117,101},{ 0, 0}}, // M_BattlemasterIIc
{{186,142},{102,142},{234, 22},{ 27, 13},{117, 46},{192, 46},{153, 13},{ 0, 0},{162, 25},{ 24, 13},{ 0, 0}}, // M_Behemoth
{{ 6,172},{246,172},{ 6, 8},{281, 8},{226, 82},{ 72, 82},{138,172},{ 0, 0},{164,122},{142, 8},{ 0, 0}}, // M_Behemoth
{{186,142},{102,142},{234, 22},{ 27, 13},{117, 46},{192, 46},{153, 13},{ 0, 0},{162, 25},{ 24, 13},{ 0, 0}}, // M_Behemothii
{{174,147},{ 72,147},{240, 78},{ 48, 78},{102, 39},{171, 39},{135, 60},{ 0, 0},{156, 69},{216, 18},{ 63, 18}}, // M_Black Hawk
{{ 8,128},{228,128},{ 8, 0},{252, 0},{188, 0},{ 0, 96},{160, 0},{ 0, 0},{164,146},{132,168},{176,168}}, // M_Black Hawk
{{186,142},{102,142},{234, 22},{ 27, 13},{117, 46},{192, 46},{153, 13},{ 0, 0},{162, 25},{ 24, 13},{ 0, 0}}, // M_Black Knight
{{196,128},{ 68,128},{234, 30},{ 62, 20},{108, 18},{194, 18},{118, 8},{ 0, 0},{164, 24},{194, 44},{126, 44}}, // M_Black Lanner
{{182,166},{ 98,166},{244, 80},{ 54, 80},{ 82, 8},{170, 40},{142, 16},{ 0, 0},{182, 42},{112,126},{200,124}}, // M_Brigand
@@ -101,9 +101,9 @@ int offset2[65][11][2]={
// {{190,180},{100,180},{230, 8},{ 66, 8},{102, 66},{194, 66},{110, 68},{ 0, 0},{156,100},{ 0, 0},{ 0, 0}}, // M_Dasher
{{191,144},{ 76,144},{242, 60},{ 35, 60},{ 93, 58},{190, 58},{152, 62},{ 0, 0},{165, 77},{184, 7},{ 96, 7}}, // M_Deimos
{{198,134},{ 99,134},{234, 65},{ 63, 59},{ 87, 20},{207, 20},{150, 29},{ 0, 0},{165, 44},{ 0, 0},{ 0, 0}}, // M_Dragon
{{177,141},{ 77,141},{278, 10},{ 10, 10},{ 60, 2},{191, 2},{141, 3},{ 0, 0},{159, 83},{ 0, 0},{ 0, 0}}, // M_Fafnir - Skippy
{{198,112},{ 56,112},{230, 62},{104, 62},{100, 18},{186, 18},{144, 6},{ 0, 0},{154, 48},{ 0, 0},{ 0, 0}}, // M_Flea - Skippy
{{182,166},{ 95,166},{221, 54},{ 72, 54},{107, 31},{194, 31},{128, 2},{ 0, 0},{156, 58},{ 0, 0},{ 0, 0}}, // M_Gladiator - Skippy
{{ 8,142},{256,142},{ 8, 2},{288, 2},{206, 14},{ 50, 14},{126,140},{ 0, 0},{162, 90},{ 0, 0},{ 0, 0}}, // M_Fafnir - Skippy
{{ 8,120},{244,120},{ 24, 62},{306, 66},{218, 14},{ 66, 14},{134, 22},{ 0, 0},{146,210},{ 0, 0},{ 0, 0}}, // M_Flea - Skippy
{{ 20,174},{232,174},{ 4, 52},{278, 52},{218, 32},{ 60, 32},{120, 4},{ 0, 0},{148, 58},{ 0, 0},{ 0, 0}}, // M_Gladiator - Skippy
{{180,170},{ 86,170},{238, 44},{ 64, 44},{102, 38},{198, 34},{142, 22},{ 0, 0},{158, 90},{210, 10},{ 0, 0}}, // M_Grizzly
{{192,155},{ 69,155},{240, 56},{ 51, 56},{102, 59},{192, 53},{135, 29},{ 0, 0},{156, 53},{102, 20},{ 0, 0}}, // M_Hauptmann
{{165,144},{ 69,144},{228, 90},{ 42, 90},{ 93, 42},{180, 42},{138, 66},{ 0, 0},{150,114},{ 0, 0},{ 0, 0}}, // M_Hellhound
@@ -111,9 +111,9 @@ int offset2[65][11][2]={
{{183,150},{ 96,150},{237, 75},{ 39, 72},{ 66, 33},{192, 33},{144, 12},{ 0, 0},{153, 27},{ 0, 0},{ 0, 0}}, // M_Highlander
{{190,126},{ 90,126},{220, 38},{ 68, 38},{114, 12},{178, 8},{148,118},{ 0, 0},{190, 24},{140, 20},{154, 92}}, // M_HollnaderII
{{180,160},{ 93,160},{231, 55},{ 63, 55},{108, 91},{195, 22},{135, 22},{ 0, 0},{165, 34},{105, 22},{ 0, 0}}, // M_Hunchback
{{178,162},{ 84,162},{236, 52},{ 36, 52},{ 86, 22},{188, 22},{118, 62},{ 0, 0},{138, 2},{ 0, 0},{ 0, 0}}, // M_Kodiak - GhostHawk
{{ 2,176},{264,176},{ 10, 60},{264, 62},{198, 22},{ 82, 22},{124,154},{ 0, 0},{160, 18},{ 0, 0},{ 0, 0}}, // M_Kodiak - GhostHawk
{{192,151},{ 96,151},{231, 64},{ 60, 64},{114, 49},{195, 34},{150, 43},{ 0, 0},{162, 55},{108, 16},{ 0, 0}}, // M_Loki
{{186,128},{ 94,128},{166, 36},{ 0, 36},{ 76, 32},{198, 30},{142, 18},{ 0, 0},{164, 62},{134,122},{ 0, 0}}, // M_Longbow - GhostHawk
{{ 4,152},{272,152},{ 6, 38},{248, 38},{192,152},{ 72,152},{134, 20},{ 0, 0},{154,146},{132,268},{ 0, 0}}, // M_Longbow - GhostHawk
{{174,147},{ 72,147},{240, 78},{ 48, 78},{102, 39},{171, 39},{135, 60},{ 0, 0},{156, 69},{216, 18},{ 63, 18}}, // M_Madcat
{{201,162},{ 48,162},{225,105},{ 57,105},{120, 48},{174, 48},{141, 81},{ 0, 0},{159, 93},{ 72, 15},{219, 15}}, // M_Madcat_MKII
{{186,143},{ 21,143},{234, 77},{ 27, 77},{102, 35},{198, 35},{147, 35},{ 0, 0},{153, 86},{ 0, 0},{ 0, 0}}, // M_Masakari
@@ -136,27 +136,27 @@ int offset2[65][11][2]={
{{207,146},{ 51,146},{243, 59},{ 39, 59},{ 90, 17},{207, 17},{138, 23},{ 0, 0},{159, 95},{138,134},{ 0, 0}}, // M_Uziel
{{185,140},{ 98,140},{226, 34},{ 56, 34},{116, 52},{186, 52},{146, 52},{ 0, 0},{130, 4},{ 0, 0},{ 0, 0}}, // M_Victor - GhostHawk
{{180,135},{ 60,135},{234, 84},{ 54, 84},{114, 27},{195, 27},{156, 21},{ 0, 0},{165, 12},{132,129},{ 0, 0}}, // M_Vulture
{{174,147},{ 72,147},{240, 78},{ 48, 78},{102, 39},{171, 39},{135, 60},{ 0, 0},{156, 69},{216, 18},{ 63, 18}}, // M_Warhammer
{{ 8,210},{238,210},{ 6, 46},{238, 46},{186, 42},{ 58, 42},{ 92, 28},{ 0, 0},{134,226},{ 76,220},{178,222}}, // M_Warhammer
{{198,155},{102,155},{219, 32},{ 69, 35},{123, 53},{186, 53},{138, 14},{ 0, 0},{156, 32},{ 0, 0},{ 0, 0}}, // M_Wolfhound
{{180,152},{ 96,152},{226,104},{ 72,104},{ 70, 32},{194, 32},{128, 12},{ 0, 0},{158, 56},{ 0, 0},{ 0, 0}} // M_Zeus
};
// LL RL LA RA RT LT CT CTR HD S1 S2
// 5.03 Secondary Damage Display
float texuv3[65][11][4]={
{{332,280,400,502},{ 94,280,164,504},{422, 14,500,214},{ 14, 12, 76,194},{ 96, 40,166,184},{332, 40,406,184},{206, 48,282,252},{ 0, 0, 0, 0},{236,300,254,310},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Annihilator
{{ 34,186,134,416},{164,186,264,416},{ 4, 78,100, 96},{198, 78,296,196},{158, 66,198,240},{100, 68,140,242},{120, 2,176,242},{ 0, 0, 0, 0},{138, 26,160, 36},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Annihilator
{{404,268,484,496},{ 22,268,102,496},{426,114,484,208},{ 22,116, 82,208},{ 92, 20,224,192},{292, 22,424,192},{230, 44,286,252},{ 0, 0, 0, 0},{240,308,276,348},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Archer
{{356,260,456,490},{ 30,260,132,488},{354,188,454,252},{ 32,188,132,252},{ 62,104,138,180},{358, 94,434,180},{200, 90,308,300},{ 0, 0, 0, 0},{234, 36,262, 56},{360, 8,464, 80},{ 32, 8,136, 80}}, // M_Arctic Wolf
{{402,240,486,472},{ 34,240,118,472},{396,108,502,170},{ 6,108,112,170},{130,100,206,200},{312,100,388,200},{220,106,308,260},{ 0, 0, 0, 0},{250,364,278,378},{230,304,298,344},{206, 14,322, 82}}, // M_Ares
{{300,200,448,448},{ 20,200,170,448},{380, 20,490,146},{ 20, 20,124,116},{140, 20,166,100},{300, 20,342,110},{180, 20,292,242},{ 0, 0, 0, 0},{250,300,276,306},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Argus - Skippy
{{402,240,474,484},{ 24,240, 98,484},{394, 50,462,156},{ 28, 50,100,156},{186, 18,316,146},{334, 14,386, 98},{218,214,292,288},{ 0, 0, 0, 0},{348,132,366,146},{126, 30,160, 92},{ 0, 0, 0, 0}}, // M_Assassin2
{{114,170,178,406},{232,170,294,406},{ 60, 48,144,184},{264, 48,348,184},{218, 18,268,140},{142, 18,190,140},{174, 36,234,140},{ 0, 0, 0, 0},{192, 78,218, 98},{178, 2,230, 48},{ 0, 0, 0, 0}}, // M_Assassin2
{{350,260,420,502},{128,246,200,488},{426, 16,500,172},{ 24, 14, 98,172},{162, 26,216,160},{352, 26,406,160},{258, 50,312,268},{ 0, 0, 0, 0},{262, 6,282, 18},{118,118,146,144},{436,218,464,282}}, // M_Atlas
{{356,274,494,504},{ 12,274,152,502},{446, 98,494,164},{ 12, 98, 60,164},{100, 24,174,162},{344, 24,416,162},{218, 4,310,222},{ 0, 0, 0, 0},{252,286,276,296},{220,358,306,396},{ 0, 0, 0, 0}}, // M_Avatar
{{ 98,174,164,400},{222,174,288,400},{ 62, 64,118,136},{266, 64,348,138},{220, 44,268,168},{118, 44,166,168},{152, 10,232,220},{ 0, 0, 0, 0},{184, 24,202, 40},{178,124,208,154},{ 0, 0, 0, 0}}, // M_Avatar
{{332,280,400,502},{ 94,280,164,504},{422, 14,500,214},{ 14, 12, 76,194},{ 96, 40,166,184},{332, 40,406,184},{206, 48,282,252},{ 0, 0, 0, 0},{236,300,254,310},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Awesome
{{350,260,420,502},{128,246,200,488},{426, 16,500,172},{ 24, 14, 98,172},{162, 26,216,160},{352, 26,406,160},{258, 50,312,268},{ 0, 0, 0, 0},{262, 6,282, 18},{118,118,146,144},{436,218,464,282}}, // M_Battlemaster
{{350,260,420,502},{128,246,200,488},{426, 16,500,172},{ 24, 14, 98,172},{162, 26,216,160},{352, 26,406,160},{258, 50,312,268},{ 0, 0, 0, 0},{262, 6,282, 18},{118,118,146,144},{ 0, 0, 0, 0}}, // M_BattlemasterIIc
{{400,258,472,478},{ 42,258,112,478},{372, 58,456,204},{ 72, 96,128,212},{150,100,190,188},{314,100,354,188},{208, 54,296,252},{ 0, 0, 0, 0},{234, 12,270, 24},{234,282,298,452},{ 0, 0, 0, 0}}, // M_Behemoth
{{ 84,206,184,374},{222,206,322,374},{ 2,124,150,210},{260,124,404,210},{214, 94,278,210},{130, 94,194,210},{166,110,242,274},{ 0, 0, 0, 0},{198,144,210,158},{170, 36,236,112},{ 0, 0, 0, 0}}, // M_Behemoth
{{400,258,472,478},{ 42,258,112,478},{372, 58,456,204},{ 72, 96,128,212},{150,100,190,188},{314,100,354,188},{208, 54,296,252},{ 0, 0, 0, 0},{234, 12,270, 24},{234,282,298,452},{ 0, 0, 0, 0}}, // M_Behemothii
{{368,252,490,494},{ 16,252,138,494},{416, 96,490,214},{ 6, 96, 82,214},{112, 52,194,182},{314, 52,396,182},{202, 80,304,226},{ 0, 0, 0, 0},{238,276,268,286},{416, 22,498, 90},{ 8, 22, 90, 90}}, // M_Black Hawk
{{ 40,118,178,390},{238,118,376,390},{ 8, 28,114,182},{296, 28,404,180},{220, 38,497,182},{114, 38,192,182},{188, 22,222,184},{ 0, 0, 0, 0},{192, 58,218, 78},{134,156,178,190},{230,156,272,190}}, // M_Black Hawk
{{400,258,472,478},{ 42,258,112,478},{372, 58,456,204},{ 72, 96,128,212},{150,100,190,188},{314,100,354,188},{208, 54,296,252},{ 0, 0, 0, 0},{234, 12,270, 24},{234,282,298,452},{ 0, 0, 0, 0}}, // M_Black Knight
{{376,226,472,482},{ 24,226,118,482},{412, 48,468,142},{ 36, 36, 92,128},{110, 34,162,104},{332, 32,386,104},{184, 24,312,202},{ 0, 0, 0, 0},{238,250,258,270},{330,126,364,196},{132,126,166,196}}, // M_Black Lanner
{{390,264,464,474},{ 48,264,122,474},{444, 90,496,188},{ 24, 92, 78,188},{ 90, 12,196,200},{324, 50,430,200},{218, 22,312,240},{ 0, 0, 0, 0},{248,312,272,334},{166,228,200,278},{320,228,354,278}}, // M_Brigand
@@ -177,7 +177,7 @@ float texuv3[65][11][4]={
{{378,276,468,482},{ 44,276,134,482},{398, 86,442,248},{ 68, 86,118,238},{140, 78,196,202},{316, 74,372,204},{222, 60,290,270},{ 0, 0, 0, 0},{232,306,282,340},{332, 26,368, 56},{ 0, 0, 0, 0}}, // M_Grizzly
{{378,266,472,496},{ 28,266,122,496},{432, 46,492,190},{ 18, 46, 80,192},{118, 42,174,154},{340, 42,394,154},{196, 14,316,238},{ 0, 0, 0, 0},{234,306,278,328},{172,294,222,342},{ 0, 0, 0, 0}}, // M_Hauptmann
{{372,242,494,494},{ 14,242,136,494},{418, 78,482,150},{ 14, 78, 78,150},{108, 6,184,148},{322, 6,398,148},{220, 44,288,208},{ 0, 0, 0, 0},{238,292,268,320},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Hellhound
{{400,260,462,482},{ 20,260, 82,482},{260, 20,324,118},{ 20,120, 88,218},{100,260,332,352},{280,260,348,342},{180,260,252,312},{ 0, 0, 0, 0},{120, 20,162, 28},{ 20, 20, 82, 80},{180, 20,232,108}}, // M_Hellspawn - Skippy
{{116,182,180,408},{232,182,294,408},{ 70, 98,138,202},{272, 98,342,202},{202, 96,274,166},{142, 80,204, 96},{136, 96,240,228},{ 0, 0, 0, 0},{156, 84,190, 92},{160, 2,222, 98},{ 78, 42,142,108}}, // M_Hellspawn - Skippy
{{358,262,432,494},{ 62,262,136,494},{412, 74,488,170},{ 10, 74, 82,182},{ 90, 28,188,150},{296, 28,394,150},{206, 4,278,226},{ 0, 0, 0, 0},{228,320,260,330},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Highlander
{{402,240,474,484},{ 24,240, 98,484},{394, 50,462,156},{ 28, 50,100,156},{186, 18,316,146},{334, 14,386, 98},{218,214,292,288},{ 0, 0, 0, 0},{348,132,366,146},{126, 30,160, 92},{232,162,278,192}}, // M_HollanderII
{{400,262,482,486},{ 16,262, 98,486},{396, 86,450,210},{ 60, 86,116,210},{138, 70,186,218},{322, 46,372,218},{214, 46,294,266},{ 0, 0, 0, 0},{240, 14,268, 34},{230,296,282,354},{ 0, 0, 0, 0}}, // M_Hunchback
@@ -206,26 +206,26 @@ float texuv3[65][11][4]={
{{400,292,498,498},{ 10,292,108,498},{378, 20,496, 98},{ 12, 20,130,100},{126,158,192,300},{314,158,380,300},{206,168,302,376},{ 0, 0, 0, 0},{234,108,272,122},{230, 32,276, 78},{ 22,136, 50,166}}, // M_Uziel
{{350,256,425,472},{ 76,236,150,472},{410, 46,484,200},{ 30, 46,104,174},{146, 82,198,192},{310, 82,362,192},{220,218,284,384},{ 0, 0, 0, 0},{206, 6,304, 64},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Victor - GhostHawk
{{356,274,494,504},{ 12,274,152,502},{446, 98,494,164},{ 12, 98, 60,164},{100, 24,174,162},{344, 24,416,162},{218, 4,310,222},{ 0, 0, 0, 0},{252,286,276,296},{220,358,306,396},{ 0, 0, 0, 0}}, // M_Vulture
{{368,252,490,494},{ 16,252,138,494},{416, 96,490,214},{ 6, 96, 82,214},{112, 52,194,182},{314, 52,396,182},{202, 80,304,226},{ 0, 0, 0, 0},{238,276,268,286},{416, 22,498, 90},{ 8, 22, 90, 90}}, // M_Warhammer
{{126,214,186,408},{228,214,286,408},{ 84, 72,146,202},{268, 72,332,204},{234, 68,270,182},{144, 68,180,178},{148, 46,264,246},{ 0, 0, 0, 0},{190, 90,226,106},{ 92, 8,146, 78},{276, 38,322, 80}}, // M_Warhammer
{{326,262,368,486},{142,262,182,486},{380, 72,438,196},{ 72, 72,138,208},{166, 96,200,218},{320, 96,352,216},{214, 48,304,278},{ 0, 0, 0, 0},{240, 10,278, 30},{ 0, 0, 0, 0},{ 0, 0, 0, 0}}, // M_Wolfhound
{{400,262,476,484},{ 34,262,110,484},{284,306,338,378},{182,302,230,362},{ 72, 34,170,166},{340, 34,440,166},{204, 6,306,230},{ 0, 0, 0, 0},{240,250,270,270},{ 0, 0, 0, 0},{ 0, 0, 0, 0}} // M_Zeus
};
// LL RL LA RA RT LT CT CTR HD S1 S2
int offset3[65][11][2]={
{{210,184},{102,182},{274, 4},{ 42, 2},{102, 30},{208, 30},{152, 38},{ 0, 0},{182, 68},{ 0, 0},{ 0, 0}}, // M_Annihilator
{{ 8,270},{398,270},{ 30, 24},{400, 28},{322,120},{146,118},{226, 54},{ 0, 0},{244,338},{ 0, 0},{ 0, 0}}, // M_Annihilator
{{224,178},{112,178},{306,114},{ 52,116},{ 62, 0},{222, 2},{180, 24},{ 0, 0},{190, 38},{ 0, 0},{ 0, 0}}, // M_Archer
{{256,182},{ 50,182},{274,100},{ 32,100},{ 92, 66},{238, 66},{150, 32},{ 0, 0},{184, 38},{240, 0},{ 62, 0}}, // M_Arctic Wolf
{{222,170},{104,170},{296, 68},{ 6, 70},{110, 60},{222, 60},{160, 66},{ 0, 0},{190, 74},{170,144},{146, 4}}, // M_Ares
{{208,158},{ 32,158},{268, 18}, {32, 24},{136, 32},{226, 22},{140, 4},{ 0, 0},{182, 48},{ 0, 0},{ 0, 0}}, // M_Argus - Skippy
{{232,150},{104,150},{264, 40},{ 78, 40},{136, 8},{214, 4},{168,134},{ 0, 0},{228, 22},{166, 20},{ 0, 0}}, // M_Assassin2
{{ 8,220},{336,220},{ 8, 48},{314, 48},{244, 48},{120, 48},{178,206},{ 0, 0},{192, 78},{178, 3},{ 0, 0}}, // M_Assassin2
{{208,162},{130,162},{266, 18},{ 70, 12},{136, 26},{220, 28},{178, 4},{ 0, 0},{182, 28},{136,118},{256,162}}, // M_Atlas
{{236,174},{ 32,174},{296, 98},{ 62, 98},{110, 24},{224, 24},{158, 4},{ 0, 0},{192, 6},{160,138},{ 0, 0}}, // M_Avatar
{{ 50,258},{384,258},{ 24, 38},{408, 38},{324, 38},{104, 38},{198, 38},{ 0, 0},{230,356},{224,302},{ 0, 0}}, // M_Avatar
{{210,184},{102,182},{274, 4},{ 42, 2},{102, 30},{208, 30},{152, 38},{ 0, 0},{182, 68},{ 0, 0},{ 0, 0}}, // M_Awesome
{{208,162},{130,162},{266, 18},{ 70, 12},{136, 26},{220, 28},{178, 4},{ 0, 0},{182, 28},{136,118},{256,162}}, // M_Battlemaster
{{208,162},{130,162},{266, 18},{ 70, 12},{136, 26},{220, 28},{178, 4},{ 0, 0},{182, 28},{136,118},{ 0, 0}}, // M_BattlemasterIIc
{{220,188},{132,188},{282, 38},{ 82, 76},{130, 80},{254, 80},{168, 34},{ 0, 0},{194, 52},{ 44, 2},{ 0, 0}}, // M_Behemoth
{{ 8,236},{348,236},{ 8, 26},{302, 26},{294,130},{108,130},{202,242},{ 0, 0},{230,174},{202, 28},{ 0, 0}}, // M_Behemoth
{{220,188},{132,188},{282, 38},{ 82, 76},{130, 80},{254, 80},{168, 34},{ 0, 0},{194, 52},{ 44, 2},{ 0, 0}}, // M_Behemothii
{{208,162},{ 76,162},{286, 76},{ 46, 76},{122, 32},{204, 32},{152, 60},{ 0, 0},{188, 66},{256, 2},{ 68, 2}}, // M_Black Hawk
{{ 8,228},{362,228},{ 8, 28},{392, 30},{ 85, 56},{136, 56},{234, 28},{ 0, 0},{242,250},{170,250},{296,250}}, // M_Black Hawk
{{220,188},{132,188},{282, 38},{ 82, 76},{130, 80},{254, 80},{168, 34},{ 0, 0},{194, 52},{ 44, 2},{ 0, 0}}, // M_Black Knight
{{236,146},{ 84,146},{282, 28},{ 76, 16},{130, 14},{232, 12},{144, 4},{ 0, 0},{198, 20},{230, 46},{152, 46}}, // M_Black Lanner
{{220,194},{118,194},{294, 80},{ 64, 82},{100, 2},{204, 40},{168, 12},{ 0, 0},{218, 42},{136,138},{240,138}}, // M_Brigand
@@ -246,7 +246,7 @@ int offset3[65][11][2]={
{{218,196},{104,196},{288, 46},{ 78, 46},{120, 38},{236, 34},{172, 20},{ 0, 0},{182, 86},{252, 6},{ 0, 0}}, // M_Grizzly
{{238,176},{ 78,176},{302, 46},{ 48, 46},{108, 42},{250, 42},{146, 14},{ 0, 0},{184, 36},{122, 4},{ 0, 0}}, // M_Hauptmann
{{212,152},{ 74,152},{308, 78},{ 34, 78},{ 98, 6},{232, 6},{170, 44},{ 0, 0},{188,102},{ 0, 0},{ 0, 0}}, // M_Hellhound
{{238,184},{122,184},{280,103},{ 76,103},{143, 78},{212, 82},{175,165},{ 0, 0},{158, 85},{ 85, 42},{167, 5}}, // M_Hellspawn - Skippy
{{ 10,226},{282,226},{ 10, 98},{334, 96},{236, 96},{140, 66},{104, 98},{ 0, 0},{152, 50},{106,294},{ 12, 18}}, // M_Hellspawn - Skippy
{{218,172},{112,172},{282, 74},{ 50, 74},{ 80, 28},{226, 28},{166, 4},{ 0, 0},{188, 30},{ 0, 0},{ 0, 0}}, // M_Highlander
{{232,150},{104,150},{264, 40},{ 78, 40},{136, 8},{214, 4},{168,134},{ 0, 0},{228, 22},{166, 20},{182,102}}, // M_HollanderII
{{220,182},{106,182},{286, 46},{ 70, 46},{118, 30},{242, 6},{164, 6},{ 0, 0},{190, 24},{130, 6},{ 0, 0}}, // M_Hunchback
@@ -275,7 +275,7 @@ int offset3[65][11][2]={
{{240,172},{ 70,172},{288, 70},{ 2, 70},{106, 28},{234, 28},{156, 38},{ 0, 0},{184,128},{180, 42},{ 12,146}}, // M_Uziel
{{220,170},{118,170},{270, 40},{ 68, 40},{140, 62},{220, 62},{176, 62},{ 0, 0},{158, 4},{ 0, 0},{ 0, 0}}, // M_Victor - GhostHawk
{{236,174},{ 32,174},{296, 98},{ 62, 98},{110, 24},{224, 24},{158, 4},{ 0, 0},{192, 6},{160,138},{ 0, 0}}, // M_Vulture
{{208,162},{ 76,162},{286, 76},{ 46, 76},{122, 32},{204, 32},{152, 60},{ 0, 0},{188, 66},{256, 2},{ 68, 2}}, // M_Warhammer
{{ 14,310},{290,310},{ 14, 16},{306, 16},{290,172},{ 90,160},{148, 46},{ 0, 0},{188,284},{108,352},{206,366}}, // M_Warhammer
{{254,182},{140,182},{258, 32},{110, 32},{164, 56},{238, 56},{172, 8},{ 0, 0},{198, 30},{ 0, 0},{ 0, 0}}, // M_Wolfhound
{{220,182},{114,182},{274,126},{ 82,122},{ 82, 34},{230, 34},{154, 6},{ 0, 0},{190, 60},{ 0, 0},{ 0, 0}} // M_Zeus
};
+452
View File
@@ -0,0 +1,452 @@
# MFD and Radar Damage Mapping Guide
This document explains how the external MFD and Radar damage-paper-doll mappings are authored,
stored, rendered, checked, and added for a new 'Mech. It records the workflow established while
reviewing and installing all usable art under `Finished HUDS from J&J/` on 2026-08-07.
## Scope and terminology
There are three related damage displays. Do not mix their mapping tables:
| Display | Mapping source | Art path |
|---|---|---|
| Normal in-cockpit HUD | `Gameleap/code/mw4/Code/MW4/huddamage.cpp`: `texuv` and `offset` | Content texture `hud\<name>` |
| External MFD | `Gameleap/code/CoreTech/Libraries/GameOS/coord.cpp`: `texuv2` and `offset2` | Loose `Gameleap/mw4/hsh/hud/<name>.bmp` |
| Radar secondary damage display | `coord.cpp`: `texuv3` and `offset3` | Loose `Gameleap/mw4/hsh/radar/hud/<name>.bmp` |
This guide is about the last two displays, both controlled by `coord.cpp`. The nearby `// MFD`
comments refer to the external display. The normal cockpit HUD has different coordinates in
`huddamage.cpp`.
The small images in `hsh/MFD/` are also a different asset set. `render.cpp` tiles those images
into a mech texture atlas. They are not the 512x512 damage-mask images mapped by `coord.cpp`.
## Controlling code
`coord.cpp` defines four positional arrays:
```cpp
float texuv2[65][11][4]; // MFD source rectangles
int offset2[65][11][2]; // MFD exploded destination positions
float texuv3[65][11][4]; // Radar source rectangles
int offset3[65][11][2]; // Radar exploded destination positions
```
The first index is the numeric Mech ID. It must match the positional ID sequence in
`Gameleap/code/mw4/Code/MW4/MechLabHeaders.h`. At present there are 65 active IDs, 0 through
64. Each array therefore has exactly 65 active rows in the same order, from Annihilator through
Zeus. Dasher rows exist but are commented out because Dasher is not active in the roster.
The second index always uses this eleven-zone order:
```text
0 LL left leg
1 RL right leg
2 LA left arm
3 RA right arm
4 RT right torso
5 LT left torso
6 CT center torso
7 CTR center torso rear
8 HD head
9 S1 special 1
10 S2 special 2
```
Use `{0,0,0,0}` and `{0,0}` for a zone that has no art. The comment above `offset2` currently
says `S2 S1`; the array consumers and the other tables use index 9 as S1 and index 10 as S2.
Treat the header comment as stale and preserve the index order above.
## What the values mean
An unexploded rectangle is recorded as:
```text
(x0, y0, x1, y1)
```
`(x0,y0)` is the upper-left pixel and `(x1,y1)` is the lower-right pixel of that component in
the prepared unexploded image. In code this becomes one `texuv2` or `texuv3` entry.
An exploded position is recorded as:
```text
(x2, y2)
```
It is the upper-left destination position of that component in the exploded layout. In code
this becomes one `offset2` or `offset3` entry. The component keeps the width and height implied
by its unexploded rectangle:
```text
width = x1 - x0
height = y1 - y0
exploded bounds = (x2, y2, x2 + width, y2 + height)
```
Coordinates are normally even because both authoring pipelines were designed around even pixel
boundaries. Do not silently round a visually validated odd value: several supplied sets contain
intentional odd coordinates, including Assassin II Radar S1 `y=3`, Behemoth MFD RA `x=281`, and
Black Hawk Radar RT `x=85`. Radar's integer `/2` truncates odd values at runtime, so changing an
odd authored value can move an edge or origin in the rendered 256x256 view. Work from the
upper-left to lower-right of each component, and do not overlap source rectangles.
## MFD authoring pipeline
Start with the finished 1024x1024 full-color 'Mech image.
1. Reduce the image to 320x320.
2. Expand the canvas to 340x340, centered. This adds 10 pixels on every side.
3. Separate adjacent body sections with a 4x4-pixel black line so source rectangles do not
overlap or bleed into one another.
4. Save this as the unexploded working view.
5. For every zone, record its unexploded `(x0,y0,x1,y1)` rectangle, preferring even values.
6. Starting from the unexploded view in the upper-left, place copies of the separated pieces in
their exploded positions.
7. Record each exploded piece's upper-left `(x2,y2)` coordinate, preferring even values.
8. Expand the canvas to 512x512, anchored at the upper-left. Added area is black.
9. Convert the exploded view to indexed color mode and save it as the runtime BMP.
Enter the rectangle values in `texuv2` and the exploded upper-left values in `offset2`.
At runtime, `huddamage.cpp` draws an MFD component as:
```cpp
mfd_device.DrawTexture(
offset2[mech][zone][0] + 100,
offset2[mech][zone][1] + 40,
color,
texuv2[mech][zone][0],
texuv2[mech][zone][1],
texuv2[mech][zone][2],
texuv2[mech][zone][3]);
```
The `+100,+40` values position the complete paper doll in the MFD UI. They are not part of the
authored coordinates and must not be added to `coord.cpp`.
`CMFD_Device::LoadDamageTexture()` receives the logical name `hud\<name>` and loads the loose
bitmap at `hsh\hud\<name>.bmp`.
## Radar authoring pipeline
Start with the same finished 1024x1024 full-color image.
1. Reduce the image to 400x400.
2. Expand the canvas to 410x410, centered. This adds 5 pixels on every side.
3. Expand the canvas to 512x512, anchored at the upper-left. Added area is black.
4. Save this as the unexploded working view.
5. Split the body into non-overlapping components from each component's upper-left to
lower-right.
6. Outline components with a 2x2-pixel white line.
7. Record each unexploded `(x0,y0,x1,y1)` rectangle, preferring even values.
8. Starting from the unexploded view in the upper-left, place copies of the pieces in their
exploded positions.
9. Record each exploded piece's upper-left `(x2,y2)` coordinate, preferring even values.
10. Convert the exploded view to indexed color mode and save it as the runtime BMP.
Enter the rectangle values in `texuv3` and the exploded upper-left values in `offset3`.
Radar coordinates are authored in the full 512x512 coordinate space. The runtime deliberately
divides every rectangle and offset coordinate by two, switches the texture dimensions to
256x256 for the draw, and then adds the Radar UI origin:
```cpp
radar_device.DrawTexture(
(offset3[mech][zone][0] / 2) + 138,
(offset3[mech][zone][1] / 2) + 406,
color,
texuv3[mech][zone][0] / 2,
texuv3[mech][zone][1] / 2,
texuv3[mech][zone][2] / 2,
texuv3[mech][zone][3] / 2);
```
Do not pre-divide values when editing `coord.cpp`. Author and store the full-size values;
preserve visually validated odd coordinates because runtime integer division determines their
final half-resolution placement.
The runtime loads `hsh\radar\hud\<name>.bmp` through
`CRadar_Device::LoadRadarDamageTexture()`.
## Reading an existing mapping
1. Find the Mech ID in `MechLabHeaders.h`.
2. Confirm that the same row number is used in all four `coord.cpp` arrays. Comments are useful
labels but do not control the mapping; array position does.
3. Read the eleven `texuv` rectangles in the fixed zone order above.
4. Read the eleven matching `offset` points in the same order.
5. Ignore spelling differences in end-of-row comments unless they indicate an actual row-order
error. Examples in the current file include `M_HollnaderII` and capitalization differences.
6. Check that every nonzero rectangle has a corresponding nonzero offset, except where a design
intentionally draws a component at `(0,0)`.
7. Check rectangle bounds, non-overlap, and whether any odd coordinate is intentional and
visually aligned. MFD source geometry is normally inside the 340x340 working area. Radar's
nominal art area is 410x410, but validate imported legacy Radar rectangles against the full
512x512 prepared canvas: accepted Black Hawk geometry extends beyond 410 and aligns visually.
## Making or fixing a mapping
1. Preserve the Mech's row position. Never sort one array independently.
2. Prepare separate MFD and Radar unexploded images using the pipelines above.
3. Measure source rectangles in zone order and record them in a text file before touching code.
4. Prepare exploded layouts and record each upper-left destination point.
5. Verify visually with overlays.
6. Replace only that Mech's row in `texuv2`, `offset2`, `texuv3`, and `offset3` as needed.
7. Preserve `coord.cpp` CRLF line endings and avoid reformatting unrelated rows.
8. Inspect the diff. A one-Mech repair should change at most four rows unless art or roster
registration is also being changed.
9. Rebuild the game because `DXRasterizer.cpp` includes `coord.cpp` directly. Rebuild the needed
Release/Profile targets and redeploy the resulting executable.
10. Compare the supplied exploded BMP to the existing canonical runtime BMP by decoded pixels,
not only by file hash. Replace it only when the pixels differ. See "Runtime art comparison"
below.
11. Install a changed runtime BMP under the matching `Gameleap/mw4/hsh` path. Loose `hsh` art
does not require a resource-package rebuild.
12. Test both intact and damaged states on the real external MFD and Radar displays.
When adding a new chassis rather than repairing an existing row, also increase the first
dimension of all four arrays and add one correctly positioned row to every array. This is part
of the larger positional-ID workflow documented in `ADDING-A-MECH.md`.
## Overlay validation method
Visual overlays are the fastest way to distinguish a coordinate problem from an art problem.
Generate every available J&J comparison and the status report with:
```bash
python3 "Finished HUDS from J&J/generate_comparison_maps.py"
```
This requires Pillow. It writes two PNGs per available display and updates
`Finished HUDS from J&J/COMPARISON-SUMMARY.md`. The summary reports exact numeric matches,
different zones, missing display inputs, and malformed source measurements.
Two J&J measurement formats exist:
- Modern: the Unexploded file contains four-value source rectangles and the Exploded file
contains two-value destination origins.
- Legacy: the Unexploded file contains only each source rectangle's upper-left point, while the
Exploded file contains the complete destination bounding box. For legacy data, derive the
source width and height from the exploded box, apply those dimensions at the unexploded point,
and use the exploded box's upper-left as the destination offset.
Some combined `Coords.txt` files contain both sections. Trust the section heading and tuple
shape, not the filename alone. The generator handles both formats and indexes current mappings
by numeric Mech ID rather than end-of-row comment spelling.
All-zero absent-zone tuples are found in both two-value and four-value forms in legacy files.
Normalize an all-zero tuple to the width expected by its section; it still means no art. For
nonzero tuples, a wrong tuple width is an input error and must not be guessed.
For an unexploded comparison:
- Use the correctly transformed unexploded canvas, not the exploded-pieces BMP.
- Draw the current `texuv` rectangles in red.
- Draw proposed rectangles in green.
- Label each rectangle with its zone.
- A good rectangle encloses one component, follows its outer extent, and does not include pixels
from another component.
For an exploded comparison:
- Use the exploded-pieces BMP.
- Mark each `offset` as an upper-left cross.
- Draw a box from that point using the matching unexploded rectangle's width and height.
- Draw current mappings in red and proposed mappings in green.
- A good exploded mapping places the box exactly over the intended exploded piece.
Do not draw `texuv2` rectangles directly on the raw 1024x1024 source. For MFD, first apply
1024 -> 320, center on 340, then place at the upper-left of 512. For Radar, apply
1024 -> 400, center on 410, then place at the upper-left of 512. When a supplied legacy
unexploded image is already 512x512, use it directly rather than reconstructing and cropping it;
the supplied coordinates may intentionally reference pixels outside the nominal 410 area.
## Runtime art comparison
The exploded 512x512 BMP is the runtime asset. The unexploded BMP and full-color source image
are authoring and measurement references only; do not copy those into `hsh` as the runtime art.
Resolve the destination by the canonical runtime texture stem for the numeric Mech ID, not by
blindly copying the supplied filename. Current destinations are:
```text
MFD: Gameleap/mw4/hsh/hud/<stem>.bmp
Radar: Gameleap/mw4/hsh/radar/hud/<stem>.bmp
```
Known naming traps from this review:
- Assassin II supplied files are misspelled `assian2_*`, but the canonical runtime stem is
`assassin2`; do not overwrite the separate historical `assassinii.bmp` by accident.
- The existing Fafnir runtime file is `Fafnir.bmp` with an uppercase `F` on this case-sensitive
checkout. Resolve destination names case-insensitively, then preserve the existing spelling.
- Folder names, comments, and supplied filenames are labels. Numeric Mech ID and the runtime
texture-name table remain authoritative.
BMP byte equality is stricter than visual/runtime equality. A supplied file may be indexed `P`,
grayscale `L`, or `RGB`, and may carry a different palette, header, row padding, or metadata while
decoding to exactly the same pixels. Compare in this order before replacing anything:
1. Confirm both images are 512x512.
2. If file bytes match, the destination is byte-exact.
3. Otherwise decode both images, convert both to a common mode such as RGBA, and compare every
pixel.
4. If decoded pixels match, the destination is already correct. Keep the existing runtime file;
replacing it creates binary churn and may exchange a compact indexed/grayscale BMP for RGB.
5. Copy the supplied exploded BMP only if decoded pixels differ, then repeat the pixel comparison
against the installed destination.
Loose `hsh` art is not packed into a `.mw4` resource for this path, so an actual art replacement
does not require `build-resources.ps1`. Coordinate changes still require rebuilding the game
executable because `DXRasterizer.cpp` includes `coord.cpp` directly.
## Reproducible validation
After editing coordinates, rerun:
```bash
python3 "Finished HUDS from J&J/generate_comparison_maps.py"
```
For the 2026-08-07 data set, the expected terminal result is:
```text
Generated 38 maps for 19 display sets: 19 exact, 0 different
Warnings: 0; missing display sets: 7
```
Also verify all of the following before considering the source complete:
- `COMPARISON-SUMMARY.md` contains 19 `EXACT` rows and no `DIFFERENT` rows.
- Each of `texuv2`, `offset2`, `texuv3`, and `offset3` still has 65 active rows.
- Every active row still has exactly 11 zones.
- The edited `coord.cpp` remains CRLF-only; the repository deliberately uses byte-exact files.
- The diff changes only intended Mech rows. This installation changes 38 rows total: two rows
for each of 19 display sets.
- `git status --short -- Gameleap/mw4/hsh` is empty when every runtime image was already correct.
- A Windows VC6 Release/Profile rebuild and physical MFD/Radar test remain required; the Linux
comparison workflow validates data and geometry but cannot replace that runtime test.
## J&J mapping status as of 2026-08-07
All 19 complete display sets under `Finished HUDS from J&J/` are installed in `coord.cpp` and
report `EXACT` in `COMPARISON-SUMMARY.md`: 12 MFD sets and 7 Radar sets across 13 chassis.
Seven opposite-display sets have no complete supplied mapping/image inputs and were not changed.
Installed sets:
| Mech ID | Chassis | MFD | Radar |
|---:|---|:---:|:---:|
| 0 | Annihilator | installed | installed |
| 4 | Argus | installed | no supplied set |
| 5 | Assassin II | installed | installed |
| 7 | Avatar | installed | installed |
| 11 | Behemoth | installed | installed |
| 13 | Black Hawk | installed | installed |
| 27 | Fafnir | installed | no supplied set |
| 28 | Flea | installed | no supplied set |
| 29 | Gladiator | installed | no supplied set |
| 33 | Hellspawn | no supplied set | installed |
| 37 | Kodiak | installed | no supplied set |
| 39 | Longbow | installed | no supplied set |
| 62 | Warhammer | installed | installed |
The supplied exploded BMP for every installed display set was also compared to its canonical
runtime destination under `Gameleap/mw4/hsh/hud/` or `Gameleap/mw4/hsh/radar/hud/`. All 19 are
pixel-identical at 512x512. Their file hashes differ because the supplied files use different BMP
encodings, palettes, or headers; the runtime copies are already correct, so none were replaced.
Pixel-identical runtime files:
```text
MFD: annihilator, argus, assassin2, avatar, behemoth, blackhawk, Fafnir,
flea, gladiator, kodiak, longbow, warhammer
Radar: annihilator, assassin2, avatar, behemoth, blackhawk, hellspawn, warhammer
```
Three obvious measurement-file transcription errors were normalized without changing the
validated geometry: Behemoth MFD LT `1742` to `174`, Behemoth Radar CT `202.242` to `202,242`,
and Fafnir MFD LL `162.326` to `162,326`. Behemoth Radar's blank S2 was made explicit as `0,0`.
### Assassin II details
Reference art and measurements are under:
```text
Finished HUDS from J&J/AssassinII/
```
The supplied files are:
```text
HUD/assassinII_HUD_unexploded.txt
HUD/assassinII_hud_exploded.txt
Radar/assassinII_radar_unexploded.txt
Radar/assassinII_radar_exploded.txt
```
The supplied Assassin II values were checked against the correctly transformed source art.
Both corrected MFD and Radar mappings align. All four rows were installed in `coord.cpp` on
2026-08-07 and are pending an executable rebuild and hardware test.
Generated comparison images from the review are:
```text
Finished HUDS from J&J/AssassinII/HUD/assian2_mfd_unexploded_coords_comparison.png
Finished HUDS from J&J/AssassinII/HUD/assian2_mfd_exploded_coords_comparison.png
Finished HUDS from J&J/AssassinII/Radar/assian2_radar_unexploded_coords_comparison.png
Finished HUDS from J&J/AssassinII/Radar/assian2_radar_exploded_coords_comparison.png
```
The generator now draws the installed `coord.cpp` mapping in red and the supplied J&J mapping in
green, so the colors overlap exactly. These PNGs are review art only and are not loaded by the
game.
### Implemented Assassin II rows
MFD `texuv2`:
```cpp
{{ 98,142,148,328},{190,142,240,328},{ 56, 48,124,154},{216, 48,282,154},{180, 22,220,120},{120, 22,158,120},{144, 36,194,186},{ 0, 0, 0, 0},{158, 70,180, 86},{148, 10,190, 48},{ 0, 0, 0, 0}}
```
MFD `offset2`:
```cpp
{{ 6,148},{282,148},{ 6, 10},{266, 10},{206, 22},{ 98, 22},{144,180},{ 0, 0},{158,140},{148, 10},{ 0, 0}}
```
Radar `texuv3`:
```cpp
{{114,170,178,406},{232,170,294,406},{ 60, 48,144,184},{264, 48,348,184},{218, 18,268,140},{142, 18,190,140},{174, 36,234,140},{ 0, 0, 0, 0},{192, 78,218, 98},{178, 2,230, 48},{ 0, 0, 0, 0}}
```
Radar `offset3`:
```cpp
{{ 8,220},{336,220},{ 8, 48},{314, 48},{244, 48},{120, 48},{178,206},{ 0, 0},{192, 78},{178, 3},{ 0, 0}}
```
One supplied Radar S1 offset is odd (`y=3`) despite the general even-coordinate rule. Preserve
the measured value until it is deliberately reviewed against the source pixels; do not silently
round it while transcribing the row.
## Common mistakes
- Drawing MFD boxes on the raw 1024 image instead of the transformed 340 working canvas.
- Drawing unexploded rectangles on the exploded-pieces BMP.
- Using `texuv`/`offset` from `huddamage.cpp` when the task is external MFD/Radar mapping.
- Using the small `hsh/MFD` atlas image as the external damage-mask source.
- Pre-dividing Radar coordinates by two before putting them in `coord.cpp`.
- Adding the MFD `+100,+40` or Radar `+138,+406` UI origins to authored offsets.
- Treating `(x2,y2)` as a second rectangle instead of an upper-left destination point.
- Measuring overlapping component rectangles or omitting separator/outline pixels.
- Swapping S1 and S2 because of the stale `offset2` header comment.
- Inserting a row into only one parallel array and shifting every later Mech out of alignment.
- Re-encoding or normalizing the entire legacy source file while changing four rows.
- Treating a different BMP hash as different art without decoding and comparing pixels.
- Copying an unexploded/reference BMP instead of the exploded 512x512 runtime BMP.
- Using the supplied `assian2` typo as the destination instead of canonical `assassin2.bmp`.
- Rounding odd coordinates despite a visually aligned overlay.