1 /**
2 Meteor
3 A burning rock falling from the sky, explodes on impact.
4
5 @author Maikel, Zapper
6 */
7
8 // A meteor can spawn objects on impact.
9 local spawn_id, spawn_amount;
10
11 /*-- Disaster Control --*/
12
13 /**
14 Enables meteorites.
15 The meteorites can be set to spawn objects on impact. The amount to spawn will be spawn_amount randomized by 50%.
16 */
SetChance(int chance,id spawn_id,int spawn_amount)17 public func SetChance(int chance, id spawn_id, int spawn_amount)
18 {
19 spawn_amount = spawn_amount ?? 1;
20 if (GetType(this) != C4V_Def) return FatalError("Must be called as a definition call.");
21
22 var effect = FindMeteoriteEffectFor(spawn_id);
23 if (!effect)
24 effect = AddEffect("IntMeteorControl", nil, 100, 20, nil, this, spawn_id, spawn_amount);
25 effect.chance = chance;
26
27 return true;
28 }
29
30 /**
31 Returns the chance of meteorites that is currently set. spawn_id can be nil for the normal meteorite.
32 */
GetChance(id spawn_id)33 public func GetChance(id spawn_id)
34 {
35 if (GetType(this) != C4V_Def) return FatalError("Must be called as a definition call.");
36 var effect = FindMeteoriteEffectFor(spawn_id);
37 if (effect)
38 return effect.chance;
39 return 0;
40 }
41
42 // Finds the meteorite effect that matches a specific spawn id.
FindMeteoriteEffectFor(id spawn_id)43 private func FindMeteoriteEffectFor(id spawn_id)
44 {
45 var i = 0, fx;
46 while (fx = GetEffect("IntMeteorControl", nil, i++))
47 {
48 if (fx.spawn_id == spawn_id) return fx;
49 }
50 return nil;
51 }
52
FxIntMeteorControlStart(object target,effect fx,temp,id spawn_id,int spawn_amount)53 private func FxIntMeteorControlStart(object target, effect fx, temp, id spawn_id, int spawn_amount)
54 {
55 if (temp) return;
56 fx.spawn_id = spawn_id;
57 fx.spawn_amount = spawn_amount;
58 }
59
FxIntMeteorControlTimer(object target,effect fx,int time)60 private func FxIntMeteorControlTimer(object target, effect fx, int time)
61 {
62 if (Random(100) < fx.chance && !Random(10))
63 {
64 // Launch a meteor.
65 var x = Random(LandscapeWidth());
66 var y = 0;
67 var size = RandomX(60, 90);
68 var xdir = RandomX(-22, 22);
69 var ydir = RandomX(28, 36);
70 var real_spawn_amount = Max(1, RandomX(fx.spawn_amount / 2, 3 * fx.spawn_amount / 2));
71 LaunchMeteor(x, y, size, xdir, ydir, fx.spawn_id, real_spawn_amount);
72 }
73 return FX_OK;
74 }
75
76 // Scenario saving
FxIntMeteorControlSaveScen(obj,fx,props)77 public func FxIntMeteorControlSaveScen(obj, fx, props)
78 {
79 props->Add("Meteor", "Meteor->SetChance(%d, %v, %d)", fx.chance, fx.spawn_id, fx.spawn_amount);
80 return true;
81 }
82
83 /*-- Meteor --*/
84
Launch(int x,int y,int size,int xdir,int ydir,id spawn_id,int spawn_amount)85 public func Launch(int x, int y, int size, int xdir, int ydir, id spawn_id, int spawn_amount)
86 {
87 // Launch from indicated position.
88 SetPosition(x, y);
89 // Set the meteor's size.
90 SetCon(BoundBy(size, 20, 100));
91 // Set the initial velocity.
92 SetXDir(xdir);
93 SetYDir(ydir);
94 // Remember spawning information.
95 this.spawn_id = spawn_id;
96 this.spawn_amount = spawn_amount ?? 1;
97 // Safety check.
98 if (!IsLaunchable())
99 return false;
100 // Allow for some more effects (overloadable).
101 this->OnAfterLaunch();
102 return this;
103 }
104
OnAfterLaunch()105 public func OnAfterLaunch()
106 {
107 // Set random rotation.
108 SetR(Random(360));
109 SetRDir(RandomX(-10, 10));
110 // Emit light
111 SetLightRange(300, 30);
112 SetLightColor(RGB(255, 160, 120));
113 // Set right action.
114 AddEffect("IntMeteor", this, 100, 1, this);
115 }
116
IsLaunchable()117 private func IsLaunchable()
118 {
119 if (GBackSemiSolid() || Stuck())
120 {
121 RemoveObject();
122 return false;
123 }
124 return true;
125 }
126
FxIntMeteorStart(object target,effect fx,bool temp)127 private func FxIntMeteorStart(object target, effect fx, bool temp)
128 {
129 if (temp) return;
130 fx.smoketrail =
131 {
132 R = 255,
133 B = PV_KeyFrames(0, 0,100, 30,0, 100,255, 1000,255),
134 G = PV_KeyFrames(0, 0,150, 30,0, 100,255, 1000,255),
135
136 Alpha = PV_KeyFrames(1000, 0, 0, 30, 255, 1000, 0),
137 Size = PV_Linear(20,60),
138 Stretch = 1000,
139 Phase = PV_Random(0,4),
140 Rotation = PV_Random(-GetR() - 15, -GetR() + 15),
141 DampingX = 1000,
142 DampingY = 1000,
143 BlitMode = 0,
144 CollisionVertex = 0,
145 OnCollision = PC_Stop(),
146 Attach = nil
147 };
148 fx.brighttrail =
149 {
150 Prototype = fx.smoketrail,
151 Alpha = PV_Linear(180,0),
152 Size = PV_Linear(20,30),
153 BlitMode = GFX_BLIT_Additive,
154 };
155 fx.frontburn =
156 {
157 R = 255,
158 B = 50,
159 G = 190,
160
161 Alpha = PV_KeyFrames(0, 0, 0, 500, 25, 1000, 0),
162 Size = PV_Linear(4,5),
163 Stretch = 1000,
164 Phase = PV_Random(0,4),
165 Rotation = PV_Random(-GetR() - 15, -GetR() + 15),
166 DampingX = 1000,
167 DampingY = 1000,
168 BlitMode = GFX_BLIT_Additive,
169 CollisionVertex = 0,
170 OnCollision = PC_Stop(),
171 Attach = ATTACH_Front | ATTACH_MoveRelative
172 };
173 }
174
FxIntMeteorTimer(object target,effect fx,bool temp)175 protected func FxIntMeteorTimer(object target, effect fx, bool temp)
176 {
177 var size = GetCon();
178 // Air drag.
179 var ydir = GetYDir(100);
180 ydir -= size * ydir ** 2 / 11552000; // Magic number.
181 SetYDir(ydir, 100);
182 // Smoke trail.
183 CreateParticle("SmokeThick", 0, 0, PV_Random(-3, 3), PV_Random(-3, 3), 200, fx.smoketrail, 5);
184 // Flash
185 CreateParticle("SmokeThick", 0, -4, PV_Random(-3, 3), PV_Random(-3, 3), 3, fx.brighttrail, 2);
186 // left and right burn
187 CreateParticle("FireDense", PV_Random(-5, 5), 15, PV_Random(-5, -20), PV_Random(-15, -30), 20, fx.frontburn, 30);
188 CreateParticle("FireDense", PV_Random(-5, 5), 15, PV_Random(5, 20), PV_Random(-15, -30), 20, fx.frontburn, 30);
189 // Sound.
190
191 // Burning and friction decrease size.
192 if (size > 10 && !Random(5))
193 DoCon(-1);
194
195 return FX_OK;
196 }
197
198 // Scenario saving
FxIntMeteorSaveScen(obj,fx,props)199 func FxIntMeteorSaveScen(obj, fx, props)
200 {
201 props->AddCall("Meteor", obj, "AddEffect", "\"IntMeteor\"", obj, 100, 1, obj);
202 return true;
203 }
204
Hit(int xdir,int ydir)205 protected func Hit(int xdir, int ydir)
206 {
207 var size = 10 + GetCon();
208 var speed2 = 20 + (xdir ** 2 + ydir ** 2) / 10000;
209 // Some fire sparks.
210 var particles =
211 {
212 Prototype = Particles_Fire(),
213 Attach = nil
214 };
215 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);
216 // Explode meteor, explode size scales with the energy of the meteor.
217 var dam = size * speed2 / 500;
218 dam = BoundBy(size/2, 5, 30);
219 // Blow up a dummy object so that the explosion can happen before we spawn items.
220 var dummy = CreateObject(Dummy, 0, 0, GetController());
221 dummy->Explode(dam);
222
223 // Fling around some objects?
224 if (spawn_id)
225 {
226 for (var i = 0; i < spawn_amount; ++i)
227 {
228 var angle = Angle(0, 0, -xdir, -ydir) + RandomX(-45, 45);
229 var force = BoundBy(GetCon() / 2, 10, 50) + RandomX(-10, 10);
230 var item = CreateObject(spawn_id, 0, 0, GetOwner());
231 item->SetSpeed(Sin(angle, force), -Cos(angle, force));
232 }
233 }
234
235 RemoveObject();
236 return;
237 }
238
239
240 /*-- Target --*/
241
OnLightningStrike(object lightning,int damage)242 public func OnLightningStrike(object lightning, int damage)
243 {
244 SetController(lightning->GetController());
245 if (GetDamage() + damage >= 6)
246 Hit();
247 return;
248 }
249
IsProjectileTarget(object projectile)250 public func IsProjectileTarget(object projectile)
251 {
252 return true;
253 }
254
OnProjectileHit(object projectile)255 public func OnProjectileHit(object projectile)
256 {
257 SetController(projectile->GetController());
258 Hit();
259 return;
260 }
261
IsMeteor()262 public func IsMeteor() { return true; }
263
264
265 /*-- Proplist --*/
266
267 local Name = "$Name$";
268