mapper: save map to TXT

This commit is contained in:
phobos2077
2026-05-10 22:56:09 +02:00
parent 456f44b2a3
commit 331bf79ec0
6 changed files with 300 additions and 35 deletions
+47 -2
View File
@@ -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)
+231 -11
View File
@@ -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<int>(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);
+2 -2
View File
@@ -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) {
+1
View File
@@ -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();
+1 -18
View File
@@ -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\\";
+18 -2
View File
@@ -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();