Added New Mods
Added - MRAA + Blindside Military Animation Pack - Dynamic Zone Transitions - Corpse Twitching - Artifact Inspection - Animated Lead Box
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user