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 //==========================================
29 // Some CTF stuff
30 //==========================================
31 static gitem_t *redflag;
32 static gitem_t *blueflag;
33 
34 
35 //==========================================
36 // BOT_DMclass_Move
37 // DMClass is generic bot class
38 //==========================================
BOT_DMclass_Move(edict_t * self,usercmd_t * ucmd)39 void BOT_DMclass_Move(edict_t *self, usercmd_t *ucmd)
40 {
41 	int current_node_flags = 0;
42 	int next_node_flags = 0;
43 	int	current_link_type = 0;
44 	int i;
45 
46 	current_node_flags = nodes[self->ai->current_node].flags;
47 	next_node_flags = nodes[self->ai->next_node].flags;
48 	if( AI_PlinkExists( self->ai->current_node, self->ai->next_node ))
49 	{
50 		current_link_type = AI_PlinkMoveType( self->ai->current_node, self->ai->next_node );
51 		//Com_Printf("%s\n", AI_LinkString( current_link_type ));
52 	}
53 
54 	// Platforms
55 	if( current_link_type == LINK_PLATFORM )
56 	{
57 		// Move to the center
58 		self->ai->move_vector[2] = 0; // kill z movement
59 		if(VectorLength(self->ai->move_vector) > 10)
60 			ucmd->forwardmove = 200; // walk to center
61 
62 		AI_ChangeAngle(self);
63 
64 		return; // No move, riding elevator
65 	}
66 	else if( next_node_flags & NODEFLAGS_PLATFORM )
67 	{
68 		// is lift down?
69 		for(i=0;i<nav.num_ents;i++){
70 			if( nav.ents[i].node == self->ai->next_node )
71 			{
72 				//testing line
73 				//vec3_t	tPoint;
74 				//int		j;
75 				//for(j=0; j<3; j++)//center of the ent
76 				//	tPoint[j] = nav.ents[i].ent->s.origin[j] + 0.5*(nav.ents[i].ent->mins[j] + nav.ents[i].ent->maxs[j]);
77 				//tPoint[2] = nav.ents[i].ent->s.origin[2] + nav.ents[i].ent->maxs[2];
78 				//tPoint[2] += 8;
79 				//AITools_DrawLine( self->s.origin, tPoint );
80 
81 				//if not reachable, wait for it (only height matters)
82 				if( ((nav.ents[i].ent->s.origin[2] + nav.ents[i].ent->maxs[2])
83 					> (self->s.origin[2] + self->mins[2] + AI_JUMPABLE_HEIGHT) ) &&
84 					nav.ents[i].ent->moveinfo.state != STATE_BOTTOM) //jabot092(2)
85 					return; //wait for elevator
86 			}
87 		}
88 	}
89 
90 	// Ladder movement
91 	if( self->is_ladder )
92 	{
93 		ucmd->forwardmove = 70;
94 		ucmd->upmove = 200;
95 		ucmd->sidemove = 0;
96 		return;
97 	}
98 
99 	// Falling off ledge
100 	if(!self->groundentity && !self->is_step && !self->is_swim )
101 	{
102 		AI_ChangeAngle(self);
103 		if (current_link_type == LINK_JUMPPAD ) {
104 			ucmd->forwardmove = 100;
105 		} else if( current_link_type == LINK_JUMP ) {
106 			self->velocity[0] = self->ai->move_vector[0] * 280;
107 			self->velocity[1] = self->ai->move_vector[1] * 280;
108 		} else {
109 			self->velocity[0] = self->ai->move_vector[0] * 160;
110 			self->velocity[1] = self->ai->move_vector[1] * 160;
111 		}
112 		return;
113 	}
114 
115 	// jumping over (keep fall before this)
116 	if( current_link_type == LINK_JUMP && self->groundentity)
117 	{
118 		trace_t trace;
119 		vec3_t  v1, v2;
120 		//check floor in front, if there's none... Jump!
121 		VectorCopy( self->s.origin, v1 );
122 		VectorCopy( self->ai->move_vector, v2 );
123 		VectorNormalize( v2 );
124 		VectorMA( v1, 12, v2, v1 );
125 		v1[2] += self->mins[2];
126 		trace = gi.trace( v1, tv(-2, -2, -AI_JUMPABLE_HEIGHT), tv(2, 2, 0), v1, self, MASK_AISOLID );
127 		if( !trace.startsolid && trace.fraction == 1.0 )
128 		{
129 			//jump!
130 			ucmd->forwardmove = 400;
131 			//prevent double jumping on crates
132 			VectorCopy( self->s.origin, v1 );
133 			v1[2] += self->mins[2];
134 			trace = gi.trace( v1, tv(-12, -12, -8), tv(12, 12, 0), v1, self, MASK_AISOLID );
135 			if( trace.startsolid )
136 				ucmd->upmove = 400;
137 			return;
138 		}
139 	}
140 
141 	// Move To Short Range goal (not following paths)
142 	// plats, grapple, etc have higher priority than SR Goals, cause the bot will
143 	// drop from them and have to repeat the process from the beginning
144 	if (AI_MoveToGoalEntity(self,ucmd))
145 		return;
146 
147 	// swimming
148 	if( self->is_swim )
149 	{
150 		// We need to be pointed up/down
151 		AI_ChangeAngle(self);
152 
153 		if( !(gi.pointcontents(nodes[self->ai->next_node].origin) & MASK_WATER) ) // Exit water
154 			ucmd->upmove = 400;
155 
156 		ucmd->forwardmove = 300;
157 		return;
158 	}
159 
160 	// Check to see if stuck, and if so try to free us
161  	if(VectorLength(self->velocity) < 37)
162 	{
163 		// Keep a random factor just in case....
164 		if( random() > 0.1 && AI_SpecialMove(self, ucmd) ) //jumps, crouches, turns...
165 			return;
166 
167 		self->s.angles[YAW] += random() * 180 - 90;
168 
169 		AI_ChangeAngle(self);
170 
171 		ucmd->forwardmove = 400;
172 
173 		return;
174 	}
175 
176 
177 	AI_ChangeAngle(self);
178 
179 	// Otherwise move as fast as we can...
180 	ucmd->forwardmove = 400;
181 }
182 
183 
184 //==========================================
185 // BOT_DMclass_Wander
186 // Wandering code (based on old ACE movement code)
187 //==========================================
BOT_DMclass_Wander(edict_t * self,usercmd_t * ucmd)188 void BOT_DMclass_Wander(edict_t *self, usercmd_t *ucmd)
189 {
190 	vec3_t  temp;
191 
192 	// Do not move
193 	if(self->ai->next_move_time > level.time)
194 		return;
195 
196 	if (self->deadflag)
197 		return;
198 
199 	// Special check for elevators, stand still until the ride comes to a complete stop.
200 	if(self->groundentity != NULL && self->groundentity->use == Use_Plat)
201 	{
202 		if(self->groundentity->moveinfo.state == STATE_UP ||
203 		   self->groundentity->moveinfo.state == STATE_DOWN)
204 		{
205 			self->velocity[0] = 0;
206 			self->velocity[1] = 0;
207 			self->velocity[2] = 0;
208 			self->ai->next_move_time = level.time + 0.5;
209 			return;
210 		}
211 	}
212 
213 	// Move To Goal (Short Range Goal, not following paths)
214 	if (AI_MoveToGoalEntity(self,ucmd))
215 		return;
216 
217 	// Swimming?
218 	VectorCopy(self->s.origin,temp);
219 	temp[2]+=24;
220 
221 //	if(trap_PointContents (temp) & MASK_WATER)
222 	if( gi.pointcontents (temp) & MASK_WATER)
223 	{
224 		// If drowning and no node, move up
225 		if( self->client && self->client->next_drown_time > 0 )	//jalfixme: client references must pass into botStatus
226 		{
227 			ucmd->upmove = 100;
228 			self->s.angles[PITCH] = -45;
229 		}
230 		else
231 			ucmd->upmove = 15;
232 
233 		ucmd->forwardmove = 300;
234 	}
235 	// else self->client->next_drown_time = 0; // probably shound not be messing with this, but
236 
237 
238 	// Lava?
239 	temp[2]-=48;
240 	//if(trap_PointContents(temp) & (CONTENTS_LAVA|CONTENTS_SLIME))
241 	if( gi.pointcontents(temp) & (CONTENTS_LAVA|CONTENTS_SLIME) )
242 	{
243 		self->s.angles[YAW] += random() * 360 - 180;
244 		ucmd->forwardmove = 400;
245 		if(self->groundentity)
246 			ucmd->upmove = 400;
247 		else
248 			ucmd->upmove = 0;
249 		return;
250 	}
251 
252 
253 	// Check for special movement
254  	if(VectorLength(self->velocity) < 37)
255 	{
256 		if(random() > 0.1 && AI_SpecialMove(self,ucmd))	//jumps, crouches, turns...
257 			return;
258 
259 		self->s.angles[YAW] += random() * 180 - 90;
260 
261 		if (!self->is_step)// if there is ground continue otherwise wait for next move
262 			ucmd->forwardmove = 0; //0
263 		else if( AI_CanMove( self, BOT_MOVE_FORWARD))
264 			ucmd->forwardmove = 100;
265 
266 		return;
267 	}
268 
269 
270 	// Otherwise move slowly, walking wondering what's going on
271 	if( AI_CanMove( self, BOT_MOVE_FORWARD))
272 		ucmd->forwardmove = 100;
273 	else
274 		ucmd->forwardmove = -100;
275 }
276 
277 
278 //==========================================
279 // BOT_DMclass_CombatMovement
280 //
281 // NOTE: Very simple for now, just a basic move about avoidance.
282 //       Change this routine for more advanced attack movement.
283 //==========================================
BOT_DMclass_CombatMovement(edict_t * self,usercmd_t * ucmd)284 void BOT_DMclass_CombatMovement( edict_t *self, usercmd_t *ucmd )
285 {
286 	float	c;
287 	vec3_t	attackvector;
288 	float	dist;
289 
290 	//jalToDo: Convert CombatMovement to a BOT_STATE, allowing
291 	//it to dodge, but still follow paths, chasing enemy or
292 	//running away... hmmm... maybe it will need 2 different BOT_STATEs
293 
294 	if(!self->enemy) {
295 
296 		//do whatever (tmp move wander)
297 		if( AI_FollowPath(self) )
298 			BOT_DMclass_Move(self, ucmd);
299 		return;
300 	}
301 
302 	// Randomly choose a movement direction
303 	c = random();
304 
305 	if(c < 0.2 && AI_CanMove(self,BOT_MOVE_LEFT))
306 		ucmd->sidemove -= 400;
307 	else if(c < 0.4 && AI_CanMove(self,BOT_MOVE_RIGHT))
308 		ucmd->sidemove += 400;
309 	else if(c < 0.6 && AI_CanMove(self,BOT_MOVE_FORWARD))
310 		ucmd->forwardmove += 400;
311 	else if(c < 0.8 && AI_CanMove(self,BOT_MOVE_BACK))
312 		ucmd->forwardmove -= 400;
313 
314 
315 	VectorSubtract( self->s.origin, self->enemy->s.origin, attackvector);
316 	dist = VectorLength( attackvector);
317 
318 	if(dist < 75)
319 		ucmd->forwardmove -= 200;
320 }
321 
322 
323 //==========================================
324 // BOT_DMclass_CheckShot
325 // Checks if shot is blocked or if too far to shoot
326 //==========================================
BOT_DMclass_CheckShot(edict_t * ent,vec3_t point)327 qboolean BOT_DMclass_CheckShot(edict_t *ent, vec3_t	point)
328 {
329 	trace_t tr;
330 	vec3_t	start, forward, right, offset;
331 
332 	AngleVectors (ent->client->v_angle, forward, right, NULL);
333 
334 	VectorSet(offset, 8, 8, ent->viewheight-8);
335 	P_ProjectSource (ent->client, ent->s.origin, offset, forward, right, start);
336 
337 	//bloqued, don't shoot
338 	tr = gi.trace( start, vec3_origin, vec3_origin, point, ent, MASK_AISOLID);
339 //	trap_Trace( &tr, self->s.origin, vec3_origin, vec3_origin, point, self, MASK_AISOLID);
340 	if (tr.fraction < 0.3) //just enough to prevent self damage (by now)
341 		return false;
342 
343 	return true;
344 }
345 
346 
347 //==========================================
348 // BOT_DMclass_FindEnemy
349 // Scan for enemy (simplifed for now to just pick any visible enemy)
350 //==========================================
BOT_DMclass_FindEnemy(edict_t * self)351 qboolean BOT_DMclass_FindEnemy(edict_t *self)
352 {
353 	int i;
354 
355 	edict_t		*bestenemy = NULL;
356 	float		bestweight = 99999;
357 	float		weight;
358 	vec3_t		dist;
359 
360 	// we already set up an enemy this frame (reacting to attacks)
361 	if(self->enemy != NULL)
362 		return true;
363 
364 	// Find Enemy
365 	for(i=0;i<num_AIEnemies;i++)
366 	{
367 		if( AIEnemies[i] == NULL || AIEnemies[i] == self
368 			|| AIEnemies[i]->solid == SOLID_NOT)
369 			continue;
370 
371 		//Ignore players with 0 weight (was set at botstatus)
372 		if(self->ai->status.playersWeights[i] == 0)
373 			continue;
374 
375 		if( !AIEnemies[i]->deadflag && visible(self, AIEnemies[i]) &&
376 			//trap_inPVS (self->s.origin, players[i]->s.origin))
377 			gi.inPVS(self->s.origin, AIEnemies[i]->s.origin))
378 		{
379 			//(weight enemies from fusionbot) Is enemy visible, or is it too close to ignore
380 			VectorSubtract(self->s.origin, AIEnemies[i]->s.origin, dist);
381 			weight = VectorLength( dist );
382 
383 			//modify weight based on precomputed player weights
384 			weight *= (1.0 - self->ai->status.playersWeights[i]);
385 
386 			if( infront( self, AIEnemies[i] ) ||
387 				(weight < 300 ) )
388 			{
389 				// Check if best target, or better than current target
390 				if (weight < bestweight)
391 				{
392 					bestweight = weight;
393 					bestenemy = AIEnemies[i];
394 				}
395 			}
396 		}
397 	}
398 
399 	// If best enemy, set up
400 	if(bestenemy)
401 	{
402 //		if (AIDevel.debugChased && bot_showcombat->value && bestenemy->ai->is_bot)
403 //			G_PrintMsg (AIDevel.chaseguy, PRINT_HIGH, "%s: selected %s as enemy.\n",
404 //			self->ai->pers.netname,
405 //			bestenemy->ai->pers.netname);
406 
407 		self->enemy = bestenemy;
408 		return true;
409 	}
410 
411 	return false;	// NO enemy
412 }
413 
414 
415 //==========================================
416 // BOT_DMClass_ChangeWeapon
417 //==========================================
BOT_DMClass_ChangeWeapon(edict_t * ent,gitem_t * item)418 qboolean BOT_DMClass_ChangeWeapon (edict_t *ent, gitem_t *item)
419 {
420 	int			ammo_index;
421 	gitem_t		*ammo_item;
422 
423 	// see if we're already using it
424 	if (!item || item == ent->client->pers.weapon)
425 		return true;
426 
427 	// Has not picked up weapon yet
428 	if(!ent->client->pers.inventory[ITEM_INDEX(item)])
429 		return false;
430 
431 	// Do we have ammo for it?
432 	if (item->ammo)
433 	{
434 		ammo_item = FindItem(item->ammo);
435 		ammo_index = ITEM_INDEX(ammo_item);
436 		if ( !ent->client->pers.inventory[ammo_index] && !g_select_empty->value )
437 			return false;
438 	}
439 
440 	// Change to this weapon
441 	ent->client->newweapon = item;
442 	ent->ai->changeweapon_timeout = level.time + 6.0;
443 
444 	return true;
445 }
446 
447 
448 //==========================================
449 // BOT_DMclass_ChooseWeapon
450 // Choose weapon based on range & weights
451 //==========================================
BOT_DMclass_ChooseWeapon(edict_t * self)452 void BOT_DMclass_ChooseWeapon(edict_t *self)
453 {
454 	float	dist;
455 	vec3_t	v;
456 	int		i;
457 	float	best_weight = 0.0;
458 	gitem_t	*best_weapon = NULL;
459 	int		weapon_range = 0;
460 
461 	// if no enemy, then what are we doing here?
462 	if(!self->enemy)
463 		return;
464 
465 	if( self->ai->changeweapon_timeout > level.time )
466 		return;
467 
468 	// Base weapon selection on distance:
469 	VectorSubtract (self->s.origin, self->enemy->s.origin, v);
470 	dist = VectorLength(v);
471 
472 	if(dist < 150)
473 		weapon_range = AIWEAP_MELEE_RANGE;
474 
475 	else if(dist < 500)	//Medium range limit is Grenade Laucher range
476 		weapon_range = AIWEAP_SHORT_RANGE;
477 
478 	else if(dist < 900)
479 		weapon_range = AIWEAP_MEDIUM_RANGE;
480 
481 	else
482 		weapon_range = AIWEAP_LONG_RANGE;
483 
484 
485 	for(i=0; i<WEAP_TOTAL; i++)
486 	{
487 		if (!AIWeapons[i].weaponItem)
488 			continue;
489 
490 		//ignore those we don't have
491 		if (!self->client->pers.inventory[ITEM_INDEX(AIWeapons[i].weaponItem)] )
492 			continue;
493 
494 		//ignore those we don't have ammo for
495 		if (AIWeapons[i].ammoItem != NULL	//excepting for those not using ammo
496 			&& !self->client->pers.inventory[ITEM_INDEX(AIWeapons[i].ammoItem)] )
497 			continue;
498 
499 		//compare range weights
500 		if (AIWeapons[i].RangeWeight[weapon_range] > best_weight) {
501 			best_weight = AIWeapons[i].RangeWeight[weapon_range];
502 			best_weapon = AIWeapons[i].weaponItem;
503 		}
504 		//jal: enable randomnes later
505 		//else if (AIWeapons[i].RangeWeight[weapon_range] == best_weight && random() > 0.2) {	//allow some random for equal weights
506 		//	best_weight = AIWeapons[i].RangeWeight[weapon_range];
507 		//	best_weapon = AIWeapons[i].weaponItem;
508 		//}
509 	}
510 
511 	//do the change (same weapon, or null best_weapon is covered at ChangeWeapon)
512 	BOT_DMClass_ChangeWeapon( self, best_weapon );
513 
514 	return;
515 }
516 
517 
518 //==========================================
519 // BOT_DMclass_FireWeapon
520 // Fire if needed
521 //==========================================
BOT_DMclass_FireWeapon(edict_t * self,usercmd_t * ucmd)522 void BOT_DMclass_FireWeapon (edict_t *self, usercmd_t *ucmd)
523 {
524 	//float	c;
525 	float	firedelay;
526 	vec3_t  target;
527 	vec3_t  angles;
528 	int		weapon;
529 	//vec3_t	attackvector;
530 	//float	dist;
531 
532 	if (!self->enemy)
533 		return;
534 
535 	//weapon = self->s.skinnum & 0xff;
536 	if (self->client->pers.weapon)
537 			weapon = (self->client->pers.weapon->weapmodel & 0xff);
538 	else
539 		weapon = 0;
540 
541 	//jalToDo: Add different aiming types (explosive aim to legs, hitscan aim to body)
542 
543 	//was find range. I might use it later
544 	//VectorSubtract( self->s.origin, self->enemy->s.origin, attackvector);
545 	//dist = VectorLength( attackvector);
546 
547 
548 	// Aim
549 	VectorCopy(self->enemy->s.origin,target);
550 
551 	// find out our weapon AIM style
552 	if( AIWeapons[weapon].aimType == AI_AIMSTYLE_PREDICTION_EXPLOSIVE )
553 	{
554 		//aim to the feets when enemy isn't higher
555 		if( self->s.origin[2] + self->viewheight > target[2] + (self->enemy->mins[2] * 0.8) )
556 			target[2] += self->enemy->mins[2];
557 	}
558 	else if ( AIWeapons[weapon].aimType == AI_AIMSTYLE_PREDICTION )
559 	{
560 		//jalToDo
561 
562 	}
563 	else if ( AIWeapons[weapon].aimType == AI_AIMSTYLE_DROP )
564 	{
565 		//jalToDo
566 
567 	} else { //AI_AIMSTYLE_INSTANTHIT
568 
569 	}
570 
571 	// modify attack angles based on accuracy (mess this up to make the bot's aim not so deadly)
572 	target[0] += (random()-0.5) * ((MAX_BOT_SKILL - self->ai->pers.skillLevel) *2);
573 	target[1] += (random()-0.5) * ((MAX_BOT_SKILL - self->ai->pers.skillLevel) *2);
574 
575 	// Set direction
576 	VectorSubtract (target, self->s.origin, self->ai->move_vector);
577 	vectoangles (self->ai->move_vector, angles);
578 	VectorCopy(angles,self->s.angles);
579 
580 
581 	// Set the attack
582 	firedelay = random()*(MAX_BOT_SKILL*1.8);
583 	if (firedelay > (MAX_BOT_SKILL - self->ai->pers.skillLevel) && BOT_DMclass_CheckShot(self, target))
584 		ucmd->buttons = BUTTON_ATTACK;
585 
586 	//if(AIDevel.debugChased && bot_showcombat->integer)
587 	//	G_PrintMsg (AIDevel.devguy, PRINT_HIGH, "%s: attacking %s\n",self->bot.pers.netname ,self->enemy->r.client->pers.netname);
588 }
589 
590 
591 //==========================================
592 // BOT_DMclass_WeightPlayers
593 // weight players based on game state
594 //==========================================
BOT_DMclass_WeightPlayers(edict_t * self)595 void BOT_DMclass_WeightPlayers(edict_t *self)
596 {
597 	int i;
598 
599 	//clear
600 	memset(self->ai->status.playersWeights, 0, sizeof (self->ai->status.playersWeights));
601 
602 	for( i=0; i<num_AIEnemies; i++ )
603 	{
604 		if( AIEnemies[i] == NULL )
605 			continue;
606 
607 		if( AIEnemies[i] == self )
608 			continue;
609 
610 		//ignore spectators and dead players
611 		if( AIEnemies[i]->svflags & SVF_NOCLIENT || AIEnemies[i]->deadflag ) {
612 			self->ai->status.playersWeights[i] = 0.0f;
613 			continue;
614 		}
615 
616 		if( ctf->value )
617 		{
618 			if( AIEnemies[i]->client->resp.ctf_team != self->client->resp.ctf_team )
619 			{
620 				//being at enemy team gives a small weight, but weight afterall
621 				self->ai->status.playersWeights[i] = 0.2;
622 
623 				//enemy has redflag
624 				if( redflag && AIEnemies[i]->client->pers.inventory[ITEM_INDEX(redflag)]
625 					&& (self->client->resp.ctf_team == CTF_TEAM1) )
626 				{
627 					if( !self->client->pers.inventory[ITEM_INDEX(blueflag)] ) //don't hunt if you have the other flag, let others do
628 						self->ai->status.playersWeights[i] = 0.9;
629 				}
630 
631 				//enemy has blueflag
632 				if( blueflag && AIEnemies[i]->client->pers.inventory[ITEM_INDEX(blueflag)]
633 					&& (self->client->resp.ctf_team == CTF_TEAM2) )
634 				{
635 					if( !self->client->pers.inventory[ITEM_INDEX(redflag)] ) //don't hunt if you have the other flag, let others do
636 						self->ai->status.playersWeights[i] = 0.9;
637 				}
638 			}
639 		}
640 		else	//if not at ctf every player has some value
641 			self->ai->status.playersWeights[i] = 0.3;
642 
643 	}
644 }
645 
646 
647 //==========================================
648 // BOT_DMclass_WantedFlag
649 // find needed flag
650 //==========================================
BOT_DMclass_WantedFlag(edict_t * self)651 gitem_t	*BOT_DMclass_WantedFlag (edict_t *self)
652 {
653 	qboolean	hasflag;
654 
655 	if (!ctf->value)
656 		return NULL;
657 
658 	if (!self->client || !self->client->resp.ctf_team)
659 		return NULL;
660 
661 	//find out if the player has a flag, and what flag is it
662 	if (redflag && self->client->pers.inventory[ITEM_INDEX(redflag)])
663 		hasflag = true;
664 	else if (blueflag && self->client->pers.inventory[ITEM_INDEX(blueflag)])
665 		hasflag = true;
666 	else
667 		hasflag = false;
668 
669 	//jalToDo: see if our flag is at base
670 
671 	if (!hasflag)//if we don't have a flag we want other's team flag
672 	{
673 		if (self->client->resp.ctf_team == CTF_TEAM1)
674 			return blueflag;
675 		else
676 			return redflag;
677 	}
678 	else	//we have a flag
679 	{
680 		if (self->client->resp.ctf_team == CTF_TEAM1)
681 			return redflag;
682 		else
683 			return blueflag;
684 	}
685 
686 	return NULL;
687 }
688 
689 
690 //==========================================
691 // BOT_DMclass_WeightInventory
692 // weight items up or down based on bot needs
693 //==========================================
BOT_DMclass_WeightInventory(edict_t * self)694 void BOT_DMclass_WeightInventory(edict_t *self)
695 {
696 	float		LowNeedFactor = 0.5;
697 	gclient_t	*client;
698 	int			i;
699 
700 	client = self->client;
701 
702 	//reset with persistant values
703 	memcpy(self->ai->status.inventoryWeights, self->ai->pers.inventoryWeights, sizeof(self->ai->pers.inventoryWeights));
704 
705 
706 	//weight ammo down if bot doesn't have the weapon for it,
707 	//or denny weight for it, if bot is packed up.
708 	//------------------------------------------------------
709 
710 	//AMMO_BULLETS
711 
712 	if (!AI_CanPick_Ammo (self, AIWeapons[WEAP_MACHINEGUN].ammoItem) )
713 		self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_MACHINEGUN].ammoItem)] = 0.0;
714 	//find out if it has a weapon for this amno
715 	else if (!client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_CHAINGUN].weaponItem)]
716 		&& !client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_MACHINEGUN].weaponItem)] )
717 		self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_MACHINEGUN].ammoItem)] *= LowNeedFactor;
718 
719 	//AMMO_SHELLS:
720 
721 	//find out if it's packed up
722 	if (!AI_CanPick_Ammo (self, AIWeapons[WEAP_SHOTGUN].ammoItem) )
723 		self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_SHOTGUN].ammoItem)] = 0.0;
724 	//find out if it has a weapon for this amno
725 	else if (!client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_SHOTGUN].weaponItem)]
726 		&& !client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_SUPERSHOTGUN].weaponItem)] )
727 		self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_SHOTGUN].ammoItem)] *= LowNeedFactor;
728 
729 	//AMMO_ROCKETS:
730 
731 	//find out if it's packed up
732 	if (!AI_CanPick_Ammo (self, AIWeapons[WEAP_ROCKETLAUNCHER].ammoItem))
733 		self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_ROCKETLAUNCHER].ammoItem)] = 0.0;
734 	//find out if it has a weapon for this amno
735 	else if (!client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_ROCKETLAUNCHER].weaponItem)] )
736 		self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_ROCKETLAUNCHER].ammoItem)] *= LowNeedFactor;
737 
738 	//AMMO_GRENADES:
739 
740 	//find if it's packed up
741 	if (!AI_CanPick_Ammo (self, AIWeapons[WEAP_GRENADES].ammoItem))
742 		self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_GRENADES].ammoItem)] = 0.0;
743 	//grenades are also weapons, and are weighted down by LowNeedFactor in weapons group
744 
745 	//AMMO_CELLS:
746 
747 	//find out if it's packed up
748 	if (!AI_CanPick_Ammo (self, AIWeapons[WEAP_HYPERBLASTER].ammoItem))
749 		self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_HYPERBLASTER].ammoItem)] = 0.0;
750 	//find out if it has a weapon for this amno
751 	else if (!client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_HYPERBLASTER].weaponItem)]
752 		&& !client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_BFG].weaponItem)]
753 		&& !client->pers.inventory[ITEM_INDEX(FindItemByClassname("item_power_shield"))])
754 		self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_HYPERBLASTER].ammoItem)] *= LowNeedFactor;
755 
756 	//AMMO_SLUGS:
757 
758 	//find out if it's packed up
759 	if (!AI_CanPick_Ammo (self, AIWeapons[WEAP_RAILGUN].ammoItem))
760 		self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_RAILGUN].ammoItem)] = 0.0;
761 	//find out if it has a weapon for this amno
762 	else if (!client->pers.inventory[ITEM_INDEX(AIWeapons[WEAP_RAILGUN].weaponItem)] )
763 		self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_RAILGUN].ammoItem)] *= LowNeedFactor;
764 
765 
766 	//WEAPONS
767 	//-----------------------------------------------------
768 
769 	//weight weapon down if bot already has it
770 	for (i=0; i<WEAP_TOTAL; i++) {
771 		if ( AIWeapons[i].weaponItem && client->pers.inventory[ITEM_INDEX(AIWeapons[i].weaponItem)])
772 			self->ai->status.inventoryWeights[ITEM_INDEX(AIWeapons[i].weaponItem)] *= LowNeedFactor;
773 	}
774 
775 	//ARMOR
776 	//-----------------------------------------------------
777 	//shards are ALWAYS accepted but still...
778 	if (!AI_CanUseArmor ( FindItemByClassname("item_armor_shard"), self ))
779 		self->ai->status.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_shard"))] = 0.0;
780 
781 	if (!AI_CanUseArmor ( FindItemByClassname("item_armor_jacket"), self ))
782 		self->ai->status.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_jacket"))] = 0.0;
783 
784 	if (!AI_CanUseArmor ( FindItemByClassname("item_armor_combat"), self ))
785 		self->ai->status.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_combat"))] = 0.0;
786 
787 	if (!AI_CanUseArmor ( FindItemByClassname("item_armor_body"), self ))
788 		self->ai->status.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_body"))] = 0.0;
789 
790 
791 	//TECH :
792 	//-----------------------------------------------------
793 	if ( self->client->pers.inventory[ITEM_INDEX( FindItemByClassname("item_tech1"))]
794 		|| self->client->pers.inventory[ITEM_INDEX( FindItemByClassname("item_tech2"))]
795 		|| self->client->pers.inventory[ITEM_INDEX( FindItemByClassname("item_tech3"))]
796 		|| self->client->pers.inventory[ITEM_INDEX( FindItemByClassname("item_tech4"))] )
797 	{
798 		self->ai->status.inventoryWeights[ITEM_INDEX( FindItemByClassname("item_tech1"))] = 0.0;
799 		self->ai->status.inventoryWeights[ITEM_INDEX( FindItemByClassname("item_tech2"))] = 0.0;
800 		self->ai->status.inventoryWeights[ITEM_INDEX( FindItemByClassname("item_tech3"))] = 0.0;
801 		self->ai->status.inventoryWeights[ITEM_INDEX( FindItemByClassname("item_tech4"))] = 0.0;
802 	}
803 
804 	//CTF:
805 	//-----------------------------------------------------
806 	if( ctf->value )
807 	{
808 		gitem_t		*wantedFlag;
809 
810 		wantedFlag = BOT_DMclass_WantedFlag( self ); //Returns the flag gitem_t
811 
812 		//flags have weights defined inside persistant inventory. Remove weight from the unwanted one/s.
813 		if (blueflag && blueflag != wantedFlag)
814 			self->ai->status.inventoryWeights[ITEM_INDEX(blueflag)] = 0.0;
815 		if (redflag && redflag != wantedFlag)
816 			self->ai->status.inventoryWeights[ITEM_INDEX(redflag)] = 0.0;
817 	}
818 }
819 
820 
821 //==========================================
822 // BOT_DMclass_UpdateStatus
823 // update ai->status values based on bot state,
824 // so ai can decide based on these settings
825 //==========================================
BOT_DMclass_UpdateStatus(edict_t * self)826 void BOT_DMclass_UpdateStatus( edict_t *self )
827 {
828 	self->enemy = NULL;
829 	self->movetarget = NULL;
830 
831 	// Set up for new client movement: jalfixme
832 	VectorCopy(self->client->ps.viewangles,self->s.angles);
833 	VectorSet (self->client->ps.pmove.delta_angles, 0, 0, 0);
834 
835 	//JALFIXMEQ2
836 /*
837 	if (self->client->jumppad_time)
838 		self->ai->status.jumpadReached = true;	//jumpad time from client to botStatus
839 	else
840 		self->ai->status.jumpadReached = false;
841 */
842 	if (self->client->ps.pmove.pm_flags & PMF_TIME_TELEPORT)
843 		self->ai->status.TeleportReached = true;
844 	else
845 		self->ai->status.TeleportReached = false;
846 
847 	//set up AI status for the upcoming AI_frame
848 	BOT_DMclass_WeightInventory( self );	//weight items
849 	BOT_DMclass_WeightPlayers( self );		//weight players
850 }
851 
852 
853 //==========================================
854 // BOT_DMClass_BloquedTimeout
855 // the bot has been bloqued for too long
856 //==========================================
BOT_DMClass_BloquedTimeout(edict_t * self)857 void BOT_DMClass_BloquedTimeout( edict_t *self )
858 {
859 	self->health = 0;
860 	self->ai->bloqued_timeout = level.time + 15.0;
861 	self->die(self, self, self, 100000, vec3_origin);
862 	self->nextthink = level.time + FRAMETIME;
863 }
864 
865 
866 //==========================================
867 // BOT_DMclass_DeadFrame
868 // ent is dead = run this think func
869 //==========================================
BOT_DMclass_DeadFrame(edict_t * self)870 void BOT_DMclass_DeadFrame( edict_t *self )
871 {
872 	usercmd_t	ucmd;
873 
874 	// ask for respawn
875 	self->client->buttons = 0;
876 	ucmd.buttons = BUTTON_ATTACK;
877 	ClientThink (self, &ucmd);
878 	self->nextthink = level.time + FRAMETIME;
879 }
880 
881 
882 //==========================================
883 // BOT_DMclass_RunFrame
884 // States Machine & call client movement
885 //==========================================
BOT_DMclass_RunFrame(edict_t * self)886 void BOT_DMclass_RunFrame( edict_t *self )
887 {
888 	usercmd_t	ucmd;
889 	memset( &ucmd, 0, sizeof(ucmd) );
890 
891 	// Look for enemies
892 	if( BOT_DMclass_FindEnemy(self) )
893 	{
894 		BOT_DMclass_ChooseWeapon( self );
895 		BOT_DMclass_FireWeapon( self, &ucmd );
896 		self->ai->state = BOT_STATE_ATTACK;
897 		self->ai->state_combat_timeout = level.time + 1.0;
898 
899 	} else if( self->ai->state == BOT_STATE_ATTACK &&
900 		level.time > self->ai->state_combat_timeout)
901 	{
902 		//Jalfixme: change to: AI_SetUpStateMove(self);
903 		self->ai->state = BOT_STATE_MOVE;
904 	}
905 
906 	// Execute the move, or wander
907 	if( self->ai->state == BOT_STATE_MOVE )
908 		BOT_DMclass_Move( self, &ucmd );
909 
910 	else if(self->ai->state == BOT_STATE_ATTACK)
911 		BOT_DMclass_CombatMovement( self, &ucmd );
912 
913 	else if ( self->ai->state == BOT_STATE_WANDER )
914 		BOT_DMclass_Wander( self, &ucmd );
915 
916 
917 
918 	//set up for pmove
919 	ucmd.angles[PITCH] = ANGLE2SHORT(self->s.angles[PITCH]);
920 	ucmd.angles[YAW] = ANGLE2SHORT(self->s.angles[YAW]);
921 	ucmd.angles[ROLL] = ANGLE2SHORT(self->s.angles[ROLL]);
922 
923 	// set approximate ping and show values
924 	ucmd.msec = 75 + floor (random () * 25) + 1;
925 	self->client->ping = ucmd.msec;
926 
927 	// send command through id's code
928 	ClientThink( self, &ucmd );
929 	self->nextthink = level.time + FRAMETIME;
930 }
931 
932 
933 //==========================================
934 // BOT_DMclass_InitPersistant
935 // Persistant after respawns.
936 //==========================================
BOT_DMclass_InitPersistant(edict_t * self)937 void BOT_DMclass_InitPersistant(edict_t *self)
938 {
939 	self->classname = "dmbot";
940 
941 	//copy name
942 	if (self->client->pers.netname)
943 		self->ai->pers.netname = self->client->pers.netname;
944 	else
945 		self->ai->pers.netname = "dmBot";
946 
947 	//set 'class' functions
948 	self->ai->pers.RunFrame = BOT_DMclass_RunFrame;
949 	self->ai->pers.UpdateStatus = BOT_DMclass_UpdateStatus;
950 	self->ai->pers.bloquedTimeout = BOT_DMClass_BloquedTimeout;
951 	self->ai->pers.deadFrame = BOT_DMclass_DeadFrame;
952 
953 	//available moveTypes for this class
954 	self->ai->pers.moveTypesMask = (LINK_MOVE|LINK_STAIRS|LINK_FALL|LINK_WATER|LINK_WATERJUMP|LINK_JUMPPAD|LINK_PLATFORM|LINK_TELEPORT|LINK_LADDER|LINK_JUMP|LINK_CROUCH);
955 
956 	//Persistant Inventory Weights (0 = can not pick)
957 	memset(self->ai->pers.inventoryWeights, 0, sizeof (self->ai->pers.inventoryWeights));
958 
959 	//weapons
960 	self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_BLASTER].weaponItem)] = 0.0;
961 	//self->bot.pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("weapon_blaster"))] = 0.0; //it's the same thing
962 	self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_SHOTGUN].weaponItem)] = 0.5;
963 	self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_SUPERSHOTGUN].weaponItem)] = 0.7;
964 	self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_MACHINEGUN].weaponItem)] = 0.5;
965 	self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_CHAINGUN].weaponItem)] = 0.7;
966 	self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_GRENADES].weaponItem)] = 0.5;
967 	self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_GRENADELAUNCHER].weaponItem)] = 0.6;
968 	self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_ROCKETLAUNCHER].weaponItem)] = 0.8;
969 	self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_HYPERBLASTER].weaponItem)] = 0.7;
970 	self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_RAILGUN].weaponItem)] = 0.8;
971 	self->ai->pers.inventoryWeights[ITEM_INDEX(AIWeapons[WEAP_BFG].weaponItem)] = 0.5;
972 
973 	//ammo
974 	self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("ammo_shells"))] = 0.5;
975 	self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("ammo_bullets"))] = 0.5;
976 	self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("ammo_cells"))] = 0.5;
977 	self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("ammo_rockets"))] = 0.5;
978 	self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("ammo_slugs"))] = 0.5;
979 	self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("ammo_grenades"))] = 0.5;
980 
981 	//armor
982 	self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_body"))] = 0.9;
983 	self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_combat"))] = 0.8;
984 	self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_jacket"))] = 0.5;
985 	self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_armor_shard"))] = 0.2;
986 
987 	//techs
988 	self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_tech1"))] = 0.5;
989 	self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_tech2"))] = 0.5;
990 	self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_tech3"))] = 0.5;
991 	self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_tech4"))] = 0.5;
992 
993 	if( ctf->value ) {
994 		redflag = FindItemByClassname("item_flag_team1");	// store pointers to flags gitem_t, for
995 		blueflag = FindItemByClassname("item_flag_team2");// simpler comparisons inside this archive
996 		self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_flag_team1"))] = 3.0;
997 		self->ai->pers.inventoryWeights[ITEM_INDEX(FindItemByClassname("item_flag_team2"))] = 3.0;
998 	}
999 }
1000 
1001 
1002