From d12dbca95111e942c11ab7a0fffc7c194d09fc10 Mon Sep 17 00:00:00 2001 From: phobos2077 Date: Sat, 6 Jun 2026 23:27:02 +0200 Subject: [PATCH] mapper: EDG angled edges (squareRect) editing --- src/map_edge.cc | 7 +- src/map_edge.h | 3 + src/mapper/map_edge_setup.cc | 580 ++++++++++++++++++++++++++--------- src/mapper/map_edge_setup.h | 4 + src/mapper/mapper.cc | 17 +- src/tile.cc | 2 +- 6 files changed, 465 insertions(+), 148 deletions(-) diff --git a/src/map_edge.cc b/src/map_edge.cc index eeb3ebe4..28664426 100644 --- a/src/map_edge.cc +++ b/src/map_edge.cc @@ -250,7 +250,7 @@ static bool mapEdgeLoadFromStream(File* stream) } // File stores RECT order: [0]=left, [1]=top, [2]=right, [3]=bottom. - EdgeZone zone{}; + EdgeZone zone {}; zone.tileRect = { tileRect[0], tileRect[1], tileRect[2], tileRect[3] }; calcEdgeData(&zone); data.zones.push_back(zone); @@ -398,6 +398,11 @@ bool mapEdgeIsEnabled() return edgeDataLoaded && edgeEnabled; } +void mapEdgeUpgradeToVersion2() +{ + edgeVersion2 = true; +} + bool mapEdgeZoneIsSelected() { return currentEdgeZone != nullptr; diff --git a/src/map_edge.h b/src/map_edge.h index 7a22733f..70f2439a 100644 --- a/src/map_edge.h +++ b/src/map_edge.h @@ -64,6 +64,9 @@ bool mapEdgeIsLoaded(); void mapEdgeSetEnabled(bool enabled); bool mapEdgeIsEnabled(); +// Marks edge data as version 2 so squareRect/clipSides are written on save. +void mapEdgeUpgradeToVersion2(); + // Returns true if a zone was selected on last tileSetCenter call. bool mapEdgeZoneIsSelected(); diff --git a/src/mapper/map_edge_setup.cc b/src/mapper/map_edge_setup.cc index b89cac71..bd0d8853 100644 --- a/src/mapper/map_edge_setup.cc +++ b/src/mapper/map_edge_setup.cc @@ -1,6 +1,7 @@ #include "mapper/map_edge_setup.h" #include +#include #include #include "color.h" @@ -22,8 +23,10 @@ namespace fallout { // Palette indices for the iso overlay (15-bit RGB lookups into _colorTable). constexpr int kActiveRectColor = 30720; // red constexpr int kMoveSideColor = 783; // green +constexpr int kClipAllColor = 29386; // square side clips all objects +constexpr int kClipLowColor = 25120; // square side clips low objects only -// A side of the active edge rect that can be moved. +// A side of the active rect that can be moved. enum class EdgeSide { None, Left, @@ -32,13 +35,30 @@ enum class EdgeSide { Bottom, }; +// Which rect the open dialog edits: per-zone tileRect or the per-elevation squareRect. +enum class EditMode { + Tiles, + Squares, +}; + static bool active = false; // dialog open → draw the overlay static bool showAllRects = false; // persistent overlay toggle (Settings menu) +static EditMode editMode = EditMode::Tiles; static int workingElevation = 0; static int activeRect = -1; static EdgeSide moveSide = EdgeSide::None; static int moveOrigValue = 0; // side value to restore on Cancel move +// Square-grid basis vectors in screen space (one step along column / row axis). +constexpr int kSquareColDx = -48; +constexpr int kSquareColDy = 12; +constexpr int kSquareRowDx = 32; +constexpr int kSquareRowDy = 24; + +// squareTileToScreenXY returns the tile's bounding-box top-left; the cell parallelogram's +// top corner sits this far to the right of it. +constexpr int kSquareOriginDx = -kSquareColDx; + // Snapshot of all edge data taken when the dialog opens, restored on Cancel. static EdgeElevationData backup[ELEVATION_COUNT]; @@ -86,12 +106,13 @@ static Rect fullGridTileRect() return result; } -static void editorOpen(int elevation) +static void editorOpen(int elevation, EditMode mode) { for (int elev = 0; elev < ELEVATION_COUNT; elev++) { backup[elev] = mapEdgeGetElevationData(elev); } + editMode = mode; workingElevation = elevation; active = true; moveSide = EdgeSide::None; @@ -100,7 +121,12 @@ static void editorOpen(int elevation) static void editorClose(bool save) { - if (!save) { + if (save) { + if (editMode == EditMode::Squares) { + mapEdgeUpgradeToVersion2(); + } + mapEdgeRecalc(); + } else { for (int elev = 0; elev < ELEVATION_COUNT; elev++) { mapEdgeGetElevationData(elev) = backup[elev]; } @@ -146,14 +172,27 @@ static void editorPrevRect() } } -// Returns a writable pointer to the active rect's side value, or nullptr. -static int* activeSideValue(EdgeSide side) +// The rect being edited: the active zone's tileRect, or the elevation's squareRect. +static Rect* activeTargetRect() { + if (editMode == EditMode::Squares) { + return &mapEdgeGetElevationData(workingElevation).squareRect; + } std::vector& zones = currentZones(); if (activeRect < 0 || activeRect >= static_cast(zones.size())) { return nullptr; } - Rect& r = zones[activeRect].tileRect; + return &zones[activeRect].tileRect; +} + +// Returns a writable pointer to the active rect's side value, or nullptr. +static int* activeSideValue(EdgeSide side) +{ + Rect* target = activeTargetRect(); + if (target == nullptr) { + return nullptr; + } + Rect& r = *target; switch (side) { case EdgeSide::Left: return &r.left; @@ -168,6 +207,24 @@ static int* activeSideValue(EdgeSide side) } } +// Mutable clip flag for a square side at the working elevation. +static bool* squareClipSideFlag(EdgeSide side) +{ + EdgeZone::ClipSides& clip = mapEdgeGetElevationData(workingElevation).clipSides; + switch (side) { + case EdgeSide::Left: + return &clip.left; + case EdgeSide::Top: + return &clip.top; + case EdgeSide::Right: + return &clip.right; + case EdgeSide::Bottom: + return &clip.bottom; + default: + return nullptr; + } +} + static void editorBeginMove(EdgeSide side) { int* value = activeSideValue(side); @@ -201,15 +258,44 @@ static bool editorUpdateMoveFromScreen(int screenX, int screenY) return false; } - int tile = tileFromScreenXY(screenX, screenY, true); - if (!hexGridTileIsValid(tile) || tile == *value) { + int newValue; + if (editMode == EditMode::Squares) { + int square = squareTileFromScreenXY(screenX, screenY, workingElevation); + if (square < 0) { + return false; + } + bool horizontal = moveSide == EdgeSide::Left || moveSide == EdgeSide::Right; + newValue = horizontal ? square % SQUARE_GRID_WIDTH : square / SQUARE_GRID_WIDTH; + } else { + newValue = tileFromScreenXY(screenX, screenY, true); + if (!hexGridTileIsValid(newValue)) { + return false; + } + } + + if (newValue == *value) { return false; } - *value = tile; + *value = newValue; return true; } +static void editorToggleClip(EdgeSide side) +{ + bool* flag = squareClipSideFlag(side); + if (flag != nullptr) { + *flag = !*flag; + } +} + +static void editorResetSquare() +{ + EdgeElevationData& data = mapEdgeGetElevationData(workingElevation); + data.squareRect = { SQUARE_GRID_WIDTH - 1, 0, 0, SQUARE_GRID_HEIGHT - 1 }; + data.clipSides = { }; +} + // Clips an axis-aligned line to clip and draws it; skips lines fully outside. static void drawClippedHLine(unsigned char* buffer, int pitch, const Rect* clip, int y, int x0, int x1, int color) { @@ -229,6 +315,58 @@ static void drawClippedVLine(unsigned char* buffer, int pitch, const Rect* clip, bufferDrawLine(buffer, pitch, x, top, x, bottom, color); } +// Clips an arbitrary line to clip (Liang-Barsky) and draws it; bufferDrawLine does not clip. +static void drawClippedLine(unsigned char* buffer, int pitch, const Rect* clip, int x0, int y0, int x1, int y1, int color) +{ + float t0 = 0.0f; + float t1 = 1.0f; + int dx = x1 - x0; + int dy = y1 - y0; + const int p[4] = { -dx, dx, -dy, dy }; + const int q[4] = { x0 - clip->left, clip->right - x0, y0 - clip->top, clip->bottom - y0 }; + + for (int i = 0; i < 4; i++) { + if (p[i] == 0) { + if (q[i] < 0) return; // parallel and outside + continue; + } + float t = static_cast(q[i]) / p[i]; + if (p[i] < 0) { + if (t > t1) return; + if (t > t0) t0 = t; + } else { + if (t < t0) return; + if (t < t1) t1 = t; + } + } + + // Clamp against float-truncation landing a pixel outside the clip rect (OOB write). + auto clampX = [&](int x) { return std::min(std::max(x, clip->left), clip->right); }; + auto clampY = [&](int y) { return std::min(std::max(y, clip->top), clip->bottom); }; + + bufferDrawLine(buffer, pitch, + clampX(x0 + static_cast(t0 * dx)), clampY(y0 + static_cast(t0 * dy)), + clampX(x0 + static_cast(t1 * dx)), clampY(y0 + static_cast(t1 * dy)), color); +} + +// Screen positions of the four outline vertices enclosing the visible squares of sr. +// Indices: 0 top, 1 left, 2 bottom, 3 right (each offset to a cell corner by the basis). +static void squareRectScreenCorners(const Rect& sr, int elevation, int outX[4], int outY[4]) +{ + auto corner = [&](int col, int row, int dx, int dy, int index) { + int x; + int y; + squareTileToScreenXY(col + SQUARE_GRID_WIDTH * row, &x, &y, elevation); + outX[index] = x + kSquareOriginDx + dx; + outY[index] = y + dy; + }; + + corner(sr.right, sr.top, 0, 0, 0); + corner(sr.left, sr.top, kSquareColDx, kSquareColDy, 1); + corner(sr.left, sr.bottom, kSquareColDx + kSquareRowDx, kSquareColDy + kSquareRowDy, 2); + corner(sr.right, sr.bottom, kSquareRowDx, kSquareRowDy, 3); +} + // Screen-space rect (normalized left<=right, top<=bottom) of a zone's tile corners. static Rect tileRectToScreenRect(const Rect& tileRect) { @@ -248,9 +386,33 @@ static void drawScreenRectOutline(unsigned char* buffer, int pitch, const Rect* drawClippedVLine(buffer, pitch, clip, s.right, s.top, s.bottom, color); } -// Registered as the tile renderer's overlay hook. While the dialog is open, only the active -// rect is drawn (plus the moving side in green). Otherwise the persistent toggle outlines -// every rect. Never drawn in play mode (edges enabled). +// Display color of a square side: green while moving, else its clip-mode color. +static int squareSideColor(EdgeSide side) +{ + if (side == moveSide) { + return _colorTable[kMoveSideColor]; + } + bool* flag = squareClipSideFlag(side); + return _colorTable[(flag != nullptr && *flag) ? kClipAllColor : kClipLowColor]; +} + +// Outlines the squareRect over the iso view, one diagonal edge per side. +static void drawSquareOverlay(unsigned char* buffer, int pitch, int elevation, const Rect* clip) +{ + int cx[4]; + int cy[4]; + squareRectScreenCorners(mapEdgeGetElevationData(elevation).squareRect, elevation, cx, cy); + + const EdgeSide sides[4] = { EdgeSide::Top, EdgeSide::Left, EdgeSide::Bottom, EdgeSide::Right }; + for (int i = 0; i < 4; i++) { + int j = (i + 1) % 4; + drawClippedLine(buffer, pitch, clip, cx[i], cy[i], cx[j], cy[j], squareSideColor(sides[i])); + } +} + +// Registered as the tile renderer's overlay hook. While a dialog is open, only the active +// rect is drawn (plus the moving side highlighted). Otherwise the persistent toggle outlines +// every edge rect. Never drawn in play mode (edges enabled). static void renderOverlay(unsigned char* buffer, int pitch, int elevation, const Rect* clip) { if (mapEdgeIsEnabled()) { @@ -264,6 +426,9 @@ static void renderOverlay(unsigned char* buffer, int pitch, int elevation, const for (const EdgeZone& zone : mapEdgeGetElevationData(elevation).zones) { drawScreenRectOutline(buffer, pitch, clip, tileRectToScreenRect(zone.tileRect), red); } + if (mapEdgeHasSquareRect(elevation)) { + drawSquareOverlay(buffer, pitch, elevation, clip); + } } return; } @@ -272,6 +437,11 @@ static void renderOverlay(unsigned char* buffer, int pitch, int elevation, const return; } + if (editMode == EditMode::Squares) { + drawSquareOverlay(buffer, pitch, elevation, clip); + return; + } + std::vector& zones = currentZones(); if (activeRect < 0 || activeRect >= static_cast(zones.size())) { return; @@ -301,6 +471,106 @@ static void renderOverlay(unsigned char* buffer, int pitch, int elevation, const } } +// Dialog button event codes (kept clear of real key/mouse codes). +constexpr int kEvSideTop = 0x3205; +constexpr int kEvSideBottom = 0x3206; +constexpr int kEvSideLeft = 0x3207; +constexpr int kEvSideRight = 0x3208; +constexpr int kEvCancel = 0x3209; +constexpr int kEvSave = 0x320A; +constexpr int kEvAddRect = 0x3201; +constexpr int kEvDelRect = 0x3202; +constexpr int kEvPrevRect = 0x3203; +constexpr int kEvNextRect = 0x3204; +constexpr int kEvClipTop = 0x3211; +constexpr int kEvClipBottom = 0x3212; +constexpr int kEvClipLeft = 0x3213; +constexpr int kEvClipRight = 0x3214; +constexpr int kEvDefaultReset = 0x3215; + +// Shared modal loop. ESC, mouse, side-move and Cancel/Save are common; dispatch handles +// dialog-specific button codes and reports whether the change needs a refresh. +static void runEditLoop(int win, int winX, int winY, int winW, int winH, + const std::function& render, const std::function& dispatch) +{ + auto refreshAll = [&]() { + tileWindowRefresh(); + render(); + }; + + refreshAll(); + + bool done = false; + while (!done) { + sharedFpsLimiter.mark(); + int keyCode = inputGetInput(); + + int mx; + int my; + mouseGetPosition(&mx, &my); + bool overDialog = mx >= winX && mx < winX + winW && my >= winY && my < winY + winH; + bool moving = moveSide != EdgeSide::None; + + if (moving && !overDialog && editorUpdateMoveFromScreen(mx, my)) { + tileWindowRefresh(); + } + + if (keyCode == KEY_ESCAPE) { + if (moving) { + editorCancelMove(); + refreshAll(); + } else { + editorClose(false); + done = true; + } + } else if (keyCode == -2) { + int buttons = mouseGetEvent(); + if (moving && (buttons & MOUSE_EVENT_LEFT_BUTTON_DOWN) && !overDialog) { + editorCommitMove(); + refreshAll(); + } else if (moving && (buttons & MOUSE_EVENT_RIGHT_BUTTON_DOWN)) { + editorCancelMove(); + refreshAll(); + } + } else if (keyCode != -1) { + bool refresh = true; + switch (keyCode) { + case kEvSideTop: + editorBeginMove(EdgeSide::Top); + break; + case kEvSideBottom: + editorBeginMove(EdgeSide::Bottom); + break; + case kEvSideLeft: + editorBeginMove(EdgeSide::Left); + break; + case kEvSideRight: + editorBeginMove(EdgeSide::Right); + break; + case kEvCancel: + editorClose(false); + done = true; + refresh = false; + break; + case kEvSave: + editorClose(true); + done = true; + refresh = false; + break; + default: + refresh = dispatch(keyCode); + break; + } + if (refresh) { + refreshAll(); + } + } + + renderPresent(); + sharedFpsLimiter.throttle(); + } +} + void mapEdgeSetupInit() { tileSetMapperOverlayProc(renderOverlay); @@ -312,63 +582,73 @@ void mapEdgeSetupToggleOverlay() tileWindowRefresh(); } +// Shared dialog palette indices. +constexpr int kWinBgColor = 3082; // dark blue +constexpr int kTextColor = 16344; // lighter gray +constexpr int kTitleColor = 32767; // white + +constexpr int kDialogWidth = 230; +constexpr int kDialogHeight = 250; + +// Created window plus the handles both dialogs need for rendering. +struct SetupWindow { + int win; + int x; + int y; + unsigned char* buf; + int pitch; +}; + +// Creates the centered setup window and draws its border, title, map name and elevation. +// Requires a font to be active. Returns win == -1 on failure. +static SetupWindow openSetupWindow(const char* title, int elevation) +{ + int x = (rectGetWidth(&_scr_size) - kDialogWidth) / 2; + int y = (rectGetHeight(&_scr_size) - kDialogHeight) / 2; + + int win = windowCreate(x, y, kDialogWidth, kDialogHeight, _colorTable[kWinBgColor], WINDOW_MOVE_ON_TOP); + if (win == -1) { + return { -1, 0, 0, nullptr, 0 }; + } + + windowDrawBorder(win); + windowDrawText(win, title, 0, (kDialogWidth - fontGetStringWidth(title)) / 2, 8, _colorTable[kTitleColor]); + windowDrawText(win, gMapHeader.name[0] != '\0' ? gMapHeader.name : "", 130, 13, 30, _colorTable[kTextColor]); + + char elev[32]; + snprintf(elev, sizeof(elev), "Elev: %d", elevation); + windowDrawText(win, elev, 0, 157, 30, _colorTable[kTextColor]); + + return { win, x, y, windowGetBuffer(win), windowGetWidth(win) }; +} + void mapEdgeSetupDialog() { - constexpr int kWinWidth = 230; - constexpr int kWinHeight = 250; - - // Button event codes (kept clear of real key/mouse codes). - constexpr int kEvAddRect = 0x3201; - constexpr int kEvDelRect = 0x3202; - constexpr int kEvPrevRect = 0x3203; - constexpr int kEvNextRect = 0x3204; - constexpr int kEvSideTop = 0x3205; - constexpr int kEvSideBottom = 0x3206; - constexpr int kEvSideLeft = 0x3207; - constexpr int kEvSideRight = 0x3208; - constexpr int kEvCancel = 0x3209; - constexpr int kEvSave = 0x320A; - // Reference diagram box; sides are highlighted green while being moved. constexpr int kBoxLeft = 60; constexpr int kBoxTop = 128; constexpr int kBoxRight = 170; constexpr int kBoxBottom = 186; - const int winBgColor = _colorTable[3082]; // dark blue - const int textColor = _colorTable[16344]; // lighter gray - // const int buttonTextColor = _colorTable[20052] | FONT_SHADOW | FONT_MONO; // grey - const int titleColor = _colorTable[32767]; // white + const int winBgColor = _colorTable[kWinBgColor]; + const int textColor = _colorTable[kTextColor]; const int rectColor = _colorTable[kActiveRectColor]; // red const int highlightColor = _colorTable[kMoveSideColor]; // green - const int winX = (rectGetWidth(&_scr_size) - kWinWidth) / 2; - const int winY = (rectGetHeight(&_scr_size) - kWinHeight) / 2; + ScopedFont font(1); - int win = windowCreate(winX, winY, kWinWidth, kWinHeight, winBgColor, WINDOW_MOVE_ON_TOP); - if (win == -1) { + editorOpen(gElevation, EditMode::Tiles); + + SetupWindow w = openSetupWindow("MAP EDGE SETUP", workingElevation); + if (w.win == -1) { return; } - unsigned char* buf = windowGetBuffer(win); - const int pitch = windowGetWidth(win); + const int win = w.win; + unsigned char* buf = w.buf; + const int pitch = w.pitch; const int lineHeight = fontGetLineHeight(); - - editorOpen(gElevation); - - auto centeredX = [&](const char* text) { return (kWinWidth - fontGetStringWidth(text)) / 2; }; - - ScopedFont font(1); - - // Static chrome and labels drawn once; buttons are themed and registered once. - windowDrawBorder(win); - windowDrawText(win, "MAP EDGE SETUP", 0, centeredX("MAP EDGE SETUP"), 8, titleColor); - - windowDrawText(win, gMapHeader.name[0] != '\0' ? gMapHeader.name : "", 130, 12, 26, textColor); - - char tmp[64]; - snprintf(tmp, sizeof(tmp), "Elev: %d", workingElevation); - windowDrawText(win, tmp, 0, 168, 26, textColor); + auto centeredX = [&](const char* text) { return (kDialogWidth - fontGetStringWidth(text)) / 2; }; _win_register_text_button(win, 12, 58, -1, -1, -1, kEvAddRect, "Add Rect", 0); _win_register_text_button(win, 120, 58, -1, -1, -1, kEvDelRect, "Delete Rect", 0); @@ -386,11 +666,11 @@ void mapEdgeSetupDialog() char buffer[64]; snprintf(buffer, sizeof(buffer), "Num Rects: %d", static_cast(currentZones().size())); - bufferFill(buf + 40 * pitch + 10, kWinWidth - 20, lineHeight, pitch, winBgColor); + bufferFill(buf + 40 * pitch + 10, kDialogWidth - 20, lineHeight, pitch, winBgColor); windowDrawText(win, buffer, 0, centeredX(buffer), 40, textColor); snprintf(buffer, sizeof(buffer), "Rect: %d", activeRect >= 0 ? activeRect + 1 : 0); - bufferFill(buf + 90 * pitch + 50, kWinWidth - 100, lineHeight, pitch, winBgColor); + bufferFill(buf + 90 * pitch + 50, kDialogWidth - 100, lineHeight, pitch, winBgColor); windowDrawText(win, buffer, 0, centeredX(buffer), 90, textColor); // Reference diagram: red rect, with the side under edit drawn green. @@ -416,97 +696,111 @@ void mapEdgeSetupDialog() windowRefresh(win); }; - // Repaint both the iso overlay and the dialog after a state change. - auto refreshAll = [&]() { - tileWindowRefresh(); - render(); + runEditLoop(win, w.x, w.y, kDialogWidth, kDialogHeight, render, [](int keyCode) { + switch (keyCode) { + case kEvAddRect: + editorAddRect(); + return true; + case kEvDelRect: + editorDeleteRect(); + return true; + case kEvPrevRect: + editorPrevRect(); + return true; + case kEvNextRect: + editorNextRect(); + return true; + default: + return false; + } + }); + + windowDestroy(win); + tileWindowRefresh(); // repaint the iso view without the overlay +} + +void mapEdgeSquareSetupDialog() +{ + // Fixed diagram parallelogram, skewed like the iso square grid. + // Vertices: 0 top, 1 left, 2 bottom, 3 right. Edge i..i+1 maps to a rect side. + constexpr int kDiagX[4] = { 136, 10, 99, 227 }; + constexpr int kDiagY[4] = { 107, 141, 207, 174 }; + const EdgeSide kDiagSides[4] = { EdgeSide::Top, EdgeSide::Left, EdgeSide::Bottom, EdgeSide::Right }; + + const int winBgColor = _colorTable[kWinBgColor]; + const int textColor = _colorTable[kTextColor]; + + ScopedFont font(1); + + editorOpen(gElevation, EditMode::Squares); + + SetupWindow w = openSetupWindow("MAP ANGLED EDGE SETUP", workingElevation); + if (w.win == -1) { + return; + } + + const int win = w.win; + unsigned char* buf = w.buf; + const int pitch = w.pitch; + const int lineHeight = fontGetLineHeight(); + auto centeredX = [&](const char* text) { return (kDialogWidth - fontGetStringWidth(text)) / 2; }; + + windowDrawText(win, "Clip Objects (clip)", 0, 12, 50, textColor); + windowDrawText(win, "All Objects", 0, 12, 62, _colorTable[kClipAllColor]); + windowDrawText(win, "Low Objects", 0, 12, 74, _colorTable[kClipLowColor]); + + _win_register_text_button(win, 113, 64, -1, -1, -1, kEvDefaultReset, "Default Reset", 0); + + _win_register_text_button(win, 51, 114, -1, -1, -1, kEvSideTop, "mv", 0); + _win_register_text_button(win, 88, 137, -1, -1, -1, kEvClipTop, "clp", 0); + _win_register_text_button(win, 43, 167, -1, -1, -1, kEvSideLeft, "mv", 0); + _win_register_text_button(win, 78, 156, -1, -1, -1, kEvClipLeft, "clp", 0); + _win_register_text_button(win, 166, 131, -1, -1, -1, kEvSideRight, "mv", 0); + _win_register_text_button(win, 125, 143, -1, -1, -1, kEvClipRight, "clp", 0); + _win_register_text_button(win, 138, 184, -1, -1, -1, kEvSideBottom, "mv", 0); + _win_register_text_button(win, 114, 161, -1, -1, -1, kEvClipBottom, "clp", 0); + + _win_register_text_button(win, 30, 224, -1, -1, -1, kEvCancel, "Cancel", 0); + _win_register_text_button(win, 143, 224, -1, -1, -1, kEvSave, "Save", 0); + + auto render = [&]() { + // Skewed diagram: fixed edges colored by clip mode (green while moving). Repainted + // on refresh; same pixels overwrite the previous colors, so no clear is needed. + for (int i = 0; i < 4; i++) { + int j = (i + 1) % 4; + bufferDrawLine(buf, pitch, kDiagX[i], kDiagY[i], kDiagX[j], kDiagY[j], squareSideColor(kDiagSides[i])); + } + + const Rect& sr = mapEdgeGetElevationData(workingElevation).squareRect; + char status[64]; + snprintf(status, sizeof(status), "col %d-%d row %d-%d", sr.right, sr.left, sr.top, sr.bottom); + bufferFill(buf + 91 * pitch + 10, kDialogWidth - 20, lineHeight, pitch, winBgColor); + windowDrawText(win, status, 0, centeredX(status), 91, textColor); + + windowRefresh(win); }; - refreshAll(); // initial draw of dialog + iso overlay - - bool done = false; - while (!done) { - sharedFpsLimiter.mark(); - int keyCode = inputGetInput(); - - int mx, my; - mouseGetPosition(&mx, &my); - bool overDialog = mx >= winX && mx < winX + kWinWidth && my >= winY && my < winY + kWinHeight; - bool moving = moveSide != EdgeSide::None; - - // Live-track the selected side under the cursor while over the iso view. - if (moving && !overDialog) { - if (editorUpdateMoveFromScreen(mx, my)) { - tileWindowRefresh(); - } + runEditLoop(win, w.x, w.y, kDialogWidth, kDialogHeight, render, [](int keyCode) { + switch (keyCode) { + case kEvClipTop: + editorToggleClip(EdgeSide::Top); + return true; + case kEvClipBottom: + editorToggleClip(EdgeSide::Bottom); + return true; + case kEvClipLeft: + editorToggleClip(EdgeSide::Left); + return true; + case kEvClipRight: + editorToggleClip(EdgeSide::Right); + return true; + case kEvDefaultReset: + editorResetSquare(); + return true; + default: + return false; } - - if (keyCode == KEY_ESCAPE) { - if (moving) { - editorCancelMove(); - refreshAll(); - } else { - editorClose(false); - done = true; - } - } else if (keyCode == -2) { - int buttons = mouseGetEvent(); - if (moving && (buttons & MOUSE_EVENT_LEFT_BUTTON_DOWN) && !overDialog) { - editorCommitMove(); - refreshAll(); - } else if (moving && (buttons & MOUSE_EVENT_RIGHT_BUTTON_DOWN)) { - editorCancelMove(); - refreshAll(); - } - } else if (keyCode != -1) { - bool refresh = true; - switch (keyCode) { - case kEvAddRect: - editorAddRect(); - break; - case kEvDelRect: - editorDeleteRect(); - break; - case kEvPrevRect: - editorPrevRect(); - break; - case kEvNextRect: - editorNextRect(); - break; - case kEvSideTop: - editorBeginMove(EdgeSide::Top); - break; - case kEvSideBottom: - editorBeginMove(EdgeSide::Bottom); - break; - case kEvSideLeft: - editorBeginMove(EdgeSide::Left); - break; - case kEvSideRight: - editorBeginMove(EdgeSide::Right); - break; - case kEvCancel: - editorClose(false); - done = true; - refresh = false; - break; - case kEvSave: - editorClose(true); - done = true; - refresh = false; - break; - default: - refresh = false; - break; - } - if (refresh) { - refreshAll(); - } - } - - renderPresent(); - sharedFpsLimiter.throttle(); - } + }); windowDestroy(win); tileWindowRefresh(); // repaint the iso view without the overlay diff --git a/src/mapper/map_edge_setup.h b/src/mapper/map_edge_setup.h index 0ad22672..e76b1a94 100644 --- a/src/mapper/map_edge_setup.h +++ b/src/mapper/map_edge_setup.h @@ -10,6 +10,10 @@ void mapEdgeSetupInit(); // Edits the in-memory edge data; the disk write happens on the next map save. void mapEdgeSetupDialog(); +// Opens the Map Angled Edge Setup dialog to edit the current elevation's squareRect/clipSides. +// Edits the in-memory edge data; the disk write happens on the next map save. +void mapEdgeSquareSetupDialog(); + // Toggles the persistent edge-rects overlay shown over the iso view while editing. void mapEdgeSetupToggleOverlay(); diff --git a/src/mapper/mapper.cc b/src/mapper/mapper.cc index 37248659..d434ebe5 100644 --- a/src/mapper/mapper.cc +++ b/src/mapper/mapper.cc @@ -139,6 +139,7 @@ static char kArtToProtos[] = " Art => New Protos "; static char kSwapPrototypse[] = " Swap Prototypes "; static char kHiResMapEdges[] = " Hi-Res Map Edges Setup "; +static char kSetAngledEdges[] = " Set Hi-Res Angled Edges "; static char kToggleMapEdgesOverlay[] = " Toggle Map Edges Overlay "; static char kTmpMapName[] = "TMP$MAP#.MAP"; @@ -228,6 +229,7 @@ char* menu_3[] = { char* menuNamesSettings[] = { kHiResMapEdges, + kSetAngledEdges, kToggleMapEdgesOverlay, }; @@ -297,7 +299,7 @@ int menu_val_2[8]; int menu_val_3[7]; -int menu_val_4[2]; +int menu_val_4[3]; // 0x6EAA80 unsigned char e_num[4][19 * 26]; @@ -577,6 +579,7 @@ constexpr int kBtnClearMapLevel = 5666; constexpr int kBtnCreateAllMapTexts = 5406; constexpr int kBtnRebuildAllMaps = 5405; constexpr int kBtnHiResMapEdges = 0x3101; +constexpr int kBtnSetAngledEdges = 0x3103; constexpr int kBtnToggleMapEdgesOverlay = 0x3102; // FILE menu pulldown keycodes @@ -738,7 +741,8 @@ void MapperInit() menu_val_3[6] = kBtnLibrarianSwapProtos; menu_val_4[0] = kBtnHiResMapEdges; - menu_val_4[1] = kBtnToggleMapEdgesOverlay; + menu_val_4[1] = kBtnSetAngledEdges; + menu_val_4[2] = kBtnToggleMapEdgesOverlay; } static int loadMapperLbm(int lbmBufWidth, int lbmBufHeight) @@ -782,7 +786,7 @@ static void initMenuBar(const int screenWidth) _win_register_menu_pulldown(menu_bar, 8, "FILE", kBtnMenuHeaderFile, 8, menu_names[0], foregroundColor, backgroundColor); _win_register_menu_pulldown(menu_bar, 40, "TOOLS", kBtnMenuHeaderTools, 21, menu_names[1], foregroundColor, backgroundColor); _win_register_menu_pulldown(menu_bar, 80, "SCRIPTS", kBtnMenuHeaderScripts, 8, menu_names[2], foregroundColor, backgroundColor); - _win_register_menu_pulldown(menu_bar, 130, "SETTINGS", kBtnMenuHeaderSettings, 2, menu_names[4], foregroundColor, backgroundColor); + _win_register_menu_pulldown(menu_bar, 130, "SETTINGS", kBtnMenuHeaderSettings, 3, menu_names[4], foregroundColor, backgroundColor); if (can_modify_protos) { _win_register_menu_pulldown(menu_bar, 180, "LIBRARIAN", kBtnMenuHeaderLibrarian, 7, menu_names[3], foregroundColor, backgroundColor); } @@ -1576,6 +1580,13 @@ void edit_mapper() } mapEdgeSetupDialog(); break; + case kBtnSetAngledEdges: + if (map_entered) { + mapperShowTimedMsg("This map has been Entered. Can't edit edges."); + break; + } + mapEdgeSquareSetupDialog(); + break; case kBtnToggleMapEdgesOverlay: mapEdgeSetupToggleOverlay(); break; diff --git a/src/tile.cc b/src/tile.cc index 1602b93c..4732b5fb 100644 --- a/src/tile.cc +++ b/src/tile.cc @@ -1545,7 +1545,7 @@ void tileRenderFloorsInRect(Rect* rect, int elevation) // Port of sfall HRP ViewMap::square_obj_render void tileRenderEdgeBlackSquares(Rect* rect, int elevation, bool drawOnTop) { - if (!mapEdgeHasSquareRect(elevation)) { + if (!mapEdgeIsEnabled() || !mapEdgeHasSquareRect(elevation)) { return; }