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 #include <string>
29 #include <stdexcept>
30 #include <stdlib.h>
31 #include <math.h>
32 
33 #include "game_monitor.h"
34 #include "object.h"
35 #include "config.h"
36 #include "world.h"
37 #include "resource_manager.h"
38 #include "player_manager.h"
39 #include "game.h"
40 #include "i18n.h"
41 #include "sdlx/font.h"
42 #include "sdlx/surface.h"
43 #include "special_owners.h"
44 #include "mrt/random.h"
45 #include "tmx/map.h"
46 #include "sound/mixer.h"
47 #include "window.h"
48 #include "var.h"
49 #include "special_zone.h"
50 #include "math/unary.h"
51 #include "player_slot.h"
52 #include "campaign.h"
53 #include "finder.h"
54 #include "console.h"
55 #include "rt_config.h"
56 
57 #ifdef ENABLE_LUA
58 #	include "luaxx/lua_hooks.h"
59 #endif
60 
61 IMPLEMENT_SINGLETON(GameMonitor, IGameMonitor);
62 
IGameMonitor()63 IGameMonitor::IGameMonitor() : _game_over(false), _win(false), _check_items(0.5, true), _state_timer(false), _timer(0),
64 	_objects_limit_reached(false), _campaign(NULL)
65 #ifdef ENABLE_LUA
66 , lua_hooks(new LuaHooks)
67 #endif
68 {
69 	on_console_slot.assign(this, &IGameMonitor::onConsole, Console->on_command);
70 	on_map_resize_slot.assign(this, &IGameMonitor::parseWaypoints, Map->map_resize_signal);
71 	add_object_slot.assign(this, &IGameMonitor::addObject, World->on_object_add);
72 	delete_object_slot.assign(this, &IGameMonitor::deleteObject, World->on_object_delete);
73 	delete_object_slot.assign(this, &IGameMonitor::deleteObject, World->on_object_broke);
74 }
75 
respawn()76 void GameItem::respawn() {
77 	if (spawn_limit == 0)
78 		return;
79 
80 	hidden = false;
81 
82 	LOG_DEBUG(("respawning item: %s:%s, z: %d, dir: %d", classname.c_str(), animation.c_str(), z, dir));
83 	Object *o = ResourceManager->createObject(classname, animation);
84 	if (z)
85 		o->set_z(z, true);
86 	o->add_owner(OWNER_MAP);
87 
88 	if (dir)
89 		o->set_direction(dir);
90 
91 	World->addObject(o, position.convert<float>());
92 	id = o->get_id();
93 	dead_on = 0;
94 	if (spawn_limit > 0)
95 		--spawn_limit;
96 }
97 
kill()98 void GameItem::kill() {
99 	Object *o = World->getObjectByID(id);
100 	if (o != NULL)
101 		o->Object::emit("death", NULL);
102 }
103 
setup(const std::string & name,const std::string & subname)104 void GameItem::setup(const std::string &name, const std::string &subname) {
105 	destroy_for_victory = name.compare(0, 19, "destroy-for-victory") == 0;
106 	special = name.compare(0, 7, "special") == 0;
107 
108 	if (name == "save-for-victory") {
109 		save_for_victory = subname;
110 		special = true;
111 	}
112 
113 	special |= destroy_for_victory;
114 
115 	size_t pos1 = name.find('(');
116 	if (pos1 == name.npos)
117 		return;
118 	++pos1;
119 
120 	size_t pos2 = name.find(')', pos1);
121 	if (pos2 == name.npos)
122 		return;
123 	--pos2;
124 
125 	if (pos1 > pos2)
126 		return;
127 
128 	int limit = atoi(name.substr(pos1, pos2 - pos1 + 1).c_str());
129 	if (limit <= 0)
130 		return;
131 
132 	//LOG_DEBUG(("respawn limit = %d", limit));
133 	spawn_limit = limit;
134 }
135 
renameProperty(const std::string & name)136 void GameItem::renameProperty(const std::string &name) {
137 	Map->properties.erase(property);
138 
139 	property = GameMonitor->generatePropertyName(name);
140 	LOG_DEBUG(("new property name %s", property.c_str()));
141 
142 	updateMapProperty();
143 }
144 
updateMapProperty()145 void GameItem::updateMapProperty() {
146 	std::string &prop = Map->properties[property];
147 	if (z)
148 		prop = mrt::format_string("%d,%d,%d", position.x, position.y, z);
149 	else
150 		prop = mrt::format_string("%d,%d", position.x, position.y);
151 
152 	const Object *o = World->getObjectByID(id);
153 	if (o != NULL) {
154 		int dir = o->get_direction();
155 		if (dir)
156 			prop += mrt::format_string("/%d", dir);
157 	}
158 }
159 
eraseLast(const std::string & property)160 void IGameMonitor::eraseLast(const std::string &property) {
161 	if (_items.empty())
162 		throw_ex(("item list is empty!"));
163 	if (_items.back().property != property)
164 		throw_ex(("eraseLast: %s is not the latest item in list", property.c_str()));
165 	_items.pop_back();
166 }
167 
find(const Object * obj) const168 const GameItem& IGameMonitor::find(const Object *obj) const {
169 	for(Items::const_iterator i = _items.begin(); i != _items.end(); ++i) {
170 		const GameItem &item = *i;
171 		Object *o = World->getObjectByID(item.id);
172 		if (obj == o)
173 			return item;
174 	}
175 	throw_ex(("could not find item %s:%s", obj->registered_name.c_str(), obj->animation.c_str()));
176 }
177 
find(const Object * obj)178 GameItem& IGameMonitor::find(const Object *obj) {
179 	for(Items::iterator i = _items.begin(); i != _items.end(); ++i) {
180 		Object *o = World->getObjectByID(i->id);
181 		if (obj == o)
182 			return *i;
183 	}
184 	throw_ex(("could not find item %s:%s", obj->registered_name.c_str(), obj->animation.c_str()));
185 }
186 
find(const std::string & property)187 GameItem& IGameMonitor::find(const std::string &property) {
188 	for(Items::iterator i = _items.begin(); i != _items.end(); ++i) {
189 		if (i->property == property)
190 			return *i;
191 	}
192 	throw_ex(("could not find item %s", property.c_str()));
193 }
194 
getBase(const Team::ID id) const195 const int IGameMonitor::getBase(const Team::ID id) const {
196 	int idx = (int)id;
197 	return (idx >= 0 && idx < 4)? team_base[idx]:0;
198 }
199 
addObject(const Object * o)200 void IGameMonitor::addObject(const Object *o) {
201 	if (o->registered_name == "ctf-base") {
202 		int team = (int)Team::get_team(o);
203 		if (team >= 0 && team < 4)
204 			team_base[team] = o->get_id();
205 	} else if (o->registered_name == "ctf-flag") {
206 		int team = (int)Team::get_team(o);
207 		if (team >= 0 && team < 2) {
208 			_flag_id.resize(2);
209 			_flag_id[team] = o->get_id();
210 		}
211 	}
212 	if (_destroy_classes.empty())
213 		return;
214 
215 	const int id = o->get_id();
216 	if (
217 		_present_objects.find(id) != _present_objects.end() || //already here. int is faster than classname check and alwaysupdate
218 		!o->has_owner(OWNER_MAP) ||
219 		o->get_variants().has("ally") ||
220 		_destroy_classes.find(o->classname) == _destroy_classes.end()
221 	)
222 		return;
223 
224 	_present_objects.insert(id);
225 	//LOG_DEBUG(("adding target object: %s (%s)", o->animation.c_str(), o->classname.c_str()));
226 }
227 
deleteObject(const Object * o)228 void IGameMonitor::deleteObject(const Object *o) {
229 	if (_destroy_classes.empty())
230 		return;
231 
232 	const int id = o->get_id();
233 	_present_objects.erase(id);
234 	//LOG_DEBUG(("deleting target object: %s (%s)", o->animation.c_str(), o->classname.c_str()));
235 }
236 
checkItems(const float dt)237 void IGameMonitor::checkItems(const float dt) {
238 	if (_game_over || !_check_items.tick(dt))
239 		return;
240 
241 	int goal = 0, goal_total = 0;
242 
243 	if (!_destroy_classes.empty()) {
244 		++goal_total;
245 		if (_present_objects.empty())
246 			++goal;
247 	}
248 
249 	_specials.clear();
250 	GET_CONFIG_VALUE("engine.kill-em-all-mode-display-last-targets", int, dlt, 5);
251 	if (!_present_objects.empty() && (_objects_limit_reached || (int)_present_objects.size() <= dlt)) {
252 		_objects_limit_reached = true; //once displayed, always display
253 		std::set<int>::iterator po = _present_objects.begin();
254 		for(int i = 0; po != _present_objects.end() && (_objects_limit_reached || i < dlt); ++i) {
255 			const int id = *po++;
256 			Object *o = World->getObjectByID(id);
257 			if (o == NULL)
258 				continue;
259 
260 			v2<int> pos;
261 			o->get_center_position(pos);
262 			_specials.push_back(v3<int>(pos.x, pos.y, id));
263 		}
264 	}
265 
266 	_flags.clear();
267 	for(size_t i = 0; i < _flag_id.size(); ++i) {
268 		const int id = _flag_id[i];
269 		Object *o = World->getObjectByID(id);
270 		if (o == NULL)
271 			continue;
272 		v2<int> pos;
273 		o->get_position(pos);
274 		_flags.push_back(v3<int>(pos.x, pos.y, id));
275 	}
276 
277 	for(size_t i = 0; i < _external_specials.size(); ++i) {
278 		const int id = _external_specials[i];
279 		Object *o = World->getObjectByID(id);
280 		if (o == NULL || o->get_state() == "broken")
281 			continue;
282 
283 		v2<int> pos;
284 		o->get_center_position(pos);
285 		_specials.push_back(v3<int>(pos.x, pos.y, id));
286 	}
287 
288 	Uint32 ticks = SDL_GetTicks();
289 
290 	for(Items::iterator i = _items.begin(); i != _items.end(); ++i) {
291 		GameItem &item = *i;
292 		Object *o = World->getObjectByID(item.id);
293 
294 		bool dead = true;
295 		if (o != NULL) {
296 			dead = o->get_state() == "broken";
297 		}
298 
299 		if (item.destroy_for_victory) {
300 			++goal_total;
301 			if (dead) {
302 				++goal;
303 			}
304 		}
305 
306 		if (!dead) {
307 			if (item.special) {
308 				v2<int> pos;
309 				o->get_center_position(pos);
310 				_specials.push_back(v3<int>(pos.x, pos.y, o->get_id()));
311 			}
312 
313 			continue;
314 		}
315 		//object is dead.
316 
317 		if (!item.save_for_victory.empty()) {
318 			game_over("messages", item.save_for_victory, 5, false);
319 			continue;
320 		}
321 
322 		if (o)
323 			continue;
324 
325 		if (item.spawn_limit == 0 || item.hidden)
326 			continue;
327 
328 		if (item.dead_on == 0) {
329 			item.dead_on = ticks;
330 			LOG_DEBUG(("item %d:%s:%s is dead, log dead time.", item.id, item.classname.c_str(), item.animation.c_str()));
331 			continue;
332 		}
333 
334 		int rt;
335 		Config->get("map." + item.classname + ".respawn-interval", rt, 5);
336 		if (rt < 0)
337 			continue;
338 		if (((ticks - item.dead_on) / 1000) >= (unsigned)rt) {
339 			//respawning item
340 			item.respawn();
341 		}
342 	}
343 	if (goal_total > 0 && goal == goal_total) {
344 		game_over("messages", "mission-accomplished", 5, true);
345 	}
346 }
347 
add(const GameItem & item_,const bool dont_respawn)348 void IGameMonitor::add(const GameItem &item_, const bool dont_respawn) {
349 	GameItem item(item_);
350 	const bool client = PlayerManager->is_client();
351 
352 #ifdef ENABLE_LUA
353 	if (!client && lua_hooks != NULL)
354 		item.hidden = !lua_hooks->on_spawn(item.classname, item.animation, item.property);
355 #endif
356 
357 	_items.push_back(item);
358 
359 	if (!dont_respawn && !item.hidden)
360 		_items.back().respawn();
361 }
362 
pushState(const std::string & state,float time)363 void IGameMonitor::pushState(const std::string &state, float time) {
364 	if (time <= 0)
365 		throw_ex(("message time <= 0 is not allowed"));
366 
367 	_state = state;
368 	_state_timer.set(time);
369 }
370 
popState(const float dt)371 const std::string IGameMonitor::popState(const float dt) {
372 	if (_state.empty() || !_state_timer.tick(dt))
373 		return std::string();
374 	std::string r = _state;
375 	_state.clear();
376 	return r;
377 }
378 
game_over(const std::string & area,const std::string & message,float time,const bool win)379 void IGameMonitor::game_over(const std::string &area, const std::string &message, float time, const bool win) {
380 	if (_game_over)
381 		return;
382 
383 	if (win) {
384 		size_t n = PlayerManager->get_slots_count();
385 		for(size_t i = 0; i < n; ++i) {
386 			PlayerSlot &slot = PlayerManager->get_slot(i);
387 			Object *o = slot.getObject();
388 			if (o != NULL) {
389 				o->add_effect("invulnerability", -1);
390 			}
391 		}
392 	}
393 
394 	_game_over = true;
395 	_win = win;
396 	displayMessage(area, message, time);
397 	PlayerManager->game_over(area, message, time);
398 	resetTimer();
399 }
400 
displayMessage(const std::string & area,const std::string & message,float time,const bool global)401 void IGameMonitor::displayMessage(const std::string &area, const std::string &message, float time, const bool global) {
402 	pushState(I18n->get(area, message), time);
403 
404 	if (global && PlayerManager->is_server()) {
405 		if (time <= 0)
406 			throw_ex(("server attempts to set up %g s timer", time));
407 		PlayerManager->broadcast_message(area, message, time);
408 	}
409 }
hideMessage()410 void IGameMonitor::hideMessage() {
411 	_state.clear();
412 	_timer = 0;
413 }
414 
setTimer(const std::string & area,const std::string & message,float time,const bool win_at_end)415 void IGameMonitor::setTimer(const std::string &area, const std::string &message, float time, const bool win_at_end) {
416 	_timer_message_area = area;
417 	_timer_message = message;
418 	_timer = time;
419 	_timer_win_at_end = win_at_end;
420 }
421 
resetTimer()422 void IGameMonitor::resetTimer() {
423 	_timer_message.clear();
424 	_timer = 0;
425 }
426 
clear()427 void IGameMonitor::clear() {
428 	resetTimer();
429 	timers.clear();
430 
431 	_game_over = false;
432 	_win = false;
433 	saveCampaign();
434 	_state.clear();
435 
436 	_items.clear();
437 	_specials.clear();
438 	_flags.clear();
439 	_external_specials.clear();
440 
441 	_check_items.reset();
442 	_disabled.clear();
443 	_destroy_classes.clear();
444 	_objects_limit_reached = false;
445 
446 	_waypoints.clear();
447 	_all_waypoints.clear();
448 	_waypoint_edges.clear();
449 	bonuses.clear();
450 
451 	memset(team_base, 0, sizeof(team_base));
452 	total_time = 0;
453 }
454 
tick(const float dt)455 void IGameMonitor::tick(const float dt) {
456 	const bool client = PlayerManager->is_client();
457 
458 #ifdef ENABLE_LUA
459 	if (!client && lua_hooks != NULL) {
460 	TRY {
461 		if (Map->loaded())
462 			lua_hooks->on_tick(dt);
463 	} CATCH("tick::on_tick", {
464 		Game->clear();
465 		displayMessage("errors", "script-error", 1);
466 		return;
467 	});
468 	processGameTimers(dt);
469 	}
470 #endif
471 
472 	if (!_timer_message.empty() && _timer > 0) {
473 		_timer -= dt;
474 		if (_timer <= 0) {
475 			if (!client)
476 				game_over(_timer_message_area, _timer_message, 5, _timer_win_at_end);
477 			_timer = 0;
478 		}
479 	}
480 
481 	if (!_game_over)
482 		total_time += dt;
483 
484 	std::string game_state = popState(dt);
485 	if (_game_over && !game_state.empty()) {
486 #ifdef ENABLE_LUA
487 	if (!client && lua_hooks != NULL) {
488 	TRY {
489 		std::string next_map = lua_hooks->getNextMap();
490 		if (!next_map.empty()) {
491 			lua_hooks->resetNextMap();
492 			startGame(_campaign, next_map);
493 			return;
494 		}
495 	} CATCH("tick::game_over", {
496 		Game->clear();
497 		displayMessage("errors", "script-error", 1);
498 		return;
499 	});
500 	}
501 #endif
502 		saveCampaign();
503 		Game->clear();
504 	}
505 }
506 
render(sdlx::Surface & window)507 void IGameMonitor::render(sdlx::Surface &window) {
508 	static const sdlx::Font * _big_font;
509 	if (_big_font == NULL)
510 		_big_font = ResourceManager->loadFont("big", true);
511 
512 	if (!_state.empty()) {
513 		int w = _big_font->render(NULL, 0, 0, _state), h = _big_font->get_height();
514 		_state_bg.init("menu/background_box.png", window.get_width() + 32, h); //fixme
515 
516 		int x = (window.get_width() - w) / 2;
517 		//int y = (window.get_height() - _big_font->get_height()) / 2;
518 		int y = window.get_height() - _big_font->get_height() - 32;
519 		_state_bg.render(window, (window.get_width() - _state_bg.w) / 2, y + (h - _state_bg.h) / 2);
520 		_big_font->render(window, x, y, _state);
521 	}
522 
523 	if (_timer > 0) {
524 		int m = (int)_timer / 60;
525 		int ms = (int)(10 * (_timer - (int)_timer));
526 		std::string timer_str;
527 		if (m) {
528 			timer_str = mrt::format_string("%2d%c%02d", m, (ms / 2 == 0 || ms /2 == 1 || ms / 2 == 4)?':':'.', ((int)_timer) % 60);
529 		} else
530 			timer_str = mrt::format_string("   %2d.%d", (int)_timer, ms);
531 
532 		int tw = timer_str.size() + 1;
533 		_big_font->render(window, window.get_width() - _big_font->get_width() * tw,
534 			 window.get_height() - _big_font->get_height() * 3 / 2,
535 			 timer_str);
536 	}
537 
538 }
539 
540 
disabled(const Object * o) const541 const bool IGameMonitor::disabled(const Object *o) const {
542 	return _disabled.find(o->classname) != _disabled.end() || _disabled.find(o->registered_name) != _disabled.end();
543 }
544 
disable(const std::string & classname,const bool value)545 void IGameMonitor::disable(const std::string &classname, const bool value) {
546 	LOG_DEBUG(("%s ai for classname %s", value?"disabling":"enabling", classname.c_str()));
547 	if (value) {
548 		_disabled.insert(classname);
549 	} else {
550 		_disabled.erase(classname);
551 	}
552 }
553 
554 
555 #include "mrt/serializator.h"
556 
serialize(mrt::Serializator & s) const557 void IGameMonitor::serialize(mrt::Serializator &s) const {
558 TRY {
559 	s.add(_game_over);
560 	s.add(_specials);
561 	s.add(_flags);
562 
563 	if (_game_over) {
564 		s.add(_state);
565 		s.add(_state_timer);
566 	}
567 
568 	s.add(_timer_message);
569 	s.add(_timer_message_area);
570 	s.add(_timer);
571 
572 	s.add(_disabled);
573 	s.add(_destroy_classes);
574 	s.add(team_base[0]);
575 	s.add(team_base[1]);
576 	s.add(team_base[2]);
577 	s.add(team_base[3]);
578 
579 } CATCH("serialize", throw);
580 }
581 
deserialize(const mrt::Serializator & s)582 void IGameMonitor::deserialize(const mrt::Serializator &s) {
583 TRY {
584 	s.get(_game_over);
585 	s.get(_specials);
586 	s.get(_flags);
587 
588 	if (_game_over) {
589 		std::string state;
590 		s.get(state);
591 		s.get(_state_timer);
592 	}
593 
594 	s.get(_timer_message);
595 	s.get(_timer_message_area);
596 	s.get(_timer);
597 
598 	s.get(_disabled);
599 	s.get(_destroy_classes);
600 
601 	s.get(team_base[0]);
602 	s.get(team_base[1]);
603 	s.get(team_base[2]);
604 	s.get(team_base[3]);
605 
606 } CATCH("deserialize", throw);
607 }
608 
killAllClasses(const std::set<std::string> & classes)609 void IGameMonitor::killAllClasses(const std::set<std::string> &classes) {
610 	_destroy_classes = classes;
611 }
612 
hasWaypoints(const std::string & classname) const613 const bool IGameMonitor::hasWaypoints(const std::string &classname) const {
614 	WaypointClassMap::const_iterator wp_class = _waypoints.find(classname);
615 	if (wp_class == _waypoints.end() && classname.compare(0, 7, "static-") == 0)  //no matter static or not
616 		wp_class = _waypoints.find(classname.substr(7));
617 
618 	return (wp_class != _waypoints.end());
619 }
620 
getRandomWaypoint(const std::string & classname,const std::string & last_wp) const621 const std::string IGameMonitor::getRandomWaypoint(const std::string &classname, const std::string &last_wp) const {
622 	if (last_wp.empty())
623 		throw_ex(("getRandomWaypoint('%s', '%s') called with empty name", classname.c_str(), last_wp.c_str()));
624 
625 	WaypointClassMap::const_iterator wp_class = _waypoints.find(classname);
626 	if (wp_class == _waypoints.end() && classname.compare(0, 7, "static-") == 0)  //no matter static or not
627 		wp_class = _waypoints.find(classname.substr(7));
628 
629 	if (wp_class == _waypoints.end())
630 		throw_ex(("no waypoints for '%s' defined", classname.c_str()));
631 
632 	WaypointEdgeMap::const_iterator b = _waypoint_edges.lower_bound(last_wp);
633 	WaypointEdgeMap::const_iterator e = _waypoint_edges.upper_bound(last_wp);
634 	if (b == e)
635 		throw_ex(("no edges defined for waypoint '%s'", last_wp.c_str()));
636 
637 	int wp = mrt::random(_waypoint_edges.size() * 2);
638 	while(true) {
639 		for(WaypointEdgeMap::const_iterator i = b; i != e; ++i) {
640 			if (wp-- <= 0) {
641 				return i->second;
642 			}
643 		}
644 	}
645 	throw_ex(("getRandomWaypoint(unexpected termination)"));
646 	return "*bug*";
647 }
648 
get_nearest_waypoint(const Object * obj,const std::string & classname) const649 const std::string IGameMonitor::get_nearest_waypoint(const Object *obj, const std::string &classname) const {
650 	v2<int> pos;
651 	obj->get_position(pos);
652 	int distance = -1;
653 	std::string wp;
654 
655 	WaypointClassMap::const_iterator i = _waypoints.find(classname);
656 	if (i == _waypoints.end() && classname.compare(0, 7, "static-") == 0)  //no matter static or not
657 		i = _waypoints.find(classname.substr(7));
658 
659 	if (i == _waypoints.end())
660 		throw_ex(("no waypoints for '%s' found", classname.c_str()));
661 
662 	for(WaypointMap::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
663 		int d = j->second.quick_distance(pos);
664 		if (distance == -1 || d < distance) {
665 			distance = d;
666 			wp = j->first;
667 		}
668 	}
669 	return wp;
670 }
671 
672 
get_waypoint(v2<float> & wp,const std::string & classname,const std::string & name)673 void IGameMonitor::get_waypoint(v2<float> &wp, const std::string &classname, const std::string &name) {
674 	if (name.empty() || classname.empty())
675 		throw_ex(("get_waypoint('%s', '%s') called with empty classname and/or name", classname.c_str(), name.c_str()));
676 
677 	WaypointClassMap::const_iterator wp_class = _waypoints.find(classname);
678 	if (wp_class == _waypoints.end() && classname.compare(0, 7, "static-") == 0)  //no matter static or not
679 		wp_class = _waypoints.find(classname.substr(7));
680 	if (wp_class == _waypoints.end())
681 		throw_ex(("no waypoints for '%s' defined", classname.c_str()));
682 
683 	WaypointMap::const_iterator i = wp_class->second.find(name);
684 	if (i == wp_class->second.end())
685 		throw_ex(("no waypoints '%s' defined", name.c_str()));
686 	wp = i->second.convert<float>();
687 }
688 
renderWaypoints(sdlx::Surface & surface,const sdlx::Rect & src,const sdlx::Rect & dst)689 void IGameMonitor::renderWaypoints(sdlx::Surface &surface, const sdlx::Rect &src, const sdlx::Rect &dst) {
690 	const sdlx::Surface *s = ResourceManager->load_surface("car-waypoint.png");
691 
692 	for(WaypointClassMap::const_iterator i = _waypoints.begin(); i != _waypoints.end(); ++i) {
693 		//const std::string &classname = i->first;
694 		for(WaypointMap::const_iterator j = i->second.begin(); j != i->second.end(); ++j) {
695 			const v2<int> &wp = j->second;
696 			surface.blit(*s,
697 			wp.x - src.x + dst.x,
698 			wp.y - src.y + dst.y - s->get_height());
699 		}
700 	}
701 
702 	s = ResourceManager->load_surface("edge.png");
703 	int w = s->get_width() / 3, h = s->get_height();
704 	sdlx::Rect normal(0, 0, w, h), out(w, 0, w, h), in(2 * w, 0, w, h);
705 
706 	for(WaypointEdgeMap::const_iterator i = _waypoint_edges.begin(); i != _waypoint_edges.end(); ++i) {
707 		WaypointMap::const_iterator a = _all_waypoints.find(i->first);
708 		if (a == _all_waypoints.end())
709 			throw_ex(("no waypoint '%s' defined", i->first.c_str()));
710 		WaypointMap::const_iterator b = _all_waypoints.find(i->second);
711 		if (b == _all_waypoints.end())
712 			throw_ex(("no waypoint '%s' defined", i->second.c_str()));
713 
714 		const v2<float> ap = a->second.convert<float>();
715 		const v2<float> bp = b->second.convert<float>();
716 		//LOG_DEBUG(("%d:%d -> %d:%d", ap.x, ap.y, bp.x, bp.y));
717 		v2<float> p = ap, d = bp - ap;
718 		d.normalize();
719 		p += d * w;
720 		int len0 = (int)ap.distance(bp);
721 		for(int len = len0; len > w; len -= w, p += d * w) {
722 			const sdlx::Rect &r = (len == len0)? out: (len <= 2 * w ? in:normal );
723 			surface.blit(*s, r,
724 			(int)(p.x - src.x + dst.x + d.x),
725 			(int)(p.y - src.y + dst.y + d.y));
726 		}
727 	}
728 }
729 
730 template<typename T>
coord2v(T & pos,const std::string & str)731 static void coord2v(T &pos, const std::string &str) {
732 	std::string pos_str = str;
733 
734 	const bool tiled_pos = pos_str[0] == '@';
735 	if (tiled_pos) {
736 		pos_str = pos_str.substr(1);
737 	}
738 
739 	TRY {
740 		pos.fromString(pos_str);
741 	} CATCH(mrt::format_string("parsing '%s'", str.c_str()).c_str() , throw;)
742 
743 	if (tiled_pos) {
744 		v2<int> tile_size = Map->getTileSize();
745 		pos.x *= tile_size.x;
746 		pos.y *= tile_size.y;
747 		//keep z untouched.
748 	}
749 }
750 
loadMap(Campaign * campaign,const std::string & name,const bool spawn_objects,const bool skip_loadmap)751 void IGameMonitor::loadMap(Campaign *campaign, const std::string &name, const bool spawn_objects, const bool skip_loadmap) {
752 	_campaign = campaign;
753 	const bool client = PlayerManager->is_client();
754 
755 	IMap &map = *IMap::get_instance();
756 
757 	if (!skip_loadmap) {
758 		map.load(name);
759 	} else {
760 		if (!map.loaded())
761 			throw_ex(("loadMap() called with skip Map::load() flag. Map must be initialized at this point."));
762 	}
763 
764 	ResourceManager->preload();
765 
766 	_waypoints.clear();
767 	_waypoint_edges.clear();
768 
769 	Config->clearOverrides();
770 
771 #	ifdef ENABLE_LUA
772 		if (lua_hooks)
773 			lua_hooks->clear();
774 #	endif
775 
776 	std::string script = Finder->find("maps/" + name + ".lua", false);
777 
778 	if (!skip_loadmap && !script.empty() && !RTConfig->editor_mode) {
779 #	ifdef ENABLE_LUA
780 		TRY {
781 			if (lua_hooks) {
782 				lua_hooks->load(script);
783 			}
784 		} CATCH("loadMap::load", {
785 			Game->clear();
786 			displayMessage("errors", "script-error", 1);
787 			return;
788 		});
789 #	else
790 		throw_ex(("this map requires lua scripting support."));
791 #	endif
792 	}
793 
794 	//difficulty settings
795 	int difficulty = 2; //map as is == hard, default: normal
796 
797 	if (campaign) {
798 
799 		std::string profile;
800 		Config->get("engine.profile", profile, std::string());
801 		if (profile.empty())
802 			throw_ex(("empty profile"));
803 
804 		Config->get("campaign." + profile + "." + campaign->name + ".difficulty", difficulty, 1);
805 
806 		Var v_true("bool");
807 		v_true.b = (difficulty >= 3);
808 		Config->setOverride("engine.fog-of-war.enabled", v_true);
809 	}
810 
811 
812 	//const v2<int> size = map.get_size();
813 	for (IMap::PropertyMap::iterator i = map.properties.begin(); i != map.properties.end(); ++i) {
814 		if (i->first.empty())
815 			throw_ex(("property name could not be empty"));
816 
817 		std::vector<std::string> res;
818 		mrt::split(res, i->first, ":");
819 		const std::string &type = res[0];
820 
821 		if (type == "waypoint" || type == "edge") //save some time
822 			continue;
823 
824 		if (type != "spawn" && type != "object" && type != "config" &&
825 			type != "zone" && type != "ambient-sound")
826 				throw_ex(("unsupported line: '%s'", i->first.c_str()));
827 
828 		if (!spawn_objects && type != "config")
829 			continue;
830 
831 		if (type == "ambient-sound") {
832 			Mixer->startAmbient(i->second);
833 			continue;
834 		}
835 
836 		v3<int> pos;
837 		int dir = 0;
838 		if (type != "edge" && type != "config") {
839 			std::string::size_type dp = i->second.rfind('/');
840 			if (dp != std::string::npos && dp + 1 < i->second.size()) {
841 				dir = atoi(i->second.substr(dp + 1).c_str());
842 				LOG_DEBUG(("found argument %d", dir));
843 			}
844 			coord2v< v3<int> >(pos, i->second.substr(0, dp));
845 		}
846 
847 		/*
848 		if (pos.x < 0)
849 			pos.x += size.x;
850 		if (pos.y < 0)
851 			pos.y += size.y;
852 		*/
853 
854 		if (type == "spawn") {
855 			LOG_DEBUG(("spawnpoint: %d,%d,%d", pos.x, pos.y, pos.z));
856 			v2<int> tile_size = Map->getTileSize();
857 			pos.x += tile_size.x / 2;
858 			pos.y += tile_size.y / 2;
859 			PlayerManager->add_slot(pos);
860 		} else {
861 			if (type == "object") {
862 				if (res.size() < 4)
863 					throw_ex(("'%s' misses an argument", i->first.c_str()));
864 
865 				const std::string classname = res[1];
866 				LOG_DEBUG(("spawning: object %s, animation %s, pos: %s", classname.c_str(), res[2].c_str(), i->second.c_str()));
867 				//LOG_DEBUG(("name: %s", res[3].c_str()));
868 				res.resize(5);
869 				GameItem item(res[1], res[2], i->first, v2<int>(pos.x, pos.y), pos.z);
870 				item.setup(res[3], res[4]);
871 				item.dir = dir;
872 
873 				bool add_item = true;
874 
875 				if (classname.compare(0, 4, "ctf-") == 0 || res[3].find("(ctf)") != std::string::npos || res[4].find("(ctf)") != std::string::npos)
876 					add_item = RTConfig->game_type == GameTypeCTF;
877 
878 				if (res[3].find("(no-ctf)") != std::string::npos || res[4].find("(no-ctf)") != std::string::npos)
879 					add_item = RTConfig->game_type != GameTypeCTF;
880 
881 				if (RTConfig->editor_mode || add_item)
882 					add(item, true);
883 			} else if (type == "config") {
884 				if (res.size() < 2)
885 					throw_ex(("'%s' misses an argument", i->first.c_str()));
886 
887 				std::vector<std::string> value;
888 				mrt::split(value, i->second, ":");
889 				value.resize(2);
890 				if (value[0] != "int" && value[0] != "float" && value[0] != "string" && value[0] != "bool")
891 					throw_ex(("cannot set config variable '%s' of type '%s'", res[1].c_str(), value[0].c_str()));
892 
893 				const std::string &name = res[1];
894 				if (difficulty == 0 && name == "map.spawn-limit") {
895 					LOG_DEBUG(("skipping spawn limit [difficulty]"));
896 					continue;
897 				}
898 
899 				Var var(value[0]);
900 				var.fromString(value[1]);
901 
902 				if (difficulty <= 1 && name.compare(0, 4, "map.") == 0) { //easy + normal
903 					//-item.respawn-interval
904 					std::vector<std::string> key_names;
905 					mrt::split(key_names, name, ".");
906 					if (key_names.size() > 2 && key_names[2] == "respawn-interval") {
907 						const std::string &item_name = key_names[1];
908 						if ((var.i < 0 || var.i >= 10000) &&
909 							(
910 								(item_name.size() > 5 && item_name.compare(item_name.size() - 5, 5, "-item") == 0) ||
911 								item_name == "megaheal" || item_name == "heal"
912 							)
913 						) { //stupid vz! :)
914 							LOG_DEBUG(("skipping: '%s' = %d override [difficulty]", name.c_str(), var.i));
915 							continue;
916 						}
917 					}
918 				}
919 
920 				Config->setOverride(name, var);
921 			} else if (type == "zone") {
922 				LOG_DEBUG(("%s %s %s", type.c_str(), i->first.c_str(), i->second.c_str()));
923 				std::vector<std::string> value;
924 				mrt::split(value, i->second, ":");
925 				if (value.size() < 2)
926 					throw_ex(("'%s' misses an argument", i->first.c_str()));
927 				v3<int> pos;
928 				v2<int> size;
929 				coord2v(pos, value[0]);
930 				coord2v(size, value[1]);
931 				res.resize(4);
932 
933 				SpecialZone zone(ZBox(pos, size), res[1], res[2], res[3]);
934 				zone.area = "hints/" + name;
935 				PlayerManager->add_special_zone(zone);
936 			}
937 		}
938 	}
939 
940 	if (Config->has("map.kill-em-all")) {
941 		std::string cstr;
942 		Config->get("map.kill-em-all", cstr, std::string());
943 		std::vector<std::string> res;
944 		mrt::split(res, cstr, ",");
945 
946 		std::set<std::string> classes;
947 		for(size_t i = 0; i < res.size(); ++i) {
948 			std::string &str = res[i];
949 			mrt::trim(str);
950 			if (!str.empty())
951 				classes.insert(str);
952 		}
953 		killAllClasses(classes);
954 		LOG_DEBUG(("kill'em all classes: %u", (unsigned)classes.size()));
955 	}
956 
957 	float time_limit = RTConfig->time_limit;
958 	if ((RTConfig->game_type == GameTypeDeathMatch || RTConfig->game_type == GameTypeTeamDeathMatch || RTConfig->game_type == GameTypeCTF )
959 		&& time_limit > 0) {
960 		setTimer("messages", "time-limit-reached", time_limit, false);
961 	}
962 
963 	LOG_DEBUG(("generating matrixes"));
964 	Map->generateMatrixes();
965 
966 	parseWaypoints(0,0,0,0);
967 
968 	Config->invalidateCachedValues();
969 
970 	GET_CONFIG_VALUE("engine.max-time-slice", float, mts, 0.025);
971 	World->setTimeSlice(mts);
972 
973 	for(Items::iterator i = _items.begin(); i != _items.end(); ++i) {
974 		if (!i->hidden)
975 			i->respawn();
976 	}
977 
978 #	ifdef ENABLE_LUA
979 	TRY {
980 		if (!client && lua_hooks)
981 			lua_hooks->on_load();
982 	} CATCH("loadMap::on_load", {
983 		Game->clear();
984 		displayMessage("errors", "script-error", 1);
985 		return;
986 	});
987 
988 #	endif
989 
990 	Window->resetTimer();
991 }
992 
parseWaypoints(int,int,int,int)993 void IGameMonitor::parseWaypoints(int, int, int, int) {
994 	LOG_DEBUG(("parsing waypoints..."));
995 	IMap &map = *IMap::get_instance();
996 	v3<int> pos;
997 
998 	_waypoints.clear();
999 	_all_waypoints.clear();
1000 	_waypoint_edges.clear();
1001 
1002 	for (IMap::PropertyMap::iterator i = map.properties.begin(); i != map.properties.end(); ++i) {
1003 		if (i->first.empty())
1004 			throw_ex(("property name could not be empty"));
1005 
1006 		std::vector<std::string> res;
1007 		mrt::split(res, i->first, ":");
1008 		const std::string &type = res[0];
1009 
1010 		if (type == "waypoint") {
1011 			if (res.size() < 3)
1012 				throw_ex(("'%s' misses an argument", i->first.c_str()));
1013 			v2<int> tile_size = Map->getTileSize(); //tiled correction
1014 			coord2v< v3<int> >(pos, i->second);
1015 			pos.x += tile_size.x / 2;
1016 			pos.y += tile_size.y / 2;
1017 			LOG_DEBUG(("waypoint class %s, name %s : %d,%d", res[1].c_str(), res[2].c_str(), pos.x, pos.y));
1018 			_waypoints[res[1]][res[2]] = v2<int>(pos.x, pos.y);
1019 			_all_waypoints[res[2]] = v2<int>(pos.x, pos.y);
1020 		} else if (type == "edge") {
1021 			if (res.size() < 3)
1022 				throw_ex(("'%s' misses an argument", i->first.c_str()));
1023 			if (res[1] == res[2])
1024 				throw_ex(("map contains edge from/to the same vertex"));
1025 			_waypoint_edges.insert(WaypointEdgeMap::value_type(res[1], res[2]));
1026 		}
1027 	}
1028 
1029 	LOG_DEBUG(("checking waypoint graph..."));
1030 	for(WaypointEdgeMap::const_iterator i = _waypoint_edges.begin(); i != _waypoint_edges.end(); ++i) {
1031 		const std::string &dst = i->second;
1032 		WaypointEdgeMap::const_iterator b = _waypoint_edges.lower_bound(dst);
1033 		if (b == _waypoint_edges.end() || b->first != dst)
1034 			throw_ex(("no edges out of waypoint '%s'", dst.c_str()));
1035 	}
1036 	LOG_DEBUG(("%u items on map, %u waypoint classes, %u edges", (unsigned)getItemsCount(), (unsigned)_waypoints.size(), (unsigned)_waypoint_edges.size()));
1037 }
1038 
generatePropertyName(const std::string & prefix)1039 const std::string IGameMonitor::generatePropertyName(const std::string &prefix) {
1040 	//LOG_DEBUG(("prefix: %s", prefix.c_str()));
1041 	IMap::PropertyMap::const_iterator b = Map->properties.lower_bound(prefix);
1042 	int n = 0;
1043 
1044 	for(IMap::PropertyMap::const_iterator i = b; i != Map->properties.end(); ++i) {
1045 		if (i->first.compare(0, prefix.size(), prefix) != 0)
1046 			continue;
1047 		std::string suffix = i->first.substr(prefix.size());
1048 		if (!suffix.empty() && suffix[0] == ':') {
1049 			int i = atoi(suffix.c_str() + 1);
1050 			if (i > n)
1051 				n = i;
1052 		}
1053 	}
1054 
1055 	++n;
1056 
1057 	std::string name =  mrt::format_string("%s:%d", prefix.c_str(), n);
1058 	if (Map->properties.find(name) != Map->properties.end())
1059 		throw_ex(("failed to generate unique name. prefix: %s, n: %d", prefix.c_str(), n));
1060 	return name;
1061 }
1062 
addBonuses(const PlayerSlot & slot)1063 void IGameMonitor::addBonuses(const PlayerSlot &slot) {
1064 	if (_campaign == NULL)
1065 		return;
1066 	Object *o = slot.getObject();
1067 	if (o == NULL)
1068 		return;
1069 	const std::vector<Campaign::ShopItem> & wares = _campaign->wares;
1070 
1071 	bool first_time = bonuses.empty();
1072 	size_t idx = 0;
1073 
1074 	for(std::vector<Campaign::ShopItem>::const_iterator i = wares.begin(); i != wares.end(); ++i) {
1075 		int n = i->amount;
1076 		if (n <= 0 || i->object.empty() || i->animation.empty())
1077 			continue;
1078 		LOG_DEBUG(("adding bonus: %s", i->name.c_str()));
1079 		int dirs = (n > 8)?16:(n > 4 ? 8: 4);
1080 		for(int d = 0; d < n; ++d) {
1081 			v2<float> dir;
1082 			dir.fromDirection(d % dirs, dirs);
1083 			dir *= o->size.length();
1084 			//LOG_DEBUG(("%g %g", d.x, d.y));
1085 			if (first_time)
1086 				bonuses.push_back(GameBonus(i->object + "(ally)", i->animation, 0));
1087 			if (World->getObjectByID(bonuses[idx].id) == NULL) {
1088 				Object *bonus = o->spawn(bonuses[idx].classname, bonuses[idx].animation, dir, v2<float>());
1089 				bonuses[idx].id = bonus->get_id();
1090 			}
1091 			++idx;
1092 		}
1093 	}
1094 }
1095 
1096 #include "nickname.h"
1097 #include "rt_config.h"
1098 
startGame(Campaign * campaign,const std::string & name)1099 void IGameMonitor::startGame(Campaign *campaign, const std::string &name) {
1100 	Game->clear();
1101 	PlayerManager->start_server();
1102 	GameMonitor->loadMap(campaign, name);
1103 	if (!Map->loaded())
1104 		return; //error
1105 
1106 	if (PlayerManager->get_slots_count() <= 0)
1107 		throw_ex(("no slots available on map"));
1108 
1109 	if (RTConfig->server_mode)
1110 		return;
1111 
1112 	std::string profile;
1113 	Config->get("engine.profile", profile, std::string());
1114 	if (profile.empty())
1115 		throw_ex(("empty profile"));
1116 
1117 	PlayerSlot &slot = PlayerManager->get_slot(0);
1118 	std::string cm;
1119 	Config->get("profile." + profile + ".control-method", cm, "keys");
1120 	Config->get("profile." + profile + ".name", slot.name, Nickname::generate());
1121 	slot.createControlMethod(cm);
1122 
1123 	std::string object, vehicle;
1124 	slot.getDefaultVehicle(object, vehicle);
1125 	slot.spawn_player(0, object, vehicle);
1126 	PlayerManager->get_slot(0).setViewport(Window->get_size());
1127 	total_time = 0;
1128 }
1129 
~IGameMonitor()1130 IGameMonitor::~IGameMonitor() {
1131 #ifdef ENABLE_LUA
1132 	delete lua_hooks;
1133 #endif
1134 }
1135 
onTooltip(const std::string & event,const int slot_id,const std::string & area,const std::string & message)1136 void IGameMonitor::onTooltip(const std::string &event, const int slot_id, const std::string &area, const std::string &message) {
1137 #ifdef ENABLE_LUA
1138 	if (lua_hooks)
1139 		lua_hooks->on_tooltip(event, slot_id, area, message);
1140 #endif
1141 }
1142 
onScriptZone(const int slot_id,const SpecialZone & zone,const bool global)1143 void IGameMonitor::onScriptZone(const int slot_id, const SpecialZone &zone, const bool global) {
1144 	const bool client = PlayerManager->is_client();
1145 	if (client)
1146 		return;
1147 
1148 #ifndef ENABLE_LUA
1149 	throw_ex(("no script support compiled in."));
1150 #else
1151 	TRY {
1152 		if (lua_hooks == NULL)
1153 			throw_ex(("lua hooks was not initialized"));
1154 		if (global)
1155 			lua_hooks->call(zone.name);
1156 		else
1157 			lua_hooks->call1(zone.name, slot_id + 1);
1158 	} CATCH("onScriptZone", {
1159 		Game->clear();
1160 		displayMessage("errors", "script-error", 1);
1161 		return;
1162 	});
1163 
1164 #endif
1165 }
1166 
onConsole(const std::string & cmd,const std::string & param)1167 const std::string IGameMonitor::onConsole(const std::string &cmd, const std::string &param) {
1168 #ifdef ENABLE_LUA
1169 	if (cmd == "call") {
1170 		try {
1171 			if (lua_hooks == NULL)
1172 				throw_ex(("lua hooks was not initialized"));
1173 			lua_hooks->call(param);
1174 		} catch(const std::exception &e) {
1175 			return std::string("error") + e.what();
1176 		}
1177 		return "ok";
1178 	}
1179 #endif
1180 	return std::string();
1181 }
1182 
usedInCampaign(const std::string & base,const std::string & id) const1183 const bool IGameMonitor::usedInCampaign(const std::string &base, const std::string &id) const {
1184 	return used_maps.find(std::pair<std::string, std::string>(base, id)) != used_maps.end();
1185 }
1186 
useInCampaign(const std::string & base,const std::string & id)1187 const void IGameMonitor::useInCampaign(const std::string &base, const std::string &id) {
1188 	used_maps.insert(std::pair<std::string, std::string>(base, id));
1189 }
1190 
saveCampaign()1191 void IGameMonitor::saveCampaign() {
1192 	if (_campaign == NULL)
1193 		return;
1194 
1195 	LOG_DEBUG(("saving compaign state..."));
1196 	std::string profile;
1197 	Config->get("engine.profile", profile, std::string());
1198 	if (profile.empty())
1199 		throw_ex(("empty profile"));
1200 
1201 	const std::string mname = "campaign." + profile + "." + _campaign->name + ".maps." + Map->getName();
1202 
1203 	std::string prefix = _campaign->get_config_prefix();
1204 
1205 	if (PlayerManager->get_slots_count()) {
1206 		PlayerSlot &slot = PlayerManager->get_slot(0);
1207 		int score;
1208 		Config->get(prefix + ".score", score, 0);
1209 		score += slot.score;
1210 		Config->set(prefix + ".score", score);
1211 		LOG_DEBUG(("total score: %d", score));
1212 
1213 		int mscore;
1214 		Config->get(mname + ".maximum-score", mscore, 0);
1215 		if (slot.score > mscore)
1216 			Config->set(mname + ".maximum-score", slot.score);
1217 
1218 		Config->set(mname + ".last-score", slot.score);
1219 	}
1220 
1221 	bool win;
1222 	Config->get(mname + ".win", win, false);
1223 	if (_win) {
1224 		Config->set(mname + ".win", _win);
1225 		_campaign->clearBonuses();
1226 	}
1227 
1228 	if (_win && total_time > 0) {
1229 		float best_time;
1230 		Config->get(mname + ".best-time", best_time, total_time);
1231 
1232 		if (total_time < best_time)
1233 			Config->set(mname + ".best-time", total_time);
1234 		Config->set(mname + ".last-time", total_time);
1235 	}
1236 	_campaign = NULL;
1237 }
1238 
startGameTimer(const std::string & name,const float period,const bool repeat)1239 void IGameMonitor::startGameTimer(const std::string &name, const float period, const bool repeat) {
1240 	LOG_DEBUG(("starting timer '%s', %g sec., repeat: %s", name.c_str(), period, repeat?"yes":"no"));
1241 	timers.insert(Timers::value_type(name, Timer(period, repeat)));
1242 }
1243 
stopGameTimer(const std::string & name)1244 void IGameMonitor::stopGameTimer(const std::string &name) {
1245 	timers.erase(name);
1246 }
1247 
processGameTimers(const float dt)1248 void IGameMonitor::processGameTimers(const float dt) {
1249 #ifdef ENABLE_LUA
1250 	if (lua_hooks == NULL)
1251 		return;
1252 
1253 	std::list<std::string> fired_timers;
1254 	for(Timers::iterator i = timers.begin(); i != timers.end(); ) {
1255 		Timer & timer = i->second;
1256 		timer.t += dt;
1257 		if (timer.t >= timer.period) {
1258 			//triggering event
1259 			std::string name = i->first;
1260 
1261 			if (timer.repeat) {
1262 				timer.t = fmodf(timer.t, timer.period);
1263 				++i;
1264 			} else {
1265 				//one shot
1266 				timers.erase(i++);
1267 			}
1268 
1269 			fired_timers.push_back(name);
1270 		} else {
1271 			++i; //continue;
1272 		}
1273 	}
1274 	for(std::list<std::string>::iterator i = fired_timers.begin(); i != fired_timers.end(); ++i) {
1275 		const std::string &name = *i;
1276 		TRY {
1277 			LOG_DEBUG(("calling on_timer(%s)", name.c_str()));
1278 			lua_hooks->on_timer(name); //callback could add/delete timers!!
1279 		} CATCH("processGameTimers", {});
1280 	}
1281 #endif
1282 }
1283 
1284