openclonk/planet/System.ocg/Explode.c

597 lines
17 KiB
C
Raw Normal View History

/*--
Explode.c
Authors: Newton
Everything about the explosion.
TODO: documentation.
--*/
2009-12-29 13:44:16 +00:00
/*--
Particle definitions used by the explosion effect.
They will be initialized lazily whenever the first blast goes off.
--*/
static ExplosionParticles_Smoke;
static ExplosionParticles_Blast;
static ExplosionParticles_BlastSmooth;
static ExplosionParticles_BlastSmoothBackground;
static ExplosionParticles_Star;
static ExplosionParticles_Shockwave;
static ExplosionParticles_Glimmer;
global func ExplosionParticles_Init()
{
ExplosionParticles_Smoke =
{
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)
};
ExplosionParticles_Blast =
{
Size = PV_KeyFrames(0, 0, 0, 260, 25, 1000, 40),
DampingY = PV_Random(890, 920, 0),
DampingX = PV_Random(900, 930, 0),
ForceY = PV_Random(-8,-2,0),
ForceX = PV_Random(-5,5,0),
R = 255,
G = PV_Random(64, 120, 0),
Rotation = PV_Random(0, 360, 0),
B = 0,
Alpha = PV_KeyFrames(0, 260, 100, 1000, 0),
BlitMode = GFX_BLIT_Additive,
Phase = PV_Random(0, 1)
};
ExplosionParticles_BlastSmooth =
{
Size = PV_KeyFrames(0, 0, 0, 250, PV_Random(30, 50), 1000, 80),
R = PV_KeyFrames(0, 0, 255, 250, 128, 1000, 0),
G = PV_KeyFrames(0, 0, 255, 125, 64, 1000, 0),
Rotation = PV_Random(0, 360, 0),
B = PV_KeyFrames(0, 0, 100, 250, 64, 100, 0),
Alpha = PV_KeyFrames(0, 0, 255, 750, 250, 1000, 0),
BlitMode = GFX_BLIT_Additive
};
ExplosionParticles_BlastSmoothBackground =
{
Prototype = ExplosionParticles_BlastSmooth,
BlitMode = nil,
R = PV_Linear(50, 0),
G = PV_Linear(50, 0),
B = PV_Linear(50, 0),
Alpha = PV_Linear(128, 0)
};
ExplosionParticles_Star =
{
Size = PV_KeyFrames(0, 0, 0, 500, 60, 1000, 0),
R = PV_KeyFrames(0, 750, 255, 1000, 0),
G = PV_KeyFrames(0, 300, 255, 1000, 0),
B = PV_KeyFrames(0, 300, 255, 500, 0),
Rotation = PV_Random(0, 360, 4),
Alpha = PV_KeyFrames(0, 750, 255, 1000, 0),
BlitMode = GFX_BLIT_Additive,
Stretch = PV_Speed(1000, 1000)
};
ExplosionParticles_Shockwave =
{
Size = PV_Linear(0, 120),
R = 255,
G = 128,
B = PV_KeyFrames(0, 0, 128, 200, 0),
Rotation = PV_Step(20),
BlitMode = GFX_BLIT_Additive,
Alpha = PV_Linear(255, 0)
};
ExplosionParticles_Glimmer=
{
Size = PV_Linear(2, 0),
ForceY = GetGravity(),
DampingY = PV_Linear(1000,700),
DampingX = PV_Linear(1000,700),
Stretch = PV_Speed(1000, 500),
Rotation = PV_Direction(),
OnCollision = PC_Die(),
CollisionVertex = 500,
R = 255,
G = PV_Linear(128,32),
B = PV_Random(0, 128, 2),
Alpha = PV_Random(255,0,3),
BlitMode = GFX_BLIT_Additive,
};
}
2009-12-29 13:44:16 +00:00
/*-- Explosion --*/
2009-12-29 13:44:16 +00:00
global func Explode(int level, bool silent)
{
2011-09-18 16:16:49 +00:00
if(!this) FatalError("Function Explode must be called from object context");
// Shake the viewport.
ShakeViewPort(level, GetX(), GetY());
// Sound must be created before object removal, for it to be played at the right position.
if(!silent) //Does object use it's own explosion sound effect?
{
var grade = BoundBy(level / 10 - 1, 1, 3);
if(GBackLiquid())
Sound(Format("BlastLiquid%d",grade));
else
Sound(Format("Blast%d", grade));
}
// Explosion parameters.
var x = GetX(), y = GetY();
var cause_plr = GetController();
var container = Contained();
var exploding_id = GetID();
var layer = GetObjectLayer();
// Explosion parameters saved: Remove object now, since it should not be involved in the explosion.
RemoveObject();
// Execute the explosion in global context.
// There is no possibility to interact with the global context, apart from GameCall.
// So at least remove the object context.
exploding_id->DoExplosion(x, y, level, container, cause_plr, layer);
2009-12-29 13:44:16 +00:00
}
global func DoExplosion(int x, int y, int level, object inobj, int cause_plr, object layer)
{
// Container to ContainBlast
var container = inobj;
while (container)
2009-12-29 13:44:16 +00:00
{
if (container->GetID()->GetDefContainBlast())
break;
else
container = container->Contained();
2009-12-29 13:44:16 +00:00
}
// Explosion outside: Explosion effects.
2009-12-29 13:44:16 +00:00
if (!container)
{
// Incinerate oil.
if (!IncinerateLandscape(x, y))
if (!IncinerateLandscape(x, y - 10))
if (!IncinerateLandscape(x - 5, y - 5))
IncinerateLandscape(x + 5, y - 5);
// Graphic effects.
ExplosionEffect(level, x, y);
2010-02-15 15:41:22 +00:00
}
// Damage in the objects, and outside.
BlastObjects(x + GetX(), y + GetY(), level, inobj, cause_plr, layer);
if (inobj != container)
BlastObjects(x + GetX(), y + GetY(), level, container, cause_plr, layer);
// Landscape destruction. Happens after BlastObjects, so that recently blown-free materials are not affected
if (!container)
BlastFree(x, y, level, cause_plr);
2010-02-15 15:41:22 +00:00
return true;
}
2010-02-15 15:41:22 +00:00
/*
Creates a visual explosion effect at a position.
smoothness (in percent) determines how round the effect will look like
*/
global func ExplosionEffect(int level, int x, int y, int smoothness)
2010-02-15 15:41:22 +00:00
{
// possibly init particle definitions?
if (!ExplosionParticles_Blast)
ExplosionParticles_Init();
smoothness = smoothness ?? 0;
var level_pow = level ** 2;
var level_pow_fraction = Max(level_pow / 25, 5 * level);
var wilderness_level = level * (100 - smoothness) / 100;
var smoothness_level = level * smoothness / 100;
var smoke_size = PV_KeyFrames(0, 180, level, 1000, level * 2);
var blast_size = PV_KeyFrames(0, 0, 0, 260, level * 2, 1000, level);
var blast_smooth_size = PV_KeyFrames(0, 0, 0, 250, PV_Random(level, level * 2), 1000, level);
var star_size = PV_KeyFrames(0, 0, 0, 500, level * 2, 1000, 0);
var shockwave_size = PV_Linear(0, level * 4);
CreateParticleEx("SmokeDirty", PV_Random(x - 10,x + 10), PV_Random(y - 10, y + 10), 0, PV_Random(-2, 0), PV_Random(50, 100), {Prototype = ExplosionParticles_Smoke, Size = smoke_size}, Max(2, wilderness_level / 10));
CreateParticleEx("SmokeDirty", PV_Random(x - 5, x + 5), PV_Random(y - 5, y + 5), PV_Random(-1, 1), PV_Random(-1, 1), PV_Random(20, 40), {Prototype = ExplosionParticles_BlastSmoothBackground, Size = blast_smooth_size}, smoothness_level / 5);
CreateParticleEx("SmokeDirty", PV_Random(x - 5, x + 5), PV_Random(y - 5, y + 5), PV_Random(-1, 1), PV_Random(-1, 1), PV_Random(20, 40), {Prototype = ExplosionParticles_BlastSmooth, Size = blast_smooth_size}, smoothness_level / 5);
CreateParticleEx("Dust", PV_Random(x - 5, x + 5), PV_Random(y - 5, y + 5), 0, 0, PV_Random(18, 25), {Prototype = ExplosionParticles_Blast, Size = blast_size}, smoothness_level / 5);
CreateParticleEx("StarFlash", PV_Random(x - 6, x + 6), PV_Random(y - 6, y + 6), PV_Random(-wilderness_level/4, wilderness_level/4), PV_Random(-wilderness_level/4, wilderness_level/4), PV_Random(10, 12), {Prototype = ExplosionParticles_Star, Size = star_size}, wilderness_level / 3);
CreateParticleEx("Shockwave", x, y, 0, 0, 15, {Prototype = ExplosionParticles_Shockwave, Size = shockwave_size}, nil);
// cast either some sparks on land or bubbles under water
if(GBackLiquid(x, y) && Global.CastBubbles)
{
Global->CastBubbles(level * 7 / 10, level, x, y);
}
else
{
CreateParticleEx("Magic", PV_Random(x - 5, x + 5), PV_Random(y - 5, y + 5), PV_Random(-level_pow_fraction, level_pow_fraction), PV_Random(-level_pow_fraction, level_pow_fraction), PV_Random(25, 70), ExplosionParticles_Glimmer, level);
}
// very wild explosion? Smoke trails!
var smoke_trail_count = wilderness_level / 15;
var angle = Random(360);
var failsafe = 0; // against infinite loops
while (smoke_trail_count > 0 && (++failsafe < smoke_trail_count * 10))
{
angle += RandomX(40, 80);
var smokex = Sin(angle, RandomX(level / 4, level / 2));
var smokey = -Cos(angle, RandomX(level / 4, level / 2));
if (GBackSolid(x + smokex, y + smokey))
continue;
var lvl = wilderness_level;
CreateSmokeTrail(lvl, angle, x + smokex, y + smokey);
smoke_trail_count--;
2009-12-29 13:44:16 +00:00
}
return;
2010-02-15 15:41:22 +00:00
}
2009-12-29 13:44:16 +00:00
/*-- Blast objects & shockwaves --*/
2009-12-29 13:44:16 +00:00
// Damage and hurl objects away.
2009-12-29 13:44:16 +00:00
global func BlastObjects(int x, int y, int level, object container, int cause_plr, object layer)
{
var obj;
// Coordinates are always supplied globally, convert to local coordinates.
var l_x = x - GetX(), l_y = y - GetY();
// caused by: if not specified, controller of calling object
if(cause_plr == nil)
if(this)
cause_plr = GetController();
// In a container?
if (container)
{
if (container->GetObjectLayer() == layer)
{
container->BlastObject(level, cause_plr);
if (!container)
return true; // Container could be removed in the meanwhile.
for (obj in FindObjects(Find_Container(container), Find_Layer(layer)))
if (obj)
obj->BlastObject(level, cause_plr);
}
}
else
{
// Object is outside.
// Damage objects at point of explosion.
for (var obj in FindObjects(Find_AtRect(l_x - 5, l_y - 5, 10,10), Find_NoContainer(), Find_Layer(layer)))
if (obj) obj->BlastObject(level, cause_plr);
2009-12-29 13:44:16 +00:00
// TODO: -> Shockwave in own global func(?)
// Hurl objects in explosion radius.
var shockwave_objs = FindObjects(Find_Distance(level, l_x, l_y), Find_NoContainer(), Find_Layer(layer),
Find_Or(Find_Category(C4D_Object|C4D_Living|C4D_Vehicle), Find_Func("CanBeHitByShockwaves")), Find_Func("BlastObjectsShockwaveCheck", x, y));
var cnt = GetLength(shockwave_objs);
if (cnt)
{
// The hurl energy is distributed over the objects.
//Log("Shockwave objs %v (%d)", shockwave_objs, cnt);
var shock_speed = Sqrt(2 * level * level / BoundBy(cnt, 2, 12));
for (var obj in shockwave_objs)
if (obj) // Test obj, cause OnShockwaveHit could have removed objects.
{
// Object has special reaction on shockwave?
if (obj->~OnShockwaveHit(level, x, y))
continue;
// Living beings are hurt more.
var cat = obj->GetCategory();
if (cat & C4D_Living)
{
obj->DoEnergy(level / -2, false, FX_Call_EngBlast, cause_plr);
obj->DoDamage(level / 2, FX_Call_DmgBlast, cause_plr);
}
// Killtracing for projectiles.
if (cat & C4D_Object)
obj->SetController(cause_plr);
// Shockwave.
var mass_fact = 20, mass_mul = 100;
if (cat & C4D_Living)
{
mass_fact = 8;
mass_mul = 80;
}
mass_fact = BoundBy(obj->GetMass() * mass_mul / 1000, 4, mass_fact);
var dx = 100 * (obj->GetX() - x) + Random(51) - 25;
var dy = 100 * (obj->GetY() - y) + Random(51) - 25;
var vx, vy;
if (dx)
vx = Abs(dx) / dx * (100 * level - Abs(dx)) * shock_speed / level / mass_fact;
vy = (Abs(dy) - 100 * level) * shock_speed / level / mass_fact;
if (cat & C4D_Object)
{
// Objects shouldn't move too fast.
var ovx = obj->GetXDir(100), ovy = obj->GetYDir(100);
if (ovx * vx > 0)
vx = (Sqrt(vx * vx + ovx * ovx) - Abs(vx)) * Abs(vx) / vx;
if (ovy * vy > 0)
vy = (Sqrt(vy * vy + ovy * ovy) - Abs(vy)) * Abs(vy) / vy;
}
//Log("%v v(%v %v) d(%v %v) m=%v l=%v s=%v", obj, vx,vy, dx,dy, mass_fact, level, shock_speed);
obj->Fling(vx, vy, 100, true);
}
}
}
// Done.
return true;
}
global func BlastObject(int Level, CausedBy)
{
var self = this;
if (CausedBy == nil)
CausedBy = GetController();
DoDamage(Level, FX_Call_DmgBlast, CausedBy);
if (!self) return;
if (GetAlive())
DoEnergy(-Level/3, false, FX_Call_EngBlast, CausedBy);
if (!self) return;
if (this.BlastIncinerate && GetDamage() >= this.BlastIncinerate)
Incinerate(Level, CausedBy);
}
2009-12-29 13:44:16 +00:00
global func BlastObjectsShockwaveCheck(int x, int y)
{
var def = GetID();
// Some special cases, which won't go into FindObjects.
if (def->GetDefHorizontalFix())
return false;
if (def->GetDefGrab() != 1)
{
if (GetCategory() & C4D_Vehicle)
return false;
if (GetProcedure() == "FLOAT")
return false;
}
// Projectiles not when they fly downwards or are exactly in the explosion point.
// This will catch the most cases in which multiple clonks throw flints at the same time.
if (GetCategory() & C4D_Object)
{
if (GetX() == x && GetY() == y) return false;
if (GetYDir() > 5) return false;
}
// No stuck objects.
if (Stuck())
return false;
return true;
}
2009-12-29 13:44:16 +00:00
/*-- Shake view port --*/
2009-12-29 13:44:16 +00:00
global func ShakeViewPort(int level, int x_off, int y_off)
{
if (level <= 0)
return false;
2009-12-29 13:44:16 +00:00
var eff = GetEffect("ShakeEffect", this);
2009-12-29 13:44:16 +00:00
if (eff)
2009-12-29 13:44:16 +00:00
{
eff.level += level;
return true;
}
2009-12-29 13:44:16 +00:00
eff = AddEffect("ShakeEffect", this, 200, 1);
if (!eff)
return false;
2009-12-29 13:44:16 +00:00
eff.level = level;
2009-12-29 13:44:16 +00:00
if (x_off || y_off)
2009-12-29 13:44:16 +00:00
{
eff.x = x_off;
eff.y = y_off;
}
2009-12-29 13:44:16 +00:00
else
{
eff.x = GetX();
eff.y = GetY();
}
return true;
2009-12-29 13:44:16 +00:00
}
// Duration of the effect: as soon as strength==0
// Strength of the effect: strength=level/(1.5*fxtime+3)-fxtime^2/400
2009-12-29 13:44:16 +00:00
global func FxShakeEffectTimer(object target, effect, int fxtime)
{
var strength;
2009-12-29 13:44:16 +00:00
var str = effect.level;
var xpos = effect.x;
var ypos = effect.y;
2009-12-29 13:44:16 +00:00
for (var i = 0; i < GetPlayerCount(); i++)
{
var plr = GetPlayerByIndex(i);
2010-10-17 13:53:19 +00:00
var cursor = GetCursor(plr);
if (!cursor)
continue;
var distance = Distance(cursor->GetX(), cursor->GetY(), xpos, ypos);
2009-12-29 13:44:16 +00:00
// Shake effect lowers as a function of the distance.
var level = (300 * str) / Max(300, distance);
2009-12-29 13:44:16 +00:00
if ((strength = level / ((3 * fxtime) / 2 + 3) - fxtime**2 / 400) <= 0)
continue;
2009-12-29 13:44:16 +00:00
2010-04-07 10:15:49 +00:00
// FixME: Use GetViewOffset, make this relative, not absolute
SetViewOffset(plr, Sin(fxtime * 100, strength), Cos(fxtime * 100, strength));
}
2009-12-29 13:44:16 +00:00
if (str / ((3 * fxtime) / 2 + 3) - fxtime**2 / 400 <= 0)
return -1;
2009-12-29 13:44:16 +00:00
}
global func FxShakeEffectStart(object target, effect)
{
FxShakeEffectTimer(target, effect, effect.Time);
2009-12-29 13:44:16 +00:00
}
global func FxShakeEffectStop()
{
for (var i = 0; i < GetPlayerCount(); i++)
{
// FxME: Return the offset to the previous value, not zero
SetViewOffset(GetPlayerByIndex(i), 0, 0);
}
2009-12-29 13:44:16 +00:00
}
/*-- Smoke trails --*/
2009-12-29 13:44:16 +00:00
global func CreateSmokeTrail(int strength, int angle, int x, int y, int color, bool noblast) {
x += GetX();
y += GetY();
var effect = AddEffect("SmokeTrail", nil, 300, 1, nil, nil, strength, angle, x, y);
if (!color)
color = RGBa(130, 130, 130, 70);
effect.color = color;
effect.noblast = noblast;
return;
2009-12-29 13:44:16 +00:00
}
global func FxSmokeTrailStart(object target, proplist effect, int temp, strength, angle, x, y)
{
if (temp)
return;
if (angle % 90 == 1)
angle += 1;
strength = Max(strength, 5);
effect.strength = strength;
effect.curr_strength = strength;
effect.x = x;
effect.y = y;
effect.xdir = Sin(angle, strength * 40);
effect.ydir = -Cos(angle, strength * 40);
2009-12-29 13:44:16 +00:00
}
global func FxSmokeTrailTimer(object target, proplist effect, int fxtime)
{
var strength = effect.strength;
var str = effect.curr_strength;
var x = effect.x;
var y = effect.y;
var x_dir = effect.xdir;
var y_dir = effect.ydir;
str = Max(1, str - str / 5);
str--;
y_dir += GetGravity() * 10 / 3;
var x_dir = x_dir * str / strength;
var y_dir = y_dir * str / strength;
// new: random
x += RandomX(-3,3);
y += RandomX(-3,3);
// draw
CreateParticle("ExploSmoke", x, y, RandomX(-2, 2), RandomX(-2, 4), 150 + str * 12, effect.color);
if (!effect.noblast)
CreateParticle("Blast", x, y, 0, 0, 10 + str * 8, RGBa(255, 100, 50, 150));
// then calc next position
x += x_dir / 100;
y += y_dir / 100;
if (GBackSemiSolid(x, y))
return -1;
if (str <= 3)
return -1;
effect.curr_strength = str;
effect.x = x;
effect.y = y;
effect.ydir = y_dir;
2009-12-29 13:44:16 +00:00
}
2010-03-04 02:58:31 +00:00
/*-- Fireworks --*/
2010-03-04 02:58:31 +00:00
global func Fireworks(int color, int x, int y)
{
if (!color)
color = HSL(Random(8) * 32, 255, 127);
2010-03-04 02:58:31 +00:00
var speed = 12;
for (var i = 0; i < 36; ++i)
2010-03-04 02:58:31 +00:00
{
var oangle = Random(70);
var eff = AddEffect("Firework", nil, 300, 1, nil, nil, Cos(oangle,speed), i * 10 + Random(5), x + GetX(), y + GetY());
eff.color = color;
2010-03-04 02:58:31 +00:00
}
for (var i = 0; i < 16; ++i)
2010-03-04 02:58:31 +00:00
{
CreateParticle("ExploSmoke", RandomX(-80, 80), RandomX(-80, 80), 0, 0, RandomX(500, 700), RGBa(255, 255, 255, 90));
2010-03-04 02:58:31 +00:00
}
CastParticles("Spark", 60, 190, 0, 0, 40, 70, color, color);
2010-03-04 02:58:31 +00:00
CreateParticle("Flash", 0, 0, 0, 0, 3500, color | (200 & 255) << 24);
return;
2010-03-04 02:58:31 +00:00
}
global func FxFireworkStart(object target, effect, int tmp, speed, angle, x, y, color)
2010-03-04 02:58:31 +00:00
{
if (tmp)
return;
2010-03-04 02:58:31 +00:00
effect.speed = speed * 100;
effect.angle = angle;
effect.x = x * 100;
effect.y = y * 100;
2010-03-04 02:58:31 +00:00
}
global func FxFireworkTimer(object target, effect, int time)
2010-03-04 02:58:31 +00:00
{
var speed = effect.speed;
var angle = effect.angle;
var x = effect.x;
var y = effect.y;
2010-03-04 02:58:31 +00:00
if (time > 65) return -1;
2010-03-04 02:58:31 +00:00
if (GBackSemiSolid(x / 100, y / 100))
return -1;
2010-03-04 02:58:31 +00:00
// lose speed
speed = 25 * speed / 26;
2010-03-04 02:58:31 +00:00
var x_dir = Sin(angle, speed);
var y_dir = -Cos(angle, speed);
2010-03-04 02:58:31 +00:00
CreateParticle("Flash", x / 100, y / 100, x_dir / 100, y_dir / 100, 50, effect.color | (200 & 255) << 24);
2010-03-04 02:58:31 +00:00
// gravity
y_dir += GetGravity() * 18 / 20;
2010-03-04 02:58:31 +00:00
effect.speed = speed;
effect.angle = Angle(0, 0, x_dir, y_dir);
effect.x = x + x_dir;
effect.y = y + y_dir;
2010-03-04 02:58:31 +00:00
}