1 #include "game.h"
2 namespace ai
3 {
4 	entities::avoidset obs;
5     int updatemillis = 0;
6     vec aitarget(0, 0, 0);
7 
8 	VAR(aidebug, 0, 0, 6);
9     VAR(aisuspend, 0, 0, 1);
10     VAR(aiforcegun, -1, -1, WEAP_SUPER-1);
11     VARP(aideadfade, 0, 10000, 30000);
12     VARP(showaiinfo, 0, 0, 2); // 0/1 = shows/hides bot join/parts, 2 = show more verbose info
13 
14 	ICOMMAND(addbot, "s", (char *s), client::addmsg(SV_ADDBOT, "ri", *s ? clamp(atoi(s), 1, 101) : -1));
15 	ICOMMAND(delbot, "", (), client::addmsg(SV_DELBOT, "r"));
16 
viewdist(int x)17 	float viewdist(int x)
18 	{
19 		return x <= 100 ? clamp((SIGHTMIN+(SIGHTMAX-SIGHTMIN))/100.f*float(x), float(SIGHTMIN), float(game::fogdist)) : float(game::fogdist);
20 	}
21 
viewfieldx(int x)22 	float viewfieldx(int x)
23 	{
24 		return x <= 100 ? clamp((VIEWMIN+(VIEWMAX-VIEWMIN))/100.f*float(x), float(VIEWMIN), float(VIEWMAX)) : float(VIEWMAX);
25 	}
26 
viewfieldy(int x)27 	float viewfieldy(int x)
28 	{
29 		return viewfieldx(x)*3.f/4.f;
30 	}
31 
owner(gameent * d)32 	int owner(gameent *d)
33 	{
34 		if(entities::ents.inrange(d->aientity))
35 		{
36 			if(m_stf(game::gamemode)) return stf::aiowner(d);
37 			else if(m_ctf(game::gamemode)) return ctf::aiowner(d);
38 		}
39 		return d->team;
40 	}
41 
weaprange(gameent * d,int weap,bool alt,float dist)42 	bool weaprange(gameent *d, int weap, bool alt, float dist)
43 	{
44 		if(weaptype[weap].extinguish[alt ? 1 : 0] && d->inliquid) return false;
45 		float mindist = weaptype[weap].explode[alt ? 1 : 0] ? weaptype[weap].explode[alt ? 1 : 0] : (d->weapselect != WEAP_MELEE ? d->radius*2 : 0),
46 			maxdist = weaptype[weap].maxdist[alt ? 1 : 0] ? weaptype[weap].maxdist[alt ? 1 : 0] : hdr.worldsize;
47 		return dist >= mindist*mindist && dist <= maxdist*maxdist;
48 	}
49 
targetable(gameent * d,gameent * e,bool z)50 	bool targetable(gameent *d, gameent *e, bool z)
51 	{
52 		if(e && d != e && game::allowmove(d) && !m_edit(game::gamemode) && e->state == CS_ALIVE && physics::issolid(e))
53 			return !z || !m_team(game::gamemode, game::mutators) || owner(d) != owner(e);
54 		return false;
55 	}
56 
cansee(gameent * d,vec & x,vec & y,vec & targ)57 	bool cansee(gameent *d, vec &x, vec &y, vec &targ)
58 	{
59 		if(game::allowmove(d)) return getsight(x, d->yaw, d->pitch, y, targ, d->ai->views[2], d->ai->views[0], d->ai->views[1]);
60 		return false;
61 	}
62 
badhealth(gameent * d)63 	bool badhealth(gameent *d)
64 	{
65 		if(d->skill <= 100) return d->health <= (111-d->skill)/4;
66 		return false;
67 	}
68 
altfire(gameent * d,gameent * e)69 	bool altfire(gameent *d, gameent *e)
70 	{
71 		if(e && !weaptype[d->weapselect].zooms && weaprange(d, d->weapselect, true, e->o.squaredist(d->o)) && d->canshoot(d->weapselect, HIT_ALT, m_weapon(game::gamemode, game::mutators), lastmillis, (1<<WEAP_S_RELOAD)))
72 		{
73 			switch(d->weapselect)
74 			{
75 				case WEAP_MELEE: return false; break;
76 				case WEAP_SHOTGUN: case WEAP_SMG: if(rnd(d->skill*3) <= d->skill) return false;
77 				case WEAP_GRENADE: if(rnd(d->skill*3) >= d->skill) return false;
78 				case WEAP_RIFLE: if(weaprange(d, d->weapselect, false, e->o.squaredist(d->o))) return false;
79 				case WEAP_PISTOL: default: break;
80 			}
81 			return true;
82 		}
83 		return false;
84 	}
85 
hastarget(gameent * d,aistate & b,gameent * e,bool alt,float yaw,float pitch,float dist)86 	bool hastarget(gameent *d, aistate &b, gameent *e, bool alt, float yaw, float pitch, float dist)
87 	{ // add margins of error
88 		if(d->skill <= 100 && !rnd(d->skill*10)) return true; // random margin of error
89 		if(weaprange(d, d->weapselect, alt, dist))
90 		{
91 			if(d->weapselect == WEAP_MELEE) return true;
92 			float skew = clamp(float(lastmillis-d->ai->enemymillis)/float((d->skill*aistyle[d->aitype].frame*weaptype[d->weapselect].rdelay/2000.f)+(d->skill*weaptype[d->weapselect].adelay[alt ? 1 : 0]/200.f)), 0.f, d->weapselect == WEAP_GRENADE || d->weapselect == WEAP_GIBS ? 0.25f : 1e16f);
93 			if(fabs(yaw-d->yaw) <= d->ai->views[0]*skew && fabs(pitch-d->pitch) <= d->ai->views[1]*skew) return true;
94 		}
95 		return false;
96 	}
97 
getaimpos(gameent * d,gameent * e,bool alt)98 	vec getaimpos(gameent *d, gameent *e, bool alt)
99 	{
100 		vec o = e->headpos();
101 		#define rndaioffset (rnd(int(e->radius*weaptype[d->weapselect].aiskew[alt ? 1 : 0]*2)+1)-e->radius*weaptype[d->weapselect].aiskew[alt ? 1 : 0])
102 		#define skewaiskill (1.f/float(max(d->skill/10, 1)))
103 		if(weaptype[d->weapselect].radial[alt ? 1 : 0]) o.z -= e->height;
104 		if(d->skill <= 100)
105 		{
106 			if(weaptype[d->weapselect].radial[alt ? 1 : 0]) o.z += e->height*skewaiskill;
107 			else o.z -= e->height*skewaiskill;
108 			o.x += rndaioffset*skewaiskill; o.y += rndaioffset*skewaiskill;
109 		}
110 		return o;
111 	}
112 
create(gameent * d)113 	void create(gameent *d)
114 	{
115 		if(!d->ai)
116 		{
117 			if((d->ai = new aiinfo()) == NULL)
118 			{
119 				fatal("could not create ai");
120 				return;
121 			}
122 		}
123 		if(d->ai)
124 		{
125 			d->ai->views[0] = viewfieldx(d->skill);
126 			d->ai->views[1] = viewfieldy(d->skill);
127 			d->ai->views[2] = viewdist(d->skill);
128 		}
129 	}
130 
destroy(gameent * d)131 	void destroy(gameent *d)
132 	{
133 		if(d->ai) DELETEP(d->ai);
134 	}
135 
init(gameent * d,int at,int et,int on,int sk,int bn,char * name,int tm)136 	void init(gameent *d, int at, int et, int on, int sk, int bn, char *name, int tm)
137 	{
138 		gameent *o = game::newclient(on);
139 		string m;
140 		if(o) copystring(m, game::colorname(o));
141 		else formatstring(m)("\fs\faunknown [\fs\fr%d\fS]\fS", on);
142 		bool resetthisguy = false;
143 		if(!d->name[0])
144 		{
145 			if(showaiinfo && game::showplayerinfo)
146 			{
147 				if(showaiinfo > 1) conoutft(game::showplayerinfo > 1 ? int(CON_EVENT) : int(CON_MESG), "\fg%s assigned to %s at skill %d", game::colorname(d, name), m, sk);
148 				else conoutft(game::showplayerinfo > 1 ? int(CON_EVENT) : int(CON_MESG), "\fg%s joined the game", game::colorname(d, name), m, sk);
149 			}
150 			resetthisguy = true;
151 		}
152 		else
153 		{
154 			if(d->ownernum != on)
155 			{
156 				if(showaiinfo && game::showplayerinfo)
157 					conoutft(game::showplayerinfo > 1 ? int(CON_EVENT) : int(CON_MESG), "\fg%s reassigned to %s", game::colorname(d, name), m);
158 				resetthisguy = true;
159 			}
160 			if(d->skill != sk && showaiinfo > 1 && game::showplayerinfo)
161 				conoutft(game::showplayerinfo > 1 ? int(CON_EVENT) : int(CON_MESG), "\fg%s changed skill to %d", game::colorname(d, name), sk);
162 		}
163 
164 		copystring(d->name, name, MAXNAMELEN);
165 		if((d->aitype = at) >= AI_START)
166 		{
167 			d->type = ENT_AI;
168 			d->aientity = et;
169 			d->maxspeed = aistyle[d->aitype].maxspeed;
170 			d->weight = aistyle[d->aitype].weight;
171 			d->xradius = aistyle[d->aitype].xradius;
172 			d->yradius = aistyle[d->aitype].yradius;
173 			d->zradius = d->height = aistyle[d->aitype].height;
174 		}
175 		d->ownernum = on;
176 		d->skill = sk;
177 		d->team = tm;
178 
179 		if(resetthisguy) projs::remove(d);
180 		if(game::player1->clientnum == d->ownernum) create(d);
181 		else if(d->ai) destroy(d);
182 	}
183 
update()184 	void update()
185 	{
186 		bool updating = lastmillis-updatemillis > 100; // fixed rate logic at 10fps
187         if(updating)
188         {
189         	avoid();
190         	if(multiplayer(false)) { aiforcegun = -1; aisuspend = 0; }
191         }
192 		loopv(game::players) if(game::players[i] && game::players[i]->ai)
193 		{
194 			if(!game::intermission) think(game::players[i], updating);
195 			else game::players[i]->stopmoving(true);
196 		}
197 	}
198 
checkothers(vector<int> & targets,gameent * d,int state,int targtype,int target,bool teams)199 	bool checkothers(vector<int> &targets, gameent *d, int state, int targtype, int target, bool teams)
200 	{ // checks the states of other ai for a match
201 		targets.setsize(0);
202 		gameent *e = NULL;
203 		loopi(game::numdynents()) if((e = (gameent *)game::iterdynents(i)) && e != d && e->ai && e->state == CS_ALIVE && e->aitype == d->aitype)
204 		{
205 			if(targets.find(e->clientnum) >= 0) continue;
206 			if(teams && m_team(game::gamemode, game::mutators) && d && owner(d) != owner(e)) continue;
207 			aistate &b = e->ai->getstate();
208 			if(state >= 0 && b.type != state) continue;
209 			if(target >= 0 && b.target != target) continue;
210 			if(targtype >=0 && b.targtype != targtype) continue;
211 			targets.add(e->clientnum);
212 		}
213 		return !targets.empty();
214 	}
215 
makeroute(gameent * d,aistate & b,int node,bool changed,int retries)216 	bool makeroute(gameent *d, aistate &b, int node, bool changed, int retries)
217 	{
218 		if(d->lastnode < 0) return false;
219 		if(changed && !d->ai->route.empty() && d->ai->route[0] == node) return true;
220 		if(entities::route(d->lastnode, node, d->ai->route, obs, d, retries <= 1) > 0)
221 		{
222 			b.override = false;
223 			return true;
224 		}
225 		d->ai->clear(true);
226 		if(retries <= 1) return makeroute(d, b, node, false, retries+1);
227 		return false;
228 	}
229 
makeroute(gameent * d,aistate & b,const vec & pos,bool changed,int retries)230 	bool makeroute(gameent *d, aistate &b, const vec &pos, bool changed, int retries)
231 	{
232 		int node = entities::closestent(WAYPOINT, pos, NEARDIST, true, d);
233 		return makeroute(d, b, node, changed, retries);
234 	}
235 
randomnode(gameent * d,aistate & b,const vec & pos,float guard,float wander)236 	bool randomnode(gameent *d, aistate &b, const vec &pos, float guard, float wander)
237 	{
238 		static vector<int> candidates;
239 		candidates.setsizenodelete(0);
240         entities::findentswithin(WAYPOINT, pos, guard, wander, candidates);
241 
242 		while(!candidates.empty())
243 		{
244 			int w = rnd(candidates.length()), n = candidates.removeunordered(w);
245 			if(n != d->lastnode && !d->ai->hasprevnode(n) && !obs.find(n, d) && makeroute(d, b, n)) return true;
246 		}
247 		return false;
248 	}
249 
randomnode(gameent * d,aistate & b,float guard,float wander)250 	bool randomnode(gameent *d, aistate &b, float guard, float wander)
251 	{
252 		return randomnode(d, b, d->feetpos(), guard, wander);
253 	}
254 
enemy(gameent * d,aistate & b,const vec & pos,float guard,bool pursue,bool force)255 	bool enemy(gameent *d, aistate &b, const vec &pos, float guard, bool pursue, bool force)
256 	{
257 		if(game::allowmove(d))
258 		{
259 			gameent *t = NULL, *e = NULL;
260 			vec dp = d->headpos(), tp(0, 0, 0);
261 			bool insight = false, tooclose = false;
262 			float mindist = d->weapselect != WEAP_MELEE ? guard*guard : 0;
263 			loopi(game::numdynents()) if((e = (gameent *)game::iterdynents(i)) && e != d && targetable(d, e, true))
264 			{
265 				vec ep = getaimpos(d, e, altfire(d, e));
266 				bool close = ep.squaredist(pos) < mindist;
267 				if(!t || ep.squaredist(dp) < tp.squaredist(dp) || close)
268 				{
269 					bool see = cansee(d, dp, ep);
270 					if(!insight || see || close)
271 					{
272 						t = e; tp = ep;
273 						if(!insight && see) insight = see;
274 						if(!tooclose && close && (see || force)) tooclose = close;
275 					}
276 				}
277 			}
278 			if(t && violence(d, b, t, pursue && (tooclose || insight))) return insight || tooclose;
279 		}
280 		return false;
281 	}
282 
patrol(gameent * d,aistate & b,const vec & pos,float guard,float wander,int walk,bool retry)283 	bool patrol(gameent *d, aistate &b, const vec &pos, float guard, float wander, int walk, bool retry)
284 	{
285 		if(aistyle[d->aitype].maxspeed)
286 		{
287 			vec feet = d->feetpos();
288 			float dist = feet.squaredist(pos);
289 			if(dist >= guard*guard) b.idle = -1;
290 			if(walk == 2 || b.override || (walk && dist <= guard*guard) || !makeroute(d, b, pos))
291 			{ // run away and back to keep ourselves busy
292 				if(!b.override && randomnode(d, b, pos, guard, wander))
293 				{
294 					b.override = true;
295 					return true;
296 				}
297 				else if(d->ai->route.length() > 1) return true;
298 				else if(!retry)
299 				{
300 					b.override = false;
301 					return patrol(d, b, pos, guard, wander, walk, true);
302 				}
303 				b.override = false;
304 				return false;
305 			}
306 		}
307 		b.override = false;
308 		return true;
309 	}
310 
defend(gameent * d,aistate & b,const vec & pos,float guard,float wander,int walk)311 	bool defend(gameent *d, aistate &b, const vec &pos, float guard, float wander, int walk)
312 	{
313 		bool hasenemy = enemy(d, b, pos, wander, false, false);
314 		if(!aistyle[d->aitype].maxspeed) { b.idle = hasenemy ? 2 : 1; return true; }
315 		else
316 		{
317 			if(!walk && pos.squaredist(d->feetpos()) <= guard*guard)
318 			{
319 				b.idle = hasenemy ? 2 : 1;
320 				return true;
321 			}
322 			return patrol(d, b, pos, guard, wander, walk);
323 		}
324 		return false;
325 	}
326 
violence(gameent * d,aistate & b,gameent * e,bool pursue)327 	bool violence(gameent *d, aistate &b, gameent *e, bool pursue)
328 	{
329 		if(e && targetable(d, e, true))
330 		{
331 			if(pursue) d->ai->switchstate(b, AI_S_PURSUE, AI_T_PLAYER, e->clientnum);
332 			if(d->ai->enemy != e->clientnum) d->ai->enemymillis = lastmillis;
333 			d->ai->enemy = e->clientnum;
334 			d->ai->enemyseen = lastmillis;
335 			vec dp = d->headpos(), ep = getaimpos(d, e, altfire(d, e));
336 			if(!cansee(d, dp, ep)) d->ai->enemyseen -= ((111-d->skill)*10)+10; // so we don't "quick"
337 			return true;
338 		}
339 		return false;
340 	}
341 
target(gameent * d,aistate & b,bool pursue=false,bool force=false)342 	bool target(gameent *d, aistate &b, bool pursue = false, bool force = false)
343 	{
344 		gameent *t = NULL, *e = NULL;
345 		vec dp = d->headpos(), tp(0, 0, 0);
346 		loopi(game::numdynents()) if((e = (gameent *)game::iterdynents(i)) && e != d && targetable(d, e, true))
347 		{
348 			vec ep = getaimpos(d, e, altfire(d, e));
349 			if((!t || ep.squaredist(dp) < tp.squaredist(dp)) && (force || cansee(d, dp, ep)))
350 			{
351 				t = e;
352 				tp = ep;
353 			}
354 		}
355 		if(t) return violence(d, b, t, pursue);
356 		return false;
357 	}
358 
assist(gameent * d,aistate & b,vector<interest> & interests,bool all=false,bool force=false)359 	void assist(gameent *d, aistate &b, vector<interest> &interests, bool all = false, bool force = false)
360 	{
361 		gameent *e = NULL;
362 		loopi(game::numdynents()) if((e = (gameent *)game::iterdynents(i)) && e != d && (all || e->aitype < 0) && owner(d) == owner(e) && e->lastnode >= 0)
363 		{
364 			interest &n = interests.add();
365 			n.state = AI_S_DEFEND;
366 			n.node = e->lastnode;
367 			n.target = e->clientnum;
368 			n.targtype = AI_T_PLAYER;
369 			n.score = e->o.squaredist(d->o)/(force || d->hasweap(d->arenaweap, m_weapon(game::gamemode, game::mutators)) ? 75.f : 1.f);
370 		}
371 	}
372 
items(gameent * d,aistate & b,vector<interest> & interests,bool force=false)373 	void items(gameent *d, aistate &b, vector<interest> &interests, bool force = false)
374 	{
375 		if(!m_noitems(game::gamemode, game::mutators))
376 		{
377 			vec pos = d->feetpos();
378 			loopj(entities::lastusetype[EU_ITEM])
379 			{
380 				gameentity &e = *(gameentity *)entities::ents[j];
381 				if(enttype[e.type].usetype != EU_ITEM) continue;
382 				int sweap = m_weapon(game::gamemode, game::mutators);
383 				switch(e.type)
384 				{
385 					case WEAPON:
386 					{
387 						int attr = w_attr(game::gamemode, e.attrs[0], sweap);
388 						if(e.spawned && isweap(attr) && !d->hasweap(d->arenaweap, sweap) && !d->hasweap(attr, sweap))
389 						{ // go get a weapon upgrade
390 							interest &n = interests.add();
391 							n.state = AI_S_INTEREST;
392 							n.node = entities::closestent(WAYPOINT, e.o, NEARDIST, true);
393 							n.target = j;
394 							n.targtype = AI_T_ENTITY;
395 							n.score = pos.squaredist(e.o)/(force || attr == d->arenaweap ? 1000.f : (d->carry(sweap, 1) <= 0 ? 100.f : 1.f));
396 						}
397 						break;
398 					}
399 					default: break;
400 				}
401 			}
402 
403 			loopvj(projs::projs) if(projs::projs[j]->projtype == PRJ_ENT && projs::projs[j]->ready())
404 			{
405 				projent &proj = *projs::projs[j];
406 				if(!entities::ents.inrange(proj.id) || enttype[entities::ents[proj.id]->type].usetype != EU_ITEM) continue;
407 				gameentity &e = *(gameentity *)entities::ents[proj.id];
408 				int sweap = m_weapon(game::gamemode, game::mutators);
409 				switch(e.type)
410 				{
411 					case WEAPON:
412 					{
413 						int attr = w_attr(game::gamemode, e.attrs[0], sweap);
414 						if(isweap(attr) && !d->hasweap(d->arenaweap, sweap) && !d->hasweap(attr, sweap))
415 						{ // go get a weapon upgrade
416 							if(proj.owner == d) break;
417 							interest &n = interests.add();
418 							n.state = AI_S_INTEREST;
419 							n.node = entities::closestent(WAYPOINT, proj.o, NEARDIST, true);
420 							n.target = proj.id;
421 							n.targtype = AI_T_DROP;
422 							n.score = pos.squaredist(proj.o)/(force || attr == d->arenaweap ? 100.f : 1.f);
423 						}
424 						break;
425 					}
426 					default: break;
427 				}
428 			}
429 		}
430 	}
431 
find(gameent * d,aistate & b)432 	bool find(gameent *d, aistate &b)
433 	{
434 		static vector<interest> interests; interests.setsizenodelete(0);
435 		if(d->aitype == AI_BOT)
436 		{
437 			if(!d->hasweap(d->arenaweap, m_weapon(game::gamemode, game::mutators))) items(d, b, interests);
438 			if(m_fight(game::gamemode))
439 			{ // don't let bots consume items in story mode (yet?)
440 				if(m_stf(game::gamemode)) stf::aifind(d, b, interests);
441 				else if(m_ctf(game::gamemode)) ctf::aifind(d, b, interests);
442 			}
443 			if(m_team(game::gamemode, game::mutators)) assist(d, b, interests, false, m_story(game::gamemode));
444 		}
445 		else if(entities::ents.inrange(d->aientity))
446 		{
447 			loopv(entities::ents[d->aientity]->links) if(entities::ents[entities::ents[d->aientity]->links[i]]->type == WAYPOINT)
448 			{
449 				interest &n = interests.add();
450 				n.state = AI_S_DEFEND;
451 				n.target = n.node = entities::ents[d->aientity]->links[i];
452 				n.targtype = AI_T_NODE;
453 				n.score = -1;
454 			}
455 		}
456 		while(!interests.empty())
457 		{
458 			int q = interests.length()-1;
459 			loopi(interests.length()-1) if(interests[i].score < interests[q].score) q = i;
460 			interest n = interests.removeunordered(q);
461 			bool proceed = true;
462 			static vector<int> targets;
463 			targets.setsizenodelete(0);
464 			if(m_fight(game::gamemode) && d->aitype == AI_BOT) switch(n.state)
465 			{
466 				case AI_S_DEFEND: // don't get into herds
467 					proceed = !checkothers(targets, d, n.state, n.targtype, n.target, true);
468 					break;
469 				default: break;
470 			}
471 			if(proceed && (!aistyle[d->aitype].maxspeed || makeroute(d, b, n.node, false)))
472 			{
473 				d->ai->addstate(n.state, n.targtype, n.target);
474 				return true;
475 			}
476 		}
477 		return false;
478 	}
479 
damaged(gameent * d,gameent * e)480 	void damaged(gameent *d, gameent *e)
481 	{
482 		if(d != e)
483 		{
484 			if(d->ai && game::allowmove(d)) // see if this ai is interested in a grudge
485 			{
486 				d->ai->suspended = false;
487 				aistate &b = d->ai->getstate();
488 				violence(d, b, e, d->aitype == AI_BOT ? false : true);
489 			}
490 			if(d->aitype >= AI_START) // alert the horde
491 			{
492 				gameent *t = NULL;
493 				vec dp = d->headpos();
494 				loopi(game::numdynents()) if((t = (gameent *)game::iterdynents(i)) && t != d && t->ai && t->state == CS_ALIVE && t->aitype >= AI_START && t->ai->suspended && targetable(t, e, true))
495 				{
496 					vec tp = t->headpos();
497 					if(cansee(t, tp, dp) || tp.squaredist(dp) <= FARDIST*2)
498 					{
499 						t->ai->suspended = false;
500 						aistate &c = t->ai->getstate();
501 						if(violence(t, c, e, true)) return;
502 					}
503 				}
504 			}
505 			else
506 			{
507 				static vector<int> targets; // check if one of our ai is defending them
508 				targets.setsizenodelete(0);
509 				if(checkothers(targets, d, AI_S_DEFEND, AI_T_PLAYER, d->clientnum, true))
510 				{
511 					gameent *t;
512 					loopv(targets) if((t = game::getclient(targets[i])) && t->ai && t->aitype == AI_BOT && game::allowmove(t) && !t->ai->suspended)
513 					{
514 						aistate &c = t->ai->getstate();
515 						violence(t, c, e, false);
516 					}
517 				}
518 			}
519 		}
520 	}
521 
setup(gameent * d,bool tryreset=false,int ent=-1)522 	void setup(gameent *d, bool tryreset = false, int ent = -1)
523 	{
524 		d->aientity = ent;
525 		if(d->ai)
526 		{
527 			d->ai->reset(tryreset);
528 			d->ai->lastrun = lastmillis;
529 			aistate &b = d->ai->getstate();
530 			b.next = lastmillis+((111-d->skill)*10)+rnd((111-d->skill)*10);
531 			if(d->aitype >= AI_START && aistyle[d->aitype].weap >= 0) d->arenaweap = aistyle[d->aitype].weap;
532 			else if(m_noitems(game::gamemode, game::mutators) && !m_arena(game::gamemode, game::mutators))
533 				d->arenaweap = m_weapon(game::gamemode, game::mutators);
534 			else if(aiforcegun >= 0 && aiforcegun < WEAP_SUPER) d->arenaweap = aiforcegun;
535 			else while(true)
536 			{
537 				d->arenaweap = rnd(WEAP_SUPER);
538 				if(d->arenaweap >= WEAP_OFFSET || !rnd(d->skill)) break;
539 			}
540 			if(d->aitype == AI_BOT)
541 			{
542 				if(m_arena(game::gamemode, game::mutators) && d->arenaweap == WEAP_GRENADE)
543 					d->arenaweap = WEAP_PISTOL;
544 			}
545 			d->ai->suspended = d->aitype == AI_BOT || !m_story(game::gamemode) ? false : true;
546 		}
547 	}
548 
spawned(gameent * d,int ent)549 	void spawned(gameent *d, int ent)
550 	{
551 		if(d->ai)
552 		{
553 			d->ai->cleartimers();
554 			setup(d, false, ent);
555 		}
556 	}
killed(gameent * d,gameent * e)557 	void killed(gameent *d, gameent *e) { if(d->ai) { d->ai->reset(); if(d->aitype >= AI_START) d->ai->suspended = true; } }
558 
check(gameent * d,aistate & b)559 	bool check(gameent *d, aistate &b)
560 	{
561 		if(d->aitype == AI_BOT)
562 		{
563 			if(m_stf(game::gamemode) && stf::aicheck(d, b)) return true;
564 			else if(m_ctf(game::gamemode) && ctf::aicheck(d, b)) return true;
565 		}
566 		return false;
567 	}
568 
dowait(gameent * d,aistate & b)569 	int dowait(gameent *d, aistate &b)
570 	{
571 		if(!m_edit(game::gamemode))
572 		{
573 			if(check(d, b) || find(d, b)) return 1;
574 			if(target(d, b, true, !d->ai->suspended ? true : false)) return 1;
575 		}
576 		if(!d->ai->suspended) switch(d->aitype)
577 		{
578 			case AI_BOT: if(m_fight(game::gamemode) && randomnode(d, b, NEARDIST, 1e16f))
579 			{
580 				d->ai->addstate(AI_S_INTEREST, AI_T_NODE, d->ai->route[0]);
581 				return 1;
582 			} break;
583 			case AI_TURRET: if(randomnode(d, b, NEARDIST, FARDIST))
584 			{
585 				d->ai->addstate(AI_S_DEFEND, AI_T_NODE, d->ai->route[0]);
586 				return 1;
587 			} break;
588 			default: break;
589 		}
590 		return 0; // but don't pop the state
591 	}
592 
dodefend(gameent * d,aistate & b)593 	int dodefend(gameent *d, aistate &b)
594 	{
595 		if(!d->ai->suspended && d->state == CS_ALIVE)
596 		{
597 			switch(b.targtype)
598 			{
599 				case AI_T_NODE:
600 				case AI_T_ENTITY:
601 				{
602 					if(check(d, b)) return 1;
603 					if(entities::ents.inrange(b.target))
604 					{
605 						gameentity &e = *(gameentity *)entities::ents[b.target];
606 						return defend(d, b, e.o) ? 1 : 0;
607 					}
608 					break;
609 				}
610 				case AI_T_AFFINITY:
611 				{
612 					if(m_stf(game::gamemode)) return stf::aidefend(d, b) ? 1 : 0;
613 					else if(m_ctf(game::gamemode)) return ctf::aidefend(d, b) ? 1 : 0;
614 					break;
615 				}
616 				case AI_T_PLAYER:
617 				{
618 					if(check(d, b)) return 1;
619 					gameent *e = game::getclient(b.target);
620 					if(e && e->state == CS_ALIVE) return defend(d, b, e->feetpos()) ? 1 : 0;
621 					break;
622 				}
623 				default: break;
624 			}
625 		}
626 		return 0;
627 	}
628 
dointerest(gameent * d,aistate & b)629 	int dointerest(gameent *d, aistate &b)
630 	{
631 		if(!d->ai->suspended && d->state == CS_ALIVE && aistyle[d->aitype].maxspeed)
632 		{
633 			int sweap = m_weapon(game::gamemode, game::mutators);
634 			switch(b.targtype)
635 			{
636 				case AI_T_NODE:
637 				{
638 					if(check(d, b)) return 1;
639 					if(entities::ents.inrange(b.target))
640 					{
641 						gameentity &e = *(gameentity *)entities::ents[b.target];
642 						if(vec(e.o).sub(d->feetpos()).magnitude() > CLOSEDIST)
643 							return makeroute(d, b, e.o) ? 1 : 0;
644 					}
645 					break;
646 				}
647 				case AI_T_ENTITY:
648 				{
649 					if(m_noitems(game::gamemode, game::mutators) || d->hasweap(d->arenaweap, sweap)) return 0;
650 					if(entities::ents.inrange(b.target))
651 					{
652 						gameentity &e = *(gameentity *)entities::ents[b.target];
653 						if(enttype[e.type].usetype != EU_ITEM) return 0;
654 						int attr = w_attr(game::gamemode, e.attrs[0], sweap);
655 						switch(e.type)
656 						{
657 							case WEAPON:
658 							{
659 								if(!e.spawned || d->hasweap(attr, sweap)) return 0;
660 								float guard = enttype[e.type].radius;
661 								if(d->feetpos().squaredist(e.o) <= guard*guard) b.idle = enemy(d, b, e.o, guard*4, false, false) ? 2 : 1;
662 								else b.idle = -1;
663 								break;
664 							}
665 							default: break;
666 						}
667 						return makeroute(d, b, e.o) ? 1 : 0;
668 					}
669 					break;
670 				}
671 				case AI_T_DROP:
672 				{
673 					if(m_noitems(game::gamemode, game::mutators) || d->hasweap(d->arenaweap, sweap)) return 0;
674 					loopvj(projs::projs) if(projs::projs[j]->projtype == PRJ_ENT && projs::projs[j]->ready() && projs::projs[j]->id == b.target)
675 					{
676 						projent &proj = *projs::projs[j];
677 						if(!entities::ents.inrange(proj.id) || enttype[entities::ents[proj.id]->type].usetype != EU_ITEM) return 0;
678 						gameentity &e = *(gameentity *)entities::ents[proj.id];
679 						int attr = w_attr(game::gamemode, e.attrs[0], sweap);
680 						switch(e.type)
681 						{
682 							case WEAPON:
683 							{
684 								if(d->hasweap(attr, sweap) || proj.owner == d) return 0;
685 								float guard = enttype[e.type].radius;
686 								if(d->feetpos().squaredist(e.o) <= guard*guard) b.idle = enemy(d, b, e.o, guard*4, false, false) ? 2 : 1;
687 								else b.idle = -1;
688 								break;
689 							}
690 							default: break;
691 						}
692 						return makeroute(d, b, proj.o) ? 1 : 0;
693 						break;
694 					}
695 					break;
696 				}
697 				default: break;
698 			}
699 		}
700 		return 0;
701 	}
702 
dopursue(gameent * d,aistate & b)703 	int dopursue(gameent *d, aistate &b)
704 	{
705 		if(!d->ai->suspended && d->state == CS_ALIVE)
706 		{
707 			switch(b.targtype)
708 			{
709 				case AI_T_AFFINITY:
710 				{
711 					if(aistyle[d->aitype].maxspeed)
712 					{
713 						if(m_stf(game::gamemode)) return stf::aipursue(d, b) ? 1 : 0;
714 						else if(m_ctf(game::gamemode)) return ctf::aipursue(d, b) ? 1 : 0;
715 					}
716 					break;
717 				}
718 
719 				case AI_T_PLAYER:
720 				{
721 					if(check(d, b)) return 1;
722 					gameent *e = game::getclient(b.target);
723 					if(e && e->state == CS_ALIVE)
724 					{
725 						bool alt = altfire(d, e);
726 						if(aistyle[d->aitype].maxspeed)
727 						{
728 							float mindist = weaptype[d->weapselect].explode[alt ? 1 : 0] ? weaptype[d->weapselect].explode[alt ? 1 : 0] : (d->weapselect != WEAP_MELEE ? NEARDIST : 0);
729 							return patrol(d, b, e->feetpos(), mindist, FARDIST) ? 1 : 0;
730 						}
731 						else
732 						{
733 							vec dp = d->headpos(), ep = getaimpos(d, e, alt);
734 							if(cansee(d, dp, ep) || (e->clientnum == d->ai->enemy && d->ai->enemyseen && lastmillis-d->ai->enemyseen <= (d->skill*50)+3000))
735 							{
736 								b.idle = -1;
737 								return 1;
738 							}
739 							return 0;
740 						}
741 					}
742 					break;
743 				}
744 				default: break;
745 			}
746 		}
747 		return 0;
748 	}
749 
closenode(gameent * d,bool force=false)750 	int closenode(gameent *d, bool force = false)
751 	{
752 		vec pos = d->feetpos();
753 		int node = -1;
754 		float mindist = FARDIST*FARDIST;
755 		loopvrev(d->ai->route) if(entities::ents.inrange(d->ai->route[i]))
756 		{
757 			gameentity &e = *(gameentity *)entities::ents[d->ai->route[i]];
758 			vec epos = e.o;
759 			int entid = obs.remap(d, d->ai->route[i], epos);
760 			if(entities::ents.inrange(entid) && (force || entid == d->ai->route[i] || !d->ai->hasprevnode(entid)))
761 			{
762 				float dist = epos.squaredist(pos);
763 				if(dist < mindist)
764 				{
765 					node = entid;
766 					mindist = dist;
767 				}
768 			}
769 		}
770 		return node;
771 	}
772 
wpspot(gameent * d,int n,bool force=false)773 	bool wpspot(gameent *d, int n, bool force = false)
774 	{
775 		if(entities::ents.inrange(n))
776 		{
777 			gameentity &e = *(gameentity *)entities::ents[n];
778 			vec epos = e.o;
779 			int entid = obs.remap(d, n, epos);
780 			if(entities::ents.inrange(entid) && (force || entid == n || !d->ai->hasprevnode(entid)))
781 			{
782 				d->ai->spot = epos;
783 				d->ai->targnode = n;
784 				if(((e.attrs[0] & WP_F_CROUCH && !d->action[AC_CROUCH]) || d->action[AC_CROUCH]) && (lastmillis-d->actiontime[AC_CROUCH] >= PHYSMILLIS*3))
785 				{
786 					d->action[AC_CROUCH] = !d->action[AC_CROUCH];
787 					d->actiontime[AC_CROUCH] = lastmillis;
788 				}
789 				return true;
790 			}
791 		}
792 		return false;
793 	}
794 
anynode(gameent * d,aistate & b,bool retry=false)795 	bool anynode(gameent *d, aistate &b, bool retry = false)
796 	{
797 		if(entities::ents.inrange(d->lastnode))
798 		{
799 			gameentity &e = *(gameentity *)entities::ents[d->lastnode];
800 			static vector<int> anyremap; anyremap.setsizenodelete(0);
801 			if(!e.links.empty())
802 			{
803 				loopv(e.links) if(entities::ents.inrange(e.links[i]) && (retry || !d->ai->hasprevnode(e.links[i])))
804 					anyremap.add(e.links[i]);
805 			}
806 			while(!anyremap.empty())
807 			{
808 				int r = rnd(anyremap.length()), t = anyremap[r];
809 				if(wpspot(d, t, retry))
810 				{
811 					d->ai->route.add(t);
812 					d->ai->route.add(d->lastnode);
813 					return true;
814 				}
815 				anyremap.remove(r);
816 			}
817 			if(!retry) return anynode(d, b, true);
818 		}
819 		return false;
820 	}
821 
hunt(gameent * d,aistate & b,int retries=0)822 	bool hunt(gameent *d, aistate &b, int retries = 0)
823 	{
824 		if(!d->ai->route.empty() && d->lastnode >= 0)
825 		{
826 			int n = retries%2 ? d->ai->route.find(d->lastnode) : closenode(d, retries >= 2);
827 			if(retries%2 && d->ai->route.inrange(n))
828 			{
829 				while(d->ai->route.length() > n+1) d->ai->route.pop(); // waka-waka-waka-waka
830 				if(!n)
831 				{
832 					if(wpspot(d, d->ai->route[n], retries >= 2))
833 					{
834 						b.next = lastmillis; // reached our goal, update
835 						d->ai->clear(false);
836 						return true;
837 					}
838 					else if(retries <= 2) return hunt(d, b, retries+1); // try again
839 				}
840 				else n--; // otherwise, we want the next in line
841 			}
842 			if(d->ai->route.inrange(n) && wpspot(d, d->ai->route[n], retries >= 2)) return true;
843 			if(retries <= 2) return hunt(d, b, retries+1); // try again
844 		}
845 		b.override = false;
846 		d->ai->clear(false);
847 		return anynode(d, b);
848 	}
849 
jumpto(gameent * d,aistate & b,const vec & pos)850 	void jumpto(gameent *d, aistate &b, const vec &pos)
851 	{
852 		vec off = vec(pos).sub(d->feetpos());
853 		bool offground = d->physstate == PHYS_FALL && !physics::liquidcheck(d) && !d->onladder,
854 			jumper = off.z >= JUMPMIN && (!offground || (d->timeinair > 500 && physics::canimpulse(d))),
855 			jump = (jumper || d->onladder || lastmillis >= d->ai->jumprand) && lastmillis >= d->ai->jumpseed;
856 		if(jump)
857 		{
858 			vec old = d->o;
859 			d->o = vec(pos).add(vec(0, 0, d->height));
860 			if(!collide(d, vec(0, 0, 1))) jump = false;
861 			d->o = old;
862 			if(jump)
863 			{
864 				loopi(entities::lastenttype[PUSHER]) if(entities::ents[i]->type == PUSHER)
865 				{
866 					gameentity &e = *(gameentity *)entities::ents[i];
867 					float radius = (e.attrs[3] ? e.attrs[3] : enttype[e.type].radius)*1.5f; radius *= radius;
868 					if(e.o.squaredist(pos) <= radius) { jump = false; break; }
869 				}
870 			}
871 		}
872 		if(jump)
873 		{
874 			if((d->action[AC_JUMP] = jump) != false) d->actiontime[AC_JUMP] = lastmillis;
875 			int seed = (111-d->skill)*(d->onladder || d->inliquid ? 2 : 8);
876 			d->ai->jumpseed = lastmillis+seed+rnd(seed);
877 			seed *= b.idle ? 200 : 100;
878 			d->ai->jumprand = lastmillis+seed+rnd(seed);
879 		}
880 	}
881 
process(gameent * d,aistate & b)882 	int process(gameent *d, aistate &b)
883 	{
884 		int result = 0, stupify = d->skill <= 30+rnd(20) ? rnd(d->skill*1111) : 0, skmod = max((111-d->skill)*10, 100);
885 		float frame = float(lastmillis-d->ai->lastrun)/float(max(skmod/2,1)*aistyle[d->aitype].frame);
886 		vec dp = d->headpos();
887 
888 		bool wasdontmove = d->ai->dontmove, idle = b.idle == 1 || (stupify && stupify <= skmod) || !aistyle[d->aitype].maxspeed || d->ai->suspended;
889 		d->ai->dontmove = false;
890 		if(idle)
891 		{
892 			d->ai->lastaction = d->ai->lasthunt = lastmillis;
893 			d->ai->dontmove = true;
894 		}
895 		else if(hunt(d, b))
896 		{
897 			game::getyawpitch(dp, vec(d->ai->spot).add(vec(0, 0, d->height)), d->ai->targyaw, d->ai->targpitch);
898 			d->ai->lasthunt = lastmillis;
899 		}
900 		else d->ai->dontmove = true;
901 
902 		if(d->aitype == AI_BOT)
903 		{
904 			if(!d->ai->dontmove) jumpto(d, b, d->ai->spot);
905 			if(idle)
906 			{
907 				bool wascrouching = lastmillis-d->actiontime[AC_CROUCH] <= PHYSMILLIS*2, wantscrouch = d->ai->dontmove && !wasdontmove && !d->action[AC_CROUCH];
908 				if(wascrouching || wantscrouch)
909 				{
910 					d->action[AC_CROUCH] = true;
911 					if(wantscrouch) d->actiontime[AC_CROUCH] = lastmillis;
912 				}
913 			}
914 		}
915 
916 		gameent *e = game::getclient(d->ai->enemy);
917 		bool enemyok = e && targetable(d, e, true);
918 		if(!enemyok || d->skill > 70)
919 		{
920 			gameent *f = game::intersectclosest(dp, d->ai->target, d);
921 			if(f && targetable(d, f, true)) e = f;
922 			else if(!enemyok) e = NULL;
923 		}
924 		if(e && e->state == CS_ALIVE)
925 		{
926 			bool alt = altfire(d, e);
927 			vec ep = getaimpos(d, e, alt);
928 			float yaw, pitch;
929 			game::getyawpitch(dp, ep, yaw, pitch);
930 			game::fixrange(yaw, pitch);
931 			bool targeting = hastarget(d, b, e, alt, yaw, pitch, dp.squaredist(ep)), insight = cansee(d, dp, ep) && targeting,
932 				hasseen = d->ai->enemyseen && lastmillis-d->ai->enemyseen <= (d->skill*50)+3000, quick = d->ai->enemyseen && lastmillis-d->ai->enemyseen <= skmod;
933 			if(insight) d->ai->enemyseen = lastmillis;
934 			if(idle || insight || hasseen)
935 			{
936 				float sskew = insight ? 2.f : (hasseen ? 1.f : 0.5f);
937 				if(idle || (insight && d->weapselect == WEAP_MELEE))
938 				{
939 					d->ai->targyaw = yaw;
940 					d->ai->targpitch = pitch;
941 					frame /= !insight ? 4.f : 2.f;
942 					d->ai->becareful = false;
943 				}
944 				else if(!insight) frame /= 2.f;
945 				game::scaleyawpitch(d->yaw, d->pitch, yaw, pitch, frame, sskew);
946 				if((insight || quick) && !d->ai->becareful)
947 				{
948 					if(d->canshoot(d->weapselect, alt ? HIT_ALT : 0, m_weapon(game::gamemode, game::mutators), lastmillis, (1<<WEAP_S_RELOAD)))
949 					{
950 						d->action[alt ? AC_ALTERNATE : AC_ATTACK] = true;
951 						d->ai->lastaction = d->actiontime[alt ? AC_ALTERNATE : AC_ATTACK] = lastmillis;
952 						result = 4;
953 					}
954 					else result = insight ? 3 : 2;
955 				}
956 				else result = hasseen ? 2 : 1;
957 			}
958 			else
959 			{
960 				d->ai->enemy = -1;
961 				d->ai->enemymillis = 0;
962 				result = 0;
963 				frame /= 2.f;
964 			}
965 		}
966 		else
967 		{
968 			if(!enemyok)
969 			{
970 				d->ai->enemy = -1;
971 				d->ai->enemymillis = 0;
972 			}
973 			result = 0;
974 			frame /= 2.f;
975 		}
976 
977 		game::fixrange(d->ai->targyaw, d->ai->targpitch);
978 		d->aimyaw = d->ai->targyaw; d->aimpitch = d->ai->targpitch;
979 		if(!result) game::scaleyawpitch(d->yaw, d->pitch, d->ai->targyaw, d->ai->targpitch, frame, 1.f);
980 
981 		bool wantsimpulse = false;
982 		if(d->aitype == AI_BOT && b.idle == -1 && !d->ai->dontmove)
983 			wantsimpulse = (d->action[AC_IMPULSE] || !d->actiontime[AC_IMPULSE] || lastmillis-d->actiontime[AC_IMPULSE] > PHYSMILLIS*2);
984 		if((d->ai->becareful && d->physstate == PHYS_FALL) || wantsimpulse)
985 		{
986 			float offyaw, offpitch;
987 			vec v = vec(d->vel).normalize();
988 			vectoyawpitch(v, offyaw, offpitch);
989 			offyaw -= d->aimyaw; offpitch -= d->aimpitch;
990 			if(fabs(offyaw)+fabs(offpitch) >= 135) wantsimpulse = d->ai->becareful = false;
991 			else if(d->ai->becareful)
992 			{
993 				d->ai->dontmove = true;
994 				wantsimpulse = false;
995 			}
996 		}
997 		else d->ai->becareful = false;
998 		if(d->action[AC_IMPULSE] != wantsimpulse)
999 			if((d->action[AC_IMPULSE] = !d->action[AC_IMPULSE]) == true) d->actiontime[AC_IMPULSE] = lastmillis;
1000 
1001 		if(d->ai->dontmove) d->move = d->strafe = 0;
1002 		else
1003 		{ // our guys move one way.. but turn another?! :)
1004 			const struct aimdir { int move, strafe, offset; } aimdirs[8] =
1005 			{
1006 				{  1,  0,   0 },
1007 				{  1,  -1,  45 },
1008 				{  0,  -1,  90 },
1009 				{ -1,  -1, 135 },
1010 				{ -1,  0, 180 },
1011 				{ -1, 1, 225 },
1012 				{  0, 1, 270 },
1013 				{  1, 1, 315 }
1014 			};
1015 			float yaw = d->aimyaw-d->yaw;
1016 			while(yaw < 0.0f) yaw += 360.0f;
1017 			while(yaw >= 360.0f) yaw -= 360.0f;
1018 			int r = clamp(((int)floor((yaw+22.5f)/45.0f))&7, 0, 7);
1019 			const aimdir &ad = aimdirs[r];
1020 			d->move = ad.move;
1021 			d->strafe = ad.strafe;
1022 			d->aimyaw -= ad.offset;
1023 			game::fixrange(d->aimyaw, d->aimpitch);
1024 		}
1025 		return result;
1026 	}
1027 
request(gameent * d,aistate & b)1028 	bool request(gameent *d, aistate &b)
1029 	{
1030 		int busy = process(d, b), sweap = m_weapon(game::gamemode, game::mutators);
1031 		if(d->aitype == AI_BOT)
1032 		{
1033 			bool haswaited = d->weapwaited(d->weapselect, lastmillis, d->skipwait(d->weapselect, 0, lastmillis, (1<<WEAP_S_RELOAD), true));
1034 			if(busy <= 1 && !m_noitems(game::gamemode, game::mutators) && d->carry(sweap, 1, d->hasweap(d->arenaweap, sweap) ? d->arenaweap : d->weapselect) > 0)
1035 			{
1036 				loopirev(WEAP_SUPER) if(i != WEAP_GRENADE && i != d->arenaweap && i != d->weapselect && entities::ents.inrange(d->entid[i]))
1037 				{
1038 					client::addmsg(SV_DROP, "ri3", d->clientnum, lastmillis-game::maptime, i);
1039 					d->setweapstate(d->weapselect, WEAP_S_WAIT, WEAPSWITCHDELAY, lastmillis);
1040 					d->ai->lastaction = lastmillis;
1041 					break;
1042 				}
1043 			}
1044 			if(game::allowmove(d) && busy <= 3 && !d->action[AC_USE] && haswaited)
1045 			{
1046 				static vector<actitem> actitems;
1047 				actitems.setsizenodelete(0);
1048 				if(entities::collateitems(d, actitems))
1049 				{
1050 					while(!actitems.empty())
1051 					{
1052 						actitem &t = actitems.last();
1053 						int ent = -1;
1054 						switch(t.type)
1055 						{
1056 							case ITEM_ENT:
1057 							{
1058 								if(!entities::ents.inrange(t.target)) break;
1059 								extentity &e = *entities::ents[t.target];
1060 								if(enttype[e.type].usetype != EU_ITEM) break;
1061 								if(!busy || (b.type == AI_S_INTEREST && b.targtype == AI_T_ENTITY && b.target == t.target))
1062 									ent = t.target;
1063 								break;
1064 							}
1065 							case ITEM_PROJ:
1066 							{
1067 								if(!projs::projs.inrange(t.target)) break;
1068 								projent &proj = *projs::projs[t.target];
1069 								if(!entities::ents.inrange(proj.id)) break;
1070 								extentity &e = *entities::ents[proj.id];
1071 								if(enttype[e.type].usetype != EU_ITEM || proj.owner == d) break;
1072 								if(!busy || (b.type == AI_S_INTEREST && b.targtype == AI_T_DROP && b.target == t.target))
1073 									ent = proj.id;
1074 								break;
1075 							}
1076 							default: break;
1077 						}
1078 						if(entities::ents.inrange(ent))
1079 						{
1080 							extentity &e = *entities::ents[ent];
1081 							if(enttype[e.type].usetype == EU_ITEM)
1082 							{
1083 								if(m_noitems(game::gamemode, game::mutators)) continue;
1084 								int attr = e.type == WEAPON ? w_attr(game::gamemode, e.attrs[0], sweap) : e.attrs[0];
1085 								if(d->canuse(e.type, attr, e.attrs, sweap, lastmillis, (1<<WEAP_S_RELOAD)|(1<<WEAP_S_SWITCH))) switch(e.type)
1086 								{
1087 									case WEAPON:
1088 									{
1089 										if(d->hasweap(d->arenaweap, sweap) || d->hasweap(attr, sweap)) break;
1090 										d->action[AC_USE] = true;
1091 										d->ai->lastaction = d->actiontime[AC_USE] = lastmillis;
1092 										return true;
1093 										break;
1094 									}
1095 									default: break;
1096 								}
1097 							}
1098 						}
1099 						actitems.pop();
1100 					}
1101 				}
1102 			}
1103 		}
1104 
1105 		if(busy <= 3 && d->weapwaited(d->weapselect, lastmillis, d->skipwait(d->weapselect, 0, lastmillis, (1<<WEAP_S_RELOAD), true)))
1106 		{
1107 			int weap = d->arenaweap;
1108 			if(!isweap(weap) || !d->hasweap(d->arenaweap, sweap))
1109 			{
1110 				loopirev(WEAP_MAX) if(d->hasweap(i, sweap)) { weap = i; break; }
1111 			}
1112 			if(isweap(weap) && weap != d->weapselect && weapons::weapselect(d, weap))
1113 			{
1114 				d->ai->lastaction = lastmillis;
1115 				return true;
1116 			}
1117 		}
1118 
1119 		if(d->hasweap(d->weapselect, sweap) && busy <= (!d->ammo[d->weapselect] ? 3 : 1) && weapons::weapreload(d, d->weapselect))
1120 		{
1121 			d->ai->lastaction = lastmillis;
1122 			return true;
1123 		}
1124 
1125 		return busy >= 2;
1126 	}
1127 
timeouts(gameent * d,aistate & b)1128 	void timeouts(gameent *d, aistate &b)
1129 	{
1130 		if(d->blocked)
1131 		{
1132 			d->ai->blocktime += lastmillis-d->ai->lastrun;
1133 			if(d->ai->blocktime > (d->ai->blockseq+1)*1000)
1134 			{
1135 				switch(d->ai->blockseq)
1136 				{
1137 					case 0: case 1: case 2:
1138 						if(entities::ents.inrange(d->ai->targnode)) d->ai->addprevnode(d->ai->targnode);
1139 						d->ai->clear(false);
1140 						break;
1141 					case 3: d->ai->reset(true); break;
1142 					case 4: d->ai->reset(false); break;
1143 					case 5: game::suicide(d, HIT_LOST); return; break;
1144 					default: break;
1145 				}
1146 				d->ai->blockseq++;
1147 			}
1148 		}
1149 		else d->ai->blocktime = d->ai->blockseq = 0;
1150 		if(d->ai->lasthunt)
1151 		{
1152 			int millis = lastmillis-d->ai->lasthunt;
1153 			if(millis <= 2000) { d->ai->tryreset = false; d->ai->huntseq = 0; }
1154 			else if(millis > (d->ai->huntseq+1)*2000)
1155 			{
1156 				switch(d->ai->huntseq)
1157 				{
1158 					case 0: d->ai->reset(true); break;
1159 					case 1: d->ai->reset(false); break;
1160 					case 2: game::suicide(d, HIT_LOST); return; break;
1161 					default: break;
1162 				}
1163 				d->ai->huntseq++;
1164 			}
1165 		}
1166 		if(d->ai->targnode == d->ai->targlast)
1167 		{
1168 			d->ai->targtime += lastmillis-d->ai->lastrun;
1169 			if(d->ai->targtime > (d->ai->targseq+1)*4000)
1170 			{
1171 				switch(d->ai->targseq)
1172 				{
1173 					case 0: case 1: case 2:
1174 						if(entities::ents.inrange(d->ai->targnode)) d->ai->addprevnode(d->ai->targnode);
1175 						d->ai->clear(false);
1176 						break;
1177 					case 3: d->ai->reset(true); break;
1178 					case 4: d->ai->reset(false); break;
1179 					case 5: game::suicide(d, HIT_LOST); return; break;
1180 					default: break;
1181 				}
1182 				d->ai->targseq++;
1183 			}
1184 		}
1185 		else
1186 		{
1187 			d->ai->targtime = d->ai->targseq = 0;
1188 			d->ai->targlast = d->ai->targnode;
1189 		}
1190 	}
1191 
logic(gameent * d,aistate & b,bool run)1192 	void logic(gameent *d, aistate &b, bool run)
1193 	{
1194 		if(!d->ai->suspended)
1195 		{
1196 			vec dp = d->headpos();
1197 			findorientation(dp, d->yaw, d->pitch, d->ai->target);
1198 			bool allowmove = game::allowmove(d);
1199 			if(d->state != CS_ALIVE || !allowmove) d->stopmoving(true);
1200 			if(d->state == CS_ALIVE && allowmove)
1201 			{
1202 				if(!request(d, b)) target(d, b, false, b.idle > 0 ? true : false);
1203 				weapons::shoot(d, d->ai->target);
1204 			}
1205 		}
1206         if(d->state == CS_DEAD || d->state == CS_WAITING)
1207         {
1208         	if(d->ragdoll) moveragdoll(d, true);
1209 			else if(lastmillis-d->lastpain < 2000)
1210 				physics::move(d, 1, false);
1211         }
1212 		else
1213         {
1214             if(d->ragdoll) cleanragdoll(d);
1215             if(d->state == CS_ALIVE && !game::intermission)
1216             {
1217             	if(!aisuspend && !d->ai->suspended)
1218             	{
1219 					bool ladder = d->onladder;
1220 					physics::move(d, 1, true);
1221 					if(aistyle[d->aitype].maxspeed) timeouts(d, b);
1222 					if(!ladder && d->onladder) d->ai->jumpseed = lastmillis;
1223 					entities::checkitems(d);
1224             	}
1225             	else
1226             	{
1227             		d->move = d->strafe = 0;
1228             		physics::move(d, 1, true);
1229             	}
1230             }
1231         }
1232 		d->action[AC_ATTACK] = d->action[AC_ALTERNATE] = d->action[AC_RELOAD] = d->action[AC_USE] = false;
1233 	}
1234 
avoid()1235 	void avoid()
1236 	{
1237 		obs.clear();
1238 		loopi(game::numdynents())
1239 		{
1240 			gameent *d = (gameent *)game::iterdynents(i);
1241 			if(!d) continue;
1242 			if(!d->ai && !d->airnodes.empty()) loopvj(d->airnodes)
1243 				if(entities::ents.inrange(d->airnodes[j]) && entities::ents[d->airnodes[j]]->type == WAYPOINT)
1244 					obs.add(d, d->airnodes[j]);
1245 			if(d->state != CS_ALIVE || !physics::issolid(d)) continue;
1246 			vec pos = d->feetpos();
1247 			float limit = enttype[WAYPOINT].radius+d->radius;
1248             obs.avoidnear(d, pos, limit);
1249 		}
1250 		loopv(projs::projs)
1251 		{
1252 			projent *p = projs::projs[i];
1253 			if(p && p->state == CS_ALIVE && p->projtype == PRJ_SHOT && weaptype[p->weap].explode[p->flags&HIT_ALT ? 1 : 0])
1254 			{
1255 				float limit = enttype[WAYPOINT].radius+(weaptype[p->weap].explode[p->flags&HIT_ALT ? 1 : 0]*p->lifesize);
1256                 obs.avoidnear(p, p->o, limit);
1257 			}
1258 		}
1259 		loopi(entities::lastenttype[MAPMODEL]) if(entities::ents[i]->type == MAPMODEL && entities::ents[i]->lastemit < 0 && !entities::ents[i]->spawned)
1260 		{
1261 			mapmodelinfo &mmi = getmminfo(entities::ents[i]->attrs[0]);
1262 			vec center, radius;
1263 			mmi.m->collisionbox(0, center, radius);
1264 			if(!mmi.m->ellipsecollide) rotatebb(center, radius, int(entities::ents[i]->attrs[1]));
1265 			float limit = enttype[WAYPOINT].radius+(max(radius.x, max(radius.y, radius.z))*mmi.m->height);
1266 			vec pos = entities::ents[i]->o; pos.z += limit*0.5f;
1267 			obs.avoidnear(NULL, pos, limit);
1268 		}
1269 	}
1270 
think(gameent * d,bool run)1271 	void think(gameent *d, bool run)
1272 	{
1273 		// the state stack works like a chain of commands, certain commands simply replace each other
1274 		// others spawn new commands to the stack the ai reads the top command from the stack and executes
1275 		// it or pops the stack and goes back along the history until it finds a suitable command to execute
1276 		if(d->ai->state.empty())
1277 		{
1278 			d->ai->cleartimers();
1279 			setup(d, false, d->aientity);
1280 		}
1281 		bool cleannext = false;
1282 		loopvrev(d->ai->state)
1283 		{
1284 			aistate &c = d->ai->state[i];
1285 			if(cleannext)
1286 			{
1287 				c.millis = lastmillis;
1288 				c.override = false;
1289 				cleannext = false;
1290 			}
1291 			if(d->state == CS_DEAD && (d->aitype == AI_BOT || !m_story(game::gamemode)) && (d->respawned < 0 || lastmillis-d->respawned >= PHYSMILLIS*12) && (!d->lastdeath || lastmillis-d->lastdeath > (d->aitype == AI_BOT || m_duke(game::gamemode, game::mutators) ? 500 : 30000)))
1292 			{
1293 				if(m_arena(game::gamemode, game::mutators)) client::addmsg(SV_ARENAWEAP, "ri2", d->clientnum, d->arenaweap);
1294 				client::addmsg(SV_TRYSPAWN, "ri", d->clientnum);
1295 				d->respawned = lastmillis;
1296 			}
1297 			else if(d->state == CS_ALIVE && run && lastmillis >= c.next)
1298 			{
1299 				int result = 0; c.idle = 0;
1300 				switch(c.type)
1301 				{
1302 					case AI_S_WAIT: result = dowait(d, c); break;
1303 					case AI_S_DEFEND: result = dodefend(d, c); break;
1304 					case AI_S_PURSUE: result = dopursue(d, c); break;
1305 					case AI_S_INTEREST: result = dointerest(d, c); break;
1306 					default: result = 0; break;
1307 				}
1308 				if(result <= 0)
1309 				{
1310 					d->ai->clear(true);
1311 					if(c.type != AI_S_WAIT)
1312 					{
1313 						switch(result)
1314 						{
1315 							case 0: default: d->ai->removestate(i); cleannext = true; break;
1316 							case -1: i = d->ai->state.length()-1; break;
1317 						}
1318 						continue; // shouldn't interfere
1319 					}
1320 					else c.next = lastmillis+250+rnd(250);
1321 				}
1322 				else
1323 				{
1324 					if(d->aitype >= AI_START) d->ai->suspended = false;
1325 					c.next = lastmillis+125+rnd(125);
1326 				}
1327 			}
1328 			logic(d, c, run);
1329 			break;
1330 		}
1331 		if(d->ai->trywipe) d->ai->wipe();
1332 		d->ai->lastrun = lastmillis;
1333 	}
1334 
drawstate(gameent * d,aistate & b,bool top,int above)1335 	void drawstate(gameent *d, aistate &b, bool top, int above)
1336 	{
1337 		const char *bnames[AI_S_MAX] = {
1338 			"wait", "defend", "pursue", "interest"
1339 		}, *btypes[AI_T_MAX+1] = {
1340 			"none", "node", "player", "affinity", "entity", "drop"
1341 		};
1342 		mkstring(s);
1343 		if(top)
1344 		{
1345 			formatstring(s)("<default>\fg%s (%d[%d]) %s:%d (%d[%d])",
1346 				bnames[b.type],
1347 				lastmillis-b.millis, b.next-lastmillis,
1348 				btypes[b.targtype+1], b.target,
1349 				!d->ai->route.empty() ? d->ai->route[0] : -1,
1350 				d->ai->route.length()
1351 			);
1352 		}
1353 		else
1354 		{
1355 			formatstring(s)("<sub>\fy%s (%d[%d]) %s:%d",
1356 				bnames[b.type],
1357 				lastmillis-b.millis, b.next-lastmillis,
1358 				btypes[b.targtype+1], b.target
1359 			);
1360 		}
1361 		if(s[0]) part_textcopy(vec(d->abovehead()).add(vec(0, 0, above)), s);
1362 	}
1363 
drawroute(gameent * d,aistate & b,float amt)1364 	void drawroute(gameent *d, aistate &b, float amt)
1365 	{
1366 		int colour = teamtype[owner(d)].colour, last = -1;
1367 		loopvrev(d->ai->route)
1368 		{
1369 			if(d->ai->route.inrange(last))
1370 			{
1371 				int index = d->ai->route[i], prev = d->ai->route[last];
1372 				if(entities::ents.inrange(index) && entities::ents.inrange(prev))
1373 				{
1374 					gameentity &e = *(gameentity *)entities::ents[index], &f = *(gameentity *)entities::ents[prev];
1375 					vec fr = f.o, dr = e.o;
1376 					fr.z += amt; dr.z += amt;
1377 					part_trace(fr, dr, 1, 1, 1, colour);
1378 				}
1379 			}
1380 			last = i;
1381 		}
1382 		if(aidebug > 4)
1383 		{
1384 			vec fr = vec(d->feetpos()).add(vec(0, 0, amt));
1385 			if(d->ai->spot != vec(0, 0, 0)) part_trace(fr, vec(d->ai->spot).add(vec(0, 0, 0.1f)), 1, 1, 1, 0x008888);
1386 			if(entities::ents.inrange(d->lastnode))
1387 			{
1388 				vec dr = vec(entities::ents[d->lastnode]->o).add(vec(0, 0, amt));
1389 				part_trace(fr, dr, 1, 1, 1, 0x884400);
1390 				fr = dr;
1391 			}
1392 			loopi(NUMPREVNODES)
1393 			{
1394 				if(entities::ents.inrange(d->ai->prevnodes[i]))
1395 				{
1396 					vec dr = vec(entities::ents[d->ai->prevnodes[i]]->o).add(vec(0, 0, amt));
1397 					part_trace(dr, fr, 1, 1, 1, 0x442200);
1398 					fr = dr;
1399 				}
1400 			}
1401 		}
1402 	}
1403 
render()1404 	void render()
1405 	{
1406 		if(aidebug > 1)
1407 		{
1408 			int amt[2] = { 0, 0 };
1409 			loopv(game::players) if(game::players[i] && game::players[i]->ai) amt[0]++;
1410 			loopv(game::players) if(game::players[i] && game::players[i]->state == CS_ALIVE && game::players[i]->ai)
1411 			{
1412 				gameent *d = game::players[i];
1413 				bool top = true;
1414 				int above = 0;
1415 				amt[1]++;
1416 				loopvrev(d->ai->state)
1417 				{
1418 					aistate &b = d->ai->state[i];
1419 					drawstate(d, b, top, above += 2);
1420 					if(aidebug > 3 && top && rendernormally && b.type != AI_S_WAIT && aistyle[d->aitype].maxspeed)
1421 						drawroute(d, b, 4.f*(float(amt[1])/float(amt[0])));
1422 					if(top)
1423 					{
1424 						if(aidebug > 2) top = false;
1425 						else break;
1426 					}
1427 				}
1428 			}
1429 			if(aidebug >= 4 && !m_edit(game::gamemode))
1430 			{
1431 				int cur = 0;
1432 				loopv(obs.obstacles)
1433 				{
1434 					const entities::avoidset::obstacle &ob = obs.obstacles[i];
1435 					int next = cur + ob.numentities;
1436 					for(; cur < next; cur++)
1437 					{
1438 						int ent = obs.entities[cur];
1439 						gameentity &e = *(gameentity *)entities::ents[ent];
1440 						part_create(PART_EDIT, 1, e.o, 0xFF6600, 1.5f);
1441 					}
1442 					cur = next;
1443 				}
1444 			}
1445 		}
1446 	}
1447 }
1448