Download Lua/Tactical/AnimationGraph.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 22.6 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/Lua/Tactical/AnimationGraph.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/Lua/Tactical/AnimationGraph.lua
-
curl -L -o AnimationGraph.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/Lua/Tactical/AnimationGraph.lua
22.6 kB
| -- not specified animations continue to "Idle" | |
| local s_TransitionRules = { | |
| ["%s_Standing_Idle"] = { | |
| { "%s_Standing_Run", moment = "any", last = true }, | |
| { "%s_Standing_Walk", moment = "any", last = true }, | |
| { "%s_Standing_CombatRun", moment = "any", last = true }, | |
| { "%s_Standing_CombatWalk", moment = "any", last = true }, | |
| { "%s_Standing_To_Crouch", moment = "any" }, | |
| { "%s_Standing_To_Prone", moment = "any" }, | |
| { "%s_Standing_Aim", moment = "any", last = true }, | |
| { "%s_Standing_ExposeLeft_Start", moment = "any", }, | |
| { "%s_Standing_ExposeRight_Start", moment = "any", }, | |
| { "gr_Standing_Aim", moment = "any", last = true }, | |
| }, | |
| ["%s_Crouch_Idle"] = { | |
| { "%s_Standing_Run", moment = "any", last = true }, | |
| { "%s_Standing_Walk", moment = "any", last = true }, | |
| { "%s_Standing_CombatRun", moment = "any", last = true }, | |
| { "%s_Standing_CombatWalk", moment = "any", last = true }, | |
| { "%s_Crouch_To_Standing", moment = "any" }, | |
| { "%s_Crouch_To_Prone", moment = "any" }, | |
| { "%s_TakeCover_Idle", moment = "any" }, | |
| { "gr_Standing_Aim", moment = "any" }, | |
| { "nw_Bandaging_Start", moment = "any" }, | |
| }, | |
| ["%s_Prone_Idle"] = { | |
| { "%s_Prone_To_Standing", moment = "any" }, | |
| { "%s_Prone_To_Crouch", moment = "any" }, | |
| }, | |
| ["%s_Downed_Idle"] = { | |
| { "%s_Downed_Standing", moment = "any" }, | |
| { "%s_Downed_Crouch", moment = "any" }, | |
| { "%s_Downed_Prone", moment = "any" }, | |
| }, | |
| ["%s_Standing_Run"] = { | |
| { "%s_Standing_Idle", moment = "any" }, | |
| }, | |
| ["%s_Standing_Walk"] = { | |
| { "%s_Standing_Idle", moment = "any" }, | |
| }, | |
| ["%s_Standing_CombatRun"] = { | |
| { "%s_Standing_Idle", moment = "any" }, | |
| }, | |
| ["%s_Standing_CombatWalk"] = { | |
| { "%s_Standing_Idle", moment = "any" }, | |
| }, | |
| ["%s_Standing_Aim"] = { | |
| { "%s_Standing_Idle", moment = "any" }, | |
| }, | |
| ["%s_Standing_Aim_Forward"] = { | |
| { "%s_Standing_Idle", moment = "any" }, | |
| }, | |
| ["%s_Standing_Aim_Down"] = { | |
| { "%s_Standing_Idle", moment = "any" }, | |
| }, | |
| ["%s_Standing_Fire"] = { | |
| { "%s_Standing_Idle", moment = "any" }, | |
| }, | |
| ["gr_Standing_Aim"] = { | |
| { "%s_Standing_Idle", moment = "any" }, | |
| { "%s_Crouch_Idle", moment = "any" }, | |
| }, | |
| ["nw_Bandaging_Start"] = { { "%s_Crouch_Idle" } }, | |
| ["nw_Bandaging_Self_Start"] = { { "%s_Crouch_Idle" } }, | |
| ["nw_Bandaging_Idle"] = { { "%s_Crouch_Idle" } }, | |
| ["nw_Bandaging_Self_Idle"] = { { "%s_Crouch_Idle" } }, | |
| ["nw_Bandaging_End"] = { { "%s_Crouch_Idle" } }, | |
| ["nw_Bandaging_Self_End"] = { { "%s_Crouch_Idle" } }, | |
| ["nw_Standing_MortarIdle"] = { | |
| { "nw_Standing_MortarEnd", moment = "any" }, | |
| }, | |
| ["nw_Standing_MortarFire"] = { | |
| { "nw_Standing_MortarEnd", moment = "any" }, | |
| }, | |
| ["nw_Standing_MortarEnd"] = { | |
| { "%s_Standing_Idle" }, | |
| }, | |
| ["%s_Standing_To_Crouch"] = { { "%s_Crouch_Idle" } }, | |
| ["%s_Standing_To_Prone"] = { { "%s_Prone_Idle" } }, | |
| ["%s_Crouch_To_Standing"] = { { "%s_Standing_Idle" } }, | |
| ["%s_Crouch_To_Prone"] = { { "%s_Prone_Idle" } }, | |
| ["%s_TakeCover_Idle"] = { { "%s_Crouch_Idle", moment = "any" }}, | |
| ["%s_Prone_To_Standing"] = { { "%s_Standing_Idle" } }, | |
| ["%s_Prone_To_Crouch"] = { { "%s_Crouch_Idle" } }, | |
| ["%s_Downed_Standing"] = { { "%s_Standing_Idle" } }, | |
| ["%s_Downed_Crouch"] = { { "%s_Crouch_Idle" } }, | |
| ["%s_Downed_Prone"] = { { "%s_Prone_Idle" } }, | |
| ["%s_Open_Door"] = { | |
| { "%s_Standing_Idle", moment = "any" }, | |
| { "%s_Crouch_Idle", moment = "any" }, | |
| }, | |
| ["%s_CloseDoor"] = { | |
| { "%s_Standing_Idle", moment = "any" }, | |
| { "%s_Crouch_Idle", moment = "any" }, | |
| }, | |
| ["%s_Standing_ExposeLeft_Start"] = { | |
| { "%s_Standing_ExposeLeft_Idle" }, | |
| locked_orientation = true, | |
| }, | |
| ["%s_Standing_ExposeRight_Start"] = { | |
| { "%s_Standing_ExposeRight_Idle" }, | |
| locked_orientation = true, | |
| }, | |
| ["%s_Standing_ExposeLeft_Idle"] = { | |
| { "%s_Standing_ExposeLeft_End", moment = "any" }, | |
| locked_orientation = true, | |
| }, | |
| ["%s_Standing_ExposeRight_Idle"] = { | |
| { "%s_Standing_ExposeRight_End", moment = "any" }, | |
| locked_orientation = true, | |
| }, | |
| ["%s_Standing_ExposeLeft_End"] = { | |
| { "%s_Standing_Idle" }, | |
| locked_orientation = true, | |
| }, | |
| ["%s_Standing_ExposeRight_End"] = { | |
| { "%s_Standing_Idle" }, | |
| locked_orientation = true, | |
| }, | |
| } | |
| function ResolveAnimationTransitions(prefixes) | |
| local result = {} | |
| for anim_format, transitions_format in sorted_pairs(s_TransitionRules) do | |
| for j, prefix in ipairs(prefixes) do | |
| local anim = string.format(anim_format, prefix) | |
| local anim_transitions = result[anim] | |
| if not anim_transitions then | |
| anim_transitions = { | |
| prefix = string.starts_with(anim, prefix) and prefix or string.match(anim, "^(%a+_).*"), | |
| } | |
| result[anim] = anim_transitions | |
| end | |
| if transitions_format.locked_orientation then | |
| anim_transitions.locked_orientation = transitions_format.locked_orientation | |
| end | |
| for k, transition_format in ipairs(transitions_format) do | |
| assert(s_TransitionRules[transition_format[1]]) | |
| local target_anim = string.format(transition_format[1], prefix) | |
| local transition = table.copy(transition_format) | |
| transition[1] = target_anim | |
| local idx = table.find(anim_transitions, 1, target_anim) or #anim_transitions + 1 | |
| anim_transitions[idx] = transition | |
| end | |
| end | |
| end | |
| return result | |
| end | |
| s_AnimTransitions = ResolveAnimationTransitions({ "civ", "ar", "mk", "dw", "hg", "gr", "nw", "hw" }) | |
| local ValidStances = { | |
| Standing = true, | |
| Crouch = true, | |
| Prone = true, | |
| } | |
| function GetAnimPath(obj, start_anim, target_anim) | |
| if start_anim == target_anim then | |
| return | |
| end | |
| local queue = { start_anim, [start_anim] = 0 } | |
| local queue_transitions = { false } | |
| if not s_AnimTransitions[start_anim] then | |
| local prefix = string.match(start_anim, "^(%a+_)") | |
| local stance = prefix and obj.species == "Human" and string.match(start_anim, "^%a+_(%a+)_") | |
| if stance == "KnockDown" then | |
| stance = "Prone" | |
| end | |
| local closest = stance and string.format("%s%s_Idle", prefix, stance) | |
| if closest == target_anim then | |
| return | |
| end | |
| if not s_AnimTransitions[closest] then | |
| if obj.species == "Human" then | |
| if not stance or not ValidStances[stance] then | |
| stance = "Standing" | |
| end | |
| if prefix == "nw_" or prefix == "civ_" then | |
| prefix = string.match(target_anim, "^(%a+_)") or obj:GetWeaponAnimPrefix() | |
| end | |
| closest = string.format("%s%s_Idle", prefix, stance) | |
| if closest == target_anim then | |
| return | |
| end | |
| end | |
| if not s_AnimTransitions[closest] then | |
| return | |
| end | |
| end | |
| queue_transitions[2] = false | |
| queue[2] = closest | |
| queue[closest] = 1 | |
| end | |
| local target_tokens = {} | |
| local target_token_start = 1 | |
| while target_token_start <= #target_anim do | |
| local index = string.find(target_anim, "_", target_token_start) | |
| if not index then | |
| break | |
| end | |
| table.insert(target_tokens, string.sub(target_anim, 1, index)) | |
| target_token_start = index + 1 | |
| end | |
| if #target_tokens == 0 then | |
| return | |
| end | |
| local target_prefix = string.sub(target_tokens[1], 1, #target_tokens[1] - 1) | |
| local variation_index = string.match(target_anim, "()%d+$") | |
| if variation_index then | |
| table.insert(target_tokens, string.sub(target_anim, 1, variation_index - 1)) | |
| else | |
| table.insert(target_tokens, target_anim) | |
| end | |
| local GetAnimDist = function(anim) | |
| if anim == target_anim then | |
| return 0 | |
| end | |
| local dist = #target_tokens + 1 | |
| for i, token in ipairs(target_tokens) do | |
| if not string.starts_with(anim, token) then | |
| break | |
| end | |
| dist = dist - 1 | |
| end | |
| if anim:ends_with("Idle") then | |
| return dist | |
| end | |
| return dist + 1 | |
| end | |
| local idx = #queue | |
| local best_idx = idx | |
| local best_dist = GetAnimDist(queue[idx]) | |
| local best_threshold = s_AnimTransitions[target_anim] and 0 or 1 | |
| while idx <= #queue and best_dist > best_threshold do | |
| local anim = queue[idx] | |
| local dist = GetAnimDist(anim) | |
| if dist - best_dist < 2 then | |
| local anim_data = s_AnimTransitions[anim] | |
| -- invalid animations can be skipped when moment == "any" | |
| local is_valid_anim = IsValidAnim(obj, anim) | |
| for i, transition in ipairs(anim_data) do | |
| local new_anim = transition[1] | |
| local new_anim_data = s_AnimTransitions[new_anim] or empty_table | |
| if not queue[new_anim] | |
| and (is_valid_anim or transition.moment == "any") | |
| and (anim_data.prefix == new_anim_data.prefix or new_anim_data.prefix == target_prefix) | |
| then | |
| queue[new_anim] = idx | |
| local dist = GetAnimDist(new_anim) | |
| if dist - best_dist < 2 then | |
| if not transition.last then | |
| table.insert(queue, new_anim) | |
| table.insert(queue_transitions, transition) | |
| end | |
| if IsValidAnim(obj, new_anim) then | |
| if dist < best_dist then | |
| best_idx, best_dist = #queue, dist | |
| end | |
| if dist == 0 then | |
| break | |
| end | |
| end | |
| end | |
| end | |
| end | |
| end | |
| idx = idx + 1 | |
| end | |
| local path = {} | |
| idx = best_idx | |
| local transition | |
| while idx > 0 do | |
| local anim = queue[idx] | |
| if transition and transition.moment ~= "any" then | |
| table.insert(path, anim) | |
| end | |
| transition = queue_transitions[idx] | |
| idx = queue[anim] | |
| end | |
| if path[1] == obj:GetStateText() and obj:GetAnimPhase() >= GetAnimDuration(obj:GetEntity(), path[1]) - 1 then | |
| table.remove(path, 1) | |
| end | |
| if #path == 0 then | |
| return | |
| end | |
| return path | |
| end | |
| function PlayTransitionAnims(obj, target_anim, angle, aim_pos) | |
| local start_anim = obj:GetStateText() | |
| local path = GetAnimPath(obj, start_anim, target_anim) | |
| if not path and obj.species == "Human" then | |
| local prefix = string.match(target_anim, "^(%a+_)") | |
| if prefix == "inf_" then | |
| local nw_target_anim = "nw" .. string.sub(target_anim, #prefix) | |
| return PlayTransitionAnims(obj, nw_target_anim, angle, aim_pos) | |
| end | |
| if prefix and not string.starts_with(start_anim, prefix) then | |
| local stance = string.match(start_anim, "^%a+_(%a+)_") | |
| if stance then | |
| if stance == "Bandaging" then | |
| stance = "Crouch" | |
| end | |
| local closest = string.format("%s%s_Idle", prefix, stance) | |
| path = GetAnimPath(obj, closest, target_anim) | |
| end | |
| end | |
| end | |
| if not path then | |
| return false | |
| end | |
| local len = #path | |
| for i = len, 1, -1 do | |
| local anim = path[i] | |
| if i < len or obj:GetStateText() ~= anim then | |
| obj:SetState(anim, const.eKeepComponentTargets) | |
| end | |
| if angle and not (s_AnimTransitions[anim] or empty_table).locked_orientation then | |
| local angle_diff = AngleDiff(angle, obj:GetOrientationAngle()) | |
| if angle_diff ~= 0 then | |
| local t = obj:TimeToMoment(1, "OrientationStart") | |
| if (t or 0) > 0 then | |
| Sleep(t) | |
| end | |
| t = obj:TimeToMoment(1, "OrientationEnd") or Min(300, obj:TimeToAnimEnd()) | |
| obj:IdleRotation(angle, t) | |
| end | |
| angle = nil | |
| end | |
| if i == 1 and aim_pos and anim:ends_with("Aim_Start") then | |
| local t = obj:TimeToAnimEnd() - obj:GetAnimDuration() / 2 -- wait middle of the animation | |
| if t > 0 then | |
| Sleep(t) | |
| end | |
| obj:SetIK("AimIK", aim_pos) | |
| Sleep(obj:TimeToAnimEnd()) | |
| end | |
| Sleep(obj:TimeToAnimEnd()) | |
| end | |
| return true | |
| end | |
| local function GetDieAnimSuffix(context, fall_angle) | |
| local unit = context.unit | |
| local diff = AngleDiff(unit:GetVisualAngle(), fall_angle) | |
| if abs(diff) <= 45 * 60 then | |
| return "_F", fall_angle | |
| elseif abs(diff) >= 135 * 60 then | |
| return "_B", fall_angle + 180 * 60 | |
| elseif diff < 0 then | |
| return "_R", fall_angle - 90*60 | |
| end | |
| return "_L", fall_angle + 90*60 | |
| end | |
| local function CheckNearbyPass(pos, except_dir) | |
| local offset = const.SlabSizeX / 2 | |
| for y = -1, 1 do | |
| for x = -1, 1 do | |
| local p = point(x * offset, y * offset, 0) | |
| if not except_dir or abs(AngleDiff(CalcOrientation(p), except_dir)) >= 90*60 then | |
| if not terrain.IsPassable(pos + p) then | |
| return false | |
| end | |
| end | |
| end | |
| end | |
| return true | |
| end | |
| -- TODO: here we can incorporate transitions to another anims | |
| local s_ConditionsGraph = { | |
| { | |
| Name = "Death_Sitting", | |
| Condition = function(context) | |
| if context.hit_descr and context.hit_descr.die_pos then | |
| local visit = context.unit and context.unit.last_visit | |
| return IsValid(visit) and IsKindOf(visit, "AL_SitChair") | |
| end | |
| end, | |
| Animation = function(context) | |
| local dir_angle = CardinalDirection(context.angle) | |
| local _, angle = GetDieAnimSuffix(context, dir_angle) | |
| return "civ_DeathChair", true, context.hit_descr.die_pos, angle | |
| end, | |
| }, | |
| { | |
| Name = "Death_Wall_NextSlab", | |
| Condition = function(context) | |
| if not context.close_shot_dist or context.move_slabs ~= 0 then | |
| return false | |
| end | |
| if GetAngleCover(context.pos, CardinalDirection(context.angle)) ~= const.CoverHigh then | |
| return false -- no cover | |
| end | |
| return true | |
| end, | |
| Animation = function(context) | |
| local dir_angle = CardinalDirection(context.angle) | |
| local suffix, angle = GetDieAnimSuffix(context, dir_angle) | |
| local variations = CheckNearbyPass(context.pos, dir_angle) | |
| return "civ_DeathWall" .. suffix, variations, context.pos, angle | |
| end, | |
| }, | |
| { | |
| Name = "Death_Wall_OverNextSlab", | |
| Condition = function(context) | |
| if not context.close_shot_dist or not context.move_pos1 then | |
| return false | |
| end | |
| local dx = context.move_pos1:x() - context.pos:x() | |
| local dy = context.move_pos1:y() - context.pos:y() | |
| if dx * dy ~= 0 then | |
| return false -- diagonal move | |
| end | |
| if GetAngleCover(context.move_pos1, CardinalDirection(context.angle)) ~= const.CoverHigh then | |
| return false -- no cover | |
| end | |
| return true | |
| end, | |
| Animation = function(context) | |
| local dir_angle = CardinalDirection(context.angle) | |
| local suffix, angle = GetDieAnimSuffix(context, dir_angle) | |
| local variations = CheckNearbyPass(context.pos, dir_angle) | |
| return "civ_DeathWall_x1" .. suffix, variations, context.move_pos1, angle | |
| end, | |
| }, | |
| { | |
| Name = "Death_Fall", | |
| Condition = function(context) | |
| if not context.close_shot_dist or context.move_slabs ~= 0 then | |
| return false | |
| end | |
| local tunnel = GetTunnelDir(context.pos, CardinalDirection(context.angle), const.TunnelMaskDrop) | |
| if not tunnel then | |
| return false | |
| end | |
| return true | |
| end, | |
| Animation = function(context) | |
| local tunnel = GetTunnelDir(context.pos, CardinalDirection(context.angle), const.TunnelMaskDrop) | |
| if not tunnel then | |
| return | |
| end | |
| local suffix, angle = GetDieAnimSuffix(context, CardinalDirection(context.angle)) | |
| local anim = string.format("civ_DeathFall_x%d%s", tunnel.tiles, suffix) | |
| return anim, true, tunnel:GetExit(), angle | |
| end, | |
| }, | |
| { | |
| Name = "Death_Window", | |
| Condition = function(context) | |
| if not context.close_shot_dist or context.move_slabs ~= 0 then | |
| return false | |
| end | |
| if (context.hit_descr.impact_force or 0) < 1 then | |
| return false | |
| end | |
| local tunnel = GetTunnelDir(context.pos, CardinalDirection(context.angle), const.TunnelTypeWindow | const.TunnelTypeJumpOver1) | |
| if not tunnel then | |
| return false | |
| end | |
| local move_pos = tunnel:GetExit() | |
| local next_pos = GetPassSlab(RotateRadius(const.SlabSizeX, context.angle, move_pos)) | |
| if not next_pos or not IsPassSlabStep(move_pos, next_pos, const.TunnelTypeWalk) then | |
| return false -- annimations enter the next tile | |
| end | |
| return true | |
| end, | |
| Animation = function(context) | |
| local tunnel = GetTunnelDir(context.pos, CardinalDirection(context.angle), const.TunnelTypeWindow | const.TunnelTypeJumpOver1) | |
| if not tunnel then | |
| return | |
| end | |
| local suffix, angle = GetDieAnimSuffix(context, CardinalDirection(context.angle)) | |
| local anim = "civ_DeathWindow" .. suffix | |
| local break_obj | |
| if tunnel.tunnel_type & const.TunnelTypeWindow ~= 0 then | |
| local obj = tunnel.pass_through_obj | |
| if obj.pass_through_state == "intact" then | |
| break_obj = obj | |
| end | |
| end | |
| return anim, true, tunnel:GetExit(), angle, break_obj | |
| end, | |
| }, | |
| { | |
| Name = "Death_Railing", | |
| Condition = function(context) | |
| if not context.close_shot_dist or context.move_slabs ~= 0 then | |
| return false | |
| end | |
| if (context.hit_descr.impact_force or 0) < 1 then | |
| return false | |
| end | |
| local tunnel_mask = const.TunnelTypeWindow | const.TunnelTypeJumpOver1 | const.TunnelTypeJumpOver2 | |
| local tunnel = GetTunnelDir(context.pos, CardinalDirection(context.angle), tunnel_mask) | |
| if not tunnel then | |
| return false | |
| end | |
| return true | |
| end, | |
| Animation = function(context) | |
| local angle = CardinalDirection(context.angle) | |
| local tunnel_mask = const.TunnelTypeWindow | const.TunnelTypeJumpOver1 | const.TunnelTypeJumpOver2 | |
| local tunnel = GetTunnelDir(context.pos, angle, tunnel_mask) | |
| if not tunnel then | |
| return | |
| end | |
| local suffix, angle = GetDieAnimSuffix(context, angle) | |
| local anim = "civ_DeathRailing" .. suffix | |
| return anim, true, context.pos, angle | |
| end, | |
| }, | |
| { | |
| Name = "Death_Over2Slabs", | |
| Condition = function(context) | |
| if not context.close_shot_dist or not context.move_pos3 then | |
| return false | |
| end | |
| if (context.hit_descr.impact_force or 0) < 3 then | |
| return false | |
| end | |
| return true | |
| end, | |
| Animation = function(context) | |
| local suffix, angle = GetDieAnimSuffix(context, context.angle) | |
| return "civ_DeathSlide" .. suffix, true, context.move_pos3, angle | |
| end, | |
| }, | |
| { | |
| Name = "Death_OverNextSlab", | |
| Condition = function(context) | |
| -- the head is in the 3th tile | |
| if not context.close_shot_dist or context.move_slabs < 3 then | |
| return false | |
| end | |
| if (context.hit_descr.impact_force or 0) < 2 then | |
| return false | |
| end | |
| return true | |
| end, | |
| Animation = function(context) | |
| local suffix, angle = GetDieAnimSuffix(context, context.angle) | |
| return "civ_DeathBlow" .. suffix, true, context.move_pos2, angle | |
| end, | |
| }, | |
| { | |
| Name = "Death_NextSlab", | |
| Condition = function(context) | |
| -- the head is in the 2th tile | |
| if context.move_slabs < 2 then | |
| return false | |
| end | |
| if (context.hit_descr.impact_force or 0) < 1 then | |
| return false | |
| end | |
| return true | |
| end, | |
| Animation = function(context) | |
| local suffix, angle = GetDieAnimSuffix(context, context.angle) | |
| local anim = "civ_Death" .. suffix | |
| local pos = context.move_pos1 | |
| if not GetPassSlab(pos) then | |
| pos = point( | |
| Clamp(pos:x(), context.move_pos1:x() - const.SlabSizeX / 2, context.move_pos1:x() + const.SlabSizeX / 2 - 1), | |
| Clamp(pos:y(), context.move_pos1:y() - const.SlabSizeY / 2, context.move_pos1:y() + const.SlabSizeY / 2 - 1), | |
| context.move_pos1:z()) | |
| end | |
| return "civ_Death" .. suffix, true, pos, angle | |
| end, | |
| }, | |
| { | |
| Name = "Death_OnPlace", | |
| Condition = function(context) | |
| return true | |
| end, | |
| Animation = function(context) | |
| local suffix, angle = GetDieAnimSuffix(context, context.angle) | |
| return "civ_DeathOnSpot" .. suffix, true, context.pos, angle | |
| end, | |
| }, | |
| } | |
| function GetConditionGraphAnim(context) | |
| if not context.hit_descr then | |
| context.hit_descr = empty_table | |
| end | |
| if context.hit_descr.death_blow then | |
| local simple = table.find_value(s_ConditionsGraph, "Name", "Death_OverNextSlab") | |
| local anim, variations, pos, angle, param = simple.Animation(context) | |
| if anim and IsValidAnim(context.unit, anim) then | |
| return anim, variations, pos, angle, param | |
| end | |
| end | |
| if context.unit.ImportantNPC then | |
| local simple = table.find_value(s_ConditionsGraph, "Name", "Death_OnPlace") | |
| local anim, variations, pos, angle, param = simple.Animation(context) | |
| if anim and IsValidAnim(context.unit, anim) then | |
| return anim, variations, pos, angle, param | |
| end | |
| end | |
| for _, node in ipairs(s_ConditionsGraph) do | |
| if string.match(node.Name, context.pattern) and (context.skip_condition or node.Condition(context)) then | |
| local anim, variations, pos, angle, param = node.Animation(context) | |
| if anim and IsValidAnim(context.unit, anim) then | |
| return anim, variations, pos, angle, param | |
| end | |
| end | |
| end | |
| end | |
| function GetDeathBaseAnim(unit, context) | |
| local hit_descr = context and context.hit_descr | |
| local variations | |
| if unit.species == "Hyena" and hit_descr then | |
| if (hit_descr.death_explosion or (hit_descr.prev_hit_points or Max(0, unit.HitPoints)) - (hit_descr.raw_damage or 0) <= -20) then | |
| variations = { "death2", "death3" } | |
| end | |
| end | |
| if variations then | |
| return variations[1], variations | |
| end | |
| if unit.species ~= "Human" or unit.stance == "Prone" then | |
| local base_anim = unit:TryGetActionAnim("Death", unit.stance) | |
| return base_anim, true | |
| end | |
| context = context or {} | |
| context.unit = context.unit or unit | |
| if not context.pos then | |
| context.pos = context.hit_descr and context.hit_descr.die_pos | |
| if not context.pos then | |
| local x, y, z = FindFallDownPos(unit) | |
| context.pos = x and point(x, y, z) or GetPassSlab(unit) or unit:GetPos() | |
| end | |
| end | |
| if not context.angle then | |
| if context.target_pos then | |
| context.angle = CalcOrientation(unit, context.target_pos) | |
| elseif context.attacker then | |
| context.angle = CalcOrientation(context.attacker, unit) | |
| else | |
| context.angle = unit:GetOrientationAngle() + 180 * 60 | |
| end | |
| end | |
| local move_slabs = 0 | |
| local move_pos = context.pos | |
| while move_slabs < 4 do | |
| local next_pos = GetPassSlab(RotateRadius((move_slabs + 1) * const.SlabSizeX, context.angle, context.pos)) | |
| if not next_pos or not IsPassSlabStep(move_pos, next_pos, const.TunnelTypeWalk) then | |
| break | |
| end | |
| move_slabs = move_slabs + 1 | |
| move_pos = next_pos | |
| context["move_pos" .. move_slabs] = next_pos | |
| end | |
| context.move_slabs = move_slabs | |
| if context.close_shot_dist == nil and context.attacker then | |
| if not IsKindOf(context.hit_descr.weapon, "MeleeWeapon") then | |
| if IsCloser(unit, context.attacker, 5 * const.SlabSizeX) then | |
| context.close_shot_dist = true | |
| end | |
| end | |
| end | |
| if not context.pattern then | |
| context.pattern = "^Death_" | |
| end | |
| return GetConditionGraphAnim(context) | |
| end | |
| function GetRandomDeathAnim(unit, context) | |
| local base_anim, variations, pos, angle, param = GetDeathBaseAnim(unit, context) | |
| local anim | |
| if type(variations) == "table" then | |
| anim = variations[1 + (#variations > 1 and unit:Random(#variations) or 0)] | |
| elseif base_anim and variations ~= false then | |
| anim = unit:GetNearbyUniqueRandomAnim(base_anim) | |
| end | |
| if not anim then | |
| anim = base_anim or "death" | |
| end | |
| return anim, pos, angle, param | |
| end | |
| function TestDeathAnim(unit, pos, angle, context, variant) | |
| unit:SetPos(pos or context.pos or GetPassSlab(unit) or unit:GetPos()) | |
| unit:SetAxis(axis_z) | |
| unit:SetAngle(angle or unit:GetAngle()) | |
| unit:SetState(unit:GetIdleBaseAnim("Standing"), 0, 0) | |
| local base_anim, variations, pos, angle, param = GetDeathBaseAnim(unit, context) | |
| if not base_anim then | |
| print("No death anim") | |
| return | |
| end | |
| if not IsValidAnim(unit, base_anim) then | |
| printf("Invalid death animation: %s", base_anim) | |
| return | |
| end | |
| local anim | |
| if type(variations) == "table" then | |
| anim = variations[variant] | |
| elseif variant and variant > 1 then | |
| anim = base_anim .. variant | |
| else | |
| anim = base_anim | |
| end | |
| if not IsValidAnim(unit, anim) then | |
| printf("Invalid death animation: %s", anim) | |
| return | |
| end | |
| printf("Death animation: %s, angle = %d", anim, angle/60) | |
| unit:SetCommand("PlayDying", false, false, anim, pos, angle, param) | |
| end | |