diff --git a/spec b/spec index 795a32387f..c7bd963239 100644 --- a/spec +++ b/spec @@ -2905,9 +2905,7 @@ beginseg name "ovl_Obj_Mine" compress include "build/src/overlays/actors/ovl_Obj_Mine/z_obj_mine.o" - include "build/data/ovl_Obj_Mine/ovl_Obj_Mine.data.o" - include "build/data/ovl_Obj_Mine/ovl_Obj_Mine.bss.o" - include "build/data/ovl_Obj_Mine/ovl_Obj_Mine.reloc.o" + include "build/src/overlays/actors/ovl_Obj_Mine/ovl_Obj_Mine_reloc.o" endseg beginseg diff --git a/src/overlays/actors/ovl_Obj_Mine/z_obj_mine.c b/src/overlays/actors/ovl_Obj_Mine/z_obj_mine.c index 36537cfe31..e5b17469c0 100644 --- a/src/overlays/actors/ovl_Obj_Mine/z_obj_mine.c +++ b/src/overlays/actors/ovl_Obj_Mine/z_obj_mine.c @@ -1,21 +1,50 @@ /* * File: z_obj_mine.c * Overlay: ovl_Obj_Mine - * Description: Spike metal Mine + * Description: Spike metal mine */ #include "z_obj_mine.h" +#include "overlays/actors/ovl_En_Bom/z_en_bom.h" +#include "objects/object_ny/object_ny.h" #define FLAGS 0x00000000 #define THIS ((ObjMine*)thisx) +#define LINK_SIZE 12.0f +#define ATTACH_OFFSET 10.0f +#define PATH_RADIUS 32.0f +#define AIR_RADIUS 20.0f +#define WATER_RADIUS 36.0f +#define BOMB_SPAWN_OFFSET 15.0f +#define WATER_KNOCKBACK 7.0f +#define AIR_KNOCKBACK 0.04f + void ObjMine_Init(Actor* thisx, PlayState* play); void ObjMine_Destroy(Actor* thisx, PlayState* play); -void ObjMine_Update(Actor* thisx, PlayState* play); -void ObjMine_Draw(Actor* thisx, PlayState* play); +void ObjMine_Path_Update(Actor* thisx, PlayState* play); +void ObjMine_Path_Draw(Actor* thisx, PlayState* play); + +void ObjMine_Path_SetupStationary(ObjMine* this); +void ObjMine_Path_Stationary(ObjMine* this, PlayState* play); +void ObjMine_Path_SetupMove(ObjMine* this); +void ObjMine_Path_Move(ObjMine* this, PlayState* play); +void ObjMine_Explode(ObjMine* this, PlayState* play); +void ObjMine_Air_SetupChained(ObjMine* this); +void ObjMine_Air_Chained(ObjMine* this, PlayState* play); +void ObjMine_Air_SetupStationary(ObjMine* this); +void ObjMine_Air_Stationary(ObjMine* this, PlayState* play); +void ObjMine_Water_SetupChained(ObjMine* this); +void ObjMine_Water_Chained(ObjMine* this, PlayState* play); +void ObjMine_Water_SetupStationary(ObjMine* this); +void ObjMine_Water_Stationary(ObjMine* this, PlayState* play); + +void ObjMine_AirWater_Update(Actor* thisx, PlayState* play); +void ObjMine_DrawExplosion(Actor* thisx, PlayState* play); +void ObjMine_Air_Draw(Actor* thisx, PlayState* play); +void ObjMine_Water_Draw(Actor* thisx, PlayState* play); -#if 0 ActorInit Obj_Mine_InitVars = { /**/ ACTOR_OBJ_MINE, /**/ ACTORCAT_PROP, @@ -24,131 +53,1183 @@ ActorInit Obj_Mine_InitVars = { /**/ sizeof(ObjMine), /**/ ObjMine_Init, /**/ ObjMine_Destroy, - /**/ ObjMine_Update, - /**/ ObjMine_Draw, + /**/ ObjMine_Path_Update, + /**/ ObjMine_Path_Draw, }; -// static ColliderJntSphElementInit sJntSphElementsInit[1] = { -static ColliderJntSphElementInit D_80A84570[1] = { +static ColliderJntSphElementInit sJntSphElementsInit[1] = { { - { ELEMTYPE_UNK2, { 0x00000000, 0x00, 0x00 }, { 0x01CBFFBE, 0x00, 0x00 }, TOUCH_NONE | TOUCH_SFX_NORMAL, BUMP_ON, OCELEM_ON, }, + { + ELEMTYPE_UNK2, + { 0x00000000, 0x00, 0x00 }, + { 0x01CBFFBE, 0x00, 0x00 }, + TOUCH_NONE | TOUCH_SFX_NORMAL, + BUMP_ON, + OCELEM_ON, + }, { 0, { { 0, 0, 0 }, 30 }, 100 }, }, }; -// static ColliderJntSphInit sJntSphInit = { -static ColliderJntSphInit D_80A84594 = { - { COLTYPE_METAL, AT_NONE, AC_ON | AC_TYPE_PLAYER, OC1_ON | OC1_TYPE_PLAYER | OC1_TYPE_1, OC2_TYPE_1, COLSHAPE_JNTSPH, }, - ARRAY_COUNT(sJntSphElementsInit), D_80A84570, // sJntSphElementsInit, +static ColliderJntSphInit sJntSphInit = { + { + COLTYPE_METAL, + AT_NONE, + AC_ON | AC_TYPE_PLAYER, + OC1_ON | OC1_TYPE_PLAYER | OC1_TYPE_1, + OC2_TYPE_1, + COLSHAPE_JNTSPH, + }, + ARRAY_COUNT(sJntSphElementsInit), + sJntSphElementsInit, }; -// static InitChainEntry sInitChain[] = { -static InitChainEntry D_80A845E8[] = { +static f32 sPathSpeeds[] = { + 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f, +}; + +static ObjMineMtxF3 sStandardBasis = { + { 1.0f, 0.0f, 0.0f }, + { 0.0f, 1.0f, 0.0f }, + { 0.0f, 0.0f, 1.0f }, +}; + +static InitChainEntry sInitChain[] = { ICHAIN_F32(uncullZoneForward, 1300, ICHAIN_CONTINUE), ICHAIN_F32(uncullZoneScale, 150, ICHAIN_CONTINUE), ICHAIN_F32(uncullZoneDownward, 100, ICHAIN_CONTINUE), ICHAIN_VEC3F_DIV1000(scale, 10, ICHAIN_STOP), }; -#endif +void ObjMine_Path_MoveToWaypoint(ObjMine* this, s32 index) { + Math_Vec3s_ToVec3f(&this->actor.world.pos, &this->waypoints[index]); +} -extern ColliderJntSphElementInit D_80A84570[1]; -extern ColliderJntSphInit D_80A84594; -extern InitChainEntry D_80A845E8[]; +s32 ObjMine_GetUnitVec3f(Vec3f* src, Vec3f* dst) { + f32 magnitude = Math3D_Vec3fMagnitude(src); -extern UNK_TYPE D_06000030; -extern UNK_TYPE D_06002068; + if (magnitude < 0.001f) { + return false; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A811D0.s") + dst->x = src->x * (1.0f / magnitude); + dst->y = src->y * (1.0f / magnitude); + dst->z = src->z * (1.0f / magnitude); + return true; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A8120C.s") +s32 ObjMine_GetUnitVec3fNorm(Vec3f* src, Vec3f* dst, f32* norm, f32* invNorm) { + f32 magnitude = Math3D_Vec3fMagnitude(src); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81288.s") + if (magnitude < 0.001f) { + return false; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A8131C.s") + dst->x = src->x * (1.0f / magnitude); + dst->y = src->y * (1.0f / magnitude); + dst->z = src->z * (1.0f / magnitude); + *norm = magnitude; + *invNorm = 1.0f / magnitude; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81384.s") + return true; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A8140C.s") +void ObjMine_Path_SpawnBomb(ObjMine* this, PlayState* play) { + EnBom* bomb = (EnBom*)Actor_Spawn(&play->actorCtx, play, ACTOR_EN_BOM, this->actor.world.pos.x, + this->actor.world.pos.y - BOMB_SPAWN_OFFSET, this->actor.world.pos.z, 0, 0, 0, + BOMB_TYPE_BODY); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A8146C.s") + if (bomb != NULL) { + bomb->timer = 0; + } +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81544.s") +void ObjMine_AirWater_SpawnBomb(ObjMine* this, PlayState* play) { + f32 x = this->collider.elements[0].dim.worldSphere.center.x; + f32 y = this->collider.elements[0].dim.worldSphere.center.y - BOMB_SPAWN_OFFSET; + f32 z = this->collider.elements[0].dim.worldSphere.center.z; + EnBom* bomb = (EnBom*)Actor_Spawn(&play->actorCtx, play, ACTOR_EN_BOM, x, y, z, 0, 0, 0, BOMB_TYPE_BODY); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81640.s") + if (bomb != NULL) { + bomb->timer = 0; + } +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A8164C.s") +s32 ObjMine_AirWater_CheckOC(ObjMine* this) { + if (this->collider.base.ocFlags2 & OC2_HIT_PLAYER) { + return true; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81684.s") + if (this->collider.base.ocFlags1 & OC1_HIT) { + Actor* hitActor = this->collider.base.oc; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81714.s") + if ((hitActor->id == ACTOR_OBJ_MINE) && (hitActor->room == this->actor.room)) { + return true; + } + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81818.s") + return false; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81868.s") +void ObjMine_Air_CheckAC(ObjMine* this, s16* hitAngle, s16* torqueAngle) { + s32 pad[2]; + Vec3s* centerPos3s = &this->collider.elements[0].dim.worldSphere.center; + Vec3f centerPos; + s16 yawToAttack; + Actor* attackActor = this->collider.base.ac; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A819A4.s") + Math_Vec3s_ToVec3f(¢erPos, centerPos3s); + yawToAttack = Math_Vec3f_Yaw(&attackActor->world.pos, ¢erPos); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81A00.s") + // dmgFlag check is (DMG_DEKU_BUBBLE | DMG_FIRE_ARROW | DMG_ICE_ARROW | DMG_FIRE_ARROW | DMG_NORMAL_ARROW) + if (this->collider.elements[0].info.acHitInfo->toucher.dmgFlags & 0x13820) { + *hitAngle = attackActor->shape.rot.y; + *torqueAngle = attackActor->shape.rot.y - yawToAttack; + } else { + Vec3f hitPos; + Vec3s* hitPos3s = &this->collider.elements[0].info.bumper.hitPos; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81AA4.s") + Math_Vec3s_ToVec3f(&hitPos, hitPos3s); + *hitAngle = Actor_WorldYawTowardActor(attackActor, &this->actor); + *torqueAngle = Math_Vec3f_Yaw(&attackActor->world.pos, &hitPos) - yawToAttack; + } +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81B14.s") +void ObjMine_Water_CheckAC(ObjMine* this, Vec3f* knockbackDir) { + Actor* attackActor = this->collider.base.ac; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81B7C.s") + // dmgFlag check is (DMG_DEKU_BUBBLE | DMG_LIGHT_ARROW | DMG_ICE_ARROW | DMG_FIRE_ARROW | DMG_NORMAL_ARROW) + if (this->collider.elements[0].info.acHitInfo->toucher.dmgFlags & 0x13820) { + Matrix_Push(); + Matrix_RotateYS(attackActor->shape.rot.y, MTXMODE_NEW); + Matrix_RotateXS(attackActor->shape.rot.x, MTXMODE_APPLY); + Matrix_MultVecZ(1.0f, knockbackDir); + Matrix_Pop(); + } else { + Vec3f posDiff; + Sphere16* sphere = &this->collider.elements[0].dim.worldSphere; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81D70.s") + posDiff.x = sphere->center.x - attackActor->world.pos.x; + posDiff.y = sphere->center.y - attackActor->world.pos.y; + posDiff.z = sphere->center.z - attackActor->world.pos.z; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81DEC.s") + if (!ObjMine_GetUnitVec3f(&posDiff, knockbackDir)) { + Math_Vec3f_Copy(knockbackDir, &sStandardBasis.y); + } + } +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81E7C.s") +void ObjMine_AirWater_Noop(ObjMine* this) { +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A81FFC.s") +void ObjMine_ReplaceTranslation(Vec3f* translation) { + MtxF* matrix = Matrix_GetCurrent(); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A828A8.s") + matrix->xw = translation->x; + matrix->yw = translation->y; + matrix->zw = translation->z; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A82C28.s") +void ObjMine_SetRotation(ObjMineMtxF3* basis) { + MtxF* matrix = Matrix_GetCurrent(); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/ObjMine_Init.s") + matrix->xx = basis->x.x; + matrix->yx = basis->x.y; + matrix->zx = basis->x.z; + matrix->wx = 0.0f; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/ObjMine_Destroy.s") + matrix->xy = basis->y.x; + matrix->yy = basis->y.y; + matrix->zy = basis->y.z; + matrix->wy = 0.0f; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A82F84.s") + matrix->xz = basis->z.x; + matrix->yz = basis->z.y; + matrix->zz = basis->z.z; + matrix->wz = 0.0f; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A82F98.s") + matrix->xw = 0.0f; + matrix->yw = 0.0f; + matrix->zw = 0.0f; + matrix->ww = 1.0f; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A82FA8.s") +s32 ObjMine_StepUntilParallel(Vec3f* value, Vec3f* target, f32 angleStep) { + Vec3f perpVec; + Vec3f prevValue; + Vec3f perpNormal; + f32 cosAngle = Math3D_Parallel(value, target); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A82FC8.s") + if (Math_CosF(angleStep) <= cosAngle) { + Math_Vec3f_Copy(value, target); + return true; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A83214.s") + Matrix_Push(); + Math_Vec3f_Copy(&prevValue, value); + Math3D_CrossProduct(value, target, &perpVec); + if (ObjMine_GetUnitVec3f(&perpVec, &perpNormal)) { + Matrix_RotateAxisS(RAD_TO_BINANG(angleStep), &perpNormal, MTXMODE_NEW); + Matrix_MultVec3f(&prevValue, value); + } else { + Matrix_RotateXFNew(angleStep); + Matrix_MultVec3f(&prevValue, value); + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A83258.s") + Matrix_Pop(); + return false; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A832BC.s") +void ObjMine_UpdateCollider(ObjMine* this) { + this->collider.elements[0].dim.worldSphere.center.x = this->actor.world.pos.x; + this->collider.elements[0].dim.worldSphere.center.y = this->actor.world.pos.y; + this->collider.elements[0].dim.worldSphere.center.z = this->actor.world.pos.z; +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A832D0.s") +void ObjMine_Air_InitChain(ObjMine* this, s32 linkCount) { + f32 linkCountF = linkCount; + ObjMineAirChain* airChain = &this->chain.air; + ObjMineAirLink* airLink; + s32 i; + f32 wallCheckRadius = this->actor.home.rot.z * 5.0f; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A83A74.s") + airChain->basis.x.x = 1.0f; + airChain->basis.y.y = 1.0f; + airChain->basis.z.z = 1.0f; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A83A88.s") + airChain->translation.y = 1.0f; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A83B14.s") + // Sets restoring force and drag. Longer chains have lower frequency and less drag. + if (linkCount > 0) { + airChain->restore = -sqrtf((0.124992f / OBJMINE_CHAIN_MAX) / linkCountF); // constant is close to 1/8 + airChain->drag = 0.95f + ((0.02000004f / OBJMINE_CHAIN_MAX) * linkCountF); // constant is close to 1/50 + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A83B28.s") + airChain->swayMax = 0.0002f; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A83CEC.s") + // Consecutive chain links are offset 90 degrees. + for (i = 0, airLink = airChain->links; i < linkCount; i++, airLink++) { + airLink->twist = 0x4000; + } -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A83D00.s") + if (wallCheckRadius < 0.0f) { + airChain->wallCheckDistSq = -1.0f; // Negative value means skip wall collision checks + } else if (wallCheckRadius <= AIR_RADIUS + 1.0f) { + airChain->wallCheckDistSq = 0.0f; + } else { + airChain->wallCheckDistSq = SQ(wallCheckRadius - AIR_RADIUS); + } +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/ObjMine_Update.s") +void ObjMine_Air_InitCollider(ObjMine* this, s32 linkCount) { + Matrix_Translate(this->actor.world.pos.x, this->actor.world.pos.y, this->actor.world.pos.z, MTXMODE_NEW); + Matrix_Scale(this->actor.scale.x, this->actor.scale.y, this->actor.scale.z, MTXMODE_APPLY); + Collider_UpdateSpheres(0, &this->collider); +} -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A83E7C.s") +void ObjMine_Air_SetBasis(ObjMine* this) { + ObjMineAirChain* airChain = &this->chain.air; + Vec3f tempVec; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/ObjMine_Draw.s") + tempVec.x = -airChain->displacement.x; + tempVec.y = 1.0f; + tempVec.z = -airChain->displacement.z; -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A83FBC.s") + ObjMine_GetUnitVec3f(&tempVec, &airChain->basis.y); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A84088.s") + Math3D_CrossProduct(&sStandardBasis.x, &airChain->basis.y, &tempVec); + ObjMine_GetUnitVec3f(&tempVec, &airChain->basis.z); -#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_Obj_Mine/func_80A84338.s") + Math3D_CrossProduct(&airChain->basis.y, &airChain->basis.z, &tempVec); + ObjMine_GetUnitVec3f(&tempVec, &airChain->basis.x); +} + +void ObjMine_Air_SetWorld(ObjMine* this) { + s32 linkCount = OBJMINE_GET_LINK_COUNT(&this->actor); + f32 chainLength = ATTACH_OFFSET + (linkCount * LINK_SIZE); + + this->actor.world.pos.x = (this->chain.air.basis.y.x * -chainLength) + this->actor.home.pos.x; + this->actor.world.pos.y = (this->chain.air.basis.y.y * -chainLength) + this->actor.home.pos.y; + this->actor.world.pos.z = (this->chain.air.basis.y.z * -chainLength) + this->actor.home.pos.z; +} + +void ObjMine_Air_SetChainXZ(ObjMine* this) { + s32 linkCount = OBJMINE_GET_LINK_COUNT(&this->actor); + f32 invLength = 1.0f / (ATTACH_OFFSET + (linkCount * LINK_SIZE)); + f32 dx = this->actor.world.pos.x - this->actor.home.pos.x; + f32 dz = this->actor.world.pos.z - this->actor.home.pos.z; + + this->chain.air.displacement.x = dx * invLength; + this->chain.air.displacement.z = dz * invLength; +} + +void ObjMine_Water_InitChain(ObjMine* this, s32 linkCount) { + ObjMineWaterChain* waterChain = &this->chain.water; + ObjMineWaterLink* waterLink; + s32 i; + f32 wallCheckRadius = this->actor.home.rot.z * 5.0f; + f32 linkY; + f32 swayVel; + + waterChain->drag = 0.9f; + waterChain->swayMax = 0.003f; + + waterChain->swayPhaseVel = Rand_Next() >> (0x20 - 13); + waterChain->restoreXZ = -0.0002f; + waterChain->restoreY = -0.0002f; + + swayVel = waterChain->swayMax * (LINK_SIZE / 2.0f); + linkY = this->actor.home.pos.y; + + for (i = 0, waterLink = waterChain->links; i < linkCount; i++, waterLink++) { + linkY += LINK_SIZE; + waterLink->basis.x.x = 1.0f; + waterLink->basis.y.y = 1.0f; + waterLink->basis.z.z = 1.0f; + waterLink->pos.x = this->actor.home.pos.x; + waterLink->pos.y = linkY; + waterLink->pos.z = this->actor.home.pos.z; + waterLink->velocity.x = (Rand_ZeroOne() - 0.5f) * swayVel; + waterLink->velocity.y = (Rand_ZeroOne() - 0.5f) * swayVel; + waterLink->velocity.z = (Rand_ZeroOne() - 0.5f) * swayVel; + } + waterChain->maxY = linkY; + + // Rest position is slightly below max chain length to give slack. + waterChain->restY = waterChain->maxY * 0.97f; + linkY -= LINK_SIZE * 1.5f; + if (waterChain->restY < linkY) { // This should only happen if linkCount > 50 + waterChain->restY = linkY; + } + + if (wallCheckRadius < 0.0f) { + waterChain->wallCheckDistSq = -1.0f; // Negative value means skip wall collision checks + } else if (wallCheckRadius <= WATER_RADIUS + 1.0f) { + waterChain->wallCheckDistSq = 0.0f; + } else { + waterChain->wallCheckDistSq = SQ(wallCheckRadius - WATER_RADIUS); + } +} + +void ObjMine_Water_InitCollider(ObjMine* this, s32 linkCount) { + Matrix_Translate(this->actor.home.pos.x, this->actor.home.pos.y + (linkCount * LINK_SIZE) + ATTACH_OFFSET, + this->actor.home.pos.z, MTXMODE_NEW); + Matrix_Scale(this->actor.scale.x, this->actor.scale.y, this->actor.scale.z, MTXMODE_APPLY); + Collider_UpdateSpheres(0, &this->collider); +} + +void ObjMine_Water_SetWorld(ObjMine* this) { + s32 pad; // Could be recast to thisx as in ObjMine_Path_Move + ObjMineWaterChain* waterChain = &this->chain.water; + s32 linkCount = OBJMINE_GET_LINK_COUNT(&this->actor); + + if (linkCount == 0) { + this->actor.world.pos.y = this->actor.home.pos.y + LINK_SIZE + ATTACH_OFFSET; + } else { + ObjMineWaterLink* lastLink = &waterChain->links[linkCount - 1]; + Vec3f mineOffset; + + Math_Vec3f_ScaleAndStore(&lastLink->basis.y, ATTACH_OFFSET, &mineOffset); + Math_Vec3f_Sum(&lastLink->pos, &mineOffset, &this->actor.world.pos); + } +} + +void ObjMine_Water_WallCheck(ObjMine* this, PlayState* play) { + s32 pad; // Could be recast to thisx as in ObjMine_Path_Move + ObjMineWaterChain* waterChain = &this->chain.water; + + waterChain->touchWall = false; + if (waterChain->wallCheckDistSq > -1e-6f) { + // Checks for walls if mine is sufficiently far from home. If found, sets ejection force towards home. + if (waterChain->wallCheckDistSq <= Math3D_XZDistanceSquared(this->actor.home.pos.x, this->actor.home.pos.z, + this->actor.world.pos.x, this->actor.world.pos.z)) { + Vec3f centerPos; + Vec3f offsetPos; + Vec3f result; // not used + Vec3f xzFromHome; + Vec3f xzDirFromHome; + CollisionPoly* poly; // not used + s32 bgId; // not used + f32 norm; // not used + f32 invNorm; // not used + + xzFromHome.x = this->actor.world.pos.x - this->actor.home.pos.x; + xzFromHome.y = 0.0f; + xzFromHome.z = this->actor.world.pos.z - this->actor.home.pos.z; + + if (ObjMine_GetUnitVec3fNorm(&xzFromHome, &xzDirFromHome, &norm, &invNorm)) { + + offsetPos.x = this->actor.world.pos.x + (xzDirFromHome.x * WATER_RADIUS); + offsetPos.y = this->actor.world.pos.y; + offsetPos.z = this->actor.world.pos.z + (xzDirFromHome.z * WATER_RADIUS); + + centerPos.x = this->actor.home.pos.x; + centerPos.y = this->actor.world.pos.y; + centerPos.z = this->actor.home.pos.z; + + if (BgCheck_EntityLineTest1(&play->colCtx, ¢erPos, &offsetPos, &result, &poly, true, false, false, + true, &bgId)) { + waterChain->touchWall = true; + waterChain->wallEject.x = xzDirFromHome.x * -0.2f; + waterChain->wallEject.z = xzDirFromHome.z * -0.2f; + } + } + } + } +} + +Vec3f sLastLinkAccel[OBJMINE_CHAIN_MAX + 1]; + +void ObjMine_Water_ApplyForces(ObjMine* this) { + ObjMineWaterChain* waterChain = &this->chain.water; + ObjMineWaterLink* waterLink; + s32 i; + s16 swayPhase = 0; + s32 linkCount = OBJMINE_GET_LINK_COUNT(&this->actor); + f32 inverseCount = 1.0f / linkCount; + f32 restoreY = (waterChain->links[linkCount - 1].pos.y - waterChain->restY) * waterChain->restoreY; + Vec3f tension; + f32 scaledKnockback; + s32 index; + s32 pad; + Vec3f* lastLinkAccel; + + // Applies the buoyant force and sway from the water. + for (i = 0, waterLink = waterChain->links, lastLinkAccel = sLastLinkAccel; i < linkCount; + i++, waterLink++, swayPhase += waterChain->swayPhaseVel, lastLinkAccel++) { + Math_Vec3f_Copy(lastLinkAccel, &waterLink->accel); + + if (waterLink->pos.y <= this->actor.home.pos.y) { + waterLink->accel.y = waterChain->restoreY * -96.0f; + } else if (restoreY > 0.0f) { + waterLink->accel.y = (i + 1) * inverseCount * (restoreY / 0.3f); + } else { + waterLink->accel.y = restoreY; + } + + waterLink->accel.x = (waterLink->pos.x - this->actor.home.pos.x) * waterChain->restoreXZ; + waterLink->accel.z = (waterLink->pos.z - this->actor.home.pos.z) * waterChain->restoreXZ; + waterLink->accel.x += waterChain->swayXZ * Math_SinS(swayPhase); + waterLink->accel.y += waterChain->swayY; + waterLink->accel.z += waterChain->swayXZ * Math_CosS(swayPhase); + } + + // Applies knockback force. Knockback on the links is scaled quadratically, possibly to account for link rotations + // being cumulative + if (waterChain->knockback > 0.0001f) { + for (i = 0, waterLink = waterChain->links; i < linkCount; i++, waterLink++) { + scaledKnockback = SQ((linkCount - i) * inverseCount); + + waterLink->accel.x += waterChain->knockbackDir.x * scaledKnockback; + waterLink->accel.y += waterChain->knockbackDir.y * scaledKnockback; + waterLink->accel.z += waterChain->knockbackDir.z * scaledKnockback; + } + } + + // Moves chain away from wall if mine is intersecting one + if (waterChain->touchWall) { + for (i = 0, waterLink = waterChain->links; i < linkCount; i++, waterLink++) { + waterLink->accel.x += waterChain->wallEject.x; + waterLink->accel.z += waterChain->wallEject.z; + } + } + + // Forces on the links from other links. This is simulated by a triangle filter on the forces from the previous + // frame. + for (i = 0, waterLink = waterChain->links; i < linkCount; i++, waterLink++) { + Math_Vec3f_Copy(&tension, &gZeroVec3f); + + if (i >= 2) { + tension.x += sLastLinkAccel[i - 2].x * 0.075f; + tension.y += sLastLinkAccel[i - 2].y * 0.075f; + tension.z += sLastLinkAccel[i - 2].z * 0.075f; + } + + if (i >= 1) { + tension.x += sLastLinkAccel[i - 1].x * 0.15f; + tension.y += sLastLinkAccel[i - 1].y * 0.15f; + tension.z += sLastLinkAccel[i - 1].z * 0.15f; + } + + tension.x += sLastLinkAccel[i].x * 0.3f; + tension.y += sLastLinkAccel[i].y * 0.3f; + tension.z += sLastLinkAccel[i].z * 0.3f; + + index = i + 1; + if (index < linkCount) { + tension.x += sLastLinkAccel[index].x * 0.15f; + tension.y += sLastLinkAccel[index].y * 0.15f; + tension.z += sLastLinkAccel[index].z * 0.15f; + } + + index = i + 2; + if (index < linkCount) { + tension.x += sLastLinkAccel[index].x * 0.075f; + tension.y += sLastLinkAccel[index].y * 0.075f; + tension.z += sLastLinkAccel[index].z * 0.075f; + } + waterLink->accel.x += tension.x; + waterLink->accel.y += tension.y; + waterLink->accel.z += tension.z; + } +} + +void ObjMine_Water_UpdateLinks(ObjMine* this) { + s32 pad; // Could be recast to thisx as in ObjMine_Path_Move + ObjMineMtxF3 newBasis; + s32 linkCount = OBJMINE_GET_LINK_COUNT(&this->actor); + ObjMineWaterChain* waterChain = &this->chain.water; + ObjMineWaterLink* waterLink; + s32 i; + Vec3f* prevBasisX; + Vec3f tempVec; + Vec3f diffDir; + Vec3f unusedLinkPos; + Vec3f jointPos; + s32 changeBasis; + f32 diffNorm; + f32 invNorm; // not used + Vec3f* tempBasisX; + + // The joint between the current link and next link is half the link's length from its center. The first link is + // assumed to be vertical and centered on home. + jointPos.x = this->actor.home.pos.x; + jointPos.y = this->actor.home.pos.y + (LINK_SIZE / 2.0f); + jointPos.z = this->actor.home.pos.z; + + for (i = 0, prevBasisX = NULL, waterLink = waterChain->links; i < linkCount; + i++, prevBasisX = &waterLink->basis.x, waterLink++) { + changeBasis = false; + + waterLink->velocity.x += waterLink->accel.x; + waterLink->velocity.y += waterLink->accel.y; + waterLink->velocity.z += waterLink->accel.z; + + Math_Vec3f_Scale(&waterLink->velocity, waterChain->drag); + Math_Vec3f_Copy(&unusedLinkPos, &waterLink->pos); + + waterLink->pos.x += waterLink->velocity.x; + waterLink->pos.y += waterLink->velocity.y; + waterLink->pos.z += waterLink->velocity.z; + + // Heavily reduce velocity if link is below home and moving downward. + if ((waterLink->pos.y <= this->actor.home.pos.y) && (waterLink->velocity.y < 0.0f)) { + waterLink->velocity.y *= 0.1f; + } + + // If the calculated position is less than 1/3 of the link's length from the joint, the chain is considered + // slack and the link keeps the same basis. Otherwise the basis is updated with the new position relative to the + // joint. The StepUntilParallel causes the chain to straighten over time. + Math_Vec3f_Diff(&waterLink->pos, &jointPos, &tempVec); + + if (ObjMine_GetUnitVec3fNorm(&tempVec, &diffDir, &diffNorm, &invNorm) && (diffNorm > LINK_SIZE / 3.0f)) { + Math_Vec3f_Copy(&newBasis.y, &waterLink->basis.y); + ObjMine_StepUntilParallel(&newBasis.y, &diffDir, M_PI / 30); + + tempBasisX = (prevBasisX == NULL) ? &sStandardBasis.x : prevBasisX; + + Math3D_CrossProduct(tempBasisX, &newBasis.y, &tempVec); + + // Skips change of basis if any of the basis vectors would be zero. + if (ObjMine_GetUnitVec3f(&tempVec, &newBasis.z)) { + Math3D_CrossProduct(&newBasis.y, &newBasis.z, &tempVec); + if (ObjMine_GetUnitVec3f(&tempVec, &newBasis.x)) { + changeBasis = true; + } + } + } + + if (changeBasis) { + Math_Vec3f_Copy(&waterLink->basis.x, &newBasis.x); + Math_Vec3f_Copy(&waterLink->basis.y, &newBasis.y); + Math_Vec3f_Copy(&waterLink->basis.z, &newBasis.z); + } else { + diffNorm = LINK_SIZE / 3.0f; + } + + // Sets new link position from calculated rotation basis. The check ensures displacement from the previous link + // doesn't cause the links to separate + if (diffNorm >= (LINK_SIZE / 2.0f)) { + waterLink->pos.x = jointPos.x + (waterLink->basis.y.x * (LINK_SIZE / 2.0f)); + waterLink->pos.y = jointPos.y + (waterLink->basis.y.y * (LINK_SIZE / 2.0f)); + waterLink->pos.z = jointPos.z + (waterLink->basis.y.z * (LINK_SIZE / 2.0f)); + } else { + waterLink->pos.x = jointPos.x + (waterLink->basis.y.x * diffNorm); + waterLink->pos.y = jointPos.y + (waterLink->basis.y.y * diffNorm); + waterLink->pos.z = jointPos.z + (waterLink->basis.y.z * diffNorm); + } + + // Calculate the position of the joint with the next link. + jointPos.x = waterLink->pos.x + (waterLink->basis.y.x * (LINK_SIZE / 2.0f)); + jointPos.y = waterLink->pos.y + (waterLink->basis.y.y * (LINK_SIZE / 2.0f)); + jointPos.z = waterLink->pos.z + (waterLink->basis.y.z * (LINK_SIZE / 2.0f)); + } +} + +void ObjMine_Water_UpdateChain(ObjMine* this, PlayState* play) { + ObjMine_Water_WallCheck(this, play); + ObjMine_Water_ApplyForces(this); + ObjMine_Water_UpdateLinks(this); +} + +void ObjMine_Init(Actor* thisx, PlayState* play) { + s32 pad; // Can be playstate recast. Must be gamestate recast. + ObjMine* this = THIS; + s32 pathIndex = OBJMINE_GET_PATH_INDEX(&this->actor); + Path* path; + s32 bgId; // not used + s32 type = OBJMINE_GET_TYPE(&this->actor); + + Actor_ProcessInitChain(&this->actor, sInitChain); + + this->actor.shape.rot.z = 0; + this->actor.world.rot.z = 0; + + Collider_InitJntSph(play, &this->collider); + Collider_SetJntSph(play, &this->collider, &this->actor, &sJntSphInit, this->colliderElements); + + if (type == OBJMINE_TYPE_PATH) { + ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawCircle, 45.0f); + this->actor.shape.shadowAlpha = 140; + this->pathSpeed = sPathSpeeds[OBJMINE_GET_PATH_SPEED(&this->actor)]; // sPathSpeeds[i] = i + 1.0f + if (pathIndex == 0xFF) { + ObjMine_Path_SetupStationary(this); + } else { + path = &play->setupPathList[pathIndex]; + + this->waypointIndex = 0; + this->waypointCount = path->count - 1; + this->waypoints = (Vec3s*)Lib_SegmentedToVirtual(path->points); + + ObjMine_Path_MoveToWaypoint(this, this->waypointIndex); + ObjMine_Path_SetupMove(this); + } + Matrix_SetTranslateRotateYXZ(this->actor.world.pos.x, this->actor.world.pos.y, this->actor.world.pos.z, + &this->actor.shape.rot); + Matrix_Scale(this->actor.scale.x, this->actor.scale.y, this->actor.scale.z, MTXMODE_APPLY); + Collider_UpdateSpheres(0, &this->collider); + this->actor.floorHeight = BgCheck_EntityRaycastFloor5(&play->colCtx, &this->actor.floorPoly, &bgId, + &this->actor, &this->actor.world.pos); + } else { + s32 linkCount = OBJMINE_GET_LINK_COUNT(&this->actor); + + this->actor.update = ObjMine_AirWater_Update; + this->actor.uncullZoneScale = 150.0f + (linkCount * (LINK_SIZE * 1.75f)); + this->actor.uncullZoneDownward = 150.0f + (linkCount * (LINK_SIZE * 1.75f)); + ActorShape_Init(&this->actor.shape, 0.0f, ActorShadow_DrawCircle, 45.0f); + this->actor.shape.shadowAlpha = 140; + + if (type == OBJMINE_TYPE_AIR) { + this->actor.draw = ObjMine_Air_Draw; + ObjMine_Air_InitChain(this, linkCount); + this->actor.world.pos.y = -ATTACH_OFFSET - (linkCount * LINK_SIZE) + this->actor.home.pos.y; + ObjMine_Air_InitCollider(this, linkCount); + func_800B4AEC(play, &this->actor, 0.0f); + if (linkCount == 0) { + ObjMine_Air_SetupStationary(this); + } else { + ObjMine_Air_SetupChained(this); + } + } else { + this->actor.draw = ObjMine_Water_Draw; + ObjMine_Water_InitChain(this, linkCount); + this->actor.world.pos.y = ATTACH_OFFSET + (linkCount * LINK_SIZE) + this->actor.home.pos.y; + ObjMine_Water_InitCollider(this, linkCount); + if (linkCount == 0) { + ObjMine_Water_SetupStationary(this); + } else { + ObjMine_Water_SetupChained(this); + } + } + } +} + +void ObjMine_Destroy(Actor* thisx, PlayState* play) { + ObjMine* this = THIS; + + Collider_DestroyJntSph(play, &this->collider); +} + +void ObjMine_Path_SetupStationary(ObjMine* this) { + this->actionFunc = ObjMine_Path_Stationary; +} + +void ObjMine_Path_Stationary(ObjMine* this, PlayState* play) { +} + +void ObjMine_Path_SetupMove(ObjMine* this) { + this->actor.flags |= ACTOR_FLAG_10; + this->actionFunc = ObjMine_Path_Move; +} + +void ObjMine_Path_Move(ObjMine* this, PlayState* play) { + Actor* thisx = &this->actor; + Vec3f nextWaypoint; + f32 distToWaypoint; + f32 step; + f32 target; + s32 bgId; // not used + + // thisx->velocity is temporarily set to the vector difference to the next waypoint. + Math_Vec3s_ToVec3f(&nextWaypoint, &this->waypoints[this->waypointIndex + 1]); + Math_Vec3f_Diff(&nextWaypoint, &thisx->world.pos, &thisx->velocity); + distToWaypoint = Math3D_Vec3fMagnitude(&thisx->velocity); + + // Mine slows to 2.0f speed when 8 frames away from the next waypoint + if ((distToWaypoint < (this->pathSpeed * 8.0f)) && (this->pathSpeed > 2.0f)) { + target = 2.0f + ((this->pathSpeed - 2.0f) * 0.1f); + step = this->pathSpeed * 0.03f; + } else { + target = this->pathSpeed; + step = this->pathSpeed * 0.16f; + } + Math_StepToF(&thisx->speed, target, step); + + // Checks if mine will reach the waypoint next frame + if ((thisx->speed + 0.05f) < distToWaypoint) { + // Rescales thisx->velocity to be equal in magnitude to speed + Math_Vec3f_Scale(&thisx->velocity, thisx->speed / distToWaypoint); + thisx->world.pos.x += thisx->velocity.x; + thisx->world.pos.y += thisx->velocity.y; + thisx->world.pos.z += thisx->velocity.z; + } else { + thisx->speed *= 0.4f; + this->waypointIndex++; + if (this->waypointIndex >= this->waypointCount) { + this->waypointIndex = 0; + } + ObjMine_Path_MoveToWaypoint(this, this->waypointIndex); + } + thisx->floorHeight = BgCheck_EntityRaycastFloor5(&play->colCtx, &thisx->floorPoly, &bgId, thisx, &thisx->world.pos); + if (thisx->flags & ACTOR_FLAG_40) { + Vec3f rotAxis; + Vec3f yhatCrossV; + MtxF rotMtxF; + + // Makes mines appear to roll while traversing the path + Math3D_CrossProduct(&sStandardBasis.y, &thisx->velocity, &yhatCrossV); + if (ObjMine_GetUnitVec3f(&yhatCrossV, &rotAxis)) { + Matrix_RotateAxisF(thisx->speed / PATH_RADIUS, &rotAxis, MTXMODE_NEW); + Matrix_RotateYS(thisx->shape.rot.y, MTXMODE_APPLY); + Matrix_RotateXS(thisx->shape.rot.x, MTXMODE_APPLY); + Matrix_RotateZS(thisx->shape.rot.z, MTXMODE_APPLY); + Matrix_Get(&rotMtxF); + Matrix_MtxFToYXZRot(&rotMtxF, &thisx->shape.rot, false); + } + } +} + +void ObjMine_SetupExplode(ObjMine* this) { + this->actor.flags |= ACTOR_FLAG_10; + this->actor.draw = ObjMine_DrawExplosion; + this->actor.shape.shadowDraw = NULL; + this->actor.scale.x = 0.02f; + this->actor.scale.y = 0.02f; + this->actor.scale.z = 0.02f; + this->actionFunc = ObjMine_Explode; +} + +void ObjMine_Explode(ObjMine* this, PlayState* play) { + this->actor.scale.x *= 1.8f; + if (this->actor.scale.x > (0.02f * 1.5f * 5.832f)) { // 5.832 = 1.8^3 + Actor_Kill(&this->actor); + return; + } + + this->actor.scale.y = this->actor.scale.x; + this->actor.scale.z = this->actor.scale.x; +} + +void ObjMine_Air_SetupChained(ObjMine* this) { + this->actionFunc = ObjMine_Air_Chained; +} + +void ObjMine_Air_Chained(ObjMine* this, PlayState* play) { + s32 pad; // Could be recast to thisx as in ObjMine_Path_Move + s32 linkCount = OBJMINE_GET_LINK_COUNT(&this->actor); + ObjMineAirChain* airChain = &this->chain.air; + ObjMineAirLink* airLink; + s32 i; + f32 xAccel; + f32 zAccel; + f32 spin; + s16 twistDiff; + + Math_Vec3f_Copy(&airChain->translation, &airChain->basis.y); + + // Explodes if collision with Player or another mine is detected. + if (ObjMine_AirWater_CheckOC(this)) { + ObjMine_AirWater_SpawnBomb(this, play); + ObjMine_AirWater_Noop(this); + ObjMine_SetupExplode(this); + return; + } + + // Calculates initial knockback and torque from hit scaled to chain length + if (this->collider.base.acFlags & AC_HIT) { + s16 torqueAngle; + f32 torque; + f32 torqueDiff; + + this->collider.base.acFlags &= ~AC_HIT; + airChain->knockback = AIR_KNOCKBACK; + ObjMine_Air_CheckAC(this, &airChain->knockbackAngle, &torqueAngle); + torque = Math_SinS(torqueAngle) * 0x96; + torqueDiff = -(torque / linkCount); + + for (i = 0, airLink = airChain->links; i < linkCount; i++, airLink++) { + airLink->spin += (s16)torque; + airLink->spin = CLAMP(airLink->spin, -0x320, 0x320); + torque -= torqueDiff; + } + } + + // Applies knockback force. Knockback applies over two frames, with the second at half strength + if (airChain->knockback > 0.0001f) { + xAccel = Math_SinS(airChain->knockbackAngle) * airChain->knockback; + zAccel = Math_CosS(airChain->knockbackAngle) * airChain->knockback; + airChain->velocity.x += xAccel; + airChain->velocity.z += zAccel; + Math_StepToF(&airChain->knockback, 0.0f, AIR_KNOCKBACK * 0.5f); + } + + // Applies chain sway, choosing a new sway randomly about every 32 frames + if ((Rand_Next() >> (0x20 - 5)) == 0) { + airChain->swaySize = Rand_ZeroOne() * airChain->swayMax; + airChain->swayPhase = Rand_Next() >> (0x20 - 16); + } + xAccel = Math_SinS(airChain->swayPhase) * airChain->swaySize; + zAccel = Math_CosS(airChain->swayPhase) * airChain->swaySize; + airChain->velocity.x += xAccel; + airChain->velocity.z += zAccel; + + // Applies the restoring force of gravity and chain tension using small-angle approximation + airChain->velocity.x += airChain->displacement.x * airChain->restore; + airChain->velocity.z += airChain->displacement.z * airChain->restore; + + // Applies linear drag + airChain->velocity.x *= airChain->drag; + airChain->velocity.z *= airChain->drag; + + // Updates scaled position and does a safety clamp to 5.0f. + airChain->displacement.x += airChain->velocity.x; + airChain->displacement.z += airChain->velocity.z; + + airChain->displacement.x = CLAMP(airChain->displacement.x, -5.0f, 5.0f); + airChain->displacement.z = CLAMP(airChain->displacement.z, -5.0f, 5.0f); + + ObjMine_Air_SetBasis(this); + ObjMine_Air_SetWorld(this); + + // Checks for wall collisions if sufficiently far from home. If collision detected, bounce off the wall at half + // speed. If speed is close to zero when hitting wall, weakly eject it instead. + if (airChain->wallCheckDistSq > -1e-6f) { + if (airChain->wallCheckDistSq <= Math3D_XZDistanceSquared(this->actor.world.pos.x, this->actor.world.pos.z, + this->actor.home.pos.x, this->actor.home.pos.z)) { + + Actor_UpdateBgCheckInfo(play, &this->actor, 0.0f, AIR_RADIUS, 0.0f, UPDBGCHECKINFO_FLAG_1); + + if ((this->actor.bgCheckFlags & BGCHECKFLAG_WALL) && (this->actor.wallPoly != NULL)) { + Vec3f xzDir; + Vec3f reflectedDir; + Vec3f wallNormal; + Vec3f xzVel; + f32 xzSpeed; + f32 invNorm; // not used + + xzVel.x = airChain->velocity.x; + xzVel.y = 0.0f; + xzVel.z = airChain->velocity.z; + + if (ObjMine_GetUnitVec3fNorm(&xzVel, &xzDir, &xzSpeed, &invNorm)) { + wallNormal.x = COLPOLY_GET_NORMAL(this->actor.wallPoly->normal.x); + wallNormal.y = COLPOLY_GET_NORMAL(this->actor.wallPoly->normal.y); + wallNormal.z = COLPOLY_GET_NORMAL(this->actor.wallPoly->normal.z); + + func_80179F64(&xzDir, &wallNormal, &reflectedDir); + + xzSpeed /= 2.0f; + airChain->velocity.x = reflectedDir.x * xzSpeed; + airChain->velocity.z = reflectedDir.z * xzSpeed; + } else { + airChain->velocity.x *= -0.1f; + airChain->velocity.z *= -0.1f; + } + ObjMine_Air_SetChainXZ(this); + ObjMine_Air_SetBasis(this); + ObjMine_Air_SetWorld(this); + } + } + } + + // Applies three torques to individual chain links: + // - restoring torque towards the default twist of 0x4000 + // - random torque for variance + // - linear drag torque + for (i = 0, airLink = airChain->links; i < linkCount; i++, airLink++) { + twistDiff = airLink->twist - 0x4000; + spin = airLink->spin + (twistDiff * -0.05f) + (30.0f * Rand_ZeroOne() - 15.0f); + spin *= 0.995f; + airLink->spin = spin; + airLink->twist += airLink->spin; + } + + func_800B4AEC(play, &this->actor, 0.0f); + ObjMine_UpdateCollider(this); + CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base); + CollisionCheck_SetAC(play, &play->colChkCtx, &this->collider.base); +} + +void ObjMine_Air_SetupStationary(ObjMine* this) { + this->actionFunc = ObjMine_Air_Stationary; +} + +void ObjMine_Air_Stationary(ObjMine* this, PlayState* play) { + // Explodes if collision with Player or another mine is detected. + if (ObjMine_AirWater_CheckOC(this)) { + ObjMine_AirWater_SpawnBomb(this, play); + ObjMine_AirWater_Noop(this); + ObjMine_SetupExplode(this); + return; + } + + CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base); + CollisionCheck_SetAC(play, &play->colChkCtx, &this->collider.base); +} + +void ObjMine_Water_SetupChained(ObjMine* this) { + this->actionFunc = ObjMine_Water_Chained; +} + +void ObjMine_Water_Chained(ObjMine* this, PlayState* play) { + ObjMineWaterChain* waterChain = &this->chain.water; + + // Explodes if collision with Player or another mine is detected. + if (ObjMine_AirWater_CheckOC(this)) { + ObjMine_AirWater_SpawnBomb(this, play); + ObjMine_AirWater_Noop(this); + ObjMine_SetupExplode(this); + return; + } + + // Calculates knockback from AC hits + if (this->collider.base.acFlags & AC_HIT) { + this->collider.base.acFlags &= ~AC_HIT; + ObjMine_Water_CheckAC(this, &waterChain->knockbackDir); + waterChain->knockback = WATER_KNOCKBACK; + Math_Vec3f_Scale(&waterChain->knockbackDir, WATER_KNOCKBACK); + } + + // Reduces knockback each frame. Effectively makes knockback 60% on the first frame and 20% on the second. + if (waterChain->knockback > 0.0001f) { + waterChain->knockback = Math_Vec3f_StepTo(&waterChain->knockbackDir, &gZeroVec3f, WATER_KNOCKBACK * 0.4f); + } + + waterChain->drag = 0.9f; + waterChain->restoreXZ = -0.0002f; + waterChain->restoreY = -0.0002f; + waterChain->swayMax = 0.003f; + + // Chooses a new sway randomly about every 32 frames. + if (Rand_Next() >> (0x20 - 5) == 0) { + s16 randAngle = Rand_Next() >> (0x20 - 16); + + waterChain->swayXZ = Math_SinS(randAngle) * 1.8f * waterChain->swayMax; + waterChain->swayY = Math_CosS(randAngle) * 0.2f * waterChain->swayMax; + waterChain->swayPhaseVel = Rand_Next() >> (0x20 - 13); + } + + ObjMine_Water_UpdateChain(this, play); + ObjMine_Water_SetWorld(this); + ObjMine_UpdateCollider(this); + CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base); + CollisionCheck_SetAC(play, &play->colChkCtx, &this->collider.base); +} + +void ObjMine_Water_SetupStationary(ObjMine* this) { + this->actionFunc = ObjMine_Water_Stationary; +} + +void ObjMine_Water_Stationary(ObjMine* this, PlayState* play) { + // Explodes if collision with Player or another mine is detected. + if (ObjMine_AirWater_CheckOC(this)) { + ObjMine_AirWater_SpawnBomb(this, play); + ObjMine_AirWater_Noop(this); + ObjMine_SetupExplode(this); + return; + } + + CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base); + CollisionCheck_SetAC(play, &play->colChkCtx, &this->collider.base); +} + +void ObjMine_Path_Update(Actor* thisx, PlayState* play) { + s32 pad; // Can be playstate recast. + ObjMine* this = THIS; + + if ((this->collider.base.ocFlags2 & OC2_HIT_PLAYER) || (this->collider.base.acFlags & AC_HIT)) { + ObjMine_Path_SpawnBomb(this, play); + ObjMine_SetupExplode(this); + } + + this->actionFunc(this, play); + + if (this->actor.update != NULL) { + this->collider.base.ocFlags1 &= ~OC1_HIT; + this->collider.base.acFlags &= ~AC_HIT; + this->collider.base.ocFlags2 &= ~OC2_HIT_PLAYER; + if ((this->actor.flags & ACTOR_FLAG_40) && (this->actionFunc != ObjMine_Explode)) { + CollisionCheck_SetOC(play, &play->colChkCtx, &this->collider.base); + CollisionCheck_SetAC(play, &play->colChkCtx, &this->collider.base); + } + } +} + +void ObjMine_AirWater_Update(Actor* thisx, PlayState* play) { + ObjMine* this = THIS; + + this->actionFunc(this, play); +} + +void ObjMine_Path_Draw(Actor* thisx, PlayState* play) { + s32 pad; // Can be playstate recast + ObjMine* this = THIS; + + OPEN_DISPS(play->state.gfxCtx); + + if (this->actionFunc == ObjMine_Path_Move) { + Collider_UpdateSpheres(0, &this->collider); + } + + func_800B8050(&this->actor, play, true); + Gfx_SetupDL25_Opa(play->state.gfxCtx); + + gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gDPSetRenderMode(POLY_OPA_DISP++, G_RM_PASS, G_RM_AA_ZB_OPA_SURF2); + gDPSetEnvColor(POLY_OPA_DISP++, 0, 0, 0, 255); + gSPDisplayList(POLY_OPA_DISP++, object_ny_DL_002068); + gSPDisplayList(POLY_OPA_DISP++, object_ny_DL_002188); + + CLOSE_DISPS(play->state.gfxCtx); +} + +void ObjMine_DrawExplosion(Actor* thisx, PlayState* play) { + s32 pad; // This could be thisx recast or playstate recast. Not enough room on stack for both. + + OPEN_DISPS(play->state.gfxCtx); + + func_800B8118(thisx, play, true); + Gfx_SetupDL25_Xlu(play->state.gfxCtx); + + gDPSetRenderMode(POLY_XLU_DISP++, G_RM_PASS, G_RM_AA_ZB_XLU_SURF2); + gDPSetEnvColor(POLY_XLU_DISP++, 0, 0, 0, 75); + gSPMatrix(POLY_XLU_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(POLY_XLU_DISP++, object_ny_DL_002068); + + CLOSE_DISPS(play->state.gfxCtx); +} + +void ObjMine_Air_Draw(Actor* thisx, PlayState* play) { + s32 pad; // Can be playstate recast + ObjMine* this = THIS; + s32 linkCount = OBJMINE_GET_LINK_COUNT(&this->actor); + ObjMineAirChain* airChain = &this->chain.air; + ObjMineAirLink* airLink; + s32 i; + Vec3f linkPos; + Vec3f linkOffset; + Gfx* gfx; + + func_800B8050(&this->actor, play, true); + + OPEN_DISPS(play->state.gfxCtx); + + gfx = POLY_OPA_DISP; + + gSPDisplayList(gfx++, &gSetupDLs[SETUPDL_25]); + gSPMatrix(gfx++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(gfx++, object_ny_DL_000030); + + ObjMine_SetRotation(&airChain->basis); + Matrix_Scale(this->actor.scale.x, this->actor.scale.y, this->actor.scale.z, MTXMODE_APPLY); + if (linkCount != 0) { + // Sets pivot point to be half a chain link length below home + Math_Vec3f_ScaleAndStore(&airChain->basis.y, -(LINK_SIZE / 2.0f), &linkOffset); + linkPos.x = this->actor.home.pos.x - linkOffset.x; + linkPos.y = this->actor.home.pos.y - (LINK_SIZE / 2.0f) - linkOffset.y; + linkPos.z = this->actor.home.pos.z - linkOffset.z; + Math_Vec3f_ScaleAndStore(&airChain->basis.y, -LINK_SIZE, &linkOffset); + + for (i = 0, airLink = airChain->links; i < linkCount; i++, airLink++) { + Matrix_RotateYS(airLink->twist, MTXMODE_APPLY); + linkPos.x += linkOffset.x; + linkPos.y += linkOffset.y; + linkPos.z += linkOffset.z; + ObjMine_ReplaceTranslation(&linkPos); + + gSPMatrix(gfx++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(gfx++, object_ny_DL_000030); + } + } + + Matrix_RotateXS(0x2000, MTXMODE_APPLY); + ObjMine_ReplaceTranslation(&this->actor.world.pos); + + gSPMatrix(gfx++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gDPPipeSync(gfx++); + gDPSetRenderMode(gfx++, G_RM_PASS, G_RM_AA_ZB_OPA_SURF2); + gDPSetEnvColor(gfx++, 0, 0, 0, 255); + gSPDisplayList(gfx++, object_ny_DL_002068); + gSPDisplayList(gfx++, object_ny_DL_002188); + + POLY_OPA_DISP = gfx; + + CLOSE_DISPS(play->state.gfxCtx); +} + +void ObjMine_Water_Draw(Actor* thisx, PlayState* play) { + s32 pad; // Can be playstate recast + ObjMine* this = THIS; + s32 linkCount = OBJMINE_GET_LINK_COUNT(&this->actor); + ObjMineWaterChain* waterChain = &this->chain.water; + ObjMineWaterLink* waterLink; + s32 i; + Gfx* gfx; + + func_800B8050(&this->actor, play, true); + + OPEN_DISPS(play->state.gfxCtx); + + gfx = POLY_OPA_DISP; + + gSPDisplayList(gfx++, &gSetupDLs[SETUPDL_25]); + gSPMatrix(gfx++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(gfx++, object_ny_DL_000030); + + for (i = 0, waterLink = waterChain->links; i < linkCount; i++, waterLink++) { + ObjMine_SetRotation(&waterLink->basis); + Matrix_Scale(this->actor.scale.x, this->actor.scale.y, this->actor.scale.z, MTXMODE_APPLY); + // Consecutive chain links are offset 90 degrees. + if ((i % 2) == 0) { + Matrix_RotateYS(0x4000, MTXMODE_APPLY); + } + ObjMine_ReplaceTranslation(&waterLink->pos); + + gSPMatrix(gfx++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPDisplayList(gfx++, object_ny_DL_000030); + } + + Matrix_RotateXS(0x2000, MTXMODE_APPLY); + ObjMine_ReplaceTranslation(&this->actor.world.pos); + + gSPMatrix(gfx++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gDPPipeSync(gfx++); + gDPSetRenderMode(gfx++, G_RM_PASS, G_RM_AA_ZB_OPA_SURF2); + gDPSetEnvColor(gfx++, 0, 0, 0, 255); + gSPDisplayList(gfx++, object_ny_DL_002068); + gSPDisplayList(gfx++, object_ny_DL_002188); + + POLY_OPA_DISP = gfx; + + CLOSE_DISPS(play->state.gfxCtx); +} diff --git a/src/overlays/actors/ovl_Obj_Mine/z_obj_mine.h b/src/overlays/actors/ovl_Obj_Mine/z_obj_mine.h index 257a6ff3a9..e831f91de8 100644 --- a/src/overlays/actors/ovl_Obj_Mine/z_obj_mine.h +++ b/src/overlays/actors/ovl_Obj_Mine/z_obj_mine.h @@ -7,11 +7,89 @@ struct ObjMine; typedef void (*ObjMineActionFunc)(struct ObjMine*, PlayState*); +#define OBJMINE_GET_LINK_COUNT(thisx) ((thisx)->params & 0x3F) +#define OBJMINE_GET_PATH_INDEX(thisx) ((thisx)->params & 0xFF) +#define OBJMINE_GET_PATH_SPEED(thisx) (((thisx)->params >> 8) & 7) +#define OBJMINE_GET_TYPE(thisx) (((thisx)->params >> 12) & 3) + +#define OBJMINE_PARAM(type, linkCount, pathIndex, pathSpeed) (((type) << 0xC) | ((type == OBJMINE_TYPE_PATH) ? ((pathIndex) | ((pathSpeed) << 8)) : (linkCount))) +#define OBJMINE_PATH_PARAM(pathIndex, pathSpeed) OBJMINE_PARAM(OBJMINE_TYPE_PATH, 0, pathIndex, pathSpeed) +#define OBJMINE_AIR_PARAM(linkCount) OBJMINE_PARAM(OBJMINE_TYPE_AIR, linkCount, 0, 0) +#define OBJMINE_WATER_PARAM(linkCount) OBJMINE_PARAM(OBJMINE_TYPE_WATER, linkCount, 0, 0) + +#define OBJMINE_CHAIN_MAX 63 + +typedef enum { + /* 0 */ OBJMINE_TYPE_PATH, + /* 1 */ OBJMINE_TYPE_AIR, + /* 2 */ OBJMINE_TYPE_WATER +} ObjMineType; + +typedef struct { + /* 0x00 */ Vec3f x; + /* 0x0C */ Vec3f y; + /* 0x18 */ Vec3f z; +} ObjMineMtxF3; // size = 0x24 + +typedef struct { + /* 0x0 */ s16 twist; + /* 0x2 */ s16 spin; +} ObjMineAirLink; // size = 0x4 + +typedef struct { + /* 0x00 */ ObjMineMtxF3 basis; + /* 0x24 */ Vec3f translation; // unused + /* 0x30 */ Vec2f displacement; + /* 0x38 */ Vec2f velocity; + /* 0x40 */ f32 restore; + /* 0x44 */ f32 drag; + /* 0x48 */ f32 knockback; + /* 0x4C */ s16 knockbackAngle; + /* 0x50 */ f32 swaySize; + /* 0x54 */ s16 swayPhase; + /* 0x58 */ f32 swayMax; + /* 0x5C */ f32 wallCheckDistSq; + /* 0x60 */ ObjMineAirLink links[OBJMINE_CHAIN_MAX]; +} ObjMineAirChain; // size = 0x15C + +typedef struct { + /* 0x00 */ ObjMineMtxF3 basis; + /* 0x24 */ Vec3f pos; + /* 0x30 */ Vec3f velocity; + /* 0x3C */ Vec3f accel; +} ObjMineWaterLink; // size = 0x48 + +typedef struct { + /* 0x00 */ f32 drag; + /* 0x04 */ Vec3f knockbackDir; + /* 0x10 */ f32 knockback; + /* 0x14 */ f32 swayXZ; + /* 0x18 */ f32 swayY; + /* 0x1C */ f32 swayMax; + /* 0x20 */ s16 swayPhaseVel; + /* 0x24 */ f32 restoreXZ; + /* 0x28 */ f32 maxY; + /* 0x2C */ f32 restY; + /* 0x30 */ f32 restoreY; + /* 0x34 */ f32 wallCheckDistSq; + /* 0x38 */ Vec2f wallEject; + /* 0x40 */ s8 touchWall; + /* 0x44 */ ObjMineWaterLink links[OBJMINE_CHAIN_MAX]; +} ObjMineWaterChain; // size = 0x11FC + typedef struct ObjMine { /* 0x0000 */ Actor actor; - /* 0x0144 */ char unk_144[0x60]; + /* 0x0144 */ ColliderJntSph collider; + /* 0x0164 */ ColliderJntSphElement colliderElements[1]; /* 0x01A4 */ ObjMineActionFunc actionFunc; - /* 0x01A8 */ char unk_1A8[0x120C]; + /* 0x01A8 */ f32 pathSpeed; + /* 0x01AC */ s32 waypointCount; + /* 0x01B0 */ s32 waypointIndex; + /* 0x01B4 */ Vec3s* waypoints; + /* 0x01B8 */ union { + ObjMineAirChain air; + ObjMineWaterChain water; + } chain; } ObjMine; // size = 0x13B4 #endif // Z_OBJ_MINE_H diff --git a/tools/disasm/functions.txt b/tools/disasm/functions.txt index 3b50554bae..0e9195dfb3 100644 --- a/tools/disasm/functions.txt +++ b/tools/disasm/functions.txt @@ -11150,53 +11150,53 @@ 0x80A80508:("EnEgol_SpawnEffect",), 0x80A80750:("EnEgol_UpdateEffects",), 0x80A80904:("EnEgol_DrawEffects",), - 0x80A811D0:("func_80A811D0",), - 0x80A8120C:("func_80A8120C",), - 0x80A81288:("func_80A81288",), - 0x80A8131C:("func_80A8131C",), - 0x80A81384:("func_80A81384",), - 0x80A8140C:("func_80A8140C",), - 0x80A8146C:("func_80A8146C",), - 0x80A81544:("func_80A81544",), - 0x80A81640:("func_80A81640",), - 0x80A8164C:("func_80A8164C",), - 0x80A81684:("func_80A81684",), - 0x80A81714:("func_80A81714",), - 0x80A81818:("func_80A81818",), - 0x80A81868:("func_80A81868",), - 0x80A819A4:("func_80A819A4",), - 0x80A81A00:("func_80A81A00",), - 0x80A81AA4:("func_80A81AA4",), - 0x80A81B14:("func_80A81B14",), - 0x80A81B7C:("func_80A81B7C",), - 0x80A81D70:("func_80A81D70",), - 0x80A81DEC:("func_80A81DEC",), - 0x80A81E7C:("func_80A81E7C",), - 0x80A81FFC:("func_80A81FFC",), - 0x80A828A8:("func_80A828A8",), - 0x80A82C28:("func_80A82C28",), + 0x80A811D0:("ObjMine_Path_MoveToWaypoint",), + 0x80A8120C:("ObjMine_GetUnitVec3f",), + 0x80A81288:("ObjMine_GetUnitVec3fNorm",), + 0x80A8131C:("ObjMine_Path_SpawnBomb",), + 0x80A81384:("ObjMine_AirWater_SpawnBomb",), + 0x80A8140C:("ObjMine_AirWater_CheckOC",), + 0x80A8146C:("ObjMine_Air_CheckAC",), + 0x80A81544:("ObjMine_Water_CheckAC",), + 0x80A81640:("ObjMine_AirWater_Noop",), + 0x80A8164C:("ObjMine_ReplaceTranslation",), + 0x80A81684:("ObjMine_ReplaceRotation",), + 0x80A81714:("ObjMine_StepUntilParallel",), + 0x80A81818:("ObjMine_UpdateCollider",), + 0x80A81868:("ObjMine_Air_InitChain",), + 0x80A819A4:("ObjMine_Air_SetCollider",), + 0x80A81A00:("ObjMine_Air_SetBasis",), + 0x80A81AA4:("ObjMine_Air_SetWorld",), + 0x80A81B14:("ObjMine_Air_SetChainXZ",), + 0x80A81B7C:("ObjMine_Water_InitChain",), + 0x80A81D70:("ObjMine_Water_SetCollider",), + 0x80A81DEC:("ObjMine_Water_SetWorld",), + 0x80A81E7C:("ObjMine_Water_WallCheck",), + 0x80A81FFC:("ObjMine_Water_ApplyForces",), + 0x80A828A8:("ObjMine_Water_UpdateLinks",), + 0x80A82C28:("ObjMine_Water_UpdateChain",), 0x80A82C5C:("ObjMine_Init",), 0x80A82F58:("ObjMine_Destroy",), - 0x80A82F84:("func_80A82F84",), - 0x80A82F98:("func_80A82F98",), - 0x80A82FA8:("func_80A82FA8",), - 0x80A82FC8:("func_80A82FC8",), - 0x80A83214:("func_80A83214",), - 0x80A83258:("func_80A83258",), - 0x80A832BC:("func_80A832BC",), - 0x80A832D0:("func_80A832D0",), - 0x80A83A74:("func_80A83A74",), - 0x80A83A88:("func_80A83A88",), - 0x80A83B14:("func_80A83B14",), - 0x80A83B28:("func_80A83B28",), - 0x80A83CEC:("func_80A83CEC",), - 0x80A83D00:("func_80A83D00",), - 0x80A83D8C:("ObjMine_Update",), - 0x80A83E7C:("func_80A83E7C",), - 0x80A83EA0:("ObjMine_Draw",), - 0x80A83FBC:("func_80A83FBC",), - 0x80A84088:("func_80A84088",), - 0x80A84338:("func_80A84338",), + 0x80A82F84:("ObjMine_Path_SetupStationary",), + 0x80A82F98:("ObjMine_Path_Stationary",), + 0x80A82FA8:("ObjMine_Path_SetupMove",), + 0x80A82FC8:("ObjMine_Path_Move",), + 0x80A83214:("ObjMine_SetupExplode",), + 0x80A83258:("ObjMine_Explode",), + 0x80A832BC:("ObjMine_Air_SetupChained",), + 0x80A832D0:("ObjMine_Air_Chained",), + 0x80A83A74:("ObjMine_Air_SetupStationary",), + 0x80A83A88:("ObjMine_Air_Stationary",), + 0x80A83B14:("ObjMine_Water_SetupChained",), + 0x80A83B28:("ObjMine_Water_Chained",), + 0x80A83CEC:("ObjMine_Water_SetupStationary",), + 0x80A83D00:("ObjMine_Water_Stationary",), + 0x80A83D8C:("ObjMine_Path_Update",), + 0x80A83E7C:("ObjMine_AirWater_Update",), + 0x80A83EA0:("ObjMine_Path_Draw",), + 0x80A83FBC:("ObjMine_DrawExplosion",), + 0x80A84088:("ObjMine_Air_Draw",), + 0x80A84338:("ObjMine_Water_Draw",), 0x80A84CD0:("ObjPurify_SetSysMatrix",), 0x80A84CF8:("ObjPurify_IsPurified",), 0x80A84D68:("ObjPurify_Init",), diff --git a/tools/disasm/variables.txt b/tools/disasm/variables.txt index 1f28ec606d..91efbd8e20 100644 --- a/tools/disasm/variables.txt +++ b/tools/disasm/variables.txt @@ -12111,7 +12111,7 @@ 0x80A84698:("D_80A84698","f32","",0x4), 0x80A8469C:("D_80A8469C","f32","",0x4), 0x80A846A0:("D_80A846A0","f32","",0x4), - 0x80A849D0:("D_80A849D0","UNK_TYPE4","",0x4), + 0x80A849D0:("sLastLinkAccel","Vec3f[]","",0x4), 0x80A85460:("Obj_Purify_InitVars","UNK_TYPE1","",0x1), 0x80A85480:("ObjPurifyInfoList","UNK_TYPE1","",0x1), 0x80A854A0:("D_80A854A0","UNK_TYPE1","",0x1),