/*-- Explode.c Authors: Newton Everything about the explosion. TODO: documentation. --*/ /*-- Explosion --*/ global func Explode(int level, bool silent) { 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); } global func DoExplosion(int x, int y, int level, object inobj, int cause_plr, object layer) { // Container to ContainBlast var container = inobj; while (container) { if (container->GetID()->GetDefContainBlast()) break; else container = container->Contained(); } // Explosion outside: Explosion effects. 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); } // 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); return true; } global func ExplosionEffect(int level, int x, int y) { // Blast particle. CreateParticle("Blast", x, y, 0, 0, level * 10, RGBa(255, 255, 255, 100)); if(!GBackLiquid(x,y)) CastParticles("Spark", 10, 80 + level, x, y, 35, 40, RGB(255, 200, 0), RGB(255, 255, 150)); if(GBackLiquid(x,y)) CastObjects(Fx_Bubble, level * 7 / 10, level, x, y); //CastParticles("FSpark", level/5+1, level, x,y, level*5+10,level*10+10, 0x00ef0000,0xffff1010)); // Smoke trails. var i = 0, count = 3 + level / 8, angle = Random(360); while (count > 0 && ++i < 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 = 16 * level / 10; CreateSmokeTrail(lvl, angle, x + smokex, y + smokey); count--; } return; } /*-- Blast objects & shockwaves --*/ // Damage and hurl objects away. 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); // 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 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; } /*-- Shake view port --*/ global func ShakeViewPort(int level, int x_off, int y_off) { if (level <= 0) return false; var eff = GetEffect("ShakeEffect", this); if (eff) { eff.level += level; return true; } eff = AddEffect("ShakeEffect", this, 200, 1); if (!eff) return false; eff.level = level; if (x_off || y_off) { eff.x = x_off; eff.y = y_off; } else { eff.x = GetX(); eff.y = GetY(); } return true; } // Duration of the effect: as soon as strength==0 // Strength of the effect: strength=level/(1.5*fxtime+3)-fxtime^2/400 global func FxShakeEffectTimer(object target, effect, int fxtime) { var strength; var str = effect.level; var xpos = effect.x; var ypos = effect.y; for (var i = 0; i < GetPlayerCount(); i++) { var plr = GetPlayerByIndex(i); var cursor = GetCursor(plr); if (!cursor) continue; var distance = Distance(cursor->GetX(), cursor->GetY(), xpos, ypos); // Shake effect lowers as a function of the distance. var level = (300 * str) / Max(300, distance); if ((strength = level / ((3 * fxtime) / 2 + 3) - fxtime**2 / 400) <= 0) continue; // FixME: Use GetViewOffset, make this relative, not absolute SetViewOffset(plr, Sin(fxtime * 100, strength), Cos(fxtime * 100, strength)); } if (str / ((3 * fxtime) / 2 + 3) - fxtime**2 / 400 <= 0) return -1; } global func FxShakeEffectStart(object target, effect) { FxShakeEffectTimer(target, effect, effect.Time); } 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); } } /*-- Smoke trails --*/ 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; } 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); } 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() * 2 / 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; } /*-- Fireworks --*/ global func Fireworks(int color, int x, int y) { if (!color) color = HSL(Random(8) * 32, 255, 127); var speed = 12; for (var i = 0; i < 36; ++i) { 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; } for (var i = 0; i < 16; ++i) { CreateParticle("ExploSmoke", RandomX(-80, 80), RandomX(-80, 80), 0, 0, RandomX(500, 700), RGBa(255, 255, 255, 90)); } CastParticles("Spark", 60, 190, 0, 0, 40, 70, color, color); CreateParticle("Flash", 0, 0, 0, 0, 3500, color | (200 & 255) << 24); return; } global func FxFireworkStart(object target, effect, int tmp, speed, angle, x, y, color) { if (tmp) return; effect.speed = speed * 100; effect.angle = angle; effect.x = x * 100; effect.y = y * 100; } global func FxFireworkTimer(object target, effect, int time) { var speed = effect.speed; var angle = effect.angle; var x = effect.x; var y = effect.y; if (time > 65) return -1; if (GBackSemiSolid(x / 100, y / 100)) return -1; // lose speed speed = 25 * speed / 26; var x_dir = Sin(angle, speed); var y_dir = -Cos(angle, speed); CreateParticle("Flash", x / 100, y / 100, x_dir / 100, y_dir / 100, 50, effect.color | (200 & 255) << 24); // gravity y_dir += GetGravity() * 18 / 100; effect.speed = speed; effect.angle = Angle(0, 0, x_dir, y_dir); effect.x = x + x_dir; effect.y = y + y_dir; }