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