1
2 /* Battle Tanks Game
3 * Copyright (C) 2006-2009 Battle Tanks team
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (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
20 /*
21 * Additional rights can be granted beyond the GNU General Public License
22 * on the terms provided in the Exception. If you modify this file,
23 * you may extend this exception to your version of the file,
24 * but you are not obligated to do so. If you do not wish to provide this
25 * exception without modification, you must delete this exception statement
26 * from your version and license this file solely under the GPL without exception.
27 */
28
29 #include "object.h"
30 #include "config.h"
31 #include "sdlx/surface.h"
32 #include "sdlx/c_map.h"
33 #include "mrt/exception.h"
34 #include "mrt/logger.h"
35 #include "animation_model.h"
36 #include "resource_manager.h"
37 #include "world.h"
38 #include "tmx/map.h"
39 #include "math/unary.h"
40 #include "math/binary.h"
41 #include "sound/mixer.h"
42 #include "special_owners.h"
43 #include "game_monitor.h"
44 #include "zbox.h"
45
46 #include "clunk/object.h"
47
get_relative_position(const Object * obj) const48 const v2<float> Object::get_relative_position(const Object *obj) const {
49 return Map->distance(this->get_center_position(), obj->get_center_position());
50 }
51
Event()52 Object::Event::Event() : name(), repeat(false), sound(), gain(1.0f), played(false), cached_pose(NULL) {}
53
Event(const std::string name,const bool repeat,const std::string & sound,const float gain,const Pose * p)54 Object::Event::Event(const std::string name, const bool repeat, const std::string &sound, const float gain, const Pose * p):
55 name(name), repeat(repeat), sound(sound), gain(gain), played(false), cached_pose(p) {}
56
serialize(mrt::Serializator & s) const57 void Object::Event::serialize(mrt::Serializator &s) const {
58 s.add(name);
59 s.add(repeat);
60 }
deserialize(const mrt::Serializator & s)61 void Object::Event::deserialize(const mrt::Serializator &s) {
62 cached_pose = NULL;
63 s.get(name);
64 s.get(repeat);
65 }
66
clone() const67 Object * Object::clone() const {
68 throw_ex(("object %s:%s doesnt provide clone() method", registered_name.c_str(), animation.c_str()));
69 return NULL;
70 }
71
deep_clone() const72 Object * Object::deep_clone() const {
73 Object *r = clone();
74 r->_fadeout_surface = NULL;
75 r->clunk_object = NULL;
76
77 for(Group::iterator i = r->_group.begin(); i != r->_group.end(); ++i) {
78 i->second = i->second->deep_clone();
79 i->second->_parent = r;
80 }
81 return r;
82 }
83
ai_disabled() const84 const bool Object::ai_disabled() const {
85 if (_variants.has("ally") || disable_ai)
86 return false;
87 return GameMonitor->disabled(this);
88 }
89
90
Object(const std::string & classname)91 Object::Object(const std::string &classname) :
92 BaseObject(classname),
93 registered_name(), animation(), fadeout_time(0),
94 _parent(NULL),
95 _animation(0), _model(0), _surface(0),
96 _fadeout_surface(0), _fadeout_alpha(0), _cmap(0),
97 _events(), _effects(),
98 _tw(0), _th(0), _direction_idx(0), _directions_n(8), _pos(0),
99 _way(), _next_target(), _next_target_rel(),
100 _rotation_time(0),
101 _dst_direction(-1),
102 _group(), _slot_id(-1),
103 clunk_object(NULL)
104 { }
105
106
~Object()107 Object::~Object() {
108 delete _fadeout_surface;
109 for(Group::iterator i = _group.begin(); i != _group.end(); ++i) {
110 delete i->second;
111 }
112 _group.clear();
113
114 if (clunk_object != NULL) {
115 if (clunk_object->active()) {
116 clunk_object->autodelete();
117 } else {
118 //inactive object. can delete it right now.
119 delete clunk_object;
120 }
121 clunk_object = NULL; //just for fun
122 }
123 }
124
spawn(const std::string & classname,const std::string & animation,const v2<float> & dpos,const v2<float> & vel,const int z)125 Object* Object::spawn(const std::string &classname, const std::string &animation, const v2<float> &dpos, const v2<float> &vel, const int z) {
126 return World->spawn(this, classname, animation, dpos, vel, z);
127 }
128
get_nearest(const std::set<std::string> & classnames,const float range,v2<float> & position,v2<float> & velocity,const bool check_shooting_range) const129 const bool Object::get_nearest(const std::set<std::string> &classnames, const float range, v2<float> &position, v2<float> &velocity, const bool check_shooting_range) const {
130 if (ai_disabled())
131 return false;
132
133 return World->get_nearest(this, classnames, range, position, velocity, check_shooting_range);
134 }
135
get_nearest_object(const std::set<std::string> & classnames,const float range,const bool check_shooting_range) const136 const Object * Object::get_nearest_object(const std::set<std::string> &classnames, const float range, const bool check_shooting_range) const {
137 if (ai_disabled())
138 return NULL;
139
140 return World->get_nearest_object(this, classnames, range, check_shooting_range);
141 }
142
143
set_direction(const int dir)144 void Object::set_direction(const int dir) {
145 if (dir >= _directions_n)
146 LOG_WARN(("%s:%s set_direction(%d) called on object with %d directions", registered_name.c_str(), animation.c_str(), dir, _directions_n));
147 if (dir >= 0)
148 _direction_idx = dir;
149 }
150
set_directions_number(const int dirs)151 void Object::set_directions_number(const int dirs) {
152 if (dirs >= 0)
153 _directions_n = dirs;
154 }
155
quantize_velocity()156 void Object::quantize_velocity() {
157 _velocity.normalize();
158 if (_directions_n == 8) {
159 _velocity.quantize8();
160 set_direction(_velocity.get_direction8() - 1);
161 } else if (_directions_n == 16) {
162 _velocity.quantize16();
163 set_direction(_velocity.get_direction16() - 1);
164 } //else throw_ex(("%s:%s cannot handle %d directions", registered_name.c_str(), animation.c_str(), _directions_n));
165 //redesign this ^^
166 }
167
168
play(const std::string & id,const bool repeat)169 void Object::play(const std::string &id, const bool repeat) {
170 if (_events.empty())
171 _pos = 0;
172 check_animation();
173 const Pose *pose = _model->getPose(id);
174 if (pose == NULL) {
175 LOG_WARN(("%d:%s:%s: animation model %s does not have pose '%s'",
176 get_id(), registered_name.c_str(), animation.c_str(), _animation->model.c_str(), id.c_str()));
177 return;
178 }
179
180 _events.push_back(Event(id, repeat, pose->sound, pose->gain, pose));
181 }
182
play_now(const std::string & id)183 void Object::play_now(const std::string &id) {
184 check_animation();
185
186 const Pose *pose = _model->getPose(id);
187 if (pose == NULL) {
188 LOG_WARN(("animation model %s does not have pose %s", _animation->model.c_str(), id.c_str()));
189 return;
190 }
191 _pos = 0;
192 _events.push_front(Event(id, false, pose->sound, pose->gain, pose));
193 }
194
cancel()195 void Object::cancel() {
196 if (_events.empty())
197 return;
198
199 if (clunk_object != NULL) {
200 const std::string &sound = _events.front().sound;
201 clunk_object->cancel(sound);
202 }
203
204 _events.pop_front();
205 _pos = 0;
206 }
207
cancel_repeatable()208 void Object::cancel_repeatable() {
209 for (EventQueue::iterator i = _events.begin(); i != _events.end();) {
210 if (i->repeat) {
211 if (i == _events.begin())
212 _pos = 0;
213
214 if (clunk_object != NULL)
215 clunk_object->cancel(i->sound);
216
217 i = _events.erase(i);
218 }
219 else ++i;
220 }
221 }
222
223
cancel_all()224 void Object::cancel_all() {
225 while(!_events.empty()) {
226 if (clunk_object != NULL)
227 clunk_object->cancel(_events.front().sound);
228 _events.pop_front();
229 }
230 _pos = 0;
231 }
232
233
234
tick(const float dt)235 void Object::tick(const float dt) {
236 if (clunk_object != NULL) {
237 v3<float> listener_pos, listener_len;
238 float r;
239 Mixer->get_listener(listener_pos, listener_len, r);
240 v2<float> pos = Map->distance(v2<float>(listener_pos.x, listener_pos.y), get_center_position());
241 clunk_object->update(clunk::v3<float>(pos.x, -pos.y, 0), clunk::v3<float>(_velocity.x, -_velocity.y, 0), clunk::v3<float>(0, 1, 0));
242 }
243 for(EffectMap::iterator ei = _effects.begin(); ei != _effects.end(); ) {
244 if (ei->second >= 0) {
245 ei->second -= dt;
246 if (ei->second <= 0) {
247 _effects.erase(ei++);
248 continue;
249 }
250 }
251 if (ei->first == "stunned") {
252 if (!_velocity.is0()) {
253 _direction = _velocity;
254 _velocity.clear();
255 }
256 }
257 ++ei;
258 }
259
260 const Pose * pose = NULL;
261
262 if (_events.empty()) {
263 if (_parent == NULL) {
264 LOG_DEBUG(("%s: no state, committing suicide", animation.c_str()));
265 emit("death", NULL);
266 }
267 return;
268 }
269
270 Event & event = _events.front();
271 //LOG_DEBUG(("%p: event: %s, pos = %f", (void *)this, event.name.c_str(), _pos));
272 pose = event.cached_pose;
273 if (pose == NULL) {
274 check_animation();
275 event.cached_pose = pose = _model->getPose(event.name);
276 }
277
278 if (pose == NULL) {
279 LOG_WARN(("animation model %s does not have pose %s", _animation->model.c_str(), event.name.c_str()));
280 cancel();
281 return;
282 }
283
284 if (pose->z > -10000) {
285 set_z(pose->z);
286 }
287
288 if (!event.played) {
289 event.played = true;
290 if (!event.sound.empty()) {
291 if (event.sound[0] != '@') {
292 Mixer->playSample(this, event.sound, event.repeat, event.gain);
293 } else {
294 Mixer->playRandomSample(this, event.sound.substr(1), event.repeat, event.gain);
295 }
296 }
297 if (pose->need_notify) {
298 emit(event.name);
299 }
300 if (event.name == "broken") {
301 World->on_object_broke.emit(this);
302 }
303 }
304
305 _pos += dt * pose->speed;
306 int n = pose->frames.size();
307 if (n == 0) {
308 LOG_WARN(("animation model %s, pose %s doesnt contain any frames", _animation->model.c_str(), event.name.c_str()));
309 return;
310 }
311
312 int cycles = (int)_pos / n;
313 //LOG_DEBUG(("%s: _pos: %f, cycles: %d", classname.c_str(), _pos, cycles));
314 _pos -= cycles * n;
315 if (_pos < 0)
316 _pos += n;
317 if (_pos >= n)
318 _pos -= n;
319
320 if (cycles) {
321 if (!event.repeat)
322 cancel();
323 }
324 }
325
326 #include "player_manager.h"
327
group_tick(const float dt)328 void Object::group_tick(const float dt) {
329 bool safe_mode = PlayerManager->is_client();
330
331 for(Group::iterator i = _group.begin(); i != _group.end(); ) {
332 Object *o = i->second;
333 assert(o != NULL);
334 assert(o->_parent == this);
335
336 if (o->is_dead()) {
337 LOG_DEBUG(("%d:%s, grouped '%s':%s is dead.", get_id(), animation.c_str(), i->first.c_str(), o->animation.c_str()));
338 if (!safe_mode) {
339 delete o;
340 _group.erase(i++);
341 } else {
342 Object *parent = o->_parent;
343 assert(parent != NULL);
344
345 while(parent->_parent != NULL)
346 parent = parent->_parent;
347
348 World->sync(parent->get_id());
349
350 ++i;
351 }
352 continue;
353 }
354 if (dt > 0 && i->first[0] != '.') {
355 o->calculate(dt);
356 o->tick(dt);
357 }
358
359 if (o->is_dead()) {
360 //LOG_DEBUG(("%d:%s, grouped '%s':%s is dead.", get_id(), animation.c_str(), i->first.c_str(), o->animation.c_str()));
361 if (!safe_mode) {
362 delete o;
363 _group.erase(i++);
364 } else {
365 ++i;
366 }
367 continue;
368 }
369
370 ++i;
371 }
372 }
373
get_subobjects(std::set<Object * > & objects)374 void Object::get_subobjects(std::set<Object *> &objects) {
375 if (skip_rendering())
376 return;
377
378 for(Group::iterator i = _group.begin(); i != _group.end(); ++i) {
379 if (i->first[0] == '.')
380 continue;
381 objects.insert(i->second);
382 i->second->get_subobjects(objects);
383 }
384 }
385
play_sound(const std::string & name,const bool loop,const float gain)386 void Object::play_sound(const std::string &name, const bool loop, const float gain) {
387 Mixer->playSample(this, name + ".ogg", loop, gain);
388 }
389
playing_sound(const std::string & name) const390 bool Object::playing_sound(const std::string &name) const {
391 return clunk_object != NULL && clunk_object->playing(name + ".ogg");
392 }
393
fadeout_sound(const std::string & name)394 void Object::fadeout_sound(const std::string &name) {
395 if (clunk_object != NULL)
396 clunk_object->fade_out(name + ".ogg");
397 }
398
play_random_sound(const std::string & classname,const bool loop,const float gain)399 void Object::play_random_sound(const std::string &classname, const bool loop, const float gain) {
400 Mixer->playRandomSample(this, classname, loop, gain);
401 }
402
403
get_render_rect(sdlx::Rect & src) const404 const bool Object::get_render_rect(sdlx::Rect &src) const {
405 if (_events.empty()) {
406 if (!is_dead() && _parent == NULL)
407 LOG_WARN(("%s: no animation played. latest position: %g", registered_name.c_str(), _pos));
408 return false;
409 }
410
411 const Event & event = _events.front();
412 const Pose * pose = event.cached_pose;
413 if (pose == NULL) {
414 check_animation();
415 event.cached_pose = pose = _model->getPose(event.name);
416 }
417
418 if (pose == NULL) {
419 LOG_WARN(("%s:%s pose '%s' is not supported", registered_name.c_str(), animation.c_str(), _events.front().name.c_str()));
420 return false;
421 }
422
423 int frame = (int)_pos;
424 int n = (int)pose->frames.size();
425 if (n == 0) {
426 LOG_WARN(("%s:%s pose '%s' doesnt have any frames", registered_name.c_str(), animation.c_str(), _events.front().name.c_str()));
427 return false;
428 }
429
430 //this stuff needs to be fixed, but I still cannot find cause for overflowing frame
431 if (frame >= n)
432 frame = n - 1;
433
434 if (frame < 0 || frame >= n) {
435 LOG_WARN(("%s:%s event '%s' frame %d is out of range (position: %g).", registered_name.c_str(), animation.c_str(), _events.front().name.c_str(), frame, _pos));
436 return false;
437 }
438
439 frame = pose->frames[frame];
440
441
442 check_surface();
443
444 if (frame * _th >= _surface->get_height()) {
445 LOG_WARN(("%s:%s event '%s' tile row %d is out of range.", registered_name.c_str(), animation.c_str(), _events.front().name.c_str(), frame));
446 return false;
447 }
448
449 src.x = _direction_idx * _tw;
450 src.y = frame * _th;
451 src.w = _tw;
452 src.h = _th;
453 return true;
454 }
455
skip_rendering() const456 const bool Object::skip_rendering() const {
457 if (!has_effect("invulnerability"))
458 return false;
459 float t = get_effect_timer("invulnerability");
460 if (t < 0)
461 return false;
462
463 GET_CONFIG_VALUE("engine.spawn-invulnerability-blinking-interval", float, ibi, 0.3);
464 int n = (int)(t / ibi * 2); //2 is legacy :)
465
466 return n & 1;
467 }
468
render(sdlx::Surface & surface,const int x_,const int y_)469 void Object::render(sdlx::Surface &surface, const int x_, const int y_) {
470 if (skip_rendering())
471 return;
472
473 sdlx::Rect src;
474 if (!get_render_rect(src))
475 return;
476
477 int x = x_, y = y_;
478 if (has_effect("teleportation")) {
479 float t = get_effect_timer("teleportation");
480 int dx = (int)(t * 50) % 3;
481 int dy = (int)(t * 50 + 7) % 3;
482 if (dx != 1) {
483 x += 5 * (dx - 1);
484 y += 5 * (dy - 1);
485 } else return;
486 }
487
488 int alpha = 0;
489 if (fadeout_time > 0 && ttl > 0 && ttl < fadeout_time)
490 alpha = (int)(255 * (fadeout_time - ttl) / fadeout_time);
491 //LOG_DEBUG(("alpha = %d", alpha));
492
493 check_surface();
494
495 if (alpha == 0) {
496 surface.blit(*_surface, src, x, y);
497 return;
498 }
499
500 //fade out feature.
501 alpha = 255 - alpha;
502
503 GET_CONFIG_VALUE("engine.fadeout-strip-alpha-bits", int, strip_alpha_bits, 4);
504 alpha &= ~((1 << strip_alpha_bits) - 1);
505
506 if (_fadeout_surface != NULL && alpha == _fadeout_alpha) {
507 surface.blit(*_fadeout_surface, x, y);
508 //LOG_DEBUG(("skipped all fadeout stuff"));
509 return;
510 }
511 _fadeout_alpha = alpha;
512
513 if (_fadeout_surface == NULL) {
514 _fadeout_surface = new sdlx::Surface;
515 _fadeout_surface->create_rgb(_tw, _th, 32, SDL_SWSURFACE);
516 _fadeout_surface->display_format_alpha();
517 }
518
519 const_cast<sdlx::Surface *>(_surface)->set_alpha(0,0);
520 _fadeout_surface->blit(*_surface, src);
521 const_cast<sdlx::Surface *>(_surface)->set_alpha(0);
522
523 SDL_Surface *s = _fadeout_surface->get_sdl_surface();
524 assert(s->format->BytesPerPixel > 2);
525
526 _fadeout_surface->lock();
527 //optimize it.
528 Uint32 *p = (Uint32 *)s->pixels;
529 int size = s->h * s->pitch / 4;
530 for(int i = 0; i < size; ++i) {
531 Uint8 r, g, b, a;
532 _fadeout_surface->get_rgba(*p, r, g, b, a);
533 if (a == 0) {
534 ++p;
535 continue;
536 }
537 a = (((int)a) * alpha) / 255;
538 *p++ = _fadeout_surface->map_rgba(r, g, b, a);
539 }
540 _fadeout_surface->unlock();
541
542 surface.blit(*_fadeout_surface, x, y);
543
544 }
545
collides(const Object * other,const int x,const int y,const bool hidden_by_other) const546 const bool Object::collides(const Object *other, const int x, const int y, const bool hidden_by_other) const {
547 sdlx::Rect src, other_src;
548 assert(other != NULL);
549 if (!get_render_rect(src))
550 return false;
551 if (!other->get_render_rect(other_src))
552 return false;
553 /* LOG_DEBUG(("::collide(%s:%d,%d,%d,%d, %s:%d,%d,%d,%d)",
554 classname.c_str(),
555 src.x, src.y, src.w, src.h,
556 other->classname.c_str(),
557 other_src.x, other_src.y, other_src.w, other_src.h
558 ));
559 */
560 check_surface();
561 other->check_surface();
562
563 return _cmap->collides(src, other->_cmap, other_src, x, y, hidden_by_other);
564 }
565
collides(const sdlx::CollisionMap * other,const int x,const int y,const bool hidden_by_other) const566 const bool Object::collides(const sdlx::CollisionMap *other, const int x, const int y, const bool hidden_by_other) const {
567 assert(other != NULL);
568 sdlx::Rect src;
569 if (!get_render_rect(src))
570 return false;
571
572 check_surface();
573 return _cmap->collides(src, other, sdlx::Rect(), x, y, hidden_by_other);
574 }
575
serialize(mrt::Serializator & s) const576 void Object::serialize(mrt::Serializator &s) const {
577 assert(!_dead);
578 BaseObject::serialize(s);
579 //Group
580 int en = _group.size();
581 s.add(en);
582 for(Group::const_iterator i = _group.begin(); i != _group.end(); ++i) {
583 s.add(i->first);
584 const Object *o = i->second;
585 s.add(o->registered_name);
586 o->serialize(s);
587 }
588 if (!_need_sync)
589 return;
590
591 s.add(animation);
592 s.add(fadeout_time);
593
594 s.add(_events);
595 s.add<std::string, float>(_effects);
596
597 s.add(_tw);
598 s.add(_th);
599 s.add(_direction_idx);
600 s.add(_directions_n);
601 s.add(_pos);
602
603 s.add(_way);
604
605 s.add(_next_target);
606 s.add(_next_target_rel);
607
608 s.add(_rotation_time);
609 s.add(_dst_direction);
610 }
611
serialize_all(mrt::Serializator & s) const612 void Object::serialize_all(mrt::Serializator &s) const {
613 std::deque<Object *> restore;
614 Object *o = const_cast<Object *>(this);
615 if (!_need_sync) {
616 restore.push_back(o);
617 o->_need_sync = true;
618 }
619 for(Group::const_iterator i = _group.begin(); i != _group.end(); ++i) {
620 o = const_cast<Object *>(i->second);
621 if (!o->_need_sync) {
622 restore.push_back(o);
623 o->_need_sync = true;
624 }
625 }
626 serialize(s);
627 for(std::deque<Object *>::iterator i = restore.begin(); i != restore.end(); ++i) {
628 (*i)->_need_sync = false;
629 }
630 }
631
set_sync(const bool sync)632 void Object::set_sync(const bool sync) {
633 _need_sync = sync;
634 for(Group::iterator i = _group.begin(); i != _group.end(); ++i) {
635 i->second->set_sync(sync);
636 }
637 }
638
deserialize(const mrt::Serializator & s)639 void Object::deserialize(const mrt::Serializator &s) {
640 try {
641 BaseObject::deserialize(s);
642
643 int en;
644 s.get(en);
645 std::set<std::string> keys;
646 while(en--) {
647 std::string name, rn;
648 s.get(name);
649 s.get(rn);
650 Object * o = _group[name];
651 if (o == NULL || o->registered_name != rn) {
652 delete o;
653 o = ResourceManager->createObject(rn);
654 o->_parent = this;
655 _group[name] = o;
656 o->deserialize(s);
657 //assert(o->_need_sync);
658 if (!o->_need_sync) {
659 LOG_DEBUG(("incomplete data for object %d:%s", o->_id, name.c_str()));
660 //incomplete object serialization. mark object as dead for future restoring.
661 o->_dead = true;
662 _dead = true;
663 }
664 } else {
665 o->deserialize(s);
666 }
667 keys.insert(name);
668 }
669 for(Group::iterator i = _group.begin(); i != _group.end(); ) {
670 if (keys.find(i->first) != keys.end()) {
671 ++i;
672 } else {
673 delete i->second;
674 i->second = NULL; //just for fun :)
675 _group.erase(i++);
676 }
677 }
678 if (!_need_sync)
679 return;
680
681 s.get(animation);
682 s.get(fadeout_time);
683
684 s.get(_events);
685 s.get<std::string, float>(_effects);
686
687 s.get(_tw);
688 s.get(_th);
689 s.get(_direction_idx);
690 s.get(_directions_n);
691 s.get(_pos);
692
693 s.get(_way);
694 s.get(_next_target);
695 s.get(_next_target_rel);
696
697
698 s.get(_rotation_time);
699 s.get(_dst_direction);
700
701 _animation = NULL;
702 _model = NULL;
703 _surface = NULL;
704 _cmap = NULL;
705
706 check_animation();
707 } CATCH("deserialize", throw);
708 }
709
emit(const std::string & event,Object * emitter)710 void Object::emit(const std::string &event, Object * emitter) {
711 if (event == "death") {
712 if (has("#ctf-flag")) {
713 drop("#ctf-flag");
714 }
715
716 if (emitter != NULL && !_dead && _parent == NULL && !piercing) {
717 World->on_object_death.emit(this, emitter);
718 }
719 _dead = true;
720 for(Group::iterator i = _group.begin(); i != _group.end(); ++i) {
721 i->second->emit("death", emitter);
722 }
723 } else if (event == "collision") {
724 if (piercing && emitter != NULL)
725 emitter->add_damage(this);
726 } else
727 LOG_WARN(("%s[%d]: unhandled event '%s'", registered_name.c_str(), _id, event.c_str()));
728 }
729
set_way(const Way & new_way)730 void Object::set_way(const Way & new_way) {
731 v2<int> pos;
732 get_center_position(pos);
733
734 _next_target.clear();
735 _velocity.clear();
736 _way = new_way;
737
738 int d = ((int)size.x + (int)size.y) / 4;
739 d *= d;
740
741 int idx, n = (int)_way.size();
742 for(idx = n - 1; idx >= 0; --idx) {
743 if (pos.quick_distance(_way[idx]) <= d)
744 break;
745 }
746 if (idx >= 0) {
747 Way::iterator i = _way.begin();
748 //LOG_DEBUG(("skipping %d vertex(es)", idx + 1));
749 while(idx--) {
750 assert(i != _way.end());
751 ++i;
752 }
753 _way.erase(_way.begin(), i);
754 }
755
756 if (!_way.empty()) {
757 //LOG_DEBUG(("%d:%s:%s set %u pending waypoints", get_id(), registered_name.c_str(), animation.c_str(), (unsigned)_way.size()));
758 _next_target = _way.begin()->convert<float>();
759 }
760
761 _need_sync = true;
762 }
763
calculate_way_velocity()764 void Object::calculate_way_velocity() {
765 if (_way.empty())
766 return;
767
768 v2<float> position;
769 get_position(position);
770 sdlx::Rect me((int)position.x, (int)position.y, (int)size.x, (int)size.y);
771
772 GET_CONFIG_VALUE("engine.allowed-pathfinding-fault", int, af, 5);
773
774 get_center_position(position);
775
776 while (!_way.empty()) {
777 _velocity.clear();
778 size_t way_size = _way.size();
779 if (way_size == 1)
780 af = 1;
781
782 if (_next_target.is0()) {
783 _next_target = _way.begin()->convert<float>();
784 v2<float> rel = Map->distance( position, _next_target);
785 //LOG_DEBUG(("%g %g", rel.x, rel.y));
786
787 sdlx::Rect wp_rect((int)_next_target.x - af, (int)_next_target.y - af, af * 2, af * 2);
788
789 if (way_size > 1 && wp_rect.inside(me)) {
790 //LOG_DEBUG(("%s skipped waypoint because of close match", animation.c_str()));
791 _next_target.clear();
792 _velocity.clear();
793 _way.pop_front();
794 continue;
795 }
796
797
798 /*if (!_next_target_rel.is0() && (rel.x == 0 || rel.x * _next_target_rel.x <= 0) && (rel.y == 0 || rel.y * _next_target_rel.y <= 0)) {
799 LOG_DEBUG(("skipped waypoint behind objects' back %g:%g (old %g:%g", rel.x, rel.y, _next_target_rel.x, _next_target_rel.y ));
800 _next_target.clear();
801 continue;
802 }
803 */
804
805 if (rel.quick_length() < af) {
806 //LOG_DEBUG(("%s skipped waypoint because of short distance (%g)", animation.c_str(), rel.quick_length()));
807 _next_target.clear();
808 _velocity.clear();
809 _way.pop_front();
810 continue;
811 }
812 _next_target_rel = rel;
813 //LOG_DEBUG(("waypoints: %d", _way.size()));
814 }
815 // LOG_DEBUG(("%d:%s:%s next waypoint: %g %g, relative: %g %g",
816 // get_id(), classname.c_str(), animation.c_str(), _next_target.x, _next_target.y, _next_target_rel.x, _next_target_rel.y));
817
818 _velocity = Map->distance(position, _next_target);
819 if ((_next_target_rel.x != 0 && _velocity.x * _next_target_rel.x <= 0) || (math::abs(_velocity.x) < af))
820 _velocity.x = 0;
821 if ((_next_target_rel.y != 0 && _velocity.y * _next_target_rel.y <= 0) || (math::abs(_velocity.y) < af))
822 _velocity.y = 0;
823
824 if (_velocity.is0()) {
825 //wiping out way point and restart
826 _way.pop_front();
827 _next_target.clear();
828 } else break;
829 }
830 _velocity.normalize();
831 // LOG_DEBUG(("%d: %s velocity: %g %g", get_id(), animation.c_str(), _velocity.x, _velocity.y));
832 }
833
834
is_driven() const835 const bool Object::is_driven() const {
836 return !_way.empty();
837 }
838
init(const std::string & an)839 void Object::init(const std::string &an) {
840 const Animation * a = ResourceManager.get_const()->getAnimation(an);
841 _animation = a;
842 _model = ResourceManager->get_animation_model(a->model);
843
844 _surface = ResourceManager->get_surface(a->surface);
845 _cmap = ResourceManager->getCollisionMap(a->surface);
846
847 _tw = a->tw;
848 _th = a->th;
849
850 size.x = _tw;
851 size.y = _th;
852
853 if (has("_outline"))
854 remove("_outline");
855
856 animation = an;
857 invalidate();
858 }
859
on_spawn()860 void Object::on_spawn() {
861 throw_ex(("%s: object MUST define on_spawn() method.", registered_name.c_str()));
862 }
863
limit_rotation(const float dt,const float speed,const bool rotate_even_stopped,const bool allow_backward)864 void Object::limit_rotation(const float dt, const float speed, const bool rotate_even_stopped, const bool allow_backward) {
865 const int dirs = get_directions_number();
866 if (dirs == 1)
867 return;
868
869 assert(dirs == 8 || dirs == 16);
870 if (_velocity.is0()) {
871 _direction.fromDirection(_direction_idx, dirs);
872 return;
873 }
874
875 if (dirs == 8) {
876 _velocity.quantize8();
877 int d = _velocity.get_direction8() - 1;
878 if (d >= 0)
879 _dst_direction = d;
880 } else {
881 _velocity.quantize16();
882 int d = _velocity.get_direction16() - 1;
883 if (d >= 0)
884 _dst_direction = d;
885 }
886 if (_dst_direction < 0)
887 return;
888
889 if (_dst_direction == _direction_idx) {
890 _rotation_time = 0;
891 return;
892 }
893
894 const int half_dirs = dirs / 2;
895
896 if (_rotation_time <= 0) {
897 //was not rotated.
898 if (allow_backward && (_dst_direction - _direction_idx + dirs) % dirs == half_dirs) {
899 return;
900 }
901
902 _rotation_time = speed;
903 }
904
905
906 if (_rotation_time > 0) {
907 _rotation_time -= dt;
908 if (_rotation_time <= 0) {
909 //rotate.
910 int dd = _dst_direction - _direction_idx;
911 if (dd < 0)
912 dd += dirs;
913 dd = (dd > half_dirs) ? -1: 1;
914 _direction_idx += dd;
915 if (_direction_idx < 0)
916 _direction_idx += dirs;
917 if (_direction_idx >= dirs)
918 _direction_idx -= dirs;
919 _rotation_time = (_direction_idx == _dst_direction)? 0: speed;
920 //LOG_DEBUG(("dd = %d, _direction_idx = %d, _dst_direction = %d", dd, _direction_idx, _dst_direction));
921 }
922 _velocity.fromDirection(_direction_idx, dirs);
923 //LOG_DEBUG(("%d -> %g %g", _direction_idx, _velocity.x, _velocity.y));
924 }
925
926 if (rotate_even_stopped) {
927 int d = math::abs<int>(_dst_direction - _direction_idx);
928 if (d > 1 && d != dirs - 1) {
929 _velocity.clear();
930 } else _velocity.fromDirection(_direction_idx, dirs);
931 }
932 _direction.fromDirection(_direction_idx, dirs); //fixme. remove it.
933 //LOG_DEBUG(("direction = %g %g, velocity = %g %g", _direction.x, _direction.y, _velocity.x, _velocity.y));
934 }
935
936 //grouped object stuff
937
pick(const std::string & name,Object * object)938 void Object::pick(const std::string &name, Object *object) {
939 if (_group.find(name) != _group.end())
940 throw_ex(("object '%s' was already added to group", name.c_str()));
941
942 object = World->pop(object);
943 object->_parent = this;
944 object->invalidate();
945 _group.insert(Group::value_type(name, object));
946 invalidate();
947 }
948
drop(const std::string & name,const v2<float> & dpos)949 Object *Object::drop(const std::string &name, const v2<float> &dpos) {
950 Group::iterator i = _group.find(name);
951 if (i == _group.end())
952 throw_ex(("object '%s' was not added to group", name.c_str()));
953
954 Object *o = i->second;
955 World->push(this, o, dpos);
956 o->invalidate();
957 o->_parent = NULL;
958 _group.erase(i);
959 invalidate();
960 return o;
961 }
962
add(const std::string & name,const std::string & classname,const std::string & animation,const v2<float> & dpos,const GroupType type)963 Object* Object::add(const std::string &name, const std::string &classname, const std::string &animation, const v2<float> &dpos, const GroupType type) {
964 if (name.empty())
965 throw_ex(("empty names are not allowed in group"));
966 if (_group.find(name) != _group.end())
967 throw_ex(("object '%s' was already added to group", name.c_str()));
968
969 Object *obj = ResourceManager->createObject(classname, animation);
970
971 assert(obj != NULL);
972 assert(obj->_owners.empty());
973
974 obj->_parent = this;
975 obj->copy_owners(this);
976 obj->add_owner(_id);
977 obj->_id = _id;
978 obj->_spawned_by = _id;
979 obj->set_slot(get_slot());
980 obj->_position = dpos;
981
982 obj->on_spawn();
983
984
985 switch(type) {
986 case Centered:
987 obj->_position += (size - obj->size)/2;
988 break;
989 case Fixed:
990 break;
991 }
992
993 obj->_z -= ZBox::getBoxBase(obj->_z);
994 obj->_z += ZBox::getBoxBase(_z);
995
996 _group.insert(Group::value_type(name, obj));
997 obj->invalidate();
998 _need_sync = true;
999 return obj;
1000 }
1001
get(const std::string & name)1002 Object *Object::get(const std::string &name) {
1003 Group::iterator i = _group.find(name);
1004 if (i == _group.end())
1005 throw_ex(("there's no object '%s' in group", name.c_str()));
1006 return i->second;
1007 }
1008
get(const std::string & name) const1009 const Object *Object::get(const std::string &name) const {
1010 Group::const_iterator i = _group.find(name);
1011 if (i == _group.end())
1012 throw_ex(("there's no object '%s' in group", name.c_str()));
1013 return i->second;
1014 }
1015
has(const std::string & name) const1016 const bool Object::has(const std::string &name) const {
1017 return _group.find(name) != _group.end();
1018 }
1019
remove(const std::string & name)1020 void Object::remove(const std::string &name) {
1021 Group::iterator i = _group.find(name);
1022 if (i == _group.end())
1023 return;
1024
1025 Object * o = i->second;
1026 assert(o != NULL);
1027 o->emit("death", this);
1028 delete o;
1029
1030 _group.erase(i);
1031 _need_sync = true;
1032 }
1033
1034
group_emit(const std::string & name,const std::string & event)1035 void Object::group_emit(const std::string &name, const std::string &event) {
1036 Group::const_iterator i = _group.find(name);
1037 if (i == _group.end())
1038 throw_ex(("there's no object '%s' in group", name.c_str()));
1039 Object * o = i->second;
1040 assert(o != NULL);
1041 o->emit(event, this);
1042 }
1043
1044 //effects
add_effect(const std::string & name,const float ttl)1045 void Object::add_effect(const std::string &name, const float ttl) {
1046 _effects[name] = ttl;
1047 _need_sync = true;
1048 }
1049
get_effect_timer(const std::string & name) const1050 const float Object::get_effect_timer(const std::string &name) const {
1051 EffectMap::const_iterator i = _effects.find(name);
1052 if (i == _effects.end())
1053 throw_ex(("getEffectTimer: object does not have effect '%s'", name.c_str()));
1054 return i->second;
1055 }
1056
remove_effect(const std::string & name)1057 void Object::remove_effect(const std::string &name) {
1058 _effects.erase(name);
1059 _need_sync = true;
1060 }
1061
calculate(const float dt)1062 void Object::calculate(const float dt) {
1063 if (_parent != NULL) {
1064 if (_directions_n > 1) {
1065 _direction = _parent->_direction;
1066 _direction_idx = _parent->_direction_idx * _directions_n / _parent->_directions_n;
1067 }
1068 return;
1069 }
1070
1071 _velocity.clear();
1072
1073 if (_state.left) _velocity.x -= 1;
1074 if (_state.right) _velocity.x += 1;
1075 if (_state.up) _velocity.y -= 1;
1076 if (_state.down) _velocity.y += 1;
1077
1078 quantize_velocity();
1079 }
1080
1081
getType() const1082 const std::string Object::getType() const {
1083 static const std::string empty;
1084 return empty;
1085 }
1086
getCount() const1087 const int Object::getCount() const {
1088 return 0;
1089 }
1090
1091
getWeaponRange(const std::string & weapon) const1092 const float Object::getWeaponRange(const std::string &weapon) const {
1093 const Object *wp = ResourceManager->getClass(weapon);
1094 GET_CONFIG_VALUE("engine.global-targeting-multiplier", float, gtm, 0.95f)
1095 float range = wp->ttl * wp->speed * gtm;
1096
1097 GET_CONFIG_VALUE("engine.window.width", int, screen_w, 800);
1098 if (range > screen_w / 2)
1099 range = screen_w / 2;
1100
1101 float tm;
1102 Config->get("objects." + registered_name + ".targeting-multiplier", tm, 1.0f);
1103
1104 if (tm <= 0 || tm > 1)
1105 throw_ex(("targeting multiplier must be greater than 0 and less or equal than 1.0 (%g)", tm));
1106 return range * tm;
1107 }
1108
1109 #include "math/vector.h"
1110
get_target_position(v2<float> & relative_position,const std::set<std::string> & targets,const std::string & weapon) const1111 const int Object::get_target_position(v2<float> &relative_position, const std::set<std::string> &targets, const std::string &weapon) const {
1112 float range = getWeaponRange(weapon);
1113 return get_target_position(relative_position, targets, range);
1114 }
1115
check_distance(const v2<float> & _map1,const v2<float> & map2,const int z,const bool use_pierceable_fixes)1116 const bool Object::check_distance(const v2<float> &_map1, const v2<float>& map2, const int z, const bool use_pierceable_fixes) {
1117 const v2<int> pfs = Map->getPathTileSize();
1118 const Matrix<int> &matrix = Map->get_impassability_matrix(z);
1119 const Matrix<int> *pmatrix = use_pierceable_fixes? &Map->get_impassability_matrix(z, true): NULL;
1120
1121 v2<float> map1 = _map1;
1122 v2<float> dp = Map->distance(map1, map2);
1123 if (dp.is0())
1124 return true;
1125
1126 float dp_len = pfs.convert<float>().length();
1127 float len = dp.normalize(dp_len);
1128
1129 Map->add(map1, dp);
1130 len -= dp_len;
1131
1132 //LOG_DEBUG(("%g:%g -> %g:%g (%+g:%+g) step: %g", map1.x, map1.y, map2.x, map2.y, dp.x, dp.y, dp.length()));
1133 //do not check map1 and map2
1134 while(len > dp_len) {
1135 Map->validate(map1);
1136 v2<int> map_pos = map1.convert<int>() / pfs;
1137
1138 //LOG_DEBUG(("(%d,%d): %d (len: %g)", map_pos.x, map_pos.y, matrix.get(map_pos.y, map_pos.x), len));
1139 //if (pmatrix)
1140 // LOG_DEBUG((" %d", pmatrix->get(map_pos.y, map_pos.x)));
1141
1142 if (matrix.get(map_pos.y, map_pos.x) < 0) {
1143 if (pmatrix == NULL || pmatrix->get(map_pos.y, map_pos.x) >= 0)
1144 return false;
1145 }
1146
1147 Map->add(map1, dp);
1148 len -= dp_len;
1149 }
1150
1151 return true;
1152 }
1153
get_target_position(v2<float> & relative_position,const std::set<std::string> & targets,const float range) const1154 const int Object::get_target_position(v2<float> &relative_position, const std::set<std::string> &targets, const float range) const {
1155 if (ai_disabled())
1156 return -1;
1157
1158 const v2<int> pfs = Map->getPathTileSize();
1159 const int dirs = _directions_n == 1?16:_directions_n;
1160 const Matrix<int> &matrix = get_impassability_matrix();
1161
1162 std::set<const Object *> objects;
1163 World->enumerate_objects(objects, this, range, &targets);
1164
1165 // v2<int> map_pos = (pos + get_position()).convert<int>() / pfs;
1166
1167 int result_dir = -1;
1168 float distance = -1; //no result if it was bug. ;)
1169
1170 for(int d = 0; d < dirs; ++d) {
1171 v2<float> dir;
1172 dir.fromDirection(d, dirs);
1173 for(std::set<const Object *>::const_iterator i = objects.begin(); i != objects.end(); ++i) {
1174 const Object *o = *i;
1175 if (has_same_owner(o) || o->ai_disabled() || o->impassability == 0 || o->has_effect("invulnerability") || o->hp <= 0)
1176 continue;
1177
1178 v2<float> pos, tp = get_relative_position(o);
1179 if (!tp.same_sign(dir))
1180 continue;
1181
1182 math::getNormalVector(pos, dir, tp);
1183 if (pos.quick_length() > tp.quick_length() || !Map->contains(pos + get_center_position()))
1184 continue;
1185
1186
1187 //skip solid objects
1188 if (impassability >= 1.0f) {
1189 // i am solid object.
1190 v2<int> map_pos = (pos + get_center_position()).convert<int>() / pfs;
1191 if (matrix.get(map_pos.y, map_pos.x) < 0)
1192 continue;
1193 }
1194
1195 float dist = pos.quick_length();
1196 if (result_dir != -1 && dist >= distance)
1197 continue;
1198
1199
1200 if (impassability >= 1.0f) {
1201 //checking map projection
1202 v2<float> map1 = pos + get_center_position();
1203 v2<float> map2 = o->get_center_position();
1204 if (!check_distance(map1, map2, get_z(), true))
1205 continue;
1206 map1 = get_center_position();
1207 map2 = pos + get_center_position();
1208 if (!check_distance(map1, map2, get_z(), false))
1209 continue;
1210 }
1211
1212 if (result_dir == -1 || dist < distance) {
1213 result_dir = d;
1214 distance = dist;
1215 relative_position = pos;
1216 //LOG_DEBUG(("enemy @ %g %g: %s (dir: %d, distance: %g)", pos.x, pos.y, o->registered_name.c_str(), d, distance));
1217 }
1218 }
1219 }
1220 return result_dir;
1221 }
1222
get_target_position(v2<float> & relative_position,const v2<float> & target,const std::string & weapon) const1223 const int Object::get_target_position(v2<float> &relative_position, const v2<float> &target, const std::string &weapon) const {
1224 if (ai_disabled())
1225 return -1;
1226
1227 float range = getWeaponRange(weapon);
1228 return get_target_position(relative_position, target, range);
1229 }
1230
get_target_position(v2<float> & relative_position,const v2<float> & target,const float range) const1231 const int Object::get_target_position(v2<float> &relative_position, const v2<float> &target, const float range) const {
1232 if (ai_disabled())
1233 return -1;
1234
1235 const int dirs = _directions_n == 1?16:_directions_n;
1236
1237 double dist = target.length();
1238 if (dist > range)
1239 dist = range;
1240
1241 //LOG_DEBUG(("searching suitable position (distance: %g, range: %g)", dist, range));
1242 double distance = 0;
1243
1244 int result_dir = -1;
1245
1246 for(int i = 0; i < dirs; ++i) {
1247 v2<float> pos;
1248 pos.fromDirection(i, dirs);
1249 pos *= dist;
1250 pos += target;
1251
1252 if (impassability >= 1.0f) {
1253 //checking map projection
1254
1255 v2<float> map1 = pos + get_center_position();
1256 v2<float> map2 = target + get_center_position();
1257 if (!check_distance(map1, map2, get_z(), true))
1258 continue;
1259
1260 map1 = get_center_position();
1261 map2 = pos + get_center_position();
1262 if (!check_distance(map1, map2, get_z(), false))
1263 continue;
1264
1265 }
1266
1267 double d = pos.quick_length();
1268
1269 if (result_dir == -1 || d < distance) {
1270 distance = d;
1271 relative_position = pos;
1272 result_dir = (i + dirs / 2) % dirs;
1273 }
1274 //LOG_DEBUG(("target position: %g %g, distance: %g", pos.x, pos.y, d));
1275 }
1276 return result_dir;
1277 }
1278
check_animation() const1279 void Object::check_animation() const {
1280 if (_animation && _model)
1281 return;
1282 _animation = ResourceManager.get_const()->getAnimation(animation);
1283 _model = ResourceManager->get_animation_model(_animation->model);
1284 }
1285
1286
check_surface() const1287 void Object::check_surface() const {
1288 if (_surface && _cmap)
1289 return;
1290 Object *nc_this = const_cast<Object *>(this);
1291 ResourceManager->check_surface(animation, nc_this->_surface, nc_this->_cmap);
1292 assert(_surface != NULL);
1293 assert(_cmap != NULL);
1294 }
1295
close(const v2<int> & vertex)1296 void Object::close(const v2<int> & vertex) {
1297 _close_list.insert(vertex);
1298 /*
1299 _close_list.insert(vertex-1);
1300 _close_list.insert(vertex+1);
1301
1302 _close_list.insert(_pitch + vertex-1);
1303 _close_list.insert(_pitch + vertex);
1304 _close_list.insert(_pitch + vertex+1);
1305
1306 _close_list.insert(-_pitch + vertex-1);
1307 _close_list.insert(-_pitch + vertex);
1308 _close_list.insert(-_pitch + vertex+1);
1309 */
1310 }
1311
h(const v2<int> & src,const v2<int> & dst,const int step)1312 static inline const int h(const v2<int>& src, const v2<int>& dst, const int step) {
1313 v2<int> dist = Map->distance(src * step, dst * step);
1314 return 500 * (math::abs(dist.x) + math::abs<int>(dist.y));
1315 }
1316
1317
find_path(const v2<int> target,const int step)1318 void Object::find_path(const v2<int> target, const int step) {
1319 _step = step;
1320 _end = target;
1321 get_center_position(_begin);
1322
1323 _begin /= step;
1324 _end /= step;
1325
1326 //LOG_DEBUG(("%s[%d]: find_path %d:%d -> %d:%d", registered_name.c_str(), get_id(), _begin.x, _begin.y, _end.x, _end.y));
1327
1328 //while(!_open_list.empty())
1329 // _open_list.pop();
1330 _open_list = OpenList();
1331
1332 _close_list.clear();
1333 _points.clear();
1334
1335
1336 Point p;
1337 p.id = _begin;
1338 p.g = 0;
1339 p.h = h(p.id, _end, _step);
1340 p.dir = get_direction();
1341
1342 _open_list.push(PD(p.g + p.h, p.id));
1343 _points[p.id] = p;
1344 }
1345
1346 #include "player_manager.h"
1347
find_path_done(Way & way)1348 const bool Object::find_path_done(Way &way) {
1349 // if (PlayerManager->is_client())
1350 // return false;
1351
1352 if (_begin == _end) {
1353 way.clear();
1354 way.push_back(_end);
1355 LOG_DEBUG(("append %d %d to way. 1 point-way.", _end.x, _end.y));
1356 _open_list = OpenList();
1357 return true;
1358 }
1359 const v2<int> map_size = Map->get_size();
1360 int dir_save = get_direction();
1361 GET_CONFIG_VALUE("engine.pathfinding-slice", int, ps, 2);
1362
1363 while(ps > 0 && !_open_list.empty()) {
1364 PointMap::const_iterator pi = _points.find(_open_list.top().id);
1365 assert(pi != _points.end());
1366 const Point& current = pi->second;
1367 assert(pi->first == current.id);
1368 _open_list.pop();
1369
1370 if (_close_list.find(current.id) != _close_list.end())
1371 continue;
1372
1373 // LOG_DEBUG(("%d: popping vertex. x=%d, y=%d, g=%d, h=%d, f=%d", get_id(),
1374 // current.id.x, current.id.y, current.g, current.h, current.g + current.h));
1375
1376 _close_list.insert(current.id);
1377 const int x = current.id.x * _step;
1378 const int y = current.id.y * _step;
1379
1380 //if (registered_name.substr(0, 2) == "ai")
1381 //LOG_DEBUG(("%s: testing node at %d,%d, value = g: %d, h: %d, f: %d", registered_name.c_str(), current.id.x, current.id.y, current.g, current.h, current.g + current.h));
1382
1383 //searching surrounds
1384 assert(current.dir != -1);
1385 const int dirs = get_directions_number();
1386 if (dirs < 4 || dirs > 8)
1387 throw_ex(("pathfinding cannot handle directions number: %d", dirs));
1388
1389 for(int i = 0; i < dirs; ++i) {
1390 v2<float> d;
1391 d.fromDirection(i, dirs);
1392 d.x = math::sign(d.x) * _step;
1393 d.y = math::sign(d.y) * _step;
1394
1395 d.x += x;
1396 d.y += y;
1397
1398 if (!Map->torus() && (d.x < 0 || d.x >= map_size.x || d.y < 0 || d.y >= map_size.y))
1399 continue;
1400
1401 Map->validate(d);
1402
1403 v2<int> id((int)(d.x / _step), (int)(d.y / _step));
1404
1405 assert( id != current.id );
1406
1407 if (_close_list.find(id) != _close_list.end())
1408 continue;
1409
1410
1411 set_direction(i);
1412 v2<int> world_pos(id.x * _step - (int)size.x / 2, id.y * _step - (int)size.y / 2);
1413 float map_im = Map->getImpassability(this, world_pos) / 100.0f;
1414 assert(map_im >= 0);
1415 //LOG_DEBUG(("%d, %d, map: %d", world_pos.x, world_pos.y, map_im));
1416 if (map_im >= 1.0f) {
1417 close(id);
1418 continue;
1419 }
1420 map_im = get_effective_impassability(map_im);
1421 if (map_im >= 1.0f) {
1422 close(id);
1423 continue;
1424 }
1425
1426 float im = World->getImpassability(this, world_pos, NULL, true, true);
1427 //LOG_DEBUG(("%d, %d, world: %g", world_pos.x, world_pos.y, im));
1428 assert(im >= 0);
1429 if (im >= 1.0f) {
1430 close(id);
1431 continue;
1432 }
1433 im = get_effective_impassability(im);
1434 if (im >= 1.0f) {
1435 close(id);
1436 continue;
1437 }
1438
1439 float result_im = 1.0f / (1.0f - math::max<float>(im, map_im));
1440
1441 Point p;
1442 p.id = id;
1443 p.dir = i;
1444 p.parent = current.id;
1445 p.g = current.g + (int)(((d.x != 0 && d.y != 0)?141:100) * result_im);
1446 p.h = h(id, _end, _step);
1447
1448
1449 //add penalty for turning
1450
1451 /*
1452 int dd = math::abs(i - current.dir);
1453 if (dd > dirs/2)
1454 dd = dirs - dd;
1455 p.h += 50 * dd;
1456 //car-specific penalties.
1457 if (map_im > 10 || im > 0.1)
1458 p.g += map_im * 30 + (int)(im * 100) * 30;
1459
1460 */
1461
1462 //LOG_DEBUG(("%s: appending %d at %d %d value = g: %d, h: %d, f: %d", registered_name.c_str(), p.id, pos.x, pos.y, p.g, p.h, p.g + p.h));
1463
1464 PointMap::iterator pi = _points.find(id);
1465
1466 if (pi != _points.end()) {
1467 if (pi->second.g > p.g) {
1468 pi->second = p;
1469 }
1470 } else
1471 _points.insert(PointMap::value_type(id, p));
1472
1473
1474 if (p.h < 100) {
1475 _end = p.id;
1476 goto found;
1477 }
1478
1479 _open_list.push(PD(p.g + p.h, p.id));
1480 }
1481 --ps;
1482 if (ps == 0)
1483 ps = -1;
1484 }
1485
1486 set_direction(dir_save);
1487
1488 if (ps < 0) {
1489 return false;
1490 }
1491
1492 way.clear();
1493 return true;
1494
1495 found:
1496 way.clear();
1497
1498 _open_list = OpenList();
1499 //while(!_open_list.empty())
1500 // _open_list.pop();
1501
1502 _close_list.clear();
1503
1504 set_direction(dir_save);
1505
1506 for(v2<int> id = _end; id != _begin; ) {
1507 Point &p = _points[id];
1508 way.push_front(p.id * _step);
1509 //LOG_DEBUG(("%dx%d -> %dx%d", p.id % _pitch, p.id / _pitch, way.front().x, way.front().y));
1510 assert(id != p.parent);
1511 id = p.parent;
1512 }
1513 _points.clear();
1514
1515 return true;
1516 }
1517
1518 #include "game.h"
1519
get_nearest_waypoint(const std::string & name) const1520 const std::string Object::get_nearest_waypoint(const std::string &name) const {
1521 return GameMonitor->get_nearest_waypoint(this, name);
1522 }
1523
add_damage(Object * from,const bool emitDeath)1524 void Object::add_damage(Object *from, const bool emitDeath) {
1525 if (from == NULL || !from->piercing)
1526 return;
1527 if (has_same_owner(from)) //friendly fire
1528 return;
1529 add_damage(from, from->max_hp, emitDeath);
1530 }
1531
1532 #include "player_slot.h"
1533
add_damage(Object * from,const int d,const bool emitDeath)1534 void Object::add_damage(Object *from, const int d, const bool emitDeath) {
1535 if (hp < 0 || d == 0 || from == NULL || has_effect("invulnerability"))
1536 return;
1537
1538 int damage = d;
1539 /*
1540 GET_CONFIG_VALUE("engine.damage-randomization", float, dr, 0.3);
1541 int radius = (int)(damage * dr);
1542 if (radius > 0) {
1543 damage += mrt::random(radius * 2 + 1) - radius;
1544 }
1545 */
1546 _need_sync = true;
1547
1548 hp -= damage;
1549 //LOG_DEBUG(("%s: received %d hp of damage from %s. hp = %d", registered_name.c_str(), damage, from->classname.c_str(), hp));
1550 if (emitDeath && hp <= 0) {
1551 emit("death", from);
1552 }
1553
1554 //look for a better place for that.
1555 if (piercing)
1556 return;
1557
1558 Object *o = ResourceManager->createObject("damage-digits", "damage-digits");
1559 o->hp = damage;
1560 if (hp < 0)
1561 o->hp += hp;
1562
1563 {
1564 PlayerSlot *slot = PlayerManager->get_slot_by_id(from->get_summoner());
1565
1566 if (slot == NULL) {
1567 std::deque<int> owners;
1568 from->get_owners(owners);
1569 for(std::deque<int>::const_iterator i = owners.begin(); i != owners.end(); ++i) {
1570 slot = PlayerManager->get_slot_by_id(*i);
1571 if (slot != NULL)
1572 break;
1573 }
1574 }
1575 if (slot != NULL) {
1576 //LOG_DEBUG(("damage from slot: %s", slot->animation.c_str()));
1577 slot->addScore(o->hp);
1578 }
1579
1580
1581 GET_CONFIG_VALUE("engine.score-decreasing-factor-for-damage", float, sdf, 0.25f);
1582 if ((slot = PlayerManager->get_slot_by_id(get_id())) != NULL) {
1583 slot->addScore(- (int)(o->hp * sdf));
1584 }
1585
1586 }
1587
1588 v2<float> pos;
1589 get_position(pos);
1590 pos.x += size.x * 0.66f;
1591 World->addObject(o, pos);
1592 o->set_z(get_z() + 1, true);
1593 }
1594
get_surface() const1595 const sdlx::Surface * Object::get_surface() const {
1596 check_surface();
1597 return _surface;
1598 }
1599
get_state_progress() const1600 const float Object::get_state_progress() const {
1601 if (_events.empty())
1602 return 0;
1603
1604 const Event & event = _events.front();
1605 //LOG_DEBUG(("%p: event: %s, pos = %f", (void *)this, event.name.c_str(), _pos));
1606 const Pose * pose = event.cached_pose;
1607 if (pose == NULL) {
1608 check_animation();
1609 event.cached_pose = pose = _model->getPose(event.name);
1610 }
1611
1612 if (pose == NULL) {
1613 return 0;
1614 }
1615
1616 const float progress = _pos / pose->frames.size();
1617
1618 return progress > 1.0 ? 1.0 : progress;
1619 }
1620
1621 #include "zbox.h"
1622
set_zbox(const int zb)1623 void Object::set_zbox(const int zb) {
1624 //LOG_DEBUG(("%s::set_zbox(%d)", registered_name.c_str(), zb));
1625 int z = get_z();
1626 z -= ZBox::getBoxBase(z); //removing current box
1627 z += ZBox::getBoxBase(zb);
1628 set_z(z, true);
1629
1630 for(Group::const_iterator i = _group.begin(); i != _group.end(); ++i) {
1631 Object *o = i->second;
1632 assert(o != NULL);
1633 o->set_zbox(zb);
1634 }
1635 }
1636
get_impassability_matrix() const1637 const Matrix<int> &Object::get_impassability_matrix() const {
1638 return Map->get_impassability_matrix(get_z());
1639 }
1640
enumerate_objects(std::set<const Object * > & o_set,const float range,const std::set<std::string> * classfilter) const1641 void Object::enumerate_objects(std::set<const Object *> &o_set, const float range, const std::set<std::string> *classfilter) const {
1642 World->enumerate_objects(o_set, this, range, classfilter);
1643 }
1644
get_children(const std::string & classname) const1645 const int Object::get_children(const std::string &classname) const {
1646 return World->get_children(get_id(), classname);
1647 }
1648
take(const BaseObject * obj,const std::string & type)1649 const bool Object::take(const BaseObject *obj, const std::string &type) {
1650 if (obj->classname == "effects" && _variants.has("player")) {
1651 if (type == "invulnerability" || type == "speedup") {
1652 float d;
1653 Config->get("objects." + registered_name + "." + type + "-duration", d, 10.0f);
1654 add_effect(type, d);
1655 return true;
1656 } else if (type == "slowdown") {
1657 float d;
1658 Config->get("objects." + registered_name + "." + type + "-duration", d, 10.0f);
1659
1660 size_t n = PlayerManager->get_slots_count();
1661 for(size_t i = 0; i < n; ++i) {
1662 PlayerSlot &slot = PlayerManager->get_slot(i);
1663 Object *o = slot.getObject();
1664 if (o != NULL && o->get_id() != get_id())
1665 o->add_effect(type, d);
1666 }
1667 return true;
1668 }
1669 }
1670 return BaseObject::take(obj, type);
1671 }
1672
attachVehicle(Object * vehicle)1673 const bool Object::attachVehicle(Object *vehicle) {
1674 if (vehicle == NULL)
1675 return false;
1676
1677 PlayerSlot *slot = PlayerManager->get_slot_by_id(get_id());
1678 if (slot == NULL)
1679 return false;
1680
1681 if (clunk_object)
1682 clunk_object->cancel_all();
1683
1684 update_player_state(PlayerState());
1685
1686 if (has("#ctf-flag")) {
1687 Object *o = drop("#ctf-flag");
1688 vehicle->pick("#ctf-flag", o);
1689 }
1690
1691 if (vehicle->classname == "vehicle" || vehicle->classname == "fighting-vehicle")
1692 Mixer->playSample(vehicle, "engine-start.ogg", false);
1693
1694 vehicle->_spawned_by = _spawned_by;
1695 if (!vehicle->_variants.has("safe") && vehicle->classname != "monster") //do not change classname for safe vehicles
1696 vehicle->classname = "fighting-vehicle";
1697
1698 if (_variants.has("player"))
1699 vehicle->_variants.add("player");
1700
1701 vehicle->copy_owners(this);
1702 vehicle->disable_ai = disable_ai;
1703 vehicle->set_slot(get_slot());
1704
1705 vehicle->pick(".me", this);
1706 World->push(get_id(), World->pop(vehicle), get_position());
1707
1708 slot->need_sync = true;
1709
1710 return true;
1711 }
1712
detachVehicle()1713 const bool Object::detachVehicle() {
1714 PlayerSlot * slot = PlayerManager->get_slot_by_id(get_id());
1715 if (
1716 slot == NULL ||
1717 classname == "monster" ||
1718 (disable_ai &&
1719 (registered_name == "machinegunner" || registered_name == "civilian")
1720 ) ||
1721 has_effect("cage")
1722 )
1723 return false;
1724
1725 bool dead = is_dead();
1726 LOG_DEBUG(("leaving %s vehicle...", dead? "dead": ""));
1727
1728 slot->need_sync = true;
1729
1730 _velocity.clear();
1731 update_player_state(PlayerState());
1732
1733 bool has_me = has(".me");
1734 Object *man;
1735 if (has_me) {
1736 Group::iterator i = _group.find(".me");
1737 assert(i != _group.end());
1738
1739 man = i->second;
1740 man->_parent = NULL;
1741 _group.erase(i);
1742 } else {
1743 man = ResourceManager->createObject(disable_ai?"machinegunner(player)": "machinegunner-player(player)", "machinegunner");
1744 man->on_spawn();
1745 }
1746
1747 if (classname == "helicopter")
1748 man->set_zbox(ResourceManager->getClass("machinegunner")->get_z());
1749 else
1750 man->set_zbox(get_z());
1751
1752 man->disable_ai = disable_ai;
1753 classname = "vehicle";
1754 if (_variants.has("player"))
1755 _variants.remove("player");
1756
1757 man->copy_owners(this);
1758
1759 disown();
1760
1761 invalidate();
1762 man->invalidate();
1763
1764 if (has("#ctf-flag")) {
1765 Object *flag = drop("#ctf-flag");
1766 man->pick("#ctf-flag", flag);
1767 }
1768
1769 Object *me = World->pop(this);
1770 if (!dead)
1771 World->push(-1, me, get_position());
1772 else
1773 delete me;
1774
1775 World->push(get_id(), man, get_center_position() + _direction * (size.x + size.y) / 4 - man->size / 2);
1776
1777 return true;
1778 }
1779
set_slot(const int id)1780 void Object::set_slot(const int id) {
1781 _slot_id = id;
1782 for(Group::iterator i = _group.begin(); i != _group.end(); ++i) {
1783 i->second->set_slot(id);
1784 }
1785 }
1786
update_outline(const bool hidden)1787 void Object::update_outline(const bool hidden) {
1788 TRY {
1789 for(Group::iterator i = _group.begin(); i != _group.end(); ++i) {
1790 if (i->first[0] != '.')
1791 i->second->update_outline(hidden);
1792 }
1793
1794 std::string outline_animation = animation + "-outline";
1795 //LOG_DEBUG(("%s: outline: %s", animation.c_str(), outline_animation.c_str()));
1796 bool has_outline = ResourceManager->hasAnimation(outline_animation);
1797 if (!has_outline)
1798 return;
1799
1800 if (hidden) {
1801 if (!has("_outline")) {
1802 //LOG_DEBUG(("%d:%s:%s: adding outline", o._id, o.classname.c_str(), o.animation.c_str()));
1803 Object *outline = add("_outline", "outline", outline_animation, v2<float>(), Centered);
1804 outline->set_z(9999, true);
1805 }
1806 //LOG_DEBUG(("%d:%s:%s: whoaaa!!! i'm in domik", o._id, o.classname.c_str(), o.animation.c_str()));
1807 } else {
1808 if (has("_outline")) {
1809 //LOG_DEBUG(("%d:%s:%s: removing outline", o._id, o.classname.c_str(), o.animation.c_str()));
1810 remove("_outline");
1811 }
1812 }
1813 } CATCH("update_outline", throw;);
1814 }
1815
1816