Compare commits

...
Author SHA1 Message Date
phobos2077 27805a6c90 mapper: fix cursor being disabled on startup into empty map, replace sscanf with strtol 2026-06-16 14:44:22 +02:00
phobos2077 74769044a6 mapper: code review fixes around proto/spray 2026-06-15 23:19:56 +02:00
phobos2077 a3f66539fb mapper: spray tools & swap prototypes 2026-06-15 22:39:09 +02:00
phobos2077 ff75e13430 mapper: proto_build_all_texts impl 2026-06-15 19:50:52 +02:00
phobos2077 a4a929f620 mapper: reworked writing paths logic
- Drop "target_path_base" and other references to BIS "fallout/dev/" folder in favor of new dev_temp_path setting
- Clear separation between "mapper temp assets" (.cfg, .txt, .tgt), "shipped assets" (.map, .edg, .pro) and "runtime temp assets" (.sav)
- dev_temp_path is mounted as highest priority xbase, so all saved data goes there by default
- as a consequence, play-mode assets (maps with .sav extension and party member .pro files) are explicitly saved to master_patches_path instead of implicitly by using xfileOpen in write mode
2026-06-14 13:29:22 +02:00
phobos2077 f5ba58f27a mapper: proto edit: fix bugs, light refactor and use correct colors 2026-06-13 00:29:09 +02:00
phobos2077 9bd179167c mapper: move proto TXT handling to separate file 2026-06-12 14:50:33 +02:00
phobos2077 9e88b16d71 mapper: fix incorrect strings 2026-06-12 00:37:21 +02:00
phobos2077 15a4f4275b mapper: critter proto editing + wire up all editors 2026-06-12 00:05:34 +02:00
phobos2077 466f12b22e mapper: proto TXT support 2026-06-11 23:45:48 +02:00
phobos2077 26622f3596 mapper: name/description editing 2026-06-11 23:41:07 +02:00
phobos2077 629d1f2b7b mapper: item proto editing 2026-06-11 21:37:20 +02:00
phobos2077 1453a3e294 mapper: scenery proto editing 2026-06-11 21:13:06 +02:00
phobos2077 b5c0fbdf86 mapper: simple proto editing 2026-06-11 21:02:42 +02:00
phobos2077 948558c64c mapper: misc proto editing functions 2026-06-07 11:52:53 +02:00
phobos2077 6412d14a28 mapper: proto_edit_init, proto_remove 2026-06-07 11:15:44 +02:00
32 changed files with 5153 additions and 365 deletions
+4
View File
@@ -614,6 +614,8 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
target_sources(mapper-ce PUBLIC
${FALLOUT_ENGINE_SOURCES}
${FALLOUT_PLATFORM_SOURCES}
"src/proto_txt.cc"
"src/proto_txt.h"
"src/mapper/map_edge_setup.cc"
"src/mapper/map_edge_setup.h"
"src/mapper/map_func.cc"
@@ -626,6 +628,8 @@ if((NOT ANDROID) AND (NOT IOS) AND (NOT CMAKE_SYSTEM_NAME MATCHES "Emscripten"))
"src/mapper/mp_proto.h"
"src/mapper/mp_scrpt.cc"
"src/mapper/mp_scrpt.h"
"src/mapper/mp_spray.cc"
"src/mapper/mp_spray.h"
"src/mapper/mp_targt.cc"
"src/mapper/mp_targt.h"
"src/mapper/mp_utils.cc"
+6
View File
@@ -367,6 +367,12 @@ void artToggleObjectTypeHidden(int objectType)
}
}
// art_total
int art_total(int objectType)
{
return objectType >= 0 && objectType < OBJ_TYPE_COUNT ? gArtListDescriptions[objectType].fileNamesLength : 0;
}
// 0x418F7C
int artGetFidgetCount(int headFid)
{
+1
View File
@@ -123,6 +123,7 @@ void artExit();
char* artGetObjectTypeName(int objectType);
int artIsObjectTypeHidden(int objectType);
void artToggleObjectTypeHidden(int objectType);
int art_total(int objectType);
int artGetFidgetCount(int headFid);
void artRender(int fid, unsigned char* dest, int width, int height, int pitch);
int art_list_str(int fid, char* name);
+24
View File
@@ -144,6 +144,30 @@ File* fileOpen(const char* filename, const char* mode)
return xfileOpen(filename, mode);
}
File* fileOpenDirect(const char* filename, const char* mode)
{
return xfileOpenDirect(filename, mode);
}
const char* buildDataPath(const char* root, const char* relative)
{
static char path[COMPAT_MAX_PATH];
size_t rootLen = strlen(root);
bool rootHasSeparator = rootLen > 0 && (root[rootLen - 1] == '\\' || root[rootLen - 1] == '/');
snprintf(path, sizeof(path), "%s%s%s", root, rootHasSeparator ? "" : "\\", relative);
char dir[COMPAT_MAX_PATH];
snprintf(dir, sizeof(dir), "%s", path);
char* separator = strrchr(dir, '\\');
if (separator != nullptr) {
*separator = '\0';
compat_mkdir_recursive(dir);
}
return path;
}
// 0x4C5ED0 db_fprintf
int filePrintFormatted(File* stream, const char* format, ...)
{
+5
View File
@@ -18,6 +18,7 @@ int dbGetFileSize(const char* filePath, int* sizePtr);
int dbGetFileContents(const char* filePath, void* ptr);
int fileClose(File* stream);
File* fileOpen(const char* filename, const char* mode);
File* fileOpenDirect(const char* filename, const char* mode);
int filePrintFormatted(File* stream, const char* format, ...);
int fileReadChar(File* stream);
char* fileReadString(char* str, size_t size, File* stream);
@@ -111,6 +112,10 @@ void fileNameListFree(char*** fileNames, int unused);
int fileGetSize(File* stream);
void fileSetReadProgressHandler(FileReadProgressHandler* handler, int size);
// Joins [root] and [relative] into a save path and creates its directory.
// Returns a pointer to a static buffer.
const char* buildDataPath(const char* root, const char* relative);
} // namespace fallout
#endif /* DB_H */
+33 -55
View File
@@ -287,7 +287,8 @@ void isoExit()
// 0x481FB4
void mapInit()
{
if (settings.system.executableIsMapper()) {
bool isMapper = settings.system.executableIsMapper();
if (isMapper) {
_map_scroll_refresh = isoWindowRefreshRectMapper;
}
@@ -304,7 +305,9 @@ void mapInit()
mapNewMap();
tickersAdd(gameMouseRefresh);
_gmouse_disable(0);
if (!isMapper) {
_gmouse_disable(0);
}
windowShow(gIsoWindow);
messageListRepositorySetStandardMessageList(STANDARD_MESSAGE_LIST_MAP, &gMapMessageList);
@@ -723,38 +726,6 @@ const char* mapBuildPath(const char* name)
return name;
}
const char* mapBuildDataSavePath(const char* relativePath)
{
static char path[COMPAT_MAX_PATH];
// Save root: the validated mapper dev_path when set, otherwise the master patches path.
const std::string& devPath = settings.mapper.dev_path;
const char* root = !devPath.empty() ? devPath.c_str() : settings.system.master_patches_path.c_str();
// Join root + relativePath, tolerating a trailing separator on the root.
size_t rootLen = strlen(root);
bool rootHasSeparator = rootLen > 0 && (root[rootLen - 1] == '\\' || root[rootLen - 1] == '/');
snprintf(path, sizeof(path), "%s%s%s", root, rootHasSeparator ? "" : "\\", relativePath);
// Ensure the directory portion exists.
char dir[COMPAT_MAX_PATH];
snprintf(dir, sizeof(dir), "%s", path);
char* separator = strrchr(dir, '\\');
if (separator != nullptr) {
*separator = '\0';
compat_mkdir_recursive(dir);
}
return path;
}
const char* mapBuildSavePath(const char* name)
{
char relativePath[COMPAT_MAX_PATH];
snprintf(relativePath, sizeof(relativePath), "MAPS\\%s", name);
return mapBuildDataSavePath(relativePath);
}
// 0x482924
int mapSetEnteringLocation(int elevation, int tile_num, int orientation)
{
@@ -1375,34 +1346,41 @@ static void _map_fix_critter_combat_data()
}
}
// map_save
// 0x483850
int _map_save()
// Saves the current map (and its .EDG sidecar) under the given save root,
// writing directly to that folder without consulting the xbase database.
int mapSaveToRoot(const char* root)
{
int rc = -1;
if (gMapHeader.name[0] != '\0') {
const char* mapFilePath = mapBuildSavePath(gMapHeader.name);
File* stream = fileOpen(mapFilePath, "wb");
if (stream != nullptr) {
rc = _map_save_file(stream);
fileClose(stream);
} else {
debugPrint("Unable to open %s to write!", gMapHeader.name);
}
if (rc == 0) {
debugPrint("%s saved.", gMapHeader.name);
// Write the edge (.EDG) sidecar alongside the map.
mapEdgeSave(gMapHeader.name);
}
} else {
if (gMapHeader.name[0] == '\0') {
debugPrint("\nError: map_save: map header corrupt!");
return -1;
}
char relativePath[COMPAT_MAX_PATH];
snprintf(relativePath, sizeof(relativePath), "MAPS\\%s", gMapHeader.name);
int rc = -1;
File* stream = fileOpenDirect(buildDataPath(root, relativePath), "wb");
if (stream != nullptr) {
rc = _map_save_file(stream);
fileClose(stream);
} else {
debugPrint("Unable to open %s to write!", gMapHeader.name);
}
if (rc == 0) {
debugPrint("%s saved.", gMapHeader.name);
mapEdgeSave(root, gMapHeader.name);
}
return rc;
}
// 0x483850 map_save
int _map_save()
{
return mapSaveToRoot(settings.system.master_patches_path.c_str());
}
// 0x483980
static int _map_save_file(File* stream)
{
+1 -6
View File
@@ -106,12 +106,7 @@ void mapNewMap();
int mapLoadByName(char* fileName);
int mapLoadById(int map_index);
const char* mapBuildPath(const char* name);
// Resolves a VFS-relative data path (e.g. "MAPS\\ARROYO.MAP") to a writable path, creating its directory.
// Save root is the validated mapper dev_path when set, otherwise the master patches path.
const char* mapBuildDataSavePath(const char* relativePath);
// Convenience wrapper around mapBuildDataSavePath for files under MAPS\, mirroring
// mapBuildPath semantics: name is the bare filename, e.g. "ARROYO.MAP".
const char* mapBuildSavePath(const char* name);
int mapSaveToRoot(const char* root);
int mapLoadSaved(char* fileName);
int mapGetLoadedAreaId();
int mapSetTransition(MapTransition* transition);
+5 -3
View File
@@ -337,7 +337,7 @@ static bool writeEdgStream(File* stream)
return true;
}
void mapEdgeSave(const char* mapName)
void mapEdgeSave(const char* root, const char* mapName)
{
int totalZones = 0;
for (const EdgeElevationData& data : edgeData) {
@@ -349,9 +349,11 @@ void mapEdgeSave(const char* mapName)
char edgName[COMPAT_MAX_PATH];
buildEdgeFileName(mapName, edgName, sizeof(edgName));
const char* edgPath = mapBuildSavePath(edgName);
char relativePath[COMPAT_MAX_PATH];
snprintf(relativePath, sizeof(relativePath), "MAPS\\%s", edgName);
const char* edgPath = buildDataPath(root, relativePath);
File* stream = fileOpen(edgPath, "wb");
File* stream = fileOpenDirect(edgPath, "wb");
if (stream == nullptr) {
debugPrint("mapEdgeSave: unable to open %s for writing\n", edgPath);
return;
+3 -3
View File
@@ -46,9 +46,9 @@ struct EdgeElevationData {
// Silently does nothing if no .edg file is found.
void mapEdgeLoad(const char* mapName);
// Writes the current in-memory edge data to the map's EDG file (mapper save path).
// Does nothing when there are no edge zones. Used by the mapper map-save flow.
void mapEdgeSave(const char* mapName);
// Writes the current in-memory edge data to the map's EDG file under the given
// save root. Does nothing when there are no edge zones.
void mapEdgeSave(const char* root, const char* mapName);
// Mutable access to a single elevation's edge data. Used by the mapper edge editor,
// which edits this data in place; the disk write happens later via mapEdgeSave.
+22 -65
View File
@@ -18,6 +18,7 @@
#include "kb.h"
#include "map.h"
#include "mapper/mapper.h"
#include "mapper/mp_spray.h"
#include "mapper/mp_utils.h"
#include "memory.h"
#include "mouse.h"
@@ -284,7 +285,7 @@ void map_save_dialog()
strcat(newName, ".map");
strncpy(gMapHeader.name, newName, sizeof(gMapHeader.name) - 1);
gMapHeader.name[sizeof(gMapHeader.name) - 1] = '\0';
_map_save();
mapSaveToRoot(mapperEditRoot());
}
// map_save_as
@@ -292,7 +293,7 @@ int map_save_as(const char* name)
{
strncpy(gMapHeader.name, name, sizeof(gMapHeader.name) - 1);
gMapHeader.name[sizeof(gMapHeader.name) - 1] = '\0';
return _map_save();
return mapSaveToRoot(mapperEditRoot());
}
// map_get_name
@@ -316,20 +317,6 @@ void map_clear_elevation()
}
}
static void showNotImplementedSprayToolMsg()
{
mapperShowTimedMsg("Spray-tile painting not implemented in CE.");
}
// copy_spray_tile
// TODO: porting this requires the spray-tool subsystem (pick_spray_tool / load_spray_tool /
// draw_spray_to_win / get_max_spray_tool / `spinfo` global / `map_brush_size` global / a sizable
// SprayTool data struct + .spr file I/O). Pending user direction before pulling that in.
void copy_spray_tile()
{
showNotImplementedSprayToolMsg();
}
// pick_hex
int pickHex()
{
@@ -451,41 +438,18 @@ void placeTile(int pid, int fid)
return;
}
int newArt = fid & 0xFFF;
int* squarePtr = &_square[gElevation]->field_0[squareTile];
int oldValue = *squarePtr;
if (tileRoofIsVisible()) {
int oldRoofFid = buildFid(OBJ_TYPE_TILE, (oldValue >> 16) & 0xFFF, 0, 0, 0);
if (oldRoofFid == fid) {
return;
}
int oldRoofWord = oldValue >> 16;
int roofRotation = (oldRoofWord & 0xF000) >> 12;
int newRoofWord = roofRotation | newArt;
*squarePtr = (newRoofWord << 16) | (oldValue & 0xFFFF);
int sx, sy;
squareTileToRoofScreenXY(squareTile, &sx, &sy, gElevation);
Rect rect = { sx, sy, sx + 80, sy + 36 };
tileWindowRefreshRect(&rect, gElevation);
} else {
int oldFloorFid = buildFid(OBJ_TYPE_TILE, oldValue & 0xFFF, 0, 0, 0);
if (oldFloorFid == fid) {
return;
}
int oldFloorWord = oldValue & 0xFFFF;
int floorRotation = (oldFloorWord & 0xF000) >> 12;
int newFloorWord = floorRotation | newArt;
*squarePtr = (oldValue & 0xFFFF0000) | newFloorWord;
int sx, sy;
squareTileToScreenXY(squareTile, &sx, &sy, gElevation);
Rect rect = { sx, sy, sx + 80, sy + 36 };
tileWindowRefreshRect(&rect, gElevation);
if (!place_tile_at(fid, squareTile)) {
return;
}
int sx, sy;
if (tileRoofIsVisible()) {
squareTileToRoofScreenXY(squareTile, &sx, &sy, gElevation);
} else {
squareTileToScreenXY(squareTile, &sx, &sy, gElevation);
}
Rect rect = { sx, sy, sx + 80, sy + 36 };
tileWindowRefreshRect(&rect, gElevation);
}
// mpMouseCheckScrolling
@@ -660,8 +624,8 @@ static void copy_object_to_tile_pobj(int srcFid, int dstTile, Object* srcObj, bo
// Look for an existing duplicate (same tile + same fid) before placing.
for (Object* obj = objectFindFirstAtElevation(gElevation);
obj != nullptr;
obj = objectFindNextAtElevation()) {
obj != nullptr;
obj = objectFindNextAtElevation()) {
if (obj->tile == dstTile
&& obj->fid == srcFid
&& obj != gGameMouseBouncingCursor
@@ -755,8 +719,8 @@ void copyObject(int filterType)
if (tile == -1) continue;
for (Object* obj = objectFindFirstAtElevation(elevation);
obj != nullptr;
obj = objectFindNextAtElevation()) {
obj != nullptr;
obj = objectFindNextAtElevation()) {
if (obj->tile != tile
|| obj == gDude
|| obj == gGameMouseBouncingCursor
@@ -928,8 +892,8 @@ void eraseObject()
// Find topmost (last-rendered) object at gElevation under the mouse.
Object* hit = nullptr;
for (Object* obj = objectFindFirstAtElevation(gElevation);
obj != nullptr;
obj = objectFindNextAtElevation()) {
obj != nullptr;
obj = objectFindNextAtElevation()) {
if (obj == gDude) continue;
if (PID_TYPE(obj->pid) == OBJ_TYPE_INTERFACE) continue;
if ((obj->flags & OBJECT_HIDDEN) != 0) continue;
@@ -982,13 +946,6 @@ void mapper_flush_cache()
artCacheFlush();
}
// create_spray_tool
// Original implementation just returns -1 (the spray-tool authoring path was disabled in the
// shipped mapper). We keep the same disabled behavior.
void create_spray_tool()
{
}
// File-scope constants used by mapper_shift_map_once and its helpers (file-scope so the
// inner lambdas don't need to capture them — MSVC won't accept implicit capture of locals
// with `[]` even if they're constexpr).
@@ -1016,8 +973,8 @@ static void mapper_shift_map_once(int dx, int dy)
};
std::vector<Snap> snap;
for (Object* obj = objectFindFirstAtElevation(gElevation);
obj != nullptr;
obj = objectFindNextAtElevation()) {
obj != nullptr;
obj = objectFindNextAtElevation()) {
if (obj->tile < 0 || obj->tile >= kShiftMapTiles) continue;
if (obj == gDude) continue;
if (obj == gGameMouseBouncingCursor) continue;
-2
View File
@@ -19,8 +19,6 @@ void map_load_dialog();
void map_save_dialog();
int map_save_as(const char* name);
void map_get_name(char* buf);
void create_spray_tool();
void copy_spray_tile();
// mode = 1 - enable, 0 - disable, -1 - toggle
void map_toggle_block_obj_viewing(int mode);
void map_clear_elevation();
+90 -46
View File
@@ -13,13 +13,11 @@
#include "automap.h"
#include "character_editor.h"
#include "color.h"
#include "config.h"
#include "critter.h"
#include "db.h"
#include "debug.h"
#include "draw.h"
#include "game.h"
#include "game_config.h"
#include "game_dialog.h"
#include "game_mouse.h"
#include "game_movie.h"
@@ -38,6 +36,7 @@
#include "mapper/mp_instance.h"
#include "mapper/mp_proto.h"
#include "mapper/mp_scrpt.h"
#include "mapper/mp_spray.h"
#include "mapper/mp_targt.h"
#include "mapper/mp_text.h"
#include "memory.h"
@@ -266,6 +265,9 @@ static ToolbarInfo toolbar_info[6];
// Currently highlighted art-slot index in the toolbar; -1 = none.
static int tool_active = -1;
// When set, the next toolbar proto click swaps that proto with the active one.
static int mapper_copy_proto = 0;
// Color (palette index) used to draw the selection box around the active slot.
static int toolbar_selection_color = 21;
@@ -565,7 +567,6 @@ constexpr int kBtnMenuHeaderLibrarian = KEY_ALT_J;
constexpr int kBtnMenuHeaderSettings = KEY_ALT_X;
constexpr int kBtnPlayMode = KEY_F8;
constexpr int kBtnRebuildProtoList = KEY_F10;
constexpr int kBtnF8AsGame = KEY_CTRL_F12;
constexpr int kBtnMoveMapElev = 4186;
@@ -639,21 +640,20 @@ constexpr int kBtnToggleInterruptWalk = KEY_ALT_R;
constexpr int kBtnRotation0 = KEY_CTRL_ARROW_UP;
constexpr int kBtnRotation3 = KEY_CTRL_ARROW_DOWN;
constexpr int kBtnDestroyAllScripts = KEY_UPPERCASE_A;
constexpr int kBtnRebuildSprayTools = 0x143;
constexpr int kBtnDestroyProtoList = 0x185;
constexpr int kBtnHighlightByProto = 0x123;
constexpr int kBtnAnimDebugStep = 0x12E;
// LIBRARIAN menu pulldown keycodes
constexpr int kBtnLibrarianRebuildAll = 5545;
constexpr int kBtnLibrarianRebuildBinary = 5546;
constexpr int kBtnLibrarianArtToProtos = 5547;
constexpr int kBtnLibrarianSwapProtos = 5548;
// LIBRARIAN menu pulldown keycodes (positionally matched to menu_3 / menu_val_3)
constexpr int kBtnRebuildSprayTools = 0x143; // 323 — "Rebuild Weapons" (rebuilds spray tools)
constexpr int kBtnRebuildProtoList = KEY_F10; // 324 — "Rebuild Proto List"
constexpr int kBtnDestroyProtoList = 0x185; // 389 — "Rebuild ALL"
constexpr int kBtnBuildAllTypeBinary = 6123; // 0x17EB — "Rebuild Binary"
constexpr int kBtnLibrarianArtToProtos = KEY_QUESTION; // 63 — "Art => New Protos"
constexpr int kBtnLibrarianSwapProtos = KEY_BRACE_RIGHT; // 125 — "Swap Prototypes"
// Direct-key bindings restored from the original mapper switch.
constexpr int kBtnAutomap = KEY_TAB; // 9 — Automap (uses existing automapShow)
constexpr int kBtnBookmarkChoose = 7777; // UI — Bookmark choose dialog (existing bookmarkChoose)
constexpr int kBtnBuildAllTypeBinary = 6123; // UI — Build all type binary (proto_build_all_type_binary)
constexpr int kArtMaxDirect = 0x4B0;
@@ -734,8 +734,8 @@ void MapperInit()
menu_val_3[0] = kBtnRebuildSprayTools;
menu_val_3[1] = kBtnRebuildProtoList;
menu_val_3[2] = kBtnLibrarianRebuildAll;
menu_val_3[3] = kBtnLibrarianRebuildBinary;
menu_val_3[2] = kBtnDestroyProtoList;
menu_val_3[3] = kBtnBuildAllTypeBinary;
menu_val_3[4] = KEY_ESCAPE;
menu_val_3[5] = kBtnLibrarianArtToProtos;
menu_val_3[6] = kBtnLibrarianSwapProtos;
@@ -776,6 +776,21 @@ static void mapperValidateDevPath()
devPath.clear();
}
// Mounts the mapper temp folder (dev_temp_path, default "dev" relative to cwd) as
// the highest-priority xbase. Mapper-only artifacts are read from here, and any
// incidental relative write lands here instead of cluttering the dev_path mod
// folder. Reverts to "dev" if explicitly cleared, and is created if missing.
static void mapperMountDevTempPath()
{
std::string& devTemp = settings.mapper.dev_temp_path;
if (devTemp.empty()) {
devTemp = "dev";
}
compat_mkdir_recursive(devTemp.c_str());
xbaseOpen(devTemp.c_str());
}
static void initMenuBar(const int screenWidth)
{
int foregroundColor = _colorTable[20052];
@@ -788,6 +803,8 @@ static void initMenuBar(const int screenWidth)
_win_register_menu_pulldown(menu_bar, 80, "SCRIPTS", kBtnMenuHeaderScripts, 8, menu_names[2], foregroundColor, backgroundColor);
_win_register_menu_pulldown(menu_bar, 130, "SETTINGS", kBtnMenuHeaderSettings, 3, menu_names[4], foregroundColor, backgroundColor);
if (can_modify_protos) {
// Count is 7 (vs the original's 6): the original hid the trailing "Swap
// Prototypes" entry, but CE wires it up (arms swap mode), so it is shown.
_win_register_menu_pulldown(menu_bar, 180, "LIBRARIAN", kBtnMenuHeaderLibrarian, 7, menu_names[3], foregroundColor, backgroundColor);
}
}
@@ -1011,6 +1028,7 @@ int mapper_edit_init(int argc, char** argv)
target_init();
mouseShowCursor();
mapperValidateDevPath();
mapperMountDevTempPath();
mapEdgeSetupInit();
mapEdgeSetMapperMode(true);
@@ -1457,6 +1475,29 @@ void edit_mapper()
if (map_entered) continue;
// Swap mode (armed by "Swap Prototypes"): swap the clicked proto with
// the active one, then clear the mode and toolbar selection.
if (mapper_copy_proto == 1) {
mapper_copy_proto = 0;
if (!settings.mapper.use_art_not_protos && tool_active != -1) {
int pid1 = toolbar_proto(currentType, scrollOffset + tool_active);
if (pid1 != -1) {
int pid2 = toolbar_proto(currentType, scrollOffset + (keyCode - rightBase));
if (map_proto_swap_proto(pid1, pid2) == -1) {
mapperShowTimedMsg("Error Swapping protos!");
}
tool_active = -1;
selectedPid = -1;
draw_mode = false;
mapper_destroy_highlight_obj(&hl_obj1, nullptr);
_screen_obj = nullptr;
gameMouseResetBouncingCursorFid();
update_art(currentType, scrollOffset);
}
}
continue;
}
if (!settings.mapper.use_art_not_protos) {
int slotIndex = keyCode - rightBase;
int pid = toolbar_proto(currentType, scrollOffset + slotIndex);
@@ -2144,7 +2185,12 @@ void edit_mapper()
if (map_entered) break;
if (!can_modify_protos) break;
if (mapperYesNoDialog("Do you REALLY want to rebuild spray tools?")) {
rebuild_spray_tools();
mapperShowTimedMsg("Please wait.");
if (rebuild_spray_tools() == -1) {
debugPrint("\nError: Rebuilding spray tools!");
mapperShowTimedMsg("Error rebuilding spray tools!");
}
mapperShowTimedMsg("Done.");
}
break;
@@ -2174,37 +2220,25 @@ void edit_mapper()
}
break;
// --- Librarian: Rebuild ALL ---
case kBtnLibrarianRebuildAll:
if (map_entered) break;
if (!can_modify_protos) break;
if (mapperYesNoDialog("Do you REALLY want to rebuild ALL?")) {
_proto_remove_all();
proto_build_all_texts();
}
break;
// --- Librarian: Rebuild Binary ---
case kBtnLibrarianRebuildBinary:
if (map_entered) break;
if (!can_modify_protos) break;
if (mapperYesNoDialog("Do you REALLY want to rebuild Binary?")) {
rebuild_binary();
}
break;
// --- Librarian: Art => New Protos ---
// Confirmed no-op in the original mapper: keycode 63 falls through to the
// default (do-nothing) case and there is no backing implementation in the
// original C/ASM. The menu item is kept for parity, but the handler is
// disabled until/unless its intended behavior is recovered.
case kBtnLibrarianArtToProtos:
if (map_entered) break;
if (!can_modify_protos) break;
art_to_protos();
// if (map_entered) break;
// if (!can_modify_protos) break;
// art_to_protos();
break;
// --- Librarian: Swap Prototypes ---
case kBtnLibrarianSwapProtos:
if (map_entered) break;
if (!can_modify_protos) break;
swap_protos();
// Original kc125 handler was a no-op (the mode flag was never set, leaving
// map_proto_swap_proto unreachable). Wire the intended mode here.
mapper_copy_proto = 1;
mapperShowTimedMsg("Click a proto to swap with the active one.");
break;
// --- Highlight object by proto ---
@@ -2288,7 +2322,10 @@ void mapper_save_toolbar()
if (dot != nullptr) *dot = '\0';
strcat(name, ".cfg");
File* stream = fileOpen(mapBuildPath(name), "wb");
char relativePath[COMPAT_MAX_PATH];
snprintf(relativePath, sizeof(relativePath), "MAPS\\%s", name);
File* stream = fileOpenDirect(buildDataPath(mapperTempRoot(), relativePath), "wb");
if (stream != nullptr) {
fileWrite(toolbar_info, sizeof(ToolbarInfo), 6, stream);
fileClose(stream);
@@ -2359,7 +2396,7 @@ void update_toolname(int* pid, int type, int id)
switch (PID_TYPE(proto->pid)) {
case OBJ_TYPE_ITEM:
windowDrawText(tool_win,
gItemTypeNames[proto->item.type],
item_pro_type[proto->item.type],
kToolNameWidth,
kToolNameX,
kToolNameY2,
@@ -2713,12 +2750,18 @@ int mapper_mark_exit_grid()
// and on editor shutdown if the user quit while still in play mode.
static void mapper_remove_tmp_map_files()
{
remove(mapBuildSavePath(kTmpMapName));
remove(mapBuildSavePath("TMP$MAP#.EDG"));
char path[COMPAT_MAX_PATH];
// The play-mode snapshot (.MAP/.EDG) and its toolbar .CFG are all mapper-only
// temp artifacts living under the temp root.
const char* tempRoot = mapperTempRoot();
snprintf(path, sizeof(path), "%s\\MAPS\\%s", tempRoot, kTmpMapName);
remove(path);
snprintf(path, sizeof(path), "%s\\MAPS\\TMP$MAP#.EDG", tempRoot);
remove(path);
snprintf(path, sizeof(path), "%s\\MAPS\\TMP$MAP#.CFG", tempRoot);
remove(path);
char cfgPath[COMPAT_MAX_PATH];
snprintf(cfgPath, sizeof(cfgPath), "%s\\MAPS\\TMP$MAP#.CFG", settings.system.master_patches_path.c_str());
remove(cfgPath);
MapDirErase("MAPS\\", "SAV");
}
@@ -2751,10 +2794,11 @@ static void mapper_enter_play_mode(Object** pHlObj1)
map_toggle_block_obj_viewing(0);
// Save current map under the temp name so we can restore on exit.
// Save current map under the temp name (to the temp root) so we can restore
// it on exit. This is mapper-only state, kept out of the dev_path mod folder.
strncpy(gMapHeader.name, kTmpMapName, sizeof(gMapHeader.name) - 1);
gMapHeader.name[sizeof(gMapHeader.name) - 1] = '\0';
_map_save();
mapSaveToRoot(mapperTempRoot());
objectSetLocation(gDude, savedCenterTile, gElevation, nullptr);
+2970 -126
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -17,10 +17,10 @@ const char* proto_wall_light_str(int flags);
int proto_pick_ai_packet(int* value);
int proto_build_all_type(int type);
int proto_build_all_type_binary(int type);
void rebuild_spray_tools();
void rebuild_binary();
void art_to_protos();
void swap_protos();
// Confirmed no-op in the original mapper (keycode 63 falls through to default);
// kept commented out, see mp_proto.cc.
// void art_to_protos();
int map_proto_swap_proto(int pid1, int pid2);
int protoEdit(int protoId);
int protoChooseMultiPids(int pidType, protoChooseFidCallback fidFunc, protoChooseAddCallback addFunc);
// protoInstEdit moved to mp_instance.h
File diff suppressed because it is too large Load Diff
+22
View File
@@ -0,0 +1,22 @@
#ifndef FALLOUT_MAPPER_MP_SPRAY_H_
#define FALLOUT_MAPPER_MP_SPRAY_H_
namespace fallout {
// Authoring path was disabled in the original mapper (the function just fails).
void create_spray_tool();
// Pick a tile pattern and mouse-paint it onto the square tile grid.
void copy_spray_tile();
// Rebuild all binary pattern (.spr) files from their text sources under the
// mapper temp root. Returns -1 on failure. Dev-only: requires the text sources.
int rebuild_spray_tools();
// Write a single floor/roof tile at an explicit square tile index (no refresh).
// Returns true if the tile data actually changed.
bool place_tile_at(int fid, int tileIndex);
} // namespace fallout
#endif /* FALLOUT_MAPPER_MP_SPRAY_H_ */
+159 -14
View File
@@ -1,17 +1,48 @@
#include "mapper/mp_targt.h"
#include <stdio.h>
#include <string.h>
#include "art.h"
#include "color.h"
#include "game.h"
#include "map.h"
#include "mapper/mp_proto.h"
#include "memory.h"
#include "platform_compat.h"
#include "proto.h"
#include "settings.h"
#include "window_manager_private.h"
namespace fallout {
// Target field-type names (text format), indexed by TargetSubNode::field_2C.
static const char* const target_types[] = {
"t_hex",
"t_hex_offset",
"rotation",
"elevation",
"t_object",
"t_item",
"t_scenery",
"t_global_var",
"t_local_var",
"t_animation",
"number",
"string",
"script",
"yes-no",
};
// Target placement names (text format), indexed by TargetSubNode::field_30.
static const char* const target_placements[] = {
"proto_inst_struct",
"object_inst_struct",
"global_var",
"local_var",
"scr_local_var",
};
#define TARGET_DAT "target.dat"
typedef struct TargetNode {
@@ -25,15 +56,9 @@ typedef struct TargetList {
int next_tid;
} TargetList;
// 0x53F354
static char default_target_path_base[] = "\\fallout2\\dev\\proto\\";
// 0x559CC4
static TargetList targetlist = { NULL, 0, 0 };
// 0x559CD0
static char* target_path_base = default_target_path_base;
// 0x559DBC
static bool tgt_overriden = false;
@@ -59,18 +84,22 @@ bool target_overriden()
return tgt_overriden;
}
// 0x49B34C
// Builds the mapper temp-root proto directory ("<temp>\proto\[<type>]"), creating
// it on disk. For pid -1 the path keeps a trailing separator so callers can
// append "target.dat"; otherwise it ends at the per-type folder.
void target_make_path(char* path, int pid)
{
if (_cd_path_base[0] != '\0' && _cd_path_base[1] == ':') {
strncpy(path, _cd_path_base, 2);
strcat(path, target_path_base);
} else {
strcpy(path, target_path_base);
}
const char* root = mapperTempRoot();
size_t rootLen = strlen(root);
const char* separator = (rootLen > 0 && (root[rootLen - 1] == '\\' || root[rootLen - 1] == '/')) ? "" : "\\";
if (pid != -1) {
strcat(path, artGetObjectTypeName(PID_TYPE(pid)));
snprintf(path, COMPAT_MAX_PATH, "%s%sproto\\%s", root, separator, artGetObjectTypeName(PID_TYPE(pid)));
compat_mkdir_recursive(path);
} else {
snprintf(path, COMPAT_MAX_PATH, "%s%sproto", root, separator);
compat_mkdir_recursive(path);
strcat(path, "\\");
}
}
@@ -240,6 +269,122 @@ int target_load(int pid, TargetSubNode** subnode_ptr)
return 0;
}
// Splits a "key: value" line in place: trims the newline, null-terminates the key at the
// first ':', and returns the value with leading spaces skipped (or nullptr if no ':').
static char* target_text_split(char* line)
{
char* nl = strchr(line, '\n');
if (nl != NULL) {
*nl = '\0';
}
char* colon = strchr(line, ':');
if (colon == NULL) {
return NULL;
}
*colon = '\0';
char* value = colon + 1;
while (*value == ' ') {
value++;
}
return value;
}
// Case-insensitive lookup of str in list[count]; returns index or -1.
static int target_strlist_index(const char* str, const char* const* list, int count)
{
for (int i = 0; i < count; i++) {
if (compat_stricmp(str, list[i]) == 0) {
return i;
}
}
return -1;
}
// target_load_text
int target_load_text(int pid)
{
char path[COMPAT_MAX_PATH];
target_make_path(path, pid);
size_t len = strlen(path);
path[len] = '\\';
_proto_list_str(pid, path + len + 1);
char* extension = strchr(path + len + 1, '.');
if (extension != NULL) {
strcpy(extension + 1, "tar");
} else {
strcat(path, ".tar");
}
FILE* stream = fopen(path, "rt");
if (stream == NULL) {
return -1;
}
TargetSubNode* subnode;
if (target_find_free_subnode(&subnode) == -1) {
fclose(stream);
return -1;
}
char line[120];
while (fgets(line, sizeof(line), stream) != NULL) {
char* value = target_text_split(line);
if (value == NULL) {
continue;
}
const char* key = line;
if (strcmp(key, "pid") == 0) {
subnode->pid = pid;
} else if (strcmp(key, "tid") == 0) {
sscanf(value, "%d", &subnode->tid);
} else if (strcmp(key, "desc") == 0) {
strcpy(reinterpret_cast<char*>(&subnode->field_8), value);
} else if (strcmp(key, "next") == 0) {
int hasNext = 0;
sscanf(value, "%d", &hasNext);
subnode->next = hasNext != 0 ? reinterpret_cast<TargetSubNode*>(1) : NULL;
} else if (strcmp(key, "type") == 0) {
int index = target_strlist_index(value, target_types, sizeof(target_types) / sizeof(target_types[0]));
if (index != -1) {
subnode->field_2C = index;
}
} else if (strcmp(key, "placement") == 0) {
int index = target_strlist_index(value, target_placements, sizeof(target_placements) / sizeof(target_placements[0]));
if (index != -1) {
subnode->field_30 = index;
}
} else if (strcmp(key, "pmid") == 0) {
sscanf(value, "%d", &subnode->field_34);
} else if (strcmp(key, "flags") == 0) {
sscanf(value, "%d", &subnode->field_38);
} else if (strcmp(key, "where") == 0) {
sscanf(value, "%d", &subnode->field_3C);
} else if (strcmp(key, "v0") == 0) {
sscanf(value, "%d", &subnode->field_44);
} else if (strcmp(key, "------") == 0) {
if (subnode->next != NULL) {
subnode->next = (TargetSubNode*)internal_malloc(sizeof(TargetSubNode));
if (subnode->next == NULL) {
break;
}
subnode = subnode->next;
}
} else {
char msg[80];
snprintf(msg, sizeof(msg), "Load text as target error:\n\t=>%s", value);
_win_msg(msg, 100, 100, _colorTable[31744] | 0x10000);
}
}
fclose(stream);
return 0;
}
// 0x49B9C0
int target_find_free_subnode(TargetSubNode** subnode_ptr)
{
+1
View File
@@ -55,6 +55,7 @@ int target_header_save();
int target_header_load();
int target_save(int pid);
int target_load(int pid, TargetSubNode** subnode_ptr);
int target_load_text(int pid);
int target_find_free_subnode(TargetSubNode** subnode_ptr);
int target_new(int pid, int* tid_ptr);
int target_remove(int pid);
+3 -3
View File
@@ -10,16 +10,16 @@
namespace fallout {
// 0x49DAC4
// proto_build_all_texts
int proto_build_all_texts()
{
for (int type = OBJ_TYPE_ITEM; type <= OBJ_TYPE_MISC; type++) {
if (inputGetInput() == KEY_ESCAPE) {
win_timed_msg("BUILD all Protos has been aborted!", _colorTable[32747] | 0x10000);
_win_msg("BUILD all Protos has been aborted!", 150, 150, _colorTable[992] | 0x10000);
return 0;
}
if (proto_build_all_type(type) == -1) {
win_timed_msg("Build Text Error: Type Failed Build!", _colorTable[32747] | 0x10000);
win_timed_msg("Build Text Error: Type Failed Build!", _colorTable[32767] | 0x10000);
return -1;
}
}
+15
View File
@@ -1,5 +1,8 @@
#include "mapper/mp_utils.h"
#include <string.h>
#include "art.h"
#include "color.h"
#include "text_font.h"
#include "window_manager_private.h"
@@ -21,4 +24,16 @@ void mapperShowMessage(const char* msg)
_win_msg(msg, 80, 80, _colorTable[31744] | FONT_SHADOW);
}
void art_name_no_ext(int fid, char* buf)
{
if (art_list_str(fid, buf) == -1) {
strcpy(buf, "None");
} else {
char* pch = strchr(buf, '.');
if (pch != nullptr) {
*pch = '\0';
}
}
}
} // namespace fallout

Some files were not shown because too many files have changed in this diff Show More