From d0415abbea4bfa0effc2fa2309830a1b71fbc572 Mon Sep 17 00:00:00 2001 From: rengstrom Date: Tue, 8 Sep 2026 15:06:56 +0000 Subject: [PATCH] Decompile the effect_028 overlay Curaga's overlay, all 126 functions, with its own header for the storage and record types the unit publishes. Most of the engine came across from the two overlays already decompiled: the eight mesh emitters and the entry point are near-twins of effect_025's and effect_001's, and func_801A5D4C/func_801A5720 are this overlay's private copies of bc_object15's sprite packet setup and emitter. The rest is the Curaga script itself -- the draw list, the strand and trail geometry, the node spawner and the pose walker. The types the overlay works in are now named. A script's entity is an EffectEntity header plus a private tail, so the allocator hands back plain storage and each script declares its own record; EffectDrawScript and EffectTintScript are each exactly the size their func_801A0B5C call asks for. A pose step's 0xC8..0x144 is thirty-two per-frame tables stored as offsets, which func_801A8CE4 relocates once per effect. Along the way, three fields that shared code with battle were mistyped: BattleGfx's 0x14 is a small ordering table for screen overlays, not a glyph set, and BattleEffectSlot carries an SVECTOR at 0x1C whose Y is the origin a model's bounds are summed with. Correcting them touches effect_001 and effect_025, which read the same fields. gte_ldfcb joins inline_c.h: the far-colour load from three 8-bit components, scaled into the GTE's 1/16 units, which gte_ldfc does not cover. --- Makefile | 2 +- config/ff8.yaml | 2 +- include/battle.h | 17 +- include/effect.h | 4 +- include/effect/effect_028.h | 376 +++ include/psxsdk/inline_c.h | 13 + src/effect/effect_001.c | 12 +- src/effect/effect_025.c | 12 +- src/effect/effect_028.c | 4490 ++++++++++++++++++++++++++++++++++- 9 files changed, 4782 insertions(+), 146 deletions(-) create mode 100644 include/effect/effect_028.h diff --git a/Makefile b/Makefile index 2ae2e84e..c68d8bca 100644 --- a/Makefile +++ b/Makefile @@ -140,7 +140,7 @@ CODE_OVERLAYS := field_init intro field \ tripletriad battle_render battle world ALL_EFFECT_OVERLAYS := $(filter effect_%,$(SPLAT_BINARIES)) -EFFECTS ?= effect_001 effect_025 +EFFECTS ?= effect_001 effect_025 effect_028 EFFECT_OVERLAYS := $(if $(filter all,$(EFFECTS)),$(ALL_EFFECT_OVERLAYS),$(EFFECTS)) OVERLAYS := $(MENU_OVERLAYS) $(CODE_OVERLAYS) $(EFFECT_OVERLAYS) ALL_OVERLAYS := $(MENU_OVERLAYS) $(CODE_OVERLAYS) $(ALL_EFFECT_OVERLAYS) diff --git a/config/ff8.yaml b/config/ff8.yaml index cc00c6bc..36c9e38c 100644 --- a/config/ff8.yaml +++ b/config/ff8.yaml @@ -1092,7 +1092,7 @@ binaries: subsegments: - [0x0, c, effect_028] - [0x9EFC, .rodata, effect_028] - - [0x9F10, bin, effect_028] + - [0x9FA0, bin, effect_028] - [0x3CCB0] - name: effect_029 sha1: 2f817a2bd73ec621f623440889ade98973bd1131 diff --git a/include/battle.h b/include/battle.h index ef5f90ee..220e15b3 100644 --- a/include/battle.h +++ b/include/battle.h @@ -1328,13 +1328,20 @@ struct EffectAttachment { */ typedef struct BattleEffectSlot { /* 0x00 */ u16 flags; /**< See @c BATTLE_SLOT_FLAG_*. */ - /* 0x02 */ u8 pad002[0x1E - 0x2]; - /* 0x1E */ u16 unk01E; /**< Y origin: summed with a model's Y bounds. */ - /* 0x20 */ u8 pad020[0x24 - 0x20]; + /* 0x02 */ u8 pad002[0xE - 0x2]; + /* 0x0E */ s16 facing; /**< Angle the slot's model is turned to. */ + /* 0x10 */ u8 pad010[0x1C - 0x10]; + /* 0x1C */ SVECTOR pos; /**< Where the slot's model stands; @c vy is + the origin a model's Y bounds are summed + with. */ /* 0x24 */ u16 unk024; /* 0x26 */ u8 pad026[0x28 - 0x26]; /* 0x28 */ u32 unk028; /**< Packed RGB the textured prims are drawn with. */ - /* 0x2C */ u8 pad02C[0x40 - 0x2C]; + /* 0x2C */ u8 pad02C[0x36 - 0x2C]; + /* 0x36 */ u16 unk036; + /* 0x38 */ u8 pad038[0x3C - 0x38]; + /* 0x3C */ s16 unk03C; /**< Its distance above @c unk036 sizes the draw list. */ + /* 0x3E */ u8 pad03E[0x40 - 0x3E]; /* 0x40 */ MATRIX mtx; /**< Pose the effect's render matrices start from. */ /* 0x60 */ u8 unk060[0x64 - 0x60]; /* 0x64 */ struct EffectMesh *mesh; @@ -1357,7 +1364,7 @@ typedef struct BattleEffectSlot { */ typedef struct { /* 0x00 */ u8 pad000[0x14]; - /* 0x0014 */ u8 font[0x44 - 0x14]; /**< Glyph set handed to the text drawer. */ + /* 0x0014 */ u32 frontOT[(0x44 - 0x14) / 4]; /**< Ordering table the screen overlays link into. */ /* 0x0044 */ u32 ot[(0x4040 - 0x44) / 4]; /**< Ordering table the effects link into. */ /* 0x4040 */ u8 unk4040[4]; } BattleGfx; diff --git a/include/effect.h b/include/effect.h index 9ab17be8..769ada62 100644 --- a/include/effect.h +++ b/include/effect.h @@ -49,7 +49,9 @@ typedef struct EffectModel { /* 0x40 */ SVECTOR unk040; /* 0x48 */ SVECTOR boundsMin; /**< Lower corner of the model's bounding box. */ /* 0x50 */ SVECTOR boundsMax; /**< Upper corner of the model's bounding box. */ - /* 0x58 */ u8 pad058[0x63 - 0x58]; + /* 0x58 */ u8 pad058[0x5A - 0x58]; + /* 0x5A */ u16 unk05A; /**< How many strands an effect builds from it. */ + /* 0x5C */ u8 pad05C[0x63 - 0x5C]; /* 0x63 */ u8 unk063; /* 0x64 */ u8 pad064[0x100 - 0x64]; } EffectModel; /* >= 0x100 */ diff --git a/include/effect/effect_028.h b/include/effect/effect_028.h new file mode 100644 index 00000000..d2a91a15 --- /dev/null +++ b/include/effect/effect_028.h @@ -0,0 +1,376 @@ +#ifndef EFFECT_EFFECT_028_H +#define EFFECT_EFFECT_028_H + +#include "effect.h" + +/** + * @file + * @brief Curaga: the overlay's own storage. + */ + +/** + * @brief Start the effect's script and hand back its task pool. + * + * @param animSet Animation set the effect plays. + * @return The pool the root entity lives in. + */ +void *func_801A0000(EffectAnimSet *animSet); + +/** @brief A packet's far colour: three bytes and the code byte beside them. */ +typedef struct { + /* 0x00 */ u8 r; + /* 0x01 */ u8 g; + /* 0x02 */ u8 b; + /* 0x03 */ u8 code; +} EffectPrimColor; /* 0x04 */ + +/** @brief One step of the pose script the emitters read. */ +typedef struct { + /* 0x00 */ s32 offsetX; /**< Offset added to the point, 16.16. */ + /* 0x04 */ s32 offsetY; + /* 0x08 */ s32 offsetZ; + /* 0x0C */ u8 pad00C[0x11 - 0xC]; + /* 0x11 */ u8 unk011; + /* 0x12 */ u8 unk012; /**< Frames a mote taken from this source lives. */ + /* 0x13 */ u8 unk013; /**< Frame of the list's life the source fires on. */ + /* 0x14 */ u8 pad014[0x16 - 0x14]; + /* 0x16 */ u8 mode; /**< 4 and 5 take the late step. */ + /* 0x17 */ u8 unk017; + /* 0x18 */ u8 unk018; + /* 0x19 */ u8 unk019; + /* 0x1A */ u8 unk01A; + /* 0x1B */ u8 unk01B; /**< Set, the pose's Y is flattened every frame. */ + /* 0x1C */ u8 unk01C; + /* 0x1D */ u8 unk01D; /**< Which order the pose's three turns are applied in. */ + /* 0x1E */ u8 kind; /**< Selects which of the four steps runs. */ + /* 0x1F */ u8 unk01F; + /* 0x20 */ u8 unk020; + /* 0x21 */ u8 unk021; /**< Where a spawned node's own spin comes from. */ + /* 0x22 */ u8 unk022; /**< Where the posed offsets are written back to. */ + /* 0x23 */ u8 pad023[0x25 - 0x23]; + /* 0x25 */ u8 anchor; /**< Which of the trail's points the motes start at. */ + /* 0x26 */ u8 unk026; /**< Set, the pose is turned before it is applied. */ + /* 0x27 */ u8 unk027; /**< Which way the pose's offsets are built. */ + /* 0x28 */ u8 unk028; + /* 0x29 */ u8 unk029; + /* 0x2A */ u8 unk02A; + /* 0x2B */ u8 unk02B; + /* 0x2C */ u8 unk02C; + /* 0x2D */ u8 unk02D; + /* 0x2E */ u8 unk02E; + /* 0x2F */ u8 unk02F; + /* 0x30 */ u8 unk030; + /* 0x31 */ u8 unk031; + /* 0x32 */ u8 pad032; + /* 0x33 */ u8 unk033; /**< Set, the spawn speeds are taken as they come. */ + /* 0x34 */ u8 unk034; /**< Which way a spawned node is aimed. */ + /* 0x35 */ u8 midpoint; /**< Set, motes start midway along the node. */ + /* 0x36 */ u8 unk036; /**< Splits a frame's work into chunks this big. */ + /* 0x37 */ u8 unk037; /**< Set, the source fires once per strand point. */ + /* 0x038 */ s16 unk038; /**< Added to the node's Z once it is placed. */ + /* 0x03A */ s16 unk03A; + /* 0x03C */ SVECTOR unk03C; + /* 0x044 */ SVECTOR unk044; + /* 0x04C */ s32 unk04C; + /* 0x050 */ s32 unk050; + /* 0x054 */ s32 unk054; + /* 0x058 */ s32 unk058; + /* 0x05C */ s32 unk05C; + /* 0x060 */ s32 unk060; + /* 0x064 */ u16 unk064; /**< Drag a spawned node keeps its motion with. */ + /* 0x066 */ u8 pad066[0x68 - 0x66]; + /* 0x068 */ VECTOR unk068; + /* 0x078 */ VECTOR unk078; + /* 0x088 */ VECTOR unk088; + /* 0x098 */ VECTOR unk098; + /* 0x0A8 */ VECTOR unk0A8; + /* 0x0B8 */ VECTOR unk0B8; + /* + * 0x0C8 to 0x144 is one table per animated property, each indexed by the + * frame the node is showing. They are stored as offsets from the pose + * tables' own start; @ref func_801A8CE4 turns all thirty-two into + * addresses the first time a script uses them. + */ + /* 0x0C8 */ u8 *unk0C8; /**< How many motes the source takes that frame. */ + /* 0x0CC */ u16 *unk0CC; + /* 0x0D0 */ u16 *unk0D0; + /* 0x0D4 */ u16 *unk0D4; + /* 0x0D8 */ u16 *unk0D8; + /* 0x0DC */ u16 *unk0DC; + /* 0x0E0 */ u16 *unk0E0; + /* 0x0E4 */ u16 *unk0E4; + /* 0x0E8 */ u16 *unk0E8; + /* 0x0EC */ u16 *unk0EC; + /* 0x0F0 */ u8 *unk0F0; + /* 0x0F4 */ u8 *unk0F4; + /* 0x0F8 */ u8 *unk0F8; + /* 0x0FC */ u16 *unk0FC; + /* 0x100 */ u8 *unk100; + /* 0x104 */ u8 *unk104; + /* 0x108 */ u8 *unk108; + /* 0x10C */ u8 *unk10C; + /* 0x110 */ u16 *unk110; + /* 0x114 */ u16 *unk114; + /* 0x118 */ u16 *unk118; + /* 0x11C */ u16 *unk11C; + /* 0x120 */ u16 *unk120; + /* 0x124 */ u16 *unk124; + /* 0x128 */ s16 *unk128; + /* 0x12C */ u8 *targets; /**< One target point set per frame. */ + /* 0x130 */ u8 *framesA; /**< First source point set per frame. */ + /* 0x134 */ u8 *framesB; /**< Second source point set per frame. */ + /* 0x138 */ s16 *weights; /**< Weight of the second source, per frame. */ + /* 0x13C */ s16 *unk13C; + /* 0x140 */ s16 *unk140; + /* 0x144 */ s16 *unk144; + /* 0x148 */ SVECTOR unk148; + /* 0x150 */ SVECTOR unk150; +} EffectPoseStep; /* 0x158 */ + +/** + * @brief The tighter of the two record kinds the draw list carries. + * + * Its bank packs these 0x6C apart, so it shares only the head of + * @ref EffectDrawNode and is linked through the same two pointers. + */ +typedef struct EffectMote { + /* 0x00 */ struct EffectDrawNode *prev; + /* 0x04 */ struct EffectDrawNode *next; + /* 0x08 */ u16 unk008; + /* 0x0A */ u8 pad00A[0x2C - 0xA]; + /* 0x2C */ MATRIX mtx; /**< Turns the step's offset into world space. */ + /* 0x4C */ VECTOR pos; /**< Where the mote sits, 16.16. */ + /* 0x5C */ struct EffectDrawNode *owner; + /* 0x60 */ s16 unk060; /**< Frames this spark has been drawn for. */ + /* 0x62 */ s16 unk062; /**< Zero until it has been set up once. */ + /* 0x64 */ s16 life; /**< Counts down; the spark unlinks at zero. */ + /* 0x66 */ u8 pad066[0x68 - 0x66]; + /* 0x68 */ u8 kind; /**< Selects the per-frame step below. */ + /* 0x69 */ u8 taken; /**< Non-zero while this slot is in use. */ + /* 0x6A */ u8 unk06A; /**< Which of the list's sources drives it. */ + /* 0x6B */ u8 unk06B; +} EffectMote; /* 0x6C */ + +/** @brief One entry on the effect's draw list, linked both ways. */ +typedef struct EffectDrawNode { + /* 0x000 */ struct EffectDrawNode *prev; + /* 0x004 */ struct EffectDrawNode *next; + /* 0x008 */ s16 unk008; + /* 0x00A */ u8 pad00A[0xC - 0xA]; + /* 0x00C */ MATRIX poses[2]; /**< One per copy; only two fit before the header. */ + /* 0x04C */ u8 pad04C[0x60 - 0x4C]; + /* 0x060 */ u16 unk060; /**< Frames this node has been drawn for. */ + /* 0x062 */ s16 unk062; /**< Zero until the node has been set up once. */ + /* 0x064 */ s16 life; /**< Counts down; the node unlinks at zero. */ + /* 0x066 */ u8 pad066[0x68 - 0x66]; + /* 0x068 */ u8 kind; /**< Selects the per-frame step below. */ + /* 0x069 */ u8 unk069; + /* 0x06A */ u8 unk06A; /**< Which of the list's sources drives the emitter. */ + /* 0x06B */ u8 pad06B[0x8C - 0x6B]; + /* 0x08C */ MATRIX orient; /**< Turn the motes hung off this node inherit. */ + /* 0x0AC */ MATRIX mtx; /**< Pose the node's model is drawn with. */ + /* 0x0CC */ SVECTOR angle; /**< This frame's turn, wrapped to a whole circle. */ + /* 0x0D4 */ s16 scaleX; /**< This frame's scale, before it is applied. */ + /* 0x0D6 */ s16 scaleY; + /* 0x0D8 */ s16 scaleZ; + /* 0x0DA */ u8 pad0DA[0xDC - 0xDA]; + /* 0x0DC */ VECTOR pos[4]; /**< Where each copy sits. */ + /* 0x11C */ VECTOR lastPos; /**< Where the first copy sat last frame. */ + /* 0x12C */ VECTOR offset[4]; /**< The turned offset each copy is drawn at. */ + /* 0x16C */ EffectPrimColor color; + /* 0x170 */ void *stream; /**< What the node draws -- a mesh stream or a + sprite animation, by kind -- or NULL. */ + /* 0x174 */ s32 spin[4]; /**< How far each copy has spun about its pose. */ + /* 0x184 */ s32 spinRate[4]; + /* 0x194 */ SVECTOR angleBase; /**< The turn this frame's angle starts from. */ + /* 0x19C */ SVECTOR offsets[1]; /**< Where each copy past the first is placed. */ + /* 0x1A4 */ u8 pad1A4[0x1BC - 0x1A4]; + /* 0x1BC */ struct EffectMote *owner; + /* 0x1C0 */ u8 pad1C0[0x1C8 - 0x1C0]; + /* 0x1C8 */ s16 unk1C8; /**< Counts down; a step fires when it goes negative. */ + /* 0x1CA */ s16 unk1CA; /**< Which frame of the pose the node is showing. */ + /* 0x1CC */ s16 unk1CC; /**< Which phase of its life the node is in. */ + /* 0x1CE */ s16 unk1CE; /**< Depth cue: zero leaves the node unshaded. */ + /* 0x1D0 */ u8 unk1D0; /**< Frames the current step has held for. */ + /* 0x1D1 */ u8 unk1D1; /**< How long it holds before advancing. */ + /* 0x1D2 */ u8 unk1D2; + /* 0x1D3 */ u8 unk1D3; + /* 0x1D4 */ u8 unk1D4; + /* 0x1D5 */ u8 unk1D5; /**< Repeats left before the hold gives up. */ + /* 0x1D6 */ u8 unk1D6; /**< Which of the list's sources this node came from. */ + /* 0x1D7 */ u8 unk1D7; + /* 0x1D8 */ u8 copies; /**< How many times the node draws its model. */ + /* 0x1D9 */ u8 unk1D9; + /* 0x1DA */ s16 unk1DA; /**< Drag: how much of each frame's motion is kept. */ + /* 0x1DC */ s32 unk1DC; /**< Added to every copy's Y velocity each frame. */ + /* 0x1E0 */ VECTOR vel[4]; + /* 0x220 */ VECTOR accel[4]; + /* 0x260 */ VECTOR jerk[4]; +} EffectDrawNode; /* 0x2A0 */ + +/** @brief A set of posed points: a two-word header, then @c count of them. */ +typedef struct EffectPointSet { + /* 0x00 */ s32 unk000; + /* 0x04 */ s32 count; + /* 0x08 */ SVECTOR points[1]; +} EffectPointSet; + +/** + * @brief The tables a script is handed: sixteen sources and their point sets. + * + * @ref func_801A8CE4 fills the pointers in once and sets @c ready so a restart + * does not do it again. + */ +typedef struct { + /* 0x000 */ s32 ready; + /* 0x004 */ EffectPoseStep *sources[16]; + /* 0x044 */ void *unk044[16]; + /* 0x084 */ EffectPointSet *targets[16]; + /* 0x0C4 */ EffectPointSet *frames[16]; +} EffectPoseTables; /* 0x104 */ + +/** @brief The draw list the emitters walk, with its head and tail. */ +typedef struct { + /* 0x000 */ s32 spread; /**< Distance between the two slots the effect spans. */ + /* 0x004 */ s32 parts[4]; /**< The model part each strand is built from. */ + /* 0x014 */ u16 count; + /* 0x016 */ u16 live; /**< Nodes still on the list. */ + /* 0x018 */ s16 age; /**< Frames this list has been running. */ + /* 0x01A */ s16 span; /**< How long it runs before it may finish. */ + /* 0x01C */ s16 strandLen; /**< How many points of each strand are used. */ + /* 0x01E */ s16 slot; /**< Battle slot the effect is aimed at. */ + /* 0x020 */ s16 phase; /**< Which stage of its life the list is in. */ + /* 0x022 */ u16 held; /**< One bit per source: set, the source is held off. */ + /* 0x024 */ s16 unk024; + /* 0x026 */ u8 pad026[0x2C - 0x26]; + /* 0x02C */ EffectDrawNode *head; + /* 0x030 */ EffectDrawNode *tail; + /* 0x034 */ u8 pad034[0x54 - 0x34]; + /* 0x054 */ VECTOR strands[5][4]; /**< The five strands the effect draws. */ + /* 0x194 */ VECTOR strandSeed; /**< The point every strand starts from. */ + /* 0x1A4 */ VECTOR strandHead; /**< The newest point, pushed on each frame. */ + /* 0x1B4 */ VECTOR trail[6]; /**< Where the head has been, newest first. */ + /* 0x214 */ s32 unk214; + /* 0x218 */ s32 unk218; + /* 0x21C */ s32 unk21C; + /* 0x220 */ u8 pad220[0x224 - 0x220]; + /* 0x224 */ EffectPoseStep **sources; + /* 0x228 */ void **unk228; + /* 0x22C */ struct EffectPointSet **targets; /**< Point sets the poses write. */ + /* 0x230 */ struct EffectPointSet **frames; /**< Point sets the poses read. */ +} EffectDrawList; /* 0x234 */ + +/** @brief The one draw list this effect builds into. */ +extern EffectDrawList *D_801D3F84; + +/** @brief One step of the tint script: a colour and what to do with it. */ +typedef struct { + /* 0x00 */ u8 r; + /* 0x01 */ u8 g; + /* 0x02 */ u8 b; /**< A hold step keeps the frame it waits for here. */ + /* 0x03 */ u8 op; /**< 0xFF ends the script, 0xFE holds, anything else steps. */ +} EffectTintStep; /* 0x04 */ + +/** @brief Cursors this frame's prims and frames are written through. */ +extern void *D_801D479C; + +extern s32 D_801D49FC; + +/** @brief The sixteen hex digit glyphs, cached from the main string table. */ +extern u8 D_801D4ADC[]; + +/** @brief Debug text cursor: X, Y, and the colour the glyphs are drawn in. */ +extern s32 D_801D4AF0; + +extern s32 D_801D4AF4; + +extern s32 D_801D4AF8; + +/** @brief The two prim banks the emitters write through. */ +extern EffectMote *D_801D3F7C; +extern EffectDrawNode *D_801D3F80; + +/** @brief Frame counters the script resets when it restarts. */ +extern s16 D_801D3F88; +extern s16 D_801D3F8A; +extern s16 D_801D3F90; +extern s16 D_801D3F92; + +/** @brief Pose script the model-taking handler runs. */ +extern EffectPoseTables D_801D4AFC; + +/** @brief Pool the trailing sparks are allocated from. */ +extern s32 D_801D3F6C; + +/** @brief Pool the pose-script children are allocated from. */ +extern s32 D_801D478C; + +/** @brief Cursor the prim banks are carved off, moving up as they are taken. */ +extern u8 *D_801D3CDC; + +/** + * @name Overlay blob + * + * The data carried in the overlay image behind the code. + * @{ + */ + +/** @brief Table of two offsets battle.bin resolves when the effect starts. */ +extern u8 D_801AA10C; + +/** + * @brief TIM uploaded to VRAM when the effect starts. + * + * Once it is in VRAM the image memory is reused: the three prim and frame + * banks the renderer double-buffers through start at this address. + */ +extern u8 D_801AACAC; +/** @} */ + +/** @brief Task pools the script's children run in. */ +extern s32 D_801D488C; +extern s32 D_801D47BC; +extern s32 D_801D489C; +extern s32 D_801D4A0C; +extern s32 D_801D3F9C; +extern s32 D_801D3CEC; + +/** @brief Cleared when the effect starts. */ +extern s32 D_801D3CCC; +extern s32 D_801D3CD0; + +/** @brief Top of the scratchpad stack the draw packets are built on. */ +extern u8 *D_801D3CD8; + +/** @brief Where that stack started, kept so the frame can be unwound. */ +extern MATRIX *D_801D3CD4; + +/** + * @name Prim buffers + * + * Two of each, so the frame being built and the frame being drawn never share. + * @{ + */ +extern void *D_801D47A4; +extern void *D_801D47A8; +extern void *D_801D47AC; +extern void *D_801D47B0; +/** @} */ + +/** @brief Cursor the effect's second prim buffer is written through. */ +extern void *D_801D47A0; + +/** @brief Sound cue the take-model script fires on its first frame. */ +extern u8 D_801A9FAC[]; + +/** @brief Argument the frame-20 child is started with. */ +extern EffectTintStep D_801A9FA0[]; + +/** @brief Pool the frame-20 child is allocated from. */ +extern s32 D_801D4ACC; + +/** @brief Running totals the draw list adds into every frame. */ +extern u16 D_801D3F8C; +extern u16 D_801D3F8E; + +#endif /* EFFECT_EFFECT_028_H */ diff --git a/include/psxsdk/inline_c.h b/include/psxsdk/inline_c.h index eea297f1..bc87e69d 100644 --- a/include/psxsdk/inline_c.h +++ b/include/psxsdk/inline_c.h @@ -34,6 +34,19 @@ : \ : "r"(r0), "r"(r1), "r"(r2)) +/* Load the far colour (RFC, GFC, BFC) from three 8-bit components, scaled into + * the GTE's 1/16 units. gte_ldfc takes a VECTOR already in those units. */ +#define gte_ldfcb(r0, r1, r2) __asm__ volatile ( \ + "sll $12, %0, 4;" \ + "sll $13, %1, 4;" \ + "sll $14, %2, 4;" \ + "ctc2 $12, $21;" \ + "ctc2 $13, $22;" \ + "ctc2 $14, $23" \ + : \ + : "r"(r0), "r"(r1), "r"(r2) \ + : "$12", "$13", "$14") + /* NCLIP -- outer product of the three screen points, sign gives the winding. */ #define gte_nclip() __asm__ volatile ( \ "nop;" \ diff --git a/src/effect/effect_001.c b/src/effect/effect_001.c index 6341e559..b1d5aac6 100644 --- a/src/effect/effect_001.c +++ b/src/effect/effect_001.c @@ -110,7 +110,7 @@ static void func_801A01F0(void) { /** @brief Draw @p value as eight hex glyphs at the current debug text cursor. */ static void func_801A0340(u32 value) { - u8 *font = D_800FA5E8->font; + u32 *ot = D_800FA5E8->frontOT; u8 text[9]; s32 i; @@ -119,7 +119,7 @@ static void func_801A0340(u32 value) { value >>= 4; } text[8] = 0; - D_801C58F4 = func_8002C56C(font, D_801C58F4, D_801D5D14, D_801D5D18, + D_801C58F4 = func_8002C56C(ot, D_801C58F4, D_801D5D14, D_801D5D18, text, D_801D5D1C); D_801D5D14 += 0x48; } @@ -557,14 +557,14 @@ static s16 func_801A13E4(EffectEntity *entity) { static s16 func_801A1408(EffectEntity *entity) { BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - return slot->unk01E + entity->unk014->boundsMax.vy; + return slot->pos.vy + entity->unk014->boundsMax.vy; } /** @brief Y of the bottom of the linked model, in battle-entity space. */ static s16 func_801A1450(EffectEntity *entity) { BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - return slot->unk01E + entity->unk014->boundsMin.vy; + return slot->pos.vy + entity->unk014->boundsMin.vy; } /** @brief A random point up the linked model, in battle-entity space. */ @@ -574,14 +574,14 @@ static s16 func_801A1498(EffectEntity *entity) { s16 height = model->boundsMax.vy - model->boundsMin.vy; s16 offset = (rand() & 0xFFF) * height / 4096; - return slot->unk01E + model->boundsMax.vy - offset / 2; + return slot->pos.vy + model->boundsMax.vy - offset / 2; } /** @brief Y of the centre of the linked model, in battle-entity space. */ static s16 func_801A1550(EffectEntity *entity) { EffectModel *model = entity->unk014; - return D_800EF2D0[entity->unk02D].unk01E + + return D_800EF2D0[entity->unk02D].pos.vy + (model->boundsMax.vy + model->boundsMin.vy) / 2; } diff --git a/src/effect/effect_025.c b/src/effect/effect_025.c index 69021308..8a61d440 100644 --- a/src/effect/effect_025.c +++ b/src/effect/effect_025.c @@ -529,7 +529,7 @@ static void func_801A014C(void) { /** @brief Draw @p value as eight hex glyphs at the current debug text cursor. */ static void func_801A029C(u32 value) { - u8 *font = D_800FA5E8->font; + u32 *ot = D_800FA5E8->frontOT; u8 text[9]; s32 i; @@ -538,7 +538,7 @@ static void func_801A029C(u32 value) { value >>= 4; } text[8] = 0; - D_801D3EB8 = func_8002C56C(font, D_801D3EB8, D_801ED418, D_801ED41C, + D_801D3EB8 = func_8002C56C(ot, D_801D3EB8, D_801ED418, D_801ED41C, text, D_801ED420); D_801ED418 += 0x48; } @@ -1473,14 +1473,14 @@ static s16 func_801A2AA4(EffectEntity *entity) { static s16 func_801A2AC8(EffectEntity *entity) { BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - return slot->unk01E + entity->unk014->boundsMax.vy; + return slot->pos.vy + entity->unk014->boundsMax.vy; } /** @brief Y of the bottom of the linked model, in battle-entity space. */ static s16 func_801A2B10(EffectEntity *entity) { BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; - return slot->unk01E + entity->unk014->boundsMin.vy; + return slot->pos.vy + entity->unk014->boundsMin.vy; } /** @brief A random point up the linked model, in battle-entity space. */ @@ -1490,14 +1490,14 @@ static s16 func_801A2B58(EffectEntity *entity) { s16 height = model->boundsMax.vy - model->boundsMin.vy; s16 offset = (rand() & 0xFFF) * height / 4096; - return slot->unk01E + model->boundsMax.vy - offset / 2; + return slot->pos.vy + model->boundsMax.vy - offset / 2; } /** @brief Y of the centre of the linked model, in battle-entity space. */ static s16 func_801A2C10(EffectEntity *entity) { EffectModel *model = entity->unk014; - return D_800EF2D0[entity->unk02D].unk01E + + return D_800EF2D0[entity->unk02D].pos.vy + (model->boundsMax.vy + model->boundsMin.vy) / 2; } diff --git a/src/effect/effect_028.c b/src/effect/effect_028.c index 98c3afc4..7eca2a5c 100644 --- a/src/effect/effect_028.c +++ b/src/effect/effect_028.c @@ -3,261 +3,4499 @@ * @brief Curaga */ #include "common.h" +#include "game.h" +#include "effect.h" +#include "psxsdk/libc.h" +#include "psxsdk/libgpu.h" +#include "psxsdk/inline_c.h" +#include "effect/effect_028.h" +#include "btl_entity.h" +#include "battle/bc_object11.h" +#include "battle/bc_object15.h" +#include "battle/bc_object13.h" -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0000); +/** @brief Where in the scratchpad the effect keeps its view matrix. */ +#define EFFECT_SCRATCHPAD ((MATRIX *)0x1F8002E0) -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0208); +/** @brief Stride between the prim and frame banks carved out of the TIM. */ +#define EFFECT_BANK_SIZE 0x6000 -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0358); +/** @brief @ref EffectEntity::unk05C -- the frame counter's low bit picks a bank. */ +#define EFFECT_FRAME_ODD 0x1 -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0404); +/** @brief Pack a screen corner the way the tint quads carry them. */ +#define EFFECT_TINT_XY(x, y) ((x) | ((y) << 16)) -void func_801A0424(void) { +/** @brief Prim code of a tint quad: a semi-transparent flat quad. */ +#define EFFECT_TINT_CODE 0x2A + +/** @brief Bytes cleared in each prim bank when the script restarts. */ +#define EFFECT_BANK0_BYTES 0x25F8 +/** @brief How far the mote ring runs before it wraps. */ +#define EFFECT_MOTE_COUNT 0x59 +/** @brief How far the node ring runs before it wraps. */ +#define EFFECT_NODE_COUNT 0x8B +#define EFFECT_BANK1_BYTES 0x16F80 + + +/** + * @name Prim emitter flags -- @ref EffectPrimBuild::flags. + * @{ + */ +#define EFFECT_EMIT_SEMITRANS 0x1 +#define EFFECT_EMIT_OPAQUE 0x4 +#define EFFECT_EMIT_TWO_SIDED 0x10 +#define EFFECT_EMIT_DEPTH_CUE 0x40 + +/* The gouraud emitters read their own copy of each of the four bits above. */ +#define EFFECT_EMIT_G_SEMITRANS 0x2 +#define EFFECT_EMIT_G_OPAQUE 0x8 +#define EFFECT_EMIT_G_TWO_SIDED 0x20 +#define EFFECT_EMIT_G_DEPTH_CUE 0x80 +#define EFFECT_EMIT_TPAGE_SET 0x100 +#define EFFECT_EMIT_CLUT_SET 0x200 +#define EFFECT_EMIT_TPAGE_ADD 0x400 +#define EFFECT_EMIT_CLUT_ADD 0x800 +/** @brief Set, the vertex buffer the stream names is already resolved. */ +#define EFFECT_EMIT_VERTS_SET 0x2000 +/** @brief Set, the emitters keep the depth the packet came in with. */ +#define EFFECT_EMIT_KEEP_DEPTH 0x1000 +/** @} */ + +/** + * @name Prim emitter clip result + * + * One bit per projected vertex per axis, accumulated in the emitters' @c reject. + * A prim is dropped only when every one of its vertices left the screen on the + * same axis, so a prim straddling an edge still draws. + * @{ + */ +#define EFFECT_CLIP_X0 0x1 +#define EFFECT_CLIP_X1 0x2 +#define EFFECT_CLIP_X2 0x4 +#define EFFECT_CLIP_X3 0x8 +#define EFFECT_CLIP_Y0 0x10 +#define EFFECT_CLIP_Y1 0x20 +#define EFFECT_CLIP_Y2 0x40 +#define EFFECT_CLIP_Y3 0x80 +#define EFFECT_CLIP_TRI_X (EFFECT_CLIP_X0 | EFFECT_CLIP_X1 | EFFECT_CLIP_X2) +#define EFFECT_CLIP_TRI_Y (EFFECT_CLIP_Y0 | EFFECT_CLIP_Y1 | EFFECT_CLIP_Y2) +#define EFFECT_CLIP_QUAD_X (EFFECT_CLIP_TRI_X | EFFECT_CLIP_X3) +#define EFFECT_CLIP_QUAD_Y (EFFECT_CLIP_TRI_Y | EFFECT_CLIP_Y3) +/** @} */ + +/** + * @name Prim emitter clip test + * + * A projected vertex is off screen once its unsigned screen coordinate reaches + * these; a prim is dropped only when all of its vertices fail the same axis. + * @{ + */ +#define EFFECT_EMIT_CLIP_X 0x141 +#define EFFECT_EMIT_CLIP_Y 0xD9 +/** @} */ + +/** @brief GTE @c FLAG bits 18 and 17: SZ3/OTZ saturated, divide overflow. */ +#define EFFECT_GTE_OUT_OF_RANGE ((1 << 18) | (1 << 17)) + + + + +/** @brief The flat triangle an emitter writes; six words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ u16 x1; + /* 0x0E */ u16 y1; + /* 0x10 */ u16 x2; + /* 0x12 */ u16 y2; +} EffectTri; /* 0x14 */ + +/** @brief One flat triangle in the mesh stream: a colour and three vertex slots. */ +typedef struct { + /* 0x00 */ u32 colour; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u8 pad00A[0xC - 0xA]; +} EffectEmitTri; /* 0xC */ + +/** @brief The flat quad an emitter writes; seven words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ u16 x1; + /* 0x0E */ u16 y1; + /* 0x10 */ u16 x2; + /* 0x12 */ u16 y2; + /* 0x14 */ u16 x3; + /* 0x16 */ u16 y3; +} EffectQuad; /* 0x18 */ + +/** @brief One flat quad in the mesh stream: a colour and four vertex slots. */ +typedef struct { + /* 0x00 */ u32 colour; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u16 idx3; +} EffectEmitQuad; /* 0xC */ + +/** + * @brief A prim's UV pair and the id that shares its word. + * + * The stream carries both halves together, so the emitters add the build's + * offset to the whole word and only then patch the id half. + */ +typedef union { + u32 word; + struct { + /* 0x00 */ u16 uv; /**< U in the low byte, V in the high. */ + /* 0x02 */ u16 id; /**< CLUT id or texture page, by slot. */ + } h; +} EffectPrimUV; /* 0x4 */ + +/** @brief The textured triangle an emitter writes; nine words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ + /* 0x10 */ u16 x1; + /* 0x12 */ u16 y1; + /* 0x14 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ + /* 0x18 */ u16 x2; + /* 0x1A */ u16 y2; + /* 0x1C */ EffectPrimUV uv2; +} EffectTexTri; /* 0x20 */ + +/** @brief One textured triangle in the mesh stream. */ +typedef struct { + /* 0x00 */ u32 colour; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u32 idx2uv2; /**< Third vertex index, with its UV pair above it. */ + /* 0x0C */ u32 uv0; + /* 0x10 */ u32 uv1; +} EffectEmitTexTri; /* 0x14 */ + +/** @brief The gouraud triangle an emitter writes; eight words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb0; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ u32 rgb1; + /* 0x10 */ u16 x1; + /* 0x12 */ u16 y1; + /* 0x14 */ u32 rgb2; + /* 0x18 */ u16 x2; + /* 0x1A */ u16 y2; +} EffectGouraudTri; /* 0x1C */ + +/** @brief One gouraud triangle in the mesh stream: three corner colours. */ +typedef struct { + /* 0x00 */ u32 colour0; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u8 pad00A[0xC - 0xA]; + /* 0x0C */ u32 colour1; + /* 0x10 */ u32 colour2; +} EffectEmitGouraudTri; /* 0x14 */ + +/** @brief The gouraud quad an emitter writes; ten words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb0; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ u32 rgb1; + /* 0x10 */ u16 x1; + /* 0x12 */ u16 y1; + /* 0x14 */ u32 rgb2; + /* 0x18 */ u16 x2; + /* 0x1A */ u16 y2; + /* 0x1C */ u32 rgb3; + /* 0x20 */ u16 x3; + /* 0x22 */ u16 y3; +} EffectGouraudQuad; /* 0x24 */ + +/** @brief One gouraud quad in the mesh stream: four corner colours. */ +typedef struct { + /* 0x00 */ u32 colour0; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u16 idx3; + /* 0x0C */ u32 colour1; + /* 0x10 */ u32 colour2; + /* 0x14 */ u32 colour3; +} EffectEmitGouraudQuad; /* 0x18 */ + +/** @brief The gouraud textured triangle an emitter writes; eleven words. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb0; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ + /* 0x10 */ u32 rgb1; + /* 0x14 */ u16 x1; + /* 0x16 */ u16 y1; + /* 0x18 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ + /* 0x1C */ u32 rgb2; + /* 0x20 */ u16 x2; + /* 0x22 */ u16 y2; + /* 0x24 */ EffectPrimUV uv2; +} EffectGouraudTexTri; /* 0x28 */ + +/** @brief One gouraud textured triangle in the mesh stream. */ +typedef struct { + /* 0x00 */ u32 colour0; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u32 idx2uv2; /**< Third vertex index, with its UV pair above it. */ + /* 0x0C */ u32 uv0; + /* 0x10 */ u32 uv1; + /* 0x14 */ u32 colour1; + /* 0x18 */ u32 colour2; +} EffectEmitGouraudTexTri; /* 0x1C */ + +/** @brief The gouraud textured quad an emitter writes; fourteen words. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb0; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ + /* 0x10 */ u32 rgb1; + /* 0x14 */ u16 x1; + /* 0x16 */ u16 y1; + /* 0x18 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ + /* 0x1C */ u32 rgb2; + /* 0x20 */ u16 x2; + /* 0x22 */ u16 y2; + /* 0x24 */ EffectPrimUV uv2; + /* 0x28 */ u32 rgb3; + /* 0x2C */ u16 x3; + /* 0x2E */ u16 y3; + /* 0x30 */ EffectPrimUV uv3; +} EffectGouraudTexQuad; /* 0x34 */ + +/** + * @brief One gouraud textured quad in the mesh stream. + * + * The last two UV pairs share a word, so the build's offset is doubled into + * both halves and the sum split again on the way out. + */ +typedef struct { + /* 0x00 */ u32 colour0; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u16 idx3; + /* 0x0C */ u32 uv0; + /* 0x10 */ u32 uv1; + /* 0x14 */ u32 uv23; + /* 0x18 */ u32 colour1; + /* 0x1C */ u32 colour2; + /* 0x20 */ u32 colour3; +} EffectEmitGouraudTexQuad; /* 0x24 */ + +/** @brief The textured quad an emitter writes; eleven words including its tag. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb; + /* 0x08 */ u16 x0; + /* 0x0A */ u16 y0; + /* 0x0C */ EffectPrimUV uv0; /**< Its id half is the CLUT. */ + /* 0x10 */ u16 x1; + /* 0x12 */ u16 y1; + /* 0x14 */ EffectPrimUV uv1; /**< Its id half is the texture page. */ + /* 0x18 */ u16 x2; + /* 0x1A */ u16 y2; + /* 0x1C */ EffectPrimUV uv2; + /* 0x20 */ u16 x3; + /* 0x22 */ u16 y3; + /* 0x24 */ EffectPrimUV uv3; +} EffectTexQuad; /* 0x28 */ + +/** @brief One textured quad in the mesh stream; its last two UV pairs share a word. */ +typedef struct { + /* 0x00 */ u32 colour; + /* 0x04 */ u16 idx0; + /* 0x06 */ u16 idx1; + /* 0x08 */ u16 idx2; + /* 0x0A */ u16 idx3; + /* 0x0C */ u32 uv0; + /* 0x10 */ u32 uv1; + /* 0x14 */ u32 uv23; +} EffectEmitTexQuad; /* 0x18 */ + + +/** + * @brief What @ref func_801A3CDC walks: the mesh stream and how to shade it. + * + * Built on the scratchpad stack @ref D_801D3CD8 walks. The overlay reserves + * battle's 0x58 bytes for it even though it fills far less. + */ +typedef struct { + /* 0x00 */ s32 *stream; /**< The mesh the packet draws. */ + /* 0x04 */ u32 *verts; /**< Where the projected points are kept. */ + /* 0x08 */ EffectPrimColor color; /**< Far colour the depth cue fades toward. */ + /* 0x0C */ s32 depth; /**< Depth-cue weight handed to DPCS. */ + /* 0x10 */ u16 tpage; /**< Texture page the textured prims take. */ + /* 0x12 */ u8 pad012[0x14 - 0x12]; + /* 0x14 */ u16 clut; /**< CLUT id the textured prims take. */ + /* 0x16 */ u8 pad016[0x18 - 0x16]; + /* 0x18 */ s32 unk018; + /* 0x1C */ u32 flags; + /* 0x20 */ s32 *cursor; /**< Walks the stream, one entry per prim kind. */ + /* 0x24 */ s32 nclip; + /* 0x28 */ u8 pad028[0x2C - 0x28]; + /* 0x2C */ s32 otz; + /* 0x30 */ u32 gteFlag; + /* 0x34 */ u8 pad034[0x58 - 0x34]; +} EffectPrimBuild; /* 0x58 */ + + +/** @brief The scratch a spawned node's aim and speed are worked out in. */ +typedef struct { + /* 0x00 */ MATRIX m; + /* 0x20 */ SVECTOR dir; /**< Straight down, then turned by @c m. */ + /* 0x28 */ SVECTOR angle; /**< The turn @c m was built from. */ + /* 0x30 */ SVECTOR spin; /**< The turn the node's own pose takes. */ + /* 0x38 */ VECTOR vel; + /* 0x48 */ s16 speedX; + /* 0x4A */ s16 speedY; + /* 0x4C */ s16 speedZ; + /* 0x4E */ s16 frame; /**< The mote's age, which indexes the tables. */ +} EffectSpawnScratch; /* 0x50 */ + + +/** + * @name Script records + * + * @ref func_801A0B5C hands back as many bytes as the script asks for, with an + * @ref EffectEntity at the front of them, so a script owns everything the + * engine leaves alone. These two lay their own state over the pose fields. + * @{ + */ + +/** @brief The entity of the script that builds and draws the list. */ +typedef struct { + /* 0x000 */ u8 pad000[0xC]; /**< The @ref EffectEntity the engine steps. */ + /* 0x00C */ EffectAnimSet *animSet; + /* 0x010 */ EffectModel *model; + /* 0x014 */ u8 pad014[0x2A - 0x14]; + /* 0x02A */ u8 anim; /**< Which of the set's animations is playing. */ + /* 0x02B */ u8 pad02B; + /* 0x02C */ u8 slot; /**< Battle slot the effect is aimed at. */ + /* 0x02D */ u8 pad02D[0x30 - 0x2D]; + /* 0x030 */ EffectPoseTables *tables; /**< The tables the script was started on. */ + /* 0x034 */ EffectDrawList list; + /* 0x268 */ u8 pad268[0x288 - 0x268]; + /* 0x288 */ s16 unk288; /**< Where the strands are seeded from. */ + /* 0x28A */ s16 unk28A; + /* 0x28C */ s16 unk28C; + /* 0x28E */ u8 pad28E[0x290 - 0x28E]; + /* 0x290 */ s16 unk290; /**< Where the trail's newest point goes. */ + /* 0x292 */ s16 unk292; + /* 0x294 */ s16 unk294; + /* 0x296 */ u8 pad296[0x298 - 0x296]; + /* 0x298 */ s16 stopFrame; /**< Frame the script starts drawing on. */ + /* 0x29A */ u16 unk29A; + /* 0x29C */ s16 unk29C; + /* 0x29E */ s16 unk29E; + /* 0x2A0 */ u8 pad2A0[0x2A4 - 0x2A0]; +} EffectDrawScript; /* 0x2A4 */ + + +/** @brief The full-screen quad the tint script draws, four to a frame. */ +typedef struct { + /* 0x00 */ u32 tag; + /* 0x04 */ u32 rgb; /**< The step's colour; its top byte is the prim code. */ + /* 0x08 */ u32 xy0; /**< X in the low half, Y in the high. */ + /* 0x0C */ u32 xy1; + /* 0x10 */ u32 xy2; + /* 0x14 */ u32 xy3; +} EffectTintQuad; /* 0x18 */ + + +/** @brief The entity of the script that tints the screen. */ +typedef struct { + /* 0x00 */ u8 pad000[0x24]; /**< The @ref EffectEntity the engine steps. */ + /* 0x24 */ s16 frame; /**< Frames the script has run. */ + /* 0x26 */ u16 flags; /**< See @c EFFECT_FLAG_*. */ + /* 0x28 */ u8 wait; /**< Live children. */ + /* 0x29 */ u8 pad029[0x30 - 0x29]; + /* 0x30 */ EffectTintStep *steps; + /* 0x34 */ EffectTintStep tint; /**< The colour this frame's quads take. */ + /* 0x38 */ s16 step; /**< How far into @c steps the play has got. */ + /* 0x3A */ u8 pad03A[0x3C - 0x3A]; +} EffectTintScript; /* 0x3C */ + +/** @} */ + +/** @brief GTE @c FLAG bits 18 and 17: SZ3/OTZ saturated, divide overflow. */ +#define BATTLE_SPRITE_GTE_OUT_OF_RANGE ((1 << 18) | (1 << 17)) + +/** @brief The draw-mode packet an additive sprite set is linked behind. */ +#define BATTLE_SPRITE_ADDITIVE_MODE (0xE1000000 | (1 << 9) | getTPage(0, 1, 0, 0)) + +/** @brief Highest texture coordinate a sprite's right or bottom edge takes. */ +#define BATTLE_SPRITE_UV_MAX 0xFF + +static void func_801A80A4(EffectDrawNode *node); +static void func_801A9050(EffectDrawScript *script); +static void func_801A8184(EffectMote *mote); +static void func_801A87F8(void); +static void func_801A71E0(EffectMote *mote); +static void func_801A6F58(EffectMote *mote); +static void func_801A75F8(EffectMote *mote); +static void func_801A73E8(EffectMote *mote); +static EffectTri *func_801A3CDC(EffectPrimBuild *prim, u32 *ot, s32 otShift, EffectTri *head); +static EffectTri *func_801A20EC(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); +static EffectTri *func_801A23B8(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); +static EffectTri *func_801A26DC(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); +static EffectTri *func_801A2A64(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); +static EffectTri *func_801A2E48(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); +static EffectTri *func_801A3150(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); +static EffectTri *func_801A34D8(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); +static EffectTri *func_801A3894(EffectPrimBuild *s, u32 *ot, s32 otShift, EffectTri *poly); +static void func_801A1F58(EffectDrawNode *node, EffectPoseStep *step); +static void func_801A8904(EffectDrawScript *script); +static void func_801A8CE4(EffectPoseTables *tables); +static void func_801A8A1C(EffectDrawScript *script); +static void func_801A85A8(EffectDrawScript *script); +static EffectDrawNode *func_801A541C(EffectMote *owner, u8 source); +static void func_801A846C(void); +static void func_801A6074(void); +static void func_801A5FB4(EffectDrawNode *node, EffectPoseStep *step); +static POLY_FT4 *func_801A5D4C(BattleSpritePrim *prim, u32 *ot, s32 otShift, POLY_FT4 *head); +static POLY_FT4 *func_801A5720(BattleSpritePrim *prim, u32 *ot, s32 otShift, POLY_FT4 *head); +static s32 func_801A16A4(EffectTintScript *script); +static EffectMote *func_801A6194(void *owner, s16 source, s16 index); +static void func_801A8068(EffectDrawNode *node, s16 value); +static void func_801A631C(EffectMote *node, EffectPoseStep *step, s32 n); +static void func_801A41E0(EffectDrawNode *node, EffectPoseStep *step); +static void func_801A4ADC(EffectDrawNode *node, EffectPoseStep *step); +static void func_801A0978(MATRIX *m, s32 angle); +static void func_801A3F20(EffectDrawNode *node, EffectPoseStep *step); +void func_801A73E8(EffectMote *node); +static void func_801A75F8(EffectMote *node); + + +static void func_801A0208(void); +static void func_801A0358(u32 value); +static void func_801A0404(void); +static void func_801A0424(void); +static void func_801A042C(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, s32 abr); +static void func_801A0528(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, s32 abr); +static void func_801A0624(MATRIX *m); +static void func_801A065C(MATRIX *m, s32 angle); +static void func_801A07EC(MATRIX *m, s32 angle); +static s32 func_801A0B04(MATRIX *m); +static void *func_801A0B5C(void *pool, void *task, s32 stride, EffectEntity *owner); +static s32 func_801A92F8(EffectDrawScript *script); +static void func_801A0E1C(EffectEntity *entity, SVECTOR *out); +static void func_801A0E4C(EffectEntity *entity, SVECTOR *out); +static void func_801A0E7C(EffectEntity *entity, SVECTOR *out); +static void func_801A0FE8(EffectEntity *entity, SVECTOR *out); +static void func_801A1018(EffectEntity *entity, SVECTOR *out); +static void func_801A1048(EffectEntity *entity, SVECTOR *out); +static void func_801A12C4(EffectEntity *entity, SVECTOR *min, SVECTOR *max); +static s16 func_801A13B8(EffectEntity *entity); +static s16 func_801A13E8(EffectEntity *entity); +static s16 func_801A1440(EffectEntity *entity); +static s16 func_801A1464(EffectEntity *entity); +static s16 func_801A14AC(EffectEntity *entity); +static s16 func_801A14F4(EffectEntity *entity); +static s16 func_801A15AC(EffectEntity *entity); +static void func_801A1600(EffectEntity *entity, SVECTOR *out); +static void func_801A166C(void *dst, s32 size); +static void func_801A9568(EffectEntity *entity); +static void func_801A9684(EffectEntity *entity); +static void func_801A97D0(EffectEntity *entity); +static void func_801A998C(EffectEntity *entity); +static void func_801A9CEC(EffectEntity *entity); +static void func_801A83B8(EffectDrawNode *node, EffectPoseStep *step); +static s32 func_801A9CF4(EffectEntity *entity); + +/** + * @brief Start the effect's script and hand back its task pool. + * + * @param animSet Animation set the effect plays. + * @return The pool the root entity lives in. + */ +void *func_801A0000(EffectAnimSet *animSet) { + u8 *bank0; + EffectEntity *entity; + u8 *bank1; + u8 *frames; + + D_801D3CCC = 0; + D_801D3CD0 = 0; + func_800B2A00(&D_801D488C, &D_801D47BC, 0x64, 2); + entity = func_801A0B5C(&D_801D488C, func_801A9CF4, 0x64, NULL); + entity->animSet = animSet; + entity->unk02C = animSet->anims[entity->unk02A].unk000; + entity->unk02D = + entity->animSet->anims[entity->unk02A].parts[entity->unk02B].unk000; + entity->pc = 0; + entity->unk05A = animSet->anims->unk010; + entity->unk058 = animSet->anims->unk011; + entity->unk02F = entity->unk05A - 1; + if (entity->unk02F < entity->unk058) { + entity->unk02F = entity->unk058; + } + if (!(animSet->flags & EFFECT_ANIMSET_FLAG_LOADED)) { + func_800C3BE0(&D_801AA10C); + func_800BB084(&D_801AACAC); + } + D_801D3CDC = &D_801AACAC; + bank0 = &D_801AACAC; + D_801D47A4 = bank0; + bank1 = bank0 + EFFECT_BANK_SIZE; + D_801D47AC = bank0 + EFFECT_BANK_SIZE; + D_801D3CDC = bank0 + EFFECT_BANK_SIZE; + frames = bank1; + D_801D47A8 = frames; + frames += EFFECT_BANK_SIZE; + D_801D3CDC = frames; + D_801D47B0 = frames; + func_800B2A00(&D_801D49FC, &D_801D489C, 0x58, 4); + func_800B2A00(&D_801D4ACC, &D_801D4A0C, 0x3C, 3); + func_800B2A00(&D_801D478C, &D_801D3F9C, 0x2A4, 3); + func_800B2A00(&D_801D3F6C, &D_801D3CEC, 0x40, 0xA); + return &D_801D488C; } -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A042C); +/** + * @brief Cache the sixteen hex digit glyphs and reset the debug text cursor. + * + * @note The lookup runs once per glyph -- the target holds 16 separate calls, + * so a loop or a cached pointer does not match. + */ +static void func_801A0208(void) { + D_801D4ADC[0] = getMenuString(11)[1]; + D_801D4ADC[1] = getMenuString(11)[2]; + D_801D4ADC[2] = getMenuString(11)[3]; + D_801D4ADC[3] = getMenuString(11)[4]; + D_801D4ADC[4] = getMenuString(11)[5]; + D_801D4ADC[5] = getMenuString(11)[6]; + D_801D4ADC[6] = getMenuString(11)[7]; + D_801D4ADC[7] = getMenuString(11)[8]; + D_801D4ADC[8] = getMenuString(11)[9]; + D_801D4ADC[9] = getMenuString(11)[10]; + D_801D4ADC[10] = getMenuString(11)[11]; + D_801D4ADC[11] = getMenuString(11)[12]; + D_801D4ADC[12] = getMenuString(11)[13]; + D_801D4ADC[13] = getMenuString(11)[14]; + D_801D4ADC[14] = getMenuString(11)[15]; + D_801D4ADC[15] = getMenuString(11)[16]; + D_801D4ADC[16] = 0; + D_801D4AF0 = 10; + D_801D4AF4 = 20; + D_801D4AF8 = 3; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0528); +/** @brief Draw @p value as eight hex glyphs at the current debug text cursor. */ +static void func_801A0358(u32 value) { + u32 *ot = D_800FA5E8->frontOT; + u8 text[9]; + s32 i; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0624); + for (i = 7; i >= 0; i--) { + text[i] = D_801D4ADC[value & 0xF]; + value >>= 4; + } + text[8] = 0; + D_801D479C = func_8002C56C(ot, D_801D479C, D_801D4AF0, D_801D4AF4, + text, D_801D4AF8); + D_801D4AF0 += 0x48; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A065C); +/** @brief Opcode handler: arm the frame delay and advance the running total. */ +static void func_801A0404(void) { + D_801D4AF0 = 10; + D_801D4AF4 += 10; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A07EC); +/** @brief Opcode handler: no-op. */ +static void func_801A0424(void) { +} + +/** + * @brief Emit a 128x128 textured quad at the head of the prim buffer. + * + * @param x Left edge in screen space. + * @param y Top edge in screen space. + * @param tx Texture page X. + * @param ty Texture page Y. + * @param clutX Palette X. + * @param clutY Palette Y. + * @param abr Semi-transparency rate for the page. + */ +static void func_801A042C(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, + s32 abr) { + POLY_FT4 *poly = D_801D479C; + + setPolyFT4(poly); + setSemiTrans(poly, 1); + setShadeTex(poly, 1); + poly->tpage = getTPage(1, abr, tx, ty); + poly->clut = getClut(clutX, clutY); + poly->u0 = poly->u2 = 0; + poly->u1 = poly->u3 = 0xFF; + poly->v0 = poly->v1 = 0; + poly->v2 = poly->v3 = 0xFF; + poly->x0 = poly->x2 = x; + poly->x1 = poly->x3 = x + 0x7F; + poly->y0 = poly->y1 = y; + poly->y2 = poly->y3 = y + 0x7F; + AddPrim(D_800FA5E8, poly); + poly++; + D_801D479C = poly; +} + +/** + * @brief Emit a 256x256 textured quad at the head of the prim buffer. + * + * @param x Left edge in screen space. + * @param y Top edge in screen space. + * @param tx Texture page X. + * @param ty Texture page Y. + * @param clutX Palette X. + * @param clutY Palette Y. + * @param abr Semi-transparency rate for the page. + */ +static void func_801A0528(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, + s32 abr) { + POLY_FT4 *poly = D_801D479C; + + setPolyFT4(poly); + setSemiTrans(poly, 1); + setShadeTex(poly, 1); + poly->tpage = getTPage(1, abr, tx, ty); + poly->clut = getClut(clutX, clutY); + poly->u0 = poly->u2 = 0; + poly->u1 = poly->u3 = 0xFF; + poly->v0 = poly->v1 = 0; + poly->v2 = poly->v3 = 0xFF; + poly->x0 = poly->x2 = x; + poly->x1 = poly->x3 = x + 0xFF; + poly->y0 = poly->y1 = y; + poly->y2 = poly->y3 = y + 0xFF; + AddPrim(D_800FA5E8, poly); + poly++; + D_801D479C = poly; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0978); +/** @brief Load the identity matrix. */ +static void func_801A0624(MATRIX *m) { + m->m[0][0] = ONE; + m->m[1][0] = 0; + m->m[2][0] = 0; + m->m[0][1] = 0; + m->m[1][1] = ONE; + m->m[2][1] = 0; + m->m[0][2] = 0; + m->m[1][2] = 0; + m->m[2][2] = ONE; + m->t[0] = 0; + m->t[1] = 0; + m->t[2] = 0; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0B04); +/** @brief Post-multiply @p m by a rotation of @p angle about X. */ +static void func_801A065C(MATRIX *m, s32 angle) { + EffectRotScratch *rot = func_800B3698(sizeof(EffectRotScratch)); -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0B34); + rot->sin = rsin(angle); + rot->cos = rsin((angle + 0x400) & 0xFFF); + rot->m.m[0][0] = ONE; + rot->m.m[0][1] = 0; + rot->m.m[0][2] = 0; + rot->m.m[1][0] = 0; + rot->m.m[1][1] = rot->cos; + rot->m.m[1][2] = -rot->sin; + rot->m.m[2][0] = 0; + rot->m.m[2][1] = rot->sin; + rot->m.m[2][2] = rot->cos; + gte_MulMatrix0(m, &rot->m, m); + func_800B36B8(sizeof(EffectRotScratch)); +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0B5C); +/** + * @brief Compose a rotation about the Y axis into @p m. + * + * @param m Matrix rotated in place. + * @param angle Rotation, in the 0x1000-per-turn units @ref rsin takes. + */ +static void func_801A07EC(MATRIX *m, s32 angle) { + EffectRotScratch *rot = func_800B3698(sizeof(EffectRotScratch)); -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0CE8); + rot->sin = rsin(angle); + rot->cos = rsin((angle + 0x400) & 0xFFF); + rot->m.m[0][0] = rot->cos; + rot->m.m[0][1] = 0; + rot->m.m[0][2] = rot->sin; + rot->m.m[1][0] = 0; + rot->m.m[1][1] = ONE; + rot->m.m[1][2] = 0; + rot->m.m[2][0] = -rot->sin; + rot->m.m[2][1] = 0; + rot->m.m[2][2] = rot->cos; + gte_MulMatrix0(m, &rot->m, m); + func_800B36B8(sizeof(EffectRotScratch)); +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0E1C); +/** @brief Turn @p m about Z by @p angle. */ +static void func_801A0978(MATRIX *m, s32 angle) { + EffectRotScratch *rot = func_800B3698(sizeof(EffectRotScratch)); -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0E4C); + rot->sin = rsin(angle); + rot->cos = rsin((angle + 0x400) & 0xFFF); + rot->m.m[0][0] = rot->cos; + rot->m.m[0][1] = -rot->sin; + rot->m.m[0][2] = 0; + rot->m.m[1][0] = rot->sin; + rot->m.m[1][1] = rot->cos; + rot->m.m[1][2] = 0; + rot->m.m[2][0] = 0; + rot->m.m[2][1] = 0; + rot->m.m[2][2] = ONE; + gte_MulMatrix0(m, &rot->m, m); + func_800B36B8(sizeof(EffectRotScratch)); +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0E7C); +/** @brief The heading @p m faces, a quarter turn ahead of its Z axis. */ +static s32 func_801A0B04(MATRIX *m) { + return (ratan2(m->m[2][2], m->m[0][2]) + 0x400) & 0xFFF; +} + +/** @brief Opcode handler: release one frame of the linked script's wait. */ +static void func_801A0B34(EffectEntity *entity) { + if (entity->unk018 != NULL) { + entity->unk018->wait--; + } +} + +/** + * @brief Allocate an effect entity from @p pool and hang it off @p owner. + * + * The pool keeps its own 12-byte task header ahead of the entity fields, so + * only the remainder of @p stride is cleared. A child inherits the owner's + * model, animation set and position; an entity with no owner becomes its own + * model. + * + * @param pool Task pool to allocate from. + * @param task Per-frame step to install on the new entity. + * @param stride Total size of the allocation, header included. + * @param owner Parent entity, or NULL for a root. + * @return The new entity, or NULL if the pool is full. + */ +static void *func_801A0B5C(void *pool, void *task, s32 stride, + EffectEntity *owner) { + EffectEntity *node = func_800B2A84(pool, task); + s32 *clear; + s32 words; + s32 i; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0EAC); + if (node == NULL) { + return NULL; + } + clear = (s32 *)&node->animSet; + stride = stride - 0xC; + words = stride / 4; + for (i = 0; i < words; i++) { + *clear = 0; + clear++; + } + if (owner == NULL) { + node->unk018 = NULL; + node->unk014 = NULL; + /* A root drives its own geometry, so it is its own model. */ + node->unk010 = (EffectModel *)node; + } else { + if (owner->unk014 == NULL) { + node->unk014 = (EffectModel *)node; + } else { + node->unk014 = owner->unk014; + } + node->unk018 = owner; + node->unk010 = owner->unk010; + node->animSet = owner->animSet; + node->pos = owner->pos; + node->unk02A = owner->unk02A; + node->unk02B = owner->unk02B; + node->unk02E = owner->unk02E; + node->unk02F = owner->unk02F; + owner->wait++; + node->unk02C = node->animSet->anims[node->unk02A].unk000; + node->unk02D = + node->animSet->anims[node->unk02A].parts[node->unk02B].unk000; + } + return node; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A0FE8); +/** @brief Cache the battle slot's two anchor points and their midpoint. */ +static void func_801A0CE8(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; + SVECTOR top; + SVECTOR bottom; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1018); + if (!(slot->flags & BATTLE_SLOT_FLAG_UNK02)) { + return; + } + func_800B3960(slot, 0xF0, 0, &top); + func_800B3960(slot, 0xF1, 0, &bottom); + entity->unk040.vx = top.vx; + entity->unk040.vy = top.vy; + entity->unk040.vz = top.vz; + entity->unk038.vx = (top.vx + bottom.vx) / 2; + entity->unk038.vy = (top.vy + bottom.vy) / 2; + entity->unk038.vz = (top.vz + bottom.vz) / 2; + entity->unk030.vx = bottom.vx; + entity->unk030.vy = slot->unk024; + entity->unk030.vz = bottom.vz; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1048); +/** @brief Copy @c unk030 of the linked model out to @p out. */ +static void func_801A0E1C(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk014->unk030; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1078); +/** @brief Copy @c unk038 of the linked model out to @p out. */ +static void func_801A0E4C(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk014->unk038; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A12C4); +/** @brief Copy @c unk040 of the linked model out to @p out. */ +static void func_801A0E7C(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk014->unk040; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1314); +/** @brief As @ref func_801A0CE8, but for the slot the animation set names. */ +static void func_801A0EAC(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->animSet->slot]; + SVECTOR top; + SVECTOR bottom; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A13B8); + if (!(slot->flags & BATTLE_SLOT_FLAG_UNK02)) { + return; + } + func_800B3960(slot, 0xF0, 0, &top); + func_800B3960(slot, 0xF1, 0, &bottom); + entity->unk040.vx = top.vx; + entity->unk040.vy = top.vy; + entity->unk040.vz = top.vz; + entity->unk038.vx = (top.vx + bottom.vx) / 2; + entity->unk038.vy = (top.vy + bottom.vy) / 2; + entity->unk038.vz = (top.vz + bottom.vz) / 2; + entity->unk030.vx = bottom.vx; + entity->unk030.vy = slot->unk024; + entity->unk030.vz = bottom.vz; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A13E8); +/** @brief Copy @c unk030 of the linked model out to @p out. */ +static void func_801A0FE8(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk010->unk030; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1440); +/** @brief Copy @c unk038 of the linked model out to @p out. */ +static void func_801A1018(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk010->unk038; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1464); +/** @brief Copy @c unk040 of the linked model out to @p out. */ +static void func_801A1048(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk010->unk040; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A14AC); +/** + * @brief Recompute the model's bounding box from its posed skeleton. + * + * Transforms every joint after the root through the GTE and keeps the running + * minimum and maximum, then publishes the box back onto the entity. + * + * @param entity Entity whose model is measured. + */ +static void func_801A1078(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; + EffectSkeleton *skeleton = slot->mesh->skeleton; + EffectJoint *joint = skeleton->joints; + EffectBoundsScratch *b = func_800B3698(sizeof(EffectBoundsScratch)); + SVECTOR *bounds; + s32 i; + s16 x = joint->mtx.t[0]; + s16 y; + s16 z; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A14F4); + b->max.vx = x; + b->min.vx = x; + y = joint->mtx.t[1]; + b->max.vy = y; + b->min.vy = y; + z = joint->mtx.t[2]; + b->max.vz = z; + b->min.vz = z; + b->v.vx = 0; + b->v.vy = 0; + joint = &skeleton->joints[1]; + for (i = 1; i < skeleton->count; i++) { + SetRotMatrix(&joint->mtx); + SetTransMatrix(&joint->mtx); + b->v.vz = -joint->unk002; + gte_ldv0(&b->v); + gte_mvmva(1, 0, 0, 0, 0); + gte_stlvnl(&b->pos); + gte_stflg(&b->flag); + if (b->pos.vx < b->min.vx) { + b->min.vx = b->pos.vx; + } else if (b->max.vx < b->pos.vx) { + b->max.vx = b->pos.vx; + } + if (b->pos.vy < b->min.vy) { + b->min.vy = b->pos.vy; + } else if (b->max.vy < b->pos.vy) { + b->max.vy = b->pos.vy; + } + if (b->pos.vz < b->min.vz) { + b->min.vz = b->pos.vz; + } else if (b->max.vz < b->pos.vz) { + b->max.vz = b->pos.vz; + } + joint++; + } + /* The box overlays unk048/unk04C, which the script opcodes use as a table + pointer -- the two never overlap in time. */ + bounds = (SVECTOR *)entity->unk048; + bounds[0] = b->min; + bounds[1] = b->max; + func_800B36B8(sizeof(EffectBoundsScratch)); +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A15AC); +/** @brief Copy the linked model's bounding box out to @p min and @p max. */ +static void func_801A12C4(EffectEntity *entity, SVECTOR *min, SVECTOR *max) { + EffectModel *model = entity->unk014; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1600); + *min = model->boundsMin; + *max = model->boundsMax; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A166C); +/** + * @brief Half the longest side of the model's bounding box. + * + * @param entity Entity whose model is measured. + * @return Half the largest of the box's three extents. + */ +static s32 func_801A1314(EffectEntity *entity) { + EffectModel *model = entity->unk014; + s16 dx = model->boundsMax.vx - model->boundsMin.vx; + s16 dy = model->boundsMax.vy - model->boundsMin.vy; + s16 dz = model->boundsMax.vz - model->boundsMin.vz; + if (dx > dy) { + if (dx > dz) { + return dx / 2; + } + return dz / 2; + } + if (dy > dz) { + return dy / 2; + } + return dz / 2; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A16A4); +/** @brief Half the height of the linked model's bounding box. */ +static s16 func_801A13B8(EffectEntity *entity) { + EffectModel *model = entity->unk014; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1980); + return (s16)(model->boundsMax.vy - model->boundsMin.vy) / 2; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1A04); +/** @brief Half the linked model's larger horizontal extent. */ +static s16 func_801A13E8(EffectEntity *entity) { + EffectModel *model = entity->unk014; + s16 spanX = model->boundsMax.vx - model->boundsMin.vx; + s16 spanZ = model->boundsMax.vz - model->boundsMin.vz; + /* Compared as `span` but returned as `spanX`: testing a separate copy is + what puts the wider-X arm on the taken branch. */ + s16 span = spanX; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1AA8); + return (span <= spanZ ? spanZ : spanX) / 2; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1B20); +/** @brief Height of the linked model's bounding box. */ +static s16 func_801A1440(EffectEntity *entity) { + return entity->unk014->boundsMax.vy - entity->unk014->boundsMin.vy; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1BEC); +/** @brief Y of the top of the linked model, in battle-entity space. */ +static s16 func_801A1464(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1C80); + return slot->pos.vy + entity->unk014->boundsMax.vy; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1CF8); +/** @brief Y of the bottom of the linked model, in battle-entity space. */ +static s16 func_801A14AC(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1D88); + return slot->pos.vy + entity->unk014->boundsMin.vy; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1E00); +/** @brief A random point up the linked model, in battle-entity space. */ +static s16 func_801A14F4(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; + EffectModel *model = entity->unk014; + s16 height = model->boundsMax.vy - model->boundsMin.vy; + s16 offset = (rand() & 0xFFF) * height / 4096; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1E30); + return slot->pos.vy + model->boundsMax.vy - offset / 2; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1E8C); +/** @brief Y of the centre of the linked model, in battle-entity space. */ +static s16 func_801A15AC(EffectEntity *entity) { + EffectModel *model = entity->unk014; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A1F58); + return D_800EF2D0[entity->unk02D].pos.vy + + (model->boundsMax.vy + model->boundsMin.vy) / 2; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A20EC); +/** @brief Centre of the linked model's bounding box. */ +static void func_801A1600(EffectEntity *entity, SVECTOR *out) { + EffectModel *model = entity->unk014; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A23B8); + out->vx = (model->boundsMax.vx + model->boundsMin.vx) / 2; + out->vy = (model->boundsMax.vy + model->boundsMin.vy) / 2; + out->vz = (model->boundsMax.vz + model->boundsMin.vz) / 2; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A26DC); +/** @brief Zero @p size bytes' worth of words starting at @p dst. */ +static void func_801A166C(void *dst, s32 size) { + s32 *p = dst; + s32 i; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A2A64); + for (i = 0; i < size / 4; i++) { + *p = 0; + p++; + } +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A2E48); +/** + * @brief Play one step of the screen tint and lay four quads over the frame. + * + * @return 2 once the script has stopped and its children have drained. + */ +static s32 func_801A16A4(EffectTintScript *script) { + EffectTintStep step = script->steps[script->step]; + s32 draw = 1; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A3150); + if (step.op == 0xFF) { + script->flags |= EFFECT_FLAG_STOP; + draw = 0; + } else if (step.op == 0xFE) { + if (script->frame >= step.b) { + script->step++; + script->tint = script->steps[script->step]; + } + } else { + script->tint = script->steps[script->step]; + script->step++; + } + if (draw == 1) { + /* The step's four bytes are the quad's colour word, code byte and all. */ + u32 *ot = &D_800FA5E8->frontOT[2]; + EffectTintQuad *quad = D_801D479C; + DR_MODE *mode; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A34D8); + quad->rgb = *(u32 *)&script->tint; + setlen(quad, 5); + setcode(quad, EFFECT_TINT_CODE); + quad->xy0 = EFFECT_TINT_XY(0, 0); + quad->xy1 = EFFECT_TINT_XY(0xA0, 0); + quad->xy2 = EFFECT_TINT_XY(0, 0x78); + quad->xy3 = EFFECT_TINT_XY(0xA0, 0x78); + AddPrim(ot, quad); + quad++; + quad->rgb = *(u32 *)&script->tint; + setlen(quad, 5); + setcode(quad, EFFECT_TINT_CODE); + quad->xy0 = EFFECT_TINT_XY(0xA0, 0); + quad->xy1 = EFFECT_TINT_XY(0x140, 0); + quad->xy2 = EFFECT_TINT_XY(0xA0, 0x78); + quad->xy3 = EFFECT_TINT_XY(0x140, 0x78); + AddPrim(ot, quad); + quad++; + quad->rgb = *(u32 *)&script->tint; + setlen(quad, 5); + setcode(quad, EFFECT_TINT_CODE); + quad->xy0 = EFFECT_TINT_XY(0, 0x78); + quad->xy1 = EFFECT_TINT_XY(0xA0, 0x78); + quad->xy2 = EFFECT_TINT_XY(0, 0xF0); + quad->xy3 = EFFECT_TINT_XY(0xA0, 0xF0); + AddPrim(ot, quad); + quad++; + quad->rgb = *(u32 *)&script->tint; + setlen(quad, 5); + setcode(quad, EFFECT_TINT_CODE); + quad->xy0 = EFFECT_TINT_XY(0xA0, 0x78); + quad->xy1 = EFFECT_TINT_XY(0x140, 0x78); + quad->xy2 = EFFECT_TINT_XY(0xA0, 0xF0); + quad->xy3 = EFFECT_TINT_XY(0x140, 0xF0); + AddPrim(ot, quad); + quad++; + mode = (DR_MODE *)quad; + SetDrawMode(mode, 0, 0, GetTPage(0, 1, 0x280, 0), NULL); + AddPrim(ot, mode); + D_801D479C = mode + 1; + } + script->frame++; + if (script->flags & EFFECT_FLAG_STOP) { + if (script->wait == 0) { + func_801A0B34((EffectEntity *)script); + return 2; + } + } + return 0; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A3894); +/** @brief A random value between @p lo and @p hi. */ +static s32 func_801A1980(s32 lo, s32 hi) { + s32 span; + s32 r; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A3CDC); + if (lo == hi) { + return lo; + } + span = hi - lo; + r = rand() % span; + if (span >= 0) { + r = r + lo; + } else { + r = lo - r; + } + return r; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A3F20); +/** @brief A random value between @p lo and @p hi, from a product of two draws. */ +static s32 func_801A1A04(s32 lo, s32 hi) { + s32 span; + s32 r; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A4128); + if (lo == hi) { + return lo; + } + span = hi - lo; + r = rand() * rand() % span; + if (span >= 0) { + r = r + lo; + } else { + r = lo - r; + } + return r; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A41E0); +/** @brief Combine @p a and @p b component-wise through @ref func_801A1A04. */ +static void func_801A1AA8(VECTOR *out, VECTOR *a, VECTOR *b) { + out->vx = func_801A1A04(a->vx, b->vx); + out->vy = func_801A1A04(a->vy, b->vy); + out->vz = func_801A1A04(a->vz, b->vz); +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A49E8); +/** @brief Build @p m from @p angles, applying only the turns that are non-zero. */ +static void func_801A1B20(VECTOR *angles, MATRIX *m) { + SVECTOR a; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A4ADC); + a.vx = angles->vx / 0x10000; + a.vy = angles->vy / 0x10000; + a.vz = angles->vz / 0x10000; + func_801A0624(m); + if (a.vz != 0) { + func_801A0978(m, a.vz); + } + if (a.vx != 0) { + func_801A065C(m, a.vx); + } + if (a.vy != 0) { + func_801A07EC(m, a.vy); + } +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A541C); +/** @brief Scatter @p base by up to half of @p spread, wrapped to one turn. */ +static s32 func_801A1BEC(s32 base, s32 spread) { + s32 half = spread / 2; + s32 v = func_801A1A04(-half, half) + base; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A5538); + if (v >= 0) { + while (v > 0xFFFFFFF) { + v -= 0x10000000; + } + } else { + while (v <= 0) { + v += 0x10000000; + } + } + return v; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A5570); +/** @brief Combine @p a and @p b component-wise through @ref func_801A1BEC. */ +static void func_801A1C80(VECTOR *out, VECTOR *a, VECTOR *b) { + out->vx = func_801A1BEC(a->vx, b->vx); + out->vy = func_801A1BEC(a->vy, b->vy); + out->vz = func_801A1BEC(a->vz, b->vz); +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A561C); +/** @brief Scatter @p base by up to half of @p spread, wrapped to one turn. */ +static s32 func_801A1CF8(s16 base, s16 spread) { + s16 half = spread / 2; + s32 v = func_801A1980(-half, half) + base; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A5708); + if (v >= 0) { + while (v >= 0x1000) { + v -= 0x1000; + } + } else { + while (v <= 0) { + v += 0x1000; + } + } + return v; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A5720); +/** @brief Combine @p a and @p b component-wise through @ref func_801A1CF8. */ +static void func_801A1D88(SVECTOR *out, SVECTOR *a, SVECTOR *b) { + out->vx = func_801A1CF8(a->vx, b->vx); + out->vy = func_801A1CF8(a->vy, b->vy); + out->vz = func_801A1CF8(a->vz, b->vz); +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A5D4C); +/** @brief Copy the three words at @p src to @p dst. */ +static void func_801A1E00(s32 *src, s32 *dst) { + /* The original reserved a slot here and never read it; dropping it moves + every local below and the frame no longer matches. */ + VECTOR unused; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A5FB4); + dst[0] = src[0]; + dst[1] = src[1]; + dst[2] = src[2]; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A6074); +/** + * @brief Fade @p value towards zero by @p t, a 16.16 fraction of it. + * + * @param t Clamped to the closed range 0 to 1.0. + * @param value Read and written in place. + */ +static void func_801A1E30(s32 t, s32 *value) { + s32 v; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A6194); + if (t > 0x10000) { + t = 0x10000; + } + if (t < 0) { + t = 0; + } + v = *value; + *value = v - (v / 256 * t / 256); +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A62B4); +/** @brief Fade all three components of @p v towards zero by @p t. */ +static void func_801A1E8C(s32 t, VECTOR *v) { + /* The original reserved a slot here and never read it. */ + VECTOR unused; + s32 x; + s32 y; + s32 z; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A631C); + if (t > 0x10000) { + t = 0x10000; + } + if (t < 0) { + t = 0; + } + x = v->vx; + v->vx = x - (x / 256 * t / 256); + y = v->vy; + v->vy = y - (y / 256 * t / 256); + z = v->vz; + v->vz = z - (z / 256 * t / 256); +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A6DB8); +/** @brief Blend this frame's two source point sets into the node's target set. */ +static void func_801A1F58(EffectDrawNode *node, EffectPoseStep *step) { + u8 *dstId = &step->targets[node->unk1CA]; + u8 *srcAId = &step->framesA[node->unk1CA]; + u8 *srcBId = &step->framesB[node->unk1CA]; + EffectPointSet *target = D_801D3F84->targets[*dstId]; + EffectPointSet *a = D_801D3F84->frames[*srcAId]; + EffectPointSet *b = D_801D3F84->frames[*srcBId]; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A6EB8); + if (a != NULL && b != NULL) { + s32 *weight = func_800B3698(8); + SVECTOR *out = target->points; + SVECTOR *pa = a->points; + SVECTOR *pb = b->points; + s32 i = 0; + s32 t = step->weights[node->unk1CA]; + s32 n = target->count; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A6F58); + weight[1] = t; + weight[0] = ONE - t; + for (; i < n; i++) { + gte_lddp(weight[0]); + gte_ldsv(pa); + gte_gpf1(); + pa++; + gte_lddp(weight[1]); + gte_ldsv(pb); + gte_gpl1(); + pb++; + gte_stsv(out); + out++; + } + func_800B36B8(8); + } +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A71E0); +/** + * @brief Emit the flat triangles the stream cursor points at. + * + * The entry starts with a triangle count; the cursor is stepped past it before + * anything is drawn, so the caller always resumes at the next entry. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + * + * @note Two spellings here are load-bearing for the whole emitter family. The + * empty @c do/while(0) wraps carry no code: they raise the loop depth + * flow.c weights register references by, which is what puts each + * variable in the register the original picked, and which statement + * needs one differs per emitter. The prim and stream cursors are cast on + * the way in and out because the driver chains one cursor type through + * eight emitters that each write a different prim. + */ +static EffectTri *func_801A20EC(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitTri *tri; + EffectTri *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A73E8); + i = 0; + prim = poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + tri = (EffectEmitTri *)next; + if (count > 0) { + do { + do { + gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], + &verts[tri->idx2]); + } while (0); + gte_rtpt(); + setlen(prim, 4); + colour = tri->colour; + prim->rgb = colour; + if (s->flags & EFFECT_EMIT_SEMITRANS) { + prim->rgb = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_OPAQUE) { + prim->rgb &= ~EFFECT_PRIM_CODE(2); + } + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_avsz3(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && + (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_DEPTH_CUE) { + gte_ldrgb(&prim->rgb); + gte_lddp(s->depth); + gte_dpcs(); + gte_strgb(&prim->rgb); + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + tri++; + } while (i < count); + } + s->cursor = (s32 *)tri; + return prim; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A75F8); +/** + * @brief Emit the flat quads the stream cursor points at. + * + * Same walk as @ref func_801A2D08 with a fourth vertex: the first three are + * projected together, the fourth on its own, and the average Z takes all four. + * A quad is dropped when every vertex leaves the screen on the same axis. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *func_801A23B8(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitQuad *quad; + EffectQuad *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A8068); + i = 0; + prim = (EffectQuad *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + quad = (EffectEmitQuad *)next; + if (count > 0) { + do { + gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], + &verts[quad->idx2]); + gte_rtpt(); + setlen(prim, 5); + colour = quad->colour; + prim->rgb = colour; + if (s->flags & EFFECT_EMIT_SEMITRANS) { + prim->rgb = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_OPAQUE) { + prim->rgb &= ~EFFECT_PRIM_CODE(2); + } + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_ldv0(&verts[quad->idx3]); + gte_rtps(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + gte_stsxy(&prim->x3); + gte_avsz4(); + if (prim->x3 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X3; + } + if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y3; + } + if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && + (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_DEPTH_CUE) { + gte_ldrgb(&prim->rgb); + gte_lddp(s->depth); + gte_dpcs(); + gte_strgb(&prim->rgb); + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + quad++; + } while (i < count); + } + s->cursor = (s32 *)quad; + return (EffectTri *)prim; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A80A4); +/** + * @brief Emit the textured triangles the stream cursor points at. + * + * Same walk as @ref func_801A2D08 with texture coordinates: each UV word comes + * from the stream biased by the build's offset, and the texture page and CLUT + * that share the first two UV words are either left alone, offset by the + * build's own, or replaced by it. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *func_801A26DC(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitTexTri *tri; + EffectTexTri *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A80E4); + i = 0; + prim = (EffectTexTri *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + tri = (EffectEmitTexTri *)next; + if (count > 0) { + do { + do { + gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], + &verts[(u16)tri->idx2uv2]); + } while (0); + gte_rtpt(); + setlen(prim, 7); + colour = tri->colour; + prim->rgb = colour; + if (s->flags & EFFECT_EMIT_SEMITRANS) { + prim->rgb = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_OPAQUE) { + prim->rgb &= ~EFFECT_PRIM_CODE(2); + } + prim->uv0.word = tri->uv0 + s->unk018; + prim->uv1.word = tri->uv1 + s->unk018; + prim->uv2.word = (tri->idx2uv2 >> 16) + s->unk018; + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + if (s->flags & EFFECT_EMIT_TPAGE_ADD) { + prim->uv1.h.id = prim->uv1.h.id + s->tpage; + } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { + prim->uv1.h.id = s->tpage; + } + if (s->flags & EFFECT_EMIT_CLUT_ADD) { + prim->uv0.h.id = prim->uv0.h.id + s->clut; + } else if (s->flags & EFFECT_EMIT_CLUT_SET) { + prim->uv0.h.id = s->clut; + } + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_avsz3(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && + (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_DEPTH_CUE) { + gte_ldrgb(&prim->rgb); + gte_lddp(s->depth); + gte_dpcs(); + gte_strgb(&prim->rgb); + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + tri++; + } while (i < count); + } + s->cursor = (s32 *)tri; + return (EffectTri *)prim; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A80FC); +/** + * @brief Emit the textured quads the stream cursor points at. + * + * @ref func_801A2FE8 with texture coordinates, laid out like + * @ref func_801A4548's but with one flat colour for the whole quad. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *func_801A2A64(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitTexQuad *quad; + EffectTexQuad *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + u32 uv; + s32 count; + s32 reject; + s32 i; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A8184); + i = 0; + prim = (EffectTexQuad *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + quad = (EffectEmitTexQuad *)next; + if (count > 0) { + do { + gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], + &verts[quad->idx2]); + gte_rtpt(); + setlen(prim, 9); + colour = quad->colour; + prim->rgb = colour; + if (s->flags & EFFECT_EMIT_SEMITRANS) { + prim->rgb = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_OPAQUE) { + prim->rgb &= ~EFFECT_PRIM_CODE(2); + } + prim->uv0.word = quad->uv0 + s->unk018; + prim->uv1.word = quad->uv1 + s->unk018; + uv = quad->uv23 + (s->unk018 + (s->unk018 << 16)); + prim->uv2.word = uv; + prim->uv3.word = uv >> 16; + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + if (s->flags & EFFECT_EMIT_TPAGE_ADD) { + prim->uv1.h.id = prim->uv1.h.id + s->tpage; + } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { + prim->uv1.h.id = s->tpage; + } + if (s->flags & EFFECT_EMIT_CLUT_ADD) { + prim->uv0.h.id = prim->uv0.h.id + s->clut; + } else if (s->flags & EFFECT_EMIT_CLUT_SET) { + prim->uv0.h.id = s->clut; + } + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_ldv0(&verts[quad->idx3]); + gte_rtps(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + gte_stsxy(&prim->x3); + gte_avsz4(); + if (prim->x3 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X3; + } + if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y3; + } + if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && + (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_DEPTH_CUE) { + gte_ldrgb(&prim->rgb); + gte_lddp(s->depth); + gte_dpcs(); + gte_strgb(&prim->rgb); + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + quad++; + } while (i < count); + } + s->cursor = (s32 *)quad; + return (EffectTri *)prim; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A83B8); +/** + * @brief Emit the gouraud triangles the stream cursor points at. + * + * Same walk as @ref func_801A2D08 with a colour per corner. When the build + * asks for a depth cue all three corners go through the GTE together, which + * also refreshes the first corner; otherwise the two extra corners are copied + * straight out of the stream. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *func_801A2E48(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitGouraudTri *tri; + EffectGouraudTri *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A846C); + i = 0; + prim = (EffectGouraudTri *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + tri = (EffectEmitGouraudTri *)next; + if (count > 0) { + do { + do { + gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], + &verts[tri->idx2]); + } while (0); + gte_rtpt(); + setlen(prim, 6); + colour = tri->colour0; + prim->rgb0 = colour; + if (s->flags & EFFECT_EMIT_G_SEMITRANS) { + prim->rgb0 = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_G_OPAQUE) { + prim->rgb0 &= ~EFFECT_PRIM_CODE(2); + } + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_avsz3(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && + (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { + gte_ldrgb3(&tri->colour1, &tri->colour2, + &prim->rgb0); + gte_lddp(s->depth); + gte_dpct(); + gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb0); + } else { + prim->rgb1 = tri->colour1; + prim->rgb2 = tri->colour2; + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + tri++; + } while (i < count); + } + s->cursor = (s32 *)tri; + return (EffectTri *)prim; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A85A8); +/** + * @brief Emit the gouraud quads the stream cursor points at. + * + * @ref func_801A3AAC with a fourth corner: the first three vertices are + * projected together and the fourth on its own, and the depth cue runs over + * the three stream colours together before the prim's own colour separately. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *func_801A3150(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitGouraudQuad *quad; + EffectGouraudQuad *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A8684); + i = 0; + prim = (EffectGouraudQuad *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + quad = (EffectEmitGouraudQuad *)next; + if (count > 0) { + do { + do { + gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], + &verts[quad->idx2]); + } while (0); + gte_rtpt(); + setlen(prim, 8); + colour = quad->colour0; + prim->rgb0 = colour; + if (s->flags & EFFECT_EMIT_G_SEMITRANS) { + prim->rgb0 = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_G_OPAQUE) { + prim->rgb0 &= ~EFFECT_PRIM_CODE(2); + } + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_ldv0(&verts[quad->idx3]); + gte_rtps(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + gte_stsxy(&prim->x3); + gte_avsz4(); + if (prim->x3 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X3; + } + if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y3; + } + if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && + (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { + gte_ldrgb3(&quad->colour1, &quad->colour2, + &quad->colour3); + gte_lddp(s->depth); + gte_dpct(); + gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb3); + gte_ldrgb(&prim->rgb0); + gte_dpcs(); + gte_strgb(&prim->rgb0); + } else { + prim->rgb1 = quad->colour1; + prim->rgb2 = quad->colour2; + prim->rgb3 = quad->colour3; + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + quad++; + } while (i < count); + } + s->cursor = (s32 *)quad; + return (EffectTri *)prim; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A87F8); +/** + * @brief Emit the gouraud textured triangles the stream cursor points at. + * + * @ref func_801A3AAC with texture coordinates: the UV words come from the + * stream biased by the build's offset, and the texture page and CLUT sharing + * the first two are left alone, offset, or replaced as the flags ask. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *func_801A34D8(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitGouraudTexTri *tri; + EffectGouraudTexTri *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + s32 count; + s32 reject; + s32 i; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A8904); + i = 0; + prim = (EffectGouraudTexTri *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + tri = (EffectEmitGouraudTexTri *)next; + if (count > 0) { + do { + do { + gte_ldv3(&verts[tri->idx0], &verts[tri->idx1], + &verts[(u16)tri->idx2uv2]); + } while (0); + gte_rtpt(); + setlen(prim, 9); + colour = tri->colour0; + prim->rgb0 = colour; + if (s->flags & EFFECT_EMIT_G_SEMITRANS) { + prim->rgb0 = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_G_OPAQUE) { + prim->rgb0 &= ~EFFECT_PRIM_CODE(2); + } + prim->uv0.word = tri->uv0 + s->unk018; + prim->uv1.word = tri->uv1 + s->unk018; + prim->uv2.word = (tri->idx2uv2 >> 16) + s->unk018; + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + if (s->flags & EFFECT_EMIT_TPAGE_ADD) { + prim->uv1.h.id = prim->uv1.h.id + s->tpage; + } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { + prim->uv1.h.id = s->tpage; + } + if (s->flags & EFFECT_EMIT_CLUT_ADD) { + prim->uv0.h.id = prim->uv0.h.id + s->clut; + } else if (s->flags & EFFECT_EMIT_CLUT_SET) { + prim->uv0.h.id = s->clut; + } + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_avsz3(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + if ((reject & EFFECT_CLIP_TRI_X) != EFFECT_CLIP_TRI_X && + (reject & EFFECT_CLIP_TRI_Y) != EFFECT_CLIP_TRI_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { + gte_ldrgb3(&tri->colour1, &tri->colour2, + &prim->rgb0); + gte_lddp(s->depth); + gte_dpct(); + gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb0); + } else { + prim->rgb1 = tri->colour1; + prim->rgb2 = tri->colour2; + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + tri++; + } while (i < count); + } + s->cursor = (s32 *)tri; + return (EffectTri *)prim; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A8A1C); +/** + * @brief Emit the gouraud textured quads the stream cursor points at. + * + * The widest of the family: four corners, each with its own colour and UV + * pair. @ref func_801A4174 with a fourth vertex. + * + * @param s Emitter state: the stream cursor, the vertex buffer and the flags. + * @param ot Ordering table the prims are linked into. + * @param otShift Right shift applied to the GTE's average Z to pick the OT slot. + * @param poly Prim cursor to write from. + * @return The prim cursor advanced past everything written. + */ +static EffectTri *func_801A3894(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *poly) { + EffectEmitGouraudTexQuad *quad; + EffectGouraudTexQuad *prim; + s32 *cursor; + s32 *next; + u32 *verts; + u32 colour; + u32 uv; + s32 count; + s32 reject; + s32 i; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A8CE4); + i = 0; + prim = (EffectGouraudTexQuad *)poly; + cursor = s->cursor; + verts = s->verts; + count = cursor[0]; + next = cursor + 1; + s->cursor = next; + quad = (EffectEmitGouraudTexQuad *)next; + if (count > 0) { + do { + gte_ldv3(&verts[quad->idx0], &verts[quad->idx1], + &verts[quad->idx2]); + gte_rtpt(); + setlen(prim, 0xC); + colour = quad->colour0; + prim->rgb0 = colour; + if (s->flags & EFFECT_EMIT_G_SEMITRANS) { + prim->rgb0 = colour | EFFECT_PRIM_CODE(2); + } + if (s->flags & EFFECT_EMIT_G_OPAQUE) { + prim->rgb0 &= ~EFFECT_PRIM_CODE(2); + } + prim->uv0.word = quad->uv0 + s->unk018; + prim->uv1.word = quad->uv1 + s->unk018; + uv = quad->uv23 + (s->unk018 + (s->unk018 << 16)); + prim->uv2.word = uv; + prim->uv3.word = uv >> 16; + gte_stflg(&s->gteFlag); + if (!(s->gteFlag & EFFECT_GTE_OUT_OF_RANGE)) { + gte_nclip(); + if (s->flags & EFFECT_EMIT_TPAGE_ADD) { + prim->uv1.h.id = prim->uv1.h.id + s->tpage; + } else if (s->flags & EFFECT_EMIT_TPAGE_SET) { + prim->uv1.h.id = s->tpage; + } + if (s->flags & EFFECT_EMIT_CLUT_ADD) { + prim->uv0.h.id = prim->uv0.h.id + s->clut; + } else if (s->flags & EFFECT_EMIT_CLUT_SET) { + prim->uv0.h.id = s->clut; + } + reject = 0; + gte_stopz(&s->nclip); + if (s->nclip >= 0 || (s->flags & EFFECT_EMIT_G_TWO_SIDED)) { + gte_stsxy3(&prim->x0, &prim->x1, &prim->x2); + gte_ldv0(&verts[quad->idx3]); + gte_rtps(); + if (prim->x0 >= EFFECT_EMIT_CLIP_X) { + reject = EFFECT_CLIP_X0; + } + if (prim->x1 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X1; + } + if (prim->x2 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X2; + } + if (prim->y0 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y0; + } + if (prim->y1 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y1; + } + if (prim->y2 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y2; + } + gte_stsxy(&prim->x3); + gte_avsz4(); + if (prim->x3 >= EFFECT_EMIT_CLIP_X) { + reject |= EFFECT_CLIP_X3; + } + if (prim->y3 >= EFFECT_EMIT_CLIP_Y) { + reject |= EFFECT_CLIP_Y3; + } + if ((reject & EFFECT_CLIP_QUAD_X) != EFFECT_CLIP_QUAD_X && + (reject & EFFECT_CLIP_QUAD_Y) != EFFECT_CLIP_QUAD_Y) { + gte_stotz(&s->otz); + if (s->flags & EFFECT_EMIT_G_DEPTH_CUE) { + gte_ldrgb3(&quad->colour1, &quad->colour2, + &quad->colour3); + gte_lddp(s->depth); + gte_dpct(); + gte_strgb3(&prim->rgb1, &prim->rgb2, &prim->rgb3); + gte_ldrgb(&prim->rgb0); + gte_dpcs(); + gte_strgb(&prim->rgb0); + } else { + prim->rgb1 = quad->colour1; + prim->rgb2 = quad->colour2; + prim->rgb3 = quad->colour3; + } + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + quad++; + } while (i < count); + } + s->cursor = (s32 *)quad; + return (EffectTri *)prim; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A8F88); +/** + * @brief Walk @p prim's mesh stream, letting each emitter draw its own prims. + * + * @return The prim list head, advanced past everything the emitters wrote. + */ +static EffectTri *func_801A3CDC(EffectPrimBuild *prim, u32 *ot, s32 otShift, EffectTri *head) { + if (!(prim->flags & EFFECT_EMIT_VERTS_SET)) { + prim->verts = (u32 *)(prim->stream + 2); + } + prim->cursor = (s32 *)((u8 *)prim->stream + prim->stream[0]); + if (!(prim->flags & EFFECT_EMIT_KEEP_DEPTH)) { + prim->unk018 = 0; + } + gte_ldfcb(prim->color.r, prim->color.g, prim->color.b); + if (*prim->cursor != 0) { + head = func_801A20EC(prim, ot, otShift, head); + } else { + prim->cursor++; + } + if (*prim->cursor != 0) { + head = func_801A23B8(prim, ot, otShift, head); + } else { + prim->cursor++; + } + if (*prim->cursor != 0) { + head = func_801A26DC(prim, ot, otShift, head); + } else { + prim->cursor++; + } + if (*prim->cursor != 0) { + head = func_801A2A64(prim, ot, otShift, head); + } else { + prim->cursor++; + } + if (*prim->cursor != 0) { + head = func_801A2E48(prim, ot, otShift, head); + } else { + prim->cursor++; + } + if (*prim->cursor != 0) { + head = func_801A3150(prim, ot, otShift, head); + } else { + prim->cursor++; + } + if (*prim->cursor != 0) { + head = func_801A34D8(prim, ot, otShift, head); + } else { + prim->cursor++; + } + if (*prim->cursor != 0) { + head = func_801A3894(prim, ot, otShift, head); + } else { + prim->cursor++; + } + return head; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A8FF8); +/** @brief Build one mesh packet for @p node and link every copy into the OT. */ +static void func_801A3F20(EffectDrawNode *node, EffectPoseStep *step) { + EffectPrimBuild *prim; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9050); + D_801D3CD8 -= sizeof(EffectPrimBuild); + prim = (EffectPrimBuild *)D_801D3CD8; + prim->stream = node->stream; + prim->flags = 0; + if (step->unk03A == 0) { + prim->flags = EFFECT_EMIT_TWO_SIDED | EFFECT_EMIT_G_TWO_SIDED; + } + if (node->unk1CE != 0) { + prim->color = node->color; + prim->flags |= EFFECT_EMIT_DEPTH_CUE | EFFECT_EMIT_G_DEPTH_CUE; + prim->depth = node->unk1CE; + } + if (node->copies == 1) { -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A92F8); + gte_SetRotMatrix(&node->mtx); + gte_SetTransMatrix(&node->mtx); + D_801D479C = func_801A3CDC(prim, D_800FA5E8->ot, 2, D_801D479C); + } else { + s32 i; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9420); + for (i = 0; i < node->copies; i++) { + node->mtx.t[0] = node->offsets[i].vx; + node->mtx.t[1] = node->offsets[i].vy; + node->mtx.t[2] = node->offsets[i].vz; + gte_SetRotMatrix(&node->mtx); + gte_SetTransMatrix(&node->mtx); + D_801D479C = func_801A3CDC(prim, D_800FA5E8->ot, 2, D_801D479C); + } + } + D_801D3CD8 += sizeof(EffectPrimBuild); +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A94B4); +/** @brief Run the late step on every node whose source is in the right mode. */ +static void func_801A4128(void) { + EffectDrawNode *node = D_801D3F84->head; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9500); + while (node != NULL) { + if (node->unk008 == 1) { + s32 idx = node->unk1D6; + EffectPoseStep *step = D_801D3F84->sources[idx]; -void func_801A9568(void) { + if ((u32)(step->mode - 4) < 2 && node->stream != NULL) { + func_801A3F20(node, step); + } + } + node = node->next; + } } -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9570); +/** @brief Advance every copy of @p node: spin it, drift it and turn its pose. */ +static void func_801A41E0(EffectDrawNode *node, EffectPoseStep *step) { + VECTOR posed[4]; + VECTOR spinOffset; + VECTOR spun; + MATRIX m; + s32 i; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9610); + node->angle = node->angleBase; + node->angle.vx += step->unk0D8[node->unk1CA]; + node->angle.vy += step->unk0DC[node->unk1CA]; + node->angle.vz += step->unk0E0[node->unk1CA]; + node->angle.vx &= 0xFFF; + node->angle.vy &= 0xFFF; + node->angle.vz &= 0xFFF; + node->lastPos = node->pos[0]; + switch (step->unk027) { + case 0: + for (i = 0; i < node->copies; i++) { + if (node->spinRate != NULL || node->spin != NULL) { + node->spin[i] += node->spinRate[i]; + if (node->unk1DA != 0) { + func_801A1E30(node->unk1DA, &node->spin[i]); + } + spinOffset.vx = 0; + spinOffset.vy = -node->spin[i]; + spinOffset.vz = 0; + ApplyMatrixLV(&node->poses[i], &spinOffset, &spun); + node->offset[i].vx += spun.vx; + node->offset[i].vy += spun.vy; + node->offset[i].vz += spun.vz; + } + node->accel[i].vx += node->jerk[i].vx; + node->accel[i].vy += node->jerk[i].vy; + node->accel[i].vz += node->jerk[i].vz; + node->vel[i].vx += node->accel[i].vx; + node->vel[i].vy += node->accel[i].vy; + node->vel[i].vz += node->accel[i].vz; + node->vel[i].vy += node->unk1DC; + if (node->unk1DA != 0) { + func_801A1E8C(node->unk1DA, &node->vel[i]); + } + node->offset[i].vx += node->vel[i].vx; + node->offset[i].vy += node->vel[i].vy; + node->offset[i].vz += node->vel[i].vz; + } + break; + case 1: + for (i = 0; i < node->copies; i++) { + node->offset[i].vx = step->unk13C[node->unk1CA] << 16; + node->offset[i].vy = step->unk140[node->unk1CA] << 16; + node->offset[i].vz = step->unk144[node->unk1CA] << 16; + if (node->owner != NULL) { + m = node->owner->mtx; + } + func_801A0624(&m); + ApplyMatrixLV(&m, &node->offset[i], &node->offset[i]); + } + break; + } + if (step->unk026 == 1) { + EffectMote *owner = node->owner; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9628); + switch (step->unk01D) { + case 0: + func_801A0624(&m); + if (owner != NULL) { + m = owner->mtx; + } + if (node->angle.vz != 0) { + func_801A0978(&m, node->angle.vz); + } + if (node->angle.vx != 0) { + func_801A065C(&m, node->angle.vx); + } + if (node->angle.vy != 0) { + func_801A07EC(&m, node->angle.vy); + } + break; + case 1: + func_801A0624(&m); + if (owner != NULL) { + m = owner->mtx; + } + if (node->angle.vy != 0) { + func_801A07EC(&m, node->angle.vy); + } + if (node->angle.vx != 0) { + func_801A065C(&m, node->angle.vx); + } + if (node->angle.vz != 0) { + func_801A0978(&m, node->angle.vz); + } + break; + case 2: + TransposeMatrix(D_801D3CD4, &m); + if (node->angle.vz != 0) { + func_801A0978(&m, node->angle.vz); + } + if (node->angle.vx != 0) { + func_801A065C(&m, node->angle.vx); + } + if (node->angle.vy != 0) { + func_801A07EC(&m, node->angle.vy); + } + break; + case 3: + TransposeMatrix(D_801D3CD4, &m); + if (node->angle.vy != 0) { + func_801A07EC(&m, node->angle.vy); + } + if (node->angle.vx != 0) { + func_801A065C(&m, node->angle.vx); + } + if (node->angle.vz != 0) { + func_801A0978(&m, node->angle.vz); + } + break; + } + for (i = 0; i < node->copies; i++) { + ApplyMatrixLV(&m, &node->offset[i], &posed[i]); + } + } else { + for (i = 0; i < node->copies; i++) { + posed[i] = node->offset[i]; + } + } + switch (step->unk022) { + case 0: + for (i = 0; i < node->copies; i++) { + node->pos[i] = posed[i]; + } + break; + case 1: { + EffectMote *owner = node->owner; -void func_801A9684(void) { + if (owner != NULL) { + for (i = 0; i < node->copies; i++) { + node->pos[i] = owner->pos; + node->pos[i].vx += posed[i].vx; + node->pos[i].vy += posed[i].vy; + node->pos[i].vz += posed[i].vz; + } + } + break; + } + } + if (step->unk01B == 1) { + for (i = 0; i < node->copies; i++) { + node->pos[i].vy = 0; + } + } } -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A968C); +/** @brief Age @p node's hold on its current step and advance when it expires. */ +static void func_801A49E8(EffectDrawNode *node, EffectPoseStep *step) { + switch (step->mode) { + case 0: + node->unk1D0++; + if (node->unk1D1 < node->unk1D0) { + node->unk1D0 = 0; + node->unk1D2 = 1; + } + break; + case 2: + node->unk1D0++; + if (node->unk1D4 < node->unk1D0) { + if (node->unk1D5 != 0) { + node->unk1D5--; + node->unk1D0 = node->unk1D3; + } + } + if (node->unk1D0 > node->unk1D1) { + node->unk1D0 = 0; + node->unk1D2 = 1; + } + break; + case 1: + node->unk1D0++; + if (node->unk1D1 < node->unk1D0) { + node->unk1D0 = 0; + } + break; + } +} + +/** @brief Build @p node's pose: orient it, colour it, scale it and place it. */ +static void func_801A4ADC(EffectDrawNode *node, EffectPoseStep *step) { + VECTOR scale; + /* The original reserved a second slot here and never read it. */ + VECTOR unused; + s32 i; + + if (step->mode == 4 || step->mode == 5) { + u8 *target = &step->targets[node->unk1CA]; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A972C); + node->stream = D_801D3F84->targets[*target]; + if (step->mode == 5) { + /* Called with no arguments: node and step are already in $a0/$a1. */ + ((void (*)())func_801A1F58)(); + } + } + switch (step->unk01B) { + case 0: + TransposeMatrix(D_801D3CD4, &node->orient); + if (node->angle.vz != 0) { + func_801A0978(&node->orient, node->angle.vz); + } + break; + case 3: { + EffectMote *owner = node->owner; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9788); + switch (step->unk01D) { + case 0: + if (owner != NULL) { + node->orient = owner->mtx; + } else { + func_801A0624(&node->orient); + } + if (node->angle.vz != 0) { + func_801A0978(&node->orient, node->angle.vz); + } + if (node->angle.vx != 0) { + func_801A065C(&node->orient, node->angle.vx); + } + if (node->angle.vy != 0) { + func_801A07EC(&node->orient, node->angle.vy); + } + break; + case 1: + if (owner != NULL) { + node->orient = owner->mtx; + } else { + func_801A0624(&node->orient); + } + if (node->angle.vy != 0) { + func_801A07EC(&node->orient, node->angle.vy); + } + if (node->angle.vx != 0) { + func_801A065C(&node->orient, node->angle.vx); + } + if (node->angle.vz != 0) { + func_801A0978(&node->orient, node->angle.vz); + } + break; + case 2: + TransposeMatrix(D_801D3CD4, &node->orient); + if (node->angle.vz != 0) { + func_801A0978(&node->orient, node->angle.vz); + } + if (node->angle.vx != 0) { + func_801A065C(&node->orient, node->angle.vx); + } + if (node->angle.vy != 0) { + func_801A07EC(&node->orient, node->angle.vy); + } + break; + case 3: + TransposeMatrix(D_801D3CD4, &node->orient); + if (node->angle.vy != 0) { + func_801A07EC(&node->orient, node->angle.vy); + } + if (node->angle.vx != 0) { + func_801A065C(&node->orient, node->angle.vx); + } + if (node->angle.vz != 0) { + func_801A0978(&node->orient, node->angle.vz); + } + break; + } + break; + } + case 1: + func_801A0624(&node->orient); + func_801A065C(&node->orient, 0x400); + break; + case 2: { + s16 angle = func_801A0B04(D_801D3CD4); -void func_801A97D0(void) { + func_801A0624(&node->orient); + if (angle != 0) { + func_801A07EC(&node->orient, angle); + } + break; + } + case 4: { + EffectMote *owner = node->owner; + + switch (step->unk01D) { + case 0: + if (owner != NULL) { + node->orient = owner->mtx; + } else { + func_801A0624(&node->orient); + } + if (node->angle.vz != 0) { + func_801A0978(&node->orient, node->angle.vz); + } + if (node->angle.vx != 0) { + func_801A065C(&node->orient, node->angle.vx); + } + if (node->angle.vy != 0) { + func_801A07EC(&node->orient, node->angle.vy); + } + break; + case 1: + if (owner != NULL) { + node->orient = owner->mtx; + } else { + func_801A0624(&node->orient); + } + if (node->angle.vy != 0) { + func_801A07EC(&node->orient, node->angle.vy); + } + if (node->angle.vx != 0) { + func_801A065C(&node->orient, node->angle.vx); + } + if (node->angle.vz != 0) { + func_801A0978(&node->orient, node->angle.vz); + } + break; + case 2: + TransposeMatrix(D_801D3CD4, &node->orient); + if (node->angle.vz != 0) { + func_801A0978(&node->orient, node->angle.vz); + } + if (node->angle.vx != 0) { + func_801A065C(&node->orient, node->angle.vx); + } + if (node->angle.vy != 0) { + func_801A07EC(&node->orient, node->angle.vy); + } + break; + case 3: + TransposeMatrix(D_801D3CD4, &node->orient); + if (node->angle.vy != 0) { + func_801A07EC(&node->orient, node->angle.vy); + } + if (node->angle.vx != 0) { + func_801A065C(&node->orient, node->angle.vx); + } + if (node->angle.vz != 0) { + func_801A0978(&node->orient, node->angle.vz); + } + break; + } + break; + } + } + if (step->mode == 4 || step->mode == 5) { + node->color.r = step->unk0F0[node->unk1CA]; + node->color.g = step->unk0F4[node->unk1CA]; + node->color.b = step->unk0F8[node->unk1CA]; + node->unk1CE = step->unk0FC[node->unk1CA]; + node->scaleX = step->unk0E4[node->unk1CA]; + node->scaleY = step->unk0E8[node->unk1CA]; + node->scaleZ = step->unk0EC[node->unk1CA]; + scale.vx = node->scaleX; + scale.vy = node->scaleY; + scale.vz = node->scaleZ; + ScaleMatrix(&node->orient, &scale); + } + if (node->copies == 1) { + node->orient.t[0] = node->pos[0].vx / 0x10000; + node->orient.t[1] = node->pos[0].vy / 0x10000; + node->orient.t[2] = node->pos[0].vz / 0x10000; + gte_MulMatrix0(D_801D3CD4, &node->orient, &node->mtx); + gte_SetTransMatrix(D_801D3CD4); + gte_ldlv0(node->orient.t); + gte_rt(); + gte_stlvnl(node->mtx.t); + node->mtx.t[2] += step->unk038; + } else { + for (i = 0; i < node->copies; i++) { + node->orient.t[0] = node->pos[i].vx / 0x10000; + node->orient.t[1] = node->pos[i].vy / 0x10000; + node->orient.t[2] = node->pos[i].vz / 0x10000; + gte_MulMatrix0(D_801D3CD4, &node->orient, &node->mtx); + gte_SetTransMatrix(D_801D3CD4); + gte_ldlv0(node->orient.t); + gte_rt(); + gte_stlvnl(node->mtx.t); + node->offsets[i].vx = node->mtx.t[0]; + node->offsets[i].vy = node->mtx.t[1]; + node->offsets[i].vz = node->mtx.t[2]; + node->offsets[i].vz += step->unk038; + } + } } -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A97D8); +/** + * @brief Take the next free node off bank 1 and put it on the draw list. + * + * The search starts where the last one left off and gives up after one lap. + * + * @return The node, or NULL when every slot is taken. + */ +static EffectDrawNode *func_801A541C(EffectMote *owner, u8 source) { + EffectDrawNode *found = NULL; + s32 i = D_801D3F8A; + s32 n; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9878); + for (n = 0; n < 0x8C; n++) { + if (D_801D3F80[i].unk1D7 == 0) { + EffectDrawNode *node = &D_801D3F80[i]; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A98D0); + found = node; + func_801A166C(node, sizeof(EffectDrawNode)); + node->unk1D7 = 1; + node->unk1D6 = source; + node->owner = owner; + node->unk1D9 = owner->unk06B; + D_801D3F84->live++; + func_801A8068(node, 1); + break; + } + i++; + if (i >= EFFECT_NODE_COUNT) { + i = 0; + } + } + i++; + if (i >= EFFECT_NODE_COUNT) { + i = 0; + } + D_801D3F8A = i; + return found; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9904); +/** @brief Run @p step when it is the kind this handler serves. */ +static void func_801A5538(EffectDrawNode *node, EffectPoseStep *step) { + if (step->unk030 == 1) { + func_801A6194(node, step->unk031, node->unk1D9); + } +} -void func_801A998C(void) { +/** + * @brief Decide whether @p step fires this frame, and run it if it does. + * @return 1 when the step ran, 0 otherwise. + */ +static s32 func_801A5570(EffectDrawNode *node, EffectPoseStep *step) { + switch (step->kind) { + case 0: + if (node->unk1D2 != 1) { + return 0; + } + break; + case 1: + node->unk1C8--; + if (node->unk1C8 >= 0) { + return 0; + } + func_801A5538(node, step); + return 1; + case 2: + node->unk1C8--; + if (node->unk1C8 < 0) { + break; + } + if (node->unk1D2 != 1) { + return 0; + } + break; + default: + return 0; + } + func_801A5538(node, step); + return 1; } -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9994); +/** @brief Step one node of the pose list, or retire it once its phase is over. */ +static void func_801A561C(EffectDrawNode *node) { + EffectPoseStep *step = D_801D3F84->sources[node->unk1D6]; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9AA4); + switch (node->unk1CC) { + case 0: + func_801A49E8(node, step); + if (func_801A5570(node, step) == 0) { + func_801A41E0(node, step); + func_801A4ADC(node, step); + node->unk1CA++; + } else { + node->stream = NULL; + node->unk1CC++; + } + break; + case 1: + func_801A80A4(node); + node->unk1D7 = 0; + D_801D3F84->live--; + break; + } +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9AB8); +/** @brief One less than @p node's count, as a signed step. */ +static s16 func_801A5708(EffectMote *node) { + return node->unk008 - 1; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9ACC); +/** + * @brief The overlay's own copy of @ref func_800C97E4: emit the frame's sprites. + * + * @return The prim buffer cursor past the packets emitted. + */ +static POLY_FT4 *func_801A5720(BattleSpritePrim *prim, u32 *ot, s32 otShift, POLY_FT4 *head) { + POLY_FT4 *p = head; + POLY_FT4 *next; + BattleSprite *sprite = prim->sprites; + s32 count = prim->spriteCount; + s32 i; + s32 flags; + s32 value; /* angle, then each scale: one local, the target keeps all three in s0 */ + s32 w; + s32 h; + s32 uv; + u32 shade; + DR_MODE *tp; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9B5C); + for (i = 0; i < count; i++, sprite++) { + prim->m.m[2][1] = 0; + prim->m.m[2][0] = 0; + prim->m.m[1][2] = 0; + prim->m.m[1][0] = 0; + prim->m.m[0][2] = 0; + prim->m.m[0][1] = 0; + prim->m.m[2][2] = ONE; + flags = sprite->flags; + if (flags & BATTLE_SPRITE_TRANSFORMED) { + value = sprite->angle; + if (value == 0) { + prim->cos = ONE; + prim->sin = 0; + prim->lastAngle = 0; + } else if (value != prim->lastAngle) { + prim->cos = rcos(value); + prim->sin = rsin(value); + prim->lastAngle = value; + } + value = sprite->scaleX; + prim->m.m[0][0] = prim->cos * value >> 12; + prim->m.m[1][0] = prim->sin * value >> 12; + value = sprite->scaleY; + prim->m.m[0][1] = -prim->sin * value >> 12; + prim->m.m[1][1] = prim->cos * value >> 12; + } else { + prim->m.m[1][1] = ONE; + prim->m.m[0][0] = ONE; + } + w = sprite->w; + h = sprite->h; + prim->corners[2].vx = -(w << 3); + prim->corners[0].vx = -(w << 3); + prim->corners[3].vx = w << 3; + prim->corners[1].vx = w << 3; + prim->corners[1].vy = -(h << 3); + prim->corners[0].vy = -(h << 3); + prim->corners[3].vy = h << 3; + prim->corners[2].vy = h << 3; + prim->m.t[0] = (sprite->x << 4) + (w << 3); + prim->m.t[1] = (sprite->y << 4) + (h << 3); + prim->m.t[2] = 0; + /* m = mtx * m, a column at a time, with the packet filled in the GTE's shadow. */ + gte_SetRotMatrix(&prim->mtx); + gte_ldclmv(&prim->m.m[0][0]); + gte_rtir(); + setlen(p, 9); + /* One word each: w, shade, h, code and u, v, clut are laid out as the packet's. */ + *(u32 *)&p->r0 = *(u32 *)&sprite->w; + *(u32 *)&p->u0 = *(u32 *)&sprite->u; + gte_stclmv(&prim->m.m[0][0]); + gte_ldclmv(&prim->m.m[0][1]); + gte_rtir(); + p->r0 = p->b0 = p->g0; + shade = p->r0 * prim->colour.r; + p->r0 = shade >> 7; + gte_stclmv(&prim->m.m[0][1]); + gte_ldclmv(&prim->m.m[0][2]); + gte_rtir(); + p->u2 = sprite->u; + shade = p->g0 * prim->colour.g; + p->g0 = shade >> 7; + gte_stclmv(&prim->m.m[0][2]); + gte_SetTransMatrix(&prim->mtx); + gte_ldlv0(prim->m.t); + gte_rt(); + p->v1 = sprite->v; + shade = p->b0 * prim->colour.b; + p->b0 = shade >> 7; + gte_stlvnl(prim->m.t); + gte_SetRotMatrix(&prim->m); + gte_SetTransMatrix(&prim->m); + gte_ldv3(&prim->corners[0], &prim->corners[1], &prim->corners[2]); + gte_rtpt(); + uv = sprite->u + w - prim->uvInset; + if (uv > BATTLE_SPRITE_UV_MAX) { + uv = BATTLE_SPRITE_UV_MAX; + } + p->u3 = uv; + p->u1 = uv; + /* The row select is the flag word's top nibble, unsigned. */ + p->clut += getClut(0, prim->clutRow[(u16)flags >> BATTLE_SPRITE_CLUT_ROW_SHIFT]); + gte_stflg(&prim->gteFlag); + if (!(prim->gteFlag & BATTLE_SPRITE_GTE_OUT_OF_RANGE)) { + p->tpage = flags & BATTLE_SPRITE_TPAGE_MASK; + gte_stsxy3(&p->x0, &p->x1, &p->x2); + gte_ldv0(&prim->corners[3]); + gte_rtps(); + uv = sprite->v + h - prim->uvInset; + if (uv > BATTLE_SPRITE_UV_MAX) { + uv = BATTLE_SPRITE_UV_MAX; + } + p->v3 = uv; + p->v2 = uv; + gte_stsxy(&p->x3); + if (prim->otz >= 0) { + addPrim(&ot[otShift], p); + p++; + } else { + gte_avsz4(); + gte_stotz(&prim->otz); + otShift = prim->otz >> otShift; + if (prim->flags & BATTLE_SPRITE_FLAG_ADDITIVE) { + next = p + 1; + setlen(next, 1); + tp = (DR_MODE *)next; + next = (POLY_FT4 *)(tp + 1); + tp->code[0] = BATTLE_SPRITE_ADDITIVE_MODE; + addPrim(&ot[otShift], tp); + addPrim(&ot[otShift], p); + p = next; + } else { + addPrim(&ot[otShift], p); + p++; + } + } + } + } + return p; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9BB4); +/** + * @brief The overlay's own copy of @ref func_800C9E10: set a sprite packet up. + * + * @return The prim buffer cursor past the sprites emitted. + */ +static POLY_FT4 *func_801A5D4C(BattleSpritePrim *prim, u32 *ot, s32 otShift, POLY_FT4 *head) { + BattleSpriteAnim *anim = prim->anim; + u16 *offsets = anim->offsets; + u8 *frame; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9C18); + prim->frameCount = anim->frameCount; + frame = (u8 *)anim + offsets[prim->frame]; + prim->sprites = (BattleSprite *)frame; + prim->spriteCount = *(s32 *)frame; + prim->sprites = (BattleSprite *)((s32 *)prim->sprites + 1); + prim->nextOffset = offsets[prim->frame + 1]; + if (prim->spriteCount < 0) { + prim->spriteCount &= ~BATTLE_SPRITE_COUNT_FLAG; + prim->flags |= BATTLE_SPRITE_FLAG_ADDITIVE; + } + prim->corners[3].vz = 0; + prim->corners[2].vz = 0; + prim->corners[1].vz = 0; + prim->corners[0].vz = 0; + gte_ReadRotMatrix(&prim->mtx); + if (prim->flags & BATTLE_SPRITE_FLAG_ROTATE) { + RotMatrixZ(prim->angle - D_800F02A0, &prim->mtx); + } else if (!(prim->flags & BATTLE_SPRITE_FLAG_NO_ROLL)) { + RotMatrixZ(-D_800F02A0, &prim->mtx); + } + if (prim->flags & BATTLE_SPRITE_FLAG_SCALE) { + prim->scale.vz = ONE; + ScaleMatrixL(&prim->mtx, &prim->scale); + } + if (!(prim->flags & BATTLE_SPRITE_FLAG_COLOUR)) { + *(u32 *)&prim->colour = BATTLE_SPRITE_COLOUR_DEFAULT; + } + if (!(prim->flags & BATTLE_SPRITE_FLAG_KEEP_CLUT0)) { + prim->clutRow[0] = anim->clutRow[0]; + } + if (!(prim->flags & BATTLE_SPRITE_FLAG_KEEP_CLUT1)) { + prim->clutRow[1] = anim->clutRow[1]; + } + if (!(prim->flags & BATTLE_SPRITE_FLAG_KEEP_CLUT2)) { + prim->clutRow[2] = anim->clutRow[2]; + } + if (!(prim->flags & BATTLE_SPRITE_FLAG_KEEP_CLUT3)) { + prim->clutRow[3] = anim->clutRow[3]; + } + if (prim->flags & BATTLE_SPRITE_FLAG_UV_INSET) { + prim->uvInset = 1; + } else { + prim->uvInset = 0; + } + prim->cos = ONE; + prim->lastAngle = 0; + prim->sin = 0; + prim->otz = -1; + return func_801A5720(prim, ot, otShift, head); +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9C80); +/** + * @brief Build one draw packet for @p node's model and link it into the OT. + * + * @note @p step is not read; every caller has it to hand and passes it. + */ +static void func_801A5FB4(EffectDrawNode *node, EffectPoseStep *step) { + BattleSpritePrim *prim; + + D_801D3CD8 -= sizeof(BattleSpritePrim); + prim = (BattleSpritePrim *)D_801D3CD8; + gte_SetRotMatrix(&node->mtx); + gte_SetTransMatrix(&node->mtx); + prim->anim = node->stream; + prim->frame = node->unk1D0; + prim->flags = 0; + D_801D479C = func_801A5D4C(prim, D_800FA5E8->ot, 2, D_801D479C); + D_801D3CD8 += sizeof(BattleSpritePrim); +} + +/** @brief Draw every node on the list that has a model and is still posing. */ +static void func_801A6074(void) { + EffectDrawNode *node = D_801D3F84->head; + + while (node != NULL) { + if (node->unk008 == 1) { + EffectPoseStep *step = D_801D3F84->sources[node->unk1D6]; + EffectDrawNode *n = node; + + if (step->mode < 3 && node->stream != NULL) { + if (node->copies == 1) { + func_801A5FB4(node, step); + } else { + s32 i; + + for (i = 0; i < n->copies; i++) { + n->mtx.t[0] = n->offsets[i].vx; + n->mtx.t[1] = n->offsets[i].vy; + n->mtx.t[2] = n->offsets[i].vz; + func_801A5FB4(n, step); + } + } + } + } + node = node->next; + } +} + +/** + * @brief Take the next free mote off bank 0 and put it on the draw list. + * + * The search starts where the last one left off and gives up after one lap. + * + * @return The mote, or NULL when every slot is taken. + */ +static EffectMote *func_801A6194(void *owner, s16 source, s16 index) { + EffectMote *found = NULL; + s32 i = D_801D3F88; + s32 n; + + for (n = 0; n < 0x5A; n++) { + if (D_801D3F7C[i].taken == 0) { + EffectMote *mote = &D_801D3F7C[i]; + + found = mote; + func_801A166C(mote, sizeof(EffectMote)); + mote->taken = 1; + mote->unk06A = source; + mote->unk06B = index; + mote->owner = owner; + D_801D3F84->count++; + func_801A8068((EffectDrawNode *)mote, 0); + break; + } + i++; + if (i >= EFFECT_MOTE_COUNT) { + i = 0; + } + } + i++; + if (i >= EFFECT_MOTE_COUNT) { + i = 0; + } + D_801D3F88 = i; + return found; +} + + +/** @brief Step @p node's life and retire it when it runs out. */ +static void func_801A62B4(EffectMote *node) { + node->life--; + if (node->life <= 0) { + func_801A80A4((EffectDrawNode *)node); + node->taken = 0; + D_801D3F84->count--; + } +} + +/** @brief Spawn a node off @p mote and give each of its @p copies its own aim. */ +static void func_801A631C(EffectMote *mote, EffectPoseStep *step, s32 copies) { + EffectSpawnScratch *s; + EffectDrawNode *node; + s32 i; + + D_801D3CD8 -= sizeof(EffectSpawnScratch); + s = (EffectSpawnScratch *)D_801D3CD8; + node = func_801A541C(mote, mote->unk06A); + node->copies = copies; + s->frame = mote->unk060; + if (step->mode < 3) { + node->stream = D_801D3F84->unk228[mote->unk06A]; + node->unk1D1 = func_801A5708(node->stream); + if (step->mode == 2) { + node->unk1D3 = step->unk017; + node->unk1D4 = step->unk018; + node->unk1D5 = func_801A1980(step->unk019, step->unk01A); + } + } + node->unk1DA = step->unk064; + for (i = 0; i < copies; i++) { + node->pos[i] = mote->pos; + if (step->unk022 == 0) { + node->offset[i] = mote->pos; + } + if (step->unk034 == 1) { + s->angle.vx = rand() & 0xFFF; + s->angle.vy = rand() & 0xFFF; + s->angle.vz = rand() & 0xFFF; + func_801A0624(&s->m); + if (s->angle.vz != 0) { + func_801A0978(&s->m, s->angle.vz); + } + if (s->angle.vx != 0) { + func_801A065C(&s->m, s->angle.vx); + } + if (s->angle.vy != 0) { + func_801A07EC(&s->m, s->angle.vy); + } + if (step->unk033 == 0) { + s->speedX = step->unk0CC[s->frame]; + s->speedY = step->unk0D0[s->frame]; + s->speedZ = step->unk0D4[s->frame]; + if (s->speedX != 0) { + s->speedX = rand() % s->speedX; + } + if (s->speedY != 0) { + s->speedY = rand() % s->speedY; + } + if (s->speedZ != 0) { + s->speedZ = rand() % s->speedZ; + } + } else { + s->speedX = step->unk0CC[s->frame]; + s->speedY = step->unk0D0[s->frame]; + s->speedZ = step->unk0D4[s->frame]; + } + s->dir.vx = 0; + s->dir.vy = -0x1000; + s->dir.vz = 0; + gte_SetRotMatrix(&s->m); + gte_ldv0(&s->dir); + gte_mvmva(1, 0, 0, 3, 0); + gte_stsv(&s->dir); + s->vel.vx = s->dir.vx * s->speedX << 4; + s->vel.vy = s->dir.vy * s->speedY << 4; + s->vel.vz = s->dir.vz * s->speedZ << 4; + node->offset[i].vx += s->vel.vx; + node->offset[i].vy += s->vel.vy; + node->offset[i].vz += s->vel.vz; + } else if (step->unk034 == 2) { + s->angle.vx = rand() & 0xFFF; + s->angle.vy = 0; + s->angle.vz = 0x400; + func_801A0624(&s->m); + if (s->angle.vz != 0) { + func_801A0978(&s->m, s->angle.vz); + } + if (s->angle.vx != 0) { + func_801A065C(&s->m, s->angle.vx); + } + if (s->angle.vy != 0) { + func_801A07EC(&s->m, s->angle.vy); + } + if (step->unk033 == 0) { + s->speedX = step->unk0CC[s->frame]; + s->speedY = step->unk0D0[s->frame]; + s->speedZ = step->unk0D4[s->frame]; + if (s->speedX != 0) { + s->speedX = rand() % s->speedX; + } + if (s->speedY != 0) { + s->speedY = rand() % s->speedY; + } + if (s->speedZ != 0) { + s->speedZ = rand() % s->speedZ; + } + } else if (step->unk033 != 1) { + s->speedX = step->unk0CC[s->frame]; + s->speedY = step->unk0D0[s->frame]; + s->speedZ = step->unk0D4[s->frame]; + if (s->speedX != 0) { + s->speedX = rand() % s->speedX; + } + if (s->speedY != 0) { + s->speedY = rand() % s->speedY; + } + if (s->speedZ != 0) { + s->speedZ = rand() % s->speedZ; + } + } else { + s->speedX = step->unk0CC[s->frame]; + s->speedY = step->unk0D0[s->frame]; + if (s->speedY != 0) { + s->speedY = rand() % s->speedY; + } + s->speedZ = step->unk0D4[s->frame]; + } + if (rand() & 1) { + s32 speed = s->speedY; + + s->speedY = -speed; + } + s->dir.vx = 0; + s->dir.vy = -0x1000; + s->dir.vz = 0; + gte_SetRotMatrix(&s->m); + gte_ldv0(&s->dir); + gte_mvmva(1, 0, 0, 3, 0); + gte_stsv(&s->dir); + s->vel.vx = s->dir.vx * s->speedX << 4; + s->vel.vy = s->speedY << 16; + s->vel.vz = s->dir.vz * s->speedZ << 4; + node->offset[i].vx += s->vel.vx; + node->offset[i].vy += s->vel.vy; + node->offset[i].vz += s->vel.vz; + } + switch (step->unk021) { + case 0: + func_801A1D88(&s->spin, &step->unk03C, &step->unk044); + break; + case 1: + s->spin = s->angle; + break; + } + func_801A0624(&node->poses[i]); + if (s->spin.vz != 0) { + func_801A0978(&node->poses[i], s->spin.vz); + } + if (s->spin.vx != 0) { + func_801A065C(&node->poses[i], s->spin.vx); + } + if (s->spin.vy != 0) { + func_801A07EC(&node->poses[i], s->spin.vy); + } + gte_MulMatrix0(&mote->mtx, &node->poses[i], &node->poses[i]); + if (step->unk027 == 0) { + node->spin[i] = func_801A1A04(step->unk04C, step->unk050); + node->spinRate[i] = func_801A1A04(step->unk054, step->unk058); + } + } + func_801A1D88(&node->angleBase, &step->unk148, &step->unk150); + if (step->kind == 1 || step->kind == 2) { + node->unk1C8 = func_801A1980(step->unk01F, step->unk020); + } + if (step->unk027 == 0) { + node->unk1DC = func_801A1A04(step->unk05C, step->unk060); + for (i = 0; i < copies; i++) { + func_801A1AA8(&node->vel[i], &step->unk068, &step->unk078); + func_801A1AA8(&node->accel[i], &step->unk088, &step->unk098); + func_801A1AA8(&node->jerk[i], &step->unk0A8, &step->unk0B8); + } + } + func_801A83B8(node, step); + D_801D3CD8 += sizeof(EffectSpawnScratch); +} + +/** @brief Emit this frame's share of @p node's work, in chunks its source sizes. */ +static void func_801A6DB8(EffectMote *node) { + EffectPoseStep *step = D_801D3F84->sources[node->unk06A]; + s32 age = node->unk060; + s32 want; + s32 full; + s32 rest; + s32 i; + + if (age < 0x28) { + want = step->unk0C8[age]; + if (want != 0) { + full = want / step->unk036; + rest = want % step->unk036; + for (i = 0; i < full; i++) { + func_801A631C(node, step, step->unk036); + } + if (rest > 0) { + func_801A631C(node, step, rest); + } + } + } +} + +/** @brief Step one drawn node: set it up once, run its kind, then age it. */ +static void func_801A6EB8(EffectMote *node) { + if (node->unk062 == 0) { + /* Called with no argument: the mote is already in $a0 and stays there. */ + ((void (*)())func_801A8184)(); + node->unk062++; + } + switch (node->kind) { + case 3: + func_801A73E8(node); + break; + case 4: + func_801A75F8(node); + break; + } + func_801A6DB8(node); + node->unk060++; + func_801A62B4(node); +} + +/** @brief Put @p mote on the strand point its source names, plus that source's offset. */ +static void func_801A6F58(EffectMote *mote) { + EffectPoseStep *step = D_801D3F84->sources[mote->unk06A]; + SVECTOR offset; + SVECTOR turned; + MATRIX m; + + switch (step->anchor) { + case 0: + mote->pos = D_801D3F84->strands[1][mote->unk06B]; + break; + case 1: + mote->pos = D_801D3F84->strands[2][mote->unk06B]; + break; + case 2: + mote->pos = D_801D3F84->strands[3][mote->unk06B]; + break; + case 3: + mote->pos = D_801D3F84->strands[4][mote->unk06B]; + break; + } + offset.vx = step->offsetX / 0x10000; + offset.vy = step->offsetY / 0x10000; + offset.vz = step->offsetZ / 0x10000; + m = mote->mtx; + gte_SetRotMatrix(&m); + gte_ldv0(&offset); + gte_mvmva(1, 0, 0, 3, 0); + gte_stsv(&turned); + mote->pos.vx += turned.vx << 16; + mote->pos.vy += turned.vy << 16; + mote->pos.vz += turned.vz << 16; +} + +/** @brief Put @p mote on the trail point its source names, plus that source's offset. */ +static void func_801A71E0(EffectMote *mote) { + EffectPoseStep *step = D_801D3F84->sources[mote->unk06A]; + SVECTOR offset; + SVECTOR turned; + + switch (step->anchor) { + case 0: + mote->pos = D_801D3F84->trail[2]; + break; + case 1: + mote->pos = D_801D3F84->trail[3]; + break; + case 2: + mote->pos = D_801D3F84->trail[4]; + break; + case 3: + mote->pos = D_801D3F84->trail[5]; + break; + } + offset.vx = step->offsetX / 0x10000; + offset.vy = step->offsetY / 0x10000; + offset.vz = step->offsetZ / 0x10000; + gte_SetRotMatrix(&mote->mtx); + gte_ldv0(&offset); + gte_mvmva(1, 0, 0, 3, 0); + gte_stsv(&turned); + mote->pos.vx += turned.vx << 16; + mote->pos.vy += turned.vy << 16; + mote->pos.vz += turned.vz << 16; +} + +/** @brief Put @p mote on the node that owns it, plus that source's turned offset. */ +static void func_801A73E8(EffectMote *mote) { + EffectPoseStep *step = D_801D3F84->sources[mote->unk06A]; + EffectDrawNode *owner = mote->owner; + + if (owner != NULL) { + SVECTOR offset; + SVECTOR turned; + + if (step->midpoint == 1) { + mote->pos.vx = (owner->pos[0].vx + owner->lastPos.vx) / 2; + mote->pos.vy = (owner->pos[0].vy + owner->lastPos.vy) / 2; + mote->pos.vz = (owner->pos[0].vz + owner->lastPos.vz) / 2; + } else { + mote->pos = owner->pos[0]; + } + if (step->unk01C == 3) { + mote->mtx = owner->orient; + offset.vx = step->offsetX / 0x10000; + offset.vy = step->offsetY / 0x10000; + offset.vz = step->offsetZ / 0x10000; + gte_SetRotMatrix(&mote->mtx); + gte_ldv0(&offset); + gte_mvmva(1, 0, 0, 3, 0); + gte_stsv(&turned); + mote->pos.vx += turned.vx << 16; + mote->pos.vy += turned.vy << 16; + mote->pos.vz += turned.vz << 16; + } + } +} + +/** @brief Slide @p mote between the two points its source names. */ +static void func_801A75F8(EffectMote *mote) { + EffectPoseStep *step = D_801D3F84->sources[mote->unk06A]; + u8 kind; + s32 index; + SVECTOR from; + SVECTOR to; + SVECTOR offset; + SVECTOR turned; + s32 t; + + kind = step->unk100[mote->unk060]; + index = step->unk108[mote->unk060]; + switch (kind) { + case 0: + switch (index) { + case 0: + from.vx = D_801D3F84->strands[1][mote->unk06B].vx / 0x10000; + from.vy = D_801D3F84->strands[1][mote->unk06B].vy / 0x10000; + from.vz = D_801D3F84->strands[1][mote->unk06B].vz / 0x10000; + break; + case 1: + from.vx = D_801D3F84->strands[2][mote->unk06B].vx / 0x10000; + from.vy = D_801D3F84->strands[2][mote->unk06B].vy / 0x10000; + from.vz = D_801D3F84->strands[2][mote->unk06B].vz / 0x10000; + break; + case 2: + from.vx = D_801D3F84->strands[3][mote->unk06B].vx / 0x10000; + from.vy = D_801D3F84->strands[3][mote->unk06B].vy / 0x10000; + from.vz = D_801D3F84->strands[3][mote->unk06B].vz / 0x10000; + break; + case 3: + from.vx = D_801D3F84->strands[4][mote->unk06B].vx / 0x10000; + from.vy = D_801D3F84->strands[4][mote->unk06B].vy / 0x10000; + from.vz = D_801D3F84->strands[4][mote->unk06B].vz / 0x10000; + break; + } + break; + case 1: + switch (index) { + case 0: + from.vx = D_801D3F84->trail[2].vx / 0x10000; + from.vy = D_801D3F84->trail[2].vy / 0x10000; + from.vz = D_801D3F84->trail[2].vz / 0x10000; + break; + case 1: + from.vx = D_801D3F84->trail[3].vx / 0x10000; + from.vy = D_801D3F84->trail[3].vy / 0x10000; + from.vz = D_801D3F84->trail[3].vz / 0x10000; + break; + case 2: + from.vx = D_801D3F84->trail[4].vx / 0x10000; + from.vy = D_801D3F84->trail[4].vy / 0x10000; + from.vz = D_801D3F84->trail[4].vz / 0x10000; + break; + case 3: + from.vx = D_801D3F84->trail[5].vx / 0x10000; + from.vy = D_801D3F84->trail[5].vy / 0x10000; + from.vz = D_801D3F84->trail[5].vz / 0x10000; + break; + } + break; + } + offset.vx = step->unk110[mote->unk060]; + offset.vy = step->unk114[mote->unk060]; + offset.vz = step->unk118[mote->unk060]; + gte_SetRotMatrix(&mote->mtx); + gte_ldv0(&offset); + gte_mvmva(1, 0, 0, 3, 0); + gte_stsv(&turned); + from.vx += turned.vx; + from.vy += turned.vy; + from.vz += turned.vz; + kind = step->unk104[mote->unk060]; + index = step->unk10C[mote->unk060]; + switch (kind) { + case 0: + switch (index) { + case 0: + to.vx = D_801D3F84->strands[1][mote->unk06B].vx / 0x10000; + to.vy = D_801D3F84->strands[1][mote->unk06B].vy / 0x10000; + to.vz = D_801D3F84->strands[1][mote->unk06B].vz / 0x10000; + break; + case 1: + to.vx = D_801D3F84->strands[2][mote->unk06B].vx / 0x10000; + to.vy = D_801D3F84->strands[2][mote->unk06B].vy / 0x10000; + to.vz = D_801D3F84->strands[2][mote->unk06B].vz / 0x10000; + break; + case 2: + to.vx = D_801D3F84->strands[3][mote->unk06B].vx / 0x10000; + to.vy = D_801D3F84->strands[3][mote->unk06B].vy / 0x10000; + to.vz = D_801D3F84->strands[3][mote->unk06B].vz / 0x10000; + break; + case 3: + to.vx = D_801D3F84->strands[4][mote->unk06B].vx / 0x10000; + to.vy = D_801D3F84->strands[4][mote->unk06B].vy / 0x10000; + to.vz = D_801D3F84->strands[4][mote->unk06B].vz / 0x10000; + break; + } + break; + case 1: + switch (index) { + case 0: + to.vx = D_801D3F84->trail[2].vx / 0x10000; + to.vy = D_801D3F84->trail[2].vy / 0x10000; + to.vz = D_801D3F84->trail[2].vz / 0x10000; + break; + case 1: + to.vx = D_801D3F84->trail[3].vx / 0x10000; + to.vy = D_801D3F84->trail[3].vy / 0x10000; + to.vz = D_801D3F84->trail[3].vz / 0x10000; + break; + case 2: + to.vx = D_801D3F84->trail[4].vx / 0x10000; + to.vy = D_801D3F84->trail[4].vy / 0x10000; + to.vz = D_801D3F84->trail[4].vz / 0x10000; + break; + case 3: + to.vx = D_801D3F84->trail[5].vx / 0x10000; + to.vy = D_801D3F84->trail[5].vy / 0x10000; + to.vz = D_801D3F84->trail[5].vz / 0x10000; + break; + } + break; + } + offset.vx = step->unk11C[mote->unk060]; + offset.vy = step->unk120[mote->unk060]; + offset.vz = step->unk124[mote->unk060]; + gte_SetRotMatrix(&mote->mtx); + gte_ldv0(&offset); + gte_mvmva(1, 0, 0, 3, 0); + gte_stsv(&turned); + to.vx += turned.vx; + to.vy += turned.vy; + to.vz += turned.vz; + t = step->unk128[mote->unk060]; + mote->pos.vx = (from.vx << 16) + ((to.vx - from.vx) * t << 4); + mote->pos.vy = (from.vy << 16) + ((to.vy - from.vy) * t << 4); + mote->pos.vz = (from.vz << 16) + ((to.vz - from.vz) * t << 4); +} + +/** + * @brief Give @p node the count @p value and put it on the tail of the draw list. + * + * @param node Either record kind: both start with the same three link fields, + * and @c unk008 is the tag the walker later dispatches on. + */ +static void func_801A8068(EffectDrawNode *node, s16 value) { + EffectDrawList *list = D_801D3F84; + + node->unk008 = value; + if (list->head == NULL) { + list->head = node; + } else { + EffectDrawNode *tail = list->tail; + + tail->next = node; + node->prev = tail; + } + list->tail = node; +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9CA8); +/** + * @brief Take @p node off the draw list, mending both links. + * + * @param node Either record kind; see @ref func_801A8068. + */ +static void func_801A80A4(EffectDrawNode *node) { + EffectDrawNode *prev = node->prev; + EffectDrawNode *next = node->next; + + if (prev == NULL) { + D_801D3F84->head = next; + } else { + prev->next = next; + } + if (next == NULL) { + D_801D3F84->tail = prev; + } else { + next->prev = prev; + } +} + +/** @brief Empty the draw list. */ +static void func_801A80E4(void) { + D_801D3F84->head = NULL; + D_801D3F84->tail = NULL; +} + +/** @brief Run every node on the draw list through the step its kind wants. */ +static void func_801A80FC(void) { + EffectDrawNode *node = D_801D3F84->head; + + while (node != NULL) { + /* Read signed here and unsigned where it is stepped; it is also the + tag saying which record kind the walker is standing on. */ + switch (node->unk008) { + case 0: + func_801A6EB8((EffectMote *)node); + break; + case 1: + func_801A561C(node); + break; + } + node = node->next; + } +} + +/** @brief Set @p mote up for this frame: turn it, place it, and reset its life. */ +static void func_801A8184(EffectMote *mote) { + EffectPoseStep *step = D_801D3F84->sources[mote->unk06A]; + EffectDrawNode *owner = mote->owner; + s32 angle; + + mote->kind = step->unk011; + switch (step->unk01C) { + case 0: + func_801A0624(&mote->mtx); + break; + case 1: + func_801A0624(&mote->mtx); + angle = func_80041E84( + D_801D3F84->trail[1].vx - D_801D3F84->strands[0][mote->unk06B].vx, + D_801D3F84->trail[1].vz - D_801D3F84->strands[0][mote->unk06B].vz); + if (angle != 0) { + func_801A07EC(&mote->mtx, angle); + } + break; + case 2: + func_801A0624(&mote->mtx); + angle = func_80041E84( + D_801D3F84->strands[0][mote->unk06B].vx - D_801D3F84->trail[1].vx, + D_801D3F84->strands[0][mote->unk06B].vz - D_801D3F84->trail[1].vz); + if (angle != 0) { + func_801A07EC(&mote->mtx, angle); + } + break; + case 3: + mote->mtx = owner->orient; + break; + case 4: + func_801A0624(&mote->mtx); + angle = D_800EF2D0[D_801D3F84->slot].facing; + if (angle != 0) { + func_801A07EC(&mote->mtx, angle); + } + break; + } + switch (mote->kind) { + case 0: + func_801A6F58(mote); + break; + case 1: + func_801A71E0(mote); + break; + case 2: + func_801A73E8(mote); + break; + case 3: + func_801A73E8(mote); + break; + case 4: + func_801A75F8(mote); + break; + } + mote->life = step->unk012; +} + +/** @brief Fire each of @p step's four slots that is armed. */ +static void func_801A83B8(EffectDrawNode *node, EffectPoseStep *step) { + if (step->unk028 == 1) { + func_801A6194(node, step->unk02C, node->unk1D9); + } + if (step->unk029 == 1) { + func_801A6194(node, step->unk02D, node->unk1D9); + } + if (step->unk02A == 1) { + func_801A6194(node, step->unk02E, node->unk1D9); + } + if (step->unk02B == 1) { + func_801A6194(node, step->unk02F, node->unk1D9); + } +} + +/** @brief Take a mote for every source whose cue falls on this frame. */ +static void func_801A846C(void) { + s32 source; + + for (source = 0; source < 0x10; source++) { + EffectPoseStep *step = D_801D3F84->sources[source]; + + if (step != NULL && !((D_801D3F84->held >> source) & 1)) { + if (step->unk011 < 2 || step->unk011 == 4) { + if (step->unk013 == D_801D3F84->age) { + if (step->unk037 == 1) { + s32 i; + + for (i = 0; i < D_801D3F84->strandLen; i++) { + func_801A6194(NULL, source, i); + } + } else { + func_801A6194(NULL, source, 0); + } + } + } + } + } +} + +/** @brief Push the node's current position onto the head of the trail. */ +static void func_801A85A8(EffectDrawScript *script) { + EffectDrawList *list = D_801D3F84; + + list->trail[0].vx = script->unk290 << 16; + list->trail[0].vy = script->unk292 << 16; + list->trail[0].vz = script->unk294 << 16; + list->trail[5] = list->trail[0]; + list->trail[4] = list->trail[5]; + list->trail[3] = list->trail[4]; + list->trail[2] = list->trail[3]; + list->trail[1] = list->trail[2]; +} + +/** @brief Aim the trail at the battle slot: its two points and its height. */ +static void func_801A8684(void) { + BattleEffectSlot *slot = &D_800EF2D0[D_801D3F84->slot]; + SVECTOR pt; -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9CBC); + if (D_801D3F84->unk024 != 2) { + func_800B3960(slot, 0xF1, 0, &pt); + } + pt.vy = slot->unk024; + D_801D3F84->trail[1].vx = pt.vx << 16; + D_801D3F84->trail[1].vy = pt.vy << 16; + D_801D3F84->trail[1].vz = pt.vz << 16; + D_801D3F84->trail[2].vx = D_801D3F84->trail[1].vx; + D_801D3F84->trail[2].vy = 0; + D_801D3F84->trail[2].vz = D_801D3F84->trail[1].vz; + if (D_801D3F84->unk024 != 2) { + func_800B3960(slot, 0xF1, 0, &pt); + } + D_801D3F84->trail[3].vx = pt.vx << 16; + D_801D3F84->trail[3].vy = pt.vy << 16; + D_801D3F84->trail[3].vz = pt.vz << 16; + if (D_801D3F84->unk024 != 2) { + func_800B3960(slot, 0xF0, 0, &pt); + } + D_801D3F84->trail[4].vx = pt.vx << 16; + D_801D3F84->trail[4].vy = pt.vy << 16; + D_801D3F84->trail[4].vz = pt.vz << 16; + D_801D3F84->trail[5].vx = D_801D3F84->trail[1].vx; + D_801D3F84->trail[5].vy = slot->unk03C << 16; + D_801D3F84->trail[5].vz = D_801D3F84->trail[1].vz; +} + +/** @brief Seed every point of all five strands from the list's seed point. */ +static void func_801A87F8(void) { + s32 i; + + for (i = 0; i < D_801D3F84->strandLen; i++) { + D_801D3F84->strands[0][i] = D_801D3F84->strandSeed; + D_801D3F84->strands[1][i] = D_801D3F84->strandSeed; + D_801D3F84->strands[2][i] = D_801D3F84->strandSeed; + D_801D3F84->strands[3][i] = D_801D3F84->strandSeed; + D_801D3F84->strands[4][i] = D_801D3F84->strandSeed; + } +} + +/** @brief Push @p node's position onto the head of every strand. */ +static void func_801A8904(EffectDrawScript *script) { + s32 i; + + for (i = 0; i < D_801D3F84->strandLen; i++) { + EffectDrawList *list = D_801D3F84; + + list->strandHead.vx = script->unk288 << 16; + list->strandHead.vy = script->unk28A << 16; + list->strandHead.vz = script->unk28C << 16; + list->strands[4][i] = list->strandHead; + list->strands[3][i] = list->strands[4][i]; + list->strands[2][i] = list->strands[3][i]; + list->strands[1][i] = list->strands[2][i]; + list->strands[0][i] = list->strands[1][i]; + } +} + +/** + * @brief Aim every strand at its own model part, then seed them from the middle. + * + * @note @p script is not read; @ref func_801A9050 passes the entity it has. + */ +static void func_801A8A1C(EffectDrawScript *script) { + SVECTOR pt; + s32 i; + s16 n; + s32 minX; + s32 maxX; + s32 minZ; + s32 maxZ; + + for (i = 0; i < D_801D3F84->strandLen; i++) { + BattleEffectSlot *slot = &D_800EF2D0[D_801D3F84->parts[i]]; + + if (D_801D3F84->unk024 != 2) { + func_800B3960(slot, 0xF1, 0, &pt); + } + pt.vy = slot->unk024; + D_801D3F84->strands[0][i].vx = pt.vx << 16; + D_801D3F84->strands[0][i].vy = pt.vy << 16; + D_801D3F84->strands[0][i].vz = pt.vz << 16; + D_801D3F84->strands[1][i].vx = D_801D3F84->strands[0][i].vx; + D_801D3F84->strands[1][i].vy = 0; + D_801D3F84->strands[1][i].vz = D_801D3F84->strands[0][i].vz; + if (D_801D3F84->unk024 != 2) { + func_800B3960(slot, 0xF1, 0, &pt); + } + D_801D3F84->strands[2][i].vx = pt.vx << 16; + D_801D3F84->strands[2][i].vy = pt.vy << 16; + D_801D3F84->strands[2][i].vz = pt.vz << 16; + if (D_801D3F84->unk024 != 2) { + func_800B3960(slot, 0xF0, 0, &pt); + } + D_801D3F84->strands[3][i].vx = pt.vx << 16; + D_801D3F84->strands[3][i].vy = pt.vy << 16; + D_801D3F84->strands[3][i].vz = pt.vz << 16; + D_801D3F84->strands[4][i].vx = D_801D3F84->strands[0][i].vx; + D_801D3F84->strands[4][i].vy = slot->unk03C << 16; + D_801D3F84->strands[4][i].vz = D_801D3F84->strands[0][i].vz; + } + maxZ = 0; + minZ = 0; + maxX = 0; + minX = 0; + n = 0; + i = 0; + + for (; i < D_801D3F84->strandLen; i++) { + s32 v; + + if (n == 0) { + maxX = D_801D3F84->strands[0][i].vx; + maxZ = D_801D3F84->strands[0][i].vz; + minX = maxX; + minZ = maxZ; + } else { + v = D_801D3F84->strands[0][i].vx; + if (v < minX) { + minX = v; + } else if (maxX < v) { + maxX = v; + } + v = D_801D3F84->strands[0][i].vz; + if (v < minZ) { + minZ = v; + } else if (maxZ < v) { + maxZ = v; + } + } + n++; + } + D_801D3F84->strandSeed.vx = (minX + maxX) / 2; + D_801D3F84->strandSeed.vy = 0; + D_801D3F84->strandSeed.vz = (minZ + maxZ) / 2; +} + +/** + * @brief Turn every offset in @p tables into an address. + * + * The tables are built with everything relative to their own start, so the + * first script to use them walks each of the four arrays -- and then every + * table inside each source -- adding that start. Each array is walked as + * words because the entries are offsets until this pass has run. + */ +static void func_801A8CE4(EffectPoseTables *tables) { + s32 base = (s32)tables; + s32 *entry; + s32 i; + + entry = (s32 *)D_801D3F84->sources; + for (i = 0; i < 16; i++) { + if (entry[i] != 0) { + entry[i] += base; + } + } + entry = (s32 *)D_801D3F84->unk228; + for (i = 0; i < 16; i++) { + if (entry[i] != 0) { + entry[i] += base; + } + } + entry = (s32 *)D_801D3F84->targets; + for (i = 0; i < 16; i++) { + if (entry[i] != 0) { + entry[i] += base; + } + } + entry = (s32 *)D_801D3F84->frames; + for (i = 0; i < 16; i++) { + if (entry[i] != 0) { + entry[i] += base; + } + } + for (i = 0; i < 16; i++) { + EffectPoseStep *step = D_801D3F84->sources[i]; + + if (step != NULL) { + step->unk0C8 += base; + step->unk0CC = (u16 *)((u8 *)step->unk0CC + base); + step->unk0D0 = (u16 *)((u8 *)step->unk0D0 + base); + step->unk0D4 = (u16 *)((u8 *)step->unk0D4 + base); + step->unk0D8 = (u16 *)((u8 *)step->unk0D8 + base); + step->unk0DC = (u16 *)((u8 *)step->unk0DC + base); + step->unk0E0 = (u16 *)((u8 *)step->unk0E0 + base); + step->unk0E4 = (u16 *)((u8 *)step->unk0E4 + base); + step->unk0E8 = (u16 *)((u8 *)step->unk0E8 + base); + step->unk0EC = (u16 *)((u8 *)step->unk0EC + base); + step->unk0F0 += base; + step->unk0F4 += base; + step->unk0F8 += base; + step->unk0FC = (u16 *)((u8 *)step->unk0FC + base); + step->unk100 += base; + step->unk104 += base; + step->unk108 += base; + step->unk10C += base; + step->unk110 = (u16 *)((u8 *)step->unk110 + base); + step->unk114 = (u16 *)((u8 *)step->unk114 + base); + step->unk118 = (u16 *)((u8 *)step->unk118 + base); + step->unk11C = (u16 *)((u8 *)step->unk11C + base); + step->unk120 = (u16 *)((u8 *)step->unk120 + base); + step->unk124 = (u16 *)((u8 *)step->unk124 + base); + step->unk128 = (s16 *)((u8 *)step->unk128 + base); + step->targets += base; + step->framesA += base; + step->framesB += base; + step->weights = (s16 *)((u8 *)step->weights + base); + step->unk13C = (s16 *)((u8 *)step->unk13C + base); + step->unk140 = (s16 *)((u8 *)step->unk140 + base); + step->unk144 = (s16 *)((u8 *)step->unk144 + base); + } + } +} -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9CD0); +/** @brief Start @p task as a child of @p owner and seed its script fields. */ +static void func_801A8F88(EffectEntity *owner, void *task, void *arg, s16 stopFrame, + s16 a, s16 b) { + EffectDrawScript *child = + func_801A0B5C(&D_801D478C, task, sizeof(EffectDrawScript), owner); -void func_801A9CEC(void) { + child->tables = arg; + child->stopFrame = stopFrame; + child->unk29C = b; + child->unk29E = a; } -INCLUDE_ASM("asm/ovl/effect_028/nonmatchings/effect_028", func_801A9CF4); +/** @brief Clear both prim banks and the script's frame counters. */ +static void func_801A8FF8(void) { + func_801A166C(D_801D3F7C, EFFECT_BANK0_BYTES); + func_801A166C(D_801D3F80, EFFECT_BANK1_BYTES); + D_801D3F88 = 0; + D_801D3F8A = 0; + D_801D3F90 = 0; + D_801D3F92 = 0; +} + +/** @brief Point the draw list at this script's tables and aim it at its slot. */ +static void func_801A9050(EffectDrawScript *script) { + EffectModel *model = script->model; + EffectPoseTables *tables = script->tables; + s32 slot = script->slot; + EffectDrawList *list = &script->list; + SVECTOR from; + SVECTOR to; + s32 i; + + list->held = script->unk29A; + list->unk024 = script->unk29C; + list->sources = tables->sources; + list->unk228 = tables->unk044; + list->targets = tables->targets; + list->frames = tables->frames; + D_801D3F84 = list; + if (tables->ready == 0) { + func_801A8CE4(tables); + tables->ready = 1; + } + D_801D3F84->slot = slot; + D_801D3F84->strandLen = model->unk05A; + for (i = 0; i < D_801D3F84->strandLen; i++) { + D_801D3F84->parts[i] = + script->animSet->anims[script->anim].parts[i].unk000; + } + D_801D3F84->span = script->unk29E; + func_801A8684(); + if (D_801D3F84->unk024 == 4) { + func_801A85A8(script); + } + func_801A8A1C(script); + if (D_801D3F84->unk024 == 1) { + /* Handed the script the others take, though this one reads nothing. */ + ((void (*)())func_801A87F8)(script); + } + if (D_801D3F84->unk024 == 3) { + func_801A8904(script); + } + from = D_800EF2D0[D_801D3F84->slot].pos; + to = D_800EF2D0[D_801D3F84->parts[0]].pos; + from.vx -= to.vx; + from.vy -= to.vy; + from.vz -= to.vz; + D_801D3F84->spread = + SquareRoot0(from.vx * from.vx + from.vy * from.vy + from.vz * from.vz); +} + +/** + * @brief Run one frame of the draw list @p script carries. + * + * @return 1 once the list has finished, 0 while it is still running. + */ +static s32 func_801A92F8(EffectDrawScript *script) { + s32 done = 0; + + D_801D3F84 = &script->list; + switch (D_801D3F84->phase) { + case 0: + D_801D3F84->phase++; + break; + case 1: + func_801A846C(); + func_801A80FC(); + func_801A6074(); + func_801A4128(); + D_801D3F84->age++; + if (D_801D3F84->age >= D_801D3F84->span) { + /* Both counts are tested as one word; neither alone would do. */ + if (*(s32 *)&D_801D3F84->count == 0) { + D_801D3F84->phase++; + } + } + break; + case 2: + done = 1; + break; + } + D_801D3F8C += D_801D3F84->count; + D_801D3F8E += D_801D3F84->live; + return done; +} + + +/** @brief Set the draw list's span from three times the slot's height, clamped. */ +static void func_801A9420(s16 slot) { + BattleEffectSlot *entry = &D_800EF2D0[slot]; + /* Both heights are subtracted as raw halfwords before the span is taken. */ + s16 span = (s16)((u16)entry->unk03C - entry->unk036) * 3; + + if (span > 0x4000) { + span = 0x4000; + } else if (span < 0x400) { + span = 0x400; + } + D_801D3F84->unk21C = span; + D_801D3F84->unk218 = span; + D_801D3F84->unk214 = span; +} + +/** @brief Opcode handler: clear the screen tint. */ +static void func_801A94B4(EffectEntity *entity) { + BattleTint *tint = D_800EF738; + s32 i; + + entity->pos.vx = 0; + for (i = 0; i < 4; i++) { + tint->level = entity->pos.vx; + tint->b = 0; + tint->g = 0; + tint->r = 0; + tint++; + } + entity->pc++; +} + +/** @brief Opcode handler: fade the screen tint up to full, then stop. */ +static void func_801A9500(EffectEntity *entity) { + BattleTint *tint = D_800EF738; + s32 i; + + entity->pos.vx += 0x100; + if (entity->pos.vx >= 0x400) { + entity->pos.vx = 0x400; + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } + for (i = 3; i >= 0; i--) { + tint->level = entity->pos.vx; + tint++; + } +} + +/** @brief Opcode handler: no-op. */ +static void func_801A9568(EffectEntity *entity) { +} + +/** + * @brief Script dispatcher: run this frame's step, draw, and retire the entity. + * + * @return 2 once the script has stopped and its children have drained, 0 while + * it is still running. + */ +static s32 func_801A9570(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A94B4, func_801A9500, func_801A9568 }; + + handlers[entity->pc](entity); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A0B34(entity); + return 2; + } + return 0; +} + +/** @brief Opcode handler: hold the tint at a quarter. */ +static void func_801A9610(EffectEntity *entity) { + entity->pos.vx = 0x400; + entity->pc++; +} + +/** @brief Opcode handler: fade the screen tint back out, then stop. */ +static void func_801A9628(EffectEntity *entity) { + BattleTint *tint = D_800EF738; + s32 i; + + entity->pos.vx -= 0x100; + if (entity->pos.vx <= 0) { + entity->pos.vx = 0; + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } + for (i = 3; i >= 0; i--) { + tint->level = entity->pos.vx; + tint++; + } +} + +/** @brief Opcode handler: no-op. */ +static void func_801A9684(EffectEntity *entity) { +} + +/** + * @brief Script dispatcher: run this frame's step, draw, and retire the entity. + * + * @return 2 once the script has stopped and its children have drained, 0 while + * it is still running. + */ +static s32 func_801A968C(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A9610, func_801A9628, func_801A9684 }; + + handlers[entity->pc](entity); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A0B34(entity); + return 2; + } + return 0; +} + +/** @brief Opcode handler: draw and retire once the frame count is reached. */ +static void func_801A972C(EffectEntity *entity) { + EffectDrawScript *script = (EffectDrawScript *)entity; + + if (entity->unk024 >= script->stopFrame) { + /* Called with no argument: the entity is already in $a0 and stays there. */ + ((void (*)())func_801A9050)(); + func_801A92F8(script); + entity->pc++; + } +} + +/** @brief Opcode handler: hold still until the count runs out. */ +static void func_801A9788(EffectEntity *entity) { + if (func_801A92F8((EffectDrawScript *)entity) != 0) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} + +/** @brief Opcode handler: no-op. */ +static void func_801A97D0(EffectEntity *entity) { +} + +/** + * @brief Script dispatcher: run this frame's step, draw, and retire the entity. + * + * @return 2 once the script has stopped and its children have drained, 0 while + * it is still running. + */ +static s32 func_801A97D8(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A972C, func_801A9788, func_801A97D0 }; + + handlers[entity->pc](entity); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A0B34(entity); + return 2; + } + return 0; +} + +/** @brief Opcode handler: start the pose script on the taken model. */ +static void func_801A9878(EffectEntity *entity) { + func_801A8F88(entity, func_801A97D8, &D_801D4AFC, 0, 0x2D, 0); + entity->pc++; +} + +/** @brief Opcode handler: release the model once the count reaches 30. */ +static void func_801A98D0(EffectEntity *entity) { + if (entity->unk024 == 30) { + entity->unk010->unk063 = 0; + entity->pc++; + } +} + +/** @brief Opcode handler: hand the current part to battle, then stop. */ +static void func_801A9904(EffectEntity *entity) { + if (entity->unk024 >= 30) { + func_800BFE1C(&entity->animSet->anims[entity->unk02A].parts[entity->unk02B]); + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} + +/** @brief Opcode handler: no-op. */ +static void func_801A998C(EffectEntity *entity) { +} + +/** @brief Script dispatcher: the take-model script, with its two timed cues. */ +static s32 func_801A9994(EffectEntity *entity) { + EffectHandler handlers[4] = { func_801A9878, func_801A98D0, func_801A9904, func_801A998C }; + + func_801A0CE8(entity); + func_801A1078(entity); + handlers[entity->pc](entity); + if (entity->unk024 == 0) { + func_800C4764(D_801A9FAC, 0, 0x80); + } + if (entity->unk024 == 20) { + EffectTintScript *child = + func_801A0B5C(&D_801D4ACC, func_801A16A4, sizeof(EffectTintScript), entity); + + child->steps = D_801A9FA0; + } + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A0B34(entity); + return 2; + } + return 0; +} + +/** @brief Opcode handler: consume the opcode and do nothing else. */ +static void func_801A9AA4(EffectEntity *entity) { + entity->pc++; +} + +/** @brief Opcode handler: consume the opcode and do nothing else. */ +static void func_801A9AB8(EffectEntity *entity) { + entity->pc++; +} + +/** @brief Opcode handler: carve both prim banks and start the draw script. */ +static void func_801A9ACC(EffectEntity *entity) { + if (entity->wait == 0) { + /* Both banks are carved out of the same byte arena behind the TIM. */ + D_801D3F7C = (EffectMote *)D_801D3CDC; + D_801D3CDC += EFFECT_BANK0_BYTES; + D_801D3F80 = (EffectDrawNode *)D_801D3CDC; + D_801D3CDC += EFFECT_BANK1_BYTES; + func_801A8FF8(); + func_801A0B5C(&D_801D3F6C, func_801A9570, 0x40, entity); + entity->pc++; + } +} + +/** @brief Opcode handler: start a task running @ref func_801A9994. */ +static void func_801A9B5C(EffectEntity *entity) { + entity->unk063 = 1; + func_801A0B5C(&D_801D49FC, func_801A9994, 0x58, entity); + entity->pc++; +} + +/** @brief Opcode handler: repeat the step until the loop counter runs out. */ +static void func_801A9BB4(EffectEntity *entity) { + if (entity->unk063 == 0) { + if (entity->unk02A < entity->unk058) { + entity->unk02A++; + entity->unk02E++; + entity->pc--; + } else { + /* The byte trio at unk060 is this script's countdown, one halfword wide. */ + *(s16 *)entity->unk060 = 10; + entity->pc++; + } + } +} + +/** @brief Opcode handler: spawn the trailing spark once the wait runs out. */ +static void func_801A9C18(EffectEntity *entity) { + /* The byte trio at unk060 is this script's countdown, one halfword wide. */ + if (--*(s16 *)entity->unk060 <= 0) { + func_801A0B5C(&D_801D3F6C, func_801A968C, 0x40, entity); + entity->pc++; + } +} + +/** @brief Opcode handler: stall here until @c unk05E reaches zero. */ +static void func_801A9C80(EffectEntity *entity) { + if (entity->unk05E == 0) { + entity->pc++; + } +} + +/** @brief Opcode handler: consume the opcode and do nothing else. */ +static void func_801A9CA8(EffectEntity *entity) { + entity->pc++; +} + +/** @brief Opcode handler: consume the opcode and do nothing else. */ +static void func_801A9CBC(EffectEntity *entity) { + entity->pc++; +} + +/** @brief Opcode handler: raise @ref EFFECT_FLAG_STOP and consume the opcode. */ +static void func_801A9CD0(EffectEntity *entity) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; +} + +/** @brief Opcode handler: no-op. */ +static void func_801A9CEC(EffectEntity *entity) { +} + +/** + * @brief Run one frame of the effect: its opcode, then every task pool it owns. + * + * @return 2 once the script has stopped and its children have drained. + */ +static s32 func_801A9CF4(EffectEntity *entity) { + EffectHandler handlers[11] = { + func_801A9AA4, func_801A9AB8, func_801A9ACC, func_801A9B5C, func_801A9BB4, + func_801A9C18, func_801A9C80, func_801A9CA8, func_801A9CBC, func_801A9CD0, + func_801A9CEC + }; + MATRIX *view = EFFECT_SCRATCHPAD; + + *view = D_800F02C8; + D_801D3CD8 = (u8 *)view; + D_801D3CD4 = view; + if (entity->unk05C & EFFECT_FRAME_ODD) { + D_801D479C = D_801D47A4; + D_801D47A0 = D_801D47AC; + } else { + D_801D479C = D_801D47A8; + D_801D47A0 = D_801D47B0; + } + func_801A0EAC(entity); + handlers[entity->pc](entity); + D_801D3F8C = 0; + entity->unk05E = 0; + D_801D3F8E = 0; + entity->unk05E += func_800B2B68(&D_801D49FC); + entity->unk05E += func_800B2B68(&D_801D4ACC); + entity->unk05E += func_800B2B68(&D_801D478C); + entity->unk05E += func_800B2B68(&D_801D3F6C); + entity->unk05C++; + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait == 0) { + func_801A0B34(entity); + return 2; + } + } + return 0; +}