openclonk/planet/Objects.ocd/Animals.ocd/Mooq.ocd/Script.c

925 lines
17 KiB
C

/**
Mooq
Author: jok
*/
// Animations
local turn_angle;
// Color
local color;
// The closest enemy that has been found.
local enemy;
// Closest food that has been found.
local food;
public func Place(int amount, proplist rectangle, proplist settings)
{
var max_tries = 3 * amount;
var loc_area = nil;
if (rectangle) loc_area = Loc_InArea(rectangle);
var animal;
while ((amount > 0) && (--max_tries > 0))
{
// Try to find walkable ground near lava.
var lava_spot = FindLocation(Loc_Material("DuroLava"));
var ground_spot = nil;
if (lava_spot)
{
var lava_rectangle = Shape->Rectangle(lava_spot.x - 200, lava_spot.y - 200, 400, 400);
// Make sure the position is inside the required target rectangle.
lava_rectangle = Shape->Intersect(lava_rectangle, rectangle)->GetBoundingRectangle();
ground_spot = FindLocation(Loc_Wall(CNAT_Bottom), Loc_Or(Loc_Sky(), Loc_Tunnel()), Loc_Space(20, CNAT_Top), Loc_InArea(lava_rectangle));
}
// If no hip and cool spot found, just get some generic spot.
if (!ground_spot) ground_spot = FindLocation(Loc_Wall(CNAT_Bottom), Loc_Or(Loc_Sky(), Loc_Tunnel()), Loc_Space(20, CNAT_Top), loc_area);
if (!ground_spot) continue;
animal = CreateObjectAbove(this, ground_spot.x, ground_spot.y, NO_OWNER);
if (!animal) continue;
if (animal->Stuck())
{
animal->RemoveObject();
continue;
}
--amount;
}
return animal;
}
func Construction()
{
turn_angle = -60;
color = 255;
AddEffect("IntActivity", this, 1, 10, this);
AddTimer("UpdateEnemy", 30);
AddTimer("UpdateFood", 60);
Stop();
CheckTurn(GetDir());
SetTailOnFire();
}
func Death()
{
RemoveTimer("UpdateEnemy");
RemoveTimer("UpdateFood");
RemoveEffect("IntActivity", this);
Sound("Animals::Mooq::Die*");
}
func CatchBlow(int damage, object from)
{
Schedule(this, "Sound(\"Animals::Mooq::Hurt*\")", RandomX(5, 20));
}
/* Action Callbacks */
func CheckStuck()
{
// Prevents getting stuck on middle vertex
if(!GetXDir())
if(Abs(GetYDir()) < 5)
if(GBackSolid(0, 3))
SetPosition(GetX(), GetY() - 1);
}
func ClearActivity()
{
this.Activity = nil;
}
func StartSwim()
{
this.Activity = this.ActivitySwimming;
}
func StartWalk()
{
SetTailOnFire();
this.Activity = this.ActivityWalking;
}
func SpitPhase()
{
if(GetActTime() > 45 && GetActTime() < 65)
{
if(!Random(4))
{
var iX, iY;
iX = 5;
if (!GetDir()) iX = -iX;
iY = -4;
Smoke(iX,iY,5);
}
}
if(GetActTime() == 58)
{
var iX, iY, iXDir, iYDir;
iX = 10;
if (!GetDir()) iX = -iX;
iY = -4;
iXDir = 300;
if (!GetDir()) iXDir = -iXDir;
iYDir = -300;
Sound("Animals::Mooq::Spit*");
var obj = CreateContents(Mooq_Firebomb);
obj->Exit(iX, iY);
obj->SetXDir(iXDir,100);
obj->SetYDir(iYDir,100);
}
}
func EatPhase()
{
var actt = GetActTime();
if(actt > 13 && actt < 25)
{
if(!Random(4))
{
var iX, iY;
iX = 5;
if (!GetDir()) iX = -iX;
iY = 4;
Smoke(iX,iY,5);
}
}
if(actt == 22)
{
if (!food) return;
Sound("Animals::Mooq::Munch*");
DoEnergy(food->GetMass());
food->RemoveObject();
}
if(actt > 43 && actt < 55)
{
if(!Random(4))
{
var iX, iY;
iX = 5;
if (!GetDir()) iX = -iX;
iY = 1;
Smoke(iX,iY,5);
}
}
}
/* Activity */
func ActivitySwimming()
{
CheckBreathe();
if(GetMaterial() == Material("Water"))
CheckFossilize();
// Stuck?
if (GetComDir() && !GetXDir() && !GetYDir())
{
DoJump(true);
return Swim(Random(2));
}
// Fossilizing?
if(GetEffect("IntFossilizing", this))
{
var lava_spot = FindLava();
if (lava_spot)
return TaskSwimTo(lava_spot);
}
if (enemy && Random(2)) return TaskSwimTo(enemy);
if (!enemy && food) return TaskSwimTo(food);
if (!Random(6)) return Swim(Random(2));
if (!Random(6)) return DoJump(true);
}
func ActivityWalking()
{
// Stuck?
if (GetComDir() && !GetXDir() && !GetYDir())
{
if(GetDir())
Walk(0);
else
Walk(1);
return DoJump();
}
// Fossilizing?
if(GetEffect("IntFossilizing", this))
{
var lava_spot = FindLava();
if (lava_spot) return TaskWalkTo(lava_spot);
}
// If enemy, hopefully attack!
if (enemy)
{
var distance = ObjectDistance(this, enemy);
if (distance < 50 && Random(2)) return TaskHeadbutt(distance);
if (Inside(distance, 85, 145) && !Random(5)) return TaskSpit();
}
// If no enemy, go to food and eat.
if (!enemy && food) return TaskFood();
// If not walking, randomly idle.
if (GetAction() == "Stand")
{
if (!enemy && !food)
{
// Idle?
if (!Random(15)) return TaskIdle();
}
if (!Random(5)) return Walk(Random(2));
return;
}
if (!Random(5)) return Stop();
if (!Random(5)) return Walk(Random(2));
if (GetAction() == "Walk")
{
if (!Random(5)) return DoJump();
}
// Anticipate holes in the landscape while walking.
}
func FxIntActivityTimer(target, effect, time)
{
if (this.Activity) this->Activity();
if (!GetAlive()) return -1;
return 1;
}
func FxIntActivityDamage(target, effect, dmg)
{
if (dmg > 0) return dmg;
return dmg;
}
/* Tasks */
func TaskIdle()
{
Sound("Animals::Mooq::Snorting*");
if (!Random(3)) return SetAction("IdleSit");
if (!Random(2)) return SetAction("IdleStand");
return SetAction("IdleTailwave");
}
func TaskFood()
{
var distance = ObjectDistance(this, food);
if (distance < 11) return Eat();
return TaskWalkTo(food);
}
func TaskHeadbutt(distance)
{
if (distance < 11) return Headbutt();
return TaskWalkTo(enemy);
}
func TaskSpit()
{
var eX = enemy->GetX();
var iX = GetX();
if (iX < eX)
{
if (GetDir() != DIR_Right) return Turn(DIR_Right);
Stop();
return Spit();
} else {
if (GetDir() != DIR_Left) return Turn(DIR_Left);
Stop();
return Spit();
}
}
func TaskWalkTo(spot)
{
var iX = GetX();
var iY = GetY();
if (GetType(spot) == C4V_C4Object)
{
var sX = spot->GetX();
var sY = spot->GetY();
}
else if (GetType(spot) == C4V_PropList)
{
var sX = spot.x;
var sY = spot.y;
}
else return;
if (iX < sX)
{
if (GetDir() == DIR_Right)
if (iY > sY + 6)
if (Random(2)) if(DoJump())
return true;
return Walk(DIR_Right);
} else {
if (GetDir() == DIR_Left)
if(iY > sY + 6)
if(Random(2)) if(DoJump())
return true;
return Walk(DIR_Left);
}
}
func TaskSwimTo(spot)
{
var iX = GetX();
var iY = GetY();
if (GetType(spot) == C4V_C4Object)
{
var sX = spot->GetX();
var sY = spot->GetY();
}
else if (GetType(spot) == C4V_PropList)
{
var sX = spot.x;
var sY = spot.y;
}
else return;
if (iX < sX)
{
if (GetDir() == DIR_Right)
if(iY > sY)
if(DoJump(true))
return true;
return Swim(DIR_Right);
} else {
if (GetDir() == DIR_Left)
if(iY > sY)
if(DoJump(true))
return true;
return Swim(DIR_Left);
}
}
/* Actions */
func Stop()
{
SetComDir(COMD_Stop);
SetXDir(0);
return SetAction("Stand");
}
func Turn(int dir, bool move)
{
if (dir == nil)
{
if (GetDir() == DIR_Left)
dir = DIR_Right;
else
dir = DIR_Left;
}
if(GetDir() == dir) return;
return CheckTurn(dir, move);
}
func Walk(int dir)
{
if (GetAction() != "Stand" && GetAction() != "Walk") return;
if (GetDir() == dir)
{
SetAction("Walk");
if (GetDir())
return SetComDir(COMD_Right);
else
return SetComDir(COMD_Left);
}
return Turn(dir, true);
}
func Swim(int dir)
{
if (GetAction() != "Swim") return;
if (GetDir() == dir)
{
SetAction("Swim");
if (GetDir())
return SetComDir(COMD_UpRight);
else
return SetComDir(COMD_UpLeft);
}
return Turn(dir, true);
}
func DoJump(bool swimming)
{
if (GetAction() != "Walk" && GetAction() != "Stand" && GetAction() != "Swim") return;
if (swimming)
{
if (GBackSky(0, -2))
SetPosition(GetX(), GetY() - 2);
else
return;
var iX, iY, iXDir, iYDir;
iX = 10;
if (!GetDir()) iX = -iX;
iY = -4;
iXDir = 200;
if (!GetDir()) iXDir = -iXDir;
iYDir = -200;
if (Random(2)) Sound("Animals::Mooq::Snort*");
SetSpeed(iXDir + GetXDir(100), iYDir + GetYDir(100), 100);
return true;
}
if (Random(2)) Sound("Animals::Mooq::Snort*");
return Jump();
}
func Spit()
{
if (GetAction() == "Stand") return SetAction("Spit");
}
func Eat(object food)
{
Stop();
if (GetAction() == "Stand") return SetAction("Eat");
}
func Headbutt()
{
if (GetAction() != "Walk") return;
Punch(enemy, 10);
return SetAction("Headbutt");
}
/* FindEnemy */
func UpdateEnemy()
{
// Already disposed of the last one?
if (enemy && !enemy->GetAlive()) enemy = nil;
// Last one too far away now?
if (enemy && ObjectDistance(this, enemy) > 250) enemy = nil;
// Slid in water?
if (enemy && enemy->GBackLiquid()) enemy = nil;
var x = GetX();
var y = GetY();
for (var obj in FindObjects(Find_Distance(200), Find_OCF(OCF_Alive), Find_AnimalHostile(GetOwner()), Sort_Distance()))
{
if (!PathFree(x, y, obj->GetX(), obj->GetY())) continue;
if (obj->GBackLiquid()) continue;
enemy = obj;
return;
}
}
/* FindFood */
func UpdateFood()
{
// Need food?
if (GetEnergy() >= MaxEnergy/1000) return food = nil;
// Last one too far away now?
if (food && ObjectDistance(this, food) > 150) food = nil;
// Slid in water?
if (food && food->GBackLiquid()) food = nil;
var x = GetX();
var y = GetY();
var Find_FoodIDs = Find_Or(Find_ID(Rock), Find_ID(Coal), Find_ID(Ore));
for (var obj in FindObjects(Find_Distance(100), Find_FoodIDs, Sort_Distance()))
{
if (!PathFree(x, y, obj->GetX(), obj->GetY())) continue;
if (obj->GBackLiquid()) continue;
food = obj;
return;
}
}
/* FindLava */
func FindLava()
{
var lava_spot = FindLocation(Loc_Material("DuroLava"), Loc_Space(20));
if (lava_spot) return lava_spot;
var lava_spot = FindLocation(Loc_Material("Lava"), Loc_Space(20));
if (lava_spot) return lava_spot;
return;
}
/* Turning */
func CheckTurn(int dir, bool move)
{
if (!GetEffect("IntTurning", this))
{
SetDir(dir);
AddEffect("IntTurning", this, 1, 1, this, nil, move);
return true;
}
return false;
}
func FxIntTurningStart(object target, effect fx, temp, move)
{
fx.mvmn = move;
if (!InLiquid())
{
Stop();
SetAction("Turn");
} else {
SetComDir(COMD_Stop);
SetXDir(0);
SetAction("SwimTurn");
}
if (temp) return true;
}
func FxIntTurningTimer(object target, effect fx, int time)
{
if (GetDir() == DIR_Left)
turn_angle += 15;
else
turn_angle -= 15;
if (turn_angle < -60 || turn_angle > 180)
{
turn_angle = BoundBy(turn_angle, -60, 180);
this.MeshTransformation = Trans_Rotate(turn_angle + 180 + 30,0,1,0);
return -1;
}
this.MeshTransformation = Trans_Rotate(turn_angle + 180 + 30,0,1,0);
return 1;
}
func FxIntTurningStop(object target, effect fx, temp)
{
if (fx.mvmn)
{
if (!InLiquid())
{
if (GetDir()) SetComDir(COMD_Right);
else SetComDir(COMD_Left);
return SetAction("Walk");
}
if (GetDir()) SetComDir(COMD_UpRight);
else SetComDir(COMD_UpLeft);
return SetAction("Swim");
}
}
/* Breathing */
func CheckBreathe()
{
if (!GetEffect("IntBreathing", this)) AddEffect("IntBreathing", this, 1, 1, this, nil);
}
func FxIntBreathingStart(object target, effect fx, temp)
{
if (temp) return true;
}
func FxIntBreathingTimer(object target, effect fx, int time)
{
DoBreath(MaxBreath - GetBreath());
if (!InLiquid()) return -1;
return 1;
}
/* Fossilizing */
func CheckFossilize()
{
if (!GetEffect("IntFossilizing", this))
// Ca. 60 seconds till death without lava
AddEffect("IntFossilizing", this, 1, 11, this, nil);
}
func FxIntFossilizingStart(object target, effect fx, temp)
{
if (temp) return true;
}
func FxIntFossilizingTimer(object target, effect fx, int time)
{
if (GetMaterial() == Material("DuroLava") || GetMaterial() == Material("Lava"))
{
color++;
if (Speed < MaxSpeed) Speed += Random(2);
if (JumpSpeed < MaxJumpSpeed)
{
JumpSpeed += Random(3);
if(JumpSpeed > MaxJumpSpeed) JumpSpeed = MaxJumpSpeed;
}
} else {
color--;
if (Speed > 0) Speed -= Random(2);
if (JumpSpeed > 0)
{
JumpSpeed -= Random(3);
if(JumpSpeed < 0) JumpSpeed = 0;
}
}
if (color < 45 || color > 255)
{
color = BoundBy(color, 45, 255);
SetClrModulation(RGBa(color, color, color, 255));
return -1;
}
SetClrModulation(RGBa(color, color, color, 255));
return 1;
}
func FxIntFossilizingStop(object target, effect fx, temp)
{
if (GetMaterial() == Material("DuroLava") || GetMaterial() == Material("Lava")) return SetAction("Swim");
else return Kill();
}
/* Burning Tail */
func SetTailOnFire()
{
if (!GetEffect("IntTailBurning", this))
AddEffect("IntTailBurning", this, 1, 2, this, nil);
}
func FxIntTailBurningStart(object target, effect fx, temp)
{
fx.fire = {
R = 200 + Random(55),
G = 200 + Random(55),
B = 200 + Random(55),
Alpha = PV_Linear(255, 0),
Size = 4,
Phase = PV_Linear(0, 9),
DampingX = 1000,
DampingY = 1000,
Attach = ATTACH_MoveRelative
};
if (temp) return true;
}
func FxIntTailBurningTimer(object target, effect fx, int time)
{
if (!GetAlive() || InLiquid())
{
var level;
level = 5 ?? 10;
var particles = Particles_Smoke();
particles.Size = PV_Linear(PV_Random(level/2, level), PV_Random(2 * level, 3 * level));
var pos = [3, -1, 0];
var dir = [PV_Random(-level/3, level/3), PV_Random(-level/2, -level/3), 0];
CreateParticleAtBone("Smoke", "tail_3", pos, dir, PV_Random(level * 2, level * 10), particles, BoundBy(level/5, 3, 20));
return -1;
}
var pos = [3, -1, 0];
var dir = [0, 0, 0];
CreateParticleAtBone("Fire", "tail_3", pos, dir, 5, fx.fire, 1);
return 1;
}
/* ActMap */
local ActMap = {
Walk = {
Prototype = Action,
Name = "Walk",
Procedure = DFA_WALK,
Speed = Speed,
Accel = 4,
Decel = 22,
Directions = 2,
FlipDir = 0,
Length = 12,
Delay = 1,
Animation = "Walk",
StartCall = "StartWalk",
InLiquidAction = "Swim",
},
Swim = {
Prototype = Action,
Name = "Swim",
Procedure = DFA_SWIM,
Speed = Speed,
Accel = 16,
Decel = 22,
Directions = 2,
FlipDir = 0,
Length = 12,
Delay = 1,
Animation = "Swim",
StartCall = "StartSwim",
},
Jump = {
Prototype = Action,
Name = "Jump",
Procedure = DFA_FLIGHT,
Speed = 200,
Accel = 14,
Directions = 2,
FlipDir = 0,
Length = 20,
Delay = 1,
Animation = "Jump",
NextAction = "Hold",
PhaseCall = "CheckStuck",
StartCall = "ClearActivity",
InLiquidAction = "Swim",
},
Dead = {
Prototype = Action,
Name = "Dead",
Directions = 2,
FlipDir = 0,
Length = 30,
Delay = 1,
Animation = "Death",
NextAction = "Hold",
StartCall = "ClearActivity",
NoOtherAction = 1,
ObjectDisabled = 1,
},
Stand = {
Prototype = Action,
Name = "Stand",
Procedure = DFA_WALK, //DFA_THROW
Directions = 2,
FlipDir = 0,
Length = 90,
Delay = 1,
Animation = "Stand",
NextAction = "Stand",
StartCall = "StartWalk",
InLiquidAction = "Swim",
},
Turn = {
Prototype = Action,
Name = "Turn",
Procedure = DFA_THROW,
Directions = 2,
FlipDir = 0,
Length = 16,
Delay = 1,
Animation = "Walk",
NextAction = "Stand",
StartCall = "ClearActivity",
},
SwimTurn = {
Prototype = Action,
Name = "SwimTurn",
Procedure = DFA_THROW,
Directions = 2,
FlipDir = 0,
Length = 16,
Delay = 1,
Animation = "Swim",
NextAction = "Swim",
StartCall = "ClearActivity",
},
Spit = {
Prototype = Action,
Name = "Spit",
Procedure = DFA_THROW,
Directions = 2,
FlipDir = 0,
Length = 90,
Delay = 1,
Animation = "SitMouthOpen",
NextAction = "Stand",
PhaseCall = "SpitPhase",
StartCall = "ClearActivity",
InLiquidAction = "Swim",
},
Eat = {
Prototype = Action,
Name = "Eat",
Procedure = DFA_NONE,
Directions = 2,
Length = 60,
Delay = 1,
Animation = "Eat",
PhaseCall = "EatPhase",
StartCall = "ClearActivity",
NextAction = "Stand",
InLiquidAction = "Swim",
Attach=CNAT_Bottom,
},
Headbutt = {
Prototype = Action,
Name = "Headbutt",
Procedure = DFA_THROW,
Directions = 2,
FlipDir = 0,
Length = 12,
Delay = 1,
Animation = "Headbutt",
StartCall = "ClearActivity",
NextAction = "Stand",
InLiquidAction = "Swim",
},
IdleStand = {
Prototype = Action,
Name = "IdleStand",
Procedure = DFA_THROW,
Directions = 2,
FlipDir = 0,
Length = 32,
Delay = 1,
Animation = "IdleStand",
NextAction = "Stand",
InLiquidAction = "Swim",
StartCall = "ClearActivity",
},
IdleSit = {
Prototype = Action,
Name = "IdleSit",
Procedure = DFA_THROW,
Directions = 2,
FlipDir = 0,
Length = 64,
Delay = 1,
Animation = "IdleSit",
NextAction = "Stand",
InLiquidAction = "Swim",
StartCall = "ClearActivity",
},
IdleTailwave = {
Prototype = Action,
Name = "IdleTailwave",
Procedure = DFA_THROW,
Directions = 2,
FlipDir = 0,
Length = 16,
Delay = 1,
Animation = "IdleTailwave",
NextAction = "Stand",
InLiquidAction = "Swim",
StartCall = "ClearActivity",
},
};
local Name = "$Name$";
local Description = "$Description$";
local MaxEnergy = 250000;
local MaxBreath = 720; // Mooq can breathe for 20 seconds under water. // But it haz special effects ignoring MaxBreath.
local MaxSpeed = 100;
local Speed = MaxSpeed;
local MaxJumpSpeed = 300;
local JumpSpeed = MaxJumpSpeed;
local NoBurnDecay = 1;
local BorderBound = C4D_Border_Sides;
local ContactCalls = true;