1 /******************************************************************************
2  *  Warmux is a convivial mass murder game.
3  *  Copyright (C) 2001-2011 Warmux Team.
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
18  ******************************************************************************
19  * Abstract class used for physical object (object with a size, mass,
20  * etc.). This object can have differents state : ready, is moving, or ghost
21  * (is outside of the world).
22  *
23  * You can : make the object move (with collision test), change state, etc.
24  * If the object go outside of the world, it become a ghost.
25  *****************************************************************************/
26 
27 #include <iostream>
28 
29 #include <WARMUX_debug.h>
30 #include <WARMUX_point.h>
31 #include <WARMUX_random.h>
32 #include <WARMUX_rectangle.h>
33 
34 #include "character/character.h"
35 #include "game/config.h"
36 #include "game/game_time.h"
37 #include "map/map.h"
38 #include "network/randomsync.h"
39 #include "object/physical_obj.h"
40 #include "object/physics.h"
41 #include "object/objects_list.h"
42 #include "sound/jukebox.h"
43 #include "team/macro.h"
44 #include "team/team.h"
45 #include "team/teams_list.h"
46 #include "tool/isnan.h"
47 #include "tool/math_tools.h"
48 #include "tool/string_tools.h"
49 #include "weapon/weapon_launcher.h"
50 
51 #define Y_OBJET_MIN  -10000
52 
PhysicalObj(const std::string & name,const std::string & xml_config)53 PhysicalObj::PhysicalObj(const std::string &name, const std::string &xml_config) :
54   m_collides_with_ground(true),
55   m_collides_with_characters(false),
56   m_collides_with_objects(false),
57   m_go_through_objects(false),
58   m_last_collided_object(NULL),
59   m_rebound_position(-1,-1),
60   m_test_left(0),
61   m_test_right(0),
62   m_test_top(0),
63   m_test_bottom(0),
64   m_width(0),
65   m_height(0),
66   can_be_ghost(true),
67   // No collision with this object until we have gone out of his collision rectangle
68   m_overlapping_object(NULL),
69   m_minimum_overlapse_time(0),
70   m_ignore_movements(false),
71   m_is_character(false),
72   m_is_fire(false),
73   m_name(name),
74   m_rebound_sound(""),
75   m_alive(ALIVE),
76   m_energy(-1),
77   m_allow_negative_y(false)
78 {
79   m_cfg = Config::GetInstance()->GetObjectConfig(m_name, xml_config);
80   ResetConstants();       // Set physics constants from the xml file
81 
82   MSG_DEBUG("physical.mem", "Construction of %s", GetName().c_str());
83 }
84 
~PhysicalObj()85 PhysicalObj::~PhysicalObj()
86 {
87   MSG_DEBUG("physical.mem", "Destruction of %s", GetName().c_str());
88 }
89 
90 //---------------------------------------------------------------------------//
91 //--                         Class Parameters SET/GET                      --//
92 //---------------------------------------------------------------------------//
93 
SetXY(const Point2d & position)94 void PhysicalObj::SetXY(const Point2d &position)
95 {
96   CheckOverlapping();
97 
98   // Don't use METER_PER_PIXEL here: bad truncation occurs
99   if (IsOutsideWorldXY(Point2i(position.x, position.y)) && can_be_ghost) {
100     SetPhysXY(position / PIXEL_PER_METER);
101     Ghost();
102     SignalOutOfMap();
103   } else {
104     SetPhysXY(position / PIXEL_PER_METER);
105     if (FootsInVacuum())
106       StartMoving();
107   }
108 }
109 
SetSize(const Point2i & newSize)110 void PhysicalObj::SetSize(const Point2i &newSize)
111 {
112   if (newSize == Point2i(0, 0))
113     Error("New size of (0, 0) !");
114   m_width = newSize.x;
115   m_height = newSize.y;
116 
117   ASSERT(m_width >= 0);
118   ASSERT(m_height >= 0);
119   SetPhysSize(newSize.x*METER_PER_PIXEL, newSize.y*METER_PER_PIXEL);
120 }
121 
SetOverlappingObject(PhysicalObj * obj,int timeout)122 void PhysicalObj::SetOverlappingObject(PhysicalObj* obj, int timeout)
123 {
124   m_minimum_overlapse_time = 0;
125   m_last_collided_object = obj;
126   if (obj) {
127     m_overlapping_object = obj;
128     ObjectsList::GetRef().AddOverlappedObject(this);
129     MSG_DEBUG("physic.overlapping", "\"%s\" doesn't check any collision with \"%s\" anymore during %d ms",
130               GetName().c_str(), obj->GetName().c_str(), timeout);
131   } else {
132     if (m_overlapping_object) {
133       m_overlapping_object = NULL;
134       ObjectsList::GetRef().RemoveOverlappedObject(this);
135       MSG_DEBUG("physic.overlapping", "clearing overlapping object in \"%s\"", GetName().c_str());
136     }
137     return;
138   }
139   if (timeout > 0)
140     m_minimum_overlapse_time = GameTime::GetInstance()->Read() + timeout;
141 
142   CheckOverlapping();
143 }
144 
CheckOverlapping()145 void PhysicalObj::CheckOverlapping()
146 {
147   if (!m_overlapping_object)
148     return;
149 
150   // Check if we are still overlapping with this object
151   if (!m_overlapping_object->GetTestRect().Intersect(GetTestRect()) &&
152       m_minimum_overlapse_time <= GameTime::GetInstance()->Read()) {
153     MSG_DEBUG("physic.overlapping", "\"%s\" just stopped overlapping with \"%s\" (%d ms left)",
154               GetName().c_str(), m_overlapping_object->GetName().c_str(),
155               (m_minimum_overlapse_time - GameTime::GetInstance()->Read()));
156     SetOverlappingObject(NULL);
157   } else {
158     MSG_DEBUG("physic.overlapping", "\"%s\" is overlapping with \"%s\"",
159               GetName().c_str(), m_overlapping_object->GetName().c_str());
160   }
161 }
162 
SetTestRect(uint left,uint right,uint top,uint bottom)163 void PhysicalObj::SetTestRect(uint left, uint right, uint top, uint bottom)
164 {
165   m_test_left =  left;
166   m_test_right = right;
167   m_test_top = top;
168   m_test_bottom = bottom;
169 
170   ASSERT(m_test_left >= 0);
171   ASSERT(m_test_right >= 0);
172   ASSERT(m_test_top >= 0);
173   ASSERT(m_test_bottom >= 0);
174 }
175 
SetEnergyDelta(int delta,Character *)176 void PhysicalObj::SetEnergyDelta(int delta, Character* /*dealer*/)
177 {
178   if (m_energy == -1)
179     return;
180   m_energy += delta;
181 
182   if (m_energy <= 0 && !IsGhost()) {
183     Ghost();
184     m_energy = -1;
185   }
186 }
187 
188 // Move to a point with collision test
NotifyMove(Point2d oldPos,Point2d newPos)189 collision_t PhysicalObj::NotifyMove(Point2d oldPos, Point2d newPos)
190 {
191   if (IsGhost())
192     return NO_COLLISION;
193 
194   Point2d contactPos;
195   Double contactAngle;
196   Point2d pos, offset;
197   PhysicalObj* collided_obj = NULL;
198 
199   collision_t collision = NO_COLLISION;
200 
201   // Convert meters to pixels.
202   oldPos *= PIXEL_PER_METER;
203   newPos *= PIXEL_PER_METER;
204 
205   // Compute distance between old and new position.
206   Double lg = oldPos.SquareDistance(newPos);
207 
208   MSG_DEBUG("physic.move", "%s moves (%s, %s) -> (%s, %s), square distance: %s",
209             GetName().c_str(),
210             Double2str(oldPos.x).c_str(), Double2str(oldPos.y).c_str(),
211             Double2str(newPos.x).c_str(), Double2str(newPos.y).c_str(),
212             Double2str(lg).c_str());
213 
214   if (!lg.IsNotZero())
215     return NO_COLLISION;
216 
217   // Compute increments to move the object step by step from the old
218   // to the new position.
219   lg = sqrt(lg);
220   offset = (newPos - oldPos) / lg;
221 
222   // First iteration position.
223   pos = oldPos + offset;
224 
225   if (!m_collides_with_ground || IsInWater()) {
226     MSG_DEBUG("physic.move", "%s moves (%s, %s) -> (%s, %s), collides ground:%d, water:%d",
227               GetName().c_str(),
228               Double2str(oldPos.x).c_str(), Double2str(oldPos.y).c_str(),
229               Double2str(newPos.x).c_str(), Double2str(newPos.y).c_str(),
230               m_collides_with_ground, IsInWater());
231 
232     SetXY(newPos);
233     return NO_COLLISION;
234   }
235 
236   do {
237     Point2i tmpPos(uround(pos.x), uround(pos.y));
238 
239     // Check if we exit the GetWorld(). If so, we stop moving and return.
240     if (IsOutsideWorldXY(tmpPos)) {
241 
242       if (!GetWorld().IsOpen()) {
243         tmpPos.x = InRange_Long(tmpPos.x, 0, GetWorld().GetWidth() - GetWidth() - 1);
244         tmpPos.y = InRange_Long(tmpPos.y, 0, GetWorld().GetHeight() - GetHeight() - 1);
245         MSG_DEBUG("physic.state", "%s - DeplaceTestCollision touche un bord : %d, %d",
246                   GetName().c_str(), tmpPos.x, tmpPos.y);
247         collision = COLLISION_ON_GROUND;
248         break;
249       }
250 
251       SetXY(pos);
252 
253       MSG_DEBUG("physic.move", "%s moves (%f, %f) -> (%f, %f) : OUTSIDE WORLD",
254                 GetName().c_str(), oldPos.x.tofloat(), oldPos.y.tofloat(),
255                 newPos.x.tofloat(), newPos.y.tofloat());
256       return NO_COLLISION;
257     }
258 
259     // Test if we collide...
260     collided_obj = CollidedObjectXY(tmpPos);
261     if (collided_obj) {
262       if (!m_go_through_objects || m_last_collided_object != collided_obj) {
263         MSG_DEBUG("physic.state", "%s collide on %s", GetName().c_str(), collided_obj->GetName().c_str());
264 
265         if (m_go_through_objects) {
266           SignalObjectCollision(GetSpeed(), collided_obj, collided_obj->GetSpeed());
267           collision = NO_COLLISION;
268         } else {
269           collision = COLLISION_ON_OBJECT;
270         }
271         m_last_collided_object = collided_obj;
272       } else {
273         collided_obj = NULL;
274         collision = NO_COLLISION;
275       }
276     } else if (!IsInVacuumXY(tmpPos, false)) {
277       collision = COLLISION_ON_GROUND;
278       m_last_collided_object = NULL;
279     }
280 
281     if (collision != NO_COLLISION) {
282       // Nothing more to do!
283       MSG_DEBUG("physic.state", "%s - Collision at %d,%d : on %s",
284                 GetName().c_str(), tmpPos.x, tmpPos.y,
285                 collision == COLLISION_ON_GROUND ? "ground" : "an object");
286 
287       // Set the object position to the current position.
288       SetXY(Point2d(pos.x - offset.x, pos.y - offset.y));
289       break;
290     }
291 
292     // Next motion step
293     pos += offset;
294     lg -= ONE;
295   } while (ZERO < lg);
296 
297   Point2d speed_before_collision = GetSpeed();
298   Point2d speed_collided_obj;
299   if (collided_obj) {
300     speed_collided_obj = collided_obj->GetSpeed();
301   }
302 
303   ContactPointAngleOnGround(pos, contactPos, contactAngle);
304 
305   Collide(collision, collided_obj, pos);
306 
307   // ===================================
308   // it's time to signal object(s) about collision!
309   // WARNING: the following calls can send Action(s) over the network (cf bug #11232)
310   // Be sure to keep it isolated here
311   // ===================================
312   ActiveTeam().AccessWeapon().NotifyMove(!!collision);
313   switch (collision) {
314   case NO_COLLISION:
315     // Nothing more to do!
316     break;
317   case COLLISION_ON_GROUND:
318     SignalGroundCollision(speed_before_collision, contactAngle);
319     break;
320   case COLLISION_ON_OBJECT:
321     SignalObjectCollision(speed_before_collision, collided_obj, speed_collided_obj);
322     collided_obj->SignalObjectCollision(speed_collided_obj, this, speed_before_collision);
323     break;
324   }
325   // ===================================
326 
327   return collision;
328 }
329 
Collide(collision_t collision,PhysicalObj * collided_obj,const Point2d & position)330 void PhysicalObj::Collide(collision_t collision, PhysicalObj* collided_obj, const Point2d& position)
331 {
332   Point2d contactPos;
333   Double contactAngle;
334 
335   switch (collision) {
336   case NO_COLLISION:
337     // Nothing more to do!
338     return;
339 
340   case COLLISION_ON_GROUND:
341     ContactPointAngleOnGround(position, contactPos, contactAngle);
342     ASSERT(!collided_obj);
343     break;
344 
345   case COLLISION_ON_OBJECT:
346     contactPos = position;
347     contactAngle = - GetSpeedAngle();
348 
349     // Compute the new speed norm of this and collided_obj, new speed angle will be set
350     // thanks to Rebound()
351 
352     // Get the current speed
353     Double v1, v2, mass1, angle1, angle2, mass2;
354     collided_obj->GetSpeed(v1, angle1);
355     GetSpeed(v2, angle2);
356     mass1 = GetMass();
357     mass2 = collided_obj->GetMass();
358 
359     // Give speed to the other object
360     // thanks to physic and calculations about chocs, we know that :
361     //
362     // v'1 =  ((m1 - m2) * v1 + 2m1 *v2) / (m1 + m2)
363     // v'2 =  ((m2 - m1) * v2 + 2m1 *v1) / (m1 + m2)
364     collided_obj->SetSpeed(abs(((mass1 - mass2) * v1 + 2 * mass1 *v2 * m_cfg.m_rebound_factor) / (mass1 + mass2)),
365                            angle1);
366     SetSpeed(abs(((mass2 - mass1) * v2 + 2 * mass1 *v1 * m_cfg.m_rebound_factor) / (mass1 + mass2)), angle2);
367     break;
368   }
369 
370   // Mark it as last collided object
371   m_last_collided_object = collided_obj;
372 
373   // Make it rebound!!
374   MSG_DEBUG("physic.state", "%s rebounds at %.3f,%.3f", GetName().c_str(),
375             contactPos.x.tofloat(), contactPos.y.tofloat());
376 
377   Rebound(contactPos, contactAngle);
378   CheckRebound();
379 }
380 
ContactPointAngleOnGround(const Point2d & oldPos,Point2d & contactPos,Double & contactAngle) const381 void PhysicalObj::ContactPointAngleOnGround(const Point2d& oldPos,
382                                             Point2d& contactPos,
383                                             Double& contactAngle) const
384 {
385   // Find the contact point and collision angle.
386   // !!! ContactPoint(...) _can_ return false when CollisionTest(...) is true !!!
387   // !!! WeaponProjectiles collide on objects, so computing the tangeante to the ground leads
388   // !!! uninitialised values of cx and cy!!
389   // if( ContactPoint(cx, cy) ){
390   int cx, cy;
391 
392   if (ContactPoint(cx, cy)) {
393     contactAngle = GetWorld().ground.Tangent(cx, cy);
394     if (!isNaN(contactAngle)) {
395       contactPos.x = (Double)cx * METER_PER_PIXEL;
396       contactPos.y = (Double)cy * METER_PER_PIXEL;
397     } else {
398       contactAngle = - GetSpeedAngle();
399       contactPos = oldPos;
400     }
401   } else {
402     contactAngle = - GetSpeedAngle();
403     contactPos = oldPos;
404   }
405 }
406 
UpdatePosition()407 void PhysicalObj::UpdatePosition()
408 {
409   // No ghost allowed here !
410   if (IsGhost()) {
411     return;
412   }
413 
414   if (m_collides_with_ground) {
415     // object is not moving
416     if (!IsMoving()) {
417       // and has no reason to move
418       if (!FootsInVacuum()) {
419         if (!IsInWater()) {
420           return;
421         }
422       } else {
423         // it should fall !
424         StartMoving();
425       }
426     }
427   }
428 
429   // Compute new position.
430   RunPhysicalEngine();
431 
432   if (IsGhost()) {
433     return;
434   }
435 
436   // Classical object sometimes sinks in water and sometimes goes out of water!
437   if (m_collides_with_ground) {
438     if (IsInWater() && m_alive!=DROWNED && m_alive!=DEAD) Drown();
439     else if (!IsInWater() && m_alive==DROWNED) GoOutOfWater();
440   }
441 }
442 
PutOutOfGround(Double direction,Double max_distance)443 bool PhysicalObj::PutOutOfGround(Double direction, Double max_distance)
444 {
445   if (IsOutsideWorld(Point2i(0, 0)))
446     return false;
447 
448   if (IsInVacuum(Point2i(0, 0), false))
449     return true;
450 
451   Double dx = cos(direction);
452   Double dy = sin(direction);
453   // (dx,dy) is a normal vector (cos^2+sin^2==1)
454 
455   Double step=1;
456   while (step<max_distance &&
457          !IsInVacuum(Point2i(dx * step, dy * step), false))
458     step+=1.0;
459 
460   if (step<max_distance)
461     SetXY(Point2i(dx*step + GetX(), dy*step + GetY()));
462   else
463     return false; //Can't put the object out of the ground
464 
465   return true;
466 }
467 
PutOutOfGround()468 bool PhysicalObj::PutOutOfGround()
469 {
470   if (IsOutsideWorld(Point2i(0, 0)))
471     return false;
472 
473   if (IsInVacuum(Point2i(0, 0)))
474     return true;
475 
476   bool left,right,top,bottom;
477   left   = GetWorld().IsInVacuum_left(*this, 0, 0);
478   right  = GetWorld().IsInVacuum_right(*this, 0, 0);
479   top    = GetWorld().IsInVacuum_top(*this, 0, 0);
480   bottom = GetWorld().IsInVacuum_bottom(*this, 0, 0);
481 
482   int dx = (int)GetTestRect().GetSizeX() * (right-left);
483   int dy = (int)GetTestRect().GetSizeY() * (top-bottom);
484 
485   if (!dx && !dy)
486     return false; //->Don't know in which direction we should go...
487 
488   Point2i b(dx, dy);
489 
490   Double dir = b.ComputeAngle();
491   return PutOutOfGround(dir);
492 }
493 
Init()494 void PhysicalObj::Init()
495 {
496   if (m_alive != ALIVE)
497     MSG_DEBUG("physic.state", "%s - Init.", GetName().c_str());
498   m_alive = ALIVE;
499   SetOverlappingObject(NULL);
500   StopMoving();
501 }
502 
Ghost()503 void PhysicalObj::Ghost()
504 {
505   if (m_alive == GHOST)
506     return;
507 
508   bool was_dead = IsDead();
509   m_alive = GHOST;
510   MSG_DEBUG("physic.state", "%s - Ghost, was_dead = %d", GetName().c_str(), was_dead);
511 
512   // The object became a gost
513   StopMoving();
514 
515   SignalGhostState(was_dead);
516 }
517 
Drown()518 void PhysicalObj::Drown()
519 {
520   ASSERT (m_alive != DROWNED);
521   MSG_DEBUG("physic.state", "%s - Drowned...", GetName().c_str());
522   m_alive = DROWNED;
523 
524   // Set the air grab to water resist factor.
525   SetAirResistFactor(m_cfg.m_water_resist_factor);
526 
527   // Ensure the gravity factor is upper than 0.0
528   if (EqualsZero(GetGravityFactor()))
529     SetGravityFactor(0.1);
530 
531   // If fire, do smoke...
532   if (m_is_fire)
533     GetWorld().water.Smoke(GetPosition());
534   // make a splash in the water :-)
535   else if (GetMass() >= 2 && GetName() != "water_particle")
536     GetWorld().water.Splash(GetPosition());
537 
538   StopMoving();
539   StartMoving();
540   SignalDrowning();
541 }
542 
GoOutOfWater()543 void PhysicalObj::GoOutOfWater()
544 {
545   ASSERT (m_alive == DROWNED);
546   MSG_DEBUG("physic.state", "%s - Go out of water!...", GetName().c_str());
547   m_alive = ALIVE;
548 
549   // Set the air grab to normal air resist factor.
550   SetAirResistFactor(m_cfg.m_air_resist_factor);
551   SetGravityFactor(m_cfg.m_gravity_factor);
552   StartMoving();
553   SignalGoingOutOfWater();
554 }
555 
SignalRebound()556 void PhysicalObj::SignalRebound()
557 {
558   // TO CLEAN...
559   if (!m_rebound_sound.empty())
560     JukeBox::GetInstance()->Play("default", m_rebound_sound);
561 
562   // It's ok to collide the same object again
563   m_last_collided_object = NULL;
564 }
565 
SetCollisionModel(bool collides_with_ground,bool collides_with_characters,bool collides_with_objects,bool go_through_objects)566 void PhysicalObj::SetCollisionModel(bool collides_with_ground,
567                                     bool collides_with_characters,
568                                     bool collides_with_objects,
569                                     bool go_through_objects)
570 {
571   m_collides_with_ground = collides_with_ground;
572   m_collides_with_characters = collides_with_characters;
573   m_collides_with_objects = collides_with_objects;
574   m_go_through_objects = go_through_objects;
575 
576   // Check boolean values
577   {
578     if (m_collides_with_characters || m_collides_with_objects)
579       ASSERT(m_collides_with_ground);
580 
581     if (!m_collides_with_ground) {
582       ASSERT(!m_collides_with_characters);
583       ASSERT(!m_collides_with_objects);
584     }
585   }
586 }
587 
CheckRebound()588 void PhysicalObj::CheckRebound()
589 {
590   // If we bounce twice in a row at the same place, stop bouncing
591   // cause it's almost sure this object is stuck bouncing indefinitely
592   if (m_rebound_position != Point2i(-1, -1)) {
593     // allow infinite rebounds for Pendulum objects (e.g. characters on rope)
594     // FIXME: test that nothing bad happens because of this
595     if (Pendulum!=GetMotionType() && m_rebound_position==GetPosition()) {
596       MSG_DEBUG("physic.state", "%s seems to be stuck in ground. Stop moving!",
597                 GetName().c_str());
598       m_rebound_position = Point2i(-1, -1);
599       StopMoving();
600       return;
601     }
602   }
603   m_rebound_position = GetPosition();
604 }
605 
IsOutsideWorldXY(const Point2i & position) const606 bool PhysicalObj::IsOutsideWorldXY(const Point2i& position) const
607 {
608   int x = position.x + m_test_left;
609   int y = position.y + m_test_top;
610 
611   if (GetWorld().IsOutsideWorldXwidth(x, GetTestWidth()))
612     return true;
613   if (GetWorld().IsOutsideWorldYheight(y, GetTestHeight())) {
614     if (m_allow_negative_y)
615       if (Y_OBJET_MIN<=y && y+GetTestHeight()-1 < 0)
616         return false;
617     return true;
618   }
619   return false;
620 }
621 
IsInVacuumXY(const Point2i & position,bool check_object) const622 bool PhysicalObj::IsInVacuumXY(const Point2i &position, bool check_object) const
623 {
624   if (IsOutsideWorldXY(position))
625     return GetWorld().IsOpen();
626 
627   if (check_object && !m_go_through_objects && CollidedObjectXY(position))
628     return false;
629 
630   int width = m_width - m_test_right - m_test_left;
631   int height = m_height -m_test_bottom - m_test_top;
632   width  =  !width ? 1 : width;
633   height = !height ? 1 : height;
634   Rectanglei rect(position.x + m_test_left, position.y + m_test_top,
635                   width, height);
636 
637   return GetWorld().RectIsInVacuum(rect);
638 }
639 
Intersect(const Rectanglei & rect) const640 inline bool PhysicalObj::Intersect(const Rectanglei & rect) const
641 {
642   int dim = m_width - m_test_right - m_test_left;
643   dim = dim ? dim : 1;
644 
645   int obj_test1 = GetX() + m_test_left;
646   int obj_test2 = obj_test1 + dim - 1;
647   int test1 = rect.GetPositionX();
648   int test2 = test1 + rect.GetSizeX() - 1;
649 
650   if (obj_test2 >= test1 && obj_test1 <= test2) {
651     dim = m_height - m_test_bottom - m_test_top;
652     dim = dim ? dim : 1;
653     obj_test1 = GetY() + m_test_top;
654     obj_test2 = obj_test1 + dim - 1;
655     test1 = rect.GetPositionY();
656     test2 = test1 + rect.GetSizeY() - 1;
657 
658     if (obj_test2 >= test1 && obj_test1 <= test2)
659       return true;
660   }
661   return false;
662 }
663 
CollidedObjectXY(const Point2i & position) const664 PhysicalObj* PhysicalObj::CollidedObjectXY(const Point2i & position) const
665 {
666   if (IsOutsideWorldXY(position))
667     return NULL;
668 
669   Rectanglei rect(position.x + m_test_left, position.y + m_test_top,
670                  m_width - m_test_right - m_test_left, m_height - m_test_bottom - m_test_top);
671 
672   if (m_collides_with_characters) {
673     FOR_ALL_LIVING_CHARACTERS(team,character) {
674       // We check both objet if one overlapses the other
675       if (&(*character) != this && !IsOverlapping(&(*character)) &&
676           !character->IsOverlapping(this) && character->Intersect(rect)) {
677         return (PhysicalObj*) &(*character);
678       } else if (IsOverlapping(&(*character)) != character->IsOverlapping(this)) {
679         //printf("Check 0\n");
680       }
681     }
682   }
683 
684   if (m_collides_with_objects) {
685     if (m_is_character) {
686       FOR_EACH_OBJECT(it) {
687         PhysicalObj * object=*it;
688         // We check both objet if one overlapses the other
689         if (object->m_collides_with_characters) {
690           if (object != this && !IsOverlapping(object) && !object->IsOverlapping(this) &&
691               object->m_collides_with_objects && object->Intersect(rect)) {
692             return object;
693           } else if (IsOverlapping(object) != object->IsOverlapping(this)) {
694             //printf("Check1\n");
695           }
696         }
697       }
698     } else {
699       FOR_EACH_OBJECT(it) {
700         PhysicalObj * object=*it;
701         // We check both objet if one overlapses the other
702         if (object != this && !IsOverlapping(object) && !object->IsOverlapping(this) &&
703             object->m_collides_with_objects && object->Intersect(rect)) {
704           return object;
705         } else if (IsOverlapping(object) != object->IsOverlapping(this)) {
706           //printf("Check2\n");
707         }
708       }
709     }
710   }
711 
712   return NULL;
713 }
714 
FootsInVacuumXY(const Point2i & position) const715 bool PhysicalObj::FootsInVacuumXY(const Point2i &position) const
716 {
717   if (IsOutsideWorldXY(position)) {
718     MSG_DEBUG("physical", "%s - physobj is outside the world", GetName().c_str());
719     return GetWorld().IsOpen();
720   }
721 
722   int y_test = position.y + m_height - m_test_bottom;
723 
724   Rectanglei rect(position.x + m_test_left, y_test,
725                   m_width - m_test_right - m_test_left, 1);
726 
727   if (m_allow_negative_y && rect.GetPositionY() < 0) {
728     int b = rect.GetPositionY() + rect.GetSizeY();
729 
730     rect.SetPositionY(0);
731     rect.SetSizeY((b > 0) ? b - rect.GetPositionY() : 0);
732   }
733 
734   // Check RectIsInVacuum() first, because it's much faster than CollidedObjectXY()
735   if (GetWorld().RectIsInVacuum(rect)) {
736     if (CollidedObjectXY(position + Point2i(0, 1)))
737       return false;
738     return true;
739   }
740   return false;
741 }
742 
IsInWater() const743 bool PhysicalObj::IsInWater() const
744 {
745   ASSERT(!IsGhost());
746   if (!GetWorld().water.IsActive())
747     return false;
748   int x = InRange_Long(GetCenterX(), 0, GetWorld().GetWidth()-1);
749   return (int)GetWorld().water.GetHeight(x) < GetCenterY();
750 }
751 
DirectFall()752 void PhysicalObj::DirectFall()
753 {
754   while (!IsGhost() && !IsInWater() && FootsInVacuum())
755     SetY(GetYDouble()+ONE);
756 }
757 
ContactPoint(int & contact_x,int & contact_y) const758 bool PhysicalObj::ContactPoint(int & contact_x, int & contact_y) const
759 {
760   int x1, x2, y1, y2;
761 
762   // We are looking for a point in contact with the bottom of the object:
763   y1 = GetY() + m_height - m_test_bottom;
764   y2 = y1 - 1;
765   Point2i pointA;
766   Point2i pointB;
767 
768   for (int x = GetX() + m_test_left; x <= GetX() + m_width - m_test_right; x++) {
769     pointA.SetValues(x, y1);
770     pointB.SetValues(x, y2);
771 
772     if (!GetWorld().IsOutsideWorld(pointA) &&
773         !GetWorld().IsOutsideWorld(pointB) &&
774          GetWorld().ground.IsEmpty(pointB) &&
775         !GetWorld().ground.IsEmpty(pointA)) {
776       contact_x = x;
777       contact_y = GetY() + m_height - m_test_bottom;
778       return true;
779     }
780   }
781 
782   // We are looking for a point in contact on the left hand of object:
783   x1 = GetX() + m_test_left;
784   x2 = x1 + 1;
785 
786   for (int y = GetY() + m_test_top; y <= GetY() + m_height - m_test_bottom; y++) {
787     pointA.SetValues(x1, y);
788     pointB.SetValues(x2, y);
789 
790     if (!GetWorld().IsOutsideWorld(pointA) && !GetWorld().IsOutsideWorld(pointB) &&
791         !GetWorld().ground.IsEmpty(pointA) &&  GetWorld().ground.IsEmpty(pointB)) {
792       contact_x = GetX() + m_test_left;
793       contact_y = y;
794       return true;
795     }
796   }
797 
798   // We are looking for a point in contact on the rigth hand of object:
799   x1 = GetX() + m_width - m_test_right;
800   x2 = x1 - 1;
801 
802   for (int y = GetY() + m_test_top; y <= GetY() + m_height - m_test_bottom; y++) {
803     pointA.SetValues(x1, y);
804     pointB.SetValues(x2, y);
805 
806     if (!GetWorld().IsOutsideWorld(pointA) && !GetWorld().IsOutsideWorld(pointB) &&
807         !GetWorld().ground.IsEmpty(pointA) && GetWorld().ground.IsEmpty(pointB)) {
808       contact_x = GetX() + m_width - m_test_right;
809       contact_y = y;
810       return true;
811     }
812   }
813 
814   // We are looking for a point in contact on top of object:
815   y1 = GetY() + m_test_top;
816   y2 = y1 - 1;
817   for (int x = GetX() + m_test_left; x <= GetX() + m_width - m_test_right; x++) {
818     pointA.SetValues(x, y1);
819     pointB.SetValues(x, y2);
820 
821     if (!GetWorld().IsOutsideWorld(pointA) && !GetWorld().IsOutsideWorld(pointB) &&
822         !GetWorld().ground.IsEmpty(pointA) && GetWorld().ground.IsEmpty(pointB)) {
823       contact_x = x;
824       contact_y = GetY() + m_test_top;
825       return true;
826     }
827   }
828   return false;
829 }
830 
PutRandomly(bool on_top_of_world,Double min_dst_with_characters,bool net_sync)831 bool PhysicalObj::PutRandomly(bool on_top_of_world, Double min_dst_with_characters, bool net_sync)
832 {
833   uint bcl=0;
834   uint NB_MAX_TRY = 60;
835   bool ok;
836   Point2i position;
837 
838   MSG_DEBUG("physic.position", "%s - Search a position...", GetName().c_str());
839 
840   do {
841     bcl++;
842     ok = true;
843     Init();
844 
845     if (bcl >= NB_MAX_TRY) {
846       MSG_DEBUG("physic.position", "%s - Impossible to find an initial position !!", GetName().c_str());
847       return false;
848     }
849 
850     if (on_top_of_world) {
851       // Give a random position for x
852       if (net_sync) {
853         MSG_DEBUG("random.get", "PhysicalObj::PutRandomly(...)");
854         position.x = RandomSync().GetInt(0, GetWorld().GetWidth() - GetWidth());
855       } else {
856         position.x = RandomLocal().GetInt(0, GetWorld().GetWidth() - GetWidth());
857       }
858       position.y = -GetHeight()+1;
859     } else {
860       if (net_sync) {
861         MSG_DEBUG("random.get", "PhysicalObj::PutRandomly(...)");
862         position = RandomSync().GetPoint(GetWorld().GetSize() - GetSize() + 1);
863       } else {
864         position = RandomLocal().GetPoint(GetWorld().GetSize() - GetSize() + 1);
865       }
866     }
867     SetXY(position);
868     MSG_DEBUG("physic.position", "%s (try %u/%u) - Test in %d, %d",
869               GetName().c_str(), bcl, NB_MAX_TRY, position.x, position.y);
870 
871     // Check physical object is not in the ground
872     ok &= !IsGhost() && GetWorld().ParanoiacRectIsInVacuum(GetTestRect()) && IsInVacuum(Point2i(0, 1));
873     if (!ok) {
874       MSG_DEBUG("physic.position", "%s - Put it in the ground -> try again !", GetName().c_str());
875       continue;
876     }
877 
878     /* check if the area rigth under the object has a bottom on the ground */
879     ok &= !GetWorld().ParanoiacRectIsInVacuum(Rectanglei(GetCenter().x, position.y, 1, GetWorld().GetHeight() -
880              (WATER_INITIAL_HEIGHT + 30) - position.y));
881     if (!ok) {
882       MSG_DEBUG("physic.position", "%s - Put in outside the map or in water -> try again", GetName().c_str());
883       continue;
884     }
885 
886     DirectFall();
887 
888     // Check distance with characters
889     FOR_ALL_LIVING_CHARACTERS(team, character) if (&(*character) != this) {
890       MSG_DEBUG("physic.position", "Checking distance to %s", (*character).m_name.c_str());
891       if (min_dst_with_characters == 0) {
892 
893         if (Overlapse(*character)) {
894             MSG_DEBUG("physic.position", "%s - Object is too close from character %s",
895                       GetName().c_str(), (*character).m_name.c_str());
896             ok = false;
897         }
898       } else {
899         Point2i p1 = character->GetCenter();
900         Point2i p2 = GetCenter();
901         Double dst = p1.Distance(p2);
902 
903         // ok this test is not perfect but quite efficient ;-)
904         // else we need to check each distance between each "corner"
905         if (dst < min_dst_with_characters)
906           ok = false;
907       }
908     }
909 
910     if (ok && on_top_of_world)
911       SetXY(position);
912   } while (!ok);
913 
914   MSG_DEBUG("physic.position", "Put '%s' after %u tries", GetName().c_str(), bcl);
915 
916   return true;
917 }
918