perfect_dark/src/game/explosions.c

1552 lines
40 KiB
C

#include <ultra64.h>
#include "constants.h"
#include "game/chraction.h"
#include "game/dlights.h"
#include "game/chr.h"
#include "game/prop.h"
#include "game/setuputils.h"
#include "game/propsnd.h"
#include "game/tex.h"
#include "game/camera.h"
#include "game/explosions.h"
#include "game/smoke.h"
#include "game/bg.h"
#include "game/room.h"
#include "game/file.h"
#include "game/gfxmemory.h"
#include "game/mplayer/mplayer.h"
#include "game/propobj.h"
#include "game/utils.h"
#include "game/wallhit.h"
#include "bss.h"
#include "lib/collision.h"
#include "lib/vi.h"
#include "lib/main.h"
#include "lib/rng.h"
#include "lib/mtx.h"
#include "lib/anim.h"
#include "data.h"
#include "types.h"
struct explosion *g_Explosions;
s32 g_MaxExplosions;
s32 g_ExplosionShakeTotalTimer = 0;
s32 g_ExplosionShakeIntensityTimer = 0;
f32 g_ExplosionDamageTxScale = 1;
u32 var8007e4ac = 0x0000004b;
u32 var8007e4b0 = 0x000001e0;
u32 var8007e4b4 = 0x000000a8;
struct explosiontype g_ExplosionTypes[] = {
// rangeh
// | rangev
// | | changerateh
// | | | changeratev
// | | | | innersize
// | | | | | blastradius
// | | | | | | damageradius
// | | | | | | | duration
// | | | | | | | | propagationrate
// | | | | | | | | | flarespeed
// | | | | | | | | | | smoketype
// | | | | | | | | | | | sound
// | | | | | | | | | | | | damage
// | | | | | | | | | | | | |
/*00*/ { 0.1, 0.1, 0, 0, 0.1, 0, 0, 1, 1, 1, SMOKETYPE_NONE, 0x0000, 0 },
/*01*/ { 1, 1, 0, 0, 1, 0, 0, 30, 1, 1, SMOKETYPE_BULLETIMPACT, 0x0000, 0 },
/*02*/ { 20, 20, 0, 0, 30, 50, 50, 40, 1, 3, SMOKETYPE_MINI, 0x8099, 0.125 },
/*03*/ { 50, 50, 0, 0, 50, 100, 100, 45, 1, 4, SMOKETYPE_MINI, 0x809a, 0.5 },
/*04*/ { 60, 80, 2, 0.6, 100, 130, 240, 60, 2, 5, SMOKETYPE_ELECTRICAL, 0x809e, 1 },
/*05*/ { 60, 120, 2, 0.6, 150, 160, 280, 60, 2, 5, SMOKETYPE_ELECTRICAL, 0x809e, 2 },
/*06*/ { 20, 20, 0, 0, 22, 40, 40, 60, 1, 3, SMOKETYPE_MINI, 0x8099, 0.5 },
/*07*/ { 35, 40, 0, 0, 35, 70, 70, 60, 1, 4, SMOKETYPE_MINI, 0x809a, 1 },
/*08*/ { 50, 80, 2, 0.6, 50, 100, 160, 60, 2, 5, SMOKETYPE_ELECTRICAL, 0x809e, 2 },
/*09*/ { 60, 120, 2, 0.6, 50, 130, 180, 60, 2, 5, SMOKETYPE_ELECTRICAL, 0x809e, 2 },
/*10*/ { 40, 40, 0.8, 0.5, 70, 80, 160, 80, 4, 5, SMOKETYPE_SMALL, 0x80a0, 1 },
/*11*/ { 50, 50, 1.2, 0.8, 100, 100, 200, 90, 1, 4, SMOKETYPE_SMALL, 0x809e, 2 },
/*12*/ { 70, 60, 2, 1.2, 150, 140, 280, 90, 2, 5, SMOKETYPE_MEDIUM, 0x809e, 4 },
/*13*/ { 80, 60, 4, 1.4, 200, 200, 400, 90, 2, 5, SMOKETYPE_LARGE, 0x809f, 4 },
/*14*/ { 50, 50, 0, 0, 120, 150, 300, 150, 4, 4, SMOKETYPE_SMALL, 0x809f, 4 },
/*15*/ { 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, SMOKETYPE_BULLETIMPACT, 0x809c, 0 },
/*16*/ { 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, SMOKETYPE_BULLETIMPACT, 0x809c, 0 },
/*17*/ { 80, 60, 10, 5, 1500, 2200, 3600, 500, 1, 2, SMOKETYPE_NONE, 0x80a5, 4 },
/*18*/ { 80, 60, 3, 1, 300, 450, 640, 60, 1, 2, SMOKETYPE_NONE, 0x809f, 4 },
/*19*/ { 90, 75, 2.5, 0.87, 250, 375, 600, 180, 2, 5, SMOKETYPE_LARGE, 0x809f, 4 },
/*20*/ { 160, 120, 6, 2, 600, 450, 640, 60, 1, 2, SMOKETYPE_NONE, 0x809f, 4 },
/*21*/ { 40, 30, 2, 0.7, 100, 140, 270, 45, 2, 5, SMOKETYPE_SMALL, 0x809f, 3.5 },
/*22*/ { 20, 20, 0, 0, 30, 100, 200, 40, 1, 3, SMOKETYPE_MINI, 0x8099, 0.25 },
/*23*/ { 100, 80, 4, 1.4, 210, 220, 500, 90, 2, 5, SMOKETYPE_LARGE, 0x809f, 4 },
/*24*/ { 80, 60, 4, 1.4, 500, 200, 400, 90, 2, 5, SMOKETYPE_LARGE, 0x809f, 4 },
/*25*/ { 640, 480, 32, 11.2, 1600, 1000, 1000, 180, 2, 5, SMOKETYPE_NONE, 0x80a4, 4 },
};
bool explosionCreateSimple(struct prop *prop, struct coord *pos, RoomNum *rooms, s16 type, s32 playernum)
{
return explosionCreate(prop, pos, rooms, type, playernum, false, NULL, 0, NULL);
}
bool explosionCreateComplex(struct prop *prop, struct coord *pos, RoomNum *rooms, s16 type, s32 playernum)
{
struct coord sp100;
struct coord sp88;
struct explosiontype *etype;
bool makescorch = true;
RoomNum room;
f32 y;
struct prop *collisionprop;
if (type == EXPLOSIONTYPE_NONE) {
return false;
}
if (prop) {
room = cdFindFloorRoomYColourNormalPropAtPos(&prop->pos, prop->rooms, &y, NULL, &sp88, &collisionprop);
sp100.x = prop->pos.x;
sp100.y = y;
sp100.z = prop->pos.z;
} else {
room = cdFindFloorRoomYColourNormalPropAtPos(pos, rooms, &y, NULL, &sp88, &collisionprop);
sp100.x = pos->x;
sp100.y = y;
sp100.z = pos->z;
}
etype = &g_ExplosionTypes[type];
if (collisionprop || room <= 0
|| !(pos->y - y <= (etype->rangev + etype->changeratev * etype->duration + etype->innersize) * 0.5f || pos->y - y <= 75)) {
makescorch = false;
}
return explosionCreate(prop, pos, rooms, type, playernum, makescorch, &sp100, room, &sp88);
}
f32 explosionGetHorizontalRangeAtFrame(struct explosion *exp, s32 frame)
{
struct explosiontype *type = &g_ExplosionTypes[exp->type];
f32 changerate = PALUPF(type->changerateh);
f32 result;
if (exp->type == EXPLOSIONTYPE_GASBARREL && frame > TICKS(32)) {
result = frame * PALUPF(3.0f) + 40.0f;
if (result > 300) {
result = 300;
}
} else {
result = type->rangeh + changerate * frame;
}
return result;
}
f32 explosionGetVerticalRangeAtFrame(struct explosion *exp, s32 frame)
{
struct explosiontype *type = &g_ExplosionTypes[exp->type];
f32 changerate = PALUPF(type->changeratev);
f32 result;
if (exp->type == EXPLOSIONTYPE_GASBARREL && frame > TICKS(32)) {
result = 20;
} else {
result = type->rangev + changerate * frame;
}
return result;
}
void explosionGetBboxAtFrame(struct coord *lower, struct coord *upper, s32 frame, struct prop *prop)
{
struct explosion *exp = prop->explosion;
struct explosiontype *type = &g_ExplosionTypes[exp->type];
f32 rangeh = explosionGetHorizontalRangeAtFrame(exp, frame);
f32 rangev = explosionGetVerticalRangeAtFrame(exp, frame);
rangeh = rangeh * 0.5f + type->innersize * 1.5f;
rangev = rangev * 0.5f + type->innersize * 1.5f;
lower->x = prop->pos.x - rangeh;
lower->y = prop->pos.y - rangev;
lower->z = prop->pos.z - rangeh;
upper->x = prop->pos.x + rangeh;
upper->y = prop->pos.y + rangev;
upper->z = prop->pos.z + rangeh;
}
void explosionAlertChrs(f32 *radius, struct coord *noisepos)
{
u32 stack[2];
s32 *end = (s32 *)&doorDestroyGlass;
s32 i;
for (i = 0; i < g_NumChrSlots; i++) {
if (g_ChrSlots[i].model
&& chrGetTargetProp(&g_ChrSlots[i]) == g_Vars.currentplayer->prop
&& g_ChrSlots[i].prop
&& g_ChrSlots[i].prop->type == PROPTYPE_CHR
&& (g_ChrSlots[i].prop->flags & PROPFLAG_ENABLED)) {
f32 distance = chrGetDistanceToCoord(&g_ChrSlots[i], noisepos);
if (distance == 0) {
distance = 2;
} else {
distance = (10.0f * *radius * g_ChrSlots[i].hearingscale) / distance;
}
if (distance > 1) {
chrRecordLastHearTargetTime(&g_ChrSlots[i]);
}
}
}
#if PIRACYCHECKS
{
u32 checksum = 0;
s32 *ptr = (s32 *)&glassDestroy;
while (ptr < end) {
checksum ^= *ptr;
checksum <<= 1;
ptr++;
}
if (checksum != CHECKSUM_PLACEHOLDER) {
struct explosiontype *type = &g_ExplosionTypes[0];
s32 i;
for (i = 0; i != ARRAYCOUNT(g_ExplosionTypes) - 1; i++) {
type->rangeh = 80;
type->rangev = 60;
type->changerateh = 15;
type->changeratev = 5;
type->innersize = 1500;
type->blastradius = 200;
type->damageradius = 3600;
type++;
}
}
}
#endif
}
bool explosionCreate(struct prop *sourceprop, struct coord *exppos, RoomNum *exprooms,
s16 type, s32 playernum, bool makescorch, struct coord *arg6, RoomNum room, struct coord *arg8)
{
u32 stack;
struct explosion *exp = NULL;
s32 i;
if (type == EXPLOSIONTYPE_NONE || exprooms[0] == -1) {
return false;
}
// Bullet holes: only crate the flame (explosion) if within 4 metres
if (type == EXPLOSIONTYPE_BULLETHOLE) {
f32 lodscale = camGetLodScaleZ();
struct coord *campos = &g_Vars.currentplayer->cam_pos;
f32 xdist = exppos->x - campos->x;
f32 ydist = exppos->y - campos->y;
f32 zdist = exppos->z - campos->z;
f32 sum = xdist * xdist + ydist * ydist + zdist * zdist;
if (sum * lodscale * lodscale > 400 * 400) {
if (random() % 2 == 0) {
if (sourceprop) {
smokeCreateSimple(&sourceprop->pos, sourceprop->rooms, g_ExplosionTypes[type].smoketype);
} else {
smokeCreateSimple(exppos, exprooms, g_ExplosionTypes[type].smoketype);
}
}
return true;
}
}
// Try to find an unused slot
for (i = 0; i < g_MaxExplosions; i++) {
if (g_Explosions[i].prop == NULL) {
exp = &g_Explosions[i];
break;
}
}
// If there's no unused slots, find the oldest bullethole flame and replace it
if (exp == NULL) {
s32 maxage = -1;
s32 index = -1;
for (i = 0; i < g_MaxExplosions; i++) {
if (g_Explosions[i].type == EXPLOSIONTYPE_BULLETHOLE && g_Explosions[i].age > maxage) {
maxage = g_Explosions[i].age;
index = i;
}
}
if (index >= 0) {
propExecuteTickOperation(g_Explosions[index].prop, TICKOP_FREE);
g_Explosions[index].prop = NULL;
exp = &g_Explosions[index];
}
}
if (exp) {
struct prop *expprop = propAllocate();
if (type != EXPLOSIONTYPE_16 && type != EXPLOSIONTYPE_BULLETHOLE) {
g_ExplosionShakeTotalTimer = 6;
}
if (expprop) {
s32 exproom;
f32 value1;
f32 value2;
s32 k;
s32 index;
s32 portalnum;
s32 indexplus1;
s32 indexplus2;
struct coord portalbbmin;
struct coord portalbbmax;
struct coord portal2bbmin;
struct coord portal2bbmax;
struct coord spd4;
struct coord spc8;
RoomNum otherroom;
RoomNum otherroom2;
f32 mult = 1;
s32 stack4;
s32 portalnum2;
struct coord spac;
u32 stack2;
u32 stack3;
s32 j;
expprop->type = PROPTYPE_EXPLOSION;
expprop->explosion = exp;
expprop->pos.x = exppos->x;
expprop->pos.y = exppos->y;
expprop->pos.z = exppos->z;
for (i = 0; exprooms[i] != -1 && i < ARRAYCOUNT(expprop->rooms) - 1; i++) {
expprop->rooms[i] = exprooms[i];
roomFlashLighting(exprooms[i], g_ExplosionTypes[type].rangeh, 255);
}
expprop->rooms[i] = -1;
propActivateThisFrame(expprop);
propEnable(expprop);
exp->type = type;
exp->prop = expprop;
exp->source = sourceprop;
exp->age = 0;
exp->makescorch = makescorch;
exp->owner = playernum;
if (type != EXPLOSIONTYPE_BULLETHOLE && type != EXPLOSIONTYPE_PHOENIX) {
propSetDangerous(expprop);
}
exproom = expprop->rooms[0];
explosionGetBboxAtFrame(&spd4, &spc8, g_ExplosionTypes[type].duration, expprop);
spd4.x *= mult;
spd4.y *= mult;
spd4.z *= mult;
spc8.x *= mult;
spc8.y *= mult;
spc8.z *= mult;
exp->bbs[0].bbmin.x = g_Rooms[exproom].bbmin[0];
exp->bbs[0].bbmin.y = g_Rooms[exproom].bbmin[1];
exp->bbs[0].bbmin.z = g_Rooms[exproom].bbmin[2];
exp->bbs[0].bbmax.x = g_Rooms[exproom].bbmax[0];
exp->bbs[0].bbmax.y = g_Rooms[exproom].bbmax[1];
exp->bbs[0].bbmax.z = g_Rooms[exproom].bbmax[2];
exp->bbs[0].room = exproom;
exp->bbs[0].room2 = -1;
exp->numbb = 1;
if (exp->type == EXPLOSIONTYPE_HUGE25) {
exp->numbb = 0;
} else {
exp->bbs[0].bbmin.x = g_Rooms[exproom].bbmin[0];
exp->bbs[0].bbmin.y = g_Rooms[exproom].bbmin[1];
exp->bbs[0].bbmin.z = g_Rooms[exproom].bbmin[2];
exp->bbs[0].bbmax.x = g_Rooms[exproom].bbmax[0];
exp->bbs[0].bbmax.y = g_Rooms[exproom].bbmax[1];
exp->bbs[0].bbmax.z = g_Rooms[exproom].bbmax[2];
exp->bbs[0].room = exproom;
exp->bbs[0].room2 = -1;
exp->numbb = 1;
for (k = 0; k < g_Rooms[exproom].numportals; k++) {
portalnum = g_RoomPortals[g_Rooms[exproom].roomportallistoffset + k];
bgCalculatePortalBbox(portalnum, &portalbbmin, &portalbbmax);
if (bgIsBboxOverlapping(&portalbbmin, &portalbbmax, &spd4, &spc8)) {
otherroom2 = -1;
index = 0;
if (exproom == g_BgPortals[portalnum].roomnum1) {
otherroom = g_BgPortals[portalnum].roomnum2;
} else {
otherroom = g_BgPortals[portalnum].roomnum1;
}
spac.f[0] = (g_PortalMetrics + portalnum)->normal.f[0];
spac.f[1] = (g_PortalMetrics + portalnum)->normal.f[1];
spac.f[2] = (g_PortalMetrics + portalnum)->normal.f[2];
if (spac.f[0] < 0.0f) {
spac.f[0] = -spac.f[0];
}
if (spac.f[1] < 0.0f) {
spac.f[1] = -spac.f[1];
}
if (spac.f[2] < 0.0f) {
spac.f[2] = -spac.f[2];
}
if (spac.f[0] < spac.f[1]) {
index = 1;
}
if (spac.f[index] < spac.f[2]) {
index = 2;
}
indexplus1 = (index + 1) % 3;
indexplus2 = (index + 2) % 3;
value1 = portalbbmax.f[indexplus1] - portalbbmin.f[indexplus1];
value2 = portalbbmax.f[indexplus2] - portalbbmin.f[indexplus2];
if (value2 < value1) {
value1 = value2;
}
portalbbmin.f[index] -= value1;
portalbbmax.f[index] += value1;
if (portalbbmin.f[index] < g_Rooms[exproom].bbmin[index]) {
portalbbmin.f[index] = g_Rooms[exproom].bbmin[index];
}
if (portalbbmax.f[index] > g_Rooms[exproom].bbmax[index]) {
portalbbmax.f[index] = g_Rooms[exproom].bbmax[index];
}
if (portalbbmin.f[index] > g_Rooms[otherroom].bbmin[index]) {
portalbbmin.f[index] = g_Rooms[otherroom].bbmin[index];
}
if (portalbbmax.f[index] < g_Rooms[otherroom].bbmax[index]) {
portalbbmax.f[index] = g_Rooms[otherroom].bbmax[index];
}
for (j = 0; j < g_Rooms[otherroom].numportals; j++) {
portalnum2 = g_RoomPortals[g_Rooms[otherroom].roomportallistoffset + j];
if (portalnum2 != portalnum) {
bgCalculatePortalBbox(portalnum2, &portal2bbmin, &portal2bbmax);
if (portal2bbmin.f[indexplus1] <= portalbbmin.f[indexplus1] + 10.0f * mult
&& portal2bbmin.f[indexplus2] <= portalbbmin.f[indexplus2] + 10.0f * mult
&& portal2bbmax.f[indexplus1] >= portalbbmax.f[indexplus1] - 10.0f * mult
&& portal2bbmax.f[indexplus2] >= portalbbmax.f[indexplus2] - 10.0f * mult) {
if (otherroom == g_BgPortals[portalnum2].roomnum1) {
otherroom2 = g_BgPortals[portalnum2].roomnum2;
} else {
otherroom2 = g_BgPortals[portalnum2].roomnum1;
}
if (portalbbmin.f[index] > g_Rooms[otherroom2].bbmin[index]) {
portalbbmin.f[index] = g_Rooms[otherroom2].bbmin[index];
}
if (portalbbmax.f[index] < g_Rooms[otherroom2].bbmax[index]) {
portalbbmax.f[index] = g_Rooms[otherroom2].bbmax[index];
}
break;
}
}
}
portalbbmin.f[0] *= mult;
portalbbmin.f[1] *= mult;
portalbbmin.f[2] *= mult;
portalbbmax.f[0] *= mult;
portalbbmax.f[1] *= mult;
portalbbmax.f[2] *= mult;
exp->bbs[exp->numbb].bbmin.x = portalbbmin.f[0];
exp->bbs[exp->numbb].bbmin.y = portalbbmin.f[1];
exp->bbs[exp->numbb].bbmin.z = portalbbmin.f[2];
exp->bbs[exp->numbb].bbmax.x = portalbbmax.f[0];
exp->bbs[exp->numbb].bbmax.y = portalbbmax.f[1];
exp->bbs[exp->numbb].bbmax.z = portalbbmax.f[2];
exp->bbs[exp->numbb].room = otherroom;
exp->bbs[exp->numbb].room2 = otherroom2;
exp->numbb++;
if (exp->numbb >= 5) {
break;
}
}
}
exp->bbs[0].bbmin.x *= mult;
exp->bbs[0].bbmin.y *= mult;
exp->bbs[0].bbmin.z *= mult;
exp->bbs[0].bbmax.x *= mult;
exp->bbs[0].bbmax.y *= mult;
exp->bbs[0].bbmax.z *= mult;
}
if (makescorch) {
exp->unk3d0.x = arg6->x;
exp->unk3d0.y = arg6->y;
exp->unk3d0.z = arg6->z;
exp->room = room;
exp->unk3dc.x = arg8->x;
exp->unk3dc.y = arg8->y;
exp->unk3dc.z = arg8->z;
} else {
exp->unk3d0.x = 999999.875f;
}
exp->parts[0].frame = 1;
exp->parts[0].pos.x = exppos->x;
exp->parts[0].pos.y = exppos->y;
exp->parts[0].pos.z = exppos->z;
exp->parts[0].size = g_ExplosionTypes[type].innersize * (RANDOMFRAC() * 0.5f + 1);
exp->parts[0].rot = RANDOMFRAC() * M_BADTAU;
exp->parts[0].bb = 0;
if (g_Vars.mplayerisrunning) {
smokeClearSomeTypes();
}
explosionAlertChrs(&g_ExplosionTypes[type].rangeh, exppos);
}
}
return exp != NULL;
}
/**
* Start a shake without any explosion.
*
* This function is unused.
*/
void explosionShake(void)
{
g_ExplosionShakeTotalTimer = 6;
g_ExplosionShakeIntensityTimer = 6;
}
void explosionsUpdateShake(struct coord *arg0, struct coord *arg1, struct coord *arg2)
{
u32 stack[4];
f32 sp54;
f32 sp50;
s32 i;
f32 intensity;
if (g_ExplosionShakeTotalTimer == 0) {
viShake(0);
return;
}
sp54 = cosf(0.8f) * arg1->f[0] - sinf(0.8f) * arg1->f[2];
sp50 = sinf(0.8f) * arg1->f[0] + cosf(0.8f) * arg1->f[2];
intensity = 0.0f;
for (i = 0; i < g_MaxExplosions; i++) {
struct prop *prop = g_Explosions[i].prop;
if (prop) {
f32 xdiff = prop->pos.x - arg0->x;
f32 ydiff = prop->pos.y - arg0->y;
f32 zdiff = prop->pos.z - arg0->z;
f32 dist = sqrtf(xdiff * xdiff + ydiff * ydiff + zdiff * zdiff);
f32 mult;
if (dist == 0.0f) {
dist = 0.0001f;
}
mult = g_ExplosionTypes[g_Explosions[i].type].innersize / dist;
intensity += mult * 15.0f;
}
}
if (g_ExplosionShakeIntensityTimer > 0) {
g_ExplosionShakeIntensityTimer--;
intensity++;
}
g_ExplosionShakeTotalTimer--;
if (g_ExplosionShakeTotalTimer & 2) {
arg2->y = intensity;
intensity = -intensity;
} else {
arg2->y = -intensity;
}
arg2->x = intensity * sp54;
arg2->z = intensity * sp50;
viShake(g_ExplosionShakeTotalTimer * intensity);
}
/**
* Check if a prop is either fully or partially overlapping the given explosion.
*
* minpos and maxpos are the bounding boxes of the prop.
*/
bool explosionOverlapsProp(struct explosion *exp, struct prop *prop, struct coord *minpos, struct coord *maxpos)
{
bool result = false;
s32 i;
RoomNum rooms[8];
if (exp->type == EXPLOSIONTYPE_HUGE25) {
result = true;
} else {
for (i = 0; i < exp->numbb; i++) {
rooms[0] = exp->bbs[i].room;
if (exp->bbs[i].room2 != -1) {
rooms[1] = exp->bbs[i].room2;
} else {
rooms[1] = -1;
}
rooms[2] = -1;
if (arrayIntersects(prop->rooms, rooms)
&& minpos->x <= exp->bbs[i].bbmax.x
&& minpos->y <= exp->bbs[i].bbmax.y
&& minpos->z <= exp->bbs[i].bbmax.z
&& maxpos->x >= exp->bbs[i].bbmin.x
&& maxpos->y >= exp->bbs[i].bbmin.y
&& maxpos->z >= exp->bbs[i].bbmin.z) {
result = true;
break;
}
}
}
return result;
}
void explosionInflictDamage(struct prop *expprop)
{
s32 stack;
struct explosion *exp = expprop->explosion;
struct explosiontype *type = &g_ExplosionTypes[exp->type];
s16 *propnumptr;
s16 propnums[256];
bool isfirstframe = exp->age <= 0;
s32 i;
f32 k;
s32 j;
f32 damageradius;
if (g_Vars.lvupdate60 <= 0) {
return;
}
if (type->damage <= 0.0f) {
return;
}
if (isfirstframe) {
damageradius = type->damageradius;
} else {
#if PAL
damageradius = type->blastradius + (type->damageradius - type->blastradius) * exp->age / (type->duration * 0.8333333f);
#else
damageradius = type->blastradius + (type->damageradius - type->blastradius) * exp->age / type->duration;
#endif
if (damageradius > type->damageradius) {
damageradius = type->damageradius;
}
}
#if PAL
if (exp->age > (s32)((type->duration + 7.0f * type->flarespeed) * 0.8333333f)) {
return;
}
#else
if (exp->age > (s32)(type->duration + 7.0f * type->flarespeed)) {
return;
}
#endif
// Flicker room lighting
for (i = 0; expprop->rooms[i] != -1; i++) {
if (random() % 2048 <= 240) {
roomFlashLighting(expprop->rooms[i], type->rangeh, 255);
}
}
// Break lights
for (i = 0; expprop->rooms[i] != -1; i++) {
s32 roomnum = expprop->rooms[i];
if (roomnum != 0) {
s32 numlights = g_Rooms[roomnum].numlights;
f32 xdist = expprop->pos.f[0];
f32 ydist = expprop->pos.f[1];
f32 zdist = expprop->pos.f[2];
struct coord sp164;
xdist -= g_BgRooms[roomnum].pos.f[0];
ydist -= g_BgRooms[roomnum].pos.f[1];
zdist -= g_BgRooms[roomnum].pos.f[2];
for (j = 0; j < numlights; j++) {
if (lightIsHealthy(roomnum, j)
&& lightIsVulnerable(roomnum, j)
&& lightGetBboxCentre(roomnum, j, &sp164)) {
struct coord sp158;
struct coord sp14c;
sp14c.f[0] = sp164.f[0] - xdist; \
sp14c.f[1] = sp164.f[1] - ydist; \
sp14c.f[2] = sp164.f[2] - zdist;
sp158.f[0] = damageradius;
sp158.f[1] = damageradius;
sp158.f[2] = damageradius;
if (func0f1773c8(&sp14c, &sp158)) {
roomSetLightBroken(roomnum, j);
}
}
}
}
}
// Damage props
roomGetProps(expprop->rooms, propnums, 256);
propnumptr = propnums;
while (*propnumptr >= 0) {
struct prop *prop = &g_Vars.props[*propnumptr];
if (prop != exp->source && prop->timetoregen == 0) {
if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_WEAPON || prop->type == PROPTYPE_DOOR) {
f32 xdist;
f32 ydist;
f32 zdist;
struct coord sp130;
struct coord sp124;
struct defaultobj *obj = prop->obj;
bool candamage = false;
xdist = prop->pos.x - expprop->pos.x;
ydist = prop->pos.y - expprop->pos.y;
zdist = prop->pos.z - expprop->pos.z;
if (candamage);
#if VERSION >= VERSION_NTSC_1_0
if (obj)
#endif
{
if (xdist <= damageradius && xdist >= -damageradius
&& ydist <= damageradius && ydist >= -damageradius
&& zdist <= damageradius && zdist >= -damageradius) {
if (setup0f092304(obj, &sp130, &sp124)) {
if (explosionOverlapsProp(exp, prop, &sp130, &sp124)) {
candamage = true;
}
} else {
candamage = true;
}
}
if (candamage && prop->type == PROPTYPE_WEAPON) {
struct weaponobj *weapon = prop->weapon;
if (weapon && weapon->weaponnum == WEAPON_SKROCKET) {
weapon->timer240 = 0;
}
}
if (candamage) {
f32 f0;
f32 xfrac;
f32 yfrac;
f32 zfrac;
f32 minfrac;
xfrac = f0 = xdist / damageradius;
if (xfrac < 0.0f) {
xfrac = -xfrac;
}
xfrac = 1.0f - xfrac;
yfrac = f0 = ydist / damageradius;
if (yfrac < 0.0f) {
yfrac = -yfrac;
}
yfrac = 1.0f - yfrac;
zfrac = f0 = zdist / damageradius;
if (zfrac < 0.0f) {
zfrac = -zfrac;
}
zfrac = 1.0f - zfrac;
minfrac = xfrac;
if (yfrac < minfrac) {
minfrac = yfrac;
}
if (zfrac < minfrac) {
minfrac = zfrac;
}
minfrac = (minfrac * 0.7f + 0.3f) * type->damage;
if (g_Vars.antiplayernum >= 0
&& g_Vars.antiplayernum == exp->owner
&& (obj->flags2 & OBJFLAG2_IMMUNETOANTI)) {
// anti cannot damage this obj
} else if (isfirstframe) {
// Unblock path if this object is a path blocker
objUpdateLinkedScenery(obj, expprop);
// Damage the object
if ((obj->hidden & OBJHFLAG_00001000) == 0
&& (obj->flags2 & (OBJFLAG2_LINKEDTOSAFE | OBJFLAG2_IMMUNETOEXPLOSIONS)) == 0) {
func0f085050(prop, (RANDOMFRAC() * 0.5f + 1.0f) * minfrac, &prop->pos, 0x22, exp->owner);
}
// Give object momentum if it's a hover obj
if ((obj->hidden & OBJHFLAG_MOUNTED) == 0
&& (obj->hidden & OBJHFLAG_GRABBED) == 0
&& (obj->flags3 & OBJFLAG3_PUSHABLE)) {
f32 dist;
struct coord spf4;
spf4.x = prop->pos.x - expprop->pos.x;
spf4.y = 0.0f;
spf4.z = prop->pos.z - expprop->pos.z;
if (spf4.f[0] != 0.0f || spf4.f[2] != 0.0f) {
dist = sqrtf(spf4.f[0] * spf4.f[0] + spf4.f[2] * spf4.f[2]);
if (dist > 0.0f) {
f32 tmp = minfrac * 4.0f / dist;
spf4.x *= tmp;
spf4.z *= tmp;
}
}
objApplyMomentum(obj, &spf4, 0.0f, true, true);
}
} else if (objIsHealthy(obj)) {
// Sustained damage
minfrac *= 0.05f * g_Vars.lvupdate60freal;
if ((obj->hidden & OBJHFLAG_00001000) == 0
&& (obj->flags2 & (OBJFLAG2_LINKEDTOSAFE | OBJFLAG2_IMMUNETOEXPLOSIONS)) == 0) {
func0f085050(prop, (RANDOMFRAC() * 0.5f + 1.0f) * minfrac,
&prop->pos, 0x22, exp->owner);
}
}
}
}
} else if (prop->type == PROPTYPE_CHR || prop->type == PROPTYPE_PLAYER) {
f32 xdist = prop->pos.f[0] - expprop->pos.f[0];
f32 ydist = prop->pos.f[1] - expprop->pos.f[1];
f32 zdist = prop->pos.f[2] - expprop->pos.f[2];
f32 radius;
f32 ymax;
f32 ymin;
struct coord spcc;
struct coord spc0;
bool candamage = false;
if (prop->type == PROPTYPE_CHR);
if (xdist <= damageradius && xdist >= -damageradius
&& ydist <= damageradius && ydist >= -damageradius
&& zdist <= damageradius && zdist >= -damageradius) {
propGetBbox(prop, &radius, &ymax, &ymin);
radius -= 20.0f;
if (radius <= 0.0f) {
radius = 0.0f;
}
spcc.f[0] = prop->pos.f[0] - radius;
spcc.f[1] = ymin;
spcc.f[2] = prop->pos.f[2] - radius;
spc0.f[0] = prop->pos.f[0] + radius;
spc0.f[1] = ymax;
spc0.f[2] = prop->pos.f[2] + radius;
if (explosionOverlapsProp(exp, prop, &spcc, &spc0)) {
candamage = true;
}
}
if (candamage) {
struct prop *ownerprop = NULL;
f32 xfrac = xdist / damageradius;
f32 yfrac = ydist / damageradius;
f32 zfrac = zdist / damageradius;
struct coord spa0 = {0, 0, 0};
struct chrdata *chr = prop->chr;
f32 minfrac;
if (xfrac < 0.0f) {
xfrac = -xfrac;
}
if (yfrac < 0.0f) {
yfrac = -yfrac;
}
if (zfrac < 0.0f) {
zfrac = -zfrac;
}
xfrac = 1.0f - xfrac;
yfrac = 1.0f - yfrac;
zfrac = 1.0f - zfrac;
minfrac = xfrac;
if (yfrac < minfrac) {
minfrac = yfrac;
}
if (zfrac < minfrac) {
minfrac = zfrac;
}
minfrac *= minfrac;
minfrac = minfrac * type->damage * 8.0f;
if (isfirstframe) {
if (xdist != 0.0f || zdist != 0.0f) {
f32 dist = sqrtf(xdist * xdist + zdist * zdist);
if (dist > 0.0f) {
xdist *= 1.0f / dist;
zdist *= 1.0f / dist;
spa0.x = xdist;
spa0.y = 0.0f;
spa0.z = zdist;
}
}
} else {
minfrac *= 0.05f * g_Vars.lvupdate60freal;
}
if (g_Vars.normmplayerisrunning) {
struct chrdata *ownerchr = mpGetChrFromPlayerIndex(exp->owner);
if (ownerchr) {
ownerprop = ownerchr->prop;
}
} else if (exp->owner == g_Vars.bondplayernum) {
ownerprop = g_Vars.bond->prop;
} else if (g_Vars.coopplayernum >= 0 && exp->owner == g_Vars.coopplayernum) {
ownerprop = g_Vars.coop->prop;
} else if (g_Vars.antiplayernum >= 0 && exp->owner == g_Vars.antiplayernum) {
ownerprop = g_Vars.anti->prop;
}
chrDamageByExplosion(chr, minfrac, &spa0, ownerprop, &expprop->pos);
if (prop->type == PROPTYPE_CHR && !isfirstframe) {
chrDisfigure(chr, &expprop->pos, damageradius);
}
}
}
}
propnumptr++;
}
}
u32 explosionTick(struct prop *prop)
{
struct explosion *exp = prop->explosion;
struct explosiontype *type = &g_ExplosionTypes[exp->type];
s32 i;
s32 j;
s32 k;
f32 hrange;
f32 vrange;
f32 lvupdate;
struct coord bbmin;
struct coord bbmax;
s16 maxage;
s32 numpartstocreate;
struct coord sp11c;
struct coord sp110;
u32 stack[2];
struct coord spfc;
struct coord spf0;
s32 bb;
bool xlu;
struct chrdata *chr;
f32 scorchsize;
struct hitthing hitthing;
maxage = TICKS(type->duration);
if (g_Vars.lvupdate60 == 0) {
return TICKOP_NONE;
}
lvupdate = g_Vars.lvupdate60 < TICKS(15) ? g_Vars.lvupdate60 : (s32)TICKS(15);
#if PAL
if (exp->age >= 7 && exp->age < maxage)
#else
if (exp->age >= 8 && exp->age < maxage)
#endif
{
hrange = explosionGetHorizontalRangeAtFrame(exp, exp->age);
vrange = explosionGetVerticalRangeAtFrame(exp, exp->age);
sp11c.x = prop->pos.x - hrange * 0.5f;
sp11c.y = prop->pos.y - vrange * 0.5f;
sp11c.z = prop->pos.z - hrange * 0.5f;
sp110.x = prop->pos.x + hrange * 0.5f;
sp110.y = prop->pos.y + vrange * 0.5f;
sp110.z = prop->pos.z + hrange * 0.5f;
// Barrel explosions ascend upwards
if (exp->type == EXPLOSIONTYPE_GASBARREL && exp->age < TICKS(32)) {
prop->pos.y += PALUPF(10.0f) * lvupdate;
}
// Create new parts
numpartstocreate = (s32)((f32)type->propagationrate * exp->age / maxage) + 1;
for (i = 0; i < numpartstocreate; i++) {
for (j = 0; j < 40; j++) {
if (exp->parts[j].frame == 0) {
if (exp->numbb == 0 || exp->type == EXPLOSIONTYPE_HUGE25) {
spfc.f[0] = sp11c.f[0];
spfc.f[1] = sp11c.f[1];
spfc.f[2] = sp11c.f[2];
spf0.f[0] = sp110.f[0];
spf0.f[1] = sp110.f[1];
spf0.f[2] = sp110.f[2];
bb = 0;
} else {
bb = j % exp->numbb;
spfc.x = exp->bbs[bb].bbmin.x;
spfc.y = exp->bbs[bb].bbmin.y;
spfc.z = exp->bbs[bb].bbmin.z;
spf0.x = exp->bbs[bb].bbmax.x;
spf0.y = exp->bbs[bb].bbmax.y;
spf0.z = exp->bbs[bb].bbmax.z;
if (spfc.x < sp11c.x) {
spfc.x = sp11c.x;
}
if (spfc.y < sp11c.y) {
spfc.y = sp11c.y;
}
if (spfc.z < sp11c.z) {
spfc.z = sp11c.z;
}
if (sp110.x < spf0.x) {
spf0.x = sp110.x;
}
if (sp110.y < spf0.y) {
spf0.y = sp110.y;
}
if (sp110.z < spf0.z) {
spf0.z = sp110.z;
}
if (spf0.x <= spfc.x || spf0.y <= spfc.y || spf0.z <= spfc.z) {
bb = 0;
spfc.x = exp->bbs[bb].bbmin.x;
spfc.y = exp->bbs[bb].bbmin.y;
spfc.z = exp->bbs[bb].bbmin.z;
spf0.x = exp->bbs[bb].bbmax.x;
spf0.y = exp->bbs[bb].bbmax.y;
spf0.z = exp->bbs[bb].bbmax.z;
if (spfc.x < sp11c.x) {
spfc.x = sp11c.x;
}
if (spfc.y < sp11c.y) {
spfc.y = sp11c.y;
}
if (spfc.z < sp11c.z) {
spfc.z = sp11c.z;
}
if (sp110.x < spf0.x) {
spf0.x = sp110.x;
}
if (sp110.y < spf0.y) {
spf0.y = sp110.y;
}
if (sp110.z < spf0.z) {
spf0.z = sp110.z;
}
}
}
exp->parts[j].pos.f[0] = spfc.f[0] + RANDOMFRAC() * (spf0.f[0] - spfc.f[0]);
exp->parts[j].pos.f[1] = spfc.f[1] + RANDOMFRAC() * (spf0.f[1] - spfc.f[1]);
exp->parts[j].pos.f[2] = spfc.f[2] + RANDOMFRAC() * (spf0.f[2] - spfc.f[2]);
exp->parts[j].bb = bb;
exp->parts[j].frame = 1;
exp->parts[j].size = (1.0f + RANDOMFRAC() * 0.5f) * type->innersize;
exp->parts[j].rot = RANDOMFRAC() * M_BADTAU;
break;
}
}
}
}
explosionGetBboxAtFrame(&bbmin, &bbmax, exp->age, prop);
bgFindEnteredRooms(&bbmin, &bbmax, prop->rooms, 7, false);
explosionInflictDamage(prop);
// Play boom sound if this is the first frame
if (exp->age == 0) {
psCreate(NULL, NULL, type->sound, -1, -1, 0, 0, PSTYPE_NONE, &exp->prop->pos, -1.0f, exp->prop->rooms, -1, -1.0f, -1.0f, -1.0f);
}
for (k = 0; k < (s32)lvupdate; k++) {
exp->age++;
for (j = 0; j < 40; j++) {
if (exp->parts[j].frame > 0) {
exp->parts[j].frame++;
}
}
// Create smoke
if (((exp->age == TICKS(15) && exp->type == EXPLOSIONTYPE_GASBARREL)
|| (exp->age == maxage - TICKS(20) && exp->type != EXPLOSIONTYPE_GASBARREL))
&& (exp->type != EXPLOSIONTYPE_BULLETHOLE || (random() % 2) == 0)) {
if (exp->source) {
smokeCreateSimple(&exp->source->pos, exp->source->rooms, type->smoketype);
} else {
smokeCreateSimple(&prop->pos, prop->rooms, type->smoketype);
}
}
// Make scorch at half duration
if (exp->age == (maxage >> 1) && exp->makescorch) {
xlu = false;
scorchsize = 2.0f * type->innersize;
if (scorchsize > 100.0f) {
scorchsize = 100.0f;
}
scorchsize *= 0.8f + 0.2f * RANDOMFRAC();
if (g_Vars.normmplayerisrunning) {
chr = mpGetChrFromPlayerIndex(exp->owner);
} else if (g_Vars.antiplayernum >= 0 && exp->owner == g_Vars.antiplayernum) {
chr = g_Vars.anti->prop->chr;
} else if (g_Vars.coopplayernum >= 0 && exp->owner == g_Vars.coopplayernum) {
chr = g_Vars.coop->prop->chr;
} else {
chr = g_Vars.bond->prop->chr;
}
if (g_Rooms[exp->room].gfxdata) {
if (g_Rooms[exp->room].gfxdata->xlublocks && bgTestHitInRoom(&prop->pos, &exp->unk3d0, exp->room, &hitthing)) {
xlu = hitthing.unk2c == 2;
}
wallhitCreateWith20Args(&exp->unk3d0, &exp->unk3dc, &prop->pos, NULL,
0, WALLHITTEX_SCORCH, exp->room, 0,
0, -1, 0, chr,
scorchsize, scorchsize, 0xff, 0xff,
0, 0, 0, xlu);
}
}
}
// Free explosion if finished
#if PAL
if (exp->age >= maxage + (s32)((16.0f * type->flarespeed) * 0.8333333f))
#else
if (exp->age >= maxage + (s32)(16.0f * type->flarespeed))
#endif
{
if (exp->type != EXPLOSIONTYPE_BULLETHOLE) {
propUnsetDangerous(exp->prop);
}
exp->prop = NULL;
return TICKOP_FREE;
}
return TICKOP_NONE;
}
u32 explosionTickPlayer(struct prop *prop)
{
Mtxf *matrix = camGetWorldToScreenMtxf();
prop->z = -(matrix->m[0][2] * prop->pos.x + matrix->m[1][2] * prop->pos.y + matrix->m[2][2] * prop->pos.z + matrix->m[3][2]);
if (prop->z < 100) {
prop->z *= 0.5f;
} else {
prop->z -= 100;
}
prop->flags |= PROPFLAG_ONANYSCREENTHISTICK | PROPFLAG_ONTHISSCREENTHISTICK;
return TICKOP_NONE;
}
Gfx *explosionRender(struct prop *prop, Gfx *gdl, bool xlupass)
{
struct explosion *exp = prop->explosion;
s32 roomnum;
s32 i;
s32 j;
if (!xlupass) {
return gdl;
}
j = 0;
roomnum = prop->rooms[j];
while (roomnum != -1) {
if (g_Rooms[roomnum].gfxdata && g_Rooms[roomnum].loaded240 && (g_Rooms[roomnum].flags & ROOMFLAG_ONSCREEN)) {
break;
}
j++;
roomnum = prop->rooms[j];
}
if (roomnum != -1) {
struct screenbox screenbox;
struct coord *coord = roomGetPosPtr(roomnum);
Col *colours;
s32 tmp;
if (func0f08e5a8(prop->rooms, &screenbox) > 0) {
gdl = bgScissorWithinViewport(gdl, screenbox.xmin, screenbox.ymin, screenbox.xmax, screenbox.ymax);
} else {
gdl = bgScissorToViewport(gdl);
}
gSPClearGeometryMode(gdl++, G_CULL_BOTH | G_FOG);
gSPMatrix(gdl++, osVirtualToPhysical(camGetOrthogonalMtxL()), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION);
gdl = roomApplyMtx(gdl, roomnum);
gSPDisplayList(gdl++, g_TexGdl2);
colours = gfxAllocateColours(1);
if (USINGDEVICE(DEVICE_NIGHTVISION) || USINGDEVICE(DEVICE_IRSCANNER)) {
colours[0].word = 0xffffffff;
} else if (g_Vars.currentplayer->visionmode == VISIONMODE_XRAY) {
u32 alpha = 0x80;
u32 red;
u32 green;
f32 expdist = sqrtf(ERASERSQDIST(prop->pos.f));
if (g_Vars.currentplayer->eraserpropdist < expdist) {
return gdl;
}
if (g_Vars.currentplayer->eraserpropdist - 150.0f < expdist) {
alpha = (1.0f - (expdist - (g_Vars.currentplayer->eraserpropdist - 150.0f)) / 150.0f) * 128.0f;
}
expdist = expdist / g_Vars.currentplayer->eraserpropdist;
if (expdist > 1.0f) {
expdist = 1.0f;
}
red = expdist * 127.0f;
green = (1.0f - expdist) * 127.0f;
colours[0].word = PD_BE32(red << 24 | green << 16 | alpha | 0x80800000);
} else {
static u32 var8007e93c = 0xffffffff;
mainOverrideVariable("ecol", &var8007e93c);
colours[0].word = 0xffffffff;
colours[0].word = var8007e93c;
}
gSPColor(gdl++, osVirtualToPhysical(colours), 1);
for (i = 14; i >= 0; i--) {
gDPSetTextureImage(gdl++, G_IM_FMT_IA, G_IM_SIZ_16b, 1, g_ExplosionTexturePairs[i].texturenum1);
gDPLoadSync(gdl++);
gDPLoadBlock(gdl++, G_TX_LOADTILE, 0, 0, 1567, 0);
gDPSetTextureImage(gdl++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 1, g_ExplosionTexturePairs[i].texturenum2);
gDPLoadSync(gdl++);
gDPLoadBlock(gdl++, 5, 0, 0, 223, 0);
gDPPipeSync(gdl++);
for (j = 0; j < ARRAYCOUNT(exp->parts); j++) {
if (exp->parts[j].frame > 0) {
#if PAL
if (i == (s32)((f32)(exp->parts[j].frame - 1) / (g_ExplosionTypes[exp->type].flarespeed * 0.83333331346512f))) {
gdl = explosionRenderPart(exp, &exp->parts[j], gdl, coord, i);
}
#else
if (i == (s32)((f32)(exp->parts[j].frame - 1) / g_ExplosionTypes[exp->type].flarespeed)) {
gdl = explosionRenderPart(exp, &exp->parts[j], gdl, coord, i);
}
#endif
}
}
}
gSPMatrix(gdl++, osVirtualToPhysical(camGetPerspectiveMtxL()), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION);
#if PAL
tmp = (g_ExplosionTypes[exp->type].flarespeed * 15.0f) * 0.83333331346512f;
#else
tmp = g_ExplosionTypes[exp->type].flarespeed * 15.0f;
#endif
for (j = 0; j < ARRAYCOUNT(exp->parts); j++) {
if (exp->parts[j].frame > tmp) {
exp->parts[j].frame = 0;
}
}
}
return gdl;
}
Gfx *explosionRenderPart(struct explosion *exp, struct explosionpart *part, Gfx *gdl, struct coord *coord, s32 arg4)
{
Vtx *vertices = gfxAllocateVertices(4);
Mtxf *mtx = camGetProjectionMtxF();
struct coord spbc;
struct coord spb0;
struct coord spa4;
struct coord sp98;
f32 x;
f32 y;
f32 z;
s32 i;
s32 j;
f32 size;
f32 cosine;
f32 max;
f32 size2;
struct coord pos;
f32 sine;
s32 bbnum;
f32 value;
size = part->size;
x = part->pos.x;
y = part->pos.y;
z = part->pos.z;
if (exp->numbb > 0) {
pos.x = x;
pos.y = y;
pos.z = z;
bbnum = part->bb;
max = 0.0f;
for (i = 0; i < exp->numbb; i++) {
if (pos.f[0] >= exp->bbs[i].bbmin.f[0] && pos.f[0] <= exp->bbs[i].bbmax.f[0]
&& pos.f[1] >= exp->bbs[i].bbmin.f[1] && pos.f[1] <= exp->bbs[i].bbmax.f[1]
&& pos.f[2] >= exp->bbs[i].bbmin.f[2] && pos.f[2] <= exp->bbs[i].bbmax.f[2]) {
f32 min = 65536.0f;
for (j = 0; j < 3; j++) {
value = pos.f[j] - exp->bbs[i].bbmin.f[j];
if (value < min) {
min = value;
}
value = exp->bbs[i].bbmax.f[j] - pos.f[j];
if (value < min) {
min = value;
}
}
if (min > max) {
max = min;
bbnum = i;
}
}
}
size2 = size * 0.7f;
for (i = 0; i < 3; i++) {
if ((exp->bbs[bbnum].bbmax.f[i] - exp->bbs[bbnum].bbmin.f[i]) * 0.8f < size2) {
size = (exp->bbs[bbnum].bbmax.f[i] - exp->bbs[bbnum].bbmin.f[i]) * 0.8f / 0.7f;
if (part->size * 0.65f > size) {
size = part->size * 0.65f;
}
size2 = size * 0.7f;
}
}
if (arg4 == 1) {
size2 *= 0.58928573f;
} else if (arg4 == 2) {
size2 *= 0.6964286f;
} else if (arg4 == 3) {
size2 *= 0.8214286f;
} else if (arg4 == 4) {
size2 *= 0.96428573f;
}
for (i = 0; i < 3; i++) {
if (exp->bbs[bbnum].bbmax.f[i] - exp->bbs[bbnum].bbmin.f[i] < size2) {
pos.f[i] = (exp->bbs[bbnum].bbmax.f[i] + exp->bbs[bbnum].bbmin.f[i]) * 0.5f;
} else {
value = exp->bbs[bbnum].bbmin.f[i] + size2 - pos.f[i];
if (value > 0.0f) {
pos.f[i] += value;
} else {
value = pos.f[i] - (exp->bbs[bbnum].bbmax.f[i] - size2);
if (value > 0.0f) {
pos.f[i] -= value;
}
}
}
}
x = pos.x;
y = pos.y;
z = pos.z;
}
cosine = cosf(part->rot) * size;
sine = sinf(part->rot) * size;
spbc.x = mtx->m[0][0] * cosine;
spbc.y = mtx->m[0][1] * cosine;
spbc.z = mtx->m[0][2] * cosine;
spb0.x = mtx->m[0][0] * sine;
spb0.y = mtx->m[0][1] * sine;
spb0.z = mtx->m[0][2] * sine;
spa4.x = mtx->m[1][0] * cosine;
spa4.y = mtx->m[1][1] * cosine;
spa4.z = mtx->m[1][2] * cosine;
sp98.x = mtx->m[1][0] * sine;
sp98.y = mtx->m[1][1] * sine;
sp98.z = mtx->m[1][2] * sine;
vertices[0].x = x - spbc.f[0] - sp98.f[0] - coord->f[0];
vertices[0].y = y - spbc.f[1] - sp98.f[1] - coord->f[1];
vertices[0].z = z - spbc.f[2] - sp98.f[2] - coord->f[2];
vertices[0].s = 1760;
vertices[0].t = 0;
vertices[1].x = x + spb0.f[0] - spa4.f[0] - coord->f[0];
vertices[1].y = y + spb0.f[1] - spa4.f[1] - coord->f[1];
vertices[1].z = z + spb0.f[2] - spa4.f[2] - coord->f[2];
vertices[1].s = 0;
vertices[1].t = 0;
vertices[2].x = x + spbc.f[0] + sp98.f[0] - coord->f[0];
vertices[2].y = y + spbc.f[1] + sp98.f[1] - coord->f[1];
vertices[2].z = z + spbc.f[2] + sp98.f[2] - coord->f[2];
vertices[2].s = 0;
vertices[2].t = 1760;
vertices[3].x = x - spb0.f[0] + spa4.f[0] - coord->f[0];
vertices[3].y = y - spb0.f[1] + spa4.f[1] - coord->f[1];
vertices[3].z = z - spb0.f[2] + spa4.f[2] - coord->f[2];
vertices[3].s = 1760;
vertices[3].t = 1760;
for (j = 0; j < 4; j++) {
vertices[j].colour = 0;
}
gSPVertex(gdl++, osVirtualToPhysical(vertices), 4, 0);
gSPTri2(gdl++, 0, 1, 2, 0, 2, 3);
return gdl;
}