1 /*
2 Copyright (C) 1997-2001 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 */
20 
21 #include "../g_local.h"
22 #include "ai_local.h"
23 
24 //ACE
25 
26 
27 //==========================================
28 // AI_Init
29 // Inits global parameters
30 //==========================================
AI_Init(void)31 void AI_Init(void)
32 {
33 	//Init developer mode
34 	bot_showpath = gi.cvar("bot_showpath", "0", CVAR_SERVERINFO);
35 	bot_showcombat = gi.cvar("bot_showcombat", "0", CVAR_SERVERINFO);
36 	bot_showsrgoal = gi.cvar("bot_showsrgoal", "0", CVAR_SERVERINFO);
37 	bot_showlrgoal = gi.cvar("bot_showlrgoal", "0", CVAR_SERVERINFO);
38 	bot_debugmonster = gi.cvar("bot_debugmonster", "0", CVAR_SERVERINFO|CVAR_ARCHIVE);
39 
40 	AIDevel.debugMode = false;
41 	AIDevel.debugChased = false;
42 	AIDevel.chaseguy = NULL;
43 	AIDevel.showPLinks = false;
44 	AIDevel.plinkguy = NULL;
45 }
46 
47 //==========================================
48 // AI_NewMap
49 // Inits Map local parameters
50 //==========================================
AI_NewMap(void)51 void AI_NewMap(void)
52 {
53 	//Load nodes
54 	AI_InitNavigationData();
55 	AI_InitAIWeapons ();
56 }
57 
58 //==========================================
59 // G_FreeAI
60 // removes the AI handle from memory
61 //==========================================
G_FreeAI(edict_t * ent)62 void G_FreeAI( edict_t *ent )
63 {
64 	if( !ent->ai ) return;
65 
66 	gi.TagFree (ent->ai);
67 	ent->ai = NULL;
68 }
69 
70 //==========================================
71 // G_SpawnAI
72 // allocate ai_handle_t for this entity
73 //==========================================
G_SpawnAI(edict_t * ent)74 void G_SpawnAI( edict_t *ent )
75 {
76 	if( !ent->ai )
77 		ent->ai = gi.TagMalloc (sizeof(ai_handle_t), TAG_LEVEL);
78 
79 	memset( ent->ai, 0, sizeof(ai_handle_t));
80 }
81 
82 //==========================================
83 // AI_SetUpMoveWander
84 //==========================================
AI_SetUpMoveWander(edict_t * ent)85 void AI_SetUpMoveWander( edict_t *ent )
86 {
87 	ent->ai->state = BOT_STATE_WANDER;
88 	ent->ai->wander_timeout = level.time + 1.0;
89 	ent->ai->nearest_node_tries = 0;
90 
91 	ent->ai->next_move_time = level.time;
92 	ent->ai->bloqued_timeout = level.time + 15.0;
93 
94 	ent->ai->goal_node = INVALID;
95 	ent->ai->current_node = INVALID;
96 	ent->ai->next_node = INVALID;
97 }
98 
99 
100 //==========================================
101 // AI_ResetWeights
102 // Init bot weights from bot-class weights.
103 //==========================================
AI_ResetWeights(edict_t * ent)104 void AI_ResetWeights(edict_t *ent)
105 {
106 	//restore defaults from bot persistant
107 	memset(ent->ai->status.inventoryWeights, 0, sizeof (ent->ai->status.inventoryWeights));
108 	memcpy(ent->ai->status.inventoryWeights, ent->ai->pers.inventoryWeights, sizeof(ent->ai->pers.inventoryWeights));
109 }
110 
111 
112 //==========================================
113 // AI_ResetNavigation
114 // Init bot navigation. Called at first spawn & each respawn
115 //==========================================
AI_ResetNavigation(edict_t * ent)116 void AI_ResetNavigation(edict_t *ent)
117 {
118 	int		i;
119 
120 	ent->enemy = NULL;
121 	ent->movetarget = NULL;
122 	ent->ai->state_combat_timeout = 0.0;
123 
124 	ent->ai->state = BOT_STATE_WANDER;
125 	ent->ai->wander_timeout = level.time;
126 	ent->ai->nearest_node_tries = 0;
127 
128 	ent->ai->next_move_time = level.time;
129 	ent->ai->bloqued_timeout = level.time + 15.0;
130 
131 	ent->ai->goal_node = INVALID;
132 	ent->ai->current_node = INVALID;
133 	ent->ai->next_node = INVALID;
134 
135 	VectorSet( ent->ai->move_vector, 0, 0, 0 );
136 
137 	//reset bot_roams timeouts
138 	for( i=0; i<nav.num_broams; i++)
139 		ent->ai->status.broam_timeouts[i] = 0.0;
140 }
141 
142 
143 //==========================================
144 // AI_BotRoamForLRGoal
145 //
146 // Try assigning a bot roam node as LR Goal
147 //==========================================
AI_BotRoamForLRGoal(edict_t * self,int current_node)148 qboolean AI_BotRoamForLRGoal(edict_t *self, int current_node)
149 {
150 	int		i;
151 	float	cost;
152 	float	weight, best_weight = 0.0;
153 	int		goal_node = INVALID;
154 	int		best_broam = INVALID;
155 	float	dist;
156 
157 	if (!nav.num_broams)
158 		return false;
159 
160 	for( i=0; i<nav.num_broams; i++)
161 	{
162 		if( self->ai->status.broam_timeouts[i] > level.time)
163 			continue;
164 
165 		//limit cost finding by distance
166 		dist = AI_Distance( self->s.origin, nodes[nav.broams[i].node].origin );
167 		if( dist > 10000 )
168 			continue;
169 
170 		//find cost
171 		cost = AI_FindCost(current_node, nav.broams[i].node, self->ai->pers.moveTypesMask);
172 		if(cost == INVALID || cost < 3) // ignore invalid and very short hops
173 			continue;
174 
175 		cost *= random(); // Allow random variations for broams
176 		weight = nav.broams[i].weight / cost;	// Check against cost of getting there
177 
178 		if(weight > best_weight)
179 		{
180 			best_weight = weight;
181 			goal_node = nav.broams[i].node;
182 			best_broam = i;
183 		}
184 	}
185 
186 	if(best_weight == 0.0 || goal_node == INVALID)
187 		return false;
188 
189 	//set up the goal
190 	self->ai->state = BOT_STATE_MOVE;
191 	self->ai->tries = 0;	// Reset the count of how many times we tried this goal
192 
193 //	if(AIDevel.debugChased && bot_showlrgoal->value)
194 //		G_PrintMsg (AIDevel.chaseguy, PRINT_HIGH, "%s: selected a bot roam of weight %f at node %d for LR goal.\n",self->ai->pers.netname, nav.broams[best_broam].weight, goal_node);
195 
196 	AI_SetGoal(self,goal_node);
197 
198 	return true;
199 }
200 
201 //==========================================
202 // AI_PickLongRangeGoal
203 //
204 // Evaluate the best long range goal and send the bot on
205 // its way. This is a good time waster, so use it sparingly.
206 // Do not call it for every think cycle.
207 //
208 // jal: I don't think there is any problem by calling it,
209 // now that we have stored the costs at the nav.costs table (I don't do it anyway)
210 //==========================================
AI_PickLongRangeGoal(edict_t * self)211 void AI_PickLongRangeGoal(edict_t *self)
212 {
213 	int		i;
214 	int		node;
215 	float	weight,best_weight=0.0;
216 	int		current_node, goal_node = INVALID;
217 	edict_t *goal_ent = NULL;
218 	float	cost;
219 	float dist;
220 
221 	// look for a target
222 	current_node = AI_FindClosestReachableNode(self->s.origin, self,((1+self->ai->nearest_node_tries)*NODE_DENSITY),NODE_ALL);
223 	self->ai->current_node = current_node;
224 
225 	if(current_node == -1)	//failed. Go wandering :(
226 	{
227 //		if (AIDevel.debugChased && bot_showlrgoal->value)
228 //			G_PrintMsg (AIDevel.chaseguy, PRINT_HIGH, "%s: LRGOAL: Closest node not found. Tries:%i\n", self->ai->pers.netname, self->ai->nearest_node_tries);
229 
230 		if( self->ai->state != BOT_STATE_WANDER )
231 			AI_SetUpMoveWander( self );
232 
233 		self->ai->wander_timeout = level.time + 1.0;
234 		self->ai->nearest_node_tries++;	//extend search radius with each try
235 		return;
236 	}
237 	self->ai->nearest_node_tries = 0;
238 
239 
240 	// Items
241 	for(i=0;i<nav.num_items;i++)
242 	{
243 		// Ignore items that are not there (solid)
244 		if(!nav.items[i].ent || nav.items[i].ent->solid == SOLID_NOT)
245 			continue;
246 
247 		//ignore items wich can't be weighted (must have a valid item flag)
248 		if( !nav.items[i].ent->item || !(nav.items[i].ent->item->flags & (IT_AMMO|IT_TECH|IT_HEALTH|IT_ARMOR|IT_WEAPON|IT_POWERUP|IT_FLAG)) )
249 			continue;
250 
251 		weight = AI_ItemWeight(self, nav.items[i].ent);
252 		if( weight == 0.0f )	//ignore zero weighted items
253 			continue;
254 
255 		//limit cost finding distance
256 		dist = AI_Distance( self->s.origin, nav.items[i].ent->s.origin );
257 
258 		//different distance limits for different types
259 		if( nav.items[i].ent->item->flags & (IT_AMMO|IT_TECH) && dist > 2000 )
260 			continue;
261 
262 		if( nav.items[i].ent->item->flags & (IT_HEALTH|IT_ARMOR|IT_POWERUP) && dist > 4000 )
263 			continue;
264 
265 		if( nav.items[i].ent->item->flags & (IT_WEAPON|IT_FLAG) && dist > 10000 )
266 			continue;
267 
268 		cost = AI_FindCost(current_node, nav.items[i].node, self->ai->pers.moveTypesMask);
269 		if(cost == INVALID || cost < 3) // ignore invalid and very short hops
270 			continue;
271 
272 		//weight *= random(); // Allow random variations
273 		weight /= cost; // Check against cost of getting there
274 
275 		if(weight > best_weight)
276 		{
277 			best_weight = weight;
278 			goal_node = nav.items[i].node;
279 			goal_ent = nav.items[i].ent;
280 		}
281 	}
282 
283 
284 	// Players: This should be its own function and is for now just finds a player to set as the goal.
285 	for( i=0; i<num_AIEnemies; i++ )
286 	{
287 		//ignore self & spectators
288 		if( AIEnemies[i] == self || AIEnemies[i]->svflags & SVF_NOCLIENT)
289 			continue;
290 
291 		//ignore zero weighted players
292 		if( self->ai->status.playersWeights[i] == 0.0f )
293 			continue;
294 
295 		node = AI_FindClosestReachableNode( AIEnemies[i]->s.origin, AIEnemies[i], NODE_DENSITY, NODE_ALL);
296 		cost = AI_FindCost(current_node, node, self->ai->pers.moveTypesMask);
297 
298 		if(cost == INVALID || cost < 4) // ignore invalid and very short hops
299 			continue;
300 
301 		//precomputed player weights
302 		weight = self->ai->status.playersWeights[i];
303 
304 		//weight *= random(); // Allow random variations
305 		weight /= cost; // Check against cost of getting there
306 
307 		if(weight > best_weight)
308 		{
309 			best_weight = weight;
310 			goal_node = node;
311 			goal_ent = AIEnemies[i];
312 		}
313 	}
314 
315 	// If do not find a goal, go wandering....
316 	if(best_weight == 0.0 || goal_node == INVALID)
317 	{
318 		//BOT_ROAMS
319 		if (!AI_BotRoamForLRGoal(self, current_node))
320 		{
321 			self->ai->goal_node = INVALID;
322 			self->ai->state = BOT_STATE_WANDER;
323 			self->ai->wander_timeout = level.time + 1.0;
324 //			if(AIDevel.debugChased && bot_showlrgoal->value)
325 //				G_PrintMsg (AIDevel.chaseguy, PRINT_HIGH, "%s: did not find a LR goal, wandering.\n",self->ai->pers.netname);
326 		}
327 		return; // no path?
328 	}
329 
330 	// OK, everything valid, let's start moving to our goal.
331 	self->ai->state = BOT_STATE_MOVE;
332 	self->ai->tries = 0;	// Reset the count of how many times we tried this goal
333 
334 //	if(goal_ent != NULL && AIDevel.debugChased && bot_showlrgoal->value)
335 //		G_PrintMsg (AIDevel.chaseguy, PRINT_HIGH, "%s: selected a %s at node %d for LR goal.\n",self->ai->pers.netname, goal_ent->classname, goal_node);
336 
337 	AI_SetGoal(self,goal_node);
338 }
339 
340 
341 //==========================================
342 // AI_PickShortRangeGoal
343 // Pick best goal based on importance and range. This function
344 // overrides the long range goal selection for items that
345 // are very close to the bot and are reachable.
346 //==========================================
AI_PickShortRangeGoal(edict_t * self)347 void AI_PickShortRangeGoal(edict_t *self)
348 {
349 	edict_t *target;
350 	float	weight,best_weight=0.0;
351 	edict_t *best = NULL;
352 
353 	if( !self->client )
354 		return;
355 
356 	// look for a target (should make more efficent later)
357 	target = findradius(NULL, self->s.origin, AI_GOAL_SR_RADIUS);
358 
359 	while(target)
360 	{
361 		if(target->classname == NULL)
362 			return;
363 
364 		// Missile detection code
365 		if(strcmp(target->classname,"rocket")==0 || strcmp(target->classname,"grenade")==0)
366 		{
367 			//if player who shoot is a potential enemy
368 			if (self->ai->status.playersWeights[target->owner->s.number-1])
369 			{
370 //				if(AIDevel.debugChased && bot_showcombat->value)
371 //					G_PrintMsg (AIDevel.chaseguy, PRINT_HIGH, "%s: ROCKET ALERT!\n", self->ai->pers.netname);
372 
373 				self->enemy = target->owner;	// set who fired the rocket as enemy
374 				return;
375 			}
376 		}
377 
378 		if (AI_ItemIsReachable(self,target->s.origin))
379 		{
380 			if (infront(self, target))
381 			{
382 				weight = AI_ItemWeight(self, target);
383 
384 				if(weight > best_weight)
385 				{
386 					best_weight = weight;
387 					best = target;
388 				}
389 			}
390 		}
391 
392 		// next target
393 		target = findradius(target, self->s.origin, AI_GOAL_SR_RADIUS);
394 	}
395 
396 	//jalfixme (what's goalentity doing here?)
397 	if(best_weight)
398 	{
399 		self->movetarget = best;
400 		self->goalentity = best;
401 //		if(AIDevel.debugChased && bot_showsrgoal->value && (self->goalentity != self->movetarget))
402 //			G_PrintMsg (AIDevel.chaseguy, PRINT_HIGH, "%s: selected a %s for SR goal.\n",self->ai->pers.netname, self->movetarget->classname);
403 	}
404 }
405 
406 
407 //===================
408 //  AI_CategorizePosition
409 //  Categorize waterlevel and groundentity/stepping
410 //===================
AI_CategorizePosition(edict_t * ent)411 void AI_CategorizePosition (edict_t *ent)
412 {
413 	qboolean stepping = AI_IsStep(ent);
414 
415 	ent->was_swim = ent->is_swim;
416 	ent->was_step = ent->is_step;
417 
418 	ent->is_ladder = AI_IsLadder( ent->s.origin, ent->s.angles, ent->mins, ent->maxs, ent );
419 
420 	M_CatagorizePosition(ent);
421 	if (ent->waterlevel > 2 || ent->waterlevel && !stepping) {
422 		ent->is_swim = true;
423 		ent->is_step = false;
424 		return;
425 	}
426 
427 	ent->is_swim = false;
428 	ent->is_step = stepping;
429 }
430 
431 
432 //==========================================
433 // AI_Think
434 // think funtion for AIs
435 //==========================================
AI_Think(edict_t * self)436 void AI_Think (edict_t *self)
437 {
438 	if( !self->ai )	//jabot092(2)
439 		return;
440 
441 	AIDebug_SetChased(self);	//jal:debug shit
442 	AI_CategorizePosition(self);
443 
444 	//freeze AI when dead
445 	if( self->deadflag ) {
446 		self->ai->pers.deadFrame(self);
447 		return;
448 	}
449 
450 	//if completely stuck somewhere
451 	if(VectorLength(self->velocity) > 37)
452 		self->ai->bloqued_timeout = level.time + 10.0;
453 
454 	if( self->ai->bloqued_timeout < level.time ) {
455 		self->ai->pers.bloquedTimeout(self);
456 		return;
457 	}
458 
459 	//update status information to feed up ai
460 	self->ai->pers.UpdateStatus(self);
461 
462 	//update position in path, set up move vector
463 	if( self->ai->state == BOT_STATE_MOVE ) {
464 
465 		if( !AI_FollowPath(self) )
466 		{
467 			AI_SetUpMoveWander( self );
468 			self->ai->wander_timeout = level.time - 1;	//do it now
469 		}
470 	}
471 
472 	//pick a new long range goal
473 	if( self->ai->state == BOT_STATE_WANDER && self->ai->wander_timeout < level.time)
474 		AI_PickLongRangeGoal(self);
475 
476 	//Find any short range goal
477 	AI_PickShortRangeGoal(self);
478 
479 	//run class based states machine
480 	self->ai->pers.RunFrame(self);
481 }
482 
483 
484