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