mirror of
https://github.com/brues-code/ClassicAPI.git
synced 2026-11-14 15:10:39 +00:00
Taxi areaID resolution now tries the node's LOCATION part first
("Orgrimmar" in "Orgrimmar, Durotar") -- accepted only when it's a
mapped area whose WorldMapArea rect contains the node -- then the zone
suffix, then the geometric resolver. Capitals thus pin on their own city
map (Orgrimmar 1637, Undercity 1497) instead of the surrounding region,
while normal nodes (location is an unmapped subzone) still resolve to the
outdoor zone. Map::Area::PercentInZone is now containment-checked so a
candidate zone is taken only if it actually holds the point.
Add C_Map.GetMapAreaIDs() -> { [mapName] = areaID } from WorldMapArea:
the missing bridge (vanilla has no GetCurrentMapAreaID) for resolving a
browsed city/instance map -- which GetMapZones() doesn't enumerate -- to
its zone id. Keys on the WorldMapArea name (matches GetMapInfo()'s dir
for nearly all maps; a few misspelled Blizzard folders like "Ogrimmar"
differ, documented).
Verified in-game: Orgrimmar/Undercity flight masters pin on their city
maps; GetMapAreaIDs()["Orgrimmar"] = 1637.
252 lines
11 KiB
C++
252 lines
11 KiB
C++
// This file is part of ClassicAPI.
|
|
//
|
|
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
|
|
// of the GNU General Public License as published by the Free Software Foundation, either
|
|
// version 3 of the License, or (at your option) any later version.
|
|
//
|
|
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
// PURPOSE. See the GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License along with
|
|
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
#include "map/Area.h"
|
|
|
|
#include "Offsets.h"
|
|
#include "dbc/Lookup.h"
|
|
|
|
namespace Map::Area {
|
|
|
|
int RowForAreaID(uint32_t areaID) {
|
|
const int count =
|
|
*reinterpret_cast<const int *>(Offsets::VAR_WORLDMAP_AREA_COUNT);
|
|
for (int id = 1; id <= count; ++id) {
|
|
const uint8_t *rec = DBC::Record(Offsets::VAR_WORLDMAP_AREA_RECORDS,
|
|
Offsets::VAR_WORLDMAP_AREA_COUNT,
|
|
static_cast<uint32_t>(id));
|
|
if (rec != nullptr &&
|
|
*reinterpret_cast<const uint32_t *>(rec + Offsets::OFF_WMA_AREA_ID) ==
|
|
areaID)
|
|
return id;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int CurrentViewRow() {
|
|
const int continent =
|
|
*reinterpret_cast<const int *>(Offsets::VAR_WORLDMAP_CONTINENT_INDEX);
|
|
if (continent < 0)
|
|
return *reinterpret_cast<const int *>(
|
|
Offsets::VAR_WORLDMAP_DEFAULT_AREA_ROW);
|
|
const uint8_t *blob = *reinterpret_cast<const uint8_t *const *>(
|
|
Offsets::VAR_WORLDMAP_CONTINENT_DATA);
|
|
if (blob == nullptr)
|
|
return -1;
|
|
const uint8_t *entry = blob + continent * Offsets::WORLDMAP_CONTINENT_STRIDE;
|
|
const int zone =
|
|
*reinterpret_cast<const int *>(Offsets::VAR_WORLDMAP_ZONE_INDEX);
|
|
if (zone < 0)
|
|
return *reinterpret_cast<const int *>(entry + 0x04);
|
|
const int *zoneRows = *reinterpret_cast<const int *const *>(entry + 0x10);
|
|
return (zoneRows != nullptr) ? zoneRows[zone] : -1;
|
|
}
|
|
|
|
namespace {
|
|
double FloatField(const uint8_t *rec, int off) {
|
|
return *reinterpret_cast<const float *>(rec + off);
|
|
}
|
|
|
|
// Vanilla zone world-map canvas size (WorldMapDetailFrame); WorldMapOverlay
|
|
// hit rects live in this pixel space.
|
|
constexpr double kZoneCanvasW = 1002.0;
|
|
constexpr double kZoneCanvasH = 668.0;
|
|
|
|
// How deep inside its containing WorldMapOverlay hit rect the map-percent point
|
|
// (0..100) sits on WMA row `wmaRow` — the max, over that zone's overlays whose
|
|
// hit rect encloses the point, of the normalized margin to the nearest overlay
|
|
// edge (0 at the edge .. 0.5 at center). Returns -1 when the point is on none
|
|
// of the zone's overlays.
|
|
//
|
|
// This is the runtime analog of an ADT area-grid lookup. WorldMapArea rects are
|
|
// loose overlapping boxes; the overlay hit rects trace the real landmass, so
|
|
// "which zone is the point drawn on" disambiguates them (Un'Goro's crater vs
|
|
// Thousand Needles' empty overlap). But hit rects are themselves rectangles
|
|
// that overlap at borders, so the DEPTH matters: a border node deep inside its
|
|
// own zone's overlay beats one merely clipping a neighbor's overlay edge
|
|
// (Nijel's Point is deep in Desolace's "Nijel's Point" overlay but only nicks
|
|
// the edge of Stonetalon's Charred Vale box).
|
|
double OverlayMargin(int wmaRow, double mapXpct, double mapYpct) {
|
|
const double cx = mapXpct / 100.0 * kZoneCanvasW;
|
|
const double cy = mapYpct / 100.0 * kZoneCanvasH;
|
|
const int count =
|
|
*reinterpret_cast<const int *>(Offsets::VAR_WORLDMAP_OVERLAY_COUNT);
|
|
double best = -1.0;
|
|
for (int id = 1; id <= count; ++id) {
|
|
const uint8_t *rec = DBC::Record(Offsets::VAR_WORLDMAP_OVERLAY_RECORDS,
|
|
Offsets::VAR_WORLDMAP_OVERLAY_COUNT,
|
|
static_cast<uint32_t>(id));
|
|
if (rec == nullptr ||
|
|
*reinterpret_cast<const int *>(rec + Offsets::OFF_WMO_WORLDMAP_AREA) !=
|
|
wmaRow)
|
|
continue;
|
|
const int hl = *reinterpret_cast<const int *>(rec + Offsets::OFF_WMO_HITRECT_LEFT);
|
|
const int hr = *reinterpret_cast<const int *>(rec + Offsets::OFF_WMO_HITRECT_RIGHT);
|
|
const int ht = *reinterpret_cast<const int *>(rec + Offsets::OFF_WMO_HITRECT_TOP);
|
|
const int hb = *reinterpret_cast<const int *>(rec + Offsets::OFF_WMO_HITRECT_BOTTOM);
|
|
const double sw = hr - hl, sh = hb - ht;
|
|
if (sw <= 0.0 || sh <= 0.0)
|
|
continue; // no / degenerate hit rect
|
|
if (cx < hl || cx > hr || cy < ht || cy > hb)
|
|
continue; // point outside this overlay
|
|
const double mX = (cx - hl < hr - cx ? cx - hl : hr - cx) / sw;
|
|
const double mY = (cy - ht < hb - cy ? cy - ht : hb - cy) / sh;
|
|
const double m = mX < mY ? mX : mY;
|
|
if (m > best)
|
|
best = m;
|
|
}
|
|
return best;
|
|
}
|
|
} // namespace
|
|
|
|
bool ZonePercent(int mapID, float x, float y, int *outAreaID, double *outMapX,
|
|
double *outMapY) {
|
|
const int count =
|
|
*reinterpret_cast<const int *>(Offsets::VAR_WORLDMAP_AREA_COUNT);
|
|
// Two candidate tracks over the zones whose rect contains the point:
|
|
// land* — the point is on the zone's drawn landmass (an overlay hit rect
|
|
// encloses it): authoritative, wins whenever any zone qualifies.
|
|
// Scored by how DEEP inside the overlay the point sits, so a
|
|
// border node deep in its own zone's overlay beats one nicking a
|
|
// neighbor's overlay edge.
|
|
// box* — bounding-box only: the fallback when the point is on no zone's
|
|
// overlay (map gaps / overlay-less custom zones), scored by the
|
|
// most-interior zone-rect margin.
|
|
bool haveLand = false, haveBox = false;
|
|
double landScore = -1.0, boxMargin = 0.0;
|
|
int landArea = 0, boxArea = 0;
|
|
double landX = 0.0, landY = 0.0, boxX = 0.0, boxY = 0.0;
|
|
for (int id = 1; id <= count; ++id) {
|
|
const uint8_t *rec = DBC::Record(Offsets::VAR_WORLDMAP_AREA_RECORDS,
|
|
Offsets::VAR_WORLDMAP_AREA_COUNT,
|
|
static_cast<uint32_t>(id));
|
|
if (rec == nullptr)
|
|
continue;
|
|
if (*reinterpret_cast<const int *>(rec + Offsets::OFF_WMA_MAP_ID) != mapID)
|
|
continue;
|
|
const int areaID =
|
|
*reinterpret_cast<const int *>(rec + Offsets::OFF_WMA_AREA_ID);
|
|
if (areaID == 0)
|
|
continue; // continent-spanning row, not a zone
|
|
|
|
const double left = FloatField(rec, Offsets::OFF_WMA_LOC_LEFT);
|
|
const double right = FloatField(rec, Offsets::OFF_WMA_LOC_RIGHT);
|
|
const double top = FloatField(rec, Offsets::OFF_WMA_LOC_TOP);
|
|
const double bottom = FloatField(rec, Offsets::OFF_WMA_LOC_BOTTOM);
|
|
const double spanX = top - bottom; // > 0
|
|
const double spanY = left - right; // > 0
|
|
if (spanX <= 0.0 || spanY <= 0.0)
|
|
continue; // degenerate / unmapped zone
|
|
|
|
if (x < bottom || x > top || y < right || y > left)
|
|
continue; // point outside this zone's rect
|
|
|
|
// mapX = horizontal (world Y), mapY = vertical (world X).
|
|
const double mapX = (left - y) / spanY * 100.0;
|
|
const double mapY = (top - x) / spanX * 100.0;
|
|
const double marginX = (x - bottom < top - x ? x - bottom : top - x) / spanX;
|
|
const double marginY = (y - right < left - y ? y - right : left - y) / spanY;
|
|
const double margin = marginX < marginY ? marginX : marginY;
|
|
|
|
if (!haveBox || margin > boxMargin) {
|
|
haveBox = true;
|
|
boxMargin = margin;
|
|
boxArea = areaID;
|
|
boxX = mapX;
|
|
boxY = mapY;
|
|
}
|
|
const double overlayMargin = OverlayMargin(id, mapX, mapY);
|
|
if (overlayMargin >= 0.0 && (!haveLand || overlayMargin > landScore)) {
|
|
haveLand = true;
|
|
landScore = overlayMargin;
|
|
landArea = areaID;
|
|
landX = mapX;
|
|
landY = mapY;
|
|
}
|
|
}
|
|
|
|
if (haveLand) {
|
|
*outAreaID = landArea;
|
|
*outMapX = landX;
|
|
*outMapY = landY;
|
|
return true;
|
|
}
|
|
if (haveBox) {
|
|
*outAreaID = boxArea;
|
|
*outMapX = boxX;
|
|
*outMapY = boxY;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool PercentInZone(int areaID, float x, float y, double *outMapX, double *outMapY) {
|
|
const int count =
|
|
*reinterpret_cast<const int *>(Offsets::VAR_WORLDMAP_AREA_COUNT);
|
|
for (int id = 1; id <= count; ++id) {
|
|
const uint8_t *rec = DBC::Record(Offsets::VAR_WORLDMAP_AREA_RECORDS,
|
|
Offsets::VAR_WORLDMAP_AREA_COUNT,
|
|
static_cast<uint32_t>(id));
|
|
if (rec == nullptr)
|
|
continue;
|
|
if (*reinterpret_cast<const int *>(rec + Offsets::OFF_WMA_AREA_ID) != areaID)
|
|
continue;
|
|
const double left = FloatField(rec, Offsets::OFF_WMA_LOC_LEFT);
|
|
const double right = FloatField(rec, Offsets::OFF_WMA_LOC_RIGHT);
|
|
const double top = FloatField(rec, Offsets::OFF_WMA_LOC_TOP);
|
|
const double bottom = FloatField(rec, Offsets::OFF_WMA_LOC_BOTTOM);
|
|
const double spanX = top - bottom;
|
|
const double spanY = left - right;
|
|
if (spanX <= 0.0 || spanY <= 0.0)
|
|
continue; // degenerate row — keep scanning for a usable one
|
|
if (x < bottom || x > top || y < right || y > left)
|
|
return false; // point outside this zone's rect — not in this zone
|
|
*outMapX = (left - y) / spanY * 100.0; // horizontal (world Y)
|
|
*outMapY = (top - x) / spanX * 100.0; // vertical (world X)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ContinentPercent(int mapID, float x, float y, double *outPx, double *outPy) {
|
|
const int count =
|
|
*reinterpret_cast<const int *>(Offsets::VAR_WORLDMAP_AREA_COUNT);
|
|
for (int id = 1; id <= count; ++id) {
|
|
const uint8_t *rec = DBC::Record(Offsets::VAR_WORLDMAP_AREA_RECORDS,
|
|
Offsets::VAR_WORLDMAP_AREA_COUNT,
|
|
static_cast<uint32_t>(id));
|
|
if (rec == nullptr)
|
|
continue;
|
|
if (*reinterpret_cast<const int *>(rec + Offsets::OFF_WMA_MAP_ID) != mapID)
|
|
continue;
|
|
if (*reinterpret_cast<const int *>(rec + Offsets::OFF_WMA_AREA_ID) != 0)
|
|
continue; // want the continent-spanning row
|
|
|
|
const double left = FloatField(rec, Offsets::OFF_WMA_LOC_LEFT);
|
|
const double right = FloatField(rec, Offsets::OFF_WMA_LOC_RIGHT);
|
|
const double top = FloatField(rec, Offsets::OFF_WMA_LOC_TOP);
|
|
const double bottom = FloatField(rec, Offsets::OFF_WMA_LOC_BOTTOM);
|
|
const double spanX = top - bottom;
|
|
const double spanY = left - right;
|
|
if (spanX <= 0.0 || spanY <= 0.0)
|
|
continue; // stray zero-rect continent row (e.g. "World") — skip
|
|
|
|
*outPx = (left - y) / spanY; // horizontal (world Y)
|
|
*outPy = (top - x) / spanX; // vertical (world X)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace Map::Area
|