function conf(...) return lual_viewmodel_droop_mcm.get_config(...) end function on_game_start() RegisterScriptCallback("actor_on_update", actor_on_update) RegisterScriptCallback("actor_on_weapon_zoom_in", actor_on_weapon_zoom_in) RegisterScriptCallback("actor_on_weapon_zoom_out", actor_on_weapon_zoom_out) RegisterScriptCallback("actor_on_weapon_fired", actor_on_weapon_fired) end function holding_pda(actor) local slot = actor:active_slot() return slot == 8 or slot == 14 end function IsThrowable(item) -- why check param manually? return IsBolt(item) or SYS_GetParam(0, item:section(), "class", "") == "II_BOLT" or IsGrenade(item) end -- themrdemonized/xray-monolith -- src/xrGame/actor_defs.h, line 32 local mcFwd = 1 local mcBack = 2 local mcLStrafe = 4 local mcRStrafe = 8 function print_loud(msg) news_manager.send_tip(db.actor, msg, nil, nil, 30000) end local state = { smoothed_droop = 0, visual_droop = {0, 0, 0, 0, 0, 0}, } _G.droop_state = state function lerp(v0, v1, t) t = math.min(t, 1) return v0 + (v1 - v0) * t end local cached_offsets = {} function isScopedWeapon(wpn) if not wpn then return false end local has_scopes_sect = SYS_GetParam(0, wpn:section(), "scopes_sect", "") ~= "" local has_texture = SYS_GetParam(0, wpn:section(), "scope_texture", "") ~= "" local cobj = wpn:cast_Weapon() -- If old scopes system if has_scopes_sect then if cobj and cobj:IsScopeAttached() or wpn:weapon_is_scope() then return true end else -- If there is fixed scope or no scope defined, but also if there is a scope texture, it will use the texture for zoomin and considered a scoped weapon if (wpn:weapon_scope_status() == 1 or wpn:weapon_scope_status() == 0) and has_texture then return true end -- Usual check if (cobj and cobj:IsScopeAttached() or wpn:weapon_is_scope()) and has_texture then return true end end return false end function get_cached_offset(sec) local t = cached_offsets[sec] if t then return t end local hud_sec = SYS_GetParam(0, sec, "hud") t = { (function() local c = str_explode( utils_xml.is_widescreen() and SYS_GetParam(0, hud_sec, "hands_position_16x9") or SYS_GetParam(0, hud_sec, "hands_position") or "0,0,0", "," ) return { tonumber(c[1]) or 0, tonumber(c[2]) or 0, tonumber(c[3]) or 0 } end)(), (function() local c = str_explode( utils_xml.is_widescreen() and SYS_GetParam(0, hud_sec, "hands_orientation_16x9") or SYS_GetParam(0, hud_sec, "hands_orientation") or "0,0,0", "," ) return { tonumber(c[1]) or 0, tonumber(c[2]) or 0, tonumber(c[3]) or 0 } end)() } cached_offsets[sec] = t return t end local ads = false local ads_scoped = false local droop_recovery_ads = 0 local droop_recovery_fire = 0 function target_droop(target, actor) local rate = ads and conf("droop_rate_ads") or conf("droop_rate") if ads_scoped or actor:active_detector() then rate = (1 / 0.4) * 8 end state.smoothed_droop = lerp(state.smoothed_droop, target, math.min(device().time_delta * rate / 1000, 1)) state.visual_droop[1] = conf("yaw") * state.smoothed_droop state.visual_droop[2] = conf("pitch") * state.smoothed_droop state.visual_droop[3] = conf("roll") * state.smoothed_droop state.visual_droop[4] = conf("right") * state.smoothed_droop state.visual_droop[5] = conf("up") * state.smoothed_droop state.visual_droop[6] = conf("forwards") * state.smoothed_droop end crosshair_enabled = false function actor_on_weapon_zoom_in(obj) crosshair_enabled = get_console_cmd(1, "hud_crosshair") if crosshair_enabled then exec_console_cmd("hud_crosshair off") end ads = true ads_scoped = isScopedWeapon(obj) end function actor_on_weapon_zoom_out(obj) if crosshair_enabled then exec_console_cmd("hud_crosshair on") end ads = false ads_scoped = false droop_recovery_ads = time_continual() end function actor_on_weapon_fired() droop_recovery_fire = time_continual() end function actor_on_update(binder) local actor = db.actor if holding_pda(actor) then return target_droop(0, actor) end local item = actor:active_item() if not item then return target_droop(0, actor) end if IsThrowable(item) then return target_droop(0, actor) end if actor:active_detector() then return target_droop(0, actor) end local move_state = level.actor_moving_state() local fwd = bit_and(move_state, mcFwd) ~= 0 local back = bit_and(move_state, mcBack) ~= 0 local left = bit_and(move_state, mcLStrafe) ~= 0 local right = bit_and(move_state, mcRStrafe) ~= 0 local strafe = (left or right) and not (left and right) if fwd and back then fwd = false back = false end local last_recovery = math.max( droop_recovery_ads + conf("aim_timeout") * 1000, droop_recovery_fire + conf("fire_timeout") * 1000 ) local droop if time_continual() <= last_recovery then droop = 0 elseif ads then droop = 0 elseif fwd then if strafe then droop = 0.75 -- strafing forwards else droop = 1.0 -- walking fowards end elseif back then if strafe then droop = 0.25 -- strafing back else droop = 0 -- walking back end elseif strafe then droop = 0.5 -- strafing else -- standing sitll droop = 0 end local time = (time_continual() - last_recovery) / (conf("recover_length") * 1000) if (0 < time and time < 1) then droop = droop * time end target_droop(droop, actor) print(conf("recover_length")) end