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 ¶m) {
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