1 /////////////////////////////////////////
2 //
3 // OpenLieroX
4 //
5 // code under LGPL, based on JasonBs work,
6 // enhanced by Dark Charlie and Albert Zeyer
7 //
8 //
9 /////////////////////////////////////////
10
11
12 // Worm class - AI
13 // Created 13/3/03
14 // Jason Boettcher
15 // Dark Charlie
16 // Albert Zeyer
17
18 // TODO: cleanup!!!
19
20
21 #ifdef _MSC_VER
22 #pragma warning(disable: 4786)
23 #endif
24
25 #include <cassert>
26 #include <set>
27
28 #include "LieroX.h"
29 #include "CGameScript.h"
30 #include "MathLib.h"
31 #include "CClient.h"
32 #include "CBonus.h"
33 #include "GfxPrimitives.h"
34 #include "StringUtils.h"
35 #include "CWorm.h"
36 #include "AuxLib.h"
37 #include "Timer.h"
38 #include "Sounds.h"
39 #include "ProfileSystem.h"
40 #include "CWormBot.h"
41 #include "Debug.h"
42 #include "CGameMode.h"
43 #include "CHideAndSeek.h"
44 #include "FlagInfo.h"
45 #include "ProjectileDesc.h"
46 #include "WeaponDesc.h"
47 #include "Mutex.h"
48
49
50 // used by searchpath algo
51 static const unsigned short wormsize = 7;
52
53
54 /*
55 ===============================
56
57 Artificial Intelligence
58
59 ===============================
60 */
61
62
63 // returns the biggest possible free rectangle with the given point inside
64 // (not in every case, but in most it is the biggest; but it is ensured that the given rect is at least free)
65 // the return-value of type SquareMatrix consists of the top-left and upper-right pixel
66 // WARNING: if the given point is not in the map, the returned
67 // area is also not in the map (but it will handle it correctly)
getMaxFreeArea(VectorD2<int> p,uchar checkflag)68 SquareMatrix<int> getMaxFreeArea(VectorD2<int> p, uchar checkflag) {
69 uint map_w = cClient->getMap()->GetWidth();
70 uint map_h = cClient->getMap()->GetHeight();
71 uint grid_w = cClient->getMap()->getGridWidth();
72 uint grid_h = cClient->getMap()->getGridHeight();
73 uint grid_cols = cClient->getMap()->getGridCols();
74 const uchar* pxflags = cClient->getMap()->GetPixelFlags();
75 const uchar* gridflags = cClient->getMap()->getAbsoluteGridFlags();
76
77 SquareMatrix<int> ret;
78 ret.v1 = p; ret.v2 = p;
79
80 // just return if we are outside
81 if(p.x < 0 || (uint)p.x >= map_w
82 || p.y < 0 || (uint)p.y >= map_h)
83 return ret;
84
85 enum { GO_RIGHT=1, GO_DOWN=2, GO_LEFT=4, GO_UP=8 }; short dir;
86 unsigned short col;
87 int x=0, y=0;
88 int grid_x=0, grid_y=0;
89 bool avoided_all_grids;
90
91 // loop over all directions until there is some obstacle
92 col = 0; dir = 1;
93 while(true) {
94 if(col == 0xF) // got we collisions in all directions?
95 break;
96
97 // change direction
98 do {
99 dir = dir << 1;
100 if(dir > 8) dir = 1;
101 } while(col & dir);
102
103 // set start pos
104 switch(dir) {
105 case GO_RIGHT: x=ret.v2.x+1; y=ret.v1.y; break;
106 case GO_DOWN: x=ret.v1.x; y=ret.v2.y+1; break;
107 case GO_LEFT: x=ret.v1.x-1; y=ret.v1.y; break;
108 case GO_UP: x=ret.v1.x; y=ret.v1.y-1; break;
109 }
110
111 // check if still inside the map (than nothing bad can happen)
112 if(x < 0 || (uint)x >= map_w
113 || y < 0 || (uint)y >= map_h) {
114 col |= dir;
115 continue;
116 }
117
118 // loop over all pxflags of the aligned line and check for an obstacle
119 avoided_all_grids = true;
120 while(true) {
121 // break if ready
122 if(dir == GO_RIGHT || dir == GO_LEFT) {
123 if(y > ret.v2.y) break;
124 } else // GO_UP / GO_DOWN
125 if(x > ret.v2.x) break;
126
127 // check if we can avoid this gridcell
128 grid_x = x / grid_w; grid_y = y / grid_h;
129 if(!(gridflags[grid_y*grid_cols + grid_x] & checkflag)) {
130 // yes we can and do now
131 switch(dir) {
132 case GO_RIGHT: case GO_LEFT: y=(grid_y+1)*grid_h; break;
133 case GO_DOWN: case GO_UP: x=(grid_x+1)*grid_w; break;
134 }
135 continue;
136 } else
137 avoided_all_grids = false;
138
139 // is there some obstacle?
140 if(pxflags[y*map_w + x] & checkflag) {
141 col |= dir;
142 break;
143 }
144
145 // inc the pos (trace the aligned line)
146 switch(dir) {
147 case GO_RIGHT: case GO_LEFT: y++; break;
148 case GO_DOWN: case GO_UP: x++; break;
149 }
150 }
151
152 if(!(col & dir)) {
153 if(avoided_all_grids) {
154 // we can jump to the end of the grids in this case
155 // grid_x/grid_y was already set here by the last loop
156 switch(dir) {
157 case GO_RIGHT: ret.v2.x=MIN((grid_x+1)*grid_w-1,map_w-1); break;
158 case GO_DOWN: ret.v2.y=MIN((grid_y+1)*grid_h-1,map_h-1); break;
159 case GO_LEFT: ret.v1.x=grid_x*grid_w; break;
160 case GO_UP: ret.v1.y=grid_y*grid_h; break;
161 }
162 } else { // not avoided_all_grids
163 // simple inc 1 pixel in the checked direction
164 switch(dir) {
165 case GO_RIGHT: ret.v2.x++; break;
166 case GO_DOWN: ret.v2.y++; break;
167 case GO_LEFT: ret.v1.x--; break;
168 case GO_UP: ret.v1.y--; break;
169 }
170 }
171 }
172
173 } // loop over directions
174
175 // cut the area if outer space...
176 if(ret.v1.x < 0) ret.v1.x = 0;
177 if(ret.v1.y < 0) ret.v1.y = 0;
178 if((uint)ret.v2.x >= map_w) ret.v2.x = map_w-1;
179 if((uint)ret.v2.y >= map_h) ret.v2.y = map_h-1;
180
181 return ret;
182 }
183
184
185
186
createNewAiNode(float x,float y,NEW_ai_node_t * next=NULL,NEW_ai_node_t * prev=NULL)187 NEW_ai_node_t* createNewAiNode(float x, float y, NEW_ai_node_t* next = NULL, NEW_ai_node_t* prev = NULL) {
188 NEW_ai_node_t* tmp = new NEW_ai_node_t;
189 tmp->fX = x; tmp->fY = y;
190 tmp->psNext = next; tmp->psPrev = prev;
191 return tmp;
192 }
193
createNewAiNode(NEW_ai_node_t * base)194 NEW_ai_node_t* createNewAiNode(NEW_ai_node_t* base) {
195 if(!base) return NULL;
196 NEW_ai_node_t* tmp = new NEW_ai_node_t;
197 tmp->fX = base->fX; tmp->fY = base->fY;
198 tmp->psNext = base->psNext; tmp->psPrev = base->psPrev;
199 return tmp;
200 }
201
createNewAiNode(const VectorD2<int> & p)202 NEW_ai_node_t* createNewAiNode(const VectorD2<int>& p) {
203 return createNewAiNode((float)p.x, (float)p.y);
204 }
205
206 // returns true, if the given line is free or not
207 // these function will either go parallel to the x-axe or parallel to the y-axe
208 // (depends on which of them is the absolute greatest)
209 // HINT: don't lock the flags here (it's done in the caller)
simpleTraceLine(VectorD2<int> start,VectorD2<int> dist,uchar checkflag)210 inline bool simpleTraceLine(VectorD2<int> start, VectorD2<int> dist, uchar checkflag) {
211 const uchar* pxflags = cClient->getMap()->GetPixelFlags();
212 if (!pxflags) { // The map has been probably shut down
213 warnings << "simpleTraceLine with pxflags==NULL" << endl;
214 return false;
215 }
216 uint map_w = cClient->getMap()->GetWidth();
217 uint map_h = cClient->getMap()->GetHeight();
218
219 if(abs(dist.x) >= abs(dist.y)) {
220 if(dist.x < 0) { // avoid anoying checks
221 start.x += dist.x;
222 dist.x = -dist.x;
223 }
224 if(start.x < 0 || (uint)(start.x + dist.x) >= map_w || start.y < 0 || (uint)start.y >= map_h)
225 return false;
226 for(int x = 0; x <= dist.x; x++) {
227 if(pxflags[start.y*map_w + start.x + x] & checkflag)
228 return false;
229 }
230 } else { // y is greater
231 if(dist.y < 0) { // avoid anoying checks
232 start.y += dist.y;
233 dist.y = -dist.y;
234 }
235 if(start.y < 0 || (uint)(start.y + dist.y) >= map_h || start.x < 0 || (uint)start.x >= map_w)
236 return false;
237 for(int y = 0; y <= dist.y; y++) {
238 if(pxflags[(start.y+y)*map_w + start.x] & checkflag)
239 return false;
240 }
241 }
242 return true;
243 }
244
245
246 /*
247 this class do the whole pathfinding (idea by AZ)
248 you can use the findPath-function directly,
249 or you can use the function StartThreadSearch, which
250 start the search in an own thread; you can ask
251 for the state with IsReady
252 */
253 class searchpath_base {
254 public:
255
256 // this will define, if we should break the search on the first result
257 // (we ensure in forEachChecklistItem, that it is not that bad)
258 #define BREAK_ON_FIRST 1
259
260 // this is one node in the map
261 class area_item {
262 public:
263
264 // needed for area_set (for area search function getArea(p))
265 struct area_v1__less {
266 // this is a well-defined transitive ordering after the v1-vector of the matrix
operator ()searchpath_base::area_item::area_v1__less267 bool operator()(const area_item* a, const area_item* b) const {
268 if(!a || !b) return a < b; // this should never happen
269 return a->area.v1 < b->area.v1;
270 }
271 };
272
273 // needed for area_set (for area search function getBestArea)
274 struct expected_min_total_dist__less {
275 // this is a well-defined transitive ordering after the v1-vector of the matrix
operator ()searchpath_base::area_item::expected_min_total_dist__less276 bool operator()(const area_item* a, const area_item* b) const {
277 if(!a || !b) return a < b; // this should never happen
278 double dista = a->expected_min_total_dist();
279 double distb = b->expected_min_total_dist();
280 // In some rare cases (depending on optimisation and hardware, (a<b)&&(b<a) is true here.
281 // To make it a valid order, we have to make the additional check.
282 if(fabs(dista - distb) < 0.0001) return false;
283 return dista < distb;
284 }
285 };
286
287 searchpath_base* const base;
288
289 // this will save the state, if we still have to check a specific end
290 // at the rectangle or not
291 short checklistRows;
292 short checklistRowStart;
293 short checklistRowHeight;
294 short checklistCols;
295 short checklistColStart;
296 short checklistColWidth;
297
298 const SquareMatrix<int> area;
299 area_item* lastArea; // the area where we came from
300 double dist_from_source; // distance from startpoint
301
initChecklists()302 void initChecklists() {
303 VectorD2<int> size = area.v2 - area.v1;
304 // ensure here, that the starts are not 0
305 checklistCols = (size.x-3) / checklistColWidth + 1;
306 checklistRows = (size.y-3) / checklistRowHeight + 1;
307 checklistColStart = (size.x - (checklistCols-1)*checklistColWidth) / 2;
308 checklistRowStart = (size.y - (checklistRows-1)*checklistRowHeight) / 2;
309 }
310
311 // expected minimum total distance from startpoint to destination
expected_min_total_dist() const312 double expected_min_total_dist() const {
313 return dist_from_source + (area.getCenter() - base->target).GetLength();
314 }
315
expected_min_total_dist(area_item * lastArea) const316 double expected_min_total_dist(area_item* lastArea) const {
317 return lastArea->dist_from_source + getDistToArea(lastArea) + (area.getCenter() - base->target).GetLength();
318 }
319
setLastArea(area_item * theValue)320 void setLastArea(area_item* theValue) {
321 lastArea = theValue;
322 if(lastArea == NULL)
323 dist_from_source = 0;
324 else
325 dist_from_source = lastArea->dist_from_source + getDistToArea(lastArea);
326 }
327
getConnectorPointForArea(area_item * otherArea) const328 VectorD2<int> getConnectorPointForArea(area_item* otherArea) const {
329 if(otherArea == NULL) return area.getCenter();
330
331 SquareMatrix<int> intersection = area.getInsersectionWithArea(otherArea->area);
332 return intersection.getCenter();
333 /*
334 VectorD2<int> otherCenter = otherArea->area.getCenter();
335 if(area.v1.x <= otherCenter.x && otherCenter.x <= area.v2.x)
336 return VectorD2<int>(otherCenter.x, area.getCenter().y);
337 else
338 return VectorD2<int>(area.getCenter().x, otherCenter.y); */
339 }
340
getDistToArea(area_item * otherArea) const341 double getDistToArea(area_item* otherArea) const {
342 VectorD2<int> conPoint = getConnectorPointForArea(otherArea);
343 return
344 (otherArea->area.getCenter() - conPoint).GetLength() +
345 (area.getCenter() - conPoint).GetLength();
346 }
347
area_item(searchpath_base * b,const SquareMatrix<int> & a)348 area_item(searchpath_base* b, const SquareMatrix<int>& a) :
349 base(b),
350 checklistRows(0),
351 checklistRowStart(0),
352 checklistRowHeight(5),
353 checklistCols(0),
354 checklistColStart(0),
355 checklistColWidth(5),
356 area(a),
357 lastArea(NULL),
358 dist_from_source(0) {}
359
360 // iterates over all checklist points
361 // calls given action with 2 parameters:
362 // VectorD2<int> p, VectorD2<int> dist
363 // p is the point inside of the area, dist the change to the target (p+dist is therefore the new position)
364 // if the returned value by the action is false, it will break
365 template<typename _action>
forEachChecklistItem(_action action)366 void forEachChecklistItem(_action action) {
367 int i;
368 VectorD2<int> p, dist;
369
370 // TODO: this sorting is not needed anymore here
371 typedef std::multiset< VectorD2<int>, VectorD2__absolute_less<int> > p_set;
372 // the set point here defines, where the 'best' point is (the sorting depends on it)
373 p_set points(VectorD2__absolute_less<int>( base->target )); // + getCenter()*2 - start) / 2));
374
375 // insert now the points to the list
376 // the list will sort itself
377
378 // left
379 p.x = area.v1.x;
380 dist.x = -checklistRowHeight; dist.y = 0;
381 for(i = 0; i < checklistRows; i++) {
382 p.y = area.v1.y + checklistRowStart + i*checklistRowHeight;
383 points.insert(p);
384 }
385
386 // right
387 p.x = area.v2.x;
388 dist.x = checklistRowHeight; dist.y = 0;
389 for(i = 0; i < checklistRows; i++) {
390 p.y = area.v1.y + checklistRowStart + i*checklistRowHeight;
391 points.insert(p);
392 }
393
394 // top
395 p.y = area.v1.y;
396 dist.x = 0; dist.y = -checklistColWidth;
397 for(i = 0; i < checklistCols; i++) {
398 p.x = area.v1.x + checklistColStart + i*checklistColWidth;
399 points.insert(p);
400 }
401
402 // bottom
403 p.y = area.v2.y;
404 dist.x = 0; dist.y = checklistColWidth;
405 for(i = 0; i < checklistCols; i++) {
406 p.x = area.v1.x + checklistColStart + i*checklistColWidth;
407 points.insert(p);
408 }
409
410 // the list is sorted, the closest (to the target) comes first
411 for(p_set::iterator it = points.begin(); it != points.end(); it++) {
412 if(it->x == area.v1.x) { // left
413 dist.x = -checklistRowHeight; dist.y = 0;
414 } else if(it->x == area.v2.x) { // right
415 dist.x = checklistRowHeight; dist.y = 0;
416 } else if(it->y == area.v1.y) { // top
417 dist.x = 0; dist.y = -checklistColWidth;
418 } else { // bottom
419 dist.x = 0; dist.y = checklistColWidth;
420 }
421 if(!action(*it, dist)) return;
422 }
423
424 }
425
426 class check_checkpoint {
427 public:
428 searchpath_base* base;
429 area_item* myArea;
430 float bestNodeLen; // this can be done better...
431
check_checkpoint(searchpath_base * b,area_item * a)432 check_checkpoint(searchpath_base* b, area_item* a) :
433 base(b),
434 myArea(a),
435 bestNodeLen(-1) {}
436
437 // this will be called by forEachChecklistItem
438 // pt is the checkpoint and dist the change to the new target (it means we want from pt to pt+dist)
439 // it will search for the best node starting at the specific pos
operator ()(VectorD2<int> pt,VectorD2<int> dist)440 bool operator() (VectorD2<int> pt, VectorD2<int> dist) {
441 bool trace = true;
442 VectorD2<int> dir, start;
443 unsigned short left, right;
444 if(abs(dist.x) >= abs(dist.y))
445 dir.y = 1;
446 else
447 dir.x = 1;
448 start = pt;
449 pt += dir;
450
451 // ensure, that the way has at least the width of wormsize
452 cClient->getMap()->lockFlags(false);
453 trace = simpleTraceLine(pt, dist, PX_ROCK);
454 for(left = 0; trace && left < wormsize; pt += dir) {
455 trace = simpleTraceLine(pt, dist, PX_ROCK);
456 if(trace) left++;
457 }
458 pt = start - dir; trace = true;
459 for(right = 0; trace && right < (wormsize-left); right++, pt -= dir) {
460 trace = simpleTraceLine(pt, dist, PX_ROCK);
461 if(trace) right++;
462 }
463 cClient->getMap()->unlockFlags(false);
464
465 // is there enough space?
466 if(left+right >= wormsize) {
467 base->addAreaNode( pt + dist, myArea );
468 }
469
470 // continue the search
471 return true;
472 }
473 }; // class check_checkpoint
474
process()475 void process() {
476 // add all successor-nodes to areas_stack
477 forEachChecklistItem( check_checkpoint(base, this) );
478 }
479
480 }; // class area_item
481
482 // area_set keeps track about all available areas. because there can be areas with the same area_v1, this must be a multiset.
483 typedef std::multiset< area_item*, area_item::area_v1__less > area_set;
484 // area_stack_set is the dynamic stack in the pathfinding algo
485 typedef std::set< area_item*, area_item::expected_min_total_dist__less > area_stack_set;
486 typedef std::set< NEW_ai_node_t* > node_set;
487
488 // these neccessary attributes have to be set manually
489 area_set areas; // set of all created areas
490 node_set nodes; // set of all created nodes
491 VectorD2<int> start, target;
492
493 area_stack_set areas_stack; // set of areas used by the searching algorithm
494
searchpath_base()495 searchpath_base() :
496 resulted_path(NULL),
497 thread(NULL),
498 thread_is_ready(true),
499 break_thread_signal(0),
500 restart_thread_searching_signal(0) {
501 thread = threadPool->start(threadSearch, this, "AI worm pathfinding");
502 if(!thread)
503 errors << "could not create AI thread" << endl;
504 }
505
~searchpath_base()506 ~searchpath_base() {
507 // thread cleaning up
508 breakThreadSignal();
509 if(thread) threadPool->wait(thread, NULL);
510 else warnings << "AI thread already uninitialized" << endl;
511 thread = NULL;
512
513 clear();
514 }
515
removePathFromList(NEW_ai_node_t * start)516 void removePathFromList(NEW_ai_node_t* start) {
517 for(NEW_ai_node_t* node = start; node; node = node->psNext)
518 nodes.erase(node);
519 }
520
521 private:
clear()522 void clear() {
523 clear_areas();
524 clear_nodes();
525 }
526
clear_areas()527 void clear_areas() {
528 for(area_set::iterator it = areas.begin(); it != areas.end(); it++) {
529 delete *it;
530 }
531 areas.clear();
532 }
533
clear_nodes()534 void clear_nodes() {
535 for(node_set::iterator it = nodes.begin(); it != nodes.end(); it++) {
536 delete *it;
537 }
538 nodes.clear();
539 }
540
541 // searches for an overleading area and returns the first
542 // returns NULL, if none found
getArea(VectorD2<int> p)543 area_item* getArea(VectorD2<int> p) {
544 // TODO: area_v1__less is not used here
545 // (take a look at pseudoless)
546 for(area_set::iterator it = areas.begin(); it != areas.end() && (*it)->area.v1 <= p; it++) {
547 if((*it)->area.isInDefinedArea(p))
548 return *it;
549 }
550
551 #ifdef _AI_DEBUG
552 /* printf("getArea( %i, %i )\n", p.x, p.y);
553 printf(" don't find an underlying area\n");
554 printf(" areas = {\n");
555 for(area_set::iterator it = areas.begin(); it != areas.end(); it++) {
556 printf(" ( %i, %i, %i, %i )%s,\n",
557 (*it)->area.v1.x, (*it)->area.v1.y,
558 (*it)->area.v2.x, (*it)->area.v2.y,
559 ((*it)->area.v1 <= p) ? "" : " (*)");
560 }
561 printf(" }\n"); */
562 #endif
563
564 return NULL;
565 }
566
getBestArea()567 area_item* getBestArea() {
568 // areas_stack is sorted and we have the best solution at the beginning
569 area_stack_set::iterator it = areas_stack.begin();
570 if(it == areas_stack.end()) return NULL;
571 return *it;
572 }
573
buildPath(area_item * lastArea)574 NEW_ai_node_t* buildPath(area_item* lastArea) {
575 NEW_ai_node_t* last_node = createNewAiNode(target);
576 nodes.insert(last_node);
577 area_item* a = lastArea;
578
579 while(a != NULL) {
580 NEW_ai_node_t* node = createNewAiNode(a->area.getCenter());
581 node->psNext = last_node;
582 last_node->psPrev = node;
583 nodes.insert(node);
584 last_node = node;
585
586 node = createNewAiNode(a->getConnectorPointForArea(a->lastArea));
587 node->psNext = last_node;
588 last_node->psPrev = node;
589 nodes.insert(node);
590 last_node = node;
591
592 a = a->lastArea;
593 }
594
595 return last_node;
596 }
597
598 // it searches for the path (dynamic algo, sort of A*)
findPath(VectorD2<int> start)599 NEW_ai_node_t* findPath(VectorD2<int> start) {
600 areas_stack.clear();
601 addAreaNode(start, NULL);
602
603 while(areas_stack.size() > 0) {
604 SDL_Delay(1); // lower priority to this thread
605 if(shouldBreakThread() || shouldRestartThread() || !cClient->getMap()->getCreated()) return NULL;
606
607 area_item* a = getBestArea();
608 areas_stack.erase(a);
609
610 // can we just finish with the search?
611 cClient->getMap()->lockFlags(false);
612 if(traceWormLine(target, a->area.getCenter())) {
613 cClient->getMap()->unlockFlags(false);
614 // yippieh!
615 return buildPath(a);
616 }
617 cClient->getMap()->unlockFlags(false);
618
619 a->process();
620 }
621
622 return NULL;
623 }
624
625 public:
626
627 // add new area as a node to path
628 // start is location (so an area surrounding start is searched)
addAreaNode(VectorD2<int> start,area_item * lastArea)629 void addAreaNode(VectorD2<int> start, area_item* lastArea) {
630
631 // is the start inside of the map?
632 if(start.x < 0 || (uint)start.x >= cClient->getMap()->GetWidth()
633 || start.y < 0 || (uint)start.y >= cClient->getMap()->GetHeight())
634 return;
635
636 // look around for an existing area here
637 area_item* a = getArea(start);
638 if(a) { // we found an area which includes this point
639 if(a->expected_min_total_dist() > a->expected_min_total_dist(lastArea)) {
640 areas_stack.erase(a);
641 a->setLastArea(lastArea);
642 areas_stack.insert(a);
643 }
644 return;
645 }
646
647 // get the max area (rectangle) around us
648 cClient->getMap()->lockFlags(false);
649 SquareMatrix<int> area = getMaxFreeArea(start, PX_ROCK);
650 cClient->getMap()->unlockFlags(false);
651 // add only if area is big enough
652 if(area.v2.x-area.v1.x >= wormsize && area.v2.y-area.v1.y >= wormsize) {
653 a = new area_item(this, area);
654 a->initChecklists();
655 a->setLastArea(lastArea);
656 areas.insert(a);
657 areas_stack.insert(a);
658 // and search
659 }
660 }
661
662 // this function will start the search, if it was not started right now
663 // WARNING: the searcher-thread will clear all current saved nodes
startThreadSearch()664 bool startThreadSearch() {
665 if(!cClient->getGameReady()) {
666 errors << "AI searchpath: cannot search yet, game not ready" << endl;
667 return false;
668 }
669
670 // if we are still searching, do nothing
671 if(!isReady()) return false;
672
673 // this is the signal to start the search
674 setReady(false);
675 return true;
676 }
677
678 private:
679 // main-function used by the thread
threadSearch(void * b)680 static int threadSearch(void* b) {
681
682 searchpath_base* base = (searchpath_base*)b;
683 NEW_ai_node_t* ret;
684
685 while(true) {
686 // sleep a little bit while we have nothing to do...
687 while(base->isReady()) {
688 // was there a break-signal?
689 if(base->shouldBreakThread()) {
690 //printf("got break signal(1) for %i\n", (long)base);
691 return 0;
692 }
693 SDL_Delay(100);
694 }
695
696 base->resulted_path = NULL;
697 base->clear(); // this is save and important here, else we would have invalid pointers
698
699 // start the main search
700 ret = base->findPath(base->start);
701
702 // finishing the result
703 base->completeNodesInfo(ret);
704 base->simplifyPath(ret);
705 base->splitUpNodes(ret, NULL);
706 base->resulted_path = ret;
707
708 if(base->shouldRestartThread()) {
709 // HINT: both locks (of shouldRestartThread and the following) are seperated
710 // this don't make any trouble, because here is the only place where we
711 // reset it and we have always restart_thread_searching_signal==true here
712 Mutex::ScopedLock lock(base->mutex);
713 base->restart_thread_searching_signal = 0;
714 base->start = base->restart_thread_searching_newdata.start;
715 base->target = base->restart_thread_searching_newdata.target;
716 continue;
717 }
718
719 // we are ready now
720 base->setReady(true);
721 }
722 }
723
724
725 // HINT: threadSearch is the only function, who should set this to true again!
726 // a set to false means for threadSearch, that it should start the search now
setReady(bool state)727 void setReady(bool state) {
728 Mutex::ScopedLock lock(mutex);
729 thread_is_ready = state;
730 }
731
732 public:
isReady()733 bool isReady() {
734 Mutex::ScopedLock lock(mutex);
735 return thread_is_ready;
736 }
737
738 // WARNING: not thread safe; call isReady before
resultedPath()739 NEW_ai_node_t* resultedPath() {
740 return resulted_path;
741 }
742
restartThreadSearch(VectorD2<int> newstart,VectorD2<int> newtarget)743 void restartThreadSearch(VectorD2<int> newstart, VectorD2<int> newtarget) {
744 // set signal
745 Mutex::ScopedLock lock(mutex);
746 thread_is_ready = false;
747 restart_thread_searching_newdata.start = newstart;
748 restart_thread_searching_newdata.target = newtarget;
749 // HINT: the reading of this isn't synchronized
750 restart_thread_searching_signal = 1;
751 }
752
753 private:
754 NEW_ai_node_t* resulted_path;
755 ThreadPoolItem* thread;
756 Mutex mutex;
757 bool thread_is_ready;
758 int break_thread_signal;
759 int restart_thread_searching_signal;
760 class start_target_pair { public:
761 VectorD2<int> start, target;
762 } restart_thread_searching_newdata;
763
breakThreadSignal()764 void breakThreadSignal() {
765 // we don't need more thread-safety here, because this will not fail
766 break_thread_signal = 1;
767 }
768
shouldBreakThread()769 bool shouldBreakThread() {
770 return (break_thread_signal != 0);
771 }
772
773 public:
shouldRestartThread()774 bool shouldRestartThread() {
775 return (restart_thread_searching_signal != 0);
776 }
777
778 private:
completeNodesInfo(NEW_ai_node_t * start)779 void completeNodesInfo(NEW_ai_node_t* start) {
780 NEW_ai_node_t* last = NULL;
781 for(NEW_ai_node_t* n = start; n; n = n->psNext) {
782 n->psPrev = last;
783 last = n;
784 }
785 }
786
splitUpNodes(NEW_ai_node_t * start,NEW_ai_node_t * end)787 void splitUpNodes(NEW_ai_node_t* start, NEW_ai_node_t* end) {
788 NEW_ai_node_t* tmpnode = NULL;
789 short s1, s2;
790 static const unsigned short dist = 50;
791 for(NEW_ai_node_t* n = start; n && n->psNext && n != end; n = n->psNext) {
792 s1 = (n->fX > n->psNext->fX) ? 1 : -1;
793 s2 = (n->fY > n->psNext->fY) ? 1 : -1;
794 if(s1*(n->fX - n->psNext->fX) > dist || s2*(n->fY - n->psNext->fY) > dist) {
795 tmpnode = new NEW_ai_node_t;
796 if(tmpnode) {
797 nodes.insert(tmpnode);
798 if(s1*(n->fX - n->psNext->fX) >= s2*(n->fY - n->psNext->fY)) {
799 tmpnode->fX = n->fX - s1*dist;
800 tmpnode->fY = n->fY
801 - s1*dist*(n->fY - n->psNext->fY)/(n->fX - n->psNext->fX);
802 } else {
803 tmpnode->fY = n->fY - s2*dist;
804 tmpnode->fX = n->fX
805 - s2*dist*(n->fX - n->psNext->fX)/(n->fY - n->psNext->fY);
806 }
807 tmpnode->psNext = n->psNext;
808 tmpnode->psPrev = n;
809 n->psNext->psPrev = tmpnode;
810 n->psNext = tmpnode;
811 }
812 }
813 }
814 }
815
816
simplifyPath(NEW_ai_node_t * start)817 void simplifyPath(NEW_ai_node_t* start) {
818 NEW_ai_node_t* node = NULL;
819 NEW_ai_node_t* last_node = NULL;
820 NEW_ai_node_t* closest_node = NULL;
821 unsigned short count = 0;
822 float dist, len;
823
824 // short up
825 for(node = start; node; node = node->psNext) {
826 len = 0;
827 last_node = node;
828 for(closest_node = node, count = 0; closest_node; closest_node = closest_node->psNext, count++) {
829 len += CVec(closest_node->fX - last_node->fX, closest_node->fY - last_node->fY).GetLength2();
830 dist = CVec(closest_node->fX - node->fX, closest_node->fY - node->fY).GetLength2();
831 if(count >= 3
832 && dist < len
833 && traceWormLine(CVec(closest_node->fX,closest_node->fY),CVec(node->fX,node->fY))) {
834 node->psNext = closest_node;
835 closest_node->psPrev = node;
836 len = dist;
837 }
838 last_node = closest_node;
839 }
840 }
841
842 // simplify
843 for(node = start; node; node = node->psNext) {
844 // While we see the two nodes, delete all nodes between them and skip to next node
845 count = 0;
846 while (closest_node && traceWormLine(CVec(closest_node->fX,closest_node->fY),CVec(node->fX,node->fY))) {
847 if(count >= 2) {
848 node->psNext = closest_node;
849 closest_node->psPrev = node;
850 }
851 closest_node = closest_node->psNext;
852 count++;
853 }
854 }
855 }
856
857 }; // class searchpath_base
858
859
860
861 ///////////////////
862 // Initialize the AI
AI_Initialize()863 bool CWormBotInputHandler::AI_Initialize() {
864 // Because this can be called multiple times, shutdown any previous allocated data
865 AI_Shutdown();
866
867 if(cClient->getMap() == NULL) {
868 errors << "CWormBotInputHandler::AI_Initialize(): map not set" << endl;
869 return false;
870 }
871
872 // Allocate the Open/Close grid
873 nGridCols = cClient->getMap()->getGridCols();
874 nGridRows = cClient->getMap()->getGridRows();
875
876 m_worm->fLastCarve = AbsTime();
877 cStuckPos = CVec(-999,-999);
878 fStuckTime = 0;
879 fLastPathUpdate = AbsTime();
880 fLastJump = AbsTime();
881 fLastCreated = AbsTime();
882 fLastThink = AbsTime();
883 bStuck = false;
884 bPathFinished = true;
885 iAiGameType = GAM_OTHER;
886 nAITargetType = AIT_NONE;
887 nAIState = AI_THINK;
888 fLastFace = AbsTime();
889 fLastShoot = AbsTime();
890 fLastCompleting = AbsTime();
891 fLastGoBack = AbsTime();
892
893
894 fCanShootTime = 0;
895
896 fRopeAttachedTime = 0;
897 fRopeHookFallingTime = 0;
898
899 if(pathSearcher)
900 warnings << "pathSearcher is already initialized" << endl;
901 else
902 pathSearcher = new searchpath_base;
903 if(!pathSearcher) {
904 errors << "cannot initialize pathSearcher" << endl;
905 return false;
906 }
907
908 return true;
909 }
910
911
912 ///////////////////
913 // Shutdown the AI stuff
AI_Shutdown()914 void CWormBotInputHandler::AI_Shutdown()
915 {
916 if(pathSearcher)
917 delete pathSearcher;
918 pathSearcher = NULL;
919
920 // in every case, the nodes of the current path are not handled by pathSearcher
921 delete_ai_nodes(NEW_psPath);
922
923 NEW_psPath = NULL;
924 NEW_psCurrentNode = NULL;
925 NEW_psLastNode = NULL;
926 }
927
928
929
930
931
932 /*
933 Algorithm:
934 ----------
935
936 1) Find nearest worm and set it as a target
937 2) If we are within a good distance, aim and shoot the target
938 3) If we are too far, try and get closer by digging and ninja rope
939
940 */
941
942 #ifdef _AI_DEBUG
943 class debug_print_col {
944 public:
945 SmartPointer<SDL_Surface> bmpDest;
debug_print_col(const SmartPointer<SDL_Surface> & dest=NULL)946 debug_print_col(const SmartPointer<SDL_Surface> & dest=NULL) : bmpDest(dest) {}
947
operator ()(int x,int y) const948 bool operator()(int x, int y) const {
949 if(!bmpDest.get())
950 return false;
951 if(x*2-4 >= 0 && x*2+4 < bmpDest.get()->w
952 && y*2-4 >= 0 && y*2+4 < bmpDest.get()->h)
953 DrawRectFill(bmpDest.get(),x*2-4,y*2-4,x*2+4,y*2+4,Color(255,255,0));
954 else
955 return false;
956 return true;
957 }
958 };
959
do_some_tests_with_fastTraceLine()960 void do_some_tests_with_fastTraceLine() {
961 CVec start, target;
962 start.x = (float)(rand() % cClient->getMap()->GetWidth());
963 start.y = (float)(rand() % cClient->getMap()->GetHeight());
964 target.x = (float)(rand() % cClient->getMap()->GetWidth());
965 target.y = (float)(rand() % cClient->getMap()->GetHeight());
966
967 debug_print_col printer(cClient->getMap()->GetDebugImage());
968 fastTraceLine(target, start, PX_ROCK, printer);
969 }
970 #endif
971
972
AI_Respawn()973 void CWormBotInputHandler::AI_Respawn() {
974 if(findNewTarget()) {
975 AI_CreatePath(true);
976 }
977 }
978
979
980 // called when the game starts (after weapon selection)
startGame()981 void CWormBotInputHandler::startGame() {
982 if( cClient->getGameLobby()->gameMode == GameMode(GM_DEATHMATCH) ||
983 cClient->getGameLobby()->gameMode == GameMode(GM_TEAMDEATH) ||
984 cClient->getGameLobby()->gameMode == GameMode(GM_TAG) ||
985 cClient->getGameLobby()->gameMode == GameMode(GM_HIDEANDSEEK) ||
986 cClient->getGameLobby()->gameMode == GameMode(GM_CTF) ||
987 cClient->getGameLobby()->gameMode == GameMode(GM_RACE) ||
988 cClient->getGameLobby()->gameMode == GameMode(GM_TEAMRACE))
989 {
990 // it's fine, we support that game mode
991 }
992 else if(cClient->getGameLobby()->gameMode == NULL) {
993 warnings << "bot: gamemode " << cClient->getGameLobby()->sGameMode << " is unknown" << endl;
994 } else {
995 warnings << "bot: support for gamemode " << cClient->getGameLobby()->gameMode->Name()
996 << " is currently not implemented" << endl;
997 }
998 }
999
1000 ///////////////////
1001 // Simulate the AI
getInput()1002 void CWormBotInputHandler::getInput() {
1003
1004 worm_state_t *ws = &m_worm->tState;
1005
1006
1007 // Init the ws
1008 ws->bCarve = false;
1009 ws->bMove = false;
1010 ws->bShoot = false;
1011 ws->bJump = false;
1012
1013 // Behave like humans and don't play immediatelly after spawn
1014 if ((tLX->currentTime - m_worm->fSpawnTime) < 0.4f)
1015 return;
1016
1017 // Update bOnGround, so we don't have to use CheckOnGround every time we need it
1018 m_worm->bOnGround = m_worm->CheckOnGround();
1019
1020
1021 tLX->debug_string = "";
1022
1023 iRandomSpread = 0;
1024 fLastRandomChange = AbsTime();
1025
1026 // Every 3 seconds we run the think function
1027 float thinkInterval = 3.0f;
1028 if(cClient->getGameLobby()->gameMode == GameMode(GM_CTF))
1029 thinkInterval = 0.5f; // recheck more often
1030 if(tLX->currentTime - fLastThink > thinkInterval && nAIState != AI_THINK)
1031 nAIState = AI_THINK;
1032
1033 // check more often if the path isn't finished yet
1034 if(tLX->currentTime - fLastThink > 0.5 && !bPathFinished)
1035 nAIState = AI_THINK;
1036
1037 // Make sure the worm is good
1038 if(nAITargetType == AIT_WORM)
1039 if(!psAITarget || !psAITarget->isUsed() || !psAITarget->getAlive() || psAITarget->getAFK() != AFK_BACK_ONLINE) {
1040 nAIState = AI_THINK;
1041 }
1042
1043 // Carve always; makes no problems and can only help
1044 AI_Carve();
1045
1046 // If we have a good shooting 'solution', shoot
1047 if(AI_Shoot()) {
1048
1049 // jump, move and carve around
1050 /*AI_Jump();
1051 ws->bMove = true;
1052
1053 if(bOnGround && fRopeAttachedTime >= 0.3f && !AI_IsInAir(vPos))
1054 cNinjaRope.Release();*/
1055
1056 // Don't move in the direction of projectiles when shooting
1057 if (m_worm->cNinjaRope.isAttached()) {
1058 CVec force = m_worm->cNinjaRope.GetForce(m_worm->vPos);
1059 float rope_angle = (float)atan(force.x / force.y);
1060 if (force.x < 0 || force.y > 0)
1061 rope_angle = -rope_angle;
1062 rope_angle = RAD2DEG(rope_angle);
1063
1064 if (fabs(m_worm->fAngle - rope_angle) <= 50)
1065 m_worm->cNinjaRope.Release();
1066 }
1067 m_worm->tState.bMove = false;
1068
1069
1070 } else {
1071
1072 // Reload weapons when we can't shoot
1073 AI_ReloadWeapons();
1074
1075 // Process depending on our current state
1076 switch(nAIState) {
1077
1078 // Think; We spawn in this state
1079 case AI_THINK:
1080 AI_Think();
1081 break;
1082
1083 // Moving towards a target
1084 case AI_MOVINGTOTARGET:
1085 AI_MoveToTarget();
1086 break;
1087
1088 default:
1089 nAIState = AI_THINK;
1090 AI_Think();
1091 break;
1092 }
1093
1094 }
1095
1096 // we have no strafing for bots at the moment
1097 m_worm->iMoveDirectionSide = m_worm->iFaceDirectionSide;
1098 }
1099
moveToOwnBase(int t,CVec & pos)1100 static bool moveToOwnBase(int t, CVec& pos) {
1101 Flag* ownFlag = cClient->flagInfo()->getFlag(t);
1102 if(!ownFlag) return false; // strange
1103 pos = ownFlag->spawnPoint.pos;
1104 return true;
1105 }
1106
findEnemyBase(CWorm * w,CVec & pos)1107 static bool findEnemyBase(CWorm* w, CVec& pos) {
1108 float lastDist = 999999999.0f;
1109 bool success = false;
1110 for(int t = 0; t < 4; ++t) {
1111 if(t == w->getTeam()) continue;
1112 Flag* flag = cClient->flagInfo()->getFlag(t);
1113 if(!flag) continue;
1114
1115 float dist = (w->getPos() - flag->spawnPoint.pos).GetLength();
1116 if(dist < lastDist) {
1117 lastDist = dist;
1118 pos = flag->spawnPoint.pos;
1119 success = true;
1120 }
1121 }
1122
1123 return success;
1124 }
1125
findEnemyFlag(CWorm * w,CVec & pos)1126 static bool findEnemyFlag(CWorm* w, CVec& pos) {
1127 float lastDist = 999999999.0f;
1128 bool success = false;
1129 for(int t = 0; t < 4; ++t) {
1130 if(t == w->getTeam()) continue;
1131 Flag* flag = cClient->flagInfo()->getFlag(t);
1132 if(!flag) continue;
1133 if(flag->holderWorm >= 0) continue;
1134
1135 float dist = (w->getPos() - flag->getPos()).GetLength();
1136 if(dist < lastDist) {
1137 lastDist = dist;
1138 pos = flag->getPos();
1139 success = true;
1140 }
1141 }
1142 if(success) return true;
1143
1144 return findEnemyBase(w, pos);
1145 }
1146
teamHasEnemyFlag(int t)1147 static Flag* teamHasEnemyFlag(int t) {
1148 for(int i = 0; i < MAX_WORMS; ++i) {
1149 CWorm* w = &cClient->getRemoteWorms()[i];
1150 if(!w->isUsed() || w->getTeam() != t) continue;
1151 Flag* flag = cClient->flagInfo()->getFlagOfWorm(w->getID());
1152 if(flag) return flag;
1153 }
1154 return NULL;
1155 }
1156
findNewTarget()1157 bool CWormBotInputHandler::findNewTarget() {
1158 if( (cClient->getGameLobby()->gameMode == GameMode(GM_TAG) && m_worm->getTagIT()) ) {
1159 CWorm* w = nearestEnemyWorm();
1160 if(!w) return findRandomSpot();
1161
1162 float l = (w->getPos() - m_worm->vPos).GetLength2();
1163 if(l < 30*30) // search new spot iff <30 pixels away from me
1164 return findRandomSpot();
1165 else
1166 return true;
1167 }
1168 else if(cClient->getGameLobby()->gameMode == GameMode(GM_HIDEANDSEEK))
1169 {
1170 if(m_worm->getTeam() == (int)HIDEANDSEEK_HIDER) {
1171 CWorm* w = nearestEnemyWorm();
1172 if(!w) return findRandomSpot();
1173
1174 float l = (w->getPos() - m_worm->vPos).GetLength2();
1175 if(l < 30*30) // search new spot iff <30 pixels away from me
1176 return findRandomSpot();
1177 else
1178 return true;
1179 }
1180 else { // we are seeker
1181 CWorm* w = nearestEnemyWorm();
1182 if(!w)
1183 // just move randomly
1184 return findRandomSpot();
1185
1186 psAITarget = w;
1187 nAITargetType = AIT_WORM;
1188 nAIState = AI_MOVINGTOTARGET;
1189 return true;
1190 }
1191 }
1192 else if(cClient->getGameLobby()->gameMode == GameMode(GM_CTF)) {
1193 Flag* wormFlag = cClient->flagInfo()->getFlagOfWorm(m_worm->getID());
1194 bool success = false;
1195 if(wormFlag != NULL) { // we have an enemy flag
1196 if(cClient->getTeamWormCount(m_worm->getTeam()) == 1) { // we are alone in the team
1197 Flag* ownFlag = cClient->flagInfo()->getFlag(m_worm->getTeam());
1198 if(ownFlag) {
1199 if(ownFlag->holderWorm >= 0) {
1200 psAITarget = &cClient->getRemoteWorms()[ownFlag->holderWorm];
1201 nAITargetType = AIT_WORM;
1202 nAIState = AI_MOVINGTOTARGET;
1203 }
1204 else {
1205 cPosTarget = ownFlag->getPos();
1206 success = true;
1207 }
1208 }
1209 }
1210 else { // not alone in our team
1211 success = moveToOwnBase(m_worm->getTeam(), cPosTarget);
1212 }
1213 }
1214 else { // we don't hold any flag
1215 if(teamHasEnemyFlag(m_worm->getTeam())) {
1216 Flag* ownFlag = cClient->flagInfo()->getFlag(m_worm->getTeam());
1217 if(ownFlag) {
1218 if(ownFlag->holderWorm >= 0) {
1219 psAITarget = &cClient->getRemoteWorms()[ownFlag->holderWorm];
1220 nAITargetType = AIT_WORM;
1221 nAIState = AI_MOVINGTOTARGET;
1222 }
1223 else {
1224 cPosTarget = ownFlag->getPos();
1225 success = true;
1226 }
1227 }
1228 } else
1229 success = findEnemyFlag(m_worm, cPosTarget);
1230 }
1231 if(success) {
1232 nAITargetType = AIT_POSITION;
1233 nAIState = AI_MOVINGTOTARGET;
1234 return true;
1235 }
1236 }
1237 else if(cClient->getGameLobby()->gameMode == GameMode(GM_RACE) || cClient->getGameLobby()->gameMode == GameMode(GM_TEAMRACE)) {
1238 int t = m_worm->getID();
1239 if(cClient->isTeamGame()) t = m_worm->getTeam();
1240
1241 Flag* flag = cClient->flagInfo()->getFlag(t);
1242 if(!flag) return false; // strange
1243
1244 nAITargetType = AIT_POSITION;
1245 nAIState = AI_MOVINGTOTARGET;
1246 cPosTarget = flag->spawnPoint.pos;
1247 return true;
1248 }
1249 else {
1250 // TODO: also check for GM_DEMOLITIONS (whereby killing other worms is not completly bad in this mode)
1251
1252 // find new target and reset the path
1253
1254 // Search for an unfriendly worm
1255 psAITarget = findTarget();
1256
1257 // Any unfriendlies?
1258 if(psAITarget) {
1259 // We have an unfriendly target, so change to a 'move-to-target' state
1260 nAITargetType = AIT_WORM;
1261 nAIState = AI_MOVINGTOTARGET;
1262 return true;
1263 }
1264
1265 return false;
1266 }
1267
1268 return false;
1269 }
1270
1271 ///////////////////
1272 // Find a target worm
findTarget()1273 CWorm *CWormBotInputHandler::findTarget()
1274 {
1275 CWorm *w = cClient->getRemoteWorms();
1276 CWorm *trg = NULL;
1277 CWorm *nonsight_trg = NULL;
1278 float fDistance = -1;
1279 float fSightDistance = -1;
1280
1281 if(w == NULL) return NULL;
1282
1283 //
1284 // Just find the closest worm
1285 //
1286
1287 for(int i=0; i<MAX_WORMS; i++, w++) {
1288 if(w == NULL) break;
1289
1290 // Don't bother about unused or dead worms
1291 if(!w->isUsed() || !w->getAlive())
1292 continue;
1293
1294 if(!w->isVisible(m_worm)) continue;
1295
1296 // Make sure i don't target myself
1297 if(w->getID() == m_worm->iID)
1298 continue;
1299
1300 // don't target AFK worms
1301 if(w->getAFK() != AFK_BACK_ONLINE)
1302 continue;
1303
1304 // If this is a team game, don't target my team mates
1305 if(cClient->isTeamGame() && w->getTeam() == m_worm->iTeam)
1306 continue;
1307
1308 // If this is a game of tag, only target the worm it (unless it's me)
1309 if(cClient->isTagGame() && !w->getTagIT() && !m_worm->bTagIT)
1310 continue;
1311
1312 /*
1313 // If this is a VIP game target:
1314 // Red worms if Blue
1315 // Green & Blue worms if Red
1316 // Blue worms if green
1317 if(iAiVIP && m_worm->iTeam == 0 && w->getTeam() != 1)
1318 continue;
1319 if(iAiVIP && m_worm->iTeam == 1 && w->getTeam() == 1)
1320 continue;
1321 if(iAiVIP && m_worm->iTeam == 2 && w->getTeam() != 0)
1322 continue;
1323
1324 // If this is a capture the flag game just aim to get the flag
1325 if(iAiCTF && !w->getFlag())
1326 continue;
1327
1328 // If this is a teams capture the flag game just aim to get the flag
1329 if(iAiTeamCTF && !w->getFlag())
1330 continue;
1331 */
1332 // Calculate distance between us two
1333 float l = (w->getPos() - m_worm->vPos).GetLength2();
1334
1335 // Prefer targets we have free line of sight to
1336 float length;
1337 int type;
1338 m_worm->traceLine(w->getPos(),&length,&type,1);
1339 if (! (type & PX_ROCK)) {
1340 // Line of sight not blocked
1341 if (fSightDistance < 0 || l < fSightDistance) {
1342 trg = w;
1343 fSightDistance = l;
1344 if (fDistance < 0 || l < fDistance) {
1345 nonsight_trg = w;
1346 fDistance = l;
1347 }
1348 }
1349 }
1350 else {
1351 // Line of sight blocked
1352 if(fDistance < 0 || l < fDistance) {
1353 nonsight_trg = w;
1354 fDistance = l;
1355 }
1356 }
1357 }
1358
1359 // If the target we have line of sight to is too far, switch back to the closest target
1360 if (fSightDistance-fDistance > 50.0f || /*TODO: in rifles we can shoot target far away!*/iAiGameType == GAM_RIFLES || trg == NULL) {
1361 if (nonsight_trg)
1362 trg = nonsight_trg;
1363 }
1364
1365 return trg;
1366 }
1367
1368
nearestEnemyWorm()1369 CWorm* CWormBotInputHandler::nearestEnemyWorm() {
1370 CWorm *w = cClient->getRemoteWorms();
1371 CWorm *trg = NULL;
1372 float fDistance = -1;
1373 float fSightDistance = -1;
1374
1375 if(w == NULL) return NULL;
1376
1377 //
1378 // Just find the closest worm
1379 //
1380
1381 for(int i=0; i<MAX_WORMS; i++, w++) {
1382
1383 // Don't bother about unused or dead worms
1384 if(!w->isUsed() || !w->getAlive())
1385 continue;
1386
1387 if(!w->isVisible(m_worm)) continue;
1388
1389 // Make sure i don't target myself
1390 if(w->getID() == m_worm->iID)
1391 continue;
1392
1393 // don't target AFK worms
1394 if(w->getAFK() != AFK_BACK_ONLINE)
1395 continue;
1396
1397 // If this is a team game, don't target my team mates
1398 if(cClient->isTeamGame() && w->getTeam() == m_worm->iTeam)
1399 continue;
1400
1401 // If this is a game of tag, only target the worm it (unless it's me)
1402 if(cClient->isTagGame() && !w->getTagIT() && !m_worm->bTagIT)
1403 continue;
1404
1405 // Calculate distance between us two
1406 float l = (w->getPos() - m_worm->vPos).GetLength2();
1407
1408 // Prefer targets we have free line of sight to
1409 float length;
1410 int type;
1411 m_worm->traceLine(w->getPos(),&length,&type,1);
1412 if (! (type & PX_ROCK)) {
1413 // Line of sight not blocked
1414 if (fSightDistance < 0 || l < fSightDistance) {
1415 trg = w;
1416 fSightDistance = l;
1417 if (fDistance < 0 || l < fDistance) {
1418 fDistance = l;
1419 }
1420 }
1421 }
1422 else {
1423 // Line of sight blocked
1424 if(fDistance < 0 || l < fDistance) {
1425 fDistance = l;
1426 }
1427 }
1428 }
1429
1430 return trg;
1431
1432 }
1433
1434
1435
1436 ///////////////////
1437 // Think State
AI_Think()1438 void CWormBotInputHandler::AI_Think()
1439 {
1440 /*
1441 We start of in an think state. When we're here we decide what we should do.
1442 If there is an unfriendly worm, or a game target we deal with it.
1443 In the event that we have no unfriendly worms or game targets we should remain in the idle state.
1444 While idling we can walk around to appear non-static, reload weapons or grab a bonus.
1445 */
1446
1447 // Clear the state
1448 psAITarget = NULL;
1449 psBonusTarget = NULL;
1450 nAITargetType = AIT_NONE;
1451 fLastThink = tLX->currentTime;
1452
1453
1454 // Reload our weapons in idle mode
1455 AI_ReloadWeapons();
1456
1457
1458 // If our health is less than 15% (critical), our main priority is health
1459 if(m_worm->health < 15)
1460 if(AI_FindHealth())
1461 return;
1462
1463 {
1464 // search for weapon if we need some
1465 int wpnNum = 0;
1466 for(int i = 0; i < 5; ++i)
1467 if(m_worm->tWeapons[i].Enabled && m_worm->tWeapons[i].Weapon)
1468 wpnNum++;
1469 if(wpnNum < 5) {
1470 if(AI_FindBonus(BNS_WEAPON)) {
1471 // select disabled weapon (which should be replaced by bonus)
1472 for(int i = 0; i < 5; ++i)
1473 if(!m_worm->tWeapons[i].Enabled || !m_worm->tWeapons[i].Weapon) {
1474 m_worm->iCurrentWeapon = i;
1475 break;
1476 }
1477 return;
1478 }
1479 }
1480 }
1481
1482 // Search for an unfriendly worm
1483 if(findNewTarget()) {
1484 AI_CreatePath();
1485 return;
1486 }
1487 else
1488 fLastShoot = AbsTime(); // force new shoot
1489
1490 // If we're down on health (less than 80%) we should look for a health bonus
1491 if(m_worm->health < 80) {
1492 //printf("we should look for health\n");
1493 if(AI_FindHealth())
1494 return;
1495 }
1496
1497
1498 /*
1499 If we get here that means we have nothing to do
1500 */
1501
1502
1503 //
1504 // Typically, high ground is safer. So if we don't have anything to do, lets up up
1505 //
1506
1507 // Our target already on high ground?
1508 if(cPosTarget.y < cClient->getMap()->getGridHeight()*5 && nAIState == AI_MOVINGTOTARGET) {
1509
1510 // Nothing todo, so go find some health if we even slightly need it
1511 if(m_worm->health < 100) {
1512 if(AI_FindHealth())
1513 return;
1514 }
1515 return;
1516 }
1517
1518 if(findRandomSpot(true))
1519 AI_CreatePath();
1520 }
1521
findRandomSpot(bool highSpot)1522 bool CWormBotInputHandler::findRandomSpot(bool highSpot) {
1523 float cols = (float)(cClient->getMap()->getGridCols()-1); // Note: -1 because the grid is slightly larger than the
1524 float rows = (float)(cClient->getMap()->getGridRows()-1); // level size
1525
1526 if(highSpot)
1527 rows /= 5.0f; // little hack to go higher
1528
1529 // Find a random spot to go to high in the level
1530 //printf("I don't find any target, so let's get somewhere (high)\n");
1531 int x, y, c;
1532 for(c=0; c<10; c++) {
1533 x = (int)(fabs(GetRandomNum()) * cols);
1534 y = (int)(fabs(GetRandomNum()) * rows);
1535
1536 uchar pf = *(cClient->getMap()->getGridFlags() + y*cClient->getMap()->getGridCols() + x);
1537
1538 if(pf & PX_ROCK)
1539 continue;
1540
1541 // Set the target
1542 cPosTarget = CVec((float)(x*cClient->getMap()->getGridWidth()+(cClient->getMap()->getGridWidth()/2)), (float)(y*cClient->getMap()->getGridHeight()+(cClient->getMap()->getGridHeight()/2)));
1543 nAITargetType = AIT_POSITION;
1544 nAIState = AI_MOVINGTOTARGET;
1545 return true;
1546 }
1547
1548 return false;
1549 }
1550
1551
AI_FindHealth()1552 bool CWormBotInputHandler::AI_FindHealth() {
1553 return AI_FindBonus(BNS_HEALTH);
1554 }
1555
1556 ///////////////////
1557 // Find a health pack
1558 // Returns true if we found one
AI_FindBonus(int bonustype)1559 bool CWormBotInputHandler::AI_FindBonus(int bonustype)
1560 {
1561 if (!cClient->getGameLobby()->bBonusesOn)
1562 return false;
1563
1564 CBonus *pcBonusList = cClient->getBonusList();
1565 short i;
1566 CBonus *pcBonus = NULL;
1567 float dist2 = -1;
1568 float d2;
1569
1570 // Find the closest health bonus
1571 for(i=0; i<MAX_BONUSES; i++) {
1572 if(pcBonusList[i].getUsed() && pcBonusList[i].getType() == bonustype) {
1573
1574 d2 = (pcBonusList[i].getPosition() - m_worm->vPos).GetLength2();
1575
1576 if(dist2 < 0 || d2 < dist2) {
1577 pcBonus = &pcBonusList[i];
1578 dist2 = d2;
1579 }
1580 }
1581 }
1582
1583
1584 // TODO: Verify that the target is not in some small cavern that is hard to get to
1585
1586
1587 // If we have found a bonus, setup the state to move towards it
1588 if(dist2 >= 0) {
1589 psBonusTarget = pcBonus;
1590 nAITargetType = AIT_BONUS;
1591 nAIState = AI_MOVINGTOTARGET;
1592 AI_CreatePath();
1593 return true;
1594 }
1595
1596 return false;
1597 }
1598
1599
1600 ///////////////////
1601 // Reloads the weapons
AI_ReloadWeapons()1602 void CWormBotInputHandler::AI_ReloadWeapons()
1603 {
1604 ushort i;
1605
1606 // Go through reloading the weapons
1607 for(i=0; i<5; i++) {
1608 if(m_worm->tWeapons[i].Reloading) {
1609 m_worm->iCurrentWeapon = i;
1610 break;
1611 }
1612 }
1613 }
1614
1615
1616 ///////////////////
1617 // Get the target's position
1618 // Also checks the target and resets to a think state if needed
AI_GetTargetPos()1619 CVec CWormBotInputHandler::AI_GetTargetPos()
1620 {
1621 // Put the target into a position
1622 switch(nAITargetType) {
1623
1624 // Bonus target
1625 case AIT_BONUS:
1626 if(psBonusTarget) {
1627 if(!psBonusTarget->getUsed())
1628 nAIState = AI_THINK;
1629 return psBonusTarget->getPosition();
1630 }
1631 break;
1632
1633 // Position target
1634 case AIT_POSITION:
1635 return cPosTarget;
1636
1637 // Worm target
1638 case AIT_WORM:
1639 default:
1640 if(nAITargetType != AIT_WORM)
1641 nAIState = AI_THINK;
1642 if(psAITarget) {
1643 if(!psAITarget->getAlive() || !psAITarget->isUsed())
1644 nAIState = AI_THINK;
1645 return psAITarget->getPos();
1646 }
1647 break;
1648
1649 }
1650
1651 // No target
1652 nAIState = AI_THINK;
1653 return CVec(0,0);
1654 }
1655
1656
1657 ///////////////////
1658 // Aim at a spot
1659 // Returns true if we're aiming at it
AI_SetAim(CVec cPos)1660 bool CWormBotInputHandler::AI_SetAim(CVec cPos)
1661 {
1662 TimeDiff dt = tLX->fDeltaTime;
1663 CVec tgPos = cPos;
1664 CVec tgDir = tgPos - m_worm->vPos;
1665 bool goodAim = false;
1666 const gs_worm_t *wd = m_worm->cGameScript->getWorm();
1667
1668 NormalizeVector(&tgDir);
1669
1670 DIR_TYPE wantedDir = (tgDir.x < 0) ? DIR_LEFT : DIR_RIGHT;
1671 if (tLX->currentTime - fLastFace > 0.3f) { // prevent turning
1672 // Make me face the target
1673 m_worm->iFaceDirectionSide = wantedDir;
1674
1675 fLastFace = tLX->currentTime;
1676 }
1677
1678 // Aim at the target
1679 float ang = (float)atan2(tgDir.x, tgDir.y);
1680 ang = RAD2DEG(ang);
1681
1682 if(wantedDir == DIR_LEFT)
1683 ang+=90;
1684 else
1685 ang = -ang + 90;
1686
1687 // Clamp the angle
1688 ang = MAX((float)-90, ang);
1689
1690 if(iAiDiffLevel < AI_XTREME) {
1691 static float angleDiff[AI_HARD + 1] = {0,0,0};
1692 static AbsTime lastAngleDiffUpdateTime;
1693 if(tLX->currentTime - lastAngleDiffUpdateTime > TimeDiff(0.5f)) {
1694 for(int aiLevel = AI_EASY; aiLevel < AI_XTREME; aiLevel++) {
1695 float maxdiff = float(AI_XTREME - aiLevel) / float(AI_XTREME);
1696 angleDiff[aiLevel] = GetRandomNum() * maxdiff * 50.0f;
1697 }
1698 lastAngleDiffUpdateTime = tLX->currentTime;
1699 }
1700 ang += angleDiff[iAiDiffLevel];
1701 ang = MAX(ang, -90.0f);
1702 }
1703
1704 // Move the angle at the same speed humans are allowed to move the angle
1705 if(ang > m_worm->fAngle)
1706 m_worm->fAngle += wd->AngleSpeed * dt.seconds();
1707 else if(ang < m_worm->fAngle)
1708 m_worm->fAngle -= wd->AngleSpeed * dt.seconds();
1709
1710 // If the angle is within +/- 3 degrees, just snap it
1711 if( fabs(m_worm->fAngle - ang) < 3 )
1712 m_worm->fAngle = ang;
1713
1714 if(fabs(m_worm->fAngle - ang) < 3 + 20 * float(AI_XTREME - iAiDiffLevel) / float(AI_XTREME))
1715 goodAim = true;
1716
1717 // Clamp the angle
1718 m_worm->fAngle = CLAMP(m_worm->fAngle, -90.0f, 60.0f);
1719
1720 return goodAim;
1721 }
1722
1723
1724 ///////////////////
1725 // A simpler method to get to a target
1726 // Used if we have no path
AI_SimpleMove(bool bHaveTarget)1727 void CWormBotInputHandler::AI_SimpleMove(bool bHaveTarget)
1728 {
1729 worm_state_t *ws = &m_worm->tState;
1730
1731 // Simple
1732 ws->bMove = true;
1733 ws->bShoot = false;
1734 ws->bJump = false;
1735
1736 //strcpy(tLX->debug_string, "AI_SimpleMove invoked");
1737
1738 cPosTarget = AI_GetTargetPos();
1739 if (nAITargetType == AIT_WORM && psAITarget)
1740 cPosTarget = AI_FindShootingSpot();
1741
1742 // Aim at the node
1743 bool aim = AI_SetAim(cPosTarget);
1744
1745 // If our line is blocked, try some evasive measures
1746 float fDist = 0;
1747 int type = 0;
1748 m_worm->traceLine(cPosTarget, &fDist, &type, 1);
1749 if(fDist < 0.75f || cPosTarget.y < m_worm->vPos.y) {
1750
1751 // Change direction
1752 if (bHaveTarget && (tLX->currentTime-fLastFace) > 1.0) {
1753 m_worm->iFaceDirectionSide = OppositeDir(m_worm->iFaceDirectionSide);
1754 fLastFace = tLX->currentTime;
1755 }
1756
1757 // Look up for a ninja throw
1758 aim = AI_SetAim(m_worm->vPos + CVec(GetRandomNum()*10, GetRandomNum()*10 + 10));
1759 if(aim) {
1760 const CVec dir = m_worm->getFaceDirection();
1761 m_worm->cNinjaRope.Shoot(m_worm, m_worm->vPos,dir);
1762 }
1763
1764 // Jump and move
1765 else {
1766 AI_Jump();
1767 ws->bMove = true;
1768 m_worm->cNinjaRope.Release();
1769 }
1770
1771 return;
1772 }
1773
1774 // Release the ninja rope
1775 m_worm->cNinjaRope.Release();
1776 }
1777
1778 float fLastDirChange = 99999;
1779
1780 ///////////////////
1781 // Finds a suitable 'clearing' weapon
1782 // A weapon used for making a path
1783 // Returns -1 on failure
AI_FindClearingWeapon()1784 int CWormBotInputHandler::AI_FindClearingWeapon()
1785 {
1786 if(iAiGameType == GAM_MORTARS)
1787 return -1;
1788 Proj_ActionType type = PJ_EXPLODE;
1789
1790 // search a good projectile weapon
1791 int i = 0;
1792 for (i=0; i<5; i++) {
1793 if(m_worm->tWeapons[i].Weapon && m_worm->tWeapons[i].Weapon->Type == WPN_PROJECTILE) {
1794 // TODO: not really all cases...
1795 type = m_worm->tWeapons[i].Weapon->Proj.Proj->Hit.Type;
1796
1797 // Nothing that could fall back onto us
1798 if (m_worm->tWeapons[i].Weapon->Proj.Speed < 100.0f) {
1799 if (!m_worm->tWeapons[i].Weapon->Proj.Proj->UseCustomGravity || m_worm->tWeapons[i].Weapon->Proj.Proj->Gravity > 30)
1800 continue;
1801 }
1802
1803 // Suspicious
1804 std::string name;
1805 name = m_worm->tWeapons[i].Weapon->Name;
1806 stringlwr(name);
1807 if(strincludes(name,"dirt") || strincludes(name,"napalm") || strincludes(name,"grenade") || strincludes(name,"nuke") || strincludes(name,"mine"))
1808 continue;
1809
1810 // Nothing explosive or dirty
1811 if (type != PJ_DIRT && type != PJ_GREENDIRT && type != PJ_BOUNCE)
1812 if(!m_worm->tWeapons[i].Reloading)
1813 return i;
1814 }
1815 }
1816
1817 // accept also beam-weapons as a second choice
1818 for (i=0; i<5; i++)
1819 if(m_worm->tWeapons[i].Weapon && m_worm->tWeapons[i].Weapon->Type == WPN_BEAM)
1820 return i;
1821
1822 // No suitable weapons
1823 return -1;
1824 }
1825
1826
1827
1828 ////////////////////
1829 // Returns true, if the weapon can hit the target
1830 // WARNING: works only when fAngle == AI_GetAimingAngle, which means the target has to be aimed
weaponCanHit(int gravity,float speed,CVec cTrgPos)1831 bool CWormBotInputHandler::weaponCanHit(int gravity, float speed, CVec cTrgPos)
1832 {
1833 // Get the target position
1834 if(!psAITarget)
1835 return false;
1836
1837 // Get the projectile
1838 wpnslot_t* wpnslot = m_worm->getWeapon(m_worm->getCurrentWeapon());
1839 const weapon_t* wpn = wpnslot ? wpnslot->Weapon : NULL;
1840 proj_t* wpnproj = wpn ? wpn->Proj.Proj : NULL;
1841 if(!wpnproj) // no valid weapon
1842 return false;
1843
1844 if(iAiDiffLevel < AI_XTREME) {
1845 static float randomNums[4] = {0,0,0,0};
1846 static AbsTime lastRandomNumRefresh;
1847 if(tLX->currentTime - lastRandomNumRefresh > TimeDiff(0.5f)) {
1848 for(uint i = 0; i < sizeof(randomNums)/sizeof(randomNums[0]); ++i)
1849 randomNums[i] = GetRandomNum();
1850 lastRandomNumRefresh = tLX->currentTime;
1851 }
1852
1853 float f = float(AI_XTREME - iAiDiffLevel) / float(AI_XTREME);
1854 gravity += int(randomNums[0] * f * 5);
1855 speed += randomNums[1] * f * 20.0f;
1856 cTrgPos.x += randomNums[2] * f * 50.0f;
1857 cTrgPos.y += randomNums[3] * f * 50.0f;
1858 }
1859
1860 // Exchange endpoints and velocity if needed
1861 float x_vel = 1;
1862 CVec from = m_worm->vPos;
1863 CVec to = cTrgPos;
1864 if (from.x > to.x) {
1865 from = cTrgPos;
1866 to = m_worm->vPos;
1867 x_vel = -1;
1868 } else if (from.x == to.x)
1869 return true;
1870
1871 // Get the parabolic trajectory
1872 // TODO: this calculation is wrong in some cases, someone who knows how to work with fAngle please fix it
1873 float alpha = DEG2RAD(m_worm->fAngle);
1874 if (cTrgPos.y > m_worm->vPos.y)
1875 alpha = -alpha;
1876 Parabola p(m_worm->vPos, alpha, cTrgPos);
1877 if (p.a > 0.1f)
1878 return false;
1879
1880 #ifdef DEBUG
1881 //cClient->getMap()->ClearDebugImage();
1882 #endif
1883
1884 // Check
1885 float last_y = (p.a * (from.x + 5) * (from.x + 5) + p.b * (from.x + 5) + p.c);
1886 for (float x = from.x + 5; x < to.x; x++) {
1887 float y = (p.a * x * x + p.b *x + p.c);
1888
1889 // Rock or dirt, trajectory not free
1890 if (CProjectile::CheckCollision(wpnproj, 1, CVec(x, y), CVec(x_vel, y - last_y)))
1891 return false;
1892
1893 last_y = y;
1894
1895 #ifdef DEBUG
1896 //PutPixel(cClient->getMap()->GetDebugImage(), CLAMP((int)x, 0, (int)cClient->getMap()->GetWidth()-1)*2, CLAMP((int)y, 0, (int)cClient->getMap()->GetHeight()-1)*2, Color(0, 255, 0));
1897 #endif
1898 }
1899
1900 return true;
1901 }
1902
1903
AI_GetAimingAngle(float v,int g,float x,float y,float * angle)1904 bool AI_GetAimingAngle(float v, int g, float x, float y, float *angle)
1905 {
1906 // TODO: returns wron angles (too big) for mortars
1907 // Is it a fault of wrong parameters or wrong calculations?
1908
1909 float v2 = v*v;
1910 float x2 = x*x;
1911 float g2 = (float)(g*g);
1912 float y2 = y*y;
1913
1914 // Small hack - for small y-distance we want positive numbers
1915 if (fabs(y) < 3) {
1916 if (y <= 0)
1917 y = -y;
1918 }
1919
1920 float tmp1 = (float)fabs(-2*v2*y*g-g2*x2+v2*v2);
1921
1922 // Validity check
1923 if (tmp1 < 0) {
1924 *angle = 0;
1925 return false;
1926 }
1927
1928 float tmp2 = -x2*(float)sqrt(tmp1)+x2*y*g+v2*(x2+2*y2);
1929
1930 // Validity check
1931 if (tmp2 < 0) {
1932 *angle = 0;
1933 return false;
1934 }
1935
1936 float tmp3 = (float)sqrt(tmp1)-y*g+v2;
1937
1938 // Validity check
1939 if (tmp3 <= 0 || x == 0) {
1940 *angle = 60;
1941 return false;
1942 }
1943
1944 // Get the angle
1945 *angle = (float)atan((float)sqrt(tmp2)/(x*(float)sqrt(tmp3)));
1946 if (x < 0)
1947 *angle = -(*angle);
1948 if (y > 0)
1949 *angle = -(*angle);
1950
1951 // Convert to degrees
1952 *angle *= R2D;
1953
1954 // Clamp the angle
1955 if (*angle > 60) {
1956 *angle = 60;
1957 return false;
1958 }
1959 if (*angle < -90) {
1960 *angle = -90;
1961 return false;
1962 }
1963
1964 // Ok!
1965 return true;
1966
1967 }
1968
1969
canShootRightNowWithCurWeapon(CWorm * w)1970 static bool canShootRightNowWithCurWeapon(CWorm* w) {
1971 // code from GameServer::WormShoot, CClient::PlayerShoot
1972 // and look also at CClient::ShootSpecial for special weapons like jetpack
1973
1974 wpnslot_t *Slot = w->getCurWeapon();
1975 if(!Slot) return false;
1976
1977 if(Slot->Reloading)
1978 return false;
1979
1980 if(Slot->LastFire>0)
1981 return false;
1982
1983 // Don't shoot with banned weapons
1984 if (!Slot->Enabled)
1985 return false;
1986
1987 if(!Slot->Weapon)
1988 return false;
1989
1990 if(Slot->Weapon->Type == WPN_SPECIAL) {
1991 switch(Slot->Weapon->Special) {
1992 case SPC_JETPACK: return true;
1993 default: return false;
1994 }
1995 }
1996
1997 // Must be a projectile
1998 if(Slot->Weapon->Type != WPN_PROJECTILE && Slot->Weapon->Type != WPN_BEAM)
1999 return false;
2000
2001 return true;
2002 }
2003
2004
2005 ///////////////////
2006 // Shoot!
2007 // returns true if we want to do it or already doing it (also in the progress of aiming)
AI_Shoot()2008 bool CWormBotInputHandler::AI_Shoot()
2009 {
2010 if(cClient->getGameLobby()->gameMode && !cClient->getGameLobby()->gameMode->Shoot(m_worm)) {
2011 // there is no shooting in this gamemode
2012 return false;
2013 }
2014
2015 if(cClient->getGameLobby()->gameMode == GameMode(GM_RACE) || cClient->getGameLobby()->gameMode == GameMode(GM_TEAMRACE))
2016 // dont care about shooting in this mod, just try to be fast
2017 return false;
2018
2019 if(!canShootRightNowWithCurWeapon(m_worm)) return false;
2020
2021 // search for best target
2022 CWorm* w = findTarget();
2023 if(!w) return false;
2024
2025 CVec cTrgPos = w->getPos();
2026 bool bDirect = true;
2027
2028
2029 /*
2030 Here we check if we have a line of sight with the target.
2031 If we do, and our 'direct' firing weapons are loaded, we shoot.
2032
2033 If the line of sight is blocked, we use 'indirect' shooting methods.
2034 We have to be careful with indirect, because if we're in a confined space, or the target is above us
2035 we shouldn't shoot at the target
2036
2037 The aim of the game is killing worms, so we put a higher priority on shooting rather than
2038 thinking tactically to prevent injury to self
2039
2040 If we play a mortar game, make sure there's enough free space to shoot - avoid suicides
2041 */
2042
2043
2044 // If the target is too far away we can't shoot at all (but not when a rifle game)
2045 CVec diff = cTrgPos - m_worm->vPos;
2046 float d = diff.GetLength();
2047 /* if(d > 300.0f && iAiGameType != GAM_RIFLES)
2048 return false; */
2049
2050 /* // If we're on the target, simply shoot // this doesn't work in every case
2051 if(d < 10.0f)
2052 return true;
2053 */
2054
2055 // TODO: first choose the weapon, and then use weaponCanHit instead of traceWeaponLine
2056 // hm, i would say, the other way around is better
2057
2058
2059 float fDist;
2060 int nType = -1;
2061
2062 traceWeaponLine(cTrgPos, &fDist, &nType);
2063
2064 // If target is blocked by rock we can't use direct firing
2065 if(nType & PX_ROCK) {
2066 bDirect = false;
2067 }
2068
2069 // Don't shoot teammates
2070 // TODO: why do we catch only teammates here and not the enemy worm?
2071 if(cClient->getGameLobby()->iGeneralGameType == GMT_TEAMS && (nType & PX_WORM)) {
2072 //notes << "bot: we don't want shoot teammates" << endl;
2073 return false;
2074 }
2075
2076 // If target is blocked by large amount of dirt, we can't shoot it
2077 // But we can use a clearing weapon :)
2078 if (nType & PX_DIRT) {
2079 bDirect = false;
2080 if(diff.y < 0 && fabs(diff.y) > fabs(diff.x) && d-fDist > 40.0f && iAiGameType != GAM_MORTARS) {
2081 int w = AI_FindClearingWeapon();
2082 if (w >= 0 && AI_GetRockBetween(m_worm->vPos, cTrgPos) <= 3) {
2083 m_worm->iCurrentWeapon = w;
2084 if(AI_SetAim(cTrgPos)) {
2085 m_worm->tState.bShoot = true;
2086 return true;
2087 }
2088 else
2089 return false;
2090 }
2091 }
2092 }
2093
2094 // In mortar game there must be enough of free cells around us
2095 if (iAiGameType == GAM_MORTARS)
2096 if (!(nType & PX_EMPTY) && fDist <= 25)
2097 return false;
2098
2099 // Set the best weapon for the situation
2100 // If there is no good weapon, we can't shoot
2101 tLX->debug_float = d;
2102 int wpn = AI_GetBestWeapon(iAiGameType, d, bDirect, fDist);
2103 if(wpn < 0) {
2104 //notes << "bot: I could not find any useable weapon" << endl;
2105 return false;
2106 }
2107
2108 m_worm->iCurrentWeapon = wpn;
2109
2110 bool bAim = false;
2111
2112 float alpha = 0;
2113
2114 const gs_worm_t *wd = m_worm->cGameScript->getWorm();
2115 if (!wd)
2116 return false;
2117
2118 bool bShoot = false;
2119
2120 // Aim in the right direction to account of weapon speed, gravity and worm velocity
2121 const weapon_t *weap = m_worm->getCurWeapon() ? m_worm->getCurWeapon()->Weapon : NULL;
2122 if(!weap) return false;
2123 if(weap->Proj.Proj) {
2124 switch (weap->Proj.Proj->Hit.Type) {
2125 //case PJ_NOTHING:
2126 //case PJ_CARVE:
2127 case PJ_DIRT:
2128 case PJ_GREENDIRT:
2129 //printf("hit_type is %i\n", weap->Proj.Proj->PlyHit.Type);
2130 // don't shoot this shit
2131 break;
2132 default:
2133 CVec direction = (w->getPos() - m_worm->vPos).Normalize();
2134 // speed of target in the direction (moving away from us)
2135 float targ_speed = direction.Scalar(w->getVelocity());
2136 float my_speed = direction.Scalar(m_worm->vVelocity);
2137
2138 // Projectile speed (see CClient::ProcessShot for reference) - targ_speed
2139 float v = (float)weap->Proj.Speed/* *weap->Proj.Proj->Dampening */ + weap->Proj.SpeedVar*100.0f + my_speed;
2140 if(v < 0) {
2141 // we have high velocities, danger to shot...
2142 // if v<0, we would shoot in the wrong direction
2143 //printf("velocities(%f) too high...\n", v);
2144 /* printf(" ProjSpeed = %f\n", (float)weap->Proj.Speed);
2145 printf(" Dampening = %f\n", (float)weap->Proj.Proj->Dampening);
2146 printf(" ProjSpeedVar = %f\n", weap->Proj.SpeedVar);
2147 printf(" my_speed = %f\n", my_speed);
2148 printf(" targ_speed = %f\n", targ_speed); */
2149 bAim = false;
2150 break;
2151 }
2152
2153 // Distance
2154 float x = (cTrgPos.x-m_worm->vPos.x);
2155 float y = (m_worm->vPos.y-cTrgPos.y); // no PC-koord but real-world-koords
2156
2157
2158
2159 // how long it takes for hitting the target
2160 float apriori_time = v ? sqrt(x*x + y*y) / v : 0;
2161 apriori_time *= 0.7f; // it's no direct line but a polynom, so this could give better results :)
2162 if(apriori_time < 0) {
2163 // target is faster than the projectile
2164 // shoot somewhere in the other direction
2165 notes << "bot: target is too fast! my speed: " << my_speed << ", trg speed: " << targ_speed << ", my abs speed: " << m_worm->vVelocity.GetLength() << ", trg abs speed: " << w->getVelocity().GetLength() << ", proj speed: " << (float)weap->Proj.Speed*weap->Proj.Proj->Dampening << "+" << (weap->Proj.SpeedVar*100.0f) << endl;
2166
2167 } else { // apriori_time >= 0
2168 // where the target would be
2169 x += apriori_time*w->getVelocity().x;
2170 y -= apriori_time*w->getVelocity().y; // HINT: real-world-koords
2171 }
2172
2173 // Gravity
2174 int g = 100;
2175 if(weap->Proj.Proj->UseCustomGravity)
2176 g = weap->Proj.Proj->Gravity;
2177
2178 proj_t *tmp = weap->Proj.Proj;
2179 while(tmp) {
2180 if (tmp->UseCustomGravity) {
2181 if (tmp->Gravity > g)
2182 g = tmp->Gravity;
2183 } else
2184 if (g < 100)
2185 g = 100;
2186
2187 // If there are any other projectiles, that are spawned with the main one, try their gravity
2188 if (tmp->Timer.Projectiles) {
2189 if (tmp->Timer.Time >= 0.5f)
2190 break;
2191 }
2192 else if (tmp->Hit.Projectiles || tmp->PlyHit.Projectiles || tmp->Tch.Projectiles)
2193 break;
2194
2195 // TODO: this is not correct anymore for newer gamescripts
2196 tmp = tmp->GeneralSpawnInfo.Proj;
2197 }
2198
2199 // Get the alpha
2200 bAim = AI_GetAimingAngle(v,g,x,y,&alpha);
2201 if (!bAim) {
2202 //printf("cannot calc the alpha, v=%f, g=%i, x=%f, y=%f\n", v,g,x,y);
2203 break;
2204 }
2205
2206 // AI diff level
2207 // Don't shoot so exactly on easier skill levels
2208 static const int diff[4] = {13,8,3,0};
2209
2210 if (tLX->currentTime-fLastRandomChange >= 0.5f) {
2211 iRandomSpread = GetRandomInt(diff[iAiDiffLevel]) * SIGN(GetRandomNum());
2212 fLastRandomChange = tLX->currentTime;
2213 }
2214
2215 alpha += iRandomSpread;
2216
2217 // Can we hit the target?
2218 //printf("proj-speed: %f\n", v);
2219 if (g <= 10 || v >= 200) {
2220 // we already have bDirect==false, if we have no direct free way
2221 bAim = bDirect;
2222 }
2223
2224 if (!bAim)
2225 break;
2226
2227 bShoot = true;
2228
2229 float trg_dist2 = (cTrgPos - m_worm->vPos).GetLength2();
2230 if (trg_dist2 >= 2500) {
2231 bAim = AI_SetAim(cTrgPos);
2232 } else if (trg_dist2 >= 100) {
2233 bAim = AI_SetAim(cTrgPos);
2234 if (iAiGameType != GAM_MORTARS) // Not in mortars - close shoot = suicide
2235 bShoot = true; // We are so close, that we almost cannot miss, just shoot
2236 break;
2237 } else { // Too close, get away!
2238 bAim = false;
2239 bShoot = false;
2240 }
2241
2242 // Check if we can aim with this angle
2243 // HINT: we have to do it here because weaponCanHit requires already finished aiming
2244 if (bAim && g >= 10 && v <= 200) {
2245 bShoot = bAim = weaponCanHit(g,v,CVec(m_worm->vPos.x+x,m_worm->vPos.y-y));
2246 }
2247
2248 break;
2249 }
2250
2251 // Not a projectile weapon
2252 } else {
2253 // Beam
2254 if (weap->Type == WPN_BEAM) {
2255 int dist = (int)(cTrgPos - m_worm->vPos).GetLength();
2256 if (bDirect && weap->Bm.Length >= dist) // Check that the beam can reach the target
2257 bShoot = bAim = AI_SetAim(cTrgPos) || dist <= 25;
2258 else {
2259 if (bDirect)
2260 AI_SetAim(cTrgPos); // Aim - when we come closer, we won't have to aim anymore
2261 bShoot = bAim = false;
2262 }
2263 }
2264
2265 }
2266
2267 //
2268 // If there's some lag or low FPS, don't shoot in the direction of our flight (avoid suicides)
2269 //
2270
2271 // HINT: we don't need this, because we ensure above in the speed-calculation, that we have no problem
2272 // TODO: avoiding projectiles should not be done by not shooting but by changing MoveToTarget
2273 if(bAim) if (tLX->iGameType == GME_JOIN) {
2274 // Get the angle
2275 float ang = (float)atan2(m_worm->vVelocity.x, m_worm->vVelocity.y);
2276 ang = RAD2DEG(ang);
2277 if(m_worm->iFaceDirectionSide == DIR_LEFT)
2278 ang+=90;
2279 else
2280 ang = -ang + 90;
2281
2282 // Cannot shoot
2283 if (fabs(m_worm->fAngle-ang) <= 30 && m_worm->vVelocity.GetLength2() >= 3600.0f && weap->Type != WPN_BEAM) {
2284 if (weap->Type == WPN_PROJECTILE) {
2285 if (weap->Proj.Proj->PlyHit.Damage > 0)
2286 return false;
2287 }
2288 }
2289 }
2290
2291 if(!bAim) {
2292
2293 // we cannot shoot here
2294
2295 // TODO: do we need this?
2296 fBadAimTime += tLX->fDeltaTime;
2297 if((fBadAimTime) > 4) {
2298 if(m_worm->IsEmpty(CELL_UP))
2299 m_worm->tState.bJump = true;
2300 fBadAimTime = 0;
2301 }
2302
2303 fCanShootTime = 0;
2304
2305 return bShoot;
2306 }
2307
2308 // Reflexes :)
2309 // TODO: this doesn't work atm
2310 /*float diff[4] = {0.45f,0.35f,0.25f,0.0f};
2311 fCanShootTime += tLX->fDeltaTime;
2312 if (fCanShootTime <= diff[iAiDiffLevel]) {
2313 return false;
2314 }*/
2315
2316 fBadAimTime = 0;
2317 vLastShootTargetPos = cTrgPos;
2318
2319 // Shoot
2320 m_worm->tState.bShoot = true;
2321 fLastShoot = tLX->currentTime;
2322 return true;
2323 }
2324
2325
2326 ///////////////////
2327 // AI: Get the best weapon for the situation
2328 // Returns weapon id or -1 if no weapon is suitable for the situation
AI_GetBestWeapon(int iGameMode,float fDistance,bool bDirect,float fTraceDist)2329 int CWormBotInputHandler::AI_GetBestWeapon(int iGameMode, float fDistance, bool bDirect, float fTraceDist) {
2330 // if we are to close to the target, don't selct any weapon (=> move away)
2331 /*if(fDistance < 5)
2332 return -1; */
2333
2334 float diff[4] = {0.50f,0.30f,0.20f,0.12f};
2335
2336 // We need to wait a certain time before we change weapon
2337 if( tLX->currentTime - fLastWeaponChange > diff[iAiDiffLevel] )
2338 fLastWeaponChange = tLX->currentTime;
2339 else
2340 return m_worm->iCurrentWeapon;
2341
2342 // For rifles and mortars just get the first unreloaded weapon
2343 if (iAiGameType == GAM_RIFLES || iAiGameType == GAM_MORTARS) {
2344 for (int i=0; i<5; i++)
2345 if (!m_worm->tWeapons[i].Reloading)
2346 return i;
2347 //printf("GAM_RIFLES|GAM_MORTARS: all weapons still are reloading...\n");
2348 return -1;
2349 }
2350
2351 CVec cTrgPos = AI_GetTargetPos();
2352
2353
2354 if (iAiGameType == GAM_100LT) {
2355 // We're above the worm
2356
2357 // If we are close enough, shoot the napalm
2358 if (m_worm->vPos.y <= cTrgPos.y && (m_worm->vPos-cTrgPos).GetLength2() < 10000.0f) {
2359 if (traceWormLine(cTrgPos,m_worm->vPos) && !m_worm->tWeapons[1].Reloading)
2360 if (psAITarget)
2361 if (psAITarget->CheckOnGround())
2362 return 1;
2363 }
2364
2365
2366
2367 float d = (m_worm->vPos-cTrgPos).GetLength();
2368 // We're close to the target
2369 if (d < 50.0f) {
2370 // We see the target
2371 if(bDirect) {
2372 // Super shotgun
2373 if (!m_worm->tWeapons[0].Reloading)
2374 return 0;
2375
2376 // Chaingun
2377 if (!m_worm->tWeapons[4].Reloading)
2378 return 4;
2379
2380
2381 // Doomsday
2382 if (!m_worm->tWeapons[3].Reloading)
2383 return 3;
2384
2385 // Let's try cannon
2386 if (!m_worm->tWeapons[2].Reloading)
2387 // Don't use cannon when we're on ninja rope, we will avoid suicides
2388 if (!m_worm->cNinjaRope.isAttached()) {
2389 m_worm->tState.bMove = false; // Don't move, avoid suicides
2390 return 2;
2391 }
2392 }
2393 // We don't see the target
2394 else {
2395 //notes << "bot: GAM_100LT: i think we should not shoot here" << endl;
2396 m_worm->tState.bJump = true; // Jump, we might get better position
2397 return -1;
2398 }
2399 }
2400
2401 // Not close, not far
2402 if (d > 50.0f && d<=300.0f) {
2403 if (bDirect) {
2404
2405 // Chaingun is the best weapon for this situation
2406 if (!m_worm->tWeapons[4].Reloading) {
2407 return 4;
2408 }
2409
2410 // Let's try cannon
2411 if (!m_worm->tWeapons[2].Reloading)
2412 // Don't use cannon when we're on ninja rope, we will avoid suicides
2413 if (!m_worm->cNinjaRope.isReleased()) {
2414 m_worm->tState.bMove = false; // Don't move, avoid suicides
2415 return 2;
2416 }
2417
2418 // Super Shotgun makes it sure
2419 if (!m_worm->tWeapons[0].Reloading)
2420 return 0;
2421
2422 // As for almost last, try doomsday
2423 if (!m_worm->tWeapons[3].Reloading)
2424 // Don't use doomsday when we're on ninja rope, we will avoid suicides
2425 if (!m_worm->cNinjaRope.isAttached()) {
2426 m_worm->tState.bMove = false; // Don't move, avoid suicides
2427 return 3;
2428 }
2429 } // End of direct shooting weaps
2430
2431 //return -1;
2432 }
2433
2434 // Quite far
2435 if (d > 300.0f && bDirect) {
2436
2437 // First try doomsday
2438 if (!m_worm->tWeapons[3].Reloading) {
2439 // Don't use doomsday when we're on ninja rope, we will avoid suicides
2440 if (!m_worm->cNinjaRope.isAttached()) {
2441 m_worm->tState.bMove = false; // Don't move, avoid suicides
2442 return 3;
2443 }
2444 }
2445
2446 // Super Shotgun
2447 if (!m_worm->tWeapons[0].Reloading)
2448 return 0;
2449
2450 // Chaingun
2451 if (!m_worm->tWeapons[4].Reloading)
2452 return 4;
2453
2454 // Cannon, the worst possible for this
2455 if (!m_worm->tWeapons[2].Reloading)
2456 // Don't use cannon when we're on ninja rope, we will avoid suicides
2457 if (!m_worm->cNinjaRope.isReleased()) {
2458 // Aim a bit up
2459 // AI_SetAim(CVec(cTrgPos.x,cTrgPos.y+5.0f)); // don't do aiming here
2460 m_worm->tState.bMove = false; // Don't move, avoid suicides
2461 return 2;
2462 }
2463 }
2464
2465 //return -1;
2466
2467 }
2468
2469 /*
2470 0: "minigun"
2471 1: "shotgun"
2472 2: "chiquita bomb"
2473 3: "blaster"
2474 4: "big nuke"
2475 */
2476
2477
2478 /*
2479
2480 Special firing cases
2481
2482 */
2483
2484 //
2485 // Case 1: The target is on the bottom of the level, a perfect spot to lob an indirect weapon
2486 //
2487 if(cTrgPos.y > cClient->getMap()->GetHeight()-50 && fDistance < 200) {
2488 for (int i=0; i<5; i++)
2489 if (!m_worm->tWeapons[i].Reloading && m_worm->tWeapons[i].Enabled)
2490 if (m_worm->tWeapons[i].Weapon && m_worm->tWeapons[i].Weapon->Type == WPN_PROJECTILE)
2491 if (m_worm->tWeapons[i].Weapon->Proj.Proj->Hit.Type == PJ_EXPLODE)
2492 return i;
2493 }
2494
2495
2496
2497 /*
2498
2499 Direct firing weapons
2500
2501 */
2502
2503 //
2504 // If we're close, use some beam or projectile weapon
2505 //
2506 if(fDistance < 100 && bDirect) {
2507 // First try beam
2508 int i;
2509 for (i=0; i<5; i++)
2510 if (!m_worm->tWeapons[i].Reloading && m_worm->tWeapons[i].Enabled)
2511 if (m_worm->tWeapons[i].Weapon && m_worm->tWeapons[i].Weapon->Type == WPN_BEAM)
2512 return i;
2513
2514 // If beam not available, try projectile
2515 for (i=0; i<5; i++)
2516 if (!m_worm->tWeapons[i].Reloading && m_worm->tWeapons[i].Enabled)
2517 if (m_worm->tWeapons[i].Weapon && m_worm->tWeapons[i].Weapon->Type == WPN_PROJECTILE)
2518 if( m_worm->tWeapons[i].Weapon->Proj.Proj->Hit.Type != PJ_DIRT
2519 && m_worm->tWeapons[i].Weapon->Proj.Proj->Hit.Type != PJ_GREENDIRT)
2520 //if (tWeapons[i].Weapon->Proj.Proj->Type == PRJ_PIXEL)
2521 return i;
2522
2523 // don't return here, try selection by other, not optimal fitting cases
2524 }
2525
2526
2527 //
2528 // If we're at a medium distance, use any weapon, but prefer the exact ones
2529 //
2530 if(fDistance < 150 && bDirect) {
2531
2532 // First try beam
2533 int i;
2534 for (i=0; i<5; i++)
2535 if (!m_worm->tWeapons[i].Reloading && m_worm->tWeapons[i].Enabled)
2536 if (m_worm->tWeapons[i].Weapon && m_worm->tWeapons[i].Weapon->Type == WPN_BEAM)
2537 return i;
2538
2539 // If beam not available, try projectile
2540 for (i=0; i<5; i++)
2541 if (!m_worm->tWeapons[i].Reloading && m_worm->tWeapons[i].Enabled)
2542 if (m_worm->tWeapons[i].Weapon && m_worm->tWeapons[i].Weapon->Type == WPN_PROJECTILE)
2543 if( m_worm->tWeapons[i].Weapon->Proj.Proj->Hit.Type != PJ_DIRT
2544 && m_worm->tWeapons[i].Weapon->Proj.Proj->Hit.Type != PJ_GREENDIRT)
2545 /*if (tWeapons[i].Weapon->Proj.Proj->Type == PRJ_PIXEL || tWeapons[i].Weapon->Proj.Proj->Hit.Type == PJ_BOUNCE)*/
2546 return i;
2547
2548 // don't return here, try selection by other, not optimal fitting cases
2549 }
2550
2551
2552 //
2553 // Any greater distance for direct firing uses a projectile weapon first
2554 //
2555 if(bDirect) {
2556 // First try projectile
2557 int i;
2558 for (i=0; i<5; i++)
2559 if (!m_worm->tWeapons[i].Reloading && m_worm->tWeapons[i].Enabled)
2560 if (m_worm->tWeapons[i].Weapon && m_worm->tWeapons[i].Weapon->Type == WPN_PROJECTILE)
2561 if( m_worm->tWeapons[i].Weapon->Proj.Proj->Hit.Type != PJ_DIRT
2562 && m_worm->tWeapons[i].Weapon->Proj.Proj->Hit.Type != PJ_GREENDIRT)
2563 return i;
2564
2565 // If projectile not available, try beam
2566 for (i=0; i<5; i++)
2567 if (!m_worm->tWeapons[i].Reloading && m_worm->tWeapons[i].Enabled)
2568 if (m_worm->tWeapons[i].Weapon && m_worm->tWeapons[i].Weapon->Type == WPN_BEAM)
2569 return i;
2570
2571 // If everything fails, try some random weapons
2572 int num=0;
2573 for (i=0; i<5; i++, num=GetRandomInt(4))
2574 if (!m_worm->tWeapons[num].Reloading && m_worm->tWeapons[i].Enabled && m_worm->tWeapons[i].Weapon)
2575 return num;
2576
2577 //return -1;
2578 }
2579
2580
2581 //
2582 // Indirect firing weapons
2583 //
2584
2585
2586 // If we're above the target, try any special weapon, for Liero mod try napalm
2587 // BUT only if our health is looking good
2588 // AND if there is no rock/dirt nearby
2589 if(fDistance > 190 && m_worm->health > 25 && fTraceDist > 0.5f && (cTrgPos.y-20) > m_worm->vPos.y ) {
2590 if (!AI_CheckFreeCells(5)) {
2591 //notes << "bot: we should not shoot because of the hints everywhere" << endl;
2592 return -1;
2593 }
2594
2595 // try projectile weapons
2596 for (int i=0; i<5; i++)
2597 if (!m_worm->tWeapons[i].Reloading && m_worm->tWeapons[i].Enabled && m_worm->tWeapons[i].Weapon && m_worm->tWeapons[i].Weapon->Type == WPN_PROJECTILE)
2598 if (m_worm->tWeapons[i].Weapon->Proj.Proj->Hit.Type == PJ_EXPLODE || m_worm->tWeapons[i].Weapon->Proj.Proj->Hit.Type == PJ_BOUNCE)
2599 return i;
2600
2601 }
2602
2603
2604 //
2605 // Last resort
2606 //
2607
2608 // Shoot a beam (we cant suicide with that)
2609 int i;
2610 for (i=0; i<5; i++)
2611 if (!m_worm->tWeapons[i].Reloading && m_worm->tWeapons[i].Enabled && m_worm->tWeapons[i].Weapon && m_worm->tWeapons[i].Weapon->Type == WPN_BEAM)
2612 return i;
2613
2614
2615
2616 // No suitable weapon found
2617 /*for (i=0;i<5;i++)
2618 if (!tWeapons[i].Reloading)
2619 {
2620 return i;
2621 }*/
2622
2623 // If everything fails, try some random weapons
2624 int num = GetRandomInt(4);
2625 for (i=0; i<5; i++, num=GetRandomInt(4))
2626 if (!m_worm->tWeapons[num].Reloading && m_worm->tWeapons[i].Enabled && m_worm->tWeapons[i].Weapon)
2627 return num;
2628
2629 // If everything fails, try all weapons
2630 for (i=0; i<5; i++)
2631 if (!m_worm->tWeapons[i].Reloading && m_worm->tWeapons[i].Enabled && m_worm->tWeapons[i].Weapon)
2632 return num;
2633
2634 // printf("simply everything failed, no luck with that\n");
2635 return -1;
2636 }
2637
2638
2639
2640
2641 ///////////////////
2642 // Trace a line from this worm to the target
2643 // Returns the distance the trace went
traceLine(CVec target,float * fDist,int * nType,int divs)2644 int CWorm::traceLine(CVec target, float *fDist, int *nType, int divs) {
2645 int res = traceLine(target, vPos, nType, divs);
2646 if (fDist && res) *fDist = (float)((int)(vPos - target).GetLength() / res);
2647 return res;
2648 }
2649
traceLine(CVec target,CVec start,int * nType,int divs,uchar checkflag)2650 int CWorm::traceLine(CVec target, CVec start, int *nType, int divs, uchar checkflag)
2651 {
2652 if( cClient->getMap() == NULL ) {
2653 errors << "CWorm::traceLine: map unset" << endl;
2654 return 0;
2655 }
2656
2657 // Trace a line from the worm to length or until it hits something
2658 CVec pos = start;
2659 CVec dir = target-pos;
2660 int nTotalLength = (int)NormalizeVector(&dir);
2661
2662 int divisions = divs; // How many pixels we go through each check (more = slower)
2663
2664 if( nTotalLength < divisions)
2665 divisions = nTotalLength;
2666
2667 if (nType)
2668 *nType = PX_EMPTY;
2669
2670 // Make sure we have at least 1 division
2671 divisions = MAX(divisions,1);
2672
2673 int i;
2674 for(i=0; i<nTotalLength; i+=divisions) {
2675 uchar px = cClient->getMap()->GetPixelFlag( (int)pos.x, (int)pos.y );
2676 //cClient->getMap()->PutImagePixel((int)pos.x, (int)pos.y, Color(255,0,0));
2677
2678 if(!(px & checkflag)) {
2679 if (nType)
2680 *nType = px;
2681 return i;
2682 }
2683
2684 pos = pos + dir * (float)divisions;
2685 }
2686
2687 return nTotalLength;
2688 }
2689
2690 ///////////////////
2691 // Returns true, if the cell is empty
2692 // Cell can be: CELL_CURRENT, CELL_LEFT,CELL_DOWN,CELL_RIGHT,CELL_UP,CELL_LEFTDOWN,CELL_RIGHTDOWN,CELL_LEFTUP,CELL_RIGHTUP
IsEmpty(int Cell)2693 bool CWorm::IsEmpty(int Cell)
2694 {
2695 bool bEmpty = false;
2696 int cx = (int)(vPos.x / cClient->getMap()->getGridWidth());
2697 int cy = (int)(vPos.y / cClient->getMap()->getGridHeight());
2698
2699 switch (Cell) {
2700 case CELL_LEFT:
2701 cx--;
2702 break;
2703 case CELL_DOWN:
2704 cy++;
2705 break;
2706 case CELL_RIGHT:
2707 cx++;
2708 break;
2709 case CELL_UP:
2710 cy--;
2711 break;
2712 case CELL_LEFTDOWN:
2713 cx--;
2714 cy++;
2715 break;
2716 case CELL_RIGHTDOWN:
2717 cx++;
2718 cy++;
2719 break;
2720 case CELL_LEFTUP:
2721 cx--;
2722 cy--;
2723 break;
2724 case CELL_RIGHTUP:
2725 cx++;
2726 cy--;
2727 break;
2728 }
2729
2730 if ((cx < 0 || cx > cClient->getMap()->getGridCols()))
2731 return false;
2732
2733 if ((cy < 0 || cy > cClient->getMap()->getGridRows()))
2734 return false;
2735
2736 const uchar *f = cClient->getMap()->getGridFlags() + cy*cClient->getMap()->getGridWidth()+cx;
2737 bEmpty = *f == PX_EMPTY;
2738
2739 return bEmpty;
2740 }
2741
2742 /////////////////////////////
2743 // TEST TEST TEST
2744 //////////////////
2745 // Finds the nearest free cell in the map and returns coordinates of its midpoint
AI_FindClosestFreeCell(CVec vPoint)2746 CVec CWormBotInputHandler::AI_FindClosestFreeCell(CVec vPoint)
2747 {
2748 // NOTE: highly unoptimized, looks many times to the same cells
2749
2750 // Get the cell
2751 int cellX = (int) fabs((vPoint.x)/cClient->getMap()->getGridWidth());
2752 int cellY = (int) fabs((vPoint.y)/cClient->getMap()->getGridHeight());
2753
2754 int cellsSearched = 1;
2755 const int numCells = cClient->getMap()->getGridCols() * cClient->getMap()->getGridRows();
2756 int i=1;
2757 int x,y;
2758 uchar tmp_pf = PX_ROCK;
2759 while (cellsSearched < numCells) {
2760 for (y=cellY-i;y<=cellY+i;y++) {
2761
2762 // Clipping
2763 if (y > cClient->getMap()->getGridRows())
2764 break;
2765 if (y < 0)
2766 continue;
2767
2768 for (x=cellX-i;x<=cellX+i;x++) {
2769 // Don't check the entry cell
2770 if (x == cellX && y == cellY)
2771 continue;
2772
2773 // Clipping
2774 if (x > cClient->getMap()->getGridCols())
2775 break;
2776 if (x < 0)
2777 continue;
2778
2779 tmp_pf = *(cClient->getMap()->getGridFlags() + y*cClient->getMap()->getGridCols() +x);
2780 if (!(tmp_pf & PX_ROCK))
2781 return CVec((float)x*cClient->getMap()->getGridWidth()+cClient->getMap()->getGridWidth()/2, (float)y*cClient->getMap()->getGridHeight()+cClient->getMap()->getGridHeight()/2);
2782 }
2783 }
2784 i++;
2785 cellsSearched++;
2786 }
2787
2788 // Can't get here
2789 return CVec(0,0);
2790 }
2791
2792 /////////////////////
2793 // Trace the line for the current weapon
traceWeaponLine(CVec target,float * fDist,int * nType)2794 int CWormBotInputHandler::traceWeaponLine(CVec target, float *fDist, int *nType)
2795 {
2796 if( cClient->getMap() == NULL) {
2797 errors << "AI:traceWeaponLine: map == NULL" << endl;
2798 return 0;
2799 }
2800
2801 if(!m_worm->getCurWeapon() || !m_worm->getCurWeapon()->Weapon)
2802 return 0;
2803
2804 // Trace a line from the worm to length or until it hits something
2805 CVec pos = m_worm->vPos;
2806 CVec dir = target-pos;
2807 int nTotalLength = (int)NormalizeVector(&dir);
2808
2809 int first_division = 7; // How many pixels we go through first check (we can shoot through walls)
2810 int divisions = 5; // How many pixels we go through each check (more = slower)
2811
2812 //
2813 // Predefined divisions
2814 //
2815
2816 // Beam
2817 if (m_worm->tWeapons[m_worm->iCurrentWeapon].Weapon->Type == WPN_BEAM) {
2818 first_division = 1;
2819 divisions = 1;
2820 }
2821
2822 // Rifles
2823 else if (iAiGameType == GAM_RIFLES) {
2824 first_division = 10;
2825 divisions = 6;
2826 }
2827
2828 // Mortars
2829 else if (iAiGameType == GAM_MORTARS) {
2830 first_division = 7;
2831 divisions = 2;
2832 }
2833
2834 // 100lt
2835 else if (iAiGameType == GAM_100LT) {
2836 first_division = 6;
2837 divisions = 3;
2838 }
2839
2840 // Add the worm thickness
2841 first_division += 5;
2842
2843 // Check
2844 if( nTotalLength < divisions)
2845 divisions = nTotalLength;
2846
2847 *nType = PX_EMPTY;
2848
2849 // Make sure we have at least 1 division
2850 divisions = MAX(divisions,1);
2851 first_division = MAX(first_division,1);
2852
2853 // Check that we don't hit any teammate
2854 CVec WormsPos[MAX_WORMS];
2855 int WormCount = 0;
2856 int i;
2857 if (cClient && cClient->getGameLobby()->iGeneralGameType == GMT_TEAMS) {
2858 CWorm *w = cClient->getRemoteWorms();
2859 for (i=0;i<MAX_WORMS;i++,w++) {
2860 if (w) {
2861 if (w->isUsed() && w->getAlive() && w->getTeam() == m_worm->iTeam && w->getID() != m_worm->iID)
2862 WormsPos[WormCount++] = w->getPos();
2863 }
2864 }
2865 }
2866
2867
2868 // Trace the line
2869 int divs = first_division;
2870 int j;
2871 for(i=0; i<nTotalLength; i+=divs) {
2872 uchar px = cClient->getMap()->GetPixelFlag( (int)pos.x, (int)pos.y );
2873
2874 if (i>first_division) // we aren't close to a wall, so we can shoot through only thin wall
2875 divs = divisions;
2876
2877 // Dirt or rock
2878 if((px & PX_DIRT) || (px & PX_ROCK)) {
2879 if(nTotalLength != 0)
2880 *fDist = (float)i / (float)nTotalLength;
2881 else
2882 *fDist = 0;
2883 *nType = px;
2884 return i;
2885 }
2886
2887 // Friendly worm
2888 for (j=0;j<WormCount;j++) {
2889 if ((pos-WormsPos[j]).GetLength2() < 400.0f) {
2890 if(nTotalLength != 0)
2891 *fDist = (float)i / (float)nTotalLength;
2892 else
2893 *fDist = 0;
2894 *nType = *nType | PX_WORM;
2895 return i;
2896 }
2897 }
2898
2899 pos += dir * (float)divs;
2900 }
2901
2902 // Full length
2903 if(nTotalLength != 0)
2904 *fDist = (float)i / (float)nTotalLength;
2905 else
2906 *fDist = 0;
2907 return nTotalLength;
2908 }
2909
2910
2911
2912
2913 class set_col_and_break {
2914 public:
2915 CVec start;
2916 CVec* collision;
2917 bool hit;
2918
set_col_and_break(CVec st,CVec * col)2919 set_col_and_break(CVec st, CVec* col) : start(st), collision(col), hit(false) {}
operator ()(int x,int y)2920 bool operator()(int x, int y) {
2921 hit = true;
2922 if(collision && (*collision - start).GetLength2() > (CVec((float)x,(float)y) - start).GetLength2()) {
2923 collision->x = (float)x;
2924 collision->y = (float)y;
2925 }
2926 return false;
2927 }
2928 };
2929
2930
2931 ////////////////////
2932 // Trace the line with worm width
traceWormLine(CVec target,CVec start,CVec * collision)2933 int traceWormLine(CVec target, CVec start, CVec* collision)
2934 {
2935 // At least three, else it goes through walls in jukke
2936 static const unsigned short wormsize = 3;
2937
2938 if(collision) {
2939 collision->x = target.x;
2940 collision->y = target.y;
2941 }
2942
2943 CVec dir = CVec(target.y-start.y,start.x-target.x); // rotate clockwise by 90 deg
2944 NormalizeVector(&dir);
2945 set_col_and_break action = set_col_and_break(start - dir*(wormsize-1)/2, collision);
2946 target -= dir*(wormsize-1)/2;
2947 for(unsigned short i = 0; i < wormsize; i++, action.start += dir, target += dir)
2948 fastTraceLine(target, action.start, (uchar)PX_ROCK, action);
2949
2950 return !action.hit;
2951
2952 }
2953
2954 ////////////////////////
2955 // Checks if there is enough free cells around us to shoot
AI_CheckFreeCells(int Num)2956 bool CWormBotInputHandler::AI_CheckFreeCells(int Num)
2957 {
2958 // Get the cell
2959 int cellX = (int) fabs((m_worm->vPos.x)/cClient->getMap()->getGridWidth());
2960 int cellY = (int) fabs((m_worm->vPos.y)/cClient->getMap()->getGridHeight());
2961
2962 // First of all, check our current cell
2963 if (*(cClient->getMap()->getGridFlags() + cellY*cClient->getMap()->getGridCols() +cellX) & PX_ROCK)
2964 return false;
2965
2966
2967 // Direction to left
2968 if (m_worm->iFaceDirectionSide == DIR_LEFT) {
2969 int dir = 0;
2970 if (m_worm->fAngle > 210)
2971 dir = 1;
2972 if (m_worm->fAngle < 210 && m_worm->fAngle > 160)
2973 dir = -Num/2;
2974 if (m_worm->fAngle < 160)
2975 dir = -1*Num;
2976
2977 /*#ifdef _AI_DEBUG
2978 int dX = (cellX-Num-1)*cClient->getMap()->getGridWidth()+cClient->getMap()->getGridWidth()/2;
2979 int dY = (cellY+dir)*cClient->getMap()->getGridHeight()+cClient->getMap()->getGridHeight()/2;
2980 int dX2 = (cellX-1)*cClient->getMap()->getGridWidth()+cClient->getMap()->getGridWidth()/2;
2981 int dY2 = (cellY+dir+Num)*cClient->getMap()->getGridHeight()+cClient->getMap()->getGridHeight()/2;
2982 DrawRect(bmpDest,dX*2,dY*2,dX2*2,dY2*2,Color(255,0,0));
2983 #endif*/
2984
2985 // Check the Num*Num square
2986 int x,y;
2987 for (x = cellX - Num - 1; x < cellX; x++) {
2988 // Clipping means rock
2989 if ((x < 0 || x > cClient->getMap()->getGridCols()))
2990 return false;
2991 for (y = cellY + dir; y < cellY + dir + Num; y++) {
2992 // Clipping means rock
2993 if ((y < 0 || y > cClient->getMap()->getGridRows()))
2994 return false;
2995
2996 // Clipping means rock
2997 if (*(cClient->getMap()->getGridFlags() + y*cClient->getMap()->getGridCols() +x) & PX_ROCK)
2998 return false;
2999 }
3000 }
3001
3002 return true;
3003 // Direction to right
3004 } else {
3005 int dir = 0;
3006 if (m_worm->fAngle > 20)
3007 dir = 1;
3008 else if (m_worm->fAngle < 20 && m_worm->fAngle > -20)
3009 dir = -Num/2;
3010 else if (m_worm->fAngle < -20)
3011 dir = -1*Num;
3012
3013 /*#ifdef _AI_DEBUG
3014 int dX = (cellX)*cClient->getMap()->getGridWidth()+cClient->getMap()->getGridWidth()/2;
3015 int dY = (cellY+dir)*cClient->getMap()->getGridHeight()+cClient->getMap()->getGridHeight()/2;
3016 int dX2 = (cellX+Num)*cClient->getMap()->getGridWidth()+cClient->getMap()->getGridWidth()/2;
3017 int dY2 = (cellY+dir+Num)*cClient->getMap()->getGridHeight()+cClient->getMap()->getGridHeight()/2;
3018 DrawRect(bmpDest,dX*2,dY*2,dX2*2,dY2*2,Color(255,0,0));
3019 #endif*/
3020
3021 // Check the square Num*Num
3022 int x,y;
3023 for (x=cellX;x<=cellX+Num;x++) {
3024 // Clipping means rock
3025 if((x< 0 || x > cClient->getMap()->getGridCols()))
3026 return false;
3027 for(y=cellY+dir;y<cellY+dir+Num;y++) {
3028 // Clipping means rock
3029 if((y < 0 || y > cClient->getMap()->getGridRows()))
3030 return false;
3031
3032 // Rock cell
3033 if(*(cClient->getMap()->getGridFlags() + y*cClient->getMap()->getGridCols() +x) & PX_ROCK)
3034 return false;
3035 }
3036 }
3037
3038 return true;
3039 }
3040
3041 // Weird, shouldn't happen
3042 errors << "bot: ouh, what???" << endl;
3043 return false;
3044 }
3045
3046
3047 ////////////////////
3048 // Creates the path
AI_CreatePath(bool force_break)3049 int CWormBotInputHandler::AI_CreatePath(bool force_break)
3050 {
3051
3052 CVec trg = AI_GetTargetPos();
3053 if (psAITarget && nAITargetType == AIT_WORM)
3054 trg = AI_FindShootingSpot(); // If our target is an enemy, go to the best spot for shooting
3055
3056 if(force_break) {
3057 bPathFinished = false;
3058 fSearchStartTime = tLX->currentTime;
3059 pathSearcher->restartThreadSearch(m_worm->vPos, trg);
3060
3061 return false;
3062 }
3063
3064 // bPathFinished also implicates, that there is currently no search
3065 // if bPathFinished is set in the wrong way, this will result in multithreading errors!
3066 // TODO: something has to be changed here, because the name bPathFinished doesn't indicates this
3067 if(!bPathFinished) {
3068 // have we finished a current search?
3069 if(pathSearcher->isReady()) {
3070
3071 bPathFinished = true;
3072 NEW_ai_node_t* res = pathSearcher->resultedPath();
3073
3074 // have we found something?
3075 if(res) {
3076 // in every case, the nodes of the current path are not handled by pathSearcher
3077 // so we have to delete it here
3078 delete_ai_nodes(NEW_psPath);
3079
3080 NEW_psPath = res;
3081 NEW_psLastNode = get_last_ai_node(NEW_psPath);
3082 NEW_psCurrentNode = NEW_psPath;
3083
3084 // prevent it from deleting the current path (it will be deleted, when the new path is found)
3085 pathSearcher->removePathFromList(NEW_psPath);
3086
3087
3088 fLastCreated = tLX->currentTime;
3089
3090 return true;
3091 }
3092 // we don't find anything, so don't return here, but start a new search
3093
3094 } else { // the searcher is still searching ...
3095
3096 // restart search in some cases
3097 if(!pathSearcher->shouldRestartThread() && (tLX->currentTime - fSearchStartTime >= 5.0f || !traceWormLine(m_worm->vPos, pathSearcher->start) || !traceWormLine(trg, pathSearcher->target))) {
3098 fSearchStartTime = tLX->currentTime;
3099 pathSearcher->restartThreadSearch(m_worm->vPos, trg);
3100 }
3101
3102 return false;
3103 }
3104 }
3105
3106 // don't start a new search, if the current end-node still has direct access to it
3107 // however, we have to have access somewhere to the path
3108 if(NEW_psLastNode && traceWormLine(CVec(NEW_psLastNode->fX, NEW_psLastNode->fY), trg)) {
3109 for(NEW_ai_node_t* node = NEW_psPath; node; node = node->psNext)
3110 if(traceWormLine(CVec(node->fX,node->fY),m_worm->vPos,NULL)) {
3111 NEW_psCurrentNode = node;
3112 NEW_psLastNode->psNext = createNewAiNode(trg.x, trg.y, NULL, NEW_psLastNode);
3113 NEW_psLastNode = NEW_psLastNode->psNext;
3114 return true;
3115 }
3116 }
3117
3118 // Don't create the path so often!
3119 if (tLX->currentTime - fLastCreated <= 0.5f) {
3120 return NEW_psPath != NULL;
3121 }
3122
3123 // if we are here, we want to start a new search
3124 // the searcher-thread is currently ready
3125 bPathFinished = false;
3126
3127 // start a new search
3128 fSearchStartTime = tLX->currentTime;
3129 pathSearcher->target.x = (int)trg.x;
3130 pathSearcher->target.y = (int)trg.y;
3131 pathSearcher->start = VectorD2<int>(m_worm->vPos);
3132 pathSearcher->startThreadSearch();
3133
3134 return false;
3135 }
3136
3137
AI_GetRockBetween(CVec pos,CVec trg)3138 int CWormBotInputHandler::AI_GetRockBetween(CVec pos, CVec trg)
3139 {
3140 assert( cClient->getMap() != NULL );
3141
3142 int result = 0;
3143
3144 // Trace a line from the worm to the target
3145 CVec dir = trg-pos;
3146 int nTotalLength = (int)NormalizeVector(&dir);
3147
3148 const int divisions = 4; // How many pixels we go through each check (less = slower)
3149
3150 int i;
3151 uchar px = PX_EMPTY;
3152 for(i=0; i<nTotalLength; i+=divisions) {
3153 px = cClient->getMap()->GetPixelFlag( (int)pos.x, (int)pos.y );
3154 //cClient->getMap()->PutImagePixel((int)pos.x, (int)pos.y, Color(255,0,0));
3155
3156 if (px & PX_ROCK)
3157 result++;
3158
3159 pos = pos + dir * (float)divisions;
3160 }
3161
3162 return result;
3163 }
3164
3165 /*CVec NEW_AI_FindBestSpot(CVec trg, CVec pos)
3166 {
3167 // Get the midpoint
3168 CVec middle = CVec((pos.x+trg.x)/2,(pos.y+trg.y)/2);
3169
3170 float a = CalculateDistance(middle,pos);
3171 float b = a;
3172 float step = DEG2RAD(20);
3173 int min = 99999999;
3174 CVec result = CVec(0,0);
3175
3176 // We make a circle and check from the points the way with least rock
3177 float i;
3178 float x,y;
3179 for (;b > 10.0f; b-=b/2)
3180 for (i=0;i<2*PI; i+=step) {
3181 x = a*sinf(2*PI*i)+middle.x;
3182 y = b*cosf(2*PI*i)+middle.y;
3183 CVec point = CVec(x,y);
3184 if (cClient->getMap()->GetPixelFlag( (int)pos.x, (int)pos.y ) & PX_ROCK)
3185 continue;
3186
3187 int rock_pixels = GetRockBetween(point,pos,cClient->getMap())+GetRockBetween(point,trg,cClient->getMap());
3188 if (rock_pixels < min) {
3189 min = rock_pixels;
3190 result.x=(point.x);
3191 result.y=(point.y);
3192
3193 if (!min)
3194 return result;
3195 }
3196 }
3197
3198 return result;
3199 }*/
3200
3201
AI_FindBestFreeSpot(CVec vPoint,CVec vStart,CVec vDirection,CVec vTarget,CVec * vEndPoint)3202 CVec CWormBotInputHandler::AI_FindBestFreeSpot(CVec vPoint, CVec vStart, CVec vDirection, CVec vTarget, CVec* vEndPoint) {
3203
3204 /*
3205 TODO: the algo can made a bit more general, which would increase the finding
3206 */
3207
3208 #ifdef _AI_DEBUG
3209 //SmartPointer<SDL_Surface> bmpDest = cClient->getMap()->GetDebugImage();
3210 #endif
3211
3212 unsigned short i = 0;
3213 int map_w = cClient->getMap()->GetWidth();
3214 int map_h = cClient->getMap()->GetHeight();
3215 const uchar* pxflags = cClient->getMap()->GetPixelFlags();
3216 CVec pos = vStart;
3217 CVec best = vStart;
3218 CVec end = pos;
3219 CVec possible_end;
3220 CVec backdir = vStart - vTarget;
3221 backdir = backdir / backdir.GetLength();
3222 end -= backdir*6;
3223 float best_length = -1; // the higher it is, the better it is
3224 vDirection = vDirection / vDirection.GetLength();
3225 bool lastWasObstacle = false;
3226 bool lastWasMissingCon = false;
3227 while(true) {
3228 #ifdef _AI_DEBUG
3229 //PutPixel(bmpDest,(int)pos.x*2,(int)pos.y*2,Color(255,255,0));
3230 #endif
3231
3232 if(!lastWasObstacle && !lastWasMissingCon) pos += vDirection;
3233 i++;
3234
3235 // make the search-dist greater, if we are far away
3236 if(i >= 32 && i % 16 == 0) {
3237 backdir = backdir * 2;
3238 vDirection = vDirection * 2;
3239 }
3240
3241 // don't search to wide
3242 if(i > 100)
3243 break;
3244
3245 // Clipping
3246 if( (int)pos.x < 0 || (int)pos.x >= map_w
3247 || (int)pos.y < 0 || (int)pos.y >= map_h )
3248 break;
3249
3250 // obstacle...
3251 if(PX_ROCK & pxflags[(int)pos.x + map_w*(int)pos.y]) {
3252 pos += backdir;
3253 lastWasObstacle = true;
3254 continue;
3255 } else
3256 lastWasObstacle = false;
3257
3258
3259 if(i % 4 == 0 || lastWasMissingCon) {
3260 // do we still have a direct connection to the point?
3261 if(!traceWormLine(vPoint,pos)) {
3262 // perhaps we are behind an edge (because auf backdir)
3263 // then go a little more to backdir
3264 pos += backdir;
3265 lastWasMissingCon = true;
3266 continue;
3267 } else
3268 lastWasMissingCon = false;
3269 }
3270
3271 // don't check to often
3272 if(i % 4 == 0) {
3273 // this is the parallel to backdir
3274 traceWormLine(pos-backdir*1000,pos,&possible_end);
3275 possible_end += backdir*5/backdir.GetLength();
3276 #ifdef _AI_DEBUG
3277 //PutPixel(bmpDest,(int)possible_end.x*2,(int)possible_end.y*2,tLX->clPink);
3278 #endif
3279 // 'best' is, if we have much free way infront of pos
3280 // and if we are not so far away from the start
3281 if((possible_end-pos).GetLength2()/(pos-vPoint).GetLength2() > best_length) {
3282 end = possible_end;
3283 best = pos;
3284 best_length = (possible_end-pos).GetLength2()/(pos-vPoint).GetLength2();
3285 }
3286 }
3287 }
3288
3289 if(vEndPoint)
3290 *vEndPoint = end;
3291
3292 return best;
3293 }
3294
3295 //////////////////
3296 // Finds the closest free spot, looking only in one direction
AI_FindClosestFreeSpotDir(CVec vPoint,CVec vDirection,int Direction=-1)3297 CVec CWormBotInputHandler::AI_FindClosestFreeSpotDir(CVec vPoint, CVec vDirection, int Direction = -1)
3298 {
3299 #ifdef _AI_DEBUG
3300 // SmartPointer<SDL_Surface> & bmpDest = cClient->getMap()->GetDebugImage();
3301 #endif
3302
3303 NormalizeVector(&vDirection);
3304 //CVec vDev = CVec(vDirection.x*5,vDirection.y*5);
3305 CVec vDev = CVec(0,0);
3306
3307 int i;
3308 int emptyPixels = 0;
3309 CVec pos = vPoint+vDev;
3310 int firstClosest = 9999;
3311 int secondClosest = 9999;
3312 CVec rememberPos1 = CVec(0,0);
3313 CVec rememberPos2 = CVec(0,0);
3314
3315 for(i=0; 1; i++) {
3316 uchar px = cClient->getMap()->GetPixelFlag( (int)pos.x, (int)pos.y );
3317
3318 // Empty pixel? Add it to the count
3319 if(!(px & PX_ROCK)) {
3320 #ifdef _AI_DEBUG
3321 //PutPixel(bmpDest,pos.x*2,pos.y*2,Color(255,255,0));
3322 #endif
3323 emptyPixels++;
3324 }
3325 // Rock pixel? This spot isn't good
3326 else {
3327 #ifdef _AI_DEBUG
3328 //PutPixel(bmpDest,pos.x*2,pos.y*2,Color(255,0,0));
3329 #endif
3330 if (emptyPixels >= 10)
3331 break;
3332 emptyPixels = 0;
3333 rememberPos1.x=(0);
3334 rememberPos1.y=(0);
3335 }
3336
3337 // Good spot
3338 if (emptyPixels >= 10) {
3339 firstClosest = i-emptyPixels;
3340 if (emptyPixels >= 30)
3341 break;
3342 rememberPos1.x=(rememberPos1.x+vDirection.x);
3343 rememberPos1.y=(rememberPos1.y+vDirection.y);
3344 }
3345
3346 if (emptyPixels == 5) {
3347 rememberPos1.x=(pos.x);
3348 rememberPos1.y=(pos.y);
3349 }
3350
3351 pos = pos + vDirection;
3352 // Clipping
3353 if (pos.x > cClient->getMap()->GetWidth() || pos.x < 0)
3354 break;
3355 if (pos.y > cClient->getMap()->GetHeight() || pos.y < 0)
3356 break;
3357 }
3358
3359 if (firstClosest != 9999 && Direction == DIR_LEFT)
3360 return rememberPos1;
3361
3362 emptyPixels = 0;
3363 vDirection.y=(-vDirection.y);
3364 vDirection.x=(-vDirection.x);
3365 vDev.x=(-vDev.x);
3366 vDev.y=(-vDev.y);
3367 pos = vPoint+vDev;
3368
3369 for(i=0; 1; i++) {
3370 uchar px = cClient->getMap()->GetPixelFlag( (int)pos.x, (int)pos.y );
3371
3372 // Empty pixel? Add it to the count
3373 if(!(px & PX_ROCK)) {
3374 #ifdef _AI_DEBUG
3375 //PutPixel(bmpDest,pos.x*2,pos.y*2,Color(255,255,0));
3376 #endif
3377 emptyPixels++;
3378 }
3379 // Rock pixel? This spot isn't good
3380 else {
3381 #ifdef _AI_DEBUG
3382 //PutPixel(bmpDest,pos.x*2,pos.y*2,Color(255,0,0));
3383 #endif
3384 if (emptyPixels >= 10)
3385 break;
3386
3387 rememberPos2.x=(0);
3388 rememberPos2.y=(0);
3389 emptyPixels = 0;
3390 }
3391
3392 // Good spot
3393 if (emptyPixels > 10) {
3394 secondClosest = i-emptyPixels;
3395 if (emptyPixels >= 30)
3396 break;
3397 rememberPos2.x=(rememberPos2.x+vDirection.x);
3398 rememberPos2.y=(rememberPos2.y+vDirection.y);
3399 }
3400
3401 // Remember this special position (in the middle of possible free spot)
3402 if (emptyPixels == 5) {
3403 rememberPos2.x=(pos.x);
3404 rememberPos2.y=(pos.y);
3405 }
3406
3407 pos = pos + vDirection;
3408 // Clipping
3409 if (pos.x > cClient->getMap()->GetWidth() || pos.x < 0)
3410 break;
3411 if (pos.y > cClient->getMap()->GetHeight() || pos.y < 0)
3412 break;
3413 }
3414
3415 if (secondClosest != 9999 && Direction == DIR_RIGHT)
3416 return rememberPos2;
3417
3418 // In what direction was the closest spot?
3419 if (firstClosest < secondClosest)
3420 return rememberPos1;
3421 else
3422 return rememberPos2;
3423 }
3424
3425
3426 #ifdef _AI_DEBUG
3427 ///////////////////
3428 // Draw the AI path
AI_DrawPath()3429 void CWormBotInputHandler::AI_DrawPath()
3430 {
3431 if (!NEW_psPath)
3432 return;
3433
3434 SmartPointer<SDL_Surface> bmpDest = cClient->getMap()->GetDebugImage();
3435 if (!bmpDest.get())
3436 return;
3437
3438 const Color NodeColour = m_worm->cSkin.getColor();
3439 const Color HighColour = Color(255, 0, 0);
3440 const Color LineColour = tLX->clWhite;
3441
3442 // Go down the path
3443 NEW_ai_node_t *node = NEW_psCurrentNode;
3444 int node_x = 0;
3445 int node_y = 0;
3446 for (;node;node=node->psNext) {
3447
3448 // Get the node position
3449 node_x = Round(node->fX*2);
3450 node_y = Round(node->fY*2);
3451
3452 // Clipping
3453 if (node_x-4 < 0 || node_x+4 > bmpDest.get()->w)
3454 continue;
3455 if (node_y-4 < 0 || node_y+4 > bmpDest.get()->h)
3456 continue;
3457
3458 // Draw the node
3459 if (node == NEW_psCurrentNode)
3460 DrawRectFill(bmpDest.get(),node_x-4,node_y-4,node_x+4,node_y+4,HighColour);
3461 else
3462 DrawRectFill(bmpDest.get(),node_x-4,node_y-4,node_x+4,node_y+4,NodeColour);
3463
3464 // Draw the line
3465 if (node->psNext)
3466 DrawLine(bmpDest.get(),MIN(Round(node->psNext->fX*2),bmpDest.get()->w),MIN(Round(node->psNext->fY*2),bmpDest.get()->h),node_x,node_y,LineColour);
3467 }
3468
3469 }
3470 #endif
3471
3472
3473 class bestropespot_collision_action {
3474 public:
3475 CWorm* worm;
3476 CVec aimDir;
3477 CVec target, best;
3478 float best_value;
3479
bestropespot_collision_action(CWorm * w,CVec t)3480 bestropespot_collision_action(CWorm* w, CVec t) : worm(w), target(t), best_value(-1) {
3481 target.y -= 30.0f; // a bit higher is always better
3482 aimDir = worm->getFaceDirection();
3483 }
3484
operator ()(int x,int y)3485 bool operator()(int x, int y) {
3486
3487 CVec suggestion((float)x, (float)y);
3488
3489 float len = (worm->getPos() - suggestion).GetLength();
3490 if(len < 30.0f) // just ignore too close spots
3491 return false;
3492
3493 if(worm->getPos().y > target.y && (y > worm->getPos().y - 10))
3494 // suggestion is lower than worm but we want to get higher -> ignore
3495 return false;
3496
3497 float trg_dist = (suggestion - target).GetLength();
3498 trg_dist = (trg_dist != 0) ? (1.0f / trg_dist) : 999999999999999.0f; // the closer we are the better it is
3499 float angle_dif = (aimDir - suggestion / suggestion.GetLength()).GetLength();
3500 angle_dif = (angle_dif != 0) ? (1.0f / angle_dif) : 999999999999999.0f; // the closer the angle the better it is
3501
3502 len = -len * (len - 200.0f); // 0 is bad and everything behind 100.0f also
3503 if(len < 0) len = 0.0f;
3504
3505 // HINT: these constant multiplicators are the critical values in the calculation
3506 float value = (float)(trg_dist * 100000.0f + angle_dif * 10000.0f + len * 0.001f);
3507 //printf("value: %f, %f, %f, %f\n", trg_dist, angle_dif, len, value);
3508
3509 // FIX: if we want to go up, then ignore angle and len
3510 if(worm->getPos().y - target.y > 40.0f)
3511 value = trg_dist;
3512
3513 if(best_value < value) {
3514 best_value = value;
3515 best = CVec((float)x, (float)y);
3516 }
3517
3518 return false;
3519 }
3520 };
3521
3522 /////////////////////////
3523 // Finds the best spot to shoot rope to if we want to get to trg
AI_GetBestRopeSpot(CVec trg)3524 CVec CWormBotInputHandler::AI_GetBestRopeSpot(CVec trg)
3525 {
3526
3527
3528 // Get the direction angle
3529 CVec dir = trg - m_worm->vPos;
3530 dir *= m_worm->cNinjaRope.getMaxLength() / dir.GetLength();
3531 dir = CVec(-dir.y, dir.x); // rotate reverse-clockwise by 90 deg
3532
3533 // Variables
3534 float step = 0.05f*(float)PI;
3535 float ang = 0;
3536
3537 SquareMatrix<float> step_m = SquareMatrix<float>::RotateMatrix(-step);
3538 bestropespot_collision_action action(m_worm, trg);
3539
3540 for(ang=0; ang<(float)PI; dir=step_m(dir), ang+=step) {
3541 fastTraceLine(m_worm->vPos+dir, m_worm->vPos, PX_ROCK|PX_DIRT, action);
3542 }
3543
3544 if(action.best_value < 0) // we don't find any spot
3545 return trg;
3546 else
3547 return action.best;
3548 }
3549
3550 ////////////////////
3551 // Finds the nearest spot to the target, where the rope can be hooked
AI_GetNearestRopeSpot(CVec trg)3552 CVec CWormBotInputHandler::AI_GetNearestRopeSpot(CVec trg)
3553 {
3554 CVec dir = trg-m_worm->vPos;
3555 NormalizeVector(&dir);
3556 dir = dir*10;
3557 float restlen2 = m_worm->cNinjaRope.getRestLength();
3558 restlen2 *= restlen2;
3559 while ((m_worm->vPos-trg).GetLength2() >= restlen2)
3560 trg = trg-dir;
3561
3562 //
3563 // Find the nearest cell with rock or dirt
3564 //
3565
3566 // Get the current cell
3567 uchar tmp_pf = PX_ROCK;
3568 int cellX = (int) (trg.x)/cClient->getMap()->getGridWidth();
3569 int cellY = (int) (trg.y)/cClient->getMap()->getGridHeight();
3570
3571 // Clipping means rock
3572 if (cellX > cClient->getMap()->getGridCols() || cellX < 0)
3573 return trg;
3574 if (cellY > cClient->getMap()->getGridRows() || cellY < 0)
3575 return trg;
3576
3577 // Check the current cell first
3578 tmp_pf = *(cClient->getMap()->getGridFlags() + cellY*cClient->getMap()->getGridCols() +cellX);
3579 if ((tmp_pf & PX_ROCK) || (tmp_pf & PX_DIRT))
3580 return trg;
3581
3582 // TODO: Note: unoptimized
3583
3584 int i=1;
3585 int x=0,y=0;
3586 bool bFound = false;
3587 while (!bFound) {
3588 for (y=cellY-i;y<=cellY+i;y++) {
3589
3590 // Clipping means rock
3591 if (y > cClient->getMap()->getGridRows()) {
3592 bFound = true;
3593 break;
3594 }
3595 if (y < 0) {
3596 bFound = true;
3597 break;
3598 }
3599
3600
3601 for (x=cellX-i;x<=cellX+i;x++) {
3602 // Don't check the entry cell
3603 if (x == cellX && y == cellY)
3604 continue;
3605
3606 // Clipping means rock
3607 if (x > cClient->getMap()->getGridCols()) {
3608 bFound = true;
3609 break;
3610 }
3611 if (x < 0) {
3612 bFound = true;
3613 break;
3614 }
3615
3616 // Get the pixel flag of the cell
3617 tmp_pf = *(cClient->getMap()->getGridFlags() + y*cClient->getMap()->getGridCols() +x);
3618 if ((tmp_pf & PX_ROCK) || (tmp_pf & PX_DIRT)) {
3619 bFound = true;
3620 break;
3621 }
3622 }
3623 }
3624 i++;
3625 }
3626
3627 return CVec((float)x*cClient->getMap()->getGridWidth()+cClient->getMap()->getGridWidth()/2, (float)y*cClient->getMap()->getGridHeight()+cClient->getMap()->getGridHeight()/2);
3628
3629 }
3630
3631 ///////////////
3632 // Returns true if the point has the specified amount of air around itself
3633 // HINT: not always the opposite to CheckOnGround because it checks also left/right/top side
3634 // At least area_a cells around have to be free for this function to return true
AI_IsInAir(CVec pos,int area_a)3635 bool CWormBotInputHandler::AI_IsInAir(CVec pos, int area_a)
3636 {
3637 if(pos.x < 0 || pos.y < 0 || pos.x >= cClient->getMap()->GetWidth() || pos.y >= cClient->getMap()->GetHeight())
3638 return false;
3639
3640 // Get the current cell
3641 uchar tmp_pf = PX_ROCK;
3642 int startX = (int) (pos.x)/cClient->getMap()->getGridWidth()-(int)floor((double)area_a/2);
3643 int startY = (int) (pos.y)/cClient->getMap()->getGridHeight()-(int)floor((double)area_a/2);
3644
3645 // Clipping means rock
3646 if (startX < 0 || startY < 0)
3647 return false;
3648
3649 int x,y,i;
3650 x=startX;
3651 y=startY;
3652
3653 cClient->getMap()->lockFlags();
3654 for (i=0;i<area_a*area_a;i++) {
3655
3656 // Clipping means rock
3657 if (x >= cClient->getMap()->getGridCols()) {
3658 cClient->getMap()->unlockFlags();
3659 return false;
3660 }
3661
3662 if (x > area_a) {
3663 x = startX;
3664 y++;
3665
3666 // Clipping means rock
3667 if (y >= cClient->getMap()->getGridRows()) {
3668 cClient->getMap()->unlockFlags();
3669 return false;
3670 }
3671 }
3672
3673 // Rock or dirt - not in air
3674 tmp_pf = *(cClient->getMap()->getGridFlags() + y*cClient->getMap()->getGridCols() +x);
3675 if(tmp_pf & (PX_ROCK|PX_DIRT)) {
3676 cClient->getMap()->unlockFlags();
3677 return false;
3678 }
3679
3680 x++;
3681 }
3682 cClient->getMap()->unlockFlags();
3683
3684 return true;
3685
3686 }
3687
3688
3689 ///////////////////
3690 // AI carving
AI_Carve()3691 void CWormBotInputHandler::AI_Carve()
3692 {
3693 // Don't carve too fast
3694 if (tLX->currentTime - m_worm->fLastCarve > 0.2f) {
3695 m_worm->fLastCarve = tLX->currentTime;
3696 m_worm->tState.bCarve = true; // Carve
3697 }
3698 else {
3699 m_worm->tState.bCarve = false;
3700 }
3701 }
3702
3703
estimateYDiffAfterJump(CWorm * w,float dt)3704 static float estimateYDiffAfterJump(CWorm* w, float dt) {
3705 const float jumpForce = w->getGameScript()->getWorm()->JumpForce;
3706 //const float drag = w->getGameScript()->getWorm()->AirFriction; // ignoring for now
3707 const float grav = w->getGameScript()->getWorm()->Gravity;
3708
3709 return grav*dt*dt*0.5f + jumpForce*dt;
3710 }
3711
isJumpingGivingDisadvantage(NEW_ai_node_t * node,CWorm * w)3712 static bool isJumpingGivingDisadvantage(NEW_ai_node_t* node, CWorm* w) {
3713 if(!node) return false;
3714
3715 float dy = estimateYDiffAfterJump(w, 0.3f);
3716 if(!traceWormLine(CVec(node->fX, node->fY), w->getPos() + CVec(0,dy)))
3717 return true;
3718
3719 return false;
3720 }
3721
estimateXDiffAfterMove(CWorm * w,float dt)3722 static float estimateXDiffAfterMove(CWorm* w, float dt) {
3723 const gs_worm_t *wd = w->getGameScript()->getWorm();
3724 worm_state_t *ws = w->getWormState();
3725 float speed = w->isOnGround() ? wd->GroundSpeed : wd->AirSpeed;
3726 if(ws->iFaceDirectionSide == DIR_LEFT) speed = -speed;
3727
3728 return CLAMP(w->getVelocity().x + speed * 90.0f, -30.0f, 30.0f) * dt;
3729 }
3730
isMoveGivingDisadvantage(CVec target,CWorm * w)3731 static bool isMoveGivingDisadvantage(CVec target, CWorm* w) {
3732 float dx = estimateXDiffAfterMove(w, 0.3f);
3733
3734 if(!traceWormLine(target, w->getPos() + CVec(dx,0)))
3735 return true;
3736
3737 return false;
3738 }
3739
3740 ///////////////////
3741 // AI jumping, returns true if we really jumped
AI_Jump()3742 bool CWormBotInputHandler::AI_Jump()
3743 {
3744 // Don't jump so often
3745 if ((GetTime() - fLastJump).seconds() > 0.3f && (m_worm->bOnGround || m_worm->canAirJump()) && !isJumpingGivingDisadvantage(NEW_psCurrentNode, m_worm)) {
3746 fLastJump = GetTime();
3747 m_worm->tState.bJump = true;
3748 }
3749 // TODO: why this? we should have reset it anyway. and multiple calls to AI_Jump should not make it false again
3750 /*else {
3751 m_worm->tState.bJump = false;
3752 }*/
3753
3754 return m_worm->tState.bJump;
3755 }
3756
3757 /////////////////////
3758 // Move to the target
AI_MoveToTarget()3759 void CWormBotInputHandler::AI_MoveToTarget()
3760 {
3761 // printf("Moving to target");
3762
3763 worm_state_t *ws = &m_worm->tState;
3764
3765 // No target?
3766 if (nAITargetType == AIT_NONE || (nAITargetType == AIT_WORM && !psAITarget)) {
3767 nAIState = AI_THINK;
3768 return;
3769 }
3770
3771 if(cClient->getGameLobby()->gameMode == GameMode(GM_RACE) || cClient->getGameLobby()->gameMode == GameMode(GM_TEAMRACE)) {
3772 int t = m_worm->getID();
3773 if(cClient->isTeamGame()) t = m_worm->getTeam();
3774
3775 Flag* flag = cClient->flagInfo()->getFlag(t);
3776 if(!flag) { // strange
3777 nAIState = AI_THINK;
3778 return;
3779 }
3780
3781 if((cPosTarget - flag->spawnPoint.pos).GetLength2() > 10.0f) {
3782 nAIState = AI_THINK;
3783 return;
3784 }
3785 }
3786
3787 // Clear the state
3788 ws->bMove = false;
3789 ws->bShoot = false;
3790 ws->bJump = false;
3791
3792 // If the rope hook is attached, increase the attached time
3793 if (m_worm->cNinjaRope.isAttached())
3794 fRopeAttachedTime += tLX->fDeltaTime;
3795 else
3796 fRopeAttachedTime = 0;
3797
3798 // release the rope if we used it to long
3799 if (fRopeAttachedTime > 5.0f) {
3800 m_worm->cNinjaRope.Release();
3801 fRopeAttachedTime = 0;
3802 }
3803
3804 if(m_worm->cNinjaRope.isShooting() && !m_worm->cNinjaRope.isAttached())
3805 fRopeHookFallingTime += tLX->fDeltaTime;
3806 else
3807 fRopeHookFallingTime = 0;
3808
3809 if (fRopeHookFallingTime >= 2.0f) {
3810 // Release & walk
3811 m_worm->cNinjaRope.Release();
3812 // ws->bMove = true;
3813 fRopeHookFallingTime = 0;
3814 }
3815
3816 cPosTarget = AI_GetTargetPos();
3817 if (nAITargetType == AIT_WORM && psAITarget)
3818 cPosTarget = AI_FindShootingSpot();
3819
3820 bool canShoot = true;
3821 if(cClient->getGameLobby()->gameMode && !cClient->getGameLobby()->gameMode->Shoot(m_worm))
3822 canShoot = false;
3823
3824 // If we just shot some mortars, release the rope if it pushes us in the direction of the shots
3825 // and move away!
3826 if (canShoot && iAiGameType == GAM_MORTARS) {
3827 if (SIGN(m_worm->vVelocity.x) == SIGN(vLastShootTargetPos.x - m_worm->vPos.x) && tLX->currentTime - fLastShoot >= 0.2f && tLX->currentTime - fLastShoot <= 1.0f) {
3828 if (m_worm->cNinjaRope.isAttached() && SIGN(m_worm->cNinjaRope.GetForce(m_worm->vPos).x) == SIGN(vLastShootTargetPos.x - m_worm->vPos.x))
3829 m_worm->cNinjaRope.Release();
3830
3831 m_worm->iFaceDirectionSide = m_worm->vPos.x < vLastShootTargetPos.x ? DIR_LEFT : DIR_RIGHT;
3832 ws->bMove = true;
3833 }
3834 }
3835
3836 if (canShoot && (iAiGameType == GAM_MORTARS || iAiGameType == GAM_100LT)) {
3837 // If there's some worm in sight and we are on ground, jump!
3838 if (m_worm->bOnGround) {
3839 for (int i = 0; i < MAX_WORMS; i++) {
3840 CWorm *w = &cClient->getRemoteWorms()[i];
3841 if (w->isUsed() && w->getAlive() && w->getID() != m_worm->iID) {
3842 if ((m_worm->vPos - w->getPos()).GetLength2() <= 2500) {
3843 float dist;
3844 int type;
3845 m_worm->traceLine(w->getPos(), &dist, &type);
3846 if (type & PX_EMPTY)
3847 AI_Jump();
3848 }
3849 }
3850 }
3851 }
3852 }
3853
3854 // Don't do anything crazy a while after shooting
3855 if (canShoot && iAiGameType == GAM_MORTARS) {
3856 if (tLX->currentTime - fLastShoot <= 1.0f)
3857 return;
3858 }
3859
3860 // If we're stuck, just get out of wherever we are
3861 if(bStuck) {
3862 // printf("Stucked");
3863
3864 ws->bMove = true;
3865 AI_Jump();
3866
3867 if (tLX->currentTime-fLastFace >= 0.5f) {
3868 m_worm->iFaceDirectionSide = OppositeDir(m_worm->iFaceDirectionSide);
3869 fLastFace = tLX->currentTime;
3870 }
3871
3872 if(tLX->currentTime - fStuckPause > 2.0f)
3873 bStuck = false;
3874
3875 // return here, because we force this stuck-action
3876 // if we would not return here, the stuck-check makes no sense
3877 return;
3878 }
3879
3880 bool fireNinja = false;
3881
3882 /*
3883 Prevent injuries! If any of the projectiles around is heading to us, try to get away from it
3884 */
3885 // TODO: this doesn't work that good atm; so it's better to ignore it at all than to go away in a situation where shooting would be better
3886 #if 0
3887 if (false) {
3888 // TODO: improve this
3889
3890 // just temp here as I removed it globally, we should try to get a collection of projectiles here automatically (not only one)
3891 CProjectile* psHeadingProjectile = NULL;
3892
3893 // Go away from the projectile
3894 if (tLX->currentTime-fLastFace >= 0.5f) {
3895 if (psHeadingProjectile->getVelocity().x > 0)
3896 m_worm->iFaceDirectionSide = DIR_LEFT; // Move in the opposite direction
3897 else
3898 m_worm->iFaceDirectionSide = DIR_RIGHT;
3899 fLastFace = tLX->currentTime;
3900 }
3901 ws->bMove = true;
3902
3903
3904 // If we're on ground, jump
3905 AI_Jump();
3906
3907 // Release any previous rope
3908 if (fRopeAttachedTime >= 1.5f)
3909 m_worm->cNinjaRope.Release();
3910
3911 // Shoot the rope
3912 fireNinja = true;
3913
3914 // We want to move away
3915 CVec desired_dir = -psHeadingProjectile->getVelocity();
3916
3917 // Choose some point and find the best rope spot to it
3918 desired_dir = desired_dir.Normalize() * 40.0f;
3919 CVec cAimPos = AI_GetBestRopeSpot(m_worm->vPos+desired_dir);
3920
3921 // Aim it
3922 /*
3923 // TODO: why isn't this used any more?
3924 fireNinja = AI_SetAim(cAimPos);
3925
3926 if (fireNinja)
3927 fireNinja = psHeadingProjectile->getVelocity().GetLength() > 50.0f;
3928
3929 if (fireNinja) {
3930
3931 // Get the direction
3932 CVec dir;
3933 dir.x=( cosf(fAngle * (PI/180)) );
3934 dir.y=( sinf(fAngle * (PI/180)) );
3935 if(iFaceDirectionSide == DIR_LEFT)
3936 dir.x=(-dir.x);
3937
3938 // Shoot it
3939 cNinjaRope.Shoot(vPos,dir);
3940 }*/
3941
3942 return;
3943 }
3944 #endif
3945 // TODO: in general, move away from projectiles
3946
3947 // prevent suicides
3948 if (canShoot && iAiGameType == GAM_MORTARS) {
3949 if (tLX->currentTime - fLastShoot <= 0.2f) {
3950 if (fRopeAttachedTime >= 0.1f)
3951 m_worm->cNinjaRope.Release();
3952 return;
3953 }
3954 }
3955
3956
3957 /*
3958 Move through the path.
3959 We have a current node that we must get to. If we go onto the node, we go to the next node, and so on.
3960 */
3961
3962 //return; // Uncomment this when you don't want the AI to move
3963
3964 if(NEW_psPath == NULL || NEW_psCurrentNode == NULL) {
3965 nAIState = AI_THINK;
3966 // If we don't have a path, resort to simpler AI methods
3967 AI_SimpleMove(psAITarget != NULL);
3968 //printf("Pathfinding problem 2; ");
3969 return;
3970 }
3971
3972 // printf("We should move now...");
3973
3974 if ((CVec(NEW_psCurrentNode->fX, NEW_psCurrentNode->fY) - m_worm->vPos).GetLength2() <= 100) {
3975 if (NEW_psCurrentNode->psNext)
3976 NEW_psCurrentNode = NEW_psCurrentNode->psNext;
3977 }
3978
3979 // If some of the next nodes is closer than the current one, just skip to it
3980 NEW_ai_node_t *next_node = NEW_psCurrentNode->psNext;
3981 bool newnode = false;
3982 while(next_node) {
3983 if(traceWormLine(CVec(next_node->fX, next_node->fY), m_worm->vPos)) {
3984 NEW_psCurrentNode = next_node;
3985 newnode = true;
3986 }
3987 next_node = next_node->psNext;
3988 }
3989 if(!newnode) {
3990 // check, if we have a direct connection to the current node
3991 // else, choose some last node
3992 // this will work and is in many cases the last chance
3993 if (tLX->currentTime - fLastGoBack >= 1) {
3994 for(next_node = NEW_psCurrentNode; next_node; next_node = next_node->psPrev) {
3995 if(traceWormLine(CVec(next_node->fX, next_node->fY), m_worm->vPos)) {
3996 if(NEW_psCurrentNode != next_node) {
3997 NEW_psCurrentNode = next_node;
3998 fLastGoBack = tLX->currentTime;
3999 newnode = true;
4000 }
4001 goto find_one_visible_node;
4002 }
4003 }
4004 }
4005
4006 // we currently have no visible node
4007 if (!NEW_psCurrentNode) {
4008 //notes << "AI: no current node" << endl;
4009 nAIState = AI_THINK;
4010 AI_SimpleMove(psAITarget != NULL);
4011 return;
4012 }
4013 }
4014 find_one_visible_node:
4015
4016
4017 // Get the target node position
4018 CVec nodePos(NEW_psCurrentNode->fX, NEW_psCurrentNode->fY);
4019
4020
4021 // release rope, if it forces us to the wrong direction
4022 if(m_worm->cNinjaRope.isAttached() && (m_worm->cNinjaRope.GetForce(m_worm->vPos).Normalize() + m_worm->vPos - nodePos).GetLength2() > (m_worm->vPos - nodePos).GetLength2()) {
4023 m_worm->cNinjaRope.Release();
4024 fRopeAttachedTime = 0;
4025 }
4026
4027
4028 bool we_see_the_target;
4029 {
4030 float dist; int type;
4031 traceWeaponLine(cPosTarget, &dist, &type);
4032 we_see_the_target = (type & PX_EMPTY);
4033
4034 /*
4035 For rifle games: it's not clever when we go to the battle with non-reloaded weapons
4036 If we're close to the target (<= 3 nodes missing), stop and reload weapons if needed
4037
4038 This is an advanced check, so simply ignore it if we are "noobs"
4039 */
4040 if (canShoot && iAiGameType == GAM_RIFLES && iAiDiffLevel >=2) {
4041 int num_reloaded=0;
4042 int i;
4043 for (i=0;i<5;i++) {
4044 if (!m_worm->tWeapons[i].Reloading)
4045 num_reloaded++;
4046 }
4047
4048 if (num_reloaded <= 3) {
4049
4050 // If we see the target, fight instead of reloading!
4051 if (!we_see_the_target) {
4052 NEW_ai_node_t *node = NEW_psLastNode;
4053 for(i=0; node && node != NEW_psCurrentNode; node = node->psPrev, i++) {}
4054 if (NEW_psLastNode == NULL || i>=3) {
4055 // Reload weapons when we're far away from the target or if we don't have any path
4056 AI_ReloadWeapons();
4057 }
4058 if(NEW_psLastNode && (i>=3 && i<=5)) {
4059 // Stop, if we are not so far away
4060 if (fRopeAttachedTime >= 0.7f)
4061 m_worm->cNinjaRope.Release();
4062 return;
4063 }
4064 }
4065 }
4066 }
4067 }
4068
4069 /*
4070 Now that we've done all the boring stuff, our single job here is to reach the node.
4071 We have walking, jumping, move-through-air, and a ninja rope to help us.
4072 */
4073
4074 {
4075 /*
4076 If there is dirt between us and the next node, don't shoot a ninja rope
4077 Instead, carve
4078 */
4079
4080 float traceDist = -1; int type;
4081 int length = m_worm->traceLine(nodePos, &traceDist, &type); // HINT: this is only a line, not the whole worm
4082 // NOTE: this can return dirt, even if there's also rock between us two
4083 bool direct_traceLine_possible = (float)(length*length) > (nodePos-m_worm->vPos).GetLength2();
4084
4085 // If the node is right above us, use a carving weapon
4086 if ((fabs(nodePos.x - m_worm->vPos.x) <= 50) && (type & PX_DIRT))
4087 if (nodePos.y < m_worm->vPos.y) {
4088 int wpn;
4089 if(canShoot && (wpn = AI_FindClearingWeapon()) != -1) {
4090 m_worm->iCurrentWeapon = wpn;
4091 ws->bShoot = AI_SetAim(nodePos); // aim at the dirt and fire if aimed
4092 if(ws->bShoot) {
4093 // Don't do any crazy things when shooting
4094 ws->bMove = false;
4095 ws->bJump = false;
4096 }
4097 } else {
4098 AI_SetAim(nodePos);
4099 AI_Jump();
4100 fireNinja = true; // we have no other option
4101 }
4102 /* else
4103 AI_SimpleMove(cClient->getMap(),psAITarget != NULL); */ // no weapon found, so move around
4104 }
4105
4106 // HINT: atm, fireNinja is always false here
4107 // if there is dirt in the way (and close to us), then don't use ninja rope
4108 if(!fireNinja && !direct_traceLine_possible && (type & PX_DIRT) && length < 10) {
4109 // HINT: as we always carve, we don't need to do it here specially
4110
4111 // release rope, if it is atached and above
4112 // if(fRopeAttachedTime > 0.5f && cNinjaRope.getHookPos().y - 5.0f > nodePos.y)
4113 // cNinjaRope.Release();
4114
4115 // Jump, if the node is above us
4116 if (nodePos.y < m_worm->vPos.y && m_worm->vPos.y - nodePos.y >= 10 && m_worm->vPos.y - nodePos.y <= 30)
4117 AI_Jump();
4118
4119 // ws->bMove = true;
4120
4121 } else { // no dirt or something close, just some free way infront of us
4122 // use ninja rope in general, it's faster
4123 fireNinja = true;
4124
4125 // If there's no dirt around and we have jetpack in our arsenal, lets use it!
4126 for (short i=0;i<5;i++) {
4127 if (m_worm->tWeapons[i].Weapon && m_worm->tWeapons[i].Enabled && m_worm->tWeapons[i].Weapon->Recoil < 0 && !m_worm->tWeapons[i].Reloading) {
4128 m_worm->iCurrentWeapon = i;
4129 ws->bShoot = AI_SetAim(nodePos);
4130 if(ws->bShoot) fireNinja = false;
4131 break;
4132 }
4133 }
4134 }
4135 }
4136
4137 //
4138 // Shooting the rope
4139 //
4140
4141 // If the node is above us by a lot, we should use the ninja rope
4142 // If the node is far, jump and use the rope, too
4143 // TODO: the above comment doesn't match the code. why?
4144 /* if(fireNinja) {
4145 fireNinja = (NEW_psCurrentNode->fY+20 < vPos.y);
4146 if (!fireNinja && (fabs(NEW_psCurrentNode->fX-vPos.x) >= 50)) {
4147 // On ground? Jump
4148 AI_Jump();
4149 }
4150 }
4151 */
4152
4153 // If we're above the node and the rope is hooked wrong, release the rope
4154 /* if(bOnGround && (vPos.y < nodePos.y)) {
4155 cNinjaRope.Release();
4156 fireNinja = false;
4157 }
4158 */
4159
4160 // It has no sense to shoot the rope on short distances
4161 // TODO: it make sense, only not for realy short distances; i try it first now completly without this check
4162 // also, we shouldn't relay on ropespot here but better on nodePos; GetBestRopeSpot should get a good ropespot if nodePos is good
4163 // if(fireNinja && (vPos-ropespot).GetLength2() < 625.0f)
4164 // fireNinja = false;
4165
4166 // In rifle games: don't continue if we see the final target and are quite close to it
4167 // If we shot the rope, we wouldnt aim the target, which is the priority now
4168 if(canShoot && fireNinja && iAiGameType == GAM_RIFLES && we_see_the_target && (m_worm->vPos-cPosTarget).GetLength2() <= 3600.0f) {
4169 fireNinja = false;
4170 }
4171
4172 bool stillAimingRopeSpot = false;
4173 CVec ropespot;
4174 if(fireNinja) {
4175 // set it to false, only if we pass the following checks, set it to true again
4176 fireNinja = false;
4177
4178 // there could be multiple good ropespot and if we already aim at one then we should use this
4179 // AI_GetBestRopeSpot takes care therefore also of fAngle
4180 ropespot = AI_GetBestRopeSpot(nodePos);
4181
4182 // Aim
4183 bool aim = AI_SetAim(ropespot);
4184 if(!aim) stillAimingRopeSpot = true;
4185
4186 /*
4187 Got aim, so shoot a ninja rope
4188 also shoot, if we are not on ground
4189 We shoot a ninja rope if it isn't shot
4190 Or if it is, we make sure it has pulled us up and that it is attached
4191 */
4192 if(aim || !m_worm->bOnGround) {
4193 if(!m_worm->cNinjaRope.isReleased())
4194 fireNinja = true;
4195 else {
4196 if(m_worm->cNinjaRope.isAttached()) {
4197 //float restlen_sq = cNinjaRope.getRestLength(); restlen_sq *= restlen_sq;
4198 //if((vPos-cNinjaRope.getHookPos()).GetLength2() < restlen_sq && vVelocity.y<-10)
4199 if(fRopeAttachedTime > 0.5f)
4200 fireNinja = true;
4201 }
4202 }
4203 if( (m_worm->vPos.y - NEW_psCurrentNode->fY) > 10.0f) {
4204 AI_Jump();
4205 }
4206 }
4207 }
4208
4209 if(fireNinja) {
4210 const CVec dir = m_worm->getFaceDirection();
4211
4212 // the final shoot of the rope...
4213 m_worm->cNinjaRope.Shoot(m_worm, m_worm->vPos, dir);
4214 fRopeHookFallingTime = 0;
4215 fRopeAttachedTime = 0;
4216
4217 } else { // not fireNinja
4218
4219 // Aim at the node
4220 if(!we_see_the_target && !stillAimingRopeSpot) AI_SetAim(nodePos);
4221
4222 if(m_worm->canAirJump()) {
4223 if((m_worm->vPos.y > NEW_psCurrentNode->fY + 10) && fabs(m_worm->vPos.y - NEW_psCurrentNode->fY) > fabs(m_worm->vPos.x - NEW_psCurrentNode->fX))
4224 AI_Jump();
4225 ws->bMove = true;
4226 }
4227 // If the node is above us by a little, jump
4228 else if((m_worm->vPos.y-NEW_psCurrentNode->fY) <= 30 && (m_worm->vPos.y - NEW_psCurrentNode->fY) > 10) {
4229 if (!AI_Jump()) {
4230 ws->bMove = true; // if we should not jump, move
4231 }
4232 }
4233
4234 if(stillAimingRopeSpot && isMoveGivingDisadvantage(m_worm->getPos() + (ropespot - m_worm->getPos()) * 0.8f, m_worm)) {
4235 ws->bMove = false;
4236 return;
4237 }
4238 }
4239
4240 // If we are using the rope to fly up, it can happen, that we will fly through the node and continue in a wrong direction
4241 // To avoid this we check the velocity and if it is too high, we release the rope
4242 // When on ground rope does not make much sense mainly, but there are rare cases where it should stay like it is
4243 if (m_worm->cNinjaRope.isAttached() && !m_worm->bOnGround && (m_worm->cNinjaRope.getHookPos().y > m_worm->vPos.y) && (NEW_psCurrentNode->fY < m_worm->vPos.y)
4244 && fabs(m_worm->cNinjaRope.getHookPos().x - m_worm->vPos.x) <= 50 && m_worm->vVelocity.y <= 0) {
4245 CVec force;
4246
4247 // Air drag (Mainly to dampen the ninja rope)
4248 // float Drag = cGameScript->getWorm()->AirFriction; // TODO: not used
4249
4250 float dist = (CVec(NEW_psCurrentNode->fX, NEW_psCurrentNode->fY) - m_worm->vPos).GetLength();
4251 float time = sqrt(2*dist/(force.GetLength()));
4252 //float time2 = dist/vVelocity.GetLength();*/
4253 float diff = m_worm->vVelocity.y - (m_worm->cGameScript->getWorm()->Gravity * time);
4254 if (diff < 0)
4255 m_worm->cNinjaRope.Release();
4256 }
4257
4258
4259 // If we are stuck in the same position for a while, take measures to get out of being stuck
4260 if(fabs(cStuckPos.x - m_worm->vPos.x) < 5 && fabs(cStuckPos.y - m_worm->vPos.y) < 5) {
4261 fStuckTime += tLX->fDeltaTime;
4262
4263 // Have we been stuck for a few seconds?
4264 if(fStuckTime.seconds() > 3) {
4265 // Jump, move, carve, switch directions and release the ninja rope
4266 AI_Jump();
4267 ws->bMove = true;
4268 ws->bCarve = true;
4269
4270 /* AI_Carve(); */
4271
4272 bStuck = true;
4273 fStuckPause = tLX->currentTime;
4274
4275 m_worm->fAngle -= m_worm->cGameScript->getWorm()->AngleSpeed * tLX->fDeltaTime.seconds();
4276 // Clamp the angle
4277 m_worm->fAngle = MIN((float)60,m_worm->fAngle);
4278 m_worm->fAngle = MAX((float)-90,m_worm->fAngle);
4279
4280 // Stucked too long?
4281 if (fStuckTime >= 5.0f) {
4282 // Try the previous node
4283 if (NEW_psCurrentNode->psPrev)
4284 NEW_psCurrentNode = NEW_psCurrentNode->psPrev;
4285 fStuckTime = 0;
4286 }
4287
4288 // Recalculate the path
4289 // TODO: should we do this in a more general way somewhere other?
4290 AI_CreatePath();
4291
4292 return;
4293 }
4294 }
4295 else {
4296 bStuck = false;
4297 fStuckTime = 0;
4298 cStuckPos = m_worm->vPos;
4299
4300 }
4301
4302 if(canShoot)
4303 // only move if we are away from the next node
4304 ws->bMove = fabs(m_worm->vPos.x - NEW_psCurrentNode->fX) > 3.0f;
4305 else
4306 // always move, we cannot do something else
4307 ws->bMove = true;
4308
4309 /*
4310 // If the next node is above us by a little, jump too
4311 NEW_ai_node_t *nextNode = NEW_psCurrentNode->psNext;
4312 if (nextNode) {
4313 if ((vPos.y-nextNode->fY) <= 30 && (vPos.y-nextNode->fY) > 0)
4314 if (!AI_Jump())
4315 ws->bMove = true; // if we should not jump, move
4316 }
4317 */
4318
4319
4320
4321 /*
4322 // If we're above the node, let go of the rope and move towards to node
4323 if(NEW_psCurrentNode->fY >= vPos.y) {
4324 // Let go of any rope
4325 cNinjaRope.Release();
4326
4327 // Walk in the direction of the node
4328 ws->bMove = true;
4329 }
4330 */
4331
4332 }
4333
drawpoint(SDL_Surface * debug_surf,CVec point)4334 void drawpoint(SDL_Surface * debug_surf, CVec point)
4335 {
4336 DrawRectFill(debug_surf, (int)point.x * 2, (int)point.y * 2, (int)point.x * 2 + 4, (int)point.y * 2 + 4, Color(0, 255, 0));
4337 }
4338
4339 ///////////////////////
4340 // Returns coordinates of a point that is best for shooting the target
AI_FindShootingSpot()4341 CVec CWormBotInputHandler::AI_FindShootingSpot()
4342 {
4343 if (psAITarget == NULL)
4344 return CVec(0,0);
4345
4346 // If the worm is not on ground, we cannot hit him with a napalm-like weapon (napalm, blaster etc.)
4347 bool have_straight = false;
4348 bool have_falling = false;
4349
4350 // Check what weapons we have
4351 for (int i=0; i < 5; i++) {
4352 if (m_worm->tWeapons[i].Weapon && m_worm->tWeapons[i].Weapon->Proj.Proj) {
4353 // Get the gravity
4354 int gravity = 100; // Default
4355 if (m_worm->tWeapons[i].Weapon->Proj.Proj->UseCustomGravity)
4356 gravity = m_worm->tWeapons[i].Weapon->Proj.Proj->Gravity;
4357
4358 // Change the flags according to the gravity
4359 if (gravity >= 5)
4360 have_falling = true;
4361 else if (gravity >= -5)
4362 have_straight = true;
4363 }
4364 }
4365
4366 float upper_bound = 0;
4367 float lower_bound = 0;
4368
4369 // Falling projectiles, we have to get above a bit
4370 if (have_falling || (have_straight && psAITarget->CheckOnGround())) {
4371 lower_bound = (float)PI/2;
4372 upper_bound = 3 * (float)PI/2;
4373
4374 // Flying straight
4375 } else if (have_straight) {
4376 lower_bound = 0;
4377 upper_bound = 2 * (float)PI;
4378
4379 // Flying up
4380 } else {
4381 lower_bound = (float)-PI/2;
4382 upper_bound = (float)PI/2;
4383 }
4384
4385
4386 // Check a best-distance upper half circle around the target
4387 CVec possible_pos;
4388 for (float j=lower_bound; j <= upper_bound; j += 0.3f) {
4389 possible_pos.x = 40.0f * sinf(j) + psAITarget->getPos().x;
4390 possible_pos.y = 40.0f * cosf(j) + psAITarget->getPos().y;
4391 //PutPixel(cClient->getMap()->GetDebugImage(), possible_pos.x * 2, possible_pos.y * 2, Color(255, 0, 0));
4392
4393 if (AI_IsInAir(possible_pos, 1) && m_worm->traceLine(possible_pos, psAITarget->getPos(), NULL) >= 40) {
4394 //drawpoint(cClient->getMap()->GetDebugImage(), possible_pos);
4395 return possible_pos;
4396 }
4397 }
4398
4399 // Let's try a bit worse pos - in the level of the target, but a bit distant from it
4400 possible_pos = psAITarget->getPos();
4401 for (int i = 0; i < 10; i++) {
4402 possible_pos.x += 5;
4403 if (!traceWormLine(possible_pos, psAITarget->getPos(), NULL)) {
4404 possible_pos.x -= 5;
4405 if (fabs(psAITarget->getPos().x - possible_pos.x) >= 15)
4406 return possible_pos;
4407 }
4408 }
4409
4410 possible_pos = psAITarget->getPos();
4411 for (int i = 0; i < 10; i++) {
4412 possible_pos.x -= 5;
4413 if (!traceWormLine(possible_pos, psAITarget->getPos(), NULL)) {
4414 possible_pos.x -= 5;
4415 if (fabs(psAITarget->getPos().x - possible_pos.x) >= 15)
4416 return possible_pos;
4417 }
4418 }
4419
4420 // Not found
4421 return possible_pos;
4422 }
4423
get_last_ai_node(NEW_ai_node_t * n)4424 NEW_ai_node_t* get_last_ai_node(NEW_ai_node_t* n) {
4425 if(!n) return NULL;
4426 for(;n->psNext;n=n->psNext) {}
4427 return n;
4428 }
4429
delete_ai_nodes(NEW_ai_node_t * start)4430 void delete_ai_nodes(NEW_ai_node_t* start) {
4431 if(!start) return;
4432 delete_ai_nodes(start->psNext);
4433 delete start;
4434 }
4435
delete_ai_nodes(NEW_ai_node_t * start,NEW_ai_node_t * end)4436 void delete_ai_nodes(NEW_ai_node_t* start, NEW_ai_node_t* end) {
4437 if(!start || start == end) return;
4438 delete_ai_nodes(start->psNext, end);
4439 delete start;
4440 }
4441
get_ai_nodes_length(NEW_ai_node_t * start)4442 float get_ai_nodes_length(NEW_ai_node_t* start) {
4443 float l,dx,dy;
4444 for(l=0;start;start=start->psNext) {
4445 if(start->psNext) {
4446 dx = start->fX - start->psNext->fX;
4447 dy = start->fY - start->psNext->fY;
4448 l += sqrt(dx*dx + dy*dy);
4449 } else
4450 break;
4451 }
4452 return l;
4453 }
4454
get_ai_nodes_length2(NEW_ai_node_t * start)4455 float get_ai_nodes_length2(NEW_ai_node_t* start) {
4456 float l,dx,dy;
4457 for(l=0;start;start=start->psNext) {
4458 if(start->psNext) {
4459 dx = start->fX - start->psNext->fX;
4460 dy = start->fY - start->psNext->fY;
4461 l += dx*dx + dy*dy;
4462 } else
4463 break;
4464 }
4465 return l;
4466 }
4467
4468
4469
4470 struct BotWormType : WormType {
createInputHandlerBotWormType4471 CWormInputHandler* createInputHandler(CWorm* w) { return new CWormBotInputHandler(w); }
toIntBotWormType4472 int toInt() { return 1; }
4473 } PRF_COMPUTER_instance;
4474 WormType* PRF_COMPUTER = &PRF_COMPUTER_instance;
4475
CWormBotInputHandler(CWorm * w)4476 CWormBotInputHandler::CWormBotInputHandler(CWorm* w) : CWormInputHandler(w) {
4477 nAIState = AI_THINK;
4478 //fLastWeaponSwitch = AbsTime();
4479 NEW_psPath = NULL;
4480 NEW_psCurrentNode = NULL;
4481 NEW_psLastNode = NULL;
4482 pathSearcher = NULL;
4483 fLastFace = 0;
4484 fBadAimTime = 0;
4485 psAITarget = NULL;
4486 fLastShoot = 0; // for AI
4487 fLastJump = AbsTime();
4488 fLastWeaponChange = 0;
4489 fLastCompleting = AbsTime();
4490 fLastGoBack = AbsTime();
4491
4492 if(w->tProfile)
4493 iAiDiffLevel = CLAMP(w->tProfile->nDifficulty, 0, 3);
4494 else
4495 iAiDiffLevel = AI_MEDIUM;
4496
4497 AI_Initialize();
4498 }
4499
~CWormBotInputHandler()4500 CWormBotInputHandler::~CWormBotInputHandler() {
4501 // Make sure the pathfinding ends
4502 AI_Shutdown();
4503 }
4504
onRespawn()4505 void CWormBotInputHandler::onRespawn() {
4506 nAIState = AI_THINK;
4507 fLastShoot = 0;
4508 fLastGoBack = AbsTime();
4509
4510 AI_Respawn();
4511 }
4512
4513
initWeaponSelection()4514 void CWormBotInputHandler::initWeaponSelection() {
4515
4516 // If this is an AI worm, lets give him a preset or random arsenal (but only with client side weapon selection)
4517
4518 // TODO: move this to CWorm_AI
4519 bool bRandomWeaps = true;
4520 // Combo (rifle)
4521 if ((cClient->getGameLobby()->iLoadingTime > 15 && cClient->getGameLobby()->iLoadingTime < 26) &&
4522 (cClient->getGameLobby()->sModName.find("Classic") != std::string::npos ||
4523 cClient->getGameLobby()->sModName.find("Liero v1.0") != std::string::npos )) {
4524 if (m_worm->cWeaponRest->isEnabled("Rifle")) {
4525 for (short i=0; i<5; i++)
4526 m_worm->tWeapons[i].Weapon = m_worm->cGameScript->FindWeapon("Rifle"); // set all weapons to Rifle
4527 bRandomWeaps = false;
4528 AI_SetGameType(GAM_RIFLES);
4529 }
4530 }
4531 // 100 lt
4532 else if ((cClient->getGameLobby()->sModName.find("Liero") != std::string::npos ||
4533 cClient->getGameLobby()->sModName.find("Classic") != std::string::npos) &&
4534 cClient->getGameLobby()->iLoadingTime == 100) {
4535 int MyWeaps = m_worm->cWeaponRest->isEnabled("Super Shotgun") + m_worm-> cWeaponRest->isEnabled("Napalm") + m_worm->cWeaponRest->isEnabled("Cannon") + m_worm->cWeaponRest->isEnabled("Doomsday") + m_worm->cWeaponRest->isEnabled("Chaingun");
4536 if (MyWeaps == 5) {
4537 // Set our weapons
4538 m_worm->tWeapons[0].Weapon = m_worm->cGameScript->FindWeapon("Super Shotgun");
4539 m_worm->tWeapons[1].Weapon = m_worm->cGameScript->FindWeapon("Napalm");
4540 m_worm->tWeapons[2].Weapon = m_worm->cGameScript->FindWeapon("Cannon");
4541 m_worm->tWeapons[3].Weapon = m_worm->cGameScript->FindWeapon("Doomsday");
4542 m_worm->tWeapons[4].Weapon = m_worm->cGameScript->FindWeapon("Chaingun");
4543 bRandomWeaps = false;
4544 AI_SetGameType(GAM_100LT);
4545 }
4546 }
4547 // Mortar game
4548 else if ((cClient->getGameLobby()->sModName.find("MW 1.0") != std::string::npos ||
4549 cClient->getGameLobby()->sModName.find("Modern Warfare1.0") != std::string::npos) &&
4550 cClient->getGameLobby()->iLoadingTime < 50) {
4551 if (m_worm->cWeaponRest->isEnabled("Mortar Launcher")) {
4552 for (short i=0; i<5; i++)
4553 m_worm->tWeapons[i].Weapon = m_worm->cGameScript->FindWeapon("Mortar Launcher"); // set all weapons to Mortar
4554 bRandomWeaps = false;
4555 AI_SetGameType(GAM_MORTARS);
4556 }
4557 }
4558
4559 // Random
4560 if (bRandomWeaps) {
4561 m_worm->GetRandomWeapons();
4562 AI_SetGameType(GAM_OTHER);
4563 }
4564
4565 // Note: Right now, it's as easy as this. But it could be more complicated later and we have to update this then.
4566 for(int i = 0; i < 5; ++i)
4567 m_worm->tWeapons[i].Enabled = m_worm->tWeapons[i].Weapon != NULL;
4568
4569 m_worm->setWeaponsReady(true);
4570
4571 }
4572
doWeaponSelectionFrame(SDL_Surface * bmpDest,CViewport * v)4573 void CWormBotInputHandler::doWeaponSelectionFrame(SDL_Surface * bmpDest, CViewport *v) {
4574 // Do nothing here. We can get here when we are waiting for the host worm to select his weapons.
4575 // In other cases, we would have already selected the weapons for the worm in initWeaponSelection().
4576
4577 //bWeaponsReady = true;
4578 //iCurrentWeapon = 0;
4579 }
4580
setAiDiff(int aiDiff)4581 void CWorm::setAiDiff(int aiDiff) {
4582 if(!m_inputHandler) {
4583 errors << "CWorm::setAiDiff: can only set ai-diff for local bots" << endl;
4584 return;
4585 }
4586
4587 CWormBotInputHandler* ai = dynamic_cast<CWormBotInputHandler*> (m_inputHandler);
4588 if(!ai) {
4589 errors << "CWorm::setAiDiff: worm " << getID() << ":" << getName() << " is not a bot" << endl;
4590 return;
4591 }
4592
4593 ai->setAiDiff(aiDiff);
4594 }
4595
setAiDiff(int aiDiff)4596 void CWormBotInputHandler::setAiDiff(int aiDiff) {
4597 if(aiDiff < AI_EASY || aiDiff > AI_XTREME) {
4598 errors << "CWormBotInputHandler::setAiDiff: " << aiDiff << " is invalid" << endl;
4599 return;
4600 }
4601
4602 iAiDiffLevel = aiDiff;
4603 }
4604