1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 
18 /* Object motion, collision, friction */
19 
20 #include "C4Include.h"
21 
22 #include "game/C4Physics.h"
23 #include "landscape/C4Landscape.h"
24 #include "landscape/C4SolidMask.h"
25 #include "object/C4Def.h"
26 #include "object/C4Object.h"
27 #include "script/C4Effect.h"
28 
29 /* Some physical constants */
30 
31 const C4Real FRedirect=C4REAL100(50);
32 const C4Real FFriction=C4REAL100(30);
33 const C4Real FixFullCircle=itofix(360),FixHalfCircle=FixFullCircle/2;
34 const C4Real FloatFriction=C4REAL100(2);
35 const C4Real RotateAccel=C4REAL100(20);
36 const C4Real HitSpeed1=C4REAL100(150); // Hit Event
37 const C4Real HitSpeed2=itofix(2); // Cross Check Hit
38 const C4Real HitSpeed3=itofix(6); // Scale disable, kneel
39 const C4Real HitSpeed4=itofix(8); // Flat
40 const C4Real DefaultGravAccel=C4REAL100(20);
41 
42 /* Some helper functions */
43 
RedirectForce(C4Real & from,C4Real & to,int32_t tdir)44 void RedirectForce(C4Real &from, C4Real &to, int32_t tdir)
45 {
46 	C4Real fred;
47 	fred=std::min(Abs(from), FRedirect);
48 	from-=fred*Sign(from);
49 	to+=fred*tdir;
50 }
51 
ApplyFriction(C4Real & tval,int32_t percent)52 void ApplyFriction(C4Real &tval, int32_t percent)
53 {
54 	C4Real ffric=FFriction*percent/100;
55 	if (tval>+ffric) { tval-=ffric; return; }
56 	if (tval<-ffric) { tval+=ffric; return; }
57 	tval=0;
58 }
59 
60 // Compares all Shape.VtxContactCNAT[] CNAT flags to search flag.
61 // Returns true if CNAT match has been found.
62 
ContactVtxCNAT(C4Object * cobj,BYTE cnat_dir)63 bool ContactVtxCNAT(C4Object *cobj, BYTE cnat_dir)
64 {
65 	int32_t cnt;
66 	bool fcontact=false;
67 	for (cnt=0; cnt<cobj->Shape.VtxNum; cnt++)
68 		if (cobj->Shape.VtxContactCNAT[cnt] & cnat_dir)
69 			fcontact=true;
70 	return fcontact;
71 }
72 
73 // Finds first vertex with contact flag set.
74 // Returns -1/0/+1 for relation on vertex to object center.
75 
ContactVtxWeight(C4Object * cobj)76 int32_t ContactVtxWeight(C4Object *cobj)
77 {
78 	int32_t cnt;
79 	for (cnt=0; cnt<cobj->Shape.VtxNum; cnt++)
80 		if (cobj->Shape.VtxContactCNAT[cnt])
81 		{
82 			if (cobj->Shape.VtxX[cnt]<0) return -1;
83 			if (cobj->Shape.VtxX[cnt]>0) return +1;
84 		}
85 	return 0;
86 }
87 
88 // ContactVtxFriction: Returns 0-100 friction value of first
89 //                     contacted vertex;
90 
ContactVtxFriction(C4Object * cobj)91 int32_t ContactVtxFriction(C4Object *cobj)
92 {
93 	int32_t cnt;
94 	for (cnt=0; cnt<cobj->Shape.VtxNum; cnt++)
95 		if (cobj->Shape.VtxContactCNAT[cnt])
96 			return cobj->Shape.VtxFriction[cnt];
97 	return 0;
98 }
99 
CNATName(int32_t cnat)100 const char *CNATName(int32_t cnat)
101 {
102 	switch (cnat)
103 	{
104 	case CNAT_None:   return "None";
105 	case CNAT_Left:   return "Left";
106 	case CNAT_Right:  return "Right";
107 	case CNAT_Top:    return "Top";
108 	case CNAT_Bottom: return "Bottom";
109 	case CNAT_Center: return "Center";
110 	}
111 	return "Undefined";
112 }
113 
Contact(int32_t iCNAT)114 bool C4Object::Contact(int32_t iCNAT)
115 {
116 	if (GetPropertyInt(P_ContactCalls))
117 	{
118 		return !! Call(FormatString(PSF_Contact, CNATName(iCNAT)).getData());
119 	}
120 	return false;
121 }
122 
DoMotion(int32_t mx,int32_t my)123 void C4Object::DoMotion(int32_t mx, int32_t my)
124 {
125 	if (pSolidMaskData) pSolidMaskData->Remove(true);
126 	fix_x += mx; fix_y += my;
127 }
128 
StopAndContact(C4Real & ctco,C4Real limit,C4Real & speed,int32_t cnat)129 void C4Object::StopAndContact(C4Real & ctco, C4Real limit, C4Real & speed, int32_t cnat)
130 {
131 	ctco = limit;
132 	speed = 0;
133 	Contact(cnat);
134 }
135 
ContactCheck(int32_t iAtX,int32_t iAtY,uint32_t * border_hack_contacts,bool collide_halfvehic)136 int32_t C4Object::ContactCheck(int32_t iAtX, int32_t iAtY, uint32_t *border_hack_contacts, bool collide_halfvehic)
137 {
138 	// Check shape contact at given position
139 	Shape.ContactCheck(iAtX,iAtY,border_hack_contacts,collide_halfvehic);
140 
141 	// Store shape contact values in object t_contact
142 	t_contact=Shape.ContactCNAT;
143 
144 	// Contact script call for the first contacted cnat
145 	if (Shape.ContactCNAT)
146 		for (int32_t ccnat=0; ccnat<4; ccnat++) // Left, right, top bottom
147 			if (Shape.ContactCNAT & (1<<ccnat))
148 				if (Contact(1<<ccnat))
149 					break; // Will stop on first positive return contact call!
150 
151 	// Return shape contact count
152 	return Shape.ContactCount;
153 }
154 
155 // Stop the object and do contact calls if it collides with the border
SideBounds(C4Real & ctcox)156 void C4Object::SideBounds(C4Real &ctcox)
157 {
158 	// layer bounds
159 	if (Layer && Layer->GetPropertyInt(P_BorderBound) & C4D_Border_Layer)
160 	{
161 		C4PropList* pActionDef = GetAction();
162 		if (!pActionDef || pActionDef->GetPropertyP(P_Procedure) != DFA_ATTACH)
163 		{
164 			C4Real lbound = itofix(Layer->GetX() + Layer->Shape.GetX() - Shape.GetX()),
165 			       rbound = itofix(Layer->GetX() + Layer->Shape.GetX() + Layer->Shape.Wdt - (Shape.GetX() + Shape.Wdt));
166 			if (ctcox < lbound) StopAndContact(ctcox, lbound, xdir, CNAT_Left);
167 			if (ctcox > rbound) StopAndContact(ctcox, rbound, xdir, CNAT_Right);
168 		}
169 	}
170 	// landscape bounds
171 	C4Real lbound = itofix(0 - Shape.GetX()),
172 	       rbound = itofix(::Landscape.GetWidth() - (Shape.GetX() + Shape.Wdt));
173 	if (ctcox < lbound && GetPropertyInt(P_BorderBound) & C4D_Border_Sides)
174 		StopAndContact(ctcox, lbound, xdir, CNAT_Left);
175 	if (ctcox > rbound && GetPropertyInt(P_BorderBound) & C4D_Border_Sides)
176 		StopAndContact(ctcox, rbound, xdir, CNAT_Right);
177 }
178 
VerticalBounds(C4Real & ctcoy)179 void C4Object::VerticalBounds(C4Real &ctcoy)
180 {
181 	// layer bounds
182 	if (Layer && Layer->GetPropertyInt(P_BorderBound) & C4D_Border_Layer)
183 	{
184 		C4PropList* pActionDef = GetAction();
185 		if (!pActionDef || pActionDef->GetPropertyP(P_Procedure) != DFA_ATTACH)
186 		{
187 			C4Real tbound = itofix(Layer->GetY() + Layer->Shape.GetY() - Shape.GetY()),
188 			       bbound = itofix(Layer->GetY() + Layer->Shape.GetY() + Layer->Shape.Hgt - (Shape.GetY() + Shape.Hgt));
189 			if (ctcoy < tbound) StopAndContact(ctcoy, tbound, ydir, CNAT_Top);
190 			if (ctcoy > bbound) StopAndContact(ctcoy, bbound, ydir, CNAT_Bottom);
191 		}
192 	}
193 	// landscape bounds
194 	C4Real tbound = itofix(0 - Shape.GetY()),
195 	       bbound = itofix(::Landscape.GetHeight() - (Shape.GetY() + Shape.Hgt));
196 	if (ctcoy < tbound && GetPropertyInt(P_BorderBound) & C4D_Border_Top)
197 		StopAndContact(ctcoy, tbound, ydir, CNAT_Top);
198 	if (ctcoy > bbound && GetPropertyInt(P_BorderBound) & C4D_Border_Bottom)
199 		StopAndContact(ctcoy, bbound, ydir, CNAT_Bottom);
200 }
201 
DoMovement()202 void C4Object::DoMovement()
203 {
204 	int32_t iContact=0;
205 	bool fAnyContact=false; int iContacts = 0;
206 	BYTE fTurned=0,fRedirectYR=0,fNoAttach=0;
207 	// Restrictions
208 	if (Def->NoHorizontalMove) xdir=0;
209 	// Dig free target area
210 	C4PropList* pActionDef = GetAction();
211 	if (pActionDef)
212 		if (pActionDef->GetPropertyInt(P_DigFree))
213 		{
214 			int ctcox, ctcoy;
215 			// Shape size square
216 			if (pActionDef->GetPropertyInt(P_DigFree)==1)
217 			{
218 				ctcox=fixtoi(fix_x+xdir); ctcoy=fixtoi(fix_y+ydir);
219 				::Landscape.DigFreeRect(ctcox+Shape.GetX(),ctcoy+Shape.GetY(),Shape.Wdt,Shape.Hgt,this);
220 			}
221 			// Free size round (variable size)
222 			else
223 			{
224 				ctcox=fixtoi(fix_x+xdir); ctcoy=fixtoi(fix_y+ydir);
225 				int32_t rad = pActionDef->GetPropertyInt(P_DigFree);
226 				if (Con<FullCon) rad = rad*6*Con/5/FullCon;
227 				::Landscape.DigFree(ctcox,ctcoy-1,rad,this);
228 			}
229 		}
230 
231 	// store previous movement and ocf
232 	C4Real oldxdir(xdir), oldydir(ydir);
233 	uint32_t old_ocf = OCF;
234 
235 	bool fMoved = false;
236 	C4Real new_x = fix_x + xdir;
237 	C4Real new_y = fix_y + ydir;
238 	SideBounds(new_x);
239 
240 	if (!Action.t_attach) // Unattached movement  = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
241 	{
242 		// Horizontal movement - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
243 		// Move to target
244 		while (fixtoi(new_x) != fixtoi(fix_x))
245 		{
246 			// Next step
247 			int step = Sign(new_x - fix_x);
248 			uint32_t border_hack_contacts = 0;
249 			iContact=ContactCheck(GetX() + step, GetY(), &border_hack_contacts);
250 			if (iContact || border_hack_contacts)
251 			{
252 				fAnyContact=true; iContacts |= t_contact | border_hack_contacts;
253 			}
254 			if (iContact)
255 			{
256 				// Abort horizontal movement
257 				new_x = fix_x;
258 				// Vertical redirection (always)
259 				RedirectForce(xdir,ydir,-1);
260 				ApplyFriction(ydir,ContactVtxFriction(this));
261 			}
262 			else // Free horizontal movement
263 			{
264 				DoMotion(step, 0);
265 				fMoved = true;
266 			}
267 		}
268 		// Vertical movement - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
269 		// Movement target
270 		new_y = fix_y + ydir;
271 		// Movement bounds (vertical)
272 		VerticalBounds(new_y);
273 		// Move to target
274 		while (fixtoi(new_y) != fixtoi(fix_y))
275 		{
276 			// Next step
277 			int step = Sign(new_y - fix_y);
278 			if ((iContact=ContactCheck(GetX(), GetY() + step, nullptr, ydir > 0)))
279 			{
280 				fAnyContact=true; iContacts |= t_contact;
281 				new_y = fix_y;
282 				// Vertical contact horizontal friction
283 				ApplyFriction(xdir,ContactVtxFriction(this));
284 				// Redirection slide or rotate
285 				if (!ContactVtxCNAT(this,CNAT_Left))
286 					RedirectForce(ydir,xdir,-1);
287 				else if (!ContactVtxCNAT(this,CNAT_Right))
288 					RedirectForce(ydir,xdir,+1);
289 				else
290 				{
291 					// living things are always capable of keeping their rotation
292 					if (OCF & OCF_Rotate) if (iContact==1) if (!Alive)
293 							{
294 								RedirectForce(ydir,rdir,-ContactVtxWeight(this));
295 								fRedirectYR=1;
296 							}
297 					ydir=0;
298 				}
299 			}
300 			else // Free vertical movement
301 			{
302 				DoMotion(0,step);
303 				fMoved = true;
304 			}
305 		}
306 	}
307 	if (Action.t_attach) // Attached movement = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
308 	{
309 		VerticalBounds(new_y);
310 		// Move to target
311 		do
312 		{
313 			// Set next step target
314 			int step_x = 0, step_y = 0;
315 			if (fixtoi(new_x) != GetX())
316 				step_x = Sign(fixtoi(new_x) - GetX());
317 			else if (fixtoi(new_y) != GetY())
318 				step_y = Sign(fixtoi(new_y) - GetY());
319 			int32_t ctx = GetX() + step_x;
320 			int32_t cty = GetY() + step_y;
321 			// Attachment check
322 			if (!Shape.Attach(ctx,cty,Action.t_attach))
323 				fNoAttach=1;
324 			else
325 			{
326 				// Attachment change to ctx/cty overrides target
327 				if (ctx != GetX() + step_x)
328 				{
329 					xdir = Fix0; new_x = itofix(ctx);
330 				}
331 				if (cty != GetY() + step_y)
332 				{
333 					ydir = Fix0; new_y = itofix(cty);
334 				}
335 			}
336 			// Contact check & evaluation
337 			uint32_t border_hack_contacts = 0;
338 			iContact=ContactCheck(ctx,cty,&border_hack_contacts);
339 			if (iContact || border_hack_contacts)
340 			{
341 				fAnyContact=true; iContacts |= border_hack_contacts | t_contact;
342 			}
343 			if (iContact)
344 			{
345 				// Abort movement
346 				if (ctx != GetX())
347 				{
348 					ctx = GetX(); new_x = fix_x;
349 				}
350 				if (cty != GetY())
351 				{
352 					cty = GetY(); new_y = fix_y;
353 				}
354 			}
355 			DoMotion(ctx - GetX(), cty - GetY());
356 			fMoved = true;
357 		}
358 		while (fixtoi(new_x) != GetX() || fixtoi(new_y) != GetY());
359 	}
360 
361 	if(fix_x != new_x || fix_y != new_y)
362 	{
363 		fMoved = true;
364 		if (pSolidMaskData) pSolidMaskData->Remove(true);
365 		fix_x = new_x;
366 		fix_y = new_y;
367 	}
368 	// Rotation  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
369 	if (OCF & OCF_Rotate && !!rdir)
370 	{
371 		C4Real target_r = fix_r + rdir * 5;
372 		// Rotation limit
373 		if (Def->Rotateable>1)
374 		{
375 			if (target_r > itofix(Def->Rotateable))
376 				{ target_r = itofix(Def->Rotateable); rdir=0; }
377 			if (target_r < itofix(-Def->Rotateable))
378 				{ target_r = itofix(-Def->Rotateable); rdir=0; }
379 		}
380 		int32_t ctx=GetX(); int32_t cty=GetY();
381 		// Move to target
382 		while (fixtoi(fix_r) != fixtoi(target_r))
383 		{
384 			// Save step undos
385 			C4Real lcobjr = fix_r; C4Shape lshape=Shape;
386 			// Try next step
387 			fix_r += Sign(target_r - fix_r);
388 			UpdateShape();
389 			// attached rotation: rotate around attachment pos
390 			if (Action.t_attach && !fNoAttach)
391 			{
392 				// more accurately, attachment should be evaluated by a rotation around the attachment vertex
393 				// however, as long as this code is only used for some surfaces adjustment for large vehicles,
394 				// it's enough to assume rotation around the center
395 				ctx=GetX(); cty=GetY();
396 				// evaluate attachment, but do not bother about attachment loss
397 				// that will then be done in next execution cycle
398 				Shape.Attach(ctx,cty,Action.t_attach);
399 			}
400 			// check for contact
401 			if ((iContact=ContactCheck(ctx,cty))) // Contact
402 			{
403 				fAnyContact=true; iContacts |= t_contact;
404 				// Undo step and abort movement
405 				Shape=lshape;
406 				target_r = fix_r = lcobjr;
407 				// last UpdateShape-call might have changed sector lists!
408 				UpdatePos();
409 				// Redirect to GetY()
410 				if (iContact==1) if (!fRedirectYR)
411 						RedirectForce(rdir,ydir,-1);
412 				// Stop rotation
413 				rdir=0;
414 			}
415 			else
416 			{
417 				fTurned=1;
418 				if (ctx != GetX() || cty != GetY())
419 				{
420 					fix_x = itofix(ctx); fix_y = itofix(cty);
421 				}
422 			}
423 		}
424 		// Circle bounds
425 		if (target_r < -FixHalfCircle) { target_r += FixFullCircle; }
426 		if (target_r > +FixHalfCircle) { target_r -= FixFullCircle; }
427 		fix_r = target_r;
428 	}
429 	// Reput solid mask if moved by motion
430 	if (fMoved || fTurned) UpdateSolidMask(true);
431 	// Misc checks ===========================================================================================
432 	// InLiquid check
433 	// this equals C4Object::UpdateLiquid, but the "fNoAttach=false;"-line
434 	if (IsInLiquidCheck()) // In Liquid
435 	{
436 		if (!InLiquid) // Enter liquid
437 		{
438 			if (OCF & OCF_HitSpeed2)
439 				if (Mass>3) Splash();
440 			fNoAttach=false;
441 			InLiquid=true;
442 		}
443 	}
444 	else // Out of liquid
445 	{
446 		if (InLiquid) // Leave liquid
447 			InLiquid=false;
448 	}
449 	// Contact Action
450 	if (fAnyContact)
451 	{
452 		t_contact = iContacts;
453 		ContactAction();
454 	}
455 	// Attachment Loss Action
456 	if (fNoAttach)
457 		NoAttachAction();
458 	// Movement Script Execution
459 	if (fAnyContact)
460 	{
461 		C4AulParSet pars(fixtoi(oldxdir, 100), fixtoi(oldydir, 100));
462 		if (old_ocf & OCF_HitSpeed1) Call(PSF_Hit, &pars);
463 		if (old_ocf & OCF_HitSpeed2) Call(PSF_Hit2, &pars);
464 		if (old_ocf & OCF_HitSpeed3) Call(PSF_Hit3, &pars);
465 	}
466 	// Rotation gfx
467 	if (fTurned)
468 		UpdateFace(true);
469 	else
470 		// pos changed?
471 		if (fMoved) UpdatePos();
472 }
473 
Stabilize()474 void C4Object::Stabilize()
475 {
476 	// def allows stabilization?
477 	if (Def->NoStabilize) return;
478 	// normalize angle
479 	C4Real nr = fix_r;
480 	while (nr < itofix(-180)) nr += 360;
481 	while (nr > itofix(180)) nr -= 360;
482 	if (nr != Fix0)
483 		if (Inside<C4Real>(nr,itofix(-StableRange),itofix(+StableRange)))
484 		{
485 			// Save step undos
486 			C4Real lcobjr=fix_r;
487 			C4Shape lshape=Shape;
488 			// Try rotation
489 			fix_r=Fix0;
490 			UpdateShape();
491 			if (ContactCheck(GetX(),GetY()))
492 			{ // Undo rotation
493 				Shape=lshape;
494 				fix_r=lcobjr;
495 			}
496 			else
497 			{ // Stabilization okay
498 				UpdateFace(true);
499 			}
500 		}
501 }
502 
CopyMotion(C4Object * from)503 void C4Object::CopyMotion(C4Object *from)
504 {
505 	// Designed for contained objects, no static
506 	if (fix_x != from->fix_x || fix_y != from->fix_y)
507 	{
508 		fix_x=from->fix_x; fix_y=from->fix_y;
509 		// Resort into sectors
510 		UpdatePos();
511 	}
512 	xdir=from->xdir; ydir=from->ydir;
513 }
514 
ForcePosition(C4Real tx,C4Real ty)515 void C4Object::ForcePosition(C4Real tx, C4Real ty)
516 {
517 	fix_x=tx; fix_y=ty;
518 	UpdatePos();
519 	UpdateSolidMask(false);
520 }
521 
MovePosition(int32_t dx,int32_t dy)522 void C4Object::MovePosition(int32_t dx, int32_t dy)
523 {
524 	MovePosition(itofix(dx), itofix(dy));
525 }
526 
MovePosition(C4Real dx,C4Real dy)527 void C4Object::MovePosition(C4Real dx, C4Real dy)
528 {
529 	// move object position; repositions SolidMask
530 	if (pSolidMaskData) pSolidMaskData->Remove(true);
531 	fix_x+=dx;
532 	fix_y+=dy;
533 	UpdatePos();
534 	UpdateSolidMask(true);
535 }
536 
537 
ExecMovement()538 bool C4Object::ExecMovement() // Every Tick1 by Execute
539 {
540 	// update in which material this object is
541 	UpdateInMat();
542 
543 	// Containment check
544 	if (Contained)
545 	{
546 		CopyMotion(Contained);
547 
548 		return true;
549 	}
550 
551 	// General mobility check
552 	if (Category & C4D_StaticBack) return false;
553 
554 	// Movement execution
555 	if (Mobile) // Object is moving
556 	{
557 		// Move object
558 		DoMovement();
559 		// Demobilization check
560 		if ((xdir==0) && (ydir==0) && (rdir==0)) Mobile=false;
561 		// Check for stabilization
562 		if (rdir==0) Stabilize();
563 	}
564 	else // Object is static
565 	{
566 		// Check for stabilization
567 		Stabilize();
568 		// Check for mobilization
569 		if (!::Game.iTick10)
570 		{
571 			// Gravity mobilization
572 			xdir=ydir=rdir=0;
573 			Mobile=true;
574 		}
575 	}
576 
577 	// Enforce zero rotation
578 	if (!Def->Rotateable) fix_r=Fix0;
579 
580 	// Out of bounds check
581 	if ((!Inside<int32_t>(GetX() + Shape.GetX(), -Shape.Wdt, ::Landscape.GetWidth()) && !(GetPropertyInt(P_BorderBound) & C4D_Border_Sides))
582 	    || ((GetY() + Shape.GetY() > ::Landscape.GetHeight()) && !(GetPropertyInt(P_BorderBound) & C4D_Border_Bottom)))
583 	{
584 		C4PropList* pActionDef = GetAction();
585 		// Never remove attached objects: If they are truly outside landscape, their target will be removed,
586 		//  and the attached objects follow one frame later
587 		if (!pActionDef || !Action.Target || pActionDef->GetPropertyP(P_Procedure) != DFA_ATTACH)
588 		{
589 			bool fRemove = true;
590 			// never remove HUD objects
591 			if (Category & C4D_Parallax)
592 			{
593 				int parX, parY;
594 				GetParallaxity(&parX, &parY);
595 				fRemove = false;
596 				if (GetX()>::Landscape.GetWidth() || GetY()>::Landscape.GetHeight()) fRemove = true; // except if they are really out of the viewport to the right...
597 				else if (GetX()<0 && !!parX) fRemove = true; // ...or it's not HUD horizontally and it's out to the left
598 				else if (!parX && GetX()<-::Landscape.GetWidth()) fRemove = true; // ...or it's HUD horizontally and it's out to the left
599 			}
600 			if (fRemove)
601 			{
602 				AssignDeath(true);
603 				AssignRemoval();
604 			}
605 		}
606 	}
607 	return true;
608 }
609 
SimFlight(C4Real & x,C4Real & y,C4Real & xdir,C4Real & ydir,int32_t iDensityMin,int32_t iDensityMax,int32_t & iIter)610 bool SimFlight(C4Real &x, C4Real &y, C4Real &xdir, C4Real &ydir, int32_t iDensityMin, int32_t iDensityMax, int32_t &iIter)
611 {
612 	bool hitOnTime = true;
613 	bool fBreak = false;
614 	int32_t ctcox,ctcoy,cx,cy,i;
615 	cx = fixtoi(x); cy = fixtoi(y);
616 	i = iIter;
617 	do
618 	{
619 		if (!--i) {hitOnTime = false; break;}
620 		// If the object isn't moving and there is no gravity either, abort
621 		if (xdir == 0 && ydir == 0 && GravAccel == 0)
622 			return false;
623 		// If the object is above the landscape flying upwards in no/negative gravity, abort
624 		if (ydir <= 0 && GravAccel <= 0 && cy < 0)
625 			return false;
626 		// Set target position by momentum
627 		x+=xdir; y+=ydir;
628 		// Movement to target
629 		ctcox=fixtoi(x); ctcoy=fixtoi(y);
630 		// Bounds
631 		if (!Inside<int32_t>(ctcox,0,::Landscape.GetWidth()) || (ctcoy>=::Landscape.GetHeight()))
632 			return false;
633 		// Move to target
634 		do
635 		{
636 			// Set next step target
637 			cx+=Sign(ctcox-cx); cy+=Sign(ctcoy-cy);
638 			// Contact check
639 			if (Inside(GBackDensity(cx,cy), iDensityMin, iDensityMax))
640 				{ fBreak = true; break; }
641 		}
642 		while ((cx!=ctcox) || (cy!=ctcoy));
643 		// Adjust GravAccel once per frame
644 		ydir+=GravAccel;
645 	}
646 	while (!fBreak);
647 	// write position back
648 	x = itofix(cx); y = itofix(cy);
649 
650 	// how many steps did it take to get here?
651 	iIter -= i;
652 
653 	return hitOnTime;
654 }
655 
SimFlightHitsLiquid(C4Real fcx,C4Real fcy,C4Real xdir,C4Real ydir)656 bool SimFlightHitsLiquid(C4Real fcx, C4Real fcy, C4Real xdir, C4Real ydir)
657 {
658 	// Start in water?
659 	int temp;
660 	if (DensityLiquid(GBackDensity(fixtoi(fcx), fixtoi(fcy))))
661 		if (!SimFlight(fcx, fcy, xdir, ydir, 0, C4M_Liquid - 1, temp=10))
662 			return false;
663 	// Hits liquid?
664 	if (!SimFlight(fcx, fcy, xdir, ydir, C4M_Liquid, 100, temp=-1))
665 		return false;
666 	// liquid & deep enough?
667 	return GBackLiquid(fixtoi(fcx), fixtoi(fcy)) && GBackLiquid(fixtoi(fcx), fixtoi(fcy) + 9);
668 }
669 
670