Feature/moding framework refactor - #4927
Draft
cerwym wants to merge 31 commits into
Draft
Conversation
cerwym
marked this pull request as draft
June 16, 2026 10:18
cerwym
marked this pull request as ready for review
June 16, 2026 10:18
cerwym
marked this pull request as draft
June 16, 2026 10:22
There was a problem hiding this comment.
Pull request overview
This PR introduces a unified modding “tier stack” + walker API and a new platform abstraction layer, then refactors multiple subsystems (config loading, Lua, sprites, textures, landview, sounds) to use these mechanisms instead of ad-hoc per-mod/per-file logic.
Changes:
- Added
kfx/moddingtier-stack + walker APIs (C + C++) and migrated several resource loaders to use walker traversal (including a new batch config loader). - Added
IPlatform+PlatformManagerand moved Windows/Linux entry points, file I/O, and file enumeration behind platform implementations. - Extended sound configuration with a new
[system]section (SPEECH_QUEUE_LIMIT) and wired SoundManager into the load-event lifecycle.
Reviewed changes
Copilot reviewed 44 out of 44 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| src/windows.cpp | Removed legacy Windows-specific entrypoint + file-find implementation (moved to PlatformWindows/PlatformManager). |
| src/linux.cpp | Removed legacy Linux entrypoint + file-find implementation (moved to PlatformLinux/PlatformManager). |
| src/sound_manager.h | SoundManager now implements IModSubsystem and participates in load events. |
| src/sound_manager.cpp | Registers with mod lifecycle; adds load-event handling; updates sound path resolution to include mod sound dirs. |
| src/platform/PlatformWindows.h | Declares Windows IPlatform implementation interface. |
| src/platform/PlatformWindows.cpp | Implements Windows platform services + WinMain that registers the platform singleton. |
| src/platform/PlatformLinux.h | Declares Linux IPlatform implementation interface. |
| src/platform/PlatformLinux.cpp | Implements Linux platform services + main() that registers the platform singleton. |
| src/platform/IWindowSystem.h | Adds stub window-system abstraction interface. |
| src/platform/IPlatform.h | Adds the core platform abstraction interface used by C wrappers. |
| src/platform/IAudioPlatform.h | Adds stub audio-platform abstraction interface. |
| src/platform/PlatformManager.h | Adds platform singleton accessor + C-callable file I/O wrapper declarations. |
| src/platform/PlatformManager.cpp | Implements platform singleton + C-callable wrappers for OS/file APIs. |
| src/main.cpp | Hooks Startup load event dispatch into init path. |
| src/main_game.c | Replaces direct sound snapshot restore with kfx_trigger_load_event(Level). |
| src/lvl_script_lib.c | Uses unified mod API helper to reload creature configs. |
| src/lua_base.c | Refactors Lua script loading to use mod walker traversal instead of per-mod probing. |
| src/kfx/modding/tier_stack.h | Adds tier-stack lifecycle + subsystem registry + event dispatch API. |
| src/kfx/modding/tier_stack.cpp | Implements tier-stack rebuild and event dispatch (including mod existence probing integration). |
| src/kfx/modding/ModWalker.hpp | Declares the C++ tier-stack walker used by both C API and C++ subsystems. |
| src/kfx/modding/ModWalker.cpp | Implements tier traversal for accumulate/find-first/batch patterns. |
| src/kfx/modding/mod_location.h | Defines tier/lifetime/event/resource-type model and KfxTierStack layout. |
| src/kfx/modding/mod_api.h | Adds C API for creating walkers, visiting tiers, batch visits, and load observers. |
| src/kfx/modding/mod_api.cpp | Implements the C API wrappers and load observer TLS stack. |
| src/kfx/modding/IModSubsystem.hpp | Adds C++ subsystem base class to register for load events. |
| src/kfx/modding/IModSubsystem.cpp | Implements registration trampoline for C++ subsystems. |
| src/gui_soundmsgs.h | Exposes g_speech_queue_limit as a configurable runtime variable. |
| src/gui_soundmsgs.cpp | Switches to configurable queue limit; routes certain scripted sounds through SoundManager resolution. |
| src/game_saves.c | Triggers KfxLoadEvent_SaveLoad after restoring save state. |
| src/frontmenu_select.c | Frontend now uses game_request_campaign_change() wrapper. |
| src/front_network.c | Network frontend now uses game_request_campaign_change() wrapper. |
| src/front_landview.c | Landview assets are located via tier-stack walker (campaign tier). |
| src/engine_textures.c | Texture resolution refactored to walker-based find-first across tiers. |
| src/dungeon_stats.c | Uses load_config_batch() and adds load progress notifications per creature. |
| src/custom_sprites.c | Sprite loading refactored to walker-based directory + per-map zip traversal. |
| src/config.h | Introduces ConfigBatchItem and load_config_batch() declaration. |
| src/config.c | Implements load_config_batch() using a single walker traversal for mod overrides; load_config() refactored. |
| src/config_strings.c | Refactors GUI/campaign string overrides to walker-based traversal. |
| src/config_sounds.h | Declares g_speech_queue_limit and documents new config behavior. |
| src/config_sounds.c | Adds parsing for new [system] section and resets speech queue limit during baseline reset. |
| src/config_mods.h | Adds sound exist-state flag; removes public recheck_all_mod_exist() decl. |
| src/config_mods.c | Probes mods/<name>/sound/ as a new existence-checked resource dir. |
| src/config_keeperfx.c | Refactors KeeperFX config overrides to walker-based traversal; removes early mod probing call. |
| src/config_crtrmodel.c | Refactors creature model config loading to walker-based traversal across tiers. |
| src/config_campaigns.h | Adds game_request_campaign_change() wrapper declaration. |
| src/config_campaigns.c | Replaces direct sound reload logic with kfx_trigger_load_event(Campaign); adds wrapper implementation. |
| src/bflib_fileio.c | Converts file I/O to thin delegates into the platform abstraction. |
| Makefile | Adds new modding/platform objects and enforces pre/post include checks for platform sources. |
| linux.mk | Swaps legacy linux.cpp for new platform sources; adds -Isrc include path. |
| config/fxdata/sounds.cfg | Adds [system] section with SPEECH_QUEUE_LIMIT. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+2146
to
+2150
| if (s_config_walker == NULL) | ||
| s_config_walker = kfx_mod_create_walker(s_config_locs, | ||
| sizeof(s_config_locs) / sizeof(s_config_locs[0])); | ||
|
|
||
| if (mods_conf.after_campaign_cnt > 0) | ||
| { | ||
| load_config_for_mod_list(file_data, flags, mods_conf.after_campaign_item, mods_conf.after_campaign_cnt); | ||
| KfxModBatchEntry entries[count]; |
Comment on lines
+2174
to
2180
| TbBool load_config(const struct ConfigFileData* file_data, unsigned short flags) | ||
| { | ||
| const ConfigBatchItem item = { file_data, flags }; | ||
| load_config_batch(&item, 1); | ||
| /* Probe base file to determine success — same semantics as before. */ | ||
| return LbFileLengthRnc(prepare_file_path(FGrp_FxData, file_data->filename)) > 0; | ||
| } |
Comment on lines
921
to
+925
| // Load configs which may have per-campaign part, and even be modified within a level | ||
| recheck_all_mod_exist(); | ||
| init_custom_sprites(SPRITE_LAST_LEVEL); | ||
| load_stats_files(); | ||
| check_and_auto_fix_stats(); | ||
| // Notify all registered subsystems that the game has started. |
Comment on lines
+65
to
+71
| /** | ||
| * Register a subsystem reload callback with the event dispatch system. | ||
| * | ||
| * Registrations persist for the lifetime of the process. The callback is | ||
| * invoked by kfx_trigger_load_event() whenever the event matches the | ||
| * subsystem's declared lifetimes. | ||
| * |
Comment on lines
+42
to
+57
| void kfx_mod_visit_all(KfxModHandle handle, | ||
| const KfxTierStack *stack, | ||
| const char *base_fname, | ||
| ModOnFoundFn cb, void *userdata) | ||
| { | ||
| static_cast<ModWalker *>(handle)->visitAll(stack, base_fname, cb, userdata); | ||
| } | ||
|
|
||
| TbBool kfx_mod_find_first(KfxModHandle handle, | ||
| const KfxTierStack *stack, | ||
| const char *base_fname, | ||
| char *out_path, size_t out_size) | ||
| { | ||
| return static_cast<ModWalker *>(handle)->findFirst(stack, base_fname, out_path, out_size) | ||
| ? 1 : 0; | ||
| } |
Comment on lines
+53
to
+66
| extern "C" TbFileFind* LbFileFindFirst(const char* filespec, TbFileEntry* fentry) | ||
| { | ||
| return PlatformManager::Get()->FileFindFirst(filespec, fentry); | ||
| } | ||
|
|
||
| extern "C" int LbFileFindNext(TbFileFind* ffind, TbFileEntry* fentry) | ||
| { | ||
| return PlatformManager::Get()->FileFindNext(ffind, fentry); | ||
| } | ||
|
|
||
| extern "C" void LbFileFindEnd(TbFileFind* ffind) | ||
| { | ||
| PlatformManager::Get()->FileFindEnd(ffind); | ||
| } |
Comment on lines
+70
to
+88
| extern "C" TbBool PlatformManager_FileExists(const char* path) | ||
| { | ||
| return PlatformManager::Get()->FileExists(path); | ||
| } | ||
|
|
||
| extern "C" int PlatformManager_MakeDirectory(const char* path) | ||
| { | ||
| return PlatformManager::Get()->MakeDirectory(path); | ||
| } | ||
|
|
||
| extern "C" int PlatformManager_GetCurrentDirectory(char* buf, unsigned long buflen) | ||
| { | ||
| return PlatformManager::Get()->GetCurrentDirectory(buf, buflen); | ||
| } | ||
|
|
||
| extern "C" TbFileHandle PlatformManager_FileOpen(const char* fname, unsigned char accmode) | ||
| { | ||
| return PlatformManager::Get()->FileOpen(fname, accmode); | ||
| } |
Comment on lines
+90
to
+103
| extern "C" int PlatformManager_FileClose(TbFileHandle handle) | ||
| { | ||
| return PlatformManager::Get()->FileClose(handle); | ||
| } | ||
|
|
||
| extern "C" int PlatformManager_FileRead(TbFileHandle handle, void* buf, unsigned long len) | ||
| { | ||
| return PlatformManager::Get()->FileRead(handle, buf, len); | ||
| } | ||
|
|
||
| extern "C" long PlatformManager_FileWrite(TbFileHandle handle, const void* buf, unsigned long len) | ||
| { | ||
| return PlatformManager::Get()->FileWrite(handle, buf, len); | ||
| } |
Comment on lines
+105
to
+123
| extern "C" int PlatformManager_FileSeek(TbFileHandle handle, long offset, unsigned char origin) | ||
| { | ||
| return PlatformManager::Get()->FileSeek(handle, offset, origin); | ||
| } | ||
|
|
||
| extern "C" int PlatformManager_FilePosition(TbFileHandle handle) | ||
| { | ||
| return PlatformManager::Get()->FilePosition(handle); | ||
| } | ||
|
|
||
| extern "C" TbBool PlatformManager_FileEof(TbFileHandle handle) | ||
| { | ||
| return PlatformManager::Get()->FileEof(handle); | ||
| } | ||
|
|
||
| extern "C" short PlatformManager_FileFlush(TbFileHandle handle) | ||
| { | ||
| return PlatformManager::Get()->FileFlush(handle); | ||
| } |
Comment on lines
+125
to
+133
| extern "C" long PlatformManager_FileLength(const char* fname) | ||
| { | ||
| return PlatformManager::Get()->FileLength(fname); | ||
| } | ||
|
|
||
| extern "C" int PlatformManager_FileDelete(const char* fname) | ||
| { | ||
| return PlatformManager::Get()->FileDelete(fname); | ||
| } |
cerwym
marked this pull request as ready for review
June 16, 2026 10:22
cerwym
marked this pull request as draft
June 16, 2026 10:34
cerwym
marked this pull request as ready for review
June 16, 2026 10:34
cerwym
marked this pull request as draft
June 16, 2026 10:48
cerwym
marked this pull request as ready for review
June 16, 2026 10:48
cerwym
marked this pull request as draft
June 16, 2026 14:38
benlp91
marked this pull request as ready for review
June 24, 2026 20:37
benlp91
marked this pull request as draft
June 24, 2026 20:42
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request introduces a new, unified modding and platform abstraction layer, refactors configuration loading to use a single walker-based batch traversal, and updates build scripts to support these changes. The main changes are the delegation of all platform-specific file and directory operations to new platform manager classes, the replacement of per-file mod config loading with a more efficient batch process, and the addition of new source files and build rules for platform and modding support.
Platform Abstraction and File I/O Refactor:
bflib_fileio.cnow delegate to thePlatformManagerand its platform-specific implementations (PlatformWindows.cpp,PlatformLinux.cpp). This removes all direct OS-specific logic from the file and centralizes it in the new platform classes. ([[1]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-6a4d0e428017c28b0c6eaa2c1d35583b79af8d13eeee3d5d1c77eeba22d65fc0L8-R10),[[2]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-6a4d0e428017c28b0c6eaa2c1d35583b79af8d13eeee3d5d1c77eeba22d65fc0L25-R99))[[1]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-76ed074a9305c04054cdebb9e9aad2d818052b07091de1f20cad0bbac34ffb52R259-R262),[[2]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-76ed074a9305c04054cdebb9e9aad2d818052b07091de1f20cad0bbac34ffb52L354-R359),[[3]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-0fe2808a49cbac879115dca8774642fe3df413773dd5f75310169713023fe636L183-R184),[[4]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-0fe2808a49cbac879115dca8774642fe3df413773dd5f75310169713023fe636R289),[[5]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-76ed074a9305c04054cdebb9e9aad2d818052b07091de1f20cad0bbac34ffb52R602-R629))Modding and Configuration Loading Overhaul:
config.cis refactored to use a walker-based, 6-tier batch traversal for mod overrides, replacing the previous per-file and per-mod-item logic. This allows loading multiple config files in a single pass through the mod tier stack, improving efficiency and consistency. ([src/config.cL2096-R2179](https://github.com/dkfans/keeperfx/pull/4927/files#diff-18680f57f5d32607a3b196afffe0d6ecb250af6c0a994b304b88fc91231250bfL2096-R2179))load_config_batch()function andConfigBatchItemstruct are introduced inconfig.hto support batch loading of configuration files. ([src/config.hR256-R271](https://github.com/dkfans/keeperfx/pull/4927/files#diff-c24f78b3519d763901eb9f67b864f01d802d803df1b24faaf154019cf812bf95R256-R271))[[1]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-18680f57f5d32607a3b196afffe0d6ecb250af6c0a994b304b88fc91231250bfR46),[[2]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-23513555a9d1310a35d7f4b1e4834bf786fa629680af5711dae0e8d4995693e1L31-R32))Configuration and Sound System:
[system]section is added tosounds.cfgto allow configuration of the speech queue limit, introducing a new modifiable parameter. ([config/fxdata/sounds.cfgR31-R35](https://github.com/dkfans/keeperfx/pull/4927/files#diff-758b71a4dacdaa54c2a9a552fb9bd67463c86a5302b14eb84010c816b66eff7dR31-R35))Most important changes:
Platform Abstraction and File I/O
bflib_fileio.care now delegated to the newPlatformManagerand platform-specific implementations, removing direct OS-specific logic from the file. ([[1]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-6a4d0e428017c28b0c6eaa2c1d35583b79af8d13eeee3d5d1c77eeba22d65fc0L8-R10),[[2]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-6a4d0e428017c28b0c6eaa2c1d35583b79af8d13eeee3d5d1c77eeba22d65fc0L25-R99))[[1]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-76ed074a9305c04054cdebb9e9aad2d818052b07091de1f20cad0bbac34ffb52R259-R262),[[2]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-76ed074a9305c04054cdebb9e9aad2d818052b07091de1f20cad0bbac34ffb52L354-R359),[[3]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-0fe2808a49cbac879115dca8774642fe3df413773dd5f75310169713023fe636L183-R184),[[4]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-0fe2808a49cbac879115dca8774642fe3df413773dd5f75310169713023fe636R289),[[5]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-76ed074a9305c04054cdebb9e9aad2d818052b07091de1f20cad0bbac34ffb52R602-R629))Modding and Configuration Loading
config.cto use a walker-based, 6-tier batch traversal for mod overrides, allowing efficient and consistent loading of multiple config files in a single pass. ([src/config.cL2096-R2179](https://github.com/dkfans/keeperfx/pull/4927/files#diff-18680f57f5d32607a3b196afffe0d6ecb250af6c0a994b304b88fc91231250bfL2096-R2179))load_config_batch()andConfigBatchIteminconfig.hfor batch configuration loading. ([src/config.hR256-R271](https://github.com/dkfans/keeperfx/pull/4927/files#diff-c24f78b3519d763901eb9f67b864f01d802d803df1b24faaf154019cf812bf95R256-R271))[[1]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-18680f57f5d32607a3b196afffe0d6ecb250af6c0a994b304b88fc91231250bfR46),[[2]](https://github.com/dkfans/keeperfx/pull/4927/files#diff-23513555a9d1310a35d7f4b1e4834bf786fa629680af5711dae0e8d4995693e1L31-R32))Configuration and Sound System
[system]section andSPEECH_QUEUE_LIMITparameter tosounds.cfgfor configurable speech queue limits. ([config/fxdata/sounds.cfgR31-R35](https://github.com/dkfans/keeperfx/pull/4927/files#diff-758b71a4dacdaa54c2a9a552fb9bd67463c86a5302b14eb84010c816b66eff7dR31-R35))