Author Topic: Testing changes to 'visibilityUnit'  (Read 1257 times)

Offline zRrr

  • Sergeant
  • **
  • Posts: 18
    • View Profile
Testing changes to 'visibilityUnit'
« on: December 11, 2023, 08:10:19 pm »
In OXCE 7.9.17 unit visibility code was changed to allow sight range increase with scripts. So I wrote this to see, how it works.

1. It is now possible to give units extra night vision, anticamo, etc with scripts. Can do dynamic sight range based on tile shade level.

2. For scripts that do not change sight ranges, it would be nice to have visibleDistanceUnitMaxTile in script parameters. Computing it now requires rewriting entire getVisibleDistanceMaxHelper, which is kind of large.

3. For scripts that change sight ranges, i'd like to have access to BattleUnit::getMaxViewDistance. Can't just have getMaxViewDistanceAtDark + MORE_NV, because of camo and anticamo mechanics.

4. New visibility code does not play well with Night Vision mode and No LoS penalty. Even unmodded, tiles highlighted by NV mode often have No LoS penalty applied to them. If script increases night vision range, units in new sight range can be targeted without penalty, but tiles between new and original sight range can't (see attached image).

Code: [Select]
extended:
  scripts:
    visibilityUnit:
      - new: NEW_VISIBILITY_UNIT
        offset: 0.1
        code: |
          var int VISION_BONUS_AT_DAY 0;
          var int VISION_BONUS_AT_NIGHT 10;
          var int SPOT_BONUS_AT_DAY 0;
          var int SPOT_BONUS_AT_NIGHT 0;
          var int HEAT_VISION_BONUS 0;
         
          var int VISION_SHADE_FOR_FULL_DAY -1;   # set both to something in 0-15 to have sight range scale with tile shadiness, -1 to disable
          var int VISION_SHADE_FOR_FULL_NIGHT -1; # base game would be 9 for day and 10 for night (people on tiles with shade >= 10 are visible with nv)
         
          var int heatVision; # armor ThV%, need this for final calculation, can be altered by bonuses;
         
          var int visibleDistanceUnitMaxTile distance_target_max; # max distance observer can see target at eithed day or night in tiles (may change if we give sight range bonuses)
          var int visibleDistanceMaxVoxel distance_max; # max distance observer can see target in voxels under current light (may change if we give sight range bonuses or have proportional visibility)
         
          var int currentVisibilityOriginal current_visibility; # preserve initial current_visibility for debug_log
         
         
          begin; # get heatVision + bonus;
            var ptr RuleArmor observerArmor;
           
            observer_unit.getRuleArmor observerArmor;
            observerArmor.getHeatVision heatVision;
            add heatVision HEAT_VISION_BONUS;
           
            limit heatVision 0 100;
          end;
         
          begin; # calculate max distance observer can see target or tile under current light (visibleDistanceMaxVoxel) and in general (visibleDistanceUnitMaxTile)
            var int maxViewDistance;
            var int maxDarknessToSeeUnits;
            var int targetIsOnFire 0;
            var int targetTileShade;
           
            var int observerVisionAtDay;
            var int observerVisionAtNight;
           
            var int maxViewDistanceAtDayInTiles;
            var int maxViewDistanceAtNightInTiles;
           
           
            rules.getMaxViewDistance maxViewDistance;
            rules.getMaxDarknessToSeeUnits maxDarknessToSeeUnits;
           
            target_tile.getShade targetTileShade;
           
            if neq target_unit null;
              target_unit.getFire targetIsOnFire;
              if gt targetIsOnFire 0; # burning people are brightly lit
                set targetTileShade 0;
              end;
            end;
           
            observer_unit.getMaxViewDistanceAtDay observerVisionAtDay null; # getMaxViewDistanceAtX takes care of default values for aliens/xcom and extra nv from soldier bonuses
            add observerVisionAtDay VISION_BONUS_AT_DAY;
            limit observerVisionAtDay 1 maxViewDistance;
            observer_unit.getMaxViewDistanceAtDark observerVisionAtNight null;
            add observerVisionAtNight VISION_BONUS_AT_NIGHT;
            limit observerVisionAtNight 1 maxViewDistance;
           
            if neq target_unit null; # if we testing for unit, do all camo/anticamo stuff
              var ptr RuleArmor observerArmor;
              var int observerSpotAtDay;
              var int observerSpotAtNight;
           
              var int targetCamoAtDay 0;
              var int targetCamoAtNight 0;
             
             
              observer_unit.getRuleArmor observerArmor;
              observerArmor.getAntiCamouflageAtDay observerSpotAtDay;
              add observerSpotAtDay SPOT_BONUS_AT_DAY;
              limit_lower observerSpotAtDay 0;
              observerArmor.getAntiCamouflageAtDark observerSpotAtNight;
              add observerSpotAtNight SPOT_BONUS_AT_NIGHT;
              limit_lower observerSpotAtNight 0;
           
              if le targetIsOnFire 0; # only people that are not burning have camo
                var ptr RuleArmor targetArmor;
               
                target_unit.getRuleArmor targetArmor;
               
                targetArmor.getCamouflageAtDay targetCamoAtDay;
                targetArmor.getCamouflageAtDark targetCamoAtNight;
              end;
           
              observer_unit.getMaxViewDistance maxViewDistanceAtDayInTiles observerVisionAtDay targetCamoAtDay observerSpotAtDay;
              observer_unit.getMaxViewDistance maxViewDistanceAtNightInTiles observerVisionAtNight targetCamoAtNight observerSpotAtNight;
            else; # for tile just use observer_unit vision ranges
              set maxViewDistanceAtDayInTiles observerVisionAtDay;
              set maxViewDistanceAtNightInTiles observerVisionAtNight;
            end;
           
            set visibleDistanceUnitMaxTile maxViewDistanceAtDayInTiles;
            limit visibleDistanceUnitMaxTile maxViewDistanceAtNightInTiles maxViewDistance;
           
            begin; # calculate visibleDistanceMaxVoxel aka 'distance_max' -- max distance target can be seen by observer under current light. Optionaly interpolate at day/at night distance based on tile shade
             
              if neq target_unit null;
                var int targetId;
                var ptr RuleArmor observerArmor;
                var ptr RuleArmor targetArmor;
                var text targetArmorType;
                var text observerArmorType;
             
                target_unit.getId targetId;
                observer_unit.getRuleArmor observerArmor;
                observerArmor.getType observerArmorType;
                target_unit.getRuleArmor targetArmor;
                targetArmor.getType targetArmorType;
               
                #debug_log " " observerArmorType "=>" targetArmorType targetId "tile shade:" targetTileShade;
              end;
             
              if or lt VISION_SHADE_FOR_FULL_DAY 0 lt VISION_SHADE_FOR_FULL_NIGHT 0; # normal binary dv/nv
                if gt targetTileShade maxDarknessToSeeUnits;
                  set visibleDistanceMaxVoxel maxViewDistanceAtNightInTiles;
                else;
                  set visibleDistanceMaxVoxel maxViewDistanceAtDayInTiles;
                end;
              else; # interpolate sight range between maxViewDistanceAtNightInTiles and maxViewDistanceAtDayInTiles based on tile shadines
                var int visionRange;
                var int shadeRange;
               
                limit targetTileShade VISION_SHADE_FOR_FULL_DAY VISION_SHADE_FOR_FULL_NIGHT;
               
                # (s - sd)/(sn - sd) == (v - vd)/(vn - vd) => v = [(s - sd)/(sn - sd)]*(vn - vd) + vd
                set visionRange maxViewDistanceAtNightInTiles;
                sub visionRange maxViewDistanceAtDayInTiles;
               
                set shadeRange VISION_SHADE_FOR_FULL_NIGHT;
                sub shadeRange VISION_SHADE_FOR_FULL_DAY;
               
                set visibleDistanceMaxVoxel targetTileShade;
                sub visibleDistanceMaxVoxel VISION_SHADE_FOR_FULL_DAY;
                muldiv visibleDistanceMaxVoxel visionRange shadeRange;
                add visibleDistanceMaxVoxel maxViewDistanceAtDayInTiles;
               
                #debug_log "  in tiles:" visibleDistanceMaxVoxel;
              end;
             
              mul visibleDistanceMaxVoxel 16;
              add visibleDistanceMaxVoxel 4; # see TileEngine::getVisibleDistanceMaxHelper near the end
             
              #debug_log "        distance_max ::: cpp:" distance_max "yscript:" visibleDistanceMaxVoxel;
              #debug_log " distance_target_max ::: cpp:" distance_target_max "yscript:" visibleDistanceUnitMaxTile;
            end;
             
          end;
         
         
          begin; # current_visibility calculation from cpp code
            var int smokeAndFireVisionReduction 0;
            begin; # calculate smoke and fire vision reduction
              var int temp;
              var int densityOfSmokeTotal 0;
              var int densityOfFireTotal 0;
             
              set densityOfSmokeTotal smoke_density;
              set temp smoke_density_near_observer;
              div temp 2;
              sub densityOfSmokeTotal temp;
             
              set temp 100;
              sub temp heatVision;
              mul densityOfSmokeTotal temp;
             
              set densityOfFireTotal fire_density;
              set temp fire_density_near_observer;
              div temp 2;
              sub densityOfFireTotal temp;
             
              mul densityOfFireTotal heatVision;
             
              set smokeAndFireVisionReduction densityOfSmokeTotal;
              add smokeAndFireVisionReduction densityOfFireTotal;
              mul smokeAndFireVisionReduction visibleDistanceUnitMaxTile;
              div smokeAndFireVisionReduction 3;
              div smokeAndFireVisionReduction 20;
              div smokeAndFireVisionReduction 100;
            end;
         
            set current_visibility visibleDistanceMaxVoxel;
            sub current_visibility distance;
            sub current_visibility smokeAndFireVisionReduction;
          end;
         
          #debug_log "  current_visibility ::: cpp:" currentVisibilityOriginal "yscript:" current_visibility;
         
          return current_visibility visibility_mode;
« Last Edit: December 16, 2023, 03:07:50 pm by zRrr »

Offline Yankes

  • Commander
  • *****
  • Posts: 3223
    • View Profile
Re: Testing changes to 'visibilityUnit'
« Reply #1 on: December 11, 2023, 09:58:12 pm »
@2, @3
Ok, this can be done

@4
I see, Meridian impletion of this feature by deliberately skip script usage, I will see what can be done.

Offline Meridian

  • Global Moderator
  • Commander
  • *****
  • Posts: 8671
    • View Profile
Re: Testing changes to 'visibilityUnit'
« Reply #2 on: December 11, 2023, 11:14:59 pm »
I don't have any objections to adding script support there

do as you feel is right, Yankes

Offline Yankes

  • Commander
  • *****
  • Posts: 3223
    • View Profile
Re: Testing changes to 'visibilityUnit'
« Reply #3 on: December 11, 2023, 11:21:22 pm »
@Meridian thanks

@zRrr in OXCE 7.9.23 all requested features should be available, testing welcomed as I did only check if game do not crash after this change :)

Offline zRrr

  • Sergeant
  • **
  • Posts: 18
    • View Profile
Re: Testing changes to 'visibilityUnit'
« Reply #4 on: December 12, 2023, 06:04:38 pm »
Thanks.

getMaxViewDistance shaved a bunch of lines from script.

Call in TileEngine::isTileInLOS needs access to Tile or some other way to get shade level.

Offline Yankes

  • Commander
  • *****
  • Posts: 3223
    • View Profile
Re: Testing changes to 'visibilityUnit'
« Reply #5 on: December 15, 2023, 01:30:26 am »
in OXCE 7.9.24 tile should be expose to this script.

Offline zRrr

  • Sergeant
  • **
  • Posts: 18
    • View Profile
Re: Testing changes to 'visibilityUnit'
« Reply #6 on: December 16, 2023, 03:40:00 pm »
Thanks. I've updated script in top post to use all new stuff.


Few more bits. First is script that emulates pre 7.9.17 smoke behavior. It is not 100% accurate, because distance metric changed, and old one was closer to min(dx, dy) than to usual sum of squares, so units on diagonals were measured closer than they really were (see attached screenshots). Second is submod to X-Com Files that makes one type of enemy units (ghosts) more visible in exchange for making everybody else less visible. Both mods are attached to first post.

Code: (old style smoke) [Select]
extended:
  scripts:
    visibilityUnit:
      - new: YE_ALMOST_GOODE_OLDE_SMOKE
        offset: 0.1   # want this to go early
        code: |
          #
          # cpp code:
          #   https://github.com/MeridianOXC/OpenXcom/blob/32794065839daf70ed54e4dcf313391d0c9bffa8/src/Battlescape/TileEngine.cpp#L1809         
          #
         
          #debug_log "YAGOS ::: observer:" observer_unit;
          #debug_log "YAGOS :::   target:" target_unit;
          #debug_log "YAGOS :::   target_tile:" target_tile;
          #debug_log "YAGOS :::   distance:" distance "distance_max:" distance_max;
          #debug_log "YAGOS :::   smoke_density:" smoke_density;
         
          begin; # test if we can see target wrt sight range, camo, invis, day/night, etc.; return -1 if don't
            var int sightRangeInTilesSq;
            var int distance2dInTilesSq;
            var int temp;
            var int tempX;
            var int tempY;
           
            set sightRangeInTilesSq distance_max; # distance_max accounst for tile light level and camo/anticamo/burning if targeting unit
            sub sightRangeInTilesSq 4;
            div sightRangeInTilesSq 16;
            pow sightRangeInTilesSq 2;
           
            observer_unit.getPosition.getX tempX;
            target_tile.getPosition.getX temp;  # we always have target_tile
            sub tempX temp;
            pow tempX 2;
           
            observer_unit.getPosition.getY tempY;
            target_tile.getPosition.getY temp;
            sub tempY temp;
            pow tempY 2;
           
            set distance2dInTilesSq tempX;
            add distance2dInTilesSq tempY;
           
            #debug_log "YAGOS :::   distance2dInTilesSq:" distance2dInTilesSq "sightRangeInTilesSq:" sightRangeInTilesSq;
            if gt distance2dInTilesSq sightRangeInTilesSq;
              #debug_log "YAGOS :::     Target too far to be seen!";
              return -1 visibility_mode; # current_visibility must be > 0 for observer to see target
            end;
          end;
         
          begin; # test if we can see target through smoke, return result based on pre 7.9.17 formula, value above zero means observer can see target.
            var int modMaxViewDistanceInTiles;
            var int modMaxViewDistanceInVoxels;
           
            var ptr RuleArmor observerArmor;
            var int smokeEffectiveness 100;
           
            var int smokePenalty;
           
           
            observer_unit.getRuleArmor observerArmor;
            if and
                neq observerArmor null
                neq target_unit null;  # 'isTileInLoS' ignored observer thermal, probably because it is 'thermal' and needs hot body, so only apply it when we checking for unit visibility
                                       # see: https://github.com/MeridianOXC/OpenXcom/blob/32794065839daf70ed54e4dcf313391d0c9bffa8/src/Battlescape/TileEngine.cpp#L2023
              observerArmor.getHeatVision smokeEffectiveness;
              sub smokeEffectiveness 100;
              mul smokeEffectiveness -1;
            end;
           
            rules.getMaxViewDistance modMaxViewDistanceInTiles;
            set modMaxViewDistanceInVoxels modMaxViewDistanceInTiles;
            mul modMaxViewDistanceInVoxels 16;
           
            set smokePenalty smoke_density;
            mul smokePenalty smokeEffectiveness;         # <---+
            mul smokePenalty modMaxViewDistanceInTiles;  # <-+ |
            div smokePenalty 3;                          #   | |
            div smokePenalty 20;                         # >-+ |
            div smokePenalty 100;                        # >---+
           
            #debug_log "YAGOS :::   modMaxViewDistanceInVoxels:" modMaxViewDistanceInVoxels "distance:" distance "smokePenalty:" smokePenalty;
           
            sub modMaxViewDistanceInVoxels distance;
            sub modMaxViewDistanceInVoxels smokePenalty;
           
            #debug_log "YAGOS :::   cant see them if modMaxViewDistanceInVoxels:" modMaxViewDistanceInVoxels "is negative";
            return modMaxViewDistanceInVoxels visibility_mode;
          end;
         
          # unreachable code
          return current_visibility visibility_mode;

Code: (alter sight range based on unit type) [Select]
    visibilityUnit:
      - new: YSCRIPT_GHOSTPILLS_VISION
        # if observer has ATE_GHOSTPILLS tag,
        #   double sight range vs units who have IS_GHOST tag, and also ignore smoke/fire effects for them
        #   1/3 sight range but at least 4 tiles vs every other unit
        offset: 40
        code: |
          var int VISIBLE_DISTANCE_BUFF_MULTIPLIER 2; # x2 max view distance for ghosts
          var int VISIBLE_DISTANCE_DEBUFF_DIVISOR 3;  # 1/3 max view distance for others
         
          var int smokeAndFireVisionReduction 0;
          var int visibleDistanceMaxVoxel distance_max;
         
          var int temp;
         
          observer_unit.getTag temp Tag.ATE_GHOSTPILLS;
          if eq temp 0;
            return current_visibility visibility_mode;
          end;
         
          if eq target_unit null;
            return current_visibility visibility_mode;
          end;
         
          target_unit.getTag temp Tag.IS_GHOST;   
          if gt temp 0;  # double max vision range for ghosts
            sub visibleDistanceMaxVoxel 4;
            mul visibleDistanceMaxVoxel VISIBLE_DISTANCE_BUFF_MULTIPLIER;  # double max view distance
            add visibleDistanceMaxVoxel 4;
           
            set smokeAndFireVisionReduction 0; # smoke does not affect our super-ghost-vision
          else; # reduce sight range to not-ghost units
            sub visibleDistanceMaxVoxel 4;
            div visibleDistanceMaxVoxel VISIBLE_DISTANCE_DEBUFF_DIVISOR; 
            limit_lower visibleDistanceMaxVoxel 64; # but at least 4 tiles
            add visibleDistanceMaxVoxel 4;
           
            begin; # calculate smoke and fire vision reduction
              var int visibleDistanceUnitMaxTile distance_target_max;
              var int densityOfSmokeTotal 0;
              var int densityOfFireTotal 0;
             
              var ptr RuleArmor observerArmor;
              var int heatVision;
           
              observer_unit.getRuleArmor observerArmor;
              observerArmor.getHeatVision heatVision;
             
              set densityOfSmokeTotal smoke_density;
              set temp smoke_density_near_observer;
              div temp 2;
              sub densityOfSmokeTotal temp;
             
              set temp 100;
              sub temp heatVision;
              mul densityOfSmokeTotal temp;
             
              set densityOfFireTotal fire_density;
              set temp fire_density_near_observer;
              div temp 2;
              sub densityOfFireTotal temp;
             
              mul densityOfFireTotal heatVision;
             
              div visibleDistanceUnitMaxTile VISIBLE_DISTANCE_DEBUFF_DIVISOR; # also divide max view distance to be less screwed by smoke;
              limit_lower visibleDistanceUnitMaxTile 4;
             
              set smokeAndFireVisionReduction densityOfSmokeTotal;
              add smokeAndFireVisionReduction densityOfFireTotal;
              mul smokeAndFireVisionReduction visibleDistanceUnitMaxTile; 
              div smokeAndFireVisionReduction 3;
              div smokeAndFireVisionReduction 20;
              div smokeAndFireVisionReduction 100;
            end;
          end;
         
          set current_visibility visibleDistanceMaxVoxel;
          sub current_visibility distance;
          sub current_visibility smokeAndFireVisionReduction;
         
          return current_visibility visibility_mode;