openclonk/planet/Objects.ocd/Vehicles.ocd/Catapult.ocd/Script.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",
},
};