diff --git a/Makefile b/Makefile index 8077eb32..90b1d2e8 100644 --- a/Makefile +++ b/Makefile @@ -53,6 +53,38 @@ PSYQ40_MASPSXFLAGS := --aspsx-version=2.56 # used by O0_SRCS PSYQ41_MASPSXFLAGS := --aspsx-version=2.67 # default for PsyQ 4.1 sources PSYQ43_MASPSXFLAGS := --aspsx-version=2.77 # used by PSYQ43_SRCS +# ASPSX expands an integer division into `div` plus the range checks that break +# on a zero divisor and on INT_MIN / -1 -- nine instructions the original build +# selected per assembler invocation, so it is a property of the OBJECT. +# +# Counted over the shipped code: every effect overlay is uniform, 198 expanding +# and 70 not, so those are keyed by binary. Every other overlay is bare. +# SLUS_008.92 is the one binary whose own objects disagree, 45 expanded against +# 9 bare, so its sources take the override below rather than a binary-wide rule. +# +# Do not key the effect overlays on filenames: the same engine source will be +# linked into overlays from both sets, and ten functions already ship both ways. +NO_EXPAND_DIV := effect_011 effect_012 effect_017 effect_044 effect_063 effect_064 \ + effect_065 effect_069 effect_070 effect_089 effect_102 effect_105 \ + effect_107 effect_108 effect_110 effect_112 effect_113 effect_116 \ + effect_117 effect_118 effect_123 effect_124 effect_126 effect_128 \ + effect_129 effect_131 effect_132 effect_134 effect_135 effect_136 \ + effect_138 effect_139 effect_140 effect_207 effect_210 effect_212 \ + effect_213 effect_214 effect_216 effect_226 effect_227 effect_251 \ + effect_260 effect_275 effect_276 effect_277 effect_278 effect_279 \ + effect_280 effect_283 effect_284 effect_285 effect_287 effect_290 \ + effect_291 effect_297 effect_298 effect_300 effect_302 effect_304 \ + effect_308 effect_311 effect_314 effect_316 effect_317 effect_320 \ + effect_322 effect_323 effect_325 effect_338 + +# Sources whose object expands inside a binary that otherwise does not. Only +# SLUS_008.92 mixes, so only its sources belong here. +EXPAND_DIV_SRCS := + +# $(call expand_div,,): the source override wins, else effect +# overlays expand unless listed above, and nothing else expands. +expand_div = $(if $(filter $(2),$(EXPAND_DIV_SRCS)),--expand-div,$(if $(filter effect_%,$(1)),$(if $(filter $(1),$(NO_EXPAND_DIV)),,--expand-div))) + # Source files compiled with PsyQ 4.3 (default is PsyQ 4.1) PSYQ43_SRCS := src/snd_init.c src/snd_dma.c src/snd_voice.c src/snd_bank.c src/snd_param.c src/snd_note.c src/snd_track.c src/snd_cmd.c \ src/world/we_object0.c \ @@ -108,7 +140,7 @@ CODE_OVERLAYS := field_init intro field \ tripletriad battle_render battle world ALL_EFFECT_OVERLAYS := $(filter effect_%,$(SPLAT_BINARIES)) -EFFECTS ?= effect_001 +EFFECTS ?= effect_001 effect_025 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) @@ -242,9 +274,9 @@ $$($(1)_DIR)/%.o: %.c $$(CPP) -E -lang-c -nostdinc -Iinclude $$< -o $$($(1)_DIR)/$$(*F).i && \ $$(if $$(filter $$<,$$(PSYQ43_SRCS)), \ $$(PSYQ43_CC1) -quiet $$(CC_FLAGS) $$($(1)_DIR)/$$(*F).i -o $$($(1)_DIR)/$$(*F).s && \ - cat $$($(1)_DIR)/$$(*F).s | $$(MASPSX) $$(PSYQ43_MASPSXFLAGS) --run-assembler $$(ASFLAGS) -o $$@, \ + cat $$($(1)_DIR)/$$(*F).s | $$(MASPSX) $$(PSYQ43_MASPSXFLAGS) $$(call expand_div,$(1),$$<) --run-assembler $$(ASFLAGS) -o $$@, \ $$(PSYQ41_CC1) -quiet $$(CC_FLAGS) $$($(1)_DIR)/$$(*F).i -o $$($(1)_DIR)/$$(*F).s && \ - cat $$($(1)_DIR)/$$(*F).s | $$(MASPSX) $$(PSYQ41_MASPSXFLAGS) --run-assembler $$(ASFLAGS) -o $$@) + cat $$($(1)_DIR)/$$(*F).s | $$(MASPSX) $$(PSYQ41_MASPSXFLAGS) $$(call expand_div,$(1),$$<) --run-assembler $$(ASFLAGS) -o $$@) $$($(1)_DIR)/assets/%.o: assets/%.bin @mkdir -p $$(dir $$@) diff --git a/config/symbols.extern.txt b/config/symbols.extern.txt index e425ad79..2be5e8bd 100644 --- a/config/symbols.extern.txt +++ b/config/symbols.extern.txt @@ -20,6 +20,7 @@ SquareRoot0 = 0x8003F4A4; VectorNormalS = 0x8003F5C0; VectorNormal = 0x8003F5D4; SquareRoot12 = 0x8003FA74; +MulMatrix0 = 0x8003FB14; CompMatrix = 0x8003FC24; ApplyMatrixLV = 0x8003FD84; ApplyMatrixSV = 0x800404D4; diff --git a/include/battle.h b/include/battle.h index 84de69f3..ef5f90ee 100644 --- a/include/battle.h +++ b/include/battle.h @@ -1365,6 +1365,39 @@ typedef struct { /** @brief Per-entity battle records the effect overlays pose their models from. */ extern BattleEffectSlot D_800EF2D0[]; +/** + * @brief One of the four screen-tint entries battle steps every frame. + * + * @c level rises and falls under an effect's control; the three colour bytes + * are also written together as one word. + */ +typedef struct { + /* 0x00 */ u16 unk000; + /* 0x02 */ u16 level; + /* 0x04 */ u8 pad004[0x28 - 0x4]; + /* 0x28 */ u8 r; + /* 0x29 */ u8 g; + /* 0x2A */ u8 b; + /* 0x2B */ u8 pad02B[0x2C - 0x2B]; +} BattleTint; /* 0x2C */ + +extern BattleTint D_800EF738[]; + +/** + * @name Battle state flags -- @ref D_800EEC5C + * + * One word the battle loop and its effects both test. The names are + * placeholders; only the bit positions are established. + * @{ + */ +#define BATTLE_STATE_UNK001 0x1 +#define BATTLE_STATE_UNK100 0x100 +#define BATTLE_STATE_UNK200 0x200 +/** @} */ + +/** @brief State the battle loop and its effects share; see the flags above. */ +extern s32 D_800EEC5C; + /** @brief Angle every sprite prim is rolled by unless it opts out. */ extern s16 D_800F02A0; diff --git a/include/effect.h b/include/effect.h index 68249764..9ab17be8 100644 --- a/include/effect.h +++ b/include/effect.h @@ -9,7 +9,6 @@ #include "battle/bc_object11.h" #include "battle/bc_object12.h" #include "battle/bc_object13.h" -#include "battle/bc_object15.h" /** * @file @@ -29,6 +28,7 @@ /** @brief @ref EffectEntity::flags -- the script's counter reached its limit. */ #define EFFECT_FLAG_DONE 0x4 #define EFFECT_FLAG_UNK08 0x8 +#define EFFECT_FLAG_UNK10 0x10 /** * @brief @ref EffectAnimSet::flags -- the effect's own assets are already up. * @@ -266,7 +266,18 @@ typedef struct EffectEntity { /* 0x63 */ u8 unk063; /* 0x64 */ u8 pad064[0x68 - 0x64]; /* 0x68 */ s16 unk068; - /* 0x6A */ u8 pad06A[0x100 - 0x6A]; + /* 0x6A */ u8 pad06A[0x6C - 0x6A]; + /* 0x6C */ s16 unk06C; + /* 0x6E */ u8 pad06E[0x70 - 0x6E]; + /* 0x70 */ BattleSpriteAnim *unk070; + /* 0x74 */ u8 pad074[0x78 - 0x74]; + /* 0x78 */ s16 unk078; + /* 0x7A */ s16 unk07A; + /* 0x7C */ s16 unk07C; + /* 0x7E */ s16 unk07E; + /* 0x80 */ u8 pad080[0xC4 - 0x80]; + /* 0xC4 */ s16 unk0C4; + /* 0xC6 */ u8 pad0C6[0x100 - 0xC6]; } EffectEntity; /* >= 0x100 */ /** @brief One step of an effect script: the handler for a single @c pc value. */ @@ -292,4 +303,119 @@ typedef void (*EffectHandler)(EffectEntity *); /** @} */ +/** + * @name Engine scratch + * + * Types every effect overlay's copy of the engine shares: the scratchpad + * records the transform helpers work in, and the mesh stream they draw. + * @{ + */ +/** @brief Scratch a model's bounding box is accumulated in. */ +typedef struct { + /* 0x00 */ SVECTOR v; + /* 0x08 */ VECTOR pos; + /* 0x18 */ SVECTOR min; + /* 0x20 */ SVECTOR max; + /* 0x28 */ s32 flag; +} EffectBoundsScratch; /* 0x2C */ + +/** @brief Scratch the billboard basis is built in. */ +typedef struct { + /* 0x00 */ VECTOR delta; + /* 0x10 */ SVECTOR up; + /* 0x18 */ SVECTOR dir; + /* 0x20 */ MATRIX rot; + /* 0x40 */ MATRIX out; + /* 0x60 */ s32 distSq; + /* 0x64 */ s32 dist; + /* 0x68 */ SVECTOR pos; +} EffectAimScratch; /* 0x70 */ + +/** @brief Scratch a rotation is built in before being folded into a matrix. */ +typedef struct { + /* 0x00 */ MATRIX m; + /* 0x20 */ s16 sin; + /* 0x22 */ s16 cos; +} EffectRotScratch; /* 0x24 */ + +/** @brief One posed mesh vertex; only the position is written here. */ +typedef struct EffectMeshVertex { + /* 0x00 */ u8 pad000[0x4]; + /* 0x04 */ SVECTOR pos; + /* 0x0C */ u8 pad00C[0x10 - 0xC]; +} EffectMeshVertex; /* 0x10 */ + +/** @brief One triangle of a mesh part: three 12-bit vertex indices and UVs. */ +typedef struct { + /* 0x00 */ u16 idx0; + /* 0x02 */ u16 idx1; + /* 0x04 */ u16 idx2; + /* 0x06 */ u16 uv2; + /* 0x08 */ u32 uv0; + /* 0x0C */ u16 uv1; + /* 0x0E */ u16 tpage; /**< Bit 0x200 selects the semi-transparent blend. */ +} EffectMeshTri; /* 0x10 */ + +/** @brief One quad of a mesh part. */ +typedef struct { + /* 0x00 */ u16 idx0; + /* 0x02 */ u16 idx1; + /* 0x04 */ u16 idx2; + /* 0x06 */ u16 idx3; + /* 0x08 */ u32 uv0; + /* 0x0C */ u16 uv1; + /* 0x0E */ u16 tpage; + /* 0x10 */ u16 uv2; + /* 0x12 */ u16 uv3; +} EffectMeshQuad; /* 0x14 */ + +/** @brief Vertex indices in a mesh part are 12 bits wide. */ +#define EFFECT_MESH_INDEX_MASK 0xFFF +/** @brief @ref EffectMeshTri::tpage -- draw the textured prim semi-transparent. */ +#define EFFECT_MESH_TPAGE_ABE 0x200 +/** @brief Screen centre the clip rect and the projected points are offset by. */ +#define EFFECT_SCREEN_CX 0xA0 +/** @brief @copybrief EFFECT_SCREEN_CX */ +#define EFFECT_SCREEN_CY 0x78 +/** @brief A prim's tag word with no link: its length in words. */ +#define EFFECT_PRIM_TAG(type) (((sizeof(type) - sizeof(u32)) / sizeof(u32)) << 24) +/** @brief A libgpu prim code in the top byte of an RGB word. */ +#define EFFECT_PRIM_CODE(code) ((u32)(code) << 24) +/** @brief The 24 colour bits of that word, with the code byte taken off. */ +#define EFFECT_PRIM_RGB 0xFFFFFF + +/** @brief Scratch @ref func_801A1EBC works a mesh in; taken from the battle scratchpad. */ +typedef struct { + /* 0x00 */ MATRIX view; + /* 0x20 */ MATRIX light; + /* 0x40 */ DVECTOR sxy[4]; /**< Projected vertices, offset into the clip rect. */ + /* 0x50 */ u8 pad050[0x70 - 0x50]; + /* 0x70 */ SVECTOR normal; + /* 0x78 */ u8 pad078[0x8C - 0x78]; + /* 0x8C */ s32 nclip; + /* 0x90 */ s32 otz; + /* 0x94 */ s32 idx0; + /* 0x98 */ s32 idx1; + /* 0x9C */ s32 idx2; + /* 0xA0 */ s32 idx3; + /* 0xA4 */ u16 unk0A4; + /* 0xA6 */ u16 unk0A6; + /* 0xA8 */ u32 colour; /**< Shaded triangle RGB + code word. */ + /* 0xAC */ u32 unk0AC; /**< Shaded quad RGB + code word. */ + /* 0xB0 */ u32 unk0B0; /**< Textured triangle RGB + code word. */ + /* 0xB4 */ u32 unk0B4; /**< Textured quad RGB + code word. */ + /* 0xB8 */ u32 visible; /**< Copy of the slot's part mask. */ + /* 0xBC */ u16 tpage; + /* 0xBE */ u16 clut; + /* 0xC0 */ s16 clipX0; + /* 0xC2 */ s16 clipX1; + /* 0xC4 */ s16 clipY0; + /* 0xC6 */ s16 clipY1; + /* 0xC8 */ s16 offX; + /* 0xCA */ s16 offY; + /* 0xCC */ s16 reject; /**< Set once a primitive fails the cull or clip test. */ + /* 0xCE */ s16 unk0CE; /**< Copy of @ref EffectRender::unk0F4. */ +} EffectMeshScratch; /* 0xD0 */ +/** @} */ + #endif /* EFFECT_H */ diff --git a/include/effect/effect_025.h b/include/effect/effect_025.h new file mode 100644 index 00000000..5d64ab4d --- /dev/null +++ b/include/effect/effect_025.h @@ -0,0 +1,138 @@ +#ifndef EFFECT_EFFECT_025_H +#define EFFECT_EFFECT_025_H + +#include "effect.h" + +/** + * @brief Entry point of the effect_025 overlay. + * + * battle.bin calls this at the overlay's load address once the image is in + * place; it builds the effect's root entity and its task pools. + * + * @param animSet Animation table the effect draws its models from. + * @return The task pool the effect's scripts run out of. + */ +void *func_801A0000(EffectAnimSet *animSet); + +/** @brief One animated part of a posed model; its flags size its pose slot. */ +typedef struct { + /* 0x00 */ s32 unk000; + /* 0x04 */ s32 flags; +} EffectPosePart; + +/** @brief The part list a posed model carries, each part at a byte offset. */ +typedef struct { + /* 0x00 */ s32 count; + /* 0x04 */ s32 unk004; + /* 0x08 */ s32 offsets[1]; +} EffectPoseParts; + +/** @brief A posed model: a header naming the part list behind it. */ +typedef struct { + /* 0x00 */ s32 unk000; + /* 0x04 */ s32 partsOffset; +} EffectPoseModel; + +/** + * @name Overlay data + * + * Sprite animation tables in the overlay image, handed to an entity through + * @ref EffectEntity::unk04C. + * @{ + */ + +/** @brief Table of offsets battle.bin resolves when the effect starts. */ +extern u8 D_801A8E50; + +/** @brief TIM uploaded to VRAM when the effect starts; also the first prim bank. */ +extern u8 D_801A9698; + +/** @brief The second prim bank, behind the first. */ +extern u8 D_801BB698; + +/** @brief AKAO sound sequence, played on the script's first frame. */ +extern u8 D_801A83D0[]; + +extern BattleSpriteAnim D_801A84E8; +extern BattleSpriteAnim D_801A8694; +extern BattleSpriteAnim D_801A8708; +extern BattleSpriteAnim D_801A875C; +extern BattleSpriteAnim D_801A8820; +extern BattleSpriteAnim D_801A894C; +extern BattleSpriteAnim D_801A8AF8; +extern BattleSpriteAnim D_801A8CA4; + +/** @} */ + +/** + * @name Overlay bss + * + * Working storage past the end of the overlay image (@c 0x801D3EA4). Each task + * pool's 0x10-byte header sits directly behind the entries it hands out. + * @{ + */ + +/** @brief Root entity pool: 2 entries of 0x64. */ +extern s32 D_801D3EC4; +extern s32 D_801D3F94; + +/** @brief Script entity pool entries; the header is @ref D_801D4104. */ +extern s32 D_801D3FA4; + +/** @brief Child entity pool entries; the header is @ref D_801D7314. */ +extern s32 D_801D4114; + +/** @brief Spark pool entries; the header is @ref D_801E74F4. */ +extern s32 D_801D7324; + +/** @brief Texture the posed model is drawn with. */ +extern s32 D_801D3EA4; + +/** @brief Cursor this frame's prims are written through. */ +extern void *D_801D3EB8; + +/** @brief Draw-mode command word every emitted prim carries. */ +extern u32 D_801EEA0C; + +/** @brief Script entity pool: 4 entries of 0x58. */ +extern s32 D_801D4104; + +/** @brief The two prim banks, one per parity of the frame counter. */ +extern u8 *D_801D3EBC; +extern u8 *D_801D3EC0; + +/** @brief Motes to spawn on each frame of a rising script. */ +extern s16 D_801D3DF0[]; +extern s16 D_801D3E2C[]; +extern s16 D_801D3E68[]; + +/** @brief Models a script step poses through @ref EffectEntity::unk060. */ +extern EffectPoseModel D_801D20B8; +extern EffectPoseModel D_801D238C; + +/** @brief Child entity pool: 0x40 entries of 0xC8. */ +extern s32 D_801D7314; + +/** @brief Render state for the effect's own model. */ +extern s32 D_801ED2F4; +extern EffectRender D_801ED304; + +/** @brief Spark pool: 0x1F4 entries of 0x84. */ +extern s32 D_801E74F4; +extern EffectRenderPart D_801E7504; +extern s32 D_801E7534; + +/** @brief The sixteen hex digit glyphs, cached from the main string table. */ +extern u8 D_801ED404[]; + +/** @brief Debug text cursor: X, Y, and the colour the glyphs are drawn in. */ +extern s32 D_801ED418; +extern s32 D_801ED41C; +extern s32 D_801ED420; + +/** @brief Per-joint world matrices, one per joint of the posed skeleton. */ +extern MATRIX D_801ED424[]; + +/** @} */ + +#endif /* EFFECT_EFFECT_025_H */ diff --git a/include/psxsdk/inline_c.h b/include/psxsdk/inline_c.h index 732f5d40..eea297f1 100644 --- a/include/psxsdk/inline_c.h +++ b/include/psxsdk/inline_c.h @@ -69,4 +69,57 @@ /* RT -- rotation matrix times V0, plus the translation vector, unscaled. */ #define gte_rt() gte_mvmva(1, 0, 0, 0, 0) +/* Load the packed RGB source DPCS reads (GTE data register 6). */ +#define gte_ldrgb(r0) __asm__ volatile ( \ + "lwc2 $6, 0( %0 )" \ + : \ + : "r"(r0)) + +/* DPCS -- fade the loaded colour toward the far colour by IR0. */ +#define gte_dpcs() __asm__ volatile ( \ + "nop;" \ + "nop;" \ + ".word 0x4A780010" \ + : : ) + +/* Load the three colour FIFO slots, and RGBC from the third. */ +#define gte_ldrgb3(r0, r1, r2) __asm__ volatile ( \ + "lwc2 $20, 0( %0 );" \ + "lwc2 $21, 0( %1 );" \ + "lwc2 $22, 0( %2 );" \ + "lwc2 $6, 0( %2 )" \ + : \ + : "r"(r0), "r"(r1), "r"(r2)) + +/* DPCT -- run DPCS over all three colours in the FIFO. */ +#define gte_dpct() __asm__ volatile ( \ + "nop;" \ + "nop;" \ + ".word 0x4AF8002A" \ + : : ) + +/* Store the three colour FIFO slots. */ +#define gte_strgb3(r0, r1, r2) __asm__ volatile ( \ + "swc2 $20, 0( %0 );" \ + "swc2 $21, 0( %1 );" \ + "swc2 $22, 0( %2 )" \ + : \ + : "r"(r0), "r"(r1), "r"(r2) \ + : "memory") + +/* GPF -- IR1..IR3 = IR0 * IR1..IR3, shifted 12. Two nops cover the stall + * between the mtc2 that loads IR and the op that reads it. */ +#define gte_gpf1() __asm__ volatile ( \ + "nop;" \ + "nop;" \ + ".word 0x4B98003D" \ + : : ) + +/* GPL -- IR1..IR3 += IR0 * IR1..IR3, shifted 12. */ +#define gte_gpl1() __asm__ volatile ( \ + "nop;" \ + "nop;" \ + ".word 0x4BA8003E" \ + : : ) + #endif /* INLINE_C_H */ diff --git a/include/psxsdk/libgpu.h b/include/psxsdk/libgpu.h index 66409595..1b83e34a 100644 --- a/include/psxsdk/libgpu.h +++ b/include/psxsdk/libgpu.h @@ -465,6 +465,7 @@ void *ReadTIM(void *timimg); s32 GetODE(void); void SetSemiTrans(void *p, s32 abe); void SetShadeTex(void *p, s32 tge); +void SetDrawMode(void *p, s32 dfe, s32 dtd, s32 tpage, RECT *tw); void SetDrawTPage(void *p, s32 dfe, s32 dtd, u16 tpage); void SetTile(void *p); void SetSprt(SPRT *p); diff --git a/include/psxsdk/libgte.h b/include/psxsdk/libgte.h index f1875e3e..d857beee 100644 --- a/include/psxsdk/libgte.h +++ b/include/psxsdk/libgte.h @@ -40,6 +40,7 @@ MATRIX *RotMatrixYXZ(SVECTOR *r, MATRIX *m); MATRIX *RotMatrixZ(s32 r, MATRIX *m); MATRIX *CompMatrix(MATRIX *m0, MATRIX *m1, MATRIX *m2); MATRIX *MulMatrix(MATRIX *m0, MATRIX *m1); +MATRIX *MulMatrix0(MATRIX *m0, MATRIX *m1, MATRIX *m2); /**< @p m2 = @p m0 * @p m1, leaving both operands alone. */ MATRIX *ScaleMatrix(MATRIX *m, VECTOR *scale); MATRIX *ScaleMatrixL(MATRIX *m, VECTOR *scale); MATRIX *TransposeMatrix(MATRIX *m0, MATRIX *m1); diff --git a/src/battle/bc_object10.c b/src/battle/bc_object10.c index cc0ca8fc..83ed7aca 100644 --- a/src/battle/bc_object10.c +++ b/src/battle/bc_object10.c @@ -1,13 +1,13 @@ #include "common.h" #include "battle/bc_object8.h" #include "battle/bc_object10.h" +#include "battle.h" extern u8 D_800F082C[]; extern u8 D_800F085C[]; extern u8 D_800F0830[]; extern u8 D_800F1668[]; extern u8 D_800E3DA8[]; -extern u8 D_800EF738[]; extern u8 D_800F05F0[]; extern u8 D_800F0854[]; extern u8 D_80170000[]; @@ -263,24 +263,22 @@ void func_800B9048(s32 a0) { INCLUDE_ASM("asm/ovl/battle/nonmatchings/bc_object10", func_800B9078); /** - * @brief Initialize D_800EF738 color entries and register animation callback. + * @brief Black out the screen tint and start the task that animates it. * - * Clears 3 bytes at offset 0x28 for each of 4 entries at stride 0x2C - * in D_800EF738. Then registers func_800B9078 via func_800B2C58, - * clears the halfword at offset 0xC of the result, and stores a0 - * at offset 0xE. + * @param a0 Stored at offset 0xE of the task's storage. * - * @param a0 Value to store at result offset 0xE. + * @note The colour stores are volatile: without it the three bytes are merged + * into one halfword and a byte. */ void func_800B9114(s32 a0) { - u8 *ptr; + BattleTint *tint; s32 i = 0; - ptr = D_800EF738; + tint = D_800EF738; for (; i < 4; i++) { - *(volatile u8 *)(ptr + 0x28) = 0; - *(volatile u8 *)(ptr + 0x29) = 0; - *(volatile u8 *)(ptr + 0x2A) = 0; - ptr += 0x2C; + *(volatile u8 *)&tint->r = 0; + *(volatile u8 *)&tint->g = 0; + *(volatile u8 *)&tint->b = 0; + tint++; } { u8 *result = func_800B2C58(func_800B9078); diff --git a/src/battle/bc_object14.c b/src/battle/bc_object14.c index 605cff04..e0c42437 100644 --- a/src/battle/bc_object14.c +++ b/src/battle/bc_object14.c @@ -9,10 +9,8 @@ extern u8 D_800FA4FC[]; extern u8 D_800E662C[]; extern u8 D_800F1B90[]; extern u8 D_800FA4F8[]; -extern u8 D_800EF738[]; extern u8 D_800FA504[]; extern u8 D_800FA500[]; -extern u8 D_800EEC5C[]; extern u8 D_800EEC54[]; extern u8 D_800F02F4[]; s32 func_800C5B1C(u8 *a0); @@ -23,6 +21,7 @@ void func_800472F4(void); void sndEnableReverb(s32); void sndDisableReverb(s32); void func_8009B6B0(void); +static void func_800C6DB4(s32 level, s32 colour); INCLUDE_ASM("asm/ovl/battle/nonmatchings/bc_object14", func_800C4A64); @@ -122,13 +121,13 @@ s32 func_800C5490(void) { volatile s32 *pVal = (volatile s32 *)D_800F1B90; s32 val = *pVal; if (val < 0) { - if (*(s32 *)D_800EEC5C & 0x200) { + if (D_800EEC5C & BATTLE_STATE_UNK200) { return -1; } func_800C5338(1); return -1; } else { - if (*(s32 *)D_800EEC5C & 0x200) { + if (D_800EEC5C & BATTLE_STATE_UNK200) { func_800C5338(0); } return *pVal; @@ -293,25 +292,23 @@ void func_800C6D3C(s32 a0, s32 a1, s32 a2, s32 a3) { } /** - * @brief Write a halfword and a word to 4 entries of D_800EF738 table. + * @brief Set every screen tint to @p colour at @p level. * - * Stores a0 as a halfword at offset 2 and a1 as a word at offset 0x28 - * for 4 consecutive entries at stride 0x2C. - * - * @param a0 Halfword value to store at each entry's offset 2. - * @param a1 Word value to store at each entry's offset 0x28. + * @param level How far each tint has come up; @ref func_800B9078 steps it. + * @param colour The three colour bytes, written together as one word. */ -void func_800C6DB4(s32 a0, s32 a1) { +static void func_800C6DB4(s32 level, s32 colour) { s32 i = 0; - s32 base = (s32)D_800EF738; - s32 base2 = base + 0x28; - s32 ptr = base; + BattleTint *base = D_800EF738; + u32 *rgb = (u32 *)&base->r; + BattleTint *tint = base; + do { - *(s16 *)(ptr + 2) = a0; - *(s32 *)base2 = a1; - base2 += 0x2C; + tint->level = level; + *rgb = colour; + rgb += sizeof(BattleTint) / sizeof(u32); i++; - ptr += 0x2C; + tint++; } while (i < 4); } diff --git a/src/battle/bc_object16.c b/src/battle/bc_object16.c index 408c1c9f..0a0217a4 100644 --- a/src/battle/bc_object16.c +++ b/src/battle/bc_object16.c @@ -16,7 +16,6 @@ extern u8 D_800FB444[]; extern u8 D_800FB448[]; extern u8 D_800F1B80[]; extern u8 D_800FB408[]; -extern s32 D_800EEC5C; u32 func_8009A2E0(void); @@ -60,29 +59,13 @@ INCLUDE_ASM("asm/ovl/battle/nonmatchings/bc_object16", func_800CCCB8); INCLUDE_ASM("asm/ovl/battle/nonmatchings/bc_object16", func_800CD1C0); /** - * @brief Render a particle/effect primitive and update its animation state. + * @brief Draw one particle and step it, unless the battle is held. * - * Builds a transform matrix from the particle's position, Y rotation and - * scale, composes it with the current world matrix at @c D_800F02C8, then - * sets the GTE rotation/translation registers. Allocates a 0x58-byte - * primitive packet from the scratch ring (@c func_800B3698), initializes - * its header fields (cmd word @c 0x3867 at offset 0x14, attribute word - * @c 0x230 at offset 0x1C). When the frame counter has reached 8, zeros - * the RGB color triple at offsets 0x8/0x9/0xA, encodes the post-frame-8 - * index as @c (frame-8)*341 at offset 0xC, and ORs @c 0xC0 into the - * attribute word. Submits the packet via @c func_800CBC68 and updates the - * scratch tail pointer @c D_800FA5F0 with the call's return value, then - * frees the temporary 0x58 byte slot. + * The draw happens either way. @ref BATTLE_STATE_UNK001 gates only the step, + * so a held battle keeps redrawing the particle where it stands. * - * If bit 0 of @c D_800EEC5C is set, returns @c 0 immediately. Otherwise - * advances the particle: angle += angVel; angVel decays by >>4; sizeX += - * sizeXVel; for the first 5 frames sizeY accumulates sizeYVel, after that - * it instead decays by sizeYVel/2; sizeYVel always decays by >>3. Frame - * counter increments by 1 and the function returns @c 2 once the counter - * passes @c 20 (signaling the effect should end), @c 0 otherwise. - * - * @param p Particle entry to render and step. - * @return @c 2 when the particle has expired, @c 0 otherwise. + * @param p Particle entry to draw and step. + * @return @c 2 once the particle has expired, @c 0 otherwise. */ s32 func_800CD35C(ParticleEntry *p) { SVECTOR rot; @@ -123,7 +106,7 @@ s32 func_800CD35C(ParticleEntry *p) { D_800FA5F0 = func_800CBC68(prim, D_800FA5E8->ot, 2, D_800FA5F0); func_800B36B8(0x58); - if (D_800EEC5C & 1) { + if (D_800EEC5C & BATTLE_STATE_UNK001) { return 0; } @@ -151,26 +134,17 @@ INCLUDE_ASM("asm/ovl/battle/nonmatchings/bc_object16", func_800CDB94); INCLUDE_ASM("asm/ovl/battle/nonmatchings/bc_object16", func_800CDD30); /** - * @brief Render a wait-gated particle and step its motion. - * - * Variant of @c func_800CD35C used by particles with a per-entry @c delay - * counter and a per-entry prim cmd word. While @c delay is positive, decrements - * it and returns @c 0 each tick without rendering (skipped entirely when - * bit @c 0 of @c D_800EEC5C is set). Otherwise builds the rotation/scale - * matrix from @c angle/posXYZ/sizeX/sizeY, composes it with @c D_800F02C8, - * allocates a 0x58-byte prim, sets its dispatch index (3 instead of 4), - * cmd word from @c p->cmdWord, and flags @c 0x233 (vs @c 0x230). When - * @c frame >= 4 (vs 8 in @c func_800CD35C) clears the BG color and writes - * depth @c (frame-4)*409 plus OR's @c 0xC0 into flags. Submits the packet - * via @c func_800CBC68 and frees the slot. + * @brief As @ref func_800CD35C, for a particle that waits before it appears. * - * If the global skip bit is clear, advances the particle: @c sizeX += sizeXVel, - * @c sizeY += sizeYVel, @c sizeYVel -= sizeYVel/3 (signed div, not @c >>3). - * Then increments @c frame and returns @c 2 once @c (frame+1) > 14 to signal - * expiration, @c 0 otherwise. + * A positive @c delay counts down in place of the draw. @ref + * BATTLE_STATE_UNK001 freezes the whole thing: neither the countdown nor the + * size step runs while it is set. Past the delay it draws with the entry's own + * command word, and differs from its sibling in its dispatch index, in + * switching behaviour at frame 4 rather than 8, and in decaying @c sizeYVel + * with a signed divide rather than a shift. * - * @param p Particle entry to render and step. - * @return @c 2 when the particle has expired, @c 0 otherwise. + * @param p Particle entry to draw and step. + * @return @c 2 once the particle has expired, @c 0 otherwise. */ s32 func_800CDF3C(ParticleEntry *p) { SVECTOR rot; @@ -179,7 +153,7 @@ s32 func_800CDF3C(ParticleEntry *p) { BattleEffectPrim *prim; if (p->delay > 0) { - if (D_800EEC5C & 1) { + if (D_800EEC5C & BATTLE_STATE_UNK001) { return 0; } p->delay = p->delay - 1; @@ -218,7 +192,7 @@ s32 func_800CDF3C(ParticleEntry *p) { D_800FA5F0 = func_800CBC68(prim, D_800FA5E8->ot, 2, D_800FA5F0); func_800B36B8(0x58); - if (!(D_800EEC5C & 1)) { + if (!(D_800EEC5C & BATTLE_STATE_UNK001)) { p->sizeX = p->sizeX + p->sizeXVel; p->sizeY = p->sizeY + p->sizeYVel; p->sizeYVel = p->sizeYVel - p->sizeYVel / 3; diff --git a/src/battle/bc_object9.c b/src/battle/bc_object9.c index a45b3871..14dc39d3 100644 --- a/src/battle/bc_object9.c +++ b/src/battle/bc_object9.c @@ -13,7 +13,6 @@ extern u8 D_800F02F8[]; extern u8 D_800F0308[]; extern u8 D_800F0408[]; extern u8 D_800F0578[]; -extern u8 D_800EEC5C[]; void func_800B8314(void); s32 func_8013E000(s32); void func_800C2B88(u8 *); @@ -271,18 +270,12 @@ INCLUDE_ASM("asm/ovl/battle/nonmatchings/bc_object9", func_800B79B8); INCLUDE_ASM("asm/ovl/battle/nonmatchings/bc_object9", func_800B7C48); -/** - * @brief Set or clear bit 8 (0x100) of D_800EEC5C. - * - * If a0 is non-zero, sets bit 8. If a0 is zero, clears bit 8. - * - * @param a0 Non-zero to set, zero to clear. - */ -void func_800B7D20(s32 a0) { - if (a0 != 0) { - *(s32 *)D_800EEC5C |= 0x100; +/** @brief Raise @ref BATTLE_STATE_UNK100 when @p on, lower it otherwise. */ +void func_800B7D20(s32 on) { + if (on != 0) { + D_800EEC5C |= BATTLE_STATE_UNK100; } else { - *(s32 *)D_800EEC5C &= ~0x100; + D_800EEC5C &= ~BATTLE_STATE_UNK100; } } diff --git a/src/effect/effect_001.c b/src/effect/effect_001.c index bcb1ceca..6341e559 100644 --- a/src/effect/effect_001.c +++ b/src/effect/effect_001.c @@ -5,6 +5,7 @@ #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_001.h" @@ -26,110 +27,6 @@ static s32 func_801A4434(EffectEntity *entity); /** @brief Stride between the prim and frame banks carved out of the TIM. */ #define EFFECT_BANK_SIZE 0x10000 -/** @brief Scratch a model's bounding box is accumulated in. */ -typedef struct { - /* 0x00 */ SVECTOR v; - /* 0x08 */ VECTOR pos; - /* 0x18 */ SVECTOR min; - /* 0x20 */ SVECTOR max; - /* 0x28 */ s32 flag; -} EffectBoundsScratch; /* 0x2C */ - -/** @brief Scratch the billboard basis is built in. */ -typedef struct { - /* 0x00 */ VECTOR delta; - /* 0x10 */ SVECTOR up; - /* 0x18 */ SVECTOR dir; - /* 0x20 */ MATRIX rot; - /* 0x40 */ MATRIX out; - /* 0x60 */ s32 distSq; - /* 0x64 */ s32 dist; - /* 0x68 */ SVECTOR pos; -} EffectAimScratch; /* 0x70 */ - -/** @brief Scratch a rotation is built in before being folded into a matrix. */ -typedef struct { - /* 0x00 */ MATRIX m; - /* 0x20 */ s16 sin; - /* 0x22 */ s16 cos; -} EffectRotScratch; /* 0x24 */ - -/** @brief One posed mesh vertex; only the position is written here. */ -typedef struct EffectMeshVertex { - /* 0x00 */ u8 pad000[0x4]; - /* 0x04 */ SVECTOR pos; - /* 0x0C */ u8 pad00C[0x10 - 0xC]; -} EffectMeshVertex; /* 0x10 */ - -/** @brief One triangle of a mesh part: three 12-bit vertex indices and UVs. */ -typedef struct { - /* 0x00 */ u16 idx0; - /* 0x02 */ u16 idx1; - /* 0x04 */ u16 idx2; - /* 0x06 */ u16 uv2; - /* 0x08 */ u32 uv0; - /* 0x0C */ u16 uv1; - /* 0x0E */ u16 tpage; /**< Bit 0x200 selects the semi-transparent blend. */ -} EffectMeshTri; /* 0x10 */ - -/** @brief One quad of a mesh part. */ -typedef struct { - /* 0x00 */ u16 idx0; - /* 0x02 */ u16 idx1; - /* 0x04 */ u16 idx2; - /* 0x06 */ u16 idx3; - /* 0x08 */ u32 uv0; - /* 0x0C */ u16 uv1; - /* 0x0E */ u16 tpage; - /* 0x10 */ u16 uv2; - /* 0x12 */ u16 uv3; -} EffectMeshQuad; /* 0x14 */ - -/** @brief Vertex indices in a mesh part are 12 bits wide. */ -#define EFFECT_MESH_INDEX_MASK 0xFFF -/** @brief @ref EffectMeshTri::tpage -- draw the textured prim semi-transparent. */ -#define EFFECT_MESH_TPAGE_ABE 0x200 -/** @brief Screen centre the clip rect and the projected points are offset by. */ -#define EFFECT_SCREEN_CX 0xA0 -#define EFFECT_SCREEN_CY 0x78 -/** @brief A prim's tag word with no link: its length in words. */ -#define EFFECT_PRIM_TAG(type) (((sizeof(type) - sizeof(u32)) / sizeof(u32)) << 24) -/** @brief A libgpu prim code in the top byte of an RGB word. */ -#define EFFECT_PRIM_CODE(code) ((u32)(code) << 24) - -/** @brief Scratch @ref func_801A1EBC works a mesh in; taken from the battle scratchpad. */ -typedef struct { - /* 0x00 */ MATRIX view; - /* 0x20 */ MATRIX light; - /* 0x40 */ DVECTOR sxy[4]; /**< Projected vertices, offset into the clip rect. */ - /* 0x50 */ u8 pad050[0x70 - 0x50]; - /* 0x70 */ SVECTOR normal; - /* 0x78 */ u8 pad078[0x8C - 0x78]; - /* 0x8C */ s32 nclip; - /* 0x90 */ s32 otz; - /* 0x94 */ s32 idx0; - /* 0x98 */ s32 idx1; - /* 0x9C */ s32 idx2; - /* 0xA0 */ s32 idx3; - /* 0xA4 */ u16 unk0A4; - /* 0xA6 */ u16 unk0A6; - /* 0xA8 */ u32 colour; /**< Shaded triangle RGB + code word. */ - /* 0xAC */ u32 unk0AC; /**< Shaded quad RGB + code word. */ - /* 0xB0 */ u32 unk0B0; /**< Textured triangle RGB + code word. */ - /* 0xB4 */ u32 unk0B4; /**< Textured quad RGB + code word. */ - /* 0xB8 */ u32 visible; /**< Copy of the slot's part mask. */ - /* 0xBC */ u16 tpage; - /* 0xBE */ u16 clut; - /* 0xC0 */ s16 clipX0; - /* 0xC2 */ s16 clipX1; - /* 0xC4 */ s16 clipY0; - /* 0xC6 */ s16 clipY1; - /* 0xC8 */ s16 offX; - /* 0xCA */ s16 offY; - /* 0xCC */ s16 reject; /**< Set once a primitive fails the cull or clip test. */ - /* 0xCE */ s16 unk0CE; /**< Copy of @ref EffectRender::unk0F4. */ -} EffectMeshScratch; /* 0xD0 */ - /** * @brief Start an effect script and hand back its task pool. @@ -969,10 +866,10 @@ static void func_801A1EBC(EffectMesh *mesh, u32 *ot, s32 mode, s->offX = render->unk0E8 + render->unk0E4 / 2 - EFFECT_SCREEN_CX; s->offY = render->unk0EA + render->unk0E6 / 2 - EFFECT_SCREEN_CY; /* The shaded prims are semi-transparent (code bit 2), the textured are not. */ - s->colour = *(u32 *)&render->r & 0xFFFFFF; + s->colour = *(u32 *)&render->r & EFFECT_PRIM_RGB; s->unk0AC = s->colour | EFFECT_PRIM_CODE(0x3C | 0x02); s->colour = s->colour | EFFECT_PRIM_CODE(0x34 | 0x02); - s->unk0B0 = slot->unk028 & 0xFFFFFF; + s->unk0B0 = slot->unk028 & EFFECT_PRIM_RGB; s->unk0B4 = s->unk0B0 | EFFECT_PRIM_CODE(0x2C); s->unk0B0 = s->unk0B0 | EFFECT_PRIM_CODE(0x24); s->view = render->unk034; diff --git a/src/effect/effect_025.c b/src/effect/effect_025.c index 3395232b..69021308 100644 --- a/src/effect/effect_025.c +++ b/src/effect/effect_025.c @@ -3,292 +3,4238 @@ * @brief Cura */ #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_025.h" +#include "btl_entity.h" -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A0000); +/** + * @name Pose channel flags + * + * @ref EffectPosePart::flags carries one trio per channel: the channel is + * keyed, it also carries a velocity, it also carries an acceleration. The + * trios are the translation, the rotation, the scale and the colour, in the + * order @ref func_801A6354 walks them; the top two bits are single channels + * of their own, the depth ramp and the vertex-animation cursor. + * @{ + */ +#define EFFECT_POSE_KEY0 0x1 +#define EFFECT_POSE_VEL0 0x2 +#define EFFECT_POSE_ACC0 0x4 +#define EFFECT_POSE_KEY1 0x8 +#define EFFECT_POSE_VEL1 0x10 +#define EFFECT_POSE_ACC1 0x20 +#define EFFECT_POSE_SCALE_KEY 0x40 +#define EFFECT_POSE_SCALE_VEL 0x80 +#define EFFECT_POSE_SCALE_ACC 0x100 +#define EFFECT_POSE_KEY3 0x200 +#define EFFECT_POSE_VEL3 0x400 +#define EFFECT_POSE_ACC3 0x800 +#define EFFECT_POSE_DEPTH 0x1000 +#define EFFECT_POSE_ANIM 0x2000 +/** @} */ + +/** @brief Ceiling of the depth and vertex-animation ramps: @ref ONE in 16.16. */ +#define EFFECT_POSE_RAMP_MAX 0x10000000 + +/** + * @name Scrolled UV words + * + * A UV pair is one halfword, U in the low byte and V in the high one, so a + * step of one V unit is 0x100; the third and fourth pairs of a quad share a + * word. A scroll masks V out, steps it and puts the rest back, and a step that + * carries out of the byte has wrapped. + * @{ + */ +#define EFFECT_UV_V 0xFF00 /**< V of a pair, in place. */ +#define EFFECT_UV_KEEP 0xFFFF00FF /**< What a scroll of one pair leaves alone. */ +#define EFFECT_UV_KEEP23 0x00FF00FF /**< Both U bytes of the shared third and fourth pair. */ +#define EFFECT_UV_WRAP 0x8000 /**< One V byte's worth of wrap. */ +#define EFFECT_UV_SCROLL_STEP (4 << 8) /**< The fixed scroll: four whole V units. */ +/** @} */ + +/** + * @brief Words of a mesh stream's block header ahead of the quad list's count. + * + * The scroll walkers step over the stream's own header, then this, to reach + * the count of gouraud textured quads and the quads themselves. + */ +#define EFFECT_QUAD_LIST_HEADER 7 + +/** + * @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 +/** + * @name Per-part draw flags -- @ref EffectDrawRequest::colours + * + * The low half of each word is the prim's own flag word; the top bits steer + * how @ref func_801A6A00 places and shades the part. + * @{ + */ +#define EFFECT_PART_WORLD_MATRIX 0x10000000 +#define EFFECT_PART_SCROLL_UV 0x20000000 +#define EFFECT_PART_DEPTH_RAMP 0x40000000 +/** @} */ + +/* 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 +#define EFFECT_EMIT_UNK1000 0x1000 +#define EFFECT_EMIT_UNK2000 0x2000 +/** @} */ + +/** + * @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) +/** @} */ -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A014C); +/** + * @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 What the prim emitters walk: the mesh stream and the GTE results. */ +typedef struct { + /* 0x00 */ s32 *stream; + /* 0x04 */ u32 *verts; + /* 0x08 */ u8 r; /**< Far colour the depth cue fades toward. */ + /* 0x09 */ u8 g; + /* 0x0A */ u8 b; + /* 0x0B */ u8 pad00B[0xC - 0xB]; + /* 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; +} EffectPrimBuild; /* 0x34 */ + +/** @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; + /* 0x14 */ u32 unk014; +} EffectTri; /* 0x18 */ + +/** @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; + /* 0x18 */ u32 unk018; +} EffectQuad; /* 0x1C */ + +/** @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 */ -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A029C); +/** + * @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; + /* 0x20 */ u32 unk020; +} EffectTexTri; /* 0x24 */ + +/** @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; + /* 0x1C */ u32 unk01C; +} EffectGouraudTri; /* 0x20 */ + +/** @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; + /* 0x24 */ u32 unk024; +} EffectGouraudQuad; /* 0x28 */ + +/** @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; + /* 0x28 */ u32 unk028; +} EffectGouraudTexTri; /* 0x2C */ + +/** @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; + /* 0x34 */ u32 unk034; +} EffectGouraudTexQuad; /* 0x38 */ -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A0348); +/** + * @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; + /* 0x28 */ u32 unk028; +} EffectTexQuad; /* 0x2C */ + +/** @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 A vertex animation: a header, then @c count posed points per frame. */ +typedef struct { + /* 0x00 */ s32 unk000; + /* 0x04 */ s32 count; + /* 0x08 */ s32 unk008; + /* 0x0C */ SVECTOR frames[1]; +} EffectVertexAnim; -void func_801A0368(void) { +/** + * @brief One posed part, rebuilt from the pose script for every draw. + * + * @ref func_801A6354 zeroes this, walks the script into it and hands it to the + * per-part callback; the fields past @c colour are the animation cursor. + */ +typedef struct { + /* 0x00 */ s16 index; /**< The part's own slot in the pose script. */ + /* 0x02 */ s16 mesh; /**< Which of the model's meshes it draws. */ + /* 0x04 */ u32 unk004; /**< The part entry's header word; @c mesh is its low byte. */ + /* 0x08 */ s16 pos[3]; + /* 0x0E */ u8 pad00E[0x10 - 0xE]; + /* 0x10 */ s16 rot[3]; + /* 0x16 */ u8 pad016[0x18 - 0x16]; + /* 0x18 */ s16 scale[3]; + /* 0x1E */ u8 pad01E[0x20 - 0x1E]; + /* 0x20 */ CVECTOR colour; + /* 0x24 */ s16 depth; + /* 0x26 */ s16 weight; /**< Blend between @c frameA and @c frameB. */ + /* 0x28 */ s16 frameA; + /* 0x2A */ s16 frameB; +} EffectPartPose; /* 0x2C */ + +/** @brief What one pose walk draws: the matrix, its colours and its texture. */ +typedef struct { + /* 0x00 */ MATRIX *m; + /* 0x04 */ u32 *colours; + /* 0x08 */ void *unk08; + /* 0x0C */ s32 unk00C; + /* 0x10 */ s16 unk010; + /* 0x12 */ u8 pad012[0x14 - 0x12]; +} EffectDrawRequest; /* 0x14 */ + +/** @brief What @ref func_801A6354 hands each part it has posed to. */ +typedef void (*EffectPartFn)(s32 *pose, EffectPartPose *part, + EffectDrawRequest *req); + +static EffectEntity *func_801A21C0(void *pool, void *task, s32 stride, + EffectEntity *owner); +static s32 func_801A6354(s32 *pose, EffectPartFn fn, EffectDrawRequest *req); +static EffectTri *func_801A2D08(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *prim); +static EffectTri *func_801A2FE8(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *prim); +static EffectTri *func_801A3320(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *prim); +static EffectTri *func_801A36B4(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *prim); +static EffectTri *func_801A3AAC(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *prim); +static EffectTri *func_801A3DD4(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *prim); +static EffectTri *func_801A4174(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *prim); +static EffectTri *func_801A4548(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *prim); +static s32 func_801A81D0(EffectEntity *entity); + +/** + * @brief Start the effect's script and hand back its task pool. + * + * Builds the four task pools, allocates the root entity, seeds it from the + * animation set, and publishes the two prim banks the renderer draws through. + * + * @param animSet Animation set the effect plays. + * @return The pool the root entity lives in. + */ +void *func_801A0000(EffectAnimSet *animSet) { + EffectEntity *entity; + u8 slot; + + slot = animSet->anims->parts->unk000; + func_800B2A00(&D_801D3F94, &D_801D3EC4, 0x64, 2); + entity = func_801A21C0(&D_801D3F94, func_801A81D0, 0x64, NULL); + entity->animSet = animSet; + entity->unk02D = slot; + entity->unk05A = animSet->anims->unk010; + entity->unk058 = animSet->anims->unk011; + if (!(animSet->flags & EFFECT_ANIMSET_FLAG_LOADED)) { + func_800C3BE0(&D_801A8E50); + func_800BB084(&D_801A9698); + } + D_801D3EBC = &D_801A9698; + D_801D3EC0 = &D_801BB698; + func_800B2A00(&D_801D4104, &D_801D3FA4, 0x58, 4); + func_800B2A00(&D_801D7314, &D_801D4114, 0xC8, 0x40); + func_800B2A00(&D_801E74F4, &D_801D7324, 0x84, 0x1F4); + return &D_801D3F94; } -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A0370); +/** + * @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_801A014C(void) { + D_801ED404[0] = getMenuString(11)[1]; + D_801ED404[1] = getMenuString(11)[2]; + D_801ED404[2] = getMenuString(11)[3]; + D_801ED404[3] = getMenuString(11)[4]; + D_801ED404[4] = getMenuString(11)[5]; + D_801ED404[5] = getMenuString(11)[6]; + D_801ED404[6] = getMenuString(11)[7]; + D_801ED404[7] = getMenuString(11)[8]; + D_801ED404[8] = getMenuString(11)[9]; + D_801ED404[9] = getMenuString(11)[10]; + D_801ED404[10] = getMenuString(11)[11]; + D_801ED404[11] = getMenuString(11)[12]; + D_801ED404[12] = getMenuString(11)[13]; + D_801ED404[13] = getMenuString(11)[14]; + D_801ED404[14] = getMenuString(11)[15]; + D_801ED404[15] = getMenuString(11)[16]; + D_801ED404[16] = 0; + D_801ED418 = 10; + D_801ED41C = 20; + D_801ED420 = 3; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A046C); +/** @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; + u8 text[9]; + s32 i; + + for (i = 7; i >= 0; i--) { + text[i] = D_801ED404[value & 0xF]; + value >>= 4; + } + text[8] = 0; + D_801D3EB8 = func_8002C56C(font, D_801D3EB8, D_801ED418, D_801ED41C, + text, D_801ED420); + D_801ED418 += 0x48; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A0568); +/** @brief Opcode handler: arm the frame delay and advance the running total. */ +static void func_801A0348(void) { + D_801ED418 = 10; + D_801ED41C += 10; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A05A0); +/** @brief Opcode handler: no-op. */ +static void func_801A0368(EffectEntity *entity) { +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A0730); +/** + * @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_801A0370(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, + s32 abr) { + POLY_FT4 *poly = D_801D3EB8; + + 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_801D3EB8 = poly; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A08BC); +/** + * @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_801A046C(s16 x, s16 y, s16 tx, s16 ty, u32 clutX, s32 clutY, + s32 abr) { + POLY_FT4 *poly = D_801D3EB8; + + 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_801D3EB8 = poly; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A09C8); +/** @brief Load the identity matrix. */ +static void func_801A0568(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_025/nonmatchings/effect_025", func_801A0A34); +/** @brief Post-multiply @p m by a rotation of @p angle about X. */ +static void func_801A05A0(MATRIX *m, s32 angle) { + EffectRotScratch *rot = func_800B3698(sizeof(EffectRotScratch)); + + 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_025/nonmatchings/effect_025", func_801A0A64); +/** + * @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_801A0730(MATRIX *m, s32 angle) { + EffectRotScratch *rot = func_800B3698(sizeof(EffectRotScratch)); + + 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_025/nonmatchings/effect_025", func_801A0B6C); +/** @brief Build a matrix in @p m that aims along @p dir with @p up as the roll. */ +static void func_801A08BC(MATRIX *m, SVECTOR *dir, SVECTOR *up) { + MATRIX *basis = func_800B3698(sizeof(MATRIX)); + SVECTOR *row1 = (SVECTOR *)basis->m[1]; + SVECTOR *row2 = (SVECTOR *)basis->m[2]; + + /* A matrix row is 6 bytes, so the whole-vector store spills one halfword + into row 2 -- harmless, row 2 is written next. The width is load-bearing: + it is the unaligned 8-byte copy the target does here, where the + element-wise stores below are three halfwords. */ + *row1 = *up; + row2->vx = dir->vx; + row2->vy = dir->vy; + row2->vz = dir->vz; + gte_ldopv1SV(row1); + gte_ldopv2SV(row2); + gte_op12(); + gte_stsv(basis->m[0]); + MatrixNormal_2(basis, basis); + TransposeMatrix(basis, m); + func_800B36B8(sizeof(MATRIX)); +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A0CB0); +/** @brief Aim @p m along the vector from @p from to @p to, Y up. */ +static void func_801A09C8(MATRIX *m, SVECTOR *from, SVECTOR *to) { + SVECTOR up; + SVECTOR delta; + + up.vx = 0; + up.vy = ONE; + up.vz = 0; + delta.vx = to->vx - from->vx; + delta.vy = to->vy - from->vy; + delta.vz = to->vz - from->vz; + func_801A08BC(m, &delta, &up); +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A0D5C); +/** @brief The heading @p m faces, a quarter turn ahead of its Z axis. */ +static s32 func_801A0A34(MATRIX *m) { + return (ratan2(m->m[2][2], m->m[0][2]) + 0x400) & 0xFFF; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A0D9C); +/** @brief Push @p entity to a random point within @p radius of where it is. */ +static void func_801A0A64(EffectEntity *entity, s16 radius) { + MATRIX m; + SVECTOR angles; + SVECTOR in; + SVECTOR out; + + if (radius == 0) { + radius = 1; + } + angles.vy = rand() & 0xFFF; + angles.vx = rand() & 0xFFF; + func_801A0568(&m); + func_801A0730(&m, angles.vy); + func_801A05A0(&m, angles.vx); + in.vx = 0; + in.vy = 0; + in.vz = rand() % radius; + ApplyMatrixSV(&m, &in, &out); + entity->pos.vx += out.vx; + entity->pos.vy += out.vy; + entity->pos.vz += out.vz; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A1088); +/** @brief Refresh the render matrices from @p pose and apply the offset. */ +static void func_801A0B6C(EffectRender *render, BattleEffectSlot *slot) { + SVECTOR offset; + + render->unk014 = slot->mtx; + render->unk054 = render->unk014; + switch (render->unk0D2) { + case 0: + ApplyMatrixSV(&render->unk054, &render->unk0B4, &offset); + render->unk054.t[0] += offset.vx; + render->unk054.t[1] += offset.vy; + render->unk054.t[2] += offset.vz; + break; + case 1: + render->unk054.t[0] += render->unk0B4.vx; + render->unk054.t[1] += render->unk0B4.vy; + render->unk054.t[2] += render->unk0B4.vz; + break; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A1144); +/** @brief Compose each joint's matrix through the pose and the per-joint offset. */ +static void func_801A0CB0(EffectSkeletonRef *ref, EffectPose *pose) { + MATRIX *offset = D_801ED424; + EffectSkeleton *skeleton = ref->mesh->skeleton; + EffectJoint *joint = skeleton->joints; + s32 i; + + for (i = 0; i < skeleton->count; i++) { + CompMatrix(&pose->world, &joint->mtx, offset); + CompMatrix(&pose->view, &joint->mtx, &joint->mtx); + joint++; + offset++; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A120C); +/** @brief Rebase a table of absolute pointers into offsets from its own head. */ +static void func_801A0D5C(s32 *table) { + s32 *base; + s32 count; + s32 i; + + base = table; + count = *table; + for (i = 0; i < count; i++) { + table++; + *table -= (s32)base; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A205C); +/** @brief Aim the render's model at the camera reference point. */ +static void func_801A0D9C(EffectRender *render) { + EffectAimScratch *a = func_800B3698(sizeof(EffectAimScratch)); + s32 dx; + s32 dy; + s32 dz; + + a->pos.vx = render->unk054.t[0]; + a->pos.vy = render->unk054.t[1]; + a->pos.vz = render->unk054.t[2]; + dx = a->pos.vx - render->unk0DC; + a->delta.vx = dx; + dy = a->pos.vy - render->unk0DE; + a->delta.vy = dy; + dz = a->pos.vz - render->unk0E0; + a->delta.vz = dz; + a->distSq = dx * dx + dy * dy + dz * dz; + a->dist = SquareRoot0(a->distSq); + a->up.vx = 0; + a->up.vy = ONE; + a->up.vz = 0; + VectorNormalS(&a->delta, &a->dir); + func_801A08BC(&a->rot, &a->dir, &a->up); + TransposeMatrix(&a->rot, &a->out); + a->out.t[0] = 0; + a->out.t[1] = 0; + a->out.t[2] = render->scale; + a->rot = render->unk014; + a->rot.t[0] = render->unk014.t[0] - render->unk054.t[0]; + a->rot.t[1] = render->unk014.t[1] - render->unk054.t[1]; + a->rot.t[2] = render->unk014.t[2] - render->unk054.t[2]; + gte_MulMatrix0(&a->out, &a->rot, &render->unk074); + gte_SetTransMatrix(&a->out); + gte_ldlv0(a->rot.t); + gte_mvmva(1, 0, 0, 0, 0); + gte_stlvnl(render->unk074.t); + func_800B36B8(sizeof(EffectAimScratch)); +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A2198); +/** @brief Reset a render request to its default pose, white, and unit scale. */ +static void func_801A1088(EffectRender *render, void *pose, EffectRenderPart *part, + void *texture, BattleEffectSlot *source, s32 frame) { + render->unk00C = &D_800FA5F0; + render->unk000 = pose; + render->part = part; + render->unk010 = &D_801D3EB8; + render->unk0B4.vx = 0; + render->unk0B4.vy = 0; + render->unk0B4.vz = 0; + render->r = 0x80; + render->g = 0x80; + render->b = 0x80; + render->scale = 0x2000; + render->unk0D2 = 1; + render->unk0CE = 0; + render->unk0F2 = 0; + render->unk0A4.vx = 0; + render->unk0A4.vy = 0; + render->unk0A4.vz = 0; + render->unk0AC.vx = 0; + render->unk0AC.vy = 0; + render->unk0AC.vz = 0; + render->unk0BC.vx = 0; + render->unk0BC.vy = 0; + render->unk0BC.vz = 0; + render->slot = source; + render->unk0DA = frame; + part->unk018 = 0x140; + part->unk004 = texture; + part->unk014 = 0; + part->unk016 = 0; + part->unk01A = 0; + part->r = 0x80; + part->g = 0x80; + part->b = 0x80; + part->unk020 = -1; + part->unk024 = 0; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A21C0); +/** @brief Point a render request at a texture page, CLUT and source rectangle. */ +static void func_801A1144(EffectRender *render, SVECTOR *pos, s16 tx, s16 ty, + s16 clutX, s16 clutY, s16 w, s16 h) { + POLY_FT3 poly; + + render->unk0DC = pos->vx; + render->unk0DE = pos->vy; + render->unk0E0 = pos->vz; + setPolyFT3(&poly); + setSemiTrans(&poly, 1); + setShadeTex(&poly, 1); + poly.tpage = getTPage(1, 1, tx, ty); + poly.clut = getClut(clutX, clutY); + render->tpage = poly.tpage; + render->clut = poly.clut; + render->unk0E4 = w; + render->unk0E6 = h; + /* The u coordinate within the page, doubled for the 4bpp texture. */ + render->unk0E8 = ((s16)tx - (s16)(tx & ~0x3F)) * 2; + render->unk0EA = ty & 0xFF; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A234C); +/** + * @brief Draw one mesh: pose its vertices by joint, then emit its triangles and + * quads into the ordering table. + * + * Each part of the mesh is a command stream. Its first section lists vertex + * groups, each posed through one joint of the skeleton with the GTE's light + * matrix (the part's vertices are normals: @c mvmva against the loaded joint). + * The aligned tail holds a triangle list and a quad list. Every primitive is + * transformed twice: once with the view matrix, which gives the screen + * coordinates and depth for BOTH the shaded prim (whose UVs are the screen + * coordinates -- an environment map) and the textured prim; then with the + * light matrix, whose winding decides the back-face cull when the render asks + * for it. A primitive is also rejected when any projected vertex leaves the + * clip rectangle, in which case only the textured prim is emitted. + * + * The prim cursors come from and return to the render's two list heads; the + * scratch record lives in the battle scratchpad for the duration. + * + * @param mesh Skeleton and part table to draw. + * @param ot Ordering table the prims are linked into. + * @param mode Unused. + * @param render Render state: matrices, colour, texture page and clip rect. + */ +static void func_801A120C(EffectMesh *mesh, u32 *ot, s32 mode, + EffectRender *render) { + EffectMeshVertex *vbuf = render->part->unk004; + EffectJoint *joints = mesh->skeleton->joints; + u32 *parts = mesh->parts; + s32 count = parts[0]; + /* battle.bin keeps this list head as a word (see D_800FA5F0). */ + POLY_FT3 *ft3 = (POLY_FT3 *)render->unk00C[0]; + POLY_GT3 *gt3 = render->unk010[0]; + BattleEffectSlot *slot = render->slot; + EffectMeshScratch *s = func_800B3698(sizeof(EffectMeshScratch)); + s32 part; + POLY_FT4 *ft4; + POLY_GT4 *gt4; + u32 colour; + + s->visible = slot->unk07C; + s->unk0CE = render->unk0F4; + s->tpage = render->tpage; + s->clut = render->clut; + s->clipX0 = -(render->unk0E4 / 2); + s->clipX1 = render->unk0E4 / 2 - 1; + s->clipY0 = -(render->unk0E6 / 2); + s->clipY1 = render->unk0E6 / 2 - 1; + s->clipX0 += EFFECT_SCREEN_CX; + s->clipX1 += EFFECT_SCREEN_CX; + s->clipY0 += EFFECT_SCREEN_CY; + s->clipY1 += EFFECT_SCREEN_CY; + s->offX = render->unk0E8 + render->unk0E4 / 2 - EFFECT_SCREEN_CX; + s->offY = render->unk0EA + render->unk0E6 / 2 - EFFECT_SCREEN_CY; + /* The shaded prims are semi-transparent (code bit 2), the textured are not. */ + s->colour = *(u32 *)&render->r & EFFECT_PRIM_RGB; + s->unk0AC = s->colour | EFFECT_PRIM_CODE(0x3C | 0x02); + s->colour = s->colour | EFFECT_PRIM_CODE(0x34 | 0x02); + s->unk0B0 = slot->unk028 & EFFECT_PRIM_RGB; + s->unk0B4 = s->unk0B0 | EFFECT_PRIM_CODE(0x2C); + s->unk0B0 = s->unk0B0 | EFFECT_PRIM_CODE(0x24); + s->view = render->unk034; + s->light = render->unk074; + s->unk0A4 = render->unk0CE; + s->unk0A6 = render->unk0CC; + parts++; + gte_SetRotMatrix(&s->view); + gte_SetTransMatrix(&s->view); + for (part = 0; part < count; part++) { + EffectMeshVertex *vb = vbuf; + s16 *stream = (s16 *)((u8 *)mesh->parts + *parts++); + s32 groups; + s32 g; + s32 n; + s32 v; + s32 tris; + EffectMeshTri *tri; + MATRIX *jm; + EffectMeshQuad *quad; + s32 quads; + POLY_GT3 *gt; + POLY_FT3 *ft; + + if (!((s->visible >> part) & 1)) { + continue; + } + groups = *stream++; + for (g = 0; g < groups; g++) { + jm = &joints[*stream++].mtx; + gte_SetLightMatrix(jm); + gte_ldbkdir(jm->t[0], jm->t[1], jm->t[2]); + n = *stream++; + for (v = 0; v < n; v++) { + s->normal.vx = *stream++; + s->normal.vy = *stream++; + s->normal.vz = *stream++; + gte_ldv0(&s->normal); + gte_mvmva(1, 1, 0, 1, 0); + gte_stsv(&vb->pos); + vb++; + } + } + stream = (s16 *)(((u32)stream + 3) & ~3); + tris = *stream++; + quads = *stream++; + stream += 4; + tri = (EffectMeshTri *)stream; + vb = vbuf; + gt = gt3; + ft = ft3; + for (g = 0; g < tris; g++) { + s->idx0 = tri->idx0 & EFFECT_MESH_INDEX_MASK; + s->idx1 = tri->idx1 & EFFECT_MESH_INDEX_MASK; + s->idx2 = tri->idx2 & EFFECT_MESH_INDEX_MASK; + gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, &vb[s->idx2].pos); + gte_rtpt(); + gte_nclip(); + gte_stopz(&s->nclip); + if (s->nclip > 0) { + gte_avsz3(); + gte_stotz(&s->otz); + s->otz = (u32)s->otz >> 2; + gte_stsxy3_gt3(gt); + gte_stsxy3_ft3(ft); + gte_SetRotMatrix(&s->light); + gte_SetTransMatrix(&s->light); + gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, + &vb[s->idx2].pos); + gte_rtpt(); + s->reject = 0; + gte_nclip(); + gte_stopz(&s->nclip); + if (s->unk0CE == 1 && s->nclip <= 0) { + s->reject = 1; + } + if (!s->reject) { + gte_stsxy3c(s->sxy); + for (v = 0; v < 3; v++) { + if (s->sxy[v].vx < s->clipX0) { + s->reject = 1; + break; + } + if (s->clipX1 < s->sxy[v].vx) { + s->reject = 1; + break; + } + if (s->sxy[v].vy < s->clipY0) { + s->reject = 1; + break; + } + if (s->clipY1 < s->sxy[v].vy) { + s->reject = 1; + break; + } + s->sxy[v].vx += s->offX; + s->sxy[v].vy += s->offY; + } + } + if (!s->reject) { + gt->tag = EFFECT_PRIM_TAG(POLY_GT3); + gt->u0 = s->sxy[0].vx; + gt->v0 = s->sxy[0].vy; + gt->u1 = s->sxy[1].vx; + gt->v1 = s->sxy[1].vy; + gt->u2 = s->sxy[2].vx; + gt->v2 = s->sxy[2].vy; + gt->tpage = s->tpage; + gt->clut = s->clut; + /* Colour and code as one word; four byte stores do not match. */ + colour = s->colour; + *(u32 *)>->r2 = colour; + *(u32 *)>->r1 = colour; + *(u32 *)>->r0 = colour; + addPrim(&ot[s->otz], gt); + gt++; + } + ft->tag = EFFECT_PRIM_TAG(POLY_FT3); + *(u32 *)&ft->u0 = tri->uv0; + *(u32 *)&ft->u1 = *(u32 *)&tri->uv1; + *(u16 *)&ft->u2 = tri->uv2; + *(u32 *)&ft->r0 = s->unk0B0; + if (tri->tpage & EFFECT_MESH_TPAGE_ABE) { + ft->code |= 2; + } + addPrim(&ot[s->otz], ft); + ft++; + gte_SetRotMatrix(&s->view); + gte_SetTransMatrix(&s->view); + } + tri++; + } + quad = (EffectMeshQuad *)tri; + ft4 = (POLY_FT4 *)ft; + gt4 = (POLY_GT4 *)gt; + for (g = 0; g < quads; g++) { + s->idx0 = quad->idx0 & EFFECT_MESH_INDEX_MASK; + s->idx1 = quad->idx1 & EFFECT_MESH_INDEX_MASK; + s->idx2 = quad->idx2 & EFFECT_MESH_INDEX_MASK; + gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, &vb[s->idx2].pos); + gte_rtpt(); + gte_nclip(); + gte_stopz(&s->nclip); + if (s->nclip > 0) { + gte_stsxy3_gt3(gt4); + gte_stsxy3_ft3(ft4); + s->idx3 = quad->idx3 & EFFECT_MESH_INDEX_MASK; + gte_ldv0(&vb[s->idx3].pos); + gte_rtps(); + gte_stsxy(>4->x3); + gte_stsxy(&ft4->x3); + gte_avsz4(); + gte_stotz(&s->otz); + s->otz = (u32)s->otz >> 2; + gte_SetRotMatrix(&s->light); + gte_SetTransMatrix(&s->light); + gte_ldv3(&vb[s->idx0].pos, &vb[s->idx1].pos, + &vb[s->idx2].pos); + gte_rtpt(); + s->reject = 0; + gte_nclip(); + gte_stopz(&s->nclip); + if (s->unk0CE == 1 && s->nclip <= 0) { + s->reject = 1; + } + if (!s->reject) { + gte_stsxy3c(s->sxy); + gte_ldv0(&vb[s->idx3].pos); + gte_rtps(); + gte_stsxy(&s->sxy[3]); + for (v = 0; v < 4; v++) { + if (s->sxy[v].vx < s->clipX0) { + s->reject = 1; + break; + } + if (s->clipX1 < s->sxy[v].vx) { + s->reject = 1; + break; + } + if (s->sxy[v].vy < s->clipY0) { + s->reject = 1; + break; + } + if (s->clipY1 < s->sxy[v].vy) { + s->reject = 1; + break; + } + s->sxy[v].vx += s->offX; + s->sxy[v].vy += s->offY; + } + } + if (!s->reject) { + gt4->tag = EFFECT_PRIM_TAG(POLY_GT4); + gt4->u0 = s->sxy[0].vx; + gt4->v0 = s->sxy[0].vy; + gt4->u1 = s->sxy[1].vx; + gt4->v1 = s->sxy[1].vy; + gt4->u2 = s->sxy[2].vx; + gt4->v2 = s->sxy[2].vy; + gt4->u3 = s->sxy[3].vx; + gt4->v3 = s->sxy[3].vy; + gt4->tpage = s->tpage; + gt4->clut = s->clut; + colour = s->unk0AC; + *(u32 *)>4->r3 = colour; + *(u32 *)>4->r2 = colour; + *(u32 *)>4->r1 = colour; + *(u32 *)>4->r0 = colour; + addPrim(&ot[s->otz], gt4); + gt4++; + } + ft4->tag = EFFECT_PRIM_TAG(POLY_FT4); + *(u32 *)&ft4->u0 = quad->uv0; + *(u32 *)&ft4->u1 = *(u32 *)&quad->uv1; + *(u16 *)&ft4->u2 = quad->uv2; + *(u16 *)&ft4->u3 = quad->uv3; + *(u32 *)&ft4->r0 = s->unk0B4; + if (quad->tpage & EFFECT_MESH_TPAGE_ABE) { + ft4->code |= 2; + } + addPrim(&ot[s->otz], ft4); + ft4++; + gte_SetRotMatrix(&s->view); + gte_SetTransMatrix(&s->view); + } + quad++; + } + ft3 = (POLY_FT3 *)ft4; + gt3 = (POLY_GT3 *)gt4; + } + render->unk00C[0] = (s32)ft3; + render->unk010[0] = gt3; + func_800B36B8(sizeof(EffectMeshScratch)); +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A2480); +/** @brief Pose the effect's model and link its skeletons into the battle OT. */ +static void func_801A205C(EffectRender *render) { + BattleEffectSlot *slot = render->slot; + + func_801A0B6C(render, slot); + if (render->unk0DA != 0xFF) { + func_800BC420(slot->unk060); + } else if (func_800BCA3C(slot->unk060, slot->unk06C) != 0) { + func_800BCF6C(slot->unk060, slot->unk06C, render->unk0D8); + } + CompMatrix(&D_800F02C8, &render->unk014, &render->unk034); + if (!(slot->flags & BATTLE_SLOT_FLAG_UNK20)) { + *render->unk00C = func_800BC060(slot, D_800FA5E8->unk4040, 0x10, *render->unk00C); + } + func_801A0D9C(render); + func_801A120C(slot->mesh, D_800FA5E8->ot, 4, render); + if (slot->unk078 != NULL) { + func_801A120C(slot->unk078->mesh, D_800FA5E8->ot, 4, render); + } + if (render->unk0DA != 0xFF) { + func_800BC420(slot->unk060); + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A24B0); +/** @brief Opcode handler: release one frame of the linked script's wait. */ +static void func_801A2198(EffectEntity *entity) { + if (entity->unk018 != NULL) { + entity->unk018->wait--; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A24E0); +/** + * @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 EffectEntity *func_801A21C0(void *pool, void *task, s32 stride, + EffectEntity *owner) { + EffectEntity *node = func_800B2A84(pool, task); + s32 *clear; + s32 words; + s32 i; + + 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; +} + +/** @brief Cache the battle slot's two anchor points and their midpoint. */ +static void func_801A234C(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; + SVECTOR top; + SVECTOR bottom; + + 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; +} + +/** @brief Copy @c unk030 of the linked model out to @p out. */ +static void func_801A2480(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk014->unk030; +} + +/** @brief Copy @c unk038 of the linked model out to @p out. */ +static void func_801A24B0(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk014->unk038; +} + +/** @brief Copy @c unk040 of the linked model out to @p out. */ +static void func_801A24E0(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk014->unk040; +} + +/** @brief As @ref func_801A234C, but for the slot the animation set names. */ +static void func_801A2510(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->animSet->slot]; + SVECTOR top; + SVECTOR bottom; + + 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; +} + +/** @brief Copy @c unk030 of the linked model out to @p out. */ +static void func_801A264C(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk010->unk030; +} + +/** @brief Copy @c unk038 of the linked model out to @p out. */ +static void func_801A267C(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk010->unk038; +} + +/** @brief Copy @c unk040 of the linked model out to @p out. */ +static void func_801A26AC(EffectEntity *entity, SVECTOR *out) { + *out = entity->unk010->unk040; +} + +/** + * @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_801A26DC(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; + + 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)); +} + +/** @brief Copy the linked model's bounding box out to @p min and @p max. */ +static void func_801A2928(EffectEntity *entity, SVECTOR *min, SVECTOR *max) { + EffectModel *model = entity->unk014; + + *min = model->boundsMin; + *max = model->boundsMax; +} + +/** + * @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_801A2978(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_025/nonmatchings/effect_025", func_801A2510); +/** @brief Half the height of the linked model's bounding box. */ +static s16 func_801A2A1C(EffectEntity *entity) { + EffectModel *model = entity->unk014; -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A264C); + return (s16)(model->boundsMax.vy - model->boundsMin.vy) / 2; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A267C); +/** @brief Half the linked model's larger horizontal extent. */ +static s16 func_801A2A4C(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_025/nonmatchings/effect_025", func_801A26AC); + return (span <= spanZ ? spanZ : spanX) / 2; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A26DC); +/** @brief Height of the linked model's bounding box. */ +static s16 func_801A2AA4(EffectEntity *entity) { + return entity->unk014->boundsMax.vy - entity->unk014->boundsMin.vy; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A2928); +/** @brief Y of the top of the linked model, in battle-entity space. */ +static s16 func_801A2AC8(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A2978); + return slot->unk01E + entity->unk014->boundsMax.vy; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A2A1C); +/** @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]; -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A2A4C); + return slot->unk01E + entity->unk014->boundsMin.vy; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A2AA4); +/** @brief A random point up the linked model, in battle-entity space. */ +static s16 func_801A2B58(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_025/nonmatchings/effect_025", func_801A2AC8); + return slot->unk01E + model->boundsMax.vy - offset / 2; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A2B10); +/** @brief Y of the centre of the linked model, in battle-entity space. */ +static s16 func_801A2C10(EffectEntity *entity) { + EffectModel *model = entity->unk014; -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A2B58); + return D_800EF2D0[entity->unk02D].unk01E + + (model->boundsMax.vy + model->boundsMin.vy) / 2; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A2C10); +/** @brief Centre of the linked model's bounding box. */ +static void func_801A2C64(EffectEntity *entity, SVECTOR *out) { + EffectModel *model = entity->unk014; -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A2C64); + 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; +} + +/** @brief Zero @p size bytes' worth of words starting at @p dst. */ +static void func_801A2CD0(s32 *dst, s32 size) { + s32 i; + + for (i = 0; i < size / 4; i++) { + *dst = 0; + dst++; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A2CD0); +/** + * @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. Each + * triangle is projected, given the build's colour with the semi-transparency + * bit forced on or off, and dropped when the GTE could not project it, when it + * faces away and the build is not two-sided, or when all three vertices leave + * the screen on the same axis. Survivors optionally take a depth cue and are + * linked into @p ot at their own depth. + * + * @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_801A2D08(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; + + 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, 5); + 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->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); + } + prim->unk014 = D_801EEA0C; + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + tri++; + } while (i < count); + } + s->cursor = (s32 *)tri; + return prim; +} + +/** + * @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_801A2FE8(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; + + 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, 6); + 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->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); + } + prim->unk018 = D_801EEA0C; + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + quad++; + } while (i < count); + } + s->cursor = (s32 *)quad; + return (EffectTri *)prim; +} + +/** + * @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_801A3320(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; + + 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, 8); + 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->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); + } + prim->unk020 = D_801EEA0C; + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + tri++; + } while (i < count); + } + s->cursor = (s32 *)tri; + return (EffectTri *)prim; +} + +/** + * @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_801A36B4(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; + + 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, 0xA); + 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->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); + } + prim->unk028 = D_801EEA0C; + addPrim(&ot[s->otz >> otShift], prim); + prim++; + } + } + } + do { + i++; + } while (0); + quad++; + } while (i < count); + } + s->cursor = (s32 *)quad; + return (EffectTri *)prim; +} + +/** + * @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_801A3AAC(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; + + 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, 7); + 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->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; + } + prim->unk01C = D_801EEA0C; + 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_025/nonmatchings/effect_025", func_801A2D08); +/** + * @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_801A3DD4(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; + + 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, 9); + 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->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; + } + prim->unk024 = D_801EEA0C; + 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_025/nonmatchings/effect_025", func_801A2FE8); +/** + * @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_801A4174(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; + + 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, 0xA); + 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->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; + } + prim->unk028 = D_801EEA0C; + 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_025/nonmatchings/effect_025", func_801A3320); +/** + * @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_801A4548(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; + + 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, 0xD); + 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->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; + } + prim->unk034 = D_801EEA0C; + 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_025/nonmatchings/effect_025", func_801A36B4); +/** + * @brief Run every prim kind of one mesh stream into the ordering table. + * + * The stream names one entry per kind; a zero entry means the mesh has none + * of that kind, and the emitters hand the prim cursor on between them. + * + * @return The prim cursor past everything emitted. + */ +static EffectTri *func_801A49A8(EffectPrimBuild *s, u32 *ot, s32 otShift, + EffectTri *prim) { + if (!(s->flags & EFFECT_EMIT_UNK2000)) { + s->verts = (u32 *)(s->stream + 2); + } + s->cursor = (s32 *)((u8 *)s->stream + s->stream[0]); + if (!(s->flags & EFFECT_EMIT_UNK1000)) { + s->unk018 = 0; + } + D_801EEA0C = 0xE1000220; + gte_SetFarColor(s->r, s->g, s->b); + if (*s->cursor != 0) { + prim = func_801A2D08(s, ot, otShift, prim); + } else { + s->cursor++; + } + if (*s->cursor != 0) { + prim = func_801A2FE8(s, ot, otShift, prim); + } else { + s->cursor++; + } + if (*s->cursor != 0) { + prim = func_801A3320(s, ot, otShift, prim); + } else { + s->cursor++; + } + if (*s->cursor != 0) { + prim = func_801A36B4(s, ot, otShift, prim); + } else { + s->cursor++; + } + if (*s->cursor != 0) { + prim = func_801A3AAC(s, ot, otShift, prim); + } else { + s->cursor++; + } + if (*s->cursor != 0) { + prim = func_801A3DD4(s, ot, otShift, prim); + } else { + s->cursor++; + } + if (*s->cursor != 0) { + prim = func_801A4174(s, ot, otShift, prim); + } else { + s->cursor++; + } + if (*s->cursor != 0) { + prim = func_801A4548(s, ot, otShift, prim); + } else { + s->cursor++; + } + return prim; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A3AAC); +/** + * @brief Scroll the textured quad list's V coordinates by one fixed step. + * + * The list @p header names holds the gouraud textured quads; each carries four + * UV pairs, the last two packed into one word. Only the V byte of each moves, + * and all four wrap together so a quad never tears. + * + * @param header Mesh stream to walk. + */ +static void func_801A4BFC(s32 *header) { + EffectEmitGouraudTexQuad *e; + u32 keep[3]; + u32 ch[4]; + u32 uv23; + s32 count; + s32 i; + + header = (s32 *)((u8 *)header + header[0] / 4 * 4); + header += EFFECT_QUAD_LIST_HEADER; + count = *header; + header++; + for (i = 0; i < count; i++) { + e = (EffectEmitGouraudTexQuad *)header; + ch[0] = e->uv0; + keep[0] = ch[0] & EFFECT_UV_KEEP; + ch[0] &= EFFECT_UV_V; + ch[1] = e->uv1; + keep[1] = ch[1] & EFFECT_UV_KEEP; + ch[1] &= EFFECT_UV_V; + uv23 = e->uv23; + ch[3] = uv23; + ch[2] = uv23; + keep[2] = uv23 & EFFECT_UV_KEEP23; + ch[2] &= EFFECT_UV_V; + ch[3] = (ch[3] >> 16) & EFFECT_UV_V; + ch[0] -= EFFECT_UV_SCROLL_STEP; + ch[1] -= EFFECT_UV_SCROLL_STEP; + ch[2] -= EFFECT_UV_SCROLL_STEP; + ch[3] -= EFFECT_UV_SCROLL_STEP; + if (ch[0] > EFFECT_UV_V || ch[1] > EFFECT_UV_V || ch[2] > EFFECT_UV_V || ch[3] > EFFECT_UV_V) { + ch[0] += EFFECT_UV_WRAP; + ch[1] += EFFECT_UV_WRAP; + ch[2] += EFFECT_UV_WRAP; + ch[3] += EFFECT_UV_WRAP; + } + ch[0] &= EFFECT_UV_V; + ch[1] &= EFFECT_UV_V; + ch[2] &= EFFECT_UV_V; + ch[3] = (ch[3] & EFFECT_UV_V) << 16; + e->uv0 = keep[0] | ch[0]; + e->uv1 = keep[1] | ch[1]; + e->uv23 = keep[2] | ch[2] | ch[3]; + header += sizeof(EffectEmitGouraudTexQuad) / sizeof(s32); + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A3DD4); +/** + * @brief Scroll the textured quad list's V coordinates by @p delta. + * + * @ref func_801A4BFC with the step supplied by the caller. + * + * @param header Mesh stream to walk. + * @param delta Step in whole V units. + */ +static void func_801A4D9C(s32 *header, s32 delta) { + EffectEmitGouraudTexQuad *e; + u32 keep[3]; + u32 ch[4]; + u32 uv23; + s32 count; + s32 i; + + header = (s32 *)((u8 *)header + header[0] / 4 * 4); + header += EFFECT_QUAD_LIST_HEADER; + count = *header; + header++; + delta <<= 8; + for (i = 0; i < count; i++) { + e = (EffectEmitGouraudTexQuad *)header; + ch[0] = e->uv0; + keep[0] = ch[0] & EFFECT_UV_KEEP; + ch[0] &= EFFECT_UV_V; + ch[1] = e->uv1; + keep[1] = ch[1] & EFFECT_UV_KEEP; + ch[1] &= EFFECT_UV_V; + uv23 = e->uv23; + ch[3] = uv23; + ch[2] = uv23; + keep[2] = uv23 & EFFECT_UV_KEEP23; + ch[2] &= EFFECT_UV_V; + ch[3] = (ch[3] >> 16) & EFFECT_UV_V; + ch[0] += delta; + ch[1] += delta; + ch[2] += delta; + ch[3] += delta; + if (ch[0] > EFFECT_UV_V || ch[1] > EFFECT_UV_V || ch[2] > EFFECT_UV_V || ch[3] > EFFECT_UV_V) { + ch[0] -= EFFECT_UV_WRAP; + ch[1] -= EFFECT_UV_WRAP; + ch[2] -= EFFECT_UV_WRAP; + ch[3] -= EFFECT_UV_WRAP; + } + ch[0] &= EFFECT_UV_V; + ch[1] &= EFFECT_UV_V; + ch[2] &= EFFECT_UV_V; + ch[3] = (ch[3] & EFFECT_UV_V) << 16; + e->uv0 = keep[0] | ch[0]; + e->uv1 = keep[1] | ch[1]; + e->uv23 = keep[2] | ch[2] | ch[3]; + header += sizeof(EffectEmitGouraudTexQuad) / sizeof(s32); + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A4174); +/** @brief Build this effect's primitive and link it into the battle display list. */ +static void func_801A4F44(EffectEntity *entity) { + s16 count; + BattleSpritePrim *prim; + + if (entity->flags & EFFECT_FLAG_DONE) { + return; + } + prim = func_800B3698(sizeof(BattleSpritePrim)); + func_800C96E4(&entity->pos, ONE, entity->unk054); + prim->anim = entity->unk04C; + count = entity->unk050; + prim->flags = 0; + prim->frame = count; + D_801D3EB8 = func_800C9E10(prim, D_800FA5E8->ot, 2, D_801D3EB8); + func_800B36B8(sizeof(BattleSpritePrim)); +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A4548); +/** @brief Draw @p anim upright at the entity's position, tinted with @p colour. */ +static void func_801A4FE0(EffectEntity *entity, BattleSpriteAnim *anim, + CVECTOR *colour) { + MATRIX m; + BattleSpritePrim *prim; + + if (entity->flags & EFFECT_FLAG_DONE) { + return; + } + func_801A0568(&m); + func_801A05A0(&m, 0x400); + m.t[0] = entity->pos.vx; + m.t[1] = 0; + m.t[2] = entity->pos.vz; + CompMatrix(&D_800F02C8, &m, &m); + SetRotMatrix(&m); + SetTransMatrix(&m); + prim = func_800B3698(sizeof(BattleSpritePrim)); + prim->anim = anim; + prim->frame = entity->unk050; + prim->flags = BATTLE_SPRITE_FLAG_COLOUR; + prim->colour = *colour; + D_801D3EB8 = func_800C9E10(prim, D_800FA5E8->ot, 2, D_801D3EB8); + func_800B36B8(sizeof(BattleSpritePrim)); +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A49A8); +/** @brief As @ref func_801A4FE0, rolled by @p angle and scaled by @p scale. */ +static void func_801A50D4(EffectEntity *entity, BattleSpriteAnim *anim, + CVECTOR *colour, s16 angle, s16 scale) { + MATRIX m; + VECTOR scaleVec; + BattleSpritePrim *prim; + + if (entity->flags & EFFECT_FLAG_DONE) { + return; + } + scaleVec.vz = scale; + scaleVec.vy = scale; + scaleVec.vx = scale; + func_801A0568(&m); + func_801A0730(&m, angle); + func_801A05A0(&m, 0x400); + ScaleMatrix(&m, &scaleVec); + m.t[0] = entity->pos.vx; + m.t[1] = 0; + m.t[2] = entity->pos.vz; + CompMatrix(&D_800F02C8, &m, &m); + SetRotMatrix(&m); + SetTransMatrix(&m); + prim = func_800B3698(sizeof(BattleSpritePrim)); + prim->anim = anim; + prim->frame = entity->unk050; + prim->flags = BATTLE_SPRITE_FLAG_COLOUR; + prim->colour = *colour; + D_801D3EB8 = func_800C9E10(prim, D_800FA5E8->ot, 2, D_801D3EB8); + func_800B36B8(sizeof(BattleSpritePrim)); +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A4BFC); +/** @brief Advance the script's counter, clamping at its limit. */ +static s32 func_801A5204(EffectEntity *entity) { + entity->unk050++; + if (entity->unk052 < entity->unk050) { + entity->unk050 = entity->unk052; + entity->flags |= EFFECT_FLAG_DONE; + return 1; + } + return 0; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A4D9C); +/** @brief Advance the animation frame, wrapping at its limit. */ +static void func_801A524C(EffectEntity *entity) { + entity->unk050++; + if (entity->unk052 < entity->unk050) { + entity->unk050 = 0; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A4F44); +/** @brief Opcode handler: start a rise with a randomised speed. */ +static void func_801A527C(EffectEntity *entity) { + entity->unk04C = &D_801A8820; + entity->unk052 = 0xA; + entity->unk054 = -0x400; + entity->unk05A = (rand() & 7) + 4; + entity->pc++; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A4FE0); +/** @brief Opcode handler: rise by the current speed until the count runs out. */ +static void func_801A52D8(EffectEntity *entity) { + entity->pos.vy += entity->unk05A; + if (func_801A5204(entity) != 0) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A50D4); +/** @brief Opcode handler: no-op. */ +static void func_801A5334(EffectEntity *entity) { +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A5204); +/** + * @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_801A533C(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A527C, func_801A52D8, func_801A5334 }; + + handlers[entity->pc](entity); + if (entity->pos.vy > 0) { + entity->flags |= EFFECT_FLAG_STOP | EFFECT_FLAG_DONE; + } + func_801A4F44(entity); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A2198(entity); + return 2; + } + return 0; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A524C); +/** @brief Opcode handler: pick one of two table pairs at random and start a fall. */ +static void func_801A5404(EffectEntity *entity) { + if (rand() & 1) { + entity->unk04C = &D_801A84E8; + entity->unk070 = &D_801A8AF8; + } else { + entity->unk04C = &D_801A894C; + entity->unk070 = &D_801A8CA4; + } + entity->unk07A = rand() & 0xFFF; + entity->unk052 = 0xF; + entity->unk054 = -0x200; + entity->pc++; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A527C); +/** @brief Opcode handler: hold still until the count runs out. */ +static void func_801A548C(EffectEntity *entity) { + if (func_801A5204(entity) != 0) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A52D8); +/** @brief Opcode handler: no-op. */ +static void func_801A54D4(EffectEntity *entity) { +} -void func_801A5334(void) { +/** @brief Script dispatcher: the falling-mote script, drawn spun and tinted. */ +static s32 func_801A54DC(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A5404, func_801A548C, func_801A54D4 }; + CVECTOR colour; + + handlers[entity->pc](entity); + func_801A4F44(entity); + entity->unk07A = (entity->unk07A + 0x40) & 0xFFF; + colour.b = 0x30; + colour.g = 0x30; + colour.r = 0x30; + func_801A50D4(entity, entity->unk070, &colour, entity->unk07A, 0x2000); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A2198(entity); + return 2; + } + return 0; } -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A533C); +/** @brief Opcode handler: start a fast rise that gravity pulls back. */ +static void func_801A55C0(EffectEntity *entity) { + entity->unk04C = &D_801A8820; + entity->unk050 = 0; + entity->unk052 = 0xA; + entity->unk05A = (rand() & 7) + 0x20; + ((SVECTOR *)entity->unk060)->vy = -3; + entity->pc++; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A5404); +/** @brief Opcode handler: apply gravity, then rise until the count runs out. */ +static void func_801A5620(EffectEntity *entity) { + entity->unk05A += ((SVECTOR *)entity->unk060)->vy; + if (entity->unk05A < 2) { + entity->unk05A = 2; + } + entity->pos.vy += entity->unk05A; + if (func_801A5204(entity) != 0) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A548C); +/** @brief Opcode handler: no-op. */ +static void func_801A56A8(EffectEntity *entity) { +} -void func_801A54D4(void) { +/** + * @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_801A56B0(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A55C0, func_801A5620, func_801A56A8 }; + + handlers[entity->pc](entity); + if (entity->pos.vy > 0) { + entity->flags |= EFFECT_FLAG_STOP | EFFECT_FLAG_DONE; + } + func_801A4F44(entity); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A2198(entity); + return 2; + } + return 0; } -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A54DC); +/** @brief Opcode handler: keep the start point and start a randomised arc. */ +static void func_801A5778(EffectEntity *entity) { + entity->unk078 = rand() & 0xFFF; + *(SVECTOR *)&entity->unk068 = entity->pos; + entity->unk04C = &D_801A8708; + entity->unk052 = 1; + entity->unk05A = (rand() & 0x3F) - 0x30; + entity->unk07C = (rand() & 0x1F) + 0x50; + entity->unk07E = -entity->unk07C / 16; + entity->pc++; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A55C0); +/** @brief Opcode handler: switch to the burst table once the frame count passes 16. */ +static void func_801A581C(EffectEntity *entity) { + func_801A524C(entity); + if (entity->unk024 >= 0x10) { + entity->unk04C = &D_801A875C; + entity->unk052 = 6; + entity->unk050 = 0; + entity->flags |= EFFECT_FLAG_UNK10; + entity->pc++; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A5620); +/** @brief Opcode handler: hold still until the count runs out. */ +static void func_801A5884(EffectEntity *entity) { + if (func_801A5204(entity) != 0) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} -void func_801A56A8(void) { +/** @brief Opcode handler: no-op. */ +static void func_801A58CC(EffectEntity *entity) { } -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A56B0); +/** @brief Script dispatcher: an arc under gravity that trails motes behind it. */ +static s32 func_801A58D4(EffectEntity *entity) { + EffectHandler handlers[4] = { func_801A5778, func_801A581C, func_801A5884, + func_801A58CC }; + EffectEntity *spark; + CVECTOR colour; + + handlers[entity->pc](entity); + entity->unk07C += entity->unk07E; + if (entity->unk07C < 0) { + entity->unk07C = 0; + } + entity->unk068 += rsin(entity->unk040.vy) * entity->unk07C / 4096; + entity->unk06C += rcos(entity->unk040.vy) * entity->unk07C / 4096; + entity->pos.vx = entity->unk068; + entity->pos.vz = entity->unk06C; + entity->unk05A += 0xA; + entity->pos.vy += entity->unk05A; + if (!(entity->flags & EFFECT_FLAG_UNK10) && (entity->unk024 & 1)) { + spark = func_801A21C0(&D_801E74F4, func_801A56B0, 0x84, entity); + func_801A0A64(spark, 0x80); + spark->pos.vy = entity->pos.vy; + ((VECTOR *)&spark->unk030)->vx = ONE; + ((VECTOR *)&spark->unk030)->vy = ONE; + ((VECTOR *)&spark->unk030)->vz = ONE; + } + if (entity->pos.vy > 0) { + entity->flags |= EFFECT_FLAG_STOP | EFFECT_FLAG_DONE; + } + func_801A4F44(entity); + colour.b = 0x50; + colour.g = 0x50; + colour.r = 0x50; + func_801A4FE0(entity, entity->unk04C, &colour); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A2198(entity); + return 2; + } + return 0; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A5778); +/** @brief Opcode handler: ring eight sparks around the model's first point. */ +static void func_801A5AE8(EffectEntity *entity) { + EffectEntity *spark; + s32 angle; + s32 i; + + func_801A2480(entity, &entity->pos); + entity->unk04C = &D_801A8694; + entity->unk052 = 3; + entity->pos.vy -= 0x600; + angle = rand() & 0xFFF; + for (i = 0; i < 8; i++) { + spark = func_801A21C0(&D_801E74F4, func_801A58D4, 0x84, entity); + ((VECTOR *)&spark->unk030)->vx = ONE; + ((VECTOR *)&spark->unk030)->vy = ONE; + ((VECTOR *)&spark->unk030)->vz = ONE; + spark->unk040.vy = angle + (rand() & 0x7F); + angle = (angle + 0x200) & 0xFFF; + } + entity->pc++; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A581C); +/** @brief Opcode handler: hold still until the count runs out. */ +static void func_801A5BD4(EffectEntity *entity) { + if (func_801A5204(entity) != 0) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A5884); +/** @brief Opcode handler: no-op. */ +static void func_801A5C1C(EffectEntity *entity) { +} -void func_801A58CC(void) { +/** @brief Script dispatcher: the rising-mote script, drawn tinted. */ +static s32 func_801A5C24(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A5AE8, func_801A5BD4, func_801A5C1C }; + CVECTOR colour; + + handlers[entity->pc](entity); + func_801A4F44(entity); + colour.b = 0x20; + colour.g = 0x20; + colour.r = 0x20; + func_801A4FE0(entity, entity->unk04C, &colour); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A2198(entity); + return 2; + } + return 0; } -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A58D4); +/** @brief Fill @p count words at @p dst with @p value. */ +static void func_801A5CEC(s32 *dst, s32 value, s32 count) { + s32 i; -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A5AE8); + for (i = count - 1; i != -1; i--) { + *dst++ = value; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A5BD4); +/** @brief Build the rotation of @p angle about X in @p m. */ +static void func_801A5D10(s16 angle, MATRIX *m) { + s32 *p = (s32 *)m; + s32 i; + s16 sin; + s16 cos; + + for (i = 5; i != 0; i--) { + *p++ = 0; + } + sin = rsin(angle); + cos = rcos(angle); + m->m[0][0] = ONE; + m->m[1][1] = cos; + m->m[2][1] = -sin; + m->m[1][2] = sin; + m->m[2][2] = cos; +} -void func_801A5C1C(void) { +/** @brief Build the rotation of @p angle about Y in @p m. */ +static void func_801A5D8C(s16 angle, MATRIX *m) { + s32 *p = (s32 *)m; + s32 i; + s16 sin; + s16 cos; + + for (i = 5; i != 0; i--) { + *p++ = 0; + } + sin = rsin(angle); + cos = rcos(angle); + m->m[2][0] = sin; + m->m[0][0] = cos; + m->m[1][1] = ONE; + m->m[0][2] = -sin; + m->m[2][2] = cos; } -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A5C24); +/** @brief Build the rotation of @p angle about Z in @p m. */ +static void func_801A5E08(s16 angle, MATRIX *m) { + s32 *p = (s32 *)m; + s32 i; + s16 sin; + s16 cos; + + for (i = 5; i != 0; i--) { + *p++ = 0; + } + sin = rsin(angle); + cos = rcos(angle); + m->m[1][0] = -sin; + m->m[0][0] = cos; + m->m[1][1] = cos; + m->m[0][1] = sin; + m->m[2][2] = ONE; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A5CEC); +/** @brief Build the Z, X then Y rotation of @p angles in @p out. */ +static void func_801A5E84(SVECTOR *angles, MATRIX *out) { + MATRIX rz; + MATRIX rx; + MATRIX ry; + MATRIX zx; + + func_801A5E08(angles->vz, &rz); + func_801A5D10(angles->vx, &rx); + MulMatrix0(&rz, &rx, &zx); + func_801A5D8C(angles->vy, &ry); + MulMatrix0(&zx, &ry, out); +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A5D10); +/** + * @brief Clear @p size bytes of pose state at @p out and seed it from @p model. + * + * Every part of the model takes a slot sized by its flags; the three-word + * scale slot starts at 1.0 rather than zero. + */ +static void func_801A5F14(EffectPoseModel *model, s32 *out, s32 size) { + EffectPoseParts *parts; + EffectPosePart *part; + s32 *offset; + s32 *slot; + s32 count; + s32 flags; + s32 i; + + func_801A5CEC(out, 0, size >> 2); + out[0] = (s32)model; + slot = out + 2; + parts = (EffectPoseParts *)((u8 *)model + model->partsOffset); + count = parts->count; + offset = parts->offsets; + for (i = 0; i < count; i++) { + part = (EffectPosePart *)((u8 *)parts + *offset++); + flags = part->flags; + if (flags & EFFECT_POSE_ACC0) { + slot += 9; + } else if (flags & EFFECT_POSE_VEL0) { + slot += 6; + } else if (flags & EFFECT_POSE_KEY0) { + slot += 3; + } + if (flags & EFFECT_POSE_ACC1) { + slot += 9; + } else if (flags & EFFECT_POSE_VEL1) { + slot += 6; + } else if (flags & EFFECT_POSE_KEY1) { + slot += 3; + } + if (flags & (EFFECT_POSE_SCALE_KEY | EFFECT_POSE_SCALE_VEL | + EFFECT_POSE_SCALE_ACC)) { + slot[2] = ONE << 16; + slot[1] = ONE << 16; + slot[0] = ONE << 16; + } + if (flags & EFFECT_POSE_SCALE_ACC) { + slot += 9; + } else if (flags & EFFECT_POSE_SCALE_VEL) { + slot += 6; + } else if (flags & EFFECT_POSE_SCALE_KEY) { + slot += 3; + } + if (flags & EFFECT_POSE_ACC3) { + slot += 6; + } else if (flags & EFFECT_POSE_VEL3) { + slot += 4; + } else if (flags & EFFECT_POSE_KEY3) { + slot += 2; + } + if (flags & EFFECT_POSE_DEPTH) { + slot += 3; + } + if (flags & EFFECT_POSE_ANIM) { + slot += 4; + } + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A5D8C); +/** + * @brief Look @p key up in a table of 12-byte entries and copy its three + * halfwords out. + * + * The entries are ordered by key and closed by -1; nothing is written when the + * key is missing. + */ +static void func_801A6094(s32 key, s32 *table, s16 *out) { + s32 k = *table; + + while (k < key) { + if (-1 == k) { + return; + } + table += 3; + k = *table; + } + if (k == key) { + table++; + *out++ = ((s16 *)table)[0]; + *out++ = ((s16 *)table)[1]; + *out = ((s16 *)table)[2]; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A5E08); +/** @brief As @ref func_801A6094, for 16-byte entries holding three words. */ +static void func_801A60FC(s32 key, s32 *table, s32 *out) { + s32 k = *table; + + while (k < key) { + if (-1 == k) { + return; + } + table += 4; + k = *table; + } + if (k == key) { + table++; + *out++ = *table++; + *out = table[0]; + out[1] = table[1]; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A5E84); +/** + * @brief Step one channel of a pose and hand its current value to @p out. + * + * @param flags Bit 0 keyed, bit 1 velocity, bit 2 acceleration. + * @param base The keyed tables are at byte offsets from here. + * @param cursorPtr Walks the channel's table offsets; advanced past the ones read. + * @param slot The channel's state; position, then velocity, then acceleration. + * @param key Frame the keyed tables are looked up at. + * @param out Receives the channel's three components. + * @return The state past this channel. + * + * @note The gotos are the shape the original had: each arm falls into the next + * one's body, so the move and emit blocks exist once. Written as an + * if/else-if chain the compiler copies them instead (143 instructions + * against 124); written as three independent tests it reaches the emit + * block through a mask of 6 and 7 rather than 2 and 1. + */ +static s32 *func_801A6164(s32 flags, u8 *base, s32 **cursorPtr, s32 *slot, s32 key, + s16 *out) { + s32 *cursor = *cursorPtr; + s32 *vel; + s32 *acc; + s16 *p; + s32 v; + + if (flags & EFFECT_POSE_ACC0) { + vel = slot + 3; + acc = slot + 6; + vel[0] += acc[0]; + vel[1] += acc[1]; + vel[2] += acc[2]; + goto move; + } + if (flags & EFFECT_POSE_VEL0) { + vel = slot + 3; +move: + slot[0] += vel[0]; + slot[1] += vel[1]; + slot[2] += vel[2]; + p = (s16 *)slot; + goto emit; + } + if (flags & EFFECT_POSE_KEY0) { + p = (s16 *)slot; +emit: + v = p[1]; + out[0] = v; + v = p[3]; + out[1] = v; + v = p[5]; + out[2] = v; + } + if (flags & EFFECT_POSE_KEY0) { + func_801A60FC(key, (s32 *)(base + *cursor++), slot); + v = ((s16 *)slot)[1]; + slot++; + out[0] = v; + v = ((s16 *)slot)[1]; + slot++; + out[1] = v; + v = ((s16 *)slot)[1]; + slot++; + out[2] = v; + } else if (flags & (EFFECT_POSE_VEL0 | EFFECT_POSE_ACC0)) { + slot += 3; + } + if (flags & EFFECT_POSE_VEL0) { + func_801A60FC(key, (s32 *)(base + *cursor++), slot); + slot += 3; + } else if (flags & EFFECT_POSE_ACC0) { + slot += 3; + } + if (flags & EFFECT_POSE_ACC0) { + func_801A60FC(key, (s32 *)(base + *cursor++), slot); + slot += 3; + } + *cursorPtr = cursor; + return slot; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A5F14); +/** + * @brief Walk one pose script and hand every part it poses to @p fn. + * + * The script opens with a part count and a frame count, then one byte offset + * per part. A part entry names the model part it drives and the channels it + * carries, and is followed by one keyed table per channel the flags claim. + * @p pose holds the model, the frame the script is on, and then the running + * state of every channel of every part -- the layout @ref func_801A5FA4 sizes. + * That state is one array of words, but a channel holds either words or pairs + * of halfwords, which is what every cast of @c slot below is reading. + * + * A channel that carries an acceleration steps its velocity by it, and one + * that carries a velocity steps its value; the keyed table is read after that, + * and overwrites the state only on a frame it holds. Channels the flags leave + * out keep whatever the zeroed pose gives them, scale excepted -- that starts + * at 1.0. + * + * @param pose The model and the frame the script is on. + * @param fn Called once per part, with the pose the script left. + * @param req Draw state handed straight to @p fn. + * @return The frames left after this one; 0 once the script has run out. + * + * @note The gotos are the shape the original had. The colour channel's three + * arms fall into each other -- acceleration into the velocity ramp, and + * that into the keyed arm's colour store -- so each body exists once; + * spelled as an if/else-if chain the compiler copies them instead, the + * same way it does in @ref func_801A6164. The two out of the table walks + * land on the code that keeps the stepped value: reaching it with a + * @c break costs an instruction, because the frame comparison the walk + * falls out to is then on the path. + */ +static s32 func_801A6354(s32 *pose, EffectPartFn fn, EffectDrawRequest *req) { + EffectPartPose part; + s32 *cursor; + s32 *script; + s32 *offsets; + s32 *depthTab; + u8 *entry; + s32 *slot; + u16 *vel; + u16 *rgb; + u16 frame; + u16 *acc; + s32 count; + s32 frames; + u32 flags; + s32 depthKey; + s32 i; + s32 v; + s32 *animTab; + s32 animKey; + s32 value; + s32 limit; + + script = (s32 *)((u8 *)pose[0] + ((s32 *)pose[0])[1]); + frames = script[1]; + if (pose[1] >= frames) { + return 0; + } + offsets = script + 2; + slot = pose + 2; + count = script[0]; + for (i = 0; i < count; i++) { + func_801A5CEC((s32 *)&part, 0, sizeof(part) / sizeof(s32)); + entry = (u8 *)script + *offsets++; + part.index = i; + part.scale[2] = ONE; + part.scale[1] = ONE; + part.scale[0] = ONE; + cursor = (s32 *)entry; + part.unk004 = *cursor++; + part.mesh = (u8)part.unk004; + flags = *cursor++; + slot = func_801A6164(flags, entry, &cursor, slot, pose[1], part.pos); + slot = func_801A6164(flags >> 3, entry, &cursor, slot, pose[1], part.rot); + slot = func_801A6164(flags >> 6, entry, &cursor, slot, pose[1], part.scale); + if (flags & EFFECT_POSE_ACC3) { + vel = (u16 *)slot + 4; + acc = (u16 *)slot + 8; + vel[0] += acc[0]; + vel[1] += acc[1]; + vel[2] += acc[2]; + goto ramp; + } + if (flags & EFFECT_POSE_VEL3) { + vel = (u16 *)slot + 4; +ramp: + rgb = (u16 *)slot; + v = rgb[0] + (s16)vel[0]; + if (v < 0) { + v = 0; + } else if (v > 0xFFFF) { + v = 0xFFFF; + } + rgb[0] = v; + v = rgb[1] + (s16)vel[1]; + if (v < 0) { + v = 0; + } else if (v > 0xFFFF) { + v = 0xFFFF; + } + rgb[1] = v; + v = rgb[2] + (s16)vel[2]; + if (v < 0) { + v = 0; + } else if (v > 0xFFFF) { + v = 0xFFFF; + } + rgb[2] = v; + goto emit; + } + if (flags & EFFECT_POSE_KEY3) { + rgb = (u16 *)slot; +emit: + part.colour.r = rgb[0] >> 8; + part.colour.g = rgb[1] >> 8; + part.colour.b = rgb[2] >> 8; + part.colour.cd = 0; + } + if (flags & EFFECT_POSE_KEY3) { + func_801A6094(pose[1], (s32 *)(entry + *cursor++), (s16 *)slot); + part.colour.r = *(u16 *)slot >> 8; + slot = (s32 *)((u16 *)slot + 1); + part.colour.g = *(u16 *)slot >> 8; + slot = (s32 *)((u16 *)slot + 1); + part.colour.b = *(u16 *)slot >> 8; + part.colour.cd = 0; + slot++; + } else if (flags & (EFFECT_POSE_VEL3 | EFFECT_POSE_ACC3)) { + slot += 2; + } + if (flags & EFFECT_POSE_VEL3) { + func_801A6094(pose[1], (s32 *)(entry + *cursor++), (s16 *)slot); + slot += 2; + } else if (flags & EFFECT_POSE_ACC3) { + slot += 2; + } + if (flags & EFFECT_POSE_ACC3) { + func_801A6094(pose[1], (s32 *)(entry + *cursor++), (s16 *)slot); + slot += 2; + } + if (flags & EFFECT_POSE_DEPTH) { + slot[1] += slot[2]; + slot[0] += slot[1]; + if (slot[0] < 0) { + slot[0] = 0; + } else { + limit = EFFECT_POSE_RAMP_MAX; + if (slot[0] > limit) { + slot[0] = limit; + } + } + depthTab = (s32 *)(entry + *cursor++); + depthKey = *depthTab; + while (depthKey < pose[1]) { + if (depthKey == -1) { + goto noDepthKey; + } + depthTab += 4; + depthKey = *depthTab; + } + if (depthKey == pose[1]) { + depthTab++; + value = *depthTab++; + *slot++ = value; + part.depth = value >> 16; + *slot++ = *depthTab; + *slot++ = depthTab[1]; + } else { +noDepthKey: + value = ((s16 *)slot)[1]; + part.depth = value; + slot += 3; + } + } + if (flags & EFFECT_POSE_ANIM) { + slot[2] += slot[3]; + slot[1] += slot[2]; + if (slot[1] < 0) { + slot[1] = 0; + } else { + limit = EFFECT_POSE_RAMP_MAX; + if (slot[1] > limit) { + slot[1] = limit; + } + } + animTab = (s32 *)(entry + *cursor++); + animKey = *animTab; + while (animKey < pose[1]) { + if (animKey == -1) { + goto noAnimKey; + } + animTab += 5; + animKey = *animTab; + } + if (animKey == pose[1]) { + animTab++; + frame = *(u16 *)animTab; + *(u16 *)slot = frame; + part.frameA = frame; + slot = (s32 *)((u16 *)slot + 1); + animTab = (s32 *)((u16 *)animTab + 1); + frame = *(u16 *)animTab; + *(u16 *)slot = frame; + part.frameB = frame; + slot = (s32 *)((u16 *)slot + 1); + animTab = (s32 *)((u16 *)animTab + 1); + value = *animTab++; + *slot++ = value; + part.weight = value >> 16; + *slot++ = *animTab; + *slot++ = animTab[1]; + } else { +noAnimKey: + part.frameA = *(u16 *)slot; + slot = (s32 *)((u16 *)slot + 1); + part.frameB = *(u16 *)slot; + slot = (s32 *)((u16 *)slot + 1); + value = ((s16 *)slot)[1]; + part.weight = value; + slot += 3; + } + } + fn(pose, &part, req); + } + pose[1]++; + return frames - pose[1]; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A6094); +/** + * @brief Interpolate every point of @p anim between two of its frames. + * + * @param frameA First frame; 0 selects the rest pose. + * @param frameB Second frame. + * @param t Weight of @p frameB, 0x1000 being all of it. + * @param out Receives @c anim->count points. + */ +static void func_801A6910(EffectVertexAnim *anim, s32 frameA, s32 frameB, s32 t, + SVECTOR *out) { + SVECTOR *a; + SVECTOR *b; + s32 n; + s32 s = ONE - t; + + if (frameA == 0) { + a = anim->frames; + } else { + a = &anim->frames[frameA * anim->count]; + } + if (frameB == 0) { + b = anim->frames; + } else { + b = &anim->frames[frameB * anim->count]; + } + n = anim->count; + while (--n != -1) { + gte_lddp(s); + gte_ldsv(a); + gte_gpf1(); + gte_lddp(t); + gte_ldsv(b); + gte_gpl1(); + gte_stsv(out); + out++; + a++; + b++; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A60FC); +/* + * battle.bin's own header spells this with its view of the 0x58-byte packet + * and keeps the list head in a word; the overlay passes its own view and a + * pointer, so the prototype it compiles against stays file-local. + */ +extern void *func_800CBC68(EffectPrimBuild *prim, u32 *ot, s32 mode, void *head); -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A6164); +/** + * @brief Build and submit one posed part's primitive packet. + * + * Skips the part outright when it has no scale, or when it is fully faded and + * carries no colour. Otherwise it takes a packet off the scratchpad, points it + * at the part's vertex animation and at the frame the pose asks for -- one of + * the two keyed frames, or the interpolated buffer when the pose sits between + * them -- builds the part's matrix, and hands the packet to the battle + * particle system. + * + * @param pose The pose script and its current frame. + * @param part The part as the pose walk left it. + * @param req Draw state: the parent matrix, the per-part colours and flags. + */ +static void func_801A6A00(s32 *pose, EffectPartPose *part, + EffectDrawRequest *req) { + EffectPrimBuild *prim; + EffectVertexAnim *anim; + MATRIX m; + VECTOR scale; + u32 partFlags; + s32 depth; + void *head; + + /* The two scale halves and the four colour bytes are tested a word at a + time; no single field type spells that. */ + if ((*(u32 *)&part->scale[0] | part->scale[2]) == 0) { + return; + } + if (part->depth >= ONE && *(u32 *)&part->colour == 0) { + return; + } + prim = func_800B3698(0x58); + anim = (EffectVertexAnim *)((u8 *)pose[0] + ((s32 *)pose[0])[part->mesh + 2]); + prim->stream = (s32 *)anim; + if (part->frameA == part->frameB) { + if (part->frameB == 0) { + anim = (EffectVertexAnim *)anim->frames; + } else { + anim = (EffectVertexAnim *)&anim->frames[part->frameB * anim->count]; + } + prim->verts = (u32 *)anim; + } else if (part->weight == 0) { + if (part->frameA == 0) { + anim = (EffectVertexAnim *)anim->frames; + } else { + anim = (EffectVertexAnim *)&anim->frames[part->frameA * anim->count]; + } + prim->verts = (u32 *)anim; + } else if (part->weight == ONE) { + if (part->frameB == 0) { + anim = (EffectVertexAnim *)anim->frames; + } else { + anim = (EffectVertexAnim *)&anim->frames[part->frameB * anim->count]; + } + prim->verts = (u32 *)anim; + } else { + func_801A6910((EffectVertexAnim *)prim->stream, part->frameA, part->frameB, + part->weight, req->unk08); + prim->verts = (u32 *)req->unk08; + } + RotMatrixYXZ((SVECTOR *)part->rot, &m); + m.t[0] = part->pos[0]; + m.t[1] = part->pos[1]; + m.t[2] = part->pos[2]; + partFlags = req->colours[part->index]; + if (partFlags & EFFECT_PART_WORLD_MATRIX) { + ApplyMatrixLV(req->m, (VECTOR *)&m.t[0], (VECTOR *)&m.t[0]); + m.t[0] += req->m->t[0]; + m.t[1] += req->m->t[1]; + m.t[2] += req->m->t[2]; + } else { + CompMatrix(req->m, &m, &m); + } + if (*(u32 *)&part->scale[0] != ((ONE << 16) | ONE) || part->scale[2] != ONE) { + scale.vx = part->scale[0]; + scale.vy = part->scale[1]; + scale.vz = part->scale[2]; + ScaleMatrix(&m, &scale); + } + SetRotMatrix(&m); + SetTransMatrix(&m); + prim->flags = (u16)req->colours[part->index] | EFFECT_EMIT_UNK2000; + depth = part->depth; + prim->depth = depth; + if (req->colours[part->index] & EFFECT_PART_DEPTH_RAMP) { + prim->depth = depth + (ONE - depth) * req->unk010 / 4096; + } + if (prim->depth != 0) { + prim->flags |= EFFECT_EMIT_DEPTH_CUE | EFFECT_EMIT_G_DEPTH_CUE; + *(u32 *)&prim->r = *(u32 *)&part->colour; + } + if (req->colours[part->index] & EFFECT_PART_SCROLL_UV) { + func_801A4D9C(prim->stream, ((s16 *)req->unk00C)[part->index]); + } + head = D_801D3EB8; + D_801D3EB8 = func_800CBC68(prim, D_800FA5E8->ot, 2, head); + func_800B36B8(0x58); +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A6354); +/** @brief Opcode handler: load the 0x34-point aim set. */ +static void func_801A6D68(EffectEntity *entity) { + func_801A5F14(&D_801D20B8, (s32 *)entity->unk060, 0x34); + entity->pc++; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A6910); +/** @brief Opcode handler: pose the model at the entity and draw it. */ +static void func_801A6DB0(EffectEntity *entity) { + EffectDrawRequest req; + MATRIX m; + u32 colours[1]; + s32 i; + + for (i = 0; i <= 0; i++) { + colours[i] = 0x30; + } + func_801A24B0(entity, &entity->pos); + RotMatrixYXZ((SVECTOR *)&entity->unk050, &m); + req.m = &m; + m.t[0] = entity->pos.vx; + m.t[1] = entity->pos.vy; + m.t[2] = entity->pos.vz; + ScaleMatrix(&m, (VECTOR *)&entity->unk030); + CompMatrix(&D_800F02C8, &m, &m); + req.colours = colours; + req.unk08 = &D_801D3EA4; + if (func_801A6354((s32 *)entity->unk060, func_801A6A00, &req) == 0) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A6A00); +/** @brief Opcode handler: no-op. */ +static void func_801A6E9C(EffectEntity *entity) { +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A6D68); +/** @brief Script dispatcher: the orbiting script, whose two angles wrap. */ +static s32 func_801A6EA4(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A6D68, func_801A6DB0, func_801A6E9C }; + + handlers[entity->pc](entity); + entity->unk050 += entity->unk058; + entity->unk054 += entity->unk05C; + entity->unk024++; + entity->unk050 &= 0xFFF; + entity->unk054 &= 0xFFF; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A2198(entity); + return 2; + } + return 0; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A6DB0); +/** @brief Opcode handler: reset the scale and load the 0x64-point aim set. */ +static void func_801A6F7C(EffectEntity *entity) { + ((VECTOR *)&entity->unk030)->vx = ONE; + ((VECTOR *)&entity->unk030)->vy = ONE; + ((VECTOR *)&entity->unk030)->vz = ONE; + func_801A5F14(&D_801D238C, (s32 *)entity->unk060, 0x64); + entity->pc++; +} -void func_801A6E9C(void) { +/** @brief Opcode handler: draw the model upright in the mode @c unk0C4 names. */ +static void func_801A6FD4(EffectEntity *entity) { + MATRIX m; + EffectDrawRequest req; + u32 colours[1]; + s32 i; + + for (i = 0; i <= 0; i++) { + colours[i] = 0x40000030; + } + req.unk010 = entity->unk0C4; + func_801A2480(entity, &entity->pos); + func_801A0568(&m); + req.m = &m; + m.t[0] = entity->pos.vx; + m.t[1] = entity->pos.vy; + m.t[2] = entity->pos.vz; + ScaleMatrix(&m, (VECTOR *)&entity->unk030); + CompMatrix(&D_800F02C8, &m, &m); + req.colours = colours; + req.unk08 = &D_801D3EA4; + if (func_801A6354((s32 *)entity->unk060, func_801A6A00, &req) == 0) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } } -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A6EA4); +/** @brief Opcode handler: no-op. */ +static void func_801A70C8(EffectEntity *entity) { +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A6F7C); +/** + * @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_801A70D0(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A6F7C, func_801A6FD4, func_801A70C8 }; + + handlers[entity->pc](entity); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A2198(entity); + return 2; + } + return 0; +} + +/** @brief Opcode handler: clear the screen tint. */ +static void func_801A7170(EffectEntity *entity) { + BattleTint *tint = D_800EF738; + s32 i; + + entity->pos.vx = 0; + for (i = 0; i < 4; i++) { + tint->level = 0; + tint->b = 0; + tint->g = 0; + tint->r = 0; + tint++; + } + entity->pc++; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A6FD4); +/** @brief Opcode handler: fade the screen tint up to full. */ +static void func_801A71B8(EffectEntity *entity) { + BattleTint *tint = D_800EF738; + s32 i; + + entity->pos.vx += 0xC0; + if (entity->pos.vx >= 0x600) { + entity->pos.vx = 0x600; + entity->pc++; + } + for (i = 3; i >= 0; i--) { + tint->level = entity->pos.vx; + tint++; + } +} -void func_801A70C8(void) { +/** @brief Opcode handler: drop back to the ground once the count passes 20. */ +static void func_801A7214(EffectEntity *entity) { + if (entity->unk024 >= 0x14) { + entity->pos.vy = 0; + entity->pc++; + } } -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A70D0); +/** @brief Opcode handler: fade the screen tint back out, then stop. */ +static void func_801A7240(EffectEntity *entity) { + BattleTint *tint = D_800EF738; + s32 i; + + entity->pos.vx -= 0xC0; + 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++; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7170); +/** @brief Opcode handler: no-op. */ +static void func_801A729C(EffectEntity *entity) { +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A71B8); +/** @brief Script dispatcher: the five-step screen-tint script. */ +static s32 func_801A72A4(EffectEntity *entity) { + EffectHandler handlers[5] = { func_801A7170, func_801A71B8, func_801A7214, + func_801A7240, func_801A729C }; + + handlers[entity->pc](entity); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A2198(entity); + return 2; + } + return 0; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7214); +/** @brief Opcode handler: move the entity onto the model's second point. */ +static void func_801A7354(EffectEntity *entity) { + func_801A24B0(entity, &entity->pos); + entity->pc++; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7240); +/** @brief Opcode handler: spawn the frame's share of orbiting children. */ +static void func_801A7390(EffectEntity *entity) { + EffectEntity *child; + s32 i; + + for (i = 0; i < D_801D3DF0[entity->unk024]; i++) { + child = func_801A21C0(&D_801D7314, func_801A6EA4, 0xC8, entity); + ((VECTOR *)&child->unk030)->vx = ONE; + ((VECTOR *)&child->unk030)->vy = ONE; + ((VECTOR *)&child->unk030)->vz = ONE; + child->unk050 = rand() & 0xFFF; + child->unk054 = rand() & 0xFFF; + child->unk058 = (rand() & 0x3F) + 0x20; + child->unk05C = (rand() & 0x3F) + 0x20; + if (rand() & 1) { + child->unk058 = ONE - child->unk058; + } + if (rand() & 1) { + child->unk05C = ONE - child->unk05C; + } + } + if (entity->unk024 >= 0x12) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} -void func_801A729C(void) { +/** @brief Opcode handler: no-op. */ +static void func_801A74F8(EffectEntity *entity) { } -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A72A4); +/** + * @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_801A7500(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A7354, func_801A7390, func_801A74F8 }; + + handlers[entity->pc](entity); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A2198(entity); + return 2; + } + return 0; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7354); +/** @brief Opcode handler: sit on the model's first point and aim at the ground. */ +static void func_801A75A0(EffectEntity *entity) { + s16 y; -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7390); + func_801A2480(entity, &entity->pos); + y = func_801A2B10(entity); + entity->pos.vy = y; + entity->unk040.vy = (0x100 - y) / 18; + entity->pc++; +} -void func_801A74F8(void) { +/** @brief Opcode handler: rise, spawning the frame's share of falling motes. */ +static void func_801A7614(EffectEntity *entity) { + EffectEntity *spark; + s32 i; + + entity->pos.vy += entity->unk040.vy; + for (i = 0; i < D_801D3E2C[entity->unk024]; i++) { + spark = func_801A21C0(&D_801E74F4, func_801A54DC, 0x84, entity); + func_801A0A64(spark, 0x200); + spark->pos.vy = entity->pos.vy; + ((VECTOR *)&spark->unk030)->vx = ONE; + ((VECTOR *)&spark->unk030)->vy = ONE; + ((VECTOR *)&spark->unk030)->vz = ONE; + } + if (entity->unk024 >= 0x12) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } } -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7500); +/** @brief Opcode handler: no-op. */ +static void func_801A7728(EffectEntity *entity) { +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A75A0); +/** + * @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_801A7730(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A75A0, func_801A7614, func_801A7728 }; + + handlers[entity->pc](entity); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A2198(entity); + return 2; + } + return 0; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7614); +/** @brief As @ref func_801A75A0, over twenty frames rather than eighteen. */ +static void func_801A77D0(EffectEntity *entity) { + s16 y; -void func_801A7728(void) { + func_801A2480(entity, &entity->pos); + y = func_801A2B10(entity); + entity->pos.vy = y; + entity->unk040.vy = (0x100 - y) / 20; + entity->pc++; } -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7730); +/** @brief Opcode handler: rise, spawning the frame's share of rising motes. */ +static void func_801A7844(EffectEntity *entity) { + EffectEntity *spark; + s32 i; + + entity->pos.vy += entity->unk040.vy; + for (i = 0; i < D_801D3E68[entity->unk024]; i++) { + spark = func_801A21C0(&D_801E74F4, func_801A533C, 0x84, entity); + spark->pos.vy = entity->pos.vy - 0x180; + func_801A0A64(spark, 0x200); + } + if (entity->unk024 >= 0x14) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A77D0); +/** @brief Opcode handler: no-op. */ +static void func_801A7940(EffectEntity *entity) { +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7844); +/** + * @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_801A7948(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A77D0, func_801A7844, func_801A7940 }; + + handlers[entity->pc](entity); + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A2198(entity); + return 2; + } + return 0; +} -void func_801A7940(void) { +/** @brief Opcode handler: take the model over and set it up to be drawn dim. */ +static void func_801A79E8(EffectEntity *entity) { + BattleEffectSlot *slot = &D_800EF2D0[entity->unk02D]; + EffectRender *render = &D_801ED304; + s16 y; + + func_801A2480(entity, &entity->pos); + y = entity->pos.vy - 0x600; + entity->pos.vy = y; + entity->unk040.vy = (0x100 - y) / 18; + func_801A1088(render, &D_801ED2F4, &D_801E7504, &D_801E7534, + &D_800EF2D0[entity->unk02D], entity->unk02D); + render->r = 0x40; + render->g = 0x40; + render->b = 0x40; + entity->flags |= EFFECT_FLAG_UNK08; + slot->flags |= BATTLE_SLOT_FLAG_UNK04; + entity->pc++; } -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7948); +/** @brief Opcode handler: fall at the armed speed, then release the model. */ +static void func_801A7B1C(EffectEntity *entity) { + BattleEffectSlot *slot; + EffectModel *model; + + entity->pos.vy += entity->unk040.vy; + if (entity->unk024 >= 0x12) { + slot = &D_800EF2D0[entity->unk02D]; + model = entity->unk010; + entity->flags &= ~EFFECT_FLAG_UNK08; + slot->flags &= ~BATTLE_SLOT_FLAG_UNK04; + model->unk063 = 0; + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A79E8); +/** @brief Opcode handler: no-op. */ +static void func_801A7BA0(EffectEntity *entity) { +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7B1C); +/** @brief Script dispatcher: the model-taking script, redrawn each frame. */ +static s32 func_801A7BA8(EffectEntity *entity) { + EffectHandler handlers[3] = { func_801A79E8, func_801A7B1C, func_801A7BA0 }; + EffectRender *render; + /* Occupies sp+0x30: the original reserved this slot and never read it, but + dropping it moves every local below and the frame no longer matches. */ + SVECTOR unused; + SVECTOR point; + + handlers[entity->pc](entity); + if (entity->flags & EFFECT_FLAG_UNK08) { + render = &D_801ED304; + func_801A2480(entity, &point); + entity->unk052 = func_801A0A34(&D_800F02C8); + entity->pos.vx = point.vx + rsin(entity->unk052) / 4; + entity->pos.vz = point.vz + rcos(entity->unk052) / 4; + render->unk0B4.vy = entity->pos.vy; + func_801A1144(render, &entity->pos, 0x280, 0x1C0, 0x140, 0xF4, 0x80, 0x40); + func_801A205C(render); + } + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A2198(entity); + return 2; + } + return 0; +} -void func_801A7BA0(void) { +/** @brief Opcode handler: start the five child scripts the effect runs. */ +static void func_801A7D18(EffectEntity *entity) { + EffectEntity *child; + + func_801A21C0(&D_801E74F4, func_801A5C24, 0x84, entity); + child = func_801A21C0(&D_801D7314, func_801A70D0, 0xC8, entity); + child->unk0C4 = 0; + func_801A21C0(&D_801D7314, func_801A7500, 0xC8, entity); + func_801A21C0(&D_801D7314, func_801A7730, 0xC8, entity); + func_801A21C0(&D_801D7314, func_801A72A4, 0xC8, entity); + entity->pc++; } -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7BA8); +/** @brief Opcode handler: start the two child scripts this step drives. */ +static void func_801A7DDC(EffectEntity *entity) { + EffectEntity *child = func_801A21C0(&D_801D7314, func_801A70D0, 0xC8, entity); -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7D18); + child->unk0C4 = 0x800; + func_801A21C0(&D_801D7314, func_801A7BA8, 0xC8, entity); + entity->pc++; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7DDC); +/** @brief Opcode handler: start a child script in mode @c 0xC00. */ +static void func_801A7E58(EffectEntity *entity) { + EffectEntity *child = func_801A21C0(&D_801D7314, func_801A70D0, 0xC8, entity); -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7E58); + child->unk0C4 = 0xC00; + entity->pc++; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7EB0); +/** @brief Opcode handler: start a child script in mode @c 0xE00. */ +static void func_801A7EB0(EffectEntity *entity) { + EffectEntity *child = func_801A21C0(&D_801D7314, func_801A70D0, 0xC8, entity); -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7F08); + child->unk0C4 = 0xE00; + entity->pc++; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7F68); +/** @brief Opcode handler: start the third child script once the count passes 12. */ +static void func_801A7F08(EffectEntity *entity) { + if (entity->unk024 >= 0xC) { + func_801A21C0(&D_801D7314, func_801A7948, 0xC8, entity); + entity->pc++; + } +} -void func_801A7FE0(void) { +/** @brief Opcode handler: hand the animation's part list to the battle renderer. */ +static void func_801A7F68(EffectEntity *entity) { + if (entity->unk024 >= 0x1F) { + func_800BFE1C(entity->animSet->anims[entity->unk02A].parts); + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } } -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A7FE8); +/** @brief Opcode handler: no-op. */ +static void func_801A7FE0(EffectEntity *entity) { +} + +/** @brief Script dispatcher: the seven-step main script, which starts the sound. */ +static s32 func_801A7FE8(EffectEntity *entity) { + EffectHandler handlers[7] = { func_801A7D18, func_801A7DDC, func_801A7E58, + func_801A7EB0, func_801A7F08, func_801A7F68, + func_801A7FE0 }; + + func_801A234C(entity); + func_801A26DC(entity); + handlers[entity->pc](entity); + if (entity->unk024 == 0) { + func_800C4764(D_801A83D0, 0, 0x80); + } + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A2198(entity); + return 2; + } + return 0; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A80DC); +/** @brief Opcode handler: consume the opcode and do nothing else. */ +static void func_801A80DC(EffectEntity *entity) { + entity->pc++; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A80F0); +/** @brief Opcode handler: start a task running @ref func_801A7FE8. */ +static void func_801A80F0(EffectEntity *entity) { + entity->unk063 = 1; + func_801A21C0(&D_801D4104, func_801A7FE8, 0x58, entity); + entity->pc++; +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A8148); +/** @brief Opcode handler: loop back one step until the animation list runs out. */ +static void func_801A8148(EffectEntity *entity) { + if (entity->unk063 != 0) { + return; + } + if (entity->unk02A < entity->unk058) { + entity->unk02A++; + entity->pc--; + } else { + entity->pc++; + } +} -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A8198); +/** @brief Opcode handler: stop once @c unk05E reaches zero. */ +static void func_801A8198(EffectEntity *entity) { + if (entity->unk05E == 0) { + entity->flags |= EFFECT_FLAG_STOP; + entity->pc++; + } +} -void func_801A81C8(void) { +/** @brief Opcode handler: no-op. */ +static void func_801A81C8(EffectEntity *entity) { } -INCLUDE_ASM("asm/ovl/effect_025/nonmatchings/effect_025", func_801A81D0); +/** + * @brief Root script dispatcher: swap the prim bank, run one step, then run + * every child pool. + * + * @return 2 once the script has stopped and its children have drained. + */ +static s32 func_801A81D0(EffectEntity *entity) { + EffectHandler handlers[5] = { func_801A80DC, func_801A80F0, func_801A8148, + func_801A8198, func_801A81C8 }; + + if (entity->unk05C & 1) { + D_801D3EB8 = D_801D3EBC; + } else { + D_801D3EB8 = D_801D3EC0; + } + func_801A2510(entity); + handlers[entity->pc](entity); + entity->unk05E = func_800B2B68(&D_801D4104); + entity->unk05E += func_800B2B68(&D_801D7314); + entity->unk05E += func_800B2B68(&D_801E74F4); + entity->unk05C++; + entity->unk024++; + if (entity->flags & EFFECT_FLAG_STOP) { + if (entity->wait != 0) { + return 0; + } + func_801A2198(entity); + return 2; + } + return 0; +} diff --git a/tools/objdiff/objdiff_generate.py b/tools/objdiff/objdiff_generate.py index e54e82b6..d26d97df 100644 --- a/tools/objdiff/objdiff_generate.py +++ b/tools/objdiff/objdiff_generate.py @@ -44,7 +44,6 @@ {"id": "battle_render", "name": "battle_render.bin"}, {"id": "battle", "name": "battle.bin"}, {"id": "world", "name": "world.bin"}, - {"id": "effect_001", "name": "effect_001.bin"}, ] # Files/dirs to skip @@ -65,6 +64,19 @@ def target_path(expected_o, o_file): return str(o_file.relative_to(ROOT)) +def effect_categories(): + """A category per effect overlay the build produced. The splat config + carries all 343 of them but the Makefile's EFFECTS list decides which ones + compile, so ask the build tree rather than the config -- a category with no + objects behind it would report an empty binary.""" + names = [] + for parent in (BUILD, BUILD / "ovl"): + for ovl_dir in sorted(parent.glob("effect_*")): + if (ovl_dir / "src").is_dir() and ovl_dir.name not in names: + names.append(ovl_dir.name) + return [{"id": name, "name": f"{name}.bin"} for name in names] + + def find_units(): units = [] @@ -128,6 +140,7 @@ def main(): ap.add_argument("--expected", action="store_true", help="point targets at the objects `make expected` built") USE_EXPECTED = ap.parse_args().expected + CATEGORIES.extend(effect_categories()) units = find_units() config = { diff --git a/tools/progress.py b/tools/progress.py index bf50ffc6..c4da0870 100644 --- a/tools/progress.py +++ b/tools/progress.py @@ -3,6 +3,7 @@ import glob import os +import re import sys # Overlay names in display order, with file extensions @@ -11,10 +12,24 @@ "menuext", "menuitem", "menumgc", "menugf", "menujnc2", "menusav", "menucrd", "menututo", "menutmag", "menutips", "menutest", ] + + +def effect_overlays(): + """The effect overlays the Makefile compiles. The splat config carries all + 343 of them; EFFECTS names the ones with source worth counting. `all` only + ever arrives on the command line, which is not a README table.""" + root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + with open(os.path.join(root, "Makefile")) as f: + match = re.search(r"^EFFECTS \?= (.*)$", f.read(), re.M) + names = match.group(1).split() if match else [] + return [name for name in names if name != "all"] + + +EFFECT_OVERLAYS = effect_overlays() CODE_OVERLAYS = [ "field_init", "intro", "field", - "tripletriad", "battle_render", "battle", "world", "effect_001", -] + "tripletriad", "battle_render", "battle", "world", +] + EFFECT_OVERLAYS OVERLAY_EXT = {name: "ovl" for name in MENU_OVERLAYS} OVERLAY_EXT.update({name: "bin" for name in CODE_OVERLAYS}) @@ -27,7 +42,7 @@ "battle": ["src/battle/*.c"], "tripletriad": ["src/tripletriad/*.c"], "world": ["src/world/*.c"], - "effect_001": ["src/effect/effect_001.c"], + **{name: [f"src/effect/{name}.c"] for name in EFFECT_OVERLAYS}, **{name: [f"src/menu/{name}/*.c"] for name in MENU_OVERLAYS}, }