mirror of
https://gitlab.com/ryandwyer/perfect-dark
synced 2026-07-01 11:08:47 -04:00
1182 lines
40 KiB
C
1182 lines
40 KiB
C
#include <ultra64.h>
|
|
#include "constants.h"
|
|
#include "game/chraction.h"
|
|
#include "game/debug.h"
|
|
#include "game/chr.h"
|
|
#include "game/ceil.h"
|
|
#include "game/gset.h"
|
|
#include "game/playermgr.h"
|
|
#include "game/mplayer/setup.h"
|
|
#include "game/bot.h"
|
|
#include "game/botact.h"
|
|
#include "game/botcmd.h"
|
|
#include "game/botinv.h"
|
|
#include "game/mplayer/mplayer.h"
|
|
#include "game/propobj.h"
|
|
#include "bss.h"
|
|
#include "lib/rng.h"
|
|
#include "data.h"
|
|
#include "types.h"
|
|
|
|
struct botweaponconfig g_BotWeaponConfigs[] = {
|
|
// score1
|
|
// | score2 targetammosec
|
|
// | | dualscore1 | criticalammopri
|
|
// | | | dualscore2 | | criticalammosec
|
|
// | | | | haspriammogoal | | | reloaddelay (seconds)
|
|
// | | | | | hassecammogoal | | | | allowpartialreloaddelay
|
|
// | | | | | | pridistconfig secdistconfig targetammopri | | | | |
|
|
/*WEAPON_NONE */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 0, 0 },
|
|
/*WEAPON_UNARMED */ { 13, 13, 13, 13, 1, 1, BOTDISTCFG_CLOSE, BOTDISTCFG_CLOSE, 0, 0, 0, 0, 0, 0 },
|
|
/*WEAPON_FALCON2 */ { 56, 60, 84, 88, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_CLOSE, 30, 0, 10, 0, 1, 0 },
|
|
/*WEAPON_FALCON2_SILENCER */ { 52, 60, 80, 88, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_CLOSE, 30, 0, 10, 0, 1, 0 },
|
|
/*WEAPON_FALCON2_SCOPE */ { 60, 60, 88, 88, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_CLOSE, 30, 0, 10, 0, 1, 0 },
|
|
/*WEAPON_MAGSEC4 */ { 76, 88, 104, 120, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_DEFAULT, 30, 30, 10, 10, 1, 0 },
|
|
/*WEAPON_MAULER */ { 64, 88, 92, 120, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_DEFAULT, 30, 30, 10, 10, 1, 0 },
|
|
/*WEAPON_PHOENIX */ { 72, 76, 100, 120, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_DEFAULT, 30, 30, 10, 10, 2, 0 },
|
|
/*WEAPON_DY357MAGNUM */ { 68, 76, 96, 120, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_CLOSE, 30, 0, 8, 0, 3, 0 },
|
|
/*WEAPON_DY357LX */ { 180, 188, 184, 188, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_CLOSE, 20, 0, 6, 0, 3, 0 },
|
|
/*WEAPON_CMP150 */ { 116, 128, 136, 152, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 100, 100, 30, 30, 2, 0 },
|
|
/*WEAPON_CYCLONE */ { 120, 128, 132, 140, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 150, 150, 50, 50, 2, 0 },
|
|
/*WEAPON_CALLISTO */ { 152, 176, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 100, 70, 25, 15, 2, 0 },
|
|
/*WEAPON_RCP120 */ { 172, 188, 0, 0, 1, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 300, 0, 40, 0, 2, 0 },
|
|
/*WEAPON_LAPTOPGUN */ { 128, 140, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 100, 0, 30, 0, 3, 0 },
|
|
/*WEAPON_DRAGON */ { 124, 148, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 90, 0, 30, 0, 1, 0 },
|
|
/*WEAPON_K7AVENGER */ { 156, 180, 0, 0, 1, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 150, 0, 40, 0, 2, 0 },
|
|
/*WEAPON_AR34 */ { 148, 176, 0, 0, 1, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 120, 0, 40, 0, 2, 0 },
|
|
/*WEAPON_SUPERDRAGON */ { 164, 188, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_SHOOTEXPLOSIVE, 120, 20, 30, 6, 1, 0 },
|
|
/*WEAPON_SHOTGUN */ { 140, 156, 0, 0, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_PISTOL, 18, 18, 8, 8, 6, 1 },
|
|
/*WEAPON_REAPER */ { 144, 176, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_CLOSE, 400, 0, 80, 0, 3, 0 },
|
|
/*WEAPON_SNIPERRIFLE */ { 28, 40, 0, 0, 1, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 30, 30, 10, 10, 2, 0 },
|
|
/*WEAPON_FARSIGHT */ { 188, 188, 0, 0, 1, 0, BOTDISTCFG_SHOOTEXPLOSIVE, BOTDISTCFG_FARSIGHT, 16, 0, 4, 0, 2, 0 },
|
|
/*WEAPON_DEVASTATOR */ { 176, 188, 0, 0, 1, 1, BOTDISTCFG_SHOOTEXPLOSIVE, BOTDISTCFG_SHOOTEXPLOSIVE, 20, 20, 4, 4, 2, 0 },
|
|
/*WEAPON_ROCKETLAUNCHER */ { 160, 188, 0, 0, 1, 1, BOTDISTCFG_SHOOTEXPLOSIVE, BOTDISTCFG_SHOOTEXPLOSIVE, 2, 2, 1, 1, 2, 0 },
|
|
/*WEAPON_SLAYER */ { 168, 188, 0, 0, 1, 1, BOTDISTCFG_SHOOTEXPLOSIVE, BOTDISTCFG_SHOOTEXPLOSIVE, 2, 2, 1, 1, 3, 0 },
|
|
#if VERSION == VERSION_JPN_FINAL
|
|
/*WEAPON_COMBATKNIFE */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
#else
|
|
/*WEAPON_COMBATKNIFE */ { 20, 40, 24, 40, 1, 1, BOTDISTCFG_CLOSE, BOTDISTCFG_DEFAULT, 0, 5, 0, 1, 1, 0 },
|
|
#endif
|
|
/*WEAPON_CROSSBOW */ { 108, 176, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 15, 15, 5, 5, 4, 1 },
|
|
/*WEAPON_TRANQUILIZER */ { 48, 188, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_CLOSE, 20, 24, 6, 8, 1, 0 },
|
|
/*WEAPON_LASER */ { 112, 112, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_CLOSE, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_GRENADE */ { 36, 172, 0, 0, 1, 1, BOTDISTCFG_THROWEXPLOSIVE, BOTDISTCFG_THROWEXPLOSIVE, 6, 6, 2, 2, 1, 0 },
|
|
/*WEAPON_NBOMB */ { 32, 188, 0, 0, 1, 1, BOTDISTCFG_THROWEXPLOSIVE, BOTDISTCFG_THROWEXPLOSIVE, 3, 3, 1, 1, 1, 0 },
|
|
/*WEAPON_TIMEDMINE */ { 12, 12, 0, 0, 0, 0, BOTDISTCFG_THROWEXPLOSIVE, BOTDISTCFG_DEFAULT, 5, 5, 1, 1, 1, 0 },
|
|
/*WEAPON_PROXIMITYMINE */ { 40, 176, 0, 0, 0, 0, BOTDISTCFG_THROWEXPLOSIVE, BOTDISTCFG_DEFAULT, 5, 5, 1, 1, 1, 0 },
|
|
/*WEAPON_REMOTEMINE */ { 44, 156, 0, 0, 1, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 5, 5, 2, 2, 1, 0 },
|
|
/*WEAPON_COMBATBOOST */ { 8, 8, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_PP9I */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_CC13 */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_KL01313 */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_KF7SPECIAL */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_ZZT */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_DMC */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_AR53 */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_RCP45 */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_PSYCHOSISGUN */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_NIGHTVISION */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_EYESPY */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_XRAYSCANNER */ { 4, 4, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_IRSCANNER */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
#if VERSION >= VERSION_PAL_FINAL
|
|
/*WEAPON_CLOAKINGDEVICE */ { 218, 218, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, TICKS(1200), 0, 0, 0, 1, 0 },
|
|
#else
|
|
/*WEAPON_CLOAKINGDEVICE */ { 218, 218, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 1200, 0, 0, 0, 1, 0 },
|
|
#endif
|
|
/*WEAPON_HORIZONSCANNER */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_TESTER */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_KINGSCEPTRE */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_ECMMINE */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_DATAUPLINK */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_RTRACKER */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_PRESIDENTSCANNER */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_DOORDECODER */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_AUTOSURGEON */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_EXPLOSIVES */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_SKEDARBOMB */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_COMMSRIDER */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_TRACERBUG */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_TARGETAMPLIFIER */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_DISGUISE40 */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_DISGUISE41 */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_FLIGHTPLANS */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_RESEARCHTAPE */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_BACKUPDISK */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_KEYCARD45 */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_KEYCARD46 */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_KEYCARD47 */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_KEYCARD48 */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_KEYCARD49 */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_KEYCARD4A */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_KEYCARD4B */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_KEYCARD4C */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_SUITCASE */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_BRIEFCASE */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
/*WEAPON_SHIELDTECHITEM */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
#endif
|
|
/*WEAPON_NECKLACE */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_HAMMER */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_SCREWDRIVER */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_ROCKET */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_HOMINGROCKET */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_GRENADEROUND */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_BOLT */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_BRIEFCASE2 */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_SKROCKET */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_CHOPPERGUN */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_WATCHLASER */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_MPSHIELD */ { 220, 220, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_DISABLED */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
/*WEAPON_SUICIDEPILL */ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 },
|
|
};
|
|
|
|
void botinv_score_weapon_by_itself(struct chrdata *chr, s32 weaponnum, s32 funcnum, s32 ifammo, bool dual, s32 *dst1, s32 *dst2);
|
|
|
|
/**
|
|
* Remove all items from the bot's inventory.
|
|
*/
|
|
void botinv_clear(struct chrdata *chr)
|
|
{
|
|
if (chr && chr->aibot) {
|
|
s32 i = 0;
|
|
|
|
for (i = 0; i < chr->aibot->maxitems; i++) {
|
|
chr->aibot->items[i].type = -1;
|
|
}
|
|
|
|
dprint();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a free slot from the bot's inventory.
|
|
*
|
|
* In theory this should never fail. The inventory has 10 slots. 6 are for
|
|
* weapons, and 4 are for scenario-specific items such as briefcases and the
|
|
* data uplink.
|
|
*/
|
|
struct invitem *botinv_get_free_slot(struct chrdata *chr)
|
|
{
|
|
s32 i;
|
|
|
|
if (!chr || !chr->aibot) {
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < chr->aibot->maxitems; i++) {
|
|
if (chr->aibot->items[i].type == -1) {
|
|
return &chr->aibot->items[i];
|
|
}
|
|
}
|
|
|
|
dprint();
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Retrieve an inventory item from the bot's inventory.
|
|
*/
|
|
struct invitem *botinv_get_item(struct chrdata *chr, s32 weaponnum)
|
|
{
|
|
s32 i;
|
|
|
|
if (!chr || !chr->aibot) {
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < chr->aibot->maxitems; i++) {
|
|
struct invitem *item = &chr->aibot->items[i];
|
|
|
|
if (item->type == -1) {
|
|
continue;
|
|
}
|
|
|
|
if (item->type == INVITEMTYPE_WEAP || item->type == INVITEMTYPE_DUAL) {
|
|
if (item->type_weap.weapon1 == weaponnum) {
|
|
return item;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Remove a weapon from the bot's inventory.
|
|
*/
|
|
void botinv_remove_item(struct chrdata *chr, s32 weaponnum)
|
|
{
|
|
s32 i;
|
|
|
|
if (!chr || !chr->aibot) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < chr->aibot->maxitems; i++) {
|
|
struct invitem *item = &chr->aibot->items[i];
|
|
|
|
if (item->type == -1) {
|
|
continue;
|
|
}
|
|
|
|
if (item->type == INVITEMTYPE_WEAP || item->type == INVITEMTYPE_DUAL) {
|
|
if (item->type_weap.weapon1 == weaponnum) {
|
|
chr->aibot->items[i].type = -1;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the item type of the given weapon number.
|
|
*
|
|
* See the INVITEMTYPE constants.
|
|
*/
|
|
u32 botinv_get_item_type(struct chrdata *chr, u32 weaponnum)
|
|
{
|
|
struct invitem *item;
|
|
|
|
if (!chr || !chr->aibot) {
|
|
return 0;
|
|
}
|
|
|
|
item = botinv_get_item(chr, weaponnum);
|
|
|
|
if (item) {
|
|
return item->type;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Give a single weapon to the bot.
|
|
*
|
|
* There is no pickup pad, so this is likely for dropped items.
|
|
*/
|
|
bool botinv_give_single_weapon(struct chrdata *chr, u32 weaponnum)
|
|
{
|
|
if (!chr || !chr->aibot) {
|
|
return false;
|
|
}
|
|
|
|
if (!botinv_get_item_type(chr, weaponnum)) {
|
|
struct invitem *item = botinv_get_free_slot(chr);
|
|
|
|
if (item) {
|
|
item->type = INVITEMTYPE_WEAP;
|
|
item->type_weap.weapon1 = weaponnum;
|
|
item->type_weap.pickuppad = -1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Grant a bot dual weapons. They must already have the single weapon in their
|
|
* inventory.
|
|
*
|
|
* This works by converting the single item into a dual item, which makes it
|
|
* impossible for the bot to single wield that weapon while they have dual.
|
|
* This is different to how the player's inventory works, where there are
|
|
* inventory items for both single and dual and the player can choose which one
|
|
* they want to use.
|
|
*/
|
|
void botinv_give_dual_weapon(struct chrdata *chr, u32 weaponnum)
|
|
{
|
|
struct invitem *item = botinv_get_item(chr, weaponnum);
|
|
|
|
if (item) {
|
|
item->type = INVITEMTYPE_DUAL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the pad number that the given weapon was collected from.
|
|
*
|
|
* Usually when you pick up two of the same weapon you get to dual wield, but
|
|
* this does not happen if the second weapon is from the same pad as the first
|
|
* (ie. is the first weapon respawned).
|
|
*/
|
|
s16 botinv_get_weapon_pad(struct chrdata *chr, u32 weaponnum)
|
|
{
|
|
struct invitem *item = botinv_get_item(chr, weaponnum);
|
|
|
|
if (item && item->type == INVITEMTYPE_WEAP) {
|
|
return item->type_weap.pickuppad;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Give a weapon to the bot, either by direct weapon or by ammo crate.
|
|
*
|
|
* This function does not give any ammo to the bot.
|
|
*/
|
|
bool botinv_give_prop(struct chrdata *chr, struct prop *prop)
|
|
{
|
|
bool result = false;
|
|
struct defaultobj *obj;
|
|
s32 i;
|
|
|
|
if (!chr || !chr->aibot) {
|
|
return false;
|
|
}
|
|
|
|
obj = prop->obj;
|
|
|
|
if (prop->type == PROPTYPE_WEAPON) {
|
|
if (obj->type == OBJTYPE_WEAPON) {
|
|
struct weaponobj *weapon = prop->weapon;
|
|
s32 weaponnum = weapon->weaponnum;
|
|
result = botinv_give_single_weapon(chr, weaponnum);
|
|
|
|
if (result) {
|
|
struct invitem *item = botinv_get_item(chr, weaponnum);
|
|
item->type_weap.pickuppad = obj->pad;
|
|
}
|
|
}
|
|
} else if (obj->type == OBJTYPE_MULTIAMMOCRATE) {
|
|
struct multiammocrateobj *multi = (struct multiammocrateobj *)prop->obj;
|
|
|
|
for (i = 0; i < 19; i++) {
|
|
if (multi->slots[i].quantity > 0) {
|
|
s32 weaponnum = botact_get_weapon_by_ammo_type(i + 1);
|
|
|
|
if (weaponnum > 0) {
|
|
botinv_give_single_weapon(chr, weaponnum);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void botinv0f198060(u32 arg0)
|
|
{
|
|
// empty
|
|
}
|
|
|
|
/**
|
|
* Score all weapons in the match's weaponset by themselves and write them to
|
|
* the 3 array pointers, ordered by score1 descending.
|
|
*/
|
|
void botinv_score_all_weapons(struct chrdata *chr, s32 *weaponnums, s32 *scores1, s32 *scores2)
|
|
{
|
|
s32 i;
|
|
s32 pri1;
|
|
s32 pri2;
|
|
s32 sec1;
|
|
s32 sec2;
|
|
|
|
// Gather scores for each weapon in the setup,
|
|
// taking the higher score out of both gun functions
|
|
for (i = 0; i < ARRAYCOUNT(g_MpSetup.weapons); i++) {
|
|
s32 weaponnum = g_MpWeapons[g_MpSetup.weapons[i]].weaponnum;
|
|
weaponnums[i] = weaponnum;
|
|
|
|
botinv_score_weapon_by_itself(chr, weaponnum, FUNC_PRIMARY, -1, false, &pri1, &pri2);
|
|
botinv_score_weapon_by_itself(chr, weaponnum, FUNC_SECONDARY, -1, false, &sec1, &sec2);
|
|
|
|
scores1[i] = pri1 >= sec1 ? pri1 : sec1;
|
|
scores2[i] = pri2 >= sec2 ? pri2 : sec2;
|
|
}
|
|
|
|
// Sort all three arrays by score1 descending
|
|
for (i = 0; i < ARRAYCOUNT(g_MpSetup.weapons); i++) {
|
|
s32 swapindex = i;
|
|
s32 tmp;
|
|
s32 j;
|
|
|
|
for (j = i + 1; j < ARRAYCOUNT(g_MpSetup.weapons); j++) {
|
|
if (scores1[j] > scores1[swapindex]) {
|
|
swapindex = j;
|
|
}
|
|
}
|
|
|
|
if (swapindex != i) {
|
|
tmp = scores1[swapindex];
|
|
scores1[swapindex] = scores1[i];
|
|
scores1[i] = tmp;
|
|
|
|
tmp = scores2[swapindex];
|
|
scores2[swapindex] = scores2[i];
|
|
scores2[i] = tmp;
|
|
|
|
tmp = weaponnums[swapindex];
|
|
weaponnums[swapindex] = weaponnums[i];
|
|
weaponnums[i] = tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true if the match's weaponset contains a shield.
|
|
*/
|
|
bool mp_has_shield(void)
|
|
{
|
|
s32 i;
|
|
|
|
for (i = 0; i < ARRAYCOUNT(g_MpSetup.weapons); i++) {
|
|
s32 weaponnum = g_MpWeapons[g_MpSetup.weapons[i]].weaponnum;
|
|
|
|
if (weaponnum == WEAPON_MPSHIELD) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get the weapon slot (0 to 5) by weapon number.
|
|
*/
|
|
s32 mp_get_weapon_slot_by_weapon_num(s32 weaponnum)
|
|
{
|
|
s32 result = -1;
|
|
s32 i;
|
|
|
|
for (i = 0; i < ARRAYCOUNT(g_MpSetup.weapons); i++) {
|
|
if (g_MpWeapons[g_MpSetup.weapons[i]].weaponnum == weaponnum && i < ARRAYCOUNT(g_MpSetup.weapons)) {
|
|
result = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Calculate scores for the given weapon and function and write them to the
|
|
* dst1 and dst2 pointers.
|
|
*
|
|
* Weapon scoring is used to determine if a weapon is better than another,
|
|
* which affects whether the bot engages in combat or seeks a better weapon.
|
|
*/
|
|
void botinv_score_weapon(struct chrdata *chr, s32 weaponnum, s32 funcnum, s32 ifammo, bool dual, s32 *dst1, s32 *dst2, bool comparewithtarget, bool learn)
|
|
{
|
|
s32 score1 = 0;
|
|
s32 score2 = 0;
|
|
s32 extra = 0;
|
|
|
|
// @dangerous: Array overflow can occur if more weapons are added to the
|
|
// game without extending the preferences table
|
|
if (ifammo < 0
|
|
|| (funcnum == FUNC_PRIMARY && ifammo == g_BotWeaponConfigs[weaponnum].haspriammogoal)
|
|
|| (funcnum != FUNC_PRIMARY && ifammo == g_BotWeaponConfigs[weaponnum].hassecammogoal)) {
|
|
if (dual) {
|
|
score1 = g_BotWeaponConfigs[weaponnum].dualscore1;
|
|
score2 = g_BotWeaponConfigs[weaponnum].dualscore2;
|
|
} else {
|
|
score1 = g_BotWeaponConfigs[weaponnum].score1;
|
|
score2 = g_BotWeaponConfigs[weaponnum].score2;
|
|
}
|
|
|
|
if (chr && chr->aibot) {
|
|
// Add weighting for explosive weapons for rocket sims
|
|
// and weighting for shields for shield sims, with extra
|
|
// weighting if the sim is meat or easy.
|
|
if (chr->aibot->config->difficulty == BOTDIFF_MEAT) {
|
|
extra = 100;
|
|
} else if (chr->aibot->config->difficulty == BOTDIFF_EASY) {
|
|
extra = 50;
|
|
}
|
|
|
|
if (chr->aibot->config->type == BOTTYPE_ROCKET) {
|
|
if (weaponnum == WEAPON_ROCKETLAUNCHER) {
|
|
score1 = extra + 300;
|
|
} else if (weaponnum == WEAPON_SLAYER) {
|
|
score1 = extra + 299;
|
|
} else if (weaponnum == WEAPON_DEVASTATOR) {
|
|
score1 = extra + 280;
|
|
} else if (weaponnum == WEAPON_SUPERDRAGON && funcnum != FUNC_PRIMARY) {
|
|
score1 = extra + 279;
|
|
} else if (weaponnum == WEAPON_PHOENIX && funcnum != FUNC_PRIMARY) {
|
|
score1 = extra + 260;
|
|
} else if (weaponnum == WEAPON_GRENADE) {
|
|
score1 = extra + 240;
|
|
}
|
|
} else if (chr->aibot->config->type == BOTTYPE_SHIELD) {
|
|
if (weaponnum == WEAPON_MPSHIELD) {
|
|
score1 = extra + 300;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Missing from this list:
|
|
// Mauler, K7 Avenger, AR34, sniper rifle, Farsight, laser, grenade
|
|
switch (weaponnum) {
|
|
case WEAPON_UNARMED:
|
|
if (comparewithtarget && funcnum != FUNC_PRIMARY) {
|
|
if (chr->target != -1
|
|
&& bot_get_targets_weapon_num(chr) > WEAPON_UNARMED
|
|
&& chr->aibot->config->difficulty > BOTDIFF_MEAT) {
|
|
score1 = 26;
|
|
score2 = 26;
|
|
} else {
|
|
score1 = 0;
|
|
score2 = 0;
|
|
}
|
|
}
|
|
break;
|
|
case WEAPON_FALCON2:
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
score1 = 15;
|
|
score2 = 15;
|
|
}
|
|
break;
|
|
case WEAPON_FALCON2_SILENCER:
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
score1 = 14;
|
|
score2 = 14;
|
|
}
|
|
break;
|
|
case WEAPON_FALCON2_SCOPE:
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
score1 = 16;
|
|
score2 = 16;
|
|
}
|
|
break;
|
|
case WEAPON_MAGSEC4:
|
|
if (funcnum == FUNC_PRIMARY) {
|
|
score1 = dual ? 91 : 63;
|
|
}
|
|
break;
|
|
case WEAPON_PHOENIX:
|
|
if (chr->aibot->config->type != BOTDIFF_HARD) {
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
if ((g_MpSetup.options & MPOPTION_ONEHITKILLS)
|
|
&& chr->aibot->config->difficulty >= BOTDIFF_NORMAL) {
|
|
score1 = 110;
|
|
score2 = 150;
|
|
}
|
|
} else {
|
|
score1 = dual ? 90 : 62;
|
|
}
|
|
}
|
|
break;
|
|
case WEAPON_DY357MAGNUM:
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
score1 = 17;
|
|
score2 = 17;
|
|
}
|
|
break;
|
|
case WEAPON_DY357LX:
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
score1 = 18;
|
|
score2 = 18;
|
|
}
|
|
break;
|
|
case WEAPON_CYCLONE:
|
|
if (funcnum == (chr->aibot->random1 % 2)) {
|
|
score1--;
|
|
}
|
|
break;
|
|
case WEAPON_CALLISTO:
|
|
if (funcnum == (chr->aibot->random1 % 2)) {
|
|
score1--;
|
|
}
|
|
break;
|
|
case WEAPON_RCP120:
|
|
if (chr->aibot->cloakdeviceenabled == false
|
|
&& botact_get_ammo_quantity_by_weapon(chr->aibot, WEAPON_RCP120, FUNC_PRIMARY, true) > 500
|
|
&& chr->aibot->config->difficulty > BOTDIFF_MEAT) {
|
|
score1 += chr->aibot->random1 % 10;
|
|
score2 += chr->aibot->random1 % 10;
|
|
}
|
|
break;
|
|
case WEAPON_LAPTOPGUN:
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
score1 = 0;
|
|
score2 = 0;
|
|
}
|
|
break;
|
|
case WEAPON_DRAGON:
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
score1 = 0;
|
|
score2 = 0;
|
|
}
|
|
break;
|
|
case WEAPON_SUPERDRAGON:
|
|
if (chr->aibot->config->type != BOTDIFF_HARD
|
|
&& (chr->aibot->random1 % 2) == funcnum) {
|
|
score1 -= 15;
|
|
}
|
|
break;
|
|
case WEAPON_SHOTGUN:
|
|
if (funcnum == (chr->aibot->random1 % 2)) {
|
|
score1--;
|
|
}
|
|
break;
|
|
case WEAPON_REAPER:
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
score1 = 19;
|
|
score2 = 80;
|
|
}
|
|
break;
|
|
case WEAPON_DEVASTATOR:
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
score1 = 0;
|
|
score2 = 0;
|
|
}
|
|
break;
|
|
case WEAPON_ROCKETLAUNCHER:
|
|
if (funcnum == (chr->aibot->random1 % 2)) {
|
|
score1--;
|
|
}
|
|
break;
|
|
case WEAPON_SLAYER:
|
|
if (chr->aibot->config->type == BOTDIFF_HARD) {
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
if (chr->aibot->config->difficulty > BOTDIFF_MEAT) {
|
|
if (comparewithtarget) {
|
|
if (chr->target != -1
|
|
&& chr->aibot->chrsinsight[mp_chr_to_chrindex(chr_get_target_prop(chr)->chr)] == 0
|
|
&& (chr->aibot->random1 % 2) == 0) {
|
|
score1 += 10;
|
|
} else {
|
|
score1 -= 10;
|
|
}
|
|
}
|
|
} else {
|
|
score1 = 0;
|
|
score2 = 0;
|
|
}
|
|
}
|
|
} else {
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
if (chr->aibot->config->difficulty >= BOTDIFF_NORMAL) {
|
|
if (comparewithtarget) {
|
|
if (chr->target != -1
|
|
&& chr->aibot->chrsinsight[mp_chr_to_chrindex(chr_get_target_prop(chr)->chr)] == 0
|
|
&& (chr->aibot->random1 % 2) == 0) {
|
|
score1 = 178;
|
|
score2 = 188;
|
|
} else {
|
|
score1 -= 15;
|
|
score2 -= 15;
|
|
}
|
|
}
|
|
} else {
|
|
score1 = 0;
|
|
score2 = 0;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case WEAPON_COMBATKNIFE:
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
score1 = 0;
|
|
score2 = 0;
|
|
}
|
|
break;
|
|
case WEAPON_CROSSBOW:
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
if (chr->aibot->config->difficulty > BOTDIFF_MEAT) {
|
|
score1 = 158;
|
|
score2 = 176;
|
|
} else {
|
|
score1 = 0;
|
|
score2 = 0;
|
|
}
|
|
} else {
|
|
if (comparewithtarget && chr->target != -1 && chr_get_target_prop(chr)->chr->blurdrugamount > TICKS(3500)) {
|
|
score1 = 0;
|
|
score2 = 0;
|
|
} else {
|
|
score1 = 49;
|
|
score2 = 188;
|
|
}
|
|
}
|
|
break;
|
|
case WEAPON_TRANQUILIZER:
|
|
if (comparewithtarget) {
|
|
s32 bluramount = 0;
|
|
|
|
if (chr->target != -1) {
|
|
bluramount = chr_get_target_prop(chr)->chr->blurdrugamount;
|
|
}
|
|
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
if (chr->aibot->config->difficulty <= BOTDIFF_MEAT) {
|
|
score1 = 0;
|
|
score2 = 0;
|
|
} else if (bluramount > TICKS(3500) && (chr->aibot->random1 % 2) == 0) {
|
|
score1 = chr->aibot->random1 % 140 + 48;
|
|
score2 = 188;
|
|
} else if (chr->aibot->random1 % 10 == 0) {
|
|
score1 = chr->aibot->random1 % 140 + 48;
|
|
score2 = 188;
|
|
} else {
|
|
score1 = 0;
|
|
score2 = 0;
|
|
}
|
|
} else {
|
|
if (bluramount >= TICKS(5000)) {
|
|
score2 = 48;
|
|
|
|
if (chr->aibot->random1 % 2) {
|
|
score1 = 0;
|
|
score2 = 0;
|
|
}
|
|
} else if (bluramount > TICKS(3500)) {
|
|
u32 value = (-bluramount * 16 + (PAL ? 66656 : 80000)) / TICKS(1500);
|
|
|
|
if (value > 15) {
|
|
value = 15;
|
|
}
|
|
|
|
value *= value;
|
|
value *= value;
|
|
value *= value;
|
|
|
|
if (value < chr->aibot->random1) {
|
|
score2 = 48;
|
|
|
|
if (chr->aibot->random1 % 2) {
|
|
score1 = 0;
|
|
score2 = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case WEAPON_NBOMB:
|
|
if (comparewithtarget && chr->target != -1) {
|
|
bot_get_targets_weapon_num(chr);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (learn) {
|
|
// Learn from recent usage stats
|
|
s32 weaponindex;
|
|
s32 extra = 0;
|
|
f32 float1;
|
|
f32 killrate = 1;
|
|
f32 float2;
|
|
|
|
if (g_Vars.lvframe60 > 0) {
|
|
killrate = g_Vars.totalkills * TICKS(3600.0f) / (f32)(g_Vars.lvframe60 * g_MpNumChrs);
|
|
|
|
if (killrate < 1) {
|
|
killrate = 1;
|
|
}
|
|
}
|
|
|
|
weaponindex = mp_get_weapon_slot_by_weapon_num(weaponnum);
|
|
|
|
if (weaponindex >= 0) {
|
|
float2 = ceilf(chr->aibot->equipdurations60[weaponindex][funcnum] * (1.0f / TICKS(3600.0f)));
|
|
|
|
if (float2 > 0) {
|
|
float1 = chr->aibot->killsbygunfunc[weaponindex][funcnum];
|
|
|
|
if (chr->aibot->config->difficulty >= BOTDIFF_NORMAL) {
|
|
float1 -= 3.0f * chr->aibot->suicidesbygunfunc[weaponindex][funcnum];
|
|
}
|
|
|
|
float1 = float1 / float2;
|
|
|
|
extra = float1 * 10.0f / killrate;
|
|
}
|
|
|
|
if (extra > 30) {
|
|
extra = 30;
|
|
}
|
|
|
|
extra += chr->aibot->equipextrascores[weaponindex];
|
|
|
|
score1 += extra;
|
|
score2 += extra;
|
|
|
|
if (score1 < 0) {
|
|
score1 = 0;
|
|
}
|
|
|
|
if (score2 < 0) {
|
|
score2 = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
*dst1 = score1;
|
|
*dst2 = score2;
|
|
}
|
|
|
|
void botinv_score_weapon_against_target(struct chrdata *chr, s32 weaponnum, s32 funcnum, s32 ifammo, bool dual, s32 *dst1, s32 *dst2)
|
|
{
|
|
botinv_score_weapon(chr, weaponnum, funcnum, ifammo, dual, dst1, dst2, true, true);
|
|
}
|
|
|
|
void botinv_score_weapon_by_itself(struct chrdata *chr, s32 weaponnum, s32 funcnum, s32 ifammo, bool dual, s32 *dst1, s32 *dst2)
|
|
{
|
|
botinv_score_weapon(chr, weaponnum, funcnum, ifammo, dual, dst1, dst2, false, true);
|
|
}
|
|
|
|
/**
|
|
* Return the aibot's distance configuration index for the given weapon and
|
|
* function.
|
|
*/
|
|
s32 botinv_get_dist_config(s32 weaponnum, s32 funcnum)
|
|
{
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
return g_BotWeaponConfigs[weaponnum].secdistconfig;
|
|
}
|
|
|
|
return g_BotWeaponConfigs[weaponnum].pridistconfig;
|
|
}
|
|
|
|
/**
|
|
* Check if the bot's personality permits it to use the given weapon and
|
|
* function.
|
|
*/
|
|
bool botinv_allows_weapon(struct chrdata *chr, s32 weaponnum, s32 funcnum)
|
|
{
|
|
bool allow = true;
|
|
|
|
if (chr->aibot->config->type == BOTTYPE_FIST) {
|
|
if (funcnum != FUNC_PRIMARY) {
|
|
if (g_BotWeaponConfigs[weaponnum].secdistconfig != BOTDISTCFG_CLOSE) {
|
|
allow = false;
|
|
}
|
|
} else {
|
|
if (g_BotWeaponConfigs[weaponnum].pridistconfig != BOTDISTCFG_CLOSE) {
|
|
allow = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return allow;
|
|
}
|
|
|
|
/**
|
|
* Tick the bot's inventory.
|
|
*
|
|
* The logic for deciding when to switch weapons is here.
|
|
*/
|
|
void botinv_tick(struct chrdata *chr)
|
|
{
|
|
s32 newweaponnum = WEAPON_UNARMED;
|
|
s32 newfuncnum = FUNC_PRIMARY;
|
|
s32 weaponindex;
|
|
struct aibot *aibot;
|
|
bool keepcurrentweapon = false;
|
|
s32 i;
|
|
|
|
if (!chr || !chr->aibot) {
|
|
return;
|
|
}
|
|
|
|
aibot = chr->aibot;
|
|
weaponindex = mp_get_weapon_slot_by_weapon_num(aibot->weaponnum);
|
|
|
|
if (weaponindex >= 0) {
|
|
aibot->equipdurations60[weaponindex][aibot->gunfunc] += g_Vars.lvupdate60;
|
|
}
|
|
|
|
// Every 60-61 seconds, decrease suicide values. These values are used when
|
|
// determining which gun to use. The more suicides with a particular gun and
|
|
// function, the less likely the aibot is to use it. This value cools off.
|
|
aibot->dampensuicidesttl60 -= g_Vars.lvupdate60;
|
|
|
|
if (aibot->dampensuicidesttl60 < 0) {
|
|
aibot->dampensuicidesttl60 = TICKS(3600) + random() % TICKS(60);
|
|
|
|
for (i = 0; i < ARRAYCOUNT(aibot->suicidesbygunfunc); i++) {
|
|
aibot->suicidesbygunfunc[i][0] *= 0.9f;
|
|
aibot->suicidesbygunfunc[i][1] *= 0.9f;
|
|
}
|
|
}
|
|
|
|
// Every 10-60 seconds, generate new equipextrascores
|
|
aibot->equipextrascorestimer60 -= g_Vars.lvupdate60;
|
|
|
|
if (aibot->equipextrascorestimer60 < 0) {
|
|
aibot->equipextrascorestimer60 = TICKS(600) + random() % TICKS(3000);
|
|
|
|
for (i = 0; i < ARRAYCOUNT(aibot->equipextrascores); i++) {
|
|
if (aibot->config->difficulty == BOTDIFF_MEAT) {
|
|
aibot->equipextrascores[i] = random() % 200 - 100; // -100 to +100
|
|
} else if (aibot->config->difficulty == BOTDIFF_EASY) {
|
|
aibot->equipextrascores[i] = random() % 100 - 50; // -50 to +50
|
|
} else {
|
|
aibot->equipextrascores[i] = random() % 30 - 15; // -15 to +15
|
|
}
|
|
}
|
|
}
|
|
|
|
// Every 2-12 seconds, generate a new random value which is used as a factor
|
|
// in various aibot logic.
|
|
aibot->random1ttl60 -= g_Vars.lvupdate60;
|
|
|
|
if (aibot->random1ttl60 < 0) {
|
|
aibot->random1ttl60 = TICKS(120) + random() % TICKS(600);
|
|
aibot->random1 = random();
|
|
}
|
|
|
|
if (aibot->cyclonedischarging[HAND_LEFT] == 0
|
|
&& aibot->cyclonedischarging[HAND_RIGHT] == 0
|
|
&& aibot->burstsdone[HAND_LEFT] <= 0
|
|
&& aibot->burstsdone[HAND_RIGHT] <= 0
|
|
&& aibot->reaperspeed[HAND_LEFT] <= 0
|
|
&& aibot->reaperspeed[HAND_RIGHT] <= 0
|
|
&& aibot->skrocket == NULL) {
|
|
if (chr->myaction == MA_AIBOTDOWNLOAD) {
|
|
keepcurrentweapon = true;
|
|
}
|
|
|
|
if (!keepcurrentweapon && aibot->config->type == BOTTYPE_PEACE) {
|
|
newfuncnum = FUNC_SECONDARY;
|
|
keepcurrentweapon = true;
|
|
}
|
|
|
|
if (!keepcurrentweapon) {
|
|
s32 bestscore = 0;
|
|
s32 score1;
|
|
s32 score2;
|
|
s32 canuse;
|
|
s32 j;
|
|
|
|
for (i = -1; i < aibot->maxitems; i++) {
|
|
s32 weaponnum = -1;
|
|
struct invitem *item = NULL;
|
|
|
|
if (i < 0) {
|
|
weaponnum = WEAPON_UNARMED;
|
|
} else {
|
|
item = &aibot->items[i];
|
|
|
|
if (item->type == INVITEMTYPE_WEAP || item->type == INVITEMTYPE_DUAL) {
|
|
weaponnum = item->type_weap.weapon1;
|
|
}
|
|
}
|
|
|
|
if (weaponnum >= 0) {
|
|
for (j = 1; j >= 0; j--) {
|
|
if (j != FUNC_PRIMARY) {
|
|
canuse = g_BotWeaponConfigs[weaponnum].hassecammogoal;
|
|
} else {
|
|
canuse = g_BotWeaponConfigs[weaponnum].haspriammogoal;
|
|
}
|
|
|
|
if (canuse && botinv_allows_weapon(chr, weaponnum, j)) {
|
|
botinv_score_weapon_against_target(chr, weaponnum, j, 1, item && item->type == INVITEMTYPE_DUAL, &score1, &score2);
|
|
|
|
if (score1 >= bestscore) {
|
|
if (!botact_get_ammo_type_by_function(weaponnum, j)
|
|
|| botact_get_ammo_quantity_by_weapon(aibot, weaponnum, j, true) > 0) {
|
|
bestscore = score1;
|
|
newweaponnum = weaponnum;
|
|
newfuncnum = j;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Consider setting knives to secondary function (throw)
|
|
if (newweaponnum == WEAPON_COMBATKNIFE
|
|
&& botact_get_ammo_quantity_by_weapon(aibot, WEAPON_COMBATKNIFE, FUNC_SECONDARY, true) >= 2
|
|
&& chr->target != -1
|
|
&& chr->aibot->chrdistances[mp_chr_to_chrindex(chr_get_target_prop(chr)->chr)] > 200
|
|
&& chr->aibot->chrdistances[mp_chr_to_chrindex(chr_get_target_prop(chr)->chr)] < 1500) {
|
|
newfuncnum = FUNC_SECONDARY;
|
|
}
|
|
|
|
// Consider setting Phoenix and SuperDragon to their explosive functions
|
|
if (aibot->config->type == BOTTYPE_ROCKET) {
|
|
if (newweaponnum == WEAPON_PHOENIX && botact_get_ammo_quantity_by_weapon(aibot, WEAPON_PHOENIX, FUNC_SECONDARY, true) > 0) {
|
|
newfuncnum = FUNC_SECONDARY;
|
|
} else if (newweaponnum == WEAPON_SUPERDRAGON && botact_get_ammo_quantity_by_weapon(aibot, WEAPON_SUPERDRAGON, FUNC_SECONDARY, true) > 0) {
|
|
newfuncnum = FUNC_SECONDARY;
|
|
}
|
|
}
|
|
|
|
botinv_switch_to_weapon(chr, newweaponnum, newfuncnum);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Equip the given weapon and switch to the given function.
|
|
*
|
|
* The weapon must already exist in the bot's inventory,
|
|
* otherwise unarmed will be equipped instead.
|
|
*/
|
|
bool botinv_switch_to_weapon(struct chrdata *chr, s32 weaponnum, s32 funcnum)
|
|
{
|
|
struct invitem *item;
|
|
struct funcdef *func;
|
|
struct aibot *aibot;
|
|
s32 i;
|
|
s32 modelnum;
|
|
bool changinggun;
|
|
bool changingfunc;
|
|
|
|
if (!chr || !chr->aibot) {
|
|
return false;
|
|
}
|
|
|
|
aibot = chr->aibot;
|
|
|
|
if (weaponnum == WEAPON_BRIEFCASE2) {
|
|
return true;
|
|
}
|
|
|
|
// If changing to anything other than unarmed, make sure the aibot has the
|
|
// weapon in their inventory. Otherwise make them switch to unarmed.
|
|
if (weaponnum != WEAPON_UNARMED) {
|
|
item = botinv_get_item(chr, weaponnum);
|
|
|
|
if (!item) {
|
|
weaponnum = WEAPON_UNARMED;
|
|
funcnum = FUNC_PRIMARY;
|
|
}
|
|
}
|
|
|
|
changinggun = weaponnum != aibot->weaponnum;
|
|
changingfunc = funcnum != aibot->gunfunc;
|
|
|
|
if (changinggun) {
|
|
aibot->changeguntimer60 = TICKS(60);
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
aibot->cyclonedischarging[i] = false;
|
|
aibot->burstsdone[i] = 0;
|
|
aibot->reaperspeed[i] = 0;
|
|
|
|
if (chr->weapons_held[i]) {
|
|
chr->weapons_held[i]->obj->hidden |= OBJHFLAG_DELETING;
|
|
chr->weapons_held[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return any loaded ammo to reserve
|
|
if (changingfunc || changinggun) {
|
|
for (i = 0; i < 2; i++) {
|
|
if (aibot->loadedammo[i] > 0) {
|
|
botact_give_ammo_by_weapon(aibot, aibot->weaponnum, aibot->gunfunc, aibot->loadedammo[i]);
|
|
aibot->loadedammo[i] = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Assign new weapon and function
|
|
aibot->gunfunc = funcnum;
|
|
aibot->weaponnum = weaponnum;
|
|
|
|
// Load ammo from reserve into new weapon
|
|
if (changingfunc && !changinggun) {
|
|
for (i = 0; i < 2; i++) {
|
|
if (chr->weapons_held[i]) {
|
|
botact_reload(chr, i, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!changinggun) {
|
|
modelnum = playermgr_get_model_of_weapon(weaponnum);
|
|
|
|
// @dangerous: item is uninitialised if weaponnum is WEAPON_UNARMED.
|
|
// This function assumes playermgr_get_model_of_weapon returns a negative value for
|
|
// WEAPON_UNARMED which is a dangerous assumption to make, but correct.
|
|
if (modelnum >= 0 && item && item->type == INVITEMTYPE_DUAL && chr->weapons_held[HAND_LEFT] == NULL) {
|
|
chr_give_weapon(chr, modelnum, weaponnum, OBJFLAG_WEAPON_LEFTHANDED);
|
|
botact_reload(chr, HAND_LEFT, false);
|
|
}
|
|
}
|
|
|
|
func = gset_get_funcdef_by_weaponnum_funcnum(weaponnum, funcnum);
|
|
|
|
aibot->ismeleeweapon = func && func->type == INVENTORYFUNCTYPE_MELEE;
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
if (chr->weapons_held[i]) {
|
|
chr->weapons_held[i]->weapon->gunfunc = chr->aibot->gunfunc;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Drop one or all items. The item does not need to be the equipped item but it
|
|
* must exist in the bot's inventory.
|
|
*
|
|
* dropall is used when the bot is killed.
|
|
*/
|
|
void botinv_drop(struct chrdata *chr, s32 weaponnum, u8 dropall)
|
|
{
|
|
s32 i;
|
|
|
|
if (!chr || !chr->aibot) {
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < chr->aibot->maxitems; i++) {
|
|
struct invitem *item = &chr->aibot->items[i];
|
|
|
|
if (item->type == -1) {
|
|
continue;
|
|
}
|
|
|
|
if ((item->type == INVITEMTYPE_WEAP || item->type == INVITEMTYPE_DUAL)
|
|
&& (dropall || weaponnum == item->type_weap.weapon1)) {
|
|
if (!gset_has_weapon_flag(item->type_weap.weapon1, WEAPONFLAG_UNDROPPABLE)
|
|
|| (g_Vars.normmplayerisrunning
|
|
&& g_MpSetup.scenario == MPSCENARIO_HACKERCENTRAL
|
|
&& item->type_weap.weapon1 == WEAPON_DATAUPLINK)) {
|
|
s32 modelnum = playermgr_get_model_of_weapon(item->type_weap.weapon1);
|
|
|
|
if (modelnum > 0) {
|
|
struct prop *prop = weapon_create_for_chr(chr, modelnum, item->type_weap.weapon1, OBJFLAG_WEAPON_AICANNOTUSE, NULL, NULL);
|
|
|
|
if (prop) {
|
|
obj_set_dropped(prop, DROPTYPE_DEFAULT);
|
|
obj_drop(prop, true);
|
|
|
|
if (item->type_weap.weapon1 == WEAPON_BRIEFCASE2) {
|
|
scenario_handle_dropped_token(chr, prop);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((dropall && weaponnum >= WEAPON_FALCON2)
|
|
|| (!dropall && weaponnum == chr->aibot->weaponnum)) {
|
|
botinv_switch_to_weapon(chr, WEAPON_UNARMED, FUNC_PRIMARY);
|
|
}
|
|
|
|
chr->hidden |= CHRHFLAG_DROPPINGITEM;
|
|
|
|
if (!dropall) {
|
|
botinv_remove_item(chr, weaponnum);
|
|
}
|
|
}
|
|
|
|
void botinv_drop_all(struct chrdata *chr, u32 weaponnum)
|
|
{
|
|
botinv_drop(chr, weaponnum, true);
|
|
}
|
|
|
|
void botinv_drop_one(struct chrdata *chr, u32 weaponnum)
|
|
{
|
|
botinv_drop(chr, weaponnum, false);
|
|
}
|