diff --git a/config/sym_ovl_export.us.txt b/config/sym_ovl_export.us.txt index 6f524c78..97448f0d 100644 --- a/config/sym_ovl_export.us.txt +++ b/config/sym_ovl_export.us.txt @@ -1878,20 +1878,31 @@ func_800B00DC = 0x800b00dc; func_800B01B0 = 0x800b01b0; func_800B0E7C = 0x800b0e7c; MAGIC_Barrier = 0x801b0000; -BarrierBaseScale = 0x801b0cc0; -BarrierPrimBuffer = 0x801b0cc4; -BarrierBufferPtr = 0x801d0cc4; +barrier_base_scale = 0x801b0cc0; +barrier_prim_buffer = 0x801b0cc4; +barrier_buffer_ptr = 0x801d0cc4; MAGIC_Brizad = 0x801b037c; -BrizadRenderDesc = 0x801b1004; -BrizadPrimBuffer = 0x801b1014; -BrizadBufferPtr = 0x801d1014; +g_BrizadRenderDesc = 0x801b1004; +brizad_prim_buffer = 0x801b1014; +brizad_buffer_ptr = 0x801d1014; MAGIC_Lv5Death = 0x801b0054; -Lv5DeathTexture = 0x801b05e0; -Lv5DeathSpriteModel = 0x801c0700; -Lv5DeathRingModel = 0x801c0868; -Lv5DeathSpriteDesc = 0x801c0e44; -Lv5DeathPrimBuffer0 = 0x801c0e50; -Lv5DeathPrimBuffer1 = 0x801cce50; -Lv5DeathBufferPtr = 0x801d8e50; -Lv5DeathFlipEffect = 0x801d8e54; -Lv5DeathTargetsRemaining = 0x801d8e58; +g_Lv5DeathTexture = 0x801b05e0; +g_Lv5DeathSpriteModel = 0x801c0700; +g_Lv5DeathRingModel = 0x801c0868; +g_Lv5DeathSpriteDesc = 0x801c0e44; +g_Lv5DeathPrimBuffer0 = 0x801c0e50; +g_Lv5DeathPrimBuffer1 = 0x801cce50; +g_Lv5DeathBufferPtr = 0x801d8e50; +g_Lv5DeathFlipEffect = 0x801d8e54; +g_Lv5DeathTargetsRemaining = 0x801d8e58; +MAGIC_Thunder = 0x801b0000; +g_ThunderRenderData0 = 0x801b073c; +g_ThunderRenderData1 = 0x801b07d0; +g_ThunderModel = 0x801b0898; +g_ThunderTexture = 0x801b0dac; +g_ThunderModelMatrix = 0x801c0e2c; +g_ThunderModelDesc = 0x801c0e4c; +g_ThunderRenderDesc0 = 0x801c0e5c; +g_ThunderRenderDesc1 = 0x801c0e68; +MAGIC_MBarrier = 0x801b0000; +g_MabariaRenderDesc = 0x801b0c90; diff --git a/config/symbols.magic-brizad.us.txt b/config/symbols.magic-brizad.us.txt index 164795ea..68e18211 100644 --- a/config/symbols.magic-brizad.us.txt +++ b/config/symbols.magic-brizad.us.txt @@ -4,6 +4,4 @@ BrizadAttachToTargetUnused = 0x801B028C; BrizadAttachToTarget = 0x801B02D4; BrizadDoubleBufferFlip = 0x801B031C; MAGIC_Brizad = 0x801B037C; -BrizadRenderDesc = 0x801B1004; // size:0x10 -BrizadPrimBuffer = 0x801B1014; -BrizadBufferPtr = 0x801D1014; +g_BrizadRenderDesc = 0x801B1004; // size:0x10 diff --git a/config/symbols.magic-lv5deth.us.txt b/config/symbols.magic-lv5deth.us.txt index 39e4fbcf..25f536f9 100644 --- a/config/symbols.magic-lv5deth.us.txt +++ b/config/symbols.magic-lv5deth.us.txt @@ -1,9 +1,9 @@ -Lv5DeathTexture = 0x801B05E0; -Lv5DeathSpriteModel = 0x801C0700; -Lv5DeathRingModel = 0x801C0868; -Lv5DeathSpriteDesc = 0x801C0E44; // size:0xC -Lv5DeathPrimBuffer0 = 0x801C0E50; -Lv5DeathPrimBuffer1 = 0x801CCE50; -Lv5DeathBufferPtr = 0x801D8E50; -Lv5DeathFlipEffect = 0x801D8E54; -Lv5DeathTargetsRemaining = 0x801D8E58; +g_Lv5DeathTexture = 0x801B05E0; +g_Lv5DeathSpriteModel = 0x801C0700; +g_Lv5DeathRingModel = 0x801C0868; +g_Lv5DeathSpriteDesc = 0x801C0E44; // size:0xC +g_Lv5DeathPrimBuffer0 = 0x801C0E50; +g_Lv5DeathPrimBuffer1 = 0x801CCE50; +g_Lv5DeathBufferPtr = 0x801D8E50; +g_Lv5DeathFlipEffect = 0x801D8E54; +g_Lv5DeathTargetsRemaining = 0x801D8E58; diff --git a/config/symbols.magic-mabaria.us.txt b/config/symbols.magic-mabaria.us.txt new file mode 100644 index 00000000..beee4507 --- /dev/null +++ b/config/symbols.magic-mabaria.us.txt @@ -0,0 +1,9 @@ +MAGIC_MBarrier = 0x801B0000; +MabariaRenderModel = 0x801B0020; +MabariaAnimationUpdate = 0x801B01C0; +MabariaAttachToTarget = 0x801B0378; +MabariaDoubleBufferFlip = 0x801B04DC; +MabariaMainSetup = 0x801B053C; +g_MabariaRenderDesc = 0x801B0C90; // size:0x10 +g_MabariaPrimBuffer = 0x801B0CA8; +g_MabariaBufferPtr = 0x801D0CA8; diff --git a/config/symbols.magic-thunder.us.txt b/config/symbols.magic-thunder.us.txt new file mode 100644 index 00000000..5098ed28 --- /dev/null +++ b/config/symbols.magic-thunder.us.txt @@ -0,0 +1,16 @@ +MAGIC_Thunder = 0x801B0000; +ThunderRenderModel = 0x801B0020; +ThunderSpawnBolt = 0x801B0350; +ThunderAttachToTarget = 0x801B05A4; +ThunderDoubleBufferFlip = 0x801B066C; +ThunderMainSetup = 0x801B06CC; +g_ThunderRenderData0 = 0x801B073C; +g_ThunderRenderData1 = 0x801B07D0; +g_ThunderModel = 0x801B0898; +g_ThunderTexture = 0x801B0DAC; +g_ThunderModelMatrix = 0x801C0E2C; // size:0x20 +g_ThunderModelDesc = 0x801C0E4C; // size:0x10 +g_ThunderRenderDesc0 = 0x801C0E5C; // size:0xC +g_ThunderRenderDesc1 = 0x801C0E68; // size:0xC +g_ThunderPrimBuffer = 0x801C0E78; +g_ThunderBufferPtr = 0x801E0E78; diff --git a/config/us.yaml b/config/us.yaml index 78c738bf..7c71d8d1 100644 --- a/config/us.yaml +++ b/config/us.yaml @@ -305,3 +305,29 @@ overlays: segments: - [0, c, lv5deth] - [0x5e0, data, lv5deth] + # THUNDER.BIN -- "Bolt", the tier-1 lightning spell + - name: thunder + disk_path: disks/us/MAGIC/THUNDER.BIN + sha1: 5767aed5f73876075f01b3958a2e4738fa33b043 + base_path: magic + vram_start: 0x801B0000 + symbol_addrs_path: + - config/sym_export.us.txt + - config/symbols.magic-thunder.us.txt + - config/sym_export_battle.us.txt + segments: + - [0, c, thunder] + - [0x73c, data, thunder] + # MABARIA.BIN -- "MBarrier", the magic-damage half of Wall + - name: mabaria + disk_path: disks/us/MAGIC/MABARIA.BIN + sha1: 9169442d3e1702b7ad4425d95367f52334f84d60 + base_path: magic + vram_start: 0x801B0000 + symbol_addrs_path: + - config/sym_export.us.txt + - config/symbols.magic-mabaria.us.txt + - config/sym_export_battle.us.txt + segments: + - [0, c, mabaria] + - [0x5b4, data, mabaria] diff --git a/src/battle/battle.h b/src/battle/battle.h index b2b4eb86..6e4f93ca 100644 --- a/src/battle/battle.h +++ b/src/battle/battle.h @@ -382,14 +382,43 @@ typedef struct { } Unk800BB75C; // size:0x38 +// Flag word at offset 4, as func_800D29D4 tests it. Each mirror bit negates +// one rotation column, reversing polygon winding, so the cull test XORs their +// parity back out. Readings agree with Akari's q-gears_reverse +// (ffvii/DISC/BATTLE/BATTLE.X_model.cpp, "funcd29d4"). +enum ModelRenderFlags { + MODEL_MIRROR_X = 0x1, // negates R11/R21/R31 + MODEL_MIRROR_Y = 0x2, // negates R12/R22/R32 + MODEL_MIRROR_Z = 0x4, // negates R13/R23/R33 + MODEL_SEMI_TRANS = 0x8, // sets bit 1 of the GPU command byte + MODEL_NO_DEPTH_CUE = 0x10, // skips dpcs/dpct under MODEL_DEPTH_CUE + MODEL_NO_CULL = 0x20, // skips nclip; both faces emitted + MODEL_PRIM_TPAGE = 0x40, // tpage from primitive byte 7, not tpage + MODEL_DEPTH_CUE = 0x80, // offset 0xA feeds GTE IR0; see below + MODEL_PRIM_PACKET_BITS = 0x100, // primitive byte 7 << 18 ORed into colour +}; + +// Model descriptor read by func_800D29D4. Field readings agree with Akari's +// q-gears_reverse (ffvii/DISC/BATTLE/BATTLE.X_model.cpp, "funcd29d4"). typedef struct { - s32* unk0; - s32 unk4; - s16 unk8; - s16 unkA; - s16 unkC; - s16 unkE; -} Unk801B0C98; + /* 0x0 */ s32* model; + /* 0x4 */ s32 flags; // ModelRenderFlags + /* 0x8 */ u16 uvOffset; // added to every UV halfword; all callers pass 0 + /* 0xA */ s16 color; // grey level ORed into the colour word; under + // MODEL_DEPTH_CUE it feeds GTE IR0 instead, so + // 0x1000 blends the model fully into SetFarColor + /* 0xC */ s16 tpage; // packet +0x16; Akari: "blending option" + /* 0xE */ s16 clut; // packet clut halfword +} ModelRenderDesc; // size:0x10 + +// Textured-quad descriptor read by func_800D4D90; ROM instances are packed +// 0xC apart. Akari: BATTLE.X_units_functions.cpp, "funcd4d90". +typedef struct { + /* 0x0 */ s32* frames; // per-frame quad blocks, count in each header + /* 0x4 */ CVECTOR color; // packet colour word; cd is 0x2C or 0x2E (POLY_FT4) + /* 0x8 */ u16 frameIndex; // blocks skipped; bit 15 enables clutBias + /* 0xA */ s16 clutBias; // added to each quad's clut halfword +} SpriteRenderDesc; // size:0xC typedef struct { s16 unk0; @@ -478,13 +507,19 @@ extern u16 D_8016376A; int BattleEffectRegister(void (*func)(void)); void func_800D2980(u_long* addr, s16 imgXY, s16 clutX, s16 clutY); -void* func_800D29D4(Unk801B0C98*, u_long**, int, void*); -void func_800D4368(SVECTOR* pos, s32 scale, s32 depthBias); -void* func_800D4D90(void* desc, u_long** ot, int otLen, void* prim); +void* func_800D29D4(ModelRenderDesc*, u_long**, int, void*); +// Build the model matrix for a battle effect: `scale` goes on the matrix +// diagonal, `pos` is transformed into view space to become the translation, +// and `depthBias` nudges it along that view vector (negative pulls it toward +// the camera). Leaves the result installed as the rot/trans matrix. +MATRIX* func_800D4368(SVECTOR* pos, s32 scale, s32 depthBias); +void* func_800D4D90(SpriteRenderDesc* desc, u_long** ot, int otLen, void* prim); void func_800D5444(int, int, int, void (*func)(int)); +// Returns a scale derived from the target's model size. +s32 func_800D55A4(s32 target); void BattleCommandSend(s32 cmdId, ...); -int func_800D574C(int); void BattleGetPartPosition(s32 arg0, s32 arg1, void* arg2); +// Runs `func` once per set bit in targetMask, frameStep frames apart. void MagicAnimationRegister(s32 targetMask, s32 arg1, s32 frameStep, void (*func)(int)); s32 BattlePositionToStereoPan(SVECTOR* sv); s32 BattleEntityGetStereoPan(s32 arg0); diff --git a/src/battle/battle1.c b/src/battle/battle1.c index d09ded83..dda2c3b6 100644 --- a/src/battle/battle1.c +++ b/src/battle/battle1.c @@ -724,9 +724,9 @@ static void func_800BAFF8(MATRIX* m, VECTOR* v) { static void func_800BB030(s16 arg0) { s32 i; - Unk801B0C98* unk; + ModelRenderDesc* unk; - unk = (Unk801B0C98*)0x1F800020; + unk = (ModelRenderDesc*)0x1F800020; SetFarColor(D_801518E4[arg0].D_8015190C, D_801518E4[arg0].D_8015190D, D_801518E4[arg0].D_8015190E); SetRotMatrix(&D_801518E4[arg0].m); SetTransMatrix(&D_801518E4[arg0].m); @@ -738,12 +738,12 @@ static void func_800BB030(s16 arg0) { func_800BAF34(&D_800FA6D8[arg0].unk8[i]); if (!D_800FA6D8[arg0].unk4[i]) continue; - unk->unk0 = D_800FA6D8[arg0].unk4[i]; - unk->unk4 = D_800FA6D8[arg0].unk3E[i] | 0x180; - unk->unk8 = 0; - unk->unkA = D_801518E4[arg0].unk14[0]; - unk->unkC = 0x20; - unk->unkE = D_801518E4[arg0].unk14[1]; + unk->model = D_800FA6D8[arg0].unk4[i]; + unk->flags = D_800FA6D8[arg0].unk3E[i] | MODEL_PRIM_PACKET_BITS | MODEL_DEPTH_CUE; + unk->uvOffset = 0; + unk->color = D_801518E4[arg0].unk14[0]; + unk->tpage = 0x20; + unk->clut = D_801518E4[arg0].unk14[1]; if (D_801518E4[arg0].D_80151909 & 4) { continue; } diff --git a/src/battle/battle2.c b/src/battle/battle2.c index 10de5c0b..452fb1f3 100644 --- a/src/battle/battle2.c +++ b/src/battle/battle2.c @@ -279,11 +279,14 @@ static void func_800CE638(void) { } } +// Shell over a covered unit; MBarrier wins when both masks are set. Masks +// named by Akari's q-gears_reverse (ffvii/address_battle.txt): D_800FA69C is +// the magic barrier mask, D_80163608 the physical one. static void func_800CE75C(void) { if ((D_800FA69C >> D_80162978[D_8015169C].D_80162980) & 1) { - func_800D67E8(D_80162978[D_8015169C].D_80162980); + func_800D67E8(D_80162978[D_8015169C].D_80162980); // MBarrier } else if ((D_80163608 >> D_80162978[D_8015169C].D_80162980) & 1) { - func_800D67BC(D_80162978[D_8015169C].D_80162980); + func_800D67BC(D_80162978[D_8015169C].D_80162980); // Barrier } } @@ -1268,29 +1271,30 @@ INCLUDE_ASM("asm/us/battle/nonmatchings/battle2", func_800D61AC); INCLUDE_ASM("asm/us/battle/nonmatchings/battle2", func_800D6260); -extern Unk801B0C98 D_800F14D0; +extern ModelRenderDesc D_800F14D0; // Draw a model 4 times through func_800D29D4 (same request-struct pattern as // barrier.c's D_801B0C98/D_801B0CB0), toggling the 0x1/0x2 flag bits between -// passes. +// passes. Those bits mirror on X and Y, so the four passes are the four +// quadrants of a symmetric model built from one quarter. static void func_800D6394(s32* arg0, s16 arg1) { - D_800F14D0.unk0 = arg0; - D_800F14D0.unkA = arg1; + D_800F14D0.model = arg0; + D_800F14D0.color = arg1; SetFarColor(0, 0, 0); PushMatrix(); D_80163C74 = func_800D29D4(&D_800F14D0, g_cDb->unk70, 0xC, D_80163C74); PopMatrix(); PushMatrix(); - D_800F14D0.unk4 |= 1; + D_800F14D0.flags |= MODEL_MIRROR_X; D_80163C74 = func_800D29D4(&D_800F14D0, g_cDb->unk70, 0xC, D_80163C74); PopMatrix(); PushMatrix(); - D_800F14D0.unk4 |= 2; + D_800F14D0.flags |= MODEL_MIRROR_Y; D_80163C74 = func_800D29D4(&D_800F14D0, g_cDb->unk70, 0xC, D_80163C74); PopMatrix(); - D_800F14D0.unk4 &= ~1; + D_800F14D0.flags &= ~MODEL_MIRROR_X; D_80163C74 = func_800D29D4(&D_800F14D0, g_cDb->unk70, 0xC, D_80163C74); - D_800F14D0.unk4 &= ~2; + D_800F14D0.flags &= ~MODEL_MIRROR_Y; } INCLUDE_ASM("asm/us/battle/nonmatchings/battle2", func_800D650C); @@ -1302,20 +1306,19 @@ u8* const D_800A0DC8[] = {D_800F10EC, D_800F11E8, D_800F1304}; INCLUDE_ASM("asm/us/battle/nonmatchings/battle2", func_800D6734); void func_800D6734(s32, s32); -extern s32 D_800F14D4; static void func_800D67BC(s32 arg0) { - D_800F14D4 = 0x88; + D_800F14D0.flags = MODEL_DEPTH_CUE | MODEL_SEMI_TRANS; // Barrier func_800D6734(arg0, 0); } static void func_800D67E8(s32 arg0) { - D_800F14D4 = 0xA8; + D_800F14D0.flags = MODEL_DEPTH_CUE | MODEL_NO_CULL | MODEL_SEMI_TRANS; // MBarrier func_800D6734(arg0, 1); } void func_800D6814(s32 arg0) { - D_800F14D4 = 0x88; + D_800F14D0.flags = MODEL_DEPTH_CUE | MODEL_SEMI_TRANS; func_800D6734(arg0, 2); } diff --git a/src/magic/barrier.c b/src/magic/barrier.c index a09de6a9..e18d6fa3 100644 --- a/src/magic/barrier.c +++ b/src/magic/barrier.c @@ -2,6 +2,12 @@ #include "common.h" #include "../battle/battle.h" +#include "magic_private.h" + +#define FADE_LAST_FRAME 7 +#define FADE_PER_FRAME 0x200 +#define SCALE_BASE 0xC00 +#define SCALE_PER_FADE_FRAME 0x180 // This is placeholder for now so I can access the SVECTORs correctly typedef struct BarrierData { @@ -11,7 +17,8 @@ typedef struct BarrierData { s16 unk6; SVECTOR Pos; SVECTOR Rot; - u16 FaceIndex; + u16 FaceIndex; // 0..3, straight into the descriptor's mirror bits + // 0x1 and 0x2; one instance per quadrant char pad1[0x6]; } BarrierData; @@ -65,45 +72,57 @@ static s32 bari_a2[] = { // Embedded Model 0x303F3F3F, // Primitive/color: command 0x30, RGB 3F3F3F 0x00D4D4D4, // Triangle 1: vertex 1 color, RGB D4D4D4 0x003F3F3F}; // Triangle 1: vertex 2 color, RGB 3F3F3F -static int emptyPoly = 0x00000000; -static SVECTOR BorderPivotOffset = {0, 0, -500}; -static Unk801B0C98 BorderRenderDesc = {bari_a1, 0, 0, 0, 0x20}; -static SVECTOR ShieldPivotOffset = {0, 0, -500}; -static Unk801B0C98 ShieldRenderDesc = {bari_a2, 0, 0, 0, 0x20}; -static int BarrierBaseScale; -static char BarrierPrimBuffer[0x20000]; -static void* BarrierBufferPtr; +static int empty_poly = 0x00000000; +static SVECTOR border_pivot_offset = {0, 0, -500}; +static ModelRenderDesc border_render_desc = {bari_a1, 0, 0, 0, 0x20}; +static SVECTOR shield_pivot_offset = {0, 0, -500}; +static ModelRenderDesc shield_render_desc = {bari_a2, 0, 0, 0, 0x20}; +static int barrier_base_scale; + +typedef struct { + /* 0x00 */ char pad[MAGIC_PAGE_SIZE]; +} BarrierPrimPage; // size:0x10000 + +static BarrierPrimPage barrier_prim_buffer[2]; +static void* barrier_buffer_ptr; // barrier.c forward declarations static void BarrierMainSetup(int arg0, int arg1); void MAGIC_Barrier(int arg0, int arg1) { BarrierMainSetup(arg0, arg1); } +// FaceIndex runs 0, 1, 3, 2 across the four instances -- Gray code, so the +// shell grows through adjacent quadrants. static void BarrierRenderBorder(void) { - MATRIX* matrix = (MATRIX*)0x1F800000; - VECTOR* scale = (VECTOR*)0x1F800020; - BarrierData* barrier = &D_80162978[D_8015169C]; - int temp_a0 = (barrier->AnimationFrame + barrier->StartFrame) - 17; - int var_s4; - int var_s3; - + MATRIX* matrix; + VECTOR* scale; + BarrierData* barrier; + int temp_a0; + int fade; + int faceFlags; + + matrix = (MATRIX*)0x1F800000; + scale = (VECTOR*)0x1F800020; + barrier = &D_80162978[D_8015169C]; + temp_a0 = (barrier->AnimationFrame + barrier->StartFrame) - 17; if (temp_a0 < 0) { - scale->vx = scale->vy = scale->vz = (BarrierBaseScale * 0xC00) >> 12; - var_s3 = barrier->FaceIndex; - var_s4 = 0; - } else if (temp_a0 > 7) { + scale->vx = scale->vy = scale->vz = (barrier_base_scale * SCALE_BASE) >> 12; + faceFlags = barrier->FaceIndex; + fade = 0; + } else if (temp_a0 > FADE_LAST_FRAME) { barrier->StartFrame = -1; return; } else { - var_s3 = barrier->FaceIndex | 8; - var_s4 = temp_a0 << 9; - scale->vx = scale->vy = scale->vz = (((temp_a0 * 0x180) + 0xC00) * BarrierBaseScale) >> 12; + faceFlags = barrier->FaceIndex | MODEL_SEMI_TRANS; + fade = temp_a0 * FADE_PER_FRAME; + scale->vx = scale->vy = scale->vz = + (((temp_a0 * SCALE_PER_FADE_FRAME) + SCALE_BASE) * barrier_base_scale) >> 12; } SetFarColor(0, 0, 0); RotMatrixYXZ(&barrier->Rot, matrix); ScaleMatrix(matrix, scale); - ApplyMatrix(matrix, &BorderPivotOffset, matrix->t); + ApplyMatrix(matrix, &border_pivot_offset, matrix->t); matrix->t[0] += barrier->Pos.vx; matrix->t[1] += barrier->Pos.vy; @@ -113,9 +132,9 @@ static void BarrierRenderBorder(void) { SetRotMatrix(matrix); SetTransMatrix(matrix); - BorderRenderDesc.unk4 = var_s3 | 0x80; - BorderRenderDesc.unkA = var_s4; - BarrierBufferPtr = func_800D29D4(&BorderRenderDesc, g_cDb->unk70, 12, BarrierBufferPtr); + border_render_desc.flags = faceFlags | MODEL_DEPTH_CUE; + border_render_desc.color = fade; + barrier_buffer_ptr = func_800D29D4(&border_render_desc, g_cDb->unk70, 12, barrier_buffer_ptr); if (D_80062D98 == 0) { barrier->AnimationFrame++; @@ -123,34 +142,40 @@ static void BarrierRenderBorder(void) { } static void BarrierRenderShield(void) { - MATRIX* matrix1 = (MATRIX*)0x1F800000; - MATRIX* matrix2 = (MATRIX*)0x1F800020; - VECTOR* scale1 = (VECTOR*)0x1F800040; - VECTOR* scale2 = (VECTOR*)0x1F800050; - BarrierData* barrier = &D_80162978[D_8015169C]; - int temp_a0 = barrier->AnimationFrame + barrier->StartFrame - 17; - int var_s5; - int var_s6; - + MATRIX* matrix1; + MATRIX* matrix2; + VECTOR* scale1; + VECTOR* scale2; + BarrierData* barrier; + int temp_a0; + int faceFlags; + int fade; + + matrix1 = (MATRIX*)0x1F800000; + matrix2 = (MATRIX*)0x1F800020; + scale1 = (VECTOR*)0x1F800040; + scale2 = (VECTOR*)0x1F800050; + barrier = &D_80162978[D_8015169C]; + temp_a0 = barrier->AnimationFrame + barrier->StartFrame - 17; if (temp_a0 < 0) { if (barrier->AnimationFrame < 6) { - scale1->vx = scale1->vy = scale1->vz = (barrier->AnimationFrame * (BarrierBaseScale << 9)) >> 12; + scale1->vx = scale1->vy = scale1->vz = (barrier->AnimationFrame * (barrier_base_scale << 9)) >> 12; } else { - scale1->vx = scale1->vy = scale1->vz = (BarrierBaseScale * 0xC00) >> 12; + scale1->vx = scale1->vy = scale1->vz = (barrier_base_scale * SCALE_BASE) >> 12; } - scale2->vx = scale2->vy = scale2->vz = (BarrierBaseScale * 0xC00) >> 12; + scale2->vx = scale2->vy = scale2->vz = (barrier_base_scale * SCALE_BASE) >> 12; - var_s5 = barrier->FaceIndex; - var_s6 = 0; - } else if (temp_a0 > 7) { + faceFlags = barrier->FaceIndex; + fade = 0; + } else if (temp_a0 > FADE_LAST_FRAME) { barrier->StartFrame = -1; return; } else { - var_s5 = barrier->FaceIndex | 8; - var_s6 = temp_a0 << 9; + faceFlags = barrier->FaceIndex | MODEL_SEMI_TRANS; + fade = temp_a0 * FADE_PER_FRAME; scale1->vx = scale1->vy = scale1->vz = scale2->vx = scale2->vy = scale2->vz = - (((temp_a0 * 0x180) + 0xC00) * BarrierBaseScale) >> 12; + (((temp_a0 * SCALE_PER_FADE_FRAME) + SCALE_BASE) * barrier_base_scale) >> 12; } SetFarColor(0, 0, 0); @@ -158,7 +183,7 @@ static void BarrierRenderShield(void) { *matrix2 = *matrix1; ScaleMatrix(matrix1, scale1); ScaleMatrix(matrix2, scale2); - ApplyMatrix(matrix2, &ShieldPivotOffset, matrix1->t); + ApplyMatrix(matrix2, &shield_pivot_offset, matrix1->t); matrix1->t[0] += barrier->Pos.vx; matrix1->t[1] += barrier->Pos.vy; matrix1->t[2] += barrier->Pos.vz; @@ -166,9 +191,9 @@ static void BarrierRenderShield(void) { SetRotMatrix(matrix1); SetTransMatrix(matrix1); - ShieldRenderDesc.unk4 = var_s5 | 0x80; - ShieldRenderDesc.unkA = var_s6; - BarrierBufferPtr = func_800D29D4(&ShieldRenderDesc, g_cDb->unk70, 12, BarrierBufferPtr); + shield_render_desc.flags = faceFlags | MODEL_DEPTH_CUE; + shield_render_desc.color = fade; + barrier_buffer_ptr = func_800D29D4(&shield_render_desc, g_cDb->unk70, 12, barrier_buffer_ptr); if (D_80062D98 == 0) { barrier->AnimationFrame++; @@ -176,9 +201,10 @@ static void BarrierRenderShield(void) { } static void BarrierAnimationUpdate(void) { - BarrierData* barrier = &D_80162978[D_8015169C]; // model instance + BarrierData* barrier; // model instance BarrierData* next; + barrier = &D_80162978[D_8015169C]; if (D_80062D98 != 0) { return; } @@ -256,8 +282,9 @@ static void BarrierAnimationUpdate(void) { } static void BarrierAttachToTarget(int target) { - BarrierData* barrier = &D_80162978[BattleEffectRegister(BarrierAnimationUpdate)]; + BarrierData* barrier; + barrier = &D_80162978[BattleEffectRegister(BarrierAnimationUpdate)]; BattleGetPartPosition(target, D_801518E4[target].D_8015190F, &barrier->Pos); barrier->Pos.vx -= (rsin(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> 12; barrier->Pos.vz -= (rcos(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> 12; @@ -266,9 +293,10 @@ static void BarrierAttachToTarget(int target) { } static void BarrierDoubleBufferFlip(void) { - BarrierData* barrier = &D_80162978[D_8015169C]; + BarrierData* barrier; - BarrierBufferPtr = &BarrierPrimBuffer[barrier->AnimationFrame * 65536]; + barrier = &D_80162978[D_8015169C]; + barrier_buffer_ptr = &barrier_prim_buffer[barrier->AnimationFrame]; barrier->AnimationFrame ^= 1; if (D_80162080 < 2) { @@ -277,7 +305,7 @@ static void BarrierDoubleBufferFlip(void) { } static void BarrierMainSetup(int arg0, int arg1) { - BarrierBaseScale = 0x3000; + barrier_base_scale = 0x3000; BattleEffectRegister(BarrierDoubleBufferFlip); MagicAnimationRegister(arg0, arg1, 4, BarrierAttachToTarget); BattleCommandSend(32, BattleEntityGetStereoPan(arg0), 94); diff --git a/src/magic/brizad.c b/src/magic/brizad.c index 10ed49a7..174cf815 100644 --- a/src/magic/brizad.c +++ b/src/magic/brizad.c @@ -2,19 +2,22 @@ #include "common.h" #include "../battle/battle.h" +#include "magic_private.h" -// Ice (ブリザド / Blizzard), tier 1. Structurally a sibling of barrier.c: -// a 3D model effect built through func_800D29D4 with the PSYQ matrix helpers, -// double-buffered into a 0x20000 primitive page per frame. +// Ice (ブリザド / Blizzard), tier 1. +// +// The model at 0x801B03F0 is 84 vertices and 120 untextured Gouraud +// triangles: a twelve-spike burst whose tips point at the vertices of an +// icosahedron. -// The primitive buffer holds two pages; the flip slot alternates between them -// so the GPU can read last frame's primitives while this frame builds. -#define BRIZAD_PAGE_SIZE 0x10000 +// brizad_buffer_ptr must land immediately after the buffer, at 0x801B1014 + +// 0x20000. +typedef struct { + /* 0x00 */ char pad[MAGIC_PAGE_SIZE]; +} BrizadPrimPage; // size:0x10000 -// .bss -- laid out so BrizadBufferPtr lands immediately after the buffer, at -// 0x801B1014 + 0x20000. Matches the BarrierPrimBuffer/BarrierBufferPtr pair. -static char BrizadPrimBuffer[2 * BRIZAD_PAGE_SIZE]; -static void* BrizadBufferPtr; // current write pointer into the above +static BrizadPrimPage brizad_prim_buffer[2]; +static void* brizad_buffer_ptr; typedef struct { /* 0x00 */ s16 StartFrame; @@ -29,64 +32,56 @@ typedef struct { /* 0x1E */ s16 unk1E; } BrizadData; // size:0x20 -// PSX fixed point: 1.0 == 1 << FIXED_SHIFT. Angles: FIXED_ONE == a full turn. -#define FIXED_SHIFT 12 -#define FIXED_ONE (1 << FIXED_SHIFT) - -// The block renders on frames 0..14, retiring after the last one, so both rates -// are per (BRIZAD_LIFETIME - 1) frames: over its life it grows from nothing to -// 3x the target's size and spins through exactly one revolution. +// The fade is the GTE depth-cue factor, driving vertex colour toward +// SetFarColor -- black here. Primitives are semi-transparent and blended +// additively, so the model fades to invisible rather than to a dark shape. #define BRIZAD_LIFETIME 15 -#define GROWTH_PER_FRAME (3 * FIXED_ONE / (BRIZAD_LIFETIME - 1)) // 0x36D -#define SPIN_PER_FRAME (FIXED_ONE / (BRIZAD_LIFETIME - 1)) // 0x124 +#define GROWTH_PER_FRAME (3 * 4096 / (BRIZAD_LIFETIME - 1)) // 0x36D +#define FADE_PER_FRAME (4096 / (BRIZAD_LIFETIME - 1)) // 0x124 -// ScaleMatrix writes into MATRIX.m, which is s16; a scale of 0x7FFF against an -// identity entry of FIXED_ONE lands exactly on the ceiling, so clamp there. +// ScaleMatrix writes into MATRIX.m, which is s16, so the scale clamps here. #define SCALE_MAX 0x7FFF -extern Unk801B0C98 BrizadRenderDesc; +extern ModelRenderDesc g_BrizadRenderDesc; extern BrizadData D_80162978[]; extern s16 D_80151774; -// Returns a scale derived from the target's model size. -s32 func_800D55A4(s32 target); - -// Render pass; registered by BrizadSpawnIce, so it takes no arguments. static void BrizadRenderIce(void) { MATRIX matrix; VECTOR scaleVec; - BrizadData* effect = &D_80162978[D_8015169C]; - s16 nextFrame; + BrizadData* effect; s16 frame; - s16 spin; - s32 growth = (effect->Scale * GROWTH_PER_FRAME); - s32 scale = (s32)(effect->AnimationFrame * growth) >> FIXED_SHIFT; + s16 fade; + s32 growth; + s32 scale; + effect = &D_80162978[D_8015169C]; + growth = (effect->Scale * GROWTH_PER_FRAME); + scale = (effect->AnimationFrame * growth) >> 12; if (scale > SCALE_MAX) { scale = SCALE_MAX; } scaleVec.vx = scaleVec.vy = scaleVec.vz = scale; frame = effect->AnimationFrame; if (frame < 0) { - spin = 0; + fade = 0; } else { - spin = frame * SPIN_PER_FRAME; + fade = frame * FADE_PER_FRAME; } RotMatrixYXZ(&effect->Rot, &matrix); ScaleMatrix(&matrix, &scaleVec); - matrix.t[0] = (s32)effect->Pos.vx; - matrix.t[1] = (s32)effect->Pos.vy; - matrix.t[2] = (s32)effect->Pos.vz; + matrix.t[0] = effect->Pos.vx; + matrix.t[1] = effect->Pos.vy; + matrix.t[2] = effect->Pos.vz; CompMatrix(&D_800FA63C.m, &matrix, &matrix); SetRotMatrix(&matrix); SetTransMatrix(&matrix); SetFarColor(0, 0, 0); - BrizadRenderDesc.unkA = spin; - BrizadBufferPtr = func_800D29D4(&BrizadRenderDesc, g_cDb->unk70, 12, BrizadBufferPtr); + g_BrizadRenderDesc.color = fade; + brizad_buffer_ptr = func_800D29D4(&g_BrizadRenderDesc, g_cDb->unk70, 12, brizad_buffer_ptr); if (D_80062D98 == 0) { - nextFrame = (u16)effect->AnimationFrame + 1; - effect->AnimationFrame = nextFrame; - if (nextFrame >= BRIZAD_LIFETIME) { + effect->AnimationFrame++; + if (effect->AnimationFrame >= BRIZAD_LIFETIME) { effect->StartFrame = -1; } } @@ -94,8 +89,9 @@ static void BrizadRenderIce(void) { static void BrizadSpawnIce(void) { BrizadData* next; - BrizadData* effect = &D_80162978[D_8015169C]; + BrizadData* effect; + effect = &D_80162978[D_8015169C]; if (D_80062D98 == 0) { if (effect->AnimationFrame == 0) { next = &D_80162978[BattleEffectRegister(BrizadRenderIce)]; @@ -119,29 +115,24 @@ static void BrizadAttachToTargetUnused(s32 target) { D_80162978[BattleEffectRegister(BrizadSpawnIce)].TargetIndex = target; } -// Registered as the MagicAnimationRegister per-target callback by MAGIC_Brizad. -// Spawns a BrizadSpawnIce slot and tells it which target it belongs to. static void BrizadAttachToTarget(s32 target) { D_80162978[BattleEffectRegister(BrizadSpawnIce)].TargetIndex = target; } -// Points the write pointer at this frame's page, then toggles to the other one. // This slot uses AnimationFrame as a 0/1 page index, not as a frame counter. -// Compare BarrierDoubleBufferFlip in barrier.c and Lv5DeathBufferFlip. static void BrizadDoubleBufferFlip(void) { - BrizadData* flip = &D_80162978[D_8015169C]; + BrizadData* flip; - BrizadBufferPtr = flip->AnimationFrame * BRIZAD_PAGE_SIZE + BrizadPrimBuffer; - flip->AnimationFrame = (u16)flip->AnimationFrame ^ 1; + flip = &D_80162978[D_8015169C]; + brizad_buffer_ptr = &brizad_prim_buffer[flip->AnimationFrame]; + flip->AnimationFrame ^= 1; if (D_80162080 < 2) { flip->StartFrame = -1; } } -// Overlay entry, at 0x801B037C; the D_800EFAF0 dispatch table sends the ice -// magic id here. Registers the buffer flip and the per-target callback, then -// fires the SFX. Unlike barrier and lv5deth this is not a trampoline -- it is -// the last function in the overlay, so it must stay last in this file. +// Overlay entry, at 0x801B037C. This is the last function in the overlay, so +// it must stay last in this file. void MAGIC_Brizad(s32 targetMask, s32 arg1) { BattleEffectRegister(BrizadDoubleBufferFlip); MagicAnimationRegister(targetMask, arg1, 4, BrizadAttachToTarget); - BattleCommandSend(0x20, BattleEntityGetStereoPan((s32)D_80151774), 0x18); + BattleCommandSend(0x20, BattleEntityGetStereoPan(D_80151774), 0x18); } diff --git a/src/magic/lv5deth.c b/src/magic/lv5deth.c index 9ce4cb77..a1373c62 100644 --- a/src/magic/lv5deth.c +++ b/src/magic/lv5deth.c @@ -9,15 +9,23 @@ extern CVECTOR D_800F5B70; extern s16 D_800F5B74; extern u8 D_80062D98; // set while the battle is paused -// One slot of the shared battle effect array, as Lv5 Death lays it out. Only -// StartFrame/AnimationFrame are common to every magic overlay; the remaining -// 0x1C bytes are payload each effect defines for itself -- compare BarrierData -// in barrier.c, which puts its SVECTORs at completely different offsets. +#define TARGET_LIFETIME 45 +#define FADE_IN_FRAMES 8 +#define FADE_OUT_START_FRAME 37 +#define RESULT_POPUP_FRAME 35 + +#define FAR_DEPTH_PER_FRAME 320 +#define FAR_DEPTH_MAX 2560 +#define SCREEN_FADE_LIFETIME 53 + +// Only StartFrame/AnimationFrame are common to every magic overlay; the +// remaining 0x1C bytes are payload each effect lays out for itself. typedef struct Lv5DeathEffect { /* 0x00 */ s16 StartFrame; /* 0x02 */ s16 AnimationFrame; /* 0x04 */ SVECTOR Pos; - /* 0x0C */ u16 Scale; // matrix diagonal handed to func_800D4368 + /* 0x0C */ u16 Scale; // func_800D4368 puts it on all three diagonal + // entries of its matrix /* 0x0E */ union { s16 TargetIndex; // ring / sprite / attach effects s16 FadeOutStartFrame; // screen-fade effect only @@ -28,76 +36,66 @@ typedef struct Lv5DeathEffect { extern Lv5DeathEffect D_80162978[]; // Primitive buffer, one 0xC000 page per double-buffered frame. -extern char Lv5DeathPrimBuffer0[]; -extern char Lv5DeathPrimBuffer1[]; -extern void* Lv5DeathBufferPtr; -extern Lv5DeathEffect* Lv5DeathFlipEffect; // slot running Lv5DeathBufferFlip -extern s32 Lv5DeathTargetsRemaining; -extern u_long Lv5DeathTexture[]; // 8bpp TIM + 256-colour CLUT, uploaded on setup +extern char g_Lv5DeathPrimBuffer0[]; +extern char g_Lv5DeathPrimBuffer1[]; +extern void* g_Lv5DeathBufferPtr; +extern Lv5DeathEffect* g_Lv5DeathFlipEffect; // slot running Lv5DeathBufferFlip +extern s32 g_Lv5DeathTargetsRemaining; +extern u_long g_Lv5DeathTexture[]; // 8bpp TIM + 256-colour CLUT, uploaded on setup // Flat 16-point ring of radius 976 lying in the XY plane at z = -21. -extern s32 Lv5DeathRingModel[]; - -// Render descriptor for the func_800D4D90 pass. Same layout func_800D29D4 -// reads, but this renderer only touches offsets 0x0..0xB, so the instance in -// ROM is truncated to 0xC bytes. -typedef struct { - /* 0x0 */ s32* model; - /* 0x4 */ CVECTOR color; // .cd holds the GPU primitive code (0x2E) - /* 0x8 */ s16 TextureFrame; - /* 0xA */ s16 unkA; -} Lv5DeathDesc; // size:0xC +extern s32 g_Lv5DeathRingModel[]; -extern Lv5DeathDesc Lv5DeathSpriteDesc; +// .color.cd holds the GPU primitive code (0x2E). +extern SpriteRenderDesc g_Lv5DeathSpriteDesc; static void Lv5DeathBufferFlip(void) { - Lv5DeathBufferPtr = g_dbIndex == 0 ? Lv5DeathPrimBuffer0 : Lv5DeathPrimBuffer1; + g_Lv5DeathBufferPtr = g_dbIndex == 0 ? g_Lv5DeathPrimBuffer0 : g_Lv5DeathPrimBuffer1; if (D_80162080 < 2) { - *(s32*)Lv5DeathFlipEffect = -1; + *(s32*)g_Lv5DeathFlipEffect = -1; } } static void Lv5DeathMainSetup(s32 targetMask, s32 arg1); // Overlay entry point, at 0x801B0054; func_800D0C80 dispatches magic id 32 -// here. Trampolines to Lv5DeathMainSetup, which sits at the end of the file. +// here. void MAGIC_Lv5Death(s32 targetMask, s32 arg1) { Lv5DeathMainSetup(targetMask, arg1); } -// The ring spins 0 -> 180 degrees while it fades in, holds, then completes the -// turn on the way out. Scales from 1.0 down to 0.5 over the same window. static void Lv5DeathRenderRing(void) { - // Unused, but required for the match; it gives the function its 0x58 frame. + // Unused; gives the function its 0x58 stack frame. char pad[0x34]; Lv5DeathEffect* effect; - s32 rot; - s32 val; - Unk801B0C98* desc; + s32 scale; + s32 frame; + ModelRenderDesc* desc; effect = &D_80162978[D_8015169C]; - rot = effect->Scale << 16; - func_800D4368(&effect->Pos, rot >> 16, -(rot >> 19)); + // The shift pair sign-extends Scale. + scale = effect->Scale << 16; + func_800D4368(&effect->Pos, scale >> 16, -(scale >> 19)); // Render descriptor built in scratchpad RAM. - desc = (Unk801B0C98*)0x1F800000; - desc->unk0 = Lv5DeathRingModel; - desc->unk4 = 0x88; - desc->unk8 = 0; - desc->unkA = 0x800; - desc->unkC = 0; - desc->unkE = 0; - - val = effect->AnimationFrame; - if (val < 8) { - val <<= 8; - desc->unkA = 0x1000 - val; - } else if (val >= 37) { - desc->unkA = (val << 8) - 0x1D00; + desc = (ModelRenderDesc*)0x1F800000; + desc->model = g_Lv5DeathRingModel; + desc->flags = MODEL_DEPTH_CUE | MODEL_SEMI_TRANS; + desc->uvOffset = 0; + desc->color = 0x800; + desc->tpage = 0; + desc->clut = 0; + + frame = effect->AnimationFrame; + if (frame < FADE_IN_FRAMES) { + frame <<= 8; + desc->color = 0x1000 - frame; + } else if (frame >= FADE_OUT_START_FRAME) { + desc->color = (frame << 8) - 0x1D00; } SetFarColor(0, 0, 0); - Lv5DeathBufferPtr = func_800D29D4(desc, g_cDb->unk70, 12, Lv5DeathBufferPtr); + g_Lv5DeathBufferPtr = func_800D29D4(desc, g_cDb->unk70, 12, g_Lv5DeathBufferPtr); - if (effect->AnimationFrame >= 45) { + if (effect->AnimationFrame >= TARGET_LIFETIME) { effect->StartFrame = -1; } if (D_80062D98 == 0) { @@ -105,74 +103,67 @@ static void Lv5DeathRenderRing(void) { } } -// Textured sprite over the target: an 8-frame texture cycle, fading up to -// half intensity over 8 frames, holding, then fading back out from frame 37. -// The result popup fires at frame 35, the slot retires at 45. static void Lv5DeathRenderTargetSprite(void) { Lv5DeathEffect* effect; s32 frame; - u8 color; + u8 intensity; effect = &D_80162978[D_8015169C]; - Lv5DeathSpriteDesc.TextureFrame = effect->AnimationFrame & 7; + g_Lv5DeathSpriteDesc.frameIndex = effect->AnimationFrame & 7; frame = effect->AnimationFrame; - if (frame < 8) { - color = frame * 16; - } else if (frame >= 37) { - color = -128 - ((frame - 37) * 16); + if (frame < FADE_IN_FRAMES) { + intensity = frame * 16; + } else if (frame >= FADE_OUT_START_FRAME) { + intensity = -128 - ((frame - FADE_OUT_START_FRAME) * 16); } else { - color = 128; + intensity = 128; } - Lv5DeathSpriteDesc.color.r = Lv5DeathSpriteDesc.color.g = Lv5DeathSpriteDesc.color.b = color; + g_Lv5DeathSpriteDesc.color.r = g_Lv5DeathSpriteDesc.color.g = g_Lv5DeathSpriteDesc.color.b = intensity; func_800D4368(&effect->Pos, (s16)effect->Scale, -((s16)effect->Scale >> 2)); - Lv5DeathBufferPtr = func_800D4D90(&Lv5DeathSpriteDesc, g_cDb->unk70, 12, Lv5DeathBufferPtr); + g_Lv5DeathBufferPtr = func_800D4D90(&g_Lv5DeathSpriteDesc, g_cDb->unk70, 12, g_Lv5DeathBufferPtr); - if (effect->AnimationFrame >= 45) { + if (effect->AnimationFrame >= TARGET_LIFETIME) { effect->StartFrame = -1; - Lv5DeathTargetsRemaining--; + g_Lv5DeathTargetsRemaining--; } if (D_80062D98 == 0) { - if (effect->AnimationFrame == 35) { + if (effect->AnimationFrame == RESULT_POPUP_FRAME) { func_800D5774(effect->u.TargetIndex); } effect->AnimationFrame++; } } -// Darkens the whole battlefield by driving the far-colour depth from 0 up to -// 2560 over 8 frames, holding while any target is still animating, then -// easing back down once the last one retires. static void Lv5DeathScreenFade(void) { Lv5DeathEffect* effect; - s32 val; + s32 farDepth; effect = &D_80162978[D_8015169C]; - if (effect->AnimationFrame < 8) { + if (effect->AnimationFrame < FADE_IN_FRAMES) { D_800F5B70.r = D_800F5B70.g = D_800F5B70.b = 0; - val = effect->AnimationFrame * 320; - } else if (Lv5DeathTargetsRemaining <= 0) { + farDepth = effect->AnimationFrame * FAR_DEPTH_PER_FRAME; + } else if (g_Lv5DeathTargetsRemaining <= 0) { if (effect->u.FadeOutStartFrame == 0) { effect->u.FadeOutStartFrame = effect->AnimationFrame; } - val = 2560 - (effect->AnimationFrame - effect->u.FadeOutStartFrame) * 320; + farDepth = FAR_DEPTH_MAX - (effect->AnimationFrame - effect->u.FadeOutStartFrame) * FAR_DEPTH_PER_FRAME; } else { - val = 2560; + farDepth = FAR_DEPTH_MAX; } - if (effect->AnimationFrame >= 53) { - val = 0; + if (effect->AnimationFrame >= SCREEN_FADE_LIFETIME) { + farDepth = 0; effect->StartFrame = -1; } - D_800F5B74 = val; + D_800F5B74 = farDepth; if (D_80062D98 == 0) { effect->AnimationFrame++; } } -// Per-target callback: spawns the sprite and the ring on the target's part. static void Lv5DeathAttachToTarget(s32 target) { Lv5DeathEffect* effect; Lv5DeathEffect* ring; @@ -194,10 +185,11 @@ static void Lv5DeathMainSetup(s32 targetMask, s32 arg1) { s32 count; s32 i; - Lv5DeathFlipEffect = &D_80162978[BattleEffectRegister(Lv5DeathBufferFlip)]; - func_800D2980(Lv5DeathTexture, 0, 0, 0); + g_Lv5DeathFlipEffect = &D_80162978[BattleEffectRegister(Lv5DeathBufferFlip)]; + func_800D2980(g_Lv5DeathTexture, 0, 0, 0); effect = &D_80162978[BattleEffectRegister(Lv5DeathScreenFade)]; effect->u.FadeOutStartFrame = 0; + // frameStep 2: with three targets the pairs spawn on frames 1, 3 and 5. MagicAnimationRegister(targetMask, arg1, 2, Lv5DeathAttachToTarget); i = 0; @@ -207,5 +199,5 @@ static void Lv5DeathMainSetup(s32 targetMask, s32 arg1) { count++; } } - Lv5DeathTargetsRemaining = count; + g_Lv5DeathTargetsRemaining = count; } diff --git a/src/magic/mabaria.c b/src/magic/mabaria.c new file mode 100644 index 00000000..c3cf232a --- /dev/null +++ b/src/magic/mabaria.c @@ -0,0 +1,140 @@ +//! PSYQ=3.3 CC1=2.6.3 + +#include "common.h" +#include "../battle/battle.h" +#include "magic_private.h" + +// Frame 16 would land on 0x7FFE, just under ScaleMatrix's 0x7FFF s16 ceiling. +#define GROWTH_TOTAL 0x3BFF +#define SCALE_BASE 0x400 + +// Fade is the GTE depth cue toward SetFarColor, black here, reaching 0xE00 on +// frame 15. Drawn semi-transparent and blended additively, so black vanishes. +#define MABARIA_LIFETIME 16 +#define FADE_START_FRAME 8 +#define FADE_PER_FRAME 0x200 + +typedef struct { + /* 0x00 */ s16 StartFrame; + /* 0x02 */ s16 AnimationFrame; + /* 0x04 */ SVECTOR Pos; + /* 0x0C */ SVECTOR Rot; + /* 0x14 */ s16 TargetIndex; + /* 0x16 */ char pad16[0xA]; // untouched by the overlay +} MabariaData; // size:0x20 + +typedef struct { + /* 0x00 */ char pad[MAGIC_PAGE_SIZE]; +} MabariaPrimPage; // size:0x10000 + +extern s32 D_801B0CA0; +extern s32 D_801B0CA4; +extern MabariaData D_80162978[]; +extern ModelRenderDesc g_MabariaRenderDesc; +extern MabariaPrimPage g_MabariaPrimBuffer[]; +extern void* g_MabariaBufferPtr; + +static void MabariaMainSetup(s32 arg0, s32 arg1); + +void MAGIC_MBarrier(s32 arg0, s32 arg1) { MabariaMainSetup(arg0, arg1); } + +static void MabariaRenderModel(void) { + MabariaData* effect; + s32 growth; + MATRIX matrix; + s32 fade; + VECTOR scale; + + effect = &D_80162978[D_8015169C]; + growth = effect->AnimationFrame * GROWTH_TOTAL / MABARIA_LIFETIME; + scale.vx = scale.vy = scale.vz = ((growth + SCALE_BASE) * D_801B0CA0) >> 12; + // D_801B0CA4 holds 0, so the lerp passes the fade through. + if (effect->AnimationFrame < FADE_START_FRAME) { + fade = 0; + } else { + fade = (effect->AnimationFrame - FADE_START_FRAME) * FADE_PER_FRAME; + } + fade += ((4096 - fade) * D_801B0CA4) >> 12; + RotMatrixYXZ(&effect->Rot, &matrix); + matrix.t[0] = effect->Pos.vx; + matrix.t[1] = effect->Pos.vy; + matrix.t[2] = effect->Pos.vz; + ScaleMatrix(&matrix, &scale); + CompMatrix(&D_800FA63C.m, &matrix, &matrix); + SetRotMatrix(&matrix); + SetTransMatrix(&matrix); + SetFarColor(0, 0, 0); + g_MabariaRenderDesc.color = fade; + g_MabariaBufferPtr = func_800D29D4(&g_MabariaRenderDesc, g_cDb->unk70, 0xC, g_MabariaBufferPtr); + if (D_80062D98 == 0) { + effect->AnimationFrame++; + if (effect->AnimationFrame >= MABARIA_LIFETIME) { + effect->StartFrame = -1; + } + } +} + +static void MabariaAnimationUpdate(void) { + MabariaData* effect; + MabariaData* next; + + effect = &D_80162978[D_8015169C]; + if (D_80062D98 != 0) { + return; + } + + if (effect->AnimationFrame == 0) { + next = &D_80162978[BattleEffectRegister(MabariaRenderModel)]; + next->Pos = effect->Pos; + next->Rot = effect->Rot; + } + + if (effect->AnimationFrame == 4) { + next = &D_80162978[BattleEffectRegister(MabariaRenderModel)]; + next->Pos = effect->Pos; + next->Rot = effect->Rot; + } + + if (effect->AnimationFrame == 8) { + next = &D_80162978[BattleEffectRegister(MabariaRenderModel)]; + next->Pos = effect->Pos; + next->Rot = effect->Rot; + } + + if (effect->AnimationFrame == 16) { + func_800D5774(effect->TargetIndex); + effect->StartFrame = -1; + } + + effect->AnimationFrame++; +} + +static void MabariaAttachToTarget(s32 target) { + MabariaData* effect; + + effect = &D_80162978[BattleEffectRegister(MabariaAnimationUpdate)]; + BattleGetPartPosition(target, D_801518E4[target].D_8015190F, &effect->Pos); + effect->Pos.vx = effect->Pos.vx - ((rsin(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> 12); + effect->Pos.vz = effect->Pos.vz - ((rcos(D_801518E4[target].unk160.vy) * D_801518E4[target].unk12) >> 12); + effect->Rot = D_801518E4[target].unk160; + effect->TargetIndex = target; +} + +static void MabariaDoubleBufferFlip(void) { + MabariaData* effect; + + effect = &D_80162978[D_8015169C]; + g_MabariaBufferPtr = &g_MabariaPrimBuffer[effect->AnimationFrame]; + effect->AnimationFrame = effect->AnimationFrame ^ 1; + if (D_80162080 < 2) { + effect->StartFrame = -1; + } +} + +static void MabariaMainSetup(s32 arg0, s32 arg1) { + D_801B0CA0 = 0x2000; + D_801B0CA4 = 0; + BattleEffectRegister(MabariaDoubleBufferFlip); + MagicAnimationRegister(arg0, arg1, 0, MabariaAttachToTarget); + BattleCommandSend(0x20, 0x40, 0x43); +} diff --git a/src/magic/magic_private.h b/src/magic/magic_private.h new file mode 100644 index 00000000..4cf9b72c --- /dev/null +++ b/src/magic/magic_private.h @@ -0,0 +1,9 @@ +#ifndef MAGIC_PRIVATE_H +#define MAGIC_PRIVATE_H + +// Shared by the magic overlays only; battle.h stays the public interface. + +// Primitive buffer page. Each overlay holds two and alternates between them. +#define MAGIC_PAGE_SIZE 0x10000 + +#endif diff --git a/src/magic/thunder.c b/src/magic/thunder.c new file mode 100644 index 00000000..d0f5dc3c --- /dev/null +++ b/src/magic/thunder.c @@ -0,0 +1,178 @@ +//! PSYQ=3.3 CC1=2.6.3 + +#include "common.h" +#include "../battle/battle.h" +#include "magic_private.h" + +// Bolt (サンダー / Thunder), tier 1. + +#define MODEL_LIFETIME 16 +#define DIM_START_FRAME 8 +#define GREY_FULL 0x80 +#define GREY_PER_FRAME 0x10 + +#define BOLT_LIFETIME 16 +#define SPARK_START_FRAME 2 + +typedef struct { + /* 0x00 */ s16 StartFrame; + /* 0x02 */ s16 AnimationFrame; + /* 0x04 */ SVECTOR Pos; + /* 0x0C */ SVECTOR unk0C; + /* 0x14 */ s16 unk14; + /* 0x16 */ s16 Scale; // 0x1000 == 1.0 + /* 0x18 */ u16 ScaleStep; // seeded 0x200 + /* 0x1A */ u16 unk1A; + /* 0x1C */ s16 unk1C; + /* 0x1E */ char pad1E[2]; +} ThunderData; // size:0x20 + +typedef struct { + /* 0x00 */ char pad[MAGIC_PAGE_SIZE]; +} ThunderPrimPage; // size:0x10000 + +extern void* g_ThunderBufferPtr; +extern ThunderData D_80162978[]; +extern ThunderPrimPage g_ThunderPrimBuffer[]; +extern u_long g_ThunderTexture[]; // 8bpp TIM + CLUT, uploaded on setup +extern SpriteRenderDesc g_ThunderRenderDesc0; +extern SpriteRenderDesc g_ThunderRenderDesc1; +extern MATRIX g_ThunderModelMatrix; +extern ModelRenderDesc g_ThunderModelDesc; + +static void ThunderMainSetup(s32 arg0, s32 arg1); + +void MAGIC_Thunder(s32 arg0, s32 arg1) { ThunderMainSetup(arg0, arg1); } + +static void ThunderRenderModel(void) { + MATRIX matrix; + ThunderData* effect; + s16 frame; + + effect = &D_80162978[D_8015169C]; + frame = effect->AnimationFrame; + if (frame < DIM_START_FRAME) { + g_ThunderModelDesc.color = GREY_FULL; + } else if (frame < MODEL_LIFETIME) { + g_ThunderModelDesc.color = GREY_FULL - ((frame - DIM_START_FRAME) * GREY_PER_FRAME); + } else { + effect->StartFrame = -1; + return; + } + + g_ThunderModelMatrix.m[0][0] = g_ThunderModelMatrix.m[2][1] = effect->Scale; + g_ThunderModelMatrix.m[1][2] = -effect->Scale; + g_ThunderModelMatrix.t[0] = effect->Pos.vx; + g_ThunderModelMatrix.t[1] = effect->Pos.vy; + g_ThunderModelMatrix.t[2] = effect->Pos.vz; + CompMatrix(&D_800FA63C.m, &g_ThunderModelMatrix, &matrix); + SetRotMatrix(&matrix); + SetTransMatrix(&matrix); + g_ThunderBufferPtr = func_800D29D4(&g_ThunderModelDesc, g_cDb->unk70, 0xC, g_ThunderBufferPtr); + if (D_80062D98 == 0) { + effect->AnimationFrame++; + effect->Scale += effect->ScaleStep; + } +} + +static void func_801B0180(void) { + ThunderData* effect; + + effect = &D_80162978[D_8015169C]; + func_800D4368(&effect->Pos, 0x2000, effect->unk1C); + g_ThunderRenderDesc0.frameIndex = effect->AnimationFrame >> 1; + g_ThunderBufferPtr = func_800D4D90(&g_ThunderRenderDesc0, g_cDb->unk70, 0xC, g_ThunderBufferPtr); + if (D_80062D98 == 0) { + effect->AnimationFrame++; + if (effect->AnimationFrame == 9) { + effect->StartFrame = -1; + } + } +} + +static void func_801B023C(void) { + MATRIX* matrix; + ThunderData* effect; + + effect = &D_80162978[D_8015169C]; + matrix = func_800D4368(&effect->Pos, 0x2000, effect->unk1C); + if (effect->unk1A & 1) { + matrix->m[0][0] = -matrix->m[0][0]; + } + if (effect->unk1A & 2) { + matrix->m[1][1] = -matrix->m[1][1]; + } + SetRotMatrix(matrix); + SetTransMatrix(matrix); + g_ThunderRenderDesc1.frameIndex = effect->AnimationFrame; + g_ThunderBufferPtr = func_800D4D90(&g_ThunderRenderDesc1, g_cDb->unk70, 0xC, g_ThunderBufferPtr); + if (D_80062D98 == 0) { + effect->AnimationFrame++; + if (effect->AnimationFrame == 8) { + effect->StartFrame = -1; + } + } +} + +static void ThunderSpawnBolt(void) { + ThunderData* next; + ThunderData* effect; + + effect = &D_80162978[D_8015169C]; + if (D_80062D98 == 0) { + if (effect->AnimationFrame == 0) { + next = &D_80162978[BattleEffectRegister(func_801B0180)]; + next->Pos = effect->Pos; + next->Pos.vy = 0; + next->unk1C = effect->unk1C; + func_800D5774(effect->unk14); + if (effect->AnimationFrame == 0) { + next = &D_80162978[BattleEffectRegister(ThunderRenderModel)]; + next->Pos = effect->Pos; + next->Scale = 4096; + next->Pos.vy = 0; + next->ScaleStep = 0x200; + next->unk1C = effect->unk1C; + } + } + if (effect->AnimationFrame >= SPARK_START_FRAME) { + next = &D_80162978[BattleEffectRegister(func_801B023C)]; + next->Pos.vx = (effect->Pos.vx + rand() % 1000) - 500; + next->Pos.vy = (effect->Pos.vy + rand() % 1000) - 500; + next->Pos.vz = (effect->Pos.vz + rand() % 1000) - 500; + next->unk1A = rand() & 3; + next->unk1C = effect->unk1C; + } + effect->AnimationFrame++; + if (effect->AnimationFrame == BOLT_LIFETIME) { + effect->StartFrame = -1; + } + } +} + +static void ThunderAttachToTarget(s32 target) { + ThunderData* effect; + + effect = &D_80162978[BattleEffectRegister(ThunderSpawnBolt)]; + BattleGetPartPosition(target, D_801518E4[target].D_8015190F, &effect->Pos); + effect->unk14 = target; + effect->unk1C = -D_801518E4[target].unk12; + BattleCommandSend(0x20, BattlePositionToStereoPan(&effect->Pos), 0xB); +} + +static void ThunderDoubleBufferFlip(void) { + ThunderData* data; + + data = &D_80162978[D_8015169C]; + g_ThunderBufferPtr = &g_ThunderPrimBuffer[data->AnimationFrame]; + data->AnimationFrame = data->AnimationFrame ^ 1; + if (D_80162080 < 2) { + data->StartFrame = -1; + } +} + +static void ThunderMainSetup(s32 arg0, s32 arg1) { + func_800D2980(g_ThunderTexture, 0, 0, 0); + BattleEffectRegister(ThunderDoubleBufferFlip); + MagicAnimationRegister(arg0, arg1, 2, ThunderAttachToTarget); +} diff --git a/tools/ninja/gen.py b/tools/ninja/gen.py index 1b629064..3f31e8df 100644 --- a/tools/ninja/gen.py +++ b/tools/ninja/gen.py @@ -433,5 +433,7 @@ def get_ovl_elf_list(file_path) -> list[str]: "barrier", "lv5deth", "brizad", + "thunder", + "mabaria", ]: add_splat_config(os.path.join(work_dir, f"{ovl}.yaml"))