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