diff --git a/README.md b/README.md index b6d6cec863..a66a423c5e 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,10 @@ - ### **[Official Website](https://twilitrealm.dev)** - ### **[Discord](https://discord.gg/QACynxeyna)** +# Overview +Dusk is a reverse-engineered reimplementation of Twilight Princess. +It aims to be as accurate as possible to the original while also providing new options, enhancements, and tools to customize your experience. + # Setup **⚠️ Dusk does NOT provide any copyrighted assets. You must provide your own copy of the game.** @@ -27,5 +31,7 @@ First make sure your dump of the game is clean and supported by Dusk. You can do # Building If you'd like to build Dusk from source, please read the [build instructions](docs/building.md). +Pull Requests are welcomed! Note that we do not accept contributions that are primarily AI generated and will close your PR if we suspect as much. + # Credits Special thanks to the [TP decompilation](https://github.com/zeldaret/tp) team, the GC/Wii decompilation community, the [Aurora](https://github.com/encounter/aurora) developers, the [TP speedrunning community](https://zsrtp.link), and all [contributors](https://github.com/TwilitRealm/dusk/graphs/contributors). diff --git a/extern/aurora b/extern/aurora index 7784b6fc95..8a2b80ecb1 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 7784b6fc95568551499c87bd093b78d86e194eba +Subproject commit 8a2b80ecb104625319c2818fd6d752bab8617679 diff --git a/include/dusk/achievements.h b/include/dusk/achievements.h index fb2da89c69..ca4676ab2d 100644 --- a/include/dusk/achievements.h +++ b/include/dusk/achievements.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include "nlohmann/json.hpp" @@ -14,6 +16,7 @@ enum class AchievementCategory : uint8_t { Collection, Challenge, Minigame, + Misc, Glitched }; @@ -42,6 +45,10 @@ public: void clearAll(); void clearOne(const char* key); + // Signals are visible to all achievement checks within the same tick, then cleared. + void signal(const char* key); + bool hasSignal(const char* key) const; + std::vector getAchievements() const; bool hasPendingUnlock() const { return !m_pendingUnlocks.empty(); } std::string consumePendingUnlock(); @@ -58,6 +65,7 @@ private: void processEntry(Entry& e); std::vector m_entries; + std::unordered_set m_signals; bool m_loaded = false; bool m_dirty = false; std::queue m_pendingUnlocks; diff --git a/src/d/actor/d_a_alink_damage.inc b/src/d/actor/d_a_alink_damage.inc index ac5175e314..e095fc0393 100644 --- a/src/d/actor/d_a_alink_damage.inc +++ b/src/d/actor/d_a_alink_damage.inc @@ -8,6 +8,10 @@ #include "d/actor/d_a_horse.h" #include "d/actor/d_a_crod.h" #include "d/d_msg_object.h" +#ifdef TARGET_PC +#include "d/actor/d_a_obj_carry.h" +#include "dusk/achievements.h" +#endif #if DEBUG #include "d/d_s_menu.h" @@ -677,6 +681,15 @@ BOOL daAlink_c::checkDamageAction() { } setDamagePoint(dmg, at_mtrl == dCcD_MTRL_FIRE || at_mtrl == dCcD_MTRL_ICE, TRUE, 0); + +#ifdef TARGET_PC + if (tghit_ac_name == fpcNm_Obj_Carry_e) { + auto* carry = static_cast(tghit_ac); + if (carry->prm_chk_type_ironball() && carry->checkCannon()) { + dusk::AchievementSystem::get().signal("iron_ball_hit_player"); + } + } +#endif if (armor_no_dmg && at_mtrl != dCcD_MTRL_ELECTRIC && at_mtrl != dCcD_MTRL_ICE) { setGuardSe(var_r29); diff --git a/src/d/actor/d_a_alink_hook.inc b/src/d/actor/d_a_alink_hook.inc index d152190161..73049af08b 100644 --- a/src/d/actor/d_a_alink_hook.inc +++ b/src/d/actor/d_a_alink_hook.inc @@ -18,6 +18,10 @@ enum { }; void daAlink_c::hsChainShape_c::draw() { + if (dusk::getSettings().game.superClawshot) { + return; + } + static const int dummy = 0; daAlink_c* alink = (daAlink_c*)getUserArea(); diff --git a/src/d/actor/d_a_e_th.cpp b/src/d/actor/d_a_e_th.cpp index f963350da4..43ad8ec19b 100644 --- a/src/d/actor/d_a_e_th.cpp +++ b/src/d/actor/d_a_e_th.cpp @@ -282,6 +282,11 @@ static void e_th_spin_B(e_th_class* i_this) { i_this->current.pos += spC; f32 speed_target; + + #if AVOID_UB + speed_target = 0; + #endif + f32 anm_frame = i_this->mpModelMorf->getFrame(); switch (i_this->mMode) { diff --git a/src/dusk/achievements.cpp b/src/dusk/achievements.cpp index 99d4648b98..e0730447d5 100644 --- a/src/dusk/achievements.cpp +++ b/src/dusk/achievements.cpp @@ -8,6 +8,7 @@ #include "d/actor/d_a_player.h" #include "d/d_demo.h" #include "f_pc/f_pc_name.h" +#include "f_op/f_op_actor_mng.h" #include #include @@ -46,6 +47,21 @@ std::vector AchievementSystem::makeEntries() { }, {} }, + { + { + "plumm_max", + "Thank You Berry Much", + "Score 61,454 points in the Plumm minigame.", + AchievementCategory::Minigame, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if (dComIfGs_getBalloonScore() >= 61454) { + a.progress = 1; + } + }, + {} + }, { { "rollgoal_8", @@ -258,6 +274,58 @@ std::vector AchievementSystem::makeEntries() { }, {} }, + { + { + "friendly_fire", + "Friendly Fire", + "Get hit by your own cannonball.", + AchievementCategory::Misc, + false, 0, 0, false + }, + [](Achievement& a, json&) { + if (AchievementSystem::get().hasSignal("iron_ball_hit_player")) { + a.progress = 1; + } + }, + {} + }, + { + { + "long_jump_attack", + "Long Jump Attack", + "Travel more than 20 meters in a single jump attack before landing.", + AchievementCategory::Misc, + false, 0, 0, false + }, + [](Achievement& a, json&) { + static bool inJump = false; + static float startX = 0.0f, startZ = 0.0f; + + const auto* link = static_cast(daPy_getPlayerActorClass()); + if (link == nullptr) { + inJump = false; + return; + } + + if (!inJump) { + if (link->mProcID == daAlink_c::PROC_CUT_JUMP) { + inJump = true; + startX = link->current.pos.x; + startZ = link->current.pos.z; + } + } else if (link->mProcID == daAlink_c::PROC_CUT_JUMP_LAND) { + inJump = false; + const float dx = link->current.pos.x - startX; + const float dz = link->current.pos.z - startZ; + if (dx * dx + dz * dz >= 2000.0f * 2000.0f) { + a.progress = 1; + } + } else if (link->mProcID != daAlink_c::PROC_CUT_JUMP) { + inJump = false; + } + }, + {} + }, { { "back_in_time", @@ -426,6 +494,14 @@ void AchievementSystem::clearAll() { save(); } +void AchievementSystem::signal(const char* key) { + m_signals.insert(key); +} + +bool AchievementSystem::hasSignal(const char* key) const { + return m_signals.count(key) > 0; +} + void AchievementSystem::clearOne(const char* key) { for (auto& e : m_entries) { if (std::string(e.achievement.key) == key) { @@ -470,6 +546,7 @@ void AchievementSystem::tick() { for (auto& e : m_entries) { processEntry(e); } + m_signals.clear(); if (m_dirty) { save(); m_dirty = false; diff --git a/src/dusk/imgui/ImGuiAchievements.cpp b/src/dusk/imgui/ImGuiAchievements.cpp index 479e307e30..f03e4176ed 100644 --- a/src/dusk/imgui/ImGuiAchievements.cpp +++ b/src/dusk/imgui/ImGuiAchievements.cpp @@ -76,8 +76,8 @@ void ImGuiAchievements::draw(bool& open) { return; } - ImGui::SetNextWindowSizeConstraints(ImVec2(640, 200), ImVec2(800, 900)); - ImGui::SetNextWindowSize(ImVec2(640, 480), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSizeConstraints(ImVec2(800, 200), ImVec2(1280, 900)); + ImGui::SetNextWindowSize(ImVec2(800, 480), ImGuiCond_FirstUseEver); if (!ImGui::Begin( "Achievements", &open, @@ -111,6 +111,7 @@ void ImGuiAchievements::draw(bool& open) { {AchievementCategory::Collection, "Collection", ImVec4(0.3f, 0.85f, 0.4f, 1.0f)}, {AchievementCategory::Challenge, "Challenge", ImVec4(1.0f, 0.65f, 0.15f, 1.0f)}, {AchievementCategory::Minigame, "Minigame", ImVec4(0.5f, 0.85f, 1.0f, 1.0f)}, + {AchievementCategory::Misc, "Misc", ImVec4(0.65f, 0.65f, 0.65f, 1.0f)}, {AchievementCategory::Glitched, "Glitched", ImVec4(0.75f, 0.4f, 1.0f, 1.0f)}, };