perfect_dark/src/game/botinv.c

1183 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/game_0b0fd0.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 aibotweaponpreference g_AibotWeaponPreferences[] = {
// haspriammogoal
// | hassecammogoal
// | | pridistconfig
// | | | secdistconfig
// | | | | targetammopri
// | | | | | targetammosec
// | | | | | | criticalammopri
// | | | | | | | criticalammosec
// | | | | | | | | reloaddelay (seconds)
// | | | | | | | | | allowpartialreloaddelay
// | | | | | | | | | |
/*0x00*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 0, 0 }, // WEAPON_NONE
/*0x01*/ { 13, 13, 13, 13, 1, 1, BOTDISTCFG_CLOSE, BOTDISTCFG_CLOSE, 0, 0, 0, 0, 0, 0 }, // WEAPON_UNARMED
/*0x02*/ { 56, 60, 84, 88, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_CLOSE, 30, 0, 10, 0, 1, 0 }, // WEAPON_FALCON2
/*0x03*/ { 52, 60, 80, 88, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_CLOSE, 30, 0, 10, 0, 1, 0 }, // WEAPON_FALCON2_SILENCER
/*0x04*/ { 60, 60, 88, 88, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_CLOSE, 30, 0, 10, 0, 1, 0 }, // WEAPON_FALCON2_SCOPE
/*0x05*/ { 76, 88, 104, 120, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_DEFAULT, 30, 30, 10, 10, 1, 0 }, // WEAPON_MAGSEC4
/*0x06*/ { 64, 88, 92, 120, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_DEFAULT, 30, 30, 10, 10, 1, 0 }, // WEAPON_MAULER
/*0x07*/ { 72, 76, 100, 120, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_DEFAULT, 30, 30, 10, 10, 2, 0 }, // WEAPON_PHOENIX
/*0x08*/ { 68, 76, 96, 120, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_CLOSE, 30, 0, 8, 0, 3, 0 }, // WEAPON_DY357MAGNUM
/*0x09*/ { 180, 188, 184, 188, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_CLOSE, 20, 0, 6, 0, 3, 0 }, // WEAPON_DY357LX
/*0x0a*/ { 116, 128, 136, 152, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 100, 100, 30, 30, 2, 0 }, // WEAPON_CMP150
/*0x0b*/ { 120, 128, 132, 140, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 150, 150, 50, 50, 2, 0 }, // WEAPON_CYCLONE
/*0x0c*/ { 152, 176, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 100, 70, 25, 15, 2, 0 }, // WEAPON_CALLISTO
/*0x0d*/ { 172, 188, 0, 0, 1, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 300, 0, 40, 0, 2, 0 }, // WEAPON_RCP120
/*0x0e*/ { 128, 140, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 100, 0, 30, 0, 3, 0 }, // WEAPON_LAPTOPGUN
/*0x0f*/ { 124, 148, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 90, 0, 30, 0, 1, 0 }, // WEAPON_DRAGON
/*0x10*/ { 156, 180, 0, 0, 1, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 150, 0, 40, 0, 2, 0 }, // WEAPON_K7AVENGER
/*0x11*/ { 148, 176, 0, 0, 1, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 120, 0, 40, 0, 2, 0 }, // WEAPON_AR34
/*0x12*/ { 164, 188, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_SHOOTEXPLOSIVE, 120, 20, 30, 6, 1, 0 }, // WEAPON_SUPERDRAGON
/*0x13*/ { 140, 156, 0, 0, 1, 1, BOTDISTCFG_PISTOL, BOTDISTCFG_PISTOL, 18, 18, 8, 8, 6, 1 }, // WEAPON_SHOTGUN
/*0x14*/ { 144, 176, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_CLOSE, 400, 0, 80, 0, 3, 0 }, // WEAPON_REAPER
/*0x15*/ { 28, 40, 0, 0, 1, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 30, 30, 10, 10, 2, 0 }, // WEAPON_SNIPERRIFLE
/*0x16*/ { 188, 188, 0, 0, 1, 0, BOTDISTCFG_SHOOTEXPLOSIVE, BOTDISTCFG_FARSIGHT, 16, 0, 4, 0, 2, 0 }, // WEAPON_FARSIGHT
/*0x17*/ { 176, 188, 0, 0, 1, 1, BOTDISTCFG_SHOOTEXPLOSIVE, BOTDISTCFG_SHOOTEXPLOSIVE, 20, 20, 4, 4, 2, 0 }, // WEAPON_DEVASTATOR
/*0x18*/ { 160, 188, 0, 0, 1, 1, BOTDISTCFG_SHOOTEXPLOSIVE, BOTDISTCFG_SHOOTEXPLOSIVE, 2, 2, 1, 1, 2, 0 }, // WEAPON_ROCKETLAUNCHER
/*0x19*/ { 168, 188, 0, 0, 1, 1, BOTDISTCFG_SHOOTEXPLOSIVE, BOTDISTCFG_SHOOTEXPLOSIVE, 2, 2, 1, 1, 3, 0 }, // WEAPON_SLAYER
#if VERSION == VERSION_JPN_FINAL
/*0x1a*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_COMBATKNIFE
#else
/*0x1a*/ { 20, 40, 24, 40, 1, 1, BOTDISTCFG_CLOSE, BOTDISTCFG_DEFAULT, 0, 5, 0, 1, 1, 0 }, // WEAPON_COMBATKNIFE
#endif
/*0x1b*/ { 108, 176, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 15, 15, 5, 5, 4, 1 }, // WEAPON_CROSSBOW
/*0x1c*/ { 48, 188, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_CLOSE, 20, 24, 6, 8, 1, 0 }, // WEAPON_TRANQUILIZER
/*0x1d*/ { 112, 112, 0, 0, 1, 1, BOTDISTCFG_DEFAULT, BOTDISTCFG_CLOSE, 0, 0, 0, 0, 1, 0 }, // WEAPON_LASER
/*0x1e*/ { 36, 172, 0, 0, 1, 1, BOTDISTCFG_THROWEXPLOSIVE, BOTDISTCFG_THROWEXPLOSIVE, 6, 6, 2, 2, 1, 0 }, // WEAPON_GRENADE
/*0x1f*/ { 32, 188, 0, 0, 1, 1, BOTDISTCFG_THROWEXPLOSIVE, BOTDISTCFG_THROWEXPLOSIVE, 3, 3, 1, 1, 1, 0 }, // WEAPON_NBOMB
/*0x20*/ { 12, 12, 0, 0, 0, 0, BOTDISTCFG_THROWEXPLOSIVE, BOTDISTCFG_DEFAULT, 5, 5, 1, 1, 1, 0 }, // WEAPON_TIMEDMINE
/*0x21*/ { 40, 176, 0, 0, 0, 0, BOTDISTCFG_THROWEXPLOSIVE, BOTDISTCFG_DEFAULT, 5, 5, 1, 1, 1, 0 }, // WEAPON_PROXIMITYMINE
/*0x22*/ { 44, 156, 0, 0, 1, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 5, 5, 2, 2, 1, 0 }, // WEAPON_REMOTEMINE
/*0x23*/ { 8, 8, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_COMBATBOOST
/*0x24*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_PP9I
/*0x25*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_CC13
/*0x26*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_KL01313
/*0x27*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_KF7SPECIAL
/*0x28*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_ZZT
/*0x29*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_DMC
/*0x2a*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_AR53
/*0x2b*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_RCP45
/*0x2c*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_PSYCHOSISGUN
/*0x2d*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_NIGHTVISION
/*0x2e*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_EYESPY
/*0x2f*/ { 4, 4, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_XRAYSCANNER
/*0x30*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_IRSCANNER
#if VERSION >= VERSION_PAL_FINAL
/*0x31*/ { 218, 218, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, TICKS(1200), 0, 0, 0, 1, 0 }, // WEAPON_CLOAKINGDEVICE
#else
/*0x31*/ { 218, 218, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 1200, 0, 0, 0, 1, 0 }, // WEAPON_CLOAKINGDEVICE
#endif
/*0x32*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_HORIZONSCANNER
/*0x33*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_TESTER
/*0x34*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_ROCKETLAUNCHER_34
/*0x35*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_ECMMINE
/*0x36*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_DATAUPLINK
/*0x37*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_RTRACKER
/*0x38*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_PRESIDENTSCANNER
/*0x39*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_DOORDECODER
/*0x3a*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_AUTOSURGEON
/*0x3b*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_EXPLOSIVES
/*0x3c*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_SKEDARBOMB
/*0x3d*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_COMMSRIDER
/*0x3e*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_TRACERBUG
/*0x3f*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_TARGETAMPLIFIER
/*0x40*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_DISGUISE40
/*0x41*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_DISGUISE41
/*0x42*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_FLIGHTPLANS
/*0x43*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_RESEARCHTAPE
/*0x44*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_BACKUPDISK
/*0x45*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_KEYCARD45
/*0x46*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_KEYCARD46
/*0x47*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_KEYCARD47
/*0x48*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_KEYCARD48
/*0x49*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_KEYCARD49
/*0x4a*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_KEYCARD4A
/*0x4b*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_KEYCARD4B
/*0x4c*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_KEYCARD4C
/*0x4d*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_SUITCASE
/*0x4e*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_BRIEFCASE
#if VERSION >= VERSION_NTSC_1_0
/*0x4f*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_SHIELDTECHITEM
#endif
/*0x50*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_NECKLACE
/*0x51*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_HAMMER
/*0x52*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_SCREWDRIVER
/*0x53*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_ROCKET
/*0x54*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_HOMINGROCKET
/*0x55*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_GRENADEROUND
/*0x56*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_BOLT
/*0x57*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_BRIEFCASE2
/*0x58*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_SKROCKET
/*0x59*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_CHOPPERGUN
/*0x5a*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_WATCHLASER
/*0x5b*/ { 220, 220, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_MPSHIELD
/*0x5c*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_DISABLED
/*0x5d*/ { 0, 0, 0, 0, 0, 0, BOTDISTCFG_DEFAULT, BOTDISTCFG_DEFAULT, 0, 0, 0, 0, 1, 0 }, // WEAPON_SUICIDEPILL
};
/**
* Remove all items from the bot's inventory.
*/
void botinvClear(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 *botinvGetFreeSlot(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 *botinvGetItem(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 botinvRemoveItem(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 botinvGetItemType(struct chrdata *chr, u32 weaponnum)
{
struct invitem *item;
if (!chr || !chr->aibot) {
return 0;
}
item = botinvGetItem(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 botinvGiveSingleWeapon(struct chrdata *chr, u32 weaponnum)
{
if (!chr || !chr->aibot) {
return false;
}
if (!botinvGetItemType(chr, weaponnum)) {
struct invitem *item = botinvGetFreeSlot(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 botinvGiveDualWeapon(struct chrdata *chr, u32 weaponnum)
{
struct invitem *item = botinvGetItem(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 botinvGetWeaponPad(struct chrdata *chr, u32 weaponnum)
{
struct invitem *item = botinvGetItem(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 botinvGiveProp(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 = botinvGiveSingleWeapon(chr, weaponnum);
if (result) {
struct invitem *item = botinvGetItem(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 = botactGetWeaponByAmmoType(i + 1);
if (weaponnum > 0) {
botinvGiveSingleWeapon(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 botinvScoreAllWeapons(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;
botinvScoreWeaponByItself(chr, weaponnum, FUNC_PRIMARY, -1, false, &pri1, &pri2);
botinvScoreWeaponByItself(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 mpHasShield(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 mpGetWeaponSlotByWeaponNum(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 botinvScoreWeapon(struct chrdata *chr, s32 weaponnum, s32 funcnum, s32 arg3, bool arg4, s32 *dst1, s32 *dst2, bool comparewithtarget, bool arg8)
{
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 (arg3 < 0
|| (!funcnum && arg3 == g_AibotWeaponPreferences[weaponnum].haspriammogoal)
|| (funcnum && arg3 == g_AibotWeaponPreferences[weaponnum].hassecammogoal)) {
if (arg4) {
score1 = g_AibotWeaponPreferences[weaponnum].unk02;
score2 = g_AibotWeaponPreferences[weaponnum].unk03;
} else {
score1 = g_AibotWeaponPreferences[weaponnum].unk00;
score2 = g_AibotWeaponPreferences[weaponnum].unk01;
}
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
&& botGetTargetsWeaponNum(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 = arg4 ? 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 = arg4 ? 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
&& botactGetAmmoQuantityByWeapon(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[mpPlayerGetIndex(chrGetTargetProp(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[mpPlayerGetIndex(chrGetTargetProp(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 && chrGetTargetProp(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 = chrGetTargetProp(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) {
botGetTargetsWeaponNum(chr);
}
break;
}
if (arg8) {
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 = mpGetWeaponSlotByWeaponNum(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->unk2a8[weaponindex];
score1 += extra;
score2 += extra;
if (score1 < 0) {
score1 = 0;
}
if (score2 < 0) {
score2 = 0;
}
}
}
*dst1 = score1;
*dst2 = score2;
}
void botinvScoreWeaponAgainstTarget(struct chrdata *chr, s32 weaponnum, s32 funcnum, s32 arg3, bool arg4, s32 *dst1, s32 *dst2)
{
botinvScoreWeapon(chr, weaponnum, funcnum, arg3, arg4, dst1, dst2, true, true);
}
void botinvScoreWeaponByItself(struct chrdata *chr, s32 weaponnum, s32 funcnum, s32 arg3, bool arg4, s32 *dst1, s32 *dst2)
{
botinvScoreWeapon(chr, weaponnum, funcnum, arg3, arg4, dst1, dst2, false, true);
}
/**
* Return the aibot's distance configuration index for the given weapon and
* function.
*/
s32 botinvGetDistConfig(s32 weaponnum, s32 funcnum)
{
if (funcnum != FUNC_PRIMARY) {
return g_AibotWeaponPreferences[weaponnum].secdistconfig;
}
return g_AibotWeaponPreferences[weaponnum].pridistconfig;
}
/**
* Check if the bot's personality permits it to use the given weapon and
* function.
*/
bool botinvAllowsWeapon(struct chrdata *chr, s32 weaponnum, s32 funcnum)
{
bool allow = true;
if (chr->aibot->config->type == BOTTYPE_FIST) {
if (funcnum != FUNC_PRIMARY) {
if (g_AibotWeaponPreferences[weaponnum].secdistconfig != BOTDISTCFG_CLOSE) {
allow = false;
}
} else {
if (g_AibotWeaponPreferences[weaponnum].pridistconfig != BOTDISTCFG_CLOSE) {
allow = false;
}
}
}
return allow;
}
/**
* Tick the bot's inventory.
*
* The logic for deciding when to switch weapons is here.
*/
void botinvTick(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 = mpGetWeaponSlotByWeaponNum(aibot->weaponnum);
if (weaponindex >= 0) {
aibot->equipdurations60[weaponindex][aibot->gunfunc] += g_Vars.lvupdate240_60;
}
// 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.lvupdate240_60;
if (aibot->dampensuicidesttl60 < 0) {
aibot->dampensuicidesttl60 = TICKS(3600) + random() % TICKS(60);
for (i = 0; i < 6; i++) {
aibot->suicidesbygunfunc[i][0] *= 0.9f;
aibot->suicidesbygunfunc[i][1] *= 0.9f;
}
}
// Every 10-60 seconds, generate new unk2a8 values
aibot->unk2a4 -= g_Vars.lvupdate240_60;
if (aibot->unk2a4 < 0) {
aibot->unk2a4 = TICKS(600) + random() % TICKS(3000);
for (i = 0; i < 6; i++) {
if (aibot->config->difficulty == BOTDIFF_MEAT) {
aibot->unk2a8[i] = random() % 200 - 100; // -100 to +100
} else if (aibot->config->difficulty == BOTDIFF_EASY) {
aibot->unk2a8[i] = random() % 100 - 50; // -50 to +50
} else {
aibot->unk2a8[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.lvupdate240_60;
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_AibotWeaponPreferences[weaponnum].hassecammogoal;
} else {
canuse = g_AibotWeaponPreferences[weaponnum].haspriammogoal;
}
if (canuse && botinvAllowsWeapon(chr, weaponnum, j)) {
botinvScoreWeaponAgainstTarget(chr, weaponnum, j, 1, item && item->type == INVITEMTYPE_DUAL, &score1, &score2);
if (score1 >= bestscore) {
if (!botactGetAmmoTypeByFunction(weaponnum, j)
|| botactGetAmmoQuantityByWeapon(aibot, weaponnum, j, true) > 0) {
bestscore = score1;
newweaponnum = weaponnum;
newfuncnum = j;
}
}
}
}
}
}
}
// Consider setting knives to secondary function (throw)
if (newweaponnum == WEAPON_COMBATKNIFE
&& botactGetAmmoQuantityByWeapon(aibot, WEAPON_COMBATKNIFE, FUNC_SECONDARY, true) >= 2
&& chr->target != -1
&& chr->aibot->chrdistances[mpPlayerGetIndex(chrGetTargetProp(chr)->chr)] > 200
&& chr->aibot->chrdistances[mpPlayerGetIndex(chrGetTargetProp(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 && botactGetAmmoQuantityByWeapon(aibot, WEAPON_PHOENIX, FUNC_SECONDARY, true) > 0) {
newfuncnum = FUNC_SECONDARY;
} else if (newweaponnum == WEAPON_SUPERDRAGON && botactGetAmmoQuantityByWeapon(aibot, WEAPON_SUPERDRAGON, FUNC_SECONDARY, true) > 0) {
newfuncnum = FUNC_SECONDARY;
}
}
botinvSwitchToWeapon(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 botinvSwitchToWeapon(struct chrdata *chr, s32 weaponnum, s32 funcnum)
{
struct invitem *item;
struct weaponfunc *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 = botinvGetItem(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_REAPABLE;
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) {
botactGiveAmmoByWeapon(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]) {
botactReload(chr, i, false);
}
}
}
if (!changinggun) {
modelnum = playermgrGetModelOfWeapon(weaponnum);
// @dangerous: item is uninitialised if weaponnum is WEAPON_UNARMED.
// This function assumes playermgrGetModelOfWeapon 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) {
chrGiveWeapon(chr, modelnum, weaponnum, OBJFLAG_WEAPON_LEFTHANDED);
botactReload(chr, HAND_LEFT, false);
}
}
func = weaponGetFunctionById(weaponnum, funcnum);
aibot->iscloserangeweapon = func && func->type == INVENTORYFUNCTYPE_CLOSE;
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 botinvDrop(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 (weaponHasFlag(item->type_weap.weapon1, WEAPONFLAG_08000000) == 0
|| (g_Vars.normmplayerisrunning
&& g_MpSetup.scenario == MPSCENARIO_HACKERCENTRAL
&& item->type_weap.weapon1 == WEAPON_DATAUPLINK)) {
s32 modelnum = playermgrGetModelOfWeapon(item->type_weap.weapon1);
if (modelnum > 0) {
struct prop *prop = weaponCreateForChr(chr, modelnum, item->type_weap.weapon1, OBJFLAG_WEAPON_AICANNOTUSE, NULL, NULL);
if (prop) {
objSetDropped(prop, DROPTYPE_DEFAULT);
objDrop(prop, true);
if (item->type_weap.weapon1 == WEAPON_BRIEFCASE2) {
scenarioHandleDroppedToken(chr, prop);
}
}
}
}
}
}
if ((dropall && weaponnum >= WEAPON_FALCON2)
|| (!dropall && weaponnum == chr->aibot->weaponnum)) {
botinvSwitchToWeapon(chr, WEAPON_UNARMED, FUNC_PRIMARY);
}
chr->hidden |= CHRHFLAG_00000001;
if (!dropall) {
botinvRemoveItem(chr, weaponnum);
}
}
void botinvDropAll(struct chrdata *chr, u32 weaponnum)
{
botinvDrop(chr, weaponnum, true);
}
void botinvDropOne(struct chrdata *chr, u32 weaponnum)
{
botinvDrop(chr, weaponnum, false);
}