openclonk/planet/Objects.ocd/Libraries.ocd/AimManager.ocd/Script.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");
}