mirror of
https://github.com/fallout2-ce/fallout2-ce.git
synced 2026-07-27 16:47:11 -07:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2de6715dd | ||
|
|
df4b787e86 | ||
|
|
6b442546cc | ||
|
|
2c08cc0b3a | ||
|
|
09a1971cd8 |
+2
-2
@@ -161,8 +161,6 @@ set(FALLOUT_ENGINE_SOURCES
|
||||
"src/map_defs.h"
|
||||
"src/map.cc"
|
||||
"src/map.h"
|
||||
"src/map_edge.cc"
|
||||
"src/map_edge.h"
|
||||
"src/memory_defs.h"
|
||||
"src/memory_manager.cc"
|
||||
"src/memory_manager.h"
|
||||
@@ -207,6 +205,8 @@ set(FALLOUT_ENGINE_SOURCES
|
||||
"src/proto_types.h"
|
||||
"src/proto.cc"
|
||||
"src/proto.h"
|
||||
"src/prototype_options.cc"
|
||||
"src/prototype_options.h"
|
||||
"src/queue.cc"
|
||||
"src/queue.h"
|
||||
"src/random.cc"
|
||||
|
||||
@@ -4,18 +4,6 @@ This document tracks Fallout 2 CE compatibility with sfall. This is for modders
|
||||
|
||||
For now, this covers opcodes/metarules, and hooks. In the future, it will include other ways of modifying the engine (like ini files), and other Sfall-specific behaviour.
|
||||
|
||||
## HRP EDG Scroll-Blocker Support
|
||||
|
||||
CE supports the `.edg` file format from the HRP (High Resolution Patch), which defines per-map scroll boundaries and square-level render clipping.
|
||||
|
||||
**How it works in CE:**
|
||||
|
||||
- On map load, `maps/<mapname>.edg` is read if present. Missing file = silent fallback to the scroll-blocker object system.
|
||||
- The `.edg` file defines per-elevation rectangle boundary zones. Multiple chained zones per elevation are supported.
|
||||
- When loaded, these zones are used for both scroll blocking and visible area clipping (black bars), replacing both vanilla scroll blocking and CE hi-res stencil system.
|
||||
- v2 EDG files also contain a `SquareRect` that defines "Angled edges", or square-grid stencil. This is also supported.
|
||||
|
||||
|
||||
## Settings (ddraw.ini → fallout2.cfg / game.cfg)
|
||||
|
||||
Settings previously read from `ddraw.ini` have been moved into standard CE config files.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 70 KiB |
@@ -110,8 +110,6 @@ iface_bar_mode=0
|
||||
iface_bar_side_art=2
|
||||
iface_bar_sides_ori=0
|
||||
iface_bar_width=800
|
||||
;Whether to load EDG files (HRP format) when loading maps. If loaded, they override default edge clipping and scroll blocking behavior.
|
||||
edg_support=1
|
||||
ignore_map_edges=0
|
||||
;Set to 1 to display numbered dialogue options
|
||||
numbers_in_dialogue=0
|
||||
|
||||
+161
-9
@@ -1,6 +1,9 @@
|
||||
#include "art.h"
|
||||
|
||||
#include <lodepng.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -134,6 +137,7 @@ static int* gArtCritterFidShoudRunData;
|
||||
|
||||
static std::unordered_map<std::string, std::shared_ptr<NamedCacheEntry>> gNamedArtCache;
|
||||
constexpr int kNamedCacheMaxBytes = 32 * 1024 * 1024; // 32MB soft limit
|
||||
constexpr size_t kMaxNamedPngPixels = 16 * 1024 * 1024;
|
||||
static unsigned int gNamedArtCacheMruCounter = 0;
|
||||
static int gNamedArtCacheCurrentBytes = 0;
|
||||
|
||||
@@ -1116,7 +1120,7 @@ static int artReadFrameData(unsigned char* data, File* stream, int count, int* p
|
||||
// 0x419E1C
|
||||
static int artReadHeader(Art* art, File* stream)
|
||||
{
|
||||
if (fileReadInt32(stream, &(art->field_0)) == -1) return -1;
|
||||
if (fileReadInt32(stream, &(art->version)) == -1) return -1;
|
||||
if (fileReadInt16(stream, &(art->framesPerSecond)) == -1) return -1;
|
||||
if (fileReadInt16(stream, &(art->actionFrame)) == -1) return -1;
|
||||
if (fileReadInt16(stream, &(art->frameCount)) == -1) return -1;
|
||||
@@ -1133,13 +1137,137 @@ static int artReadHeader(Art* art, File* stream)
|
||||
return 0;
|
||||
}
|
||||
|
||||
// NOTE: Original function was slightly different, but never used. Basically
|
||||
// it's a memory allocating variant of `artRead` (which reads data into given
|
||||
// buffer). This function is useful to load custom `frm` files since `Art` now
|
||||
// needs more memory then it's on-disk size (due to memory padding).
|
||||
//
|
||||
// 0x419EC0
|
||||
Art* artLoad(const char* path)
|
||||
static bool artPathHasExtension(const char* path, const char* extension)
|
||||
{
|
||||
size_t pathLength = strlen(path);
|
||||
size_t extensionLength = strlen(extension);
|
||||
if (pathLength < extensionLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return compat_stricmp(path + pathLength - extensionLength, extension) == 0;
|
||||
}
|
||||
|
||||
static bool artReadFile(const char* path, std::vector<unsigned char>& data)
|
||||
{
|
||||
int size = 0;
|
||||
if (dbGetFileSize(path, &size) != 0 || size <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
data.resize(size);
|
||||
return dbGetFileContents(path, data.data()) == 0;
|
||||
}
|
||||
|
||||
static bool artUnpackIndexedPngPixels(const std::vector<unsigned char>& indexedData, unsigned width, unsigned height, unsigned bitdepth, unsigned char* output)
|
||||
{
|
||||
size_t pixelCount = static_cast<size_t>(width) * static_cast<size_t>(height);
|
||||
if (bitdepth == 8) {
|
||||
if (indexedData.size() < pixelCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(output, indexedData.data(), pixelCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned char mask = static_cast<unsigned char>((1 << bitdepth) - 1);
|
||||
size_t neededBytes = (pixelCount * bitdepth + 7) / 8;
|
||||
if (indexedData.size() < neededBytes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t index = 0; index < pixelCount; index++) {
|
||||
size_t bitOffset = index * bitdepth;
|
||||
unsigned char byte = indexedData[bitOffset / 8];
|
||||
unsigned shift = 8 - bitdepth - static_cast<unsigned>(bitOffset % 8);
|
||||
output[index] = (byte >> shift) & mask;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static Art* artLoadIndexedPng(const char* path)
|
||||
{
|
||||
std::vector<unsigned char> encoded;
|
||||
if (!artReadFile(path, encoded)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
lodepng::State state;
|
||||
state.decoder.color_convert = 0;
|
||||
|
||||
std::vector<unsigned char> indexedData;
|
||||
unsigned width = 0;
|
||||
unsigned height = 0;
|
||||
unsigned error = lodepng::decode(indexedData, width, height, state, encoded);
|
||||
if (error != 0) {
|
||||
debugPrint("ART: failed to decode indexed PNG %s: %s\n", path, lodepng_error_text(error));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (state.info_png.color.colortype != LCT_PALETTE) {
|
||||
debugPrint("ART: PNG is not palette-indexed: %s\n", path);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (lodepng_has_palette_alpha(&state.info_png.color)) {
|
||||
debugPrint("ART: indexed PNG transparency is unsupported, reserve palette index 0 instead: %s\n", path);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t pixelCount = static_cast<size_t>(width) * static_cast<size_t>(height);
|
||||
|
||||
if (width == 0 || height == 0 || width > SHRT_MAX || height > SHRT_MAX) {
|
||||
debugPrint("ART: invalid indexed PNG dimensions for %s: %ux%u\n", path, width, height);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (pixelCount > kMaxNamedPngPixels || pixelCount > INT_MAX) {
|
||||
debugPrint("ART: indexed PNG is too large: %s\n", path);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Art header = {};
|
||||
header.version = 4;
|
||||
header.framesPerSecond = 10;
|
||||
header.actionFrame = 0;
|
||||
header.frameCount = 1;
|
||||
header.dataSize = sizeof(ArtFrame) + static_cast<int>(pixelCount);
|
||||
|
||||
int currentPadding = paddingForSize(sizeof(Art));
|
||||
for (int rotation = 0; rotation < ROTATION_COUNT; rotation++) {
|
||||
header.dataOffsets[rotation] = 0;
|
||||
header.padding[rotation] = currentPadding;
|
||||
}
|
||||
|
||||
int dataSize = artGetDataSize(&header);
|
||||
unsigned char* data = reinterpret_cast<unsigned char*>(internal_malloc(dataSize));
|
||||
if (data == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
memset(data, 0, dataSize);
|
||||
Art* art = reinterpret_cast<Art*>(data);
|
||||
*art = header;
|
||||
|
||||
ArtFrame* frame = reinterpret_cast<ArtFrame*>(data + sizeof(Art) + art->padding[0]);
|
||||
frame->width = static_cast<short>(width);
|
||||
frame->height = static_cast<short>(height);
|
||||
frame->size = static_cast<int>(pixelCount);
|
||||
frame->x = 0;
|
||||
frame->y = 0;
|
||||
|
||||
if (!artUnpackIndexedPngPixels(indexedData, width, height, state.info_png.color.bitdepth, reinterpret_cast<unsigned char*>(frame) + sizeof(ArtFrame))) {
|
||||
debugPrint("ART: failed to read indexed PNG pixels: %s\n", path);
|
||||
internal_free(data);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return art;
|
||||
}
|
||||
|
||||
static Art* artLoadFrm(const char* path)
|
||||
{
|
||||
File* stream = fileOpen(path, "rb");
|
||||
if (stream == nullptr) {
|
||||
@@ -1167,6 +1295,30 @@ Art* artLoad(const char* path)
|
||||
return reinterpret_cast<Art*>(data);
|
||||
}
|
||||
|
||||
// NOTE: Original function was slightly different, but never used. Basically
|
||||
// it's a memory allocating variant of `artRead` (which reads data into given
|
||||
// buffer). This function is useful to load custom `frm` files since `Art` now
|
||||
// needs more memory then it's on-disk size (due to memory padding).
|
||||
//
|
||||
// 0x419EC0
|
||||
Art* artLoad(const char* path)
|
||||
{
|
||||
if (path == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (artPathHasExtension(path, ".png")) {
|
||||
return artLoadIndexedPng(path);
|
||||
}
|
||||
|
||||
Art* art = artLoadFrm(path);
|
||||
if (art != nullptr) {
|
||||
return art;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static Art* artLoadLocalized(const char* path)
|
||||
{
|
||||
const char* localizedPath;
|
||||
@@ -1239,7 +1391,7 @@ int artWriteFrameData(unsigned char* data, File* stream, int count)
|
||||
// 0x41A138
|
||||
int artWriteHeader(Art* art, File* stream)
|
||||
{
|
||||
if (fileWriteInt32(stream, art->field_0) == -1) return -1;
|
||||
if (fileWriteInt32(stream, art->version) == -1) return -1;
|
||||
if (fileWriteInt16(stream, art->framesPerSecond) == -1) return -1;
|
||||
if (fileWriteInt16(stream, art->actionFrame) == -1) return -1;
|
||||
if (fileWriteInt16(stream, art->frameCount) == -1) return -1;
|
||||
|
||||
@@ -69,7 +69,7 @@ typedef enum Background {
|
||||
} Background;
|
||||
|
||||
typedef struct Art {
|
||||
int field_0;
|
||||
int version;
|
||||
short framesPerSecond;
|
||||
short actionFrame;
|
||||
short frameCount;
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
#include "interface.h"
|
||||
#include "item.h"
|
||||
#include "kb.h"
|
||||
#include "map_edge.h"
|
||||
#include "mouse.h"
|
||||
#include "object.h"
|
||||
#include "proto.h"
|
||||
@@ -932,9 +931,6 @@ void _gmouse_handle_event(int mouseX, int mouseY, int mouseState)
|
||||
if (gameMouseClickOnInterfaceBar()) {
|
||||
return;
|
||||
}
|
||||
if (mapEdgeIsOverClippedArea(mouseX, mouseY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we should block mouse button up events for inventoryOpenUseItemOn inventory window
|
||||
if (gBlockMouseUpEvent && (mouseState & MOUSE_EVENT_LEFT_BUTTON_UP) != 0) {
|
||||
@@ -2451,11 +2447,6 @@ int gameMouseHandleScrolling(int x, int y, int cursor)
|
||||
}
|
||||
|
||||
if (dx == 0 && dy == 0) {
|
||||
if (mapEdgeIsOverClippedArea(x, y)) {
|
||||
gameMouseObjectsHide();
|
||||
gameMouseSetCursor(MOUSE_CURSOR_ARROW);
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -779,9 +779,9 @@ static void inventoryLootApplyLayout(int columns)
|
||||
|
||||
static void inventoryNormalLayoutUpdate()
|
||||
{
|
||||
int columns = inventoryChooseColumns(inventoryFrmImage, INVENTORY_WINDOW_WIDTH + INVENTORY_SLOT_WIDTH, INVENTORY_NORMAL_BACKGROUND_FRM_ID, "invbox2.frm");
|
||||
int columns = inventoryChooseColumns(inventoryFrmImage, INVENTORY_WINDOW_WIDTH + INVENTORY_SLOT_WIDTH, INVENTORY_NORMAL_BACKGROUND_FRM_ID, "INVBOX2.PNG");
|
||||
if (columns == 1) {
|
||||
inventoryBackgroundLoad(inventoryFrmImage, INVENTORY_NORMAL_BACKGROUND_FRM_ID, "invbox2.frm", 1);
|
||||
inventoryBackgroundLoad(inventoryFrmImage, INVENTORY_NORMAL_BACKGROUND_FRM_ID, "INVBOX2.PNG", 1);
|
||||
}
|
||||
|
||||
inventoryNormalApplyLayout(columns);
|
||||
@@ -789,9 +789,9 @@ static void inventoryNormalLayoutUpdate()
|
||||
|
||||
static void inventoryLootLayoutUpdate()
|
||||
{
|
||||
int columns = inventoryChooseColumns(inventoryLootFrmImage, INVENTORY_LOOT_WINDOW_WIDTH_EXPANDED, INVENTORY_LOOT_BACKGROUND_FRM_ID, "loot2.frm");
|
||||
int columns = inventoryChooseColumns(inventoryLootFrmImage, INVENTORY_LOOT_WINDOW_WIDTH_EXPANDED, INVENTORY_LOOT_BACKGROUND_FRM_ID, "LOOT2.png");
|
||||
if (columns == 1) {
|
||||
inventoryBackgroundLoad(inventoryLootFrmImage, INVENTORY_LOOT_BACKGROUND_FRM_ID, "loot2.frm", 1);
|
||||
inventoryBackgroundLoad(inventoryLootFrmImage, INVENTORY_LOOT_BACKGROUND_FRM_ID, "LOOT2.png", 1);
|
||||
}
|
||||
|
||||
inventoryLootApplyLayout(columns);
|
||||
|
||||
@@ -147,6 +147,11 @@ int _kb_getch()
|
||||
return rc;
|
||||
}
|
||||
|
||||
int keyboardGetLastScanCode()
|
||||
{
|
||||
return gLastKeyboardEvent.scanCode;
|
||||
}
|
||||
|
||||
// 0x4CBE00 kb_disable
|
||||
void keyboardDisable()
|
||||
{
|
||||
|
||||
@@ -346,6 +346,7 @@ int keyboardInit();
|
||||
void keyboardFree();
|
||||
void keyboardReset();
|
||||
int _kb_getch();
|
||||
int keyboardGetLastScanCode();
|
||||
void keyboardDisable();
|
||||
void keyboardEnable();
|
||||
int keyboardIsDisabled();
|
||||
|
||||
+14
-2
@@ -30,6 +30,7 @@
|
||||
#include "platform_compat.h"
|
||||
#include "preferences.h"
|
||||
#include "proto.h"
|
||||
#include "prototype_options.h"
|
||||
#include "random.h"
|
||||
#include "scripts.h"
|
||||
#include "settings.h"
|
||||
@@ -75,6 +76,7 @@ static bool _main_show_death_scene = false;
|
||||
static bool _main_death_voiceover_done;
|
||||
|
||||
static int commandLineDevLoadGameSlot = -1;
|
||||
static bool commandLineDevOptionsMenu = false;
|
||||
|
||||
// 0x48099C
|
||||
int falloutMain(int argc, char** argv)
|
||||
@@ -111,7 +113,10 @@ int falloutMain(int argc, char** argv)
|
||||
mouseShowCursor();
|
||||
int devLoadGameSlot = commandLineDevLoadGameSlot;
|
||||
int mainMenuRc;
|
||||
if (devLoadGameSlot != -1) {
|
||||
if (commandLineDevOptionsMenu) {
|
||||
commandLineDevOptionsMenu = false;
|
||||
mainMenuRc = MAIN_MENU_PROTOTYPE_OPTIONS;
|
||||
} else if (devLoadGameSlot != -1) {
|
||||
commandLineDevLoadGameSlot = -1;
|
||||
mainMenuRc = MAIN_MENU_LOAD_GAME;
|
||||
} else {
|
||||
@@ -217,6 +222,10 @@ int falloutMain(int argc, char** argv)
|
||||
mainMenuWindowHide(true);
|
||||
doPreferences(true);
|
||||
break;
|
||||
case MAIN_MENU_PROTOTYPE_OPTIONS:
|
||||
mainMenuWindowHide(true);
|
||||
showPrototypeOptionsMenu(true);
|
||||
break;
|
||||
case MAIN_MENU_CREDITS:
|
||||
mainMenuWindowHide(true);
|
||||
creditsOpen("credits.txt", -1, false);
|
||||
@@ -264,9 +273,12 @@ static void mainParseCommandLineArguments(int argc, char** argv)
|
||||
{
|
||||
const char* devLoadGamePrefix = "--dev-load-game=";
|
||||
size_t devLoadGamePrefixLength = strlen(devLoadGamePrefix);
|
||||
const char* devOptionsMenuFlag = "--dev-options-menu";
|
||||
|
||||
for (int arg = 1; arg < argc; arg += 1) {
|
||||
if (strncmp(argv[arg], devLoadGamePrefix, devLoadGamePrefixLength) == 0) {
|
||||
if (strcmp(argv[arg], devOptionsMenuFlag) == 0) {
|
||||
commandLineDevOptionsMenu = true;
|
||||
} else if (strncmp(argv[arg], devLoadGamePrefix, devLoadGamePrefixLength) == 0) {
|
||||
int slot;
|
||||
if (mainTryParseDevLoadGameSlot(argv[arg] + devLoadGamePrefixLength, &slot)) {
|
||||
commandLineDevLoadGameSlot = slot;
|
||||
|
||||
@@ -355,6 +355,10 @@ int mainMenuWindowHandleEvents()
|
||||
if (keyCode == KEY_CTRL_R) {
|
||||
rc = MAIN_MENU_SELFRUN;
|
||||
continue;
|
||||
} else if (keyCode == KEY_UPPERCASE_Z || keyCode == KEY_LOWERCASE_Z) {
|
||||
main_menu_play_sound("nmselec1");
|
||||
rc = MAIN_MENU_PROTOTYPE_OPTIONS;
|
||||
continue;
|
||||
} else if (keyCode == KEY_PLUS || keyCode == KEY_EQUAL) {
|
||||
brightnessIncrease();
|
||||
} else if (keyCode == KEY_MINUS || keyCode == KEY_UNDERSCORE) {
|
||||
|
||||
@@ -14,6 +14,7 @@ typedef enum MainMenuOption {
|
||||
MAIN_MENU_EXIT,
|
||||
MAIN_MENU_SELFRUN,
|
||||
MAIN_MENU_OPTIONS,
|
||||
MAIN_MENU_PROTOTYPE_OPTIONS,
|
||||
} MainMenuOption;
|
||||
|
||||
int mainMenuWindowInit();
|
||||
|
||||
+8
-34
@@ -26,7 +26,6 @@
|
||||
#include "item.h"
|
||||
#include "light.h"
|
||||
#include "loadsave.h"
|
||||
#include "map_edge.h"
|
||||
#include "memory.h"
|
||||
#include "object.h"
|
||||
#include "palette.h"
|
||||
@@ -226,9 +225,7 @@ int isoInit()
|
||||
colorCycleInit();
|
||||
debugPrint(">cycle_init\t\t");
|
||||
|
||||
if (!settings.ui.ignore_map_edges) {
|
||||
tileScrollBlockingEnable();
|
||||
}
|
||||
tileScrollBlockingEnable();
|
||||
tileScrollLimitingEnable();
|
||||
|
||||
if (interfaceInit() != 0) {
|
||||
@@ -737,7 +734,6 @@ int mapSetEnteringLocation(int elevation, int tile_num, int orientation)
|
||||
// 0x482938
|
||||
void mapNewMap()
|
||||
{
|
||||
mapEdgeFree();
|
||||
mapSetElevation(0);
|
||||
tileSetCenter(20100, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS);
|
||||
memset(&gMapTransition, 0, sizeof(gMapTransition));
|
||||
@@ -935,10 +931,6 @@ static int mapLoad(File* stream)
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (settings.ui.edg_support) {
|
||||
mapEdgeLoad(gMapHeader.name);
|
||||
}
|
||||
|
||||
error = "Error setting tile center";
|
||||
if (tileSetCenter(gEnteringTile, TILE_SET_CENTER_FLAG_IGNORE_SCROLL_RESTRICTIONS) != 0) {
|
||||
goto err;
|
||||
@@ -1069,7 +1061,7 @@ err:
|
||||
// NOTE: Uninline.
|
||||
mapSetEnteringLocation(-1, -1, -1);
|
||||
|
||||
tile_hires_stencil_on_map_load();
|
||||
tile_hires_stencil_init();
|
||||
|
||||
gameMovieFadeOut();
|
||||
|
||||
@@ -1540,7 +1532,7 @@ static void isoWindowRefreshRect(Rect* rect)
|
||||
windowRefreshRect(gIsoWindow, rect);
|
||||
}
|
||||
|
||||
// 0x483EE4 map_scroll_refresh_game
|
||||
// 0x483EE4
|
||||
static void isoWindowRefreshRectGame(Rect* rect)
|
||||
{
|
||||
Rect rectToUpdate;
|
||||
@@ -1548,31 +1540,23 @@ static void isoWindowRefreshRectGame(Rect* rect)
|
||||
return;
|
||||
}
|
||||
|
||||
Rect visArea;
|
||||
bool hasVisArea = mapEdgeComputeVisibleArea(gElevation, &visArea);
|
||||
|
||||
// HRP rect_inside_bound_scroll_clip: always clear srcRect, then clip to mapVisibleArea.
|
||||
// CE: Clear dirty rect to prevent most of the visual artifacts near map
|
||||
// edges.
|
||||
bufferFill(gIsoWindowBuffer + rectToUpdate.top * rectGetWidth(&gIsoWindowRect) + rectToUpdate.left,
|
||||
rectGetWidth(&rectToUpdate),
|
||||
rectGetHeight(&rectToUpdate),
|
||||
rectGetWidth(&gIsoWindowRect),
|
||||
0);
|
||||
|
||||
if (hasVisArea && rectIntersection(&rectToUpdate, &visArea, &rectToUpdate) == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
tileRenderFloorsInRect(&rectToUpdate, gElevation);
|
||||
_obj_render_pre_roof(&rectToUpdate, gElevation);
|
||||
tileRenderRoofsInRect(&rectToUpdate, gElevation);
|
||||
_obj_render_post_roof(&rectToUpdate, gElevation);
|
||||
|
||||
if (!hasVisArea) {
|
||||
tile_hires_stencil_draw(&rectToUpdate, gIsoWindowBuffer, rectGetWidth(&gIsoWindowRect), rectGetHeight(&gIsoWindowRect));
|
||||
}
|
||||
tile_hires_stencil_draw(&rectToUpdate, gIsoWindowBuffer, rectGetWidth(&gIsoWindowRect), rectGetHeight(&gIsoWindowRect));
|
||||
}
|
||||
|
||||
// 0x483F44 map_scroll_refresh_mapper
|
||||
// 0x483F44
|
||||
static void isoWindowRefreshRectMapper(Rect* rect)
|
||||
{
|
||||
Rect rectToUpdate;
|
||||
@@ -1580,29 +1564,19 @@ static void isoWindowRefreshRectMapper(Rect* rect)
|
||||
return;
|
||||
}
|
||||
|
||||
Rect visArea;
|
||||
bool hasVisArea = mapEdgeComputeVisibleArea(gElevation, &visArea);
|
||||
|
||||
// HRP rect_inside_bound_scroll_clip: always clear srcRect, then clip to mapVisibleArea.
|
||||
bufferFill(gIsoWindowBuffer + rectToUpdate.top * rectGetWidth(&gIsoWindowRect) + rectToUpdate.left,
|
||||
rectGetWidth(&rectToUpdate),
|
||||
rectGetHeight(&rectToUpdate),
|
||||
rectGetWidth(&gIsoWindowRect),
|
||||
0);
|
||||
|
||||
if (hasVisArea && rectIntersection(&rectToUpdate, &visArea, &rectToUpdate) == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
tileRenderFloorsInRect(&rectToUpdate, gElevation);
|
||||
_grid_render(&rectToUpdate, gElevation);
|
||||
_obj_render_pre_roof(&rectToUpdate, gElevation);
|
||||
tileRenderRoofsInRect(&rectToUpdate, gElevation);
|
||||
_obj_render_post_roof(&rectToUpdate, gElevation);
|
||||
|
||||
if (!hasVisArea) {
|
||||
tile_hires_stencil_draw(&rectToUpdate, gIsoWindowBuffer, rectGetWidth(&gIsoWindowRect), rectGetHeight(&gIsoWindowRect));
|
||||
}
|
||||
tile_hires_stencil_draw(&rectToUpdate, gIsoWindowBuffer, rectGetWidth(&gIsoWindowRect), rectGetHeight(&gIsoWindowRect));
|
||||
}
|
||||
|
||||
// NOTE: Inlined.
|
||||
|
||||
-462
@@ -1,462 +0,0 @@
|
||||
#include "map_edge.h"
|
||||
|
||||
#include "db.h"
|
||||
#include "debug.h"
|
||||
#include "geometry.h"
|
||||
#include "map.h"
|
||||
#include "map_defs.h"
|
||||
#include "platform_compat.h"
|
||||
#include "svga.h"
|
||||
#include "tile.h"
|
||||
#include "window_manager.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <memory>
|
||||
|
||||
namespace fallout {
|
||||
|
||||
// Tile size in pixels
|
||||
constexpr int kTileWidth = 32;
|
||||
constexpr int kTileHeight = 24;
|
||||
|
||||
static std::unique_ptr<EdgeZone> gEdgeZones[ELEVATION_COUNT];
|
||||
static bool gEdgeDataLoaded = false;
|
||||
static bool gEdgeVersion2 = false;
|
||||
static EdgeZone* gCurrentEdgeZone = nullptr;
|
||||
|
||||
// Boundary alignment mods (sfall mapModWidth/Height), set by clamp/check functions.
|
||||
static int currentTileXAlignment = 0;
|
||||
static int currentTileYAlignment = 0;
|
||||
static int maxTileXAlignment = 0;
|
||||
static int maxTileYAlignment = 0;
|
||||
|
||||
static Rect gMapVisibleArea;
|
||||
|
||||
// Convert tile index to pixel-offset coordinates.
|
||||
// Equivalent to sfall ViewMap::GetTileCoordOffset.
|
||||
void tileToPixelOffset(int tile, int& outX, int& outY)
|
||||
{
|
||||
int x = tile % HEX_GRID_WIDTH;
|
||||
int y = (tile / HEX_GRID_WIDTH) + (x / 2);
|
||||
y &= ~1; // force even row
|
||||
x = (2 * x) + 200 - y;
|
||||
outY = kTileHeight / 2 * y;
|
||||
outX = kTileWidth / 2 * x;
|
||||
}
|
||||
|
||||
// Convert pixel-offset to tile coord (in-place, like sfall GetCoordFromOffset).
|
||||
void pixelToTileCoord(int& inOutX, int& inOutY)
|
||||
{
|
||||
int y = inOutY / kTileHeight;
|
||||
int x = (inOutX / kTileWidth) + y - 100;
|
||||
inOutX = x;
|
||||
inOutY = (2 * y) - (x / 2);
|
||||
}
|
||||
|
||||
// Convert pixel-offset to tile number.
|
||||
static int pixelToTile(int px, int py)
|
||||
{
|
||||
pixelToTileCoord(px, py);
|
||||
return px + py * HEX_GRID_WIDTH;
|
||||
}
|
||||
|
||||
static Size getIsoWindowSize()
|
||||
{
|
||||
Rect winRect;
|
||||
assert(windowGetRect(gIsoWindow, &winRect) != -1);
|
||||
return { rectGetWidth(&winRect), rectGetHeight(&winRect) };
|
||||
}
|
||||
|
||||
// Fill pixel-space fields of a zone from its stored tileRect corners.
|
||||
// Screen-size dependent — must be re-run if resolution changes.
|
||||
static void calcEdgeData(EdgeZone* zone)
|
||||
{
|
||||
const auto [winWidth, winHeight] = getIsoWindowSize();
|
||||
const int winHalfWidth = winWidth / 2;
|
||||
const int winHalfHeight = winHeight / 2;
|
||||
|
||||
// Compute sub-tile alignment sizes (sfall mapWidthModSize / mapHeightModSize).
|
||||
maxTileXAlignment = winHalfWidth % kTileWidth;
|
||||
maxTileYAlignment = winHalfHeight % kTileHeight;
|
||||
|
||||
// Truncated half sizes for border contraction (matching sfall ViewMap::GetWinMapHalfSize).
|
||||
const int winHalfWidthSnapped = winHalfWidth - maxTileXAlignment;
|
||||
const int winHalfHeightSnapped = winHalfHeight - maxTileYAlignment;
|
||||
|
||||
// Convert tileRect corners to pixel offsets.
|
||||
// pixelRect = raw pixel-space rect before any contraction.
|
||||
int px, py;
|
||||
|
||||
tileToPixelOffset(zone->tileRect.left, px, py);
|
||||
zone->pixelRect.left = zone->scrollBorderRect.left = px;
|
||||
|
||||
tileToPixelOffset(zone->tileRect.top, px, py);
|
||||
zone->pixelRect.top = zone->scrollBorderRect.top = py;
|
||||
|
||||
tileToPixelOffset(zone->tileRect.right, px, py);
|
||||
zone->pixelRect.right = zone->scrollBorderRect.right = px;
|
||||
|
||||
tileToPixelOffset(zone->tileRect.bottom, px, py);
|
||||
zone->pixelRect.bottom = zone->scrollBorderRect.bottom = py;
|
||||
|
||||
// Contract scrollBorderRect inward by window half-size (or half its own size snapped to hex grid, if it's smaller than the window).
|
||||
// Narrows the scrollable area, with guards below collapsing to a point if it crosses.
|
||||
{
|
||||
long rectHalfWidth = (zone->scrollBorderRect.left - zone->scrollBorderRect.right) / 2;
|
||||
if (rectHalfWidth < winHalfWidthSnapped) {
|
||||
long remainder = rectHalfWidth % kTileWidth;
|
||||
long rectHalfWidthSnapped = rectHalfWidth - remainder;
|
||||
zone->scrollBorderRect.left -= rectHalfWidthSnapped;
|
||||
zone->scrollBorderRect.right += rectHalfWidthSnapped + (remainder ? kTileWidth : 0);
|
||||
} else {
|
||||
zone->scrollBorderRect.left -= winHalfWidthSnapped;
|
||||
zone->scrollBorderRect.right += winHalfWidthSnapped;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
long rectHalfHeight = (zone->scrollBorderRect.bottom - zone->scrollBorderRect.top) / 2;
|
||||
if (rectHalfHeight < winHalfHeightSnapped) {
|
||||
long remainder = rectHalfHeight % kTileHeight;
|
||||
long rectHalfHeightSnapped = rectHalfHeight - remainder;
|
||||
zone->scrollBorderRect.top += rectHalfHeightSnapped + (remainder ? kTileHeight : 0);
|
||||
zone->scrollBorderRect.bottom -= rectHalfHeightSnapped;
|
||||
} else {
|
||||
zone->scrollBorderRect.top += winHalfHeightSnapped;
|
||||
zone->scrollBorderRect.bottom -= winHalfHeightSnapped;
|
||||
}
|
||||
}
|
||||
|
||||
// Inverted-X guard: collapse to vertical line if left/right cross or nearly touch.
|
||||
if ((zone->scrollBorderRect.left < zone->scrollBorderRect.right) || (zone->scrollBorderRect.left - zone->scrollBorderRect.right) == kTileWidth) {
|
||||
zone->scrollBorderRect.left = zone->scrollBorderRect.right;
|
||||
}
|
||||
|
||||
// Inverted-Y guard
|
||||
if ((zone->scrollBorderRect.bottom < zone->scrollBorderRect.top) || (zone->scrollBorderRect.bottom - zone->scrollBorderRect.top) == kTileHeight) {
|
||||
zone->scrollBorderRect.bottom = zone->scrollBorderRect.top;
|
||||
}
|
||||
|
||||
debugPrint("EDG[%p] tileRect=(%d,%d,%d,%d) win=(%d,%d) half size snap=(%d,%d) pixelRect=(%d,%d,%d,%d) scrollBorderRect=(%d,%d,%d,%d)\n",
|
||||
static_cast<void*>(zone), zone->tileRect.left, zone->tileRect.top,
|
||||
zone->tileRect.right, zone->tileRect.bottom, winWidth, winHeight, winHalfWidthSnapped, winHalfHeightSnapped,
|
||||
zone->pixelRect.left, zone->pixelRect.top,
|
||||
zone->pixelRect.right, zone->pixelRect.bottom,
|
||||
zone->scrollBorderRect.left, zone->scrollBorderRect.top,
|
||||
zone->scrollBorderRect.right, zone->scrollBorderRect.bottom);
|
||||
}
|
||||
|
||||
// Multi-edge zone selection: pick the zone whose scrollBorderRect contains the pixel position.
|
||||
// Matches GetCenterTile logic: advance while target is outside current zone, use last if
|
||||
// none contains it.
|
||||
static EdgeZone* findZoneByPixel(int px, int py, int elevation)
|
||||
{
|
||||
EdgeZone* zone = gEdgeZones[elevation].get();
|
||||
if (zone == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Multi-edge: advance while target is outside current zone.
|
||||
// width/height in original are half-window values (winHalfWidth-1, winHalfHeight+1),
|
||||
// so window size cancels out of the condition — only pixelRect ± small constants remain.
|
||||
constexpr int kZoneMarginY = 2;
|
||||
|
||||
while (zone->next != nullptr) {
|
||||
// Point is inside current zone.
|
||||
if (px < zone->pixelRect.left && px > zone->pixelRect.right
|
||||
&& py > zone->pixelRect.top - kZoneMarginY && py < zone->pixelRect.bottom - kZoneMarginY) {
|
||||
break;
|
||||
}
|
||||
zone = zone->next.get();
|
||||
}
|
||||
|
||||
return zone;
|
||||
}
|
||||
|
||||
// Load EDG file, populate gEdgeZones, and compute pixel-space fields.
|
||||
// EDG files use big-endian byte order (like all Fallout 2 files).
|
||||
static bool mapEdgeLoadFromStream(File* stream)
|
||||
{
|
||||
int magic;
|
||||
if (fileReadInt32(stream, &magic) == -1 || magic != 'EDGE') return false;
|
||||
|
||||
int version;
|
||||
if (fileReadInt32(stream, &version) == -1 || (version != 1 && version != 2)) return false;
|
||||
|
||||
int reserved;
|
||||
if (fileReadInt32(stream, &reserved) == -1 || reserved != 0) return false;
|
||||
|
||||
gEdgeVersion2 = (version == 2);
|
||||
|
||||
int levelIndicator = 0;
|
||||
|
||||
for (int elev = 0; elev < ELEVATION_COUNT; elev++) {
|
||||
int sqLeft = 99, sqTop = 0, sqRight = 0, sqBottom = 99;
|
||||
int sqClipData = 0;
|
||||
|
||||
if (gEdgeVersion2) {
|
||||
int sqRect[4];
|
||||
if (fileReadInt32List(stream, sqRect, 4) == -1
|
||||
|| fileReadInt32(stream, &sqClipData) == -1) {
|
||||
return false;
|
||||
}
|
||||
sqLeft = sqRect[0];
|
||||
sqTop = sqRect[1];
|
||||
sqRight = sqRect[2];
|
||||
sqBottom = sqRect[3];
|
||||
}
|
||||
|
||||
if (levelIndicator != elev) {
|
||||
continue; // no tileRect data for this elevation
|
||||
}
|
||||
|
||||
auto tail = &gEdgeZones[elev];
|
||||
bool isFirstZone = true;
|
||||
|
||||
while (true) {
|
||||
int tileRect[4];
|
||||
if (fileReadInt32List(stream, tileRect, 4) == -1) {
|
||||
return elev == ELEVATION_COUNT - 1;
|
||||
}
|
||||
|
||||
auto zone = std::make_unique<EdgeZone>();
|
||||
// File stores RECT order: [0]=left, [1]=top, [2]=right, [3]=bottom.
|
||||
zone->tileRect.left = tileRect[0];
|
||||
zone->tileRect.top = tileRect[1];
|
||||
zone->tileRect.right = tileRect[2];
|
||||
zone->tileRect.bottom = tileRect[3];
|
||||
|
||||
zone->squareRect.left = sqLeft;
|
||||
zone->squareRect.top = sqTop;
|
||||
zone->squareRect.right = sqRight;
|
||||
zone->squareRect.bottom = sqBottom;
|
||||
int rawClip = isFirstZone ? sqClipData : 0;
|
||||
zone->clipSides.bottom = (rawClip & 1) != 0;
|
||||
zone->clipSides.right = ((rawClip >> 8) & 1) != 0;
|
||||
zone->clipSides.top = ((rawClip >> 16) & 1) != 0;
|
||||
zone->clipSides.left = ((rawClip >> 24) & 1) != 0;
|
||||
zone->next = nullptr;
|
||||
|
||||
calcEdgeData(zone.get());
|
||||
|
||||
*tail = std::move(zone);
|
||||
tail = &(*tail)->next;
|
||||
isFirstZone = false;
|
||||
|
||||
if (fileReadInt32(stream, &levelIndicator) == -1) {
|
||||
return elev == ELEVATION_COUNT - 1;
|
||||
}
|
||||
|
||||
if (levelIndicator != elev) {
|
||||
break; // next elevation begins
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void mapEdgeLoad(const char* mapName)
|
||||
{
|
||||
mapEdgeFree();
|
||||
|
||||
char fname[COMPAT_MAX_FNAME];
|
||||
compat_splitpath(mapName, nullptr, nullptr, fname, nullptr);
|
||||
|
||||
char edgPath[COMPAT_MAX_PATH];
|
||||
snprintf(edgPath, sizeof(edgPath), "MAPS\\%s.EDG", fname);
|
||||
|
||||
File* stream = fileOpen(edgPath, "rb");
|
||||
if (stream == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool ok = mapEdgeLoadFromStream(stream);
|
||||
fileClose(stream);
|
||||
|
||||
if (ok) {
|
||||
gEdgeDataLoaded = true;
|
||||
debugPrint("mapEdgeLoad: loaded %s\n", edgPath);
|
||||
} else {
|
||||
debugPrint("mapEdgeLoad: failed to parse %s\n", edgPath);
|
||||
mapEdgeFree();
|
||||
}
|
||||
}
|
||||
|
||||
void mapEdgeFree()
|
||||
{
|
||||
for (auto& gEdgeZone : gEdgeZones) {
|
||||
gEdgeZone.reset();
|
||||
}
|
||||
gEdgeDataLoaded = false;
|
||||
gCurrentEdgeZone = nullptr;
|
||||
currentTileXAlignment = 0;
|
||||
currentTileYAlignment = 0;
|
||||
maxTileXAlignment = 0;
|
||||
maxTileYAlignment = 0;
|
||||
gMapVisibleArea = {};
|
||||
}
|
||||
|
||||
bool mapEdgeIsLoaded()
|
||||
{
|
||||
return gEdgeDataLoaded;
|
||||
}
|
||||
|
||||
bool mapEdgeZoneIsSelected()
|
||||
{
|
||||
return gCurrentEdgeZone != nullptr;
|
||||
}
|
||||
|
||||
// Shared helper: set currentTileXAlignment/Height if the pixel is on a scrollBorderRect edge.
|
||||
static void updateTileAlignment(const EdgeZone* zone, int px, int py);
|
||||
|
||||
int mapEdgeSelectZoneAndClamp(int tile, int elevation)
|
||||
{
|
||||
int px, py;
|
||||
tileToPixelOffset(tile, px, py);
|
||||
|
||||
// Set current zone for subsequent scroll blocking checks.
|
||||
EdgeZone* zone = gCurrentEdgeZone = findZoneByPixel(px, py, elevation);
|
||||
if (zone == nullptr) {
|
||||
return tile;
|
||||
}
|
||||
|
||||
// Clamp pixel position to scrollBorderRect (matching sfall GetCenterTile).
|
||||
// Note: X is inverted (left > right), so the valid range is [right, left].
|
||||
int cx = (px <= zone->scrollBorderRect.left)
|
||||
? ((px >= zone->scrollBorderRect.right) ? px : zone->scrollBorderRect.right)
|
||||
: zone->scrollBorderRect.left;
|
||||
|
||||
int cy = (py <= zone->scrollBorderRect.bottom)
|
||||
? ((py >= zone->scrollBorderRect.top) ? py : zone->scrollBorderRect.top)
|
||||
: zone->scrollBorderRect.bottom;
|
||||
|
||||
updateTileAlignment(zone, cx, cy);
|
||||
|
||||
return pixelToTile(cx, cy);
|
||||
}
|
||||
|
||||
bool mapEdgeTileInBounds(int tile)
|
||||
{
|
||||
int px, py;
|
||||
tileToPixelOffset(tile, px, py);
|
||||
|
||||
EdgeZone* zone = gCurrentEdgeZone;
|
||||
if (zone == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Note: X is inverted (left > right), so check px in [right, left].
|
||||
return px >= zone->scrollBorderRect.right && px <= zone->scrollBorderRect.left
|
||||
&& py >= zone->scrollBorderRect.top && py <= zone->scrollBorderRect.bottom;
|
||||
}
|
||||
|
||||
// Shared helper: set currentTileXAlignment if the pixel is on a scrollBorderRect edge.
|
||||
static void updateTileAlignment(const EdgeZone* zone, int px, int py)
|
||||
{
|
||||
currentTileXAlignment = 0;
|
||||
currentTileYAlignment = 0;
|
||||
if (px == zone->scrollBorderRect.left) {
|
||||
currentTileXAlignment = -maxTileXAlignment;
|
||||
} else if (px == zone->scrollBorderRect.right) {
|
||||
currentTileXAlignment = maxTileXAlignment;
|
||||
}
|
||||
if (py == zone->scrollBorderRect.top) {
|
||||
currentTileYAlignment = -maxTileYAlignment;
|
||||
} else if (py == zone->scrollBorderRect.bottom) {
|
||||
currentTileYAlignment = maxTileYAlignment;
|
||||
}
|
||||
}
|
||||
|
||||
bool mapEdgeSetBoundaryMods(int tile)
|
||||
{
|
||||
int px, py;
|
||||
tileToPixelOffset(tile, px, py);
|
||||
|
||||
EdgeZone* zone = gCurrentEdgeZone;
|
||||
if (zone == nullptr) {
|
||||
currentTileXAlignment = 0;
|
||||
currentTileYAlignment = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
const int oldModW = currentTileXAlignment;
|
||||
const int oldModH = currentTileYAlignment;
|
||||
updateTileAlignment(zone, px, py);
|
||||
return currentTileXAlignment != oldModW || currentTileYAlignment != oldModH;
|
||||
}
|
||||
|
||||
int mapEdgeGetTileXAlignment() { return currentTileXAlignment; }
|
||||
|
||||
int mapEdgeGetTileYAlignment() { return currentTileYAlignment; }
|
||||
|
||||
bool mapEdgeHasSquareRect(int elevation)
|
||||
{
|
||||
const auto& zone = gEdgeZones[elevation];
|
||||
return gEdgeVersion2 && zone != nullptr && zone->squareRect.left >= 0;
|
||||
}
|
||||
|
||||
void mapEdgeGetSquareRect(int elevation, Rect* outRect)
|
||||
{
|
||||
const auto& zone = gEdgeZones[elevation];
|
||||
*outRect = zone->squareRect;
|
||||
}
|
||||
|
||||
EdgeZone::ClipSides mapEdgeGetClipSides(int elevation)
|
||||
{
|
||||
const auto& zone = gEdgeZones[elevation];
|
||||
return zone ? zone->clipSides : EdgeZone::ClipSides{};
|
||||
}
|
||||
|
||||
void mapEdgeRecalc()
|
||||
{
|
||||
for (auto& gEdgeZone : gEdgeZones) {
|
||||
const auto* zone = &gEdgeZone;
|
||||
while (zone != nullptr) {
|
||||
calcEdgeData(zone->get());
|
||||
zone = &zone->get()->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool mapEdgeComputeVisibleArea(int elevation, Rect* outRect)
|
||||
{
|
||||
if (!gEdgeDataLoaded) return false;
|
||||
|
||||
int px, py;
|
||||
tileToPixelOffset(gCenterTile, px, py);
|
||||
|
||||
px += currentTileXAlignment;
|
||||
py -= currentTileYAlignment;
|
||||
|
||||
EdgeZone* zone = findZoneByPixel(px, py, elevation);
|
||||
if (zone == nullptr) return false;
|
||||
|
||||
// Convert pixel-offset space to screen space via screen origin.
|
||||
// screenOrigin = pixel-space coord of the screen's top-left corner.
|
||||
// X is inverted in pixel-space: screenX = screenOriginX - pixelX.
|
||||
// Matches sfall EdgeClipping::rect_inside_bound_clip mapVisibleArea computation.
|
||||
const auto [winWidth, winHeight] = getIsoWindowSize();
|
||||
const int screenOriginX = px + winWidth / 2 - 1;
|
||||
const int screenOriginY = py - (winHeight / 2 - 1);
|
||||
|
||||
outRect->left = screenOriginX - zone->pixelRect.left;
|
||||
outRect->right = screenOriginX - zone->pixelRect.right;
|
||||
outRect->top = zone->pixelRect.top - screenOriginY;
|
||||
outRect->bottom = zone->pixelRect.bottom - screenOriginY;
|
||||
|
||||
gMapVisibleArea = *outRect;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool mapEdgeIsOverClippedArea(int screenX, int screenY)
|
||||
{
|
||||
if (!gEdgeDataLoaded) return false;
|
||||
|
||||
if (screenX >= gMapVisibleArea.left && screenX <= gMapVisibleArea.right && screenY >= gMapVisibleArea.top && screenY < gMapVisibleArea.bottom) return false;
|
||||
|
||||
return windowGetAtPoint(screenX, screenY) == gIsoWindow;
|
||||
}
|
||||
|
||||
} // namespace fallout
|
||||
-103
@@ -1,103 +0,0 @@
|
||||
#ifndef MAP_EDGE_H
|
||||
#define MAP_EDGE_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "geometry.h"
|
||||
|
||||
namespace fallout {
|
||||
|
||||
struct EdgeZone {
|
||||
struct ClipSides {
|
||||
bool bottom = false;
|
||||
bool right = false;
|
||||
bool top = false;
|
||||
bool left = false;
|
||||
};
|
||||
|
||||
// Raw tile indices from EDG file (tileRect in tile space, 0-39999).
|
||||
Rect tileRect;
|
||||
|
||||
// Pixel-space rect from tileRect corner conversion (before contraction).
|
||||
Rect pixelRect;
|
||||
|
||||
// Pixel-space boundary for center-tile scroll blocking (screen-size dependent).
|
||||
// Derived from pixelRect by contracting inward by window half-size and snapping to hex grid.
|
||||
// X axis is inverted: left > right (smaller tile index → larger pixel X).
|
||||
// Y axis is normal: bottom > top.
|
||||
Rect scrollBorderRect;
|
||||
|
||||
// Square-grid clip rect for floor/roof rendering (v2 EDG only).
|
||||
// Valid when left >= 0.
|
||||
Rect squareRect;
|
||||
|
||||
// Per-side clip flags unpacked from EDG v2. True means the black square overlay
|
||||
// for that side is drawn on top of (after) non-flat objects.
|
||||
ClipSides clipSides;
|
||||
|
||||
std::unique_ptr<EdgeZone> next;
|
||||
};
|
||||
|
||||
// Load EDG file for a map. mapName is the raw map filename e.g. "ARROYO.MAP".
|
||||
// Silently does nothing if no .edg file is found.
|
||||
void mapEdgeLoad(const char* mapName);
|
||||
|
||||
// Free all loaded EDG data. Safe to call when nothing is loaded.
|
||||
void mapEdgeFree();
|
||||
|
||||
// Returns true if EDG data was successfully loaded for the current map.
|
||||
bool mapEdgeIsLoaded();
|
||||
|
||||
// Returns true if a zone was selected on last tileSetCenter call.
|
||||
bool mapEdgeZoneIsSelected();
|
||||
|
||||
// Clamps tile to the EDG boundary for the given elevation.
|
||||
// Returns the clamped tile (may equal tile if already in bounds).
|
||||
int mapEdgeSelectZoneAndClamp(int tile, int elevation);
|
||||
|
||||
// Returns true if tile is within the EDG boundary for the given elevation.
|
||||
// Used by the stencil flood-fill as a traversal gate.
|
||||
bool mapEdgeTileInBounds(int tile);
|
||||
|
||||
// Returns true if squareRect data (v2 EDG) is available for this elevation.
|
||||
bool mapEdgeHasSquareRect(int elevation);
|
||||
|
||||
// Convert tile index to pixel-offset coordinates.
|
||||
// Equivalent to sfall ViewMap::GetTileCoordOffset.
|
||||
void tileToPixelOffset(int tile, int& outX, int& outY);
|
||||
|
||||
// Convert pixel-offset to tile coord (in-place, like sfall GetCoordFromOffset).
|
||||
void pixelToTileCoord(int& inOutX, int& inOutY);
|
||||
|
||||
// Fills the squareRect for the given elevation.
|
||||
// Only valid when mapEdgeHasSquareRect(elevation) returns true.
|
||||
void mapEdgeGetSquareRect(int elevation, Rect* outRect);
|
||||
|
||||
// Returns the clipSides for the given elevation (default-constructed if no EDG data).
|
||||
EdgeZone::ClipSides mapEdgeGetClipSides(int elevation);
|
||||
|
||||
// Recalculate all pixel-space fields using current screen dimensions.
|
||||
// Call when resolution changes while a map is loaded.
|
||||
void mapEdgeRecalc();
|
||||
|
||||
// Pixel-offset adjustments for sub-tile boundary alignment (sfall mapModWidth/Height).
|
||||
// Used when camera is exactly at one of the scroll border edges.
|
||||
int mapEdgeGetTileXAlignment();
|
||||
int mapEdgeGetTileYAlignment();
|
||||
|
||||
// For the blocking (flags == 0) scroll path: sets boundary mod values if the given
|
||||
// tile's pixel position is exactly on a scrollBorderRect edge.
|
||||
// Returns true if the tile is on a boundary edge (mods changed) — caller should set
|
||||
// TILE_SET_CENTER_REFRESH_WINDOW for full redraw, matching sfall CheckBorder result==1.
|
||||
bool mapEdgeSetBoundaryMods(int tile);
|
||||
|
||||
// Computes the screen-space visible area rectangle.
|
||||
// Equivalent to sfall EdgeClipping's mapVisibleArea. Returns false if no EDG loaded.
|
||||
bool mapEdgeComputeVisibleArea(int elevation, Rect* outRect);
|
||||
|
||||
// Returns true if the screen coordinate is over the map window but outside the EDG-visible area.
|
||||
bool mapEdgeIsOverClippedArea(int screenX, int screenY);
|
||||
|
||||
} // namespace fallout
|
||||
|
||||
#endif /* MAP_EDGE_H */
|
||||
@@ -590,12 +590,12 @@ static void mp_run_placement_loop(PlaceFn place)
|
||||
}
|
||||
|
||||
switch (keyCode) {
|
||||
case KEY_HOME: // Home
|
||||
case 327: // Home
|
||||
if (gDude != nullptr) {
|
||||
if (gElevation != gDude->elevation) {
|
||||
mapSetElevation(gDude->elevation);
|
||||
}
|
||||
tileSetCenter(gDude->tile, TILE_SET_CENTER_REFRESH_WINDOW);
|
||||
tileSetCenter(gDude->tile, 1);
|
||||
}
|
||||
break;
|
||||
case KEY_ARROW_UP:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user