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

302 lines
7.4 KiB
C

/**
Rockfall
Big pieces of rock that fall down cliffs.
@author Maikel
*/
/*-- Disaster Control --*/
public func SetChance(int chance)
{
if (this != Rockfall)
return;
var effect = GetEffect("IntRockfallControl");
if (!effect)
effect = AddEffect("IntRockfallControl", nil, 100, 20, nil, Rockfall);
effect.chance = chance;
return;
}
public func GetChance()
{
if (this != Rockfall)
return;
var effect = GetEffect("IntRockfallControl");
if (effect)
return effect.chance;
return;
}
public func SetArea(proplist rect)
{
if (this != Rockfall)
return;
var effect = GetEffect("IntRockfallControl");
if (effect)
effect.area = rect;
return;
}
public func SetExplosiveness(int explosiveness)
{
if (this != Rockfall)
return;
var effect = GetEffect("IntRockfallControl");
if (!effect)
return;
effect.explosiveness = explosiveness;
return;
}
// Sets the spawn distance from crew members.
public func SetSpawnDistance(int dist)
{
if (this != Rockfall)
return;
var effect = GetEffect("IntRockfallControl");
if (!effect)
return;
effect.spawn_distance = dist;
return;
}
protected func FxIntRockfallControlTimer(object target, proplist effect, int time)
{
if (Random(100) < effect.chance && !Random(6))
{
// Attempt to find a suitable location for the rock to be created.
var x, y;
var max_tries = 500;
for (var i = 0; i < max_tries; i++)
{
var x = Random(LandscapeWidth());
var y = 0;
if (effect.area)
{
x = effect.area.x + Random(effect.area.w);
y = effect.area.y + Random(effect.area.h);
}
if (effect.spawn_distance)
{
var dist = (max_tries - i) * effect.spawn_distance / max_tries;
if (FindObject(Find_OCF(OCF_CrewMember), Find_Distance(dist, x, y)))
continue;
}
break;
}
// Explosive rocks demanded?
var explosive = effect.explosiveness && Random(100) < effect.explosiveness;
// Launch rockfall of varying sizes between 40 and 120.
LaunchRockfall(x, y, 80 + Random(41), RandomX(-2, 2), RandomX(4, 8), explosive);
}
return FX_OK;
}
// Scenario saving through an effect call.
public func FxIntRockfallControlSaveScen(obj, fx, props)
{
props->Add("Rockfall", "Rockfall->SetChance(%d)", fx.chance);
props->Add("Rockfall", "Rockfall->SetArea(%v)", fx.area);
if (fx.explosiveness)
props->Add("Rockfall", "Rockfall->SetExplosiveness(%d)", fx.explosiveness);
if (fx.spawn_distance)
props->Add("Rockfall", "Rockfall->SetSpawnDistance(%d)", fx.spawn_distance);
return true;
}
// Launches an earthquake with epicenter (x,y).
global func LaunchRockfall(int x, int y, int size, int xdir, int ydir, bool explosive)
{
// The rockfall size is constrained between 40 and 120%.
size = BoundBy(size, 40, 120);
// Rockfall should be launched in the free.
if (GBackSemiSolid(x, y))
return false;
// Create rock and adjust its size.
var rock = CreateObject(Rockfall, x, y);
rock->SetCon(size);
// Remove rock if stuck.
if (rock->Stuck())
return rock->RemoveObject();
// Set speed and rotation.
rock->SetXDir(xdir);
rock->SetYDir(ydir);
rock->SetR(Random(360));
rock->SetRDir(RandomX(-6, 6));
// Make explosive
if (explosive)
rock->MakeExplosive();
return true;
}
/*-- Rockfall --*/
local damage, is_explosive;
protected func Construction()
{
damage = 0;
this.MeshTransformation = Trans_Scale(2000, 2000, 2000);
// Add an effect for rolling then the rock is just lying around.
AddEffect("IntRockMovement", this, 100, 4, this);
return;
}
public func MakeExplosive()
{
is_explosive = true;
SetClrModulation(0xffff0000);
return true;
}
protected func FxIntRockRollStart(object target, proplist effect, int temporary)
{
if (temporary)
return 1;
effect.damtime = 0;
return 1;
}
protected func FxIntRockMovementTimer(object target, proplist effect, int time)
{
// If rock is not moving give it a kick.
if (GetXDir() == 0 || GetYDir() == 0)
{
SetXDir(RandomX(-160, 160), 100);
SetYDir(-60 - Random(60), 100);
SetRDir(GetRDir() + RandomX(-2, 2));
}
// Damage rock every 120 frames to make sure it breaks at some point.
effect.damtime += 4;
if (effect.damtime > 120)
{
effect.damtime = 0;
damage++;
}
return 1;
}
protected func Hit(int dx, int dy)
{
// Acid kills rockfall
if (GetMaterialVal("Corrosive", "Material", GetMaterial()))
{
Sound("Liquids::Pshshsh");
var sz = Max(GetCon()/10, 5);
var particles = new Particles_Dust() { Size = sz*3, };
if (is_explosive)
{ particles.R = 200; particles.G =100; particles.B = 0; }
else
{ particles.R = 100; particles.G =200; particles.B = 100; }
CreateParticle("Dust", PV_Random(-sz, sz), PV_Random(-sz, sz), PV_Random(-3, 3), PV_Random(-3, -3), PV_Random(36, 2 * 36), particles, sz/2);
return RemoveObject();
}
// Determine caused damage to this rock by impact.
damage += Distance(dx, dy, 0, 0) / 100;
if (damage > 12)
return SplitRock();
// Rebounce or crack on downward hit.
if (dy > 0)
{
SetXDir(dx + RandomX(-160, 160), 100);
SetYDir(Min(-60, -7 * dy / 10) - Random(60), 100);
SetRDir(GetRDir() + RandomX(-2, 2));
}
// Some particles and smoke.
if (GetMaterial(0, 9 * GetCon() / 100))
{
var clr = GetAverageTextureColor(GetTexture(0, 9 * GetCon() / 100));
var particles =
{
Prototype = Particles_Dust(),
R = (clr >> 16) & 0xff,
G = (clr >> 8) & 0xff,
B = clr & 0xff,
Size = 4,
};
CreateParticle("Dust", PV_Random(-2, 2), 8 * GetCon() / 100, PV_Random(-3, 3), PV_Random(-2, -3), PV_Random(36, 2 * 36), particles, 2);
}
// Fling living beings near impact point for a big hit.
if (GetCon() > 80 && dy > 60)
{
for (var obj in FindObjects(Find_NoContainer(), Find_OCF(OCF_Alive), Find_InRect(-16, -8, 32, 24)))
{
if (!Random(3) || !obj->GetAction())
continue;
var act = obj.ActMap[obj->GetAction()];
if (act.Attach || (act.Procedure && act.Procedure != DFA_FLIGHT && act.Procedure != DFA_LIFT && act.Procedure != DFA_FLOAT && act.Procedure != DFA_ATTACH && act.Procedure != DFA_CONNECT))
obj->Fling(Random(3) - 1);
}
}
// Sound.
Sound("Environment::Disasters::EarthquakeEnd", nil, 3 * GetCon() / 2);
StonyObjectHit(dx, dy);
return;
}
private func SplitRock()
{
var con = GetCon(), erock;
// Explosive rocks do some damage
if (is_explosive)
if (erock = CreateObjectAbove(Rock, 0, 4, GetController()))
erock->Explode(Max(15 * con / 100, 3));
// Split the rock into smaller ones if it is big enough.
if (con > 40)
{
while (con > 0)
{
var rock = CreateObjectAbove(Rockfall);
var rock_con = Max(30, GetCon() / 2 + RandomX(-20, 20));
rock->SetCon(rock_con);
con -= 2 * rock_con / 3;
rock->SetXDir(RandomX(-100, 100), 100);
rock->SetYDir(RandomX(-200, -100), 100);
rock->SetRDir(RandomX(-6, 6));
if (is_explosive)
rock->MakeExplosive();
}
}
// Some particles.
var rock_explode =
{
Size = PV_KeyFrames(0, 180, 25, 1000, 50),
DampingY = PV_Random(890, 920, 5),
DampingX = PV_Random(900, 930, 5),
ForceY=-1,
ForceX = PV_Wind(20, PV_Random(-2, 2)),
Rotation=PV_Random(0,360,0),
R=PV_KeyFrames(0, 0, 255, 260, 64, 1000, 64),
G=PV_KeyFrames(0, 0, 128, 260, 64, 1000, 64),
B=PV_KeyFrames(0, 0, 0, 260, 108, 1000, 108),
Alpha = PV_KeyFrames(0, 0, 0, 100, 20, 500, 20, 1000, 0)
};
CreateParticle("SmokeDirty", PV_Random(-5, 5), PV_Random(-5, 5), 0, PV_Random(-2, 0), PV_Random(50, 100), rock_explode, 8);
// Some sound effects.
Sound("Environment::Disasters::EarthquakeEnd", nil, 100);
RemoveObject();
return;
}
/*-- Proplist --*/
local Name = "$Name$";