Enhancement: Room/Scene Timers (#2478)

* Groundwork on scene/room timers; naming changes

* added to save manager; reworked storing timestamps

* actually saved stuff to savemanager;
accounted for null playstate

* finally fixed the fucking timers

* Added scene mapping

* Added CVar for room/scene level; fixed some displays

* reworked logic

* increase name spec for scene timestamps

* Actually save item timestamps when loading v3 save

* Cleanup

* fix merge artifact

* apply suggestions
This commit is contained in:
Ralphie Morell
2023-04-03 00:06:55 -04:00
committed by GitHub
parent 0f40472c1a
commit ff1d8a9e9d
17 changed files with 591 additions and 318 deletions

View File

@@ -1625,7 +1625,7 @@ void func_80084BF4(PlayState* play, u16 flag) {
void GameplayStats_SetTimestamp(PlayState* play, u8 item) {
// If we already have a timestamp for this item, do nothing
if (gSaveContext.sohStats.timestamp[item] != 0){
if (gSaveContext.sohStats.itemTimestamp[item] != 0){
return;
}
// Use ITEM_KEY_BOSS only for Ganon's boss key - not any other boss keys
@@ -1644,20 +1644,20 @@ void GameplayStats_SetTimestamp(PlayState* play, u8 item) {
// Count any bottled item as a bottle
if (item >= ITEM_BOTTLE && item <= ITEM_POE) {
if (gSaveContext.sohStats.timestamp[ITEM_BOTTLE] == 0) {
gSaveContext.sohStats.timestamp[ITEM_BOTTLE] = time;
if (gSaveContext.sohStats.itemTimestamp[ITEM_BOTTLE] == 0) {
gSaveContext.sohStats.itemTimestamp[ITEM_BOTTLE] = time;
}
return;
}
// Count any bombchu pack as bombchus
if (item == ITEM_BOMBCHU || (item >= ITEM_BOMBCHUS_5 && item <= ITEM_BOMBCHUS_20)) {
if (gSaveContext.sohStats.timestamp[ITEM_BOMBCHU] == 0) {
gSaveContext.sohStats.timestamp[ITEM_BOMBCHU] = time;
if (gSaveContext.sohStats.itemTimestamp[ITEM_BOMBCHU] == 0) {
gSaveContext.sohStats.itemTimestamp[ITEM_BOMBCHU] = time;
}
return;
}
gSaveContext.sohStats.timestamp[item] = time;
gSaveContext.sohStats.itemTimestamp[item] = time;
}
// Gameplay stat tracking: Update time the item was acquired
@@ -1673,28 +1673,28 @@ void Randomizer_GameplayStats_SetTimestamp(uint16_t item) {
// Use ITEM_KEY_BOSS to timestamp Ganon's boss key
if (item == RG_GANONS_CASTLE_BOSS_KEY) {
gSaveContext.sohStats.timestamp[ITEM_KEY_BOSS] = time;
gSaveContext.sohStats.itemTimestamp[ITEM_KEY_BOSS] = time;
}
// Count any bottled item as a bottle
if (item >= RG_EMPTY_BOTTLE && item <= RG_BOTTLE_WITH_BIG_POE) {
if (gSaveContext.sohStats.timestamp[ITEM_BOTTLE] == 0) {
gSaveContext.sohStats.timestamp[ITEM_BOTTLE] = time;
if (gSaveContext.sohStats.itemTimestamp[ITEM_BOTTLE] == 0) {
gSaveContext.sohStats.itemTimestamp[ITEM_BOTTLE] = time;
}
return;
}
// Count any bombchu pack as bombchus
if (item >= RG_BOMBCHU_5 && item <= RG_BOMBCHU_DROP) {
if (gSaveContext.sohStats.timestamp[ITEM_BOMBCHU] = 0) {
gSaveContext.sohStats.timestamp[ITEM_BOMBCHU] = time;
if (gSaveContext.sohStats.itemTimestamp[ITEM_BOMBCHU] = 0) {
gSaveContext.sohStats.itemTimestamp[ITEM_BOMBCHU] = time;
}
return;
}
if (item == RG_MAGIC_SINGLE) {
gSaveContext.sohStats.timestamp[ITEM_SINGLE_MAGIC] = time;
gSaveContext.sohStats.itemTimestamp[ITEM_SINGLE_MAGIC] = time;
}
if (item == RG_DOUBLE_DEFENSE) {
gSaveContext.sohStats.timestamp[ITEM_DOUBLE_DEFENSE] = time;
gSaveContext.sohStats.itemTimestamp[ITEM_DOUBLE_DEFENSE] = time;
}
}
@@ -2525,7 +2525,7 @@ u16 Randomizer_Item_Give(PlayState* play, GetItemEntry giEntry) {
if (item == RG_GREG_RUPEE) {
Rupees_ChangeBy(1);
Flags_SetRandomizerInf(RAND_INF_GREG_FOUND);
gSaveContext.sohStats.timestamp[TIMESTAMP_FOUND_GREG] = GAMEPLAYSTAT_TOTAL_TIME;
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_FOUND_GREG] = GAMEPLAYSTAT_TOTAL_TIME;
return Return_Item_Entry(giEntry, RG_NONE);
}

View File

@@ -647,6 +647,33 @@ void Play_Init(GameState* thisx) {
gSaveContext.natureAmbienceId = play->sequenceCtx.natureAmbienceId;
func_8002DF18(play, GET_PLAYER(play));
AnimationContext_Update(play, &play->animationCtx);
if (gSaveContext.sohStats.sceneNum != gPlayState->sceneNum) {
u16 idx = gSaveContext.sohStats.tsIdx;
gSaveContext.sohStats.sceneTimestamps[idx].sceneTime = gSaveContext.sohStats.sceneTimer / 2;
gSaveContext.sohStats.sceneTimestamps[idx].roomTime = gSaveContext.sohStats.roomTimer / 2;
gSaveContext.sohStats.sceneTimestamps[idx].scene = gSaveContext.sohStats.sceneNum;
gSaveContext.sohStats.sceneTimestamps[idx].room = gSaveContext.sohStats.roomNum;
gSaveContext.sohStats.sceneTimestamps[idx].isRoom =
gPlayState->sceneNum == gSaveContext.sohStats.sceneTimestamps[idx].scene &&
gPlayState->roomCtx.curRoom.num != gSaveContext.sohStats.sceneTimestamps[idx].room;
gSaveContext.sohStats.tsIdx++;
gSaveContext.sohStats.sceneTimer = 0;
gSaveContext.sohStats.roomTimer = 0;
} else if (gSaveContext.sohStats.roomNum != gPlayState->roomCtx.curRoom.num) {
u16 idx = gSaveContext.sohStats.tsIdx;
gSaveContext.sohStats.sceneTimestamps[idx].roomTime = gSaveContext.sohStats.roomTimer / 2;
gSaveContext.sohStats.sceneTimestamps[idx].scene = gSaveContext.sohStats.sceneNum;
gSaveContext.sohStats.sceneTimestamps[idx].room = gSaveContext.sohStats.roomNum;
gSaveContext.sohStats.sceneTimestamps[idx].isRoom =
gPlayState->sceneNum == gSaveContext.sohStats.sceneTimestamps[idx].scene &&
gPlayState->roomCtx.curRoom.num != gSaveContext.sohStats.sceneTimestamps[idx].room;
gSaveContext.sohStats.tsIdx++;
gSaveContext.sohStats.roomTimer = 0;
}
gSaveContext.sohStats.sceneNum = gPlayState->sceneNum;
gSaveContext.sohStats.roomNum = gPlayState->roomCtx.curRoom.num;
gSaveContext.respawnFlag = 0;
#if 0
if (dREG(95) != 0) {
@@ -1085,6 +1112,8 @@ void Play_Update(PlayState* play) {
// Gameplay stat tracking
if (!gSaveContext.sohStats.gameComplete) {
gSaveContext.sohStats.playTimer++;
gSaveContext.sohStats.sceneTimer++;
gSaveContext.sohStats.roomTimer++;
if (CVarGetInteger("gMMBunnyHood", 0) && Player_GetMask(play) == PLAYER_MASK_BUNNY) {
gSaveContext.sohStats.count[COUNT_TIME_BUNNY_HOOD]++;

View File

@@ -641,11 +641,21 @@ void Room_Draw(PlayState* play, Room* room, u32 flags) {
void func_80097534(PlayState* play, RoomContext* roomCtx) {
roomCtx->prevRoom.num = -1;
roomCtx->prevRoom.segment = NULL;
func_80031B14(play, &play->actorCtx);
func_80031B14(play, &play->actorCtx); //kills all actors without room num set to -1
Actor_SpawnTransitionActors(play, &play->actorCtx);
Map_InitRoomData(play, roomCtx->curRoom.num);
if (!((play->sceneNum >= SCENE_SPOT00) && (play->sceneNum <= SCENE_SPOT20))) {
Map_SavePlayerInitialInfo(play);
}
Audio_SetEnvReverb(play->roomCtx.curRoom.echo);
u8 idx = gSaveContext.sohStats.tsIdx;
gSaveContext.sohStats.sceneTimestamps[idx].scene = gSaveContext.sohStats.sceneNum;
gSaveContext.sohStats.sceneTimestamps[idx].room = gSaveContext.sohStats.roomNum;
gSaveContext.sohStats.sceneTimestamps[idx].roomTime = gSaveContext.sohStats.roomTimer / 2;
gSaveContext.sohStats.sceneTimestamps[idx].isRoom =
gPlayState->sceneNum == gSaveContext.sohStats.sceneTimestamps[idx].scene &&
gPlayState->roomCtx.curRoom.num != gSaveContext.sohStats.sceneTimestamps[idx].room;
gSaveContext.sohStats.tsIdx++;
gSaveContext.sohStats.roomNum = roomCtx->curRoom.num;
gSaveContext.sohStats.roomTimer = 0;
}

View File

@@ -1330,7 +1330,7 @@ void BossDodongo_DeathCutscene(BossDodongo* this, PlayState* play) {
this->cameraAt.x = camera->at.x;
this->cameraAt.y = camera->at.y;
this->cameraAt.z = camera->at.z;
gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_KING_DODONGO] = GAMEPLAYSTAT_TOTAL_TIME;
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_KING_DODONGO] = GAMEPLAYSTAT_TOTAL_TIME;
break;
case 5:
tempSin = Math_SinS(this->actor.shape.rot.y - 0x1388) * 150.0f;

View File

@@ -893,7 +893,7 @@ void BossFd2_CollisionCheck(BossFd2* this, PlayState* play) {
Audio_QueueSeqCmd(0x1 << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0x100FF);
Audio_PlayActorSound2(&this->actor, NA_SE_EN_VALVAISA_DEAD);
Enemy_StartFinishingBlow(play, &this->actor);
gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_VOLVAGIA] = GAMEPLAYSTAT_TOTAL_TIME;
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_VOLVAGIA] = GAMEPLAYSTAT_TOTAL_TIME;
} else if (damage) {
BossFd2_SetupDamaged(this, play);
this->work[FD2_DAMAGE_FLASH_TIMER] = 10;

View File

@@ -2802,7 +2802,7 @@ void BossGanon_UpdateDamage(BossGanon* this, PlayState* play) {
func_80078914(&sZeroVec, NA_SE_EN_LAST_DAMAGE);
Audio_QueueSeqCmd(0x100100FF);
this->screenFlashTimer = 4;
gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_GANONDORF] = GAMEPLAYSTAT_TOTAL_TIME;
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GANONDORF] = GAMEPLAYSTAT_TOTAL_TIME;
} else {
Audio_PlayActorSound2(&this->actor, NA_SE_EN_GANON_DAMAGE2);
Audio_PlayActorSound2(&this->actor, NA_SE_EN_GANON_CUTBODY);

View File

@@ -1680,7 +1680,7 @@ void func_8090120C(BossGanon2* this, PlayState* play) {
(player->swordState != 0) && (player->heldItemAction == PLAYER_IA_SWORD_MASTER)) {
func_80064520(play, &play->csCtx);
gSaveContext.sohStats.gameComplete = true;
gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME;
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GANON] = GAMEPLAYSTAT_TOTAL_TIME;
this->unk_39E = Play_CreateSubCamera(play);
Play_ChangeCameraStatus(play, MAIN_CAM, CAM_STAT_WAIT);
Play_ChangeCameraStatus(play, this->unk_39E, CAM_STAT_ACTIVE);

View File

@@ -1280,7 +1280,7 @@ void BossGanondrof_CollisionCheck(BossGanondrof* this, PlayState* play) {
if ((s8)this->actor.colChkInfo.health <= 0) {
BossGanondrof_SetupDeath(this, play);
Enemy_StartFinishingBlow(play, &this->actor);
gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_PHANTOM_GANON] = GAMEPLAYSTAT_TOTAL_TIME;
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_PHANTOM_GANON] = GAMEPLAYSTAT_TOTAL_TIME;
return;
}
}

View File

@@ -1841,7 +1841,7 @@ void BossGoma_UpdateHit(BossGoma* this, PlayState* play) {
} else {
BossGoma_SetupDefeated(this, play);
Enemy_StartFinishingBlow(play, &this->actor);
gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_GOHMA] = GAMEPLAYSTAT_TOTAL_TIME;
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_GOHMA] = GAMEPLAYSTAT_TOTAL_TIME;
}
this->invincibilityFrames = 10;

View File

@@ -1786,7 +1786,7 @@ void BossMo_CoreCollisionCheck(BossMo* this, PlayState* play) {
if (((sMorphaTent1->csCamera == 0) && (sMorphaTent2 == NULL)) ||
((sMorphaTent1->csCamera == 0) && (sMorphaTent2 != NULL) && (sMorphaTent2->csCamera == 0))) {
Enemy_StartFinishingBlow(play, &this->actor);
gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_MORPHA] = GAMEPLAYSTAT_TOTAL_TIME;
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_MORPHA] = GAMEPLAYSTAT_TOTAL_TIME;
Audio_QueueSeqCmd(0x1 << 28 | SEQ_PLAYER_BGM_MAIN << 24 | 0x100FF);
this->csState = MO_DEATH_START;
sMorphaTent1->drawActor = false;

View File

@@ -2557,7 +2557,7 @@ void BossSst_HeadCollisionCheck(BossSst* this, PlayState* play) {
if (Actor_ApplyDamage(&this->actor) == 0) {
Enemy_StartFinishingBlow(play, &this->actor);
BossSst_HeadSetupDeath(this, play);
gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_BONGO_BONGO] = GAMEPLAYSTAT_TOTAL_TIME;
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_BONGO_BONGO] = GAMEPLAYSTAT_TOTAL_TIME;
} else {
BossSst_HeadSetupDamage(this);
}

View File

@@ -5285,7 +5285,7 @@ void BossTw_TwinrovaDamage(BossTw* this, PlayState* play, u8 damage) {
BossTw_TwinrovaSetupDeathCS(this, play);
Enemy_StartFinishingBlow(play, &this->actor);
Audio_PlayActorSound2(&this->actor, NA_SE_EN_TWINROBA_YOUNG_DEAD);
gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_TWINROVA] = GAMEPLAYSTAT_TOTAL_TIME;
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_TWINROVA] = GAMEPLAYSTAT_TOTAL_TIME;
return;
}

View File

@@ -1394,7 +1394,7 @@ void BossVa_BodyPhase4(BossVa* this, PlayState* play) {
if (sFightPhase >= PHASE_DEATH) {
BossVa_SetupBodyDeath(this, play);
Enemy_StartFinishingBlow(play, &this->actor);
gSaveContext.sohStats.timestamp[TIMESTAMP_DEFEAT_BARINADE] = GAMEPLAYSTAT_TOTAL_TIME;
gSaveContext.sohStats.itemTimestamp[TIMESTAMP_DEFEAT_BARINADE] = GAMEPLAYSTAT_TOTAL_TIME;
return;
}
this->actor.speedXZ = -10.0f;