Add image overlay support to possession mode lenses#4506
Add image overlay support to possession mode lenses#4506Loobinex merged 23 commits intodkfans:masterfrom
Conversation
Implements overlay image rendering for lens effects, allowing modders to add custom visual overlays (helmets, visors, HUD elements) during creature possession. Features: - RAW (256x256, 8-bit indexed) and PNG/BMP format support - Automatic mod directory detection and fallback to base game - Palette index 0 treated as transparent - Stretch-to-fit scaling for widescreen compatibility - Overlay configuration via lenses.cfg: Overlay = filename.raw alpha Files modified: - src/config_lenses.c/h: Configuration parsing and data structures - src/lens_api.c: Image loading and rendering implementation
There was a problem hiding this comment.
Pull request overview
Adds support for defining and rendering an image overlay as part of a lens effect, intended to be drawn on top of existing mist/distortion lens rendering (e.g., for helmet/visor visuals in possession mode).
Changes:
- Extends
LensConfigwith overlay fields (file, mod dir, in-memory image buffer, dimensions, alpha). - Adds config parsing for a new
OVERLAYdirective inlenses.cfg. - Implements overlay loading (RAW + PNG/BMP) and overlay drawing in the lens render pipeline.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| src/lens_api.c | Loads overlay images and draws them after other lens effects. |
| src/config_lenses.h | Extends LensConfig with overlay-related fields and adds LCF_HasOverlay. |
| src/config_lenses.c | Adds parsing support for OVERLAY = <file> <alpha> and tracks mod-relative paths. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Just let me know if I should test it |
…o feature/lens-overlay
|
I forgot how to trigger a Prototype lol @Loobinex please can you do? |
it just needed approval because Cerwym isn't maintainer |
What with me being new and untrustworthy and all that |
…ook at zip, then mod data, then core data in case of not finding the file
…tion - Created try_load_file_from_mods_with_fallback() helper function - Reduces code duplication for mod->base game file loading pattern - Applied to lens overlay loading as proof of concept - Helper function can be reused for other mod fallback scenarios
- Mist files now check mods' data directories before base game - Consistent mod fallback behavior between overlays and mists - Better logging to show which mod provided the mist file
…ence - Changed from checking fx_data flag to mod_dir flag (allows any mod directory) - Added LbFileExists() check before LbFileLoadAt() to avoid error spam - Fixes crash when loading files that don't exist in all mods - Prevents access violations from failed file loads
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 11 out of 12 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/config.c
Outdated
| // Set the terminator at the actual count position (not at max_count-1) | ||
| if (named_fields_set->names != NULL && named_fields_set->count_field != NULL) | ||
| { | ||
| named_fields_set->names[*named_fields_set->count_field].name = NULL; |
There was a problem hiding this comment.
parse_named_field_blocks() sets the terminator at names[*count_field], but names is only sized to max_count entries in callers (e.g., lenses_desc[LENS_ITEMS_MAX]). If a config defines block index max_count-1, count_field becomes max_count and this writes one past the end. Either allocate names arrays as max_count+1 (space for sentinel) or clamp the terminator write to max_count-1 and adjust semantics accordingly.
| // Set the terminator at the actual count position (not at max_count-1) | |
| if (named_fields_set->names != NULL && named_fields_set->count_field != NULL) | |
| { | |
| named_fields_set->names[*named_fields_set->count_field].name = NULL; | |
| // Set the terminator safely within the allocated range | |
| if (named_fields_set->names != NULL && named_fields_set->count_field != NULL) | |
| { | |
| int terminator_index = *named_fields_set->count_field; | |
| if (terminator_index >= named_fields_set->max_count) | |
| { | |
| terminator_index = named_fields_set->max_count - 1; | |
| } | |
| named_fields_set->names[terminator_index].name = NULL; |
src/lens_api.c
Outdated
|
|
||
| void setup_eye_lens(long nlens) | ||
| { | ||
| WARNLOG("CONFIG_DEBUG: setup_eye_lens called with nlens=%ld", nlens); |
There was a problem hiding this comment.
setup_eye_lens() currently logs "CONFIG_DEBUG" via WARNLOG on every invocation. Since this can happen frequently during play, please switch to SYNCDBG (or gate behind BFDEBUG_LEVEL) so release logs aren’t polluted with warning-level messages.
| } | ||
|
|
||
| if (LbFileLoadAt(fname, cache->data) != size) { | ||
| WARNLOG("Failed to load overlay file '%s' from /data directory", lenscfg->overlay_file); |
There was a problem hiding this comment.
When loading a ".raw" overlay file, this code calls LbFileLoadAt() directly. LbFileLoadAt() reads the full file length into the buffer, so without checking LbFileLengthRnc(fname) first, an oversized file can overflow cache->data before the return-value check runs. Validate size (or reuse try_load_file_from_mods_with_fallback) before loading.
| // Apply lens effect to the viewport area only (not including sidebar) | ||
| draw_lens_effect(lbDisplay.WScreen + view_x, lbDisplay.GraphicsScreenWidth, | ||
| scrmem + view_x, eye_lens_width, view_width, view_height, game.applied_lens_type); |
There was a problem hiding this comment.
draw_lens_effect() is applied only with a +view_x offset. If player->engine_window_y is ever non-zero (eg centered/shifted view), the lens effect will be applied to the wrong region and the buffer pointers will be incorrect. Include view_y and offset dst/src by (view_y * pitch) + view_x.
src/config_lenses.c
Outdated
| SYNCLOG("value_overlay called: argnum=%d, value='%s', lens=%d", named_field->argnum, value_text, idx); | ||
|
|
||
| if (value_text == NULL || value_text[0] == '\0') { |
There was a problem hiding this comment.
value_overlay() uses SYNCLOG for routine parsing traces. This is very noisy during normal config loading; please switch these to SYNCDBG at an appropriate level (or remove once validated) so logs aren’t polluted in non-debug runs.
src/config_lenses.h
Outdated
| #endif | ||
| /******************************************************************************/ | ||
| #define LENS_ITEMS_MAX 32 | ||
| #define LENS_ITEMS_MAX 255 |
There was a problem hiding this comment.
Lenses are indexed starting at 1 (0 is the default entry), and get_lens_config() returns &lenses[lens_idx]. With LENS_ITEMS_MAX set to 255, the highest valid index is 254, but lenses_count can become 255 (from lens254) and an eye_effect of 255 would access lenses[255] out of bounds. If the intent is to support IDs up to 255, LENS_ITEMS_MAX should be 256 (or adjust indexing/count logic to keep indices < LENS_ITEMS_MAX).
| #define LENS_ITEMS_MAX 255 | |
| #define LENS_ITEMS_MAX 256 |
Implements overlay image rendering for lens effects
Overlays can be used with mist / distortion effects, they will be rendered AFTER such and won't be affected
This PR also as per feedback has been extended to allow for support of loading both overlays and mist effects from data, and fxdata and will allow for fallback into the base game's directory too.
Here's a full mod proving the setup.
test-pr-4506.zip
Test Scenarios
mist_zip_testfromlens_mists.zipmist_data_test.rawfrom moddata/overlay_zip_testfromlens_overlays.zipoverlay_data_test.rawfrom moddata/(vertical blue stripes)mist_fallback_test.rawin moddata/overlay_fallback_test.rawin moddata/(checkerboard + red border)Load Order Priority
The
try_load_file_from_mods_with_fallback()helper function searches in this order:test-pr-4506.zip
Verification
To test, possess each creature in-game. Expected results:
Example of overlay and mist effect on a Dragon