diff --git a/CMakeLists.txt b/CMakeLists.txt index 82450a2..7b72f45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,8 +64,12 @@ add_custom_command(OUTPUT ${CMAKE_SOURCE_DIR}/patches/patches.elf ) # Recompile patches elf into patches.c -add_custom_command(OUTPUT ${CMAKE_SOURCE_DIR}/RecompiledPatches/patches.c - COMMAND RecompPort patches.toml +add_custom_command(OUTPUT + ${CMAKE_SOURCE_DIR}/RecompiledPatches/patches.c + ${CMAKE_SOURCE_DIR}/RecompiledPatches/recomp_overlays.inl + ${CMAKE_SOURCE_DIR}/RecompiledPatches/funcs.h + ${CMAKE_SOURCE_DIR}/src/recomp/patch_loading.cpp + COMMAND RecompPort patches.toml && touch ${CMAKE_SOURCE_DIR}/src/recomp/patch_loading.cpp WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} DEPENDS ${CMAKE_SOURCE_DIR}/patches/patches.elf ) diff --git a/patches/camera_patches.c b/patches/camera_patches.c new file mode 100644 index 0000000..de30df9 --- /dev/null +++ b/patches/camera_patches.c @@ -0,0 +1,792 @@ +#include "patches.h" +#include "input.h" +#include "z64quake.h" + +RecompCameraMode recomp_camera_mode = RECOMP_CAMERA_DUALANALOG; + +VecGeo recomp_camera_pos = { .r = 66.0f, .pitch = 0, .yaw = 0 }; + +float recomp_camera_yaw_vel = 0.0f; +float recomp_camera_pitch_vel = 0.0f; + + +float recomp_deadzone = 0.2f; +float recomp_camera_x_sensitivity = 1500.0f; +float recomp_camera_y_sensitivity = -500.0f; +// float recomp_camera_acceleration = 500.0f; + +void update_recomp_camera_params(Camera* camera) { + recomp_camera_pos.yaw = Math_Atan2S(-camera->at.x + camera->eye.x, -camera->at.z + camera->eye.z); + // recomp_printf("Camera at: %.2f %.2f %.2f\n" + // " eye: %.2f %.2f %.2f\n" + // " yaw: %d", + // camera->at.x, camera->at.y, camera->at.z, + // camera->eye.x, camera->eye.y, camera->eye.z, + // recomp_camera_pos.yaw); + + float input_x, input_y; + recomp_get_camera_inputs(&input_x, &input_y); + + // Math_StepToF(&recomp_camera_yaw_vel, input_x * recomp_camera_x_sensitivity, recomp_camera_acceleration); + // Math_StepToF(&recomp_camera_pitch_vel, input_y * recomp_camera_y_sensitivity, recomp_camera_acceleration); + if (fabsf(input_x) > recomp_deadzone) { + recomp_camera_yaw_vel = input_x * recomp_camera_x_sensitivity; + } + else { + recomp_camera_yaw_vel = 0; + } + + if (fabsf(input_y) > recomp_deadzone) { + recomp_camera_pitch_vel = input_y * recomp_camera_y_sensitivity; + } + else { + recomp_camera_pitch_vel = 0; + } + + recomp_camera_pos.pitch += recomp_camera_pitch_vel; + recomp_camera_pos.yaw += recomp_camera_yaw_vel; +} + +extern s32 sUpdateCameraDirection; +static s32 sIsFalse = false; +extern s32 sCameraInitSceneTimer; + +extern s16 sCameraNextUID; +extern s32 sCameraInterfaceFlags; +extern s32 sCameraHudVisibility; +extern s32 sCameraLetterboxSize; +extern s32 sCameraNegOne1; + +#define CAM_DATA_IS_BG (1 << 12) // if not set, then cam data is for actor cutscenes + +typedef s32 (*CameraUpdateFunc)(Camera*); + +typedef struct { + /* 0x0 */ s16 val; + /* 0x2 */ s16 param; +} CameraModeValue; // size = 0x4 + +typedef struct { + /* 0x0 */ s16 funcId; + /* 0x2 */ s16 numValues; + /* 0x4 */ CameraModeValue* values; +} CameraMode; // size = 0x8 + +typedef struct { + /* 0x0 */ u32 validModes; + /* 0x4 */ u32 flags; + /* 0x8 */ CameraMode* cameraModes; +} CameraSetting; + +extern CameraUpdateFunc sCameraUpdateHandlers[]; +extern CameraSetting sCameraSettings[]; + +Vec3f Camera_CalcUpVec(s16 pitch, s16 yaw, s16 roll); +f32 Camera_fabsf(f32 f); +s32 Camera_GetBgCamIndex(Camera* camera, s32* bgId, CollisionPoly* poly); +f32 Camera_GetFocalActorHeight(Camera* camera); +f32 Camera_GetRunSpeedLimit(Camera* camera); +s32 Camera_IsDekuHovering(Camera* camera); +s32 Camera_IsMountedOnHorse(Camera* camera); +s32 Camera_IsUnderwaterAsZora(Camera* camera); +s32 Camera_IsUsingZoraFins(Camera* camera); +void Camera_UpdateInterface(s32 interfaceFlags); +f32 Camera_Vec3fMagnitude(Vec3f* vec); +s32 func_800CB7CC(Camera* camera); +s32 func_800CB854(Camera* camera); + +Vec3s Camera_Update(Camera* camera) { + Vec3f viewAt; + Vec3f viewEye; + Vec3f viewUp; + Vec3f focalActorPos; + s32 bgId; + s32 sp98; + s32 changeCamSceneDataType; + CollisionPoly* sp90; + CollisionPoly* sp8C; + f32 runSpeedLimit; + f32 speed; + f32 viewFov; + DynaPolyActor* meshActor; + PosRot focalActorPosRot; + ShakeInfo camShake; + Actor* focalActor = camera->focalActor; + VecGeo sp3C; + s16 bgCamIndex; + s16 numQuakesApplied; + f32 focalActorFloorHeight; + + // Camera of status CUT only updates to this point + if (camera->status == CAM_STATUS_CUT) { + return camera->inputDir; + } + + sUpdateCameraDirection = false; + sIsFalse = false; + + if (camera->play->view.unk164 == 0) { + if (camera->focalActor != NULL) { + // Updates camera info on the actor it's tracking + + if (camera->focalActor == &GET_PLAYER(camera->play)->actor) { + focalActorPosRot = Actor_GetWorldPosShapeRot(camera->focalActor); + } else { + focalActorPosRot = Actor_GetWorld(camera->focalActor); + } + camera->unk_0F0.x = focalActorPosRot.pos.x - camera->focalActorPosRot.pos.x; + camera->unk_0F0.y = focalActorPosRot.pos.y - camera->focalActorPosRot.pos.y; + camera->unk_0F0.z = focalActorPosRot.pos.z - camera->focalActorPosRot.pos.z; + + // bg related to tracked actor + sp98 = 0; + if (Camera_IsMountedOnHorse(camera)) { + if (((Player*)focalActor)->rideActor->floorPoly != NULL) { + sp90 = ((Player*)focalActor)->rideActor->floorPoly; + camera->bgId = ((Player*)focalActor)->rideActor->floorBgId; + camera->focalActorFloorHeight = ((Player*)focalActor)->rideActor->floorHeight; + sp98 = 3; + } + } else if (func_800CB7CC(camera)) { + if (camera->focalActor->floorPoly != NULL) { + sp90 = camera->focalActor->floorPoly; + camera->bgId = camera->focalActor->floorBgId; + camera->focalActorFloorHeight = camera->focalActor->floorHeight; + sp98 = 1; + } + } else { + focalActorPos = focalActorPosRot.pos; + focalActorPos.y += Camera_GetFocalActorHeight(camera); + focalActorFloorHeight = BgCheck_EntityRaycastFloor5_3(camera->play, &camera->play->colCtx, &sp90, &bgId, + camera->focalActor, &focalActorPos); + if (focalActorFloorHeight != BGCHECK_Y_MIN) { + camera->bgId = bgId; + camera->focalActorFloorHeight = focalActorFloorHeight; + sp98 = 2; + } + } + + if ((sp98 != 0) && (Camera_fabsf(camera->focalActorPosRot.pos.y - camera->focalActorFloorHeight) < 11.0f)) { + meshActor = DynaPoly_GetActor(&camera->play->colCtx, camera->bgId); + if (meshActor != NULL) { + camera->floorNorm.x = COLPOLY_GET_NORMAL(sp90->normal.x); + camera->floorNorm.y = COLPOLY_GET_NORMAL(sp90->normal.y); + camera->floorNorm.z = COLPOLY_GET_NORMAL(sp90->normal.z); + camera->unk_0F0.x -= meshActor->actor.world.pos.x - camera->meshActorPos.x; + camera->unk_0F0.y -= meshActor->actor.world.pos.y - camera->meshActorPos.y; + camera->unk_0F0.z -= meshActor->actor.world.pos.z - camera->meshActorPos.z; + camera->meshActorPos = meshActor->actor.world.pos; + } + } + + // Set camera speed + runSpeedLimit = Camera_GetRunSpeedLimit(camera) * 1.5f; + speed = Camera_Vec3fMagnitude(&camera->unk_0F0); + camera->xzSpeed = OLib_ClampMaxDist(speed, runSpeedLimit); + camera->speedRatio = OLib_ClampMaxDist(speed / runSpeedLimit, 1.8f); + camera->focalActorPosRot = focalActorPosRot; + + if (camera->camId == CAM_ID_MAIN) { + Camera_UpdateWater(camera); + Camera_UpdateHotRoom(camera); + Camera_EarthquakeDay3(camera); + Camera_SetSwordDistortion(camera); + } + + /** + * This section is about updating the camera setting based on the camera scene data + * + */ + + // If doorTimer1 is active, set CAM_STATE_10 which suppresses bg camera scene data from being read + if (camera->doorTimer1 != 0) { + Camera_SetStateFlag(camera, CAM_STATE_10); + } else if (!(camera->stateFlags & CAM_STATE_2)) { + camera->nextCamSceneDataId = -1; + } + + changeCamSceneDataType = 0; // default to no change in the cam scene data + bgId = camera->bgId; + + // Sets the next cam scene data Index based on the bg surface + if ((camera->stateFlags & CAM_STATE_0) && (camera->stateFlags & CAM_STATE_2) && + !(camera->stateFlags & CAM_STATE_10) && + (!(camera->stateFlags & CAM_STATE_9) || Camera_IsUnderwaterAsZora(camera)) && + !(camera->stateFlags & CAM_STATE_15) && !Camera_IsMountedOnHorse(camera) && + !Camera_RequestGiantsMaskSetting(camera) && !Camera_IsDekuHovering(camera) && (sp98 != 0)) { + + bgCamIndex = Camera_GetBgCamIndex(camera, &bgId, sp90); + if ((bgCamIndex != -1) && (camera->bgId == BGCHECK_SCENE)) { + if (Camera_IsUsingZoraFins(camera) == 0) { + camera->nextCamSceneDataId = bgCamIndex | CAM_DATA_IS_BG; + } + } + + focalActorPos = focalActorPosRot.pos; + focalActorPos.y += Camera_GetFocalActorHeight(camera); + focalActorFloorHeight = + BgCheck_CameraRaycastFloor2(&camera->play->colCtx, &sp8C, &bgId, &focalActorPos); + + if ((focalActorFloorHeight != BGCHECK_Y_MIN) && (sp8C != sp90) && (bgId == BGCHECK_SCENE) && + ((camera->focalActorFloorHeight - 2.0f) < focalActorFloorHeight)) { + bgCamIndex = Camera_GetBgCamIndex(camera, &bgId, sp8C); + if ((bgCamIndex != -1) && (bgId == BGCHECK_SCENE)) { + camera->nextCamSceneDataId = bgCamIndex | CAM_DATA_IS_BG; + changeCamSceneDataType = 1; // change cam scene data based on the bg cam data + } + } + } + + if (camera->doorTimer1 != 0) { + camera->doorTimer1--; + if (camera->doorTimer1 == 0) { + Camera_UnsetStateFlag(camera, CAM_STATE_10); + changeCamSceneDataType = 5; // change cam scene data based on the cutscene cam data + } + } + + if (((camera->camId == CAM_ID_MAIN) || (camera->stateFlags & CAM_STATE_6)) && + ((camera->bgId == BGCHECK_SCENE) || ((bgId == BGCHECK_SCENE) && (changeCamSceneDataType != 0))) && + (camera->nextCamSceneDataId != -1) && (camera->doorTimer1 == 0) && + ((Camera_fabsf(camera->focalActorPosRot.pos.y - camera->focalActorFloorHeight) < 11.0f) || + (changeCamSceneDataType != 0)) && + (!(camera->stateFlags & CAM_STATE_9) || Camera_IsUnderwaterAsZora(camera))) { + + Camera_ChangeActorCsCamIndex(camera, camera->nextCamSceneDataId); + camera->nextCamSceneDataId = -1; + if (camera->doorTimer2 != 0) { + camera->doorTimer1 = camera->doorTimer2; + camera->doorTimer2 = 0; + } + } + } + + // Camera of status WAIT only updates to this point + if (camera->status == CAM_STATUS_WAIT) { + return camera->inputDir; + } + + camera->behaviorFlags = 0; + Camera_UnsetStateFlag(camera, CAM_STATE_10 | CAM_STATE_DISABLE_MODE_CHANGE); + Camera_SetStateFlag(camera, CAM_STATE_4); + } + + // Call the camera update function + recomp_printf( + "Camera:\n" + " setting: %d\n" + " mode: %d\n" + " funcId: %d\n", camera->setting, camera->mode, sCameraSettings[camera->setting].cameraModes[camera->mode].funcId); + sCameraUpdateHandlers[sCameraSettings[camera->setting].cameraModes[camera->mode].funcId](camera); + + // @recomp + update_recomp_camera_params(camera); + + // Update the interface + if (sCameraInitSceneTimer != 0) { + sCameraInitSceneTimer--; + } + if (camera->status == CAM_STATUS_ACTIVE) { + if (((sCameraInitSceneTimer != 0) || func_800CB854(camera)) && (camera->camId == CAM_ID_MAIN)) { + // Surpresses the interface for the first few frames of a scene + sCameraInterfaceFlags = CAM_INTERFACE_FLAGS(CAM_LETTERBOX_LARGE, CAM_HUD_VISIBILITY_NONE_ALT, 0); + Camera_UpdateInterface(sCameraInterfaceFlags); + } else if ((camera->play->transitionMode != TRANS_MODE_OFF) && (camera->camId != CAM_ID_MAIN)) { + sCameraInterfaceFlags = CAM_INTERFACE_FLAGS(CAM_LETTERBOX_IGNORE, CAM_HUD_VISIBILITY_IGNORE, 0); + Camera_UpdateInterface(sCameraInterfaceFlags); + } else { + Camera_UpdateInterface(sCameraInterfaceFlags); + } + } + + // Camera of status UNK3 only updates to this point + if (camera->status == CAM_STATUS_UNK3) { + return camera->inputDir; + } + + /** + * This section is about updating view structs from the active camera, + * which view uses to calculate the viewing/projection matrices + */ + numQuakesApplied = Quake_Update(camera, &camShake); + + bgId = numQuakesApplied; // required to match + + if (numQuakesApplied != 0) { + viewAt.x = camera->at.x + camShake.atOffset.x; + viewAt.y = camera->at.y + camShake.atOffset.y; + viewAt.z = camera->at.z + camShake.atOffset.z; + viewEye.x = camera->eye.x + camShake.eyeOffset.x; + viewEye.y = camera->eye.y + camShake.eyeOffset.y; + viewEye.z = camera->eye.z + camShake.eyeOffset.z; + sp3C = OLib_Vec3fDiffToVecGeo(&viewEye, &viewAt); + viewUp = Camera_CalcUpVec(sp3C.pitch, sp3C.yaw, camera->roll + camShake.upRollOffset); + viewFov = camera->fov + CAM_BINANG_TO_DEG(camShake.fovOffset); + } else if (sIsFalse) { + //! condition is impossible to achieve + viewAt = camera->at; + viewEye = camera->eye; + sp3C = OLib_Vec3fDiffToVecGeo(&viewEye, &viewAt); + viewUp = camera->up; + viewFov = camera->fov; + } else { + viewAt = camera->at; + viewEye = camera->eye; + sp3C = OLib_Vec3fDiffToVecGeo(&viewEye, &viewAt); + viewUp = Camera_CalcUpVec(sp3C.pitch, sp3C.yaw, camera->roll); + viewFov = camera->fov; + } + + // set view up + if (camera->viewFlags & CAM_VIEW_UP) { + camera->viewFlags &= ~CAM_VIEW_UP; + viewUp = camera->up; + } else { + camera->up = viewUp; + } + + camera->quakeOffset = camShake.eyeOffset; + View_SetScale(&camera->play->view, (OREG(67) * 0.01f) + 1.0f); + camera->play->view.fovy = viewFov; + View_LookAt(&camera->play->view, &viewEye, &viewAt, &viewUp); + camera->camDir.x = sp3C.pitch; + camera->camDir.y = sp3C.yaw; + camera->camDir.z = 0; + + if (!sUpdateCameraDirection) { + camera->inputDir.x = sp3C.pitch; + camera->inputDir.y = sp3C.yaw; + camera->inputDir.z = 0; + } + + return camera->inputDir; +} + +extern SwingAnimation D_801EDC30[4]; +s32 Camera_CalcAtDefault(Camera* camera, VecGeo* eyeAtDir, f32 yOffset, s16 calcSlope); +s32 Camera_CalcAtForNormal1(Camera* camera, VecGeo* arg1, f32 yOffset, f32 forwardDist); +s32 Camera_CalcAtForScreen(Camera* camera, VecGeo* eyeAtDir, f32 yOffset, f32* focalActorPosY, f32 deltaYMax); +s16 Camera_CalcDefaultPitch(Camera* camera, s16 pitch, s16 flatSurfacePitchTarget, s16 slopePitchAdj); +void Camera_CalcDefaultSwing(Camera* camera, VecGeo* arg1, VecGeo* arg2, f32 arg3, f32 arg4, SwingAnimation* swing2, + s16* flags); +s16 Camera_CalcDefaultYaw(Camera* camera, s16 yaw, s16 target, f32 attenuationYawDiffRange, + f32 attenuationYawDiffInterpParam); +f32 Camera_ClampDist1(Camera* camera, f32 dist, f32 minDist, f32 maxDist, s16 timer); +f32 Camera_ClampDist2(Camera* camera, f32 dist, f32 minDist, f32 maxDist, s16 timer); +f32 Camera_ClampLerpScale(Camera* camera, f32 maxLerpScale); +s16 Camera_GetPitchAdjFromFloorHeightDiffs(Camera* camera, s16 viewYaw, s16 shouldInit); +f32 Camera_ScaledStepToCeilF(f32 target, f32 cur, f32 stepScale, f32 minDiff); +s16 Camera_ScaledStepToCeilS(s16 target, s16 cur, f32 stepScale, s16 minDiff); +void Camera_SetUpdateRatesFastYaw(Camera* camera); +s32 func_800CB924(Camera* camera); +s32 func_800CB950(Camera* camera); +s32 func_800CBA7C(Camera* camera); + +#define GET_NEXT_RO_DATA(values) ((values++)->val) +#define RELOAD_PARAMS(camera) ((camera->animState == 0) || (camera->animState == 10) || (camera->animState == 20)) + +s32 Camera_Normal1(Camera* camera) { + Vec3f* eye = &camera->eye; + Vec3f* at = &camera->at; + Vec3f* eyeNext = &camera->eyeNext; + Vec3f spD8; + f32 spD4; + f32 spD0; + Vec3f* temp; + f32 spC8; + f32 spC4; + f32 spC0; + f32 phi_f0_4; + VecGeo spB4; + VecGeo spAC; + VecGeo spA4; + VecGeo sp9C; + PosRot* sp40 = &camera->focalActorPosRot; + Normal1ReadOnlyData* roData = &camera->paramData.norm1.roData; + Normal1ReadWriteData* rwData = &camera->paramData.norm1.rwData; + s16 phi_v1_2; + s16 temp_a0_3; + f32 sp88 = Camera_GetFocalActorHeight(camera); + CameraModeValue* values = sCameraSettings[camera->setting].cameraModes[camera->mode].values; + f32 phi_f2; + f32 rand; + + recomp_printf("Camera_Normal1\n"); + + roData->unk_00 = GET_NEXT_RO_DATA(values) * (sp88 * 0.01f * (0.8f - ((68.0f / sp88) * -0.2f))); + roData->unk_04 = GET_NEXT_RO_DATA(values) * (sp88 * 0.01f * (0.8f - ((68.0f / sp88) * -0.2f))); + roData->unk_08 = GET_NEXT_RO_DATA(values) * (sp88 * 0.01f * (0.8f - ((68.0f / sp88) * -0.2f))); + roData->unk_04 = roData->unk_08 - (roData->unk_08 - roData->unk_04); + + if (RELOAD_PARAMS(camera)) { + roData->unk_20 = CAM_DEG_TO_BINANG(GET_NEXT_RO_DATA(values)); + roData->unk_0C = GET_NEXT_RO_DATA(values); + roData->unk_0C = 40.0f - (40.0f - roData->unk_0C); + roData->unk_10 = GET_NEXT_RO_DATA(values); + roData->unk_14 = GET_NEXT_RO_DATA(values) * 0.01f; + roData->unk_14 = 1.0f - (1.0f - roData->unk_14); + roData->unk_18 = GET_NEXT_RO_DATA(values); + roData->unk_1C = GET_NEXT_RO_DATA(values) * 0.01f; + roData->interfaceFlags = GET_NEXT_RO_DATA(values); + } + + sCameraInterfaceFlags = roData->interfaceFlags; + + spA4 = OLib_Vec3fDiffToVecGeo(at, eye); + sp9C = OLib_Vec3fDiffToVecGeo(at, eyeNext); + + switch (camera->animState) { + case 20: + Camera_SetUpdateRatesFastYaw(camera); + // fallthrough + case 0: + rwData->unk_0C = 1; + if (!(roData->interfaceFlags & NORMAL1_FLAG_3) && (camera->animState != 20)) { + rwData->unk_0C |= 0x1000; + } + // fallthrough + case 10: + if (camera->animState == 10) { + rwData->unk_0C = 0; + } + rwData->unk_08 = 0; + D_801EDC30[camera->camId].yaw = D_801EDC30[camera->camId].pitch = D_801EDC30[camera->camId].unk_64 = 0; + rwData->unk_0A = 0x514; + D_801EDC30[camera->camId].swingUpdateRate = roData->unk_0C; + rwData->unk_00 = sp40->pos.y; + rwData->unk_04 = camera->xzSpeed; + D_801EDC30[camera->camId].timer = 0; + sUpdateCameraDirection = false; + rwData->unk_10 = 120.0f; + break; + + default: + break; + } + + camera->animState = 1; + sUpdateCameraDirection = true; + + if ((camera->speedRatio < 0.01f) || (rwData->unk_0A > 0x4B0)) { + if (rwData->unk_0A > -0x4B0) { + rwData->unk_0A--; + } + } else { + rwData->unk_0A = 0x4B0; + } + + if (func_800CB950(camera)) { + rwData->unk_00 = sp40->pos.y; + } + + if (rwData->unk_0C & 0x1000) { + spC8 = camera->speedRatio; + } else { + spC8 = ((camera->speedRatio * 3.0f) + 1.0f) * 0.25f; + } + + spD8 = camera->focalActorAtOffset; + spD8.y -= sp88 + roData->unk_00; + spC4 = Camera_Vec3fMagnitude(&spD8); + + if ((roData->unk_04 + roData->unk_08) < spC4) { + spC4 = 1.0f; + } else { + spC4 = spC4 / (roData->unk_04 + roData->unk_08); + } + + spD0 = 0.2f; + + phi_f0_4 = (camera->xzSpeed - rwData->unk_04) * (0.2f * 1.0f); + if (phi_f0_4 < 0.0f) { + phi_f0_4 = 0.0f; + } + + spC0 = OLib_ClampMaxDist(SQ(phi_f0_4), 1.0f); + + camera->yOffsetUpdateRate = + Camera_ScaledStepToCeilF(0.05f, camera->yOffsetUpdateRate, (0.5f * spC8) + (0.5f * spC4), 0.0001f); + camera->xzOffsetUpdateRate = + Camera_ScaledStepToCeilF(0.05f, camera->xzOffsetUpdateRate, (0.5f * spC8) + (0.5f * spC4), 0.0001f); + camera->fovUpdateRate = + Camera_ScaledStepToCeilF(0.05f, camera->fovUpdateRate, (0.5f * spC8) + (0.5f * spC4), 0.0001f); + + if (D_801EDC30[camera->camId].unk_64 == 1) { + phi_f2 = 0.5f; + } else { + phi_f2 = (0.5f * spC8) + (0.5f * spC4); + } + + rwData->unk_04 = camera->xzSpeed; + + if (D_801EDC30[camera->camId].timer != 0) { + camera->yawUpdateRateInv = + Camera_ScaledStepToCeilF(D_801EDC30[camera->camId].swingUpdateRate + (D_801EDC30[camera->camId].timer * 2), + camera->yawUpdateRateInv, phi_f2, 0.1f); + if (roData->interfaceFlags & NORMAL1_FLAG_3) { + camera->pitchUpdateRateInv = Camera_ScaledStepToCeilF(100.0f, camera->pitchUpdateRateInv, 0.5f, 0.1f); + } else { + camera->pitchUpdateRateInv = Camera_ScaledStepToCeilF((D_801EDC30[camera->camId].timer * 2) + 16.0f, + camera->pitchUpdateRateInv, 0.2f, 0.1f); + } + D_801EDC30[camera->camId].timer--; + } else { + camera->yawUpdateRateInv = Camera_ScaledStepToCeilF( + D_801EDC30[camera->camId].swingUpdateRate - (D_801EDC30[camera->camId].swingUpdateRate * 0.7f * spC0), + camera->yawUpdateRateInv, phi_f2, 0.1f); + if ((roData->interfaceFlags & NORMAL1_FLAG_3) && (camera->speedRatio > 0.01f)) { + camera->pitchUpdateRateInv = Camera_ScaledStepToCeilF(100.0f, camera->pitchUpdateRateInv, 0.5f, 0.1f); + } else if (D_801ED920 != NULL) { + camera->pitchUpdateRateInv = Camera_ScaledStepToCeilF(32.0f, camera->pitchUpdateRateInv, 0.5f, 0.1f); + } else { + camera->pitchUpdateRateInv = Camera_ScaledStepToCeilF(16.0f, camera->pitchUpdateRateInv, 0.2f, 0.1f); + } + } + + if (roData->interfaceFlags & NORMAL1_FLAG_0) { + //! FAKE: + if (spC8) {} + + temp_a0_3 = Camera_GetPitchAdjFromFloorHeightDiffs(camera, spA4.yaw + 0x8000, rwData->unk_0C & 1); + phi_f2 = (1.0f / roData->unk_10) * 0.7f; + spD0 = (1.0f / roData->unk_10) * 0.3f * (1.0f - camera->speedRatio); + rwData->unk_08 = Camera_ScaledStepToCeilS(temp_a0_3, rwData->unk_08, phi_f2 + spD0, 5); + } else { + rwData->unk_08 = 0; + } + + if ((D_801EDC30[camera->camId].unk_64 == 1) && (roData->unk_00 > -40.0f)) { + spD0 = Math_SinS(D_801EDC30[camera->camId].pitch); + phi_f2 = (-40.0f * spD0) + roData->unk_00 * (1.0f - spD0); + camera->yawUpdateRateInv = 80.0f; + camera->pitchUpdateRateInv = 80.0f; + } else { + phi_f2 = roData->unk_00; + } + + if (roData->interfaceFlags & (NORMAL1_FLAG_6 | NORMAL1_FLAG_5)) { + if (camera->dist < roData->unk_04) { + spD0 = 0.0f; + } else if (roData->unk_08 < camera->dist) { + spD0 = 1.0f; + } else if (roData->unk_08 == roData->unk_04) { + spD0 = 1.0f; + } else { + spD0 = (camera->dist - roData->unk_04) / (roData->unk_08 - roData->unk_04); + } + + Camera_CalcAtForNormal1(camera, &sp9C, phi_f2, 25.0f * spD0 * camera->speedRatio); + rwData->unk_10 = 120.0f; + } else if ((roData->interfaceFlags & NORMAL1_FLAG_7) && (rwData->unk_0A < 0)) { + phi_f0_4 = rwData->unk_0A / -1200.0f; + Camera_CalcAtForNormal1( + camera, &sp9C, phi_f2 - ((phi_f2 - ((0.8f - ((68.0f / sp88) * -0.2f)) * sp88 * -0.45f)) * phi_f0_4 * 0.75f), + 10.0f * phi_f0_4); + rwData->unk_10 = 120.0f; + } else if (roData->interfaceFlags & NORMAL1_FLAG_3) { + Camera_CalcAtForScreen(camera, &sp9C, roData->unk_00, &rwData->unk_00, rwData->unk_10); + if (rwData->unk_10 > 20.0f) { + rwData->unk_10 -= 0.2f; + } + } else { + Camera_CalcAtDefault(camera, &sp9C, phi_f2, roData->interfaceFlags & NORMAL1_FLAG_0); + rwData->unk_10 = 120.0f; + } + + spB4 = OLib_Vec3fDiffToVecGeo(at, eyeNext); + + if ((roData->interfaceFlags & NORMAL1_FLAG_7) && (rwData->unk_0A < 0)) { + if (camera->focalActor == &GET_PLAYER(camera->play)->actor) { + switch (((Player*)camera->focalActor)->transformation) { + case PLAYER_FORM_HUMAN: + spD0 = 66.0f; + break; + + case PLAYER_FORM_DEKU: + spD0 = 66.0f; + break; + + case PLAYER_FORM_GORON: + spD0 = 115.0f; + break; + + case PLAYER_FORM_ZORA: + spD0 = 115.0f; + break; + + case PLAYER_FORM_FIERCE_DEITY: + spD0 = roData->unk_04; + break; + + default: + spD0 = roData->unk_04; + break; + } + } + phi_f0_4 = Camera_ClampDist2(camera, spB4.r, spD0, spD0, 0); + } else if (roData->interfaceFlags & NORMAL1_FLAG_7) { + phi_f0_4 = Camera_ClampDist2(camera, spB4.r, roData->unk_04, roData->unk_08, 1); + } else { + phi_f0_4 = Camera_ClampDist1(camera, spB4.r, roData->unk_04, roData->unk_08, rwData->unk_0A > 0); + } + + camera->dist = spB4.r = phi_f0_4; + + if (D_801EDC30[camera->camId].unk_64 != 0) { + spB4.pitch = + Camera_ScaledStepToCeilS(D_801EDC30[camera->camId].pitch, sp9C.pitch, 1.0f / camera->yawUpdateRateInv, 5); + spB4.yaw = + Camera_ScaledStepToCeilS(D_801EDC30[camera->camId].yaw, sp9C.yaw, 1.0f / camera->yawUpdateRateInv, 5); + } else if (roData->interfaceFlags & NORMAL1_FLAG_5) { + spB4.yaw = sp9C.yaw; + spB4.pitch = sp9C.pitch; + camera->animState = 20; + } else if (D_801ED920 != NULL) { + VecGeo sp74; + s16 sp72; + f32 sp6C; + + //! FAKE: + if (1) {} + + temp = &D_801ED920->world.pos; + sp74 = OLib_Vec3fDiffToVecGeo(&sp40->pos, temp); + sp72 = sp40->rot.y - sp74.yaw; + // Interface and shrink-window flags + if ((roData->interfaceFlags & 0xFF00) == 0xFF00) { + sp6C = 1.0f; + } else { + sp6C = 1.0f - (ABS(sp72) / 10922.0f); + } + + if (ABS((s16)(sp9C.yaw - sp74.yaw)) < 0x4000) { + sp74.yaw += 0x8000; + } + + if (!(roData->interfaceFlags & NORMAL1_FLAG_3) || !func_800CB924(camera)) { + spB4.yaw = + Camera_CalcDefaultYaw(camera, sp9C.yaw, (s16)(sp40->rot.y - (s16)(sp72 * sp6C)), roData->unk_14, spC0); + } + + if (!(roData->interfaceFlags & NORMAL1_FLAG_3) || (camera->speedRatio < 0.01f)) { + spB4.pitch = Camera_CalcDefaultPitch(camera, sp9C.pitch, + roData->unk_20 + (s16)((roData->unk_20 - sp74.pitch) * sp6C * 0.75f), + rwData->unk_08); + } + } else if (roData->interfaceFlags & NORMAL1_FLAG_1) { + VecGeo sp64; + + if ((camera->speedRatio > 0.1f) || (rwData->unk_0A > 0x4B0)) { + sp64 = OLib_Vec3fToVecGeo(&camera->unk_0F0); + if (!(roData->interfaceFlags & NORMAL1_FLAG_3) || !func_800CB924(camera)) { + spB4.yaw = Camera_CalcDefaultYaw(camera, sp9C.yaw, sp64.yaw, roData->unk_14, spC0); + } + if (!(roData->interfaceFlags & NORMAL1_FLAG_3)) { + spB4.pitch = Camera_CalcDefaultPitch(camera, sp9C.pitch, roData->unk_20, rwData->unk_08); + } else if ((camera->unk_0F0.y > 0.0f) && func_800CB924(camera)) { + spB4.pitch = Camera_CalcDefaultPitch(camera, sp9C.pitch, roData->unk_20, rwData->unk_08); + } + } else { + spB4.yaw = sp9C.yaw; + spB4.pitch = sp9C.pitch; + } + } else { + spB4.yaw = Camera_CalcDefaultYaw(camera, sp9C.yaw, sp40->rot.y, roData->unk_14, spC0); + if (!(roData->interfaceFlags & NORMAL1_FLAG_3) || (camera->speedRatio < 0.1f)) { + spB4.pitch = Camera_CalcDefaultPitch(camera, sp9C.pitch, roData->unk_20, rwData->unk_08); + } + } + + // @recomp + if (recomp_camera_mode == RECOMP_CAMERA_DUALANALOG) { + spB4.pitch = recomp_camera_pos.pitch; + // spB4.r = recomp_camera_pos.r; + spB4.yaw = recomp_camera_pos.yaw; + } + + // 76.9 degrees + if (spB4.pitch > 0x36B0) { + spB4.pitch = 0x36B0; + } + + // -76.9 degrees + if (spB4.pitch < -0x36B0) { + spB4.pitch = -0x36B0; + } + + // @recomp + recomp_camera_pos.pitch = spB4.pitch; + + *eyeNext = OLib_AddVecGeoToVec3f(at, &spB4); + + if ((camera->status == CAM_STATUS_ACTIVE) && !(roData->interfaceFlags & NORMAL1_FLAG_4) && (spC4 <= 0.9f)) { + if (!func_800CBA7C(camera)) { + CollisionPoly* sp60; + s32 sp5C; // bgId + f32 sp58; + + Camera_CalcDefaultSwing(camera, &spB4, &sp9C, roData->unk_04, roData->unk_0C, &D_801EDC30[camera->camId], + &rwData->unk_0C); + sp58 = BgCheck_CameraRaycastFloor2(&camera->play->colCtx, &sp60, &sp5C, eye); + if ((roData->interfaceFlags & NORMAL1_FLAG_3) && func_800CB924(camera)) { + spD0 = 25.0f; + } else { + spD0 = 5.0f; + } + + phi_f2 = eye->y - sp58; + if ((sp58 != BGCHECK_Y_MIN) && (phi_f2 < spD0)) { + eye->y = sp58 + spD0; + } else if ((camera->waterYPos != camera->focalActorFloorHeight) && ((eye->y - camera->waterYPos) < 5.0f) && + ((eye->y - camera->waterYPos) > -5.0f)) { + eye->y = camera->waterYPos + 5.0f; + } + } + + spAC = OLib_Vec3fDiffToVecGeo(eye, at); + camera->inputDir.x = spAC.pitch; + camera->inputDir.y = spAC.yaw; + camera->inputDir.z = 0; + + // crit wiggle + if (gSaveContext.save.saveInfo.playerData.health <= 0x10) { + phi_v1_2 = ((s32)(camera->play->state.frames << 0x18) >> 0x15) & 0xFD68; + camera->inputDir.y += phi_v1_2; + } + } else { + D_801EDC30[camera->camId].swingUpdateRate = roData->unk_0C; + D_801EDC30[camera->camId].unk_64 = 0; + sUpdateCameraDirection = false; + *eye = *eyeNext; + } + + phi_f2 = (gSaveContext.save.saveInfo.playerData.health <= 0x10) ? 0.8f : 1.0f; + + // @recomp + // // Don't zoom in on low health when dual analog is used + // if (recomp_camera_mode == RECOMP_CAMERA_DUALANALOG) { + // phi_f2; + // } + + camera->fov = Camera_ScaledStepToCeilF(roData->unk_18 * phi_f2, camera->fov, camera->fovUpdateRate, 0.1f); + + if (roData->interfaceFlags & NORMAL1_FLAG_2) { + spD4 = Math_SinS((s16)(spA4.yaw - spB4.yaw)); + rand = Rand_ZeroOne() - 0.5f; + camera->roll = Camera_ScaledStepToCeilS((rand * 500.0f * camera->speedRatio) + (spD4 * spD4 * spD4 * 10000.0f), + camera->roll, 0.1f, 5); + } else { + if (gSaveContext.save.saveInfo.playerData.health <= 0x10) { + rand = Rand_ZeroOne() - 0.5f; + phi_v1_2 = rand * 100.0f * camera->speedRatio; + } else { + phi_v1_2 = 0.0f; + } + camera->roll = Camera_ScaledStepToCeilS(phi_v1_2, camera->roll, 0.2f, 5); + } + + camera->atLerpStepScale = Camera_ClampLerpScale(camera, roData->unk_1C); + rwData->unk_0C &= ~1; + + return true; +} diff --git a/patches/input.c b/patches/input.c index f269427..79472a9 100644 --- a/patches/input.c +++ b/patches/input.c @@ -8,6 +8,13 @@ u32 sPlayerItemButtons[] = { BTN_CRIGHT, }; +u32 sPlayerItemButtonsDualAnalog[] = { + BTN_B, + BTN_DLEFT, + BTN_DDOWN, + BTN_DRIGHT +}; + u32 prev_item_buttons = 0; u32 cur_item_buttons = 0; u32 pressed_item_buttons = 0; @@ -23,6 +30,11 @@ typedef enum { void GameState_GetInput(GameState* gameState) { PadMgr_GetInput(gameState->input, true); + if (recomp_camera_mode == RECOMP_CAMERA_DUALANALOG) { + gameState->input[0].cur.button &= ~BTN_CUP; + gameState->input[0].press.button &= ~BTN_CUP; + gameState->input[0].rel.button &= ~BTN_CUP; + } prev_item_buttons = cur_item_buttons; recomp_get_item_inputs(&cur_item_buttons); @@ -46,25 +58,27 @@ struct SlotMap exSlotMapping[] = { // D-Pad items // TODO restore this once UI is made // Return currently-pressed button, in order of priority D-Pad, B, CLEFT, CDOWN, CRIGHT. -/* + EquipSlot func_8082FDC4(void) { EquipSlot i; - for (int mapping_index = 0; mapping_index < ARRAY_COUNT(exSlotMapping); mapping_index++) { - if (pressed_item_buttons & exSlotMapping[mapping_index].button) { - return (EquipSlot)exSlotMapping[mapping_index].slot; - } - } + // for (int mapping_index = 0; mapping_index < ARRAY_COUNT(exSlotMapping); mapping_index++) { + // if (pressed_item_buttons & exSlotMapping[mapping_index].button) { + // return (EquipSlot)exSlotMapping[mapping_index].slot; + // } + // } + + u32* button_map = recomp_camera_mode == RECOMP_CAMERA_DUALANALOG ? sPlayerItemButtonsDualAnalog : sPlayerItemButtons; for (i = 0; i < ARRAY_COUNT(sPlayerItemButtons); i++) { - if (CHECK_BTN_ALL(pressed_item_buttons, sPlayerItemButtons[i])) { + if (CHECK_BTN_ALL(pressed_item_buttons, button_map[i])) { break; } } return i; } - +/* ItemId Player_GetItemOnButton(PlayState* play, Player* player, EquipSlot slot) { if (slot >= EQUIP_SLOT_A) { return ITEM_NONE; diff --git a/patches/input.h b/patches/input.h index 4753ad0..8484b69 100644 --- a/patches/input.h +++ b/patches/input.h @@ -7,6 +7,13 @@ #include "recomp.h" #endif +typedef enum { + RECOMP_CAMERA_NORMAL, + RECOMP_CAMERA_DUALANALOG, +} RecompCameraMode; + +extern RecompCameraMode recomp_camera_mode; + #ifdef __cplusplus extern "C" { #endif @@ -20,6 +27,7 @@ extern "C" { #endif DECLARE_FUNC(void, recomp_get_item_inputs, u32* buttons); +DECLARE_FUNC(void, recomp_get_camera_inputs, float* x_out, float* y_out); // TODO move this DECLARE_FUNC(void, recomp_puts, const char* data, u32 size); diff --git a/patches/patches.h b/patches/patches.h index a045534..d0d7656 100644 --- a/patches/patches.h +++ b/patches/patches.h @@ -2,6 +2,7 @@ #define __PATCHES_H__ #include "global.h" +#include "rt64_extended_gbi.h" int recomp_printf(const char* fmt, ...); diff --git a/patches/syms.ld b/patches/syms.ld index 0a354d5..3449416 100644 --- a/patches/syms.ld +++ b/patches/syms.ld @@ -3,3 +3,4 @@ __start = 0x80000000; /* Dummy addresses that get recompiled into function calls */ recomp_get_item_inputs = 0x81000000; recomp_puts = 0x81000004; +recomp_get_camera_inputs = 0x81000008; diff --git a/patches/ui_patches.c b/patches/ui_patches.c new file mode 100644 index 0000000..d0eca0a --- /dev/null +++ b/patches/ui_patches.c @@ -0,0 +1,1524 @@ +#include "patches.h" +#include "buffers.h" +#include "sys_cfb.h" +#include "overlays/kaleido_scope/ovl_kaleido_scope/z_kaleido_scope.h" + +// This moves elements towards the screen edges when increased +s32 margin_reduction = 8; + +// Modified to enable RT64 extended GBI mode +void Graph_SetNextGfxPool(GraphicsContext* gfxCtx) { + GfxPool* pool = &gGfxPools[gfxCtx->gfxPoolIdx % 2]; + + gGfxMasterDL = &pool->master; + gSegments[0x0E] = (uintptr_t)gGfxMasterDL; + + pool->headMagic = GFXPOOL_HEAD_MAGIC; + pool->tailMagic = GFXPOOL_TAIL_MAGIC; + + Graph_InitTHGA(&gfxCtx->polyOpa, pool->polyOpaBuffer, sizeof(pool->polyOpaBuffer)); + Graph_InitTHGA(&gfxCtx->polyXlu, pool->polyXluBuffer, sizeof(pool->polyXluBuffer)); + Graph_InitTHGA(&gfxCtx->overlay, pool->overlayBuffer, sizeof(pool->overlayBuffer)); + Graph_InitTHGA(&gfxCtx->work, pool->workBuffer, sizeof(pool->workBuffer)); + Graph_InitTHGA(&gfxCtx->debug, pool->debugBuffer, sizeof(pool->debugBuffer)); + + gfxCtx->polyOpaBuffer = pool->polyOpaBuffer; + gfxCtx->polyXluBuffer = pool->polyXluBuffer; + gfxCtx->overlayBuffer = pool->overlayBuffer; + gfxCtx->workBuffer = pool->workBuffer; + gfxCtx->debugBuffer = pool->debugBuffer; + + gfxCtx->curFrameBuffer = SysCfb_GetFramebuffer(gfxCtx->framebufferIndex % 2); + gSegments[0x0F] = (uintptr_t)gfxCtx->curFrameBuffer; + + gfxCtx->zbuffer = SysCfb_GetZBuffer(); + + gSPBranchList(&gGfxMasterDL->disps[0], pool->polyOpaBuffer); + gSPBranchList(&gGfxMasterDL->disps[1], pool->polyXluBuffer); + gSPBranchList(&gGfxMasterDL->disps[2], pool->overlayBuffer); + gSPBranchList(&gGfxMasterDL->disps[3], pool->workBuffer); + gSPEndDisplayList(&gGfxMasterDL->disps[4]); + gSPBranchList(&gGfxMasterDL->debugDisp[0], pool->debugBuffer); + + // Enable RT64 extended GBI mode + OPEN_DISPS(gfxCtx); + gEXEnable(POLY_OPA_DISP++); + // gEXPrint(POLY_OPA_DISP++); + CLOSE_DISPS(gfxCtx); +} + +// Adjusts an x-coordinate to the relative to be given origin +s16 adjust_x(s16 xPos, u32 origin) { + switch (origin) { + default: + return xPos - margin_reduction; + case G_EX_ORIGIN_RIGHT: + return xPos - SCREEN_WIDTH + margin_reduction; + case G_EX_ORIGIN_CENTER: + return xPos - (SCREEN_WIDTH / 2); + } +} + +// Adjusts an x-coordinate with 2 fractional bits to be relative to the given origin +s16 adjust_x_fractional(s16 xPos, u32 origin) { + switch (origin) { + default: + return xPos - (margin_reduction << 2); + case G_EX_ORIGIN_RIGHT: + return xPos - (SCREEN_WIDTH << 2) + (margin_reduction << 2); + case G_EX_ORIGIN_CENTER: + return xPos - ((SCREEN_WIDTH / 2) << 2); + } +} + +typedef enum { + Y_ORIGIN_TOP, + Y_ORIGIN_CENTER, + Y_ORIGIN_BOTTOM +} YOrigin; + +// Adjusts a top y-coordinate to be relative to the given origin +s16 adjust_y(s16 yPos, YOrigin origin) { + switch (origin) { + default: + return yPos - margin_reduction; + case Y_ORIGIN_CENTER: + return yPos; + case Y_ORIGIN_BOTTOM: + return yPos + margin_reduction; + } +} + +// Adjusts a y-coordinate with 2 fractional bits to be relative to the given origin +s16 adjust_y_fractional(s16 yPos, YOrigin origin) { + switch (origin) { + default: + return yPos - (margin_reduction << 2); + case Y_ORIGIN_CENTER: + return yPos; + case Y_ORIGIN_BOTTOM: + return yPos + (margin_reduction << 2); + } +} + +/** + * Draw an IA8 texture on a rectangle with a shadow slightly offset to the bottom-right + * + * @param gfx the display list pointer + * @param texture + * @param textureWidth texture image width in texels + * @param textureHeight texture image height in texels + * @param rectLeft the x-coordinate of upper-left corner of rectangle + * @param rectTop the y-coordinate of upper-left corner of rectangle + * @param rectWidth rectangle width in texels + * @param rectHeight rectangle height in texels + * @param dsdx the change in s for each change in x (s5.10) + * @param dtdy the change in t for each change in y (s5.10) + * @param r texture red + * @param g texture green + * @param b texture blue + * @param a texture alpha + * @return Gfx* the display list pointer + */ +Gfx* GfxEx_DrawTexRectIA8_DropShadow(Gfx* gfx, TexturePtr texture, s16 textureWidth, s16 textureHeight, s16 rectLeft, + s16 rectTop, s16 rectWidth, s16 rectHeight, u16 dsdx, u16 dtdy, s16 r, s16 g, s16 b, + s16 a, u32 origin_x, u32 origin_y) { + s16 dropShadowAlpha = a; + + if (a > 100) { + dropShadowAlpha = 100; + } + + rectLeft = adjust_x(rectLeft, origin_x); + rectTop = adjust_x(rectTop, origin_y); + + gDPPipeSync(gfx++); + gDPSetPrimColor(gfx++, 0, 0, 0, 0, 0, dropShadowAlpha); + + gDPLoadTextureBlock(gfx++, texture, G_IM_FMT_IA, G_IM_SIZ_8b, textureWidth, textureHeight, 0, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, + G_TX_NOLOD); + + gEXTextureRectangle(gfx++, origin_x, origin_x, (rectLeft + 2) * 4, (rectTop + 2) * 4, (rectLeft + rectWidth + 2) * 4, + (rectTop + rectHeight + 2) * 4, G_TX_RENDERTILE, 0, 0, dsdx, dtdy); + + gDPPipeSync(gfx++); + gDPSetPrimColor(gfx++, 0, 0, r, g, b, a); + + gEXTextureRectangle(gfx++, origin_x, origin_x, rectLeft * 4, rectTop * 4, (rectLeft + rectWidth) * 4, (rectTop + rectHeight) * 4, + G_TX_RENDERTILE, 0, 0, dsdx, dtdy); + + return gfx; +} + +/** + * Draw a colored rectangle with a shadow slightly offset to the bottom-right + * + * @param gfx the display list pointer + * @param rectLeft the x-coordinate of upper-left corner of rectangle + * @param rectTop the y-coordinate of upper-left corner of rectangle + * @param rectWidth rectangle width in texels + * @param rectHeight rectangle height in texels + * @param dsdx the change in s for each change in x (s5.10) + * @param dtdy the change in t for each change in y (s5.10) + * @param r // rectangle red + * @param g // rectangle green + * @param b // rectangle blue + * @param a // rectangle alpha + * @return Gfx* the display list pointer + */ +Gfx* GfxEx_DrawRect_DropShadow(Gfx* gfx, s16 rectLeft, s16 rectTop, s16 rectWidth, s16 rectHeight, u16 dsdx, u16 dtdy, + s16 r, s16 g, s16 b, s16 a, u32 origin_x, u32 origin_y) { + s16 dropShadowAlpha = a; + + if (a > 100) { + dropShadowAlpha = 100; + } + + rectLeft = adjust_x(rectLeft, origin_x); + rectTop = adjust_x(rectTop, origin_y); + + gDPPipeSync(gfx++); + gDPSetPrimColor(gfx++, 0, 0, 0, 0, 0, dropShadowAlpha); + gEXTextureRectangle(gfx++, origin_x, origin_x, (rectLeft + 2) * 4, (rectTop + 2) * 4, (rectLeft + rectWidth + 2) * 4, + (rectTop + rectHeight + 2) * 4, G_TX_RENDERTILE, 0, 0, dsdx, dtdy); + + gDPPipeSync(gfx++); + gDPSetPrimColor(gfx++, 0, 0, r, g, b, a); + + gEXTextureRectangle(gfx++, origin_x, origin_x, rectLeft * 4, rectTop * 4, (rectLeft + rectWidth) * 4, (rectTop + rectHeight) * 4, + G_TX_RENDERTILE, 0, 0, dsdx, dtdy); + + return gfx; +} + + +/** + * Draw an IA8 texture on a rectangle with a shadow slightly offset to the bottom-right with additional texture offsets + * + * @param gfx the display list pointer + * @param texture + * @param textureWidth texture image width in texels + * @param textureHeight texture image height in texels + * @param rectLeft the x-coordinate of upper-left corner of rectangle + * @param rectTop the y-coordinate of upper-left corner of rectangle + * @param rectWidth rectangle width in texels + * @param rectHeight rectangle height in texels + * @param dsdx the change in s for each change in x (s5.10) + * @param dtdy the change in t for each change in y (s5.10) + * @param r // texture red + * @param g // texture green + * @param b // texture blue + * @param a // texture alpha + * @param masks specify the mask for the s axis + * @param rects the texture coordinate s of upper-left corner of rectangle (s10.5) + * @return Gfx* the display list pointer + */ +Gfx* GfxEx_DrawTexRectIA8_DropShadowOffset(Gfx* gfx, TexturePtr texture, s16 textureWidth, s16 textureHeight, + s16 rectLeft, s16 rectTop, s16 rectWidth, s16 rectHeight, u16 dsdx, u16 dtdy, + s16 r, s16 g, s16 b, s16 a, s32 masks, s32 rects, u32 origin_x, u32 origin_y) { + s16 dropShadowAlpha = a; + + if (a > 100) { + dropShadowAlpha = 100; + } + + rectLeft = adjust_x(rectLeft, origin_x); + rectTop = adjust_x(rectTop, origin_y); + + gDPPipeSync(gfx++); + gDPSetPrimColor(gfx++, 0, 0, 0, 0, 0, dropShadowAlpha); + + gDPLoadTextureBlock(gfx++, texture, G_IM_FMT_IA, G_IM_SIZ_8b, textureWidth, textureHeight, 0, + G_TX_MIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, masks, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); + + gEXTextureRectangle(gfx++, origin_x, origin_x, (rectLeft + 2) * 4, (rectTop + 2) * 4, (rectLeft + rectWidth + 2) * 4, + (rectTop + rectHeight + 2) * 4, G_TX_RENDERTILE, rects, 0, dsdx, dtdy); + + gDPPipeSync(gfx++); + gDPSetPrimColor(gfx++, 0, 0, r, g, b, a); + + gEXTextureRectangle(gfx++, origin_x, origin_x, rectLeft * 4, rectTop * 4, (rectLeft + rectWidth) * 4, (rectTop + rectHeight) * 4, + G_TX_RENDERTILE, rects, 0, dsdx, dtdy); + + return gfx; +} + +/** + * Draw an IA8 texture on a rectangle + * + * @param gfx the display list pointer + * @param texture + * @param textureWidth texture image width in texels + * @param textureHeight texture image height in texels + * @param rectLeft the x-coordinate of upper-left corner of rectangle + * @param rectTop the y-coordinate of upper-left corner of rectangle + * @param rectWidth rectangle width in texels + * @param rectHeight rectangle height in texels + * @param dsdx the change in s for each change in x (s5.10) + * @param dtdy the change in t for each change in y (s5.10) + * @return Gfx* the display list pointer + */ +Gfx* GfxEx_DrawTexRectIA8(Gfx* gfx, TexturePtr texture, s16 textureWidth, s16 textureHeight, s16 rectLeft, s16 rectTop, + s16 rectWidth, s16 rectHeight, u16 dsdx, u16 dtdy, u32 origin_x, u32 origin_y) { + gDPLoadTextureBlock(gfx++, texture, G_IM_FMT_IA, G_IM_SIZ_8b, textureWidth, textureHeight, 0, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, + G_TX_NOLOD); + + rectLeft = adjust_x(rectLeft, origin_x); + rectTop = adjust_y(rectTop, origin_y); + + gEXTextureRectangle(gfx++, origin_x, origin_x, rectLeft << 2, rectTop << 2, (rectLeft + rectWidth) << 2, (rectTop + rectHeight) << 2, + G_TX_RENDERTILE, 0, 0, dsdx, dtdy); + + return gfx; +} + +/** + * Draw an I8 texture on a rectangle + * + * @param gfx the display list pointer + * @param texture + * @param textureWidth texture image width in texels + * @param textureHeight texture image height in texels + * @param rectLeft the x-coordinate of upper-left corner of rectangle + * @param rectTop the y-coordinate of upper-left corner of rectangle + * @param rectWidth rectangle width in texels + * @param rectHeight rectangle height in texels + * @param dsdx the change in s for each change in x (s5.10) + * @param dtdy the change in t for each change in y (s5.10) + * @return Gfx* the display list pointer + */ +Gfx* GfxEx_DrawTexRectI8(Gfx* gfx, TexturePtr texture, s16 textureWidth, s16 textureHeight, s16 rectLeft, s16 rectTop, + s16 rectWidth, s16 rectHeight, u16 dsdx, u16 dtdy, u32 origin_x, u32 origin_y) { + gDPLoadTextureBlock(gfx++, texture, G_IM_FMT_I, G_IM_SIZ_8b, textureWidth, textureHeight, 0, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, + G_TX_NOLOD); + + rectLeft = adjust_x(rectLeft, origin_x); + rectTop = adjust_y(rectTop, origin_y); + + gEXTextureRectangle(gfx++, origin_x, origin_x, rectLeft << 2, rectTop << 2, (rectLeft + rectWidth) << 2, (rectTop + rectHeight) << 2, + G_TX_RENDERTILE, 0, 0, dsdx, dtdy); + + return gfx; +} + +extern u8 gTatlCUpENGTex[]; +extern u8 gTatlCUpGERTex[]; +extern u8 gTatlCUpFRATex[]; +extern u8 gTatlCUpESPTex[]; +extern u8 gButtonBackgroundTex[]; +extern s16 D_801BF9D4[]; +extern s16 D_801BF9DC[]; +extern s16 D_801BF9E4[]; +extern s16 D_801BF9BC[]; +extern u8 gAmmoDigit0Tex[]; + +typedef enum { + /* 0 */ PICTO_BOX_STATE_OFF, // Not using the pictograph + /* 1 */ PICTO_BOX_STATE_LENS, // Looking through the lens of the pictograph + /* 2 */ PICTO_BOX_STATE_SETUP_PHOTO, // Looking at the photo currently taken + /* 3 */ PICTO_BOX_STATE_PHOTO +} PictoBoxState; + +extern s16 sPictoState; +extern u16 sCUpInvisible; +extern u16 sCUpTimer; + +#define DO_ACTION_TEX_WIDTH 48 +#define DO_ACTION_TEX_HEIGHT 16 +#define DO_ACTION_TEX_SIZE ((DO_ACTION_TEX_WIDTH * DO_ACTION_TEX_HEIGHT) / 2) + +// Modify item button drawing to use the extended GBI texture rectangles for widescreen support +void Interface_DrawItemButtons(PlayState* play) { + static TexturePtr cUpLabelTextures[] = { + gTatlCUpENGTex, gTatlCUpENGTex, gTatlCUpGERTex, gTatlCUpFRATex, gTatlCUpESPTex, + }; + static s16 startButtonLeftPos[] = { + // Remnant of OoT + 130, 136, 136, 136, 136, + }; + static s16 D_801BFAF4[] = { + 0x1D, // EQUIP_SLOT_B + 0x1B, // EQUIP_SLOT_C_LEFT + 0x1B, // EQUIP_SLOT_C_DOWN + 0x1B, // EQUIP_SLOT_C_RIGHT + }; + InterfaceContext* interfaceCtx = &play->interfaceCtx; + Player* player = GET_PLAYER(play); + PauseContext* pauseCtx = &play->pauseCtx; + MessageContext* msgCtx = &play->msgCtx; + s16 temp; // Used as both an alpha value and a button index + s32 pad; + + OPEN_DISPS(play->state.gfxCtx); + + gDPPipeSync(OVERLAY_DISP++); + gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM); + + // B Button Color & Texture + OVERLAY_DISP = GfxEx_DrawTexRectIA8_DropShadow( + OVERLAY_DISP, gButtonBackgroundTex, 0x20, 0x20, D_801BF9D4[EQUIP_SLOT_B], D_801BF9DC[EQUIP_SLOT_B], + D_801BFAF4[EQUIP_SLOT_B], D_801BFAF4[EQUIP_SLOT_B], D_801BF9E4[EQUIP_SLOT_B] * 2, D_801BF9E4[EQUIP_SLOT_B] * 2, + 100, 255, 120, interfaceCtx->bAlpha, G_EX_ORIGIN_RIGHT, Y_ORIGIN_TOP); + gDPPipeSync(OVERLAY_DISP++); + + // C-Left Button Color & Texture + OVERLAY_DISP = GfxEx_DrawRect_DropShadow(OVERLAY_DISP, D_801BF9D4[EQUIP_SLOT_C_LEFT], D_801BF9DC[EQUIP_SLOT_C_LEFT], + D_801BFAF4[EQUIP_SLOT_C_LEFT], D_801BFAF4[EQUIP_SLOT_C_LEFT], + D_801BF9E4[EQUIP_SLOT_C_LEFT] * 2, D_801BF9E4[EQUIP_SLOT_C_LEFT] * 2, 255, + 240, 0, interfaceCtx->cLeftAlpha, G_EX_ORIGIN_RIGHT, Y_ORIGIN_TOP); + // C-Down Button Color & Texture + OVERLAY_DISP = GfxEx_DrawRect_DropShadow(OVERLAY_DISP, D_801BF9D4[EQUIP_SLOT_C_DOWN], D_801BF9DC[EQUIP_SLOT_C_DOWN], + D_801BFAF4[EQUIP_SLOT_C_DOWN], D_801BFAF4[EQUIP_SLOT_C_DOWN], + D_801BF9E4[EQUIP_SLOT_C_DOWN] * 2, D_801BF9E4[EQUIP_SLOT_C_DOWN] * 2, 255, + 240, 0, interfaceCtx->cDownAlpha, G_EX_ORIGIN_RIGHT, Y_ORIGIN_TOP); + // C-Right Button Color & Texture + OVERLAY_DISP = GfxEx_DrawRect_DropShadow(OVERLAY_DISP, D_801BF9D4[EQUIP_SLOT_C_RIGHT], D_801BF9DC[EQUIP_SLOT_C_RIGHT], + D_801BFAF4[EQUIP_SLOT_C_RIGHT], D_801BFAF4[EQUIP_SLOT_C_RIGHT], + D_801BF9E4[EQUIP_SLOT_C_RIGHT] * 2, D_801BF9E4[EQUIP_SLOT_C_RIGHT] * 2, 255, + 240, 0, interfaceCtx->cRightAlpha, G_EX_ORIGIN_RIGHT, Y_ORIGIN_TOP); + + if (!IS_PAUSE_STATE_GAMEOVER) { + if ((play->pauseCtx.state != PAUSE_STATE_OFF) || (play->pauseCtx.debugEditor != DEBUG_EDITOR_NONE)) { + OVERLAY_DISP = GfxEx_DrawRect_DropShadow(OVERLAY_DISP, 0x88, 0x11, 0x16, 0x16, 0x5B6, 0x5B6, 0xFF, 0x82, 0x3C, + interfaceCtx->startAlpha, G_EX_ORIGIN_RIGHT, Y_ORIGIN_TOP); + // Start Button Texture, Color & Label + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->startAlpha); + gDPSetEnvColor(OVERLAY_DISP++, 0, 0, 0, 0); + gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, + PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0); + gDPLoadTextureBlock_4b(OVERLAY_DISP++, interfaceCtx->doActionSegment + DO_ACTION_TEX_SIZE * 2, G_IM_FMT_IA, + DO_ACTION_TEX_WIDTH, DO_ACTION_TEX_HEIGHT, 0, G_TX_NOMIRROR | G_TX_WRAP, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); + gEXTextureRectangle(OVERLAY_DISP++, G_EX_ORIGIN_RIGHT, G_EX_ORIGIN_RIGHT, + adjust_x(126, G_EX_ORIGIN_RIGHT) * 4, adjust_y(21, Y_ORIGIN_TOP) * 4, + adjust_x(181, G_EX_ORIGIN_RIGHT) * 4, adjust_y(39, Y_ORIGIN_TOP) * 4, + G_TX_RENDERTILE, 0, 0, 0x04A6, 0x04A6); + } + } + + if (interfaceCtx->tatlCalling && (play->pauseCtx.state == PAUSE_STATE_OFF) && + (play->pauseCtx.debugEditor == DEBUG_EDITOR_NONE) && (play->csCtx.state == CS_STATE_IDLE) && + (sPictoState == PICTO_BOX_STATE_OFF)) { + if (sCUpInvisible == 0) { + // C-Up Button Texture, Color & Label (Tatl Text) + gDPPipeSync(OVERLAY_DISP++); + + if ((gSaveContext.hudVisibility == HUD_VISIBILITY_NONE) || + (gSaveContext.hudVisibility == HUD_VISIBILITY_NONE_ALT) || + (gSaveContext.hudVisibility == HUD_VISIBILITY_A_HEARTS_MAGIC_WITH_OVERWRITE) || + (msgCtx->msgMode != MSGMODE_NONE)) { + temp = 0; + } else if (player->stateFlags1 & PLAYER_STATE1_200000) { + temp = 70; + } else { + temp = interfaceCtx->aAlpha; + } + + OVERLAY_DISP = + GfxEx_DrawRect_DropShadow(OVERLAY_DISP, 0xFE, 0x10, 0x10, 0x10, 0x800, 0x800, 0xFF, 0xF0, 0, temp, G_EX_ORIGIN_RIGHT, Y_ORIGIN_TOP); + + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, temp); + gDPSetEnvColor(OVERLAY_DISP++, 0, 0, 0, 0); + gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, + PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0); + gDPLoadTextureBlock_4b(OVERLAY_DISP++, cUpLabelTextures[gSaveContext.options.language], G_IM_FMT_IA, 32, 12, + 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, + G_TX_NOLOD, G_TX_NOLOD); + gEXTextureRectangle(OVERLAY_DISP++, G_EX_ORIGIN_RIGHT, G_EX_ORIGIN_RIGHT, + adjust_x(247, G_EX_ORIGIN_RIGHT) * 4, adjust_y(18, Y_ORIGIN_TOP) * 4, + adjust_x(279, G_EX_ORIGIN_RIGHT) * 4, adjust_y(30, Y_ORIGIN_TOP) * 4, G_TX_RENDERTILE, 0, 0, 1 << 10, + 1 << 10); + } + + sCUpTimer--; + if (sCUpTimer == 0) { + sCUpInvisible ^= 1; + sCUpTimer = 10; + } + } + + gDPPipeSync(OVERLAY_DISP++); + + // Empty C Button Arrows + for (temp = EQUIP_SLOT_C_LEFT; temp <= EQUIP_SLOT_C_RIGHT; temp++) { + if (GET_CUR_FORM_BTN_ITEM(temp) > 0xF0) { + if (temp == EQUIP_SLOT_C_LEFT) { + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 240, 0, interfaceCtx->cLeftAlpha); + } else if (temp == EQUIP_SLOT_C_DOWN) { + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 240, 0, interfaceCtx->cDownAlpha); + } else { // EQUIP_SLOT_C_RIGHT + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 240, 0, interfaceCtx->cRightAlpha); + } + OVERLAY_DISP = GfxEx_DrawTexRectIA8(OVERLAY_DISP, ((u8*)gButtonBackgroundTex + ((32 * 32) * (temp + 1))), + 0x20, 0x20, D_801BF9D4[temp], D_801BF9DC[temp], D_801BFAF4[temp], + D_801BFAF4[temp], D_801BF9E4[temp] * 2, D_801BF9E4[temp] * 2, G_EX_ORIGIN_RIGHT, Y_ORIGIN_TOP); + } + } + + CLOSE_DISPS(play->state.gfxCtx); +} + +// Modify item icon drawing to use the extended GBI texture rectangles for widescreen support +void Interface_DrawItemIconTexture(PlayState* play, TexturePtr texture, s16 button) { + static s16 D_801BFAFC[] = { 30, 24, 24, 24 }; + + OPEN_DISPS(play->state.gfxCtx); + + gDPLoadTextureBlock(OVERLAY_DISP++, texture, G_IM_FMT_RGBA, G_IM_SIZ_32b, 32, 32, 0, G_TX_NOMIRROR | G_TX_WRAP, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); + + gEXTextureRectangle(OVERLAY_DISP++, G_EX_ORIGIN_RIGHT, G_EX_ORIGIN_RIGHT, + adjust_x(D_801BF9D4[button], G_EX_ORIGIN_RIGHT) * 4, adjust_y(D_801BF9DC[button], Y_ORIGIN_TOP) * 4, + adjust_x(D_801BF9D4[button] + D_801BFAFC[button], G_EX_ORIGIN_RIGHT) * 4, adjust_y(D_801BF9DC[button] + D_801BFAFC[button], Y_ORIGIN_TOP) * 4, + G_TX_RENDERTILE, 0, 0, D_801BF9BC[button] << 1, D_801BF9BC[button] << 1); + + CLOSE_DISPS(play->state.gfxCtx); +} + +// Modify drawing A button for widescreen +extern f32 D_801BF9CC[]; + +void Interface_SetPerspectiveView(PlayState* play, s32 topY, s32 bottomY, s32 leftX, s32 rightX); +void View_ViewportToVp(Vp* dest, Viewport* src); + +void ViewEx_SetScissor(Gfx** gfx, s32 ulx, s32 uly, s32 lrx, s32 lry, u32 lorigin, u32 rorigin) { + Gfx* gfxp = *gfx; + + ulx = adjust_x(ulx, lorigin); + lrx = adjust_x(lrx, rorigin); + + gEXSetScissor(gfxp++, G_SC_NON_INTERLACE, lorigin, rorigin, ulx, uly, lrx, lry); + + *gfx = gfxp; +} + + +void View_SetScissor(Gfx** gfx, s32 ulx, s32 uly, s32 lrx, s32 lry); + +/** + * Apply scissor, viewport, view and projection (perspective) to OVERLAY_DISP. + */ +s32 ViewEx_ApplyPerspectiveToOverlay(View* view, u32 origin_x, u32 origin_y) { + f32 aspect; + s32 width; + s32 height; + Vp* vp; + Mtx* projection; + Mtx* viewing; + GraphicsContext* gfxCtx; + s32 pad; + + gfxCtx = view->gfxCtx; + + OPEN_DISPS(gfxCtx); + + vp = GRAPH_ALLOC(gfxCtx, sizeof(Vp)); + View_ViewportToVp(vp, &view->viewport); + view->vp = *vp; + + gDPPipeSync(OVERLAY_DISP++); + { + s32 pad; + Gfx* overlay; + + overlay = OVERLAY_DISP; + ViewEx_SetScissor(&overlay, view->viewport.leftX, view->viewport.topY, view->viewport.rightX, + view->viewport.bottomY, origin_x, origin_x); + OVERLAY_DISP = overlay; + } + + vp->vp.vtrans[0] = adjust_x_fractional(vp->vp.vtrans[0], origin_x); + vp->vp.vtrans[1] = adjust_y_fractional(vp->vp.vtrans[1], origin_y); + + gEXViewport(OVERLAY_DISP++, origin_x, vp); + projection = GRAPH_ALLOC(gfxCtx, sizeof(Mtx)); + view->projectionPtr = projection; + + width = view->viewport.rightX - view->viewport.leftX; + height = view->viewport.bottomY - view->viewport.topY; + aspect = (f32)width / (f32)height; + + guPerspective(projection, &view->perspNorm, view->fovy, aspect, view->zNear, view->zFar, view->scale); + + view->projection = *projection; + + gSPPerspNormalize(OVERLAY_DISP++, view->perspNorm); + gSPMatrix(OVERLAY_DISP++, projection, G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_PROJECTION); + + viewing = GRAPH_ALLOC(gfxCtx, sizeof(Mtx)); + view->viewingPtr = viewing; + + // This check avoids a divide-by-zero in guLookAt if eye == at + if (view->eye.x == view->at.x && view->eye.y == view->at.y && view->eye.z == view->at.z) { + view->eye.z += 2.0f; + } + + guLookAt(viewing, view->eye.x, view->eye.y, view->eye.z, view->at.x, view->at.y, view->at.z, view->up.x, view->up.y, + view->up.z); + + view->viewing = *viewing; + + gSPMatrix(OVERLAY_DISP++, viewing, G_MTX_NOPUSH | G_MTX_MUL | G_MTX_PROJECTION); + + CLOSE_DISPS(gfxCtx); + + return 1; +} + +void InterfaceEx_SetPerspectiveView(PlayState* play, s32 topY, s32 bottomY, s32 leftX, s32 rightX, u32 origin_x, u32 origin_y) { + InterfaceContext* interfaceCtx = &play->interfaceCtx; + Vec3f eye; + Vec3f at; + Vec3f up; + + eye.x = eye.y = eye.z = 0.0f; + at.x = at.y = 0.0f; + at.z = -1.0f; + up.x = up.z = 0.0f; + up.y = 1.0f; + + View_LookAt(&interfaceCtx->view, &eye, &at, &up); + + interfaceCtx->viewport.topY = topY; + interfaceCtx->viewport.bottomY = bottomY; + interfaceCtx->viewport.leftX = leftX; + interfaceCtx->viewport.rightX = rightX; + View_SetViewport(&interfaceCtx->view, &interfaceCtx->viewport); + + View_SetPerspective(&interfaceCtx->view, 60.0f, 10.0f, 60.0f); + ViewEx_ApplyPerspectiveToOverlay(&interfaceCtx->view, origin_x, origin_y); +} + +void Interface_DrawAButton(PlayState* play) { + InterfaceContext* interfaceCtx = &play->interfaceCtx; + s16 aAlpha; + + OPEN_DISPS(play->state.gfxCtx); + + aAlpha = interfaceCtx->aAlpha; + + if (aAlpha > 100) { + aAlpha = 100; + } + + Gfx_SetupDL42_Overlay(play->state.gfxCtx); + + InterfaceEx_SetPerspectiveView(play, 25 + R_A_BTN_Y_OFFSET, 70 + R_A_BTN_Y_OFFSET, 192, 237, G_EX_ORIGIN_RIGHT, Y_ORIGIN_TOP); + + gSPClearGeometryMode(OVERLAY_DISP++, G_CULL_BOTH); + gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM); + gDPSetAlphaCompare(OVERLAY_DISP++, G_AC_THRESHOLD); + + Matrix_Translate(0.0f, 0.0f, -38.0f, MTXMODE_NEW); + Matrix_Scale(1.0f, 1.0f, 1.0f, MTXMODE_APPLY); + Matrix_RotateXFApply(interfaceCtx->aButtonRoll / 10000.0f); + + // Draw A button Shadow + gSPMatrix(OVERLAY_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gDPPipeSync(OVERLAY_DISP++); + gSPVertex(OVERLAY_DISP++, &interfaceCtx->actionVtx[4], 4, 0); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 0, 0, aAlpha); + + OVERLAY_DISP = Gfx_DrawTexQuadIA8(OVERLAY_DISP, gButtonBackgroundTex, 32, 32, 0); + + // Draw A Button Colored + gDPPipeSync(OVERLAY_DISP++); + InterfaceEx_SetPerspectiveView(play, 23 + R_A_BTN_Y_OFFSET, 68 + R_A_BTN_Y_OFFSET, 190, 235, G_EX_ORIGIN_RIGHT, Y_ORIGIN_TOP); + gSPVertex(OVERLAY_DISP++, &interfaceCtx->actionVtx[0], 4, 0); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 100, 200, 255, interfaceCtx->aAlpha); + gSP1Quadrangle(OVERLAY_DISP++, 0, 2, 3, 1, 0); + + // Draw A Button Do-Action + gDPPipeSync(OVERLAY_DISP++); + InterfaceEx_SetPerspectiveView(play, 23 + R_A_BTN_Y_OFFSET, 68 + R_A_BTN_Y_OFFSET, 190, 235, G_EX_ORIGIN_RIGHT, Y_ORIGIN_TOP); + gSPSetGeometryMode(OVERLAY_DISP++, G_CULL_BACK); + gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0, PRIMITIVE, + ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->aAlpha); + gDPSetEnvColor(OVERLAY_DISP++, 0, 0, 0, 0); + + Matrix_Translate(0.0f, 0.0f, D_801BF9CC[gSaveContext.options.language] / 10.0f, MTXMODE_NEW); + Matrix_Scale(1.0f, 1.0f, 1.0f, MTXMODE_APPLY); + Matrix_RotateXFApply(interfaceCtx->aButtonRoll / 10000.0f); + gSPMatrix(OVERLAY_DISP++, Matrix_NewMtx(play->state.gfxCtx), G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPVertex(OVERLAY_DISP++, &interfaceCtx->actionVtx[8], 4, 0); + + // Draw Action Label + if (((interfaceCtx->aButtonState <= A_BTN_STATE_1) || (interfaceCtx->aButtonState == A_BTN_STATE_3))) { + OVERLAY_DISP = Gfx_DrawTexQuad4b(OVERLAY_DISP, interfaceCtx->doActionSegment, 3, DO_ACTION_TEX_WIDTH, + DO_ACTION_TEX_HEIGHT, 0); + } else { + OVERLAY_DISP = Gfx_DrawTexQuad4b(OVERLAY_DISP, interfaceCtx->doActionSegment + DO_ACTION_TEX_SIZE, 3, + DO_ACTION_TEX_WIDTH, DO_ACTION_TEX_HEIGHT, 0); + } + + CLOSE_DISPS(play->state.gfxCtx); +} + +extern s16 D_801BFB04[]; +extern s16 D_801BFB0C[]; + +// Modify item ammo count drawing to use the extended GBI texture rectangles for widescreen support +void Interface_DrawAmmoCount(PlayState* play, s16 button, s16 alpha) { + u8 i; + u16 ammo; + + OPEN_DISPS(play->state.gfxCtx); + + i = ((void)0, GET_CUR_FORM_BTN_ITEM(button)); + + if ((i == ITEM_DEKU_STICK) || (i == ITEM_DEKU_NUT) || (i == ITEM_BOMB) || (i == ITEM_BOW) || + ((i >= ITEM_BOW_FIRE) && (i <= ITEM_BOW_LIGHT)) || (i == ITEM_BOMBCHU) || (i == ITEM_POWDER_KEG) || + (i == ITEM_MAGIC_BEANS) || (i == ITEM_PICTOGRAPH_BOX)) { + + if ((i >= ITEM_BOW_FIRE) && (i <= ITEM_BOW_LIGHT)) { + i = ITEM_BOW; + } + + ammo = AMMO(i); + + if (i == ITEM_PICTOGRAPH_BOX) { + if (!CHECK_QUEST_ITEM(QUEST_PICTOGRAPH)) { + ammo = 0; + } else { + ammo = 1; + } + } + + gDPPipeSync(OVERLAY_DISP++); + + if ((button == EQUIP_SLOT_B) && (gSaveContext.minigameStatus == MINIGAME_STATUS_ACTIVE)) { + ammo = play->interfaceCtx.minigameAmmo; + } else if ((button == EQUIP_SLOT_B) && (play->unk_1887C > 1)) { + ammo = play->unk_1887C - 1; + } else if (((i == ITEM_BOW) && (AMMO(i) == CUR_CAPACITY(UPG_QUIVER))) || + ((i == ITEM_BOMB) && (AMMO(i) == CUR_CAPACITY(UPG_BOMB_BAG))) || + ((i == ITEM_DEKU_STICK) && (AMMO(i) == CUR_CAPACITY(UPG_DEKU_STICKS))) || + ((i == ITEM_DEKU_NUT) && (AMMO(i) == CUR_CAPACITY(UPG_DEKU_NUTS))) || + ((i == ITEM_BOMBCHU) && (AMMO(i) == CUR_CAPACITY(UPG_BOMB_BAG))) || + ((i == ITEM_POWDER_KEG) && (ammo == 1)) || ((i == ITEM_PICTOGRAPH_BOX) && (ammo == 1)) || + ((i == ITEM_MAGIC_BEANS) && (ammo == 20))) { + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 120, 255, 0, alpha); + } + + if ((u32)ammo == 0) { + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 100, 100, 100, alpha); + } + + for (i = 0; ammo >= 10; i++) { + ammo -= 10; + } + + // Draw upper digit (tens) + if ((u32)i != 0) { + OVERLAY_DISP = GfxEx_DrawTexRectIA8(OVERLAY_DISP, ((u8*)gAmmoDigit0Tex + ((8 * 8) * i)), 8, 8, + D_801BFB04[button], D_801BFB0C[button], 8, 8, 1 << 10, 1 << 10, G_EX_ORIGIN_RIGHT, Y_ORIGIN_TOP); + } + + // Draw lower digit (ones) + OVERLAY_DISP = GfxEx_DrawTexRectIA8(OVERLAY_DISP, ((u8*)gAmmoDigit0Tex + ((8 * 8) * ammo)), 8, 8, + D_801BFB04[button] + 6, D_801BFB0C[button], 8, 8, 1 << 10, 1 << 10, G_EX_ORIGIN_RIGHT, Y_ORIGIN_TOP); + } + + CLOSE_DISPS(play->state.gfxCtx); +} + +// Modify magic meter drawing +extern u8 gMagicMeterEndTex[]; +extern u8 gMagicMeterFillTex[]; +extern u8 gMagicMeterMidTex[]; + +extern s16 sMagicMeterOutlinePrimBlue; +extern s16 sMagicMeterOutlinePrimGreen; +extern s16 sMagicMeterOutlinePrimRed; + +void Magic_DrawMeter(PlayState* play) { + InterfaceContext* interfaceCtx = &play->interfaceCtx; + s16 magicBarY; + + OPEN_DISPS(play->state.gfxCtx); + + if (gSaveContext.save.saveInfo.playerData.magicLevel != 0) { + if (gSaveContext.save.saveInfo.playerData.healthCapacity > 0xA0) { + magicBarY = 42; // two rows of hearts + } else { + magicBarY = 34; // one row of hearts + } + + Gfx_SetupDL39_Overlay(play->state.gfxCtx); + + gDPSetEnvColor(OVERLAY_DISP++, 100, 50, 50, 255); + + OVERLAY_DISP = GfxEx_DrawTexRectIA8_DropShadow( + OVERLAY_DISP, gMagicMeterEndTex, 8, 16, 18, magicBarY, 8, 16, 1 << 10, 1 << 10, sMagicMeterOutlinePrimRed, + sMagicMeterOutlinePrimGreen, sMagicMeterOutlinePrimBlue, interfaceCtx->magicAlpha, G_EX_ORIGIN_LEFT, Y_ORIGIN_TOP); + OVERLAY_DISP = GfxEx_DrawTexRectIA8_DropShadow(OVERLAY_DISP, gMagicMeterMidTex, 24, 16, 26, magicBarY, + ((void)0, gSaveContext.magicCapacity), 16, 1 << 10, 1 << 10, + sMagicMeterOutlinePrimRed, sMagicMeterOutlinePrimGreen, + sMagicMeterOutlinePrimBlue, interfaceCtx->magicAlpha, G_EX_ORIGIN_LEFT, Y_ORIGIN_TOP); + OVERLAY_DISP = GfxEx_DrawTexRectIA8_DropShadowOffset( + OVERLAY_DISP, gMagicMeterEndTex, 8, 16, ((void)0, gSaveContext.magicCapacity) + 26, magicBarY, 8, 16, + 1 << 10, 1 << 10, sMagicMeterOutlinePrimRed, sMagicMeterOutlinePrimGreen, sMagicMeterOutlinePrimBlue, + interfaceCtx->magicAlpha, 3, 0x100, G_EX_ORIGIN_LEFT, Y_ORIGIN_TOP); + + gDPPipeSync(OVERLAY_DISP++); + gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, 0, 0, 0, PRIMITIVE, PRIMITIVE, + ENVIRONMENT, TEXEL0, ENVIRONMENT, 0, 0, 0, PRIMITIVE); + gDPSetEnvColor(OVERLAY_DISP++, 0, 0, 0, 255); + + if (gSaveContext.magicState == MAGIC_STATE_METER_FLASH_2) { + // Yellow part of the meter indicating the amount of magic to be subtracted + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 250, 250, 0, interfaceCtx->magicAlpha); + gDPLoadTextureBlock_4b(OVERLAY_DISP++, gMagicMeterFillTex, G_IM_FMT_I, 16, 16, 0, G_TX_NOMIRROR | G_TX_WRAP, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); + gEXTextureRectangle(OVERLAY_DISP++, G_EX_ORIGIN_LEFT, G_EX_ORIGIN_LEFT, + adjust_x(26, G_EX_ORIGIN_LEFT) * 4, adjust_y(magicBarY + 3, Y_ORIGIN_TOP) << 2, + adjust_x(gSaveContext.save.saveInfo.playerData.magic + 26, G_EX_ORIGIN_LEFT) * 4, adjust_y(magicBarY + 10, Y_ORIGIN_TOP) << 2, + G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); + + // Fill the rest of the meter with the normal magic color + gDPPipeSync(OVERLAY_DISP++); + if (CHECK_WEEKEVENTREG(WEEKEVENTREG_DRANK_CHATEAU_ROMANI)) { + // Blue magic + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 0, 200, interfaceCtx->magicAlpha); + } else { + // Green magic (default) + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 200, 0, interfaceCtx->magicAlpha); + } + + gEXTextureRectangle( + OVERLAY_DISP++, G_EX_ORIGIN_LEFT, G_EX_ORIGIN_LEFT, + adjust_x(26, G_EX_ORIGIN_LEFT) * 4, adjust_y(magicBarY + 3, Y_ORIGIN_TOP) << 2, + adjust_x(gSaveContext.save.saveInfo.playerData.magic - gSaveContext.magicToConsume + 26, G_EX_ORIGIN_LEFT) * 4, adjust_y(magicBarY + 10, Y_ORIGIN_TOP) << 2, + G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); + } else { + // Fill the whole meter with the normal magic color + if (CHECK_WEEKEVENTREG(WEEKEVENTREG_DRANK_CHATEAU_ROMANI)) { + // Blue magic + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 0, 200, interfaceCtx->magicAlpha); + } else { + // Green magic (default) + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 200, 0, interfaceCtx->magicAlpha); + } + + gDPLoadTextureBlock_4b(OVERLAY_DISP++, gMagicMeterFillTex, G_IM_FMT_I, 16, 16, 0, G_TX_NOMIRROR | G_TX_WRAP, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); + gEXTextureRectangle(OVERLAY_DISP++, G_EX_ORIGIN_LEFT, G_EX_ORIGIN_LEFT, + adjust_x(26, G_EX_ORIGIN_LEFT) * 4, adjust_y(magicBarY + 3, Y_ORIGIN_TOP) << 2, + adjust_x(gSaveContext.save.saveInfo.playerData.magic + 26, G_EX_ORIGIN_LEFT) * 4, adjust_y(magicBarY + 10, Y_ORIGIN_TOP) << 2, + G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); + } + } + + CLOSE_DISPS(play->state.gfxCtx); +} + +// Modify life meter drawing +extern u8 gHeartEmptyTex[]; +extern u8 gHeartFullTex[]; +extern u8 gHeartHalfTex[]; +extern u8 gHeartQuarterTex[]; +extern u8 gHeartShapeTex[]; +extern u8 gHeartThreeQuarterTex[]; + +static TexturePtr sHeartTextures[] = { + gHeartFullTex, gHeartQuarterTex, gHeartQuarterTex, gHeartQuarterTex, + gHeartQuarterTex, gHeartQuarterTex, gHeartHalfTex, gHeartHalfTex, + gHeartHalfTex, gHeartHalfTex, gHeartHalfTex, gHeartThreeQuarterTex, + gHeartThreeQuarterTex, gHeartThreeQuarterTex, gHeartThreeQuarterTex, gHeartThreeQuarterTex, +}; + +extern u8 gDefenseHeartEmptyTex[]; +extern u8 gDefenseHeartFullTex[]; +extern u8 gDefenseHeartHalfTex[]; +extern u8 gDefenseHeartQuarterTex[]; +extern u8 gDefenseHeartThreeQuarterTex[]; + +static TexturePtr sHeartDDTextures[] = { + gDefenseHeartFullTex, gDefenseHeartQuarterTex, gDefenseHeartQuarterTex, + gDefenseHeartQuarterTex, gDefenseHeartQuarterTex, gDefenseHeartQuarterTex, + gDefenseHeartHalfTex, gDefenseHeartHalfTex, gDefenseHeartHalfTex, + gDefenseHeartHalfTex, gDefenseHeartHalfTex, gDefenseHeartThreeQuarterTex, + gDefenseHeartThreeQuarterTex, gDefenseHeartThreeQuarterTex, gDefenseHeartThreeQuarterTex, + gDefenseHeartThreeQuarterTex, +}; + +extern s16 sBeatingHeartsDDPrim[3]; +extern s16 sBeatingHeartsDDEnv[3]; +extern s16 sHeartsDDPrim[2][3]; +extern s16 sHeartsDDEnv[2][3]; + +void LifeMeter_Draw(PlayState* play) { + s32 pad[5]; + TexturePtr heartTex; + s32 curColorSet; + f32 offsetX; + f32 offsetY; + s32 i; + f32 posY; + f32 posX; + f32 halfTexSize; + f32 temp_f4; + GraphicsContext* gfxCtx = play->state.gfxCtx; + InterfaceContext* interfaceCtx = &play->interfaceCtx; + Vtx* beatingHeartVtx = interfaceCtx->beatingHeartVtx; + s32 fractionHeartCount = gSaveContext.save.saveInfo.playerData.health % 0x10; + s16 healthCapacity = gSaveContext.save.saveInfo.playerData.healthCapacity / 0x10; + s16 fullHeartCount = gSaveContext.save.saveInfo.playerData.health / 0x10; + s32 pad2; + f32 lifesize = interfaceCtx->lifeSizeChange * 0.1f; + u32 curCombineModeSet = 0; + TexturePtr temp = NULL; + s32 ddCount = gSaveContext.save.saveInfo.inventory.defenseHearts - 1; + + OPEN_DISPS(gfxCtx); + + if ((gSaveContext.save.saveInfo.playerData.health % 0x10) == 0) { + fullHeartCount--; + } + + offsetY = 0.0f; + offsetX = 0.0f; + curColorSet = -1; + + for (i = 0; i < healthCapacity; i++) { + if ((ddCount < 0) || (ddCount < i)) { + if (i < fullHeartCount) { + if (curColorSet != 0) { + curColorSet = 0; + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, interfaceCtx->heartsPrimR[0], interfaceCtx->heartsPrimG[0], + interfaceCtx->heartsPrimB[0], interfaceCtx->healthAlpha); + gDPSetEnvColor(OVERLAY_DISP++, interfaceCtx->heartsEnvR[0], interfaceCtx->heartsEnvG[0], + interfaceCtx->heartsEnvB[0], 255); + } + } else if (i == fullHeartCount) { + if (curColorSet != 1) { + curColorSet = 1; + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, interfaceCtx->beatingHeartPrim[0], + interfaceCtx->beatingHeartPrim[1], interfaceCtx->beatingHeartPrim[2], + interfaceCtx->healthAlpha); + gDPSetEnvColor(OVERLAY_DISP++, interfaceCtx->beatingHeartEnv[0], interfaceCtx->beatingHeartEnv[1], + interfaceCtx->beatingHeartEnv[2], 255); + } + } else if (fullHeartCount < i) { + if (curColorSet != 2) { + curColorSet = 2; + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, interfaceCtx->heartsPrimR[0], interfaceCtx->heartsPrimG[0], + interfaceCtx->heartsPrimB[0], interfaceCtx->healthAlpha); + gDPSetEnvColor(OVERLAY_DISP++, interfaceCtx->heartsEnvR[0], interfaceCtx->heartsEnvG[0], + interfaceCtx->heartsEnvB[0], 255); + } + } else { + if (curColorSet != 3) { + curColorSet = 3; + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, interfaceCtx->heartsPrimR[1], interfaceCtx->heartsPrimG[1], + interfaceCtx->heartsPrimB[1], interfaceCtx->healthAlpha); + gDPSetEnvColor(OVERLAY_DISP++, interfaceCtx->heartsEnvR[1], interfaceCtx->heartsEnvG[1], + interfaceCtx->heartsEnvB[1], 255); + } + } + + if (i < fullHeartCount) { + heartTex = gHeartFullTex; + } else if (i == fullHeartCount) { + heartTex = sHeartTextures[fractionHeartCount]; + } else { + heartTex = gHeartEmptyTex; + } + } else { + if (i < fullHeartCount) { + if (curColorSet != 4) { + curColorSet = 4; + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, sHeartsDDPrim[0][0], sHeartsDDPrim[0][1], sHeartsDDPrim[0][2], + interfaceCtx->healthAlpha); + gDPSetEnvColor(OVERLAY_DISP++, sHeartsDDEnv[0][0], sHeartsDDEnv[0][1], sHeartsDDEnv[0][2], 255); + } + } else if (i == fullHeartCount) { + if (curColorSet != 5) { + curColorSet = 5; + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, sBeatingHeartsDDPrim[0], sBeatingHeartsDDPrim[1], + sBeatingHeartsDDPrim[2], interfaceCtx->healthAlpha); + gDPSetEnvColor(OVERLAY_DISP++, sBeatingHeartsDDEnv[0], sBeatingHeartsDDEnv[1], + sBeatingHeartsDDEnv[2], 255); + } + } else if (i > fullHeartCount) { + if (curColorSet != 6) { + curColorSet = 6; + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, sHeartsDDPrim[0][0], sHeartsDDPrim[0][1], sHeartsDDPrim[0][2], + interfaceCtx->healthAlpha); + gDPSetEnvColor(OVERLAY_DISP++, sHeartsDDEnv[0][0], sHeartsDDEnv[0][1], sHeartsDDEnv[0][2], 255); + } + } else if (curColorSet != 7) { + curColorSet = 7; + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, sHeartsDDPrim[1][0], sHeartsDDPrim[1][1], sHeartsDDPrim[1][2], + interfaceCtx->healthAlpha); + gDPSetEnvColor(OVERLAY_DISP++, sHeartsDDEnv[1][0], sHeartsDDEnv[1][1], sHeartsDDEnv[1][2], 255); + } + if (i < fullHeartCount) { + heartTex = gDefenseHeartFullTex; + } else if (i == fullHeartCount) { + heartTex = sHeartDDTextures[fractionHeartCount]; + } else { + heartTex = gDefenseHeartEmptyTex; + } + } + + if (temp != heartTex) { + temp = heartTex; + gDPLoadTextureBlock(OVERLAY_DISP++, heartTex, G_IM_FMT_IA, G_IM_SIZ_8b, 16, 16, 0, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, + G_TX_NOLOD, G_TX_NOLOD); + } + + if (i != fullHeartCount) { + if ((ddCount < 0) || (i > ddCount)) { + if (curCombineModeSet != 1) { + curCombineModeSet = 1; + Gfx_SetupDL39_Overlay(gfxCtx); + gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, + 0, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0); + } + } else if (curCombineModeSet != 3) { + curCombineModeSet = 3; + Gfx_SetupDL39_Overlay(gfxCtx); + gDPSetCombineLERP(OVERLAY_DISP++, ENVIRONMENT, PRIMITIVE, TEXEL0, PRIMITIVE, TEXEL0, 0, PRIMITIVE, 0, + ENVIRONMENT, PRIMITIVE, TEXEL0, PRIMITIVE, TEXEL0, 0, PRIMITIVE, 0); + } + posY = 26.0f + offsetY; + posX = 30.0f + offsetX; + temp_f4 = 1.0f; + temp_f4 /= 0.68f; + temp_f4 *= 1 << 10; + halfTexSize = 8.0f; + halfTexSize *= 0.68f; + gEXTextureRectangle(OVERLAY_DISP++, G_EX_ORIGIN_LEFT, G_EX_ORIGIN_LEFT, + adjust_x(posX - halfTexSize, G_EX_ORIGIN_LEFT) * 4, adjust_y(posY - halfTexSize, Y_ORIGIN_TOP) * 4, + adjust_x(posX + halfTexSize, G_EX_ORIGIN_LEFT) * 4, adjust_y(posY + halfTexSize, Y_ORIGIN_TOP) * 4, + G_TX_RENDERTILE, 0, 0, (s32)temp_f4, (s32)temp_f4); + } else { + Mtx* mtx; + + if ((ddCount < 0) || (ddCount < i)) { + if (curCombineModeSet != 2) { + curCombineModeSet = 2; + Gfx_SetupDL42_Overlay(gfxCtx); + gDPSetCombineLERP(OVERLAY_DISP++, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, + 0, PRIMITIVE, ENVIRONMENT, TEXEL0, ENVIRONMENT, TEXEL0, 0, PRIMITIVE, 0); + gDPSetAlphaCompare(OVERLAY_DISP++, G_AC_THRESHOLD); + } + } else { + if (curCombineModeSet != 4) { + curCombineModeSet = 4; + Gfx_SetupDL42_Overlay(gfxCtx); + gDPSetCombineLERP(OVERLAY_DISP++, ENVIRONMENT, PRIMITIVE, TEXEL0, PRIMITIVE, TEXEL0, 0, PRIMITIVE, + 0, ENVIRONMENT, PRIMITIVE, TEXEL0, PRIMITIVE, TEXEL0, 0, PRIMITIVE, 0); + gDPSetAlphaCompare(OVERLAY_DISP++, G_AC_THRESHOLD); + } + } + mtx = GRAPH_ALLOC(gfxCtx, sizeof(Mtx)); + Vp* vp = GRAPH_ALLOC(gfxCtx, sizeof(Vp)); + View_ViewportToVp(vp, &play->view.viewport); + + vp->vp.vtrans[0] = adjust_x_fractional(vp->vp.vtrans[0], G_EX_ORIGIN_LEFT); + vp->vp.vtrans[1] = adjust_y_fractional(vp->vp.vtrans[1], Y_ORIGIN_TOP); + + gEXViewport(OVERLAY_DISP++, G_EX_ORIGIN_LEFT, vp); + + Mtx_SetTranslateScaleMtx(mtx, 1.0f - (0.32f * lifesize), 1.0f - (0.32f * lifesize), + 1.0f - (0.32f * lifesize), -130.0f + offsetX, 94.5f - offsetY, 0.0f); + gSPMatrix(OVERLAY_DISP++, mtx, G_MTX_LOAD | G_MTX_MODELVIEW); + gSPVertex(OVERLAY_DISP++, beatingHeartVtx, 4, 0); + gSP1Quadrangle(OVERLAY_DISP++, 0, 2, 3, 1, 0); + + // Restore the old viewport + Vp* old_vp = GRAPH_ALLOC(gfxCtx, sizeof(Vp)); + View_ViewportToVp(old_vp, &play->view.viewport); + gSPViewport(OVERLAY_DISP++, old_vp); + } + + offsetX += 10.0f; + if (i == 9) { + offsetY += 10.0f; + offsetX = 0.0f; + } + } + CLOSE_DISPS(gfxCtx); +} + +// Modify interface drawing (rupees, key counter, etc.) +extern TexturePtr sStoryTextures[]; +extern TexturePtr sStoryTLUTs[]; + +extern Color_RGB16 sRupeeCounterIconPrimColors[]; +extern Color_RGB16 sRupeeCounterIconEnvColors[]; +extern u8 gRupeeCounterIconTex[]; +extern s16 sRupeeDigitsFirst[]; +extern s16 sRupeeDigitsCount[]; + +extern u8 gSmallKeyCounterIconTex[]; +extern u8 gCounterDigit0Tex[]; +extern u8 gGoldSkulltulaCounterIconTex[]; + +extern Color_RGB16 sMinigameCountdownPrimColors[]; +extern TexturePtr sMinigameCountdownTextures[]; +extern s16 sMinigameCountdownTexWidths[]; + +extern u8 gPictoBoxFocusBorderTex[]; +extern u8 gPictoBoxFocusIconTex[]; +extern u8 gPictoBoxFocusTextTex[]; + +static Gfx sScreenFillSetupDL[] = { + gsDPPipeSync(), + gsSPClearGeometryMode(G_ZBUFFER | G_SHADE | G_CULL_BOTH | G_FOG | G_LIGHTING | G_TEXTURE_GEN | + G_TEXTURE_GEN_LINEAR | G_LOD | G_SHADING_SMOOTH), + gsDPSetOtherMode(G_AD_DISABLE | G_CD_MAGICSQ | G_CK_NONE | G_TC_FILT | G_TF_BILERP | G_TT_NONE | G_TL_TILE | + G_TD_CLAMP | G_TP_NONE | G_CYC_1CYCLE | G_PM_1PRIMITIVE, + G_AC_NONE | G_ZS_PIXEL | G_RM_CLD_SURF | G_RM_CLD_SURF2), + gsDPSetCombineMode(G_CC_PRIMITIVE, G_CC_PRIMITIVE), + gsSPEndDisplayList(), +}; + +void Interface_DrawBButtonIcons(PlayState* play); +void Interface_DrawCButtonIcons(PlayState* play); +void Interface_DrawClock(PlayState* play); +void Interface_DrawMinigameIcons(PlayState* play); +void Interface_DrawPauseMenuEquippingIcons(PlayState* play); +void Interface_DrawPerfectLetters(PlayState* play); +void Interface_DrawTimers(PlayState* play); +void Interface_SetOrthoView(InterfaceContext* interfaceCtx); +void Interface_SetVertices(PlayState* play); + +void Interface_Draw(PlayState* play) { + s32 pad; + InterfaceContext* interfaceCtx = &play->interfaceCtx; + Player* player = GET_PLAYER(play); + Gfx* gfx; + s16 sp2CE; + s16 sp2CC; + s16 sp2CA; + s16 sp2C8; + PauseContext* pauseCtx = &play->pauseCtx; + f32 minigameCountdownScale; + s16 counterDigits[4]; + s16 magicAlpha; + + OPEN_DISPS(play->state.gfxCtx); + + gSPSegment(OVERLAY_DISP++, 0x02, interfaceCtx->parameterSegment); + gSPSegment(OVERLAY_DISP++, 0x09, interfaceCtx->doActionSegment); + gSPSegment(OVERLAY_DISP++, 0x08, interfaceCtx->iconItemSegment); + gSPSegment(OVERLAY_DISP++, 0x0B, interfaceCtx->mapSegment); + + if (pauseCtx->debugEditor == DEBUG_EDITOR_NONE) { + Interface_SetVertices(play); + Interface_SetOrthoView(interfaceCtx); + + // Draw Grandma's Story + if (interfaceCtx->storyDmaStatus == STORY_DMA_DONE) { + gSPSegment(OVERLAY_DISP++, 0x07, interfaceCtx->storySegment); + Gfx_SetupDL39_Opa(play->state.gfxCtx); + + gDPSetTextureFilter(POLY_OPA_DISP++, G_TF_POINT); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, pauseCtx->alpha); + + // Load in Grandma's Story + gSPLoadUcodeL(OVERLAY_DISP++, gspS2DEX2_fifo); + gfx = OVERLAY_DISP; + Prerender_DrawBackground2D(&gfx, sStoryTextures[interfaceCtx->storyType], + sStoryTLUTs[interfaceCtx->storyType], SCREEN_WIDTH, SCREEN_HEIGHT, 2, 1, 0x8000, + 0x100, 0.0f, 0.0f, 1.0f, 1.0f, 0); + OVERLAY_DISP = gfx; + gSPLoadUcode(OVERLAY_DISP++, SysUcode_GetUCode(), SysUcode_GetUCodeData()); + + gDPPipeSync(OVERLAY_DISP++); + + // Fill the screen with a black rectangle + gDPSetRenderMode(OVERLAY_DISP++, G_RM_XLU_SURF, G_RM_XLU_SURF2); + gDPSetCombineMode(OVERLAY_DISP++, G_CC_PRIMITIVE, G_CC_PRIMITIVE); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 0, 0, R_STORY_FILL_SCREEN_ALPHA); + gDPFillRectangle(OVERLAY_DISP++, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); + } + + gEXSetScissor(OVERLAY_DISP++, G_SC_NON_INTERLACE, G_EX_ORIGIN_LEFT, G_EX_ORIGIN_RIGHT, 0, 0, 0, SCREEN_HEIGHT); + LifeMeter_Draw(play); + + Gfx_SetupDL39_Overlay(play->state.gfxCtx); + + // Draw Rupee Icon + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, sRupeeCounterIconPrimColors[CUR_UPG_VALUE(UPG_WALLET)].r, + sRupeeCounterIconPrimColors[CUR_UPG_VALUE(UPG_WALLET)].g, + sRupeeCounterIconPrimColors[CUR_UPG_VALUE(UPG_WALLET)].b, interfaceCtx->magicAlpha); + gDPSetEnvColor(OVERLAY_DISP++, sRupeeCounterIconEnvColors[CUR_UPG_VALUE(UPG_WALLET)].r, + sRupeeCounterIconEnvColors[CUR_UPG_VALUE(UPG_WALLET)].g, + sRupeeCounterIconEnvColors[CUR_UPG_VALUE(UPG_WALLET)].b, 255); + OVERLAY_DISP = + GfxEx_DrawTexRectIA8(OVERLAY_DISP, gRupeeCounterIconTex, 16, 16, 26, 206, 16, 16, 1 << 10, 1 << 10, G_EX_ORIGIN_LEFT, Y_ORIGIN_BOTTOM); + + switch (play->sceneId) { + case SCENE_INISIE_N: + case SCENE_INISIE_R: + case SCENE_MITURIN: + case SCENE_HAKUGIN: + case SCENE_SEA: + if (DUNGEON_KEY_COUNT(gSaveContext.mapIndex) >= 0) { + // Small Key Icon + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 200, 230, 255, interfaceCtx->magicAlpha); + gDPSetEnvColor(OVERLAY_DISP++, 0, 0, 20, 255); + OVERLAY_DISP = GfxEx_DrawTexRectIA8(OVERLAY_DISP, gSmallKeyCounterIconTex, 16, 16, 26, 190, 16, 16, + 1 << 10, 1 << 10, G_EX_ORIGIN_LEFT, Y_ORIGIN_BOTTOM); + + // Small Key Counter + gDPPipeSync(OVERLAY_DISP++); + gDPSetCombineLERP(OVERLAY_DISP++, 0, 0, 0, PRIMITIVE, TEXEL0, 0, PRIMITIVE, 0, 0, 0, 0, PRIMITIVE, + TEXEL0, 0, PRIMITIVE, 0); + + counterDigits[2] = 0; + counterDigits[3] = DUNGEON_KEY_COUNT(gSaveContext.mapIndex); + + while (counterDigits[3] >= 10) { + counterDigits[2]++; + counterDigits[3] -= 10; + } + + sp2CA = 42; + + if (counterDigits[2] != 0) { + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 0, 0, interfaceCtx->magicAlpha); + + OVERLAY_DISP = + GfxEx_DrawTexRectI8(OVERLAY_DISP, (u8*)gCounterDigit0Tex + (8 * 16 * counterDigits[2]), 8, 16, + 43, 191, 8, 16, 1 << 10, 1 << 10, G_EX_ORIGIN_LEFT, Y_ORIGIN_BOTTOM); + + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->magicAlpha); + gEXTextureRectangle(OVERLAY_DISP++, G_EX_ORIGIN_LEFT, G_EX_ORIGIN_LEFT, + adjust_x(42, G_EX_ORIGIN_LEFT) * 4, adjust_y(190, Y_ORIGIN_BOTTOM) * 4, + adjust_x(50, G_EX_ORIGIN_LEFT) * 4, adjust_y(206, Y_ORIGIN_BOTTOM) * 4, + G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); + + sp2CA += 8; + } + + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 0, 0, interfaceCtx->magicAlpha); + + OVERLAY_DISP = GfxEx_DrawTexRectI8(OVERLAY_DISP, (u8*)gCounterDigit0Tex + (8 * 16 * counterDigits[3]), + 8, 16, sp2CA + 1, 191, 8, 16, 1 << 10, 1 << 10, G_EX_ORIGIN_LEFT, Y_ORIGIN_BOTTOM); + + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->magicAlpha); + gEXTextureRectangle(OVERLAY_DISP++, G_EX_ORIGIN_LEFT, G_EX_ORIGIN_LEFT, + adjust_x(sp2CA, G_EX_ORIGIN_LEFT) * 4, adjust_y(190, Y_ORIGIN_BOTTOM) * 4, + adjust_x(sp2CA + 8, G_EX_ORIGIN_LEFT) * 4, adjust_y(206, Y_ORIGIN_BOTTOM) * 4, + G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); + } + break; + + case SCENE_KINSTA1: + case SCENE_KINDAN2: + // Gold Skulltula Icon + gDPPipeSync(OVERLAY_DISP++); + gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->magicAlpha); + gDPSetEnvColor(OVERLAY_DISP++, 0, 0, 0, 255); + gDPLoadTextureBlock(OVERLAY_DISP++, gGoldSkulltulaCounterIconTex, G_IM_FMT_RGBA, G_IM_SIZ_32b, 24, 24, + 0, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, + G_TX_NOLOD, G_TX_NOLOD); + gEXTextureRectangle(OVERLAY_DISP++, G_EX_ORIGIN_LEFT, G_EX_ORIGIN_LEFT, + adjust_x(20, G_EX_ORIGIN_LEFT), adjust_y(187, Y_ORIGIN_BOTTOM) * 4, + adjust_x(44, G_EX_ORIGIN_LEFT), adjust_y(205, Y_ORIGIN_BOTTOM) * 4, + G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); + + // Gold Skulluta Counter + gDPPipeSync(OVERLAY_DISP++); + gDPSetCombineLERP(OVERLAY_DISP++, 0, 0, 0, PRIMITIVE, TEXEL0, 0, PRIMITIVE, 0, 0, 0, 0, PRIMITIVE, + TEXEL0, 0, PRIMITIVE, 0); + + counterDigits[2] = 0; + counterDigits[3] = Inventory_GetSkullTokenCount(play->sceneId); + + while (counterDigits[3] >= 10) { + counterDigits[2]++; + counterDigits[3] -= 10; + } + + sp2CA = 42; + + if (counterDigits[2] != 0) { + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 0, 0, interfaceCtx->magicAlpha); + + OVERLAY_DISP = GfxEx_DrawTexRectI8(OVERLAY_DISP, (u8*)gCounterDigit0Tex + (8 * 16 * counterDigits[2]), + 8, 16, 43, 191, 8, 16, 1 << 10, 1 << 10, G_EX_ORIGIN_LEFT, Y_ORIGIN_BOTTOM); + + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->magicAlpha); + gEXTextureRectangle(OVERLAY_DISP++, G_EX_ORIGIN_LEFT, G_EX_ORIGIN_LEFT, + adjust_x(42, G_EX_ORIGIN_LEFT), adjust_y(190, Y_ORIGIN_BOTTOM) * 4, + adjust_x(50, G_EX_ORIGIN_LEFT), adjust_y(206, Y_ORIGIN_BOTTOM) * 4, + G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); + + sp2CA += 8; + } + + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 0, 0, interfaceCtx->magicAlpha); + + OVERLAY_DISP = GfxEx_DrawTexRectI8(OVERLAY_DISP, (u8*)gCounterDigit0Tex + (8 * 16 * counterDigits[3]), 8, + 16, sp2CA + 1, 191, 8, 16, 1 << 10, 1 << 10, G_EX_ORIGIN_LEFT, Y_ORIGIN_BOTTOM); + + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->magicAlpha); + gEXTextureRectangle(OVERLAY_DISP++, G_EX_ORIGIN_LEFT, G_EX_ORIGIN_LEFT, + adjust_x(sp2CA, G_EX_ORIGIN_LEFT) * 4, adjust_y(190, Y_ORIGIN_BOTTOM) * 4, + adjust_x(sp2CA + 8, G_EX_ORIGIN_LEFT) * 4, adjust_y(206, Y_ORIGIN_BOTTOM) * 4, + G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); + break; + + default: + break; + } + + // Rupee Counter + gDPPipeSync(OVERLAY_DISP++); + gDPSetCombineLERP(OVERLAY_DISP++, 0, 0, 0, PRIMITIVE, TEXEL0, 0, PRIMITIVE, 0, 0, 0, 0, PRIMITIVE, TEXEL0, 0, + PRIMITIVE, 0); + + counterDigits[0] = counterDigits[1] = 0; + counterDigits[2] = gSaveContext.save.saveInfo.playerData.rupees; + + if ((counterDigits[2] > 9999) || (counterDigits[2] < 0)) { + counterDigits[2] &= 0xDDD; + } + + while (counterDigits[2] >= 100) { + counterDigits[0]++; + counterDigits[2] -= 100; + } + + while (counterDigits[2] >= 10) { + counterDigits[1]++; + counterDigits[2] -= 10; + } + + sp2CC = sRupeeDigitsFirst[CUR_UPG_VALUE(UPG_WALLET)]; + sp2C8 = sRupeeDigitsCount[CUR_UPG_VALUE(UPG_WALLET)]; + + magicAlpha = interfaceCtx->magicAlpha; + if (magicAlpha > 180) { + magicAlpha = 180; + } + + for (sp2CE = 0, sp2CA = 42; sp2CE < sp2C8; sp2CE++, sp2CC++, sp2CA += 8) { + gDPPipeSync(OVERLAY_DISP++); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 0, 0, magicAlpha); + + OVERLAY_DISP = GfxEx_DrawTexRectI8(OVERLAY_DISP, (u8*)gCounterDigit0Tex + (8 * 16 * counterDigits[sp2CC]), 8, + 16, sp2CA + 1, 207, 8, 16, 1 << 10, 1 << 10, G_EX_ORIGIN_LEFT, Y_ORIGIN_BOTTOM); + + gDPPipeSync(OVERLAY_DISP++); + + if (gSaveContext.save.saveInfo.playerData.rupees == CUR_CAPACITY(UPG_WALLET)) { + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 120, 255, 0, interfaceCtx->magicAlpha); + } else if (gSaveContext.save.saveInfo.playerData.rupees != 0) { + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 255, interfaceCtx->magicAlpha); + } else { + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 100, 100, 100, interfaceCtx->magicAlpha); + } + + gEXTextureRectangle(OVERLAY_DISP++, G_EX_ORIGIN_LEFT, G_EX_ORIGIN_LEFT, + adjust_x(sp2CA, G_EX_ORIGIN_LEFT) * 4, adjust_y(206, Y_ORIGIN_BOTTOM) * 4, + adjust_x(sp2CA + 8, G_EX_ORIGIN_LEFT) * 4, adjust_y(222, Y_ORIGIN_BOTTOM) * 4, + G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); + } + + Magic_DrawMeter(play); + Minimap_Draw(play); + + if ((R_PAUSE_BG_PRERENDER_STATE != 2) && (R_PAUSE_BG_PRERENDER_STATE != 3)) { + Target_Draw(&play->actorCtx.targetCtx, play); + } + + Gfx_SetupDL39_Overlay(play->state.gfxCtx); + + Interface_DrawItemButtons(play); + + if (player->transformation == GET_PLAYER_FORM) { + Interface_DrawBButtonIcons(play); + } + Interface_DrawCButtonIcons(play); + + Interface_DrawAButton(play); + + Interface_DrawPauseMenuEquippingIcons(play); + + // Draw either the minigame countdown or the three-day clock + if ((play->pauseCtx.state == PAUSE_STATE_OFF) && (play->pauseCtx.debugEditor == DEBUG_EDITOR_NONE)) { + if ((interfaceCtx->minigameState != MINIGAME_STATE_NONE) && + (interfaceCtx->minigameState < MINIGAME_STATE_NO_COUNTDOWN_SETUP)) { + // Minigame Countdown + if (((u32)interfaceCtx->minigameState % 2) == 0) { + + sp2CE = (interfaceCtx->minigameState >> 1) - 1; + minigameCountdownScale = interfaceCtx->minigameCountdownScale / 100.0f; + + if (sp2CE == 3) { + interfaceCtx->actionVtx[40 + 0].v.ob[0] = interfaceCtx->actionVtx[40 + 2].v.ob[0] = -20; + interfaceCtx->actionVtx[40 + 1].v.ob[0] = interfaceCtx->actionVtx[40 + 3].v.ob[0] = + interfaceCtx->actionVtx[40 + 0].v.ob[0] + 40; + interfaceCtx->actionVtx[40 + 1].v.tc[0] = interfaceCtx->actionVtx[40 + 3].v.tc[0] = 40 << 5; + } + + interfaceCtx->actionVtx[40 + 2].v.tc[1] = interfaceCtx->actionVtx[40 + 3].v.tc[1] = 32 << 5; + + Gfx_SetupDL42_Overlay(play->state.gfxCtx); + + gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM); + gDPSetAlphaCompare(OVERLAY_DISP++, G_AC_THRESHOLD); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, sMinigameCountdownPrimColors[sp2CE].r, + sMinigameCountdownPrimColors[sp2CE].g, sMinigameCountdownPrimColors[sp2CE].b, + interfaceCtx->minigameCountdownAlpha); + + Matrix_Translate(0.0f, -40.0f, 0.0f, MTXMODE_NEW); + Matrix_Scale(minigameCountdownScale, minigameCountdownScale, 0.0f, MTXMODE_APPLY); + + gSPMatrix(OVERLAY_DISP++, Matrix_NewMtx(play->state.gfxCtx), + G_MTX_NOPUSH | G_MTX_LOAD | G_MTX_MODELVIEW); + gSPVertex(OVERLAY_DISP++, &interfaceCtx->actionVtx[40], 4, 0); + + OVERLAY_DISP = Gfx_DrawTexQuadIA8(OVERLAY_DISP, sMinigameCountdownTextures[sp2CE], + sMinigameCountdownTexWidths[sp2CE], 32, 0); + } + } else { + Interface_DrawClock(play); + } + } + + // Draw the letters of minigame perfect + if (interfaceCtx->perfectLettersOn) { + Interface_DrawPerfectLetters(play); + } + + Interface_DrawMinigameIcons(play); + Interface_DrawTimers(play); + } + + // Draw pictograph focus icons + if (sPictoState == PICTO_BOX_STATE_LENS) { + + Gfx_SetupDL39_Overlay(play->state.gfxCtx); + + gDPSetAlphaCompare(OVERLAY_DISP++, G_AC_THRESHOLD); + gDPSetRenderMode(OVERLAY_DISP++, G_RM_XLU_SURF, G_RM_XLU_SURF2); + gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 255, 255, 155, 255); + gDPLoadTextureBlock_4b(OVERLAY_DISP++, gPictoBoxFocusBorderTex, G_IM_FMT_IA, 16, 16, 0, G_TX_MIRROR | G_TX_WRAP, + G_TX_MIRROR | G_TX_WRAP, 4, 4, G_TX_NOLOD, G_TX_NOLOD); + + gSPTextureRectangle(OVERLAY_DISP++, R_PICTO_FOCUS_BORDER_TOPLEFT_X << 2, R_PICTO_FOCUS_BORDER_TOPLEFT_Y << 2, + (R_PICTO_FOCUS_BORDER_TOPLEFT_X << 2) + (16 << 2), + (R_PICTO_FOCUS_BORDER_TOPLEFT_Y << 2) + (16 << 2), G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); + gSPTextureRectangle(OVERLAY_DISP++, R_PICTO_FOCUS_BORDER_TOPRIGHT_X << 2, R_PICTO_FOCUS_BORDER_TOPRIGHT_Y << 2, + (R_PICTO_FOCUS_BORDER_TOPRIGHT_X << 2) + (16 << 2), + (R_PICTO_FOCUS_BORDER_TOPRIGHT_Y << 2) + (16 << 2), G_TX_RENDERTILE, 512, 0, 1 << 10, + 1 << 10); + gSPTextureRectangle( + OVERLAY_DISP++, R_PICTO_FOCUS_BORDER_BOTTOMLEFT_X << 2, R_PICTO_FOCUS_BORDER_BOTTOMLEFT_Y << 2, + (R_PICTO_FOCUS_BORDER_BOTTOMLEFT_X << 2) + (16 << 2), (R_PICTO_FOCUS_BORDER_BOTTOMLEFT_Y << 2) + (16 << 2), + G_TX_RENDERTILE, 0, 512, 1 << 10, 1 << 10); + gSPTextureRectangle( + OVERLAY_DISP++, R_PICTO_FOCUS_BORDER_BOTTOMRIGHT_X << 2, R_PICTO_FOCUS_BORDER_BOTTOMRIGHT_Y << 2, + (R_PICTO_FOCUS_BORDER_BOTTOMRIGHT_X << 2) + (16 << 2), + (R_PICTO_FOCUS_BORDER_BOTTOMRIGHT_Y << 2) + (16 << 2), G_TX_RENDERTILE, 512, 512, 1 << 10, 1 << 10); + + gDPLoadTextureBlock_4b(OVERLAY_DISP++, gPictoBoxFocusIconTex, G_IM_FMT_I, 32, 16, 0, G_TX_NOMIRROR | G_TX_WRAP, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); + + gSPTextureRectangle(OVERLAY_DISP++, R_PICTO_FOCUS_ICON_X << 2, R_PICTO_FOCUS_ICON_Y << 2, + (R_PICTO_FOCUS_ICON_X << 2) + 0x80, (R_PICTO_FOCUS_ICON_Y << 2) + (16 << 2), + G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); + + gDPLoadTextureBlock_4b(OVERLAY_DISP++, gPictoBoxFocusTextTex, G_IM_FMT_I, 32, 8, 0, G_TX_NOMIRROR | G_TX_WRAP, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); + + gSPTextureRectangle(OVERLAY_DISP++, R_PICTO_FOCUS_TEXT_X << 2, R_PICTO_FOCUS_TEXT_Y << 2, + (R_PICTO_FOCUS_TEXT_X << 2) + 0x80, (R_PICTO_FOCUS_TEXT_Y << 2) + 0x20, G_TX_RENDERTILE, 0, + 0, 1 << 10, 1 << 10); + } + + // Draw pictograph photo + if (sPictoState >= PICTO_BOX_STATE_SETUP_PHOTO) { + if (!(play->actorCtx.flags & ACTORCTX_FLAG_PICTO_BOX_ON)) { + Play_CompressI8ToI5((play->pictoPhotoI8 != NULL) ? play->pictoPhotoI8 : gWorkBuffer, + (u8*)gSaveContext.pictoPhotoI5, PICTO_PHOTO_WIDTH * PICTO_PHOTO_HEIGHT); + + interfaceCtx->unk_222 = interfaceCtx->unk_224 = 0; + + sPictoState = PICTO_BOX_STATE_OFF; + gSaveContext.hudVisibility = HUD_VISIBILITY_IDLE; + Interface_SetHudVisibility(HUD_VISIBILITY_ALL); + } else { + s16 pictoRectTop; + s16 pictoRectLeft; + + if (sPictoState == PICTO_BOX_STATE_SETUP_PHOTO) { + sPictoState = PICTO_BOX_STATE_PHOTO; + Message_StartTextbox(play, 0xF8, NULL); + Interface_SetHudVisibility(HUD_VISIBILITY_NONE); + player->stateFlags1 |= PLAYER_STATE1_200; + } + + gDPPipeSync(OVERLAY_DISP++); + gDPSetRenderMode(OVERLAY_DISP++, G_RM_XLU_SURF, G_RM_XLU_SURF2); + gDPSetCombineMode(OVERLAY_DISP++, G_CC_PRIMITIVE, G_CC_PRIMITIVE); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 200, 200, 200, 250); + gDPFillRectangle(OVERLAY_DISP++, 70, 22, 251, 151); + + Gfx_SetupDL39_Overlay(play->state.gfxCtx); + + gDPSetRenderMode(OVERLAY_DISP++, G_RM_OPA_SURF, G_RM_OPA_SURF2); + gDPSetCombineMode(OVERLAY_DISP++, G_CC_MODULATEI_PRIM, G_CC_MODULATEI_PRIM); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 250, 160, 160, 255); + + // Picture is offset up by 33 pixels to give room for the message box at the bottom + pictoRectTop = PICTO_PHOTO_TOPLEFT_Y - 33; + for (sp2CC = 0; sp2CC < (PICTO_PHOTO_HEIGHT / 8); sp2CC++, pictoRectTop += 8) { + pictoRectLeft = PICTO_PHOTO_TOPLEFT_X; + gDPLoadTextureBlock(OVERLAY_DISP++, + (u8*)((play->pictoPhotoI8 != NULL) ? play->pictoPhotoI8 : gWorkBuffer) + + (0x500 * sp2CC), + G_IM_FMT_I, G_IM_SIZ_8b, PICTO_PHOTO_WIDTH, 8, 0, G_TX_NOMIRROR | G_TX_WRAP, + G_TX_NOMIRROR | G_TX_WRAP, G_TX_NOMASK, G_TX_NOMASK, G_TX_NOLOD, G_TX_NOLOD); + + gSPTextureRectangle(OVERLAY_DISP++, pictoRectLeft << 2, pictoRectTop << 2, + (pictoRectLeft + PICTO_PHOTO_WIDTH) << 2, (pictoRectTop << 2) + (8 << 2), + G_TX_RENDERTILE, 0, 0, 1 << 10, 1 << 10); + } + } + } + + // Draw over the entire screen (used in gameover) + if (interfaceCtx->screenFillAlpha != 0) { + gDPPipeSync(OVERLAY_DISP++); + gSPDisplayList(OVERLAY_DISP++, sScreenFillSetupDL); + gDPSetPrimColor(OVERLAY_DISP++, 0, 0, 0, 0, 0, interfaceCtx->screenFillAlpha); + gSPDisplayList(OVERLAY_DISP++, D_0E000000.fillRect); + } + + CLOSE_DISPS(play->state.gfxCtx); +} \ No newline at end of file diff --git a/src/game/controls.cpp b/src/game/controls.cpp index 136d77c..6258a96 100644 --- a/src/game/controls.cpp +++ b/src/game/controls.cpp @@ -152,6 +152,30 @@ extern "C" void recomp_get_item_inputs(uint8_t* rdram, recomp_context* ctx) { *buttons_out = cur_buttons; } +extern "C" void recomp_get_camera_inputs(uint8_t* rdram, recomp_context* ctx) { + float* x_out = _arg<0, float*>(rdram, ctx); + float* y_out = _arg<1, float*>(rdram, ctx); + + float x_val = 0.0f; + float y_val = 0.0f; + + // Process controller inputs + std::vector input_states = recomp::get_input_states(); + + for (const recomp::InputState& state : input_states) { + if (const auto* controller_state = std::get_if(&state)) { + x_val += controller_state->axes[recomp::ControllerState::AXIS_RIGHT_X]; + y_val += controller_state->axes[recomp::ControllerState::AXIS_RIGHT_Y]; + } + else if (const auto* mouse_state = std::get_if(&state)) { + // Mouse currently unused + } + } + + *x_out = x_val; + *y_out = y_val; +} + // TODO move this extern "C" void recomp_puts(uint8_t* rdram, recomp_context* ctx) { PTR(char) cur_str = _arg<0, PTR(char)>(rdram, ctx); diff --git a/src/recomp/patch_loading.cpp b/src/recomp/patch_loading.cpp index 541a18b..225bef9 100644 --- a/src/recomp/patch_loading.cpp +++ b/src/recomp/patch_loading.cpp @@ -2,7 +2,7 @@ #include #include #include "recomp.h" -#include "../RecompiledPatches/recomp_overlays.inl" +#include "../../RecompiledPatches/recomp_overlays.inl" void load_special_overlay(const SectionTableEntry& section, int32_t ram); diff --git a/src/ui/ui_renderer.cpp b/src/ui/ui_renderer.cpp index 3d48afc..3c6b42a 100644 --- a/src/ui/ui_renderer.cpp +++ b/src/ui/ui_renderer.cpp @@ -171,6 +171,7 @@ public: RT64::RenderDescriptorSetBuilder sampler_set_builder{}; sampler_set_builder.begin(); sampler_set_builder.addImmutableSampler(1, linearSampler_.get()); + sampler_set_builder.addConstantBuffer(3, 1); // Workaround D3D12 crash due to an empty RT64 descriptor set sampler_set_builder.end(); sampler_set_ = sampler_set_builder.create(render_context->device);