Author Topic: Reshading BattleUnits  (Read 1383 times)

Offline Ranakastrasz

  • Captain
  • ***
  • Posts: 53
    • View Profile
Reshading BattleUnits
« on: September 12, 2023, 07:24:45 pm »
So I am trying to write a script to adjust the shading of battleunits based on the background lighting, so as to highlight the difference between illuminated and not. However, using the Hit flash script and API
I have to say I am uncertain how to do it.

I can set color or set shade and white color, but I can't figure out what the original shade was, or what changing the shade does exactly. It also seems to strip the color (or set to white) when I set_shade but not set_color. Also, old_pixel doesn't appear to be the previous frame's "pixel" but rather full transparency status.

I probably have a significant misunderstanding of either how shades work, as well as what new_pixel does(I thought it was essentially a shift from the original sprite image moving right on a pallet for each pixel)

I've tried several different things, and this one works, but isn't ideal.
Code: [Select]
extended:
  scripts:
    recolorUnitSprite:
      - offset: 2
        code: |
          var int tileShade;
          var int newShade;
         
          unit.getTileShade tileShade;
         
         
       
          if gt tileShade 9;
            set_color new_pixel 4;
            set_shade new_pixel 12;
          else;
           
          end;
         
         
          return new_pixel;
« Last Edit: September 12, 2023, 07:43:04 pm by Ranakastrasz »

Offline Yankes

  • Commander
  • *****
  • Posts: 3228
    • View Profile
Re: Reshading BattleUnits
« Reply #1 on: September 12, 2023, 08:18:17 pm »
`old_pixel` is color of background, is used when you want have some pseudo transparency effects.
You need set both `set_shade` and `set_color` as both affect final color index of pixel (this is effective `index = (shade & 0x0F) | (index & 0xF0)` and `index = ((color << 4) & 0xF0) | (index & 0x0F)`).
You can too set value directly like `set new_pixel 232;`.

Another thing is that global scripts are not perfect place to alter shade, as in most cases basic default script set shade past 16 that make pixel pitch black.
If you use `offset: -2` then your code will be run before basic script and have original pixel value but you should be careful as right after your script will be run default one and it will try to apply normal shade.

Only (bad) solution for now is override all basic script in each unit but this will easy conflict with any other mod that try change this scripts.
I will think if there is better solution for this.

Offline Ranakastrasz

  • Captain
  • ***
  • Posts: 53
    • View Profile
Re: Reshading BattleUnits
« Reply #2 on: September 12, 2023, 09:41:08 pm »
`old_pixel` is color of background, is used when you want have some pseudo transparency effects.
Understood. Useless here then. Explains the invisiblity though.
Quote
You need set both `set_shade` and `set_color` as both affect final color index of pixel (this is effective `index = (shade & 0x0F) | (index & 0xF0)` and `index = ((color << 4) & 0xF0) | (index & 0x0F)`).
I have both being used. What is the "Color index" and what is "Pixel"?
So set_shade sets the 1s place, while set_color sets the 16s place? Since its essentially a lookup table for the pallet imagine file. By default both are set to 0, because that is 0, and running both should set the full value.

What does setting the new_pixel actually do? It looks like, for the sprite being drawn, setting color sets the color for each drawn pixel. but setting both doesn't always set it all to a solid singular color, so it offsets the original selected color pixel in the pallet?

What value would I set to leave the entire thing unchanged?

Essentially I am confused on the mechanics.

Quote
You can too set value directly like `set new_pixel 232;`.
Yep, but same thing either way, and the set method sets both separate and I don't have to work out the result manually. Might simplify later admittedly.
Quote
Another thing is that global scripts are not perfect place to alter shade, as in most cases basic default script set shade past 16 that make pixel pitch black.
If you use `offset: -2` then your code will be run before basic script and have original pixel value but you should be careful as right after your script will be run default one and it will try to apply normal shade.
Offset says when it runs, and base code runs at offset 0, and all scripts run in ascending order.
Since that base code reshades/colors, I can't run it before 0, since that would just be overwritten by basecode. Not sure what you mean by be careful, because it wouldn't do anything, right?
Quote

Only (bad) solution for now is override all basic script in each unit but this will easy conflict with any other mod that try change this scripts.
As in full override whatever draw function is there?
« Last Edit: September 12, 2023, 09:58:33 pm by Ranakastrasz »

Offline Yankes

  • Commander
  • *****
  • Posts: 3228
    • View Profile
Re: Reshading BattleUnits
« Reply #3 on: September 12, 2023, 10:51:16 pm »
What is the "Color index" and what is "Pixel"?
So set_shade sets the 1s place, while set_color sets the 16s place? Since its essentially a lookup table for the pallet imagine file. By default both are set to 0, because that is 0, and running both should set the full value.
I use them interchangeably as in case of OXC graphic can mean same.
Simply each pixel of screen is drawn based on "Color index", and each index point to one position in current palette that game use.
In case of OXC you can arrange this palette in 16x16 table (max 255 values plus transaprent). And yes, row is set by `set_color` and column by `set_shade`
Here you have linear presentation where you can see this row than have same color:
https://www.ufopaedia.org/index.php/PALETTES.DAT
Some post on this forum show same palette as 16x16 table, probably best look for "safe palette".




What does setting the new_pixel actually do? It looks like, for the sprite being drawn, setting color sets the color for each drawn pixel. but setting both doesn't always set it all to a solid singular color, so it offsets the original selected color pixel in the pallet?

What value would I set to leave the entire thing unchanged?
game try draw some pixel from unit image, then this script hook intercept this draw operation and allows user to override its value before game draw this pixel of screen. What you set on `new_pixel ` will be final color on screen (of corse if you exit script using `return new_pixel;` as this determine what will be used).
Setting both should set final pixel to given color, of corse there could be another script that alter it further.


Essentially I am confused on the mechanics.
Yep, but same thing either way, and the set method sets both separate and I don't have to work out the result manually. Might simplify later admittedly.Offset says when it runs, and base code runs at offset 0, and all scripts run in ascending order.
Since that base code reshades/colors, I can't run it before 0, since that would just be overwritten by basecode. Not sure what you mean by be careful, because it wouldn't do anything, right?As in full override whatever draw function is there?
Your description is mostly right but "default script" work bit different, it use value that could be prepared by script that run on `offset: -1`.
Code: [Select]
unit.getRecolor new_pixel;
add_burn_shade new_pixel burn shade;
return new_pixel;
here current implementation. Both `unit.getRecolor` and `add_burn_shade` in most cases leave `new_pixel` untouched, if you alter it before, this function will think that was original pixel from unit image.
But as I mention before this will not work good if you try remove shade that is lot bigger than current shade of pixel as values will clip value range accessible for shade, some thing like `min(max(original - 8) + 8, 15)` if value `original < 8`  than result will be always `8`.

Offline Ranakastrasz

  • Captain
  • ***
  • Posts: 53
    • View Profile
Re: Reshading BattleUnits
« Reply #4 on: September 15, 2023, 05:07:27 am »
Alright, I think I mostly understand this, and have something that works better now. That said, Not sure how I would go about adjusting things before the initial function run, or override it. As is, I at least recover the small amount of remaining detail now, but obviously would prefer keeping all of it if possible.

I'm probably still doing this wrong anyway, and am unsure how to not have the green color get brighter the darker the tile is. I understand why it happens, shade caps at 15, so the darker the tile is, the more I increase the brightness. Which is suboptimal, but unsure how to fix that.

Also, if I try and brighten unit's it invariably seems to make them kinda glow, even if I just add a small amount, when the target is only slightly shaded. Always ends up washed out. I thought each point of shade offset all colors by 1, and reversing that, unless you fall off the 1-15 range, would undo the effect.

Also, is there a way to print to screen instead of log, or chose a specific pixel to log, and only once per "Change"? This is worse that Wc3 debugging. Or just debug message best practice?

Code: [Select]
extended:
  scripts:
    recolorUnitSprite:
      - offset: 1
        code: |
          var int tileShade;
          var int oldShade;
          var int newShade;
         
          unit.getTileShade tileShade;
          unit.getRecolor new_pixel;
          get_shade oldShade new_pixel;
         
          if le tileShade 9;
            # Fully Lit. Not on border.
            if ge tileShade 6;
           
              ## Reduce shade.
              #set newShade oldShade;
              #sub newShade tileShade;
              #add newShade 4;
              #limit newShade 1 15;
              #set_shade new_pixel newShade;
             
           
            end;
           
          else eq tileShade 10;
            # dark, on border. Leave as is.
           
            set newShade oldShade;
            sub newShade tileShade;
            add newShade 4;
            limit newShade 1 15;
            set_shade new_pixel newShade;
           
          else gt tileShade 10;
            # Dark, and not on border. Greenlight it, but keep it dark-ish
            set_color new_pixel 4;
           
            set newShade oldShade;
            sub newShade tileShade;
            add newShade 6;
            limit newShade 1 15;
            set_shade new_pixel newShade;
          end;
         
          return new_pixel;

« Last Edit: September 15, 2023, 05:21:09 am by Ranakastrasz »

Offline Yankes

  • Commander
  • *****
  • Posts: 3228
    • View Profile
Re: Reshading BattleUnits
« Reply #5 on: September 15, 2023, 12:03:12 pm »
Also, is there a way to print to screen instead of log, or chose a specific pixel to log, and only once per "Change"? This is worse that Wc3 debugging. Or just debug message best practice?
One problem is that script hook is call by game around 60*10*48*32*5 times per second. Without some filtering no solution will work fine.
For outputting some messages in screen, I do not know if is possible to easy access screen from script internals, for now I use game log as it globally available and script can directly access it. Maybe in future I will look on this and see how easy would be adding some thing like this.

For "glow", could you show example of this?


Offline Ranakastrasz

  • Captain
  • ***
  • Posts: 53
    • View Profile
Re: Reshading BattleUnits
« Reply #6 on: September 15, 2023, 06:07:29 pm »
The glow is when the shade is enough to drop an already dark part.of the sprite to index 15 rather than past it,, and then trying to counter the shade by offsetting back by the same amount. Since it overflowed and was capped, that part of the sprite.got brighter than the original image, so the border and other dark parts all end up a lot lighter, to the point of a glow illusion. Makes it look washed out and wrong.

It is like trying to recover from a lossy compression. You can kinda do it, but there are always limits since the data is gone.
Only solution I can see is to somehow use the original sprite instead of the post-shade sprite

As for logging, yea, it runs way too often for not using a filter, but I have no idea how I would run the log on on, say, entering a new tile, once for each pixel.

Mod objective, if it wasn't clear, is to make it blatantly obvious whether a unit is illuminated or not, and if moving one step forward would change that. Initial intent was to fully override all shading to just have lit (0 shade) dark (8 shade) and black mask (15 shade/not drawn)
But with api limits, I seem to be limited to unit shading, so just showing if lit and if on border between light and dark seems best I can manage.
« Last Edit: September 15, 2023, 06:28:47 pm by Ranakastrasz »

Offline Yankes

  • Commander
  • *****
  • Posts: 3228
    • View Profile
Re: Reshading BattleUnits
« Reply #7 on: September 15, 2023, 07:11:34 pm »
The glow is when the shade is enough to drop an already dark part.of the sprite to index 15 rather than past it,, and then trying to counter the shade by offsetting back by the same amount. Since it overflowed and was capped, that part of the sprite.got brighter than the original image, so the border and other dark parts all end up a lot lighter, to the point of a glow illusion. Makes it look washed out and wrong.

It is like trying to recover from a lossy compression. You can kinda do it, but there are always limits since the data is gone.
Only solution I can see is to somehow use the original sprite instead of the post-shade sprite
ok, right, I couple times consider how best fix problems like this, when one script change some data and another cant restore previous state.
I lean to solution to allow transfer some "shared variables" that are accessible between different global scripts in same hook.
With this you could intercept pixel value before default script alter it, and after that you could run your script using this unaltered value.



As for logging, yea, it runs way too often for not using a filter, but I have no idea how I would run the log on on, say, entering a new tile, once for each pixel.

Most cases this information are not available. In long run "shared variables" could in theory fix this problem too,
if lifetime of "shared variables" is for whole surface draw on screen, then you can reduce log spam from 48x32 times to 1 and with this other filter options (hardcoding unit ID and skipping majority of animations frames) could make logs more useful for your case.


Mod objective, if it wasn't clear, is to make it blatantly obvious whether a unit is illuminated or not, and if moving one step forward would change that. Initial intent was to fully override all shading to just have lit (0 shade) dark (8 shade) and black mask (15 shade/not drawn)
But with api limits, I seem to be limited to unit shading, so just showing if lit and if on border between light and dark seems best I can manage.
I will look on this, but proper solution (shared variables) could take long time to implement correctly.

Offline Ranakastrasz

  • Captain
  • ***
  • Posts: 53
    • View Profile
Re: Reshading BattleUnits
« Reply #8 on: September 16, 2023, 09:10:41 pm »
What is burn?

Why not just expose the base sprite's color as something you can read?

Does the script run all offsets of a single pixel, or all offsets of a value for all pixels before repeating? In the former case, might be able to save data to a variable of some kind. However, any time I tried to have two scrips with different offsets, the second one wouldn't run, and wiki page has no examples of it.

And the really big question, why is indentation part of the script? If the indent isn't perfect the whole thing breaks, which is just so weird.

Offline Ranakastrasz

  • Captain
  • ***
  • Posts: 53
    • View Profile
Re: Reshading BattleUnits
« Reply #9 on: September 17, 2023, 03:44:27 am »
Well, it works well in vanilla, but when it runs with TheXcomFiles, it flickers between white and green shaded when a unit is moving. Obvious interference, and given how much shade shifts for shields, damage flash, daze, and probably other reasons I shouldn't be surprised, but I have no idea why it is flicking, and only when moving, when the units should have no other shade adjustment.
« Last Edit: September 17, 2023, 04:01:20 am by Ranakastrasz »

Offline Yankes

  • Commander
  • *****
  • Posts: 3228
    • View Profile
Re: Reshading BattleUnits
« Reply #10 on: September 17, 2023, 10:39:12 am »
`burn` is functionality added for overkill damage, it cause most pixel change to black and black pixel to transparent:
https://www.youtube.com/watch?v=MnKh1nU4XZU

Script with same offset should both run, offset is only for defining precise order of execution.

For indentation, its ignored but you probably forget that you edit yaml file, where is not. You need obey yaml file indentation other wise scrip it self will be chop in pieces.

For flicking, because you use wrong value for shade, tile shade is not used when unit move between tiles, its use "proportion" of destitution and source tile shade

Offline Ranakastrasz

  • Captain
  • ***
  • Posts: 53
    • View Profile
Re: Reshading BattleUnits
« Reply #11 on: September 17, 2023, 08:13:45 pm »
I got the two scripts to run. Also, it doesn't look like I can set a tag from recolorUnitSprite, to potentially transfer the original shade, since it is a readonly pointer. Given a test, it looks like it does each pixel at all offsets before moving to the next, but since I can't save that data, I can't just reference the original value. Also looks like global variables are readonly as well, and might not be possible to add to.

It is a .rul file, but it uses yaml rules? Probably a Notepad++ plugin to errorcheck exists.

Flickering. if it is averaged, shouldn't be able to change by more than 1, and not constant flicker back and forth when pitch-black. Also, it only occurs with TheXcomFiles active, rather than vanilla...
Reason is because of "personal Light" being exactly one below light threshhold, so each tile you move it goes from 9 to 10, so flickers between the two states. Just have to make sure to disable personal lights and it fixes it.

Offline Yankes

  • Commander
  • *****
  • Posts: 3228
    • View Profile
Re: Reshading BattleUnits
« Reply #12 on: September 18, 2023, 11:13:08 am »
I got the two scripts to run. Also, it doesn't look like I can set a tag from recolorUnitSprite, to potentially transfer the original shade, since it is a readonly pointer. Given a test, it looks like it does each pixel at all offsets before moving to the next, but since I can't save that data, I can't just reference the original value. Also looks like global variables are readonly as well, and might not be possible to add to.

It is a .rul file, but it uses yaml rules? Probably a Notepad++ plugin to errorcheck exists.

Flickering. if it is averaged, shouldn't be able to change by more than 1, and not constant flicker back and forth when pitch-black. Also, it only occurs with TheXcomFiles active, rather than vanilla...
Reason is because of "personal Light" being exactly one below light threshhold, so each tile you move it goes from 9 to 10, so flickers between the two states. Just have to make sure to disable personal lights and it fixes it.
there is VS code plugin that check OXC and OXCE yaml rule files, and I recall there is another plugin that is dedicated to my y-scripts.

for constnes is partially by design, most script can't alter state aside from return value. For now as I previously said, I working on new script feature that allow share some state between different script hooks in one script call, this mean you could save in one "offset" and load it in another "offset".
As bonus all scripts hook calls for one surface will be consider one "script call" and this should allow making bit more complex logic as you could avoid repating same calcinations for every pixel of surface you blit.

for personal light, I think this should be fixed in engine as light should be "carried" by unit not tile where it was.

Online Delian

  • Colonel
  • ****
  • Posts: 268
    • View Profile
Re: Reshading BattleUnits
« Reply #13 on: May 12, 2024, 12:56:29 am »
@Ranakastrasz
Code: [Select]
extended:
  scripts:
    recolorUnitSprite:
      - new: YSCRIPT_DAYFLASH
        offset: 49.99
        code: |
          var int frame;
          var int period;
          var int tileShade;
          var int newShade;
          var int id;

          unit.getId id;
          if gt id 1000; # only friendly units
            return new_pixel;
          end;
          set period 10; # flash once every x frames
          set frame anim_frame;
          mod frame period;
          if neq frame 0;
            return new_pixel;
          end;
          unit.getTileShade tileShade;
          get_shade newShade new_pixel;
          sub newShade 1;
          if and lt tileShade 10 gt newShade 0;
            set_shade new_pixel newShade;
          end;
          return new_pixel;

@Yankes, is there a way for script to access SavedBattleGame.globalshade? I want to prevent a script from running in day missions.
« Last Edit: May 12, 2024, 01:53:01 am by Delian »

Offline Yankes

  • Commander
  • *****
  • Posts: 3228
    • View Profile
Re: Reshading BattleUnits
« Reply #14 on: May 12, 2024, 01:02:18 am »
What if unit is in dark place during day?