From 6683eb5f581626b0ec4d81f6b2383ffc1c7a10be Mon Sep 17 00:00:00 2001 From: Karjala22 <78927981+Karjala22@users.noreply.github.com> Date: Wed, 9 Apr 2025 00:28:31 -0400 Subject: [PATCH 01/17] Fix for remaster darkpilo.cfg which is not located in the game folder. --- TheForceEngine/TFE_DarkForces/agent.cpp | 17 +++++++++++---- TheForceEngine/TFE_FileSystem/paths.cpp | 29 +++++++++++++++++++++++++ TheForceEngine/TFE_FileSystem/paths.h | 3 +++ TheForceEngine/main.cpp | 1 + 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/TheForceEngine/TFE_DarkForces/agent.cpp b/TheForceEngine/TFE_DarkForces/agent.cpp index 1a0a22396..58f7c268a 100644 --- a/TheForceEngine/TFE_DarkForces/agent.cpp +++ b/TheForceEngine/TFE_DarkForces/agent.cpp @@ -541,10 +541,19 @@ namespace TFE_DarkForces } else { - // Finally generate a new one. - TFE_System::logWrite(LOG_WARNING, "DarkForcesMain", "Cannot find 'DARKPILO.CFG' at '%s'. Creating a new file for save data.", sourcePath); -newpilo: - createDarkPilotConfig(documentsPath); + // Also check the remaster documents path. + TFE_Paths::appendPath(PATH_REMASTER_DOCS, "DARKPILO.CFG", sourcePath); + if (FileUtil::exists(sourcePath)) + { + FileUtil::copyFile(sourcePath, documentsPath); + } + else + { + // Finally generate a new one. + TFE_System::logWrite(LOG_WARNING, "DarkForcesMain", "Cannot find 'DARKPILO.CFG' at '%s'. Creating a new file for save data.", sourcePath); + newpilo: + createDarkPilotConfig(documentsPath); + } } } } diff --git a/TheForceEngine/TFE_FileSystem/paths.cpp b/TheForceEngine/TFE_FileSystem/paths.cpp index 2a688bcd7..774cac56c 100644 --- a/TheForceEngine/TFE_FileSystem/paths.cpp +++ b/TheForceEngine/TFE_FileSystem/paths.cpp @@ -123,6 +123,35 @@ namespace TFE_Paths return false; } + // This is the path to the Remaster documents folder. + bool setRemasterDocsPath(GameID game) + { + #ifdef _WIN32 + char path[TFE_MAX_PATH]; + + if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PROFILE, NULL, 0, path))) + { + // Append product-specific path + if (game == Game_Dark_Forces) + { + PathAppend(path, "Saved Games\\Nightdive Studios\\Dark Forces Remaster"); + } + else if (game == Game_Outlaws) + { + PathAppend(path, "Saved Games\\Nightdive Studios\\Outlaws Remaster"); + } + else + { + return false; + } + s_paths[PATH_REMASTER_DOCS] = path; + s_paths[PATH_REMASTER_DOCS] += "\\"; + } + return !s_paths[PATH_REMASTER_DOCS].empty(); + #endif + return false; + } + bool setProgramPath() { char path[TFE_MAX_PATH]; diff --git a/TheForceEngine/TFE_FileSystem/paths.h b/TheForceEngine/TFE_FileSystem/paths.h index 786419d28..9eadc8384 100644 --- a/TheForceEngine/TFE_FileSystem/paths.h +++ b/TheForceEngine/TFE_FileSystem/paths.h @@ -1,6 +1,7 @@ #pragma once #include "fileutil.h" #include +#include enum TFE_PathType { @@ -10,6 +11,7 @@ enum TFE_PathType PATH_SOURCE_DATA, // This is the location of the source data, such as maps, textures, etc. PATH_EMULATOR, // Path to the dosbox exe (for the editor). PATH_MOD, // Use this to reference mods. + PATH_REMASTER_DOCS, // Path for Remaster Document Folder. PATH_COUNT }; @@ -34,6 +36,7 @@ namespace TFE_Paths bool setProgramDataPath(const char* append); bool setUserDocumentsPath(const char* append); // Platform specific executable path. + bool setRemasterDocsPath(GameID game); bool setProgramPath(); // find a relative path in a TFE system directory. true if mapping was done. bool mapSystemPath(char *path); diff --git a/TheForceEngine/main.cpp b/TheForceEngine/main.cpp index 1582757a3..b06e47ebe 100644 --- a/TheForceEngine/main.cpp +++ b/TheForceEngine/main.cpp @@ -547,6 +547,7 @@ int main(int argc, char* argv[]) const TFE_GameHeader* gameHeader = TFE_Settings::getGameHeader(game->game); TFE_Paths::setPath(PATH_SOURCE_DATA, gameHeader->sourcePath); TFE_Paths::setPath(PATH_EMULATOR, gameHeader->emulatorPath); + TFE_Paths::setRemasterDocsPath(game->id); // Validate the current game path. validatePath(); From 85b21947e6fcf5f8e35d1866185b66dcb51ae702 Mon Sep 17 00:00:00 2001 From: Karjala22 <78927981+Karjala22@users.noreply.github.com> Date: Wed, 9 Apr 2025 00:56:24 -0400 Subject: [PATCH 02/17] Add for linux compatibility --- TheForceEngine/TFE_FileSystem/paths-posix.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/TheForceEngine/TFE_FileSystem/paths-posix.cpp b/TheForceEngine/TFE_FileSystem/paths-posix.cpp index 02a79f8d6..1e8079199 100644 --- a/TheForceEngine/TFE_FileSystem/paths-posix.cpp +++ b/TheForceEngine/TFE_FileSystem/paths-posix.cpp @@ -130,6 +130,18 @@ namespace TFE_Paths return true; } + bool setRemasterDocsPath(GameID game) + { + if (isPortableInstall()) + { + s_paths[PATH_REMASTER_DOCS] = s_paths[PATH_PROGRAM]; + return true; + } + + // TO DO - find out where (if?) the remaster docs are available on Linux + return false; + } + bool setProgramPath(void) { char p[TFE_MAX_PATH]; From d3ed1cb152c97dc2edebc33e9a27cd615373a8d8 Mon Sep 17 00:00:00 2001 From: Karjala22 <78927981+Karjala22@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:24:29 -0400 Subject: [PATCH 03/17] Update Linux pathing. --- TheForceEngine/TFE_FileSystem/paths-posix.cpp | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/TheForceEngine/TFE_FileSystem/paths-posix.cpp b/TheForceEngine/TFE_FileSystem/paths-posix.cpp index 1e8079199..a4906954a 100644 --- a/TheForceEngine/TFE_FileSystem/paths-posix.cpp +++ b/TheForceEngine/TFE_FileSystem/paths-posix.cpp @@ -3,6 +3,7 @@ #include "paths.h" #include "fileutil.h" #include "filestream.h" +#include #include #include #include @@ -130,6 +131,8 @@ namespace TFE_Paths return true; } + // There is no official remaster support on linux + // Lets approximate proton save locations. bool setRemasterDocsPath(GameID game) { if (isPortableInstall()) @@ -137,9 +140,23 @@ namespace TFE_Paths s_paths[PATH_REMASTER_DOCS] = s_paths[PATH_PROGRAM]; return true; } + + string id = to_string(c_steamRemasterProductId[game]); + string append = "steamapps/compatdata/" + id + "/pfx/drive_c/users/steamuser/Saved Games/Nightdive Studios"; - // TO DO - find out where (if?) the remaster docs are available on Linux - return false; + if (id == Game_Dark_Forces) + { + append += "/Dark Forces Remaster/"; + } + else if (id == Game_Outlaws) + { + append += "/Outlaws Remaster/"; + } + { + return false; + } + s_paths[PATH_REMASTER_DOCS] = append; + return !s_paths[PATH_REMASTER_DOCS].empty(); } bool setProgramPath(void) From 8241d5f5d150a23f3b686e8cc875e8dfceacb192 Mon Sep 17 00:00:00 2001 From: Karjala22 <78927981+Karjala22@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:44:26 -0400 Subject: [PATCH 04/17] fix for nix paths --- TheForceEngine/TFE_FileSystem/paths-posix.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/TheForceEngine/TFE_FileSystem/paths-posix.cpp b/TheForceEngine/TFE_FileSystem/paths-posix.cpp index a4906954a..dfcabcfea 100644 --- a/TheForceEngine/TFE_FileSystem/paths-posix.cpp +++ b/TheForceEngine/TFE_FileSystem/paths-posix.cpp @@ -140,15 +140,14 @@ namespace TFE_Paths s_paths[PATH_REMASTER_DOCS] = s_paths[PATH_PROGRAM]; return true; } - - string id = to_string(c_steamRemasterProductId[game]); - string append = "steamapps/compatdata/" + id + "/pfx/drive_c/users/steamuser/Saved Games/Nightdive Studios"; + string strId = to_string((int)TFE_Settings::c_steamRemasterProductId[game]); + string append = "steamapps/compatdata/" + strId + "/pfx/drive_c/users/steamuser/Saved Games/Nightdive Studios"; - if (id == Game_Dark_Forces) + if (game == Game_Dark_Forces) { append += "/Dark Forces Remaster/"; } - else if (id == Game_Outlaws) + else if (game == Game_Outlaws) { append += "/Outlaws Remaster/"; } From 1babf9159ca44c51f76a96e821c81327476dac66 Mon Sep 17 00:00:00 2001 From: Karjala22 <78927981+Karjala22@users.noreply.github.com> Date: Wed, 9 Apr 2025 20:16:20 -0400 Subject: [PATCH 05/17] change the path path based on testing on proton. --- TheForceEngine/TFE_FileSystem/paths-posix.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/TheForceEngine/TFE_FileSystem/paths-posix.cpp b/TheForceEngine/TFE_FileSystem/paths-posix.cpp index dfcabcfea..1901010b9 100644 --- a/TheForceEngine/TFE_FileSystem/paths-posix.cpp +++ b/TheForceEngine/TFE_FileSystem/paths-posix.cpp @@ -141,7 +141,7 @@ namespace TFE_Paths return true; } string strId = to_string((int)TFE_Settings::c_steamRemasterProductId[game]); - string append = "steamapps/compatdata/" + strId + "/pfx/drive_c/users/steamuser/Saved Games/Nightdive Studios"; + string append = "~/.steam/steam/steamapps/compatdata/" + strId + "/pfx/drive_c/users/steamuser/Saved Games/Nightdive Studios"; if (game == Game_Dark_Forces) { @@ -151,6 +151,7 @@ namespace TFE_Paths { append += "/Outlaws Remaster/"; } + else { return false; } From a7c89ded0eed82d3f6e517ed8f16ce27c94c8c95 Mon Sep 17 00:00:00 2001 From: luciusDXL Date: Sat, 13 Sep 2025 12:49:56 -0700 Subject: [PATCH 06/17] * Level Script now setup properly after loading from save file. * Inf Script Calls are now serialized. --- .../TFE_DarkForces/darkForcesMain.cpp | 6 ++ .../TFE_Jedi/InfSystem/infState.cpp | 65 ++++++++++++++++++- .../TFE_Jedi/InfSystem/infSystem.cpp | 13 +++- TheForceEngine/TFE_Jedi/InfSystem/infSystem.h | 1 + .../TFE_Jedi/InfSystem/infTypesInternal.h | 9 ++- TheForceEngine/TFE_Jedi/Level/level.cpp | 2 +- .../TFE_Jedi/Serialization/serialization.cpp | 43 ++++++++++++ .../TFE_Jedi/Serialization/serialization.h | 1 + 8 files changed, 134 insertions(+), 6 deletions(-) diff --git a/TheForceEngine/TFE_DarkForces/darkForcesMain.cpp b/TheForceEngine/TFE_DarkForces/darkForcesMain.cpp index 166539ad8..ac3cc4b34 100644 --- a/TheForceEngine/TFE_DarkForces/darkForcesMain.cpp +++ b/TheForceEngine/TFE_DarkForces/darkForcesMain.cpp @@ -1617,6 +1617,12 @@ namespace TFE_DarkForces // TFE - Scripting. serialization_setVersion(curVersion); TFE_ForceScript::serialize(stream); + if (!writeState) + { + // Setup the level script after script serialization and fixup INF function pointers. + loadLevelScript(); + inf_fixupScriptCalls(); + } TFE_System::messages_serialize(stream); diff --git a/TheForceEngine/TFE_Jedi/InfSystem/infState.cpp b/TheForceEngine/TFE_Jedi/InfSystem/infState.cpp index 2e4f1c308..fe13d1d83 100644 --- a/TheForceEngine/TFE_Jedi/InfSystem/infState.cpp +++ b/TheForceEngine/TFE_Jedi/InfSystem/infState.cpp @@ -15,13 +15,16 @@ namespace TFE_Jedi enum InfStateVersion : u32 { InfState_InitVersion = 1, - InfState_CurVersion = InfState_InitVersion, + InfState_ScriptCall, + InfState_CurVersion = InfState_ScriptCall, }; // INF State InfSerializableState s_infSerState = { }; InfState s_infState = { }; + static std::vector s_infScriptCallsToFixup; + ///////////////////////////////////////////// // Forward Declarations ///////////////////////////////////////////// @@ -493,6 +496,8 @@ namespace TFE_Jedi void inf_serialize(Stream* stream) { + s_infScriptCallsToFixup.clear(); + SERIALIZE_VERSION(InfState_CurVersion); s32 elevCount, teleCount, trigCount; @@ -627,6 +632,32 @@ namespace TFE_Jedi SERIALIZE(InfState_InitVersion, msg->arg2, 0); } + void inf_serializeSciptCall(Stream* stream, Stop* stop, InfScriptCall* call) + { + SERIALIZE(InfState_ScriptCall, call->argCount, 0); + for (s32 i = 0; i < call->argCount; i++) + { + serialization_serializeScriptArg(stream, InfState_ScriptCall, &call->args[i]); + } + SERIALIZE_CSTRING(InfState_ScriptCall, call->funcName); + if (serialization_getMode() == SMODE_READ) + { + call->funcPtr = nullptr; + s_infScriptCallsToFixup.push_back(call); + } + } + + void inf_fixupScriptCalls() + { + const s32 count = (s32)s_infScriptCallsToFixup.size(); + for (s32 i = 0; i < count; i++) + { + InfScriptCall* call = s_infScriptCallsToFixup[i]; + call->funcPtr = getLevelScriptFunc(call->funcName); + } + s_infScriptCallsToFixup.clear(); + } + void inf_serializeAdjoin(Stream* stream, Stop* stop, AdjoinCmd* adjCmd) { serialization_serializeSectorPtr(stream, InfState_InitVersion, adjCmd->sector0); @@ -684,6 +715,35 @@ namespace TFE_Jedi } } + // Script Calls + s32 scriptCallCount = 0; + if (serialization_getMode() == SMODE_WRITE) + { + scriptCallCount = allocator_getCount(stop->scriptCalls); + } + SERIALIZE(InfState_ScriptCall, scriptCallCount, 0); + if (serialization_getMode() == SMODE_WRITE) + { + allocator_saveIter(stop->scriptCalls); + InfScriptCall* call = (InfScriptCall*)allocator_getHead(stop->scriptCalls); + while (call) + { + inf_serializeSciptCall(stream, stop, call); + call = (InfScriptCall*)allocator_getNext(stop->scriptCalls); + } + allocator_restoreIter(stop->scriptCalls); + } + else if (scriptCallCount > 0) // SMODE_READ + { + stop->scriptCalls = allocator_create(sizeof(InfScriptCall)); + for (s32 c = 0; c < scriptCallCount; c++) + { + InfScriptCall* call = (InfScriptCall*)allocator_newItem(stop->scriptCalls); + if (!call) { return; } + inf_serializeSciptCall(stream, stop, call); + } + } + // Adjoin Commands s32 adjCount; if (serialization_getMode() == SMODE_WRITE) @@ -708,8 +768,7 @@ namespace TFE_Jedi for (s32 a = 0; a < adjCount; a++) { AdjoinCmd* adjCmd = (AdjoinCmd*)allocator_newItem(stop->adjoinCmds); - if (!adjCmd) - return; + if (!adjCmd) { return; } inf_serializeAdjoin(stream, stop, adjCmd); } } diff --git a/TheForceEngine/TFE_Jedi/InfSystem/infSystem.cpp b/TheForceEngine/TFE_Jedi/InfSystem/infSystem.cpp index 7a316ec91..452238b86 100644 --- a/TheForceEngine/TFE_Jedi/InfSystem/infSystem.cpp +++ b/TheForceEngine/TFE_Jedi/InfSystem/infSystem.cpp @@ -2375,7 +2375,8 @@ namespace TFE_Jedi Stop* stop = inf_getStopByIndex(elev, index); if (stop) { - TFE_ForceScript::FunctionHandle func = getLevelScriptFunc(s_infArg1); + std::string funcName = s_infArg1; + TFE_ForceScript::FunctionHandle func = getLevelScriptFunc(funcName.c_str()); if (func) { if (!stop->scriptCalls) @@ -2388,6 +2389,16 @@ namespace TFE_Jedi // Up to 4 arguments. scriptCall->argCount = inf_parseScriptCallArg(elev, stop, scriptCall->args, argCount - 3, s_infArg2, s_infArg3, s_infArg4, s_infArgExtra); scriptCall->funcPtr = func; + // Copy the name, zero out any unused space to avoid saving to disk. + memset(scriptCall->funcName, 0, MAX_SCRIPT_CALL_NAME_LEN); + if (funcName.length() >= MAX_SCRIPT_CALL_NAME_LEN) + { + TFE_System::logWrite(LOG_ERROR, "InfSerialization", "Function name %s too long, the limit is 63 characters.", funcName.c_str()); + } + else + { + strcpy(scriptCall->funcName, funcName.c_str()); + } } } } break; diff --git a/TheForceEngine/TFE_Jedi/InfSystem/infSystem.h b/TheForceEngine/TFE_Jedi/InfSystem/infSystem.h index 156da8494..29564d869 100644 --- a/TheForceEngine/TFE_Jedi/InfSystem/infSystem.h +++ b/TheForceEngine/TFE_Jedi/InfSystem/infSystem.h @@ -36,6 +36,7 @@ namespace TFE_Jedi // Serialization & State void inf_clearState(); void inf_serialize(Stream* stream); + void inf_fixupScriptCalls(); // ** Runtime API ** // Messages are the way entities and the player interact with the INF system during gameplay. diff --git a/TheForceEngine/TFE_Jedi/InfSystem/infTypesInternal.h b/TheForceEngine/TFE_Jedi/InfSystem/infTypesInternal.h index da5ee2292..46b88587a 100644 --- a/TheForceEngine/TFE_Jedi/InfSystem/infTypesInternal.h +++ b/TheForceEngine/TFE_Jedi/InfSystem/infTypesInternal.h @@ -83,6 +83,12 @@ namespace TFE_Jedi TELEPORT_CHUTE = 1 }; + enum ScriptCallConst + { + MAX_SCRIPT_CALL_NAME_LEN = 64, + MAX_SCRIPT_CALL_ARG = 5, + }; + struct Teleport { RSector* sector; @@ -150,7 +156,8 @@ namespace TFE_Jedi { void* funcPtr; s32 argCount; - TFE_ForceScript::ScriptArg args[5]; + TFE_ForceScript::ScriptArg args[MAX_SCRIPT_CALL_ARG]; + char funcName[MAX_SCRIPT_CALL_NAME_LEN]; // Needed for serialization. }; struct Slave diff --git a/TheForceEngine/TFE_Jedi/Level/level.cpp b/TheForceEngine/TFE_Jedi/Level/level.cpp index cbbf38337..9d5ada3a5 100644 --- a/TheForceEngine/TFE_Jedi/Level/level.cpp +++ b/TheForceEngine/TFE_Jedi/Level/level.cpp @@ -1131,7 +1131,7 @@ namespace TFE_Jedi TFE_ForceScript::FunctionHandle getLevelScriptFunc(const char* funcName) { - if (!s_levelState.levelScript) { return nullptr; } + if (!s_levelState.levelScript || !funcName) { return nullptr; } return TFE_ForceScript::findScriptFuncByNameNoCase(s_levelState.levelScript, funcName); } } diff --git a/TheForceEngine/TFE_Jedi/Serialization/serialization.cpp b/TheForceEngine/TFE_Jedi/Serialization/serialization.cpp index b9a7acaf3..870291d0d 100644 --- a/TheForceEngine/TFE_Jedi/Serialization/serialization.cpp +++ b/TheForceEngine/TFE_Jedi/Serialization/serialization.cpp @@ -151,4 +151,47 @@ namespace TFE_Jedi frame = (id < 0) ? nullptr : TFE_Sprite_Jedi::getFrameByIndex(ID_GET_INDEX(id), ID_GET_POOL(id)); } } + + void serialization_serializeScriptArg(Stream* stream, u32 version, TFE_ForceScript::ScriptArg* arg) + { + const bool write = s_sMode == SMODE_WRITE; + const bool read = !write; + + // Use a fixed type rather than relying on the enum type. + s32 type = write ? s32(arg->type) : 0; + SERIALIZE(version, type, 0); + if (read) { arg->type = TFE_ForceScript::ScriptArgType(type); } + + switch (arg->type) + { + case TFE_ForceScript::ARG_S32: + SERIALIZE(version, arg->iValue, 0); + break; + case TFE_ForceScript::ARG_U32: + SERIALIZE(version, arg->uValue, 0); + break; + case TFE_ForceScript::ARG_F32: + SERIALIZE(version, arg->fValue, 0.0f); + break; + case TFE_ForceScript::ARG_BOOL: + SERIALIZE(version, arg->bValue, false); + break; + case TFE_ForceScript::ARG_OBJECT: + // Not yet supported. + TFE_System::logWrite(LOG_ERROR, "ScriptArg Serialization", "Cannot serialize type \"object\""); + break; + case TFE_ForceScript::ARG_STRING: + SERIALIZE_STRING(version, arg->stdStr); + break; + case TFE_ForceScript::ARG_FLOAT2: + SERIALIZE_BUF(version, arg->float2Value.m, sizeof(Vec2f)); + break; + case TFE_ForceScript::ARG_FLOAT3: + SERIALIZE_BUF(version, arg->float3Value.m, sizeof(Vec3f)); + break; + case TFE_ForceScript::ARG_FLOAT4: + SERIALIZE_BUF(version, arg->float4Value.m, sizeof(Vec4f)); + break; + } + } } \ No newline at end of file diff --git a/TheForceEngine/TFE_Jedi/Serialization/serialization.h b/TheForceEngine/TFE_Jedi/Serialization/serialization.h index 189bc3da2..4f61cce78 100644 --- a/TheForceEngine/TFE_Jedi/Serialization/serialization.h +++ b/TheForceEngine/TFE_Jedi/Serialization/serialization.h @@ -110,4 +110,5 @@ namespace TFE_Jedi void serialization_serialize3doPtr(Stream* stream, u32 version, JediModel*& model); void serialization_serializeWaxPtr(Stream* stream, u32 version, JediWax*& wax); void serialization_serializeFramePtr(Stream* stream, u32 version, JediFrame*& frame); + void serialization_serializeScriptArg(Stream* stream, u32 version, TFE_ForceScript::ScriptArg* arg); } \ No newline at end of file From 16b0710624776d7fb1998a7dab8f0942fa2b6371 Mon Sep 17 00:00:00 2001 From: jerethk <82040941+jerethk@users.noreply.github.com> Date: Sun, 14 Sep 2025 03:31:31 +1000 Subject: [PATCH 07/17] Send messages from ForceScript (#564) * Enable sending messages to a sector via forcescript * Enable sending message to object * Move the message enum to gs_game Makes more sense there --- .../TFE_DarkForces/Scripting/gs_game.cpp | 12 +++++++ .../TFE_DarkForces/Scripting/scriptObject.cpp | 10 ++++++ .../TFE_DarkForces/Scripting/scriptSector.cpp | 33 +++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/TheForceEngine/TFE_DarkForces/Scripting/gs_game.cpp b/TheForceEngine/TFE_DarkForces/Scripting/gs_game.cpp index 7f564b3bc..e90e1e199 100644 --- a/TheForceEngine/TFE_DarkForces/Scripting/gs_game.cpp +++ b/TheForceEngine/TFE_DarkForces/Scripting/gs_game.cpp @@ -31,6 +31,18 @@ namespace TFE_DarkForces { ScriptClassBegin("Game", "game", api); { + // Enums + ScriptEnumRegister("MessageType"); + ScriptEnum("M_TRIGGER", MSG_TRIGGER); + ScriptEnum("NEXT_STOP", MSG_NEXT_STOP); + ScriptEnum("PREV_STOP", MSG_PREV_STOP); + // ScriptEnum("GOTO_STOP", MSG_GOTO_STOP); // GOTO_STOP requires a parameter, not yet implemented + ScriptEnum("DONE", MSG_DONE); + ScriptEnum("WAKEUP", MSG_WAKEUP); + ScriptEnum("MASTER_ON", MSG_MASTER_ON); + ScriptEnum("MASTER_OFF", MSG_MASTER_OFF); + ScriptEnum("CRUSH", MSG_CRUSH); + // Functions ScriptObjMethod("float getGameTime()", getGameTime); ScriptObjMethod("int random(int)", scriptRandom); diff --git a/TheForceEngine/TFE_DarkForces/Scripting/scriptObject.cpp b/TheForceEngine/TFE_DarkForces/Scripting/scriptObject.cpp index cf4373c57..923092fbc 100644 --- a/TheForceEngine/TFE_DarkForces/Scripting/scriptObject.cpp +++ b/TheForceEngine/TFE_DarkForces/Scripting/scriptObject.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -277,6 +278,14 @@ namespace TFE_DarkForces if (s_nightVisionActive) { disableNightVision(); } } + void sendMessageToObject(MessageType messageType, ScriptObject* sObject) + { + if (!doesObjectExist(sObject)) { return; } + + SecObject* obj = TFE_Jedi::s_objectRefList[sObject->m_id].object; + message_sendToObj(obj, messageType, actor_messageFunc); + } + void ScriptObject::registerType() { s32 res = 0; @@ -314,5 +323,6 @@ namespace TFE_DarkForces ScriptObjFunc("void delete()", deleteObject); ScriptObjFunc("void addLogic(string)", addLogicToObject); ScriptObjFunc("void setCamera()", setCamera); + ScriptObjFunc("void sendMessage(int)", sendMessageToObject) } } diff --git a/TheForceEngine/TFE_DarkForces/Scripting/scriptSector.cpp b/TheForceEngine/TFE_DarkForces/Scripting/scriptSector.cpp index c74c6fe72..7b3994024 100644 --- a/TheForceEngine/TFE_DarkForces/Scripting/scriptSector.cpp +++ b/TheForceEngine/TFE_DarkForces/Scripting/scriptSector.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include using namespace TFE_Jedi; @@ -169,6 +170,33 @@ namespace TFE_DarkForces } } + // Message with event and arg, eg. GOTO_STOP 131072 2 + void sendMessageToSector(MessageType messageType, u32 evt, u32 msgArg1, ScriptSector* sSector) + { + if (!isScriptSectorValid(sSector)) + { + return; + } + + // TODO: investigate how to do this safely + // s_msgArg1 = msgArg1; + + RSector* sector = &s_levelState.sectors[sSector->m_id]; + message_sendToSector(sector, nullptr, evt, messageType); + } + + // Message with no event and no arg, eg. NEXT_STOP + void sendMessageToSector1(MessageType messageType, ScriptSector* sSector) + { + sendMessageToSector(messageType, 0, 0, sSector); + } + + // Message with event, eg. NEXT_STOP 131072 + void sendMessageToSector2(MessageType messageType, u32 evt, ScriptSector* sSector) + { + sendMessageToSector(messageType, evt, 0, sSector); + } + void ScriptSector::registerType() { s32 res = 0; @@ -185,6 +213,11 @@ namespace TFE_DarkForces ScriptObjFunc("void setFlag(int, uint)", setSectorFlag); ScriptObjFunc("float2 getCenterXZ()", getCenterXZ); ScriptObjFunc("Wall getWall(int)", getWall); + + ScriptObjFunc("void sendMessage(int)", sendMessageToSector1); + ScriptObjFunc("void sendMessage(int, uint)", sendMessageToSector2); + //ScriptObjFunc("void sendMessage(int, uint, uint)", sendMessageToSector); // For now do not expose the ability to pass an arg + // Properties ScriptPropertyGetFunc("float get_floorHeight()", getFloorHeight); ScriptPropertyGetFunc("float get_ceilHeight()", getCeilHeight); From 38b8fc01c0dbe44e8e3092b4137badca810a0126 Mon Sep 17 00:00:00 2001 From: luciusDXL Date: Sat, 13 Sep 2025 13:11:53 -0700 Subject: [PATCH 08/17] * Make Script Arguments fixed size to work with Dark Forces allocators. * This means that string arguments are limited to 31 characters. --- TheForceEngine/TFE_ForceScript/forceScript.cpp | 3 ++- TheForceEngine/TFE_ForceScript/forceScript.h | 6 +++--- TheForceEngine/TFE_Jedi/InfSystem/infSystem.cpp | 4 +++- TheForceEngine/TFE_Jedi/Level/level.cpp | 2 +- TheForceEngine/TFE_Jedi/Serialization/serialization.cpp | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/TheForceEngine/TFE_ForceScript/forceScript.cpp b/TheForceEngine/TFE_ForceScript/forceScript.cpp index 89fa5d2aa..9e4ececc3 100644 --- a/TheForceEngine/TFE_ForceScript/forceScript.cpp +++ b/TheForceEngine/TFE_ForceScript/forceScript.cpp @@ -919,7 +919,8 @@ namespace TFE_ForceScript } break; case ARG_STRING: { - context->SetArgObject(i, (void*)&arg[i].stdStr); + std::string stdStr(arg[i].strValue); + context->SetArgObject(i, (void*)&stdStr); } break; case ARG_FLOAT2: { diff --git a/TheForceEngine/TFE_ForceScript/forceScript.h b/TheForceEngine/TFE_ForceScript/forceScript.h index 34a2bbfd3..a750f0a15 100644 --- a/TheForceEngine/TFE_ForceScript/forceScript.h +++ b/TheForceEngine/TFE_ForceScript/forceScript.h @@ -54,8 +54,8 @@ namespace TFE_ForceScript Vec2f float2Value; Vec3f float3Value; Vec4f float4Value; + char strValue[32]; }; - std::string stdStr; }; // Opaque Handles. @@ -109,8 +109,8 @@ namespace TFE_ForceScript inline ScriptArg scriptArg(u32 value) { ScriptArg arg; arg.type = ARG_U32; arg.uValue = value; return arg; } inline ScriptArg scriptArg(f32 value) { ScriptArg arg; arg.type = ARG_F32; arg.fValue = value; return arg; } inline ScriptArg scriptArg(bool value) { ScriptArg arg; arg.type = ARG_BOOL; arg.bValue = value; return arg; } - inline ScriptArg scriptArg(const std::string& value) { ScriptArg arg; arg.type = ARG_STRING; arg.stdStr = value; return arg; } - inline ScriptArg scriptArg(const char* value) { ScriptArg arg; arg.type = ARG_STRING; arg.stdStr = value; return arg; } + inline ScriptArg scriptArg(const std::string& value) { ScriptArg arg; arg.type = ARG_STRING; strcpy(arg.strValue, value.c_str()); return arg; } + inline ScriptArg scriptArg(const char* value) { ScriptArg arg; arg.type = ARG_STRING; strcpy(arg.strValue, value); return arg; } inline ScriptArg scriptArg(Vec2f value) { ScriptArg arg; arg.type = ARG_FLOAT2; arg.float2Value = value; return arg; } inline ScriptArg scriptArg(Vec3f value) { ScriptArg arg; arg.type = ARG_FLOAT3; arg.float3Value = value; return arg; } inline ScriptArg scriptArg(Vec4f value) { ScriptArg arg; arg.type = ARG_FLOAT4; arg.float4Value = value; return arg; } diff --git a/TheForceEngine/TFE_Jedi/InfSystem/infSystem.cpp b/TheForceEngine/TFE_Jedi/InfSystem/infSystem.cpp index 452238b86..29af69a2c 100644 --- a/TheForceEngine/TFE_Jedi/InfSystem/infSystem.cpp +++ b/TheForceEngine/TFE_Jedi/InfSystem/infSystem.cpp @@ -2034,7 +2034,9 @@ namespace TFE_Jedi memcpy(unquotedStr, &value[1], newLen); unquotedStr[newLen] = 0; - arg[argCount].stdStr = std::string(unquotedStr); + // Force string to fixed-size. + unquotedStr[31] = 0; + strcpy(arg[argCount].strValue, unquotedStr); arg[argCount].type = TFE_ForceScript::ARG_STRING; argCount++; } diff --git a/TheForceEngine/TFE_Jedi/Level/level.cpp b/TheForceEngine/TFE_Jedi/Level/level.cpp index 9d5ada3a5..f0edb9927 100644 --- a/TheForceEngine/TFE_Jedi/Level/level.cpp +++ b/TheForceEngine/TFE_Jedi/Level/level.cpp @@ -1108,7 +1108,7 @@ namespace TFE_Jedi { TFE_ForceScript::ScriptArg arg; arg.type = TFE_ForceScript::ARG_STRING; - arg.stdStr = levelName; + strcpy(arg.strValue, levelName); TFE_ForceScript::execFunc(s_levelState.levelScriptStart, 1, &arg); } } diff --git a/TheForceEngine/TFE_Jedi/Serialization/serialization.cpp b/TheForceEngine/TFE_Jedi/Serialization/serialization.cpp index 870291d0d..c3fe8cbd2 100644 --- a/TheForceEngine/TFE_Jedi/Serialization/serialization.cpp +++ b/TheForceEngine/TFE_Jedi/Serialization/serialization.cpp @@ -181,7 +181,7 @@ namespace TFE_Jedi TFE_System::logWrite(LOG_ERROR, "ScriptArg Serialization", "Cannot serialize type \"object\""); break; case TFE_ForceScript::ARG_STRING: - SERIALIZE_STRING(version, arg->stdStr); + SERIALIZE_CSTRING(version, arg->strValue); break; case TFE_ForceScript::ARG_FLOAT2: SERIALIZE_BUF(version, arg->float2Value.m, sizeof(Vec2f)); From 30d9c6dd62ea2dc46479c930f3ab34f42b3e35d9 Mon Sep 17 00:00:00 2001 From: luciusDXL Date: Sat, 13 Sep 2025 13:42:20 -0700 Subject: [PATCH 09/17] Missing include. --- TheForceEngine/TFE_ForceScript/forceScript.h | 1 + 1 file changed, 1 insertion(+) diff --git a/TheForceEngine/TFE_ForceScript/forceScript.h b/TheForceEngine/TFE_ForceScript/forceScript.h index a750f0a15..60a8f6061 100644 --- a/TheForceEngine/TFE_ForceScript/forceScript.h +++ b/TheForceEngine/TFE_ForceScript/forceScript.h @@ -6,6 +6,7 @@ #include #include #include +#include #include namespace TFE_ForceScript From 9cd351892185ebda6979a5cbfa9c34afbaa4e0f7 Mon Sep 17 00:00:00 2001 From: luciusDXL Date: Sat, 13 Sep 2025 14:07:43 -0700 Subject: [PATCH 10/17] * Fixed smooth delta time at high framerates. It turns out that even with fractional ticks, there is still a minimum frame length needed to avoid precision issues. --- TheForceEngine/TFE_Jedi/Task/task.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TheForceEngine/TFE_Jedi/Task/task.cpp b/TheForceEngine/TFE_Jedi/Task/task.cpp index 3dfda9279..68df76acf 100644 --- a/TheForceEngine/TFE_Jedi/Task/task.cpp +++ b/TheForceEngine/TFE_Jedi/Task/task.cpp @@ -565,6 +565,12 @@ namespace TFE_Jedi { return JFALSE; } + // Timing still breaks down if the frames are too short - even with fractional ticks. + if (time - s_prevTime < 0.5f * s_minIntervalInSec) + { + return JFALSE; + } + s_prevTime = time; s_currentMsg = MSG_RUN_TASK; s_frameActiveTaskCount = 0; From d7e2b10226b7905c425a911caf88d3354819502f Mon Sep 17 00:00:00 2001 From: Karjala22 <78927981+Karjala22@users.noreply.github.com> Date: Thu, 18 Sep 2025 14:30:31 -0400 Subject: [PATCH 11/17] Add secret count to the UI and allow for hud centering. --- TheForceEngine/TFE_DarkForces/GameUI/pda.cpp | 10 +++- TheForceEngine/TFE_DarkForces/hud.cpp | 23 ++++++++- TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp | 43 +++++++++++----- TheForceEngine/TFE_Settings/settings.cpp | 50 +++++++++++-------- TheForceEngine/TFE_Settings/settings.h | 6 ++- TheForceEngine/TheForceEngine.vcxproj | 18 +++---- .../x64/Debug/TheForceEngine.exe.recipe | 11 ++++ 7 files changed, 115 insertions(+), 46 deletions(-) create mode 100644 TheForceEngine/x64/Debug/TheForceEngine.exe.recipe diff --git a/TheForceEngine/TFE_DarkForces/GameUI/pda.cpp b/TheForceEngine/TFE_DarkForces/GameUI/pda.cpp index c17baf288..7a546aa61 100644 --- a/TheForceEngine/TFE_DarkForces/GameUI/pda.cpp +++ b/TheForceEngine/TFE_DarkForces/GameUI/pda.cpp @@ -835,7 +835,15 @@ namespace TFE_DarkForces char secretStr[32]; LRect rect; - sprintf(secretStr, "%2d%%", secretPercentage); + bool showCount = TFE_Settings::getGameSettings()->df_showSecretCount; + if (showCount) + { + sprintf(secretStr, "%d/%d", s_secretsFound, s_levelState.secretCount); + } + else + { + sprintf(secretStr, "%2d%%", secretPercentage); + } lactor_setState(s_pdaArt, 31, 0); lactorAnim_getFrame(s_pdaArt, &rect); diff --git a/TheForceEngine/TFE_DarkForces/hud.cpp b/TheForceEngine/TFE_DarkForces/hud.cpp index 773cc2a39..29c730c25 100644 --- a/TheForceEngine/TFE_DarkForces/hud.cpp +++ b/TheForceEngine/TFE_DarkForces/hud.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -549,7 +550,22 @@ namespace TFE_DarkForces s32 xOffset = floor16(div16(intToFixed16(vfb_getWidescreenOffset()), vfb_getXScale())); u8 dataStr[64]; - sprintf((char*)dataStr, "X:%04d Y:%.1f Z:%04d H:%.1f S:%d%%", floor16(x), -fixed16ToFloat(s_playerEye->posWS.y), floor16(z), fixed16ToFloat(s_playerEye->worldHeight), s_secretsPercent); + char secretStr[8]; + + bool showCount = TFE_Settings::getGameSettings()->df_showSecretCount; + if (showCount) + { + sprintf(secretStr, "%d/%d ", s_secretsFound, TFE_Jedi::s_levelState.secretCount); + + // Offset text for large # of secrets. + if (TFE_Jedi::s_levelState.secretCount > 10) xOffset -= 12; + } + else + { + sprintf(secretStr, "%2d%%", s_secretsPercent); + } + + sprintf((char*)dataStr, "X:%04d Y:%.1f Z:%04d H:%.1f S:%s", floor16(x), -fixed16ToFloat(s_playerEye->posWS.y), floor16(z), fixed16ToFloat(s_playerEye->worldHeight), secretStr); displayHudMessage(s_hudFont, (DrawRect*)vfb_getScreenRect(VFB_RECT_UI), 164 + xOffset, 10, dataStr, framebuffer); } } @@ -1287,7 +1303,10 @@ namespace TFE_DarkForces xScale /= fntScale; yScale /= fntScale; - fixed16_16 xf = mul16(intToFixed16(x), xScale); + bool centerText = TFE_Settings::getGameSettings()->df_centerHudPosition; + + // Center X Offset otherwise use the font centered scaling + fixed16_16 xf = mul16(intToFixed16(x), centerText ? xScale : vfb_getXScale()); fixed16_16 yf = mul16(intToFixed16(y), yScale); fixed16_16 x0 = xf; diff --git a/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp b/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp index 73dad7ed0..f02e52357 100644 --- a/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp +++ b/TheForceEngine/TFE_FrontEndUI/frontEndUi.cpp @@ -1201,18 +1201,6 @@ namespace TFE_FrontEndUI gameSettings->df_enableAutoaim = enableAutoaim; } - bool showSecretMsg = gameSettings->df_showSecretFoundMsg; - if (ImGui::Checkbox("Show Secret Found Message", &showSecretMsg)) - { - gameSettings->df_showSecretFoundMsg = showSecretMsg; - } - - bool showKeysUsed = gameSettings->df_showKeyUsed; - if (ImGui::Checkbox("Show Key Used messages", &showKeysUsed)) - { - gameSettings->df_showKeyUsed = showKeysUsed; - } - bool autorun = gameSettings->df_autorun; if (ImGui::Checkbox("Autorun", &autorun)) { @@ -1243,12 +1231,43 @@ namespace TFE_FrontEndUI gameSettings->df_autoEndMission = autoEndMission; } + ImGui::Separator(); + + ImGui::PushFont(s_versionFont); + ImGui::LabelText("##ConfigLabel", "UI Settings"); + ImGui::PopFont(); + bool showKeyColors = gameSettings->df_showKeyColors; if (ImGui::Checkbox("Show the key color of the door on the map", &showKeyColors)) { gameSettings->df_showKeyColors = showKeyColors; } + bool centerDataPos = gameSettings->df_centerHudPosition; + if (ImGui::Checkbox("Center the hud LADATA overlay", ¢erDataPos)) + { + gameSettings->df_centerHudPosition = centerDataPos; + } + + bool showSecretMsg = gameSettings->df_showSecretFoundMsg; + if (ImGui::Checkbox("Show Secret Found Message", &showSecretMsg)) + { + gameSettings->df_showSecretFoundMsg = showSecretMsg; + } + + bool showSecretCount = gameSettings->df_showSecretCount; + if (ImGui::Checkbox("Show the Total Number of Secrets Found", &showSecretCount)) + { + gameSettings->df_showSecretCount = showSecretCount; + } + + bool showKeysUsed = gameSettings->df_showKeyUsed; + if (ImGui::Checkbox("Show Key Used messages", &showKeysUsed)) + { + gameSettings->df_showKeyUsed = showKeysUsed; + } + + ImGui::Separator(); ImGui::PushFont(s_versionFont); diff --git a/TheForceEngine/TFE_Settings/settings.cpp b/TheForceEngine/TFE_Settings/settings.cpp index b69397508..0881b5d13 100644 --- a/TheForceEngine/TFE_Settings/settings.cpp +++ b/TheForceEngine/TFE_Settings/settings.cpp @@ -554,29 +554,31 @@ namespace TFE_Settings void writeDarkForcesGameSettings(FileStream& settings) { - writeKeyValue_Int(settings, "airControl", s_gameSettings.df_airControl); + writeKeyValue_Int(settings, "airControl", s_gameSettings.df_airControl); writeKeyValue_Bool(settings, "bobaFettFacePlayer", s_gameSettings.df_bobaFettFacePlayer); writeKeyValue_Bool(settings, "smoothVUEs", s_gameSettings.df_smoothVUEs); writeKeyValue_Bool(settings, "disableFightMusic", s_gameSettings.df_disableFightMusic); writeKeyValue_Bool(settings, "enableAutoaim", s_gameSettings.df_enableAutoaim); writeKeyValue_Bool(settings, "showSecretFoundMsg", s_gameSettings.df_showSecretFoundMsg); + writeKeyValue_Bool(settings, "showSecretCount", s_gameSettings.df_showSecretCount); writeKeyValue_Bool(settings, "autorun", s_gameSettings.df_autorun); writeKeyValue_Bool(settings, "crouchToggle", s_gameSettings.df_crouchToggle); writeKeyValue_Bool(settings, "ignoreInfLimit", s_gameSettings.df_ignoreInfLimit); writeKeyValue_Bool(settings, "stepSecondAlt", s_gameSettings.df_stepSecondAlt); - writeKeyValue_Int(settings, "pitchLimit", s_gameSettings.df_pitchLimit); + writeKeyValue_Int(settings, "pitchLimit", s_gameSettings.df_pitchLimit); writeKeyValue_Bool(settings, "solidWallFlagFix", s_gameSettings.df_solidWallFlagFix); writeKeyValue_Bool(settings, "enableUnusedItem", s_gameSettings.df_enableUnusedItem); writeKeyValue_Bool(settings, "jsonAiLogics", s_gameSettings.df_jsonAiLogics); - writeKeyValue_Bool(settings, "df_showReplayCounter", s_gameSettings.df_showReplayCounter); - writeKeyValue_Int(settings, "df_recordFrameRate", s_gameSettings.df_recordFrameRate); - writeKeyValue_Int(settings, "df_playbackFrameRate", s_gameSettings.df_playbackFrameRate); - writeKeyValue_Bool(settings, "df_enableRecording", s_gameSettings.df_enableRecording); - writeKeyValue_Bool(settings, "df_enableRecordingAll", s_gameSettings.df_enableRecordingAll); - writeKeyValue_Bool(settings, "df_demologging", s_gameSettings.df_demologging); - writeKeyValue_Bool(settings, "df_autoNextMission", s_gameSettings.df_autoEndMission); - writeKeyValue_Bool(settings, "df_showKeyUsed", s_gameSettings.df_showKeyUsed); - writeKeyValue_Bool(settings, "df_showKeyColors", s_gameSettings.df_showKeyColors); + writeKeyValue_Bool(settings, "showReplayCounter", s_gameSettings.df_showReplayCounter); + writeKeyValue_Int(settings, "recordFrameRate", s_gameSettings.df_recordFrameRate); + writeKeyValue_Int(settings, "playbackFrameRate", s_gameSettings.df_playbackFrameRate); + writeKeyValue_Bool(settings, "enableRecording", s_gameSettings.df_enableRecording); + writeKeyValue_Bool(settings, "enableRecordingAll", s_gameSettings.df_enableRecordingAll); + writeKeyValue_Bool(settings, "demologging", s_gameSettings.df_demologging); + writeKeyValue_Bool(settings, "autoNextMission", s_gameSettings.df_autoEndMission); + writeKeyValue_Bool(settings, "showKeyUsed", s_gameSettings.df_showKeyUsed); + writeKeyValue_Bool(settings, "showKeyColors", s_gameSettings.df_showKeyColors); + writeKeyValue_Bool(settings, "centerHudPos", s_gameSettings.df_centerHudPosition); } void writePerGameSettings(FileStream& settings) @@ -1178,6 +1180,10 @@ namespace TFE_Settings { s_gameSettings.df_showSecretFoundMsg = parseBool(value); } + else if (strcasecmp("showSecretCount", key) == 0) + { + s_gameSettings.df_showSecretCount = parseBool(value); + } else if (strcasecmp("autorun", key) == 0) { s_gameSettings.df_autorun = parseBool(value); @@ -1210,42 +1216,46 @@ namespace TFE_Settings { s_gameSettings.df_jsonAiLogics = parseBool(value); } - else if (strcasecmp("df_showReplayCounter", key) == 0) + else if (strcasecmp("showReplayCounter", key) == 0) { s_gameSettings.df_showReplayCounter = parseBool(value); } - else if (strcasecmp("df_recordFrameRate", key) == 0) + else if (strcasecmp("recordFrameRate", key) == 0) { s_gameSettings.df_recordFrameRate = parseInt(value); } - else if (strcasecmp("df_playbackFrameRate", key) == 0) + else if (strcasecmp("playbackFrameRate", key) == 0) { s_gameSettings.df_playbackFrameRate = parseInt(value); } - else if (strcasecmp("df_enableRecording", key) == 0) + else if (strcasecmp("enableRecording", key) == 0) { s_gameSettings.df_enableRecording = parseBool(value); } - else if (strcasecmp("df_enableRecordingAll", key) == 0) + else if (strcasecmp("enableRecordingAll", key) == 0) { s_gameSettings.df_enableRecordingAll = parseBool(value); } - else if (strcasecmp("df_demologging", key) == 0) + else if (strcasecmp("demologging", key) == 0) { s_gameSettings.df_demologging = parseBool(value); } - else if (strcasecmp("df_autoNextMission", key) == 0) + else if (strcasecmp("autoNextMission", key) == 0) { s_gameSettings.df_autoEndMission = parseBool(value); } - else if (strcasecmp("df_showKeyUsed", key) == 0) + else if (strcasecmp("showKeyUsed", key) == 0) { s_gameSettings.df_showKeyUsed = parseBool(value); } - else if (strcasecmp("df_showKeyColors", key) == 0) + else if (strcasecmp("showKeyColors", key) == 0) { s_gameSettings.df_showKeyColors = parseBool(value); } + else if (strcasecmp("centerHudPos", key) == 0) + { + s_gameSettings.df_centerHudPosition = parseBool(value); + } } void parseOutlawsSettings(const char* key, const char* value) diff --git a/TheForceEngine/TFE_Settings/settings.h b/TheForceEngine/TFE_Settings/settings.h index 39b9b0823..9f24ee5b9 100644 --- a/TheForceEngine/TFE_Settings/settings.h +++ b/TheForceEngine/TFE_Settings/settings.h @@ -214,7 +214,9 @@ struct TFE_Settings_Game bool df_smoothVUEs = false; // Smooths VUE animations (e.g. the Moldy Crow entering and exiting levels) bool df_disableFightMusic = false; // Set to true to disable fight music and music transitions during gameplay. bool df_enableAutoaim = true; // Set to true to enable autoaim, false to disable. - bool df_showSecretFoundMsg = true; // Show a message when the player finds a secret. + bool df_showSecretFoundMsg = false; // Show a message when the player finds a secret. + bool df_showSecretCount = true; // Show secret counts instead of percentage. + bool df_centerHudPosition = false; // Center LADATA position instead of top-right (Default). bool df_autorun = false; // Run by default instead of walk. bool df_crouchToggle = false; // Use toggle instead of hold for crouch. bool df_ignoreInfLimit = true; // Ignore the vanilla INF limit. @@ -228,7 +230,7 @@ struct TFE_Settings_Game bool df_showReplayCounter = false; // Show the replay counter on the HUD. bool df_demologging = false; // Log the record/playback logging bool df_autoEndMission = false; // Automatically skip to the next mission - bool df_showKeyColors = false; // Shows the door key color on the minimap + bool df_showKeyColors = false; // Shows the door key color on the minimap s32 df_recordFrameRate = 4; // Recording Framerate value s32 df_playbackFrameRate = 2; // Playback Framerate value bool df_showKeyUsed = true; // Show a message when a key is used. diff --git a/TheForceEngine/TheForceEngine.vcxproj b/TheForceEngine/TheForceEngine.vcxproj index f511b487d..e5cea176e 100644 --- a/TheForceEngine/TheForceEngine.vcxproj +++ b/TheForceEngine/TheForceEngine.vcxproj @@ -39,60 +39,60 @@ {31CD2991-9EF0-4AA9-AEEB-BA1194D2DAB4} Win32Proj TheForceEngine - 10.0.17763.0 + 10.0 Application true - v141 + v143 Unicode Application false - v141 + v143 true Unicode Application false - v141 + v143 true Unicode Application false - v141 + v143 true Unicode Application true - v141 + v143 NotSet Application false - v141 + v143 true NotSet Application false - v141 + v143 true NotSet Application false - v141 + v143 true NotSet diff --git a/TheForceEngine/x64/Debug/TheForceEngine.exe.recipe b/TheForceEngine/x64/Debug/TheForceEngine.exe.recipe new file mode 100644 index 000000000..342a16bae --- /dev/null +++ b/TheForceEngine/x64/Debug/TheForceEngine.exe.recipe @@ -0,0 +1,11 @@ + + + + + C:\Users\Karjala\Source\Repos\TheForceEngine\x64\Debug\TheForceEngine.exe + + + + + + \ No newline at end of file From 664464e34bf1fe11386d9d402bb2aa8c61100d12 Mon Sep 17 00:00:00 2001 From: luciusDXL Date: Sun, 21 Sep 2025 13:35:20 -0700 Subject: [PATCH 12/17] It looks like there are more problems with trying to run faster than the tick rate, so I restored the tick rate limitation but kept the improved fractional delta-time precision when smooth delta time is enabled. --- TheForceEngine/TFE_Jedi/Task/task.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/TheForceEngine/TFE_Jedi/Task/task.cpp b/TheForceEngine/TFE_Jedi/Task/task.cpp index 68df76acf..f7df4d3e8 100644 --- a/TheForceEngine/TFE_Jedi/Task/task.cpp +++ b/TheForceEngine/TFE_Jedi/Task/task.cpp @@ -530,7 +530,7 @@ namespace TFE_Jedi JBool task_canRun() { - bool timeLimiter = s_enableTimeLimiter && !TFE_Settings::getGraphicsSettings()->useSmoothDeltaTime; + bool timeLimiter = s_enableTimeLimiter; if (s_taskCount && timeLimiter) { const f64 time = TFE_System::getTime(); @@ -560,16 +560,11 @@ namespace TFE_Jedi // Limit the update rate by the minimum interval. // Dark Forces uses discrete 'ticks' to track time and the game behavior is very odd with 0 tick frames. const f64 time = TFE_System::getTime(); - const bool timeLimiter = s_enableTimeLimiter && !TFE_Settings::getGraphicsSettings()->useSmoothDeltaTime && !TFE_Input::isReplaySystemLive(); + const bool timeLimiter = s_enableTimeLimiter && !TFE_Input::isReplaySystemLive(); if (time - s_prevTime < s_minIntervalInSec && timeLimiter) { return JFALSE; } - // Timing still breaks down if the frames are too short - even with fractional ticks. - if (time - s_prevTime < 0.5f * s_minIntervalInSec) - { - return JFALSE; - } s_prevTime = time; s_currentMsg = MSG_RUN_TASK; From d1ba47e797453c3810ce4675c417f95f3220cb8c Mon Sep 17 00:00:00 2001 From: jerethk <82040941+jerethk@users.noreply.github.com> Date: Tue, 23 Sep 2025 03:10:26 +1000 Subject: [PATCH 13/17] Object Scripting - manipulate logic data (#568) * Get logic hitpoints * Set hitpoints and projectile * Get and set Velocity --- .../TFE_DarkForces/Scripting/gs_player.cpp | 1 - .../TFE_DarkForces/Scripting/gs_player.h | 4 + .../TFE_DarkForces/Scripting/scriptObject.cpp | 323 +++++++++++++++++- 3 files changed, 326 insertions(+), 2 deletions(-) diff --git a/TheForceEngine/TFE_DarkForces/Scripting/gs_player.cpp b/TheForceEngine/TFE_DarkForces/Scripting/gs_player.cpp index 64de19705..9350ce0bd 100644 --- a/TheForceEngine/TFE_DarkForces/Scripting/gs_player.cpp +++ b/TheForceEngine/TFE_DarkForces/Scripting/gs_player.cpp @@ -1,6 +1,5 @@ #include "gs_player.h" #include "assert.h" -#include #include #include #include diff --git a/TheForceEngine/TFE_DarkForces/Scripting/gs_player.h b/TheForceEngine/TFE_DarkForces/Scripting/gs_player.h index 746ff4e86..f4f49d2dd 100644 --- a/TheForceEngine/TFE_DarkForces/Scripting/gs_player.h +++ b/TheForceEngine/TFE_DarkForces/Scripting/gs_player.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include @@ -11,4 +12,7 @@ namespace TFE_DarkForces public: bool scriptRegister(ScriptAPI api) override; }; + + vec3_float getPlayerVelocity(); + void setPlayerVelocity(vec3_float vel); } \ No newline at end of file diff --git a/TheForceEngine/TFE_DarkForces/Scripting/scriptObject.cpp b/TheForceEngine/TFE_DarkForces/Scripting/scriptObject.cpp index 923092fbc..00d0529ea 100644 --- a/TheForceEngine/TFE_DarkForces/Scripting/scriptObject.cpp +++ b/TheForceEngine/TFE_DarkForces/Scripting/scriptObject.cpp @@ -1,4 +1,5 @@ #include "gs_level.h" +#include "gs_player.h" #include "scriptObject.h" #include "scriptSector.h" #include @@ -14,9 +15,17 @@ #include #include #include +#include namespace TFE_DarkForces { + // The "special" actors can all be cast to this struct (eg. bosses, dark troopers, mousebot, turret) + struct SpecialActor + { + Logic logic; + PhysicsActor actor; + }; + bool isScriptObjectValid(ScriptObject* sObject) { return sObject->m_id >= 0 && sObject->m_id < s_objectRefList.size(); @@ -278,6 +287,7 @@ namespace TFE_DarkForces if (s_nightVisionActive) { disableNightVision(); } } + void sendMessageToObject(MessageType messageType, ScriptObject* sObject) { if (!doesObjectExist(sObject)) { return; } @@ -286,6 +296,288 @@ namespace TFE_DarkForces message_sendToObj(obj, messageType, actor_messageFunc); } + ////////////////////////////////////////////////////// + // Logic related functionality + ////////////////////////////////////////////////////// + + // Helper function - gets the first Dispatch actor linked to an object + ActorDispatch* getDispatch(SecObject* obj) + { + Logic** logicPtr = (Logic**)allocator_getHead((Allocator*)obj->logic); + while (logicPtr) + { + Logic* logic = *logicPtr; + if (logic->type == LOGIC_DISPATCH) + { + return (ActorDispatch*)logic; + } + logicPtr = (Logic**)allocator_getNext((Allocator*)obj->logic); + } + + return nullptr; + } + + // Helper function - gets the damage module from a dispatch logic + DamageModule* getDamageModule(ActorDispatch* dispatch) + { + for (s32 i = 0; i < ACTOR_MAX_MODULES; i++) + { + ActorModule* module = dispatch->modules[ACTOR_MAX_MODULES - 1 - i]; + if (module && module->type == ACTMOD_DAMAGE) + { + return (DamageModule*)module; + } + } + + return nullptr; + } + + // Helper function - gets the attack module from a dispatch logic + AttackModule* getAttackModule(ActorDispatch* dispatch) + { + for (s32 i = 0; i < ACTOR_MAX_MODULES; i++) + { + ActorModule* module = dispatch->modules[ACTOR_MAX_MODULES - 1 - i]; + if (module && module->type == ACTMOD_ATTACK) + { + return (AttackModule*)module; + } + } + + return nullptr; + } + + int getHitPoints(ScriptObject* sObject) + { + if (!doesObjectExist(sObject)) { return -1; } + SecObject* obj = TFE_Jedi::s_objectRefList[sObject->m_id].object; + + // First try to find a dispatch logic + ActorDispatch* dispatch = getDispatch(obj); + if (dispatch) + { + DamageModule* damageMod = getDamageModule(dispatch); + if (damageMod) + { + return floor16(damageMod->hp); + } + } + + // Then try other logics + Logic** logicPtr = (Logic**)allocator_getHead((Allocator*)obj->logic); + while (logicPtr) + { + Logic* logic = *logicPtr; + PhysicsActor* actor = nullptr; + + switch (logic->type) + { + case LOGIC_BOBA_FETT: + case LOGIC_DRAGON: + case LOGIC_PHASE_ONE: + case LOGIC_PHASE_TWO: + case LOGIC_PHASE_THREE: + case LOGIC_TURRET: + case LOGIC_WELDER: + case LOGIC_MOUSEBOT: + SpecialActor* data = (SpecialActor*)logic; + actor = &data->actor; + break; + } + + if (actor) + { + return floor16(actor->hp); + } + + logicPtr = (Logic**)allocator_getNext((Allocator*)obj->logic); + } + + return -1; + } + + void setHitPoints(int hitpoints, ScriptObject* sObject) + { + if (!doesObjectExist(sObject)) { return; } + SecObject* obj = TFE_Jedi::s_objectRefList[sObject->m_id].object; + + // First try to find a dispatch logic + ActorDispatch* dispatch = getDispatch(obj); + if (dispatch) + { + DamageModule* damageMod = getDamageModule(dispatch); + if (damageMod) + { + damageMod->hp = FIXED(hitpoints); + return; + } + } + + // Then try other logics + Logic** logicPtr = (Logic**)allocator_getHead((Allocator*)obj->logic); + while (logicPtr) + { + Logic* logic = *logicPtr; + PhysicsActor* actor = nullptr; + + switch (logic->type) + { + case LOGIC_BOBA_FETT: + case LOGIC_DRAGON: + case LOGIC_PHASE_ONE: + case LOGIC_PHASE_TWO: + case LOGIC_PHASE_THREE: + case LOGIC_TURRET: + case LOGIC_WELDER: + case LOGIC_MOUSEBOT: + SpecialActor* data = (SpecialActor*)logic; + actor = &data->actor; + break; + } + + if (actor) + { + actor->hp = FIXED(hitpoints); + return; + } + + logicPtr = (Logic**)allocator_getNext((Allocator*)obj->logic); + } + } + + void setProjectile(ProjectileType projectile, ScriptObject* sObject) + { + if (!doesObjectExist(sObject)) { return; } + SecObject* obj = TFE_Jedi::s_objectRefList[sObject->m_id].object; + + ActorDispatch* dispatch = getDispatch(obj); + if (dispatch) + { + AttackModule* attackMod = getAttackModule(dispatch); + if (attackMod) + { + attackMod->projType = projectile; + } + } + } + + vec3_float getVelocity(ScriptObject* sObject) + { + if (!doesObjectExist(sObject)) { return { 0, 0, 0,}; } + SecObject* obj = TFE_Jedi::s_objectRefList[sObject->m_id].object; + + // Is it the player? + if (obj->entityFlags & ETFLAG_PLAYER) + { + return getPlayerVelocity(); + } + + // First try to find a dispatch logic + ActorDispatch* dispatch = getDispatch(obj); + if (dispatch) + { + return + { + fixed16ToFloat(dispatch->vel.x), + -fixed16ToFloat(dispatch->vel.y), + fixed16ToFloat(dispatch->vel.z) + }; + } + + // Then try other logics + Logic** logicPtr = (Logic**)allocator_getHead((Allocator*)obj->logic); + while (logicPtr) + { + Logic* logic = *logicPtr; + PhysicsActor* actor = nullptr; + + switch (logic->type) + { + case LOGIC_BOBA_FETT: + case LOGIC_DRAGON: + case LOGIC_PHASE_ONE: + case LOGIC_PHASE_TWO: + case LOGIC_PHASE_THREE: + case LOGIC_TURRET: + case LOGIC_WELDER: + case LOGIC_MOUSEBOT: + SpecialActor* data = (SpecialActor*)logic; + actor = &data->actor; + break; + } + + if (actor) + { + return + { + fixed16ToFloat(actor->vel.x), + -fixed16ToFloat(actor->vel.y), + fixed16ToFloat(actor->vel.z) + }; + } + + logicPtr = (Logic**)allocator_getNext((Allocator*)obj->logic); + } + + return { 0, 0, 0 }; + } + + void setVelocity(vec3_float vel, ScriptObject* sObject) + { + if (!doesObjectExist(sObject)) { return; } + SecObject* obj = TFE_Jedi::s_objectRefList[sObject->m_id].object; + + // Is it the player? + if (obj->entityFlags & ETFLAG_PLAYER) + { + setPlayerVelocity(vel); + return; + } + + // First try to find a dispatch logic + ActorDispatch* dispatch = getDispatch(obj); + if (dispatch) + { + dispatch->vel.x = floatToFixed16(vel.x); + dispatch->vel.y = -floatToFixed16(vel.y); + dispatch->vel.z = floatToFixed16(vel.z); + return; + } + + // Then try other logics + Logic** logicPtr = (Logic**)allocator_getHead((Allocator*)obj->logic); + while (logicPtr) + { + Logic* logic = *logicPtr; + PhysicsActor* actor = nullptr; + + switch (logic->type) + { + case LOGIC_BOBA_FETT: + case LOGIC_DRAGON: + case LOGIC_PHASE_ONE: + case LOGIC_PHASE_TWO: + case LOGIC_PHASE_THREE: + case LOGIC_TURRET: + case LOGIC_WELDER: + case LOGIC_MOUSEBOT: + SpecialActor* data = (SpecialActor*)logic; + actor = &data->actor; + break; + } + + if (actor) + { + actor->vel.x = floatToFixed16(vel.x); + actor->vel.y = -floatToFixed16(vel.y); + actor->vel.z = floatToFixed16(vel.z); + return; + } + + logicPtr = (Logic**)allocator_getNext((Allocator*)obj->logic); + } + } + void ScriptObject::registerType() { s32 res = 0; @@ -295,6 +587,28 @@ namespace TFE_DarkForces // Variables ScriptMemberVariable("int id", m_id); + // Enums + ScriptEnumRegister("Projectiles"); + ScriptEnumStr(PROJ_PUNCH); + ScriptEnumStr(PROJ_PISTOL_BOLT); + ScriptEnumStr(PROJ_RIFLE_BOLT); + ScriptEnumStr(PROJ_THERMAL_DET); + ScriptEnumStr(PROJ_REPEATER); + ScriptEnumStr(PROJ_PLASMA); + ScriptEnumStr(PROJ_MORTAR); + ScriptEnumStr(PROJ_LAND_MINE); + ScriptEnumStr(PROJ_LAND_MINE_PROX); + ScriptEnumStr(PROJ_LAND_MINE_PLACED); + ScriptEnumStr(PROJ_CONCUSSION); + ScriptEnumStr(PROJ_CANNON); + ScriptEnumStr(PROJ_MISSILE); + ScriptEnumStr(PROJ_TURRET_BOLT); + ScriptEnumStr(PROJ_REMOTE_BOLT); + ScriptEnumStr(PROJ_EXP_BARREL); + ScriptEnumStr(PROJ_HOMING_MISSILE); + ScriptEnumStr(PROJ_PROBE_PROJ); + ScriptEnum("PROJ_BOBAFETT_BALL", PROJ_BOBAFET_BALL); + // Checks ScriptObjFunc("bool isValid()", isScriptObjectValid); ScriptObjFunc("bool exists()", doesObjectExist); @@ -323,6 +637,13 @@ namespace TFE_DarkForces ScriptObjFunc("void delete()", deleteObject); ScriptObjFunc("void addLogic(string)", addLogicToObject); ScriptObjFunc("void setCamera()", setCamera); - ScriptObjFunc("void sendMessage(int)", sendMessageToObject) + ScriptObjFunc("void sendMessage(int)", sendMessageToObject); + + // Logic getters & setters + ScriptPropertyGetFunc("int get_hitPoints()", getHitPoints); + ScriptPropertySetFunc("void set_hitPoints(int)", setHitPoints); + ScriptPropertySetFunc("void set_projectile(int)", setProjectile); + ScriptPropertyGetFunc("float3 get_velocity()", getVelocity); + ScriptPropertySetFunc("void set_velocity(float3)", setVelocity); } } From e0df74d5f456e2c9ce10c20e25b00298cfe6aae5 Mon Sep 17 00:00:00 2001 From: jerethk <82040941+jerethk@users.noreply.github.com> Date: Tue, 23 Sep 2025 03:10:47 +1000 Subject: [PATCH 14/17] Bug fix - external data needs to be cleared out before loading a game (#569) --- TheForceEngine/TFE_Game/saveSystem.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/TheForceEngine/TFE_Game/saveSystem.cpp b/TheForceEngine/TFE_Game/saveSystem.cpp index e9ef8387e..10156e815 100644 --- a/TheForceEngine/TFE_Game/saveSystem.cpp +++ b/TheForceEngine/TFE_Game/saveSystem.cpp @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -247,6 +250,14 @@ namespace TFE_SaveSystem { SaveHeader header; loadHeader(&stream, &header, filename); + + // Clear out custom logics and external data before loading + TFE_ExternalData::getExternalLogics()->actorLogics.clear(); + TFE_ExternalData::clearExternalWeapons(); + TFE_ExternalData::clearExternalProjectiles(); + TFE_ExternalData::clearExternalEffects(); + TFE_ExternalData::clearExternalPickups(); + ret = s_game->serializeGameState(&stream, filename, false); stream.close(); } From 55d64f28c2375f1a7382357b6c708a2707a4f726 Mon Sep 17 00:00:00 2001 From: Karjala22 <78927981+Karjala22@users.noreply.github.com> Date: Mon, 22 Sep 2025 19:40:41 -0400 Subject: [PATCH 15/17] Add recipes to ignore and restore vcxproj. --- .gitignore | 3 +++ TheForceEngine/TheForceEngine.vcxproj | 18 +++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 862b76a87..61723ce00 100644 --- a/.gitignore +++ b/.gitignore @@ -84,3 +84,6 @@ TheForceEngine/tfelnx # Ignore dumps *.dmp + +# ignore recipe +*.recipe diff --git a/TheForceEngine/TheForceEngine.vcxproj b/TheForceEngine/TheForceEngine.vcxproj index e5cea176e..f511b487d 100644 --- a/TheForceEngine/TheForceEngine.vcxproj +++ b/TheForceEngine/TheForceEngine.vcxproj @@ -39,60 +39,60 @@ {31CD2991-9EF0-4AA9-AEEB-BA1194D2DAB4} Win32Proj TheForceEngine - 10.0 + 10.0.17763.0 Application true - v143 + v141 Unicode Application false - v143 + v141 true Unicode Application false - v143 + v141 true Unicode Application false - v143 + v141 true Unicode Application true - v143 + v141 NotSet Application false - v143 + v141 true NotSet Application false - v143 + v141 true NotSet Application false - v143 + v141 true NotSet From 872ab7f5c9d0e7cb724c97bf31578420f8fc61d3 Mon Sep 17 00:00:00 2001 From: Karjala <36711831+ifeldshteyn@users.noreply.github.com> Date: Mon, 22 Sep 2025 19:42:12 -0400 Subject: [PATCH 16/17] Delete TheForceEngine/x64/Debug/TheForceEngine.exe.recipe --- TheForceEngine/x64/Debug/TheForceEngine.exe.recipe | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 TheForceEngine/x64/Debug/TheForceEngine.exe.recipe diff --git a/TheForceEngine/x64/Debug/TheForceEngine.exe.recipe b/TheForceEngine/x64/Debug/TheForceEngine.exe.recipe deleted file mode 100644 index 342a16bae..000000000 --- a/TheForceEngine/x64/Debug/TheForceEngine.exe.recipe +++ /dev/null @@ -1,11 +0,0 @@ - - - - - C:\Users\Karjala\Source\Repos\TheForceEngine\x64\Debug\TheForceEngine.exe - - - - - - \ No newline at end of file From d324c9c76d6250645a91d8bb041853409c324dd7 Mon Sep 17 00:00:00 2001 From: Karjala22 <78927981+Karjala22@users.noreply.github.com> Date: Mon, 22 Sep 2025 23:03:36 -0400 Subject: [PATCH 17/17] Ensure offset is centered even when in non-hd mode. --- TheForceEngine/TFE_DarkForces/hud.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/TheForceEngine/TFE_DarkForces/hud.cpp b/TheForceEngine/TFE_DarkForces/hud.cpp index 29c730c25..628063957 100644 --- a/TheForceEngine/TFE_DarkForces/hud.cpp +++ b/TheForceEngine/TFE_DarkForces/hud.cpp @@ -1300,13 +1300,14 @@ namespace TFE_DarkForces fixed16_16 xScale = vfb_getXScale(); fixed16_16 yScale = vfb_getYScale(); - xScale /= fntScale; - yScale /= fntScale; - bool centerText = TFE_Settings::getGameSettings()->df_centerHudPosition; // Center X Offset otherwise use the font centered scaling - fixed16_16 xf = mul16(intToFixed16(x), centerText ? xScale : vfb_getXScale()); + fixed16_16 xf = mul16(intToFixed16(x), centerText ? xScale / 2 : xScale); + + xScale /= fntScale; + yScale /= fntScale; + fixed16_16 yf = mul16(intToFixed16(y), yScale); fixed16_16 x0 = xf;