--Used demonized's code under_suicide = false local wct = weapon_cover_tilt local dte = demonized_time_events local keybind = DIK_keys.DIK_T --change this to your NV Toggle Keybind if you don't use MCM -- Params local max_deg = 100 -- Max deg of gun Y rotation local max_coeff = max_deg / 75 -- Max value of coeff, 75 is WCT default local coeff = 0 -- Progress of suicide animation in terms of 0 to max_coeff local normalized_coeff = 0 -- Normalized coeff from 0 to 1 local up_increment = 0.0005 * max_coeff -- Increment of coeff --0.00018 local down_increment = 0.00027 * max_coeff -- Decrement of coeff local detector_hidden = false local default_smoothing = 11.5 local smoothed_values = {} local forced = false local function get_value(t) return type(t) == "table" and dup_table(t) or t 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 get_safe_sound_object = xr_sound.get_safe_sound_object local function bisect_left(a, x, lo, hi) local lo = lo or 1 local hi = hi or #a if lo < 0 then 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 local tkeys = array_keys(t, true) end local tkeys_len = #tkeys if key <= tkeys[1] then return get_value(t[tkeys[1]]) end if key >= tkeys[tkeys_len] then return get_value(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 get_value(t[lo]) end local delta = (key - lo) / (hi - lo) if type(t[lo]) ~= "table" then local res = delta * t[hi] + (1 - delta) * t[lo] return res else local res = {} for i = 1, #t[lo] do res[i] = delta * t[hi][i] + (1 - delta) * t[lo][i] end return res end end local offset_luts = { pistol = { [0] = {0, 0, 0}, [max_deg] = {-0.05, -0.17, -0.12}, }, pistol_silenced = { [0] = {0, 0, 0}, [max_deg] = {-0.05, -0.21, -0.12}, }, rifle = { [0] = {0, 0, 0}, [max_deg] = {-0.05, -0.23, -0.12}, }, rifle_silenced = { [0] = {0, 0, 0}, [max_deg] = {-0.05, -0.29, -0.12}, }, } local offset_luts_keys = (function() local t = {} for k, v in pairs(offset_luts) do t[k] = array_keys(v, true) end return t end)() local function ema(key, value, def, steps, delta) local steps = steps or default_smoothing local delta = delta or steps local smoothing_alpha = 2.0 / (steps + 1) smoothed_values[key] = smoothed_values[key] and smoothed_values[key] + math.min(smoothing_alpha * (delta / steps), 1) * (value - smoothed_values[key]) or def or value --printf("EMA fired, key %s, target %s, current %s, going %s", key, value, smoothed_values[key], (value > smoothed_values[key] and "up" or "down")) return smoothed_values[key] end local function reset_ema() smoothed_values["controller_coeff_roll"] = nil smoothed_values["controller_coeff_yaw"] = nil for i = 1, 3 do smoothed_values["offsets"..i] = nil end end function IsFirearm(wpn) return IsWeapon(wpn) and not (IsMelee(wpn) or IsItem("fake_ammo_wpn", wpn:section()) or wct.IsBinoc(wpn:section())) end local function lerp(a, b, f) if a and b and f then return a + f * (b - a) else return a or b or 0 end end function set_suicide() if not under_suicide then local actor = db.actor local active_item = actor:active_item() if active_item and IsFirearm(active_item) then actor_menu.set_msg(1, game.translate_string("st_suicide_start"),4) RegisterScriptCallback("actor_on_update", actor_on_update) under_suicide = true end end end function set_forced_suicide() if not under_suicide then actor_menu.set_msg(1, game.translate_string("st_suicide_start"),4) RegisterScriptCallback("actor_on_update", actor_on_update) under_suicide = true forced = true local actor = db.actor local active_item = actor:active_item() if not (active_item and IsFirearm(active_item)) then local slot_check = {2, 3, 1, 5, 4, 6, 7, 8, 9, 10, 11, 12, 13} for i, v in ipairs(slot_check) do local item = actor:item_in_slot(v) if item and IsFirearm(item) then actor:activate_slot(v) return end end actor:iterate_inventory(function(owner, item) if item and IsFirearm(item) then actor:make_item_active(item) return end end) end end end function unset_suicide() if under_suicide and (not forced) then actor_menu.set_msg(1, game.translate_string("st_suicide_next_time"),4) under_suicide = false end end function update_suicide_state() local actor = db.actor local delta = device().time_delta local up_increment = up_increment if forced then up_increment = up_increment / 2 end local diff = (under_suicide and up_increment or -down_increment) * delta local c = clamp(coeff + diff, 0, max_coeff) coeff = ema("controller_coeff", c, c, 1) normalized_coeff = normalize(coeff, 0, max_coeff) if coeff > 0 then wct.set_force_disabled() local current_deg = max_deg * normalized_coeff local det_active = actor:active_detector() if det_active and not detector_hidden then detector_hidden = true det_active:switch_state(2) end if axr_main.weapon_is_zoomed then if (get_console():get_bool("wpn_aim_toggle")) then level.press_action(bind_to_dik(key_bindings.kWPN_ZOOM)) else level.release_action(bind_to_dik(key_bindings.kWPN_ZOOM)) end end local wpn = actor:active_item() if not wpn then return end if not IsFirearm(wpn) then return end local sec = wpn:section() local hud_sec = SYS_GetParam(0, sec, "hud") if not hud_sec then return end wct.add_to_weapon_table(sec) local roll = ema("controller_coeff_roll", math.random(-10, 10), 0, 11, delta) local yaw = ema("controller_coeff_yaw", math.random(-5, 5), 0, 11, delta) wct.set_roll(roll) wct.set_yaw(yaw) local is_pistol = SYS_GetParam(0, sec, "kind", "") == "w_pistol" and not wct.not_pistol_sec[sec] local is_silenced = wct.isSilencedWeapon(wpn) local lut_key if is_silenced then lut_key = is_pistol and "pistol_silenced" or "rifle_silenced" else lut_key = is_pistol and "pistol" or "rifle" end local offset_lut = offset_luts[lut_key] local offset_lut_keys = offset_luts_keys[lut_key] local offsets = lookup(offset_lut, current_deg, offset_lut_keys) for i = 1, 3 do -- if weapon_cover_tilt_positions -- and weapon_cover_tilt_positions.weapon_offsets -- and weapon_cover_tilt_positions.weapon_offsets[sec] then -- local t = {"x", "y", "z"} -- local o = weapon_cover_tilt_positions.weapon_offsets[sec][t[i]] or 0 -- if t[i] == "y" then -- if current_deg < 90 then -- o = lerp(0, o, coeff) -- else -- local d = o * 90 / 75 -- o = lerp(d, 0, normalize(current_deg, 90, 180)) -- end -- -- offsets[i] = offsets[i] - o * 0.5 -- end -- end offsets[i] = ema("offsets"..i, offsets[i], 0, 15, delta) end wct.set_custom_offsets(offsets[1], offsets[2], offsets[3]) local new_pos, new_ori = wct.calculate_new_position(wpn, sec, coeff, delta, 10) wct.enable_tilt(sec, wpn) wct.set_weapon_position(sec, new_pos, new_ori) if coeff == max_coeff then if not forced then actor_menu.set_msg(1, game.translate_string("st_suicide_shoot"),4) end if forced then reset() local snd = (function() local def = "weapons\\ak74\\ak74_shoot" local snds = is_silenced and {"snd_silncer_shot_actor", "snd_silncer_shot", "snd_shoot_actor", "snd_shoot"} or {"snd_shoot_actor", "snd_shoot"} for i, v in ipairs(snds) do local s = SYS_GetParam(0, sec, v) if s then if ini_sys:section_exist(s) then s = SYS_GetParam(0, s, "snd_1_layer", s) end s = get_safe_sound_object(s) if s then return s end end end return get_safe_sound_object(def) end)() if not arszi_psy.fate_of_player_zombfication then if snd then snd:play(actor, 0, sound_object.s2d) snd.volume = 1 snd.frequency = 1 end actor_menu.set_msg(1, game.translate_string("st_psy_death_scene"), 4) actor:set_health_ex(0) else local item,slot = db.actor:active_item(),db.actor:active_slot() if item and (slot == 2 or slot == 3) then db.actor:drop_item(item) end arszi_psy.psy_table.actor_psy_health = 1.0 --trace_this("GOODWILL BEFORE: "..relation_registry.community_goodwill("stalker", AC_ID)) db.actor:set_character_community("actor_zombied", 0, 0) arszi_psy.psy_table.actor_zombied = true --game_relations.set_factions_community_num("actor_zombied", "stalker", -5000) --trace_this("GOODWILL AFTER: "..relation_registry.community_goodwill("stalker", AC_ID)) actor_menu.set_msg(1, game.translate_string("st_psy_zombification_scene"), 4) forced = false unset_suicide() end level.enable_input() end else suicide_countdown = 0 end else reset() end end function arszi_psy.manage_zombification() --Original community will be restored on load game always. Temp solution. if (arszi_psy.psy_table.actor_zombied and db.actor:character_community() ~= "actor_zombied") then db.actor:set_character_community("actor_zombied", 0, 0) end --Add visual effects if (arszi_psy.psy_table.actor_zombied) then --Todo why is alcohol not working!? level.add_pp_effector("radiation.ppe", c_id_ppe_radiation, true) level.add_pp_effector("alcohol.ppe", c_id_ppe_alcohol, true) end if (arszi_psy.psy_table.actor_psy_health <= 0) then --Death or Zombification if (arszi_psy.psy_table.current_stage ~= c_stage_3) then --show_message_news("ENTER STAGE 6") arszi_psy.remove_all_psy_ppe_effects() --level.add_pp_effector("black_infinite.ppe", c_id_ppe_black_infinite, true) set_forced_suicide() arszi_psy.psy_table.psy_death_scene = true level.disable_input() arszi_psy.psy_table.current_stage = c_stage_3 end end end function actor_on_update() try(function() update_suicide_state() end) end function reset() wct.set_force_disabled(false) wct.remove_roll_yaw() wct.set_custom_offsets() reset_ema() UnregisterScriptCallback("actor_on_update", actor_on_update) if actor_sound and actor_sound:playing() then actor_sound:stop() end actor_sound = nil if psy_sound and psy_sound:playing() then psy_sound:stop() end psy_sound = nil detector_hidden = false under_suicide = false coeff = 0 normalized_coeff = 0 end function actor_on_weapon_before_fire(flags) local actor = db.actor if coeff == max_coeff then flags.ret_value = false reset() actor_menu.set_msg(1, game.translate_string("st_suicide_goodbye"),4) local snd = (function() local wpn = actor:active_item() if not wpn then return end local sec = wpn:section() local hud_sec = SYS_GetParam(0, sec, "hud") if not hud_sec then return end local def = "weapons\\ak74\\ak74_shoot" local snds = is_silenced and {"snd_silncer_shot_actor", "snd_silncer_shot", "snd_shoot_actor", "snd_shoot"} or {"snd_shoot_actor", "snd_shoot"} for i, v in ipairs(snds) do local s = SYS_GetParam(0, sec, v) if s then if ini_sys:section_exist(s) then s = SYS_GetParam(0, s, "snd_1_layer", s) end s = get_safe_sound_object(s) if s then return s end end end return get_safe_sound_object(def) end)() if snd then snd:play(actor, 0, sound_object.s2d) snd.volume = 1 snd.frequency = 1 end actor:set_health_ex(0) end end function on_key_press(key) if (key == keybind) then if (not under_suicide) then set_suicide() else unset_suicide() end end end function on_option_change() keybind = selfkill_mcm.get_config("keybind_mcm") end function on_game_start() assert(weapon_cover_tilt and weapon_cover_tilt.VERSION and weapon_cover_tilt.VERSION >= 9, "Suicide mod ERROR: Weapon Cover Tilt UPDATE 9 or upper is required for Suicide mod") RegisterScriptCallback("actor_on_weapon_before_fire", actor_on_weapon_before_fire) wct.add_callback("actor_on_weapon_before_tilt", block_cover_tilt) RegisterScriptCallback("on_key_press", on_key_press) RegisterScriptCallback("on_option_change", on_option_change) on_option_change() end