forked from Mirrors/openclonk
409 lines
14 KiB
C
409 lines
14 KiB
C
/*--
|
|
Aim Manager
|
|
Authors: Randrian
|
|
|
|
Manages loading, aiming and shooting
|
|
The weapon has to define an animations set which can have the following entries
|
|
animation_set = {
|
|
AimMode = AIM_Position, // The aiming mode see constants for explanations
|
|
AnimationAim = "BowAimArms", // The animation that is used during aiming
|
|
AnimationAim2 = "", // The animation the first is blended with (only for mode AIM_Weight)
|
|
AimTime = nil, // Time for the aim animation cycle (only for mode AIM_Weight)
|
|
AnimationLoad = "BowLoadArms",// The animation during loading
|
|
LoadTime = 30, // The duration of the loading animation
|
|
LoadTime2 = 5, // For a callback LoadTime2 frames after LoadShoot: DuringLoad
|
|
AnimationShoot = nil, // The animation for shooting
|
|
AnimationShoot2 = nil, // If not nil the shooting will blend according to angle (0° animation 1, 180° animation 2)
|
|
AnimationShoot3 = nil, // If not nil blending between 3 actions (90° anination 1, 0° animation 2, 180° animation 3)
|
|
ShootTime = 20, // The duration of shooting
|
|
ShootTime2 = 5, // For a callback ShootTime2 frames after StartShoot: DuringShoot
|
|
TurnType = 1, // Specify a special turn type (0: turn 120 degrees, 1: turn 180 degrees, 2: turn 220 degrees) see SetTurnType in clonk
|
|
WalkSpeed = 84, // Reduce the walk speed during aiming
|
|
WalkBack = 56, // Reduce the walk speed for walking backwards (speed 0 means no walking backwards at all)
|
|
AimSpeed = 5, // the speed of aiming default 5° per frame
|
|
AnimationReplacements = [ // Replace some animations with others during load/aim/shoot
|
|
["Walk", "BowWalk"],
|
|
["Walk_Position", 20], // Walk_Position changes the distance the clonk travels in one cycle of the animation
|
|
["Stand", "BowStand"],
|
|
["Jump", "BowJump"],
|
|
["KneelDown", "BowKneel"]
|
|
],
|
|
// If an animation is nil there is no new animation played and the clonk just waits *Time frames
|
|
};
|
|
The weapon gets the following callbacks
|
|
GetAnimationSet(); // Has to return the animation set
|
|
// The following Stop* Callbacks, have to return true if the clonk doesn't have to be reset (e.g. stating aiming after loading)
|
|
FinishedLoading(object clonk); // When the loading animation is over (after LoadTime frames)
|
|
FinishedAiming(object clonk, int angle); // When the clonk has finished loading and aiming at the desired position
|
|
FinishedShooting(object clonk, int angle); // When the shooting animation is over (after ShootTime frames)
|
|
DuringLoad(object clonk); // LoadTime2 frames after load start
|
|
DuringShoot(object clonk, int angle); // ShootTime2 frames after shoot start
|
|
// When the clonk has during aiming an action where he can't use his hands, the aiming is paused
|
|
Reset(object clonk); // Callback when the clonk has been reseted
|
|
|
|
The Weapon can use the following functions on the clonk:
|
|
StartLoad(object weapon); // The weapon wants to start loading (e.g. on ControlUseStart)
|
|
StartAim(object weapon); // The weapon wants to switch in aim mode (e.g. after loading on StopLoad)
|
|
SetAimPosition(int angle); // The weapon specifies a new angle (e.g. on ControlUseHolding)
|
|
StopAim(); // The weapon wants to shoot (e.g. on ControlUseStop) BUT: the stop is just scheduled! The clonk finished loading or aiming and then really stops aiming!
|
|
StartShoot(object weapon); // The weapon wants to start the shoot animation (e.g. on StopAim)
|
|
|
|
CancelAiming(); // The weapon wants to cancel the aiming (e.g. on ControlUseCancel)
|
|
// When the clonk starts with load/aim/shoot (when StartLoad, StartAim or StartShoot is called) he uses the animation set
|
|
// the means he replaces the animtione specified in AnimationReplacements and adjust the TurnType and Walkspeed (if these are not nil)
|
|
--*/
|
|
|
|
local aim_set;
|
|
local aim_weapon;
|
|
local aim_animation_index;
|
|
local aim_angle;
|
|
local aim_stop;
|
|
|
|
local aim_schedule_timer;
|
|
local aim_schedule_call;
|
|
|
|
local aim_schedule_timer2;
|
|
local aim_schedule_call2;
|
|
|
|
// Aim modes
|
|
static const AIM_Position = 1; // The aim angle alters the position of the animation (0° menas 0% animation position, 180° menas 100% andimation position)
|
|
static const AIM_Weight = 2; // The aim angle alters with blending between AnimationAim (for 0°) and AnimationAim2 (for 180°)
|
|
|
|
func ReadyToAction() { return _inherited(...); }
|
|
func SetHandAction() { return _inherited(...); }
|
|
func ReplaceAction() { return _inherited(...); }
|
|
func SetTurnType () { return _inherited(...); }
|
|
func SetTurnForced() { return _inherited(...); }
|
|
func SetBackwardsSpeed() { return _inherited(...); }
|
|
|
|
func IsAiming() { return !!GetEffect("IntAim", this); }
|
|
|
|
func FxIntAimCheckProcedureStart(target, effect, tmp)
|
|
{
|
|
if(tmp) return;
|
|
}
|
|
|
|
func FxIntAimCheckProcedureTimer()
|
|
{
|
|
// Care about the aim schedules
|
|
if(aim_schedule_timer != nil)
|
|
{
|
|
aim_schedule_timer--;
|
|
if(aim_schedule_timer == 0)
|
|
{
|
|
Call(aim_schedule_call);
|
|
aim_schedule_call = nil;
|
|
aim_schedule_timer = nil;
|
|
}
|
|
}
|
|
if(aim_schedule_timer2 != nil)
|
|
{
|
|
aim_schedule_timer2--;
|
|
if(aim_schedule_timer2 == 0)
|
|
{
|
|
Call(aim_schedule_call2);
|
|
aim_schedule_call2 = nil;
|
|
aim_schedule_timer2 = nil;
|
|
}
|
|
}
|
|
|
|
// check procedure
|
|
if(!ReadyToAction())
|
|
PauseAim();
|
|
}
|
|
|
|
func FxIntAimCheckProcedureStop(target, effect, reason, tmp)
|
|
{
|
|
if(tmp) return;
|
|
if(reason == 4)
|
|
CancelAiming();
|
|
}
|
|
|
|
func PauseAim()
|
|
{
|
|
if(!aim_weapon || aim_stop) return CancelAiming();
|
|
// reissue the CON_Use command to the weapon when ready
|
|
this->PauseUse(aim_weapon);
|
|
CancelAiming();
|
|
}
|
|
|
|
public func StartLoad(object weapon)
|
|
{
|
|
// only if we aren't adjusted to this weapon already
|
|
if(weapon != aim_weapon)
|
|
{
|
|
// Reset old
|
|
if(aim_weapon != nil) aim_weapon->~Reset();
|
|
if(aim_set != nil) ResetHands();
|
|
|
|
// Remember new
|
|
aim_weapon = weapon;
|
|
aim_set = weapon->~GetAnimationSet();
|
|
|
|
// Applay the set
|
|
ApplySet(aim_set);
|
|
|
|
// Add effect to ensure procedure
|
|
AddEffect("IntAimCheckProcedure", this, 1, 1, this);
|
|
}
|
|
|
|
if(aim_set["AnimationLoad"] != nil)
|
|
PlayAnimation(aim_set["AnimationLoad"], CLONK_ANIM_SLOT_Arms, Anim_Linear(0, 0, GetAnimationLength(aim_set["AnimationLoad"]), aim_set["LoadTime"], ANIM_Remove), Anim_Const(1000));
|
|
|
|
aim_schedule_timer = aim_set["LoadTime"];
|
|
aim_schedule_call = "StopLoad";
|
|
|
|
if(aim_set["LoadTime2"] != nil)
|
|
{
|
|
aim_schedule_timer2 = aim_set["LoadTime2"];
|
|
aim_schedule_call2 = "DuringLoad";
|
|
}
|
|
}
|
|
|
|
public func DuringLoad() { aim_weapon->~DuringLoad(this); }
|
|
|
|
public func StopLoad()
|
|
{
|
|
if(!aim_weapon || !aim_weapon->~FinishedLoading(this)) // return 1 means the weapon goes on doing something (e.g. start aiming) then we don't reset
|
|
ResetHands();
|
|
}
|
|
|
|
public func StartAim(object weapon, int angle)
|
|
{
|
|
// only if we aren't adjusted to this weapon already
|
|
if(weapon != aim_weapon)
|
|
{
|
|
// Reset old
|
|
if(aim_weapon != nil) aim_weapon->~Reset();
|
|
if(aim_set != nil) ResetHands();
|
|
|
|
// Remember new
|
|
aim_weapon = weapon;
|
|
aim_set = weapon->~GetAnimationSet();
|
|
|
|
// Apply the set
|
|
ApplySet(aim_set);
|
|
|
|
// Add effect to ensure procedure
|
|
AddEffect("IntAimCheckProcedure", this, 1, 1, this);
|
|
}
|
|
|
|
if(aim_set["AnimationAim"] != nil)
|
|
{
|
|
if(aim_set["AimMode"] == AIM_Position)
|
|
aim_animation_index = PlayAnimation(aim_set["AnimationAim"], CLONK_ANIM_SLOT_Arms, Anim_Const(GetAnimationLength(aim_set["AnimationAim"])/2), Anim_Const(1000));
|
|
if(aim_set["AimMode"] == AIM_Weight)
|
|
{
|
|
aim_animation_index = PlayAnimation(aim_set["AnimationAim"], CLONK_ANIM_SLOT_Arms, Anim_Linear(0, 0, GetAnimationLength(aim_set["AnimationAim"]), aim_set["AimTime"], ANIM_Loop), Anim_Const(1000));
|
|
aim_animation_index = PlayAnimation(aim_set["AnimationAim2"], CLONK_ANIM_SLOT_Arms, Anim_Linear(0, 0, GetAnimationLength(aim_set["AnimationAim2"]), aim_set["AimTime"], ANIM_Loop), Anim_Const(1000), aim_animation_index);
|
|
aim_animation_index++;
|
|
SetAnimationWeight(aim_animation_index, Anim_Const(500));
|
|
}
|
|
}
|
|
|
|
// aim_angle = -90;
|
|
// if(GetDir()) aim_angle = 90;
|
|
AddEffect("IntAim", this, 1, 1, this);
|
|
}
|
|
|
|
func FxIntAimTimer(target, effect, time)
|
|
{
|
|
var angle, delta_angle, length;
|
|
var speed = aim_set["AimSpeed"];
|
|
if(speed == nil) speed = 50;
|
|
else speed *= 10;
|
|
if(aim_angle < 0) SetTurnForced(DIR_Left);
|
|
if(aim_angle > 0) SetTurnForced(DIR_Right);
|
|
if(aim_set["AimMode"] == AIM_Position)
|
|
{
|
|
length = GetAnimationLength(aim_set["AnimationAim"]);
|
|
angle = Abs(aim_angle)*10;//GetAnimationPosition(aim_animation_index)*1800/length;
|
|
delta_angle = 0;//BoundBy(Abs(aim_angle*10)-angle, -speed, speed);
|
|
SetAnimationPosition(aim_animation_index, Anim_Const( (angle+delta_angle)*length/1800 ));
|
|
}
|
|
if(aim_set["AimMode"] == AIM_Weight)
|
|
{
|
|
angle = Abs(aim_angle)*10;//GetAnimationWeight(aim_animation_index)*1800/1000;
|
|
delta_angle = 0;//BoundBy(Abs(aim_angle*10)-angle, -speed, speed);
|
|
SetAnimationWeight(aim_animation_index, Anim_Const( (angle+delta_angle)*1000/1800 ));
|
|
}
|
|
// We have reached the angle and we want to stop
|
|
if(Abs(delta_angle) <= 5 && aim_stop == 1)
|
|
{
|
|
DoStopAim();
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// returns the current aiming angle
|
|
public func GetAimPosition() { return aim_angle; }
|
|
|
|
public func SetAimPosition(int angle)
|
|
{
|
|
// Save angle
|
|
aim_angle = angle;
|
|
// Remove scheduled stop if aiming again
|
|
aim_stop = 0;
|
|
}
|
|
|
|
public func StopAim()
|
|
{
|
|
// Schedule Stop
|
|
aim_stop = 1;
|
|
}
|
|
|
|
private func DoStopAim()
|
|
{
|
|
// Return true means the weapon goes on doing something (e.g. start aiming) then we don't reset.
|
|
if (!aim_weapon || !aim_weapon->~FinishedAiming(this, aim_angle))
|
|
ResetHands();
|
|
return;
|
|
}
|
|
|
|
public func StartShoot(object weapon)
|
|
{
|
|
// only if we aren't adjusted to this weapon already
|
|
if(weapon != aim_weapon)
|
|
{
|
|
// Reset old
|
|
if(aim_weapon != nil) aim_weapon->~Reset();
|
|
if(aim_set != nil) ResetHands();
|
|
|
|
// Remember new
|
|
aim_weapon = weapon;
|
|
aim_set = weapon->~GetAnimationSet();
|
|
|
|
// Applay the set
|
|
ApplySet(aim_set);
|
|
|
|
// Add effect to ensure procedure
|
|
AddEffect("IntAimCheckProcedure", this, 1, 1, this);
|
|
}
|
|
|
|
if(aim_set["AnimationShoot"] != nil)
|
|
{
|
|
// Do we just have one animation? Then just play it
|
|
if(aim_set["AnimationShoot2"] == nil)
|
|
PlayAnimation(aim_set["AnimationShoot"], CLONK_ANIM_SLOT_Arms, Anim_Linear(0, 0, GetAnimationLength(aim_set["AnimationShoot"]), aim_set["ShootTime"], ANIM_Remove), Anim_Const(1000));
|
|
// Well two animations blend betweend them (animtion 1 is 0° animation2 for 180°)
|
|
else if(aim_set["AnimationShoot3"] == nil)
|
|
{
|
|
var iAim;
|
|
iAim = PlayAnimation(aim_set["AnimationShoot"], CLONK_ANIM_SLOT_Arms, Anim_Linear(0, 0, GetAnimationLength(aim_set["AnimationShoot"] ), aim_set["ShootTime"], ANIM_Remove), Anim_Const(1000));
|
|
iAim = PlayAnimation(aim_set["AnimationShoot2"], CLONK_ANIM_SLOT_Arms, Anim_Linear(0, 0, GetAnimationLength(aim_set["AnimationShoot2"]), aim_set["ShootTime"], ANIM_Remove), Anim_Const(1000), iAim);
|
|
SetAnimationWeight(iAim+1, Anim_Const(1000*Abs(aim_angle)/180));
|
|
}
|
|
// Well then we'll have three to blend (animation 1 is 90°, animation 2 is 0°, animation 2 for 180°)
|
|
else
|
|
{
|
|
var iAim;
|
|
if(Abs(aim_angle) < 90)
|
|
{
|
|
iAim = PlayAnimation(aim_set["AnimationShoot2"], CLONK_ANIM_SLOT_Arms, Anim_Linear(0, 0, GetAnimationLength(aim_set["AnimationShoot2"]), aim_set["ShootTime"], ANIM_Remove), Anim_Const(1000));
|
|
iAim = PlayAnimation(aim_set["AnimationShoot"], CLONK_ANIM_SLOT_Arms, Anim_Linear(0, 0, GetAnimationLength(aim_set["AnimationShoot"] ), aim_set["ShootTime"], ANIM_Remove), Anim_Const(1000), iAim);
|
|
SetAnimationWeight(iAim+1, Anim_Const(1000*Abs(aim_angle)/90));
|
|
}
|
|
else
|
|
{
|
|
iAim = PlayAnimation(aim_set["AnimationShoot"], CLONK_ANIM_SLOT_Arms, Anim_Linear(0, 0, GetAnimationLength(aim_set["AnimationShoot"] ), aim_set["ShootTime"], ANIM_Remove), Anim_Const(1000));
|
|
iAim = PlayAnimation(aim_set["AnimationShoot3"], CLONK_ANIM_SLOT_Arms, Anim_Linear(0, 0, GetAnimationLength(aim_set["AnimationShoot3"]), aim_set["ShootTime"], ANIM_Remove), Anim_Const(1000), iAim);
|
|
SetAnimationWeight(iAim+1, Anim_Const(1000*(Abs(aim_angle)-90)/90));
|
|
}
|
|
}
|
|
}
|
|
|
|
aim_schedule_timer = aim_set["ShootTime"];
|
|
aim_schedule_call = "StopShoot";
|
|
|
|
if(aim_set["ShootTime2"] != nil)
|
|
{
|
|
aim_schedule_timer2 = aim_set["ShootTime2"];
|
|
aim_schedule_call2 = "DuringShoot";
|
|
}
|
|
}
|
|
|
|
public func DuringShoot() { if (aim_weapon) aim_weapon->~DuringShoot(this, aim_angle); }
|
|
|
|
public func StopShoot()
|
|
{
|
|
if(aim_weapon == nil || !aim_weapon->~FinishedShooting(this, aim_angle)) // return 1 means the weapon goes on doing something (e.g. start aiming) then we don't reset
|
|
ResetHands();
|
|
}
|
|
|
|
public func CancelAiming()
|
|
{
|
|
ResetHands();
|
|
}
|
|
|
|
public func ApplySet(set)
|
|
{
|
|
// Setting the hands as blocked, so that no other items are carried in the hands
|
|
SetHandAction(1);
|
|
|
|
if(set["TurnType"] != nil)
|
|
SetTurnType(set["TurnType"], 1);
|
|
|
|
if(set["AnimationReplacements"] != nil)
|
|
for(var replace in set["AnimationReplacements"])
|
|
ReplaceAction(replace[0], replace[1]);
|
|
|
|
if(set["WalkSpeed"] != nil)
|
|
AddEffect("IntWalkSlow", this, 1, 0, this, nil, set["WalkSpeed"]);
|
|
|
|
if(set["WalkBack"] != nil)
|
|
SetBackwardsSpeed(set["WalkBack"]);
|
|
}
|
|
|
|
public func ResetHands()
|
|
{
|
|
if(!GetEffect("IntAimCheckProcedure", this))
|
|
return;
|
|
|
|
if(aim_weapon != nil)
|
|
{
|
|
aim_weapon->~Reset(this);
|
|
|
|
if(aim_set["AnimationReplacements"] != nil)
|
|
for(var replace in aim_set["AnimationReplacements"])
|
|
ReplaceAction(replace[0], nil);
|
|
}
|
|
|
|
aim_stop = 0;
|
|
aim_angle = -90+180*GetDir();
|
|
|
|
StopAnimation(GetRootAnimation(10));
|
|
|
|
RemoveEffect("IntWalkSlow", this);
|
|
SetBackwardsSpeed(nil);
|
|
|
|
RemoveEffect("IntAim", this);
|
|
|
|
SetTurnForced(-1);
|
|
|
|
SetTurnType(0, -1);
|
|
SetHandAction(0);
|
|
|
|
aim_weapon = nil;
|
|
aim_set = nil;
|
|
|
|
aim_schedule_call = nil;
|
|
aim_schedule_timer = nil;
|
|
aim_schedule_call2 = nil;
|
|
aim_schedule_timer2 = nil;
|
|
|
|
RemoveEffect("IntAimCheckProcedure", this);
|
|
}
|
|
|
|
/* +++++++++++ Slow walk +++++++++++ */
|
|
|
|
func FxIntWalkSlowStart(pTarget, effect, fTmp, iValue)
|
|
{
|
|
if(iValue == nil) iValue = 84;
|
|
pTarget->PushActionSpeed("Walk", iValue);
|
|
}
|
|
|
|
func FxIntWalkSlowStop(pTarget, effect)
|
|
{
|
|
pTarget->PopActionSpeed("Walk");
|
|
}
|