#include #include "constants.h" #include "game/bondmove.h" #include "game/bondwalk.h" #include "game/cheats.h" #include "game/chraction.h" #include "game/chrai.h" #include "game/dlights.h" #include "game/nbomb.h" #include "game/playerreset.h" #include "game/chr.h" #include "game/prop.h" #include "game/setuputils.h" #include "game/propsnd.h" #include "game/objectives.h" #include "game/game_096360.h" #include "game/atan2f.h" #include "game/acosfasinf.h" #include "game/quaternion.h" #include "game/floor.h" #include "game/ceil.h" #include "game/bondgun.h" #include "game/gunfx.h" #include "game/game_0b0fd0.h" #include "game/modelmgr.h" #include "game/tex.h" #include "game/camera.h" #include "game/portal.h" #include "game/player.h" #include "game/game_0c33f0.h" #include "game/hudmsg.h" #include "game/menu.h" #include "game/inv.h" #include "game/playermgr.h" #include "game/game_1291b0.h" #include "game/vtxstore.h" #include "game/explosions.h" #include "game/smoke.h" #include "game/sparks.h" #include "game/game_1531a0.h" #include "game/bg.h" #include "game/stagetable.h" #include "game/env.h" #include "game/gfxmemory.h" #include "game/lv.h" #include "game/mplayer/setup.h" #include "game/mplayer/scenarios.h" #include "game/mpstats.h" #include "game/bot.h" #include "game/botact.h" #include "game/training.h" #include "game/lang.h" #include "game/mplayer/mplayer.h" #include "game/pad.h" #include "game/options.h" #include "game/propobj.h" #include "game/wallhit.h" #include "game/shards.h" #include "bss.h" #include "tvcmds.h" #include "lib/vi.h" #include "lib/dma.h" #include "lib/main.h" #include "lib/snd.h" #include "lib/str.h" #include "lib/memp.h" #include "lib/model.h" #include "lib/path.h" #include "lib/rng.h" #include "lib/mtx.h" #include "lib/anim.h" #include "lib/collision.h" #include "lib/lib_17ce0.h" #include "lib/lib_317f0.h" #include "data.h" #include "textures.h" #include "types.h" #include "string.h" struct weaponobj *g_Proxies[30]; s32 g_NumProxies; s32 g_MaxWeaponSlots; s32 g_MaxAmmoCrates; s32 g_MaxDebrisSlots; s32 g_MaxProjectiles; s32 g_MaxEmbedments; struct weaponobj *g_WeaponSlots; struct ammocrateobj *g_AmmoCrates; struct defaultobj *g_DebrisSlots; struct projectile *g_Projectiles; struct embedment *g_Embedments; u32 g_TintedGlassEnabled = 0; s32 g_AlarmTimer = 0; struct sndstate *g_AlarmAudioHandle = NULL; f32 g_AlarmSpeakerWeight = 64; f32 g_AlarmSpeakerDirection = 1; u32 g_CountdownTimerOff = COUNTDOWNTIMERREASON_AI; bool g_CountdownTimerRunning = false; f32 g_CountdownTimerValue60 = 0; u32 g_PlayersDetonatingMines = 0x00000000; s32 g_NextWeaponSlot = 0; struct linkliftdoorobj *g_LiftDoors = NULL; struct linksceneryobj *g_LinkedScenery = NULL; struct blockedpathobj *g_BlockedPaths = NULL; struct prop *g_EmbedProp = NULL; s32 g_EmbedHitPart = 0; u32 g_EmbedSide = 0x00000000; s16 var8006993c[3] = {0}; u32 var80069944 = 0x00000000; f32 g_CctvWaitScale = 1; f32 g_CctvDamageRxScale = 1; f32 g_AutogunAccuracyScale = 1; f32 g_AutogunDamageTxScale = 1; f32 g_AutogunDamageRxScale = 1; f32 g_AmmoQuantityScale = 1; struct padeffectobj *g_PadEffects = NULL; s32 g_LastPadEffectIndex = -1; struct autogunobj *g_ThrownLaptops = NULL; struct beam *g_ThrownLaptopBeams = NULL; s32 g_MaxThrownLaptops = 0; /** * Attempt to call a lift from the given door. * * Returns true if the door activation was handled by this function, or false * if the caller should proceed with opening or closing the door. A true return * doesn't necessarily mean the lift was called. * * The allowclose argument determines whether the door should be closed if the * lift is at the door. This is typically true when the player has activated the * door, and false when NPCs have activated the door. If true, doorCallLift * doesn't handle the activation which allows the caller to close the door. * * Lifts will not be called if it's occupied by anyone. This prevents chrs from * from calling lifts back when players are in them. */ bool doorCallLift(struct prop *doorprop, bool allowclose) { struct doorobj *door = doorprop->door; bool handled = false; if (door->base.hidden & OBJHFLAG_LIFTDOOR) { struct linkliftdoorobj *link = g_LiftDoors; while (link) { if (doorprop == link->door && link->lift && (link->lift->type & (PROPTYPE_OBJ | PROPTYPE_DOOR))) { bool type = link->lift->obj->type; handled = true; if (type == OBJTYPE_DOOR) { // This appears to be handling situations where the setup // file specifies a door as the lift object. It activates // that door, which then calls doorCallLift. This allows // setup files to chain lift doors to other lift doors // rather than directly to the lift, but this doesn't happen // in practice so this branch is unused. doorsActivate(link->lift, allowclose); } else if (type == OBJTYPE_LIFT) { if (allowclose && door->base.type == OBJTYPE_DOOR && !doorIsClosed(door)) { handled = false; } else { bool vacant = true; s32 numchrslots = chrsGetNumSlots(); s32 i; for (i = 0; i < PLAYERCOUNT(); i++) { if (g_Vars.players[i]->lift == link->lift) { vacant = false; } } if (vacant) { for (i = 0; i < numchrslots; i++) { /** * @bug: This is missing a chrIsDead check. * If a chr dies in a lift it can no longer be called. */ if (g_ChrSlots[i].prop && g_ChrSlots[i].lift == link->lift) { vacant = false; break; } } } if (vacant) { liftGoToStop((struct liftobj *) link->lift->obj, link->stopnum); } } } } link = link->next; } } return handled; } void objUpdateLinkedScenery(struct defaultobj *obj, struct prop *prop) { if ((obj->hidden & OBJHFLAG_CONDITIONALSCENERY) && (obj->flags & OBJFLAG_INVINCIBLE) == 0) { struct linksceneryobj *link = g_LinkedScenery; while (link) { if (link->trigger == obj) { objCreateDebris(obj, prop); link->trigger->flags2 |= OBJFLAG2_INVISIBLE; link->trigger->hidden |= OBJHFLAG_REAPABLE; if (link->unexp) { link->unexp->flags2 |= OBJFLAG2_INVISIBLE; } if (link->exp) { link->exp->flags2 &= ~OBJFLAG2_INVISIBLE; } objSetBlockedPathUnblocked(obj, true); return; } link = link->next; } } } f32 objGetLocalXMin(struct modelrodata_bbox *bbox) { return bbox->xmin; } f32 objGetLocalXMax(struct modelrodata_bbox *bbox) { return bbox->xmax; } f32 objGetLocalYMin(struct modelrodata_bbox *bbox) { return bbox->ymin; } f32 objGetLocalYMax(struct modelrodata_bbox *bbox) { return bbox->ymax; } f32 objGetLocalZMin(struct modelrodata_bbox *bbox) { return bbox->zmin; } f32 objGetLocalZMax(struct modelrodata_bbox *bbox) { return bbox->zmax; } f32 objGetRotatedLocalXMinByMtx4(struct modelrodata_bbox *bbox, Mtxf *mtx) { return objGetRotatedLocalMin(bbox, mtx->m[0][0], mtx->m[1][0], mtx->m[2][0]); } f32 objGetRotatedLocalXMaxByMtx4(struct modelrodata_bbox *bbox, Mtxf *mtx) { return objGetRotatedLocalMax(bbox, mtx->m[0][0], mtx->m[1][0], mtx->m[2][0]); } f32 objGetRotatedLocalYMinByMtx4(struct modelrodata_bbox *bbox, Mtxf *mtx) { return objGetRotatedLocalMin(bbox, mtx->m[0][1], mtx->m[1][1], mtx->m[2][1]); } f32 objGetRotatedLocalYMaxByMtx4(struct modelrodata_bbox *bbox, Mtxf *mtx) { return objGetRotatedLocalMax(bbox, mtx->m[0][1], mtx->m[1][1], mtx->m[2][1]); } f32 objGetRotatedLocalZMinByMtx4(struct modelrodata_bbox *bbox, Mtxf *mtx) { return objGetRotatedLocalMin(bbox, mtx->m[0][2], mtx->m[1][2], mtx->m[2][2]); } f32 objGetRotatedLocalZMaxByMtx4(struct modelrodata_bbox *bbox, Mtxf *mtx) { return objGetRotatedLocalMax(bbox, mtx->m[0][2], mtx->m[1][2], mtx->m[2][2]); } f32 objGetRotatedLocalXMinByMtx3(struct modelrodata_bbox *bbox, f32 realrot[3][3]) { return objGetRotatedLocalMin(bbox, realrot[0][0], realrot[1][0], realrot[2][0]); } f32 objGetRotatedLocalXMaxByMtx3(struct modelrodata_bbox *bbox, f32 realrot[3][3]) { return objGetRotatedLocalMax(bbox, realrot[0][0], realrot[1][0], realrot[2][0]); } f32 objGetRotatedLocalYMinByMtx3(struct modelrodata_bbox *bbox, f32 realrot[3][3]) { return objGetRotatedLocalMin(bbox, realrot[0][1], realrot[1][1], realrot[2][1]); } f32 objGetRotatedLocalYMaxByMtx3(struct modelrodata_bbox *bbox, f32 realrot[3][3]) { return objGetRotatedLocalMax(bbox, realrot[0][1], realrot[1][1], realrot[2][1]); } f32 objGetRotatedLocalZMinByMtx3(struct modelrodata_bbox *bbox, f32 realrot[3][3]) { return objGetRotatedLocalMin(bbox, realrot[0][2], realrot[1][2], realrot[2][2]); } f32 objGetRotatedLocalZMaxByMtx3(struct modelrodata_bbox *bbox, f32 realrot[3][3]) { return objGetRotatedLocalMax(bbox, realrot[0][2], realrot[1][2], realrot[2][2]); } f32 objGetRotatedLocalMin(struct modelrodata_bbox *bbox, f32 arg1, f32 arg2, f32 arg3) { f32 sum = 0; if (arg1 >= 0) { sum += bbox->xmin * arg1; } else { sum += bbox->xmax * arg1; } if (arg2 >= 0) { sum += bbox->ymin * arg2; } else { sum += bbox->ymax * arg2; } if (arg3 >= 0) { sum += bbox->zmin * arg3; } else { sum += bbox->zmax * arg3; } return sum; } f32 objGetRotatedLocalMax(struct modelrodata_bbox *bbox, f32 arg1, f32 arg2, f32 arg3) { f32 sum = 0; if (arg1 <= 0) { sum += bbox->xmin * arg1; } else { sum += bbox->xmax * arg1; } if (arg2 <= 0) { sum += bbox->ymin * arg2; } else { sum += bbox->ymax * arg2; } if (arg3 <= 0) { sum += bbox->zmin * arg3; } else { sum += bbox->zmax * arg3; } return sum; } s32 objCalculateGeoBlockVertices(f32 xmin, f32 xmax, f32 ymin, f32 ymax, f32 zmin, f32 zmax, Mtxf *mtx, struct geoblock *block) { s32 i; s32 j; s32 len = 0; s32 numindexes; f64 sp270[8][2]; f64 sp1f0[8][2]; s32 numverts; s32 t3 = 0; s32 t1 = 0; s32 t2 = 0; s32 t0 = 0; s32 indexes[8]; f64 a; f64 b; f64 xmin64 = xmin; f64 xmax64 = xmax; f64 ymin64 = ymin; f64 ymax64 = ymax; f64 zmin64 = zmin; f64 zmax64 = zmax; f64 mtx00 = mtx->m[0][0]; f64 mtx02 = mtx->m[0][2]; f64 mtx10 = mtx->m[1][0]; f64 mtx12 = mtx->m[1][2]; f64 mtx20 = mtx->m[2][0]; f64 mtx22 = mtx->m[2][2]; f64 mtx00min = mtx00 * xmin64; f64 mtx02min = mtx02 * xmin64; f64 mtx10min = mtx10 * ymin64; f64 mtx12min = mtx12 * ymin64; f64 mtx20min = mtx20 * zmin64; f64 mtx22min = mtx22 * zmin64; f64 mtx00max = mtx00 * xmax64; f64 mtx02max = mtx02 * xmax64; f64 mtx10max = mtx10 * ymax64; f64 mtx12max = mtx12 * ymax64; f64 mtx20max = mtx20 * zmax64; f64 mtx22max = mtx22 * zmax64; sp270[0][0] = mtx00min + mtx10min + mtx20min; sp270[0][1] = mtx02min + mtx12min + mtx22min; sp270[1][0] = mtx00min + mtx10min + mtx20max; sp270[1][1] = mtx02min + mtx12min + mtx22max; sp270[2][0] = mtx00min + mtx10max + mtx20min; sp270[2][1] = mtx02min + mtx12max + mtx22min; sp270[3][0] = mtx00min + mtx10max + mtx20max; sp270[3][1] = mtx02min + mtx12max + mtx22max; sp270[4][0] = mtx00max + mtx10min + mtx20min; sp270[4][1] = mtx02max + mtx12min + mtx22min; sp270[5][0] = mtx00max + mtx10min + mtx20max; sp270[5][1] = mtx02max + mtx12min + mtx22max; sp270[6][0] = mtx00max + mtx10max + mtx20min; sp270[6][1] = mtx02max + mtx12max + mtx22min; sp270[7][0] = mtx00max + mtx10max + mtx20max; sp270[7][1] = mtx02max + mtx12max + mtx22max; for (i = 0; i < 8; i++) { for (j = 0; j < len; j++) { f32 tmp = 0.001f; f64 f0 = tmp; a = sp270[i][0] - sp1f0[j][0]; b = sp270[i][1] - sp1f0[j][1]; if (a < f0 && a > -f0 && b < f0 && b > -f0) { break; } } if (j >= len) { sp1f0[len][0] = sp270[i][0]; sp1f0[len][1] = sp270[i][1]; len++; } } for (i = 1; i < len; i++) { if (sp1f0[i][0] < sp1f0[t3][0] || (sp1f0[i][0] == sp1f0[t3][0] && sp1f0[i][1] < sp1f0[t3][1])) { t3 = i; } } for (i = 1; i < len; i++) { if (sp1f0[t2][1] < sp1f0[i][1] || (sp1f0[i][1] == sp1f0[t2][1] && sp1f0[i][0] < sp1f0[t2][0])) { t2 = i; } } for (i = 1; i < len; i++) { if (sp1f0[t1][0] < sp1f0[i][0] || (sp1f0[i][0] == sp1f0[t1][0] && sp1f0[t1][1] < sp1f0[i][1])) { t1 = i; } } for (i = 1; i < len; i++) { if (sp1f0[i][1] < sp1f0[t0][1] || (sp1f0[i][1] == sp1f0[t0][1] && sp1f0[t0][0] < sp1f0[i][0])) { t0 = i; } } numindexes = 0; for (i = 0; i < len; i++) { if (i != t3 && i != t1 && i != t2 && i != t0) { indexes[numindexes] = i; numindexes++; } } numverts = 0; block->vertices[numverts][0] = sp1f0[t3][0]; block->vertices[numverts][1] = sp1f0[t3][1]; numverts++; if (t0 != t3) { for (i = 0; i < numindexes; i++) { s32 index = indexes[i]; if ((sp1f0[index][0] - sp1f0[t0][0]) * (sp1f0[t3][1] - sp1f0[t0][1]) < (sp1f0[t3][0] - sp1f0[t0][0]) * (sp1f0[index][1] - sp1f0[t0][1])) { block->vertices[numverts][0] = sp1f0[index][0]; block->vertices[numverts][1] = sp1f0[index][1]; numverts++; break; } } block->vertices[numverts][0] = sp1f0[t0][0]; block->vertices[numverts][1] = sp1f0[t0][1]; numverts++; } if (t1 != t0) { for (i = 0; i < numindexes; i++) { s32 index = indexes[i]; if ((sp1f0[index][0] - sp1f0[t1][0]) * (sp1f0[t0][1] - sp1f0[t1][1]) < (sp1f0[t0][0] - sp1f0[t1][0]) * (sp1f0[index][1] - sp1f0[t1][1])) { block->vertices[numverts][0] = sp1f0[index][0]; block->vertices[numverts][1] = sp1f0[index][1]; numverts++; break; } } block->vertices[numverts][0] = sp1f0[t1][0]; block->vertices[numverts][1] = sp1f0[t1][1]; numverts++; } if (t2 != t1) { for (i = 0; i < numindexes; i++) { s32 index = indexes[i]; if ((sp1f0[index][0] - sp1f0[t2][0]) * (sp1f0[t1][1] - sp1f0[t2][1]) < (sp1f0[t1][0] - sp1f0[t2][0]) * (sp1f0[index][1] - sp1f0[t2][1])) { block->vertices[numverts][0] = sp1f0[index][0]; block->vertices[numverts][1] = sp1f0[index][1]; numverts++; break; } } } if (t2 != t1 && t3 != t2) { block->vertices[numverts][0] = sp1f0[t2][0]; block->vertices[numverts][1] = sp1f0[t2][1]; numverts++; } if (t3 != t2) { for (i = 0; i < numindexes; i++) { s32 index = indexes[i]; if ((sp1f0[index][0] - sp1f0[t3][0]) * (sp1f0[t2][1] - sp1f0[t3][1]) < (sp1f0[t2][0] - sp1f0[t3][0]) * (sp1f0[index][1] - sp1f0[t3][1])) { block->vertices[numverts][0] = sp1f0[index][0]; block->vertices[numverts][1] = sp1f0[index][1]; numverts++; break; } } } for (i = 0; i < numverts; i++) { block->vertices[i][0] += mtx->m[3][0]; block->vertices[i][1] += mtx->m[3][2]; } return numverts; } void objCalculateGeoBlockFromBboxAndMtx(struct modelrodata_bbox *bbox, Mtxf *mtx, struct geoblock *block) { block->header.numvertices = objCalculateGeoBlockVertices( bbox->xmin, bbox->xmax, bbox->ymin, bbox->ymax, bbox->zmin, bbox->zmax, mtx, block); block->header.type = GEOTYPE_BLOCK; block->ymin = mtx->m[3][1] + objGetRotatedLocalYMinByMtx4(bbox, mtx); block->ymax = mtx->m[3][1] + objGetRotatedLocalYMaxByMtx4(bbox, mtx); } void objCalculateGeoBlockFromNode19Data(struct modelrodata_type19 *rodata19, struct modelrodata_bbox *bbox, Mtxf *mtx, struct geoblock *block) { s32 i; for (i = 0; i < rodata19->numvertices; i++) { block->vertices[i][0] = mtx->m[3][0] + mtx->m[0][0] * rodata19->vertices[i].x + mtx->m[1][0] * rodata19->vertices[i].y + mtx->m[2][0] * rodata19->vertices[i].z; block->vertices[i][1] = mtx->m[3][2] + mtx->m[0][2] * rodata19->vertices[i].x + mtx->m[1][2] * rodata19->vertices[i].y + mtx->m[2][2] * rodata19->vertices[i].z; } block->header.numvertices = rodata19->numvertices; block->header.type = GEOTYPE_BLOCK; block->ymin = mtx->m[3][1] + objGetRotatedLocalYMinByMtx4(bbox, mtx); block->ymax = mtx->m[3][1] + objGetRotatedLocalYMaxByMtx4(bbox, mtx); } bool func0f0675c8(struct coord *pos, f32 arg1, struct modelrodata_bbox *bbox, Mtxf *mtx) { Mtxf sp58; struct coord sp4c; struct coord sp40; struct coord sp34; struct coord sp28; sp34.f[0] = sp34.f[1] = sp34.f[2] = arg1; sp4c.x = pos->x - mtx->m[3][0]; sp4c.y = pos->y - mtx->m[3][1]; sp4c.z = pos->z - mtx->m[3][2]; mtx000170e4(mtx->m, sp58.m); mtx4RotateVec(&sp58, &sp4c, &sp40); mtx4RotateVec(&sp58, &sp34, &sp28); if (sp28.x < 0.0f) { sp28.x = -sp28.x; } if (sp28.y < 0.0f) { sp28.y = -sp28.y; } if (sp28.z < 0.0f) { sp28.z = -sp28.z; } return sp40.x - sp28.x <= bbox->xmax && sp28.x + sp40.x >= bbox->xmin && sp40.y - sp28.y <= bbox->ymax && sp28.y + sp40.y >= bbox->ymin && sp40.z - sp28.z <= bbox->zmax && sp28.z + sp40.z >= bbox->zmin; } bool func0f0677ac(struct coord *coord, struct coord *arg1, struct coord *pos, struct coord *normal, struct coord *up, struct coord *look, f32 xmin, f32 xmax, f32 ymin, f32 ymax, f32 zmin, f32 zmax) { f32 xdiff = coord->x - pos->x; f32 ydiff = coord->y - pos->y; f32 zdiff = coord->z - pos->z; f32 f0; f0 = xdiff * look->f[0] + ydiff * look->f[1] + zdiff * look->f[2]; if (f0 > arg1->z + zmax || f0 < zmin - arg1->z) { return false; } f0 = xdiff * up->f[0] + ydiff * up->f[1] + zdiff * up->f[2]; if (f0 > arg1->y + ymax || f0 < ymin - arg1->y) { return false; } f0 = xdiff * normal->f[0] + ydiff * normal->f[1] + zdiff * normal->f[2]; if (f0 > arg1->x + xmax || f0 < xmin - arg1->x) { return false; } return true; } bool func0f0678f8(struct coord *coord, struct coord *arg1, s32 padnum) { struct pad *pad = &g_Pads[padnum]; return func0f0677ac(coord, arg1, &pad->pos, &pad->normal, &pad->up, &pad->look, pad->bbox.xmin, pad->bbox.xmax, pad->bbox.ymin, pad->bbox.ymax, pad->bbox.zmin, pad->bbox.zmax); } bool func0f06797c(struct coord *coord, f32 arg1, s32 padnum) { struct coord sp1c; sp1c.x = arg1; sp1c.y = arg1; sp1c.z = arg1; return func0f0678f8(coord, &sp1c, padnum); } bool func0f0679ac(struct model *model, f32 *max, f32 *min, f32 arg3[2], f32 arg4[2]) { struct modelnode *node = model->filedata->rootnode; bool first = true; while (node) { u32 type = node->type & 0xff; if (type == MODELNODETYPE_BBOX) { struct modelrodata_bbox *bbox = &node->rodata->bbox; Mtxf *mtx = model0001a5cc(model, node, 0); f32 dist1; f32 dist2; dist1 = objGetRotatedLocalXMaxByMtx4(bbox, mtx) + mtx->m[3][0]; dist2 = objGetRotatedLocalXMinByMtx4(bbox, mtx) + mtx->m[3][0]; if (arg3[0] - arg4[0] <= dist1 && arg3[0] + arg4[0] >= dist2) { dist1 = objGetRotatedLocalYMaxByMtx4(bbox, mtx) + mtx->m[3][1]; dist2 = objGetRotatedLocalYMinByMtx4(bbox, mtx) + mtx->m[3][1]; if (arg3[1] - arg4[1] <= dist1 && arg3[1] + arg4[1] >= dist2) { dist1 = objGetRotatedLocalZMaxByMtx4(bbox, mtx) + mtx->m[3][2]; dist2 = objGetRotatedLocalZMinByMtx4(bbox, mtx) + mtx->m[3][2]; if (first || dist1 > *max) { *max = dist1; } if (first || dist2 < *min) { *min = dist2; } first = false; } } } else { // empty } if (node->child) { node = node->child; } else { while (node) { if (node->next) { node = node->next; break; } node = node->parent; } } } return !first; } void func0f067bc4(struct model *model, f32 *max, f32 *min, s32 axis) { struct modelnode *node = model->filedata->rootnode; bool first = true; while (node) { u32 type = node->type & 0xff; if (type == MODELNODETYPE_BBOX) { struct modelrodata_bbox *bbox = &node->rodata->bbox; Mtxf *mtx = model0001a5cc(model, node, 0); f32 dist1; f32 dist2; if (axis == 0) { dist1 = objGetRotatedLocalXMaxByMtx4(bbox, mtx) + mtx->m[3][0]; dist2 = objGetRotatedLocalXMinByMtx4(bbox, mtx) + mtx->m[3][0]; } else if (axis == 1) { dist1 = objGetRotatedLocalYMaxByMtx4(bbox, mtx) + mtx->m[3][1]; dist2 = objGetRotatedLocalYMinByMtx4(bbox, mtx) + mtx->m[3][1]; } else { dist1 = objGetRotatedLocalZMaxByMtx4(bbox, mtx) + mtx->m[3][2]; dist2 = objGetRotatedLocalZMinByMtx4(bbox, mtx) + mtx->m[3][2]; } if (first || dist1 > *max) { *max = dist1; } if (first || dist2 < *min) { *min = dist2; } first = false; } else { // empty } if (node->child) { node = node->child; } else { while (node) { if (node->next) { node = node->next; break; } node = node->parent; } } } } void func0f067d88(struct model *model, f32 *arg1, f32 *arg2, f32 *arg3, f32 *arg4) { func0f067bc4(model, arg1, arg2, 0); func0f067bc4(model, arg3, arg4, 1); } bool modelGetScreenCoords2(struct model *model, f32 *x2, f32 *x1, f32 *y2, f32 *y1) { bool first = true; if (model) { struct modelfiledata *modeldef = model->filedata; if (modeldef) { struct modelnode *node = modeldef->rootnode; while (node) { u32 type = node->type & 0xff; if (type == MODELNODETYPE_BBOX) { struct modelrodata_bbox *bbox = &node->rodata->bbox; f32 sp74[2]; f32 sp6c[2]; f32 sp64[2]; f32 sp5c[2]; struct coord sp50; Mtxf *mtx = model0001a5cc(model, node, 0); if (mtx->m[3][2] < 0.0f) { sp50.x = mtx->m[3][0]; sp50.y = mtx->m[3][1]; sp50.z = mtx->m[3][2]; sp64[0] = objGetRotatedLocalXMinByMtx4(bbox, mtx) + sp50.f[0]; sp64[1] = objGetRotatedLocalXMaxByMtx4(bbox, mtx) + sp50.f[0]; sp5c[0] = objGetRotatedLocalYMinByMtx4(bbox, mtx) + sp50.f[1]; sp5c[1] = objGetRotatedLocalYMaxByMtx4(bbox, mtx) + sp50.f[1]; func0f06803c(&sp50, sp64, sp5c, sp74, sp6c); if (first || sp74[0] < *x1) { *x1 = sp74[0]; } if (first || sp6c[0] > *x2) { *x2 = sp6c[0]; } if (first || sp74[1] < *y1) { *y1 = sp74[1]; } if (first || sp6c[1] > *y2) { *y2 = sp6c[1]; } first = false; } } else { // empty } if (node->child) { node = node->child; } else { while (node) { if (node->next) { node = node->next; break; } node = node->parent; } } } } } return first ? false : true; } bool modelGetScreenCoords(struct model *model, f32 *x2, f32 *x1, f32 *y2, f32 *y1) { return modelGetScreenCoords2(model, x2, x1, y2, y1); } void func0f06803c(struct coord *arg0, f32 *arg1, f32 *arg2, f32 *arg3, f32 *arg4) { struct coord sp4c; f32 sp44[2]; f32 aspect = viGetAspect(); f32 fovy = viGetFovY(); if (g_Vars.currentplayer->devicesactive & ~g_Vars.currentplayer->devicesinhibit & DEVICE_EYESPY) { if (g_Vars.currentplayer->eyespy && g_Vars.currentplayer->eyespy->active) { fovy = 120.0f; } } sp4c.x = arg1[0]; sp4c.y = arg0->y; sp4c.z = arg0->z; cam0f0b4eb8(&sp4c, sp44, fovy, aspect); arg3[0] = sp44[0]; sp4c.x = arg1[1]; sp4c.y = arg0->y; sp4c.z = arg0->z; cam0f0b4eb8(&sp4c, sp44, fovy, aspect); arg4[0] = sp44[0]; sp4c.x = arg0->x; sp4c.y = arg2[1]; sp4c.z = arg0->z; cam0f0b4eb8(&sp4c, sp44, fovy, aspect); arg3[1] = sp44[1]; sp4c.x = arg0->x; sp4c.y = arg2[0]; sp4c.z = arg0->z; cam0f0b4eb8(&sp4c, sp44, fovy, aspect); arg4[1] = sp44[1]; } struct defaultobj *objFindByPos(struct coord *pos, s16 *rooms) { struct prop *prop = g_Vars.activeprops; u8 *sp38; u8 *sp34; while (prop) { if (prop->type == PROPTYPE_OBJ && arrayIntersects(prop->rooms, rooms) && propUpdateGeometry(prop, &sp38, &sp34) && cd000266a4(pos->x, pos->z, (struct geo *)sp38)) { return prop->obj; } prop = prop->next; } return NULL; } void projectileFree(struct projectile *projectile) { if (projectile) { projectile->flags |= PROJECTILEFLAG_FREE; } } void projectilesUnrefOwner(struct prop *owner) { s32 i; for (i = 0; i < g_MaxProjectiles; i++) { if ((g_Projectiles[i].flags & PROJECTILEFLAG_FREE) == 0 && g_Projectiles[i].ownerprop == owner) { g_Projectiles[i].ownerprop = NULL; } } } void projectileReset(struct projectile *projectile) { projectile->flags = 0; projectile->speed.x = 0; projectile->speed.y = 0; projectile->speed.z = 0; projectile->unk010 = 0; projectile->unk014 = 0; projectile->unk018 = 0; projectile->unk01c = 0; mtx4LoadIdentity(&projectile->mtx); projectile->unk060 = 1; projectile->ownerprop = NULL; projectile->unk08c = 0.05f; projectile->bouncecount = 0; projectile->bounceframe = -1; projectile->lastwooshframe = -1; projectile->flighttime240 = 0; projectile->unk0a4 = -1; projectile->droptype = DROPTYPE_DEFAULT; projectile->pickuptimer240 = 0; projectile->losttimer240 = 0; projectile->obj = NULL; projectile->unk0d8 = 0; projectile->smoketimer240 = 0; projectile->targetprop = NULL; projectile->pickupby = NULL; projectile->powerlimit240 = -1; projectile->unk0b8[0] = 1; projectile->unk0b8[1] = 1; projectile->unk0b8[2] = 1; projectile->unk0e4 = 1; projectile->unk098 = 0; projectile->unk0dc = 0; projectile->unk0e0 = 0; projectile->unk0ec = 0; projectile->unk0f0 = 0; } struct projectile *projectileAllocate(void) { s32 bestindex = -1; s32 i; // Happy path - find one that is already free for (i = 0; i < g_MaxProjectiles; i++) { if (g_Projectiles[i].flags & PROJECTILEFLAG_FREE) { projectileReset(&g_Projectiles[i]); return &g_Projectiles[i]; } } // Find one with the lowest unk0d8 (some kind of age/timer?) // and some other conditions for (i = 0; i < g_MaxProjectiles; i++) { if (g_Projectiles[i].obj && g_Projectiles[i].unk0d8 > 0 && (bestindex < 0 || g_Projectiles[i].unk0d8 < g_Projectiles[bestindex].unk0d8)) { bestindex = i; } } // If there were none, pick one at random if (bestindex == -1 && g_MaxProjectiles) { bestindex = random() % g_MaxProjectiles; } if (bestindex >= 0) { // Reset and return it if (g_Projectiles[bestindex].obj) { if (g_Projectiles[bestindex].obj->prop) { objFreeEmbedmentOrProjectile(g_Projectiles[bestindex].obj->prop); } g_Projectiles[bestindex].obj->hidden |= OBJHFLAG_REAPABLE; } projectileReset(&g_Projectiles[bestindex]); return &g_Projectiles[bestindex]; } else { return NULL; } } void func0f0685e4(struct prop *prop) { struct defaultobj *obj = prop->obj; if (obj->hidden & OBJHFLAG_EMBEDDED) { if (obj->embedment->projectile) { projectileReset(obj->embedment->projectile); } else { obj->embedment->projectile = projectileAllocate(); } } else if ((obj->hidden & OBJHFLAG_PROJECTILE) == 0) { if (obj->projectile) { projectileReset(obj->projectile); } else { obj->projectile = projectileAllocate(); } if (obj->projectile) { obj->hidden |= OBJHFLAG_PROJECTILE; } } } void projectileSetSticky(struct prop *prop) { struct defaultobj *obj = prop->obj; struct projectile *projectile = NULL; if (obj->hidden & OBJHFLAG_EMBEDDED) { projectile = obj->embedment->projectile; } else if (obj->hidden & OBJHFLAG_PROJECTILE) { projectile = obj->projectile; } if (projectile) { projectile->flags |= PROJECTILEFLAG_STICKY; } } void embedmentFree(struct embedment *embedment) { embedment->flags |= EMBEDMENTFLAG_FREE; } struct embedment *embedmentAllocate(void) { s32 i; for (i = 0; i < g_MaxEmbedments; i++) { if (g_Embedments[i].flags & EMBEDMENTFLAG_FREE) { g_Embedments[i].flags = 0; g_Embedments[i].projectile = NULL; return &g_Embedments[i]; } } return NULL; } /** * This doesn't exactly return the number of shots taken but it's the best way * to describe the behaviour of the function without writing a novel into the * function's name. * * The number returned is 0 when at full health and only ever increments as the * object takes damage. While healthy, the number scales from 0 to 4 based on * how close it is to being destroyed, where 4 is destroyed. After being * destroyed, the number increments at 1 per shot up to a max of 12. */ s32 objGetShotsTaken(struct defaultobj *obj) { if ((obj->hidden2 & OBJH2FLAG_DESTROYED) == 0) { return obj->damage * 3.0f / obj->maxdamage; } return obj->damage + 4; } /** * Return 0 if not destroyed * Return 1 if at destroyed level 1 * Return 2 if at destroyed level 2 * Return 3 if at destroyed level 3 * * Each destroyed level is a new phase of visual brokenness. Typically the * object is destroyed and it looks broken (level 1), then after a couple of * shots it enters level 2, and a few shots later level 3. * * While healthy, damage goes from 0 to maxdamage (eg. 1000) but this function * returns 0 due to the if statement. * * When destroyed, damage is reset to 0 then incremented at one unit per shot, * so four shots causes it to enter a new destroyed level. */ s32 objGetDestroyedLevel(struct defaultobj *obj) { if ((obj->hidden2 & OBJH2FLAG_DESTROYED) == 0) { return 0; } return (obj->damage >> 2) + 1; } struct modelnode *func0f0687e4(struct model *model) { struct modelfiledata *filedata = model->filedata; struct modelnode *node = filedata->rootnode; while (node) { u32 type = node->type & 0xff; switch (type) { case MODELNODETYPE_DL: return node; case MODELNODETYPE_DISTANCE: model0001c784(model, node); break; case MODELNODETYPE_TOGGLE: model0001c7d0(model, node); break; case MODELNODETYPE_HEADSPOT: modelAttachHead(model, node); break; } if (node->child) { node = node->child; } else { while (node) { if (node->next) { node = node->next; break; } node = node->parent; } } } return NULL; } struct modelnode *modelFileDataFindBboxNode(struct modelfiledata *filedata) { struct modelnode *node = filedata->rootnode; while (node) { if ((node->type & 0xff) == MODELNODETYPE_BBOX) { return node; } if (node->child) { node = node->child; } else { while (node) { if (node->next) { node = node->next; break; } node = node->parent; } } } return NULL; } struct modelrodata_bbox *modelFileDataFindBboxRodata(struct modelfiledata *filedata) { struct modelnode *node = modelFileDataFindBboxNode(filedata); if (node) { return &node->rodata->bbox; } return NULL; } struct modelnode *modelFindBboxNode(struct model *model) { struct modelnode *node = model->filedata->rootnode; while (node) { u32 type = node->type & 0xff; switch (type) { case MODELNODETYPE_BBOX: return node; case MODELNODETYPE_DISTANCE: model0001c784(model, node); break; case MODELNODETYPE_TOGGLE: model0001c7d0(model, node); break; case MODELNODETYPE_HEADSPOT: modelAttachHead(model, node); break; } if (node->child) { node = node->child; } else { while (node) { if (node->next) { node = node->next; break; } node = node->parent; } } } return NULL; } struct modelrodata_bbox *modelFindBboxRodata(struct model *model) { struct modelnode *node = modelFindBboxNode(model); if (node) { return &node->rodata->bbox; } return NULL; } struct modelnode *objFindBboxNode(struct defaultobj *obj) { return modelFindBboxNode(obj->model); } struct modelrodata_bbox *objFindBboxRodata(struct defaultobj *obj) { return modelFindBboxRodata(obj->model); } s32 func0f068b14(s16 *rooms, s32 arg1) { s32 total = 0; s32 i; for (i = 0; rooms[i] != -1; i++) { if (arg1 == 0) { total += func0f000b24(rooms[i]); } else if (arg1 == 1) { total += func0f000c54(rooms[i]); } } if (i) { s32 average = total / i; if (average > 255) { average = 255; } return average; } return 0; } s32 door0f068c04(struct prop *prop, s32 *arg1, s32 *arg2) { struct doorobj *door = prop->door; struct doorobj *sibling; s32 i; s32 s1; s32 s3 = 0; s32 s4 = 0; s32 s2 = 0; s32 s5 = 0; s32 v1; s32 result; struct prop *loopprop; struct pad *pad; sibling = door; while (sibling && sibling->base.prop) { if (sibling->portalnum == door->portalnum) { if (g_Vars.currentplayer->prop) { struct coord *campos = &g_Vars.currentplayer->cam_pos; loopprop = sibling->base.prop; pad = &g_Pads[sibling->base.pad]; if (door->base.flags3 & OBJFLAG3_04000000) { s3 += func0f000b24(pad->room); s4 += func0f000c54(pad->room); s5++; s2 = s3 + s4; if (s2 > 255) { s2 = 255; } } else { f32 xdist; f32 ydist; f32 zdist; struct coord *pos = &pad->pos; struct coord normal = pad->normal; f32 sum1; f32 sum2; if (door->doorflags & DOORFLAG_FLIP) { normal.f[0] = -normal.f[0]; normal.f[1] = -normal.f[1]; normal.f[2] = -normal.f[2]; } xdist = campos->f[0] - pos->x; ydist = campos->f[1] - pos->y; zdist = campos->f[2] - pos->z; sum1 = xdist * normal.x + ydist * normal.y + zdist * normal.z; for (i = 0; loopprop->rooms[i] != -1; i++) { f32 roomx = g_Rooms[loopprop->rooms[i]].centre.x; f32 roomy = g_Rooms[loopprop->rooms[i]].centre.y; f32 roomz = g_Rooms[loopprop->rooms[i]].centre.z; f32 xdist = roomx - pos->x; f32 ydist = roomy - pos->y; f32 zdist = roomz - pos->z; sum2 = xdist * normal.x + ydist * normal.y + zdist * normal.z; // @bug? Duplicate sum1 < 0.0f check in the first part. // Perhaps one of them should be sum2 < 0.0f. if ((sum1 < 0.0f && sum1 < 0.0f) || (sum1 > 0.0f && sum2 > 0.0f)) { s32 value1 = func0f000c54(loopprop->rooms[i]); s32 value2 = func0f000b24(loopprop->rooms[i]); s32 sum = value2 + value1; if (sum > 255) { sum = 255; } s5++; s3 += value2; s4 += value1; s2 += sum; } } } } } sibling = sibling->sibling; if (sibling == door) { break; } } if (s5 == 0) { s1 = func0f068b14(prop->rooms, 0); v1 = func0f068b14(prop->rooms, 1); } else { s1 = s3 / s5; v1 = s4 / s5; } if (arg1 != NULL) { *arg1 = s1; } if (arg2 != NULL) { *arg2 = v1; } if (s1 + v1 < 255) { result = s1 + v1; } else { result = 255; } return result; } s32 func0f068fc8(struct prop *prop, bool arg1) { struct defaultobj *obj = prop->obj; s32 actual = 0; s32 extra = 0; s32 *actualptr; s32 *extraptr; if (prop->rooms[0] == -1) { actual = random() % 255; extra = 0; } else if (obj->type == OBJTYPE_DOOR) { struct doorobj *door = (struct doorobj *)obj; if (g_Vars.normmplayerisrunning) { actual = 255; } else { actualptr = arg1 == 0 ? &actual : NULL; extraptr = arg1 == 1 ? &extra : NULL; door0f068c04(prop, actualptr, extraptr); if (g_Vars.coopplayernum >= 0 || g_Vars.antiplayernum >= 0) { if (g_Vars.currentplayernum == 1) { if (actualptr) { door->actual1 = actual & 0xff; } if (extraptr) { door->extra1 = extra & 0xff; } } else { if (actualptr) { door->actual2 = actual & 0xff; } if (extraptr) { door->extra2 = extra & 0xff; } } } } } else { actual = func0f068b14(prop->rooms, 0); extra = func0f068b14(prop->rooms, 1); } if (arg1 == 0) { return actual; } if (arg1 == 1) { return extra; } return 255; } void propCalculateShadeColour(struct prop *prop, u8 *nextcol, u16 floorcol) { struct defaultobj *obj; s32 max; s32 med; s32 min; f32 alphafrac; s32 roomr; s32 roomg; s32 roomb; s32 tmp; if (prop->type & (PROPTYPE_OBJ | PROPTYPE_WEAPON | PROPTYPE_DOOR)) { obj = prop->obj; } else { obj = NULL; } if (prop->type == PROPTYPE_CHR) { struct chrdata *chr = prop->chr; chr->unk32c_18 = false; } // Set nextcol to the floor colour if (obj && (obj->flags & OBJFLAG_IGNOREFLOORCOLOUR)) { nextcol[0] = 0xff; nextcol[1] = 0xff; nextcol[2] = 0xff; nextcol[3] = 0xff; } else { u8 r = ((floorcol >> 8) & 0xf); u8 g = ((floorcol >> 4) & 0xf); u8 b = ((floorcol >> 0) & 0xf); nextcol[0] = ((r << 4) | r); nextcol[1] = ((g << 4) | g); nextcol[2] = ((b << 4) | b); nextcol[3] = 0xff; } if (obj == NULL || (obj->flags & OBJFLAG_IGNOREROOMCOLOUR) == 0 || cheatIsActive(CHEAT_PERFECTDARKNESS)) { s32 shade = func0f068fc8(prop, 0); roomr = shade; roomg = shade; roomb = shade; alphafrac = 1.0f - shade * (1.0f / 2550.0f); scenarioHighlightRoom(prop->rooms[0], &roomr, &roomg, &roomb); nextcol[0] = (nextcol[0] * roomr) >> 8; nextcol[1] = (nextcol[1] * roomg) >> 8; nextcol[2] = (nextcol[2] * roomb) >> 8; tmp = nextcol[0] * 79 + nextcol[1] * 156 + nextcol[2] * 21; tmp >>= 8; nextcol[3] = (0xff - tmp) * alphafrac; } else { tmp = nextcol[0] * 79 + nextcol[1] * 156 + nextcol[2] * 21; tmp >>= 8; nextcol[3] = (0xff - tmp) * 0.9f; } if (1); // Figure out which colour component is the lowest, middle and highest max = 0; min = 0; med = 0; if (nextcol[1] > nextcol[0]) { max = 1; } else { min = 1; } if (nextcol[2] > nextcol[max]) { med = max; max = 2; } else if (nextcol[2] > nextcol[min]) { med = 2; } else { med = min; min = 2; } // @bug: The min and max component values are subtracted by the same amount, but the middle value is scaled. // So when a prop's shade colour is changing one of the component values is out of step with the others. // This is pretty much impossible to notice though. if (nextcol[max] > 0) { s32 tmp = nextcol[med] * (nextcol[max] - nextcol[min]) / nextcol[max]; s32 range = nextcol[max] - nextcol[min]; nextcol[min] = 0; nextcol[med] = tmp; nextcol[max] = range; } // Halving the next colour values causes them to transition over // multiple frames and slow down as they approach the desired colour. nextcol[0] >>= 1; nextcol[1] >>= 1; nextcol[2] >>= 1; } void propCalculateShadeInfo(struct prop *prop, u8 *nextcol, u16 floorcol) { propCalculateShadeColour(prop, nextcol, floorcol); nextcol[0] >>= 1; nextcol[1] >>= 1; nextcol[2] >>= 1; if (prop->type == PROPTYPE_DOOR && (g_Vars.coopplayernum >= 0 || g_Vars.antiplayernum >= 0)) { struct doorobj *door = prop->door; if (g_Vars.currentplayernum == 0) { door->shadeinfo1[0] = nextcol[0]; door->shadeinfo1[1] = nextcol[1]; door->shadeinfo1[2] = nextcol[2]; door->shadeinfo1[3] = nextcol[3]; } else { door->shadeinfo2[0] = nextcol[0]; door->shadeinfo2[1] = nextcol[1]; door->shadeinfo2[2] = nextcol[2]; door->shadeinfo2[3] = nextcol[3]; } } } /** * Shift col to be closer to nextcol. * * It works by moving halfway towards the nextcol colour each time it's called. */ void colourTween(u8 *col, u8 *nextcol) { s32 i; for (i = 0; i < 4; i++) { s32 remaining = nextcol[i] - col[i]; s32 newcol = col[i] + (remaining >> 1); col[i] = newcol; } } void func0f069750(s32 *arg0, s32 arg1, f32 arg2[4]) { if (arg1 == 1) { f32 tmp; arg2[0] *= 255.0f; arg2[1] *= 255.0f; arg2[2] *= 255.0f; tmp = arg0[0]; arg0[0] = tmp + (arg2[0] - tmp) * arg2[3]; tmp = arg0[1]; arg0[1] = tmp + (arg2[1] - tmp) * arg2[3]; tmp = arg0[2]; arg0[2] = tmp + (arg2[2] - tmp) * arg2[3]; tmp = arg0[3]; arg0[3] = tmp + (255.0f - tmp) * arg2[3]; } } struct hovtype g_HovTypes[]; void func0f069850(struct defaultobj *obj, struct coord *pos, f32 rot[3][3], struct geocyl *cyl) { Mtxf mtx; struct modelrodata_bbox *bbox = objFindBboxRodata(obj); struct modelrodata_type19 *rodata19 = NULL; struct hoverbikeobj *hoverbike; struct hoverpropobj *hoverprop; mtx3ToMtx4(rot, &mtx); mtx4SetTranslation(pos, &mtx); if (obj->model->filedata->skel == &g_SkelHoverbike || obj->model->filedata->skel == &g_SkelBasic || obj->model->filedata->skel == &g_SkelMaianUfo || obj->model->filedata->skel == &g_SkelDropship) { rodata19 = modelGetPartRodata(obj->model->filedata, MODELPART_HOVERBIKE_0064); } if (obj->flags3 & OBJFLAG3_GEOCYL) { cyl->header.type = GEOTYPE_CYL; cyl->header.flags = GEOFLAG_WALL | GEOFLAG_BLOCK_SIGHT | GEOFLAG_BLOCK_SHOOT; if (obj->type == OBJTYPE_HOVERBIKE) { hoverbike = (struct hoverbikeobj *)obj; cyl->ymax = hoverbike->hov.ground + g_HovTypes[hoverbike->hov.type].unk00 + objGetLocalYMax(bbox) * obj->model->scale; cyl->ymin = hoverbike->hov.ground + 20.0f; } else if (obj->type == OBJTYPE_HOVERPROP) { hoverprop = (struct hoverpropobj *)obj; cyl->ymax = hoverprop->hov.ground + g_HovTypes[hoverprop->hov.type].unk00 + objGetLocalYMax(bbox) * obj->model->scale; cyl->ymin = hoverprop->hov.ground + 20.0f; } else { cyl->ymin = mtx.m[3][1] + objGetRotatedLocalYMinByMtx4(bbox, &mtx); cyl->ymax = mtx.m[3][1] + objGetRotatedLocalYMaxByMtx4(bbox, &mtx); } cyl->x = pos->x; cyl->z = pos->z; cyl->radius = 90.0f; } else { if (rodata19 != NULL) { objCalculateGeoBlockFromNode19Data(rodata19, bbox, &mtx, (struct geoblock *)cyl); } else { objCalculateGeoBlockFromBboxAndMtx(bbox, &mtx, (struct geoblock *)cyl); } if (obj->type == OBJTYPE_HOVERBIKE) { hoverbike = (struct hoverbikeobj *)obj; cyl->ymax = hoverbike->hov.ground + g_HovTypes[hoverbike->hov.type].unk00 + objGetLocalYMax(bbox) * obj->model->scale; cyl->ymin = hoverbike->hov.ground + 20.0f; } else if (obj->type == OBJTYPE_HOVERPROP) { hoverprop = (struct hoverpropobj *)obj; cyl->ymax = hoverprop->hov.ground + g_HovTypes[hoverprop->hov.type].unk00 + objGetLocalYMax(bbox) * obj->model->scale; cyl->ymin = hoverprop->hov.ground + 20.0f; } } } void func0f069b4c(struct defaultobj *obj) { union modelrodata *rodata; u8 *ptr = (u8 *) obj->unkgeo; if (ptr != NULL) { if ((obj->hidden2 & OBJH2FLAG_08)) { if (obj->flags3 & OBJFLAG3_GEOCYL) { ptr += sizeof(struct geocyl); } else { ptr += sizeof(struct geoblock); } } rodata = modelGetPartRodata(obj->model->filedata, MODELPART_0065); if (rodata != NULL) { u32 flags = GEOFLAG_FLOOR1 | GEOFLAG_FLOOR2; if (obj->type == OBJTYPE_ESCASTEP) { flags |= GEOFLAG_LIFTFLOOR; } func0f070ca0(obj, (struct geotilef *)ptr, flags, NULL, &rodata->type19); ptr += 0x40; } rodata = modelGetPartRodata(obj->model->filedata, MODELPART_0066); if (rodata != NULL) { func0f070ca0(obj, (struct geotilef *)ptr, GEOFLAG_WALL | GEOFLAG_BLOCK_SIGHT | GEOFLAG_BLOCK_SHOOT, NULL, &rodata->type19); } } } void func0f069c1c(struct defaultobj *obj) { if (obj->geocyl) { if (obj->hidden2 & OBJH2FLAG_08) { func0f069850(obj, &obj->prop->pos, obj->realrot, obj->geocyl); } func0f069b4c(obj); } } void func0f069c70(struct defaultobj *obj, bool arg1, bool arg2) { struct prop *prop; if (arg1) { func0f069c1c(obj); } if (arg2) { setup0f0923d4(obj); } prop = obj->prop; propCalculateShadeInfo(prop, obj->nextcol, obj->floorcol); coordTriggerProxies(&obj->prop->pos, false); } /** * Iterate the model parts in range 201 to 220 (0xc9 to 0xdc) and disable them, * stopping when any part doesn't exist. * * This range of part numbers is a special range that is hidden when the object * is initialised. */ void objInitToggleNodes(struct defaultobj *obj) { struct model *model = obj->model; union modelrwdata *rwdata; s32 i; for (i = 0; i < 20; i++) { struct modelnode *node = modelGetPart(model->filedata, 201 + i); if (!node) { return; } rwdata = modelGetNodeRwData(model, node); rwdata->toggle.visible = false; } } void objCreateOneDebris(struct defaultobj *obj, s32 partindex, struct prop *prop) { struct defaultobj *debris = debrisAllocate(); if (debris) { struct defaultobj tmp = { 256, // extrascale 0, // hidden2 OBJTYPE_DEBRIS, // type 0, // modelnum -1, // pad OBJFLAG_00000001, // flags 0, // flags2 0, // flags3 NULL, // prop NULL, // model 1, 0, 0, // realrot 0, 1, 0, 0, 0, 1, 0, // hidden NULL, // geo NULL, // projectile 0, // damage 1000, // maxdamage 0xff, 0xff, 0xff, 0x00, // shadecol 0xff, 0xff, 0xff, 0x00, // nextcol 0x0fff, // floorcol 0, // tiles }; struct modelnode *node; *debris = tmp; debris->modelnum = obj->modelnum; if (objInitWithModelDef(debris, g_ModelStates[debris->modelnum].filedata)) { propReparent(debris->prop, obj->prop); objSetDropped(debris->prop, DROPTYPE_5); if (debris->hidden & OBJHFLAG_PROJECTILE) { f32 distance; struct projectile *projectile = debris->projectile; struct coord rot = {0, 0, 0}; struct coord dist; dist.x = obj->prop->pos.x - prop->pos.x; dist.y = 0.0f; dist.z = obj->prop->pos.z - prop->pos.z; distance = sqrtf(dist.f[0] * dist.f[0] + dist.f[2] * dist.f[2]); if (distance > 0.0f) { distance = 1.0f / distance; dist.x *= distance; dist.z *= distance; } else { dist.x = RANDOMFRAC() * 0.5f; dist.z = RANDOMFRAC() * 0.5f; } projectile->speed.x = dist.x * 3.3333333f; projectile->speed.y = RANDOMFRAC() * 6.6666665f; projectile->speed.z = dist.z * 3.3333333f; #if PAL rot.x = RANDOMFRAC() * 0.058895487f - 0.029447744f; rot.y = RANDOMFRAC() * 0.058895487f - 0.029447744f; rot.z = RANDOMFRAC() * 0.058895487f - 0.029447744f; #else rot.x = RANDOMFRAC() * 0.04907957f - 0.024539785f; rot.y = RANDOMFRAC() * 0.04907957f - 0.024539785f; rot.z = RANDOMFRAC() * 0.04907957f - 0.024539785f; #endif mtx4LoadRotation(&rot, &projectile->mtx); } debris->model->scale = obj->model->scale; debris->flags |= OBJFLAG_INVINCIBLE | OBJFLAG_BOUNCEIFSHOT | OBJFLAG_01000000; debris->flags2 |= OBJFLAG2_IMMUNETOGUNFIRE | OBJFLAG2_00200000; debris->flags3 |= OBJFLAG3_00000008; node = modelGetPart(debris->model->filedata, MODELPART_BASIC_00C8); { struct modelrwdata_toggle *rodata; if (node) { rodata = modelGetNodeRwData(debris->model, node); rodata->visible = false; } node = modelGetPart(debris->model->filedata, MODELPART_BASIC_00C9 + partindex); if (node) { rodata = modelGetNodeRwData(debris->model, node); rodata->visible = true; } } } } } void objCreateDebris(struct defaultobj *obj, struct prop *prop) { struct model *model = obj->model; s32 i; if (prop); for (i = 0; i < 20; i++) { if (modelGetPart(model->filedata, 201 + i) == NULL) { break; } objCreateOneDebris(obj, i, prop); } } struct prop *objInit(struct defaultobj *obj, struct modelfiledata *filedata, struct prop *prop, struct model *model) { if (prop == NULL) { prop = propAllocate(); } if (model == NULL) { model = modelmgrInstantiateModelWithoutAnim(filedata); } if (prop && model) { s32 geosize; obj->model = model; if (modelGetPartRodata(filedata, MODELPART_BASIC_0065)) { obj->geocount++; } if (modelGetPartRodata(filedata, MODELPART_BASIC_0066)) { obj->geocount++; } geosize = obj->geocount * 0x40; if (obj->flags & OBJFLAG_00000100) { if (obj->flags3 & OBJFLAG3_GEOCYL) { geosize += sizeof(struct geocyl); } else { geosize += sizeof(struct geoblock); } obj->geocount++; obj->hidden2 |= OBJH2FLAG_08; } else { obj->hidden2 &= ~OBJH2FLAG_08; } if (obj->geocount > 0) { obj->unkgeo = mempAlloc(ALIGN16(geosize), MEMPOOL_STAGE); } else { obj->unkgeo = NULL; } obj->prop = prop; obj->damage = 0; obj->projectile = NULL; obj->shadecol[0] = 0; obj->shadecol[1] = 0; obj->shadecol[2] = 0; obj->shadecol[3] = 0; obj->nextcol[0] = 0; obj->nextcol[1] = 0; obj->nextcol[2] = 0; obj->nextcol[3] = 0; obj->floorcol = 0xfff; obj->model->obj = obj; obj->model->unk01 = 0; modelSetScale(obj->model, g_ModelStates[obj->modelnum].scale * (1.0f / 4096.0f)); prop->type = PROPTYPE_OBJ; prop->obj = obj; prop->pos.x = 0; prop->pos.y = 0; prop->pos.z = 0; objInitToggleNodes(obj); if (obj->flags3 & OBJFLAG3_RENDERPOSTBG) { prop->flags |= PROPFLAG_RENDERPOSTBG; } if (obj->flags3 & OBJFLAG3_DRAWONTOP) { prop->flags |= PROPFLAG_DRAWONTOP; } } else { if (model) { modelmgrFreeModel(model); } if (prop) { propFree(prop); prop = NULL; } if (obj) { obj->prop = NULL; obj->model = NULL; } } if (g_Vars.normmplayerisrunning && prop && obj && obj->type == OBJTYPE_WEAPON) { struct weaponobj *weapon = (struct weaponobj *) obj; if (weapon->weaponnum == WEAPON_BRIEFCASE2) { if (g_MpSetup.scenario == MPSCENARIO_HOLDTHEBRIEFCASE) { g_ScenarioData.htb.token = prop; } prop->forcetick = true; obj->flags |= OBJFLAG_INVINCIBLE | OBJFLAG_00400000; obj->flags2 |= OBJFLAG2_IMMUNETOGUNFIRE | OBJFLAG2_00200000; } else if (weapon->weaponnum == WEAPON_DATAUPLINK) { if (g_MpSetup.scenario == MPSCENARIO_HACKERCENTRAL) { g_ScenarioData.htm.uplink = prop; } prop->forcetick = true; obj->flags |= OBJFLAG_INVINCIBLE | OBJFLAG_00400000; obj->flags2 |= OBJFLAG2_IMMUNETOGUNFIRE | OBJFLAG2_00200000; } } return prop; } struct prop *objInitWithModelDef(struct defaultobj *obj, struct modelfiledata *filedata) { return objInit(obj, filedata, NULL, NULL); } struct prop *objInitWithAutoModel(struct defaultobj *obj) { return objInitWithModelDef(obj, g_ModelStates[obj->modelnum].filedata); } void func0f06a580(struct defaultobj *obj, struct coord *pos, Mtxf *matrix, s16 *rooms) { struct prop *prop = obj->prop; mtx4ToMtx3(matrix, obj->realrot); prop->pos = *pos; propDeregisterRooms(prop); roomsCopy(rooms, prop->rooms); func0f069c70(obj, true, true); obj->shadecol[0] = obj->nextcol[0]; obj->shadecol[1] = obj->nextcol[1]; obj->shadecol[2] = obj->nextcol[2]; obj->shadecol[3] = obj->nextcol[3]; } f32 func0f06a620(struct defaultobj *obj) { if (obj->type == OBJTYPE_WEAPON) { return 0; } return 4; } void func0f06a730(struct defaultobj *obj, struct coord *arg1, Mtxf *mtx, s16 *rooms, struct coord *centre) { struct modelrodata_bbox *bbox = modelFindBboxRodata(obj->model); f32 min = objGetLocalYMin(bbox); f32 max = objGetLocalYMax(bbox); struct coord pos2; Mtxf sp70; s16 rooms2[8]; f32 curval; f32 y; f32 maxval; s32 row; bool isnegative; if (obj->flags & OBJFLAG_00000004) { mtx4LoadZRotation(M_BADPI, &sp70); mtx4MultMtx4InPlace(mtx, &sp70); pos2.x = centre->x - sp70.m[1][0] * max; pos2.y = centre->y - sp70.m[1][1] * max; pos2.z = centre->z - sp70.m[1][2] * max; } else if (obj->flags & OBJFLAG_00000008) { mtx4Copy(mtx, &sp70); pos2.x = centre->x - sp70.m[1][0] * min; pos2.y = centre->y - sp70.m[1][1] * min; pos2.z = centre->z - sp70.m[1][2] * min; } else { mtx4Copy(mtx, &sp70); row = 0; isnegative = false; // Row 0 curval = sp70.m[0][1]; if (curval < 0.0f) { curval = -curval; } { row = 0; isnegative = sp70.m[row][1] < 0.0f; maxval = curval; } // Row 1 curval = sp70.m[1][1]; if (curval < 0.0f) { curval = -curval; } if (curval > maxval) { row = 1; isnegative = sp70.m[row][1] < 0.0f; maxval = curval; } // Row 2 curval = sp70.m[2][1]; if (curval < 0.0f) { curval = -curval; } if (curval > maxval) { row = 2; isnegative = sp70.m[row][1] < 0.0f; maxval = curval; } if (row == 0) { min = objGetLocalXMin(bbox); max = objGetLocalXMax(bbox); } else if (row == 2) { min = objGetLocalZMin(bbox); max = objGetLocalZMax(bbox); } if (isnegative) { f32 tmp = min; min = max; max = tmp; } pos2.x = centre->x - sp70.m[row][0] * min; pos2.y = centre->y - sp70.m[row][1] * min; pos2.z = centre->z - sp70.m[row][2] * min; func0f065e74(arg1, rooms, &pos2, rooms2); if (cdFindFloorRoomYColourFlagsAtPos(&pos2, rooms2, &y, &obj->floorcol, NULL) > 0) { bool updated; struct defaultobj *obj2 = objFindByPos(&pos2, rooms2); u8 *start; u8 *end; struct geoblock *block; if (obj2) { updated = propUpdateGeometry(obj2->prop, &start, &end); if (updated && (block = (struct geoblock *) start, block->header.type == GEOTYPE_BLOCK) && block->ymax > y && block->ymin < y + (max - min) * sp70.m[row][1] + func0f06a620(obj)) { pos2.y = block->ymax - sp70.m[row][1] * min; obj->hidden |= OBJHFLAG_00008000; } else { pos2.y = y - min * sp70.m[row][1] + func0f06a620(obj); } } else { pos2.y = y - min * sp70.m[row][1] + func0f06a620(obj); } } } func0f065e74(arg1, rooms, &pos2, rooms2); func0f06a580(obj, &pos2, &sp70, rooms2); } void func0f06ab60(struct defaultobj *obj, struct coord *arg1, Mtxf *arg2, s16 *rooms, struct coord *arg4) { struct modelrodata_bbox *bbox; f32 mult; struct coord newpos; s16 newrooms[8]; Mtxf sp5c; Mtxf sp1c; bbox = modelFindBboxRodata(obj->model); mult = objGetLocalZMin(bbox); mtx4LoadXRotation(4.7116389274597f, &sp5c); mtx4LoadYRotation(M_BADPI, &sp1c); mtx4MultMtx4InPlace(&sp1c, &sp5c); mtx4MultMtx4InPlace(arg2, &sp5c); newpos.x = arg4->x - sp5c.m[2][0] * mult; newpos.y = arg4->y - sp5c.m[2][1] * mult; newpos.z = arg4->z - sp5c.m[2][2] * mult; func0f065e74(arg1, rooms, &newpos, newrooms); func0f06a580(obj, &newpos, &sp5c, newrooms); } void objFreeProjectile(struct defaultobj *obj) { if (obj->hidden & OBJHFLAG_PROJECTILE) { projectileFree(obj->projectile); obj->projectile = NULL; obj->hidden &= ~OBJHFLAG_PROJECTILE; } } void objFreeEmbedmentOrProjectile(struct prop *prop) { if (prop && prop->obj) { struct defaultobj *obj = prop->obj; if (obj->hidden & OBJHFLAG_EMBEDDED) { if (obj->embedment) { if (obj->embedment->projectile) { projectileFree(obj->embedment->projectile); } embedmentFree(obj->embedment); } obj->embedment = NULL; obj->hidden &= ~OBJHFLAG_EMBEDDED; } else if (obj->hidden & OBJHFLAG_PROJECTILE) { objFreeProjectile(obj); } } } /** * Remove an object from the game world by clearing all references to it. * * If freeprop is true, the obj's prop will be returned to the freeprops list. * Child objects such as attached knives and mines will always have their props * freed. */ void objFree(struct defaultobj *obj, bool freeprop, bool canregen) { struct prop *child; if (obj->type == OBJTYPE_WEAPON) { struct weaponobj *weapon = (struct weaponobj *) obj; if (weapon->dualweapon) { weapon->dualweapon->dualweapon = NULL; weapon->dualweapon = NULL; } if (weapon->weaponnum == WEAPON_PROXIMITYMINE) { weaponUnregisterProxy(weapon); } if (weapon->weaponnum == WEAPON_DRAGON && weapon->gunfunc == FUNC_SECONDARY) { weaponUnregisterProxy(weapon); } if (weapon->weaponnum == WEAPON_NBOMB && weapon->gunfunc == FUNC_SECONDARY) { weaponUnregisterProxy(weapon); } if (weapon->weaponnum == WEAPON_GRENADE && weapon->gunfunc == FUNC_SECONDARY) { weaponUnregisterProxy(weapon); smokeClearForProp(obj->prop); } if (weapon->weaponnum == WEAPON_BOLT) { s32 beammnum = boltbeamFindByProp(obj->prop); if (beammnum != -1) { boltbeamSetAutomatic(beammnum, 1400); } } if (g_Vars.normmplayerisrunning && weapon->weaponnum == WEAPON_SKROCKET && obj->projectile && obj->projectile->ownerprop) { s32 i; for (i = 0; i < g_MpNumChrs; i++) { if (g_MpAllChrPtrs[i]->aibot && g_MpAllChrPtrs[i]->aibot->skrocket == obj->prop) { g_MpAllChrPtrs[i]->aibot->skrocket = NULL; } } } } else if (obj->type == OBJTYPE_TINTEDGLASS) { struct tintedglassobj *glass = (struct tintedglassobj *) obj; if (glass->portalnum >= 0) { portalSetXluFrac(glass->portalnum, 1); portalSetOpen(glass->portalnum, true); g_BgPortals[glass->portalnum].flags |= PORTALFLAG_FORCEOPEN; } } else if (obj->type == OBJTYPE_GLASS) { struct glassobj *glass = (struct glassobj *) obj; if (glass->portalnum >= 0) { portalSetXluFrac(glass->portalnum, 1); } } else if (obj->type == OBJTYPE_DOOR) { struct doorobj *door = (struct doorobj *) obj; doorActivatePortal(door); if (door->portalnum >= 0) { g_BgPortals[door->portalnum].flags |= PORTALFLAG_FORCEOPEN; } } if (obj->prop) { s32 prevplayernum = g_Vars.currentplayernum; s32 i; for (i = 0; i < PLAYERCOUNT(); i++) { setCurrentPlayerNum(i); if (obj->prop == bmoveGetGrabbedProp() || obj->prop == bmoveGetHoverbike()) { bmoveSetMode(MOVEMODE_WALK); } invRemoveProp(obj->prop); } setCurrentPlayerNum(prevplayernum); // If obj is an occupied chair, remove the chr from it if (obj->hidden & OBJHFLAG_OCCUPIEDCHAIR) { s32 numchrs = chrsGetNumSlots(); s32 i; obj->hidden &= ~OBJHFLAG_OCCUPIEDCHAIR; for (i = 0; i < numchrs; i++) { // @bug: Should be ==, but this isn't a problem because occupied // chairs are never removed using this code. If they were, and // the propnum was > 0, all chrs would be disassociated with // their chairs but their chairs would still have the occupied // flag. If the propnum was 0, all chrs would be assigned to // this one chair, and their original chairs would still have // the occupied flag. if ((g_ChrSlots[i].proppreset1 = obj->prop - g_Vars.props)) { g_ChrSlots[i].proppreset1 = -1; } } } // Remove references from aibots if the obj is the item being fetched if (g_Vars.normmplayerisrunning) { s32 i; for (i = 0; i < g_MpNumChrs; i++) { if (g_MpAllChrPtrs[i]->aibot && g_MpAllChrPtrs[i]->aibot->gotoprop == obj->prop) { g_MpAllChrPtrs[i]->aibot->gotoprop = NULL; } } } func0f0926bc(obj->prop, 1, 0xffff); shieldhitsRemoveByProp(obj->prop); chrClearReferences(obj->prop - g_Vars.props); projectilesUnrefOwner(obj->prop); wallhitsFreeByProp(obj->prop, 0); wallhitsFreeByProp(obj->prop, 1); objFreeEmbedmentOrProjectile(obj->prop); child = obj->prop->child; while (child) { struct prop *next = child->next; objFreePermanently(child->obj, true); child = next; } if (!canregen) { if (obj->prop->parent) { objDetach(obj->prop); } propDeregisterRooms(obj->prop); if (obj->prop->type != PROPTYPE_DOOR) { modelFreeVertices(1, obj->model); } modelmgrFreeModel(obj->model); if (freeprop) { propDelist(obj->prop); propDisable(obj->prop); propFree(obj->prop); } obj->prop->obj = NULL; obj->prop = NULL; } } } void objFreePermanently(struct defaultobj *obj, bool freeprop) { objFree(obj, freeprop, false); } f32 objGetRadius(struct defaultobj *obj) { if (obj->type == OBJTYPE_KEY) { return 20; } return 10; } bool func0f06b39c(struct coord *arg0, struct coord *arg1, struct coord *arg2, f32 arg3) { struct coord sp0c; f32 value; sp0c.x = arg2->x - arg0->x; sp0c.y = arg2->y - arg0->y; sp0c.z = arg2->z - arg0->z; value = arg1->f[0] * sp0c.f[0] + arg1->f[1] * sp0c.f[1] + arg1->f[2] * sp0c.f[2]; if (value > 0) { f32 a = arg1->f[0] * arg1->f[0] + arg1->f[1] * arg1->f[1] + arg1->f[2] * arg1->f[2]; f32 b = sp0c.f[0] * sp0c.f[0] + sp0c.f[1] * sp0c.f[1] + sp0c.f[2] * sp0c.f[2]; if ((b - arg3 * arg3) * a <= value * value) { return true; } } return false; } bool func0f06b488(struct prop *prop, struct coord *arg1, struct coord *arg2, struct coord *arg3, struct coord *arg4, struct coord *arg5, f32 *arg6) { struct coord sp3c; struct coord sp30; f32 f0; struct coord sp20; if (!cd0002ded8(arg1, arg2, prop)) { cdGetEdge(&sp3c, &sp30, 2909, "propobj.c"); cdGetPos(&sp20, 2910, "propobj.c"); f0 = (sp20.f[0] - arg1->f[0]) * arg3->f[0] + (sp20.f[1] - arg1->f[1]) * arg3->f[1] + (sp20.f[2] - arg1->f[2]) * arg3->f[2]; if (f0 < *arg6) { *arg6 = f0; *arg4 = sp20; arg5->x = -arg3->x; arg5->y = 0.0f; arg5->z = -arg3->z; if (arg5->x != 0.0f || arg5->z != 0.0f) { guNormalize(&arg5->x, &arg5->y, &arg5->z); } else { arg5->z = 1.0f; } g_EmbedProp = prop; g_EmbedHitPart = 0; g_EmbedModel = NULL; g_EmbedNode = NULL; return true; } } return false; } bool func0f06b610(struct defaultobj *obj, struct coord *arg1, struct coord *arg2, struct coord *arg3, f32 arg4, struct coord *arg5, struct coord *arg6, struct coord *arg7, struct coord *arg8, f32 *arg9) { struct model *model = obj->model; f32 f0 = model0001af80(model); f32 xdiff; f32 ydiff; f32 zdiff; f32 sum1 = 0.0f; struct prop *prop = obj->prop; struct prop *child; bool result = false; f32 sum2; struct coord spfc; struct coord spf0; struct modelnode *node1; s32 hitpart; struct modelnode *spe4 = NULL; struct hitthing thing1; s32 mtxindex1; struct modelnode *node; struct hitthing thing2; s32 mtxindex2; struct modelnode *node2; f32 sum3; if (prop->parent == NULL) { xdiff = prop->pos.f[0] - arg1->f[0]; ydiff = prop->pos.f[1] - arg1->f[1]; zdiff = prop->pos.f[2] - arg1->f[2]; sum1 = xdiff * arg3->f[0] + ydiff * arg3->f[1] + zdiff * arg3->f[2]; } if (sum1 >= -f0 && sum1 <= arg4 + f0) { if (prop->flags & PROPFLAG_ONTHISSCREENTHISTICK) { if (var8005efc0 > 0.0f) { hitpart = model000225d4(model, arg5, arg6, &spe4); while (hitpart > 0) { if (func0f084594(model, spe4, arg5, arg6, &thing1, &mtxindex1, &node1)) { mtx4TransformVec(&model->matrices[mtxindex1], &thing1.unk00, &spfc); sum2 = (spfc.f[0] - arg5->f[0]) * arg6->f[0] + (spfc.f[1] - arg5->f[1]) * arg6->f[1] + (spfc.f[2] - arg5->f[2]) * arg6->f[2]; if (sum2 < *arg9) { mtx4RotateVec(&model->matrices[mtxindex1], &thing1.unk0c, &spf0); *arg9 = sum2; mtx4TransformVec(camGetProjectionMtxF(), &spfc, arg7); mtx4RotateVec(camGetProjectionMtxF(), &spf0, arg8); if (arg8->x != 0.0f || arg8->y != 0.0f || arg8->z != 0.0f) { guNormalize(&arg8->x, &arg8->y, &arg8->z); } else { arg8->z = 1.0f; } g_EmbedProp = prop; g_EmbedModel = model; g_EmbedHitPart = hitpart; g_EmbedNode = spe4; g_EmbedSide = thing1.unk28 / 2; var8006993c[0] = thing1.unk00.x; var8006993c[1] = thing1.unk00.y; var8006993c[2] = thing1.unk00.z; result = 1; } } hitpart = model000225d4(model, arg5, arg6, &spe4); } } else { do { hitpart = model000225d4(model, arg5, arg6, &spe4); if (hitpart > 0) { if (func0f0849dc(model, spe4, arg5, arg6, &thing1, &mtxindex1, &node1)) { break; } } } while (hitpart > 0); if (obj->flags3 & OBJFLAG3_HOVERBEDSHIELD) { node = modelGetPart(model->filedata, MODELPART_BASIC_0067); if (node && func0f084594(model, node, arg5, arg6, &thing2, &mtxindex2, &node2)) { if (hitpart <= 0 || + model->matrices[mtxindex2].m[0][2] * thing2.unk00.f[0] + model->matrices[mtxindex2].m[1][2] * thing2.unk00.f[1] + model->matrices[mtxindex2].m[2][2] * thing2.unk00.f[2] > + model->matrices[mtxindex1].m[0][2] * thing1.unk00.f[0] + model->matrices[mtxindex1].m[1][2] * thing1.unk00.f[1] + model->matrices[mtxindex1].m[2][2] * thing1.unk00.f[2] ) { hitpart = 1; thing1 = thing2; mtxindex1 = mtxindex2; node1 = node2; thing1.texturenum = 10000; } } } if (hitpart > 0) { mtx4TransformVec(&model->matrices[mtxindex1], &thing1.unk00, &spfc); sum3 = (spfc.f[0] - arg5->f[0]) * arg6->f[0] + (spfc.f[1] - arg5->f[1]) * arg6->f[1] + (spfc.f[2] - arg5->f[2]) * arg6->f[2]; if (sum3 >= 0.0f && sum3 <= *arg9) { mtx4RotateVec(&model->matrices[mtxindex1], &thing1.unk0c, &spf0); *arg9 = sum1; mtx4TransformVec(camGetProjectionMtxF(), &spfc, arg7); if (spf0.f[0] * arg6->f[0] + spf0.f[1] * arg6->f[1] + spf0.f[2] * arg6->f[2] > 0.0f) { spf0.f[0] = -spf0.f[0]; spf0.f[1] = -spf0.f[1]; spf0.f[2] = -spf0.f[2]; } mtx4RotateVec(camGetProjectionMtxF(), &spf0, arg8); if (arg8->f[0] != 0.0f || arg8->f[1] != 0.0f || arg8->f[2] != 0.0f) { guNormalize(&arg8->x, &arg8->y, &arg8->z); } else { arg8->z = 1.0f; } g_EmbedProp = prop; g_EmbedHitPart = hitpart; g_EmbedModel = model; g_EmbedNode = node1; var80069944 = thing1.texturenum; result = true; if (thing1.texturenum == 10000) { g_EmbedSide = thing1.unk28 / 2; var8006993c[0] = thing1.unk00.x; var8006993c[1] = thing1.unk00.y; var8006993c[2] = thing1.unk00.z; } } } } } else { if (func0f06b39c(arg1, arg3, &prop->pos, model0001af80(model)) && func0f06b488(prop, arg1, arg2, arg3, arg7, arg8, arg9)) { g_EmbedModel = model; g_EmbedNode = model->filedata->rootnode; result = true; } } } if (prop->flags & PROPFLAG_ONTHISSCREENTHISTICK) { child = prop->child; while (child) { if (child->flags & PROPFLAG_ONTHISSCREENTHISTICK) { if (func0f06b610(child->obj, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)) { result = true; } } child = child->next; } } return result; } s32 func0f06be44(struct modelnode *rootnode) { s32 count = 0; struct modelnode *node = rootnode; while (node) { count++; if (node->child) { count += func0f06be44(node->child); } node = node->next; } return count; } bool func0f06bea0(struct model *model, struct modelnode *endnode, struct modelnode *node, struct coord *arg3, struct coord *arg4, void *arg5, f32 *arg6, struct modelnode **arg7, s32 *hitpart, s32 *arg9, struct modelnode **arg10) { u32 stack; union modelrodata *rodata; bool ok = false; f32 sp98 = MAXFLOAT; Gfx *s4; Gfx *s6; struct gfxvtx *vertices; struct modelnode *sp88; struct modelnode *sp84 = NULL; bool s7; struct coord sp74; struct modelrwdata_dl *rwdata; vertices = NULL; s7 = false; var8005efc0 = 2.5f / model->scale; sp74.x = arg3->x + arg4->f[0] * 32767.0f; sp74.y = arg3->y + arg4->f[1] * 32767.0f; sp74.z = arg3->z + arg4->f[2] * 32767.0f; g_Vars.hitboundscount = 0; while (node) { u32 type = node->type & 0xff; s4 = NULL; s6 = NULL; switch (type) { case MODELNODETYPE_BBOX: rodata = node->rodata; if (model000220fc(&rodata->bbox, model0001a5cc(model, node, 0), arg3, arg4)) { s7 = true; sp84 = node; if (g_Vars.hitboundscount < ARRAYCOUNT(g_Vars.hitnodes)) { g_Vars.hitnodes[g_Vars.hitboundscount] = node; g_Vars.hitboundscount++; } } else { s7 = false; var8005efc0 = 10.0f / model->scale; if (model000220fc(&rodata->bbox, model0001a5cc(model, node, 0), arg3, arg4)) { if (g_Vars.hitboundscount < ARRAYCOUNT(g_Vars.hitnodes)) { g_Vars.hitnodes[g_Vars.hitboundscount] = node; g_Vars.hitboundscount++; } } var8005efc0 = 2.5f / model->scale; } break; case MODELNODETYPE_DL: if (s7) { rodata = node->rodata; rwdata = modelGetNodeRwData(model, node); if (rwdata->gdl != NULL) { if (rwdata->gdl == rodata->dl.primary) { s4 = (Gfx *)((u32)rodata->dl.colourtable + ((u32)rodata->dl.primary & 0xffffff)); } else { s4 = rwdata->gdl; } if (rodata->dl.secondary != NULL) { s6 = (Gfx *)((u32)rodata->dl.colourtable + ((u32)rodata->dl.secondary & 0xffffff)); } vertices = rwdata->vertices; } } break; case MODELNODETYPE_GUNDL: if (s7) { if (node->rodata->gundl.primary != NULL) { s32 base = (s32)node->rodata->gundl.baseaddr; s4 = (Gfx *)(base + ((u32)node->rodata->gundl.primary & 0xffffff)); if (node->rodata->gundl.secondary != NULL) { s6 = (Gfx *)(base + ((u32)node->rodata->gundl.secondary & 0xffffff)); } vertices = (struct gfxvtx *)base; } } break; case MODELNODETYPE_DISTANCE: model0001c784(model, node); break; case MODELNODETYPE_TOGGLE: model0001c7d0(model, node); break; case MODELNODETYPE_HEADSPOT: modelAttachHead(model, node); break; } if (s4 && bgTestHitOnChr(model, arg3, &sp74, arg4, s4, s6, vertices, &sp98, arg5)) { ok = true; sp88 = node; *arg7 = sp84; *hitpart = sp84->rodata->bbox.hitpart; } if (node->child) { node = node->child; } else { while (node) { if (node == endnode) { node = NULL; break; } if (node->next) { node = node->next; break; } node = node->parent; } } } if (ok) { *arg6 = sqrtf(sp98); *arg10 = sp88; *arg9 = model0001a524(sp88, 0); } var8005efc0 = 0.0f; return ok; } bool func0f06c28c(struct chrdata *chr, struct coord *arg1, struct coord *arg2, struct coord *arg3, f32 arg4, struct coord *arg5, struct coord *arg6, struct coord *arg7, struct coord *arg8, f32 *arg9) { f32 spec; struct prop *prop = chr->prop; f32 spe4 = chr0f0278a4(chr); f32 x = (prop->pos.f[0] - arg1->f[0]); f32 y = (prop->pos.f[1] - arg1->f[1]); f32 z = (prop->pos.f[2] - arg1->f[2]); f32 spd4 = x * arg3->f[0] + y * arg3->f[1] + z * arg3->f[2]; s32 hitpart = 0; struct modelnode *spcc = NULL; bool result = false; struct prop *child; struct coord spb8; struct coord spac; struct hitthing sp7c; s32 sp78 = 0; struct modelnode *sp74 = NULL; struct model *model = chr->model; if (chrGetShield(chr) > 0.0f) { var8005efc0 = 10.0f / chr->model->scale; } if (-spe4 <= spd4 && spd4 <= arg4 + spe4 && func0f06b39c(arg1, arg3, &prop->pos, spe4)) { if ((prop->flags & PROPFLAG_ONTHISSCREENTHISTICK)) { if (var8005efc0 > 0.0f) { hitpart = model000225d4(model, arg5, arg6, &spcc); while (hitpart > 0) { if (func0f084594(model, spcc, arg5, arg6, &sp7c, &sp78, &sp74)) { mtx4TransformVec(&model->matrices[sp78], &sp7c.unk00, &spb8); spec = (spb8.f[0] - arg5->f[0]) * arg6->f[0] + (spb8.f[1] - arg5->f[1]) * arg6->f[1] + (spb8.f[2] - arg5->f[2]) * arg6->f[2]; if (spec < *arg9) { mtx4RotateVec(&model->matrices[sp78], &sp7c.unk0c, &spac); *arg9 = spec; mtx4TransformVec(camGetProjectionMtxF(), &spb8, arg7); mtx4RotateVec(camGetProjectionMtxF(), &spac, arg8); if (arg8->x != 0.0f || arg8->y != 0.0f || arg8->z != 0.0f) { guNormalize(&arg8->x, &arg8->y, &arg8->z); } else { arg8->z = 1.0f; } g_EmbedProp = prop; g_EmbedModel = model; g_EmbedHitPart = hitpart; g_EmbedNode = spcc; g_EmbedSide = sp7c.unk28 / 2; var8006993c[0] = sp7c.unk00.x; var8006993c[1] = sp7c.unk00.y; var8006993c[2] = sp7c.unk00.z; result = true; } } hitpart = model000225d4(model, arg5, arg6, &spcc); } } else { hitpart = model000225d4(model, arg5, arg6, &spcc); if (hitpart > 0 && func0f06bea0(model, model->filedata->rootnode, model->filedata->rootnode, arg5, arg6, &sp7c.unk00, &spec, &spcc, &hitpart, &sp78, &sp74) && spec < *arg9) { *arg9 = spec; mtx4TransformVec(camGetProjectionMtxF(), &sp7c.unk00, arg7); mtx4RotateVec(camGetProjectionMtxF(), &sp7c.unk0c, arg8); if (arg8->x != 0.0f || arg8->y != 0.0f || arg8->z != 0.0f) { guNormalize(&arg8->x, &arg8->y, &arg8->z); } else { arg8->z = 1.0f; } g_EmbedProp = prop; g_EmbedModel = model; g_EmbedHitPart = hitpart; g_EmbedNode = spcc; result = true; } } } else if (func0f06b488(prop, arg1, arg2, arg3, arg7, arg8, arg9)) { g_EmbedHitPart = HITPART_TORSO; result = true; } } if (prop->flags & PROPFLAG_ONTHISSCREENTHISTICK) { child = prop->child; while (child) { if (child->flags & PROPFLAG_ONTHISSCREENTHISTICK) { if (func0f06b610(child->obj, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)) { result = true; } } child = child->next; } } if (var8005efc0 > 0.0f) { var8005efc0 = 0.0f; } return result; } bool projectileFindCollidingProp(struct prop *prop, struct coord *pos1, struct coord *pos2, u32 cdtypes, struct coord *arg4, struct coord *arg5, s16 *rooms) { bool result = false; f32 dist; s16 *propnumptr; s16 propnums[256]; f32 spa8; bool spa4 = false; struct coord sp98; f32 tmp; struct coord sp88; struct coord sp7c; struct chrdata *chr; sp98.x = pos2->x - pos1->x; sp98.y = pos2->y - pos1->y; sp98.z = pos2->z - pos1->z; dist = sqrtf(sp98.f[0] * sp98.f[0] + sp98.f[1] * sp98.f[1] + sp98.f[2] * sp98.f[2]); if (dist == 0.0f) { return false; } tmp = 1.0f / dist; sp98.x *= tmp; sp98.y *= tmp; sp98.z *= tmp; sp88 = *pos1; mtx4TransformVecInPlace(camGetWorldToScreenMtxf(), &sp88); sp7c = sp98; mtx4RotateVecInPlace(camGetWorldToScreenMtxf(), &sp7c); spa8 = dist; if (cdtypes != 0) { roomGetProps(rooms, propnums, 256); for (propnumptr = propnums; *propnumptr >= 0; propnumptr++) { struct prop *iterprop = &g_Vars.props[*propnumptr]; if (*propnumptr); if (iterprop != prop) { if (iterprop->type & (PROPTYPE_OBJ | PROPTYPE_WEAPON | PROPTYPE_DOOR)) { struct defaultobj *obj = iterprop->obj; if ((obj->hidden & OBJHFLAG_ISRETICK) == 0 && (obj->flags2 & OBJFLAG2_THROWTHROUGH) == 0) { if (iterprop->type == PROPTYPE_DOOR) { if ((cdtypes & CDTYPE_DOORS) == 0 && (propDoorGetCdTypes(iterprop) & cdtypes) == 0) { continue; } } else { if ((cdtypes & CDTYPE_OBJS) == 0) { continue; } } if (func0f06b610(obj, pos1, pos2, &sp98, dist, &sp88, &sp7c, arg4, arg5, &spa8)) { spa4 = true; } } } else if (iterprop->type == PROPTYPE_CHR || (iterprop->type == PROPTYPE_PLAYER && g_Vars.players[playermgrGetPlayerNumByProp(iterprop)]->haschrbody)) { struct chrdata *chr = iterprop->chr; if (iterprop->type == PROPTYPE_PLAYER) { if (!g_Vars.players[playermgrGetPlayerNumByProp(iterprop)]->bondperimenabled || (cdtypes & CDTYPE_PLAYERS) == 0) { continue; } } else if (iterprop->type == PROPTYPE_CHR) { if ((chr->hidden & CHRHFLAG_PERIMDISABLED) || (chr->chrflags & CHRCFLAG_HIDDEN) || (cdtypes & CDTYPE_CHRS) == 0) { continue; } } if (func0f06c28c(chr, pos1, pos2, &sp98, dist, &sp88, &sp7c, arg4, arg5, &spa8)) { spa4 = true; } } else if (iterprop->type == PROPTYPE_PLAYER && g_Vars.players[playermgrGetPlayerNumByProp(iterprop)]->bondperimenabled) { if (func0f06b488(iterprop, pos1, pos2, &sp98, arg4, arg5, &spa8)) { spa4 = true; } } } } } if (spa4) { result = true; var8009ce78 = sp98; var8009ce88 = sp7c; } return result; } s32 func0f06cd00(struct defaultobj *obj, struct coord *pos, struct coord *arg2, struct coord *arg3) { struct prop *prop = obj->prop; s32 cdresult; struct hitthing hitthing; struct coord sp1c4; u32 stack; bool s0; s16 spcc[120]; s16 *ptr; s16 spb8[8]; s32 i; f32 scale = 1.0f; cdresult = CDRESULT_NOCOLLISION; if (g_Vars.normmplayerisrunning) { g_Vars.useperimshoot = true; } g_EmbedProp = 0; var80069944 = 0; sp1c4 = *pos; if ((prop->pos.x != pos->x || prop->pos.y != pos->y || prop->pos.z != pos->z) && (obj->hidden & OBJHFLAG_PROJECTILE) && (obj->projectile->flags & PROJECTILEFLAG_STICKY)) { portal00018148(&prop->pos, &sp1c4, prop->rooms, spb8, spcc, 20); ptr = spcc; while (*ptr != -1) { ptr++; } roomsGetActive(ptr, 100); for (i = 0; spcc[i] != -1; i++) { s0 = false; if (roomIsLoaded(spcc[i])) { if (bgTestHitInRoom(&prop->pos, &sp1c4, spcc[i], &hitthing)) { hitthing.unk00.x *= scale; hitthing.unk00.y *= scale; hitthing.unk00.z *= scale; var80069944 = hitthing.texturenum; s0 = true; if (g_Textures[hitthing.texturenum].surfacetype == SURFACETYPE_DEEPWATER) { struct coord spa4 = {0, 0, 0}; s0 = false; sparksCreate(prop->rooms[0], prop, &hitthing.unk00, &spa4, &hitthing.unk0c, SPARKTYPE_DEEPWATER); propsnd0f0939f8(0, prop, SFX_HIT_WATER, -1, -1, 1024, 0, 0, 0, -1.0f, 0, -1, -1.0f, -1.0f, -1.0f); obj->hidden |= OBJHFLAG_REAPABLE; } } } else { s16 spa0[2]; spa0[0] = spcc[i]; spa0[1] = -1; if (cdExamLos09(&prop->pos, spa0, &sp1c4, CDTYPE_BG) == CDRESULT_COLLISION) { s0 = true; cdGetPos(&hitthing.unk00, 4257, "propobj.c"); cdGetObstacleNormal(&hitthing.unk0c); } } if (s0 && ((prop->pos.x <= sp1c4.x && hitthing.unk00.x <= sp1c4.x && prop->pos.x <= hitthing.unk00.x) || (sp1c4.x <= prop->pos.x && sp1c4.x <= hitthing.unk00.x && hitthing.unk00.x <= prop->pos.x)) && ((prop->pos.y <= sp1c4.y && hitthing.unk00.y <= sp1c4.y && prop->pos.y <= hitthing.unk00.y) || (sp1c4.y <= prop->pos.y && sp1c4.y <= hitthing.unk00.y && hitthing.unk00.y <= prop->pos.y)) && ((prop->pos.z <= sp1c4.z && hitthing.unk00.z <= sp1c4.z && prop->pos.z <= hitthing.unk00.z) || (sp1c4.z <= prop->pos.z && sp1c4.z <= hitthing.unk00.z && hitthing.unk00.z <= prop->pos.z)) && (prop->pos.f[0] != hitthing.unk00.f[0] || prop->pos.f[1] != hitthing.unk00.f[1] || prop->pos.f[2] != hitthing.unk00.f[2])) { cdresult = CDRESULT_COLLISION; sp1c4 = hitthing.unk00; *arg3 = hitthing.unk0c; } } if (!projectileFindCollidingProp(prop, &prop->pos, &sp1c4, CDTYPE_ALL, arg2, arg3, spcc)) { if (cdresult == CDRESULT_COLLISION) { *arg2 = sp1c4; } } else { cdresult = CDRESULT_COLLISION; } if (cdresult != CDRESULT_NOCOLLISION) { struct coord dist; f32 sqdist; f32 mult; dist.x = pos->x - prop->pos.x; dist.y = pos->y - prop->pos.y; dist.z = pos->z - prop->pos.z; sqdist = dist.f[0] * dist.f[0] + dist.f[1] * dist.f[1] + dist.f[2] * dist.f[2]; if (sqdist > 0.01f) { mult = 0.1f / sqrtf(sqdist); } else { mult = 0.5f; } arg2->x -= mult * dist.x; arg2->y -= mult * dist.y; arg2->z -= mult * dist.z; if (arg3->x != 0.0f || arg3->y != 0.0f || arg3->z != 0.0f) { guNormalize(&arg3->x, &arg3->y, &arg3->z); } else { arg3->z = 1.0f; } } } if (g_Vars.normmplayerisrunning) { g_Vars.useperimshoot = false; } return cdresult; } bool func0f06d37c(struct defaultobj *obj, struct coord *arg1, struct coord *arg2, struct coord *arg3) { struct prop *prop = obj->prop; f32 radius = objGetRadius(obj); bool result = true; bool sp98 = false; struct coord sp8c; struct coord sp80; s16 rooms[8]; struct coord sp64; struct coord sp58; struct coord sp4c; f32 f2; g_EmbedProp = NULL; var80069944 = 0; sp80 = *arg1; if (prop->pos.x != arg1->x || prop->pos.y != arg1->y || prop->pos.z != arg1->z) { if (obj->hidden & OBJHFLAG_PROJECTILE) { if (cdExamCylMove08(&prop->pos, prop->rooms, &sp80, rooms, radius, CDTYPE_ALL, false, 0.0f, 0.0f) != CDRESULT_COLLISION) { setup0f09233c(obj, &sp80, obj->realrot, rooms); if (cdExamCylMove02(&prop->pos, &sp80, radius, rooms, CDTYPE_ALL, false, 0.0f, 0.0f) != CDRESULT_COLLISION) { prop->pos = sp80; propDeregisterRooms(prop); roomsCopy(rooms, prop->rooms); } else { result = false; } } else { result = false; } if (!result) { cdGetEdge(&sp64, &sp58, 4385, "propobj.c"); arg3->x = sp58.z - sp64.z; arg3->y = 0.0f; arg3->z = sp64.x - sp58.x; if (arg3->x != 0.0f || arg3->z != 0.0f) { guNormalize(&arg3->x, &arg3->y, &arg3->z); } else { arg3->z = 1.0f; } if (sp80.x != prop->pos.x || sp80.y != prop->pos.y || sp80.z != prop->pos.z) { sp8c.x = sp80.x - prop->pos.x; sp8c.y = sp80.y - prop->pos.y; sp8c.z = sp80.z - prop->pos.z; func0f02e4f8(&prop->pos, &sp8c, arg2); if (prop->pos.x < sp80.x) { if (arg2->x > sp80.x) { arg2->x = sp80.x; } else if (arg2->x < prop->pos.x) { arg2->x = prop->pos.x; } } else { if (arg2->x > prop->pos.x) { arg2->x = prop->pos.x; } else if (arg2->x < sp80.x) { arg2->x = sp80.x; } } if (prop->pos.y < sp80.y) { if (arg2->y > sp80.y) { arg2->y = sp80.y; } else if (arg2->y < prop->pos.y) { arg2->y = prop->pos.y; } } else { if (arg2->y > prop->pos.y) { arg2->y = prop->pos.y; } else if (arg2->y < sp80.y) { arg2->y = sp80.y; } } if (prop->pos.z < sp80.z) { if (arg2->z > sp80.z) { arg2->z = sp80.z; } else if (arg2->z < prop->pos.z) { arg2->z = prop->pos.z; } } else { if (arg2->z > prop->pos.z) { arg2->z = prop->pos.z; } else if (arg2->z < sp80.z) { arg2->z = sp80.z; } } f2 = cd00024e98() * 0.99f; sp4c.x = sp8c.x * f2 + prop->pos.x; sp4c.y = sp80.y; sp4c.z = sp8c.z * f2 + prop->pos.z; if (cdExamCylMove07(&prop->pos, prop->rooms, &sp4c, rooms, CDTYPE_ALL, false, 0.0f, 0.0f) != CDRESULT_COLLISION) { setup0f09233c(obj, &sp4c, obj->realrot, rooms); if (cdTestVolume(&sp4c, radius, rooms, CDTYPE_ALL, CHECKVERTICAL_NO, 0.0f, 0.0f) != CDRESULT_COLLISION) { prop->pos = sp4c; propDeregisterRooms(prop); roomsCopy(rooms, prop->rooms); sp98 = true; } } } else { *arg2 = sp80; } if (!sp98) { sp4c.x = prop->pos.x; sp4c.y = sp80.y; sp4c.z = prop->pos.z; func0f065e74(&prop->pos, prop->rooms, &sp4c, rooms); prop->pos.y = sp4c.y; propDeregisterRooms(prop); roomsCopy(rooms, prop->rooms); } } } } return result; } /** * Update a speed and distance travelled, factoring in acceleration, * deceleration and the global update multiplier. * * The new speed and distance done are written back to those pointers. */ void applySpeed(f32 *distdone, f32 maxdist, f32 *speedptr, f32 accel, f32 decel, f32 maxspeed) { f32 speed = *speedptr; s32 i; for (i = 0; i < g_Vars.lvupdate60; i++) { f32 limit = speed * speed * 0.5f / decel; f32 distremaining = maxdist - *distdone; if (distremaining > 0.0f) { if (speed > 0.0f && distremaining <= limit) { // Slow down for end speed -= decel; if (speed < decel) { speed = decel; } } else if (speed < maxspeed) { // Accelerate if (speed < 0.0f) { speed += decel; } else { speed += accel; } if (speed > maxspeed) { speed = maxspeed; } } if (speed >= distremaining) { *distdone = maxdist; break; } *distdone += speed; } else { if (speed < 0.0f && -distremaining <= limit) { speed += decel; if (speed > -decel) { speed = -decel; } } else if (speed > -maxspeed) { if (speed > 0.0f) { speed -= decel; } else { speed -= accel; } if (speed < -maxspeed) { speed = -maxspeed; } } if (speed <= distremaining) { *distdone = maxdist; break; } *distdone += speed; } } *speedptr = speed; } void applyRotation(f32 *angle, f32 maxrot, f32 *speed, f32 accel, f32 decel, f32 maxspeed) { f32 tmp = maxrot - *angle; if (tmp < -M_PI) { maxrot += M_BADTAU; } else if (tmp >= M_PI) { maxrot -= M_BADTAU; } applySpeed(angle, maxrot, speed, accel, decel, maxspeed); if (*angle < 0) { *angle += M_BADTAU; } if (*angle >= M_BADTAU) { *angle -= M_BADTAU; } } #define NEXT(i) ((i + 1) % 3) #define PREV(i) ((i + 2) % 3) /** * Make a projectile fall to the ground once it's hit a wall. * * The vast majority of this is calculating the rotation for the projectile. */ void projectileFall(struct defaultobj *obj, f32 arg1[3][3]) { s32 t2; s32 t4; s32 t3; struct coord sp188; Mtxf sp148; Mtxf sp108; Mtxf spc8; Mtxf sp88; f32 sp84; f32 sp80; struct modelrodata_bbox *bbox; s32 i; u32 stack[2]; f32 sp6c; struct projectile *projectile; f32 f2; f32 sp58[3]; f32 sp4c[3]; f32 sp40[3]; obj->hidden &= ~OBJHFLAG_00010000; if (obj->hidden & OBJHFLAG_PROJECTILE) { projectile = obj->projectile; if (obj->type == OBJTYPE_DOOR) { objFreeProjectile(obj); return; } projectile->ownerprop = NULL; projectile->flags &= ~PROJECTILEFLAG_AIRBORNE; projectile->flags |= PROJECTILEFLAG_FALLING; projectile->flags &= ~PROJECTILEFLAG_STICKY; mtx3ToMtx4(obj->realrot, &sp148); mtx4GetRotation(sp148.m, &sp188); mtx4LoadRotation(&sp188, &sp108); quaternion0f096ca0(&sp188, projectile->unk068); mtx4LoadRotationFrom(sp108.m, spc8.m); mtx4MultMtx4(&spc8, &sp148, &sp88); projectile->unk0b8[0] = sqrtf(sp88.m[0][0] * sp88.m[0][0] + sp88.m[0][1] * sp88.m[0][1] + sp88.m[0][2] * sp88.m[0][2]); projectile->unk0b8[1] = sqrtf(sp88.m[1][0] * sp88.m[1][0] + sp88.m[1][1] * sp88.m[1][1] + sp88.m[1][2] * sp88.m[1][2]); projectile->unk0b8[2] = sqrtf(sp88.m[2][0] * sp88.m[2][0] + sp88.m[2][1] * sp88.m[2][1] + sp88.m[2][2] * sp88.m[2][2]); t2 = -1; t4 = -1; t3 = -1; bbox = objFindBboxRodata(obj); sp4c[0] = bbox->xmax - bbox->xmin; sp4c[1] = bbox->ymax - bbox->ymin; sp4c[2] = bbox->zmax - bbox->zmin; for (i = 0; i < 3; i++) { sp58[i] = sp4c[i] * projectile->unk0b8[i]; sp40[i] = obj->realrot[i][1] * sp4c[i]; if (sp40[i] < 0.0f) { sp40[i] = -sp40[i]; } } if (obj->flags3 & (OBJFLAG3_00000008 | OBJFLAG3_00000200 | OBJFLAG3_08000000)) { if (obj->flags3 & OBJFLAG3_00000008) { for (i = 0; i < 3; i++) { if (sp58[i] < sp58[NEXT(i)] && sp58[i] < sp58[PREV(i)]) { t4 = i; break; } } } else { t4 = 1; } if (sp40[(t4 + 2) % 3] <= sp40[(t4 + 1) % 3]) { t2 = (t4 + 1) % 3; t3 = (t4 + 2) % 3; } else { t2 = (t4 + 2) % 3; t3 = (t4 + 1) % 3; } } if (t2 < 0) { for (i = 0; i < 3; i++) { if (sp58[i] > sp58[NEXT(i)] * 3.0f && sp58[i] > sp58[PREV(i)] * 3.0f) { t2 = i; if (sp58[NEXT(i)] > sp58[PREV(i)] * 2.0f) { t4 = PREV(i); t3 = NEXT(i); } else if (sp58[PREV(i)] > sp58[NEXT(i)] * 2.0f) { t4 = NEXT(i); t3 = PREV(i); } else { if ((random() % 2) == 0) { t4 = PREV(i); t3 = NEXT(i); } else { t4 = NEXT(i); t3 = PREV(i); } } break; } } } if (t2 < 0) { for (i = 0; i < 3; i++) { if (sp58[i] > sp58[NEXT(i)] * 3.0f || sp58[i] > sp58[PREV(i)] * 3.0f) { if (sp58[i] > sp58[NEXT(i)] * 3.0f) { t4 = NEXT(i); } else if (sp58[i] > sp58[PREV(i)] * 3.0f) { t4 = PREV(i); } if (sp40[(t4 + 2) % 3] <= sp40[(t4 + 1) % 3]) { t2 = (t4 + 1) % 3; t3 = (t4 + 2) % 3; } else { t2 = (t4 + 2) % 3; t3 = (t4 + 1) % 3; } break; } } } if (t2 < 0) { for (i = 0; i < 3; i++) { if (sp40[i] >= sp40[NEXT(i)] && sp40[i] >= sp40[PREV(i)]) { t4 = i; if (sp40[PREV(i)] <= sp40[NEXT(i)]) { t3 = PREV(i); t2 = NEXT(i); } else { t2 = PREV(i); t3 = NEXT(i); } break; } } } if (t2 < 0) { t2 = 0; t4 = 1; t3 = 2; } sp84 = obj->realrot[t2][0]; sp80 = obj->realrot[t2][2]; if (sp84 != 0.0f || sp80 != 0.0f) { f32 f0 = sqrtf(sp84 * sp84 + sp80 * sp80); if (f0 > 0.0f) { f0 = 1.0f / f0; sp84 *= f0; sp80 *= f0; } else { sp84 = 0.0f; sp80 = 1.0f; } } else { sp84 = 0.0f; sp80 = 1.0f; } spc8.m[t2][0] = sp84; spc8.m[t2][1] = 0.0f; spc8.m[t2][2] = sp80; spc8.m[t2][3] = 0.0f; if (((obj->realrot[t4][1] >= 0.0f || (obj->flags3 & OBJFLAG3_08000000)) && t3 == ((t4 + 1) % 3)) || (obj->realrot[t4][1] <= 0.0f && (obj->flags3 & OBJFLAG3_08000000) == 0 && t3 == (t4 + 2) % 3)) { spc8.m[t3][0] = -sp80; spc8.m[t3][1] = 0.0f; spc8.m[t3][2] = sp84; spc8.m[t3][3] = 0.0f; } else { spc8.m[t3][0] = sp80; spc8.m[t3][1] = 0.0f; spc8.m[t3][2] = -sp84; spc8.m[t3][3] = 0.0f; } if (obj->realrot[t4][1] >= 0.0f || (obj->flags3 & OBJFLAG3_08000000)) { spc8.m[t4][0] = 0.0f; spc8.m[t4][1] = 1.0f; spc8.m[t4][2] = 0.0f; spc8.m[t4][3] = 0.0f; } else { spc8.m[t4][0] = 0.0f; spc8.m[t4][1] = -1.0f; spc8.m[t4][2] = 0.0f; spc8.m[t4][3] = 0.0f; } spc8.m[3][0] = 0.0f; spc8.m[3][1] = 0.0f; spc8.m[3][2] = 0.0f; spc8.m[3][3] = 1.0f; mtx4GetRotation(spc8.m, &sp188); quaternion0f096ca0(&sp188, projectile->unk078); quaternion0f0976c0(projectile->unk068, projectile->unk078); projectile->unk060 = 0.0f; sp6c = acosf(spc8.m[t2][0] * sp108.m[t2][0] + spc8.m[t2][1] * sp108.m[t2][1] + spc8.m[t2][2] * sp108.m[t2][2]); if (sp6c > 0.0f && obj->realrot[t2][1] > 0.0f && obj->realrot[t2][1] > arg1[t2][1]) { projectile->unk064 = 0.05f / (sp6c * 0.63672113f); } else if (sp6c > 0.0f && obj->realrot[t2][1] < 0.0f && obj->realrot[t2][1] < arg1[t2][1]) { projectile->unk064 = 0.05f / (sp6c * 0.63672113f); } else { f2 = acosf((arg1[t2][0] * obj->realrot[t2][0] + arg1[t2][1] * obj->realrot[t2][1] + arg1[t2][2] * obj->realrot[t2][2]) / (obj->model->scale * obj->model->scale)) / g_Vars.lvupdate60freal; if (sp6c != 0.0f) { projectile->unk064 = f2 / sp6c; } else { projectile->unk064 = 1.0f; } } if (projectile->unk064 < 0.0f) { projectile->unk064 = -projectile->unk064; } if (projectile->unk064 < 0.03f) { projectile->unk064 = 0.03f; } else if (projectile->unk064 > 0.15f) { projectile->unk064 = 0.15f; } } } void knifePlayWooshSound(struct defaultobj *obj) { if (obj->hidden & OBJHFLAG_PROJECTILE) { if ((obj->projectile->flags & PROJECTILEFLAG_AIRBORNE) && obj->projectile->bouncecount <= 0 && (obj->hidden & OBJHFLAG_00000020)) { u16 soundnums[] = { SFX_8074, SFX_8074, SFX_8074 }; s32 index = random() % ARRAYCOUNT(soundnums); if (obj->projectile->lastwooshframe < g_Vars.lvframe60 - TICKS(6)) { func0f0926bc(obj->prop, 1, 0xffff); if (!lvIsPaused()) { propsnd0f0939f8(0, obj->prop, soundnums[index], -1, -1, 0, 0, 0, 0, -1.0f, 0, -1, -1.0f, -1.0f, -1.0f); obj->projectile->lastwooshframe = g_Vars.lvframe60; } } } else { obj->hidden &= ~OBJHFLAG_00000020; func0f0926bc(obj->prop, 1, 0xffff); } } } void func0f06e9cc(struct coord *arg0, Mtxf *arg1) { f32 sp124; f32 sp120; f32 sp11c; f32 sp118; f32 sp114; f32 f0; f32 sp10c; f32 sp108; f32 sp104; f32 a; f32 b; f32 stack; f32 spf4; f32 spf0; Mtxf spb0; Mtxf sp70; Mtxf sp30; struct coord sp24; f0 = sqrtf(arg0->f[0] * arg0->f[0] + arg0->f[1] * arg0->f[1] + arg0->f[2] * arg0->f[2]); sp10c = arg0->x / f0; sp108 = arg0->y / f0; sp104 = arg0->z / f0; if (sp10c == 0.0f && sp104 == 0.0f) { sp124 = 0.0f; sp120 = 0.0f; sp11c = sp108; sp118 = 1.0f; sp114 = 0.0f; } else { a = sqrtf(sp10c * sp10c + sp104 * sp104); b = sp10c / a; sp118 = sp104 / a; sp114 = -b; sp124 = sp108 * b; sp120 = -a; sp11c = sp108 * sp118; } spf4 = atan2f(sp118, sp114); mtx4LoadYRotation(-spf4, &spb0); sp24.x = sp124; sp24.y = sp120; sp24.z = sp11c; mtx4RotateVecInPlace(&spb0, &sp24); spf0 = atan2f(sp24.x, sp24.y); mtx4LoadYRotation(-1.5705463f + spf4, &sp70); mtx4LoadXRotation(-1.5705463f - spf0, &sp30); mtx4MultMtx4(&sp70, &sp30, arg1); } void objLand2(struct defaultobj *obj, struct coord *arg1, struct coord *arg2) { Mtxf sp40; struct coord newpos; struct modelrodata_bbox *bbox = modelFindBboxRodata(obj->model); f32 ymin = objGetLocalYMin(bbox); struct prop *prop = obj->prop; s16 newrooms[8]; func0f06e9cc(arg2, &sp40); mtx00015f04(obj->model->scale, &sp40); newpos.x = arg1->x - sp40.m[1][0] * ymin; newpos.y = arg1->y - sp40.m[1][1] * ymin; newpos.z = arg1->z - sp40.m[1][2] * ymin; func0f065e74(&prop->pos, prop->rooms, &newpos, newrooms); func0f06a580(obj, &newpos, &sp40, newrooms); } void boltLand(struct weaponobj *weapon, struct coord *arg1) { Mtxf mtx; struct coord newpos; struct modelrodata_bbox *bbox; s32 beamnum; f32 zmax; struct prop *prop; s16 newrooms[8]; bbox = modelFindBboxRodata(weapon->base.model); prop = weapon->base.prop; weapon->timer240 = 13; zmax = objGetLocalZMax(bbox); zmax -= 25.0f + 2.0f * RANDOMFRAC(); mtx3ToMtx4(weapon->base.realrot, &mtx); newpos.x = arg1->x - mtx.m[2][0] * zmax; newpos.y = arg1->y - mtx.m[2][1] * zmax; newpos.z = arg1->z - mtx.m[2][2] * zmax; func0f065dd8(&prop->pos, prop->rooms, &newpos, newrooms); func0f06a580(&weapon->base, &newpos, &mtx, newrooms); beamnum = boltbeamFindByProp(prop); if (beamnum != -1) { boltbeamSetTailPos(beamnum, &prop->pos); boltbeamSetAutomatic(beamnum, 2100); } } void knifeLand(struct defaultobj *obj, struct coord *arg1, struct coord *arg2) { Mtxf spd0; Mtxf sp90; Mtxf sp50; struct coord newpos; struct modelrodata_bbox *bbox = modelFindBboxRodata(obj->model); f32 zero = 0.0f; struct prop *prop = obj->prop; s16 newrooms[8]; struct coord sp1c; // @bug? Should these be assigned to zero? objGetLocalZMin(bbox); random(); sp1c.x = RANDOMFRAC() * 0.8f + arg2->x - 0.4f; sp1c.y = RANDOMFRAC() * 0.8f + arg2->y - 0.4f; sp1c.z = RANDOMFRAC() * 0.8f + arg2->z - 0.4f; func0f06e9cc(&sp1c, &sp90); mtx4LoadXRotation(-1.5705463f, &sp50); mtx4MultMtx4(&sp90, &sp50, &spd0); mtx00015f04(obj->model->scale, &spd0); newpos.x = arg1->x - zero; newpos.y = arg1->y - zero; newpos.z = arg1->z - zero; func0f065e74(&prop->pos, prop->rooms, &newpos, newrooms); func0f06a580(obj, &newpos, &spd0, newrooms); } bool objEmbed(struct prop *prop, struct prop *parent, struct model *model, struct modelnode *node) { if (parent->flags & PROPFLAG_ONTHISSCREENTHISTICK) { struct defaultobj *obj = prop->obj; Mtxf sp134; Mtxf spf4; Mtxf spb4; Mtxf sp74; Mtxf sp34; struct coord sp28; Mtxf *sp24; obj->embedment = embedmentAllocate(); if (obj->embedment) { sp24 = model0001a5cc(model, node, 0); obj->hidden |= OBJHFLAG_EMBEDDED; propDeregisterRooms(prop); propDelist(prop); propDisable(prop); obj->model->attachedtomodel = model; obj->model->attachedtonode = node; propReparent(prop, parent); modelGetRootPosition(obj->model, &sp28); sp28.x = -sp28.x; sp28.y = -sp28.y; sp28.z = -sp28.z; mtx4LoadTranslation(&sp28, &sp74); mtx3ToMtx4(obj->realrot, &sp34); mtx4SetTranslation(&prop->pos, &sp34); mtx00015be4(&sp34, &sp74, &sp134); mtx00015be4(camGetProjectionMtxF(), sp24, &spf4); mtx000172f0(spf4.m, spb4.m); mtx00015be4(&spb4, &sp134, &obj->embedment->matrix); return true; } } return false; } void objLand(struct prop *prop, struct coord *arg1, struct coord *arg2, bool *embedded) { struct defaultobj *obj = prop->obj; struct prop *ownerprop = NULL; if (obj->hidden & OBJHFLAG_PROJECTILE) { ownerprop = obj->projectile->ownerprop; objFreeProjectile(obj); } obj->hidden |= OBJHFLAG_00020000; if (obj->type == OBJTYPE_WEAPON) { struct weaponobj *weapon = (struct weaponobj *)obj; if (weapon->weaponnum == WEAPON_ECMMINE || weapon->weaponnum == WEAPON_COMMSRIDER || weapon->weaponnum == WEAPON_TRACERBUG || weapon->weaponnum == WEAPON_TARGETAMPLIFIER) { obj->flags |= OBJFLAG_INVINCIBLE; obj->flags |= OBJFLAG_00400000; obj->flags2 |= OBJFLAG2_IMMUNETOGUNFIRE; } if (weapon->weaponnum == WEAPON_BOLT) { boltLand(weapon, arg1); } else if (weapon->weaponnum == WEAPON_COMBATKNIFE) { knifeLand(obj, arg1, arg2); } else { objLand2(obj, arg1, arg2); } } else if (obj->type == OBJTYPE_AUTOGUN) { struct autogunobj *autogun = (struct autogunobj *)obj; objLand2(obj, arg1, arg2); autogun->yzero = atan2f(arg2->x, arg2->z); autogun->xzero = atan2f(arg2->y, sqrtf(arg2->f[0] * arg2->f[0] + arg2->f[2] * arg2->f[2])); autogun->xrot = autogun->xzero; autogun->yrot = autogun->yzero; } if (g_EmbedProp) { if (obj->type == OBJTYPE_WEAPON) { struct weaponobj *weapon = (struct weaponobj *)obj; bgunPlayPropHitSound(&weapon->gset, g_EmbedProp, -1); if (weapon->weaponnum == WEAPON_COMBATKNIFE && (g_EmbedProp->type & (PROPTYPE_CHR | PROPTYPE_PLAYER))) { chrSetPoisoned(g_EmbedProp->chr, ownerprop); } } if (g_EmbedProp->flags & PROPFLAG_ONTHISSCREENTHISTICK) { if (objEmbed(prop, g_EmbedProp, g_EmbedModel, g_EmbedNode)) { *embedded = true; } } else { obj->hidden |= OBJHFLAG_REAPABLE; } } else if (obj->type == OBJTYPE_WEAPON) { struct weaponobj *weapon = (struct weaponobj *)obj; bgunPlayBgHitSound(&weapon->gset, arg1, -1, prop->rooms); } } bool propExplode(struct prop *prop, s32 exptype) { struct defaultobj *obj = prop->obj; s32 playernum = (obj->hidden & 0xf0000000) >> 28; bool result; if (prop->parent) { struct prop *parent = prop->parent; struct coord pos; s16 rooms[8]; while (parent->parent) { parent = parent->parent; } if (prop->flags & PROPFLAG_ONTHISSCREENTHISTICK) { Mtxf *mtx = model0001a60c(obj->model); pos.x = mtx->m[3][0]; pos.y = mtx->m[3][1]; pos.z = mtx->m[3][2]; mtx4TransformVecInPlace(camGetProjectionMtxF(), &pos); } else { pos = parent->pos; } func0f065e74(&parent->pos, parent->rooms, &pos, rooms); result = explosionCreateComplex(NULL, &pos, rooms, exptype, playernum); } else if ((obj->hidden & (OBJHFLAG_EMBEDDED | OBJHFLAG_PROJECTILE | OBJHFLAG_00020000)) == OBJHFLAG_00020000) { struct coord sp5c; struct coord sp50; f32 ymin = objGetLocalYMin(modelFindBboxRodata(obj->model)); s32 room = prop->rooms[0]; sp50.x = obj->realrot[1][0]; sp50.y = obj->realrot[1][1]; sp50.z = obj->realrot[1][2]; sp5c.x = prop->pos.f[0] + obj->realrot[1][0] * ymin; sp5c.y = prop->pos.f[1] + obj->realrot[1][1] * ymin; sp5c.z = prop->pos.f[2] + obj->realrot[1][2] * ymin; result = explosionCreate(NULL, &prop->pos, prop->rooms, exptype, playernum, true, &sp5c, room, &sp50); } else { result = explosionCreateComplex(NULL, &prop->pos, prop->rooms, exptype, playernum); } return result; } void ammocrateTick(struct prop *prop) { struct defaultobj *obj = prop->obj; if (obj->flags & OBJFLAG_AMMOCRATE_EXPLODENOW) { propExplode(prop, EXPLOSIONTYPE_12); obj->hidden |= OBJHFLAG_REAPABLE; } } /** * Handles the following: * * - Grenade timers * - Wall hugger timers * - Nbomb timers * - Rockets * - Timed mines * - Remote mines * - Proximity items * - Removal of weapons when there are too many on-screen */ void weaponTick(struct prop *prop) { struct defaultobj *obj = prop->obj; struct weaponobj *weapon = prop->weapon; // Handle grenade timers if (((weapon->weaponnum == WEAPON_GRENADE && weapon->gunfunc == FUNC_PRIMARY) || weapon->weaponnum == WEAPON_GRENADEROUND) && weapon->timer240 >= 0) { // Handle Devastator wall hugger timer if (weapon->weaponnum == WEAPON_GRENADEROUND && weapon->gunfunc == FUNC_SECONDARY && weapon->timer240 > 0) { if (weapon->timer240 >= 2) { // Still on the wall weapon->timer240 -= g_Vars.lvupdate240; if (weapon->timer240 < 8) { // Time to fall struct coord direction = {0, -10, 0}; struct prop *parent; struct projectile *projectile = NULL; func0f0685e4(prop); if (obj->hidden & OBJHFLAG_EMBEDDED) { projectile = obj->embedment->projectile; } else if (obj->hidden & OBJHFLAG_PROJECTILE) { projectile = obj->projectile; } if (projectile) { parent = prop; while (parent->parent) { parent = parent->parent; } if (parent && (parent->type & (PROPTYPE_CHR | PROPTYPE_PLAYER))) { parent->chr->hidden |= CHRHFLAG_00000001; } else { projectile->ownerprop = NULL; projectile->flags |= PROJECTILEFLAG_AIRBORNE; } weapon->timer240 = 1; projectileSetSticky(prop); projectile->speed = direction; mtx4LoadIdentity(&projectile->mtx); projectile->obj = (struct defaultobj *)weapon; projectile->unk0d8 = g_Vars.lvframenum; } else { // Couldn't create projectile - try again next frame weapon->timer240 = 2; } } } else { // empty } } else { // Normal grenade weapon->timer240 -= g_Vars.lvupdate240; if (weapon->timer240 < 0) { s32 i; propUnsetDangerous(prop); if (weapon->gunfunc == FUNC_2) { propExplode(prop, EXPLOSIONTYPE_SDGRENADE); } else { propExplode(prop, (obj->flags2 & OBJFLAG2_WEAPON_HUGEEXP) ? EXPLOSIONTYPE_HUGE17 : EXPLOSIONTYPE_ROCKET); } obj->hidden |= OBJHFLAG_REAPABLE; for (i = 0; i < PLAYERCOUNT(); i++) { if (g_Vars.players[i]->slayerrocket == (struct weaponobj *) obj) { g_Vars.players[i]->slayerrocket = NULL; g_Vars.players[i]->visionmode = VISIONMODE_SLAYERROCKETSTATIC; } } } } } else if (weapon->weaponnum == WEAPON_NBOMB && weapon->gunfunc == FUNC_PRIMARY) { // Handle nbombs being thrown normally if (weapon->timer240 >= 0) { struct prop *ownerprop = NULL; weapon->timer240 -= g_Vars.lvupdate240; if (weapon->timer240 < 0) { // Nbombs detonate when they hit the ground, so this code only // runs if it's airborne for the entire duration of its timer. s32 ownerplayernum = (obj->hidden & 0xf0000000) >> 28; s32 i; if (g_Vars.normmplayerisrunning) { struct chrdata *chr = mpGetChrFromPlayerIndex(ownerplayernum); if (chr) { ownerprop = chr->prop; } } nbombCreateStorm(&prop->pos, ownerprop); propUnsetDangerous(prop); obj->hidden |= OBJHFLAG_REAPABLE; for (i = 0; i < PLAYERCOUNT(); i++) { if (g_Vars.players[i]->slayerrocket == (struct weaponobj *)obj) { g_Vars.players[i]->slayerrocket = NULL; g_Vars.players[i]->visionmode = VISIONMODE_SLAYERROCKETSTATIC; } } } } } else if (weapon->weaponnum == WEAPON_ROCKET || weapon->weaponnum == WEAPON_HOMINGROCKET || weapon->weaponnum == WEAPON_SKROCKET) { // Handle rockets if (weapon->timer240 == 0) { s32 i; propExplode(prop, (obj->flags2 & OBJFLAG2_WEAPON_HUGEEXP) ? EXPLOSIONTYPE_HUGE17 : EXPLOSIONTYPE_ROCKET); obj->hidden |= OBJHFLAG_REAPABLE; for (i = 0; i < PLAYERCOUNT(); i++) { if (g_Vars.players[i]->slayerrocket == (struct weaponobj *)obj) { g_Vars.players[i]->slayerrocket = NULL; g_Vars.players[i]->visionmode = VISIONMODE_SLAYERROCKETSTATIC; } } } } else if (weapon->weaponnum == WEAPON_TIMEDMINE && weapon->timer240 >= 0) { // Handle timed mines if (weapon->gunfunc == FUNC_PRIMARY) { weapon->timer240 -= g_Vars.lvupdate240; if (weapon->timer240 < 0) { if (propExplode(prop, (obj->flags2 & OBJFLAG2_WEAPON_HUGEEXP) ? EXPLOSIONTYPE_HUGE17 : EXPLOSIONTYPE_ROCKET)) { weapon->timer240 = -1; obj->hidden |= OBJHFLAG_REAPABLE; } } } else { // empty } } else if (weapon->weaponnum == WEAPON_REMOTEMINE) { // Handle remote mines if (g_PlayersDetonatingMines != 0) { s32 ownerplayernum = (obj->hidden & 0xf0000000) >> 28; struct chrdata *parentchr = prop->parent ? prop->parent->chr : NULL; // If a player manages to throw a mine on themselves, it will not detonate. // You can't throw a mine on yourself anyway, so this check always passes if (prop->parent == NULL || parentchr == NULL || mpPlayerGetIndex(parentchr) != ownerplayernum) { if (g_Vars.coopplayernum >= 0 || g_Vars.antiplayernum >= 0) { if (ownerplayernum == 2) { u32 mask = 0; if (g_Vars.coop && g_Vars.coop->prop) { mask |= 1 << playermgrGetPlayerNumByProp(g_Vars.coop->prop); } if (g_Vars.bond && g_Vars.bond->prop) { mask |= 1 << playermgrGetPlayerNumByProp(g_Vars.bond->prop); } g_PlayersDetonatingMines &= mask; if (g_PlayersDetonatingMines != 0) { weapon->timer240 = 0; } } else if (g_PlayersDetonatingMines & 1 << ownerplayernum) { weapon->timer240 = 0; } } else if (g_PlayersDetonatingMines & 1 << ownerplayernum) { weapon->timer240 = 0; } } } if (weapon->timer240 >= 2) { // I don't think this is reachable? Remote mines don't use a timer. weapon->timer240 -= g_Vars.lvupdate240; if (weapon->timer240 < 2) { weapon->timer240 = 1; } } else if (weapon->timer240 == 0) { // Mine was damaged or timer was set to 0 above s32 exptype = EXPLOSIONTYPE_ROCKET; if (obj->flags2 & OBJFLAG2_WEAPON_HUGEEXP) { exptype = EXPLOSIONTYPE_HUGE17; } if (propExplode(prop, exptype)) { weapon->timer240 = -1; obj->hidden |= OBJHFLAG_REAPABLE; } } } else if (weapon->weaponnum == WEAPON_PROXIMITYMINE || (weapon->weaponnum == WEAPON_DRAGON && weapon->gunfunc == FUNC_SECONDARY) || (weapon->weaponnum == WEAPON_GRENADE && weapon->gunfunc == FUNC_SECONDARY) || (weapon->weaponnum == WEAPON_NBOMB && weapon->gunfunc == FUNC_SECONDARY)) { // Handle proximity items if (weapon->timer240 >= 2) { // The timer is still active, so the proxy isn't active yet weapon->timer240 -= g_Vars.lvupdate240; if (weapon->timer240 < 2) { weapon->timer240 = 1; weaponRegisterProxy(weapon); } } else if (weapon->timer240 == 1) { // Proxy is active struct coord *playerpos = &g_Vars.currentplayer->prop->pos; f32 xdist = playerpos->f[0] - prop->pos.f[0]; f32 ydist = playerpos->f[1] - prop->pos.f[1]; f32 zdist = playerpos->f[2] - prop->pos.f[2]; if (xdist * xdist + ydist * ydist + zdist * zdist < 250 * 250) { weapon->timer240 = 0; } } if (weapon->timer240 == 0) { // Proxy was triggered or shot if (weapon->weaponnum == WEAPON_NBOMB) { s32 i; struct prop *ownerprop = NULL; s32 ownerplayernum = (obj->hidden & 0xf0000000) >> 28; if (g_Vars.normmplayerisrunning) { struct chrdata *chr = mpGetChrFromPlayerIndex(ownerplayernum); if (chr) { ownerprop = chr->prop; } } nbombCreateStorm(&prop->pos, ownerprop); propUnsetDangerous(prop); obj->hidden |= OBJHFLAG_REAPABLE; for (i = 0; i < PLAYERCOUNT(); i++) { if (g_Vars.players[i]->slayerrocket == (struct weaponobj *) obj) { g_Vars.players[i]->slayerrocket = NULL; g_Vars.players[i]->visionmode = VISIONMODE_SLAYERROCKETSTATIC; } } } else { // Regular explosive s32 exptype; if (obj->flags2 & OBJFLAG2_WEAPON_HUGEEXP) { exptype = EXPLOSIONTYPE_HUGE17; } else { exptype = EXPLOSIONTYPE_ROCKET; } if (weapon->weaponnum == WEAPON_DRAGON) { exptype = EXPLOSIONTYPE_DRAGONBOMBSPY; } if (propExplode(prop, exptype)) { weapon->timer240 = -1; obj->hidden |= OBJHFLAG_REAPABLE; } } } } else if (weapon->weaponnum == WEAPON_BOLT) { // Handle crossbow bolts // Note that the timer240 value doesn't act like a timer at all if (weapon->timer240 >= 2) { // Bolt is travelling s32 i; struct modelrodata_bbox *bbox = modelFindBboxRodata(obj->model); s32 ival = weapon->timer240 - 1; f32 radians = 0.026179939508438f * (ival / 12.0f); Mtxf spf8; Mtxf spb8; Mtxf sp78; struct coord sp6c; struct coord sp60; if (ival < 12) { radians += 0.026179939508438f * ((ival + 1) / 12.0f); } if ((ival & 1) == 1) { radians = -radians; } mtx4LoadYRotation(radians, &spb8); if (obj->embedment) { if (prop->parent && prop->parent->type != PROPTYPE_CHR) { mtx4Copy(&obj->embedment->matrix, &spf8); spf8.m[3][0] = spf8.m[3][1] = spf8.m[3][2] = 0.0f; spf8.m[0][3] = spf8.m[1][3] = spf8.m[2][3] = 0.0f; sp6c.f[0] = sp60.f[0] = sp6c.f[1] = sp60.f[1] = 0.0f; sp6c.f[2] = sp60.f[2] = objGetLocalZMax(bbox); mtx4MultMtx4(&spf8, &spb8, &sp78); mtx4RotateVecInPlace(&spf8, &sp6c); mtx4RotateVecInPlace(&sp78, &sp60); sp78.m[3][0] = obj->embedment->matrix.m[3][0] - (sp60.f[0] - sp6c.f[0]); sp78.m[3][1] = obj->embedment->matrix.m[3][1] - (sp60.f[1] - sp6c.f[1]); sp78.m[3][2] = obj->embedment->matrix.m[3][2] - (sp60.f[2] - sp6c.f[2]); mtx4Copy(&sp78, &obj->embedment->matrix); } } else { sp6c.f[0] = sp60.f[0] = sp6c.f[1] = sp60.f[1] = 0.0f; sp6c.f[2] = sp60.f[2] = objGetLocalZMax(bbox); mtx3ToMtx4(obj->realrot, &spf8); mtx4MultMtx4(&spf8, &spb8, &sp78); mtx4ToMtx3(&sp78, obj->realrot); mtx4RotateVecInPlace(&spf8, &sp6c); mtx4RotateVecInPlace(&sp78, &sp60); prop->pos.f[0] -= sp60.f[0] - sp6c.f[0]; prop->pos.f[1] -= sp60.f[1] - sp6c.f[1]; prop->pos.f[2] -= sp60.f[2] - sp6c.f[2]; func0f069c70(obj, false, true); } weapon->timer240--; } if (weapon->timer240 < 0) { struct projectile *projectile = obj->projectile; s32 beamnum = boltbeamFindByProp(prop); if (beamnum != -1) { boltbeamSetTailPos(beamnum, &prop->pos); boltbeamIncrementHeadPos(beamnum, 3000, 0); if (projectile && projectile->bouncecount > 0) { projectile = NULL; } if (projectile == NULL) { weapon->timer240 = 0; boltbeamSetAutomatic(beamnum, 1400); } } } else { s32 i; for (i = 0; i < PLAYERCOUNT(); i++) { if (g_Vars.players[i]->slayerrocket == (struct weaponobj *)obj) { g_Vars.players[i]->slayerrocket = NULL; g_Vars.players[i]->visionmode = VISIONMODE_SLAYERROCKETSTATIC; } } } if (1); } // Hard freeing is the practice of freeing a prop while it's on screen. // Hard frees are triggered when there are over 20 props on screen with the // flag CANHARDFREE. This flag is given to dropped weapons in multiplayer. // When this occurs, the props past the first 20 are given the flag // HARDFREEING. They then fade out over 1 second, at which point they are // given the REAPABLE flag and soon freed. if (obj->flags3 & OBJFLAG3_HARDFREEING) { weapon->fadeouttimer60 -= g_Vars.lvupdate60; if (weapon->fadeouttimer60 <= 0) { weapon->fadeouttimer60 = 0; obj->hidden |= OBJHFLAG_REAPABLE; } } if ((obj->flags3 & OBJFLAG3_CANHARDFREE) && (prop->flags & PROPFLAG_ONTHISSCREENTHISTICK)) { g_Vars.hardfreeabletally++; if (g_Vars.hardfreeabletally > 20) { weapon->fadeouttimer60 = TICKS(60); obj->flags3 &= ~OBJFLAG3_CANHARDFREE; obj->flags3 |= OBJFLAG3_HARDFREEING; } } } void func0f07063c(struct prop *prop, bool arg1) { struct defaultobj *obj = prop->obj; if (arg1) { if (obj->type == OBJTYPE_AMMOCRATE || obj->type == OBJTYPE_MULTIAMMOCRATE) { ammocrateTick(prop); } else if (obj->type == OBJTYPE_WEAPON) { weaponTick(prop); } } } void objDropRecursively(struct prop *prop, bool arg1) { struct prop *child = prop->child; while (child) { struct prop *next = child->next; objDropRecursively(child, arg1); objDrop(child, arg1); child = next; } } void func0f0706f8(struct prop *prop, bool arg1) { struct defaultobj *obj = prop->obj; struct prop *child; if (obj->hidden & OBJHFLAG_REAPABLE) { objFree(obj, true, obj->hidden2 & OBJH2FLAG_CANREGEN); } else { prop->flags &= ~PROPFLAG_ONTHISSCREENTHISTICK; func0f07063c(prop, arg1); // Recurse into children child = prop->child; while (child) { struct prop *next = child->next; func0f0706f8(child, arg1); child = next; } } } void func0f07079c(struct prop *prop, bool fulltick) { struct defaultobj *obj = prop->obj; struct model *model = obj->model; struct prop *child; struct prop *next; if (obj->hidden & OBJHFLAG_REAPABLE) { objFree(obj, true, obj->hidden2 & OBJH2FLAG_CANREGEN); return; } if (model->attachedtonode && (obj->hidden & OBJHFLAG_EMBEDDED)) { Mtxf *mtx = model0001a5cc(model->attachedtomodel, model->attachedtonode, 0); struct modelrenderdata renderdata = {NULL, true, 3}; u32 stack; Mtxf sp30; prop->flags |= PROPFLAG_ONTHISSCREENTHISTICK | PROPFLAG_ONANYSCREENTHISTICK; mtx00015be4(mtx, &obj->embedment->matrix, &sp30); renderdata.unk10 = gfxAllocate(model->filedata->nummatrices * sizeof(Mtxf)); renderdata.unk00 = &sp30; model0001ce64(&renderdata, model); func0f07063c(prop, fulltick); child = prop->child; while (child) { next = child->next; func0f07079c(child, fulltick); child = next; } } else { prop->flags &= ~PROPFLAG_ONTHISSCREENTHISTICK; func0f07063c(prop, fulltick); child = prop->child; while (child) { next = child->next; func0f0706f8(child, fulltick); child = next; } } } s32 glassCalculateOpacity(struct coord *pos, f32 xludist, f32 opadist, f32 arg3) { struct coord *campos = &g_Vars.currentplayer->cam_pos; s32 opacity; f32 xdiff = pos->x - campos->x; f32 ydiff = pos->y - campos->y; f32 zdiff = pos->z - campos->z; f32 distance = sqrtf(xdiff * xdiff + ydiff * ydiff + zdiff * zdiff); if (distance > opadist) { opacity = 255; } else if (distance < xludist) { opacity = arg3 * 255; } else { opacity = (((distance - xludist) * (1.0f - arg3)) / (opadist - xludist) + arg3) * 255; } return opacity; } struct prop *g_Lifts[MAX_LIFTS]; s32 g_NumLifts; u8 g_LiftnumToIndex[MAX_LIFTS]; struct hovtype g_HovTypes[] = { #if PAL /* HOVTYPE_BED */ { 90, 1, 2, 0.0012, 1.200000000, 0.0062821852043271, 0.0062821852043271, 0.000012564370990731, 0.00037693112972192, 0.0062821852043271, 0.0062821852043271, 0.000012564370990731, 0.00037693112972192 }, /* HOVTYPE_BIKE */ { 80, 1, 3, 0.0030, 0.120000004, 0.0125643704086540, 0.0188465546816590, 0.000025128741981462, 0.00075386225944385, 0.0125643704086540, 0.0188465546816590, 0.000025128741981462, 0.00075386225944385 }, /* HOVTYPE_CRATE */ { 70, 2, 4, 0.0012, 1.200000000, 0.0062821852043271, 0.0125643704086540, 0.000012564370990731, 0.00037693112972192, 0.0062821852043271, 0.0125643704086540, 0.000012564370990731, 0.00037693112972192 }, /* HOVTYPE_3 */ { 170, 2, 2, 0.0012, 1.200000000, 0.0031410926021636, 0.0031410926021636, 0.000006282185495365, 0.00022615866328124, 0.0031410926021636, 0.0031410926021636, 0.000006282185495365, 0.00022615866328124 }, /* HOVTYPE_4 */ { 170, 2, 2, 0.0012, 1.200000000, 0.0031410926021636, 0.0031410926021636, 0.000006282185495365, 0.00022615866328124, 0.0031410926021636, 0.0031410926021636, 0.000006282185495365, 0.00022615866328124 }, #else /* HOVTYPE_BED */ { 90, 1, 2, 0.0010, 1.000000000, 0.0062821852043271, 0.0062821852043271, 0.000010470308552613, 0.00031410926021636, 0.0062821852043271, 0.0062821852043271, 0.000010470308552613, 0.00031410926021636 }, /* HOVTYPE_BIKE */ { 80, 1, 3, 0.0025, 0.100000000, 0.0125643704086540, 0.0188465546816590, 0.000020940617105225, 0.00062821852043271, 0.0125643704086540, 0.0188465546816590, 0.000020940617105225, 0.00062821852043271 }, /* HOVTYPE_CRATE */ { 70, 2, 4, 0.0010, 1.000000000, 0.0062821852043271, 0.0125643704086540, 0.000010470308552613, 0.00031410926021636, 0.0062821852043271, 0.0125643704086540, 0.000010470308552613, 0.00031410926021636 }, /* HOVTYPE_3 */ { 170, 2, 2, 0.0010, 1.000000000, 0.0031410926021636, 0.0031410926021636, 0.000005235154276306, 0.00018846555030905, 0.0031410926021636, 0.0031410926021636, 0.000005235154276306, 0.00018846555030905 }, /* HOVTYPE_4 */ { 170, 2, 2, 0.0010, 1.000000000, 0.0031410926021636, 0.0031410926021636, 0.000005235154276306, 0.00018846555030905, 0.0031410926021636, 0.0031410926021636, 0.000005235154276306, 0.00018846555030905 }, #endif }; void func0f070a1c(struct modelrodata_bbox *bbox, f32 rot[3][3], struct coord *pos, struct coord *vertices) { f32 sp54 = rot[0][0] * bbox->xmin; f32 sp50 = rot[0][1] * bbox->xmin; f32 sp4c = rot[0][2] * bbox->xmin; f32 sp48 = rot[2][0] * bbox->zmin; f32 sp44 = rot[2][1] * bbox->zmin; f32 sp40 = rot[2][2] * bbox->zmin; f32 sp3c = rot[0][0] * bbox->xmax; f32 sp38 = rot[0][1] * bbox->xmax; f32 sp34 = rot[0][2] * bbox->xmax; f32 sp30 = rot[2][0] * bbox->zmax; f32 sp2c = rot[2][1] * bbox->zmax; f32 sp28 = rot[2][2] * bbox->zmax; f32 sp24 = rot[1][0] * bbox->ymin + pos->f[0]; f32 sp20 = rot[1][1] * bbox->ymin + pos->f[1]; f32 sp1c = rot[1][2] * bbox->ymin + pos->f[2]; vertices[0].x = sp54 + sp24 + sp48; vertices[0].y = sp50 + sp20 + sp44; vertices[0].z = sp4c + sp1c + sp40; vertices[1].x = sp54 + sp24 + sp30; vertices[1].y = sp50 + sp20 + sp2c; vertices[1].z = sp4c + sp1c + sp28; vertices[2].x = sp3c + sp24 + sp30; vertices[2].y = sp38 + sp20 + sp2c; vertices[2].z = sp34 + sp1c + sp28; vertices[3].x = sp3c + sp24 + sp48; vertices[3].y = sp38 + sp20 + sp44; vertices[3].z = sp34 + sp1c + sp40; } void func0f070bd0(struct modelrodata_type19 *rodata, f32 rot[3][3], struct coord *pos, struct coord *vertices) { s32 i; for (i = 0; i < 4; i++) { vertices[i].x = pos->x + rot[0][0] * rodata->vertices[i].x + rot[1][0] * rodata->vertices[i].y + rot[2][0] * rodata->vertices[i].z; vertices[i].y = pos->y + rot[0][1] * rodata->vertices[i].x + rot[1][1] * rodata->vertices[i].y + rot[2][1] * rodata->vertices[i].z; vertices[i].z = pos->z + rot[0][2] * rodata->vertices[i].x + rot[1][2] * rodata->vertices[i].y + rot[2][2] * rodata->vertices[i].z; } } void func0f070ca0(struct defaultobj *obj, struct geotilef *tile, u32 flags, struct modelrodata_bbox *bbox, struct modelrodata_type19 *rodata) { struct coord vertices[4]; s32 i; s32 j; if (bbox != NULL) { func0f070a1c(bbox, obj->realrot, &obj->prop->pos, vertices); } else if (rodata != NULL) { func0f070bd0(rodata, obj->realrot, &obj->prop->pos, vertices); } tile->header.type = GEOTYPE_TILE_F; tile->header.flags = flags; tile->header.numvertices = 4; for (i = 0; i < 4; i++) { tile->vertices[i] = vertices[i]; } tile->floorcol = 0xfff; tile->floortype = FLOORTYPE_DEFAULT; for (i = 0; i < 3; i++) { tile->min[i] = 0; tile->max[i] = 0; for (j = 1; j < tile->header.numvertices; j++) { if (tile->vertices[j].f[i] < tile->vertices[tile->min[i]].f[i]) { tile->min[i] = j; } if (tile->vertices[j].f[i] > tile->vertices[tile->max[i]].f[i]) { tile->max[i] = j; } } } } void liftActivate(struct prop *prop, u8 liftnum) { g_Lifts[g_NumLifts] = prop; g_LiftnumToIndex[liftnum] = g_NumLifts; g_NumLifts++; } struct prop *liftFindByPad(s16 padnum) { if (g_Pads[padnum].liftnum <= 0 || g_Pads[padnum].liftnum > MAX_LIFTS) { return NULL; } return g_Lifts[g_LiftnumToIndex[g_Pads[padnum].liftnum]]; } f32 liftGetY(struct liftobj *lift) { f32 y = lift->base.prop->pos.y; if (lift->base.geocount > 0) { struct geotilef *tile = lift->base.geotilef; if (tile && tile->header.type == GEOTYPE_TILE_F) { if (tile->header.flags & GEOFLAG_FLOOR1) { y = tile->vertices[tile->max[1]].y; } } } return y; } /** * Recalculate a lift's tile/geometry information. * * The lift models and the part numbers in each model are: * * a51_lift_control 0 * a51_lift_hangar 0 * a51_lift_store 0 * a51_lift_thinwall 0 * a51dish 0 * airbase_lift_angle 0 1 2 * airbase_lift_enclosed 0 1 2 * al_docklift 5 6 * baggagecarrier 0 * chamber 0 * ci_lift 0 * dd_liftr 0 1 2 3 4 * lift_platform 0 1 * matrix_lift 0 1 2 * ravinelift 0 * sk_lift 0 * skedarbridge 0 * * Part 0 is the floor if rectangular * Parts 1-3 are walls (some lifts rely on BG geometry instead) * Part 4 is a door blocker (used by dataDyne tower lifts) * Part 5 is the first half of the floor if non-rectangular (Deep Sea lift) * Part 6 is the second half of the floor if non-rectangular (Deep Sea lift) */ void liftUpdateTiles(struct liftobj *lift, bool stationary) { u8 *geo; union modelrodata *rodata; struct modelrodata_bbox *bbox; s32 geocount; u32 flags; s32 i; lift->base.geocount = 0; i = 0; do { geo = (u8 *)lift->base.unkgeo + lift->base.geocount * 0x40; bbox = NULL; rodata = NULL; do { if (i == 0) { flags = GEOFLAG_FLOOR1 | GEOFLAG_FLOOR2 | GEOFLAG_BLOCK_SIGHT | GEOFLAG_BLOCK_SHOOT | GEOFLAG_LIFTFLOOR; // Look for a non-rectangular floor with fallback to rectangular rodata = modelGetPartRodata(lift->base.model->filedata, MODELPART_LIFT_FLOORNONRECT1); if (rodata == NULL) { union modelrodata *tmp; tmp = modelGetPartRodata(lift->base.model->filedata, MODELPART_LIFT_FLOORRECT); bbox = &tmp->bbox; if (bbox == NULL) { bbox = objFindBboxRodata(&lift->base); } } } else if (i == 1) { flags = GEOFLAG_WALL; rodata = modelGetPartRodata(lift->base.model->filedata, MODELPART_LIFT_WALL1); } else if (i == 2) { flags = GEOFLAG_WALL; rodata = modelGetPartRodata(lift->base.model->filedata, MODELPART_LIFT_WALL2); } else if (i == 3) { flags = GEOFLAG_WALL; rodata = modelGetPartRodata(lift->base.model->filedata, MODELPART_LIFT_WALL3); } else if (i == 4) { // The doorblock model part exists in the dataDyne tower lifts. // It's a tile across the door that only applies while the lift // is moving. Without it, the player could exit the lift through // the doorway while it's moving. if (!stationary) { flags = GEOFLAG_WALL; rodata = modelGetPartRodata(lift->base.model->filedata, MODELPART_LIFT_DOORBLOCK); } } else if (i == 5) { flags = GEOFLAG_FLOOR1 | GEOFLAG_FLOOR2 | GEOFLAG_BLOCK_SIGHT | GEOFLAG_BLOCK_SHOOT | GEOFLAG_LIFTFLOOR; rodata = modelGetPartRodata(lift->base.model->filedata, MODELPART_LIFT_FLOORNONRECT2); } else { break; } i++; } while (!bbox && !rodata); if (bbox || rodata) { func0f070ca0(&lift->base, (struct geotilef *)geo, flags, bbox, &rodata->type19); lift->base.geocount++; } } while (bbox || rodata); } void liftGoToStop(struct liftobj *lift, s32 stopnum) { struct pad *curpad; struct pad *aimpad; struct pad *reqpad; if (lift->pads[stopnum] >= 0 && lift->levelaim != stopnum) { // If lift is stopped (cur == aim) // or door is not fully closed yet if (lift->levelcur == lift->levelaim || (lift->doors[lift->levelcur] && !doorIsClosed(lift->doors[lift->levelcur]))) { // Sanity check to make sure lift is actually not moving if (lift->dist == 0 && lift->speed == 0) { lift->levelaim = stopnum; return; } } curpad = &g_Pads[lift->pads[lift->levelcur]]; aimpad = &g_Pads[lift->pads[lift->levelaim]]; reqpad = &g_Pads[lift->pads[stopnum]]; // Figure out if the lift needs to reverse direction on any axis if (stopnum != lift->levelcur && ((aimpad->pos.x >= curpad->pos.x && reqpad->pos.x >= curpad->pos.x) || (curpad->pos.x >= aimpad->pos.x && curpad->pos.x >= reqpad->pos.x)) && ((aimpad->pos.y >= curpad->pos.y && reqpad->pos.y >= curpad->pos.y) || (curpad->pos.y >= aimpad->pos.y && curpad->pos.y >= reqpad->pos.y)) && ((aimpad->pos.z >= curpad->pos.z && reqpad->pos.z >= curpad->pos.z) || (curpad->pos.z >= aimpad->pos.z && curpad->pos.z >= reqpad->pos.z))) { // Same direction lift->levelaim = stopnum; } else { // Reverse direction f32 xdiff = aimpad->pos.x - curpad->pos.x; f32 ydiff = aimpad->pos.y - curpad->pos.y; f32 zdiff = aimpad->pos.z - curpad->pos.z; f32 result = sqrtf(xdiff * xdiff + ydiff * ydiff + zdiff * zdiff); lift->levelcur = lift->levelaim; lift->dist = result - lift->dist; lift->speed = -lift->speed; lift->levelaim = stopnum; } } } f32 objGetHov04(struct defaultobj *obj) { struct hov *hov = NULL; f32 result; if (obj->type == OBJTYPE_HOVERPROP) { struct hoverpropobj *tmp = (struct hoverpropobj *) obj; hov = &tmp->hov; } else if (obj->type == OBJTYPE_HOVERBIKE) { struct hoverbikeobj *tmp = (struct hoverbikeobj *) obj; hov = &tmp->hov; } if (hov) { result = hov->unk04; } else { struct modelrodata_bbox *bbox = objFindBboxRodata(obj); f32 value = objGetRotatedLocalYMinByMtx3(bbox, obj->realrot); result = func0f06a620(obj) - value; } return result; } void hovUpdateGround(struct defaultobj *obj, struct hov *hov, struct coord *pos, s16 *rooms, f32 matrix[3][3]) { f32 ground; s16 testrooms[8]; struct coord testpos; if (g_Vars.lvframe60 > hov->nexttick60) { testpos.x = pos->x; testpos.y = pos->y - 50; testpos.z = pos->z; roomsCopy(rooms, testrooms); setup0f09233c(obj, &testpos, matrix, testrooms); ground = cdFindGroundAtCyl(pos, 5, testrooms, &obj->floorcol, NULL); if (ground < -30000) { ground = hov->ground; } hov->ground = ground; hov->prevtick60 = g_Vars.lvframe60; } } void hovTick(struct defaultobj *obj, struct hov *hov) { struct prop *prop; f32 sp1d0; f32 sp1cc; f32 ground1; f32 ground2; struct modelrodata_bbox *bbox; struct coord sp1b4; struct coord sp1a8; s16 sp198[8]; s16 sp188[8]; Mtxf sp148; Mtxf sp108; Mtxf spc8; s32 i; struct hovtype *type; f32 spbc; f32 spb8; f32 spb4; f32 xrot; f32 spac; s16 sp9c[8]; struct coord sp90; bool sp8c; f32 radius; f32 ymax; f32 ymin; if (g_Vars.lvframe60 > hov->nexttick60) { prop = obj->prop; bbox = objFindBboxRodata(obj); type = &g_HovTypes[hov->type]; sp8c = false; if (hov->prevtick60 < g_Vars.lvframe60) { hovUpdateGround(obj, hov, &prop->pos, prop->rooms, obj->realrot); } hov->nexttick60 = g_Vars.lvframe60; if (obj->flags & OBJFLAG_DEACTIVATED) { spb4 = 0.0f; } else { if (obj->flags3 & OBJFLAG3_GEOCYL) { objGetBbox(prop, &radius, &ymax, &ymin); sp1cc = radius * 0.9f; sp1d0 = -sp1cc; } else { sp1d0 = bbox->zmin * 0.9f * obj->model->scale; sp1cc = bbox->zmax * 0.9f * obj->model->scale; } spbc = cosf(hov->unk10); spb8 = sinf(hov->unk10); sp1b4.x = prop->pos.x + sp1d0 * spb8; sp1b4.y = prop->pos.y; sp1b4.z = prop->pos.z + sp1d0 * spbc; sp1a8.x = prop->pos.x + sp1cc * spb8; sp1a8.y = prop->pos.y; sp1a8.z = prop->pos.z + sp1cc * spbc; sp90.x = prop->pos.x; sp90.y = prop->pos.y - 50.0f; sp90.z = prop->pos.z; roomsCopy(prop->rooms, sp9c); setup0f09233c(obj, &sp90, obj->realrot, sp9c); func0f065e74(&prop->pos, prop->rooms, &sp1b4, sp198); roomsAppend(sp9c, sp198, ARRAYCOUNT(sp198)); ground1 = cdFindGroundAtCyl(&sp1b4, 5, sp198, &obj->floorcol, NULL); func0f065e74(&prop->pos, prop->rooms, &sp1a8, sp188); roomsAppend(sp9c, sp188, ARRAYCOUNT(sp188)); ground2 = cdFindGroundAtCyl(&sp1a8, 5, sp188, NULL, NULL); if (ground1 >= -30000.0f && ground2 >= -30000.0f) { spb4 = atan2f(ground1 - ground2, sp1cc - sp1d0); if (spb4 >= M_PI) { spb4 -= M_BADTAU; } } else if (ground1 >= -30000.0f) { spb4 = atan2f(ground1 - hov->ground, -sp1d0); if (spb4 >= M_PI) { spb4 -= M_BADTAU; } } else if (ground2 >= -30000.0f) { spb4 = atan2f(hov->ground - ground2, sp1cc); if (spb4 >= M_PI) { spb4 -= M_BADTAU; } } else { spb4 = 0.0f; } } spac = hov->ground; if (obj->hidden & OBJHFLAG_GRABBED) { if (spac < g_Vars.currentplayer->vv_ground - 70.0f) { spac = g_Vars.currentplayer->vv_ground; } } if (hov->flags & 1) { sp8c = true; hov->unk04 = hov->unk08 = type->unk00; hov->unk30 = spac; hov->flags &= ~1; if (obj->type == OBJTYPE_HOVERBIKE) { propsnd0f0939f8(NULL, obj->prop, SFX_BIKE_PULSE, -1, -1, 0, 0, 0, 0, -1.0f, 0, -1, -1.0f, -1.0f, -1.0f); } } applySpeed(&hov->unk04, hov->unk08, &hov->unk0c, type->unk0c, type->unk0c, type->unk10); if (hov->unk08 >= type->unk00 && hov->unk08 <= hov->unk04) { hov->unk0c = 0.0f; hov->unk08 = type->unk00 - type->unk04 - RANDOMFRAC() * type->unk08; } else if (hov->unk08 < type->unk00 && hov->unk08 >= hov->unk04) { hov->unk0c = 0.0f; hov->unk08 = type->unk00 + type->unk04 + RANDOMFRAC() * type->unk08; } applyRotation(&hov->unk14, hov->unk18, &hov->unk1c, type->unk1c, type->unk1c, type->unk20); if (hov->unk14 == hov->unk18) { if (hov->unk1c <= 2.0f * type->unk1c && hov->unk1c >= 2.0f * -type->unk1c) { hov->unk1c = 0.0f; if (hov->unk18 < M_PI) { hov->unk18 = M_BADTAU - type->unk14 - RANDOMFRAC() * type->unk18; } else { hov->unk18 = type->unk14 + RANDOMFRAC() * type->unk18; } } } applyRotation(&hov->unk20, hov->unk24, &hov->unk28, type->unk2c, type->unk2c, type->unk30); if (hov->unk20 == hov->unk24) { if (hov->unk28 <= 2.0f * type->unk2c && hov->unk28 >= 2.0f * -type->unk2c) { hov->unk28 = 0.0f; if (hov->unk24 < M_PI) { hov->unk24 = M_BADTAU - type->unk24 - RANDOMFRAC() * type->unk28; } else { hov->unk24 = type->unk24 + RANDOMFRAC() * type->unk28; } } } for (i = 0; i < g_Vars.lvupdate60; i++) { f32 f0; f32 f12; f32 f2; hov->unk2c += (spb4 - hov->unk2c) * (PAL ? 0.0893f : 0.075f); f0 = spac - hov->unk30; f12 = (PAL ? 0.102000005f : 0.085f); if (hov->unk30 < hov->ground) { if (f0 >= 0.0f) { f2 = f0; } else { f2 = -f0; } if (f2 > 10.0f) { f12 *= 1.0f + (f2 - 10.0f) * 0.2f; } if (f12 > PALUPF(0.5f)) { f12 = PALUPF(0.5f); } f0 *= f12; } else { f0 *= f12; if (obj->hidden & OBJHFLAG_MOUNTED) { if (f0 > 10.0f) { f0 = 10.0f; } else if (f0 < -10.0f) { f0 = -10.0f; } } else { if (f0 > 5.0f) { f0 = 5.0f; } else if (f0 < -5.0f) { f0 = -5.0f; } } } hov->unk30 += f0; if (f0 > 1.0f || f0 < -1.0f) { sp8c = true; } } if (sp8c) { func0f069c70(obj, true, true); } if (hov->unk30 < hov->ground - 5.0f || hov->unk30 > hov->ground + 5.0f) { obj->flags |= OBJFLAG_80000000; } else { obj->flags &= ~OBJFLAG_80000000; } prop->pos.y = objGetHov04(obj) + hov->unk30; mtx4LoadZRotation(hov->unk20, &sp148); xrot = hov->unk2c + hov->unk14; if (xrot >= M_BADTAU) { xrot -= M_BADTAU; } else if (xrot < 0.0f) { xrot += M_BADTAU; } mtx4LoadXRotation(xrot, &sp108); mtx00015be0(&sp108, &sp148); mtx4LoadYRotation(hov->unk10, &sp108); mtx00015be0(&sp108, &sp148); mtx00015f04(obj->model->scale, &sp148); if (obj->type == OBJTYPE_HOVERBIKE) { struct hoverbikeobj *bike = (struct hoverbikeobj *) obj; f32 ezreal = bike->ezreal + bike->ezreal2; if (bike->exreal != 0.0f) { mtx4LoadXRotation(bike->exreal, &sp108); mtx00015be4(&sp148, &sp108, &spc8); mtx4Copy(&spc8, &sp148); } if (ezreal != 0.0f) { mtx4LoadZRotation(ezreal, &sp108); mtx00015be4(&sp148, &sp108, &spc8); mtx4Copy(&spc8, &sp148); } } mtx4ToMtx3(&sp148, obj->realrot); } } s32 objIsHoverpropOrBike(struct defaultobj *obj) { return obj->type == OBJTYPE_HOVERPROP || obj->type == OBJTYPE_HOVERBIKE; } f32 hoverpropGetTurnAngle(struct defaultobj *obj) { f32 angle = 0; if (obj->type == OBJTYPE_HOVERPROP) { struct hoverpropobj *hoverprop = (struct hoverpropobj *)obj; angle = hoverprop->hov.unk10; } else if (obj->type == OBJTYPE_HOVERBIKE) { struct hoverbikeobj *hoverbike = (struct hoverbikeobj *)obj; angle = hoverbike->hov.unk10; } return angle; } void hoverpropSetTurnAngle(struct defaultobj *obj, f32 angle) { if (obj->type == OBJTYPE_HOVERPROP) { struct hoverpropobj *hoverprop = (struct hoverpropobj *)obj; hoverprop->hov.unk10 = angle; } else if (obj->type == OBJTYPE_HOVERBIKE) { struct hoverbikeobj *hoverbike = (struct hoverbikeobj *)obj; hoverbike->hov.unk10 = angle; } } s32 func0f072144(struct defaultobj *obj, struct coord *arg1, f32 arg2, bool arg3) { s32 cdresult = CDRESULT_NOCOLLISION; f32 sp460[3][3]; f32 yrot; struct coord pos; s16 rooms[8]; struct hov prevhov; struct hov *hov = NULL; union geounion geounion; struct prop *prop = obj->prop; struct hoverbikeobj *hoverbike; Mtxf spa4; Mtxf sp64; f32 sp40[3][3]; if (objIsHoverpropOrBike(obj)) { if (arg2 != 0.0f) { yrot = arg2 + hoverpropGetTurnAngle(obj); if (yrot >= M_BADTAU) { yrot -= M_BADTAU; } else if (yrot < 0.0f) { yrot += M_BADTAU; } } else { yrot = hoverpropGetTurnAngle(obj); } mtx4LoadYRotation(yrot, &spa4); mtx00015f04(obj->model->scale, &spa4); mtx4ToMtx3(&spa4, sp460); } else { yrot = 0.0f; if (arg2 != 0.0f) { if (arg2 >= M_BADTAU) { arg2 -= M_BADTAU; } else if (arg2 < 0.0f) { arg2 += M_BADTAU; } mtx4LoadYRotation(arg2, &sp64); mtx4ToMtx3(&sp64, sp40); mtx00016140(sp40, obj->realrot, sp460); } else { mtx3Copy(obj->realrot, sp460); } } pos = prop->pos; propSetPerimEnabled(prop, false); if (arg1->x != 0.0f || arg1->z != 0.0f) { pos.x += arg1->x; pos.z += arg1->z; func0f065e74(&prop->pos, prop->rooms, &pos, rooms); setup0f09233c(obj, &pos, sp460, rooms); if (obj->type == OBJTYPE_HOVERBIKE) { hoverbike = (struct hoverbikeobj *) obj; hov = &hoverbike->hov; } else if (obj->type == OBJTYPE_HOVERPROP) { hov = &((struct hoverpropobj *) obj)->hov; } if (hov != NULL) { prevhov = *hov; hovUpdateGround(obj, hov, &pos, rooms, sp460); pos.y += hov->ground - prevhov.ground; } cdresult = cdExamCylMove05(&prop->pos, prop->rooms, &pos, rooms, CDTYPE_ALL, true, 0.0f, 0.0f); if (cdresult == CDRESULT_ERROR) { // empty } else if (cdresult == CDRESULT_COLLISION) { cdSetSavedPos(&prop->pos, &pos); } } else { roomsCopy(prop->rooms, rooms); setup0f09233c(obj, &pos, sp460, rooms); } if (cdresult == CDRESULT_NOCOLLISION) { func0f069850(obj, &pos, sp460, &geounion.cyl); if (obj->flags3 & OBJFLAG3_GEOCYL) { cdresult = cdExamCylMove01(&prop->pos, &pos, geounion.cyl.radius, rooms, CDTYPE_ALL, CHECKVERTICAL_YES, geounion.cyl.ymax - pos.y, geounion.cyl.ymin - pos.y); } else { cdresult = cd0002f02c(&geounion.block, rooms, CDTYPE_ALL); } } propSetPerimEnabled(prop, true); if (cdresult == CDRESULT_NOCOLLISION && arg3) { hoverpropSetTurnAngle(obj, yrot); mtx3Copy(sp460, obj->realrot); prop->pos.x = pos.x; prop->pos.z = pos.z; propDeregisterRooms(prop); roomsCopy(rooms, prop->rooms); if (obj->geocyl && (obj->hidden2 & OBJH2FLAG_08)) { if (obj->flags3 & OBJFLAG3_GEOCYL) { *obj->geocyl = geounion.cyl; } else { *obj->geoblock = geounion.block; } } } else if (hov) { *hov = prevhov; } return cdresult; } void hovercarFindNextPath(struct hovercarobj *hovercar) { s32 index = hovercar->path - g_StageSetup.paths + 1; while (true) { if (g_StageSetup.paths[index].pads == NULL) { index = 0; } if (&g_StageSetup.paths[index] == hovercar->path) { break; } if ((g_StageSetup.paths[index].flags & PATHFLAG_FLYING) && (g_StageSetup.paths[index].flags & PATHFLAG_INUSE) == 0) { hovercar->path->flags &= ~PATHFLAG_INUSE; hovercar->path = &g_StageSetup.paths[index]; hovercar->path->flags |= PATHFLAG_INUSE; return; } index++; } } void hovercarStartNextPath(struct hovercarobj *hovercar) { s32 *pads; struct pad *pad; Mtxf matrix; s16 rooms[2]; hovercarFindNextPath(hovercar); pads = hovercar->path->pads; hovercar->nextstep = 0; pad = &g_Pads[pads[0]]; mtx3ToMtx4(hovercar->base.realrot, &matrix); rooms[0] = pad->room; rooms[1] = -1; func0f06a730(&hovercar->base, &pad->pos, &matrix, rooms, &pad->pos); hovercar->base.flags |= OBJFLAG_HOVERCAR_20000000; } void hovercarIncrementStep(struct hovercarobj *hovercar) { hovercar->nextstep++; if (hovercar->path->pads[hovercar->nextstep] < 0) { if (hovercar->path->flags & PATHFLAG_CIRCULAR) { hovercar->nextstep = 0; } else { hovercarStartNextPath(hovercar); } } } /** * Check if the given obj is colliding with another obj. If so, transfer * velocity into the collided obj. Return a frac that should be applied to the * moving obj's speed. * * Assumes the caller has used the collision system already, so any obstacle is * already known by the collision system. * * For collisions with chrs, no transfer of intertia is done and the moving obj * rebounds at full speed. For collisions with pushable objects, the force is * applied half to both objects. */ f32 objCollide(struct defaultobj *movingobj, struct coord *movingvel, f32 rotation) { f32 force = 1.0f; struct prop *obstacle = cdGetObstacleProp(); if (obstacle && g_Vars.lvupdate240 > 0) { if (obstacle->type & (PROPTYPE_CHR | PROPTYPE_PLAYER)) { if (1); } else if (obstacle->type == PROPTYPE_OBJ) { struct defaultobj *obstacleobj = obstacle->obj; if ((obstacleobj->hidden & OBJHFLAG_MOUNTED) == 0 && (obstacleobj->hidden & OBJHFLAG_GRABBED) == 0 && (obstacleobj->flags3 & OBJFLAG3_PUSHABLE)) { struct coord sp88; struct coord obstaclevel = {0, 0, 0}; struct coord sp70; struct coord sp64; struct coord sp58; struct coord sp4c; obstaclevel.x = movingvel->x * 0.5f / g_Vars.lvupdate60freal; obstaclevel.y = 0.0f; obstaclevel.z = movingvel->z * 0.5f / g_Vars.lvupdate60freal; objApplyMomentum(obstacleobj, &obstaclevel, 0.0f, true, true); cdGetEdge(&sp70, &sp64, 7355, "propobj.c"); if (cdGetSavedPos(&sp58, &sp4c)) { sp4c.x -= sp58.x; sp4c.y -= sp58.y; sp4c.z -= sp58.z; } else { sp58 = movingobj->prop->pos; sp4c.x = obstacle->pos.x - movingobj->prop->pos.x; sp4c.y = obstacle->pos.y - movingobj->prop->pos.y; sp4c.z = obstacle->pos.z - movingobj->prop->pos.z; } func0f02e3dc(&sp70, &sp64, &sp58, &sp4c, &sp88); force = 0.5f; if (rotation != 0.0f) { f32 xdiff = sp88.f[0] - movingobj->prop->pos.f[0]; f32 zdiff = sp88.f[2] - movingobj->prop->pos.f[2]; f32 finalrotation = 0.0f; struct coord speed = {0, 0, 0}; f32 mult = 1.0f / sqrtf(xdiff * xdiff + zdiff * zdiff); xdiff *= mult; zdiff *= mult; rotation /= g_Vars.lvupdate60freal; speed.f[0] += -zdiff * rotation * 10.0f; speed.f[2] += xdiff * rotation * 10.0f; finalrotation += rotation * 0.1f; objApplyMomentum(obstacleobj, &speed, finalrotation, true, true); } } } } return force; } void hoverbikeUpdateMovement(struct hoverbikeobj *bike, f32 speedforwards, f32 speedsideways, f32 speedtheta) { f32 f12; f32 angle; f32 sinangle; f32 cosangle; f32 f2; f32 sp70 = 0; f32 sp6c = 0; f32 sp68 = 0; s32 i; u32 stack[6]; f32 tmp; tmp = speedtheta * 0.04362628236413f; if (speedforwards < 0) { tmp *= 1.0f - speedforwards * 0.5f; } for (i = 0; i < g_Vars.lvupdate60; i++) { bike->w += (tmp - bike->w) * (PAL ? 0.0893f : 0.075f); } sp6c += bike->w * 12; angle = hoverpropGetTurnAngle(&bike->base); sinangle = sinf(angle); cosangle = cosf(angle); if (speedforwards >= 0) { f2 = (speedforwards + 0.1f) * 0.3f * g_Vars.lvupdate60freal; } else { f2 = (0.1f - speedforwards) * 0.3f * g_Vars.lvupdate60freal; } if (bike->rels[1] < speedforwards * 0.5f) { bike->rels[1] += f2; if (bike->rels[1] > speedforwards * 0.5f) { bike->rels[1] = speedforwards * 0.5f; } } else { bike->rels[1] -= f2; if (bike->rels[1] < speedforwards * 0.5f) { bike->rels[1] = speedforwards * 0.5f; } } bike->leandiff *= 0.93f; bike->leandiff += speedforwards - bike->leanspeed; bike->leanspeed = speedforwards; f2 = bike->leandiff * 5; if (f2 > 1.0f) { f2 = 1.0f; } else if (f2 < -1.0f) { f2 = -1.0f; } if (speedforwards >= 0) { if (f2 > 0) { f12 = speedforwards * 0.3f + speedforwards * 0.7f * f2; } else { f12 = speedforwards * 0.3f; } } else { if (f2 < 0) { f12 = speedforwards * 0.5f - speedforwards * 0.5f * f2; } else { f12 = speedforwards * 0.5f; } } sp70 += f12 * 0.04f * M_BADTAU; if (speedsideways >= 0) { f12 = (speedsideways + 0.1f) * 0.3f * g_Vars.lvupdate60freal; } else { f12 = (0.1f - speedsideways) * 0.3f * g_Vars.lvupdate60freal; } if (bike->rels[0] < 0.4f * speedsideways) { bike->rels[0] += f12; if (bike->rels[0] > speedsideways * 0.4f) { bike->rels[0] = speedsideways * 0.4f; } } else { bike->rels[0] -= f12; if (bike->rels[0] < speedsideways * 0.4f) { bike->rels[0] = speedsideways * 0.4f; } } sp68 += speedsideways * 0.2512874007225f; for (i = 0; i < g_Vars.lvupdate60; i++) { bike->speedabs[1] *= PAL ? 0.964f : 0.97f; bike->speedabs[0] *= PAL ? 0.964f : 0.97f; bike->speedabs[1] += bike->rels[1] * cosangle * PALUPF(1.08f); bike->speedabs[0] += bike->rels[1] * sinangle * PALUPF(1.08f); bike->speedabs[1] += bike->rels[0] * sinangle * PALUPF(0.72f); bike->speedabs[0] += -bike->rels[0] * cosangle * PALUPF(0.72f); } for (i = 0; i < g_Vars.lvupdate60; i++) { bike->exreal += (sp70 - bike->exreal) * (PAL ? 0.0478f : 0.04f); bike->ezreal += (sp6c - bike->ezreal) * (PAL ? 0.177f : 0.15f); bike->ezreal2 += (sp68 - bike->ezreal2) * (PAL ? 0.0478f : 0.04f); } if (speedforwards >= 0.99f) { bike->maxspeedtime240 += g_Vars.lvupdate240; if (bike->maxspeedtime240 > TICKS(2400)) { bike->maxspeedtime240 = TICKS(2400); } } else if (bike->maxspeedtime240 > 0) { if (speedforwards >= 0.8f) { // empty } else if (speedforwards >= -0.1f) { bike->maxspeedtime240 -= g_Vars.lvupdate240; if (bike->maxspeedtime240 < 0) { bike->maxspeedtime240 = 0; } } else { bike->maxspeedtime240 = 0; } } bike->speedrel[1] = bike->maxspeedtime240 * 5000.0f / TICKS(2400000); bike->speed[1] = bike->speedabs[1] + bike->speedrel[1] * cosangle + bike->speedrel[0] * sinangle; bike->speed[0] = bike->speedabs[0] + bike->speedrel[1] * sinangle - bike->speedrel[0] * cosangle; if (bike->base.flags & OBJFLAG_HOVERBIKE_MOVINGWHILEEMPTY) { if (bike->speed[0] > 0.1f || bike->speed[1] > 0.1f || bike->w > 0.001f || bike->rels[0] > 0.001f || bike->rels[1] > 0.001f || bike->exreal > 0.001f || bike->ezreal > 0.001f || bike->ezreal2 > 0.001f || bike->leandiff > 0.1f || bike->speed[0] < -0.1f || bike->speed[1] < -0.1f || bike->w < -0.001f || bike->rels[0] < -0.001f || bike->rels[1] < -0.001f || bike->exreal < -0.001f || bike->ezreal < -0.001f || bike->ezreal2 < -0.001f || bike->leandiff < -0.1f) { // still moving } else { bike->speed[0] = 0; bike->speed[1] = 0; bike->w = 0; bike->rels[0] = 0; bike->rels[1] = 0; bike->exreal = 0; bike->ezreal = 0; bike->ezreal2 = 0; bike->leanspeed = 0; bike->leandiff = 0; bike->maxspeedtime240 = 0; bike->speedabs[0] = 0; bike->speedabs[1] = 0; bike->speedrel[0] = 0; bike->speedrel[1] = 0; if (1); bike->base.flags &= ~OBJFLAG_HOVERBIKE_MOVINGWHILEEMPTY; } } } void platformDisplaceProps2(struct prop *platform, Mtxf *arg1) { struct prop *prop; s16 *propnumptr; s16 propnums[256]; u8 *sp9c; u8 *sp98; Mtxf sp58; if (propUpdateGeometry(platform, &sp9c, &sp98)) { roomGetProps(platform->rooms, propnums, 256); propnumptr = propnums; while (*propnumptr >= 0) { prop = &g_Vars.props[*propnumptr]; if (prop->type & (PROPTYPE_OBJ | PROPTYPE_WEAPON)) { struct defaultobj *obj = prop->obj; if (prop->pos.y > platform->pos.y && (obj->hidden & OBJHFLAG_00008000) && cd000266a4(prop->pos.x, prop->pos.z, (struct geo *)sp9c)) { mtx3ToMtx4(obj->realrot, &sp58); mtx4SetTranslation(&prop->pos, &sp58); mtx4MultMtx4InPlace(arg1, &sp58); mtx4ToMtx3(&sp58, obj->realrot); prop->pos.x = sp58.m[3][0]; prop->pos.y = sp58.m[3][1]; prop->pos.z = sp58.m[3][2]; propDeregisterRooms(prop); func0f065e74(&platform->pos, platform->rooms, &prop->pos, prop->rooms); func0f069c70(obj, true, true); } } propnumptr++; } } } /** * Tick a Slayer fly-by-wire rocket that's being controlled by a bot. */ bool rocketTickFbw(struct weaponobj *rocket) { bool cdresult; f32 speed; s32 i; struct projectile *projectile = rocket->base.projectile; struct prop *rocketprop = rocket->base.prop; struct chrdata *ownerchr; struct coord sp164; struct coord sp158; Mtxf sp118; Mtxf spd8; Mtxf sp98; f32 xdist; f32 ydist; f32 zdist; struct prop *targetprop; f32 xrot; f32 yrot; struct coord newpos; struct coord dir; u32 stack[2]; s16 newrooms[8]; if (projectile->ownerprop && projectile->ownerprop->type == PROPTYPE_CHR) { ownerchr = projectile->ownerprop->chr; } else { ownerchr = NULL; } // If close to the next step (ie. a waypoint) // then advance to the one after that xdist = projectile->nextsteppos.x - rocketprop->pos.x; ydist = projectile->nextsteppos.y - rocketprop->pos.y; zdist = projectile->nextsteppos.z - rocketprop->pos.z; if (ownerchr && xdist * xdist + ydist * ydist + zdist * zdist < 100 * 100) { projectile->step++; if (projectile->numwaypads <= 0 || projectile->step >= projectile->numwaypads) { targetprop = chrGetTargetProp(ownerchr); if (!botactFindRocketRoute(ownerchr, &rocketprop->pos, &targetprop->pos, rocketprop->rooms, targetprop->rooms, projectile)) { rocket->timer240 = 0; } } else { botactGetRocketNextStepPos(projectile->waypads[projectile->step], &projectile->nextsteppos); } } if (ownerchr) { xrot = atan2f(xdist, zdist); yrot = atan2f(ydist, sqrtf(xdist * xdist + zdist * zdist)); for (i = 0; i < g_Vars.lvupdate240; i++) { projectile->unk018 = model0001afe8(projectile->unk018, xrot, PAL ? 0.02246f : 0.01875f); projectile->unk014 = model0001afe8(projectile->unk014, yrot, PAL ? 0.02246f : 0.01875f); } mtx4LoadXRotation(M_BADTAU - projectile->unk014, &sp118); mtx4LoadYRotation(projectile->unk018, &spd8); mtx4MultMtx4(&spd8, &sp118, &sp98); mtx00015f04(rocket->base.model->scale, &sp98); mtx4ToMtx3(&sp98, rocket->base.realrot); } // Calculate new pos dir.f[0] = sinf(projectile->unk018) * cosf(projectile->unk014); dir.f[1] = sinf(projectile->unk014); dir.f[2] = cosf(projectile->unk018) * cosf(projectile->unk014); newpos = rocketprop->pos; for (i = 0; i < g_Vars.lvupdate60; i++) { projectile->unk010 += PAL ? 0.0021600001f : 0.0018f; speed = projectile->unk010; if (ownerchr && ownerchr->target == -1) { speed = PAL ? 0.120000004f : 0.10f; } newpos.x += dir.x * speed; newpos.y += dir.y * speed; newpos.z += dir.z * speed; } // Check if new pos would collide with anything if (projectile->ownerprop) { propSetPerimEnabled(projectile->ownerprop, false); } cdresult = func0f06cd00(&rocket->base, &newpos, &sp164, &sp158); if (projectile->ownerprop) { propSetPerimEnabled(projectile->ownerprop, true); } if (cdresult == CDRESULT_NOCOLLISION) { func0f065e74(&rocketprop->pos, rocketprop->rooms, &newpos, newrooms); rocketprop->pos = newpos; propDeregisterRooms(rocketprop); roomsCopy(newrooms, rocketprop->rooms); func0f069c70(&rocket->base, true, true); } else { // Boom rocket->timer240 = 0; } // Create smoke behind the rocket if (projectile->smoketimer240 <= 0) { projectile->smoketimer240 = TICKS(24); smokeCreateSimple(&rocketprop->pos, rocketprop->rooms, SMOKETYPE_ROCKETTAIL); } else { projectile->smoketimer240 -= g_Vars.lvupdate240; } // Blow up rocket if it's gone too high if (rocketprop->pos.y > 10000.0f) { rocket->timer240 = 0; } // Check if close to an enemy if (ownerchr && rocket->timer240) { for (i = 0; i < g_MpNumChrs; i++) { struct chrdata *chr = mpGetChrFromPlayerIndex(i); if (chr != ownerchr && !chrIsDead(chr) && chrCompareTeams(ownerchr, chr, COMPARE_ENEMIES) && !botIsTargetInvisible(NULL, chr)) { // Explode if within 250 units xdist = rocketprop->pos.x - chr->prop->pos.x; ydist = rocketprop->pos.y - chr->prop->pos.y; zdist = rocketprop->pos.z - chr->prop->pos.z; if (xdist * xdist + ydist * ydist + zdist * zdist < 250 * 250) { rocket->timer240 = 0; break; } // Check if rocket can fly directly to target if (chrGetTargetProp(ownerchr) == chr->prop && mpPlayerGetIndex(ownerchr) == g_Vars.lvframenum % g_MpNumChrs && cdTestLos05(&rocketprop->pos, rocketprop->rooms, &chr->prop->pos, chr->prop->rooms, CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_PATHBLOCKER | CDTYPE_BG | CDTYPE_AIOPAQUE, GEOFLAG_BLOCK_SIGHT)) { projectile->nextsteppos = chr->prop->pos; projectile->numwaypads = 0; } } } } // If target is lost for 8 seconds, explode if (ownerchr && ownerchr->target == -1) { projectile->numwaypads = 0; projectile->losttimer240 += g_Vars.lvupdate240; if (projectile->losttimer240 > 8 * TICKS(240)) { rocket->timer240 = 0; } } else { projectile->losttimer240 = 0; } // If bot is killed, rocket becomes uncontrolled if (ownerchr && ((ownerchr->aibot && ownerchr->aibot->skrocket == NULL) || chrIsDead(ownerchr))) { projectile->ownerprop = NULL; } return cdresult; } s32 projectileLaunch(struct defaultobj *obj, struct projectile *projectile, struct coord *arg2, struct coord *arg3) { s32 cdresult; struct prop *prop = obj->prop; if (projectile->ownerprop) { propSetPerimEnabled(projectile->ownerprop, false); } cdresult = func0f06cd00(obj, &projectile->nextsteppos, arg2, arg3); if (projectile->ownerprop) { propSetPerimEnabled(projectile->ownerprop, true); } if (cdresult == CDRESULT_NOCOLLISION) { s16 rooms[8]; func0f065e74(&prop->pos, prop->rooms, &projectile->nextsteppos, rooms); prop->pos = projectile->nextsteppos; propDeregisterRooms(prop); roomsCopy(rooms, prop->rooms); } else if (cdresult != CDRESULT_NOCOLLISION && obj->type == OBJTYPE_WEAPON) { struct weaponobj *weapon = (struct weaponobj *)obj; s16 rooms[8]; if (weapon->weaponnum == WEAPON_ROCKET || weapon->weaponnum == WEAPON_HOMINGROCKET) { weapon->timer240 = 0; func0f065e74(&prop->pos, prop->rooms, arg2, rooms); prop->pos = *arg2; propDeregisterRooms(prop); roomsCopy(rooms, prop->rooms); } } projectile->flags &= ~PROJECTILEFLAG_LAUNCHING; return cdresult; } s32 projectileTick(struct defaultobj *obj, bool *embedded) { struct projectile *projectile = obj->projectile; s32 cdresult; struct coord sp5f4; struct coord sp5e8; struct coord sp5dc; bool result = false; struct prop *prop = obj->prop; struct coord sp5c8; s16 sp5b8[8]; struct coord sp5ac; f32 sp5a8; struct coord sp59c; struct coord sp590; f32 sp58c; bool haslimitedarea; f32 ground; Mtxf sp544; Mtxf sp504; Mtxf sp4c4; Mtxf sp484; struct pad *pad; f32 dist; f32 shield; s32 i; if (g_Vars.lvupdate240 > 0) { if (obj->type == OBJTYPE_WEAPON && ((struct weaponobj *)obj)->weaponnum == WEAPON_SKROCKET) { result = rocketTickFbw((struct weaponobj *) obj); } else if (projectile->flags & PROJECTILEFLAG_00001000) { result = (projectile->flags & PROJECTILEFLAG_00002000) != 0; projectile->flags &= ~(PROJECTILEFLAG_00001000 | PROJECTILEFLAG_00002000); } else { obj->hidden &= ~OBJHFLAG_00020000; if (projectile->flags & PROJECTILEFLAG_LAUNCHING) { projectileLaunch(obj, projectile, &sp5e8, &sp5f4); } sp5dc = prop->pos; if (projectile->pickuptimer240 > 0) { projectile->pickuptimer240 -= g_Vars.lvupdate240; } if (projectile->flags & PROJECTILEFLAG_SLIDING) { f32 x; f32 innerdist; f32 outerdist; f32 z; mtx3ToMtx4(obj->realrot, &sp504); mtx4SetTranslation(&prop->pos, &sp504); if (projectile->unk0dc > 0.0f) { projectile->unk0dc -= projectile->unk0e0 * g_Vars.lvupdate60freal; if (projectile->unk0dc < 0.0f) { projectile->unk0dc = 0.0f; } else if (projectile->unk0e4 < 1.0f) { for (i = 0; i < g_Vars.lvupdate60; i++) { if (projectile->unk0dc > projectile->unk0ec) { projectile->unk0dc = projectile->unk0ec + (projectile->unk0dc - projectile->unk0ec) * projectile->unk0e4; } } } } else if (projectile->unk0dc < 0.0f) { projectile->unk0dc += projectile->unk0e0 * g_Vars.lvupdate60freal; if (projectile->unk0dc > 0.0f) { projectile->unk0dc = 0.0f; } else if (projectile->unk0e4 < 1.0f) { for (i = 0; i < g_Vars.lvupdate60; i++) { if (projectile->unk0dc < -projectile->unk0ec) { projectile->unk0dc = -projectile->unk0ec + (projectile->unk0dc + projectile->unk0ec) * projectile->unk0e4; } } } } if ((projectile->speed.f[0] != 0.0f || projectile->speed.f[2] != 0.0f) && projectile->unk098 > 0.0f) { dist = sqrtf(projectile->speed.f[0] * projectile->speed.f[0] + projectile->speed.f[2] * projectile->speed.f[2]); if (dist > 0.0f) { f32 f12 = projectile->unk098 * g_Vars.lvupdate60freal / dist; if (f12 >= 1.0f) { projectile->speed.x = 0.0f; projectile->speed.z = 0.0f; } else { projectile->speed.x -= projectile->speed.x * f12; projectile->speed.z -= projectile->speed.z * f12; if (projectile->unk0e4 < 1.0f) { for (i = 0; i < g_Vars.lvupdate60; i++) { if (projectile->speed.x > projectile->unk0f0) { projectile->speed.x = (projectile->speed.x - projectile->unk0f0) * projectile->unk0e4 + projectile->unk0f0; } else if (projectile->speed.x < -projectile->unk0f0) { projectile->speed.x = (projectile->speed.x + projectile->unk0f0) * projectile->unk0e4 + -projectile->unk0f0; } if (projectile->speed.z > projectile->unk0f0) { projectile->speed.z = (projectile->speed.z - projectile->unk0f0) * projectile->unk0e4 + projectile->unk0f0; } else if (projectile->speed.z < -projectile->unk0f0) { projectile->speed.z = (projectile->speed.z + projectile->unk0f0) * projectile->unk0e4 + -projectile->unk0f0; } } } } } else { projectile->speed.x = 0.0f; projectile->speed.z = 0.0f; } } if (projectile->ownerprop) { propSetPerimEnabled(projectile->ownerprop, false); } // Objects become more difficult to push // as you push them away from their pad haslimitedarea = obj->pad >= 0 && (obj->flags3 & (OBJFLAG3_GRABBABLE | OBJFLAG3_PUSHFREELY)) == 0; if (haslimitedarea) { pad = &g_Pads[obj->pad]; innerdist = 200.0f; outerdist = 300.0f; if (obj->flags3 & OBJFLAG3_LONGPUSHRANGE) { innerdist += 700.0f; outerdist += 700.0f; } if (obj->modelnum == MODEL_SKPUZZLEOBJECT) { innerdist = 230.0f; } x = pad->pos.x - prop->pos.x; z = pad->pos.z - prop->pos.z; dist = sqrtf(x * x + z * z); if (dist > outerdist) { projectile->unk0dc = 0.0f; } else if (dist > innerdist) { projectile->unk0dc *= (outerdist - dist) * 0.01f; } } sp5a8 = projectile->unk0dc * g_Vars.lvupdate60freal; if (sp5a8 != 0.0f) { struct coord sp404 = {0, 0, 0}; cdresult = func0f072144(obj, &sp404, sp5a8, true); if (cdresult != CDRESULT_ERROR && cdresult == CDRESULT_COLLISION) { projectile->unk0dc = -projectile->unk0dc * projectile->unk08c; objCollide(obj, &sp404, sp5a8); } } sp59c.x = projectile->speed.x * g_Vars.lvupdate60freal; sp59c.y = 0.0f; sp59c.z = projectile->speed.z * g_Vars.lvupdate60freal; if (haslimitedarea) { x = pad->pos.x - prop->pos.x - sp59c.x; z = pad->pos.z - prop->pos.z - sp59c.z; dist = sqrtf(x * x + z * z); if (dist > outerdist) { projectile->speed.f[0] = \ projectile->speed.f[2] = \ projectile->unk0dc = \ sp59c.f[0] = \ sp59c.f[2] = 0.0f; } else if (dist > innerdist) { projectile->speed.x *= (outerdist - dist) * 0.01f; projectile->speed.z *= (outerdist - dist) * 0.01f; sp59c.x *= (outerdist - dist) * 0.01f; sp59c.z *= (outerdist - dist) * 0.01f; } } cdresult = func0f072144(obj, &sp59c, 0.0f, true); if (cdresult == CDRESULT_COLLISION) { sp58c = objCollide(obj, &sp59c, 0.0f) * projectile->unk08c; if (sp58c > 0.0f) { f32 f0; struct coord sp3f4; struct coord sp3e8; struct coord sp3dc; struct coord sp3d0; struct coord sp3c4; struct coord sp3b8; struct coord sp3ac; f32 f0_2; if (cdGetSavedPos(&sp3d0, &sp3c4)) { sp3c4.x -= sp3d0.x; sp3c4.y -= sp3d0.y; sp3c4.z -= sp3d0.z; } else { cdGetEdge(&sp3d0, &sp3c4, 8339, "propobj.c"); sp3d0.x -= sp3c4.x; sp3d0.y -= sp3c4.y; sp3d0.z -= sp3c4.z; sp3c4.x = -sp3d0.z; sp3c4.y = sp3d0.y; sp3c4.z = sp3d0.x; sp3d0 = prop->pos; } func0f02e4f8(&sp3d0, &sp3c4, &sp3b8); sp3ac.x = prop->pos.x - sp5dc.x; sp3ac.y = 0.0f; sp3ac.z = prop->pos.z - sp5dc.z; sp3b8.x -= prop->pos.x; sp3b8.z -= prop->pos.z; f0 = (-sp3b8.f[0] * sp3ac.f[2] + sp3b8.f[2] * sp3ac.f[0]) * 0.0001f; if (f0 > projectile->unk0ec) { f0 = projectile->unk0ec; } else if (f0 < -projectile->unk0ec) { f0 = -projectile->unk0ec; } projectile->unk0dc += f0; cdGetEdge(&sp3e8, &sp3dc, 8377, "propobj.c"); sp3f4.x = sp3dc.z - sp3e8.z; sp3f4.y = 0.0f; sp3f4.z = sp3e8.x - sp3dc.x; if (sp3f4.f[0] != 0.0f || sp3f4.f[2] != 0.0f) { guNormalize(&sp3f4.x, &sp3f4.y, &sp3f4.z); } else { sp3f4.z = 1.0f; } f0_2 = projectile->speed.f[0] * sp3f4.f[0] + projectile->speed.f[2] * sp3f4.f[2]; f0_2 *= -(sp58c + 1.0f); sp590.x = projectile->speed.x + sp3f4.f[0] * f0_2; sp590.y = 0.0f; sp590.z = projectile->speed.z + sp3f4.f[2] * f0_2; sp59c.x = sp590.f[0] * g_Vars.lvupdate60freal; sp59c.y = 0.0f; sp59c.z = sp590.f[2] * g_Vars.lvupdate60freal; if (haslimitedarea) { x = pad->pos.x - prop->pos.x - sp59c.x; z = pad->pos.z - prop->pos.z - sp59c.z; dist = sqrtf(x * x + z * z); if (dist > outerdist) { projectile->speed.f[0] = \ projectile->speed.f[2] = \ projectile->unk0dc = \ sp59c.f[0] = \ sp59c.f[2] = 0.0f; } else if (dist > innerdist) { projectile->speed.x *= (outerdist - dist) * 0.01f; projectile->speed.z *= (outerdist - dist) * 0.01f; sp59c.x *= (outerdist - dist) * 0.01f; sp59c.z *= (outerdist - dist) * 0.01f; } } cdresult = func0f072144(obj, &sp59c, 0.0f, true); if (cdresult == CDRESULT_COLLISION) { sp58c = objCollide(obj, &sp59c, 0.0f) * projectile->unk08c; sp590.x = -projectile->speed.f[0] * sp58c; sp590.y = 0.0f; sp590.z = -projectile->speed.f[2] * sp58c; sp59c.x = sp590.f[0] * g_Vars.lvupdate60freal; sp59c.y = 0.0f; sp59c.z = sp590.f[2] * g_Vars.lvupdate60freal; if (haslimitedarea) { x = pad->pos.x - prop->pos.x - sp59c.x; z = pad->pos.z - prop->pos.z - sp59c.z; dist = sqrtf(x * x + z * z); if (dist > outerdist) { projectile->speed.f[0] = \ projectile->speed.f[2] = \ projectile->unk0dc = \ sp59c.f[0] = \ sp59c.f[2] = 0.0f; } else if (dist > innerdist) { projectile->speed.x *= (outerdist - dist) * 0.01f; projectile->speed.z *= (outerdist - dist) * 0.01f; sp59c.x *= (outerdist - dist) * 0.01f; sp59c.z *= (outerdist - dist) * 0.01f; } } cdresult = func0f072144(obj, &sp59c, 0.0f, true); if (cdresult == CDRESULT_COLLISION) { objCollide(obj, &sp59c, 0.0f); if (sp3e8.f[0] != sp3dc.f[0] || sp3e8.f[2] != sp3dc.f[2]) { f32 f12; struct coord sp398; f32 f14; sp398.x = sp3dc.x - sp3e8.x; sp398.z = sp3dc.z - sp3e8.z; f12 = 1.0f / sqrtf(sp398.f[0] * sp398.f[0] + sp398.f[2] * sp398.f[2]); sp398.x *= f12; sp398.z *= f12; f14 = projectile->speed.f[0] * sp398.f[0] + projectile->speed.f[2] * sp398.f[2]; sp590.x = sp398.f[0] * f14; sp590.y = 0.0f; sp590.z = sp398.f[2] * f14; sp59c.x = sp590.f[0] * g_Vars.lvupdate60freal; sp59c.y = 0.0f; sp59c.z = sp590.f[2] * g_Vars.lvupdate60freal; if (haslimitedarea) { x = pad->pos.x - prop->pos.x - sp59c.x; z = pad->pos.z - prop->pos.z - sp59c.z; dist = sqrtf(x * x + z * z); if (dist > outerdist) { projectile->speed.f[0] = \ projectile->speed.f[2] = \ projectile->unk0dc = \ sp59c.f[0] = \ sp59c.f[2] = 0.0f; } else if (dist > innerdist) { projectile->speed.x *= (outerdist - dist) * 0.01f; projectile->speed.z *= (outerdist - dist) * 0.01f; sp59c.x *= (outerdist - dist) * 0.01f; sp59c.z *= (outerdist - dist) * 0.01f; } } cdresult = func0f072144(obj, &sp59c, 0.0f, true); if (cdresult == CDRESULT_COLLISION) { objCollide(obj, &sp59c, 0.0f); } } } } if (cdresult == CDRESULT_NOCOLLISION) { projectile->speed = sp590; } } } if (cdresult != CDRESULT_NOCOLLISION) { projectile->unk0dc = 0.0f; projectile->speed.z = 0.0f; projectile->speed.x = 0.0f; } if (projectile->ownerprop) { propSetPerimEnabled(projectile->ownerprop, true); } if (projectile->speed.f[0] == 0.0f && projectile->speed.f[2] == 0.0f && projectile->unk0dc == 0.0f) { objFreeProjectile(obj); } if (cdresult == CDRESULT_NOCOLLISION) { ground = cdFindGroundAtCyl(&prop->pos, 2, prop->rooms, &obj->floorcol, NULL); if (ground > -30000.0f) { prop->pos.y = ground + objGetHov04(obj); } } func0f069c70(obj, false, true); mtx3ToMtx4(obj->realrot, &sp484); mtx4SetTranslation(&prop->pos, &sp484); mtx000172f0(sp504.m, sp4c4.m); mtx4MultMtx4(&sp484, &sp4c4, &sp544); platformDisplaceProps2(prop, &sp544); result = true; } else if (projectile->flags & PROJECTILEFLAG_AIRBORNE) { f32 sp390; s16 roomnum; struct coord sp380; f32 sp37c; f32 realrot[3][3]; bool sp354 = false; bool sp350 = false; bool handled = false; Mtxf sp30c; bool homingrocket; u32 stack; projectile->losttimer240 += g_Vars.lvupdate240; if (((projectile->flags & PROJECTILEFLAG_NOTIMELIMIT) == 0 && projectile->losttimer240 > TICKS(9600)) || prop->pos.y < -20000.0f || prop->pos.y > 32000.0f || prop->pos.x < -32000.0f || prop->pos.x > 32000.0f || prop->pos.z < -32000.0f || prop->pos.z > 32000.0f) { obj->hidden |= OBJHFLAG_REAPABLE; } projectile->flighttime240 += g_Vars.lvupdate240; mtx3Copy(obj->realrot, realrot); if (projectile->flags & PROJECTILEFLAG_00000020) { if (projectile->unk01c < (1.0f / 3.6f)) { projectile->unk0ac += projectile->unk014 * g_Vars.lvupdate60freal; projectile->unk0a8 += projectile->unk0ac * g_Vars.lvupdate60freal; projectile->unk01c += (1.0f / 90.0f) * g_Vars.lvupdate60freal; if (projectile->unk01c > (1.0f / 3.6f)) { projectile->unk01c = (1.0f / 3.6f); } } else { if (projectile->unk0a8 > sp5dc.y) { projectile->unk0ac += projectile->unk014 * g_Vars.lvupdate60freal; projectile->unk0a8 += projectile->unk0ac * g_Vars.lvupdate60freal; sp5dc.y += 0.07f * (projectile->unk0a8 - sp5dc.y) * g_Vars.lvupdate60freal; } else { sp5dc.y = projectile->unk0a8; projectile->flags &= ~PROJECTILEFLAG_00000020; projectile->flags |= PROJECTILEFLAG_POWERED; projectile->speed.y = projectile->unk0ac; projectile->unk01c = 0.0f; } } } homingrocket = false; if (obj->type == OBJTYPE_WEAPON && ((struct weaponobj *)obj)->weaponnum == WEAPON_HOMINGROCKET) { homingrocket = true; } if (homingrocket) { struct coord sp2f8 = {0, 0, 0}; struct coord sp2ec = {0, 0, 1}; struct coord sp2e0 = {0, 1, 0}; if (obj && obj->prop && projectile->targetprop) { Mtxf mtx; f32 sp29c = sqrtf(obj->realrot[0][0] * obj->realrot[0][0] + obj->realrot[1][0] * obj->realrot[1][0] + obj->realrot[2][0] * obj->realrot[2][0]); struct coord sp290; f32 sp28c; mtx4LoadIdentity(&mtx); mtx.m[0][0] = obj->realrot[0][0] / sp29c; mtx.m[0][1] = obj->realrot[0][1] / sp29c; mtx.m[0][2] = obj->realrot[0][2] / sp29c; mtx.m[1][0] = obj->realrot[1][0] / sp29c; mtx.m[1][1] = obj->realrot[1][1] / sp29c; mtx.m[1][2] = obj->realrot[1][2] / sp29c; mtx.m[2][0] = obj->realrot[2][0] / sp29c; mtx.m[2][1] = obj->realrot[2][1] / sp29c; mtx.m[2][2] = obj->realrot[2][2] / sp29c; sp2f8 = obj->prop->pos; sp290.x = projectile->targetprop->pos.x - sp2f8.x; sp290.y = projectile->targetprop->pos.y - sp2f8.y; sp290.z = projectile->targetprop->pos.z - sp2f8.z; guNormalize(&sp290.x, &sp290.y, &sp290.z); sp2ec = projectile->speed; guNormalize(&sp2ec.x, &sp2ec.y, &sp2ec.z); sp28c = acosf(sp2ec.f[0] * sp290.f[0] + sp2ec.f[1] * sp290.f[1] + sp2ec.f[2] * sp290.f[2]); if (sp28c > 0.001f || sp28c < -0.001f) { struct coord sp280; f32 sp270[4]; f32 sp260[4]; f32 sp250[4]; f32 angle; Mtxf sp20c; f32 tmp; u32 stack[2]; static f32 var80069bc4 = 0; tmp = ((20 / 100.0f * var80069bc4 / LVUPDATE60FREAL()) + (120 / 100.00f * sp28c * LVUPDATE60FREAL())) * (3 / 100.000f); var80069bc4 = sp28c; sp280.x = sp2ec.f[1] * sp290.f[2] - sp2ec.f[2] * sp290.f[1]; sp280.y = -(sp2ec.f[0] * sp290.f[2] - sp2ec.f[2] * sp290.f[0]); sp280.z = sp2ec.f[0] * sp290.f[1] - sp2ec.f[1] * sp290.f[0]; sp260[0] = cosf(tmp * 0.5f); sp260[1] = sp280.f[0] * sinf(tmp * 0.5f); sp260[2] = sp280.f[1] * sinf(tmp * 0.5f); sp260[3] = sp280.f[2] * sinf(tmp * 0.5f); quaternionToMtx(sp260, &sp20c); projectile->unk018 = 0.0f; projectile->unk014 = 0.0f; projectile->unk010 = 0.0f; mtx4RotateVecInPlace(&sp20c, &projectile->speed); quaternion0f097044(&mtx, sp270); quaternionMultQuaternion(sp270, sp260, sp250); quaternionToMtx(sp250, &sp20c); obj->realrot[0][0] = sp20c.m[0][0] * sp29c; obj->realrot[0][1] = sp20c.m[0][1] * sp29c; obj->realrot[0][2] = sp20c.m[0][2] * sp29c; obj->realrot[1][0] = sp20c.m[1][0] * sp29c; obj->realrot[1][1] = sp20c.m[1][1] * sp29c; obj->realrot[1][2] = sp20c.m[1][2] * sp29c; obj->realrot[2][0] = sp20c.m[2][0] * sp29c; obj->realrot[2][1] = sp20c.m[2][1] * sp29c; obj->realrot[2][2] = sp20c.m[2][2] * sp29c; } } } if ((projectile->flags & PROJECTILEFLAG_POWERED) == 0) { // Apply gravity f32 f0; projectile->speed.y += (projectile->unk014 + projectile->unk01c) * g_Vars.lvupdate60freal; if (projectile->flags & PROJECTILEFLAG_LIGHTWEIGHT) { f0 = projectile->speed.y - (1.0f / 7.2f) * g_Vars.lvupdate60freal; } else { f0 = projectile->speed.y - (1.0f / 3.6f) * g_Vars.lvupdate60freal; } sp5dc.y += g_Vars.lvupdate60freal * (projectile->speed.y + f0) * 0.5f; projectile->speed.y = f0; } else { projectile->speed.y += (projectile->unk014 + projectile->unk01c) * g_Vars.lvupdate60freal; sp5dc.y += projectile->speed.y * g_Vars.lvupdate60freal; } projectile->speed.x += projectile->unk010 * g_Vars.lvupdate60freal; projectile->speed.z += projectile->unk018 * g_Vars.lvupdate60freal; sp5dc.x += projectile->speed.x * g_Vars.lvupdate60freal; sp5dc.z += projectile->speed.z * g_Vars.lvupdate60freal; mtx3ToMtx4(obj->realrot, &sp30c); func0f096698(&sp30c, &projectile->mtx, g_Vars.lvupdate240); mtx4ToMtx3(&sp30c, obj->realrot); sp5c8 = prop->pos; roomsCopy(prop->rooms, sp5b8); if (projectile->ownerprop) { propSetPerimEnabled(projectile->ownerprop, false); } if (projectile->flags & PROJECTILEFLAG_STICKY) { cdresult = func0f06cd00(obj, &sp5dc, &sp5e8, &sp5f4); } else { cdresult = func0f06d37c(obj, &sp5dc, &sp5e8, &sp5f4); } if (projectile->ownerprop) { propSetPerimEnabled(projectile->ownerprop, true); } result = true; if (projectile->flags & PROJECTILEFLAG_STICKY) { if (cdresult == CDRESULT_COLLISION) { bool stick = false; struct prop *hitprop = NULL; struct weaponobj *weapon2; struct weaponobj *weapon; if (g_EmbedProp != NULL) { hitprop = g_EmbedProp; while (hitprop->parent) { hitprop = hitprop->parent; } } if (obj->type == OBJTYPE_AUTOGUN) { // Thrown laptops can stick to the BG but not props if (hitprop == NULL) { stick = true; } } else if (obj->type == OBJTYPE_WEAPON) { weapon2 = (struct weaponobj *) obj; if (weapon2->weaponnum == WEAPON_REMOTEMINE || weapon2->weaponnum == WEAPON_TIMEDMINE || weapon2->weaponnum == WEAPON_PROXIMITYMINE || weapon2->weaponnum == WEAPON_COMMSRIDER || weapon2->weaponnum == WEAPON_TRACERBUG || weapon2->weaponnum == WEAPON_TARGETAMPLIFIER || weapon2->weaponnum == WEAPON_BOLT || weapon2->weaponnum == WEAPON_COMBATKNIFE || weapon2->weaponnum == WEAPON_ECMMINE || gsetHasFunctionFlags(&weapon2->gset, FUNCFLAG_STICKTOWALL)) { stick = true; if (weapon2->weaponnum == WEAPON_GRENADEROUND && weapon2->gunfunc == FUNC_SECONDARY) { if (weapon2->timer240 == 1) { stick = false; weapon2->timer240 = 0; } else { weapon2->timer240 = TICKS(480); } } } } if (stick) { weapon = NULL; if (obj->type == OBJTYPE_WEAPON) { weapon = (struct weaponobj *) obj; } if (g_EmbedProp && (g_EmbedProp->type & (PROPTYPE_OBJ | PROPTYPE_WEAPON | PROPTYPE_DOOR))) { struct defaultobj *embedobj = g_EmbedProp->obj; if (weapon && (weapon->weaponnum == WEAPON_BOLT || weapon->weaponnum == WEAPON_COMBATKNIFE) && embedobj->type == OBJTYPE_WEAPON) { stick = false; } } if (hitprop != NULL) { if (hitprop->type & (PROPTYPE_OBJ | PROPTYPE_WEAPON | PROPTYPE_DOOR)) { struct defaultobj *hitobj = hitprop->obj; if ((hitobj->hidden & OBJHFLAG_PROJECTILE) && (hitobj->projectile->flags & PROJECTILEFLAG_SLIDING) == 0) { stick = false; } if (var80069944 == 10000) { stick = false; } if (weapon && (weapon->weaponnum == WEAPON_BOLT || weapon->weaponnum == WEAPON_COMBATKNIFE)) { if (hitobj->type == OBJTYPE_GLASS || hitobj->type == OBJTYPE_TINTEDGLASS) { bgunPlayGlassHitSound(&prop->pos, prop->rooms, -1); if ((hitobj->flags2 & OBJFLAG2_IMMUNETOGUNFIRE) == 0) { objTakeGunfire(hitobj, 100, &prop->pos, weapon->weaponnum, g_Vars.currentplayernum); objDropRecursively(hitprop, false); stick = false; handled = true; } } } } else if ((hitprop->type & (PROPTYPE_CHR | PROPTYPE_PLAYER)) && chrGetShield(hitprop->chr) > 0.0f) { stick = false; } } } if (!handled && g_EmbedProp && obj->type == OBJTYPE_WEAPON) { struct weaponobj *weapon = (struct weaponobj *) obj; if (weapon->weaponnum == WEAPON_BOLT || weapon->weaponnum == WEAPON_COMBATKNIFE) { if (hitprop->type == PROPTYPE_CHR || (hitprop->type == PROPTYPE_PLAYER && hitprop->chr)) { struct chrdata *hitchr = hitprop->chr; if ((obj->projectile->flags & PROJECTILEFLAG_AIRBORNE) && obj->projectile->bouncecount <= 0) { f32 ownershield; struct prop *ownerprop; ownerprop = obj->projectile->ownerprop; ownershield = chrGetShield(hitchr); func0f0341dc(hitchr, gsetGetDamage(&weapon->gset), &var8009ce78, &weapon->gset, ownerprop, g_EmbedHitPart, g_EmbedProp, g_EmbedNode, g_EmbedModel, g_EmbedSide, var8006993c); if (ownershield <= 0.0f) { chrEmitSparks(hitchr, g_EmbedProp, g_EmbedHitPart, &sp5e8, &sp5f4, ownerprop ? ownerprop->chr : NULL); if (g_EmbedProp->flags & PROPFLAG_ONTHISSCREENTHISTICK) { if (g_EmbedModel && g_EmbedHitPart != HITPART_GUN && g_EmbedHitPart != HITPART_HAT) { struct coord sp1c8; Mtxf sp188; Mtxf *sp184; sp184 = model0001a5cc(g_EmbedModel, g_EmbedNode, 0); mtx4TransformVec(camGetWorldToScreenMtxf(), &sp5e8, &sp1c8); mtx0001719c(sp184->m, sp188.m); mtx4TransformVecInPlace(&sp188, &sp1c8); chr0f0260c4(g_EmbedModel, g_EmbedHitPart, g_EmbedNode, &sp1c8); } } } } } else if (hitprop->type == PROPTYPE_OBJ) { struct defaultobj *hitobj = hitprop->obj; if (var80069944 == 10000) { shield = (hitobj->flags3 & OBJFLAG3_SHOWSHIELD) ? 4 : 8; shieldhitCreate(hitprop, shield, g_EmbedProp, g_EmbedNode, g_EmbedModel, g_EmbedSide, var8006993c); } if (hitobj->modelnum == MODEL_TARGET && var80069944 == TEXTURE_0B9E) { frCalculateHit(hitobj, &sp5e8, 0.0f); } } } else if (weapon->weaponnum == WEAPON_ROCKET || weapon->weaponnum == WEAPON_HOMINGROCKET) { s32 ownerplayernum = (obj->hidden & 0xf0000000) >> 28; if (g_EmbedProp->type == PROPTYPE_CHR || (g_EmbedProp->type == PROPTYPE_PLAYER && g_EmbedProp->chr)) { struct prop *ownerprop2 = NULL; if (g_Vars.normmplayerisrunning) { struct chrdata *ownerchr = mpGetChrFromPlayerIndex(ownerplayernum); if (ownerchr != NULL) { ownerprop2 = ownerchr->prop; } } func0f0341dc(g_EmbedProp->chr, 2.0f, &var8009ce78, &weapon->gset, ownerprop2, g_EmbedHitPart, g_EmbedProp, g_EmbedNode, g_EmbedModel, g_EmbedSide, var8006993c); } else if (g_EmbedProp->type & (PROPTYPE_OBJ | PROPTYPE_WEAPON)) { if (var80069944 == 10000) { f32 shield = (g_EmbedProp->obj->flags3 & OBJFLAG3_SHOWSHIELD) ? 4 : 8; shieldhitCreate(hitprop, shield, g_EmbedProp, g_EmbedNode, g_EmbedModel, g_EmbedSide, var8006993c); } objDamage(g_EmbedProp->obj, 100, &prop->pos, weapon->weaponnum, ownerplayernum); } handled = true; func0f069c70(obj, true, true); weapon->timer240 = 0; } else { if (hitprop->type == PROPTYPE_CHR || (hitprop->type == PROPTYPE_PLAYER && hitprop->chr)) { struct chrdata *chr = hitprop->chr; func0f034080(chr, g_EmbedNode, g_EmbedProp, g_EmbedModel, g_EmbedSide, var8006993c); } else if ((hitprop->type & (PROPTYPE_OBJ | PROPTYPE_WEAPON)) && var80069944 == 10000) { shield = (hitprop->obj->flags3 & OBJFLAG3_SHOWSHIELD) ? 4 : 8; shieldhitCreate(hitprop, shield, g_EmbedProp, g_EmbedNode, g_EmbedModel, g_EmbedSide, var8006993c); } } } if (!handled && stick) { handled = true; func0f069c70(obj, true, true); if (obj->type == OBJTYPE_WEAPON) { struct weaponobj *weapon = (struct weaponobj *) obj; if (weapon->weaponnum == WEAPON_BOLT || weapon->weaponnum == WEAPON_COMBATKNIFE) { if (obj->projectile->ownerprop && obj->projectile->ownerprop->type == PROPTYPE_PLAYER) { s32 prevplayernum = g_Vars.currentplayernum; setCurrentPlayerNum(playermgrGetPlayerNumByProp(obj->projectile->ownerprop)); mpstatsIncrementPlayerShotCount(&weapon->gset, SHOTREGION_TOTAL); if (g_EmbedProp != NULL) { if (g_EmbedProp->type == PROPTYPE_OBJ) { if (objIsHealthy(g_EmbedProp->obj)) { mpstatsIncrementPlayerShotCount(&weapon->gset, SHOTREGION_OBJECT); } } else if (g_EmbedProp->type & (PROPTYPE_CHR | PROPTYPE_PLAYER)) { struct chrdata *embedchr = g_EmbedProp->chr; bool dead = false; if (embedchr && embedchr->actiontype == ACT_DEAD) { dead = true; } if (g_EmbedProp->type == PROPTYPE_PLAYER && g_Vars.players[playermgrGetPlayerNumByProp(g_EmbedProp)]->isdead) { dead = true; } if (!dead) { switch (g_EmbedHitPart) { case 0: break; case HITPART_HEAD: mpstatsIncrementPlayerShotCount(&weapon->gset, SHOTREGION_HEAD); break; case HITPART_GUN: mpstatsIncrementPlayerShotCount(&weapon->gset, SHOTREGION_GUN); break; case HITPART_HAT: mpstatsIncrementPlayerShotCount(&weapon->gset, SHOTREGION_HAT); break; case HITPART_PELVIS: case HITPART_TORSO: mpstatsIncrementPlayerShotCount(&weapon->gset, SHOTREGION_BODY); break; default: mpstatsIncrementPlayerShotCount(&weapon->gset, SHOTREGION_LIMB); break; } } } } setCurrentPlayerNum(prevplayernum); } if (hitprop == NULL || (hitprop->type & (PROPTYPE_OBJ | PROPTYPE_WEAPON | PROPTYPE_DOOR))) { struct coord dir; struct prop *ownerprop = obj->projectile->ownerprop; dir = projectile->speed; guNormalize(&dir.x, &dir.y, &dir.z); if (chrIsUsingPaintball(ownerprop ? ownerprop->chr : NULL)) { sparksCreate(prop->rooms[0], prop, &sp5e8, &dir, &sp5f4, SPARKTYPE_PAINT); } else { sparksCreate(prop->rooms[0], prop, &sp5e8, &dir, &sp5f4, SPARKTYPE_PROJECTILE); } } } } objLand(prop, &sp5e8, &sp5f4, embedded); } } if (!handled) { if (cdresult != CDRESULT_COLLISION) { s16 rooms[8]; func0f065e74(&prop->pos, prop->rooms, &sp5dc, rooms); prop->pos = sp5dc; propDeregisterRooms(prop); roomsCopy(rooms, prop->rooms); } else { s16 rooms[8]; if (g_EmbedProp && (g_EmbedProp->type & (PROPTYPE_CHR | PROPTYPE_PLAYER))) { sp5dc.x = prop->pos.x; sp5dc.z = prop->pos.z; } else { sp5dc = sp5e8; } func0f065e74(&prop->pos, prop->rooms, &sp5dc, rooms); prop->pos = sp5dc; propDeregisterRooms(prop); roomsCopy(rooms, prop->rooms); } } } if (!handled) { u16 geoflags; sp37c = objGetRotatedLocalYMinByMtx3(objFindBboxRodata(obj), obj->realrot); sp5ac.x = prop->pos.x; sp5ac.y = prop->pos.y + sp37c; sp5ac.z = prop->pos.z; roomnum = cdFindCeilingRoomYColourFlagsNormalAtPos(&sp5ac, prop->rooms, &sp390, &obj->floorcol, &geoflags, &sp380); if (roomnum > 0 && prop->pos.y + sp37c < sp390 && !cdTestLos03(&sp5c8, sp5b8, &sp5ac, CDTYPE_OBJS | CDTYPE_BG, GEOFLAG_FLOOR1 | GEOFLAG_FLOOR2)) { sp354 = true; sp5f4 = sp380; guNormalize(&sp5f4.x, &sp5f4.y, &sp5f4.z); sp5e8.x = prop->pos.x; sp5e8.y = sp390; sp5e8.z = prop->pos.z; cdresult = CDRESULT_COLLISION; if (geoflags & GEOFLAG_DIE) { obj->hidden |= OBJHFLAG_REAPABLE; } } else { roomnum = cdFindFloorRoomYColourNormalPropAtPos(&prop->pos, prop->rooms, &sp390, &obj->floorcol, &sp380, NULL); if (roomnum <= 0 && (projectile->flags & PROJECTILEFLAG_STICKY) == 0) { if ((projectile->flags & PROJECTILEFLAG_00010000) == 0) { projectile->flags |= PROJECTILEFLAG_00010000; if (cdFindFloorRoomAtPos(&sp5c8, sp5b8) > 0) { projectile->flags |= PROJECTILEFLAG_INROOM; } } if (projectile->flags & PROJECTILEFLAG_INROOM) { prop->pos = sp5c8; propDeregisterRooms(prop); roomsCopy(sp5b8, prop->rooms); roomnum = cdFindFloorRoomYColourFlagsAtPos(&prop->pos, prop->rooms, &sp390, &obj->floorcol, NULL); projectile->speed.x = 0.0f; projectile->speed.z = 0.0f; } } } if (roomnum > 0) { projectile->flags |= PROJECTILEFLAG_INROOM; } else { projectile->flags &= ~PROJECTILEFLAG_INROOM; } if (cdresult == CDRESULT_COLLISION) { // Bouncing if ((projectile->speed.y <= 0.0f && sp5c8.y <= prop->pos.y) || ((projectile->flags & PROJECTILEFLAG_STICKY) == 0 && sp354)) { sp350 = true; } if (projectile->unk08c > 0.0f) { f32 oldyspeed; f32 f0 = projectile->speed.f[0] * sp5f4.f[0] + projectile->speed.f[1] * sp5f4.f[1] + projectile->speed.f[2] * sp5f4.f[2]; f0 *= -(projectile->unk08c + 1.0f); oldyspeed = projectile->speed.y; projectile->speed.x += f0 * sp5f4.x; projectile->speed.y += f0 * sp5f4.y; projectile->speed.z += f0 * sp5f4.z; if (oldyspeed <= 0.0f && projectile->speed.y >= 0.0f) { sp350 = true; } if (obj->type == OBJTYPE_WEAPON) { struct weaponobj *weapon = (struct weaponobj *) obj; if (weapon->weaponnum == WEAPON_GRENADE && weapon->gunfunc == FUNC_SECONDARY) { smokeCreateAtProp(prop, SMOKETYPE_PINBALL); } } } if (sp350) { prop->pos.y = sp5e8.y - sp37c; if (sp354) { prop->pos.y += func0f06a620(obj); } } if ((projectile->flags & PROJECTILEFLAG_00000100) == 0 && (projectile->bounceframe < 0 || projectile->bounceframe < g_Vars.lvframe60 - TICKS(60))) { mtxLoadRandomRotation(&projectile->mtx); } projectile->bouncecount++; projectile->bounceframe = g_Vars.lvframe60; if ((obj->hidden & OBJHFLAG_00010000) == 0) { obj->hidden |= OBJHFLAG_00000100; } if (sp350) { if ((projectile->flags & PROJECTILEFLAG_STICKY) == 0 && projectile->bouncecount >= 6) { if (sp354) { projectileFall(obj, realrot); } } else if (projectile->unk08c > 0.0f) { if (projectile->speed.y >= 0.0f && projectile->speed.y < 2.2222223f) { if ((projectile->flags & PROJECTILEFLAG_00000002) && projectile->bouncecount == 1) { projectile->speed.y = 2.2222223f; } else { if (sp354) { projectileFall(obj, realrot); } } } } else { if (sp354) { projectileFall(obj, realrot); } } } } if (obj->type == OBJTYPE_WEAPON) { struct weaponobj *weapon = (struct weaponobj *) obj; if (weapon->weaponnum == WEAPON_COMBATKNIFE && weapon->gunfunc == FUNC_SECONDARY) { knifePlayWooshSound(obj); } else if (weapon->weaponnum == WEAPON_ROCKET) { if (cdresult == CDRESULT_COLLISION) { weapon->timer240 = 0; } else { f32 tmp = projectile->speed.f[0] * projectile->speed.f[0] + projectile->speed.f[1] * projectile->speed.f[1] + projectile->speed.f[2] * projectile->speed.f[2]; if (tmp > 27777.773f) { projectile->unk010 = 0.0f; projectile->unk014 = 0.0f; projectile->unk018 = 0.0f; } if (projectile->powerlimit240 >= 0 && projectile->flighttime240 > projectile->powerlimit240) { projectile->unk01c = 0.0f; projectile->flags &= ~(PROJECTILEFLAG_POWERED | PROJECTILEFLAG_00000020); } else { struct coord smokepos; smokepos = projectile->speed; guNormalize(&smokepos.x, &smokepos.y, &smokepos.z); smokepos.x = prop->pos.x - smokepos.x * 20.0f; smokepos.y = prop->pos.y - smokepos.y * 20.0f; smokepos.z = prop->pos.z - smokepos.z * 20.0f; smokeCreateSimple(&smokepos, prop->rooms, SMOKETYPE_ROCKETTAIL); } } } else if (weapon->weaponnum == WEAPON_HOMINGROCKET) { if (cdresult == CDRESULT_COLLISION) { weapon->timer240 = 0; } else { smokeCreateSimple(&prop->pos, prop->rooms, SMOKETYPE_HOMINGTAIL); } } else if (weapon->weaponnum == WEAPON_GRENADEROUND || (weapon->weaponnum == WEAPON_NBOMB && weapon->gunfunc == FUNC_PRIMARY)) { if (sp350 || (projectile->flags & PROJECTILEFLAG_FALLING) || (projectile->speed.x < 0.1f && projectile->speed.x > -0.1f && projectile->speed.y < 0.1f && projectile->speed.y > -0.1f && projectile->speed.z < 0.1f && projectile->speed.z > -0.1f) || (prop->pos.x - sp5c8.x < 0.1f && prop->pos.x - sp5c8.x > -0.1f && prop->pos.y - sp5c8.y < 0.1f && prop->pos.y - sp5c8.y > -0.1f && prop->pos.z - sp5c8.z < 0.1f && prop->pos.z - sp5c8.z > -0.1f)) { if (weapon->weaponnum != WEAPON_NBOMB || weapon->timer240 >= 0) { weapon->timer240 = 0; } } else if (weapon->weaponnum != WEAPON_NBOMB) { smokeCreateSimple(&prop->pos, prop->rooms, SMOKETYPE_GRENADETAIL); } } if (cdresult == CDRESULT_COLLISION) { if (projectile->unk0a4 < g_Vars.lvframenum - 2) { if (weapon->weaponnum == WEAPON_COMBATKNIFE || weapon->weaponnum == WEAPON_COMBATKNIFE) { propsnd0f0939f8(0, prop, SFX_808B, -1, -1, 0, 0, 0, 0, -1.0f, 0, -1, -1.0f, -1.0f, -1.0f); } else if (weapon->weaponnum == WEAPON_GRENADE && weapon->gunfunc == FUNC_SECONDARY) { u16 sp100[] = {SFX_0027, SFX_0028, SFX_0029, SFX_002A}; propsnd0f0939f8(0, prop, sp100[random() % 4], -1, -1, 0, 0, 0, 0, -1.0f, 0, -1, -1.0f, -1.0f, -1.0f); propsnd0f0939f8(0, prop, SFX_EYESPYHIT, -1, -1, 0, 0, 0, 0, -1.0f, 0, -1, -1.0f, -1.0f, -1.0f); } else { propsnd0f0939f8(0, prop, SFX_EYESPYHIT, -1, -1, 0, 0, 0, 0, -1.0f, 0, -1, -1.0f, -1.0f, -1.0f); } } projectile->unk0a4 = g_Vars.lvframenum; } } func0f069c70(obj, true, true); } } else if (projectile->flags & PROJECTILEFLAG_FALLING) { // Some objects are placed in mid-air and then given this flag // at level start, which causes them fall down to their resting // position. Once stopped, the flag is removed. bool stop = true; f32 quaternion[4]; Mtxf spac; if (projectile->unk060 < 1.0f) { projectile->unk060 += projectile->unk064 * g_Vars.lvupdate60freal; if (g_Vars.lvupdate60 > 0) { projectile->unk064 *= 1.1f; } if (projectile->unk060 > 1.0f) { projectile->unk060 = 1.0f; } quaternionSlerp(projectile->unk068, projectile->unk078, projectile->unk060, quaternion); quaternionToMtx(quaternion, &spac); mtx00015e24(projectile->unk0b8[0], &spac); mtx00015e80(projectile->unk0b8[1], &spac); mtx00015edc(projectile->unk0b8[2], &spac); mtx4ToMtx3(&spac, obj->realrot); stop = false; } if (projectile->speed.f[0] != 0.0f || projectile->speed.f[2] != 0.0f || projectile->unk060 < 1.0f) { f32 f12; f32 spa4; s16 roomnum; s32 i; f32 sp98 = objGetRotatedLocalYMinByMtx3(objFindBboxRodata(obj), obj->realrot); u16 geoflags; stop = false; for (i = 0; i < g_Vars.lvupdate60; i++) { sp5dc.x += PALUPF(projectile->speed.x); sp5dc.z += PALUPF(projectile->speed.z); if (projectile->unk060 >= 1.0f) { if (projectile->unk098 > 0.0f) { f32 dist = sqrtf(projectile->speed.f[0] * projectile->speed.f[0] + projectile->speed.f[2] * projectile->speed.f[2]); if (dist > 0.0f) { f12 = projectile->unk098 * g_Vars.lvupdate60freal / dist; if (f12 >= 1.0f) { projectile->speed.x = 0.0f; projectile->speed.z = 0.0f; } else { projectile->speed.x -= projectile->speed.x * f12; projectile->speed.z -= projectile->speed.z * f12; } } else { projectile->speed.x = 0.0f; projectile->speed.z = 0.0f; } } else { projectile->speed.x *= 0.9f; projectile->speed.z *= 0.9f; } } } sp5c8 = prop->pos; roomsCopy(prop->rooms, sp5b8); func0f06d37c(obj, &sp5dc, &sp5e8, &sp5f4); result = true; sp5ac.x = prop->pos.x; sp5ac.y = prop->pos.y + sp98; sp5ac.z = prop->pos.z; roomnum = cdFindCeilingRoomYColourFlagsAtPos(&sp5ac, prop->rooms, &spa4, &obj->floorcol, &geoflags); if (roomnum <= 0 || cdTestLos03(&sp5c8, sp5b8, &sp5ac, CDTYPE_OBJS | CDTYPE_BG, GEOFLAG_FLOOR1 | GEOFLAG_FLOOR2)) { roomnum = cdFindFloorRoomYColourFlagsAtPos(&prop->pos, prop->rooms, &spa4, &obj->floorcol, &geoflags); } if (roomnum <= 0) { prop->pos.x = sp5c8.x; prop->pos.z = sp5c8.z; propDeregisterRooms(prop); roomsCopy(sp5b8, prop->rooms); roomnum = cdFindFloorRoomYColourFlagsAtPos(&prop->pos, prop->rooms, &spa4, &obj->floorcol, &geoflags); projectile->speed.x = 0.0f; projectile->speed.z = 0.0f; } if (roomnum > 0) { prop->pos.y = spa4 - sp98 + func0f06a620(obj); if (geoflags & GEOFLAG_DIE) { obj->hidden |= OBJHFLAG_REAPABLE; } } else { prop->pos.y = sp5c8.y; } if (projectile->speed.x < 0.1f && projectile->speed.x > -0.1f && projectile->speed.z < 0.1f && projectile->speed.z > -0.1f) { projectile->speed.z = 0.0f; projectile->speed.x = 0.0f; } } if (stop) { objFreeProjectile(obj); } if (result) { func0f069c70(obj, true, true); } } } } return result; } void doorTick(struct prop *doorprop) { struct doorobj *door = (struct doorobj *)doorprop->obj; struct model *model = door->base.model; f32 prevfrac = door->frac; u32 stack[2]; // If door should autoclose this tick if (door->lastopen60 > 0 && door->mode == DOORMODE_IDLE && (door->base.flags & OBJFLAG_DOOR_KEEPOPEN) == 0 && door->lastopen60 < g_Vars.lvframe60 - TICKS(door->autoclosetime)) { // Check if any sibling is automatic struct doorobj *loopdoor = door->sibling; bool pass = door->doorflags & DOORFLAG_AUTOMATIC; while (loopdoor && loopdoor != door && !pass) { pass = loopdoor->doorflags & DOORFLAG_AUTOMATIC; loopdoor = loopdoor->sibling; } if (!pass) { // Not automatic doorsRequestMode(door, DOORMODE_CLOSING); } else if (door->doorflags & DOORFLAG_AUTOMATIC) { // Check if any sibling has anything in range pass = !doorIsRangeEmpty(door); loopdoor = door->sibling; while (loopdoor && loopdoor != door && !pass) { pass = !doorIsRangeEmpty(loopdoor); loopdoor = loopdoor->sibling; } if (pass) { // Something is in range, so keep open loopdoor = door->sibling; door->lastopen60 = g_Vars.lvframe60; while (loopdoor && loopdoor != door) { loopdoor->lastopen60 = g_Vars.lvframe60; loopdoor = loopdoor->sibling; } } else { doorsRequestMode(door, DOORMODE_CLOSING); } } } // If waiting for sibling to close, check for that if (door->mode == DOORMODE_WAITING) { bool shouldopen = true; struct doorobj *loopdoor = door->sibling; while (loopdoor && loopdoor != door) { if (loopdoor->mode != DOORMODE_IDLE || loopdoor->frac > 0) { shouldopen = false; } loopdoor = loopdoor->sibling; } if (shouldopen) { doorSetMode(door, DOORMODE_OPENING); } } // Update frac if (door->lastcalc60 < g_Vars.lvframe60 || g_Vars.lvupdate240 == 0) { doorsCalcFrac(door); } // Consider playing a sound effect if (model->filedata->skel == &g_Skel13) { f32 soundpoint = door->maxfrac * 0.3f; if (door->frac > soundpoint) { if (prevfrac <= soundpoint) { // frac increased past the soundpoint propsnd0f0939f8(NULL, doorprop, SFX_DOOR_8014, -1, -1, 0, 0, 12, 0, -1, 0, -1, -1, -1, -1); } } else { if (prevfrac > soundpoint) { // frac decreased past the soundpoint propsnd0f0939f8(NULL, doorprop, SFX_DOOR_8015, -1, -1, 0, 0, 12, 0, -1, 0, -1, -1, -1, -1); } } } } struct escastepkeyframe g_EscaStepKeyframesX[] = { { 0, { -1535.98, -511, -5258.18 } }, { 41, { -1535.98, -505.76, -5188.43 } }, { 149, { -1535.98, -504.79, -5008.75 } }, { 698, { -1535.98, -100.91, -4210.86 } }, { 759, { -1535.98, -98.89, -4109.62 } }, { 800, { -1535.98, -102, -4039.06 } }, { -1, { 0, 0, 0 } }, }; struct escastepkeyframe g_EscaStepKeyframesZ[] = { { 0, { -1932.27, -102, -3824.58 } }, { 41, { -2002.83, -98.89, -3824.58 } }, { 102, { -2104.07, -100.91, -3824.58 } }, { 651, { -2901.96, -504.79, -3824.58 } }, { 759, { -3081.64, -505.76, -3824.58 } }, { 800, { -3151.39, -511, -3824.58 } }, { -1, { 0, 0, 0 } }, }; void doorUpdatePortalIfWindowed(struct prop *doorprop, s32 playercount) { struct doorobj *doorobj = doorprop->door; struct modelnode *node; bool canhide = true; struct model *model = doorprop->obj->model; union modelrwdata *rwdata; if (doorobj->doorflags & DOORFLAG_WINDOWED) { doorobj->fadealpha = glassCalculateOpacity(&doorprop->pos, doorobj->xludist, doorobj->opadist, 0); if (doorobj->fadealpha != 255 || doorobj->frac > 0) { canhide = false; } if (model->filedata->skel == &g_SkelWindowedDoor) { node = modelGetPart(model->filedata, MODELPART_WINDOWEDDOOR_0001); rwdata = modelGetNodeRwData(model, node); if (!rwdata->toggle.visible) { canhide = false; } } if (playercount >= 2) { canhide = false; } if (canhide) { doorDeactivatePortal(doorobj); } else { doorActivatePortal(doorobj); } } } #define MTX(i) ((Mtxf *)((u32)matrices + i * sizeof(Mtxf))) void doorInitMatrices(struct prop *prop) { struct doorobj *door = prop->door; struct model *model = door->base.model; Mtxf *matrices = model->matrices; func0f08c424(door, matrices); mtx00015be0(camGetWorldToScreenMtxf(), matrices); if (model->filedata->skel == &g_Skel11) { union modelrodata *rodata; f32 xrot = M_BADTAU - door->frac * 0.017450513318181f; rodata = modelGetPartRodata(model->filedata, 1); mtx4LoadXRotation(xrot, MTX(1)); mtx4SetTranslation(&rodata->position.pos, MTX(1)); mtx4MultMtx4InPlace(MTX(0), MTX(1)); rodata = modelGetPartRodata(model->filedata, 2); mtx4LoadXRotation(M_BADTAU - xrot, MTX(2)); mtx4SetTranslation(&rodata->position.pos, MTX(2)); mtx4MultMtx4InPlace(MTX(0), MTX(2)); } else if (model->filedata->skel == &g_Skel13) { union modelrodata *rodata; f32 zrot1 = 0; f32 zrot2 = door->frac * 0.017450513318181f; f32 limit = door->maxfrac * 0.3f; s32 i; if (door->frac > limit) { zrot1 = ((door->maxfrac * (door->frac - limit)) / (door->maxfrac - limit)) * 0.017450513318181f; } for (i = 0; i < 6; i++) { s32 index1 = (i << 1) + 1; s32 index2 = (i << 1) + 2; rodata = modelGetPartRodata(model->filedata, index1); mtx4LoadZRotation(zrot1, MTX(index1)); mtx4SetTranslation(&rodata->position.pos, MTX(index1)); mtx4MultMtx4InPlace(MTX(0), MTX(index1)); rodata = modelGetPartRodata(model->filedata, index2); mtx4LoadZRotation(zrot2, MTX(index2)); mtx4SetTranslation(&rodata->position.pos, MTX(index2)); mtx4MultMtx4InPlace(MTX(index1), MTX(index2)); } } } void platformDisplaceProps(struct prop *platform, s16 *propnums, struct coord *prevpos, struct coord *newpos) { struct prop *prop; s16 *propnumptr = propnums; while (*propnumptr >= 0) { prop = &g_Vars.props[*propnumptr]; if (prop->type & (PROPTYPE_OBJ | PROPTYPE_WEAPON)) { struct defaultobj *obj = prop->obj; if ((obj->hidden & OBJHFLAG_00020000) == 0) { if ((obj->hidden & OBJHFLAG_PROJECTILE) == 0 || (obj->projectile->flags & (PROJECTILEFLAG_FALLING | PROJECTILEFLAG_SLIDING))) { struct hov *hov = NULL; if (obj->type == OBJTYPE_HOVERPROP) { struct hoverpropobj *hoverobj = (struct hoverpropobj *)obj; hov = &hoverobj->hov; } else if (obj->type == OBJTYPE_HOVERBIKE) { struct hoverbikeobj *bike = (struct hoverbikeobj *)obj; hov = &bike->hov; } if (hov) { hov->unk30 += newpos->y - prevpos->y; hov->ground += newpos->y - prevpos->y; } prop->pos.x += newpos->x - prevpos->x; prop->pos.y += newpos->y - prevpos->y; prop->pos.z += newpos->z - prevpos->z; propDeregisterRooms(prop); func0f065e74(&platform->pos, platform->rooms, &prop->pos, prop->rooms); func0f069c70(obj, true, true); } } } else if (prop->type == PROPTYPE_CHR) { struct chrdata *chr = prop->chr; s32 nodetype; if (chr->fallspeed.y == 0.0f) { chr->ground += newpos->y - prevpos->y; chr->manground += newpos->y - prevpos->y; chr->sumground = chr->manground * (PAL ? 8.417509f : 9.999998f); prop->pos.x += newpos->x - prevpos->x; prop->pos.y += newpos->y - prevpos->y; prop->pos.z += newpos->z - prevpos->z; propDeregisterRooms(prop); func0f065e74(&platform->pos, platform->rooms, &prop->pos, prop->rooms); chr0f0220ac(chr); modelSetRootPosition(chr->model, &prop->pos); nodetype = chr->model->filedata->rootnode->type; if ((nodetype & 0xff) == MODELNODETYPE_CHRINFO) { struct modelrwdata_chrinfo *rwdata = modelGetNodeRwData(chr->model, chr->model->filedata->rootnode); rwdata->ground += newpos->y - prevpos->y; } } } else if (prop->type == PROPTYPE_PLAYER) { struct defaultobj *platformobj = platform->obj; struct coord sp8c; s32 playernum = playermgrGetPlayerNumByProp(prop); s32 prevplayernum; if (platformobj->type == OBJTYPE_LIFT) { if (g_Vars.players[playernum]->lift == platform && g_Vars.players[playernum]->bondmovemode == MOVEMODE_WALK) { if (platformobj->flags & OBJFLAG_20000000) { g_Vars.players[playernum]->bondextrapos.x += newpos->x - prevpos->x; g_Vars.players[playernum]->bondextrapos.z += newpos->z - prevpos->z; sp8c.x = newpos->x - prevpos->x; sp8c.y = 0.0f; sp8c.z = newpos->z - prevpos->z; prevplayernum = g_Vars.currentplayernum; setCurrentPlayerNum(playernum); bwalk0f0c63bc(&sp8c, 1, CDTYPE_BG); playerUpdatePerimInfo(); bmoveUpdateRooms(g_Vars.players[playernum]); setCurrentPlayerNum(prevplayernum); } if (g_Vars.players[playernum]->inlift && !g_Vars.players[playernum]->onladder && !g_Vars.players[playernum]->isfalling) { struct coord sp78; s16 sp68[8]; f32 ydist = newpos->y - prevpos->y; if (ydist != 0.0f) { g_Vars.players[playernum]->bondextrapos.y += ydist; prevplayernum = g_Vars.currentplayernum; setCurrentPlayerNum(playernum); g_Vars.players[playernum]->vv_ground += ydist; if (ydist > 0.0f || (platformobj->flags & OBJFLAG_80000000) == 0) { sp78.x = prop->pos.x; sp78.y = prop->pos.y + ydist; sp78.z = prop->pos.z; func0f065e74(&prop->pos, prop->rooms, &sp78, sp68); prop->pos = sp78; propDeregisterRooms(prop); roomsCopy(sp68, prop->rooms); g_Vars.players[playernum]->vv_manground += ydist; g_Vars.players[playernum]->sumground = g_Vars.players[playernum]->vv_manground / (PAL ? 0.054400026798248f : 0.045499980449677f); } else if (bwalkTryMoveUpwards(ydist) == CDRESULT_NOCOLLISION) { g_Vars.players[playernum]->vv_manground += ydist; g_Vars.players[playernum]->sumground = g_Vars.players[playernum]->vv_manground / (PAL ? 0.054400026798248f : 0.045499980449677f); } playerUpdatePerimInfo(); bmoveUpdateRooms(g_Vars.players[playernum]); setCurrentPlayerNum(prevplayernum); if (g_Vars.players[playernum]->walkinitmove) { g_Vars.players[playernum]->walkinitstart.y += ydist; } } } } } } propnumptr++; } } void liftTick(struct prop *prop) { struct liftobj *lift = (struct liftobj *)prop->obj; struct defaultobj *obj = prop->obj; struct doorobj *door; struct pad *padcur; struct pad *padaim; f32 segdist; f32 xdiff; f32 ydiff; f32 zdiff; struct coord curcentre; f32 frac; s32 move; struct coord newpos; s16 newrooms[8]; struct coord prevpos; f32 prevdist; s16 propnums[256]; s32 stop; lift->prevpos = prop->pos; if (lift->levelcur != lift->levelaim) { // Lift is not at the desired level. So try to move, but not if the lift // is disabled or if the door needs to be closed first. move = true; if (obj->flags & OBJFLAG_DEACTIVATED) { move = false; } else if (lift->doors[lift->levelcur] && !doorIsClosed(lift->doors[lift->levelcur])) { doorsRequestMode(lift->doors[lift->levelcur], DOORMODE_CLOSING); move = false; } if (move) { prevpos = prop->pos; cdGetPropsOnPlatform(prop, propnums, ARRAYCOUNT(propnums)); if (lift->dist == 0 && lift->speed == 0) { doorPlayOpeningSound(lift->soundtype, lift->base.prop); if (obj->flags & OBJFLAG_LIFT_TRIGGERDISABLE) { obj->flags &= ~OBJFLAG_LIFT_TRIGGERDISABLE; obj->flags |= OBJFLAG_DEACTIVATED; } } padGetCentre(lift->pads[lift->levelcur], &curcentre); padcur = &g_Pads[lift->pads[lift->levelcur]]; padaim = &g_Pads[lift->pads[lift->levelaim]]; xdiff = padaim->pos.f[0] - padcur->pos.f[0]; ydiff = padaim->pos.f[1] - padcur->pos.f[1]; zdiff = padaim->pos.f[2] - padcur->pos.f[2]; segdist = sqrtf(xdiff * xdiff + ydiff * ydiff + zdiff * zdiff); prevdist = lift->dist; applySpeed(&lift->dist, segdist, &lift->speed, lift->accel, lift->accel, lift->maxspeed); // If arriving at the destination, set the distance explicitly if (lift->speed < 1 && lift->speed > -1) { if (prevdist < segdist && lift->dist >= segdist) { lift->dist = segdist; } else if (prevdist > 0 && lift->dist <= 0) { lift->dist = 0; } } frac = segdist == 0 ? 0 : lift->dist / segdist; newpos.x = curcentre.f[0] + xdiff * frac; newpos.y = curcentre.f[1] + ydiff * frac; newpos.z = curcentre.f[2] + zdiff * frac; if (segdist == lift->dist) { lift->dist = 0; lift->speed = 0; lift->levelcur = lift->levelaim; doorPlayOpenedSound(lift->soundtype, lift->base.prop); if (obj->flags & OBJFLAG_LIFT_TRIGGERDISABLE) { obj->flags &= ~OBJFLAG_LIFT_TRIGGERDISABLE; obj->flags |= OBJFLAG_DEACTIVATED; } door = lift->doors[lift->levelcur]; if (door && door->keyflags == 0) { doorsRequestMode(door, DOORMODE_OPENING); } } func0f065e74(&prop->pos, prop->rooms, &newpos, newrooms); prop->pos = newpos; propDeregisterRooms(prop); roomsCopy(newrooms, prop->rooms); func0f069c70(obj, true, true); liftUpdateTiles(lift, lift->levelcur == lift->levelaim); platformDisplaceProps(prop, propnums, &prevpos, &prop->pos); } } else { // Lift is at the aim stop door = lift->doors[lift->levelcur]; if (!door || (doorIsClosed(door) && door->keyflags == 0)) { // Find next stop stop = lift->levelaim; do { stop = (stop + 1) % 4; } while (lift->pads[stop] < 0); liftGoToStop(lift, stop); } } } void escastepTick(struct prop *prop) { struct escalatorobj *step = (struct escalatorobj *)prop->obj; struct defaultobj *obj = prop->obj; struct coord newpos; struct escastepkeyframe *keyframes; s32 i; f32 mult; struct coord oldpos; s16 propnums[256]; bool resetting; resetting = false; step->frame += g_Vars.lvupdate60; keyframes = (obj->flags & OBJFLAG_ESCSTEP_ZALIGNED) ? g_EscaStepKeyframesZ : g_EscaStepKeyframesX; for (i = 0; true; i++) { if (keyframes[i + 1].frame == -1) { step->frame -= keyframes[i].frame; i = -1; resetting = true; } else if (step->frame < keyframes[i + 1].frame) { mult = (f32)(step->frame - keyframes[i].frame) / (f32)(keyframes[i + 1].frame - keyframes[i].frame); newpos.x = keyframes[i].pos.x + (keyframes[i + 1].pos.x - keyframes[i].pos.x) * mult; newpos.y = keyframes[i].pos.y + (keyframes[i + 1].pos.y - keyframes[i].pos.y) * mult; newpos.z = keyframes[i].pos.z + (keyframes[i + 1].pos.z - keyframes[i].pos.z) * mult; break; } else { // empty } } if (!resetting) { oldpos = prop->pos; cdGetPropsOnPlatform(prop, propnums, ARRAYCOUNT(propnums)); step->prevpos = prop->pos; } else { step->prevpos.x = newpos.f[0]; step->prevpos.y = newpos.f[1]; step->prevpos.z = newpos.f[2]; } prop->pos = newpos; if ((obj->flags & OBJFLAG_IGNOREFLOORCOLOUR) == 0) { cdFindFloorYColourTypeAtPos(&prop->pos, prop->rooms, &obj->floorcol, 0); } func0f069c70(obj, true, true); if (!resetting) { platformDisplaceProps(prop, propnums, &oldpos, &prop->pos); } } void cctvTick(struct prop *camprop) { struct cctvobj *camera = (struct cctvobj *)camprop->obj; struct defaultobj *obj = camprop->obj; f32 yaw; struct prop *playerprop; f32 xdist; f32 ydist; f32 zdist; bool canseeplayer = true; // If playing in coop mode, cycle between players in alternating frames if (g_Vars.coopplayernum >= 0) { if (g_Vars.lvframenum & 1) { playerprop = g_Vars.bond->prop; } else { playerprop = g_Vars.coop->prop; } } else { playerprop = g_Vars.bond->prop; } // Check distance xdist = playerprop->pos.x - camprop->pos.x; ydist = playerprop->pos.y - camprop->pos.y; zdist = playerprop->pos.z - camprop->pos.z; yaw = camera->toleft ? camera->yleft : camera->yright; if (camera->maxdist > 0) { if (xdist * xdist + ydist * ydist + zdist * zdist > camera->maxdist * camera->maxdist) { canseeplayer = false; } } if (g_Vars.bondvisible == false || (obj->flags & OBJFLAG_CAMERA_DISABLED) || (playerprop->chr->hidden & CHRHFLAG_CLOAKED)) { canseeplayer = false; } // Check horizontal angle if (canseeplayer) { f32 angle = atan2f(xdist, zdist); f32 yrot = camera->yrot; f32 finalangle; if (yrot < 0) { yrot += M_BADTAU; } else if (yrot >= M_BADTAU) { yrot -= M_BADTAU; } yrot += camera->yzero; if (yrot >= M_BADTAU) { yrot -= M_BADTAU; } finalangle = angle - yrot; if (angle < yrot) { finalangle += M_BADTAU; } finalangle -= M_BADPI; if (finalangle < 0) { finalangle += M_BADTAU; } if (finalangle > M_BADPI) { finalangle -= M_BADTAU; } if (finalangle > 0.7852731347084f || finalangle < -0.7852731347084f) { canseeplayer = false; } } // Check vertical angle if (canseeplayer) { f32 angle = atan2f(ydist, sqrtf(xdist * xdist + zdist * zdist)); f32 finalangle = angle - camera->xzero; if (angle < camera->xzero) { finalangle = angle - camera->xzero + M_BADTAU; } if (finalangle > M_BADTAU) { finalangle -= M_BADTAU; } if (finalangle > M_BADPI) { finalangle -= M_BADTAU; } if (finalangle); if (finalangle > 0.7852731347084f || finalangle < -0.7852731347084f) { canseeplayer = false; } } // Check line of sight if (canseeplayer) { playerSetPerimEnabled(playerprop, false); if (!cdTestLos05(&camprop->pos, camprop->rooms, &playerprop->pos, playerprop->rooms, CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_CHRS | CDTYPE_PATHBLOCKER | CDTYPE_BG | CDTYPE_AIOPAQUE, GEOFLAG_BLOCK_SIGHT)) { canseeplayer = false; } playerSetPerimEnabled(playerprop, true); } if (canseeplayer) { obj->flags |= OBJFLAG_CAMERA_BONDINVIEW; camera->seebondtime60 += g_Vars.lvupdate60; if (g_Vars.coopplayernum >= 0) { camera->seebondtime60 += g_Vars.lvupdate60; } if (camera->seebondtime60 >= (s32)(TICKS(300) * g_CctvWaitScale)) { alarmActivate(); camera->seebondtime60 = 0; } } else { obj->flags &= ~OBJFLAG_CAMERA_BONDINVIEW; } // Update yaw if (camera->yrot < yaw) { f32 tmp = camera->yspeed * camera->yspeed * 764.06536865234f; if (camera->yrot >= yaw - tmp) { camera->yspeed -= 0.00065439427271485f * g_Vars.lvupdate60freal; if (camera->yspeed < 0.00065439427271485f) { camera->yspeed = 0.00065439427271485f; } } else if (camera->yspeed < camera->ymaxspeed) { f32 newspeed = camera->yspeed + 0.00065439427271485f * g_Vars.lvupdate60freal; if (newspeed > camera->ymaxspeed) { newspeed = camera->ymaxspeed; } if (camera->yrot < yaw - newspeed * newspeed * 764.06536865234f) { camera->yspeed = newspeed; } } camera->yrot += camera->yspeed * g_Vars.lvupdate60freal; if (camera->yrot >= yaw) { camera->yrot = yaw; camera->toleft = false; camera->yspeed = 0; } } else { f32 tmp = camera->yspeed * camera->yspeed * 764.06536865234f; if (camera->yrot <= yaw + tmp) { camera->yspeed -= 0.00065439427271485f * g_Vars.lvupdate60freal; if (camera->yspeed < 0.00065439427271485f) { camera->yspeed = 0.00065439427271485f; } } else if (camera->yspeed < camera->ymaxspeed) { f32 newspeed = camera->yspeed + 0.00065439427271485f * g_Vars.lvupdate60freal; if (newspeed > camera->ymaxspeed) { newspeed = camera->ymaxspeed; } if (camera->yrot > yaw + newspeed * newspeed * 764.06536865234f) { camera->yspeed = newspeed; } } camera->yrot -= camera->yspeed * g_Vars.lvupdate60freal; if (camera->yrot <= yaw) { camera->yrot = yaw; camera->toleft = true; camera->yspeed = 0; } } } void cctvInitMatrices(struct prop *prop, Mtxf *mtx) { struct cctvobj *cctv = (struct cctvobj *)prop->obj; struct model *model = cctv->base.model; Mtxf *matrices = model->matrices; union modelrodata *rodata = modelGetPartRodata(model->filedata, MODELPART_CCTV_CASING); struct coord sp64; Mtxf sp24; f32 yrot = cctv->yrot; if (yrot < 0) { yrot += M_BADTAU; } else if (yrot >= M_BADTAU) { yrot -= M_BADTAU; } mtx4LoadYRotation(yrot, &sp24); mtx4MultMtx4(&sp24, &cctv->camrotm, &matrices[1]); sp64.x = rodata->position.pos.x; sp64.y = rodata->position.pos.y; sp64.z = rodata->position.pos.z; mtx4TransformVecInPlace(mtx, &sp64); mtx4SetTranslation(&sp64, &matrices[1]); mtx00015be0(camGetWorldToScreenMtxf(), &matrices[1]); } void fanTick(struct prop *prop) { struct defaultobj *obj = (struct defaultobj *)prop->obj; struct fanobj *fan = (struct fanobj *)prop->obj; // If activated, toggle the power state if (obj->hidden & (OBJHFLAG_ACTIVATED_BY_BOND | OBJHFLAG_ACTIVATED_BY_COOP)) { if (fan->on == true) { fan->on = false; obj->hidden &= ~(OBJHFLAG_ACTIVATED_BY_BOND | OBJHFLAG_ACTIVATED_BY_COOP); } else { fan->on = true; obj->hidden &= ~(OBJHFLAG_ACTIVATED_BY_BOND | OBJHFLAG_ACTIVATED_BY_COOP); } } // Adjust fan speed if (fan->on == true) { // Accelerating fan->yspeed += fan->yaccel * g_Vars.lvupdate60freal; if (fan->yspeed > fan->ymaxspeed) { fan->yspeed = fan->ymaxspeed; } } else if (fan->yspeed > 0) { // Decelerating s32 i; for (i = 0; i < g_Vars.lvupdate240; i++) { fan->yspeed -= fan->yaccel * 0.2f; } if (fan->yspeed <= fan->ymaxspeed * 0.01f) { fan->yspeed = 0; } } // Rotate if (fan->yspeed > 0) { fan->yrot += fan->yspeed * g_Vars.lvupdate60freal; while (fan->yrot >= 1.5705462694168f) { // almost BADDEG2RAD(90) fan->yrot -= 1.5705462694168f; } fan->yrotprev = fan->yrot; } } void fanUpdateModel(struct prop *prop) { struct fanobj *fan = (struct fanobj *) prop->obj; Mtxf sp6c; f32 sp48[3][3]; f32 sp24[3][3]; f32 angle = fan->yspeed * g_Vars.lvupdate60freal; while (angle >= M_BADTAU) { angle -= M_BADTAU; } mtx4LoadYRotation(angle, &sp6c); mtx4ToMtx3(&sp6c, sp48); mtx00016140(fan->base.realrot, sp48, sp24); mtx3Copy(sp24, fan->base.realrot); } void autogunTick(struct prop *prop) { struct autogunobj *autogun; struct defaultobj *obj; f32 goalyrot; f32 goalxrot; f32 f0; f32 maxspeed; f32 xdist; f32 ydist; f32 zdist; f32 sqdist; f32 dist; f32 horizdist; bool awake; bool spinup; bool insight; f32 limitangle; struct prop *target; s32 ownerplayernum; f32 f2; f32 f12; s32 numchrs; struct chrdata *chr; s32 i; f32 targetangleh; f32 targetanglev; f32 relangleh; bool track; autogun = (struct autogunobj *)prop->obj; obj = prop->obj; target = NULL; awake = false; spinup = false; insight = false; limitangle = 0.0f; // Malfunctioning mode 1: The gun looks around continuously in random // directions on both axis without spinning the barrel. if (obj->flags2 & OBJFLAG2_AUTOGUN_MALFUNCTIONING1) { if (obj->flags2 & OBJFLAG2_AUTOGUN_40000000) { autogun->xzero = autogun->xrot; autogun->yzero = autogun->yrot; } else if (autogun->yrot == autogun->yzero && autogun->xrot == autogun->xzero) { autogun->xzero = (RANDOMFRAC() * 39.0f + 1.0f) * 0.017450513f; autogun->yzero = RANDOMFRAC() * M_BADTAU; } applyRotation(&autogun->yrot, autogun->yzero, &autogun->yspeed, PALUPF(0.00001163367596746f), PALUPF(0.00001163367596746f), PALUPF(0.00069802056532353f)); applyRotation(&autogun->xrot, autogun->xzero, &autogun->xspeed, PALUPF(0.0000058168379837298f), PALUPF(0.0000058168379837298f), PALUPF(0.00034901028266177f)); return; } // Malfunctioning mode 2: The gun looks around left/right continuously // and spins the barrel based on its angle. if (obj->flags2 & OBJFLAG2_AUTOGUN_MALFUNCTIONING2) { spinup = true; if (obj->flags2 & OBJFLAG2_AUTOGUN_40000000) { autogun->xzero = autogun->xrot; autogun->yzero = autogun->yrot; } else if (autogun->yrot == autogun->yzero) { autogun->yzero = RANDOMFRAC() * M_BADTAU; } applyRotation(&autogun->yrot, autogun->yzero, &autogun->yspeed, PALUPF(0.00001163367596746f), PALUPF(0.00001163367596746f), PALUPF(0.00069802056532353f)); applyRotation(&autogun->xrot, autogun->xzero, &autogun->xspeed, PALUPF(0.0000058168379837298f), PALUPF(0.0000058168379837298f), PALUPF(0.00034901028266177f)); maxspeed = cosf(autogun->yrot); if (maxspeed > 0.0f) { maxspeed = 0.02512874f * maxspeed; } else { maxspeed = 0.000001f; } // spinup is always true here, so the first branch is unconditional and // the else branch is optimised out. The spinup check and else branch // must be here for a match, so it was likely copied from later in the // function. if (spinup) { autogun->barrelspeed += 0.009971722f * g_Vars.lvupdate60freal; if (autogun->barrelspeed > maxspeed) { autogun->barrelspeed = maxspeed; } } else if (autogun->barrelspeed > 0.0f) { for (i = 0; i < g_Vars.lvupdate60; i++) { autogun->barrelspeed *= 0.99f; } if (autogun->barrelspeed <= 0.0001f) { autogun->barrelspeed = 0.0f; } } if (autogun->barrelspeed > 0.0f) { autogun->barrelrot += autogun->barrelspeed * g_Vars.lvupdate60freal; while (autogun->barrelrot >= M_BADTAU) { autogun->barrelrot -= M_BADTAU; } } return; } // Regular behaviour if (autogun->ammoquantity == 0) { // Don't set target } else if (autogun->target) { target = autogun->target; } else { // Find new target if (frIsInTraining()) { // Laptop gun in firing range target = frChooseAutogunTarget(&prop->pos); if (1); } else if (autogun->targetteam != 0) { // Autogun (solo or MP) configured to attack specific teams if (g_Vars.normmplayerisrunning) { numchrs = g_MpNumChrs; } else { numchrs = chrsGetNumSlots(); } while (true) { autogun->nextchrtest++; if (autogun->nextchrtest >= numchrs) { autogun->nextchrtest = -1; break; } if (g_Vars.normmplayerisrunning) { ownerplayernum = (obj->hidden & 0xf0000000) >> 28; if (autogun->nextchrtest == ownerplayernum) { continue; } chr = g_MpAllChrPtrs[autogun->nextchrtest]; if (!chr->prop) { continue; } if (!chr->model) { continue; } if ((g_MpSetup.options & MPOPTION_TEAMSENABLED) && (chr->team & autogun->targetteam) == 0) { continue; } } else { chr = &g_ChrSlots[autogun->nextchrtest]; if (!chr->prop) { continue; } if ((chr->team & autogun->targetteam) == 0) { continue; } if ((!chr->model || (chr->prop->flags & PROPFLAG_ENABLED) == 0) && chr->prop->type != PROPTYPE_PLAYER) { continue; } } if ((chr->chrflags & CHRCFLAG_HIDDEN) == 0 && (chr->hidden & CHRHFLAG_CLOAKED) == 0 && !chrIsDead(chr)) { target = chr->prop; break; } if (1); } } else { // Not configured for teams, so target a player if (g_Vars.coopplayernum >= 0) { if (g_Vars.lvframenum & 1) { target = g_Vars.bond->prop; } else { target = g_Vars.coop->prop; } } else { target = g_Vars.bond->prop; } } } if (target) { if (target->chr == NULL) { target = NULL; } else if ((target->type & (PROPTYPE_CHR | PROPTYPE_PLAYER)) == 0 && !frIsInTraining()) { target = NULL; } } goalyrot = autogun->yzero; goalxrot = autogun->xzero; if (target) { xdist = target->pos.f[0] - prop->pos.f[0]; ydist = target->pos.f[1] - prop->pos.f[1]; zdist = target->pos.f[2] - prop->pos.f[2]; if (target->type == PROPTYPE_PLAYER) { ydist -= 20.0f; } if (ydist); sqdist = xdist * xdist + zdist * zdist; dist = sqrtf(sqdist); horizdist = dist; if (obj->flags & OBJFLAG_08000000) { sqdist += ydist * ydist; dist = sqrtf(sqdist); } limitangle = chrGetAimLimitAngle(sqdist); if (obj->flags && obj->flags); if (dist <= autogun->aimdist) { // Target is in range targetangleh = atan2f(xdist, zdist); targetanglev = atan2f(ydist, horizdist); if ((obj->flags & OBJFLAG_AUTOGUN_DAMAGED) || (obj->flags & OBJFLAG_AUTOGUN_SEENTARGET)) { awake = true; } else { f12 = targetangleh - autogun->yrot; if (f12 < 0.0f) { f12 += M_BADTAU; } if (f12 > M_BADPI) { f12 -= M_BADTAU; } f2 = targetanglev - autogun->xrot; if (f2 < 0.0f) { f2 += M_BADTAU; } if (f2 > M_BADPI) { f2 -= M_BADTAU; } if (f12 < 1.221536f && f12 > -1.221536f) { awake = true; } } if (awake) { relangleh = targetangleh - autogun->yzero; track = true; if (relangleh < -M_PI) { relangleh += M_BADTAU; } else if (relangleh >= M_PI) { relangleh -= M_BADTAU; } // Decide if target can be tracked if (target->type == PROPTYPE_PLAYER) { if (!g_Vars.bondvisible || g_Vars.players[playermgrGetPlayerNumByProp(target)]->isdead || (target->chr->chrflags & CHRCFLAG_HIDDEN) || (target->chr->hidden & CHRHFLAG_CLOAKED)) { track = false; } } else if (target->type == PROPTYPE_CHR) { struct chrdata *chr = target->chr; if (chr == NULL || (chr->chrflags & CHRCFLAG_HIDDEN) || (chr->hidden & CHRHFLAG_CLOAKED) || (chr->hidden & CHRHFLAG_ANTINONINTERACTABLE) || chrIsDead(chr) || chr->actiontype == ACT_DRUGGEDCOMINGUP || chr->actiontype == ACT_DRUGGEDDROP || chr->actiontype == ACT_DRUGGEDKO) { track = false; } } else if (target->type == PROPTYPE_OBJ) { struct defaultobj *obj = target->obj; if (obj && obj->modelnum == MODEL_TARGET) { if (!frIsTargetFacingPos(target, &prop->pos)) { track = false; } } else { track = false; } } propSetPerimEnabled(prop, false); propSetPerimEnabled(target, false); if (relangleh <= autogun->ymaxleft && relangleh >= autogun->ymaxright && track && cdTestLos05(&prop->pos, prop->rooms, &target->pos, target->rooms, CDTYPE_ALL, GEOFLAG_BLOCK_SIGHT)) { // Target is in sight obj->flags |= OBJFLAG_AUTOGUN_SEENTARGET; insight = true; goalxrot = targetanglev; goalyrot = targetangleh; if (autogun->target == NULL) { autogun->target = target; } } else if (autogun->lastseebond60 >= 0 && autogun->lastseebond60 > g_Vars.lvframe60 - TICKS(120)) { // Target recently lost goalyrot = autogun->yrot; goalxrot = autogun->xrot; } else { awake = false; } propSetPerimEnabled(prop, true); propSetPerimEnabled(target, true); } } } if (!awake) { autogun->target = NULL; } // The turret swivels left and right while firing if (autogun->firing) { goalyrot += limitangle * 0.8f * sinf((g_Vars.lvframe60 % TICKS(120)) * PALUPF(0.05235154f)); if (goalyrot < 0.0f) { goalyrot += M_BADTAU; } if (goalyrot >= M_BADTAU) { goalyrot -= M_BADTAU; } } f0 = goalyrot - autogun->yzero; if (f0 < -M_PI) { f0 += M_BADTAU; } else if (f0 >= M_PI) { f0 -= M_BADTAU; } if (f0 > autogun->ymaxleft) { goalyrot = autogun->yzero + autogun->ymaxleft; } else if (f0 < autogun->ymaxright) { goalyrot = autogun->yzero + autogun->ymaxright; } if (goalyrot < 0.0f) { goalyrot += M_BADTAU; } if (goalyrot >= M_BADTAU) { goalyrot -= M_BADTAU; } applyRotation(&autogun->yrot, goalyrot, &autogun->yspeed, PALUPF(0.00087252567755058f), PALUPF(0.00087252567755058f), autogun->maxspeed); applyRotation(&autogun->xrot, goalxrot, &autogun->xspeed, PALUPF(0.00087252567755058f), PALUPF(0.00087252567755058f), autogun->maxspeed); f12 = goalyrot - autogun->yrot; if (f12 < 0.0f) { f12 += M_BADTAU; } if (f12 > M_BADPI) { f12 -= M_BADTAU; } f2 = goalxrot - autogun->xrot; if (f2 < 0.0f) { f2 += M_BADTAU; } if (f2 > M_BADPI) { f2 -= M_BADTAU; } autogun->firing = false; if (awake) { if (f12 < limitangle && -limitangle < f12 && f2 < limitangle && -limitangle < f2) { autogun->firing = true; spinup = true; if (insight) { autogun->lastseebond60 = g_Vars.lvframe60; autogun->lastaimbond60 = g_Vars.lvframe60; } } else { f32 f0 = 2.0f * limitangle; u32 stack[2]; if (f12 < f0 && -f0 < f12 && f2 < f0 && -f0 < f2) { autogun->firing = true; spinup = true; if (insight) { autogun->lastseebond60 = g_Vars.lvframe60; } } else { if (autogun->lastseebond60 >= 0 && autogun->lastseebond60 > g_Vars.lvframe60 - TICKS(120)) { autogun->firing = true; spinup = true; } } } } // Increase or decrease the barrel speed and apply the rotation if (spinup) { autogun->barrelspeed += 0.009971722f * g_Vars.lvupdate60freal; if (autogun->barrelspeed > 0.5983033f) { autogun->barrelspeed = 0.5983033f; } } else if (autogun->barrelspeed > 0.0f) { for (i = 0; i < g_Vars.lvupdate60; i++) { autogun->barrelspeed *= 0.99f; } if (autogun->barrelspeed <= 0.0001f) { autogun->barrelspeed = 0.0f; } } if (autogun->barrelspeed > 0.0f) { autogun->barrelrot += autogun->barrelspeed * g_Vars.lvupdate60freal; while (autogun->barrelrot >= M_BADTAU) { autogun->barrelrot -= M_BADTAU; } } } void autogunInitMatrices(struct prop *prop, Mtxf *mtx) { struct autogunobj *autogun = (struct autogunobj *)prop->obj; struct model *model = autogun->base.model; Mtxf *matrices = model->matrices; union modelrodata *rodata; struct coord sp4c; f32 yrot = autogun->yrot + 1.5705462694168f; f32 xrot = -autogun->xrot; Mtxf *tmp; struct modelnode *node2; struct modelnode *node3; struct modelnode *node4; struct modelnode *node6; if (yrot >= M_BADTAU) { yrot -= M_BADTAU; } rodata = modelGetPartRodata(model->filedata, MODELPART_AUTOGUN_0001); sp4c = rodata->position.pos; mtx4TransformVecInPlace(mtx, &sp4c); mtx4LoadYRotation(yrot, &matrices[1]); mtx4SetTranslation(&sp4c, &matrices[1]); mtx00015f04(autogun->base.model->scale, &matrices[1]); mtx00015be0(camGetWorldToScreenMtxf(), &matrices[1]); node2 = modelGetPart(model->filedata, MODELPART_AUTOGUN_0002); rodata = node2->rodata; mtx4LoadZRotation(xrot, &matrices[2]); mtx4SetTranslation(&rodata->position.pos, &matrices[2]); mtx00015be0(&matrices[1], &matrices[2]); tmp = model0001a5cc(model, node2, 0x100); if (tmp != NULL) { mtx4LoadZRotation(xrot * 0.5f, tmp); mtx4SetTranslation(&rodata->position.pos, tmp); mtx00015be0(&matrices[1], tmp); } node3 = modelGetPart(model->filedata, MODELPART_AUTOGUN_0003); if (node3 != NULL) { tmp = model0001a5cc(model, node3, 0); rodata = node3->rodata; mtx4LoadXRotation(autogun->barrelrot, tmp); mtx4SetTranslation(&rodata->position.pos, tmp); mtx00015be0(&matrices[2], tmp); } node4 = modelGetPart(model->filedata, MODELPART_AUTOGUN_0004); if (node4 != NULL) { tmp = model0001a5cc(model, node4, 0); rodata = node4->rodata; mtx4LoadTranslation(&rodata->position.pos, tmp); mtx00015be0(&matrices[2], tmp); } node6 = modelGetPart(model->filedata, MODELPART_AUTOGUN_0006); if (node6 != NULL) { tmp = model0001a5cc(model, node6, 0); rodata = node6->rodata; mtx4LoadXRotation(autogun->barrelrot, tmp); mtx4SetTranslation(&rodata->position.pos, tmp); mtx00015be0(&matrices[2], tmp); } } void autogunTickShoot(struct prop *autogunprop) { if (!lvIsPaused()) { struct autogunobj *autogun = (struct autogunobj *) autogunprop->obj; struct defaultobj *obj = autogunprop->obj; bool fireleft = false; bool fireright = false; struct model *model = obj->model; struct modelnode *node1; struct modelnode *node2; struct prop *hitprop; bool friendly = false; if (autogun->firing && (obj->flags & OBJFLAG_DEACTIVATED) == 0) { autogun->firecount++; fireleft = (autogun->firecount % 2) == 0; if (modelGetPart(model->filedata, MODELPART_AUTOGUN_FLASHLEFT)) { fireright = (autogun->firecount % 2) == 1; } if (fireleft || fireright) { struct coord gunpos; s16 gunrooms[8]; struct coord dir; bool missed = false; struct coord hitpos; s16 hitrooms[8]; bool makebeam = (autogun->firecount % 4) == 0; struct prop *targetprop = autogun->target; struct modelnode *flashnode; struct modelnode *posnode = NULL; struct gset gset = { WEAPON_RCP45, 0, 0, FUNC_PRIMARY }; struct prop *ownerprop = NULL; struct chrdata *ownerchr = NULL; s32 ownerplayernum = (obj->hidden & 0xf0000000) >> 28; if (g_Vars.normmplayerisrunning) { // Multiplayer - it must be a laptop gun ownerchr = mpGetChrFromPlayerIndex(ownerplayernum); if (ownerchr) { ownerprop = ownerchr->prop; } } if ((autogun->firecount & 7) || (flashnode = modelGetPart(model->filedata, MODELPART_AUTOGUN_FLASHRIGHT)) == NULL) { flashnode = modelGetPart(model->filedata, MODELPART_AUTOGUN_FLASHLEFT); } if (flashnode == NULL) { posnode = modelGetPart(model->filedata, MODELPART_AUTOGUN_0003); } propSetPerimEnabled(autogunprop, false); if ((autogunprop->flags & PROPFLAG_ONTHISSCREENTHISTICK) && (flashnode || posnode)) { Mtxf *sp108; Mtxf spc8; union modelrodata *rodata; if (flashnode) { sp108 = model0001a5cc(model, flashnode, 0); rodata = flashnode->rodata; gunpos = rodata->chrgunfire.pos; } else { sp108 = model0001a5cc(model, posnode, 0); gunpos.x = 0.0f; gunpos.y = 0.0f; gunpos.z = 0.0f; } mtx00015be4(camGetProjectionMtxF(), sp108, &spc8); mtx4TransformVecInPlace(&spc8, &gunpos); if (cdTestLos10(&autogunprop->pos, autogunprop->rooms, &gunpos, gunrooms, CDTYPE_BG, GEOFLAG_BLOCK_SHOOT) == CDRESULT_COLLISION) { gunpos = autogunprop->pos; roomsCopy(autogunprop->rooms, gunrooms); } } else { gunpos = autogunprop->pos; roomsCopy(autogunprop->rooms, gunrooms); } dir.x = cosf(autogun->xrot) * sinf(autogun->yrot); dir.y = sinf(autogun->xrot); dir.z = cosf(autogun->xrot) * cosf(autogun->yrot); hitpos.x = gunpos.x + dir.x * 65536.0f; hitpos.y = gunpos.y + dir.y * 65536.0f; hitpos.z = gunpos.z + dir.z * 65536.0f; // If multiplayer, or targeting a bad guy in solo // (ie. autogun is a Defense autogun or a thrown laptop) if (g_Vars.normmplayerisrunning || (targetprop && (targetprop->type == PROPTYPE_CHR)) || (g_Vars.antiplayernum >= 0 && targetprop && targetprop == g_Vars.anti->prop)) { if (cdExamLos08(&gunpos, gunrooms, &hitpos, CDTYPE_ALL, GEOFLAG_BLOCK_SHOOT) == CDRESULT_COLLISION) { cdGetPos(&hitpos, 11458, "propobj.c"); hitprop = cdGetObstacleProp(); // SP: If the hit prop is a chr and it's our target // MP: If the hit prop is a chr if (hitprop && (hitprop->type & (PROPTYPE_CHR | PROPTYPE_PLAYER)) && (g_Vars.normmplayerisrunning || targetprop == hitprop)) { struct modelnode *hitnode = NULL; struct model *hitmodel = NULL; s32 hitside = -1; s32 hitpart = HITPART_GENERAL; f32 damage = gsetGetDamage(&gset); struct chrdata *hitchr = hitprop->chr; if (g_Vars.normmplayerisrunning) { damage *= 0.5f; } if (ownerprop == hitprop || (ownerchr && chrCompareTeams(hitprop->chr, ownerchr, COMPARE_FRIENDS))) { // A teammate entered the line of fire makebeam = false; fireleft = false; fireright = false; friendly = true; } if (fireleft || fireright) { bgunPlayPropHitSound(&gset, hitprop, -1); if (hitchr->model && chrGetShield(hitchr) > 0.0f) { chrCalculateShieldHit(hitchr, &hitpos, &dir, &hitnode, &hitpart, &hitmodel, &hitside); } chrEmitSparks(hitchr, hitprop, hitpart, &hitpos, &dir, ownerchr); func0f0341dc(hitchr, damage, &dir, &gset, ownerprop, HITPART_GENERAL, hitprop, hitnode, hitmodel, hitside, NULL); } } else { missed = true; } } } else if (targetprop && targetprop->type == PROPTYPE_OBJ) { // Laptop in firing range struct prop *hitprop = NULL; if (cdExamLos08(&gunpos, gunrooms, &hitpos, CDTYPE_ALL & ~CDTYPE_PLAYERS, GEOFLAG_BLOCK_SHOOT) == CDRESULT_COLLISION) { cdGetPos(&hitpos, 11513, "propobj.c"); hitprop = cdGetObstacleProp(); missed = true; } if (hitprop && hitprop->type == PROPTYPE_OBJ) { struct defaultobj *hitobj = hitprop->obj; if (hitobj->modelnum == MODEL_TARGET) { struct gset gset = { WEAPON_LAPTOPGUN, 0, 0, FUNC_SECONDARY }; missed = false; frCalculateHit(hitobj, &hitpos, 0); if (chrIsUsingPaintball(ownerchr)) { sparksCreate(hitprop->rooms[0], hitprop, &hitpos, 0, 0, SPARKTYPE_PAINT); } else { sparksCreate(hitprop->rooms[0], hitprop, &hitpos, 0, 0, SPARKTYPE_DEFAULT); } bgunPlayPropHitSound(&gset, hitprop, TEXTURE_00F2); } } } else { // Enemy autogun in solo if (cdExamLos08(&gunpos, gunrooms, &hitpos, CDTYPE_DOORS | CDTYPE_BG, GEOFLAG_BLOCK_SHOOT) == CDRESULT_COLLISION) { cdGetPos(&hitpos, 11539, "propobj.c"); missed = true; } if (g_Vars.lvframe60 == autogun->lastaimbond60 && targetprop && targetprop->type == PROPTYPE_PLAYER) { f32 x; f32 y; f32 z; f32 sqguntotargetdist; f32 sqguntohitdist; f32 damage; x = targetprop->pos.x - gunpos.x; y = targetprop->pos.y - gunpos.y; z = targetprop->pos.z - gunpos.z; sqguntotargetdist = x * x + y * y + z * z; x = hitpos.f[0] - gunpos.f[0]; y = hitpos.f[1] - gunpos.f[1]; z = hitpos.f[2] - gunpos.f[2]; sqguntohitdist = x * x + y * y + z * z; if (sqguntohitdist >= sqguntotargetdist) { f32 increment = 0.16f * g_Vars.lvupdate60freal * g_AutogunAccuracyScale; if (sqguntotargetdist > 40000.0f) { increment *= 200.0f / sqrtf(sqguntotargetdist); } autogun->shotbondsum += increment; if (autogun->shotbondsum >= 1.0f) { hitpos = targetprop->pos; missed = false; if (random() % 2) { hitpos.y += 2 + (random() % 10); } else { hitpos.y -= 2 + (random() % 10); } bgunPlayPropHitSound(&gset, targetprop, -1); damage = 0.5f * g_AutogunDamageTxScale; chrDamageByImpact(targetprop->chr, damage, &dir, &gset, 0, HITPART_GENERAL); autogun->shotbondsum = 0.0f; } } } } propSetPerimEnabled(autogunprop, true); if (fireleft || fireright) { if (autogun->ammoquantity > 0 && autogun->ammoquantity != 255) { autogun->ammoquantity--; } } if (missed) { portal00018148(&gunpos, &hitpos, gunrooms, hitrooms, NULL, 0); if (chrIsUsingPaintball(ownerchr)) { sparksCreate(hitrooms[0], NULL, &hitpos, 0, 0, SPARKTYPE_PAINT); } else { sparksCreate(hitrooms[0], NULL, &hitpos, 0, 0, autogun->base.modelnum == MODEL_CETROOFGUN ? SPARKTYPE_BGHIT_GREEN : SPARKTYPE_DEFAULT); } bgunPlayBgHitSound(&gset, &hitpos, -1, hitrooms); } if (makebeam) { f32 distance; struct beam *beam = autogun->beam; f32 sum; struct coord dist; dist.x = hitpos.x - gunpos.x; dist.y = hitpos.y - gunpos.y; dist.z = hitpos.z - gunpos.z; sum = dist.f[0] * dist.f[0] + dist.f[1] * dist.f[1] + dist.f[2] * dist.f[2]; distance = sqrtf(sum); if (distance > 0.0f) { beam->from = gunpos; beam->dir.x = dist.f[0]; beam->dir.y = dist.f[1]; beam->dir.z = dist.f[2]; beam->dir.x *= 1.0f / distance; beam->dir.y *= 1.0f / distance; beam->dir.z *= 1.0f / distance; if (distance > 10000.0f) { distance = 10000.0f; } beam->age = 0; beam->weaponnum = autogun->base.modelnum == MODEL_CETROOFGUN ? WEAPON_CALLISTO : WEAPON_RCP45; beam->maxdist = distance; if (distance < 500.0f) { distance = 500.0f; } if (beam->weaponnum == WEAPON_LASER) { // Unreachable - weaponnum was assigned above beam->speed = 0.25f * distance; beam->mindist = 0.6f * distance; if (beam->mindist > 3000.0f) { beam->mindist = 3000.0f; } beam->dist = (-0.1f - RANDOMFRAC() * 0.3f) * distance; } else { beam->speed = 0.2f * distance; beam->mindist = 0.2f * distance; if (beam->mindist > 3000.0f) { beam->mindist = 3000.0f; } beam->dist = (2.0f * RANDOMFRAC() - 1.0f) * beam->speed; } } } } if (autogun->allowsoundframe < g_Vars.lvframe60) { s32 soundgap = 2; func0f0926bc(autogunprop, 1, 0xffff); if (!friendly) { s32 soundnum = SFX_806F; if (autogun->base.modelnum == MODEL_CETROOFGUN) { soundnum = SFX_MENU_ERROR; } if (autogun->base.modelnum == MODEL_CHRAUTOGUN) { soundnum = SFX_8044; soundgap = 4; } propsnd0f0939f8(NULL, autogunprop, soundnum, -1, -1, 0, 0, 0, 0, -1.0f, 0, -1, -1.0f, -1.0f, -1.0f); } autogun->allowsoundframe = soundgap + g_Vars.lvframe60; } } node1 = modelGetPart(model->filedata, MODELPART_AUTOGUN_FLASHLEFT); if (node1) { union modelrwdata *rwdata = modelGetNodeRwData(model, node1); rwdata->chrgunfire.visible = fireleft; } node2 = modelGetPart(model->filedata, MODELPART_AUTOGUN_FLASHRIGHT); if (node2) { union modelrwdata *rwdata = modelGetNodeRwData(model, node2); rwdata->chrgunfire.visible = fireright; } } } u32 var80069cc0 = 0x00000000; void chopperInitMatrices(struct prop *prop) { struct chopperobj *chopper = (struct chopperobj *)prop->obj; struct model *model = chopper->base.model; Mtxf *matrices = model->matrices; union modelrodata *rodata; Mtxf spa8; Mtxf sp68; Mtxf sp28; rodata = modelGetPartRodata(model->filedata, MODELPART_CHOPPER_0001); mtx4LoadZRotation(M_BADTAU - chopper->gunrotx, &sp68); mtx4LoadYRotation(chopper->gunroty + 1.5707963705063f, &sp28); mtx00015be4(&sp28, &sp68, &spa8); mtx4SetTranslation(&rodata->position.pos, &spa8); mtx00015be4(matrices, &spa8, &matrices[1]); rodata = modelGetPartRodata(model->filedata, MODELPART_CHOPPER_0002); mtx4LoadXRotation(chopper->barrelrot, &spa8); mtx4SetTranslation(&rodata->position.pos, &spa8); mtx00015be4(&matrices[1], &spa8, &matrices[2]); } struct prop *chopperGetTargetProp(struct chopperobj *chopper) { if (chopper->target == -1) { return g_Vars.currentplayer->prop; } return g_Vars.props + chopper->target; } struct chopperobj *chopperFromHovercar(struct chopperobj *chopper) { if (chopper->base.type == OBJTYPE_CHOPPER) { return chopper; } return NULL; } bool chopperCheckTargetInFov(struct chopperobj *hovercar, u8 fov) { struct chopperobj *chopper = chopperFromHovercar(hovercar); u8 visible = false; if (chopper) { f32 roty = chopper->roty; struct prop *target = chopperGetTargetProp(chopper); struct prop *prop = chopper->base.prop; f32 angle = atan2f(prop->pos.x - target->pos.x, prop->pos.z - target->pos.z); f32 anglediff = angle - roty; if (angle < roty) { anglediff += M_BADTAU; } visible = false; // This logic looks wrong, but is actually correct. I think the fov is // actually the not viewable area and starts at the back of the chopper, // which makes sense because the chopper's windows go around the side. if (!(anglediff < fov * 0.024539785459638f && anglediff < M_PI) && !(anglediff > M_BADTAU - fov * 0.024539785459638f && anglediff > M_PI)) { visible = true; } chopper->targetvisible = visible; } return visible; } bool chopperCheckTargetInSight(struct chopperobj *obj) { struct chopperobj *chopper = chopperFromHovercar(obj); if (chopper) { bool visible = false; struct prop *target = chopperGetTargetProp(chopper); if (target->type != PROPTYPE_PLAYER || g_Vars.bondvisible) { visible = cdTestLos05(&target->pos, target->rooms, &chopper->base.prop->pos, chopper->base.prop->rooms, CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_PATHBLOCKER | CDTYPE_BG | CDTYPE_AIOPAQUE, GEOFLAG_BLOCK_SHOOT); } chopper->targetvisible = visible; return visible; } return false; } void chopperSetTarget(struct chopperobj *obj, u32 chrnum) { struct chopperobj *chopper = chopperFromHovercar(obj); if (chopper) { struct chrdata *chr = chrFindById(NULL, chrnum); if (chr && chr->prop) { chopper->target = chr->prop - g_Vars.props; } } } bool chopperAttack(struct chopperobj *obj) { struct chopperobj *chopper = chopperFromHovercar(obj); if (chopper) { chopper->attackmode = CHOPPERMODE_COMBAT; chopper->patroltimer60 = TICKS(240); return true; } return false; } bool chopperStop(struct chopperobj *obj) { struct chopperobj *chopper = chopperFromHovercar(obj); if (chopper) { chopper->attackmode = CHOPPERMODE_PATROL; chopper->patroltimer60 = TICKS(120); chopper->power = 0; return true; } return false; } bool chopperSetArmed(struct chopperobj *obj, bool armed) { struct chopperobj *chopper = chopperFromHovercar(obj); if (chopper) { chopper->weaponsarmed = armed; return true; } return false; } void chopperRestartTimer(struct chopperobj *obj) { struct chopperobj *chopper = chopperFromHovercar(obj); if (chopper) { chopper->timer60 = 0; } } s32 chopperGetTimer(struct chopperobj *obj) { struct chopperobj *chopper = chopperFromHovercar(obj); return chopper->timer60; } void chopperSetMaxDamage(struct chopperobj *chopper, u16 health) { chopper->base.maxdamage = health; } f32 func0f07b164(struct coord *pos1, struct coord *pos2, struct coord *pos3, struct coord *result) { struct coord sp34; struct coord sp28; f32 tmp; sp34.x = pos2->x - pos1->x; sp34.y = pos2->y - pos1->y; sp34.z = pos2->z - pos1->z; sp28.x = pos3->x - pos1->x; sp28.y = pos3->y - pos1->y; sp28.z = pos3->z - pos1->z; guNormalize(&sp34.x, &sp34.y, &sp34.z); tmp = sp28.f[0] * sp34.f[0] + sp28.f[1] * sp34.f[1] + sp28.f[2] * sp34.f[2]; result->x = sp34.x * tmp + pos1->x; result->y = sp34.y * tmp + pos1->y; result->z = sp34.z * tmp + pos1->z; return sp28.f[0] * sp28.f[0] + sp28.f[1] * sp28.f[1] + sp28.f[2] * sp28.f[2] - tmp * tmp; } void chopperFireRocket(struct chopperobj *chopper, bool side) { if (chopper->ontarget) { struct coord direction; struct coord pos; struct prop *targetprop = chopperGetTargetProp(chopper); struct prop *chopperprop = chopper->base.prop; u32 stack; Mtxf sp6c; Mtxf sp2c; pos.x = side ? -754 : 754; pos.y = -400; pos.z = -400; mtx3ToMtx4(chopper->base.realrot, &sp2c); mtx4LoadTranslation(&pos, &sp6c); mtx4MultMtx4InPlace(&sp2c, &sp6c); pos.x = sp6c.m[3][0] + chopperprop->pos.f[0]; pos.y = sp6c.m[3][1] + chopperprop->pos.f[1]; pos.z = sp6c.m[3][2] + chopperprop->pos.f[2]; direction.x = targetprop->pos.x - pos.x; direction.y = targetprop->pos.y - pos.y + (s32)(random() % 100); direction.z = targetprop->pos.z - pos.z; guNormalize(&direction.x, &direction.y, &direction.z); smokeCreateSimple(&pos, chopperprop->rooms, SMOKETYPE_3); projectileCreate(chopperprop, 0, &pos, &direction, WEAPON_ROCKETLAUNCHER, targetprop); } } void chopperIncrementBarrel(struct prop *chopperprop, bool firing) { struct defaultobj *obj = chopperprop->obj; struct chopperobj *chopper = (struct chopperobj *)chopperprop->obj; struct model *model = obj->model; f32 angleh = 0.0f; f32 anglev = 0.0f; f32 speedmult = 1.0f; struct coord rot; struct coord gunpos; f32 gunroty = chopper->gunroty; f32 gunrotx = chopper->gunrotx; f32 gunturnyspeed60 = chopper->gunturnyspeed60; f32 gunturnxspeed60 = chopper->gunturnxspeed60; struct prop *targetprop = chopperGetTargetProp(chopper); struct modelnode *node; struct modelrwdata_chrgunfire *rwdata = NULL; struct modelrodata_position *rodata; Mtxf sp90; Mtxf sp50; f32 gunaimy; if (chopper->fireslotthing->unk00); chopper->fireslotthing->unk01 = (chopper->fireslotthing->unk00 % 3) == 0; if (firing) { rodata = modelGetPartRodata(model->filedata, MODELPART_CHOPPER_0001); gunaimy = targetprop->pos.y - 20.0f; gunpos.x = random() * random() * 0 * 30.0f + rodata->pos.x; gunpos.y = random() * 0 * 30.0f + (rodata->pos.y - 50.0f); gunpos.z = rodata->pos.z + 250.0f; if (obj->modelnum == MODEL_A51INTERCEPTOR) { f32 scale = 0.1f / obj->model->scale; gunpos.x *= scale; gunpos.y *= scale; gunpos.z *= scale; } mtx3ToMtx4(obj->realrot, &sp50); mtx4LoadTranslation(&gunpos, &sp90); mtx4MultMtx4InPlace(&sp50, &sp90); gunpos.x = sp90.m[3][0] + chopperprop->pos.f[0]; gunpos.y = sp90.m[3][1] + chopperprop->pos.f[1]; gunpos.z = sp90.m[3][2] + chopperprop->pos.f[2]; angleh = atan2f(targetprop->pos.x - gunpos.x, targetprop->pos.z - gunpos.z); if (angleh <= 0.0f) { angleh += M_BADTAU; } if (angleh > M_BADTAU) { angleh -= M_BADTAU; } angleh -= chopper->roty; if (angleh > M_PI) { angleh -= M_BADTAU; } if (angleh < -M_PI) { angleh += M_BADTAU; } anglev = atan2f(gunaimy - gunpos.y, sqrtf((targetprop->pos.x - gunpos.x) * (targetprop->pos.x - gunpos.x) + (targetprop->pos.z - gunpos.z) * (targetprop->pos.z - gunpos.z))) - chopper->rotx + M_BADTAU; if (anglev > M_PI) { anglev -= M_BADTAU; } if (chopper->barrelrotspeed < 0.34906584f) { chopper->barrelrotspeed += 0.017453292f * LVUPDATE60FREAL(); } else { chopper->barrelrotspeed = 0.34906584f; } } else { speedmult = 0.125f; if (chopper->barrelrotspeed > 0.0f) { chopper->barrelrotspeed -= 0.017453292f; } else { chopper->barrelrotspeed = 0.0f; } } chopper->barrelrot += chopper->barrelrotspeed * LVUPDATE60FREAL(); #if PAL applySpeed(&gunroty, angleh, &gunturnyspeed60, 0.0027920822612941f * speedmult, 0.0055841645225883f * speedmult, 0.16752494871616f * speedmult); if (gunroty == angleh && gunturnyspeed60 <= 0.0055841645225883f * speedmult && -0.0055841645225883f * speedmult <= gunturnyspeed60) { gunturnyspeed60 = 0.0f; } applySpeed(&gunrotx, anglev, &gunturnxspeed60, 0.0027920822612941f * speedmult, 0.0055841645225883f * speedmult, 0.16752494871616f * speedmult); if (gunrotx == anglev && gunturnxspeed60 <= 0.0055841645225883f * speedmult && -0.0055841645225883f * speedmult <= gunturnxspeed60) { gunturnxspeed60 = 0.0f; } #else applySpeed(&gunroty, angleh, &gunturnyspeed60, 0.0023267353f * speedmult, 0.0046534706f * speedmult, 0.1396041f * speedmult); if (gunroty == angleh && gunturnyspeed60 <= 0.0046534706f * speedmult && -0.0046534706f * speedmult <= gunturnyspeed60) { gunturnyspeed60 = 0.0f; } applySpeed(&gunrotx, anglev, &gunturnxspeed60, 0.0023267353f * speedmult, 0.0046534706f * speedmult, 0.1396041f * speedmult); if (gunrotx == anglev && gunturnxspeed60 <= 0.0046534706f * speedmult && -0.0046534706f * speedmult <= gunturnxspeed60) { gunturnxspeed60 = 0.0f; } #endif chopper->gunroty = gunroty; chopper->gunrotx = gunrotx; chopper->gunturnyspeed60 = gunturnyspeed60; chopper->gunturnxspeed60 = gunturnxspeed60; if (!(chopper->fireslotthing->unk00 % 2)) { firing = false; } node = modelGetPart(model->filedata, MODELPART_CHOPPER_GUNFLASH); if (node) { rwdata = modelGetNodeRwData(model, node); } if (firing) { f32 totalrotx = chopper->gunrotx + chopper->rotx; f32 totalroty = chopper->gunroty + chopper->roty; rot.x = sinf(totalroty) * cosf(totalrotx); rot.y = sinf(totalrotx); rot.z = cosf(totalroty) * cosf(totalrotx); projectileCreate(chopperprop, chopper->fireslotthing, &gunpos, &rot, WEAPON_CHOPPERGUN, targetprop); if (rwdata != NULL) { rwdata->visible = true; } } else { if (rwdata != NULL) { rwdata->visible = false; } } chopper->fireslotthing->unk00++; } void chopperIncrementMovement(struct prop *prop, f32 goalroty, f32 goalrotx, struct coord *dir, bool firing) { struct defaultobj *obj = prop->obj; struct chopperobj *chopper = (struct chopperobj *)obj; s16 newrooms[8]; struct coord newpos; f32 curroty; f32 currotx; f32 currotz; f32 turnyspeed; f32 turnxspeed; f32 rotmtx3[3][3]; f32 tmp; f32 f2; s32 i; f32 speed; s32 soundnum; struct coord spfc; Mtxf spbc; Mtxf sp7c; Mtxf sp3c; f32 angle; curroty = chopper->roty; currotx = chopper->rotx; currotz = chopper->rotz; turnyspeed = chopper->turnyspeed60; turnxspeed = chopper->turnxspeed60; chopper->otx = dir->x; chopper->oty = dir->y; chopper->otz = dir->z; dir->x *= chopper->power; dir->y *= chopper->power; dir->z *= chopper->power; if (chopper->power < 3.7f) { chopper->power += 0.030833334f * g_Vars.lvupdate60freal; } chopper->bob += 0.052359f; if (chopper->bob > M_BADTAU) { chopper->bob = 0.0f; chopper->bobstrength = ((random() % 8) + 2) * 0.01f; if (chopper->base.flags & OBJFLAG_CHOPPER_INACTIVE) { chopper->bobstrength *= 0.15f; } } dir->y += chopper->bobstrength * sinf(chopper->bob); f2 = PAL ? 0.976f : 0.98f; for (i = 1; i < g_Vars.lvupdate60; i++) { f2 *= PAL ? 0.976f : 0.98f; } chopper->vx += dir->x; chopper->vy += dir->y; chopper->vz += dir->z; chopper->vx *= f2; chopper->vy *= f2; chopper->vz *= f2; if (chopper->attackmode != CHOPPERMODE_FALL) { if (chopper->attackmode != CHOPPERMODE_PATROL || chopper->patroltimer60 > 0) { speed = 5.0f; } else { speed = 8.0f; } tmp = chopper->vx * chopper->vx + chopper->vy * chopper->vy + chopper->vz * chopper->vz; if (tmp > speed * speed) { guNormalize(&chopper->vx, &chopper->vy, &chopper->vz); chopper->vx *= speed; chopper->vy *= speed; chopper->vz *= speed; } } if (goalrotx > M_PI && goalrotx < 5.8f) { goalrotx = 5.8f; } #if PAL applyRotation(&curroty, goalroty, &turnyspeed, 0.00026175772654824f, 0.00052351545309648f, 0.015705462545156f); if (curroty == goalroty && turnyspeed <= 0.00052351545309648f && turnyspeed >= -0.00052351545309648f) { turnyspeed = 0.0f; } applyRotation(&currotx, goalrotx, &turnxspeed, 0.00026175772654824, 0.00052351545309648f, 0.015705462545156f); if (currotx == goalrotx && turnxspeed <= 0.00052351545309648f && turnxspeed >= -0.00052351545309648f) { turnxspeed = 0.0f; } #else applyRotation(&curroty, goalroty, &turnyspeed, 0.00021813141938765f, 0.00043626284f, 0.013087885f); if (curroty == goalroty && turnyspeed <= 0.00043626284f && turnyspeed >= -0.00043626284f) { turnyspeed = 0.0f; } applyRotation(&currotx, goalrotx, &turnxspeed, 0.00021813141938765f, 0.00043626284f, 0.013087885f); if (currotx == goalrotx && turnxspeed <= 0.00043626284f && turnxspeed >= -0.00043626284f) { turnxspeed = 0.0f; } #endif currotz += (-turnyspeed * 40.0f - currotz) * 0.1f; spfc.x = M_BADTAU - currotx; spfc.y = curroty; spfc.z = 0.0f; if (currotz >= 0) { mtx4LoadZRotation(currotz, &sp3c); } else { mtx4LoadZRotation(currotz + M_BADTAU, &sp3c); } mtx4LoadRotation(&spfc, &sp7c); mtx00015f04(chopper->base.model->scale, &sp7c); mtx4MultMtx4(&sp7c, &sp3c, &spbc); mtx4ToMtx3(&spbc, rotmtx3); mtx3Copy(rotmtx3, chopper->base.realrot); if (chopper->power > 0.45f && !firing && (chopper->base.flags2 & OBJFLAG2_INVISIBLE) == 0 && objIsHealthy(&chopper->base)) { soundnum = g_Vars.stagenum == STAGE_EXTRACTION ? SFX_810D : SFX_8110; propsnd0f09294c(prop, soundnum, 6); } else { func0f0926bc(prop, 6, 0xffff); } chopper->roty = curroty; chopper->rotx = currotx; chopper->rotz = currotz; chopper->turnyspeed60 = turnyspeed; chopper->turnxspeed60 = turnxspeed; if (goalroty < curroty) { angle = curroty - goalroty; } else { angle = -(curroty - goalroty); } chopper->ontarget = angle < 0.1745f; newpos.x = prop->pos.x + chopper->vx * g_Vars.lvupdate60freal; newpos.y = prop->pos.y + chopper->vy * g_Vars.lvupdate60freal; newpos.z = prop->pos.z + chopper->vz * g_Vars.lvupdate60freal; func0f065e74(&prop->pos, prop->rooms, &newpos, newrooms); prop->pos = newpos; propDeregisterRooms(prop); roomsCopy(newrooms, prop->rooms); func0f069c70(&chopper->base, false, true); chopperIncrementBarrel(prop, firing); if ((chopper->base.flags2 & OBJFLAG2_INVISIBLE) == 0 && objIsHealthy(&chopper->base)) { soundnum = g_Vars.stagenum == STAGE_EXTRACTION ? SFX_SHIP_HUM : SFX_810F; propsnd0f09294c(prop, soundnum, 5); } else { func0f0926bc(prop, 5, 0xffff); } } #define NEXTSTEP() (chopper->cw ? i : (i + 1) % chopper->path->len) #define PREVSTEP() (chopper->cw ? (i + 1) % chopper->path->len : i) void chopperTickFall(struct prop *chopperprop) { struct defaultobj *obj = chopperprop->obj; struct chopperobj *chopper = (struct chopperobj *) obj; f32 goalroty; f32 *x = &chopper->fall.x; f32 *y = &chopper->fall.y; f32 *z = &chopper->fall.z; struct coord speed; s32 i; struct pad *prevpad; struct pad *nextpad; f32 xdiff; f32 zdiff; f32 tmp; struct coord newpos; struct coord newspeed; // The timer is set explicitly to 2 when the chopper is destroyed if (chopper->timer60 == 2) { // Figure out what direction to fall in xdiff = 0; zdiff = 0; chopper->timer60 -= g_Vars.lvupdate60; if (chopper->path) { for (i = 0; i < chopper->path->len; i++) { if (NEXTSTEP() == chopper->nextstep) { break; } } prevpad = &g_Pads[chopper->path->pads[PREVSTEP()]]; nextpad = &g_Pads[chopper->path->pads[NEXTSTEP()]]; xdiff = prevpad->pos.f[0] - nextpad->pos.f[0]; zdiff = prevpad->pos.f[2] - nextpad->pos.f[2]; tmp = 0.25f / sqrtf(xdiff * xdiff + zdiff * zdiff); xdiff *= tmp; zdiff *= tmp; } *x = -zdiff; *y = 0; *z = xdiff; } else if (chopper->timer60 >= 0) { // Haven't started falling yet chopper->timer60 -= g_Vars.lvupdate60; } else { if (*y > -0.7f) { // Increase fall speed *y -= 0.009f * g_Vars.lvupdate60f; } } speed.x = *x; speed.y = *y; speed.z = *z; goalroty = chopper->roty + *x + *z; if (stageGetIndex(g_Vars.stagenum) == STAGEINDEX_EXTRACTION) { // The Extraction chopper falls without any collision checks and is // reaped once it reaches the lower barrier if (chopperprop->pos.y < -30000) { func0f0926bc(chopperprop, 1, 0xffff); obj->hidden |= OBJHFLAG_REAPABLE; } else { chopperIncrementMovement(chopperprop, goalroty, chopper->rotx < 0 ? M_PI : -M_PI, &speed, false); } } else { // Area 51 interceptors do collision checks // and explode once they hit the ground. f32 mult = 1.0f - PALUPF(0.02f); f32 bob; s32 i; for (i = 1; i < g_Vars.lvupdate60; i++) { mult *= 1.0f - PALUPF(0.02f); } bob = chopper->bob + 0.052358999848366f; if (bob > M_BADTAU) { bob = 0; chopper->bobstrength = (random() % 8 + 2) * 0.01f; if (obj->flags & OBJFLAG_80000000) { chopper->bobstrength *= 0.15f; } } speed.y += chopper->bobstrength * sinf(bob); newspeed.x = mult * (speed.f[0] * chopper->power + chopper->vx); newspeed.y = mult * (speed.f[1] * chopper->power + chopper->vy); newspeed.z = mult * (speed.f[2] * chopper->power + chopper->vz); newpos.x = chopperprop->pos.x + newspeed.f[0] * g_Vars.lvupdate60freal; newpos.y = chopperprop->pos.y + newspeed.f[1] * g_Vars.lvupdate60freal; newpos.z = chopperprop->pos.z + newspeed.f[2] * g_Vars.lvupdate60freal; if (cdExamLos09(&chopperprop->pos, chopperprop->rooms, &newpos, CDTYPE_BG) == CDRESULT_COLLISION) { struct coord sp74; s16 room; struct coord sp64; f32 ground; s16 newrooms[8]; chopperprop->pos.y += 100; ground = cdFindGroundAtCyl(&chopperprop->pos, 5, chopperprop->rooms, NULL, NULL); chopperprop->pos.y -= 100; cdGetPos(&sp64, 12449, "propobj.c"); newpos.x = sp64.x; newpos.y = ground + 20; newpos.z = sp64.z; func0f065e74(&chopperprop->pos, chopperprop->rooms, &newpos, newrooms); chopperprop->pos = newpos; propDeregisterRooms(chopperprop); roomsCopy(newrooms, chopperprop->rooms); func0f069c70(obj, false, true); // Move to CHOPPERMODE_DEAD chopper->attackmode++; objDeform(&chopper->base, 8); room = chopperprop->rooms[0]; sp74.x = 0; sp74.y = 1; sp74.z = 0; func0f0926bc(chopperprop, 1, 0xffff); explosionCreate(NULL, &chopperprop->pos, chopperprop->rooms, EXPLOSIONTYPE_ROCKET, 0, true, &newpos, room, &sp74); chopper->dead = true; } else { smokeCreateSimple(&chopperprop->pos, chopperprop->rooms, SMOKETYPE_3); chopperIncrementMovement(chopperprop, goalroty, chopper->rotx < 0 ? M_PI : -M_PI, &speed, false); } } } void chopperTickIdle(struct prop *prop) { struct chopperobj *chopper = (struct chopperobj *)prop->obj; u32 stack; f32 roty = chopper->roty; f32 rotx = chopper->rotx; struct coord coord; chraiExecute(chopper, PROPTYPE_OBJ); chopper->timer60 += g_Vars.lvupdate60; coord.x = 0; coord.y = 0; coord.z = 0; chopperIncrementMovement(prop, roty, rotx, &coord, false); } void chopperTickPatrol(struct prop *chopperprop) { struct chopperobj *chopper = (struct chopperobj *)chopperprop->obj; f32 xdiff; f32 roty = chopper->roty; f32 rotx = chopper->rotx; struct coord vector; struct coord padpos; f32 mult; f32 zdiff; chraiExecute(chopper, PROPTYPE_OBJ); chopper->timer60 += g_Vars.lvupdate60; if (chopper->patroltimer60 > 0) { chopper->patroltimer60 -= g_Vars.lvupdate60; } if (chopper->path) { padpos = g_Pads[chopper->path->pads[chopper->nextstep]].pos; padpos.y += -250; if (posIsArrivingLaterallyAtPos(&chopperprop->pos, &chopperprop->pos, &padpos, 350)) { chopper->nextstep = ((chopper->cw ? -1 : 1) + chopper->nextstep + chopper->path->len) % chopper->path->len; padpos = g_Pads[chopper->path->pads[chopper->nextstep]].pos; padpos.y += -250; } roty = atan2f(padpos.x - chopperprop->pos.x, padpos.z - chopperprop->pos.z); xdiff = padpos.x - chopperprop->pos.x; zdiff = padpos.z - chopperprop->pos.z; rotx = atan2f(padpos.y - chopperprop->pos.y, sqrtf(xdiff * xdiff + zdiff * zdiff)); } if (chopper->base.flags & OBJFLAG_20000000) { chopper->roty = roty; chopper->rotx = rotx; if (1); chopper->base.flags &= ~OBJFLAG_20000000; } if (chopper->patroltimer60 > 0) { vector.x = padpos.x - chopperprop->pos.x; vector.y = padpos.y - chopperprop->pos.y; vector.z = padpos.z - chopperprop->pos.z; guNormalize(&vector.x, &vector.y, &vector.z); } else { mult = cosf(chopper->rotx); vector.x = sinf(chopper->roty) * mult; vector.y = sinf(chopper->rotx); vector.z = cosf(chopper->roty) * mult; } chopperIncrementMovement(chopperprop, roty, rotx, &vector, false); } /** * Tick the chopper when it's in combat mode (ie. knows where the player is). * * The actual firing logic is in the chopper's AI scripting. Same with choosing * to return to patrol when the player is out of sight for a few seconds. * * This function is only directly responsible for the chopper's movement during * combat. */ void chopperTickCombat(struct prop *chopperprop) { struct defaultobj *obj = chopperprop->obj; struct chopperobj *chopper = (struct chopperobj *)obj; s32 i; f32 f0; struct prop *targetprop = chopperGetTargetProp(chopper); f32 dist; struct coord goalpos; struct coord dir; struct coord padpos; struct coord nextpadpos; f32 f20; s32 sp90; s32 sp8c; bool reverse; s8 numsteps; s8 tmp; struct coord sp78; struct coord sp6c; dist = coordGetSquaredDistanceToCoord(&targetprop->pos, &chopperprop->pos); chraiExecute(chopper, PROPTYPE_OBJ); chopper->timer60 += g_Vars.lvupdate60; if ((chopper->targetvisible && dist < 2000000.0f) || chopper->path == NULL) { // Stay put goalpos = chopperprop->pos; } else { f20 = 2.6843546e8f; for (i = 0; i < chopper->path->len; i++) { padpos = g_Pads[chopper->path->pads[i]].pos; nextpadpos = g_Pads[chopper->path->pads[(i + 1) % chopper->path->len]].pos; padpos.y += -250.0f; nextpadpos.y += -250.0f; f0 = func0f07b164(&padpos, &nextpadpos, &targetprop->pos, &sp78); if (f0 < f20) { f20 = f0; sp90 = i; goalpos = sp78; } if ((chopper->cw ? i : (i + 1) % chopper->path->len) == chopper->nextstep) { sp8c = i; sp6c = sp78; } } if (sp8c != sp90) { if (dist > 400000.0f) { reverse = false; numsteps = sp8c - sp90; if (numsteps < 0) { numsteps *= -1; reverse = true; } tmp = chopper->path->len - numsteps; chopper->cw = numsteps < tmp; if (reverse) { chopper->cw = !chopper->cw; } chopper->nextstep = chopper->cw ? sp8c : (sp8c + 1) % chopper->path->len; padpos = g_Pads[chopper->path->pads[chopper->nextstep]].pos; padpos.y += -250.0f; if (coordGetSquaredDistanceToCoord(&padpos, &chopperprop->pos) < 10000.0f) { chopper->power = 0.0f; chopper->nextstep = (chopper->nextstep + (chopper->cw ? -1 : 1) + chopper->path->len) % chopper->path->len; padpos = g_Pads[chopper->path->pads[chopper->nextstep]].pos; padpos.y += -250.0f; } goalpos = padpos; } else { goalpos = sp6c; } } else if (cdTestLos03(&targetprop->pos, targetprop->rooms, &goalpos, CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_PATHBLOCKER | CDTYPE_BG | CDTYPE_AIOPAQUE, GEOFLAG_BLOCK_SHOOT) == 0) { padpos = g_Pads[chopper->path->pads[chopper->cw ? (sp8c + 1) % chopper->path->len : sp8c]].pos; padpos.y += -250.0f; sp78.x = padpos.x - goalpos.x; sp78.y = padpos.y - goalpos.y; sp78.z = padpos.z - goalpos.z; guNormalize(&sp78.x, &sp78.y, &sp78.z); goalpos.x += sp78.x * 400.0f; goalpos.y += sp78.y * 400.0f; goalpos.z += sp78.z * 400.0f; } } if (coordGetSquaredDistanceToCoord(&goalpos, &chopperprop->pos) < 2500.0f) { // Close to the goal pos - power off chopper->power = 0.0f; dir.x = 0.0f; dir.y = 0.0f; dir.z = 0.0f; } else { // Check if chopper has overshot the goal pos? dir.x = goalpos.x - chopperprop->pos.x; dir.y = goalpos.y - chopperprop->pos.y; dir.z = goalpos.z - chopperprop->pos.z; guNormalize(&dir.x, &dir.y, &dir.z); if (dir.f[0] * chopper->otx + dir.f[1] * chopper->oty + dir.f[2] * chopper->otz < 0.0f) { chopper->power = 0.0f; } } { f32 roty; f32 rotx; roty = atan2f(targetprop->pos.x - chopperprop->pos.x, targetprop->pos.z - chopperprop->pos.z); dist = sqrtf((targetprop->pos.x - chopperprop->pos.x) * (targetprop->pos.x - chopperprop->pos.x) + (targetprop->pos.z - chopperprop->pos.z) * (targetprop->pos.z - chopperprop->pos.z)); rotx = atan2f(targetprop->pos.y - chopperprop->pos.y, dist); chopperIncrementMovement(chopperprop, roty, rotx, &dir, chopper->targetvisible && chopper->weaponsarmed); } } #define HOVVALUE1() ((active ? 15.0f : 5.0f) * PALUPF(0.00021813141938765f)) #define HOVVALUE2() ((active ? 15.0f : 5.0f) * PALUPF(0.013087885454297f)) void hovercarTick(struct prop *prop) { bool stopping; struct pad *pad; struct coord sp214; s16 sp210[2]; struct hovercarobj *hovercar = (struct hovercarobj *) prop->obj; struct defaultobj *obj = &hovercar->base; u32 stack; f32 sp200 = hovercar->roty; f32 sp1fc = hovercar->rotx; u32 active = obj->flags & OBJFLAG_80000000; f32 sp1f4 = active ? 5 : 10; struct prop *doorprop; struct coord sp1e4; struct coord sp1d8; s16 sp1d6; f32 x; f32 z; struct coord sp1c0; s16 sp1b0[8]; struct modelrodata_bbox *bbox; f32 ymin; s32 *padnum; struct doorobj *door; struct coord sp194; f32 sp190; f32 sp18c; f32 sp188; f32 sp184; f32 sp180; f32 sp15c[3][3]; struct coord sp150; s16 sp140[8]; f32 tmp2; f32 sp138; struct coord sp12c; Mtxf spec; Mtxf spac; Mtxf sp6c; doorprop = NULL; if (active && hovercar->deadtimer60 < 0) { // Exploding bbox = modelFindBboxRodata(hovercar->base.model); ymin = objGetLocalYMin(bbox); sp1d6 = prop->rooms[0]; sp1d8.x = hovercar->base.realrot[1][0]; sp1d8.y = hovercar->base.realrot[1][1]; sp1d8.z = hovercar->base.realrot[1][2]; sp1e4.x = hovercar->base.realrot[1][0] * ymin + prop->pos.x; sp1e4.y = hovercar->base.realrot[1][1] * ymin + prop->pos.y; sp1e4.z = hovercar->base.realrot[1][2] * ymin + prop->pos.z; func0f0926bc(prop, 1, 0xffff); explosionCreate(NULL, &prop->pos, prop->rooms, EXPLOSIONTYPE_7, g_Vars.currentplayernum, true, &sp1e4, sp1d6, &sp1d8); hovercar->base.hidden |= OBJHFLAG_REAPABLE; return; } if (hovercar->dead) { if (active) { hovercar->deadtimer60 -= g_Vars.lvupdate60; hovercar->sparkstimer60 -= g_Vars.lvupdate60; if (hovercar->sparkstimer60 < 0) { hovercar->sparkstimer60 = TICKS(50); propsnd0f0939f8(NULL, prop, SFX_SHIELD_DAMAGE, -1, -1, 1024, 0, 0, 0, -1, 0, -1, -1, -1, -1); sparksCreate(prop->rooms[0], prop, &prop->pos, NULL, 0, SPARKTYPE_DEFAULT); } } else { if (hovercar->speedtime60 == 0) { hovercar->speed += 6; sp1c0.x = prop->pos.x; sp1c0.y = prop->pos.y - hovercar->speed; sp1c0.z = prop->pos.z; if (sp1c0.y < hovercar->speedaim) { sp1c0.y = hovercar->speedaim; hovercar->speedtime60 = 1; } func0f065e74(&prop->pos, prop->rooms, &sp1c0, sp1b0); prop->pos = sp1c0; propDeregisterRooms(prop); roomsCopy(sp1b0, prop->rooms); func0f069c70(&hovercar->base, false, true); } } return; } chraiExecute(obj, PROPTYPE_OBJ); stopping = false; if (hovercar->path) { padnum = &hovercar->path->pads[hovercar->nextstep]; pad = &g_Pads[*padnum]; sp214.x = pad->pos.x; if (active) { sp210[0] = pad->room; sp210[1] = -1; sp214.y = cdFindGroundAtCyl(&pad->pos, 5, sp210, NULL, NULL) + 35; } else { sp214.y = pad->pos.y; } sp214.z = pad->pos.z; if ((hovercar->base.flags & OBJFLAG_20000000) && posIsArrivingLaterallyAtPos(&prop->pos, &prop->pos, &sp214, (0, sp1f4))) { hovercarIncrementStep(hovercar); padnum = &hovercar->path->pads[hovercar->nextstep]; pad = &g_Pads[*padnum]; } sp200 = atan2f(sp214.x - prop->pos.x, sp214.z - prop->pos.z); tmp2 = sqrtf((sp214.x - prop->pos.x) * (sp214.x - prop->pos.x) + (sp214.z - prop->pos.z) * (sp214.z - prop->pos.z)); sp1fc = atan2f(sp214.y - prop->pos.y, tmp2); if (hovercar->base.flags & OBJFLAG_20000000) { hovercar->roty = sp200; hovercar->rotx = sp1fc; obj->flags &= ~OBJFLAG_20000000; if (active) { prop->pos.y = sp214.y; } } if (active) { if (cdExamCylMove03(&prop->pos, prop->rooms, &sp214, CDTYPE_CLOSEDDOORS | CDTYPE_AJARDOORS, 0, 0, 0) == CDRESULT_COLLISION) { doorprop = cdGetObstacleProp(); } if (doorprop) { door = (struct doorobj *) doorprop->obj; x = doorprop->pos.x - prop->pos.x; z = doorprop->pos.z - prop->pos.z; if (x * x + z * z < 200 * 200) { doorsChooseSwingDirection(prop, door); doorsRequestMode(door, DOORMODE_OPENING); } if (x * x + z * z < 195 * 195) { stopping = !doorIsOpen(door); } } } } else { if (hovercar->base.flags & OBJFLAG_20000000) { hovercar->roty = atan2f(hovercar->base.realrot[2][0], hovercar->base.realrot[2][2]); tmp2 = sqrtf(hovercar->base.realrot[2][2] * hovercar->base.realrot[2][2] + hovercar->base.realrot[2][0] * hovercar->base.realrot[2][0]); hovercar->rotx = atan2f(hovercar->base.realrot[2][1], tmp2); hovercar->base.flags &= ~OBJFLAG_20000000; } } // @bug: To calculate the speed correctly, the code needs to iterate the // lvupdate multiplier, then adjust the speed and test for reaching the // speedaim within each iteration. Or at least test for exceeding the // speedaim after updating it. With the below implementation, a lag frame // during acceleration can cause the hovercar to exceed its max speed. if (hovercar->speedtime60 >= 0) { if (1); if (hovercar->speedtime60 <= g_Vars.lvupdate60freal) { hovercar->speed = hovercar->speedaim; } else { hovercar->speed += (hovercar->speedaim - hovercar->speed) * g_Vars.lvupdate60freal / hovercar->speedtime60; } hovercar->speedtime60 -= g_Vars.lvupdate60freal; } if (active) { if (hovercar->turnyspeed60 > 0) { hovercar->speed -= hovercar->speedaim * (1.0f / 24.0f) * LVUPDATE60FREAL(); if (hovercar->speed < 0) { hovercar->speed = 0.1f; } } else { if (hovercar->speed < hovercar->speedaim) { hovercar->speed += hovercar->speedaim * (1.0f / 24.0f) * LVUPDATE60FREAL(); } } if (stopping) { hovercar->speed -= 50.0f / 240.f * LVUPDATE60FREAL(); if (hovercar->speed < 0) { hovercar->speed = 0; } } } if (hovercar->speed != 0.0f) { sp190 = hovercar->roty; sp18c = hovercar->rotx; sp188 = hovercar->rotz; sp184 = hovercar->turnyspeed60; sp180 = hovercar->turnxspeed60; sp194.x = sinf(hovercar->roty) * cosf(hovercar->rotx); sp194.y = sinf(hovercar->rotx); sp194.z = cosf(hovercar->roty) * cosf(hovercar->rotx); if (posIsMovingTowardsPosOrStoppedInRange(&prop->pos, &sp194, &sp214, sp1f4)) { sp200 = hovercar->roty; sp1fc = hovercar->rotx; } applyRotation(&sp190, sp200, &sp184, HOVVALUE1(), HOVVALUE1() * 2.0f, HOVVALUE2()); if (sp190 == sp200 && HOVVALUE1() * 2.0f >= sp184 && -HOVVALUE1() * 2.0f <= sp184) { sp184 = 0; } applyRotation(&sp18c, sp1fc, &sp180, HOVVALUE1(), HOVVALUE1() * 2.0f, HOVVALUE2()); if (sp18c == sp1fc && HOVVALUE1() * 2.0f >= sp180 && -HOVVALUE1() * 2.0f <= sp180) { sp180 = 0; } if (hovercar->base.flags & OBJFLAG_80000000) { sp188 = 0; } else { sp188 += (-sp184 * 120 - sp188) * 0.1f; } sp12c.x = active ? M_BADTAU - sp18c : 0; sp12c.y = sp190; sp12c.z = 0; if (sp188 >= 0) { mtx4LoadZRotation(sp188, &sp6c); } else { mtx4LoadZRotation(sp188 + M_BADTAU, &sp6c); } mtx4LoadRotation(&sp12c, &spac); mtx00015f04(hovercar->base.model->scale, &spac); mtx4MultMtx4(&spac, &sp6c, &spec); mtx4ToMtx3(&spec, sp15c); mtx3Copy(sp15c, hovercar->base.realrot); sp138 = cosf(sp18c); sp194.x = sinf(sp190) * sp138; sp194.y = active ? sinf(sp1fc) : sinf(sp18c); sp194.z = cosf(sp190) * sp138; sp150.x = prop->pos.x + sp194.f[0] * (hovercar->speed * g_Vars.lvupdate60freal); sp150.y = prop->pos.y + sp194.f[1] * (hovercar->speed * g_Vars.lvupdate60freal); sp150.z = prop->pos.z + sp194.f[2] * (hovercar->speed * g_Vars.lvupdate60freal); func0f065e74(&prop->pos, prop->rooms, &sp150, sp140); if (active) { sp150.y = cdFindGroundAtCyl(&sp150, 5, sp140, NULL, NULL) + 35; if (sp150.y < -100000) { sp150.y = prop->pos.y + sp194.f[1] * (hovercar->speed * g_Vars.lvupdate60freal); } } prop->pos = sp150; propDeregisterRooms(prop); roomsCopy(sp140, prop->rooms); hovercar->roty = sp190; hovercar->rotx = sp18c; hovercar->rotz = sp188; hovercar->turnyspeed60 = sp184; hovercar->turnxspeed60 = sp180; if (hovercar->path) { if (hovercar->path->pads[hovercar->nextstep + 1] >= 0) { if (posIsArrivingLaterallyAtPos(&prop->pos, &sp150, &sp214, hovercar->speed * sp1f4)) { hovercarIncrementStep(hovercar); } } else { if (posIsArrivingLaterallyAtPos(&prop->pos, &sp150, &sp214, hovercar->speed * sp1f4)) { hovercarIncrementStep(hovercar); } } } func0f069c70(&hovercar->base, false, true); } } void hoverpropTick(struct prop *prop, bool arg1) { struct hoverpropobj *obj = (struct hoverpropobj *)prop->obj; if ((obj->base.hidden & OBJHFLAG_GRABBED) == 0 && (arg1 || (prop->flags & PROPFLAG_ONANYSCREENPREVTICK) || (obj->base.flags & OBJFLAG_CHOPPER_INACTIVE))) { hovTick(&obj->base, &obj->hov); } } void hoverbikeTick(struct prop *prop, bool arg1) { struct hoverbikeobj *obj = (struct hoverbikeobj *)prop->obj; if ((obj->base.hidden & OBJHFLAG_MOUNTED) == 0) { if ((obj->base.hidden & OBJHFLAG_GRABBED) == 0 && (arg1 || (prop->flags & PROPFLAG_ONANYSCREENPREVTICK))) { hovTick(&obj->base, &obj->hov); } if (obj->base.flags & OBJFLAG_HOVERBIKE_MOVINGWHILEEMPTY) { hoverbikeUpdateMovement(obj, 0, 0, 0); } } } /** * Show or hide the CI dropship's interior features depending on whether the * dropship object's deactivated flag is set. */ void dropshipUpdateInterior(struct prop *prop) { struct defaultobj *obj = prop->obj; struct model *model = obj->model; struct modelnode *node = modelGetPart(model->filedata, MODELPART_DROPSHIP_INTERIOR); if (node) { union modelrwdata *data = modelGetNodeRwData(model, node); u32 flags = obj->flags; data->toggle.visible = (flags & OBJFLAG_DEACTIVATED) == 0; } } void glassUpdatePortal(struct prop *prop, s32 playercount, bool *arg2) { struct tintedglassobj *glass = (struct tintedglassobj *) prop->obj; if (g_TintedGlassEnabled) { glass->opacity = 255; } else { glass->opacity = glassCalculateOpacity(&prop->pos, glass->xludist, glass->opadist, glass->unk64); } if (glass->portalnum >= 0 && playercount == 1) { if (glass->opacity == 255) { portalSetOpen(glass->portalnum, false); } else { portalSetOpen(glass->portalnum, true); } } *arg2 = false; } void weaponInitMatrices(struct prop *prop) { struct weaponobj *weapon = prop->weapon; struct model *model = weapon->base.model; s32 i = 1; Mtxf *mtxes = model->matrices; Mtxf *ptr = &mtxes[i]; for (; i < model->filedata->nummatrices; i++) { mtx4LoadIdentity(ptr); ptr++; } } void objInitMatrices(struct prop *prop) { struct defaultobj *obj = prop->obj; Mtxf mtx; if (obj->type == OBJTYPE_DOOR) { doorInitMatrices(prop); } else { mtx3ToMtx4(obj->realrot, &mtx); mtx4SetTranslation(&prop->pos, &mtx); mtx00015be4(camGetWorldToScreenMtxf(), &mtx, obj->model->matrices); if (obj->type == OBJTYPE_CCTV) { cctvInitMatrices(prop, &mtx); } else if (obj->type == OBJTYPE_AUTOGUN) { autogunInitMatrices(prop, &mtx); } else if (obj->type == OBJTYPE_CHOPPER) { chopperInitMatrices(prop); } else if (obj->type == OBJTYPE_WEAPON) { weaponInitMatrices(prop); } else { if (obj->model->filedata->nummatrices >= 2) { struct modelrenderdata thing = {NULL, 1, 3}; u32 stack; Mtxf sp28; mtx4Copy(obj->model->matrices, &sp28); thing.unk10 = obj->model->matrices; thing.unk00 = &sp28; model0001ce64(&thing, obj->model); } } } } void objTick(struct prop *prop) { struct defaultobj *obj = prop->obj; bool silent = false; bool regenning; u32 cmdindex; u32 padnum; struct defaultobj *newparent; if (prop->timetoregen > 0) { // Prop is taken/unavailable regenning = true; if (prop->timetoregen >= TICKS(60)) { regenning = false; } prop->timetoregen -= g_Vars.lvupdate60; if (prop->timetoregen <= 0) { // Prop has finished fading in prop->timetoregen = 0; if (obj->hidden2 & OBJH2FLAG_10) { obj->hidden |= OBJHFLAG_00001000; } else { obj->hidden &= ~OBJHFLAG_00001000; } } else if (prop->timetoregen < TICKS(60) && !regenning) { // 1 second left - time to start fading in if (obj->damage == 0 && (obj->hidden2 & OBJH2FLAG_DESTROYED) == 0) { if (obj->flags & OBJFLAG_INSIDEANOTHEROBJ) { propDeregisterRooms(prop); propDelist(prop); obj->hidden &= ~OBJHFLAG_00000800; cmdindex = setupGetCmdIndexByProp(prop); // Find the parent obj (pad is repurposed here) padnum = obj->pad; newparent = setupGetObjByCmdIndex(cmdindex + padnum); if (newparent && newparent->prop) { modelSetScale(obj->model, obj->model->scale); propReparent(obj->prop, newparent->prop); silent = true; } } else { propEnable(prop); setup0f0923d4(obj); obj->hidden &= ~OBJHFLAG_00000800; } } else { // Object was previously damaged. Maybe glass? if (obj->hidden2 & OBJH2FLAG_08) { obj->flags |= OBJFLAG_00000100; } else { obj->flags &= ~OBJFLAG_00000100; } obj->damage = 0; obj->hidden2 &= ~OBJH2FLAG_DESTROYED; modelFreeVertices(1, obj->model); } if (obj->type == OBJTYPE_SHIELD) { struct shieldobj *shield = (struct shieldobj *)obj; shield->amount = shield->initialamount; } if (!silent) { // Play respawn sound propsnd0f0939f8(NULL, prop, SFX_REGEN, -1, -1, 0, 0, 0, 0, -1, 0, -1, -1, -1, -1); } } } } /** * Tick the given object. * * This function is called once per player per frame. The first time it is * called per frame a "fulltick" is done. On consecutive calls for this tick * much of the logic is skipped, and only the logic specific to the current * player is executed. */ s32 objTickPlayer(struct prop *prop) { bool pass; struct defaultobj *obj = prop->obj; struct model *model = obj->model; bool sp592 = false; bool pass2; struct prop *child; struct prop *next; s32 result = TICKOP_NONE; bool fulltick = false; u32 playercount = PLAYERCOUNT(); bool sp564 = true; bool embedded = false; bool sp556 = false; bool sp552 = false; if (obj->hidden & OBJHFLAG_ISRETICK) { obj->hidden &= ~OBJHFLAG_ISRETICK; } else if ((obj->hidden & OBJHFLAG_PROJECTILE) && (obj->projectile->flags & PROJECTILEFLAG_SLIDING) == 0) { prop->flags &= ~PROPFLAG_ONTHISSCREENTHISTICK; obj->hidden |= OBJHFLAG_ISRETICK; return TICKOP_RETICK; } if (obj->hidden & OBJHFLAG_REAPABLE) { pass = false; if (obj->type == OBJTYPE_TINTEDGLASS) { struct tintedglassobj *glass = (struct tintedglassobj *)obj; if (glass->portalnum >= 0) { pass = PORTAL_IS_CLOSED(glass->portalnum); g_BgPortals[glass->portalnum].flags |= PORTALFLAG_FORCEOPEN; } } else if (obj->type == OBJTYPE_DOOR) { struct doorobj *door = (struct doorobj *)obj; if (door->portalnum >= 0) { pass = PORTAL_IS_CLOSED(door->portalnum); g_BgPortals[door->portalnum].flags |= PORTALFLAG_FORCEOPEN; } } if (!pass) { objDropRecursively(prop, true); objFree(obj, false, obj->hidden2 & OBJH2FLAG_CANREGEN); return TICKOP_FREE; } } if (prop->flags & PROPFLAG_NOTYETTICKED) { fulltick = true; prop->flags &= ~PROPFLAG_NOTYETTICKED; } if (fulltick) { if (obj->type == OBJTYPE_AUTOGUN) { struct autogunobj *autogun = (struct autogunobj *)prop->obj; if (autogun->beam) { beamTick(autogun->beam); } } else if (obj->type == OBJTYPE_CHOPPER) { struct chopperobj *chopper = (struct chopperobj *)prop->obj; beamTick(chopper->fireslotthing->beam); } } if (obj->hidden & OBJHFLAG_PROJECTILE) { struct projectile *projectile = obj->projectile; if (projectile->ownerprop && playermgrGetPlayerNumByProp(projectile->ownerprop) >= 0) { fulltick = (projectile->ownerprop == g_Vars.currentplayer->prop); } } if (model->anim) { if (g_Anims[model->anim->animnum].flags & 0x02) { if (g_Vars.tickmode != TICKMODE_CUTSCENE && modelGetCurAnimFrame(model) >= modelGetNumAnimFrames(model) - 1) { modelmgrFreeAnim(model->anim); model->anim = NULL; } else { // In cutscene struct modelrenderdata sp476 = {0, 1, 3}; Mtxf sp412; struct coord sp400; s16 sp384[8]; struct hov *hov = NULL; if (fulltick) { s32 iVar10 = g_Vars.lvupdate240; if (g_Vars.tickmode == TICKMODE_CUTSCENE && iVar10 > 0 && g_Vars.cutsceneskip60ths > 0) { iVar10 += g_Vars.cutsceneskip60ths * 4; } model0001ee18(model, iVar10, true); } anim00023d38(model->anim->animnum); if ((g_Anims[model->anim->animnum].flags & 0x04) && anim0002384c(model->anim->animnum, model->anim->framea) < 0) { sp552 = true; } else { if (fulltick) { model0001b3bc(model); } sp556 = true; sp476.unk10 = gfxAllocate(model->filedata->nummatrices * sizeof(Mtxf)); sp476.unk00 = camGetWorldToScreenMtxf(); model0001cebc(&sp476, model); if (fulltick) { mtx00015be4(camGetProjectionMtxF(), model->matrices, &sp412); mtx4ToMtx3(&sp412, obj->realrot); sp400.x = sp412.m[3][0]; sp400.y = sp412.m[3][1]; sp400.z = sp412.m[3][2]; if (obj->flags3 & OBJFLAG3_00000010) { func0f065e98(&prop->pos, prop->rooms, &sp400, sp384); } else { func0f065e74(&prop->pos, prop->rooms, &sp400, sp384); } prop->pos = sp400; propDeregisterRooms(prop); roomsCopy(sp384, prop->rooms); if (obj->type == OBJTYPE_HOVERPROP) { hov = &((struct hoverpropobj *)obj)->hov; } else if (obj->type == OBJTYPE_HOVERBIKE) { hov = &((struct hoverbikeobj *)obj)->hov; } if (hov) { hovUpdateGround(obj, hov, &prop->pos, prop->rooms, obj->realrot); hoverpropSetTurnAngle(obj, atan2f(sp412.m[2][0], sp412.m[2][2])); hov->unk14 = 0; hov->unk1c = 0; hov->unk20 = 0; hov->unk28 = 0; hov->unk30 = hov->ground; hov->unk04 = prop->pos.y - hov->ground; hov->unk0c = 0; } if ((obj->flags & OBJFLAG_IGNOREFLOORCOLOUR) == 0) { cdFindFloorYColourTypeAtPos(&prop->pos, prop->rooms, &obj->floorcol, 0); } func0f069c70(obj, true, true); if (obj->type == OBJTYPE_LIFT) { liftUpdateTiles((struct liftobj *)obj, false); } sp592 = true; } } } } else { struct modelrenderdata sp312 = {0, 1, 3}; Mtxf sp248; struct coord sp236; s16 sp220[8]; s32 numchrs; Mtxf sp152; s32 sp148; s32 sp144; s32 i; struct coord sp128; struct coord sp116 = {0, 0, 0}; f32 sp112; s32 tagnum; struct geo *geos[2]; u8 *end; f32 damage; if (fulltick) { sp148 = floorf(model->anim->frame); sp148++; model0001ee18(model, g_Vars.lvupdate240, 1); sp144 = floorf(model->anim->frame); for (i = sp148; i <= sp144; i++) { anim00024b64(0, 0, model->filedata->skel, model->anim->animnum, i, &sp128, 0); sp116.x += sp128.x * 0.1f; sp112 = sp128.y * 0.1f; sp116.z += sp128.z * 0.1f; } mtx00016208(obj->realrot, &sp116); } mtx3ToMtx4(obj->realrot, &sp248); mtx4SetTranslation(&prop->pos, &sp248); mtx4MultMtx4(camGetWorldToScreenMtxf(), &sp248, &sp152); sp556 = true; sp312.unk10 = gfxAllocate(model->filedata->nummatrices * sizeof(Mtxf)); sp312.unk00 = &sp152; model0001cebc(&sp312, model); if (fulltick) { sp236.x = (f32)sp116.x + prop->pos.x; sp236.y = prop->pos.y; sp236.z = (f32)sp116.z + prop->pos.z; if (obj->flags3 & OBJFLAG3_00000010) { func0f065e98(&prop->pos, prop->rooms, &sp236, sp220); } else { func0f065e74(&prop->pos, prop->rooms, &sp236, sp220); } if (modelGetCurAnimFrame(model) >= modelGetNumAnimFrames(model) - 1) { modelmgrFreeAnim(model->anim); model->anim = NULL; mtx00015be4(camGetProjectionMtxF(), model->matrices, &sp248); mtx4ToMtx3(&sp248, obj->realrot); tagnum = objGetTagNum(obj); if (tagnum >= 0) { numchrs = chrsGetNumSlots(); for (i = 0; i < numchrs; i++) { if (g_ChrSlots[i].myspecial == tagnum) { g_ChrSlots[i].myspecial = -1; } } } } prop->pos.x = sp236.x; prop->pos.z = sp236.z; propDeregisterRooms(prop); roomsCopy(sp220, prop->rooms); if (sp148 <= sp144) { prop->pos.y = cdFindGroundAtCyl(&prop->pos, 5, prop->rooms, &obj->floorcol, NULL) + func0f06a620(obj) + sp112; } func0f069c70(obj, true, true); sp592 = true; if (objUpdateGeometry(prop, (u8 **)geos, &end) && geos[0]->type == GEOTYPE_BLOCK && cdTestBlockOverlapsAnyProp((struct geoblock *) geos[0], prop->rooms, CDTYPE_PLAYERS) == CDRESULT_COLLISION) { damage = ((obj->maxdamage - obj->damage) + 1) / 250.0f; obj->flags &= ~OBJFLAG_INVINCIBLE; objDamage(obj, damage, &prop->pos, WEAPON_REMOTEMINE, -1); } } } } if (fulltick) { if (model->anim == NULL && (obj->hidden & OBJHFLAG_PROJECTILE)) { sp592 = projectileTick(obj, &embedded); if (embedded) { result = TICKOP_CHANGEDLIST; } } if (obj->type == OBJTYPE_DOOR) { doorTick(prop); } else if (obj->type == OBJTYPE_CCTV && (obj->flags & OBJFLAG_DEACTIVATED) == 0) { cctvTick(prop); } else if (obj->type == OBJTYPE_FAN) { fanTick(prop); } else if (obj->type == OBJTYPE_AUTOGUN && (obj->flags & OBJFLAG_DEACTIVATED) == 0) { autogunTick(prop); } else if (obj->type == OBJTYPE_HOVERCAR) { hovercarTick(prop); } else if (obj->type == OBJTYPE_CHOPPER) { struct chopperobj *chopper = (struct chopperobj *)obj; if (!chopper->dead) { if (!lvIsPaused()) { if (chopper->attackmode == CHOPPERMODE_DEAD) { // empty } else if (chopper->attackmode == CHOPPERMODE_FALL) { if (obj->flags & OBJFLAG_CHOPPER_INACTIVE) { chopper->dead = true; } else { chopperTickFall(prop); } } else if (obj->flags & OBJFLAG_CHOPPER_INACTIVE) { chopperTickIdle(prop); } else if (chopper->attackmode == CHOPPERMODE_PATROL) { chopperTickPatrol(prop); } else if (chopper->attackmode == CHOPPERMODE_COMBAT) { chopperTickCombat(prop); } } } else { func0f0926bc(prop, 1, 0xffff); } } else if (obj->type == OBJTYPE_HOVERPROP) { hoverpropTick(prop, sp592); } else if (obj->type == OBJTYPE_HOVERBIKE) { hoverbikeTick(prop, sp592); } } if (obj->type == OBJTYPE_TINTEDGLASS) { glassUpdatePortal(prop, playercount, &sp564); } else if (obj->type == OBJTYPE_DOOR) { doorUpdatePortalIfWindowed(prop, playercount); } if (sp552) { pass2 = false; } else if (prop == bmoveGetHoverbike() || prop == bmoveGetGrabbedProp()) { pass2 = posIsInDrawDistance(&prop->pos); } else if (obj->flags2 & OBJFLAG2_04000000) { pass2 = posIsInDrawDistance(&prop->pos); } else if ((obj->hidden & OBJHFLAG_00000800) == 0 && (obj->flags2 & OBJFLAG2_INVISIBLE) == 0) { pass2 = func0f08e8ac(prop, &prop->pos, model0001af80(model), sp564); } else { pass2 = false; } if (pass2) { if (sp592 == false) { propCalculateShadeInfo(prop, obj->nextcol, obj->floorcol); } if (fulltick) { if (prop->flags & PROPFLAG_ONANYSCREENPREVTICK) { colourTween(obj->shadecol, obj->nextcol); } else { obj->shadecol[0] = obj->nextcol[0]; obj->shadecol[1] = obj->nextcol[1]; obj->shadecol[2] = obj->nextcol[2]; obj->shadecol[3] = obj->nextcol[3]; } } prop->flags |= PROPFLAG_ONANYSCREENTHISTICK | PROPFLAG_ONTHISSCREENTHISTICK; if (obj->type == OBJTYPE_FAN) { fanUpdateModel(prop); } else if (obj->model->filedata->skel == &g_SkelDropship) { dropshipUpdateInterior(prop); } if (sp556 == false) { model->matrices = gfxAllocate(model->filedata->nummatrices * sizeof(Mtxf)); objInitMatrices(prop); model0001cb0c(model, model->filedata->rootnode); } prop->z = -model->matrices[0].m[3][2]; func0f07063c(prop, fulltick); child = prop->child; while (child) { next = child->next; func0f07079c(child, fulltick); child = next; } } else { prop->flags &= ~PROPFLAG_ONTHISSCREENTHISTICK; func0f07063c(prop, fulltick); child = prop->child; while (child) { next = child->next; func0f0706f8(child, fulltick); child = next; } } if (obj->hidden & OBJHFLAG_00000100) { obj->hidden &= ~OBJHFLAG_00000100; objDamage(obj, RANDOMFRAC() * 4.0f + 2.0f, &prop->pos, WEAPON_NONE, (obj->hidden & 0xf0000000) >> 28); } if (fulltick) { if (obj->type == OBJTYPE_AUTOGUN) { autogunTickShoot(prop); } objDropRecursively(prop, false); } return result; } Gfx *propsRenderBeams(Gfx *gdl) { struct prop *prop = g_Vars.activeprops; while (prop) { if (prop->type == PROPTYPE_CHR) { struct chrdata *chr = prop->chr; if (CHRRACE(chr) == RACE_ROBOT) { gdl = beamRender(gdl, chr->unk348[0]->beam, true, true); gdl = beamRender(gdl, chr->unk348[1]->beam, true, true); } else { if (chr->fireslots[0] >= 0) { gdl = beamRender(gdl, &g_Fireslots[chr->fireslots[0]].beam, true, false); } if (chr->fireslots[1] >= 0) { gdl = beamRender(gdl, &g_Fireslots[chr->fireslots[1]].beam, true, false); } } } else if (prop->type == PROPTYPE_OBJ) { struct defaultobj *obj = prop->obj; if (obj->type == OBJTYPE_AUTOGUN) { struct autogunobj *autogun = (struct autogunobj *)prop->obj; gdl = beamRender(gdl, autogun->beam, true, false); } else if (obj->type == OBJTYPE_CHOPPER) { struct chopperobj *chopper = (struct chopperobj *)prop->obj; gdl = beamRender(gdl, chopper->fireslotthing->beam, true, true); } } else if (prop->type == PROPTYPE_PLAYER) { if (prop->chr && playermgrGetPlayerNumByProp(prop) != g_Vars.currentplayernum) { struct chrdata *chr = prop->chr; if (chr->fireslots[0] >= 0) { gdl = beamRender(gdl, &g_Fireslots[chr->fireslots[0]].beam, true, false); } if (chr->fireslots[1] >= 0) { gdl = beamRender(gdl, &g_Fireslots[chr->fireslots[1]].beam, true, false); } } } prop = prop->next; } return gdl; } void tvscreenSetCmdlist(struct tvscreen *screen, u32 *cmdlist) { screen->cmdlist = cmdlist; screen->offset = 0; } u32 g_TvCmdlist00[] = { tvcmd_settexture(29), tvcmd_setcolour(0x008000ff, 1), tvcmd_scrollrely(-512, 80), tvcmd_pause(120), tvcmd_scrollrely(-256, 20), tvcmd_pause(120), tvcmd_scrollrely(-128, 10), tvcmd_pause(40), tvcmd_scrollrely(-512, 40), tvcmd_pause(60), tvcmd_scrollrely(-64, 30), tvcmd_pause(120), tvcmd_restart(), }; u32 g_TvCmdlist01[] = { tvcmd_setcolour(0x202020ff, 1), tvcmd_settexture(28), tvcmd_scrollrelx(2048, 120), tvcmd_pause(120), tvcmd_scaleabsx(256, 1), tvcmd_scaleabsy(512, 60), tvcmd_scrollrelx(-8192, 120), tvcmd_pause(120), tvcmd_scaleabsx(1024, 1), tvcmd_scaleabsy(1024, 60), tvcmd_scaleabsy(1024, 60), tvcmd_scrollrelx(2048, 120), tvcmd_pause(120), tvcmd_scaleabsx(128, 1), tvcmd_scaleabsy(2048, 60), tvcmd_scaleabsy(1024, 120), tvcmd_scrollrely(1024, 60), tvcmd_scrollrelx(512, 120), tvcmd_pause(120), tvcmd_restart(), }; u32 g_TvCmdlist02[] = { tvcmd_setcolour(0x202020ff, 1), tvcmd_settexture(28), tvcmd_scaleabsx(128, 1), tvcmd_scaleabsy(2048, 60), tvcmd_scaleabsy(1024, 120), tvcmd_scrollrely(1024, 10), tvcmd_scrollrelx(512, 40), tvcmd_pause(120), tvcmd_restart(), }; u32 g_TvCmdlist03[] = { tvcmd_settexture(29), tvcmd_setcolour(0x008000ff, 1), tvcmd_scrollrely(-512, 80), tvcmd_pause(120), tvcmd_scrollrely(-256, 20), tvcmd_pause(120), tvcmd_scrollrely(-128, 10), tvcmd_pause(40), tvcmd_scrollrely(-512, 40), tvcmd_pause(60), tvcmd_scrollrely(-64, 30), tvcmd_pause(120), tvcmd_restart(), }; u32 g_TvCmdlist15[] = { tvcmd_settexture(50), tvcmd_setcolour(0x008000fe, 1), tvcmd_scrollrely(-512, 80), tvcmd_pause(120), tvcmd_scrollrely(-256, 20), tvcmd_pause(120), tvcmd_scrollrely(-128, 10), tvcmd_pause(40), tvcmd_scrollrely(-512, 40), tvcmd_pause(60), tvcmd_scrollrely(-64, 30), tvcmd_pause(120), tvcmd_restart(), }; u32 g_TvCmdlist04[] = { tvcmd_settexture(29), tvcmd_setcolour(0x280000ff, 1), tvcmd_scrollrely(512, 80), tvcmd_pause(120), tvcmd_scrollrely(256, 20), tvcmd_pause(120), tvcmd_scrollrely(128, 10), tvcmd_pause(40), tvcmd_scrollrely(512, 40), tvcmd_pause(60), tvcmd_scrollrely(64, 30), tvcmd_pause(120), tvcmd_scrollrely(256, 20), tvcmd_pause(120), tvcmd_scrollrely(128, 10), tvcmd_restart(), }; u32 g_TvCmdlist05[] = { tvcmd_settexture(29), tvcmd_setcolour(0x003c00ff, 1), tvcmd_scrollrely(512, 80), tvcmd_pause(120), tvcmd_scrollrely(128, 10), tvcmd_pause(40), tvcmd_scrollrely(256, 20), tvcmd_pause(120), tvcmd_scrollrely(128, 10), tvcmd_pause(40), tvcmd_scrollrely(512, 40), tvcmd_pause(60), tvcmd_scrollrely(64, 30), tvcmd_pause(120), tvcmd_restart(), }; u32 g_TvCmdlist06[] = { tvcmd_settexture(30), tvcmd_setcolour(0x404000ff, 1), tvcmd_scrollrelx(640, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist07[] = { tvcmd_settexture(30), tvcmd_setcolour(0x004040ff, 1), tvcmd_scrollrelx(640, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist08[] = { tvcmd_settexture(30), tvcmd_setcolour(0x008000ff, 1), tvcmd_scrollrelx(-640, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist0F[] = { tvcmd_settexture(49), tvcmd_scaleabsx(512, 0), tvcmd_scaleabsy(512, 0), tvcmd_setcolour(0xdc2828ff, 60), tvcmd_pause(60), tvcmd_setcolour(0x323232ff, 10), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist10[] = { tvcmd_settexture(49), tvcmd_scaleabsx(512, 0), tvcmd_scaleabsy(512, 0), tvcmd_setcolour(0x32c832ff, 60), tvcmd_pause(60), tvcmd_setcolour(0x323232ff, 10), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist11[] = { tvcmd_settexture(49), tvcmd_scaleabsx(512, 0), tvcmd_scaleabsy(512, 0), tvcmd_setcolour(0x323232ff, 10), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist12[] = { tvcmd_settexture(49), tvcmd_scaleabsx(512, 0), tvcmd_scaleabsy(512, 0), tvcmd_setcolour(0xdc2828ff, 10), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist13[] = { tvcmd_settexture(49), tvcmd_scaleabsx(512, 0), tvcmd_scaleabsy(512, 0), tvcmd_setcolour(0x32c832ff, 10), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist16[] = { tvcmd_settexture(51), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist17[] = { tvcmd_settexture(72), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist18[] = { tvcmd_settexture(73), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist19[] = { tvcmd_settexture(74), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist1A[] = { tvcmd_settexture(75), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist1B[] = { tvcmd_settexture(76), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist1C[] = { tvcmd_settexture(77), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist24[] = { tvcmd_setcolour(0x000000ff, 1), tvcmd_settexture(75), tvcmd_pause(1), tvcmd_setcolour(0xffffffff, 180), tvcmd_pause(360), tvcmd_setcolour(0x000000ff, 30), tvcmd_pause(30), tvcmd_settexture(76), tvcmd_pause(1), tvcmd_setcolour(0xffffffff, 180), tvcmd_pause(360), tvcmd_setcolour(0x000000ff, 30), tvcmd_pause(30), tvcmd_settexture(77), tvcmd_pause(1), tvcmd_setcolour(0xffffffff, 180), tvcmd_pause(360), tvcmd_setcolour(0x000000ff, 29), tvcmd_pause(29), tvcmd_restart(), }; u32 g_TvCmdlist1D[] = { tvcmd_settexture(78), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 var8006a4dc[] = { tvcmd_settexture(52), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(5), tvcmd_settexture(53), tvcmd_pause(5), tvcmd_settexture(54), tvcmd_pause(5), tvcmd_settexture(55), tvcmd_pause(5), tvcmd_restart(), }; u32 var8006a52c[] = { tvcmd_settexture(79), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(5), tvcmd_settexture(80), tvcmd_pause(5), tvcmd_settexture(81), tvcmd_pause(5), tvcmd_settexture(82), tvcmd_pause(5), tvcmd_restart(), }; u32 var8006a57c[] = { tvcmd_settexture(56), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_settexture(57), tvcmd_pause(10), tvcmd_settexture(58), tvcmd_pause(10), tvcmd_settexture(59), tvcmd_pause(10), tvcmd_restart(), }; u32 var8006a5cc[] = { tvcmd_settexture(60), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(15), tvcmd_settexture(61), tvcmd_pause(15), tvcmd_restart(), }; u32 var8006a5fc[] = { tvcmd_settexture(62), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_settexture(63), tvcmd_pause(10), tvcmd_settexture(64), tvcmd_pause(10), tvcmd_settexture(65), tvcmd_pause(10), tvcmd_settexture(66), tvcmd_pause(10), tvcmd_restart(), }; u32 var8006a65c[] = { tvcmd_settexture(67), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_settexture(68), tvcmd_pause(10), tvcmd_settexture(69), tvcmd_pause(10), tvcmd_settexture(70), tvcmd_pause(10), tvcmd_settexture(71), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist25[] = { tvcmd_settexture(83), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist26[] = { tvcmd_settexture(84), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist27[] = { tvcmd_settexture(85), tvcmd_setcolour(0x008000ff, 1), tvcmd_scrollrely(-512, 80), tvcmd_pause(80), tvcmd_restart(), }; u32 g_TvCmdlist28[] = { tvcmd_settexture(85), tvcmd_setcolour(0x0032c8ff, 1), tvcmd_scrollrely(512, 80), tvcmd_pause(80), tvcmd_restart(), }; u32 g_TvCmdlist29[] = { tvcmd_settexture(86), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist2A[] = { tvcmd_settexture(86), tvcmd_setcolour(0xffffffff, 1), tvcmd_scrollrely(-512, 80), tvcmd_pause(80), tvcmd_restart(), }; u32 g_TvCmdlist2B[] = { tvcmd_settexture(87), tvcmd_setcolour(0x008000ff, 1), tvcmd_scrollrely(-512, 80), tvcmd_pause(80), tvcmd_restart(), }; u32 g_TvCmdlist2C[] = { tvcmd_settexture(87), tvcmd_setcolour(0x0032c8ff, 1), tvcmd_scrollrely(512, 80), tvcmd_pause(80), tvcmd_restart(), }; u32 g_TvCmdlist2D[] = { tvcmd_settexture(88), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist2E[] = { tvcmd_settexture(89), tvcmd_setcolour(0x007f00ff, 1), tvcmd_scrollrely(-512, 80), tvcmd_pause(120), tvcmd_scrollrely(-256, 20), tvcmd_pause(120), tvcmd_scrollrely(-128, 10), tvcmd_pause(40), tvcmd_scrollrely(-512, 40), tvcmd_pause(60), tvcmd_scrollrely(-64, 30), tvcmd_pause(120), tvcmd_restart(), }; u32 g_TvCmdlist2F[] = { tvcmd_settexture(89), tvcmd_setcolour(0xff7f00ff, 1), tvcmd_scrollrely(512, 80), tvcmd_pause(120), tvcmd_scrollrely(256, 20), tvcmd_pause(120), tvcmd_scrollrely(128, 10), tvcmd_pause(40), tvcmd_scrollrely(512, 40), tvcmd_pause(60), tvcmd_scrollrely(64, 30), tvcmd_pause(120), tvcmd_restart(), }; u32 g_TvCmdlist30[] = { tvcmd_settexture(90), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist31[] = { tvcmd_settexture(91), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist32[] = { tvcmd_settexture(92), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist33[] = { tvcmd_setcolour(0x000000ff, 1), tvcmd_settexture(90), tvcmd_pause(1), tvcmd_setcolour(0xffffffff, 180), tvcmd_pause(360), tvcmd_setcolour(0x000000ff, 30), tvcmd_pause(30), tvcmd_settexture(91), tvcmd_pause(1), tvcmd_setcolour(0xffffffff, 180), tvcmd_pause(360), tvcmd_setcolour(0x000000ff, 30), tvcmd_pause(30), tvcmd_settexture(92), tvcmd_pause(1), tvcmd_setcolour(0xffffffff, 180), tvcmd_pause(360), tvcmd_setcolour(0x000000ff, 29), tvcmd_pause(29), tvcmd_restart(), }; u32 g_TvCmdlist34[] = { tvcmd_settexture(93), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist35[] = { tvcmd_settexture(94), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist36[] = { tvcmd_settexture(95), tvcmd_setcolour(0xffffffff, 1), tvcmd_pause(10), tvcmd_restart(), }; u32 g_TvCmdlist14[] = { tvcmd_settexture(0), tvcmd_setcolour(0x000000ff, 0), tvcmd_yield(), // flow on to next cmdlist }; u32 var8006aaa0[] = { tvcmd_scaleabsx(1024, 0), tvcmd_scaleabsy(1024, 0), tvcmd_pause(1), tvcmd_scaleabsx(4096, 20), tvcmd_scaleabsy(4096, 20), tvcmd_pause(20), tvcmd_restart(), }; u32 var8006aae4[] = { tvcmd_settexture(0), tvcmd_scrollrelx(1024, 20), tvcmd_pause(20), tvcmd_scrollrely(1024, 20), tvcmd_setcolour(0x000000ff, 20), tvcmd_pause(20), tvcmd_scaleabsx(512, 20), tvcmd_scaleabsy(512, 20), tvcmd_setcolour(0xffffffff, 20), tvcmd_pause(20), tvcmd_scaleabsx(1024, 20), tvcmd_scaleabsy(1024, 20), tvcmd_pause(20), tvcmd_restart(), }; void tvscreenSetImageByNum(struct tvscreen *screen, s32 imagenum) { u32 *image = g_TvCmdlist00; switch (imagenum) { case TVCMDLIST_01: image = g_TvCmdlist01; break; case TVCMDLIST_02: image = g_TvCmdlist02; break; case TVCMDLIST_03: image = g_TvCmdlist03; break; case TVCMDLIST_04: image = g_TvCmdlist04; break; case TVCMDLIST_05: image = g_TvCmdlist05; break; case TVCMDLIST_06: image = g_TvCmdlist06; break; case TVCMDLIST_07: image = g_TvCmdlist07; break; case TVCMDLIST_08: image = g_TvCmdlist08; break; case TVCMDLIST_09: image = g_TvCmdlist03; break; case TVCMDLIST_0A: image = g_TvCmdlist03; break; case TVCMDLIST_0B: image = g_TvCmdlist03; break; case TVCMDLIST_0C: image = g_TvCmdlist03; break; case TVCMDLIST_0D: image = g_TvCmdlist03; break; case TVCMDLIST_0E: image = g_TvCmdlist03; break; case TVCMDLIST_0F: image = g_TvCmdlist0F; break; case TVCMDLIST_10: image = g_TvCmdlist10; break; case TVCMDLIST_11: image = g_TvCmdlist11; break; case TVCMDLIST_12: image = g_TvCmdlist12; break; case TVCMDLIST_13: image = g_TvCmdlist13; break; case TVCMDLIST_14: image = g_TvCmdlist14; break; case TVCMDLIST_15: image = g_TvCmdlist15; break; case TVCMDLIST_16: image = g_TvCmdlist16; break; case TVCMDLIST_17: image = g_TvCmdlist17; break; case TVCMDLIST_18: image = g_TvCmdlist18; break; case TVCMDLIST_19: image = g_TvCmdlist19; break; case TVCMDLIST_1A: image = g_TvCmdlist1A; break; case TVCMDLIST_1B: image = g_TvCmdlist1B; break; case TVCMDLIST_1C: image = g_TvCmdlist1C; break; case TVCMDLIST_1D: image = g_TvCmdlist1D; break; case TVCMDLIST_24: image = g_TvCmdlist24; break; case TVCMDLIST_1E: image = g_TvCmdlist08; break; case TVCMDLIST_1F: image = g_TvCmdlist08; break; case TVCMDLIST_20: image = g_TvCmdlist08; break; case TVCMDLIST_21: image = g_TvCmdlist08; break; case TVCMDLIST_22: image = g_TvCmdlist08; break; case TVCMDLIST_23: image = g_TvCmdlist08; break; case TVCMDLIST_25: image = g_TvCmdlist25; break; case TVCMDLIST_26: image = g_TvCmdlist26; break; case TVCMDLIST_27: image = g_TvCmdlist27; break; case TVCMDLIST_28: image = g_TvCmdlist28; break; case TVCMDLIST_29: image = g_TvCmdlist29; break; case TVCMDLIST_2A: image = g_TvCmdlist2A; break; case TVCMDLIST_2B: image = g_TvCmdlist2B; break; case TVCMDLIST_2C: image = g_TvCmdlist2C; break; case TVCMDLIST_2D: image = g_TvCmdlist2D; break; case TVCMDLIST_2E: image = g_TvCmdlist2E; break; case TVCMDLIST_2F: image = g_TvCmdlist2F; break; case TVCMDLIST_30: image = g_TvCmdlist30; break; case TVCMDLIST_31: image = g_TvCmdlist31; break; case TVCMDLIST_32: image = g_TvCmdlist32; break; case TVCMDLIST_33: image = g_TvCmdlist33; break; case TVCMDLIST_34: image = g_TvCmdlist34; break; case TVCMDLIST_35: image = g_TvCmdlist35; break; case TVCMDLIST_36: image = g_TvCmdlist36; break; case TVCMDLIST_00: break; } tvscreenSetCmdlist(screen, image); } void tvscreenSetTexture(struct tvscreen *screen, s32 texturenum) { screen->tconfig = (struct textureconfig *)texturenum; } struct tvcmd { u32 type; s32 arg1; u32 arg2; }; Gfx *tvscreenRender(struct model *model, struct modelnode *node, struct tvscreen *screen, Gfx *gdl, s32 arg4, s32 arg5) { if (node && (node->type & 0xff) == MODELNODETYPE_DL) { struct gfxvtx *vertices = gfxAllocateVertices(4); // b4 struct colour *colours = gfxAllocateColours(1); // b0 Gfx *savedgdl = gdl++; // ac union modelrodata *rodata = node->rodata; // a8 union modelrwdata *rwdata = modelGetNodeRwData(model, node); // a4 struct textureconfig *tconfig; bool yielding = false; while (!yielding) { struct tvcmd *cmd = (struct tvcmd *) &screen->cmdlist[screen->offset]; // 98 switch (cmd->type) { case TVCMD_STOPSCROLL: screen->xmidinc = 0.0f; screen->ymidinc = 0.0f; screen->offset++; break; case TVCMD_SCROLLRELX: screen->xmidfrac = 0.0f; screen->xmidinc = cmd->arg2 == 0 ? 1.0f : 1.0f / cmd->arg2; screen->xmidold = screen->xmid; screen->xmidnew = screen->xmid + cmd->arg1 * (1.0f / 1024.0f); screen->offset += 3; break; case TVCMD_SCROLLRELY: screen->ymidfrac = 0.0f; screen->ymidinc = cmd->arg2 == 0 ? 1.0f : 1.0f / cmd->arg2; screen->ymidold = screen->ymid; screen->ymidnew = screen->ymid + cmd->arg1 * (1.0f / 1024.0f); screen->offset += 3; break; case TVCMD_SCROLLABSX: screen->xmidfrac = 0.0f; screen->xmidinc = cmd->arg2 == 0 ? 1.0f : 1.0f / cmd->arg2; screen->xmidold = screen->xmid; screen->xmidnew = cmd->arg1 * (1.0f / 1024.0f); screen->offset += 3; break; case TVCMD_SCROLLABSY: screen->ymidfrac = 0.0f; screen->ymidinc = cmd->arg2 == 0 ? 1.0f : 1.0f / cmd->arg2; screen->ymidold = screen->ymid; screen->ymidnew = cmd->arg1 * (1.0f / 1024.0f); screen->offset += 3; break; case TVCMD_SCALEABSX: screen->xscalefrac = 0.0f; screen->xscaleinc = cmd->arg2 == 0 ? 1.0f : 1.0f / cmd->arg2; screen->xscaleold = screen->xscale; screen->xscalenew = cmd->arg1 * (1.0f / 1024.0f); screen->offset += 3; break; case TVCMD_SCALEABSY: screen->yscalefrac = 0.0f; screen->yscaleinc = cmd->arg2 == 0 ? 1.0f : 1.0f / cmd->arg2; screen->yscaleold = screen->yscale; screen->yscalenew = cmd->arg1 * (1.0f / 1024.0f); screen->offset += 3; break; case TVCMD_SETTEXTURE: tvscreenSetTexture(screen, cmd->arg1); screen->offset += 2; break; case TVCMD_PAUSE: if (screen->pause60 >= 0) { screen->pause60 -= g_Vars.lvupdate60; if (screen->pause60 >= 0) { yielding = true; } else { screen->offset += 2; } } else { yielding = true; screen->pause60 = cmd->arg1; } break; case TVCMD_SETCMDLIST: tvscreenSetCmdlist(screen, (u32 *) cmd->arg1); break; case TVCMD_RANDSETCMDLIST: if ((random() >> 16) < cmd->arg2) { tvscreenSetCmdlist(screen, (u32 *) cmd->arg1); } else { screen->offset += 3; } break; case TVCMD_RESTART: screen->offset = 0; break; case TVCMD_YIELD: yielding = true; break; case TVCMD_SETCOLOUR: screen->colfrac = 0.0f; screen->colinc = cmd->arg2 == 0 ? 1.0f : 1.0f / cmd->arg2; screen->redold = screen->red; screen->rednew = ((u32)cmd->arg1 >> 24) & 0xff; screen->greenold = screen->green; screen->greennew = ((u32)cmd->arg1 >> 16) & 0xff; screen->blueold = screen->blue; screen->bluenew = ((u32)cmd->arg1 >> 8) & 0xff; screen->alphaold = screen->alpha; screen->alphanew = cmd->arg1 & 0xff; screen->offset += 3; break; case TVCMD_ROTATEABS: screen->rot = cmd->arg1 * (M_BADTAU / 65536.0f); screen->offset += 2; break; case TVCMD_ROTATEREL: screen->rot += g_Vars.lvupdate60f * cmd->arg1 * (M_BADTAU / 65536.0f); if (screen->rot >= M_BADTAU) { screen->rot -= M_BADTAU; } if (screen->rot < 0.0f) { screen->rot += M_BADTAU; } screen->offset += 2; break; } } // Increment X scale if (screen->xscaleinc > 0.0f) { screen->xscalefrac += screen->xscaleinc * g_Vars.lvupdate60f; if (screen->xscalefrac < 1.0f) { screen->xscale = screen->xscaleold + (screen->xscalenew - screen->xscaleold) * screen->xscalefrac; } else { screen->xscalefrac = 1.0f; screen->xscaleinc = 0.0f; screen->xscale = screen->xscalenew; } } // Increment Y scale if (screen->yscaleinc > 0.0f) { screen->yscalefrac += screen->yscaleinc * g_Vars.lvupdate60f; if (screen->yscalefrac < 1.0f) { screen->yscale = screen->yscaleold + (screen->yscalenew - screen->yscaleold) * screen->yscalefrac; } else { screen->yscalefrac = 1.0f; screen->yscaleinc = 0.0f; screen->yscale = screen->yscalenew; } } // Increment X scroll if (screen->xmidinc > 0.0f) { screen->xmidfrac += screen->xmidinc * g_Vars.lvupdate60f; if (screen->xmidfrac < 1.0f) { screen->xmid = screen->xmidold + (screen->xmidnew - screen->xmidold) * screen->xmidfrac; } else { screen->xmidfrac = 1.0f; screen->xmidinc = 0.0f; screen->xmid = screen->xmidnew; } } // Increment Y scroll if (screen->ymidinc > 0.0f) { screen->ymidfrac += screen->ymidinc * g_Vars.lvupdate60f; if (screen->ymidfrac < 1.0f) { screen->ymid = screen->ymidold + (screen->ymidnew - screen->ymidold) * screen->ymidfrac; } else { screen->ymidfrac = 1.0f; screen->ymidinc = 0.0f; screen->ymid = screen->ymidnew; } } // Increment colour change if (screen->colinc > 0.0f) { screen->colfrac += screen->colinc * g_Vars.lvupdate60f; if (screen->colfrac < 1.0f) { screen->red = screen->redold + (s32) ((screen->rednew - screen->redold) * screen->colfrac); screen->green = screen->greenold + (s32) ((screen->greennew - screen->greenold) * screen->colfrac); screen->blue = screen->blueold + (s32) ((screen->bluenew - screen->blueold) * screen->colfrac); screen->alpha = screen->alphaold + (s32) ((screen->alphanew - screen->alphaold) * screen->colfrac); } else { screen->colfrac = 1.0f; screen->colinc = 0.0f; screen->red = screen->rednew; screen->green = screen->greennew; screen->blue = screen->bluenew; screen->alpha = screen->alphanew; } } // Set up everything for rendering rwdata->dl.gdl = gdl; rwdata->dl.vertices = vertices; rwdata->dl.colours = colours; vertices[0] = rodata->dl.vertices[0]; vertices[1] = rodata->dl.vertices[1]; vertices[2] = rodata->dl.vertices[2]; vertices[3] = rodata->dl.vertices[3]; if ((u32)screen->tconfig < 100) { tconfig = &g_TexScreenConfigs[(s32)screen->tconfig]; } else { tconfig = screen->tconfig; } if (tconfig != NULL) { u32 stack[13]; f32 f20; f32 f24; f32 f14; // 58 f32 f16; // 54 u8 stack2[0x8]; f32 a; f32 b; f20 = screen->xscale / 2.0f; f24 = screen->yscale / 2.0f; f14 = f20; f16 = f24; if (1); if (1); if (1); if (1); if (1); if (screen->rot != 0.0f) { f32 f22; f32 f2; f22 = cosf(screen->rot) * 1.4142f; f2 = sinf(screen->rot) * 1.4142f; f20 *= f22; f24 *= f2; f14 *= f2; f16 *= f22; } vertices[0].s = tconfig->width * (screen->xmid + f20) * 32.0f; vertices[0].t = tconfig->height * (screen->ymid + f24) * 32.0f; vertices[1].s = tconfig->width * (screen->xmid - f14) * 32.0f; vertices[1].t = tconfig->height * (screen->ymid + f16) * 32.0f; vertices[2].s = tconfig->width * (screen->xmid - f20) * 32.0f; vertices[2].t = tconfig->height * (screen->ymid - f24) * 32.0f; vertices[3].s = tconfig->width * (screen->xmid + f14) * 32.0f; vertices[3].t = tconfig->height * (screen->ymid - f16) * 32.0f; } colours[0].r = screen->red; colours[0].g = screen->green; colours[0].b = screen->blue; colours[0].a = screen->alpha; vertices[0].colour = 0; vertices[1].colour = 0; vertices[2].colour = 0; vertices[3].colour = 0; if (screen->alpha < 255) { arg5 = 2; } // Render the image gSPSetGeometryMode(gdl++, G_CULL_BACK); if (1); texSelect(&gdl, tconfig, arg5, arg4, 2, 1, NULL); gSPMatrix(gdl++, osVirtualToPhysical(model->matrices), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); gSPSegment(gdl++, SPSEGMENT_MODEL_VTX, osVirtualToPhysical(vertices)); gDPSetColorArray(gdl++, osVirtualToPhysical(colours), 1); gDPSetVerticeArray(gdl++, SPSEGMENT_MODEL_VTX << 24, 4); gDPTri2(gdl++, 0, 1, 2, 0, 2, 3); gSPEndDisplayList(gdl++); gSPBranchList(savedgdl++, gdl); } return gdl; } void objRenderProp(struct prop *prop, struct modelrenderdata *renderdata, bool xlupass) { if (prop->flags & PROPFLAG_ONTHISSCREENTHISTICK) { struct defaultobj *obj = prop->obj; struct model *model = obj->model; bool sp6c; struct prop *child; Gfx *gdl; s32 sp60; sp6c = 0; sp6c += (obj->flags & OBJFLAG_00000200) && camGetOrthogonalMtxL(); gdl = renderdata->gdl; if (obj->type == OBJTYPE_SINGLEMONITOR) { if (renderdata->flags & 1) { struct singlemonitorobj *monitor = (struct singlemonitorobj *) prop->obj; if (obj->flags2 & OBJFLAG2_DRAWONTOP) { sp60 = 0; } else if (obj->flags & OBJFLAG_DEACTIVATED) { sp60 = wallhit0f140750(&prop->pos); } else { sp60 = 1; } gdl = tvscreenRender(model, modelGetPart(model->filedata, MODELPART_0000), &monitor->screen, gdl, sp60, 1); } } else if (obj->type == OBJTYPE_MULTIMONITOR) { if (renderdata->flags & 1) { struct multimonitorobj *monitor = (struct multimonitorobj *) prop->obj; if (obj->flags2 & OBJFLAG2_DRAWONTOP) { sp60 = 0; } else if (obj->flags & OBJFLAG_DEACTIVATED) { sp60 = wallhit0f140750(&prop->pos); } else { sp60 = 1; } gdl = tvscreenRender(model, modelGetPart(model->filedata, MODELPART_0000), &monitor->screens[0], gdl, sp60, 1); if (obj->flags2 & OBJFLAG2_DRAWONTOP) { sp60 = 0; } else if (obj->flags & (OBJFLAG_DEACTIVATED | OBJFLAG_20000000)) { sp60 = wallhit0f140750(&prop->pos); } else { sp60 = 1; } gdl = tvscreenRender(model, modelGetPart(model->filedata, MODELPART_0001), &monitor->screens[1], gdl, sp60, 1); gdl = tvscreenRender(model, modelGetPart(model->filedata, MODELPART_0002), &monitor->screens[2], gdl, sp60, 1); gdl = tvscreenRender(model, modelGetPart(model->filedata, MODELPART_0003), &monitor->screens[3], gdl, sp60, 1); } } if (obj->type == OBJTYPE_DOOR) { struct doorobj *door = prop->door; gSPClearGeometryMode(gdl++, G_CULL_BOTH); if (door->doorflags & DOORFLAG_FLIP) { renderdata->cullmode = CULLMODE_FRONT; } else { renderdata->cullmode = CULLMODE_BACK; } if (renderdata->unk30 == 9) { renderdata->envcolour &= 0xffffff00; } } else if ((obj->hidden2 & OBJH2FLAG_80) == 0) { renderdata->cullmode = CULLMODE_BACK; if (renderdata->unk30 == 9) { renderdata->envcolour &= 0xffffff00; } } else { s32 level = objGetDestroyedLevel(obj); renderdata->cullmode = CULLMODE_NONE; if (renderdata->unk30 == 9) { s32 alpha = 100 + level * 50; if (alpha > 255) { alpha = 255; } renderdata->envcolour &= 0xffffff00; renderdata->envcolour |= alpha; } else if (level > 0) { renderdata->envcolour |= 0x0000ff00; } } if (sp6c) { gSPMatrix(gdl++, camGetOrthogonalMtxL(), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); } renderdata->gdl = gdl; modelRender(renderdata, model); gdl = renderdata->gdl; if (obj->type == OBJTYPE_DOOR) { gSPClearGeometryMode(gdl++, G_CULL_BOTH); } if (obj->hidden2 & (OBJH2FLAG_HASOPA << xlupass)) { gdl = wallhitRenderPropHits(gdl, prop, xlupass); } if (sp6c) { gSPMatrix(gdl++, camGetPerspectiveMtxL(), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); } renderdata->gdl = gdl; child = prop->child; while (child) { objRenderProp(child, renderdata, xlupass); child = child->next; } if (xlupass) { if (sp6c) { player0f0c3320(model->matrices, model->filedata->nummatrices); } else { func0f0c33f0(model->matrices, model->filedata->nummatrices); } if ((obj->flags3 & (OBJFLAG3_SHOWSHIELD | OBJFLAG3_SHIELDHIT)) && objIsHealthy(obj)) { gSPSetGeometryMode(renderdata->gdl++, G_CULL_BACK); renderdata->gdl = shieldhitRender(renderdata->gdl, prop, prop, 0xff, 0, 0, 1, 2, 3); } } } } Gfx *gfxRenderRadialShadow(Gfx *gdl, f32 x, f32 y, f32 z, f32 angle, f32 radius, u32 colour) { Mtxf spc0; Mtxf sp80; Mtxf *mtx; struct gfxvtx *vertices; u32 *colours; struct coord pos; struct textureconfig *tconfig; s32 i; f32 base1 = 0.0f; f32 base2 = 0.0f; f32 base3 = 0.0f; f32 base4 = 0.0f; if (radius); vertices = gfxAllocateVertices(4); colours = gfxAllocateColours(1); tconfig = &g_TexShadowConfigs[0]; colours[0] = colour; pos.x = x; pos.y = y + 2.0f; pos.z = z; mtx = gfxAllocateMatrix(); mtx4LoadYRotationWithTranslation(&pos, angle, &spc0); mtx4MultMtx4(camGetWorldToScreenMtxf(), &spc0, &sp80); mtx00016054(&sp80, mtx); for (i = 0; i < 4; i++) { vertices[i].y = 0; vertices[i].colour = 0; } vertices[0].x = base1 - radius; vertices[0].z = base3 - radius; vertices[1].x = base1 - radius; vertices[1].z = base4 + radius; vertices[2].x = base2 + radius; vertices[2].z = base4 + radius; vertices[3].x = base2 + radius; vertices[3].z = base3 - radius; if (tconfig) { vertices[0].s = 0; vertices[0].t = 0; vertices[1].s = tconfig->width * 32 - 1; vertices[1].t = 0; vertices[2].s = tconfig->width * 32 - 1; vertices[2].t = tconfig->height * 32 - 1; vertices[3].s = 0; vertices[3].t = tconfig->height * 32 - 1; texSelect(&gdl, tconfig, 4, 1, 2, 1, NULL); } else { texSelect(&gdl, NULL, 1, 1, 2, 1, NULL); } gSPSetGeometryMode(gdl++, G_CULL_BACK); gSPMatrix(gdl++, osVirtualToPhysical(mtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); gDPSetColorArray(gdl++, osVirtualToPhysical(colours), 1); gDPSetVerticeArray(gdl++, osVirtualToPhysical(vertices), 4); gDPTri2(gdl++, 0, 1, 2, 2, 3, 0); return gdl; } Gfx *objRenderShadow(struct defaultobj *obj, Gfx *gdl) { f32 angle; f32 y; s32 room = cdFindFloorRoomYColourFlagsAtPos(&obj->prop->pos, obj->prop->rooms, &y, NULL, NULL); if (room > 0 && (obj->modelnum == MODEL_HOOVERBOT || obj->modelnum == MODEL_TESTERBOT)) { angle = hoverpropGetTurnAngle(obj); gdl = gfxRenderRadialShadow(gdl, obj->prop->pos.x, y, obj->prop->pos.z, angle, 20, 0xffffff78); } else if (room > 0) { angle = hoverpropGetTurnAngle(obj); gdl = gfxRenderRadialShadow(gdl, obj->prop->pos.x, y, obj->prop->pos.z, angle, 30, 0xffffff78); } return gdl; } Gfx *objRender(struct prop *prop, Gfx *gdl, bool xlupass) { f32 spe8[4]; s32 spe4; struct defaultobj *obj = prop->obj; struct modelrenderdata renderdata = {NULL, true, 3}; struct screenbox screenbox; s32 colour[4]; s32 sp84; s32 healththing; s32 alpha = 0xff; f32 xrayalphafrac; s32 mult; struct weaponobj *weapon; struct doorobj *door; f32 frac; struct modelnode *node; struct modelrodata_dl *dldata1; struct modelrwdata_dl *dldata2; f32 fadedist; struct colour *oldcolours; struct colour *newcolours; f32 objdist; s32 i; spe4 = env0f1667f4(prop, spe8); if (spe4 == 0) { return gdl; } if (obj->type != OBJTYPE_TINTEDGLASS) { frac = func0f08e6bc(prop, model0001af80(obj->model)); if (prop->timetoregen > 0 && prop->timetoregen < TICKS(60)) { frac *= (TICKS(60.0f) - prop->timetoregen) * (PAL ? 0.019999999552965f : 0.016666667535901f); } alpha = frac * 255.0f; if (alpha <= 0) { return gdl; } } if (g_Vars.currentplayer->visionmode == VISIONMODE_NORMAL) { // empty } else if (g_Vars.currentplayer->visionmode == VISIONMODE_SLAYERROCKET) { if (g_Vars.currentplayer->slayerrocket && g_Vars.currentplayer->slayerrocket->base.prop && g_Vars.currentplayer->slayerrocket->base.prop == prop) { return gdl; } } else if (g_Vars.currentplayer->visionmode == VISIONMODE_XRAY) { fadedist; objdist = sqrtf(ERASERSQDIST(prop->pos.f)); if (objdist > g_Vars.currentplayer->eraserpropdist) { return gdl; } alpha = 128; fadedist = g_Vars.currentplayer->eraserpropdist - 150.0f; if (objdist > fadedist) { alpha = (1.0f - (objdist - fadedist) / 150.0f) * 128; } xrayalphafrac = objdist / g_Vars.currentplayer->eraserpropdist; if (xrayalphafrac > 1.0f) { xrayalphafrac = 1.0f; } } if (g_Vars.lvmpbotlevel && obj->type == OBJTYPE_WEAPON) { if (obj->flags3 & OBJFLAG3_HARDFREEING) { weapon = (struct weaponobj *)obj; alpha = ((f32)alpha * (f32)weapon->fadeouttimer60) * (PAL ? 0.019999999552965f : 0.016666667535901f); if (alpha < 0) { alpha = 0; } else if (alpha > 0xff) { alpha = 0xff; } } } if (alpha < 0xff || (obj->flags2 & OBJFLAG2_DRAWONTOP)) { if (!xlupass) { return gdl; } sp84 = 3; } else { if (!xlupass) { sp84 = 1; } else { sp84 = 2; } } if (obj->type == OBJTYPE_DOOR) { door = (struct doorobj *)obj; if (door->doortype == DOORTYPE_LASER) { node = func0f0687e4(obj->model); dldata1 = &node->rodata->dl; dldata2 = (struct modelrwdata_dl *) modelGetNodeRwData(obj->model, node); oldcolours = (struct colour *) ((((u32) &dldata1->vertices[dldata1->numvertices] + 7) | 7) ^ 7); newcolours = (struct colour *) gfxAllocateColours(dldata1->numcolours); for (i = 0; i < dldata1->numcolours; i++) { newcolours[i] = oldcolours[i]; newcolours[i].a = door->laserfade; } dldata2->colours = newcolours; } } if ((obj->flags2 & OBJFLAG2_04000000) == 0 && func0f08e5a8(prop->rooms, &screenbox) > 0) { gdl = currentPlayerScissorWithinViewport(gdl, screenbox.xmin, screenbox.ymin, screenbox.xmax, screenbox.ymax); } else { gdl = currentPlayerScissorToViewport(gdl); } renderdata.flags = sp84; renderdata.zbufferenabled = (obj->flags2 & OBJFLAG2_DRAWONTOP) == 0; renderdata.gdl = gdl; if (alpha < 0xff) { renderdata.unk30 = 5; renderdata.envcolour = alpha; } else { renderdata.unk30 = 9; if (obj->type == OBJTYPE_TINTEDGLASS) { struct tintedglassobj *glass = (struct tintedglassobj *)obj; renderdata.envcolour = glass->opacity << 8; } else { if (obj->type == OBJTYPE_DOOR && ((struct doorobj *)obj)->doorflags & DOORFLAG_WINDOWED) { renderdata.envcolour = ((struct doorobj *)obj)->fadealpha << 8; } else { renderdata.envcolour = 0; } } } if (prop->type == PROPTYPE_DOOR && (g_Vars.coopplayernum >= 0 || g_Vars.antiplayernum >= 0)) { struct doorobj *door = prop->door; if (g_Vars.currentplayernum == 0) { colour[0] = door->shadeinfo1[0]; colour[1] = door->shadeinfo1[1]; colour[2] = door->shadeinfo1[2]; colour[3] = door->shadeinfo1[3]; } else { colour[0] = door->shadeinfo2[0]; colour[1] = door->shadeinfo2[1]; colour[2] = door->shadeinfo2[2]; colour[3] = door->shadeinfo2[3]; } } else { colour[0] = obj->shadecol[0]; colour[1] = obj->shadecol[1]; colour[2] = obj->shadecol[2]; colour[3] = obj->shadecol[3]; } if (g_Vars.normmplayerisrunning) { scenarioHighlightProp(prop, colour); } if (g_Vars.currentplayer->visionmode == VISIONMODE_XRAY) { colour[g_Vars.currentplayer->epcol_0] = xrayalphafrac * 255.0f; colour[g_Vars.currentplayer->epcol_1] = (1.0f - xrayalphafrac) * 255.0f; colour[g_Vars.currentplayer->epcol_2] = 0; colour[3] = 0xff; } else { colour[3] -= func0f068fc8(obj->prop, true); if (colour[3]); if (colour[3] > 0xff) { colour[3] = 0xff; } if (colour[3] < 0) { colour[3] = 0; } } healththing = objGetShotsTaken(obj); mult = 0xff - (healththing * 21); if (mult < 0) { mult = 0; } colour[0] = (colour[0] * mult) >> 8; colour[1] = (colour[1] * mult) >> 8; colour[2] = (colour[2] * mult) >> 8; colour[3] += healththing * 15; if (colour[3] > 0xff) { colour[3] = 0xff; } func0f069750(colour, spe4, spe8); if (USINGDEVICE(DEVICE_NIGHTVISION)) { if ((obj->flags & OBJFLAG_PATHBLOCKER) == 0) { colour[0] = var8009caed; colour[1] = var8009caed; colour[2] = var8009caed; colour[3] = var8009caee; } } else if (USINGDEVICE(DEVICE_IRSCANNER)) { if ((obj->hidden & OBJHFLAG_CONDITIONALSCENERY) || (obj->flags3 & OBJFLAG3_INFRARED)) { colour[0] = 0xff; colour[1] = 0xff; colour[2] = 0xff; colour[3] = 0x46; } } renderdata.fogcolour = colour[0] << 24 | colour[1] << 16 | colour[2] << 8 | colour[3]; objRenderProp(prop, &renderdata, xlupass); gdl = renderdata.gdl; if (xlupass) { if (obj->type == OBJTYPE_HOVERPROP || obj->type == OBJTYPE_HOVERBIKE || obj->modelnum == MODEL_HOOVERBOT || obj->modelnum == MODEL_TESTERBOT) { gdl = objRenderShadow(obj, gdl); } if (obj->modelnum == MODEL_A51INTERCEPTOR && (obj->flags & OBJFLAG_80000000)) { gdl = objRenderShadow(obj, gdl); } } return gdl; } bool modelIsNodeNotTvscreen(struct modelfiledata *filedata, struct modelnode *node) { if (filedata->skel == &g_SkelTerminal) { if (modelGetPart(filedata, MODELPART_TERMINAL_0000) == node) { return false; } } if (filedata->skel == &g_SkelCiHub) { if (modelGetPart(filedata, MODELPART_CIHUB_0000) == node || modelGetPart(filedata, MODELPART_CIHUB_0001) == node || modelGetPart(filedata, MODELPART_CIHUB_0002) == node || modelGetPart(filedata, MODELPART_CIHUB_0003) == node) { return false; } } return true; } /** * Deform an object due to it being destroyed. */ void objDeform(struct defaultobj *obj, s32 level) { f32 min; f32 max; f32 average; f32 f2; f32 spbc = 0.0f; f32 spb0[3]; s32 i; struct modelrodata_bbox *parentbbox; struct modelrodata_bbox *bbox; struct modelnode *node; struct modelnode *parent; struct model *model = obj->model; struct modelfiledata *modeldef = model->filedata; volatile s32 salt; bool ok = true; f32 mult; bool swap; s32 axis; s32 chance; func0f0926bc(obj->prop, 15, 0xffff); salt = 0; salt = random(); wallhitsFreeByProp(obj->prop, 1); swap = false; axis = 1; for (i = 0; i < 3; i++) { bool swapthis = false; spb0[i] = sqrtf(obj->realrot[i][0] * obj->realrot[i][0] + obj->realrot[i][1] * obj->realrot[i][1] + obj->realrot[i][2] * obj->realrot[i][2]); f2 = obj->realrot[i][1] / spb0[i]; if (f2 < 0.0f) { swapthis = true; f2 = -f2; } if (spbc < f2) { spbc = f2; axis = i; swap = swapthis; } } min = 99999.0f; max = -99999.0f; bbox = modelFindBboxRodata(model); if (axis == 0) { min = objGetLocalXMin(bbox); max = objGetLocalXMax(bbox); } else if (axis == 1) { min = objGetLocalYMin(bbox); max = objGetLocalYMax(bbox); } else if (axis == 2) { min = objGetLocalZMin(bbox); max = objGetLocalZMax(bbox); } if (swap) { f32 tmp = max; max = min; min = tmp; } if (min < max) { average = (min + max) * 0.5f; } else { average = 0.0f; min = 0.0f; max = 0.0f; } if (level == 3 && average - min > 4.0f) { average = min + 6.0f - 2.0f; } if (max - min > 6.0f) { if (level < 3) { mult = 0.9f; } else { mult = (max - min - 6.0f) / (max - min); } } else { mult = 1.0f; } obj->realrot[1][0] *= mult; obj->realrot[1][1] *= mult; obj->realrot[1][2] *= mult; obj->prop->pos.f[axis] += (1.0f - mult) * min; node = modeldef->rootnode; while (node) { u32 type = node->type & 0xff; switch (type) { case MODELNODETYPE_DL: parentbbox = NULL; parent = node; while (parent) { if ((parent->type & 0xff) == MODELNODETYPE_BBOX) { parentbbox = &parent->rodata->bbox; break; } parent = parent->parent; } if (modelIsNodeNotTvscreen(modeldef, node) && parentbbox == bbox) { struct modelrodata_dl *rodata = &node->rodata->dl; struct modelrwdata_dl *rwdata = (struct modelrwdata_dl *)&model->rwdatas[rodata->rwdataindex]; struct gfxvtx *vertices = vtxstoreAllocate(rodata->numvertices, VTXSTORETYPE_OBJVTX, node, objGetDestroyedLevel(obj)); if (vertices) { if (rwdata->vertices != rodata->vertices) { // Replacing modified vertices with a new set for (i = 0; i < rodata->numvertices; i++) { vertices[i] = rwdata->vertices[i]; } vtxstoreFree(VTXSTORETYPE_OBJVTX, rwdata->vertices); } else { // Replacing original vertices with modified vertices for (i = 0; i < rodata->numvertices; i++) { vertices[i] = rodata->vertices[i]; } } rwdata->vertices = vertices; } else { ok = false; } if ((u32)rwdata->colours == ALIGN8((u32)&rodata->vertices[rodata->numvertices])) { struct colour *colours = vtxstoreAllocate(rodata->numcolours, VTXSTORETYPE_OBJCOL, NULL, 0); if (colours) { for (i = 0; i < rodata->numcolours; i++) { colours[i] = rwdata->colours[i]; } rwdata->colours = colours; } else { ok = false; } } if (ok) { for (i = 0; i < rodata->numcolours; i++) { if (i > 0) { rwdata->colours[i].a = 0; } } for (i = 0; i < rodata->numvertices; i++) { s16 tmp = average; rng2SetSeed(rodata->vertices[i].x + rodata->vertices[i].y + rodata->vertices[i].z + salt); { if (swap) { if (rwdata->vertices[i].v[axis] >= tmp) { chance = 20; } else { chance = 90; } } else { if (rwdata->vertices[i].v[axis] <= tmp) { chance = 20; } else { chance = 90; } } if ((s32)(random2() % 100) < chance) { rwdata->vertices[i].colour = 0; } } rwdata->vertices[i].x += (s32)(((s32)(random2() % 20) - 10) * spb0[0]); rwdata->vertices[i].y += (s32)(((s32)(random2() % 20) - 10) * spb0[1]); rwdata->vertices[i].z += (s32)(((s32)(random2() % 20) - 10) * spb0[2]); if (parentbbox != NULL) { if (rwdata->vertices[i].x < (s16)parentbbox->xmin) { rwdata->vertices[i].x = (s16)parentbbox->xmin; } if (rwdata->vertices[i].x > (s16)parentbbox->xmax) { rwdata->vertices[i].x = (s16)parentbbox->xmax; } if (rwdata->vertices[i].y < (s16)parentbbox->ymin) { rwdata->vertices[i].y = (s16)parentbbox->ymin; } if (rwdata->vertices[i].y > (s16)parentbbox->ymax) { rwdata->vertices[i].y = (s16)parentbbox->ymax; } if (rwdata->vertices[i].z < (s16)parentbbox->zmin) { rwdata->vertices[i].z = (s16)parentbbox->zmin; } if (rwdata->vertices[i].z > (s16)parentbbox->zmax) { rwdata->vertices[i].z = (s16)parentbbox->zmax; } } } } } break; case MODELNODETYPE_DISTANCE: model0001c784(obj->model, node); break; case MODELNODETYPE_TOGGLE: model0001c7d0(obj->model, node); break; case MODELNODETYPE_HEADSPOT: modelAttachHead(obj->model, node); break; } if (node->child) { node = node->child; } else { while (node) { if (node->next) { node = node->next; break; } node = node->parent; } } } if ((obj->hidden2 & OBJH2FLAG_80) == 0) { if (!ok) { modelFreeVertices(VTXSTORETYPE_OBJVTX, model); } else { obj->hidden2 |= OBJH2FLAG_80; } } } /** * Bounce an object, such as a hoverbot when it's destroyed. */ void objBounce(struct defaultobj *obj, struct coord *arg1) { struct coord dir; struct coord rot = {0, 0, 0}; struct projectile *projectile = NULL; func0f0685e4(obj->prop); if (obj->hidden & OBJHFLAG_EMBEDDED) { projectile = obj->embedment->projectile; } else if (obj->hidden & OBJHFLAG_PROJECTILE) { projectile = obj->projectile; } if (projectile) { projectile->speed.x = (RANDOMFRAC() * 6.6666665f) - 3.3333333f; projectile->speed.y = (RANDOMFRAC() * 3.3333333f) + 3.3333333f; projectile->speed.z = (RANDOMFRAC() * 6.6666665f) - 3.3333333f; #if PAL rot.x = RANDOMFRAC() * 0.029447744f - 0.014723872f; rot.y = RANDOMFRAC() * 0.029447744f - 0.014723872f; rot.z = RANDOMFRAC() * 0.029447744f - 0.014723872f; #else rot.x = RANDOMFRAC() * 0.024539785f - 0.012269893f; rot.y = RANDOMFRAC() * 0.024539785f - 0.012269893f; rot.z = RANDOMFRAC() * 0.024539785f - 0.012269893f; #endif mtx4LoadRotation(&rot, &projectile->mtx); projectile->flags |= PROJECTILEFLAG_AIRBORNE; dir = *arg1; mtx4RotateVecInPlace(camGetProjectionMtxF(), &dir); projectile->speed.x += 3.3333333f * dir.x; projectile->speed.z += 3.3333333f * dir.z; projectile->ownerprop = g_Vars.currentplayer->prop; projectile->bouncecount = 1; } } void objSetDropped(struct prop *prop, u32 droptype) { struct prop *parent = prop->parent; if (parent) { struct defaultobj *obj = prop->obj; func0f0685e4(prop); if ((obj->hidden & OBJHFLAG_EMBEDDED) && obj->embedment->projectile) { obj->embedment->projectile->droptype = droptype; } else if (obj->hidden & OBJHFLAG_PROJECTILE) { obj->projectile->droptype = droptype; } if (g_Vars.lvmpbotlevel && obj->type == OBJTYPE_WEAPON && obj->modelnum != MODEL_CHRBRIEFCASE && obj->modelnum != MODEL_CHRDATATHIEF) { obj->flags3 |= OBJFLAG3_CANHARDFREE; } } } void objApplyMomentum(struct defaultobj *obj, struct coord *speed, f32 rotation, bool addspeed, bool addrotation) { struct projectile *projectile = NULL; struct modelrodata_bbox *bbox; f32 sp24; f32 sp20; func0f0685e4(obj->prop); if (obj->hidden & OBJHFLAG_EMBEDDED) { projectile = obj->embedment->projectile; } else if (obj->hidden & OBJHFLAG_PROJECTILE) { projectile = obj->projectile; } if (projectile) { projectile->flags |= PROJECTILEFLAG_SLIDING; if (addspeed) { projectile->speed.x += speed->x; projectile->speed.y += speed->y; projectile->speed.z += speed->z; } else { projectile->speed = *speed; } if (addrotation) { projectile->unk0dc += rotation; } else { projectile->unk0dc = rotation; } if (obj->type == OBJTYPE_HOVERPROP || obj->type == OBJTYPE_HOVERBIKE) { if (obj->flags & OBJFLAG_20000000) { projectile->unk08c = 0.8f; projectile->unk098 = 0.0027777778f; projectile->unk0e0 = 0.000041881234f; projectile->unk0e4 = PAL ? 0.969f : 0.974f; projectile->unk0ec = 0.07852732f; projectile->unk0f0 = 6.6666665f; } else { projectile->unk08c = 0.5f; projectile->unk098 = 0.013888889f; projectile->unk0e0 = 0.00020940616f; projectile->unk0e4 = PAL ? 0.953f : 0.961f; projectile->unk0ec = 0.07852732f; projectile->unk0f0 = 6.6666665f; } return; } bbox = objFindBboxRodata(obj); sp24 = objGetRotatedLocalXMaxByMtx3(bbox, obj->realrot) - objGetRotatedLocalXMinByMtx3(bbox, obj->realrot); sp20 = objGetRotatedLocalZMaxByMtx3(bbox, obj->realrot) - objGetRotatedLocalZMinByMtx3(bbox, obj->realrot); if (sp24 > 150.0f || sp20 > 150.0f) { projectile->unk08c = 0.1f; projectile->unk098 = 0.055555556f; projectile->unk0e0 = 0.00083762466f; projectile->unk0e4 = PAL ? 0.953f : 0.961f; projectile->unk0ec = 0.009815915f; projectile->unk0f0 = 0.8333333f; } else if (sp24 > 75.0f || sp20 > 75.0f) { projectile->unk08c = 0.1f; projectile->unk098 = 0.055555556f; projectile->unk0e0 = 0.00083762466f; projectile->unk0e4 = PAL ? 0.953f : 0.961f; projectile->unk0ec = 0.01963183f; projectile->unk0f0 = 0.8333333f; } else { projectile->unk08c = 0.1f; projectile->unk098 = 0.055555556f; projectile->unk0e0 = 0.00041881233f; projectile->unk0e4 = PAL ? 0.953f : 0.961f; projectile->unk0ec = 0.07852732f; projectile->unk0f0 = 1.6666666f; } } } void func0f082e84(struct defaultobj *obj, struct coord *pos, struct coord *dir, struct coord *tween, bool addrotation) { struct coord speed = {0, 0, 0}; f32 a = tween->f[0] * dir->f[0] + tween->f[2] * dir->f[2]; f32 b = pos->f[0] - obj->prop->pos.f[0]; f32 c = pos->f[2] - obj->prop->pos.f[2]; f32 d = -b * dir->f[2] + c * dir->f[0]; speed.f[0] += a * dir->f[0] * 0.2f; speed.f[2] += a * dir->f[2] * 0.2f; objApplyMomentum(obj, &speed, a * d * 0.0001f, true, addrotation); } void objDetach(struct prop *prop) { struct prop *parent = prop->parent; if (parent) { struct defaultobj *obj = prop->obj; struct model *model = obj->model; propDetach(prop); model->attachedtomodel = NULL; model->attachedtonode = NULL; obj->hidden &= ~OBJHFLAG_HASOWNER; if (parent->type & (PROPTYPE_CHR | PROPTYPE_PLAYER)) { struct chrdata *chr = parent->chr; if (chr) { if (prop == chr->weapons_held[HAND_RIGHT]) { chrSetFiring(chr, HAND_RIGHT, false); chr->weapons_held[HAND_RIGHT] = NULL; } if (prop == chr->weapons_held[HAND_LEFT]) { chrSetFiring(chr, HAND_LEFT, false); chr->weapons_held[HAND_LEFT] = NULL; } } } } } bool objDrop(struct prop *prop, bool lazy) { struct prop *parent = prop->parent; struct defaultobj *obj = prop->obj; struct prop *root; struct model *model; Mtxf spf0; struct coord spe4; s16 rooms[8]; if ((obj->hidden & OBJHFLAG_EMBEDDED) && obj->embedment->projectile) { struct projectile *projectile2 = obj->embedment->projectile; embedmentFree(obj->embedment); obj->projectile = projectile2; obj->hidden &= ~OBJHFLAG_EMBEDDED; obj->hidden |= OBJHFLAG_PROJECTILE; } if (parent && (obj->hidden & OBJHFLAG_PROJECTILE)) { struct projectile *projectile; model = obj->model; projectile = obj->projectile; root = parent; while (root->parent) { root = root->parent; } projectile->ownerprop = parent; projectile->flags |= PROJECTILEFLAG_AIRBORNE; if (projectile->droptype == DROPTYPE_5) { struct defaultobj *rootobj = root->obj; struct modelnode *node1; struct coord spb8; struct modelnode *node2; struct coord spa8; f32 spa4; f32 spa0; node1 = objFindBboxNode(obj); modelNodeGetPosition(obj->model, model0001a740(node1), &spb8); node2 = objFindBboxNode(rootobj); modelNodeGetPosition(rootobj->model, model0001a740(node2), &spa8); spe4.x = spb8.x - spa8.x; spe4.y = spb8.y - spa8.y; spe4.z = spb8.z - spa8.z; mtx00016208(rootobj->realrot, &spe4); spa4 = RANDOMFRAC() * 13.333333015442f; spa0 = atan2f(spe4.x, spe4.z); spa0 += RANDOMFRAC() * 0.7852731347084f - 0.3926365673542f; if (spa0 >= M_BADTAU) { spa0 -= M_BADTAU; } else if (spa0 < 0.0f) { spa0 += M_BADTAU; } projectile->speed.x += spa4 * sinf(spa0); projectile->speed.z += spa4 * cosf(spa0); mtx3ToMtx4(rootobj->realrot, &spf0); spe4.x += root->pos.x; spe4.y += root->pos.y; spe4.z += root->pos.z; mtx4SetTranslation(&spe4, &spf0); func0f065e74(&root->pos, root->rooms, &spe4, rooms); } else { if (projectile->droptype == DROPTYPE_SURRENDER && parent->type == PROPTYPE_CHR) { struct chrdata *chr = parent->chr; struct coord rot = {0, 0, 0}; f32 angle = chrGetInverseTheta(chr); projectile->speed.x = sinf(angle) * 1.6666666269302f; projectile->speed.y = -RANDOMFRAC() * 0.83333331346512f; projectile->speed.z = cosf(angle) * 1.6666666269302f; rot.x = RANDOMFRAC() * PALUPF(0.012269892729819f) - PALUPF(0.0061349463649094f); rot.y = RANDOMFRAC() * PALUPF(0.012269892729819f) - PALUPF(0.0061349463649094f); rot.z = RANDOMFRAC() * PALUPF(0.012269892729819f) - PALUPF(0.0061349463649094f); mtx4LoadRotation(&rot, (Mtxf *)&projectile->mtx); } else if (projectile->droptype == DROPTYPE_THROWGRENADE && parent->type == PROPTYPE_CHR) { struct chrdata *chr = parent->chr; struct coord rot = {0, 0, 0}; f32 angle = chrGetInverseTheta(chr); f32 dist; if (chr->aibot) { dist = chrGetDistanceToTarget(chr); } else { dist = chrGetAttackEntityDistance(chr, chr->act_throwgrenade.flags, chr->act_throwgrenade.entityid); } if (chr->aibot == NULL && dist < 300) { dist = 300; } projectile->speed.x = sinf(angle) * 13.333333015442f * (dist / 1000); projectile->speed.y = (((dist >= 1200) * ((dist - 1200) / 1200)) + 1) * 6.6666665077209f; projectile->speed.z = cosf(angle) * 13.333333015442f * (dist / 1000); rot.x = RANDOMFRAC() * PALUPF(0.012269892729819f) - PALUPF(0.0061349463649094f); rot.y = RANDOMFRAC() * PALUPF(0.012269892729819f) - PALUPF(0.0061349463649094f); rot.z = RANDOMFRAC() * PALUPF(0.012269892729819f) - PALUPF(0.0061349463649094f); mtx4LoadRotation(&rot, (Mtxf *)&projectile->mtx); projectileSetSticky(prop); } else if (projectile->droptype == DROPTYPE_HAT) { struct coord rot = {0, 0, 0}; struct prop *playerprop = g_Vars.currentplayer->prop; f32 x = parent->pos.x - playerprop->pos.x; f32 z = parent->pos.z - playerprop->pos.z; f32 angle = atan2f(x, z); projectile->speed.x = (RANDOMFRAC() * 3.3333332538605f + 3.3333332538605f) * sinf(angle); projectile->speed.y = RANDOMFRAC() * 3.3333332538605f; projectile->speed.z = (RANDOMFRAC() * 3.3333332538605f + 3.3333332538605f) * cosf(angle); rot.x = RANDOMFRAC() * PALUPF(0.049079570919275f) - PALUPF(0.024539785459638f); rot.y = RANDOMFRAC() * PALUPF(0.049079570919275f) - PALUPF(0.024539785459638f); rot.z = RANDOMFRAC() * PALUPF(0.049079570919275f) - PALUPF(0.024539785459638f); mtx4LoadRotation(&rot, (Mtxf *)&projectile->mtx); } else if (projectile->droptype == DROPTYPE_OWNERREAP) { struct coord rot = {0, 0, 0}; projectile->speed.x = (0.5f - RANDOMFRAC()) * 1.6666666269302f; projectile->speed.y = 0.0f; projectile->speed.z = (0.5f - RANDOMFRAC()) * 1.6666666269302f; rot.x = RANDOMFRAC() * PALUPF(0.049079570919275f) - PALUPF(0.024539785459638f); rot.y = RANDOMFRAC() * PALUPF(0.049079570919275f) - PALUPF(0.024539785459638f); rot.z = RANDOMFRAC() * PALUPF(0.049079570919275f) - PALUPF(0.024539785459638f); mtx4LoadRotation(&rot, (Mtxf *)&projectile->mtx); } else { // DROPTYPE_OWNERREAP func0f0964b4(&projectile->speed, (Mtxf *)&projectile->mtx); } if (!lazy && (prop->flags & PROPFLAG_ONTHISSCREENTHISTICK)) { // Do collision checks Mtxf *sp48 = model0001a60c(model); mtx00015be4(camGetProjectionMtxF(), sp48, &spf0); propSetPerimEnabled(root, false); spe4.x = spf0.m[3][0]; spe4.y = spf0.m[3][1]; spe4.z = spf0.m[3][2]; if (cdTestLos10(&root->pos, root->rooms, &spe4, rooms, CDTYPE_ALL, GEOFLAG_FLOOR1 | GEOFLAG_FLOOR2 | GEOFLAG_WALL) == CDRESULT_COLLISION || (projectile->flags & PROJECTILEFLAG_STICKY) == 0) { if (cdTestVolume(&spe4, objGetRadius(obj), rooms, CDTYPE_ALL, CHECKVERTICAL_NO, 0.0f, 0) == CDRESULT_COLLISION) { spf0.m[3][0] = root->pos.x; spf0.m[3][2] = root->pos.z; } } propSetPerimEnabled(root, true); prop->z = -sp48->m[3][2]; } else { // No collision checks mtx4LoadIdentity(&spf0); mtx00015f04(model->scale, &spf0); mtx4SetTranslation(&root->pos, &spf0); roomsCopy(root->rooms, rooms); } } objDetach(prop); propActivate(prop); propEnable(prop); prop->pos.x = spf0.m[3][0]; prop->pos.y = spf0.m[3][1]; prop->pos.z = spf0.m[3][2]; propDeregisterRooms(prop); roomsCopy(rooms, prop->rooms); spf0.m[3][0] = 0; spf0.m[3][1] = 0; spf0.m[3][2] = 0; mtx4ToMtx3(&spf0, obj->realrot); func0f069c70(obj, true, true); obj->shadecol[0] = obj->nextcol[0]; obj->shadecol[1] = obj->nextcol[1]; obj->shadecol[2] = obj->nextcol[2]; obj->shadecol[3] = obj->nextcol[3]; obj->hidden |= OBJHFLAG_SUSPICIOUS; if (obj->type == OBJTYPE_WEAPON) { struct weaponobj *weapon = (struct weaponobj *)obj; if (weapon->weaponnum == WEAPON_GRENADE && weapon->timer240 >= 0) { propSetDangerous(prop); } } return true; } return false; } /** * Make an object fall. Eg. due to it sitting on a table which is now destroyed, * or because it was a chopper that is now destroyed. */ void objFall(struct defaultobj *obj, s32 playernum) { if (obj->type == OBJTYPE_AUTOGUN && g_Vars.normmplayerisrunning) { // Don't set owner playernum } else { obj->hidden &= 0x0fffffff; obj->hidden |= (playernum << 28) & 0xf0000000; } if ((obj->flags2 & OBJFLAG2_00000100) == 0 && (obj->flags3 & OBJFLAG3_10000000) == 0 && (obj->flags & (OBJFLAG_00000001 | OBJFLAG_00000008)) && (obj->hidden & (OBJHFLAG_EMBEDDED | OBJHFLAG_PROJECTILE)) == 0) { struct coord rot = {0, 0, 0}; struct projectile *projectile = NULL; func0f0685e4(obj->prop); if (obj->hidden & OBJHFLAG_PROJECTILE) { projectile = obj->projectile; } if (projectile) { projectile->speed.x = RANDOMFRAC() * 1.6666666f - 0.8333333f; projectile->speed.y = RANDOMFRAC() * 3.3333333f + 1.6666666f; projectile->speed.z = RANDOMFRAC() * 1.6666666f - 0.8333333f; if ((obj->flags2 & OBJFLAG2_00000200) == 0) { #if PAL rot.x = RANDOMFRAC() * 0.0058895489f - 0.0029447745f; rot.y = RANDOMFRAC() * 0.0058895489f - 0.0029447745f; rot.z = RANDOMFRAC() * 0.0058895489f - 0.0029447745f; #else rot.x = RANDOMFRAC() * 0.0049079573f - 0.0024539786f; rot.y = RANDOMFRAC() * 0.0049079573f - 0.0024539786f; rot.z = RANDOMFRAC() * 0.0049079573f - 0.0024539786f; #endif } mtx4LoadRotation(&rot, &projectile->mtx); projectile->flags |= PROJECTILEFLAG_AIRBORNE; obj->flags &= ~OBJFLAG_00000100; obj->hidden &= ~OBJHFLAG_00008000; } } } /** * Destroy the objects that the given prop is supporting. * * For example, destroying a table will also destroy all the props that are * sitting on that table. */ void objDestroySupportedObjects(struct prop *tableprop, s32 playernum) { struct prop *prop; s16 *propnumptr; s16 propnums[256]; u8 *start; u8 *end; if (propUpdateGeometry(tableprop, &start, &end)) { roomGetProps(tableprop->rooms, propnums, 256); propnumptr = propnums; while (*propnumptr >= 0) { prop = &g_Vars.props[*propnumptr]; if (prop->type & (PROPTYPE_OBJ | PROPTYPE_WEAPON)) { struct defaultobj *obj = prop->obj; if (obj) { if (prop->pos.y > tableprop->pos.y && (obj->hidden & OBJHFLAG_00008000) && cd000266a4(prop->pos.x, prop->pos.z, (struct geo *)start)) { objFall(obj, playernum); } } } propnumptr++; } } } void objCheckDestroyed(struct defaultobj *obj, struct coord *pos, s32 playernum) { if (obj->damage > obj->maxdamage || objGetDestroyedLevel(obj)) { struct prop *prop = obj->prop; struct prop *rootprop = prop; s16 exptype = g_PropExplosionTypes[8 + obj->modelnum]; s16 rooms[8]; // If in Deep Sea outro if (g_Vars.tickmode == TICKMODE_CUTSCENE && g_CutsceneAnimNum == ANIM_CUT_PAM_OUTRO_CAM) { exptype = EXPLOSIONTYPE_24; } while (rootprop->parent) { rootprop = rootprop->parent; } if (objGetDestroyedLevel(obj) == 0) { // Obj is now destroyed obj->damage = 0; obj->hidden2 |= OBJH2FLAG_DESTROYED; func0f065e74(&rootprop->pos, rootprop->rooms, pos, rooms); explosionCreateComplex(prop, pos, rooms, exptype, playernum); if (obj->flags2 & OBJFLAG2_REMOVEWHENDESTROYED) { obj->hidden |= OBJHFLAG_REAPABLE; } else if (obj->type == OBJTYPE_CHOPPER) { struct chopperobj *chopper = (struct chopperobj *)obj; objFall(obj, playernum); chopper->attackmode = CHOPPERMODE_FALL; chopper->timer60 = (obj->flags & OBJFLAG_CHOPPER_INACTIVE) ? 0 : 2; obj->flags &= ~OBJFLAG_CHOPPER_INACTIVE; } else if (obj->type == OBJTYPE_HOVERCAR) { struct hovercarobj *hovercar = (struct hovercarobj *)obj; struct coord sp40; struct coord sp34; if (obj->flags & OBJFLAG_HOVERCAR_ISHOVERBOT) { // Hoverbots bounce and spark when dead hovercar->dead = true; hovercar->deadtimer60 = TICKS(250); hovercar->sparkstimer60 = TICKS(50); bgunCalculatePlayerShotSpread(&sp40, &sp34, HAND_RIGHT, false); objBounce(obj, &sp34); } else if (obj->modelnum == MODEL_TAXICAB || obj->modelnum == MODEL_POLICECAR) { // Taxi and police car (limo) fall to the ground and retain collision objDeform(obj, 10); hovercar->dead = true; hovercar->speed = 0.0f; hovercar->speedtime60 = 0.0f; hovercar->speedaim += 10.0f; } else { // Cars in Defection "explode" but actually warp to their next path explosionCreateSimple(prop, &prop->pos, prop->rooms, exptype, playernum); hovercarStartNextPath(hovercar); } } else { // Other objects objDeform(obj, 1); if (rootprop == prop) { objDestroySupportedObjects(prop, playernum); if ((obj->hidden & OBJHFLAG_00008000) == 0) { obj->hidden |= OBJHFLAG_00010000; objFall(obj, playernum); } } } } else { s32 shotstaken = objGetShotsTaken(obj); if ((shotstaken % 4) == 0) { if (obj->type != OBJTYPE_CHOPPER && obj->type != OBJTYPE_HOVERCAR) { objDeform(obj, (shotstaken >> 2) + 1); } func0f065e74(&rootprop->pos, rootprop->rooms, pos, rooms); if (exptype != EXPLOSIONTYPE_NONE) { explosionCreateSimple(prop, pos, rooms, EXPLOSIONTYPE_6, playernum); } } if (objGetDestroyedLevel(obj) > 0) { if (obj->hidden2 & OBJH2FLAG_CANREGEN) { if (obj->hidden & OBJHFLAG_00001000) { obj->hidden2 |= OBJH2FLAG_10; } else { obj->hidden2 &= ~OBJH2FLAG_10; } prop->timetoregen = TICKS(1200); } } if (shotstaken >= 12) { obj->hidden |= OBJHFLAG_00001000; if ((obj->flags3 & OBJFLAG3_10000000) == 0) { obj->flags &= ~OBJFLAG_00000100; } } } } } bool func0f084594(struct model *model, struct modelnode *node, struct coord *arg2, struct coord *arg3, struct hitthing *hitthing, s32 *mtxindexptr, struct modelnode **nodeptr) { s32 i; s32 mtxindex; bool ok; bool reset; s32 maxindex; struct modelrodata_bbox *rodata; s8 spc4[3]; struct coord spb8; struct coord spac; struct coord min; struct coord max; struct coord sp88; struct coord sp7c; Mtxf mtx; rodata = &node->rodata->bbox; mtxindex = model0001a524(node, 0); mtx000172f0(model->matrices[mtxindex].m, mtx.m); spb8 = *arg2; mtx4TransformVecInPlace(&mtx, &spb8); spac = *arg3; mtx4RotateVecInPlace(&mtx, &spac); if (var8005efc0 != 0.0f) { min.x = rodata->xmin - var8005efc0; min.y = rodata->ymin - var8005efc0; min.z = rodata->zmin - var8005efc0; max.x = rodata->xmax + var8005efc0; max.y = rodata->ymax + var8005efc0; max.z = rodata->zmax + var8005efc0; } else { min.x = rodata->xmin; min.y = rodata->ymin; min.z = rodata->zmin; max.x = rodata->xmax; max.y = rodata->ymax; max.z = rodata->zmax; } ok = true; reset = true; hitthing->unk28 = 0; for (i = 0; i < 3; i++) { if (spb8.f[i] < min.f[i]) { spc4[i] = 1; sp88.f[i] = min.f[i]; reset = false; } else if (spb8.f[i] > max.f[i]) { spc4[i] = 0; sp88.f[i] = max.f[i]; reset = false; } else { spc4[i] = 2; } } if (reset) { for (i = 0; i < 3; i++) { hitthing->unk00.f[i] = spb8.f[i]; } hitthing->unk0c.x = 0.0f; hitthing->unk0c.y = 1.0f; hitthing->unk0c.z = 0.0f; hitthing->unk28 = 2; } else { for (i = 0; i < 3; i++) { if (spc4[i] != 2 && spac.f[i] != 0.0f) { sp7c.f[i] = (sp88.f[i] - spb8.f[i]) / spac.f[i]; } else { sp7c.f[i] = -1.0f; } } maxindex = 0; for (i = 1; i < 3; i++) { if (sp7c.f[i] > sp7c.f[maxindex]) { maxindex = i; } } if (sp7c.f[maxindex] < 0.0f) { ok = false; } else { for (i = 0; i < 3; i++) { if (maxindex != i) { hitthing->unk00.f[i] = spb8.f[i] + sp7c.f[maxindex] * spac.f[i]; if (hitthing->unk00.f[i] < min.f[i] || hitthing->unk00.f[i] > max.f[i]) { ok = false; } } else { hitthing->unk00.f[i] = sp88.f[i]; } } if (ok) { hitthing->unk28 = maxindex * 2; if (spc4[maxindex] == 0) { hitthing->unk28 = (maxindex << 2) + 2; for (i = 0; i < 3; i++) { hitthing->unk0c.f[i] = (i != maxindex ? 0.0f : 1.0f); } } else { hitthing->unk28 = maxindex << 2; for (i = 0; i < 3; i++) { hitthing->unk0c.f[i] = (i != maxindex ? 0.0f : -1.0f); } } } } } if (ok) { *mtxindexptr = mtxindex; *nodeptr = node; } return ok; } bool func0f0849dc(struct model *model, struct modelnode *nodearg, struct coord *arg2, struct coord *arg3, struct hitthing *hitthing, s32 *dstmtxindex, struct modelnode **dstnode) { struct coord spec; struct coord spe0; struct coord spd4; Mtxf *spd0 = NULL; bool done = false; struct modelnode *node = nodearg; struct gfxvtx *vertices = NULL; while (node && !done) { u32 type = node->type & 0xff; Gfx *s3 = NULL; void *s5 = NULL; switch (type) { case MODELNODETYPE_DL: { struct modelrodata_dl *rodata = &node->rodata->dl; struct modelrwdata_dl *rwdata = modelGetNodeRwData(model, node); if (rwdata->gdl != NULL) { if (rwdata->gdl == rodata->primary) { s3 = (Gfx *)((u32)rodata->colourtable + ((u32)rodata->primary & 0xffffff)); } else { s3 = rwdata->gdl; } if (rodata->secondary != NULL) { s5 = (void *)((u32)rodata->colourtable + ((u32)rodata->secondary & 0xffffff)); } vertices = rwdata->vertices; } } break; case MODELNODETYPE_GUNDL: { struct modelrodata_gundl *rodata = &node->rodata->gundl; if (rodata->primary != NULL) { s3 = (Gfx *)((u32)rodata->baseaddr + ((u32)rodata->primary & 0xffffff)); if (rodata->secondary != NULL) { s5 = (Gfx *)((u32)rodata->baseaddr + ((u32)rodata->secondary & 0xffffff)); } vertices = (void *)(u32)rodata->baseaddr; } } break; case MODELNODETYPE_DISTANCE: model0001c784(model, node); break; case MODELNODETYPE_TOGGLE: model0001c7d0(model, node); break; case MODELNODETYPE_HEADSPOT: modelAttachHead(model, node); break; } if (s3 != NULL) { s32 mtxindex = model0001a524(node, 0); Mtxf *mtx = NULL; Mtxf sp64; if (mtxindex >= 0) { mtx = &model->matrices[mtxindex]; } if (mtx && mtx != spd0) { spd0 = mtx; mtx000172f0(mtx->m, sp64.m); spec = *arg2; mtx4TransformVecInPlace(&sp64, &spec); spd4 = *arg3; mtx4RotateVecInPlace(&sp64, &spd4); spe0.x = spd4.x * 32767.0f + spec.x; spe0.y = spd4.y * 32767.0f + spec.y; spe0.z = spd4.z * 32767.0f + spec.z; } if (bgTestHitOnObj(&spec, &spe0, &spd4, s3, s5, vertices, hitthing)) { *dstmtxindex = mtxindex; *dstnode = node; done = true; } } if (node->child) { node = node->child; } else { while (node) { if (node == nodearg) { node = NULL; break; } if (node->next) { node = node->next; break; } node = node->parent; } } } return done; } void glassDestroy(struct defaultobj *obj) { struct prop *prop = obj->prop; struct modelrodata_bbox *bbox = objFindBboxRodata(obj); wallhitsFreeByProp(prop, 0); wallhitsFreeByProp(prop, 1); if (obj->modelnum == MODEL_AIVILLABOT1 || obj->modelnum == MODEL_AIVILLABOT2 || obj->modelnum == MODEL_AIVILLABOT3) { shardsCreate(&prop->pos, &obj->realrot[0][0], &obj->realrot[1][0], &obj->realrot[2][0], bbox->xmin, bbox->xmax, bbox->ymin, bbox->ymax, SHARDTYPE_BOTTLE, prop); } else { shardsCreate(&prop->pos, &obj->realrot[0][0], &obj->realrot[1][0], &obj->realrot[2][0], bbox->xmin, bbox->xmax, bbox->ymin, bbox->ymax, SHARDTYPE_GLASS, prop); } obj->damage = 0; obj->hidden |= OBJHFLAG_REAPABLE; obj->hidden2 |= OBJH2FLAG_DESTROYED; } void doorDestroyGlass(struct doorobj *door) { struct modelnode *node; bool closed; struct prop *prop = door->base.prop; struct model *model = door->base.model; union modelrodata *rodata; union modelrwdata *rwdata; Mtxf matrix; rodata = modelGetPartRodata(model->filedata, 2); if (door->portalnum >= 0) { // @bug: Firing three shots at door glass is supposed to destroy it, // and this function is called when the third shot happens, but if it's // shot from a long distance (such that the glass is opaque) then this // function returns early and the glass remains in place, despite // setting the portal as unblocked. On the fourth shot // PORTALFLAG_FORCEOPEN is already set, so closed is false and the // glass is destroyed. closed = PORTAL_IS_CLOSED(door->portalnum); g_BgPortals[door->portalnum].flags |= PORTALFLAG_FORCEOPEN; if (closed) { return; } } func0f08c424(door, &matrix); shardsCreate((struct coord *) &matrix.m[3][0], &matrix.m[0][0], &matrix.m[1][0], &matrix.m[2][0], rodata->bbox.xmin, rodata->bbox.xmax, rodata->bbox.ymin, rodata->bbox.ymax, SHARDTYPE_GLASS, prop); wallhitsFreeByProp(prop, 1); node = modelGetPart(model->filedata, 1); rwdata = modelGetNodeRwData(model, node); rwdata->toggle.visible = false; } void func0f085050(struct prop *prop, f32 damage, struct coord *pos, s32 arg3, s32 playernum) { struct defaultobj *obj = prop->obj; if (obj->type == OBJTYPE_AUTOGUN && g_Vars.normmplayerisrunning) { // do nothing } else { obj->hidden &= 0x0fffffff; obj->hidden |= (playernum << 28) & 0xf0000000; } if ((obj->hidden & OBJHFLAG_HASOWNER) == 0) { struct prop *child = prop->child; while (child) { struct prop *next = child->next; func0f085050(child, damage, pos, arg3, playernum); child = next; } objDamage(prop->obj, damage, pos, arg3, playernum); } } bool func0f085194(struct defaultobj *obj) { switch (obj->type) { case OBJTYPE_KEY: case OBJTYPE_AMMOCRATE: case OBJTYPE_WEAPON: case OBJTYPE_HAT: case OBJTYPE_MULTIAMMOCRATE: case OBJTYPE_SHIELD: case OBJTYPE_ESCASTEP: return true; } return false; } bool objIsMortal(struct defaultobj *obj) { if (obj->type == OBJTYPE_DOOR) { return false; } if (func0f085194(obj) && obj->type != OBJTYPE_SHIELD) { if ((obj->flags & OBJFLAG_00010000) == 0) { return false; } } else if (obj->flags & OBJFLAG_INVINCIBLE) { return false; } return true; } void objTakeGunfire(struct defaultobj *obj, f32 damage, struct coord *pos, s32 weaponnum, s32 playernum) { if ((obj->flags2 & OBJFLAG2_IMMUNETOGUNFIRE) == 0) { objDamage(obj, damage, pos, weaponnum, playernum); } } void objDamage(struct defaultobj *obj, f32 damage, struct coord *pos, s32 weaponnum, s32 playernum) { // Store the attacker playernum into the object's "hidden" field // ...but not for deployed laptop guns in multiplayer, because those bits // likely designate the owner of the gun if (obj->type != OBJTYPE_AUTOGUN || !g_Vars.normmplayerisrunning) { obj->hidden &= 0x0fffffff; obj->hidden |= (playernum << 28) & 0xf0000000; } if (weaponnum == WEAPON_NONE) { if (func0f085194(obj)) { return; } if (obj->flags & OBJFLAG_01000000) { return; } } else { if (obj->flags & OBJFLAG_INVINCIBLE) { return; } if (obj->type == OBJTYPE_WEAPON) { struct weaponobj *weapon; if (obj->flags2 & OBJFLAG2_AICANNOTUSE) { propExplode(obj->prop, EXPLOSIONTYPE_12); obj->hidden |= OBJHFLAG_REAPABLE; } // If damaging an explosive item, make it explode immediately by // zeroing its timer weapon = (struct weaponobj *) obj; if (weapon->weaponnum == WEAPON_GRENADE || weapon->weaponnum == WEAPON_TIMEDMINE || weapon->weaponnum == WEAPON_REMOTEMINE || weapon->weaponnum == WEAPON_PROXIMITYMINE || weapon->weaponnum == WEAPON_ROCKET || weapon->weaponnum == WEAPON_HOMINGROCKET || weapon->weaponnum == WEAPON_GRENADEROUND || (weapon->weaponnum == WEAPON_DRAGON && weapon->gunfunc == FUNC_SECONDARY)) { // Homing rockets are immune to remote mines? Or maybe they just // don't explode because the mine is exploding anyway if (weapon->weaponnum != WEAPON_HOMINGROCKET || weaponnum != WEAPON_REMOTEMINE) { weapon->timer240 = 0; } } return; } if (obj->type == OBJTYPE_AMMOCRATE || obj->type == OBJTYPE_MULTIAMMOCRATE) { // @bug: It's not safe to cast a multiammocrateobj to an ammocrateobj. // For multiammocrateobjs, this is reading the first model/qty pair // as a single word... this turns out to not have any bad effects. struct ammocrateobj *crate = (struct ammocrateobj *) obj; if (crate->ammotype == AMMOTYPE_GRENADE || crate->ammotype == AMMOTYPE_ROCKET || crate->ammotype == AMMOTYPE_HOMINGROCKET || crate->ammotype == AMMOTYPE_REMOTE_MINE || crate->ammotype == AMMOTYPE_PROXY_MINE || crate->ammotype == AMMOTYPE_TIMED_MINE || crate->ammotype == AMMOTYPE_DEVASTATOR) { obj->flags |= OBJFLAG_AMMOCRATE_EXPLODENOW; } return; } if (!objIsMortal(obj)) { return; } } // Tweak damage and apply it if (obj->type == OBJTYPE_CHOPPER) { damage *= 0.25f; } if (objGetDestroyedLevel(obj) == 0) { // Not destroyed damage *= 250; if (damage < 1) { damage = 1; } } else { // Figure out how much to go within this destroyed level and cap it there f32 max = 4 - (objGetShotsTaken(obj) % 4); if (damage > max) { damage = max; } else if (damage < 1) { damage = 1; } } damage = obj->damage + damage; if (damage >= 32767) { obj->damage = 32767; } else { obj->damage = damage; } // Handle objects that do things when damaged if (obj->modelnum == MODEL_TARGET) { // empty } else { if (obj->type == OBJTYPE_GLASS || obj->type == OBJTYPE_TINTEDGLASS) { if (obj->damage >= obj->maxdamage) { glassDestroy(obj); } } else { objSetDropped(obj->prop, DROPTYPE_DEFAULT); objCheckDestroyed(obj, pos, playernum); } // This code appears to be unused... // It appears to handle spawning a weapon when the ammo crate is shot. if (obj->type == OBJTYPE_MULTIAMMOCRATE) { if (objGetDestroyedLevel(obj) == 1) { u32 stack; struct multiammocrateobj *crate = (struct multiammocrateobj *) obj; s32 startindex = random() % ARRAYCOUNT(crate->slots); s32 i = startindex; do { if (crate->slots[i].quantity > 0 && crate->slots[i].modelnum != 0xffff) { struct ammocrateobj *newcrate = ammocrateAllocate(); if (newcrate) { s32 modelnum = crate->slots[i].modelnum; struct defaultobj tmp = { 256, // extrascale 0, // hidden2 OBJTYPE_AMMOCRATE, // type 0, // modelnum -1, // pad OBJFLAG_00000001, // flags 0, // flags2 0, // flags3 NULL, // prop NULL, // model 1, 0, 0, // realrot 0, 1, 0, 0, 0, 1, 0, // hidden NULL, // geo NULL, // projectile 0, // damage 1000, // maxdamage 0xff, 0xff, 0xff, 0x00, // shadecol 0xff, 0xff, 0xff, 0x00, // nextcol 0x0fff, // floorcol 0, // tiles }; newcrate->base = tmp; newcrate->base.modelnum = modelnum; newcrate->ammotype = i + 1; if (objInitWithModelDef(&newcrate->base, g_ModelStates[modelnum].filedata)) { propReparent(newcrate->base.prop, obj->prop); } break; } } i = (i + 1) % ARRAYCOUNT(crate->slots); } while (i != startindex); } } else if (obj->type == OBJTYPE_CHOPPER) { struct chopperobj *chopper = (struct chopperobj *) obj; if (chopper->attackmode != CHOPPERMODE_FALL) { chopper->attackmode = CHOPPERMODE_COMBAT; } } else if (obj->type == OBJTYPE_AUTOGUN) { obj->flags |= OBJFLAG_AUTOGUN_DAMAGED; if (objGetDestroyedLevel(obj) == 1) { obj->flags |= OBJFLAG_DEACTIVATED; } } else if (obj->type == OBJTYPE_CCTV) { if (objGetDestroyedLevel(obj) == 1) { obj->flags |= OBJFLAG_DEACTIVATED; } } else if (obj->type == OBJTYPE_SINGLEMONITOR) { struct singlemonitorobj *monitor = (struct singlemonitorobj *) obj; if (objGetDestroyedLevel(obj) == 1) { tvscreenSetCmdlist(&monitor->screen, g_TvCmdlist14); } } else if (obj->type == OBJTYPE_MULTIMONITOR) { struct multimonitorobj *monitor = (struct multimonitorobj *) obj; if (objGetDestroyedLevel(obj) == 1) { tvscreenSetCmdlist(&monitor->screens[0], g_TvCmdlist14); tvscreenSetCmdlist(&monitor->screens[1], g_TvCmdlist14); tvscreenSetCmdlist(&monitor->screens[2], g_TvCmdlist14); tvscreenSetCmdlist(&monitor->screens[3], g_TvCmdlist14); } } else if (obj->type == OBJTYPE_SHIELD) { struct shieldobj *shield = (struct shieldobj *) obj; if (objGetDestroyedLevel(obj) == 0) { shield->amount = shield->initialamount * (f32)(obj->maxdamage - obj->damage) / (f32)obj->maxdamage; } else { shield->amount = 0; } } if (objGetDestroyedLevel(obj) == 1) { struct prop *child = obj->prop->child; while (child) { struct prop *next = child->next; objSetDropped(child, DROPTYPE_DEFAULT); child = next; } } } } void func0f0859a0(struct prop *prop, struct shotdata *shotdata) { struct defaultobj *obj = prop->obj; struct model *model = obj->model; struct prop *child; struct modelnode *node1 = NULL; struct hitthing hitthing1; s32 spe4; struct coord spd8; f32 spd4; struct modelnode *node2; s32 lVar3; bool isnotglass; struct modelnode *node3; struct hitthing hitthing2; s32 sp90; struct modelnode *node4; struct prop *next; struct coord sp7c; struct coord sp70; if (obj->hidden & OBJHFLAG_00001000) { return; } if ((prop->flags & PROPFLAG_ONTHISSCREENTHISTICK) == 0) { return; } child = prop->child; while (child) { next = child->next; func0f0859a0(child, shotdata); child = next; } if (var8005efc0 > 0.0f) { lVar3 = model000225d4(model, &shotdata->unk00, &shotdata->unk0c, &node1); while (lVar3 > 0) { if (func0f084594(model, node1, &shotdata->unk00, &shotdata->unk0c, &hitthing1, &spe4, &node2)) { break; } lVar3 = model000225d4(model, &shotdata->unk00, &shotdata->unk0c, &node1); } } else { do { lVar3 = model000225d4(model, &shotdata->unk00, &shotdata->unk0c, &node1); if (lVar3 > 0 && func0f0849dc(model, node1, &shotdata->unk00, &shotdata->unk0c, &hitthing1, &spe4, &node2)) { break; } } while (lVar3 > 0); } if (obj->flags3 & OBJFLAG3_HOVERBEDSHIELD) { node3 = modelGetPart(model->filedata, MODELPART_0067); if (node3 && func0f084594(model, node3, &shotdata->unk00, &shotdata->unk0c, &hitthing2, &sp90, &node4)) { if (lVar3 <= 0 || model->matrices[sp90].m[0][2] * hitthing2.unk00.f[0] + model->matrices[sp90].m[1][2] * hitthing2.unk00.f[1] + model->matrices[sp90].m[2][2] * hitthing2.unk00.f[2] > model->matrices[spe4].m[0][2] * hitthing1.unk00.f[0] + model->matrices[spe4].m[1][2] * hitthing1.unk00.f[1] + model->matrices[spe4].m[2][2] * hitthing1.unk00.f[2]) { lVar3 = 1; hitthing1 = hitthing2; node1 = node3; spe4 = sp90; node2 = node4; hitthing1.texturenum = 10000; } } } if (lVar3 > 0) { mtx4TransformVec(&model->matrices[spe4], &hitthing1.unk00, &spd8); spd4 = -spd8.f[2]; if (spd4 <= shotdata->unk34) { isnotglass = true; if ((obj->flags & OBJFLAG_INVINCIBLE) == 0) { if (obj->type == OBJTYPE_GLASS || obj->type == OBJTYPE_TINTEDGLASS) { isnotglass = false; } else if (obj->model->filedata->skel == &g_SkelWindowedDoor && modelGetPart(obj->model->filedata, MODELPART_WINDOWEDDOOR_0003) == node2) { isnotglass = false; } } mtx4TransformVec(camGetProjectionMtxF(), &spd8, &sp7c); mtx4RotateVec(&model->matrices[spe4], &hitthing1.unk0c, &sp70); mtx4RotateVecInPlace(camGetProjectionMtxF(), &sp70); func0f061fa8(shotdata, prop, spd4, lVar3, node1, &hitthing1, spe4, node2, model, isnotglass && shotdata->gset.weaponnum != WEAPON_FARSIGHT, (obj->flags2 & OBJFLAG2_BULLETPROOF) && shotdata->gset.weaponnum != WEAPON_DY357MAGNUM && shotdata->gset.weaponnum != WEAPON_FARSIGHT, &sp7c, &sp70); } } } void func0f085e00(struct prop *prop, struct shotdata *shotdata) { f32 tmp; struct defaultobj *obj = prop->obj; struct model *model = obj->model; struct modelrodata_bbox *bbox = objFindBboxRodata(obj); if ((prop->flags & PROPFLAG_ONTHISSCREENTHISTICK) && (obj->hidden & OBJHFLAG_00001000) == 0 && (obj->flags2 & OBJFLAG2_SHOOTTHROUGH) == 0) { tmp = -(model->matrices[0].m[3][2] + objGetRotatedLocalZMaxByMtx4(bbox, model->matrices)); if (tmp <= shotdata->unk34) { func0f0859a0(prop, shotdata); } } } void objHit(struct shotdata *shotdata, struct hit *hit) { struct defaultobj *obj; struct coord sp110; struct prop *prop; u8 isclosefunc = false; s32 i; bool explosiveshells = false; bool spfc = hit->unk4c; struct weaponfunc *func = gsetGetWeaponFunction(&shotdata->gset); struct coord spec; f32 tmp; struct prop *spe4; s16 textureindex; s16 spdc[3]; if (func != NULL) { if ((func->type & 0xff) == INVENTORYFUNCTYPE_CLOSE) { isclosefunc = true; } if (func->flags & FUNCFLAG_EXPLOSIVESHELLS) { explosiveshells = true; } } prop = hit->prop; while (prop->parent) { prop = prop->parent; } obj = hit->prop->obj; sp110.x = shotdata->unk00.x - hit->distance * shotdata->unk0c.x / shotdata->unk0c.z; sp110.y = shotdata->unk00.y - hit->distance * shotdata->unk0c.y / shotdata->unk0c.z; sp110.z = shotdata->unk00.z - hit->distance; mtx4TransformVecInPlace(camGetProjectionMtxF(), &sp110); if (!spfc && chrIsUsingPaintball(g_Vars.currentplayer->prop->chr)) { spfc = true; } if (hit->unk4c) { bgunSetHitPos(&sp110); } if (obj->modelnum == MODEL_TARGET) { if (hit->hitthing.texturenum == TEXTURE_0B9E) { frCalculateHit(obj, &sp110, shotdata->gset.unk063a); } else if ((shotdata->gset.weaponnum != WEAPON_CALLISTO || shotdata->gset.weaponfunc != FUNC_SECONDARY) && shotdata->gset.weaponnum != WEAPON_FARSIGHT) { // For some penetrating weapons, unset hits beyond the shot distance? spe4 = hit->prop; mtx4TransformVec(obj->model->matrices, &sp110, &spec); tmp = -spec.z; for (i = 0; i < 10; i++) { if (shotdata->hits[i].prop && shotdata->hits[i].prop != spe4 && shotdata->hits[i].distance > tmp) { shotdata->hits[i].prop = NULL; } } shotdata->unk34 = tmp; } } // Create sparks if (!isclosefunc) { if (chrIsUsingPaintball(g_Vars.currentplayer->prop->chr)) { sparksCreate(prop->rooms[0], prop, &sp110, 0, 0, SPARKTYPE_PAINT); } else { sparksCreate(prop->rooms[0], prop, &sp110, 0, 0, SPARKTYPE_DEFAULT); } } // Create shield hit if object is shielded if (hit->hitthing.texturenum == 10000) { spdc[0] = hit->hitthing.unk00.x; spdc[1] = hit->hitthing.unk00.y; spdc[2] = hit->hitthing.unk00.z; shieldhitCreate(prop, (obj->flags3 & OBJFLAG3_SHOWSHIELD) ? 4.0f : 8.0f, hit->prop, hit->node, hit->model, hit->hitthing.unk28 / 2, spdc); } // Increment object hit count if (objIsHealthy(obj) && objIsMortal(obj) && hit->unk4c) { mpstatsIncrementPlayerShotCount2(&shotdata->gset, SHOTREGION_OBJECT); } // Play hit sound if (!spfc) { bgunPlayGlassHitSound(&hit->prop->pos, hit->prop->rooms, hit->hitthing.texturenum); } else if (!isclosefunc) { bgunPlayPropHitSound(&shotdata->gset, hit->prop, hit->hitthing.texturenum); } // Create wall hit (bullet hole) if (!isclosefunc && hit->hitthing.texturenum != 10000 && shotdata->gset.weaponnum != WEAPON_UNARMED && shotdata->gset.weaponnum != WEAPON_LASER && shotdata->gset.weaponnum != WEAPON_TRANQUILIZER && shotdata->gset.weaponnum != WEAPON_FARSIGHT) { if (hit->unk4c == 0) { struct prop *hitprop = hit->prop; s8 iswindoweddoor = obj->model->filedata->skel == &g_SkelWindowedDoor ? true : false; textureindex = WALLHITTEX_GLASS1 + (random() % 3); if ((obj->type == OBJTYPE_DOOR && !iswindoweddoor) || (obj->flags & OBJFLAG_INVINCIBLE) || (obj->flags2 & OBJFLAG2_IMMUNETOGUNFIRE)) { // Use a bulletproof glass texture textureindex += 10; } wallhitCreate(&hit->hitthing.unk00, &hit->hitthing.unk0c, &shotdata->gunpos, 0, 0, textureindex, 1, hitprop, hit->mtxindex, iswindoweddoor, g_Vars.currentplayer->prop->chr, true); } else { s16 textureindex; struct surfacetype *surfacetype; s32 spcc; s8 spcb = false; bool spc4; if (hit->hitthing.texturenum < 0 || hit->hitthing.texturenum >= NUM_TEXTURES) { surfacetype = g_SurfaceTypes[0]; } else if (g_Textures[hit->hitthing.texturenum].surfacetype < 15) { surfacetype = g_SurfaceTypes[g_Textures[hit->hitthing.texturenum].surfacetype]; } else { surfacetype = g_SurfaceTypes[0]; } if (surfacetype->numwallhittexes > 0) { spc4 = false; spcc = random() % surfacetype->numwallhittexes; if (obj->model->filedata->skel == &g_SkelWindowedDoor && hit->unk44 == modelGetPart(obj->model->filedata, MODELPART_WINDOWEDDOOR_0003)) { spcb = true; } textureindex = surfacetype->wallhittexes[spcc]; if (textureindex >= WALLHITTEX_GLASS1 && textureindex <= WALLHITTEX_GLASS3) { if (obj->type == OBJTYPE_DOOR || (obj->flags & OBJFLAG_INVINCIBLE) || (obj->flags2 & OBJFLAG2_IMMUNETOGUNFIRE)) { // Use a bulletproof glass texture textureindex += 10; } spc4 = true; } wallhitCreate(&hit->hitthing.unk00, &hit->hitthing.unk0c, &shotdata->gunpos, 0, 0, textureindex, 1, hit->prop, hit->mtxindex, spcb, g_Vars.currentplayer->prop->chr, spc4); } } } if (g_Vars.antiplayernum < 0 || g_Vars.currentplayer != g_Vars.anti || (obj->flags2 & OBJFLAG2_IMMUNETOANTI) == 0) { if (hit->hitthing.texturenum != 10000) { f32 damage = gsetGetDamage(&shotdata->gset); if (obj->type == OBJTYPE_AUTOGUN) { damage *= g_AutogunDamageRxScale; } else if (explosiveshells) { if (obj->type == OBJTYPE_GLASS || obj->type == OBJTYPE_TINTEDGLASS) { damage *= 100.0f; } else { damage *= 5.0f; } } objTakeGunfire(obj, damage, &sp110, shotdata->gset.weaponnum, g_Vars.currentplayernum); if (obj->model->filedata->skel == &g_SkelWindowedDoor && !hit->unk4c) { struct doorobj *door = (struct doorobj *)obj; door->glasshits++; if (door->glasshits >= 3) { doorDestroyGlass(door); } } } objDropRecursively(hit->prop, false); // Handle pushing and bouncing if ((obj->hidden & OBJHFLAG_MOUNTED) == 0 && (obj->hidden & OBJHFLAG_GRABBED) == 0) { if (obj->flags3 & OBJFLAG3_PUSHABLE) { struct coord spb0; struct coord spa4; struct coord pushdir; Mtxf sp58; spb0.x = shotdata->dir.x * 3.0f; spb0.y = shotdata->dir.y * 3.0f; spb0.z = shotdata->dir.z * 3.0f; mtx4MultMtx4(camGetProjectionMtxF(), &obj->model->matrices[hit->mtxindex], &sp58); mtx4TransformVec(&sp58, &hit->hitthing.unk00, &spa4); pushdir = shotdata->dir; func0f082e84(obj, &spa4, &pushdir, &spb0, true); } else { bool bounce = false; if (func0f085194(obj)) { if ((obj->flags & OBJFLAG_00400000) == 0) { bounce = true; } } else if (obj->flags & OBJFLAG_BOUNCEIFSHOT) { bounce = true; } if (obj->flags2 & OBJFLAG2_00000002) { if (!objIsHealthy(obj)) { bounce = true; } } if (obj->flags2 & OBJFLAG2_LINKEDTOSAFE) { bounce = false; } if (bounce) { objBounce(obj, &shotdata->unk0c); } } } } } u32 propobjGetCiTagId(struct prop *prop) { if (prop && g_Vars.stagenum == STAGE_CITRAINING) { u8 tags[8] = { 0x0e, 0x0f, 0x10, 0x47, 0x46, 0x45, 0x1b, 0x7f }; struct defaultobj *obj = prop->obj; u32 i; for (i = 0; i != 8; i++) { struct defaultobj *taggedobj = objFindByTagId(tags[i]); if (obj == taggedobj) { return tags[i]; } } } return 0; } bool objIsHealthy(struct defaultobj *obj) { return objGetDestroyedLevel(obj) == 0; } bool objTestForInteract(struct prop *prop) { u32 stack; struct defaultobj *obj = prop->obj; bool maybe = false; if (propobjGetCiTagId(prop)) { maybe = true; } else if (obj->type == OBJTYPE_ALARM || (obj->flags & OBJFLAG_THROWNLAPTOP) || (obj->flags3 & (OBJFLAG3_HTMTERMINAL | OBJFLAG3_INTERACTABLE)) || (obj->hidden & (OBJHFLAG_LIFTDOOR | OBJHFLAG_00000002))) { maybe = true; } else if (obj->type == OBJTYPE_HOVERBIKE) { if (g_Vars.currentplayer->bondmovemode == MOVEMODE_GRAB) { maybe = true; } else if (g_Vars.currentplayer->bondmovemode == MOVEMODE_WALK && bmoveGetCrouchPos() == CROUCHPOS_STAND && g_Vars.currentplayer->crouchoffset == 0.0f) { maybe = true; } } else if ((obj->flags3 & OBJFLAG3_GRABBABLE) && g_Vars.currentplayer->bondmovemode == MOVEMODE_WALK && bmoveGetCrouchPos() == CROUCHPOS_STAND && g_Vars.currentplayer->crouchoffset == 0.0f) { maybe = true; } if (maybe && (obj->hidden & OBJHFLAG_MOUNTED) && prop == bmoveGetHoverbike()) { maybe = false; } if (maybe && (prop->flags & PROPFLAG_ONTHISSCREENTHISTICK) && objIsHealthy(obj) && (obj->flags & OBJFLAG_CANNOT_ACTIVATE) == 0) { struct prop *playerprop = g_Vars.currentplayer->prop; f32 x = prop->pos.x - playerprop->pos.x; f32 y = prop->pos.y - playerprop->pos.y; f32 z = prop->pos.z - playerprop->pos.z; f32 range; if (obj->modelnum == MODEL_SK_SHUTTLE) { range = 500; } else if (obj->modelnum == MODEL_TAXICAB) { range = 300; } else if (obj->modelnum == MODEL_PRESCAPSULE) { range = 280; } else if (obj->flags3 & OBJFLAG3_INTERACTSHORTRANGE) { range = 100; } else { range = 200; } if (x * x + z * z < range * range && y < range && y > -range) { f32 angle = atan2f(x, z) - (360.0f - g_Vars.currentplayer->vv_theta) * M_BADTAU / 360.0f; if (angle < 0.0f) { angle += M_BADTAU; } if (angle > M_BADPI) { angle = M_BADTAU - angle; } if (angle <= 0.3926365673542f) { if ((obj->flags2 & OBJFLAG2_INTERACTCHECKLOS) == 0 || cdTestLos06(&playerprop->pos, playerprop->rooms, &prop->pos, prop->rooms, CDTYPE_BG)) { g_InteractProp = prop; } } } } return true; } bool currentPlayerTryMountHoverbike(struct prop *prop) { struct defaultobj *obj = prop->obj; bool mount = false; u32 stack[2]; if (obj->type == OBJTYPE_HOVERBIKE && g_Vars.lvframe60 - g_Vars.currentplayer->activatetimelast < TICKS(30) && (obj->hidden & OBJHFLAG_MOUNTED) == 0) { if (obj->hidden & OBJHFLAG_GRABBED) { if (bmoveGetGrabbedProp() == prop) { mount = true; } else { mount = false; } } else { mount = true; } } if (mount && g_Vars.currentplayer->bondmovemode != MOVEMODE_GRAB) { if (g_Vars.currentplayer->bondmovemode != MOVEMODE_WALK || bmoveGetCrouchPos() != CROUCHPOS_STAND || g_Vars.currentplayer->crouchoffset != 0) { mount = false; } } if (mount) { f32 angle = atan2f( prop->pos.x - g_Vars.currentplayer->prop->pos.x, prop->pos.z - g_Vars.currentplayer->prop->pos.z); angle -= hoverpropGetTurnAngle(obj); if (angle < 0) { angle += M_BADTAU; } if ((angle > 0.3926365673542f && angle < 2.3558194637299f) || (angle < 5.8895483016968f && angle > 3.9263656139374f)) { g_Vars.currentplayer->hoverbike = prop; bmoveSetMode(MOVEMODE_BIKE); return true; } } return false; } bool propobjInteract(struct prop *prop) { struct defaultobj *obj = prop->obj; bool result = false; u32 tag_id = propobjGetCiTagId(prop); if (tag_id) { // CI object - terminals etc u8 handled = false; if (ciIsTourDone()) { if (tag_id == 0x10) { struct trainingdata *data = dtGetData(); handled = true; if (data->intraining) { func0f0f85e0(&g_DtDetailsMenuDialog, MENUROOT_TRAINING); } else { func0f0f85e0(&g_DtListMenuDialog, MENUROOT_TRAINING); } } else if (tag_id == 0x45) { struct trainingdata *data = getHoloTrainingData(); handled = true; if (data->intraining) { func0f0f85e0(&g_HtDetailsMenuDialog, MENUROOT_TRAINING); } else { func0f0f85e0(&g_HtListMenuDialog, MENUROOT_TRAINING); } } else if (tag_id == 0x7f) { handled = true; if (frIsInTraining()) { func0f0f85e0(&g_FrTrainingInfoInGameMenuDialog, MENUROOT_TRAINING); } else { func0f0f85e0(&g_FrWeaponListMenuDialog, MENUROOT_TRAINING); } } } if (!handled) { if (tag_id == 0x0e) { handled = true; func0f0f85e0(&g_BioListMenuDialog, MENUROOT_TRAINING); } else if (tag_id == 0x0f) { handled = true; func0f0f85e0(&g_CheatsMenuDialog, MENUROOT_TRAINING); } else if (tag_id == 0x1b) { handled = true; func0f0f85e0(&g_FrWeaponsAvailableMenuDialog, MENUROOT_TRAINING); } else if (tag_id == 0x47) { handled = true; func0f0f85e0(&g_CiMenuViaPcMenuDialog, MENUROOT_MAINMENU); } else if (tag_id == 0x46) { handled = true; func0f0f85e0(&g_HangarListMenuDialog, MENUROOT_TRAINING); } } if (handled) { // Typing sound sndStart(var80095200, SFX_TYPING_8118, NULL, -1, -1, -1, -1, -1); } func0f0fd494(&prop->pos); } else if (obj->type == OBJTYPE_ALARM) { // Button press sound sndStart(var80095200, SFX_PRESS_SWITCH, NULL, -1, -1, -1, -1, -1); if (alarmIsActive()) { alarmDeactivate(); } else { alarmActivate(); } } else if (obj->flags & OBJFLAG_THROWNLAPTOP) { // Thrown laptop if (obj->type == OBJTYPE_AUTOGUN) { struct autogunobj *laptop = (struct autogunobj *)obj; s32 playernum; if (g_Vars.normmplayerisrunning) { playernum = mpPlayerGetIndex(g_Vars.currentplayer->prop->chr); } else { playernum = g_Vars.currentplayernum; } if (playernum >= 0 && laptop == &g_ThrownLaptops[playernum]) { obj->hidden |= OBJHFLAG_REAPABLE; invGiveSingleWeapon(WEAPON_LAPTOPGUN); currentPlayerQueuePickupWeaponHudmsg(WEAPON_LAPTOPGUN, false); weaponPlayPickupSound(WEAPON_LAPTOPGUN); if (laptop->ammoquantity > 0 && laptop->ammoquantity != 255) { s32 newqty = bgunGetAmmoQtyForWeapon(WEAPON_LAPTOPGUN, FUNC_PRIMARY) + laptop->ammoquantity; bgunSetAmmoQtyForWeapon(WEAPON_LAPTOPGUN, FUNC_PRIMARY, newqty); } } } else { result = propPickupByPlayer(prop, 1); } } else if (currentPlayerTryMountHoverbike(prop) == false && (obj->flags3 & OBJFLAG3_GRABBABLE) && g_Vars.currentplayer->bondmovemode == MOVEMODE_WALK && bmoveGetCrouchPos() == CROUCHPOS_STAND && g_Vars.currentplayer->crouchoffset == 0 && g_Vars.currentplayer->onladder == false) { bmoveGrabProp(prop); } if (g_Vars.normmplayerisrunning) { scenarioHandleActivatedProp(g_Vars.currentplayer->prop->chr, prop); } else { if (g_Vars.currentplayernum == g_Vars.coopplayernum) { obj->hidden |= OBJHFLAG_ACTIVATED_BY_COOP; } else if (g_Vars.currentplayernum == g_Vars.bondplayernum) { obj->hidden |= OBJHFLAG_ACTIVATED_BY_BOND; } } doorCallLift(prop, false); return result; } void objSetPerimEnabled(struct prop *prop, bool enable) { struct defaultobj *obj = prop->obj; if (enable) { obj->hidden &= ~OBJHFLAG_PERIMDISABLED; } else { obj->hidden |= OBJHFLAG_PERIMDISABLED; } } bool objUpdateGeometry(struct prop *prop, u8 **start, u8 **end) { struct defaultobj *obj = prop->obj; if (obj->unkgeo && (obj->flags3 & OBJFLAG3_WALKTHROUGH) == 0) { if ((obj->hidden2 & OBJH2FLAG_08)) { s32 len = (obj->flags3 & OBJFLAG3_GEOCYL) ? sizeof(struct geocyl) : sizeof(struct geoblock); if (obj->flags & OBJFLAG_00000100) { if ((obj->hidden & (OBJHFLAG_PERIMDISABLED | OBJHFLAG_DOORPERIMDISABLED)) == 0) { *start = (void *) obj->unkgeo; *end = (void *)((u32)obj->unkgeo + len); if (obj->geocount >= 2) { *end += obj->geocount * 0x40 - 0x40; } return true; } } if (obj->geocount >= 2) { *start = (void *)((u32)obj->unkgeo + len); *end = (void *)(*start + obj->geocount * 0x40 - 0x40); return true; } *end = NULL; *start = NULL; return false; } *start = (void *) obj->unkgeo; *end = (void *) ((u32)obj->unkgeo + obj->geocount * 0x40); return true; } *end = NULL; *start = NULL; return false; } void objGetBbox(struct prop *prop, f32 *radius, f32 *ymax, f32 *ymin) { struct defaultobj *obj = prop->obj; if (obj->unkgeo && obj->hidden2 & OBJH2FLAG_08) { if (obj->flags3 & OBJFLAG3_GEOCYL) { *radius = obj->geocyl->radius; *ymin = obj->geocyl->ymin; *ymax = obj->geocyl->ymax; } else { *radius = model0001af80(obj->model); *ymin = obj->geoblock->ymin; *ymax = obj->geoblock->ymax; } } else { *radius = 1; *ymin = 0; *ymax = 0; } } void ammotypeGetPickedUpText(char *dst) { strcat(dst, langGet(L_PROPOBJ_000)); // "Picked up" } void ammotypeGetDeterminer(char *dst, s32 ammotype, s32 qty) { bool determiner_a = false; bool determiner_an = false; bool determiner_some = false; bool determiner_the = false; s32 playercount = PLAYERCOUNT(); s32 full = playercount <= 2 && !(playercount == 2 && optionsGetScreenSplit() == SCREENSPLIT_VERTICAL); switch (ammotype) { case AMMOTYPE_CLOAK: determiner_a = true; break; case AMMOTYPE_PISTOL: case AMMOTYPE_SMG: case AMMOTYPE_RIFLE: case AMMOTYPE_SEDATIVE: case AMMOTYPE_PSYCHOSIS: case AMMOTYPE_PLASTIQUE: determiner_some = true; break; case AMMOTYPE_CROSSBOW: case AMMOTYPE_SHOTGUN: case AMMOTYPE_GRENADE: case AMMOTYPE_ROCKET: case AMMOTYPE_KNIFE: case AMMOTYPE_MAGNUM: case AMMOTYPE_DEVASTATOR: case AMMOTYPE_REMOTE_MINE: case AMMOTYPE_PROXY_MINE: case AMMOTYPE_TIMED_MINE: case AMMOTYPE_REAPER: case AMMOTYPE_HOMINGROCKET: case AMMOTYPE_DART: case AMMOTYPE_BOOST: case AMMOTYPE_BUG: case AMMOTYPE_MICROCAMERA: if (qty == 1) { determiner_a = true; } else { determiner_some = true; } break; case AMMOTYPE_FARSIGHT: case AMMOTYPE_NBOMB: case AMMOTYPE_ECM_MINE: if (qty == 1) { determiner_an = true; } else { determiner_some = true; } break; case AMMOTYPE_TOKEN: if (qty == 1) { determiner_the = true; } else { determiner_some = true; } break; } if (determiner_a) { if (full) { strcat(dst, langGet(L_PROPOBJ_004)); // "a" } else { strcat(dst, langGet(L_PROPOBJ_005)); // "A" } } if (determiner_an) { if (full) { strcat(dst, langGet(L_PROPOBJ_006)); // "an" } else { strcat(dst, langGet(L_PROPOBJ_007)); // "An" } } if (determiner_some) { if (full) { strcat(dst, langGet(L_PROPOBJ_002)); // "some" } else { strcat(dst, langGet(L_PROPOBJ_003)); // "Some" } } if (determiner_the) { if (full) { strcat(dst, langGet(L_PROPOBJ_008)); // "the" } else { strcat(dst, langGet(L_PROPOBJ_009)); // "The" } } } void ammotypeGetPickupName(char *dst, s32 ammotype2, s32 qty) { s32 ammotype = ammotype2; if (ammotype == AMMOTYPE_PISTOL || ammotype == AMMOTYPE_SMG || ammotype == AMMOTYPE_RIFLE) { strcat(dst, langGet(L_PROPOBJ_010)); // "ammo" } else if (ammotype == AMMOTYPE_KNIFE) { strcat(dst, langGet(L_PROPOBJ_021)); // "combat" if (qty == 1) { strcat(dst, langGet(L_PROPOBJ_022)); // "knife" } else { strcat(dst, langGet(L_PROPOBJ_023)); // "knives" } } else { s32 textnum = -1; switch (ammotype) { case AMMOTYPE_CROSSBOW: textnum = L_PROPOBJ_045; break; // "bolt" case AMMOTYPE_SHOTGUN: textnum = L_PROPOBJ_011; break; // "cartridge" case AMMOTYPE_FARSIGHT: textnum = L_PROPOBJ_046; break; // "orb" case AMMOTYPE_GRENADE: textnum = L_PROPOBJ_014; break; // "grenade" case AMMOTYPE_ROCKET: textnum = L_PROPOBJ_016; break; // "rocket" case AMMOTYPE_MAGNUM: textnum = L_PROPOBJ_012; break; // "magnum bullet" case AMMOTYPE_DEVASTATOR: textnum = L_PROPOBJ_015; break; // "grenade round" case AMMOTYPE_REMOTE_MINE: textnum = L_PROPOBJ_018; break; // "remote mine" case AMMOTYPE_PROXY_MINE: textnum = L_PROPOBJ_019; break; // "proximity mine" case AMMOTYPE_TIMED_MINE: textnum = L_PROPOBJ_020; break; // "timed mine" case AMMOTYPE_REAPER: textnum = L_PROPOBJ_047; break; // "Reaper ammo" case AMMOTYPE_HOMINGROCKET: textnum = L_PROPOBJ_017; break; // "homing rocket" case AMMOTYPE_DART: textnum = L_PROPOBJ_025; break; // "dart" case AMMOTYPE_NBOMB: textnum = L_PROPOBJ_026; break; // "N-Bomb" case AMMOTYPE_SEDATIVE: textnum = L_PROPOBJ_027; break; // "sedatives" case AMMOTYPE_PSYCHOSIS: textnum = L_PROPOBJ_027; break; // "sedatives" case AMMOTYPE_BUG: textnum = L_PROPOBJ_035; break; // "bug" case AMMOTYPE_MICROCAMERA: textnum = L_PROPOBJ_036; break; // "micro camera" case AMMOTYPE_TOKEN: textnum = L_PROPOBJ_038; break; // "token" case AMMOTYPE_PLASTIQUE: textnum = L_PROPOBJ_039; break; // "plastique" case AMMOTYPE_CLOAK: textnum = L_PROPOBJ_048; break; // "cloaking device" case AMMOTYPE_BOOST: textnum = L_PROPOBJ_049; break; // "boost pill" } if (textnum >= 0) { strcat(dst, langGet(textnum)); } if (qty >= 2 && ammotype != AMMOTYPE_REAPER && ammotype != AMMOTYPE_SEDATIVE && ammotype != AMMOTYPE_CLOAK) { strcat(dst, langGet(L_PROPOBJ_024)); // "s" } } } void ammotypePlayPickupSound(u32 ammotype) { switch (ammotype) { case AMMOTYPE_PISTOL: case AMMOTYPE_SMG: case AMMOTYPE_RIFLE: case AMMOTYPE_SHOTGUN: case AMMOTYPE_GRENADE: case AMMOTYPE_ROCKET: case AMMOTYPE_MAGNUM: case AMMOTYPE_DEVASTATOR: case AMMOTYPE_REAPER: case AMMOTYPE_HOMINGROCKET: case AMMOTYPE_DART: case AMMOTYPE_NBOMB: case AMMOTYPE_SEDATIVE: case AMMOTYPE_CLOAK: case AMMOTYPE_BOOST: case AMMOTYPE_TOKEN: sndStart(var80095200, SFX_PICKUP_AMMO, NULL, -1, -1, -1, -1, -1); break; case AMMOTYPE_REMOTE_MINE: case AMMOTYPE_PROXY_MINE: case AMMOTYPE_TIMED_MINE: case AMMOTYPE_BUG: case AMMOTYPE_MICROCAMERA: case AMMOTYPE_PLASTIQUE: case AMMOTYPE_ECM_MINE: sndStart(var80095200, SFX_PICKUP_MINE, NULL, -1, -1, -1, -1, -1); break; case AMMOTYPE_KNIFE: sndStart(var80095200, SFX_PICKUP_KNIFE, NULL, -1, -1, -1, -1, -1); break; } } s32 propPlayPickupSound(struct prop *prop, s32 weapon) { s16 sound; if (weapon == WEAPON_COMBATKNIFE || weapon == WEAPON_COMBATKNIFE) { sound = SFX_PICKUP_KNIFE; } else if (weapon == WEAPON_REMOTEMINE || weapon == WEAPON_PROXIMITYMINE || weapon == WEAPON_TIMEDMINE || weapon == WEAPON_COMMSRIDER || weapon == WEAPON_TRACERBUG || weapon == WEAPON_TARGETAMPLIFIER || weapon == WEAPON_ECMMINE) { sound = SFX_PICKUP_MINE; } else if (weapon == WEAPON_GRENADE || weapon == WEAPON_GRENADEROUND || weapon == WEAPON_ROCKET || weapon == WEAPON_HOMINGROCKET) { sound = SFX_PICKUP_AMMO; } else if (weapon == WEAPON_LASER) { sound = SFX_PICKUP_LASER; } else { sound = SFX_PICKUP_GUN; } return propsnd0f0939f8(NULL, prop, sound, -1, -1, 1024, 0, 0, 0, -1, 0, -1, -1, -1, -1); } void weaponPlayPickupSound(s32 weaponnum) { s32 sound; if (weaponnum == WEAPON_COMBATKNIFE || weaponnum == WEAPON_COMBATKNIFE) { sound = SFX_PICKUP_KNIFE; } else if (weaponnum == WEAPON_REMOTEMINE || weaponnum == WEAPON_PROXIMITYMINE || weaponnum == WEAPON_TIMEDMINE || weaponnum == WEAPON_TRACERBUG || weaponnum == WEAPON_TARGETAMPLIFIER || weaponnum == WEAPON_COMMSRIDER || weaponnum == WEAPON_ECMMINE) { sound = SFX_PICKUP_MINE; } else if (weaponnum == WEAPON_GRENADE || weaponnum == WEAPON_GRENADEROUND || weaponnum == WEAPON_ROCKET || weaponnum == WEAPON_HOMINGROCKET) { sound = SFX_PICKUP_AMMO; } else if (weaponnum == WEAPON_LASER) { sound = SFX_PICKUP_LASER; } else if (weaponnum == WEAPON_BOLT) { sound = SFX_PICKUP_GUN; } else if (weaponnum == WEAPON_EYESPY) { sound = SFX_PICKUP_KEYCARD; } else if (weaponnum > WEAPON_PSYCHOSISGUN) { sound = SFX_PICKUP_KEYCARD; } else { sound = SFX_PICKUP_GUN; } sndStart(var80095200, sound, NULL, -1, -1, -1, -1, -1); } void ammotypeGetPickupMessage(char *dst, s32 ammotype, s32 qty) { s32 playercount = PLAYERCOUNT(); s32 full = playercount <= 2 && !(playercount == 2 && optionsGetScreenSplit() == SCREENSPLIT_VERTICAL); *dst = '\0'; if (full) { ammotypeGetPickedUpText(dst); // "Picked up" } ammotypeGetDeterminer(dst, ammotype, qty); // "a", "an", "some" or "the" ammotypeGetPickupName(dst, ammotype, qty); // name of ammo type strcat(dst, ".\n"); } void currentPlayerQueuePickupAmmoHudmsg(s32 ammotype, s32 pickupqty) { char buffer[100] = ""; ammotypeGetPickupMessage(buffer, ammotype, pickupqty); hudmsgCreateWithFlags(buffer, HUDMSGTYPE_DEFAULT, HUDMSGFLAG_ONLYIFALIVE); } void ammoHandlePickup(s32 ammotype, s32 quantity, bool withsound, bool withhudmsg) { s32 weapon; if (quantity > 0) { if (bgunGetReservedAmmoCount(ammotype) < bgunGetCapacityByAmmotype(ammotype)) { bgunSetAmmoQuantity(ammotype, bgunGetReservedAmmoCount(ammotype) + quantity); if (withhudmsg) { currentPlayerQueuePickupAmmoHudmsg(ammotype, quantity); } } if (withsound) { ammotypePlayPickupSound(ammotype); } if (ammotype == AMMOTYPE_GRENADE) { weapon = WEAPON_GRENADE; } else if (ammotype == AMMOTYPE_REMOTE_MINE) { weapon = WEAPON_REMOTEMINE; } else if (ammotype == AMMOTYPE_PROXY_MINE) { weapon = WEAPON_PROXIMITYMINE; } else if (ammotype == AMMOTYPE_TIMED_MINE) { weapon = WEAPON_TIMEDMINE; } else if (ammotype == AMMOTYPE_NBOMB) { weapon = WEAPON_NBOMB; } else if (ammotype == AMMOTYPE_KNIFE) { weapon = WEAPON_COMBATKNIFE; } else if (ammotype == AMMOTYPE_ECM_MINE) { weapon = WEAPON_ECMMINE; } else if (ammotype == AMMOTYPE_TOKEN) { weapon = WEAPON_BRIEFCASE2; } else if (ammotype == AMMOTYPE_CLOAK) { weapon = WEAPON_CLOAKINGDEVICE; } else if (ammotype == AMMOTYPE_BOOST) { weapon = WEAPON_COMBATBOOST; } else { weapon = -1; } if (weapon >= 0) { invGiveSingleWeapon(weapon); } } } s32 ammocrateGetPickupAmmoQty(struct ammocrateobj *crate) { s32 qty = 1; switch (crate->ammotype) { case AMMOTYPE_PISTOL : qty = 10; break; case AMMOTYPE_SMG : qty = 10; break; case AMMOTYPE_CROSSBOW : qty = 10; break; case AMMOTYPE_RIFLE : qty = 10; break; case AMMOTYPE_SHOTGUN : qty = 5; break; case AMMOTYPE_MAGNUM : qty = 5; break; case AMMOTYPE_REAPER : qty = 200; break; case AMMOTYPE_DART : qty = 4; break; case AMMOTYPE_CLOAK : qty = TICKS(1200); break; case AMMOTYPE_SEDATIVE : qty = 16; break; case AMMOTYPE_BOOST : qty = 1; break; } if (qty > 1 && !g_Vars.normmplayerisrunning) { qty *= g_AmmoQuantityScale; } return qty; } s32 weaponGetPickupAmmoQty(struct weaponobj *weapon) { s32 ammotype; s32 qty = 1; if (!weapon) { return 20; } ammotype = bgunGetAmmoTypeForWeapon(weapon->weaponnum, 0); if (weapon->weaponnum == WEAPON_COMBATKNIFE || weapon->weaponnum == WEAPON_BOLT) { return 1; } if (weapon->base.flags & OBJFLAG_WEAPON_40000000) { return 0; } if (g_Vars.normmplayerisrunning) { switch (ammotype) { case AMMOTYPE_PISTOL: qty = 10; break; case AMMOTYPE_SMG: qty = 20; break; case AMMOTYPE_CROSSBOW: qty = 5; break; case AMMOTYPE_RIFLE: qty = 20; break; case AMMOTYPE_SHOTGUN: qty = 10; break; case AMMOTYPE_FARSIGHT: qty = 4; break; case AMMOTYPE_MAGNUM: qty = 10; break; case AMMOTYPE_DEVASTATOR: qty = 3; break; case AMMOTYPE_REAPER: qty = 200; break; case AMMOTYPE_DART: qty = 10; break; case AMMOTYPE_CLOAK: qty = TICKS(1200); break; case AMMOTYPE_SEDATIVE: qty = 16; break; case AMMOTYPE_BOOST: qty = 1; break; } } else { switch (ammotype) { case AMMOTYPE_PISTOL: qty = 10; break; case AMMOTYPE_SMG: qty = 10; break; case AMMOTYPE_CROSSBOW: qty = 5; break; case AMMOTYPE_RIFLE: qty = 10; break; case AMMOTYPE_SHOTGUN: qty = 5; break; case AMMOTYPE_FARSIGHT: qty = 4; break; case AMMOTYPE_MAGNUM: qty = 5; break; case AMMOTYPE_DEVASTATOR: qty = 3; break; case AMMOTYPE_REAPER: qty = 100; break; case AMMOTYPE_DART: qty = 4; break; case AMMOTYPE_CLOAK: qty = TICKS(1200); break; case AMMOTYPE_BOOST: qty = 2; break; case AMMOTYPE_SEDATIVE: qty = 16; break; } if (qty > 1) { qty *= g_AmmoQuantityScale; } } return qty; } void weaponGetPickupText(char *buffer, s32 weaponnum, bool dual) { s32 playercount = PLAYERCOUNT(); s32 full = playercount <= 2 && !(playercount == 2 && optionsGetScreenSplit() == SCREENSPLIT_VERTICAL); s32 textid; bool plural = false; if (dual) { strcat(buffer, langGet(L_PROPOBJ_001)); // "Double" } else { if (full) { strcat(buffer, langGet(L_PROPOBJ_000)); // "Picked up" if (weaponnum == WEAPON_EYESPY && g_Vars.currentplayer->eyespy) { textid = L_PROPOBJ_050; // "your" } else if (weaponHasFlag(weaponnum, WEAPONFLAG_DETERMINER_F_SOME)) { textid = L_PROPOBJ_002; // "some" } else if (weaponHasFlag(weaponnum, WEAPONFLAG_DETERMINER_F_AN)) { textid = L_PROPOBJ_006; // "an" } else if (weaponHasFlag(weaponnum, WEAPONFLAG_DETERMINER_F_THE)) { textid = L_PROPOBJ_008; // "the" } else { textid = L_PROPOBJ_004; // "a" } strcat(buffer, langGet(textid)); } else { if (weaponnum == WEAPON_EYESPY && g_Vars.currentplayer->eyespy) { textid = L_PROPOBJ_051; // "Your" } else if (weaponHasFlag(weaponnum, WEAPONFLAG_DETERMINER_S_SOME)) { textid = L_PROPOBJ_003; // "Some" } else if (weaponHasFlag(weaponnum, WEAPONFLAG_DETERMINER_S_AN)) { textid = L_PROPOBJ_007; // "An" } else if (weaponHasFlag(weaponnum, WEAPONFLAG_DETERMINER_S_THE)) { textid = L_PROPOBJ_009; // "The" } else { textid = L_PROPOBJ_005; // "A" } strcat(buffer, langGet(textid)); } } if (full) { strcat(buffer, bgunGetName(weaponnum)); plural = weaponHasFlag(weaponnum, WEAPONFLAG_DETERMINER_F_SOME); } else { strcat(buffer, bgunGetShortName(weaponnum)); plural = weaponHasFlag(weaponnum, WEAPONFLAG_DETERMINER_S_SOME); } // Note that weapon names have a line break on the end which is undesirable // here. The code below removes them conditionally, then ends up removing // them unconditionally anyway which is a bit wasteful. if (plural) { if (buffer[strlen(buffer) - 1] == '\n') { buffer[strlen(buffer) - 1] = '\0'; } strcat(buffer, "s"); } if (buffer[strlen(buffer) - 1] == '\n') { buffer[strlen(buffer) - 1] = '\0'; } strcat(buffer, ".\n"); } void currentPlayerQueuePickupWeaponHudmsg(u32 weaponnum, bool dual) { char buffer[100] = ""; weaponGetPickupText(buffer, weaponnum, dual); hudmsgCreateWithFlags(buffer, HUDMSGTYPE_DEFAULT, HUDMSGFLAG_ONLYIFALIVE | HUDMSGFLAG_ALLOWDUPES); } s32 propPickupByPlayer(struct prop *prop, bool showhudmsg) { struct defaultobj *obj = prop->obj; s32 result; bool given = false; if (g_Vars.currentplayer->isdead || g_Vars.lvupdate240 == 0) { return TICKOP_NONE; } switch (obj->type) { case OBJTYPE_KEY: if (g_Vars.in_cutscene == false) { sndStart(var80095200, SFX_PICKUP_KEYCARD, NULL, -1, -1, -1, -1, -1); } if (showhudmsg) { char *text = invGetPickupTextByObj(obj); if (text == NULL) { text = langGet(L_PROPOBJ_040); // "Picked up a key." } hudmsgCreateWithFlags(text, HUDMSGTYPE_DEFAULT, HUDMSGFLAG_ONLYIFALIVE | HUDMSGFLAG_ALLOWDUPES); } result = TICKOP_GIVETOPLAYER; break; case OBJTYPE_AMMOCRATE: { struct ammocrateobj *crate = (struct ammocrateobj *) prop->obj; s32 quantity = ammocrateGetPickupAmmoQty(crate); ammoHandlePickup(crate->ammotype, quantity, !g_Vars.in_cutscene, showhudmsg); result = TICKOP_FREE; } break; case OBJTYPE_MULTIAMMOCRATE: { struct multiammocrateobj *crate = (struct multiammocrateobj *)prop->obj; u32 stack; s32 i; for (i = 0; i != 19; i++) { s32 qty = crate->slots[i].quantity; if (!g_Vars.normmplayerisrunning) { qty *= g_AmmoQuantityScale; } ammoHandlePickup(i + 1, qty, false, showhudmsg); } if (g_Vars.in_cutscene == false) { sndStart(var80095200, SFX_PICKUP_AMMO, NULL, -1, -1, -1, -1, -1); } result = TICKOP_FREE; } break; case OBJTYPE_WEAPON: { struct weaponobj *weapon = (struct weaponobj *) prop->obj; bool sp70 = false; s32 ammotype; s32 count = 0; s32 sp64; if (g_Vars.normmplayerisrunning) { if (weapon->weaponnum == WEAPON_BRIEFCASE2) { sp64 = scenarioPickUpBriefcase(g_Vars.currentplayer->prop->chr, prop); if (sp64) { weaponPlayPickupSound(weapon->weaponnum); } return sp64; } if (weapon->weaponnum == WEAPON_DATAUPLINK) { sp64 = scenarioPickUpUplink(g_Vars.currentplayer->prop->chr, prop); if (sp64) { weaponPlayPickupSound(weapon->weaponnum); } return sp64; } } if (g_Vars.in_cutscene == false) { weaponPlayPickupSound(weapon->weaponnum); } if (obj->hidden & OBJHFLAG_HASTEXTOVERRIDE) { if (weapon->weaponnum <= WEAPON_PSYCHOSISGUN) { count = invGiveWeaponsByProp(prop); given = true; } if (showhudmsg) { char *text = invGetPickupTextByObj(obj); if (text) { hudmsgCreateWithFlags(text, HUDMSGTYPE_DEFAULT, HUDMSGFLAG_ONLYIFALIVE | HUDMSGFLAG_ALLOWDUPES); } else { currentPlayerQueuePickupWeaponHudmsg(weapon->weaponnum, count == 2); } sp70 = true; } result = TICKOP_GIVETOPLAYER; } else { if (weapon->weaponnum == WEAPON_BOLT) { count = 1; given = true; ammoHandlePickup(AMMOTYPE_CROSSBOW, 1, !g_Vars.in_cutscene, true); result = TICKOP_FREE; showhudmsg = false; sp70 = true; } else { count = invGiveWeaponsByProp(prop); if (count) { sp70 = true; } given = true; if (showhudmsg) { char *text = invGetPickupTextByWeaponNum(weapon->weaponnum); if (text) { sp70 = true; hudmsgCreateWithFlags(text, HUDMSGTYPE_DEFAULT, HUDMSGFLAG_ONLYIFALIVE | HUDMSGFLAG_ALLOWDUPES); } else { if (sp70) { currentPlayerQueuePickupWeaponHudmsg(weapon->weaponnum, count == 2); } } } result = TICKOP_FREE; } } if (count == 2 && bgunGetWeaponNum(HAND_RIGHT) == weapon->weaponnum && bgunGetWeaponNum(HAND_LEFT) != weapon->weaponnum) { bgunEquipWeapon2(HAND_LEFT, weapon->weaponnum); } ammotype = bgunGetAmmoTypeForWeapon(weapon->weaponnum, FUNC_PRIMARY); if (ammotype) { s32 pickupqty = weaponGetPickupAmmoQty(weapon); if (pickupqty > 0) { s32 heldqty = bgunGetReservedAmmoCount(ammotype); if (bgunGetReservedAmmoCount(ammotype) < bgunGetCapacityByAmmotype(ammotype)) { heldqty += pickupqty; bgunSetAmmoQuantity(ammotype, heldqty); if (!sp70 && showhudmsg) { currentPlayerQueuePickupAmmoHudmsg(ammotype, pickupqty); } } } } if (weapon->weaponnum == WEAPON_SUPERDRAGON) { s32 pickupqty = weaponGetPickupAmmoQty(weapon); if (bgunGetReservedAmmoCount(AMMOTYPE_DEVASTATOR) < bgunGetCapacityByAmmotype(AMMOTYPE_DEVASTATOR)) { s32 quantity = bgunGetReservedAmmoCount(AMMOTYPE_DEVASTATOR) + 5; bgunSetAmmoQuantity(AMMOTYPE_DEVASTATOR, quantity); if (!sp70 && showhudmsg) { currentPlayerQueuePickupAmmoHudmsg(AMMOTYPE_DEVASTATOR, pickupqty); } } } if (weapon->weaponnum == WEAPON_EYESPY && g_Vars.currentplayer->eyespy == NULL) { playerInitEyespy(); } } break; case OBJTYPE_SHIELD: { playerSetShieldFrac(((struct shieldobj *) prop->obj)->amount); if (!g_Vars.in_cutscene) { sndStart(var80095200, SFX_PICKUP_SHIELD, NULL, -1, -1, -1, -1, -1); } if (showhudmsg) { char *text = invGetPickupTextByObj(obj); if (text == NULL) { s32 playercount = PLAYERCOUNT(); if (playercount <= 2 && !(playercount == 2 && optionsGetScreenSplit() == SCREENSPLIT_VERTICAL)) { text = langGet(L_PROPOBJ_041); // "Picked up a shield." } else { text = langGet(L_PROPOBJ_042); // "A shield." } } hudmsgCreateWithFlags(text, HUDMSGTYPE_DEFAULT, HUDMSGFLAG_ONLYIFALIVE); } result = TICKOP_FREE; } break; case OBJTYPE_BASIC: case OBJTYPE_ALARM: case OBJTYPE_SINGLEMONITOR: case OBJTYPE_MULTIMONITOR: case OBJTYPE_AUTOGUN: case OBJTYPE_DEBRIS: case OBJTYPE_GLASS: case OBJTYPE_TINTEDGLASS: default: if (g_Vars.in_cutscene == false) { sndStart(var80095200, SFX_PICKUP_KEYCARD, NULL, -1, -1, -1, -1, -1); } if (showhudmsg) { char *text = invGetPickupTextByObj(obj); if (text == NULL) { text = langGet(L_PROPOBJ_043); // "Picked up something." } hudmsgCreateWithFlags(text, HUDMSGTYPE_DEFAULT, HUDMSGFLAG_ONLYIFALIVE | HUDMSGFLAG_ALLOWDUPES); } result = TICKOP_GIVETOPLAYER; break; } if (result == TICKOP_FREE && (obj->hidden & OBJHFLAG_TAGGED) == 0) { objFree(obj, false, obj->hidden2 & OBJH2FLAG_CANREGEN); return TICKOP_FREE; } if (result != TICKOP_NONE) { if (!given) { invGiveProp(prop); } return TICKOP_GIVETOPLAYER; } return TICKOP_NONE; } s32 objTestForPickup(struct prop *prop) { struct defaultobj *obj = prop->obj; struct prop *playerprop = g_Vars.currentplayer->prop; f32 xdiff = prop->pos.x - playerprop->pos.x; f32 ydiff = prop->pos.y - playerprop->pos.y; f32 zdiff = prop->pos.z - playerprop->pos.z; f32 range; bool usebigrange; bool pickup; if (func0f085194(obj)) { if (obj->flags & OBJFLAG_UNCOLLECTABLE) { return TICKOP_NONE; } } else { if ((obj->flags & OBJFLAG_COLLECTABLE) == 0) { return TICKOP_NONE; } } usebigrange = (obj->flags3 & OBJFLAG3_ONSHELF) && (cheatIsActive(CHEAT_SMALLJO) || cheatIsActive(CHEAT_PLAYASELVIS)); if (usebigrange) { range = 200 * 200; } else { range = 100 * 100; } pickup = xdiff * xdiff + zdiff * zdiff <= range && ydiff >= -200 && ydiff <= 200; if (!pickup) { return TICKOP_NONE; } if (obj->hidden & OBJHFLAG_REAPABLE) { return TICKOP_NONE; } if (obj->type == OBJTYPE_WEAPON) { struct weaponobj *weapon = prop->weapon; bool maybe = true; s32 leftweaponnum; s32 rightweaponnum; if (weapon->weaponnum == WEAPON_GRENADE || weapon->weaponnum == WEAPON_GRENADEROUND || weapon->weaponnum == WEAPON_NBOMB || weapon->weaponnum == WEAPON_SKROCKET) { if (weapon->timer240 >= 0 || (obj->hidden & OBJHFLAG_REAPABLE)) { return TICKOP_NONE; } } if (weapon->weaponnum == WEAPON_REMOTEMINE || weapon->weaponnum == WEAPON_PROXIMITYMINE || weapon->weaponnum == WEAPON_TIMEDMINE || (weapon->weaponnum == WEAPON_DRAGON && weapon->gunfunc == FUNC_SECONDARY) || weapon->weaponnum == WEAPON_TRACERBUG || weapon->weaponnum == WEAPON_TARGETAMPLIFIER || weapon->weaponnum == WEAPON_COMMSRIDER || weapon->weaponnum == WEAPON_ECMMINE) { if (weapon->timer240 >= 0 || (obj->hidden & OBJHFLAG_REAPABLE)) { return TICKOP_NONE; } } if (weapon->weaponnum == WEAPON_ROCKET || weapon->weaponnum == WEAPON_HOMINGROCKET || weapon->weaponnum == WEAPON_BOLT || weapon->weaponnum == WEAPON_COMBATKNIFE) { if (obj->hidden & OBJHFLAG_PROJECTILE) { return TICKOP_NONE; } } if (invHasSingleWeaponExcAllGuns(weapon->weaponnum) && bgunGetAmmoTypeForWeapon(weapon->weaponnum, FUNC_PRIMARY)) { if (cheatIsActive(CHEAT_UNLIMITEDAMMO) || cheatIsActive(CHEAT_UNLIMITEDAMMONORELOADS)) { maybe = false; } else { maybe = bgunGetAmmoQtyForWeapon(weapon->weaponnum, FUNC_PRIMARY) >= bgunGetAmmoCapacityForWeapon(weapon->weaponnum, FUNC_PRIMARY); } if (weapon->weaponnum == WEAPON_SUPERDRAGON) { if (bgunGetAmmoQtyForWeapon(weapon->weaponnum, FUNC_SECONDARY) < bgunGetAmmoCapacityForWeapon(weapon->weaponnum, FUNC_SECONDARY)) { maybe = false; } } if (maybe) { if (weapon->dualweapon || weapon->dualweaponnum >= 0) { if (weapon->dualweapon) { leftweaponnum = rightweaponnum = weapon->dualweapon->weaponnum; } else { leftweaponnum = rightweaponnum = weapon->dualweaponnum; } if ((weapon->base.flags & OBJFLAG_DEACTIVATED)) { rightweaponnum = weapon->weaponnum; } else { leftweaponnum = weapon->weaponnum; } if (invHasDoubleWeaponExcAllGuns(leftweaponnum, rightweaponnum)) { return TICKOP_NONE; } } else { if (g_Vars.normmplayerisrunning && weaponHasFlag(weapon->weaponnum, WEAPONFLAG_DUALWIELD) && !invHasDoubleWeaponExcAllGuns(weapon->weaponnum, weapon->weaponnum)) { struct invitem *item = invFindSingleWeapon(weapon->weaponnum); if ((item && item->type_weap.pickuppad == weapon->base.pad) || weapon->base.pad < 0) { return TICKOP_NONE; } } else { return TICKOP_NONE; } } } } } else if (obj->type == OBJTYPE_AMMOCRATE) { struct ammocrateobj *crate = (struct ammocrateobj *) prop->obj; if (bgunGetReservedAmmoCount(crate->ammotype) >= bgunGetCapacityByAmmotype(crate->ammotype)) { if ((crate->ammotype != AMMOTYPE_GRENADE || invHasSingleWeaponExcAllGuns(WEAPON_GRENADE)) && (crate->ammotype != AMMOTYPE_CLOAK || invHasSingleWeaponExcAllGuns(WEAPON_CLOAKINGDEVICE)) && (crate->ammotype != AMMOTYPE_BOOST || invHasSingleWeaponExcAllGuns(WEAPON_COMBATBOOST)) && (crate->ammotype != AMMOTYPE_NBOMB || invHasSingleWeaponExcAllGuns(WEAPON_NBOMB)) && (crate->ammotype != AMMOTYPE_REMOTE_MINE || invHasSingleWeaponExcAllGuns(WEAPON_REMOTEMINE)) && (crate->ammotype != AMMOTYPE_PROXY_MINE || invHasSingleWeaponExcAllGuns(WEAPON_PROXIMITYMINE)) && (crate->ammotype != AMMOTYPE_TIMED_MINE || invHasSingleWeaponExcAllGuns(WEAPON_TIMEDMINE)) && (crate->ammotype != AMMOTYPE_KNIFE || invHasSingleWeaponExcAllGuns(WEAPON_COMBATKNIFE))) { return TICKOP_NONE; } } } else if (obj->type == OBJTYPE_MULTIAMMOCRATE) { struct multiammocrateobj *crate = (struct multiammocrateobj *) prop->obj; bool ignore = true; s32 i; if (objGetDestroyedLevel(obj)) { return TICKOP_NONE; } for (i = 0; i <= AMMOTYPE_NBOMB; i++) { s32 ammotype = i + 1; if (crate->slots[i].quantity > 0) { if (bgunGetReservedAmmoCount(ammotype) < bgunGetCapacityByAmmotype(ammotype)) { ignore = false; break; } if ((ammotype == AMMOTYPE_GRENADE && !invHasSingleWeaponExcAllGuns(WEAPON_GRENADE)) || (ammotype == AMMOTYPE_CLOAK && !invHasSingleWeaponExcAllGuns(WEAPON_CLOAKINGDEVICE)) || (ammotype == AMMOTYPE_BOOST && !invHasSingleWeaponExcAllGuns(WEAPON_COMBATBOOST)) || (ammotype == AMMOTYPE_NBOMB && !invHasSingleWeaponExcAllGuns(WEAPON_NBOMB)) || (ammotype == AMMOTYPE_REMOTE_MINE && !invHasSingleWeaponExcAllGuns(WEAPON_REMOTEMINE)) || (ammotype == AMMOTYPE_PROXY_MINE && !invHasSingleWeaponExcAllGuns(WEAPON_PROXIMITYMINE)) || (ammotype == AMMOTYPE_TIMED_MINE && !invHasSingleWeaponExcAllGuns(WEAPON_TIMEDMINE)) || (ammotype == AMMOTYPE_KNIFE && !invHasSingleWeaponExcAllGuns(WEAPON_COMBATKNIFE))) { ignore = false; break; } } } if (ignore) { return TICKOP_NONE; } } else if (obj->type == OBJTYPE_SHIELD) { struct shieldobj *shield = (struct shieldobj *) prop->obj; bool ignore = false; if (shield->amount <= playerGetShieldFrac()) { ignore = true; } else if (g_Vars.normmplayerisrunning && g_MpSetup.scenario == MPSCENARIO_HOLDTHEBRIEFCASE && invHasBriefcase()) { ignore = true; } if (ignore) { return TICKOP_NONE; } } else { if (obj->flags & OBJFLAG_THROWNLAPTOP) { return TICKOP_NONE; } } // For disarmed weapons that are falling, picktimer timer is 1 second and // pickupby is set to the chr who disarmed. This makes it so only the // disarmer can pick up the weapon within the first second. // For thrown projectiles, the pickup timer is also 1 second but there is no // pickupby. This prevents the thrower from picking up their own projectile // within the first second, unless it's immediately bounced or landed. if ((obj->hidden & OBJHFLAG_PROJECTILE) && obj->projectile->pickuptimer240 > 0) { if (obj->projectile->pickupby == NULL) { if (obj->projectile->bouncecount == 0) { return TICKOP_NONE; } } else { if (obj->projectile->pickupby != g_Vars.currentplayer->prop) { return TICKOP_NONE; } } } if ((obj->flags2 & OBJFLAG2_PICKUPWITHOUTLOS) == 0 && !usebigrange && cdTestLos05(&playerprop->pos, playerprop->rooms, &prop->pos, prop->rooms, CDTYPE_DOORS | CDTYPE_BG, GEOFLAG_WALL | GEOFLAG_BLOCK_SIGHT | GEOFLAG_BLOCK_SHOOT) == false) { return TICKOP_NONE; } return propPickupByPlayer(prop, true); } bool func0f0899dc(struct prop *prop, struct coord *arg1, f32 *arg2, f32 *arg3) { if (prop->flags & PROPFLAG_ONTHISSCREENTHISTICK) { struct defaultobj *obj = prop->obj; Mtxf *matrix = model0001a60c(obj->model); arg1->z = matrix->m[3][2]; if (arg1->z < 0) { arg1->x = matrix->m[3][0]; arg1->y = matrix->m[3][1]; arg3[0] = 0; arg3[1] = 0; arg2[0] = 0; arg2[1] = 0; func0f067d88(obj->model, &arg2[1], &arg2[0], &arg3[1], &arg3[0]); return true; } } return false; } void modelFreeVertices(s32 vtxstoretype, struct model *model) { struct modelfiledata *modeldef = model->filedata; struct modelnode *node = modeldef->rootnode; while (node) { u32 type = node->type & 0xff; union modelrodata *rodata; union modelrwdata *rwdata; s32 newtype; switch (type) { case MODELNODETYPE_DL: rodata = node->rodata; rwdata = modelGetNodeRwData(model, node); if (modelIsNodeNotTvscreen(modeldef, node)) { if (rwdata->dl.vertices != rodata->dl.vertices) { vtxstoreFree(vtxstoretype, rwdata->dl.vertices); rwdata->dl.vertices = rodata->dl.vertices; } if ((u32)rwdata->dl.colours != ALIGN8((u32)rodata->dl.vertices + rodata->dl.numvertices * sizeof(struct gfxvtx))) { if (vtxstoretype == VTXSTORETYPE_OBJVTX) { newtype = VTXSTORETYPE_OBJCOL; } else { newtype = VTXSTORETYPE_CHRCOL; } vtxstoreFree(newtype, rwdata->dl.colours); rwdata->dl.colours = (struct colour *)ALIGN8((u32)rodata->dl.vertices + rodata->dl.numvertices * sizeof(struct gfxvtx)); } } break; case MODELNODETYPE_DISTANCE: model0001c784(model, node); break; case MODELNODETYPE_TOGGLE: model0001c7d0(model, node); break; case MODELNODETYPE_HEADSPOT: modelAttachHead(model, node); break; } if (node->child) { node = node->child; } else { while (node) { if (node->next) { node = node->next; break; } node = node->parent; } } } } struct weaponobj *weaponCreate(bool musthaveprop, bool musthavemodel, struct modelfiledata *filedata) { s32 i; struct weaponobj *tmp; struct weaponobj *sp4c = NULL; struct weaponobj *sp48 = NULL; s32 sp44 = -1; s32 sp40 = -1; s32 sp3c = -1; for (i = g_NextWeaponSlot; true; ) { bool usable = false; if (g_WeaponSlots[i].base.prop == NULL) { if (!musthaveprop && !musthavemodel) { sp44 = i; break; } } else { if ((g_WeaponSlots[i].base.hidden & OBJHFLAG_PROJECTILE) == 0 && (g_WeaponSlots[i].base.hidden2 & OBJH2FLAG_CANREGEN) == 0 && (g_WeaponSlots[i].base.flags & OBJFLAG_00800000) == 0) { if (g_WeaponSlots[i].base.prop->parent) { if (g_WeaponSlots[i].base.hidden & OBJHFLAG_EMBEDDED) { usable = true; } } else { usable = true; } } } if (usable) { if (!musthavemodel || modelmgrCanSlotFitRwdata(g_WeaponSlots[i].base.model, filedata)) { if ((g_WeaponSlots[i].base.prop->flags & (PROPFLAG_ONTHISSCREENTHISTICK | PROPFLAG_ONANYSCREENTHISTICK | PROPFLAG_ONANYSCREENPREVTICK)) == 0 && sp40 < 0) { sp40 = i; } if (sp3c < 0) { sp3c = i; } } } i = (i + 1) % g_MaxWeaponSlots; if (i == g_NextWeaponSlot) { break; } } if (sp44 >= 0) { g_NextWeaponSlot = (sp44 + 1) % g_MaxWeaponSlots; return &g_WeaponSlots[sp44]; } tmp = (struct weaponobj *)setupFindObjForReuse(OBJTYPE_WEAPON, (struct defaultobj **)&sp4c, (struct defaultobj **)&sp48, musthaveprop, musthavemodel, filedata); if (tmp) { return tmp; } if (sp40 >= 0) { if (g_WeaponSlots[sp40].base.prop) { objFreePermanently(&g_WeaponSlots[sp40].base, true); } g_NextWeaponSlot = (sp40 + 1) % g_MaxWeaponSlots; return &g_WeaponSlots[sp40]; } if (sp4c) { if (sp4c->base.prop) { objFreePermanently(&sp4c->base, true); } return sp4c; } if (sp3c >= 0) { if (g_WeaponSlots[sp3c].base.prop) { objFreePermanently(&g_WeaponSlots[sp3c].base, true); } g_NextWeaponSlot = (sp3c + 1) % g_MaxWeaponSlots; return &g_WeaponSlots[sp3c]; } if (sp48) { if (sp48->base.prop) { objFreePermanently(&sp48->base, true); } return sp48; } return NULL; } struct ammocrateobj *ammocrateAllocate(void) { s32 i; // Try to find a free one for (i = 0; i < g_MaxAmmoCrates; i++) { if (g_AmmoCrates[i].base.prop == NULL) { return &g_AmmoCrates[i]; } } // Find one that can be freed off-screen for (i = 0; i < g_MaxAmmoCrates; i++) { if ((g_AmmoCrates[i].base.hidden & OBJHFLAG_PROJECTILE) == 0 && (g_AmmoCrates[i].base.hidden2 & OBJH2FLAG_CANREGEN) == 0 && g_AmmoCrates[i].base.prop->parent == NULL && (g_AmmoCrates[i].base.prop->flags & (PROPFLAG_ONTHISSCREENTHISTICK | PROPFLAG_ONANYSCREENTHISTICK | PROPFLAG_ONANYSCREENPREVTICK)) == 0) { objFreePermanently(&g_AmmoCrates[i].base, true); return &g_AmmoCrates[i]; } } // Find one that can be freed on-screen for (i = 0; i < g_MaxAmmoCrates; i++) { if ((g_AmmoCrates[i].base.hidden & OBJHFLAG_PROJECTILE) == 0 && (g_AmmoCrates[i].base.hidden2 & OBJH2FLAG_CANREGEN) == 0 && g_AmmoCrates[i].base.prop->parent == NULL) { objFreePermanently(&g_AmmoCrates[i].base, true); return &g_AmmoCrates[i]; } } return NULL; } struct defaultobj *debrisAllocate(void) { s32 i; // Try to find an unused slot for (i = 0; i < g_MaxDebrisSlots; i++) { if (g_DebrisSlots[i].prop == NULL) { return &g_DebrisSlots[i]; } } // Try to find one that's landed and offscreen for (i = 0; i < g_MaxDebrisSlots; i++) { if ((g_DebrisSlots[i].hidden & OBJHFLAG_PROJECTILE) == 0 && (g_DebrisSlots[i].hidden2 & OBJH2FLAG_CANREGEN) == 0 && g_DebrisSlots[i].prop->parent == NULL && (g_DebrisSlots[i].prop->flags & (PROPFLAG_ONTHISSCREENTHISTICK | PROPFLAG_ONANYSCREENTHISTICK | PROPFLAG_ONANYSCREENPREVTICK)) == 0) { objFreePermanently(&g_DebrisSlots[i], true); return &g_DebrisSlots[i]; } } // Same as above but onscreen for (i = 0; i < g_MaxDebrisSlots; i++) { if ((g_DebrisSlots[i].hidden & OBJHFLAG_PROJECTILE) == 0 && (g_DebrisSlots[i].hidden2 & OBJH2FLAG_CANREGEN) == 0 && g_DebrisSlots[i].prop->parent == NULL) { objFreePermanently(&g_DebrisSlots[i], true); return &g_DebrisSlots[i]; } } return NULL; } void playerActivateRemoteMineDetonator(s32 playernum) { g_PlayersDetonatingMines |= 1 << playernum; sndStart(var80095200, SFX_DETONATE, 0, -1, -1, -1, -1, -1); bgunStartDetonateAnimation(playernum); } struct weaponobj *weaponFindChildByWeaponNum(s32 weaponnum, struct prop *prop) { struct weaponobj *weapon; struct prop *child; if (prop->type == PROPTYPE_WEAPON && weaponnum == prop->weapon->weaponnum) { return prop->weapon; } child = prop->child; while (child) { weapon = weaponFindChildByWeaponNum(weaponnum, child); if (weapon) { return weapon; } child = child->next; } return NULL; } struct weaponobj *weaponFindLanded(s32 weaponnum) { struct prop *prop = g_Vars.activeprops; while (prop) { struct weaponobj *weapon = weaponFindChildByWeaponNum(weaponnum, prop); if (weapon && (weapon->base.hidden & OBJHFLAG_PROJECTILE) == 0) { return weapon; } prop = prop->next; } return NULL; } void weaponRegisterProxy(struct weaponobj *weapon) { s32 i; if (g_NumProxies < ARRAYCOUNT(g_Proxies)) { g_Proxies[g_NumProxies] = weapon; g_NumProxies++; } } void weaponUnregisterProxy(struct weaponobj *weapon) { s32 i; for (i = 0; i < ARRAYCOUNT(g_Proxies); i++) { if (g_Proxies[i] == weapon) { g_Proxies[i] = g_Proxies[g_NumProxies - 1]; g_NumProxies--; return; } } } void coordTriggerProxies(struct coord *pos, bool arg1) { s32 i; for (i = 0; i < g_NumProxies; i++) { struct weaponobj *weapon = g_Proxies[i]; if (weapon->timer240 == 1) { if (weapon->weaponnum != WEAPON_GRENADE || arg1 == true) { f32 xdiff; f32 ydiff; f32 zdiff; f32 range = 250 * 250; if (weapon->weaponnum == WEAPON_DRAGON) { range += range; } xdiff = pos->x - weapon->base.prop->pos.x; ydiff = pos->y - weapon->base.prop->pos.y; zdiff = pos->z - weapon->base.prop->pos.z; if (xdiff * xdiff + ydiff * ydiff + zdiff * zdiff < range) { weapon->timer240 = 0; } } } } } void chrsTriggerProxies(void) { s32 numchrs = chrsGetNumSlots(); s32 i; for (i = 0; i < numchrs; i++) { struct chrdata *chr = &g_ChrSlots[i]; struct coord pos; if (chr->prop && !chrIsDead(chr) && (chr->chrflags & CHRCFLAG_HIDDEN) == 0 && (chr->prop->flags & PROPFLAG_ENABLED) && chr->model) { chrCalculatePosition(chr, &pos); coordTriggerProxies(&pos, true); } } } void propweaponSetDual(struct weaponobj *weapon1, struct weaponobj *weapon2) { weapon1->dualweaponnum = weapon2->weaponnum; weapon1->dualweapon = weapon2; weapon2->dualweaponnum = weapon1->weaponnum; weapon2->dualweapon = weapon1; } struct prop *func0f08adc8(struct weaponobj *weapon, struct modelfiledata *filedata, struct prop *prop, struct model *model) { prop = objInit(&weapon->base, filedata, prop, model); if (prop) { prop->type = PROPTYPE_WEAPON; weaponSetGunfireVisible(prop, false, -1); } return prop; } struct prop *func0f08ae0c(struct weaponobj *weapon, struct modelfiledata *filedata) { struct prop *prop = objInitWithModelDef(&weapon->base, filedata); if (prop) { prop->type = PROPTYPE_WEAPON; weaponSetGunfireVisible(prop, false, -1); } return prop; } bool chrEquipWeapon(struct weaponobj *weapon, struct chrdata *chr) { u32 stack1; s32 handnum = (weapon->base.flags & OBJFLAG_WEAPON_LEFTHANDED) ? HAND_LEFT : HAND_RIGHT; u32 stack2[2]; if (weapon->base.prop && weapon->base.model) { if (g_Vars.mplayerisrunning) { s32 playernum = mpPlayerGetIndex(chr); weapon->base.hidden &= 0x0fffffff; weapon->base.hidden |= (playernum << 28) & 0xf0000000; } if ((weapon->base.flags & OBJFLAG_WEAPON_AICANNOTUSE) == 0) { if (chr->weapons_held[handnum]) { if (chr->aibot) { chr->weapons_held[handnum]->weapon->base.hidden |= OBJHFLAG_REAPABLE; chr->weapons_held[handnum] = NULL; } else { return false; } } if (!chr->weapons_held[handnum]) { if (chr->model->filedata->skel == &g_SkelChr) { weapon->base.model->attachedtomodel = chr->model; if (handnum == HAND_RIGHT) { weapon->base.model->attachedtonode = modelGetPart(chr->model->filedata, MODELPART_CHR_RIGHTHAND); } else { weapon->base.model->attachedtonode = modelGetPart(chr->model->filedata, MODELPART_CHR_LEFTHAND); } chr->weapons_held[handnum] = weapon->base.prop; if ((weapon->base.flags & OBJFLAG_80000000) && chr->weapons_held[1 - handnum]) { propweaponSetDual(weapon, chr->weapons_held[1 - handnum]->weapon); } } else if (chr->model->filedata->skel == &g_SkelSkedar) { weapon->base.model->attachedtomodel = chr->model; if (handnum == HAND_RIGHT) { weapon->base.model->attachedtonode = modelGetPart(chr->model->filedata, MODELPART_SKEDAR_RIGHTHAND); } else { weapon->base.model->attachedtonode = modelGetPart(chr->model->filedata, MODELPART_SKEDAR_LEFTHAND); } chr->weapons_held[handnum] = weapon->base.prop; if ((weapon->base.flags & OBJFLAG_80000000) && chr->weapons_held[1 - handnum]) { propweaponSetDual(weapon, chr->weapons_held[1 - handnum]->weapon); } } else { return false; } } } propReparent(weapon->base.prop, chr->prop); } else { return false; } return true; } struct prop *func0f08b108(struct weaponobj *weapon, struct chrdata *chr, struct modelfiledata *filedata, struct prop *prop, struct model *model) { prop = func0f08adc8(weapon, filedata, prop, model); if (prop && weapon->base.model) { f32 scale = weapon->base.extrascale * (1.0f / 256.0f); modelSetScale(weapon->base.model, weapon->base.model->scale * scale); if (!chrEquipWeapon(weapon, chr)) { propFree(prop); prop = NULL; weapon->base.prop = NULL; modelmgrFreeModel(weapon->base.model); weapon->base.model = NULL; } } else { if (prop) { propFree(prop); prop = NULL; weapon->base.prop = NULL; } if (weapon->base.model) { modelmgrFreeModel(weapon->base.model); weapon->base.model = NULL; } } return prop; } void func0f08b208(struct weaponobj *weapon, struct chrdata *chr) { u32 stack; s32 modelnum = weapon->base.modelnum; setupLoadModeldef(modelnum); func0f08b108(weapon, chr, g_ModelStates[modelnum].filedata, 0, 0); } void func0f08b25c(struct weaponobj *weapon, struct chrdata *chr) { func0f08b208(weapon, chr); } struct autogunobj *laptopDeploy(s32 modelnum, struct gset *gset, struct chrdata *chr) { struct modelfiledata *filedata; struct prop *prop; struct model *model; struct autogunobj *laptop = NULL; s32 index; if (g_Vars.normmplayerisrunning) { index = mpPlayerGetIndex(chr); } else { index = playermgrGetPlayerNumByProp(chr->prop); } if (index >= 0 && index < g_MaxThrownLaptops) { setupLoadModeldef(modelnum); filedata = g_ModelStates[modelnum].filedata; laptop = &g_ThrownLaptops[index]; if (laptop->base.prop) { explosionCreateSimple(NULL, &laptop->base.prop->pos, laptop->base.prop->rooms, EXPLOSIONTYPE_LAPTOP, index); objFreePermanently(&laptop->base, true); } prop = propAllocate(); model = modelmgrInstantiateModelWithoutAnim(filedata); if (prop == NULL) { prop = propAllocate(); } if (model == NULL) { model = modelmgrInstantiateModelWithoutAnim(filedata); } if (laptop && prop && model) { struct defaultobj tmp = { 256, // extrascale 0, // hidden2 OBJTYPE_AUTOGUN, // type 0, // modelnum 1, // pad 0, // flags 0, // flags2 0, // flags3 NULL, // prop NULL, // model 1, 0, 0, // realrot 0, 1, 0, 0, 0, 1, 0, // hidden NULL, // geo NULL, // projectile 0, // damage 1000, // maxdamage 0xff, 0xff, 0xff, 0x00, // shadecol 0xff, 0xff, 0xff, 0x00, // nextcol 0x0fff, // floorcol 0, // tiles }; laptop->base = tmp; laptop->base.modelnum = modelnum; prop = objInit(&laptop->base, filedata, prop, model); laptop->targetpad = -1; laptop->aimdist = 5000; laptop->target = NULL; laptop->targetteam = ~chr->team & 0xff; laptop->nextchrtest = 0; laptop->firecount = 0; laptop->lastseebond60 = -1; laptop->lastaimbond60 = -1; laptop->allowsoundframe = -1; laptop->firing = false; laptop->yspeed = 0; laptop->xspeed = 0; laptop->barrelspeed = 0; laptop->barrelrot = 0; laptop->shotbondsum = 0; if (chr->aibot) { laptop->ammoquantity = botactTryRemoveAmmoFromReserve(chr->aibot, WEAPON_LAPTOPGUN, FUNC_PRIMARY, 200); } else if (chr->prop->type == PROPTYPE_PLAYER) { s32 qty; s32 prevplayernum = g_Vars.currentplayernum; setCurrentPlayerNum(playermgrGetPlayerNumByProp(chr->prop)); qty = bgunGetAmmoQtyForWeapon(WEAPON_LAPTOPGUN, FUNC_PRIMARY); if (qty >= 200) { laptop->ammoquantity = 200; } else { laptop->ammoquantity = qty; } if (cheatIsActive(CHEAT_UNLIMITEDAMMOLAPTOP)) { laptop->ammoquantity = 255; } else { qty -= laptop->ammoquantity; } bgunSetAmmoQtyForWeapon(WEAPON_LAPTOPGUN, FUNC_PRIMARY, qty); setCurrentPlayerNum(prevplayernum); } else { laptop->ammoquantity = 255; } laptop->beam = &g_ThrownLaptopBeams[index]; laptop->beam->age = -1; laptop->yzero = 0; laptop->xzero = 0; laptop->yrot = 0; laptop->xrot = 0; laptop->ymaxleft = 12.56f; laptop->ymaxright = -12.56f; laptop->maxspeed = PALUPF(0.0697f); prop->forcetick = true; laptop->base.hidden |= OBJHFLAG_TAGGED; laptop->base.flags |= OBJFLAG_THROWNLAPTOP | OBJFLAG_01000000 | OBJFLAG_20000000; laptop->base.flags3 |= OBJFLAG3_INTERACTABLE | OBJFLAG3_08000000; } else { if (model) { modelmgrFreeModel(model); } if (prop) { propFree(prop); } laptop = NULL; } } return laptop; } struct weaponobj *weaponCreateProjectileFromGset(s32 modelnum, struct gset *gset, struct chrdata *chr) { struct modelfiledata *modeldef; struct prop *prop; struct model *model; struct weaponobj *weapon; setupLoadModeldef(modelnum); modeldef = g_ModelStates[modelnum].filedata; prop = propAllocate(); model = modelmgrInstantiateModelWithoutAnim(modeldef); weapon = weaponCreate(prop == NULL, model == NULL, modeldef); if (prop == NULL) { prop = propAllocate(); } if (model == NULL) { model = modelmgrInstantiateModelWithoutAnim(modeldef); } if (weapon && prop && model) { struct weaponobj tmp = { 256, // extrascale 0, // hidden2 OBJTYPE_WEAPON, // type 0, // modelnum -1, // pad OBJFLAG_00000001, // flags 0, // flags2 0, // flags3 NULL, // prop NULL, // model 1, 0, 0, // realrot 0, 1, 0, 0, 0, 1, 0, // hidden NULL, // geo NULL, // projectile 0, // damage 1000, // maxdamage 0xff, 0xff, 0xff, 0x00, // shadecol 0xff, 0xff, 0xff, 0x00, // nextcol 0x0fff, // floorcol 0, // tiles 0, // weaponnum 0, // unk5d 0, // unk5e 0, // gunfunc 0, // fadeouttimer60 -1, // dualweaponnum -1, // timer240 NULL, // dualweapon }; *weapon = tmp; weapon->weaponnum = gset->weaponnum; weapon->unk5d = gset->unk0639; weapon->unk5e = gset->unk063a; weapon->gunfunc = gset->weaponfunc; // This switch is useless because everything uses the same case switch (gset->weaponnum) { case WEAPON_SUPERDRAGON: case WEAPON_DEVASTATOR: case WEAPON_ROCKETLAUNCHER: case WEAPON_SLAYER: case WEAPON_COMBATKNIFE: case WEAPON_CROSSBOW: case WEAPON_GRENADE: case WEAPON_NBOMB: case WEAPON_TIMEDMINE: case WEAPON_PROXIMITYMINE: case WEAPON_REMOTEMINE: case WEAPON_ROCKETLAUNCHER_34: default: weapon->base.modelnum = modelnum; prop = func0f08adc8(weapon, modeldef, prop, model); if (g_Vars.mplayerisrunning) { s32 index = mpPlayerGetIndex(chr); weapon->base.hidden &= 0x0fffffff; weapon->base.hidden |= ((index << 28) & 0xf0000000); } prop->forcetick = true; break; } } else { if (weapon) { weapon->base.prop = NULL; weapon->base.model = NULL; } weapon = NULL; if (model) { modelmgrFreeModel(model); } if (prop) { propFree(prop); } } return weapon; } struct weaponobj *weaponCreateProjectileFromWeaponNum(s32 modelnum, s32 weaponnum, struct chrdata *chr) { struct gset gset = {0}; gset.weaponnum = weaponnum; return weaponCreateProjectileFromGset(modelnum, &gset, chr); } void weaponDeleteFromChr(struct chrdata *chr, s32 hand) { if (chr && chr->weapons_held[hand]) { struct defaultobj *obj = chr->weapons_held[hand]->obj; obj->hidden |= OBJHFLAG_REAPABLE; } } struct prop *weaponCreateForChr(struct chrdata *chr, s32 modelnum, s32 weaponnum, u32 flags, struct weaponobj *obj, struct modelfiledata *filedata) { struct prop *prop; struct model *model; if (filedata == NULL) { setupLoadModeldef(modelnum); filedata = g_ModelStates[modelnum].filedata; } prop = propAllocate(); model = modelmgrInstantiateModelWithoutAnim(filedata); if (obj == NULL) { obj = weaponCreate(prop == NULL, model == NULL, filedata); } if (prop == NULL) { prop = propAllocate(); } if (model == NULL) { model = modelmgrInstantiateModelWithoutAnim(filedata); } if (obj && prop && model) { struct weaponobj tmp = { 256, // extrascale 0, // hidden2 OBJTYPE_WEAPON, // type 0, // modelnum 0, // pad OBJFLAG_00000001 | OBJFLAG_ASSIGNEDTOCHR, // flags 0, // flags2 0, // flags3 NULL, // prop NULL, // model 1, 0, 0, // realrot 0, 1, 0, 0, 0, 1, 0, // hidden NULL, // geo NULL, // projectile 0, // damage 1000, // maxdamage 0xff, 0xff, 0xff, 0x00, // shadecol 0xff, 0xff, 0xff, 0x00, // nextcol 0x0fff, // floorcol 0, // tiles 0, // weaponnum 0, // unk5d 0, // unk5e 0, // gunfunc 0, // fadeouttimer60 -1, // dualweaponnum -1, // timer240 NULL, // dualweapon }; *obj = tmp; obj->weaponnum = weaponnum; obj->gunfunc = FUNC_PRIMARY; obj->unk5e = 0; obj->unk5d = 0; obj->base.modelnum = modelnum; obj->base.flags = flags | OBJFLAG_ASSIGNEDTOCHR; obj->base.pad = chr->chrnum; prop = func0f08b108(obj, chr, filedata, prop, model); } else { if (model) { modelmgrFreeModel(model); } if (prop) { propFree(prop); prop = NULL; } if (obj) { obj->base.prop = NULL; obj->base.model = NULL; } } return prop; } struct prop *chrGiveWeapon(struct chrdata *chr, s32 model, s32 weaponnum, u32 flags) { return weaponCreateForChr(chr, model, weaponnum, flags, NULL, NULL); } void weaponSetGunfireVisible(struct prop *prop, bool visible, s16 room) { u32 stack[4]; bool flash = false; struct defaultobj *obj = prop->obj; struct modelnode *node1; struct modelnode *node2; union modelrwdata *rwdata1; union modelrwdata *rwdata2; if (obj && obj->type == OBJTYPE_WEAPON) { struct model *model = obj->model; if (model && model->filedata->skel == &g_SkelChrGun) { node1 = modelGetPart(model->filedata, MODELPART_CHRGUN_GUNFIRE); if (node1) { rwdata1 = modelGetNodeRwData(model, node1); rwdata1->chrgunfire.visible = visible; if (visible) { flash = true; } } node2 = modelGetPart(model->filedata, MODELPART_CHRGUN_0002); if (node2) { rwdata2 = modelGetNodeRwData(model, node2); rwdata2->toggle.visible = visible; if (visible) { flash = true; } } } } if (flash && room != -1) { roomAdjustLighting(room, 48, 128); } } bool doorIsUnlocked(struct prop *playerprop, struct prop *doorprop) { struct doorobj *door = doorprop->door; bool canopen = false; if (door->keyflags == 0) { canopen = true; } else if (invHasKeyFlags(door->keyflags)) { canopen = true; } else { if (posIsInFrontOfDoor(&playerprop->pos, door)) { if ((door->base.flags2 & OBJFLAG2_LOCKEDBACK) && (door->base.flags2 & OBJFLAG2_LOCKEDFRONT) == 0) { canopen = true; } } else { if ((door->base.flags2 & OBJFLAG2_LOCKEDBACK) == 0 && (door->base.flags2 & OBJFLAG2_LOCKEDFRONT)) { canopen = true; } } } return canopen; } bool doorIsPosInRange(struct doorobj *door, struct coord *pos, f32 distance, bool isbike) { struct coord range; if ((door->doorflags & DOORFLAG_LONGRANGE) || isbike) { distance += 400; } else { distance += 200; } range.x = distance; range.y = 0; range.z = 0; if (door->doortype == DOORTYPE_VERTICAL || door->doortype == DOORTYPE_SLIDING || door->doortype == DOORTYPE_SWINGING) { if (func0f0678f8(pos, &range, door->base.pad)) { return true; } } return false; } bool doorIsObjInRange(struct doorobj *door, struct defaultobj *obj, bool isbike) { struct modelrodata_bbox *bbox = objFindBboxRodata(obj); f32 scale = 0; if (scale < bbox->xmin) { scale = bbox->xmin; } if (scale < -bbox->xmax) { scale = -bbox->xmax; } if (scale < bbox->ymin) { scale = bbox->ymin; } if (scale < -bbox->ymax) { scale = -bbox->ymax; } if (scale < bbox->zmin) { scale = bbox->zmin; } if (scale < -bbox->zmax) { scale = -bbox->zmax; } scale *= obj->model->scale; return doorIsPosInRange(door, &obj->prop->pos, scale, isbike); } bool vectorIsInFrontOfDoor(struct doorobj *door, struct coord *vector) { s32 result; struct pad *pad = &g_Pads[door->base.pad]; result = vector->f[0] * pad->normal.f[0] + vector->f[1] * pad->normal.f[1] + vector->f[2] * pad->normal.f[2] >= 0.0f; if (door->doorflags & DOORFLAG_FLIP) { result = !result; } return result; } /** * Return true if there are no chrs or grabbed/mounted objects within opening * range of the door (for automatic doors). */ bool doorIsRangeEmpty(struct doorobj *door) { u32 stack; s16 *propnumptr; s16 propnums[256]; roomGetProps(door->base.prop->rooms, propnums, 256); propnumptr = propnums; while (*propnumptr >= 0) { struct prop *prop = &g_Vars.props[*propnumptr]; if (prop->type & (PROPTYPE_CHR | PROPTYPE_PLAYER)) { if (doorIsPosInRange(door, &prop->pos, 0, false)) { return false; } } else if (prop->type == PROPTYPE_OBJ) { if (prop->obj->hidden & (OBJHFLAG_MOUNTED | OBJHFLAG_GRABBED) && doorIsObjInRange(door, prop->obj, (prop->obj->hidden & OBJHFLAG_MOUNTED) != 0)) { return false; } } propnumptr++; } return true; } /** * Find automatic doors and open them if the player is close to them. */ void doorsCheckAutomatic(void) { struct prop *doorprop; s16 *propnumptr; s16 propnums[256]; roomGetProps(g_Vars.currentplayer->prop->rooms, propnums, 256); propnumptr = propnums; while (*propnumptr >= 0) { doorprop = &g_Vars.props[*propnumptr]; if (doorprop->type == PROPTYPE_DOOR) { struct doorobj *door = doorprop->door; if ((door->doorflags & DOORFLAG_AUTOMATIC) && doorIsUnlocked(g_Vars.currentplayer->prop, doorprop) && (door->mode == DOORMODE_CLOSING || (door->mode == DOORMODE_IDLE && door->frac <= 0))) { bool canopen = false; struct defaultobj *obj = NULL; bool isbike = false; struct doorobj *sibling; if (g_Vars.currentplayer->bondmovemode == MOVEMODE_GRAB) { obj = bmoveGetGrabbedProp()->obj; } else if (g_Vars.currentplayer->bondmovemode == MOVEMODE_BIKE) { obj = bmoveGetHoverbike()->obj; isbike = true; } if ((posIsInFrontOfDoor(&g_Vars.currentplayer->prop->pos, door) != vectorIsInFrontOfDoor(door, &g_Vars.currentplayer->bond2.unk00)) != 0) { canopen = doorIsPosInRange(door, &g_Vars.currentplayer->prop->pos, 0, isbike); if (!canopen && obj) { canopen = doorIsObjInRange(door, obj, isbike); } } sibling = door->sibling; while (sibling && sibling != door && !canopen) { if ((posIsInFrontOfDoor(&g_Vars.currentplayer->prop->pos, sibling) != vectorIsInFrontOfDoor(sibling, &g_Vars.currentplayer->bond2.unk00)) != 0) { canopen = doorIsPosInRange(sibling, &g_Vars.currentplayer->prop->pos, 0, isbike); if (!canopen && obj) { canopen = doorIsObjInRange(door, obj, isbike); } } sibling = sibling->sibling; } if (canopen) { doorsRequestMode(door, DOORMODE_OPENING); } } } propnumptr++; } } void func0f08c424(struct doorobj *door, Mtxf *matrix) { mtx3ToMtx4(door->base.realrot, matrix); mtx4SetTranslation(&door->base.prop->pos, matrix); if (door->doorflags & DOORFLAG_FLIP) { mtx00015edc(-1, matrix); } } void doorGetBbox(struct doorobj *door, struct modelrodata_bbox *dst) { struct modelrodata_bbox *bbox = modelFindBboxRodata(door->base.model); *dst = *bbox; if (door->doorflags & DOORFLAG_0004) { if (door->doortype == DOORTYPE_VERTICAL) { dst->ymax = bbox->ymax + (bbox->ymin - bbox->ymax) * door->frac; } else { dst->xmin = bbox->xmin + (bbox->xmax - bbox->xmin) * door->frac; } } } void doorUpdateTiles(struct doorobj *door) { struct modelrodata_bbox bbox; Mtxf spdc; struct geoblock *geo; Mtxf sp98; struct coord sp8c; struct coord sp80; struct pad *pad; if (door->doorflags & DOORFLAG_0080) { door->base.prop->pos.x = door->unk98.x * door->frac + door->startpos.x; door->base.prop->pos.y = door->unk98.y * door->frac + door->startpos.y; door->base.prop->pos.z = door->unk98.z * door->frac + door->startpos.z; } else if (door->doortype == DOORTYPE_SWINGING || door->doortype == DOORTYPE_AZTECCHAIR || door->doortype == DOORTYPE_HULL) { pad = &g_Pads[door->base.pad]; sp8c.x = pad->pos.x + pad->up.x * pad->bbox.ymin; sp8c.y = pad->pos.y + pad->up.y * pad->bbox.ymin; sp8c.z = pad->pos.z + pad->up.z * pad->bbox.ymin; if (door->doortype == DOORTYPE_AZTECCHAIR) { sp8c.x += pad->normal.x * pad->bbox.xmax; sp8c.y += pad->normal.y * pad->bbox.xmax; sp8c.z += pad->normal.z * pad->bbox.xmax; } else if (door->doortype == DOORTYPE_HULL) { sp8c.x += pad->look.x * pad->bbox.zmin; sp8c.y += pad->look.y * pad->bbox.zmin; sp8c.z += pad->look.z * pad->bbox.zmin; } else { if (door->base.flags & OBJFLAG_DOOR_OPENTOFRONT) { sp8c.x += pad->normal.x * pad->bbox.xmax; sp8c.y += pad->normal.y * pad->bbox.xmax; sp8c.z += pad->normal.z * pad->bbox.xmax; } else { sp8c.x += pad->normal.x * pad->bbox.xmin; sp8c.y += pad->normal.y * pad->bbox.xmin; sp8c.z += pad->normal.z * pad->bbox.xmin; } } sp80.x = door->startpos.x - sp8c.x; sp80.y = door->startpos.y - sp8c.y; sp80.z = door->startpos.z - sp8c.z; mtx3ToMtx4(door->mtx98, &spdc); mtx4LoadTranslation(&sp80, &sp98); mtx4MultMtx4InPlace(&sp98, &spdc); if (door->doortype == DOORTYPE_AZTECCHAIR) { if (door->base.flags & OBJFLAG_DOOR_OPENTOFRONT) { mtx4LoadZRotation(M_BADTAU - door->frac * 0.017450513318181f, &sp98); } else { mtx4LoadZRotation(door->frac * 0.017450513318181f, &sp98); } } else if (door->doortype == DOORTYPE_HULL) { if (door->base.flags & OBJFLAG_DOOR_OPENTOFRONT) { guRotateF(sp98.m, 360 - door->frac, pad->normal.x, pad->normal.y, pad->normal.z); } else { guRotateF(sp98.m, door->frac, pad->normal.x, pad->normal.y, pad->normal.z); } } else { if (door->base.flags & OBJFLAG_DOOR_OPENTOFRONT) { mtx4LoadYRotation(M_BADTAU - door->frac * 0.017450513318181f, &sp98); } else { mtx4LoadYRotation(door->frac * 0.017450513318181f, &sp98); } } mtx4MultMtx4InPlace(&sp98, &spdc); mtx4LoadTranslation(&sp8c, &sp98); mtx4MultMtx4InPlace(&sp98, &spdc); mtx4ToMtx3(&spdc, door->base.realrot); door->base.prop->pos.x = spdc.m[3][0]; door->base.prop->pos.y = spdc.m[3][1]; door->base.prop->pos.z = spdc.m[3][2]; } doorGetBbox(door, &bbox); if (door->frac >= door->perimfrac) { door->base.hidden |= OBJHFLAG_DOORPERIMDISABLED; return; } geo = door->base.geoblock; door->base.hidden &= ~OBJHFLAG_DOORPERIMDISABLED; if ((door->doorflags & DOORFLAG_0020) == 0) { func0f08c424(door, &spdc); objCalculateGeoBlockFromBboxAndMtx(&bbox, &spdc, geo); if (door->doortype == DOORTYPE_VERTICAL) { door->doorflags |= DOORFLAG_0020; } } if (door->doortype == DOORTYPE_VERTICAL) { geo->ymin = door->startpos.y + objGetRotatedLocalYMinByMtx3(&bbox, door->base.realrot); } else if (door->doortype == DOORTYPE_FALLAWAY) { geo->ymin = door->base.prop->pos.y - 10000; } else if (door->doorflags & DOORFLAG_0001) { geo->ymin -= 1000; } if ((door->doortype == DOORTYPE_EYE && door->frac > 0.4f * door->maxfrac) || (door->doortype == DOORTYPE_IRIS && door->frac > 0.4f * door->maxfrac)) { geo->ymax = geo->ymin + 50; } else if (door->doortype == DOORTYPE_FALLAWAY) { geo->ymax = door->base.prop->pos.y + 1000; } else if (door->doorflags & DOORFLAG_0001) { geo->ymax += 1000; } } #define NEXT1() (j + 1) % 4 #define NEXT2() (j + 2) % 4 #define NEXT3() (j + 3) % 4 void door0f08cb20(struct doorobj *door, struct gfxvtx *src, struct gfxvtx *dst, s32 numvertices) { s32 i; s32 j; s16 ref; struct modelrodata_bbox bbox; s32 stack[5]; doorGetBbox(door, &bbox); if (door->doortype == DOORTYPE_VERTICAL) { ref = ceilf(bbox.ymax); } else { ref = floorf(bbox.xmin); } for (i = 0; i < numvertices / 4; i++) { struct gfxvtx *psrc = &src[i * 4]; struct gfxvtx *pdst = &dst[i * 4]; for (j = 0; j < 4; j++) { if (j == 0) { pdst[j] = psrc[j]; pdst[NEXT1()] = psrc[NEXT1()]; pdst[NEXT2()] = psrc[NEXT2()]; pdst[NEXT3()] = psrc[NEXT3()]; if (1); } if (door->doortype == DOORTYPE_VERTICAL) { if (psrc[j].y >= ref) { if (psrc[NEXT1()].x == psrc[j].x && psrc[NEXT1()].z == psrc[j].z && psrc[NEXT1()].y != psrc[j].y) { pdst[j].s = psrc[j].s + (psrc[j].y - ref) * (psrc[NEXT1()].s - psrc[j].s) / (psrc[j].y - psrc[NEXT1()].y); pdst[j].t = psrc[j].t + (psrc[j].y - ref) * (psrc[NEXT1()].t - psrc[j].t) / (psrc[j].y - psrc[NEXT1()].y); } else if (psrc[NEXT2()].x == psrc[j].x && psrc[NEXT2()].z == psrc[j].z && psrc[NEXT2()].y != psrc[j].y) { pdst[j].s = psrc[j].s + (psrc[j].y - ref) * (psrc[NEXT2()].s - psrc[j].s) / (psrc[j].y - psrc[NEXT2()].y); pdst[j].t = psrc[j].t + (psrc[j].y - ref) * (psrc[NEXT2()].t - psrc[j].t) / (psrc[j].y - psrc[NEXT2()].y); } else if (psrc[NEXT3()].x == psrc[j].x && psrc[NEXT3()].z == psrc[j].z && psrc[NEXT3()].y != psrc[j].y) { pdst[j].s = psrc[j].s + (psrc[j].y - ref) * (psrc[NEXT3()].s - psrc[j].s) / (psrc[j].y - psrc[NEXT3()].y); pdst[j].t = psrc[j].t + (psrc[j].y - ref) * (psrc[NEXT3()].t - psrc[j].t) / (psrc[j].y - psrc[NEXT3()].y); } pdst[j].y = ref; } } else { if (psrc[j].x <= ref) { if (psrc[NEXT1()].y == psrc[j].y && psrc[NEXT1()].z == psrc[j].z && psrc[NEXT1()].x != psrc[j].x) { pdst[j].s = psrc[j].s + (ref - psrc[j].x) * (psrc[NEXT1()].s - psrc[j].s) / (psrc[NEXT1()].x - psrc[j].x); pdst[j].t = psrc[j].t + (ref - psrc[j].x) * (psrc[NEXT1()].t - psrc[j].t) / (psrc[NEXT1()].x - psrc[j].x); } else if (psrc[NEXT2()].y == psrc[j].y && psrc[NEXT2()].z == psrc[j].z && psrc[NEXT2()].x != psrc[j].x) { pdst[j].s = psrc[j].s + (ref - psrc[j].x) * (psrc[NEXT2()].s - psrc[j].s) / (psrc[NEXT2()].x - psrc[j].x); pdst[j].t = psrc[j].t + (ref - psrc[j].x) * (psrc[NEXT2()].t - psrc[j].t) / (psrc[NEXT2()].x - psrc[j].x); } else if (psrc[NEXT3()].y == psrc[j].y && psrc[NEXT3()].z == psrc[j].z && psrc[NEXT3()].x != psrc[j].x) { pdst[j].s = psrc[j].s + (ref - psrc[j].x) * (psrc[NEXT3()].s - psrc[j].s) / (psrc[NEXT3()].x - psrc[j].x); pdst[j].t = psrc[j].t + (ref - psrc[j].x) * (psrc[NEXT3()].t - psrc[j].t) / (psrc[NEXT3()].x - psrc[j].x); } pdst[j].x = ref; } } } } } void func0f08d3dc(struct doorobj *door) { func0f069b4c(&door->base); if (door->doorflags & DOORFLAG_0004) { struct modelnode *node = func0f0687e4(door->base.model); union modelrodata *rodata = node->rodata; union modelrwdata *rwdata = modelGetNodeRwData(door->base.model, node); rwdata->dl.vertices = gfxAllocateVertices(rodata->dl.numvertices); door0f08cb20(door, rodata->dl.vertices, rwdata->dl.vertices, rodata->dl.numvertices); } } void func0f08d460(struct doorobj *door) { if ((door->doorflags & (DOORFLAG_0004 | DOORFLAG_0080)) == (DOORFLAG_0004 | DOORFLAG_0080)) { struct modelnode *node = func0f0687e4(door->base.model); union modelrodata *rodata = node->rodata; union modelrwdata *rwdata = modelGetNodeRwData(door->base.model, node); if (rwdata->dl.vertices != door->unka4) { door0f08cb20(door, rodata->dl.vertices, door->unka4, rodata->dl.numvertices); } rwdata->dl.vertices = door->unka4; } } void doorActivatePortal(struct doorobj *door) { if (door->portalnum >= 0) { portalSetOpen(door->portalnum, true); } } void doorDeactivatePortal(struct doorobj *door) { if (door->portalnum >= 0) { portalSetOpen(door->portalnum, false); } } struct prop *doorInit(struct doorobj *door, struct coord *pos, Mtxf *mtx, s16 *rooms, struct coord *coord, struct coord *centre) { struct prop *prop; union modelrodata *rodata; Mtxf sp38; s16 sp28[8]; door->base.flags |= OBJFLAG_00000100; prop = objInitWithAutoModel(&door->base); if (prop != NULL) { switch (door->doortype) { case DOORTYPE_SLIDING: case DOORTYPE_FLEXI1: case DOORTYPE_FLEXI2: case DOORTYPE_FLEXI3: case DOORTYPE_VERTICAL: case DOORTYPE_FALLAWAY: case DOORTYPE_LASER: door->doorflags |= DOORFLAG_0080; break; } mtx4Copy(mtx, &sp38); mtx00015f04(g_ModelStates[door->base.modelnum].scale * (1.0f / 4096.0f), &sp38); mtx4ToMtx3(&sp38, door->base.realrot); door->frac = (door->base.flags & OBJFLAG_DOOR_KEEPOPEN) ? door->maxfrac : 0; door->fracspeed = 0; door->mode = DOORMODE_IDLE; door->glasshits = 0; door->portalnum = -1; door->startpos = *centre; if (door->doorflags & DOORFLAG_0080) { door->unk98 = *coord; if (door->doorflags & DOORFLAG_0004) { struct modelnode *node = func0f0687e4(door->base.model); rodata = node->rodata; door->unka4 = mempAlloc(ALIGN16(rodata->dl.numvertices * sizeof(struct gfxvtx)), MEMPOOL_STAGE); } else { door->unka4 = NULL; } } else { mtx4ToMtx3(&sp38, door->mtx98); } func0f065e74(pos, rooms, centre, sp28); prop->type = PROPTYPE_DOOR; prop->door = door; prop->pos = *centre; propDeregisterRooms(prop); roomsCopy(sp28, prop->rooms); doorUpdateTiles(door); func0f069c70(&door->base, false, true); func0f08d3dc(door); door->base.shadecol[0] = door->base.nextcol[0]; door->base.shadecol[1] = door->base.nextcol[1]; door->base.shadecol[2] = door->base.nextcol[2]; door->base.shadecol[3] = door->base.nextcol[3]; } if (door); if (door); return prop; } void doorPlayOpeningSound(s32 soundtype, struct prop *prop) { s32 sound1 = 0; s32 sound2 = 0; s32 sound3 = 0; func0f0926bc(prop, 12, 0xffff); if (g_Vars.in_cutscene && (prop->type & (PROPTYPE_OBJ | PROPTYPE_DOOR)) && (prop->obj->flags3 & OBJFLAG3_AUTOCUTSCENESOUNDS) == 0) { return; } switch (soundtype) { case 28: sound1 = SFX_DOOR_8007; break; case 1: sound1 = SFX_DOOR_801A; sound2 = SFX_DOOR_801B; break; case 29: sound1 = SFX_DOOR_8015; sound2 = SFX_DOOR_801D; break; case 2: sound1 = SFX_DOOR_801A; sound2 = SFX_DOOR_801C; break; case 3: sound1 = SFX_DOOR_8014; sound2 = SFX_DOOR_8016; break; case 4: sound1 = SFX_DOOR_801E; sound2 = SFX_DOOR_8020; break; case 5: sound1 = SFX_DOOR_8001; break; case 6: sound1 = SFX_DOOR_8004; break; case 7: sound1 = SFX_DOOR_8005; break; case 8: sound1 = SFX_DOOR_800A; sound2 = SFX_DOOR_8008; break; case 9: sound1 = SFX_DOOR_8004; sound2 = SFX_DOOR_800B; break; case 10: sound1 = SFX_DOOR_800C; break; case 11: sound1 = SFX_DOOR_800E; break; case 12: sound1 = SFX_DOOR_8010; break; case 13: sound1 = SFX_DOOR_8012; break; case 30: sound1 = SFX_DOOR_816B; sound2 = SFX_DOOR_81AA; break; case 14: sound1 = SFX_DOOR_8017; sound2 = SFX_DOOR_8019; break; case 15: sound1 = SFX_DOOR_8022; break; case 25: sound1 = SFX_DOOR_81B8; break; case 16: sound1 = SFX_DOOR_8026; break; case 17: sound1 = SFX_DOOR_801E; break; case 18: sound1 = SFX_DOOR_81B0; sound2 = SFX_DOOR_8014; sound3 = SFX_DOOR_8016; break; case 19: sound1 = SFX_DOOR_81AE; sound2 = SFX_DOOR_81B3; break; case 26: sound1 = SFX_DOOR_042C; sound2 = SFX_DOOR_042B; break; case 20: sound1 = SFX_DOOR_81B1; sound2 = SFX_DOOR_81B6; break; case 21: sound1 = SFX_DOOR_81A8; sound2 = SFX_DOOR_81AA; break; case 32: sound1 = SFX_DOOR_81AB; sound2 = SFX_DOOR_81AD; break; case 31: sound1 = SFX_DOOR_81AB; sound2 = SFX_DOOR_81B4; break; case 22: sound1 = SFX_DOOR_81AE; sound2 = SFX_DOOR_81B5; break; case 23: sound1 = SFX_DOOR_80AC; sound2 = SFX_DOOR_80AE; break; case 24: sound1 = SFX_DOOR_816B; sound2 = SFX_DOOR_816C; break; case 27: sound1 = SFX_DOOR_8014; sound2 = SFX_DOOR_042B; break; } if (sound1) { propsnd0f0939f8(NULL, prop, sound1, -1, -1, 1024, 0, 12, 0, -1, 0, -1, -1, -1, -1); } if (sound2) { propsnd0f0939f8(NULL, prop, sound2, -1, -1, 0, 0, 12, 0, -1, 0, -1, -1, -1, -1); } if (sound3) { propsnd0f0939f8(NULL, prop, sound3, -1, -1, 0, 0, 12, 0, -1, 0, -1, -1, -1, -1); } } /** * This is identical to the function above but with less cases. */ void doorPlayClosingSound(s32 soundtype, struct prop *prop) { s32 sound1 = 0; s32 sound2 = 0; s32 sound3 = 0; func0f0926bc(prop, 12, 0xffff); if (g_Vars.in_cutscene && (prop->type & (PROPTYPE_OBJ | PROPTYPE_DOOR)) && (prop->obj->flags3 & OBJFLAG3_AUTOCUTSCENESOUNDS) == 0) { return; } switch (soundtype) { case 28: sound1 = SFX_DOOR_8007; break; case 1: sound1 = SFX_DOOR_801A; sound2 = SFX_DOOR_801B; break; case 29: sound1 = SFX_DOOR_8015; sound2 = SFX_DOOR_801D; break; case 2: sound1 = SFX_DOOR_801A; sound2 = SFX_DOOR_801C; break; case 3: sound1 = SFX_DOOR_8014; sound2 = SFX_DOOR_8016; break; case 4: sound1 = SFX_DOOR_801E; sound2 = SFX_DOOR_8020; break; case 5: sound1 = SFX_DOOR_8001; break; case 8: sound1 = SFX_DOOR_800A; sound2 = SFX_DOOR_8008; break; case 9: sound1 = SFX_DOOR_8004; sound2 = SFX_DOOR_800B; break; case 10: sound1 = SFX_DOOR_800C; break; case 30: sound1 = SFX_DOOR_816B; sound2 = SFX_DOOR_81AA; break; case 14: sound1 = SFX_DOOR_8017; sound2 = SFX_DOOR_8019; break; case 15: sound1 = SFX_DOOR_8022; break; case 25: sound1 = SFX_DOOR_81B8; break; case 16: sound1 = SFX_DOOR_8026; break; case 17: sound1 = SFX_DOOR_801E; break; case 18: sound1 = SFX_DOOR_81B0; sound2 = SFX_DOOR_8014; sound3 = SFX_DOOR_8016; break; case 23: sound1 = SFX_DOOR_80AC; sound2 = SFX_DOOR_80AE; break; case 24: sound1 = SFX_DOOR_816B; sound2 = SFX_DOOR_816C; break; } if (sound1) { propsnd0f0939f8(NULL, prop, sound1, -1, -1, 1024, 0, 12, 0, -1, 0, -1, -1, -1, -1); } if (sound2) { propsnd0f0939f8(NULL, prop, sound2, -1, -1, 0, 0, 12, 0, -1, 0, -1, -1, -1, -1); } if (sound3) { propsnd0f0939f8(NULL, prop, sound3, -1, -1, 0, 0, 12, 0, -1, 0, -1, -1, -1, -1); } } void doorPlayOpenedSound(s32 soundtype, struct prop *prop) { s32 sound = 0; func0f0926bc(prop, 12, 0xffff); if (g_Vars.in_cutscene && (prop->type & (PROPTYPE_OBJ | PROPTYPE_DOOR)) && (prop->obj->flags3 & OBJFLAG3_AUTOCUTSCENESOUNDS) == 0) { return; } switch (soundtype) { case 28: sound = SFX_DOOR_801A; break; case 1: sound = SFX_DOOR_801A; break; case 29: sound = SFX_DOOR_8015; break; case 2: sound = SFX_DOOR_801A; break; case 3: sound = SFX_DOOR_8015; break; case 4: sound = SFX_DOOR_801F; break; case 5: sound = SFX_DOOR_8002; break; case 8: sound = SFX_DOOR_801A; break; case 9: sound = SFX_DOOR_8003; break; case 10: sound = SFX_DOOR_800D; break; case 30: sound = SFX_DOOR_816D; break; case 14: sound = SFX_DOOR_816D; break; case 15: sound = SFX_DOOR_8021; break; case 25: sound = SFX_DOOR_81B7; break; case 16: sound = SFX_DOOR_8027; break; case 17: sound = SFX_DOOR_801F; break; case 18: sound = SFX_DOOR_8015; break; case 26: sound = SFX_DOOR_042C; break; case 19: sound = SFX_DOOR_81AF; break; case 20: sound = SFX_DOOR_81B2; break; case 21: sound = SFX_DOOR_81A8; break; case 32: sound = SFX_DOOR_81AB; break; case 31: sound = SFX_DOOR_81AB; break; case 22: sound = SFX_DOOR_81AF; break; case 23: sound = SFX_DOOR_80AD; break; case 24: sound = SFX_DOOR_816D; break; case 27: sound = SFX_DOOR_8015; break; } if (sound) { propsnd0f0939f8(NULL, prop, sound, -1, -1, 1024, 0, 12, 0, -1, 0, -1, -1, -1, -1); } } void doorPlayClosedSound(s32 soundtype, struct prop *prop) { s32 sound = 0; func0f0926bc(prop, 12, 0xffff); if (g_Vars.in_cutscene && (prop->type & (PROPTYPE_OBJ | PROPTYPE_DOOR)) && (prop->obj->flags3 & OBJFLAG3_AUTOCUTSCENESOUNDS) == 0) { return; } switch (soundtype) { case 28: sound = SFX_DOOR_801A; break; case 1: sound = SFX_DOOR_801A; break; case 29: sound = SFX_DOOR_8015; break; case 2: sound = SFX_DOOR_801A; break; case 3: sound = SFX_DOOR_8015; break; case 4: sound = SFX_DOOR_801F; break; case 5: sound = SFX_DOOR_8002; break; case 6: sound = SFX_DOOR_8003; break; case 7: sound = SFX_DOOR_8006; break; case 8: sound = SFX_DOOR_801A; break; case 9: sound = SFX_DOOR_8003; break; case 10: sound = SFX_DOOR_800D; break; case 11: sound = SFX_DOOR_800F; break; case 12: sound = SFX_DOOR_8011; break; case 13: sound = SFX_DOOR_8013; break; case 30: sound = SFX_DOOR_816D; break; case 14: sound = SFX_DOOR_8018; break; case 15: sound = SFX_DOOR_8021; break; case 25: sound = SFX_DOOR_81B7; break; case 16: sound = SFX_DOOR_8027; break; case 17: sound = SFX_DOOR_801F; break; case 18: sound = SFX_DOOR_8015; break; case 23: sound = SFX_DOOR_80AD; break; case 24: sound = SFX_DOOR_816D; break; } if (sound) { propsnd0f0939f8(NULL, prop, sound, -1, -1, 1024, 0, 12, 0, -1, 0, -1, -1, -1, -1); } } /** * Play the door open sound, activate the door's portal, * and configure the laser fade properties if it's a laser. */ void doorStartOpen(struct doorobj *door) { door->base.flags &= ~OBJFLAG_DOOR_KEEPOPEN; door->base.hidden |= OBJHFLAG_00000200; doorPlayOpeningSound(door->soundtype, door->base.prop); doorActivatePortal(door); if (door->doortype == DOORTYPE_FALLAWAY) { struct geocyl *cyl = door->base.geocyl; door->base.flags |= OBJFLAG_CANNOT_ACTIVATE; door->perimfrac = 0; if (cyl && (door->base.flags & OBJFLAG_00000100)) { cyl->header.numvertices = 0; door->base.flags &= ~OBJFLAG_00000100; } } door->fadetime60 = door->doortype == DOORTYPE_LASER ? TICKS(60) : 0; if (door->doortype == DOORTYPE_LASER) { door->laserfade = 255; } } /** * Play the door close sound and configure the * laser fade properties if it's a laser. */ void doorStartClose(struct doorobj *door) { door->base.flags &= ~OBJFLAG_DOOR_KEEPOPEN; doorPlayClosingSound(door->soundtype, door->base.prop); door->fadetime60 = door->doortype == DOORTYPE_LASER ? TICKS(60) : 0; if (door->doortype == DOORTYPE_LASER) { door->laserfade = 0; } } void doorFinishOpen(struct doorobj *door) { doorPlayOpenedSound(door->soundtype, door->base.prop); if (door->doortype == DOORTYPE_FALLAWAY) { func0f0685e4(door->base.prop); if (door->base.hidden & OBJHFLAG_PROJECTILE) { door->base.projectile->flags |= PROJECTILEFLAG_AIRBORNE; mtx4LoadIdentity((Mtxf *)&door->base.projectile->mtx); } } if (door->doortype == DOORTYPE_LASER) { door->laserfade = 0; } } extern s32 osCicId; void doorFinishClose(struct doorobj *door) { bool pass = true; struct doorobj *loopdoor; doorPlayClosedSound(door->soundtype, door->base.prop); loopdoor = door; while (loopdoor) { if (loopdoor->frac > 0 && loopdoor->portalnum == door->portalnum) { pass = false; } loopdoor = loopdoor->sibling; if (loopdoor == door) { break; } } if (pass) { doorDeactivatePortal(door); } if (door->doortype == DOORTYPE_LASER && door->fadetime60 == 0) { door->laserfade = 255; } } /** * Apply the given mode to an individual door (not its siblings). * * Handles playing door open/close sounds and activating the portal if opening. */ void doorSetMode(struct doorobj *door, s32 newmode) { if (newmode == DOORMODE_OPENING) { if (door->mode == DOORMODE_IDLE || door->mode == DOORMODE_WAITING) { if (!door->base.prop->active && (door->base.prop->flags & PROPFLAG_ENABLED)) { propUnpause(door->base.prop); } doorStartOpen(door); } door->mode = newmode; } else if (newmode == DOORMODE_CLOSING) { if (door->mode == DOORMODE_IDLE && door->frac > 0) { doorStartClose(door); } if ((door->mode != DOORMODE_IDLE && door->mode != DOORMODE_WAITING) || door->frac > 0) { door->mode = newmode; } else if (door->mode == DOORMODE_WAITING) { door->mode = DOORMODE_IDLE; } } else { door->mode = newmode; } } /** * Request that the door and its siblings be applied the given mode * (opening or closing). * * When opening an airlock-style door (eg. GE Dam gate), the requested mode is * modified so that the sibling begins closing instead, and the main door waits * for the sibling before it opens. */ void doorsRequestMode(struct doorobj *door, s32 newmode) { struct doorobj *sibling; s32 siblingmode = newmode; if ((door->base.flags2 & OBJFLAG2_AIRLOCKDOOR) && newmode == DOORMODE_OPENING) { siblingmode = DOORMODE_CLOSING; if (door->mode == DOORMODE_IDLE) { newmode = DOORMODE_WAITING; } } doorSetMode(door, newmode); sibling = door->sibling; while (sibling && sibling != door) { doorSetMode(sibling, siblingmode); sibling = sibling->sibling; } } s32 doorIsClosed(struct doorobj *door) { return (door->mode == DOORMODE_IDLE || door->mode == DOORMODE_WAITING) && door->frac <= 0; } s32 doorIsOpen(struct doorobj *door) { return (door->mode == DOORMODE_IDLE || door->mode == DOORMODE_WAITING) && door->frac >= door->maxfrac; } s32 func0f08e5a8(s16 *rooms2, struct screenbox *box) { bool result = false; s16 *rooms = rooms2; s32 roomnum = *rooms; while (roomnum != -1) { struct var800a4640_00 *thing = func0f158140(roomnum); if (thing != var8007fc24) { if (result) { if (box->xmin > thing->box.xmin) { box->xmin = thing->box.xmin; } if (box->ymin > thing->box.ymin) { box->ymin = thing->box.ymin; } if (box->xmax < thing->box.xmax) { box->xmax = thing->box.xmax; } if (box->ymax < thing->box.ymax) { box->ymax = thing->box.ymax; } } else { box->xmin = thing->box.xmin; box->ymin = thing->box.ymin; box->xmax = thing->box.xmax; box->ymax = thing->box.ymax; } result = true; } rooms++; roomnum = *rooms; } return result; } f32 func0f08e6bc(struct prop *prop, f32 arg1) { f32 result = 1; struct coord *coord = env0f1667e8(); if (coord != NULL && coord->z < prop->z) { f32 scalez = camGetLodScaleZ(); f32 value = ((prop->z - coord->z) * 100.0f / arg1 + coord->z) * scalez; if (value >= coord->y) { result = 0; } else if (value > coord->x) { result = (coord->y - value) / (coord->y - coord->x); } } return result; } bool func0f08e794(struct coord *coord, f32 arg1) { bool result = true; struct coord *ptr = env0f1667e8(); struct coord tmp; f32 sp20; if (ptr != NULL) { struct coord *campos = &g_Vars.currentplayer->cam_pos; Mtxf *mtx = camGetWorldToScreenMtxf(); tmp.x = coord->x - campos->x; tmp.y = coord->y - campos->y; tmp.z = coord->z - campos->z; sp20 = tmp.f[0] * mtx->m[0][0] + tmp.f[1] * mtx->m[0][1] + tmp.f[2] * mtx->m[0][2]; if (sp20 > ptr->z) { f32 scalez = camGetLodScaleZ(); sp20 = ((sp20 - ptr->z) * 100 / arg1 + ptr->z) * scalez; if (sp20 >= ptr->y) { result = false; } } } return result; } bool func0f08e8ac(struct prop *prop, struct coord *pos, f32 arg2, bool arg3) { s16 *rooms; s32 roomnum; bool result = false; u32 stack; rooms = prop->rooms; roomnum = *rooms; while (roomnum != -1) { if (g_Rooms[roomnum].flags & ROOMFLAG_ONSCREEN) { if (env0f1666f8(pos, arg2) && (!arg3 || func0f08e794(pos, arg2))) { result = camIsPosInFovAndVisibleRoom(prop->rooms, pos, arg2); if (result) { struct coord *campos = &g_Vars.currentplayer->cam_pos; f32 xdiff = pos->x - campos->x; f32 ydiff = pos->y - campos->y; f32 zdiff = pos->z - campos->z; if (xdiff * xdiff + ydiff * ydiff + zdiff * zdiff > 32000 * 32000) { result = false; } } } break; } rooms++; roomnum = *rooms; result = false; } return result; } bool posIsInDrawDistance(struct coord *pos) { struct coord *campos = &g_Vars.currentplayer->cam_pos; f32 x = pos->x - campos->x; f32 y = pos->y - campos->y; f32 z = pos->z - campos->z; f32 aggregate = x * x + y * y + z * z; bool result = true; if (aggregate > 1024000000) { result = false; } return result; } void doorCreateSparks(struct doorobj *door) { struct pad *pad; struct coord sp88; struct coord sp7c; struct coord sp70; s32 i; pad = &g_Pads[door->base.pad]; sp88.x = sp7c.f[0] = pad->pos.f[0] + pad->up.f[0] * (pad->bbox.ymin + (1 - door->frac) * (pad->bbox.ymax - pad->bbox.ymin)); sp88.y = sp7c.f[1] = pad->pos.f[1] + pad->up.f[1] * (pad->bbox.ymin + (1 - door->frac) * (pad->bbox.ymax - pad->bbox.ymin)); sp88.z = sp7c.f[2] = pad->pos.f[2] + pad->up.f[2] * (pad->bbox.ymin + (1 - door->frac) * (pad->bbox.ymax - pad->bbox.ymin)); sp88.x += pad->look.f[0] * pad->bbox.zmax; sp88.y += pad->look.f[1] * pad->bbox.zmax; sp88.z += pad->look.f[2] * pad->bbox.zmax; sp7c.x += pad->look.f[0] * pad->bbox.zmin; sp7c.y += pad->look.f[1] * pad->bbox.zmin; sp7c.z += pad->look.f[2] * pad->bbox.zmin; sp70.x = -pad->up.x; sp70.y = -pad->up.y; sp70.z = -pad->up.z; sparksCreate(door->base.prop->rooms[0], door->base.prop, &sp88, &sp70, &pad->up, SPARKTYPE_ENVIRONMENTAL1); sparksCreate(door->base.prop->rooms[0], door->base.prop, &sp7c, &sp70, &pad->up, SPARKTYPE_ENVIRONMENTAL1); if (random() % 2) { sparksCreate(door->base.prop->rooms[0], door->base.prop, &sp88, &sp70, &pad->up, SPARKTYPE_ENVIRONMENTAL4); } else { sparksCreate(door->base.prop->rooms[0], door->base.prop, &sp88, &sp70, &pad->up, SPARKTYPE_ENVIRONMENTAL5); } propsnd0f0939f8(NULL, door->base.prop, propsndGetRandomSparkSound(), -1, -1, 0, 0, 0, &sp88, -1, door->base.prop->rooms, -1, -1, -1, -1); propsnd0f0939f8(NULL, door->base.prop, propsndGetRandomSparkSound(), -1, -1, 0, 0, 0, &sp7c, -1, door->base.prop->rooms, -1, -1, -1, -1); for (i = 0; door->base.prop->rooms[i] != -1; i++) { roomAdjustLighting(door->base.prop->rooms[i], 128, 200); } } /** * Calculate/tick a door's frac (the amount it's open) without any consideration * for props which might be blocking the door. The new frac is written to the * door's frac property. * * Return true if collision checks should be considered. * * Also handles sticky doors such as the ones in Skedar Ruins. */ bool doorCalcIntendedFrac(struct doorobj *door) { bool checkcollision = false; if (door->doortype == DOORTYPE_LASER && door->fadetime60 != 0) { door->fadetime60 -= g_Vars.lvupdate60; if (door->fadetime60 < 0) { door->fadetime60 = 0; } if (door->mode == DOORMODE_OPENING) { door->laserfade = (u32)((door->fadetime60 * 255.0f) / TICKS(60.0f)); return false; } door->laserfade = (u32)(((TICKS(60.0f) - door->fadetime60) * 255.0f) / TICKS(60.0f)); } if (door->mode == DOORMODE_OPENING || door->mode == DOORMODE_CLOSING) { f32 end = door->mode == DOORMODE_OPENING ? door->maxfrac : 0; // Skedar Ruins random door stuckage if (door->base.flags3 & OBJFLAG3_DOOR_STICKY) { s32 value = (random() % 64) + 30; if ((g_Vars.lvframenum % value) == 0) { bool dothething = false; struct doorobj *loopdoor; door->fracspeed = 0.0f; doorCreateSparks(door); if (random() % 2) { dothething = true; func0f0926bc(door->base.prop, 12, 0xffff); door->mode = DOORMODE_IDLE; door->lastopen60 = g_Vars.lvframe60; } loopdoor = door; while (loopdoor) { if (random() % 2 && loopdoor->mode != DOORMODE_IDLE) { loopdoor->fracspeed = 0.0f; doorCreateSparks(loopdoor); if (dothething) { func0f0926bc(loopdoor->base.prop, 12, 0xffff); loopdoor->mode = DOORMODE_IDLE; loopdoor->lastopen60 = g_Vars.lvframe60; } } loopdoor = loopdoor->sibling; if (loopdoor == door) { break; } } doorPlayClosedSound(door->soundtype, door->base.prop); } } applySpeed(&door->frac, end, &door->fracspeed, door->accel, door->decel, door->maxspeed); if (door->frac >= door->maxfrac) { door->frac = door->maxfrac; } else if (door->frac <= 0.0f) { door->frac = 0.0f; } checkcollision = true; } return checkcollision; } /** * Calculate the frac of a door and its siblings, taking into consideration * objects which may be blocking the door. * * For each sibling, the original frac is backed up into the sibling's * lastcalc60 field. The desired frac is then calculated and set in the * sibling's frac property. Then collision checks are done, and the original * frac is restored if any sibling is blocked. * * Chrs who are blocking a lift door may be warped out of the way. */ void doorsCalcFrac(struct doorobj *door) { bool checkcollision = false; s32 cdresult = CDRESULT_NOCOLLISION; struct doorobj *loopdoor; struct doorobj *loopdoor2; f32 frac; s32 numsameportal; // For each sibling door, save their previous frac // then calculate the new desired frac loopdoor = door; while (loopdoor) { *(f32 *)&loopdoor->lastcalc60 = loopdoor->frac; if (doorCalcIntendedFrac(loopdoor)) { checkcollision = true; } loopdoor = loopdoor->sibling; if (loopdoor == door) { break; } } // Do collision checks if (checkcollision) { loopdoor = door; while (loopdoor) { struct prop *loopprop; doorUpdateTiles(loopdoor); setup0f0923d4(&loopdoor->base); loopprop = loopdoor->base.prop; if ((door->doorflags & DOORFLAG_UNBLOCKABLEOPEN) == 0 || loopdoor->mode == DOORMODE_CLOSING) { propSetPerimEnabled(loopprop, false); cdresult = cdTestBlockOverlapsAnyProp(loopdoor->base.geoblock, loopprop->rooms, CDTYPE_OBJS | CDTYPE_PLAYERS | CDTYPE_CHRS | CDTYPE_PATHBLOCKER | CDTYPE_OBJSNOTSAFEORHELI); propSetPerimEnabled(loopprop, true); if (cdresult == CDRESULT_COLLISION) { struct prop *blockerprop = cdGetObstacleProp(); if (blockerprop && blockerprop->type == PROPTYPE_CHR) { struct chrdata *chr = blockerprop->chr; chr->hidden |= CHRHFLAG_BLOCKINGDOOR; // Consider warping the chr past the lift door if (!g_Vars.normmplayerisrunning && (chr->hidden & CHRHFLAG_00400000)) { bool isliftdoor = false; loopdoor2 = loopdoor; while (loopdoor2) { if (loopdoor2->base.hidden & OBJHFLAG_LIFTDOOR) { isliftdoor = true; break; } loopdoor2 = loopdoor2->sibling; if (loopdoor2 == loopdoor) { break; } } if (isliftdoor) { if (chr->actiontype == ACT_STAND || (chr->actiontype == ACT_ATTACK && (chr->act_attack.flags & ATTACKFLAG_DONTTURN)) || (chr->actiontype == ACT_GOPOS && chrGoPosIsWaiting(chr))) { struct prop *target = chrGetTargetProp(chr); if (chrGoToRoomPos(chr, &target->pos, target->rooms, 0)) { chr->goposforce = TICKS(600); } } else if (chr->actiontype == ACT_GOPOS) { if (chr->goposforce >= 0 || chr->lastmoveok60 < g_Vars.lvframe60 - TICKS(60)) { chr->goposforce = TICKS(600); } } } } } break; } } loopdoor = loopdoor->sibling; if (loopdoor == door) { break; } } } // Finalise the frac loopdoor = door; while (loopdoor) { if (checkcollision) { if (cdresult != CDRESULT_COLLISION) { // No collision - handle door becoming fully open or closed if (loopdoor->mode == DOORMODE_OPENING) { if (loopdoor->frac >= loopdoor->maxfrac) { loopdoor->mode = DOORMODE_IDLE; loopdoor->fracspeed = 0; loopdoor->lastopen60 = g_Vars.lvframe60; doorFinishOpen(loopdoor); } } else { if (loopdoor->mode == DOORMODE_CLOSING && loopdoor->frac <= 0) { loopdoor->mode = DOORMODE_IDLE; loopdoor->fracspeed = 0; loopdoor->lastopen60 = 0; doorFinishClose(loopdoor); } } func0f069c70(&loopdoor->base, false, false); func0f08d3dc(loopdoor); } else { // Door is blocked - restore the original frac loopdoor->fracspeed = 0; loopdoor->frac = *(f32 *)&loopdoor->lastcalc60; doorUpdateTiles(loopdoor); setup0f0923d4(&loopdoor->base); func0f08d460(loopdoor); } } else { func0f08d460(loopdoor); } loopdoor->lastcalc60 = g_Vars.lvframe60; loopdoor = loopdoor->sibling; if (loopdoor == door) { break; } } // Update portal visibility frac = 0; numsameportal = 0; if (checkcollision) { loopdoor = door; while (loopdoor) { if (loopdoor->portalnum == door->portalnum) { numsameportal++; if (loopdoor->doortype == DOORTYPE_SWINGING || loopdoor->doortype == DOORTYPE_SWINGING) { frac += loopdoor->frac / loopdoor->maxfrac; } else { frac += loopdoor->frac / loopdoor->maxfrac; } } loopdoor = loopdoor->sibling; if (loopdoor == door) { break; } } portalSetXluFrac(door->portalnum, frac / numsameportal); portalSetXluFrac2(door->portalnum, frac / numsameportal); } } f32 func0f08f538(f32 x, f32 y) { f32 angle = atan2f(x, y); if (g_Vars.currentplayer->eyespy && g_Vars.currentplayer->eyespy->active && g_Vars.currentplayer->eyespy->prop && g_Vars.currentplayer->eyespy->prop->chr) { angle -= chrGetInverseTheta(g_Vars.currentplayer->eyespy->prop->chr); } else { angle -= (360.0f - g_Vars.currentplayer->vv_theta) * M_BADTAU / 360.0f; } if (angle < 0) { angle += M_BADTAU; } if (angle > M_BADPI) { angle -= M_BADTAU; } return angle; } /** * Get some coordinates/distances related to activating doors. */ void door0f08f604(struct doorobj *door, f32 *arg1, f32 *arg2, f32 *arg3, f32 *arg4, bool arg5) { f32 value1; f32 value2; f32 value3; f32 value4; f32 x1; f32 y1; f32 x2; f32 y2; u32 stack[4]; struct prop *playerprop; f32 spb0; f32 spac; f32 spa8; f32 spa4; struct coord playerpos; struct pad *pad; f32 xfrac; f32 zfrac; f32 angle; f32 cosine; f32 sine; if (g_Vars.currentplayer->eyespy && g_Vars.currentplayer->eyespy->active) { playerprop = g_Vars.currentplayer->eyespy->prop; } else { playerprop = g_Vars.currentplayer->prop; } pad = &g_Pads[door->base.pad]; playerpos.f[0] = playerprop->pos.x; playerpos.f[1] = playerprop->pos.y; playerpos.f[2] = playerprop->pos.z; if (arg5) { spa8 = pad->bbox.xmin; spa4 = pad->bbox.xmax; spb0 = pad->up.y * pad->look.z - pad->look.y * pad->up.z; spac = pad->up.x * pad->look.y - pad->look.x * pad->up.y; } else { spa8 = pad->bbox.ymin; spa4 = pad->bbox.ymax; spb0 = pad->up.x; spac = pad->up.z; } x1 = pad->pos.x + spb0 * spa8 - playerpos.f[0]; y1 = pad->pos.z + spac * spa8 - playerpos.f[2]; value1 = func0f08f538(x1, y1); x2 = pad->pos.x + spb0 * spa4 - playerpos.f[0]; y2 = pad->pos.z + spac * spa4 - playerpos.f[2]; value2 = func0f08f538(x2, y2); if (value1 < value2) { *arg1 = value1; *arg2 = value2; } else { *arg1 = value2; *arg2 = value1; } if (arg3 != NULL && arg4 != NULL) { if (door->doortype == DOORTYPE_SWINGING) { angle = door->frac * 0.017450513318181f; value3 = value1; if (door->base.flags & OBJFLAG_DOOR_OPENTOFRONT) { angle = M_BADTAU - angle; } cosine = cosf(angle); sine = sinf(angle); x1 = pad->pos.x + (spb0 * spa8) - playerpos.f[0] + (spa4 - spa8) * (spb0 * cosine + spac * sine); y1 = pad->pos.z + (spac * spa8) - playerpos.f[2] + (spa4 - spa8) * (-spb0 * sine + spac * cosine); value4 = func0f08f538(x1, y1); } else if (door->doortype == DOORTYPE_SLIDING || door->doortype == DOORTYPE_FLEXI1 || door->doortype == DOORTYPE_FLEXI2 || door->doortype == DOORTYPE_FLEXI3) { xfrac = door->unk98.x * door->frac; zfrac = door->unk98.z * door->frac; value3 = func0f08f538(x1 + xfrac, y1 + zfrac); value4 = func0f08f538(x2 + xfrac, y2 + zfrac); } else { value3 = value1; value4 = value2; } if (value3 < value4) { *arg3 = value3; *arg4 = value4; } else { *arg3 = value4; *arg4 = value3; } } } bool func0f08f968(struct doorobj *door, bool arg1) { bool checkmore = true; f32 sp58; f32 sp54; f32 sp50; f32 sp4c; bool maybe; struct prop *playerprop; f32 limit = 0.34901028871536f; if (g_InteractProp == NULL) { maybe = false; if (g_Vars.currentplayer->eyespy && g_Vars.currentplayer->eyespy->active) { playerprop = g_Vars.currentplayer->eyespy->prop; } else { playerprop = g_Vars.currentplayer->prop; } if ((door->doorflags & (DOORFLAG_0080 | DOORFLAG_0100)) != DOORFLAG_0080) { maybe = true; } else if (func0f06797c(&playerprop->pos, 30, door->base.pad)) { maybe = true; } if (maybe) { door0f08f604(door, &sp58, &sp54, &sp50, &sp4c, arg1); } else { door0f08f604(door, &sp58, &sp54, NULL, NULL, arg1); } if (maybe && ((sp50 >= -limit && sp50 <= limit && sp4c >= -limit && sp4c <= limit) || (sp4c - sp50 < M_BADPI && sp50 < 0.0f && sp4c > 0.0f))) { g_InteractProp = door->base.prop; checkmore = false; } else if (sp58 >= -limit && sp58 <= limit && sp54 >= -limit && sp54 <= limit) { g_InteractProp = door->base.prop; checkmore = false; } else { struct doorobj *sibling = door->sibling; f32 sp38; f32 sp34; while (sibling != NULL && sibling != door && (sp58 >= 0.0f || sp54 < 0.0f)) { door0f08f604(sibling, &sp38, &sp34, NULL, NULL, arg1); if (sp58 >= 0.0f && sp38 < sp58) { sp58 = sp38; } if (sp54 <= 0.0f && sp54 < sp34) { sp54 = sp34; } sibling = sibling->sibling; } if (sp54 - sp58 < M_BADPI && sp58 < 0.0f && sp54 > 0.0f) { g_InteractProp = door->base.prop; checkmore = false; } } } return checkmore; } /** * This function is used to help find the door that should be opened when * interacting. The argument given is a door to be tested. * * A pointer to the best candidate is stored at g_InteractProp. This function * doesn't write to it directly so it must be done by one of the called * functions. * * This function should return true if more doors and objects should be tested, * or false if the prop at g_InteractProp is certain to be final. */ bool doorTestForInteract(struct prop *prop) { bool checkmore = true; struct doorobj *door = prop->door; if ((door->base.flags & OBJFLAG_CANNOT_ACTIVATE) == 0 && door->maxfrac > 0 && (prop->flags & PROPFLAG_ONTHISSCREENTHISTICK)) { bool maybe = false; bool usingeyespy = g_Vars.currentplayer->eyespy && g_Vars.currentplayer->eyespy->active; struct prop *playerprop = usingeyespy ? g_Vars.currentplayer->eyespy->prop : g_Vars.currentplayer->prop; f32 xdiff = door->startpos.x - playerprop->pos.x; f32 ydiff = door->startpos.y - playerprop->pos.y; f32 zdiff = door->startpos.z - playerprop->pos.z; if (xdiff * xdiff + zdiff * zdiff < 40000 && ydiff < 200 && ydiff > -200) { maybe = true; } else if (arrayIntersects(prop->rooms, playerprop->rooms)) { if (func0f06797c(&playerprop->pos, 150, door->base.pad)) { maybe = true; } else if ((door->doorflags & (DOORFLAG_0080 | DOORFLAG_0100)) != DOORFLAG_0080) { u32 stack; struct modelrodata_bbox bbox; Mtxf matrix; doorGetBbox(door, &bbox); func0f08c424(door, &matrix); if (func0f0675c8(&playerprop->pos, 150, &bbox, &matrix)) { maybe = true; } } } if (maybe) { if ((door->base.flags2 & OBJFLAG2_INTERACTCHECKLOS) == 0 || cdTestLos06(&playerprop->pos, playerprop->rooms, &prop->pos, prop->rooms, CDTYPE_BG)) { checkmore = func0f08f968(door, false); if (checkmore && (door->base.flags2 & OBJFLAG2_80000000)) { checkmore = func0f08f968(door, true); } } } } return checkmore; } /** * Activate the doors by calling the lift or requesting the new door mode * (opening/closing) for the given door and its siblings. * * Assumes any lock checks have already been done and have passed. * * The allowliftclose argument determines whether the door should be closed if * it's a lift door and the lift is at the door. This is typically true when the * player has activated the door, and false when NPCs have activated the door. */ void doorsActivate(struct prop *doorprop, bool allowliftclose) { struct doorobj *door = doorprop->door; if (!doorCallLift(doorprop, allowliftclose)) { if (door->mode == DOORMODE_OPENING || door->mode == DOORMODE_WAITING) { doorsRequestMode(door, DOORMODE_CLOSING); } else if (door->mode == DOORMODE_CLOSING) { doorsRequestMode(door, DOORMODE_OPENING); } else if (door->mode == DOORMODE_IDLE) { if (door->frac > 0.5f * door->maxfrac) { doorsRequestMode(door, DOORMODE_CLOSING); } else { doorsRequestMode(door, DOORMODE_OPENING); } } } if (g_Vars.currentplayernum == g_Vars.coopplayernum) { door->base.hidden |= OBJHFLAG_ACTIVATED_BY_COOP; } else if (g_Vars.currentplayernum == g_Vars.bondplayernum) { door->base.hidden |= OBJHFLAG_ACTIVATED_BY_BOND; } door->base.flags2 &= ~OBJFLAG2_00000008; } bool posIsInFrontOfDoor(struct coord *pos, struct doorobj *door) { f32 x; f32 y; f32 z; f32 value; struct pad *pad; pad = &g_Pads[door->base.pad]; x = pos->x - pad->pos.x; y = pos->y - pad->pos.y; z = pos->z - pad->pos.z; value = x * pad->normal.f[0] + y * pad->normal.f[1] + z * pad->normal.f[2]; if (door->doorflags & DOORFLAG_FLIP) { value = -value; } if (value < 0) { return false; } if (value > 0) { return true; } return true; } void doorsChooseSwingDirection(struct prop *chrprop, struct doorobj *door) { if ((door->base.flags & OBJFLAG_DOOR_TWOWAY) && door->mode == DOORMODE_IDLE && door->frac == 0) { bool infront = posIsInFrontOfDoor(&chrprop->pos, door); u32 wantflag = 0; if ((door->doorflags & DOORFLAG_FLIP) == 0) { if (!infront) { wantflag = OBJFLAG_DOOR_OPENTOFRONT; } } else { if (infront) { wantflag = OBJFLAG_DOOR_OPENTOFRONT; } } // If flags are different if ((s32)((door->base.flags ^ wantflag) << 2) < 0) { // Toggle direction on door and siblings struct doorobj *sibling = door; do { sibling->base.flags ^= OBJFLAG_DOOR_OPENTOFRONT; sibling = sibling->sibling; } while (sibling && sibling != door); } } } bool propdoorInteract(struct prop *doorprop) { struct doorobj *door = doorprop->door; bool usingeyespy = g_Vars.currentplayer->eyespy && g_Vars.currentplayer->eyespy->active; struct prop *playerprop = usingeyespy ? g_Vars.currentplayer->eyespy->prop : g_Vars.currentplayer->prop; if (doorIsUnlocked(playerprop, doorprop)) { doorsChooseSwingDirection(playerprop, door); doorsActivate(doorprop, true); } else if (door->mode == DOORMODE_IDLE && door->frac < 0.5f * door->maxfrac) { if ((door->base.flags2 & OBJFLAG2_SKIPDOORLOCKEDMSG) == 0) { struct textoverride *override = invGetTextOverrideForObj(&door->base); u8 intraining = false; if (g_Vars.stagenum == STAGE_CITRAINING) { struct trainingdata *devdata = dtGetData(); struct trainingdata *holodata = getHoloTrainingData(); intraining = (devdata && devdata->intraining) || (holodata && holodata->intraining) || g_Vars.currentplayer->prop->rooms[0] == 0x0a; } if (override && override->pickuptext) { hudmsgCreateWithFlags(langGet(override->pickuptext), HUDMSGTYPE_DEFAULT, HUDMSGFLAG_ONLYIFALIVE); } else if (intraining) { hudmsgCreateWithFlags(langGet(L_DISH_080), HUDMSGTYPE_DEFAULT, HUDMSGFLAG_ONLYIFALIVE); // "Cannot exit while training is in progress." } else { hudmsgCreateWithFlags(langGet(L_PROPOBJ_044), HUDMSGTYPE_DEFAULT, HUDMSGFLAG_ONLYIFALIVE); // "This door is locked." } } if (g_Vars.currentplayernum == g_Vars.coopplayernum) { door->base.hidden |= OBJHFLAG_ACTIVATED_BY_COOP; } else if (g_Vars.currentplayernum == g_Vars.bondplayernum) { door->base.hidden |= OBJHFLAG_ACTIVATED_BY_BOND; } door->base.flags2 |= OBJFLAG2_00000008; } return TICKOP_NONE; } void alarmActivate(void) { if (g_AlarmTimer < 1) { g_AlarmTimer = 1; } } void alarmStopAudio(void) { if (g_AlarmAudioHandle && sndGetState(g_AlarmAudioHandle) != AL_STOPPED) { audioStop(g_AlarmAudioHandle); } } void alarmDeactivate(void) { g_AlarmTimer = 0; alarmStopAudio(); } bool alarmIsActive(void) { return g_AlarmTimer > 0; } void countdownTimerSetVisible(u32 reason, bool visible) { if (visible) { g_CountdownTimerOff &= ~reason; } else { g_CountdownTimerOff |= reason; } } bool countdownTimerIsVisible(void) { return !g_CountdownTimerOff; } void countdownTimerSetValue60(f32 value) { g_CountdownTimerValue60 = value; } void countdownTimerSetRunning(bool running) { g_CountdownTimerRunning = running; } void countdownTimerTick(void) { if (g_CountdownTimerRunning) { g_CountdownTimerValue60 -= g_Vars.lvupdate60freal; } } Gfx *countdownTimerRender(Gfx *gdl) { s32 mins; s32 secs; s32 ms; f32 value60 = g_CountdownTimerValue60; u32 stack; s32 viewright = viGetViewLeft() + (viGetViewWidth() >> 1); s32 y = viGetViewTop() + viGetViewHeight() - 18; s32 playercount = PLAYERCOUNT(); char *fmt = ":\n"; if (playercount == 2) { if (optionsGetScreenSplit() != SCREENSPLIT_VERTICAL && g_Vars.currentplayernum == 0) { y += 10; } else { y += 2; } } else if (playercount >= 3) { if (g_Vars.currentplayernum <= 1) { y += 10; } else { y += 2; } } else { if (optionsGetEffectiveScreenSize() != SCREENSIZE_FULL) { y += 8; } } if (value60 < 0) { value60 = -value60; } mins = floorf(value60 * (1.0f / 3600.0f)); secs = (s32)floorf(value60 * (1.0f / 60.0f)) - mins * 60; ms = (s32)floorf(value60 * 1.6666666269302f) - mins * 6000 - secs * 100; gdl = text0f153628(gdl); gdl = bgunDrawHudInteger(gdl, (mins % 100) / 10, viewright - 18, HUDHALIGN_MIDDLE, y, HUDVALIGN_MIDDLE, 0x00ff00a0); gdl = bgunDrawHudInteger(gdl, mins % 10, viewright - 14, HUDHALIGN_MIDDLE, y, HUDVALIGN_MIDDLE, 0x00ff00a0); gdl = bgunDrawHudString(gdl, fmt, viewright - 8, HUDHALIGN_MIDDLE, y, HUDVALIGN_MIDDLE, 0x00ff00a0); gdl = bgunDrawHudInteger(gdl, (secs % 60) / 10, viewright - 2, HUDHALIGN_MIDDLE, y, HUDVALIGN_MIDDLE, 0x00ff00a0); gdl = bgunDrawHudInteger(gdl, secs % 10, viewright + 2, HUDHALIGN_MIDDLE, y, HUDVALIGN_MIDDLE, 0x00ff00a0); gdl = bgunDrawHudString(gdl, fmt, viewright + 8, HUDHALIGN_MIDDLE, y, HUDVALIGN_MIDDLE, 0x00ff00a0); gdl = bgunDrawHudInteger(gdl, (ms % 100) / 10, viewright + 14, HUDHALIGN_MIDDLE, y, HUDVALIGN_MIDDLE, 0x00ff00a0); gdl = bgunDrawHudInteger(gdl, ms % 10, viewright + 18, HUDHALIGN_MIDDLE, y, HUDVALIGN_MIDDLE, 0x00ff00a0); gdl = text0f153780(gdl); return gdl; } void alarmTick(void) { if (alarmIsActive()) { s32 limit; s16 sound; // These sounds are alarm sounds. // They go for a fraction of a second and are repeated by this function. switch (g_Vars.stagenum) { case STAGE_CHICAGO: sound = SFX_ALARM_CHICAGO; break; case STAGE_G5BUILDING: sound = SFX_ALARM_2; break; case STAGE_AIRBASE: sound = SFX_ALARM_AIRBASE; break; case STAGE_PELAGIC: sound = SFX_ALARM_2; break; case STAGE_ATTACKSHIP: sound = SFX_ALARM_ATTACKSHIP; break; case STAGE_INFILTRATION: sound = SFX_ALARM_INFILTRATION; break; default: sound = SFX_ALARM_DEFAULT; break; } if (!lvIsPaused()) { if (g_AlarmAudioHandle) { // The sound is currently playing. Cycle between the left/right // speaker for stereo or headphone mode. f32 increment = g_Vars.lvupdate240 / 15.0f; if (increment > 10) { increment = 10; } g_AlarmSpeakerWeight += g_AlarmSpeakerDirection * increment; if (g_AlarmSpeakerWeight < 30) { g_AlarmSpeakerWeight = 30; g_AlarmSpeakerDirection *= -1; } else if (g_AlarmSpeakerWeight > 98) { g_AlarmSpeakerWeight = 98; g_AlarmSpeakerDirection *= -1; } sndAdjust(&g_AlarmAudioHandle, 0, 0x7fff, g_AlarmSpeakerWeight, -1, -1, 0, -1, 1); } else { // The alarm finished, or this is the first one. // Start the sound again. sndStart(var80095200, sound, &g_AlarmAudioHandle, -1, -1, -1, -1, -1); } } g_AlarmTimer += g_Vars.lvupdate60; // For G5, stop alarm after 55 seconds. // For all other levels, stop alarm after 30 seconds. limit = g_Vars.stagenum == STAGE_G5BUILDING ? 3300 : 1800; if (g_AlarmTimer > limit) { alarmDeactivate(); } } countdownTimerTick(); if (g_NumProxies) { chrsTriggerProxies(); } g_PlayersDetonatingMines = 0; } void func0f091030(void) { struct prop *prop = g_Vars.activeprops; while (prop) { if (prop->type == PROPTYPE_OBJ && (prop->flags & (PROPFLAG_ONTHISSCREENTHISTICK | PROPFLAG_ONANYSCREENTHISTICK | PROPFLAG_ONANYSCREENPREVTICK)) == 0 && (prop->obj->hidden2 & OBJH2FLAG_DESTROYED) && (prop->obj->hidden2 & OBJH2FLAG_80)) { objFreePermanently(prop->obj, true); return; } prop = prop->next; } } void currentPlayerDropAllItems(void) { struct chrdata *chr = g_Vars.currentplayer->prop->chr; s32 i; weaponDeleteFromChr(chr, HAND_RIGHT); weaponDeleteFromChr(chr, HAND_LEFT); for (i = WEAPON_UNARMED; i <= WEAPON_SUICIDEPILL; i++) { if (playermgrGetModelOfWeapon(i) >= 0 && invHasSingleWeaponExcAllGuns(i)) { if (!weaponHasFlag(i, WEAPONFLAG_UNDROPPABLE) || (g_Vars.normmplayerisrunning && g_MpSetup.scenario == MPSCENARIO_HACKERCENTRAL && i == WEAPON_DATAUPLINK)) { if (g_Vars.coopplayernum >= 0) { bool canremove = true; struct prop *child = g_Vars.currentplayer->prop->child; while (child) { struct defaultobj *obj = child->obj; if (obj->type == OBJTYPE_WEAPON) { struct weaponobj *weapon = child->weapon; if (i == weapon->weaponnum && (obj->flags3 & OBJFLAG3_PLAYERUNDROPPABLE)) { canremove = false; break; } } child = child->next; } if (canremove) { invRemoveItemByNum(i); } if (!bgunIsMissionCritical(i)) { weaponCreateForPlayerDrop(i); } } else { weaponCreateForPlayerDrop(i); } } } } } void weaponCreateForPlayerDrop(s32 weaponnum) { u32 stack; struct prop *prop; struct chrdata *chr; u32 stack2; chr = g_Vars.currentplayer->prop->chr; prop = weaponCreateForChr(chr, playermgrGetModelOfWeapon(weaponnum), weaponnum, OBJFLAG_WEAPON_AICANNOTUSE, NULL, NULL); if (prop) { objSetDropped(prop, DROPTYPE_DEFAULT); objDrop(prop, true); if (weaponnum == WEAPON_BRIEFCASE2) { scenarioHandleDroppedToken(chr, prop); } } } void projectileCreate(struct prop *fromprop, struct fireslotthing *arg1, struct coord *pos, struct coord *dir, u8 weaponnum, struct prop *targetprop) { if (!lvIsPaused()) { bool blocked = false; struct coord endpos; u32 stack; f32 x; f32 y; f32 z; f32 sqdist; struct prop *obstacle = NULL; s16 sp1c8[8]; struct coord frompos; u32 stack2; u8 drug = false; u32 stack3; frompos = *pos; if (weaponnum == WEAPON_TRANQUILIZER) { drug = true; } if (arg1 && arg1->unk08 < g_Vars.lvframe60) { switch (weaponnum) { case WEAPON_CHOPPERGUN: func0f0926bc(fromprop, 7, 0xffff); propsnd0f0939f8(0, fromprop, SFX_810E, -1, -1, 0, 0, 7, 0, -1.0f, 0, -1, -1.0f, -1.0f, -1.0f); arg1->unk08 = g_Vars.lvframe60 + 4; break; case WEAPON_RCP45: func0f0926bc(fromprop, 1, 0xffff); propsnd0f0939f8(0, fromprop, SFX_805A, -1, -1, 0, 0, 0, 0, -1.0f, 0, -1, -1.0f, -1.0f, -1.0f); arg1->unk08 = g_Vars.lvframe60 + 2; break; case WEAPON_WATCHLASER: func0f0926bc(fromprop, 1, 0xffff); propsnd0f0939f8(0, fromprop, SFX_8043, -1, -1, 0, 0, 0, 0, -1.0f, 0, -1, -1.0f, -1.0f, -1.0f); arg1->unk08 = g_Vars.lvframe60 + 8; break; default: func0f0926bc(fromprop, 1, 0xffff); propsnd0f0939f8(0, fromprop, SFX_8045, -1, -1, 0, 0, 0, 0, -1.0f, 0, -1, -1.0f, -1.0f, -1.0f); arg1->unk08 = g_Vars.lvframe60 + 2; break; } } if (targetprop) { x = targetprop->pos.f[0] - pos->f[0] - dir->f[0] * 15.0f; y = targetprop->pos.f[1] - pos->f[1] - dir->f[1] * 15.0f; z = targetprop->pos.f[2] - pos->f[2] - dir->f[2] * 15.0f; sqdist = x * x + y * y + z * z; } else { sqdist = 0x20000000; } if (weaponnum == WEAPON_ROCKETLAUNCHER) { struct weaponobj *rocket; Mtxf sp13c; struct coord sp130; struct chopperobj *chopper = chopperFromHovercar((struct chopperobj *)fromprop->obj); if (chopper && sqdist > 400.0f * 400.0f) { struct coord sp120; Mtxf spe0; Mtxf spa0; f32 rotx = chopper->rotx; f32 roty = chopper->roty; rocket = weaponCreateProjectileFromWeaponNum(MODEL_CHRDYROCKETMIS, WEAPON_ROCKET, NULL); if (rocket) { mtx4LoadIdentity(&sp13c); mtx4LoadXRotation(rotx, &spe0); mtx4LoadYRotation(roty, &spa0); mtx00015be0(&spa0, &spe0); sp120.x = dir->x * 0.27777776f; sp120.y = dir->y * 0.27777776f; sp120.z = dir->z * 0.27777776f; sp130.x = sp120.f[0] * g_Vars.lvupdate60freal; sp130.y = sp120.f[1] * g_Vars.lvupdate60freal; sp130.z = sp120.f[2] * g_Vars.lvupdate60freal; bgun0f09ebcc(&rocket->base, pos, fromprop->rooms, &spe0, &sp130, &sp13c, fromprop, pos); if (rocket->base.hidden & OBJHFLAG_PROJECTILE) { rocket->timer240 = -1; rocket->base.projectile->flags |= PROJECTILEFLAG_POWERED; rocket->base.projectile->unk010 = sp120.x; rocket->base.projectile->unk014 = sp120.y; rocket->base.projectile->unk018 = sp120.z; propsnd0f0939f8(NULL, rocket->base.prop, SFX_LAUNCH_ROCKET_8053, -1, -1, 0, 0, 0, 0, -1.0f, 0, -1, -1.0f, -1.0f, -1.0f); } } } } else { struct gset gset = {0}; gset.weaponnum = weaponnum; endpos.x = pos->x + dir->f[0] * 65536.0f; endpos.y = pos->y + dir->f[1] * 65536.0f; endpos.z = pos->z + dir->f[2] * 65536.0f; propSetPerimEnabled(fromprop, false); if (cdExamLos08(pos, fromprop->rooms, &endpos, CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_CHRS | CDTYPE_PATHBLOCKER| CDTYPE_BG, GEOFLAG_BLOCK_SHOOT) == CDRESULT_COLLISION) { blocked = true; cdGetPos(&endpos, 24482, "propobj.c"); obstacle = cdGetObstacleProp(); } propSetPerimEnabled(fromprop, true); x = endpos.x - pos->x; y = endpos.y - pos->y; z = endpos.z - pos->z; if (targetprop && sqdist <= x * x + y * y + z * z) { struct coord aimpos; aimpos.x = targetprop->pos.x; aimpos.y = targetprop->pos.y - 20.0f; aimpos.z = targetprop->pos.z; if (func0f06b39c(pos, dir, &aimpos, 30)) { f32 f0 = 0.16f * g_Vars.lvupdate60freal * arg1->unk0c; if (sqdist > 40000.0f) { f0 *= 200.0f / sqrtf(sqdist); } arg1->unk14 += f0; if (arg1->unk14 >= 1.0f) { blocked = false; endpos = targetprop->pos; if (random() % 2) { endpos.y += (random() % 10) + 2; } else { endpos.y -= (random() % 10) + 2; } bgunPlayPropHitSound(&gset, targetprop, -1); chrDamageByImpact(targetprop->chr, gsetGetDamage(&gset) * arg1->unk10, dir, &gset, 0, 200); arg1->unk14 = 0.0f; } } } if (blocked) { if (obstacle) { if (obstacle->type & (PROPTYPE_CHR | PROPTYPE_PLAYER)) { struct modelnode *node = NULL; struct model *model = NULL; s32 side = -1; s32 hitpart = HITPART_GENERAL; struct chrdata *chr = obstacle->chr; if (weaponnum != WEAPON_CHOPPERGUN) { bgunPlayPropHitSound(&gset, obstacle, -1); } if (chr->model) { chrCalculateShieldHit(chr, &endpos, dir, &node, &hitpart, &model, &side); } chrEmitSparks(chr, obstacle, hitpart, &endpos, dir, NULL); if (drug) { chr->blurdrugamount = TICKS(5000); } func0f0341dc(chr, gsetGetDamage(&gset), dir, &gset, 0, hitpart, obstacle, node, model, side, NULL); } else if (obstacle->type & (PROPTYPE_OBJ | PROPTYPE_WEAPON | PROPTYPE_DOOR)) { struct defaultobj *obj = obstacle->obj; if (weaponnum != WEAPON_CHOPPERGUN) { bgunPlayPropHitSound(&gset, obstacle, -1); } func0f065e74(pos, fromprop->rooms, &endpos, sp1c8); sparksCreate(sp1c8[0], obstacle, &endpos, NULL, NULL, SPARKTYPE_DEFAULT); objTakeGunfire(obstacle->obj, gsetGetDamage(&gset), &endpos, weaponnum, -1); if (obj->type == OBJTYPE_WEAPON) { struct weaponobj *weapon = (struct weaponobj *)obj; if (weapon->weaponnum == WEAPON_DRAGON && weapon->gunfunc == FUNC_SECONDARY) { weapon->timer240 = 0; } } } } else { func0f065e74(pos, fromprop->rooms, &endpos, sp1c8); if (weaponnum != WEAPON_CHOPPERGUN) { bgunPlayBgHitSound(&gset, &endpos, -1, sp1c8); } sparksCreate(sp1c8[0], NULL, &endpos, NULL, NULL, SPARKTYPE_DEFAULT); } } if (arg1 && arg1->unk01) { beamCreate(arg1->beam, weaponnum, &frompos, &endpos); } } } } void objSetModelPartVisible(struct defaultobj *obj, s32 partnum, bool visible) { if (obj && obj->model && obj->model->filedata) { struct modelnode *node = modelGetPart(obj->model->filedata, partnum); if (node) { union modelrwdata *rwdata = modelGetNodeRwData(obj->model, node); if (rwdata) { if (visible) { visible = true; } else { visible = false; } rwdata->toggle.visible = visible; } } } }