From e2909c643085aa76a32042728b2f7727712c7449 Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Wed, 4 Feb 2026 08:04:29 +0000 Subject: [PATCH 1/5] chore(git) add .local/ to ignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index b9771d146e..da0d262840 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ res/keeperfx_icon.ico /src/ver_defs.h keeperfx.log crash_log.txt +.local/ From 98eb4f9e1a12fc2e2f4ac578f8d82d377a6afd14 Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Sun, 8 Feb 2026 10:43:12 +0000 Subject: [PATCH 2/5] fix: correct displacement lenses to use full scrbuf, mist effects use viewport scrbuf --- src/lens_api.c | 30 ++++++++++++++++++++++-------- src/lens_api.h | 2 +- src/thing_creature.c | 3 ++- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/lens_api.c b/src/lens_api.c index 3348508531..06d6d365c8 100644 --- a/src/lens_api.c +++ b/src/lens_api.c @@ -544,11 +544,13 @@ void reinitialise_eye_lens(long nlens) SYNCDBG(18,"Finished"); } -static void draw_displacement_lens(unsigned char *dstbuf, unsigned char *srcbuf, uint32_t *lens_mem, int width, int height, int dstpitch) +static void draw_displacement_lens(unsigned char *dstbuf, unsigned char *srcbuf, uint32_t *lens_mem, int width, int height, int dstpitch, int lens_pitch) { SYNCDBG(16,"Starting"); unsigned char* dst = dstbuf; uint32_t* mem = lens_mem; + // srcbuf is the full screen buffer - pos_map indexes into it correctly + // lens_pitch is the width of the displacement map (full screen width) for (int h = 0; h < height; h++) { for (int w = 0; w < width; w++) @@ -558,6 +560,8 @@ static void draw_displacement_lens(unsigned char *dstbuf, unsigned char *srcbuf, mem++; } dst += dstpitch; + // Skip to the next row in the displacement map if it's wider than our render width + mem += (lens_pitch - width); } } @@ -650,7 +654,7 @@ static void draw_overlay(unsigned char *dstbuf, long dstpitch, long width, long } } -void draw_lens_effect(unsigned char *dstbuf, long dstpitch, unsigned char *srcbuf, long srcpitch, long width, long height, long effect) +void draw_lens_effect(unsigned char *dstbuf, long dstpitch, unsigned char *srcbuf, long srcpitch, long width, long height, long viewport_x, long effect) { long copied = 0; if ((effect < 1) || (effect > lenses_conf.lenses_count)) @@ -660,9 +664,13 @@ void draw_lens_effect(unsigned char *dstbuf, long dstpitch, unsigned char *srcbu effect = 0; } struct LensConfig* lenscfg = &lenses_conf.lenses[effect]; + + // For sequential operations (mist, copy), offset srcbuf by viewport_x + unsigned char* viewport_srcbuf = srcbuf + viewport_x; + if ((lenscfg->flags & LCF_HasMist) != 0) { - draw_mist(dstbuf, dstpitch, srcbuf, srcpitch, width, height); + draw_mist(dstbuf, dstpitch, viewport_srcbuf, srcpitch, width, height); copied = true; } if ((lenscfg->flags & LCF_HasDisplace) != 0) @@ -671,12 +679,18 @@ void draw_lens_effect(unsigned char *dstbuf, long dstpitch, unsigned char *srcbu { case 1: case 2: - draw_displacement_lens(dstbuf, srcbuf, eye_lens_memory, - width, height, dstpitch); + // For displacement, use full srcbuf (not offset) because displacement map contains absolute positions + // Offset the displacement map to start at the viewport position + long full_width = eye_lens_width; + uint32_t* viewport_lens_mem = eye_lens_memory + viewport_x; + draw_displacement_lens(dstbuf, srcbuf, viewport_lens_mem, + width, height, dstpitch, full_width); copied = true; break; case 3: - flyeye_blitsec(srcbuf, srcpitch, dstbuf, dstpitch, 1, height); + // Flyeye effect uses its own optimized blitting function that handles displacement internally + // It also expects the full srcbuf and uses the viewport_x internally for lookups, so we pass the full srcbuf + flyeye_blitsec(viewport_srcbuf, srcpitch, dstbuf, dstpitch, 1, height); copied = true; break; } @@ -691,7 +705,7 @@ void draw_lens_effect(unsigned char *dstbuf, long dstpitch, unsigned char *srcbu // First, copy the source buffer to destination if not already done if (!copied) { - draw_copy(dstbuf, dstpitch, srcbuf, srcpitch, width, height); + draw_copy(dstbuf, dstpitch, viewport_srcbuf, srcpitch, width, height); } // Load overlay if not already loaded if (load_overlay_image(effect)) { @@ -704,7 +718,7 @@ void draw_lens_effect(unsigned char *dstbuf, long dstpitch, unsigned char *srcbu // If we haven't copied the buffer to screen yet, do so now if (!copied) { - draw_copy(dstbuf, dstpitch, srcbuf, srcpitch, width, height); + draw_copy(dstbuf, dstpitch, viewport_srcbuf, srcpitch, width, height); } } diff --git a/src/lens_api.h b/src/lens_api.h index bf0b418df4..2789c83cad 100644 --- a/src/lens_api.h +++ b/src/lens_api.h @@ -41,7 +41,7 @@ void initialise_eye_lenses(void); void setup_eye_lens(long nlens); void reinitialise_eye_lens(long nlens); void reset_eye_lenses(void); -void draw_lens_effect(unsigned char *dstbuf, long dstpitch, unsigned char *srcbuf, long srcpitch, long width, long height, long effect); +void draw_lens_effect(unsigned char *dstbuf, long dstpitch, unsigned char *srcbuf, long srcpitch, long width, long height, long viewport_x, long effect); /******************************************************************************/ #ifdef __cplusplus } diff --git a/src/thing_creature.c b/src/thing_creature.c index 1d82e3ca6b..93e3883f76 100644 --- a/src/thing_creature.c +++ b/src/thing_creature.c @@ -4286,8 +4286,9 @@ void draw_creature_view(struct Thing *thing) // Draw the buffer on real screen using actual viewport dimensions setup_engine_window(0, 0, MyScreenWidth, MyScreenHeight); // Apply lens effect to the viewport area only (not including sidebar) + // Pass full srcbuf so displacement map lookups work correctly draw_lens_effect(lbDisplay.WScreen + view_x, lbDisplay.GraphicsScreenWidth, - scrmem + view_x, eye_lens_width, view_width, view_height, game.applied_lens_type); + scrmem, eye_lens_width, view_width, view_height, view_x, game.applied_lens_type); } struct Thing *get_creature_near_for_controlling(PlayerNumber plyr_idx, MapCoord x, MapCoord y) From 873cbf51b9bd41788fd6938092da1823b5b88cee Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Sun, 8 Feb 2026 11:40:25 +0000 Subject: [PATCH 3/5] feat(lens): mist speed is now configurable --- config/fxdata/lenses.cfg | 27 +++++++++++++++++++++++++-- src/config_lenses.c | 9 ++++----- src/config_lenses.h | 4 ++++ src/lens_api.c | 6 +++++- src/lens_mist.cpp | 24 +++++++++++++++--------- src/lens_mist.h | 4 +++- 6 files changed, 56 insertions(+), 18 deletions(-) diff --git a/config/fxdata/lenses.cfg b/config/fxdata/lenses.cfg index 78e9ae1586..403fa4cafb 100644 --- a/config/fxdata/lenses.cfg +++ b/config/fxdata/lenses.cfg @@ -29,8 +29,16 @@ Displacement = 3 1 1 [lens4] Name = MIST_WATER -; Mist effect has three parameters: file name of a RAW texture (or name of a custom lens), -; lightness scaled 0..63, and ghost position (irrelevant) +; Mist effect parameters: +; 1. File name of a RAW texture (or name of a custom lens) +; 2. Lightness scaled 0..63 (0=darkest, 63=lightest) +; 3. Ghost position 0..255 (transparency effect) +; 4-7. Optional animation parameters (0..255): +; 4. Primary layer X movement speed (default: 2) +; 5. Primary layer Y movement speed (default: 1) +; 6. Secondary layer X movement speed (default: 253) +; 7. Secondary layer Y movement speed (default: 3) +; Set all to 0 for static/frozen mist (no animation) Mist = frac00.raw 0 0 [lens5] @@ -73,3 +81,18 @@ Palette = dogpal.pal [lens14] Name = LENS_VAMPIRE Palette = vampal.pal + +[lens15] +Name = OVERLAY_EXAMPLE +; Overlay effect parameters (can be combined with Mist): +; 1. File name of a RAW texture (or name of a custom lens overlay) +; 2. Alpha transparency 0..255 (0=transparent, 255=fully opaque) +; Note: Palette index 255 in overlay files is treated as transparent +Overlay = frac00.raw 128 + +[lens16] +Name = OVERLAY_WITH_MIST +; Example of combining Overlay with frozen Mist for layered effects +; Mist is rendered first, then Overlay is drawn on top +Mist = frac02.raw 10 0 0 0 0 0 +Overlay = frac03.raw 192 diff --git a/src/config_lenses.c b/src/config_lenses.c index 771b90391d..c99e2a2c23 100644 --- a/src/config_lenses.c +++ b/src/config_lenses.c @@ -58,6 +58,10 @@ const struct NamedField lenses_data_named_fields[] = { {"MIST", 0, field(lenses_conf.lenses[0].mist_file), 0, 0, 0, NULL, value_mist, assign_null}, {"MIST", 1, field(lenses_conf.lenses[0].mist_lightness), 0, 0, 63, NULL, value_default, assign_default}, {"MIST", 2, field(lenses_conf.lenses[0].mist_ghost), 0, 0, 255, NULL, value_default, assign_default}, + {"MIST", 3, field(lenses_conf.lenses[0].mist_pos_x_step), 2, 0, 255, NULL, value_default, assign_default}, + {"MIST", 4, field(lenses_conf.lenses[0].mist_pos_y_step), 1, 0, 255, NULL, value_default, assign_default}, + {"MIST", 5, field(lenses_conf.lenses[0].mist_sec_x_step), 253, 0, 255, NULL, value_default, assign_default}, + {"MIST", 6, field(lenses_conf.lenses[0].mist_sec_y_step), 3, 0, 255, NULL, value_default, assign_default}, {"DISPLACEMENT", 0, field(lenses_conf.lenses[0].displace_kind), 0, 0, 255, NULL, value_default, assign_default}, {"DISPLACEMENT", 1, field(lenses_conf.lenses[0].displace_magnitude), 0, 0, 511, NULL, value_default, assign_default}, {"DISPLACEMENT", 2, field(lenses_conf.lenses[0].displace_period), 1, 0, 511, NULL, value_displace, assign_default}, @@ -124,11 +128,6 @@ static int64_t value_overlay(const struct NamedField* named_field, const char* v lenses_conf.lenses[idx].flags |= LCF_HasOverlay; struct LensConfig* lenscfg = &lenses_conf.lenses[idx]; - // Clear mist flag and mist_file if this lens only has an overlay - // This prevents garbage mist data from previous lens configurations - lenscfg->flags &= ~LCF_HasMist; - lenscfg->mist_file[0] = '\0'; - // Only store the overlay name when processing position 0 (the name field) // Position 1 is the alpha value, handled by value_default if (named_field->argnum == 0) diff --git a/src/config_lenses.h b/src/config_lenses.h index 299ae8fae5..7ffa8bcaea 100644 --- a/src/config_lenses.h +++ b/src/config_lenses.h @@ -45,6 +45,10 @@ struct LensConfig { short mist_lightness; short mist_ghost; char mist_file[DISKPATH_SIZE]; + short mist_pos_x_step; + short mist_pos_y_step; + short mist_sec_x_step; + short mist_sec_y_step; short displace_kind; short displace_magnitude; short displace_period; diff --git a/src/lens_api.c b/src/lens_api.c index 06d6d365c8..609b6edb8c 100644 --- a/src/lens_api.c +++ b/src/lens_api.c @@ -494,7 +494,11 @@ void setup_eye_lens(long nlens) setup_mist((unsigned char *)eye_lens_memory, &pixmap.fade_tables[(lenscfg->mist_lightness)*256], - &pixmap.ghost[(lenscfg->mist_ghost)*256]); + &pixmap.ghost[(lenscfg->mist_ghost)*256], + (unsigned char)lenscfg->mist_pos_x_step, + (unsigned char)lenscfg->mist_pos_y_step, + (unsigned char)lenscfg->mist_sec_x_step, + (unsigned char)lenscfg->mist_sec_y_step); } if ((lenscfg->flags & LCF_HasDisplace) != 0) { diff --git a/src/lens_mist.cpp b/src/lens_mist.cpp index d69f32797e..1625c32afc 100644 --- a/src/lens_mist.cpp +++ b/src/lens_mist.cpp @@ -30,7 +30,9 @@ class CMistFade { public: CMistFade(void); virtual ~CMistFade(void); - void setup(unsigned char *lens_mem, unsigned char *fade, unsigned char *ghost); + void setup(unsigned char *lens_mem, unsigned char *fade, unsigned char *ghost, + unsigned char pos_x_step, unsigned char pos_y_step, + unsigned char sec_x_step, unsigned char sec_y_step); void animset(long a1, long a2); void mist(unsigned char *dstbuf, long dstwidth, unsigned char *srcbuf, long srcwidth, long width, long height); void animate(void); @@ -56,7 +58,9 @@ class CMistFade { CMistFade *mist = NULL; /******************************************************************************/ -void CMistFade::setup(unsigned char *lens_mem, unsigned char *fade, unsigned char *ghost) +void CMistFade::setup(unsigned char *lens_mem, unsigned char *fade, unsigned char *ghost, + unsigned char pos_x_step, unsigned char pos_y_step, + unsigned char sec_x_step, unsigned char sec_y_step) { this->lens_data = lens_mem; this->fade_data = fade; @@ -68,10 +72,10 @@ void CMistFade::setup(unsigned char *lens_mem, unsigned char *fade, unsigned cha this->secondary_offset_y = 128; this->animation_speed = 1024; this->animation_counter = 0; - this->position_x_step = 2; - this->position_y_step = 1; - this->secondary_x_step = 253; - this->secondary_y_step = 3; + this->position_x_step = pos_x_step; + this->position_y_step = pos_y_step; + this->secondary_x_step = sec_x_step; + this->secondary_y_step = sec_y_step; } void CMistFade::animset(long a1, long a2) @@ -161,7 +165,7 @@ void CMistFade::mist(unsigned char *dstbuf, long dstpitch, unsigned char *srcbuf CMistFade::CMistFade(void) { - setup(NULL, NULL, NULL); + setup(NULL, NULL, NULL, 2, 1, 253, 3); } CMistFade::~CMistFade(void) @@ -181,12 +185,14 @@ TbBool draw_mist(unsigned char *dstbuf, long dstpitch, unsigned char *srcbuf, lo return true; } -void setup_mist(unsigned char *lens_mem, unsigned char *fade, unsigned char *ghost) +void setup_mist(unsigned char *lens_mem, unsigned char *fade, unsigned char *ghost, + unsigned char pos_x_step, unsigned char pos_y_step, + unsigned char sec_x_step, unsigned char sec_y_step) { SYNCDBG(8,"Starting"); if (mist == NULL) mist = new CMistFade(); - mist->setup(lens_mem, fade, ghost); + mist->setup(lens_mem, fade, ghost, pos_x_step, pos_y_step, sec_x_step, sec_y_step); mist->animset(0, 1024); } diff --git a/src/lens_mist.h b/src/lens_mist.h index d11a721c57..f7a3d44d44 100644 --- a/src/lens_mist.h +++ b/src/lens_mist.h @@ -26,7 +26,9 @@ extern "C" { #endif /******************************************************************************/ -void setup_mist(unsigned char *lens_mem, unsigned char *fade, unsigned char *ghost); +void setup_mist(unsigned char *lens_mem, unsigned char *fade, unsigned char *ghost, + unsigned char pos_x_step, unsigned char pos_y_step, + unsigned char sec_x_step, unsigned char sec_y_step); TbBool draw_mist(unsigned char *dstbuf, long dstpitch, unsigned char *srcbuf, long srcpitch, long width, long height); void free_mist(void); /******************************************************************************/ From 3e7928186a3a73367d39823a6b78c9fe9b22ea4e Mon Sep 17 00:00:00 2001 From: Cerwym <1760289+Cerwym@users.noreply.github.com> Date: Sun, 8 Feb 2026 12:57:20 +0000 Subject: [PATCH 4/5] fix(lens): always ensure mist is cleared to reset fade, always clear spare scrbuf --- src/lens_api.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lens_api.c b/src/lens_api.c index 609b6edb8c..74f8a46415 100644 --- a/src/lens_api.c +++ b/src/lens_api.c @@ -452,6 +452,8 @@ void setup_eye_lens(long nlens) } if (nlens == 0) { + // Switching to no lens - free mist to reset state + free_mist(); game.active_lens_type = 0; game.applied_lens_type = 0; return; @@ -461,6 +463,13 @@ void setup_eye_lens(long nlens) game.applied_lens_type = nlens; return; } + + // Clear the spare screen buffer when switching lenses to prevent corruption + // This ensures we don't have stale data from previous lens renders + if (eye_lens_spare_screen_memory != NULL) { + memset(eye_lens_spare_screen_memory, 0, (eye_lens_width * eye_lens_height) * sizeof(TbPixel)); + } + struct LensConfig* lenscfg = get_lens_config(nlens); if ((lenscfg->flags & LCF_HasMist) != 0) { From 63e205d3f04d2c04a053b19737935172e2dba57d Mon Sep 17 00:00:00 2001 From: Loobinex Date: Sun, 8 Feb 2026 16:46:26 +0100 Subject: [PATCH 5/5] Removed example lenses --- config/fxdata/lenses.cfg | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/config/fxdata/lenses.cfg b/config/fxdata/lenses.cfg index 403fa4cafb..b7ba30d3a1 100644 --- a/config/fxdata/lenses.cfg +++ b/config/fxdata/lenses.cfg @@ -13,9 +13,11 @@ Name = LENS_WIBBLE ; For displacement kind=1, following parameters are: ; magnitude and period Displacement = 1 9 10 -; Overlay has two parameters: file name of a RAW texture (or name of a custom lens), -; Alpha transparency scaled 0..255, where 255 is fully opaque -; Overlay = frac00.raw 0 +; Overlay effect parameters (can be combined with Mist, which renders first): +; 1. File name of a RAW texture (or name of a custom lens overlay) +; 2. Alpha transparency 0..255 (0=transparent, 255=fully opaque) +; Note: Palette index 255 in overlay files is treated as transparent +; Overlay = frac00.raw 0 [lens2] Name = FISH_EYE @@ -39,7 +41,7 @@ Name = MIST_WATER ; 6. Secondary layer X movement speed (default: 253) ; 7. Secondary layer Y movement speed (default: 3) ; Set all to 0 for static/frozen mist (no animation) -Mist = frac00.raw 0 0 +Mist = frac00.raw 0 0 2 1 253 3 [lens5] Name = MIST_TWO_LAYERED_WATER @@ -81,18 +83,3 @@ Palette = dogpal.pal [lens14] Name = LENS_VAMPIRE Palette = vampal.pal - -[lens15] -Name = OVERLAY_EXAMPLE -; Overlay effect parameters (can be combined with Mist): -; 1. File name of a RAW texture (or name of a custom lens overlay) -; 2. Alpha transparency 0..255 (0=transparent, 255=fully opaque) -; Note: Palette index 255 in overlay files is treated as transparent -Overlay = frac00.raw 128 - -[lens16] -Name = OVERLAY_WITH_MIST -; Example of combining Overlay with frozen Mist for layered effects -; Mist is rendered first, then Overlay is drawn on top -Mist = frac02.raw 10 0 0 0 0 0 -Overlay = frac03.raw 192