forked from Mirrors/openclonk
1540 lines
42 KiB
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();
|
|
}
|