#include #include "lib/sched.h" #include "constants.h" #include "game/bondmove.h" #include "game/cheats.h" #include "game/chraction.h" #include "game/debug.h" #include "game/chr.h" #include "game/env.h" #include "game/prop.h" #include "game/propsnd.h" #include "game/objectives.h" #include "game/projectile.h" #include "game/bondgun.h" #include "game/gunfx.h" #include "game/gset.h" #include "game/modelmgr.h" #include "game/tex.h" #include "game/camera.h" #include "game/player.h" #include "game/mtxf2lbulk.h" #include "game/playermgr.h" #include "game/rng2.h" #include "game/vtxstore.h" #include "game/gfxmemory.h" #include "game/explosions.h" #include "game/smoke.h" #include "game/sparks.h" #include "game/bg.h" #include "game/file.h" #include "game/mplayer/setup.h" #include "game/bot.h" #include "game/botact.h" #include "game/mplayer/mplayer.h" #include "game/pad.h" #include "game/propobj.h" #include "game/splat.h" #include "game/wallhit.h" #include "bss.h" #include "lib/vi.h" #include "lib/main.h" #include "lib/model.h" #include "lib/rng.h" #include "lib/mtx.h" #include "lib/anim.h" #include "lib/collision.h" #include "data.h" #include "gbiex.h" #include "types.h" void rng2_set_seed(u32 seed); void *var8009ccc0[20]; s32 g_NumChrs; s16 *g_Chrnums; s16 *g_ChrIndexes; struct chrdata *g_CurModelChr; struct onscreendoor *g_ChrsOnscreenDoors = NULL; s32 g_ChrsNumOnscreenDoors = 0; f32 g_ChrsAnimSpeed = 0; bool g_ChrsAnimDebugSetAll = false; s32 g_ChrsAnimDebugAnimNum = 0; u32 g_ChrsAnimDebugPaused = 0x00000000; u32 g_ChrsAnimDebugSlow = 0x00000000; bool g_ChrsDebugPatrols = false; bool g_ChrsDebugSomething = false; s32 g_NextChrnum = 5000; struct chrdata *g_ChrSlots = NULL; s32 g_NumChrSlots = 0; Gfx *chr_render_cloak(Gfx *gdl, struct prop *chrprop, struct prop *thisprop); Gfx *chr_render_shield(Gfx *gdl, struct chrdata *chr, u32 alpha); void chr_set_drcaroll_images(struct chrdata *drcaroll, s32 imageleft, s32 imageright); s32 chrs_get_num_slots(void) { return g_NumChrSlots; } void chr_set_chrnum(struct chrdata *chr, s16 chrnum) { s32 i; bool modified; s16 tmp; // Set the new chrnum for (i = 0; i < g_NumChrs; i++) { if (g_Chrnums[i] == chr->chrnum) { g_Chrnums[i] = chrnum; break; } } chr->chrnum = chrnum; // Sort the g_Chrnums and g_ChrIndexes arrays do { modified = false; for (i = 0; i < g_NumChrs - 1; i++) { if (g_Chrnums[i] > g_Chrnums[i + 1]) { tmp = g_Chrnums[i]; g_Chrnums[i] = g_Chrnums[i + 1]; g_Chrnums[i + 1] = tmp; tmp = g_ChrIndexes[i]; g_ChrIndexes[i] = g_ChrIndexes[i + 1]; g_ChrIndexes[i + 1] = tmp; modified = true; } } } while (modified); } void chr_register(s32 chrnum, s32 chrindex) { s32 i; s16 tmp; for (i = 0; i < g_NumChrs; i++) { if (g_Chrnums[i] > chrnum) { tmp = g_Chrnums[i]; g_Chrnums[i] = chrnum; chrnum = tmp; tmp = g_ChrIndexes[i]; g_ChrIndexes[i] = chrindex; chrindex = tmp; } } g_Chrnums[g_NumChrs] = chrnum; g_ChrIndexes[g_NumChrs] = chrindex; g_NumChrs++; } void chr_deregister(s32 chrnum) { s32 i; for (i = 0; i < g_NumChrs; i++) { if (g_Chrnums[i] == chrnum) { s32 j = i + 1; while (j < g_NumChrs) { g_Chrnums[i] = g_Chrnums[j]; g_ChrIndexes[i] = g_ChrIndexes[j]; i++; j++; } g_NumChrs--; return; } } } Vtx *chr_allocate_vertices(s32 numvertices) { return (Vtx *) gfx_allocate(numvertices * sizeof(Vtx)); } void chrs_set_debug_patrols(bool enabled) { g_ChrsDebugPatrols = enabled; } bool chrs_get_debug_patrols(void) { return g_ChrsDebugPatrols; } void chrs_set_debug_something(bool enabled) { g_ChrsDebugSomething = enabled; } bool chrs_get_debug_something(void) { return g_ChrsDebugSomething; } void chr_set_perim_enabled(struct chrdata *chr, bool enable) { if (chr) { if (enable) { chr->hidden &= ~CHRHFLAG_PERIMDISABLED; } else { chr->hidden |= CHRHFLAG_PERIMDISABLED; } } } /** * When a chr is being pushed by a player, this function is called with the new * pos and rooms for the chr. It does collision checks and updates dstpos and * dstrooms to valid ones if needed. */ void chr_calculate_push_pos(struct chrdata *chr, struct coord *dstpos, RoomNum *dstrooms, bool arg3) { f32 ymax; f32 ymin; f32 radius; bool moveok = false; f32 movex; f32 movez; struct prop *prop = chr->prop; u32 stack; f32 halfradius; struct defaultobj *chair = NULL; s32 cdresult; RoomNum sp84[20]; #if VERSION < VERSION_NTSC_1_0 s32 i; #endif struct coord sp78; struct coord sp6c; struct coord sp60; struct coord sp54; f32 value; struct coord sp44; #if VERSION < VERSION_NTSC_1_0 s32 j; s32 k; s32 l; #endif // The eyespy can't be pushed if (CHRRACE(chr) == RACE_EYESPY) { rooms_copy(prop->rooms, dstrooms); chr_set_perim_enabled(chr, true); return; } chr_get_bbox(prop, &radius, &ymax, &ymin); halfradius = radius * 0.5f; chr_set_perim_enabled(chr, false); // myspecial is the chr's chair if (chr->myspecial != -1) { chair = obj_find_by_tag_id(chr->myspecial); if (chair && chair->prop) { obj_set_perim_enabled(chair->prop, false); } } los_find_intersecting_rooms_exhaustive(&prop->pos, prop->rooms, dstpos, dstrooms, sp84, 20); #if VERSION < VERSION_NTSC_1_0 for (i = 0; dstrooms[i] != -1; i++) { if (dstrooms[i] == chr->floorroom) { dstrooms[0] = chr->floorroom; dstrooms[1] = -1; break; } } #endif chr_find_entered_rooms_at_pos(chr, dstpos, dstrooms); movex = dstpos->x - prop->pos.x; movez = dstpos->z - prop->pos.z; if (movex > halfradius || movez > halfradius || movex < -halfradius || movez < -halfradius) { cdresult = cd_test_cylmove_oobfail_findclosest(&prop->pos, prop->rooms, dstpos, dstrooms, CDTYPE_ALL, CHECKVERTICAL_YES, ymax - prop->pos.y, ymin - prop->pos.y); if (cdresult == CDRESULT_NOCOLLISION) { cdresult = cd_test_volume_closestedge(&prop->pos, dstpos, radius, dstrooms, CDTYPE_ALL, CHECKVERTICAL_YES, ymax - prop->pos.y, ymin - prop->pos.y); } } else { cdresult = cd_test_volume_closestedge(&prop->pos, dstpos, radius, sp84, CDTYPE_ALL, CHECKVERTICAL_YES, ymax - prop->pos.y, ymin - prop->pos.y); } if (cdresult == CDRESULT_ERROR) { // empty } else if (cdresult == CDRESULT_NOCOLLISION) { // The move was completely valid if (arg3) { chr->invalidmove = 0; chr->lastmoveok60 = g_Vars.lvframe60; } moveok = true; } else { // Collision #if VERSION >= VERSION_PAL_FINAL cd_get_edge(&sp78, &sp6c, 453, "chr/chr.c"); #elif VERSION >= VERSION_PAL_BETA cd_get_edge(&sp78, &sp6c, 453, "chr.c"); #elif VERSION >= VERSION_NTSC_1_0 cd_get_edge(&sp78, &sp6c, 453, "chr/chr.c"); #else cd_get_edge(&sp78, &sp6c, 451, "chr.c"); #endif // Attempt to find a valid position - method #1 sp60.x = dstpos->x - prop->pos.x; sp60.z = dstpos->z - prop->pos.z; if (sp78.f[0] != sp6c.f[0] || sp78.f[2] != sp6c.f[2]) { sp54.x = sp6c.x - sp78.x; sp54.z = sp6c.z - sp78.z; value = 1.0f / sqrtf(sp54.f[0] * sp54.f[0] + sp54.f[2] * sp54.f[2]); sp54.x *= value; sp54.z *= value; value = sp60.f[0] * sp54.f[0] + sp60.f[2] * sp54.f[2]; sp44.x = sp54.x * value + prop->pos.x; sp44.y = dstpos->y; sp44.z = sp54.z * value + prop->pos.z; los_find_intersecting_rooms_exhaustive(&prop->pos, prop->rooms, &sp44, dstrooms, sp84, 20); #if VERSION < VERSION_NTSC_1_0 for (j = 0; dstrooms[j] != -1; j++) { if (dstrooms[j] == chr->floorroom) { dstrooms[0] = chr->floorroom; dstrooms[1] = -1; break; } } #endif chr_find_entered_rooms_at_pos(chr, &sp44, dstrooms); movex = sp44.x - prop->pos.x; movez = sp44.z - prop->pos.z; if (movex > halfradius || movez > halfradius || movex < -halfradius || movez < -halfradius) { cdresult = cd_test_cylmove_oobfail(&prop->pos, prop->rooms, &sp44, dstrooms, CDTYPE_ALL, CHECKVERTICAL_YES, ymax - prop->pos.y, ymin - prop->pos.y); if (cdresult == CDRESULT_NOCOLLISION) { cdresult = cd_test_volume_simple(&sp44, radius, dstrooms, CDTYPE_ALL, CHECKVERTICAL_YES, ymax - prop->pos.y, ymin - prop->pos.y); } } else { cdresult = cd_test_volume_simple(&sp44, radius, sp84, CDTYPE_ALL, CHECKVERTICAL_YES, ymax - prop->pos.y, ymin - prop->pos.y); } if (cdresult == CDRESULT_NOCOLLISION) { dstpos->x = sp44.x; dstpos->z = sp44.z; chr->invalidmove = 2; moveok = true; } } if (!moveok) { // Attempt to find a valid position - method #2 sp54.x = sp78.x - dstpos->x; sp54.z = sp78.z - dstpos->z; if (sp54.f[0] * sp54.f[0] + sp54.f[2] * sp54.f[2] <= radius * radius) { if (sp78.f[0] != prop->pos.f[0] || sp78.f[2] != prop->pos.f[2]) { sp54.x = -(sp78.z - prop->pos.z); sp54.z = sp78.x - prop->pos.x; value = 1.0f / sqrtf(sp54.f[0] * sp54.f[0] + sp54.f[2] * sp54.f[2]); sp54.x *= value; sp54.z *= value; value = sp60.f[0] * sp54.f[0] + sp60.f[2] * sp54.f[2]; sp44.x = sp54.x * value + prop->pos.x; sp44.y = dstpos->y; sp44.z = sp54.z * value + prop->pos.z; los_find_intersecting_rooms_exhaustive(&prop->pos, prop->rooms, &sp44, dstrooms, sp84, 20); #if VERSION < VERSION_NTSC_1_0 for (k = 0; dstrooms[k] != -1; k++) { if (dstrooms[k] == chr->floorroom) { dstrooms[0] = chr->floorroom; dstrooms[1] = -1; break; } } #endif chr_find_entered_rooms_at_pos(chr, &sp44, dstrooms); movex = sp44.x - prop->pos.x; movez = sp44.z - prop->pos.z; if (movex > halfradius || movez > halfradius || movex < -halfradius || movez < -halfradius) { cdresult = cd_test_cylmove_oobfail(&prop->pos, prop->rooms, &sp44, dstrooms, CDTYPE_ALL, CHECKVERTICAL_YES, ymax - prop->pos.y, ymin - prop->pos.y); if (cdresult == CDRESULT_NOCOLLISION) { cdresult = cd_test_volume_simple(&sp44, radius, dstrooms, CDTYPE_ALL, CHECKVERTICAL_YES, ymax - prop->pos.y, ymin - prop->pos.y); } } else { cdresult = cd_test_volume_simple(&sp44, radius, sp84, CDTYPE_ALL, CHECKVERTICAL_YES, ymax - prop->pos.y, ymin - prop->pos.y); } if (cdresult == CDRESULT_NOCOLLISION) { dstpos->x = sp44.x; dstpos->z = sp44.z; chr->invalidmove = 2; moveok = true; } } } else { sp54.x = sp6c.x - dstpos->x; sp54.z = sp6c.z - dstpos->z; if (sp54.f[0] * sp54.f[0] + sp54.f[2] * sp54.f[2] <= radius * radius) { if (sp6c.f[0] != prop->pos.f[0] || sp6c.f[2] != prop->pos.f[2]) { sp54.x = -(sp6c.z - prop->pos.z); sp54.z = sp6c.x - prop->pos.x; value = 1.0f / sqrtf(sp54.f[0] * sp54.f[0] + sp54.f[2] * sp54.f[2]); sp54.x *= value; sp54.z *= value; value = sp60.f[0] * sp54.f[0] + sp60.f[2] * sp54.f[2]; sp44.x = sp54.x * value + prop->pos.x; sp44.y = dstpos->y; sp44.z = sp54.z * value + prop->pos.z; los_find_intersecting_rooms_exhaustive(&prop->pos, prop->rooms, &sp44, dstrooms, sp84, 20); #if VERSION < VERSION_NTSC_1_0 for (l = 0; dstrooms[l] != -1; l++) { if (dstrooms[l] == chr->floorroom) { dstrooms[0] = chr->floorroom; dstrooms[1] = -1; break; } } #endif chr_find_entered_rooms_at_pos(chr, &sp44, dstrooms); movex = sp44.x - prop->pos.x; movez = sp44.z - prop->pos.z; if (movex > halfradius || movez > halfradius || movex < -halfradius || movez < -halfradius) { cdresult = cd_test_cylmove_oobfail(&prop->pos, prop->rooms, &sp44, dstrooms, CDTYPE_ALL, CHECKVERTICAL_YES, ymax - prop->pos.y, ymin - prop->pos.y); if (cdresult == CDRESULT_NOCOLLISION) { cdresult = cd_test_volume_simple(&sp44, radius, dstrooms, CDTYPE_ALL, CHECKVERTICAL_YES, ymax - prop->pos.y, ymin - prop->pos.y); } } else { cdresult = cd_test_volume_simple(&sp44, radius, sp84, CDTYPE_ALL, CHECKVERTICAL_YES, ymax - prop->pos.y, ymin - prop->pos.y); } if (cdresult == CDRESULT_NOCOLLISION) { dstpos->x = sp44.x; dstpos->z = sp44.z; chr->invalidmove = 2; moveok = true; } } } } } } if (!moveok) { // Keep chr where they are dstpos->x = prop->pos.x; dstpos->z = prop->pos.z; rooms_copy(prop->rooms, dstrooms); chr->invalidmove = 1; } chr_set_perim_enabled(chr, true); if (chair && chair->prop) { obj_set_perim_enabled(chair->prop, true); } } #if VERSION >= VERSION_NTSC_1_0 bool chr_ascend(struct chrdata *chr, struct coord *pos, RoomNum *rooms, f32 amount, bool writerooms) #else bool chr_ascend(struct chrdata *chr, struct coord *pos, RoomNum *rooms, f32 amount) #endif { bool result; struct coord newpos; RoomNum newrooms[8]; f32 ymax; f32 ymin; f32 radius; newpos.x = pos->x; newpos.y = pos->y + amount; newpos.z = pos->z; chr_get_bbox(chr->prop, &radius, &ymax, &ymin); los_find_final_room_exhaustive(pos, rooms, &newpos, newrooms); chr_find_entered_rooms_at_pos(chr, &newpos, newrooms); chr_set_perim_enabled(chr, false); result = cd_test_volume_simple(&newpos, radius, newrooms, CDTYPE_ALL, CHECKVERTICAL_YES, ymax - chr->prop->pos.y, ymin - chr->prop->pos.y); chr_set_perim_enabled(chr, true); #if VERSION >= VERSION_NTSC_1_0 if (result == true && writerooms) { pos->y = newpos.y; rooms_copy(newrooms, rooms); } #endif return result == CDRESULT_NOCOLLISION; } bool chr_update_position(struct model *model, struct coord *arg1, struct coord *arg2, f32 *mangroundptr) { struct chrdata *chr = model->chr; struct prop *prop = chr->prop; struct prop *lift; s32 i; f32 ground; RoomNum spfc[8]; f32 manground = chr->manground; s32 race = CHRRACE(chr); f32 yincrement = 0.0f; bool inlift; u16 floorflags = 0; #if VERSION >= VERSION_NTSC_1_0 s32 lvupdate240; f32 lvupdate60f; f32 lvupdate60freal; struct coord spd0; RoomNum spc0[8]; #endif // NTSC beta reads g_Vars lvupdate properties throughout this function, // while NTSC 1.0 and newer copy them into stack variables at the start. // A macro is used here for readability #if VERSION >= VERSION_NTSC_1_0 lvupdate240 = g_Vars.lvupdate240; lvupdate60f = g_Vars.lvupdate60f; lvupdate60freal = g_Vars.lvupdate60freal; #define VAR(property) property #else #define VAR(property) g_Vars.property #endif if (g_Anims[model->anim->animnum].flags & ANIMFLAG_ABSOLUTETRANSLATION) { if (chr->hidden & CHRHFLAG_FINDROOMSFAST) { los_find_final_room_fast(&prop->pos, prop->rooms, arg2, spfc); } else { los_find_final_room_exhaustive(&prop->pos, prop->rooms, arg2, spfc); } ground = cd_find_ground_at_cyl_ctfril(arg2, chr->radius, spfc, &chr->floorcol, &chr->floortype, &floorflags, &chr->floorroom, &inlift, &lift); if (ground < -1000000) { ground = 0.0f; } chr->ground = ground; chr->act_die.timeextra = 0.0f; // @bug? Assuming the actiontype chr->fallspeed.x = 0.0f; chr->fallspeed.y = 0.0f; chr->fallspeed.z = 0.0f; chr->manground = ground; chr->sumground = ground * (PAL ? 8.4175090789795f : 9.999998f); arg2->y -= ground; } else { arg2->y += manground; if (chr->aibot) { f32 move[2] = {0, 0}; if (VAR(lvupdate240) > 0) { #if VERSION >= VERSION_NTSC_1_0 if (chr->aibot->forceslowupdates != 0) { // forceslowupdates is set when the bot is being saved from // falling out of bounds due to high lag. It forces them to // update in smaller increments, which gives a higher chance // of their collision detection working correctly. if (chr->prop->flags & PROPFLAG_ONANYSCREENPREVTICK) { chr->aibot->forceslowupdates = 0; } else { chr->aibot->forceslowupdates--; if (lvupdate240 >= 25) { lvupdate60f = 4.0f; lvupdate60freal = PALUPF(4.0f); lvupdate240 = 16; } } } else if (chr->onladder && ((chr->prop->flags & (PROPFLAG_ONANYSCREENTHISTICK | PROPFLAG_ONANYSCREENPREVTICK)) == 0) && lvupdate240 >= 25) { lvupdate60f = 4.0f; lvupdate60freal = PALUPF(4.0f); lvupdate240 = 16; } bot_update_lateral(chr, move, lvupdate240, lvupdate60freal); #else bot_update_lateral(chr, move); #endif } arg2->x = arg1->x + move[0]; arg2->z = arg1->z + move[1]; } if (chr->actiontype == ACT_PATROL || chr->actiontype == ACT_GOPOS) { chr->onladder = cd_find_ladder(&chr->prop->pos, chr->radius * 2.5f, chr->manground + chr->height - chr->prop->pos.y, chr->manground + 1.0f - chr->prop->pos.y, chr->prop->rooms, GEOFLAG_LADDER, &chr->laddernormal); } else { chr->onladder = false; } if (chr->aibot != NULL) { chr->height = 185.0f; if (chr->actiontype == ACT_GOPOS && (chr->act_gopos.flags & GOPOSFLAG_DUCK)) { chr->height = 135.0f; } else if (chr->actiontype == ACT_GOPOS && (chr->act_gopos.flags & GOPOSFLAG_CROUCH)) { chr->height = 90.0f; } else if (is_cyl_touching_tile_with_flags(&chr->prop->pos, chr->radius * 1.1f, chr->manground + 185.0f - chr->prop->pos.y, chr->manground - 10.0f - chr->prop->pos.y, chr->prop->rooms, GEOFLAG_AIBOTDUCK)) { chr->height = 135.0f; } else if (is_cyl_touching_tile_with_flags(&chr->prop->pos, chr->radius * 1.1f, chr->manground + 135.0f - chr->prop->pos.y, chr->manground - 10.0f - chr->prop->pos.y, chr->prop->rooms, GEOFLAG_AIBOTCROUCH)) { chr->height = 90.0f; } bmove_dampen_shotspeed(&chr->aibot->shotspeed); arg2->x += chr->aibot->shotspeed.x * g_HeadAnims[HEADANIM_MOVING].translateperframe * VAR(lvupdate60freal) * 0.5f; arg2->z += chr->aibot->shotspeed.z * g_HeadAnims[HEADANIM_MOVING].translateperframe * VAR(lvupdate60freal) * 0.5f; } if (chr->actiontype == ACT_DIE && chr->act_die.timeextra > 0.0f) { f32 speed = model->anim->playspeed * VAR(lvupdate60f) * (chr->act_die.timeextra - chr->act_die.elapseextra) / chr->act_die.timeextra; arg2->x += chr->act_die.extraspeed.x * speed; arg2->z += chr->act_die.extraspeed.z * speed; yincrement += chr->act_die.extraspeed.y * speed; chr->act_die.elapseextra += VAR(lvupdate60f) * model->anim->playspeed; if (chr->act_die.elapseextra > chr->act_die.timeextra) { chr->act_die.timeextra = 0.0f; } } else if (chr->timeextra > 0.0f) { f32 speed = model->anim->playspeed * VAR(lvupdate60f) * (chr->timeextra - chr->elapseextra) / chr->timeextra; arg2->x += chr->extraspeed.x * speed; arg2->z += chr->extraspeed.z * speed; yincrement += chr->extraspeed.y * speed; chr->elapseextra += VAR(lvupdate60f) * model->anim->playspeed; if (chr->elapseextra > chr->timeextra) { chr->timeextra = 0.0f; } } if (chr->pushspeed[0] != 0.0f || chr->pushspeed[1] != 0.0f) { arg2->x += chr->pushspeed[0] * VAR(lvupdate60freal); arg2->z += chr->pushspeed[1] * VAR(lvupdate60freal); chr->pushspeed[0] *= 0.9f; chr->pushspeed[1] *= 0.9f; if (chr->pushspeed[0] != 0.0f || chr->pushspeed[1] != 0.0f) { f32 pushdist = sqrtf(chr->pushspeed[0] * chr->pushspeed[0] + chr->pushspeed[1] * chr->pushspeed[1]); if (pushdist > 0.0f) { pushdist = 0.1f * VAR(lvupdate60freal) / pushdist; if (pushdist >= 1.0f) { chr->pushspeed[0] = 0.0f; chr->pushspeed[1] = 0.0f; } else { chr->pushspeed[0] -= chr->pushspeed[0] * pushdist; chr->pushspeed[1] -= chr->pushspeed[1] * pushdist; } } else { chr->pushspeed[0] = 0.0f; chr->pushspeed[1] = 0.0f; } } } arg2->x += chr->fallspeed.x * VAR(lvupdate60freal); arg2->z += chr->fallspeed.z * VAR(lvupdate60freal); if (race == RACE_EYESPY) { struct eyespy *eyespy = chr_to_eyespy(chr); if (eyespy && eyespy->deployed) { arg2->x = chr->prop->pos.x; arg2->y = chr->prop->pos.y; arg2->z = chr->prop->pos.z; } } else if (chr->actiontype == ACT_SKJUMP && chr->act_skjump.state == SKJUMPSTATE_AIRBORNE && !chr->act_skjump.needsnewanim && g_Vars.lvupdate60 != 0) { arg2->x = chr->act_skjump.pos.x; yincrement = chr->act_skjump.pos.y; arg2->z = chr->act_skjump.pos.z; } else if (chr->onladder) { f32 dist; f32 xdiff = arg2->x - arg1->x; f32 zdiff = arg2->z - arg1->z; arg2->x = arg1->x; arg2->z = arg1->z; dist = sqrtf(xdiff * xdiff + zdiff * zdiff); #if VERSION >= VERSION_NTSC_1_0 if (dist > 100.0f) { dist = 100.0f; } #endif yincrement += dist; chr->floortype = FLOORTYPE_METAL; } if (prop->type == PROPTYPE_PLAYER && chr->actiontype == ACT_BONDMULTI) { arg2->x = prop->pos.x; arg2->z = prop->pos.z; rooms_copy(prop->rooms, spfc); chr->invalidmove = 0; chr->lastmoveok60 = g_Vars.lvframe60; } else { if (chr->chrflags & CHRCFLAG_HAS_SPECIAL_DEATH_ANIMATION) { arg2->x = arg1->x; arg2->z = arg1->z; } chr_calculate_push_pos(chr, arg2, spfc, true); } if (chr->actiontype == ACT_SKJUMP && chr->act_skjump.state == SKJUMPSTATE_AIRBORNE && !chr->act_skjump.needsnewanim && g_Vars.lvupdate60 != 0) { #if VERSION >= VERSION_NTSC_1_0 if (chr_ascend(chr, arg2, spfc, yincrement, true)) { chr->manground += yincrement; } #else if (chr_ascend(chr, arg2, spfc, yincrement)) { chr->manground += yincrement; arg2->y += yincrement; } #endif chr->sumground = chr->manground * (PAL ? 8.4175090789795f : 9.999998f); chr->ground = chr->manground; arg2->y -= chr->manground; } else { struct coord *sp98; RoomNum *sp94; struct coord sp88; RoomNum sp78[8]; f32 ground; struct modelnode *node; u16 nodetype; f32 fallspeed; u8 die; if (chr->onladder) { #if VERSION >= VERSION_NTSC_1_0 if (chr_ascend(chr, arg2, spfc, yincrement, true)) { chr->manground += yincrement; } #else if (chr_ascend(chr, arg2, spfc, yincrement)) { chr->manground += yincrement; arg2->y += yincrement; } #endif chr->sumground = chr->manground * (PAL ? 8.4175090789795f : 9.999998f); chr->ground = chr->manground; arg2->y -= chr->manground; } else { if (race == RACE_EYESPY) { ground = chr->manground; } else if (prop->type == PROPTYPE_PLAYER) { struct player *player = g_Vars.players[playermgr_get_player_num_by_prop(prop)]; ground = player->vv_manground; chr->floorcol = player->floorcol; chr->floortype = player->floortype; } else { if (arg2->y - manground < 69.0f) { sp98 = &sp88; sp94 = sp78; sp88.x = arg2->x; sp88.y = manground + 69.0f; sp88.z = arg2->z; los_find_final_room_exhaustive(arg2, spfc, &sp88, sp78); chr_find_entered_rooms_at_pos(chr, &sp88, sp78); } else { sp98 = arg2; sp94 = spfc; } ground = cd_find_ground_at_cyl_ctfril(sp98, chr->radius, sp94, &chr->floorcol, &chr->floortype, &floorflags, &chr->floorroom, &inlift, &lift); #if VERSION >= VERSION_NTSC_1_0 if (chr->aibot && chr->aibot->forceslowupdates == 0 && ground < -100000 && g_Vars.lvupdate60 >= 5 && (chr->prop->flags & PROPFLAG_ONANYSCREENPREVTICK) == 0) { // The new position has no ground and is offscreen, // So they're about to fall out of the geometry. // Run the previous calculations but using their current // position instead. This holds them in place. chr->aibot->forceslowupdates = 10; arg2->x = prop->pos.x; arg2->y = prop->pos.y; arg2->z = prop->pos.z; rooms_copy(prop->rooms, spfc); lvupdate60freal = 0.0f; ground = cd_find_ground_at_cyl_ctfril(arg2, chr->radius, spfc, &chr->floorcol, &chr->floortype, &floorflags, &chr->floorroom, &inlift, &lift); } #endif if (inlift) { chr->inlift = true; chr->lift = lift; } else { chr->inlift = false; chr->lift = NULL; } if (ground < -100000) { ground = -100000; } } chr->ground = ground; if (chr->chrflags & CHRCFLAG_FORCETOGROUND) { node = model->definition->rootnode; nodetype = (u8)node->type; arg2->y += yincrement + chr->ground - manground; chr->chrflags &= ~CHRCFLAG_FORCETOGROUND; chr->manground = chr->ground; chr->sumground = chr->ground * (PAL ? 8.4175090789795f : 9.999998f); manground = chr->manground; if (nodetype == MODELNODETYPE_CHRINFO) { union modelrwdata *rwdata = model_get_node_rw_data(model, node); rwdata->chrinfo.unk34.y = rwdata->chrinfo.unk24.y; } } else { if (chr->fallspeed.y != 0.0f || chr->ground < chr->manground) { die = false; if (prop->type == PROPTYPE_CHR && chr->manground <= -30000) { die = true; } fallspeed = chr->fallspeed.y; projectile_update_fall(&yincrement, &fallspeed, VAR(lvupdate60freal)); #if VERSION >= VERSION_NTSC_1_0 if (chr_ascend(chr, arg2, spfc, yincrement, false)) #else if (chr_ascend(chr, arg2, spfc, yincrement)) #endif { chr->manground += yincrement; chr->fallspeed.y = fallspeed; } if (chr->manground <= chr->ground) { chr->manground = chr->ground; chr->sumground = chr->ground * (PAL ? 8.4175090789795f : 9.999998f); chr->fallspeed.y = 0.0f; if (floorflags & GEOFLAG_DIE) { die = true; } } if (die) { if (chr->aibot) { s32 shooter; if (chr->lastshooter >= 0 && chr->timeshooter > 0) { shooter = chr->lastshooter; } else { shooter = mp_chr_to_chrindex(chr); } chr_die(chr, shooter); } else { chr->hidden |= CHRHFLAG_DELETING; } } } else if (chr->manground <= chr->ground) { for (i = 0; i < g_Vars.lvupdate60; i++) { chr->sumground = chr->sumground * (PAL ? 0.88120001554489f : 0.9f) + chr->ground; chr->fallspeed.x *= (PAL ? 0.88120001554489f : 0.9f); chr->fallspeed.z *= (PAL ? 0.88120001554489f : 0.9f); } chr->manground = chr->sumground * (PAL ? 0.11879998445511f : 0.10000002384186f); if (chr->manground < chr->ground - 30.0f) { chr->manground = chr->ground - 30.0f; chr->sumground = (chr->ground - 30.0f) * (PAL ? 8.4175090789795f : 9.999998f); } if (chr->fallspeed.x < 0.1f && chr->fallspeed.x > -0.1f) { if (chr->fallspeed.z < 0.1f && chr->fallspeed.z > -0.1f) { chr->fallspeed.z = 0.0f; chr->fallspeed.x = 0.0f; } } } #if VERSION >= VERSION_NTSC_1_0 if (manground != chr->manground) { spd0.x = arg2->x; spd0.y = arg2->y; spd0.z = arg2->z; rooms_copy(spfc, spc0); arg2->y += chr->manground - manground; los_find_final_room_exhaustive(&spd0, spc0, arg2, spfc); chr_find_entered_rooms_at_pos(chr, arg2, spfc); } #endif } #if VERSION < VERSION_NTSC_1_0 arg2->y += chr->manground - manground; #endif arg2->y -= chr->manground; } } } *mangroundptr = chr->manground; prop->pos.x = arg2->x; prop->pos.y = arg2->y + chr->manground; prop->pos.z = arg2->z; if (chr->actiontype == ACT_SKJUMP) { #if VERSION >= VERSION_NTSC_1_0 f32 ground; #endif ground = chr->act_skjump.ground; if (prop->pos.y < ground) { prop->pos.y = ground; chr->manground = chr->act_skjump.ground; *mangroundptr = chr->act_skjump.ground; } } prop_deregister_rooms(prop); rooms_copy(spfc, prop->rooms); #if VERSION >= VERSION_NTSC_1_0 if (prop->type == PROPTYPE_CHR) { for (i = 0; prop->rooms[i] != -1; i++) { if (chr->floorroom == prop->rooms[i]) { prop_deregister_rooms(prop); prop->rooms[0] = chr->floorroom; prop->rooms[1] = -1; break; } } } #endif chr_detect_rooms(chr); prop_calculate_shade_colour(prop, chr->nextcol, chr->floorcol); return true; } s32 chrs_get_num_free(void) { s32 count = 0; s32 i; for (i = 0; i < g_NumChrSlots; i++) { if (g_ChrSlots[i].chrnum < 0) { count++; } } return count; } void chr_set_max_damage(struct chrdata *chr, f32 maxdamage) { chr->maxdamage = maxdamage; } f32 chr_get_max_damage(struct chrdata *chr) { return chr->maxdamage; } void chr_add_health(struct chrdata *chr, f32 health) { chr->damage -= health; } f32 chr_get_armor(struct chrdata *chr) { if (chr->damage < 0) { return -chr->damage; } return 0; } s16 chrs_get_next_unused_chrnum(void) { s32 chrnum; struct chrdata *chr; do { chrnum = ++g_NextChrnum; if (chrnum > 32767) { chrnum = g_NextChrnum = 5000; } chr = chr_find_by_literal_id((s16)chrnum); } while (chr); return chrnum; } /** * Allocate a chr from the pool, set default values, register a chrnum * and assign it to the prop. */ void chr_allocate(struct prop *prop, u8 *ailist) { s32 i; struct chrdata *chr = NULL; for (i = 0; i < g_NumChrSlots; i++) { if (g_ChrSlots[i].chrnum < 0) { chr = &g_ChrSlots[i]; break; } } prop->chr = chr; chr->chrnum = chrs_get_next_unused_chrnum(); chr_register(chr->chrnum, i); chr->headnum = 0; chr->bodynum = 0; chr->prop = prop; chr->model = NULL; chr->numarghs = 0; chr->lastwalk60 = 0; chr->invalidmove = 0; chr->lastmoveok60 = g_Vars.lvframe60; chr->visionrange = 250; if (cheat_is_active(CHEAT_PERFECTDARKNESS)) { chr->visionrange = 4; } chr->shotbondsum = 0; chr->damage = 0; chr->sumground = 0; chr->manground = 0; chr->ground = 0; chr->fallspeed.x = 0; chr->fallspeed.y = 0; chr->fallspeed.z = 0; chr->prevpos.x = 0; chr->prevpos.y = 0; chr->prevpos.z = 0; chr->hearingscale = 1; chr->maxdamage = 4; chr->lastseetarget60 = 0; chr->lastvisibletarget60 = 0; chr->lastheartarget60 = 0; chr->numclosearghs = 0; chr->shadecol[0] = chr->nextcol[0] = 0xff; chr->shadecol[1] = chr->nextcol[1] = 0xff; chr->shadecol[2] = chr->nextcol[2] = 0xff; chr->shadecol[3] = chr->nextcol[3] = 0xff; chr->floorcol = 0x0fff; chr->floortype = 0; chr->floorroom = -1; chr->fadealpha = 0xff; chr->chrflags = CHRCFLAG_FORCETOGROUND; chr->hidden = 0; chr->hidden2 = 0; chr->actiontype = ACT_INIT; chr->sleep = 0; chr->ailist = ailist; chr->aioffset = 0; chr->aireturnlist = -1; chr->aishotlist = -1; chr->aipunchdodgelist = -1; chr->aishootingatmelist = -1; chr->aidarkroomlist = -1; chr->aiplayerdeadlist = -1; chr->radius = 20; chr->height = 185; chr->morale = 0; chr->alertness = 0; chr->flags = 0; chr->random = 0; chr->timer60 = 0; chr->soundtimer = 0; chr->soundgap = 0; chr->talkgap = 0; chr->padpreset1 = -1; chr->proppreset1 = -1; chr->chrseeshot = -1; chr->chrseedie = -1; chr->chrpreset1 = -1; chr->chrdup = -1; chr->firecount[0] = 0; chr->firecount[1] = 0; chr->darkroomthing = 0; chr->playerdeadthing = 0; chr->unk32c_12 = 0; chr->grenadeprob = 0; chr->accuracyrating = 0; chr->speedrating = 0; chr->arghrating = 0; chr->dodgerating = 0; chr->unarmeddodgerating = 0; chr->maxdodgerating = 0; chr->flinchcnt = -1; chr->aimendcount = 0; chr->weapons_held[0] = NULL; chr->weapons_held[1] = NULL; chr->weapons_held[2] = NULL; chr->gunprop = NULL; chr->fireslots[0] = -1; chr->fireslots[1] = -1; chr->aimuplshoulder = 0; chr->aimuprshoulder = 0; chr->aimupback = 0; chr->aimsideback = 0; chr->aimendlshoulder = 0; chr->aimendrshoulder = 0; chr->aimendback = 0; chr->aimendsideback = 0; if (g_Vars.currentplayer->prop == NULL) { chr->target = -2; } else { chr->target = g_Vars.currentplayer->prop - g_Vars.props; } chr->path = -1; chr->team = TEAM_01; chr_set_shield(chr, 0); chr->cmnum = 0; chr->cmnum2 = 0; chr->cmnum3 = 0; chr->cmnum4 = 0; chr->cmcount = random() % 300; chr->footstep = 0; chr->magicanim = -1; chr->cover = -1; chr->bdstart = 0; chr->oldframe = 0; chr->magicframe = 0; chr->magicspeed = VERSION >= VERSION_PAL_BETA ? 1 : 0.25; i = 0; while (i != 60) { chr->bdlist[i++] = 0; } chr->talktimer = TICKS(3600); chr->cloakfadefrac = 0; chr->cloakfadefinished = false; chr->inlift = false; chr->targetlastseenp.x = 0; chr->targetlastseenp.y = 0; chr->targetlastseenp.z = 0; chr->myaction = MA_NONE; chr->orders = MA_NONE; chr->squadron = 0; chr->listening = 0; chr->convtalk = 0; chr->question = 0; chr->runfrompos.x = 0; chr->runfrompos.y = 0; chr->runfrompos.z = 0; chr->oldrooms[0] = -1; chr->aibot = NULL; chr->blurdrugamount = 0; chr->drugheadsway = 0; chr->drugheadcount = 0; chr->blurnumtimesdied = 0; chr->cloakpause = 0; chr->timeextra = 0; chr->elapseextra = 0; chr->extraspeed.x = 0; chr->extraspeed.y = 0; chr->extraspeed.z = 0; chr->hitpart = 0; chr->voicebox = 0; chr->pushspeed[0] = 0; chr->pushspeed[1] = 0; chr->gunroty[0] = 0; chr->gunrotx[0] = 0; chr->gunroty[1] = 0; chr->gunrotx[1] = 0; chr->unk348[0] = 0; chr->unk348[1] = 0; chr->onladder = 0; chr->laddernormal.x = 0; chr->laddernormal.y = 0; chr->laddernormal.z = 0; chr->liftaction = 0; chr->lift = NULL; chr->pouncebits = 0; chr->specialdie = 0; chr->roomtosearch = 0; chr->propsoundcount = 0; chr->patrolnextstep = -1; chr->p1p2 = g_Vars.bondplayernum; chr->lastattacker = NULL; chr->race = RACE_HUMAN; chr->aimtesttimer60 = random() % TICKS(30); chr->lastfootsample = 0; chr->poisoncounter = 0; chr->poisonprop = NULL; chr->lastshooter = -1; chr->timeshooter = 0; chr->noblood = false; chr->rtracked = false; #if VERSION >= VERSION_NTSC_1_0 chr->goposhitcount = 0; #endif splat_reset_chr(chr); } /** * Place the chr in the stage at the given position. * Allocate a chr struct if the prop doesn't have one yet. */ struct prop *chr_place(struct prop *prop, struct model *model, struct coord *pos, RoomNum *rooms, f32 faceangle, u8 *ailist) { struct chrdata *chr; struct coord testpos; f32 ground; u32 nodetype; prop->type = PROPTYPE_CHR; if (prop->chr == NULL) { chr_allocate(prop, ailist); } chr = prop->chr; model_set_anim70(model, chr_update_position); model->chr = chr; model->unk01 = 1; chr->model = model; chr_set_theta(chr, faceangle); model_set_anim_play_speed(model, PALUPF(g_ChrsAnimSpeed), 0); testpos.x = pos->x; testpos.y = pos->y + 100; testpos.z = pos->z; chr->ground = chr->manground = ground = cd_find_ground_at_cyl_ctfril(&testpos, chr->radius, rooms, &chr->floorcol, &chr->floortype, NULL, &chr->floorroom, NULL, NULL); chr->sumground = ground * (PAL ? 8.4175090789795f : 9.999998f); prop->pos.x = testpos.x; prop->pos.y = ground + 100; prop->pos.z = testpos.z; prop_deregister_rooms(prop); rooms_copy(rooms, prop->rooms); chr_detect_rooms(chr); model_set_root_position(model, &prop->pos); nodetype = chr->model->definition->rootnode->type; if ((nodetype & 0xff) == MODELNODETYPE_CHRINFO) { union modelrwdata *rwdata = model_get_node_rw_data(chr->model, chr->model->definition->rootnode); rwdata->chrinfo.ground = ground; } chr->prevpos.x = prop->pos.x; chr->prevpos.y = prop->pos.y; chr->prevpos.z = prop->pos.z; prop_calculate_shade_colour(prop, chr->nextcol, chr->floorcol); return prop; } struct prop *chr_create_with_model(struct model *model, struct coord *pos, RoomNum *rooms, f32 faceangle, u8 *ailist) { struct prop *prop = prop_allocate(); if (prop) { prop = chr_place(prop, model, pos, rooms, faceangle, ailist); if (cheat_is_active(CHEAT_ENEMYSHIELDS)) { chr_set_shield(prop->chr, 8); } } return prop; } /** * Removes a chr. If free is true, free the chr. */ void chr_remove(struct prop *prop, bool free) { struct chrdata *chr = prop->chr; struct model *model = chr->model; struct defaultobj *eyespyobj = NULL; struct prop *child; u32 stack[2]; bgun_free_fireslot_wrapper(chr->fireslots[0]); bgun_free_fireslot_wrapper(chr->fireslots[1]); if (chr->proppreset1 >= 0) { struct prop *proppreset = &g_Vars.props[chr->proppreset1]; struct defaultobj *chair = proppreset->obj; chair->hidden &= ~OBJHFLAG_OCCUPIEDCHAIR; } wallhit_fade_splats_for_removed_chr(prop); ps_stop_sound(prop, PSTYPE_GENERAL, 0xffff); shieldhits_remove_by_prop(prop); model_free_vtxstores(VTXSTORETYPE_CHRVTX, model); prop_deregister_rooms(prop); if (g_Vars.stagenum == STAGE_CITRAINING) { eyespyobj = obj_find_by_tag_id(0x26); } child = prop->child; while (child) { struct defaultobj *obj = child->obj; struct prop *next = child->next; if ((obj->hidden & OBJHFLAG_HASTEXTOVERRIDE) == 0 && obj != eyespyobj && (prop->type != PROPTYPE_PLAYER || (obj->flags3 & OBJFLAG3_PLAYERUNDROPPABLE) == 0)) { obj_detach(child); obj_free_permanently(obj, true); } child = next; } modelmgr_free_model(model); chr->model = NULL; if (free) { chr_deregister(chr->chrnum); if (chr->cover != -1) { cover_set_in_use(chr->cover, false); chr->cover = -1; } chr_clear_references(prop - g_Vars.props); projectiles_unref_owner(prop); if (g_Vars.normmplayerisrunning == false && g_MissionConfig.iscoop) { s32 i; for (i = 0; i < g_Vars.numaibuddies && i < ARRAYCOUNT(g_Vars.aibuddies); i++) { if (g_Vars.aibuddies[i] == prop) { g_Vars.aibuddies[i] = NULL; } } } chr->chrnum = -1; rebuild_teams(); rebuild_squadrons(); } } void chr_clear_references(s32 propnum) { s32 i; s32 j; struct prop *prop = &g_Vars.props[propnum]; for (i = 0; i < g_NumChrSlots; i++) { if (g_ChrSlots[i].target == propnum) { if (prop_get_index_by_chr_id(&g_ChrSlots[i], g_ChrSlots[i].chrpreset1) == propnum) { g_ChrSlots[i].chrpreset1 = -1; } g_ChrSlots[i].target = -1; } } for (i = 0; i < PLAYERCOUNT(); i++) { if (g_Vars.players[i]->lookingatprop.prop == prop) { g_Vars.players[i]->lookingatprop.prop = NULL; } for (j = 0; j != 4; j++) { if (g_Vars.players[i]->trackedprops[j].prop == prop) { g_Vars.players[i]->trackedprops[j].prop = NULL; } } } } /** * For the fast animation cheat from GE. * * The cheat doesn't exist in PD, but the functions are still here. * These functions are not called. */ void chrs_set_anim_speed(f32 speed) { s32 i; g_ChrsAnimSpeed = speed; for (i = 0; i < g_NumChrSlots; i++) { if (g_ChrSlots[i].model) { model_set_anim_play_speed(g_ChrSlots[i].model, PALUPF(g_ChrsAnimSpeed), 600); } } } f32 chrs_get_anim_speed(void) { return g_ChrsAnimSpeed; } /** * Tween the chr's aim properties towards their aimend properties. */ void chr_tween_aim(struct chrdata *chr) { if (chr->aimendcount >= 2) { f32 mult = g_Vars.lvupdate60f / chr->aimendcount; if (mult > 1) { mult = 1; } chr->aimuplshoulder += (chr->aimendlshoulder - chr->aimuplshoulder) * mult; chr->aimuprshoulder += (chr->aimendrshoulder - chr->aimuprshoulder) * mult; chr->aimupback += (chr->aimendback - chr->aimupback) * mult; chr->aimsideback += (chr->aimendsideback - chr->aimsideback) * mult; chr->aimendcount -= g_Vars.lvupdate60; } else { chr->aimuplshoulder = chr->aimendlshoulder; chr->aimuprshoulder = chr->aimendrshoulder; chr->aimupback = chr->aimendback; chr->aimsideback = chr->aimendsideback; } } void chr_flinch_body(struct chrdata *chr) { if (chr->actiontype != ACT_DEAD && chr->flinchcnt < 0) { chr->flinchcnt = 1; chr->hidden2 &= 0x0fff; chr->hidden2 |= (u16)(random() << 13); } } void chr_flinch_head(struct chrdata *chr, f32 arg1) { s32 value; if (chr->flinchcnt < 0) { chr->flinchcnt = 1; } else if (chr->flinchcnt > 8) { chr->flinchcnt = 4; } chr->hidden2 &= 0x0fff; chr->hidden2 |= CHRH2FLAG_HEADSHOTTED; value = (arg1 + BADDTOR(22.5f)) * 8.0f / BADDTOR(360); if (value < 0) { value = 0; } if (value > 7) { value = 7; } chr->hidden2 |= value << 13; } f32 chr_get_flinch_amount(struct chrdata *chr) { f32 value = chr->flinchcnt; if (chr->hidden2 & CHRH2FLAG_HEADSHOTTED) { if (value < 4) { value = sinf(value * BADDTOR(90) / 4); } else { value = 1 - sinf((value - 4) * (PAL ? RAD(4, 0.07478791475296f) : RAD(3, 0.060405626893044f))); } } else { if (value < TICKS(10)) { value = sinf(value * BADDTOR(90) / TICKS(10)); } else { value = 1 - sinf((value - TICKS(10)) * (PAL ? RAD(6, 0.098159141838551f) : RAD(4, 0.078527316451073f))); } } return value; } /** * This is a callback function that is called by model code after the model's * animation has done its positioning. It allows tweaks to be made to the model * at particular joints such as changing the angle and scale. * * The function is called multiple times per model per frame, with the joint * argument changing each time. * * This function implements the following features: * - Chicago robot gun angles * - DK mode * - Head flinching when shot * - Body flinching when shot * - Chrs aiming up, down, left and right */ void chr_handle_joint_positioned(s32 joint, Mtxf *mtx) { f32 scale = 1.0f; s32 lshoulderjoint; s32 rshoulderjoint; s32 waistjoint; s32 neckjoint; struct coord sp138; Mtxf spf8; Mtxf spb8; f32 gunroty; f32 gunrotx; f32 theta; f32 gunrot; // chr is facing into the Z axis f32 xrot; // eg. bending over or nodding head f32 yrot; // eg. twist left/right or shaking head f32 zrot; // eg. cartwheeling if (g_CurModelChr->model->definition->skel == &g_SkelRobot) { // Handle Chicago robot guns theta = chr_get_theta(g_CurModelChr); if (joint == 1) { gunrotx = g_CurModelChr->gunrotx[0]; gunroty = g_CurModelChr->gunroty[0]; } else if (joint == 2) { gunrotx = g_CurModelChr->gunrotx[1]; gunroty = g_CurModelChr->gunroty[1]; } else { return; } mtx00015be0(cam_get_projection_mtxf(), mtx); sp138.x = mtx->m[3][0]; sp138.y = mtx->m[3][1]; sp138.z = mtx->m[3][2]; mtx->m[3][0] = 0.0f; mtx->m[3][1] = 0.0f; mtx->m[3][2] = 0.0f; if (gunrotx < 0.0f) { gunrotx += BADDTOR(360); } if (gunroty < 0.0f) { gunroty += BADDTOR(360); } gunrot = BADDTOR(360) - theta + DTOR(90); if (gunrot >= BADDTOR(360)) { gunrot -= BADDTOR(360); } mtx4_load_y_rotation(gunrot, &spb8); mtx00015be0(&spb8, mtx); mtx4_load_x_rotation(gunrotx, &spf8); mtx00015be0(&spf8, mtx); gunrot = gunroty + theta; if (gunrot >= BADDTOR(360)) { gunrot -= BADDTOR(360); } mtx4_load_y_rotation(gunrot, &spb8); mtx00015be0(&spb8, mtx); if (scale != 1.0f) { mtx00015f04(scale, mtx); } mtx->m[3][0] = sp138.x; mtx->m[3][1] = sp138.y; mtx->m[3][2] = sp138.z; mtx00015be0(cam_get_world_to_screen_mtxf(), mtx); } else { if (g_CurModelChr->model->definition->skel == &g_SkelChr) { lshoulderjoint = 2; rshoulderjoint = 3; waistjoint = 1; neckjoint = 0; } else if (g_CurModelChr->model->definition->skel == &g_SkelSkedar) { lshoulderjoint = 3; rshoulderjoint = 4; waistjoint = 2; neckjoint = 1; } else { lshoulderjoint = -1; rshoulderjoint = -1; waistjoint = -1; neckjoint = -1; } if (cheat_is_active(CHEAT_DKMODE) && CHRRACE(g_CurModelChr) == RACE_HUMAN) { if (joint == neckjoint) { scale = 4.0f; } else if (joint == lshoulderjoint || joint == rshoulderjoint) { scale = 2.5f; } } if (joint == lshoulderjoint || joint == rshoulderjoint || joint == waistjoint || joint == neckjoint) { xrot = 0.0f; yrot = 0.0f; zrot = 0.0f; // Apply rotation based on chr's aiming properties if (joint == rshoulderjoint) { xrot = g_CurModelChr->aimuprshoulder; } else if (joint == lshoulderjoint) { xrot = g_CurModelChr->aimuplshoulder; } else if (joint == waistjoint) { // Up/down at the waist xrot = g_CurModelChr->aimupback; if (g_CurModelChr->hidden2 & CHRH2FLAG_AUTOANIM) { if (xrot > BADDTOR(60)) { xrot -= BADDTOR(60); } else if (xrot < BADDTOR(-50)) { xrot += BADDTOR(50); } else { xrot = 0.0f; } } // Left/right at the waist yrot = g_CurModelChr->aimsideback; if (g_CurModelChr->aibot) { yrot += g_CurModelChr->aibot->angleoffset; } else if (g_CurModelChr->prop->type == PROPTYPE_PLAYER) { yrot += g_Vars.players[playermgr_get_player_num_by_prop(g_CurModelChr->prop)]->angleoffset; } } else if (joint == neckjoint) { // Head up/down if (g_CurModelChr->hidden2 & CHRH2FLAG_AUTOANIM) { xrot = g_CurModelChr->aimupback; if (xrot > BADDTOR(60)) { xrot = BADDTOR(60); } else if (xrot < BADDTOR(-50)) { xrot = BADDTOR(-50); } } else if (g_CurModelChr->model->anim->flip) { xrot = g_CurModelChr->aimuplshoulder; } else { xrot = g_CurModelChr->aimuprshoulder; } // Apply head bobbing when dizzy if (g_CurModelChr->blurdrugamount > TICKS(1000) && g_Vars.tickmode != TICKMODE_CUTSCENE && g_CurModelChr->actiontype != ACT_DEAD && g_CurModelChr->actiontype != ACT_DIE) { zrot = BADDTOR4(g_CurModelChr->drugheadsway); xrot -= (28.0f - ABS(g_CurModelChr->drugheadsway)) / 250.0f * BADDTOR(360); } } // Apply flinch when chr is shot if (g_CurModelChr->flinchcnt >= 0 && (CHRRACE(g_CurModelChr) == RACE_HUMAN || CHRRACE(g_CurModelChr) == RACE_SKEDAR)) { bool isskedar = CHRRACE(g_CurModelChr) == RACE_SKEDAR; if (g_CurModelChr->hidden2 & CHRH2FLAG_HEADSHOTTED) { if (joint == neckjoint) { f32 flinchamount = chr_get_flinch_amount(g_CurModelChr); s32 flinchtype = (g_CurModelChr->hidden2 >> 13) & 7; f32 degrees = isskedar ? 25.0f : 60.0f; if ((flinchtype & 1) == 0) { degrees = isskedar ? 37.5f : 85.0f; } if (flinchtype >= 5 && flinchtype < 8) { zrot -= flinchamount * (M_BADTAU * degrees / 360.0f); } else if (flinchtype > 0 && flinchtype < 4) { zrot += flinchamount * (M_BADTAU * degrees / 360.0f); } if (flinchtype == 7 || flinchtype == 0 || flinchtype == 1) { xrot += flinchamount * (M_BADTAU * degrees / 360.0f); } else if (flinchtype >= 3 && flinchtype < 6) { xrot -= flinchamount * (M_BADTAU * degrees / 360.0f); } } } else if (joint == rshoulderjoint || joint == lshoulderjoint) { s32 flinchtype = (g_CurModelChr->hidden2 >> 13) & 7; f32 flinchamount = chr_get_flinch_amount(g_CurModelChr) * BADDTOR(15); xrot -= flinchamount; if (flinchtype < 3) { yrot -= flinchamount; } else if (flinchtype >= 3 && flinchtype < 6) { yrot += flinchamount; } } else if (joint == waistjoint) { f32 flinchamount; s32 flinchtype; flinchamount = chr_get_flinch_amount(g_CurModelChr); flinchtype = (g_CurModelChr->hidden2 >> 13) & 7; xrot += flinchamount * BADDTOR(15); if (flinchtype < 3) { yrot += flinchamount * BADDTOR(15); } else if (flinchtype >= 3 && flinchtype < 6) { yrot -= flinchamount * BADDTOR(15); } if (flinchtype == 2 || flinchtype == 5 || flinchtype == 7) { zrot += flinchamount * BADDTOR(10); } else if (flinchtype == 1 || flinchtype == 4 || flinchtype == 6) { zrot -= flinchamount * BADDTOR(10); } } } if (xrot != 0.0f || yrot != 0.0f || zrot != 0.0f || scale != 1.0f) { struct coord sp70; f32 aimangle; Mtxf tmpmtx; aimangle = chr_get_aimx_angle(g_CurModelChr); if (xrot < 0.0f) { xrot = -xrot; } else { xrot = BADDTOR(360) - xrot; } if (yrot < 0.0f) { yrot += BADDTOR(360); } mtx00015be0(cam_get_projection_mtxf(), mtx); sp70.x = mtx->m[3][0]; sp70.y = mtx->m[3][1]; sp70.z = mtx->m[3][2]; mtx->m[3][0] = 0.0f; mtx->m[3][1] = 0.0f; mtx->m[3][2] = 0.0f; if (xrot != 0.0f || zrot != 0.0f) { yrot -= aimangle; if (yrot < 0.0f) { yrot += BADDTOR(360); } mtx4_load_y_rotation(yrot, &tmpmtx); mtx00015be0(&tmpmtx, mtx); if (xrot != 0.0f) { mtx4_load_x_rotation(xrot, &tmpmtx); mtx00015be0(&tmpmtx, mtx); } if (zrot != 0.0f) { mtx4_load_z_rotation(zrot, &tmpmtx); mtx00015be0(&tmpmtx, mtx); } mtx4_load_y_rotation(aimangle, &tmpmtx); mtx00015be0(&tmpmtx, mtx); } else { mtx4_load_y_rotation(yrot, &tmpmtx); mtx00015be0(&tmpmtx, mtx); } if (scale != 1.0f) { mtx00015f04(scale, mtx); } mtx->m[3][0] = sp70.x; mtx->m[3][1] = sp70.y; mtx->m[3][2] = sp70.z; mtx00015be0(cam_get_world_to_screen_mtxf(), mtx); } } } } void chr_find_entered_rooms_at_pos(struct chrdata *chr, struct coord *pos, RoomNum *rooms) { struct coord lower; struct coord upper; f32 height = 110; if ( #if VERSION >= VERSION_NTSC_1_0 chr && chr->race == RACE_EYESPY #else chr->race == RACE_EYESPY #endif ) { struct eyespy *eyespy = chr_to_eyespy(chr); if (eyespy) { height = eyespy->height + 30.0f; } else { height = 230; } } lower.x = pos->x - 50.0f; lower.y = pos->y - height; lower.z = pos->z - 50.0f; upper.x = pos->x + 50.0f; upper.y = pos->y + height; upper.z = pos->z + 50.0f; bg_find_entered_rooms(&lower, &upper, rooms, 7, true); } void chr_find_entered_rooms(struct chrdata *chr, RoomNum *room) { chr_find_entered_rooms_at_pos(chr, &chr->prop->pos, room); } void chr_detect_rooms(struct chrdata *chr) { prop_deregister_rooms(chr->prop); chr_find_entered_rooms(chr, chr->prop->rooms); prop_register_rooms(chr->prop); } void chr_update_anim(struct chrdata *chr, s32 lvupdate240, bool arg2) { struct model *model = chr->model; if (g_Vars.tickmode == TICKMODE_CUTSCENE) { if (chr->prop->type == PROPTYPE_PLAYER) { chr->hidden &= ~CHRHFLAG_00000800; } if (model->anim && (g_Anims[model->anim->animnum].flags & ANIMFLAG_ABSOLUTETRANSLATION) && lvupdate240 > 0 && g_Vars.cutsceneskip60ths > 0) { lvupdate240 += g_Vars.cutsceneskip60ths * 4; } } if (chr->chrflags & CHRCFLAG_DELAYANIM) { chr->chrflags &= ~CHRCFLAG_DELAYANIM; } else if (arg2) { if ((chr->hidden & CHRHFLAG_00000800) == 0) { model_get_root_position(model, &chr->prevpos); model_tick_anim_quarter_speed(model, lvupdate240, true); model_update_info(model); } } else { model_tick_anim_quarter_speed(model, lvupdate240, false); } } void chr_tick_child(struct chrdata *chr, 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_DELETING) { obj_free(obj, true, obj->hidden2 & OBJH2FLAG_CANREGEN); return; } if (model->attachedtomodel && model->attachedtonode && (obj->hidden & OBJHFLAG_GONE) == 0 && (obj->flags2 & OBJFLAG2_INVISIBLE) == 0) { Mtxf *mtx0 = model_find_node_mtx(model->attachedtomodel, model->attachedtonode, 0); struct modelrenderdata renderdata = { NULL, true, MODELRENDERFLAG_DEFAULT }; u32 stack; Mtxf rendermtx; Mtxf sp40; prop->flags |= PROPFLAG_ONTHISSCREENTHISTICK | PROPFLAG_ONANYSCREENTHISTICK; if (obj->hidden & OBJHFLAG_EMBEDDED) { mtx00015be4(mtx0, &obj->embedment->matrix, &rendermtx); renderdata.rendermtx = &rendermtx; } else if (CHRRACE(chr) == RACE_SKEDAR) { // The skedar hand position is rotated weirdly, so compensate for it mtx4_load_y_rotation(BADDTOR(75.6), &rendermtx); mtx4_load_z_rotation(BADDTOR(90.0), &sp40); mtx4_mult_mtx4_in_place(&sp40, &rendermtx); mtx4_mult_mtx4_in_place(mtx0, &rendermtx); renderdata.rendermtx = &rendermtx; } else if (prop == chr->weapons_held[HAND_LEFT]) { // Flip the model mtx4_load_z_rotation(BADDTOR(180), &rendermtx); mtx4_mult_mtx4_in_place(mtx0, &rendermtx); renderdata.rendermtx = &rendermtx; } else { renderdata.rendermtx = mtx0; } renderdata.matrices = gfx_allocate(model->definition->nummatrices * sizeof(Mtxf)); model_set_matrices(&renderdata, model); obj_child_tick_player(prop, fulltick); child = prop->child; while (child) { if (prop); if (prop); next = child->next; chr_tick_child(chr, child, fulltick); child = next; } } else { prop->flags &= ~PROPFLAG_ONTHISSCREENTHISTICK; obj_child_tick_player(prop, fulltick); child = prop->child; while (child) { next = child->next; obj_child_tick_player_offscreen(child, fulltick); child = next; } } } void chr_cloak(struct chrdata *chr, bool value) { if (!chr_is_dead(chr)) { chr->hidden |= CHRHFLAG_CLOAKED; if (value) { ps_create(0, chr->prop, SFXNUM_005B_CLOAK_ON, -1, -1, 0, 0, PSTYPE_NONE, 0, -1, 0, -1, -1, -1, -1); } } } void chr_uncloak(struct chrdata *chr, bool value) { if (chr->hidden & CHRHFLAG_CLOAKED) { chr->hidden &= ~CHRHFLAG_CLOAKED; if (value) { ps_create(0, chr->prop, SFXNUM_005C_CLOAK_OFF, -1, -1, 0, 0, PSTYPE_NONE, 0, -1, 0, -1, -1, -1, -1); } #if PIRACYCHECKS { u32 checksum = 0; u32 *i = (u32 *)&bot_pickup_prop; u32 *end = (u32 *)&bot_test_prop_for_pickup; while (i < end) { checksum += ~*i; i++; } if (checksum != CHECKSUM_PLACEHOLDER) { ((u32 *)&cd_return_zero)[-2] = 0; } } #endif } } void chr_uncloak_temporarily(struct chrdata *chr) { chr_uncloak(chr, true); chr->cloakpause = TICKS(120); } void chr_update_cloak(struct chrdata *chr) { s32 qty; s32 ammotype; u32 prevplayernum; f32 fVar14; s32 fadefrac; // Decrement cloakpause if (chr->cloakpause > 0) { chr->cloakpause -= g_Vars.lvupdate60; if (chr->cloakpause < 1) { chr->cloakpause = 0; } } // Handle ammo decrease and determine if cloak is still enabled if (chr->aibot) { if (chr->aibot->cloakdeviceenabled) { qty = chr->aibot->ammoheld[AMMOTYPE_CLOAK]; if (qty > 0 && !chr_is_dead(chr)) { if (chr->hidden & CHRHFLAG_CLOAKED) { qty -= g_Vars.lvupdate60; if (qty <= 0) { qty = 0; } chr->aibot->ammoheld[AMMOTYPE_CLOAK] = qty; } } else { chr->aibot->cloakdeviceenabled = false; } } else if (chr->aibot->rcp120cloakenabled) { if (chr->aibot->weaponnum == WEAPON_RCP120 && !chr_is_dead(chr) && botact_get_ammo_quantity_by_weapon(chr->aibot, WEAPON_RCP120, 0, 1) > 0) { if (chr->hidden & CHRHFLAG_CLOAKED) { chr->aibot->rcpcloaktimer60 += LVUPDATE60FREAL() * 0.4f; if (chr->aibot->rcpcloaktimer60 >= 1) { qty = chr->aibot->rcpcloaktimer60; chr->aibot->rcpcloaktimer60 -= qty; if (chr->aibot->loadedammo[0] > 0) { chr->aibot->loadedammo[0] -= qty; if (chr->aibot->loadedammo[0] <= 0) { chr->aibot->loadedammo[0] = 0; } } else { ammotype = botact_get_ammo_type_by_function(WEAPON_RCP120, 0); if (chr->aibot->ammoheld[ammotype] > 0) { chr->aibot->ammoheld[ammotype] -= qty; if (chr->aibot->ammoheld[ammotype] <= 0) { chr->aibot->ammoheld[ammotype] = 0; } } } } } } else { chr->aibot->rcp120cloakenabled = false; } } if (chr->aibot->cloakdeviceenabled || chr->aibot->rcp120cloakenabled) { if ((chr->hidden & CHRHFLAG_CLOAKED) == 0) { if (chr->cloakpause < 1) { chr_cloak(chr, true); } } } else if (chr->hidden & CHRHFLAG_CLOAKED) { chr_uncloak(chr, true); } } else if (chr->prop->type == PROPTYPE_PLAYER) { prevplayernum = g_Vars.currentplayernum; set_current_player_num(playermgr_get_player_num_by_prop(chr->prop)); if (g_Vars.currentplayer->devicesactive & DEVICE_CLOAKDEVICE) { // Cloak is active - but may or may not be in effect due to recent shooting s32 qty = bgun_get_reserved_ammo_count(AMMOTYPE_CLOAK); if (qty > 0) { if (chr->hidden & CHRHFLAG_CLOAKED) { // Cloak is effective qty -= g_Vars.lvupdate60; if (qty < 1) { qty = 0; } bgun_set_ammo_quantity(AMMOTYPE_CLOAK, qty); } } else { // Out of cloak ammo - turn off cloak g_Vars.currentplayer->devicesactive &= ~DEVICE_CLOAKDEVICE; } } // If cloak is enabled via cloaking device or via RCP120 if ((g_Vars.currentplayer->devicesactive & DEVICE_CLOAKDEVICE) || (g_Vars.currentplayer->gunctrl.weaponnum == WEAPON_RCP120 && (g_Vars.currentplayer->devicesactive & DEVICE_CLOAKRCP120))) { if ((chr->hidden & CHRHFLAG_CLOAKED) == 0 && chr->cloakpause < 1) { chr_cloak(chr, true); } } else { if ((g_Vars.currentplayer->devicesactive & DEVICE_CLOAKDEVICE) == false && (chr->hidden & CHRHFLAG_CLOAKED)) { chr_uncloak(chr, true); } } set_current_player_num(prevplayernum); } // Update cloakfade if (chr->hidden & CHRHFLAG_CLOAKED) { if (chr->cloakfadefinished == false) { fadefrac = chr->cloakfadefrac + (g_Vars.lvupdate240 * 5) / 8; if (fadefrac >= 128) { chr->cloakfadefinished = true; chr->cloakfadefrac = 0; } else { chr->cloakfadefrac = fadefrac; } } else { s32 tmp = chr->cloakfadefrac + g_Vars.lvupdate60; chr->cloakfadefrac = tmp % 127; } } else { if (chr->cloakfadefinished == true) { chr->cloakfadefinished = false; fVar14 = 1.0f - cosf((chr->cloakfadefrac / 127.0f + chr->cloakfadefrac / 127.0f) * DTOR(180)); chr->cloakfadefrac = (254 - (s32)(fVar14 * 20.0f * 0.5f)) / 2; } if (chr->cloakfadefrac > 0) { fadefrac = chr->cloakfadefrac - (g_Vars.lvupdate240 * 5) / 8; if (fadefrac < 0) { fadefrac = 0; } chr->cloakfadefrac = fadefrac; } } } s32 chr_get_cloak_alpha(struct chrdata *chr) { s32 alpha = 255; if (chr->cloakfadefrac > 0 || chr->cloakfadefinished == true) { if (!chr->cloakfadefinished) { alpha = 255 - chr->cloakfadefrac * 2; } else { f32 fVar3 = (f32)cosf((chr->cloakfadefrac / 127.0f + chr->cloakfadefrac / 127.0f) * DTOR(180)); alpha = (1.0f - fVar3) * 20.0f * 0.5f; } if (alpha == 0) { alpha = 1; } } return alpha; } void chr_set_poisoned(struct chrdata *chr, struct prop *poisonprop) { if (chr->actiontype != ACT_DEAD && chr->actiontype != ACT_DIE && chr->prop->type == PROPTYPE_PLAYER) { // This was probably used in a debug print playermgr_get_player_num_by_prop(chr->prop); } if (g_Vars.normmplayerisrunning) { chr->poisonprop = poisonprop; chr->poisoncounter += TICKS(3360); } else if (chr->poisoncounter == 0) { chr->poisoncounter = TICKS(1680); chr->poisonprop = poisonprop; } } void chr_tick_poisoned(struct chrdata *chr) { if (chr->poisoncounter > 0) { struct coord coord = {0, 0, 0}; struct gset gset = { WEAPON_COMBATKNIFE, 0, 0, FUNC_POISON }; if (chr->actiontype == ACT_DEAD || chr->actiontype == ACT_DIE) { // Dying chr if (!g_Vars.normmplayerisrunning) { chr->poisoncounter = 0; } else { if (chr->poisoncounter > TICKS(3600)) { chr->poisoncounter = TICKS(3600); } if (g_MpSetup.options & MPOPTION_ONEHITKILLS) { chr->poisoncounter = 0; } } } else if (chr->prop->type == PROPTYPE_PLAYER && g_Vars.players[playermgr_get_player_num_by_prop(chr->prop)]->bondhealth < 0.001f) { // Player who's alive but on almost zero health if (g_Vars.normmplayerisrunning) { if (chr->poisoncounter > TICKS(3600)) { chr->poisoncounter = TICKS(3600); } if (g_MpSetup.options & MPOPTION_ONEHITKILLS) { chr->poisoncounter = 0; } } } else { // Alive chr chr->poisoncounter -= g_Vars.lvupdate240; if (chr->poisoncounter <= 0) { if (!g_Vars.normmplayerisrunning) { chr_damage_by_dizziness(chr, 100, &coord, &gset, chr->poisonprop); chr_flinch_head(chr, DTOR(180)); } chr->poisoncounter = 0; } else if (chr->poisoncounter < TICKS(1680)) { chr->blurdrugamount += g_Vars.lvupdate240 * 10; } if (g_Vars.normmplayerisrunning) { if (chr->poisoncounter / TICKS(720) != (chr->poisoncounter + g_Vars.lvupdate240) / TICKS(720)) { chr_damage_by_dizziness(chr, 1.3f, &coord, &gset, chr->poisonprop); } } } } } f32 var800629e8 = 1; u32 var800629ec = 0x00000000; u32 var800629f0 = 0x00000000; u32 var800629f4 = 0x00000000; u32 var800629f8 = 0x00000000; bool chr_tick_beams(struct prop *prop) { struct chrdata *chr = prop->chr; if (chr->fireslots[0] >= 0) { beam_tick(&g_Fireslots[chr->fireslots[0]].beam); } if (chr->fireslots[1] >= 0) { beam_tick(&g_Fireslots[chr->fireslots[1]].beam); } if (chr->aibot && chr->aibot->fadeintimer60 > 0) { if (chr->aibot->fadeintimer60 > g_Vars.lvupdate60) { chr->aibot->fadeintimer60 -= g_Vars.lvupdate60; } else { chr->aibot->fadeintimer60 = 0; } } return false; } /** * Tick the given chr. * * 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 chr_tick(struct prop *prop) { struct modelrenderdata renderdata = { NULL, true, MODELRENDERFLAG_DEFAULT }; struct chrdata *chr = prop->chr; struct model *model = chr->model; bool needsupdate; bool hatvisible = true; s32 lvupdate240 = g_Vars.lvupdate240; struct prop *child; struct prop *next; bool fulltick = false; s32 race = CHRRACE(chr); s32 sp1e8; Mtxf rendermtx; s32 sp1a4; bool isrepeatframe; bool isrepeatframe2; struct coord sp190; f32 angle; struct player *player; struct coord sp17c; f32 sp178; struct hoverbikeobj *bike; u8 stack[0x28]; if (prop->flags & PROPFLAG_NOTYETTICKED) { fulltick = true; prop->flags &= ~PROPFLAG_NOTYETTICKED; } if (fulltick) { #if VERSION >= VERSION_NTSC_1_0 if (chr->goposhitcount > 0 && (chr->hidden & CHRHFLAG_BLOCKINGDOOR) == 0) { chr->goposhitcount--; } #endif if (g_Vars.in_cutscene) { chr->drugheadcount = 0; chr->drugheadsway = 0; } else if (chr->blurdrugamount > TICKS(1000) && chr->actiontype != ACT_DRUGGEDKO) { chr->drugheadcount += g_Vars.lvupdate240 >> 1; chr->drugheadsway = cosf(chr->drugheadcount / 255.0f * BADDTOR(360)) * 20.0f; } else if (chr->drugheadsway != 0.0f) { chr->drugheadcount = 0; if (chr->drugheadsway > 0.0f) { #if VERSION >= VERSION_PAL_BETA chr->drugheadsway -= 0.175f * g_Vars.lvupdate60freal; #else chr->drugheadsway -= 0.04375f * g_Vars.lvupdate240; #endif if (chr->drugheadsway < 0.0f) { chr->drugheadsway = 0.0f; } } else if (chr->drugheadsway < 0.0f) { #if VERSION >= VERSION_PAL_BETA chr->drugheadsway += 0.175f * g_Vars.lvupdate60freal; #else chr->drugheadsway += 0.04375f * g_Vars.lvupdate240; #endif if (chr->drugheadsway > 0.0f) { chr->drugheadsway = 0.0f; } } } chr_update_cloak(chr); chr_tick_poisoned(chr); if ((chr->chrflags & CHRCFLAG_HIDDEN) == 0 || (chr->chrflags & CHRCFLAG_NEVERSLEEP)) { if (g_ChrsAnimDebugSetAll) { if (anim_has_frames(g_ChrsAnimDebugAnimNum)) { if (model_get_anim_num(model) != g_ChrsAnimDebugAnimNum || !anim_has_frames(model_get_anim_num(model))) { model_set_animation(model, g_ChrsAnimDebugAnimNum, 0, 0.0f, 0.5f, 0.0f); } } } else { chra_tick(chr); if (chr->model == NULL) { return TICKOP_FREE; } } if (g_ChrsAnimDebugPaused) { lvupdate240 = 0; if (g_ChrsAnimDebugSlow) { lvupdate240 = 1; } } } if (chr->hidden & CHRHFLAG_DELETING) { if (chr->hidden & CHRHFLAG_DROPPINGITEM) { obj_drop_recursively(prop, true); } chr_remove(prop, true); return TICKOP_FREE; } } if (race == RACE_EYESPY) { struct eyespy *eyespy = chr_to_eyespy(chr); if (eyespy && eyespy->deployed) { if (eyespy == g_Vars.currentplayer->eyespy && eyespy->active) { needsupdate = false; } else { needsupdate = pos_is_onscreen(prop, &prop->pos, model_get_effective_scale(model), true); } if (fulltick) { chr_update_anim(chr, lvupdate240, true); } } else { needsupdate = false; } } else if (chr->chrflags & CHRCFLAG_HIDDEN) { needsupdate = false; } else if ((chr->chrflags & CHRCFLAG_UNPLAYABLE) || (prop->type == PROPTYPE_PLAYER && g_Vars.currentplayer == (player = g_Vars.players[playermgr_get_player_num_by_prop(prop)]) && player->cameramode == CAMERAMODE_THIRDPERSON && player->visionmode != VISIONMODE_SLAYERROCKET)) { // Cutscene chr? isrepeatframe = false; if (fulltick) { model->anim->average = false; if (chr->actiontype == ACT_ANIM && !chr->act_anim.movewheninvis && chr->act_anim.lockpos) { chr_update_anim(chr, lvupdate240, false); } else { chr_update_anim(chr, lvupdate240, true); } } if (chr->model && chr->model->anim && (g_Anims[chr->model->anim->animnum].flags & ANIMFLAG_HASREPEATFRAMES)) { anim_load_header(chr->model->anim->animnum); isrepeatframe = anim_get_remapped_frame(chr->model->anim->animnum, chr->model->anim->framea) < 0 || (anim_get_remapped_frame(chr->model->anim->animnum, chr->model->anim->frameb) < 0 && chr->model->anim->frac != 0.0f); } if (isrepeatframe) { needsupdate = false; } else { needsupdate = pos_is_in_draw_distance(&prop->pos); } } else if (chr->actiontype == ACT_PATROL || chr->actiontype == ACT_GOPOS) { if ((chr->actiontype == ACT_PATROL && chr->act_patrol.waydata.mode == WAYMODE_MAGIC) || (chr->actiontype == ACT_GOPOS && chr->act_gopos.waydata.mode == WAYMODE_MAGIC)) { needsupdate = pos_is_onscreen(prop, &prop->pos, model_get_effective_scale(model), true); if (needsupdate) { model->anim->average = false; model_get_root_position(model, &chr->prevpos); model_update_info(model); } } else { if (fulltick) { chr_update_anim(chr, lvupdate240, true); } needsupdate = pos_is_onscreen(prop, &prop->pos, model_get_effective_scale(model), true); if (needsupdate) { if (chr->actiontype == ACT_PATROL) { chr->act_patrol.waydata.lastvisible60 = g_Vars.lvframe60; } else if (chr->actiontype == ACT_GOPOS) { chr->act_gopos.waydata.lastvisible60 = g_Vars.lvframe60; } } model->anim->average = !needsupdate && !((prop->flags & (PROPFLAG_ONANYSCREENTHISTICK | PROPFLAG_ONANYSCREENPREVTICK)) != 0); } } else if (chr->actiontype == ACT_ANIM && !chr->act_anim.movewheninvis) { needsupdate = pos_is_onscreen(prop, &prop->pos, model_get_effective_scale(model), true); if (fulltick) { model->anim->average = false; if (needsupdate && !chr->act_anim.lockpos) { chr_update_anim(chr, lvupdate240, true); } else { chr_update_anim(chr, lvupdate240, false); } } } else if (chr->actiontype == ACT_STAND) { model->anim->average = false; if (chr->chrflags & CHRCFLAG_FORCETOGROUND) { chr_update_anim(chr, lvupdate240, true); needsupdate = pos_is_onscreen(prop, &prop->pos, model_get_effective_scale(model), true); } else { needsupdate = pos_is_onscreen(prop, &prop->pos, model_get_effective_scale(model), true); if (g_Vars.mplayerisrunning) { if (fulltick) { if (g_Vars.coopplayernum >= 0 || g_Vars.antiplayernum >= 0) { if (needsupdate) { chr_update_anim(chr, lvupdate240, true); } else if (model->anim->animnum2 != 0) { chr_update_anim(chr, lvupdate240, false); } } else { chr_update_anim(chr, lvupdate240, true); } } } else if (needsupdate) { if (chr->act_stand.playwalkanim == true) { chr_update_anim(chr, lvupdate240, false); } else { chr_update_anim(chr, lvupdate240, true); } } else if (model->anim->animnum2 != 0) { chr_update_anim(chr, lvupdate240, false); } } } else if (chr->actiontype == ACT_DEAD) { needsupdate = pos_is_onscreen(prop, &prop->pos, model_get_effective_scale(model), true); } else if (prop->type == PROPTYPE_PLAYER && (g_Vars.mplayerisrunning || (player = g_Vars.players[playermgr_get_player_num_by_prop(prop)], player->cameramode == CAMERAMODE_EYESPY) || (player->cameramode == CAMERAMODE_THIRDPERSON && player->visionmode == VISIONMODE_SLAYERROCKET))) { model->anim->average = false; chr_update_anim(chr, lvupdate240, true); needsupdate = pos_is_onscreen(prop, &prop->pos, model_get_effective_scale(model), true); } else { isrepeatframe2 = false; if (fulltick) { model->anim->average = false; chr_update_anim(chr, lvupdate240, true); } if (chr->model && chr->model->anim && (g_Anims[chr->model->anim->animnum].flags & ANIMFLAG_HASREPEATFRAMES)) { anim_load_header(chr->model->anim->animnum); isrepeatframe2 = anim_get_remapped_frame(chr->model->anim->animnum, chr->model->anim->framea) < 0 || (anim_get_remapped_frame(chr->model->anim->animnum, chr->model->anim->frameb) < 0 && chr->model->anim->frac != 0.0f); } if (isrepeatframe2) { needsupdate = false; } else { needsupdate = pos_is_onscreen(prop, &prop->pos, model_get_effective_scale(model), true); } } if (fulltick) { if (chr->actiontype != ACT_STAND || model->anim->animnum2 != 0 || prop->type == PROPTYPE_PLAYER) { #if VERSION >= VERSION_NTSC_1_0 chr->hidden2 |= CHRH2FLAG_CONSIDERPROXIES; #else chr->hidden |= CHRHFLAG_CONSIDERPROXIES; #endif } chr_tween_aim(chr); } if (prop->pos.y < -65536) { needsupdate = false; } #if VERSION >= VERSION_NTSC_1_0 if (!g_Vars.normmplayerisrunning && needsupdate) { if (chr->actiontype == ACT_DEAD || (chr->actiontype == ACT_DRUGGEDKO && (chr->chrflags & CHRCFLAG_KEEPCORPSEKO) == 0)) { var8009cdac++; if (var8009cdac > 10) { needsupdate = false; chr_drop_items_for_owner_reap(chr); chr->hidden |= CHRHFLAG_DELETING; } } else { var8009cdb0++; } if (var8009cdb0 + var8009cdac > 30) { needsupdate = false; } } #endif if (needsupdate) { #ifdef DEBUG debug0f1199f0nb(); #endif prop->flags |= PROPFLAG_ONTHISSCREENTHISTICK | PROPFLAG_ONANYSCREENTHISTICK; chr->chrflags |= CHRCFLAG_EVERONSCREEN; if (g_Vars.antiplayernum >= 0 && g_Vars.currentplayer == g_Vars.bond) { chr->hidden |= CHRHFLAG_ONBONDSSCREEN; } if (cheat_is_active(CHEAT_DKMODE)) { model_set_distance_scale(0.3125f); } g_ModelJointPositionedFunc = &chr_handle_joint_positioned; g_CurModelChr = chr; if (CHRRACE(chr) == RACE_DRCAROLL && g_Vars.tickmode != TICKMODE_CUTSCENE) { angle = chr_get_theta(chr); sp190.x = sinf(angle) * 19; sp190.y = 0.0f; sp190.z = cosf(angle) * 19; mtx4_load_translation(&sp190, &rendermtx); mtx4_mult_mtx4_in_place(cam_get_world_to_screen_mtxf(), &rendermtx); renderdata.rendermtx = &rendermtx; } else if (prop->type == PROPTYPE_PLAYER) { u8 stack[0x14]; f32 sp130; player = g_Vars.players[playermgr_get_player_num_by_prop(prop)]; if (player->bondmovemode == MOVEMODE_BIKE) { sp178 = chr_get_theta(chr); bike = (struct hoverbikeobj *)player->hoverbike->obj; sp130 = bike->w * 1000; sp17c.x = cosf(sp178) * sp130; sp17c.y = ABS(bike->w) * 200 + 25; sp17c.z = sinf(-sp178) * sp130; mtx4_load_translation(&sp17c, &rendermtx); mtx4_mult_mtx4_in_place(cam_get_world_to_screen_mtxf(), &rendermtx); renderdata.rendermtx = &rendermtx; } else { renderdata.rendermtx = cam_get_world_to_screen_mtxf(); } } else { renderdata.rendermtx = cam_get_world_to_screen_mtxf(); } renderdata.matrices = gfx_allocate(model->definition->nummatrices * sizeof(Mtxf)); if (fulltick && g_CurModelChr->flinchcnt >= 0) { g_CurModelChr->flinchcnt += g_Vars.lvupdate60; if (g_CurModelChr->flinchcnt >= (PAL ? 24 : 30)) { g_CurModelChr->flinchcnt = -1; } } { f32 limit; struct anim *anim; struct coord *campos = &g_Vars.currentplayer->cam_pos; f32 xdiff; f32 ydiff; f32 zdiff; f32 sp114 = cam_get_lod_scale_z(); bool restore = false; f32 prevfrac; s32 prevframea; f32 prevfrac2; s32 prevframe2a; if (g_Vars.normmplayerisrunning) { if (g_MpSetup.options & (MPOPTION_SLOWMOTION_ON | MPOPTION_SLOWMOTION_SMART)) { limit = 2000 * 2000; } else { limit = 700 * 700; } } else { if (debug_get_slow_motion() != SLOWMOTION_OFF) { limit = 2000 * 2000; } else { limit = 700 * 700; } } anim = model->anim; if (anim && anim->animnum != 0) { xdiff = prop->pos.x - campos->x; ydiff = prop->pos.y - campos->y; zdiff = prop->pos.z - campos->z; if ((xdiff * xdiff + ydiff * ydiff + zdiff * zdiff) * sp114 * sp114 > limit) { prevfrac = anim->frac; prevframea = anim->framea; prevfrac2 = anim->frac2; prevframe2a = anim->frame2a; restore = true; if (anim->frac != 0 && anim->speed * anim->playspeed >= 0.25f) { if (anim->frac > 0.5f) { anim->framea = anim->frameb; } anim->frac = 0; } if (anim->fracmerge != 0 && anim->speed2 * anim->playspeed >= 0.25f && anim->frac2 != 0) { if (anim->frac2 > 0.5f) { anim->frame2a = anim->frame2b; } anim->frac2 = 0; } } } model_set_matrices_with_anim(&renderdata, model); if (restore) { anim->frac = prevfrac; anim->framea = prevframea; anim->frac2 = prevfrac2; anim->frame2a = prevframe2a; } g_ModelJointPositionedFunc = NULL; model_set_distance_scale(var800629e8); if (fulltick) { colour_tween(chr->shadecol, chr->nextcol); } prop->z = model_get_screen_distance(model); child = prop->child; while (child) { next = child->next; chr_tick_child(chr, child, fulltick); child = next; } if (chr->weapons_held[2] != NULL) { s32 index; struct defaultobj *obj = chr->weapons_held[2]->obj; struct model *hatmodel = obj->model; if (chr->headnum >= HEAD_SHAUN && chr->headnum <= HEAD_SHAUN) { struct coord hatpos = {0, 0, 0}; f32 spe4; f32 spe0; f32 spdc; Mtxf sp9c; Mtxf sp5c; s32 hattype = hat_get_type(chr->weapons_held[2]); u8 stack[0x0c]; index = chr->headnum - HEAD_SHAUN; hatpos.x = var8007dae4[index][hattype].x * 21.3f; hatpos.y = var8007dae4[index][hattype].y * 21.3f; hatpos.z = var8007dae4[index][hattype].z * 21.3f; spe4 = var8007dae4[index][hattype].unk0c; spe0 = var8007dae4[index][hattype].unk10; spdc = var8007dae4[index][hattype].unk14; mtx4_load_translation(&hatpos, &sp9c); mtx00015e24(spe4, &sp9c); mtx00015e80(spe0, &sp9c); mtx00015edc(spdc, &sp9c); mtx00015be4(hatmodel->matrices, &sp9c, &sp5c); mtx4_copy(&sp5c, hatmodel->matrices); if (hattype == HATTYPE_2) { hatvisible = false; } } } if (model->definition->skel == &g_SkelChr) { struct modelnode *headspotnode = model_get_part(model->definition, MODELPART_CHR_HEADSPOT); if (headspotnode && headspotnode->type == MODELNODETYPE_HEADSPOT) { union modelrwdata *rwdata = model_get_node_rw_data(model, headspotnode); if (rwdata->headspot.headmodeldef != NULL) { struct modelnode *hatnode = model_get_part(rwdata->headspot.headmodeldef, MODELPART_HEAD_HAT); if (hatnode != NULL) { union modelrwdata *hatrwdata = model_get_node_rw_data(model, hatnode); hatrwdata->toggle.visible = hatvisible; } } } } } } else { // Offscreen prop->flags &= ~PROPFLAG_ONTHISSCREENTHISTICK; if (g_Vars.antiplayernum >= 0 && g_Vars.currentplayer == g_Vars.bond) { chr->hidden &= ~CHRHFLAG_ONBONDSSCREEN; } child = prop->child; while (child) { next = child->next; obj_child_tick_player_offscreen(child, fulltick); child = next; } chr->shadecol[0] = chr->nextcol[0]; chr->shadecol[1] = chr->nextcol[1]; chr->shadecol[2] = chr->nextcol[2]; chr->shadecol[3] = chr->nextcol[3]; } if (fulltick && (chr->chrflags & CHRCFLAG_HIDDEN) == 0) { if (chr->hidden & CHRHFLAG_DROPPINGITEM) { obj_drop_recursively(prop, false); chr->hidden &= ~CHRHFLAG_DROPPINGITEM; } chr_tick_shots(chr); } return TICKOP_NONE; } void chr_drop_concealed_items(struct chrdata *chr) { struct prop *prop = chr->prop->child; while (prop) { if (prop != chr->weapons_held[2] && prop != chr->weapons_held[1] && prop != chr->weapons_held[0] && (prop->obj->hidden & OBJHFLAG_EMBEDDED) == 0 && (prop->obj->flags & OBJFLAG_AIUNDROPPABLE) == 0) { obj_set_dropped(prop, DROPTYPE_DEFAULT); } prop = prop->next; } chr->hidden |= CHRHFLAG_DROPPINGITEM; } void chr_set_hudpiece_visible(struct chrdata *chr, bool visible) { struct modeldef *modeldef = chr->model->definition; if (modeldef->skel == &g_SkelChr) { struct modelnode *headspotnode = model_get_part(modeldef, MODELPART_CHR_HEADSPOT); if (headspotnode && headspotnode->type == MODELNODETYPE_HEADSPOT) { union modelrwdata *rwdata = model_get_node_rw_data(chr->model, headspotnode); if (rwdata->headspot.headmodeldef) { struct modelnode *hudpiecenode = model_get_part(rwdata->headspot.headmodeldef, MODELPART_HEAD_HUDPIECE); if (hudpiecenode) { union modelrwdata *rwdata2 = model_get_node_rw_data(chr->model, hudpiecenode); rwdata2->toggle.visible = visible; } } } } } void chr_drop_items_for_owner_reap(struct chrdata *chr) { struct prop *prop = chr->prop->child; while (prop) { // If prop is not hat if (prop != chr->weapons_held[2]) { struct defaultobj *obj = prop->obj; if ((obj->flags & OBJFLAG_AIUNDROPPABLE) == 0) { obj_set_dropped(prop, DROPTYPE_OWNERREAP); } } prop = prop->next; } chr->hidden |= CHRHFLAG_DROPPINGITEM; } u8 g_ChrBloodColour[] = { 0x40, 0x0a, 0x0a }; void chrs_set_blood_colour(u8 *rgb) { g_ChrBloodColour[0] = rgb[0]; g_ChrBloodColour[1] = rgb[1]; g_ChrBloodColour[2] = rgb[2]; } void chrs_get_blood_colour(u8 *rgb) { rgb[0] = g_ChrBloodColour[0]; rgb[1] = g_ChrBloodColour[1]; rgb[2] = g_ChrBloodColour[2]; } void chrs_reset_onscreen_doors(void) { g_ChrsNumOnscreenDoors = 0; } bool chr_find_onscreen_doors(struct chrdata *chr) { s16 *propnumptr; s16 propnums[256]; s32 i; struct onscreendoor *osd; struct coord *campos; bool result = false; for (i = 0; i < g_ChrsNumOnscreenDoors; i++) { g_ChrsOnscreenDoors[i].unk004 = false; } room_get_props(chr->prop->rooms, propnums, 256); propnumptr = propnums; while (*propnumptr >= 0) { struct prop *prop = &g_Vars.props[*propnumptr]; if (prop->type == PROPTYPE_DOOR) { if (prop->flags & PROPFLAG_ONTHISSCREENTHISTICK) { struct defaultobj *obj = prop->obj; struct doorobj *door = prop->door; struct coord pos; if (obj->model->definition->skel != &g_SkelWindowedDoor && door->doortype != DOORTYPE_EYE && door->doortype != DOORTYPE_IRIS && door->doortype != DOORTYPE_FALLAWAY && door->doortype != DOORTYPE_AZTECCHAIR && (obj->flags & (OBJFLAG_00000010 | OBJFLAG_AISEETHROUGH)) == 0 && (obj->flags2 & OBJFLAG2_DOOR_ALTCOORDSYSTEM) == 0 && !((door->doorflags & DOORFLAG_TRANSLATION) == 0 && door->frac > 0)) { for (i = 0; i < g_ChrsNumOnscreenDoors; i++) { if (g_ChrsOnscreenDoors[i].prop == prop) { break; } } if (i < g_ChrsNumOnscreenDoors) { osd = &g_ChrsOnscreenDoors[i]; } else { if (g_ChrsNumOnscreenDoors > 14) { goto next; } osd = &g_ChrsOnscreenDoors[g_ChrsNumOnscreenDoors]; osd->prop = prop; osd->unk00c = 0; osd->unk130 = 0; osd->unk004 = 0; g_ChrsNumOnscreenDoors++; } if (!osd->unk00c) { struct modelrodata_bbox *bbox = obj_find_bbox_rodata(obj); osd->bbox = *bbox; mtx3_to_mtx4(obj->realrot, &osd->unk02c); mtx4_set_translation(&obj->prop->pos, &osd->unk02c); mtx000172f0(osd->unk02c.m, osd->unk06c.m); campos = &g_Vars.currentplayer->cam_pos; osd->unk12c = ( + osd->unk06c.m[0][2] * campos->f[0] + osd->unk06c.m[1][2] * campos->f[1] + osd->unk06c.m[2][2] * campos->f[2]) + osd->unk06c.m[3][2]; mtx00015be4(&osd->unk06c, cam_get_projection_mtxf(), &osd->unk0ac); osd->unk00c = true; } model_get_root_position(chr->model, &pos); osd->unk008 = ( osd->unk06c.m[0][2] * pos.f[0] + osd->unk06c.m[1][2] * pos.f[1] + osd->unk06c.m[2][2] * pos.f[2]) + osd->unk06c.m[3][2]; if ((osd->unk008 > osd->bbox.zmax && osd->unk12c < osd->bbox.zmin) || (osd->unk008 < osd->bbox.zmin && osd->unk12c > osd->bbox.zmax)) { osd->unk004 = true; result = true; } } } } next: propnumptr++; } return result; } bool chr_should_render_gundl(struct model *model, struct modelnode *node) { struct model *rootmodel; struct modelnode *bboxnode; f32 value; struct doorobj *door; struct modelrodata_bbox *bbox; struct onscreendoor *osd; s32 i; s32 j; bool done; Mtxf spb4; s32 spb0; struct chrdata *chr; struct coord spa0; struct coord sp94; struct coord sp88; f32 sp80[2]; Mtxf *mtx; struct coord sp70; struct coord sp64; rootmodel = model; while (rootmodel->attachedtonode && rootmodel->attachedtomodel) { rootmodel = rootmodel->attachedtomodel; } chr = rootmodel->chr; if (chr) { bboxnode = node; while (bboxnode) { if ((bboxnode->type & 0xff) == MODELNODETYPE_BBOX) { break; } bboxnode = bboxnode->parent; } if (bboxnode) { bbox = &bboxnode->rodata->bbox; mtx = model_find_node_mtx(model, node, 0); for (i = 0; i < g_ChrsNumOnscreenDoors; i++) { if (g_ChrsOnscreenDoors[i].unk004) { osd = &g_ChrsOnscreenDoors[i]; done = false; spb0 = false; door = osd->prop->door; mtx00015be4(&osd->unk0ac, mtx, &spb4); if (osd->unk130 == 0) { if (door->doortype == DOORTYPE_VERTICAL) { spa0.x = osd->bbox.xmin; spa0.y = osd->bbox.ymin; spa0.z = osd->bbox.zmin; sp94.x = osd->bbox.xmax; sp94.y = osd->bbox.ymin; sp94.z = osd->bbox.zmin; sp88.x = osd->bbox.xmax; sp88.y = osd->bbox.ymax; sp88.z = osd->bbox.zmin; } else { spa0.x = osd->bbox.xmax; spa0.y = osd->bbox.ymax; spa0.z = osd->bbox.zmin; sp94.x = osd->bbox.xmax; sp94.y = osd->bbox.ymin; sp94.z = osd->bbox.zmin; sp88.x = osd->bbox.xmin; sp88.y = osd->bbox.ymin; sp88.z = osd->bbox.zmin; } mtx00015be4(cam_get_world_to_screen_mtxf(), &osd->unk02c, &osd->unk0ec); mtx4_transform_vec(&osd->unk0ec, &spa0, &sp70); cam0f0b4dec(&sp70, osd->unk134); mtx4_transform_vec(&osd->unk0ec, &sp94, &sp70); cam0f0b4dec(&sp70, osd->unk13c); mtx4_transform_vec(&osd->unk0ec, &sp88, &sp70); cam0f0b4dec(&sp70, osd->unk144); osd->unk130 = 1; osd->unk14c = osd->unk13c[1] - osd->unk134[1]; osd->unk150 = -(osd->unk13c[0] - osd->unk134[0]); osd->unk154 = osd->unk14c * osd->unk134[0] + osd->unk150 * osd->unk134[1]; osd->unk158 = osd->unk14c * osd->unk144[0] + osd->unk150 * osd->unk144[1] - osd->unk154; } for (j = 0; j < 8; j++) { switch (j) { case 0: sp64.x = bbox->xmin; sp64.y = bbox->ymin; sp64.z = bbox->zmin; break; case 1: sp64.x = bbox->xmin; sp64.y = bbox->ymin; sp64.z = bbox->zmax; break; case 2: sp64.x = bbox->xmin; sp64.y = bbox->ymax; sp64.z = bbox->zmin; break; case 3: sp64.x = bbox->xmin; sp64.y = bbox->ymax; sp64.z = bbox->zmax; break; case 4: sp64.x = bbox->xmax; sp64.y = bbox->ymin; sp64.z = bbox->zmin; break; case 5: sp64.x = bbox->xmax; sp64.y = bbox->ymin; sp64.z = bbox->zmax; break; case 6: sp64.x = bbox->xmax; sp64.y = bbox->ymax; sp64.z = bbox->zmin; break; case 7: sp64.x = bbox->xmax; sp64.y = bbox->ymax; sp64.z = bbox->zmax; break; } mtx4_transform_vec(mtx, &sp64, &sp70); cam0f0b4dec(&sp70, sp80); value = osd->unk14c * sp80[0] + osd->unk150 * sp80[1] - osd->unk154; if ((osd->unk158 >= 0.0f && value < 0.0f) || (osd->unk158 <= 0.0f && value > 0.0f)) { spb0 = true; break; } if (!done) { mtx4_transform_vec(&spb4, &sp64, &sp70); if (sp70.x >= osd->bbox.xmin && sp70.x <= osd->bbox.xmax && sp70.y >= osd->bbox.ymin && sp70.y <= osd->bbox.ymax && ((osd->bbox.zmax < osd->unk008 && sp70.z < osd->bbox.zmax) || (osd->unk008 < osd->bbox.zmin && osd->bbox.zmin < sp70.z))) { done = true; } } } if (done && !spb0) { return false; } } } } } return true; } /** * Render an object that's attached to or held by a chr such as their weapon, * mines that are stuck to them, and knives/bolts which are embedded in them. * * This function is recursive. The chr's gun can have mines placed on it, and * mines can also have further mines placed on them. */ void chr_render_attached_object(struct prop *prop, struct modelrenderdata *renderdata, bool xlupass, struct chrdata *chr) { if (prop->flags & PROPFLAG_ONTHISSCREENTHISTICK) { u32 stack; struct defaultobj *obj = prop->obj; struct model *model = obj->model; struct prop *child; model_render(renderdata, model); // Note: OBJH2FLAG_HASOPA << 1 is OBJH2FLAG_HASXLU // so this is just checking if the appropriate flag is enabled if (obj->hidden2 & (OBJH2FLAG_HASOPA << xlupass)) { renderdata->gdl = wallhit_render_prop_hits(renderdata->gdl, prop, xlupass); } child = prop->child; while (child) { chr_render_attached_object(child, renderdata, xlupass, chr); child = child->next; } if (xlupass) { mtx_f2l_bulk(model->matrices, model->definition->nummatrices); } } } void chr_get_blood_colour(s16 bodynum, u8 *colour1, u32 *colour2) { switch (bodynum) { case BODY_ELVIS1: case BODY_THEKING: case BODY_ELVISWAISTCOAT: if (colour1) { colour1[0] = 0x0a; colour1[1] = 0x40; colour1[2] = 0x0a; } if (colour2) { colour2[0] = 0x103010ff; colour2[1] = 0x104010ff; colour2[2] = 0x005611a0; } return; case BODY_DRCAROLL: case BODY_EYESPY: case BODY_CHICROB: if (colour1) { colour1[0] = 0x0a; colour1[1] = 0x0a; colour1[2] = 0x0a; } if (colour2) { colour2[0] = 0xb0b030a0; colour2[1] = 0xe0e030a0; colour2[2] = 0xe0e050a0; } return; case BODY_MRBLONDE: case BODY_SKEDAR: case BODY_MINISKEDAR: case BODY_SKEDARKING: if (colour1) { colour1[0] = 0x40; colour1[1] = 0x19; colour1[2] = 0x0a; } if (colour2) { colour2[0] = 0x302010ff; colour2[1] = 0x402010ff; colour2[2] = 0x560011a0; } return; } if (colour1) { colour1[0] = 0x40; colour1[1] = 0x0a; colour1[2] = 0x0a; } if (colour2) { colour2[0] = 0x301010ff; colour2[1] = 0x401010ff; colour2[2] = 0x560011a0; } } Gfx *chr_render(struct prop *prop, Gfx *gdl, bool xlupass) { struct chrdata *chr = prop->chr; struct model *model = chr->model; f32 shadecolourfracs[4]; s32 shademode; s32 flags; s32 alpha; struct eyespy *eyespy; struct prop *child; f32 xrayalphafrac; u8 spec[4]; u8 speb = 0; // Don't render the eyespy if we're the one controlling it if (CHRRACE(chr) == RACE_EYESPY) { eyespy = chr_to_eyespy(chr); if (eyespy) { if (!eyespy->deployed) { return gdl; } if (eyespy == g_Vars.currentplayer->eyespy && eyespy->active) { return gdl; } } } if (chr->chrflags & CHRCFLAG_UNPLAYABLE) { alpha = 0xff; } else { alpha = chr->fadealpha; } if (chr->aibot && chr->aibot->fadeintimer60 > 0) { alpha = (f32)alpha * (TICKS(120) - chr->aibot->fadeintimer60) * (1.0f / TICKS(120)); } chr_get_blood_colour(chr->bodynum, spec, NULL); chrs_set_blood_colour(spec); alpha *= obj_calculate_fade_dist_opacity_frac(prop, model_get_effective_scale(model)); if (g_Vars.currentplayer->visionmode == VISIONMODE_XRAY) { f32 fadedist; f32 chrdist = sqrtf(ERASERSQDIST(prop->pos.f)); if (chrdist > g_Vars.currentplayer->eraserpropdist) { return gdl; } alpha = 128; fadedist = g_Vars.currentplayer->eraserpropdist - 150; if (chrdist > fadedist) { alpha = (1.0f - (chrdist - fadedist) / 150.0f) * 128; } xrayalphafrac = chrdist / g_Vars.currentplayer->eraserpropdist; if (xrayalphafrac > 1.0f) { xrayalphafrac = 1.0f; } } if (!USINGDEVICE(DEVICE_IRSCANNER)) { alpha = chr_get_cloak_alpha(chr) * alpha * 0.0039215688593686f; } if (alpha < 0xff) { if (!xlupass) { return gdl; } flags = MODELRENDERFLAG_OPA | MODELRENDERFLAG_XLU; } else { if (!xlupass) { flags = MODELRENDERFLAG_OPA; } else { flags = MODELRENDERFLAG_XLU; } } shademode = env_get_obj_shade_mode(prop, shadecolourfracs); if (chr->unk32c_18) { prop_calculate_shade_colour(chr->prop, chr->nextcol, chr->floorcol); chr->unk32c_18 = false; } if (shademode != SHADEMODE_XLU && alpha > 0) { struct modelrenderdata renderdata = { NULL, true, MODELRENDERFLAG_DEFAULT }; struct screenbox screenbox; s32 colour[4]; // rgba levels, but allowing > 256 temporarily u32 stack; if (xlupass && chr->cloakfadefrac > 0 && !chr->cloakfadefinished) { gdl = chr_render_cloak(gdl, chr->prop, chr->prop); } if (rooms_get_cumulative_screenbox(prop->rooms, &screenbox) > 0 && (chr->chrflags & CHRCFLAG_UNPLAYABLE) == 0) { gdl = bg_scissor_within_viewport(gdl, screenbox.xmin, screenbox.ymin, screenbox.xmax, screenbox.ymax); } else { gdl = bg_scissor_to_viewport(gdl); } renderdata.flags = flags; renderdata.zbufferenabled = true; renderdata.gdl = gdl; // Configure colours for IR scanner or default if (USINGDEVICE(DEVICE_IRSCANNER)) { colour[0] = 0xff; colour[1] = 0; colour[2] = 0; colour[3] = 0x80; } else { colour[0] = chr->shadecol[0]; colour[1] = chr->shadecol[1]; colour[2] = chr->shadecol[2]; colour[3] = chr->shadecol[3]; } if (g_Vars.normmplayerisrunning) { speb = scenario_highlight_prop(prop, colour); } if (!speb) { colour[3] = colour[3] - obj_get_brightness(prop, true); if (colour[3] > 0xff) { colour[3] = 0xff; } if (colour[3] < 0) { colour[3] = 0; } } obj_merge_colour_fracs(colour, shademode, shadecolourfracs); // Configure colours for night vision if in use if (USINGDEVICE(DEVICE_NIGHTVISION)) { colour[0] = g_GogglesChrColourIntensity; colour[1] = g_GogglesChrColourIntensity; colour[2] = g_GogglesChrColourIntensity; colour[3] = g_GogglesChrColourAlpha; } // Configure colours for xray if in use if (g_Vars.currentplayer->visionmode == VISIONMODE_XRAY) { colour[g_Vars.currentplayer->epcol_0] = xrayalphafrac * 255; colour[g_Vars.currentplayer->epcol_1] = (1 - xrayalphafrac) * 255; colour[g_Vars.currentplayer->epcol_2] = 0; colour[3] = 0xff; } renderdata.envcolour = g_ChrBloodColour[0] << 24 | g_ChrBloodColour[1] << 16 | g_ChrBloodColour[2] << 8; renderdata.fogcolour = colour[0] << 24 | colour[1] << 16 | colour[2] << 8 | colour[3]; if (alpha < 0xff) { renderdata.context = MODELRENDERCONTEXT_CHR_XLU; renderdata.envcolour |= (u8)alpha; } else { renderdata.context = MODELRENDERCONTEXT_CHR_OPA; } // Set Skedar eyes open or closed if (model->definition->skel == &g_SkelSkedar) { struct modelnode *node1 = model_get_part(model->definition, MODELPART_SKEDAR_EYESOPEN); struct modelnode *node2 = model_get_part(model->definition, MODELPART_SKEDAR_EYESCLOSED); if (node1 && node2) { union modelrwdata *data1 = model_get_node_rw_data(model, node1); union modelrwdata *data2 = model_get_node_rw_data(model, node2); data2->toggle.visible = chr->actiontype == ACT_DIE || chr->actiontype == ACT_DEAD; data1->toggle.visible = !data2->toggle.visible; } } // Set Maian eyes open or closed if (chr->headnum == HEAD_THEKING || chr->headnum == HEAD_ELVIS || chr->headnum == HEAD_MAIAN_S || chr->headnum == HEAD_ELVIS_GOGS) { if (model->definition->skel == &g_SkelChr) { struct modelnode *headspotnode = model_get_part(model->definition, MODELPART_CHR_HEADSPOT); if (headspotnode && headspotnode->type == MODELNODETYPE_HEADSPOT) { union modelrwdata *headrwdata = model_get_node_rw_data(model, headspotnode); if (headrwdata->headspot.headmodeldef) { struct modelnode *node1 = model_get_part(headrwdata->headspot.headmodeldef, MODELPART_HEAD_EYESOPEN); struct modelnode *node2 = model_get_part(headrwdata->headspot.headmodeldef, MODELPART_HEAD_EYESCLOSED); if (node1 && node2) { union modelrwdata *data1 = model_get_node_rw_data(model, node1); union modelrwdata *data2 = model_get_node_rw_data(model, node2); data2->toggle.visible = chr->actiontype == ACT_DIE || chr->actiontype == ACT_DEAD; data1->toggle.visible = !data2->toggle.visible; } } } } } if (chr->race == RACE_DRCAROLL) { chr_set_drcaroll_images(chr, chr->drcarollimage_left, chr->drcarollimage_right); } g_Vars.currentplayerstats->drawplayercount++; if (chr_find_onscreen_doors(chr)) { g_ModelShouldRenderGunDlCallback = chr_should_render_gundl; } // Render the chr's model model_render(&renderdata, model); // Render attached props (eg. held guns and attached mines/knives/bolts) child = prop->child; while (child) { chr_render_attached_object(child, &renderdata, xlupass, chr); child = child->next; } gdl = renderdata.gdl; g_ModelShouldRenderGunDlCallback = NULL; // Render shadow if (xlupass) { if (!chr->onladder && chr->actiontype != ACT_SKJUMP) { s32 shadowalpha = 0; if (chr->ground == 0) { shadowalpha = 1; } // @bug: The wrong variable is being used in the minimum bounds // check. However, the approach to this is also flawed. The RSP // can crash if drawing outside of its allowed range, which is // actually -32768.99 to 32767.99. And this range is relative // to the camera, not in world coordinates. This bad check // doesn't cause any crashes though, because even if a chr falls // out of the geometry they likely wouldn't be rendered due to // not being in an active room and also being out of the draw // distance. if (shadowalpha > -65536.0f && chr->ground < 65536.0f) { f32 gaptoground = prop->pos.y - chr->ground; f32 radius; if (gaptoground <= 400 && g_Vars.currentplayer->visionmode != VISIONMODE_XRAY) { if (chr->bodynum == BODY_SKEDAR || chr->bodynum == BODY_SKEDARKING) { radius = 80; } else if (chr->bodynum == BODY_EYESPY) { radius = 12; } else { radius = 35; } if (chr->chrflags & CHRCFLAG_NOSHADOW) { shadowalpha = 0; } else if (shademode == SHADEMODE_FRAC) { shadowalpha = (1.0f - shadecolourfracs[3]) * ((alpha * 100) >> 8); } else { shadowalpha = (s32)(alpha * 100) >> 8; } if (gaptoground >= 150.0f) { shadowalpha = shadowalpha * (400 - gaptoground) * 0.004f; } if (cheat_is_active(CHEAT_SMALLCHARACTERS)) { radius *= 0.4f; } gdl = gfx_render_radial_shadow(gdl, prop->pos.x, chr->ground, prop->pos.z, chr_get_theta(chr), radius, 0xffffff00 | shadowalpha); } } } mtx_f2l_bulk(model->matrices, model->definition->nummatrices); if (USINGDEVICE(DEVICE_IRSCANNER)) { gdl = chr_render_shield(gdl, chr, 0x80); } else { gdl = chr_render_shield(gdl, chr, alpha); } } } return gdl; } struct shieldhit *g_ShieldHits = NULL; void chr_emit_sparks(struct chrdata *chr, struct prop *prop, s32 hitpart, struct coord *coord, struct coord *coord2, struct chrdata *chr2) { struct prop *chrprop = chr->prop; s32 race; if (chr_is_using_paintball(chr2)) { sparks_create(chrprop->rooms[0], chrprop, coord, coord2, 0, SPARKTYPE_PAINT); return; } if (chr_get_shield(chr) > 0.0f) { sparks_create(chrprop->rooms[0], chrprop, coord, coord2, 0, SPARKTYPE_DEFAULT); return; } if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_WEAPON || prop->type == PROPTYPE_DOOR || hitpart == HITPART_GUN || hitpart == HITPART_HAT) { sparks_create(chrprop->rooms[0], chrprop, coord, coord2, 0, SPARKTYPE_DEFAULT); return; } race = CHRRACE(chr); if (race == RACE_DRCAROLL || race == RACE_ROBOT || race == RACE_EYESPY) { sparks_create(chrprop->rooms[0], chrprop, coord, coord2, 0, SPARKTYPE_ELECTRICAL); return; } if (chr->noblood) { return; } if ((random() & 4) == 0) { struct coord coord3; coord3.x = coord2->x * 42.0f + coord->x; coord3.y = coord2->y * 42.0f + coord->y; coord3.z = coord2->z * 42.0f + coord->z; sparks_create(chrprop->rooms[0], chrprop, &coord3, coord2, 0, SPARKTYPE_FLESH_LARGE); } #if VERSION < VERSION_JPN_FINAL sparks_create(chrprop->rooms[0], chrprop, coord, coord2, 0, SPARKTYPE_BLOOD); sparks_create(chrprop->rooms[0], chrprop, coord, coord2, 0, SPARKTYPE_FLESH); #endif } void chr_bruise_from_stabby_projectile(struct model *model, s32 hitpart, struct modelnode *node, struct coord *arg3) { struct modelnode *bestnode = NULL; s32 mindist = 0x7fffffff; s32 bestcoords[3]; struct modelnode *curnode; Gfx *gdlptr; Gfx *gdlptr2; Vtx *vertices; struct modelnode *posnode = NULL; struct coord relpos; struct coord spd4; struct coord spc8; s32 spbc[3]; s32 alpha = 20 + (random() % 50); struct modelrodata_dl *rodata; struct modelrwdata_dl *rwdata; s32 spac = 0; s32 op; s32 nodetype; model_node_get_model_relative_position(model, model_node_find_mtx_node(node), &relpos); spc8.f[0] = arg3->x - relpos.x; spc8.f[1] = arg3->y - relpos.y; spc8.f[2] = arg3->z - relpos.z; // This first pass over the node tree is deciding which modelnode to use. curnode = node; while (curnode) { s32 nodetype = curnode->type & 0xff; switch (nodetype) { case MODELNODETYPE_DL: rodata = &curnode->rodata->dl; rwdata = model_get_node_rw_data(model, curnode); if (rwdata->gdl == NULL) { break; } // By default, the model instance's displaylist points to the // one in the model definition. If it hasn't been changed we'll // use the space... after the model definition's colour table? // Let's hope that's not being used by other instances... if (rwdata->gdl == rodata->opagdl) { gdlptr = (Gfx *)((uintptr_t)rodata->colours + ((uintptr_t)rodata->opagdl & 0xffffff)); } else { gdlptr = rwdata->gdl; } if (rodata->xlugdl) { gdlptr2 = (Gfx *)((uintptr_t)rodata->colours + ((uintptr_t)rodata->xlugdl & 0xffffff)); } else { gdlptr2 = NULL; } // Iterate the primary DL, and once the end is reached // iterate the secondary DL if we have one. while (true) { op = *(s8 *)&gdlptr->words.w0; if (op == G_ENDDL) { if (gdlptr2) { // Switch to second DL gdlptr = gdlptr2; gdlptr2 = NULL; } else { // We're done gdlptr = NULL; break; } } else { // Note: We should have found an MTX op before VTX. if (op == G_VTX) { u8 *ptr = (u8 *)&gdlptr->words.w0; u32 word = gdlptr->words.w1 & 0xffffff; s32 numverts; s32 i; vertices = (Vtx *)((uintptr_t)rodata->vertices + word); numverts = (u32)ptr[1] / 16 + 1; if (posnode) { for (i = 0; i < numverts; i++) { s32 x = spbc[0] - vertices[i].x; s32 y = spbc[1] - vertices[i].y; s32 z = spbc[2] - vertices[i].z; s32 dist = x * x + y * y + z * z; if (dist < mindist) { mindist = dist; bestnode = curnode; bestcoords[0] = vertices[i].x + (s32)spd4.f[0]; bestcoords[1] = vertices[i].y + (s32)spd4.f[1]; bestcoords[2] = vertices[i].z + (s32)spd4.f[2]; } } } } else if (op == G_MTX) { u32 addr = gdlptr->words.w1 & 0xffffff; posnode = model_find_node_by_mtx_index(model, addr / sizeof(Mtxf)); model_node_get_model_relative_position(model, posnode, &spd4); spbc[0] = spd4.x + spc8.x; spbc[1] = spd4.y + spc8.y; spbc[2] = spd4.z + spc8.z; } gdlptr++; } } break; case MODELNODETYPE_DISTANCE: model_apply_distance_relations(model, curnode); break; case MODELNODETYPE_TOGGLE: model_apply_toggle_relations(model, curnode); break; case MODELNODETYPE_HEADSPOT: model_apply_head_relations(model, curnode); break; } if (curnode->child && (curnode == node || (nodetype != MODELNODETYPE_BBOX && nodetype != MODELNODETYPE_11))) { curnode = curnode->child; } else { while (curnode) { if (curnode == node) { curnode = NULL; break; } if (curnode->next) { curnode = curnode->next; break; } curnode = curnode->parent; } } } if (bestnode == NULL) { return; } // Do a pass over the entire model's tree, looking for vertices that share // the chosen vertex, and darken them. curnode = model->definition->rootnode; while (curnode) { nodetype = curnode->type & 0xff; switch (nodetype) { case MODELNODETYPE_DL: rodata = &curnode->rodata->dl; rwdata = model_get_node_rw_data(model, curnode); if (rwdata->gdl == NULL) { break; } if (rwdata->gdl == rodata->opagdl) { gdlptr = (Gfx *)((uintptr_t)rodata->colours + ((u32)rodata->opagdl & 0xffffff)); } else { gdlptr = rwdata->gdl; } if (rodata->xlugdl) { gdlptr2 = (Gfx *)((uintptr_t)rodata->colours + ((u32)rodata->xlugdl & 0xffffff)); } else { gdlptr2 = NULL; } while (true) { s32 op = *(s8 *)&gdlptr->words.w0; if (op == G_ENDDL) { if (gdlptr2) { // Switch to second DL gdlptr = gdlptr2; gdlptr2 = NULL; } else { // We're done gdlptr = NULL; break; } } else { // Note: We should have found an MTX op before VTX. if (op == G_VTX) { u8 *ptr = (u8 *)&gdlptr->words.w0; u32 word = gdlptr->words.w1 & 0xffffff; Vtx *vertices = (Vtx *)((uintptr_t)rodata->vertices + word); s32 numverts = (u32)ptr[1] / 16 + 1; s32 i; if (posnode) { for (i = 0; i < numverts; i++) { s32 x = vertices[i].x + (s32)spd4.f[0]; s32 y = vertices[i].y + (s32)spd4.f[1]; s32 z = vertices[i].z + (s32)spd4.f[2]; if (x == bestcoords[0] && y == bestcoords[1] && z == bestcoords[2]) { if ((uintptr_t)rwdata->colours == ALIGN8((uintptr_t)rodata->vertices + rodata->numvertices * sizeof(Vtx))) { Col *colours = vtxstore_allocate(rodata->numcolours, VTXSTORETYPE_CHRCOL, 0, 0); s32 j; if (colours) { for (j = 0; j < rodata->numcolours; j++) { colours[j] = rwdata->colours[j]; } rwdata->colours = colours; } else { // empty } } if ((uintptr_t)rwdata->colours != ALIGN8((uintptr_t)rodata->vertices + rodata->numvertices * sizeof(Vtx))) { s32 offset = rwdata->vertices[word / sizeof(Vtx) + i].colour >> 2; Col *colours = (Col *) ((uintptr_t)rwdata->colours + spac); colours[offset].a = alpha; } } } } } else if (op == G_MTX) { u32 addr = gdlptr->words.w1 & 0xffffff; posnode = model_find_node_by_mtx_index(model, addr / sizeof(Mtxf)); model_node_get_model_relative_position(model, posnode, &spd4); } else if (op == G_COL) { spac = gdlptr->words.w1 & 0xffffff; } gdlptr++; } } break; case MODELNODETYPE_DISTANCE: model_apply_distance_relations(model, curnode); break; case MODELNODETYPE_TOGGLE: model_apply_toggle_relations(model, curnode); break; case MODELNODETYPE_HEADSPOT: model_apply_head_relations(model, curnode); break; } if (curnode->child) { curnode = curnode->child; } else { while (curnode) { if (curnode && curnode->next) { curnode = curnode->next; break; } curnode = curnode->parent; } } } } /** * Bruise a chr by darkening their vertices. * * This happens when the chr is shot, which creates the illusion of blood * soaking through their clothing. */ void chr_bruise(struct model *model, s32 hitpart, struct modelnode *node, struct coord *arg3) { struct modelnode *bestnode = NULL; bool ok; s32 nodetype; s32 mindist = 0x7fffffff; Vtx *vertices; s32 bestcoords[3]; struct modelnode *curnode; Gfx *gdlptr; Gfx *gdlptr2; s32 op; struct modelnode *posnode = NULL; struct coord relpos; struct coord spd4; struct coord spc8; s32 spbc[3]; s32 alpha = 20 + (random() % 50); struct modelrodata_dl *rodata; struct modelrwdata_dl *rwdata; s32 spac = 0; model_node_get_model_relative_position(model, model_node_find_mtx_node(node), &relpos); spc8.f[0] = arg3->f[0] - relpos.f[0]; spc8.f[1] = arg3->f[1] - relpos.f[1]; spc8.f[2] = arg3->f[2] - relpos.f[2]; // This first pass over the node tree is deciding which modelnode to use. curnode = node; while (curnode) { nodetype = curnode->type & 0xff; switch (nodetype) { case MODELNODETYPE_DL: rodata = &curnode->rodata->dl; rwdata = model_get_node_rw_data(model, curnode); if (rwdata->gdl == NULL) { break; } // By default, the model instance's displaylist points to the // one in the model definition. If it hasn't been changed we'll // use the space... after the model definition's colour table? // Let's hope that's not being used by other instances... if (rwdata->gdl == rodata->opagdl) { gdlptr = (Gfx *)((uintptr_t)rodata->colours + ((u32)rodata->opagdl & 0xffffff)); } else { gdlptr = rwdata->gdl; } if (rodata->xlugdl) { gdlptr2 = (Gfx *)((uintptr_t)rodata->colours + ((u32)rodata->xlugdl & 0xffffff)); } else { gdlptr2 = NULL; } // Iterate the primary DL, and once the end is reached // iterate the secondary DL if we have one. while (true) { op = *(s8 *)&gdlptr->words.w0; if (op == G_ENDDL) { if (gdlptr2) { // Switch to second DL gdlptr = gdlptr2; gdlptr2 = NULL; } else { // We're done gdlptr = NULL; break; } } else { // Note: We should have found an MTX op before VTX. if (op == G_VTX) { u8 *ptr = (u8 *)&gdlptr->words.w0; u32 word = gdlptr->words.w1 & 0xffffff; s32 numverts; s32 i; vertices = (Vtx *)((uintptr_t)rodata->vertices + word); numverts = (u32)ptr[1] / 16 + 1; if (posnode) { for (i = 0; i < numverts; i++) { s32 x = spbc[0] - vertices[i].x; s32 y = spbc[1] - vertices[i].y; s32 z = spbc[2] - vertices[i].z; s32 dist = x * x + y * y + z * z; if (dist < mindist) { mindist = dist; bestnode = curnode; bestcoords[0] = vertices[i].x + (s32)spd4.f[0]; bestcoords[1] = vertices[i].y + (s32)spd4.f[1]; bestcoords[2] = vertices[i].z + (s32)spd4.f[2]; } } } } else if (op == G_MTX) { u32 addr = gdlptr->words.w1 & 0xffffff; posnode = model_find_node_by_mtx_index(model, addr / sizeof(Mtxf)); model_node_get_model_relative_position(model, posnode, &spd4); spbc[0] = spd4.x + spc8.x; spbc[1] = spd4.y + spc8.y; spbc[2] = spd4.z + spc8.z; } gdlptr++; } } break; case MODELNODETYPE_DISTANCE: model_apply_distance_relations(model, curnode); break; case MODELNODETYPE_TOGGLE: model_apply_toggle_relations(model, curnode); break; case MODELNODETYPE_HEADSPOT: model_apply_head_relations(model, curnode); break; } if (curnode->child && (curnode == node || (nodetype != MODELNODETYPE_BBOX && nodetype != MODELNODETYPE_11))) { curnode = curnode->child; } else { while (curnode) { if (curnode == node) { curnode = NULL; break; } if (curnode->next) { curnode = curnode->next; break; } curnode = curnode->parent; } } } ok = true; if (bestnode == NULL) { return; } // Do a pass over the entire model's tree, looking for vertices that share // the chosen vertex, and darken then. curnode = model->definition->rootnode; while (curnode) { nodetype = curnode->type & 0xff; switch (nodetype) { case MODELNODETYPE_BBOX: if (g_Vars.hitboundscount) { s32 i; ok = false; for (i = 0; i < g_Vars.hitboundscount; i++) { if (g_Vars.hitnodes[i] == curnode) { ok = true; } } } else { ok = true; } break; case MODELNODETYPE_DL: if (ok) { rodata = &curnode->rodata->dl; rwdata = model_get_node_rw_data(model, curnode); if (rwdata->gdl == NULL) { break; } if (rwdata->gdl == rodata->opagdl) { gdlptr = (Gfx *)((uintptr_t)rodata->colours + ((u32)rodata->opagdl & 0xffffff)); } else { gdlptr = rwdata->gdl; } if (rodata->xlugdl) { gdlptr2 = (Gfx *)((uintptr_t)rodata->colours + ((u32)rodata->xlugdl & 0xffffff)); } else { gdlptr2 = NULL; } while (true) { s32 op = *(s8 *)&gdlptr->words.w0; if (op == G_ENDDL) { if (gdlptr2) { // Switch to second DL gdlptr = gdlptr2; gdlptr2 = NULL; } else { // We're done gdlptr = NULL; break; } } else { // Note: We should have found an MTX op before VTX. if (op == G_VTX) { u8 *ptr = (u8 *)&gdlptr->words.w0; u32 word = gdlptr->words.w1 & 0xffffff; Vtx *vertices = (Vtx *)((uintptr_t)rodata->vertices + word); s32 numverts = (u32)ptr[1] / 16 + 1; s32 i; if (posnode) { for (i = 0; i < numverts; i++) { s32 coordinate = vertices[i].x + (s32)spd4.f[0]; if (coordinate == bestcoords[0]) { coordinate = vertices[i].y + (s32)spd4.f[1]; if (coordinate == bestcoords[1]) { coordinate = vertices[i].z + (s32)spd4.f[2]; if (coordinate == bestcoords[2]) { if ((uintptr_t)rwdata->colours == ALIGN8((u32)rodata->vertices + rodata->numvertices * sizeof(Vtx))) { Col *colours = vtxstore_allocate(rodata->numcolours, VTXSTORETYPE_CHRCOL, 0, 0); s32 j; if (colours) { for (j = 0; j < rodata->numcolours; j++) { colours[j] = rwdata->colours[j]; } rwdata->colours = colours; } } if ((uintptr_t)rwdata->colours != ALIGN8((uintptr_t)rodata->vertices + rodata->numvertices * sizeof(Vtx))) { s32 offset = rwdata->vertices[word / sizeof(Vtx) + i].colour >> 2; Col *colours = (Col *) ((uintptr_t)rwdata->colours + spac); colours[offset].a = alpha; } } } } } } } else if (op == G_MTX) { u32 addr = gdlptr->words.w1 & 0xffffff; posnode = model_find_node_by_mtx_index(model, addr / sizeof(Mtxf)); model_node_get_model_relative_position(model, posnode, &spd4); } else if (op == G_COL) { spac = gdlptr->words.w1 & 0xffffff; } gdlptr++; } } } break; case MODELNODETYPE_DISTANCE: model_apply_distance_relations(model, curnode); break; case MODELNODETYPE_TOGGLE: model_apply_toggle_relations(model, curnode); break; case MODELNODETYPE_HEADSPOT: model_apply_head_relations(model, curnode); break; } if (curnode->child) { curnode = curnode->child; } else { while (curnode) { if (curnode && curnode->next) { curnode = curnode->next; break; } curnode = curnode->parent; } } } } /** * Disfigure and darken a chr due to them dying from explosion damage. * * Vertices and colours are copied from the modeldef into new vtxstore * allocations. If the vtxstore is full, the chr will not become disfigured * or will only be partially disfigured. * * The function looks for displaylist nodes in the chr's model. For each DL node * found, its vertex and colour information is copied to the vtx store. The GBI * commands are then iterated, looking for pointers into the vertex table. For * each pointer found, the vertices at that offset in the table are adjusted * randomly. Lastly, every colour in the node's colour table is darkened. */ void chr_disfigure(struct chrdata *chr, struct coord *exppos, f32 damageradius) { struct modelnode *node; struct modelrodata_dl *rodata; struct modelrwdata_dl *rwdata; Col *colours; struct model *model = chr->model; Vtx *vertices; u32 stack; Gfx *gdlptr; Gfx *gdlptr2; struct modelnode *posnode = NULL; struct coord pos; s32 i; s32 j; u32 rand = random(); if (g_Vars.mplayerisrunning || !chr_is_dead(chr)) { return; } // Iterate the nodes in the chr model node = model->definition->rootnode; while (node) { switch (node->type & 0xff) { case MODELNODETYPE_DL: rodata = &node->rodata->dl; rwdata = model_get_node_rw_data(model, node); if (rwdata->gdl == NULL) { break; } // Check that the node is using the modeldef's vertices // (ie. we haven't disfigured this node already) if (rwdata->vertices == rodata->vertices) { // Copy the vertices from the modeldef to the vtxstore if (rwdata->vertices == rodata->vertices) { Vtx *vertices = vtxstore_allocate(rodata->numvertices, VTXSTORETYPE_CHRVTX, 0, 0); if (vertices) { for (i = 0; i < rodata->numvertices; i++) { vertices[i] = rwdata->vertices[i]; } rwdata->vertices = vertices; } } // Copy the colours from the modeldef to the vtxstore if ((uintptr_t)rwdata->colours == ALIGN8((uintptr_t)&rodata->vertices[rodata->numvertices])) { colours = vtxstore_allocate(rodata->numcolours, VTXSTORETYPE_CHRCOL, 0, 0); if (colours) { for (i = 0; i < rodata->numcolours; i++) { colours[i] = rwdata->colours[i]; } rwdata->colours = colours; } } // Iterate the node's primary and secondary DLs, looking for // MTX and VTX pointers if (rwdata->vertices != rodata->vertices && (uintptr_t)rwdata->colours != ALIGN8((uintptr_t)&rodata->vertices[rodata->numvertices])) { if (rwdata->gdl == rodata->opagdl) { gdlptr = (Gfx *)((uintptr_t)rodata->colours + ((s32)rodata->opagdl & 0xffffff)); } else { gdlptr = rwdata->gdl; } if (rodata->xlugdl) { gdlptr2 = (Gfx *)((uintptr_t)rodata->colours + ((s32)rodata->xlugdl & 0xffffff)); } else { gdlptr2 = NULL; } while (true) { s32 op = *(s8 *)&gdlptr->words.w0; if (op == G_ENDDL) { if (gdlptr2 == NULL) { // We're done gdlptr = NULL; break; } // Switch to second DL gdlptr = gdlptr2; gdlptr2 = NULL; } else { // Note: We should have found an MTX op before VTX. // MTX sets posnode. if (op == G_VTX) { // Iterate the vertex table and fudge them u8 *ptr = (u8 *)&gdlptr->words.w0; s32 word = gdlptr->words.w1 & 0xffffff; s32 numverts; vertices = (Vtx *)((uintptr_t)rwdata->vertices + word); numverts = (u32)ptr[1] / 16 + 1; if (posnode) { for (i = 0; i < numverts; i++) { rng2_set_seed(rand + vertices[i].x + vertices[i].y + vertices[i].z + (u32)pos.f[0] + (u32)pos.f[1] + (u32)pos.f[2]); if (vertices[i].x > 0) { vertices[i].x -= (s16)(random2() % 20); } else if (vertices[i].x < 0) { vertices[i].x += (s16)(random2() % 20); } if (vertices[i].y > 0) { vertices[i].y -= (s16)(random2() % 20); } else if (vertices[i].y < 0) { vertices[i].y += (s16)(random2() % 20); } if (vertices[i].z > 0) { vertices[i].z -= (s16)(random2() % 20); } else if (vertices[i].z < 0) { // @bug: should be z instead of y vertices[i].y += (s16)(random2() % 20); } } } } else if (op == G_MTX) { // Get the position of the node relative to the model u32 addr = gdlptr->words.w1 & 0xffffff; posnode = model_find_node_by_mtx_index(model, addr / sizeof(Mtxf)); model_node_get_model_relative_position(model, posnode, &pos); } gdlptr++; } } // Darken colours for (i = 0; i < rodata->numcolours; i++) { for (j = 0; j < 3; j++) { s32 tmp = rwdata->colours[i].bytes[j]; tmp = tmp * 5 / 16; if (tmp < 0) { tmp = 0; } rwdata->colours[i].bytes[j] = tmp; } } } } break; case MODELNODETYPE_DISTANCE: model_apply_distance_relations(model, node); break; case MODELNODETYPE_TOGGLE: model_apply_toggle_relations(model, node); break; case MODELNODETYPE_HEADSPOT: model_apply_head_relations(model, node); break; } // Find the next node to iterate if (node->child) { node = node->child; } else { while (node) { if (node->next) { node = node->next; break; } node = node->parent; } } } } f32 chr_get_hit_radius(struct chrdata *chr) { s32 i; f32 result; f32 highest = 0; if (chr->model) { result = model_get_effective_scale(chr->model); for (i = 0; i < 2; i++) { if (chr->weapons_held[i]) { struct defaultobj *obj = chr->weapons_held[i]->obj; f32 value = model_get_effective_scale(obj->model) * chr->model->scale; if (value > highest) { highest = value; } } } result += highest; if (chr_get_shield(chr) > 0) { result += 10; } } else { result = 100; } return result; } void chr_test_hit(struct prop *prop, struct shotdata *shotdata, bool isshooting, bool cheap) { struct coord spdc; struct coord spd0; struct chrdata *chr = prop->chr; if ((chr->chrflags & CHRCFLAG_HIDDEN) == 0 && (prop->flags & PROPFLAG_ONTHISSCREENTHISTICK)) { f32 radius = chr_get_hit_radius(chr); if (prop->z - radius < shotdata->distance) { struct model *model = chr->model; s32 hitpart = 0; struct modelnode *node = NULL; s32 spb8 = 0; struct hitthing sp88; s32 sp84 = 0; struct modelnode *sp80 = NULL; Mtxf *rootmtx = model_get_root_mtx(model); struct prop *next; struct prop *child; f32 sp70; Mtxf *mtx; f32 sp68; if (pos_is_facing_pos(&shotdata->gunpos2d, &shotdata->gundir2d, (struct coord *)rootmtx->m[3], radius)) { spb8 = 1; hitpart = 1; } if (hitpart) { if (chr_get_shield(chr) > 0.0f) { var8005efc0 = 10.0f / model->scale; } child = prop->child; while (child) { next = child->next; obj_attachment_test_hit(child, shotdata); child = next; } if (cheap || var8005efc0 > 0.0f) { hitpart = model_test_for_hit(model, &shotdata->gunpos2d, &shotdata->gundir2d, &node); while (hitpart > 0) { if (obj_find_hitthing_by_bboxrodata_mtx(model, node, &shotdata->gunpos2d, &shotdata->gundir2d, &sp88, &sp84, &sp80)) { mtx4_transform_vec(&model->matrices[sp84], &sp88.pos, &spdc); mtx4_transform_vec_in_place(cam_get_projection_mtxf(), &spdc); mtx4_rotate_vec(&model->matrices[sp84], &sp88.unk0c, &spd0); mtx4_rotate_vec_in_place(cam_get_projection_mtxf(), &spd0); break; } hitpart = model_test_for_hit(model, &shotdata->gunpos2d, &shotdata->gundir2d, &node); } } else { hitpart = model_test_for_hit(model, &shotdata->gunpos2d, &shotdata->gundir2d, &node); if (hitpart > 0) { if (projectile_0f06bea0(model, model->definition->rootnode, model->definition->rootnode, &shotdata->gunpos2d, &shotdata->gundir2d, &sp88.pos, &sp70, &node, &hitpart, &sp84, &sp80)) { mtx4_transform_vec(cam_get_projection_mtxf(), &sp88.pos, &spdc); mtx4_rotate_vec(cam_get_projection_mtxf(), &sp88.unk0c, &spd0); } else { hitpart = 0; } } } if (var8005efc0 > 0.0f) { var8005efc0 = 0.0f; } } if (hitpart > 0) { mtx = cam_get_world_to_screen_mtxf(); sp68 = spdc.x * mtx->m[0][2] + spdc.y * mtx->m[1][2] + spdc.z * mtx->m[2][2] + mtx->m[3][2]; sp68 = -sp68; if (sp68 < shotdata->distance) { hit_create(shotdata, prop, sp68, hitpart, node, &sp88, sp84, sp80, model, true, chr_get_shield(chr) > 0.0f, &spdc, &spd0); } } if (spb8 && hitpart <= 0 && prop->z <= shotdata->distance && isshooting) { if (chr_get_target_prop(chr) == g_Vars.currentplayer->prop) { chr->chrflags |= CHRCFLAG_NEAR_MISS; } chr->numclosearghs++; } } } } /** * Handle a chr being shot. */ void chr_hit(struct shotdata *shotdata, struct hit *hit) { struct prop *prop; struct chrdata *chr; Mtxf spb0; struct coord hitpos; struct coord sp98; s16 hitpos_s16[3]; u8 ismelee = false; struct funcdef *func = gset_get_funcdef_by_gset(&shotdata->gset); f32 shield; if (func && (func->type & 0xff) == INVENTORYFUNCTYPE_MELEE) { ismelee = true; } // The prop that was hit could be an item held by the chr, // so traverse the parents. The root parent is the chr itself. prop = hit->prop; while (prop->parent) { prop = prop->parent; } chr = prop->chr; if ((chr->chrflags & CHRCFLAG_HIDDEN) == 0) { sp98.x = shotdata->gunpos2d.x - (hit->distance * shotdata->gundir2d.x) / shotdata->gundir2d.z; sp98.y = shotdata->gunpos2d.y - (hit->distance * shotdata->gundir2d.y) / shotdata->gundir2d.z; sp98.z = shotdata->gunpos2d.z - hit->distance; mtx4_transform_vec(cam_get_projection_mtxf(), &sp98, &hitpos); bgun_set_hit_pos(&hitpos); bgun_play_prop_hit_sound(&shotdata->gset, hit->prop, -1); chr_emit_sparks(chr, hit->prop, hit->hitpart, &hitpos, &shotdata->gundir3d, g_Vars.currentplayer->prop->chr); hitpos_s16[0] = hit->hitthing.pos.x; hitpos_s16[1] = hit->hitthing.pos.y; hitpos_s16[2] = hit->hitthing.pos.z; shield = chr_get_shield(chr); chr_damage_by_impact(chr, gset_get_damage(&shotdata->gset), &shotdata->gundir3d, &shotdata->gset, g_Vars.currentplayer->prop, hit->hitpart, hit->prop, hit->bboxnode, hit->model, hit->hitthing.unk28 / 2, hitpos_s16); if (g_Vars.antiplayernum >= 0 && g_Vars.currentplayer == g_Vars.anti && (chr->hidden & CHRHFLAG_ANTINONINTERACTABLE)) { return; } if (g_Vars.coopplayernum >= 0 && g_Vars.coopfriendlyfire == false && prop->type == PROPTYPE_PLAYER) { return; } if (g_MissionConfig.iscoop && g_Vars.coopfriendlyfire == false && chr->team == TEAM_ALLY) { return; } if (shield <= 0) { if (hit->prop->type == PROPTYPE_WEAPON) { // Shot a chr's weapon struct weaponobj *weapon = hit->prop->weapon; struct surfacetype *type; s32 surfacetype; s32 index; // Shooting an explosive in a chr's hand causes it to explode if (weapon->weaponnum == WEAPON_GRENADE || weapon->weaponnum == WEAPON_GRENADEROUND || weapon->weaponnum == WEAPON_ROCKET || weapon->weaponnum == WEAPON_HOMINGROCKET || weapon->weaponnum == WEAPON_TIMEDMINE || weapon->weaponnum == WEAPON_REMOTEMINE || weapon->weaponnum == WEAPON_PROXIMITYMINE) { obj_set_dropped(hit->prop, DROPTYPE_DEFAULT); chr->hidden |= CHRHFLAG_DROPPINGITEM; obj_damage(&weapon->base, gset_get_damage(&shotdata->gset), &sp98, shotdata->gset.weaponnum, g_Vars.currentplayernum); return; } // Create decal depending on the weapon's surface type if (hit->hitthing.texturenum < 0 || hit->hitthing.texturenum >= NUM_TEXTURES) { surfacetype = SURFACETYPE_DEFAULT; } else { surfacetype = g_Textures[hit->hitthing.texturenum].surfacetype; } if (surfacetype >= 0 && surfacetype < 15) { type = g_SurfaceTypes[surfacetype]; if (type->numwallhittexes > 0) { index = random() % type->numwallhittexes; wallhit_create( &hit->hitthing.pos, &hit->hitthing.unk0c, &shotdata->gunpos3d, 0, 0, type->wallhittexes[index], 1, hit->prop, hit->mtxindex, 0, g_Vars.currentplayer->prop->chr, 0); } } // Drop gun if (chr->aibot == NULL && (chr->flags & CHRFLAG0_CANLOSEGUN)) { chr->gunprop = hit->prop; obj_set_dropped(hit->prop, DROPTYPE_DEFAULT); chr->hidden |= CHRHFLAG_DROPPINGITEM; } } else if (hit->hitpart == HITPART_HAT) { // Shot a chr's hat struct surfacetype *type; s32 index; // Create decal depending on the hat's surface type if (hit->hitthing.texturenum < 0) { type = g_SurfaceTypes[0]; } else { type = g_SurfaceTypes[g_Textures[hit->hitthing.texturenum].surfacetype]; } index = random() % type->numwallhittexes; wallhit_create( &hit->hitthing.pos, &hit->hitthing.unk0c, &shotdata->gunpos3d, 0, 0, type->wallhittexes[index], 1, chr->weapons_held[2], hit->mtxindex, 0, g_Vars.currentplayer->prop->chr, 0); } else { // Shot a chr in the flesh s32 race = CHRRACE(chr); struct coord sp5c; Mtxf *sp58 = model_find_node_mtx(hit->model, hit->bboxnode, 0); // Create blood mtx0001719c(sp58->m, spb0.m); mtx4_transform_vec(&spb0, &sp98, &sp5c); #if VERSION >= VERSION_NTSC_1_0 if (!chr->noblood && race != RACE_DRCAROLL && race != RACE_ROBOT && race != RACE_EYESPY && !ismelee && shotdata->gset.weaponnum != WEAPON_TRANQUILIZER) { u8 darker; if (chr->bodynum == BODY_MRBLONDE || race == RACE_SKEDAR) { darker = true; } else { darker = false; } if (!chr_is_using_paintball(g_Vars.currentplayer->prop->chr)) { chr_bruise(hit->model, hit->hitpart, hit->bboxnode, &sp5c); } splats_create_for_chr_hit(prop, shotdata, &sp98, &hitpos, darker, 0, g_Vars.currentplayer->prop->chr); } #else // NTSC beta wraps all the blood logic in this paintball check. // If paintball is enabled, neither blood nor paint is created. if (!chr_is_using_paintball(g_Vars.currentplayer->prop->chr) && !chr->noblood && race != RACE_DRCAROLL && race != RACE_ROBOT && race != RACE_EYESPY && !ismelee && shotdata->gset.weaponnum != WEAPON_TRANQUILIZER) { u8 darker; if (chr->bodynum == BODY_MRBLONDE || race == RACE_SKEDAR) { darker = true; } else { darker = false; } chr_bruise(hit->model, hit->hitpart, hit->bboxnode, &sp5c); splats_create_for_chr_hit(prop, shotdata, &sp98, &hitpos, darker, 0, g_Vars.currentplayer->prop->chr); } #endif } } } } void chrs_set_stage_translation_thing(f32 arg1) { // empty } void chranimdebug_everyone(bool enable) { g_ChrsAnimDebugSetAll = enable; g_ChrsAnimDebugForceLoop = enable; } void chranimdebug_decrement_anim(s32 quantity) { g_ChrsAnimDebugAnimNum -= quantity; if (g_ChrsAnimDebugAnimNum <= 0) { g_ChrsAnimDebugAnimNum = anim_get_num_animations() - 1; } } void chranimdebug_increment_anim(s32 quantity) { g_ChrsAnimDebugAnimNum += quantity; if (g_ChrsAnimDebugAnimNum >= anim_get_num_animations()) { g_ChrsAnimDebugAnimNum = 1; } } void chranimdebug_toggle_paused(void) { g_ChrsAnimDebugPaused = !g_ChrsAnimDebugPaused; } void chranimdebug_select_anim(bool enable) { g_ChrsAnimDebugSlow = enable; main_override_variable("selectanimnum", &g_ChrsAnimDebugAnimNum); } void chrs_check_for_noise(f32 noiseradius) { s32 i; f32 add = 0.075f; for (i = 0; i < g_NumChrSlots; i++) { if (g_ChrSlots[i].model) { struct prop *prop = g_ChrSlots[i].prop; if (prop && prop->type == PROPTYPE_CHR && chr_get_target_prop(&g_ChrSlots[i]) == g_Vars.currentplayer->prop) { f32 distance = chr_get_distance_to_current_player(&g_ChrSlots[i]); if (distance == 0) { distance = 2; } else { distance = (noiseradius * 100 * g_ChrSlots[i].hearingscale * (1.0f + add)) / distance; } if (distance > 1.0f) { chr_record_last_hear_target_time(&g_ChrSlots[i]); #if PIRACYCHECKS { s32 *i = (s32 *)&__scHandleRetrace; s32 *end = (s32 *)&__scHandleTasks; u32 checksum = 0; while (i < end) { checksum *= 2; checksum += *i; i++; } if (checksum != CHECKSUM_PLACEHOLDER) { g_HeadsAndBodies[BODY_SKEDARKING].filenum = 0; } } #endif } } } } } struct chrdata *chr_find_by_literal_id(s32 chrnum) { s32 lower = 0; s32 upper = g_NumChrs; s32 i; while (upper >= lower) { i = (lower + upper) / 2; if (chrnum == g_Chrnums[i]) { return &g_ChrSlots[g_ChrIndexes[i]]; } if (chrnum < g_Chrnums[i]) { upper = i - 1; } else { lower = i + 1; } } return NULL; } struct prop *chr_get_held_prop(struct chrdata *chr, s32 hand) { return chr->weapons_held[hand]; } struct prop *chr_get_held_usable_prop(struct chrdata *chr, s32 hand) { struct prop *prop = chr->weapons_held[hand]; if (prop) { struct weaponobj *weapon = prop->weapon; if (!gset_has_weapon_flag(weapon->weaponnum, WEAPONFLAG_AICANUSE)) { prop = NULL; } } return prop; } struct prop *chr_get_target_prop(struct chrdata *chr) { struct prop *ret; if (chr->target == -1) { ret = g_Vars.players[chr->p1p2]->prop; } else { ret = g_Vars.props + chr->target; } return ret; } bool chr_get_geometry(struct prop *prop, u8 **start, u8 **end) { struct chrdata *chr = prop->chr; if (chr->actiontype != ACT_DEAD && chr->actiontype != ACT_DRUGGEDKO && (chr->chrflags & (CHRCFLAG_PERIMDISABLEDTMP | CHRCFLAG_HIDDEN)) == 0 && (chr->hidden & CHRHFLAG_PERIMDISABLED) == 0) { chr->geo.header.type = GEOTYPE_CYL; if (chr->actiontype == ACT_DIE || chr->actiontype == ACT_DRUGGEDDROP) { chr->geo.header.flags = GEOFLAG_BLOCK_SHOOT; } else { chr->geo.header.flags = GEOFLAG_WALL | GEOFLAG_BLOCK_SHOOT; } chr->geo.ymin = chr->manground; chr->geo.ymax = chr->manground + chr->height; if (chr->actiontype == ACT_SKJUMP) { if (chr->manground > chr->act_skjump.ground) { chr->geo.ymin = chr->act_skjump.ground; } } chr->geo.x = prop->pos.x; chr->geo.z = prop->pos.z; chr->geo.radius = chr->radius; if (g_Vars.useperimshoot) { chr->geo.radius = 15; } *start = (void *) &chr->geo; *end = *start + sizeof(struct geocyl); return true; } *end = NULL; *start = NULL; return false; } void chr_get_bbox(struct prop *prop, f32 *radius, f32 *ymax, f32 *ymin) { struct chrdata *chr = prop->chr; *radius = chr->radius; *ymax = chr->manground + chr->height; *ymin = chr->manground + 20; if (chr->actiontype == ACT_SKJUMP && chr->act_skjump.ground < chr->manground) { *ymin = chr->act_skjump.ground + 20; } } f32 chr_get_ground(struct prop *prop) { struct chrdata *chr = prop->chr; return chr->ground; } bool chr_calculate_autoaim(struct prop *prop, struct coord *screenpos, f32 *xrange, f32 *yrange) { struct chrdata *chr = prop->chr; // Check if the chr is eligible for autoaim if ((prop->flags & PROPFLAG_ONTHISSCREENTHISTICK) && chr->actiontype != ACT_DIE && chr->actiontype != ACT_DRUGGEDDROP && chr->actiontype != ACT_DRUGGEDKO && chr->actiontype != ACT_DEAD && (chr->chrflags & CHRCFLAG_NOAUTOAIM) == 0 && ((chr->hidden & CHRHFLAG_CLOAKED) == 0 || USINGDEVICE(DEVICE_IRSCANNER)) && !(prop->type == PROPTYPE_PLAYER && g_Vars.players[playermgr_get_player_num_by_prop(prop)]->isdead) && !(g_Vars.coopplayernum >= 0 && (prop == g_Vars.bond->prop || prop == g_Vars.coop->prop))) { struct model *model = chr->model; Mtxf *mtx1; Mtxf *mtx2; if (model->definition->skel == &g_SkelChr) { mtx1 = &model->matrices[0]; mtx2 = &model->matrices[1]; screenpos->z = mtx2->m[3][2] + (mtx1->m[3][2] - mtx2->m[3][2]) * 0.5f; } else if (model->definition->skel == &g_SkelSkedar) { mtx2 = &model->matrices[0]; screenpos->z = mtx2->m[3][2]; } else if (model->definition->skel == &g_SkelDrCaroll) { mtx2 = &model->matrices[0]; screenpos->z = mtx2->m[3][2]; } else { screenpos->z = model->matrices[0].m[3][2]; } if (screenpos->z < 0) { if (model->definition->skel == &g_SkelChr) { screenpos->x = mtx2->m[3][0] + (mtx1->m[3][0] - mtx2->m[3][0]) * 0.5f; screenpos->y = mtx2->m[3][1] + (mtx1->m[3][1] - mtx2->m[3][1]) * 0.5f; } else if (model->definition->skel == &g_SkelSkedar) { screenpos->x = mtx2->m[3][0]; screenpos->y = mtx2->m[3][1]; } else if (model->definition->skel == &g_SkelDrCaroll) { screenpos->x = mtx2->m[3][0]; screenpos->y = mtx2->m[3][1]; } else { screenpos->x = model->matrices[0].m[3][0]; screenpos->y = model->matrices[0].m[3][1]; } yrange[0] = yrange[1] = 0; xrange[0] = xrange[1] = 0; model_get_screen_coords3(model, &xrange[1], &xrange[0], &yrange[1], &yrange[0]); return true; } } return false; } /** * Iterate the iterprop, its children and siblings to find wantprop. * Sum the number of matrix slots in the model data of each model that has been iterated. * * This function is recursive, where each recursion is for an attached child * or sibling. * * @bug: The matrices are counted from the same model definition * regardless of what model the children have. */ bool shieldhit_find_cmnum_for_prop(struct prop *iterprop, struct prop *wantprop, struct modelnode *node, struct model *model, s32 *total) { if (wantprop == iterprop) { *total += model_find_node_mtx_index(node, 0); return true; } *total += model->definition->nummatrices; if (iterprop->child && shieldhit_find_cmnum_for_prop(iterprop->child, wantprop, node, model, total) > 0) { return true; } if (iterprop->next && shieldhit_find_cmnum_for_prop(iterprop->next, wantprop, node, model, total) > 0) { return true; } return false; } /** * Given a model node, find the associated cmnum. * * The node may be on the root prop (chr or obj). It may also be a node on an * attached prop such as a knife or mine. */ s32 shieldhit_node_to_cmnum(struct prop *wantprop, struct modelnode *node, struct model *model, struct prop *rootprop) { s32 cmnum = 0; if (shieldhit_find_cmnum_for_prop(rootprop, wantprop, node, model, &cmnum)) { return cmnum; } return -1; } /** * Convert a cmnum to a prop, model and node. * * For example, a chr might have 14 matrices, and they might have a mine * attached to them which has one matrix. cmnums 0-13 will refer to matrices in * the chr's model, while cmnum 14 will be the mine. * * Return true if the cmnum was valid and values were written to the pointers. */ bool shieldhit_cmnum_to_node(s32 cmnum, struct prop *prop, struct prop **propptr, struct modelnode **nodeptr, struct model **modelptr) { \ while (true) { bool result = false; struct model *model; s32 stack; if (prop->type == PROPTYPE_CHR || prop->type == PROPTYPE_PLAYER) { model = prop->chr->model; } else { model = prop->obj->model; } if (1); if (cmnum >= model->definition->nummatrices) { cmnum -= model->definition->nummatrices; if (prop->child) { result = shieldhit_cmnum_to_node(cmnum, prop->child, propptr, nodeptr, modelptr); } if (prop->next && !result) { prop = prop->next; continue; } } else { *propptr = prop; *nodeptr = model_find_node_by_mtx_index(model, cmnum); *modelptr = model; result = true; } return result; } } void shieldhit_create(struct prop *rootprop, f32 shield, struct prop *hitprop, struct modelnode *node, struct model *model, s32 side, s16 *hitpos) { struct shieldhit *shieldhit = NULL; s32 i; s32 j; // Find any slot that isn't in use (ie. prop is NULL) for (i = 0; i < MAX_SHIELDHITS; i++) { if (g_ShieldHits[i].prop == NULL) { shieldhit = &g_ShieldHits[i]; break; } } // If all slots are in use, take the oldest one if (shieldhit == NULL) { struct shieldhit *oldesthit = NULL; s32 oldestframe = g_Vars.lvframe60; for (i = 0; i < MAX_SHIELDHITS; i++) { if (g_ShieldHits[i].lvframe60 < oldestframe) { oldesthit = &g_ShieldHits[i]; oldestframe = g_ShieldHits[i].lvframe60; } } shieldhit = oldesthit; } if (shieldhit) { shieldhit->prop = rootprop; shieldhit->node = node; shieldhit->model = model; shieldhit->side = side; shieldhit->lvframe60 = g_Vars.lvframe60; for (i = 0; i < 32; i++) { shieldhit->unk018[i] = -1; } shieldhit->unk011 = 2 + (random() % 6); shieldhit->shield = shield; if (hitpos) { shieldhit->hitposx = hitpos[0]; shieldhit->hitposy = hitpos[1]; shieldhit->hitposz = hitpos[2]; } else { shieldhit->hitposx = 0x7fff; } if (node) { bool pass = true; for (i = 0; i < MAX_SHIELDHITS; i++) { if (g_ShieldHits[i].prop == rootprop) { for (j = 0; j < 32; j++) { if (shieldhit->unk018[j] != -1 && shieldhit->unk018[j] != -2) { pass = false; break; } } if (!pass) { break; } } } if (pass) { s32 index = shieldhit_node_to_cmnum(hitprop, node, model, rootprop); if (index < 32) { shieldhit->unk018[index] = 0; shieldhit->unk038[index] = 0; } } } if (rootprop->type == PROPTYPE_CHR || rootprop->type == PROPTYPE_PLAYER) { rootprop->chr->hidden2 |= CHRH2FLAG_SHIELDHIT; } else if (rootprop->type == PROPTYPE_OBJ || rootprop->type == PROPTYPE_WEAPON || rootprop->type == PROPTYPE_DOOR) { rootprop->obj->flags3 |= OBJFLAG3_SHIELDHIT; } } g_ShieldHitActive = true; } void shieldhit_remove(struct shieldhit *shieldhit) { s32 exists = false; s32 i; struct prop *prop = shieldhit->prop; shieldhit->prop = NULL; // Check if there are other shield hits active g_ShieldHitActive = false; for (i = 0; i < MAX_SHIELDHITS; i++) { if (g_ShieldHits[i].prop) { g_ShieldHitActive = true; break; } } // Check if the prop being removed has other shield hits too for (i = 0; i < MAX_SHIELDHITS; i++) { if (prop == g_ShieldHits[i].prop) { exists = true; break; } } if (!exists) { // Mark prop as shield no longer visible if (prop->type == PROPTYPE_CHR || prop->type == PROPTYPE_PLAYER) { struct chrdata *chr = prop->chr; chr->hidden2 &= ~CHRH2FLAG_SHIELDHIT; } else if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_WEAPON || prop->type == PROPTYPE_DOOR) { struct defaultobj *obj = prop->obj; obj->flags3 &= ~OBJFLAG3_SHIELDHIT; } } } void shieldhits_remove_by_prop(struct prop *prop) { s32 i; for (i = 0; i < MAX_SHIELDHITS; i++) { if (prop == g_ShieldHits[i].prop) { shieldhit_remove(&g_ShieldHits[i]); } } } s32 shieldhit_find_parentnode_cmnum(struct prop *prop, s32 prevcmnum) { s32 cmnum = -1; struct modelnode *node2; struct prop *prop2; struct modelnode *node; struct model *model; if (shieldhit_cmnum_to_node(prevcmnum, prop, &prop2, &node, &model) && node) { node2 = model_node_find_parent_mtx_node(node); if (node2) { cmnum = shieldhit_node_to_cmnum(prop2, node2, model, prop); } else if (prop2->parent && model->attachedtomodel && model->attachedtonode) { cmnum = shieldhit_node_to_cmnum(prop2->parent, model->attachedtonode, model->attachedtomodel, prop); } } return cmnum; } s32 shieldhit_find_childnode_cmnum(struct prop *prop, s32 prevcmnum) { s32 cmnum = -1; struct modelnode *node2; struct prop *prop2; struct modelnode *node; struct model *model; if (shieldhit_cmnum_to_node(prevcmnum, prop, &prop2, &node, &model) && node) { node2 = model_node_find_child_mtx_node(node); if (node2) { cmnum = shieldhit_node_to_cmnum(prop2, node2, model, prop); } else { struct prop *child = prop2->child; while (child) { struct model *parentmodel = child->parentmodel; if (model == parentmodel->attachedtomodel) { if (node == parentmodel->attachedtonode) { cmnum = shieldhit_node_to_cmnum(child, parentmodel->definition->rootnode, parentmodel, prop); break; } } child = child->next; } } } return cmnum; } s32 shieldhit_find_anynode_cmnum(struct prop *prop, s32 prevcmnum) { s32 cmnum = -1; struct prop *child; struct prop *prop2; struct modelnode *node2; struct model *model2; if (shieldhit_cmnum_to_node(prevcmnum, prop, &prop2, &node2, &model2) && node2) { struct modelnode *node3 = model_node_find_child_or_parent_mtx_node(node2); if (node3) { cmnum = shieldhit_node_to_cmnum(prop2, node3, model2, prop); } else if (model_node_find_parent_mtx_node(node2) == NULL && prop2->parent) { child = prop2->parent->child; while (child && child != prop2) { child = child->next; } if (child) { child = child->next; while (child) { struct model *parent = child->parentmodel; if (parent->attachedtomodel == model2->attachedtomodel) { if (parent->attachedtonode == model2->attachedtonode) { cmnum = shieldhit_node_to_cmnum(child, parent->definition->rootnode, parent, prop); break; } } child = child->next; } } } } return cmnum; } void shieldhit_health_to_rgb(f32 health, s32 *r, s32 *g, s32 *b) { if (health < 1.5f) { *r = 57 - (s32) ((1.5f - health) * 28); *g = 75 - (s32) ((1.5f - health) * 20); *b = 0; } else if (health < 3.0f) { *r = 102 - (s32) ((3.0f - health) * 30); *g = 90 - (s32) ((3.0f - health) * 10); *b = 0; } else if (health < 4.5f) { *r = 174 - (s32) ((4.5f - health) * 48); *g = 129 - (s32) ((4.5f - health) * 26); *b = 0; } else if (health < 6.0f) { *r = 162 - (s32) ((6.0f - health) * -8); *g = 54 - (s32) ((6.0f - health) * -50); *b = 0; } else { *r = 162; *g = 54; *b = 0; } } f32 shieldhit_get_health(struct prop **propptr) { struct prop *prop = *propptr; if (prop->type == PROPTYPE_CHR || prop->type == PROPTYPE_PLAYER) { return chr_get_shield(prop->chr); } if (prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_WEAPON || prop->type == PROPTYPE_DOOR) { if (prop->obj->flags3 & OBJFLAG3_SHOWSHIELD) { return 4; } // Objects don't have shields with health, // hence why the max health is returned here. return 8; } return 0; } bool g_ShieldHitActive = false; Gfx *shieldhit_render_component(Gfx *gdl, struct shieldhit *hit, struct prop *prop, struct model *model, struct modelnode *node, s32 side, s32 arg6, s32 arg7, s32 alpha) { struct modelrodata_bbox *bbox = &node->rodata->bbox; Vtx vtxtemplate = {0}; Vtx *vertices; Col *colours; Mtxf *modelmtx; s32 i; s32 xmin; s32 xmax; s32 ymin; s32 ymax; s32 zmin; s32 zmax; s32 j; u32 stack; s16 sp180[8][3]; u8 alpha1; u8 alpha2; s16 st1; s16 st2; s16 st4; s16 st3; f32 gap; f32 shieldamount; s32 cloakfade; s32 cmcount; s32 sp104[6][4] = { { 0, 1, 3, 2 }, { 7, 5, 4, 6 }, { 5, 1, 0, 4 }, { 2, 3, 7, 6 }, { 0, 2, 6, 4 }, { 7, 3, 1, 5 }, }; s32 red1; s32 green1; s32 blue1; s32 red2; s32 green2; s32 blue2; s32 red3; s32 green3; s32 blue3; s32 mtxindex; if (prop->type == PROPTYPE_CHR || prop->type == PROPTYPE_PLAYER) { struct chrdata *chr = prop->chr; gap = 10.0f / chr->model->scale; shieldamount = chr_get_shield(chr); cloakfade = chr->cloakfadefrac; cmcount = chr->cmcount; } else { struct defaultobj *obj = prop->obj; gap = 0.0f; cloakfade = 64; cmcount = 0; shieldamount = (obj->flags3 & OBJFLAG3_SHOWSHIELD) ? 4.0f : 8.0f; } mtxindex = model_find_node_mtx_index(node, 0); modelmtx = &model->matrices[mtxindex]; xmin = bbox->xmin - gap; xmax = bbox->xmax + gap; ymin = bbox->ymin - gap; ymax = bbox->ymax + gap; zmin = bbox->zmin - gap; zmax = bbox->zmax + gap; sp180[0][0] = xmin; sp180[0][1] = ymin; sp180[0][2] = zmin; sp180[1][0] = xmin; sp180[1][1] = ymin; sp180[1][2] = zmax; sp180[2][0] = xmin; sp180[2][1] = ymax; sp180[2][2] = zmin; sp180[3][0] = xmin; sp180[3][1] = ymax; sp180[3][2] = zmax; sp180[4][0] = xmax; sp180[4][1] = ymin; sp180[4][2] = zmin; sp180[5][0] = xmax; sp180[5][1] = ymin; sp180[5][2] = zmax; sp180[6][0] = xmax; sp180[6][1] = ymax; sp180[6][2] = zmin; sp180[7][0] = xmax; sp180[7][1] = ymax; sp180[7][2] = zmax; gSPMatrix(gdl++, osVirtualToPhysical(modelmtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); if (side == -7) { colours = gfx_allocate_colours(1); gSPColor(gdl++, osVirtualToPhysical(colours), 1); colours[0].r = 0xff; colours[0].g = 0xff; colours[0].b = 0xff; if (cloakfade < 64) { colours[0].a = (s32) (cloakfade * (f32) alpha * (1.0f / 85.0f)); } else { colours[0].a = (s32) ((127 - cloakfade) * (f32) alpha * (1.0f / 85.0f)); } vertices = gfx_allocate_vertices(24); for (i = 0; i < ARRAYCOUNT(sp104); i++) { for (j = 0; j < ARRAYCOUNT(sp104[i]); j++) { vertices[j] = vtxtemplate; vertices[j].x = sp180[sp104[i][j]][0]; vertices[j].y = sp180[sp104[i][j]][1]; vertices[j].z = sp180[sp104[i][j]][2]; vertices[j].colour = 0; } vertices[0].s = 0; vertices[0].t = 0; vertices[1].s = 512; vertices[1].t = 0; vertices[2].s = 512; vertices[2].t = 512; vertices[3].s = 0; vertices[3].t = 512; gSPVertex(gdl++, osVirtualToPhysical(vertices), 4, 0); gSPTri2(gdl++, 0, 1, 2, 0, 2, 3); vertices += 4; } } else { f32 shield; tex_select(&gdl, &g_TexShieldConfigs[TEX_SHIELD_00], 4, 1, 2, true, NULL); if (side >= 0) { shield = hit->shield; } else { shield = shieldamount; if (side == -2) { if (hit->unk011 < arg7) { shield = 0.0f; } else { shield *= ((4.0f * hit->unk011) - arg7 + 1.0f) / (4.0f * hit->unk011); } } } shieldhit_health_to_rgb(shield, &red1, &green1, &blue1); red2 = red1 - 20; green2 = green1 - 20; blue2 = blue1 - 20; if (red2 < 0) { red2 = 0; } if (green2 < 0) { green2 = 0; } if (blue2 < 0) { blue2 = 0; } red3 = red1 - 60; green3 = green1 - 60; blue3 = blue1 - 60; if (red3 < 0) { red3 = 0; } if (green3 < 0) { green3 = 0; } if (blue3 < 0) { blue3 = 0; } st1 = (sinf((g_Vars.thisframestart240 % TICKS(1200)) * PALUPF(0.005235154f)) + 1.0f) * 0.5f * 32.0f * 32.0f; st2 = (cosf((g_Vars.thisframestart240 % TICKS(1200)) * PALUPF(0.005235154f)) + 1.0f) * 0.5f * 32.0f * 32.0f; st3 = st1 + 512; st4 = st2 + 512; if (side == -3 || side == -4 || side == -5 || side == -6) { colours = gfx_allocate_colours(1); gSPColor(gdl++, osVirtualToPhysical(colours), 1); if (side == -3) { colours[0].r = red2; colours[0].g = green2; colours[0].b = blue2; colours[0].a = (cmcount < 10) ? (s32) (alpha * 0.5882353f) : 0; } else { colours[0].r = red3; colours[0].g = green3; colours[0].b = blue3; if (cmcount < 10) { if (side == -4) { colours[0].a = (s32) (alpha * 0.3137255f); } else if (side == -5) { colours[0].a = (s32) (alpha * 0.23529412f); } else if (side == -6) { colours[0].a = (s32) (alpha * 0.15686275f); } } else { colours[0].a = 0; } } vertices = gfx_allocate_vertices(24); if ((prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_WEAPON || prop->type == PROPTYPE_DOOR) && (prop->obj->flags3 & OBJFLAG3_SHOWSHIELD)) { f32 mult = (sinf((g_Vars.thisframestart240 % TICKS(350)) * (PAL ? 0.021588264033198f : 0.0179491f)) + 1.0f) * 0.5f; colours->a = 50 + (u8) (s32) (120.0f * mult); colours->r = (u8) red2 + 50.0f * mult; colours->g = (u8) green2 + 50.0f * mult; colours->b = (u8) blue2 + 50.0f * mult; } else { st3 = 512; st1 = 0; st2 = 0; st4 = 512; } for (i = 0; i < ARRAYCOUNT(sp104); i++) { for (j = 0; j < ARRAYCOUNT(sp104[i]); j++) { vertices[j] = vtxtemplate; vertices[j].x = sp180[sp104[i][j]][0]; vertices[j].y = sp180[sp104[i][j]][1]; vertices[j].z = sp180[sp104[i][j]][2]; vertices[j].colour = 0; } vertices[0].s = st1; vertices[0].t = st2; vertices[1].s = st3; vertices[1].t = st2; vertices[2].s = st3; vertices[2].t = st4; vertices[3].s = st1; vertices[3].t = st4; gSPVertex(gdl++, osVirtualToPhysical(vertices), 4, 0); gSPTri2(gdl++, 0, 1, 2, 0, 2, 3); vertices += 4; } } else if (side == -2) { f32 alpha3; if (hit->unk011 < arg7) { alpha3 = 0.0f; } else { #if PAL alpha3 = ((TICKS(30.0f) - arg6) * 6.4f + 40.0f) * ((hit->unk011 - (f32) arg7 + 1.0f) / hit->unk011); #else alpha3 = ((TICKS(30.0f) - arg6) * 5.3333335f + 40.0f) * ((hit->unk011 - (f32) arg7 + 1.0f) / hit->unk011); #endif alpha3 *= alpha * (1.0f / 255.0f); } if (alpha3 > 255.0f) { alpha3 = 255.0f; } else if (alpha3 < 0.0f) { alpha3 = 0.0f; } colours = gfx_allocate_colours(3); gSPColor(gdl++, osVirtualToPhysical(colours), 3); colours[0].r = red3; colours[1].r = red3; colours[0].g = green3; colours[1].g = green3; colours[0].b = blue3; colours[1].b = blue3; colours[0].a = (s32) alpha3; colours[1].a = 0; vertices = gfx_allocate_vertices(30); for (i = 0; i < ARRAYCOUNT(sp104); i++) { for (j = 0; j < ARRAYCOUNT(sp104[i]); j++) { vertices[j] = vtxtemplate; vertices[j].x = sp180[sp104[i][j]][0]; vertices[j].y = sp180[sp104[i][j]][1]; vertices[j].z = sp180[sp104[i][j]][2]; vertices[j].colour = 0; } vertices[0].s = st1; vertices[0].t = st2; vertices[1].s = st3; vertices[1].t = st2; vertices[2].s = st3; vertices[2].t = st4; vertices[3].s = st1; vertices[3].t = st4; vertices[4] = vtxtemplate; vertices[4].x = (vertices[0].x + vertices[1].x + vertices[2].x + vertices[3].x) >> 2; vertices[4].y = (vertices[0].y + vertices[1].y + vertices[2].y + vertices[3].y) >> 2; vertices[4].z = (vertices[0].z + vertices[1].z + vertices[2].z + vertices[3].z) >> 2; vertices[4].s = (st1 + st3) >> 1; vertices[4].t = (st2 + st4) >> 1; vertices[4].colour = 4; gSPVertex(gdl++, osVirtualToPhysical(vertices), 5, 0); gSPTri4(gdl++, 0, 1, 4, 1, 2, 4, 2, 3, 4, 3, 0, 4); vertices += 5; } } else if (side < 0) { vertices = gfx_allocate_vertices(12); if (side == -1) { if (g_Vars.lvframe60 - hit->lvframe60 <= TICKS(80)) { f32 tmp = (hit->lvframe60 - g_Vars.lvframe60 + TICKS(80)) * (PAL ? 3.8636362552643f : 3.1875f); tmp *= alpha * (1.0f / 255.0f); alpha1 = (s32) tmp; } else { alpha1 = 0; } colours = gfx_allocate_colours(1); gSPColor(gdl++, osVirtualToPhysical(colours), 1); colours[0].r = red1; colours[0].g = green1; colours[0].b = blue1; colours[0].a = alpha1; } else if (side == -2) { f32 alpha4; if (hit->unk011 < arg7) { alpha4 = 0.0f; } else { alpha4 = (((TICKS(30.0f) - (f32) arg6) * PALUPF(4.0f)) + 40.0f) * (((hit->unk011 - (f32) arg7) + 1.0f) / hit->unk011); alpha4 *= alpha * (1.0f / 255.0f); } if (alpha4 > 255.0f) { alpha4 = 255.0f; } else if (alpha4 < 0.0f) { alpha4 = 0.0f; } colours = gfx_allocate_colours(1); gSPColor(gdl++, osVirtualToPhysical(colours), 1); colours[0].r = red3; colours[0].g = green3; colours[0].b = blue3; colours[0].a = (s32) alpha4; } for (j = 0; j < ARRAYCOUNT(sp180); j++) { vertices[j] = vtxtemplate; vertices[j].x = sp180[j][0]; vertices[j].y = sp180[j][1]; vertices[j].z = sp180[j][2]; vertices[j].colour = 0; } vertices[0].s = st1; vertices[0].t = st2; vertices[1].s = st3; vertices[1].t = st2; vertices[5].s = st3; vertices[5].t = st4; vertices[4].s = st1; vertices[4].t = st4; vertices[3].s = st1; vertices[3].t = st2; vertices[2].t = st2; vertices[2].s = st3; vertices[6].s = st3; vertices[6].t = st4; vertices[7].s = st1; vertices[7].t = st4; vertices[8] = vtxtemplate; vertices[8].x = sp180[2][0]; vertices[8].y = sp180[2][1]; vertices[8].z = sp180[2][2]; vertices[8].colour = 0; vertices[9] = vtxtemplate; vertices[9].x = sp180[3][0]; vertices[9].y = sp180[3][1]; vertices[9].z = sp180[3][2]; vertices[9].colour = 0; vertices[8].s = st1; vertices[8].t = st4; vertices[9].s = st3; vertices[9].t = st4; vertices[10] = vtxtemplate; vertices[10].x = sp180[6][0]; vertices[10].y = sp180[6][1]; vertices[10].z = sp180[6][2]; vertices[10].colour = 0; vertices[11] = vtxtemplate; vertices[11].x = sp180[7][0]; vertices[11].y = sp180[7][1]; vertices[11].z = sp180[7][2]; vertices[11].colour = 0; vertices[10].s = st1; vertices[10].t = st2; vertices[11].s = st3; vertices[11].t = st2; gSPVertex(gdl++, osVirtualToPhysical(vertices), 12, 0); gSPTri4(gdl++, 0, 1, 9, 0, 9, 8, 11, 5, 4, 11, 4, 10); for (j = 2; j < 6; j++) { gSPTri2(gdl++, sp104[j][0], sp104[j][1], sp104[j][2], sp104[j][0], sp104[j][2], sp104[j][3]); } } else { if ((prop->type == PROPTYPE_OBJ || prop->type == PROPTYPE_WEAPON || prop->type == PROPTYPE_DOOR) && (prop->obj->flags3 & OBJFLAG3_SHOWSHIELD)) { alpha1 = 0xff; alpha2 = 0xff; } else if (g_Vars.lvframe60 - hit->lvframe60 <= TICKS(80)) { f32 value; value = (hit->lvframe60 - g_Vars.lvframe60 + TICKS(80)) * (PAL ? 3.8636362552643f : 3.1875f); value *= alpha * (1.0f / 255.0f); alpha1 = (s32) value; value = (hit->lvframe60 - g_Vars.lvframe60 + TICKS(80)) * (PAL ? 3.8636362552643f : 3.1875f); value *= alpha * (1.0f / 255.0f); alpha2 = (s32) value; } else { alpha1 = 0; alpha2 = 0; } colours = gfx_allocate_colours(5); gSPColor(gdl++, osVirtualToPhysical(colours), 5); colours[0].r = red1; colours[0].g = green1; colours[0].b = blue1; colours[0].a = alpha1; colours[1].r = red2; colours[1].g = green2; colours[1].b = blue2; colours[1].a = alpha1; colours[2].r = red2; colours[2].g = green2; colours[2].b = blue2; colours[2].a = alpha1; red1 += 100; if (red1 > 255) { red1 = 255; } colours[3].r = red1; green1 += 100; if (green1 > 255) { green1 = 255; } colours[3].g = green1; blue1 += 100; if (blue1 > 255) { blue1 = 255; } colours[3].b = blue1; colours[3].a = alpha2; red2 += 70; if (red2 > 255) { red2 = 255; } colours[4].r = red2; green2 += 70; if (green2 > 255) { green2 = 255; } colours[4].g = green2; blue2 += 70; if (blue2 > 255) { blue2 = 255; } colours[4].b = blue2; colours[4].a = alpha1; vertices = gfx_allocate_vertices(30); for (j = 0; j < ARRAYCOUNT(sp104[side]); j++) { vertices[j] = vtxtemplate; vertices[j].x = sp180[sp104[side][j]][0]; vertices[j].y = sp180[sp104[side][j]][1]; vertices[j].z = sp180[sp104[side][j]][2]; vertices[j].colour = 0; } vertices[0].s = st1; vertices[0].t = st2; vertices[1].s = st3; vertices[1].t = st2; vertices[2].s = st3; vertices[2].t = st4; vertices[3].s = st1; vertices[3].t = st4; vertices[4] = vtxtemplate; if (hit->hitposx == 0x7fff) { vertices[4].x = (vertices[0].x + vertices[1].x + vertices[2].x + vertices[3].x) >> 2; vertices[4].y = (vertices[0].y + vertices[1].y + vertices[2].y + vertices[3].y) >> 2; vertices[4].z = (vertices[0].z + vertices[1].z + vertices[2].z + vertices[3].z) >> 2; } else { vertices[4].x = hit->hitposx; vertices[4].y = hit->hitposy; vertices[4].z = hit->hitposz; } vertices[4].colour = 0xc; vertices[4].s = (st1 + st3) >> 1; vertices[4].t = (st2 + st4) >> 1; gSPVertex(gdl++, osVirtualToPhysical(vertices), 5, 0); gSPTri4(gdl++, 0, 1, 4, 1, 2, 4, 2, 3, 4, 3, 0, 4); vertices += 5; for (j = 0; j < ARRAYCOUNT(sp104[side]); j++) { s32 next = (j + 1) % 4; u32 stack; vertices[0] = vtxtemplate; vertices[0].x = sp180[sp104[side][next]][0]; vertices[0].y = sp180[sp104[side][next]][1]; vertices[0].z = sp180[sp104[side][next]][2]; vertices[0].colour = 4; vertices[1] = vtxtemplate; vertices[1].x = sp180[sp104[side][j]][0]; vertices[1].y = sp180[sp104[side][j]][1]; vertices[1].z = sp180[sp104[side][j]][2]; vertices[1].colour = 4; vertices[2] = vtxtemplate; vertices[2].x = sp180[sp104[side][j]][0]; vertices[2].y = sp180[sp104[side][j]][1]; vertices[2].z = sp180[sp104[side][j]][2]; vertices[2].colour = 8; if (side == 0) { vertices[2].x = xmax; } else if (side == 1) { vertices[2].x = xmin; } else if (side == 2) { vertices[2].y = ymax; } else if (side == 3) { vertices[2].y = ymin; } else if (side == 4) { vertices[2].z = zmax; } else if (side == 5) { vertices[2].z = zmin; } vertices[3] = vtxtemplate; vertices[3].x = sp180[sp104[side][next]][0]; vertices[3].y = sp180[sp104[side][next]][1]; vertices[3].z = sp180[sp104[side][next]][2]; vertices[3].colour = 8; if (side == 0) { vertices[3].x = xmax; } else if (side == 1) { vertices[3].x = xmin; } else if (side == 2) { vertices[3].y = ymax; } else if (side == 3) { vertices[3].y = ymin; } else if (side == 4) { vertices[3].z = zmax; } else if (side == 5) { vertices[3].z = zmin; } vertices[0].s = st1; vertices[0].t = st2; vertices[1].s = st3; vertices[1].t = st2; vertices[2].s = st3; vertices[2].t = st4; vertices[3].s = st1; vertices[3].t = st4; vertices[4] = vtxtemplate; vertices[4].x = (vertices[0].x + vertices[1].x + vertices[2].x + vertices[3].x) >> 2; vertices[4].y = (vertices[0].y + vertices[1].y + vertices[2].y + vertices[3].y) >> 2; vertices[4].z = (vertices[0].z + vertices[1].z + vertices[2].z + vertices[3].z) >> 2; vertices[4].colour = 0x10; vertices[4].s = (st1 + st3) >> 1; vertices[4].t = (st2 + st4) >> 1; gSPVertex(gdl++, osVirtualToPhysical(vertices), 5, 0); gSPTri4(gdl++, 0, 1, 4, 1, 2, 4, 2, 3, 4, 3, 0, 4); vertices += 5; } for (j = 0; j < ARRAYCOUNT(sp104[side]); j++) { vertices[j] = vtxtemplate; vertices[j].x = sp180[sp104[side][3 - j]][0]; vertices[j].y = sp180[sp104[side][3 - j]][1]; vertices[j].z = sp180[sp104[side][3 - j]][2]; vertices[j].colour = 8; if (side == 0) { vertices[j].x = xmax; } else if (side == 1) { vertices[j].x = xmin; } else if (side == 2) { vertices[j].y = ymax; } else if (side == 3) { vertices[j].y = ymin; } else if (side == 4) { vertices[j].z = zmax; } else if (side == 5) { vertices[j].z = zmin; } } vertices[0].s = st1; vertices[0].t = st2; vertices[1].s = st3; vertices[1].t = st2; vertices[2].s = st3; vertices[2].t = st4; vertices[3].s = st1; vertices[3].t = st4; vertices[4] = vtxtemplate; vertices[4].x = (vertices[0].x + vertices[1].x + vertices[2].x + vertices[3].x) >> 2; vertices[4].y = (vertices[0].y + vertices[1].y + vertices[2].y + vertices[3].y) >> 2; vertices[4].z = (vertices[0].z + vertices[1].z + vertices[2].z + vertices[3].z) >> 2; vertices[4].colour = 0x10; vertices[4].s = (st1 + st3) >> 1; vertices[4].t = (st2 + st4) >> 1; gSPVertex(gdl++, osVirtualToPhysical(vertices), 5, 0); gSPTri4(gdl++, 0, 1, 4, 1, 2, 4, 2, 3, 4, 3, 0, 4); } } return gdl; } /** * Render a shield hit. * * Chrs have shield bounding boxes around each bone. For chrs, a shield hit is * rendered around every bone. * * Objects can have a specific model part for the shield area (eg. hoverbed). * If not specified, every bbox node in the object will be used. */ Gfx *shieldhit_render(Gfx *gdl, struct prop *prop1, struct prop *prop2, s32 alpha, bool arg4, s32 cmnum1, s32 cmnum2, s32 cmnum3, s32 cmnum4) { u32 stack[4]; struct model *model; struct modelnode *specificnode = NULL; struct modelnode *node; if (prop2->flags & PROPFLAG_ONTHISSCREENTHISTICK) { // Find the model and specific node if any if (prop2->type == PROPTYPE_CHR || prop2->type == PROPTYPE_PLAYER) { struct chrdata *chr = prop2->chr; model = chr->model; } else { model = prop2->obj->model; specificnode = model_get_part(model->definition, MODELPART_BASIC_SHIELD); } node = model->definition->rootnode; while (node) { if ((node->type & 0xff) == MODELNODETYPE_BBOX) { if (specificnode == NULL || specificnode == node) { struct shieldhit *s0 = NULL; struct shieldhit *s1 = NULL; struct shieldhit *s2 = NULL; s32 index = shieldhit_node_to_cmnum(prop2, node, model, prop1); s32 i; for (i = 0; i < MAX_SHIELDHITS; i++) { struct shieldhit *iter = &g_ShieldHits[i]; if (iter->prop == prop1) { if (iter->model) { if (iter->node == node) { if (s0 == NULL || s0->lvframe60 < iter->lvframe60) { s0 = iter; } } else { if (index < 32 && iter->unk018[index] >= 0 && (s2 == NULL || s2->lvframe60 < iter->lvframe60)) { s2 = iter; } } } else { if (s1 == NULL || s1->lvframe60 < iter->lvframe60) { s1 = iter; } } } } if (s0) { gdl = shieldhit_render_component(gdl, s0, prop1, s0->model, s0->node, s0->side, -1, -1, 255); } else if (s1) { gdl = shieldhit_render_component(gdl, s1, prop1, model, node, -1, -1, -1, 255); } else if (s2) { gdl = shieldhit_render_component(gdl, s2, prop1, model, node, -2, s2->unk018[index], s2->unk038[index], 255); } else { if (arg4) { if (specificnode) { index = 19; } if (index > 19) { index = 0; } gDPPipeSync(gdl++); gDPSetTextureLUT(gdl++, G_TT_NONE); gDPLoadTextureBlock(gdl++, var8009ccc0[index], G_IM_FMT_RGBA, G_IM_SIZ_16b, 16, 16, 0, G_TX_MIRROR | G_TX_WRAP, G_TX_MIRROR | G_TX_WRAP, 4, 4, G_TX_NOLOD, G_TX_NOLOD); gDPSetCycleType(gdl++, G_CYC_1CYCLE); gDPSetRenderMode(gdl++, G_RM_AA_ZB_XLU_SURF, G_RM_AA_ZB_XLU_SURF2); gDPSetCombineMode(gdl++, G_CC_MODULATEI, G_CC_MODULATEI); gSPTexture(gdl++, 0xffff, 0xffff, 0, G_TX_RENDERTILE, G_ON); gDPSetTextureFilter(gdl++, G_TF_BILERP); gDPSetColorDither(gdl++, G_CD_BAYER); gdl = shieldhit_render_component(gdl, NULL, prop1, model, node, -7, -1, -1, 255); } else { if (index == cmnum1) { gdl = shieldhit_render_component(gdl, NULL, prop1, model, node, -3, -1, -1, alpha); } else if (index == cmnum2) { gdl = shieldhit_render_component(gdl, NULL, prop1, model, node, -4, -1, -1, alpha); } else if (index == cmnum3) { gdl = shieldhit_render_component(gdl, NULL, prop1, model, node, -5, -1, -1, alpha); } else if (index == cmnum4) { gdl = shieldhit_render_component(gdl, NULL, prop1, model, node, -6, -1, -1, alpha); } } } } } if (node->child) { node = node->child; } else { while (node) { if (node->next) { node = node->next; break; } node = node->parent; } } } if (specificnode == NULL) { struct prop *child; for (child = prop2->child; child != NULL; child = child->next) { gdl = shieldhit_render(gdl, prop1, child, alpha, arg4, cmnum1, cmnum2, cmnum3, cmnum4); } } } return gdl; } /** * Render the cloak transition effect for a chr prop and any attached * child props such as held weapons or embedded knives. * * The function is initially called with both prop arguments being the chr prop. * The function iterates the child props and calls itself recursively, setting * thisprop to the current child being iterated. */ Gfx *chr_render_cloak(Gfx *gdl, struct prop *chrprop, struct prop *thisprop) { struct model *model; struct modelnode *bbox = NULL; struct modelnode *node; if (thisprop->flags & PROPFLAG_ONTHISSCREENTHISTICK) { if (thisprop->type == PROPTYPE_CHR || thisprop->type == PROPTYPE_PLAYER) { model = thisprop->chr->model; } else { model = thisprop->obj->model; bbox = model_get_part(model->definition, MODELPART_BASIC_SHIELD); } if (thisprop->parent == NULL) { // Rendering the chr prop - configure renderer gDPPipeSync(gdl++); gDPSetScissor(gdl++, G_SC_NON_INTERLACE, 0, 0, 16, 16); gDPSetCycleType(gdl++, G_CYC_COPY); gDPSetTile(gdl++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 0, 0x0000, 5, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOLOD); gDPSetTile(gdl++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 0, 0x0080, 4, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOLOD); gDPSetTile(gdl++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 160, 0x0000, G_TX_RENDERTILE, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOLOD); gDPSetTile(gdl++, G_IM_FMT_I, G_IM_SIZ_8b, 160, 0x0080, 1, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, 15); gSPTexture(gdl++, 0xffff, 0xffff, 0, G_TX_RENDERTILE, G_ON); gDPSetEnvColor(gdl++, 0xff, 0xff, 0xff, 0xff); gDPSetPrimColor(gdl++, 0, 0, 0xff, 0xff, 0xff, 0xff); gDPSetRenderMode(gdl++, G_RM_NOOP, G_RM_NOOP2); gDPSetCombineMode(gdl++, G_CC_DECALRGBA, G_CC_DECALRGBA); gDPSetTextureFilter(gdl++, G_TF_POINT); gDPSetTexturePersp(gdl++, G_TP_NONE); gDPSetColorDither(gdl++, G_CD_DISABLE); gDPSetAlphaDither(gdl++, G_AD_DISABLE); gDPSetTextureLOD(gdl++, G_TL_TILE); gDPSetTextureDetail(gdl++, G_TD_CLAMP); gDPSetTextureLUT(gdl++, G_TT_NONE); gDPSetAlphaCompare(gdl++, G_AC_NONE); gSPClearGeometryMode(gdl++, G_ZBUFFER); } // Iterate nodes in the prop and render each node = model->definition->rootnode; while (node) { if ((node->type & 0xff) == MODELNODETYPE_BBOX) { if (bbox == NULL || node == bbox) { s32 index = shieldhit_node_to_cmnum(thisprop, node, model, chrprop); if (bbox) { index = 19; } if (index <= 19) { Mtxf *mtx = model_find_node_mtx(model, model_node_find_mtx_node(node), 0); s32 uls; // upper left s coordinate s32 ult; // upper left t coordinate struct coord coord; f32 screenpos[2]; s32 lrs; // lower right s coordinate s32 lrt; // lower right t coordinate coord.x = mtx->m[3][0]; coord.y = mtx->m[3][1]; coord.z = mtx->m[3][2]; cam0f0b4d68(&coord, screenpos); if (screenpos[0] < 0.0f) { screenpos[0] = 0.0f; } if (screenpos[0] > vi_get_width()) { screenpos[0] = vi_get_width(); } if (screenpos[1] < 0.0f) { screenpos[1] = 0.0f; } if (screenpos[1] > vi_get_height()) { screenpos[1] = vi_get_height(); } uls = (s32)screenpos[0] - 8; ult = (s32)screenpos[1] - 8; if (uls < vi_get_view_left()) { uls = vi_get_view_left(); } if (uls > vi_get_view_left() + vi_get_view_width() - 16) { uls = vi_get_view_left() + vi_get_view_width() - 16; } if (ult < vi_get_view_top()) { ult = vi_get_view_top(); } if (ult > vi_get_view_top() + vi_get_view_height() - 16) { ult = vi_get_view_top() + vi_get_view_height() - 16; } lrs = uls + 15; lrt = ult + 15; gDPSetColorImage(gdl++, G_IM_FMT_RGBA, G_IM_SIZ_16b, 16, OS_K0_TO_PHYSICAL(var8009ccc0[index])); gDPTileSync(gdl++); gDPLoadTextureTile(gdl++, vi_get_back_buffer(), G_IM_FMT_RGBA, G_IM_SIZ_16b, vi_get_width(), 0, uls, ult, lrs, lrt, 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, 4, 4, G_TX_NOLOD, G_TX_NOLOD); gDPPipeSync(gdl++); gSPTextureRectangle(gdl++, 0, 0, 60, 60, G_TX_RENDERTILE, 0, 0, 4096, 1024); } } } if (node->child) { node = node->child; } else { while (node) { if (node->next) { node = node->next; break; } node = node->parent; } } } // Render child props if (bbox == NULL) { struct prop *child; for (child = thisprop->child; child != NULL; child = child->next) { gdl = chr_render_cloak(gdl, chrprop, child); } } if (thisprop->parent == NULL) { // Back in the chr prop - reconfigure the renderer for normal use gDPPipeSync(gdl++); gDPLoadSync(gdl++); gDPTileSync(gdl++); gDPSetColorImage(gdl++, G_IM_FMT_RGBA, G_IM_SIZ_16b, vi_get_buf_width(), OS_K0_TO_PHYSICAL(vi_get_back_buffer())); gDPSetScissor(gdl++, G_SC_NON_INTERLACE, 0, 0, vi_get_width(), vi_get_height()); gDPSetCycleType(gdl++, G_CYC_1CYCLE); gDPSetRenderMode(gdl++, G_RM_AA_ZB_OPA_SURF, G_RM_AA_ZB_OPA_SURF2); gDPSetCombineMode(gdl++, G_CC_MODULATEI, G_CC_MODULATEI); gDPSetTextureFilter(gdl++, G_TF_BILERP); gDPSetTexturePersp(gdl++, G_TP_PERSP); gSPSetGeometryMode(gdl++, G_ZBUFFER); } } return gdl; } Gfx *chr_render_shield(Gfx *gdl, struct chrdata *chr, u32 alpha) { if (chr_get_shield(chr) > 0 && g_Vars.lvupdate240 > 0) { chr->cmcount++; if (chr->cmcount > 300) { chr->cmcount = 0; } } if ((chr->hidden2 & CHRH2FLAG_SHIELDHIT) || (chr_get_shield(chr) > 0 && chr->cmcount < 10) || (chr->cloakfadefrac > 0 && !chr->cloakfadefinished)) { if (chr_get_shield(chr) > 0 && g_Vars.lvupdate240 > 0) { s32 numiterations = (random() % 4) + 1; s32 newcmnum = chr->cmnum2; s32 candidate; s8 operation = 0; s8 again = true; s32 i; for (i = 0; i <= numiterations; ) { if (operation == 0) { candidate = shieldhit_find_parentnode_cmnum(chr->prop, chr->cmnum); operation = 1; if (candidate >= 0) { if (candidate != chr->cmnum2) { newcmnum = candidate; } i++; } else { again = false; } } else if (operation == 1) { candidate = shieldhit_find_childnode_cmnum(chr->prop, chr->cmnum); if (candidate >= 0) { operation = 2; if (candidate != chr->cmnum2) { newcmnum = candidate; } i++; } else { if (again) { operation = 0; } else { break; } } } else if (operation == 2) { candidate = shieldhit_find_anynode_cmnum(chr->prop, candidate); if (candidate >= 0) { if (candidate != chr->cmnum2) { newcmnum = candidate; } i++; } else { operation = 0; } } } chr->cmnum4 = chr->cmnum3; chr->cmnum3 = chr->cmnum2; chr->cmnum2 = chr->cmnum; chr->cmnum = newcmnum; } gSPSetGeometryMode(gdl++, G_CULL_BACK); gdl = shieldhit_render(gdl, chr->prop, chr->prop, alpha, chr->cloakfadefrac > 0 && !chr->cloakfadefinished, chr->cmnum, chr->cmnum2, chr->cmnum3, chr->cmnum4); gSPSetGeometryMode(gdl++, G_CULL_BACK); } return gdl; } /** * This ticks the shield damage effect when you shoot a shielded chr. */ void shieldhits_tick(void) { s32 index; bool changed = false; s32 time60; s32 i; s32 j; if (g_ShieldHitActive) { for (i = 0; i < MAX_SHIELDHITS; i++) { if (g_ShieldHits[i].prop) { if (g_ShieldHits[i].lvframe60 >= g_Vars.lvframe60 - TICKS(80)) { changed = true; g_ShieldHits[i].shield += (shieldhit_get_health(&g_ShieldHits[i].prop) - g_ShieldHits[i].shield) * g_Vars.lvupdate60f * (PAL ? 0.0151515156f : 0.0125f); } for (j = 0; j < 32; j++) { if (g_ShieldHits[i].unk018[j] >= 0) { changed = true; time60 = g_ShieldHits[i].unk018[j] + g_Vars.lvupdate60; if (g_ShieldHits[i].unk018[j] < 1 && time60 > 0) { index = shieldhit_find_parentnode_cmnum(g_ShieldHits[i].prop, j); if (index >= 0 && index < 32) { if (g_ShieldHits[i].unk018[index] == -1) { g_ShieldHits[i].unk018[index] = -3; g_ShieldHits[i].unk038[index] = g_ShieldHits[i].unk038[j] + 1; } } index = shieldhit_find_childnode_cmnum(g_ShieldHits[i].prop, j); while (index >= 0) { if (index < 32) { if (g_ShieldHits[i].unk018[index] == -1) { g_ShieldHits[i].unk018[index] = -3; g_ShieldHits[i].unk038[index] = g_ShieldHits[i].unk038[j] + 1; } } index = shieldhit_find_anynode_cmnum(g_ShieldHits[i].prop, index); } } if (time60 < TICKS(30)) { g_ShieldHits[i].unk018[j] = time60; } else { g_ShieldHits[i].unk018[j] = -2; } } } for (j = 0; j < 32; j++) { if (g_ShieldHits[i].unk018[j] == -3) { g_ShieldHits[i].unk018[j] = 0; } } if (!changed) { shieldhit_remove(&g_ShieldHits[i]); } } } } } void chr_set_drcaroll_images(struct chrdata *drcaroll, s32 imageleft, s32 imageright) { if (drcaroll && imageleft >= DRCAROLLIMAGE_EYESDEFAULT && imageleft <= DRCAROLLIMAGE_BINARY && imageright >= DRCAROLLIMAGE_EYESDEFAULT && imageright <= DRCAROLLIMAGE_BINARY) { struct model *model = drcaroll->model; struct modelnode *nodes[2]; union modelrwdata *rwdata; s32 i; s32 j; u32 stack; // Iterate model parts relating to images // Parts 0-5 are the left image // Parts 6-11 are the right image for (i = 0; i < 6; i++) { nodes[0] = model_get_part(model->definition, i); nodes[1] = model_get_part(model->definition, i + 6); for (j = 0; j < ARRAYCOUNT(nodes); j++) { if (nodes[j]) { rwdata = model_get_node_rw_data(model, nodes[j]); if (j == 0) { rwdata->toggle.visible = (imageleft == i) ? true : false; } else { rwdata->toggle.visible = (imageright == i) ? true : false; } } } } } }