diff --git a/artifacts/scripting/headers/sfall.h b/artifacts/scripting/headers/sfall.h index df8ef480..973650c6 100644 --- a/artifacts/scripting/headers/sfall.h +++ b/artifacts/scripting/headers/sfall.h @@ -144,9 +144,9 @@ #define reverse_array(array) resize_array(array, -4) // randomly shuffle elements in list/map #define shuffle_array(array) resize_array(array, -5) -// sort map in ascending order by key value +// sort map in ascending order by value #define sort_map_value(array) resize_array(array, -6) -// sort map in descending order by key value +// sort map in descending order by value #define sort_map_reverse(array) resize_array(array, -7) // remove element from map or just replace value with 0 for list #define unset_array(array, key) set_array(array, key, 0) @@ -282,6 +282,7 @@ /* SFALL_FUNCX MACROS */ +#define FUNC_SELECTOR_4(_1,_2,_3,_4,FUNC,...) FUNC #define FUNC_SELECTOR_7(_1,_2,_3,_4,_5,_6,_7,FUNC,...) FUNC #define add_extra_msg_file(name) sfall_func1("add_extra_msg_file", name) @@ -289,6 +290,7 @@ #define add_iface_tag sfall_func0("add_iface_tag") #define add_trait(traitID) sfall_func1("add_trait", traitID) #define art_cache_clear sfall_func0("art_cache_clear") +#define art_frame_data(art, frame, rot) sfall_func3("art_frame_data", art, frame, rot) #define attack_is_aimed sfall_func0("attack_is_aimed") #define car_gas_amount sfall_func0("car_gas_amount") #define clear_window sfall_func0("win_fill_color") @@ -350,7 +352,11 @@ #define item_weight(obj) sfall_func1("item_weight", obj) #define lock_is_jammed(obj) sfall_func1("lock_is_jammed", obj) #define loot_obj sfall_func0("loot_obj") -#define message_box(text) sfall_func1("message_box", text) +#define message_box1(text) sfall_func1("message_box", text) +#define message_box2(text, flags) sfall_func2("message_box", text, flags) +#define message_box3(text, flags, color1) sfall_func3("message_box", text, flags, color1) +#define message_box4(text, flags, color1, color2) sfall_func4("message_box", text, flags, color1, color2) +#define message_box(...) FUNC_SELECTOR_4(__VA_ARGS__,message_box4,message_box3,message_box2,message_box1)(__VA_ARGS__) #define metarule_exist(metaruleName) sfall_func1("metarule_exist", metaruleName) #define npc_engine_level_up(toggle) sfall_func1("npc_engine_level_up", toggle) #define obj_is_openable(obj) sfall_func1("obj_is_openable", obj) diff --git a/artifacts/scripting/sfall function notes.md b/artifacts/scripting/sfall function notes.md index a334e2ef..7e51e7f4 100644 --- a/artifacts/scripting/sfall function notes.md +++ b/artifacts/scripting/sfall function notes.md @@ -1122,6 +1122,16 @@ sfall_funcX metarule functions `void sfall_func0("signal_close_game")` - Works in a similar way to vanilla function: `metarule(METARULE_SIGNAL_END_GAME, 0)`, but it will then close the game instead of only returning the player to the main menu +---- +#### art_frame_data +`array sfall_func3("art_frame_data", string/int artFile/artId, int frame, int rotation)` +- Returns the dimensions of a given PCX or FRM frame as a temp array in the form **[width, height]** +- `artFile/artId`: path to the PCX/FRM file (e.g. `art\\inven\\5mmap.frm`), or its FRM ID number (e.g. `0x7000026`, see specification of the FID format) + +**Optional arguments:** +- `frame`: frame number, the first frame starts from zero +- `rotation`: rotation to get the frame for, useful when reading FRM files by path + **** _See other documentation files (arrays.md, hookscripts.md) for related functions reference._ diff --git a/sfall/FalloutEngine/EngineUtils.cpp b/sfall/FalloutEngine/EngineUtils.cpp index d663ae72..c39d5cb4 100644 --- a/sfall/FalloutEngine/EngineUtils.cpp +++ b/sfall/FalloutEngine/EngineUtils.cpp @@ -220,7 +220,7 @@ long ObjIsOpenable(fo::GameObject* object) { long result = 0; if (fo::func::obj_is_openable(object)) { DWORD lock; - fo::FrmHeaderData* frm = fo::func::art_ptr_lock(object->artFid, &lock); + fo::FrmFile* frm = fo::func::art_ptr_lock(object->artFid, &lock); if (frm) { if (frm->numFrames > 1) result = 1; fo::func::art_ptr_unlock(lock); diff --git a/sfall/FalloutEngine/Functions.cpp b/sfall/FalloutEngine/Functions.cpp index a018d98e..11bb3912 100644 --- a/sfall/FalloutEngine/Functions.cpp +++ b/sfall/FalloutEngine/Functions.cpp @@ -391,23 +391,6 @@ void __cdecl trans_buf_to_buf(BYTE* src, long width, long height, long src_width __asm emms; } -BYTE* __fastcall loadPCX(const char* file, long* width, long* height) { - __asm { - mov eax, ecx; - mov ebx, height; - mov ecx, FO_VAR_pal; - call fo::funcoffs::loadPCX_; - push eax; - mov ebx, [width]; - mov edx, FO_VAR_pal; - mov ecx, [height]; - mov ebx, [ebx]; - mov ecx, [ecx]; - call fo::funcoffs::datafileConvertData_; - pop eax; - } -} - long __fastcall get_game_config_string(const char** outValue, const char* section, const char* param) { __asm { mov ebx, param; @@ -416,6 +399,11 @@ long __fastcall get_game_config_string(const char** outValue, const char* sectio } } +void __stdcall freePtr_invoke(void *p) { + __asm mov eax, p; + __asm call ds:[FO_VAR_freePtr]; +} + //////////////////////////////////// // X-Macro for wrapper functions. // //////////////////////////////////// diff --git a/sfall/FalloutEngine/Functions.h b/sfall/FalloutEngine/Functions.h index b87e8cf6..0a099378 100644 --- a/sfall/FalloutEngine/Functions.h +++ b/sfall/FalloutEngine/Functions.h @@ -80,10 +80,10 @@ void __cdecl buf_to_buf(BYTE* src, long width, long height, long src_width, BYTE // trans_buf_to_buf_ function implementation void __cdecl trans_buf_to_buf(BYTE* src, long width, long height, long src_width, BYTE* dst, long dst_width); -BYTE* __fastcall loadPCX(const char* file, long* width, long* height); - long __fastcall get_game_config_string(const char** outValue, const char* section, const char* param); +void __stdcall freePtr_invoke(void *ptr); + // X-Macro for wrapper functions. #define WRAP_WATCOM_FUNC0(retType, name) \ retType __stdcall name(); diff --git a/sfall/FalloutEngine/Functions_def.h b/sfall/FalloutEngine/Functions_def.h index 41a7ddda..d01f5984 100644 --- a/sfall/FalloutEngine/Functions_def.h +++ b/sfall/FalloutEngine/Functions_def.h @@ -81,7 +81,8 @@ WRAP_WATCOM_FUNC5(long, art_id, long, artType, long, lstIndex, long, animCode, l WRAP_WATCOM_FUNC3(BYTE*, art_frame_data, fo::FrmHeaderData*, frm, long, frameNum, long, rotation) WRAP_WATCOM_FUNC3(long, art_frame_width, fo::FrmHeaderData*, frm, long, frameNum, long, rotation) WRAP_WATCOM_FUNC3(long, art_frame_length, fo::FrmHeaderData*, frm, long, frameNum, long, rotation) -WRAP_WATCOM_FUNC2(fo::FrmHeaderData*, art_ptr_lock, long, frmId, DWORD*, lockPtr) +WRAP_WATCOM_FUNC5(long, art_frame_width_length, fo::FrmHeaderData*, frm, long, frameNum, long, rotation, long*, widthPtr, long*, heightPtr) +WRAP_WATCOM_FUNC2(fo::FrmFile*, art_ptr_lock, long, frmId, DWORD*, lockPtr) WRAP_WATCOM_FUNC4(BYTE*, art_ptr_lock_data, long, frmId, long, frameNum, long, rotation, DWORD*, lockPtr) WRAP_WATCOM_FUNC4(BYTE*, art_lock, long, frmId, DWORD*, lockPtr, long*, widthOut, long*, heightOut) WRAP_WATCOM_FUNC1(long, art_ptr_unlock, DWORD, lockId) @@ -94,6 +95,7 @@ WRAP_WATCOM_FUNC1(long, critter_kill_count_type, fo::GameObject*, critter) WRAP_WATCOM_FUNC1(const char*, critter_name, fo::GameObject*, critter) // Returns the name of the critter WRAP_WATCOM_FUNC1(void, critter_pc_set_name, const char*, newName) // Change the name of playable character WRAP_WATCOM_FUNC1(long, critterIsOverloaded, fo::GameObject*, critter) +WRAP_WATCOM_FUNC4(void, datafileConvertData, BYTE*, data, BYTE*, palette, long, width, long, height) WRAP_WATCOM_FUNC1(void, display_print, const char*, msg) // Displays message in main UI console window WRAP_WATCOM_FUNC0(void, display_stats) // Execute script proc by internal proc number (from script's proc table, basically a sequential number of a procedure as defined in code, starting from 1) @@ -155,7 +157,8 @@ WRAP_WATCOM_FUNC1(long, item_w_secondary_mp_cost, fo::GameObject*, item) WRAP_WATCOM_FUNC2(long, item_w_subtype, fo::GameObject*, item, long, hitMode) WRAP_WATCOM_FUNC1(long, item_weight, fo::GameObject*, item) WRAP_WATCOM_FUNC2(long, light_get_tile, long, elevation, long, tileNum) // Returns light level at given tile -WRAP_WATCOM_FUNC2(long, load_frame, const char*, filename, fo::FrmFile**, frmPtr) +WRAP_WATCOM_FUNC2(long, load_frame, const char*, fileName, fo::FrmFile**, frmPtr) +WRAP_WATCOM_FUNC4(BYTE*, loadPCX, const char*, fileName, long*, width, long*, height, BYTE*, palette) WRAP_WATCOM_FUNC1(fo::Program*, loadProgram, const char*, fileName) WRAP_WATCOM_FUNC1(const char*, map_get_short_name, long, mapID) WRAP_WATCOM_FUNC2(void, MapDirErase, const char*, folder, const char*, ext) diff --git a/sfall/FalloutEngine/Structs.h b/sfall/FalloutEngine/Structs.h index 2fc4b809..b778ab22 100644 --- a/sfall/FalloutEngine/Structs.h +++ b/sfall/FalloutEngine/Structs.h @@ -406,62 +406,49 @@ struct ElevatorFrms { #pragma pack(push, 2) typedef class FrmHeaderData { // sizeof 62 public: - DWORD version; // version num - WORD fps; // frames per sec - WORD actionFrame; - WORD numFrames; // number of frames per direction - WORD xCentreShift[6]; // shift in the X direction, of frames with orientations [0-5] - WORD yCentreShift[6]; // shift in the Y direction, of frames with orientations [0-5] - DWORD oriOffset[6]; // offset of first frame for direction [0-5] from begining of frame area - DWORD frameAreaSize; // size of all frames area + long id; // 0x00 - version num? + short fps; // 0x04 - frames per sec + short actionFrame; // 0x06 - action frame index + short numFrames; // 0x08 - number of frames per direction + short xOffsets[6]; // 0x0A - shift in the X direction, of frames with orientations [0-5] + short yOffsets[6]; // 0x16 - shift in the Y direction, of frames with orientations [0-5] + long oriFrameOffset[6];// 0x22 - offset of first frame for direction [0-5] from begining of frame area + long frameAreaSize; // 0x3A - size of all frames area } FrmHeaderData; #pragma pack(pop) -// structures for holding frms loaded with fallout2 functions -typedef class FrmFrameData { // sizeof 12 + 1 byte -public: - WORD width; - WORD height; +struct FrmFrameHeader { // sizeof 12 byte + short width; + short height; DWORD size; // width * height - WORD x; - WORD y; - BYTE data[1]; // begin frame image data -} FrmFrameData; + short x; + short y; +}; -struct FrmFile { // sizeof 2954 - long id; // 0x00 - short fps; // 0x04 - short actionFrame; // 0x06 - short frames; // 0x08 - short xshift[6]; // 0x0A - short yshift[6]; // 0x16 - long oriFrameOffset[6]; // 0x22 - long frameAreaSize; // 0x3A - union { - FrmFrameData* frameData; - struct { - short width; // 0x3E - short height; // 0x40 - }; - }; - long frameSize; // 0x42 - short xoffset; // 0x46 - short yoffset; // 0x48 - union { // 0x4A - BYTE *pixelData; - BYTE pixels[80 * 36]; // for tiles FRM - }; +// structures for holding frms loaded with fallout2 functions +struct FrmFrameData : FrmFrameHeader { // sizeof 12 + 1 byte + BYTE data[1]; // begin frame image data +}; + +// for one frame +struct FrmData { + FrmHeaderData header; + FrmFrameData frame; +}; + +struct FrmFile : public FrmHeaderData { // sizeof 2954 + FrmFrameData frameData[1]; FrmFile() {}; // Returns a pointer to the data of the frame in the direction FrmFrameData* GetFrameData(long dir, long frame) { - BYTE* offsDirectionFrame = (BYTE*)&frameData; + BYTE* offsDirectionFrame = (BYTE*)&frameData[0]; if (dir > 0 && dir < 6) { offsDirectionFrame += oriFrameOffset[dir]; } if (frame > 0) { - int maxFrames = frames - 1; + int maxFrames = numFrames - 1; if (frame > maxFrames) frame = maxFrames; while (frame-- > 0) { offsDirectionFrame += ((FrmFrameData*)offsDirectionFrame)->size + (sizeof(FrmFrameData) - 1); @@ -471,7 +458,12 @@ struct FrmFile { // sizeof 2954 } }; -static_assert(sizeof(FrmFile) == 2954, "Incorrect FrmFile definition."); +struct TileFrmFile : public FrmHeaderData { + FrmFrameHeader frameHeader; + BYTE pixels[80 * 36]; +}; + +static_assert(sizeof(TileFrmFile) == 2954, "Incorrect TileFrmFile definition."); // structures for loading unlisted frms struct UnlistedFrm { diff --git a/sfall/FalloutEngine/VarPointers_def.h b/sfall/FalloutEngine/VarPointers_def.h index 8aff05a5..5a32338e 100644 --- a/sfall/FalloutEngine/VarPointers_def.h +++ b/sfall/FalloutEngine/VarPointers_def.h @@ -156,6 +156,7 @@ PTR_(optionsButtonUp1, DWORD) PTR_(optionsButtonUpKey, DWORD) PTRC(optnwin, DWORD) PTR_(outlined_object, fo::GameObject*) +PTR_(pal, BYTE) // array of 768 BYTE PTR_(partyMemberAIOptions, DWORD) PTR_(partyMemberCount, DWORD) PTR_(partyMemberLevelUpInfoList, DWORD*) diff --git a/sfall/IniReader.h b/sfall/IniReader.h index d374f350..5a161336 100644 --- a/sfall/IniReader.h +++ b/sfall/IniReader.h @@ -71,7 +71,7 @@ public: void init(); void clearCache(); - DWORD modifiedIni() { return _modifiedIni; } + DWORD modifiedIni() const { return _modifiedIni; } const char* getConfigFile(); void setDefaultConfigFile(); diff --git a/sfall/Modules/HeroAppearance.cpp b/sfall/Modules/HeroAppearance.cpp index 8259c4b6..353d0040 100644 --- a/sfall/Modules/HeroAppearance.cpp +++ b/sfall/Modules/HeroAppearance.cpp @@ -517,7 +517,7 @@ static void surface_draw(long width, long height, long fromWidth, long fromX, lo static void DrawBody(long critNum, BYTE* surface, long x, long y, long toWidth) { DWORD critFrmLock; - fo::FrmHeaderData *critFrm = fo::func::art_ptr_lock(BuildFrmId(1, critNum), &critFrmLock); + fo::FrmFile *critFrm = fo::func::art_ptr_lock(BuildFrmId(1, critNum), &critFrmLock); DWORD critWidth = fo::func::art_frame_width(critFrm, 0, charRotOri); DWORD critHeight = fo::func::art_frame_length(critFrm, 0, charRotOri); BYTE* critSurface = fo::func::art_frame_data(critFrm, 0, charRotOri); diff --git a/sfall/Modules/LoadOrder.cpp b/sfall/Modules/LoadOrder.cpp index 81f9c529..8a54418d 100644 --- a/sfall/Modules/LoadOrder.cpp +++ b/sfall/Modules/LoadOrder.cpp @@ -535,7 +535,7 @@ static void RemoveSavFiles() { static DWORD aliasFID = -1; -static void __declspec(naked) art_get_name_hook() { +static void __declspec(naked) art_alias_fid_hook() { __asm { call fo::funcoffs::art_alias_fid_; cmp eax, -1; @@ -650,7 +650,7 @@ void LoadOrder::init() { // Redefined behavior for replacing art aliases for critters // first check the existence of the art file of the current critter and then replace the art alias if file not found - HookCall(0x419440, art_get_name_hook); + HookCall(0x419440, art_alias_fid_hook); SafeWrite16(0x419521, 0x003B); // jmp 0x419560 if (IniReader::GetConfigInt("Misc", "EnableHeroAppearanceMod", 0) <= 0) { // Hero Appearance mod uses an alternative code MakeCall(0x419560, art_get_name_hack); diff --git a/sfall/Modules/Scripting/Arrays.h b/sfall/Modules/Scripting/Arrays.h index 11581615..3fadca2f 100644 --- a/sfall/Modules/Scripting/Arrays.h +++ b/sfall/Modules/Scripting/Arrays.h @@ -204,7 +204,7 @@ ScriptValue GetArrayKey(DWORD id, int index); ScriptValue GetArray(DWORD id, const ScriptValue& key); // set array element by index or key -void SetArray(DWORD id, const ScriptValue& key, const ScriptValue& val, bool allowUnset); +void SetArray(DWORD id, const ScriptValue& key, const ScriptValue& val, bool allowUnset = true); // number of elements in list or pairs in map int LenArray(DWORD id); diff --git a/sfall/Modules/Scripting/Handlers/Interface.cpp b/sfall/Modules/Scripting/Handlers/Interface.cpp index 0302fd92..5c04cbf8 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.cpp +++ b/sfall/Modules/Scripting/Handlers/Interface.cpp @@ -480,43 +480,90 @@ void mf_set_window_flag(OpcodeContext& ctx) { } } -static void __fastcall FreeArtFile(fo::FrmFile* frmPtr) { - if (frmPtr->id == 'PCX') { - __asm mov eax, frmPtr; - __asm mov eax, [eax]frmPtr.pixelData; - __asm call ds:[FO_VAR_freePtr]; - delete[] frmPtr; - } else { - __asm mov eax, frmPtr; - __asm call fo::funcoffs::my_free_; +// raw frame data loaded from FRM or PCX +struct FrameData { + BYTE* pixelData; + short width; + short height; + short xOffset; + short yOffset; + + // Empty data. + FrameData() : pixelData(nullptr), width(0), height(0), xOffset(0), yOffset(0) {} + + // Data from FRM file. + FrameData(fo::FrmFile* frmPtr, long direction, long frame) { + fo::FrmFrameData* frmData = frmPtr->GetFrameData(direction, frame); + pixelData = frmData->data; + width = frmData->width; + height = frmData->height; + xOffset = frmPtr->xOffsets[direction]; + yOffset = frmPtr->yOffsets[direction]; } + + // Data from PCX file. + FrameData(BYTE* data, long w, long h) { + pixelData = data; + width = (short)w; + height = (short)h; + } +}; + +static FrameData LoadPCXFile(const char* file) { + long w, h; + BYTE* pixelData = fo::func::loadPCX(file, &w, &h, fo::ptr::pal); + if (pixelData == nullptr) return FrameData(); + + fo::func::datafileConvertData(pixelData, fo::ptr::pal, w, h); + return FrameData(pixelData, w, h); } -static fo::FrmFile* LoadArtFile(const char* file, long frame, long direction, fo::FrmFrameData* &framePtr, bool checkPCX) { - fo::FrmFile* frmPtr = nullptr; - if (checkPCX) { - const char* pos = strrchr(file, '.'); - if (pos && _stricmp(++pos, "PCX") == 0) { - long w, h; - BYTE* data = fo::func::loadPCX(file, &w, &h); - if (!data) return nullptr; +static bool IsPCXFile(const char* file) { + const char* pos = strrchr(file, '.'); + return pos && _stricmp(++pos, "PCX") == 0; +} - frmPtr = reinterpret_cast(new BYTE[78]); - std::memset(frmPtr, 0, 74); +typedef std::unordered_map TFRMCache; +typedef std::unordered_map TPCXCache; - frmPtr->id = 'PCX'; - frmPtr->width = static_cast(w); - frmPtr->height = static_cast(h); - frmPtr->pixelData = data; - framePtr = frmPtr->GetFrameData(0, 0); - return frmPtr; +static TFRMCache frmFileCache; +static TPCXCache pcxFileCache; + +//static fo::FrmFile* LoadArtFileCached(const char* file, long frame, long direction, fo::FrmFrameData* &framePtr, bool checkPCX) { +static FrameData LoadFrameDataCached(const char* file, long frame, long direction) { + if (IsPCXFile(file)) { + TPCXCache::iterator cacheHit = pcxFileCache.find(file); + if (cacheHit != pcxFileCache.end()) { + return cacheHit->second; } + return pcxFileCache.insert(std::make_pair(file, LoadPCXFile(file))).first->second; } - if (fo::func::load_frame(file, &frmPtr)) { - return nullptr; + + fo::FrmFile* frmPtr = nullptr; + TFRMCache::iterator cacheHit = frmFileCache.find(file); + if (cacheHit != frmFileCache.end()) { + frmPtr = cacheHit->second; + } else { + if (fo::func::load_frame(file, &frmPtr)) { + frmPtr = nullptr; + } + frmFileCache.insert(std::make_pair(file, frmPtr)); } - framePtr = frmPtr->GetFrameData(direction, frame); - return frmPtr; + return (frmPtr != nullptr) + ? FrameData(frmPtr, direction, frame) + : FrameData(); +} + +void ClearInterfaceArtCache() { + for (TPCXCache::iterator it = pcxFileCache.begin(); it != pcxFileCache.end(); ++it) { + fo::func::freePtr_invoke(it->second.pixelData); + } + pcxFileCache.clear(); + + for (TFRMCache::iterator it = frmFileCache.begin(); it != frmFileCache.end(); ++it) { + fo::func::mem_free(it->second); + } + frmFileCache.clear(); } static long GetArtFIDFile(long fid, char* outFilePath) { @@ -543,69 +590,92 @@ static long GetArtFIDFile(long fid, char* outFilePath) { return direction; } +struct ArtCacheLock { + DWORD entryPtr; + + ArtCacheLock() :entryPtr(0) {} + ArtCacheLock(DWORD _lock) : entryPtr(_lock) {} + ~ArtCacheLock() { + if (entryPtr != 0) { + fo::func::art_ptr_unlock(entryPtr); + entryPtr = 0; + } + } +}; + +static FrameData LockFrameData(unsigned long fid, ArtCacheLock& lock, long direction, long frame) { + long objType = (fid >> 24) & 0xF; + if (direction < 0) { + // If direction is not specified, take it from FID. + direction = (objType == fo::OBJ_TYPE_CRITTER) + ? (fid >> 28) + : 0; + } else if (objType == fo::OBJ_TYPE_CRITTER) { + // Apply direction to FID. + fid = (direction << 28) | (fid & (0xFFFFFFF)); + } + return FrameData(fo::func::art_ptr_lock(fid, &lock.entryPtr), direction, frame); +} + static long DrawImage(OpcodeContext& ctx, bool isScaled) { if (!fo::func::selectWindowID(ctx.program()->currentScriptWin) || fo::var::getInt(FO_VAR_currentWindow) == -1) { ctx.printOpcodeError("%s() - no created or selected window.", ctx.getMetaruleName()); return 0; } - long direction = 0; - const char* file = nullptr; + FrameData frm; + ArtCacheLock cacheLock; bool isID = ctx.arg(0).isInt(); + long frame = ctx.arg(1).rawValue(); if (isID) { // art id long fid = ctx.arg(0).rawValue(); if (fid == -1) return -1; - char fileBuf[MAX_PATH]; - direction = GetArtFIDFile(fid, fileBuf); - file = fileBuf; + frm = LockFrameData(fid, cacheLock, -1, frame); + if (frm.pixelData == nullptr) { + ctx.printOpcodeError("%s() - cannot load art by FID: %d", ctx.getMetaruleName(), fid); + return -1; + } } else { - file = ctx.arg(0).strValue(); // path to frm/pcx file + const char* file = ctx.arg(0).strValue(); + frm = LoadFrameDataCached(file, frame, 0); + if (frm.pixelData == nullptr) { + ctx.printOpcodeError("%s() - cannot load art from file: %s", ctx.getMetaruleName(), file); + return -1; + } } - fo::FrmFrameData* framePtr; - fo::FrmFile* frmPtr = LoadArtFile(file, ctx.arg(1).rawValue(), direction, framePtr, !isID); - if (frmPtr == nullptr) { - ctx.printOpcodeError("%s() - cannot open the file: %s", ctx.getMetaruleName(), file); - return -1; - } long result = 1; - BYTE* pixelData = (frmPtr->id == 'PCX') ? frmPtr->pixelData : framePtr->data; - if (isScaled && ctx.numArgs() < 3) { - fo::func::displayInWindow(framePtr->width, framePtr->width, framePtr->height, pixelData); // scaled to window size (w/o transparent) + fo::func::displayInWindow(frm.width, frm.width, frm.height, frm.pixelData); // scaled to window size (w/o transparent) } else { int x = ctx.arg(2).rawValue(), y = ctx.arg(3).rawValue(); if (isScaled) { // draw to scale long s_width, s_height; if (ctx.numArgs() < 5) { - s_width = framePtr->width; - s_height = framePtr->height; + s_width = frm.width; + s_height = frm.height; } else { s_width = ctx.arg(4).rawValue(); s_height = (ctx.numArgs() > 5) ? ctx.arg(5).rawValue() : -1; } // scale with aspect ratio if w or h is set to -1 if (s_width <= -1 && s_height > 0) { - s_width = s_height * framePtr->width / framePtr->height; + s_width = s_height * frm.width / frm.height; } else if (s_height <= -1 && s_width > 0) { - s_height = s_width * framePtr->height / framePtr->width; + s_height = s_width * frm.height / frm.width; } if (s_width <= 0 || s_height <= 0) { - result = 0; - goto exit; + return 0; } long w_width = fo::func::windowWidth(); long xy_pos = (y * w_width) + x; - fo::func::window_trans_cscale(framePtr->width, framePtr->height, s_width, s_height, xy_pos, w_width, pixelData); // custom scaling + fo::func::window_trans_cscale(frm.width, frm.height, s_width, s_height, xy_pos, w_width, frm.pixelData); // custom scaling } else { // with x/y frame offsets - fo::func::windowDisplayBuf(x + frmPtr->xshift[direction], framePtr->width, y + frmPtr->yshift[direction], framePtr->height, pixelData, ctx.arg(4).rawValue()); + fo::func::windowDisplayBuf(x + frm.xOffset, frm.width, y + frm.yOffset, frm.height, frm.pixelData, ctx.arg(4).rawValue()); } } - -exit: - FreeArtFile(frmPtr); return result; } @@ -617,25 +687,50 @@ void mf_draw_image_scaled(OpcodeContext& ctx) { ctx.setReturn(DrawImage(ctx, true)); } +static DWORD GetArtFrameSize(OpcodeContext& ctx) { + long width, height; + bool isID = ctx.arg(0).isInt(); + if (isID) { // art id + long fid = ctx.arg(0).rawValue(); + if (fid == -1) return -1; + + // Get data directly from game's art cache. + DWORD lockPtr; + fo::FrmFile* art = fo::func::art_ptr_lock(fid, &lockPtr); + if (art == nullptr) { + ctx.printOpcodeError("%s() - cannot load art by FID: %d", ctx.getMetaruleName(), fid); + return -1; + } + DWORD result = fo::func::art_frame_width_length(art, ctx.arg(1).rawValue(), ctx.arg(2).rawValue(), &width, &height); + fo::func::art_ptr_unlock(lockPtr); + } else { + // Load data from DB. + const char* file = ctx.arg(0).strValue(); // path to frm/pcx file + FrameData frm = LoadFrameDataCached(file, ctx.arg(1).rawValue(), ctx.arg(2).rawValue()); + if (frm.pixelData == nullptr) { + ctx.printOpcodeError("%s() - cannot load art from file: %s", ctx.getMetaruleName(), file); + return -1; + } + width = frm.width; + height = frm.height; + } + + DWORD arrayId = CreateTempArray(4, 0); + SetArray(arrayId, 0, width); + SetArray(arrayId, 1, height); + return arrayId; +} + +void mf_art_frame_data(OpcodeContext& ctx) { + ctx.setReturn(GetArtFrameSize(ctx)); +} + static long InterfaceDrawImage(OpcodeContext& ctx, fo::Window* ifaceWin) { - const char* file = nullptr; bool useShift = false; long direction = -1, w = -1, h = -1; bool isID = ctx.arg(1).isInt(); - if (isID) { // art id - long fid = ctx.arg(1).rawValue(); - if (fid == -1) return -1; - - useShift = (((fid & 0xF000000) >> 24) == fo::OBJ_TYPE_CRITTER); - - char fileBuf[MAX_PATH]; - direction = GetArtFIDFile(fid, fileBuf); - file = fileBuf; - } else { - file = ctx.arg(1).strValue(); // path to frm/pcx file - } - + long frame = ctx.arg(4).rawValue(); if (ctx.numArgs() > 5) { // array params sArrayVar* sArray = GetRawArray(ctx.arg(5).rawValue()); if (sArray) { @@ -645,38 +740,50 @@ static long InterfaceDrawImage(OpcodeContext& ctx, fo::Window* ifaceWin) { if (size > 2) h = sArray->val[2].intVal; } } - long frame = ctx.arg(4).rawValue(); + ArtCacheLock cacheLock; + FrameData frm; + if (isID) { // art id + long fid = ctx.arg(1).rawValue(); + if (fid == -1) return -1; - fo::FrmFrameData* framePtr; - fo::FrmFile* frmPtr = LoadArtFile(file, frame, direction, framePtr, !isID); - if (frmPtr == nullptr) { - ctx.printOpcodeError("%s() - cannot open the file: %s", ctx.getMetaruleName(), file); - return -1; + useShift = (((fid & 0xF000000) >> 24) == fo::OBJ_TYPE_CRITTER); + + frm = LockFrameData(fid, cacheLock, direction, frame); + if (frm.pixelData == nullptr) { + ctx.printOpcodeError("%s() - cannot load art by FID: %d", ctx.getMetaruleName(), fid); + return -1; + } + } else { + const char* file = ctx.arg(1).strValue(); // path to frm/pcx file + frm = LoadFrameDataCached(file, frame, direction); + if (frm.pixelData == nullptr) { + ctx.printOpcodeError("%s() - load art from file: %s", ctx.getMetaruleName(), file); + return -1; + } } + int x = ctx.arg(2).rawValue(); int y = ctx.arg(3).rawValue(); if (useShift && direction >= 0) { - x += frmPtr->xshift[direction]; - y += frmPtr->yshift[direction]; + x += frm.xOffset; + y += frm.yOffset; } if (x < 0) x = 0; if (y < 0) y = 0; - int width = (w >= 0) ? w : framePtr->width; - int height = (h >= 0) ? h : framePtr->height; + int width = (w >= 0) ? w : frm.width; + int height = (h >= 0) ? h : frm.height; BYTE* surface = (ifaceWin->randY) ? WindowRender::GetOverlaySurface(ifaceWin) : ifaceWin->surface; - fo::func::trans_cscale(((frmPtr->id == 'PCX') ? frmPtr->pixelData : framePtr->data), framePtr->width, framePtr->height, framePtr->width, + fo::func::trans_cscale(frm.pixelData, frm.width, frm.height, frm.width, surface + (y * ifaceWin->width) + x, width, height, ifaceWin->width ); if (!(ctx.arg(0).rawValue() & 0x1000000)) { // is set to "Don't redraw" game::Render::GNW_win_refresh(ifaceWin, &ifaceWin->wRect, 0); } - - FreeArtFile(frmPtr); return 1; } diff --git a/sfall/Modules/Scripting/Handlers/Interface.h b/sfall/Modules/Scripting/Handlers/Interface.h index 0a4451ac..074ce07b 100644 --- a/sfall/Modules/Scripting/Handlers/Interface.h +++ b/sfall/Modules/Scripting/Handlers/Interface.h @@ -27,6 +27,8 @@ namespace script class OpcodeContext; +void ClearInterfaceArtCache(); + // input_functions void __declspec() op_input_funcs_available(); @@ -111,6 +113,8 @@ void mf_draw_image(OpcodeContext&); void mf_draw_image_scaled(OpcodeContext&); +void mf_art_frame_data(OpcodeContext&); + void mf_interface_art_draw(OpcodeContext&); void mf_get_window_attribute(OpcodeContext&); diff --git a/sfall/Modules/Scripting/Handlers/Metarule.cpp b/sfall/Modules/Scripting/Handlers/Metarule.cpp index 23e16679..8c2f279e 100644 --- a/sfall/Modules/Scripting/Handlers/Metarule.cpp +++ b/sfall/Modules/Scripting/Handlers/Metarule.cpp @@ -75,6 +75,7 @@ static const SfallMetarule metarules[] = { {"add_g_timer_event", mf_add_g_timer_event, 2, 2, -1, {ARG_INT, ARG_INT}}, {"add_trait", mf_add_trait, 1, 1, -1, {ARG_INT}}, {"art_cache_clear", mf_art_cache_flush, 0, 0}, + {"art_frame_data", mf_art_frame_data, 1, 3, 0, {ARG_INTSTR, ARG_INT, ARG_INT}}, {"attack_is_aimed", mf_attack_is_aimed, 0, 0}, {"car_gas_amount", mf_car_gas_amount, 0, 0}, {"combat_data", mf_combat_data, 0, 0}, diff --git a/sfall/Modules/Scripting/Opcodes.cpp b/sfall/Modules/Scripting/Opcodes.cpp index 19bbe055..a6fdffe3 100644 --- a/sfall/Modules/Scripting/Opcodes.cpp +++ b/sfall/Modules/Scripting/Opcodes.cpp @@ -277,6 +277,7 @@ void Opcodes::OnGameReset() { PipboyAvailableRestore(); ForceEncounterRestore(); // restore if the encounter did not happen ResetIniCache(); + ClearInterfaceArtCache(); } void Opcodes::InitNew() { diff --git a/sfall/Modules/Tiles.cpp b/sfall/Modules/Tiles.cpp index a86d096f..81920bb7 100644 --- a/sfall/Modules/Tiles.cpp +++ b/sfall/Modules/Tiles.cpp @@ -151,13 +151,13 @@ exit: } long listID = listPos - tiles->total; - fo::FrmFile frame; + fo::TileFrmFile frame; fo::func::db_fseek(artFile, 0, SEEK_SET); fo::func::db_freadByteCount(artFile, (BYTE*)&frame, 74); - frame.height = ByteSwapW(36); - frame.width = ByteSwapW(80); - frame.frameSize = ByteSwapD(80 * 36); + frame.frameHeader.height = ByteSwapW(36); + frame.frameHeader.width = ByteSwapW(80); + frame.frameHeader.size = ByteSwapD(80 * 36); frame.frameAreaSize = ByteSwapD(80 * 36 + 12); for (int y = 0; y < ySize; y++) {