#include #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++; } else { rooms = prop->rooms; while (*rooms != -1) { if (g_Rooms[*rooms].flags & ROOMFLAG_STANDBY) { break; } rooms++; } if (*rooms != -1) { 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; } // If this is the first time propsTickPlayer is being called on this frame, // and we've completed a full cycle of the propstates, redistribute them. // For each combination of background/foreground and chr/nonchr, take the // propstates with the highest quantity of these props and move some to the // lowest quantity propstate. if (g_Vars.currentplayerindex == 0 && g_Vars.runstateindex == 0) { // Background non-chrs leastindex = g_Vars.numpropstates; mostindex = g_Vars.numpropstates; least = 0x7fff; most = 0; for (i = 0; i < g_Vars.numpropstates; i++) { if (g_Vars.propstates[i].propcount < least) { least = g_Vars.propstates[i].propcount; leastindex = i; } if (g_Vars.propstates[i].propcount > most) { most = g_Vars.propstates[i].propcount; mostindex = i; } } i = (g_Vars.propstates[mostindex].propcount - g_Vars.propstates[leastindex].propcount) >> 1; if (i != 0) { prop = g_Vars.activeprops; while (prop != g_Vars.pausedprops) { if (prop->propstateindex == mostindex && prop->backgrounded == 1 && prop->type != PROPTYPE_CHR) { prop->propstateindex = leastindex; i--; if (i == 0) { break; } } prop = prop->next; } } // Foreground non-chrs leastindex = g_Vars.numpropstates; mostindex = g_Vars.numpropstates; least = 0x7fff; most = 0; for (i = 0; i < g_Vars.numpropstates; i++) { if (g_Vars.propstates[i].foregroundpropcount < least) { least = g_Vars.propstates[i].foregroundpropcount; leastindex = i; } if (g_Vars.propstates[i].foregroundpropcount > most) { most = g_Vars.propstates[i].foregroundpropcount; mostindex = i; } } i = (g_Vars.propstates[mostindex].foregroundpropcount - g_Vars.propstates[leastindex].foregroundpropcount) >> 1; if (i != 0) { prop = g_Vars.activeprops; while (prop != g_Vars.pausedprops) { if (prop->propstateindex == mostindex && prop->backgrounded == 0 && prop->type != PROPTYPE_CHR) { prop->propstateindex = leastindex; i--; if (i == 0) { break; } } prop = prop->next; } } // Background chrs leastindex = g_Vars.numpropstates; mostindex = g_Vars.numpropstates; least = 0x7fff; most = 0; for (i = 0; i < g_Vars.numpropstates; i++) { if (g_Vars.propstates[i].chrpropcount < least) { least = g_Vars.propstates[i].chrpropcount; leastindex = i; } if (g_Vars.propstates[i].chrpropcount > most) { most = g_Vars.propstates[i].chrpropcount; mostindex = i; } } i = (g_Vars.propstates[mostindex].chrpropcount - g_Vars.propstates[leastindex].chrpropcount) >> 1; if (i != 0) { prop = g_Vars.activeprops; while (prop != g_Vars.pausedprops) { if (prop->propstateindex == mostindex && prop->backgrounded == 1 && prop->type == PROPTYPE_CHR) { prop->propstateindex = leastindex; i--; if (i == 0) { break; } } prop = prop->next; } } // Foreground chrs leastindex = g_Vars.numpropstates; mostindex = g_Vars.numpropstates; least = 0x7fff; most = 0; for (i = 0; i < g_Vars.numpropstates; i++) { if (g_Vars.propstates[i].foregroundchrpropcount < least) { least = g_Vars.propstates[i].foregroundchrpropcount; leastindex = i; } if (g_Vars.propstates[i].foregroundchrpropcount > most) { most = g_Vars.propstates[i].foregroundchrpropcount; mostindex = i; } } i = (g_Vars.propstates[mostindex].foregroundchrpropcount - g_Vars.propstates[leastindex].foregroundchrpropcount) >> 1; if (i != 0) { prop = g_Vars.activeprops; while (prop != g_Vars.pausedprops) { if (prop->propstateindex == mostindex && prop->backgrounded == 0 && prop->type == PROPTYPE_CHR) { prop->propstateindex = leastindex; i--; if (i == 0) { break; } } prop = 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; }