mirror of
https://gitlab.com/ryandwyer/perfect-dark
synced 2026-06-25 09:02:06 -04:00
3004 lines
76 KiB
C
3004 lines
76 KiB
C
#include <ultra64.h>
|
|
#include "constants.h"
|
|
#include "game/bondmove.h"
|
|
#include "game/bondwalk.h"
|
|
#include "game/chraction.h"
|
|
#include "game/dlights.h"
|
|
#include "game/chr.h"
|
|
#include "game/prop.h"
|
|
#include "game/propsnd.h"
|
|
#include "game/floor.h"
|
|
#include "game/ceil.h"
|
|
#include "game/bondgun.h"
|
|
#include "game/game_0b0fd0.h"
|
|
#include "game/tex.h"
|
|
#include "game/camera.h"
|
|
#include "game/player.h"
|
|
#include "game/playermgr.h"
|
|
#include "game/explosions.h"
|
|
#include "game/smoke.h"
|
|
#include "game/sparks.h"
|
|
#include "game/bg.h"
|
|
#include "game/bot.h"
|
|
#include "game/pad.h"
|
|
#include "game/propobj.h"
|
|
#include "game/splat.h"
|
|
#include "game/wallhit.h"
|
|
#include "game/mpstats.h"
|
|
#include "bss.h"
|
|
#include "lib/collision.h"
|
|
#include "lib/lib_17ce0.h"
|
|
#include "lib/model.h"
|
|
#include "lib/snd.h"
|
|
#include "lib/rng.h"
|
|
#include "lib/mtx.h"
|
|
#include "lib/anim.h"
|
|
#include "lib/lib_317f0.h"
|
|
#include "data.h"
|
|
#include "types.h"
|
|
|
|
s16 *g_RoomPropListChunkIndexes;
|
|
struct roomproplistchunk *g_RoomPropListChunks;
|
|
struct prop *g_InteractProp;
|
|
s32 var8009cdac;
|
|
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
s32 var8009cdb0;
|
|
u32 var8009cdb4;
|
|
u32 var8009cdb8;
|
|
u32 var8009cdbc;
|
|
#endif
|
|
|
|
f32 g_AutoAimScale = 1;
|
|
|
|
/**
|
|
* Populate g_Vars.onscreenprops. This is an array of prop pointers, filtered by
|
|
* props that are on screen and sorted by distance descending (furthest first).
|
|
*/
|
|
void propsSort(void)
|
|
{
|
|
s32 count = 0;
|
|
struct prop *prop = g_Vars.activeprops;
|
|
s32 swapindex;
|
|
f32 depth;
|
|
s32 i;
|
|
s32 j;
|
|
f32 depths[201];
|
|
|
|
// Populate onscreenprops with the list of props
|
|
while (prop != g_Vars.pausedprops) {
|
|
if ((prop->flags & (PROPFLAG_ONTHISSCREENTHISTICK | PROPFLAG_ENABLED)) == (PROPFLAG_ONTHISSCREENTHISTICK | PROPFLAG_ENABLED)) {
|
|
depths[count] = prop->z;
|
|
g_Vars.onscreenprops[count] = prop;
|
|
count++;
|
|
}
|
|
|
|
prop = prop->next;
|
|
}
|
|
|
|
g_Vars.numonscreenprops = count;
|
|
g_Vars.onscreenprops[count] = NULL;
|
|
g_Vars.endonscreenprops = &g_Vars.onscreenprops[count];
|
|
|
|
// Sort the onscreenprops list
|
|
for (i = 0; i < count; i++) {
|
|
swapindex = -1;
|
|
depth = -4294967296;
|
|
|
|
for (j = i; j < count; j++) {
|
|
if (depths[j] > depth) {
|
|
depth = depths[j];
|
|
swapindex = j;
|
|
}
|
|
}
|
|
|
|
if (swapindex >= 0) {
|
|
prop = g_Vars.onscreenprops[i];
|
|
depth = depths[i];
|
|
|
|
g_Vars.onscreenprops[i] = g_Vars.onscreenprops[swapindex];
|
|
depths[i] = depths[swapindex];
|
|
|
|
g_Vars.onscreenprops[swapindex] = prop;
|
|
depths[swapindex] = depth;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enable a prop. This is the opposite of disabling (see propDisable below).
|
|
*/
|
|
void propEnable(struct prop *prop)
|
|
{
|
|
prop->flags |= PROPFLAG_ENABLED;
|
|
}
|
|
|
|
/**
|
|
* Disable the given prop. Disabled props do not tick, do not render and do not
|
|
* have any collision checks. This is commonly used for chrs who "spawn" later
|
|
* in the level.
|
|
*
|
|
* Due to a probable bug, these props can be damaged by explosives which causes
|
|
* them to become enabled.
|
|
*/
|
|
void propDisable(struct prop *prop)
|
|
{
|
|
struct prop **ptr;
|
|
|
|
prop->flags &= ~PROPFLAG_ENABLED;
|
|
|
|
for (ptr = g_Vars.onscreenprops; ptr < g_Vars.endonscreenprops; ptr++) {
|
|
if (*ptr == prop) {
|
|
*ptr = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Allocate a prop. The prop is taken from the head of the freeprops list and
|
|
* initialised.
|
|
*/
|
|
struct prop *propAllocate(void)
|
|
{
|
|
struct prop *prop = g_Vars.freeprops;
|
|
|
|
if (g_Vars.freeprops) {
|
|
g_Vars.freeprops = g_Vars.freeprops->next;
|
|
|
|
prop->next = NULL;
|
|
prop->prev = NULL;
|
|
prop->parent = NULL;
|
|
prop->child = NULL;
|
|
prop->flags = 0;
|
|
prop->timetoregen = 0;
|
|
prop->rooms[0] = -1;
|
|
prop->chr = NULL;
|
|
prop->propstateindex = g_Vars.allocstateindex;
|
|
prop->backgroundedframes = 0;
|
|
prop->forceonetick = true;
|
|
prop->forcetick = false;
|
|
prop->active = false;
|
|
prop->backgrounded = false;
|
|
prop->lastupdateframe = 0xffff;
|
|
prop->propupdate240 = 0;
|
|
prop->propupdate60err = 2;
|
|
prop->opawallhits = NULL;
|
|
prop->xluwallhits = NULL;
|
|
g_Vars.propstates[prop->propstateindex].propcount++;
|
|
|
|
g_Vars.allocstateindex++;
|
|
|
|
if (g_Vars.allocstateindex >= g_Vars.numpropstates) {
|
|
g_Vars.allocstateindex = 0;
|
|
}
|
|
|
|
return prop;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Free the given prop. The prop must not be in any list prior to calling this
|
|
* function.
|
|
*
|
|
* The prop is inserted to the head of the freeprops list.
|
|
*/
|
|
void propFree(struct prop *prop)
|
|
{
|
|
if (prop->type == PROPTYPE_CHR) {
|
|
g_Vars.propstates[prop->propstateindex].chrpropcount--;
|
|
} else {
|
|
g_Vars.propstates[prop->propstateindex].propcount--;
|
|
}
|
|
|
|
prop->next = g_Vars.freeprops;
|
|
prop->prev = NULL;
|
|
prop->chr = NULL;
|
|
prop->rooms[0] = -1;
|
|
g_Vars.freeprops = prop;
|
|
}
|
|
|
|
/**
|
|
* Insert the prop to the head of activeprops. The prop must not be in any list
|
|
* prior to calling this function.
|
|
*
|
|
* If this function is being called from propsTickPlayer (which iterates active props)
|
|
* then the prop will do its next tick on the next frame, due to it being
|
|
* inserted at the head.
|
|
*/
|
|
void propActivate(struct prop *prop)
|
|
{
|
|
if (g_Vars.activeprops && g_Vars.activeprops != g_Vars.pausedprops) {
|
|
if (prop != g_Vars.activeprops && !prop->prev) {
|
|
g_Vars.activeprops->prev = prop;
|
|
prop->next = g_Vars.activeprops;
|
|
prop->prev = NULL;
|
|
g_Vars.activeprops = prop;
|
|
}
|
|
} else {
|
|
prop->next = g_Vars.pausedprops;
|
|
|
|
if (prop->next) {
|
|
prop->next->prev = prop;
|
|
}
|
|
|
|
prop->prev = NULL;
|
|
g_Vars.activeprops = prop;
|
|
g_Vars.activepropstail = g_Vars.activeprops;
|
|
}
|
|
|
|
prop->backgroundedframes = 0;
|
|
prop->active = true;
|
|
}
|
|
|
|
/**
|
|
* Similar to propActivate, but the prop inserted to the tail of the activeprops
|
|
* list. This makes the prop tick on the current frame if called from propsTickPlayer,
|
|
* because propsTickPlayer iterates the activeprops list from head to tail.
|
|
*/
|
|
void propActivateThisFrame(struct prop *prop)
|
|
{
|
|
if (g_Vars.activepropstail && g_Vars.activepropstail != g_Vars.pausedprops) {
|
|
if (prop != g_Vars.activepropstail && !prop->next) {
|
|
prop->prev = g_Vars.activepropstail;
|
|
prop->next = g_Vars.activepropstail->next;
|
|
|
|
if (prop->next) {
|
|
prop->next->prev = prop;
|
|
}
|
|
|
|
g_Vars.activepropstail->next = prop;
|
|
g_Vars.activepropstail = prop;
|
|
}
|
|
} else {
|
|
prop->next = g_Vars.pausedprops;
|
|
|
|
if (prop->next) {
|
|
prop->next->prev = prop;
|
|
}
|
|
|
|
prop->prev = NULL;
|
|
g_Vars.activeprops = prop;
|
|
g_Vars.activepropstail = g_Vars.activeprops;
|
|
}
|
|
|
|
prop->backgroundedframes = 0;
|
|
prop->active = true;
|
|
}
|
|
|
|
/**
|
|
* Remove the prop from its current list (activeprops or pausedprops).
|
|
*/
|
|
void propDelist(struct prop *prop)
|
|
{
|
|
if (prop->active) {
|
|
if (prop == g_Vars.activeprops) {
|
|
g_Vars.activeprops = prop->next;
|
|
}
|
|
|
|
if (prop == g_Vars.activepropstail) {
|
|
if (g_Vars.activeprops == g_Vars.pausedprops) {
|
|
g_Vars.activepropstail = g_Vars.pausedprops;
|
|
} else {
|
|
g_Vars.activepropstail = prop->prev;
|
|
}
|
|
}
|
|
} else {
|
|
if (prop == g_Vars.pausedprops) {
|
|
if (g_Vars.activeprops == g_Vars.pausedprops) {
|
|
g_Vars.activepropstail = prop->next;
|
|
g_Vars.activeprops = g_Vars.activepropstail;
|
|
}
|
|
|
|
g_Vars.pausedprops = prop->next;
|
|
}
|
|
}
|
|
|
|
if (prop->next) {
|
|
prop->next->prev = prop->prev;
|
|
}
|
|
|
|
if (prop->prev) {
|
|
prop->prev->next = prop->next;
|
|
}
|
|
|
|
prop->next = NULL;
|
|
prop->prev = NULL;
|
|
prop->active = false;
|
|
prop->backgroundedframes = 0;
|
|
}
|
|
|
|
void propReparent(struct prop *mover, struct prop *adopter)
|
|
{
|
|
mover->parent = adopter;
|
|
|
|
if (adopter->child) {
|
|
adopter->child->prev = mover;
|
|
}
|
|
|
|
mover->next = adopter->child;
|
|
mover->prev = NULL;
|
|
adopter->child = mover;
|
|
}
|
|
|
|
void propDetach(struct prop *prop)
|
|
{
|
|
if (prop->parent) {
|
|
if (prop == prop->parent->child) {
|
|
prop->parent->child = prop->next;
|
|
}
|
|
|
|
if (prop->next) {
|
|
prop->next->prev = prop->prev;
|
|
}
|
|
|
|
if (prop->prev) {
|
|
prop->prev->next = prop->next;
|
|
}
|
|
|
|
prop->parent = NULL;
|
|
prop->next = NULL;
|
|
prop->prev = NULL;
|
|
}
|
|
}
|
|
|
|
Gfx *propRender(Gfx *gdl, struct prop *prop, bool xlupass)
|
|
{
|
|
switch (prop->type) {
|
|
case 0:
|
|
break;
|
|
case PROPTYPE_OBJ:
|
|
case PROPTYPE_DOOR:
|
|
case PROPTYPE_WEAPON:
|
|
gdl = objRender(prop, gdl, xlupass);
|
|
break;
|
|
case PROPTYPE_CHR:
|
|
gdl = chrRender(prop, gdl, xlupass);
|
|
break;
|
|
case PROPTYPE_PLAYER:
|
|
gdl = playerRender(prop, gdl, xlupass);
|
|
break;
|
|
case PROPTYPE_EXPLOSION:
|
|
gdl = explosionRender(prop, gdl, xlupass);
|
|
break;
|
|
case PROPTYPE_SMOKE:
|
|
gdl = smokeRender(prop, gdl, xlupass);
|
|
break;
|
|
}
|
|
|
|
return gdl;
|
|
}
|
|
|
|
/**
|
|
* Render one pass of all props in the given room number.
|
|
*
|
|
* The render order is:
|
|
* - Opaque components of props (pre-BG)
|
|
* - Opaque components of BG
|
|
* - Opaque components of props (post-BG)
|
|
* - Wall hits
|
|
* - Translucent components of BG
|
|
* - Translucent components of props
|
|
*
|
|
* Most props are rendered in the pre-bg pass for performance reasons, as there
|
|
* is less BG to draw if it's being obscured by props.
|
|
*
|
|
* Props can be rendered in multiple passes, such as terminals which render the
|
|
* terminal in the pre-bg pass and the screen in the post-bg pass, likely to
|
|
* avoid Z-fighting issues.
|
|
*/
|
|
Gfx *propsRender(Gfx *gdl, s16 renderroomnum, s32 renderpass, s16 *roomnumsbyprop)
|
|
{
|
|
struct prop **ptr;
|
|
struct prop *prop;
|
|
s16 *proprooms;
|
|
|
|
if (renderpass == RENDERPASS_OPA_PREBG || renderpass == RENDERPASS_OPA_POSTBG) {
|
|
// Iterate onscreen props near to far
|
|
ptr = g_Vars.endonscreenprops - 1;
|
|
|
|
proprooms = roomnumsbyprop + (g_Vars.endonscreenprops - g_Vars.onscreenprops);
|
|
proprooms--;
|
|
|
|
while (ptr >= g_Vars.onscreenprops) {
|
|
if (renderroomnum == *proprooms) {
|
|
prop = *ptr;
|
|
|
|
if (prop) {
|
|
if ((renderpass == RENDERPASS_OPA_PREBG && (prop->flags & (PROPFLAG_DRAWONTOP | PROPFLAG_RENDERPOSTBG)) == 0)
|
|
|| (renderpass == RENDERPASS_OPA_POSTBG && (prop->flags & (PROPFLAG_DRAWONTOP | PROPFLAG_RENDERPOSTBG)) == PROPFLAG_RENDERPOSTBG)) {
|
|
gdl = propRender(gdl, prop, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
ptr--;
|
|
proprooms--;
|
|
}
|
|
} else {
|
|
// Iterate onscreen props far to near
|
|
ptr = g_Vars.onscreenprops;
|
|
proprooms = roomnumsbyprop;
|
|
|
|
while (ptr < g_Vars.endonscreenprops) {
|
|
if (renderroomnum == *proprooms) {
|
|
prop = *ptr;
|
|
|
|
if (prop) {
|
|
if (prop->flags & PROPFLAG_DRAWONTOP) {
|
|
gdl = propRender(gdl, prop, false);
|
|
}
|
|
|
|
gdl = propRender(gdl, prop, true);
|
|
}
|
|
}
|
|
|
|
ptr++;
|
|
proprooms++;
|
|
}
|
|
}
|
|
|
|
gdl = currentPlayerScissorToViewport(gdl);
|
|
|
|
return gdl;
|
|
}
|
|
|
|
void weaponPlayWhooshSound(s32 weaponnum, struct prop *prop)
|
|
{
|
|
s32 soundnum = -1;
|
|
f32 speed = 1;
|
|
|
|
if (weaponnum == WEAPON_TRANQUILIZER) {
|
|
soundnum = SFX_RELOAD_04FB;
|
|
speed = 2.78f;
|
|
} else if (weaponnum == WEAPON_REAPER) {
|
|
// empty
|
|
} else if (weaponnum == WEAPON_COMBATKNIFE) {
|
|
soundnum = random() % 2 == 1 ? SFX_8060 : SFX_8061;
|
|
speed = 1.05f - RANDOMFRAC() * 0.2f;
|
|
} else {
|
|
soundnum = SFX_0069;
|
|
speed = 1.0f - RANDOMFRAC() * 0.2f;
|
|
}
|
|
|
|
if (soundnum != -1) {
|
|
if (prop == g_Vars.currentplayer->prop) {
|
|
struct sndstate *handle;
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
u32 stack;
|
|
OSPri prevpri = osGetThreadPri(0);
|
|
osSetThreadPri(0, osGetThreadPri(&g_AudioManager.thread) + 1);
|
|
|
|
handle = sndStart(var80095200, soundnum, NULL, -1, -1, -1, -1, -1);
|
|
|
|
if (handle) {
|
|
audioPostEvent(handle, 0x10, *(s32 *)&speed);
|
|
}
|
|
|
|
osSetThreadPri(0, prevpri);
|
|
#else
|
|
handle = sndStart(var80095200, soundnum, NULL, -1, -1, -1, -1, -1);
|
|
|
|
if (handle) {
|
|
audioPostEvent(handle, 0x10, *(s32 *)&speed);
|
|
}
|
|
#endif
|
|
} else {
|
|
propsnd0f0939f8(NULL, prop, soundnum, -1,
|
|
-1, 0, 0, 0, NULL, speed, NULL, -1, -1, -1, -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is similar to the above but the sound numbers seem wrong...
|
|
* Perhaps the function was from GE and not updated for PD.
|
|
*/
|
|
void func0f060bac(s32 weaponnum, struct prop *prop)
|
|
{
|
|
s32 soundnum = -1;
|
|
f32 speed = 1;
|
|
struct sndstate *handle;
|
|
|
|
if (weaponnum == WEAPON_UNARMED) {
|
|
soundnum = SFX_THUD_808F;
|
|
|
|
if ((random() % 2) == 1) {
|
|
soundnum = SFX_THUD_8094;
|
|
}
|
|
|
|
speed = 1.0f - RANDOMFRAC() * 0.1f;
|
|
} else if (weaponnum == WEAPON_TRANQUILIZER) {
|
|
soundnum = SFX_RELOAD_04FB;
|
|
speed = 2.78f;
|
|
} else {
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
soundnum = SFX_HIT_METAL_8079;
|
|
speed = 1.0f - RANDOMFRAC() * 0.1f;
|
|
#else
|
|
soundnum = SFX_HIT_METAL_8079;
|
|
|
|
if (weaponnum != WEAPON_COMBATKNIFE && (random() % 2) == 1) {
|
|
soundnum = SFX_HATHIT_807C;
|
|
}
|
|
|
|
speed = 1.0f - RANDOMFRAC() * 0.1f;
|
|
#endif
|
|
}
|
|
|
|
if (soundnum != -1) {
|
|
if (prop == g_Vars.currentplayer->prop) {
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
OSPri prevpri = osGetThreadPri(0);
|
|
osSetThreadPri(0, osGetThreadPri(&g_AudioManager.thread) + 1);
|
|
|
|
handle = sndStart(var80095200, soundnum, 0, -1, -1, -1, -1, -1);
|
|
|
|
if (handle) {
|
|
audioPostEvent(handle, 0x10, *(s32 *)&speed);
|
|
}
|
|
|
|
osSetThreadPri(0, prevpri);
|
|
#else
|
|
handle = sndStart(var80095200, soundnum, 0, -1, -1, -1, -1, -1);
|
|
|
|
if (handle) {
|
|
audioPostEvent(handle, 0x10, *(s32 *)&speed);
|
|
}
|
|
#endif
|
|
} else {
|
|
propsnd0f0939f8(NULL, prop, soundnum, -1, -1, 0, 0, 0, NULL, speed, NULL, -1, -1, -1, -1);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate what was hit from a single shot.
|
|
*
|
|
* Multiple things can be hit, because shots can pass through glass and the more
|
|
* powerful weapons can shoot through chrs.
|
|
*
|
|
* The return value is the final prop that was hit.
|
|
*/
|
|
struct prop *shotCalculateHits(s32 handnum, bool arg1, struct coord *arg2, struct coord *arg3, struct coord *gunpos, struct coord *dir, u32 arg6, f32 arg7, bool arg8)
|
|
{
|
|
u32 index;
|
|
struct prop **propptr;
|
|
struct prop *root;
|
|
bool explosiveshells = false;
|
|
bool sp6cc = false;
|
|
bool hitbg = false;
|
|
s32 room = 0;
|
|
struct hitthing sp694;
|
|
struct hitthing sp664;
|
|
struct coord sp658;
|
|
struct prop *playerprop = g_Vars.currentplayer->prop;
|
|
struct coord hitpos;
|
|
struct shotdata shotdata;
|
|
s32 i;
|
|
s32 s1 = 0;
|
|
struct weaponfunc *func;
|
|
bool laserstream = false;
|
|
bool shortrange = false;
|
|
f32 range = 200;
|
|
struct prop *result = NULL;
|
|
s32 hitindex;
|
|
struct surfacetype *surfacetype;
|
|
bool done;
|
|
s32 sparktype;
|
|
|
|
#ifdef AVOID_UB
|
|
s16 rooms[131];
|
|
#else
|
|
s16 rooms[124];
|
|
#endif
|
|
|
|
s16 spc8[8];
|
|
s16 spb8[8];
|
|
s32 uVar6;
|
|
s16 *roomsptr;
|
|
struct prop *prop;
|
|
struct coord spa0;
|
|
struct defaultobj *obj;
|
|
bool doexplosiveshells;
|
|
struct coord sp8c;
|
|
s16 sp7c[8];
|
|
s16 sp6c[8];
|
|
struct prop *hitprop;
|
|
|
|
bgun0f0a9494(arg6);
|
|
|
|
shotdata.gunpos.x = gunpos->x;
|
|
shotdata.gunpos.y = gunpos->y;
|
|
shotdata.gunpos.z = gunpos->z;
|
|
|
|
shotdata.unk00.x = arg2->x;
|
|
shotdata.unk00.y = arg2->y;
|
|
shotdata.unk00.z = arg2->z;
|
|
|
|
shotdata.dir.x = dir->x;
|
|
shotdata.dir.y = dir->y;
|
|
shotdata.dir.z = dir->z;
|
|
|
|
shotdata.unk0c.x = arg3->x;
|
|
shotdata.unk0c.y = arg3->y;
|
|
shotdata.unk0c.z = arg3->z;
|
|
|
|
gsetPopulateFromCurrentPlayer(handnum, &shotdata.gset);
|
|
func = gsetGetWeaponFunction(&shotdata.gset);
|
|
|
|
if (func) {
|
|
if (arg1 && (func->flags & FUNCFLAG_EXPLOSIVESHELLS)) {
|
|
explosiveshells = true;
|
|
}
|
|
|
|
if ((func->type & 0xff) == INVENTORYFUNCTYPE_CLOSE && arg1) {
|
|
shortrange = true;
|
|
arg1 = false;
|
|
}
|
|
}
|
|
|
|
if (shotdata.gset.weaponnum == WEAPON_LASER && shotdata.gset.weaponfunc == FUNC_SECONDARY) {
|
|
laserstream = true;
|
|
}
|
|
|
|
if (arg1) {
|
|
shotdata.penetration = gsetGetSinglePenetration(&shotdata.gset);
|
|
} else {
|
|
shotdata.penetration = 1;
|
|
}
|
|
|
|
shotdata.unk34 = arg7;
|
|
|
|
for (i = 0; i != 10; i++) {
|
|
shotdata.hits[i].prop = NULL;
|
|
shotdata.hits[i].hitpart = 0;
|
|
shotdata.hits[i].node = NULL;
|
|
}
|
|
|
|
if (laserstream) {
|
|
hitpos.x = shotdata.gunpos.x + shotdata.dir.x * 300;
|
|
hitpos.y = shotdata.gunpos.y + shotdata.dir.y * 300;
|
|
hitpos.z = shotdata.gunpos.z + shotdata.dir.z * 300;
|
|
} else if (shortrange) {
|
|
if ((func->type & 0xff) == INVENTORYFUNCTYPE_CLOSE) {
|
|
struct weaponfunc_close *close = (struct weaponfunc_close *) func;
|
|
range = close->range;
|
|
}
|
|
|
|
hitpos.x = shotdata.gunpos.x + shotdata.dir.x * range;
|
|
hitpos.y = shotdata.gunpos.y + shotdata.dir.y * range;
|
|
hitpos.z = shotdata.gunpos.z + shotdata.dir.z * range;
|
|
} else {
|
|
hitpos.x = shotdata.gunpos.x + shotdata.dir.x * 65536;
|
|
hitpos.y = shotdata.gunpos.y + shotdata.dir.y * 65536;
|
|
hitpos.z = shotdata.gunpos.z + shotdata.dir.z * 65536;
|
|
}
|
|
|
|
portal00018148(&playerprop->pos, &shotdata.gunpos, playerprop->rooms, spc8, 0, 0);
|
|
portal00018148(&shotdata.gunpos, &hitpos, spc8, spb8, rooms, 30);
|
|
|
|
if (shotdata.gset.weaponnum != WEAPON_FARSIGHT || g_Vars.currentplayer->visionmode != VISIONMODE_XRAY) {
|
|
roomsptr = rooms;
|
|
|
|
while (*roomsptr != -1) {
|
|
roomsptr++;
|
|
}
|
|
|
|
roomsGetActive(roomsptr, 100);
|
|
|
|
for (i = 0; rooms[i] != -1; i++) {
|
|
if (bgTestHitInRoom(&shotdata.gunpos, &hitpos, rooms[i], &sp664)) {
|
|
sp664.unk00.x *= 1;
|
|
sp664.unk00.y *= 1;
|
|
sp664.unk00.z *= 1;
|
|
|
|
if (((hitpos.x >= shotdata.gunpos.x && hitpos.x >= sp664.unk00.x && sp664.unk00.x >= shotdata.gunpos.x)
|
|
|| (hitpos.x <= shotdata.gunpos.x && hitpos.x <= sp664.unk00.x && sp664.unk00.x <= shotdata.gunpos.x))
|
|
&& ((hitpos.y >= shotdata.gunpos.y && hitpos.y >= sp664.unk00.y && sp664.unk00.y >= shotdata.gunpos.y)
|
|
|| (hitpos.y <= shotdata.gunpos.y && hitpos.y <= sp664.unk00.y && sp664.unk00.y <= shotdata.gunpos.y))
|
|
&& ((shotdata.gunpos.z <= hitpos.z && sp664.unk00.z <= hitpos.z && shotdata.gunpos.z <= sp664.unk00.z)
|
|
|| (hitpos.z <= shotdata.gunpos.z && hitpos.z <= sp664.unk00.z && sp664.unk00.z <= shotdata.gunpos.z))
|
|
&& (shotdata.gunpos.x != sp664.unk00.x || shotdata.gunpos.y != sp664.unk00.y || shotdata.gunpos.z != sp664.unk00.z)) {
|
|
hitbg = true;
|
|
room = rooms[i];
|
|
|
|
sp694 = sp664;
|
|
|
|
hitpos.x = sp664.unk00.x;
|
|
hitpos.y = sp664.unk00.y;
|
|
hitpos.z = sp664.unk00.z;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hitbg && shotdata.gset.weaponnum != WEAPON_FARSIGHT) {
|
|
mtx4TransformVec(camGetWorldToScreenMtxf(), &sp694.unk00, &sp658);
|
|
|
|
if (shotdata.unk34 > -sp658.z) {
|
|
shotdata.unk34 = -sp658.z;
|
|
}
|
|
}
|
|
|
|
propptr = g_Vars.endonscreenprops - 1;
|
|
|
|
while (propptr >= g_Vars.onscreenprops) {
|
|
prop = *propptr;
|
|
|
|
if (prop) {
|
|
if (prop->type == PROPTYPE_CHR
|
|
|| (prop->type == PROPTYPE_PLAYER && prop->chr && playermgrGetPlayerNumByProp(prop) != g_Vars.currentplayernum)) {
|
|
if (!shortrange) {
|
|
chr0f027994(prop, &shotdata, arg1, arg8);
|
|
}
|
|
} else if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_WEAPON || prop->type == PROPTYPE_DOOR) {
|
|
func0f085e00(prop, &shotdata);
|
|
}
|
|
}
|
|
|
|
propptr--;
|
|
}
|
|
|
|
hitindex = -1;
|
|
|
|
if (shotdata.hits[0].prop) {
|
|
spa0.x = shotdata.hits[0].pos.x;
|
|
spa0.y = shotdata.hits[0].pos.y;
|
|
spa0.z = shotdata.hits[0].pos.z;
|
|
hitindex = 0;
|
|
}
|
|
|
|
if (&spa0);
|
|
|
|
if (hitindex != -1) {
|
|
bgun0f0a94d0(arg6, &shotdata.hits[hitindex].pos, &shotdata.hits[hitindex].dir);
|
|
} else if (hitbg) {
|
|
bgun0f0a94d0(arg6, &sp694.unk00, &sp694.unk0c);
|
|
}
|
|
|
|
if (arg1) {
|
|
for (i = 0; i < 10; i++) {
|
|
hitprop = shotdata.hits[i].prop;
|
|
|
|
if (hitprop && !(laserstream && shotdata.hits[i].distance > 300)) {
|
|
obj = hitprop->obj;
|
|
doexplosiveshells = false;
|
|
root = hitprop;
|
|
|
|
while (root->parent) {
|
|
root = root->parent;
|
|
}
|
|
|
|
if (root->type == PROPTYPE_CHR || root->type == PROPTYPE_PLAYER) {
|
|
chrHit(&shotdata, &shotdata.hits[i]);
|
|
} else if (hitprop->type == PROPTYPE_OBJ || hitprop->type == PROPTYPE_WEAPON || hitprop->type == PROPTYPE_DOOR) {
|
|
objHit(&shotdata, &shotdata.hits[i]);
|
|
}
|
|
|
|
if (shotdata.hits[i].unk4d) {
|
|
sp6cc = true;
|
|
doexplosiveshells = explosiveshells;
|
|
hitpos.x = shotdata.hits[i].pos.x;
|
|
hitpos.y = shotdata.hits[i].pos.y;
|
|
hitpos.z = shotdata.hits[i].pos.z;
|
|
} else if (shotdata.hits[i].unk4c
|
|
|| (explosiveshells && (obj->type == OBJTYPE_GLASS || obj->type == OBJTYPE_TINTEDGLASS))) {
|
|
s1++;
|
|
|
|
if (s1 >= shotdata.penetration) {
|
|
sp6cc = true;
|
|
doexplosiveshells = explosiveshells;
|
|
hitpos.x = shotdata.hits[i].pos.x;
|
|
hitpos.y = shotdata.hits[i].pos.y;
|
|
hitpos.z = shotdata.hits[i].pos.z;
|
|
}
|
|
}
|
|
|
|
if (doexplosiveshells) {
|
|
sp8c.x = shotdata.hits[i].pos.x;
|
|
sp8c.y = shotdata.hits[i].pos.y;
|
|
sp8c.z = shotdata.hits[i].pos.z;
|
|
|
|
func0f065e74(&root->pos, root->rooms, &sp8c, sp7c);
|
|
explosionCreateSimple(0, &sp8c, sp7c, EXPLOSIONTYPE_PHOENIX, g_Vars.currentplayernum);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hitbg && !sp6cc) {
|
|
sp6c[0] = room;
|
|
sp6c[1] = -1;
|
|
|
|
uVar6 = lightsHandleHit(&shotdata.gunpos, &hitpos, room);
|
|
|
|
if (sp694.texturenum < 0 || sp694.texturenum >= NUM_TEXTURES) {
|
|
surfacetype = g_SurfaceTypes[SURFACETYPE_DEFAULT];
|
|
} else {
|
|
index = g_Textures[sp694.texturenum].surfacetype;
|
|
|
|
if (index < ARRAYCOUNT(g_SurfaceTypes)) {
|
|
surfacetype = g_SurfaceTypes[index];
|
|
} else {
|
|
surfacetype = g_SurfaceTypes[SURFACETYPE_DEFAULT];
|
|
}
|
|
}
|
|
|
|
bgunSetHitPos(&sp694.unk00);
|
|
|
|
if (surfacetype->numwallhittexes > 0 && (!func || (func->type & 0xff) != INVENTORYFUNCTYPE_CLOSE)) {
|
|
if (shotdata.gset.weaponnum != WEAPON_UNARMED
|
|
&& shotdata.gset.weaponnum != WEAPON_LASER
|
|
&& shotdata.gset.weaponnum != WEAPON_TRANQUILIZER
|
|
&& shotdata.gset.weaponnum != WEAPON_FARSIGHT) {
|
|
uVar6 = random() % surfacetype->numwallhittexes;
|
|
uVar6 = surfacetype->wallhittexes[uVar6];
|
|
|
|
if (uVar6 >= WALLHITTEX_GLASS1 && uVar6 <= WALLHITTEX_GLASS3) {
|
|
// Use bulletproof glass hit textures instead
|
|
uVar6 += 10;
|
|
}
|
|
|
|
if (uVar6) {
|
|
wallhitCreate(&sp694.unk00, &sp694.unk0c, &shotdata.gunpos, 0, 0, uVar6, room, 0, -1, 0, g_Vars.currentplayer->prop->chr, sp694.unk2c == 2);
|
|
}
|
|
}
|
|
|
|
bgunPlayBgHitSound(&shotdata.gset, &sp694.unk00, sp694.texturenum, sp6c);
|
|
|
|
if (explosiveshells) {
|
|
explosionCreateSimple(NULL, &sp694.unk00, sp6c, EXPLOSIONTYPE_PHOENIX, g_Vars.currentplayernum);
|
|
} else {
|
|
if (!chrIsUsingPaintball(g_Vars.currentplayer->prop->chr)) {
|
|
if (PLAYERCOUNT() >= 2) {
|
|
if ((random() % 8) == 0) {
|
|
smokeCreateSimple(&sp694.unk00, sp6c, SMOKETYPE_BULLETIMPACT);
|
|
}
|
|
} else {
|
|
if (uVar6) {
|
|
explosionCreateSimple(NULL, &sp694.unk00, sp6c, EXPLOSIONTYPE_BULLETHOLE, g_Vars.currentplayernum);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (PLAYERCOUNT() <= 2 || g_Vars.lvupdate240 <= 8 || (random() % 4) == 0) {
|
|
if (sp694.unk00.x > -32000 && sp694.unk00.x < 32000
|
|
&& sp694.unk00.y > -32000 && sp694.unk00.y < 32000
|
|
&& sp694.unk00.z > -32000 && sp694.unk00.z < 32000) {
|
|
sparktype = SPARKTYPE_DEFAULT;
|
|
|
|
if (chrIsUsingPaintball(g_Vars.currentplayer->prop->chr)) {
|
|
sparktype = SPARKTYPE_PAINT;
|
|
} else {
|
|
switch (shotdata.gset.weaponnum) {
|
|
case WEAPON_FARSIGHT:
|
|
sparktype = SPARKTYPE_BGHIT_ORANGE;
|
|
break;
|
|
case WEAPON_CYCLONE:
|
|
sparktype = SPARKTYPE_ELECTRICAL;
|
|
break;
|
|
case WEAPON_MAULER:
|
|
case WEAPON_PHOENIX:
|
|
case WEAPON_CALLISTO:
|
|
case WEAPON_REAPER:
|
|
sparktype = SPARKTYPE_BGHIT_GREEN;
|
|
break;
|
|
case WEAPON_TRANQUILIZER:
|
|
sparktype = SPARKTYPE_BGHIT_TRANQULIZER;
|
|
break;
|
|
}
|
|
|
|
uVar6 = g_Textures[sp694.texturenum].surfacetype;
|
|
|
|
if (uVar6 == SURFACETYPE_SHALLOWWATER || uVar6 == SURFACETYPE_DEEPWATER) {
|
|
sparktype = SPARKTYPE_SHALLOWWATER;
|
|
}
|
|
}
|
|
|
|
sparksCreate(room, NULL, &sp694.unk00, &shotdata.dir, &sp694.unk0c, sparktype);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
bgunSetHitPos(&hitpos);
|
|
}
|
|
} else if (shortrange) {
|
|
s32 hitindex;
|
|
bool hitaprop = false;
|
|
|
|
hitindex = 0;
|
|
|
|
for (i = 0; i < 10; i++) {
|
|
if (shotdata.hits[i].prop && shotdata.hits[i].distance < range) {
|
|
hitaprop = true;
|
|
hitindex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hitaprop || hitbg) {
|
|
func0f060bac(shotdata.gset.weaponnum, g_Vars.currentplayer->prop);
|
|
|
|
if (shotdata.gset.weaponnum != WEAPON_UNARMED && shotdata.gset.weaponnum != WEAPON_TRANQUILIZER) {
|
|
if (hitaprop) {
|
|
sparksCreate(shotdata.hits[hitindex].prop->rooms[0], NULL, &shotdata.hits[hitindex].pos, &shotdata.dir, &shotdata.hits[hitindex].dir, SPARKTYPE_DEFAULT);
|
|
} else {
|
|
sparksCreate(room, NULL, &sp694.unk00, &shotdata.dir, &sp694.unk0c, SPARKTYPE_DEFAULT);
|
|
}
|
|
}
|
|
} else {
|
|
weaponPlayWhooshSound(shotdata.gset.weaponnum, g_Vars.currentplayer->prop);
|
|
}
|
|
} else {
|
|
// Figure out what prop was hit so it can be returned,
|
|
// by iterating the props in order of distance.
|
|
// For laser stream, bail early once the laser's range is reached.
|
|
done = false;
|
|
|
|
for (i = 0; i < 10; i++) {
|
|
hitprop = shotdata.hits[i].prop;
|
|
|
|
if (hitprop && !done) {
|
|
if (laserstream && shotdata.hits[i].distance > 300) {
|
|
done = true;
|
|
} else {
|
|
if (hitprop->type == PROPTYPE_CHR
|
|
|| hitprop->type == PROPTYPE_PLAYER) {
|
|
result = hitprop;
|
|
done = true;
|
|
} else if (hitprop->type == PROPTYPE_OBJ
|
|
|| hitprop->type == PROPTYPE_WEAPON
|
|
|| hitprop->type == PROPTYPE_DOOR) {
|
|
result = hitprop;
|
|
done = true;
|
|
}
|
|
|
|
// This seems like it handles penetration (bullets going
|
|
// through multiple props), but the loop is effectively
|
|
// stopped when any prop is hit so this seems unlikely.
|
|
if (shotdata.hits[i].unk4c) {
|
|
s1++;
|
|
|
|
if (s1 >= shotdata.penetration) {
|
|
done = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
struct prop *func0f061d54(s32 handnum, u32 arg1, u32 arg2)
|
|
{
|
|
struct coord sp64;
|
|
struct coord sp58;
|
|
struct coord sp4c;
|
|
struct coord sp40;
|
|
|
|
bgunCalculatePlayerShotSpread(&sp58, &sp64, handnum, arg2);
|
|
|
|
if (arg2 == 2 && bgunGetWeaponNum(HAND_RIGHT) == WEAPON_REAPER) {
|
|
sp58.y -= 15 * RANDOMFRAC();
|
|
}
|
|
|
|
mtx4TransformVec(camGetProjectionMtxF(), &sp58, &sp40);
|
|
mtx4RotateVec(camGetProjectionMtxF(), &sp64, &sp4c);
|
|
|
|
return shotCalculateHits(handnum, arg1, &sp58, &sp64, &sp40, &sp4c, 0, 4294836224, PLAYERCOUNT() >= 2);
|
|
}
|
|
|
|
void shotCreate(s32 handnum, bool arg1, bool dorandom, s32 arg3, bool arg4)
|
|
{
|
|
struct coord shootdir;
|
|
struct coord shootpos;
|
|
struct coord sp44;
|
|
struct coord sp38;
|
|
|
|
bgunCalculatePlayerShotSpread(&sp38, &sp44, handnum, dorandom);
|
|
|
|
if (arg3 > 0) {
|
|
mtx4TransformVec(camGetProjectionMtxF(), &sp38, &shootpos);
|
|
mtx4RotateVec(camGetProjectionMtxF(), &sp44, &shootdir);
|
|
|
|
shotCalculateHits(handnum, arg1, &sp38, &sp44, &shootpos, &shootdir, 0, 4294836224, arg4);
|
|
|
|
if (arg3 < 2) {
|
|
bgunSetLastShootInfo(&shootpos, &shootdir, handnum);
|
|
}
|
|
}
|
|
}
|
|
|
|
void func0f061fa8(struct shotdata *shotdata, struct prop *prop, f32 arg2, s32 hitpart, struct modelnode *node, struct hitthing *hitthing, s32 arg6, struct modelnode *arg7, struct model *model, bool arg9, s32 arg10, struct coord *arg11, struct coord *arg12)
|
|
{
|
|
s32 i;
|
|
f32 fVar8;
|
|
|
|
if (arg9) {
|
|
s32 bestindex = 0;
|
|
s32 count = 0;
|
|
f32 mostdist = 0;
|
|
f32 prevmostdist = 0;
|
|
|
|
for (i = 0; i < 10; i++) {
|
|
if (shotdata->hits[i].prop && shotdata->hits[i].unk4c) {
|
|
count++;
|
|
|
|
if (shotdata->hits[i].distance > mostdist) {
|
|
prevmostdist = mostdist;
|
|
mostdist = shotdata->hits[i].distance;
|
|
bestindex = i;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (count >= shotdata->penetration) {
|
|
shotdata->hits[bestindex].prop = NULL;
|
|
shotdata->unk34 = prevmostdist;
|
|
|
|
if (shotdata->unk34 < arg2) {
|
|
shotdata->unk34 = arg2;
|
|
}
|
|
|
|
for (i = 0; i < 10; i++) {
|
|
if (shotdata->hits[i].prop && !shotdata->hits[i].unk4c && shotdata->hits[i].distance > prevmostdist) {
|
|
shotdata->hits[i].prop = NULL;
|
|
}
|
|
}
|
|
} else if (count + 1 == shotdata->penetration) {
|
|
if (shotdata->unk34 > arg2) {
|
|
shotdata->unk34 = arg2;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (arg10) {
|
|
for (i = 0; i < 10; i++) {
|
|
if (shotdata->hits[i].prop && shotdata->hits[i].distance > arg2) {
|
|
shotdata->hits[i].prop = NULL;
|
|
}
|
|
}
|
|
|
|
shotdata->unk34 = arg2;
|
|
}
|
|
|
|
for (i = 0; i < 10; i++) {
|
|
if (shotdata->hits[i].prop == NULL) {
|
|
struct hit *hit = &shotdata->hits[i];
|
|
|
|
hit->distance = arg2;
|
|
hit->prop = prop;
|
|
hit->hitpart = hitpart;
|
|
hit->node = node;
|
|
hit->hitthing = *hitthing;
|
|
hit->mtxindex = arg6;
|
|
hit->unk44 = arg7;
|
|
hit->model = model;
|
|
hit->unk4c = arg9;
|
|
hit->unk4d = arg10;
|
|
hit->pos.x = arg11->x;
|
|
hit->pos.y = arg11->y;
|
|
hit->pos.z = arg11->z;
|
|
|
|
fVar8 = sqrtf(arg12->f[0] * arg12->f[0] + arg12->f[1] * arg12->f[1] + arg12->f[2] * arg12->f[2]);
|
|
|
|
if (fVar8 > 0) {
|
|
hit->dir.x = arg12->x / fVar8;
|
|
hit->dir.y = arg12->y / fVar8;
|
|
hit->dir.z = arg12->z / fVar8;
|
|
} else {
|
|
hit->dir.x = 0;
|
|
hit->dir.y = 0;
|
|
hit->dir.z = 1;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void handInflictCloseRangeDamage(s32 handnum, struct gset *gset, bool arg2)
|
|
{
|
|
s32 cdtypes;
|
|
struct prop **ptr;
|
|
struct prop *playerprop;
|
|
bool skipthething;
|
|
|
|
playerprop = g_Vars.currentplayer->prop;
|
|
ptr = g_Vars.endonscreenprops - 1;
|
|
skipthething = false;
|
|
|
|
// Iterate onscreen props near to far
|
|
while (ptr >= g_Vars.onscreenprops) {
|
|
struct prop *prop = *ptr;
|
|
|
|
if (prop && prop->z < 500) {
|
|
/**
|
|
* @bug: There is no check to make sure the prop's type is obj
|
|
* before accessing the obj properties. prop->obj is a void *
|
|
* pointer or union of pointers so it may point to a non-obj struct.
|
|
* This means it's checking if the byte at offset 3 in whichever
|
|
* struct it's pointing to has the value 0x2a (glass) or 0x2f
|
|
* (tinted glass).
|
|
*
|
|
* If the prop being iterated is a chr, this offset corresponds to
|
|
* the speedrating property in the chrdata struct. So inflicting
|
|
* close range damage on a chr with a speedrating of 42 (0x2a) or
|
|
* 47 (0x2f) will cause a crash. No chrs have these speedrating
|
|
* values though, and the reaction speed setting in PD mode was
|
|
* removed... maybe because of this bug?
|
|
*
|
|
* If the prop being iterated is an explosion or smoke, the offset
|
|
* corresponds to the last byte of a pointer to a struct. Because
|
|
* structs are 4-byte aligned, its value will never be 0x2a or 0x2f.
|
|
* If glass or tinted glass were assigned values that were multiples
|
|
* of 4 then the game could crash when you punch explosions or
|
|
* smoke.
|
|
*
|
|
* The other prop types use pointers that also point to chr structs,
|
|
* or to other objs which extend defaultobj, so the bug is not
|
|
* present for those.
|
|
*/
|
|
struct defaultobj *obj = prop->obj;
|
|
bool isglass = false;
|
|
|
|
if (obj && gset->weaponnum != WEAPON_TRANQUILIZER) {
|
|
isglass = obj->type == OBJTYPE_GLASS || obj->type == OBJTYPE_TINTEDGLASS;
|
|
}
|
|
|
|
if (arg2) {
|
|
isglass = false;
|
|
}
|
|
|
|
if (prop->type == PROPTYPE_CHR
|
|
|| (prop->type == PROPTYPE_PLAYER && prop->chr && playermgrGetPlayerNumByProp(prop) != g_Vars.currentplayernum)
|
|
|| isglass) {
|
|
f32 rangelimit = 60;
|
|
f32 distance;
|
|
f32 sp110;
|
|
struct chrdata *chr = prop->chr;
|
|
f32 x;
|
|
f32 y;
|
|
f32 spfc[2];
|
|
f32 spf4[2];
|
|
struct model *model;
|
|
struct weaponfunc *func = gsetGetWeaponFunction(gset);
|
|
|
|
if ((func->type & 0xff) == 3) {
|
|
struct weaponfunc_close *closefunc = (struct weaponfunc_close *)func;
|
|
rangelimit = closefunc->range;
|
|
}
|
|
|
|
bgunGetCrossPos(&x, &y);
|
|
|
|
spfc[0] = (x - camGetScreenLeft()) / (camGetScreenWidth() * 0.5f) - 1.0f;
|
|
spfc[1] = (y - camGetScreenTop()) / (camGetScreenHeight() * 0.5f) - 1.0f;
|
|
spf4[0] = camGetScreenHeight() * 0.16666667163372f;
|
|
spf4[1] = camGetScreenHeight() * 0.125f;
|
|
|
|
if (isglass) {
|
|
model = obj->model;
|
|
} else {
|
|
model = chr->model;
|
|
}
|
|
|
|
if (func0f0679ac(model, &distance, &sp110, spfc, spf4)
|
|
&& sp110 <= 0
|
|
&& distance >= -rangelimit) {
|
|
cdtypes = CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_PATHBLOCKER | CDTYPE_BG;
|
|
|
|
if (isglass) {
|
|
cdtypes = 0;
|
|
}
|
|
|
|
if (cdTestLos04(&playerprop->pos, playerprop->rooms, &prop->pos, cdtypes)) {
|
|
if (isglass) {
|
|
struct model *model = obj->model;
|
|
struct coord spd8;
|
|
struct coord spcc;
|
|
struct modelnode *node = NULL;
|
|
|
|
bgunCalculatePlayerShotSpread(&spd8, &spcc, handnum, true);
|
|
|
|
if (model000225d4(model, &spd8, &spcc, &node) > 0) {
|
|
f32 damage = gsetGetDamage(gset) * 2.5f;
|
|
skipthething = true;
|
|
bgunPlayGlassHitSound(&playerprop->pos, playerprop->rooms, -1);
|
|
objTakeGunfire(obj, damage, &prop->pos, gset->weaponnum, g_Vars.currentplayernum);
|
|
objDropRecursively(prop, false);
|
|
}
|
|
} else if (arg2) {
|
|
chr->chrflags |= CHRCFLAG_10000000;
|
|
} else {
|
|
struct coord spb8;
|
|
struct coord vector;
|
|
struct modelnode *node = NULL;
|
|
struct model *model = NULL;
|
|
s32 side = -1;
|
|
s32 hitpart = HITPART_TORSO;
|
|
|
|
bgunCalculatePlayerShotSpread(&spb8, &vector, handnum, true);
|
|
skipthething = true;
|
|
mtx4RotateVecInPlace(camGetProjectionMtxF(), &vector);
|
|
bgunPlayPropHitSound(gset, prop, -1);
|
|
|
|
if (chr->model && chrGetShield(chr) > 0) {
|
|
chrCalculateShieldHit(chr, &playerprop->pos, &vector, &node, &hitpart, &model, &side);
|
|
}
|
|
|
|
if (bmoveGetCrouchPos() == CROUCHPOS_DUCK) {
|
|
hitpart = HITPART_GENERAL;
|
|
} else if (bmoveGetCrouchPos() == CROUCHPOS_SQUAT) {
|
|
hitpart = HITPART_GENERALHALF;
|
|
} else {
|
|
hitpart = HITPART_TORSO;
|
|
}
|
|
|
|
func0f0341dc(chr, gsetGetDamage(gset), &vector, gset,
|
|
g_Vars.currentplayer->prop, hitpart, chr->prop, node, model, side, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ptr--;
|
|
}
|
|
|
|
if (!skipthething && !arg2) {
|
|
g_Vars.currentplayer->hands[handnum].unk0d0f_02 = true;
|
|
}
|
|
}
|
|
|
|
void handTickAttack(s32 handnum)
|
|
{
|
|
if (g_Vars.currentplayer->hands[handnum].unk0d0f_02) {
|
|
s32 doit = true;
|
|
|
|
if (bgunGetWeaponNum(handnum) == WEAPON_REAPER
|
|
&& (g_Vars.currentplayer->hands[handnum].burstbullets % 3) != 1) {
|
|
doit = false;
|
|
}
|
|
|
|
if (doit) {
|
|
func0f061d54(handnum, 1, 2);
|
|
}
|
|
|
|
g_Vars.currentplayer->hands[handnum].unk0d0f_02 = false;
|
|
}
|
|
|
|
if (bgunIsFiring(handnum)) {
|
|
s32 type = bgunGetAttackType(handnum);
|
|
s32 weaponnum = bgunGetWeaponNum(handnum);
|
|
struct gset gset;
|
|
bool cloaked;
|
|
|
|
g_Vars.currentplayer->hands[handnum].activatesecondary = false;
|
|
|
|
gsetPopulateFromCurrentPlayer(handnum, &gset);
|
|
|
|
switch (type) {
|
|
case HANDATTACKTYPE_SHOOT:
|
|
// Always execute if right hand, but if left hand then execute if
|
|
// right hand is not (ie. prevent firing both guns on the same tick)
|
|
if (handnum == HAND_RIGHT || !bgunIsFiring(HAND_RIGHT)) {
|
|
chrUncloakTemporarily(g_Vars.currentplayer->prop->chr);
|
|
mpstatsIncrementPlayerShotCount2(&gset, 0);
|
|
|
|
if (weaponnum == WEAPON_SHOTGUN) {
|
|
shotCreate(handnum, true, true, 1, true);
|
|
shotCreate(handnum, true, true, 1, true);
|
|
shotCreate(handnum, true, true, 1, true);
|
|
shotCreate(handnum, true, true, 1, true);
|
|
shotCreate(handnum, true, true, 1, true);
|
|
shotCreate(handnum, true, true, 1, true);
|
|
} else {
|
|
shotCreate(handnum, true, true, bgunGetShotsToTake(handnum), g_Vars.mplayerisrunning);
|
|
}
|
|
|
|
mpstats0f0b0520();
|
|
}
|
|
break;
|
|
case HANDATTACKTYPE_CLOSERANGE:
|
|
chrUncloakTemporarily(g_Vars.currentplayer->prop->chr);
|
|
handInflictCloseRangeDamage(handnum, &gset, false);
|
|
break;
|
|
case HANDATTACKTYPE_CLOSERANGENOUNCLOAK:
|
|
handInflictCloseRangeDamage(handnum, &gset, true);
|
|
break;
|
|
case HANDATTACKTYPE_DETONATE:
|
|
playerActivateRemoteMineDetonator(g_Vars.currentplayernum);
|
|
break;
|
|
case HANDATTACKTYPE_UPLINK:
|
|
propFindForUplink();
|
|
break;
|
|
case HANDATTACKTYPE_BOOST:
|
|
bgunApplyBoost();
|
|
break;
|
|
case HANDATTACKTYPE_REVERTBOOST:
|
|
bgunRevertBoost();
|
|
break;
|
|
case HANDATTACKTYPE_SHOOTPROJECTILE:
|
|
bgunCreateFiredProjectile(handnum);
|
|
break;
|
|
case HANDATTACKTYPE_CROUCH:
|
|
if (g_Vars.currentplayer->crouchpos == CROUCHPOS_SQUAT) {
|
|
bwalkAdjustCrouchPos(2);
|
|
} else {
|
|
bwalkAdjustCrouchPos(-2);
|
|
}
|
|
break;
|
|
case HANDATTACKTYPE_THROWPROJECTILE:
|
|
bgunCreateThrownProjectile(handnum, &gset);
|
|
break;
|
|
case HANDATTACKTYPE_RCP120CLOAK:
|
|
cloaked = (g_Vars.currentplayer->devicesactive & DEVICE_CLOAKRCP120) != 0;
|
|
|
|
if (cloaked) {
|
|
g_Vars.currentplayer->devicesactive &= ~DEVICE_CLOAKRCP120;
|
|
} else {
|
|
g_Vars.currentplayer->devicesactive =
|
|
(g_Vars.currentplayer->devicesactive & ~DEVICE_CLOAKDEVICE)
|
|
| DEVICE_CLOAKRCP120;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void handsTickAttack(void)
|
|
{
|
|
if (g_Vars.lvupdate240 > 0) {
|
|
handTickAttack(HAND_RIGHT);
|
|
handTickAttack(HAND_LEFT);
|
|
}
|
|
}
|
|
|
|
void propExecuteTickOperation(struct prop *prop, s32 op)
|
|
{
|
|
if (op == TICKOP_FREE) {
|
|
if ((prop->type == PROPTYPE_WEAPON || prop->type == PROPTYPE_OBJ)
|
|
&& prop->obj && (prop->obj->hidden2 & OBJH2FLAG_CANREGEN)) {
|
|
struct defaultobj *obj = prop->obj;
|
|
|
|
prop->timetoregen = TICKS(1200);
|
|
|
|
obj->damage = 0;
|
|
obj->hidden |= OBJHFLAG_00000800;
|
|
obj->hidden &= ~OBJHFLAG_REAPABLE;
|
|
obj->hidden2 &= ~OBJH2FLAG_DESTROYED;
|
|
|
|
propDeregisterRooms(prop);
|
|
propDisable(prop);
|
|
|
|
if (!prop->active) {
|
|
propUnpause(prop);
|
|
}
|
|
} else {
|
|
propDeregisterRooms(prop);
|
|
propDelist(prop);
|
|
propDisable(prop);
|
|
propFree(prop);
|
|
}
|
|
} else if (op == TICKOP_DISABLE) {
|
|
propDeregisterRooms(prop);
|
|
propDelist(prop);
|
|
propDisable(prop);
|
|
} else if (op == TICKOP_GIVETOPLAYER) {
|
|
propDeregisterRooms(prop);
|
|
propDelist(prop);
|
|
propDisable(prop);
|
|
objDetach(prop);
|
|
objFreeEmbedmentOrProjectile(prop);
|
|
propReparent(prop, g_Vars.currentplayer->prop);
|
|
}
|
|
}
|
|
|
|
struct prop *propFindForInteract(bool usingeyespy)
|
|
{
|
|
struct prop **ptr;
|
|
bool checkmore = true;
|
|
|
|
g_InteractProp = NULL;
|
|
|
|
// Iterate onscreen list near to far
|
|
for (ptr = g_Vars.endonscreenprops - 1; ptr >= g_Vars.onscreenprops; ptr--) {
|
|
struct prop *prop = *ptr;
|
|
|
|
if (prop) {
|
|
if (prop->type == PROPTYPE_CHR) {
|
|
// empty
|
|
} else if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_WEAPON) {
|
|
if (!usingeyespy) {
|
|
checkmore = objTestForInteract(prop);
|
|
}
|
|
} else if (prop->type == PROPTYPE_DOOR) {
|
|
checkmore = doorTestForInteract(prop);
|
|
} else if (prop->type == PROPTYPE_EXPLOSION) {
|
|
// empty
|
|
} else if (prop->type == PROPTYPE_SMOKE) {
|
|
// empty
|
|
}
|
|
|
|
if (!checkmore) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return g_InteractProp;
|
|
}
|
|
|
|
/**
|
|
* While this function is called, it doesn't return anything and doesn't appear
|
|
* to be useful. Uplinking still works when this function is empty.
|
|
*/
|
|
void propFindForUplink(void)
|
|
{
|
|
struct prop **ptr = g_Vars.endonscreenprops - 1;
|
|
bool checkmore = true;
|
|
|
|
// Iterate onscreen props near to far
|
|
while (ptr >= g_Vars.onscreenprops) {
|
|
struct prop *prop = *ptr;
|
|
|
|
if (prop) {
|
|
if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_WEAPON) {
|
|
checkmore = objTestForInteract(prop);
|
|
}
|
|
|
|
if (!checkmore) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
ptr--;
|
|
}
|
|
}
|
|
|
|
bool currentPlayerInteract(bool eyespy)
|
|
{
|
|
struct prop *prop;
|
|
bool op = TICKOP_NONE;
|
|
|
|
prop = propFindForInteract(eyespy);
|
|
|
|
if (prop) {
|
|
switch (prop->type) {
|
|
case PROPTYPE_OBJ:
|
|
case PROPTYPE_WEAPON:
|
|
op = propobjInteract(prop);
|
|
break;
|
|
case PROPTYPE_DOOR:
|
|
op = propdoorInteract(prop);
|
|
break;
|
|
case PROPTYPE_CHR:
|
|
case PROPTYPE_PLAYER:
|
|
case PROPTYPE_EXPLOSION:
|
|
case PROPTYPE_SMOKE:
|
|
break;
|
|
}
|
|
|
|
propExecuteTickOperation(prop, op);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Pause a prop. Paused props still exist in the stage, but are not near the
|
|
* player and do not tick.
|
|
*
|
|
* The prop is removed from its current list (activeprops or pausedprops)
|
|
* if any, and is then inserted to the head of pausedprops.
|
|
*/
|
|
void propPause(struct prop *prop)
|
|
{
|
|
if ((prop->flags & PROPFLAG_DONTPAUSE) == 0) {
|
|
propDelist(prop);
|
|
|
|
if (g_Vars.pausedprops) {
|
|
prop->prev = g_Vars.pausedprops->prev;
|
|
|
|
if (prop->prev) {
|
|
prop->prev->next = prop;
|
|
}
|
|
|
|
g_Vars.pausedprops->prev = prop;
|
|
prop->next = g_Vars.pausedprops;
|
|
|
|
if (g_Vars.activeprops == g_Vars.pausedprops) {
|
|
g_Vars.activeprops = g_Vars.activepropstail = prop;
|
|
}
|
|
|
|
g_Vars.pausedprops = prop;
|
|
} else {
|
|
prop->next = NULL;
|
|
|
|
if (g_Vars.activepropstail) {
|
|
prop->prev = g_Vars.activepropstail;
|
|
g_Vars.activepropstail->next = prop;
|
|
} else {
|
|
g_Vars.activepropstail = prop;
|
|
g_Vars.activeprops = prop;
|
|
}
|
|
|
|
g_Vars.pausedprops = prop;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unpause a prop. The prop will begin ticking.
|
|
*
|
|
* The prop is removed from its current list (activeprops or pausedprops)
|
|
* if any, and is then inserted to the head of activeprops.
|
|
*
|
|
* If this function is being called from propsTickPlayer (which iterates active props)
|
|
* then the prop will do its next tick on the next frame, due to it being
|
|
* inserted at the head.
|
|
*/
|
|
void propUnpause(struct prop *prop)
|
|
{
|
|
if (prop == g_Vars.pausedprops) {
|
|
if (g_Vars.activeprops == g_Vars.pausedprops) {
|
|
g_Vars.activeprops = g_Vars.activepropstail = prop->next;
|
|
}
|
|
|
|
g_Vars.pausedprops = prop->next;
|
|
}
|
|
|
|
if (prop->next) {
|
|
prop->next->prev = prop->prev;
|
|
}
|
|
|
|
if (prop->prev) {
|
|
prop->prev->next = prop->next;
|
|
}
|
|
|
|
prop->next = NULL;
|
|
prop->prev = NULL;
|
|
|
|
propActivate(prop);
|
|
}
|
|
|
|
// 0 = will tick when backgrounded
|
|
// 1 = will not tick when backgrounded
|
|
u8 g_PausableObjs[] = {
|
|
0, // dummy element because objects are 1-indexed
|
|
0, // OBJTYPE_DOOR
|
|
0, // OBJTYPE_DOORSCALE
|
|
1, // OBJTYPE_BASIC
|
|
1, // OBJTYPE_KEY
|
|
1, // OBJTYPE_ALARM
|
|
1, // OBJTYPE_CCTV
|
|
1, // OBJTYPE_AMMOCRATE
|
|
1, // OBJTYPE_WEAPON
|
|
0, // OBJTYPE_CHR
|
|
1, // OBJTYPE_SINGLEMONITOR
|
|
1, // OBJTYPE_MULTIMONITOR
|
|
1, // OBJTYPE_HANGINGMONITORS
|
|
1, // OBJTYPE_AUTOGUN
|
|
1, // OBJTYPE_LINKGUNS
|
|
0, // OBJTYPE_DEBRIS
|
|
0, // OBJTYPE_10
|
|
1, // OBJTYPE_HAT
|
|
1, // OBJTYPE_GRENADEPROB
|
|
1, // OBJTYPE_LINKLIFTDOOR
|
|
1, // OBJTYPE_MULTIAMMOCRATE
|
|
1, // OBJTYPE_SHIELD
|
|
0, // OBJTYPE_TAG
|
|
0, // OBJTYPE_BEGINOBJECTIVE
|
|
0, // OBJTYPE_ENDOBJECTIVE
|
|
0, // OBJECTIVETYPE_DESTROYOBJ
|
|
0, // OBJECTIVETYPE_COMPFLAGS
|
|
0, // OBJECTIVETYPE_FAILFLAGS
|
|
0, // OBJECTIVETYPE_COLLECTOBJ
|
|
0, // OBJECTIVETYPE_THROWOBJ
|
|
1, // OBJECTIVETYPE_HOLOGRAPH
|
|
0, // OBJECTIVETYPE_1F
|
|
0, // OBJECTIVETYPE_ENTERROOM
|
|
0, // OBJECTIVETYPE_THROWINROOM
|
|
0, // OBJTYPE_22
|
|
0, // OBJTYPE_BRIEFING
|
|
1, // OBJTYPE_GASBOTTLE
|
|
1, // OBJTYPE_RENAMEOBJ
|
|
0, // OBJTYPE_PADLOCKEDDOOR
|
|
0, // OBJTYPE_TRUCK
|
|
0, // OBJTYPE_HELI
|
|
0, // OBJTYPE_29
|
|
1, // OBJTYPE_GLASS
|
|
1, // OBJTYPE_SAFE
|
|
1, // OBJTYPE_SAFEITEM
|
|
0, // OBJTYPE_TANK
|
|
1, // OBJTYPE_CAMERAPOS
|
|
1, // OBJTYPE_TINTEDGLASS
|
|
0, // OBJTYPE_LIFT
|
|
0, // OBJTYPE_CONDITIONALSCENERY
|
|
0, // OBJTYPE_BLOCKEDPATH
|
|
0, // OBJTYPE_HOVERBIKE
|
|
0, // OBJTYPE_END
|
|
0, // OBJTYPE_HOVERPROP
|
|
1, // OBJTYPE_FAN
|
|
0, // OBJTYPE_HOVERCAR
|
|
0, // OBJTYPE_PADEFFECT
|
|
0, // OBJTYPE_CHOPPER
|
|
0, // OBJTYPE_MINE
|
|
1, // OBJTYPE_ESCASTEP
|
|
};
|
|
|
|
/**
|
|
* Figure out which props need to update their state for the current player on
|
|
* this tick, and do so.
|
|
*
|
|
* This function is called once per player per frame. Most logic applies to all
|
|
* players, but some logic only applies to the first player and some only to the
|
|
* last.
|
|
*
|
|
* Props are split into two classes: foreground and background props. Foreground
|
|
* props are generally props that are near the player. These ones are updated on
|
|
* every tick. Background props are props elsewhere in the stage. These ones are
|
|
* updated less frequently by assigning them to one of 7 timeslots and only
|
|
* updating one timeslot per frame (in multiplayer, only 4 timeslots are used).
|
|
*
|
|
* Timeslots and propstates are the same thing. It is likely that the original
|
|
* code called them propstates. The propstate structs are used to track timing
|
|
* information so all props in that timeslot can be updated using the correct
|
|
* multipliers.
|
|
*
|
|
* Each time all timeslots have been updated (ie. every 7 or 4 frames) the
|
|
* timeslots are redistributed among the props, to handle situations where props
|
|
* have been allocated or freed during gameplay. This redistribution is done by
|
|
* categorising each prop into one of four categories:
|
|
*
|
|
* - foreground chrs
|
|
* - background chrs
|
|
* - foreground non-chrs
|
|
* - background non-chrs
|
|
*
|
|
* Timeslots are then assigned evenly within those categories. This method
|
|
* ensures that there aren't an uneven amount of props being updated on any
|
|
* given frame, which helps give a consistent frame rate.
|
|
*/
|
|
void propsTickPlayer(bool islastplayer)
|
|
{
|
|
struct prop *prop;
|
|
struct prop *end;
|
|
s32 savedlvupdate240;
|
|
s32 savedlvupdate60;
|
|
f32 savedlvupdate60f;
|
|
f32 savedlvupdate60freal;
|
|
s32 savedslotupdate240;
|
|
s32 savedslotupdate240_60;
|
|
f32 savedslotupdate240f;
|
|
struct g_vars *vars = &g_Vars;
|
|
s16 *rooms;
|
|
u8 mostindex;
|
|
u8 leastindex;
|
|
u8 runstateindex;
|
|
u8 flags;
|
|
s32 op;
|
|
struct prop *next;
|
|
struct prop *savednext;
|
|
struct defaultobj *obj;
|
|
u16 least;
|
|
u16 most;
|
|
s32 i;
|
|
bool done;
|
|
struct chrdata *chr1;
|
|
struct chrdata *chr2;
|
|
|
|
g_Vars.hardfreeabletally = 0;
|
|
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
var8009cdac = 0;
|
|
var8009cdb0 = 0;
|
|
#endif
|
|
|
|
if (islastplayer) {
|
|
g_Vars.prevupdateframe = g_Vars.updateframe;
|
|
g_Vars.updateframe++;
|
|
|
|
// This condition never passes because g_Vars.updateframe is a u16
|
|
if (g_Vars.updateframe == 0xffffffff) {
|
|
g_Vars.updateframe = 0;
|
|
}
|
|
}
|
|
|
|
// Save these global timing values because we'll be modifying them
|
|
// and restoring them afterwards
|
|
savedlvupdate240 = g_Vars.lvupdate240;
|
|
savedlvupdate60 = g_Vars.lvupdate60;
|
|
savedlvupdate60f = g_Vars.lvupdate60f;
|
|
savedlvupdate60freal = g_Vars.lvupdate60freal;
|
|
|
|
for (i = 0; i < g_Vars.numpropstates; i++) {
|
|
g_Vars.propstates[i].slotupdate240 += g_Vars.lvupdate240;
|
|
}
|
|
|
|
g_Vars.runstateindex++;
|
|
|
|
if (g_Vars.runstateindex >= g_Vars.numpropstates) {
|
|
g_Vars.runstateindex = 0;
|
|
}
|
|
|
|
runstateindex = g_Vars.runstateindex;
|
|
|
|
savedslotupdate240_60 = g_Vars.propstates[runstateindex].slotupdate240 + g_Vars.propstates[runstateindex].slotupdate60error;
|
|
g_Vars.propstates[runstateindex].slotupdate60error = (savedslotupdate240_60 & 3);
|
|
|
|
savedslotupdate240 = g_Vars.propstates[runstateindex].slotupdate240;
|
|
savedslotupdate240f = g_Vars.propstates[runstateindex].slotupdate240 / 4.0f;
|
|
|
|
savedslotupdate240_60 = (savedslotupdate240 + 2) >> 2;
|
|
|
|
for (i = 0; i < g_Vars.numpropstates; i++) {
|
|
g_Vars.propstates[i].propcount = 0;
|
|
g_Vars.propstates[i].chrpropcount = 0;
|
|
g_Vars.propstates[i].foregroundpropcount = 0;
|
|
g_Vars.propstates[i].foregroundchrpropcount = 0;
|
|
}
|
|
|
|
// Update the onscreen flags for all props
|
|
if (g_Vars.currentplayerindex == 0) {
|
|
// This is the first time propsTickPlayer has been called on this frame
|
|
prop = g_Vars.props;
|
|
end = &g_Vars.props[g_Vars.maxprops];
|
|
|
|
for (; prop < end; prop++) {
|
|
flags = prop->flags;
|
|
|
|
if (flags & PROPFLAG_ONTHISSCREENTHISTICK) {
|
|
flags &= ~PROPFLAG_ONTHISSCREENTHISTICK;
|
|
}
|
|
|
|
if (flags & PROPFLAG_ONANYSCREENTHISTICK) {
|
|
flags |= PROPFLAG_ONANYSCREENPREVTICK;
|
|
flags &= ~PROPFLAG_ONANYSCREENTHISTICK;
|
|
} else if (flags & PROPFLAG_ONANYSCREENPREVTICK) {
|
|
flags &= ~PROPFLAG_ONANYSCREENPREVTICK;
|
|
}
|
|
|
|
flags |= PROPFLAG_NOTYETTICKED;
|
|
|
|
prop->flags = flags;
|
|
}
|
|
} else {
|
|
// This is a subsequent call of propsTickPlayer on this frame
|
|
prop = g_Vars.props;
|
|
end = &g_Vars.props[g_Vars.maxprops];
|
|
|
|
for (; prop < end; prop++) {
|
|
flags = prop->flags;
|
|
|
|
if (flags & PROPFLAG_ONTHISSCREENTHISTICK) {
|
|
flags &= ~PROPFLAG_ONTHISSCREENTHISTICK;
|
|
}
|
|
|
|
prop->flags = flags;
|
|
}
|
|
}
|
|
|
|
// Iterate all active props, decide if they are in the foreground or
|
|
// background and decide if they should be ticked.
|
|
done = false;
|
|
if (1);
|
|
for (prop = g_Vars.activeprops; !done; ) {
|
|
op = TICKOP_NONE;
|
|
savednext = prop->next;
|
|
done = savednext == g_Vars.pausedprops;
|
|
|
|
// Tally up reasons for the prop to be in the foreground.
|
|
// The i variable is being reused as the score value.
|
|
// If the score is non-zero, the prop is a foreground prop.
|
|
i = 0;
|
|
|
|
// During normal gameplay, all props would be put in the foreground
|
|
// if g_Vars.alwaystick is set. But it never is.
|
|
// For other modes such as cutscenes, everything is in the foreground.
|
|
if (g_Vars.tickmode != TICKMODE_NORMAL) {
|
|
i++;
|
|
} else {
|
|
i += g_Vars.alwaystick;
|
|
}
|
|
|
|
// If the prop is in an on-screen room, it's in the foreground
|
|
rooms = prop->rooms;
|
|
|
|
while (*rooms != -1) {
|
|
if (g_Rooms[*rooms].flags & ROOMFLAG_ONSCREEN) {
|
|
i++;
|
|
}
|
|
|
|
rooms++;
|
|
}
|
|
|
|
if (i == 0) {
|
|
// The player and projectiles must always be in the foreground
|
|
if (prop->type == PROPTYPE_PLAYER) {
|
|
i++;
|
|
} else if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_WEAPON) {
|
|
obj = prop->obj;
|
|
|
|
if (obj->hidden & OBJHFLAG_PROJECTILE) {
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if (i == 0) {
|
|
// The prop will be in the foreground if it was on-screen on the
|
|
// previous tick, or if it explicitly needs to be ticked, or if
|
|
// it's in a standby room.
|
|
if ((prop->flags & (PROPFLAG_ENABLED | PROPFLAG_ONANYSCREENPREVTICK)) == (PROPFLAG_ENABLED | PROPFLAG_ONANYSCREENPREVTICK)) {
|
|
i++;
|
|
} else if (prop->forceonetick) {
|
|
i++;
|
|
prop->forceonetick = false;
|
|
} else if (prop->forcetick) {
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
prop->propupdate240 += g_Vars.lvupdate240;
|
|
|
|
if (i > 0) {
|
|
// The prop is in the foreground, so it must be ticked on this frame
|
|
if (prop->lastupdateframe != g_Vars.prevupdateframe) {
|
|
g_Vars.lvupdate240 = prop->propupdate240;
|
|
g_Vars.lvupdate60 = prop->propupdate240 + prop->propupdate60err;
|
|
prop->propupdate60err = g_Vars.lvupdate60 & 3;
|
|
g_Vars.lvupdate60 >>= 2;
|
|
g_Vars.lvupdate60f = g_Vars.lvupdate240 / 4.0f;
|
|
g_Vars.lvupdate60freal = PALUPF(g_Vars.lvupdate60f);
|
|
} else {
|
|
g_Vars.lvupdate240 = savedlvupdate240;
|
|
g_Vars.lvupdate60 = savedlvupdate60;
|
|
g_Vars.lvupdate60f = savedlvupdate60f;
|
|
g_Vars.lvupdate60freal = savedlvupdate60freal;
|
|
}
|
|
|
|
prop->backgroundedframes = 0;
|
|
|
|
// Tick the prop
|
|
if (prop->type == PROPTYPE_CHR) {
|
|
chr1 = prop->chr;
|
|
|
|
splatTickChr(prop);
|
|
|
|
if (chr1 && chr1->aibot) {
|
|
op = botTick(prop);
|
|
} else {
|
|
op = chrTick(prop);
|
|
}
|
|
|
|
g_Vars.propstates[prop->propstateindex].foregroundchrpropcount++;
|
|
} else {
|
|
g_Vars.propstates[prop->propstateindex].foregroundpropcount++;
|
|
|
|
if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_WEAPON || prop->type == PROPTYPE_DOOR) {
|
|
op = objTickPlayer(prop);
|
|
} else if (prop->type == PROPTYPE_EXPLOSION) {
|
|
op = explosionTickPlayer(prop);
|
|
} else if (prop->type == PROPTYPE_SMOKE) {
|
|
op = smokeTickPlayer(prop);
|
|
} else if (prop->type == PROPTYPE_PLAYER) {
|
|
splatTickChr(prop);
|
|
op = playerTickThirdPerson(prop);
|
|
}
|
|
}
|
|
|
|
if (prop->lastupdateframe != g_Vars.prevupdateframe) {
|
|
g_Vars.lvupdate240 = savedlvupdate240;
|
|
g_Vars.lvupdate60 = savedlvupdate60;
|
|
g_Vars.lvupdate60f = savedlvupdate60f;
|
|
g_Vars.lvupdate60freal = savedlvupdate60freal;
|
|
}
|
|
|
|
prop->lastupdateframe = g_Vars.updateframe;
|
|
prop->propupdate240 = 0;
|
|
prop->backgrounded = false;
|
|
} else {
|
|
// The prop is in the background. It should only be ticked on this
|
|
// frame if its propstate is the one being run on this frame.
|
|
if (prop->type == PROPTYPE_CHR) {
|
|
g_Vars.propstates[prop->propstateindex].chrpropcount++;
|
|
} else {
|
|
g_Vars.propstates[prop->propstateindex].propcount++;
|
|
}
|
|
|
|
if (prop->propstateindex == runstateindex) {
|
|
if (prop->lastupdateframe != g_Vars.propstates[runstateindex].lastupdateframe) {
|
|
g_Vars.lvupdate240 = prop->propupdate240;
|
|
g_Vars.lvupdate60 = prop->propupdate240 + prop->propupdate60err;
|
|
prop->propupdate60err = g_Vars.lvupdate60 & 3;
|
|
g_Vars.lvupdate60 >>= 2;
|
|
g_Vars.lvupdate60f = g_Vars.lvupdate240 / 4.0f;
|
|
g_Vars.lvupdate60freal = PALUPF(g_Vars.lvupdate60f);
|
|
} else {
|
|
g_Vars.lvupdate240 = savedslotupdate240;
|
|
g_Vars.lvupdate60 = savedslotupdate240_60;
|
|
g_Vars.lvupdate60f = savedslotupdate240f;
|
|
vars->lvupdate60freal = PALUPF(savedslotupdate240f);
|
|
}
|
|
|
|
// Tick the prop
|
|
if (prop->type == PROPTYPE_CHR) {
|
|
chr2 = prop->chr;
|
|
|
|
splatTickChr(prop);
|
|
|
|
if (chr2 && chr2->aibot) {
|
|
op = botTick(prop);
|
|
} else {
|
|
op = chrTick(prop);
|
|
}
|
|
} else if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_WEAPON || prop->type == PROPTYPE_DOOR) {
|
|
obj = prop->obj;
|
|
|
|
if (!g_PausableObjs[obj->type]) {
|
|
op = objTickPlayer(prop);
|
|
} else if (prop->timetoregen <= 0) {
|
|
// The prop does not regenerate. If we've done a full
|
|
// cycle of propstates while backgrounded and the prop
|
|
// hasn't moved to the foreground, pause it.
|
|
prop->backgroundedframes++;
|
|
|
|
if (prop->backgroundedframes > g_Vars.numpropstates - 1) {
|
|
propPause(prop);
|
|
op = TICKOP_CHANGEDLIST;
|
|
}
|
|
}
|
|
} else if (prop->type == PROPTYPE_EXPLOSION) {
|
|
op = explosionTickPlayer(prop);
|
|
} else if (prop->type == PROPTYPE_SMOKE) {
|
|
op = smokeTickPlayer(prop);
|
|
} else if (prop->type == PROPTYPE_PLAYER) {
|
|
splatTickChr(prop);
|
|
op = playerTickThirdPerson(prop);
|
|
}
|
|
|
|
if (prop->lastupdateframe != g_Vars.propstates[runstateindex].lastupdateframe) {
|
|
g_Vars.lvupdate240 = savedslotupdate240;
|
|
g_Vars.lvupdate60 = savedslotupdate240_60;
|
|
g_Vars.lvupdate60f = savedslotupdate240f;
|
|
vars->lvupdate60freal = PALUPF(savedslotupdate240f);
|
|
}
|
|
|
|
prop->lastupdateframe = g_Vars.updateframe;
|
|
prop->propupdate240 = 0;
|
|
prop->backgrounded = true;
|
|
}
|
|
}
|
|
|
|
g_Vars.lvupdate240 = savedlvupdate240;
|
|
g_Vars.lvupdate60 = savedlvupdate60;
|
|
g_Vars.lvupdate60f = savedlvupdate60f;
|
|
g_Vars.lvupdate60freal = savedlvupdate60freal;
|
|
|
|
if (op == TICKOP_CHANGEDLIST) {
|
|
// The prop has changed from active to paused, which means its
|
|
// `next` pointer now points to a paused prop and must not be used.
|
|
// Use the prop->next value that was taken before it ticked.
|
|
next = savednext;
|
|
} else {
|
|
// Use the current prop->next value
|
|
next = prop->next;
|
|
done = next == g_Vars.pausedprops;
|
|
|
|
if (op == TICKOP_RETICK) {
|
|
prop->lastupdateframe = 0xffff;
|
|
prop->forceonetick = true;
|
|
|
|
// Delisting and activating it again appends it to the active
|
|
// props list, which means it'll get ticked again on this frame.
|
|
propDelist(prop);
|
|
propActivateThisFrame(prop);
|
|
|
|
if (done) {
|
|
next = prop;
|
|
done = false;
|
|
}
|
|
} else {
|
|
propExecuteTickOperation(prop, op);
|
|
}
|
|
}
|
|
|
|
prop = next;
|
|
}
|
|
|
|
g_Vars.lvupdate240 = savedlvupdate240;
|
|
g_Vars.lvupdate60 = savedlvupdate60;
|
|
g_Vars.lvupdate60f = savedlvupdate60f;
|
|
g_Vars.lvupdate60freal = savedlvupdate60freal;
|
|
|
|
g_Vars.propstates[runstateindex].slotupdate240 = 0;
|
|
g_Vars.propstates[runstateindex].lastupdateframe = g_Vars.updateframe;
|
|
|
|
if (islastplayer) {
|
|
alarmTick();
|
|
propsndTick();
|
|
propsDefragRoomProps();
|
|
}
|
|
|
|
chr0f02472c();
|
|
}
|
|
|
|
void propSetPerimEnabled(struct prop *prop, s32 enable)
|
|
{
|
|
if (prop->type == PROPTYPE_CHR) {
|
|
chrSetPerimEnabled(prop->chr, enable);
|
|
} else if (prop->type == PROPTYPE_PLAYER) {
|
|
playerSetPerimEnabled(prop, enable);
|
|
} else if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_DOOR || prop->type == PROPTYPE_WEAPON) {
|
|
objSetPerimEnabled(prop, enable);
|
|
}
|
|
}
|
|
|
|
void propsTestForPickup(void)
|
|
{
|
|
s16 *propnumptr;
|
|
s32 i;
|
|
s16 propnums[256];
|
|
s16 allrooms[21];
|
|
s16 tmp[11];
|
|
|
|
if (g_Vars.currentplayer->bondmovemode != MOVEMODE_CUTSCENE
|
|
&& !g_PlayerInvincible
|
|
&& g_Vars.currentplayer != g_Vars.anti) {
|
|
roomsCopy(g_Vars.currentplayer->prop->rooms, allrooms);
|
|
|
|
for (i = 0; g_Vars.currentplayer->prop->rooms[i] != -1; i++) {
|
|
roomGetNeighbours(g_Vars.currentplayer->prop->rooms[i], tmp, 10);
|
|
roomsAppend(tmp, allrooms, 20);
|
|
}
|
|
|
|
roomGetProps(allrooms, propnums, 256);
|
|
propnumptr = propnums;
|
|
|
|
while (*propnumptr >= 0) {
|
|
struct prop *prop = &g_Vars.props[*propnumptr];
|
|
s32 op = TICKOP_NONE;
|
|
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
if (prop->timetoregen <= 0 && prop->obj)
|
|
#else
|
|
if (prop->timetoregen <= 0)
|
|
#endif
|
|
{
|
|
switch (prop->type) {
|
|
case PROPTYPE_OBJ:
|
|
op = objTestForPickup(prop);
|
|
break;
|
|
case PROPTYPE_WEAPON:
|
|
op = weaponTestForPickup(prop);
|
|
break;
|
|
case PROPTYPE_DOOR:
|
|
case PROPTYPE_CHR:
|
|
case PROPTYPE_PLAYER:
|
|
case PROPTYPE_EXPLOSION:
|
|
case PROPTYPE_SMOKE:
|
|
break;
|
|
}
|
|
}
|
|
|
|
propExecuteTickOperation(prop, op);
|
|
|
|
propnumptr++;
|
|
}
|
|
}
|
|
}
|
|
|
|
f32 func0f06438c(struct prop *prop, struct coord *arg1, f32 *arg2, f32 *arg3, f32 *arg4, bool throughobjects, bool cangangsta, s32 arg7)
|
|
{
|
|
f32 spa0[2];
|
|
struct coord sp94;
|
|
f32 sp8c[2];
|
|
f32 sp84[2];
|
|
f32 sp7c[2];
|
|
f32 sp74[2];
|
|
f32 sp70;
|
|
f32 sp6c;
|
|
f32 top;
|
|
f32 bottom;
|
|
f32 left;
|
|
f32 right;
|
|
f32 result = -2;
|
|
struct weaponfunc *func = currentPlayerGetWeaponFunction(HAND_RIGHT);
|
|
bool sp50 = arg7;
|
|
bool sp4c;
|
|
f32 sp48;
|
|
struct prop *playerprop;
|
|
s32 ok;
|
|
|
|
if (func && bgun0f0a27c8()) {
|
|
sp50 = true;
|
|
}
|
|
|
|
if (sp50) {
|
|
top = camGetScreenTop();
|
|
bottom = camGetScreenTop() + camGetScreenHeight();
|
|
left = camGetScreenLeft();
|
|
right = camGetScreenLeft() + camGetScreenWidth();
|
|
} else {
|
|
top = camGetScreenTop() + camGetScreenHeight() * 0.175f;
|
|
bottom = camGetScreenTop() + camGetScreenHeight() * 0.825f;
|
|
left = camGetScreenLeft() + camGetScreenWidth() * 0.25f;
|
|
right = camGetScreenLeft() + camGetScreenWidth() * 0.75f;
|
|
}
|
|
|
|
if (arg1->z > -2.5f) {
|
|
return -1;
|
|
}
|
|
|
|
cam0f0b4d04(arg1, spa0);
|
|
sp94.x = arg2[0];
|
|
sp94.y = arg1->y;
|
|
sp94.z = arg1->z;
|
|
cam0f0b4d04(&sp94, sp8c);
|
|
sp94.x = arg2[1];
|
|
sp94.y = arg1->y;
|
|
sp94.z = arg1->z;
|
|
cam0f0b4d04(&sp94, sp84);
|
|
sp94.x = arg1->x;
|
|
sp94.y = arg3[1];
|
|
sp94.z = arg1->z;
|
|
cam0f0b4d04(&sp94, sp7c);
|
|
sp94.x = arg1->x;
|
|
sp94.y = arg3[0];
|
|
sp94.z = arg1->z;
|
|
cam0f0b4d04(&sp94, sp74);
|
|
|
|
if (sp74[1] >= top && bottom >= sp7c[1]) {
|
|
sp4c = false;
|
|
bgunGetCrossPos(&sp70, &sp6c);
|
|
sp8c[0] = floorf(sp8c[0]);
|
|
sp84[0] = ceilf(sp84[0]);
|
|
|
|
if (bmoveIsAutoAimXEnabledForCurrentWeapon() || cangangsta) {
|
|
if (sp8c[0] <= right && left <= sp84[0]) {
|
|
sp48 = (sp84[0] - sp8c[0]) * 1.5f;
|
|
|
|
if (!g_Vars.normmplayerisrunning) {
|
|
sp48 = sp48 * g_AutoAimScale;
|
|
}
|
|
|
|
sp4c = camGetScreenLeft() + 0.5f * camGetScreenWidth() >= (sp8c[0] + sp84[0]) * 0.5f - sp48
|
|
&& camGetScreenLeft() + 0.5f * camGetScreenWidth() <= (sp8c[0] + sp84[0]) * 0.5f + sp48
|
|
&& left <= spa0[0]
|
|
&& right >= spa0[0];
|
|
}
|
|
} else {
|
|
sp4c = sp8c[0] <= sp70 && sp70 <= sp84[0];
|
|
}
|
|
|
|
if (sp4c) {
|
|
playerprop = g_Vars.currentplayer->prop;
|
|
|
|
playerSetPerimEnabled(playerprop, false);
|
|
|
|
if (throughobjects) {
|
|
ok = cdTestLos03(&playerprop->pos, playerprop->rooms, &prop->pos,
|
|
CDTYPE_DOORS | CDTYPE_PATHBLOCKER | CDTYPE_BG,
|
|
GEOFLAG_BLOCK_SHOOT);
|
|
} else {
|
|
ok = cdTestLos03(&playerprop->pos, playerprop->rooms, &prop->pos,
|
|
CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_PATHBLOCKER | CDTYPE_BG,
|
|
GEOFLAG_BLOCK_SHOOT);
|
|
}
|
|
|
|
if (ok) {
|
|
f32 value = spa0[1];
|
|
|
|
if (value < top) {
|
|
value = top;
|
|
} else if (value > bottom) {
|
|
value = bottom;
|
|
}
|
|
|
|
arg4[1] = value;
|
|
|
|
if (bmoveIsAutoAimXEnabledForCurrentWeapon() || cangangsta) {
|
|
f32 value = spa0[0];
|
|
|
|
if (value < left) {
|
|
value = left;
|
|
} else if (value > right) {
|
|
value = right;
|
|
}
|
|
|
|
arg4[0] = value;
|
|
}
|
|
|
|
if (camGetScreenLeft() + 0.5f * camGetScreenWidth() >= sp8c[0]
|
|
&& camGetScreenLeft() + 0.5f * camGetScreenWidth() <= sp84[0]) {
|
|
result = 1;
|
|
} else if (camGetScreenLeft() + 0.5f * camGetScreenWidth() >= sp8c[0]) {
|
|
result = 1 - ((camGetScreenLeft() + 0.5f * camGetScreenWidth()) - sp84[0]) / sp48;
|
|
} else {
|
|
result = 1 - (sp8c[0] - (camGetScreenLeft() + 0.5f * camGetScreenWidth())) / sp48;
|
|
}
|
|
}
|
|
|
|
playerSetPerimEnabled(playerprop, true);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void farsightChooseTarget(void)
|
|
{
|
|
struct prop *besttarget = NULL;
|
|
f32 bestthing = 1;
|
|
f32 bestdist = -1;
|
|
s32 weaponnum = bgunGetWeaponNum(HAND_RIGHT);
|
|
s32 i;
|
|
|
|
if (weaponnum == WEAPON_FARSIGHT) {
|
|
s32 numchrs = chrsGetNumSlots();
|
|
|
|
for (i = numchrs - 1; i >= 0; i--) {
|
|
struct prop *prop = g_ChrSlots[i].prop;
|
|
|
|
if (prop && prop->chr) {
|
|
if ((prop->type == PROPTYPE_CHR && (prop->flags & PROPFLAG_ENABLED))
|
|
|| (prop->type == PROPTYPE_PLAYER && playermgrGetPlayerNumByProp(prop) != g_Vars.currentplayernum)) {
|
|
struct chrdata *chr = prop->chr;
|
|
|
|
if ((chr->chrflags & CHRCFLAG_UNEXPLODABLE) == 0
|
|
&& !chrCompareTeams(g_Vars.currentplayer->prop->chr, chr, COMPARE_FRIENDS)
|
|
&& chr->actiontype != ACT_DIE
|
|
&& chr->actiontype != ACT_DRUGGEDDROP
|
|
&& chr->actiontype != ACT_DRUGGEDKO
|
|
&& chr->actiontype != ACT_DEAD
|
|
&& (chr->hidden & CHRHFLAG_CLOAKED) == 0
|
|
&& (prop->type != PROPTYPE_PLAYER || !g_Vars.players[playermgrGetPlayerNumByProp(prop)]->isdead)) {
|
|
f32 xdist = g_Vars.currentplayer->bond2.unk10.x - prop->pos.x;
|
|
f32 ydist = g_Vars.currentplayer->bond2.unk10.y - prop->pos.y;
|
|
f32 zdist = g_Vars.currentplayer->bond2.unk10.z - prop->pos.z;
|
|
|
|
f32 dist = sqrtf(xdist * xdist + ydist * ydist + zdist * zdist);
|
|
|
|
if (dist > 0) {
|
|
f32 thing = (xdist * g_Vars.currentplayer->bond2.unk1c.f[0]
|
|
+ ydist * g_Vars.currentplayer->bond2.unk1c.f[1]
|
|
+ zdist * g_Vars.currentplayer->bond2.unk1c.f[2]) / dist;
|
|
|
|
if (thing < 0 && thing < bestthing) {
|
|
bestthing = thing;
|
|
besttarget = prop;
|
|
bestdist = dist;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
g_Vars.currentplayer->autoeraserdist = bestdist;
|
|
g_Vars.currentplayer->autoerasertarget = besttarget;
|
|
}
|
|
|
|
void autoaimTick(void)
|
|
{
|
|
struct prop *bestprop = NULL;
|
|
f32 aimpos[2] = {0, 0};
|
|
bool isclose = false;
|
|
bool cangangsta = weaponHasFlag(bgunGetWeaponNum(HAND_RIGHT), WEAPONFLAG_GANGSTA);
|
|
bool iscmpsec = false;
|
|
struct weaponfunc *func = currentPlayerGetWeaponFunction(HAND_RIGHT);
|
|
s32 i;
|
|
|
|
if (func && (func->type & 0xff) == INVENTORYFUNCTYPE_CLOSE) {
|
|
isclose = true;
|
|
}
|
|
|
|
farsightChooseTarget();
|
|
|
|
if (bgunGetWeaponNum(HAND_RIGHT) == WEAPON_CMP150
|
|
&& g_Vars.currentplayer->hands[HAND_RIGHT].gset.weaponfunc == FUNC_SECONDARY) {
|
|
iscmpsec = true;
|
|
}
|
|
|
|
if (iscmpsec) {
|
|
// For CMP on secondary mode, find the first prop that is within the aim limits
|
|
for (i = 0; i < ARRAYCOUNT(g_Vars.currentplayer->trackedprops); i++) {
|
|
struct trackedprop *trackedprop = &g_Vars.currentplayer->trackedprops[i];
|
|
|
|
if (trackedprop->prop
|
|
&& (trackedprop->x1 >= 0 || trackedprop->x2 >= 0)
|
|
&& (trackedprop->y1 >= 0 || trackedprop->y2 >= 0)) {
|
|
// Define the aim limits
|
|
f32 top = camGetScreenTop() + camGetScreenHeight() * 0.125f;
|
|
f32 bottom = camGetScreenTop() + camGetScreenHeight() * 0.875f;
|
|
f32 left = camGetScreenLeft() + camGetScreenWidth() * 0.125f;
|
|
f32 right = camGetScreenLeft() + camGetScreenWidth() * 0.875f;
|
|
struct chrdata *chr = NULL;
|
|
|
|
bestprop = trackedprop->prop;
|
|
|
|
if (bestprop->type == PROPTYPE_OBJ
|
|
|| bestprop->type == PROPTYPE_WEAPON
|
|
|| bestprop->type == PROPTYPE_DOOR) {
|
|
// trackedprop is an object
|
|
aimpos[0] = (trackedprop->x2 + trackedprop->x1) / 2;
|
|
aimpos[1] = (trackedprop->y2 + trackedprop->y1) / 2;
|
|
|
|
if (bestprop->flags & PROPFLAG_ONTHISSCREENTHISTICK) {
|
|
struct defaultobj *obj = bestprop->obj;
|
|
Mtxf *mtx = model0001a60c(obj->model);
|
|
struct coord spac;
|
|
spac.z = mtx->m[3][2];
|
|
|
|
if (spac.z < 0) {
|
|
spac.x = mtx->m[3][0];
|
|
spac.y = mtx->m[3][1];
|
|
cam0f0b4d04(&spac, aimpos);
|
|
}
|
|
}
|
|
} else {
|
|
// trackedprop is a chr
|
|
chr = bestprop->chr;
|
|
aimpos[0] = (trackedprop->x2 + trackedprop->x1) / 2;
|
|
|
|
// Aim 2/3 up the chr, so about their chest
|
|
aimpos[1] = (trackedprop->y2 + trackedprop->y1 * 2) / 3;
|
|
}
|
|
|
|
// Constrain aimpos to the aim limits
|
|
if (aimpos[0] > right) {
|
|
aimpos[0] = right;
|
|
}
|
|
|
|
if (aimpos[0] < left) {
|
|
aimpos[0] = left;
|
|
}
|
|
|
|
if (aimpos[1] > bottom) {
|
|
aimpos[1] = bottom;
|
|
}
|
|
|
|
if (aimpos[1] < top) {
|
|
aimpos[1] = top;
|
|
}
|
|
|
|
if (aimpos[0] > trackedprop->x2
|
|
|| aimpos[0] < trackedprop->x1
|
|
|| aimpos[1] > trackedprop->y2
|
|
|| aimpos[1] < trackedprop->y1) {
|
|
bestprop = NULL;
|
|
aimpos[0] = aimpos[1] = 0;
|
|
}
|
|
}
|
|
|
|
if (bestprop) {
|
|
break;
|
|
}
|
|
}
|
|
} else if ((bmoveIsAutoAimYEnabledForCurrentWeapon()
|
|
|| bmoveIsAutoAimXEnabledForCurrentWeapon()
|
|
|| cangangsta) && !isclose) {
|
|
// Standard auto aim
|
|
f32 bestthing = -1;
|
|
struct prop *prop;
|
|
struct coord sp94;
|
|
f32 sp8c[2];
|
|
f32 sp84[2];
|
|
struct chrdata *chr;
|
|
f32 sp78[2];
|
|
struct prop **ptr = g_Vars.endonscreenprops - 1;
|
|
|
|
// Iterate onscreen props near to far
|
|
while (ptr >= g_Vars.onscreenprops) {
|
|
prop = *ptr;
|
|
|
|
if (prop && prop->chr) {
|
|
if (prop->type == PROPTYPE_CHR
|
|
|| (prop->type == PROPTYPE_PLAYER && playermgrGetPlayerNumByProp(prop) != g_Vars.currentplayernum)) {
|
|
chr = prop->chr;
|
|
|
|
if (!chrCompareTeams(g_Vars.currentplayer->prop->chr, chr, COMPARE_FRIENDS)
|
|
&& (chrGetHeldProp(chr, HAND_RIGHT)
|
|
|| chrGetHeldProp(chr, HAND_LEFT)
|
|
|| (chr->chrflags & CHRCFLAG_FORCEAUTOAIM)
|
|
|| chr->gunprop)
|
|
&& chrCalculateAutoAim(prop, &sp94, sp8c, sp84)) {
|
|
f32 thing = func0f06438c(prop, &sp94, sp8c, sp84, sp78, false, cangangsta, 0);
|
|
|
|
if (thing > bestthing) {
|
|
bestthing = thing;
|
|
aimpos[0] = sp78[0];
|
|
aimpos[1] = sp78[1];
|
|
bestprop = prop;
|
|
|
|
if (thing >= 1) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ptr--;
|
|
}
|
|
}
|
|
|
|
if (bestprop) {
|
|
if (bmoveIsAutoAimYEnabledForCurrentWeapon() || iscmpsec) {
|
|
bmoveUpdateAutoAimYProp(bestprop, (aimpos[1] - camGetScreenTop()) / (camGetScreenHeight() * 0.5f) - 1);
|
|
}
|
|
|
|
if (bmoveIsAutoAimXEnabledForCurrentWeapon() || iscmpsec) {
|
|
bmoveUpdateAutoAimXProp(bestprop, (aimpos[0] - camGetScreenLeft()) / (camGetScreenWidth() * 0.5f) - 1);
|
|
}
|
|
|
|
if (cangangsta) {
|
|
f32 xdist = g_Vars.currentplayer->bond2.unk10.x - bestprop->pos.x;
|
|
f32 ydist = g_Vars.currentplayer->bond2.unk10.y - bestprop->pos.y;
|
|
f32 zdist = g_Vars.currentplayer->bond2.unk10.z - bestprop->pos.z;
|
|
f32 dist = sqrtf(xdist * xdist + ydist * ydist + zdist * zdist);
|
|
|
|
if (dist < 200) {
|
|
g_Vars.currentplayer->gunctrl.gangsta = true;
|
|
} else {
|
|
g_Vars.currentplayer->gunctrl.gangsta = false;
|
|
}
|
|
} else {
|
|
g_Vars.currentplayer->gunctrl.gangsta = false;
|
|
}
|
|
} else {
|
|
u32 stack;
|
|
bmoveUpdateAutoAimYProp(NULL, 0);
|
|
bmoveUpdateAutoAimXProp(NULL, 0);
|
|
|
|
g_Vars.currentplayer->gunctrl.gangsta = false;
|
|
}
|
|
}
|
|
|
|
u32 propDoorGetCdTypes(struct prop *prop)
|
|
{
|
|
struct doorobj *door = prop->door;
|
|
u32 types;
|
|
|
|
if (door->frac <= 0) {
|
|
types = CDTYPE_CLOSEDDOORS;
|
|
} else if (door->frac >= door->maxfrac) {
|
|
types = CDTYPE_OPENDOORS;
|
|
} else {
|
|
types = CDTYPE_AJARDOORS;
|
|
}
|
|
|
|
if (door->base.flags2 & OBJFLAG2_AICANNOTUSE) {
|
|
types |= CDTYPE_DOORSLOCKEDTOAI;
|
|
}
|
|
|
|
return types;
|
|
}
|
|
|
|
bool propIsOfCdType(struct prop *prop, u32 types)
|
|
{
|
|
bool result = true;
|
|
|
|
if (prop->type == PROPTYPE_DOOR) {
|
|
if (types & CDTYPE_AIOPAQUE) {
|
|
struct defaultobj *obj = prop->obj;
|
|
|
|
if (obj->flags & OBJFLAG_AISEETHROUGH) {
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
if (types & CDTYPE_DOORSWITHOUTFLAG) {
|
|
struct defaultobj *obj = prop->obj;
|
|
|
|
if (obj->flags3 & OBJFLAG3_80000000) {
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
if ((types & CDTYPE_DOORS) == 0) {
|
|
if ((propDoorGetCdTypes(prop) & types) == 0) {
|
|
result = false;
|
|
}
|
|
}
|
|
} else if (prop->type == PROPTYPE_PLAYER) {
|
|
if ((types & CDTYPE_PLAYERS) == 0) {
|
|
result = false;
|
|
} else {
|
|
struct player *player = g_Vars.players[playermgrGetPlayerNumByProp(prop)];
|
|
|
|
if (!player->bondperimenabled || (g_Vars.mplayerisrunning && player->isdead)) {
|
|
result = false;
|
|
}
|
|
}
|
|
} else if (prop->type == PROPTYPE_CHR) {
|
|
if ((types & CDTYPE_CHRS) == 0) {
|
|
result = false;
|
|
} else {
|
|
struct chrdata *chr = prop->chr;
|
|
|
|
if (chr->actiontype == ACT_DEAD
|
|
|| (chr->chrflags & (CHRCFLAG_HIDDEN | CHRCFLAG_PERIMDISABLEDTMP))
|
|
|| (chr->hidden & CHRHFLAG_PERIMDISABLED)) {
|
|
result = false;
|
|
}
|
|
}
|
|
} else if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_WEAPON) {
|
|
struct defaultobj *obj = prop->obj;
|
|
|
|
if (obj->unkgeo == NULL) {
|
|
result = false;
|
|
} else {
|
|
if ((types & CDTYPE_AIOPAQUE) && (obj->flags & OBJFLAG_AISEETHROUGH)) {
|
|
result = false;
|
|
}
|
|
|
|
if ((types & CDTYPE_OBJSWITHFLAG)
|
|
&& (obj->flags & OBJFLAG_INVINCIBLE) == 0
|
|
&& (obj->flags2 & OBJFLAG2_IMMUNETOGUNFIRE) == 0) {
|
|
result = false;
|
|
}
|
|
|
|
if ((types & CDTYPE_OBJSWITHFLAG2)
|
|
&& (obj->flags & OBJFLAG_INVINCIBLE) == 0
|
|
&& (obj->flags2 & OBJFLAG2_00200000) == 0) {
|
|
result = false;
|
|
}
|
|
|
|
if ((obj->flags & OBJFLAG_PATHBLOCKER)) {
|
|
if ((types & CDTYPE_PATHBLOCKER) == 0) {
|
|
result = false;
|
|
}
|
|
} else {
|
|
if ((types & CDTYPE_OBJS) == 0) {
|
|
result = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void roomsCopy(s16 *src, s16 *dst)
|
|
{
|
|
s16 *srcptr = src;
|
|
s16 *dstptr = dst;
|
|
s32 val;
|
|
|
|
while ((val = *srcptr) != -1) {
|
|
*dstptr = val;
|
|
srcptr++;
|
|
dstptr++;
|
|
}
|
|
|
|
*dstptr = -1;
|
|
}
|
|
|
|
/**
|
|
* Append newrooms to dstrooms without duplicates.
|
|
*/
|
|
void roomsAppend(s16 *newrooms, s16 *dstrooms, s32 maxlen)
|
|
{
|
|
s32 i;
|
|
|
|
for (i = 0; newrooms[i] != -1; i++) {
|
|
s32 j;
|
|
|
|
for (j = 0; dstrooms[j] != -1 && dstrooms[j] != newrooms[i]; j++);
|
|
|
|
if (dstrooms[j] == -1 && j < maxlen) {
|
|
dstrooms[j] = newrooms[i];
|
|
dstrooms[j + 1] = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool arrayIntersects(s16 *a, s16 *b)
|
|
{
|
|
s16 *aptr = a;
|
|
s16 aval = *aptr;
|
|
s16 *bptr;
|
|
s16 bval;
|
|
|
|
while (aval != -1) {
|
|
bptr = b; bval = *bptr;
|
|
|
|
while (bval != -1) {
|
|
if (aval == bval) {
|
|
return true;
|
|
}
|
|
|
|
bptr++;
|
|
bval = *bptr;
|
|
}
|
|
|
|
aptr++;
|
|
aval = *aptr;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool propTryAddToChunk(s16 propnum, s32 chunkindex)
|
|
{
|
|
s32 i;
|
|
|
|
for (i = 0; i < 7; i++) {
|
|
if (g_RoomPropListChunks[chunkindex].propnums[i] < 0) {
|
|
g_RoomPropListChunks[chunkindex].propnums[i] = propnum;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
s32 roomAllocatePropListChunk(s32 room, s32 prevchunkindex)
|
|
{
|
|
s32 i;
|
|
s32 j;
|
|
|
|
for (i = 0; i < 256; i++) {
|
|
if (g_RoomPropListChunks[i].propnums[0] == -2) {
|
|
for (j = 0; j < 8; j++) {
|
|
g_RoomPropListChunks[i].propnums[j] = -1;
|
|
}
|
|
|
|
if (prevchunkindex >= 0) {
|
|
g_RoomPropListChunks[prevchunkindex].propnums[7] = i;
|
|
} else {
|
|
g_RoomPropListChunkIndexes[room] = i;
|
|
}
|
|
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
void propRegisterRoom(struct prop *prop, s16 room)
|
|
{
|
|
s32 prev = -1;
|
|
s32 i;
|
|
|
|
if (room >= 0 && room < g_Vars.roomcount) {
|
|
// Find which chunk to start at
|
|
s32 chunkindex = g_RoomPropListChunkIndexes[room];
|
|
s16 propnum = prop - g_Vars.props;
|
|
|
|
for (i = 0; chunkindex >= 0; i++) {
|
|
if (propTryAddToChunk(propnum, chunkindex)) {
|
|
return;
|
|
}
|
|
|
|
prev = chunkindex;
|
|
chunkindex = g_RoomPropListChunks[chunkindex].propnums[7];
|
|
}
|
|
|
|
// Allocate a new chunk
|
|
chunkindex = roomAllocatePropListChunk(room, prev);
|
|
|
|
if (chunkindex >= 0) {
|
|
propTryAddToChunk(propnum, chunkindex);
|
|
}
|
|
}
|
|
}
|
|
|
|
void propDeregisterRoom(struct prop *prop, s16 room)
|
|
{
|
|
bool removed = false;
|
|
s32 prev = -1;
|
|
|
|
if (room >= 0 && room < g_Vars.roomcount) {
|
|
// Find which chunk to start at
|
|
s32 chunkindex = g_RoomPropListChunkIndexes[room];
|
|
s16 propnum = prop - g_Vars.props;
|
|
|
|
while (chunkindex >= 0) {
|
|
bool populated = false;
|
|
s32 j;
|
|
|
|
// Iterate propnums in this chunk
|
|
for (j = 0; j < 7; j++) {
|
|
if (g_RoomPropListChunks[chunkindex].propnums[j] == propnum) {
|
|
g_RoomPropListChunks[chunkindex].propnums[j] = -1;
|
|
removed = true;
|
|
} else if (!populated && g_RoomPropListChunks[chunkindex].propnums[j] >= 0) {
|
|
populated = true;
|
|
}
|
|
}
|
|
|
|
if (!populated) {
|
|
// This chunk is empty, so it can be marked as available
|
|
g_RoomPropListChunks[chunkindex].propnums[0] = -2;
|
|
|
|
if (prev >= 0) {
|
|
g_RoomPropListChunks[prev].propnums[7] = g_RoomPropListChunks[chunkindex].propnums[7];
|
|
} else {
|
|
g_RoomPropListChunkIndexes[room] = g_RoomPropListChunks[chunkindex].propnums[7];
|
|
}
|
|
} else {
|
|
prev = chunkindex;
|
|
}
|
|
|
|
if (removed) {
|
|
return;
|
|
}
|
|
|
|
chunkindex = g_RoomPropListChunks[chunkindex].propnums[7];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove a prop from the room registration system.
|
|
* The prop's rooms list is unchanged.
|
|
*
|
|
* Room registration is used to look up props by room number.
|
|
*/
|
|
void propDeregisterRooms(struct prop *prop)
|
|
{
|
|
s16 *rooms = prop->rooms;
|
|
s16 room = *rooms;
|
|
|
|
while (room != -1) {
|
|
propDeregisterRoom(prop, room);
|
|
rooms++;
|
|
room = *rooms;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a prop to the room registration system based on its rooms list.
|
|
*
|
|
* Room registration is used to look up props by room number.
|
|
*/
|
|
void propRegisterRooms(struct prop *prop)
|
|
{
|
|
s16 *rooms = prop->rooms;
|
|
s16 room = *rooms;
|
|
|
|
while (room != -1) {
|
|
propRegisterRoom(prop, room);
|
|
rooms++;
|
|
room = *rooms;
|
|
}
|
|
}
|
|
|
|
void func0f065d1c(struct coord *pos, s16 *rooms, struct coord *newpos, s16 *newrooms, s16 *morerooms, u32 arg5)
|
|
{
|
|
s16 stackrooms[8];
|
|
s32 index;
|
|
s32 i;
|
|
|
|
portal00018148(pos, newpos, rooms, stackrooms, morerooms, arg5);
|
|
|
|
index = 0;
|
|
|
|
for (i = 0; stackrooms[i] != -1; i++) {
|
|
if (roomContainsCoord(newpos, stackrooms[i])) {
|
|
newrooms[index] = stackrooms[i];
|
|
index++;
|
|
}
|
|
}
|
|
|
|
newrooms[index] = -1;
|
|
}
|
|
|
|
void func0f065dd8(struct coord *pos, s16 *rooms, struct coord *newpos, s16 *newrooms)
|
|
{
|
|
func0f065d1c(pos, rooms, newpos, newrooms, NULL, 0);
|
|
}
|
|
|
|
void func0f065dfc(struct coord *pos, s16 *rooms, struct coord *newpos, s16 *newrooms, s16 *morerooms, u32 arg5)
|
|
{
|
|
func0f065d1c(pos, rooms, newpos, newrooms, morerooms, arg5);
|
|
|
|
if (newrooms[0] == -1) {
|
|
func0f065e98(pos, rooms, newpos, newrooms);
|
|
|
|
if (morerooms) {
|
|
roomsAppend(newrooms, morerooms, arg5);
|
|
}
|
|
}
|
|
}
|
|
|
|
void func0f065e74(struct coord *pos, s16 *rooms, struct coord *newpos, s16 *newrooms)
|
|
{
|
|
func0f065dfc(pos, rooms, newpos, newrooms, NULL, 0);
|
|
}
|
|
|
|
void func0f065e98(struct coord *pos, s16 *rooms, struct coord *pos2, s16 *dstrooms)
|
|
{
|
|
s16 inrooms[21];
|
|
s16 aboverooms[21];
|
|
s16 *ptr = NULL;
|
|
s32 i;
|
|
|
|
bgFindRoomsByPos(pos2, inrooms, aboverooms, 20, NULL);
|
|
|
|
if (inrooms[0] != -1) {
|
|
ptr = inrooms;
|
|
} else if (aboverooms[0] != -1) {
|
|
ptr = aboverooms;
|
|
}
|
|
|
|
if (ptr) {
|
|
s32 room = cdFindFloorRoomAtPos(pos2, ptr);
|
|
|
|
if (room > 0) {
|
|
dstrooms[0] = room;
|
|
dstrooms[1] = -1;
|
|
} else {
|
|
dstrooms[0] = *ptr;
|
|
dstrooms[1] = -1;
|
|
}
|
|
} else {
|
|
for (i = 0; rooms[i] != -1; i++) {
|
|
dstrooms[i] = rooms[i];
|
|
}
|
|
|
|
dstrooms[i] = -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given a list of rooms (terminated by -1), and a pointer to an empty
|
|
* allocation of propnums, populate the propnums list based on which props are
|
|
* in any of those rooms.
|
|
*
|
|
* @dangerous: The len argument is ignored, so array overflow will occur if
|
|
* there are too many props in a small area. In most (if not all) invocations,
|
|
* the caller uses an array that can hold 256 propnums, so if you can find a way
|
|
* to get 256 props in a small space without exhausing the memory of the
|
|
* console, you could potentially achieve arbitrary code execution.
|
|
*/
|
|
void roomGetProps(s16 *rooms, s16 *propnums, s32 len)
|
|
{
|
|
s16 *writeptr = propnums;
|
|
s32 room;
|
|
s32 i;
|
|
s32 j;
|
|
|
|
room = *rooms;
|
|
|
|
// Iterate rooms
|
|
while (room != -1) {
|
|
// Find the chunk to start at
|
|
s32 chunkindex = g_RoomPropListChunkIndexes[room];
|
|
|
|
// Iterate the chunks
|
|
while (chunkindex >= 0) {
|
|
// Iterate the propnums within each chunk
|
|
for (i = 0; i < 7; i++) {
|
|
s16 propnum = g_RoomPropListChunks[chunkindex].propnums[i];
|
|
|
|
if (propnum >= 0) {
|
|
// Check if it's in the list already
|
|
s16 *ptr = propnums;
|
|
|
|
while (ptr < writeptr) {
|
|
if (*ptr == propnum) {
|
|
break;
|
|
}
|
|
|
|
ptr++;
|
|
}
|
|
|
|
if (ptr == writeptr) {
|
|
// Prop is not in the list, so insert it
|
|
writeptr++;
|
|
writeptr[-1] = propnum;
|
|
}
|
|
}
|
|
}
|
|
|
|
chunkindex = g_RoomPropListChunks[chunkindex].propnums[7];
|
|
}
|
|
|
|
rooms++;
|
|
room = *rooms;
|
|
}
|
|
|
|
*writeptr = -1;
|
|
}
|
|
|
|
void propsDefragRoomProps(void)
|
|
{
|
|
s32 i;
|
|
s32 j;
|
|
s32 k;
|
|
|
|
// Iterate rooms
|
|
for (i = 0; i < g_Vars.roomcount; i++) {
|
|
s32 previndex = g_RoomPropListChunkIndexes[i];
|
|
|
|
if (previndex >= 0) {
|
|
s32 nextindex = g_RoomPropListChunks[previndex].propnums[7];
|
|
|
|
// Iterate this room's chunks but skip the first
|
|
while (nextindex >= 0) {
|
|
// Iterate propnums within this chunk
|
|
for (j = 0; j < 7; j++) {
|
|
// If this propnum is unallocated
|
|
if (g_RoomPropListChunks[previndex].propnums[j] < 0) {
|
|
// Iterate forward through the chunk list and find a
|
|
// propnum to move back to the prev chunk
|
|
for (k = 0; k < 7; k++) {
|
|
if (g_RoomPropListChunks[nextindex].propnums[k] >= 0) {
|
|
g_RoomPropListChunks[previndex].propnums[j] = g_RoomPropListChunks[nextindex].propnums[k];
|
|
g_RoomPropListChunks[nextindex].propnums[k] = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check if there are more propnums in the future chunk
|
|
for (; k < 7; k++) {
|
|
if (g_RoomPropListChunks[nextindex].propnums[k] >= 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (k == 7) {
|
|
// There's no more propnums, so this chunk can be removed
|
|
g_RoomPropListChunks[nextindex].propnums[0] = -2;
|
|
g_RoomPropListChunks[previndex].propnums[7] = g_RoomPropListChunks[nextindex].propnums[7];
|
|
|
|
nextindex = g_RoomPropListChunks[previndex].propnums[7];
|
|
|
|
if (nextindex < 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (nextindex >= 0) {
|
|
previndex = nextindex;
|
|
nextindex = g_RoomPropListChunks[nextindex].propnums[7];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void propGetBbox(struct prop *prop, f32 *radius, f32 *ymax, f32 *ymin)
|
|
{
|
|
if (prop->type == PROPTYPE_CHR) {
|
|
chrGetBbox(prop, radius, ymax, ymin);
|
|
} else if (prop->type == PROPTYPE_PLAYER) {
|
|
playerGetBbox(prop, radius, ymax, ymin);
|
|
} else if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_DOOR) {
|
|
objGetBbox(prop, radius, ymax, ymin);
|
|
} else {
|
|
*radius = 0;
|
|
*ymin = 0;
|
|
*ymax = 0;
|
|
}
|
|
}
|
|
|
|
bool propUpdateGeometry(struct prop *prop, u8 **start, u8 **end)
|
|
{
|
|
bool result = false;
|
|
|
|
if (prop->type == PROPTYPE_PLAYER) {
|
|
result = playerUpdateGeometry(prop, start, end);
|
|
} else if (prop->type == PROPTYPE_CHR) {
|
|
result = chrUpdateGeometry(prop, start, end);
|
|
} else if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_DOOR) {
|
|
result = objUpdateGeometry(prop, start, end);
|
|
}
|
|
|
|
return result;
|
|
}
|