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