Grabs And Throws Examples¶
Grab scripts are state machines shared by two characters: the attacker and the victim. The attacker usually owns the script, but the script must keep the victim aligned, vulnerable or invulnerable, animated, and released at the correct time.
The examples below use neutral animation IDs and behaviour modes. Replace them with the IDs used by your character logic.
Important Grab Rules¶
character.victimresolvesattacked_character_id. It becomesnilif no valid victim is connected.front_grab_offsetis read from the attacker's logic data. It is the stable anchor for holding the victim in front of the attacker.position_offset_ycontrols height. A grabbed airborne victim usually has a negative offset.mode_specific_timeris used by several original grab behaviours as a release timer. Holding it high prevents unwanted automatic release.try_grab(target, check_vicinity, enforcement, snap_to_victim)is useful when a script needs to convert a hit or branch back into a grab.cancel_grab()clears the grab relationship before a throw release or recovery.
Force A Back Grab Into A Front Grab¶
Attach this to a back-grab hold or back-grab transition. When the animation ends, it turns the victim around and enters the normal front-grab hold.
return function()
local player = game.this_player
if player == nil then
return true
end
player.is_vulnerable = true
if player.is_animation_over and player.victim ~= nil then
-- Face the victim toward the attacker. Front-grab logic expects this.
player.victim.is_facing_right = not player.is_facing_right
-- Animation 4 is the example front-grab hold.
-- Object mode 26 is the example front-grab behaviour mode.
player.set_animation(4)
player.object_mode = 26
-- We made a decisive state change. Stop the previous grab controller.
return false
end
return true
end
Grab Hit Repositioning¶
Use this when a grab attack has an impact section and then returns to a hold. The victim offset changes during the hit frames, then snaps back to the carry offset.
local grabbed_offset_x = 1040
local grabbed_offset_y = -1800
local hit_offset_x = 100
local hit_offset_y = -1500
return function()
local player = game.this_player
if player == nil then
return true
end
player.is_vulnerable = true
player.object_mode = 22
local victim = player.victim
if victim == nil then
return true
end
victim.is_vulnerable = true
if player.is_animation_over then
-- Return to the hold animation at the frame where the victim is already
-- aligned. This avoids replaying the initial snap frames.
player.set_animation(4, 9)
end
if player.current_animation_frame < 6 then
victim.position_offset_y = hit_offset_y
victim.position_offset_x = hit_offset_x
else
victim.position_offset_y = grabbed_offset_y
victim.position_offset_x = grabbed_offset_x
end
if not player.is_facing_right then
victim.position_offset_x = -victim.position_offset_x
end
return true
end
Walk While Holding A Victim¶
This is the core grab-hold controller: keep the victim attached, let the attacker walk, and branch into throws or jumps.
local grabbed_offset_x = 1040
local grabbed_offset_y = -1800
local grab_walk_speed_x = 80
local grab_walk_speed_y = 50
local prepare_jump_offset_x = 800
local prepare_jump_offset_y = -1000
local function get_extra_bob_offset(player)
local frame = player.current_animation_frame % 35
-- Small vertical offsets make the victim follow the walk cycle.
-- The values are in script units, not sprite pixels.
if frame < 3 then
return 4
elseif (frame >= 3 and frame < 6) or frame >= 33 then
return 3
elseif (frame >= 6 and frame < 9) or
(frame >= 18 and frame < 27) or
frame >= 30 then
return 2
end
return 0
end
return function()
local player = game.this_player
if player == nil then
return true
end
player.object_mode = 26
player.is_vulnerable = true
local victim = player.victim
if victim == nil then
return true
end
victim.is_vulnerable = true
-- Hold the original release timer high so this script decides when the grab
-- ends. Without this, inherited grab logic can release the victim early.
player.mode_specific_timer = 99
if player.pressed_button_b then
if player.is_holding_backward or player.is_holding_forward then
-- Direction plus attack starts the example slam.
victim.is_facing_right = player.is_facing_right
player.set_animation(5)
player.object_mode = 28
return false
elseif victim.is_in_the_air then
-- While the victim is airborne, choose one of two follow-up strikes.
if player.grab_combo_count < 4 then
player.set_animation(33)
else
player.set_animation(34)
end
return false
end
elseif victim.is_in_the_air then
if player.pressed_button_c then
-- Jump while carrying the victim. The victim is pre-positioned before
-- the jump animation starts so the first airborne frame is stable.
player.set_animation(3)
player.impulse_x = 0
victim.position_offset_y = prepare_jump_offset_y
victim.position_offset_x = prepare_jump_offset_x
if not player.is_facing_right then
victim.position_offset_x = -victim.position_offset_x
end
return false
elseif player.is_holding_backward then
-- Rewind the hold cycle for a backward tug.
player.set_animation(44)
player.current_animation_frame = player.current_animation_frame - 2
if player.current_animation_frame < 0 then
player.current_animation_frame = 34
player.has_just_teleported = true
end
elseif player.is_holding_forward or player.is_holding_up or player.is_holding_down then
player.set_animation(44)
else
player.set_animation(4, 9)
end
end
if player.current_animation_id == 4 then
if player.current_animation_frame < 9 and victim.is_in_the_air then
-- Skip startup frames when re-entering the hold from an airborne branch.
player.current_animation_frame = 9
player.has_just_teleported = true
elseif player.current_animation_frame >= 9 then
victim.position_offset_y = grabbed_offset_y
victim.position_offset_x = grabbed_offset_x
if not player.is_facing_right then
victim.position_offset_x = -victim.position_offset_x
end
end
elseif player.current_animation_id == 44 then
if player.is_holding_right then
player.velocity_x = grab_walk_speed_x
elseif player.is_holding_left then
player.velocity_x = -grab_walk_speed_x
end
if player.is_holding_up then
player.velocity_y = -grab_walk_speed_y
elseif player.is_holding_down then
player.velocity_y = grab_walk_speed_y
end
local grab_offset = player.front_grab_offset
if not player.is_facing_right then
grab_offset = -grab_offset
end
victim.position_x = player.position_x + grab_offset
victim.position_y = player.position_y + 1 + player.velocity_y
victim.position_offset_y =
player.position_offset_y + grabbed_offset_y +
get_extra_bob_offset(player) * -50
end
-- Returning whether the victim is airborne lets the reference path continue
-- only in the case this grab state was designed around.
return victim.is_in_the_air
end
Air Grab Lift¶
This carries a victim upward, lets the player steer before the lift begins, and keeps the victim pinned to the attacker until landing.
local grabbed_offset_x = 1040
local grabbed_offset_y = -1800
local vertical_impulse = -340
local horizontal_impulse = 131
local prepare_jump_offset_x = 800
local prepare_jump_offset_y = -1000
return function()
local player = game.this_player
if player == nil then
return true
end
player.is_vulnerable = true
if player.check_being_hit() then
return false
end
if player.current_animation_frame < 4 then
-- Allow tiny horizontal aiming before the lift begins.
if player.is_holding_left then
player.impulse_x = -horizontal_impulse
elseif player.is_holding_right then
player.impulse_x = horizontal_impulse
end
return false
elseif player.current_animation_frame == 4 then
player.impulse_y = vertical_impulse
end
player.position_offset_y = player.position_offset_y + player.impulse_y
player.impulse_y = player.impulse_y + 16
player.velocity_x = player.impulse_x
local victim = player.victim
if player.impulse_y > 0 and player.position_offset_y >= 0 then
-- Land back into the hold and prepare the victim for the next branch.
player.set_animation(47)
player.impulse_y = 0
player.position_offset_y = 0
if victim ~= nil then
victim.position_offset_y = prepare_jump_offset_y
victim.position_offset_x = prepare_jump_offset_x
if not player.is_facing_right then
victim.position_offset_x = -victim.position_offset_x
end
end
return false
end
if victim == nil then
return false
end
victim.is_vulnerable = true
victim.position_offset_x = grabbed_offset_x
local grab_offset = player.front_grab_offset
if not player.is_facing_right then
victim.position_offset_x = -victim.position_offset_x
grab_offset = -grab_offset
end
victim.position_x = player.position_x + grab_offset
victim.position_y = player.position_y + 1 + player.velocity_y
victim.position_offset_y = player.position_offset_y + grabbed_offset_y
return false
end
Piledriver-Style Throw¶
This is a full manual throw controller. It disables victim vulnerability while carried, applies throw damage on impact, cancels the grab, and puts the attacker into a rebound animation.
local ground_level = 1000
local function get_victim_offset(player)
local frame = player.current_animation_frame
if frame < 4 then
return -150, -1800
elseif frame < 10 then
return -500, -1300
end
return -1100, -1000
end
return function()
local player = game.this_player
if player == nil then
return true
end
player.position_offset_y = player.position_offset_y + player.impulse_y
player.impulse_y = player.impulse_y + 16
player.velocity_x = player.impulse_x
if player.current_animation_id == 50 then
-- Rebound/recovery animation after impact.
if player.impulse_y > 0 and player.position_offset_y >= 0 then
player.impulse_x = 0
player.impulse_y = 0
player.position_offset_y = 0
player.object_mode = 2
player.is_vulnerable = true
player.set_animation(15)
end
return false
end
player.is_vulnerable = false
if player.current_animation_frame == 0 then
player.play_sound("My Pack", "throw-start", 2)
end
local victim = player.victim
if player.impulse_y > 0 and player.position_offset_y >= ground_level then
player.cancel_grab()
-- Bounce the attacker away from the impact.
player.impulse_x = 100
if player.is_facing_right then
player.impulse_x = -player.impulse_x
end
player.impulse_y = -200
player.position_offset_y = -10
player.set_animation(50)
player.object_mode = -1
if victim ~= nil then
victim.position_offset_y = -10
-- Preserve dying mode if the victim was already in it.
local victim_was_dying = victim.object_mode == 6
-- apply_throw releases the victim with throw damage and vertical force.
victim.apply_throw(-2, -220, 0)
victim.is_being_thrown = false
victim.set_animation(20, 20)
if victim_was_dying then
victim.object_mode = 6
end
end
return false
end
if victim == nil then
return false
end
victim.is_vulnerable = false
victim.is_being_thrown = true
victim.is_facing_right = player.is_facing_right
victim.set_animation(18)
victim.damage_taken = 56
if victim.current_animation_frame < 16 then
victim.current_animation_frame = 16
end
local offset_x, offset_y = get_victim_offset(player)
victim.position_offset_x = offset_x
local grab_offset = player.front_grab_offset
if not player.is_facing_right then
victim.position_offset_x = -victim.position_offset_x
grab_offset = -grab_offset
end
victim.position_x = player.position_x + grab_offset
victim.position_y = player.position_y + 1 + player.velocity_y
victim.position_offset_y = player.position_offset_y + offset_y
return false
end
Keep A Victim Airborne At Combo End¶
Use this at the end of a grab combo when you want a launcher route. The script keeps the victim above the ground and lets their default physics apply the new impulse.
return function()
local player = game.this_player
if player == nil then
return true
end
local victim = player.victim
if victim ~= nil and victim.impulse_y > -300 then
victim.impulse_y = -300
-- Apply the victim's normal physics immediately so the new impulse takes
-- effect during this frame instead of waiting for their next update.
victim.apply_default_physics()
return false
end
if player.current_animation_frame == 0 and
player.is_holding_down and
victim ~= nil and
player.try_grab(victim) then
-- Convert the combo end back into a throw if down is held.
victim.is_facing_right = player.is_facing_right
player.set_animation(5)
player.object_mode = 28
return false
end
player.combo_count = 0
player.extra_delay_after_move = 8
return true
end