Added New Mods

Added
- MRAA + Blindside Military Animation Pack
- Dynamic Zone Transitions
- Corpse Twitching
- Artifact Inspection
- Animated Lead Box
This commit is contained in:
2025-01-17 23:53:41 -05:00
parent 2009ca2711
commit fd1ec3a2ba
797 changed files with 20762 additions and 145 deletions
@@ -0,0 +1,40 @@
function is_valid_wpn(wpn)
local section = wpn:section()
local has_tristate_reload = ini_sys:r_string_ex(section,"tri_state_reload")
-- Ignore weapons that are not jammed / at max ammo capacity
local casted_wpn = wpn:cast_Weapon()
if (not casted_wpn) or (not casted_wpn:IsMisfire() and casted_wpn:GetAmmoElapsed() == casted_wpn:GetAmmoMagSize()) then
return false
end
if has_tristate_reload then
if animation_common.has_animation(section, "anm_open_aim") then
return true
else
return false
end
else
if animation_common.has_animation(section, "anm_reload_aim") then
return true
else
return false
end
end
end
function on_before_key_press(key, bind, dis, flags)
if bind ~= key_bindings.kWPN_RELOAD then return end
local wpn = db.actor:item_in_slot(db.actor:active_slot())
if not wpn then return end
if not is_valid_wpn(wpn) then return end
if wpn:get_state() == 7 then return end
wpn:switch_state(7)
flags.ret_value = false
end
function on_game_start()
if magazines then return end
RegisterScriptCallback("on_before_key_press", on_before_key_press)
end
@@ -0,0 +1,130 @@
-- Might want to extend this to track flags based on IDs since detectors have animations too
-- Likely overkill unless someone implements custom anims for them
FLAGS = {}
local active_flags = {}
-- Export: Flags
function get_flag(flag)
return active_flags[flag]
end
function set_flag(flag)
active_flags[flag] = true
end
function remove_flag(flag)
active_flags[flag] = nil
end
function add_flag(flag)
-- Exit early if it already exists
if FLAGS[flag] then return end
FLAGS[flag] = #FLAGS
end
local anim_mutators = {}
-- Export: Animation Mutators
function add_anim_mutator(functor, priority)
-- Add mutator func to list of mutators
table.insert(anim_mutators, {functor=functor, priority=priority})
-- Sort anim_mutators again
table.sort(anim_mutators, function(a,b) return a.priority < b.priority end)
end
function has_animation(section, anm)
local hud_section = ini_sys:r_string_ex(section,"hud") or section
return SYS_GetParam(0, hud_section, anm)
end
function mutate_anim(anm_table, anm_suffix, section)
local new_anm = anm_table.anm_name .. anm_suffix
if has_animation(section, new_anm) then
anm_table.anm_name = new_anm
return true
end
return false
end
-- Sounds
local sound_object_by_sec_by_path = {}
function get_safe_sound_object(path, sec)
assert(path, "path to sound file was nil")
assert(sec, "item section was nil")
if sound_object_by_sec_by_path[sec] == nil then
sound_object_by_sec_by_path[sec] = {}
end
local snd_obj = sound_object(path)
sound_object_by_sec_by_path[sec][path] = snd_obj
return snd_obj
end
function stop_all_sound_object(sec)
-- printf("stop_all_sound_object(sec): %s", sec)
for section, list in pairs(sound_object_by_sec_by_path) do
for path, v in pairs(list) do
if section == sec and v:playing() then
-- printf("sec: %s| Stopping: %s", sec, path)
v:stop()
end
end
sound_object_by_sec_by_path[sec] = nil
end
end
function get_sound_from_anm(anm_name) -- TODO: allow sound volume to be defined in config
return anm_name:gsub("anm_", "scripted_snd_")
end
-- Probably worth adding an ordered list of functions with conditions that can mutate anm_table
-- and use this script as source of truth for about-to-be-played animations since callback order is random between scripts
---@param anm_table {anm_name: string, anm_mixin: boolean, anm_mixin2: boolean, anm_state: integer, anm_speed: number, anm_end: number}
---@param item game_object
function actor_on_hud_animation_play(anm_table, item)
if not item then return end
stop_all_sound_object(item:section())
local section = item:section()
-- Execute each mutator functor in turn, ordered by priority
for i, mutator in ipairs(anim_mutators) do
local stop = mutator.functor(anm_table, item)
if stop then break end
end
-- Sound: Check if weapon has a valid scripted sound in config
local anm_name = anm_table.anm_name
local snd_name = get_sound_from_anm(anm_name)
local snd_value = SYS_GetParam(0, section, snd_name)
if not snd_value then return end
-- Look for actor-specific sounds if they exist
if item:parent() and item:parent():id() == AC_ID then
local snd_value_actor = SYS_GetParam(0, section, snd_name .. "_actor")
if snd_value_actor then
snd_value = snd_value_actor
end
end
local snd_values = str_explode(snd_value,",")
local snd_path = snd_values[1]
local never_cancel = snd_values[2]
-- Play sound
local snd_obj = get_safe_sound_object( snd_path, item:section() )
if not snd_obj then return end
if never_cancel then
snd_obj:play_no_feedback(db.actor, sound_object.s2d, 0, vector(), 1.0, 1.0)
else
snd_obj:play(db.actor, 0, sound_object.s2d)
end
end
function on_game_start()
RegisterScriptCallback("actor_on_hud_animation_play",actor_on_hud_animation_play)
end
@@ -0,0 +1,169 @@
local common = animation_common
valid_anims = {}
local mutate_anim = animation_common.mutate_anim
function animation_common.mutate_anim(anm_table, anm_suffix, section)
table.insert(valid_anims, anm_suffix)
return mutate_anim(anm_table, anm_suffix, section)
end
transitions = {
}
function add_strict_transition(hud_section, start_anm, end_anm, transition_anm)
if not (hud_section and start_anm and end_anm and transition_anm) then
printf("[ERROR] aol_anim_transitions: Could not add new transition (a parameter was nil)")
return
end
-- Make sure transitions is properly set as a table of tables
transitions[hud_section] = transitions[hud_section] or {}
transitions[hud_section][start_anm] = transitions[hud_section][start_anm] or {}
transitions[hud_section][start_anm][end_anm] = transition_anm
end
pattern_transitions = {
}
function add_pattern_transition(hud_section, start_anm, end_anm, transition_anm, priority)
priority = priority or 25565
if not (hud_section and start_anm and end_anm and transition_anm) then
printf("[ERROR] aol_anim_transitions: Could not add new transition (a parameter was nil)")
return
end
pattern_transitions[hud_section] = pattern_transitions[hud_section] or {}
table.insert(pattern_transitions[hud_section], {
["start_anm"] = start_anm,
["end_anm"] = end_anm,
["transition_anm"] = transition_anm,
["priority"] = priority
})
end
local ini_items = ini_file("system.ltx")
local function itr(section)
local is_hud_section = section:match('_hud$')
if is_hud_section then
local line_count = ini_items:line_count(section)
for i=0,line_count-1 do
local result, id, value = ini_items:r_line_ex(section,i,"","")
local is_strict_transition = id:match("^ts_strict_")
if is_strict_transition then
local anm_names = str_explode(value,",")
add_strict_transition(section, anm_names[1], anm_names[2], anm_names[3])
end
local is_pattern_transition = id:match("^ts_pattern_")
if is_pattern_transition then
local anm_names = str_explode(value,",")
add_pattern_transition(section, anm_names[1], anm_names[2], anm_names[3], anm_names[4])
end
end
end
return false
end
ini_items:section_for_each(itr)
table.sort(pattern_transitions, function(a,b) return a["priority"] < b["priority"] end)
local previous_anms = {}
function set_strict_transition(anm_table, hud_section)
-- Make sure item has transitions
local item_transitions = transitions[hud_section]
if not (item_transitions) then
-- previous_anms[hud_section] = anm_table.anm_name
return false
end
-- Make sure there is a previous anm to check
local start_anm = previous_anms[hud_section]
if not start_anm then
-- previous_anms[hud_section] = anm_table.anm_name
return false
end
-- Get transition anm from table
local end_anm = anm_table.anm_name
local transition_anm = item_transitions[start_anm] and item_transitions[start_anm][end_anm]
if not (transition_anm) then
-- previous_anms[hud_section] = anm_table.anm_name
return false
end
-- Re-add valid suffixes from previous mutations
for i, anm_suffix in ipairs(valid_anims) do
if animation_common.has_animation(hud_section, transition_anm .. anm_suffix) then
transition_anm = transition_anm .. anm_suffix
end
end
anm_table.anm_name = transition_anm
return true
end
function set_pattern_transition(anm_table, hud_section)
-- Make sure item has transitions
local item_transitions = pattern_transitions[hud_section]
if not item_transitions then
return false
end
-- Make sure there is a previous anm to check
local start_anm = previous_anms[hud_section]
if not start_anm then
-- previous_anms[hud_section] = anm_table.anm_name
return false
end
local end_anm = anm_table.anm_name
for _, transition in ipairs(item_transitions) do
local transition_anm = transition["transition_anm"]
-- Re-add valid suffixes from previous mutations
for i, anm_suffix in ipairs(valid_anims) do
if animation_common.has_animation(hud_section, transition_anm .. anm_suffix) then
transition_anm = transition_anm .. anm_suffix
end
end
if end_anm:match(transition["end_anm"]) and
start_anm:match(transition["start_anm"]) and
start_anm ~= transition_anm and
end_anm ~= transition_anm and
start_anm ~= end_anm then
anm_table.anm_name = transition_anm
return true
end
end
return false
end
function anm_transitions(anm_table, item)
-- printf("ITEM: " .. item:section() .. " | ANM: " .. anm_table.anm_name)
local hud_section = SYS_GetParam(0, item:section(), "hud", nil)
if not hud_section then
return
end
local has_strict_transition = set_strict_transition(anm_table, hud_section)
if not has_strict_transition then
set_pattern_transition(anm_table, hud_section)
end
-- Track now playing animation
previous_anms[hud_section] = anm_table.anm_name
-- Clear valid anims
valid_anims = {}
-- printf("ITEM: " .. item:section() .. " | ANM: " .. anm_table.anm_name)
end
function on_game_start()
common.add_anim_mutator(anm_transitions, 999)
end
@@ -0,0 +1,47 @@
-- The idea is to stop the walk2sprint animation if the player stops sprinting during the animation
-- Use my transition animations script as 'truth' for currently playing animation
-- Likely to break if multiple scripts are using actor_on_hud_animation_play, but should be good enough for transition animations specifically
local current_animation = ""
local stop_animation = false
-- Slightly cleaner string equivalence check which should ignore variants like _empty or _jammed
function isAnimationEqualTo(anim_name)
if current_animation:match(anim_name) then
return true
end
return false
end
aol_anim_transitions_anm_transitions = aol_anim_transitions.anm_transitions
function aol_anim_transitions.anm_transitions(anm_table, item)
local prev_name = anm_table.anm_name
aol_anim_transitions_anm_transitions(anm_table, item)
-- Restore original animation (probably idle) if previous animation was cancelled
-- This should prevent sprint2walk from playing if its counterpart walk2sprint was cancelled
if stop_animation then
stop_animation = false
if (isAnimationEqualTo("anm_walk2sprint") or isAnimationEqualTo("anm_sprint2walk")) then
anm_table.anm_name = prev_name
end
end
current_animation = anm_table.anm_name
end
-- Detect if the player is not sprinting during the walk2sprint animation
-- Firebreath: added the elseif in because sometimes the player stops sprint animation
-- when interrupted by small obstacles like stairs
function actor_on_movement_changed(num)
if stop_animation then return end
if (not IsMoveState("mcSprint") and isAnimationEqualTo("anm_walk2sprint")) then
stop_animation = true
game.stop_hud_motion()
elseif (IsMoveState("mcSprint") and isAnimationEqualTo("anm_sprint2walk")) then
stop_animation = true
game.stop_hud_motion()
end
end
function on_game_start()
RegisterScriptCallback("actor_on_movement_changed",actor_on_movement_changed)
end
@@ -0,0 +1,255 @@
-- custom loadouts
local custom_loadouts = {
name = {},
squad = {},
visual = {}
}
local custom_weapons = {}
local ini_npc_loadouts = ini_file("loadouts\\npc_importer.ltx")
local ini_wep_loadouts = ini_file("loadouts\\wep_importer.ltx")
local ini_loadouts = ini_file("items\\settings\\npc_loadouts\\npc_loadouts.ltx")
local scope_chance = {}
local MAX_ITER = 10
local factions = {
["stalker"] = true,
["csky"] = true,
["dolg"] = true,
["ecolog"] = true,
["freedom"] = true,
["killer"] = true,
["army"] = true,
["bandit"]= true,
["monolith"] = true,
["renegade"] = true,
["isg"] = true,
["zombied"] = true,
["greh"] = true
}
local ranks = {
["novice"] = true,
["trainee"] = true,
["experienced"] = true,
["professional"] = true,
["veteran"] = true,
["expert"] = true,
["master"] = true,
["legend"] = true,
}
local slots = {
["primary"] = "primary",
["p"] = "primary",
["secondary"] = "secondary",
["s"] = "secondary",
["extra"] = "extra",
["e"] = "extra",
}
function preload()
tmp = ini_loadouts:r_string_ex("settings","scope_chance")
if tmp then
tmp = str_explode(tmp,",")
for i=1,#tmp do
scope_chance[i] = tonumber(tmp[i]) or 0
end
end
custom_loadouts.meta = {}
ini_npc_loadouts:section_for_each(function(section)
local loadouts = ini_npc_loadouts:r_string_ex(section, "loadout_sec")
loadouts = str_explode(loadouts, ",")
local weapons = ini_npc_loadouts:r_string_ex(section, "weapons_sec")
-- skip if loadout/weapons not defined (bad section)
if is_empty(loadouts) or not weapons then return end
if not ini_wep_loadouts:section_exist(weapons) then return end
local n = ini_wep_loadouts:line_count(weapons)
for i=0,n-1 do
local _, id, value = ini_wep_loadouts:r_line_ex(weapons,i)
--printf("Adding custom weapon %s", id)
if not custom_weapons[id] then
tmp = str_explode(id,":")
if tmp and tmp[1] and ini_sys:section_exist(tmp[1]) then
custom_weapons[id] = {}
custom_weapons[id].sec = tmp[1]
custom_weapons[id].attachment = tmp[2] or "0"
custom_weapons[id].ammo_type = tmp[3] or "0"
custom_weapons[id].chance = tonumber(tmp[4]) or 50
end
end
end
for k,loadout in pairs(loadouts) do
add_weapons_to_loadout(loadout, weapons)
end
end)
end
function add_weapons_to_loadout(loadout, weapons)
--printf("Adding %s section of weapons to %s", weapons, loadout)
tokens = str_explode(loadout, "_")
if not (#tokens == 2 or #tokens == 3) then
--printf("Invalid length")
return end
if not tokens[3] then tokens[3] = "primary" end
if not factions[tokens[1]] then
--printf("%s not faction", tokens[1])
return end
if not ranks[tokens[2]] then
--printf("%s not rank", tokens[2])
return end
if not slots[tokens[3]] then
--printf("%s not slot", tokens[3])
return end
faction, rank, slot = unpack(tokens)
slot = slots[slot]
if not custom_loadouts[faction] then custom_loadouts[faction] = {} end
if not custom_loadouts[faction][rank] then custom_loadouts[faction][rank] = {} end
if not custom_loadouts[faction][rank][slot] then custom_loadouts[faction][rank][slot] = {} end
local n = ini_wep_loadouts:line_count(weapons)
--printf("Adding %s weapons for %s %s %s", n, faction, rank, slot)
for i=0,n-1 do
local _, id, value = ini_wep_loadouts:r_line_ex(weapons,i)
if custom_weapons[id] then
l = custom_loadouts[faction][rank][slot]
l[#l+1] = id
end
end
end
function get_table(comm, rank, slot)
if not custom_loadouts[comm] then return {} end
if not custom_loadouts[comm][rank] then return {} end
return custom_loadouts[comm][rank][slot] or {}
end
CreateItem = xrs_rnd_npc_loadout.create_item_on_npc
function xrs_rnd_npc_loadout.create_item_on_npc(se_npc, squad_name, comm, rank, visual, player_id, slot_type)
-- Get loadout section
local loadout = ini_loadouts:r_string_ex("loadouts_per_squad",squad_name) or ini_loadouts:r_string_ex("loadouts_per_name",se_npc:section_name()) or ini_loadouts:r_string_ex("loadouts_per_visual",visual) or (comm .. "_" .. rank)
if (not ini_loadouts:section_exist(loadout)) then
loadout = comm
if (not ini_loadouts:section_exist(loadout)) then
loadout = "default"
end
end
-- Get slot section
local slot = ini_loadouts:r_string_ex(loadout,slot_type)
--printf("Reading entries for slot %s, for %s %s %s", slot, comm, rank, slot_type)
local vanilla_loadout_entries = not (slot and ini_loadouts:section_exist(slot)) and 0 or ini_loadouts:line_count(slot)
local custom_tbl = get_table(comm, rank, slot_type)
if not custom_tbl or is_empty(custom_tbl) then
--printf("Defaulting to vanilla loadout")
CreateItem(se_npc, squad_name, comm, rank, visual, player_id, slot_type)
return
end
-- randomly pick entries from this range
-- if the range is in vanilla loadout entries, roll from there
-- otherwise
local to_pick = vanilla_loadout_entries + #custom_tbl
--printf("Picking from %s + %s entries", vanilla_loadout_entries, #custom_tbl)
local pick = nil
local iterations = 0
while not pick and iterations < MAX_ITER do
local p = math.random(to_pick)
if p > vanilla_loadout_entries then
local new_entry = p - vanilla_loadout_entries
local wpn = custom_weapons[custom_tbl[new_entry]]
if math.random(100) < wpn.chance then
pick = p
break
end
elseif math.random(100) < 50 then
pick = p
break
end
iterations = iterations + 1
end
if not pick then pick = math.random(to_pick) end
local weapon_entry
if pick > vanilla_loadout_entries then
pick = pick - vanilla_loadout_entries
id = custom_tbl[pick]
if id then
weapon_entry = custom_weapons[id]
end
end
if weapon_entry then
create_item(se_npc, weapon_entry)
else
CreateItem(se_npc, squad_name, comm, rank, visual, player_id, slot_type)
end
end
function create_item(se_npc, item_data)
local section = item_data.sec
local ammo_typ = item_data.ammo_type
local attachment = item_data.attachment
-- Items
if (slot_type == "extra") then
alife_create_item(section, se_npc)
-- Weapons
else
-- Add random scope
local cha = game_difficulties.get_eco_factor("scope_chance") or scope_chance[ game_difficulties.get_eco_factor("type") or 1 ]
cha = cha * 100
if ini_sys:line_exist(section,"scopes") and (math.random(100) <= cha) then
local scopes = parse_list(ini_sys, section, "scopes")
if scopes and (#scopes > 0) then
local pick_scope = scopes[math.random(#scopes)]
if pick_scope and ini_sys:section_exist(section .. "_" .. pick_scope) then
section = section .. "_" .. pick_scope
end
end
end
local se_wpn = alife_create_item(section, se_npc)
if (se_wpn) then
local ammos = parse_list(ini_sys, section, "ammo_class")
local ct = ammos and #ammos
local ammo_type = (ammos and ammo_typ and ammo_typ == "r" and ct and math.random(0,ct-1)) or (ammos and ammo_typ and tonumber(ammo_typ)) or 0
local ammo_section = ammo_type and ammos[ammo_type+1]
if not (ammo_section) then
ammo_type = 0
ammo_section = ammo_type and ammos[ammo_type+1]
printe("! ERROR: NPC Loadouts | wrong ammo_type set for [%s], missing value in ammo_class", section)
end
if (attachment or ammo_typ) then
local data = utils_stpk.get_weapon_data(se_wpn)
local flag = tonumber(attachment) or 0
if (attachment == "r") then
flag = 0
if (utils_data.read_from_ini(nil,section,"scope_status","float",nil)) then
flag = flag + 1
end
if (utils_data.read_from_ini(nil,section,"grenade_launcher_status","float",nil)) then
flag = flag + 2
end
if (utils_data.read_from_ini(nil,section,"silencer_status","float",nil)) then
flag = flag + 4
end
flag = math.random(0,flag)
end
data.addon_flags = flag
data.ammo_type = ammo_type
utils_stpk.set_weapon_data(data,se_wpn)
end
end
end
end
function on_game_start()
preload()
end
@@ -0,0 +1,11 @@
function actor_on_hud_animation_play(anm_table)
if (anm_table.anm_name == "anm_add_cartridge") then
anm_table.anm_mixin = false
end
end
function on_game_start()
RegisterScriptCallback("actor_on_hud_animation_play", actor_on_hud_animation_play)
end
@@ -0,0 +1,58 @@
common = animation_common
function block_shotgun_reload(anm_table, item)
-- Ignore weapons without unjam animation
if not common.has_animation(item:section(), "anm_reload_misfire") then
return
end
-- Exit early
if not (IsShotgun(item)
and anm_table.anm_name == "anm_open"
and item:cast_Weapon():IsMisfire()) then
return
end
-- Cancel shotgun reload
anm_table.anm_name = "$cancel"
-- Play unjam animation and sound
CreateTimeEvent("shotgun_unjam_fix", "start_unjam", 0.001, function(wpn)
wpn:play_hud_motion("anm_reload_misfire", true, 0, 1, 0)
-- Allow sound cancel if animation is interrupted
local sound_path = SYS_GetParam(0, wpn:section(), "snd_reload_misfire")
if sound_path then
local sound = common.get_safe_sound_object(sound_path, wpn:section())
sound:play(db.actor, 0, sound_object.s2d)
end
common.set_flag("SHOTGUN_UNJAM")
return true
end, item)
-- Stop checks for variants
return true
end
common.add_anim_mutator(block_shotgun_reload, -9999)
function actor_on_hud_animation_end(item,section,motion,state,slot)
-- Clear jam when unjam anim ends
if common.get_flag("SHOTGUN_UNJAM") then
item:cast_Weapon():SetMisfire(false)
common.remove_flag("SHOTGUN_UNJAM")
end
end
-- Stop unjam spam
function on_before_key_press(dik, bind, dis, flags)
if bind == key_bindings.kWPN_RELOAD
and common.get_flag("SHOTGUN_UNJAM") then
flags.ret_value = false
end
end
function on_game_start()
RegisterScriptCallback("actor_on_hud_animation_end",actor_on_hud_animation_end)
RegisterScriptCallback("on_before_key_press",on_before_key_press)
end
@@ -0,0 +1,12 @@
local common = animation_common
-- Animation: Append with '_ammoX' if weapon has X amount of ammo left
local function anm_ammo(anm_table, item)
local section = item:section()
local ammo_amount = item:get_ammo_in_magazine()
local suffix = "_ammo" .. ammo_amount
common.mutate_anim(anm_table, suffix, section)
end
common.add_anim_mutator(anm_ammo, 3.5)
@@ -0,0 +1,128 @@
local common = animation_common
-- Flags
common.add_flag("SHOTGUN_PUMP")
-------------------------------------------------
-- Animation Mutators
-------------------------------------------------
-- Animation: Append animation name with '_empty' if weapon is empty and has valid anim for it
local function anm_empty(anm_table, item)
local section = item:section()
if IsWeapon(item) and item.get_ammo_in_magazine then
local ammo = item:get_ammo_in_magazine()
-- check if weapon is empty
if ammo == 0 then
-- check if anim is defined in config
common.mutate_anim(anm_table, "_empty", section)
common.set_flag("SHOTGUN_PUMP")
end
end
end
common.add_anim_mutator(anm_empty, 1)
-- Animation: Append with '_g' if weapon has a grenade launcher and can fire a grenade
-- Append with '_w_gl' if weapon has a grenade launcher, but is not in GL mode (can fire bullets)
-- Append with '_alt' if weapon does not have a GL, but is in alt aim mode
local function anm_alt_modes(anm_table, item)
local section = item:section()
local alt = item:weapon_in_grenade_mode()
local has_gl = utils_item.has_attached_gl(item)
if alt and has_gl then
common.mutate_anim(anm_table, "_g", section)
elseif has_gl then
common.mutate_anim(anm_table, "_w_gl", section)
elseif alt then
common.mutate_anim(anm_table, "_alt", section)
end
end
common.add_anim_mutator(anm_alt_modes, 1.1)
-- Animation: Append with '_aim' is player is aiming down sights
local function anm_ads(anm_table, item)
local section = item:section()
local wpn = item:cast_Weapon()
if not wpn then return end
if wpn:IsZoomed() then
common.mutate_anim(anm_table, "_aim", section)
end
end
common.add_anim_mutator(anm_ads, 1.3)
-- Animation: Append with '_jammed' if weapon misfires (vanilla) or fails to eject (WPO)
-- Append with '_superjammed' if magazine needs to be removed before unjam (WPO)
local function anm_jammed(anm_table, item)
local section = item:section()
local jam_type = nil
-- Check for WPO jams
if arti_jamming then
local jam_status = arti_jamming.get_jam_status(item:id())
if jam_status == arti_jamming.JamType.FailureToEject then
jam_type = "_jammed"
elseif jam_status == arti_jamming.JamType.DoubleFeed then
jam_type = "_superjammed"
end
end
-- Check for vanilla jams (vanilla jams appear in anm_shots before WPO catches them)
if not jam_type then
local wpn = item:cast_Weapon()
if not wpn then return end
jam_type = item:cast_Weapon():IsMisfire() and "_jammed" or nil
end
if jam_type then
local success = common.mutate_anim(anm_table, jam_type, section)
-- Fallback to '_jammed' if '_superjammed' does not exist
if not success and jam_type == "_superjammed" then
common.mutate_anim(anm_table, jam_type, section)
end
end
end
common.add_anim_mutator(anm_jammed, 2)
-- Animation: Prepend with _pump if performing empty reload
local function anm_pump(anm_table, item)
local section = item:section()
if common.get_flag("SHOTGUN_PUMP") then
if anm_table.anm_name == "anm_close" then
common.mutate_anim(anm_table, "_pump", section)
-- Clear flag
common.remove_flag("SHOTGUN_PUMP")
elseif string.find(anm_table.anm_name, "anm_hide") then
common.remove_flag("SHOTGUN_PUMP")
end
end
end
common.add_anim_mutator(anm_pump, 3)
-- Animation: Randomly select between variants of the same animation
local function anm_variants(anm_table, item)
local section = item:section()
local variants = {anm_table.anm_name}
local i = 2
while (true) do
local anm_variant = anm_table.anm_name .. "_variant" .. (i-1)
if common.has_animation(section, anm_variant) then
variants[i] = "_variant" .. (i-1)
else
break
end
i = i + 1
end
if #variants > 1 then
local rand_i = random_number(1, #variants)
common.mutate_anim(anm_table, variants[rand_i], section)
end
end
common.add_anim_mutator(anm_variants, 4)
-------------------------------------------------
-- Callbacks
-------------------------------------------------
function on_game_start()
end
@@ -0,0 +1,198 @@
-- Animation: Append with '_quick' for detectors if:
-- - weapon in main hand is reloading or unjamming
-- - player is climbing
-- - switching to PDA
-- - quick throwing grenades
-- - wiping mask
-- - toggling headlamp
-- - playing an FDDA animation
local common = animation_common
common.add_flag("QUICK_DETECTOR")
-- Flag to force _quick when hiding detectors
force_quick = false
-- Compatibility for FDDA: Use '_quick' if an FDDA animation is about to play
if enhanced_animations then
fdda_anim_prepare = enhanced_animations.anim_prepare
function enhanced_animations.anim_prepare()
force_quick = true
fdda_anim_prepare()
end
fdda_call_my_slot_back = enhanced_animations.call_my_slot_back
function enhanced_animations.call_my_slot_back()
local result = fdda_call_my_slot_back()
if result == true then
force_quick = false
end
return result
end
fdda_bp_call_my_slot_back = zzz_ea_addon_backpack.call_my_slot_back
function zzz_ea_addon_backpack.call_my_slot_back()
local result = fdda_bp_call_my_slot_back()
if result == true then
force_quick = false
end
return result
end
end
-- Switching to PDA (PDA keybind)
-- Beefs nvg switch
function on_key_press(dik)
local bind = dik_to_bind(dik)
-- Quickhide detector when switching on beefs nvgs
if z_beefs_nvgs and bind == key_bindings.kNIGHT_VISION and db.actor:active_detector() then
force_quick = true
return
end
-- PDA keybind
if bind ~= key_bindings.kACTIVE_JOBS then return end
-- Detector drawn
if not db.actor:active_detector() then return end
-- PDA equipped
if not db.actor:item_in_slot(8) then return end
force_quick = true
end
-- Switching to PDA (Map keybind)
function actor_on_first_update()
local activate_slot = db.actor.activate_slot
db.actor.activate_slot = function(self, slot_num)
if slot_num == 8 and db.actor:active_detector() then
force_quick = true
end
activate_slot(self, slot_num)
end
end
-- Toggling headlamp
local Hit_TorchToggle = actor_effects.Hit_TorchToggle
function actor_effects.Hit_TorchToggle()
-- Check if headlamp is equipped. No need for battery check
if (not actor_effects.allow_animation()) or (not item_device.can_toggle_torch()) then
return
end
force_quick = true
Hit_TorchToggle()
end
-- Wiping Mask
local Hit_MaskCleaning = actor_effects.Hit_MaskCleaning
function actor_effects.Hit_MaskCleaning()
-- Check if mask overlay is ON
if (not actor_effects.allow_animation()) or (not actor_effects.is_mask_on()) then
return
end
force_quick = true
Hit_MaskCleaning()
end
-- Quick throw grenade
local Hit_GrenadeQuickthrow = actor_effects.Hit_GrenadeQuickthrow
function actor_effects.Hit_GrenadeQuickthrow()
if (not actor_effects.allow_animation())
or (not db.actor:item_in_slot(4))
or db.actor:get_current_holder() ~= nil
then
return
end
force_quick = true
Hit_GrenadeQuickthrow()
end
---@param item game_object
local function is_item_valid(item)
-- valid if item is equipped detector
local player_detector = db.actor:active_detector()
if player_detector and item:id() == player_detector:id() then
return true
end
return false
end
---@param anm_table any
---@param item game_object
local function anm_quick_detector(anm_table, item)
-- cancel if parent is not player
local parent = item:parent()
if not parent then return end
if parent:id() ~= AC_ID then return end
-- cancel if not valid item
if not is_item_valid(item) then return end
-- Add 'quick' if previous anim was also quick (intended for holster-draw pairs)
if common.get_flag("QUICK_DETECTOR") then
if string.find(anm_table.anm_name, "show") then
common.mutate_anim(anm_table, "_quick", item:section())
end
common.remove_flag("QUICK_DETECTOR")
return
end
local is_state_valid = {
[4] = true, -- inspect
[7] = true, -- reload
[8] = true, -- misfire
}
-- Add 'quick' suffix if above conditions are fulfilled
local player_wpn = db.actor:active_item()
if (
string.find(anm_table.anm_name, "hide")
and (
player_wpn and is_state_valid[player_wpn:get_state()]
or IsMoveState("mcClimb")
or force_quick
)
) then
common.mutate_anim(anm_table, "_quick", item:section())
common.set_flag("QUICK_DETECTOR")
force_quick = false
end
end
common.add_anim_mutator(anm_quick_detector, 5)
-- Use anim mutator to hide detector during inspect animations (added by Utjan)
local inspected_wpn
---@param anm_table any
---@param item game_object
local function inspect_hides_detector(anm_table, item)
-- cancel if parent is not player
local parent = item:parent()
if not parent then return end
if parent:id() ~= AC_ID then return end
if string.find(anm_table.anm_name, "bore") then
local active_det = db.actor:active_detector()
if active_det then
active_det:switch_state(2)
inspected_wpn = item
end
elseif inspected_wpn and inspected_wpn:id() == item:id() then
inspected_wpn = nil
db.actor:show_detector(true)
end
end
common.add_anim_mutator(inspect_hides_detector, 6)
function on_game_start()
RegisterScriptCallback("actor_on_first_update", actor_on_first_update)
RegisterScriptCallback("on_key_press", on_key_press)
end
@@ -0,0 +1,21 @@
-- Clear jam mid animation with motion marks
---@param state number
---@param mark string
---@param hud_item game_object
---@param owner game_object
function actor_on_hud_animation_mark(state, mark, hud_item, owner)
if mark == "clear_jam" then
if not hud_item or not owner then return end
local wpn = hud_item:cast_Weapon()
if not wpn then return end
if owner:id() ~= AC_ID then return end
wpn:SetMisfire(false)
end
end
function on_game_start()
RegisterScriptCallback("actor_on_hud_animation_mark",actor_on_hud_animation_mark)
end
@@ -0,0 +1,93 @@
local common = animation_common
do_ammo_check_anim = nil
if ammo_check_mcm then
base_check_Ammo = ammo_check_mcm.check_Ammo
function ammo_check_mcm.check_Ammo()
if ammo_check_mcm.busy_hands_fix then
base_check_Ammo()
return
end
if ammo_check_mcm.actor_on_hud_animation_play then
printf("!Old mag check script (ammo_check_mcm) detected. Remove it to get full features of mag checks")
base_check_Ammo()
return
end
local weapon = db.actor:active_item()
-- ends if no weapon is in hand etc.
if (weapon == nil or (not IsWeapon(weapon)) or IsItem("fake_ammo_wpn", nil, weapon)) then
base_check_Ammo()
return
end
local currentState = weapon:get_state()
if not (currentState == 0 or weapon:weapon_in_grenade_mode()) then
base_check_Ammo()
return
end
-- if magazine weapon
local currentAmmo = weapon:get_ammo_in_magazine()
if (currentAmmo == nil) then
base_check_Ammo()
return
end
local anm_ammo_check = common.has_animation(weapon:section(), "anm_ammo_check")
if not anm_ammo_check then
base_check_Ammo()
return
end
if do_ammo_check_anim then return end -- If an ammo check is already happening, don't do anything
if ammo_check_mcm.busy_hands_fix ~= nil then -- If busy_hands_fix is unlocalized
local prev_hands_fix = ammo_check_mcm.busy_hands_fix
ammo_check_mcm.busy_hands_fix = true
base_check_Ammo()
ammo_check_mcm.busy_hands_fix = prev_hands_fix
else -- Else fall back to workaround
local prev_hands_fix = ui_mcm.get("rax_ammo_check/busy_hands_fix")
ui_mcm.set("rax_ammo_check/busy_hands_fix", true)
ammo_check_mcm.on_option_change()
base_check_Ammo()
ui_mcm.set("rax_ammo_check/busy_hands_fix", prev_hands_fix)
ammo_check_mcm.on_option_change()
end
do_ammo_check_anim = 1
weapon:switch_state(4)
CreateTimeEvent("mag_check", "mag_check_reset", 3, function()
do_ammo_check_anim = nil
return true
end)
end
end
-- Catch the inspect weapon state switch and do the ammo check animation instead
function mag_check_animation(anm_table, obj)
if not (do_ammo_check_anim == 1 and obj and obj:get_state() == 4) then return end
local is_supported, has_mag_data
if magazine_binder then
is_supported = magazine_binder.is_supported_weapon(obj:section())
has_mag_data = magazine_binder.get_mag_loaded(obj:id())
end
if is_supported and not has_mag_data and common.has_animation(obj:section(), "anm_ammo_check_no_mag") then
anm_table.anm_name = "anm_ammo_check_no_mag"
else
anm_table.anm_name = "anm_ammo_check"
end
do_ammo_check_anim = 2
end
if ammo_check_mcm.actor_on_hud_animation_play then
printf("!Old mag check script (ammo_check_mcm) detected. Remove it to get full features of mag checks")
else
common.add_anim_mutator(mag_check_animation, 0.1)
end