diff --git a/CMakeLists.txt b/CMakeLists.txt index c0cf8a292..367360081 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,8 @@ set(CMAKE_CXX_STANDARD 20 CACHE STRING "The C++ standard to use") set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version" FORCE) project(Ship LANGUAGES C CXX - VERSION 4.0.2) -set(PROJECT_BUILD_NAME "ZHORA CHARLIE" CACHE STRING "") + VERSION 4.0.3) +set(PROJECT_BUILD_NAME "ZHORA DELTA" CACHE STRING "") set(PROJECT_TEAM "github.com/harbourmasters" CACHE STRING "") set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT soh) diff --git a/libultraship/libultraship/Lib/Fast3D/gfx_direct3d_common.cpp b/libultraship/libultraship/Lib/Fast3D/gfx_direct3d_common.cpp index dd17af0a0..b5872d69a 100644 --- a/libultraship/libultraship/Lib/Fast3D/gfx_direct3d_common.cpp +++ b/libultraship/libultraship/Lib/Fast3D/gfx_direct3d_common.cpp @@ -253,6 +253,13 @@ void gfx_direct3d_common_build_shader(char buf[4096], size_t& len, size_t& num_f append_line(buf, &len, "[RootSignature(RS)]"); } append_line(buf, &len, "float4 PSMain(PSInput input, float4 screenSpace : SV_Position) : SV_TARGET {"); + + // Reference approach to color wrapping as per GLideN64 + // Return wrapped value of x in interval [low, high) + // Mod implementation of GLSL sourced from https://registry.khronos.org/OpenGL-Refpages/gl4/html/mod.xhtml + append_line(buf, &len, "#define MOD(x, y) ((x) - (y) * floor((x)/(y)))"); + append_line(buf, &len, "#define WRAP(x, low, high) MOD((x)-(low), (high)-(low)) + (low)"); + for (int i = 0; i < 2; i++) { if (cc_features.used_textures[i]) { len += sprintf(buf + len, " float2 tc%d = input.uv%d;\r\n", i, i); @@ -294,11 +301,18 @@ void gfx_direct3d_common_build_shader(char buf[4096], size_t& len, size_t& num_f append_formula(buf, &len, cc_features.c[c], cc_features.do_single[c][0], cc_features.do_multiply[c][0], cc_features.do_mix[c][0], cc_features.opt_alpha, false, cc_features.opt_alpha); } append_line(buf, &len, ";"); + + if (c == 0) { + append_str(buf, &len, "texel = WRAP(texel, -1.01, 1.01);"); + } } if (cc_features.opt_texture_edge && cc_features.opt_alpha) { append_line(buf, &len, " if (texel.a > 0.19) texel.a = 1.0; else discard;"); } + + append_str(buf, &len, "texel = WRAP(texel, -0.51, 1.51);"); + append_str(buf, &len, "texel = clamp(texel, 0.0, 1.0);"); // TODO discard if alpha is 0? if (cc_features.opt_fog) { if (cc_features.opt_alpha) { diff --git a/libultraship/libultraship/Lib/Fast3D/gfx_opengl.cpp b/libultraship/libultraship/Lib/Fast3D/gfx_opengl.cpp index 1a7a62885..884535542 100644 --- a/libultraship/libultraship/Lib/Fast3D/gfx_opengl.cpp +++ b/libultraship/libultraship/Lib/Fast3D/gfx_opengl.cpp @@ -414,6 +414,10 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad append_line(fs_buf, &fs_len, "void main() {"); + // Reference approach to color wrapping as per GLideN64 + // Return wrapped value of x in interval [low, high) + append_line(fs_buf, &fs_len, "#define WRAP(x, low, high) mod((x)-(low), (high)-(low)) + (low)"); + for (int i = 0; i < 2; i++) { if (cc_features.used_textures[i]) { bool s = cc_features.clamp[i][0], t = cc_features.clamp[i][1]; @@ -448,7 +452,14 @@ static struct ShaderProgram* gfx_opengl_create_and_load_new_shader(uint64_t shad append_formula(fs_buf, &fs_len, cc_features.c[c], cc_features.do_single[c][0], cc_features.do_multiply[c][0], cc_features.do_mix[c][0], cc_features.opt_alpha, false, cc_features.opt_alpha); } append_line(fs_buf, &fs_len, ";"); + + if (c == 0) { + append_str(fs_buf, &fs_len, "texel = WRAP(texel, -1.01, 1.01);"); + } } + + append_str(fs_buf, &fs_len, "texel = WRAP(texel, -0.51, 1.51);"); + append_str(fs_buf, &fs_len, "texel = clamp(texel, 0.0, 1.0);"); // TODO discard if alpha is 0? if (cc_features.opt_fog) { diff --git a/libultraship/libultraship/Window.cpp b/libultraship/libultraship/Window.cpp index 539ed73c9..821f9d87f 100644 --- a/libultraship/libultraship/Window.cpp +++ b/libultraship/libultraship/Window.cpp @@ -480,7 +480,7 @@ namespace Ship { WmApi = &gfx_dxgi_api; #endif #ifdef ENABLE_DX11 - RenderingApi = &gfx_direct3d11_api; + RenderingApi = &gfx_direct3d11_api; WmApi = &gfx_dxgi_api; #endif #ifdef __WIIU__ diff --git a/soh/soh/GameMenuBar.cpp b/soh/soh/GameMenuBar.cpp index 837568706..cb8261563 100644 --- a/soh/soh/GameMenuBar.cpp +++ b/soh/soh/GameMenuBar.cpp @@ -298,6 +298,8 @@ namespace GameMenuBar { CVar_SetS32("gCrouchStabHammerFix", 0); // Fix all crouch stab CVar_SetS32("gCrouchStabFix", 0); + // Fix Gerudo Warrior's clothing colors + CVar_SetS32("gGerudoWarriorClothingFix", 0); // Red Ganon blood CVar_SetS32("gRedGanonBlood", 0); @@ -1084,6 +1086,8 @@ namespace GameMenuBar { UIWidgets::PaddedEnhancementCheckbox("Remove power crouch stab", "gCrouchStabFix", true, false); UIWidgets::Tooltip("Make crouch stabbing always do the same damage as a regular slash"); } + UIWidgets::PaddedEnhancementCheckbox("Fix Gerudo Warrior's clothing colors", "gGerudoWarriorClothingFix", true, false); + UIWidgets::Tooltip("Prevent the Gerudo Warrior's clothes changing color when changing Link's tunic or using bombs in front of her"); ImGui::EndMenu(); } diff --git a/soh/src/code/z_parameter.c b/soh/src/code/z_parameter.c index c7766502b..8299c2f12 100644 --- a/soh/src/code/z_parameter.c +++ b/soh/src/code/z_parameter.c @@ -2502,13 +2502,6 @@ u8 Item_CheckObtainability(u8 item) { } else { return ITEM_NONE; } - } else if ( gSaveContext.n64ddFlag && - ((item >= RG_GERUDO_FORTRESS_SMALL_KEY) && (item <= RG_GANONS_CASTLE_SMALL_KEY) || - (item >= RG_FOREST_TEMPLE_BOSS_KEY) && (item <= RG_GANONS_CASTLE_BOSS_KEY) || - (item >= RG_DEKU_TREE_MAP) && (item <= RG_ICE_CAVERN_MAP) || - (item >= RG_DEKU_TREE_COMPASS) && (item <= RG_ICE_CAVERN_COMPASS)) - ) { - return ITEM_NONE; } else if ((item == ITEM_KEY_BOSS) || (item == ITEM_COMPASS) || (item == ITEM_DUNGEON_MAP)) { return ITEM_NONE; } else if (item == ITEM_KEY_SMALL) { diff --git a/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c b/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c index ac6b8e62e..46436893d 100644 --- a/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c +++ b/soh/src/overlays/actors/ovl_Boss_Ganon2/z_boss_ganon2.c @@ -577,6 +577,14 @@ void func_808FD5F4(BossGanon2* this, GlobalContext* globalCtx) { BossGanon2_SetObjectSegment(this, globalCtx, OBJECT_GANON_ANIME3, false); func_8002DF54(globalCtx, &this->actor, 0x54); this->unk_314 = 3; + + // At this point, the actor has Ganon's skeleton but is still playing an animation for Ganondorf. This + // causes issues when trying to access the limb posotions as Ganon has more limbs than Ganondorf. When + // animating, data from past the end of the animation data is accessed. This is a hack solution so + // that we are at least playing an animation meant for Ganon. There is no visible change since Ganon is + // off-screen. There is actually 1 frame where he is visible, and in the vanilla game he is an + // explosion of limbs since half of them are in random positions from the junk data accessed. + Animation_PlayOnce(&this->skelAnime, &gGanonUncurlAndFlailAnim); } // fake, tricks the compiler into using stack the way we need it to if (zero) { diff --git a/soh/src/overlays/actors/ovl_En_Ge3/z_en_ge3.c b/soh/src/overlays/actors/ovl_En_Ge3/z_en_ge3.c index 261ce49ec..e646a95cd 100644 --- a/soh/src/overlays/actors/ovl_En_Ge3/z_en_ge3.c +++ b/soh/src/overlays/actors/ovl_En_Ge3/z_en_ge3.c @@ -249,28 +249,30 @@ s32 EnGe3_OverrideLimbDraw(GlobalContext* globalCtx, s32 limbIndex, Gfx** dList, case GELDB_LIMB_HEAD: rot->x += this->headRot.y; - // This is a hack to fix the color-changing clothes this Gerudo has on N64 versions default: - OPEN_DISPS(globalCtx->state.gfxCtx); - switch (limbIndex) { - case GELDB_LIMB_NECK: - break; - case GELDB_LIMB_HEAD: - gDPPipeSync(POLY_OPA_DISP++); - gDPSetEnvColor(POLY_OPA_DISP++, 80, 60, 10, 255); - break; - case GELDB_LIMB_R_SWORD: - case GELDB_LIMB_L_SWORD: - gDPPipeSync(POLY_OPA_DISP++); - gDPSetEnvColor(POLY_OPA_DISP++, 140, 170, 230, 255); - gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, 255); - break; - default: - gDPPipeSync(POLY_OPA_DISP++); - gDPSetEnvColor(POLY_OPA_DISP++, 140, 0, 0, 255); - break; + if (CVar_GetS32("gGerudoWarriorClothingFix", 0)) { + // This is a hack to fix the color-changing clothes this Gerudo has on N64 versions + OPEN_DISPS(globalCtx->state.gfxCtx); + switch (limbIndex) { + case GELDB_LIMB_NECK: + break; + case GELDB_LIMB_HEAD: + gDPPipeSync(POLY_OPA_DISP++); + gDPSetEnvColor(POLY_OPA_DISP++, 80, 60, 10, 255); + break; + case GELDB_LIMB_R_SWORD: + case GELDB_LIMB_L_SWORD: + gDPPipeSync(POLY_OPA_DISP++); + gDPSetEnvColor(POLY_OPA_DISP++, 140, 170, 230, 255); + gDPSetPrimColor(POLY_OPA_DISP++, 0, 0, 255, 255, 255, 255); + break; + default: + gDPPipeSync(POLY_OPA_DISP++); + gDPSetEnvColor(POLY_OPA_DISP++, 140, 0, 0, 255); + break; + } + CLOSE_DISPS(globalCtx->state.gfxCtx); } - CLOSE_DISPS(globalCtx->state.gfxCtx); break; } return false; diff --git a/soh/src/overlays/actors/ovl_En_Niw_Lady/z_en_niw_lady.c b/soh/src/overlays/actors/ovl_En_Niw_Lady/z_en_niw_lady.c index eeaff6c33..7a8a7e6d3 100644 --- a/soh/src/overlays/actors/ovl_En_Niw_Lady/z_en_niw_lady.c +++ b/soh/src/overlays/actors/ovl_En_Niw_Lady/z_en_niw_lady.c @@ -401,7 +401,7 @@ void func_80ABA9B8(EnNiwLady* this, GlobalContext* globalCtx) { } else { // TODO: get-item-rework Adult trade sequence this->getItemEntry = Randomizer_GetItemFromKnownCheck(RC_KAK_ANJU_AS_ADULT, GI_POCKET_EGG); - GiveItemEntryFromActor(&this->actor, globalCtx, this->getItemEntry, 200.0f, 100.0f); + gSaveContext.itemGetInf[2] |= 0x1000; } this->actionFunc = func_80ABAC00; @@ -431,7 +431,14 @@ void func_80ABAB08(EnNiwLady* this, GlobalContext* globalCtx) { case 0: Message_CloseTextbox(globalCtx); this->actor.parent = NULL; - func_8002F434(&this->actor, globalCtx, GI_COJIRO, 200.0f, 100.0f); + if (!gSaveContext.n64ddFlag) { + func_8002F434(&this->actor, globalCtx, GI_COJIRO, 200.0f, 100.0f); + } else { + // TODO: get-item-rework Adult trade sequence + this->getItemEntry = Randomizer_GetItemFromKnownCheck(RC_KAK_TRADE_POCKET_CUCCO, GI_COJIRO); + Randomizer_ConsumeAdultTradeItem(globalCtx, ITEM_POCKET_CUCCO); + gSaveContext.itemGetInf[2] |= 0x4000; + } this->actionFunc = func_80ABAC00; break; case 1: @@ -455,18 +462,14 @@ void func_80ABAC00(EnNiwLady* this, GlobalContext* globalCtx) { } else { getItemId = this->getItemId; if (LINK_IS_ADULT) { - getItemId = !(gSaveContext.itemGetInf[2] & 0x1000) ? GI_POCKET_EGG : GI_COJIRO; - - if (gSaveContext.n64ddFlag) { - if (getItemId == GI_POCKET_EGG) { - // TODO: get-item-rework Adult trade sequence - this->getItemEntry = Randomizer_GetItemFromKnownCheck(RC_KAK_ANJU_AS_ADULT, GI_POCKET_EGG); - GiveItemEntryFromActor(&this->actor, globalCtx, this->getItemEntry, 200.0f, 100.0f); - } else { - this->getItemEntry = Randomizer_GetItemFromKnownCheck(RC_KAK_TRADE_POCKET_CUCCO, GI_COJIRO); - Randomizer_ConsumeAdultTradeItem(globalCtx, ITEM_POCKET_CUCCO); - GiveItemEntryFromActor(&this->actor, globalCtx, this->getItemEntry, 200.0f, 100.0f); - } + if (!gSaveContext.n64ddFlag) { + getItemId = !(gSaveContext.itemGetInf[2] & 0x1000) ? GI_POCKET_EGG : GI_COJIRO; + } else { + // TODO: get-item-rework Adult trade sequence + getItemId = this->getItemEntry.getItemId; + GiveItemEntryFromActor(&this->actor, globalCtx, this->getItemEntry, 200.0f, 100.0f); + // Skip setting item flags because that was done earlier + this->actionFunc = func_80ABA778; } } if (this->getItemEntry.getItemId == GI_NONE) { diff --git a/soh/src/overlays/actors/ovl_En_Ru2/z_en_ru2.c b/soh/src/overlays/actors/ovl_En_Ru2/z_en_ru2.c index ea53206f8..433c90521 100644 --- a/soh/src/overlays/actors/ovl_En_Ru2/z_en_ru2.c +++ b/soh/src/overlays/actors/ovl_En_Ru2/z_en_ru2.c @@ -820,6 +820,19 @@ void func_80AF3F20(EnRu2* this, GlobalContext* globalCtx) { void EnRu2_Draw(Actor* thisx, GlobalContext* globalCtx) { EnRu2* this = (EnRu2*)thisx; + // FAST3D: This is a hack for the issue of both TEXEL0 and TEXEL1 using the same texture with different settings. + // Ruto's earring uses both TEXEL0 and TEXEL1 to render. The issue is that it never loads anything into TEXEL1, so + // it reuses whatever happens to be there, which is the water temple brick texture. It just so happens that the + // earring texture loads into the same place in tmem as the brick texture, so when it comes to rendering, TEXEL1 + // uses the earring texture with diffrent clamp settings, and it displays without noticeable error. However, both + // texel samplers are not intended to be used for the same texture with different settings, so this misuse confuses + // our texture cache, and we load the wrong settings for the earrings texture. This patch is a hack that replaces + // TEXEL1 with TEXEL0, which is most likely the original intention, and all is well. + Gfx* gfx = ResourceMgr_LoadGfxByName(gAdultRutoHeadDL); + Gfx patch = gsDPSetCombineLERP(TEXEL0, 0, PRIMITIVE, 0, TEXEL0, 0, ENVIRONMENT, 0, 0, 0, 0, COMBINED, TEXEL0, 0, + PRIM_LOD_FRAC, COMBINED); + gfx[0xA2] = patch; + if ((this->drawConfig < 0) || (this->drawConfig >= ARRAY_COUNT(sDrawFuncs)) || (sDrawFuncs[this->drawConfig] == 0)) { // "Draw Mode is improper!" diff --git a/soh/src/overlays/actors/ovl_player_actor/z_player.c b/soh/src/overlays/actors/ovl_player_actor/z_player.c index 28411dc11..92e108a00 100644 --- a/soh/src/overlays/actors/ovl_player_actor/z_player.c +++ b/soh/src/overlays/actors/ovl_player_actor/z_player.c @@ -6252,21 +6252,27 @@ s32 func_8083E5A8(Player* this, GlobalContext* globalCtx) { } } - // Skip cutscenes from picking up items when they come from bushes/rocks/etc, but nowhere else. - uint8_t skipItemCutscene = CVar_GetS32("gFastDrops", 0) && interactedActor->id == ACTOR_EN_ITEM00 && - interactedActor->params != 6 && interactedActor->params != 17; + // Show the cutscene for picking up an item. In vanilla, this happens in bombchu bowling alley (because getting bombchus need to show the cutscene) + // and whenever the player doesn't have the item yet. In rando, we're overruling this because we need to keep showing the cutscene + // because those items can be randomized and thus it's important to keep showing the cutscene. + uint8_t showItemCutscene = globalCtx->sceneNum == SCENE_BOWLING || Item_CheckObtainability(giEntry.itemId) == ITEM_NONE || gSaveContext.n64ddFlag; - // Same as above but for rando. We need this specifically for rando because we need to be enable the cutscenes everywhere else in the game - // because the items are randomized and thus it's important to show the get item animation. - uint8_t skipItemCutsceneRando = gSaveContext.n64ddFlag && - Item_CheckObtainability(giEntry.itemId) != ITEM_NONE && - interactedActor->id == ACTOR_EN_ITEM00 && - interactedActor->params != 6 && interactedActor->params != 17; + // Only skip cutscenes for drops when they're items/consumables from bushes/rocks/enemies. + uint8_t isDropToSkip = (interactedActor->id == ACTOR_EN_ITEM00 && interactedActor->params != 6 && interactedActor->params != 17) || + interactedActor->id == ACTOR_EN_KAREBABA || + interactedActor->id == ACTOR_EN_DEKUBABA; - // Show cutscene when picking up a item that the player doesn't own yet. - // We want to ALWAYS show "get item animations" for items when they're randomized to account for - // randomized freestanding items etc, but we still don't want to show it every time you pick up a consumable from a pot/bush etc. - if ((globalCtx->sceneNum == SCENE_BOWLING || Item_CheckObtainability(giEntry.itemId) == ITEM_NONE || gSaveContext.n64ddFlag) && !skipItemCutscene && !skipItemCutsceneRando) { + // Skip cutscenes from picking up consumables with "Fast Pickup Text" enabled, even when the player never picked it up before. + // But only for bushes/rocks/enemies because otherwise it can lead to softlocks in deku mask theatre and potentially other places. + uint8_t skipItemCutscene = CVar_GetS32("gFastDrops", 0) && isDropToSkip; + + // Same as above but for rando. Rando is different because we want to enable cutscenes for items that the player already has because + // those items could be a randomized item coming from scrubs, freestanding PoH's and keys. So we need to once again overrule + // this specifically for items coming from bushes/rocks/enemies when the player has already picked that item up. + uint8_t skipItemCutsceneRando = gSaveContext.n64ddFlag && Item_CheckObtainability(giEntry.itemId) != ITEM_NONE && isDropToSkip; + + // Show cutscene when picking up a item. + if (showItemCutscene && !skipItemCutscene && !skipItemCutsceneRando) { func_808323B4(globalCtx, this); func_8083AE40(this, giEntry.objectId); @@ -6282,7 +6288,7 @@ s32 func_8083E5A8(Player* this, GlobalContext* globalCtx) { return 1; } - // Don't show cutscene when picking up an item + // Don't show cutscene when picking up an item. func_8083E4C4(globalCtx, this, &giEntry); this->getItemId = GI_NONE; this->getItemEntry = (GetItemEntry)GET_ITEM_NONE;