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 "buratino.h"
29 #include <assert.h>
30 #include "world.h"
31 #include "config.h"
32
33 #include "math/unary.h"
34 #include "player_manager.h"
35 #include "game_monitor.h"
36 #include "mrt/random.h"
37 #include "math/binary.h"
38 #include "vehicle_traits.h"
39 #include "tmx/map.h"
40 #include "special_zone.h"
41 #include "team.h"
42
43 using namespace ai;
44
Buratino()45 Buratino::Buratino() : _reaction_time(true), _refresh_path(false), _target_id(-1), _target_dir(-1) {}
46
active() const47 const bool Buratino::active() const {
48 return !PlayerManager->is_client();
49 }
50
~Buratino()51 Buratino::~Buratino() {
52 if (!active())
53 return;
54
55 if (!_traits.empty())
56 LOG_DEBUG(("traits: \n%s", _traits.save().c_str()));
57 }
58
addEnemyClass(const std::string & classname)59 void Buratino::addEnemyClass(const std::string &classname) {
60 _enemies.insert(classname);
61 }
62
addBonusName(const std::string & rname)63 void Buratino::addBonusName(const std::string &rname) {
64 _bonuses.insert(rname);
65 }
66
processPF(Object * object)67 void Buratino::processPF(Object *object) {
68 if (object->calculating_path()) {
69 Way way;
70 int n = 1;
71 bool found;
72 while(! (found = object->find_path_done(way)) && n < _pf_slice)
73 ++n;
74
75 if (found) {
76 //LOG_DEBUG(("n = %d", n));
77 if (!way.empty()) {
78 object->set_way(way);
79 _skip_objects.clear();
80 } else {
81 LOG_DEBUG(("no path, adding %d to targets black list ", _target_id));
82 _skip_objects.insert(_target_id);
83 }
84 }
85 } else {
86 //LOG_DEBUG(("idle"));
87
88 }
89 }
90
91
on_spawn(const Object * object)92 void Buratino::on_spawn(const Object *object) {
93 if (!active())
94 return;
95
96 const std::string vehicle = object->getType();
97 if (vehicle.empty())
98 throw_ex(("vehicle MUST provide its type"));
99
100 LOG_DEBUG(("spawning as '%s'", vehicle.c_str()));
101 if (_enemies.empty() && _bonuses.empty())
102 throw_ex(("vehicle had not provided enemies/bonuses"));
103
104 float rt;
105 Config->get("objects.ai-" + object->registered_name + ".reaction-time", rt, 0.1f);
106 float rpi = 2.0f;
107
108 mrt::randomize(rt, rt/10);
109 _reaction_time.set(rt);
110
111 mrt::randomize(rpi, rpi/10);
112 _refresh_path.set(rpi);
113
114 Config->get("objects.ai-" + vehicle + ".pathfinding-slice", _pf_slice, 10);
115 }
116
isEnemy(const Object * o) const117 const bool Buratino::isEnemy(const Object *o) const {
118 return _enemies.find(o->classname) != _enemies.end();
119 }
120
convertName(const std::string & weapon)121 const std::string Buratino::convertName(const std::string &weapon) {
122 std::string wc, wt;
123 std::string::size_type p;
124 if ((p = weapon.rfind(':')) != std::string::npos) {
125 wc = weapon.substr(0, p);
126 wt = weapon.substr(p + 1);
127 } else {
128 wt = weapon;
129 }
130 if (wc.empty())
131 return wt;
132 return wt + "-" + wc.substr(0, wc.size() - 1);
133 }
134
135
checkTarget(const Object * object,const Object * target,const std::string & weapon) const136 const bool Buratino::checkTarget(const Object *object, const Object * target, const std::string &weapon) const {
137 if (!isEnemy(target))
138 return false;
139
140 if (object->registered_name == "shilka" || object->registered_name == "static-shilka") {
141 return true;
142 }
143
144 v2<float> pos = object->get_relative_position(target);
145
146 std::string wc, wt;
147 {
148 std::string::size_type p;
149 if ((p = weapon.rfind(':')) != std::string::npos) {
150 wc = weapon.substr(0, p);
151 wt = weapon.substr(p + 1);
152 } else {
153 wc = weapon;
154 }
155 }
156
157 bool codir, codir1;
158 {
159 v2<float> d(pos);
160 d.normalize();
161 int dir = d.get_direction(object->get_directions_number()) - 1;
162 codir = dir == object->get_direction();
163 int dd = math::abs(dir - object->get_direction());
164 codir1 = codir || dd == 1 || dd == (object->get_directions_number() - 1);
165 }
166
167 //LOG_DEBUG(("checking target(%s/%s): %g %g codir: %c, codir1: %c", wc.c_str(), wt.c_str(), pos.x, pos.y, codir?'+':'-', codir1?'+':'-'));
168
169 if (wc == "missiles" || wc == "bullets" || wc == "bullet") {
170 if (codir)
171 return true;
172 if ((wt == "guided" && codir1) || wt == "dispersion")
173 return true;
174 if (wt == "boomerang")
175 return true;
176 } else if (wc == "mines") {
177 if (!object->_velocity.is0())
178 return true;
179 }
180 return false;
181 }
182
calculateCloseCombat(Object * object,const Object * target,const float range,const bool dumb)183 void Buratino::calculateCloseCombat(Object *object, const Object *target, const float range, const bool dumb) {
184 assert(object != NULL);
185 assert(target != NULL);
186
187 //LOG_DEBUG(("close combat with %s, range: %g, dumb: %c", target->animation.c_str(), range, dumb?'+':'-'));
188
189 if (!dumb) {
190 _target_dir = object->get_target_position(_target_position, object->get_relative_position(target), range);
191 if (_target_dir >= 0)
192 Map->add(_target_position, object->get_center_position());
193 }
194
195 object->_velocity = Map->distance(object->get_center_position(), _target_position);
196
197 //LOG_DEBUG(("object velocity: %g,%g, target dir: %d", object->_velocity.x, object->_velocity.y, _target_dir));
198
199 if (_target_dir >= 0) {
200 int dirs = object->get_directions_number();
201 if (object->_velocity.length() >= 9) {
202 object->quantize_velocity();
203 object->_direction.fromDirection(object->get_direction(), dirs);
204 } else {
205 object->_velocity.clear();
206 object->set_direction(_target_dir);
207 //LOG_DEBUG(("%d", _target_dir));
208 object->_direction.fromDirection(_target_dir, dirs);
209 std::string weapon1 = getWeapon(0), weapon2 = getWeapon(1);
210 object->_state.fire = checkTarget(object, target, weapon1);
211 object->_state.alt_fire = checkTarget(object, target, weapon2);
212 //LOG_DEBUG(("firing: %c%c", object->_state.fire?'+':'-', object->_state.alt_fire?'+':'-'));
213 }
214 } else {
215 object->_velocity.clear();
216 }
217 }
218
getWeaponRange(const Object * object) const219 const float Buratino::getWeaponRange(const Object *object) const {
220 std::string weapon1 = getWeapon(0), weapon2 = getWeapon(1);
221
222 float range = 0;
223 if (!weapon1.empty()) {
224 range = math::max(range, object->getWeaponRange(convertName(weapon1)));
225 }
226 if (!weapon2.empty()) {
227 range = math::max(range, object->getWeaponRange(convertName(weapon2)));
228 }
229 return range;
230 }
231
calculate(Object * object,const float dt)232 void Buratino::calculate(Object *object, const float dt) {
233 if (object->ai_disabled()) {
234 return;
235 }
236
237 if (!active()) {
238 if (object->is_driven())
239 object->calculate_way_velocity();
240 else
241 object->Object::calculate(dt);
242 object->update_state_from_velocity();
243 return;
244 }
245
246 const bool racing = object->get_variants().has("racing");
247 const bool refresh_path = !racing && _refresh_path.tick(dt) && object->is_driven();
248 const bool dumb = !_reaction_time.tick(dt);
249 const Object *target = NULL;
250
251 std::string weapon1, weapon2;
252 int amount1, amount2;
253
254 if (dumb) {
255 if (_target_dir >= 0) {
256 if (target == NULL)
257 target = World->getObjectByID(_target_id);
258 if (target == NULL)
259 goto gogogo;
260
261 //processPF(object);
262 calculateCloseCombat(object, target, getWeaponRange(object), true);
263 goto skip_calculations;
264 }
265 goto gogogo;
266 }
267
268
269 weapon1 = getWeapon(0), weapon2 = getWeapon(1);
270 amount1 = getWeaponAmount(0), amount2 = getWeaponAmount(1);
271
272 if (target == NULL)
273 target = World->getObjectByID(_target_id);
274
275 if (amount1 < 0)
276 amount1 = 50;
277 if (amount2 < 0)
278 amount2 = 50; //infinite amount
279
280 if (target != NULL) {
281 if (!weapon1.empty())
282 object->_state.fire = checkTarget(object, target, weapon1);
283 if (!weapon2.empty())
284 object->_state.alt_fire = checkTarget(object, target, weapon2);
285
286 float range = getWeaponRange(object);
287
288 v2<float> dpos = object->get_relative_position(target);
289 if (_enemy && dpos.length() <= range) {
290 //processPF(object);
291 calculateCloseCombat(object, target, range, false);
292
293 if (_target_dir >= 0) {
294 if (object->is_driven())
295 object->set_way(Way());
296 }
297 } else {
298 _target_dir = -1;
299 }
300 }
301
302 if (racing) {
303 Way way;
304 if (object->calculating_path()) {
305 goto gogogo;
306 }
307 if (!object->is_driven()) {
308 int slot_id = PlayerManager->get_slot_id(object->get_id());
309 if (slot_id <= 0)
310 throw_ex(("ai in racing mode cannot operate without slot."));
311
312 PlayerSlot &slot = PlayerManager->get_slot(slot_id);
313 const SpecialZone &zone = PlayerManager->get_next_checkpoint(slot);
314 v3<int> position = zone.getPlayerPosition(slot_id);
315
316 object->find_path(v2<int>(position.x, position.y), 24);
317 }
318 }
319
320 target = findTarget(object,
321 (amount1 > 0 || amount2 > 0)?_enemies:std::set<std::string>(),
322 object->get_variants().has("no-bonuses")?std::set<std::string>():_bonuses,
323 _traits, _skip_objects);
324
325 if (target != NULL) {
326 if ( ((refresh_path && isEnemy(target)) || target->get_id() != _target_id)) {
327 _target_id = target->get_id();
328 _enemy = isEnemy(target);
329 v2<int> target_position;
330 target->get_center_position(target_position);
331 /*
332 if (_enemy && !weapon1.empty()) {
333 v2<float> r;
334 if (object->get_target_position(r, target->get_position(), convertName(weapon1)))
335 _target_position = r.convert<int>();
336 }
337 */
338 LOG_DEBUG(("%d: %s: next target: %s at %d,%d",
339 object->get_id(), object->animation.c_str(), target->animation.c_str(), target_position.x, target_position.y));
340 object->find_path(target_position, 24);
341 _refresh_path.reset();
342
343 //Way way;
344 //if (!old_find_path(target, way))
345 // LOG_WARN(("no way"));
346 //else set_way(way);
347 }
348 } else if (!object->is_driven()) {
349 //LOG_DEBUG(("%d:%s idle", object->_id, object->animation.c_str()));
350 object->_velocity.clear();
351 }
352
353 //2 fire or not 2 fire.
354
355
356
357 //LOG_DEBUG(("w1: %s", getWeapon(0).c_str()));
358 //LOG_DEBUG(("w2: %s", getWeapon(1).c_str()));
359
360 //bool driven = is_driven();
361
362 //LOG_DEBUG(("calculating: %c, driven: %c", calculating?'+':'-', driven?'+':'-'));
363 gogogo:
364
365 processPF(object);
366
367 object->calculate_way_velocity();
368
369 skip_calculations:
370 if (target != NULL) {
371 if (!weapon1.empty() && !object->_state.fire)
372 object->_state.fire = checkTarget(object, target, weapon1);
373 if (!weapon2.empty() && !object->_state.alt_fire)
374 object->_state.alt_fire = checkTarget(object, target, weapon2);
375 }
376 object->update_state_from_velocity();
377 }
378
findTarget(const Object * src,const std::set<std::string> & enemies,const std::set<std::string> & bonuses,ai::Traits & traits,const std::set<int> & skip_objects) const379 const Object * Buratino::findTarget(const Object *src, const std::set<std::string> &enemies, const std::set<std::string> &bonuses, ai::Traits &traits, const std::set<int> &skip_objects) const {
380 if (src->get_variants().has("racing"))
381 return NULL;
382
383 if (src->has("#ctf-flag")) {
384 Team::ID team = Team::get_team(src->get("#ctf-flag"));
385 if (team != Team::Red && team != Team::Green)
386 throw_ex(("flag team must be red or green"));
387 int base_id = GameMonitor->getBase(team == Team::Red? Team::Green: Team::Red);
388 Object *base = World->getObjectByID(base_id);
389 if (base != NULL && !base->has_effect("abandoned")) {
390 return base;
391 }
392 }
393
394 if (src->getType().empty())
395 throw_ex(("findTarget source must always provide its type"));
396
397 const Object *result = NULL;
398 float result_value = 0;
399 std::set<const Object *> objects;
400 {
401 float range;
402 Config->get("objects." + src->registered_name + ".range", range, 640.0f);
403 World->enumerate_objects(objects, src, range, NULL);
404 }
405
406 for(std::set<const Object *>::const_iterator i = objects.begin(); i != objects.end(); ++i) {
407 const Object *o = *i;
408 if (o->impassability == 0 || o->_id == src->_id || o->hp <= 0 ||
409 !ZBox::sameBox(src->get_z(), o->get_z()) ||
410 o->has_same_owner(src) ||
411 o->has_effect("invulnerability") ||
412 skip_objects.find(o->get_id()) != skip_objects.end()
413 )
414 continue;
415
416 const bool enemy = enemies.find(o->classname) != enemies.end();
417 if (enemy && o->hp < 0)
418 continue; //invulnerable enemy
419
420 const bool bonus = bonuses.find(o->registered_name) != bonuses.end() || bonuses.find(o->classname) != bonuses.end();
421 //LOG_DEBUG(("object: %s", o->registered_name.c_str()));
422 if (!enemy && !bonus)
423 continue;
424
425 //bonus!
426 int min = 0, max = 0;
427 float multiplier = 1;
428
429 std::string mod_type = o->classname;
430 if (mod_type == "teleport") {
431 v2<float> dpos = src->get_relative_position(o);
432 if (dpos.quick_length() < ((o->size.x + o->size.y) * 141 / 100))
433 continue;
434 }
435 if (!o->getType().empty())
436 mod_type += ":" + o->getType();
437 if (o->has_effect("invulnerability"))
438 continue;
439
440 if (o->classname == "missiles" || o->classname == "mines") {
441 if (src->has("mod")) {
442 const Object *mod = src->get("mod");
443 if (mod->getType() == mod_type)
444 min = mod->getCount();
445 }
446
447 if (src->has("alt-mod")) {
448 const Object *mod = src->get("alt-mod");
449 if (mod->getType() == mod_type)
450 min = mod->getCount();
451 }
452 assert(min >= 0);
453 int max_v;
454 VehicleTraits::getWeaponCapacity(max, max_v, src->getType(), o->classname, o->getType());
455 if (min > max)
456 min = max;
457 } else if (o->classname == "heal") {
458 min = src->hp;
459 max = src->max_hp;
460 } else if (o->classname == "effects" || o->classname == "mod") {
461 max = 1;
462 } else if (o->classname == "teleport") {
463 max = 1;
464 } else if (o->classname == "vehicle") {
465 max = 1;
466 } else if (o->classname == "ctf-flag") {
467 PlayerSlot *slot = PlayerManager->get_slot_by_id(src->get_id());
468 if (slot == NULL)
469 continue;
470 Team::ID flag_team = Team::get_team(o);
471 if (flag_team == slot->team) {
472 const Object *base = World->getObjectByID(o->get_summoner());
473 if (base == NULL) {
474 LOG_WARN(("could not find base #%d for %s", o->get_summoner(), o->animation.c_str()));
475 continue;
476 }
477
478 v2<float> dpos = o->get_relative_position(base);
479 if (dpos.quick_length() < o->size.x * o->size.y / 4) {
480 //my flag is on the base
481 continue;
482 }
483 multiplier = traits.get("value", "ctf-flag-multiplier", 2, 4);
484 }
485 min = 0;
486 max = 1;
487 }
488 float value = 0;
489 const std::string type = o->getType();
490
491 if (enemy) {
492 value = traits.get("enemy", type.empty()?o->classname:type, 1000.0, 1000.0) * multiplier;
493 } else if (bonus) {
494 if (max == 0) {
495 LOG_WARN(("cannot determine bonus for %s", o->registered_name.c_str()));
496 continue;
497 }
498 value = traits.get("value", mod_type, 1.0, 1000.0) * (max - min) * multiplier / max;
499 } else assert(0);
500
501 if (enemy) {
502 value *= (getFirePower(src, traits) + 1) / (getFirePower(o, traits) + 1);
503 if (o->has("#ctf-flag"))
504 value *= traits.get("value", "pwned-ctf-flag", 2, 4);
505 }
506 value /= (src->_position.distance(o->_position));
507 //LOG_DEBUG(("item: %s, value: %g", o->registered_name.c_str(), value));
508 //find most valuable item.
509 if (value > result_value) {
510 result = o;
511 result_value = value;
512 }
513 }
514 return result;
515 }
516
getFirePower(const Object * o,ai::Traits & traits)517 const float Buratino::getFirePower(const Object *o, ai::Traits &traits) {
518 float value = 0;
519 if (o->has("mod")) {
520 const Object *mod = o->get("mod");
521 int c = mod->getCount();
522 const std::string& type = mod->getType();
523 if (c > 0 && !type.empty())
524 value += traits.get("value", type, 1.0, 1000.0) * c;
525 }
526 if (o->has("alt-mod")) {
527 const Object *mod = o->get("alt-mod");
528 int c = mod->getCount();
529 const std::string& type = mod->getType();
530 if (c > 0 && !type.empty())
531 value += traits.get("value", type, 1.0, 1000.0) * c;
532 }
533 return value;
534 }
535