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