1 /*
2 ===========================================================================
3
4 Return to Castle Wolfenstein single player GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6
7 This file is part of the Return to Castle Wolfenstein single player GPL Source Code (RTCW SP Source Code).
8
9 RTCW SP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 RTCW SP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with RTCW SP Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the RTCW SP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW SP Source Code. If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29
30 /*****************************************************************************
31 * name: ai_main.c
32 *
33 * desc: Quake3 bot AI
34 *
35 *
36 *****************************************************************************/
37
38 #include "g_local.h"
39 #include "../qcommon/q_shared.h"
40 #include "../botlib/botlib.h" //bot lib interface
41 #include "../botlib/be_aas.h"
42 #include "../botlib/be_ea.h"
43 #include "../botlib/be_ai_char.h"
44 #include "../botlib/be_ai_chat.h"
45 #include "../botlib/be_ai_gen.h"
46 #include "../botlib/be_ai_goal.h"
47 #include "../botlib/be_ai_move.h"
48 #include "../botlib/be_ai_weap.h"
49 #include "../botlib/botai.h" //bot ai interface
50
51 #include "ai_main.h"
52 #include "ai_dmq3.h"
53 #include "ai_chat.h"
54 #include "ai_cmd.h"
55 #include "ai_dmnet.h"
56 //
57 #include "chars.h"
58 #include "inv.h"
59 #include "syn.h"
60
61 //bot states
62 bot_state_t *botstates[MAX_CLIENTS];
63 //number of bots
64 int numbots;
65 //time to do a regular update
66 float regularupdate_time;
67 //
68 vmCvar_t bot_thinktime;
69 vmCvar_t memorydump;
70
71
72 /*
73 ==================
74 BotAI_Print
75 ==================
76 */
BotAI_Print(int type,char * fmt,...)77 void QDECL BotAI_Print( int type, char *fmt, ... ) {
78 char str[2048];
79 va_list ap;
80
81 va_start( ap, fmt );
82 Q_vsnprintf(str, sizeof(str), fmt, ap);
83 va_end( ap );
84
85 switch ( type ) {
86 case PRT_MESSAGE: {
87 G_Printf( "%s", str );
88 break;
89 }
90 case PRT_WARNING: {
91 G_Printf( S_COLOR_YELLOW "Warning: %s", str );
92 break;
93 }
94 case PRT_ERROR: {
95 G_Printf( S_COLOR_RED "Error: %s", str );
96 break;
97 }
98 case PRT_FATAL: {
99 G_Printf( S_COLOR_RED "Fatal: %s", str );
100 break;
101 }
102 case PRT_EXIT: {
103 G_Error( S_COLOR_RED "Exit: %s", str );
104 break;
105 }
106 default: {
107 G_Printf( "unknown print type\n" );
108 break;
109 }
110 }
111 }
112
113 /*
114 ==================
115 BotAI_Trace
116 ==================
117 */
BotAI_Trace(bsp_trace_t * bsptrace,vec3_t start,vec3_t mins,vec3_t maxs,vec3_t end,int passent,int contentmask)118 void BotAI_Trace( bsp_trace_t *bsptrace, vec3_t start, vec3_t mins, vec3_t maxs, vec3_t end, int passent, int contentmask ) {
119 trace_t trace;
120
121 trap_Trace( &trace, start, mins, maxs, end, passent, contentmask );
122 //copy the trace information
123 bsptrace->allsolid = trace.allsolid;
124 bsptrace->startsolid = trace.startsolid;
125 bsptrace->fraction = trace.fraction;
126 VectorCopy( trace.endpos, bsptrace->endpos );
127 bsptrace->plane.dist = trace.plane.dist;
128 VectorCopy( trace.plane.normal, bsptrace->plane.normal );
129 bsptrace->plane.signbits = trace.plane.signbits;
130 bsptrace->plane.type = trace.plane.type;
131 bsptrace->surface.value = 0;
132 bsptrace->surface.flags = trace.surfaceFlags;
133 bsptrace->ent = trace.entityNum;
134 bsptrace->exp_dist = 0;
135 bsptrace->sidenum = 0;
136 bsptrace->contents = 0;
137 }
138
139 /*
140 ==================
141 BotAI_GetClientState
142 ==================
143 */
BotAI_GetClientState(int clientNum,playerState_t * state)144 int BotAI_GetClientState( int clientNum, playerState_t *state ) {
145 gentity_t *ent;
146
147 ent = &g_entities[clientNum];
148 if ( !ent->inuse ) {
149 return qfalse;
150 }
151 if ( !ent->client ) {
152 return qfalse;
153 }
154
155 memcpy( state, &ent->client->ps, sizeof( playerState_t ) );
156 return qtrue;
157 }
158
159 /*
160 ==================
161 BotAI_GetEntityState
162 ==================
163 */
BotAI_GetEntityState(int entityNum,entityState_t * state)164 int BotAI_GetEntityState( int entityNum, entityState_t *state ) {
165 gentity_t *ent;
166
167 ent = &g_entities[entityNum];
168 memset( state, 0, sizeof( entityState_t ) );
169 if ( !ent->inuse ) {
170 return qfalse;
171 }
172 if ( !ent->r.linked ) {
173 return qfalse;
174 }
175 if ( ent->r.svFlags & SVF_NOCLIENT ) {
176 return qfalse;
177 }
178 memcpy( state, &ent->s, sizeof( entityState_t ) );
179 return qtrue;
180 }
181
182 /*
183 ==================
184 BotAI_GetSnapshotEntity
185 ==================
186 */
BotAI_GetSnapshotEntity(int clientNum,int sequence,entityState_t * state)187 int BotAI_GetSnapshotEntity( int clientNum, int sequence, entityState_t *state ) {
188 int entNum;
189
190 entNum = trap_BotGetSnapshotEntity( clientNum, sequence );
191 if ( entNum == -1 ) {
192 memset( state, 0, sizeof( entityState_t ) );
193 return -1;
194 }
195
196 BotAI_GetEntityState( entNum, state );
197
198 return sequence + 1;
199 }
200
201 /*
202 ==================
203 BotAI_BotInitialChat
204 ==================
205 */
BotAI_BotInitialChat(bot_state_t * bs,char * type,...)206 void QDECL BotAI_BotInitialChat( bot_state_t *bs, char *type, ... ) {
207 int i, mcontext;
208 va_list ap;
209 char *p;
210 char *vars[MAX_MATCHVARIABLES];
211
212 memset( vars, 0, sizeof( vars ) );
213 va_start( ap, type );
214 p = va_arg( ap, char * );
215 for ( i = 0; i < MAX_MATCHVARIABLES; i++ ) {
216 if ( !p ) {
217 break;
218 }
219 vars[i] = p;
220 p = va_arg( ap, char * );
221 }
222 va_end( ap );
223
224 mcontext = CONTEXT_NORMAL | CONTEXT_NEARBYITEM | CONTEXT_NAMES;
225 if ( BotCTFTeam( bs ) == CTF_TEAM_RED ) {
226 mcontext |= CONTEXT_CTFREDTEAM;
227 } else { mcontext |= CONTEXT_CTFBLUETEAM;}
228
229 trap_BotInitialChat( bs->cs, type, mcontext, vars[0], vars[1], vars[2], vars[3], vars[4], vars[5], vars[6], vars[7] );
230 }
231
232 /*
233 ==============
234 BotInterbreeding
235 ==============
236 */
BotInterbreeding(void)237 void BotInterbreeding( void ) {
238 float ranks[MAX_CLIENTS];
239 int parent1, parent2, child;
240 int i;
241
242 // get rankings for all the bots
243 for ( i = 0; i < MAX_CLIENTS; i++ ) {
244 if ( botstates[i] && botstates[i]->inuse ) {
245 ranks[i] = botstates[i]->num_kills * 2 - botstates[i]->num_deaths;
246 } else {
247 ranks[i] = -1;
248 }
249 }
250
251 if ( trap_GeneticParentsAndChildSelection( MAX_CLIENTS, ranks, &parent1, &parent2, &child ) ) {
252 trap_BotInterbreedGoalFuzzyLogic( botstates[parent1]->gs, botstates[parent2]->gs, botstates[child]->gs );
253 trap_BotMutateGoalFuzzyLogic( botstates[child]->gs, 1 );
254 }
255 // reset the kills and deaths
256 for ( i = 0; i < MAX_CLIENTS; i++ ) {
257 if ( botstates[i] && botstates[i]->inuse ) {
258 botstates[i]->num_kills = 0;
259 botstates[i]->num_deaths = 0;
260 }
261 }
262 }
263
264 /*
265 ==============
266 BotEntityInfo
267 ==============
268 */
BotEntityInfo(int entnum,aas_entityinfo_t * info)269 void BotEntityInfo( int entnum, aas_entityinfo_t *info ) {
270 trap_AAS_EntityInfo( entnum, info );
271 }
272
273 /*
274 ==============
275 NumBots
276 ==============
277 */
NumBots(void)278 int NumBots( void ) {
279 return numbots;
280 }
281
282 /*
283 ==============
284 AngleDifference
285 ==============
286 */
AngleDifference(float ang1,float ang2)287 float AngleDifference( float ang1, float ang2 ) {
288 float diff;
289
290 diff = ang1 - ang2;
291 if ( ang1 > ang2 ) {
292 if ( diff > 180.0 ) {
293 diff -= 360.0;
294 }
295 } else {
296 if ( diff < -180.0 ) {
297 diff += 360.0;
298 }
299 }
300 return diff;
301 }
302
303 /*
304 ==============
305 BotChangeViewAngle
306 ==============
307 */
BotChangeViewAngle(float angle,float ideal_angle,float speed)308 float BotChangeViewAngle( float angle, float ideal_angle, float speed ) {
309 float move;
310
311 angle = AngleMod( angle );
312 ideal_angle = AngleMod( ideal_angle );
313 if ( angle == ideal_angle ) {
314 return angle;
315 }
316 move = ideal_angle - angle;
317 if ( ideal_angle > angle ) {
318 if ( move > 180.0 ) {
319 move -= 360.0;
320 }
321 } else {
322 if ( move < -180.0 ) {
323 move += 360.0;
324 }
325 }
326 if ( move > 0 ) {
327 if ( move > speed ) {
328 move = speed;
329 }
330 } else {
331 if ( move < -speed ) {
332 move = -speed;
333 }
334 }
335 return AngleMod( angle + move );
336 }
337
338 /*
339 ==============
340 BotChangeViewAngles
341 ==============
342 */
BotChangeViewAngles(bot_state_t * bs,float thinktime)343 void BotChangeViewAngles( bot_state_t *bs, float thinktime ) {
344 float diff, factor, maxchange, anglespeed;
345 int i;
346
347 if ( bs->ideal_viewangles[PITCH] > 180 ) {
348 bs->ideal_viewangles[PITCH] -= 360;
349 }
350 //
351 if ( bs->enemy >= 0 ) {
352 factor = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_VIEW_FACTOR, 0.01, 1 );
353 maxchange = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_VIEW_MAXCHANGE, 1, 1800 );
354 } else {
355 factor = 0.25;
356 maxchange = 300;
357 }
358 maxchange *= thinktime;
359 for ( i = 0; i < 2; i++ ) {
360 diff = fabs( AngleDifference( bs->viewangles[i], bs->ideal_viewangles[i] ) );
361 anglespeed = diff * factor;
362 if ( anglespeed > maxchange ) {
363 anglespeed = maxchange;
364 }
365 bs->viewangles[i] = BotChangeViewAngle( bs->viewangles[i],
366 bs->ideal_viewangles[i], anglespeed );
367 //BotAI_Print(PRT_MESSAGE, "ideal_angles %f %f\n", bs->ideal_viewangles[0], bs->ideal_viewangles[1], bs->ideal_viewangles[2]);`
368 //bs->viewangles[i] = bs->ideal_viewangles[i];
369 }
370 if ( bs->viewangles[PITCH] > 180 ) {
371 bs->viewangles[PITCH] -= 360;
372 }
373 //elementary action: view
374 trap_EA_View( bs->client, bs->viewangles );
375 }
376
377 /*
378 ==============
379 BotInputToUserCommand
380 ==============
381 */
BotInputToUserCommand(bot_input_t * bi,usercmd_t * ucmd,int delta_angles[3],int time)382 void BotInputToUserCommand( bot_input_t *bi, usercmd_t *ucmd, int delta_angles[3], int time ) {
383 vec3_t angles, forward, right;
384 short temp;
385 int j;
386 float f, r, u, m;
387
388 //clear the whole structure
389 memset( ucmd, 0, sizeof( usercmd_t ) );
390 //
391 //the duration for the user command in milli seconds
392 ucmd->serverTime = time;
393 //
394 if ( bi->actionflags & ACTION_DELAYEDJUMP ) {
395 bi->actionflags |= ACTION_JUMP;
396 bi->actionflags &= ~ACTION_DELAYEDJUMP;
397 }
398 //set the buttons
399 if ( bi->actionflags & ACTION_RESPAWN ) {
400 ucmd->buttons = BUTTON_ATTACK;
401 }
402 if ( bi->actionflags & ACTION_ATTACK ) {
403 ucmd->buttons |= BUTTON_ATTACK;
404 }
405 if ( bi->actionflags & ACTION_TALK ) {
406 ucmd->buttons |= BUTTON_TALK;
407 }
408 if ( bi->actionflags & ACTION_GESTURE ) {
409 ucmd->buttons |= BUTTON_GESTURE;
410 }
411 if ( bi->actionflags & ACTION_USE ) {
412 ucmd->buttons |= BUTTON_USE_HOLDABLE;
413 }
414 if ( bi->actionflags & ACTION_WALK ) {
415 ucmd->buttons |= BUTTON_WALKING;
416 }
417 ucmd->weapon = bi->weapon;
418 //set the view angles
419 //NOTE: the ucmd->angles are the angles WITHOUT the delta angles
420 ucmd->angles[PITCH] = ANGLE2SHORT( bi->viewangles[PITCH] );
421 ucmd->angles[YAW] = ANGLE2SHORT( bi->viewangles[YAW] );
422 ucmd->angles[ROLL] = ANGLE2SHORT( bi->viewangles[ROLL] );
423 //subtract the delta angles
424 for ( j = 0; j < 3; j++ ) {
425 temp = ucmd->angles[j] - delta_angles[j];
426 /*NOTE: disabled because temp should be mod first
427 if ( j == PITCH ) {
428 // don't let the player look up or down more than 90 degrees
429 if ( temp > 16000 ) temp = 16000;
430 else if ( temp < -16000 ) temp = -16000;
431 }
432 */
433 ucmd->angles[j] = temp;
434 }
435 //NOTE: movement is relative to the REAL view angles
436 //get the horizontal forward and right vector
437 //get the pitch in the range [-180, 180]
438 if ( bi->dir[2] ) {
439 angles[PITCH] = bi->viewangles[PITCH];
440 } else { angles[PITCH] = 0;}
441 angles[YAW] = bi->viewangles[YAW];
442 angles[ROLL] = 0;
443 AngleVectors( angles, forward, right, NULL );
444 //bot input speed is in the range [0, 400]
445 bi->speed = bi->speed * 127 / 400;
446 //set the view independent movement
447 f = DotProduct(forward, bi->dir);
448 r = DotProduct(right, bi->dir);
449 u = fabs(forward[2]) * bi->dir[2];
450 m = fabs(f);
451
452 if (fabs(r) > m) {
453 m = fabs(r);
454 }
455
456 if (fabs(u) > m) {
457 m = fabs(u);
458 }
459
460 if (m > 0) {
461 f *= bi->speed / m;
462 r *= bi->speed / m;
463 u *= bi->speed / m;
464 }
465
466 ucmd->forwardmove = f;
467 ucmd->rightmove = r;
468 ucmd->upmove = u;
469
470 if (bi->actionflags & ACTION_MOVEFORWARD) ucmd->forwardmove = 127;
471 if (bi->actionflags & ACTION_MOVEBACK) ucmd->forwardmove = -127;
472 if (bi->actionflags & ACTION_MOVELEFT) ucmd->rightmove = -127;
473 if (bi->actionflags & ACTION_MOVERIGHT) ucmd->rightmove = 127;
474 //jump/moveup
475 if (bi->actionflags & ACTION_JUMP) ucmd->upmove = 127;
476 //crouch/movedown
477 if (bi->actionflags & ACTION_CROUCH) ucmd->upmove = -127;
478 }
479
480 /*
481 ==============
482 BotUpdateInput
483 ==============
484 */
BotUpdateInput(bot_state_t * bs,int time)485 void BotUpdateInput( bot_state_t *bs, int time ) {
486 bot_input_t bi;
487 int j;
488
489 //add the delta angles to the bot's current view angles
490 for ( j = 0; j < 3; j++ ) {
491 bs->viewangles[j] = AngleMod( bs->viewangles[j] + SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) );
492 }
493 //
494 BotChangeViewAngles( bs, (float) time / 1000 );
495 trap_EA_GetInput( bs->client, (float) time / 1000, &bi );
496 //respawn hack
497 if ( bi.actionflags & ACTION_RESPAWN ) {
498 if ( bs->lastucmd.buttons & BUTTON_ATTACK ) {
499 bi.actionflags &= ~( ACTION_RESPAWN | ACTION_ATTACK );
500 }
501 }
502 //
503 BotInputToUserCommand( &bi, &bs->lastucmd, bs->cur_ps.delta_angles, time );
504 bs->lastucmd.serverTime = time;
505 //subtract the delta angles
506 for ( j = 0; j < 3; j++ ) {
507 bs->viewangles[j] = AngleMod( bs->viewangles[j] - SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) );
508 }
509 }
510
511 /*
512 ==============
513 BotAIRegularUpdate
514 ==============
515 */
BotAIRegularUpdate(void)516 void BotAIRegularUpdate( void ) {
517 if ( regularupdate_time < trap_AAS_Time() ) {
518 trap_BotUpdateEntityItems();
519 regularupdate_time = trap_AAS_Time() + 1;
520 }
521 }
522
523 /*
524 ==============
525 BotAI
526 ==============
527 */
BotAI(int client,float thinktime)528 int BotAI( int client, float thinktime ) {
529 bot_state_t *bs;
530 char buf[1024], *args;
531 int j;
532
533 trap_EA_ResetInput( client, NULL );
534 //
535 bs = botstates[client];
536 if ( !bs || !bs->inuse ) {
537 BotAI_Print( PRT_FATAL, "client %d hasn't been setup\n", client );
538 return BLERR_AICLIENTNOTSETUP;
539 }
540
541 //retrieve the current client state
542 if ( !BotAI_GetClientState( client, &bs->cur_ps ) ) {
543 BotAI_Print( PRT_FATAL, "BotAI: failed to get player state for player %d\n", client );
544 return qfalse;
545 }
546
547 //retrieve any waiting console messages
548 while ( trap_BotGetServerCommand( client, buf, sizeof( buf ) ) ) {
549 //have buf point to the command and args to the command arguments
550 args = strchr( buf, ' ' );
551 if ( !args ) {
552 continue;
553 }
554 *args++ = '\0';
555
556 //remove color espace sequences from the arguments
557 Q_CleanStr( args );
558
559 //botai_import.Print(PRT_MESSAGE, "ConsoleMessage: \"%s\"\n", buf);
560 if ( !Q_stricmp( buf, "cp " ) ) { /*CenterPrintf*/
561 } else if ( !Q_stricmp( buf, "cs" ) ) { /*ConfigStringModified*/
562 } else if ( !Q_stricmp( buf, "print" ) ) {
563 trap_BotQueueConsoleMessage( bs->cs, CMS_NORMAL, args );
564 } else if ( !Q_stricmp( buf, "chat" ) ) {
565 trap_BotQueueConsoleMessage( bs->cs, CMS_CHAT, args );
566 } else if ( !Q_stricmp( buf, "tchat" ) ) {
567 trap_BotQueueConsoleMessage( bs->cs, CMS_CHAT, args );
568 } else if ( !Q_stricmp( buf, "scores" ) ) { /*FIXME: parse scores?*/
569 } else if ( !Q_stricmp( buf, "clientLevelShot" ) ) { /*ignore*/
570 }
571 }
572 //add the delta angles to the bot's current view angles
573 for ( j = 0; j < 3; j++ ) {
574 bs->viewangles[j] = AngleMod( bs->viewangles[j] + SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) );
575 }
576 //increase the local time of the bot
577 bs->ltime += thinktime;
578 //
579 bs->thinktime = thinktime;
580 //origin of the bot
581 VectorCopy( bs->cur_ps.origin, bs->origin );
582 //eye coordinates of the bot
583 VectorCopy( bs->cur_ps.origin, bs->eye );
584 bs->eye[2] += bs->cur_ps.viewheight;
585 //get the area the bot is in
586 bs->areanum = BotPointAreaNum( bs->origin );
587 //the real AI
588 BotDeathmatchAI( bs, thinktime );
589 //set the weapon selection every AI frame
590 trap_EA_SelectWeapon( bs->client, bs->weaponnum );
591 //subtract the delta angles
592 for ( j = 0; j < 3; j++ ) {
593 bs->viewangles[j] = AngleMod( bs->viewangles[j] - SHORT2ANGLE( bs->cur_ps.delta_angles[j] ) );
594 }
595 //everything was ok
596 return BLERR_NOERROR;
597 }
598
599 /*
600 ==================
601 BotScheduleBotThink
602 ==================
603 */
BotScheduleBotThink(void)604 void BotScheduleBotThink( void ) {
605 int i, botnum;
606
607 botnum = 0;
608
609 for ( i = 0; i < MAX_CLIENTS; i++ ) {
610 if ( !botstates[i] || !botstates[i]->inuse ) {
611 continue;
612 }
613 //initialize the bot think residual time
614 botstates[i]->botthink_residual = bot_thinktime.integer * botnum / numbots;
615 botnum++;
616 }
617 }
618
619 /*
620 ==============
621 BotAISetupClient
622 ==============
623 */
BotAISetupClient(int client,struct bot_settings_s * settings)624 int BotAISetupClient( int client, struct bot_settings_s *settings ) {
625 char filename[144], name[144], gender[144];
626 bot_state_t *bs;
627 int errnum;
628
629 if ( !botstates[client] ) {
630 botstates[client] = G_Alloc( sizeof( bot_state_t ) );
631 }
632 bs = botstates[client];
633
634 if ( !bs ) {
635 return qfalse;
636 }
637
638 if ( bs && bs->inuse ) {
639 BotAI_Print( PRT_FATAL, "client %d already setup\n", client );
640 return qfalse;
641 }
642
643 if ( !trap_AAS_Initialized() ) {
644 BotAI_Print( PRT_FATAL, "AAS not initialized\n" );
645 return qfalse;
646 }
647
648 //load the bot character
649 bs->character = trap_BotLoadCharacter( settings->characterfile, settings->skill );
650 if ( !bs->character ) {
651 BotAI_Print( PRT_FATAL, "couldn't load skill %f from %s\n", settings->skill, settings->characterfile );
652 return qfalse;
653 }
654 //copy the settings
655 memcpy( &bs->settings, settings, sizeof( bot_settings_t ) );
656 //allocate a goal state
657 bs->gs = trap_BotAllocGoalState( client );
658 //load the item weights
659 trap_Characteristic_String( bs->character, CHARACTERISTIC_ITEMWEIGHTS, filename, sizeof( filename ) );
660 errnum = trap_BotLoadItemWeights( bs->gs, filename );
661 if ( errnum != BLERR_NOERROR ) {
662 trap_BotFreeGoalState( bs->gs );
663 return qfalse;
664 }
665 //allocate a weapon state
666 bs->ws = trap_BotAllocWeaponState();
667 //load the weapon weights
668 trap_Characteristic_String( bs->character, CHARACTERISTIC_WEAPONWEIGHTS, filename, sizeof( filename ) );
669 errnum = trap_BotLoadWeaponWeights( bs->ws, filename );
670 if ( errnum != BLERR_NOERROR ) {
671 trap_BotFreeGoalState( bs->gs );
672 trap_BotFreeWeaponState( bs->ws );
673 return qfalse;
674 }
675 //allocate a chat state
676 bs->cs = trap_BotAllocChatState();
677 //load the chat file
678 trap_Characteristic_String( bs->character, CHARACTERISTIC_CHAT_FILE, filename, sizeof( filename ) );
679 trap_Characteristic_String( bs->character, CHARACTERISTIC_CHAT_NAME, name, sizeof( name ) );
680 errnum = trap_BotLoadChatFile( bs->cs, filename, name );
681 if ( errnum != BLERR_NOERROR ) {
682 trap_BotFreeChatState( bs->cs );
683 trap_BotFreeGoalState( bs->gs );
684 trap_BotFreeWeaponState( bs->ws );
685 return qfalse;
686 }
687 //get the gender characteristic
688 trap_Characteristic_String( bs->character, CHARACTERISTIC_GENDER, gender, sizeof( gender ) );
689 //set the chat gender
690 if ( *gender == 'f' || *gender == 'F' ) {
691 trap_BotSetChatGender( bs->cs, CHAT_GENDERFEMALE );
692 } else if ( *gender == 'm' || *gender == 'M' ) {
693 trap_BotSetChatGender( bs->cs, CHAT_GENDERMALE );
694 } else { trap_BotSetChatGender( bs->cs, CHAT_GENDERLESS );}
695
696 bs->inuse = qtrue;
697 bs->client = client;
698 bs->entitynum = client;
699 bs->setupcount = 4;
700 bs->entergame_time = trap_AAS_Time();
701 bs->ms = trap_BotAllocMoveState();
702 bs->walker = trap_Characteristic_BFloat( bs->character, CHARACTERISTIC_WALKER, 0, 1 );
703 numbots++;
704
705 if ( trap_Cvar_VariableIntegerValue( "bot_testichat" ) ) {
706 trap_BotLibVarSet( "bot_testichat", "1" );
707 BotChatTest( bs );
708 }
709 //NOTE: reschedule the bot thinking
710 BotScheduleBotThink();
711 //
712 return qtrue;
713 }
714
715 /*
716 ==============
717 BotAIShutdownClient
718 ==============
719 */
BotAIShutdownClient(int client)720 int BotAIShutdownClient( int client ) {
721 bot_state_t *bs;
722
723 // Wolfenstein
724 if ( g_entities[client].r.svFlags & SVF_CASTAI ) {
725 AICast_ShutdownClient( client );
726 return BLERR_NOERROR;
727 }
728 // done.
729
730 bs = botstates[client];
731 if ( !bs || !bs->inuse ) {
732 // BotAI_Print(PRT_ERROR, "client %d already shutdown\n", client);
733 return BLERR_AICLIENTALREADYSHUTDOWN;
734 }
735
736 if ( BotChat_ExitGame( bs ) ) {
737 trap_BotEnterChat( bs->cs, bs->client, CHAT_ALL );
738 }
739
740 trap_BotFreeMoveState( bs->ms );
741 //free the goal state
742 trap_BotFreeGoalState( bs->gs );
743 //free the chat file
744 trap_BotFreeChatState( bs->cs );
745 //free the weapon weights
746 trap_BotFreeWeaponState( bs->ws );
747 //free the bot character
748 trap_BotFreeCharacter( bs->character );
749 //
750 BotFreeWaypoints( bs->checkpoints );
751 BotFreeWaypoints( bs->patrolpoints );
752 //clear the bot state
753 memset( bs, 0, sizeof( bot_state_t ) );
754 //set the inuse flag to qfalse
755 bs->inuse = qfalse;
756 //there's one bot less
757 numbots--;
758 //everything went ok
759 return BLERR_NOERROR;
760 }
761
762 /*
763 ==============
764 BotResetState
765
766 called when a bot enters the intermission or observer mode and
767 when the level is changed
768 ==============
769 */
BotResetState(bot_state_t * bs)770 void BotResetState( bot_state_t *bs ) {
771 int client, entitynum, inuse;
772 int movestate, goalstate, chatstate, weaponstate;
773 bot_settings_t settings;
774 int character;
775 playerState_t ps; //current player state
776 float entergame_time;
777
778 //save some things that should not be reset here
779 memcpy( &settings, &bs->settings, sizeof( bot_settings_t ) );
780 memcpy( &ps, &bs->cur_ps, sizeof( playerState_t ) );
781 inuse = bs->inuse;
782 client = bs->client;
783 entitynum = bs->entitynum;
784 character = bs->character;
785 movestate = bs->ms;
786 goalstate = bs->gs;
787 chatstate = bs->cs;
788 weaponstate = bs->ws;
789 entergame_time = bs->entergame_time;
790 //free checkpoints and patrol points
791 BotFreeWaypoints( bs->checkpoints );
792 BotFreeWaypoints( bs->patrolpoints );
793 //reset the whole state
794 memset( bs, 0, sizeof( bot_state_t ) );
795 //copy back some state stuff that should not be reset
796 bs->ms = movestate;
797 bs->gs = goalstate;
798 bs->cs = chatstate;
799 bs->ws = weaponstate;
800 memcpy( &bs->cur_ps, &ps, sizeof( playerState_t ) );
801 memcpy( &bs->settings, &settings, sizeof( bot_settings_t ) );
802 bs->inuse = inuse;
803 bs->client = client;
804 bs->entitynum = entitynum;
805 bs->character = character;
806 bs->entergame_time = entergame_time;
807 //reset several states
808 if ( bs->ms ) {
809 trap_BotResetMoveState( bs->ms );
810 }
811 if ( bs->gs ) {
812 trap_BotResetGoalState( bs->gs );
813 }
814 if ( bs->ws ) {
815 trap_BotResetWeaponState( bs->ws );
816 }
817 if ( bs->gs ) {
818 trap_BotResetAvoidGoals( bs->gs );
819 }
820 if ( bs->ms ) {
821 trap_BotResetAvoidReach( bs->ms );
822 }
823 }
824
825 /*
826 ==============
827 BotAILoadMap
828 ==============
829 */
BotAILoadMap(int restart)830 int BotAILoadMap( int restart ) {
831 int i;
832 vmCvar_t mapname;
833
834 if ( !restart ) {
835 trap_Cvar_Register( &mapname, "mapname", "", CVAR_SERVERINFO | CVAR_ROM );
836 trap_BotLibLoadMap( mapname.string );
837 }
838
839 for ( i = 0; i < MAX_CLIENTS; i++ ) {
840 if ( botstates[i] && botstates[i]->inuse ) {
841 BotResetState( botstates[i] );
842 botstates[i]->setupcount = 4;
843 }
844 }
845
846 BotSetupDeathmatchAI();
847
848 return BLERR_NOERROR;
849 }
850
851 /*
852 ==================
853 BotAIStartFrame
854 ==================
855 */
BotAIStartFrame(int time)856 int BotAIStartFrame( int time ) {
857 int i;
858 gentity_t *ent;
859 bot_entitystate_t state;
860 //entityState_t entitystate;
861 //vec3_t mins = {-15, -15, -24}, maxs = {15, 15, 32};
862 int elapsed_time, thinktime;
863 static int local_time;
864 static int botlib_residual;
865 static int lastbotthink_time;
866
867 if ( g_gametype.integer != GT_SINGLE_PLAYER ) {
868 G_CheckBotSpawn();
869 }
870
871 trap_Cvar_Update( &bot_rocketjump );
872 trap_Cvar_Update( &bot_grapple );
873 trap_Cvar_Update( &bot_fastchat );
874 trap_Cvar_Update( &bot_nochat );
875 trap_Cvar_Update( &bot_testrchat );
876 trap_Cvar_Update( &bot_thinktime );
877 // Ridah, set the default AAS world
878 trap_AAS_SetCurrentWorld( 0 );
879 trap_Cvar_Update( &memorydump );
880
881 if ( memorydump.integer ) {
882 trap_BotLibVarSet( "memorydump", "1" );
883 trap_Cvar_Set( "memorydump", "0" );
884 }
885
886 //if the bot think time changed we should reschedule the bots
887 if ( bot_thinktime.integer != lastbotthink_time ) {
888 lastbotthink_time = bot_thinktime.integer;
889 BotScheduleBotThink();
890 }
891
892 elapsed_time = time - local_time;
893 local_time = time;
894
895 botlib_residual += elapsed_time;
896
897 if ( elapsed_time > bot_thinktime.integer ) {
898 thinktime = elapsed_time;
899 } else { thinktime = bot_thinktime.integer;}
900
901 // update the bot library
902 if ( botlib_residual >= thinktime ) {
903 botlib_residual -= thinktime;
904
905 trap_BotLibStartFrame( (float) time / 1000 );
906
907 // Ridah, only check the default world
908 trap_AAS_SetCurrentWorld( 0 );
909
910 if ( !trap_AAS_Initialized() ) {
911 return BLERR_NOERROR;
912 }
913
914 //update entities in the botlib
915 for ( i = 0; i < MAX_GENTITIES; i++ ) {
916
917 // Ridah, in single player, we only need client entity information
918 if ( g_gametype.integer == GT_SINGLE_PLAYER && i > level.maxclients ) {
919 break;
920 }
921
922 ent = &g_entities[i];
923 if ( !ent->inuse ) {
924 continue;
925 }
926 if ( !ent->r.linked ) {
927 continue;
928 }
929 if ( ent->r.svFlags & SVF_NOCLIENT ) {
930 continue;
931 }
932 if ( ent->aiInactive ) {
933 continue;
934 }
935 //
936 memset( &state, 0, sizeof( bot_entitystate_t ) );
937 //
938 VectorCopy( ent->r.currentOrigin, state.origin );
939 VectorCopy( ent->r.currentAngles, state.angles );
940 VectorCopy( ent->s.origin2, state.old_origin );
941 VectorCopy( ent->r.mins, state.mins );
942 VectorCopy( ent->r.maxs, state.maxs );
943 state.type = ent->s.eType;
944 state.flags = ent->s.eFlags;
945 if ( ent->r.bmodel ) {
946 state.solid = SOLID_BSP;
947 } else { state.solid = SOLID_BBOX;}
948 state.groundent = ent->s.groundEntityNum;
949 state.modelindex = ent->s.modelindex;
950 state.modelindex2 = ent->s.modelindex2;
951 state.frame = ent->s.frame;
952 //state.event = ent->s.event;
953 //state.eventParm = ent->s.eventParm;
954 state.powerups = ent->s.powerups;
955 state.legsAnim = ent->s.legsAnim;
956 state.torsoAnim = ent->s.torsoAnim;
957 // state.weapAnim = ent->s.weapAnim; //----(SA)
958 //----(SA) didn't want to comment in as I wasn't sure of any implications of changing the aas_entityinfo_t and bot_entitystate_t structures.
959 state.weapon = ent->s.weapon;
960 /*
961 if (!BotAI_GetEntityState(i, &entitystate)) continue;
962 //
963 memset(&state, 0, sizeof(bot_entitystate_t));
964 //
965 VectorCopy(entitystate.pos.trBase, state.origin);
966 VectorCopy(entitystate.angles, state.angles);
967 VectorCopy(ent->s.origin2, state.old_origin);
968 //VectorCopy(ent->r.mins, state.mins);
969 //VectorCopy(ent->r.maxs, state.maxs);
970 state.type = entitystate.eType;
971 state.flags = entitystate.eFlags;
972 if (ent->r.bmodel) state.solid = SOLID_BSP;
973 else state.solid = SOLID_BBOX;
974 state.modelindex = entitystate.modelindex;
975 state.modelindex2 = entitystate.modelindex2;
976 state.frame = entitystate.frame;
977 state.event = entitystate.event;
978 state.eventParm = entitystate.eventParm;
979 state.powerups = entitystate.powerups;
980 state.legsAnim = entitystate.legsAnim;
981 state.torsoAnim = entitystate.torsoAnim;
982 state.weapon = entitystate.weapon;
983 */
984 //
985 trap_BotLibUpdateEntity( i, &state );
986 }
987
988 BotAIRegularUpdate();
989
990 }
991
992 // Ridah, in single player, don't need bot's thinking
993 if ( g_gametype.integer == GT_SINGLE_PLAYER ) {
994 return BLERR_NOERROR;
995 }
996
997 // execute scheduled bot AI
998 for ( i = 0; i < MAX_CLIENTS; i++ ) {
999 if ( !botstates[i] || !botstates[i]->inuse ) {
1000 continue;
1001 }
1002 // Ridah
1003 if ( g_entities[i].r.svFlags & SVF_CASTAI ) {
1004 continue;
1005 }
1006 // done.
1007 //
1008 botstates[i]->botthink_residual += elapsed_time;
1009 //
1010 if ( botstates[i]->botthink_residual >= thinktime ) {
1011 botstates[i]->botthink_residual -= thinktime;
1012
1013 if ( !trap_AAS_Initialized() ) {
1014 return BLERR_NOERROR;
1015 }
1016
1017 if ( g_entities[i].client->pers.connected == CON_CONNECTED ) {
1018 BotAI( i, (float) thinktime / 1000 );
1019 }
1020 }
1021 }
1022
1023
1024 // execute bot user commands every frame
1025 for ( i = 0; i < MAX_CLIENTS; i++ ) {
1026 if ( !botstates[i] || !botstates[i]->inuse ) {
1027 continue;
1028 }
1029 // Ridah
1030 if ( g_entities[i].r.svFlags & SVF_CASTAI ) {
1031 continue;
1032 }
1033 // done.
1034 if ( g_entities[i].client->pers.connected != CON_CONNECTED ) {
1035 continue;
1036 }
1037
1038 BotUpdateInput( botstates[i], time );
1039 trap_BotUserCommand( botstates[i]->client, &botstates[i]->lastucmd );
1040 }
1041
1042 return BLERR_NOERROR;
1043 }
1044
1045 /*
1046 ==============
1047 BotInitLibrary
1048 ==============
1049 */
BotInitLibrary(void)1050 int BotInitLibrary( void ) {
1051 char buf[144];
1052
1053 //set the maxclients and maxentities library variables before calling BotSetupLibrary
1054 Com_sprintf( buf, sizeof( buf ), "%d", level.maxclients );
1055 trap_BotLibVarSet( "maxclients", buf );
1056 Com_sprintf( buf, sizeof( buf ), "%d", MAX_GENTITIES );
1057 trap_BotLibVarSet( "maxentities", buf );
1058 //bsp checksum
1059 trap_Cvar_VariableStringBuffer( "sv_mapChecksum", buf, sizeof( buf ) );
1060 if ( strlen( buf ) ) {
1061 trap_BotLibVarSet( "sv_mapChecksum", buf );
1062 }
1063 //maximum number of aas links
1064 trap_Cvar_VariableStringBuffer( "max_aaslinks", buf, sizeof( buf ) );
1065 if ( strlen( buf ) ) {
1066 trap_BotLibVarSet( "max_aaslinks", buf );
1067 }
1068 //maximum number of items in a level
1069 trap_Cvar_VariableStringBuffer( "max_levelitems", buf, sizeof( buf ) );
1070 if ( strlen( buf ) ) {
1071 trap_BotLibVarSet( "max_levelitems", buf );
1072 }
1073 //automatically launch WinBSPC if AAS file not available
1074 trap_Cvar_VariableStringBuffer( "autolaunchbspc", buf, sizeof( buf ) );
1075 if ( strlen( buf ) ) {
1076 trap_BotLibVarSet( "autolaunchbspc", "1" );
1077 }
1078 //
1079 trap_Cvar_VariableStringBuffer( "g_gametype", buf, sizeof( buf ) );
1080 if ( !strlen( buf ) ) {
1081 strcpy( buf, "0" );
1082 }
1083 trap_BotLibVarSet( "g_gametype", buf );
1084 //
1085 // Rafael gameskill
1086 trap_Cvar_VariableStringBuffer( "g_gameskill", buf, sizeof( buf ) );
1087 if ( !strlen( buf ) ) {
1088 strcpy( buf, "0" );
1089 }
1090 trap_BotLibVarSet( "g_gamekill", buf );
1091 // done
1092 //
1093 trap_Cvar_VariableStringBuffer( "bot_developer", buf, sizeof( buf ) );
1094 if ( !strlen( buf ) ) {
1095 strcpy( buf, "0" );
1096 }
1097 trap_BotLibVarSet( "bot_developer", buf );
1098 //log file
1099 trap_Cvar_VariableStringBuffer( "bot_developer", buf, sizeof( buf ) );
1100 if ( !strlen( buf ) ) {
1101 strcpy( buf, "0" );
1102 }
1103 trap_Cvar_VariableStringBuffer( "logfile", buf, sizeof( buf ) );
1104 trap_BotLibVarSet( "log", buf );
1105 //no chatting
1106 trap_Cvar_VariableStringBuffer( "bot_nochat", buf, sizeof( buf ) );
1107 if ( strlen( buf ) ) {
1108 trap_BotLibVarSet( "nochat", buf );
1109 }
1110 //forced clustering calculations
1111 trap_Cvar_VariableStringBuffer( "forceclustering", buf, sizeof( buf ) );
1112 if ( strlen( buf ) ) {
1113 trap_BotLibVarSet( "forceclustering", buf );
1114 }
1115 //forced reachability calculations
1116 trap_Cvar_VariableStringBuffer( "forcereachability", buf, sizeof( buf ) );
1117 if ( strlen( buf ) ) {
1118 trap_BotLibVarSet( "forcereachability", buf );
1119 }
1120 //force writing of AAS to file
1121 trap_Cvar_VariableStringBuffer( "forcewrite", buf, sizeof( buf ) );
1122 if ( strlen( buf ) ) {
1123 trap_BotLibVarSet( "forcewrite", buf );
1124 }
1125 //no AAS optimization
1126 trap_Cvar_VariableStringBuffer( "nooptimize", buf, sizeof( buf ) );
1127 if ( strlen( buf ) ) {
1128 trap_BotLibVarSet( "nooptimize", buf );
1129 }
1130 //number of reachabilities to calculate each frame
1131 trap_Cvar_VariableStringBuffer( "framereachability", buf, sizeof( buf ) );
1132 if ( !strlen( buf ) ) {
1133 strcpy( buf, "20" );
1134 }
1135 trap_BotLibVarSet( "framereachability", buf );
1136 //
1137 trap_Cvar_VariableStringBuffer( "bot_reloadcharacters", buf, sizeof( buf ) );
1138 if ( !strlen( buf ) ) {
1139 strcpy( buf, "0" );
1140 }
1141 trap_BotLibVarSet( "bot_reloadcharacters", buf );
1142 //base directory
1143 trap_Cvar_VariableStringBuffer( "fs_basepath", buf, sizeof( buf ) );
1144 if ( strlen( buf ) ) {
1145 trap_BotLibVarSet( "basedir", buf );
1146 }
1147 //game directory
1148 trap_Cvar_VariableStringBuffer( "fs_game", buf, sizeof( buf ) );
1149 if ( strlen( buf ) ) {
1150 trap_BotLibVarSet( "gamedir", buf );
1151 }
1152 //home directory
1153 trap_Cvar_VariableStringBuffer( "fs_homepath", buf, sizeof( buf ) );
1154 if ( strlen( buf ) ) {
1155 trap_BotLibVarSet("homedir", buf);
1156 }
1157 //setup the bot library
1158 return trap_BotLibSetup();
1159 }
1160
1161 /*
1162 ==============
1163 BotAISetup
1164 ==============
1165 */
BotAISetup(int restart)1166 int BotAISetup( int restart ) {
1167 int errnum;
1168
1169 #ifdef RANDOMIZE
1170 srand( (unsigned)time( NULL ) );
1171 #endif //RANDOMIZE
1172
1173 trap_Cvar_Register( &bot_thinktime, "bot_thinktime", "100", 0 );
1174 trap_Cvar_Register( &memorydump, "memorydump", "0", 0 );
1175
1176 //if the game is restarted for a tournament
1177 if ( restart ) {
1178 return BLERR_NOERROR;
1179 }
1180
1181 //initialize the bot states
1182 memset( botstates, 0, sizeof( botstates ) );
1183
1184 trap_Cvar_Register( &bot_thinktime, "bot_thinktime", "100", 0 );
1185
1186 errnum = BotInitLibrary();
1187 if ( errnum != BLERR_NOERROR ) {
1188 return qfalse;
1189 }
1190 return BLERR_NOERROR;
1191 }
1192
1193 /*
1194 ==============
1195 BotAIShutdown
1196 ==============
1197 */
BotAIShutdown(int restart)1198 int BotAIShutdown( int restart ) {
1199
1200 int i;
1201
1202 //if the game is restarted for a tournament
1203 if ( restart ) {
1204 //shutdown all the bots in the botlib
1205 for ( i = 0; i < MAX_CLIENTS; i++ ) {
1206 if ( botstates[i] && botstates[i]->inuse ) {
1207 BotAIShutdownClient( botstates[i]->client );
1208 }
1209 }
1210 //don't shutdown the bot library
1211 } else {
1212 trap_BotLibShutdown();
1213 }
1214 return qtrue;
1215 }
1216
1217