perfect_dark/src/game/body.c

792 lines
18 KiB
C

#include <ultra64.h>
#include "constants.h"
#include "game/cheats.h"
#include "game/chraction.h"
#include "game/chr.h"
#include "game/body.h"
#include "game/prop.h"
#include "game/atan2f.h"
#include "game/modelmgr.h"
#include "game/lv.h"
#include "game/modeldef.h"
#include "game/mplayer/mplayer.h"
#include "game/pad.h"
#include "game/propobj.h"
#include "bss.h"
#include "lib/memp.h"
#include "lib/model.h"
#include "lib/mema.h"
#include "lib/rng.h"
#include "lib/mtx.h"
#include "lib/ailist.h"
#include "lib/anim.h"
#include "lib/collision.h"
#include "data.h"
#include "types.h"
s32 g_NumActiveHeadsPerGender;
u32 var8009cd24;
s32 g_ActiveMaleHeads[8];
s32 g_ActiveFemaleHeads[8];
s32 g_NumBondBodies = 0;
s32 g_NumMaleGuardHeads = 0;
s32 g_NumFemaleGuardHeads = 0;
s32 g_NumMaleGuardTeamHeads = 0;
s32 g_NumFemaleGuardTeamHeads = 0;
s32 var80062b14 = 0;
s32 var80062b18 = 0;
s32 g_BondBodies[] = {
BODY_DJBOND,
BODY_CONNERY,
BODY_DALTON,
BODY_MOORE,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
};
s32 g_MaleGuardHeads[] = {
HEAD_BEAU1,
HEAD_CHRIST,
HEAD_DARLING,
HEAD_JON,
HEAD_ROSS,
HEAD_RUSS,
HEAD_MARK2,
HEAD_JAMIE,
HEAD_DUNCAN2,
HEAD_BRIAN,
HEAD_STEVEM,
HEAD_KEITH,
HEAD_GRANT,
HEAD_PENNY,
HEAD_DAVEC,
HEAD_JONES,
HEAD_GRAHAM,
HEAD_SHAUN,
HEAD_NEIL2,
HEAD_EDMCG,
HEAD_MATT_C,
HEAD_PEER_S,
HEAD_ANDY_R,
HEAD_BEN_R,
HEAD_STEVE_K,
HEAD_SCOTT_H,
HEAD_SANCHEZ,
HEAD_COOK,
HEAD_PRYCE,
HEAD_SILKE,
HEAD_SMITH,
HEAD_GARETH,
HEAD_MURCHIE,
HEAD_WONG,
HEAD_CARTER,
HEAD_TINTIN,
HEAD_MUNTON,
HEAD_PHELPS,
HEAD_KEN,
HEAD_JOEL,
HEAD_TIM,
HEAD_ROBIN,
-1,
};
s32 g_MaleGuardTeamHeads[] = {
HEAD_BEAU1,
HEAD_CHRIST,
HEAD_DARLING,
HEAD_JON,
HEAD_ROSS,
HEAD_RUSS,
HEAD_MARK2,
HEAD_JAMIE,
HEAD_DUNCAN2,
HEAD_BRIAN,
HEAD_STEVEM,
HEAD_KEITH,
HEAD_GRANT,
HEAD_PENNY,
HEAD_DAVEC,
HEAD_JONES,
-1,
};
s32 g_FemaleGuardHeads[] = {
HEAD_LESLIE_S,
HEAD_ANKA,
HEAD_EILEEN_T,
HEAD_EILEEN_H,
-1,
};
s32 g_FemaleGuardTeamHeads[] = {
HEAD_LESLIE_S,
HEAD_ANKA,
HEAD_EILEEN_T,
HEAD_EILEEN_H,
-1,
};
s32 var80062c80 = 0;
s32 g_ActiveMaleHeadsIndex = 0;
s32 g_ActiveFemaleHeadsIndex = 0;
s32 g_FemGuardHeads[3] = {
HEAD_ALEX,
HEAD_JULIANNE,
HEAD_LAURA,
};
u32 bodyGetRace(s32 bodynum)
{
switch (bodynum) {
case BODY_SKEDAR:
case BODY_MINISKEDAR:
case BODY_SKEDARKING:
return RACE_SKEDAR;
case BODY_DRCAROLL:
return RACE_DRCAROLL;
case BODY_EYESPY:
return RACE_EYESPY;
case BODY_CHICROB:
return RACE_ROBOT;
}
return RACE_HUMAN;
}
bool bodyLoad(s32 bodynum)
{
if (!g_HeadsAndBodies[bodynum].modeldef) {
g_HeadsAndBodies[bodynum].modeldef = modeldefLoadToNew(g_HeadsAndBodies[bodynum].filenum);
return true;
}
return false;
}
struct model *body0f02ce8c(s32 bodynum, s32 headnum, struct modeldef *bodymodeldef, struct modeldef *headmodeldef, bool sunglasses, struct model *model, bool isplayer, u8 varyheight)
{
f32 scale = g_HeadsAndBodies[bodynum].scale * 0.10000001f;
f32 animscale = g_HeadsAndBodies[bodynum].animscale;
struct modelnode *node = NULL;
u32 stack[2];
if (cheatIsActive(CHEAT_DKMODE)) {
scale *= 0.8f;
}
if (bodymodeldef == NULL) {
if (g_HeadsAndBodies[bodynum].modeldef == NULL) {
g_HeadsAndBodies[bodynum].modeldef = modeldefLoadToNew(g_HeadsAndBodies[bodynum].filenum);
}
bodymodeldef = g_HeadsAndBodies[bodynum].modeldef;
}
modelAllocateRwData(bodymodeldef);
if (!g_HeadsAndBodies[bodynum].unk00_01) {
if (bodymodeldef->skel == &g_SkelChr) {
node = modelGetPart(bodymodeldef, MODELPART_CHR_HEADSPOT);
if (node != NULL) {
if (headnum < 0) {
headmodeldef = func0f18e57c(-1 - headnum, &headnum);
bodymodeldef->rwdatalen += headmodeldef->rwdatalen;
} else if (headnum > 0) {
if (headmodeldef == NULL) {
if (g_Vars.normmplayerisrunning && !IS4MB()) {
headmodeldef = modeldefLoadToNew(g_HeadsAndBodies[headnum].filenum);
g_HeadsAndBodies[headnum].modeldef = headmodeldef;
g_FileInfo[g_HeadsAndBodies[headnum].filenum].loadedsize = 0;
bodyCalculateHeadOffset(headmodeldef, headnum, bodynum);
} else {
if (g_HeadsAndBodies[headnum].modeldef == NULL) {
g_HeadsAndBodies[headnum].modeldef = modeldefLoadToNew(g_HeadsAndBodies[headnum].filenum);
}
headmodeldef = g_HeadsAndBodies[headnum].modeldef;
}
}
modelAllocateRwData(headmodeldef);
bodymodeldef->rwdatalen += headmodeldef->rwdatalen;
if (g_HeadsAndBodies[bodynum].canvaryheight && varyheight) {
// Set height to between 95% and 115%
f32 frac = RANDOMFRAC() * 0.05f;
scale *= 2.0f * frac - 0.05f + 1.0f;
}
}
if (!isplayer) {
if (cheatIsActive(CHEAT_SMALLCHARACTERS)) {
scale *= 0.4f;
}
if (cheatIsActive(CHEAT_DKMODE)) {
scale *= 1.25f;
}
} else {
if (cheatIsActive(CHEAT_SMALLJO)) {
scale *= 0.4f;
}
}
}
} else if (bodymodeldef->skel == &g_SkelSkedar) {
if (g_HeadsAndBodies[bodynum].canvaryheight && varyheight && bodynum == BODY_SKEDAR) {
// Set height to between 65% and 85%
f32 frac = RANDOMFRAC();
scale *= 2.0f * (0.1f * frac) - 0.1f + 0.75f;
}
if (1);
}
}
if (model) {
if (model->rwdatalen < bodymodeldef->rwdatalen);
} else {
model = modelmgrInstantiateModelWithAnim(bodymodeldef);
}
if (model) {
modelSetScale(model, scale);
modelSetAnimScale(model, animscale);
if (headmodeldef && !g_HeadsAndBodies[bodynum].unk00_01) {
bodymodeldef->rwdatalen -= headmodeldef->rwdatalen;
modelmgrAttachHead(model, node, headmodeldef);
if ((s16)*(s32 *)&headmodeldef->skel == SKEL_HEAD) {
struct modelnode *node2;
if (!sunglasses) {
node2 = modelGetPart(headmodeldef, MODELPART_HEAD_SUNGLASSES);
if (node2) {
union modelrwdata *rwdata = modelGetNodeRwData(model, node2);
rwdata->toggle.visible = false;
}
}
node2 = modelGetPart(headmodeldef, MODELPART_HEAD_HUDPIECE);
if (node2) {
union modelrwdata *rwdata = modelGetNodeRwData(model, node2);
rwdata->toggle.visible = false;
}
}
}
}
return model;
}
struct model *body0f02d338(s32 bodynum, s32 headnum, struct modeldef *bodymodeldef, struct modeldef *headmodeldef, bool sunglasses, u8 varyheight)
{
return body0f02ce8c(bodynum, headnum, bodymodeldef, headmodeldef, sunglasses, NULL, false, varyheight);
}
struct model *bodyAllocateModel(s32 bodynum, s32 headnum, u32 spawnflags)
{
bool sunglasses = false;
u8 varyheight = true;
if (spawnflags & SPAWNFLAG_FORCESUNGLASSES) {
sunglasses = true;
} else if (spawnflags & SPAWNFLAG_MAYBESUNGLASSES) {
sunglasses = random() % 2 == 0;
}
if (spawnflags & SPAWNFLAG_FIXEDHEIGHT) {
varyheight = false;
}
return body0f02d338(bodynum, headnum, NULL, NULL, sunglasses, varyheight);
}
s32 body0f02d3f8(void)
{
return g_BondBodies[var80062c80];
}
s32 bodyChooseHead(s32 bodynum)
{
s32 head;
if (g_HeadsAndBodies[bodynum].ismale) {
head = g_ActiveMaleHeads[g_ActiveMaleHeadsIndex++];
if (g_ActiveMaleHeadsIndex == g_NumActiveHeadsPerGender) {
g_ActiveMaleHeadsIndex = 0;
}
} else if (bodynum == BODY_FEM_GUARD) {
head = g_FemGuardHeads[random() % 3];
} else {
head = g_ActiveFemaleHeads[g_ActiveFemaleHeadsIndex++];
if (g_ActiveFemaleHeadsIndex == g_NumActiveHeadsPerGender) {
g_ActiveFemaleHeadsIndex = 0;
}
}
return head;
}
/**
* Read a "packed" chr definition and create a runtime chr from it.
*
* Chr definitions are stored in a packed format in each stage's setup file.
* The packed format is used for space saving reasons.
*/
void bodyAllocateChr(s32 stagenum, struct packedchr *packed, s32 cmdindex)
{
struct pad pad;
s16 rooms[2];
struct chrdata *chr;
struct modeldef *headmodeldef;
struct model *model;
struct prop *prop;
s32 bodynum;
s32 headnum;
f32 angle;
s32 index;
padUnpack(packed->padnum, PADFIELD_POS | PADFIELD_LOOK | PADFIELD_ROOM, &pad);
rooms[0] = pad.room;
rooms[1] = -1;
if (cdTestVolume(&pad.pos, 20, rooms, CDTYPE_ALL, CHECKVERTICAL_YES, 200, -200) == CDRESULT_COLLISION
&& packed->chair == -1
&& (packed->spawnflags & SPAWNFLAG_IGNORECOLLISION) == 0) {
return;
}
if (packed->spawnflags & (SPAWNFLAG_ONLYONA | SPAWNFLAG_ONLYONSA | SPAWNFLAG_ONLYONPA)) {
if ((packed->spawnflags & (SPAWNFLAG_ONLYONA | SPAWNFLAG_ONLYONSA | SPAWNFLAG_ONLYONPA)) == 0) {
return;
}
if (((packed->spawnflags & SPAWNFLAG_ONLYONA) && lvGetDifficulty() == DIFF_A)
|| ((packed->spawnflags & SPAWNFLAG_ONLYONSA) && lvGetDifficulty() == DIFF_SA)
|| ((packed->spawnflags & SPAWNFLAG_ONLYONPA) && lvGetDifficulty() == DIFF_PA)) {
// ok
} else {
return;
}
}
headnum = -55555;
headmodeldef = NULL;
if (packed->bodynum == 255) {
bodynum = body0f02d3f8();
} else {
bodynum = packed->bodynum;
}
if (!g_HeadsAndBodies[bodynum].unk00_01) {
if (packed->headnum >= 0) {
headnum = packed->headnum;
} else if (headnum == -55555) {
headnum = bodyChooseHead(bodynum);
}
}
if (headnum < 0) {
index = -1 - headnum;
if (index >= 0 && index < 22) {
headmodeldef = func0f18e57c(index, &headnum);
}
model = body0f02ce8c(bodynum, headnum, NULL, headmodeldef, false, NULL, false, false);
} else {
model = bodyAllocateModel(bodynum, headnum, packed->spawnflags);
}
if (model != NULL) {
angle = atan2f(pad.look.x, pad.look.z);
prop = chrAllocate(model, &pad.pos, rooms, angle, ailistFindById(packed->ailistnum));
if (prop != NULL) {
propActivate(prop);
propEnable(prop);
chr = prop->chr;
chrSetChrnum(chr, packed->chrnum);
chr->hearingscale = packed->hearscale / 1000.0f;
chr->visionrange = packed->viewdist;
chr->padpreset1 = packed->padpreset;
chr->chrpreset1 = packed->chrpreset;
chr->headnum = headnum;
chr->bodynum = bodynum;
chr->race = bodyGetRace(chr->bodynum);
chr->rtracked = false;
if (bodynum == BODY_DRCAROLL) {
chr->drcarollimage_left = 0;
chr->drcarollimage_right = 0;
chr->height = 185;
chr->radius = 30;
} else if (bodynum == BODY_CHICROB) {
chr->unk348[0] = mempAlloc(sizeof(struct fireslotthing), MEMPOOL_STAGE);
chr->unk348[1] = mempAlloc(sizeof(struct fireslotthing), MEMPOOL_STAGE);
chr->unk348[0]->beam = mempAlloc(ALIGN16(sizeof(struct beam)), MEMPOOL_STAGE);
chr->unk348[1]->beam = mempAlloc(ALIGN16(sizeof(struct beam)), MEMPOOL_STAGE);
chr->unk348[0]->beam->age = -1;
chr->unk348[1]->beam->age = -1;
chr->height = 200;
chr->radius = 42;
}
if (packed->spawnflags & SPAWNFLAG_INVINCIBLE) {
chr->chrflags |= CHRCFLAG_INVINCIBLE;
}
if (packed->spawnflags & SPAWNFLAG_BASICGUARD) {
chr->hidden |= CHRHFLAG_BASICGUARD;
}
if (packed->spawnflags & SPAWNFLAG_ANTINONINTERACTABLE) {
chr->hidden |= CHRHFLAG_ANTINONINTERACTABLE;
}
if (packed->spawnflags & SPAWNFLAG_DONTSHOOTME) {
chr->hidden |= CHRHFLAG_DONTSHOOTME;
}
if (packed->spawnflags & SPAWNFLAG_HIDDEN) {
chr->chrflags |= CHRCFLAG_HIDDEN;
}
if (packed->spawnflags & SPAWNFLAG_RTRACKED) {
chr->rtracked = true;
}
if (packed->spawnflags & SPAWNFLAG_NOBLOOD) {
chr->noblood = true;
}
if (packed->spawnflags & SPAWNFLAG_BLUESIGHT) {
chr->hidden2 |= CHRH2FLAG_BLUESIGHT;
}
chr->flags = packed->flags;
chr->flags2 = packed->flags2;
if (cheatIsActive(CHEAT_MARQUIS)) {
chr->flags2 &= ~CHRFLAG1_NOHANDCOMBAT;
chr->flags2 |= CHRFLAG1_HANDCOMBATONLY;
}
chr->team = packed->team;
chr->squadron = packed->squadron;
chr->aibot = NULL;
if (packed->tude != 4) {
chr->tude = packed->tude;
} else {
chr->tude = random() % 4;
}
chr->voicebox = random() % 3;
if (!g_HeadsAndBodies[chr->bodynum].ismale) {
chr->voicebox = VOICEBOX_FEMALE;
}
chr->naturalanim = packed->naturalanim;
chr->myspecial = packed->chair;
chr->yvisang = packed->yvisang;
packed->chrindex = chr - g_ChrSlots;
chr->teamscandist = packed->teamscandist;
chr->convtalk = packed->convtalk;
if (chr->flags & CHRFLAG0_CAN_HEARSPAWN) {
chr->chrflags |= CHRCFLAG_CLONEABLE;
}
if (!g_Vars.normmplayerisrunning && g_MissionConfig.iscoop && g_Vars.numaibuddies > 0) {
chr->flags |= CHRFLAG0_AIVSAI;
}
if (random() % 5 == 0) {
// Make chr punch slower
chr->flags2 |= CHRFLAG1_ADJUSTPUNCHSPEED;
}
if (CHRRACE(chr) == RACE_SKEDAR) {
chr->chrflags |= CHRCFLAG_FORCEAUTOAIM;
}
}
}
}
struct prop *bodyAllocateEyespy(struct pad *pad, s16 room)
{
s16 rooms[2];
struct prop *prop;
struct chrdata *chr;
struct model *model;
s32 inlift;
struct prop *lift;
f32 ground;
rooms[0] = room;
rooms[1] = -1;
#if PIRACYCHECKS
{
u32 stack[2];
u32 checksum = 0;
s32 *ptr = (s32 *)&lvReset;
s32 *end = (s32 *)&lvConfigureFade;
while (ptr < end) {
checksum <<= 1;
checksum ^= *ptr;
ptr++;
}
if (checksum != CHECKSUM_PLACEHOLDER) {
s32 *ptr2 = (s32 *)_memaFree;
s32 *end2 = (s32 *)memaInit;
while (ptr2 < end2) {
ptr2[0] = 0;
ptr2++;
}
}
}
#endif
model = bodyAllocateModel(BODY_EYESPY, 0, 0);
if (model) {
prop = chrAllocate(model, &pad->pos, rooms, 0, ailistFindById(GAILIST_IDLE));
if (prop) {
propActivate(prop);
propEnable(prop);
chr = prop->chr;
chrSetChrnum(chr, chrsGetNextUnusedChrnum());
chr->bodynum = BODY_EYESPY;
chr->padpreset1 = 0;
chr->chrpreset1 = 0;
chr->headnum = 0;
chr->hearingscale = 0;
chr->visionrange = 0;
chr->race = bodyGetRace(chr->bodynum);
ground = cdFindGroundInfoAtCyl(&pad->pos, 30, rooms, NULL, NULL, NULL, NULL, &inlift, &lift);
chr->ground = ground;
chr->manground = ground;
chr->flags = 0;
chr->flags2 = 0;
chr->team = 0;
chr->squadron = 0;
chr->maxdamage = 2;
chr->tude = random() & 3;
chr->voicebox = random() % 3;
chr->naturalanim = 0;
chr->myspecial = 0;
chr->yvisang = 0;
chr->teamscandist = 0;
chr->convtalk = 0;
chr->radius = 26;
chr->height = 200;
func0f02e9a0(chr, 0);
chr->chrflags |= CHRCFLAG_HIDDEN;
#if VERSION >= VERSION_NTSC_1_0
chr->hidden2 |= CHRH2FLAG_CONSIDERPROXIES;
#else
chr->hidden |= CHRHFLAG_CONSIDERPROXIES;
#endif
return prop;
}
}
return NULL;
}
void body0f02ddbf(void)
{
// empty
}
/**
* Tweak the head's Y offset to suit the body.
*
* By default, heads and their matching bodies align perfectly and don't need
* any tweaking. This function is used in multiplayer where players can put any
* heads on any bodies.
*/
void bodyCalculateHeadOffset(struct modeldef *headmodeldef, s32 headnum, s32 bodynum)
{
struct modelnode *node;
struct modelnode *prev;
Gfx *gdl;
s32 offset;
struct modelrodata_bbox *bbox;
s32 i;
#if VERSION >= VERSION_JPN_FINAL
offset = 0;
switch (headnum) {
case HEAD_DARK_COMBAT:
case HEAD_DARK_FROCK:
case HEAD_DARKAQUA:
case HEAD_DARK_SNOW:
switch (bodynum) {
case BODY_DARK_COMBAT:
case BODY_DARK_FROCK:
case BODY_DARK_TRENCH:
case BODY_DARK_RIPPED:
case BODY_DARK_AF1:
case BODY_DARKWET:
case BODY_DARKAQUALUNG:
case BODY_DARKSNOW:
case BODY_DARKLAB:
case BODY_DARK_LEATHER:
case BODY_DARK_NEGOTIATOR:
break;
default:
offset = -12;
break;
}
break;
}
#endif
if ((s16)(*(s32 *)&headmodeldef->skel) == SKEL_HEAD) {
#if VERSION >= VERSION_JPN_FINAL
if (g_HeadsAndBodies[headnum].type == g_HeadsAndBodies[bodynum].type && offset == 0) {
return;
}
#else
if (g_HeadsAndBodies[headnum].type == g_HeadsAndBodies[bodynum].type) {
return;
}
#endif
#if VERSION >= VERSION_JPN_FINAL
switch (g_HeadsAndBodies[headnum].type) {
default:
case HEADBODYTYPE_FEMALE:
offset += 0;
break;
case HEADBODYTYPE_MAIAN:
offset += 0;
break;
case HEADBODYTYPE_DEFAULT:
offset -= 35;
break;
case HEADBODYTYPE_MRBLONDE:
offset += 0;
break;
case HEADBODYTYPE_CASS:
offset -= 20;
break;
case HEADBODYTYPE_FEMALEGUARD:
offset -= 40;
break;
}
#else
// Same as JPN, but sets the value rather than adjusts
switch (g_HeadsAndBodies[headnum].type) {
default:
case HEADBODYTYPE_FEMALE:
offset = 0;
break;
case HEADBODYTYPE_MAIAN:
offset = 0;
break;
case HEADBODYTYPE_DEFAULT:
offset = -35;
break;
case HEADBODYTYPE_MRBLONDE:
offset = 0;
break;
case HEADBODYTYPE_CASS:
offset = -20;
break;
case HEADBODYTYPE_FEMALEGUARD:
offset = -40;
break;
}
#endif
switch (g_HeadsAndBodies[bodynum].type) {
case HEADBODYTYPE_FEMALE:
break;
case HEADBODYTYPE_MAIAN:
offset -= 30;
break;
case HEADBODYTYPE_DEFAULT:
offset += 35;
break;
case HEADBODYTYPE_MRBLONDE:
break;
case HEADBODYTYPE_CASS:
offset += 20;
break;
case HEADBODYTYPE_FEMALEGUARD:
offset += 40;
break;
}
if (g_HeadsAndBodies[bodynum].type == HEADBODYTYPE_FEMALE) {
if (g_HeadsAndBodies[headnum].type == HEADBODYTYPE_DEFAULT
|| g_HeadsAndBodies[headnum].type == HEADBODYTYPE_MRBLONDE) {
offset -= 10;
} else if (g_HeadsAndBodies[headnum].type == HEADBODYTYPE_CASS
|| g_HeadsAndBodies[headnum].type == HEADBODYTYPE_FEMALEGUARD) {
offset -= 5;
}
} else if (g_HeadsAndBodies[bodynum].type == HEADBODYTYPE_CASS
&& (g_HeadsAndBodies[headnum].type == HEADBODYTYPE_DEFAULT
|| g_HeadsAndBodies[headnum].type == HEADBODYTYPE_MRBLONDE)) {
offset -= 5;
}
// Apply the offset
if (offset != 0) {
node = NULL;
do {
prev = node;
modelIterateDisplayLists(headmodeldef, &node, &gdl);
if (node && node != prev && node->type == MODELNODETYPE_DL) {
struct modelrodata_dl *rodata = &node->rodata->dl;
for (i = 0; i < rodata->numvertices; i++) {
rodata->vertices[i].y += offset;
}
}
} while (node);
bbox = modeldefFindBboxRodata(headmodeldef);
if (bbox != NULL) {
bbox->ymin += offset;
bbox->ymax += offset;
}
}
}
}