From 331bf79ec05017b0844909b320877dd6c2ea7216 Mon Sep 17 00:00:00 2001 From: phobos2077 Date: Sun, 10 May 2026 22:56:09 +0200 Subject: [PATCH] mapper: save map to TXT --- src/art.cc | 49 ++++++++- src/mapper/mp_text.cc | 242 ++++++++++++++++++++++++++++++++++++++++-- src/object.cc | 4 +- src/object.h | 1 + src/scripts.cc | 19 +--- src/scripts.h | 20 +++- 6 files changed, 300 insertions(+), 35 deletions(-) diff --git a/src/art.cc b/src/art.cc index 4f379fa0..71b06b2d 100644 --- a/src/art.cc +++ b/src/art.cc @@ -447,9 +447,54 @@ void artRender(int fid, unsigned char* dest, int width, int height, int pitch) // mapper2.exe: 0x40A03C int art_list_str(int fid, char* name) { - // TODO: Incomplete. + if (fid == -1 || name == nullptr) { + return -1; + } - return -1; + int objectType = (fid & 0xF000000) >> 24; + int index = fid & 0xFFF; + + const char* typeName = artGetObjectTypeName(objectType); + if (typeName == nullptr) { + return -1; + } + + char path[260]; + snprintf(path, sizeof(path), "%s%s%s\\%s.lst", _cd_path_base, "art\\", typeName, typeName); + + File* stream = fileOpen(path, "rt"); + if (stream == nullptr) { + return -1; + } + + char buffer[260]; + int line = 0; + bool found = false; + while (fileReadString(buffer, sizeof(buffer), stream) != nullptr) { + if (line == index) { + char* p = buffer; + while (*p) { + if (*p == ' ' || *p == '\n') { + *p = '\0'; + break; + } + p++; + } + char* dst = name; + const char* src = buffer; + do { + *dst++ = *src++; + *dst++ = *src++; + } while (dst[-2] != '\0' || dst[-1] != '\0'); + found = true; + break; + } + line++; + } + + fileClose(stream); + + return found ? 0 : -1; } int artListIndex(int objectType, const char* name) diff --git a/src/mapper/mp_text.cc b/src/mapper/mp_text.cc index b99b3203..1036bf1f 100644 --- a/src/mapper/mp_text.cc +++ b/src/mapper/mp_text.cc @@ -15,6 +15,7 @@ #include "mapper/mp_proto.h" #include "obj_types.h" #include "object.h" +#include "proto.h" #include "scripts.h" #include "settings.h" #include "text_font.h" @@ -23,17 +24,238 @@ namespace fallout { -// Stub implementations for text-format save functions. -// These will be fully implemented in future rounds. -static int scrSaveText(File* stream) +// Forward declarations for save functions. +static int obj_save_obj_text(File* stream, Object* obj); +// Functions to build path strings for PID/FID (maps to original pid_to_string/fid_to_string) +static const char* pid_to_string(int pid); +static const char* fid_to_string(int fid); + +static const char* pid_to_string(int pid) { - xfilePrintFormatted(stream, "/* Scripts not implemented in text format yet */\n"); + static char pidStr[COMPAT_MAX_PATH]; + + if (_proto_list_str(pid, pidStr) == -1) { + return ""; + } + + char* dot = strchr(pidStr, '.'); + if (dot != nullptr) { + *dot = '\0'; + } + + return pidStr; +} + +static const char* fid_to_string(int fid) +{ + static char fidStr[COMPAT_MAX_PATH]; + + if (art_list_str(fid, fidStr) == -1) { + return ""; + } + + char* dot = strchr(fidStr, '.'); + if (dot != nullptr) { + *dot = '\0'; + } + + return fidStr; +} + +static int scr_save_text(File* stream) +{ + if (stream == nullptr) { + return -1; + } + + for (int scriptType = 0; scriptType < SCRIPT_TYPE_COUNT; scriptType++) { + ScriptList* scriptList = &gScriptLists[scriptType]; + + int scriptCount = scriptList->length * SCRIPT_LIST_EXTENT_SIZE; + if (scriptList->tail != nullptr) { + scriptCount += scriptList->tail->length - SCRIPT_LIST_EXTENT_SIZE; + } + + xfilePrintFormatted(stream, "scr_num: %d\n", scriptCount); + + if (scriptList->head == nullptr) { + continue; + } + + ScriptListExtent* scriptExtent = scriptList->head; + while (scriptExtent != nullptr) { + xfilePrintFormatted(stream, "\n[[SCRIPT]]\n\n"); + for (int index = 0; index < scriptExtent->length; index++) { + Script* script = &scriptExtent->scripts[index]; + + xfilePrintFormatted(stream, "scr_id: %u\n", script->sid); + xfilePrintFormatted(stream, "scr_next: %u\n", script->field_4); + xfilePrintFormatted(stream, "scr_flags: %d\n", script->flags); + xfilePrintFormatted(stream, "scr_script_idx: %d\n", script->index); + xfilePrintFormatted(stream, "scr_oid: %u\n", script->ownerId); + xfilePrintFormatted(stream, "scr_local_var_offset: %u\n", script->localVarsOffset); + xfilePrintFormatted(stream, "scr_num_local_vars: %u\n\n", script->localVarsCount); + + if (scriptType == SCRIPT_TYPE_SPATIAL) { + xfilePrintFormatted(stream, "scr_udata.sp.built_tile: %d\n\n", script->sp.built_tile); + xfilePrintFormatted(stream, "scr_udata.sp.radius: %d\n\n", script->sp.radius); + } else if (scriptType == SCRIPT_TYPE_TIMED) { + xfilePrintFormatted(stream, "scr_udata.tm.time: %d\n\n", script->tm.time); + } + } + scriptExtent = scriptExtent->next; + } + } + return 0; } -static int objSaveText(File* stream) +static int obj_save_text(File* stream) { - xfilePrintFormatted(stream, "/* Objects not implemented in text format yet */\n"); + if (stream == nullptr) { + return -1; + } + + xfilePrintFormatted(stream, "[[OBJECTS BEGIN]]\n\n"); + + for (int elevation = 0; elevation < ELEVATION_COUNT; elevation++) { + for (int tile = 0; tile < HEX_GRID_SIZE; tile++) { + for (ObjectListNode* objectListNode = gObjectListHeadByTile[tile]; objectListNode != nullptr; objectListNode = objectListNode->next) { + Object* obj = objectListNode->obj; + if (obj->elevation != elevation) { + continue; + } + if ((obj->flags & OBJECT_NO_SAVE) == 0) { + obj_save_obj_text(stream, obj); + } + } + } + } + + xfilePrintFormatted(stream, "[[OBJECTS END]]\n"); + return 0; +} + +static int obj_save_obj_text(File* stream, Object* obj) +{ + if (stream == nullptr || obj == nullptr) { + return -1; + } + + xfilePrintFormatted(stream, "[OBJECT BEGIN]\n"); + xfilePrintFormatted(stream, "obj_id: %d\n", obj->id); + xfilePrintFormatted(stream, "obj_tile_num: %d\n", obj->tile); + xfilePrintFormatted(stream, "obj_x: %d\n", obj->x); + xfilePrintFormatted(stream, "obj_y: %d\n", obj->y); + xfilePrintFormatted(stream, "obj_sx: %d\n", obj->sx); + xfilePrintFormatted(stream, "obj_sy: %d\n", obj->sy); + xfilePrintFormatted(stream, "obj_cur_frm: %u\n", obj->frame); + xfilePrintFormatted(stream, "obj_cur_rot: %u\n", obj->rotation); + xfilePrintFormatted(stream, "obj_pid: %u %s\n", obj->pid, pid_to_string(obj->pid)); + xfilePrintFormatted(stream, "obj_fid: %u %s\n", obj->fid, fid_to_string(obj->fid)); + xfilePrintFormatted(stream, "obj_flags: %u\n", obj->flags); + xfilePrintFormatted(stream, "obj_elev: %d\n", obj->elevation); + xfilePrintFormatted(stream, "obj_cid: %u\n", obj->cid); + xfilePrintFormatted(stream, "obj_light_distance: %d\n", obj->lightDistance); + xfilePrintFormatted(stream, "obj_light_intensity: %d\n", obj->lightIntensity); + xfilePrintFormatted(stream, "obj_outline: %d\n", obj->outline); + xfilePrintFormatted(stream, "obj_sid: %u\n", obj->sid); + xfilePrintFormatted(stream, "obj_pud.inv_size: %d\n", obj->data.inventory.length); + xfilePrintFormatted(stream, "obj_pud.inv_max: %d\n", obj->data.inventory.capacity); + + int pidType = PID_TYPE(obj->pid); + + if (pidType == OBJ_TYPE_CRITTER) { + xfilePrintFormatted(stream, "obj_pud.reaction_to_pc: %d\n", obj->data.critter.reaction); + xfilePrintFormatted(stream, "obj_pud.curr_hp: %d\n", obj->data.critter.hp); + xfilePrintFormatted(stream, "obj_pud.curr_rad: %d\n", obj->data.critter.radiation); + xfilePrintFormatted(stream, "obj_pud.curr_poison: %d\n", obj->data.critter.poison); + xfilePrintFormatted(stream, "obj_pud.combat_data.curr_mp: %d\n", obj->data.critter.combat.ap); + xfilePrintFormatted(stream, "obj_pud.combat_data.damage_last_turn: %d\n", obj->data.critter.combat.damageLastTurn); + xfilePrintFormatted(stream, "obj_pud.combat_data.results: %d\n", obj->data.critter.combat.results); + xfilePrintFormatted(stream, "obj_pud.combat_data.ai_packet: %d\n", obj->data.critter.combat.aiPacket); + xfilePrintFormatted(stream, "obj_pud.combat_data.team_num: %d\n", obj->data.critter.combat.team); + if (obj->data.critter.combat.whoHitMe != nullptr) { + xfilePrintFormatted(stream, "obj_pud.combat_data.who_hit_me: %d\n", obj->data.critter.combat.whoHitMeCid); + } else { + xfilePrintFormatted(stream, "obj_pud.combat_data.who_hit_me: -1\n"); + } + } else { + xfilePrintFormatted(stream, "obj_pudg.updated_flags: %d\n", obj->data.flags); + + Proto* proto; + if (protoGetProto(obj->pid, &proto) == -1) { + return -1; + } + + // Get subtype from proto type field (same offset for ItemProto and SceneryProto) + int protoSubtype = static_cast(proto->item.type); + + switch (pidType) { + case OBJ_TYPE_ITEM: + switch (protoSubtype) { + case ITEM_TYPE_WEAPON: + xfilePrintFormatted(stream, "obj_pudg.pudweapon.cur_ammo_quantity: %d\n", obj->data.item.weapon.ammoQuantity); + xfilePrintFormatted(stream, "obj_pudg.pudweapon.cur_ammo_type_pid: %d\n", obj->data.item.weapon.ammoTypePid); + break; + case ITEM_TYPE_AMMO: + xfilePrintFormatted(stream, "obj_pudg.pudammo.cur_ammo_quantity: %d\n", obj->data.item.ammo.quantity); + break; + case ITEM_TYPE_MISC: + xfilePrintFormatted(stream, "obj_pudg.pudmisc_item.curr_charges: %d\n", obj->data.item.misc.charges); + break; + case ITEM_TYPE_KEY: + xfilePrintFormatted(stream, "obj_pudg.pudkey_item.cur_key_code: %d\n", obj->data.item.key.keyCode); + break; + } + break; + case OBJ_TYPE_SCENERY: + switch (protoSubtype) { + case SCENERY_TYPE_DOOR: + xfilePrintFormatted(stream, "obj_pudg.pudportal.cur_open_flags: %d\n", obj->data.scenery.door.openFlags); + break; + case SCENERY_TYPE_STAIRS: + xfilePrintFormatted(stream, "obj_pudg.pudstairs.destMap: %d\n", obj->data.scenery.stairs.destinationMap); + xfilePrintFormatted(stream, "obj_pudg.pudstairs.destBuiltTile: %d\n", obj->data.scenery.stairs.destinationBuiltTile); + break; + case SCENERY_TYPE_ELEVATOR: + xfilePrintFormatted(stream, "obj_pudg.pudelevator.elevType: %d\n", obj->data.scenery.elevator.type); + xfilePrintFormatted(stream, "obj_pudg.pudelevator.elevLevel: %d\n", obj->data.scenery.elevator.level); + break; + default: + break; + } + break; + default: + break; + } + } + + if (obj->data.inventory.length == 0) { + xfilePrintFormatted(stream, "[OBJECT END]\n\n"); + return 0; + } + + xfilePrintFormatted(stream, "obj_pud.[BEGIN INVEN ITEMS]:\n"); + + if (obj->data.inventory.length <= 0) { + xfilePrintFormatted(stream, "obj_pud.[END INVEN ITEMS]:\n"); + xfilePrintFormatted(stream, "[OBJECT END]\n\n"); + return 0; + } + + for (int i = 0; i < obj->data.inventory.length; i++) { + InventoryItem* item = &obj->data.inventory.items[i]; + xfilePrintFormatted(stream, "obj_pud.inven_item: %d\n", i); + xfilePrintFormatted(stream, "obj_pud.inv.count: %d\n", item->quantity); + if (obj_save_obj_text(stream, item->item) == -1) { + return -1; + } + } + + xfilePrintFormatted(stream, "obj_pud.[END INVEN ITEMS]:\n"); + xfilePrintFormatted(stream, "[OBJECT END]\n\n"); + return 0; } @@ -158,9 +380,7 @@ void map_save_text() for (int elev = 0; elev < ELEVATION_COUNT; elev++) { if ((gMapHeader.flags & _map_data_elev_flags[elev]) == 0) { xfilePrintFormatted(stream, "\nsquare_elev: %d\n\n", elev); - for (int tile = 0; tile < SQUARE_GRID_SIZE; tile++) { - int tileData = _square[elev]->field_0[tile]; - + for (int tileData : _square[elev]->field_0) { xfilePrintFormatted(stream, "sq: %u ", tileData); // Floor tile art file path. @@ -206,7 +426,7 @@ void map_save_text() // Save scripts in text format. xfilePrintFormatted(stream, "\n>>>>>>>>>>: SCRIPTS <<<<<<<<<<\n\n"); xfilePrintFormatted(stream, "\nSCRS:\n"); - int scrResult = scrSaveText(stream); + int scrResult = scr_save_text(stream); if (scrResult == -1) { char msg[80]; snprintf(msg, sizeof(msg), "Error saving scripts in %s--TEXT", gMapHeader.name); @@ -215,7 +435,7 @@ void map_save_text() // Save objects in text format. xfilePrintFormatted(stream, "\n>>>>>>>>>>: OBJECTS <<<<<<<<<<\n\n"); - int objResult = objSaveText(stream); + int objResult = obj_save_text(stream); if (objResult == -1) { char msg[80]; snprintf(msg, sizeof(msg), "Error saving objects in %s--TEXT", gMapHeader.name); diff --git a/src/object.cc b/src/object.cc index f60e5582..5a3886e7 100644 --- a/src/object.cc +++ b/src/object.cc @@ -242,7 +242,7 @@ static Rect gObjectsUpdateAreaPixelBounds; // Contains objects that are bounded to tiles. // // 0x639DA0 -static ObjectListNode* gObjectListHeadByTile[HEX_GRID_SIZE]; +ObjectListNode* gObjectListHeadByTile[HEX_GRID_SIZE]; // 0x660EA0 static unsigned char _glassGrayTable[256]; @@ -2308,7 +2308,7 @@ Object* objectFindNextAtLocation() return nullptr; } -// 0x0x48B66C +// 0x48B66C void objectGetRect(Object* obj, Rect* rect) { if (obj == nullptr) { diff --git a/src/object.h b/src/object.h index a7bf2c58..41b4816b 100644 --- a/src/object.h +++ b/src/object.h @@ -20,6 +20,7 @@ extern Object* _moveBlockObj; extern unsigned char _commonGrayTable[256]; extern Object* gEgg; extern Object* gDude; +extern ObjectListNode* gObjectListHeadByTile[HEX_GRID_SIZE]; int objectsInit(unsigned char* buf, int width, int height, int pitch); void objectsReset(); diff --git a/src/scripts.cc b/src/scripts.cc index fbac58f3..2f7d0071 100644 --- a/src/scripts.cc +++ b/src/scripts.cc @@ -45,8 +45,6 @@ namespace fallout { -#define SCRIPT_LIST_EXTENT_SIZE 16 - // SFALL: Increase number of message lists for scripted dialogs. // CE: In Sfall this increase is configurable with `BoostScriptDialogLimit`. #define SCRIPT_DIALOG_MESSAGE_LIST_CAPACITY 10000 @@ -56,21 +54,6 @@ typedef struct ScriptsListEntry { int local_vars_num; } ScriptsListEntry; -typedef struct ScriptListExtent { - Script scripts[SCRIPT_LIST_EXTENT_SIZE]; - // Number of scripts in the extent - int length; - struct ScriptListExtent* next; -} ScriptListExtent; - -typedef struct ScriptList { - ScriptListExtent* head; - ScriptListExtent* tail; - // Number of extents in the script list. - int length; - int nextScriptId; -} ScriptList; - static Program* scriptsCreateProgramByName(const char* name); static void _doBkProcesses(); static void _script_chk_critters(); @@ -116,7 +99,7 @@ static int gScriptsEnumerationElevation = 0; static bool gSpatialsEnabled = true; // 0x51C6C0 -static ScriptList gScriptLists[SCRIPT_TYPE_COUNT]; +ScriptList gScriptLists[SCRIPT_TYPE_COUNT]; // 0x51C710 static const char* gScriptsBasePath = "scripts\\"; diff --git a/src/scripts.h b/src/scripts.h index 0040ea23..415c2c27 100644 --- a/src/scripts.h +++ b/src/scripts.h @@ -79,7 +79,7 @@ typedef enum ScriptProc { SCRIPT_PROC_COUNT, } ScriptProc; -typedef struct Script { +struct Script { // scr_id int sid; @@ -146,8 +146,24 @@ typedef struct Script { int field_DC; Object* overriddenSelf; -} Script; +}; +#define SCRIPT_LIST_EXTENT_SIZE (16) + +struct ScriptListExtent { + Script scripts[SCRIPT_LIST_EXTENT_SIZE]; + int length; + ScriptListExtent* next; +}; + +struct ScriptList { + ScriptListExtent* head; + ScriptListExtent* tail; + int length; + int nextScriptId; +}; + +extern ScriptList gScriptLists[SCRIPT_TYPE_COUNT]; extern const char* gScriptProcNames[SCRIPT_PROC_COUNT]; unsigned int gameTimeGetTime();