openclonk/planet/Objects.ocd/Clonk.ocd/Animations.ocd/Script.c

1540 lines
42 KiB
C

/*--
Animation effects of the clonk
Authors: Randrian
Is responsible for the management of all the animations of the clonk. It containes:
* Turn
* Animation Manager
* Eyes
* Walk
* Scale
* Jump
* Hangle
* Swim
* Roll and kneel
* Digging
* Throwing
* Dead
* Tumble
* Riding
* Pushing
* HangOnto
--*/
/* Predefined animation slots */
// Any kind of movement, either legs only (like Walk or Stand) or whole body (like Swim).
// In cases of whole body animation, make sure to let other effect know that the clonk
// can do arms animation (e.g. check ReadyToAction in the Clonk's script).
static const CLONK_ANIM_SLOT_Movement = 5;
// Arms or upper body animations (like Aiming or Throwing)
static const CLONK_ANIM_SLOT_Arms = 10;
// Eye animations (like Blinking or Closing)
static const CLONK_ANIM_SLOT_Eyes = 3;
// Hand animations (like closing the hand)
static const CLONK_ANIM_SLOT_Hands = 6;
// Make sure to never use a higher slot than this, so Death will always overwrite all other animations.
static const CLONK_ANIM_SLOT_Death = 20;
local lAnim; // proplist containing all the specific variables. "Pseudo-Namespace"
func Construction()
{
lAnim =
{
turnType = nil,
turnSpecial = nil,
turnForced = nil,
backwards = nil,
backwardsSpeed = nil,
closedEyes = nil,
rollDir = nil,
rollLength = nil
};
_inherited(...);
}
/*--
Turn
Turn behavoir of the clonk. There are two turn types. Type 0 always rotated the clonk 25° to the screen. Type 1 views the clonk always from the side.
The clonk turns around, when he changes dir.
--*/
// todo, implement, make carryheavy decreasing it
static const Clonk_TurnTime = 18;
func SetMeshTransformation() { return _inherited(...); }
func UpdateAttach() { return _inherited(...); }
func GetHandAction() { return _inherited(...); }
func DoThrow() { return _inherited(...); }
local ActMap;
func SetTurnForced(int dir)
{
lAnim.turnForced = dir+1;
}
func FxIntTurnStart(pTarget, effect, fTmp)
{
if(fTmp) return;
effect.dir = GetDirection();
var iTurnPos = 0;
if(effect.dir == COMD_Right) iTurnPos = 1;
effect.curr_rot = 24;
effect.rot = 25;
effect.turn_type = -1;
SetTurnType(0);
}
func FxIntTurnTimer(pTarget, effect, iTime)
{
// Check wether the clonk wants to turn (Not when he wants to stop)
var iRot = effect.rot;
if( (effect.dir != GetDirection() && (GetAction() != "Jump") || this->~IsAiming()) || effect.turn_type != lAnim.turnType)
{
effect.dir = GetDirection();
if(effect.dir == COMD_Right)
{
if(lAnim.turnType == 0)
iRot = 180-25;
if(lAnim.turnType == 1)
iRot = 180;
}
else
{
if(lAnim.turnType == 0)
iRot = 25;
if(lAnim.turnType == 1)
iRot = 0;
}
// Save new ComDir
effect.dir = GetDirection();
effect.turn_type = lAnim.turnType;
// Notify effects
// ResetAnimationEffects();
}
if(iRot != effect.curr_rot)
{
effect.curr_rot += BoundBy(iRot-effect.curr_rot, -18, 18);
SetMeshTransformation(Trans_Rotate(effect.curr_rot, 0, 1, 0), 0);
}
effect.rot = iRot;
return;
}
public func UpdateTurnRotation()
{
var iEff = GetEffect("IntTurn", this);
iEff.turn_type = -1;
}
public func GetTurnPhase()
{
var iEff = GetEffect("IntTurn", this);
var iRot = iEff.curr_rot;
if(lAnim.turnType == 0)
return (iRot-25)*100/130;
if(lAnim.turnType == 1)
return iRot*100/180;
}
func SetTurnType(iIndex, iSpecial)
{
if(iSpecial != nil && iSpecial != 0)
{
if(iSpecial == 1) // Start a turn that is forced to the clonk and overwrites the normal action's turntype
lAnim.turnSpecial = 1;
if(iSpecial == -1) // Reset special turn (here the iIndex is ignored)
{
lAnim.turnSpecial = 0;
SetTurnType(lAnim.turnType);
return;
}
}
else
{
// Standart turn? Save and do nothing if we are blocked
lAnim.turnType = iIndex;
if(lAnim.turnSpecial) return;
}
return;
}
func GetDirection()
{
// Are we forced to a special direction?
if(lAnim.turnForced)
{
if(lAnim.turnForced == 1) return COMD_Left;
if(lAnim.turnForced == 2) return COMD_Right;
}
// Get direction from ComDir
if(GetAction() != "Scale")
{
if(ComDirLike(GetComDir(), COMD_Right)) return COMD_Right;
else if(ComDirLike(GetComDir(), COMD_Left)) return COMD_Left;
}
// if ComDir hasn't a direction, use GetDir
if(GetDir()==DIR_Right) return COMD_Right;
else return COMD_Left;
}
/*--
Animation Manager
Animations can be replaced. E.g. while aiming with the bow the normal walk animation gets replaced by a bow walk animation.
--*/
local PropAnimations;
local ActualReplace;
public func ReplaceAction(string action, byaction)
{
if(PropAnimations == nil) PropAnimations = CreatePropList();
if(byaction == nil || byaction == 0)
{
SetProperty(action, nil, PropAnimations);
ResetAnimationEffects();
return true;
}
/* if(GetAnimationLength(byaction) == nil)
{
Log("ERROR: No animation %s in Definition %s", byaction, GetID()->GetName());
return false;
}*/
if(GetType(byaction) == C4V_Array)
{
var old = GetProperty(action, PropAnimations);
SetProperty(action, byaction, PropAnimations);
if(GetType(old) == C4V_Array)
{
if(ActualReplace == nil) return true;
if(old[0] == byaction[0] && old[1] == byaction[1])
{
var i = 0;
for (test in ActualReplace)
{
if(test && test[0] == action)
break;
i++;
}
if(i < GetLength(ActualReplace))
SetAnimationWeight(ActualReplace[i][1], Anim_Const(byaction[2]));
return true;
}
}
}
else SetProperty(action, byaction, PropAnimations);
// if(ActualReplace != nil)
// SetAnimationWeight(ActualReplace, Anim_Const(byaction[2]));
ResetAnimationEffects();
return true;
}
public func ResetAnimationEffects()
{
if(GetEffect("IntWalk", this))
EffectCall(this, GetEffect("IntWalk", this), "Reset");
if(GetAction() == "Jump")
StartJump();
}
public func PlayAnimation(string animation, int index, array position, array weight, int sibling)
{
if(!ActualReplace) ActualReplace = [];
ActualReplace[index] = nil;
if(PropAnimations != nil)
if(GetProperty(animation, PropAnimations) != nil)
{
var replacement = GetProperty(animation, PropAnimations);
if(GetType(replacement) == C4V_Array)
{
var animation1 = inherited(replacement[0], index, position, weight);
var animation2 = inherited(replacement[1], index, position, Anim_Const(500), animation1);
var animationKnot = animation2 + 1;
ActualReplace[index] = [animation, animationKnot];
SetAnimationWeight(animationKnot, Anim_Const(replacement[2]));
return animation1;
}
else
animation = GetProperty(animation, PropAnimations);
}
return inherited(animation, index, position, weight, sibling, ...);
}
public func GetAnimationLength(string animation)
{
var replacement;
if(PropAnimations != nil)
if(replacement = GetProperty(animation, PropAnimations))
{
if(GetType(replacement) == C4V_Array)
animation = replacement[0];
else
animation = replacement;
}
return inherited(animation, ...);
}
/*--
Eyes
The clonk automatically closed his eyes every now and then. With CloseEyes(number) the eyes can be opened and shut. If the counter is > 0 the eyes are shut if not they are open. This is also used e.g. by the tumbling animation. While tumbling the clonk's eyes are shut.
--*/
func FxIntEyesTimer(target, effect, time)
{
if(!Random(4))
AddEffect("IntEyesClosed", this, 10, 6, this);
}
func FxIntEyesClosedStart(target, effect, tmp)
{
CloseEyes(1);
}
func FxIntEyesClosedStop(target, effect, reason, tmp)
{
CloseEyes(-1);
}
func CloseEyes(iCounter)
{
lAnim.closedEyes += iCounter;
if(lAnim.closedEyes >= 1)
PlayAnimation("CloseEyes" , CLONK_ANIM_SLOT_Eyes, Anim_Linear(0, 0, GetAnimationLength("CloseEyes")/2, 3, ANIM_Hold));
else
PlayAnimation("CloseEyes" , CLONK_ANIM_SLOT_Eyes, Anim_Linear(GetAnimationLength("CloseEyes")/2, GetAnimationLength("CloseEyes")/2, GetAnimationLength("CloseEyes"), 3, ANIM_Remove));
}
/*--
Walk
The Clonk adjusts his animation acording to his velocity: Stand, Walk and Run. It he is in a building he uses "Inside" insead of "Stand". For walking while aiming in the different direction a speed for walking bachwards can be set.
While standing and doing nothing the clonk starts an idle animation every now and then.
--*/
/* Walking backwards */
func SetBackwardsSpeed(int value)
{
lAnim.backwardsSpeed = value;
UpdateBackwardsSpeed();
}
func UpdateBackwardsSpeed()
{
if(GetComDir() != GetDirection() && lAnim.backwards != 1 && lAnim.backwardsSpeed != nil)
{
AddEffect("IntWalkBack", this, 1, 0, this, nil, lAnim.backwardsSpeed);
lAnim.backwards = 1;
}
if( (GetComDir() == GetDirection() && lAnim.backwards == 1) || lAnim.backwardsSpeed == nil)
{
RemoveEffect("IntWalkBack", this);
lAnim.backwards = nil;
}
}
func FxIntWalkBackStart(pTarget, effect, fTmp, iValue)
{
if(iValue == nil) iValue = 84;
pTarget->PushActionSpeed("Walk", iValue);
}
func FxIntWalkBackStop(pTarget, effect)
{
pTarget->PopActionSpeed("Walk");
}
/* Walk */
static const Clonk_WalkInside = "Inside";
static const Clonk_WalkStand = "Stand";
static const Clonk_WalkWalk = "Walk";
static const Clonk_WalkRun = "Run";
static Clonk_IdleActions;
func StartWalk()
{
if(Clonk_IdleActions == nil)
Clonk_IdleActions = [["IdleLookAround", 60], ["IdleHandwatch", 100], ["IdleScratch", 70], ["IdleStrech", 100], ["IdleShoe", 120], ["IdleShoeSole", 200], ["IdleHandstrech", 100]];
if(!GetEffect("IntWalk", this))
AddEffect("IntWalk", this, 1, 1, this);
}
func StopWalk()
{
if(GetAction() != "Walk") RemoveEffect("IntWalk", this);
}
func GetCurrentWalkAnimation()
{
if(Contained())
{
if(Contained()->GetCategory() & C4D_Structure)
{
return Clonk_WalkInside;
}
return;
}
else SetProperty("PictureTransformation", Trans_Mul(Trans_Translate(0,1000,5000), Trans_Rotate(70,0,1,0)), this);
var velocity = Distance(0,0,GetXDir(),GetYDir());
if(velocity < 1) return Clonk_WalkStand;
if(velocity < 10) return Clonk_WalkWalk;
return Clonk_WalkRun;
}
func Footstep()
{
if (GetMaterialVal("DigFree", "Material", GetMaterial(0,10)) == 0)
Sound("Clonk::Movement::StepHard?");
else
{
var dir = Sign(GetXDir());
var clr = GetAverageTextureColor(GetTexture(0,10));
var particles =
{
Prototype = Particles_Dust(),
R = (clr >> 16) & 0xff,
G = (clr >> 8) & 0xff,
B = clr & 0xff,
};
CreateParticle("Dust", PV_Random(dir * -2, dir * -1), 8, PV_Random(dir * 2, dir * 1), PV_Random(-2, -3), PV_Random(36, 2 * 36), particles, 5);
Sound("Clonk::Movement::StepSoft?");
}
}
func GetWalkAnimationPosition(string anim, int pos)
{
var dir = -1;
if(GetDirection() == COMD_Right) dir = +1;
if(PropAnimations != nil)
if(GetProperty(Format("%s_Position", anim), PropAnimations))
{
var length = GetAnimationLength(anim), replacement;
if(replacement = GetProperty(anim, PropAnimations))
{
// at this point /replacement/ may contain an array of two animations that signal a merge
// in that case, just take the first one..
if(GetType(replacement) == C4V_Array)
replacement = replacement[0];
length = GetAnimationLength(replacement);
}
return Anim_X(pos, 0, length, GetProperty(Format("%s_Position", anim), PropAnimations)*dir);
}
// TODO: Choose proper starting positions, depending on the current
// animation and its position: For Stand->Walk or Stand->Run, start
// with a frame where one of the clonk's feets is on the ground and
// the other one is in the air. For Walk->Run and Run->Walk, fade to
// a state where its feets are at a similar position (just taking
// over previous animation's position might work, using
// GetAnimationPosition()). Walk->Stand is arbitrary I guess.
// First parameter of Anim_Linear/Anim_AbsX is initial position.
// Movement synchronization might also be tweaked somewhat as well.
if(anim == Clonk_WalkInside)
return Anim_Const(0);
if(anim == Clonk_WalkStand)
return Anim_Linear(pos, 0, GetAnimationLength(anim), 35, ANIM_Loop);
else if(anim == Clonk_WalkWalk)
return Anim_X(pos, 0, GetAnimationLength(anim), 20*dir);
else if(anim == Clonk_WalkRun)
return Anim_X(pos, 0, GetAnimationLength(anim), 50*dir);
}
func FxIntWalkStart(pTarget, effect, fTmp)
{
if(fTmp) return;
// Always start in Stand for now... should maybe fade properly from previous animation instead
var anim = "Stand"; //GetCurrentWalkAnimation();
effect.animation_name = anim;
effect.animation_id = PlayAnimation(anim, CLONK_ANIM_SLOT_Movement, GetWalkAnimationPosition(anim), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
effect.idle_animation_time = 0;
effect.idle_time = 0; // Idle counter
effect.idle_offset = Random(300); // Random offset for idle time
// Update carried items
UpdateAttach();
// Set proper turn
SetTurnType(0);
}
func FxIntWalkTimer(pTarget, effect)
{
// Test Waterlevel
if(InLiquid() && GBackLiquid(0, -5) && !Contained())
{
SetAction("Swim");
if(GetComDir() == COMD_Left)
SetComDir(COMD_UpLeft);
else if(GetComDir() == COMD_Right)
SetComDir(COMD_UpRight);
else if(GetComDir() != COMD_Down && GetComDir() != COMD_DownLeft && GetComDir() != COMD_DownRight)
SetComDir(COMD_Up);
return;
}
if(lAnim.backwardsSpeed != nil)
UpdateBackwardsSpeed();
if(effect.idle_animation_time)
{
effect.idle_animation_time--;
if(effect.idle_animation_time == 0)
effect.animation_name = nil;
}
var anim = GetCurrentWalkAnimation();
if(anim != effect.animation_name)
{
effect.animation_name = anim;
effect.idle_time = 0;
effect.animation_id = PlayAnimation(anim, CLONK_ANIM_SLOT_Movement, GetWalkAnimationPosition(anim, 0), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
}
// The clonk has to stand, not making a pause animation yet and not doing other actions with the hands (e.g. loading the bow)
else if(anim == Clonk_WalkStand && !GetHandAction() && GetMenu() == nil)
{
if (effect.footstop_time) effect.footstep_time = 0;
if(!effect.idle_animation_time)
{
effect.idle_time++;
if(effect.idle_time > 300+effect.idle_offset)
{
effect.idle_time = 0;
effect.idle_offset = Random(300);
var rand = Random(GetLength(Clonk_IdleActions));
PlayAnimation(Clonk_IdleActions[rand][0], CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, GetAnimationLength(Clonk_IdleActions[rand][0]), Clonk_IdleActions[rand][1], ANIM_Remove), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
effect.idle_animation_time = Clonk_IdleActions[rand][1]-5;
if (!Random(10))
this->PlaySoundIdle();
}
}
}
else
{
effect.idle_time = 0;
if(effect.idle_animation_time)
{
effect.animation_name = nil;
effect.idle_animation_time = 0;
}
if (anim == Clonk_WalkRun)
{
// There are roughly two animation positions in Run when a foot touches the ground:
// 550 and 1700
// This here is trying to trigger a footstep as close as possible to these two moments.
var pos = GetAnimationPosition(effect.animation_id);
if (pos < 550 && effect.footstep_time)
effect.footstep_time = 0;
if (Inside(pos, 550, 1699) && effect.footstep_time != 1)
{
Footstep();
effect.footstep_time = 1;
}
if (pos >= 1700 && effect.footstep_time != 2)
{
Footstep();
effect.footstep_time = 2;
}
}
else if(effect.footstep_time) effect.footstep_time = 0;
}
}
func FxIntWalkReset(pTarget, effect)
{
effect.animation_name = nil;
}
func StartStand()
{
PlayAnimation(Clonk_WalkStand, CLONK_ANIM_SLOT_Movement, GetWalkAnimationPosition(Clonk_WalkStand), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
// Update carried items
UpdateAttach();
// Set proper turn type
SetTurnType(0);
}
/*--
Scale
During scaling the clonk adjusts his rotation to the ground.. When he is a the top, he uses a extra animation. When the wall doesn't have a platform for the feet he just scales using his arms.
--*/
func StartScale()
{
if(!GetEffect("IntScale", this))
AddEffect("IntScale", this, 1, 1, this);
// Set proper turn type
SetTurnType(1);
// Update carried items
UpdateAttach();
}
func StopScale()
{
if(GetAction() != "Scale") RemoveEffect("IntScale", this);
}
func CheckScaleTop()
{
// Test whether the clonk has reached a top corner
// That is, the leg vertices are the only ones attached to the wall
// Check the head vertex
if (GBackSolid(-1+2*GetDir(),-7)) return false;
// Check the shoulder vertices
if(GBackSolid(-3+6*GetDir(),-3)) return false;
// Check the hip vertices
if(GBackSolid(-5+10*GetDir(),2)) return false;
return true;
}
func CheckScaleTopHelper()
{
// Check if the clonk has passed the material with its leg vertices
// and if COMD_Up is used to climb in which case corner scale would fail
if (GBackSolid(-3+6*GetDir(), 6)) return false;
if (GetComDir() != COMD_Up) return false;
return true;
}
func FxIntScaleStart(target, effect, tmp)
{
if(tmp) return;
effect.animation_id = PlayAnimation("Scale", CLONK_ANIM_SLOT_Movement, Anim_Y(0, GetAnimationLength("Scale"), 0, 15), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
effect.animation_mode = 0;
}
func FxIntScaleTimer(target, number, time)
{
if(GetAction() != "Scale") return;
// When the clonk reaches the top play an extra animation
if(CheckScaleTop())
{
// If the animation is not already set
var dist = 0;
while(!(GBackSolid(-3+6*GetDir(),dist-3) || GBackSolid(-5+10*GetDir(),dist+2)) && dist < 8) dist++;
dist *= 100;
// add the fractional part of the position (dist counts in the opposite direction of y)
dist -= GetY(100)-GetY()*100;
dist = BoundBy(dist, 0, GetAnimationLength("ScaleTop"));
if(number.animation_mode != 1)
{
number.animation_id = PlayAnimation("ScaleTop", CLONK_ANIM_SLOT_Movement, Anim_Const(GetAnimationLength("ScaleTop")*dist/800), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
number.animation_mode = 1;
}
this.dist = dist;
SetAnimationPosition(number.animation_id, Anim_Const(GetAnimationLength("ScaleTop")*dist/800));
// The animation's graphics has to be shifet a bit to adjust to the clonk movement
var pos = GetAnimationPosition(number.animation_id);
SetScaleRotation(0, 0, 0, 0, 0, 1);
// Check if corner scale help is needed
if (CheckScaleTopHelper())
{
if (GetDir() == DIR_Left)
SetComDir(COMD_UpLeft);
else
SetComDir(COMD_UpRight);
number.corner_scale_helper = true;
}
else if (number.corner_scale_helper)
number.corner_scale_helper = false;
}
else if (number.corner_scale_helper)
{
// This will delay everything for 1 frame just for cleanup, hopefully it's not too bad
number.corner_scale_helper = false;
}
else if(!GBackSolid(-10+20*GetDir(), 8))
{
if(number.animation_mode != 2)
{
var pos = GetAnimationPosition(number.animation_id);
number.animation_id = PlayAnimation("ScaleHands" , CLONK_ANIM_SLOT_Movement, Anim_Y(pos, GetAnimationLength("ScaleHands"), 0, 15), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
number.animation_id2 = PlayAnimation("ScaleHands2", CLONK_ANIM_SLOT_Movement, Anim_Y(pos, GetAnimationLength("ScaleHands2"), 0, 15), Anim_Const(1000), number.animation_id);
number.animation_id2++;
number.animation_mode = 2;
}
SetAnimationWeight(number.animation_id2, Anim_Const(Cos(time*2, 500)+500));
SetScaleRotation(0);
}
// If not play the normal scale animation
else if(number.animation_mode != 0)
{
if(number.ScheduleStop)
{
SetComDir(COMD_Stop);
number.ScheduleStop = 0;
}
var pos = 0;
if(number.animation_mode == 2) pos = GetAnimationPosition(number.animation_id);
number.animation_id = PlayAnimation("Scale", CLONK_ANIM_SLOT_Movement, Anim_Y(0, GetAnimationLength("Scale"), 0, 15), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
number.animation_mode = 0;
SetScaleRotation(0);
}
if(number.animation_mode == 0)
{
var x, x2;
var y = -7, y2 = 8;
var dir = -1+2*GetDir();
for(x = 0; x < 10; x++)
if(GBackSolid(x*dir, y)) break;
for(x2 = 0; x2 < 10; x2++)
if(GBackSolid(x2*dir, y2)) break;
var angle = Angle(x2, y2, x, y)*dir;
var mid = (x+x2)*1000/2 - 5000 - this.Off;
this.TestAngle = angle;
this.TestMid = mid;
SetScaleRotation(angle, mid*dir);
}
}
func FxIntScaleRotTimer(target, eff, time)
{
eff.oldR += BoundBy(eff.r-eff.oldR, -3, 3);
eff.oldX += BoundBy(eff.xoff-eff.oldX, -500, 500);
eff.oldY += BoundBy(eff.yoff-eff.oldY, -500, 500);
var turnx = -1000;
var turny = 10000;
SetMeshTransformation(Trans_Mul(Trans_Translate(eff.oldX-turnx, eff.oldY-turny), Trans_Rotate(eff.oldR,0,0,1), Trans_Translate(turnx, turny)), 1);
}
func SetScaleRotation (int r, int xoff, int yoff, int rotZ, int turny, int instant) {
if(r < -180) r += 360;
if(r > 180) r -= 360;
// set matrix values
var turnx = -1000;
var turny = 10000;
if(instant)
{
RemoveEffect("IntScaleRot", this);
SetMeshTransformation(Trans_Mul(Trans_Translate(xoff-turnx, yoff-turny), Trans_Rotate(r,0,0,1), Trans_Translate(turnx, turny), Trans_Rotate(rotZ, 0, 1, 0)), 1);
}
else
{
var eff = GetEffect("IntScaleRot", this);
if(!eff)
eff = AddEffect("IntScaleRot", this, 1, 1, this);
eff.r = r;
eff.xoff = xoff;
eff.yoff = yoff;
}
}
func FxIntScaleStop(target, number, reason, tmp)
{
if(tmp) return;
// Set the animation to stand without blending! That's cause the animation of Scale moves the clonkmesh wich would result in a stange blend moving the clonk around while blending
/* if(number.animation_mode == 1) PlayAnimation(Clonk_WalkStand, CLONK_ANIM_SLOT_Movement, GetWalkAnimationPosition(Clonk_WalkStand), Anim_Const(1000));
// Finally stop if the user has scheduled a stop
if(number.ScheduleStop) SetComDir(COMD_Stop);*/
// Reset the transform
SetScaleRotation(0);
// Remove the corner scale helper com dir
if (number.corner_scale_helper)
if (GetComDir() == COMD_UpLeft || GetComDir() == COMD_UpRight)
Schedule(this, "SetComDir(COMD_Up)", 2);
}
/*--
Jump
Different jump animations for different situations.
--*/
func StartJump()
{
//which leg to kick off with?
var side = "R";
if(Random(2)) side = "L";
//Normal forward jump
if(Abs(GetXDir()) >= 1)
PlayAnimation(Format("Jump.%s",side), CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, GetAnimationLength("Jump.L"), 8*5, ANIM_Hold), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
//Walk kick jump
if(GetEffect("WallKick",this))
{
SetAction("WallJump");
var side = "L";
if(GetDir() == DIR_Left) side = "R";
PlayAnimation(Format("JumpWall.%s", side), CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, GetAnimationLength("JumpWall.L"), 8*5, ANIM_Hold), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
}
//Upwards jump
else if(GetXDir() == 0)
{
PlayAnimation(Format("JumpUp.%s", side), CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, GetAnimationLength("JumpUp.L"), 8*5, ANIM_Hold), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
}
// Update carried items
UpdateAttach();
// Set proper turn type
SetTurnType(0);
//Dive jump (only if not aiming)
if(!this->~IsAiming())
{
var flight = SimFlight(AbsX(GetX()), AbsY(GetY()), GetXDir()*2, GetYDir()*2, 25); //I have no clue why the dirs must be doubled... but it seems to fix it
if(GBackLiquid(flight[0] - GetX(), flight[1] - GetY()) && GBackLiquid(flight[0] - GetX(), flight[1] + GetDefHeight() / 2 - GetY()))
{
PlayAnimation("JumpDive", CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, GetAnimationLength("JumpDive"), 60, ANIM_Hold), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
return 1;
}
}
if(!GetEffect("Fall", this))
AddEffect("Fall",this,1,1,this);
RemoveEffect("WallKick",this);
}
func FxFallEffect(string new_name, object target)
{
// reject more than one fall effects.
if(new_name == "Fall") return -1;
}
func FxFallTimer(object target, effect, int timer)
{
if(GetAction() != "Jump")
return -1;
//falling off ledges without jumping results in fall animation
if(timer == 2 && GetYDir() > 1)
{
PlayAnimation("FallShort", CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, GetAnimationLength("FallShort"), 8*3, ANIM_Hold), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
}
if(timer == 2 && GetYDir() < 1)
{
Sound("Clonk::Movement::Rustle?");
}
if(GetYDir() > 55 && GetAction() == "Jump")
{
PlayAnimation("FallLong", CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, GetAnimationLength("FallLong"), 8*3, ANIM_Hold), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
return -1;
}
}
/*--
Hangle
Adjust the speed sinusoidal. Plays two different stand animations according to the position the clonk stops.
--*/
/* Replaces the named action by an instance with a different speed */
func PushActionSpeed(string action, int n)
{
if (ActMap == this.Prototype.ActMap)
ActMap = { Prototype = this.Prototype.ActMap };
ActMap[action] = { Prototype = ActMap[action], Speed = n };
if (this.Action == ActMap[action].Prototype)
this.Action = ActMap[action];
}
/* Resets the named action to the previous one */
func PopActionSpeed(string action, int n) {
// FIXME: This only works if PushActionSpeed and PopActionSpeed are the only functions manipulating the ActMap
if (this.Action == ActMap[action])
this.Action = ActMap[action].Prototype;
ActMap[action] = ActMap[action].Prototype;
}
func StartHangle()
{
/* if(Clonk_HangleStates == nil)
Clonk_HangleStates = ["HangleStand", "Hangle"];*/
if(!GetEffect("IntHangle", this))
AddEffect("IntHangle", this, 1, 1, this);
// Set proper turn type
SetTurnType(1);
// Update carried items
UpdateAttach();
}
func StopHangle()
{
if(GetAction() != "Hangle") RemoveEffect("IntHangle", this);
}
func FxIntHangleStart(pTarget, effect, fTmp)
{
effect.hangle_speed = ActMap.Hangle.Speed;
PushActionSpeed("Hangle", effect.hangle_speed);
if(fTmp) return;
// is_moving: whether the clonk is currently moving or not (<=> current animation is Hangle or HangleStand)
// request_stop: Player requested the clonk to stop
// facing_front: Whether the HangleStand animation is shown front-facing or back-facing
effect.animation_id = PlayAnimation("HangleStand", CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, 2000, 100, ANIM_Loop), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
}
func FxIntHangleStop(pTarget, effect, iReasonm, fTmp)
{
PopActionSpeed("Hangle");
if(fTmp) return;
// Delayed stop request
if (effect.request_stop) SetComDir(COMD_Stop);
}
func FxIntHangleTimer(pTarget, effect, iTime)
{
// (TODO: Instead of effect.is_moving we should be able
// to query the current animation... maybe via a to-be-implemented
// GetAnimationName() engine function.
// If we are currently moving
if(effect.is_moving)
{
// Use a cosine-shaped movement speed (the clonk only moves when he makes a "stroke")
var iSpeed = 50-Cos(GetAnimationPosition(effect.animation_id)/10*360*2/1000, 50);
ActMap.Hangle.Speed = effect.hangle_speed*iSpeed/50;
// Exec movement animation (TODO: Use Anim_Linear?)
var position = GetAnimationPosition(effect.animation_id);
position += (effect.hangle_speed*5/48*1000/(14*2));
SetAnimationPosition(effect.animation_id, Anim_Const(position % GetAnimationLength("Hangle")));
// Continue movement, if the clonk still has momentum
if(GetComDir() == COMD_Stop && iSpeed>10)
{
// Make it stop after the current movement
effect.request_stop = 1;
if(GetDir())
SetComDir(COMD_Right);
else
SetComDir(COMD_Left);
}
// Stop movement if the clonk has lost his momentum
else if(iSpeed <= 10 && (GetComDir() == COMD_Stop || effect.request_stop))
{
effect.request_stop = 0;
SetComDir(COMD_Stop);
// and remeber the pose (front or back)
if(GetAnimationPosition(effect.animation_id) > 2500 && GetAnimationPosition(effect.animation_id) < 7500)
effect.facing_front = 1;
else
effect.facing_front = 0;
// Change to HangleStand animation
var begin = 4000*effect.facing_front;
var end = 2000+begin;
effect.animation_id = PlayAnimation("HangleStand", CLONK_ANIM_SLOT_Movement, Anim_Linear(begin, begin, end, 100, ANIM_Loop), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
effect.is_moving = 0;
}
}
else
{
// We are currently not moving
if(GetComDir() != COMD_Stop)
{
// Switch to move
effect.is_moving = 1;
// start with frame 100 or from the back hanging pose frame 600
var begin = 10*(100 + 500*effect.facing_front);
effect.animation_id = PlayAnimation("Hangle", CLONK_ANIM_SLOT_Movement, Anim_Const(begin), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
}
}
}
/*--
Swim
Different animation for swiming and diving. Adjusting the rotation while diving.
--*/
func StartSwim()
{
if (!InLiquid())
return;
if (!GetEffect("IntSwim", this))
AddEffect("IntSwim", this, 1, 1, this);
SetSwimmingVertices(true);
return;
}
func StopSwim()
{
if (GetAction() != "Swim")
RemoveEffect("IntSwim", this);
SetSwimmingVertices(false);
return;
}
func SetSwimmingVertices(bool is_swimming)
{
// Remove old delayed vertex effects.
while (GetEffect("IntDelayedVertex", this))
RemoveEffect("IntDelayedVertex", this);
var vtx_list = [[0,2,0,300], [0,-7,4,300], [0,9,11,300], [-2,-3,1,300], [2,-3,2,300], [-4,2,1,300], [4,2,2,300], [-2,6,1,300], [2,6,2,300]];
if (is_swimming)
vtx_list = [[0,2,0,50], [0,-2,4,50], [0,7,11,50], [-4,-1,1,50], [4,-1,2,50], [-4,2,1,50], [4,2,2,50], [-4,4,1,50], [4,4,2,50]];
for (var i = 0; i < GetVertexNum(); i++)
{
var x = GetVertex(i, VTX_X);
var y = GetVertex(i, VTX_Y);
var cnat = GetVertex(i, VTX_CNAT);
var friction = GetVertex(i, VTX_Friction);
SetVertex(i, VTX_X, vtx_list[i][0], 2);
SetVertex(i, VTX_Y, vtx_list[i][1], 2);
SetVertex(i, VTX_CNAT, vtx_list[i][2], 2);
SetVertex(i, VTX_Friction, vtx_list[i][3], 2);
// Don't do the update if that would stuck the clonk.
if (Stuck())
{
SetVertex(i, VTX_X, x, 2);
SetVertex(i, VTX_Y, y, 2);
SetVertex(i, VTX_CNAT, cnat, 2);
SetVertex(i, VTX_Friction, friction, 2);
// But add an effect which does this delayed.
var effect = AddEffect("IntDelayedVertex", this, 100, 1, this);
effect.vertex = i;
effect.to_vertex = vtx_list[i];
}
}
return;
}
protected func FxIntDelayedVertexTimer(object target, proplist effect, int time)
{
if (Stuck())
return FX_OK;
SetVertex(effect.vertex, VTX_X, effect.to_vertex[0], 2);
SetVertex(effect.vertex, VTX_Y, effect.to_vertex[1], 2);
SetVertex(effect.vertex, VTX_CNAT, effect.to_vertex[2], 2);
SetVertex(effect.vertex, VTX_Friction, effect.to_vertex[3], 2);
return FX_Execute_Kill;
}
func FxIntSwimStart(pTarget, effect, fTmp)
{
if(fTmp) return;
effect.animation_name = "SwimStand";
effect.animation = PlayAnimation("SwimStand", CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, GetAnimationLength("SwimStand"), 20, ANIM_Loop), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
// Set proper turn type
SetTurnType(0);
// Update carried items
UpdateAttach();
}
func FxIntSwimTimer(pTarget, effect, iTime)
{
var iSpeed = Distance(0,0,GetXDir(),GetYDir());
// TODO: Smaller transition time between dive<->swim, keep 15 for swimstand<->swim/swimstand<->dive
// Play stand animation when not moving
if(Abs(GetXDir()) < 1 && !GBackSemiSolid(0, -5))
{
if (GetContact(-1) & CNAT_Bottom)
{
SetAction("Walk");
return -1;
}
if(effect.animation_name != "SwimStand")
{
effect.animation_name = "SwimStand";
effect.animation = PlayAnimation("SwimStand", CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, GetAnimationLength("SwimStand"), 20, ANIM_Loop), Anim_Linear(0, 0, 1000, 15, ANIM_Remove));
}
}
// Swimming
else if(!GBackSemiSolid(0, -5))
{
if (GBackLiquid()) // re-check water background before effects to prevent waves in wrong color
{
var percent = GetAnimationPosition(GetRootAnimation(5)) * 200 / GetAnimationLength("Swim");
percent = (percent % 100);
if (percent < 40)
{
if (iTime % 5 == 0)
{
var phases = PV_Linear(0, 7);
if (GetDir() == 1) phases = PV_Linear(8, 15);
var color = GetAverageTextureColor(GetTexture(0, 0));
var particles =
{
Size = 16,
Phase = phases,
CollisionVertex = 750,
OnCollision = PC_Die(),
R = (color >> 16) & 0xff,
G = (color >> 8) & 0xff,
B = (color >> 0) & 0xff,
Attach = ATTACH_Front,
};
CreateParticle("Wave", 0, -4, (RandomX(-5, 5) - (-1 + 2 * GetDir()) * 4) / 4, 0, 16, particles);
}
Sound("Liquids::Swim?");
}
}
// Animation speed by X
if(effect.animation_name != "Swim")
{
effect.animation_name = "Swim";
// TODO: Determine starting position from previous animation
PlayAnimation("Swim", CLONK_ANIM_SLOT_Movement, Anim_AbsX(0, 0, GetAnimationLength("Swim"), 25), Anim_Linear(0, 0, 1000, 15, ANIM_Remove));
}
}
// Diving
else
{
if(effect.animation_name != "SwimDive")
{
effect.animation_name = "SwimDive";
// TODO: Determine starting position from previous animation
effect.animation2 = PlayAnimation("SwimDiveUp", CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, GetAnimationLength("SwimDiveUp"), 40, ANIM_Loop), Anim_Linear(0, 0, 1000, 15, ANIM_Remove));
effect.animation3 = PlayAnimation("SwimDiveDown", CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, GetAnimationLength("SwimDiveDown"), 40, ANIM_Loop), Anim_Const(500), effect.animation2);
effect.animation = effect.animation3 + 1;
// TODO: This should depend on which animation we come from
// Guess for SwimStand we should fade from 0, otherwise from 90.
effect.rot = 90;
}
if(iSpeed)
{
var iRot = Angle(-Abs(GetXDir()), GetYDir());
effect.rot += BoundBy(iRot - effect.rot, -4, 4);
}
// TODO: Shouldn't weight go by sin^2 or cos^2 instead of linear in angle?
var weight = 1000*effect.rot/180;
SetAnimationWeight(effect.animation, Anim_Const(1000 - weight));
}
}
func GetSwimRotation()
{
var effect = GetEffect("IntSwim", this);
if(!effect) return 0;
return effect.rot*(-1+2*(GetDirection()==COMD_Right));
}
/*--
Roll and kneel
When the clonk hits the ground a kneel animation or are roll are performed.
--*/
func Hit(int iXSpeed, int iYSpeed)
{
if (this->IsWalking() && iYSpeed > 450)
{
// roll :D
var x_movement = ComDir2XY(GetComDir())[0];
var looking_right = GetDir() == DIR_Right;
if ((x_movement > 0 && looking_right) || (x_movement < 0 && !looking_right))
{
DoRoll();
}
else // Force kneel-down when hitting the ground at high velocity.
DoKneel(true);
}
return _inherited(iXSpeed, iYSpeed, ...);
}
func DoKneel(bool create_dust)
{
var iKneelDownSpeed = 18;
SetXDir(0);
SetAction("Kneel");
Sound("Clonk::Movement::RustleLand");
PlayAnimation("KneelDown", CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, GetAnimationLength("KneelDown"), iKneelDownSpeed, ANIM_Remove), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
ScheduleCall(this, "EndKneel", iKneelDownSpeed, 1);
if (create_dust)
{
if (GetMaterialVal("DigFree", "Material", GetMaterial(0,10)))
{
var clr = GetAverageTextureColor(GetTexture(0,10));
var particles =
{
Prototype = Particles_Dust(),
R = (clr >> 16) & 0xff,
G = (clr >> 8) & 0xff,
B = clr & 0xff,
};
CreateParticle("Dust", PV_Random(-4, 4), 8, PV_Random(-3, 3), PV_Random(-2, -4), PV_Random(36, 2 * 36), particles, 12);
}
}
return 1;
}
func EndKneel()
{
if(GetAction() != "Roll") SetAction("Walk");
}
// Start a roll into the current direction.
func DoRoll()
{
SetAction("Roll");
}
// Called when the roll action is started.
func OnStartRoll()
{
SetTurnForced(GetDir());
Sound("Clonk::Movement::Roll");
if(GetDir() == 1) lAnim.rollDir = 1;
else
lAnim.rollDir = -1;
lAnim.rollLength = 22;
PlayAnimation("KneelRoll", CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, 1500, lAnim.rollLength, ANIM_Remove), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
AddEffect("Rolling", this, 1, 1, this);
}
// Called when the roll action is interrupted.
func OnAbortRoll()
{
var e = GetEffect("Rolling", this);
if (e)
RemoveEffect(nil, this, e);
}
func FxRollingTimer(object target, effect effect, int timer)
{
if(GetContact(-1)) SetXDir(23 * lAnim.rollDir);
//Hacky fun
var i = 3;
while(GBackSolid(lAnim.rollDir, 9) && i != 0)
{
SetPosition(GetX(),GetY() - 1);
i--;
}
if(timer > lAnim.rollLength)
{
return -1;
}
if (GetMaterialVal("DigFree", "Material", GetMaterial(0,10)))
{
var clr = GetAverageTextureColor(GetTexture(0,10));
var dir = GetDir()*2-1;
var particles =
{
Prototype = Particles_Dust(),
R = (clr >> 16) & 0xff,
G = (clr >> 8) & 0xff,
B = clr & 0xff,
};
CreateParticle("Dust", PV_Random(dir * -2, dir * -1), 8, PV_Random(dir * 2, dir * 1), PV_Random(-2, -5), PV_Random(36, 2 * 36), particles, 6);
}
}
func FxRollingStop(object target, proplist effect, int reason, temp)
{
if (temp && !this) return;
if (GetAction() == "Roll")
SetAction("Walk");
SetTurnForced(-1);
lAnim.rollDir = nil;
}
/*--
Digging
The effect just is responsible for the sound and the termination of digging.
--*/
public func StartDigging()
{
if (!GetEffect("IntDig", this))
AddEffect("IntDig", this, 1, 1, this);
}
public func StopDigging()
{
if (GetAction() != "Dig")
RemoveEffect("IntDig", this);
}
public func GetDiggingAnimation()
{
var dig_effect = GetEffect("IntDig", this);
if (dig_effect)
return dig_effect.animation;
return;
}
public func FxIntDigStart(object target, effect fx, int temp)
{
if (temp)
return FX_OK;
fx.animation = PlayAnimation("Dig", CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, GetAnimationLength("Dig"), 36, ANIM_Loop), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
// Update carried items
UpdateAttach();
// Sound
Sound("Clonk::Action::Dig::Dig?");
// Set proper turn type
SetTurnType(0);
return FX_OK;
}
public func FxIntDigTimer(object target, effect fx, int time)
{
if (time % 36 == 0)
{
Sound("Clonk::Action::Dig::Dig?");
}
if (time == 18 || time >= 36)
{
var no_dig = true;
for (var shovel in FindObjects(Find_ID(Shovel), Find_Container(this)))
if (shovel->IsDigging())
no_dig = false;
if (no_dig)
{
SetAction("Walk");
SetComDir(COMD_Stop);
return FX_Execute_Kill;
}
}
return FX_OK;
}
/*--
Throwing
Throw animation
--*/
// custom throw
public func ControlThrow(object target, int x, int y)
{
// standard throw after all
if (!x && !y) return false;
if (!target) return false;
var throwAngle = Angle(0,0,x,y);
// walking (later with animation: flight, scale, hangle?) and hands free
if ( (GetProcedure() == "WALK" || GetAction() == "Jump" || GetAction() == "WallJump" || GetAction() == "Dive")
&& this->~HasHandAction())
{
if (throwAngle < 180) SetDir(DIR_Right);
else SetDir(DIR_Left);
//SetAction("Throw");
this->~SetHandAction(1); // Set hands ocupied
AddEffect("IntThrow", this, 1, 1, this, nil, target, throwAngle);
return true;
}
// attached
if (GetProcedure() == "ATTACH")
{
//SetAction("RideThrow");
return DoThrow(target,throwAngle);
}
return false;
}
func FxIntThrowStart(target, effect, tmp, targetobj, throwAngle)
{
var iThrowTime = 16;
if(tmp) return;
PlayAnimation("ThrowArms", CLONK_ANIM_SLOT_Arms, Anim_Linear(0, 0, GetAnimationLength("ThrowArms"), iThrowTime));
effect.targetobj = targetobj;
effect.angle = throwAngle;
}
func FxIntThrowTimer(target, effect, time)
{
// cancel throw if object does not exist anymore
if(!effect.targetobj)
return -1;
var iThrowTime = 16;
if(time == iThrowTime*8/15)
DoThrow(effect.targetobj, effect.angle);
if(time >= iThrowTime)
return -1;
}
func FxIntThrowStop(target, effect, reason, tmp)
{
if(tmp) return;
StopAnimation(GetRootAnimation(10));
this->~SetHandAction(0);
}
/*--
Dead
Animation on clonk death
--*/
func StartDead()
{
PlayAnimation("Dead", CLONK_ANIM_SLOT_Death, Anim_Linear(0, 0, GetAnimationLength("Dead"), 20, ANIM_Hold), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
// Update carried items
UpdateAttach();
// Set proper turn type
SetTurnType(1);
}
/*--
Tumble
Tumble animation with shut eyes.
--*/
func StartTumble()
{
if(GetEffect("IntTumble", this)) return;
// Close eyes
CloseEyes(1);
PlayAnimation("Tumble", CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, GetAnimationLength("Tumble"), 20, ANIM_Loop), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
// Update carried items
UpdateAttach();
// Set proper turn type
SetTurnType(0);
AddEffect("IntTumble", this, 1, 0);
}
func StopTumble()
{
if(GetAction() != "Tumble")
{
RemoveEffect("IntTumble", this);
CloseEyes(-1);
}
}
/*--
Riding
Riding effect. Makes clonk invisible if the mount attaches the clonk as a mesh (e.g. the boompack)
--*/
public func StartRiding()
{
if(!GetEffect("IntRiding", this))
AddEffect("IntRiding", this, 1, 0, this);
}
public func AttachTargetLost()
{
if(GetEffect("IntRiding", this))
RemoveEffect("IntRiding", this);
}
public func StopRiding()
{
if(GetEffect("IntRiding", this))
RemoveEffect("IntRiding", this);
}
func FxIntRidingStart(pTarget, effect, fTmp)
{
if(fTmp) return;
var pMount = GetActionTarget();
if(!pMount) return -1;
if(pMount->~OnMount(this)) // Notifiy the mount, that the clonk is mounted (it should take care, that the clonk get's attached!
{
// if mount has returned true we should be attached
// So make the clonk object invisible
effect.vis = GetProperty("Visibility");
SetProperty("Visibility", VIS_None);
}
else effect.vis = -1;
effect.mount = pMount;
}
func FxIntRidingStop(pTarget, effect, fTmp)
{
if(fTmp) return;
if(effect.vis != -1)
SetProperty("Visibility", effect.vis);
var pMount = effect.mount;
if(pMount)
pMount->~OnUnmount(this);
}
/*--
Pushing
Just plays the push animation.
--*/
func StartPushing()
{
// if(GetEffect("IntTumble", this)) return;
// Close eyes
PlayAnimation("Push", CLONK_ANIM_SLOT_Movement, Anim_AbsX(0, 0, GetAnimationLength("Push"), 20), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
// Update carried items
UpdateAttach();
// Set proper turn type
SetTurnType(1);
// AddEffect("IntTumble", this, 1, 0);
}
protected func StopPushing()
{
return _inherited(...);
}
/*--
HangOnto
Plays the animation and notifies the target if aborted. Used then the clonk hangs on the grappler's rope
--*/
func StartHangOnto()
{
// if(GetEffect("IntTumble", this)) return;
// Close eyes
PlayAnimation("OnRope", CLONK_ANIM_SLOT_Movement, Anim_Linear(0, 0, GetAnimationLength("OnRope"), 20, ANIM_Loop), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
// Update carried items
UpdateAttach();
// Set proper turn type
SetTurnType(1);
// AddEffect("IntTumble", this, 1, 0);
}
protected func AbortHangOnto()
{
if (GetActionTarget(0))
GetActionTarget(0)->~HangOntoLost(this);
return;
}
/*--
Eat
Plays the animation
--*/
func StartEat()
{
// Nom nom
PlayAnimation("Eat", CLONK_ANIM_SLOT_Arms, Anim_Linear(0,0, GetAnimationLength("Eat"), 45, ANIM_Remove), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
// Update carried items
UpdateAttach();
}