1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4
5 This file is part of Quake III Arena source code.
6
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 ===========================================================================
21 */
22
23 /*****************************************************************************
24 * name: be_ai_weap.c
25 *
26 * desc: weapon AI
27 *
28 * $Archive: /MissionPack/code/botlib/be_ai_weap.c $
29 *
30 *****************************************************************************/
31
32 #include "../qcommon/q_shared.h"
33 #include "l_libvar.h"
34 #include "l_log.h"
35 #include "l_memory.h"
36 #include "l_utils.h"
37 #include "l_script.h"
38 #include "l_precomp.h"
39 #include "l_struct.h"
40 #include "aasfile.h"
41 #include "botlib.h"
42 #include "be_aas.h"
43 #include "be_aas_funcs.h"
44 #include "be_interface.h"
45 #include "be_ai_weight.h" //fuzzy weights
46 #include "be_ai_weap.h"
47
48 //#define DEBUG_AI_WEAP
49
50 //structure field offsets
51 #define WEAPON_OFS(x) (size_t)&(((weaponinfo_t *)0)->x)
52 #define PROJECTILE_OFS(x) (size_t)&(((projectileinfo_t *)0)->x)
53
54 //weapon definition
55 static fielddef_t weaponinfo_fields[] =
56 {
57 {"number", WEAPON_OFS(number), FT_INT}, //weapon number
58 {"name", WEAPON_OFS(name), FT_STRING}, //name of the weapon
59 {"level", WEAPON_OFS(level), FT_INT},
60 {"model", WEAPON_OFS(model), FT_STRING}, //model of the weapon
61 {"weaponindex", WEAPON_OFS(weaponindex), FT_INT}, //index of weapon in inventory
62 {"flags", WEAPON_OFS(flags), FT_INT}, //special flags
63 {"projectile", WEAPON_OFS(projectile), FT_STRING}, //projectile used by the weapon
64 {"numprojectiles", WEAPON_OFS(numprojectiles), FT_INT}, //number of projectiles
65 {"hspread", WEAPON_OFS(hspread), FT_FLOAT}, //horizontal spread of projectiles (degrees from middle)
66 {"vspread", WEAPON_OFS(vspread), FT_FLOAT}, //vertical spread of projectiles (degrees from middle)
67 {"speed", WEAPON_OFS(speed), FT_FLOAT}, //speed of the projectile (0 = instant hit)
68 {"acceleration", WEAPON_OFS(acceleration), FT_FLOAT}, //"acceleration" * time (in seconds) + "speed" = projectile speed
69 {"recoil", WEAPON_OFS(recoil), FT_FLOAT|FT_ARRAY, 3}, //amount of recoil the player gets from the weapon
70 {"offset", WEAPON_OFS(offset), FT_FLOAT|FT_ARRAY, 3}, //projectile start offset relative to eye and view angles
71 {"angleoffset", WEAPON_OFS(angleoffset), FT_FLOAT|FT_ARRAY, 3},//offset of the shoot angles relative to the view angles
72 {"extrazvelocity", WEAPON_OFS(extrazvelocity), FT_FLOAT},//extra z velocity the projectile gets
73 {"ammoamount", WEAPON_OFS(ammoamount), FT_INT}, //ammo amount used per shot
74 {"ammoindex", WEAPON_OFS(ammoindex), FT_INT}, //index of ammo in inventory
75 {"activate", WEAPON_OFS(activate), FT_FLOAT}, //time it takes to select the weapon
76 {"reload", WEAPON_OFS(reload), FT_FLOAT}, //time it takes to reload the weapon
77 {"spinup", WEAPON_OFS(spinup), FT_FLOAT}, //time it takes before first shot
78 {"spindown", WEAPON_OFS(spindown), FT_FLOAT}, //time it takes before weapon stops firing
79 {NULL, 0, 0, 0}
80 };
81
82 //projectile definition
83 static fielddef_t projectileinfo_fields[] =
84 {
85 {"name", PROJECTILE_OFS(name), FT_STRING}, //name of the projectile
86 {"model", PROJECTILE_OFS(model), FT_STRING}, //model of the projectile
87 {"flags", PROJECTILE_OFS(flags), FT_INT}, //special flags
88 {"gravity", PROJECTILE_OFS(gravity), FT_FLOAT}, //amount of gravity applied to the projectile [0,1]
89 {"damage", PROJECTILE_OFS(damage), FT_INT}, //damage of the projectile
90 {"radius", PROJECTILE_OFS(radius), FT_FLOAT}, //radius of damage
91 {"visdamage", PROJECTILE_OFS(visdamage), FT_INT}, //damage of the projectile to visible entities
92 {"damagetype", PROJECTILE_OFS(damagetype), FT_INT}, //type of damage (combination of the DAMAGETYPE_? flags)
93 {"healthinc", PROJECTILE_OFS(healthinc), FT_INT}, //health increase the owner gets
94 {"push", PROJECTILE_OFS(push), FT_FLOAT}, //amount a player is pushed away from the projectile impact
95 {"detonation", PROJECTILE_OFS(detonation), FT_FLOAT}, //time before projectile explodes after fire pressed
96 {"bounce", PROJECTILE_OFS(bounce), FT_FLOAT}, //amount the projectile bounces
97 {"bouncefric", PROJECTILE_OFS(bouncefric), FT_FLOAT}, //amount the bounce decreases per bounce
98 {"bouncestop", PROJECTILE_OFS(bouncestop), FT_FLOAT}, //minimum bounce value before bouncing stops
99 //recurive projectile definition??
100 {NULL, 0, 0, 0}
101 };
102
103 static structdef_t weaponinfo_struct =
104 {
105 sizeof(weaponinfo_t), weaponinfo_fields
106 };
107 static structdef_t projectileinfo_struct =
108 {
109 sizeof(projectileinfo_t), projectileinfo_fields
110 };
111
112 //weapon configuration: set of weapons with projectiles
113 typedef struct weaponconfig_s
114 {
115 int numweapons;
116 int numprojectiles;
117 projectileinfo_t *projectileinfo;
118 weaponinfo_t *weaponinfo;
119 } weaponconfig_t;
120
121 //the bot weapon state
122 typedef struct bot_weaponstate_s
123 {
124 struct weightconfig_s *weaponweightconfig; //weapon weight configuration
125 int *weaponweightindex; //weapon weight index
126 } bot_weaponstate_t;
127
128 static bot_weaponstate_t *botweaponstates[MAX_CLIENTS+1];
129 static weaponconfig_t *weaponconfig;
130
131 //========================================================================
132 //
133 // Parameter: -
134 // Returns: -
135 // Changes Globals: -
136 //========================================================================
BotValidWeaponNumber(int weaponnum)137 int BotValidWeaponNumber(int weaponnum)
138 {
139 if (weaponnum <= 0 || weaponnum > weaponconfig->numweapons)
140 {
141 botimport.Print(PRT_ERROR, "weapon number out of range\n");
142 return qfalse;
143 } //end if
144 return qtrue;
145 } //end of the function BotValidWeaponNumber
146 //========================================================================
147 //
148 // Parameter: -
149 // Returns: -
150 // Changes Globals: -
151 //========================================================================
BotWeaponStateFromHandle(int handle)152 bot_weaponstate_t *BotWeaponStateFromHandle(int handle)
153 {
154 if (handle <= 0 || handle > MAX_CLIENTS)
155 {
156 botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle);
157 return NULL;
158 } //end if
159 if (!botweaponstates[handle])
160 {
161 botimport.Print(PRT_FATAL, "invalid move state %d\n", handle);
162 return NULL;
163 } //end if
164 return botweaponstates[handle];
165 } //end of the function BotWeaponStateFromHandle
166 //===========================================================================
167 //
168 // Parameter: -
169 // Returns: -
170 // Changes Globals: -
171 //===========================================================================
172 #ifdef DEBUG_AI_WEAP
DumpWeaponConfig(weaponconfig_t * wc)173 void DumpWeaponConfig(weaponconfig_t *wc)
174 {
175 FILE *fp;
176 int i;
177
178 fp = Log_FileStruct();
179 if (!fp) return;
180 for (i = 0; i < wc->numprojectiles; i++)
181 {
182 WriteStructure(fp, &projectileinfo_struct, (char *) &wc->projectileinfo[i]);
183 Log_Flush();
184 } //end for
185 for (i = 0; i < wc->numweapons; i++)
186 {
187 WriteStructure(fp, &weaponinfo_struct, (char *) &wc->weaponinfo[i]);
188 Log_Flush();
189 } //end for
190 } //end of the function DumpWeaponConfig
191 #endif //DEBUG_AI_WEAP
192 //===========================================================================
193 //
194 // Parameter: -
195 // Returns: -
196 // Changes Globals: -
197 //===========================================================================
LoadWeaponConfig(char * filename)198 weaponconfig_t *LoadWeaponConfig(char *filename)
199 {
200 int max_weaponinfo, max_projectileinfo;
201 token_t token;
202 char path[MAX_PATH];
203 int i, j;
204 source_t *source;
205 weaponconfig_t *wc;
206 weaponinfo_t weaponinfo;
207
208 max_weaponinfo = (int) LibVarValue("max_weaponinfo", "32");
209 if (max_weaponinfo < 0)
210 {
211 botimport.Print(PRT_ERROR, "max_weaponinfo = %d\n", max_weaponinfo);
212 max_weaponinfo = 32;
213 LibVarSet("max_weaponinfo", "32");
214 } //end if
215 max_projectileinfo = (int) LibVarValue("max_projectileinfo", "32");
216 if (max_projectileinfo < 0)
217 {
218 botimport.Print(PRT_ERROR, "max_projectileinfo = %d\n", max_projectileinfo);
219 max_projectileinfo = 32;
220 LibVarSet("max_projectileinfo", "32");
221 } //end if
222 strncpy(path, filename, MAX_PATH);
223 PC_SetBaseFolder(BOTFILESBASEFOLDER);
224 source = LoadSourceFile(path);
225 if (!source)
226 {
227 botimport.Print(PRT_ERROR, "counldn't load %s\n", path);
228 return NULL;
229 } //end if
230 //initialize weapon config
231 wc = (weaponconfig_t *) GetClearedHunkMemory(sizeof(weaponconfig_t) +
232 max_weaponinfo * sizeof(weaponinfo_t) +
233 max_projectileinfo * sizeof(projectileinfo_t));
234 wc->weaponinfo = (weaponinfo_t *) ((char *) wc + sizeof(weaponconfig_t));
235 wc->projectileinfo = (projectileinfo_t *) ((char *) wc->weaponinfo +
236 max_weaponinfo * sizeof(weaponinfo_t));
237 wc->numweapons = max_weaponinfo;
238 wc->numprojectiles = 0;
239 //parse the source file
240 while(PC_ReadToken(source, &token))
241 {
242 if (!strcmp(token.string, "weaponinfo"))
243 {
244 Com_Memset(&weaponinfo, 0, sizeof(weaponinfo_t));
245 if (!ReadStructure(source, &weaponinfo_struct, (char *) &weaponinfo))
246 {
247 FreeMemory(wc);
248 FreeSource(source);
249 return NULL;
250 } //end if
251 if (weaponinfo.number < 0 || weaponinfo.number >= max_weaponinfo)
252 {
253 botimport.Print(PRT_ERROR, "weapon info number %d out of range in %s\n", weaponinfo.number, path);
254 FreeMemory(wc);
255 FreeSource(source);
256 return NULL;
257 } //end if
258 Com_Memcpy(&wc->weaponinfo[weaponinfo.number], &weaponinfo, sizeof(weaponinfo_t));
259 wc->weaponinfo[weaponinfo.number].valid = qtrue;
260 } //end if
261 else if (!strcmp(token.string, "projectileinfo"))
262 {
263 if (wc->numprojectiles >= max_projectileinfo)
264 {
265 botimport.Print(PRT_ERROR, "more than %d projectiles defined in %s\n", max_projectileinfo, path);
266 FreeMemory(wc);
267 FreeSource(source);
268 return NULL;
269 } //end if
270 Com_Memset(&wc->projectileinfo[wc->numprojectiles], 0, sizeof(projectileinfo_t));
271 if (!ReadStructure(source, &projectileinfo_struct, (char *) &wc->projectileinfo[wc->numprojectiles]))
272 {
273 FreeMemory(wc);
274 FreeSource(source);
275 return NULL;
276 } //end if
277 wc->numprojectiles++;
278 } //end if
279 else
280 {
281 botimport.Print(PRT_ERROR, "unknown definition %s in %s\n", token.string, path);
282 FreeMemory(wc);
283 FreeSource(source);
284 return NULL;
285 } //end else
286 } //end while
287 FreeSource(source);
288 //fix up weapons
289 for (i = 0; i < wc->numweapons; i++)
290 {
291 if (!wc->weaponinfo[i].valid) continue;
292 if (!wc->weaponinfo[i].name[0])
293 {
294 botimport.Print(PRT_ERROR, "weapon %d has no name in %s\n", i, path);
295 FreeMemory(wc);
296 return NULL;
297 } //end if
298 if (!wc->weaponinfo[i].projectile[0])
299 {
300 botimport.Print(PRT_ERROR, "weapon %s has no projectile in %s\n", wc->weaponinfo[i].name, path);
301 FreeMemory(wc);
302 return NULL;
303 } //end if
304 //find the projectile info and copy it to the weapon info
305 for (j = 0; j < wc->numprojectiles; j++)
306 {
307 if (!strcmp(wc->projectileinfo[j].name, wc->weaponinfo[i].projectile))
308 {
309 Com_Memcpy(&wc->weaponinfo[i].proj, &wc->projectileinfo[j], sizeof(projectileinfo_t));
310 break;
311 } //end if
312 } //end for
313 if (j == wc->numprojectiles)
314 {
315 botimport.Print(PRT_ERROR, "weapon %s uses undefined projectile in %s\n", wc->weaponinfo[i].name, path);
316 FreeMemory(wc);
317 return NULL;
318 } //end if
319 } //end for
320 if (!wc->numweapons) botimport.Print(PRT_WARNING, "no weapon info loaded\n");
321 botimport.Print(PRT_MESSAGE, "loaded %s\n", path);
322 return wc;
323 } //end of the function LoadWeaponConfig
324 //===========================================================================
325 //
326 // Parameter: -
327 // Returns: -
328 // Changes Globals: -
329 //===========================================================================
WeaponWeightIndex(weightconfig_t * wwc,weaponconfig_t * wc)330 int *WeaponWeightIndex(weightconfig_t *wwc, weaponconfig_t *wc)
331 {
332 int *index, i;
333
334 //initialize item weight index
335 index = (int *) GetClearedMemory(sizeof(int) * wc->numweapons);
336
337 for (i = 0; i < wc->numweapons; i++)
338 {
339 index[i] = FindFuzzyWeight(wwc, wc->weaponinfo[i].name);
340 } //end for
341 return index;
342 } //end of the function WeaponWeightIndex
343 //===========================================================================
344 //
345 // Parameter: -
346 // Returns: -
347 // Changes Globals: -
348 //===========================================================================
BotFreeWeaponWeights(int weaponstate)349 void BotFreeWeaponWeights(int weaponstate)
350 {
351 bot_weaponstate_t *ws;
352
353 ws = BotWeaponStateFromHandle(weaponstate);
354 if (!ws) return;
355 if (ws->weaponweightconfig) FreeWeightConfig(ws->weaponweightconfig);
356 if (ws->weaponweightindex) FreeMemory(ws->weaponweightindex);
357 } //end of the function BotFreeWeaponWeights
358 //===========================================================================
359 //
360 // Parameter: -
361 // Returns: -
362 // Changes Globals: -
363 //===========================================================================
BotLoadWeaponWeights(int weaponstate,char * filename)364 int BotLoadWeaponWeights(int weaponstate, char *filename)
365 {
366 bot_weaponstate_t *ws;
367
368 ws = BotWeaponStateFromHandle(weaponstate);
369 if (!ws) return BLERR_CANNOTLOADWEAPONWEIGHTS;
370 BotFreeWeaponWeights(weaponstate);
371 //
372 ws->weaponweightconfig = ReadWeightConfig(filename);
373 if (!ws->weaponweightconfig)
374 {
375 botimport.Print(PRT_FATAL, "couldn't load weapon config %s\n", filename);
376 return BLERR_CANNOTLOADWEAPONWEIGHTS;
377 } //end if
378 if (!weaponconfig) return BLERR_CANNOTLOADWEAPONCONFIG;
379 ws->weaponweightindex = WeaponWeightIndex(ws->weaponweightconfig, weaponconfig);
380 return BLERR_NOERROR;
381 } //end of the function BotLoadWeaponWeights
382 //===========================================================================
383 //
384 // Parameter: -
385 // Returns: -
386 // Changes Globals: -
387 //===========================================================================
BotGetWeaponInfo(int weaponstate,int weapon,weaponinfo_t * weaponinfo)388 void BotGetWeaponInfo(int weaponstate, int weapon, weaponinfo_t *weaponinfo)
389 {
390 bot_weaponstate_t *ws;
391
392 if (!BotValidWeaponNumber(weapon)) return;
393 ws = BotWeaponStateFromHandle(weaponstate);
394 if (!ws) return;
395 if (!weaponconfig) return;
396 Com_Memcpy(weaponinfo, &weaponconfig->weaponinfo[weapon], sizeof(weaponinfo_t));
397 } //end of the function BotGetWeaponInfo
398 //===========================================================================
399 //
400 // Parameter: -
401 // Returns: -
402 // Changes Globals: -
403 //===========================================================================
BotChooseBestFightWeapon(int weaponstate,int * inventory)404 int BotChooseBestFightWeapon(int weaponstate, int *inventory)
405 {
406 int i, index, bestweapon;
407 float weight, bestweight;
408 weaponconfig_t *wc;
409 bot_weaponstate_t *ws;
410
411 ws = BotWeaponStateFromHandle(weaponstate);
412 if (!ws) return 0;
413 wc = weaponconfig;
414 if (!weaponconfig) return 0;
415
416 //if the bot has no weapon weight configuration
417 if (!ws->weaponweightconfig) return 0;
418
419 bestweight = 0;
420 bestweapon = 0;
421 for (i = 0; i < wc->numweapons; i++)
422 {
423 if (!wc->weaponinfo[i].valid) continue;
424 index = ws->weaponweightindex[i];
425 if (index < 0) continue;
426 weight = FuzzyWeight(inventory, ws->weaponweightconfig, index);
427 if (weight > bestweight)
428 {
429 bestweight = weight;
430 bestweapon = i;
431 } //end if
432 } //end for
433 return bestweapon;
434 } //end of the function BotChooseBestFightWeapon
435 //===========================================================================
436 //
437 // Parameter: -
438 // Returns: -
439 // Changes Globals: -
440 //===========================================================================
BotResetWeaponState(int weaponstate)441 void BotResetWeaponState(int weaponstate)
442 {
443 struct weightconfig_s *weaponweightconfig;
444 int *weaponweightindex;
445 bot_weaponstate_t *ws;
446
447 ws = BotWeaponStateFromHandle(weaponstate);
448 if (!ws) return;
449 weaponweightconfig = ws->weaponweightconfig;
450 weaponweightindex = ws->weaponweightindex;
451
452 //Com_Memset(ws, 0, sizeof(bot_weaponstate_t));
453 ws->weaponweightconfig = weaponweightconfig;
454 ws->weaponweightindex = weaponweightindex;
455 } //end of the function BotResetWeaponState
456 //========================================================================
457 //
458 // Parameter: -
459 // Returns: -
460 // Changes Globals: -
461 //========================================================================
BotAllocWeaponState(void)462 int BotAllocWeaponState(void)
463 {
464 int i;
465
466 for (i = 1; i <= MAX_CLIENTS; i++)
467 {
468 if (!botweaponstates[i])
469 {
470 botweaponstates[i] = GetClearedMemory(sizeof(bot_weaponstate_t));
471 return i;
472 } //end if
473 } //end for
474 return 0;
475 } //end of the function BotAllocWeaponState
476 //========================================================================
477 //
478 // Parameter: -
479 // Returns: -
480 // Changes Globals: -
481 //========================================================================
BotFreeWeaponState(int handle)482 void BotFreeWeaponState(int handle)
483 {
484 if (handle <= 0 || handle > MAX_CLIENTS)
485 {
486 botimport.Print(PRT_FATAL, "move state handle %d out of range\n", handle);
487 return;
488 } //end if
489 if (!botweaponstates[handle])
490 {
491 botimport.Print(PRT_FATAL, "invalid move state %d\n", handle);
492 return;
493 } //end if
494 BotFreeWeaponWeights(handle);
495 FreeMemory(botweaponstates[handle]);
496 botweaponstates[handle] = NULL;
497 } //end of the function BotFreeWeaponState
498 //===========================================================================
499 //
500 // Parameter: -
501 // Returns: -
502 // Changes Globals: -
503 //===========================================================================
BotSetupWeaponAI(void)504 int BotSetupWeaponAI(void)
505 {
506 char *file;
507
508 file = LibVarString("weaponconfig", "weapons.c");
509 weaponconfig = LoadWeaponConfig(file);
510 if (!weaponconfig)
511 {
512 botimport.Print(PRT_FATAL, "couldn't load the weapon config\n");
513 return BLERR_CANNOTLOADWEAPONCONFIG;
514 } //end if
515
516 #ifdef DEBUG_AI_WEAP
517 DumpWeaponConfig(weaponconfig);
518 #endif //DEBUG_AI_WEAP
519 //
520 return BLERR_NOERROR;
521 } //end of the function BotSetupWeaponAI
522 //===========================================================================
523 //
524 // Parameter: -
525 // Returns: -
526 // Changes Globals: -
527 //===========================================================================
BotShutdownWeaponAI(void)528 void BotShutdownWeaponAI(void)
529 {
530 int i;
531
532 if (weaponconfig) FreeMemory(weaponconfig);
533 weaponconfig = NULL;
534
535 for (i = 1; i <= MAX_CLIENTS; i++)
536 {
537 if (botweaponstates[i])
538 {
539 BotFreeWeaponState(i);
540 } //end if
541 } //end for
542 } //end of the function BotShutdownWeaponAI
543
544