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).
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;