1 /**
2 	Rockfall
3 	Big pieces of rock that fall down cliffs.
4 
5 	@author Maikel
6 */
7 
8 
9 /*-- Disaster Control --*/
10 
SetChance(int chance)11 public func SetChance(int chance)
12 {
13 	if (this != Rockfall)
14 		return;
15 	var effect = GetEffect("IntRockfallControl");
16 	if (!effect)
17 		effect = AddEffect("IntRockfallControl", nil, 100, 20, nil, Rockfall);
18 	effect.chance = chance;
19 	return;
20 }
21 
GetChance()22 public func GetChance()
23 {
24 	if (this != Rockfall)
25 		return;
26 	var effect = GetEffect("IntRockfallControl");
27 	if (effect)
28 		return effect.chance;
29 	return;
30 }
31 
SetArea(proplist rect)32 public func SetArea(proplist rect)
33 {
34 	if (this != Rockfall)
35 		return;
36 	var effect = GetEffect("IntRockfallControl");
37 	if (effect)
38 		effect.area = rect;
39 	return;
40 }
41 
SetExplosiveness(int explosiveness)42 public func SetExplosiveness(int explosiveness)
43 {
44 	if (this != Rockfall)
45 		return;
46 	var effect = GetEffect("IntRockfallControl");
47 	if (!effect)
48 		return;
49 	effect.explosiveness = explosiveness;
50 	return;
51 }
52 
53 // Sets the spawn distance from crew members.
SetSpawnDistance(int dist)54 public func SetSpawnDistance(int dist)
55 {
56 	if (this != Rockfall)
57 		return;
58 	var effect = GetEffect("IntRockfallControl");
59 	if (!effect)
60 		return;
61 	effect.spawn_distance = dist;
62 	return;
63 }
64 
FxIntRockfallControlTimer(object target,proplist effect,int time)65 protected func FxIntRockfallControlTimer(object target, proplist effect, int time)
66 {
67 	if (Random(100) < effect.chance && !Random(6))
68 	{
69 		// Attempt to find a suitable location for the rock to be created.
70 		var x, y;
71 		var max_tries = 500;
72 		for (var i = 0; i < max_tries; i++)
73 		{
74 			var x = Random(LandscapeWidth());
75 			var y = 0;
76 			if (effect.area)
77 			{
78 				x = effect.area.x + Random(effect.area.wdt);
79 				y = effect.area.y + Random(effect.area.hgt);
80 			}
81 			if (effect.spawn_distance)
82 			{
83 				var dist = (max_tries - i) * effect.spawn_distance / max_tries;
84 				if (FindObject(Find_OCF(OCF_CrewMember), Find_Distance(dist, x, y)))
85 					continue;
86 			}
87 			break;
88 		}
89 		// Explosive rocks demanded?
90 		var explosive = effect.explosiveness && Random(100) < effect.explosiveness;
91 		// Launch rockfall of varying sizes between 40 and 120.
92 		LaunchRockfall(x, y, 80 + Random(41), RandomX(-2, 2), RandomX(4, 8), explosive);
93 	}
94 	return FX_OK;
95 }
96 
97 // Scenario saving through an effect call.
FxIntRockfallControlSaveScen(obj,fx,props)98 public func FxIntRockfallControlSaveScen(obj, fx, props)
99 {
100 	props->Add("Rockfall", "Rockfall->SetChance(%d)", fx.chance);
101 	props->Add("Rockfall", "Rockfall->SetArea(%v)", fx.area);
102 	if (fx.explosiveness)
103 		props->Add("Rockfall", "Rockfall->SetExplosiveness(%d)", fx.explosiveness);
104 	if (fx.spawn_distance)
105 		props->Add("Rockfall", "Rockfall->SetSpawnDistance(%d)", fx.spawn_distance);
106 	return true;
107 }
108 
109 // Launches an earthquake with epicenter (x,y).
LaunchRockfall(int x,int y,int size,int xdir,int ydir,bool explosive)110 global func LaunchRockfall(int x, int y, int size, int xdir, int ydir, bool explosive)
111 {
112 	// The rockfall size is constrained between 40 and 120%.
113 	size = BoundBy(size, 40, 120);
114 
115 	// Rockfall should be launched in the free.
116 	if (GBackSemiSolid(x, y))
117 		return false;
118 
119 	// Create rock and adjust its size.
120 	var rock = CreateObject(Rockfall, x, y);
121 	rock->SetCon(size);
122 
123 	// Remove rock if stuck.
124 	if (rock->Stuck())
125 		return rock->RemoveObject();
126 
127 	// Set speed and rotation.
128 	rock->SetXDir(xdir);
129 	rock->SetYDir(ydir);
130 	rock->SetR(Random(360));
131 	rock->SetRDir(RandomX(-6, 6));
132 
133 	// Make explosive
134 	if (explosive)
135 		rock->MakeExplosive();
136 
137 	return true;
138 }
139 
140 
141 /*-- Rockfall --*/
142 
143 local damage, is_explosive;
144 
Construction()145 protected func Construction()
146 {
147 	damage = 0;
148 	this.MeshTransformation = Trans_Scale(2000, 2000, 2000);
149 	// Add an effect for rolling then the rock is just lying around.
150 	AddEffect("IntRockMovement", this, 100, 4, this);
151 	return;
152 }
153 
MakeExplosive()154 public func MakeExplosive()
155 {
156 	is_explosive = true;
157 	SetClrModulation(0xffff0000);
158 	return true;
159 }
160 
FxIntRockRollStart(object target,proplist effect,int temporary)161 protected func FxIntRockRollStart(object target, proplist effect, int temporary)
162 {
163 	if (temporary)
164 		return 1;
165 	effect.damtime = 0;
166 	return 1;
167 }
168 
FxIntRockMovementTimer(object target,proplist effect,int time)169 protected func FxIntRockMovementTimer(object target, proplist effect, int time)
170 {
171 	// If rock is not moving give it a kick.
172 	if (GetXDir() == 0 || GetYDir() == 0)
173 	{
174 		SetXDir(RandomX(-160, 160), 100);
175 		SetYDir(-60 - Random(60), 100);
176 		SetRDir(GetRDir() + RandomX(-2, 2));
177 	}
178 
179 	// Damage rock every 120 frames to make sure it breaks at some point.
180 	effect.damtime += 4;
181 	if (effect.damtime > 120)
182 	{
183 		effect.damtime = 0;
184 		damage++;
185 	}
186 	return 1;
187 }
188 
Hit(int dx,int dy)189 protected func Hit(int dx, int dy)
190 {
191 	// Acid kills rockfall
192 	if (GetMaterialVal("Corrosive", "Material", GetMaterial()))
193 	{
194 		Sound("Liquids::Pshshsh");
195 		var sz = Max(GetCon()/10, 5);
196 		var particles = new Particles_Dust() { Size = sz*3, };
197 		if (is_explosive)
198 			{ particles.R = 200; particles.G =100; particles.B = 0; }
199 		else
200 			{ particles.R = 100; particles.G =200; particles.B = 100; }
201 		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);
202 		return RemoveObject();
203 	}
204 
205 	// Determine caused damage to this rock by impact.
206 	damage += Distance(dx, dy, 0, 0) / 100;
207 	if (damage > 12)
208 		return SplitRock();
209 
210 	// Rebounce or crack on downward hit.
211 	if (dy > 0)
212 	{
213 		SetXDir(dx + RandomX(-160, 160), 100);
214 		SetYDir(Min(-60, -7 * dy / 10) - Random(60), 100);
215 		SetRDir(GetRDir() + RandomX(-2, 2));
216 	}
217 
218 	// Some particles and smoke.
219 	if (GetMaterial(0, 9 * GetCon() / 100))
220 	{
221 		var clr = GetAverageTextureColor(GetTexture(0, 9 * GetCon() / 100));
222 		var particles =
223 		{
224 			Prototype = Particles_Dust(),
225 			R = (clr >> 16) & 0xff,
226 			G = (clr >> 8) & 0xff,
227 			B = clr & 0xff,
228 			Size = 4,
229 		};
230 		CreateParticle("Dust", PV_Random(-2, 2), 8 * GetCon() / 100, PV_Random(-3, 3), PV_Random(-2, -3), PV_Random(36, 2 * 36), particles, 2);
231 	}
232 
233 	// Fling living beings near impact point for a big hit.
234 	if (GetCon() > 80 && dy > 60)
235 	{
236 		for (var obj in FindObjects(Find_NoContainer(), Find_OCF(OCF_Alive), Find_InRect(-16, -8, 32, 24)))
237 		{
238 			if (!Random(3) || !obj->GetAction())
239 				continue;
240 			var act = obj.ActMap[obj->GetAction()];
241 			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))
242 				obj->Fling(Random(3) - 1);
243 		}
244 	}
245 
246 	// Sound.
247 	Sound("Environment::Disasters::EarthquakeEnd", nil, 3 * GetCon() / 2);
248 	StonyObjectHit(dx, dy);
249 	return;
250 }
251 
SplitRock()252 private func SplitRock()
253 {
254 	var con = GetCon(), erock;
255 	// Explosive rocks do some damage
256 	if (is_explosive)
257 		if (erock = CreateObjectAbove(Rock, 0, 4, GetController()))
258 			erock->Explode(Max(15 * con / 100, 3));
259 	// Split the rock into smaller ones if it is big enough.
260 	if (con > 40)
261 	{
262 		while (con > 0)
263 		{
264 			var rock = CreateObjectAbove(Rockfall);
265 			var rock_con = Max(30, GetCon() / 2 + RandomX(-20, 20));
266 			rock->SetCon(rock_con);
267 			con -= 2 * rock_con / 3;
268 			rock->SetXDir(RandomX(-100, 100), 100);
269 			rock->SetYDir(RandomX(-200, -100), 100);
270 			rock->SetRDir(RandomX(-6, 6));
271 			if (is_explosive)
272 				rock->MakeExplosive();
273 		}
274 	}
275 	// Some particles.
276 	var rock_explode =
277 	{
278 		Size = PV_KeyFrames(0, 180, 25, 1000, 50),
279 	    DampingY = PV_Random(890, 920, 5),
280 		DampingX = PV_Random(900, 930, 5),
281 		ForceY=-1,
282 		ForceX = PV_Wind(20, PV_Random(-2, 2)),
283 		Rotation=PV_Random(0,360,0),
284 		R=PV_KeyFrames(0, 0, 255, 260, 64, 1000, 64),
285 		G=PV_KeyFrames(0, 0, 128,  260, 64, 1000, 64),
286 		B=PV_KeyFrames(0, 0, 0, 260, 108, 1000, 108),
287 	    Alpha = PV_KeyFrames(0, 0, 0, 100, 20, 500, 20, 1000, 0)
288 	};
289 	CreateParticle("SmokeDirty", PV_Random(-5, 5), PV_Random(-5, 5), 0, PV_Random(-2, 0), PV_Random(50, 100), rock_explode, 8);
290 
291 	// Some sound effects.
292 	Sound("Environment::Disasters::EarthquakeEnd", nil, 100);
293 
294 	RemoveObject();
295 	return;
296 }
297 
298 
299 /*-- Proplist --*/
300 
301 local Name = "$Name$";
302