#include #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); }