openclonk/planet/Objects.ocd/Environment.ocd/Disasters.ocd/Meteor.ocd/Script.c

268 lines
6.5 KiB
C

/**
Meteor
A burning rock falling from the sky, explodes on impact.
@author Maikel, Zapper
*/
// A meteor can spawn objects on impact.
local spawn_id, spawn_amount;
/*-- Disaster Control --*/
/**
Enables meteorites.
The meteorites can be set to spawn objects on impact. The amount to spawn will be spawn_amount randomized by 50%.
*/
public func SetChance(int chance, id spawn_id, int spawn_amount)
{
spawn_amount = spawn_amount ?? 1;
if (GetType(this) != C4V_Def) return FatalError("Must be called as a definition call.");
var effect = FindMeteoriteEffectFor(spawn_id);
if (!effect)
effect = AddEffect("IntMeteorControl", nil, 100, 20, nil, this, spawn_id, spawn_amount);
effect.chance = chance;
return true;
}
/**
Returns the chance of meteorites that is currently set. spawn_id can be nil for the normal meteorite.
*/
public func GetChance(id spawn_id)
{
if (GetType(this) != C4V_Def) return FatalError("Must be called as a definition call.");
var effect = FindMeteoriteEffectFor(spawn_id);
if (effect)
return effect.chance;
return 0;
}
// Finds the meteorite effect that matches a specific spawn id.
private func FindMeteoriteEffectFor(id spawn_id)
{
var i = 0, fx;
while (fx = GetEffect("IntMeteorControl", nil, i++))
{
if (fx.spawn_id == spawn_id) return fx;
}
return nil;
}
private func FxIntMeteorControlStart(object target, effect fx, temp, id spawn_id, int spawn_amount)
{
if (temp) return;
fx.spawn_id = spawn_id;
fx.spawn_amount = spawn_amount;
}
private func FxIntMeteorControlTimer(object target, effect fx, int time)
{
if (Random(100) < fx.chance && !Random(10))
{
// Launch a meteor.
var x = Random(LandscapeWidth());
var y = 0;
var size = RandomX(60, 90);
var xdir = RandomX(-22, 22);
var ydir = RandomX(28, 36);
var real_spawn_amount = Max(1, RandomX(fx.spawn_amount / 2, 3 * fx.spawn_amount / 2));
LaunchMeteor(x, y, size, xdir, ydir, fx.spawn_id, real_spawn_amount);
}
return FX_OK;
}
// Scenario saving
public func FxIntMeteorControlSaveScen(obj, fx, props)
{
props->Add("Meteor", "Meteor->SetChance(%d, %v, %d)", fx.chance, fx.spawn_id, fx.spawn_amount);
return true;
}
/*-- Meteor --*/
public func Launch(int x, int y, int size, int xdir, int ydir, id spawn_id, int spawn_amount)
{
// Launch from indicated position.
SetPosition(x, y);
// Set the meteor's size.
SetCon(BoundBy(size, 20, 100));
// Set the initial velocity.
SetXDir(xdir);
SetYDir(ydir);
// Remember spawning information.
this.spawn_id = spawn_id;
this.spawn_amount = spawn_amount ?? 1;
// Safety check.
if (!IsLaunchable())
return false;
// Allow for some more effects (overloadable).
this->OnAfterLaunch();
return this;
}
public func OnAfterLaunch()
{
// Set random rotation.
SetR(Random(360));
SetRDir(RandomX(-10, 10));
// Emit light
SetLightRange(300, 30);
SetLightColor(RGB(255, 160, 120));
// Set right action.
AddEffect("IntMeteor", this, 100, 1, this);
}
private func IsLaunchable()
{
if (GBackSemiSolid() || Stuck())
{
RemoveObject();
return false;
}
return true;
}
private func FxIntMeteorStart(object target, effect fx, bool temp)
{
if (temp) return;
fx.smoketrail =
{
R = 255,
B = PV_KeyFrames(0, 0,100, 30,0, 100,255, 1000,255),
G = PV_KeyFrames(0, 0,150, 30,0, 100,255, 1000,255),
Alpha = PV_KeyFrames(1000, 0, 0, 30, 255, 1000, 0),
Size = PV_Linear(20,60),
Stretch = 1000,
Phase = PV_Random(0,4),
Rotation = PV_Random(-GetR() - 15, -GetR() + 15),
DampingX = 1000,
DampingY = 1000,
BlitMode = 0,
CollisionVertex = 0,
OnCollision = PC_Stop(),
Attach = nil
};
fx.brighttrail =
{
Prototype = fx.smoketrail,
Alpha = PV_Linear(180,0),
Size = PV_Linear(20,30),
BlitMode = GFX_BLIT_Additive,
};
fx.frontburn =
{
R = 255,
B = 50,
G = 190,
Alpha = PV_KeyFrames(0, 0, 0, 500, 25, 1000, 0),
Size = PV_Linear(4,5),
Stretch = 1000,
Phase = PV_Random(0,4),
Rotation = PV_Random(-GetR() - 15, -GetR() + 15),
DampingX = 1000,
DampingY = 1000,
BlitMode = GFX_BLIT_Additive,
CollisionVertex = 0,
OnCollision = PC_Stop(),
Attach = ATTACH_Front | ATTACH_MoveRelative
};
}
protected func FxIntMeteorTimer(object target, effect fx, bool temp)
{
var size = GetCon();
// Air drag.
var ydir = GetYDir(100);
ydir -= size * ydir ** 2 / 11552000; // Magic number.
SetYDir(ydir, 100);
// Smoke trail.
CreateParticle("SmokeThick", 0, 0, PV_Random(-3, 3), PV_Random(-3, 3), 200, fx.smoketrail, 5);
// Flash
CreateParticle("SmokeThick", 0, -4, PV_Random(-3, 3), PV_Random(-3, 3), 3, fx.brighttrail, 2);
// left and right burn
CreateParticle("FireDense", PV_Random(-5, 5), 15, PV_Random(-5, -20), PV_Random(-15, -30), 20, fx.frontburn, 30);
CreateParticle("FireDense", PV_Random(-5, 5), 15, PV_Random(5, 20), PV_Random(-15, -30), 20, fx.frontburn, 30);
// Sound.
// Burning and friction decrease size.
if (size > 10 && !Random(5))
DoCon(-1);
return FX_OK;
}
// Scenario saving
func FxIntMeteorSaveScen(obj, fx, props)
{
props->AddCall("Meteor", obj, "AddEffect", "\"IntMeteor\"", obj, 100, 1, obj);
return true;
}
protected func Hit(int xdir, int ydir)
{
var size = 10 + GetCon();
var speed2 = 20 + (xdir ** 2 + ydir ** 2) / 10000;
// Some fire sparks.
var particles =
{
Prototype = Particles_Fire(),
Attach = nil
};
CreateParticle("Fire", PV_Random(-size / 4, size / 4), PV_Random(-size / 4, size / 4), PV_Random(-size/4, size/4), PV_Random(-size/4, size/4), 30, particles, 20 + size);
// Explode meteor, explode size scales with the energy of the meteor.
var dam = size * speed2 / 500;
dam = BoundBy(size/2, 5, 30);
// Blow up a dummy object so that the explosion can happen before we spawn items.
var dummy = CreateObject(Dummy, 0, 0, GetController());
dummy->Explode(dam);
// Fling around some objects?
if (spawn_id)
{
for (var i = 0; i < spawn_amount; ++i)
{
var angle = Angle(0, 0, -xdir, -ydir) + RandomX(-45, 45);
var force = BoundBy(GetCon() / 2, 10, 50) + RandomX(-10, 10);
var item = CreateObject(spawn_id, 0, 0, GetOwner());
item->SetSpeed(Sin(angle, force), -Cos(angle, force));
}
}
RemoveObject();
return;
}
/*-- Target --*/
public func OnLightningStrike(object lightning, int damage)
{
SetController(lightning->GetController());
if (GetDamage() + damage >= 6)
Hit();
return;
}
public func IsProjectileTarget(object projectile)
{
return true;
}
public func OnProjectileHit(object projectile)
{
SetController(projectile->GetController());
Hit();
return;
}
public func IsMeteor() { return true; }
/*-- Proplist --*/
local Name = "$Name$";