Implement exploding heads
This commit is contained in:
parent
51442c4db1
commit
ca226144f9
2
Makefile
2
Makefile
|
@ -22,7 +22,7 @@ ROMID ?= ntsc-final
|
|||
#
|
||||
# If enabled, all further options are ignored and are set automatically.
|
||||
|
||||
MATCHING = 1
|
||||
MATCHING = 0
|
||||
|
||||
# COMPILER - Choose which compiler to use.
|
||||
#
|
||||
|
|
63
README.md
63
README.md
|
@ -1,62 +1,5 @@
|
|||
# Perfect Dark Decompilation
|
||||
# Perfect Dark - Exploding Heads
|
||||
|
||||
This repository contains a complete decompilation of Perfect Dark for the Nintendo 64.
|
||||

|
||||
|
||||
The project aims to be a matching decompilation. When a matching decompilation is compiled with the same compiler that the original developers used, the output will be exactly the same as the retail game, byte for byte.
|
||||
|
||||
To build the project, you must already have a Perfect Dark ROM. The project can build the exact same ROM from decomp's source code combined with assets that it extracts from your base ROM.
|
||||
|
||||
## Status
|
||||
|
||||
See the [Perfect Dark Decompilation Status Page](https://ryandwyer.gitlab.io/pdstatus/).
|
||||
|
||||
The ntsc-1.0 and ntsc-final versions are fully decompiled, but a small handful of functions are not yet byte-matching even though they are functionally the same. The status page doesn't show these as 100% because it counts matching functions only.
|
||||
|
||||
## Installation Requirements
|
||||
|
||||
For Arch Linux:
|
||||
|
||||
* Install these packages: `binutils fakeroot gcc make python vim`
|
||||
* Install from AUR: `armips`
|
||||
* Install from AUR: a MIPS binutils package of your choice (eg. `mips-elf-binutils`)
|
||||
* Install from AUR: a MIPS gcc package of your choice (eg. `mips-elf-gcc`)
|
||||
|
||||
For Debian and Ubuntu:
|
||||
|
||||
* Install these packages: `binutils-mips-linux-gnu build-essential gcc-mips-linux-gnu libc6-dev-i386 libcapstone-dev make`
|
||||
* Compile and install `armips`
|
||||
|
||||
## ROM Versions
|
||||
|
||||
Perfect Dark has six known versions:
|
||||
|
||||
| ROM ID | Description | MD5 Checksum |
|
||||
|------------|---------------------------------------------|----------------------------------|
|
||||
| ntsc-beta | NTSC 6.4 beta | aa93f4df16fceada399a749f5ad2f273 |
|
||||
| ntsc-1.0 | NTSC 8.7 final (the initial, buggy release) | 7f4171b0c8d17815be37913f535e4e93 |
|
||||
| ntsc-final | NTSC 8.7 final | e03b088b6ac9e0080440efed07c1e40f |
|
||||
| pal-beta | PAL 28.7 beta | ad2de210a3455ba5ec541f0c78d91444 |
|
||||
| pal-final | PAL 8.7 final | d9b5cd305d228424891ce38e71bc9213 |
|
||||
| jpn-final | JPN 8.9 final | 538d2b75945eae069b29c46193e74790 |
|
||||
|
||||
The project uses the `$ROMID` environment variable to know which version to work with. If not set, it defaults to `ntsc-final`. You can change it by running something like `export ROMID=ntsc-1.0`.
|
||||
|
||||
## Extracting the base ROM
|
||||
|
||||
1. Save your existing ROM file into the root of the repository with the name `pd.ntsc-final.z64`. It should not be byteswapped (the first four bytes should be `0x80371240`).
|
||||
2. Run `make extract`.
|
||||
|
||||
This will extract assets to `src/assets`. If any asset already exists then it will not be overwritten. This means you can modify assets as desired, and your changes will not be overwritten if you run the extract command again.
|
||||
|
||||
The extract command will also create an `extracted/ntsc-final` directory. This directory contains some compiled code segments from the ROM and is only used for comparison purposes.
|
||||
|
||||
## Compiling
|
||||
|
||||
* Run `git submodule update --init --recursive`. You only have to do this once.
|
||||
* Run `make -j` to build the ROM. The ROM will be written to `build/ntsc-final/pd.z64`.
|
||||
|
||||
## How do I know the built files are matching?
|
||||
|
||||
Run `make` followed by `make test`. If `make test` produces no output then the compiled project is matching.
|
||||
|
||||
You can also md5sum your base ROM with the built ROM and check they have the same hash: `md5sum pd.ntsc-final.z64 build/ntsc-final/pd.z64`.
|
||||
Shoot characters in the head to have it explode.
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 231 KiB |
|
@ -5184,6 +5184,8 @@ void chrHit(struct shotdata *shotdata, struct hit *hit)
|
|||
|
||||
shield = chrGetShield(chr);
|
||||
|
||||
g_Decapitate = false;
|
||||
|
||||
func0f0341dc(chr, gsetGetDamage(&shotdata->gset), &shotdata->dir, &shotdata->gset,
|
||||
g_Vars.currentplayer->prop, hit->hitpart, hit->prop, hit->node,
|
||||
hit->model, hit->hitthing.unk28 / 2, sp90);
|
||||
|
@ -5313,12 +5315,17 @@ void chrHit(struct shotdata *shotdata, struct hit *hit)
|
|||
darker = false;
|
||||
}
|
||||
|
||||
if (!chrIsUsingPaintball(g_Vars.currentplayer->prop->chr)) {
|
||||
if (!g_Decapitate && !chrIsUsingPaintball(g_Vars.currentplayer->prop->chr)) {
|
||||
chrBruise(hit->model, hit->hitpart, hit->node, &sp5c);
|
||||
}
|
||||
|
||||
splatsCreateForChrHit(prop, shotdata, &sp98, &hitpos, darker, 0, g_Vars.currentplayer->prop->chr);
|
||||
}
|
||||
|
||||
if (g_Decapitate) {
|
||||
chrDecapitate(chr);
|
||||
sparksCreate(prop->rooms[0], prop, &hitpos, &shotdata->dir, 0, SPARKTYPE_HEADEXP_BLOOD);
|
||||
}
|
||||
#else
|
||||
// NTSC beta wraps all the blood logic in this paintball check.
|
||||
// If paintball is enabled, neither blood nor paint is created.
|
||||
|
|
|
@ -63,6 +63,7 @@ u32 var8009cd8c;
|
|||
u32 var8009cd90;
|
||||
u32 var8009cd94;
|
||||
u8 g_RecentQuipsIndex;
|
||||
bool g_Decapitate;
|
||||
|
||||
f32 g_EnemyAccuracyScale = 1;
|
||||
f32 g_PlayerDamageRxScale = 1;
|
||||
|
@ -3525,6 +3526,18 @@ void chrBeginArgh(struct chrdata *chr, f32 angle, s32 hitpart)
|
|||
}
|
||||
}
|
||||
|
||||
void chrDecapitate(struct chrdata *chr)
|
||||
{
|
||||
struct modelnode *headspotnode = modelGetPart(chr->model->filedata, MODELPART_CHR_HEADSPOT);
|
||||
struct modelnode *child;
|
||||
|
||||
if (headspotnode && headspotnode->type == MODELNODETYPE_HEADSPOT) {
|
||||
struct modelrwdata_headspot *rwdata = modelGetNodeRwData(chr->model, headspotnode);
|
||||
rwdata->modelfiledata = NULL;
|
||||
rwdata->rwdatas = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void chrReactToDamage(struct chrdata *chr, struct coord *vector, f32 angle, s32 hitpart, struct gset *gset, s32 aplayernum)
|
||||
{
|
||||
s32 race = CHRRACE(chr);
|
||||
|
@ -3991,14 +4004,14 @@ void chrChoke(struct chrdata *chr, s32 choketype)
|
|||
}
|
||||
|
||||
if (allowoverride) {
|
||||
if (choketype == CHOKETYPE_GURGLE) {
|
||||
if (choketype == CHOKETYPE_GURGLE || choketype == CHOKETYPE_FORCE_GURGLE) {
|
||||
s32 sounds[] = {
|
||||
SFX_M1_CHOKING,
|
||||
SFX_GURGLE_05B1,
|
||||
SFX_GURGLE_05B2,
|
||||
};
|
||||
|
||||
if ((random() % 8) == 0) {
|
||||
if (choketype == CHOKETYPE_FORCE_GURGLE || (random() % 8) == 0) {
|
||||
soundnum = sounds[random() % 3];
|
||||
}
|
||||
|
||||
|
@ -4632,12 +4645,8 @@ void chrDamage(struct chrdata *chr, f32 damage, struct coord *vector, struct gse
|
|||
// If chr is dying or already dead, consider making their head flinch
|
||||
// then we're done
|
||||
if (chr->actiontype == ACT_DIE || chr->actiontype == ACT_DEAD) {
|
||||
if (hitpart == HITPART_HEAD && chr->actiontype == ACT_DIE && race != RACE_SKEDAR && isfar) {
|
||||
struct coord pos;
|
||||
pos.x = vprop->pos.x - vector->x;
|
||||
pos.y = vprop->pos.y - vector->y;
|
||||
pos.z = vprop->pos.z - vector->z;
|
||||
chrFlinchHead(chr, chrGetAngleToPos(chr, &pos));
|
||||
if (hitpart == HITPART_HEAD && race != RACE_SKEDAR && isfar) {
|
||||
g_Decapitate = true;
|
||||
}
|
||||
|
||||
return;
|
||||
|
@ -5014,6 +5023,11 @@ void chrDamage(struct chrdata *chr, f32 damage, struct coord *vector, struct gse
|
|||
}
|
||||
} else {
|
||||
// Non-explosion damage to solo mode chr
|
||||
if (hitpart == HITPART_HEAD && chr->damage >= chr->maxdamage && !makedizzy && race != RACE_SKEDAR) {
|
||||
g_Decapitate = true;
|
||||
choketype = CHOKETYPE_FORCE_GURGLE;
|
||||
}
|
||||
|
||||
if (chr->actiontype != ACT_DRUGGEDKO && canchoke) {
|
||||
chrChoke(chr, choketype);
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ struct sparktype g_SparkTypes[] = {
|
|||
/*0x18*/ { 100, 28, 100, 1, 0, 0, 2, 60, 60, 15, 1, 0xffff7f7f, 0xffffffff, 0.02 },
|
||||
/*0x19*/ { 40, -1, 30, 10, 0, 0, 2, 50, 35, 10, 1, 0x301010ff, 0x401010ff, 0.02 },
|
||||
/*0x1a*/ { 70, 0, 150, 15, 0, 0, 6, 40, 10, 3, 0, 0x1111a880, 0xaaaaff40, 0.02 },
|
||||
/*0x1b*/ { 40, -1, 120, 120, 0, 0, 2, 60, 1, 100, 1, 0x301010ff, 0x401010ff, 0.02 },
|
||||
#endif
|
||||
};
|
||||
|
||||
|
@ -181,14 +182,14 @@ void sparksCreate(s32 room, struct prop *prop, struct coord *pos, struct coord *
|
|||
struct coord grouppos;
|
||||
s32 i;
|
||||
|
||||
if ((typenum == SPARKTYPE_BLOOD || typenum == SPARKTYPE_FLESH) && prop && prop->type == PROPTYPE_CHR) {
|
||||
if ((typenum == SPARKTYPE_BLOOD || typenum == SPARKTYPE_HEADEXP_BLOOD || typenum == SPARKTYPE_FLESH) && prop && prop->type == PROPTYPE_CHR) {
|
||||
struct chrdata *chr = prop->chr;
|
||||
u32 colours[3];
|
||||
u32 stack;
|
||||
|
||||
chrGetBloodColour(chr->bodynum, NULL, colours);
|
||||
|
||||
if (typenum == SPARKTYPE_BLOOD) {
|
||||
if (typenum == SPARKTYPE_BLOOD || typenum == SPARKTYPE_HEADEXP_BLOOD) {
|
||||
type->unk1c = colours[0];
|
||||
type->unk20 = colours[1];
|
||||
} else if (typenum == SPARKTYPE_FLESH) {
|
||||
|
|
|
@ -287,5 +287,6 @@ extern u8 g_AmBotCommands[16];
|
|||
extern struct mpsetup g_MpSetup;
|
||||
extern struct bossfile g_BossFile;
|
||||
extern struct chrdata *g_MpBotChrPtrs[MAX_BOTS];
|
||||
extern bool g_Decapitate;
|
||||
|
||||
#endif
|
||||
|
|
|
@ -435,9 +435,10 @@
|
|||
|
||||
#define CHECKSUM_PLACEHOLDER 0x99aabbcc
|
||||
|
||||
#define CHOKETYPE_NONE 0
|
||||
#define CHOKETYPE_GURGLE 1
|
||||
#define CHOKETYPE_COUGH 2
|
||||
#define CHOKETYPE_NONE 0
|
||||
#define CHOKETYPE_GURGLE 1
|
||||
#define CHOKETYPE_COUGH 2
|
||||
#define CHOKETYPE_FORCE_GURGLE 3
|
||||
|
||||
#define CHR_P1P2_OPPOSITE 0xf1
|
||||
#define CHR_P1P2 0xf2
|
||||
|
@ -3771,6 +3772,7 @@
|
|||
#define SPARKTYPE_BGHIT_TRANQULIZER 0x18
|
||||
#define SPARKTYPE_PAINT 0x19
|
||||
#define SPARKTYPE_DEEPWATER 0x1a
|
||||
#define SPARKTYPE_HEADEXP_BLOOD 0x1b
|
||||
|
||||
#define SPAWNFLAG_FORCESUNGLASSES 0x00000001 // 100% chance of wearing sunglasses if head model supports it
|
||||
#define SPAWNFLAG_MAYBESUNGLASSES 0x00000002 // 50% chance of wearing sunglasses if head model supports it
|
||||
|
|
|
@ -344,5 +344,6 @@ Gfx *chrsRenderChrStats(Gfx *gdl, s16 *rooms);
|
|||
void chrToggleModelPart(struct chrdata *chr, s32 partnum);
|
||||
bool chrIsAvoiding(struct chrdata *chr);
|
||||
void chrDrCarollEmitSparks(struct chrdata *chr);
|
||||
void chrDecapitate(struct chrdata *chr);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1216,6 +1216,8 @@ void modelAttachHead(struct model *model, struct modelnode *bodynode)
|
|||
headnode->parent = bodynode;
|
||||
headnode = headnode->next;
|
||||
}
|
||||
} else {
|
||||
bodynode->child = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3456,6 +3458,8 @@ void modelRender(struct modelrenderdata *renderdata, struct model *model)
|
|||
loopnode->parent = node;
|
||||
loopnode = loopnode->next;
|
||||
}
|
||||
} else {
|
||||
node->child = NULL;
|
||||
}
|
||||
break;
|
||||
case MODELNODETYPE_REORDER:
|
||||
|
@ -3753,6 +3757,8 @@ s32 model000225d4(struct model *model, struct coord *arg1, struct coord *arg2, s
|
|||
loopnode->parent = node;
|
||||
loopnode = loopnode->next;
|
||||
}
|
||||
} else {
|
||||
node->child = NULL;
|
||||
}
|
||||
break;
|
||||
case MODELNODETYPE_CHRINFO:
|
||||
|
@ -4049,6 +4055,7 @@ void modelInitRwData(struct model *model, struct modelnode *startnode)
|
|||
rwdata = modelGetNodeRwData(model, node);
|
||||
rwdata->headspot.modelfiledata = NULL;
|
||||
rwdata->headspot.rwdatas = NULL;
|
||||
node->child = NULL;
|
||||
break;
|
||||
case MODELNODETYPE_REORDER:
|
||||
rwdata = modelGetNodeRwData(model, node);
|
||||
|
|
Loading…
Reference in New Issue