forked from Mirrors/openclonk
387 lines
9.8 KiB
C
387 lines
9.8 KiB
C
/**
|
|
Catapult
|
|
Tosses objects farther than a clonk can, withour requiring gunpowder.
|
|
|
|
@author Ringwaul
|
|
*/
|
|
|
|
#include Library_ElevatorControl
|
|
#include Library_Destructible
|
|
|
|
local aim_anim;
|
|
local turn_anim;
|
|
local olddir;
|
|
local dir;
|
|
local clonkmesh;
|
|
|
|
// Shooting strength for normal launching and self launches.
|
|
static const CATAPULT_MaxPower = 100;
|
|
static const CATAPULT_MaxPower_SelfLaunch = 75;
|
|
|
|
public func IsVehicle() { return true; }
|
|
public func IsArmoryProduct() { return true; }
|
|
public func FitsInDoubleElevator() { return true; }
|
|
|
|
protected func Initialize()
|
|
{
|
|
dir = DIR_Right;
|
|
olddir = DIR_Right;
|
|
SetAction("Roll");
|
|
aim_anim = PlayAnimation("ArmPosition", 1, Anim_Const(0));
|
|
turn_anim = PlayAnimation("TurnRight", 5, Anim_Const(GetAnimationLength("TurnRight")));
|
|
}
|
|
|
|
protected func ContactLeft()
|
|
{
|
|
if (Stuck() && !Random(5))
|
|
SetRDir(RandomX(-7, +7));
|
|
}
|
|
|
|
protected func ContactRight()
|
|
{
|
|
if (Stuck() && !Random(5))
|
|
SetRDir(RandomX(-7, +7));
|
|
}
|
|
|
|
public func SetDir(int to_dir)
|
|
{
|
|
if (to_dir == DIR_Left)
|
|
{
|
|
turn_anim = PlayAnimation("TurnLeft", 5, Anim_Const(GetAnimationLength("TurnLeft")));
|
|
dir = DIR_Left;
|
|
}
|
|
if (to_dir == DIR_Right)
|
|
{
|
|
turn_anim = PlayAnimation("TurnRight", 5, Anim_Const(GetAnimationLength("TurnRight")));
|
|
dir = DIR_Right;
|
|
}
|
|
return inherited(dir, ...);
|
|
}
|
|
|
|
/*-- Controls --*/
|
|
|
|
// Activate turn animations
|
|
func ControlLeft(object clonk)
|
|
{
|
|
dir = DIR_Left;
|
|
if (dir != olddir)
|
|
{
|
|
olddir = dir;
|
|
|
|
// If the animation is playing for the other direction, turn back from where it already is in the animation.
|
|
var animstart = 0;
|
|
if (GetAnimationPosition(turn_anim) != GetAnimationLength("TurnRight"))
|
|
animstart = GetAnimationLength("TurnRight") - GetAnimationPosition(turn_anim);
|
|
|
|
turn_anim = PlayAnimation("TurnLeft", 5, Anim_Linear(animstart, 0, GetAnimationLength("TurnLeft"), Max(36 - (animstart * 204617 / 10000000), 1), ANIM_Hold));
|
|
}
|
|
}
|
|
|
|
func ControlRight(object clonk)
|
|
{
|
|
dir = DIR_Right;
|
|
if (dir != olddir)
|
|
{
|
|
olddir = dir;
|
|
|
|
// If the animation is playing for the other direction, turn back from where it already is in the animation.
|
|
var animstart = 0;
|
|
if (GetAnimationPosition(turn_anim) != GetAnimationLength("TurnLeft"))
|
|
animstart = GetAnimationLength("TurnLeft") - GetAnimationPosition(turn_anim);
|
|
|
|
turn_anim = PlayAnimation("TurnRight", 5, Anim_Linear(animstart, 0, GetAnimationLength("TurnRight"), Max(36 - (animstart * 204617 / 10000000), 1), ANIM_Hold));
|
|
}
|
|
}
|
|
|
|
public func TurnLeft()
|
|
{
|
|
// Instantly set catapult orientation to face left
|
|
var len = GetAnimationLength("TurnLeft");
|
|
turn_anim = PlayAnimation("TurnLeft", 5, Anim_Linear(len, 0, len, 1, ANIM_Hold));
|
|
dir = olddir = DIR_Left;
|
|
return true;
|
|
}
|
|
|
|
public func HoldingEnabled() { return true; }
|
|
|
|
public func ControlUseStart(object clonk)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public func ControlUseHolding(object clonk, int x, int y)
|
|
{
|
|
var power = DefinePower(x, y);
|
|
DoArmAnimation(power);
|
|
ShowTrajectory(power);
|
|
return true;
|
|
}
|
|
|
|
public func ControlUseStop(object clonk, int x, int y)
|
|
{
|
|
DoFire(clonk, DefinePower(x,y));
|
|
return true;
|
|
}
|
|
|
|
public func ControlUseCancel(object clonk)
|
|
{
|
|
Trajectory->Remove(this);
|
|
return true;
|
|
}
|
|
|
|
public func ContainedUseStart(object clonk, int x, int y)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
public func ContainedUseHolding(object clonk, int x, int y)
|
|
{
|
|
var power = DefinePower(x, y, CATAPULT_MaxPower_SelfLaunch);
|
|
DoArmAnimation(power);
|
|
ShowTrajectory(power);
|
|
return true;
|
|
}
|
|
|
|
public func ContainedUseStop(object clonk, int x, int y)
|
|
{
|
|
DoFire(clonk, DefinePower(x, y, CATAPULT_MaxPower_SelfLaunch));
|
|
return true;
|
|
}
|
|
|
|
public func ContainedUseCancel(object clonk)
|
|
{
|
|
Trajectory->Remove(this);
|
|
return true;
|
|
}
|
|
|
|
|
|
/*-- Shooting --*/
|
|
|
|
// Define the catapult power according to where the player aims.
|
|
// Power will try to match such that the trajectory goes through (x, y).
|
|
public func DefinePower(int x, int y, int max_power)
|
|
{
|
|
if (max_power == nil)
|
|
max_power = CATAPULT_MaxPower;
|
|
var min_power = 20;
|
|
// Correct coordinates for the projectile exit.
|
|
var exit = GetProjectileExit();
|
|
x -= exit[0];
|
|
y -= exit[1];
|
|
// Ensure max_power if aiming above the diagonal line.
|
|
if (y + (2 * dir - 1) * x <= 0)
|
|
return max_power;
|
|
// Calculate the exit speed using a 45 degree angle.
|
|
var vsquared = x**2 * GetGravity() / (y + (2 * dir - 1) * x);
|
|
if (vsquared < 0)
|
|
return min_power;
|
|
return BoundBy(Sqrt(vsquared), min_power, max_power);
|
|
}
|
|
|
|
public func DoArmAnimation(int power)
|
|
{
|
|
SetAnimationPosition(aim_anim, Anim_Const(759 - (power * 759 / 100)));
|
|
return;
|
|
}
|
|
|
|
public func ShowTrajectory(int power)
|
|
{
|
|
var exit = GetProjectileExit();
|
|
var x = GetX() + exit[0];
|
|
var y = GetY() + exit[1];
|
|
var angle = exit[2] + GetR();
|
|
var xdir = Sin(angle, power);
|
|
var ydir = -Cos(angle, power);
|
|
Trajectory->Create(this, x, y, xdir, ydir);
|
|
return;
|
|
}
|
|
|
|
protected func DoFire(object clonk, int power)
|
|
{
|
|
// Play the fire animation.
|
|
aim_anim = PlayAnimation("ArmPosition", 1, Anim_Linear(GetAnimationPosition(aim_anim), 0, GetAnimationLength("ArmPosition"), 3, ANIM_Hold));
|
|
|
|
// Sound.
|
|
Sound("Objects::Catapult_Launch");
|
|
|
|
// Remove trajectory display.
|
|
Trajectory->Remove(this);
|
|
|
|
// The clonk will be the projectile if he sits in the catapult.
|
|
var projectile;
|
|
if (clonk && (clonk->Contained() == this))
|
|
projectile = clonk;
|
|
// If clonk isn't sitting in there, shoot stuff placed into the catapult
|
|
if (!projectile)
|
|
projectile = Contents(0);
|
|
// Otherwise, fire what is in the clonk's hand.
|
|
if (!projectile)
|
|
projectile = clonk->GetHandItem(0);
|
|
|
|
// Don't do anything further if there is no projectile.
|
|
if (!projectile)
|
|
return;
|
|
|
|
// Find the spot of the catapult's arm depending on rotation.
|
|
var exit = GetProjectileExit();
|
|
var x = exit[0];
|
|
var y = exit[1];
|
|
var angle = exit[2] + GetR();
|
|
|
|
projectile->Exit();
|
|
// Put the ammo at the catapult's arm.
|
|
projectile->SetPosition(GetX() + x, GetY() + y);
|
|
|
|
// Special behavior for crew members.
|
|
if (projectile->GetOCF() & OCF_CrewMember)
|
|
{
|
|
CatapultDismount(projectile);
|
|
projectile->SetAction("Tumble");
|
|
|
|
// Make sure the Clonk can't shoot itself into solid material.
|
|
if (projectile->Stuck())
|
|
{
|
|
// First, try to just put the Clonk a few pixels lower - might still look okay in some situations.
|
|
for (var i = 0; i <= 4; ++i)
|
|
{
|
|
projectile->SetPosition(projectile->GetX(), projectile->GetY() + 2);
|
|
if (!projectile->Stuck())
|
|
break;
|
|
}
|
|
// Then as a safeguard, just place the Clonk at the catapult's feet and do nothing.
|
|
if (projectile->Stuck())
|
|
{
|
|
projectile->SetPosition(GetX(), GetY());
|
|
// Still stuck? Then we don't actually care if stuck here or at the end of the arm.
|
|
if (projectile->Stuck())
|
|
{
|
|
// Go back to normal shooting position.
|
|
projectile->SetPosition(GetX() + x, GetY() + y);
|
|
}
|
|
else
|
|
{
|
|
// We set the Clonk back down on the ground. This is not a normal shot.
|
|
angle = 0;
|
|
power = 20;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set the speed of the projectile.
|
|
projectile->SetVelocity(angle + GetR(), power);
|
|
return;
|
|
}
|
|
|
|
private func GetProjectileExit()
|
|
{
|
|
var xdir = 1;
|
|
if (dir == DIR_Left)
|
|
xdir = -1;
|
|
var x = 8 * xdir;
|
|
var y = -28;
|
|
var angle = 45 * xdir;
|
|
return [x, y, angle];
|
|
}
|
|
|
|
public func ActivateEntrance(object clonk)
|
|
{
|
|
var cnt = ObjectCount(Find_Container(this), Find_OCF(OCF_CrewMember));
|
|
if (cnt > 0)
|
|
{
|
|
if (clonk->Contained() == this)
|
|
{
|
|
CatapultDismount(clonk);
|
|
clonk->Exit();
|
|
}
|
|
return;
|
|
}
|
|
if (cnt == 0)
|
|
{
|
|
clonk->Enter(this);
|
|
SetOwner(clonk->GetController());
|
|
clonkmesh = AttachMesh(clonk,"shot","skeleton_body",Trans_Mul(Trans_Rotate(180, 1, 0, 0), Trans_Translate(-3000, 1000, 0)), AM_DrawBefore);
|
|
clonk->PlayAnimation("CatapultSit", CLONK_ANIM_SLOT_Movement, Anim_Const(0), Anim_Const(1000));
|
|
DoArmAnimation(CATAPULT_MaxPower_SelfLaunch);
|
|
}
|
|
return;
|
|
}
|
|
|
|
public func CatapultDismount(object clonk)
|
|
{
|
|
clonk->StopAnimation(clonk->GetRootAnimation(CLONK_ANIM_SLOT_Movement));
|
|
DetachMesh(clonkmesh);
|
|
clonkmesh = nil;
|
|
return true;
|
|
}
|
|
|
|
|
|
/* Register enemy spawn with catapult */
|
|
|
|
func Definition(proplist def)
|
|
{
|
|
def.PictureTransformation = Trans_Mul(Trans_Translate(-1000, -4000, 0), Trans_Rotate(-20, 1, 0, 0), Trans_Rotate(35, 0, 1, 0));
|
|
var spawn_editor_props = { Type="proplist", Name=def->GetName(), EditorProps= {
|
|
Gunner = new EnemySpawn->GetAICreatureEditorProps(EnemySpawn->GetAIClonkDefaultPropValues("Firestone"), "$NoGunnerHelp$") { Name="$Gunner$", EditorHelp="$GunnerHelp$" },
|
|
} };
|
|
var spawn_default_values = {
|
|
Gunner = { Type="Clonk", Properties=EnemySpawn->GetAIClonkDefaultPropValues("Firestone") },
|
|
};
|
|
EnemySpawn->AddEnemyDef("Catapult",
|
|
{ SpawnType=Catapult,
|
|
SpawnFunction=def.SpawnCatapult,
|
|
OffsetAttackPathByPos=false,
|
|
GetInfoString=def.GetSpawnInfoString },
|
|
spawn_default_values,
|
|
spawn_editor_props);
|
|
}
|
|
|
|
private func SpawnCatapult(array pos, proplist enemy_data, proplist enemy_def, array attack_path, object spawner)
|
|
{
|
|
// First spawn the catapult
|
|
var catapult = CreateObjectAbove(Catapult, pos[0], pos[1], g_enemyspawn_player);
|
|
catapult->Unstick(10);
|
|
if (!catapult) return;
|
|
// Next let a clonk steer the catapult
|
|
var clonk = EnemySpawn->SpawnAICreature(enemy_data.Gunner, pos, enemy_def, attack_path, spawner);
|
|
if (!clonk) return;
|
|
clonk->SetAction("Push", catapult);
|
|
// Set attack mode
|
|
AI->SetVehicle(clonk, catapult);
|
|
// Only the clonk is an actual enemy
|
|
return clonk;
|
|
}
|
|
|
|
private func GetSpawnInfoString(proplist enemy_data)
|
|
{
|
|
// Prepend balloon to clonk info string
|
|
return Format("{{Catapult}}%s", EnemySpawn->GetAICreatureInfoString(enemy_data.Gunner));
|
|
}
|
|
|
|
/* Properties */
|
|
|
|
local Name = "$Name$";
|
|
local Description = "$Description$";
|
|
local Touchable = 1;
|
|
local BorderBound = C4D_Border_Sides;
|
|
local ContactCalls = true;
|
|
local Components = {Metal = 1, Wood = 6};
|
|
local HitPoints = 100;
|
|
|
|
local ActMap = {
|
|
Roll = {
|
|
Prototype = Action,
|
|
Name = "Roll",
|
|
Procedure = DFA_NONE,
|
|
Directions = 2,
|
|
//FlipDir = 1,
|
|
Length = 50,
|
|
Delay = 2,
|
|
X = 0,
|
|
Y = 0,
|
|
Wdt = 22,
|
|
Hgt = 16,
|
|
NextAction = "Roll",
|
|
},
|
|
};
|