diff --git a/assets/xml/objects/gameplay_dangeon_keep.xml b/assets/xml/objects/gameplay_dangeon_keep.xml
index f3ad6195a9..9d5a9901ad 100644
--- a/assets/xml/objects/gameplay_dangeon_keep.xml
+++ b/assets/xml/objects/gameplay_dangeon_keep.xml
@@ -75,15 +75,19 @@
+
+
-
-
-
-
+
+
+
+
+
+
diff --git a/include/functions.h b/include/functions.h
index 0e641326d1..c4bd9eef77 100644
--- a/include/functions.h
+++ b/include/functions.h
@@ -3582,7 +3582,7 @@ void Audio_SetCutsceneFlag(u8 flag);
// void func_801A3FB4(void);
// void func_801A3FFC(UNK_TYPE1 param_1);
void audio_setBGM(u32 bgmID);
-// void func_801A4058(void);
+void func_801A4058(UNK_TYPE arg0);
// void func_801A41C8(void);
// void func_801A41F8(void);
// void func_801A429C(void);
diff --git a/spec b/spec
index 16fb78b81c..66ab043e0b 100644
--- a/spec
+++ b/spec
@@ -2346,8 +2346,7 @@ beginseg
name "ovl_En_Warp_tag"
compress
include "build/src/overlays/actors/ovl_En_Warp_tag/z_en_warp_tag.o"
- include "build/data/ovl_En_Warp_tag/ovl_En_Warp_tag.data.o"
- include "build/data/ovl_En_Warp_tag/ovl_En_Warp_tag.reloc.o"
+ include "build/src/overlays/actors/ovl_En_Warp_tag/ovl_En_Warp_tag_reloc.o"
endseg
beginseg
diff --git a/src/overlays/actors/ovl_En_Warp_tag/z_en_warp_tag.c b/src/overlays/actors/ovl_En_Warp_tag/z_en_warp_tag.c
index ce6367b877..5759b0a9bd 100644
--- a/src/overlays/actors/ovl_En_Warp_tag/z_en_warp_tag.c
+++ b/src/overlays/actors/ovl_En_Warp_tag/z_en_warp_tag.c
@@ -2,9 +2,11 @@
* File: z_en_warp_tag.c
* Overlay: ovl_En_Warp_tag
* Description: Warp to Trial Entrance
+ * if GoronTrial, has model: Uses GAMEPLAY_DANGEON_KEEP object assigned in EnWarptag_Init
*/
#include "z_en_warp_tag.h"
+#include "objects/gameplay_dangeon_keep/gameplay_dangeon_keep.h"
#define FLAGS (ACTOR_FLAG_1 | ACTOR_FLAG_10 | ACTOR_FLAG_2000000 | ACTOR_FLAG_8000000)
@@ -13,15 +15,15 @@
void EnWarptag_Init(Actor* thisx, GlobalContext* globalCtx);
void EnWarptag_Destroy(Actor* thisx, GlobalContext* globalCtx);
void EnWarptag_Update(Actor* thisx, GlobalContext* globalCtx);
+void EnWarpTag_Draw(Actor* thisx, GlobalContext* globalCtx);
-void func_809C085C(EnWarptag* this, GlobalContext* globalCtx);
-void func_809C08E0(EnWarptag* this, GlobalContext* globalCtx);
-void func_809C09A0(EnWarptag* this, GlobalContext* globalCtx);
-void func_809C0A20(EnWarptag* this, GlobalContext* globalCtx);
-void func_809C0AB4(EnWarptag* this, GlobalContext* globalCtx);
-void func_809C0E30(EnWarptag* this, GlobalContext* globalCtx);
+void EnWarpTag_CheckDungeonKeepObject(EnWarptag* this, GlobalContext* globalCtx);
+void EnWarpTag_WaitForPlayer(EnWarptag* this, GlobalContext* globalCtx);
+void EnWarpTag_Unused809C09A0(EnWarptag* this, GlobalContext* globalCtx);
+void EnWarpTag_Unused809C0A20(EnWarptag* this, GlobalContext* globalCtx);
+void EnWarpTag_RespawnPlayer(EnWarptag* this, GlobalContext* globalCtx);
+void EnWarpTag_GrottoReturn(EnWarptag* this, GlobalContext* globalCtx);
-#if 0
const ActorInit En_Warp_tag_InitVars = {
ACTOR_EN_WARP_TAG,
ACTORCAT_ITEMACTION,
@@ -34,32 +36,238 @@ const ActorInit En_Warp_tag_InitVars = {
(ActorFunc)NULL,
};
-// static InitChainEntry sInitChain[] = {
-static InitChainEntry D_809C1008[] = {
+// this appears to be unused, as the code never accesses it in known vanilla cases
+// these unknown values get passed to a unknown z_message function
+u8 D_809C1000[] = { 0x28, 0x29, 0x2A, 0x2B, 0x2D, 0x2C, 0, 0 };
+
+static InitChainEntry sInitChain[] = {
ICHAIN_VEC3F(scale, 1, ICHAIN_CONTINUE),
ICHAIN_VEC3S(shape.rot, 0, ICHAIN_STOP),
};
-#endif
+void EnWarptag_Init(Actor* thisx, GlobalContext* globalCtx) {
+ EnWarptag* this = THIS;
-extern InitChainEntry D_809C1008[];
+ Actor_ProcessInitChain(&this->dyna.actor, sInitChain);
+ Actor_SetFocus(&this->dyna.actor, 0.0f);
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Warp_tag/EnWarptag_Init.s")
+ if (GET_WARPTAG_3C0_MAX(thisx) == WARPTAG_3C0_MAX) {
+ this->dyna.actor.flags &= ~ACTOR_FLAG_1;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Warp_tag/EnWarptag_Destroy.s")
+ if (GET_WARPTAG_INVISIBLE(&this->dyna.actor)) {
+ this->actionFunc = EnWarpTag_WaitForPlayer;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Warp_tag/func_809C085C.s")
+ } else {
+ if ((this->dangeonKeepObject = Object_GetIndex(&globalCtx->objectCtx, GAMEPLAY_DANGEON_KEEP)) < 0) {
+ Actor_MarkForDeath(&this->dyna.actor);
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Warp_tag/func_809C08E0.s")
+ this->actionFunc = EnWarpTag_CheckDungeonKeepObject;
+ }
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Warp_tag/func_809C09A0.s")
+ } else { // not used by known variants
+ this->actionFunc = EnWarpTag_Unused809C09A0;
+ }
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Warp_tag/func_809C0A20.s")
+void EnWarptag_Destroy(Actor* thisx, GlobalContext* globalCtx) {
+ EnWarptag* this = THIS;
+ if (this->dyna.actor.draw != NULL) {
+ DynaPoly_DeleteBgActor(globalCtx, &globalCtx->colCtx.dyna, this->dyna.bgId);
+ }
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Warp_tag/func_809C0AB4.s")
+/**
+ * Loads DynaPoly from GAMEPLAY_DANGEON_KEEP.
+ */
+void EnWarpTag_CheckDungeonKeepObject(EnWarptag* this, GlobalContext* globalCtx) {
+ if (Object_IsLoaded(&globalCtx->objectCtx, this->dangeonKeepObject)) {
+ this->actionFunc = EnWarpTag_WaitForPlayer;
+ DynaPolyActor_Init(&this->dyna, 0x1);
+ DynaPolyActor_LoadMesh(globalCtx, &this->dyna, &gWarpTagGoronTrialBaseCollider);
+ this->dyna.actor.objBankIndex = this->dangeonKeepObject;
+ this->dyna.actor.draw = EnWarpTag_Draw;
+ }
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Warp_tag/func_809C0E30.s")
+void EnWarpTag_WaitForPlayer(EnWarptag* this, GlobalContext* globalCtx) {
+ if (!Player_InCsMode(&globalCtx->state) && (this->dyna.actor.xzDistToPlayer <= 30.0f) &&
+ (this->dyna.actor.playerHeightRel <= 10.0f)) {
+ if (GET_WARPTAG_INVISIBLE(&this->dyna.actor)) {
+ func_800B7298(globalCtx, NULL, 0x51);
+ this->actionFunc = EnWarpTag_GrottoReturn;
+ } else {
+ func_800B7298(globalCtx, NULL, 0xF);
+ this->actionFunc = EnWarpTag_RespawnPlayer;
+ }
+ }
+}
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Warp_tag/EnWarptag_Update.s")
+/**
+ * Unused ActionFunc: assigned in EnWarpTag_Init, no known variants use.
+ */
+void EnWarpTag_Unused809C09A0(EnWarptag* this, GlobalContext* globalCtx) {
+ if (func_800B8718(&this->dyna.actor, &globalCtx->state)) {
+ // func above: checks for ACTOR_FLAG_20000000, returns true and resets if set, else return false
+ // this actor doesnt have that flag set default, or in init, and this is called shortly after init
+ // and I doubt its set externally by another actor, so I believe this is unused
+ // might be a bug, they might have meant to set actor flag (0x2000 0000) up above but mistyped (0x200 0000)
+ // also GET_WARPTAG_3C0 should always return 2C0 -> 0xF for all known in-game uses, which is OOB
+ func_80152434(globalCtx, D_809C1000[GET_WARPTAG_3C0(&this->dyna.actor)]); // unk message function
+ this->actionFunc = EnWarpTag_Unused809C0A20;
-#pragma GLOBAL_ASM("asm/non_matchings/overlays/ovl_En_Warp_tag/func_809C0F3C.s")
+ } else {
+ func_800B8804(&this->dyna.actor, globalCtx, 50.0f); // updates player->unk_A90
+ }
+}
+
+/**
+ * Unused ActionFunc: assigned by EnWarpTag_Unused809C09A0, no known variants use.
+ */
+void EnWarpTag_Unused809C0A20(EnWarptag* this, GlobalContext* globalCtx) {
+ if (globalCtx->msgCtx.ocarinaMode == 9) {
+ func_800B7298(globalCtx, NULL, 7);
+ this->actionFunc = EnWarpTag_RespawnPlayer;
+ ActorCutscene_Stop(ActorCutscene_GetCurrentIndex());
+
+ } else if (globalCtx->msgCtx.ocarinaMode >= 2) {
+ globalCtx->msgCtx.ocarinaMode = 4;
+ this->actionFunc = EnWarpTag_Unused809C09A0;
+ }
+}
+
+/**
+ * ActionFunc: Goron Trial (Moon), respawn at the beginning of goron rolling track, try again.
+ */
+void EnWarpTag_RespawnPlayer(EnWarptag* this, GlobalContext* globalCtx) {
+ ActorEntry* playerActorEntry;
+ Player* player;
+ s32 playerSpawnIndex;
+ s32 new15E;
+ s32 entranceIndex;
+ u32 playerSpawnIndexPerForm[PLAYER_FORM_MAX];
+ u8 playerForm;
+ s16 playerParams;
+
+ player = GET_PLAYER(globalCtx);
+ if (globalCtx->playerActorCsIds[4] >= 0 && ActorCutscene_GetCurrentIndex() != globalCtx->playerActorCsIds[4]) {
+ if (ActorCutscene_GetCanPlayNext(globalCtx->playerActorCsIds[4]) == 0) {
+ ActorCutscene_SetIntentToPlay(globalCtx->playerActorCsIds[4]);
+
+ } else {
+ ActorCutscene_StartAndSetUnkLinkFields(globalCtx->playerActorCsIds[4], &this->dyna.actor);
+ func_800B8E58(player, NA_SE_PL_WARP_PLATE);
+ func_8016566C(0);
+ }
+
+ } else {
+ f32 diffX = player->actor.world.pos.x - this->dyna.actor.world.pos.x;
+ Vec3f newRespawnPos;
+ f32 diffZ = player->actor.world.pos.z - this->dyna.actor.world.pos.z;
+ f32 distance = sqrtf(SQ(diffX) + SQ(diffZ));
+
+ // some weird float behavior prevention?
+ if (distance != 0.0f) {
+ distance = (distance - 1.0f) / distance;
+ distance = CLAMP_MIN(distance, 0.0f);
+ }
+
+ player->actor.world.pos.x = this->dyna.actor.world.pos.x + (diffX * distance);
+ player->actor.world.pos.z = this->dyna.actor.world.pos.z + (diffZ * distance);
+
+ if (Math_StepToS(&this->unkValue15E, 0x2710, 0xC8)) {
+ player->stateFlags3 |= 0x1;
+ player->actor.gravity = -0.5f;
+
+ if (this->dyna.actor.playerHeightRel < -80.0f) {
+ playerSpawnIndexPerForm[PLAYER_FORM_FIERCE_DEITY] = GET_WARPTAG_EXIT_INDEX(&this->dyna.actor);
+ playerSpawnIndexPerForm[PLAYER_FORM_HUMAN] = playerSpawnIndexPerForm[PLAYER_FORM_FIERCE_DEITY];
+ playerSpawnIndexPerForm[PLAYER_FORM_GORON] = this->dyna.actor.world.rot.x;
+ playerSpawnIndexPerForm[PLAYER_FORM_ZORA] = this->dyna.actor.world.rot.y;
+ playerSpawnIndexPerForm[PLAYER_FORM_DEKU] = this->dyna.actor.world.rot.z;
+
+ if (this->dyna.actor.draw != NULL) {
+ playerForm = PLAYER_BOOTS_FIERCE_DEITY;
+ } else {
+ playerForm = player->transformation;
+ }
+
+ entranceIndex = gSaveContext.save.entranceIndex;
+
+ playerSpawnIndex = playerSpawnIndexPerForm[playerForm];
+ playerActorEntry = &globalCtx->linkActorEntry[playerSpawnIndex];
+ newRespawnPos.x = playerActorEntry->pos.x;
+ newRespawnPos.y = playerActorEntry->pos.y;
+ newRespawnPos.z = playerActorEntry->pos.z;
+
+ if (GET_WARPTAG_3C0_MAX(&this->dyna.actor) == WARPTAG_3C0_MAX) {
+ playerParams = 0x9FF;
+ } else { // not used by any known variant
+ playerParams = 0x8FF;
+ }
+
+ // why are we getting player home rotation from the room data? doesnt player have home.rot.y?
+ // especially because we are converting from deg to binang, but isnt home.rot.y already in binang??
+ Play_SetRespawnData(
+ &globalCtx->state, 0, entranceIndex, // parameter 3 is called "sceneSetup"
+ globalCtx->setupEntranceList[playerSpawnIndex].room, playerParams, &newRespawnPos,
+ ((((playerActorEntry->rot.y >> 7) & 0x1FF) / 180.0f) * 32768.0f)); // DEG_TO_BINANG ?
+
+ func_80169EFC(&globalCtx->state);
+ gSaveContext.respawnFlag = ~0x4;
+ func_80165690();
+ }
+ }
+
+ player->actor.shape.rot.y += this->unkValue15E;
+ new15E = this->unkValue15E - 0xFA0;
+ if (new15E < 0) {
+ new15E = 0;
+ }
+ func_80165658(new15E * 0.04f); // unknown Play_ function
+ }
+}
+
+/**
+ * ActionFunc: Deku Playground, return to North Clock Town.
+ */
+void EnWarpTag_GrottoReturn(EnWarptag* this, GlobalContext* globalCtx) {
+ if (ActorCutscene_GetCurrentIndex() != this->dyna.actor.cutscene) {
+ if (ActorCutscene_GetCanPlayNext(this->dyna.actor.cutscene)) {
+ ActorCutscene_StartAndSetUnkLinkFields(this->dyna.actor.cutscene, &this->dyna.actor);
+ } else {
+ ActorCutscene_SetIntentToPlay(this->dyna.actor.cutscene);
+ }
+ }
+
+ if (this->grottoExitDelay++ == 10) {
+ globalCtx->nextEntranceIndex = globalCtx->setupExitList[GET_WARPTAG_EXIT_INDEX(&this->dyna.actor)];
+ Scene_SetExitFade(globalCtx);
+ globalCtx->sceneLoadFlag = 0x14;
+ func_8019F128(NA_SE_OC_SECRET_HOLE_OUT);
+ func_801A4058(5);
+ if (1) {}
+ gSaveContext.seqIndex = 0xFF;
+ gSaveContext.nightSeqIndex = 0xFF;
+ }
+}
+
+void EnWarptag_Update(Actor* thisx, GlobalContext* globalCtx) {
+ EnWarptag* this = THIS;
+ this->actionFunc(this, globalCtx);
+}
+
+/**
+ * Only draws for Goron Trial (a rainblow animated target).
+ */
+void EnWarpTag_Draw(Actor* thisx, GlobalContext* globalCtx) {
+ OPEN_DISPS(globalCtx->state.gfxCtx);
+
+ func_8012C28C(globalCtx->state.gfxCtx);
+ AnimatedMat_Draw(globalCtx, Lib_SegmentedToVirtual(&gWarpTagRainbowAnimMat));
+ gSPMatrix(POLY_OPA_DISP++, Matrix_NewMtx(globalCtx->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW);
+
+ gSPDisplayList(POLY_OPA_DISP++, gWarpTagGoronTrialBaseDL);
+
+ CLOSE_DISPS(globalCtx->state.gfxCtx);
+}
diff --git a/src/overlays/actors/ovl_En_Warp_tag/z_en_warp_tag.h b/src/overlays/actors/ovl_En_Warp_tag/z_en_warp_tag.h
index 1be6eb5ec7..925fda9305 100644
--- a/src/overlays/actors/ovl_En_Warp_tag/z_en_warp_tag.h
+++ b/src/overlays/actors/ovl_En_Warp_tag/z_en_warp_tag.h
@@ -8,11 +8,27 @@ struct EnWarptag;
typedef void (*EnWarptagActionFunc)(struct EnWarptag*, GlobalContext*);
typedef struct EnWarptag {
- /* 0x0000 */ Actor actor;
- /* 0x0144 */ char unk_144[0x1C];
- /* 0x0160 */ EnWarptagActionFunc actionFunc;
+ /* 0x000 */ DynaPolyActor dyna;
+ /* 0x15C */ s8 dangeonKeepObject;
+ /* 0x15E */ union {
+ s16 reusedValue; // default name
+ s16 unkValue15E; // passed to unk play func, mods player rotation, stepped to 0x2710
+ s16 grottoExitDelay; // 10 frame delay before player can leave the grotto
+ };
+ /* 0x160 */ EnWarptagActionFunc actionFunc;
} EnWarptag; // size = 0x164
extern const ActorInit En_Warp_tag_InitVars;
+// Only two known Variants:
+// Goron Trial (MOON): 0x03C1
+// Deku Playground: 0x83C0
+
+#define GET_WARPTAG_3C0_MAX(thisx) ((thisx)->params & 0x3C0)
+#define GET_WARPTAG_3C0(thisx) (((thisx)->params >> 6) & 0xF)
+#define GET_WARPTAG_EXIT_INDEX(thisx) ((thisx)->params & 0x3F)
+#define GET_WARPTAG_INVISIBLE(thisx) ((thisx)->params < 0) // 0x8000 flag
+
+#define WARPTAG_3C0_MAX 0x3C0
+
#endif // Z_EN_WARP_TAG_H
diff --git a/tools/disasm/functions.txt b/tools/disasm/functions.txt
index fd1b38a799..bd06baf023 100644
--- a/tools/disasm/functions.txt
+++ b/tools/disasm/functions.txt
@@ -8989,14 +8989,14 @@
0x809BD858:("func_809BD858",),
0x809C0760:("EnWarptag_Init",),
0x809C0824:("EnWarptag_Destroy",),
- 0x809C085C:("func_809C085C",),
- 0x809C08E0:("func_809C08E0",),
- 0x809C09A0:("func_809C09A0",),
- 0x809C0A20:("func_809C0A20",),
- 0x809C0AB4:("func_809C0AB4",),
- 0x809C0E30:("func_809C0E30",),
+ 0x809C085C:("EnWarpTag_CheckDungeonKeepObject",),
+ 0x809C08E0:("EnWarpTag_WaitForPlayer",),
+ 0x809C09A0:("EnWarpTag_Unused809C09A0",),
+ 0x809C0A20:("EnWarpTag_Unused809C0A20",),
+ 0x809C0AB4:("EnWarpTag_RespawnPlayer",),
+ 0x809C0E30:("EnWarpTag_GrottoReturn",),
0x809C0F18:("EnWarptag_Update",),
- 0x809C0F3C:("func_809C0F3C",),
+ 0x809C0F3C:("EnWarpTag_Draw",),
0x809C10B0:("func_809C10B0",),
0x809C1124:("func_809C1124",),
0x809C1158:("func_809C1158",),