Divergent/mods/Arszis Radiation Overhaul -.../gamedata/scripts/arszi_radiation.script

1863 lines
61 KiB
Plaintext
Raw Normal View History

2024-03-17 20:18:03 -04:00
--Author: Arszi
--Dynamic and Weather-based radiation module 2.1
-- Vastly edited by demonized
-- Last modified 2023 somewhere
--***SETTINGS**************************************************************************************************************************************
-- MCM
function load_defaults()
local t = {}
local op = arszi_radiation_mcm.op
for i, v in ipairs(op.gr) do
if v.def ~= nil then
t[v.id] = v.def
end
end
return t
end
settings = load_defaults()
function load_settings()
settings = load_defaults()
if ui_mcm then
for k, v in pairs(settings) do
settings[k] = ui_mcm.get("arszi_radiation/" .. k)
end
end
buildRadTable()
return settings
end
local mcm_keybinds = ui_mcm and ui_mcm.key_hold
-- UTILS
local function throttle(func, tg_throttle)
local tg = 0
if not tg_throttle or tg_throttle == 0 then
return function(...)
local t = time_global()
if t ~= tg then
tg = t
return func(...)
end
end
else
return function(...)
local t = time_global()
if t < tg then return end
tg = t + tg_throttle
return func(...)
end
end
end
local function array_keys(t, sorted, sort_func)
local res = {}
local res_count = 1
for k, v in pairs(t) do
res[res_count] = k
res_count = res_count + 1
end
if sorted then
if sort_func then
table.sort(res, sort_func)
else
table.sort(res)
end
end
return res
end
local function bisect_left(a, x, lo, hi)
local lo = lo or 1
local hi = hi or #a
if lo < 0 then
printf('bisect, lo must be non-negative')
return
end
while lo < hi do
local mid = math.floor((lo + hi) * 0.5)
if a[mid] < x then
lo = mid+1
else
hi = mid
end
end
return lo
end
local function lookup(t, key, tkeys)
if is_empty(t) then return 0 end
if not tkeys then
tkeys = array_keys(t, true)
end
local tkeys_len = #tkeys
if key <= tkeys[1] then return t[tkeys[1]] end
if key >= tkeys[tkeys_len] then return t[tkeys[tkeys_len]] end
local where = bisect_left(tkeys, key)
local lo = tkeys[where-1] or tkeys[where]
local hi = tkeys[where]
if lo == hi then return t[lo] end
local delta = (key - lo) / (hi - lo)
local res = delta * t[hi] + (1 - delta) * t[lo]
--printf(res)
return res
end
local OVERRIDE_MAX_POWER_INCREASEMENT_BY_WIND = true --Overrides difficulty setting-based setting with settings.MAX_POWER_INCREASEMENT_BY_WIND
local ENABLE_WIND_POWER_NOTIFICATIONS = false --Enables notifications about wind power changes (medium and high power)
--Wind alert states
local WIND_ALERT_STATES = {
NoAlert = 0,
Medium = 1,
Strong = 2
}
--settings.MAX_POWER_INCREASEMENT_BY_WIND settings based on difficulty
local DIFFICULTY_WIND_POWER_SETTINGS = {
Easy = 1.42,
Medium = 1.52,
Hard = 1.62
}
local WIND_DIRECTIONS = {
N = "North",
NE = "NorthEast",
E = "East",
SE = "SouthEast",
S = "South",
SW = "SouthWest",
W = "West",
NW = "NorthWest",
No_wind = ""
}
--New radiation zones should not spawn on theese smarts.
--They are either faction bases, or the are too close to faction bases
local blacklisted_smarts = {
l01_escape = {
esc_smart_terrain_3_16 = true,
esc_smart_terrain_2_12 = true,
esc_smart_terrain_5_7 = true,
esc_smart_terrain_4_9 = true
},
y04_pole = {
},
k00_marsh = {
mar_smart_terrain_base = true,
mar_smart_terrain_5_8 = true,
mar_smart_terrain_doc_2 = true
},
k01_darkscape = {
ds2_domik_st = true
},
l02_garbage = {
gar_smart_terrain_3_5 = true,
gar_smart_terrain_6_3 = true
},
l04_darkvalley = {
val_smart_terrain_7_3 = true,
val_smart_terrain_7_4 = true,
val_smart_terrain_7_5 = true,
val_smart_terrain_8_6 = true,
val_smart_terrain_7_8 = true, --Smart Z position is too low
val_smart_terrain_8_9 = true --Smart Z position is too low
},
l03_agroprom = {
agr_smart_terrain_1_6 = true,
agr_smart_terrain_1_6_near_1 = true,
agr_smart_terrain_1_6_near_2 = true
},
l08_yantar = {
yan_smart_terrain_3_6 = true,
yan_smart_terrain_6_4 = true,
},
l06_rostok = {
},
l05_bar = {
bar_dolg_bunker = true,
bar_dolg_general = true,
bar_visitors = true,
bar_zastava = true,
bar_zastava_2 = true,
},
k02_trucks_cemetery = {
trc_sim_20 = true
},
l09_deadcity = {
cit_killers = true,
},
l07_military = {
mil_smart_terrain_7_10 = true,
mil_smart_terrain_7_8 = true,
mil_smart_terrain_7_7 = true,
mil_smart_terrain_7_12 = true
},
l10_red_forest = {
red_smart_terrain_3_2 = true
},
l10_radar = {
},
l10_limansk = {
},
jupiter = {
jup_a6 = true,
jup_b41 = true
},
l11_pripyat = {
mlr_terrain = true,
pri_monolith = true,
},
pripyat = {
pri_a18_smart_terrain = true,
kbo_terrain = true,
pri_b306 = true,
pri_a16 = true,
pri_a16_mlr_copy = true,
pri_a28_base = true,
pri_b36_smart_terrain = true
},
l11_hospital = {
},
zaton = {
zat_b40_smart_terrain = true,
zat_b18 = true,
zat_stalker_base_smart = true,
zat_sim_27 = true
},
l12_stancia = {
},
l12_stancia_2 = {
},
l13_generators = {
},
l12u_sarcofag = {
sar_monolith_general = true
}
}
--New radiation zones settings for levels
--Chance_to_spawn is the percentage
--Radius original (minimum) radius of the new radiation zone
local radiation_field_level_settings = {
l01_escape = {
Name = "l01_escape",
Radiation_field = "zone_radioactive_very_weak",
Chance_to_spawn = 25,
Radius = 10
},
y04_pole = {
Name = "y04_pole",
Radiation_field = "zone_radioactive_very_weak",
Chance_to_spawn = 25,
Radius = 10
},
k00_marsh = {
Name = "k00_marsh",
Radiation_field = "zone_radioactive_very_weak",
Chance_to_spawn = 25,
Radius = 10
},
k01_darkscape = {
Name = "k01_darkscape",
Radiation_field = "zone_radioactive_very_weak",
Chance_to_spawn = 25,
Radius = 10
},
l02_garbage = {
Name = "l02_garbage",
Radiation_field = "zone_radioactive_weak",
Chance_to_spawn = 30,
Radius = 12
},
l04_darkvalley = {
Name = "l04_darkvalley",
Radiation_field = "zone_radioactive_weak",
Chance_to_spawn = 30,
Radius = 12
},
l03_agroprom = {
Name = "l03_agroprom",
Radiation_field = "zone_radioactive_weak",
Chance_to_spawn = 30,
Radius = 12
},
l08_yantar = {
Name = "l08_yantar",
Radiation_field = "zone_radioactive_below_average",
Chance_to_spawn = 35,
Radius = 14
},
l06_rostok = {
Name = "l06_rostok",
Radiation_field = "zone_radioactive_below_average",
Chance_to_spawn = 35,
Radius = 14
},
l05_bar = {
Name = "l05_bar",
Radiation_field = "zone_radioactive_below_average",
Chance_to_spawn = 35,
Radius = 10
},
k02_trucks_cemetery = {
Name = "k02_trucks_cemetery",
Radiation_field = "zone_radioactive_below_average",
Chance_to_spawn = 35,
Radius = 14
},
l09_deadcity = {
Name = "l09_deadcity",
Radiation_field = "zone_radioactive_below_average",
Chance_to_spawn = 35,
Radius = 14
},
l07_military = {
Name = "l07_military",
Radiation_field = "zone_radioactive_below_average",
Chance_to_spawn = 35,
Radius = 14
},
l10_red_forest = {
Name = "l10_red_forest",
Radiation_field = "zone_radioactive_average",
Chance_to_spawn = 40,
Radius = 16
},
l10_radar = {
Name = "l10_radar",
Radiation_field = "zone_radioactive_average",
Chance_to_spawn = 40,
Radius = 16
},
l10_limansk = {
Name = "l10_limansk",
Radiation_field = "zone_radioactive_average",
Chance_to_spawn = 40,
Radius = 16
},
jupiter = {
Name = "jupiter",
Radiation_field = "zone_radioactive_above_average",
Chance_to_spawn = 45,
Radius = 18
},
l11_pripyat = {
Name = "l11_pripyat",
Radiation_field = "zone_radioactive_above_average",
Chance_to_spawn = 0,
Radius = 18
},
pripyat = {
Name = "pripyat",
Radiation_field = "zone_radioactive_above_average",
Chance_to_spawn = 45,
Radius = 18
},
l11_hospital = {
Name = "l11_hospital",
Radiation_field = "zone_radioactive_above_average",
Chance_to_spawn = 45,
Radius = 18
},
zaton = {
Name = "zaton",
Radiation_field = "zone_radioactive_above_average",
Chance_to_spawn = 45,
Radius = 18
},
l12_stancia = {
Name = "l12_stancia",
Radiation_field = "zone_radioactive_strong",
Chance_to_spawn = 50,
Radius = 20
},
l12_stancia_2 = {
Name = "l12_stancia_2",
Radiation_field = "zone_radioactive_strong",
Chance_to_spawn = 50,
Radius = 20
},
l13_generators = {
Name = "l13_generators",
Radiation_field = "zone_radioactive_strong",
Chance_to_spawn = 50,
Radius = 20
}
}
--Special radioactive field spawns for NPP interrior
local special_spawn_l12u_sarcofag = {
Name = "l12u_sarcofag",
Radiation_field = "zone_radioactive_lethal",
Chance_to_spawn = 100,
Radius = 20
}
--Some anomlies will affect new game start point, like scientist bunker in Yantar. They should be capped.
local blacklisted_static_radiation_anomalies = {
-- l01_escape
esc_zone_field_radioactive_weak_0000 = 80,
esc_zone_field_radioactive_weak_0011 = 75,
esc_zone_field_radioactive_weak_0012 = 75,
esc_zone_field_radioactive_weak_0013 = 75,
--k00_marsh
mar_zone_field_radioactive_weak_0007 = 80,
--pripyat
pripyat_zone_field_radioactive_weak_0001 = 5,
--l04_darkvalley
val_zone_field_radioactive_weak_baza_freedom_0016 = 3,
val_zone_field_radioactive_weak_baza_freedom_0018 = 3,
val_zone_field_radioactive_weak_baza_freedom_0019 = 3,
--l03_agroprom
agr_zone_field_radioactive_average_000 = 20,
--l07_military
mil_zone_field_radioactive_strong = 25,
mil_zone_field_radioactive_strong_3 = 50,
--zaton
zaton_zone_field_radioactive_average_0007 = 20,
zaton_zone_field_radioactive_average_0008 = 20,
zaton_zone_field_radioactive_average_0013 = 20,
--Name of level: l02_garbage
gar_zone_field_radioactive_weak_0001 = 50,
gar_zone_field_radioactive_weak_0002 = 40
}
--Underground maps should not be affected by dynamic radiations and wind behaviour
local underground_maps = {
l03u_agr_underground = true,
l08u_brainlab = true,
l10u_bunker = true,
jupiter_underground = true,
l04u_labx18 = true,
labx8 = true,
l12u_sarcofag = true,
l12u_control_monolith = true,
l13u_warlab = true
}
--Radiation zones the player is in
local radiation_zones = {}
--Table for state management
local radiation_table = {}
--Hack for a wonderful bug in the LUA interpreter itself possibly. IT IS CURSED!
environmental_radiation = 0
function get_adjusted_radiation()
return normalize(db.actor.radiation, settings.RADIATION_THRESHOLD, 1)
end
function get_adjusted_damage_radiation()
return normalize(db.actor.radiation, settings.RADIATION_DAMAGE_THRESHOLD, 1)
end
--***POWER**************************************************************************************************************************************
function manage_power()
if (not db.actor) then return end
-- if (db.actor.radiation <= settings.RADIATION_THRESHOLD) then return end
if (not radiation_table.last_power) then radiation_table.last_power = db.actor.power end
local last_power = radiation_table.last_power
local difference = db.actor.power - last_power
if (difference > 0) then
db.actor.power = db.actor.power - get_power_difference(difference)
--Cap maximum power
if (db.actor.power > (get_power_total())) then
db.actor.power = get_power_total()
end
end
radiation_table.last_power = db.actor.power
--show_message("power "..db.actor.power)
end
function get_power_difference(difference)
local min = difference / settings.ARSZI_POWER_REGENERATION_REDUCTION_RATIO
local reduce_by_min = difference - min
return reduce_by_min * (get_adjusted_radiation() ^ settings.powerReduceCurve)
end
function get_power_total()
local min = 1 / settings.ARSZI_POWER_CAP_REDUCTION_RATIO
local reduce_by_min = 1 - min
local reduce_by_total = reduce_by_min * (get_adjusted_radiation() ^ settings.powerReduceCurve)
return 1 - reduce_by_total
end
--***SATIETY***********************************************************************************************************************************
function manage_satiety()
if (not db.actor) then return end
local conditions = db.actor:cast_Actor():conditions()
local satiety = conditions and conditions:GetSatiety()
if (not radiation_table.last_satiety) then
radiation_table.last_satiety = satiety
end
local last_satiety = radiation_table.last_satiety
if (satiety > last_satiety) then
local difference = math.abs(satiety - last_satiety)
local new_satiety = satiety - get_satiety_difference(difference)
db.actor.satiety = new_satiety
radiation_table.last_satiety = new_satiety
else
radiation_table.last_satiety = satiety
end
end
-- < 1 means more aggressive debuff, > 1 means more tame that will kick later
function get_satiety_difference(difference)
local min = difference / settings.ARSZI_SATIETY_REDUCTION_RATIO
local reduce_by_min = difference - min
return reduce_by_min * (get_adjusted_radiation() ^ settings.satietyReduceCurve)
end
manage_health = throttle(function()
if not db.actor then return end
db.actor:change_health(-get_adjusted_damage_radiation() * settings.RADIATION_MAX_DAMAGE * 0.1)
end, 100)
--***SPAWN RADIATION FIELDS**************************************************************************************************************************************
--Spawns dynamic radiation zones at non-blacklisted smart randomly
function spawn_radiation_fields_at_new_game()
trace_this("spawn_radiation_fields_at_new_game()")
for k, v in pairs (radiation_field_level_settings) do
spawn_radiation_fields_for_level(v)
end
end
--Creates new radiation fields for the current level
function spawn_radiation_fields_for_level(current_level_settings)
trace_this("spawn_radiation_fields_for_level: "..current_level_settings.Name.." ************************************************")
trace_this("get_smart_terrains_of_level: "..current_level_settings.Name)
local smarts_of_level = get_smart_terrains_of_level(current_level_settings.Name)
trace_this("get blacklisted smarts")
local blacklisted_smarts_for_level = blacklisted_smarts[current_level_settings.Name]
trace_this("Iterate through smarts")
for ksmart, smart in pairs (smarts_of_level) do
local name_of_smart = smart:name()
if (not blacklisted_smarts_for_level[name_of_smart]) then
if (math.random(1, 100) <= current_level_settings.Chance_to_spawn) then
trace_this("Spawn default radiation zone for smart: "..name_of_smart)
create_radiation_field(
current_level_settings.Radiation_field,
smart.position,
smart.m_game_vertex_id,
level.vertex_id(smart.position),
current_level_settings.Radius)
end
else
trace_this("Smart: "..name_of_smart.." is black listed, skip")
end
end
end
--Creates a new radiation field
function create_radiation_field(anomaly_type, position, game_vertex_id, level_vertex_id, radius)
trace_this("create_radiation_field: "..anomaly_type.." Radius: "..radius.. " *****************")
trace_this("spawn anomaly")
local se_obj = alife( ):create(anomaly_type, position, level_vertex_id, game_vertex_id)
if (not se_obj) then
trace_this("NO ANOMALY WERE SPAWNED")
return
end
local data = utils_stpk.get_anom_zone_data( se_obj )
if (not data) then
trace_this( "Error: Unable to set dynamic anomaly properties" )
return
end
data.shapes[1] = {}
data.shapes[1].shtype = 0
data.shapes[1].offset = vector( ):set( 0, 0, 0 ) -- Leave for compatibility with CoC 1.4.22, delete later
data.shapes[1].center = vector( ):set( 0, 0, 0 )
data.shapes[1].radius = radius
utils_stpk.set_anom_zone_data(data, se_obj)
trace_this(se_obj.id)
trace_this("Radiation field was spawned succesfully")
end
--Get smart terrains if the level
function get_smart_terrains_of_level(level_name)
trace_this("get_smart_terrains_of_level("..level_name..")")
smarts_of_level = {}
for id,smart in pairs(db.smart_terrain_by_id) do
cvertex = smart and game_graph():vertex(smart.m_game_vertex_id)
if (cvertex and alife():level_name(cvertex:level_id()) == level_name) then
table.insert(smarts_of_level, smart)
end
end
return smarts_of_level
end
--Resizes every radiation field in the game, except ones from blacklisted maps
function resize_radiation_fields()
trace_this("resize_radiation_fields()")
--Check every objects
for i=1,65534 do
local se_obj = alife():object(i)
if (se_obj) then
if (se_obj["name"]) then
local name = se_obj:name()
if (string.match(name, "zone_field_radioactive") or string.match(name, "zone_radioactive")) then
trace_this("**************************************************")
trace_this(name.." ID: "..i)
local cvertex = game_graph():vertex(se_obj.m_game_vertex_id)
local name_of_level = alife():level_name(cvertex:level_id())
trace_this("Name of level: "..name_of_level)
if (not underground_maps[name_of_level]) then
local data = utils_stpk.get_anom_zone_data(se_obj)
if (data) then
if (data.shapes[1]["radius"]) then
local radius = data.shapes[1].radius
local position = se_obj.position
local game_vertex_id = se_obj.m_game_vertex_id
local level_vertex_id = level.vertex_id(se_obj.position)
local new_radius = get_new_radius_of_static_anomaly_field(name, radius)
trace_this("Original radius: "..radius.." New radius: "..new_radius)
data.shapes[1] = {}
data.shapes[1].shtype = 0
data.shapes[1].offset = vector():set( 0, 0, 0 ) -- Leave for compatibility with CoC 1.4.22, delete later
data.shapes[1].center = vector():set( 0, 0, 0 )
data.shapes[1].radius = new_radius
utils_stpk.set_anom_zone_data( data, se_obj )
alife():set_switch_offline(se_obj.id,false)
alife():set_switch_online(se_obj.id,true)
trace_this("Resizing completed")
else
trace_this("NO RADIUS FOR: "..i)
end
else
trace_this("NO DATA FOR: "..i)
end
else
trace_this("Level is underground level, skip: "..name_of_level)
end
end
else
trace_this("No name for: "..i)
end
end
end
end
--Return the new radiation field size, taking blacklist into account
function get_new_radius_of_static_anomaly_field(name_of_radiation_zone, radius)
local override = blacklisted_static_radiation_anomalies[name_of_radiation_zone]
if (override) then
trace_this("Radiation zone is blacklisted by position, return custom max radius: "..override)
return override
else
trace_this("Not a blacklisted radiation zone")
return get_maximal_radius_with_wind_direction(radius)
end
end
--***UTILITIES**************************************************************************************************************************************
--Power for radiation damage calculation
function calculate_zone_radiation_power(power, radiation_zone)
--check of level is an underground level
if (is_underground_map()) then
radiation_zones[radiation_zone:id()] = true
return power
end
--Check if the source of radiation damage is water
if (is_player_in_water() and radiation_zone and (radiation_zone:id() == db.actor:id())) then
--Ignore mask protection
local headgear_object = db.actor:item_in_slot(12)
local outfit_object = db.actor:item_in_slot(7)
local headgear_radiation_protection = (headgear_object and get_radiation_protection(headgear_object)) or 0
local outfit_radiation_protection = (outfit_object and get_radiation_protection(outfit_object)) or 0
local helmet_avaliable = is_helmet_integrated()
local total_radiation_protection = headgear_radiation_protection + outfit_radiation_protection
--irradiate material
if (outfit_object) then
local condition = outfit_object:condition()
if (condition > 0.05) then
local armour_radiation_protection_ratio = 0.06 - outfit_radiation_protection
if (armour_radiation_protection_ratio < 0) then armour_radiation_protection_ratio = 0.004 end
outfit_object:set_condition(condition - (power * armour_radiation_protection_ratio / 4))
db.actor.radiation = db.actor.radiation + (power * armour_radiation_protection_ratio)
end
--show_message("RADIATION PROT: "..outfit_radiation_protection.." CONDITION: "..condition.." POWER: "..power)
end
--If helmet is avaiable, increase the radiation of water by (1 + outfit_with_helmet_radiation_protection_reduction_ratio). Else, increase it by the protection ratio of the headgear
if (outfit_object and helmet_avaliable) then
power = power + ((outfit_radiation_protection * settings.OUTFIT_WITH_HELMET_RADIATION_PROTECTION_REDUCTION_RATIO) / 10)
--show_message(tostring(s_hit.power.." "..s_hit.type))
--show_message("outfit with helmet radiation protection: "..outfit_radiation_protection.." reduced: "..(outfit_radiation_protection * 0.3))
else
power = power + (headgear_radiation_protection / 10)
--show_message(tostring(s_hit.power.." "..s_hit.type))
--show_message("Headgear rad protection: "..tostring(headgear_radiation_protection).." Outfit rad protection: "..outfit_radiation_protection.." total: "..total_radiation_protection)
end
radiation_zones[radiation_zone:id()] = true
return power
end
--Disable radiation damage, if the player is not in the current anomaly zone radius
if (not is_actor_in_radiation_zone(radiation_zone:id())) then
radiation_zones[radiation_zone:id()] = nil
return 0
end
--Give 20% radiation resistance boost for suits with integrated helmet
if (settings.ARSZI_INTEGRATED_SUIT_BONUS_ENABLED) then
if (is_helmet_integrated() and db.actor:item_in_slot(7)) then
local original_hit_power = power
power = power * 0.80
end
end
local wind_velocity = get_wind_velocity()
local current_weather = get_current_weather()
--In rain and storm radiation zones won't expand in radius and be weaker
if (current_weather == "rain" or current_weather == "storm") then
--show_message("Reduced by rain. Original: "..s_hit.power.." Reduced: "..(s_hit.power * settings.POWER_REDUCTION_BY_RAIN))
radiation_zones[radiation_zone:id()] = true
return power * settings.POWER_REDUCTION_BY_RAIN
end
--In foggy weather radiation zones won't expand in radius
if (current_weather == "foggy") then
radiation_zones[radiation_zone:id()] = true
return power
end
local power_increasement_factor = (wind_velocity / settings.MAX_WIND_VELOCITY) * (get_max_power_increasement_by_wind() - 1)
--show_message("Wind velocity: "..wind_velocity.." Power incerase factor: "..power_increasement_factor.." Original: "..s_hit.power.." Increased: "..s_hit.power * (1 + power_increasement_factor))
radiation_zones[radiation_zone:id()] = true
return power * (1 + power_increasement_factor)
end
--Power dosimeter-related power calcuation. HUD and ticking.
function get_environmental_radiation_power()
--WATER
if (is_player_in_water()) then
local power = 0
if (is_underground_map()) then
power = math.random(29, 35) / 1000
else
power = math.random(15, 17) / 1000
end
environmental_radiation = power
return power
end
--Check if player is inside a zone
local count = 0
for k, _ in pairs(radiation_zones) do
count = count + 1
end
if (count == 0) then
environmental_radiation = 0
return 0
end
local power = level.get_env_rads()
local min = math.floor(power * 1000 * 0.9)
local max = math.floor(power * 1000 * 1.1)
power = math.random(min, max) / 1000
--UNDERGROUND
if (is_underground_map()) then
environmental_radiation = power
return power
end
local wind_velocity = get_wind_velocity()
local current_weather = get_current_weather()
--RAIN/STORM
if (current_weather == "rain" or current_weather == "storm") then
power = power * settings.POWER_REDUCTION_BY_RAIN
environmental_radiation = power
return power
end
--FOG
if (current_weather == "foggy") then
environmental_radiation = power
return power
end
--NORMAL
local power_increasement_factor = (wind_velocity / settings.MAX_WIND_VELOCITY) * (get_max_power_increasement_by_wind() - 1)
power = power * (1 + power_increasement_factor)
--Hax to make Dosimeter UI work
environmental_radiation = power
return power
end
--Gets maximal power increasement by wind based on game difficulty setting or override
function get_max_power_increasement_by_wind()
--Check if overridden
if (OVERRIDE_MAX_POWER_INCREASEMENT_BY_WIND) then
--show_message("WIND POWER SETTING OVERRIDE: "..settings.MAX_POWER_INCREASEMENT_BY_WIND)
return settings.MAX_POWER_INCREASEMENT_BY_WIND
end
--Return setting based on game-difficulty
local gameplay = alife_storage_manager.get_state().diff_game
local game_num = gameplay and gameplay["type"] or 2
local wind_power_setting = 1.6
if (game_num == 1) then wind_power_setting = DIFFICULTY_WIND_POWER_SETTINGS.Easy end
if (game_num == 2) then wind_power_setting = DIFFICULTY_WIND_POWER_SETTINGS.Medium end
if (game_num == 3) then wind_power_setting = DIFFICULTY_WIND_POWER_SETTINGS.Hard end
--show_message("WIND POWER SETTING: "..wind_power_setting)
return wind_power_setting
end
--Gets if a map is an underground map
function is_underground_map()
local name_of_level = level.name()
if (not name_of_level) then return true end
if (underground_maps[name_of_level]) then
return true
else
return false
end
end
function get_min_radius_multiplier()
local min_radius = 10
local max_radius = min_radius * settings.MAX_SIZE_INCREASEMENT_BY_WIND
local max_width = max_radius * 2
local max_radius_with_wind = max_width - min_radius
return (min_radius / max_radius_with_wind)
end
function get_current_radius_of_radioactive_zone(radius_max)
local min_radius = radius_max / settings.MAX_SIZE_INCREASEMENT_BY_WIND
local percentage_ratio = get_wind_velocity() / settings.MAX_WIND_VELOCITY
return ((radius_max - min_radius) * percentage_ratio) + min_radius
end
function get_original_radius_of_radiation_zone(radius_max)
return radius_max * get_min_radius_multiplier()
end
--Maximum radius without buffer
function get_max_radius_without_wind(radius_max_with_wind)
local radius_min = radius_max_with_wind * get_min_radius_multiplier()
local radius_max = radius_min * settings.MAX_SIZE_INCREASEMENT_BY_WIND
return radius_max
end
--Maximum radius with buffer
function get_maximal_radius_with_wind_direction(min_radius)
local max_radius = min_radius * settings.MAX_SIZE_INCREASEMENT_BY_WIND
local max_width = max_radius * 2
return max_width - min_radius
end
function get_current_center_of_radioactive_zone(radius_max_without_wind, initial_position, direction_of_wind)
local min_radius = radius_max_without_wind / settings.MAX_SIZE_INCREASEMENT_BY_WIND
local current_radius = get_current_radius_of_radioactive_zone(radius_max_without_wind)
local offset = current_radius - min_radius
if (direction_of_wind == WIND_DIRECTIONS.N) then
return vector():set(initial_position.x, initial_position.y, initial_position.z - offset)
end
if (direction_of_wind == WIND_DIRECTIONS.NE) then
return vector():set(initial_position.x - offset, initial_position.y, initial_position.z - offset)
end
if (direction_of_wind == WIND_DIRECTIONS.NW) then
return vector():set(initial_position.x + offset, initial_position.y, initial_position.z - offset)
end
if (direction_of_wind == WIND_DIRECTIONS.S) then
return vector():set(initial_position.x, initial_position.y, initial_position.z + offset)
end
if (direction_of_wind == WIND_DIRECTIONS.SE) then
return vector():set(initial_position.x - offset, initial_position.y, initial_position.z + offset)
end
if (direction_of_wind == WIND_DIRECTIONS.SW) then
return vector():set(initial_position.x + offset, initial_position.y, initial_position.z + offset)
end
if (direction_of_wind == WIND_DIRECTIONS.E) then
return vector():set(initial_position.x - offset, initial_position.y, initial_position.z)
end
if (direction_of_wind == WIND_DIRECTIONS.W) then
return vector():set(initial_position.x + offset, initial_position.y, initial_position.z)
end
end
function is_actor_in_radiation_zone(id_of_radiation_zone)
local se_obj = alife():object(id_of_radiation_zone)
local name_of_radiation_zone = se_obj:name()
if (not se_obj) then return false end
local data = utils_stpk.get_anom_zone_data(se_obj)
local maximum_radius = data and data.shapes[1].radius
if (not maximum_radius) then return true end
local maximum_radius_without_wind = get_max_radius_without_wind(maximum_radius)
local original_radius = get_original_radius_of_radiation_zone(maximum_radius) --Just for debug purposes
local original_position = se_obj.position
local actor_distance_from_original_position = get_distance(db.actor:position(), original_position) --Just for debug purposes
local current_radius = get_current_radius_of_radioactive_zone(maximum_radius_without_wind)
local wind_direction = radiation_table.last_wind_direction
local current_center_with_wind = get_current_center_of_radioactive_zone(maximum_radius_without_wind, original_position, wind_direction)
local actor_distance_from_current_position = get_distance(db.actor:position(), current_center_with_wind)
local current_weather = get_current_weather()
if (current_weather == "rain" or current_weather == "storm" or current_weather == "foggy") then
current_radius = original_radius
actor_distance_from_current_position = actor_distance_from_original_position
end
--Uncomment for debugging purposes
--show_message("NAME: "..name_of_radiation_zone.." MAX_R_WIND: "..maximum_radius.." MAX_R :"..maximum_radius_without_wind.." ORIGINAL_R: "..original_radius.." CURRENT_R: "..current_radius.." DISTANCE_O: "..actor_distance_from_original_position.." Distance_C: "..actor_distance_from_current_position)
--show_message("NAME: "..name_of_radiation_zone.." CURRENT_R: "..current_radius.." Distance_C: "..actor_distance_from_current_position.." IN ZONE: "..tostring(actor_distance_from_current_position <= current_radius))
return actor_distance_from_current_position <= current_radius
end
function get_distance(position_1, position_2)
local x = math.abs(position_1.x - position_2.x)
local z = math.abs(position_1.z - position_2.z)
local distance = math.sqrt((x * x) + (z * z))
return distance
end
function is_player_in_water()
return load_var(db.actor,"grw_in_water") == true
end
function get_radiation_protection(object)
local section_object = object and object:section()
local id_object = object and object:id()
local total_radiation_protection = 0
local helmet_avaliable = false
local se_object = id_object and alife_object(id_object)
if se_object then
local data = utils_stpk.get_item_data(se_object)
for k,v in pairs(data) do
local radiation_protection = utils_data.read_from_ini(ini_sys, section_object, "radiation_protection", "float", 0)
total_radiation_protection = radiation_protection * object:condition()
helmet_avaliable = utils_data.read_from_ini(ini_sys, section_object, "helmet_avaliable", "bool", false)
break
end
for k,v in pairs(data.upgrades) do
local upgrade_sect = utils_data.read_from_ini(ini_sys, tostring(v), "section", "string", nil)
if upgrade_sect then
local upgrade_radiation_protection = utils_data.read_from_ini(ini_sys, upgrade_sect, "radiation_protection", "float", 0)
if (upgrade_radiation_protection) then
total_radiation_protection = total_radiation_protection + upgrade_radiation_protection
end
end
end
return total_radiation_protection
end
end
function is_helmet_integrated()
local object = db.actor:item_in_slot(7)
local section_object = object and object:section()
local id_object = object and object:id()
local helmet_avaliable = false
local se_object = id_object and alife_object(id_object)
if se_object then
local data = utils_stpk.get_item_data(se_object)
for k,v in pairs(data) do
helmet_avaliable = utils_data.read_from_ini(ini_sys, section_object, "helmet_avaliable", "bool", false)
break
end
end
return not helmet_avaliable
end
function play_random_geiger_sound()
local radiation = math.random(8)
local snd = xr_sound.get_safe_sound_object("detectors\\geiger_" .. radiation)
snd:play(db.actor, 0, sound_object.s2d)
snd.volume = radiation_table.dosimeter_volume
end
function show_message_news(news_id, icon)
if news_id == nil then return false end
xr_sound.set_sound_play(AC_ID, "pda_tips")
-- play script sound with matching section name to news id string
if (sound_theme.theme[news_id]) then
xr_sound.set_sound_play(AC_ID, news_id)
if (sound_theme.theme[news_id].snd_obj) then
local length = sound_theme.theme[news_id].snd_obj:length()
showtime = showtime < length and length or showtime
end
end
db.actor:give_game_news(game.translate_string("st_tip"), game.translate_string(news_id), icon, 0, 10000, 0)
return true
end
function show_message(msg,time)
local hud = get_hud()
hud:AddCustomStatic("can_use_weapon_now", true)
hud:GetCustomStatic("can_use_weapon_now"):wnd():TextControl():SetTextST(msg)
ShowMessage = true
ShowMessageInit = game.get_game_time()
ShowMessageTime = time
end
function player_has_operational_dosimeter()
--Return if player does not have a geiger counter or battery is dead
local itm_geiger = item_device.device_geiger
local obj_geiger = itm_geiger and db.actor:object(itm_geiger)
if not (obj_geiger and obj_geiger:condition() > 0.09) then
return false
else
return true
end
end
--Tracing, comment out method body for release
function trace_this(to_trace)
--local log_file = io.open("log_arszi_radiation.txt", "a")
--log_file:write(to_trace.."\n")
--log_file:close(log_file)
end
function initialize_radiation_table()
if (not radiation_table.dosimeter_volume) then
radiation_table.dosimeter_volume = 1
trace_this("INITIALIZE - dosimeter_volume")
end
if (not radiation_table.wind_alert_state) then
radiation_table.wind_alert_state = WIND_ALERT_STATES.NoAlert
trace_this("INITIALIZE - wind_alert_state")
end
end
savedIcons = {}
actor_status_get_radiation = actor_status.get_radiation
actor_status.get_radiation = function(visual)
local radiation = db.actor.radiation
if settings.displayRadIcon == 1 then
if radiation > settings.RADIATION_DAMAGE_THRESHOLD then
return actor_status_get_radiation(visual)
else
return 0
end
else
return 0
end
end
function toggleInventoryIcon(icoKey, icoEl)
if not savedIcons[icoKey] then
savedIcons[icoKey] = {
wnd_pos = vector2():set(icoEl:GetWndPos().x, icoEl:GetWndPos().y)
}
end
if settings.displayRadIcon == 1 then
if db.actor.radiation > settings.RADIATION_DAMAGE_THRESHOLD then
icoEl:SetWndPos(savedIcons[icoKey].wnd_pos)
else
icoEl:SetWndPos(vector2():set(0,-1000))
end
else
icoEl:SetWndPos(vector2():set(0,-1000))
end
end
remove_actor_status_icon_radiation = throttle(function()
local inventory_ui = ui_inventory.GUI
if inventory_ui and inventory_ui.stat["radia"] then
local ico_n = inventory_ui.stat["radia"].ico_n
if ico_n then
toggleInventoryIcon("ico_n", ico_n)
end
local ico_p = inventory_ui.stat["radia"].ico_p
if ico_p then
toggleInventoryIcon("ico_p", ico_p)
end
end
end, 300)
--***CALLBACKS**************************************************************************************************************************************
function on_game_start()
RegisterScriptCallback("on_option_change", load_settings)
RegisterScriptCallback("actor_on_first_update", load_settings)
RegisterScriptCallback("actor_on_before_hit",actor_on_before_hit)
RegisterScriptCallback("actor_on_update", actor_on_update)
RegisterScriptCallback("on_key_press", on_key_press)
RegisterScriptCallback("on_key_hold", on_key_hold)
RegisterScriptCallback("on_game_load", on_game_load)
RegisterScriptCallback("save_state", save_state)
RegisterScriptCallback("load_state", load_state)
end
function save_state(m_data)
m_data.radiation_table = radiation_table
end
function load_state(m_data)
radiation_table = m_data.radiation_table or {}
end
function on_game_load()
--Initialize radiation_table of not initialized
initialize_radiation_table()
if (settings.ENABLE_DYNAMIC_RADIATION_ZONES) then
if (not radiation_table.dynamic_radiation_zones_generated) then
--spawn dynamic radiation fields
spawn_radiation_fields_at_new_game()
radiation_table.dynamic_radiation_zones_generated = true
end
end
if (not radiation_table.static_radiation_fields_resized) then
--Resize both original and new radiation fields
resize_radiation_fields()
radiation_table.static_radiation_fields_resized = true
end
if (settings.ENABLE_DYNAMIC_RADIATION_ZONES_NPP) then
if (not radiation_table.dynamic_radiation_zones_generated_npp) then
--Spawn special radiation fields for NPP interrior
spawn_radiation_fields_for_level(special_spawn_l12u_sarcofag)
radiation_table.dynamic_radiation_zones_generated_npp = true
end
end
end
function actor_on_before_hit(s_hit)
--Check if damage source is radiation
if (s_hit.type ~= 3) then
return
end
--Calculate new power
s_hit.power = calculate_zone_radiation_power(s_hit.power, s_hit.draftsman)
end
-- Less effective boosters the less rads player has
-- Full effectiveness of rads when it goes past settings.RADIATION_DAMAGE_THRESHOLD, then it drops linearly to at least 33% at settings.RADIATION_THRESHOLD
function buildRadTable()
-- LUT with meds effectiveness depending on rads
radTable = {
[settings.RADIATION_THRESHOLD] = settings.minMedEfficiency,
[settings.RADIATION_DAMAGE_THRESHOLD] = 1,
}
radTableKeys = array_keys(radTable, true)
end
local currentBoosters = {}
local boosterCounters = {}
function manage_boosters()
local c_obj = db.actor:cast_Actor()
local function restoreState()
if currentBoosters[BoosterID["RadiationRestore"]] then
currentBoosters[BoosterID["RadiationRestore"]] = nil
c_obj:conditions():BoostRadiationRestore(-(boosterCounters[BoosterID["RadiationRestore"]] or 0))
boosterCounters[BoosterID["RadiationRestore"]] = nil
end
end
if not settings.medDigestibility then
restoreState()
return
end
if not radTable then buildRadTable() end
local foundRadiationBooster = false
c_obj:conditions():BoosterForEach(function(booster_type, booster_time, booster_value)
if booster_type == BoosterID["RadiationRestore"] then
if boosterCounters[booster_type] then
c_obj:conditions():BoostRadiationRestore(-boosterCounters[booster_type])
else
boosterCounters[booster_type] = 0
end
-- If booster is too low - don't apply penalty
if booster_value < 0.00015 then
-- printf("booster_value %s < 0.00015, too low", booster_value)
return
end
currentBoosters[booster_type] = booster_value
boosterCounters[booster_type] = booster_value * lookup(radTable, db.actor.radiation, radTableKeys) - booster_value
c_obj:conditions():BoostRadiationRestore(boosterCounters[booster_type])
-- printf("adjust radiation restore from %s to %s", booster_value, booster_value + boosterCounters[booster_type])
-- printf("rad level %s, booster_value %s time %s", db.actor.radiation, booster_value + boosterCounters[booster_type], booster_time)
foundRadiationBooster = true
end
end)
if not foundRadiationBooster then
restoreState()
end
end
function actor_on_update()
--Removes the radiation indicator from the actor status icons
remove_actor_status_icon_radiation()
manage_radiation_zones()
manage_weather()
manage_power()
manage_satiety()
manage_health()
manage_boosters()
manage_geiger_sound()
end
local last_check_time = nil
function manage_radiation_zones()
--Remove of player is not in the water anymore
if (not is_player_in_water()) then
if (radiation_zones[AC_ID]) then
radiation_zones[AC_ID] = nil
end
end
--If not underground, it will be handled elsewhere
if (not is_underground_map()) then return end
--If there is environmental radiation still, the player is in a radiation zone
if (level.get_env_rads() > 0) then return end
if (not last_check_time) then last_check_time = game.get_game_time() end
local current_time = game.get_game_time()
if (current_time:diffSec(last_check_time) > 6) then
for k, _ in pairs(radiation_zones) do
radiation_zones[k] = nil
last_check_time = current_time
end
end
end
local last_tick_time = nil
function manage_geiger_sound()
--Return if player does not have a geiger counter or battery is dead
if (not player_has_operational_dosimeter()) then return end
if (not last_tick_time) then last_tick_time = game.get_game_time() end
local current_time = game.get_game_time()
local power = get_environmental_radiation_power()
if (power > 0.07) then
if (current_time:diffSec(last_tick_time) >= 0.1) then
play_random_geiger_sound()
last_tick_time = current_time
end
elseif (power > 0.06 and power <= 0.07) then
if (current_time:diffSec(last_tick_time) >= 0.25) then
play_random_geiger_sound()
last_tick_time = current_time
end
elseif (power > 0.055 and power <= 0.06) then
if (current_time:diffSec(last_tick_time) >= 0.5) then
play_random_geiger_sound()
last_tick_time = current_time
end
elseif (power > 0.05 and power <= 0.055) then
if (current_time:diffSec(last_tick_time) >= 1) then
play_random_geiger_sound()
last_tick_time = current_time
end
elseif (power > 0.045 and power <= 0.05) then
if (current_time:diffSec(last_tick_time) >= 2) then
play_random_geiger_sound()
last_tick_time = current_time
end
elseif (power > 0.04 and power <= 0.045) then
if (current_time:diffSec(last_tick_time) >= 3) then
play_random_geiger_sound()
last_tick_time = current_time
end
elseif (power > 0.035 and power <= 0.04) then
if (current_time:diffSec(last_tick_time) >= 4) then
play_random_geiger_sound()
last_tick_time = current_time
end
elseif (power > 0.03 and power <= 0.035) then
if (current_time:diffSec(last_tick_time) >= 5) then
play_random_geiger_sound()
last_tick_time = current_time
end
elseif (power > 0.025 and power <= 0.03) then
if (current_time:diffSec(last_tick_time) >= 6) then
play_random_geiger_sound()
last_tick_time = current_time
end
elseif (power > 0.02 and power <= 0.025) then
if (current_time:diffSec(last_tick_time) >= 7) then
play_random_geiger_sound()
last_tick_time = current_time
end
elseif (power > 0.015 and power <= 0.02) then
if (current_time:diffSec(last_tick_time) >= 8) then
play_random_geiger_sound()
last_tick_time = current_time
end
elseif (power > 0.01 and power <= 0.015) then
if (current_time:diffSec(last_tick_time) >= 9) then
play_random_geiger_sound()
last_tick_time = current_time
end
elseif (power > 0.001 and power <= 0.01) then
if (current_time:diffSec(last_tick_time) >= 10) then
play_random_geiger_sound()
last_tick_time = current_time
end
end
end
--Issues wind alert
function issue_wind_alert(wind_power, previous_wind_power)
--If notifications are not enabled, do not send them
if (not ENABLE_WIND_POWER_NOTIFICATIONS) then return end
--Return if PDA is not charged
if (not item_device.is_pda_charged(true)) then return end
--Return if surge or psy storm have started
if (xr_conditions.surge_started()) then return end
--If underground map, do not send notification
if (is_underground_map()) then return end
wind_power = math.floor(wind_power)
previous_wind_power = math.floor(previous_wind_power)
if (wind_power > max_wind_power_percent(60) and wind_power > previous_wind_power) then
if (radiation_table.wind_alert_state ~= WIND_ALERT_STATES.Strong) then
show_message_news("st_warning_medium_to_strong", "ui_inGame2_V_zone_nedavno_proshel_vibros")
radiation_table.wind_alert_state = WIND_ALERT_STATES.Strong
return
end
end
if (wind_power > max_wind_power_percent(30) and wind_power <= max_wind_power_percent(60) and wind_power > previous_wind_power) then
if (radiation_table.wind_alert_state ~= WIND_ALERT_STATES.Medium) then
show_message_news("st_warning_none_to_medium", "ui_inGame2_V_zone_nedavno_proshel_vibros")
radiation_table.wind_alert_state = WIND_ALERT_STATES.Medium
return
end
end
if (wind_power <= max_wind_power_percent(60) and wind_power > max_wind_power_percent(30) and previous_wind_power > wind_power) then
if (radiation_table.wind_alert_state ~= WIND_ALERT_STATES.Medium) then
show_message_news("st_warning_strong_to_medium", "ui_inGame2_V_zone_nedavno_proshel_vibros")
radiation_table.wind_alert_state = WIND_ALERT_STATES.Medium
return
end
end
if (wind_power <= max_wind_power_percent(30) and previous_wind_power > wind_power) then
if (radiation_table.wind_alert_state ~= WIND_ALERT_STATES.NoAlert) then
show_message_news("st_warning_medium_to_none", "ui_inGame2_V_zone_nedavno_proshel_vibros")
radiation_table.wind_alert_state = WIND_ALERT_STATES.NoAlert
return
end
end
end
--Issues weather report
function issue_weather_report(current_weather, previous_weather)
--If notifications are not enabled, do not send them
if (not settings.ENABLE_WEATHER_REPORTS) then return end
--Return if PDA is not charged
if (not item_device.is_pda_charged(true)) then return end
--Return if surge or psy storm have started
if (xr_conditions.surge_started()) then return end
--If underground map, do not send notification
if (is_underground_map()) then return end
local weather_text = ""
--clear
if (current_weather == "clear" and previous_weather == "partly") then weather_text = "st_weather_clear_from_partly" end
if (current_weather == "clear" and previous_weather == "foggy") then weather_text = "st_weather_clear_from_foggy" end
if (current_weather == "clear" and previous_weather == "cloudy") then weather_text = "st_weather_clear_from_cloudy" end
if (current_weather == "clear" and previous_weather == "rain") then weather_text = "st_weather_clear_from_rain" end
if (current_weather == "clear" and previous_weather == "storm") then weather_text = "st_weather_clear_from_storm" end
--partly
if (current_weather == "partly" and previous_weather == "clear") then weather_text = "st_weather_partly_from_clear" end
if (current_weather == "partly" and previous_weather == "foggy") then weather_text = "st_weather_partly_from_foggy" end
if (current_weather == "partly" and previous_weather == "cloudy") then weather_text = "st_weather_partly_from_cloudy" end
if (current_weather == "partly" and previous_weather == "rain") then weather_text = "st_weather_partly_from_rain" end
if (current_weather == "partly" and previous_weather == "storm") then weather_text = "st_weather_partly_from_storm" end
--foggy
if (current_weather == "foggy" and previous_weather == "clear") then weather_text = "st_weather_foggy_from_clear" end
if (current_weather == "foggy" and previous_weather == "partly") then weather_text = "st_weather_foggy_from_partly" end
if (current_weather == "foggy" and previous_weather == "cloudy") then weather_text = "st_weather_foggy_from_cloudy" end
if (current_weather == "foggy" and previous_weather == "rain") then weather_text = "st_weather_foggy_from_rain" end
if (current_weather == "foggy" and previous_weather == "storm") then weather_text = "st_weather_foggy_from_storm" end
--cloudy
if (current_weather == "cloudy" and previous_weather == "clear") then weather_text = "st_weather_cloudy_from_clear" end
if (current_weather == "cloudy" and previous_weather == "partly") then weather_text = "st_weather_cloudy_from_partly" end
if (current_weather == "cloudy" and previous_weather == "foggy") then weather_text = "st_weather_cloudy_from_foggy" end
if (current_weather == "cloudy" and previous_weather == "rain") then weather_text = "st_weather_cloudy_from_rain" end
if (current_weather == "cloudy" and previous_weather == "storm") then weather_text = "st_weather_cloudy_from_storm" end
--rain
if (current_weather == "rain" and previous_weather == "clear") then weather_text = "st_weather_rain_from_clear" end
if (current_weather == "rain" and previous_weather == "partly") then weather_text = "st_weather_rain_from_partly" end
if (current_weather == "rain" and previous_weather == "cloudy") then weather_text = "st_weather_rain_from_cloudy" end
if (current_weather == "rain" and previous_weather == "foggy") then weather_text = "st_weather_rain_from_foggy" end
if (current_weather == "rain" and previous_weather == "storm") then weather_text = "st_weather_rain_from_storm" end
--storm
if (current_weather == "storm" and previous_weather == "clear") then weather_text = "st_weather_storm_from_clear" end
if (current_weather == "storm" and previous_weather == "partly") then weather_text = "st_weather_storm_from_partly" end
if (current_weather == "storm" and previous_weather == "cloudy") then weather_text = "st_weather_storm_from_cloudy" end
if (current_weather == "storm" and previous_weather == "rain") then weather_text = "st_weather_storm_from_rain" end
if (current_weather == "storm" and previous_weather == "foggy") then weather_text = "st_weather_storm_from_foggy" end
--Override some texts with sunlight in them during night
local hour = level.get_time_hours()
if (hour <= 6 or hour >= 20) then
if (current_weather == "clear" and previous_weather == "partly") then weather_text = "st_weather_clear_from_partly_night" end
if (current_weather == "clear" and previous_weather == "foggy") then weather_text = "st_weather_clear_from_foggy_night" end
if (current_weather == "clear" and previous_weather == "storm") then weather_text = "st_weather_clear_from_storm_night" end
if (current_weather == "storm" and previous_weather == "clear") then weather_text = "st_weather_storm_from_clear_night" end
end
--Select icon
local current_icon = "" --"ui_inGame2_Radiopomehi"
if (current_weather == "clear") then current_icon = "ui_weather_clear" end
if (current_weather == "partly") then current_icon = "ui_weather_partly" end
if (current_weather == "foggy") then current_icon = "ui_weather_foggy" end
if (current_weather == "cloudy") then current_icon = "ui_weather_cloudy" end
if (current_weather == "rain") then current_icon = "ui_weather_rain" end
if (current_weather == "storm") then current_icon = "ui_weather_storm" end
show_message_news(weather_text, current_icon)
end
--Get information about the current weather by pressing key "9"
function getWeatherInfo()
local name_of_level = level.name()
if (not name_of_level) then return end
if (underground_maps[name_of_level]) then
show_message("st_underground", 1000)
return
end
local current_weather = get_weather_text(get_current_weather())
local wind_velocity = get_reported_wind_velocity()
local wind_direction = radiation_table.last_wind_direction
if (wind_velocity ~= nil and wind_direction ~= nil) then
show_message(game.translate_string("st_weather_is").." "..current_weather..". "..get_wind_power_text(wind_velocity).." "..get_wind_direction_text(wind_direction), 1000)
end
end
--Dosimeter volume up
function dosimeterVolumeUp()
if (not player_has_operational_dosimeter()) then return end
if (radiation_table.dosimeter_volume > 1.9) then
show_message("Dosimeter volume: MAX")
return
end
radiation_table.dosimeter_volume = radiation_table.dosimeter_volume + 0.2
if (radiation_table.dosimeter_volume > 1.9) then
show_message("Dosimeter volume: MAX")
else
show_message("Dosimeter volume: "..radiation_table.dosimeter_volume)
end
end
--Dosimeter volume down
function dosimeterVolumeDown()
if (not player_has_operational_dosimeter()) then return end
if (radiation_table.dosimeter_volume < 0.1) then
show_message("Dosimeter volume: MUTED")
return
end
radiation_table.dosimeter_volume = radiation_table.dosimeter_volume - 0.2
if (radiation_table.dosimeter_volume < 0.1) then
show_message("Dosimeter volume: MUTED")
else
show_message("Dosimeter volume: "..radiation_table.dosimeter_volume)
end
end
function on_key_press(key)
local function mcmKeyPress(settings_key, func, ...)
local k = settings_key
local mode = settings_key .. "_mode"
local modifier = settings_key .. "_modifier"
local keyname = "arszi_radiation_" .. k
if (key == settings[k]) then
if not mcm_keybinds then
func(...)
else
if settings[mode] == 0 then
ui_mcm.simple_press(keyname, key, func, ...)
elseif settings[mode] == 1 and ui_mcm.get_mod_key(settings[modifier]) and ui_mcm.double_tap(keyname, key) then
func(...)
end
end
end
end
--Weather
mcmKeyPress("WEATHER_INFO_KEY", getWeatherInfo)
--Dosimeter volume up
mcmKeyPress("DOSIMETER_VOLUME_UP_KEY", dosimeterVolumeUp)
--Dosimeter volume down
mcmKeyPress("DOSIMETER_VOLUME_DOWN", dosimeterVolumeDown)
end
function on_key_hold(key)
local function mcmKeyHold(settings_key, func, ...)
local k = settings_key
local mode = settings_key .. "_mode"
local modifier = settings_key .. "_modifier"
local keyname = "arszi_radiation_" .. k
if mcm_keybinds and key == settings[k] and settings[mode] == 2 and ui_mcm.get_mod_key(settings[modifier]) and ui_mcm.key_hold(keyname, key) then
func(...)
end
end
--Weather
mcmKeyHold("WEATHER_INFO_KEY", getWeatherInfo)
--Dosimeter volume up
mcmKeyHold("DOSIMETER_VOLUME_UP_KEY", dosimeterVolumeUp)
--Dosimeter volume down
mcmKeyHold("DOSIMETER_VOLUME_DOWN_KEY", dosimeterVolumeDown)
end
--***WEATHER**************************************************************************************************************************************
function manage_weather()
local name_of_level = level.name()
if (not name_of_level) then return end
--Check if weather changed, and calculate new wind direction, if yes
if (not radiation_table.last_weather) then
radiation_table.last_weather = get_current_weather()
end
if (not radiation_table.last_wind_direction) then
radiation_table.last_wind_direction = get_new_wind_direction()
end
if (not radiation_table.last_wind_velocity) then
radiation_table.last_wind_velocity = get_wind_velocity()
end
local current_weather = get_current_weather()
--Issue wind warning if necessary
if (current_weather == "clear" or current_weather == "partly" or current_weather == "cloudy") then
local current_wind_velocity = get_wind_velocity()
--Issues wind alert if wind velocity is too high
issue_wind_alert(current_wind_velocity, radiation_table.last_wind_velocity)
radiation_table.last_wind_velocity = current_wind_velocity
end
--Change wind direction on weather change
if (current_weather ~= radiation_table.last_weather) then
--Issue new weather report
issue_weather_report(current_weather, radiation_table.last_weather)
radiation_table.last_weather = current_weather
radiation_table.last_wind_direction = get_new_wind_direction()
end
end
--Gets a new wind direction randomly, favouring western directions mostly
function get_new_wind_direction()
local rand = math.random(100)
if (rand >= 60) then
local direction = math.random(100)
if (direction >= 60) then
return WIND_DIRECTIONS.W
elseif (direction >= 30) then
return WIND_DIRECTIONS.SW
else
return WIND_DIRECTIONS.NW
end
elseif (rand >= 40) then
local direction = math.random(100)
if (direction >= 60) then
return WIND_DIRECTIONS.N
elseif (direction >= 30) then
return WIND_DIRECTIONS.NW
else
return WIND_DIRECTIONS.NE
end
elseif (rand >= 20) then
local direction = math.random(100)
if (direction >= 60) then
return WIND_DIRECTIONS.E
elseif (direction >= 30) then
return WIND_DIRECTIONS.NE
else
return WIND_DIRECTIONS.SE
end
else
local direction = math.random(100)
if (direction >= 60) then
return WIND_DIRECTIONS.S
elseif (direction >= 30) then
return WIND_DIRECTIONS.SE
else
return WIND_DIRECTIONS.SW
end
end
end
--Gets the text for the corresponding wind power
function get_wind_power_text(wind_velocity)
if (wind_velocity == 0) then
return game.translate_string("st_wind_power_no_wind")
elseif (wind_velocity < max_wind_power_percent(15)) then
return game.translate_string("st_wind_power_slight_breeze")
elseif (wind_velocity < max_wind_power_percent(30)) then
return game.translate_string("st_wind_power_weak")
elseif (wind_velocity < max_wind_power_percent(40)) then
return game.translate_string("st_wind_power_moderate")
elseif (wind_velocity < max_wind_power_percent(55)) then
return game.translate_string("st_wind_power_strong")
elseif (wind_velocity < max_wind_power_percent(70)) then
return game.translate_string("st_wind_power_very_strong")
else
return game.translate_string("st_wind_power_storm")
end
end
--Gets the text for the corresponding wind direction
function get_wind_direction_text(wind_direction)
if (wind_direction == WIND_DIRECTIONS.No_wind) then
return ""
end
local text = game.translate_string("st_direction").." "
if (wind_direction == WIND_DIRECTIONS.N) then
return text..game.translate_string("st_direction_north")
elseif (wind_direction == WIND_DIRECTIONS.NE) then
return text..game.translate_string("st_direction_north_east")
elseif (wind_direction == WIND_DIRECTIONS.E) then
return text..game.translate_string("st_direction_east")
elseif (wind_direction == WIND_DIRECTIONS.SE) then
return text..game.translate_string("st_direction_south_east")
elseif (wind_direction == WIND_DIRECTIONS.S) then
return text..game.translate_string("st_direction_south")
elseif (wind_direction == WIND_DIRECTIONS.SW) then
return text..game.translate_string("st_direction_south_west")
elseif (wind_direction == WIND_DIRECTIONS.W) then
return text..game.translate_string("st_direction_west")
elseif (wind_direction == WIND_DIRECTIONS.NW) then
return text..game.translate_string("st_direction_north_west")
end
end
function get_weather_text(current_weather)
if (current_weather == "clear") then
return game.translate_string("st_clear")
end
if (current_weather == "partly") then
return game.translate_string("st_partly_clear")
end
if (current_weather == "foggy") then
return game.translate_string("st_foggy")
end
if (current_weather == "cloudy") then
return game.translate_string("st_cloudy")
end
if (current_weather == "rain") then
return game.translate_string("st_rain")
end
if (current_weather == "storm") then
return game.translate_string("st_storm")
end
return ""
end
function get_current_weather()
local current_weather = level_weathers.get_weather_manager().weather_file
local weather_file_to_weather = {
w_clear = "clear",
w_partly = "partly",
w_cloudy = "cloudy",
w_rain = "rain",
w_storm = "storm",
w_foggy = "foggy",
tuman = "foggy"
}
local found = false
for k, v in pairs(weather_file_to_weather) do
if string.find(current_weather, k) then
current_weather = v
found = true
break
end
end
if not found then
current_weather = nil
end
return current_weather or level_weathers.get_weather_manager():get_curr_weather() or ""
end
function get_wind_velocity()
local wind_velocity = weather.get_value_numric("wind_velocity") or 0
if (wind_velocity > 0) then
return wind_velocity
else
return 1
end
end
-- Prints wind velocity from weather ltx, independednt on actual wind
function get_reported_wind_velocity()
local current_weather = level_weathers.get_weather_manager().weather_file
local hr = level.get_time_hours()
local ini = current_weather and ini_file("environment\\weathers\\" .. current_weather .. ".ltx")
local hr_string = level_weathers.get_weather_manager():get_hour_as_string(hr)
local wind_velocity = ini and hr_string and ini:section_exist(hr_string) and ini:r_string_ex(hr_string,"wind_velocity")
if not wind_velocity then
return get_wind_velocity()
end
return tonumber(wind_velocity)
end
function max_wind_power_percent(percentage)
return settings.MAX_WIND_VELOCITY * (percentage / 100)
end
-- CHEATS
-- Give dosimeter on game start
new_game_equippment = itms_manager.new_game_equippment
itms_manager.new_game_equippment = function()
new_game_equippment()
if settings.giveDosimeterOnStart and not db.actor:object("detector_geiger") then
alife_create_item("detector_geiger", db.actor)
end
return true
end