1 /**
2 	Objects.c
3 	Functions generally applicable to objects; not enough to be worth distinct scripts though.
4 
5 	@author Maikel, boni, Ringwaul, Sven2, flgr, Clonkonaut, Günther, Randrian
6 */
7 
8 // Does not set the speed of an object. But you can set two components of the velocity vector with this function.
9 // documented in /docs/sdk/script/fn
SetSpeed(int x_dir,int y_dir,int prec)10 global func SetSpeed(int x_dir, int y_dir, int prec)
11 {
12 	SetXDir(x_dir, prec);
13 	SetYDir(y_dir, prec);
14 	return;
15 }
16 
17 // Returns the speed of an object.
GetSpeed(int prec)18 global func GetSpeed(int prec)
19 {
20 	return Sqrt(GetXDir(prec)**2 + GetYDir(prec)**2);
21 }
22 
23 // You can add to the two components of the velocity vector individually with this function.
AddSpeed(int x_dir,int y_dir,int prec)24 global func AddSpeed(int x_dir, int y_dir, int prec)
25 {
26 	SetXDir(GetXDir(prec) + x_dir, prec);
27 	SetYDir(GetYDir(prec) + y_dir, prec);
28 }
29 
30 // Sets an objects's speed and its direction, doesn't it?
31 // Can set either speed or angle of velocity, or both
SetVelocity(int angle,int speed,int precAng,int precSpd)32 global func SetVelocity(int angle, int speed, int precAng, int precSpd)
33 {
34 	if (!precSpd) precSpd = 10;
35 	if (!precAng) precAng = 1;
36 	speed = speed ?? Distance(0, 0, GetXDir(precSpd), GetYDir(precSpd));
37 	angle = angle ?? Angle(0, 0, GetXDir(precSpd), GetYDir(precSpd), precAng);
38 
39 	var x_dir = Sin(angle, speed, precAng);
40 	var y_dir = -Cos(angle, speed, precAng);
41 
42 	SetXDir(x_dir, precSpd);
43 	SetYDir(y_dir, precSpd);
44 	return;
45 }
46 
47 // Adds to an objects's speed and its direction:
48 // Can set either speed or angle of velocity, or both
AddVelocity(int angle,int speed,int precision_angle,int precision_speed)49 global func AddVelocity(int angle, int speed, int precision_angle, int precision_speed)
50 {
51 	precision_speed = precision_speed ?? 10;
52 	precision_angle = precision_angle ?? 1;
53 	speed = speed ?? 0;
54 	angle = angle ?? 0;
55 
56 	var current_x_dir = GetXDir(precision_speed);
57 	var current_y_dir = GetYDir(precision_speed);
58 
59 	var x_dir = +Sin(angle, speed, precision_angle);
60 	var y_dir = -Cos(angle, speed, precision_angle);
61 
62 	SetXDir(current_x_dir + x_dir, precision_speed);
63 	SetYDir(current_y_dir + y_dir, precision_speed);
64 	return;
65 }
66 
67 
68 // Sets the completion of this to new_con.
69 // documented in /docs/sdk/script/fn
SetCon(int new_con,int precision,bool grow_from_center)70 global func SetCon(int new_con, int precision, bool grow_from_center)
71 {
72 	return DoCon(new_con - GetCon(), precision, grow_from_center);
73 }
74 
GetObjAlpha()75 global func GetObjAlpha()
76 {
77 	return (GetClrModulation() >> 24) & 0xFF;
78 }
79 
80 // Sets the object's transparency.
SetObjAlpha(int by_alpha)81 global func SetObjAlpha(int by_alpha)
82 {
83 	var clr_mod = GetClrModulation();
84 
85 	if (!clr_mod)
86 		clr_mod = by_alpha << 24;
87 	else
88 		clr_mod = clr_mod & 16777215 | by_alpha << 24;
89 	return SetClrModulation(clr_mod);
90 }
91 
MakeInvincible(bool allow_fire)92 global func MakeInvincible(bool allow_fire)
93 {
94 	if (!this) return false;
95 	var fx = GetEffect("IntInvincible", this);
96 	if (!fx) fx = AddEffect("IntInvincible", this, 300, 0);
97 	if (!fx) return false;
98 	fx.allow_fire = allow_fire;
99 	if (!allow_fire && this->OnFire()) this->Extinguish();
100 	fx.OnShockwaveHit = this.OnShockwaveHit;
101 	fx.RejectWindbagForce = this.RejectWindbagForce;
102 	fx.QueryCatchBlow = this.QueryCatchBlow;
103 	this.OnShockwaveHit = Global.Invincibility_OnShockwaveHit;
104 	this.RejectWindbagForce = Global.Invincibility_RejectWindbagForce;
105 	this.QueryCatchBlow = Global.Invincibility_QueryCatchBlow;
106 	return true;
107 }
108 
IsInvincible()109 global func IsInvincible()
110 {
111 	return !!GetEffect("IntInvincible", this);
112 }
113 
SetInvincibility(bool to_val)114 global func SetInvincibility(bool to_val)
115 {
116 	// Turn invincibility on or off
117 	if (to_val)
118 		return MakeInvincible(false);
119 	else
120 		return ClearInvincible();
121 }
122 
FxIntInvincibleDamage(target)123 global func FxIntInvincibleDamage(target)
124 {
125 	// avert all damage
126 	return 0;
127 }
128 
FxIntInvincibleEffect(string new_name,object target,proplist fx)129 global func FxIntInvincibleEffect(string new_name, object target, proplist fx)
130 {
131 	// Block fire effects.
132 	if (WildcardMatch(new_name, "*Fire*") && !fx.allow_fire)
133 		return FX_Effect_Deny;
134 	// All other effects are okay.
135 	return FX_OK;
136 }
137 
FxIntInvincibleSaveScen(object obj,proplist fx,proplist props)138 global func FxIntInvincibleSaveScen(object obj, proplist fx, proplist props)
139 {
140 	// this is invincible. Save to scenario.
141 	props->AddCall("Invincible", obj, "MakeInvincible", fx.allow_fire);
142 	return true;
143 }
144 
145 // Removes invincibility from object
ClearInvincible()146 global func ClearInvincible()
147 {
148 	if (!this) return nil;
149 	var fx = GetEffect("IntInvincible", this);
150 	if (fx)
151 	{
152 		this.OnShockwaveHit = fx.OnShockwaveHit;
153 		this.RejectWindbagForce = fx.RejectWindbagForce;
154 		this.QueryCatchBlow = fx.QueryCatchBlow;
155 	} else { // just to be sure
156 		this.OnShockwaveHit = this->GetID().OnShockwaveHit;
157 		this.RejectWindbagForce = this->GetID().RejectWindbagForce;
158 		this.QueryCatchBlow = this->GetID().QueryCatchBlow;
159 	}
160 	return RemoveEffect("IntInvincible", this);
161 }
162 
Invincibility_OnShockwaveHit()163 global func Invincibility_OnShockwaveHit()
164 {
165 	return true;
166 }
167 
Invincibility_RejectWindbagForce()168 global func Invincibility_RejectWindbagForce()
169 {
170 	return true;
171 }
172 
Invincibility_QueryCatchBlow()173 global func Invincibility_QueryCatchBlow()
174 {
175 	return true;
176 }
177 
178 // Move an object by the given parameters relative to its position.
MovePosition(int x,int y,int prec)179 global func MovePosition(int x, int y, int prec)
180 {
181 	SetPosition(GetX(prec) + x, GetY(prec) + y, nil, prec);
182 }
183 
184 // Returns the position as an array
GetPosition(int prec)185 global func GetPosition(int prec)
186 {
187 	return [GetX(prec), GetY(prec)];
188 }
189 
190 // Speed the calling object into the given direction (angle)
LaunchProjectile(int angle,int dist,int speed,int x,int y,int precAng,int precSpd,bool rel_x)191 global func LaunchProjectile(int angle, int dist, int speed, int x, int y, int precAng, int precSpd, bool rel_x)
192 {
193 	// dist: Distance object travels on angle. Offset from calling object.
194 	// x: X offset from container's center
195 	// y: Y offset from container's center
196 	// rel_x: if true, makes the X offset relative to container direction. (x=+30 will become x=-30 when Clonk turns left. This way offset always stays in front of a Clonk.)
197 
198 	var x_offset = x ?? Sin(angle, dist, precAng);
199 	var y_offset = y ?? -Cos(angle, dist, precAng);
200 
201 	if(!precAng) precAng = 1;
202 	if(!precSpd) precSpd = 10;
203 
204 	if (Contained() != nil && rel_x == true)
205 		if (Contained()->GetDir() == 0)
206 			x = -x;
207 
208 	if (Contained() != nil)
209 	{
210 		Exit(x_offset, y_offset, angle / precAng);
211 		SetVelocity(angle, speed, precAng, precSpd);
212 		return true;
213 	}
214 
215 	if (Contained() == nil)
216 	{
217 		SetPosition(GetX() + x_offset, GetY() + y_offset);
218 		SetR(angle/precAng);
219 		SetVelocity(angle, speed, precAng, precSpd);
220 		return true;
221 	}
222 	return false;
223 }
224 
225 // Sets the MaxEnergy value of an object and does the necessary callbacks.
SetMaxEnergy(int value)226 global func SetMaxEnergy(int value)
227 {
228 	if (!this)
229 		return;
230 	value *= 1000;
231 	var old_maxenergy = this.MaxEnergy;
232 	this.MaxEnergy = value;
233 	// Change current energy percentage wise and implicit callback.
234 	DoEnergy(GetEnergy() * (value - old_maxenergy) / old_maxenergy);
235 	return;
236 }
237 
238 // Returns the MaxEnergy value of an object.
GetMaxEnergy()239 global func GetMaxEnergy()
240 {
241 	if (!this)
242 		return;
243 	return this.MaxEnergy / 1000;
244 }
245 
246 // Returns whether an object is at full energy, that is if its energy is >= its MaxEnergy
HasMaxEnergy()247 global func HasMaxEnergy()
248 {
249 	if (!this)
250 		return false;
251 	return GetEnergy() >= GetMaxEnergy();
252 }
253 
254 // Sets the MaxBreath value of an object and does the necessary callbacks.
SetMaxBreath(int value)255 global func SetMaxBreath(int value)
256 {
257 	if (!this)
258 		return;
259 	var old_maxbreath = this.MaxBreath;
260 	this.MaxBreath = value;
261 	// Change current breath percentage wise and implicit callback.
262 	DoBreath(GetBreath() * (value - old_maxbreath) / old_maxbreath);
263 	return;
264 }
265 
266 // Returns the MaxBreath value of an object.
GetMaxBreath()267 global func GetMaxBreath()
268 {
269 	if (!this)
270 		return;
271 	return this.MaxBreath;
272 }
273 
274 // Makes an object gain Con until it is FullCon.
275 // value: the object grows approx. every second, in tenths of percent.
276 // max_size = the maximum object size in tenths of percent.
StartGrowth(int value,int max_size)277 global func StartGrowth(int value, int max_size)
278 {
279 	if (value <= 0) return nil;
280 	// Ensure max size is set and does not conflict with Oversize.
281 	max_size = max_size ?? 1000;
282 	if (!GetDefCoreVal("Oversize", "DefCore"))
283 		max_size = Min(max_size, 1000);
284 	var fx = AddEffect("IntGrowth", this, 1, 35, this, nil, value, max_size);
285 	fx.Time = Random(35);
286 	return fx;
287 }
288 
StopGrowth()289 global func StopGrowth()
290 {
291 	return RemoveEffect("IntGrowth", this);
292 }
293 
GetGrowthValue()294 global func GetGrowthValue()
295 {
296 	var fx = GetEffect("IntGrowth", this);
297 	if (!fx)
298 		return 0;
299 	return fx.growth;
300 }
301 
GetGrowthMaxSize()302 global func GetGrowthMaxSize()
303 {
304 	var fx = GetEffect("IntGrowth", this);
305 	if (!fx)
306 		return 0;
307 	return fx.max_size;
308 }
309 
FxIntGrowthStart(object obj,effect fx,int temporary,int value,int max_size)310 global func FxIntGrowthStart(object obj, effect fx, int temporary, int value, int max_size)
311 {
312 	if (!temporary)
313 	{
314 		fx.growth = value;
315 		fx.max_size = max_size;
316 	}
317 	return FX_OK;
318 }
319 
FxIntGrowthTimer(object obj,effect fx)320 global func FxIntGrowthTimer(object obj, effect fx)
321 {
322 	if (obj->OnFire())
323 		return FX_OK;
324 	obj->DoCon(fx.growth, 1000);
325 	// Negative growth might have removed the object.
326 	if (!obj)
327 		return FX_Execute_Kill;
328 	var done = obj->GetCon(1000) >= fx.max_size;
329 	return -done;
330 }
331 
332 // Plays hit sounds for an average object made of stone or stone-like material.
333 // x and y need to be the parameters passed to Hit() from the engine.
StonyObjectHit(int x,int y)334 global func StonyObjectHit(int x, int y)
335 {
336 	// Failsafe
337 	if (!this) return false;
338 	var xdir = GetXDir(), ydir = GetYDir();
339 	if (x) x = x / Abs(x);
340 	if (y) y = y / Abs(y);
341 	// Check for solid in hit direction
342 	var i = 0;
343 	var average_obj_size = Distance(0,0, GetObjWidth(), GetObjHeight()) / 2 + 2;
344 	while (!GBackSolid(x * i, y * i) && i < average_obj_size)
345 		i++;
346 	// To catch some high speed cases: if no solid found, check directly beneath
347 	if (!GBackSolid(x * i, y * i))
348 		while (!GBackSolid(x * i, y * i) && i < average_obj_size)
349 			i++;
350 	// Check if digfree
351 	if (!GetMaterialVal("DigFree", "Material", GetMaterial(x*i, y*i)) && GBackSolid(x*i, y*i))
352 		return Sound("Hits::Materials::Rock::RockHit?");
353 	// Else play standard sound
354 	if (Distance(0,0,xdir,ydir) > 10)
355 			return Sound("Hits::SoftTouch?");
356 		else
357 			return Sound("Hits::SoftHit?");
358 }
359 
360 // Removes all objects of the given type.
361 // documented in /docs/sdk/script/fn
RemoveAll(p,...)362 global func RemoveAll(p, ...)
363 {
364 	var cnt;
365 	if (GetType(p) == C4V_PropList) p = Find_ID(p); // RemoveAll(ID) shortcut
366 	for (var obj in FindObjects(p, ...))
367 	{
368 		if (obj)
369 		{
370 			obj->RemoveObject();
371 			cnt++;
372 		}
373 	}
374 	return cnt;
375 }
376 
377 // Pulls an object above ground if it was buried (e.g. by PlaceVegetation), mainly used by plants.
378 // The object must have 'Bottom' and 'Center' CNAT to use this.
379 // (bottom is the point which should be buried, center the lowest point that must not be buried)
RootSurface(int max_movement)380 global func RootSurface(int max_movement)
381 {
382 	max_movement = max_movement ?? GetObjHeight() / 2;
383 
384 	if (HasCNAT(CNAT_Center))
385 	{
386 		var i = 0;
387 		// Move up if too far underground.
388 		while (GetContact(-1) & CNAT_Center && i < max_movement)
389 		{
390 			SetPosition(GetX(),	GetY()	-	1);
391 			i++;
392 		}
393 	}
394 	if (HasCNAT(CNAT_Bottom))
395 	{
396 		var i = 0;
397 		 // Move down if in midair.
398 		while (!(GetContact(-1) & CNAT_Bottom) && i < max_movement)
399 		{
400 			SetPosition(GetX(), GetY() + 1);
401 			i++;
402 		}
403 		// Try to make the plant stuck.
404 		if (!Stuck())
405 			SetPosition(GetX(), GetY() + 1);
406 	}
407 }
408 
409 // Buys an object. Returns the object if it could be bought.
410 // documented in /docs/sdk/script/fn
Buy(id buy_def,int for_plr,int pay_plr,object from_vendor,bool show_errors)411 global func Buy(id buy_def, int for_plr, int pay_plr, object from_vendor, bool show_errors)
412 {
413 	// if no vendor is given try this
414 	if (!from_vendor)
415 		from_vendor = this;
416 	// not a vendor?
417 	if (!from_vendor->~IsVendor())
418 		return nil;
419 	return from_vendor->DoBuy(buy_def, for_plr, pay_plr, nil, false, show_errors);
420 }
421 
422 // Sells an object. Returns true if it could be sold.
423 // documented in /docs/sdk/script/fn
Sell(int plr,object obj,object to_vendor)424 global func Sell(int plr, object obj, object to_vendor)
425 {
426 	// if no vendor is given try this
427 	if (!to_vendor)
428 		to_vendor = this;
429 	// not a vendor?
430 	if (!to_vendor->~IsVendor())
431 		return false;
432 	return to_vendor->DoSell(obj, plr);
433 }
434 
435 // GetXEdge returns the position of the objects top/bottom/left/right edge.
GetLeftEdge()436 global func GetLeftEdge()
437 {
438 	return GetX() - GetObjWidth() / 2;
439 }
440 
GetRightEdge()441 global func GetRightEdge()
442 {
443 	return GetX() + GetObjWidth() / 2;
444 }
445 
GetTopEdge()446 global func GetTopEdge()
447 {
448 	return GetY() - GetObjHeight() / 2;
449 }
450 
GetBottomEdge()451 global func GetBottomEdge()
452 {
453 	return GetY() + GetObjHeight() / 2;
454 }
455 
456 // Returns if the object is standing in front of the back-object
InFrontOf(object back)457 global func InFrontOf(object back)
458 {
459 	var front = this;
460 	if (!front)
461 		return;
462 	return front->FindObject(front->Find_AtPoint(), Find_Not(Find_Exclude(back))) != nil;
463 }
464 
465 // Returns the current left of the object relative to its current position.
GetLeft()466 global func GetLeft()
467 {
468 	var offset_x = GetObjectVal("Offset", nil, 0);
469 	if (offset_x == nil)
470 		offset_x = GetDefCoreVal("Offset", nil, 0);
471 	return offset_x;
472 }
473 
474 // Returns the current right of the object relative to its current position.
GetRight()475 global func GetRight()
476 {
477 	var offset_x = GetObjectVal("Offset", nil, 0);
478 	if (offset_x == nil)
479 		offset_x = GetDefCoreVal("Offset", nil, 0);
480 	var width = GetObjectVal("Width");
481 	return offset_x + width;
482 }
483 
484 // Returns the current top of the object relative to its current position.
GetTop()485 global func GetTop()
486 {
487 	var offset_y = GetObjectVal("Offset", nil, 1);
488 	if (offset_y == nil)
489 		offset_y = GetDefCoreVal("Offset", nil, 1);
490 	return offset_y;
491 }
492 
493 // Returns the current bottom of the object relative to its current position.
GetBottom()494 global func GetBottom()
495 {
496 	var offset_y = GetObjectVal("Offset", nil, 1);
497 	if (offset_y == nil)
498 		offset_y = GetDefCoreVal("Offset", nil, 1);
499 	var height = GetObjectVal("Height");
500 	return offset_y + height;
501 }
502 
503 // Returns the current shape as an array [offset_x, offset_y, width, height].
GetShape()504 global func GetShape()
505 {
506 	var offset_x = GetObjectVal("Offset", nil, 0);
507 	if (offset_x == nil)
508 		offset_x= GetDefCoreVal("Offset", nil, 0);
509 	var offset_y = GetObjectVal("Offset", nil, 1);
510 	if (offset_y == nil)
511 		offset_y = GetDefCoreVal("Offset", nil, 1);
512 	var width = GetObjectVal("Width");
513 	var height = GetObjectVal("Height");
514 	return [offset_x, offset_y, width, height];
515 }
516 
GetEntranceRectangle()517 global func GetEntranceRectangle()
518 {
519 	var entrance_x = GetDefCoreVal("Entrance", "DefCore", 0);
520 	var entrance_y = GetDefCoreVal("Entrance", "DefCore", 1);
521 	var entrance_wdt = GetDefCoreVal("Entrance", "DefCore", 2);
522 	var entrance_hgt = GetDefCoreVal("Entrance", "DefCore", 3);
523 	return [entrance_x, entrance_y, entrance_wdt, entrance_hgt];
524 }
525