perfect_dark/src/game/botact.c

543 lines
13 KiB
C

#include <ultra64.h>
#include "constants.h"
#include "game/chraction.h"
#include "game/debug.h"
#include "game/chr.h"
#include "game/propsnd.h"
#include "game/bondgun.h"
#include "game/gset.h"
#include "game/playermgr.h"
#include "game/botact.h"
#include "game/pad.h"
#include "game/padhalllv.h"
#include "game/propobj.h"
#include "bss.h"
#include "lib/rng.h"
#include "lib/mtx.h"
#include "lib/anim.h"
#include "lib/collision.h"
#include "data.h"
#include "types.h"
s32 botact_get_ammo_type_by_function(s32 weaponnum, s32 funcnum)
{
if (weaponnum >= WEAPON_FALCON2 && weaponnum <= WEAPON_SUICIDEPILL) {
struct ammodef *ammo = gset_get_ammodef(weaponnum, funcnum);
if (ammo) {
return ammo->type;
}
}
return 0;
}
s32 botact_get_clip_capacity_by_function(s32 weaponnum, u32 funcnum)
{
if (weaponnum >= WEAPON_FALCON2 && weaponnum <= WEAPON_SUICIDEPILL) {
struct ammodef *ammo = gset_get_ammodef(weaponnum, funcnum);
if (ammo) {
return ammo->clipsize;
}
}
return 0;
}
void botact_reload(struct chrdata *chr, s32 handnum, bool withsound)
{
struct aibot *aibot = chr->aibot;
aibot->timeuntilreload60[handnum] = 0;
aibot->maulercharge[handnum] = 0;
if (chr->weapons_held[handnum] && !botact_is_weapon_throwable(aibot->weaponnum, aibot->gunfunc)) {
s32 capacity = botact_get_clip_capacity_by_function(aibot->weaponnum, aibot->gunfunc);
if (capacity > 0) {
s32 tryamount = capacity - aibot->loadedammo[handnum];
s32 actualamount = botact_try_remove_ammo_from_reserve(aibot, aibot->weaponnum, aibot->gunfunc, tryamount);
if (actualamount > 0) {
aibot->loadedammo[handnum] += actualamount;
if (withsound) {
if (aibot->weaponnum == WEAPON_FARSIGHT) {
ps_create(NULL, chr->prop, SFX_RELOAD_FARSIGHT, -1,
-1, PSFLAG_0400, 0, PSTYPE_NONE, 0, -1, 0, -1, -1, -1, -1);
} else {
ps_create(NULL, chr->prop, SFX_RELOAD_DEFAULT, -1,
-1, PSFLAG_0400, 0, PSTYPE_NONE, 0, -1, 0, -1, -1, -1, -1);
}
}
}
}
}
}
s32 botact_get_ammo_quantity_by_weapon(struct aibot *aibot, s32 weaponnum, s32 funcnum, bool include_equipped)
{
s32 qty = 0;
s32 ammotype;
s32 equippedammotype;
if (aibot) {
if (aibot->flags & BOTFLAG_UNLIMITEDAMMO) {
ammotype = botact_get_ammo_type_by_function(weaponnum, funcnum);
qty = bgun_get_capacity_by_ammotype(ammotype);
} else {
ammotype = botact_get_ammo_type_by_function(weaponnum, funcnum);
qty = aibot->ammoheld[ammotype];
}
if (include_equipped) {
ammotype = botact_get_ammo_type_by_function(weaponnum, funcnum);
equippedammotype = botact_get_ammo_type_by_function(aibot->weaponnum, aibot->gunfunc);
if (equippedammotype == ammotype) {
qty += aibot->loadedammo[HAND_LEFT] + aibot->loadedammo[HAND_RIGHT];
}
}
}
return qty;
}
s32 botact_get_ammo_quantity_by_type(struct aibot *aibot, s32 ammotype, bool include_equipped)
{
s32 qty = 0;
if (aibot) {
if (aibot->flags & BOTFLAG_UNLIMITEDAMMO) {
qty = bgun_get_capacity_by_ammotype(ammotype);
} else {
qty = aibot->ammoheld[ammotype];
}
if (include_equipped
&& botact_get_ammo_type_by_function(aibot->weaponnum, aibot->gunfunc) == ammotype) {
qty += aibot->loadedammo[HAND_LEFT] + aibot->loadedammo[HAND_RIGHT];
}
}
return qty;
}
/**
* Attempt to remove the given quantity of ammo from the aibot's reserve and
* return the amount actually removed.
*
* The amount removed will be less than the attempted amount if the aibot
* doesn't have enough ammo in reserve.
*/
s32 botact_try_remove_ammo_from_reserve(struct aibot *aibot, s32 weaponnum, s32 funcnum, s32 tryqty)
{
s32 amountremoved;
s32 *ammoheld = &aibot->ammoheld[botact_get_ammo_type_by_function(weaponnum, funcnum)];
if (!aibot || *ammoheld <= 0 || tryqty <= 0) {
return 0;
}
if (aibot->flags & BOTFLAG_UNLIMITEDAMMO) {
return tryqty;
}
dprint();
*ammoheld -= tryqty;
if (*ammoheld < 0) {
amountremoved = tryqty + *ammoheld;
*ammoheld = 0;
if (dprint()) {
return amountremoved;
}
} else {
amountremoved = tryqty;
dprint();
}
return amountremoved;
}
void botact_give_ammo_by_weapon(struct aibot *aibot, s32 weaponnum, s32 funcnum, s32 qty)
{
s32 max;
s32 *heldquantity = &aibot->ammoheld[botact_get_ammo_type_by_function(weaponnum, funcnum)];
if (aibot && (aibot->flags & BOTFLAG_UNLIMITEDAMMO) == 0 && qty > 0) {
dprint();
*heldquantity += qty;
if (heldquantity);
max = bgun_get_capacity_by_ammotype(botact_get_ammo_type_by_function(weaponnum, funcnum));
if (*heldquantity > max) {
*heldquantity = max;
}
dprint();
}
}
void botact_give_ammo_by_type(struct aibot *aibot, u32 ammotype, s32 quantity)
{
s32 max;
s32 *heldquantity = &aibot->ammoheld[ammotype];
if (!aibot || (aibot->flags & BOTFLAG_UNLIMITEDAMMO) || quantity <= 0) {
return;
}
dprint();
*heldquantity += quantity;
if (heldquantity);
max = bgun_get_capacity_by_ammotype(ammotype);
if (*heldquantity > max) {
*heldquantity = max;
}
dprint();
}
bool botact_shoot_farsight(struct chrdata *chr, s32 arg1, struct coord *vector, struct coord *arg3)
{
struct aibot *aibot;
struct chrdata *oppchr;
struct prop *oppprop;
s32 i;
s32 rand;
f32 speed;
if (!chr || !chr->aibot) {
return false;
}
aibot = chr->aibot;
if (aibot->weaponnum == WEAPON_FARSIGHT) {
rand = random() % 100;
// 3 in 10 chance of this passing
if (rand < 30) {
struct modelnode *node = NULL;
struct model *model = NULL;
s32 side = -1;
s32 hitpart = HITPART_GENERAL;
struct gset gset = {WEAPON_FARSIGHT, 0, 0, FUNC_PRIMARY};
f32 damage = gset_get_damage(&gset);
s32 fallback = 30;
s32 value = fallback;
for (i = 0; i < g_MpNumChrs; i++) {
oppchr = g_MpAllChrPtrs[i];
oppprop = g_MpAllChrPtrs[i]->prop;
if (oppprop->type == PROPTYPE_PLAYER) {
struct player *player = g_Vars.players[playermgr_get_player_num_by_prop(oppprop)];
speed = player->speedforwards * player->speedforwards
+ player->speedsideways * player->speedsideways;
if (speed > 0) {
value = fallback * 0.05f;
}
} else {
if (oppchr->actiontype != ACT_STAND) {
value = fallback * 0.05f;
}
}
// value is 30 if player was still, or 1.5 if moving. So if 30
// then this will always pass, or if 1.5 then this has 1 in 15
// chance of passing.
if (oppchr != chr
&& value > rand
&& pos_is_facing_pos(arg3, vector, &oppprop->pos, chr_get_hit_radius(oppchr))) {
bgun_play_prop_hit_sound(&gset, oppprop, -1);
if (oppchr->model && chr_get_shield(oppchr) > 0) {
chr_calculate_shield_hit(oppchr, &oppprop->pos, vector, &node, &hitpart, &model, &side);
}
chr_emit_sparks(oppchr, oppprop, hitpart, &oppprop->pos, vector, chr);
chr_damage_by_impact(oppchr, damage, vector, &gset, chr->prop, HITPART_GENERAL, oppprop, node, model, side, NULL);
}
}
return true;
}
return false;
}
return true;
}
s32 botact_get_weapon_model(s32 weapon)
{
return playermgr_get_model_of_weapon(weapon);
}
bool botact_is_weapon_throwable(s32 weaponnum, bool is_secondary)
{
switch (weaponnum) {
case WEAPON_LAPTOPGUN:
case WEAPON_DRAGON:
case WEAPON_COMBATKNIFE:
return is_secondary;
case WEAPON_GRENADE:
case WEAPON_NBOMB:
case WEAPON_TIMEDMINE:
case WEAPON_PROXIMITYMINE:
case WEAPON_REMOTEMINE:
return true;
}
return false;
}
u32 botact_get_projectile_throw_interval(u32 weapon)
{
switch (weapon) {
case WEAPON_COMBATKNIFE:
return TICKS(120);
case WEAPON_GRENADE:
case WEAPON_NBOMB:
return TICKS(90);
case WEAPON_CROSSBOW:
case WEAPON_TRANQUILIZER:
case WEAPON_LASER:
case WEAPON_TIMEDMINE:
case WEAPON_PROXIMITYMINE:
case WEAPON_REMOTEMINE:
default:
return TICKS(60);
}
}
s32 botact_get_weapon_by_ammo_type(s32 ammotype)
{
switch (ammotype) {
case AMMOTYPE_NBOMB: return WEAPON_NBOMB;
case AMMOTYPE_GRENADE: return WEAPON_GRENADE;
case AMMOTYPE_KNIFE: return WEAPON_COMBATKNIFE;
case AMMOTYPE_REMOTE_MINE: return WEAPON_REMOTEMINE;
case AMMOTYPE_PROXY_MINE: return WEAPON_PROXIMITYMINE;
case AMMOTYPE_TIMED_MINE: return WEAPON_TIMEDMINE;
}
return 0;
}
void botact_throw(struct chrdata *chr)
{
struct coord sp228 = {0, 0, 0};
Mtxf sp164;
struct coord sp152;
struct prop *prop = chr->prop;
Mtxf sp84;
f32 sp80 = chr_get_aimx_angle(chr);
u32 stack;
struct gset gset = {0};
struct prop *target = chr_get_target_prop(chr);
struct coord sp56;
f32 mult;
gset.weaponnum = chr->aibot->weaponnum;
gset.weaponfunc = chr->aibot->gunfunc;
if (chr_is_target_in_fov(chr, 30, 0)) {
sp56.x = target->pos.x;
sp56.z = target->pos.z;
if (chr->aibot->weaponnum == WEAPON_GRENADE || chr->aibot->weaponnum == WEAPON_NBOMB) {
sp56.y = target->chr->manground;
} else {
sp56.y = target->pos.y;
if (target->type == PROPTYPE_PLAYER) {
sp56.y -= 25.0f;
}
}
chr_calculate_trajectory(&prop->pos, 16.666666f, &sp56, &sp152);
} else {
sp152.x = cosf(BADDTOR(20)) * sinf(sp80);
sp152.y = sinf(BADDTOR(20));
sp152.z = cosf(BADDTOR(20)) * cosf(sp80);
}
mult = 16.666666f;
sp228.x = sp152.x * mult;
sp228.y = sp152.y * mult;
sp228.z = sp152.z * mult;
mtx4_load_identity(&sp164);
if (chr->aibot->weaponnum == WEAPON_COMBATKNIFE) {
mtx4_load_z_rotation(BADDTOR(180) * 1.5f, &sp164);
mtx4_load_x_rotation(BADDTOR(180), &sp84);
mtx4_mult_mtx4_in_place(&sp84, &sp164);
}
mtx4_load_x_rotation(BADDTOR(20), &sp84);
mtx00015be0(&sp84, &sp164);
mtx4_load_y_rotation(sp80, &sp84);
mtx00015be0(&sp84, &sp164);
bgun_create_thrown_projectile2(chr, &gset, &prop->pos, prop->rooms, &sp164, &sp228);
if (gset.weaponnum == WEAPON_REMOTEMINE) {
chr->aibot->flags |= BOTFLAG_THREWREMOTEMINE;
}
}
/**
* Get the shoot interval of the given weapon, in time60.
*/
s32 botact_get_shoot_interval60(s32 weaponnum, s32 funcnum)
{
s32 stack[2];
s32 result = 1;
struct weapondef *weapon = gset_get_weapondef(weaponnum);
if (weapon) {
struct funcdef *func = weapon->functions[funcnum];
if (func) {
if (func->type == INVENTORYFUNCTYPE_SHOOT_SINGLE) {
struct funcdef_shoot *func2 = (struct funcdef_shoot *)func;
result = func2->unk24 + func2->unk25;
} else if (func->type == INVENTORYFUNCTYPE_SHOOT_AUTOMATIC) {
struct funcdef_shoot *func2 = (struct funcdef_shoot *)func;
result = func2->unk24 + func2->unk25;
} else if (func->type == INVENTORYFUNCTYPE_SHOOT_PROJECTILE) {
struct funcdef_shoot *func2 = (struct funcdef_shoot *)func;
result = func2->unk24 + func2->unk25;
} else if (func->type == INVENTORYFUNCTYPE_MELEE && weaponnum != WEAPON_REAPER) {
result = 60;
}
}
}
return result;
}
/**
* Do pathfinding for a bot's Slayer rocket in fly-by-wire mode and populate
* the projectil's waypads.
*
* Return true if a route was found, false if not.
*/
bool botact_find_rocket_route(struct chrdata *chr, struct coord *frompos, struct coord *topos, RoomNum *fromrooms, RoomNum *torooms, struct projectile *projectile)
{
struct waypoint *from = waypoint_find_closest_to_pos(frompos, fromrooms);
struct waypoint *to = waypoint_find_closest_to_pos(topos, torooms);
struct waypoint *waypoints[MAX_CHRWAYPOINTS];
s32 numwaypoints;
if (from && to) {
nav_set_seed(CHRNAVSEED(chr), CHRNAVSEED(chr));
numwaypoints = nav_find_route(from, to, waypoints, 6);
nav_set_seed(0, 0);
if (numwaypoints > 1) {
s32 i = 0;
while (waypoints[i]) {
projectile->waypads[i] = waypoints[i]->padnum;
i++;
}
projectile->step = 0;
projectile->numwaypads = i;
return true;
}
}
return false;
}
/**
* Populate pos with the position of the given pad
* for a Slayer rocket in fly-by-wire mode.
*
* It's the ground position of the pad plus 1.5 metres.
*/
void botact_get_rocket_next_step_pos(u16 padnum, struct coord *pos)
{
struct pad pad;
RoomNum rooms[2];
pad_unpack(padnum, PADFIELD_ROOM | PADFIELD_POS, &pad);
rooms[0] = pad.room;
rooms[1] = -1;
pos->x = pad.pos.x;
pos->y = cd_find_floor_y_colour_type_at_pos(&pad.pos, rooms, 0, 0) + 150;
pos->z = pad.pos.z;
}
/**
* Create a Slayer rocket in fly-by-wire mode (ie. remote controlled).
*/
void botact_create_slayer_rocket(struct chrdata *chr)
{
struct weaponobj *rocket = weapon_create_projectile_from_weapon_num(MODEL_CHRSKROCKETMIS, WEAPON_SKROCKET, chr);
if (rocket) {
Mtxf sp260;
Mtxf sp196;
Mtxf sp132;
struct coord sp120 = {0, 0, 0};
f32 yrot;
f32 xrot;
struct coord sp100;
yrot = chr_get_aimx_angle(chr);
xrot = chr_get_aimy_angle(chr);
sp100.x = cosf(xrot) * sinf(yrot);
sp100.y = sinf(xrot);
sp100.z = cosf(xrot) * cosf(yrot);
mtx4_load_x_rotation(xrot, &sp196);
mtx4_load_y_rotation(yrot, &sp132);
mtx00015be0(&sp132, &sp196);
mtx4_load_identity(&sp260);
bgun0f09ebcc(&rocket->base, &chr->prop->pos, chr->prop->rooms, &sp196, &sp100, &sp260, chr->prop, &chr->prop->pos);
if (rocket->base.hidden & OBJHFLAG_PROJECTILE) {
struct prop *target = chr_get_target_prop(chr);
rocket->timer240 = -1;
rocket->base.projectile->unk010 = 7.5;
rocket->base.projectile->unk014 = xrot;
rocket->base.projectile->unk018 = yrot;
rocket->base.projectile->smoketimer240 = 0;
rocket->base.projectile->pickuptimer240 = 0x20000000;
// Fire rocket sound
ps_create(NULL, rocket->base.prop, SFX_LAUNCH_ROCKET_8053, -1,
-1, 0, 0, PSTYPE_NONE, 0, -1, 0, -1, -1, -1, -1);
if (!botact_find_rocket_route(chr, &chr->prop->pos, &target->pos, chr->prop->rooms, target->rooms, rocket->base.projectile)) {
rocket->timer240 = 0; // blow up rocket
} else {
botact_get_rocket_next_step_pos(rocket->base.projectile->waypads[0], &rocket->base.projectile->nextsteppos);
chr->aibot->skrocket = rocket->base.prop;
}
}
}
}