From 89839be3af31f1784951f15f1cfff07ad40e1fe3 Mon Sep 17 00:00:00 2001 From: Alexander Batalov Date: Wed, 31 May 2023 21:48:44 +0300 Subject: [PATCH] Add global scripts (#294) --- CMakeLists.txt | 2 + src/combat.cc | 5 + src/config.cc | 6 +- src/game.cc | 8 ++ src/interpreter.cc | 3 +- src/interpreter.h | 1 + src/loadsave.cc | 4 + src/main.cc | 8 ++ src/scripts.cc | 6 +- src/scripts.h | 2 + src/sfall_config.cc | 1 + src/sfall_config.h | 1 + src/sfall_global_scripts.cc | 213 ++++++++++++++++++++++++++++++++++++ src/sfall_global_scripts.h | 23 ++++ src/sfall_ini.cc | 8 +- src/sfall_opcodes.cc | 36 ++++++ src/worldmap.cc | 5 + 17 files changed, 323 insertions(+), 9 deletions(-) create mode 100644 src/sfall_global_scripts.cc create mode 100644 src/sfall_global_scripts.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6eb6375..cd91dbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -267,6 +267,8 @@ target_sources(${EXECUTABLE_NAME} PUBLIC "src/sfall_config.h" "src/sfall_global_vars.cc" "src/sfall_global_vars.h" + "src/sfall_global_scripts.cc" + "src/sfall_global_scripts.h" "src/sfall_ini.cc" "src/sfall_ini.h" "src/sfall_lists.cc" diff --git a/src/combat.cc b/src/combat.cc index cde7b7d..8548137 100644 --- a/src/combat.cc +++ b/src/combat.cc @@ -37,6 +37,7 @@ #include "scripts.h" #include "settings.h" #include "sfall_config.h" +#include "sfall_global_scripts.h" #include "skill.h" #include "stat.h" #include "svga.h" @@ -3144,6 +3145,10 @@ static int _combat_input() } int keyCode = inputGetInput(); + + // SFALL: CombatLoopHook. + sfall_gl_scr_process_main(); + if (_action_explode_running()) { // NOTE: Uninline. _combat_turn_run(); diff --git a/src/config.cc b/src/config.cc index 2ec1aad..5a90fae 100644 --- a/src/config.cc +++ b/src/config.cc @@ -397,7 +397,7 @@ static bool configParseLine(Config* config, char* string) // keys there. // Skip leading whitespace. - while (isspace(*string)) { + while (isspace(static_cast(*string))) { string++; } @@ -500,7 +500,7 @@ static bool configTrimString(char* string) // Starting from the end of the string, loop while it's a whitespace and // decrement string length. char* pch = string + length - 1; - while (length != 0 && isspace(*pch)) { + while (length != 0 && isspace(static_cast(*pch))) { length--; pch--; } @@ -511,7 +511,7 @@ static bool configTrimString(char* string) // Starting from the beginning of the string loop while it's a whitespace // and decrement string length. pch = string; - while (isspace(*pch)) { + while (isspace(static_cast(*pch))) { pch++; length--; } diff --git a/src/game.cc b/src/game.cc index 4412024..c215edf 100644 --- a/src/game.cc +++ b/src/game.cc @@ -52,6 +52,7 @@ #include "settings.h" #include "sfall_arrays.h" #include "sfall_config.h" +#include "sfall_global_scripts.h" #include "sfall_global_vars.h" #include "sfall_ini.h" #include "sfall_lists.h" @@ -355,6 +356,11 @@ int gameInitWithOptions(const char* windowTitle, bool isMapper, int font, int a4 return -1; } + if (!sfall_gl_scr_init()) { + debugPrint("Failed on sfall_gl_scr_init"); + return -1; + } + char* customConfigBasePath; configGetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_INI_CONFIG_FOLDER, &customConfigBasePath); sfall_ini_set_base_path(customConfigBasePath); @@ -407,6 +413,7 @@ void gameReset() sfallListsReset(); messageListRepositoryReset(); sfallArraysReset(); + sfall_gl_scr_reset(); } // 0x442C34 @@ -415,6 +422,7 @@ void gameExit() debugPrint("\nGame Exit\n"); // SFALL + sfall_gl_scr_exit(); sfallArraysExit(); sfallListsExit(); sfallGlobalVarsExit(); diff --git a/src/interpreter.cc b/src/interpreter.cc index 3653f4d..b2e0cd1 100644 --- a/src/interpreter.cc +++ b/src/interpreter.cc @@ -43,7 +43,6 @@ static opcode_t programReturnStackPopInt16(Program* program); static int programReturnStackPopInt32(Program* program); static void _detachProgram(Program* program); static void _purgeProgram(Program* program); -static void programFree(Program* program); static opcode_t _getOp(Program* program); static void programMarkHeap(Program* program); static void opNoop(Program* program); @@ -421,7 +420,7 @@ static void _purgeProgram(Program* program) } // 0x467614 -static void programFree(Program* program) +void programFree(Program* program) { // NOTE: Uninline. _detachProgram(program); diff --git a/src/interpreter.h b/src/interpreter.h index 835e2fd..6095292 100644 --- a/src/interpreter.h +++ b/src/interpreter.h @@ -204,6 +204,7 @@ void _interpretOutputFunc(int (*func)(char*)); int _interpretOutput(const char* format, ...); [[noreturn]] void programFatalError(const char* str, ...); void _interpretDecStringRef(Program* program, opcode_t a2, int a3); +void programFree(Program* program); Program* programCreateByPath(const char* path); char* programGetString(Program* program, opcode_t opcode, int offset); char* programGetIdentifier(Program* program, int offset); diff --git a/src/loadsave.cc b/src/loadsave.cc index d469311..ebee1e6 100644 --- a/src/loadsave.cc +++ b/src/loadsave.cc @@ -45,6 +45,7 @@ #include "random.h" #include "scripts.h" #include "settings.h" +#include "sfall_global_scripts.h" #include "skill.h" #include "stat.h" #include "svga.h" @@ -1676,6 +1677,9 @@ static int lsgLoadGameInSlot(int slot) _loadingGame = 0; + // SFALL: Start global scripts. + sfall_gl_scr_exec_start_proc(); + return 0; } diff --git a/src/main.cc b/src/main.cc index a016f16..9c90e29 100644 --- a/src/main.cc +++ b/src/main.cc @@ -33,6 +33,7 @@ #include "selfrun.h" #include "settings.h" #include "sfall_config.h" +#include "sfall_global_scripts.h" #include "svga.h" #include "text_font.h" #include "window.h" @@ -148,6 +149,9 @@ int falloutMain(int argc, char** argv) _main_load_new(mapNameCopy); free(mapNameCopy); + // SFALL: AfterNewGameStartHook. + sfall_gl_scr_exec_start_proc(); + mainLoop(); paletteFadeTo(gPaletteWhite); @@ -357,6 +361,10 @@ static void mainLoop() sharedFpsLimiter.mark(); int keyCode = inputGetInput(); + + // SFALL: MainLoopHook. + sfall_gl_scr_process_main(); + gameHandleKey(keyCode, false); scriptsHandleRequests(); diff --git a/src/scripts.cc b/src/scripts.cc index 7de46fd..f0d2c60 100644 --- a/src/scripts.cc +++ b/src/scripts.cc @@ -31,6 +31,7 @@ #include "queue.h" #include "sfall_arrays.h" #include "sfall_config.h" +#include "sfall_global_scripts.h" #include "stat.h" #include "svga.h" #include "tile.h" @@ -145,7 +146,7 @@ static const int gGameTimeDaysPerMonth[12] = { }; // 0x51C758 -static const char* gScriptProcNames[28] = { +const char* gScriptProcNames[SCRIPT_PROC_COUNT] = { "no_p_proc", "start", "spatial_p_proc", @@ -2589,6 +2590,9 @@ void scriptsExecMapUpdateProc() // 0x4A67EC void scriptsExecMapUpdateScripts(int proc) { + // SFALL: Run global scripts. + sfall_gl_scr_exec_map_update_scripts(proc); + _scr_SpatialsEnabled = false; int fixedParam = 0; diff --git a/src/scripts.h b/src/scripts.h index 8efdd1d..0c4b508 100644 --- a/src/scripts.h +++ b/src/scripts.h @@ -145,6 +145,8 @@ typedef struct Script { int field_DC; } Script; +extern const char* gScriptProcNames[SCRIPT_PROC_COUNT]; + int gameTimeGetTime(); void gameTimeGetDate(int* monthPtr, int* dayPtr, int* yearPtr); int gameTimeGetHour(); diff --git a/src/sfall_config.cc b/src/sfall_config.cc index c38b825..b85a83b 100644 --- a/src/sfall_config.cc +++ b/src/sfall_config.cc @@ -59,6 +59,7 @@ bool sfallConfigInit(int argc, char** argv) configSetInt(&gSfallConfig, SFALL_CONFIG_MISC_KEY, SFALL_CONFIG_MOVIE_TIMER_ARTIMER4, 360); configSetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_INI_CONFIG_FOLDER, ""); + configSetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_GLOBAL_SCRIPT_PATHS, ""); char path[COMPAT_MAX_PATH]; char* executable = argv[0]; diff --git a/src/sfall_config.h b/src/sfall_config.h index f165450..1f786ce 100644 --- a/src/sfall_config.h +++ b/src/sfall_config.h @@ -70,6 +70,7 @@ namespace fallout { #define SFALL_CONFIG_EXTRA_MESSAGE_LISTS_KEY "ExtraGameMsgFileList" #define SFALL_CONFIG_NUMBERS_IS_DIALOG_KEY "NumbersInDialogue" #define SFALL_CONFIG_INI_CONFIG_FOLDER "IniConfigFolder" +#define SFALL_CONFIG_GLOBAL_SCRIPT_PATHS "GlobalScriptPaths" #define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_MULTIPLIER 1 #define SFALL_CONFIG_BURST_MOD_DEFAULT_CENTER_DIVISOR 3 diff --git a/src/sfall_global_scripts.cc b/src/sfall_global_scripts.cc new file mode 100644 index 0000000..1cd65d7 --- /dev/null +++ b/src/sfall_global_scripts.cc @@ -0,0 +1,213 @@ +#include "sfall_global_scripts.h" + +#include +#include +#include +#include + +#include "db.h" +#include "input.h" +#include "platform_compat.h" +#include "scripts.h" +#include "sfall_config.h" + +namespace fallout { + +struct GlobalScript { + Program* program = nullptr; + int procs[SCRIPT_PROC_COUNT] = { 0 }; + int repeat = 0; + int count = 0; + int mode = 0; + bool once = true; +}; + +struct GlobalScriptsState { + std::vector paths; + std::vector globalScripts; +}; + +static GlobalScriptsState* state = nullptr; + +bool sfall_gl_scr_init() +{ + state = new (std::nothrow) GlobalScriptsState(); + if (state == nullptr) { + return false; + } + + char* paths; + configGetString(&gSfallConfig, SFALL_CONFIG_SCRIPTS_KEY, SFALL_CONFIG_GLOBAL_SCRIPT_PATHS, &paths); + + char* curr = paths; + while (curr != nullptr && *curr != '\0') { + char* end = strchr(curr, ','); + if (end != nullptr) { + *end = '\0'; + } + + char drive[COMPAT_MAX_DRIVE]; + char dir[COMPAT_MAX_DIR]; + compat_splitpath(curr, drive, dir, nullptr, nullptr); + + char** files; + int filesLength = fileNameListInit(curr, &files, 0, 0); + if (filesLength != 0) { + for (int index = 0; index < filesLength; index++) { + char path[COMPAT_MAX_PATH]; + compat_makepath(path, drive, dir, files[index], nullptr); + + state->paths.push_back(std::string { path }); + } + + fileNameListFree(&files, 0); + } + + if (end != nullptr) { + *end = ','; + curr = end + 1; + } else { + curr = nullptr; + } + } + + std::sort(state->paths.begin(), state->paths.end()); + + return true; +} + +void sfall_gl_scr_reset() +{ + if (state != nullptr) { + sfall_gl_scr_remove_all(); + } +} + +void sfall_gl_scr_exit() +{ + if (state != nullptr) { + sfall_gl_scr_remove_all(); + + delete state; + state = nullptr; + } +} + +void sfall_gl_scr_exec_start_proc() +{ + for (auto& path : state->paths) { + Program* program = programCreateByPath(path.c_str()); + if (program != nullptr) { + GlobalScript scr; + scr.program = program; + + for (int action = 0; action < SCRIPT_PROC_COUNT; action++) { + scr.procs[action] = programFindProcedure(program, gScriptProcNames[action]); + } + + state->globalScripts.push_back(std::move(scr)); + + _interpret(program, -1); + } + } + + tickersAdd(sfall_gl_scr_process_input); +} + +void sfall_gl_scr_remove_all() +{ + tickersRemove(sfall_gl_scr_process_input); + + for (auto& scr : state->globalScripts) { + programFree(scr.program); + } + + state->globalScripts.clear(); +} + +void sfall_gl_scr_exec_map_update_scripts(int action) +{ + for (auto& scr : state->globalScripts) { + if (scr.mode == 0 || scr.mode == 3) { + if (scr.procs[action] != -1) { + _executeProcedure(scr.program, scr.procs[action]); + } + } + } +} + +static void sfall_gl_scr_process_simple(int mode1, int mode2) +{ + for (auto& scr : state->globalScripts) { + if (scr.repeat != 0 && (scr.mode == mode1 || scr.mode == mode2)) { + scr.count++; + if (scr.count >= scr.repeat) { + _executeProcedure(scr.program, scr.procs[SCRIPT_PROC_START]); + scr.count = 0; + } + } + } +} + +void sfall_gl_scr_process_main() +{ + sfall_gl_scr_process_simple(0, 3); +} + +void sfall_gl_scr_process_input() +{ + sfall_gl_scr_process_simple(1, 1); +} + +void sfall_gl_scr_process_worldmap() +{ + sfall_gl_scr_process_simple(2, 3); +} + +static GlobalScript* sfall_gl_scr_map_program_to_scr(Program* program) +{ + auto it = std::find_if(state->globalScripts.begin(), + state->globalScripts.end(), + [&program](const GlobalScript& scr) { + return scr.program == program; + }); + return it != state->globalScripts.end() ? &(*it) : nullptr; +} + +void sfall_gl_scr_set_repeat(Program* program, int frames) +{ + GlobalScript* scr = sfall_gl_scr_map_program_to_scr(program); + if (scr != nullptr) { + scr->repeat = frames; + } +} + +void sfall_gl_scr_set_type(Program* program, int type) +{ + if (type < 0 || type > 3) { + return; + } + + GlobalScript* scr = sfall_gl_scr_map_program_to_scr(program); + if (scr != nullptr) { + scr->mode = type; + } +} + +bool sfall_gl_scr_is_loaded(Program* program) +{ + GlobalScript* scr = sfall_gl_scr_map_program_to_scr(program); + if (scr != nullptr) { + if (scr->once) { + scr->once = false; + return true; + } + + return false; + } + + // Not a global script. + return true; +} + +} // namespace fallout diff --git a/src/sfall_global_scripts.h b/src/sfall_global_scripts.h new file mode 100644 index 0000000..bf8eadf --- /dev/null +++ b/src/sfall_global_scripts.h @@ -0,0 +1,23 @@ +#ifndef FALLOUT_SFALL_GLOBAL_SCRIPTS_H_ +#define FALLOUT_SFALL_GLOBAL_SCRIPTS_H_ + +#include "interpreter.h" + +namespace fallout { + +bool sfall_gl_scr_init(); +void sfall_gl_scr_reset(); +void sfall_gl_scr_exit(); +void sfall_gl_scr_exec_start_proc(); +void sfall_gl_scr_remove_all(); +void sfall_gl_scr_exec_map_update_scripts(int action); +void sfall_gl_scr_process_main(); +void sfall_gl_scr_process_input(); +void sfall_gl_scr_process_worldmap(); +void sfall_gl_scr_set_repeat(Program* program, int frames); +void sfall_gl_scr_set_type(Program* program, int type); +bool sfall_gl_scr_is_loaded(Program* program); + +} // namespace fallout + +#endif /* FALLOUT_SFALL_GLOBAL_SCRIPTS_H_ */ diff --git a/src/sfall_ini.cc b/src/sfall_ini.cc index ee4aced..91af85c 100644 --- a/src/sfall_ini.cc +++ b/src/sfall_ini.cc @@ -128,19 +128,21 @@ bool sfall_ini_get_string(const char* triplet, char* value, size_t size) loaded = configRead(&config, path, false); } - bool ok = false; + // NOTE: Sfall's `GetIniSetting` returns error code (-1) only when it cannot + // parse triplet. Otherwise the default for string settings is empty string. + value[0] = '\0'; + if (loaded) { char* stringValue; if (configGetString(&config, section, key, &stringValue)) { strncpy(value, stringValue, size - 1); value[size - 1] = '\0'; - ok = true; } } configFree(&config); - return ok; + return true; } } // namespace fallout diff --git a/src/sfall_opcodes.cc b/src/sfall_opcodes.cc index 307a65b..e21a079 100644 --- a/src/sfall_opcodes.cc +++ b/src/sfall_opcodes.cc @@ -21,6 +21,7 @@ #include "proto.h" #include "scripts.h" #include "sfall_arrays.h" +#include "sfall_global_scripts.h" #include "sfall_global_vars.h" #include "sfall_ini.h" #include "sfall_lists.h" @@ -101,6 +102,30 @@ static void op_get_year(Program* program) programStackPushInteger(program, year); } +// game_loaded +static void op_game_loaded(Program* program) +{ + bool loaded = sfall_gl_scr_is_loaded(program); + programStackPushInteger(program, loaded ? 1 : 0); +} + +// set_global_script_repeat +static void op_set_global_script_repeat(Program* program) +{ + int frames = programStackPopInteger(program); + sfall_gl_scr_set_repeat(program, frames); +} + +// key_pressed +static void op_key_pressed(Program* program) +{ + int key = programStackPopInteger(program); + + // TODO: Incomplete. + + programStackPushInteger(program, 0); +} + // in_world_map static void op_in_world_map(Program* program) { @@ -121,6 +146,13 @@ static void opGetCurrentHand(Program* program) programStackPushInteger(program, interfaceGetCurrentHand()); } +// set_global_script_type +static void op_set_global_script_type(Program* program) +{ + int type = programStackPopInteger(program); + sfall_gl_scr_set_type(program, type); +} + // set_sfall_global static void opSetGlobalVar(Program* program) { @@ -851,9 +883,13 @@ void sfallOpcodesInit() interpreterRegisterOpcode(0x815C, op_get_pc_base_stat); interpreterRegisterOpcode(0x815D, opGetPcBonusStat); interpreterRegisterOpcode(0x8163, op_get_year); + interpreterRegisterOpcode(0x8164, op_game_loaded); + interpreterRegisterOpcode(0x816A, op_set_global_script_repeat); + interpreterRegisterOpcode(0x816C, op_key_pressed); interpreterRegisterOpcode(0x8170, op_in_world_map); interpreterRegisterOpcode(0x8172, op_set_world_map_pos); interpreterRegisterOpcode(0x8193, opGetCurrentHand); + interpreterRegisterOpcode(0x819B, op_set_global_script_type); interpreterRegisterOpcode(0x819D, opSetGlobalVar); interpreterRegisterOpcode(0x819E, opGetGlobalInt); interpreterRegisterOpcode(0x81AC, op_get_ini_setting); diff --git a/src/worldmap.cc b/src/worldmap.cc index 9b6ff3d..3243d8d 100644 --- a/src/worldmap.cc +++ b/src/worldmap.cc @@ -41,6 +41,7 @@ #include "scripts.h" #include "settings.h" #include "sfall_config.h" +#include "sfall_global_scripts.h" #include "skill.h" #include "stat.h" #include "string_parsers.h" @@ -2982,6 +2983,10 @@ static int wmWorldMapFunc(int a1) sharedFpsLimiter.mark(); int keyCode = inputGetInput(); + + // SFALL: WorldmapLoopHook. + sfall_gl_scr_process_worldmap(); + unsigned int now = getTicks(); int mouseX;