oot/src/code/z_horse.c

339 lines
16 KiB
C

#include "array_count.h"
#include "terminal.h"
#include "z_lib.h"
#include "printf.h"
#include "regs.h"
#include "translation.h"
#include "horse.h"
#include "play_state.h"
#include "player.h"
#include "save.h"
#include "overlays/actors/ovl_En_Horse/z_en_horse.h"
/**
* Tests if the player horse can be spawned
*
* @param sceneId the checked scene
* @return true if the player horse can appear in the scene, else false
*/
s32 Horse_CanSpawn(s32 sceneId) {
s32 validSceneIds[] = { SCENE_HYRULE_FIELD, SCENE_LAKE_HYLIA, SCENE_GERUDO_VALLEY, SCENE_GERUDOS_FORTRESS,
SCENE_LON_LON_RANCH };
s32 i;
for (i = 0; i < ARRAY_COUNT(validSceneIds); i++) {
if (sceneId == validSceneIds[i]) {
return true;
}
}
return false;
}
/**
* Sets horseData to a neutral spawn in Hyrule Field
*/
void Horse_ResetHorseData(PlayState* play) {
gSaveContext.save.info.horseData.sceneId = SCENE_HYRULE_FIELD;
gSaveContext.save.info.horseData.pos.x = -1840;
gSaveContext.save.info.horseData.pos.y = 72;
gSaveContext.save.info.horseData.pos.z = 5497;
gSaveContext.save.info.horseData.angle = -0x6AD9;
}
/**
* Forces the player horse to spawn in a safe spot if the current spawn is in Lake Hylia
* This prevents the horse from spawning underwater after obtaining the Water Medallion
*/
void Horse_FixLakeHyliaPosition(PlayState* play) {
if (gSaveContext.save.info.horseData.sceneId == SCENE_LAKE_HYLIA) {
gSaveContext.save.info.horseData.sceneId = SCENE_LAKE_HYLIA;
gSaveContext.save.info.horseData.pos.x = -2065;
gSaveContext.save.info.horseData.pos.y = -863;
gSaveContext.save.info.horseData.pos.z = 1839;
gSaveContext.save.info.horseData.angle = 0;
}
}
typedef struct HorseSpawn {
/* 0x00 */ s16 sceneId;
/* 0x02 */ Vec3s pos;
/* 0x08 */ s16 angle;
/* 0x0A */ s16 type;
} HorseSpawn;
void Horse_SetupInGameplay(PlayState* play, Player* player) {
s32 i;
HorseSpawn horseSpawns[] = {
{ SCENE_HYRULE_FIELD, -460, 100, 6640, 0, HORSE_PTYPE_INACTIVE },
{ SCENE_LAKE_HYLIA, -1929, -1025, 768, 0, HORSE_PTYPE_INACTIVE },
{ SCENE_GERUDO_VALLEY, 2566, -259, 767, 0, HORSE_PTYPE_INACTIVE },
{ SCENE_GERUDOS_FORTRESS, -328, 10, 953, 0, HORSE_PTYPE_INACTIVE },
{ SCENE_LON_LON_RANCH, 928, 0, -2280, 0, HORSE_PTYPE_INACTIVE },
};
if (R_EXITED_SCENE_RIDING_HORSE &&
(Flags_GetEventChkInf(EVENTCHKINF_EPONA_OBTAINED) || R_DEBUG_FORCE_EPONA_OBTAINED)) {
// Player entering scene on top of horse
player->rideActor =
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_HORSE, player->actor.world.pos.x, player->actor.world.pos.y,
player->actor.world.pos.z, player->actor.shape.rot.x, player->actor.shape.rot.y,
player->actor.shape.rot.z, HORSE_PTYPE_PLAYER_SPAWNED_RIDING);
ASSERT(player->rideActor != NULL, "player->ride.actor != NULL", "../z_horse.c", 343);
Actor_MountHorse(play, player, player->rideActor);
Actor_RequestHorseCameraSetting(play, player);
gSaveContext.save.info.horseData.sceneId = play->sceneId;
if (play->sceneId == SCENE_GERUDOS_FORTRESS) {
player->rideActor->room = -1;
}
} else if ((play->sceneId == SCENE_GERUDOS_FORTRESS) && (gSaveContext.minigameState == 3)) {
// Completed Horseback Archery Minigame
Actor* horseActor;
gSaveContext.minigameState = 0;
horseActor =
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_HORSE, 3586.0f, 1413.0f, -402.0f, 0, 0x4000, 0, HORSE_PTYPE_1);
horseActor->room = -1;
} else if ((gSaveContext.save.entranceIndex == ENTR_LON_LON_RANCH_7) &&
GET_EVENTCHKINF(EVENTCHKINF_EPONA_OBTAINED)) {
// Completed Horse Race
Actor* horseActor =
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_HORSE, -25.0f, 0.0f, -1600.0f, 0, -0x4000, 0, HORSE_PTYPE_1);
ASSERT(horseActor != NULL, "horse_actor != NULL", "../z_horse.c", 389);
} else if ((play->sceneId == gSaveContext.save.info.horseData.sceneId) &&
(Flags_GetEventChkInf(EVENTCHKINF_EPONA_OBTAINED) || R_DEBUG_FORCE_EPONA_OBTAINED)) {
// Player enters a scene where the horse was left previously
PRINTF(T("馬存在によるセット %d %d %d\n", "Set by existence of horse %d %d %d\n"),
gSaveContext.save.info.horseData.sceneId, Flags_GetEventChkInf(EVENTCHKINF_EPONA_OBTAINED),
R_DEBUG_FORCE_EPONA_OBTAINED);
if (Horse_CanSpawn(gSaveContext.save.info.horseData.sceneId)) {
Actor* horseActor =
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_HORSE, gSaveContext.save.info.horseData.pos.x,
gSaveContext.save.info.horseData.pos.y, gSaveContext.save.info.horseData.pos.z, 0,
gSaveContext.save.info.horseData.angle, 0, HORSE_PTYPE_1);
ASSERT(horseActor != NULL, "horse_actor != NULL", "../z_horse.c", 414);
if (play->sceneId == SCENE_GERUDOS_FORTRESS) {
horseActor->room = -1;
}
} else {
PRINTF_COLOR_ERROR();
PRINTF(
T("Horse_SetNormal():%d セットスポットまずいです。\n", "Horse_SetNormal():%d set spot is no good.\n"),
gSaveContext.save.info.horseData.sceneId);
PRINTF_RST();
Horse_ResetHorseData(play);
}
} else if ((play->sceneId == SCENE_LON_LON_RANCH) &&
!(Flags_GetEventChkInf(EVENTCHKINF_EPONA_OBTAINED) || R_DEBUG_FORCE_EPONA_OBTAINED)) {
// Player spawns in Lon-Lon Ranch without owning Epona
Actor* horseActor =
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_HORSE, 0.0f, 0.0f, -500.0f, 0, 0, 0, HORSE_PTYPE_1);
ASSERT(horseActor != NULL, "horse_actor != NULL", "../z_horse.c", 443);
} else if (Flags_GetEventChkInf(EVENTCHKINF_EPONA_OBTAINED) || R_DEBUG_FORCE_EPONA_OBTAINED) {
// Player owns Epona, but isn't riding her and the current scene is not the same as the horse's last location.
for (i = 0; i < ARRAY_COUNT(horseSpawns); i++) {
HorseSpawn* horseSpawn = &horseSpawns[i];
if (horseSpawn->sceneId == play->sceneId) {
Actor* horseActor =
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_HORSE, horseSpawn->pos.x, horseSpawn->pos.y,
horseSpawn->pos.z, 0, horseSpawn->angle, 0, horseSpawn->type);
ASSERT(horseActor != NULL, "horse_actor != NULL", "../z_horse.c", 466);
if (play->sceneId == SCENE_GERUDOS_FORTRESS) {
horseActor->room = -1;
}
break;
}
}
} else if (!(Flags_GetEventChkInf(EVENTCHKINF_EPONA_OBTAINED) || R_DEBUG_FORCE_EPONA_OBTAINED) &&
(play->sceneId == SCENE_LON_LON_BUILDINGS) && !IS_DAY) {
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_HORSE, 0.0f, 0.0f, -60.0f, 0, 0x7360, 0, HORSE_PTYPE_1);
}
}
typedef struct HorseCutsceneSpawn {
/* 0x00 */ s16 sceneId;
/* 0x04 */ s32 cutsceneIndex;
/* 0x08 */ Vec3s pos;
/* 0x0E */ s16 angle;
/* 0x10 */ s16 type;
} HorseCutsceneSpawn;
void Horse_SetupInCutscene(PlayState* play, Player* player) {
static HorseCutsceneSpawn horseSpawns[] = {
{ SCENE_GERUDOS_FORTRESS, CS_INDEX_0, { 3600, 1413, 360 }, 0x8001, HORSE_PTYPE_HORSEBACK_ARCHERY },
{ SCENE_LON_LON_RANCH, CS_INDEX_0, { -250, 1, -1580 }, 0x4000, HORSE_PTYPE_6 }, // Horse Race
{ SCENE_LON_LON_RANCH, CS_INDEX_1, { 0, 0, 0 }, 0x0000, HORSE_PTYPE_5 }, // Learned Epona's Song
{ SCENE_LON_LON_RANCH, CS_INDEX_5, { 0, 0, 0 }, 0x0000, HORSE_PTYPE_7 }, // Credits
{ SCENE_HYRULE_FIELD, CS_INDEX_3, { -2961, 313, 7700 }, 0x0000, HORSE_PTYPE_7 }, // Title Screen
{ SCENE_HYRULE_FIELD, CS_INDEX_4, { -1900, 313, 7015 }, 0x0000, HORSE_PTYPE_7 },
{ SCENE_HYRULE_FIELD, CS_INDEX_5, { -4043, 313, 6933 }, 0x0000, HORSE_PTYPE_7 }, // Credits
{ SCENE_HYRULE_FIELD, CS_INDEX_6, { -4043, 313, 6933 }, 0x0000, HORSE_PTYPE_7 }, // Unused. Hopping Lon Lon
// Ranch North Gate
};
s32 pad;
s32 i;
if ((gSaveContext.save.entranceIndex == ENTR_HYRULE_FIELD_11 ||
gSaveContext.save.entranceIndex == ENTR_HYRULE_FIELD_12 ||
gSaveContext.save.entranceIndex == ENTR_HYRULE_FIELD_13 ||
gSaveContext.save.entranceIndex == ENTR_HYRULE_FIELD_15) &&
(gSaveContext.respawnFlag == 0)) {
// Epona hopping over one of the Lon Lon Ranch fences
Vec3s spawnPos;
Vec3s spawnPositions[] = {
// Note: The east and west positions are paired with the wrong entranceIndex. However, the subsequent
// cutscene repositions the horse will override these coordinates while the horse is still obstructed, so no
// visual glitch occurs.
{ -2961, 313, 7700 }, // South
{ -1900, 313, 7015 }, // East
{ -4043, 313, 6933 }, // West
{ -2313, 313, 5990 }, // North-East
};
// South of Lon Lon player spawn
if (gSaveContext.save.entranceIndex == ENTR_HYRULE_FIELD_11) {
spawnPos = spawnPositions[0];
// West of Lon Lon player spawn
} else if (gSaveContext.save.entranceIndex == ENTR_HYRULE_FIELD_12) {
spawnPos = spawnPositions[1];
// East of Lon Lon player spawn
} else if (gSaveContext.save.entranceIndex == ENTR_HYRULE_FIELD_13) {
spawnPos = spawnPositions[2];
// Lon Lon exit player spawn
} else {
spawnPos = spawnPositions[3];
}
player->rideActor = Actor_Spawn(&play->actorCtx, play, ACTOR_EN_HORSE, spawnPos.x, spawnPos.y, spawnPos.z, 0,
player->actor.world.rot.y, 0, HORSE_PTYPE_7);
ASSERT(player->rideActor != NULL, "player->ride.actor != NULL", "../z_horse.c", 561);
Actor_MountHorse(play, player, player->rideActor);
Actor_RequestHorseCameraSetting(play, player);
gSaveContext.save.info.horseData.sceneId = play->sceneId;
} else if ((play->sceneId == SCENE_LON_LON_RANCH) &&
(GET_EVENTINF_INGO_RACE_STATE() == INGO_RACE_STATE_TRAPPED_WIN_EPONA) &&
!(Flags_GetEventChkInf(EVENTCHKINF_EPONA_OBTAINED) || R_DEBUG_FORCE_EPONA_OBTAINED)) {
player->rideActor =
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_HORSE, 894.0f, 0.0f, -2084.0f, 0, -0x7FFF, 0, HORSE_PTYPE_5);
ASSERT(player->rideActor != NULL, "player->ride.actor != NULL", "../z_horse.c", 582);
Actor_MountHorse(play, player, player->rideActor);
Actor_RequestHorseCameraSetting(play, player);
gSaveContext.save.info.horseData.sceneId = play->sceneId;
if (play->sceneId == SCENE_GERUDOS_FORTRESS) {
player->rideActor->room = -1;
}
} else {
for (i = 0; i < ARRAY_COUNT(horseSpawns); i++) {
if ((play->sceneId == horseSpawns[i].sceneId) &&
(((void)0, gSaveContext.save.cutsceneIndex) == horseSpawns[i].cutsceneIndex)) {
if (horseSpawns[i].type == HORSE_PTYPE_7) {
if ((play->sceneId == SCENE_LON_LON_RANCH) &&
(((void)0, gSaveContext.save.cutsceneIndex) == CS_INDEX_1)) {
horseSpawns[i].pos.x = player->actor.world.pos.x;
horseSpawns[i].pos.y = player->actor.world.pos.y;
horseSpawns[i].pos.z = player->actor.world.pos.z;
}
player->rideActor =
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_HORSE, horseSpawns[i].pos.x, horseSpawns[i].pos.y,
horseSpawns[i].pos.z, 0, player->actor.world.rot.y, 0, horseSpawns[i].type);
ASSERT(player->rideActor != NULL, "player->ride.actor != NULL", "../z_horse.c", 628);
Actor_MountHorse(play, player, player->rideActor);
Actor_RequestHorseCameraSetting(play, player);
} else if ((horseSpawns[i].type == HORSE_PTYPE_5) || (horseSpawns[i].type == HORSE_PTYPE_6) ||
(horseSpawns[i].type == HORSE_PTYPE_HORSEBACK_ARCHERY)) {
Vec3f eye;
s32 paramFlag = 0;
if (GET_EVENTINF_INGO_RACE_HORSETYPE() != HORSE_EPONA && horseSpawns[i].type == HORSE_PTYPE_6) {
// HORSE_HNI
paramFlag = 0x8000;
}
player->rideActor =
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_HORSE, horseSpawns[i].pos.x, horseSpawns[i].pos.y,
horseSpawns[i].pos.z, 0, horseSpawns[i].angle, 0, horseSpawns[i].type | paramFlag);
ASSERT(player->rideActor != NULL, "player->ride.actor != NULL", "../z_horse.c", 667);
player->actor.world.pos.x = horseSpawns[i].pos.x;
player->actor.world.pos.y = horseSpawns[i].pos.y;
player->actor.world.pos.z = horseSpawns[i].pos.z;
player->actor.shape.rot.x = player->actor.shape.rot.z = 0;
player->actor.shape.rot.y = horseSpawns[i].angle;
Actor_MountHorse(play, player, player->rideActor);
Actor_RequestHorseCameraSetting(play, player);
eye.x = player->actor.world.pos.x - 200.0f;
eye.y = player->actor.world.pos.y + 100.0f;
eye.z = player->actor.world.pos.z;
Play_SetCameraAtEye(play, play->activeCamId, &player->actor.world.pos, &eye);
} else {
Actor_Spawn(&play->actorCtx, play, ACTOR_EN_HORSE, horseSpawns[i].pos.x, horseSpawns[i].pos.y,
horseSpawns[i].pos.z, 0, horseSpawns[i].angle, 0, horseSpawns[i].type);
}
break;
}
}
}
}
/**
* Initializes the player's horse, but only if required.
*
* This function should be called during `Play_Init`.
*/
void Horse_InitPlayerHorse(PlayState* play, Player* player) {
if (LINK_IS_ADULT) {
if (!Horse_CanSpawn(gSaveContext.save.info.horseData.sceneId)) {
PRINTF_COLOR_ERROR();
PRINTF(
T("Horse_Set_Check():%d セットスポットまずいです。\n", "Horse_Set_Check():%d set spot is no good.\n"),
gSaveContext.save.info.horseData.sceneId);
PRINTF_RST();
Horse_ResetHorseData(play);
}
if (Horse_CanSpawn(play->sceneId)) {
if (IS_CUTSCENE_LAYER ||
// has hopped over one of the Lon-Lon Ranch fences
((gSaveContext.save.entranceIndex == ENTR_HYRULE_FIELD_11 ||
gSaveContext.save.entranceIndex == ENTR_HYRULE_FIELD_12 ||
gSaveContext.save.entranceIndex == ENTR_HYRULE_FIELD_13 ||
gSaveContext.save.entranceIndex == ENTR_HYRULE_FIELD_15) &&
(gSaveContext.respawnFlag == 0)) ||
// trapped in Lon Lon Ranch
((play->sceneId == SCENE_LON_LON_RANCH) &&
(GET_EVENTINF_INGO_RACE_STATE() == INGO_RACE_STATE_TRAPPED_WIN_EPONA) &&
!(Flags_GetEventChkInf(EVENTCHKINF_EPONA_OBTAINED) || R_DEBUG_FORCE_EPONA_OBTAINED))) {
Horse_SetupInCutscene(play, player);
} else {
Horse_SetupInGameplay(play, player);
}
}
}
}
void Horse_RotateToPoint(Actor* actor, Vec3f* pos, s16 turnAmount) {
s16 x = Math_Vec3f_Yaw(&actor->world.pos, pos) - actor->world.rot.y;
if (x > turnAmount) {
actor->world.rot.y += turnAmount;
} else if (x < -turnAmount) {
actor->world.rot.y -= turnAmount;
} else {
actor->world.rot.y += x;
}
actor->shape.rot.y = actor->world.rot.y;
}