diff --git a/.clang-format b/.clang-format index 1428d3c71e..8ffd4ebe96 100644 --- a/.clang-format +++ b/.clang-format @@ -2,7 +2,7 @@ Language: Cpp Standard: C++03 AccessModifierOffset: -4 -AlignAfterOpenBracket: Align +AlignAfterOpenBracket: DontAlign AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignOperands: true diff --git a/CMakeLists.txt b/CMakeLists.txt index b326931e79..6045ebc734 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,6 +100,7 @@ if (CMAKE_SYSTEM_NAME STREQUAL Linux) endif () set(AURORA_ENABLE_DVD ON CACHE BOOL "Enable DVD API support" FORCE) set(AURORA_ENABLE_CARD ON CACHE BOOL "Enable CARD API support" FORCE) +set(AURORA_ENABLE_RMLUI ON CACHE BOOL "Enable RmlUi UI support" FORCE) add_subdirectory(extern/aurora EXCLUDE_FROM_ALL) add_subdirectory(libs/freeverb) @@ -296,8 +297,10 @@ set(GAME_INCLUDE_DIRS extern ${CMAKE_BINARY_DIR}) +find_package(Threads REQUIRED) set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::pad aurora::mtx aurora::os aurora::dvd - aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt) + aurora::card freeverb cxxopts::cxxopts absl::flat_hash_map nlohmann_json::nlohmann_json TracyClient fmt::fmt + Threads::Threads) list(APPEND GAME_LIBS libzstd_static) @@ -306,6 +309,10 @@ if (DUSK_ENABLE_SENTRY_NATIVE) list(APPEND GAME_COMPILE_DEFS DUSK_ENABLE_SENTRY_NATIVE=1 SENTRY_BUILD_STATIC=1) endif () +if (WIN32) + list(APPEND GAME_LIBS Ws2_32) +endif () + if (DUSK_MOVIE_SUPPORT) if (TARGET libjpeg-turbo::turbojpeg-static) list(APPEND GAME_LIBS libjpeg-turbo::turbojpeg-static) @@ -315,46 +322,13 @@ if (DUSK_MOVIE_SUPPORT) list(APPEND GAME_COMPILE_DEFS MOVIE_SUPPORT=1) endif () -option(DUSK_ENABLE_DISCORD_RPC "Enable Discord Rich Presence support" ON) -if (DUSK_ENABLE_DISCORD_RPC AND NOT ANDROID AND NOT IOS AND NOT TVOS) - - FetchContent_Populate(discord_rpc - URL https://github.com/discord/discord-rpc/archive/refs/tags/v3.4.0.tar.gz - URL_HASH SHA256=e13427019027acd187352dacba6c65953af66fdf3c35fcf38fc40b454a9d7855 - DOWNLOAD_EXTRACT_TIMESTAMP TRUE - ) - # RapidJSON is a git submodule absent from the discord-rpc tarball; fetch separately. - FetchContent_Populate(rapidjson - URL https://github.com/Tencent/rapidjson/archive/refs/tags/v1.1.0.tar.gz - URL_HASH SHA256=bf7ced29704a1e696fbccf2a2b4ea068e7774fa37f6d7dd4039d0787f8bed98e - DOWNLOAD_EXTRACT_TIMESTAMP TRUE - ) - - if (NOT TARGET discord-rpc) - set(_drpc ${discord_rpc_SOURCE_DIR}/src) - set(_drpc_src - ${_drpc}/discord_rpc.cpp - ${_drpc}/rpc_connection.cpp - ${_drpc}/serialization.cpp - ) - if (WIN32) - list(APPEND _drpc_src ${_drpc}/connection_win.cpp ${_drpc}/discord_register_win.cpp) - elseif (APPLE) - list(APPEND _drpc_src ${_drpc}/connection_unix.cpp ${_drpc}/discord_register_osx.m) - else () - list(APPEND _drpc_src ${_drpc}/connection_unix.cpp ${_drpc}/discord_register_linux.cpp) - endif () - add_library(discord-rpc STATIC ${_drpc_src}) - target_include_directories(discord-rpc PUBLIC - ${discord_rpc_SOURCE_DIR}/include - ${rapidjson_SOURCE_DIR}/include - ) - if (UNIX) - target_link_libraries(discord-rpc PUBLIC pthread) - endif () - endif () - list(APPEND GAME_LIBS discord-rpc) - list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD_RPC=1) +set(DUSK_ENABLE_DISCORD_DEFAULT ON) +if (DEFINED DUSK_ENABLE_DISCORD_RPC AND NOT DEFINED DUSK_ENABLE_DISCORD) + set(DUSK_ENABLE_DISCORD_DEFAULT ${DUSK_ENABLE_DISCORD_RPC}) +endif () +option(DUSK_ENABLE_DISCORD "Enable Discord Rich Presence support" ${DUSK_ENABLE_DISCORD_DEFAULT}) +if (DUSK_ENABLE_DISCORD AND NOT ANDROID AND NOT IOS AND NOT TVOS) + list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1) endif () # Edit & Continue diff --git a/README.md b/README.md index b6d6cec863..02b1ded875 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,48 @@ -![DuskLogo](res/logo-mascot.webp) +
+ Logo -- ### **[Official Website](https://twilitrealm.dev)** -- ### **[Discord](https://discord.gg/QACynxeyna)** +

+ Official Website + • + Discord +

+
+ +# 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.** -### 1. Verify your ROM dump -First make sure your dump of the game is clean and supported by Dusk. You can do this by checking the sha1 hash of your dump against this list of supported versions. +> [!IMPORTANT] +> Dusk does *not* provide any copyrighted assets. You must provide your own copy of the original game. -| Version | sha1 hash | -|--------------| ---------------------------------------- | -| GameCube USA | 75edd3ddff41f125d1b4ce1a40378f1b565519e7 | -| GameCube PAL | 2601822a488eeb86fb89db16ca8f29c2c953e1ca | +### 1. Verify your dump + +First, make sure your dump of the game is clean and supported by Dusk. You can do this by checking the SHA-1 hash of your dump against this list of supported versions: + +| Version | SHA-1 hash | +|--------------| ------------------------------------------ | +| GameCube USA | `75edd3ddff41f125d1b4ce1a40378f1b565519e7` | +| GameCube EUR | `2601822a488eeb86fb89db16ca8f29c2c953e1ca` | ### 2. Download [Dusk](https://github.com/TwilitRealm/dusk/releases) ### 3. Setup the game -- Extract the zip folder -- Launch Dusk -- Select Options, then set the ISO Path to your supported game dump -- Press Start Game to play! -![Dusk options](assets/dusk_options.png) +- Extract the .zip file +- Launch Dusk +- Press **Select Disc Image**, navigate to your game dump, and select the file +- Press **Start Game** to play! # 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/assets/dusk_options.png b/assets/dusk_options.png deleted file mode 100644 index 4ed4ad7563..0000000000 Binary files a/assets/dusk_options.png and /dev/null differ diff --git a/extern/aurora b/extern/aurora index 8af9057689..518747aa86 160000 --- a/extern/aurora +++ b/extern/aurora @@ -1 +1 @@ -Subproject commit 8af9057689b9b85a55bbbd6218bc95304558fabd +Subproject commit 518747aa86a50be62ecf92aeb309309b0d58a54a diff --git a/files.cmake b/files.cmake index f001a72f47..a463ce5129 100644 --- a/files.cmake +++ b/files.cmake @@ -1,7 +1,7 @@ set(DOLZEL_FILES src/m_Do/m_Do_main.cpp - src/m_Do/m_Do_printf.cpp + #src/m_Do/m_Do_printf.cpp src/m_Do/m_Do_audio.cpp src/m_Do/m_Do_controller_pad.cpp #src/m_Do/m_Re_controller_pad.cpp @@ -1429,6 +1429,7 @@ set(DUSK_FILES src/dusk/globals.cpp src/dusk/gyro.cpp src/dusk/gamepad_color.cpp + src/dusk/autosave.cpp src/dusk/io.cpp src/dusk/layout.cpp src/dusk/logging.cpp @@ -1448,8 +1449,6 @@ set(DUSK_FILES src/dusk/imgui/ImGuiMenuTools.hpp src/dusk/imgui/ImGuiPreLaunchWindow.cpp src/dusk/imgui/ImGuiPreLaunchWindow.hpp - src/dusk/imgui/ImGuiFirstRunPreset.hpp - src/dusk/imgui/ImGuiFirstRunPreset.cpp src/dusk/imgui/ImGuiProcessOverlay.cpp src/dusk/imgui/ImGuiCameraOverlay.cpp src/dusk/imgui/ImGuiHeapOverlay.cpp @@ -1460,14 +1459,61 @@ set(DUSK_FILES src/dusk/imgui/ImGuiSaveEditor.cpp src/dusk/imgui/ImGuiStateShare.hpp src/dusk/imgui/ImGuiStateShare.cpp - src/dusk/imgui/ImGuiAchievements.hpp - src/dusk/imgui/ImGuiAchievements.cpp + src/dusk/ui/bool_button.cpp + src/dusk/ui/bool_button.hpp + src/dusk/ui/button.cpp + src/dusk/ui/button.hpp + src/dusk/ui/component.cpp + src/dusk/ui/component.hpp + src/dusk/ui/controller_config.cpp + src/dusk/ui/controller_config.hpp + src/dusk/ui/document.cpp + src/dusk/ui/document.hpp + src/dusk/ui/achievements.cpp + src/dusk/ui/achievements.hpp + src/dusk/ui/preset.cpp + src/dusk/ui/preset.hpp + src/dusk/ui/editor.cpp + src/dusk/ui/editor.hpp + src/dusk/ui/event.cpp + src/dusk/ui/event.hpp + src/dusk/ui/input.cpp + src/dusk/ui/input.hpp + src/dusk/ui/nav_types.hpp + src/dusk/ui/number_button.cpp + src/dusk/ui/number_button.hpp + src/dusk/ui/overlay.cpp + src/dusk/ui/overlay.hpp + src/dusk/ui/pane.cpp + src/dusk/ui/pane.hpp + src/dusk/ui/popup.cpp + src/dusk/ui/popup.hpp + src/dusk/ui/prelaunch.cpp + src/dusk/ui/prelaunch.hpp + src/dusk/ui/prelaunch_options.cpp + src/dusk/ui/prelaunch_options.hpp + src/dusk/ui/select_button.cpp + src/dusk/ui/select_button.hpp + src/dusk/ui/settings.cpp + src/dusk/ui/settings.hpp + src/dusk/ui/string_button.cpp + src/dusk/ui/string_button.hpp + src/dusk/ui/tab_bar.cpp + src/dusk/ui/tab_bar.hpp + src/dusk/ui/ui.cpp + src/dusk/ui/ui.hpp + src/dusk/ui/window.cpp + src/dusk/ui/window.hpp src/dusk/achievements.cpp src/dusk/iso_validate.cpp + src/dusk/livesplit.cpp src/dusk/offset_ptr.cpp src/dusk/OSContext.cpp + src/dusk/OSReport.cpp src/dusk/OSThread.cpp src/dusk/OSMutex.cpp + src/dusk/discord.cpp + src/dusk/discord.hpp src/dusk/discord_presence.cpp src/dusk/version.cpp ) diff --git a/include/d/actor/d_a_mirror.h b/include/d/actor/d_a_mirror.h index 06c5603899..e2f9a51e30 100644 --- a/include/d/actor/d_a_mirror.h +++ b/include/d/actor/d_a_mirror.h @@ -27,6 +27,7 @@ public: /* 0x17C */ cXyz mViewScale; #if TARGET_PC bool mbReset = false; + bool mbHadEntry = false; #endif }; diff --git a/include/d/actor/d_a_obj_klift00.h b/include/d/actor/d_a_obj_klift00.h index 865fab8c47..ab5403d51b 100644 --- a/include/d/actor/d_a_obj_klift00.h +++ b/include/d/actor/d_a_obj_klift00.h @@ -25,6 +25,10 @@ public: int Draw(); int Delete(); +#if TARGET_PC + void onInterpCallback(); +#endif + enum Param_e { LOCK_e = (1 << 6), NO_BASE_DISP = (1 << 7) }; @@ -50,6 +54,13 @@ private: /* 0x1020 */ dCcD_Cyl mCylinderCollider; /* 0x115C */ s32 mStopSwingingFrames; +#if TARGET_PC + cXyz mChainInterpPrev[64]; + cXyz mChainInterpCurr[64]; + bool mChainInterpPrevValid; + bool mChainInterpCurrValid; +#endif + // Number of chain models u32 getArg0() { return fopAcM_GetParamBit(this, 0, 6); diff --git a/include/d/actor/d_a_obj_lv8Lift.h b/include/d/actor/d_a_obj_lv8Lift.h index c5a8ae9f27..c327f4cf3c 100644 --- a/include/d/actor/d_a_obj_lv8Lift.h +++ b/include/d/actor/d_a_obj_lv8Lift.h @@ -58,6 +58,9 @@ public: void setNextPoint(); int Draw(); int Delete(); +#if TARGET_PC + friend void daL8Lift_interp_callback(bool isSimFrame, void* pUserWork); +#endif u8 getPthID() { return fopAcM_GetParamBit(this, 0, 8); } u8 getMoveSpeed() { return fopAcM_GetParamBit(this, 8, 4); } diff --git a/include/d/d_camera.h b/include/d/d_camera.h index 105c9cae9f..c481262a46 100644 --- a/include/d/d_camera.h +++ b/include/d/d_camera.h @@ -118,6 +118,18 @@ class camera_class; class dCamera_c; typedef bool (dCamera_c::*engine_fn)(s32); +#if TARGET_PC +struct DebugFlyCam { + bool initialized; + f32 pitch; + f32 yaw; + cXyz savedCenter; + cXyz savedEye; + f32 savedFovy; + cSAngle savedBank; +}; +#endif + class dCamera_c { public: class dCamInfo_c { @@ -1028,6 +1040,8 @@ public: bool test2Camera(s32); #if TARGET_PC bool freeCamera(); + bool executeDebugFlyCam(); + void deactivateDebugFlyCam(); #endif bool towerCamera(s32); bool hookshotCamera(s32); @@ -1376,6 +1390,10 @@ public: /* 0x970 */ dCamSetup_c mCamSetup; /* 0xAEC */ dCamParam_c mCamParam; /* 0xB0C */ u8 field_0xb0c; + +#if TARGET_PC + DebugFlyCam mDebugFlyCam; +#endif }; // Size: 0xB10 dCamera_c* dCam_getBody(); diff --git a/include/d/d_menu_dmap.h b/include/d/d_menu_dmap.h index e50c533654..81baea1446 100644 --- a/include/d/d_menu_dmap.h +++ b/include/d/d_menu_dmap.h @@ -91,6 +91,10 @@ public: void calcCursor(); void drawCursor(); + #if TARGET_PC + void dMapBgWide(); + #endif + void setDPDFloorSelCurPos(s8 i_pos) { field_0xdd6 = i_pos; } f32 getMapWidth() { return mMapWidth; } diff --git a/include/d/d_menu_fmap2D.h b/include/d/d_menu_fmap2D.h index 9b237f2771..d0152f0229 100644 --- a/include/d/d_menu_fmap2D.h +++ b/include/d/d_menu_fmap2D.h @@ -81,6 +81,10 @@ public: void calcDrawPriority(); void setArrowPosAxis(f32, f32); + #if TARGET_PC + void fMapBackWide(); + #endif + virtual void draw(); virtual ~dMenu_Fmap2DBack_c(); @@ -330,6 +334,10 @@ public: void setHIO(bool); bool isWarpAccept(); + #if TARGET_PC + void fMapTopWide(); + #endif + virtual void draw(); virtual ~dMenu_Fmap2DTop_c(); diff --git a/include/d/d_msg_object.h b/include/d/d_msg_object.h index b55ea73904..d2ac4ecb2a 100644 --- a/include/d/d_msg_object.h +++ b/include/d/d_msg_object.h @@ -67,6 +67,9 @@ public: bool isStaffMessage(); bool isSaveMessage(); bool isTalkMessage(); +#if TARGET_PC + bool isShopItemMessage(); +#endif const char* getSmellName(); const char* getPortalName(); const char* getBombName(); diff --git a/include/dusk/achievements.h b/include/dusk/achievements.h index cd4294b6f1..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 }; @@ -40,6 +43,11 @@ public: void save(); void tick(); 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(); } @@ -57,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/include/dusk/autosave.h b/include/dusk/autosave.h new file mode 100644 index 0000000000..248924fcb8 --- /dev/null +++ b/include/dusk/autosave.h @@ -0,0 +1,17 @@ +#pragma once + +#ifndef AUTOSAVE_H +#define AUTOSAVE_H + +#include +#include + +void noAutoSave(); +void triggerAutoSave(); +void updateAutoSave(); +void enterAutoSave(); +void autoSaving(); +void waitingForWrite(); +void endAutoSave(); + +#endif \ No newline at end of file diff --git a/include/dusk/discord_presence.hpp b/include/dusk/discord_presence.hpp index 899b2ad5f9..ebecc2d07b 100644 --- a/include/dusk/discord_presence.hpp +++ b/include/dusk/discord_presence.hpp @@ -1,18 +1,14 @@ #pragma once -#ifdef DUSK_DISCORD_RPC +#ifdef DUSK_DISCORD -namespace dusk { -namespace discord { +namespace dusk::discord { -void Initialize(); +void initialize(); +void run_callbacks(); +void update_presence(); +void shutdown(); -void RunCallbacks(); +} // namespace dusk::discord -void UpdatePresence(); - -void Shutdown(); -} -} - -#endif // DUSK_DISCORD_RPC +#endif // DUSK_DISCORD diff --git a/include/dusk/livesplit.h b/include/dusk/livesplit.h new file mode 100644 index 0000000000..b283a29af4 --- /dev/null +++ b/include/dusk/livesplit.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace dusk::speedrun { +void onGameFrame(); +uint64_t getFrameCount(); +void start(); +void reset(); +void connectLiveSplit(const char* host = "127.0.0.1", int port = 16834); +void disconnectLiveSplit(); +bool consumeConnectedEvent(); +bool consumeDisconnectedEvent(); +void updateLiveSplit(); +void shutdown(); +} diff --git a/include/dusk/settings.h b/include/dusk/settings.h index a7815569d0..7f7aa8cd69 100644 --- a/include/dusk/settings.h +++ b/include/dusk/settings.h @@ -55,6 +55,8 @@ struct UserSettings { ConfigVar soundEffectsVolume; ConfigVar fanfareVolume; ConfigVar enableReverb; + ConfigVar enableHrtf; + ConfigVar menuSounds; } audio; // Game settings @@ -76,9 +78,11 @@ struct UserSettings { ConfigVar fastClimbing; ConfigVar noMissClimbing; ConfigVar fastTears; + ConfigVar no2ndFishForCat; ConfigVar instantSaves; ConfigVar instantText; ConfigVar sunsSong; + ConfigVar autoSave; // Preferences ConfigVar enableMirrorMode; @@ -96,6 +100,7 @@ struct UserSettings { ConfigVar internalResolutionScale; ConfigVar shadowResolutionMultiplier; ConfigVar enableDepthOfField; + ConfigVar enableMapBackground; // Audio ConfigVar noLowHpSound; @@ -115,6 +120,7 @@ struct UserSettings { ConfigVar invertCameraXAxis; ConfigVar invertCameraYAxis; ConfigVar freeCameraSensitivity; + ConfigVar debugFlyCam; // Cheats ConfigVar infiniteHearts; @@ -137,6 +143,10 @@ struct UserSettings { // Controls ConfigVar enableTurboKeybind; + + // Tools + ConfigVar speedrunMode; + ConfigVar liveSplitEnabled; } game; struct { @@ -146,7 +156,6 @@ struct UserSettings { ConfigVar showPipelineCompilation; ConfigVar wasPresetChosen; ConfigVar enableCrashReporting; - ConfigVar duskMenuOpen; ConfigVar cardFileType; } backend; }; diff --git a/include/m_Do/m_Do_audio.h b/include/m_Do/m_Do_audio.h index 1d58e8d8f2..4bc2f20c94 100644 --- a/include/m_Do/m_Do_audio.h +++ b/include/m_Do/m_Do_audio.h @@ -5,6 +5,7 @@ #include "Z2AudioLib/Z2EnvSeMgr.h" #include "Z2AudioLib/Z2LinkMgr.h" #include "dusk/audio.h" +#include "dusk/settings.h" class mDoAud_zelAudio_c : public Z2AudioMgr { public: @@ -132,6 +133,18 @@ inline void mDoAud_seStart(u32 i_sfxID, const Vec* i_sePos, u32 param_2, s8 i_re -1.0f, -1.0f, 0); } +#if TARGET_PC +inline void mDoAud_seStartMenu(u32 i_sfxID) { + if (!mDoAud_zelAudio_c::isInitFlag()) { + return; + } + if (!dusk::getSettings().audio.menuSounds.getValue()) { + return; + } + mDoAud_seStart(i_sfxID, nullptr, 0, 0); +} +#endif + inline void mDoAud_seStartLevel(u32 i_sfxID, const Vec* i_sePos, u32 param_2, s8 i_reverb) { DUSK_AUDIO_SKIP() Z2AudioMgr::getInterface()->seStartLevel(i_sfxID, i_sePos, param_2, i_reverb, 1.0f, 1.0f, diff --git a/libs/JSystem/include/JSystem/JAudio2/JAISeqMgr.h b/libs/JSystem/include/JSystem/JAudio2/JAISeqMgr.h index 87c85f316f..6f6d29ca98 100644 --- a/libs/JSystem/include/JSystem/JAudio2/JAISeqMgr.h +++ b/libs/JSystem/include/JSystem/JAudio2/JAISeqMgr.h @@ -59,6 +59,9 @@ public: bool isActive() const { return mSeqList.getNumLinks() != 0; } int getNumActiveSeqs() const { return mSeqList.getNumLinks(); } void pause(bool paused) { mActivity.field_0x0.flags.flag2 = paused; } + #if TARGET_PC + JSUList* getSeqList() { return &mSeqList; } + #endif private: /* 0x08 */ JAIAudience* mAudience; diff --git a/libs/JSystem/include/JSystem/JParticle/JPABaseShape.h b/libs/JSystem/include/JSystem/JParticle/JPABaseShape.h index d348859ebe..795a5a2628 100644 --- a/libs/JSystem/include/JSystem/JParticle/JPABaseShape.h +++ b/libs/JSystem/include/JSystem/JParticle/JPABaseShape.h @@ -207,4 +207,11 @@ void JPARegistAlphaEnv(JPAEmitterWorkData*, JPABaseParticle*); void JPARegistPrmAlpha(JPAEmitterWorkData*, JPABaseParticle*); void JPARegistPrmAlphaEnv(JPAEmitterWorkData*, JPABaseParticle*); +#if TARGET_PC +void JPAInterpBillboard(JPAEmitterWorkData*, JPABaseParticle*); +void JPAInterpRotBillboard(JPAEmitterWorkData*, JPABaseParticle*); +void JPAInterpDirection(JPAEmitterWorkData*, JPABaseParticle*); +void JPAInterpRotDirection(JPAEmitterWorkData*, JPABaseParticle*); +#endif + #endif /* JPABASESHAPE_H */ diff --git a/libs/JSystem/include/JSystem/JParticle/JPAParticle.h b/libs/JSystem/include/JSystem/JParticle/JPAParticle.h index 842f293286..c864a5cd4a 100644 --- a/libs/JSystem/include/JSystem/JParticle/JPAParticle.h +++ b/libs/JSystem/include/JSystem/JParticle/JPAParticle.h @@ -24,6 +24,9 @@ public: void init_c(JPAEmitterWorkData*, JPABaseParticle*); bool calc_p(JPAEmitterWorkData*); bool calc_c(JPAEmitterWorkData*); +#if TARGET_PC + void interp(JPAEmitterWorkData*, void const* drawFunc); +#endif bool canCreateChild(JPAEmitterWorkData*); f32 getWidth(JPABaseEmitter const*) const; f32 getHeight(JPABaseEmitter const*) const; diff --git a/libs/JSystem/include/JSystem/JUtility/JUTPalette.h b/libs/JSystem/include/JSystem/JUtility/JUTPalette.h index 017d511d50..5d29005a92 100644 --- a/libs/JSystem/include/JSystem/JUtility/JUTPalette.h +++ b/libs/JSystem/include/JSystem/JUtility/JUTPalette.h @@ -40,6 +40,9 @@ public: JUTTransparency getTransparency() const { return JUTTransparency(mTransparency); } u16 getNumColors() const { return mNumColors; } ResTLUT* getColorTable() const { return mColorTable; } +#if TARGET_PC + void dataUploaded(); +#endif private: /* 0x00 */ GXTlutObj mTlutObj; diff --git a/libs/JSystem/include/JSystem/JUtility/JUTTexture.h b/libs/JSystem/include/JSystem/JUtility/JUTTexture.h index e0f1e15ff0..6a7a8f9a0e 100644 --- a/libs/JSystem/include/JSystem/JUtility/JUTTexture.h +++ b/libs/JSystem/include/JSystem/JUtility/JUTTexture.h @@ -75,6 +75,7 @@ public: s32 getTransparency() const { return mTexInfo->alphaEnabled; } s32 getWidth() const { return mTexInfo->width; } s32 getHeight() const { return mTexInfo->height; } + JUTPalette* getPalette() const { return mPalette; } void setCaptureFlag(bool flag) { mFlags &= 2 | flag; } bool getCaptureFlag() const { return mFlags & 1; } bool getEmbPaletteDelFlag() const { return mFlags & 2; } @@ -82,7 +83,7 @@ public: int getTlutName() const { return mTlutName; } bool operator==(const JUTTexture& other) { return mTexInfo == other.mTexInfo - && field_0x2c == other.field_0x2c + && mPalette == other.mPalette && mWrapS == other.mWrapS && mWrapT == other.mWrapT && mMinFilter == other.mMinFilter @@ -100,7 +101,7 @@ private: /* 0x20 */ const ResTIMG* mTexInfo; /* 0x24 */ void* mTexData; /* 0x28 */ JUTPalette* mEmbPalette; - /* 0x2C */ JUTPalette* field_0x2c; + /* 0x2C */ JUTPalette* mPalette; /* 0x30 */ u8 mWrapS; /* 0x31 */ u8 mWrapT; /* 0x32 */ u8 mMinFilter; diff --git a/libs/JSystem/src/JAudio2/JASChannel.cpp b/libs/JSystem/src/JAudio2/JASChannel.cpp index 61fe80a098..758167cc5c 100644 --- a/libs/JSystem/src/JAudio2/JASChannel.cpp +++ b/libs/JSystem/src/JAudio2/JASChannel.cpp @@ -1,6 +1,9 @@ #include "JSystem/JSystem.h" // IWYU pragma: keep #include "JSystem/JAudio2/JASChannel.h" +#if TARGET_PC +#include "dusk/audio/DuskDsp.hpp" +#endif #include "JSystem/JAudio2/JASAiCtrl.h" #include "JSystem/JAudio2/JASCalc.h" #include "JSystem/JAudio2/JASDriverIF.h" @@ -170,7 +173,12 @@ void JASChannel::updateEffectorParam(JASDsp::TChannel* i_channel, u16* i_mixerVo f32 pan = 0.5f; f32 dolby = 0.0f; - switch (JASDriver::getOutputMode()) { +#if TARGET_PC + u32 effectiveOutputMode = dusk::audio::EnableHrtf ? JAS_OUTPUT_SURROUND : JASDriver::getOutputMode(); +#else + u32 effectiveOutputMode = JASDriver::getOutputMode(); +#endif + switch (effectiveOutputMode) { case JAS_OUTPUT_MONO: break; case JAS_OUTPUT_STEREO: diff --git a/libs/JSystem/src/JAudio2/JASHeapCtrl.cpp b/libs/JSystem/src/JAudio2/JASHeapCtrl.cpp index 45568cc29a..e3722e3726 100644 --- a/libs/JSystem/src/JAudio2/JASHeapCtrl.cpp +++ b/libs/JSystem/src/JAudio2/JASHeapCtrl.cpp @@ -302,7 +302,6 @@ void JASKernel::setupRootHeap(JKRSolidHeap* heap, u32 size) { JKRHEAP_NAME(sSystemHeap, "JASKernel::sSystemHeap"); JUT_ASSERT(787, sSystemHeap); sCommandHeap = JKR_NEW_ARGS (heap, 0) JASMemChunkPool<1024, JASThreadingModel::ObjectLevelLockable>; - JKRHEAP_NAME(sSystemHeap, "JASKernel::sCommandHeap"); JUT_ASSERT(790, sCommandHeap); JASDram = heap; } diff --git a/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp b/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp index e1f45de1ae..cc94097712 100644 --- a/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp +++ b/libs/JSystem/src/JAudio2/JAUSectionHeap.cpp @@ -442,6 +442,7 @@ static JAUSectionHeap* JAUNewSectionHeap(JKRSolidHeap* heap, bool param_1) { JAUSectionHeap* JAUNewSectionHeap(bool param_0) { s32 freeSize = JASDram->getFreeSize(); JKRSolidHeap* sectionHeap = JKRCreateSolidHeap(freeSize, JASDram, true); + JKRHEAP_NAME(sectionHeap, "sectionHeap"); JUT_ASSERT(821, sectionHeap); return JAUNewSectionHeap(sectionHeap, param_0); } diff --git a/libs/JSystem/src/JKernel/JKRExpHeap.cpp b/libs/JSystem/src/JKernel/JKRExpHeap.cpp index 4a6712cb65..dc88090a47 100644 --- a/libs/JSystem/src/JKernel/JKRExpHeap.cpp +++ b/libs/JSystem/src/JKernel/JKRExpHeap.cpp @@ -222,16 +222,11 @@ void* JKRExpHeap::do_alloc(u32 size, int alignment) { OSReport_Error("Free block list as follows:\n"); OSReport_Error("Start | End | Size \n"); - int i = 0; for (const CMemBlock* block = mHeadFreeList; block; block = block->mNext) { if (block->mMagic) { // Allocated, ignore. continue; } - if (i++ > 10) { - OSReport_Error("\n"); - break; - } auto blockStart = (uintptr_t)block - (uintptr_t)mStart; auto blockEnd = (uintptr_t)block + block->size - (uintptr_t)mStart; @@ -239,6 +234,14 @@ void* JKRExpHeap::do_alloc(u32 size, int alignment) { OSReport_Error("%08X | %08X | %08X\n", (u32) blockStart, (u32) blockEnd, (u32) blockSize); } + OSReport_Error("Child heaps as follows:\n"); + OSReport_Error("Start | End | Name \n"); + + const JSUTree& tree = getHeapTree(); + for (JSUTreeIterator iter(tree.getFirstChild()); iter != tree.getEndChild(); ++iter) { + OSReport_Error("%08X | %08X | %s\n", iter->getStartAddr(), iter->getEndAddr(), iter->getName()); + } + CRASH("Aborting due to allocation failure!"); } #else diff --git a/libs/JSystem/src/JParticle/JPABaseShape.cpp b/libs/JSystem/src/JParticle/JPABaseShape.cpp index 178606a687..ff346984cd 100644 --- a/libs/JSystem/src/JParticle/JPABaseShape.cpp +++ b/libs/JSystem/src/JParticle/JPABaseShape.cpp @@ -9,6 +9,9 @@ #include #include +#if TARGET_PC +#include "dusk/frame_interpolation.h" +#endif #include "tracy/Tracy.hpp" void JPASetPointSize(JPAEmitterWorkData* work) { @@ -418,50 +421,95 @@ static projectionFunc p_prj[3] = { loadPrjAnm, }; -void JPADrawBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) { - if (param_1->checkStatus(JPAPtclStts_Invisible)) { +#if TARGET_PC +void JPAInterpBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) { + Mtx ptclPosMtx; + MTXTrans(ptclPosMtx, ptcl->mPosition.x, ptcl->mPosition.y, ptcl->mPosition.z); + dusk::frame_interp::record_final_mtx(ptclPosMtx, ptcl); +} + +void JPAInterpRotBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) { + Mtx ptclPosMtx; + f32 sinRot = JMASSin(ptcl->mRotateAngle); + f32 cosRot = JMASCos(ptcl->mRotateAngle); + MTXTrans(ptclPosMtx, ptcl->mPosition.x, ptcl->mPosition.y, ptcl->mPosition.z); + ptclPosMtx[0][0] = cosRot; + ptclPosMtx[0][1] = -sinRot; + ptclPosMtx[1][0] = sinRot; + ptclPosMtx[1][1] = cosRot; + dusk::frame_interp::record_final_mtx(ptclPosMtx, ptcl); +} +#endif + +void JPADrawBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) { + if (ptcl->checkStatus(JPAPtclStts_Invisible)) { return; } - JGeometry::TVec3 local_48; - MTXMultVec(work->mPosCamMtx, ¶m_1->mPosition, &local_48); - Mtx local_38; - local_38[0][0] = work->mGlobalPtclScl.x * param_1->mParticleScaleX; - local_38[0][3] = local_48.x; - local_38[1][1] = work->mGlobalPtclScl.y * param_1->mParticleScaleY; - local_38[1][3] = local_48.y; - local_38[2][2] = 1.0f; - local_38[2][3] = local_48.z; - local_38[0][1] = local_38[0][2] = local_38[1][0] = local_38[1][2] = local_38[2][0] = local_38[2][1] = 0.0f; - GXLoadPosMtxImm(local_38, 0); - p_prj[work->mPrjType](work, local_38); + JGeometry::TVec3 pos; +#if TARGET_PC + Mtx ptclPosMtx; + if (dusk::frame_interp::lookup_replacement(ptcl, ptclPosMtx)) { + pos.set(ptclPosMtx[0][3], ptclPosMtx[1][3], ptclPosMtx[2][3]); + MTXMultVec(work->mPosCamMtx, &pos, &pos); + } else +#endif + { + MTXMultVec(work->mPosCamMtx, &ptcl->mPosition, &pos); + } + Mtx posMtx; + posMtx[0][0] = work->mGlobalPtclScl.x * ptcl->mParticleScaleX; + posMtx[0][3] = pos.x; + posMtx[1][1] = work->mGlobalPtclScl.y * ptcl->mParticleScaleY; + posMtx[1][3] = pos.y; + posMtx[2][2] = 1.0f; + posMtx[2][3] = pos.z; + posMtx[0][1] = posMtx[0][2] = posMtx[1][0] = posMtx[1][2] = posMtx[2][0] = posMtx[2][1] = 0.0f; + GXLoadPosMtxImm(posMtx, GX_PNMTX0); + p_prj[work->mPrjType](work, posMtx); GXCallDisplayList(jpa_dl, sizeof(jpa_dl)); } -void JPADrawRotBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) { - if (param_1->checkStatus(JPAPtclStts_Invisible)) { +void JPADrawRotBillboard(JPAEmitterWorkData* work, JPABaseParticle* ptcl) { + if (ptcl->checkStatus(JPAPtclStts_Invisible)) { return; } - JGeometry::TVec3 local_48; - MTXMultVec(work->mPosCamMtx, ¶m_1->mPosition, &local_48); - f32 sinRot = JMASSin(param_1->mRotateAngle); - f32 cosRot = JMASCos(param_1->mRotateAngle); - f32 particleX = work->mGlobalPtclScl.x * param_1->mParticleScaleX; - f32 particleY = work->mGlobalPtclScl.y * param_1->mParticleScaleY; + if (work->mpRes->getUsrIdx() == 0x89d7) { + int a = 0; + } - Mtx local_38; - local_38[0][0] = cosRot * particleX; - local_38[0][1] = -sinRot * particleY; - local_38[0][3] = local_48.x; - local_38[1][0] = sinRot * particleX; - local_38[1][1] = cosRot * particleY; - local_38[1][3] = local_48.y; - local_38[2][2] = 1.0f; - local_38[2][3] = local_48.z; - local_38[0][2] = local_38[1][2] = local_38[2][0] = local_38[2][1] = 0.0f; - GXLoadPosMtxImm(local_38, 0); - p_prj[work->mPrjType](work, local_38); + JGeometry::TVec3 pos; + f32 sinRot, cosRot; +#if TARGET_PC + Mtx ptclPosMtx; + MTXTrans(ptclPosMtx, ptcl->mPosition.x, ptcl->mPosition.y, ptcl->mPosition.z); + if (dusk::frame_interp::lookup_replacement(ptcl, ptclPosMtx)) { + pos.set(ptclPosMtx[0][3], ptclPosMtx[1][3], ptclPosMtx[2][3]); + sinRot = ptclPosMtx[1][0]; + cosRot = ptclPosMtx[0][0]; + MTXMultVec(work->mPosCamMtx, &pos, &pos); + } else +#endif + { + MTXMultVec(work->mPosCamMtx, &ptcl->mPosition, &pos); + sinRot = JMASSin(ptcl->mRotateAngle); + cosRot = JMASCos(ptcl->mRotateAngle); + } + f32 particleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX; + f32 particleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY; + Mtx posMtx; + posMtx[0][0] = cosRot * particleX; + posMtx[0][1] = -sinRot * particleY; + posMtx[0][3] = pos.x; + posMtx[1][0] = sinRot * particleX; + posMtx[1][1] = cosRot * particleY; + posMtx[1][3] = pos.y; + posMtx[2][2] = 1.0f; + posMtx[2][3] = pos.z; + posMtx[0][2] = posMtx[1][2] = posMtx[2][0] = posMtx[2][1] = 0.0f; + GXLoadPosMtxImm(posMtx, GX_PNMTX0); + p_prj[work->mPrjType](work, posMtx); GXCallDisplayList(jpa_dl, sizeof(jpa_dl)); } @@ -484,7 +532,7 @@ void JPADrawYBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) { local_38[2][2] = work->mYBBCamMtx[2][2]; local_38[2][3] = local_48.z; local_38[0][1] = local_38[0][2] = local_38[1][0] = local_38[2][0] = 0.0f; - GXLoadPosMtxImm(local_38, 0); + GXLoadPosMtxImm(local_38, GX_PNMTX0); p_prj[work->mPrjType](work, local_38); GXCallDisplayList(jpa_dl, sizeof(jpa_dl)); } @@ -517,7 +565,7 @@ void JPADrawRotYBillboard(JPAEmitterWorkData* work, JPABaseParticle* param_1) { local_38[2][1] = local_94 * fVar1; local_38[2][2] = local_90; local_38[2][3] = local_48.z; - GXLoadPosMtxImm(local_38, 0); + GXLoadPosMtxImm(local_38, GX_PNMTX0); p_prj[work->mPrjType](work, local_38); GXCallDisplayList(jpa_dl, sizeof(jpa_dl)); } @@ -681,103 +729,197 @@ static u8* p_dl[2] = { jpa_dl_x, }; -void JPADrawDirection(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) { - if (param_1->checkStatus(JPAPtclStts_Invisible)) { +#if TARGET_PC +void JPAInterpDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) { + JGeometry::TVec3 axisY; + JGeometry::TVec3 axisZ; + p_direction[work->mDirType](work, ptcl, &axisY); + + if (axisY.isZero()) { return; } - ZoneScoped; + axisY.normalize(); + axisZ.cross(ptcl->mBaseAxis, axisY); - JGeometry::TVec3 local_6c; - JGeometry::TVec3 local_78; - p_direction[param_0->mDirType](param_0, param_1, &local_6c); - - if (local_6c.isZero()) { + if (axisZ.isZero()) { return; } - local_6c.normalize(); - local_78.cross(param_1->mBaseAxis, local_6c); - - if (local_78.isZero()) { - return; - } - - local_78.normalize(); - param_1->mBaseAxis.cross(local_6c, local_78); - param_1->mBaseAxis.normalize(); - Mtx local_60; - f32 fVar1 = param_0->mGlobalPtclScl.x * param_1->mParticleScaleX; - f32 fVar2 = param_0->mGlobalPtclScl.y * param_1->mParticleScaleY; - local_60[0][0] = param_1->mBaseAxis.x; - local_60[0][1] = local_6c.x; - local_60[0][2] = local_78.x; - local_60[0][3] = param_1->mPosition.x; - local_60[1][0] = param_1->mBaseAxis.y; - local_60[1][1] = local_6c.y; - local_60[1][2] = local_78.y; - local_60[1][3] = param_1->mPosition.y; - local_60[2][0] = param_1->mBaseAxis.z; - local_60[2][1] = local_6c.z; - local_60[2][2] = local_78.z; - local_60[2][3] = param_1->mPosition.z; - p_plane[param_0->mPlaneType](local_60, fVar1, fVar2); - MTXConcat(param_0->mPosCamMtx, local_60, local_60); - GXLoadPosMtxImm(local_60, 0); - p_prj[param_0->mPrjType](param_0, local_60); - GXCallDisplayList(p_dl[param_0->mDLType], sizeof(jpa_dl)); + axisZ.normalize(); + ptcl->mBaseAxis.cross(axisY, axisZ); + ptcl->mBaseAxis.normalize(); + Mtx posMtx; + f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX; + f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY; + posMtx[0][0] = ptcl->mBaseAxis.x; + posMtx[0][1] = axisY.x; + posMtx[0][2] = axisZ.x; + posMtx[0][3] = ptcl->mPosition.x; + posMtx[1][0] = ptcl->mBaseAxis.y; + posMtx[1][1] = axisY.y; + posMtx[1][2] = axisZ.y; + posMtx[1][3] = ptcl->mPosition.y; + posMtx[2][0] = ptcl->mBaseAxis.z; + posMtx[2][1] = axisY.z; + posMtx[2][2] = axisZ.z; + posMtx[2][3] = ptcl->mPosition.z; + p_plane[work->mPlaneType](posMtx, scaleX, scaleY); + dusk::frame_interp::record_final_mtx(posMtx, ptcl); } -void JPADrawRotDirection(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) { - if (param_1->checkStatus(JPAPtclStts_Invisible)) { +void JPAInterpRotDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) { + f32 sinRot = JMASSin(ptcl->mRotateAngle); + f32 cosRot = JMASCos(ptcl->mRotateAngle); + JGeometry::TVec3 axisY; + JGeometry::TVec3 axisZ; + p_direction[work->mDirType](work, ptcl, &axisY); + + if (axisY.isZero()) { + return; + } + + axisY.normalize(); + axisZ.cross(ptcl->mBaseAxis, axisY); + + if (axisZ.isZero()) { + return; + } + + axisZ.normalize(); + ptcl->mBaseAxis.cross(axisY, axisZ); + ptcl->mBaseAxis.normalize(); + f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX; + f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY; + Mtx mtx1; + Mtx mtx2; + p_rot[work->mRotType](sinRot, cosRot, mtx1); + p_plane[work->mPlaneType](mtx1, scaleX, scaleY); + mtx2[0][0] = ptcl->mBaseAxis.x; + mtx2[0][1] = axisY.x; + mtx2[0][2] = axisZ.x; + mtx2[0][3] = ptcl->mPosition.x; + mtx2[1][0] = ptcl->mBaseAxis.y; + mtx2[1][1] = axisY.y; + mtx2[1][2] = axisZ.y; + mtx2[1][3] = ptcl->mPosition.y; + mtx2[2][0] = ptcl->mBaseAxis.z; + mtx2[2][1] = axisY.z; + mtx2[2][2] = axisZ.z; + mtx2[2][3] = ptcl->mPosition.z; + MTXConcat(mtx2, mtx1, mtx1); + dusk::frame_interp::record_final_mtx(mtx1, ptcl); +} +#endif + +void JPADrawDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) { + if (ptcl->checkStatus(JPAPtclStts_Invisible)) { return; } ZoneScoped; - f32 sinRot = JMASSin(param_1->mRotateAngle); - f32 cosRot = JMASCos(param_1->mRotateAngle); - JGeometry::TVec3 local_6c; - JGeometry::TVec3 local_78; - p_direction[param_0->mDirType](param_0, param_1, &local_6c); + Mtx posMtx; +#if TARGET_PC + if (!dusk::frame_interp::lookup_replacement(ptcl, posMtx)) +#endif + { + JGeometry::TVec3 axisY; + JGeometry::TVec3 axisZ; + p_direction[work->mDirType](work, ptcl, &axisY); - if (local_6c.isZero()) { + if (axisY.isZero()) { + return; + } + + axisY.normalize(); + axisZ.cross(ptcl->mBaseAxis, axisY); + + if (axisZ.isZero()) { + return; + } + + axisZ.normalize(); + ptcl->mBaseAxis.cross(axisY, axisZ); + ptcl->mBaseAxis.normalize(); + f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX; + f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY; + posMtx[0][0] = ptcl->mBaseAxis.x; + posMtx[0][1] = axisY.x; + posMtx[0][2] = axisZ.x; + posMtx[0][3] = ptcl->mPosition.x; + posMtx[1][0] = ptcl->mBaseAxis.y; + posMtx[1][1] = axisY.y; + posMtx[1][2] = axisZ.y; + posMtx[1][3] = ptcl->mPosition.y; + posMtx[2][0] = ptcl->mBaseAxis.z; + posMtx[2][1] = axisY.z; + posMtx[2][2] = axisZ.z; + posMtx[2][3] = ptcl->mPosition.z; + p_plane[work->mPlaneType](posMtx, scaleX, scaleY); + } + + MTXConcat(work->mPosCamMtx, posMtx, posMtx); + GXLoadPosMtxImm(posMtx, GX_PNMTX0); + p_prj[work->mPrjType](work, posMtx); + GXCallDisplayList(p_dl[work->mDLType], sizeof(jpa_dl)); +} + +void JPADrawRotDirection(JPAEmitterWorkData* work, JPABaseParticle* ptcl) { + if (ptcl->checkStatus(JPAPtclStts_Invisible)) { return; } - local_6c.normalize(); - local_78.cross(param_1->mBaseAxis, local_6c); + ZoneScoped; - if (local_78.isZero()) { - return; + Mtx mtx1; + Mtx mtx2; +#if TARGET_PC + if (!dusk::frame_interp::lookup_replacement(ptcl, mtx1)) +#endif + { + f32 sinRot = JMASSin(ptcl->mRotateAngle); + f32 cosRot = JMASCos(ptcl->mRotateAngle); + JGeometry::TVec3 axisY; + JGeometry::TVec3 axisZ; + p_direction[work->mDirType](work, ptcl, &axisY); + + if (axisY.isZero()) { + return; + } + + axisY.normalize(); + axisZ.cross(ptcl->mBaseAxis, axisY); + + if (axisZ.isZero()) { + return; + } + + axisZ.normalize(); + ptcl->mBaseAxis.cross(axisY, axisZ); + ptcl->mBaseAxis.normalize(); + f32 scaleX = work->mGlobalPtclScl.x * ptcl->mParticleScaleX; + f32 scaleY = work->mGlobalPtclScl.y * ptcl->mParticleScaleY; + p_rot[work->mRotType](sinRot, cosRot, mtx1); + p_plane[work->mPlaneType](mtx1, scaleX, scaleY); + mtx2[0][0] = ptcl->mBaseAxis.x; + mtx2[0][1] = axisY.x; + mtx2[0][2] = axisZ.x; + mtx2[0][3] = ptcl->mPosition.x; + mtx2[1][0] = ptcl->mBaseAxis.y; + mtx2[1][1] = axisY.y; + mtx2[1][2] = axisZ.y; + mtx2[1][3] = ptcl->mPosition.y; + mtx2[2][0] = ptcl->mBaseAxis.z; + mtx2[2][1] = axisY.z; + mtx2[2][2] = axisZ.z; + mtx2[2][3] = ptcl->mPosition.z; + MTXConcat(mtx2, mtx1, mtx1); } - - local_78.normalize(); - param_1->mBaseAxis.cross(local_6c, local_78); - param_1->mBaseAxis.normalize(); - f32 particleX = param_0->mGlobalPtclScl.x * param_1->mParticleScaleX; - f32 particleY = param_0->mGlobalPtclScl.y * param_1->mParticleScaleY; - Mtx auStack_80; - Mtx local_60; - p_rot[param_0->mRotType](sinRot, cosRot, auStack_80); - p_plane[param_0->mPlaneType](auStack_80, particleX, particleY); - local_60[0][0] = param_1->mBaseAxis.x; - local_60[0][1] = local_6c.x; - local_60[0][2] = local_78.x; - local_60[0][3] = param_1->mPosition.x; - local_60[1][0] = param_1->mBaseAxis.y; - local_60[1][1] = local_6c.y; - local_60[1][2] = local_78.y; - local_60[1][3] = param_1->mPosition.y; - local_60[2][0] = param_1->mBaseAxis.z; - local_60[2][1] = local_6c.z; - local_60[2][2] = local_78.z; - local_60[2][3] = param_1->mPosition.z; - MTXConcat(local_60, auStack_80, auStack_80); - MTXConcat(param_0->mPosCamMtx, auStack_80, local_60); - GXLoadPosMtxImm(local_60, 0); - p_prj[param_0->mPrjType](param_0, local_60); - GXCallDisplayList(p_dl[param_0->mDLType], sizeof(jpa_dl)); + MTXConcat(work->mPosCamMtx, mtx1, mtx2); + GXLoadPosMtxImm(mtx2, GX_PNMTX0); + p_prj[work->mPrjType](work, mtx2); + GXCallDisplayList(p_dl[work->mDLType], sizeof(jpa_dl)); } void JPADrawDBillboard(JPAEmitterWorkData* param_0, JPABaseParticle* param_1) { diff --git a/libs/JSystem/src/JParticle/JPAParticle.cpp b/libs/JSystem/src/JParticle/JPAParticle.cpp index a9294e0b4e..d3490ab294 100644 --- a/libs/JSystem/src/JParticle/JPAParticle.cpp +++ b/libs/JSystem/src/JParticle/JPAParticle.cpp @@ -204,6 +204,28 @@ void JPABaseParticle::init_c(JPAEmitterWorkData* work, JPABaseParticle* parent) mTexAnmIdx = 0; } +#if TARGET_PC +void JPABaseParticle::interp(JPAEmitterWorkData* work, void const* drawFunc) { + static bool enable = false; + if (!enable) + return; + + // don't interpolate the first frame + if (mAge == 0) + return; + + if (drawFunc == JPADrawBillboard) { + JPAInterpBillboard(work, this); + } else if (drawFunc == JPADrawRotBillboard) { + JPAInterpRotBillboard(work, this); + } else if (drawFunc == JPADrawDirection) { + JPAInterpDirection(work, this); + } else if (drawFunc == JPADrawRotDirection) { + JPAInterpRotDirection(work, this); + } +} +#endif + bool JPABaseParticle::calc_p(JPAEmitterWorkData* work) { if (++mAge >= mLifeTime) { return true; @@ -247,6 +269,17 @@ bool JPABaseParticle::calc_p(JPAEmitterWorkData* work) { mOffsetPosition.y + mLocalPosition.y * work->mPublicScale.y, mOffsetPosition.z + mLocalPosition.z * work->mPublicScale.z); +#if TARGET_PC + JPABaseShape* pBsp = work->mpRes->getBsp(); + work->mGlobalPtclScl.x = work->mpEmtr->mGlobalPScl.x * pBsp->getBaseSizeX(); + work->mGlobalPtclScl.y = work->mpEmtr->mGlobalPScl.y * pBsp->getBaseSizeY(); + work->mDirType = pBsp->getDirType(); + work->mRotType = pBsp->getRotType(); + work->mDLType = pBsp->getType() == 4 || pBsp->getType() == 8; + work->mPlaneType = work->mDLType ? 2 : pBsp->getBasePlaneType(); + interp(work, (void const*)work->mpRes->mpDrawParticleFuncList[0]); +#endif + return false; } @@ -289,6 +322,23 @@ bool JPABaseParticle::calc_c(JPAEmitterWorkData* work) { mOffsetPosition.y + mLocalPosition.y * work->mPublicScale.y, mOffsetPosition.z + mLocalPosition.z * work->mPublicScale.z); +#if TARGET_PC + JPABaseShape* pBsp = work->mpRes->getBsp(); + JPAChildShape* pCsp = work->mpRes->getCsp(); + if (pCsp->isScaleInherited()) { + work->mGlobalPtclScl.x = work->mpEmtr->mGlobalPScl.x * pBsp->getBaseSizeX(); + work->mGlobalPtclScl.y = work->mpEmtr->mGlobalPScl.y * pBsp->getBaseSizeY(); + } else { + work->mGlobalPtclScl.x = work->mpEmtr->mGlobalPScl.x * pCsp->getScaleX(); + work->mGlobalPtclScl.y = work->mpEmtr->mGlobalPScl.y * pCsp->getScaleY(); + } + work->mDirType = pCsp->getDirType(); + work->mRotType = pCsp->getRotType(); + work->mDLType = pCsp->getType() == 4 || pCsp->getType() == 8; + work->mPlaneType = work->mDLType ? 2 : pCsp->getBasePlaneType(); + interp(work, (void const*)work->mpRes->mpDrawParticleChildFuncList[0]); +#endif + return false; } diff --git a/libs/JSystem/src/JUtility/JUTPalette.cpp b/libs/JSystem/src/JUtility/JUTPalette.cpp index d98bf98c2e..9c8a51432c 100644 --- a/libs/JSystem/src/JUtility/JUTPalette.cpp +++ b/libs/JSystem/src/JUtility/JUTPalette.cpp @@ -38,3 +38,9 @@ bool JUTPalette::load() { return check; } + +#if TARGET_PC +void JUTPalette::dataUploaded() { + GXInitTlutObjData(&mTlutObj, (void*)mColorTable); +} +#endif diff --git a/libs/JSystem/src/JUtility/JUTTexture.cpp b/libs/JSystem/src/JUtility/JUTTexture.cpp index 060c581e80..1d799c2820 100644 --- a/libs/JSystem/src/JUtility/JUTTexture.cpp +++ b/libs/JSystem/src/JUtility/JUTTexture.cpp @@ -27,7 +27,7 @@ void JUTTexture::storeTIMG(ResTIMG const* param_0, u8 param_1) { mTexData = (void*)((intptr_t)mTexInfo + 0x20); } - field_0x2c = NULL; + mPalette = NULL; mTlutName = 0; mWrapS = mTexInfo->wrapS; mWrapT = mTexInfo->wrapT; @@ -95,7 +95,7 @@ void JUTTexture::storeTIMG(ResTIMG const* param_0, JUTPalette* param_1, GXTlut p } mEmbPalette = param_1; setEmbPaletteDelFlag(false); - field_0x2c = NULL; + mPalette = NULL; if (param_1 != NULL) { mTlutName = param_2; if (param_2 != param_1->getTlutName()) { @@ -120,11 +120,11 @@ void JUTTexture::storeTIMG(ResTIMG const* param_0, JUTPalette* param_1, GXTlut p void JUTTexture::attachPalette(JUTPalette* param_0) { if (mTexInfo->indexTexture) { if (param_0 == NULL && mEmbPalette != NULL) { - field_0x2c = mEmbPalette; + mPalette = mEmbPalette; } else { - field_0x2c = param_0; + mPalette = param_0; } - initTexObj(field_0x2c->getTlutName()); + initTexObj(mPalette->getTlutName()); } } @@ -133,9 +133,9 @@ void JUTTexture::init() { initTexObj(); } else { if (mEmbPalette != NULL) { - field_0x2c = mEmbPalette; + mPalette = mEmbPalette; - initTexObj(field_0x2c->getTlutName()); + initTexObj(mPalette->getTlutName()); } else { OS_REPORT("This texture is CI-Format, but EmbPalette is NULL.\n"); } @@ -179,8 +179,8 @@ void JUTTexture::initTexObj(GXTlut param_0) { } void JUTTexture::load(GXTexMapID param_0) { - if (field_0x2c) { - field_0x2c->load(); + if (mPalette) { + mPalette->load(); } GXLoadTexObj(&mTexObj, param_0); } diff --git a/res/AlegreyaSC-Bold.ttf b/res/AlegreyaSC-Bold.ttf new file mode 100644 index 0000000000..dc73beb916 Binary files /dev/null and b/res/AlegreyaSC-Bold.ttf differ diff --git a/res/AlegreyaSC-Regular.ttf b/res/AlegreyaSC-Regular.ttf new file mode 100644 index 0000000000..31cae6c8ad Binary files /dev/null and b/res/AlegreyaSC-Regular.ttf differ diff --git a/res/FiraSans-Bold.ttf b/res/FiraSans-Bold.ttf new file mode 100644 index 0000000000..e3593fb0f3 Binary files /dev/null and b/res/FiraSans-Bold.ttf differ diff --git a/res/FiraSans-Regular.ttf b/res/FiraSans-Regular.ttf new file mode 100644 index 0000000000..6f80647494 Binary files /dev/null and b/res/FiraSans-Regular.ttf differ diff --git a/res/FiraSansCondensed-Bold.ttf b/res/FiraSansCondensed-Bold.ttf new file mode 100644 index 0000000000..ec7e841549 Binary files /dev/null and b/res/FiraSansCondensed-Bold.ttf differ diff --git a/res/FiraSansCondensed-Regular.ttf b/res/FiraSansCondensed-Regular.ttf new file mode 100644 index 0000000000..6e1a192127 Binary files /dev/null and b/res/FiraSansCondensed-Regular.ttf differ diff --git a/res/MaterialSymbolsRounded-Regular.ttf b/res/MaterialSymbolsRounded-Regular.ttf new file mode 100644 index 0000000000..782fc0a406 Binary files /dev/null and b/res/MaterialSymbolsRounded-Regular.ttf differ diff --git a/res/logo-mascot.png b/res/logo-mascot.png new file mode 100644 index 0000000000..9f9a5d1ace Binary files /dev/null and b/res/logo-mascot.png differ diff --git a/res/logo-mascot.webp b/res/logo-mascot.webp deleted file mode 100644 index c22f3bc90f..0000000000 Binary files a/res/logo-mascot.webp and /dev/null differ diff --git a/res/prelaunch-bg.png b/res/prelaunch-bg.png new file mode 100644 index 0000000000..045dcbe655 Binary files /dev/null and b/res/prelaunch-bg.png differ diff --git a/res/rml/overlay.rcss b/res/rml/overlay.rcss new file mode 100644 index 0000000000..e18bb436e5 --- /dev/null +++ b/res/rml/overlay.rcss @@ -0,0 +1,147 @@ +*, *:before, *:after { + box-sizing: border-box; +} + +body { + overflow: visible; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + font-family: "Fira Sans Condensed"; + font-size: 24dp; + color: #FFFFFF; + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: stretch; +} + +.overlay-root { + width: 100%; + min-height: 45%; + display: flex; + flex-direction: column; + justify-content: flex-end; + align-items: stretch; + decorator: vertical-gradient(#00000000 #151610F2); + filter: opacity(0); + transition: filter 0.2s linear-in-out; +} + +.overlay-root[open] { + filter: opacity(1); +} + +.overlay { + width: 100%; + max-width: 1216dp; + margin-left: auto; + margin-right: auto; + display: flex; + flex-direction: column; + gap: 24dp; + padding: 48dp 64dp; +} + +@media (max-height: 800dp) { + .overlay-root { + min-height: 38%; + } + + .overlay { + gap: 16dp; + padding: 32dp 48dp; + } +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + gap: 24dp; +} + +.carousel-container { + flex: 1 1 auto; + display: flex; + justify-content: flex-end; + min-width: 0; +} + +.description { + font-size: 18dp; + line-height: 22dp; + color: rgba(255, 255, 255, 50%); +} + +.divider { + margin: 1dp 0; + border-top: 1dp rgba(217, 217, 217, 50%); +} + +.footer { + display: flex; + justify-content: space-between; + align-items: center; + gap: 24dp; +} + +footer-button { + display: block; + width: 100%; + max-width: 220dp; + border: 0; + padding: 0; + background-color: transparent; + font-family: "Fira Sans Condensed"; + font-weight: bold; + font-size: 20dp; + line-height: 24dp; + text-transform: uppercase; + color: #FFFFFF; + opacity: 1; + cursor: pointer; +} + +footer-button.return { + text-align: left; +} + +footer-button.reset { + text-align: right; +} + +.stepped-carousel { + display: flex; + align-items: center; + justify-content: center; + gap: 16dp; + width: auto; + min-width: 246dp; + padding: 0; + background-color: transparent; + font-family: "Fira Sans Condensed"; + font-weight: bold; +} + +.stepped-carousel-value { + line-height: 29dp; + min-width: 166dp; + text-align: center; + white-space: nowrap; + opacity: 0.9; +} + +.stepped-carousel-arrow { + width: 24dp; + height: 24dp; + min-width: 24dp; + padding: 0; + border: 0; + background-color: transparent; + opacity: 1; + cursor: pointer; + font-family: "Material Symbols Rounded"; + font-weight: normal; +} diff --git a/res/rml/popup.rcss b/res/rml/popup.rcss new file mode 100644 index 0000000000..effc80344d --- /dev/null +++ b/res/rml/popup.rcss @@ -0,0 +1,45 @@ +*, *:before, *:after { + box-sizing: border-box; +} + +body { + overflow: visible; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + font-family: "Fira Sans Condensed"; + font-weight: bold; + font-size: 18dp; + color: #E0DBC8; +} + +button { + cursor: pointer; + focus: auto; +} + +popup { + width: 100%; + display: flex; + align-items: stretch; + height: 64dp; + background-color: rgba(21, 22, 16, 80%); + border-bottom: 2dp #92875B; + backdrop-filter: blur(5dp); + transform: translateY(-64dp); + transition: transform 0.2s cubic-in-out; +} + +popup[open] { + transform: translateY(0); +} + +popup tab-bar { + flex: 1 1 0; +} + +popup tab-bar tab { + opacity: 0.35; + color: #E0DBC8; +} diff --git a/res/rml/prelaunch.rcss b/res/rml/prelaunch.rcss new file mode 100644 index 0000000000..9556e74ef2 --- /dev/null +++ b/res/rml/prelaunch.rcss @@ -0,0 +1,212 @@ +*, *:before, *:after { + box-sizing: border-box; +} + +body { + width: 100%; + height: 100%; + font-family: "Fira Sans"; + font-weight: normal; + font-size: 20dp; + color: #FFFFFF; + background-color: #000000; + decorator: image(../prelaunch-bg.png cover left center); + filter: opacity(0); + transition: filter 1s 0.2s linear-in-out; + z-index: -1; +} + +body[open] { + filter: opacity(1); +} + +content { + display: block; + width: 100%; + height: 100%; + filter: opacity(0); + transition: filter 0.2s linear-in-out; +} + +content[open] { + filter: opacity(1); +} + +menu { + position: absolute; + left: 96dp; + top: 50%; + transform: translateY(-50%); + /* Scale based on a reference screen width, 428/1216 */ + width: 35.230264vw; + min-width: 428dp; + max-width: 856dp; + height: auto; + display: flex; + flex-direction: column; + gap: 48dp; +} + +hero { + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + gap: 8dp; +} + +hero img { + width: 100%; +} + +.eyebrow { + font-family: "Alegreya SC"; + font-size: 32dp; +} + +@media (min-width: 1216dp) { + .eyebrow { + /* Same logic as .menu, 32/1216 */ + font-size: 2.631579vw; + } +} + +.eyebrow span { + font-weight: bold; +} + +#menu-list { + display: flex; + flex-direction: column; + gap: 12dp; +} + +#menu-list button { + width: 428dp; + height: 54dp; + padding: 8dp 16dp; + border-radius: 8dp; + text-transform: uppercase; + font-family: "Fira Sans Condensed"; + font-size: 32dp; + font-weight: normal; + cursor: pointer; + /* Define a fully transparent gradient as the default state, otherwise a white flash occurs */ + decorator: horizontal-gradient(#00000000 #00000000); +} + +#menu-list button.anim-done { + transition: decorator color 0.1s linear-in-out; +} + +#menu-list button:hover, +#menu-list button:focus-visible { + color: black; + decorator: horizontal-gradient(#FEE685FF #FEE68500); +} + +disc-info { + position: absolute; + left: 96dp; + bottom: 72dp; + display: flex; + flex-direction: column; + gap: 12dp; + font-size: 24dp; +} + +version-info { + position: absolute; + right: 96dp; + bottom: 72dp; + display: flex; + flex-direction: column; + gap: 12dp; + text-align: right; + font-size: 24dp; +} + +#disc-status { + display: flex; + align-items: center; + gap: 8dp; +} + +#disc-status[status=good] { + color: #D8F999; +} + +#disc-status[status=bad] { + color: #FFC9C9; +} + +#disc-status icon { + display: block; + width: 24dp; + height: 24dp; + font-family: "Material Symbols Rounded"; + font-weight: normal; + font-size: 24dp; +} + +#disc-status[status=good] icon { + decorator: text("" center center); +} + +#disc-status[status=bad] icon { + decorator: text("" center center); +} + +#disc-version { + font-size: 20dp; +} + +/* TODO: Hidden until an actual update checker is introduced */ +.update { + display: none; + font-size: 16dp; + font-weight: bold; + cursor: pointer; + color: #D8F999; +} + +.detail, +.update span { + color: #A6A09B; +} + +/* Startup animation */ +.intro-item { + opacity: 0; + transform: translateY(10dp); + transition: opacity transform 0.3s 0.1s cubic-in-out; +} + +body.animate-in .intro-item { + opacity: 1; + transform: translateY(0dp); +} + +.delay-0 { + transition: opacity transform 0.3s 0.1s cubic-in-out; +} + +.delay-1 { + transition: opacity transform 0.3s 0.2s cubic-in-out; +} + +.delay-2 { + transition: opacity transform 0.3s 0.3s cubic-in-out; +} + +.delay-3 { + transition: opacity transform 0.3s 0.4s cubic-in-out; +} + +.delay-4 { + transition: opacity transform 0.3s 0.5s cubic-in-out; +} + +.delay-5 { + transition: opacity transform 0.3s 0.6s cubic-in-out; +} diff --git a/res/rml/tabbing.rcss b/res/rml/tabbing.rcss new file mode 100644 index 0000000000..194ca89f05 --- /dev/null +++ b/res/rml/tabbing.rcss @@ -0,0 +1,81 @@ +tab-bar { + display: flex; + position: relative; + min-width: 0; + overflow: auto hidden; + clip: always; + text-transform: uppercase; +} + +tab-bar scrollbarhorizontal, +tab-bar scrollbarhorizontal sliderarrowdec, +tab-bar scrollbarhorizontal sliderarrowinc, +tab-bar scrollbarhorizontal slidertrack, +tab-bar scrollbarhorizontal sliderbar { + width: 0; + height: 0; +} + +tab-bar tab { + flex: 0 0 auto; + padding: 0 24dp; + line-height: 64dp; + white-space: nowrap; + decorator: vertical-gradient(#c2a42d00 #c2a42d00); + transition: decorator 0.1s linear-in-out, opacity 0.1s linear-in-out; + cursor: pointer; +} + +tab-bar tab:selected { + opacity: 1; + border-bottom: 4dp #C2A42D; + font-effect: glow(0dp 4dp 0dp 4dp black); +} + +tab-bar tab:focus-visible, +tab-bar tab:hover { + opacity: 1; + font-effect: glow(0dp 4dp 0dp 4dp black); + decorator: vertical-gradient(#c2a42d00 #c2a42d26); +} + +tab-bar tab:active { + decorator: vertical-gradient(#c2a42d10 #c2a42d40); +} + +tab-bar[closable] tab-end-spacer { + display: block; + flex: 0 0 64dp; + width: 64dp; + pointer-events: none; +} + +tab-bar[closable] close { + display: block; + position: fixed; + top: 8dp; + right: 8dp; + z-index: 1; + width: 48dp; + height: 48dp; + font-family: "Material Symbols Rounded"; + font-weight: normal; + font-size: 24dp; + color: rgba(224, 219, 200, 70%); + backdrop-filter: blur(2dp); + border-radius: 6dp; + decorator: text("" center center); + transition: color background-color 0.12s linear-in-out; + cursor: pointer; +} + +tab-bar[closable] close:hover, +tab-bar[closable] close:focus-visible { + color: #fff; + background-color: rgba(194, 164, 45, 24%); +} + +tab-bar[closable] close:active { + color: #fff; + background-color: rgba(194, 164, 45, 40%); +} diff --git a/res/rml/window.rcss b/res/rml/window.rcss new file mode 100644 index 0000000000..db3558778a --- /dev/null +++ b/res/rml/window.rcss @@ -0,0 +1,401 @@ +*, *:before, *:after { + box-sizing: border-box; +} + +body { + display: flex; + width: 100%; + height: 100%; + padding: 64dp; + font-family: "Fira Sans"; + font-weight: normal; + font-style: normal; + font-size: 15dp; + color: #E0DBC8; +} + +window { + display: flex; + flex-flow: column; + height: 100%; + width: 100%; + max-width: 1088dp; + max-height: 768dp; + margin: auto; + border-radius: 14dp; + overflow: hidden; + border: 2dp #92875B; + backdrop-filter: blur(5dp); + box-shadow: 0 0 25dp 5dp; + background-color: rgba(21, 22, 16, 90%); + filter: opacity(0); + transform: scale(0.9); + transform-origin: center; + transition: filter transform 0.2s cubic-in-out; +} + +window.small { + height: auto; + width: auto; +} + +window.preset { + min-width: 650dp; +} + +window[open] { + filter: opacity(1); + transform: scale(1); +} + +@media (max-height: 640dp) { + body { + padding: 16dp; + } + window { + box-shadow: none; + } +} + +window tab-bar { + flex: 0 0 64dp; + height: 64dp; + background-color: rgba(217, 217, 217, 10%); + font-family: "Fira Sans Condensed"; + font-weight: bold; + font-size: 18dp; + border-bottom: 2dp #92875B; +} + +window tab-bar tab { + opacity: 0.25; +} + +window content { + display: flex; + flex: 1 1 auto; + min-width: 0; + min-height: 0; + overflow: hidden; +} + +window content pane { + display: flex; + flex-flow: column; + flex: 1 1 0; + min-width: 0; + min-height: 0; + padding: 24dp; + padding-bottom: 0dp; + gap: 8dp; + overflow: hidden auto; + font-size: 20dp; +} + +window content pane:not(:last-of-type) { + border-right: 1dp #92875B; +} + +window content pane > * { + flex: 0 0 auto; +} + +window content pane > spacer { + display: block; + /* Completes the 24dp bottom inset after the pane's 8dp gap. */ + flex: 0 0 16dp; + height: 16dp; + pointer-events: none; +} + +scrollbarvertical { + width: 8dp; + margin: 4dp 4dp 4dp 0; +} + +scrollbarvertical sliderarrowdec, +scrollbarvertical sliderarrowinc { + width: 0; + height: 0; +} + +scrollbarvertical slidertrack { + width: 8dp; +} + +scrollbarvertical sliderbar { + width: 8dp; + min-height: 24dp; + background-color: rgba(224, 219, 200, 45%); + border-radius: 2dp; + transition: background-color 0.2s cubic-in-out; +} + +scrollbarvertical sliderbar:hover, +scrollbarvertical sliderbar:active { + background-color: rgba(194, 164, 45, 80%); +} + +scrollbarhorizontal { + height: 0; +} + +scrollbarhorizontal sliderarrowdec, +scrollbarhorizontal sliderarrowinc { + width: 0; + height: 0; +} + +scrollbarhorizontal slidertrack, +scrollbarhorizontal sliderbar { + width: 0; + height: 0; +} + +.section-heading { + font-family: "Fira Sans Condensed"; + font-weight: bold; + text-transform: uppercase; + font-size: 22dp; + opacity: 0.25; +} + +.section-heading:not(:first-of-type) { + padding-top: 12dp; +} + +button { + text-align: center; + background-color: rgba(17, 16, 10, 20%); + opacity: 0.9; + padding: 8dp 16dp; + border-radius: 14dp; + box-shadow: rgba(146, 135, 91, 25%) 0 0 0 1dp; + font-size: 20dp; + transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out; + cursor: pointer; + focus: auto; +} + +button:not(:disabled):hover, +button:not(:disabled):focus-visible { + background-color: rgba(204, 184, 119, 20%); + box-shadow: #C2A42D 0 0 0 2dp; +} + +button:not(:disabled):selected { + opacity: 1; + background-color: rgba(204, 184, 119, 40%); +} + +button:not(:disabled):active { + opacity: 1; + background-color: rgba(204, 184, 119, 40%); + box-shadow: #C2A42D 0 0 0 2dp; +} + +select-button { + display: flex; + align-items: center; + gap: 8dp; + background-color: rgba(17, 16, 10, 20%); + opacity: 0.9; + padding: 8dp 16dp; + border-radius: 14dp; + box-shadow: rgba(146, 135, 91, 25%) 0 0 0 1dp; + transition: background-color 0.1s linear-in-out, opacity 0.1s linear-in-out; + cursor: pointer; + focus: auto; +} + +select-button:not(:disabled):hover, +select-button:not(:disabled):focus-visible { + background-color: rgba(204, 184, 119, 20%); + box-shadow: #C2A42D 0 0 0 2dp; +} + +select-button:not(:disabled):selected { + opacity: 1; + background-color: rgba(204, 184, 119, 40%); +} + +select-button:not(:disabled):active { + opacity: 1; + background-color: rgba(204, 184, 119, 40%); + box-shadow: #C2A42D 0 0 0 2dp; +} + +select-button:disabled { + opacity: 0.35; + cursor: default; +} + +select-button key { + font-family: "Fira Sans Condensed"; + font-weight: bold; + font-size: 18dp; + text-transform: uppercase; + flex: 0 1 auto; +} + +select-button value { + flex: 1 1 auto; + text-align: right; + font-size: 20dp; +} + +select-button value.modified { + font-weight: bold; +} + +select-button input { + text-align: right; + font-size: 20dp; +} + +icon { + font-family: "Material Symbols Rounded"; + font-weight: normal; + display: inline-block; + vertical-align: middle; +} + +icon.warning { + width: 1em; + height: 1em; + decorator: text("" center center); + color: #ffcc00; +} + +.achievement-row { + display: flex; + align-items: flex-start; + gap: 10dp; + padding: 12dp 0; + border-bottom: 1dp rgba(146, 135, 91, 30%); +} + +.achievement-info { + display: block; + flex: 1 1 0; + min-width: 0; +} + +.achievement-header { + display: flex; + align-items: center; +} + +.achievement-name { + flex: 1; + font-weight: bold; +} + +.achievement-name.unlocked { + color: #ffa826; +} + +.achievement-badge { + font-size: 14dp; + opacity: 0.7; +} + +.achievement-badge.unlocked { + color: #44cc55; + opacity: 1; +} + +.achievement-badge.locked { + color: #cc4444; + opacity: 1; +} + +.achievement-desc { + display: block; + color: rgba(224, 219, 200, 55%); + font-size: 16dp; + margin: 4dp 0 0 0; +} + +.achievement-progress { + display: block; + font-size: 13dp; + color: rgba(224, 219, 200, 45%); +} + +progressbar { + display: block; + width: 100%; + height: 6dp; + border-radius: 3dp; + background-color: rgba(255, 255, 255, 10%); + margin: 6dp 0 2dp 0; +} + +progressbar.progress-done fill { + background-color: #44aa22; + border-radius: 3dp; +} + +progressbar.progress-ongoing fill { + background-color: #2255bb; + border-radius: 3dp; +} + +button.achievement-clear { + flex: 0 0 auto; + align-self: center; + font-size: 14dp; + padding: 2dp 8dp; + opacity: 0.45; +} + +.preset-dialog { + display: flex; + flex-flow: column; + padding: 32dp; + gap: 20dp; + flex: 0 1 auto; +} + +.preset-title { + display: block; + font-family: "Fira Sans Condensed"; + font-weight: bold; + font-size: 30dp; + text-align: center; +} + +.preset-intro { + display: block; + font-size: 18dp; + text-align: center; + color: rgba(224, 219, 200, 65%); +} + +.preset-grid { + display: flex; + flex-direction: row; + gap: 20dp; + flex: 0 1 auto; + align-items: flex-start; +} + +.preset-col { + display: flex; + flex-flow: column; + gap: 12dp; + flex: 1 1 0; +} + +button.preset-btn { + font-size: 22dp; + padding: 20dp 16dp; +} + +.preset-desc { + display: block; + font-size: 16dp; + color: rgba(224, 219, 200, 65%); + text-align: center; +} diff --git a/src/SSystem/SComponent/c_lib.cpp b/src/SSystem/SComponent/c_lib.cpp index 6441d6ceb8..d73dfb8627 100644 --- a/src/SSystem/SComponent/c_lib.cpp +++ b/src/SSystem/SComponent/c_lib.cpp @@ -468,9 +468,20 @@ s16 cLib_targetAngleX(cXyz const* lhs, cXyz const* rhs) { void cLib_offsetPos(cXyz* pdest, cXyz const* psrc, s16 angle, cXyz const* vec) { f32 cos = cM_scos(angle); f32 sin = cM_ssin(angle); + // MWCC loads vec members into registers before writing to pdest; other compilers may not, + // which corrupts results when pdest and vec alias the same memory. +#if !__MWERKS__ + f32 vx = vec->x; + f32 vy = vec->y; + f32 vz = vec->z; + pdest->x = psrc->x + (vx * cos + vz * sin); + pdest->y = psrc->y + vy; + pdest->z = psrc->z + (vz * cos - vx * sin); +#else pdest->x = psrc->x + (vec->x * cos + vec->z * sin); pdest->y = psrc->y + vec->y; pdest->z = psrc->z + (vec->z * cos - vec->x * sin); +#endif } /** diff --git a/src/Z2AudioLib/Z2Audience.cpp b/src/Z2AudioLib/Z2Audience.cpp index 5bb91eef31..dda1a4b2d0 100644 --- a/src/Z2AudioLib/Z2Audience.cpp +++ b/src/Z2AudioLib/Z2Audience.cpp @@ -1,5 +1,10 @@ #include "Z2AudioLib/Z2Audience.h" #include "Z2AudioLib/Z2SoundInfo.h" +#if TARGET_PC +#include "dusk/audio/DuskDsp.hpp" +#include "dusk/settings.h" +#include +#endif #include "Z2AudioLib/Z2Calc.h" #include "Z2AudioLib/Z2Param.h" #include "JSystem/JAudio2/JAISound.h" @@ -701,6 +706,11 @@ f32 Z2Audience::calcRelPosVolume(const Vec& param_0, f32 param_1, int camID) { f32 Z2Audience::calcRelPosPan(const Vec& param_0, int camID) { Vec local_54 = param_0; local_54.y = 0.0f; +#if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + local_54.x = -local_54.x; + } +#endif f32 dVar6 = VECMag(&local_54); if (dVar6 < 0.1f) { @@ -734,9 +744,22 @@ f32 Z2Audience::calcRelPosPan(const Vec& param_0, int camID) { f32 Z2Audience::calcRelPosDolby(const Vec& param_0, int camID) { f32 fVar1 = param_0.z + mAudioCamera[camID].getDolbyCenterZ(); +#if TARGET_PC + if (dusk::audio::EnableHrtf) { + // Normalize the direction so result is purely front/back orientation, + // independent of how far away the sound is + f32 lenSq = param_0.x * param_0.x + param_0.y * param_0.y + param_0.z * param_0.z; + if (lenSq < 0.0001f) { + return 0.5f; + } + f32 zNorm = param_0.z / sqrtf(lenSq); + f32 t = (zNorm + 1.0f) * 0.5f; + return 0.5f - 0.5f * cosf(t * static_cast(M_PI)); + } +#endif if (fVar1 > mSetting.field_0x48) { return 1.0f; - } + } if (fVar1 < mSetting.field_0x44) { return 0.0f; diff --git a/src/d/actor/d_a_alink.cpp b/src/d/actor/d_a_alink.cpp index d84e25abd3..31cd2a8062 100644 --- a/src/d/actor/d_a_alink.cpp +++ b/src/d/actor/d_a_alink.cpp @@ -4962,13 +4962,16 @@ int daAlink_c::create() { setArcName(checkWolf()); setOriginalHeap(&mpArcHeap, 0xA2800); + JKRHEAP_NAME(mpArcHeap, "Alink ArcHeap"); if (dComIfG_resLoad(&mPhaseReq, mArcName, mpArcHeap) != cPhs_COMPLEATE_e) { return cPhs_INIT_e; } setShieldArcName(); setOriginalHeap(&mpShieldArcHeap, 0x7000); - if (dComIfG_resLoad(&mShieldPhaseReq, mShieldArcName, mpShieldArcHeap) != cPhs_COMPLEATE_e) { + JKRHEAP_NAME(mpShieldArcHeap, "Alink ShieldArcHeap"); + if (dComIfG_resLoad(&mShieldPhaseReq, mShieldArcName, mpShieldArcHeap) != cPhs_COMPLEATE_e) + { return cPhs_INIT_e; } 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_demo.inc b/src/d/actor/d_a_alink_demo.inc index 5b2d36dce1..13ec1f2136 100644 --- a/src/d/actor/d_a_alink_demo.inc +++ b/src/d/actor/d_a_alink_demo.inc @@ -23,6 +23,7 @@ #include "d/actor/d_a_npc_tkc.h" #include +#include "dusk/imgui/ImGuiConsole.hpp" #include "dusk/settings.h" BOOL daAlink_c::checkEventRun() const { @@ -4005,6 +4006,15 @@ int daAlink_c::procGanonFinishInit() { field_0x37c8 = current.pos; onEndResetFlg1(ERFLG1_SHIELD_BACKBONE); + +#if TARGET_PC + if (dusk::getSettings().game.speedrunMode) { + if (dusk::m_speedrunInfo.m_isRunStarted) { + dusk::m_speedrunInfo.stopRun(); + } + } +#endif + return 1; } 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_alink_horse.inc b/src/d/actor/d_a_alink_horse.inc index ac7ce73e50..f0c585f1b8 100644 --- a/src/d/actor/d_a_alink_horse.inc +++ b/src/d/actor/d_a_alink_horse.inc @@ -2721,7 +2721,7 @@ int daAlink_c::procHorseRun() { } if (mProcVar2.field_0x300c == 0) { - set3DStatus(BUTTON_STATUS_HOLD_ON, 4); + set3DStatus(BUTTON_STATUS_HOLD_ON, IF_DUSK(dusk::getSettings().game.enableMirrorMode ? 1 :) 4); } } else { if (mProcVar3.field_0x300e != 0) { @@ -2731,7 +2731,7 @@ int daAlink_c::procHorseRun() { } if (mProcVar2.field_0x300c == 0) { - set3DStatus(BUTTON_STATUS_HOLD_ON, 1); + set3DStatus(BUTTON_STATUS_HOLD_ON, IF_DUSK(dusk::getSettings().game.enableMirrorMode ? 4 :) 1); } } diff --git a/src/d/actor/d_a_alink_swindow.inc b/src/d/actor/d_a_alink_swindow.inc index 81c7a1ca18..4f651e16e2 100644 --- a/src/d/actor/d_a_alink_swindow.inc +++ b/src/d/actor/d_a_alink_swindow.inc @@ -46,7 +46,6 @@ void daAlink_c::setOriginalHeap(JKRExpHeap** i_ppheap, u32 i_size) { JKRHeap* parent = mDoExt_getGameHeap(); JKRExpHeap* heap = JKRExpHeap::create(size + (var_r29 + var_r28), parent, true); - JKRHEAP_NAME(heap, "Alink original"); *i_ppheap = heap; } } diff --git a/src/d/actor/d_a_balloon_2D.cpp b/src/d/actor/d_a_balloon_2D.cpp index 6b5b5a2bd2..8627505371 100644 --- a/src/d/actor/d_a_balloon_2D.cpp +++ b/src/d/actor/d_a_balloon_2D.cpp @@ -6,6 +6,7 @@ #include "d/dolzel_rel.h" // IWYU pragma: keep #include "d/actor/d_a_balloon_2D.h" +#include "dusk/frame_interpolation.h" #include "JSystem/J2DGraph/J2DGrafContext.h" #include "JSystem/J2DGraph/J2DScreen.h" #include "JSystem/J2DGraph/J2DTextBox.h" @@ -438,7 +439,12 @@ void daBalloon2D_c::setComboAlpha() { void daBalloon2D_c::drawAddScore() { for (s32 i = 19; i >= 0; i--) { if (field_0x5f8[i].field_0xe != 0) { - field_0x5f8[i].field_0xe--; +#ifdef TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + field_0x5f8[i].field_0xe--; + } s32 score3; s32 score2; s32 score = field_0x5f8[i].field_0xc; @@ -446,8 +452,13 @@ void daBalloon2D_c::drawAddScore() { u8 local_88 = 0xff; f32 dVar11 = 30.0f; f32 dVar9 = 30.0f; - field_0x5f8[i].field_0x0.x += cM_ssin(temp0) * 0.3f; - field_0x5f8[i].field_0x0.y -= 1.0f; +#ifdef TARGET_PC + if (dusk::frame_interp::get_ui_tick_pending()) +#endif + { + field_0x5f8[i].field_0x0.x += cM_ssin(temp0) * 0.3f; + field_0x5f8[i].field_0x0.y -= 1.0f; + } if (field_0x5f8[i].field_0xe < 10) { f32 fVar5 = field_0x5f8[i].field_0xe / 10.0f; local_88 = fVar5 * 255.0f; diff --git a/src/d/actor/d_a_door_shutter.cpp b/src/d/actor/d_a_door_shutter.cpp index a481762132..ebd1c9a185 100644 --- a/src/d/actor/d_a_door_shutter.cpp +++ b/src/d/actor/d_a_door_shutter.cpp @@ -17,6 +17,11 @@ #include #include +#if TARGET_PC +#include +#include +#endif + char* daDoor20_c::getStopBmdName() { switch (door_param2_c::getKind(this)) { case 3: @@ -196,6 +201,7 @@ void daDoor20_c::setEventPrm() { } else { roomNo = FRoomNo; } + if (dComIfGp_roomControl_checkStatusFlag(roomNo, 1)) { if (door_param2_c::getKind(this) == 9) { if (daPy_py_c::checkNowWolf()) { @@ -564,6 +570,11 @@ int daDoor20_c::openEnd(int param_1) { openEnd_1(); break; } + + #if TARGET_PC + triggerAutoSave(); + #endif + return 1; } diff --git a/src/d/actor/d_a_e_oct_bg.cpp b/src/d/actor/d_a_e_oct_bg.cpp index 14a421a3b6..94fa69596b 100644 --- a/src/d/actor/d_a_e_oct_bg.cpp +++ b/src/d/actor/d_a_e_oct_bg.cpp @@ -517,6 +517,12 @@ void daE_OctBg_c::core_fish_attack() { field_0xbaf = cM_rndFX(80.0f) + 100.0f; } } + #if AVOID_UB + else { + in_f31 = cM_rndF(400.0f) + 80.0f; + field_0xbaf = cM_rndFX(80.0f) + 100.0f; + } + #endif } else if (current.pos.abs(cStack_5c) < 400.0f) { in_f31 = cM_rndF(50.0f) + 20.0f; field_0xbaf = cM_rndFX(20.0f) + 40.0f; 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/d/actor/d_a_kago.cpp b/src/d/actor/d_a_kago.cpp index 147a031f7e..9444237f12 100644 --- a/src/d/actor/d_a_kago.cpp +++ b/src/d/actor/d_a_kago.cpp @@ -3519,7 +3519,15 @@ void daKago_c::action() { checkSizeBg(); setFlyEffect(); +#if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + mStickX = -mDoCPd_c::getStickX3D(PAD_1); + } else { + mStickX = mDoCPd_c::getStickX3D(PAD_1); + } +#else mStickX = mDoCPd_c::getStickX3D(PAD_1); +#endif mStickY = mDoCPd_c::getStickY(PAD_1); u8 prevIsWaterfall = mIsWaterfall; diff --git a/src/d/actor/d_a_midna.cpp b/src/d/actor/d_a_midna.cpp index 7bb844932e..2356b693be 100644 --- a/src/d/actor/d_a_midna.cpp +++ b/src/d/actor/d_a_midna.cpp @@ -463,6 +463,23 @@ int daMidna_c::createHeap() { JKRReadIdxResource(mBckHeap[0].getBuffer(), mBckHeap[0].getBufferSize(), 0x1DC, dComIfGp_getAnmArchive()); J3DAnmTransform* md_anm = (J3DAnmTransform*)J3DAnmLoaderDataBase::load(mBckHeap[0].getBuffer()); modelData = (J3DModelData*)dComIfG_getObjectRes(l_arcName, 14); + +#if TARGET_PC + J3DTexture* tex = modelData->getTexture(); + JUTNameTab* nametable = modelData->getTextureName(); + if (tex != NULL && nametable != NULL) { + for (u16 i = 0; i < tex->getNum(); i++) { + const char* name = nametable->getName(i); + if (name != NULL && strcmp(name, "midona_eye") == 0) { + ResTIMG* timg = tex->getResTIMG(i); + timg->mipmapEnabled = false; + tex->loadGXTexObj(i); + break; + } + } + } +#endif + JUT_ASSERT(852, modelData != NULL); mpMorf = JKR_NEW mDoExt_McaMorfSO(modelData, &mMorfCB, NULL, md_anm, J3DFrameCtrl::EMode_LOOP, 1.0f, 0, -1, NULL, 0, 0x11000284); if (mpMorf == NULL || mpMorf->getModel() == NULL) { diff --git a/src/d/actor/d_a_mirror.cpp b/src/d/actor/d_a_mirror.cpp index 86ef4314f3..5c5218328e 100644 --- a/src/d/actor/d_a_mirror.cpp +++ b/src/d/actor/d_a_mirror.cpp @@ -40,6 +40,7 @@ dMirror_packet_c::dMirror_packet_c() { void dMirror_packet_c::reset() { #if TARGET_PC mbReset = true; + mbHadEntry = false; #else mModelCount = 0; #endif @@ -84,11 +85,21 @@ void dMirror_packet_c::calcMinMax() { } int dMirror_packet_c::entryModel(J3DModel* i_model) { +#if TARGET_PC + if (mbReset) { + mModelCount = 0; + mbReset = false; + } +#endif + if (mModelCount >= 0x40) { return 0; } mModels[mModelCount++] = i_model; +#if TARGET_PC + mbHadEntry = true; +#endif return 1; } @@ -592,13 +603,6 @@ int daMirror_c::execute() { return 1; } -#if TARGET_PC - if (mPacket.mbReset) { - mPacket.mModelCount = 0; - mPacket.mbReset = false; - } -#endif - daPy_py_c* player = daPy_getLinkPlayerActorClass(); JUT_ASSERT(0, player != NULL); @@ -624,6 +628,12 @@ int daMirror_c::draw() { mDoExt_modelUpdateDL(mpModel); } +#if TARGET_PC + if (mPacket.mbReset && !mPacket.mbHadEntry) { + mPacket.mModelCount = 0; + } + mPacket.mbHadEntry = true; +#endif dComIfGd_getOpaListBG()->entryImm(&mPacket, 0); return 1; } diff --git a/src/d/actor/d_a_npc_ne.cpp b/src/d/actor/d_a_npc_ne.cpp index 1324c56cf5..90e33adea5 100644 --- a/src/d/actor/d_a_npc_ne.cpp +++ b/src/d/actor/d_a_npc_ne.cpp @@ -956,7 +956,7 @@ static void npc_ne_tame(npc_ne_class* i_this) { i_this->mpMorf->setPlaySpeed(i_this->mAnmSpeed); /* dSv_event_flag_c::F_0470 - Fishing Pond - Reserved for fishing */ - if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[470])) { + if (IF_DUSK(dusk::getSettings().game.no2ndFishForCat) || dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[470])) { if (fpcEx_Search(s_fish_sub, _this) != NULL) { i_this->mAction = npc_ne_class::ACT_HOME; i_this->mMode = 10; @@ -2948,8 +2948,7 @@ static int daNpc_Ne_Execute(npc_ne_class* i_this) { if (i_this->mWantsFish && (i_this->mCounter & 0xf) == 0) { /* dSv_event_flag_c::F_0470 - Fishing Pond - Reserved for fishing */ - if (dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[470]) - && i_this->mDistToTarget < 1500.0f) { + if ((IF_DUSK(dusk::getSettings().game.no2ndFishForCat) || dComIfGs_isEventBit(dSv_event_flag_c::saveBitLabels[470])) && i_this->mDistToTarget < 1500.0f) { if (fopAcM_SearchByName(fpcNm_MG_ROD_e) != NULL) { i_this->mNoFollow = false; } else { diff --git a/src/d/actor/d_a_npc_toby.cpp b/src/d/actor/d_a_npc_toby.cpp index 543d4b9abf..d175d199b7 100644 --- a/src/d/actor/d_a_npc_toby.cpp +++ b/src/d/actor/d_a_npc_toby.cpp @@ -14,6 +14,7 @@ #include "d/actor/d_a_obj_automata.h" #include "d/d_msg_object.h" #include "d/actor/d_a_obj_scannon.h" +#include "dusk/frame_interpolation.h" #include const daNpc_Toby_HIOParam daNpc_Toby_Param_c::m = { @@ -1398,6 +1399,7 @@ int daNpc_Toby_c::cutRepairSCannon(int arg0) { old.pos = current.pos; setAngle(cM_deg2s(5.0f * f32(mPath.getArg0()))); mEventTimer = mPath.getArg2(); + dusk::frame_interp::request_presentation_sync(); } } else if (!mHide) { mHide = 1; diff --git a/src/d/actor/d_a_obj_balloon.cpp b/src/d/actor/d_a_obj_balloon.cpp index 41430f97f0..d857a569ba 100644 --- a/src/d/actor/d_a_obj_balloon.cpp +++ b/src/d/actor/d_a_obj_balloon.cpp @@ -62,6 +62,16 @@ void daObj_Balloon_c::saveBestScore() { dComIfGp_setMessageCountNumber(m_balloon_score); } +#if TARGET_PC +static void minigameReset() { + // !@bug d_a_obj_balloon.rel unload used to zero these file-statics; with static linking they dangle across scenes. + m_combo_type = 0xFFFFFFFF; + m_combo_count = 0; + m_combo_next_score = 0; + m_balloon_score = 0; +} +#endif + static u8 hio_set; static daObj_Balloon_HIO_c l_HIO; @@ -246,6 +256,7 @@ int daObj_Balloon_c::create() { } if (!hio_set) { + IF_DUSK(minigameReset()); mHIOInit = true; hio_set = true; l_HIO.field_0x04 = -1; diff --git a/src/d/actor/d_a_obj_klift00.cpp b/src/d/actor/d_a_obj_klift00.cpp index 99b204592e..e44cce7467 100644 --- a/src/d/actor/d_a_obj_klift00.cpp +++ b/src/d/actor/d_a_obj_klift00.cpp @@ -11,6 +11,8 @@ #include "d/d_bg_w.h" #include "d/d_cc_uty.h" #include "d/d_com_inf_game.h" +#include "dusk/frame_interpolation.h" +#include "dusk/settings.h" struct daObjKLift00_HIO_c : public mDoHIO_entry_c { daObjKLift00_HIO_c(); @@ -295,6 +297,11 @@ int daObjKLift00_c::Create() { if(getLock()) mStopSwingingFrames = 5; +#if TARGET_PC + mChainInterpPrevValid = false; + mChainInterpCurrValid = false; +#endif + return 1; } @@ -436,6 +443,34 @@ int daObjKLift00_c::Execute(Mtx** i_mtx) { return 1; } +#if TARGET_PC +static void klift00_interp_callback(bool isSimFrame, void* pUserWork) { + static_cast(pUserWork)->onInterpCallback(); +} + +void daObjKLift00_c::onInterpCallback() { + if (!mChainInterpPrevValid || !mChainInterpCurrValid) { + return; + } + + const f32 alpha = dusk::frame_interp::get_interpolation_step(); + cXyz savedPositions[64]; + + for (int i = 0; i < mNumChains; i++) { + savedPositions[i] = mChainPositions[i].mCurrentPos; + const cXyz& p0 = mChainInterpPrev[i]; + const cXyz& p1 = mChainInterpCurr[i]; + mChainPositions[i].mCurrentPos = p0 + (p1 - p0) * alpha; + } + + setMtx(); + + for (int i = 0; i < mNumChains; i++) { + mChainPositions[i].mCurrentPos = savedPositions[i]; + } +} +#endif + int daObjKLift00_c::Draw() { g_env_light.settingTevStruct(16, ¤t.pos, &tevStr); g_env_light.setLightTevColorType_MAJI(mpLiftPlatform, &tevStr); @@ -457,6 +492,22 @@ int daObjKLift00_c::Draw() { dComIfGd_setList(); +#if TARGET_PC + if (dusk::getSettings().game.enableFrameInterpolation) { + if (mChainInterpCurrValid) { + memcpy(mChainInterpPrev, mChainInterpCurr, mNumChains * sizeof(cXyz)); + mChainInterpPrevValid = true; + } + + for (int i = 0; i < mNumChains; i++) { + mChainInterpCurr[i] = mChainPositions[i].mCurrentPos; + } + + mChainInterpCurrValid = true; + dusk::frame_interp::add_interpolation_callback(&klift00_interp_callback, this); + } +#endif + return 1; } diff --git a/src/d/actor/d_a_obj_lv8Lift.cpp b/src/d/actor/d_a_obj_lv8Lift.cpp index 4f475a626c..8fa9422325 100644 --- a/src/d/actor/d_a_obj_lv8Lift.cpp +++ b/src/d/actor/d_a_obj_lv8Lift.cpp @@ -10,6 +10,10 @@ #include "d/d_path.h" #include "d/d_bg_w.h" +#if TARGET_PC +#include "dusk/frame_interpolation.h" +#endif + daL8Lift_HIO_c::daL8Lift_HIO_c() { mStopDisappearTime = 30; mStartMoveTime = 60; @@ -380,7 +384,44 @@ void daL8Lift_c::setNextPoint() { mCurrentPoint = next_point; } +#if TARGET_PC +void daL8Lift_interp_callback(bool isSimFrame, void* pUserWork) { + daL8Lift_c* lift = static_cast(pUserWork); + if (lift == NULL || lift->mpModel == NULL) { + return; + } + + g_env_light.settingTevStruct(0x10, &lift->current.pos, &lift->tevStr); + g_env_light.setLightTevColorType_MAJI(lift->mpModel, &lift->tevStr); + + J3DModelData* modelData = lift->mpModel->getModelData(); + J3DMaterial* materialp = modelData->getMaterialNodePointer(0); + + if (materialp->getTexGenBlock()->getTexMtx(1) != NULL) { + J3DTexMtxInfo* mtx_info = &materialp->getTexGenBlock()->getTexMtx(1)->getTexMtxInfo(); + if (mtx_info != NULL) { + Mtx m; + C_MTXLightOrtho(m, 100.0f, -100.0f, -100.0f, 100.0f, 1.0f, 1.0f, 0.0f, 0.0f); + mDoMtx_stack_c::XrotS(0x4000); + mDoMtx_stack_c::transM(-lift->current.pos.x, -lift->current.pos.y, -lift->current.pos.z); + cMtx_concat(m, mDoMtx_stack_c::get(), mtx_info->mEffectMtx); + } + } + + lift->mBtk.entry(modelData); + + J3DGXColor* color = materialp->getTevKColor(1); + color->r = l_HIO.mColorR; + color->g = l_HIO.mColorG; + color->b = l_HIO.mColorB; +} +#endif + int daL8Lift_c::Draw() { +#if TARGET_PC + dusk::frame_interp::add_interpolation_callback(&daL8Lift_interp_callback, this); +#endif + g_env_light.settingTevStruct(16, ¤t.pos, &tevStr); g_env_light.setLightTevColorType_MAJI(mpModel, &tevStr); J3DModelData* modelData = mpModel->getModelData(); diff --git a/src/d/actor/d_flower.inc b/src/d/actor/d_flower.inc index 5f54ea4bda..4024d5ad6a 100644 --- a/src/d/actor/d_flower.inc +++ b/src/d/actor/d_flower.inc @@ -7,13 +7,8 @@ #include "dusk/frame_interpolation.h" -#if TARGET_PC -const u16 l_J_Ohana00_64TEX__width = 64; -const u16 l_J_Ohana00_64TEX__height = 64; -#else const u16 l_J_Ohana00_64TEX__width = 63; const u16 l_J_Ohana00_64TEX__height = 63; -#endif #if TARGET_PC #include "dusk/dvd_asset.hpp" @@ -136,13 +131,8 @@ l_matDL__d_a_grass(l_J_Ohana00_64TEX) l_matLight4DL(l_J_Ohana00_64TEX) #endif -#if TARGET_PC -const u16 l_J_Ohana01_64128_0419TEX__width = 64; -const u16 l_J_Ohana01_64128_0419TEX__height = 128; -#else const u16 l_J_Ohana01_64128_0419TEX__width = 63; const u16 l_J_Ohana01_64128_0419TEX__height = 127; -#endif #if TARGET_PC using GameVersion = dusk::version::GameVersion; @@ -592,11 +582,11 @@ dFlower_packet_c::dFlower_packet_c() { #if TARGET_PC GXInitTexObj(&mTexObj_l_J_Ohana00_64TEX, l_J_Ohana00_64TEX, - l_J_Ohana00_64TEX__width, l_J_Ohana00_64TEX__height, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE + l_J_Ohana00_64TEX__width + 1, l_J_Ohana00_64TEX__height + 1, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE ); GXInitTexObj(&mTexObj_l_J_Ohana01_64128_0419TEX, l_J_Ohana01_64128_0419TEX, - l_J_Ohana01_64128_0419TEX__width, l_J_Ohana01_64128_0419TEX__height, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE + l_J_Ohana01_64128_0419TEX__width + 1, l_J_Ohana01_64128_0419TEX__height + 1, GX_TF_CMPR, GX_MIRROR, GX_MIRROR, GX_FALSE ); #endif diff --git a/src/d/actor/d_grass.inc b/src/d/actor/d_grass.inc index 9bc8815753..3d67a57007 100644 --- a/src/d/actor/d_grass.inc +++ b/src/d/actor/d_grass.inc @@ -494,11 +494,11 @@ dGrass_packet_c::dGrass_packet_c() { #if TARGET_PC GXInitTexObj(&mTexObj_l_M_kusa05_RGBATEX, l_M_kusa05_RGBATEX, - l_M_kusa05_RGBATEX__width, l_M_kusa05_RGBATEX__height, GX_TF_RGB5A3, GX_REPEAT, GX_CLAMP, GX_FALSE + l_M_kusa05_RGBATEX__width + 1, l_M_kusa05_RGBATEX__height + 1, GX_TF_RGB5A3, GX_REPEAT, GX_CLAMP, GX_FALSE ); GXInitTexObj(&mTexObj_l_M_Hijiki00TEX, l_M_Hijiki00TEX, - l_M_Hijiki00TEX__width, l_M_Hijiki00TEX__height, GX_TF_RGB5A3, GX_REPEAT, GX_CLAMP, GX_FALSE + l_M_Hijiki00TEX__width + 1, l_M_Hijiki00TEX__height + 1, GX_TF_RGB5A3, GX_REPEAT, GX_CLAMP, GX_FALSE ); #endif @@ -646,18 +646,14 @@ void dGrass_packet_c::draw() { } if (var_r29->field_0x05 <= 3 || var_r29->field_0x05 >= 10) { -#if TARGET_PC - GXLoadTexObj(&mTexObj_l_M_kusa05_RGBATEX, GX_TEXMAP0); -#endif + IF_DUSK(GXLoadTexObj(&mTexObj_l_M_kusa05_RGBATEX, GX_TEXMAP0)); if (sp48 <= 3) { GXCallDisplayList(mp_kusa9q_14_DL, m_kusa9q_DL_14_size); } else { GXCallDisplayList(mp_kusa9q_DL, m_kusa9q_DL_size); } } else { -#if TARGET_PC - GXLoadTexObj(&mTexObj_l_M_Hijiki00TEX, GX_TEXMAP0); -#endif + IF_DUSK(GXLoadTexObj(&mTexObj_l_M_Hijiki00TEX, GX_TEXMAP0)); GXCallDisplayList(l_Tengusa_matDL, 0xA0); } @@ -683,12 +679,14 @@ void dGrass_packet_c::draw() { while (var_r29 != NULL) { if (var_r29->field_0x05 <= 3 || var_r29->field_0x05 >= 10) { + IF_DUSK(GXLoadTexObj(&mTexObj_l_M_kusa05_RGBATEX, GX_TEXMAP0)); if (sp48 <= 2) { GXCallDisplayList(mp_kusa9q_14_DL, m_kusa9q_DL_14_size); } else { GXCallDisplayList(mp_kusa9q_DL, m_kusa9q_DL_size); } } else { + IF_DUSK(GXLoadTexObj(&mTexObj_l_M_Hijiki00TEX, GX_TEXMAP0)); GXCallDisplayList(l_Tengusa_matDL, 0xA0); } diff --git a/src/d/d_bright_check.cpp b/src/d/d_bright_check.cpp index c3fcc806d5..3ee98be5c7 100644 --- a/src/d/d_bright_check.cpp +++ b/src/d/d_bright_check.cpp @@ -9,6 +9,8 @@ #include "JSystem/J2DGraph/J2DScreen.h" #include "JSystem/J2DGraph/J2DTextBox.h" #include "d/d_msg_string.h" +#include "dusk/livesplit.h" +#include "dusk/imgui/ImGuiConsole.hpp" #include "m_Do/m_Do_controller_pad.h" dBrightCheck_c::dBrightCheck_c(JKRArchive* i_archive) { @@ -138,6 +140,17 @@ void dBrightCheck_c::modeWait() {} void dBrightCheck_c::modeMove() { if (mDoCPd_c::getTrigA(PAD_1) || mDoCPd_c::getTrigStart(PAD_1)) { mDoAud_seStart(Z2SE_ENTER_GAME, NULL, 0, 0); +#ifdef TARGET_PC + dusk::speedrun::start(); + + if (dusk::getSettings().game.speedrunMode && !dusk::getSettings().game.hideTvSettingsScreen) { + // start a new run if a run isn't already in progress + if (!dusk::m_speedrunInfo.m_isRunStarted) { + dusk::ImGuiMenuGame::resetForSpeedrunMode(); + dusk::m_speedrunInfo.startRun(); + } + } +#endif mCompleteCheck = true; mMode = MODE_WAIT_e; } diff --git a/src/d/d_camera.cpp b/src/d/d_camera.cpp index 917b30709a..e559a56d95 100644 --- a/src/d/d_camera.cpp +++ b/src/d/d_camera.cpp @@ -794,16 +794,15 @@ void dCamera_c::updatePad() { if (mTriggerLeftLast > mCamSetup.ManualEndVal()) { if (mLockLActive == 0) { + #if TARGET_PC + mCamParam.mManualMode = 0; + #endif mLockLJustActivated = 1; } else { mLockLJustActivated = 0; } mLockLActive = 1; - - #if TARGET_PC - mCamParam.mManualMode = 0; - #endif } else { mLockLJustActivated = 0; mLockLActive = 0; @@ -1041,6 +1040,11 @@ void dCamera_c::debugDrawInit() { bool dCamera_c::Run() { #if TARGET_PC ResetView(); + if (executeDebugFlyCam()) { + mFrameCounter++; + mTicks++; + return true; + } #endif daAlink_c* link = daAlink_getAlinkActorClass(); @@ -1176,14 +1180,14 @@ bool dCamera_c::Run() { clrFlag(0x200000); } } else { - sp0F = (this->*engine_tbl[mCamParam.Algorythmn(mCamStyle)])(mCamStyle); - #if TARGET_PC if (mCamParam.Algorythmn(mCamStyle) != 1) { mCamParam.mManualMode = 0; } #endif + sp0F = (this->*engine_tbl[mCamParam.Algorythmn(mCamStyle)])(mCamStyle); + field_0x170++; field_0x160++; mCurCamStyleTimer++; @@ -1488,7 +1492,7 @@ void dCamera_c::CalcTrimSize() { mTrimHeight += -mTrimHeight * 0.25f; break; case 2: -#if WIDESCREEN_SUPPORT +#if !TARGET_PC && WIDESCREEN_SUPPORT if (mDoGph_gInf_c::isWide() && mDoGph_gInf_c::isWideZoom()) { mTrimHeight += (16.0f - mTrimHeight) * 0.25f; break; @@ -3096,10 +3100,6 @@ bool dCamera_c::bumpCheck(u32 i_flags) { field_0x968 *= mMonitor.field_0xc / 5.0f; } - #if TARGET_PC - if (!dusk::getSettings().game.freeCamera || !mCamParam.mManualMode) { - #endif - f32 tmp = field_0x96c * (mIsWolf == 1 ? 30.0f : 30.0f); center += vec3.norm() * (tmp * globe.V().Sin()); cSGlobe globe2(vec2 - center); @@ -3113,10 +3113,6 @@ bool dCamera_c::bumpCheck(u32 i_flags) { vec = lin_chk1.GetCross(); } - #if TARGET_PC - } - #endif - #if DEBUG if (mCamSetup.CheckFlag(0x8000)) { dDbVw_Report(20, 235, " U"); @@ -4208,6 +4204,11 @@ bool dCamera_c::chaseCamera(s32 param_0) { chase->field_0x8 -= chase->field_0xc; chase->field_0x8c = 0; chase->field_0x90 = false; + + #if TARGET_PC + freeCamera(); + #endif + return true; } @@ -4631,10 +4632,6 @@ bool dCamera_c::chaseCamera(s32 param_0) { sp110 = mViewCache.mDirection.R(); mViewCache.mDirection.R(mViewCache.mDirection.R() + (fVar55 - mViewCache.mDirection.R()) * chase->field_0x74); - #if TARGET_PC - freeCamera(); - #endif - chase->field_0x64 = mViewCache.mCenter + mViewCache.mDirection.Xyz(); mViewCache.mEye = chase->field_0x64; @@ -4649,6 +4646,11 @@ bool dCamera_c::chaseCamera(s32 param_0) { if (chase->field_0x1c != 0) { chase->field_0x1c--; } + + #if TARGET_PC + freeCamera(); + #endif + return true; } @@ -7096,10 +7098,12 @@ bool dCamera_c::subjectCamera(s32 param_0) { cXyz sp1E0(val0, val2, val1); #if TARGET_PC - f32 aspect = mDoGph_gInf_c::getAspect(); - f32 baseAspect = FB_WIDTH / FB_HEIGHT; - if (aspect > baseAspect) { - sp1E0.z += (aspect - baseAspect) * 4; + if (sp13) { + f32 aspect = mDoGph_gInf_c::getAspect(); + f32 baseAspect = FB_WIDTH / FB_HEIGHT; + if (aspect > baseAspect) { + sp1E0.z += (aspect - baseAspect) * 4; + } } #endif @@ -7475,38 +7479,147 @@ bool dCamera_c::test2Camera(s32 param_0) { return false; } +static constexpr f32 FLYCAM_SPEED = 0.5f; +static constexpr f32 FLYCAM_FAST_SPEED = 4.0f; +static constexpr f32 FLYCAM_ROTATION_SPEED = 0.002f; +static constexpr f32 FLYCAM_TRIGGER_DEADZONE = 20.0f; + #if TARGET_PC +bool dCamera_c::executeDebugFlyCam() { + if (!dusk::getSettings().game.debugFlyCam) { + if (mDebugFlyCam.initialized) { + deactivateDebugFlyCam(); + } + return false; + } + + dEvt_control_c* event = dComIfGp_getEvent(); + if (event == nullptr) { + return false; + } + + if (!mDebugFlyCam.initialized && (event->mEventStatus != 0 || dComIfGp_isPauseFlag())) { + dusk::getSettings().game.debugFlyCam.setValue(false); + return false; + } + + if (!mDebugFlyCam.initialized) { + mDebugFlyCam.savedCenter = mCenter; + mDebugFlyCam.savedEye = mEye; + mDebugFlyCam.savedFovy = mFovy; + mDebugFlyCam.savedBank = mBank; + + f32 dx = mCenter.x - mEye.x; + f32 dy = mCenter.y - mEye.y; + f32 dz = mCenter.z - mEye.z; + mDebugFlyCam.yaw = atan2f(dz, dx); + f32 horizontal = sqrtf(dx * dx + dz * dz); + mDebugFlyCam.pitch = atan2f(dy, horizontal); + + mDebugFlyCam.initialized = true; + } + + event->mEventStatus = 1; + dComIfGp_getEventManager().setCameraPlay(1); + + interface_of_controller_pad& pad = mDoCPd_c::getCpadInfo(0); + f32 stickY = pad.mMainStickPosY * 72.0f; + f32 stickX = pad.mMainStickPosX * 72.0f; + f32 cStickY = pad.mCStickPosY * 59.0f; + f32 cStickX = pad.mCStickPosX * 59.0f; + f32 trigL = pad.mTriggerLeft * 150.0f; + f32 trigR = pad.mTriggerRight * 150.0f; + + f32 verticalDisp = 0.0f; + if (trigR >= FLYCAM_TRIGGER_DEADZONE) { + verticalDisp += trigR; + } + if (trigL >= FLYCAM_TRIGGER_DEADZONE) { + verticalDisp -= trigL; + } + + f32 moveDy = stickY * sinf(mDebugFlyCam.pitch) + verticalDisp; + f32 moveDx = stickY * cosf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) - stickX * sinf(mDebugFlyCam.yaw); + f32 moveDz = stickY * sinf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) + stickX * cosf(mDebugFlyCam.yaw); + + f32 speed = mDoCPd_c::getHoldZ(PAD_1) ? FLYCAM_FAST_SPEED : FLYCAM_SPEED; + + mEye.x += speed * moveDx; + mEye.y += speed * moveDy; + mEye.z += speed * moveDz; + + static constexpr f32 FLYCAM_TARGET_DIST = 100.0f; + mCenter.x = mEye.x + cosf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) * FLYCAM_TARGET_DIST; + mCenter.z = mEye.z + sinf(mDebugFlyCam.yaw) * cosf(mDebugFlyCam.pitch) * FLYCAM_TARGET_DIST; + mCenter.y = mEye.y + sinf(mDebugFlyCam.pitch) * FLYCAM_TARGET_DIST; + + Reset(mCenter, mEye); + + f32 yawInput = dusk::getSettings().game.invertCameraXAxis ? cStickX : -cStickX; + mDebugFlyCam.yaw += yawInput * FLYCAM_ROTATION_SPEED; + mDebugFlyCam.yaw = fmodf(mDebugFlyCam.yaw + 2.0f * (f32)M_PI, 2.0f * (f32)M_PI); + + f32 maxPitch = (f32)M_PI / 2.0f - 0.1f; + f32 minPitch = -(f32)M_PI / 2.0f + 0.1f; + mDebugFlyCam.pitch = std::clamp(mDebugFlyCam.pitch + cStickY * FLYCAM_ROTATION_SPEED, minPitch, maxPitch); + + return true; +} + +void dCamera_c::deactivateDebugFlyCam() { + Reset(mDebugFlyCam.savedCenter, mDebugFlyCam.savedEye, mDebugFlyCam.savedFovy, mDebugFlyCam.savedBank.Val()); + + dEvt_control_c* event = dComIfGp_getEvent(); + if (event != nullptr) { + event->mEventStatus = 0; + } + dComIfGp_getEventManager().setCameraPlay(0); + mDebugFlyCam.initialized = false; +} + bool dCamera_c::freeCamera() { - if (!dusk::getSettings().game.freeCamera) { + if (dusk::getSettings().game.freeCamera && mGear == 1) { + mGear = 0; + } + + if (!dusk::getSettings().game.freeCamera || mCamStyle == 70) + { mCamParam.mManualMode = 0; return false; } + if (!mCamParam.mManualMode) { + mCamParam.freeXAngle = mViewCache.mDirection.mAzimuth.Degree(); + mCamParam.freeYAngle = mViewCache.mDirection.mInclination.Degree(); + } + cXyz camMovement = {mPadInfo.mCStick.mLastPosX, mPadInfo.mCStick.mLastPosY, 0.0f}; f32 magnitude = sqrt(mPadInfo.mCStick.mLastPosX * mPadInfo.mCStick.mLastPosX + mPadInfo.mCStick.mLastPosY * mPadInfo.mCStick.mLastPosY); if (mPadInfo.mCStick.mLastPosX != 0 || mPadInfo.mCStick.mLastPosY != 0) { - if (!mCamParam.mManualMode) { - mCamParam.mManualMode = 1; - mCamParam.freeXAngle = mViewCache.mDirection.mAzimuth.Degree(); - mCamParam.freeYAngle = mViewCache.mDirection.mInclination.Degree(); - } - + mCamParam.mManualMode = 1; camMovement = camMovement.normalize(); - camMovement.x *= (dusk::getSettings().game.invertCameraXAxis ? 1.0f : -1.0f) * dusk::getSettings().game.freeCameraSensitivity * 4.0f; - camMovement.y *= (dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f) * dusk::getSettings().game.freeCameraSensitivity * 4.0f; - mCamParam.freeXAngle += camMovement.x * magnitude * dusk::getSettings().game.freeCameraSensitivity; - mCamParam.freeYAngle += camMovement.y * magnitude * dusk::getSettings().game.freeCameraSensitivity; + camMovement.y *= dusk::getSettings().game.invertCameraYAxis ? 1.0f : -1.0f; + mCamParam.freeXAngle += camMovement.x * magnitude * dusk::getSettings().game.freeCameraSensitivity * 5.0f; + mCamParam.freeYAngle += camMovement.y * magnitude * dusk::getSettings().game.freeCameraSensitivity * 5.0f; } - if (mCamParam.mManualMode) { - mCamParam.freeYAngle = std::clamp(mCamParam.freeYAngle, -35.0f, 60.0f); - mViewCache.mDirection.mAzimuth = cSAngle(mCamParam.freeXAngle); - mViewCache.mDirection.mInclination = cSAngle(mCamParam.freeYAngle); - mViewCache.mDirection.mRadius = std::clamp(mCamParam.freeYAngle * 15.0f, 300.0f, 10000.0f); + fopAc_ac_c* player = dComIfGp_getPlayer(0); + if (!mCamParam.mManualMode || player == nullptr) { + return false; } - return mCamParam.mManualMode; + f32 minYAngle = -30.0f; + f32 maxAngle = 50.0f; + + mCamParam.freeYAngle = std::clamp(mCamParam.freeYAngle, minYAngle, maxAngle); + mViewCache.mDirection.mAzimuth = cSAngle(mCamParam.freeXAngle); + mViewCache.mDirection.mInclination = cSAngle(mCamParam.freeYAngle); + + cXyz finalEye = mViewCache.mCenter + mViewCache.mDirection.Xyz(); + mViewCache.mEye = finalEye; + + return true; } #endif @@ -11149,12 +11262,25 @@ static int camera_draw(camera_process_class* i_this) { } #endif - int trim_height = body->TrimHeight(); - #if TARGET_PC + auto trim_height = body->TrimHeight(); + + if (mDoGph_gInf_c::isWideZoom()) { + const auto target_ar = FB_WIDTH / (FB_HEIGHT - trim_height * 2.0f); + const auto current_ar = mDoGph_gInf_c::m_safeWidthF / mDoGph_gInf_c::m_safeHeightF; + + if (current_ar < target_ar) { + trim_height = FB_HEIGHT / 2.0f * (1.0f - current_ar / target_ar); + } else { + trim_height = 0.0f; + } + } + trim_height *= viewport->height / FB_HEIGHT; window->setScissor(0.0f, trim_height, viewport->width, viewport->height - trim_height * 2.0f); #else + int trim_height = body->TrimHeight(); + window->setScissor(0.0f, trim_height, FB_WIDTH, FB_HEIGHT - trim_height * 2.0f); #endif diff --git a/src/d/d_drawlist.cpp b/src/d/d_drawlist.cpp index 373791f994..1f16b2f655 100644 --- a/src/d/d_drawlist.cpp +++ b/src/d/d_drawlist.cpp @@ -22,6 +22,10 @@ #include "dusk/frame_interpolation.h" #include "dusk/gx_helper.h" #include "dusk/logging.h" + +static const void* getInterpKey(const void* base, int idx) { + return reinterpret_cast(reinterpret_cast(base) ^ idx); +} #endif class dDlst_2Dm_c { @@ -1062,7 +1066,15 @@ void dDlst_shadowReal_c::reset() { } void dDlst_shadowReal_c::imageDraw(Mtx param_0) { - GXSetProjection(mRenderProjMtx, GX_ORTHOGRAPHIC); +#ifdef TARGET_PC + Mtx render_proj_mtx; + if (dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 2), render_proj_mtx)) { + GXSetProjection(render_proj_mtx, GX_ORTHOGRAPHIC); + } else +#endif + { + GXSetProjection(mRenderProjMtx, GX_ORTHOGRAPHIC); + } JUT_ASSERT(1916, mModelNum); J3DModelData* model_data; J3DModel** models = mpModels; @@ -1075,7 +1087,15 @@ void dDlst_shadowReal_c::imageDraw(Mtx param_0) { for (u16 j = 0; j < model_data->getShapeNum(); j++) { if (!model_data->getShapeNodePointer(j)->checkFlag(1)) { shape_pkt = (*models)->getShapePacket(j); - shape_pkt->setBaseMtxPtr(&mViewMtx); +#ifdef TARGET_PC + Mtx view_mtx; + if (dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 1), view_mtx)) { + shape_pkt->setBaseMtxPtr(&view_mtx); + } else +#endif + { + shape_pkt->setBaseMtxPtr(&mViewMtx); + } shape_pkt->drawFast(); shape_pkt->setBaseMtxPtr((Mtx*)param_0); } @@ -1096,7 +1116,18 @@ void dDlst_shadowReal_c::draw() { GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); GXSetCurrentMtx(GX_PNMTX0); - GXLoadTexMtxImm(mReceiverProjMtx, GX_TEXMTX0, GX_MTX3x4); +#ifdef TARGET_PC + Mtx view_mtx, recv_proj_mtx; + const auto have_view_mtx = dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 1), view_mtx); + const auto have_recv_proj_mtx = dusk::frame_interp::lookup_replacement(getInterpKey(mpModels[0], 3), recv_proj_mtx); + if (have_view_mtx && have_recv_proj_mtx) { + cMtx_concat(recv_proj_mtx, view_mtx, recv_proj_mtx); + GXLoadTexMtxImm(recv_proj_mtx, GX_TEXMTX0, GX_MTX3x4); + } else +#endif + { + GXLoadTexMtxImm(mReceiverProjMtx, GX_TEXMTX0, GX_MTX3x4); + } mShadowRealPoly.draw(); } @@ -1253,6 +1284,13 @@ u8 dDlst_shadowReal_c::setShadowRealMtx(cXyz* param_0, cXyz* param_1, f32 param_ cMtx_lookAt(mViewMtx, &local_64, param_1, 0); C_MTXOrtho(mRenderProjMtx, param_2, -param_2, -param_2, param_2, 1.0f, 10000.0f); C_MTXLightOrtho(mReceiverProjMtx, param_2, -param_2, -param_2, param_2, 0.5f, -0.5f, 0.5f, 0.5f); + +#ifdef TARGET_PC + const auto keybase = mpModels[0]; + dusk::frame_interp::record_final_mtx(mViewMtx, getInterpKey(keybase, 1)); + dusk::frame_interp::record_final_mtx(mRenderProjMtx, getInterpKey(keybase, 2)); + dusk::frame_interp::record_final_mtx(mReceiverProjMtx, getInterpKey(keybase, 3)); +#endif cMtx_concat(mReceiverProjMtx, mViewMtx, mReceiverProjMtx); return r29; } @@ -1277,6 +1315,10 @@ u32 dDlst_shadowReal_c::set(u32 i_key, J3DModel* i_model, cXyz* param_2, f32 par } } +#ifdef TARGET_PC + // provide a stable key for interpolation + mpModels[0] = i_model; +#endif field_0x1 = setShadowRealMtx(&sp60, param_2, param_3, param_4, param_7, param_5); if (!field_0x1) { @@ -1370,12 +1412,6 @@ void dDlst_shadowSimple_c::draw() { GXCallDisplayList(l_shadowVolumeDL, 0x40); } -#if TARGET_PC -static const void* getInterpKey(const void* base, int idx) { - return reinterpret_cast(reinterpret_cast(base) ^ idx); -} -#endif - void dDlst_shadowSimple_c::set(cXyz* param_0, f32 param_1, f32 param_2, cXyz* param_3, s16 param_4, f32 param_5, TGXTexObj* param_6) { if (param_5 < 0.0f) { diff --git a/src/d/d_kankyo_rain.cpp b/src/d/d_kankyo_rain.cpp index 822612768c..09e230c9fd 100644 --- a/src/d/d_kankyo_rain.cpp +++ b/src/d/d_kankyo_rain.cpp @@ -5962,6 +5962,8 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) { fopAc_ac_c* player = dComIfGp_getPlayer(0); if (evil_packet != NULL) { + IF_DUSK(GXPushDebugGroup("dKyr_evil_draw2")); + j3dSys.reinitGX(); if (dComIfGd_getView() != NULL) { MTXInverse(dComIfGd_getView()->viewMtxNoTrans, camMtx); @@ -6162,6 +6164,8 @@ static void dKyr_evil_draw2(Mtx drawMtx, u8** tex) { } } + IF_DUSK(GXPopDebugGroup()); + GXSetClipMode(GX_CLIP_ENABLE); J3DShape::resetVcdVatCache(); } @@ -6199,6 +6203,8 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { f32 sp60 = fabsf(cM_ssin(g_Counter.mCounter0 * 215)); if (evil_packet != NULL) { + IF_DUSK(GXPushDebugGroup("dKyr_evil_draw")); + j3dSys.reinitGX(); if (dComIfGd_getView() != NULL) { MTXInverse(dComIfGd_getView()->viewMtxNoTrans, camMtx); @@ -6231,8 +6237,8 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { GXLoadPosMtxImm(drawMtx, GX_PNMTX0); GXSetCurrentMtx(GX_PNMTX0); - GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGBA, GX_F32, 0); - GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_CLR_RGBA, GX_RGBA4, 8); + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_RGBA4, 8); GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT); @@ -6255,6 +6261,19 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { GXSetClipMode(GX_CLIP_DISABLE); GXSetNumIndStages(0); +#if TARGET_PC + // move color_reg0 to vtx for perf + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0); + GXSetVtxDesc(GX_VA_CLR0, GX_DIRECT); + GXSetNumChans(1); + GXSetChanCtrl(GX_COLOR0A0, GX_FALSE, GX_SRC_REG, GX_SRC_VTX, 0, GX_DF_NONE, GX_AF_NONE); + GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0); + GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_C1, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO); + GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_TEXA, GX_CA_RASA, GX_CA_ZERO); + GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); +#endif + dComIfG_Ccsp()->PrepareMass(); for (int i = 0; i < g_env_light.field_0x1054; i++) { @@ -6373,7 +6392,7 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { color_reg0.b = (115.0f * sp28) + (15.0f * fabsf(sp2C - sp64)); } - GXSetTevColor(GX_TEVREG0, color_reg0); + IF_NOT_DUSK(GXSetTevColor(GX_TEVREG0, color_reg0)); GXSetTevColor(GX_TEVREG1, color_reg1); spC8 = spA4; @@ -6412,12 +6431,16 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { GXBegin(GX_QUADS, GX_VTXFMT0, 4); GXPosition3f32(pos[0].x, pos[0].y, pos[0].z); + IF_DUSK(GXColor4u8(color_reg0.r, color_reg0.g, color_reg0.b, color_reg0.a)); GXTexCoord2s16(0, 0); GXPosition3f32(pos[1].x, pos[1].y, pos[1].z); + IF_DUSK(GXColor4u8(color_reg0.r, color_reg0.g, color_reg0.b, color_reg0.a)); GXTexCoord2s16(0xFF, 0); GXPosition3f32(pos[2].x, pos[2].y, pos[2].z); + IF_DUSK(GXColor4u8(color_reg0.r, color_reg0.g, color_reg0.b, color_reg0.a)); GXTexCoord2s16(0xFF, 0xFF); GXPosition3f32(pos[3].x, pos[3].y, pos[3].z); + IF_DUSK(GXColor4u8(color_reg0.r, color_reg0.g, color_reg0.b, color_reg0.a)); GXTexCoord2s16(0, 0xFF); GXEnd(); } @@ -6425,6 +6448,8 @@ void dKyr_evil_draw(Mtx drawMtx, u8** tex) { } } + IF_DUSK(GXPopDebugGroup()); + J3DShape::resetVcdVatCache(); GXSetClipMode(GX_CLIP_ENABLE); diff --git a/src/d/d_map.cpp b/src/d/d_map.cpp index 7dc5187a88..d4e5a98888 100644 --- a/src/d/d_map.cpp +++ b/src/d/d_map.cpp @@ -13,6 +13,9 @@ #include "SSystem/SComponent/c_math.h" #include "d/actor/d_a_player.h" #include "d/d_com_inf_game.h" +#if TARGET_PC +#include +#endif #include #if DEBUG @@ -539,17 +542,14 @@ void renderingAmap_c::rendering(dDrawPath_c::poly_class const* i_poly) { } } -/* Enabling the following definition will modify the following function to - * make the map look worse for extra speed in the emulator, especially in large - * areas such as hyrule field. - */ -#define HYRULE_FIELD_SPEEDHACK bool renderingAmap_c::isDrawOutSideTrim() { bool rt = false; - #ifdef HYRULE_FIELD_SPEEDHACK - return 0; + #if TARGET_PC + if (!dusk::getSettings().game.enableMapBackground) { + return 0; + } #endif if (getDispType() == 0 || getDispType() == 4 || getDispType() == 3 || getDispType() == 2 || @@ -1218,6 +1218,9 @@ void dMap_c::changeTextureSize(int param_1, int param_2, int param_3) { void dMap_c::_remove() { if (mImage_p != NULL) { +#if TARGET_PC + GXDestroyCopyTex(mImage_p); +#endif JKR_DELETE_ARRAY(mImage_p); mImage_p = NULL; } diff --git a/src/d/d_map_path.cpp b/src/d/d_map_path.cpp index 8288a2ef9f..eca620ef98 100644 --- a/src/d/d_map_path.cpp +++ b/src/d/d_map_path.cpp @@ -16,6 +16,7 @@ #ifdef TARGET_PC constexpr u16 kMapResolutionMultiplier = 4; +constexpr u16 kMapCircleSize = 16 * kMapResolutionMultiplier; #endif void dMpath_n::dTexObjAggregate_c::create() { @@ -32,6 +33,48 @@ void dMpath_n::dTexObjAggregate_c::create() { JUT_ASSERT(74, image->magFilter == GX_NEAR); mDoLib_setResTimgObj(image, mp_texObj[lp1], 0, NULL); } + +#if TARGET_PC + auto hqCircle = JKR_NEW TGXTexObj(); + + static bool hqCircleDrawn = false; + static u8 hqCircleData[kMapCircleSize * kMapCircleSize]; + + if (!hqCircleDrawn) { + const auto center = kMapCircleSize / 2.0f; + const auto radiusSq = center * center; + const auto blocksAcross = kMapCircleSize >> 3; + const auto totalPixels = sizeof(hqCircleData); + + for (size_t i = 0; i < totalPixels; i++) { + // 8x4 block swizzling for I8 + const auto blockIdx = i >> 5; + const auto localIdx = i & 31; + + const auto blockY = blockIdx / blocksAcross; + const auto blockX = blockIdx % blocksAcross; + + const auto localY = localIdx >> 3; + const auto localX = localIdx & 7; + + const auto x = (blockX << 3) + localX; + const auto y = (blockY << 2) + localY; + + const auto dx = (x + 0.5f) - center; + const auto dy = (y + 0.5f) - center; + + // the original texture is in I4 format and uses 1 to indicate if inside the circle + // so we scale to I8 range: 255 / 15 = 17 + hqCircleData[i] = (dx * dx + dy * dy < radiusSq) ? 17 : 0; + } + hqCircleDrawn = true; + } + + GXInitTexObj(hqCircle, hqCircleData, kMapCircleSize, kMapCircleSize, GX_TF_I8, GX_CLAMP, + GX_CLAMP, GX_FALSE); + GXInitTexObjLOD(hqCircle, GX_NEAR, GX_NEAR, 0.0f, 0.0f, 0.0f, GX_FALSE, GX_FALSE, GX_ANISO_1); + mp_texObj[6] = hqCircle; +#endif } void dMpath_n::dTexObjAggregate_c::remove() { @@ -497,12 +540,6 @@ void dRenderingFDAmap_c::postRenderingMap() { dMpath_n::dTexObjAggregate_c dMpath_n::m_texObjAgg; -/* Enabling the following definition will modify the following function to - * make the map look worse for extra speed in the emulator, especially in large - * areas such as hyrule field. - */ -#define HYRULE_FIELD_SPEEDHACK - void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_line) { s32 width = getDecorationLineWidth(p_line->field_0x1); if (width <= 0) { @@ -527,8 +564,32 @@ void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_li lineColor.r = lineColor.r - 4; GXSetTevColor(GX_TEVREG1, lineColor); +#if TARGET_PC + GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0); + GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_KONST); + GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GXBegin(GX_LINESTRIP, GX_VTXFMT0, 2 * (data_num - 1)); + for (int i = 0; i < data_num - 1; i++) { + GXPosition1x16(data_p[i]); + GXTexCoord2f32(0, 0); + GXPosition1x16(data_p[i + 1]); + GXTexCoord2f32(0, 0); + } + GXEnd(); + + GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_KONST, GX_CC_TEXC, GX_CC_C1); + GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA); + GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); + GXBegin(GX_POINTS, GX_VTXFMT0, data_num); + for (int i = 0; i < data_num; i++) { + GXPosition1x16(data_p[i]); + GXTexCoord2f32(0, 0); + } + GXEnd(); +#else for (int i = 0; i < data_num; i++) { -#ifndef HYRULE_FIELD_SPEEDHACK if (i < data_num - 1) { GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0); GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, @@ -547,7 +608,6 @@ void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_li GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_TEXA); GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_TRUE, GX_TEVPREV); -#endif GXBegin(GX_POINTS, GX_VTXFMT0, 1); GXPosition1x16(data_p[0]); @@ -555,6 +615,7 @@ void dRenderingFDAmap_c::renderingDecoration(dDrawPath_c::line_class const* p_li GXEnd(); data_p++; } +#endif setTevSettingNonTextureDirectColor(); GXClearVtxDesc(); diff --git a/src/d/d_menu_dmap.cpp b/src/d/d_menu_dmap.cpp index bb557efdac..8ef7fadd4b 100644 --- a/src/d/d_menu_dmap.cpp +++ b/src/d/d_menu_dmap.cpp @@ -856,7 +856,46 @@ void dMenu_DmapBg_c::decGoldFrameAlphaRate() { setGoldFrameAlphaRate(rate); } +void dMenu_DmapBg_c::dMapBgWide() { + // Scale Base HUD + mBaseScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); + mBaseScreen->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); + + // Boss Key, Compass & Map icons + mBaseScreen->search(MULTI_CHAR('key_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBaseScreen->search(MULTI_CHAR('con_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBaseScreen->search(MULTI_CHAR('map_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Text Header + mBaseScreen->search(MULTI_CHAR('t_t00'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mBaseScreen->search(MULTI_CHAR('f_t_00'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // C Button + mBaseScreen->search(MULTI_CHAR('c_btn2'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Scale Buttons HUD + mButtonScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); + mButtonScreen->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); + + // Buttons + mButtonScreen->search(MULTI_CHAR('cont_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // C Button + mButtonScreen->search(MULTI_CHAR('c_btn'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mButtonScreen->search(MULTI_CHAR('c_text_s'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mButtonScreen->search(MULTI_CHAR('c_text'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mButtonScreen->search(MULTI_CHAR('f_text_s'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + mButtonScreen->search(MULTI_CHAR('f_text'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); + + // Decorations + mButtonScreen->search(MULTI_CHAR('kazari_n'))->scale(mDoGph_gInf_c::hudAspectScaleDown, 1.0f); +} + void dMenu_DmapBg_c::draw() { + #if TARGET_PC + dMapBgWide(); + #endif + u32 scissor_left; u32 scissor_top; u32 scissor_width; diff --git a/src/d/d_menu_dmap_map.cpp b/src/d/d_menu_dmap_map.cpp index 8971b2630d..9100a7d63a 100644 --- a/src/d/d_menu_dmap_map.cpp +++ b/src/d/d_menu_dmap_map.cpp @@ -11,6 +11,9 @@ #include "d/d_menu_dmap_map.h" #include "f_op/f_op_msg_mng.h" #include "m_Do/m_Do_graphic.h" +#if TARGET_PC +#include +#endif struct dMdm_HIO_prm_res_dst_s { static void* m_res; @@ -291,6 +294,9 @@ void dMenu_DmapMap_c::_create(u16 param_0, u16 param_1, u16 param_2, u16 param_3 void dMenu_DmapMap_c::_delete() { for (int i = 0; i < 2; i++) { if (mMapImage_p[i] != NULL) { +#if TARGET_PC + GXDestroyCopyTex(mMapImage_p[i]); +#endif JKR_DELETE_ARRAY(mMapImage_p[i]); } diff --git a/src/d/d_menu_fmap2D.cpp b/src/d/d_menu_fmap2D.cpp index 8692156daf..ea9912998b 100644 --- a/src/d/d_menu_fmap2D.cpp +++ b/src/d/d_menu_fmap2D.cpp @@ -20,6 +20,15 @@ #include "dusk/frame_interpolation.h" #include +#if TARGET_PC +void dMenu_Fmap2DBack_c::fMapBackWide() { + mpBaseScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); + mpBaseScreen->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); + mpBackScreen->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); + mpBackScreen->translate(mDoGph_gInf_c::getSafeMinXF(), 0.0f); +} +#endif + dMenu_Fmap2DBack_c::dMenu_Fmap2DBack_c() { dMeter2Info_setMapDrugFlag(0); @@ -267,6 +276,10 @@ dMenu_Fmap2DBack_c::~dMenu_Fmap2DBack_c() { } void dMenu_Fmap2DBack_c::draw() { + #if TARGET_PC + fMapBackWide(); + #endif + calcBlink(); J2DGrafContext* grafPort = dComIfGp_getCurrentGrafPort(); @@ -1199,7 +1212,7 @@ f32 dMenu_Fmap2DBack_c::getMapScissorAreaSizeX() { } f32 dMenu_Fmap2DBack_c::getMapScissorAreaSizeRealX() { -#if PLATFORM_GCN && !TARGET_PC +#if PLATFORM_GCN return getMapScissorAreaSizeX(); #else return getMapScissorAreaSizeX() * mDoGph_gInf_c::getScale(); @@ -1407,6 +1420,11 @@ void dMenu_Fmap2DBack_c::stageTextureDraw() { mpSpotTexture->setAlpha(mAlphaRate * 255.0f * field_0xfa8 * mSpotTextureFadeAlpha); } +#if TARGET_PC + JUTPalette* pPalette = mpSpotTexture->getTexture(0)->getPalette(); + pPalette->dataUploaded(); +#endif + mpSpotTexture->draw(mTransX + getMapScissorAreaLX(), mTransZ + getMapScissorAreaLY(), getMapScissorAreaSizeRealX(), getMapScissorAreaSizeRealY(), false, false, false); @@ -2179,6 +2197,17 @@ void dMenu_Fmap2DBack_c::setArrowPosAxis(f32 i_posX, f32 i_posZ) { control_ypos = 0.0f; } +#if TARGET_PC +void dMenu_Fmap2DTop_c::fMapTopWide() { + mpTitleScreen->search(MULTI_CHAR('spot0_n'))->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); + mpTitleScreen->search(MULTI_CHAR('spot2_n'))->scale(mDoGph_gInf_c::hudAspectScaleUp, 1.0f); + mpTitleScreen->search(MULTI_CHAR('name_n'))->translate(mDoGph_gInf_c::ScaleHUDXLeft(-243.0f), -169.0f); + mpTitleScreen->search(MULTI_CHAR('sub_n_n'))->translate(mDoGph_gInf_c::ScaleHUDXLeft(-80.0f), -154.0f); + mpTitleScreen->search(MULTI_CHAR('btn_i_n'))->translate(mDoGph_gInf_c::ScaleHUDXLeft(-241.0f), 177.0f); + mpTitleScreen->search(MULTI_CHAR('cont_n'))->translate(mDoGph_gInf_c::ScaleHUDXRight(515.0f), 83.0f); +} +#endif + dMenu_Fmap2DTop_c::dMenu_Fmap2DTop_c(JKRExpHeap* i_heap, STControl* i_stick) { mpHeap = i_heap; mTransX = 0.0f; @@ -2572,6 +2601,10 @@ void dMenu_Fmap2DTop_c::setAllAlphaRate(f32 i_rate, bool i_init) { } void dMenu_Fmap2DTop_c::draw() { + #if TARGET_PC + fMapTopWide(); + #endif + u32 scissor_left, scissor_top, scissor_width, scissor_height; J2DOrthoGraph* ctx = static_cast(dComIfGp_getCurrentGrafPort()); ctx->setup2D(); diff --git a/src/d/d_menu_fmap_map.cpp b/src/d/d_menu_fmap_map.cpp index 3d1a9e2523..c500aff0be 100644 --- a/src/d/d_menu_fmap_map.cpp +++ b/src/d/d_menu_fmap_map.cpp @@ -8,6 +8,9 @@ #include "d/d_debug_viewer.h" #include "d/d_menu_fmap_map.h" #include "m_Do/m_Do_graphic.h" +#if TARGET_PC +#include +#endif #include static u8 twoValueLineInterpolation(u8 i_value1, u8 i_value2, f32 i_param) { @@ -494,6 +497,9 @@ void dMenu_FmapMap_c::_delete() { mResTIMG = NULL; } if (mMapImage_p != NULL) { +#if TARGET_PC + GXDestroyCopyTex(mMapImage_p); +#endif JKR_DELETE_ARRAY(mMapImage_p); mMapImage_p = NULL; } diff --git a/src/d/d_menu_letter.cpp b/src/d/d_menu_letter.cpp index a0155ef6b4..589b2553c0 100644 --- a/src/d/d_menu_letter.cpp +++ b/src/d/d_menu_letter.cpp @@ -17,6 +17,10 @@ #include "d/d_msg_scrn_arrow.h" #include "d/d_lib.h" +#ifdef TARGET_PC +#include "dusk/achievements.h" +#endif + #if VERSION == VERSION_GCN_JPN #define D_MENU_LETTER_LINE_MAX 9 #else @@ -514,6 +518,10 @@ void dMenu_Letter_c::read_open_init() { setAButtonString(0); setBButtonString(0); mpBlackTex->setAlpha(0); + + #ifdef TARGET_PC + dusk::AchievementSystem::get().signal("open_letter"); + #endif } void dMenu_Letter_c::read_open_move() { diff --git a/src/d/d_meter2.cpp b/src/d/d_meter2.cpp index 5bd321e7a8..fa6774d0a5 100644 --- a/src/d/d_meter2.cpp +++ b/src/d/d_meter2.cpp @@ -316,6 +316,12 @@ int dMeter2_c::_execute() { } int dMeter2_c::_draw() { + #if TARGET_PC + if (dusk::getSettings().game.disableMainHUD) { + return 1; + } + #endif + if (mpMap != NULL) { mpMap->_draw(); } @@ -424,12 +430,6 @@ void dMeter2_c::setLifeZero() { void dMeter2_c::checkStatus() { mStatus = 0; - #if TARGET_PC - if (dusk::getSettings().game.disableMainHUD) { - mStatus |= 0xF0000000; - } - #endif - field_0x12c = field_0x128; field_0x128 = daPy_py_c::checkNowWolf(); diff --git a/src/d/d_meter_HIO.cpp b/src/d/d_meter_HIO.cpp index 2df28b2655..584a2852f6 100644 --- a/src/d/d_meter_HIO.cpp +++ b/src/d/d_meter_HIO.cpp @@ -2306,6 +2306,10 @@ void dMeter_drawHIO_c::updateOnWide() { // River Canoe Minigame g_drawHIO.mMiniGame.mCounterPosX[1] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mCounterPosX[1]); g_drawHIO.mMiniGame.mIconPosX[1] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mIconPosX[1]); + + // Bulblin Count in Hidden Village + g_drawHIO.mMiniGame.mCounterPosX[2] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mCounterPosX[2]); + g_drawHIO.mMiniGame.mIconPosX[2] = mDoGph_gInf_c::ScaleHUDXRight(g_drawHIO.mMiniGame.mIconPosX[2]); #endif } diff --git a/src/d/d_msg_class.cpp b/src/d/d_msg_class.cpp index 4040eb0d7f..f67d6054e6 100644 --- a/src/d/d_msg_class.cpp +++ b/src/d/d_msg_class.cpp @@ -2066,7 +2066,7 @@ bool jmessage_tSequenceProcessor::do_isReady() { case 0: case 5: case 6: - if (mDoCPd_c::getTrigA(PAD_1) || field_0xb2 != 0) { + if (mDoCPd_c::getTrigA(PAD_1) || field_0xb2 != 0 IF_DUSK(|| (dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)))) { field_0xa4 = 0; pReference->onBatchFlag(); pReference->setCharCnt(D_MSG_CLASS_CHAR_CNT_MAX); diff --git a/src/d/d_msg_object.cpp b/src/d/d_msg_object.cpp index ae0e3d8427..7e3be2cb08 100644 --- a/src/d/d_msg_object.cpp +++ b/src/d/d_msg_object.cpp @@ -32,6 +32,9 @@ #if TARGET_PC #include "dusk/settings.h" +#include +#include +#include #endif static void dMsgObject_addFundRaising(s16 param_0); @@ -424,6 +427,16 @@ static void dummyStrings() { dMsgObject_HIO_c g_MsgObject_HIO_c; int dMsgObject_c::_execute() { +#if TARGET_PC + if (dusk::getSettings().game.enableMirrorMode) { + // enable wii message index override + g_MsgObject_HIO_c.mMessageDisplay = 1; + } else if (!dusk::getSettings().game.enableMirrorMode && g_MsgObject_HIO_c.mMessageDisplay == 1) { + g_MsgObject_HIO_c.mMessageDisplay = 0; + } +#endif + + field_0x4c7 = 0; if (mpTalkHeap != NULL) { field_0x148 = mDoExt_setCurrentHeap(mpTalkHeap); @@ -1594,7 +1607,7 @@ u8 dMsgObject_c::isSend() { return 2; } } else { - if (IF_DUSK((dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0)) ||) + if (IF_DUSK((dusk::getSettings().game.instantText && mDoCPd_c::getHoldB(0) && !isShopItemMessage()) ||) mDoCPd_c::getTrigA(0) != 0 || mDoCPd_c::getTrigB(0) != 0) { return 2; } @@ -1866,6 +1879,39 @@ bool dMsgObject_c::isTalkMessage() { return true; } +#if TARGET_PC +bool dMsgObject_c::isShopItemMessage() { + + // Probably a better way to do this than just listing every message id, but this works for now + // Note: Keep contents sorted so we can use binary search + const auto shopMsgIds = std::to_array>({ + {}, + // zel_01.bmg - Seras Shop + {7001, 7003, 7004, 7005, 7006, 7007, 7008, 7009, 7010, 7013, 7014, 7022, 7023, 7028, 7029, + 7044, 7045, 7053}, + // zel_02.bmg - Kakariko Shops + {5181, 5182, 5251, 5253, 5254, 5256, 5258, 5259, 5653, 5654, 5656, 5660, 5661, 5664, 5665, + 5697, 5698, 5699, 5803, 5804, 5806, 5810, 5811, 5812, 5814, 5821, 5823, 5824, 5987, 5988, + 5989, 5990, 5991, 5992, 5993, 5994, 5995, 5996, 5997, 5998, 5999}, + // zel_03.bmg - Death Mountain Shop + {5303, 5304, 5306, 5310, 5311, 5314, 5315, 5322, 5323, 5324, 5496, 5497, 5498, 5499}, + // zel_04.bmg - Castle Town Shops + {5407, 5408, 5409, 5410, 5411, 5412, 5413, 5414, 5415, 5416, 5417, 5418, 5419, 5420, 5431, + 5432, 5434, 5435, 5436, 5437, 5438, 5439, 5440, 5441, 5444, 5449, 5450, 5451, 5452, 5462}, + // zel_05.bmg - Oocca Shop + {9428, 9429, 9430, 9431, 9432, 9437, 9443, 9448, 9449, 9451, 9459} + }); + + u16 id = mMessageID; + s16 group = dMsgObject_getGroupID(); + if (group < shopMsgIds.size()) { + return std::ranges::binary_search(shopMsgIds[group], id); + } + return false; + +} +#endif + const char* dMsgObject_c::getSmellName() { JMSMesgInfo_c* info_header_p = (JMSMesgInfo_c*)((char*)mpMsgRes + 0x20); char* data_ptr = (char*)info_header_p + info_header_p->header.size; diff --git a/src/d/d_s_name.cpp b/src/d/d_s_name.cpp index 735ce4d0ca..6baa07da30 100644 --- a/src/d/d_s_name.cpp +++ b/src/d/d_s_name.cpp @@ -5,19 +5,20 @@ #include "d/dolzel.h" // IWYU pragma: keep -#include "d/d_s_name.h" #include "JSystem/JKernel/JKRExpHeap.h" #include "d/d_com_inf_game.h" #include "d/d_meter2_info.h" +#include "d/d_s_name.h" +#include "dusk/imgui/ImGuiConsole.hpp" +#include "dusk/memory.h" +#include "dusk/settings.h" +#include "f_op/f_op_overlap_mng.h" #include "f_op/f_op_scene_mng.h" #include "m_Do/m_Do_Reset.h" #include "m_Do/m_Do_graphic.h" #include "m_Do/m_Do_machine.h" -#include "m_Do/m_Do_mtx.h" #include "m_Do/m_Do_main.h" -#include "f_op/f_op_overlap_mng.h" -#include "dusk/memory.h" -#include "dusk/settings.h" +#include "m_Do/m_Do_mtx.h" #if TARGET_PC #define SHOW_TV_SETTINGS_SCREEN (this->mShowTvSettingsScreen) @@ -412,6 +413,16 @@ void dScnName_c::changeGameScene() { dKy_clear_game_init(); dComIfGs_resetDan(); dComIfGs_setRestartRoomParam(0); + +#if TARGET_PC + if (dusk::getSettings().game.speedrunMode && dusk::getSettings().game.hideTvSettingsScreen) { + // start a new run on file load if a run isn't already in progress + if (!dusk::m_speedrunInfo.m_isRunStarted) { + dusk::ImGuiMenuGame::resetForSpeedrunMode(); + dusk::m_speedrunInfo.startRun(); + } + } +#endif } } diff --git a/src/d/d_s_play.cpp b/src/d/d_s_play.cpp index 38308d0668..f0e2330a15 100644 --- a/src/d/d_s_play.cpp +++ b/src/d/d_s_play.cpp @@ -41,6 +41,7 @@ #if TARGET_PC #include "dusk/memory.h" +#include #endif #if DEBUG @@ -700,6 +701,10 @@ static u8 lbl_8074CAE4; static u32 l_sceneChangeStartTick; #endif +#if TARGET_PC +static BOOL autoSaved; +#endif + static int dScnPly_Execute(dScnPly_c* i_this) { #if DEBUG fapGm_HIO_c::startCpuTimer(); @@ -742,6 +747,15 @@ static int dScnPly_Execute(dScnPly_c* i_this) { } } + #if TARGET_PC + if (!dComIfGp_event_runCheck() && !fopOvlpM_IsPeek() && !dComIfG_resetToOpening(i_this) && + !dComIfGp_isEnableNextStage() && autoSaved == FALSE) + { + triggerAutoSave(); + autoSaved = TRUE; + } + #endif + dKy_itudemo_se(); #if DEBUG @@ -1593,6 +1607,11 @@ static int dScnPly_Create(scene_class* i_this) { dScnPly_c* a_this = (dScnPly_c*)i_this; int phase_state = dComLbG_PhaseHandler(&a_this->field_0x1c4, l_method, a_this); + + #if TARGET_PC + autoSaved = FALSE; + #endif + return phase_state; } diff --git a/src/d/d_save.cpp b/src/d/d_save.cpp index 15559e6307..0708c59716 100644 --- a/src/d/d_save.cpp +++ b/src/d/d_save.cpp @@ -27,7 +27,11 @@ #include "lingcod/lingcod.h" #endif +#if TARGET_PC #include "dusk/settings.h" +#include +#include +#endif static u8 dSv_item_rename(u8 i_itemNo) { switch (i_itemNo) { @@ -345,6 +349,10 @@ void dSv_player_item_c::setItem(int i_slotNo, u8 i_itemNo) { dComIfGp_setSelectItem(i); } } + + #if TARGET_PC + triggerAutoSave(); + #endif } u8 dSv_player_item_c::getItem(int i_slotNo, bool i_checkCombo) const { diff --git a/src/dusk/OSReport.cpp b/src/dusk/OSReport.cpp new file mode 100644 index 0000000000..a54bcf0c78 --- /dev/null +++ b/src/dusk/OSReport.cpp @@ -0,0 +1,118 @@ +#include + +#include "aurora/lib/logging.hpp" +#include "os_report.h" + +aurora::Module Log("dusk::osReport"); + +bool dusk::OSReportReallyForceEnable = false; + +u8 __OSReport_disable; + +void OSReportDisable() { + __OSReport_disable = true; +} + +void OSReportEnable() { + __OSReport_disable = false; +} + +static bool checkEnabled() { + return !__OSReport_disable || dusk::OSReportReallyForceEnable; +} + +static std::string FormatToString(const char* msg, va_list list) { + int ret = vsnprintf(nullptr, 0, msg, list); + if (ret <= 0) { + return {}; + } + ++ret; + std::unique_ptr buf(new char[ret]); + vsnprintf(buf.get(), ret, msg, list); + buf[ret - 1] = '\0'; + return {buf.get()}; +} + +void OSReport(const char* fmt, ...) { + if (!checkEnabled()) { + return; + } + va_list args; + va_start(args, fmt); + const auto str = FormatToString(fmt, args); + va_end(args); + + Log.info("{}", str); +} + +void OSPanic(const char* file, int line, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + const auto str = FormatToString(fmt, args); + va_end(args); + + Log.fatal("[{}:{}] {}", file, line, str); +} + +void OSReport_Error(const char* fmt, ...) { + if (!checkEnabled()) { + return; + } + + va_list args; + va_start(args, fmt); + const auto str = FormatToString(fmt, args); + va_end(args); + + Log.error("{}", str); +} + +void OSReport_FatalError(const char* fmt, ...) { + if (!checkEnabled()) { + return; + } + + va_list args; + va_start(args, fmt); + const auto str = FormatToString(fmt, args); + va_end(args); + + Log.fatal("{}", str); +} + +void OSReport_Warning(const char* fmt, ...) { + if (!checkEnabled()) { + return; + } + + va_list args; + va_start(args, fmt); + const auto str = FormatToString(fmt, args); + va_end(args); + + Log.warn("{}", str); +} + +void OSReport_System(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + OSVAttention(fmt, args); + va_end(args); +} + +void OSVAttention(const char* fmt, va_list args) { + if (!checkEnabled()) { + return; + } + + const auto str = FormatToString(fmt, args); + + Log.info("{}", str); +} + +void OSAttention(const char* fmt, ...) { + va_list args; + va_start(args, fmt); + OSVAttention(fmt, args); + va_end(args); +} \ No newline at end of file diff --git a/src/dusk/achievements.cpp b/src/dusk/achievements.cpp index 071332a7c2..26a302678e 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", @@ -267,18 +335,13 @@ std::vector AchievementSystem::makeEntries() { false, 0, 0, false }, [](Achievement& a, json&) { - static int titleNoDemoFrames = 0; if (fopAcM_SearchByName(fpcNm_TITLE_e) == nullptr) { - titleNoDemoFrames = 0; return; } - const auto* link = static_cast(daPy_getPlayerActorClass()); - if (link != nullptr && dDemo_c::getMode() == 0) { - if (++titleNoDemoFrames >= 60) { + const auto* player = static_cast(daPy_getPlayerActorClass()); + + if (player != nullptr && player->mDemo.getDemoMode() == 1) { a.progress = 1; - } - } else { - titleNoDemoFrames = 0; } }, {} @@ -345,6 +408,41 @@ std::vector AchievementSystem::makeEntries() { } }, {} + }, + { + { + "email_me", + "Email Me", + "Read a letter during the Dark Beast Ganon fight.", + AchievementCategory::Misc, + false, 0, 0, false + }, + [](Achievement& a, json&) { + void* dbgExists = fopAcM_SearchByName(fpcNm_B_MGN_e); + if (dbgExists && AchievementSystem::get().hasSignal("open_letter")) { + a.progress = 1; + } + }, + {} + }, + { + { + "heavy-hitter", + "Heavy Hitter", + "Wear the Iron Boots during the end credits.", + AchievementCategory::Misc, + false, 0, 0, false + }, + [](Achievement& a, json&) { + const auto* link = static_cast(daPy_getPlayerActorClass()); + if (link == nullptr || link->mProcID != daAlink_c::PROC_GANON_FINISH) { + return; + } + if (daPy_getPlayerActorClass()->checkEquipHeavyBoots()) { + a.progress = 1; + } + }, + {} } }; } @@ -426,6 +524,26 @@ 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) { + e.achievement.progress = 0; + e.achievement.unlocked = false; + e.extra = {}; + break; + } + } + save(); +} + void AchievementSystem::processEntry(Entry& e) { if (e.achievement.unlocked) { return; @@ -458,6 +576,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/audio/DuskDsp.cpp b/src/dusk/audio/DuskDsp.cpp index f576a5d0f1..697371702f 100644 --- a/src/dusk/audio/DuskDsp.cpp +++ b/src/dusk/audio/DuskDsp.cpp @@ -48,6 +48,20 @@ f32 dusk::audio::MasterVolume = 1.0f; f32 dusk::audio::PrevMasterVolume = 1.0f; bool dusk::audio::EnableReverb = true; bool dusk::audio::DumpAudio = false; +bool dusk::audio::EnableHrtf = false; +f32 dusk::audio::HrtfGain = 0.5f; + + +// 3dB at 5kHz. +static constexpr f32 HRTF_LP_K = 0.75f; +static constexpr f32 HRTF_ALLPASS_G = 0.3f; +// Front never drops below (1 - HRTF_EXTRACT_MAX). +static constexpr f32 HRTF_EXTRACT_MAX = 0.6f; + +static f32 sHrtfLp1 = 0.0f; +static f32 sHrtfLp2 = 0.0f; +static f32 sHrtfApIn1 = 0.0f; +static f32 sHrtfApOut1 = 0.0f; /** * Validate that a DSP channel's format is actually something we know how to play. @@ -283,6 +297,9 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { DspSubframe reverbInputR = {}; bool anyReverbInput = false; + DspSubframe surroundBus = {}; + bool anySurroundInput = false; + for (int i = 0; i < channels.size(); i++) { auto& channel = channels[i]; auto& channelAux = ChannelAux[i]; @@ -324,6 +341,21 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { } } + if (EnableHrtf && channel.mAutoMixerBeenSet) { + f32 dolby = (channel.mAutoMixerPanDolby & 0xFF) / 127.0f; + if (dolby > 0.0f) { + anySurroundInput = true; + f32 extract = dolby * HRTF_EXTRACT_MAX; + f32 frontScale = 1.0f - extract; + for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { + f32 mono = (channelSubframe.channels[0][j] + channelSubframe.channels[1][j]) * 0.5f; + surroundBus[j] += mono * extract; + channelSubframe.channels[0][j] *= frontScale; + channelSubframe.channels[1][j] *= frontScale; + } + } + } + if (DumpAudio && sChannelDumpFiles[i]) { f32 interleaved[DSP_SUBFRAME_SIZE * 2]; for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { @@ -349,6 +381,28 @@ void dusk::audio::DspRender(OutputSubframe& subframe) { ReverbHasTail = wetEnergy >= REVERB_ENERGY_EPSILON; } + if (EnableHrtf && anySurroundInput) { + // Two-pole LPF: -12 dB/oct above 3 kHz + for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { + sHrtfLp1 = (1.0f - HRTF_LP_K) * sHrtfLp1 + HRTF_LP_K * surroundBus[j]; + sHrtfLp2 = (1.0f - HRTF_LP_K) * sHrtfLp2 + HRTF_LP_K * sHrtfLp1; + surroundBus[j] = sHrtfLp2; + } + + // Mix into L and R + // L gets the filtered signal directly; R gets it allpass for mild decorrelation + for (int j = 0; j < DSP_SUBFRAME_SIZE; j++) { + f32 s = surroundBus[j]; + + subframe.channels[0][j] += s * HrtfGain; + + f32 r = -HRTF_ALLPASS_G * s + sHrtfApIn1 + HRTF_ALLPASS_G * sHrtfApOut1; + sHrtfApIn1 = s; + sHrtfApOut1 = r; + subframe.channels[1][j] += r * HrtfGain; + } + } + for (auto& channel : subframe.channels) { ApplyVolume(channel, channel, PrevMasterVolume, MasterVolume); } diff --git a/src/dusk/audio/DuskDsp.hpp b/src/dusk/audio/DuskDsp.hpp index 8000e627a1..cfcbfe3f46 100644 --- a/src/dusk/audio/DuskDsp.hpp +++ b/src/dusk/audio/DuskDsp.hpp @@ -133,4 +133,6 @@ namespace dusk::audio { extern f32 PrevMasterVolume; extern bool EnableReverb; extern bool DumpAudio; + extern bool EnableHrtf; + extern f32 HrtfGain; } diff --git a/src/dusk/autosave.cpp b/src/dusk/autosave.cpp new file mode 100644 index 0000000000..f9b76f4c67 --- /dev/null +++ b/src/dusk/autosave.cpp @@ -0,0 +1,88 @@ +#include "dusk/autosave.h" +#include "imgui/ImGuiConsole.hpp" + +u8 mSaveBuffer[QUEST_LOG_SIZE * 3]; +u8 mAutoSaveProc = 0; +int autoSaveWriteState = 0; + +typedef void (*AutoSaveFuncs)(); +static AutoSaveFuncs AutoSaveFuncsProc[] = { + noAutoSave, enterAutoSave, autoSaving, waitingForWrite, endAutoSave, +}; + +void noAutoSave() {} + +void triggerAutoSave() { + if (dusk::getSettings().game.autoSave && mAutoSaveProc == 0 && + strcmp(dComIfGp_getStartStageName(), "F_SP102") != 0) + { + mAutoSaveProc = 1; + } +} + +void updateAutoSave() { + (AutoSaveFuncsProc[mAutoSaveProc])(); +} + +void writeAutoSave() { + int stageNo = dStage_stagInfo_GetSaveTbl(dComIfGp_getStageStagInfo()); + + dComIfGs_putSave(stageNo); + dComIfGs_setMemoryToCard(mSaveBuffer, dComIfGs_getDataNum()); + mDoMemCdRWm_SetCheckSumGameData(mSaveBuffer, dComIfGs_getDataNum()); + + u8* save = mSaveBuffer; + for (int i = 0; i < 3; i++) { + mDoMemCdRWm_TestCheckSumGameData(save); + save += QUEST_LOG_SIZE; + } + + g_mDoMemCd_control.save(mSaveBuffer, sizeof(mSaveBuffer), 0); +} + +void autoSaving() { + int cardState = g_mDoMemCd_control.LoadSync(mSaveBuffer, sizeof(mSaveBuffer), 0); + if (cardState != 0) { + if (cardState == 2) { + mAutoSaveProc = 1; + } else if (cardState == 1) { + writeAutoSave(); + mAutoSaveProc = 3; + } + } +} + +void enterAutoSave() { + u32 cardStatus = g_mDoMemCd_control.getStatus(0); + + if (cardStatus != 14) { + switch (cardStatus) { + case 2: + g_mDoMemCd_control.load(); + mAutoSaveProc = 2; + break; + case 3: + case 4: + case 5: + break; + default: + mAutoSaveProc = 0; + break; + } + } +} + +void waitingForWrite() { + autoSaveWriteState = g_mDoMemCd_control.SaveSync(); + + if (autoSaveWriteState == 2) { + mAutoSaveProc = 0; + } else if (autoSaveWriteState == 1) { + mAutoSaveProc = 4; + } +} + +void endAutoSave() { + dusk::g_imguiConsole.AddToast("Saving...", 2.0f); + mAutoSaveProc = 0; +} \ No newline at end of file diff --git a/src/dusk/discord.cpp b/src/dusk/discord.cpp new file mode 100644 index 0000000000..f20541bfa4 --- /dev/null +++ b/src/dusk/discord.cpp @@ -0,0 +1,867 @@ +#ifdef DUSK_DISCORD + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#include "discord.hpp" + +#include "dusk/logging.h" +#include "nlohmann/json.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMCX +#define NOSERVICE +#define NOIME +#include +#else +#include +#include +#include +#include +#include +#include +#include +#endif + +namespace dusk::discord::rpc { +namespace { + +using json = nlohmann::json; + +constexpr uint32_t kRpcVersion = 1; +constexpr size_t kFrameHeaderSize = sizeof(uint32_t) * 2; +constexpr size_t kMaxFramePayloadSize = 64 * 1024; +constexpr auto kIoWait = std::chrono::milliseconds(500); +constexpr auto kShutdownClearTimeout = std::chrono::milliseconds(200); +constexpr auto kInitialReconnectDelay = std::chrono::milliseconds(500); +constexpr auto kMaxReconnectDelay = std::chrono::milliseconds(60 * 1000); + +enum class Opcode : uint32_t { + Handshake = 0, + Frame = 1, + Close = 2, + Ping = 3, + Pong = 4, +}; + +enum class ConnectionState { + Disconnected, + SentHandshake, + Connected, +}; + +enum class DisconnectCode : int { + PipeClosed = 1, + ReadCorrupt = 2, + BadFrame = 3, +}; + +struct Frame { + Opcode opcode = Opcode::Frame; + std::string payload; +}; + +struct QueuedEvent { + enum class Type { + Ready, + Disconnected, + Error, + }; + + Type type = Type::Ready; + User user; + int code = 0; + std::string message; +}; + +class Backoff { +public: + std::chrono::milliseconds next_delay() { + const auto delay = currentDelay; + currentDelay = std::min(currentDelay * 2, kMaxReconnectDelay); + return delay; + } + + void reset() { currentDelay = kInitialReconnectDelay; } + +private: + std::chrono::milliseconds currentDelay = kInitialReconnectDelay; +}; + +class IpcConnection { +public: + IpcConnection() = default; + ~IpcConnection() { close(); } + + IpcConnection(const IpcConnection&) = delete; + IpcConnection& operator=(const IpcConnection&) = delete; + + bool open() { +#ifdef _WIN32 + wchar_t pipeName[] = L"\\\\?\\pipe\\discord-ipc-0"; + constexpr size_t kPipeDigit = sizeof(pipeName) / sizeof(wchar_t) - 2; + + for (wchar_t pipeNumber = L'0'; pipeNumber <= L'9'; ++pipeNumber) { + pipeName[kPipeDigit] = pipeNumber; + pipe = CreateFileW( + pipeName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); + if (pipe != INVALID_HANDLE_VALUE) { + return true; + } + + if (GetLastError() == ERROR_PIPE_BUSY && WaitNamedPipeW(pipeName, 10000)) { + --pipeNumber; + } + } + return false; +#else + const auto tempPaths = get_temp_paths(); + for (const std::string& tempPath : tempPaths) { + for (int pipeNumber = 0; pipeNumber < 10; ++pipeNumber) { + socketFd = socket(AF_UNIX, SOCK_STREAM, 0); + if (socketFd == -1) { + return false; + } + + sockaddr_un pipeAddress{}; + pipeAddress.sun_family = AF_UNIX; + const std::string socketPath = + tempPath + "/discord-ipc-" + std::to_string(pipeNumber); + if (socketPath.size() >= sizeof(pipeAddress.sun_path)) { + close(); + continue; + } + + std::strncpy( + pipeAddress.sun_path, socketPath.c_str(), sizeof(pipeAddress.sun_path) - 1); + if (connect(socketFd, reinterpret_cast(&pipeAddress), + sizeof(pipeAddress)) == 0) + { + fcntl(socketFd, F_SETFL, O_NONBLOCK); +#ifdef SO_NOSIGPIPE + int optval = 1; + setsockopt(socketFd, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval)); +#endif + return true; + } + close(); + } + } + return false; +#endif + } + + void close() { +#ifdef _WIN32 + if (pipe != INVALID_HANDLE_VALUE) { + CloseHandle(pipe); + pipe = INVALID_HANDLE_VALUE; + } +#else + if (socketFd != -1) { + ::close(socketFd); + socketFd = -1; + } +#endif + pendingRead.clear(); + } + + bool is_open() const { +#ifdef _WIN32 + return pipe != INVALID_HANDLE_VALUE; +#else + return socketFd != -1; +#endif + } + + bool write_frame(const Frame& frame) { + std::array header{}; + write_u32(header.data(), static_cast(frame.opcode)); + write_u32(header.data() + sizeof(uint32_t), static_cast(frame.payload.size())); + + return write_all(header.data(), header.size()) && + write_all( + reinterpret_cast(frame.payload.data()), frame.payload.size()); + } + + enum class ReadStatus { + None, + Frame, + Closed, + Corrupt, + }; + + ReadStatus read_frame(Frame& frame) { + if (!read_available()) { + return is_open() ? ReadStatus::None : ReadStatus::Closed; + } + + if (pendingRead.size() < kFrameHeaderSize) { + return ReadStatus::None; + } + + const uint32_t payloadLength = read_u32(pendingRead.data() + sizeof(uint32_t)); + if (payloadLength > kMaxFramePayloadSize) { + return ReadStatus::Corrupt; + } + + const size_t frameLength = kFrameHeaderSize + payloadLength; + if (pendingRead.size() < frameLength) { + return ReadStatus::None; + } + + frame.opcode = static_cast(read_u32(pendingRead.data())); + frame.payload.assign( + reinterpret_cast(pendingRead.data() + kFrameHeaderSize), payloadLength); + pendingRead.erase( + pendingRead.begin(), pendingRead.begin() + static_cast(frameLength)); + return ReadStatus::Frame; + } + +private: +#ifndef _WIN32 + static std::vector get_temp_paths() { + std::vector paths; + for (const char* name : {"XDG_RUNTIME_DIR", "TMPDIR", "TMP", "TEMP"}) { + if (const char* value = std::getenv(name); value && value[0] != '\0') { + if (std::find(paths.begin(), paths.end(), value) == paths.end()) { + paths.emplace_back(value); + } + } + } + if (std::find(paths.begin(), paths.end(), "/tmp") == paths.end()) { + paths.emplace_back("/tmp"); + } + return paths; + } +#endif + + static void write_u32(uint8_t* out, uint32_t value) { + out[0] = static_cast(value & 0xff); + out[1] = static_cast((value >> 8) & 0xff); + out[2] = static_cast((value >> 16) & 0xff); + out[3] = static_cast((value >> 24) & 0xff); + } + + static uint32_t read_u32(const uint8_t* in) { + return static_cast(in[0]) | (static_cast(in[1]) << 8) | + (static_cast(in[2]) << 16) | (static_cast(in[3]) << 24); + } + + bool write_all(const uint8_t* data, size_t length) { + size_t written = 0; + while (written < length) { +#ifdef _WIN32 + DWORD bytesWritten = 0; + if (WriteFile(pipe, data + written, static_cast(length - written), &bytesWritten, + nullptr) == FALSE || + bytesWritten == 0) + { + close(); + return false; + } + written += bytesWritten; +#else +#ifdef MSG_NOSIGNAL + constexpr int kMsgFlags = MSG_NOSIGNAL; +#else + constexpr int kMsgFlags = 0; +#endif + const ssize_t bytesWritten = + send(socketFd, data + written, length - written, kMsgFlags); + if (bytesWritten < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + close(); + return false; + } + if (bytesWritten == 0) { + close(); + return false; + } + written += static_cast(bytesWritten); +#endif + } + return true; + } + + bool read_available() { + std::array buffer{}; + bool readAny = false; + + for (;;) { +#ifdef _WIN32 + DWORD bytesAvailable = 0; + if (PeekNamedPipe(pipe, nullptr, 0, nullptr, &bytesAvailable, nullptr) == FALSE) { + close(); + return readAny; + } + if (bytesAvailable == 0) { + return readAny; + } + + const DWORD bytesToRead = + std::min(bytesAvailable, static_cast(buffer.size())); + DWORD bytesRead = 0; + if (ReadFile(pipe, buffer.data(), bytesToRead, &bytesRead, nullptr) == FALSE) { + close(); + return readAny; + } + if (bytesRead == 0) { + close(); + return readAny; + } + pendingRead.insert(pendingRead.end(), buffer.begin(), buffer.begin() + bytesRead); + readAny = true; +#else +#ifdef MSG_NOSIGNAL + constexpr int kMsgFlags = MSG_NOSIGNAL; +#else + constexpr int kMsgFlags = 0; +#endif + const ssize_t bytesRead = recv(socketFd, buffer.data(), buffer.size(), kMsgFlags); + if (bytesRead < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + return readAny; + } + close(); + return readAny; + } + if (bytesRead == 0) { + close(); + return readAny; + } + pendingRead.insert(pendingRead.end(), buffer.begin(), buffer.begin() + bytesRead); + readAny = true; +#endif + } + } + +#ifdef _WIN32 + HANDLE pipe = INVALID_HANDLE_VALUE; +#else + int socketFd = -1; +#endif + std::vector pendingRead; +}; + +int current_process_id() { +#ifdef _WIN32 + return static_cast(GetCurrentProcessId()); +#else + return static_cast(getpid()); +#endif +} + +std::string next_nonce() { + static std::atomic_uint64_t sNonce{1}; + return std::to_string(sNonce.fetch_add(1)); +} + +const json* find_member(const json& object, const char* key) { + if (!object.is_object()) { + return nullptr; + } + + const auto member = object.find(key); + if (member == object.end()) { + return nullptr; + } + return &*member; +} + +std::string json_string_member(const json& object, const char* key) { + const json* member = find_member(object, key); + if (!member || !member->is_string()) { + return {}; + } + return member->get(); +} + +int json_int_member(const json& object, const char* key, int defaultValue) { + const json* member = find_member(object, key); + if (!member || !member->is_number_integer()) { + return defaultValue; + } + + try { + return member->get(); + } catch (const json::exception&) { + return defaultValue; + } +} + +json make_presence_activity(const Presence& presence) { + json activity = json::object(); + + if (!presence.state.empty()) { + activity["state"] = presence.state; + } + if (!presence.details.empty()) { + activity["details"] = presence.details; + } + if (presence.startTimestamp != 0) { + activity["timestamps"] = {{"start", presence.startTimestamp}}; + } + if (!presence.largeImageKey.empty() || !presence.largeImageText.empty()) { + json assets = json::object(); + if (!presence.largeImageKey.empty()) { + assets["large_image"] = presence.largeImageKey; + } + if (!presence.largeImageText.empty()) { + assets["large_text"] = presence.largeImageText; + } + activity["assets"] = std::move(assets); + } + + return activity; +} + +Frame make_handshake_frame(std::string_view applicationId) { + return { + Opcode::Handshake, + json{ + {"v", kRpcVersion}, + {"client_id", std::string(applicationId)}, + } + .dump(), + }; +} + +Frame make_set_activity_frame(std::string nonce, int pid, std::optional presence) { + json args = {{"pid", pid}}; + if (presence) { + args["activity"] = make_presence_activity(*presence); + } else { + args["activity"] = nullptr; + } + + return { + Opcode::Frame, + json{ + {"cmd", "SET_ACTIVITY"}, + {"nonce", std::move(nonce)}, + {"args", std::move(args)}, + } + .dump(), + }; +} + +class Client { +public: + void initialize(std::string applicationId, EventHandlers handlers) { + shutdown(); + + { + std::lock_guard lock(mutex); + this->applicationId = std::move(applicationId); + this->handlers = std::move(handlers); + shouldRun = true; + queuedPresence.reset(); + hasQueuedPresence = false; + clearRequested = false; + sentInitialConnectLog = false; + } + + ioThread = std::thread([this] { io_loop(); }); + } + + void run_callbacks() { + std::deque events; + EventHandlers localHandlers; + { + std::lock_guard lock(mutex); + events.swap(queuedEvents); + localHandlers = handlers; + } + + for (const QueuedEvent& event : events) { + switch (event.type) { + case QueuedEvent::Type::Ready: + if (localHandlers.ready) { + localHandlers.ready(event.user); + } + break; + case QueuedEvent::Type::Disconnected: + if (localHandlers.disconnected) { + localHandlers.disconnected(event.code, event.message); + } + break; + case QueuedEvent::Type::Error: + if (localHandlers.error) { + localHandlers.error(event.code, event.message); + } + break; + } + } + } + + void update_presence(Presence presence) { + { + std::lock_guard lock(mutex); + if (!shouldRun) { + return; + } + queuedPresence = std::move(presence); + hasQueuedPresence = true; + } + cv.notify_all(); + } + + void clear_presence() { + { + std::lock_guard lock(mutex); + if (!shouldRun) { + return; + } + queuedPresence.reset(); + hasQueuedPresence = false; + clearRequested = true; + } + cv.notify_all(); + } + + void shutdown() { + { + std::lock_guard lock(mutex); + if (!shouldRun && !ioThread.joinable()) { + return; + } + shouldRun = false; + clearRequested = true; + } + cv.notify_all(); + + if (ioThread.joinable()) { + ioThread.join(); + } + + std::lock_guard lock(mutex); + queuedPresence.reset(); + hasQueuedPresence = false; + clearRequested = false; + queuedEvents.clear(); + handlers = {}; + applicationId.clear(); + } + +private: + void io_loop() { + IpcConnection connection; + ConnectionState state = ConnectionState::Disconnected; + Backoff reconnectBackoff; + auto nextConnect = std::chrono::steady_clock::now(); + const int pid = current_process_id(); + std::string localApplicationId; + + for (;;) { + { + std::unique_lock lock(mutex); + if (!shouldRun) { + break; + } + localApplicationId = applicationId; + } + + const auto now = std::chrono::steady_clock::now(); + if (state == ConnectionState::Disconnected && now >= nextConnect) { + if (connection.open()) { + if (connection.write_frame(make_handshake_frame(localApplicationId))) { + state = ConnectionState::SentHandshake; + } else { + connection.close(); + } + } + + if (state == ConnectionState::Disconnected) { + log_waiting_for_discord_once(); + nextConnect = now + reconnectBackoff.next_delay(); + } + } + + if (state != ConnectionState::Disconnected) { + process_reads(connection, state, reconnectBackoff, nextConnect); + } + + if (state == ConnectionState::Connected) { + flush_pending_presence(connection, pid); + } + + std::unique_lock lock(mutex); + if (!shouldRun) { + break; + } + cv.wait_for(lock, kIoWait); + } + + flush_shutdown_clear(connection, state, pid); + connection.close(); + } + + void process_reads(IpcConnection& connection, ConnectionState& state, Backoff& reconnectBackoff, + std::chrono::steady_clock::time_point& nextConnect) { + for (;;) { + Frame frame; + const auto status = connection.read_frame(frame); + if (status == IpcConnection::ReadStatus::None) { + return; + } + if (status == IpcConnection::ReadStatus::Closed) { + handle_disconnect(connection, state, reconnectBackoff, nextConnect, + static_cast(DisconnectCode::PipeClosed), "Pipe closed"); + return; + } + if (status == IpcConnection::ReadStatus::Corrupt) { + handle_disconnect(connection, state, reconnectBackoff, nextConnect, + static_cast(DisconnectCode::ReadCorrupt), "Oversized Discord IPC frame"); + return; + } + + switch (frame.opcode) { + case Opcode::Frame: + process_json_frame(frame.payload, state, reconnectBackoff); + break; + case Opcode::Close: + process_close_frame( + frame.payload, connection, state, reconnectBackoff, nextConnect); + return; + case Opcode::Ping: + connection.write_frame({Opcode::Pong, frame.payload}); + break; + case Opcode::Pong: + break; + case Opcode::Handshake: + default: + handle_disconnect(connection, state, reconnectBackoff, nextConnect, + static_cast(DisconnectCode::BadFrame), "Bad Discord IPC frame"); + return; + } + } + } + + void process_json_frame( + const std::string& payload, ConnectionState& state, Backoff& reconnectBackoff) { + json message; + try { + message = json::parse(payload); + } catch (const json::parse_error&) { + enqueue_error( + static_cast(DisconnectCode::ReadCorrupt), "Invalid Discord IPC JSON"); + return; + } + + const std::string cmd = json_string_member(message, "cmd"); + const std::string evt = json_string_member(message, "evt"); + + if (state == ConnectionState::SentHandshake && cmd == "DISPATCH" && evt == "READY") { + state = ConnectionState::Connected; + reconnectBackoff.reset(); + enqueue_ready(message); + return; + } + + if (evt == "ERROR") { + const json* data = find_member(message, "data"); + enqueue_error(data ? json_int_member(*data, "code", 0) : 0, + data ? json_string_member(*data, "message") : std::string{}); + } + } + + void process_close_frame(const std::string& payload, IpcConnection& connection, + ConnectionState& state, Backoff& reconnectBackoff, + std::chrono::steady_clock::time_point& nextConnect) { + int code = static_cast(DisconnectCode::PipeClosed); + std::string message = "Discord closed IPC connection"; + + try { + const json closePayload = json::parse(payload); + code = json_int_member(closePayload, "code", code); + const std::string closeMessage = json_string_member(closePayload, "message"); + if (!closeMessage.empty()) { + message = closeMessage; + } + } catch (const json::exception&) { + } + + handle_disconnect(connection, state, reconnectBackoff, nextConnect, code, message); + } + + void handle_disconnect(IpcConnection& connection, ConnectionState& state, + Backoff& reconnectBackoff, std::chrono::steady_clock::time_point& nextConnect, int code, + std::string_view message) { + const bool wasConnected = + state == ConnectionState::Connected || state == ConnectionState::SentHandshake; + connection.close(); + state = ConnectionState::Disconnected; + nextConnect = std::chrono::steady_clock::now() + reconnectBackoff.next_delay(); + if (wasConnected) { + enqueue_disconnected(code, message); + } + } + + void flush_pending_presence(IpcConnection& connection, int pid) { + std::optional presence; + bool shouldClear = false; + { + std::lock_guard lock(mutex); + if (hasQueuedPresence) { + presence = queuedPresence; + hasQueuedPresence = false; + } else if (clearRequested) { + shouldClear = true; + clearRequested = false; + } + } + + if (presence) { + if (!connection.write_frame( + make_set_activity_frame(next_nonce(), pid, std::move(presence)))) + { + requeue_presence(); + } + } else if (shouldClear) { + connection.write_frame(make_set_activity_frame(next_nonce(), pid, std::nullopt)); + } + } + + void flush_shutdown_clear(IpcConnection& connection, ConnectionState state, int pid) { + if (state != ConnectionState::Connected || !connection.is_open()) { + return; + } + + connection.write_frame(make_set_activity_frame(next_nonce(), pid, std::nullopt)); + const auto deadline = std::chrono::steady_clock::now() + kShutdownClearTimeout; + while (std::chrono::steady_clock::now() < deadline) { + Frame frame; + const auto status = connection.read_frame(frame); + if (status == IpcConnection::ReadStatus::None) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + continue; + } + if (status != IpcConnection::ReadStatus::Frame) { + break; + } + if (frame.opcode == Opcode::Ping) { + connection.write_frame({Opcode::Pong, frame.payload}); + } + } + } + + void requeue_presence() { + std::lock_guard lock(mutex); + if (queuedPresence) { + hasQueuedPresence = true; + } + } + + void enqueue_ready(const json& readyMessage) { + User user; + const auto data = readyMessage.find("data"); + if (data != readyMessage.end() && data->is_object()) { + const auto userIt = data->find("user"); + if (userIt != data->end() && userIt->is_object()) { + user.id = json_string_member(*userIt, "id"); + user.username = json_string_member(*userIt, "username"); + user.discriminator = json_string_member(*userIt, "discriminator"); + user.avatar = json_string_member(*userIt, "avatar"); + } + } + + std::lock_guard lock(mutex); + queuedEvents.push_back({QueuedEvent::Type::Ready, std::move(user)}); + } + + void enqueue_disconnected(int code, std::string_view message) { + std::lock_guard lock(mutex); + QueuedEvent event; + event.type = QueuedEvent::Type::Disconnected; + event.code = code; + event.message = message; + queuedEvents.push_back(std::move(event)); + } + + void enqueue_error(int code, std::string_view message) { + std::lock_guard lock(mutex); + QueuedEvent event; + event.type = QueuedEvent::Type::Error; + event.code = code; + event.message = message; + queuedEvents.push_back(std::move(event)); + } + + void log_waiting_for_discord_once() { + bool shouldLog = false; + { + std::lock_guard lock(mutex); + if (!sentInitialConnectLog) { + sentInitialConnectLog = true; + shouldLog = true; + } + } + if (shouldLog) { + DuskLog.info("Discord: Waiting for local Discord IPC"); + } + } + + std::mutex mutex; + std::condition_variable cv; + std::thread ioThread; + std::string applicationId; + EventHandlers handlers; + std::deque queuedEvents; + std::optional queuedPresence; + bool hasQueuedPresence = false; + bool clearRequested = false; + bool shouldRun = false; + bool sentInitialConnectLog = false; +}; + +Client& client() { + static Client sClient; + return sClient; +} + +} // namespace + +void initialize(std::string applicationId, EventHandlers handlers) { + client().initialize(std::move(applicationId), std::move(handlers)); +} + +void run_callbacks() { + client().run_callbacks(); +} + +void update_presence(Presence presence) { + client().update_presence(std::move(presence)); +} + +void clear_presence() { + client().clear_presence(); +} + +void shutdown() { + client().shutdown(); +} + +} // namespace dusk::discord::rpc + +#endif // DUSK_DISCORD diff --git a/src/dusk/discord.hpp b/src/dusk/discord.hpp new file mode 100644 index 0000000000..67f54ff2f3 --- /dev/null +++ b/src/dusk/discord.hpp @@ -0,0 +1,41 @@ +#pragma once + +#ifdef DUSK_DISCORD + +#include +#include +#include +#include + +namespace dusk::discord::rpc { + +struct User { + std::string id; + std::string username; + std::string discriminator; + std::string avatar; +}; + +struct Presence { + std::string state; + std::string details; + int64_t startTimestamp = 0; + std::string largeImageKey; + std::string largeImageText; +}; + +struct EventHandlers { + std::function ready; + std::function disconnected; + std::function error; +}; + +void initialize(std::string applicationId, EventHandlers handlers); +void run_callbacks(); +void update_presence(Presence presence); +void clear_presence(); +void shutdown(); + +} // namespace dusk::discord::rpc + +#endif // DUSK_DISCORD diff --git a/src/dusk/discord_presence.cpp b/src/dusk/discord_presence.cpp index 3b12075c8b..6b2c7c912b 100644 --- a/src/dusk/discord_presence.cpp +++ b/src/dusk/discord_presence.cpp @@ -1,38 +1,39 @@ -#ifdef DUSK_DISCORD_RPC +#ifdef DUSK_DISCORD #include "dusk/discord_presence.hpp" +#include "d/d_com_inf_game.h" +#include "discord.hpp" #include "dusk/logging.h" #include "dusk/main.h" #include "dusk/map_loader_definitions.h" -#include "d/d_com_inf_game.h" -#include "discord_rpc.h" #include "fmt/format.h" #include -#include #include +#include +#include -namespace dusk { -namespace discord { +namespace dusk::discord { static int64_t g_startTime = 0; static bool g_initialized = false; -static const char* APPLICATION_ID = "1495632471994405035"; +static constexpr const char* kApplicationId = "1495632471994405035"; -static void OnReady(const DiscordUser* user) { - DuskLog.info("Discord: Connected as {}", user->username); +static void on_ready(const rpc::User& user) { + DuskLog.info("Discord: Connected as {}", user.username); } -static void OnDisconnected(int errorCode, const char* message) { +static void on_disconnected(int errorCode, std::string_view message) { DuskLog.warn("Discord: Disconnected ({}: {})", errorCode, message); } -static void OnError(int errorCode, const char* message) { +static void on_error(int errorCode, std::string_view message) { DuskLog.warn("Discord: Error ({}: {})", errorCode, message); } -static const char* LookupMapName(const char* mapFile) { - if (!mapFile || mapFile[0] == '\0') return nullptr; +static const char* lookup_map_name(const char* mapFile) { + if (!mapFile || mapFile[0] == '\0') + return nullptr; for (const auto& region : gameRegions) { for (const auto& map : region.maps) { if (map.mapFile && strcmp(mapFile, map.mapFile) == 0) { @@ -43,80 +44,80 @@ static const char* LookupMapName(const char* mapFile) { return nullptr; } -void Initialize() { - g_startTime = static_cast( - std::chrono::duration_cast( - std::chrono::system_clock::now().time_since_epoch() - ).count() - ); +void initialize() { + g_startTime = std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch()) + .count(); - DiscordEventHandlers handlers{}; - handlers.ready = OnReady; - handlers.disconnected = OnDisconnected; - handlers.errored = OnError; - Discord_Initialize(APPLICATION_ID, &handlers, 0, nullptr); + rpc::EventHandlers handlers{}; + handlers.ready = on_ready; + handlers.disconnected = on_disconnected; + handlers.error = on_error; + rpc::initialize(kApplicationId, std::move(handlers)); g_initialized = true; DuskLog.info("Discord Rich Presence initialized"); } -void RunCallbacks() { - if (!g_initialized) return; - Discord_RunCallbacks(); +void run_callbacks() { + if (!g_initialized) + return; + rpc::run_callbacks(); } -void UpdatePresence() { - if (!g_initialized) return; +void update_presence() { + if (!g_initialized) + return; - static auto lastUpdate = std::chrono::steady_clock::time_point{}; + static auto sLastUpdate = std::chrono::steady_clock::time_point{}; const auto now = std::chrono::steady_clock::now(); - if (now - lastUpdate < std::chrono::seconds(15)) return; - lastUpdate = now; + if (now - sLastUpdate < std::chrono::seconds(15)) + return; + sLastUpdate = now; - static std::string detailsBuf; - static std::string stateBuf; + static std::string sDetailsBuf; + static std::string sStateBuf; - DiscordRichPresence presence{}; + rpc::Presence presence{}; presence.startTimestamp = g_startTime; presence.largeImageKey = "icon"; presence.largeImageText = "Dusk"; - if (dusk::IsGameLaunched) { + if (IsGameLaunched) { const char* stageName = dComIfGp_getLastPlayStageName(); // stageName is empty until a room is actually entered if (stageName[0] != '\0') { - const char* locationName = LookupMapName(stageName); + const char* locationName = lookup_map_name(stageName); if (locationName) { - detailsBuf = locationName; - } - else { - detailsBuf = "Twilight Princess"; + sDetailsBuf = locationName; + } else { + sDetailsBuf = "Twilight Princess"; } - presence.details = detailsBuf.c_str(); + presence.details = sDetailsBuf; - stateBuf = fmt::format(FMT_STRING("{}/{} \u2665 | {} Rupees"), + sStateBuf = fmt::format(FMT_STRING("{}/{} \u2665 | {} Rupees"), dComIfGs_getLife() / 4, dComIfGs_getMaxLife() / 5, dComIfGs_getRupee()); - presence.state = stateBuf.c_str(); + presence.state = sStateBuf; } } - Discord_UpdatePresence(&presence); + rpc::update_presence(std::move(presence)); DuskLog.debug("Discord Rich Presence sent"); } -void Shutdown() { - if (!g_initialized) return; - Discord_ClearPresence(); - Discord_Shutdown(); +void shutdown() { + if (!g_initialized) + return; + rpc::clear_presence(); + rpc::shutdown(); g_initialized = false; DuskLog.info("Discord Rich Presence shut down"); } -} // namespace discord -} // namespace dusk +} // namespace dusk::discord -#endif // DUSK_DISCORD_RPC +#endif // DUSK_DISCORD diff --git a/src/dusk/imgui/ImGuiAchievements.cpp b/src/dusk/imgui/ImGuiAchievements.cpp deleted file mode 100644 index 4b844b8386..0000000000 --- a/src/dusk/imgui/ImGuiAchievements.cpp +++ /dev/null @@ -1,228 +0,0 @@ -#include "ImGuiAchievements.hpp" -#include "ImGuiConfig.hpp" -#include "dusk/achievements.h" -#include "dusk/settings.h" -#include "fmt/format.h" -#include "imgui.h" - -namespace dusk { - -void ImGuiAchievements::notify(std::string name) { - if (m_notifyTimer <= 0.f) { - m_notifyName = std::move(name); - m_notifyTimer = NOTIFY_DURATION; - } else { - m_notifyQueue.push(std::move(name)); - } -} - -void ImGuiAchievements::showNotification() { - if (!getSettings().game.enableAchievementNotifications.getValue()) { - return; - } - if (m_notifyTimer <= 0.f) { - if (m_notifyQueue.empty()) { - return; - } - m_notifyName = std::move(m_notifyQueue.front()); - m_notifyQueue.pop(); - m_notifyTimer = NOTIFY_DURATION; - } - - m_notifyTimer -= ImGui::GetIO().DeltaTime; - - const float alpha = std::min({ - m_notifyTimer / NOTIFY_FADE_TIME, - (NOTIFY_DURATION - m_notifyTimer) / NOTIFY_FADE_TIME, - 1.0f - }); - - const ImGuiViewport* viewport = ImGui::GetMainViewport(); - const float padding = 12.0f; - ImGui::SetNextWindowPos( - ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - padding, viewport->WorkPos.y + padding), - ImGuiCond_Always, ImVec2(1.0f, 0.0f) - ); - - ImGui::SetNextWindowBgAlpha(alpha * 0.92f); - ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.06f, 0.01f, alpha * 0.92f)); - ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.1f, alpha)); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, alpha)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.0f, 10.0f)); - - constexpr ImGuiWindowFlags flags = - ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | - ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | - ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs; - - if (ImGui::Begin("##achievement_notify", nullptr, flags)) { - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.82f, 0.1f, alpha)); - ImGui::TextUnformatted("Achievement Unlocked!"); - ImGui::PopStyleColor(); - ImGui::Spacing(); - ImGui::TextUnformatted(m_notifyName.c_str()); - } - ImGui::End(); - - ImGui::PopStyleVar(2); - ImGui::PopStyleColor(3); -} - -void ImGuiAchievements::draw(bool& open) { - showNotification(); - - if (!open) { - return; - } - - ImGui::SetNextWindowSizeConstraints(ImVec2(640, 200), ImVec2(800, 900)); - ImGui::SetNextWindowSize(ImVec2(640, 480), ImGuiCond_FirstUseEver); - - if (!ImGui::Begin( - "Achievements", &open, - ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav) - ) - { - ImGui::End(); - return; - } - - const auto achievements = AchievementSystem::get().getAchievements(); - - int unlocked = 0; - for (const auto& a : achievements) { - if (a.unlocked) { - ++unlocked; - } - } - - ImGui::Text("%d / %d achievements unlocked", unlocked, (int)achievements.size()); - ImGui::SameLine(); - config::ImGuiCheckbox("Notifications", getSettings().game.enableAchievementNotifications); - ImGui::Separator(); - - static const struct { - AchievementCategory cat; - const char* label; - ImVec4 color; - } ACHIEVEMENT_CATEGORIES[] = { - {AchievementCategory::Story, "Story", ImVec4(1.0f, 0.82f, 0.1f, 1.0f)}, - {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::Glitched, "Glitched", ImVec4(0.75f, 0.4f, 1.0f, 1.0f)}, - }; - - const float footerHeight = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); - - if (ImGui::BeginTabBar("##achievement_tabs", ImGuiTabBarFlags_FittingPolicyScroll)) { - for (const auto& catInfo : ACHIEVEMENT_CATEGORIES) { - int catTotal = 0, catUnlocked = 0; - for (const auto& a : achievements) { - if (a.category == catInfo.cat) { - ++catTotal; - if (a.unlocked) { - ++catUnlocked; - } - } - } - if (catTotal == 0) { - continue; - } - - const std::string tabLabel = fmt::format("{} ({}/{})", catInfo.label, catUnlocked, catTotal); - - ImGui::PushStyleColor(ImGuiCol_Text, catInfo.color); - const bool tabOpen = ImGui::BeginTabItem(tabLabel.c_str()); - ImGui::PopStyleColor(); - - if (tabOpen) { - ImGui::BeginChild( - "##cat_list", - ImVec2(0, -footerHeight), - ImGuiChildFlags_None, - ImGuiWindowFlags_NoBackground - ); - - ImGui::Spacing(); - - for (const auto& a : achievements) { - if (a.category != catInfo.cat) { - continue; - } - ImGui::PushID(a.key); - - ImGui::PushStyleColor( - ImGuiCol_Text, - a.unlocked ? - ImVec4(1.0f, 0.65f, 0.15f, 1.0f) : - ImGui::GetStyleColorVec4(ImGuiCol_Text) - ); - - ImGui::TextUnformatted(a.name); - ImGui::PopStyleColor(); - - const char* statusLabel = a.unlocked ? "[Unlocked]" : "[Locked]"; - ImGui::SameLine( - ImGui::GetContentRegionMax().x - - ImGui::CalcTextSize(statusLabel).x - ); - - if (a.unlocked) { - ImGui::TextColored(ImVec4(0.2f, 0.8f, 0.2f, 1.0f), "%s", statusLabel); - } else { - ImGui::TextColored(ImVec4(0.8f, 0.2f, 0.2f, 1.0f), "%s", statusLabel); - } - - ImGui::TextDisabled("%s", a.description); - - if (a.isCounter) { - const float fraction = a.goal > 0 ? (float)(a.progress) / (float)(a.goal) : 1.0f; - const std::string overlay = fmt::format("{} / {}", a.progress, a.goal); - ImGui::PushStyleColor( - ImGuiCol_PlotHistogram, - a.unlocked ? - ImVec4(0.4f, 0.7f, 0.1f, 1.0f) : - ImVec4(0.2f, 0.45f, 0.8f, 1.0f) - ); - ImGui::ProgressBar(fraction, ImVec2(-1.0f, 0.0f), overlay.c_str()); - ImGui::PopStyleColor(); - } - - ImGui::Spacing(); - ImGui::PopID(); - } - - ImGui::EndChild(); - ImGui::EndTabItem(); - } - } - ImGui::EndTabBar(); - } - - ImGui::Separator(); - ImGui::Spacing(); - - if (ImGui::Button("Clear All Achievements")) { - ImGui::OpenPopup("##confirm_clear"); - } - - if (ImGui::BeginPopup("##confirm_clear")) { - ImGui::Text("Reset all achievement progress?"); - ImGui::Spacing(); - if (ImGui::Button("Yes, reset all")) { - AchievementSystem::get().clearAll(); - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - if (ImGui::Button("Cancel")) { - ImGui::CloseCurrentPopup(); - } - ImGui::EndPopup(); - } - - ImGui::End(); -} - -} // namespace dusk diff --git a/src/dusk/imgui/ImGuiAchievements.hpp b/src/dusk/imgui/ImGuiAchievements.hpp deleted file mode 100644 index 5ee77373fc..0000000000 --- a/src/dusk/imgui/ImGuiAchievements.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include - -namespace dusk { - -class ImGuiAchievements { -public: - void draw(bool& open); - void notify(std::string name); - -private: - void showNotification(); - - std::string m_notifyName; - float m_notifyTimer = 0.f; - std::queue m_notifyQueue; - static constexpr float NOTIFY_DURATION = 4.0f; - static constexpr float NOTIFY_FADE_TIME = 0.5f; -}; - -} // namespace dusk diff --git a/src/dusk/imgui/ImGuiAudio.cpp b/src/dusk/imgui/ImGuiAudio.cpp index 9abe4c5261..6bddc6f07c 100644 --- a/src/dusk/imgui/ImGuiAudio.cpp +++ b/src/dusk/imgui/ImGuiAudio.cpp @@ -1,5 +1,7 @@ #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" +#include +#include "JSystem/JAudio2/JAISeq.h" #include "JSystem/JAudio2/JAISeMgr.h" #include "JSystem/JAudio2/JAISeqMgr.h" #include "JSystem/JAudio2/JAIStreamMgr.h" @@ -15,6 +17,24 @@ static std::array lastResetCounts = {}; static bool sortUpdateCount = true; +static void DrawDirectionGauge(float pan, float dolby) { + constexpr float R = 20.0f; + constexpr float SIZE = R * 2.0f + 4.0f; + + ImVec2 origin = ImGui::GetCursorScreenPos(); + ImGui::Dummy(ImVec2(SIZE, SIZE)); + ImDrawList* dl = ImGui::GetWindowDrawList(); + ImVec2 c = ImVec2(origin.x + SIZE * 0.5f, origin.y + SIZE * 0.5f); + + dl->AddCircle(c, R, IM_COL32(90, 90, 90, 255), 32); + + float dx = (pan - 0.5f) * 2.0f; + float dy = dolby * 2.0f - 1.0f; + float len = sqrtf(dx * dx + dy * dy); + if (len > 1.0f) { dx /= len; dy /= len; } + dl->AddLine(c, ImVec2(c.x + dx * R, c.y + dy * R), IM_COL32(255, 200, 50, 255), 1.5f); +} + static void DisplayDspChannel(int i) { using namespace dusk::audio; @@ -52,8 +72,10 @@ static void DisplayDspChannel(int i) { auto fxMix = (channel.mAutoMixerFxMix >> 8) / 127.5f; auto volume = VolumeFromU16(channel.mAutoMixerVolume); auto pitch = channel.mPitch / 4096.0f; + DrawDirectionGauge(pan, dolby); + ImGui::SameLine(); ImGui::Text( - "Auto mixer active (pan: %f, dolby: %f, fx: %f, volume: %f, pitch %f)", + "pan: %.2f dolby: %.2f\nfx: %.2f vol: %.2f pitch: %.2f", pan, dolby, fxMix, volume, pitch); } else { ImGui::Text( @@ -183,6 +205,10 @@ static void ShowAllJAISes() { if (ImGui::Button("Pause All")) { category->pause(true); } + ImGui::SameLine(); + if (ImGui::Button("Resume All")) { + category->pause(false); + } for (auto seLink = category->getSeList()->getFirst(); seLink != nullptr; seLink = seLink->getNext()) { const auto se = seLink->getObject(); @@ -196,6 +222,33 @@ static void ShowAllJAISes() { } +static void ShowSeqTracks(JAISeq& seq) { + JASTrack& root = seq.inner_.outputTrack; + + for (int group = 0; group < 2; group++) { + JASTrack* groupTrack = root.getChild(group); + if (groupTrack == nullptr) { + continue; + } + + for (int j = 0; j < JASTrack::MAX_CHILDREN; j++) { + JASTrack* track = groupTrack->getChild(j); + if (track == nullptr) { + continue; + } + + int trackIdx = group * 16 + j; + char label[64]; + snprintf(label, sizeof(label), "Track %d (bank %hu, prog %hu)##%p", + trackIdx, track->getBankNumber(), track->getProgNumber(), track); + bool muted = track->mFlags.mute; + if (ImGui::Checkbox(label, &muted)) { + track->mute(muted); + } + } + } +} + static void ShowAllJAISeqs() { auto& mgr = *JAISeqMgr::getInstance(); @@ -206,6 +259,26 @@ static void ShowAllJAISeqs() { if (ImGui::Button("Unpause")) { mgr.pause(false); } + + ImGui::Text("Active sequences: %d", mgr.getNumActiveSeqs()); + + auto* seqList = mgr.getSeqList(); + for (auto* link = seqList->getFirst(); link != nullptr; link = link->getNext()) { + JAISeq* seq = link->getObject(); + if (seq == nullptr) { + continue; + } + + char buf[32]; + snprintf(buf, sizeof(buf), "%p", seq); + + if (ImGui::BeginChild(buf, ImVec2(), ImGuiChildFlags_Border | ImGuiChildFlags_AutoResizeY)) { + ImGui::Text("Seq [%p]", seq); + ShowSeqTracks(*seq); + } + + ImGui::EndChild(); + } } void dusk::ImGuiMenuTools::ShowAudioDebug() { diff --git a/src/dusk/imgui/ImGuiCameraOverlay.cpp b/src/dusk/imgui/ImGuiCameraOverlay.cpp index aa3d1ac093..2a39ef85eb 100644 --- a/src/dusk/imgui/ImGuiCameraOverlay.cpp +++ b/src/dusk/imgui/ImGuiCameraOverlay.cpp @@ -1,9 +1,12 @@ #include "f_op/f_op_camera_mng.h" #include "SSystem/SComponent/c_xyz.h" +#include "d/d_com_inf_game.h" #include "imgui.h" +#include "ImGuiConfig.hpp" #include "ImGuiConsole.hpp" #include "ImGuiMenuTools.hpp" +#include "dusk/settings.h" namespace dusk { void ImGuiMenuTools::ShowCameraOverlay() { @@ -46,70 +49,25 @@ namespace dusk { ImGui::InputFloat("Camera FOV", &dCam->mFovy); - ImGui::SeparatorText("Free-look Data"); + ImGui::SeparatorText("Options"); - static float eyeYawDeg = 0.0f; - static float moveSpeed = 5000.0f; - static float rotSpeed = 5.0f; - static cXyz freeLookPos = cXyz::Zero; - static bool freeLookActive = false; - - bool changed = false; - - if (ImGui::IsKeyDown(ImGuiKey_LeftArrow)) { - eyeYawDeg += rotSpeed; - if (eyeYawDeg >= 360.0f) - eyeYawDeg -= 360.0f; - - changed = true; + bool eventRunning = (dComIfGp_event_runCheck() || dComIfGp_isPauseFlag()) && !getSettings().game.debugFlyCam; + if (eventRunning) { + ImGui::BeginDisabled(); } - else if (ImGui::IsKeyDown(ImGuiKey_RightArrow)) { - eyeYawDeg -= rotSpeed; - if (eyeYawDeg < 0.0f) - eyeYawDeg += 360.0f; - - changed = true; + config::ImGuiCheckbox("Fly Mode", getSettings().game.debugFlyCam); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + if (eventRunning) { + ImGui::SetTooltip("Cannot enable while paused or during an active event."); + } else { + ImGui::SetTooltip("Detach camera and fly freely.\n" + "Left stick: move, C-stick: look\n" + "L/R triggers: up/down, Z: fast"); + } } - cSAngle yawAngle = cSAngle(eyeYawDeg); - cXyz frontDir = cXyz(yawAngle.Sin(), 0.0f, yawAngle.Cos()); - - if (ImGui::IsKeyDown(ImGuiKey_UpArrow)) { - freeLookPos -= frontDir * moveSpeed * ImGui::GetIO().DeltaTime; - changed = true; + if (eventRunning) { + ImGui::EndDisabled(); } - else if (ImGui::IsKeyDown(ImGuiKey_DownArrow)) { - freeLookPos += frontDir * moveSpeed * ImGui::GetIO().DeltaTime; - changed = true; - } - - if (ImGui::IsKeyDown(ImGuiKey_LeftShift)) { - freeLookPos += cXyz::BaseY * moveSpeed * ImGui::GetIO().DeltaTime; - changed = true; - } - - if (ImGui::IsKeyDown(ImGuiKey_LeftCtrl)) { - freeLookPos -= cXyz::BaseY * moveSpeed * ImGui::GetIO().DeltaTime; - changed = true; - } - - if (!freeLookActive && changed) { - freeLookPos += dCam->Center(); - freeLookActive = true; - } - - if (ImGui::IsKeyDown(ImGuiKey_R)) { - freeLookPos = cXyz::Zero; - freeLookActive = false; - } - - if (freeLookActive) { - dCam->Reset(freeLookPos, freeLookPos + (frontDir * 100.0f)); - } - - ImGui::InputFloat("Free-look Yaw", &eyeYawDeg); - ImGui::InputFloat3("Free-look Position", &freeLookPos.x); - ImGui::InputFloat("Free-look Move Speed", &moveSpeed); - ImGui::InputFloat("Free-look Rotation Speed", &rotSpeed); ShowCornerContextMenu(m_cameraOverlayCorner, 0); diff --git a/src/dusk/imgui/ImGuiConsole.cpp b/src/dusk/imgui/ImGuiConsole.cpp index c8a9fa86aa..355ef14450 100644 --- a/src/dusk/imgui/ImGuiConsole.cpp +++ b/src/dusk/imgui/ImGuiConsole.cpp @@ -10,17 +10,20 @@ #include "fmt/format.h" #include "ImGuiConsole.hpp" +#include "dusk/ui/preset.hpp" +#include "dusk/ui/ui.hpp" #include "JSystem/JUtility/JUTGamePad.h" -#include "SDL3/SDL_events.h" #include "SDL3/SDL_mouse.h" -#include "aurora/lib/window.hpp" #include "dusk/achievements.h" #include "dusk/audio/DuskAudioSystem.h" #include "dusk/config.hpp" #include "dusk/dusk.h" #include "dusk/frame_interpolation.h" +#include "dusk/livesplit.h" #include "dusk/main.h" #include "dusk/settings.h" +#include "f_pc/f_pc_manager.h" +#include "f_pc/f_pc_name.h" #include "m_Do/m_Do_controller_pad.h" #include "m_Do/m_Do_main.h" #include "tracy/Tracy.hpp" @@ -34,14 +37,6 @@ using namespace std::string_literals; using namespace std::string_view_literals; namespace { -ImVec2 TouchEventToScreenPos(const SDL_TouchFingerEvent& touch) { - const AuroraWindowSize size = aurora::window::get_window_size(); - return ImVec2{ - touch.x * static_cast(size.width), - touch.y * static_cast(size.height), - }; -} - ImGuiWindow* FindDragScrollWindow(ImGuiWindow* window) { while (window != nullptr) { const bool canScrollX = window->ScrollMax.x > 0.0f; @@ -65,6 +60,10 @@ namespace dusk { ImGui::TextUnformatted(text.data(), text.data() + text.size()); } + void DuskToast(std::string_view message, float duration) { + g_imguiConsole.AddToast(message, duration); + } + void ImGuiTextCenter(std::string_view text) { ImGui::NewLine(); float fontSize = ImGui::CalcTextSize( @@ -236,48 +235,7 @@ namespace dusk { ImGuiConsole::ImGuiConsole() {} void ImGuiConsole::HandleSDLEvent(const SDL_Event& event) { - if (!IsGameLaunched) { - return; - } - - switch (event.type) { - case SDL_EVENT_FINGER_DOWN: - if (!m_touchTapActive) { - m_touchTapActive = true; - m_touchTapMoved = false; - m_touchTapFingerId = event.tfinger.fingerID; - m_touchTapStartPos = TouchEventToScreenPos(event.tfinger); - } - break; - case SDL_EVENT_FINGER_MOTION: - if (m_touchTapActive && m_touchTapFingerId == event.tfinger.fingerID) { - const auto currentPos = TouchEventToScreenPos(event.tfinger); - const auto delta = currentPos - m_touchTapStartPos; - if (ImLengthSqr(delta) > 144.0f) { - m_touchTapMoved = true; - } - } - break; - case SDL_EVENT_FINGER_UP: - if (m_touchTapActive && m_touchTapFingerId == event.tfinger.fingerID) { - const bool shouldToggle = - !m_touchTapMoved && (m_isHidden || !ImGui::GetIO().WantCaptureMouse); - m_touchTapActive = false; - m_touchTapMoved = false; - if (shouldToggle) { - m_isHidden = !m_isHidden; - getSettings().backend.duskMenuOpen.setValue(!m_isHidden); - Save(); - } - } - break; - case SDL_EVENT_FINGER_CANCELED: - m_touchTapActive = false; - m_touchTapMoved = false; - break; - default: - break; - } + (void)event; } void ImGuiConsole::UpdateSettings() { @@ -305,7 +263,8 @@ namespace dusk { } } - if ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && + if (!fpcM_SearchByName(fpcNm_LOGO_SCENE_e) && + (ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && ImGui::IsKeyPressed(ImGuiKey_R)) { JUTGamePad::C3ButtonReset::sResetSwitchPushing = true; @@ -315,23 +274,14 @@ namespace dusk { ImGuiMenuGame::ToggleFullscreen(); } - if (ImGui::IsKeyPressed(ImGuiKey_Escape) && getSettings().video.enableFullscreen) { - ImGuiMenuGame::ToggleFullscreen(); - } + // if (!dusk::IsGameLaunched) { + // m_preLaunchWindow.draw(); + // } - if (!dusk::IsGameLaunched) { - m_preLaunchWindow.draw(); - } - - m_isHidden = !getSettings().backend.duskMenuOpen; - bool showMenu = !dusk::IsGameLaunched || !CheckMenuViewToggle(ImGuiKey_F1, m_isHidden); - if (dusk::IsGameLaunched) { - const bool menuOpen = !m_isHidden; - if (getSettings().backend.duskMenuOpen != menuOpen) { - getSettings().backend.duskMenuOpen.setValue(menuOpen); - Save(); - } + if (ImGui::GetIO().KeyShift && ImGui::IsKeyPressed(ImGuiKey_F1)) { + m_isHidden = !m_isHidden; } + bool showMenu = !m_isHidden; // The menu bar renders with ImGuiCol_WindowBg behind it. We just want ImGuiCol_MenuBarBg, // so make the window bg fully transparent temporarily @@ -354,24 +304,32 @@ namespace dusk { } ImGui::PopStyleColor(); - if (!getSettings().backend.wasPresetChosen) { - m_firstRunPreset.draw(); - return; - } - if (dusk::IsGameLaunched && !m_isLaunchInitialized) { - m_toasts.emplace_back(ImGui::GetIO().MouseSource == ImGuiMouseSource_TouchScreen ? - "Tap to toggle menu"s : + AddToast(ImGui::GetIO().MouseSource == ImGuiMouseSource_TouchScreen ? + "3-finger tap to toggle menu"s : "Press F1 to toggle menu"s, - 2.5f); + 4.f); m_isLaunchInitialized = true; + if (getSettings().game.liveSplitEnabled) { + dusk::speedrun::connectLiveSplit(); + } } UpdateDragScroll(); m_menuGame.windowControllerConfig(); m_menuGame.windowInputViewer(); - if (dusk::IsGameLaunched) { + m_menuGame.drawSpeedrunTimerOverlay(); + + if (getSettings().game.liveSplitEnabled) { + dusk::speedrun::updateLiveSplit(); + if (dusk::speedrun::consumeConnectedEvent()) + AddToast("LiveSplit connected"); + else if (dusk::speedrun::consumeDisconnectedEvent()) + AddToast("LiveSplit disconnected"); + } + + if (dusk::IsGameLaunched && !dusk::getSettings().game.speedrunMode) { m_menuTools.ShowDebugOverlay(); m_menuTools.ShowCameraOverlay(); m_menuTools.ShowProcessManager(); @@ -382,9 +340,9 @@ namespace dusk { m_menuTools.ShowPlayerInfo(); m_menuTools.ShowAudioDebug(); m_menuTools.ShowSaveEditor(); + m_menuTools.ShowStateShare(); } - m_menuTools.ShowStateShare(); - m_menuTools.ShowAchievements(); + m_menuTools.showAchievementNotification(); DuskDebugPad(); // temporary, remove later // Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds. @@ -556,6 +514,10 @@ namespace dusk { return false; } + void ImGuiConsole::AddToast(std::string_view message, float duration) { + m_toasts.emplace_back(std::string(message), duration); + } + void ImGuiConsole::ShowToasts() { if (m_toasts.empty()) { return; diff --git a/src/dusk/imgui/ImGuiConsole.hpp b/src/dusk/imgui/ImGuiConsole.hpp index de4e66c7d6..1755c02856 100644 --- a/src/dusk/imgui/ImGuiConsole.hpp +++ b/src/dusk/imgui/ImGuiConsole.hpp @@ -6,9 +6,7 @@ #include #include -#include -#include "ImGuiFirstRunPreset.hpp" #include "ImGuiMenuGame.hpp" #include "ImGuiMenuTools.hpp" #include "ImGuiPreLaunchWindow.hpp" @@ -27,6 +25,7 @@ public: void PostDraw(); static bool CheckMenuViewToggle(ImGuiKey key, bool& active); + void AddToast(std::string_view message, float duration = 3.f); private: struct Toast { @@ -41,15 +40,10 @@ private: bool m_isHidden = true; bool m_isLaunchInitialized = false; - bool m_touchTapActive = false; - bool m_touchTapMoved = false; - SDL_FingerID m_touchTapFingerId = 0; - ImVec2 m_touchTapStartPos = {}; ImGuiWindow* m_dragScrollWindow = nullptr; ImVec2 m_dragScrollLastMousePos = {}; std::deque m_toasts; - ImGuiFirstRunPreset m_firstRunPreset; ImGuiMenuGame m_menuGame; ImGuiPreLaunchWindow m_preLaunchWindow; @@ -70,6 +64,7 @@ std::string BytesToString(size_t bytes); void SetOverlayWindowLocation(int corner); bool ShowCornerContextMenu(int& corner, int avoidCorner); void ImGuiStringViewText(std::string_view text); +void DuskToast(std::string_view message, float duration = 3.f); void ImGuiBeginGroupPanel(const char* name, const ImVec2& size); void ImGuiEndGroupPanel(); void ImGuiTextCenter(std::string_view text); diff --git a/src/dusk/imgui/ImGuiEventFlags.hpp b/src/dusk/imgui/ImGuiEventFlags.hpp index 694336da03..9928690b9b 100644 --- a/src/dusk/imgui/ImGuiEventFlags.hpp +++ b/src/dusk/imgui/ImGuiEventFlags.hpp @@ -3,6 +3,9 @@ #include #include +#include +#include +#include struct duskImguiEventFlagEntry { uint8_t byteIndex; @@ -839,4 +842,2555 @@ inline const MultiBitEventFlag duskImguiSwappedU16Events[] = { { 0xfd, "Rupees owed to Trill" }, }; +struct EventAreaFlags +{ + uint8_t byteIndex; + uint8_t bitIndex; + uint16_t flagID; + std::string description; +}; + +inline EventAreaFlags eventAreaFlagsAG[] = +{ + { 0x00, 0x80, 0x0080, "Ooccoo Map Flag" }, + { 0x00, 0x40, 0x0040, "fourth small chest epic spinner room" }, + { 0x00, 0x20, 0x0020, "third small chest epic spinner room" }, + { 0x00, 0x10, 0x0010, "first small chest epic spinner room" }, + { 0x00, 0x08, 0x0008, "second small chest epic spinner room" }, + { 0x00, 0x04, 0x0004, "PoH first big chest epic spinner room" }, + { 0x00, 0x02, 0x0002, "small chest first west room" }, + { 0x00, 0x01, 0x0001, "small key big chest elevator room B1" }, + { 0x01, 0x80, 0x0180, "small key big chest first room" }, + { 0x01, 0x20, 0x0120, "small key small chest fouth east room" }, + { 0x01, 0x10, 0x0110, "big key chest" }, + { 0x01, 0x08, 0x0108, "PoH right big chest main room" }, + { 0x01, 0x04, 0x0104, "map left big chest main room" }, + { 0x01, 0x02, 0x0102, "west small chest in second west room" }, + { 0x01, 0x01, 0x0101, "north east small chest in second west room" }, + { 0x02, 0x08, 0x0208, "Spinner big chest" }, + { 0x03, 0x40, 0x0340, "small key small chest first east room B1" }, + { 0x03, 0x20, 0x0320, "small key big chest second east room 2F" }, + { 0x03, 0x10, 0x0310, "compass big chest second east room 2F" }, + { 0x03, 0x08, 0x0308, "big chest first west room" }, + { 0x08, 0x80, 0x0880, "killed second poe (triggers flame getting lit cs)" }, + { 0x08, 0x40, 0x0840, "killed first poe" }, + { 0x08, 0x08, 0x0808, "open gate to spinner in mini-boss room cs" }, + { 0x08, 0x04, 0x0804, "unlock exit in mini-boss room (despawns mini-boss)" }, + { 0x08, 0x01, 0x0801, "unlocked boss door" }, + { 0x09, 0x10, 0x0910, "started pulling chain in second east room 1F (stays set)" }, + { 0x09, 0x04, 0x0904, "stairs to lower east room appeared in main room" }, + { 0x09, 0x02, 0x0902, "risen tracks on pillar before boss" }, + { 0x09, 0x01, 0x0901, "unlocked door in second west room" }, + { 0x0A, 0x10, 0x0A10, "killed second poe (duplicate)" }, + { 0x0A, 0x02, 0x0A02, "Tiny bugs spawn in first room small key area (duplicate)" }, + { 0x0A, 0x01, 0x0A01, "Tiny bugs spawn in first room small key area" }, + { 0x0B, 0x80, 0x0B80, "stairs to lower west room appeared in main room cs" }, + { 0x0B, 0x20, 0x0B20, "turn walls in third room 2B cs" }, + { 0x0B, 0x10, 0x0B10, "turn walls in third room 2B" }, + { 0x0B, 0x08, 0x0B08, "open gates in first room" }, + { 0x0B, 0x04, 0x0B04, "open gates in first room cs" }, + { 0x0B, 0x01, 0x0B01, "Stallord Phase 2" }, + { 0x0C, 0x80, 0x0C80, "killed third poe (triggers flame getting lit cs)" }, + { 0x0C, 0x40, 0x0C40, "Main Hall Dig Spot" }, + { 0x0C, 0x01, 0x0C01, "turned walls in first east room (stays set)" }, + { 0x0D, 0x02, 0x0D02, "turned slab in first east room B1 (stays set)" }, + { 0x0E, 0x10, 0x0E10, "Intro Cutscene" }, + { 0x0F, 0x80, 0x0F80, "unlocked door in second east room 2F" }, + { 0x0F, 0x40, 0x0F40, "open poe door once all poe flames are present (removes poe bodies)" }, + { 0x0F, 0x20, 0x0F20, "poe flame top left present" }, + { 0x0F, 0x10, 0x0F10, "poe flame bottom right present" }, + { 0x0F, 0x08, 0x0F08, "poe flame top right present" }, + { 0x0F, 0x04, 0x0F04, "poe flame bottom left present" }, + { 0x0F, 0x02, 0x0F02, "killed fourth poe (triggers flame getting lit cs)" }, + { 0x0F, 0x01, 0x0F01, "flame from third poe leaving lantern cs" }, + { 0x10, 0x40, 0x1040, "killed third poe (duplicate)" }, + { 0x10, 0x20, 0x1020, "flame from second poe leaving lantern cs" }, + { 0x10, 0x10, 0x1010, "open walls in third east room" }, + { 0x10, 0x08, 0x1008, "unlocked door in elevator room 2B" }, + { 0x10, 0x04, 0x1004, "unlocked door in first room" }, + { 0x10, 0x02, 0x1002, "killed stalfos in second west room" }, + { 0x11, 0x80, 0x1180, "killed gibdo in first east room B1" }, + { 0x11, 0x20, 0x1120, "reach boss door (unset once you enter)" }, + { 0x11, 0x04, 0x1104, "killed third poe (duplicate)" }, + { 0x11, 0x02, 0x1102, "unlock door in second room" }, + { 0x11, 0x01, 0x1101, "lit right torch in second room" }, + { 0x12, 0x80, 0x1280, "lit left torch in second room" }, + { 0x12, 0x40, 0x1240, "close spinner slot for walls in third room 2B" }, + { 0x12, 0x20, 0x1220, "unlocked door in first east room 1F" }, + { 0x12, 0x08, 0x1208, "risen tracks on pillar before boss cs" }, + { 0x12, 0x04, 0x1204, "pushed block in first west room" }, + { 0x12, 0x01, 0x1201, "taken down the cube in the second east room 1F" }, + { 0x13, 0x80, 0x1380, "placed cube at the right spot in the second east room 1F" }, + { 0x13, 0x40, 0x1340, "unlock door in fouth east room" }, + { 0x13, 0x20, 0x1320, "close poe door (unsets after lighting torches)" }, + { 0x13, 0x08, 0x1308, "main room poes taking flames cs" }, + { 0x14, 0x10, 0x1410, "explored first room" }, + { 0x14, 0x08, 0x1408, "open exit door in boss room" }, + { 0x15, 0x80, 0x1580, "Midna Text after defeating boss" }, + { 0x15, 0x08, 0x1508, "killed first stalfos in room before mini-boss" }, + { 0x15, 0x04, 0x1504, "killed second stalfos in room before mini-boss" }, + { 0x15, 0x02, 0x1502, "killed third stalfos in room before mini-boss" }, + { 0x15, 0x01, 0x1501, "extend platform to exit in boss room" }, + { 0x16, 0x80, 0x1680, "open gate to spinner in mini-boss room cs" }, + { 0x16, 0x40, 0x1640, "open gate to spinner in mini-boss room" }, + { 0x16, 0x10, 0x1610, "open wall in third west room cs (unset once you leave)" }, + { 0x16, 0x08, 0x1608, "pulled chain in main room" }, + { 0x16, 0x04, 0x1604, "pulled the chain in first room (triggers cs)" }, + { 0x16, 0x02, 0x1602, "open wall in third west room" }, + { 0x17, 0x40, 0x1740, "killed gibdos in third east room" }, + { 0x17, 0x20, 0x1720, "Ooccoo Rescued" }, + { 0x17, 0x10, 0x1710, "opened gates in room before mini-boss" }, + { 0x17, 0x08, 0x1708, "killed fourth poe (duplicate)" }, + { 0x17, 0x02, 0x1702, "dig spot in third east room" }, + { 0x17, 0x01, 0x1701, "Spinner big chest (set after)" }, +}; + +inline EventAreaFlags eventAreaFlagsCastleTown[] = +{ + { 0x03, 0x02, 0x0302, "small chest on doctor's balcony" }, + { 0x06, 0x10, 0x0610, "tear of light next to Telma's bar" }, + { 0x08, 0x80, 0x0880, "killed poe in Jovani's house (set if you dig into the house)" }, + { 0x08, 0x04, 0x0804, "explored 3nd room Jovani-Sewers " }, + { 0x08, 0x02, 0x0802, "explored 2nd room Jovani-Sewers " }, + { 0x08, 0x01, 0x0801, "?" }, + { 0x09, 0x80, 0x0980, "?" }, + { 0x09, 0x40, 0x0940, "?" }, + { 0x09, 0x20, 0x0920, "?" }, + { 0x09, 0x02, 0x0902, "intro cs twlight" }, + { 0x09, 0x01, 0x0901, "midna text after listening to Telma and Ilia spirits during twilight" }, + { 0x0A, 0x80, 0x0A80, "midna breaking castle barrier cs" }, + { 0x0A, 0x20, 0x0A20, "warp you to Lanayru spring (trigger twilight end)" }, + { 0x0A, 0x10, 0x0A10, "fan girl text next to star tent" }, + { 0x0A, 0x04, 0x0A04, "star tent intro cs" }, + { 0x0A, 0x02, 0x0A02, "invisible wall behind doctor gone" }, + { 0x0A, 0x01, 0x0A01, "?" }, + { 0x0B, 0x80, 0x0B80, "water is back in town (twilight)" }, + { 0x0B, 0x40, 0x0B40, "explored east alley that leads south" }, + { 0x0B, 0x20, 0x0B20, "star tent has double clawshot mini-game" }, + { 0x0B, 0x10, 0x0B10, "explored area with star tent" }, + { 0x0B, 0x08, 0x0B08, "explored alley with entrance to Jovani's house" }, + { 0x0B, 0x04, 0x0B04, "explored alley with entrance to agitha's house" }, + { 0x0B, 0x02, 0x0B02, "explored area with entrance to Telma's bar" }, + { 0x0B, 0x01, 0x0B01, "STAR 1 Completed" }, + { 0x0D, 0x04, 0x0D04, "map marker hero's shade" }, + { 0x0E, 0x10, 0x0E10, "malo mart posters on shop" }, + { 0x0E, 0x08, 0x0E08, "magic armor bought malo mart" }, + { 0x0E, 0x04, 0x0E04, "?" }, + { 0x0E, 0x02, 0x0E02, "enter Telma's bar from top intro cs" }, + { 0x0E, 0x01, 0x0E01, "first right door to castle pushed once (default)" }, + { 0x0F, 0x80, 0x0F80, "first left door to castle pushed once (default)" }, + { 0x0F, 0x40, 0x0F40, "first right door to castle pushed twice" }, + { 0x0F, 0x20, 0x0F20, "first right door to castle half closed" }, + { 0x0F, 0x10, 0x0F10, "first left door to castle closed" }, + { 0x0F, 0x08, 0x0F08, "first left door to castle pushed twice" }, + { 0x0F, 0x04, 0x0F04, "Jovani's house intro cs" }, + { 0x0F, 0x02, 0x0F02, "Jovani text after killing poe" }, + { 0x0F, 0x01, 0x0F01, "open path to sewers (Jovani's house)" }, + { 0x10, 0x80, 0x1080, "?" }, + { 0x16, 0x80, 0x1680, "killed light bug next to Telma's bar" }, + { 0x17, 0x08, 0x1708, "Gengle free (Jovani's house)" }, + { 0x17, 0x04, 0x1704, "Midna opens map to look for the last light bug" }, +}; + +inline EventAreaFlags eventAreaFlagsCitS[] = +{ + { 0x00, 0x08, 0x0008, "south east underwater big chest outside shop" }, + { 0x00, 0x04, 0x0004, "big chest east wing second room 2F" }, + { 0x00, 0x02, 0x0002, "big chest west wing main room 2F" }, + { 0x00, 0x01, 0x0001, "small chest main room 4F outside" }, + { 0x01, 0x80, 0x0180, "small chest west wing north room 2F" }, + { 0x01, 0x40, 0x0140, "center small chest west wing main room 1F" }, + { 0x01, 0x20, 0x0120, "small chest west wing main room B1" }, + { 0x01, 0x10, 0x0110, "north east small chest west wing main room 1F" }, + { 0x01, 0x08, 0x0108, "small chest east wing second room 1F" }, + { 0x01, 0x04, 0x0104, "small chest east wing second room 2F" }, + { 0x01, 0x02, 0x0102, "big chest ouside north wing" }, + { 0x02, 0x80, 0x0280, "small chest west wing main room 3F" }, + { 0x02, 0x40, 0x0240, "small chest main room 3F inside" }, + { 0x02, 0x20, 0x0220, "big key chest" }, + { 0x02, 0x10, 0x0210, "big chest main room 4F outside" }, + { 0x02, 0x08, 0x0208, "small chest west wing main room 2F" }, + { 0x02, 0x02, 0x0202, "PoH big chest west wing main room 3F" }, + { 0x03, 0x80, 0x0380, "small chest west wing north room 3F" }, + { 0x03, 0x40, 0x0340, "PoH big chest west wing north room 2F" }, + { 0x03, 0x20, 0x0320, "small key big chest west wing main room 1F" }, + { 0x03, 0x10, 0x0310, "compass big chest east wing first room B1" }, + { 0x03, 0x08, 0x0308, "south west underwater big chest outside shop" }, + { 0x03, 0x04, 0x0304, "map big chest east wing fourth room 1F" }, + { 0x03, 0x01, 0x0301, "double clawshot big chest east wing fifth room B3" }, + { 0x08, 0x80, 0x0880, "turn on wind in east wing second room 2F" }, + { 0x08, 0x40, 0x0840, "open gate in east wing second room 2F" }, + { 0x08, 0x20, 0x0820, "explored west wing main room 1F (unset if you enter main room)" }, + { 0x08, 0x10, 0x0810, "explored east wing first room 1F (unset if you enter main room)" }, + { 0x08, 0x04, 0x0804, "killed big baba west wing north room 1F" }, + { 0x08, 0x02, 0x0802, "double clawshot big chest east wing fifth room B3 (set after)" }, + { 0x08, 0x01, 0x0801, "west bridge broken" }, + { 0x09, 0x80, 0x0980, "east wing fifth room B3 intro cs" }, + { 0x09, 0x40, 0x0940, "open gate in east wing third room 2F" }, + { 0x09, 0x20, 0x0920, "spawn baba serpants under east bridge" }, + { 0x09, 0x08, 0x0908, "extended east bridge (spinner slot needs to be closed)" }, + { 0x09, 0x04, 0x0904, "extended west bridge (spinner slot needs to be closed)" }, + { 0x09, 0x02, 0x0902, "small key big chest west wing main room 1F (set after)" }, + { 0x0A, 0x80, 0x0A80, "explored main room 1F" }, + { 0x0A, 0x20, 0x0A20, "turn on wind in east wing third room 2F" }, + { 0x0A, 0x10, 0x0A10, "open door in east wing third room 1F" }, + { 0x0A, 0x04, 0x0A04, "fan on ceiling of main room active" }, + { 0x0A, 0x02, 0x0A02, "open gate in east wing fourth room 1F" }, + { 0x0B, 0x80, 0x0B80, "unlock boss door" }, + { 0x0B, 0x40, 0x0B40, "unlock east bridge door 1F" }, + { 0x0B, 0x08, 0x0B08, "east bridge extended (close east spinner slot)" }, + { 0x0B, 0x04, 0x0B04, "west bridge extended (close west spinner slot)" }, + { 0x0B, 0x02, 0x0B02, "open gate outside shop" }, + { 0x0C, 0x08, 0x0C08, "north wing main room intro cs" }, + { 0x0C, 0x04, 0x0C04, "east wing fourth room 2F intro cs" }, + { 0x0C, 0x02, 0x0C02, "went beyond first gate outside shop intro cs" }, + { 0x0C, 0x01, 0x0C01, "Intro CS" }, + { 0x0D, 0x80, 0x0D80, "killed left dynalfos in east wing third room 1F" }, + { 0x0D, 0x40, 0x0D40, "killed right dynalfos in east wing third room 1F" }, + { 0x0D, 0x20, 0x0D20, "?" }, + { 0x0D, 0x10, 0x0D10, "west bridge destroyed cs (triggered when you have a key)" }, + { 0x0D, 0x08, 0x0D08, "killed helmasaurus in main room 1F" }, + { 0x0D, 0x04, 0x0D04, "west bridge extented cs" }, + { 0x0D, 0x02, 0x0D02, "east bridge extented cs" }, + { 0x0E, 0x01, 0x0E01, "small key big chest west wing main room 1F (set after)" }, + { 0x0F, 0x80, 0x0F80, "exited dungeon with midna warp" }, + { 0x0F, 0x40, 0x0F40, "exited dungeon with midna warp" }, + { 0x0F, 0x20, 0x0F20, "save promt after boss" }, + { 0x0F, 0x10, 0x0F10, "open gate in west wing main room B2" }, + { 0x0F, 0x08, 0x0F08, "killed left aeralfos in north wing main room 1F" }, + { 0x0F, 0x04, 0x0F04, "killed right aeralfos in north wing main room 1F" }, + { 0x0F, 0x02, 0x0F02, "open gate in east wing fifth room B3" }, + { 0x0F, 0x01, 0x0F01, "stop fan in east wing fifth room B3" }, + { 0x10, 0x80, 0x1080, "killed third baba serpant under east bridge" }, + { 0x10, 0x10, 0x1010, "killed third tile worm in east wing second room 1F" }, + { 0x10, 0x08, 0x1008, "killed second tile worm in east wing second room 1F" }, + { 0x10, 0x04, 0x1004, "killed first tile worm in east wing second room 1F" }, + { 0x11, 0x20, 0x1120, "killed poe in main room 4F outside" }, + { 0x11, 0x10, 0x1110, "killed poe in west wing main room 2F" }, + { 0x12, 0x20, 0x1220, "small key big chest west wing main room 1F (set after)" }, + { 0x12, 0x02, 0x1202, "latched on to first pillar in east wing first room B2" }, + { 0x13, 0x40, 0x1340, "open door in east wing fifth room B3" }, + { 0x13, 0x10, 0x1310, "?" }, + { 0x15, 0x10, 0x1510, "activate north path fan " }, + { 0x17, 0x40, 0x1740, "killed east dynalfos in main room 3F outside" }, + { 0x17, 0x20, 0x1720, "killed north dynalfos in main room 3F outside" }, + { 0x17, 0x10, 0x1710, "killed dynalfos in west wing north room 3F" }, + { 0x17, 0x08, 0x1708, "killed first baba serpent in path to east spinner slot" }, + { 0x17, 0x04, 0x1704, "killed second baba serpent in path to east spinner slot" }, + { 0x17, 0x02, 0x1702, "killed first baba serpant under east bridge" }, + { 0x17, 0x01, 0x1701, "killed second baba serpant under east bridge" }, +}; + +inline EventAreaFlags eventAreaFlagsCoO[] = +{ + { 0x03, 0x01, 0x0301, "big chest Lanayru Ice Puzzle" }, + { 0x04, 0x80, 0x0480, "big chest right right right path Eldin Long Cave" }, + { 0x04, 0x40, 0x0440, "big chest right right left right path Eldin Long Cave" }, + { 0x04, 0x20, 0x0420, "small chest left right path Eldin Long Cave" }, + //{ 0x08, 0x80, 0x0880, "position of block 1 puzzle 2 Lanayru Ice Puzzle" }, + //{ 0x08, 0x40, 0x0840, "position of block 1 puzzle 2 Lanayru Ice Puzzle" }, + //{ 0x08, 0x20, 0x0820, "position of block 1 puzzle 2 Lanayru Ice Puzzle" }, + //{ 0x08, 0x10, 0x0810, "position of block 1 puzzle 2 Lanayru Ice Puzzle" }, + //{ 0x08, 0x08, 0x0808, "position of block 2 puzzle 2 Lanayru Ice Puzzle" }, + //{ 0x08, 0x04, 0x0804, "position of block 2 puzzle 2 Lanayru Ice Puzzle" }, + //{ 0x08, 0x02, 0x0802, "position of block 2 puzzle 2 Lanayru Ice Puzzle" }, + //{ 0x08, 0x01, 0x0801, "position of block 2 puzzle 2 Lanayru Ice Puzzle" }, + //{ 0x09, 0x80, 0x0980, "position of block 3 puzzle 2 Lanayru Ice Puzzle" }, + //{ 0x09, 0x40, 0x0940, "position of block 3 puzzle 2 Lanayru Ice Puzzle" }, + //{ 0x09, 0x20, 0x0920, "position of block 3 puzzle 2 Lanayru Ice Puzzle" }, + //{ 0x09, 0x10, 0x0910, "position of block 3 puzzle 2 Lanayru Ice Puzzle" }, + //{ 0x09, 0x04, 0x0904, "position of block 1 puzzle 1 Lanayru Ice Puzzle" }, + //{ 0x09, 0x02, 0x0902, "position of block 1 puzzle 1 Lanayru Ice Puzzle" }, + //{ 0x09, 0x01, 0x0901, "position of block 1 puzzle 1 Lanayru Ice Puzzle" }, + //{ 0x0A, 0x80, 0x0A80, "position of block 1 puzzle 1 Lanayru Ice Puzzle" }, + //{ 0x0A, 0x20, 0x0A20, "position of block 2 puzzle 1 Lanayru Ice Puzzle" }, + //{ 0x0A, 0x10, 0x0A10, "position of block 2 puzzle 1 Lanayru Ice Puzzle" }, + //{ 0x0A, 0x08, 0x0A08, "position of block 2 puzzle 1 Lanayru Ice Puzzle" }, + //{ 0x0A, 0x04, 0x0A04, "position of block 2 puzzle 1 Lanayru Ice Puzzle" }, + //{ 0x0B, 0x08, 0x0B08, "position of block 3 puzzle 1 Lanayru Ice Puzzle" }, + //{ 0x0B, 0x04, 0x0B04, "position of block 3 puzzle 1 Lanayru Ice Puzzle" }, + //{ 0x0B, 0x02, 0x0B02, "position of block 3 puzzle 1 Lanayru Ice Puzzle" }, + //{ 0x0B, 0x01, 0x0B01, "position of block 3 puzzle 1 Lanayru Ice Puzzle" }, + { 0x0D, 0x10, 0x0D10, "broke right ice blocking first doorway in Lanayru Ice Puzzle" }, + { 0x0D, 0x08, 0x0D08, "broke left ice blocking first doorway in Lanayru Ice Puzzle" }, + { 0x0D, 0x04, 0x0D04, "open fourth gate in Lanayru Ice Puzzle" }, + { 0x0D, 0x02, 0x0D02, "open third gate in Lanayru Ice Puzzle" }, + { 0x0D, 0x01, 0x0D01, "open second gate in Lanayru Ice Puzzle" }, + { 0x0E, 0x80, 0x0E80, "open first gate in Lanayru Ice Puzzle" }, + //{ 0x0E, 0x40, 0x0E40, "position of block 1 puzzle 3 Lanayru Ice Puzzle" }, + //{ 0x0E, 0x20, 0x0E20, "position of block 1 puzzle 3 Lanayru Ice Puzzle" }, + //{ 0x0E, 0x10, 0x0E10, "position of block 1 puzzle 3 Lanayru Ice Puzzle" }, + //{ 0x0E, 0x08, 0x0E08, "position of block 1 puzzle 3 Lanayru Ice Puzzle" }, + //{ 0x0E, 0x04, 0x0E04, "position of block 1 puzzle 3 Lanayru Ice Puzzle" }, + //{ 0x0E, 0x02, 0x0E02, "position of block 2 puzzle 3 Lanayru Ice Puzzle" }, + //{ 0x0E, 0x01, 0x0E01, "position of block 2 puzzle 3 Lanayru Ice Puzzle" }, + //{ 0x0F, 0x80, 0x0F80, "position of block 2 puzzle 3 Lanayru Ice Puzzle" }, + //{ 0x0F, 0x40, 0x0F40, "position of block 2 puzzle 3 Lanayru Ice Puzzle" }, + //{ 0x0F, 0x20, 0x0F20, "position of block 2 puzzle 3 Lanayru Ice Puzzle" }, + //{ 0x0F, 0x10, 0x0F10, "position of block 3 puzzle 3 Lanayru Ice Puzzle" }, + //{ 0x0F, 0x08, 0x0F08, "position of block 3 puzzle 3 Lanayru Ice Puzzle" }, + //{ 0x0F, 0x04, 0x0F04, "position of block 3 puzzle 3 Lanayru Ice Puzzle" }, + //{ 0x0F, 0x02, 0x0F02, "position of block 3 puzzle 3 Lanayru Ice Puzzle" }, + //{ 0x0F, 0x01, 0x0F01, "position of block 3 puzzle 3 Lanayru Ice Puzzle" }, + { 0x10, 0x80, 0x1080, "spawn big chest right right left right path Eldin Long Cave" }, + { 0x10, 0x40, 0x1040, "lit torch 3 (right right right path) Eldin Long Cave" }, + { 0x13, 0x80, 0x1380, "killed poe on floor 44 in Cave of Ordeals" }, + { 0x13, 0x40, 0x1340, "killed poe on floor 33 in Cave of Ordeals" }, + { 0x13, 0x20, 0x1320, "killed poe on floor 17 in Cave of Ordeals" }, + { 0x13, 0x08, 0x1308, "Cave of Ordeals intro cs" }, + { 0x13, 0x04, 0x1304, "obtained fairy's tears (if unset, you can get it even if you already have some) (also spawns that extra darknut on floor 49)" }, + { 0x13, 0x02, 0x1302, "open floor 1 door in Cave of Ordeals cs" }, + { 0x13, 0x01, 0x1301, "broke ice on floor 21 in Cave of Ordeals" }, + { 0x14, 0x40, 0x1440, "explored section 2 right right left right path Eldin Long Cave" }, + { 0x14, 0x20, 0x1420, "explored section 1 right right left right path Eldin Long Cave" }, + { 0x14, 0x10, 0x1410, "explored section 2 right right left left path Eldin Long Cave" }, + { 0x14, 0x08, 0x1408, "explored section 1 right right left left path Eldin Long Cave" }, + { 0x14, 0x04, 0x1404, "explored section 3 right right left path Eldin Long Cave" }, + { 0x14, 0x02, 0x1402, "explored section 2 right right left path Eldin Long Cave" }, + { 0x14, 0x01, 0x1401, "explored section 1 right right left path Eldin Long Cave" }, + { 0x15, 0x80, 0x1580, "explored section 2 right right right path Eldin Long Cave" }, + { 0x15, 0x40, 0x1540, "explored section 1 right right right path Eldin Long Cave" }, + { 0x15, 0x20, 0x1520, "explored section 3 right right path Eldin Long Cave" }, + { 0x15, 0x10, 0x1510, "explored section 2 right right path Eldin Long Cave" }, + { 0x15, 0x08, 0x1508, "explored section 1 right right path Eldin Long Cave" }, + { 0x15, 0x04, 0x1504, "explored section 1 right left path Eldin Long Cave" }, + { 0x15, 0x02, 0x1502, "explored section 6 right path Eldin Long Cave" }, + { 0x15, 0x01, 0x1501, "explored section 5 right path Eldin Long Cave" }, + { 0x16, 0x80, 0x1680, "explored section 4 right path Eldin Long Cave" }, + { 0x16, 0x40, 0x1640, "explored section 3 right path Eldin Long Cave" }, + { 0x16, 0x20, 0x1620, "explored section 2 right path Eldin Long Cave" }, + { 0x16, 0x10, 0x1610, "explored section 1 right path Eldin Long Cave" }, + { 0x16, 0x08, 0x1608, "explored section 2 left right path Eldin Long Cave" }, + { 0x16, 0x04, 0x1604, "explored section 1 right right path Eldin Long Cave" }, + { 0x16, 0x02, 0x1602, "explored section 2 left left path Eldin Long Cave" }, + { 0x16, 0x01, 0x1601, "explored section 1 right right path Eldin Long Cave" }, + { 0x17, 0x80, 0x1780, "explored section 2 left path Eldin Long Cave" }, + { 0x17, 0x40, 0x1740, "explored section 1 right path Eldin Long Cave" }, + { 0x17, 0x20, 0x1720, "explored section 2 Eldin Long Cave" }, + { 0x17, 0x10, 0x1710, "explored section 1 Eldin Long Cave" }, + { 0x17, 0x08, 0x1708, "lit torch 2 (right path) Eldin Long Cave" }, + { 0x17, 0x04, 0x1704, "lit left torch right right left right path Eldin Long Cave" }, + { 0x17, 0x02, 0x1702, "lit right torch right right left right path Eldin Long Cave" }, + { 0x17, 0x01, 0x1701, "killed poe right right left left path Eldin Long Cave" }, +}; + +inline EventAreaFlags eventAreaFlagsEldin[] = +{ + { 0x00, 0x01, 0x0001, "Big chest from torches graveyard" }, + { 0x01, 0x80, 0x0180, "Small chest in the inn" }, + { 0x01, 0x40, 0x0140, "PoH big chest death mountain archer alcove" }, + { 0x01, 0x20, 0x0120, "PoH big chest underwater behind spring" }, + { 0x01, 0x10, 0x0110, "Big chest next to watch tower" }, + { 0x01, 0x04, 0x0104, "?" }, + { 0x01, 0x02, 0x0102, "Big chest watch house" }, + { 0x01, 0x01, 0x0101, "tear of light death mountain hot spring water" }, + { 0x02, 0x80, 0x0280, "tear of light next to howling stone death mountain " }, + { 0x02, 0x40, 0x0240, "tear of light death mountain base" }, + { 0x02, 0x10, 0x0210, "tear of light sancuary basement" }, + { 0x02, 0x08, 0x0208, "tear of light Barnes storage house" }, + { 0x02, 0x04, 0x0204, "tear of light watch tower" }, + { 0x02, 0x02, 0x0202, "tear of light at Malo mart" }, + { 0x02, 0x01, 0x0201, "tear of light inn fire room" }, + { 0x03, 0x80, 0x0380, "tear of light Barnes shop" }, + { 0x03, 0x40, 0x0340, "tear of light graveyard" }, + { 0x03, 0x20, 0x0320, "tear of light Barnes storage house" }, + { 0x03, 0x10, 0x0310, "tear of light Barnes storage house" }, + { 0x03, 0x08, 0x0308, "tear of light sancuary basement" }, + { 0x03, 0x04, 0x0304, "tear of light sancuary basement" }, + { 0x03, 0x02, 0x0302, "tear of light ant house" }, + { 0x03, 0x01, 0x0301, "tear of light inn back room" }, + { 0x08, 0x80, 0x0880, "Kakariko Village Portal" }, + { 0x08, 0x40, 0x0840, "kakariko shadow beasts barrier appears (unset after fight)" }, + { 0x08, 0x20, 0x0820, "DM big rock fell" }, + { 0x08, 0x10, 0x0810, "done midna jumps 4 death mountain (hot spring water)" }, + { 0x08, 0x08, 0x0808, "done midna jumps 3 death mountain (1st floor)" }, + { 0x08, 0x04, 0x0804, "killed death mountain shadow beasts cs (unset later?)" }, + { 0x08, 0x01, 0x0801, "done midna jumps 1 death mountain (entrance)" }, + { 0x09, 0x80, 0x0980, "Malo mart has banners inside" }, + { 0x09, 0x40, 0x0940, "DM proper human intro CS (Map marker GM)" }, + { 0x09, 0x20, 0x0920, "Death Mountain Portal" }, + { 0x09, 0x10, 0x0910, "death mountain dark beasts barrier appears" }, + { 0x09, 0x08, 0x0908, "?" }, + { 0x09, 0x04, 0x0904, "?" }, + { 0x09, 0x02, 0x0902, "killed HV bublins (unset when cats spawn)" }, + { 0x09, 0x01, 0x0901, "killed light bug death mountain hot spring water" }, + { 0x0A, 0x80, 0x0A80, "killed light bug next to howling stone death mountain " }, + { 0x0A, 0x40, 0x0A40, "killed light bug death mountain base" }, + { 0x0A, 0x20, 0x0A20, "trigger twilight end cs (vessel of light filled) (Hot Spring Water Present on map)" }, + { 0x0A, 0x10, 0x0A10, "killed light bug sancuary basement" }, + { 0x0A, 0x04, 0x0A04, "killed light bug watch tower" }, + { 0x0A, 0x02, 0x0A02, "killed light bug malo mart" }, + { 0x0A, 0x01, 0x0A01, "killed light bug inn fire room" }, + { 0x0B, 0x80, 0x0B80, "killed light bug Barnes shop" }, + { 0x0B, 0x40, 0x0B40, "killed light bug graveyard" }, + { 0x0B, 0x08, 0x0B08, "killed light bug sancuary basement" }, + { 0x0B, 0x04, 0x0B04, "killed light bug sancuary basement" }, + { 0x0B, 0x02, 0x0B02, "killed light bug ant house" }, + { 0x0B, 0x01, 0x0B01, "killed light bug inn bedroom" }, + { 0x0C, 0x80, 0x0C80, "saw light bug enter Barnes storage house twilight cs" }, + { 0x0C, 0x40, 0x0C40, "malo mart sells hawkeye (moves potion to the right) (set when starting bow mini-game)" }, + { 0x0C, 0x20, 0x0C20, "malo mart sells arrows" }, + { 0x0C, 0x10, 0x0C10, "broke sanctuary roof patch during twilight (duplicate)" }, + { 0x0C, 0x08, 0x0C08, "box moved ant house (spawns light bug)" }, + { 0x0C, 0x04, 0x0C04, "Unknown crashes if rocks are pushed" }, + { 0x0C, 0x02, 0x0C02, "malo mart hylian shield bought" }, + { 0x0C, 0x01, 0x0C01, "ant house explored" }, + { 0x0D, 0x80, 0x0D80, "entered sanctuary from roof during twilight" }, + { 0x0D, 0x40, 0x0D40, "enter Barnes shop through the window in twilight cs" }, + { 0x0D, 0x20, 0x0D20, "light bug comes out of box in ant house cs" }, + { 0x0D, 0x10, 0x0D10, "spawn PoH cat mini game hidden village" }, + { 0x0D, 0x08, 0x0D08, "malo mart hawkeye sold out (needs to be set for arrows to show up)" }, + { 0x0D, 0x04, 0x0D04, "lit all torches in sanctuary twilight (opens basment)" }, + { 0x0D, 0x02, 0x0D02, "light bug in inn fireplace jumps out cs" }, + { 0x0D, 0x01, 0x0D01, "lit inn fireplace" }, + { 0x0E, 0x80, 0x0E80, "knocked down dresser in Barnes shop twilight cs" }, + { 0x0E, 0x40, 0x0E40, "?" }, + { 0x0E, 0x20, 0x0E20, "? (malo mart explored)" }, + { 0x0E, 0x10, 0x0E10, "lit west torch in sanctuary twilight" }, + { 0x0E, 0x08, 0x0E08, "lit north east torch in sanctuary twilight" }, + { 0x0E, 0x04, 0x0E04, "lit east torch in sanctuary twilight" }, + { 0x0E, 0x02, 0x0E02, "lit north west torch in sanctuary twilight" }, + { 0x0F, 0x80, 0x0F80, "broke ant house roof patch (duplicate)" }, + { 0x0F, 0x40, 0x0F40, "moved death mountain rock to exit" }, + { 0x0F, 0x20, 0x0F20, "Eldin spirit talks to Link after getting kakariko Portal cs" }, + { 0x0F, 0x10, 0x0F10, "broke ant house roof patch" }, + { 0x0F, 0x08, 0x0F08, "broke window of Barnes shop twilight" }, + { 0x0F, 0x04, 0x0F04, "broke sanctuary roof patch during twilight" }, + { 0x0F, 0x02, 0x0F02, "Renado talks to Link after trying to go up death mountain trail" }, + { 0x0F, 0x01, 0x0F01, "killed kakariko shadow beasts cs" }, + { 0x10, 0x80, 0x1080, "Killed poe watch tower" }, + { 0x10, 0x40, 0x1040, "Killed poe Barnes storage house" }, + { 0x10, 0x20, 0x1020, "blown up rock underwater to lake hylia from graveward" }, + { 0x10, 0x10, 0x1010, "Graveyard intro cs" }, + { 0x10, 0x08, 0x1008, "explored zora tombstone area" }, + { 0x10, 0x04, 0x1004, "broke floor patch in graveyard during twilight" }, + { 0x10, 0x02, 0x1002, "Killed poe death mountain" }, + { 0x10, 0x01, 0x1001, "Killed poe graveyard center" }, + { 0x11, 0x80, 0x1180, "Killed poe graveyard tombstone" }, + { 0x11, 0x40, 0x1140, "gorons in kakariko hot spring water gone" }, + { 0x11, 0x20, 0x1120, "Map Marker Sanctuary" }, + { 0x11, 0x08, 0x1108, "Midna text after DM big rock fell" }, + { 0x11, 0x04, 0x1104, "done midna jumps 2 death mountain (base floor)" }, + { 0x11, 0x02, 0x1102, "death mountain proper intro cs twilight" }, + { 0x11, 0x01, 0x1101, "moved death mountain rock to hot spring water" }, + { 0x12, 0x80, 0x1280, "sanctuary basement canon room explored" }, + { 0x12, 0x40, 0x1240, "done Midna jumps in sanctuary basement" }, + { 0x12, 0x20, 0x1220, "Midna text before Midna jumps in sanctuary basement" }, + { 0x12, 0x10, 0x1210, "sanctuary twilight cs" }, + { 0x12, 0x08, 0x1208, "killed right shadow bulblin inside inn twilight" }, + { 0x12, 0x04, 0x1204, "killed left shadow bulblin inside inn twilight" }, + { 0x12, 0x02, 0x1202, "killed top shadow bulblin inside inn twilight" }, + { 0x12, 0x01, 0x1201, "enter central room of inn during twilight cs" }, + { 0x13, 0x80, 0x1380, "sanctuary basement hallway to canon explored" }, + { 0x13, 0x40, 0x1340, "Malo mart shield on counter (requires reload)" }, + { 0x13, 0x20, 0x1320, "Kakariko intro cs" }, + { 0x13, 0x10, 0x1310, "blown up rocks on kakariko cliff PoH" }, + { 0x13, 0x08, 0x1308, "Blown up rock to secret passage to back of spring (updates map)" }, + { 0x13, 0x04, 0x1304, "Barnes storage house not blown up (unset once you enter) (twilight)" }, + { 0x13, 0x02, 0x1302, "Barnes storage house blew up cs (also changes map)" }, + { 0x13, 0x01, 0x1301, "Killed poe hidden village" }, + { 0x14, 0x80, 0x1480, "Midna text after sanctuary twilight cs" }, + { 0x14, 0x20, 0x1420, "light bug comes out of dresser in Barnes shop twilight" }, + { 0x14, 0x10, 0x1410, "GM save prompt" }, + { 0x14, 0x08, 0x1408, "Barnes sells Bombs" }, + { 0x14, 0x04, 0x1404, "midna text after shad leaves canon room" }, + { 0x14, 0x02, 0x1402, "Hero's shade map marker" }, + { 0x14, 0x01, 0x1401, "done midna jumps to top of sanctuary (also unlocks it)" }, + { 0x15, 0x80, 0x1580, "blown up rock underwater behind spring" }, + { 0x15, 0x40, 0x1540, "malo mart sells red potion on right slot" }, + { 0x15, 0x20, 0x1520, "beat bow mini-game" }, + { 0x15, 0x10, 0x1510, "malo mart sells red potion on left slot (default)" }, + { 0x15, 0x08, 0x1508, "explored bedroom in the inn" }, + { 0x15, 0x04, 0x1504, "explored central room in the inn" }, + { 0x15, 0x02, 0x1502, "blown up right rock underwater zora tombstone area" }, + { 0x15, 0x01, 0x1501, "midna text after charging dominion rod" }, + { 0x16, 0x80, 0x1680, "Goron lets you enter elevator in sumo hall" }, + { 0x16, 0x40, 0x1640, "Midna text after blowing up Barnes storage house" }, + { 0x16, 0x20, 0x1620, "hit third target in bow mini-game" }, + { 0x16, 0x10, 0x1610, "saved Colin cs (malo mart now open)" }, + { 0x16, 0x04, 0x1604, "Midna text after meeting the kids after twilight" }, + { 0x16, 0x02, 0x1602, "Started Rutela escort" }, + { 0x16, 0x01, 0x1601, "hit second target in bow mini-game" }, + { 0x17, 0x80, 0x1780, "hit first target in bow mini-game" }, + { 0x17, 0x40, 0x1740, "rock to zora tombstone gone + Barnes sells water bombs" }, + { 0x17, 0x20, 0x1720, "Followed Rutela to graveyard" }, + { 0x17, 0x10, 0x1710, "death mountain path intro cs twilight" }, + { 0x17, 0x08, 0x1708, "owl statue possessed cs (sanctuary basement)" }, + { 0x17, 0x04, 0x1704, "done midna jumps ant house (also unlocks it)" }, + { 0x17, 0x02, 0x1702, "Impaz in her house (hidden village)" }, + { 0x17, 0x01, 0x1701, "can't transform in hidden village (if bit 2 is set you can)" }, + { 0x1A, 0x40, 0x1A40, "picked up rupees from right rock underwater zora tombstone area" }, + { 0x1A, 0x20, 0x1A20, "picked up rupees from rock underwater behind spring" }, + { 0x1A, 0x10, 0x1A10, "Free standing PoH kakariko cliff" }, + { 0x1A, 0x08, 0x1A08, "Free standing PoH cat mini game hidden village" }, + { 0x1A, 0x04, 0x1A04, "picked up silver rupee from bell above sanctuary" }, + { 0x1A, 0x02, 0x1A02, "picked up yellow rupee in box secret passage to spring" }, + { 0x1A, 0x01, 0x1A01, "picked up blue rupee in box secret passage to spring" }, + { 0x1B, 0x80, 0x1B80, "picked up yellow rupee in box next to kakariko hot spring water" }, + { 0x1B, 0x40, 0x1B40, "picked up yellow rupee behind dresser inn bedroom" }, + { 0x1B, 0x20, 0x1B20, "picked up red rupee in box on platform above ant house" }, + { 0x1B, 0x10, 0x1B10, "picked up blue rupee behind dresser inn 2nd floor" }, + { 0x1B, 0x08, 0x1B08, "picked up yellow rupee 1 on leadge of top of death montain" }, + { 0x1B, 0x04, 0x1B04, "picked up yellow rupee 2 on leadge of top of death montain" }, + { 0x1B, 0x02, 0x1B02, "picked up yellow rupee 3 on leadge of top of death montain" }, + { 0x1B, 0x01, 0x1B01, "picked up rupee in death montain in rock below pipe (need BaC and rang)" }, +}; + +inline EventAreaFlags eventAreaFlagsFaron[] = +{ + { 0x00, 0x80, 0x0080, "small chest in lantern cave" }, + { 0x00, 0x40, 0x0040, "HP Chest Coro-Mist shortcut" }, + { 0x00, 0x20, 0x0020, "big chest mist area" }, + { 0x00, 0x10, 0x0010, "small chest mist area trunk" }, + { 0x00, 0x08, 0x0008, "small chest mist area next to key cave" }, + { 0x00, 0x04, 0x0004, "PoH chest key cave" }, + { 0x00, 0x01, 0x0001, "Small key cave key chest" }, + { 0x01, 0x80, 0x0180, "Dig Tear #1" }, + { 0x01, 0x20, 0x0120, "Final Tear #1" }, + { 0x01, 0x10, 0x0110, "Final Tear #2" }, + { 0x01, 0x04, 0x0104, "Wall Tear #1 (Mist Area)" }, + { 0x01, 0x02, 0x0102, "Wall Tear #2 (Mist Area)" }, + { 0x02, 0x40, 0x0240, "Tree Tear #1" }, + { 0x02, 0x20, 0x0220, "Tree Tear #2" }, + { 0x02, 0x10, 0x0210, "Tree Tear #3" }, + { 0x02, 0x08, 0x0208, "Gate Tear #1" }, + { 0x02, 0x02, 0x0202, "Coro Tear #1 (inside)" }, + { 0x02, 0x01, 0x0201, "Dig Tear #2" }, + { 0x03, 0x40, 0x0340, "Tear #2" }, + { 0x03, 0x20, 0x0320, "Gate Tear #2" }, + { 0x03, 0x10, 0x0310, "Coro Tear #2 (inside)" }, + { 0x03, 0x02, 0x0302, "Tear #1" }, + { 0x03, 0x01, 0x0301, "Coro Tear (outisde)" }, + { 0x04, 0x80, 0x0480, "Yellow Rupee Chest (N-Faron)" }, + { 0x08, 0x80, 0x0880, "explored east section of mist area after midna jump 1 twilight (gets unset)" }, + { 0x08, 0x40, 0x0840, "explored section with south entrance of mist area (gets unset)" }, + { 0x08, 0x10, 0x0810, "went up east section of mist area after midna jump 1 twilight" }, + { 0x08, 0x08, 0x0808, "S Faron warp twilight fences fall cs" }, + { 0x08, 0x04, 0x0804, "Sky character under owl statue" }, + { 0x08, 0x02, 0x0802, "midna jump 1 mist area (duplicate)" }, + { 0x08, 0x01, 0x0801, "unlock midna jump 1 mist area" }, + { 0x09, 0x80, 0x0980, "Coro spirits talk after killing light bugs in his house" }, + { 0x09, 0x40, 0x0940, "midna jump to Coro's house" }, + { 0x09, 0x20, 0x0920, "Map marker owl statue" }, + { 0x09, 0x10, 0x0910, "opened mist area gate to N faron" }, + { 0x09, 0x08, 0x0908, "lit torch 2 in lantern cave" }, + { 0x09, 0x04, 0x0904, "Talked to Midna next to the deku babas before Coro" }, + { 0x09, 0x02, 0x0902, "Rescued Monkey from Puppets" }, + { 0x09, 0x01, 0x0901, "dug behind Coro gate in twilight" }, + { 0x0A, 0x80, 0x0A80, "took Midna back s warp fight" }, + { 0x0A, 0x40, 0x0A40, "S warp shadow beast revive cs" }, + { 0x0A, 0x20, 0x0A20, "trigger twilight end cs (vessel of light filled)" }, + { 0x0A, 0x10, 0x0A10, "unlocked Coro gate" }, + { 0x0A, 0x08, 0x0A08, "Explored path to sacred grove" }, + { 0x0A, 0x04, 0x0A04, "burned cobweb in front of forest temple" }, + { 0x0A, 0x02, 0x0A02, "see spirit in front of FT twilight cs" }, + { 0x0A, 0x01, 0x0A01, "enter twilight cs" }, + { 0x0B, 0x40, 0x0B40, "talked to the light spirit in twilight" }, + { 0x0B, 0x20, 0x0B20, "saw light bugs dig underground mist area exit twilight" }, + { 0x0B, 0x10, 0x0B10, "Blown up rock next to Coro" }, + { 0x0B, 0x08, 0x0B08, "n warp fight fences appear cs" }, + { 0x0B, 0x04, 0x0B04, "North Faron Portal" }, + { 0x0B, 0x02, 0x0B02, "FT save prompt" }, + { 0x0C, 0x80, 0x0C80, "got lantern back after monkey follow" }, + { 0x0C, 0x40, 0x0C40, "saw light bugs move inside Coro's house cs" }, + { 0x0C, 0x20, 0x0C20, "in monkey follow (gets unset afterwards)" }, + { 0x0C, 0x10, 0x0C10, "Trill lets you shop" }, + { 0x0C, 0x04, 0x0C04, "killed left bokoblin before Trill after twilight" }, + { 0x0C, 0x02, 0x0C02, "killed right bokoblin before Trill after twilight" }, + { 0x0C, 0x01, 0x0C01, "explored section 6 in lantern cave" }, + { 0x0D, 0x80, 0x0D80, "explored section 5 in lantern cave" }, + { 0x0D, 0x40, 0x0D40, "explored section 4 in lantern cave" }, + { 0x0D, 0x20, 0x0D20, "explored section 3 in lantern cave" }, + { 0x0D, 0x10, 0x0D10, "explored section 2 in lantern cave" }, + { 0x0D, 0x08, 0x0D08, "explored section 1 in lantern cave" }, + { 0x0D, 0x04, 0x0D04, "got Coro key from Coro" }, + { 0x0D, 0x02, 0x0D02, "?" }, + { 0x0D, 0x01, 0x0D01, "killed right bokoblin next to Talo's cage" }, + { 0x0E, 0x80, 0x0E80, "killed left bokoblin next to Talo's cage" }, + { 0x0E, 0x40, 0x0E40, "broke Talo's cage" }, + { 0x0E, 0x20, 0x0E20, "killed both bokoblins next to Talo's cage" }, + { 0x0E, 0x08, 0x0E08, "enter Faron intro cs" }, + { 0x0E, 0x04, 0x0E04, "lit Coro's soup" }, + { 0x0E, 0x02, 0x0E02, "saved Talo cs" }, + { 0x0E, 0x01, 0x0E01, "see light spirit from far away in twilight cs" }, + { 0x0F, 0x80, 0x0F80, "enter mist area twilight cs" }, + { 0x0F, 0x40, 0x0F40, "found Talo's stick (child chase)" }, + { 0x0F, 0x20, 0x0F20, "explored top of east slope mist area twilight" }, + { 0x0F, 0x10, 0x0F10, "talked to Coro spirit before killing bugs" }, + { 0x0F, 0x08, 0x0F08, "entered mist area as human" }, + { 0x0F, 0x04, 0x0F04, "Talked to midna before talking to Coro spirit" }, + { 0x0F, 0x02, 0x0F02, "explored section with north exit of mist area (gets unset)" }, + { 0x0F, 0x01, 0x0F01, "saw light bugs on trunk mist area (gets unset)" }, + { 0x10, 0x80, 0x1080, "Map marker Rusl" }, + { 0x10, 0x40, 0x1040, "lit torch 1 in lantern cave" }, + { 0x10, 0x20, 0x1020, "Killed poe mist area" }, + { 0x10, 0x10, 0x1010, "spawn PoH chest key cave" }, + { 0x10, 0x08, 0x1008, "lit right torch in key cave" }, + { 0x10, 0x04, 0x1004, "lit left torch in key cave" }, + { 0x10, 0x02, 0x1002, "explored section 15 in lantern cave" }, + { 0x10, 0x01, 0x1001, "add mist area mist to mini-map" }, + { 0x11, 0x80, 0x1180, "explored section 14 in lantern cave" }, + { 0x11, 0x40, 0x1140, "explored section 13 in lantern cave" }, + { 0x11, 0x20, 0x1120, "explored section 12 in lantern cave" }, + { 0x11, 0x10, 0x1110, "explored section 11 in lantern cave" }, + { 0x11, 0x08, 0x1108, "explored section 10 in lantern cave" }, + { 0x11, 0x04, 0x1104, "explored section 9 in lantern cave" }, + { 0x11, 0x02, 0x1102, "explored section 3 of branching path in lantern cave" }, + { 0x11, 0x01, 0x1101, "explored section 8 in lantern cave" }, + { 0x12, 0x80, 0x1280, "explored section 7 in lantern cave" }, + { 0x12, 0x20, 0x1220, "Midna text before jumping to Lost Woods" }, + { 0x12, 0x10, 0x1210, "killed light bug outside Coro's house (duplicate)" }, + { 0x12, 0x08, 0x1208, "saw light bug run behind Coro gate" }, + { 0x12, 0x04, 0x1204, "Midna text warp to N faron for bridge" }, + { 0x12, 0x02, 0x1202, "saw first 2 light bugs run away from you" }, + { 0x12, 0x01, 0x1201, "S warp shadow beasts are spawned" }, + { 0x13, 0x80, 0x1380, "South Faron Portal" }, + { 0x13, 0x40, 0x1340, "explored section 2 of branching path in lantern cave" }, + { 0x13, 0x20, 0x1320, "killed right bokoblin in front of FT after twilight" }, + { 0x13, 0x10, 0x1310, "killed left bokoblin in front of FT after twilight" }, + { 0x13, 0x08, 0x1308, "explored section 1 of branching path in lantern cave" }, + { 0x13, 0x01, 0x1301, "S warp shadow beasts killed" }, + { 0x14, 0x80, 0x1480, "lit torch 4 in lantern cave" }, + { 0x14, 0x40, 0x1440, "killed light bug mist area north exit" }, + { 0x14, 0x20, 0x1420, "did midna jump 2 mist area" }, + { 0x14, 0x10, 0x1410, "killed light bug mist area north exit" }, + { 0x14, 0x08, 0x1408, "explored section 4 of branching path in lantern cave" }, + { 0x14, 0x04, 0x1404, "killed light bug in front of Forest Temple" }, + { 0x14, 0x02, 0x1402, "killed light bug in front of Forest Temple" }, + { 0x14, 0x01, 0x1401, "killed both light bugs in front of FT cs (map marker FT)" }, + { 0x15, 0x80, 0x1580, "killed light bug mist area south entrance" }, + { 0x15, 0x40, 0x1540, "killed light bug mist area south entrance" }, + { 0x15, 0x20, 0x1520, "did midna jump 5 mist area" }, + { 0x15, 0x10, 0x1510, "did midna jump 3 mist area" }, + { 0x15, 0x08, 0x1508, "killed light bug mist area trunk" }, + { 0x15, 0x04, 0x1504, "killed light bug mist area trunk" }, + { 0x15, 0x02, 0x1502, "killed light bug mist area trunk" }, + { 0x15, 0x01, 0x1501, "killed light bug between latern cave and gate" }, + { 0x16, 0x80, 0x1680, "map marker Talo (only during child save)" }, + { 0x16, 0x40, 0x1640, "killed light bug outside Coro's house" }, + { 0x16, 0x20, 0x1620, "Map marker key cave (only during child save)" }, + { 0x16, 0x10, 0x1610, "remove map marker key cave" }, + { 0x16, 0x08, 0x1608, "did midna jump 4 mist area" }, + { 0x16, 0x04, 0x1604, "killed light bug hallway to Coro (closer to spring)" }, + { 0x16, 0x02, 0x1602, "killed light bug between latern cave and gate" }, + { 0x16, 0x01, 0x1601, "killed both light bugs in Coro's house cs" }, + { 0x17, 0x80, 0x1780, "killed light bug inside Coro's house" }, + { 0x17, 0x40, 0x1740, "killed light bug inside Coro's house" }, + { 0x17, 0x20, 0x1720, "killed light bug hallway to Coro (closer to Coro)" }, + { 0x17, 0x10, 0x1710, "did midna jump 6 mist area" }, + { 0x17, 0x08, 0x1708, "explored east section of mist area after midna jump 1 twilight (gets unset)" }, + { 0x17, 0x04, 0x1704, "lit torch 3 in lantern cave" }, + { 0x17, 0x02, 0x1702, "burned cobweb 2 in lantern cave" }, + { 0x17, 0x01, 0x1701, "burned cobweb 1 in lantern cave" }, + { 0x1B, 0x01, 0x1B01, "picked up rupees from rock next to Coro" }, +}; + +inline EventAreaFlags eventAreaFlagsFishingPond[] = +{ + { 0x1B, 0x01, 0x1B01, "free standing PoH" }, +}; + +inline EventAreaFlags eventAreaFlagsFT[] = +{ + { 0x00, 0x80, 0x0080, "PoH big chest behind Deku Like" }, + { 0x00, 0x04, 0x0004, "big chest underwater Tiny Cave" }, + { 0x00, 0x02, 0x0002, "small key big chest on pillar tile worm room" }, + { 0x00, 0x01, 0x0001, "Small Chest tile worm room" }, + { 0x01, 0x10, 0x0110, "big chest compass main room" }, + { 0x01, 0x08, 0x0108, "PoH big chest behind stairs tile worm room" }, + { 0x01, 0x02, 0x0102, "saved monkey in front of big pit" }, + { 0x02, 0x80, 0x0280, "saved monkey in boomerang bombling room" }, + { 0x02, 0x20, 0x0220, "saved monkey in tile worm room" }, + { 0x02, 0x10, 0x0210, "saved monkey in big baba room" }, + { 0x02, 0x08, 0x0208, "saved 2nd monkey" }, + { 0x02, 0x04, 0x0204, "first Monkey saved" }, + { 0x02, 0x02, 0x0202, "Small Chest 2nd Monkey room" }, + { 0x03, 0x80, 0x0380, "small chest first room" }, + { 0x03, 0x40, 0x0340, "Big Baba key acquired" }, + { 0x03, 0x10, 0x0310, "small key big chest in single wind bridge room" }, + { 0x03, 0x04, 0x0304, "small key big chest in boomerang bombling room" }, + { 0x03, 0x02, 0x0302, "Big Chest in broken stairs room" }, + { 0x06, 0x01, 0x0601, "Ooccoo map marker" }, + { 0x07, 0x80, 0x0780, "big chest with map main room" }, + { 0x07, 0x40, 0x0740, "Big Key Chest" }, + { 0x07, 0x20, 0x0720, "small chest behind bombable wall main room" }, + { 0x07, 0x04, 0x0704, "Outside Monkey saved" }, + { 0x07, 0x02, 0x0702, "saved monkey in spider cave room" }, + { 0x08, 0x40, 0x0840, "Midna Boomerang text seen" }, + { 0x08, 0x20, 0x0820, "midna text after bridge before Ook broken" }, + { 0x08, 0x08, 0x0808, "Big Baba killed cutscene (still spawned)" }, + { 0x08, 0x02, 0x0802, "hit totem of 2nd monkey once cs" }, + { 0x08, 0x01, 0x0801, "Midna first monkey text end" }, + { 0x09, 0x80, 0x0980, "Boss Door opened" }, + { 0x09, 0x40, 0x0940, "bridge before Ook broken" }, + { 0x09, 0x20, 0x0920, "burned cob web big key room" }, + { 0x09, 0x10, 0x0910, "bridge 2nd monkey room broken" }, + { 0x09, 0x02, 0x0902, "Compass Chest knocked down" }, + { 0x0A, 0x80, 0x0A80, "Monkey freed in spider room" }, + { 0x0A, 0x08, 0x0A08, "unlocked door to 2nd monkey room" }, + { 0x0A, 0x04, 0x0A04, "broke cage of 2nd monkey cs" }, + { 0x0A, 0x02, 0x0A02, "bokoblins see you in single wind bridge room cs" }, + { 0x0A, 0x01, 0x0A01, "turned wind bridge in single wind bridge room cs" }, + { 0x0B, 0x80, 0x0B80, "Unlock Windbridge East Door" }, + { 0x0B, 0x10, 0x0B10, "Girl Monkey helps Link" }, + { 0x0C, 0x40, 0x0C40, "opened cage of monkey in big baba room" }, + { 0x0C, 0x20, 0x0C20, "killed higher bokoblin in room before boss" }, + { 0x0C, 0x10, 0x0C10, "killed lower bokoblin in room before boss" }, + { 0x0C, 0x02, 0x0C02, "blown up rock to Ooccoo" }, + { 0x0D, 0x80, 0x0D80, "blown up rock blocking tile worm room" }, + { 0x0D, 0x40, 0x0D40, "blown up main room bombable wall" }, + //{ 0x0D, 0x20, 0x0D20, "small key big chest tile worm room position" }, + //{ 0x0D, 0x10, 0x0D10, "small key big chest tile worm room position" }, + //{ 0x0D, 0x08, 0x0D08, "small key big chest tile worm room position" }, + //{ 0x0D, 0x04, 0x0D04, "small key big chest tile worm room position" }, + { 0x0D, 0x02, 0x0D02, "Staircase rises main room" }, + { 0x0E, 0x20, 0x0E20, "tile worm room intro cs" }, + { 0x0E, 0x08, 0x0E08, "opened cage of monkey in tile worm room" }, + { 0x0E, 0x04, 0x0E04, "gate to big key opened" }, + { 0x0E, 0x02, 0x0E02, "2nd Monkey room intro cs" }, + { 0x0F, 0x80, 0x0F80, "lit bottom right torch main room" }, + { 0x0F, 0x40, 0x0F40, "lit bottom left torch main room" }, + { 0x0F, 0x20, 0x0F20, "lit upper right torch main room" }, + { 0x0F, 0x10, 0x0F10, "lit upper left torch main room" }, + { 0x0F, 0x08, 0x0F08, "big baba room intro cs" }, + { 0x0F, 0x04, 0x0F04, "enter room before boss Midna text" }, + { 0x0F, 0x02, 0x0F02, "Midna text after saveing a monkey after Ook" }, + { 0x0F, 0x01, 0x0F01, "Small Bridge breaks scene seen" }, + { 0x10, 0x10, 0x1010, "enter southwest door outside intro cs" }, + { 0x10, 0x04, 0x1004, "Midna text after compass" }, + { 0x10, 0x02, 0x1002, "boomerang obtained" }, + { 0x10, 0x01, 0x1001, "burned west cob web main room" }, + { 0x11, 0x08, 0x1108, "5th monkey added to room before boss" }, + { 0x11, 0x04, 0x1104, "4 Monkeys in main room cutscene" }, + { 0x11, 0x02, 0x1102, "turned wind bridge in single wind bridge room" }, + { 0x11, 0x01, 0x1101, "Monkeys regroup in Main Room" }, + { 0x12, 0x80, 0x1280, "4 Monkeys gather in Main Room" }, + { 0x12, 0x40, 0x1240, "blown up 1st rock in boomeang bombling room" }, + { 0x12, 0x10, 0x1210, "Ooccoo Freed" }, + { 0x12, 0x01, 0x1201, "2nd Monkey Bokoblins Killed" }, + { 0x13, 0x80, 0x1380, "Midna first Monkey text prompt (makes vines climbable)" }, + { 0x13, 0x40, 0x1340, "killed big baba cs" }, + { 0x13, 0x08, 0x1308, "?" }, + { 0x13, 0x04, 0x1304, "?" }, + { 0x13, 0x02, 0x1302, "Diababa killed" }, + { 0x13, 0x01, 0x1301, "Diababa Phase 2 (unset post kill)" }, + { 0x14, 0x20, 0x1420, "broke bridge over water that leads to big key" }, + { 0x14, 0x04, 0x1404, "monkeys form a rope in room before boss cs" }, + { 0x14, 0x02, 0x1402, "monkeys start to form a rope in room before boss" }, + { 0x15, 0x80, 0x1580, "All Monkeys form rope" }, + { 0x15, 0x40, 0x1540, "Ook exit door opened" }, + { 0x15, 0x20, 0x1520, "opened gate to monkey in broken stairs room" }, + { 0x15, 0x08, 0x1508, "blown up 2nd rock in boomerang bombling room" }, + { 0x15, 0x02, 0x1502, "saved 5th monkey cs" }, + { 0x15, 0x01, 0x1501, "killed bokoblin next to first monkey" }, + { 0x16, 0x20, 0x1620, "see locked monkey post Ook" }, + { 0x16, 0x10, 0x1610, "Ook defeated" }, + { 0x16, 0x08, 0x1608, "monkeys form a rope in room before boss cs" }, + { 0x16, 0x04, 0x1604, "Staircase rising cutscene for 4th Monkey" }, + { 0x16, 0x02, 0x1602, "Intro Cutscene" }, + { 0x16, 0x01, 0x1601, "2nd Monkey Pillar scene" }, + { 0x17, 0x80, 0x1780, "enter room before boss intro cs" }, + { 0x17, 0x40, 0x1740, "turned mill for the first time in Ook room cs" }, + { 0x17, 0x20, 0x1720, "big chest compass main room (set after)" }, + { 0x17, 0x01, 0x1701, "hit totem of 2nd monkey once" }, + { 0x1B, 0x10, 0x1B10, "picked up red rupee from deku like in spider room" }, +}; + +inline EventAreaFlags eventAreaFlagsGerudoDesert[] = +{ + { 0x02, 0x80, 0x0280, "big chest next to Arbiter's Grounds" }, + { 0x02, 0x20, 0x0220, "small chest next to camp entrance" }, + { 0x02, 0x08, 0x0208, "small chest on pillar weat of entrance to desert" }, + { 0x02, 0x04, 0x0204, "small chest under tower of left bulblin outside camp" }, + { 0x02, 0x02, 0x0202, "small chest under tower of right bulblin outside camp" }, + { 0x02, 0x01, 0x0201, "small chest next to fire outside camp" }, + { 0x03, 0x80, 0x0380, "small chest island north of cave of ordeals" }, + { 0x03, 0x40, 0x0340, "small chest east canyon" }, + { 0x03, 0x20, 0x0320, "small chest next to pillar in the middle of the messa" }, + { 0x03, 0x08, 0x0308, "small chest behind 2 breakable walls (the west one)" }, + { 0x03, 0x04, 0x0304, "small chest behind 2 breakable walls (the east one)" }, + { 0x03, 0x02, 0x0302, "big chest owl statue" }, + { 0x03, 0x01, 0x0301, "big chest in south secret path" }, + { 0x04, 0x80, 0x0480, "both small chests and key in camp (each one sets it)" }, + { 0x08, 0x80, 0x0880, "?" }, + { 0x08, 0x40, 0x0840, "?" }, + { 0x08, 0x20, 0x0820, "explored island north of cave of ordeals" }, + { 0x08, 0x10, 0x0810, "explored part 9 of bulblin camp (KB battle area)" }, + { 0x08, 0x08, 0x0808, "explored part 8 of bulblin camp" }, + { 0x08, 0x04, 0x0804, "explored part 7 of bulblin camp" }, + { 0x08, 0x02, 0x0802, "explored part 6 of bulblin camp" }, + { 0x08, 0x01, 0x0801, "explored part 5 of bulblin camp" }, + { 0x09, 0x80, 0x0980, "Desert Intro CS (PoT)" }, + { 0x09, 0x20, 0x0920, "Gerudo Messa Portal" }, + { 0x09, 0x08, 0x0908, "explored part 2 of bulblin camp" }, + { 0x09, 0x04, 0x0904, "explored part 4 of bulblin camp" }, + { 0x09, 0x02, 0x0902, "explored part 3 of bulblin camp" }, + { 0x09, 0x01, 0x0901, "?" }, + { 0x0A, 0x40, 0x0A40, "explored south secret path to big chest" }, + { 0x0A, 0x10, 0x0A10, "Mirror chamber intro cs" }, + { 0x0A, 0x04, 0x0A04, "save prompt after beating Arbiter's Grounds" }, + { 0x0A, 0x02, 0x0A02, "killed archer bulblin on bore outside camp" }, + { 0x0A, 0x01, 0x0A01, "killed rider bulblin on bore outside camp" }, + { 0x0B, 0x80, 0x0B80, "killed lone bulblin on bore outside camp" }, + { 0x0B, 0x40, 0x0B40, "killed bulblin on left tower outside camp" }, + { 0x0B, 0x20, 0x0B20, "killed bulblin on right tower outside camp" }, + { 0x0B, 0x10, 0x0B10, "watched outside AG cutscene after bublin camp" }, + { 0x0B, 0x04, 0x0B04, "destroyed fire outside camp" }, + { 0x0B, 0x02, 0x0B02, "destroyed bore meat in camp" }, + { 0x0D, 0x08, 0x0D08, "killed poe next to entrance to camp" }, + { 0x0D, 0x04, 0x0D04, "Hero's Shade map marker" }, + { 0x0D, 0x02, 0x0D02, "spawn big chest next to Arbiter's Grounds" }, + { 0x0D, 0x01, 0x0D01, "lit right torch for big chest next to Arbiter's Grounds entrance" }, + { 0x0E, 0x80, 0x0E80, "lit left torch for big chest next to Arbiter's Grounds entrance" }, + { 0x0E, 0x20, 0x0E20, "explored area with Arbiter's Gounds entrance" }, + { 0x0E, 0x02, 0x0E02, "mirror light stairs appear" }, + { 0x0E, 0x01, 0x0E01, "Mirror Chamber Portal" }, + { 0x0F, 0x04, 0x0F04, "sky character under owl statue" }, + { 0x0F, 0x02, 0x0F02, "map marker owl statue" }, + { 0x0F, 0x01, 0x0F01, "Mirror Raised Cutscene Flag (Places Boar at desert entrance)" }, + { 0x10, 0x40, 0x1040, "broke left wall that prevents access to east small chest (set once pieces despawn)" }, + { 0x10, 0x20, 0x1020, "killed poe next to cave of ordeals" }, + { 0x10, 0x10, 0x1010, "killed poe south of desert entrance" }, + { 0x10, 0x08, 0x1008, "killed poe on elevated platform with grotto (messa)" }, + { 0x10, 0x04, 0x1004, "killed poe next to Arbiter's Grounds entrance" }, + { 0x10, 0x02, 0x1002, "destroyed tower of right bulbin outside camp" }, + { 0x10, 0x01, 0x1001, "destroyed tower of left bulbin outside camp" }, + { 0x11, 0x20, 0x1120, "broke first right wall to camp (set once pieces despawn)" }, + { 0x11, 0x10, 0x1110, "broke first middle wall to camp (set once pieces despawn)" }, + { 0x11, 0x08, 0x1108, "broke first left wall to camp (set once pieces despawn)" }, + { 0x11, 0x04, 0x1104, "broke second right wall to camp (set once pieces despawn)" }, + { 0x11, 0x02, 0x1102, "broke second left wall to camp (set once pieces despawn)" }, + { 0x12, 0x10, 0x1210, "explored elavated section with poe over grotto" }, + { 0x13, 0x40, 0x1340, "Desert Intro CS" }, + { 0x14, 0x80, 0x1480, "started climbing stairs to mirror chamber" }, + { 0x14, 0x40, 0x1440, "exit Arbiters Grounds to go to mirror chamber" }, + { 0x14, 0x02, 0x1402, "map marker palace of twilight (explored mirror chamber)" }, + { 0x14, 0x01, 0x1401, "killed poe in bulblin camp" }, + { 0x17, 0x08, 0x1708, "broke right wall that prevents access to east small chest (set once pieces despawn)" }, + { 0x17, 0x04, 0x1704, "broke left wall that prevents access to west small chest (set once pieces despawn)" }, + { 0x17, 0x02, 0x1702, "broke right wall that prevents access to west small chest (set once pieces despawn)" }, + { 0x18, 0x80, 0x1880, "free standing PoH bore meat bulblin camp" }, +}; + +inline EventAreaFlags eventAreaFlagsGM[] = +{ + { 0x00, 0x40, 0x0040, "big chest main room top floor" }, + { 0x00, 0x10, 0x0010, "small chest next to switch in toadpoli room" }, + { 0x00, 0x08, 0x0008, "small chest first room" }, + { 0x00, 0x04, 0x0004, "big chest elder 3 room" }, + { 0x00, 0x02, 0x0002, "small chest elder 2 room" }, + { 0x00, 0x01, 0x0001, "small chest elder 1 room" }, + { 0x01, 0x40, 0x0140, "Ooccoo Map Flag" }, + { 0x01, 0x20, 0x0120, "big chest underwater outside room" }, + { 0x01, 0x10, 0x0110, "PoH big chest room 3" }, + { 0x01, 0x02, 0x0102, "map big chest elder 1 room" }, + { 0x01, 0x01, 0x0101, "small key underwater big chest toadpoli room" }, + { 0x02, 0x80, 0x0280, "small key small chest outside room" }, + { 0x02, 0x40, 0x0240, "big chest outside room clawshot" }, + { 0x02, 0x20, 0x0220, "small chest room leading to elder 2" }, + { 0x02, 0x10, 0x0210, "compass big chest" }, + { 0x02, 0x08, 0x0208, "small key big chest main room bottom floor" }, + { 0x02, 0x02, 0x0202, "PoH big chest toadpoli room (the one after the gate)" }, + { 0x03, 0x40, 0x0340, "big chest bow" }, + { 0x08, 0x10, 0x0810, "cut rope of door outside room cs" }, + { 0x08, 0x08, 0x0808, "pressed second button main room floor 2 cs" }, + { 0x08, 0x04, 0x0804, "pressed third button in first room cs" }, + { 0x08, 0x02, 0x0802, "pressed second button in first room cs" }, + { 0x08, 0x01, 0x0801, "cut rope of door in toadpoli room cs" }, + { 0x09, 0x80, 0x0980, "unlocked boss door" }, + { 0x09, 0x20, 0x0920, "pressed first button main room floor 2 cs" }, + { 0x09, 0x10, 0x0910, "killed beamos outside room" }, + { 0x0A, 0x40, 0x0A40, "unlock mini-boss doors" }, + { 0x0A, 0x08, 0x0A08, "pressed button outside room" }, + { 0x0A, 0x04, 0x0A04, "cut rope of door in room before boss" }, + { 0x0A, 0x02, 0x0A02, "cut rope of door in toadpoli room" }, + { 0x0A, 0x01, 0x0A01, "cut rope of door in bow room" }, + { 0x0B, 0x20, 0x0B20, "cut rope of door at top floor of main room" }, + { 0x0B, 0x10, 0x0B10, "pressed first button main room floor 2" }, + { 0x0B, 0x08, 0x0B08, "reach west locked door in main room bottom floor" }, + { 0x0B, 0x04, 0x0B04, "lowered platform to open gate in first room (read only)" }, + { 0x0C, 0x80, 0x0C80, "unlocked north door in toadpoli room" }, + { 0x0C, 0x40, 0x0C40, "pressed second button main room floor 2" }, + { 0x0C, 0x20, 0x0C20, "unlock west locked door in main room bottom floor" }, + { 0x0C, 0x08, 0x0C08, "killed right beamos in toadpoli room" }, + { 0x0C, 0x04, 0x0C04, "killed left beamos in toadpoli room" }, + { 0x0D, 0x80, 0x0D80, "pressed button outside room cs" }, + { 0x0D, 0x40, 0x0D40, "pressed button at top floor of main room" }, + { 0x0D, 0x08, 0x0D08, "unlocked east door outside room" }, + { 0x0D, 0x01, 0x0D01, "broke underwater wood barrier outside room" }, + { 0x0E, 0x80, 0x0E80, "replace tektites with toadpolis in toadpoli room (set after obtaining bow)" }, + { 0x0E, 0x20, 0x0E20, "main room intro cs" }, + { 0x0E, 0x10, 0x0E10, "main room intro cs" }, + { 0x0E, 0x08, 0x0E08, "broke second wood barrier in outside room" }, + { 0x0E, 0x04, 0x0E04, "broke first wood barrier in outside room" }, + { 0x0E, 0x02, 0x0E02, "Oocco Freed" }, + { 0x0E, 0x01, 0x0E01, "elder 2 lets you climb ladder in his room" }, + { 0x0F, 0x80, 0x0F80, "elder 1 lets you climb ladder in his room" }, + { 0x0F, 0x40, 0x0F40, "pulled beamos outside room" }, + { 0x0F, 0x20, 0x0F20, "outside room intro cs" }, + { 0x0F, 0x10, 0x0F10, "outside room getting shot at after mini-boss cs" }, + { 0x0F, 0x08, 0x0F08, "outside room getting shot at after mini-boss cs" }, + { 0x0F, 0x04, 0x0F04, "outside room intro cs" }, + { 0x10, 0x80, 0x1080, "room after bow intro cs" }, + { 0x10, 0x40, 0x1040, "pulled beamos outside room cs" }, + { 0x10, 0x20, 0x1020, "activate first force field in toadpoli room" }, + { 0x10, 0x10, 0x1010, "pressed first button in toadpoli room cs" }, + { 0x10, 0x08, 0x1008, "open gate in toadpoli room cs" }, + { 0x10, 0x04, 0x1004, "pressed second button in toadpoli room cs" }, + { 0x10, 0x02, 0x1002, "activate second force field in toadpoli room" }, + { 0x10, 0x01, 0x1001, "room before elder 1 intro cs" }, + { 0x11, 0x80, 0x1180, "room before elder 1 intro cs" }, + { 0x11, 0x20, 0x1120, "intro cs" }, + { 0x11, 0x10, 0x1110, "pressed first button in first room cs" }, + { 0x11, 0x08, 0x1108, "open gate in first room cs" }, + { 0x11, 0x04, 0x1104, "pressed button in room after bow" }, + { 0x11, 0x02, 0x1102, "hit switch in room after bow" }, + { 0x11, 0x01, 0x1101, "press button room 3" }, + { 0x12, 0x80, 0x1280, "killed south-east beamos bow room" }, + { 0x12, 0x40, 0x1240, "killed south-west beamos bow room" }, + { 0x12, 0x20, 0x1220, "killed west beamos bow room" }, + { 0x12, 0x10, 0x1210, "killed north-east beamos bow room" }, + { 0x12, 0x08, 0x1208, "killed north-west beamos bow room" }, + { 0x12, 0x04, 0x1204, "killed east beamos bow room" }, + { 0x12, 0x02, 0x1202, "killed south beamos bow room" }, + { 0x12, 0x01, 0x1201, "pulled south-east beamos bow room" }, + { 0x13, 0x80, 0x1380, "pulled south-west beamos bow room" }, + { 0x13, 0x40, 0x1340, "pulled west beamos bow room" }, + { 0x13, 0x08, 0x1308, "pulled east beamos bow room" }, + { 0x13, 0x04, 0x1304, "pulled south beamos bow room" }, + { 0x13, 0x02, 0x1302, "pressed first button in toadpoli room" }, + { 0x13, 0x01, 0x1301, "pressed second button in toadpoli room" }, + { 0x15, 0x01, 0x1501, "reached area past pullable wall in room 3 (first in path to elder 1)" }, + { 0x16, 0x10, 0x1610, "reached bottom of water in room before elder 1" }, + { 0x16, 0x08, 0x1608, "reached area past pullable wall in room 3 (first in path to elder 1)" }, + { 0x16, 0x04, 0x1604, "knocked down fence in room after bow" }, + { 0x16, 0x02, 0x1602, "pullable wall in room 3 closed itself" }, + { 0x17, 0x80, 0x1780, "outside room killed leader bulblin archer" }, + { 0x17, 0x20, 0x1720, "cut rope of door outside room" }, + { 0x17, 0x10, 0x1710, "pressed unerwater button in room before elder" }, + { 0x17, 0x08, 0x1708, "main room floor 2 intro cs" }, + { 0x17, 0x04, 0x1704, "main room floor 2 intro cs" }, + { 0x17, 0x02, 0x1702, "hit switch in room after bow cs" }, + { 0x17, 0x01, 0x1701, "room after bow intro cs" }, +}; + +inline EventAreaFlags eventAreaFlagsGrotto[] = +{ + { 0x01, 0x20, 0x0120, "big chest grotto 5-4" }, + { 0x01, 0x08, 0x0108, "big chest grotto 4-3" }, + { 0x01, 0x04, 0x0104, "left small chest grotto 2-0" }, + { 0x01, 0x02, 0x0102, "right small chest grotto 2-0" }, + { 0x01, 0x01, 0x0101, "small chest grotto 5-3" }, + { 0x02, 0x80, 0x0280, "big chest grotto 4-2" }, + { 0x02, 0x40, 0x0240, "big chest grotto 3-0" }, + { 0x02, 0x20, 0x0220, "south small chest grotto 2-1" }, + { 0x02, 0x10, 0x0210, "east small chest grotto 2-1" }, + { 0x02, 0x08, 0x0208, "north small chest grotto 2-1" }, + { 0x02, 0x04, 0x0204, "small chest grotto 1-1" }, + { 0x02, 0x01, 0x0201, "big chest grotto 4-1" }, + { 0x03, 0x80, 0x0380, "big chest grotto 1-2" }, + { 0x03, 0x40, 0x0340, "big chest grotto 1-1" }, + { 0x03, 0x20, 0x0320, "big chest grotto 5-2" }, + { 0x03, 0x10, 0x0310, "big chest grotto 4-0" }, + { 0x03, 0x08, 0x0308, "big chest grotto 2-2" }, + { 0x03, 0x04, 0x0304, "big chest grotto 2-0" }, + { 0x03, 0x02, 0x0302, "big chest grotto 5-0" }, + { 0x03, 0x01, 0x0301, "big chest grotto 1-0" }, + { 0x08, 0x80, 0x0880, "lit right torch grotto 1-1" }, + { 0x08, 0x40, 0x0840, "lit left torch grotto 1-1" }, + { 0x08, 0x20, 0x0820, "killed red chu-chu grotto 3-3" }, + { 0x08, 0x10, 0x0810, "killed chu-chu grotto 3-2" }, + { 0x08, 0x08, 0x0808, "killed blue chu-chu grotto 3-1" }, + { 0x08, 0x04, 0x0804, "killed chu-chu grotto 2-0" }, + { 0x08, 0x02, 0x0802, "killed chu-chu grotto 1-2" }, + { 0x08, 0x01, 0x0801, "killed right freezard grotto 4-2" }, + { 0x09, 0x80, 0x0980, "broke ice wall 1 grotto 4-2" }, + { 0x09, 0x40, 0x0940, "killed chu-chu grotto 1-1" }, + { 0x09, 0x20, 0x0920, "killed all enemies grotto 5-4 (spawm chest)" }, + { 0x09, 0x10, 0x0910, "killed left freezard grotto 4-2" }, + { 0x09, 0x08, 0x0908, "killed blue chu-chu grotto 3-3" }, + { 0x09, 0x04, 0x0904, "broke ice wall 2 grotto 4-2" }, + { 0x09, 0x02, 0x0902, "broke ice wall 4 grotto 4-2" }, + { 0x09, 0x01, 0x0901, "killed poe in the back entrance grotto 3-0" }, + { 0x0A, 0x80, 0x0A80, "killed poe next to entrance grotto 3-0" }, + { 0x0A, 0x40, 0x0A40, "broke ice wall 3 grotto 4-2" }, + { 0x0A, 0x20, 0x0A20, "broke ice wall 6 grotto 4-2" }, + { 0x0A, 0x10, 0x0A10, "broke ice wall 5 grotto 4-2" }, + { 0x0A, 0x08, 0x0A08, "killed middle poe grotto 1-3" }, + { 0x0A, 0x04, 0x0A04, "killed right poe grotto 1-3" }, + { 0x0A, 0x02, 0x0A02, "spawn big chest grotto 1-1" }, + { 0x0A, 0x01, 0x0A01, "killed all enemies grotto 4-1 (spawm chest)" }, + { 0x0B, 0x80, 0x0B80, "killed middle freezard grotto 4-2" }, + { 0x0B, 0x40, 0x0B40, "blown up rocks in grotto 3-2" }, + { 0x0B, 0x20, 0x0B20, "killed all enemies grotto 5-2 (spawm chest)" }, + { 0x0B, 0x10, 0x0B10, "killed all enemies grotto 4-0 (spawm chest)" }, + { 0x0B, 0x08, 0x0B08, "killed all enemies grotto 2-2 (spawm chest)" }, + { 0x0B, 0x04, 0x0B04, "killed all enemies grotto 2-0 (spawm chest)" }, + { 0x0B, 0x02, 0x0B02, "killed all enemies grotto 5-0 (spawm chest)" }, + { 0x0B, 0x01, 0x0B01, "killed all enemies grotto 1-0 (spawm chest)" }, + { 0x0E, 0x10, 0x0E10, "spawn big chest grotto 4-3" }, + { 0x0E, 0x08, 0x0E08, "lit torch 3 grotto 4-3" }, + { 0x0E, 0x04, 0x0E04, "lit torch 2 grotto 4-3" }, + { 0x0E, 0x02, 0x0E02, "lit torch 1 grotto 4-3" }, + { 0x0E, 0x01, 0x0E01, "spawn big chest grotto 3-0" }, + { 0x0F, 0x80, 0x0F80, "lit right torch grotto 3-0" }, + { 0x0F, 0x40, 0x0F40, "lit middle torch grotto 3-0" }, + { 0x0F, 0x20, 0x0F20, "lit left torch grotto 3-0" }, + { 0x0F, 0x10, 0x0F10, "spawn big chest grotto 1-2" }, + { 0x0F, 0x08, 0x0F08, "lit middle torch grotto 1-2" }, + { 0x0F, 0x04, 0x0F04, "lit right torch grotto 1-2" }, + { 0x0F, 0x02, 0x0F02, "lit left torch grotto 1-2" }, + { 0x1A, 0x02, 0x1A02, "picked up yellow rupee from pot grotto 1-2" }, + { 0x1A, 0x01, 0x1A01, "picked up blue rupee from pot grotto 1-2" }, + { 0x1B, 0x80, 0x1B80, "picked up blue rupee from pot grotto 1-2" }, + { 0x1B, 0x40, 0x1B40, "picked up blue rupee from pot grotto 1-2" }, + { 0x1B, 0x20, 0x1B20, "picked up blue rupee from pot grotto 1-2" }, + { 0x1B, 0x10, 0x1B10, "picked up blue rupee from pot grotto 1-2" }, + { 0x1B, 0x08, 0x1B08, "picked up blue rupee from pot grotto 1-2" }, + { 0x1B, 0x04, 0x1B04, "picked up yellow rupee from pot grotto 1-2" }, + { 0x1B, 0x02, 0x1B02, "picked up red rupee from pot grotto 1-2" }, + { 0x1B, 0x01, 0x1B01, "picked up red rupee from pot grotto 1-2" }, +}; + +inline EventAreaFlags eventAreaFlagsHC[] = +{ + { 0x00, 0x40, 0x0040, "big chest behind first gate in graveyard" }, + { 0x00, 0x20, 0x0020, "second small chest treasure room" }, + { 0x00, 0x10, 0x0010, "eighth small chest treasure room" }, + { 0x00, 0x08, 0x0008, "seventh small chest treasure room" }, + { 0x00, 0x04, 0x0004, "sixth small chest treasure room" }, + { 0x00, 0x02, 0x0002, "big chest north room 2F" }, + { 0x01, 0x80, 0x0180, "fourth small chest treasure room" }, + { 0x01, 0x40, 0x0140, "fifth small chest treasure room" }, + { 0x01, 0x20, 0x0120, "third small chest treasure room" }, + { 0x01, 0x08, 0x0108, "fifth big chest treasure room" }, + { 0x01, 0x04, 0x0104, "fourth big chest treasure room" }, + { 0x01, 0x02, 0x0102, "third big chest treasure room" }, + { 0x01, 0x01, 0x0101, "second big chest treasure room" }, + { 0x02, 0x80, 0x0280, "first big chest treasure room" }, + { 0x02, 0x20, 0x0220, "north west big chest center room 2F" }, + { 0x02, 0x08, 0x0208, "small chest east garden" }, + { 0x02, 0x02, 0x0202, "north small chest west garden" }, + { 0x02, 0x01, 0x0201, "compass north east big chest center room 2F" }, + { 0x03, 0x80, 0x0380, "small key big chest behind third gate in graveyard" }, + { 0x03, 0x20, 0x0320, "center small chest west garden" }, + { 0x03, 0x10, 0x0310, "map big chest east garden" }, + { 0x03, 0x08, 0x0308, "king bulblin small key" }, + { 0x03, 0x04, 0x0304, "south west big chest center room 2F" }, + { 0x03, 0x02, 0x0302, "small key big chest outside 2F" }, + { 0x03, 0x01, 0x0301, "big key chest" }, + { 0x07, 0x08, 0x0708, "first small chest treasure room" }, + { 0x07, 0x04, 0x0704, "east small chest behind first gate in graveyard" }, + { 0x07, 0x02, 0x0702, "west small chest behind first gate in graveyard" }, + { 0x08, 0x10, 0x0810, "killed darknut in north room 2F (spawn big chest)" }, + { 0x08, 0x08, 0x0808, "reach end of specter maze 3F" }, + { 0x08, 0x04, 0x0804, "darknut 4F intro cs part 2" }, + { 0x08, 0x02, 0x0802, "darknut 4F intro cs part 1" }, + { 0x08, 0x01, 0x0801, "cut painting in north east room 2F cs" }, + { 0x09, 0x80, 0x0980, "cut painting in north east room 2F cs" }, + { 0x09, 0x40, 0x0940, "killed all enemies in center room 1F cs 1/2" }, + { 0x09, 0x20, 0x0920, "spawn north east big chest center room 2F" }, + { 0x09, 0x10, 0x0910, "killed lizalfos garding big key chest" }, + { 0x09, 0x08, 0x0908, "open third gate in graveyard cs" }, + { 0x09, 0x04, 0x0904, "blown up rock in graveyard" }, + { 0x09, 0x02, 0x0902, "lit torch behind first gate in graveyard (stops rain)" }, + { 0x09, 0x01, 0x0901, "graveyard intro cs" }, + { 0x0A, 0x80, 0x0A80, "open second gate in graveyard cs" }, + { 0x0A, 0x40, 0x0A40, "open first gate in graveyard cs" }, + { 0x0A, 0x20, 0x0A20, "open third gate in graveyard" }, + { 0x0A, 0x10, 0x0A10, "open first gate in graveyard" }, + { 0x0A, 0x04, 0x0A04, "open second gate in graveyard" }, + { 0x0B, 0x01, 0x0B01, "defeated dark beast Ganon" }, + { 0x0C, 0x40, 0x0C40, "cut first left painting in north west room 2F" }, + { 0x0C, 0x20, 0x0C20, "cut second left painting in north west room 2F" }, + { 0x0C, 0x10, 0x0C10, "cut third left painting in north west room 2F" }, + { 0x0C, 0x02, 0x0C02, "killed all enemies in center room 1F" }, + { 0x0D, 0x80, 0x0D80, "despawn yellow magic walls in center room 1F" }, + { 0x0D, 0x40, 0x0D40, "spawn yellow magic walls in center room 1F" }, + { 0x0D, 0x20, 0x0D20, "outside 4F intro cs" }, + { 0x0D, 0x10, 0x0D10, "outside 4F intro cs" }, + { 0x0D, 0x08, 0x0D08, "killed all east bokoblins in south garden" }, + { 0x0D, 0x04, 0x0D04, "killed all west bokoblins in south garden" }, + { 0x0D, 0x01, 0x0D01, "east room 2F intro cs" }, + { 0x0E, 0x80, 0x0E80, "east room 2F intro cs" }, + { 0x0E, 0x10, 0x0E10, "killed right lezalfos in north west room 2F" }, + { 0x0E, 0x08, 0x0E08, "killed left lezalfos in north west room 2F" }, + { 0x0E, 0x01, 0x0E01, "midna text at the end of east garden (talk about wall)" }, + { 0x0F, 0x80, 0x0F80, "explored graveyard" }, + { 0x10, 0x80, 0x1080, "killed both lizalfos in 4F (removes yellow magic barrior)" }, + { 0x10, 0x40, 0x1040, "killed both lizalfos in 3F (removes yellow magic barrior)" }, + { 0x10, 0x20, 0x1020, "south garden intro cs" }, + { 0x10, 0x02, 0x1002, "Midna text prompt after king bulblin" }, + { 0x10, 0x01, 0x1001, "killed all bokoblins in west garden" }, + { 0x11, 0x80, 0x1180, "prevent all torches form extinguishing north east room 2F" }, + { 0x11, 0x40, 0x1140, "unlock door in north east room 2F cs" }, + { 0x11, 0x10, 0x1110, "open second gate in west garden" }, + { 0x11, 0x08, 0x1108, "east garden intro cs" }, + { 0x11, 0x04, 0x1104, "east garden intro cs" }, + { 0x11, 0x02, 0x1102, "open gate in east garden" }, + { 0x11, 0x01, 0x1101, "open gate in east garden cs" }, + { 0x12, 0x80, 0x1280, "unlock door in north east room 2F" }, + { 0x12, 0x40, 0x1240, "lit all torches correctly in north east room 2F" }, + { 0x12, 0x20, 0x1220, "lit all torches correctly in north east room 2F cs" }, + { 0x12, 0x10, 0x1210, "unlock door outside 3F" }, + { 0x12, 0x08, 0x1208, "open gate to big key chest" }, + { 0x12, 0x04, 0x1204, "killed arealfos outside 2F" }, + { 0x12, 0x01, 0x1201, "killed all enemies in center room 1F cs 1/2" }, + { 0x13, 0x40, 0x1340, "Double Darknut room intro cs" }, + { 0x13, 0x08, 0x1308, "spawn north west big chest center room 2F" }, + { 0x13, 0x04, 0x1304, "west room 2F intro cs" }, + { 0x13, 0x02, 0x1302, "cut right painting in north west room 2F" }, + { 0x14, 0x40, 0x1440, "unlock boss door" }, + { 0x14, 0x10, 0x1410, "unlock door in south garden" }, + { 0x14, 0x08, 0x1408, "midna text seen after small key in graveyard" }, + { 0x14, 0x04, 0x1404, "midna text promt after small key in graveyard" }, + { 0x14, 0x02, 0x1402, "Midna text seen after king bulblin" }, + { 0x14, 0x01, 0x1401, "north west room 2F intro cs" }, + { 0x15, 0x40, 0x1540, "cut painting in north east room 2F" }, + { 0x15, 0x08, 0x1508, "second gate in west garden stops moving" }, + { 0x15, 0x04, 0x1504, "open second gate in west garden cs" }, + { 0x15, 0x01, 0x1501, "killed dynalfos in east room 2F (unlocks doors)" }, + { 0x16, 0x80, 0x1680, "unlock treasure room door 4F" }, + { 0x16, 0x04, 0x1604, "killed darknuts in west room 2F (unlocks doors)" }, + { 0x16, 0x02, 0x1602, "reach big chest in north room 2F" }, + { 0x16, 0x01, 0x1601, "lit south east torch north room 2F for the first time cs" }, + { 0x17, 0x80, 0x1780, "lit north east torch north room 2F for the first time cs" }, + { 0x17, 0x10, 0x1710, "unlock door in north west room 2F" }, + { 0x17, 0x01, 0x1701, "killed darknut 4F (removes yellow magic barrier)" }, +}; + +inline EventAreaFlags eventAreaFlagsHyruleField[] = +{ + { 0x02, 0x80, 0x0280, "Big chest owl statue hylia bridge" }, + { 0x02, 0x40, 0x0240, "Big chest spinner south of castle town" }, + { 0x02, 0x20, 0x0220, "Big chest double clawshot south of castle town" }, + { 0x02, 0x10, 0x0210, "Big chest tight rope south of castle town" }, + { 0x02, 0x08, 0x0208, "Big chest owl statue next to castle town" }, + { 0x02, 0x04, 0x0204, "Big chest on hylia bridge" }, + { 0x02, 0x02, 0x0202, "Big chest next to poe past hylia bridge" }, + { 0x02, 0x01, 0x0201, "Big chest spinner tracks (Lanayru)" }, + { 0x03, 0x80, 0x0380, "Big chest underwater Lanayru field" }, + { 0x03, 0x40, 0x0340, "Big chest under bridge faron field" }, + { 0x03, 0x20, 0x0320, "PoH big chest double clawshot Eldin gorge" }, + { 0x03, 0x10, 0x0310, "Big chest Eldin gorge owl statue" }, + { 0x03, 0x08, 0x0308, "PoH big chest owl statue Eldin bridge" }, + { 0x03, 0x04, 0x0304, "PoH big chest leage Eldin field after kakariko" }, + { 0x03, 0x02, 0x0302, "Map marker Telma carrage past hylia bridge" }, + { 0x03, 0x01, 0x0301, "Map marker Telma carrage west of castle town" }, + { 0x08, 0x80, 0x0880, "Lanayru field intro cs twilight" }, + { 0x08, 0x20, 0x0820, "blown up rock blocking Eldin long cave" }, + { 0x08, 0x10, 0x0810, "Hidden village path open" }, + { 0x08, 0x08, 0x0808, "East Castle Town Bridge Flag" }, + { 0x08, 0x04, 0x0804, "blown up rocks blocking path to zora's domain" }, + { 0x08, 0x01, 0x0801, "Midna pulls up the map to show you can warp" }, + { 0x09, 0x80, 0x0980, "midna text Eldin gorge bridge gone" }, + { 0x09, 0x40, 0x0940, "exit flight by foul after lanayru twilight cleared" }, + { 0x09, 0x20, 0x0920, "Kakariko Gorge Portal" }, + { 0x09, 0x10, 0x0910, "spawn barriers Eldin gorge dark beasts (unset once killed)" }, + { 0x09, 0x08, 0x0908, "bridge of Eldin placed (cs)" }, + { 0x09, 0x04, 0x0904, "Eldin bridge getting stolen cs trigger" }, + { 0x09, 0x02, 0x0902, "blown up rocks past Eldin bridge that lead to Lanayru" }, + { 0x09, 0x01, 0x0901, "see gorge gate cs" }, + { 0x0A, 0x80, 0x0A80, "blown up rocks to spinner tracks (Lanayru field side)" }, + { 0x0A, 0x40, 0x0A40, "blown up rocks to spinner tracks (hylia bridge side)" }, + { 0x0A, 0x20, 0x0A20, "entered Lanayru twilight cs" }, + { 0x0A, 0x10, 0x0A10, "entered Eldin twilight cs" }, + { 0x0A, 0x08, 0x0A08, "killed left shadow bulblin behind gorge gate twilight" }, + { 0x0A, 0x04, 0x0A04, "killed right shadow bulblin behind gorge gate twilight" }, + { 0x0A, 0x02, 0x0A02, "jumped over gorge fence after obtaining Epona" }, + { 0x0B, 0x80, 0x0B80, "Lanayru Main Feild has water on map" }, + { 0x0B, 0x40, 0x0B40, "seeing Lanayru twilight from up close cs" }, + { 0x0B, 0x20, 0x0B20, "seeing Eldin twilight from up close cs" }, + { 0x0B, 0x10, 0x0B10, "spawn dark beasts castle town portal (stays set)" }, + { 0x0B, 0x08, 0x0B08, "Castle Town Portal" }, + { 0x0B, 0x04, 0x0B04, "?" }, + { 0x0B, 0x01, 0x0B01, "blown up rocks from Eldin gorge to Eldin field" }, + { 0x0C, 0x80, 0x0C80, "blown up second rock to poe past hylia bridge" }, + { 0x0C, 0x20, 0x0C20, "Epona is accessible to Link (without calling her) (after lanayru twilight)" }, + { 0x0C, 0x08, 0x0C08, "killed poe past hylia bridge" }, + { 0x0C, 0x04, 0x0C04, "killed poe Eldin gorge" }, + { 0x0C, 0x02, 0x0C02, "killed poe faron field" }, + { 0x0D, 0x80, 0x0D80, "Midna text after warping gorge bridge back" }, + { 0x0D, 0x40, 0x0D40, "?" }, + { 0x0D, 0x10, 0x0D10, "spinner path next to hidden village explored" }, + { 0x0D, 0x08, 0x0D08, "killed poe Lanayru field" }, + { 0x0D, 0x02, 0x0D02, "enter field west of castle town from the east" }, + { 0x0D, 0x01, 0x0D01, "killed poe south of castle town" }, + { 0x0E, 0x80, 0x0E80, "bridge of Hylia intro cs twilight" }, + { 0x0E, 0x40, 0x0E40, "blown up rock blocking ice cave Lanayru field" }, + { 0x0E, 0x20, 0x0E20, "faron field intro cs" }, + { 0x0E, 0x10, 0x0E10, "blown up rock on leadge Eldin field after kakariko" }, + { 0x0E, 0x08, 0x0E08, "blown up first rock to poe past hylia bridge" }, + { 0x0E, 0x04, 0x0E04, "Hero's shade map marker (south castle town)" }, + { 0x0E, 0x02, 0x0E02, "Hero's shade map marker (west castle town)" }, + { 0x0E, 0x01, 0x0E01, "enter bridge east of castle town" }, + { 0x0F, 0x80, 0x0F80, "enter field south of castle town" }, + { 0x0F, 0x40, 0x0F40, "enter field west of castle town from the north" }, + { 0x0F, 0x20, 0x0F20, "?" }, + { 0x0F, 0x10, 0x0F10, "open path from faron field to south of castle town" }, + { 0x0F, 0x08, 0x0F08, "Midna text after Lanayru field twilight cs" }, + { 0x0F, 0x04, 0x0F04, "blown up rocks Eldin field after kakariko" }, + { 0x0F, 0x02, 0x0F02, "Eldin gorge bridge placed cs" }, + { 0x0F, 0x01, 0x0F01, "see Ilia's bag from far away" }, + { 0x10, 0x01, 0x1001, "Zora rivver boat path on map (Eldin)" }, + { 0x11, 0x80, 0x1180, "Sky letter next to castle town" }, + { 0x11, 0x40, 0x1140, "Map marker owl stature next to castle town" }, + { 0x11, 0x20, 0x1120, "Sky letter hylia bridge" }, + { 0x11, 0x10, 0x1110, "Map marker owl stature Hylia bridge" }, + { 0x11, 0x08, 0x1108, "Sky letter Eldin bridge" }, + { 0x11, 0x04, 0x1104, "Map marker owl stature Eldin bridge" }, + { 0x11, 0x02, 0x1102, "Sky letter Eldin gorge" }, + { 0x11, 0x01, 0x1101, "Map marker owl stature Eldin gorge" }, + { 0x12, 0x10, 0x1210, "blown up northern rock in field west of castle town" }, + { 0x12, 0x08, 0x1208, "blown up most northern rock in field west of castle town" }, + { 0x12, 0x04, 0x1204, "blown up rock south of castle town" }, + { 0x12, 0x02, 0x1202, "killed poe next to owl statue next to castle town" }, + { 0x13, 0x80, 0x1380, "killed poe east of castle town" }, + { 0x13, 0x10, 0x1310, "?" }, + { 0x13, 0x08, 0x1308, "?" }, + { 0x13, 0x04, 0x1304, "Epona isn't accessible to Link (without calling her) (after lanayru twilight)" }, + { 0x13, 0x02, 0x1302, "?" }, + { 0x13, 0x01, 0x1301, "Map marker hidden village" }, + { 0x14, 0x80, 0x1480, "blown up southern rock underwater Lanayru field" }, + { 0x14, 0x40, 0x1440, "blown up northern rock underwater Lanayru field" }, + { 0x14, 0x20, 0x1420, "blown up eastern rock Lanayru field " }, + { 0x14, 0x10, 0x1410, "blown up rock next to hylia bridge owl statue" }, + { 0x14, 0x08, 0x1408, "blown up rock closest to faron after hylia bridge" }, + { 0x14, 0x04, 0x1404, "blown up rock next to free standing PoH Elding gorge" }, + { 0x14, 0x01, 0x1401, "blown up rock next to Eldin gorge owl statue" }, + { 0x15, 0x80, 0x1580, "blown up rock past Eldin bridge" }, + { 0x16, 0x20, 0x1620, "Midna text after getting Ilia's scent" }, + { 0x16, 0x10, 0x1610, "Midna text after entering Lanayru twilight" }, + { 0x16, 0x08, 0x1608, "Midna text when seeing Lanayru twilight from far away" }, + { 0x16, 0x04, 0x1604, "Midna text after getting Youth's scent" }, + { 0x16, 0x02, 0x1602, "Midna text after entering Eldin twilight" }, + { 0x16, 0x01, 0x1601, "Midna text when seeing Eldin twilight from far away" }, + { 0x17, 0x80, 0x1780, "got Ilia's scent cs" }, + { 0x17, 0x40, 0x1740, "got youth's scent cs" }, + { 0x17, 0x20, 0x1720, "Epona can't cross Eldin bridge (set when stolen, unset when fixed)" }, + { 0x17, 0x10, 0x1710, "see broken wooden sword from far away cs" }, + { 0x17, 0x08, 0x1708, "Bridge of Eldin Portal" }, + { 0x1A, 0x80, 0x1A80, "picked up rupees from rock past Eldin bridge" }, + { 0x1A, 0x40, 0x1A40, "picked up rupees from rock next to free standing PoH Elding gorge" }, + { 0x1A, 0x20, 0x1A20, "picked up rupees from rock next to Eldin gorge owl statue" }, + { 0x1A, 0x10, 0x1A10, "picked up rupees from rock closest to faron after hylia bridge" }, + { 0x1A, 0x08, 0x1A08, "picked up rupees from rock next to hylia bridge owl statue" }, + { 0x1A, 0x04, 0x1A04, "picked up rupees from rocks to spinner tracks (hylia bridge side)" }, + { 0x1A, 0x02, 0x1A02, "picked up rupees from rocks to spinner tracks (Lanayru field side)" }, + { 0x1A, 0x01, 0x1A01, "picked up rupees from eastern rock Lanayru field " }, + { 0x1B, 0x80, 0x1B80, "picked up rupees from northern rock underwater Lanayru field" }, + { 0x1B, 0x40, 0x1B40, "picked up rupees from southern rock underwater Lanayru field" }, + { 0x1B, 0x20, 0x1B20, "picked up rupees from northern rock in field west of castle town" }, + { 0x1B, 0x10, 0x1B10, "picked up rupees from most northern rock in field west of castle town" }, + { 0x1B, 0x08, 0x1B08, "picked up rupees from rock south of castle town" }, + { 0x1B, 0x04, 0x1B04, "free standing PoH Elding gorge" }, + { 0x1B, 0x02, 0x1B02, "free standing PoH faron field" }, + { 0x1B, 0x01, 0x1B01, "free standing PoH goron Eldin field" }, +}; + +inline EventAreaFlags eventAreaFlagsLanayru[] = +{ + { 0x00, 0x40, 0x0040, "small chest next to mother and child iles zora's domain" }, + { 0x00, 0x20, 0x0020, "big chest fountain back room" }, + { 0x00, 0x10, 0x0010, "left small chest fountain back room" }, + { 0x00, 0x08, 0x0008, "right small chest fountain back room" }, + { 0x00, 0x04, 0x0004, "small chest midna jumps zora's domain" }, + { 0x01, 0x10, 0x0110, "west big chest zora's domain throne room" }, + { 0x01, 0x08, 0x0108, "east big chest zora's domain throne room" }, + { 0x02, 0x10, 0x0210, "small chest large underwater pillar in fountain" }, + { 0x02, 0x04, 0x0204, "small chest small underwater pillar in fountain" }, + { 0x02, 0x01, 0x0201, "small chest floor 3 flight by foul" }, + { 0x03, 0x80, 0x0380, "small chest floor 2 flight by foul" }, + { 0x03, 0x40, 0x0340, "big chest right pillar in front of fountain Lake Hylia" }, + { 0x03, 0x20, 0x0320, "big chest underwater next to Lake Hylia warp" }, + { 0x03, 0x10, 0x0310, "east big chest fountain" }, + { 0x03, 0x08, 0x0308, "west big chest fountain" }, + { 0x03, 0x04, 0x0304, "big chest floor 4 flight by foul" }, + { 0x03, 0x02, 0x0302, "small chest left pillar in front of fountain Lae Hylia" }, + { 0x03, 0x01, 0x0301, "big chest floor 5 flight by foul" }, + { 0x04, 0x40, 0x0440, "tear of light throne room zora's domain" }, + { 0x04, 0x20, 0x0420, "first tear of light flying" }, + { 0x04, 0x10, 0x0410, "tear of light midna jumps zora's domain" }, + { 0x04, 0x08, 0x0408, "tear of light on top of water zora's domain" }, + { 0x04, 0x04, 0x0404, "third tear of light flying" }, + { 0x04, 0x02, 0x0402, "fourth tear of light flying" }, + { 0x05, 0x80, 0x0580, "tear of light next to Iza's shop" }, + { 0x05, 0x40, 0x0540, "second tear of light flying" }, + { 0x05, 0x20, 0x0520, "final tear of light" }, + { 0x05, 0x04, 0x0504, "tear of light behind of Fyer's canon" }, + { 0x05, 0x02, 0x0502, "tear of light island east of Lake Hylia" }, + { 0x05, 0x01, 0x0501, "tear of light southmost island Lake Hylia" }, + { 0x06, 0x80, 0x0680, "tear of light next to fountain Lake Hylia" }, + { 0x06, 0x40, 0x0640, "tear of light on top of water zora's domain" }, + { 0x06, 0x20, 0x0620, "tear of light next to mother and child iles zora's domain" }, + { 0x08, 0x80, 0x0880, "see frozen zora spirits in domain twilight" }, + { 0x08, 0x40, 0x0840, "Lake hylia intro cs twilight" }, + { 0x08, 0x20, 0x0820, "blown up underwater rock in center of fountain" }, + { 0x08, 0x10, 0x0810, "the two zoras in upper zora river went down stream (twilight)" }, + { 0x08, 0x08, 0x0808, "saw light bug come out next to Iza spirit cs" }, + { 0x08, 0x02, 0x0802, "blown up rock to Lake hylia long cave" }, + { 0x08, 0x01, 0x0801, "spawned UZR portal fight barriers" }, + { 0x09, 0x80, 0x0980, "opened Upper Zora's River Portal cs" }, + { 0x09, 0x40, 0x0940, "talked to Iza before UZR portal" }, + { 0x09, 0x20, 0x0920, "Upper Zora's River Portal" }, + { 0x09, 0x10, 0x0910, "spawn big chest fountain back room" }, + { 0x09, 0x08, 0x0908, "lit left torch fountain back room" }, + { 0x09, 0x04, 0x0904, "lit right torch fountain back room" }, + { 0x09, 0x02, 0x0902, "seeing Twilight Bloat with sense cs" }, + { 0x09, 0x01, 0x0901, "Twilight Bloat comes out of water cs" }, + { 0x0A, 0x80, 0x0A80, "seeing Twilit Bloat move from far away cs" }, + { 0x0A, 0x40, 0x0A40, "Save Prompt after Lakebed (if disabled, triggers MDH after Lakebed)" }, + { 0x0A, 0x20, 0x0A20, "twilight end cs trigger (also map marker Lakebed Temple)" }, + { 0x0A, 0x10, 0x0A10, "obtained vessel of light (unset after twilight)" }, + { 0x0A, 0x08, 0x0A08, "spawn dark beasts Lake Hylia (unset once killed)" }, + { 0x0A, 0x04, 0x0A04, "Lake Hylia Portal" }, + { 0x0A, 0x02, 0x0A02, "Zora's river intro cs during twilight (flying with bird)" }, + { 0x0A, 0x01, 0x0A01, "midna text promt saying you can call down Kargarok to fly" }, + { 0x0B, 0x80, 0x0B80, "Rutella cs in domain twilight" }, + { 0x0B, 0x40, 0x0B40, "Zora river boat path on map" }, + { 0x0B, 0x20, 0x0B20, "entered Twilight Bloat arena (unset once killed)" }, + { 0x0B, 0x10, 0x0B10, "explored entrance to snowpeak" }, + { 0x0B, 0x08, 0x0B08, "got PoH flight by foul" }, + { 0x0B, 0x04, 0x0B04, "Zora's Domain Portal" }, + { 0x0B, 0x02, 0x0B02, "spawn barriers Zora's domain dark beasts fight" }, + { 0x0B, 0x01, 0x0B01, "Zora's domain intro cs twilight (frozen)" }, + { 0x0C, 0x08, 0x0C08, "blown up south underwater rock zora's domain " }, + { 0x0C, 0x04, 0x0C04, "blown up north underwater rock zora's domain" }, + { 0x0C, 0x02, 0x0C02, "blown up underwater rock in back of fountain" }, + { 0x0C, 0x01, 0x0C01, "Midna text after landing in Lake hylia twilight" }, + { 0x0D, 0x80, 0x0D80, "Iza text after Upper Zora's River Portal" }, + { 0x0D, 0x40, 0x0D40, "can now exit from the sides of zora's domain inside (set during domain outside cs after melting it)" }, + { 0x0D, 0x20, 0x0D20, "Midna text after domain outside cs after melting it" }, + { 0x0D, 0x10, 0x0D10, "domain outside cs after melting it" }, + { 0x0D, 0x08, 0x0D08, "map marker Auru" }, + { 0x0D, 0x04, 0x0D04, "Upper zora's river intro cs during twilight" }, + { 0x0D, 0x02, 0x0D02, "Midna text after leaving lake hylia after Lanayru twilight" }, + { 0x0D, 0x01, 0x0D01, "Zora's domain waterfall is going fast (during twilight only) (unset when you come back to Lake hylia)" }, + { 0x0E, 0x80, 0x0E80, "blown up rock blocking lakebed entrance cs" }, + { 0x0E, 0x40, 0x0E40, "opened stream next to lakebed entance" }, + { 0x0E, 0x20, 0x0E20, "Midna text after arriving at upper zora's river twilight" }, + { 0x0E, 0x10, 0x0E10, "melted zora's domain (Lake Hylia water on map (top part))" }, + { 0x0E, 0x08, 0x0E08, "twilight end cs watched" }, + { 0x0E, 0x04, 0x0E04, "map marker Snowpeak" }, + { 0x0E, 0x01, 0x0E01, "Midna text after coming back to lake after filling it (twilight)" }, + { 0x0F, 0x80, 0x0F80, "blown up rock blocking lakebed entrance" }, + { 0x0F, 0x40, 0x0F40, "went down the fast water in zora's domain (void out) (unset after twilight)" }, + { 0x0F, 0x20, 0x0F20, "Lake hylia filled twilight intro cs (domain water level normal)" }, + { 0x0F, 0x10, 0x0F10, "blown up first rock to throne room zora's domain" }, + { 0x0F, 0x08, 0x0F08, "Midna text prompt to tell you to look under the ice in zora's domain twilight" }, + { 0x0F, 0x04, 0x0F04, "Midna text before midna jumps fozen zora's domain twilight" }, + { 0x0F, 0x02, 0x0F02, "Midna text before midna jumps melted zora's domain twilight" }, + { 0x0F, 0x01, 0x0F01, "Midna text after melting domain (water everywhere on map)" }, + { 0x10, 0x80, 0x1080, "blown up underwater rock zora river town path" }, + { 0x10, 0x40, 0x1040, "spawn west big chest zora's domain throne room" }, + { 0x10, 0x20, 0x1020, "spawn east big chest zora's domain throne room" }, + { 0x10, 0x10, 0x1010, "lit east torch zora's domain throne room" }, + { 0x10, 0x08, 0x1008, "extingushed north torch zora's domain throne room" }, + { 0x10, 0x04, 0x1004, "lit west torch zora's domain throne room" }, + { 0x10, 0x02, 0x1002, "blown up west underwater rock Lakebed area" }, + { 0x10, 0x01, 0x1001, "blown up south underwater rock Lakebed area" }, + { 0x11, 0x80, 0x1180, "saw the two zora spirits from far away in upper zora river cs (twilight)" }, + { 0x11, 0x40, 0x1140, "saw light bug moving after exiting lanayru spring" }, + { 0x11, 0x20, 0x1120, "went down zora's river with bird during twilight" }, + { 0x11, 0x10, 0x1110, "blown up underwater rock zora river lake path" }, + { 0x11, 0x08, 0x1108, "midna text after seeing the frozen zoras in domain twilight" }, + { 0x11, 0x02, 0x1102, "paid Fyer to launch you to the flight by foul platform (unset once you enter the canon)" }, + { 0x11, 0x01, 0x1101, "blown up north underwater rock throne room zora's domain" }, + { 0x12, 0x80, 0x1280, "Lanayru spring map marker" }, + { 0x12, 0x40, 0x1240, "Midna Text after frozen zora's domain twilight intro cs" }, + { 0x12, 0x20, 0x1220, "killed poe underneath flight by foul" }, + { 0x12, 0x10, 0x1210, "killed poe next to watch tower to desert" }, + { 0x12, 0x08, 0x1208, "killed poe east of lake Hylia" }, + { 0x12, 0x04, 0x1204, "killed poe next to mother and chile iles Zora's Domain" }, + { 0x12, 0x02, 0x1202, "killed poe midna jumps Zora's Domain" }, + { 0x12, 0x01, 0x1201, "killed poe in Upper Zora's River" }, + { 0x13, 0x80, 0x1380, "killed poe on flight by foul prizes" }, + { 0x13, 0x40, 0x1340, "killed poe next to fountain Lake Hylia" }, + { 0x13, 0x20, 0x1320, "blew up rock in Iza's house" }, + { 0x13, 0x10, 0x1310, "Iza text after blowing the rocks inside her house" }, + { 0x13, 0x01, 0x1301, "blew up rock in Iza's house cs" }, + { 0x14, 0x40, 0x1440, "killed light bug throne room zora's domain" }, + { 0x14, 0x20, 0x1420, "killed first light bug flying" }, + { 0x14, 0x10, 0x1410, "killed light bug midna jumps zora's domain" }, + { 0x14, 0x08, 0x1408, "killed light bug on top of water zora's domain" }, + { 0x14, 0x04, 0x1404, "killed third light bug flying" }, + { 0x14, 0x02, 0x1402, "killed fourth light bug flying" }, + { 0x14, 0x01, 0x1401, "killed light bug behind of Fyer's canon" }, + { 0x15, 0x80, 0x1580, "killed light bug next to Iza's shop" }, + { 0x15, 0x40, 0x1540, "killed second light bug flying" }, + { 0x15, 0x20, 0x1520, "killed final light bug (Twilight Bloat)" }, + { 0x15, 0x10, 0x1510, "killed light bug island east of Lake Hylia" }, + { 0x15, 0x02, 0x1502, "killed light bug next to mother and child iles zora's domain" }, + { 0x15, 0x01, 0x1501, "?" }, + { 0x16, 0x80, 0x1680, "Watched CS of Ooccoo running to Sky Cannon" }, + { 0x16, 0x40, 0x1640, "killed light bug on top of water zora's domain" }, + { 0x16, 0x20, 0x1620, "killed light bug southmost island Lake Hylia" }, + { 0x16, 0x10, 0x1610, "killed light bug next to fountain Lake Hylia" }, + { 0x16, 0x02, 0x1602, "talked to light spirit during twilight" }, + { 0x16, 0x01, 0x1601, "went up zora's river after melting domain in twilight" }, + { 0x17, 0x80, 0x1780, "blown up rock zora river on land" }, + { 0x17, 0x40, 0x1740, "went up zora's river after melting domain in twilight (also set when failing to do so)" }, + { 0x17, 0x20, 0x1720, "blown up second rock to throne room zora's domain" }, + { 0x17, 0x10, 0x1710, "?" }, + { 0x17, 0x02, 0x1702, "Midna text after Zora's domain portal" }, + { 0x17, 0x01, 0x1701, "blown up underwater rock zora river middle" }, + { 0x18, 0x08, 0x1808, "picked up rupees from south underwater rock Lakebed area" }, + { 0x18, 0x04, 0x1804, "picked up rupees from west underwater rock Lakebed area" }, + { 0x18, 0x02, 0x1802, "picked up green rupees domain midna jumps twilight" }, + { 0x18, 0x01, 0x1801, "picked up fourth green rupee domain midna jumps twilight" }, + { 0x19, 0x80, 0x1980, "picked up third blue rupee midna jumps zora's domain" }, + { 0x19, 0x40, 0x1940, "picked up second blue rupee midna jumps zora's domain" }, + { 0x19, 0x20, 0x1920, "picked up first blue rupee midna jumps zora's domain" }, + { 0x19, 0x10, 0x1910, "picked up rupees from rock zora river on land" }, + { 0x19, 0x08, 0x1908, "picked up rupees from underwater rock zora river lake path" }, + { 0x19, 0x04, 0x1904, "picked up rupees from underwater rock zora river town path" }, + { 0x19, 0x02, 0x1902, "picked up rupees from underwater rock zora river middle" }, + { 0x19, 0x01, 0x1901, "picked up rupees from underwater rock back fountain" }, + { 0x1A, 0x80, 0x1A80, "picked up rupees from underwater rock center fountain" }, + { 0x1A, 0x40, 0x1A40, "picked up rupees from south underwater rock zora's domain " }, + { 0x1A, 0x20, 0x1A20, "picked up rupees from north underwater rock zora's domain" }, + { 0x1A, 0x10, 0x1A10, "picked up rupees from first rock to throne room zora's domain" }, + { 0x1A, 0x08, 0x1A08, "picked up rupees from second rock to throne room zora's domain" }, + { 0x1A, 0x04, 0x1A04, "picked up east blue rupees behind underwater bridge throne room zora's domain" }, + { 0x1A, 0x02, 0x1A02, "picked up west blue rupees behind underwater bridge throne room zora's domain" }, + { 0x1A, 0x01, 0x1A01, "picked up south yellow rupee unerwater throne room zora's domain" }, + { 0x1B, 0x80, 0x1B80, "picked up north yellow rupee unerwater throne room zora's domain" }, + { 0x1B, 0x40, 0x1B40, "picked up east yellow rupee unerwater throne room zora's domain" }, + { 0x1B, 0x20, 0x1B20, "picked up yellow rupee above north underwater rock throne room zora's domain" }, + { 0x1B, 0x10, 0x1B10, "picked up first yellow rupee midna jumps zora's domain" }, + { 0x1B, 0x08, 0x1B08, "picked up second yellow rupee midna jumps zora's domain" }, + { 0x1B, 0x02, 0x1B02, "picked up yellow rupee from broken pillar lakebed area" }, + { 0x1B, 0x01, 0x1B01, "picked up yellow rupee from broken pillar lakebed area" }, +}; + +inline EventAreaFlags eventAreaFlagsLBT[] = +{ + { 0x00, 0x40, 0x0040, "small chest first west room 1F" }, + { 0x00, 0x20, 0x0020, "south small chest first room" }, + { 0x00, 0x10, 0x0010, "west small chest first room" }, + { 0x00, 0x08, 0x0008, "small chest second east room 4F" }, + { 0x00, 0x02, 0x0002, "underwater big chest in first west room 2F" }, + { 0x00, 0x01, 0x0001, "small chest first east room 2F" }, + { 0x01, 0x40, 0x0140, "Ooccoo Map Flag" }, + { 0x01, 0x20, 0x0120, "small chest second west room 4F" }, + { 0x01, 0x10, 0x0110, "compass big chest second west room 4F" }, + { 0x01, 0x08, 0x0108, "center small chest first west room 2F" }, + { 0x01, 0x04, 0x0104, "small chest in room before big key" }, + { 0x01, 0x02, 0x0102, "map big chest main room 1F" }, + { 0x01, 0x01, 0x0101, "big chest under boss door main room B1" }, + { 0x02, 0x80, 0x0280, "south underwater big chest in room before mini-boss" }, + { 0x02, 0x40, 0x0240, "north underwater big chest in room before mini-boss" }, + { 0x02, 0x20, 0x0220, "north west big chest first west room 2F" }, + { 0x02, 0x10, 0x0210, "big key chest" }, + { 0x02, 0x08, 0x0208, "big chest second east room 4F" }, + { 0x02, 0x04, 0x0204, "small key big chest first east room 2F" }, + { 0x02, 0x01, 0x0201, "PoH big chest behind gate in first east room 1F" }, + { 0x03, 0x80, 0x0380, "south small chest in first west room 2F" }, + { 0x03, 0x40, 0x0340, "small key big chest first east room 1F" }, + { 0x03, 0x20, 0x0320, "PoH big chest main room 2F" }, + { 0x03, 0x08, 0x0308, "small chest second room" }, + { 0x03, 0x04, 0x0304, "small key big chest room before mini-boss" }, + { 0x03, 0x02, 0x0302, "small chest main room 1F" }, + { 0x03, 0x01, 0x0301, "clawshot big chest mini-boss room" }, + { 0x08, 0x80, 0x0880, "raised water in first east room 1F" }, + { 0x08, 0x40, 0x0840, "water flowing into first east room 1F cs" }, + { 0x08, 0x20, 0x0820, "?" }, + { 0x08, 0x02, 0x0802, "Midna Stalactite text second room" }, + { 0x09, 0x80, 0x0980, "water flowing into first east room 1F" }, + { 0x09, 0x20, 0x0920, "west water flowing into stairs in main room" }, + { 0x09, 0x10, 0x0910, "east water flowing into stairs in main room" }, + { 0x09, 0x08, 0x0908, "water flowing into east wing 1F" }, + { 0x09, 0x04, 0x0904, "water flowing into west wing 1F" }, + { 0x09, 0x02, 0x0902, "water in main room raised twice" }, + { 0x09, 0x01, 0x0901, "water in main room raised once" }, + { 0x0A, 0x80, 0x0A80, "west water not flowing into stairs in main room" }, + { 0x0A, 0x40, 0x0A40, "east water not flowing into stairs in main room" }, + { 0x0A, 0x20, 0x0A20, "west water is flowing into main room" }, + { 0x0A, 0x10, 0x0A10, "staircase top is west main room" }, + { 0x0A, 0x08, 0x0A08, "staircase top is east main room" }, + { 0x0A, 0x04, 0x0A04, "east water is flowing into main room" }, + { 0x0A, 0x02, 0x0A02, "let water flow out of room west wing 2F" }, + { 0x0A, 0x01, 0x0A01, "let water flow out of room east wing 2F" }, + { 0x0B, 0x10, 0x0B10, "spawn clawshot big chest" }, + { 0x0B, 0x08, 0x0B08, "set staircase top is east main room" }, + { 0x0B, 0x04, 0x0B04, "set staircase top is north main room" }, + { 0x0B, 0x02, 0x0B02, "set staircase top is west main room" }, + { 0x0B, 0x01, 0x0B01, "set staircase top is south main room (default)" }, + { 0x0D, 0x40, 0x0D40, "PoH big chest behind gate in first east room 1F (set after)" }, + { 0x0D, 0x20, 0x0D20, "horizontal wheel is turning in first east room" }, + { 0x0D, 0x10, 0x0D10, "unlock east door main room 2F" }, + { 0x0D, 0x08, 0x0D08, "vertical wheel is turning in first east room 2F" }, + { 0x0D, 0x04, 0x0D04, "small key big chest first east room 1F (set after)" }, + { 0x0D, 0x02, 0x0D02, "east wing water switch 4F" }, + { 0x0D, 0x01, 0x0D01, "west wing water switch 4F" }, + { 0x0E, 0x04, 0x0E04, "opened north gate first east room 2F" }, + { 0x0E, 0x01, 0x0E01, "opened gate in first room" }, + { 0x0F, 0x80, 0x0F80, "blown up second rock in room before big key" }, + { 0x0F, 0x40, 0x0F40, "blown up first rock in room before big key" }, + { 0x0F, 0x10, 0x0F10, "unlocked door in second east room 2F" }, + { 0x0F, 0x08, 0x0F08, "unlocked door in room before mini-boss" }, + { 0x0F, 0x02, 0x0F02, "horizontal wheel is turning in first east room cs" }, + { 0x0F, 0x01, 0x0F01, "horizontal wheels turning in first west room" }, + { 0x10, 0x80, 0x1080, "killed lizalfos in south hallway to main room 2F" }, + { 0x10, 0x40, 0x1040, "killed lizalfos behind gate in first east room 1F " }, + { 0x10, 0x04, 0x1004, "blown up rock in room before mini-boss" }, + { 0x11, 0x80, 0x1180, "Ooccoo Freed" }, + { 0x11, 0x20, 0x1120, "unlocked boss door" }, + { 0x11, 0x10, 0x1110, "a stalactite fell in second room (other than the first one)" }, + { 0x11, 0x04, 0x1104, "stalactite fell in first west room 2F" }, + { 0x11, 0x02, 0x1102, "stalactite falls by itself in second room" }, + { 0x11, 0x01, 0x1101, "left stalactite fell in first east room 2F" }, + { 0x12, 0x80, 0x1280, "right stalactite fell in first east room 2F" }, + { 0x12, 0x40, 0x1240, "blown up rock in first east room 2F" }, + { 0x12, 0x20, 0x1220, "south stalactite fell in first east room 1F" }, + { 0x12, 0x10, 0x1210, "north stalactite fell in first east room 1F" }, + { 0x12, 0x08, 0x1208, "south-east stalactite fell second room" }, + { 0x12, 0x04, 0x1204, "north east stalactite fell in second room" }, + { 0x12, 0x02, 0x1202, "south Stalactite fell second room" }, + { 0x12, 0x01, 0x1201, "north west stalactite fell in second room" }, + { 0x13, 0x80, 0x1380, "south west stalactile fell in second room" }, + { 0x13, 0x20, 0x1320, "killed first shell blade in room before big key" }, + { 0x13, 0x10, 0x1310, "killed second shell blade in room before big key" }, + { 0x13, 0x04, 0x1304, "opened north gate in first west room 2F" }, + { 0x13, 0x02, 0x1302, "opened south gate in first west room 2F" }, + { 0x13, 0x01, 0x1301, "opened gates in mini-boss room" }, + { 0x14, 0x10, 0x1410, "explore main room" }, + { 0x14, 0x04, 0x1404, "main room intro cs" }, + { 0x14, 0x02, 0x1402, "south Hallway to Main room intro cs" }, + { 0x14, 0x01, 0x1401, "first room intro cs" }, + { 0x15, 0x20, 0x1520, "move staircase main room cs" }, + { 0x15, 0x08, 0x1508, "west water flowing into staircase main room cs" }, + { 0x15, 0x04, 0x1504, "east water flowing into staircase main room cs" }, + { 0x15, 0x02, 0x1502, "water in main room raised rwice cs" }, + { 0x15, 0x01, 0x1501, "west water is flowing into main room cs" }, + { 0x16, 0x80, 0x1680, "water in main room raised once cs" }, + { 0x16, 0x40, 0x1640, "east water is flowing into main room cs" }, + { 0x16, 0x20, 0x1620, "big key chest (set after)" }, + { 0x16, 0x10, 0x1610, "compass big chest second west room 4F (set after)" }, + { 0x16, 0x08, 0x1608, "bubble worm cs second east room 2F" }, + { 0x16, 0x01, 0x1601, "enter/exit mini-boss room from 1F door (spawns enemies in room before)" }, + { 0x17, 0x04, 0x1704, "killed right lizalfos in first west room 2F" }, + { 0x17, 0x02, 0x1702, "killed left lizalfos in first west room 2F" }, + { 0x17, 0x01, 0x1701, "killed lizalfos in first east room 2F" }, +}; + +inline EventAreaFlags eventAreaFlagsLHLC[] = +{ + { 0x02, 0x40, 0x0240, "big chest room 5 lake hylia long cave" }, + { 0x02, 0x20, 0x0220, "east big chest room 10 Lake Hylia Long Cave" }, + { 0x02, 0x10, 0x0210, "north small chest room 9 Lake Hylia Long Cave" }, + { 0x02, 0x08, 0x0208, "east small chest room 8 Lake Hylia Long Cave" }, + { 0x02, 0x04, 0x0204, "north small chest room 6 Lake hylia Long Cave" }, + { 0x02, 0x02, 0x0202, "south small chest room 7 Lake Hylia Long Cave" }, + { 0x02, 0x01, 0x0201, "north small chest room 5 Lake hylia Long Cave" }, + { 0x03, 0x80, 0x0380, "north small chest room 4 Lake Hylia Long Cave" }, + { 0x03, 0x40, 0x0340, "west small chest room 2 Lake hylia Long Cave" }, + { 0x03, 0x20, 0x0320, "west big chest room 7 Lake Hylia Long Cave" }, + { 0x03, 0x10, 0x0310, "north big chest room 8 Lake Hylia Long Cave" }, + { 0x03, 0x08, 0x0308, "big chest room 11 lake hylia long cave" }, + { 0x03, 0x04, 0x0304, "north small chest room 1 Lake hylia Long Cave" }, + { 0x03, 0x02, 0x0302, "west small chest room 1 Lake hylia Long Cave" }, + { 0x03, 0x01, 0x0301, "east small chest room 3 Lake Hylia Long Cave" }, + { 0x04, 0x80, 0x0480, "small chest Goron Stock Cave" }, + { 0x04, 0x40, 0x0440, "big chest in front of exit Goron Stock Cave" }, + { 0x04, 0x20, 0x0420, "big chest spawned form torches Goron Stock Cave" }, + { 0x08, 0x80, 0x0880, "explored section 18 Lake hylia Long Cave" }, + { 0x08, 0x40, 0x0840, "explored section 15 Lake hylia Long Cave" }, + { 0x08, 0x20, 0x0820, "explored section 12 Lake hylia Long Cave" }, + { 0x08, 0x10, 0x0810, "explored section 11 Lake hylia Long Cave" }, + { 0x08, 0x08, 0x0808, "explored section 9 Lake hylia Long Cave" }, + { 0x08, 0x04, 0x0804, "explored section 6 Lake hylia Long Cave" }, + { 0x08, 0x02, 0x0802, "explored section 4 Lake hylia Long Cave" }, + { 0x08, 0x01, 0x0801, "explored section 27 (room 11) Lake hylia Long Cave" }, + { 0x09, 0x80, 0x0980, "explored section 24 (room 10) Lake hylia Long Cave" }, + { 0x09, 0x40, 0x0940, "explored section 23 (room 9) Lake hylia Long Cave" }, + { 0x09, 0x20, 0x0920, "explored section 22 Lake hylia Long Cave" }, + { 0x09, 0x10, 0x0910, "explored east area room 8 Lake hylia Long Cave" }, + { 0x09, 0x08, 0x0908, "explored north area room 8 Lake hylia Long Cave" }, + { 0x09, 0x04, 0x0904, "explored section 21 (room 8) Lake hylia Long Cave" }, + { 0x09, 0x02, 0x0902, "explored section 17 Lake hylia Long Cave" }, + { 0x09, 0x01, 0x0901, "explored south area room 7 Lake hylia Long Cave" }, + { 0x0A, 0x80, 0x0A80, "explored west area room 7 Lake hylia Long Cave" }, + { 0x0A, 0x40, 0x0A40, "explored section 16 (room 7) Lake hylia Long Cave" }, + { 0x0A, 0x20, 0x0A20, "explored section 14 Lake hylia Long Cave" }, + { 0x0A, 0x10, 0x0A10, "explored north area room 6 Lake hylia Long Cave" }, + { 0x0A, 0x08, 0x0A08, "explored section 13 (room 6) Lake hylia Long Cave" }, + { 0x0A, 0x04, 0x0A04, "explored section 10 (room 5) Lake hylia Long Cave" }, + { 0x0A, 0x02, 0x0A02, "explored section 8 Lake hylia Long Cave" }, + { 0x0A, 0x01, 0x0A01, "explored north area room 4 Lake hylia Long Cave" }, + { 0x0B, 0x80, 0x0B80, "explored section 7 (room 4) Lake hylia Long Cave" }, + { 0x0B, 0x40, 0x0B40, "explored section 5 (room 3) Lake hylia Long Cave" }, + { 0x0B, 0x20, 0x0B20, "explored section 3 (room 2) Lake hylia Long Cave" }, + { 0x0B, 0x10, 0x0B10, "explored section 2 Lake hylia Long Cave" }, + { 0x0B, 0x08, 0x0B08, "explored north area room 1 Lake hylia Long Cave" }, + { 0x0B, 0x04, 0x0B04, "explored west area room 1 Lake hylia Long Cave" }, + { 0x0B, 0x02, 0x0B02, "explored section 1 (room 1) Lake hylia Long Cave" }, + { 0x0C, 0x80, 0x0C80, "blown up north rock room 7 Lake hylia long cave" }, + { 0x0C, 0x10, 0x0C10, "blown up north rock room 3 Lake hylia long cave" }, + { 0x0C, 0x04, 0x0C04, "blown up north rock room 4 Lake hylia long cave" }, + { 0x0C, 0x01, 0x0C01, "blown up south rock room 8 Lake hylia long cave" }, + { 0x0D, 0x40, 0x0D40, "blown up east rock room 6 Lake Hylia Long Cave" }, + { 0x0D, 0x20, 0x0D20, "blown up west rock room 6 Lake Hylia Long Cave" }, + { 0x0D, 0x04, 0x0D04, "blown up north rock room 5 Lake Hylia Long Cave" }, + { 0x0D, 0x02, 0x0D02, "blown up north rock room 9 Lake Hylia Long Cave" }, + { 0x0D, 0x01, 0x0D01, "blown up east rock room 3 Lake hylia long cave" }, + { 0x0E, 0x80, 0x0E80, "lit right torch room 5 lake hylia long cave" }, + { 0x0E, 0x40, 0x0E40, "lit left torch room 5 lake hylia long cave" }, + { 0x0E, 0x20, 0x0E20, "spawn big chest room 11 lake hylia long cave" }, + { 0x0E, 0x10, 0x0E10, "lit right torch room 11 Lake Hylia Long Cave" }, + { 0x0E, 0x08, 0x0E08, "lit left torch room 11 Lake Hylia Long Cave" }, + { 0x0E, 0x04, 0x0E04, "explored south area room 11 Lake hylia Long Cave" }, + { 0x0E, 0x02, 0x0E02, "explored east area room 10 Lake hylia Long Cave" }, + { 0x0E, 0x01, 0x0E01, "explored north area room 9 Lake hylia Long Cave" }, + { 0x0F, 0x80, 0x0F80, "explored east area room 6 Lake hylia Long Cave" }, + { 0x0F, 0x40, 0x0F40, "explored north area room 5 Lake hylia Long Cave" }, + { 0x0F, 0x20, 0x0F20, "explored east area room 3 Lake hylia Long Cave" }, + { 0x0F, 0x10, 0x0F10, "explored west area room 2 Lake hylia Long Cave" }, + { 0x0F, 0x08, 0x0F08, "explored section 25 Lake hylia Long Cave" }, + { 0x0F, 0x04, 0x0F04, "explored section 26 Lake hylia Long Cave" }, + { 0x0F, 0x02, 0x0F02, "explored section 20 Lake hylia Long Cave" }, + { 0x0F, 0x01, 0x0F01, "explored section 19 Lake hylia Long Cave" }, + { 0x10, 0x80, 0x1080, "killed poe in room 11 Lake Hylia Long Cave" }, + { 0x10, 0x40, 0x1040, "killed poe in room 3 Lake Hylia Long Cave" }, + { 0x10, 0x20, 0x1020, "killed poe in room 8 Lake Hylia Long Cave" }, + { 0x10, 0x04, 0x1004, "lit torch 9 (room 7) Lake hylia Long Cave" }, + { 0x10, 0x01, 0x1001, "lit torch 5 (room 4) Lake hylia Long Cave" }, + { 0x11, 0x40, 0x1140, "lit torch 3 (room 2) Lake hylia Long Cave" }, + { 0x11, 0x20, 0x1120, "lit torch 2 (room 1) Lake hylia Long Cave" }, + { 0x11, 0x08, 0x1108, "lit torch 12 (room 10) Lake hylia Long Cave" }, + { 0x11, 0x04, 0x1104, "lit torch 10 (room 8) Lake hylia Long Cave" }, + { 0x11, 0x02, 0x1102, "spawn big chest room 5 lake hylia long cave" }, + { 0x11, 0x01, 0x1101, "lit torch 4 (room 3) Lake hylia Long Cave" }, + { 0x12, 0x80, 0x1280, "blown up east rock room 9 Lake hylia long cave" }, + { 0x12, 0x40, 0x1240, "blown up west rock room 2 Lake hylia Long Cave" }, + { 0x12, 0x20, 0x1220, "blown up east rock room 2 Lake hylia Long Cave" }, + { 0x12, 0x10, 0x1210, "blown up north rock room 6 Lake hylia Long Cave" }, + { 0x12, 0x08, 0x1208, "blown up west rock room 5 Lake hylia Long Cave" }, + { 0x12, 0x04, 0x1204, "blown up east rock room 8 Lake Hylia Long Cave" }, + { 0x12, 0x02, 0x1202, "blown up east rock room 10 Lake Hylia Long Cave" }, + { 0x12, 0x01, 0x1201, "blown up north rock room 8 Lake hylia Long Cave" }, + { 0x13, 0x80, 0x1380, "blown up west rock room 7 Lake hylia Long Cave" }, + { 0x13, 0x40, 0x1340, "blown up south rock room 7 Lake hylia Long Cave" }, + { 0x13, 0x20, 0x1320, "blown up south rock room 10 Lake hylia Long Cave" }, + { 0x13, 0x10, 0x1310, "blown up west rock room 4 Lake hylia Long Cave" }, + { 0x13, 0x08, 0x1308, "blown up west rock room 1 Lake hylia Long Cave" }, + { 0x13, 0x04, 0x1304, "blown up north rock room 1 Lake hylia Long Cave" }, + { 0x13, 0x02, 0x1302, "blown up east rock room 1 Lake hylia Long Cave" }, + { 0x14, 0x80, 0x1480, "spawn big chest bottom floor Goron Stock Cave" }, + { 0x14, 0x40, 0x1440, "lit right torch bottom floor Goron Stock Cave" }, + { 0x14, 0x20, 0x1420, "lit left torch bottom floor Goron Stock Cave" }, +}; + +inline EventAreaFlags eventAreaFlagsOrdon[] = +{ + { 0x03, 0x10, 0x0310, "wooden sword big chest" }, + { 0x03, 0x04, 0x0304, "iron boots big chest" }, + { 0x03, 0x02, 0x0302, "Big chest link's basement" }, + { 0x08, 0x80, 0x0880, "despawn Bo and Jaggle after scaring them away (wolf night)" }, + { 0x08, 0x40, 0x0840, "scare away Bo and Jaggle after hearing them (wolf night)" }, + { 0x08, 0x20, 0x0820, "started sword training" }, + { 0x08, 0x10, 0x0810, "obtained wooden sword" }, + { 0x08, 0x08, 0x0808, "midna text after getting ordon shield (spawns sword)" }, + { 0x08, 0x04, 0x0804, "got ordon shield (will despawn it if set)" }, + { 0x08, 0x02, 0x0802, "midna text after getting ordon sword" }, + { 0x08, 0x01, 0x0801, "got ordon sword" }, + { 0x09, 0x80, 0x0980, "exit shield house as wolf cs" }, + { 0x09, 0x40, 0x0940, "Goats 2" }, + { 0x09, 0x20, 0x0920, "day 3 intro cs (spawn in ranch)" }, + { 0x09, 0x08, 0x0908, "knocked down bee's nest day 2 cs" }, + { 0x09, 0x04, 0x0904, "? (wolf in village night)" }, + { 0x09, 0x02, 0x0902, "Ranch first time CS" }, + { 0x0A, 0x80, 0x0A80, "Rusl moving during wolf night" }, + { 0x0A, 0x40, 0x0A40, "killed spider on top of Link's house" }, + { 0x0A, 0x20, 0x0A20, "spawn 2 spiders around Link,s house (day 2) (set after buying slingshot)" }, + { 0x0A, 0x10, 0x0A10, "Ilia spring CS watched" }, + { 0x0A, 0x08, 0x0A08, "Ilia spring CS started" }, + { 0x0A, 0x04, 0x0A04, "Ordon Village first time CS" }, + { 0x0A, 0x02, 0x0A02, "Ilia spring CS Trigger" }, + { 0x0A, 0x01, 0x0A01, "killed spider on ladder to link's house" }, + { 0x0B, 0x80, 0x0B80, "Epona being in spring" }, + { 0x0B, 0x20, 0x0B20, "finished slingshot training" }, + { 0x0B, 0x10, 0x0B10, "Entered spring Area first time" }, + { 0x0B, 0x08, 0x0B08, "Intro CS watched" }, + { 0x0B, 0x02, 0x0B02, "finished sword training" }, + { 0x0C, 0x80, 0x0C80, "killed bulblin ordon woods" }, + { 0x0C, 0x40, 0x0C40, "killed second bulblin Link's house" }, + { 0x0C, 0x20, 0x0C20, "killed first bulblin Link's house" }, + { 0x0C, 0x10, 0x0C10, "spawn wooden sword big chest" }, + { 0x0C, 0x08, 0x0C08, "Day 2 intro CS" }, + { 0x0C, 0x04, 0x0C04, "talked to owl as wolf for the first time" }, + { 0x0C, 0x02, 0x0C02, "Goats 1" }, + { 0x0C, 0x01, 0x0C01, "set after getting ordon shield" }, + { 0x0D, 0x80, 0x0D80, "approach faron twilight to enter it with midna cs" }, + { 0x0D, 0x20, 0x0D20, "spawn shadow beast" }, + { 0x0D, 0x10, 0x0D10, "Ordon Spring Portal" }, + { 0x0D, 0x08, 0x0D08, "hear Bo and Jaggle talk about shield from far away (wolf night)" }, + { 0x0D, 0x02, 0x0D02, "entered shield house as wolf cs" }, + { 0x0D, 0x01, 0x0D01, "ordon shield fell down cs" }, + { 0x0E, 0x80, 0x0E80, "midna text leaving spring" }, + { 0x0E, 0x20, 0x0E20, "started midna jump to ordon shield inside house" }, + { 0x0E, 0x08, 0x0E08, "Hanch started attacking (unlocks midna jump to top of shop)" }, + { 0x0E, 0x04, 0x0E04, "midna cs after hearing Bo and Jaggle talk about shield" }, + { 0x0E, 0x02, 0x0E02, "day 3 Fado intro text" }, + { 0x0F, 0x80, 0x0F80, "Allows Link to stop goat in Village" }, + { 0x0F, 0x20, 0x0F20, "started midna jumps to top of shop" }, + { 0x0F, 0x08, 0x0F08, "midna text before doing jumps to top of shop" }, + { 0x0F, 0x04, 0x0F04, "Rusl talking to his wife cs (wolf night)" }, + { 0x0F, 0x02, 0x0F02, "unlock midna jumps to shield house" }, + { 0x0F, 0x01, 0x0F01, "scared Hanch" }, + { 0x12, 0x02, 0x1202, "torch next to Hanch is lit (wolf night)" }, + { 0x12, 0x01, 0x1201, "? (wolf in village night)" }, + { 0x13, 0x80, 0x1380, "iron boots big chest (duplicate)" }, + { 0x13, 0x40, 0x1340, "spawn iron boots chest in Bo's house" }, + { 0x13, 0x20, 0x1320, "brigthen up area under Hanch after getting spoted" }, + { 0x13, 0x10, 0x1310, "set after midna text after getting ordon shield" }, + { 0x13, 0x08, 0x1308, "Hanch spoted you cs (wolf night)" }, + { 0x13, 0x04, 0x1304, "randomly set during wolf night (often during Hanch part)" }, + { 0x13, 0x02, 0x1302, "Hero's shade map marker" }, + { 0x13, 0x01, 0x1301, "Ordon day 1 (save promt)" }, + { 0x14, 0x80, 0x1480, "set after midna text after getting ordon sword" }, + { 0x14, 0x01, 0x1401, "Jump to eagle grass next to Jaggle day 2 (removes Jaggle text)" }, + { 0x15, 0x10, 0x1510, "did midna jumps to top of shop" }, + { 0x16, 0x01, 0x1601, "King Bulblin cs" }, + { 0x17, 0x80, 0x1780, "enter village as wolf intro cs" }, + { 0x17, 0x20, 0x1720, "explored area with link's house (wolf)" }, + { 0x19, 0x80, 0x1980, "picked up yellow rupee on top of Hanch's house" }, + { 0x19, 0x40, 0x1940, "picked up green rupee in grass in front of Bo's house" }, + { 0x19, 0x20, 0x1920, "picked up green rupees in pond next to Rusl's house (day 2)" }, + { 0x19, 0x10, 0x1910, "picked up purple rupee behind Jaggle's house" }, + { 0x19, 0x01, 0x1901, "picked up green rupee in the back of the grass to the right of Link's house" }, + { 0x1A, 0x80, 0x1A80, "picked up green rupee in grass right of gate to village " }, + { 0x1A, 0x40, 0x1A40, "picked up green rupee next to rock in the grass to the right of Link's house" }, + { 0x1A, 0x20, 0x1A20, "picked up green rupee in grass to the right of Link's house" }, + { 0x1A, 0x10, 0x1A10, "picked up first green rupee in crawl space" }, + { 0x1A, 0x08, 0x1A08, "picked up yellow rupee on top of Bo's house" }, + { 0x1A, 0x02, 0x1A02, "picked up blue rupee on top of Hanch's house" }, + { 0x1A, 0x01, 0x1A01, "Picked up Orange Rupee by Rusl's house" }, + { 0x1B, 0x80, 0x1B80, "picked up green rupees on top of Hanch's house (day 2)" }, + { 0x1B, 0x40, 0x1B40, "picked up green rupees on top of Bo's house (day 2)" }, + { 0x1B, 0x08, 0x1B08, "Picked up Ordon Sword" }, + { 0x1B, 0x04, 0x1B04, "picked up green rupee under bridge (day 2)" }, + { 0x1B, 0x02, 0x1B02, "picked up yellow rupees on top of Rusl's house" }, + { 0x1B, 0x01, 0x1B01, "picked up yellow rupee on cliff next to Bo's house" }, +}; + +inline EventAreaFlags eventAreaFlagsPoT[] = +{ + { 0x00, 0x40, 0x0040, "PoH hidden big chest west room 1" }, + { 0x00, 0x20, 0x0020, "north west small chest east room 2" }, + { 0x00, 0x10, 0x0010, "north east small chest east room 2" }, + { 0x00, 0x08, 0x0008, "west small chest east room 1" }, + { 0x00, 0x04, 0x0004, "small key big chest north room 3" }, + { 0x00, 0x02, 0x0002, "south higher big chest west room 2" }, + { 0x00, 0x01, 0x0001, "small key big chest north room 2" }, + { 0x01, 0x80, 0x0180, "big key chest" }, + { 0x01, 0x40, 0x0140, "small key big chest north room 1" }, + { 0x02, 0x08, 0x0208, "placed west sol" }, + { 0x02, 0x04, 0x0204, "placed east sol" }, + { 0x03, 0x80, 0x0380, "small key east big chest east room 2" }, + { 0x03, 0x40, 0x0340, "small key north east big chest east room 1" }, + { 0x03, 0x20, 0x0320, "small key north big chest west room 2" }, + { 0x03, 0x10, 0x0310, "small key big chest west room 1" }, + { 0x03, 0x08, 0x0308, "compass lower south big chest west room 2" }, + { 0x03, 0x01, 0x0301, "PoH east big chest east room 1" }, + { 0x07, 0x10, 0x0710, "north west small chest east room 1" }, + { 0x07, 0x02, 0x0702, "map west big chest east room 2" }, + { 0x08, 0x10, 0x0810, "spawn south fence in east room 3" }, + { 0x08, 0x08, 0x0808, "spawn north fence in east room 3" }, + { 0x08, 0x04, 0x0804, "spawn south fence in west room 3" }, + { 0x08, 0x02, 0x0802, "spawn north fence in west room 3" }, + { 0x09, 0x80, 0x0980, "spawn in main room from midna warp" }, + { 0x09, 0x40, 0x0940, "save prompt after boss" }, + { 0x09, 0x20, 0x0920, "killed east phantom Zant (unlocks door)" }, + { 0x09, 0x10, 0x0910, "killed west phantom Zant (unlocks door)" }, + { 0x09, 0x04, 0x0904, "Phantom Zant 1 CS" }, + { 0x09, 0x02, 0x0902, "?" }, + { 0x09, 0x01, 0x0901, "?" }, + { 0x0A, 0x40, 0x0A40, "killed all Zant heads in east room 2 (spawn big chest)" }, + { 0x0A, 0x20, 0x0A20, "killed Zant head in east room 1 (spawn chest)" }, + { 0x0A, 0x10, 0x0A10, "killed Zant head in west room 2 (spawn 2 big chests)" }, + { 0x0A, 0x08, 0x0A08, "killed Zant head in west room 1 (spawn chest)" }, + { 0x0A, 0x02, 0x0A02, "unlock boss door" }, + { 0x0A, 0x01, 0x0A01, "unlock door in north room 3" }, + { 0x0B, 0x80, 0x0B80, "unlock door in east room 2" }, + { 0x0B, 0x40, 0x0B40, "unlock door in west room 2" }, + { 0x0B, 0x02, 0x0B02, "intro cs" }, + { 0x0B, 0x01, 0x0B01, "?" }, + { 0x0C, 0x80, 0x0C80, "get light master sword cs" }, + { 0x0C, 0x40, 0x0C40, "? (main room)" }, + { 0x0C, 0x20, 0x0C20, "? (main room)" }, + { 0x0C, 0x10, 0x0C10, "Watched CS of platform to east wing being activated" }, + { 0x0C, 0x08, 0x0C08, "Platform to east wing is active" }, + { 0x0C, 0x04, 0x0C04, "Midna text when west sol is placed" }, + { 0x0C, 0x02, 0x0C02, "Midna text when west hand steals sol" }, + { 0x0D, 0x80, 0x0D80, "killed south west Zant head in east room 2" }, + { 0x0D, 0x40, 0x0D40, "killed south east Zant head in east room 2" }, + { 0x0D, 0x20, 0x0D20, "killed center Zant head in east room 2" }, + { 0x0D, 0x10, 0x0D10, "killed first Zant head in east room 2" }, + { 0x0D, 0x08, 0x0D08, "unlock door in north room 2" }, + { 0x0D, 0x04, 0x0D04, "killed dark beasts in north room 3" }, + { 0x0D, 0x02, 0x0D02, "crossed black fog waterfall main room" }, + { 0x0D, 0x01, 0x0D01, "midna text black fog west room 1" }, + { 0x0E, 0x80, 0x0E80, "Midna text finding west sol" }, + { 0x0E, 0x40, 0x0E40, "midna text black fog waterfall main room" }, + { 0x0E, 0x20, 0x0E20, "midna text when seeing a civilian seen" }, + { 0x0E, 0x10, 0x0E10, "midna text after warping back to main room after boss" }, + { 0x0E, 0x02, 0x0E02, "killed second Zant head in north room 3 (spawn big chest)" }, + { 0x0E, 0x01, 0x0E01, "killed first Zant head in north room 3 (spawn head 2)" }, + { 0x0F, 0x80, 0x0F80, "midna text promt when seeing a civilian" }, + { 0x0F, 0x40, 0x0F40, "midna text after light master sword" }, + { 0x0F, 0x20, 0x0F20, "unlock door in north room 1" }, + { 0x0F, 0x10, 0x0F10, "unlock door in east room 1" }, + { 0x0F, 0x08, 0x0F08, "unlock door in west room 1" }, + { 0x10, 0x80, 0x1080, "killed south Zant head in north room 1 (wave 1)" }, + { 0x10, 0x40, 0x1040, "killed center Zant head in north room 1 (wave 1)" }, + { 0x10, 0x20, 0x1020, "killed north west Zant head in north room 1 (wave 1)" }, + { 0x10, 0x10, 0x1010, "killed north east Zant head in north room 1 (wave 1)" }, + { 0x10, 0x08, 0x1008, "?" }, + { 0x10, 0x04, 0x1004, "?" }, + { 0x10, 0x01, 0x1001, "? (main room)" }, + { 0x11, 0x80, 0x1180, "Heal Twili citizen outside west wing" }, + { 0x11, 0x40, 0x1140, "? (main room)" }, + { 0x11, 0x20, 0x1120, "Heal Twili near PoT entrance" }, + { 0x11, 0x10, 0x1110, "Heal Twili citizen outside east wing" }, + { 0x11, 0x08, 0x1108, "Heal Twili citizen next to west sol" }, + { 0x11, 0x04, 0x1104, "Heal Twili citizen next to east sol" }, + { 0x11, 0x02, 0x1102, "Heal Twili citizen north of Light Sword" }, + { 0x11, 0x01, 0x1101, "Heal Twili citizen near fog waterfall" }, + { 0x12, 0x08, 0x1208, "Opened Big Key Chest (causes fence to fall in room before Zant)" }, + { 0x12, 0x04, 0x1204, "Midna text after re-entering west wing after sol stolen" }, + { 0x12, 0x02, 0x1202, "remove fog in north room 4" }, + { 0x13, 0x40, 0x1340, "remove fog and spawn stairs in north room 1" }, + { 0x13, 0x20, 0x1320, "placed east sol in north room 1" }, + { 0x13, 0x10, 0x1310, "placed west sol in north room 1" }, + { 0x13, 0x02, 0x1302, "midna intro text" }, + { 0x15, 0x40, 0x1540, "killed all wave 4 Zant heads in north room 2 (spawn big chest)" }, + { 0x15, 0x20, 0x1520, "killed east Zant head north room 2 (wave 4)" }, + { 0x15, 0x10, 0x1510, "killed west Zant head north room 2 (wave 4)" }, + { 0x15, 0x08, 0x1508, "killed Zant head wave 3 in north room 2 (spawn wave 4)" }, + { 0x15, 0x04, 0x1504, "killed all wave 2 Zant heads in north room 2 (spawn wave 3)" }, + { 0x15, 0x02, 0x1502, "killed west Zant head north room 2 (wave 2)" }, + { 0x15, 0x01, 0x1501, "killed east Zant head north room 2 (wave 2)" }, + { 0x16, 0x80, 0x1680, "killed middle Zant head north room 2 (wave 2)" }, + { 0x16, 0x40, 0x1640, "killed first Zant head north room 2 (spawn wave 2)" }, + { 0x16, 0x10, 0x1610, "Watched east wing second room stairs CS" }, + { 0x16, 0x01, 0x1601, "?" }, + { 0x17, 0x80, 0x1780, "?" }, + { 0x17, 0x40, 0x1740, "killed dark beasts in east room 2" }, + { 0x17, 0x20, 0x1720, "spawn fog in east room 3" }, + { 0x17, 0x10, 0x1710, "spawn fog in west room 3" }, + { 0x17, 0x08, 0x1708, "killed all wave 2 Zant heads in north room 1 (spawn big chest)" }, + { 0x17, 0x04, 0x1704, "killed north west Zant head north room 1 (wave 2)" }, + { 0x17, 0x02, 0x1702, "killed north east Zant head north room 1 (wave 2)" }, + { 0x17, 0x01, 0x1701, "killed all wave 1 Zant heads in north room 1 (spawn wave 2)" }, + { 0x1B, 0x01, 0x1B01, "heart container" }, +}; + +inline EventAreaFlags eventAreaFlagsSacredGrove[] = +{ + { 0x03, 0x08, 0x0308, "Big chest lost woods 2 torches" }, + { 0x03, 0x04, 0x0304, "Big chest spinner in skull kid wolf battle area" }, + { 0x03, 0x02, 0x0302, "PoH big chest temple of time (past)" }, + { 0x08, 0x80, 0x0880, "lit left torch chest lost woods" }, + { 0x08, 0x40, 0x0840, "killed poe temple of time (past)" }, + { 0x08, 0x20, 0x0820, "midna text after pushing block human" }, + { 0x08, 0x10, 0x0810, "cs after pushing block human" }, + { 0x08, 0x08, 0x0808, "intro cs lost woods" }, + { 0x08, 0x02, 0x0802, "?" }, + { 0x08, 0x01, 0x0801, "?" }, + { 0x09, 0x80, 0x0980, "explored section after window before dungeon (includes map marker for dungeon)" }, + { 0x09, 0x40, 0x0940, "stairs to temple of time area near (dungeon)" }, + { 0x09, 0x20, 0x0920, "strike MS into pedestal in past" }, + { 0x09, 0x10, 0x0910, "window and stairs to dungeon work properly" }, + { 0x09, 0x02, 0x0902, "killed poe lost woods water" }, + { 0x09, 0x01, 0x0901, "killed poe in skull kid wolf battle area" }, + { 0x0A, 0x80, 0x0A80, "killed poe in master sword area (present)" }, + { 0x0A, 0x10, 0x0A10, "skull kid wolf battle area explored" }, + { 0x0B, 0x80, 0x0B80, "temple of time (present) main room explored" }, + { 0x0B, 0x40, 0x0B40, "temple of time (past) main room explored" }, + { 0x0B, 0x20, 0x0B20, "blown up rock in skull kid wolf battle area" }, + { 0x0B, 0x10, 0x0B10, "block pushed human" }, + { 0x0B, 0x04, 0x0B04, "Master Sword Puzzle Complete" }, + { 0x0B, 0x02, 0x0B02, "Master sword area explored" }, + { 0x0B, 0x01, 0x0B01, "Temple of time (present) human entrence explored" }, + { 0x0F, 0x02, 0x0F02, "spawn big chest lost woods 2 torches" }, + { 0x0F, 0x01, 0x0F01, "lit right torch chest lost woods" }, + { 0x16, 0x20, 0x1620, "Master Sword Pulled (does not despawn)" }, + { 0x16, 0x10, 0x1610, "Blocks entrance to Woods (auto-set upon re-entering Grove before Skull Kid 2" }, + { 0x16, 0x08, 0x1608, "Lost woods skull kid human chase (unset once done)" }, + { 0x16, 0x02, 0x1602, "skull kid human defeated" }, + { 0x16, 0x01, 0x1601, "lost woods turns to day after skull kid fight human" }, + { 0x17, 0x80, 0x1780, "Skull Kid Appears (set automatically)" }, + { 0x17, 0x40, 0x1740, "Transition to day after Skull Kid" }, + { 0x17, 0x20, 0x1720, "dark beasts spawned" }, + { 0x17, 0x10, 0x1710, "Sacred Grove Portal (removes statue blocking door of time (to past)" }, +}; + +inline EventAreaFlags eventAreaFlagsSewer[] = +{ + { 0x00, 0x80, 0x0080, "0" }, + { 0x00, 0x40, 0x0040, "1" }, + { 0x00, 0x20, 0x0020, "2" }, + { 0x00, 0x10, 0x0010, "3" }, + { 0x00, 0x08, 0x0008, "4" }, + { 0x00, 0x04, 0x0004, "5" }, + { 0x00, 0x02, 0x0002, "6" }, + { 0x00, 0x01, 0x0001, "7" }, + { 0x01, 0x80, 0x0180, "8" }, + { 0x01, 0x40, 0x0140, "9" }, + { 0x01, 0x20, 0x0120, "10" }, + { 0x01, 0x10, 0x0110, "11" }, + { 0x01, 0x08, 0x0108, "12" }, + { 0x01, 0x04, 0x0104, "13" }, + { 0x01, 0x02, 0x0102, "14" }, + { 0x01, 0x01, 0x0101, "15" }, + { 0x02, 0x80, 0x0280, "16" }, + { 0x02, 0x40, 0x0240, "17" }, + { 0x02, 0x20, 0x0220, "18" }, + { 0x02, 0x10, 0x0210, "19" }, + { 0x02, 0x08, 0x0208, "20" }, + { 0x08, 0x80, 0x0880, "twilight final cs" }, + { 0x08, 0x40, 0x0840, "Zelda cs twilight" }, + { 0x08, 0x20, 0x0820, "midna text after first gate sewers (how to fight)" }, + { 0x08, 0x10, 0x0810, "midna text after exiting to rooftops (twilight)" }, + { 0x08, 0x08, 0x0808, "wake up in jail cs" }, + { 0x08, 0x04, 0x0804, "midna promt to use sense to see gard (entrance to sewers)" }, + { 0x08, 0x02, 0x0802, "pushed box outside (MDH)" }, + { 0x08, 0x01, 0x0801, "pulled lever of first water gate in sewers" }, + { 0x09, 0x80, 0x0980, "dug up blue rupee after crawl space (sewers) (twilight)" }, + { 0x09, 0x40, 0x0940, "second water gate in sewers cs (unset afterwards)" }, + { 0x09, 0x20, 0x0920, "first water gate in sewers cs (unset afterwards)" }, + { 0x09, 0x10, 0x0910, "midna cs after diging out of jail" }, + { 0x09, 0x08, 0x0908, "midna intro cs" }, + { 0x09, 0x04, 0x0904, "waited long enough in jail" }, + { 0x09, 0x02, 0x0902, "broke fragile floor first jump of stairway (unlocks first midna jump)" }, + { 0x09, 0x01, 0x0901, "started midna jumps 3 stairway" }, + { 0x0A, 0x80, 0x0A80, "started midna jumps 2 stairway" }, + { 0x0A, 0x40, 0x0A40, "zelda tower intro cs" }, + { 0x0A, 0x20, 0x0A20, "midna went to the other side of the fence in sewers" }, + { 0x0A, 0x10, 0x0A10, "started midna jumps on rooftops" }, + { 0x0A, 0x08, 0x0A08, "started midna jumps 4 stairway (top to door)" }, + { 0x0A, 0x04, 0x0A04, "broke box inside Link's cell" }, + { 0x0A, 0x02, 0x0A02, "did midna jumps 1 stairway (broke small platform)" }, + { 0x0A, 0x01, 0x0A01, "outside top door intro cs" }, + { 0x0B, 0x80, 0x0B80, "broke fire in Zelda's room" }, + { 0x0B, 0x40, 0x0B40, "midna rejoinded link after crawl space" }, + { 0x0B, 0x20, 0x0B20, "went to the other side of the fence in sewers cs" }, + { 0x0B, 0x10, 0x0B10, "top of stairway intro cs" }, + { 0x0B, 0x08, 0x0B08, "stairway intro cs" }, + { 0x0B, 0x04, 0x0B04, "read midna promt to open door" }, + { 0x0B, 0x02, 0x0B02, "opened door in cell next to Link's" }, + { 0x0B, 0x01, 0x0B01, "dug out of Link's cell" }, + { 0x0D, 0x80, 0x0D80, "killed first bulblin outside (MDH)" }, + { 0x0D, 0x20, 0x0D20, "killed second bulblin outside (MDH)" }, + { 0x0D, 0x04, 0x0D04, "killed bulblin at top of stairway (on the patform) (MDH)" }, + { 0x0E, 0x80, 0x0E80, "killed fourth bulblin in stairway (MDH)" }, + { 0x0E, 0x20, 0x0E20, "killed third bulblin in stairway (MDH)" }, + { 0x0E, 0x04, 0x0E04, "killed second bulblin in stairway (MDH)" }, + { 0x0E, 0x02, 0x0E02, "killed first bulblin in stairway (MDH)" }, + { 0x0F, 0x20, 0x0F20, "talked to midna after using sense on first rooftop gard" }, + { 0x0F, 0x10, 0x0F10, "midna text when approching first rooftop gard" }, + { 0x0F, 0x08, 0x0F08, "listened to first gard in sewers" }, + { 0x0F, 0x04, 0x0F04, "saw first rooftop gard with sense" }, + { 0x0F, 0x02, 0x0F02, "saw first gard in sewers" }, + { 0x0F, 0x01, 0x0F01, "midna cs after diging out of jail" }, + { 0x1B, 0x08, 0x1B08, "picked up green rupee in box in secret passge next to fence (sewers) (twilight)" }, + { 0x1B, 0x04, 0x1B04, "picked up blue rupee in box in left secret way (sewers) (twilight)" }, + { 0x1B, 0x02, 0x1B02, "picked up purple rupee in boxes (first windy bridge) (MDH)" }, + { 0x1B, 0x01, 0x1B01, "picked up yellow rupee in Zelda's fire" }, +}; + +inline EventAreaFlags eventAreaFlagsSnowpeak[] = +{ + { 0x03, 0x01, 0x0301, "Big chest transition cave" }, + { 0x08, 0x40, 0x0840, "montain top explored" }, + { 0x08, 0x20, 0x0820, "Map marker Ashei" }, + { 0x08, 0x04, 0x0804, "montain top cs (pan towards tree)" }, + { 0x08, 0x02, 0x0802, "Post SPR Save Prompt" }, + { 0x09, 0x80, 0x0980, "?" }, + { 0x09, 0x40, 0x0940, "?" }, + { 0x09, 0x20, 0x0920, "Snowpeak top portal" }, + { 0x0B, 0x08, 0x0B08, "explored second section up montain" }, + { 0x0B, 0x04, 0x0B04, "explored first section up montain" }, + { 0x0B, 0x02, 0x0B02, "Midna text in front of SPR" }, + { 0x0B, 0x01, 0x0B01, "intro cs" }, + { 0x0D, 0x10, 0x0D10, "spawn big chest transition cave" }, + { 0x0D, 0x08, 0x0D08, "lit left torch transition cave" }, + { 0x0D, 0x04, 0x0D04, "lit right torch transition cave" }, + { 0x0F, 0x08, 0x0F08, "snow fell down next to howling stone" }, + { 0x14, 0x80, 0x1480, "killed poe transition cave" }, + { 0x14, 0x40, 0x1440, "killed poe next to snowpeak ruins" }, + { 0x14, 0x20, 0x1420, "killed first poe up the montain" }, + { 0x14, 0x10, 0x1410, "killed second poe next to lone tree on cliff" }, + { 0x14, 0x08, 0x1408, "killed third poe near 2 trees before howling stone" }, +}; + +inline EventAreaFlags eventAreaFlagsSPR[] = +{ + { 0x00, 0x02, 0x0002, "PoH big chest under broken ceiling" }, + { 0x01, 0x80, 0x0180, "left small chest behind armor in first room" }, + { 0x01, 0x40, 0x0140, "right small chest behind armor in first room" }, + { 0x01, 0x20, 0x0120, "PoH big chest in first room 2F" }, + { 0x01, 0x10, 0x0110, "west small chest behind ice in north west room" }, + { 0x01, 0x08, 0x0108, "east small chest behind ice in north west room" }, + { 0x01, 0x04, 0x0104, "small key small chest pumpkin room" }, + { 0x01, 0x01, 0x0101, "Ooccoo" }, + { 0x02, 0x80, 0x0280, "south east small chest in courtyard" }, + { 0x02, 0x40, 0x0240, "small key buried east small chest in courtyard" }, + { 0x02, 0x20, 0x0220, "small key buried west small chest in courtyard" }, + { 0x02, 0x08, 0x0208, "big key chest" }, + { 0x02, 0x04, 0x0204, "pumpkin big chest" }, + { 0x03, 0x80, 0x0380, "small key big chest in compass room 2F" }, + { 0x03, 0x40, 0x0340, "cheese big chest" }, + { 0x03, 0x08, 0x0308, "south small chest behind ice in courtyard" }, + { 0x03, 0x04, 0x0304, "compass big chest" }, + { 0x03, 0x02, 0x0302, "small chest compass room 1F" }, + //{ 0x08, 0x80, 0x0880, "position of second block in ice puzzle room" }, + //{ 0x08, 0x40, 0x0840, "position of second block in ice puzzle room" }, + //{ 0x08, 0x20, 0x0820, "position of second block in ice puzzle room" }, + //{ 0x08, 0x10, 0x0810, "position of second block in ice puzzle room" }, + //{ 0x08, 0x08, 0x0808, "position of second block in ice puzzle room" }, + //{ 0x08, 0x04, 0x0804, "position of third block in ice puzzle room (frozen one)" }, + //{ 0x08, 0x02, 0x0802, "position of third block in ice puzzle room (frozen one)" }, + //{ 0x08, 0x01, 0x0801, "position of third block in ice puzzle room (frozen one)" }, + //{ 0x09, 0x80, 0x0980, "position of third block in ice puzzle room (frozen one)" }, + //{ 0x09, 0x40, 0x0940, "position of third block in ice puzzle room (frozen one)" }, + { 0x09, 0x20, 0x0920, "killed poe in armor in first room" }, + { 0x09, 0x08, 0x0908, "unlock west living room door" }, + { 0x09, 0x04, 0x0904, "unlock north living room door" }, + { 0x09, 0x02, 0x0902, "lowered ball transport (west hallway-freezard)" }, + { 0x09, 0x01, 0x0901, "lowered ball transport (courtyard 2F)" }, + { 0x0A, 0x80, 0x0A80, "dug first time west buried small chest in courtyard" }, + { 0x0A, 0x40, 0x0A40, "lowered ball transport (west hallway-courtyard)" }, + { 0x0A, 0x20, 0x0A20, "dug second time west buried small chest in courtyard" }, + { 0x0A, 0x10, 0x0A10, "talked to Yeta for the first time (gives map)" }, + { 0x0A, 0x04, 0x0A04, "destroyed ice in courtyard 2F" }, + { 0x0A, 0x02, 0x0A02, "Yeta enters bedroom cs (in courtyard)" }, + { 0x0A, 0x01, 0x0A01, "unlock bedroom door" }, + { 0x0B, 0x80, 0x0B80, "enter courtyard" }, + { 0x0B, 0x20, 0x0B20, "compass big chest (set after)" }, + { 0x0B, 0x10, 0x0B10, "broke ice in pumpkin room 2F" }, + { 0x0B, 0x08, 0x0B08, "dug up east small chest in courtyard" }, + { 0x0B, 0x04, 0x0B04, "open west door 2F in ice puzzle room" }, + { 0x0B, 0x02, 0x0B02, "open west door 1F in ice puzzle room" }, + { 0x0C, 0x80, 0x0C80, "destroyed first left armor in first room" }, + { 0x0C, 0x40, 0x0C40, "destroyed second left armor in first room" }, + { 0x0C, 0x20, 0x0C20, "destroyed third left armor in first room" }, + //{ 0x0C, 0x08, 0x0C08, "canon in north west room orientation" }, + //{ 0x0C, 0x04, 0x0C04, "canon in north west room orientation" }, + { 0x0C, 0x02, 0x0C02, "killed freezard in courtyard 1F" }, + { 0x0C, 0x01, 0x0C01, "explored compass room" }, + { 0x0D, 0x80, 0x0D80, "killed first freezard in cage" }, + { 0x0D, 0x40, 0x0D40, "killed freezard in courtyard 2F" }, + { 0x0D, 0x20, 0x0D20, "killed west freezard in room above livng room (2F)" }, + { 0x0D, 0x10, 0x0D10, "killed north freezard in room above livng room (2F)" }, + { 0x0D, 0x08, 0x0D08, "broke ice in first room 2F" }, + { 0x0D, 0x04, 0x0D04, "destroyed ice on block in ice puzzle room" }, + { 0x0D, 0x01, 0x0D01, "unlock door in south east room 2F" }, + { 0x0E, 0x80, 0x0E80, "unlocked door to first room in freezard cage room" }, + { 0x0E, 0x20, 0x0E20, "unlock doors in room before pumpkin" }, + { 0x0E, 0x10, 0x0E10, "unlock door in east outside hallway" }, + { 0x0E, 0x08, 0x0E08, "unlock west door in courtyard" }, + { 0x0E, 0x04, 0x0E04, "explored courtyard 1F" }, + { 0x0E, 0x02, 0x0E02, "explored room above ice puzzle (2F)" }, + { 0x0E, 0x01, 0x0E01, "explored room above pumpkin (2F)" }, + { 0x0F, 0x80, 0x0F80, "puched left block in room above living room (2F)" }, + { 0x0F, 0x40, 0x0F40, "puched right block in room above living room (2F)" }, + { 0x0F, 0x20, 0x0F20, "2nd floor block pushed down in ice puzzle room" }, + //{ 0x0F, 0x10, 0x0F10, "position of first block in ice puzzle room" }, + //{ 0x0F, 0x08, 0x0F08, "position of first block in ice puzzle room" }, + //{ 0x0F, 0x04, 0x0F04, "position of first block in ice puzzle room" }, + //{ 0x0F, 0x02, 0x0F02, "position of first block in ice puzzle room" }, + //{ 0x0F, 0x01, 0x0F01, "position of first block in ice puzzle room" }, + { 0x10, 0x80, 0x1080, "Picked up B&C" }, + { 0x10, 0x40, 0x1040, "?" }, + { 0x10, 0x20, 0x1020, "Yeta lets you open door to kitchen" }, + { 0x10, 0x10, 0x1010, "north west room 1F intro cs" }, + { 0x10, 0x08, 0x1008, "Midna big key text prompt" }, + { 0x10, 0x02, 0x1002, "pumpkin room ambush cs trigger" }, + //{ 0x10, 0x01, 0x1001, "courtyard 2F canon orientation" }, + //{ 0x11, 0x80, 0x1180, "courtyard 2F canon orientation" }, + //{ 0x11, 0x40, 0x1140, "courtyard 1F canon orientation" }, + //{ 0x11, 0x20, 0x1120, "courtyard 1F canon orientation" }, + { 0x11, 0x02, 0x1102, "unlock mini-boss doors" }, + { 0x11, 0x01, 0x1101, "pushed block in west outside hallway" }, + { 0x12, 0x40, 0x1240, "unlock doors in pumpkin room" }, + { 0x12, 0x20, 0x1220, "explored path to bedroom 2F & 3F" }, + { 0x12, 0x10, 0x1210, "killed second freezard in cage" }, + { 0x12, 0x08, 0x1208, "explored west outside hallway 2F (canon)" }, + { 0x12, 0x04, 0x1204, "unlock door to big key in big key room" }, + //{ 0x12, 0x02, 0x1202, "freezard cage canon orientation" }, + //{ 0x12, 0x01, 0x1201, "freezard cage canon orientation" }, + { 0x13, 0x40, 0x1340, "killed chilfos in pumpkin room (unlock south door)" }, + { 0x13, 0x20, 0x1320, "?" }, + { 0x13, 0x10, 0x1310, "killed chilfos in big key room" }, + { 0x13, 0x08, 0x1308, "broke center ice in Ice puzzle room" }, + { 0x13, 0x04, 0x1304, "broke first right armor in first room" }, + { 0x13, 0x02, 0x1302, "broke second right armor in first room (spawns poe)" }, + { 0x13, 0x01, 0x1301, "broke third right armor in first room" }, + { 0x14, 0x80, 0x1480, "killed poe in south east room 2F" }, + { 0x14, 0x40, 0x1440, "Midna big key text seen" }, + { 0x14, 0x10, 0x1410, "explored east outside hallway 2F " }, + { 0x14, 0x08, 0x1408, "explored west outside hallway 1F (canonballs)" }, + { 0x14, 0x04, 0x1404, "explored room above living room (2F)" }, + { 0x14, 0x02, 0x1402, "explored freezard in cages room 2F" }, + { 0x14, 0x01, 0x1401, "Intro Cutscene" }, + { 0x15, 0x80, 0x1580, "unlock west door in pumpkin room" }, + { 0x15, 0x40, 0x1540, "destroyed first right armor in cheese room" }, + { 0x15, 0x20, 0x1520, "freezard in cage cs" }, + { 0x15, 0x08, 0x1508, "courtyard intro cs" }, + { 0x15, 0x04, 0x1504, "killed poe in center of first room" }, + { 0x15, 0x02, 0x1502, "pumpkin room intro cs" }, + { 0x15, 0x01, 0x1501, "?" }, + { 0x16, 0x80, 0x1680, "?" }, + { 0x16, 0x40, 0x1640, "Yeta points to kitchen cs" }, + { 0x16, 0x20, 0x1620, "Midna Cheese text prompt" }, + { 0x16, 0x10, 0x1610, "Midna Cheese text seen" }, + { 0x16, 0x08, 0x1608, "Midna pumpkin text seen" }, + { 0x16, 0x04, 0x1604, "Darkhammer (unsets after defeat)" }, + { 0x16, 0x02, 0x1602, "Midna pumpkin text prompt" }, + { 0x16, 0x01, 0x1601, "unlock exit door in big key room" }, + { 0x17, 0x80, 0x1780, "map marker bedroom key" }, + { 0x17, 0x40, 0x1740, "map marker ordon cheese" }, + { 0x17, 0x20, 0x1720, "map marker ordon pumpkin" }, + { 0x17, 0x10, 0x1710, "Blizzeta Cutscene" }, + { 0x17, 0x08, 0x1708, "broke first ice south door in north west room 1F" }, + { 0x17, 0x04, 0x1704, "broke second ice south door in north west room 1F" }, + { 0x17, 0x02, 0x1702, "broke third ice south door in north west room 1F" }, + { 0x17, 0x01, 0x1701, "broke ice wall in compass room 2F" }, +}; + +inline EventAreaFlags eventAreaFlagsToT[] = +{ + { 0x00, 0x80, 0x0080, "map marker statue in room 2" }, + { 0x00, 0x40, 0x0040, "map marker statue will spawn in room 2" }, + { 0x00, 0x20, 0x0020, "map marker statue in room 3" }, + { 0x00, 0x10, 0x0010, "map marker statue will spawn in room 3" }, + { 0x00, 0x08, 0x0008, "map marker statue in room 4" }, + { 0x00, 0x04, 0x0004, "map marker statue will spawn in room 4" }, + { 0x00, 0x02, 0x0002, "map marker statue in room 5" }, + { 0x00, 0x01, 0x0001, "map marker statue will spawn in room 5" }, + { 0x01, 0x80, 0x0180, "map marker statue in room 6" }, + { 0x01, 0x40, 0x0140, "map marker statue will spawn in room 6" }, + { 0x01, 0x20, 0x0120, "map marker statue in room 7" }, + { 0x01, 0x10, 0x0110, "map marker statue will spawn in room 7" }, + { 0x02, 0x10, 0x0210, "PoH big chest room 4" }, + { 0x02, 0x02, 0x0202, "small key big chest room 6 7F" }, + { 0x02, 0x01, 0x0201, "big key chest" }, + { 0x03, 0x40, 0x0340, "compass big chest room 4" }, + { 0x03, 0x20, 0x0320, "PoH big chest south room 5F" }, + { 0x03, 0x10, 0x0310, "small key big chest south room 5F" }, + { 0x03, 0x04, 0x0304, "small chest room 2 2F" }, + { 0x03, 0x02, 0x0302, "map big chest room 2 3F" }, + { 0x03, 0x01, 0x0301, "dominion rod big chest" }, + { 0x04, 0x80, 0x0480, "Ooccoo" }, + { 0x05, 0x01, 0x0501, "small chest big key room" }, + { 0x06, 0x80, 0x0680, "small chest room 5 7F" }, + { 0x06, 0x40, 0x0640, "south small chest south room 5F" }, + { 0x06, 0x20, 0x0620, "small chest room 2 3F" }, + { 0x06, 0x10, 0x0610, "big chest room 5 6F" }, + { 0x06, 0x08, 0x0608, "big chest room 6 8F" }, + { 0x06, 0x02, 0x0602, "small key big chest room 1" }, + { 0x07, 0x02, 0x0702, "map marker statue in room 1" }, + { 0x07, 0x01, 0x0701, "map marker statue will spawn in room 1" }, + { 0x08, 0x80, 0x0880, "Midna text asking to look at missing statue room 1" }, + { 0x08, 0x40, 0x0840, "Midna text after looking at missing statue room 1" }, + { 0x08, 0x20, 0x0820, "unlock door in room 6 8F" }, + { 0x08, 0x10, 0x0810, "unlock door in room 3 5F (spawn baby and young gohmas in south room 5F)" }, + { 0x08, 0x08, 0x0808, "unlock door in room 1" }, + { 0x08, 0x04, 0x0804, "dominion rod big chest (set after)" }, + { 0x08, 0x02, 0x0802, "killed poe in room 3 3F" }, + { 0x08, 0x01, 0x0801, "killed poe in room 5 7F" }, + { 0x09, 0x20, 0x0920, "intro cs" }, + { 0x09, 0x10, 0x0910, "scale intro cs room 5 6F" }, + { 0x09, 0x08, 0x0908, "change balance of scale for the first time cs" }, + { 0x09, 0x04, 0x0904, "change balance of scale for the first time" }, + { 0x0A, 0x80, 0x0A80, "entered room 7" }, + { 0x0A, 0x40, 0x0A40, "spawn west big chest in south room 5F" }, + { 0x0A, 0x08, 0x0A08, "statue getting possessed for the first time cs" }, + { 0x0B, 0x40, 0x0B40, "deactivate statue slot in room 1 (opens door and deactivates statue)" }, + { 0x0B, 0x20, 0x0B20, "deactivate bell in room 2 (required to spawn in room 1)" }, + { 0x0B, 0x10, 0x0B10, "deactivate bell in room 3 (required to spawn in room 2)" }, + { 0x0B, 0x08, 0x0B08, "deactivate bell in room 4 (required to spawn in room 3)" }, + { 0x0B, 0x04, 0x0B04, "deactivate bell in room 5 (required to spawn in room 4)" }, + { 0x0B, 0x02, 0x0B02, "deactivate bell in room 6 (required to spawn in room 5)" }, + { 0x0B, 0x01, 0x0B01, "deactivate bell in room 7 (required to spawn in room 6)" }, + { 0x0C, 0x80, 0x0C80, "unlock door in south room 5F" }, + { 0x0C, 0x40, 0x0C40, "killed armos in room 2 3F (spawn big chest)" }, + { 0x0C, 0x20, 0x0C20, "killed armos in big key room (unlocks door)" }, + { 0x0C, 0x10, 0x0C10, "lit both torches in room 1 (unset if you leave) (spawn big chest)" }, + { 0x0C, 0x04, 0x0C04, "broke gate in room 2 3F" }, + { 0x0C, 0x02, 0x0C02, "broke fouth gate in room 2 2F" }, + { 0x0C, 0x01, 0x0C01, "broke first gate in room 2 2F" }, + { 0x0D, 0x80, 0x0D80, "broke third gate in room 2 2F" }, + { 0x0D, 0x40, 0x0D40, "broke second gate in room 2 2F" }, + { 0x0D, 0x20, 0x0D20, "broke second sliding door in room 4 4F" }, + { 0x0D, 0x10, 0x0D10, "broke third sliding door in room 4 5F" }, + { 0x0D, 0x08, 0x0D08, "broke second sliding door in room 4 5F" }, + { 0x0D, 0x04, 0x0D04, "broke first sliding door in room 4 5F" }, + { 0x0D, 0x02, 0x0D02, "broke first sliding door in room 4 4F" }, + { 0x0D, 0x01, 0x0D01, "broke first beamos in room before boss" }, + { 0x0E, 0x80, 0x0E80, "broke left beamos in room before boss" }, + { 0x0E, 0x40, 0x0E40, "broke right beamos in room before boss" }, + { 0x0E, 0x20, 0x0E20, "broke beamos in room 6 7F" }, + { 0x0E, 0x10, 0x0E10, "broke first beamos in room 4 4F" }, + { 0x0E, 0x08, 0x0E08, "broke second beamos in room 4 4F" }, + { 0x0E, 0x04, 0x0E04, "broke beamos in room 3 3F" }, + { 0x0E, 0x01, 0x0E01, "broke gate to bell in room 3 3F" }, + { 0x0F, 0x80, 0x0F80, "broke gate to poe in room 3 3F" }, + { 0x0F, 0x40, 0x0F40, "broke gate in room 6 8F" }, + { 0x0F, 0x20, 0x0F20, "open door to poe in room 3 3F" }, + { 0x0F, 0x10, 0x0F10, "spawn baby gohmas in beamos room 3 3F" }, + { 0x0F, 0x08, 0x0F08, "broke gate in room 6 8F cs" }, + { 0x0F, 0x01, 0x0F01, "unlock boss door" }, + { 0x10, 0x80, 0x1080, "open big door in room 1 cs part 2" }, + { 0x10, 0x40, 0x1040, "open big door in room 1 cs part 1" }, + { 0x10, 0x10, 0x1010, "killed a baby gohma in room 6 8F" }, + { 0x10, 0x08, 0x1008, "killed a baby gohma in room 6 8F" }, + { 0x10, 0x04, 0x1004, "killed a baby gohma in room 6 8F" }, + { 0x10, 0x02, 0x1002, "killed a baby gohma in room 6 8F" }, + { 0x10, 0x01, 0x1001, "killed a baby gohma in room 6 8F" }, + { 0x11, 0x80, 0x1180, "killed a baby gohma in room 6 8F" }, + { 0x11, 0x40, 0x1140, "killed a baby gohma in room 6 8F" }, + { 0x11, 0x20, 0x1120, "killed a baby gohma in room 6 8F" }, + { 0x11, 0x10, 0x1110, "killed a baby gohma in room 6 8F" }, + { 0x11, 0x08, 0x1108, "killed a baby gohma in room 6 8F" }, + { 0x11, 0x04, 0x1104, "killed a baby gohma in room 6 8F" }, + { 0x11, 0x02, 0x1102, "killed a baby gohma in room 6 8F" }, + { 0x12, 0x80, 0x1280, "pressed button in room 1 (or 6 8F) for the first time cs" }, + { 0x12, 0x40, 0x1240, "pressed button in room 6 7F for the first time cs" }, + { 0x12, 0x20, 0x1220, "killed both armos in room 6 8F (opens gate)" }, + { 0x12, 0x08, 0x1208, "pressed buttons in room 3 5F for the first time cs" }, + { 0x12, 0x04, 0x1204, "pressed buttons in room 2 3F for the first time cs" }, + { 0x12, 0x02, 0x1202, "pressed button in room 2 2F for the first time cs" }, + { 0x13, 0x40, 0x1340, "open gate to dominion rod in room 7" }, + { 0x13, 0x20, 0x1320, "statue spawning out of bell in room 1 cs trigger" }, + { 0x13, 0x10, 0x1310, "statue spawning out of bell in room 1 cs" }, + { 0x13, 0x08, 0x1308, "killed all baby gohmas in room 6 8F (spawn big chest)" }, + { 0x13, 0x04, 0x1304, "killed all enemies in room 5 6F (spawn big chest)" }, + { 0x13, 0x02, 0x1302, "killed west armos in south room 5F" }, + { 0x13, 0x01, 0x1301, "killed east armos in south room 5F" }, + { 0x14, 0x40, 0x1440, "unlock door in room 7" }, + { 0x14, 0x20, 0x1420, "statue getting in bell in room 7 cs first part" }, + { 0x14, 0x10, 0x1410, "statue getting in bell in room 7 cs second part" }, + { 0x14, 0x08, 0x1408, "statue getting in bell in room 7 cs trigger" }, + { 0x14, 0x04, 0x1404, "open gate to dominion rod in room 7 cs" }, + { 0x14, 0x02, 0x1402, "statue spawn out of bell in room 6" }, + { 0x14, 0x01, 0x1401, "statue spawning out of bell in room 6 cs trigger" }, + { 0x15, 0x80, 0x1580, "statue spawning out of bell in room 6 cs part 1" }, + { 0x15, 0x40, 0x1540, "statue getting in bell in room 6 cs trigger" }, + { 0x15, 0x20, 0x1520, "statue getting in bell in room 6 cs" }, + { 0x15, 0x10, 0x1510, "statue spawning out of bell in room 5 cs trigger" }, + { 0x15, 0x08, 0x1508, "statue spawning out of bell in room 5 cs" }, + { 0x15, 0x04, 0x1504, "statue getting in bell in room 5 cs trigger" }, + { 0x15, 0x02, 0x1502, "statue getting in bell in room 5 cs" }, + { 0x15, 0x01, 0x1501, "statue spawning out of bell in room 4 cs trigger" }, + { 0x16, 0x80, 0x1680, "statue spawning out of bell in room 4 cs" }, + { 0x16, 0x40, 0x1640, "statue getting in bell in room 4 cs trigger" }, + { 0x16, 0x20, 0x1620, "statue getting in bell in room 4 cs" }, + { 0x16, 0x10, 0x1610, "statue spawning out of bell in room 3 cs trigger" }, + { 0x16, 0x08, 0x1608, "statue spawning out of bell in room 3 cs" }, + { 0x16, 0x04, 0x1604, "statue getting in bell in room 3 cs trigger" }, + { 0x16, 0x02, 0x1602, "statue getting in bell in room 3 cs" }, + { 0x16, 0x01, 0x1601, "statue spawning out of bell in room 2 cs trigger" }, + { 0x17, 0x80, 0x1780, "statue spawning out of bell in room 2 cs" }, + { 0x17, 0x40, 0x1740, "statue getting in bell in room 2 cs trigger" }, + { 0x17, 0x20, 0x1720, "statue getting in bell in room 2 cs" }, + { 0x17, 0x10, 0x1710, "deactivate spawning bell in room 7" }, + { 0x17, 0x08, 0x1708, "big door in room 1 opens" }, + { 0x17, 0x04, 0x1704, "open big door in room 1 cs part 2 trigger" }, + { 0x17, 0x02, 0x1702, "open big door in room 1 cs part 1 trigger" }, + { 0x17, 0x01, 0x1701, "statue placed in slot in room 1" }, +}; + +struct MultiByteAreaFlag +{ + // flags treated as bool, shift left after checking bool + const char* name; + uint16_t highOrderflag; + uint16_t lowOrderflag; + std::map enumValues; +}; +constexpr uint16_t AREA_FLAG_NONE = 0; + +inline MultiByteAreaFlag ForestTempleMultiByteFlags[] = +{ + { + "Worm Room position", + 0x0D3C, AREA_FLAG_NONE, + { + { 0x0, "On Totem", }, + { 0x8, "North", }, + { 0x7, "North North East", }, + { 0x6, "North East", }, + { 0x5, "East North East", }, + { 0x4, "East", }, + { 0x3, "East South East", }, + { 0x2, "South South East", }, + { 0x1, "South", }, + { 0xF, "South South West", }, + { 0xE, "South West", }, + { 0xD, "West South West", }, + { 0xC, "West", }, + { 0xA, "West North West", }, + { 0xB, "North West", }, + { 0x9, "North North West", }, + }, + } +}; + +inline const std::map _SPRIceBlockPuzzleLocations = +{ + { 0x00, "N/A" }, + { 0x0C, "(-2, 2)" }, + { 0x0B, "(-1, 2)" }, + { 0x0A, "( 0, 2)" }, + { 0x09, "( 1, 2)" }, + { 0x08, "( 2, 2)" }, + + { 0x0D, "(-2, 1)" }, + { 0x14, "(-1, 1)" }, + { 0x15, "( 0, 1)" }, + { 0x16, "( 1, 1)" }, + { 0x07, "( 2, 1)" }, + + { 0x0E, "(-2, 0)" }, + { 0x13, "(-1, 0)" }, + { 0x1A, "( 0, 0)" }, + { 0x17, "( 1, 0)" }, + { 0x06, "( 2, 0)" }, + + { 0x0F, "(-2, -1)" }, + { 0x12, "(-1, -1)" }, + { 0x19, "( 0, -1)" }, + { 0x18, "( 1, -1)" }, + { 0x05, "( 2, -1)" }, + + { 0x10, "(-2, -2)" }, + { 0x11, "(-1, -2)" }, + { 0x02, "( 0, -2)" }, + { 0x03, "( 1, -2)" }, + { 0x04, "( 2, -2)" }, + + { 0x01, "( 0, -3)" }, +}; + +inline MultiByteAreaFlag SPRMultiByteFlags[] = { + { + "Courtyard First Floor Cannon", + 0x1160, AREA_FLAG_NONE, + { + { 1, "North" }, + { 0, "South" }, + { 2, "East" }, + { 3, "West" } + }, + }, + { + "Courtyard Second Floor Cannon", + 0x1001, 0x1180, + { + { 3, "North East" }, + { 0, "South East" }, + { 2, "South West" }, + { 1, "North West" }, + }, + }, + { + "Freezard Cage Room Cannon", + 0x1203, AREA_FLAG_NONE, + { + { 0, "North" }, + { 2, "East" }, + { 1, "South" }, + { 3, "West" }, + }, + }, + { + "North West Room Cannon", + 0x0C0C, AREA_FLAG_NONE, + { + { 3, "North" }, + { 0, "East" }, + { 2, "South" }, + { 1, "West" }, + }, + }, + { + "Ice Block 1 Location", + 0x0F1F, AREA_FLAG_NONE, + _SPRIceBlockPuzzleLocations + }, + { + "Ice Block 2 Location", + 0x08F8, AREA_FLAG_NONE, + _SPRIceBlockPuzzleLocations + }, + { + "Ice Block 3 Location", + 0x0807, 0x09C0, + _SPRIceBlockPuzzleLocations + } +}; + +inline const std::map _CoOBlockPuzzle1Locations = { + { 0x00, "N/A" }, + { 0x0B, "(-1, 2)" }, + { 0x0C, "( 0, 2)" }, + { 0x01, "( 1, 2)" }, + + { 0x0A, "(-1, 1)" }, + { 0x0D, "( 0, 1)" }, + { 0x02, "( 1, 1)" }, + + { 0x09, "(-1, 0)" }, + { 0x0E, "( 0, 0)" }, + { 0x03, "( 1, 0)" }, + + { 0x08, "(-1, -1)" }, + { 0x0F, "( 0, -1)" }, + { 0x04, "( 1, -1)" }, + + { 0x07, "(-1, -2)" }, + { 0x06, "( 0, -2)" }, + { 0x05, "( 1, -2)" }, +}; + +inline const std::map _CoOBlockPuzzle2Locations = { + { 0x00, "N/A" }, + { 0x01, "(-1, 2)" }, + { 0x02, "( 0, 2)" }, + { 0x03, "( 1, 2)" }, + + { 0x0C, "(-1, 1)" }, + { 0x0D, "( 0, 1)" }, + { 0x04, "( 1, 1)" }, + { 0x05, "( 2, 1)" }, + + { 0x0B, "(-1, 0)" }, + { 0x0E, "( 0, 0)" }, + { 0x0F, "( 1, 0)" }, + { 0x06, "( 2, 0)" }, + + { 0x0A, "(-1, -1)" }, + { 0x09, "( 0, -1)" }, + { 0x08, "( 1, -1)" }, + { 0x07, "( 2, -1)" }, +}; + +inline const std::map _CoOBlockPuzzle3Locations = { + { 0x00, "N/A" }, + { 0x01, "(-2, 2)" }, + { 0x02, "(-1, 2)" }, + { 0x03, "( 0, 2)" }, + { 0x04, "( 1, 2)" }, + { 0x05, "( 2, 2)" }, + + { 0x10, "(-2, 1)" }, + { 0x11, "(-1, 1)" }, + { 0x12, "( 0, 1)" }, + { 0x13, "( 1, 1)" }, + { 0x06, "( 2, 1)" }, + + { 0x0F, "(-2, 0)" }, + { 0x18, "(-1, 0)" }, + { 0x19, "( 0, 0)" }, + { 0x14, "( 1, 0)" }, + { 0x07, "( 2, 0)" }, + + { 0x0E, "(-2, -1)" }, + { 0x17, "(-1, -1)" }, + { 0x16, "( 0, -1)" }, + { 0x15, "( 1, -1)" }, + { 0x08, "( 2, -1)" }, + + { 0x0D, "(-2, -2)" }, + { 0x0C, "(-1, -2)" }, + { 0x0B, "( 0, -2)" }, + { 0x0A, "( 1, -2)" }, + { 0x09, "( 2, -2)" }, +}; + +inline MultiByteAreaFlag CoOMultiByteFlags[] = { + { + "Puzzle 1 Block 1 Location", + 0x0907, 0x0A80, + _CoOBlockPuzzle1Locations + }, + { + "Puzzle 1 Block 2 Location", + 0x0A3C, AREA_FLAG_NONE, + _CoOBlockPuzzle1Locations + }, + { + "Puzzle 1 Block 3 Location", + 0x0B0F, AREA_FLAG_NONE, + _CoOBlockPuzzle1Locations + }, + { + "Puzzle 2 Block 1 Location", + 0x08F0, AREA_FLAG_NONE, + _CoOBlockPuzzle2Locations + }, + { + "Puzzle 2 Block 2 Location", + 0x080F, AREA_FLAG_NONE, + _CoOBlockPuzzle2Locations + }, + { + "Puzzle 2 Block 3 Location", + 0x09F0, AREA_FLAG_NONE, + _CoOBlockPuzzle2Locations + }, + { + "Puzzle 3 Block 1 Location", + 0x0E7C, AREA_FLAG_NONE, + _CoOBlockPuzzle3Locations + }, + { + "Puzzle 3 Block 2 Location", + 0x0E03, 0x0FE0, + _CoOBlockPuzzle3Locations + }, + { + "Puzzle 3 Block 3 Location", + 0x0F1F, AREA_FLAG_NONE, + _CoOBlockPuzzle3Locations + } +}; + +struct AreaFlagIter { + std::span bitFlags; + std::span multibyteFlags; +}; + +inline std::map imguiAreaFlagLookup = +{ + { 0x00, AreaFlagIter{ eventAreaFlagsOrdon, {} } }, + { 0x01, AreaFlagIter{ eventAreaFlagsSewer, {} } }, + { 0x02, AreaFlagIter{ eventAreaFlagsFaron, {} } }, + { 0x03, AreaFlagIter{ eventAreaFlagsEldin, {} } }, + { 0x04, AreaFlagIter{ eventAreaFlagsLanayru, {} } }, + // 5 unused + { 0x06, AreaFlagIter{ eventAreaFlagsHyruleField, {} } }, + { 0x07, AreaFlagIter{ eventAreaFlagsSacredGrove, {} } }, + { 0x08, AreaFlagIter{ eventAreaFlagsSnowpeak, {} } }, + { 0x09, AreaFlagIter{ eventAreaFlagsCastleTown, {} } }, + { 0x0A, AreaFlagIter{ eventAreaFlagsGerudoDesert, {} } }, + { 0x0B, AreaFlagIter{ eventAreaFlagsFishingPond, {} } }, + // C-F unused + { 0x10, AreaFlagIter{ eventAreaFlagsFT, ForestTempleMultiByteFlags } }, + { 0x11, AreaFlagIter{ eventAreaFlagsGM, {} } }, + { 0x12, AreaFlagIter{ eventAreaFlagsLBT, {} } }, + { 0x13, AreaFlagIter{ eventAreaFlagsAG, {} } }, + { 0x14, AreaFlagIter{ eventAreaFlagsSPR, SPRMultiByteFlags } }, + { 0x15, AreaFlagIter{ eventAreaFlagsToT, {} } }, + { 0x16, AreaFlagIter{ eventAreaFlagsCitS, {} } }, + { 0x17, AreaFlagIter{ eventAreaFlagsPoT, {} } }, + { 0x18, AreaFlagIter{ eventAreaFlagsHC, {} } }, + { 0x19, AreaFlagIter{ eventAreaFlagsCoO, CoOMultiByteFlags } }, + { 0x1A, AreaFlagIter{ eventAreaFlagsLHLC, {} } }, + { 0x1B, AreaFlagIter{ eventAreaFlagsGrotto, {} } }, +}; + #endif // !DUSK_IMGUI_EVENTFLAGS_HPP \ No newline at end of file diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.cpp b/src/dusk/imgui/ImGuiFirstRunPreset.cpp deleted file mode 100644 index 2a9bcc5c6e..0000000000 --- a/src/dusk/imgui/ImGuiFirstRunPreset.cpp +++ /dev/null @@ -1,139 +0,0 @@ -#include "ImGuiFirstRunPreset.hpp" - -#include "imgui.h" -#include "ImGuiConsole.hpp" -#include "ImGuiEngine.hpp" -#include "dusk/settings.h" -#include "dusk/config.hpp" -#include - -namespace dusk { - -static void ApplyPresetClassic() { - auto& s = getSettings(); - s.video.lockAspectRatio.setValue(true); - s.game.bloomMode.setValue(BloomMode::Classic); - AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT); -} - -static void ApplyPresetHD() { - auto& s = getSettings(); - s.game.bloomMode.setValue(BloomMode::Classic); - s.game.hideTvSettingsScreen.setValue(true); - s.game.skipWarningScreen.setValue(true); - s.game.noReturnRupees.setValue(true); - s.game.disableRupeeCutscenes.setValue(true); - s.game.noSwordRecoil.setValue(true); - s.game.fastClimbing.setValue(true); - s.game.noMissClimbing.setValue(true); - s.game.fastTears.setValue(true); - s.game.biggerWallets.setValue(true); - s.game.invertCameraXAxis.setValue(true); - s.game.freeCamera.setValue(true); -} - -static void ApplyPresetDusk() { - ApplyPresetHD(); - - auto& s = getSettings(); - s.game.enableAchievementNotifications.setValue(true); - s.game.enableQuickTransform.setValue(true); - s.game.instantSaves.setValue(true); - s.game.midnasLamentNonStop.setValue(true); - s.game.enableFrameInterpolation.setValue(true); - s.game.sunsSong.setValue(true); - s.game.bloomMode.setValue(BloomMode::Dusk); -} - -// ========================================================================= - -void ImGuiFirstRunPreset::draw() { - const char* modalTitle = "Welcome to Dusk!"; - - if (m_done) return; - - if (!m_opened) { - ImGui::OpenPopup(modalTitle); - m_opened = true; - } - - const ImGuiViewport* viewport = ImGui::GetMainViewport(); - ImGui::SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Always, ImVec2(0.5f, 0.5f)); - ImGui::SetNextWindowSize(ImVec2(800.0f * ImGuiScale(), 0.0f), ImGuiCond_Always); - - if (!ImGui::BeginPopupModal(modalTitle, nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) { - // force the user to actually pick one, and not just hit escape to skip the dialog - m_opened = false; - return; - } - - ImGui::TextWrapped("Choose a preset to get started. You can change any setting later from the Enhancements menu."); - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - - int chosen = -1; - - if (ImGui::BeginTable("##presets", 5, ImGuiTableFlags_None)) { - ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale()); - ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthFixed, 16.0f * ImGuiScale()); - ImGui::TableSetupColumn(nullptr, ImGuiTableColumnFlags_WidthStretch); - - ImGui::TableNextRow(); - - ImGui::PushFont(ImGuiEngine::fontLarge); - - ImGui::TableSetColumnIndex(0); - if (ImGui::Button("Classic##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { - chosen = 0; - } - - ImGui::TableSetColumnIndex(2); - if (ImGui::Button("HD##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) { - chosen = 1; - } - - ImGui::TableSetColumnIndex(4); - if (ImGui::Button("Dusk##btn", ImVec2(ImGui::GetContentRegionAvail().x, 80.0f * ImGuiScale()))) - { - chosen = 2; - } - - ImGui::PopFont(); - - ImGui::TableNextRow(); - - ImGui::TableSetColumnIndex(0); - ImGui::Spacing(); - ImGui::TextWrapped("All enhancements disabled to match the GameCube version. Good for speedrunning or simple nostalgia!"); - - ImGui::TableSetColumnIndex(2); - ImGui::Spacing(); - ImGui::TextWrapped("Some enhancements enabled to match the HD version. A good starting point for most players!"); - - ImGui::TableSetColumnIndex(4); - ImGui::Spacing(); - ImGui::TextWrapped("More enhancements enabled than the HD preset. Veteran players will appreciate the additional tweaks!"); - - ImGui::EndTable(); - } - - if (chosen >= 0) { - if (chosen == 0) ApplyPresetClassic(); - if (chosen == 1) ApplyPresetHD(); - if (chosen == 2) ApplyPresetDusk(); - - getSettings().backend.wasPresetChosen.setValue(true); - config::Save(); - - m_done = true; - - ImGui::CloseCurrentPopup(); - } - - ImGui::EndPopup(); -} - -} // namespace dusk diff --git a/src/dusk/imgui/ImGuiFirstRunPreset.hpp b/src/dusk/imgui/ImGuiFirstRunPreset.hpp deleted file mode 100644 index 33ba6b3d0f..0000000000 --- a/src/dusk/imgui/ImGuiFirstRunPreset.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -namespace dusk { - -class ImGuiFirstRunPreset { -public: - void draw(); - -private: - bool m_opened = false; - bool m_done = false; -}; - -} // namespace dusk diff --git a/src/dusk/imgui/ImGuiHeapOverlay.cpp b/src/dusk/imgui/ImGuiHeapOverlay.cpp index 577db12fc1..a63ce4cfbe 100644 --- a/src/dusk/imgui/ImGuiHeapOverlay.cpp +++ b/src/dusk/imgui/ImGuiHeapOverlay.cpp @@ -180,9 +180,9 @@ namespace dusk { void ShowHeapDetailed(JKRHeap* heap, OpenHeapData& data, bool& open) { char title[128]; const char* name = data.Safe ? heap->getName() : "INVALID"; - snprintf(title, sizeof(title), "Heap %s##%p", heap->getName(), static_cast(heap)); + snprintf(title, sizeof(title), "Heap %s##%p", name, static_cast(heap)); - if (!ImGui::Begin(name, &open)) { + if (!ImGui::Begin(title, &open)) { ImGui::End(); return; } @@ -195,7 +195,7 @@ namespace dusk { heap->lock(); - ImGui::Text("Name: %s", heap->getName()); + ImGui::Text("Name: %s", name); const auto size = BytesToString(heap->getSize()); const auto freeSize = BytesToString(heap->getFreeSize()); ImGui::Text("Size: %08X (%s), free: %08X (%s)", heap->getSize(), size.c_str(), heap->getFreeSize(), freeSize.c_str()); diff --git a/src/dusk/imgui/ImGuiMenuGame.cpp b/src/dusk/imgui/ImGuiMenuGame.cpp index dcfcc55657..616e0ddbac 100644 --- a/src/dusk/imgui/ImGuiMenuGame.cpp +++ b/src/dusk/imgui/ImGuiMenuGame.cpp @@ -3,29 +3,14 @@ #include "ImGuiEngine.hpp" #include "ImGuiConsole.hpp" -#include "ImGuiMenuGame.hpp" #include "ImGuiConfig.hpp" -#include "JSystem/JUtility/JUTGamePad.h" -#include "dusk/audio/DuskAudioSystem.h" -#include "dusk/audio/DuskDsp.hpp" #include "dusk/main.h" #include "dusk/hotkeys.h" -#include "dusk/settings.h" -#include "m_Do/m_Do_controller_pad.h" -#include "m_Do/m_Do_graphic.h" +#include "m_Do/m_Do_main.h" -#include #include -namespace { -constexpr int kInternalResolutionScaleMax = 12; -} // namespace - -namespace aurora::gx { -extern bool enableLodBias; -} - namespace dusk { void ImGuiMenuGame::ToggleFullscreen() { getSettings().video.enableFullscreen.setValue(!getSettings().video.enableFullscreen); @@ -37,435 +22,17 @@ namespace dusk { void ImGuiMenuGame::draw() { if (ImGui::BeginMenu("Settings")) { - drawAudioMenu(); - drawCheatsMenu(); - drawGameplayMenu(); - drawGraphicsMenu(); - drawInputMenu(); - drawInterfaceMenu(); - - ImGui::Separator(); - - if (ImGui::MenuItem("Reset", hotkeys::DO_RESET)) { - JUTGamePad::C3ButtonReset::sResetSwitchPushing = true; - } - - if (!IsMobile && ImGui::MenuItem("Exit")) { - dusk::IsRunning = false; - } - - ImGui::EndMenu(); - } - } - - void ImGuiMenuGame::drawGraphicsMenu() { - if (ImGui::BeginMenu("Graphics")) { - ImGui::SeparatorText("Display"); - - if (!IsMobile) { - if (ImGui::MenuItem("Toggle Fullscreen", hotkeys::TOGGLE_FULLSCREEN)) { - ToggleFullscreen(); - } - - if (ImGui::Button("Restore Default Window Size")) { - getSettings().video.enableFullscreen.setValue(false); - VISetWindowFullscreen(false); - VISetWindowSize(FB_WIDTH * 2, FB_HEIGHT * 2); - VICenterWindow(); - } - } - - ImGui::Separator(); - - bool vsync = getSettings().video.enableVsync; - if (ImGui::Checkbox("Enable VSync", &vsync)) { - getSettings().video.enableVsync.setValue(vsync); - aurora_enable_vsync(vsync); - config::Save(); - } - - bool lockAspect = getSettings().video.lockAspectRatio; - if (ImGui::Checkbox("Force 4:3 Aspect Ratio", &lockAspect)) { - getSettings().video.lockAspectRatio.setValue(lockAspect); - - if (lockAspect) { - AuroraSetViewportPolicy(AURORA_VIEWPORT_FIT); - } else { - AuroraSetViewportPolicy(AURORA_VIEWPORT_STRETCH); - } - - config::Save(); - } - - ImGui::SeparatorText("Resolution"); - - u32 internalResolutionWidth = 0; - u32 internalResolutionHeight = 0; - AuroraGetRenderSize(&internalResolutionWidth, &internalResolutionHeight); - ImGui::TextDisabled("Current internal resolution: %ux%u", internalResolutionWidth, - internalResolutionHeight); - - int scale = std::clamp(getSettings().game.internalResolutionScale.getValue(), 0, - kInternalResolutionScaleMax); - if (ImGui::SliderInt("Internal Resolution", &scale, 0, kInternalResolutionScaleMax, - scale == 0 ? "Auto" : "%dx")) - { - getSettings().game.internalResolutionScale.setValue(scale); - VISetFrameBufferScale(static_cast(scale)); - config::Save(); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Auto renders at the native window resolution.\n" - "Higher values scale the game's internal framebuffer."); - } - - config::ImGuiSliderInt("Shadow Resolution", getSettings().game.shadowResolutionMultiplier, 1, 8, "x%d"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Improves the shadow resolution, making them higher quality."); - } - - ImGui::SeparatorText("Post-Processing"); - - constexpr const char* bloomModeNames[] = {"Off", "Classic", "Dusk"}; - int bloomMode = static_cast(getSettings().game.bloomMode.getValue()); - if (ImGui::BeginCombo("Bloom", bloomModeNames[bloomMode])) { - for (int i = 0; i < IM_ARRAYSIZE(bloomModeNames); i++) { - const bool selected = bloomMode == i; - if (ImGui::Selectable(bloomModeNames[i], selected)) { - getSettings().game.bloomMode.setValue(static_cast(i)); - config::Save(); - } - if (selected) { - ImGui::SetItemDefaultFocus(); - } - } - ImGui::EndCombo(); - } - - bool bloomOff = bloomMode == static_cast(BloomMode::Off); - if (bloomOff) ImGui::BeginDisabled(); - float mult = getSettings().game.bloomMultiplier.getValue(); - if (ImGui::SliderFloat("Bloom Brightness", &mult, 0.0f, 1.0f, "%.2f")) { - getSettings().game.bloomMultiplier.setValue(mult); - config::Save(); - } - if (bloomOff) ImGui::EndDisabled(); - - ImGui::SeparatorText("Rendering"); - - config::ImGuiCheckbox("Unlock Framerate", getSettings().game.enableFrameInterpolation); - const bool frameInterpolationHovered = ImGui::IsItemHovered(); - ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.72f, 0.2f, 1.0f)); - ImGui::TextUnformatted("[EXPERIMENTAL]"); - ImGui::PopStyleColor(); - if (frameInterpolationHovered || ImGui::IsItemHovered()) { - ImGui::SetTooltip("Uses inter-frame interpolation to enable higher frame rates.\nVisual artifacts, animation glitches, or instability may occur."); - } - - ImGui::Checkbox("Enable LOD Bias", &aurora::gx::enableLodBias); - - config::ImGuiCheckbox("Enable Depth of Field", getSettings().game.enableDepthOfField); - - ImGui::EndMenu(); - } - } - - void ImGuiMenuGame::drawGameplayMenu() { - if (ImGui::BeginMenu("Gameplay")) { - ImGui::SeparatorText("General"); - - config::ImGuiCheckbox("Mirror Mode", getSettings().game.enableMirrorMode); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Mirrors the world horizontally, matching the Wii version of the game."); - } - - config::ImGuiCheckbox("Disable Main HUD", getSettings().game.disableMainHUD); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Disables the main HUD of the game.\n" - "Useful for recording or a more immersive experience!"); - } - - config::ImGuiCheckbox("Restore Wii 1.0 Glitches", getSettings().game.restoreWiiGlitches); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Restores patched glitches from Wii USA 1.0,\n" - "the first released version."); - } - - config::ImGuiCheckbox("Enable Rotating Link Doll", getSettings().game.enableLinkDollRotation); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Enables rotating Link in the collection menu with the C-Stick"); - } - - ImGui::SeparatorText("Difficulty"); - - config::ImGuiSliderInt("Damage Multiplier", getSettings().game.damageMultiplier, 1, 8, "x%d"); - - config::ImGuiCheckbox("Instant Death", getSettings().game.instantDeath); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Any hit will instantly kill you."); - } - - config::ImGuiCheckbox("No Heart Drops", getSettings().game.noHeartDrops); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Hearts will never drop from enemies,\n" - "pots and various other places."); - } - - ImGui::SeparatorText("Quality of Life"); - - config::ImGuiCheckbox("Bigger Wallets", getSettings().game.biggerWallets); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Wallet sizes are like in the HD version. (500, 1000, 2000)"); - } - - config::ImGuiCheckbox("Disable Rupee Cutscenes", getSettings().game.disableRupeeCutscenes); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Rupees won't play cutscenes after you've collected them the first time."); - } - - config::ImGuiCheckbox("Faster Climbing", getSettings().game.fastClimbing); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Quicker climbing on ladders and vines like the HD version."); - } - - config::ImGuiCheckbox("Faster Tears of Light", getSettings().game.fastTears); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Tears of Light dropped by Shadow Insects pop out faster like the HD version."); - } - - config::ImGuiCheckbox("Instant Saves", getSettings().game.instantSaves); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Skip the delay when writing to the Memory Card."); - } - - config::ImGuiCheckbox("Hold B for Instant Text", getSettings().game.instantText); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Make text scroll immediately by holding B."); - } - - config::ImGuiCheckbox("No Climbing Miss Animation", getSettings().game.noMissClimbing); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Prevents Link from playing a struggle animation\n" - "when grabbing ledges or climbing on vines."); - } - - config::ImGuiCheckbox("No Rupee Returns", getSettings().game.noReturnRupees); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Always collect Rupees even if your Wallet is too full."); - } - - config::ImGuiCheckbox("No Sword Recoil", getSettings().game.noSwordRecoil); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Link won't recoil when his sword hits walls."); - } - - config::ImGuiCheckbox("Skip TV Settings Screen", getSettings().game.hideTvSettingsScreen); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Skip the TV calibration screen shown when loading a save."); - } - - config::ImGuiCheckbox("Skip Warning Screen", getSettings().game.skipWarningScreen); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Skip the warning screen shown when starting the game."); - } - - config::ImGuiCheckbox("Sun's Song (R+X)", getSettings().game.sunsSong); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Allows Wolf Link to howl and change the time of day."); - } - - config::ImGuiCheckbox("Quick Transform (R+Y)", getSettings().game.enableQuickTransform); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Transform instantly by pressing R and Y simultaneously."); - } - - ImGui::EndMenu(); - } - } - - void ImGuiMenuGame::drawCheatsMenu() { - if (ImGui::BeginMenu("Cheats")) { - ImGui::SeparatorText("Resources"); - config::ImGuiCheckbox("Infinite Hearts", getSettings().game.infiniteHearts); - config::ImGuiCheckbox("Infinite Arrows", getSettings().game.infiniteArrows); - config::ImGuiCheckbox("Infinite Bombs", getSettings().game.infiniteBombs); - config::ImGuiCheckbox("Infinite Oil", getSettings().game.infiniteOil); - config::ImGuiCheckbox("Infinite Oxygen", getSettings().game.infiniteOxygen); - config::ImGuiCheckbox("Infinite Rupees", getSettings().game.infiniteRupees); - config::ImGuiCheckbox("Items Don't Despawn", getSettings().game.enableIndefiniteItemDrops); - ImGui::SetItemTooltip("Items Don't Despawn Unless You Load A Different Room In Which Case They Do But Even Under Some Circumstances They Don't, It Is Quite Rare Though"); - - ImGui::SeparatorText("Abilities"); - config::ImGuiCheckbox("Moon Jump (R+A)", getSettings().game.moonJump); - config::ImGuiCheckbox("Super Clawshot", getSettings().game.superClawshot); - config::ImGuiCheckbox("Always Greatspin", getSettings().game.alwaysGreatspin); - config::ImGuiCheckbox("Fast Iron Boots", getSettings().game.enableFastIronBoots); - - config::ImGuiCheckbox("Can Transform Anywhere", getSettings().game.canTransformAnywhere); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Allows you to transform even if NPCs are looking."); - } - - config::ImGuiCheckbox("Fast Spinner", getSettings().game.fastSpinner); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Speeds up Spinner movement when holding R."); - } - - config::ImGuiCheckbox("Free Magic Armor", getSettings().game.freeMagicArmor); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Makes the magic armor work without rupees."); - } - - ImGui::EndMenu(); - } - } - - void ImGuiMenuGame::drawAudioMenu() { - if (ImGui::BeginMenu("Audio")) { - - ImGui::SeparatorText("Volume"); - - ImGui::Text("Master Volume"); - if (config::ImGuiSliderInt("##masterVolume", getSettings().audio.masterVolume, 0, 100)) { - dusk::audio::SetMasterVolume(getSettings().audio.masterVolume / 100.0f); - } - - /* - // TODO: Implement additional settings - ImGui::Text("Main Music Volume"); - ImGui::SliderFloat("##mainMusicVolume", &getSettings().audio.mainMusicVolume, 0, 100); - - ImGui::Text("Sub Music Volume"); - ImGui::SliderFloat("##subMusicVolume", &getSettings().audio.subMusicVolume, 0, 100); - - ImGui::Text("Sound Effects Volume"); - ImGui::SliderFloat("##soundEffectsVolume", &getSettings().audio.soundEffectsVolume, 0, 100); - - ImGui::Text("Fanfare Volume"); - ImGui::SliderFloat("##fanfareVolume", &getSettings().audio.fanfareVolume, 0, 100); - - Z2AudioMgr* audioMgr = Z2AudioMgr::getInterface(); - if (audioMgr != nullptr) { - } - */ - - ImGui::SeparatorText("Effects"); - - if (config::ImGuiCheckbox("Enable Reverb", getSettings().audio.enableReverb)) { - dusk::audio::SetEnableReverb(getSettings().audio.enableReverb); - } - - - ImGui::SeparatorText("Tweaks"); - - config::ImGuiCheckbox("No Low HP Sound", getSettings().game.noLowHpSound); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Disable the beeping sound when having low health."); - } - - config::ImGuiCheckbox("Non-Stop Midna's Lament", getSettings().game.midnasLamentNonStop); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Prevents enemy music while Midna's Lament is playing."); - } - - ImGui::EndMenu(); - } - } - - void ImGuiMenuGame::drawInputMenu() { - if (ImGui::BeginMenu("Input")) { - ImGui::SeparatorText("Controller"); - + // TODO: Remove this once Controller Config exists in RmlUi if (ImGui::Button("Configure Controller")){ m_showControllerConfig = !m_showControllerConfig; } - ImGui::SeparatorText("Camera"); - - config::ImGuiCheckbox("Free Camera", getSettings().game.freeCamera); - - if (getSettings().game.freeCamera) { - config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis); - config::ImGuiCheckbox("Invert Camera Y Axis", getSettings().game.invertCameraYAxis); - config::ImGuiSliderFloat("Free Camera Sensitivity", getSettings().game.freeCameraSensitivity, 0.5f, 2.0f, "%.1f"); - } else { - config::ImGuiCheckbox("Invert Camera X Axis", getSettings().game.invertCameraXAxis); - } - - ImGui::SeparatorText("Gyro"); - - config::ImGuiCheckbox("Gyro Aim", getSettings().game.enableGyroAim); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Enables the gyroscope on supported controllers\n" - "while in look mode (C-Up) and while aiming the\n" - "Slingshot, Gale Boomerang, Hero's Bow, Clawshot(s),\n" - "Ball and Chain, and Dominion Rod."); - } - - config::ImGuiCheckbox("Gyro Rollgoal", getSettings().game.enableGyroRollgoal); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Enables the gyroscope on supported controllers to\n" - "tilt the Rollgoal table in Hena's Cabin."); - } - - if (getSettings().game.enableGyroAim || getSettings().game.enableGyroRollgoal) { - config::ImGuiSliderFloat("Gyro Pitch Sensitivity", getSettings().game.gyroSensitivityY, 0.25f, 4.0f, "%.2f"); - config::ImGuiSliderFloat("Gyro Yaw Sensitivity", getSettings().game.gyroSensitivityX, 0.25f, 4.0f, "%.2f"); - - if (getSettings().game.enableGyroRollgoal) { - config::ImGuiSliderFloat("Rollgoal Sensitivity", getSettings().game.gyroSensitivityRollgoal, 0.25f, 4.0f, "%.2f"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Additional multiplier for scaling how strongly\n" - "the gyroscope affects the Rollgoal table."); - } - } - - config::ImGuiSliderFloat("Gyro Deadband", getSettings().game.gyroDeadband, 0.0f, 0.5f, "%.3f"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Angular rates below this magnitude are treated as zero,\n" - "reducing drift and jitter when the controller is still."); - } - - config::ImGuiSliderFloat("Gyro Smoothing", getSettings().game.gyroSmoothing, 0.0f, 1.0f, "%.2f"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Low values track raw gyro input more closely,\n" - "while higher values smooth out input over time."); - } - - config::ImGuiCheckbox("Invert Gyro Pitch", getSettings().game.gyroInvertPitch); - config::ImGuiCheckbox("Invert Gyro Yaw", getSettings().game.gyroInvertYaw); - } - - ImGui::SeparatorText("Tools"); - - config::ImGuiCheckbox("Turbo Key", getSettings().game.enableTurboKeybind); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Hold TAB to increase game speed by up to 4x."); - } - ImGui::Checkbox("Show Input Viewer", &m_showInputViewer); ImGui::EndMenu(); } } - void ImGuiMenuGame::drawInterfaceMenu() { - if (ImGui::BeginMenu("Interface")) { - config::ImGuiCheckbox("Achievement Notifications", getSettings().game.enableAchievementNotifications); - config::ImGuiCheckbox("Skip Pre-Launch UI", getSettings().backend.skipPreLaunchUI); - config::ImGuiCheckbox("Show Pipeline Compilation", getSettings().backend.showPipelineCompilation); -#if DUSK_ENABLE_SENTRY_NATIVE - config::ImGuiCheckbox("Enable Crash Reporting", getSettings().backend.enableCrashReporting); -#endif - if (!IsMobile) { - config::ImGuiCheckbox("Pause on Focus Lost", getSettings().game.pauseOnFocusLost); - } - - ImGui::EndMenu(); - } - } - static void drawVirtualStick(const char* id, const ImVec2& stick) { float scale = ImGuiScale(); ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x + 45 * scale, ImGui::GetCursorPos().y + 10)); @@ -1001,4 +568,89 @@ namespace dusk { ImGui::End(); } + + static std::string GetFormattedTime(OSTime ticks) { + OSCalendarTime time; + OSTicksToCalendarTime(ticks, &time); + + return fmt::format("{0:02}:{1:02}:{2:02}.{3:03}", time.hour, time.min, time.sec, time.msec); + } + + void ImGuiMenuGame::resetForSpeedrunMode() { + // reset settings that should be off for speedrun mode + mDoMain::developmentMode = -1; + + getSettings().game.damageMultiplier.setValue(1); + getSettings().game.instantDeath.setValue(false); + getSettings().game.noHeartDrops.setValue(false); + + getSettings().game.infiniteHearts.setValue(false); + getSettings().game.infiniteArrows.setValue(false); + getSettings().game.infiniteBombs.setValue(false); + getSettings().game.infiniteOil.setValue(false); + getSettings().game.infiniteOxygen.setValue(false); + getSettings().game.infiniteRupees.setValue(false); + getSettings().game.enableIndefiniteItemDrops.setValue(false); + + getSettings().game.moonJump.setValue(false); + getSettings().game.superClawshot.setValue(false); + getSettings().game.alwaysGreatspin.setValue(false); + getSettings().game.enableFastIronBoots.setValue(false); + getSettings().game.canTransformAnywhere.setValue(false); + getSettings().game.fastSpinner.setValue(false); + getSettings().game.freeMagicArmor.setValue(false); + + getSettings().game.enableTurboKeybind.setValue(false); + getSettings().game.debugFlyCam.setValue(false); + } + + SpeedrunInfo m_speedrunInfo; + + void ImGuiMenuGame::drawSpeedrunTimerOverlay() { + if (!getSettings().game.speedrunMode) { + return; + } + + // L+R+A+Start to reset timer + if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigStart(PAD_1)) { + m_speedrunInfo.reset(); + } + + // L+R+A+Z to manually stop timer + if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigZ(PAD_1)) { + if (m_speedrunInfo.m_isRunStarted) { + m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp; + m_speedrunInfo.m_isRunStarted = false; + } + } + + ImGui::SetNextWindowBgAlpha(0.65f); + ImGuiWindowFlags flags = + ImGuiWindowFlags_NoResize + | ImGuiWindowFlags_NoDocking + | ImGuiWindowFlags_NoTitleBar + | ImGuiWindowFlags_NoScrollbar; + + if (ImGui::Begin("##SpeedrunTimerWindow", nullptr, flags)) { + OSTime elapsedTime = 0; + if (m_speedrunInfo.m_isRunStarted) { + elapsedTime = OSGetTime() - m_speedrunInfo.m_startTimestamp; + } else if (m_speedrunInfo.m_endTimestamp != 0) { + elapsedTime = m_speedrunInfo.m_endTimestamp; + } + + ImGui::Text("RTA"); + ImGui::SameLine(60.0f); + ImGuiStringViewText(GetFormattedTime(elapsedTime)); + + if (!m_speedrunInfo.m_isPauseIGT) { + m_speedrunInfo.m_igtTimer = elapsedTime - m_speedrunInfo.m_totalLoadTime; + } + + ImGui::Text("IGT"); + ImGui::SameLine(60.0f); + ImGuiStringViewText(GetFormattedTime(m_speedrunInfo.m_igtTimer)); + } + ImGui::End(); + } } diff --git a/src/dusk/imgui/ImGuiMenuGame.hpp b/src/dusk/imgui/ImGuiMenuGame.hpp index fb3f3f10ca..b5ecfa37c0 100644 --- a/src/dusk/imgui/ImGuiMenuGame.hpp +++ b/src/dusk/imgui/ImGuiMenuGame.hpp @@ -8,6 +8,39 @@ #include "imgui.h" namespace dusk { + struct SpeedrunInfo { + void startRun() { + m_isRunStarted = true; + m_startTimestamp = OSGetTime(); + } + + void stopRun() { + m_isRunStarted = false; + m_endTimestamp = OSGetTime() - m_startTimestamp; + } + + void reset() { + m_isRunStarted = false; + m_startTimestamp = 0; + m_endTimestamp = 0; + m_isPauseIGT = false; + m_loadStartTimestamp = 0; + m_totalLoadTime = 0; + m_igtTimer = 0; + } + + bool m_isRunStarted = false; + OSTime m_startTimestamp = 0; + OSTime m_endTimestamp = 0; + + bool m_isPauseIGT = false; + OSTime m_loadStartTimestamp = 0; + OSTime m_totalLoadTime = 0; + OSTime m_igtTimer = 0; + }; + + extern SpeedrunInfo m_speedrunInfo; + class ImGuiMenuGame { public: ImGuiMenuGame(); @@ -15,17 +48,13 @@ namespace dusk { void windowInputViewer(); void windowControllerConfig(); + void drawSpeedrunTimerOverlay(); static void ToggleFullscreen(); - private: - void drawAudioMenu(); - void drawInputMenu(); - void drawGraphicsMenu(); - void drawGameplayMenu(); - void drawCheatsMenu(); - void drawInterfaceMenu(); + static void resetForSpeedrunMode(); + private: struct { int m_selectedPort = 0; bool m_isReading = false; @@ -41,6 +70,8 @@ namespace dusk { bool m_showInputViewerGyro = false; int m_inputOverlayCorner = 3; std::string m_controllerName; + + bool m_showTimerWindow = false; }; } diff --git a/src/dusk/imgui/ImGuiMenuTools.cpp b/src/dusk/imgui/ImGuiMenuTools.cpp index a82067a438..714add87c4 100644 --- a/src/dusk/imgui/ImGuiMenuTools.cpp +++ b/src/dusk/imgui/ImGuiMenuTools.cpp @@ -41,6 +41,10 @@ static void OpenDataFolder() { #define DUSK_CAN_OPEN_DATA_FOLDER 0 #endif +namespace aurora::gx { +extern bool enableLodBias; +} + namespace dusk { ImGuiMenuTools::ImGuiMenuTools() {} @@ -50,15 +54,18 @@ namespace dusk { ImGui::BeginDisabled(); } + ImGui::BeginDisabled(getSettings().game.speedrunMode); + ImGui::MenuItem("Save Editor", hotkeys::SHOW_SAVE_EDITOR, &m_showSaveEditor); ImGui::MenuItem("Map Loader", hotkeys::SHOW_MAP_LOADER, &m_showMapLoader); ImGui::MenuItem("State Share", hotkeys::SHOW_STATE_SHARE, &m_showStateShare); + ImGui::EndDisabled(); + if (!dusk::IsGameLaunched) { ImGui::EndDisabled(); } - ImGui::MenuItem("Achievements", nullptr, &m_showAchievements); #if DUSK_CAN_OPEN_DATA_FOLDER ImGui::Separator(); @@ -71,6 +78,8 @@ namespace dusk { } if (ImGui::BeginMenu("Debug")) { + ImGui::BeginDisabled(getSettings().game.speedrunMode); + bool developmentMode = mDoMain::developmentMode == 1; if (ImGui::Checkbox("Development Mode", &developmentMode)) { mDoMain::developmentMode = developmentMode ? 1 : -1; @@ -85,6 +94,7 @@ namespace dusk { getSettings().game.disableWaterRefraction.setValue(disableWaterRefraction); config::Save(); } + ImGui::Checkbox("Enable LOD Bias", &aurora::gx::enableLodBias); ImGui::EndMenu(); } @@ -119,6 +129,9 @@ namespace dusk { } ImGui::MenuItem("OSReport Force", nullptr, &OSReportReallyForceEnable); + + ImGui::EndDisabled(); + ImGui::EndMenu(); } } @@ -255,11 +268,65 @@ namespace dusk { ImGui::PopFont(); } - void ImGuiMenuTools::ShowAchievements() { - m_achievementsWindow.draw(m_showAchievements); + void ImGuiMenuTools::notifyAchievement(std::string name) { + if (m_notifyTimer <= 0.f) { + m_notifyName = std::move(name); + m_notifyTimer = NOTIFY_DURATION; + } else { + m_notifyQueue.push(std::move(name)); + } } - void ImGuiMenuTools::notifyAchievement(std::string name) { - m_achievementsWindow.notify(std::move(name)); + void ImGuiMenuTools::showAchievementNotification() { + if (!getSettings().game.enableAchievementNotifications.getValue()) { + return; + } + if (m_notifyTimer <= 0.f) { + if (m_notifyQueue.empty()) { + return; + } + m_notifyName = std::move(m_notifyQueue.front()); + m_notifyQueue.pop(); + m_notifyTimer = NOTIFY_DURATION; + } + + m_notifyTimer -= ImGui::GetIO().DeltaTime; + + const float alpha = std::min({ + m_notifyTimer / NOTIFY_FADE_TIME, + (NOTIFY_DURATION - m_notifyTimer) / NOTIFY_FADE_TIME, + 1.0f + }); + + const ImGuiViewport* viewport = ImGui::GetMainViewport(); + const float padding = 12.0f; + ImGui::SetNextWindowPos( + ImVec2(viewport->WorkPos.x + viewport->WorkSize.x - padding, viewport->WorkPos.y + padding), + ImGuiCond_Always, ImVec2(1.0f, 0.0f) + ); + + ImGui::SetNextWindowBgAlpha(alpha * 0.92f); + ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.08f, 0.06f, 0.01f, alpha * 0.92f)); + ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 0.8f, 0.1f, alpha)); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, alpha)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 2.0f); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(14.0f, 10.0f)); + + constexpr ImGuiWindowFlags flags = + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | + ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing | + ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoInputs; + + if (ImGui::Begin("##achievement_notify", nullptr, flags)) { + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.82f, 0.1f, alpha)); + ImGui::TextUnformatted("Achievement Unlocked!"); + ImGui::PopStyleColor(); + ImGui::Spacing(); + ImGui::TextUnformatted(m_notifyName.c_str()); + } + ImGui::End(); + + ImGui::PopStyleVar(2); + ImGui::PopStyleColor(3); } } diff --git a/src/dusk/imgui/ImGuiMenuTools.hpp b/src/dusk/imgui/ImGuiMenuTools.hpp index de94ad2d8f..7ae0e5bec6 100644 --- a/src/dusk/imgui/ImGuiMenuTools.hpp +++ b/src/dusk/imgui/ImGuiMenuTools.hpp @@ -2,10 +2,10 @@ #define DUSK_IMGUI_MENUTOOLS_HPP #include +#include #include #include "imgui.h" -#include "ImGuiAchievements.hpp" #include "ImGuiSaveEditor.hpp" #include "ImGuiStateShare.hpp" @@ -27,8 +27,8 @@ namespace dusk { void ShowAudioDebug(); void ShowSaveEditor(); void ShowStateShare(); - void ShowAchievements(); void notifyAchievement(std::string name); + void showAchievementNotification(); private: bool m_showDebugOverlay = false; @@ -69,8 +69,11 @@ namespace dusk { bool m_showStateShare = false; ImGuiStateShare m_stateShare; - bool m_showAchievements = false; - ImGuiAchievements m_achievementsWindow; + std::string m_notifyName; + float m_notifyTimer = 0.f; + std::queue m_notifyQueue; + static constexpr float NOTIFY_DURATION = 4.0f; + static constexpr float NOTIFY_FADE_TIME = 0.5f; }; } diff --git a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp index 6347e9bc01..a0006f0ad3 100644 --- a/src/dusk/imgui/ImGuiPreLaunchWindow.cpp +++ b/src/dusk/imgui/ImGuiPreLaunchWindow.cpp @@ -75,6 +75,7 @@ void fileDialogCallback(void* userdata, const char* path, const char* error) { } self->m_selectedIsoPath = path; + self->m_isPal = iso::isPal(path); getSettings().backend.isoPath.setValue(self->m_selectedIsoPath); config::Save(); } @@ -92,6 +93,7 @@ bool ImGuiPreLaunchWindow::isSelectedPathValid() const { void ImGuiPreLaunchWindow::draw() { if (m_IsFirstDraw) { m_selectedIsoPath = getSettings().backend.isoPath; + m_isPal = !m_selectedIsoPath.empty() && iso::isPal(m_selectedIsoPath.c_str()); m_initialGraphicsBackend = getSettings().backend.graphicsBackend; m_IsFirstDraw = false; } @@ -193,24 +195,24 @@ void ImGuiPreLaunchWindow::drawOptions() { ImGui::InputText("Game ISO Path", &m_selectedIsoPath, ImGuiInputTextFlags_ReadOnly); ImGui::SameLine(); - if (ImGui::Button("Set")) { + if (ImGui::Button(m_selectedIsoPath == "" ? "Set" : "Change")) { ShowFileSelect(&fileDialogCallback, this, aurora::window::get_sdl_window(), skGameDiscFileFilters.data(), int(skGameDiscFileFilters.size()), nullptr, false); } - // TODO: Only show if PAL disc selected? - // Language selection - auto selectedLanguage = getSettings().game.language.getValue(); - if (ImGui::BeginCombo("Language", skLanguageNames[static_cast(selectedLanguage)])) { - for (u8 i = 0; i < skLanguageNames.size(); ++i) { - if (ImGui::Selectable(skLanguageNames[i])) { - getSettings().game.language.setValue(static_cast(i)); - config::Save(); + if (m_isPal) { + auto selectedLanguage = getSettings().game.language.getValue(); + if (ImGui::BeginCombo("Language", skLanguageNames[static_cast(selectedLanguage)])) { + for (u8 i = 0; i < skLanguageNames.size(); ++i) { + if (ImGui::Selectable(skLanguageNames[i])) { + getSettings().game.language.setValue(static_cast(i)); + config::Save(); + } } - } - ImGui::EndCombo(); + ImGui::EndCombo(); + } } AuroraBackend configuredBackend = BACKEND_AUTO; diff --git a/src/dusk/imgui/ImGuiPreLaunchWindow.hpp b/src/dusk/imgui/ImGuiPreLaunchWindow.hpp index 5d16a6ba0b..6cb078a228 100644 --- a/src/dusk/imgui/ImGuiPreLaunchWindow.hpp +++ b/src/dusk/imgui/ImGuiPreLaunchWindow.hpp @@ -18,5 +18,6 @@ public: std::string m_selectedIsoPath; std::string m_errorString; + bool m_isPal = false; }; } // namespace dusk diff --git a/src/dusk/imgui/ImGuiSaveEditor.cpp b/src/dusk/imgui/ImGuiSaveEditor.cpp index a70df75c34..c31d4e6899 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.cpp +++ b/src/dusk/imgui/ImGuiSaveEditor.cpp @@ -13,6 +13,7 @@ #include "d/actor/d_a_player.h" #include +#include namespace dusk { enum ItemType { @@ -1296,8 +1297,33 @@ namespace dusk { membit.offDungeonItem(flag); } } + + static void genCommonAreaFlags(dSv_memBit_c& membit) { + ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f); - void genMembitFlags(const char* id, dSv_memBit_c& membit) { + genDungeonItemCheckbox(membit, "Got Map", dSv_memBit_c::MAP); + ImGui::SameLine(230.0f); + genDungeonItemCheckbox(membit, "Got Compass", dSv_memBit_c::COMPASS); + + genDungeonItemCheckbox(membit, "Got Boss Key", dSv_memBit_c::BOSS_KEY); + ImGui::SameLine(230.0f); + genDungeonItemCheckbox(membit, "Saw Boss Demo", dSv_memBit_c::STAGE_BOSS_DEMO); + + genDungeonItemCheckbox(membit, "Got Heart Container", dSv_memBit_c::STAGE_LIFE); + ImGui::SameLine(230.0f); + genDungeonItemCheckbox(membit, "Defeated Boss", dSv_memBit_c::STAGE_BOSS_ENEMY); + + genDungeonItemCheckbox(membit, "Defeated Miniboss", dSv_memBit_c::STAGE_BOSS_ENEMY_2); + ImGui::SameLine(230.0f); + genDungeonItemCheckbox(membit, "Got Ooccoo", dSv_memBit_c::OOCCOO_NOTE); + + int keyTemp = membit.getKeyNum(); + if (ImGui::SliderInt("Keys", &keyTemp, 0, 5)) { + membit.setKeyNum(keyTemp); + } + } + + static void genMembitFlags(const char* id, dSv_memBit_c& membit) { ImGuiBeginGroupPanel("Chest", { 100, 100 }); for (int j = 0; j < 2; j++) { drawFlagList(fmt::format("##_tbox{}", j).c_str(), membit.mTbox[j]); @@ -1323,29 +1349,10 @@ namespace dusk { drawFlagList(fmt::format("##_item{}", j).c_str(), membit.mItem[j]); } ImGuiEndGroupPanel(); + ImVec2 post_item_custor = ImGui::GetCursorPos(); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 10.0f); - - genDungeonItemCheckbox(membit, "Got Map", dSv_memBit_c::MAP); - ImGui::SameLine(230.0f); - genDungeonItemCheckbox(membit, "Got Compass", dSv_memBit_c::COMPASS); - - genDungeonItemCheckbox(membit, "Got Boss Key", dSv_memBit_c::BOSS_KEY); - ImGui::SameLine(230.0f); - genDungeonItemCheckbox(membit, "Saw Boss Demo", dSv_memBit_c::STAGE_BOSS_DEMO); - - genDungeonItemCheckbox(membit, "Got Heart Container", dSv_memBit_c::STAGE_LIFE); - ImGui::SameLine(230.0f); - genDungeonItemCheckbox(membit, "Defeated Boss", dSv_memBit_c::STAGE_BOSS_ENEMY); - - genDungeonItemCheckbox(membit, "Defeated Miniboss", dSv_memBit_c::STAGE_BOSS_ENEMY_2); - ImGui::SameLine(230.0f); - genDungeonItemCheckbox(membit, "Got Ooccoo", dSv_memBit_c::OOCCOO_NOTE); - - int keyTemp = membit.getKeyNum(); - if (ImGui::SliderInt("Keys", &keyTemp, 0, 5)) { - membit.setKeyNum(keyTemp); - } + ImGui::SetCursorPos({post_item_custor.x, post_switch_cursor.y}); + // genCommonAreaFlags(membit); } template @@ -1393,74 +1400,326 @@ namespace dusk { } } - void ImGuiSaveEditor::drawFlagsTab() { - if (ImGui::TreeNode("Current Region Flags")) { - dSv_memBit_c& membit = g_dComIfG_gameInfo.info.mMemory.mBit; - genMembitFlags("##TempSceneFlags", membit); + + static void genAreaFlagTable(uint8_t areaIndex, dSv_memBit_c& membit) { + constexpr auto makeMask = [](uint8_t size) -> uint16_t { return (1 << size) - 1; }; + constexpr auto getByteIndexFromFlag = [](uint16_t f) -> uint8_t { return f >> 8; }; + constexpr auto getBitMaskFromFlag = [](uint16_t f) -> uint8_t { return f & 0xff; }; + constexpr auto getValueSize = [getBitMaskFromFlag](uint16_t f) -> uint8_t { + return std::popcount(getBitMaskFromFlag(f)); + }; + + constexpr auto makeEventFlag = [](uint8_t byteIndex, uint8_t bitIndices) -> uint16_t { + return (byteIndex << 8) | bitIndices; + }; - stage_stag_info_class* pstag = dComIfGp_getStageStagInfo(); - if (pstag != nullptr) { - int stageNo = dStage_stagInfo_GetSaveTbl(pstag); - if (ImGui::Button("Save##SaveTempFlags")) { - dComIfGs_putSave(stageNo); + const auto eventFlagToAreaFlag = [&](uint16_t areaFlag) -> int { + auto byteInd = getByteIndexFromFlag(areaFlag); + constexpr size_t areaIndexSize = 5; + // if we're looking at 0x580, that would be byte 5, and check if 0x80 is set on that byte + // the event flags are structured differently than area flags + // B is byte index, b is the flag mask to check + // event flags are BBBBBBBB bbbbbbbb + // for area flags, they check bitIndex, not mask, i is index + // also area uses u32 index, not byte index + // area flags are BBBiiiii + // so we need to convert from bit mask to index + // also our byte index has to become a u32 index + + // dividing byte index by sizeof(u32) gets us the u32 index + // but in big endian, the first byte is the highest order byte of the u32 + // so we skip 24 bytes for the first byte, 16 for the second, etc + // essentially (3 - (x % 4)), reversing the modulus, 0=3, 1=2 + auto bitsToSkip = 8 * ((sizeof(u32) - 1) - (byteInd % sizeof(u32))); + return ((byteInd / sizeof(u32)) << areaIndexSize) | ((std::countr_zero(areaFlag) + bitsToSkip) & makeMask(areaIndexSize)); + }; + + constexpr uint8_t validTbox = sizeof(membit.mTbox); + constexpr uint8_t validSwitch = validTbox + sizeof(membit.mSwitch); + constexpr uint8_t validItem = validSwitch + sizeof(membit.mItem); + constexpr uint16_t tboxConvert = 0; + constexpr uint16_t switchConvert = sizeof(membit.mTbox) << 8; + constexpr uint16_t itemConvert = switchConvert + (sizeof(membit.mItem) << 8); + + const auto LoadFlag = [&](uint16_t flag) -> bool { + const auto byteIndex = getByteIndexFromFlag(flag); + + if (byteIndex < validTbox) { + return membit.isTbox(eventFlagToAreaFlag(flag - tboxConvert)); + } else if (byteIndex < validSwitch) { + return membit.isSwitch(eventFlagToAreaFlag(flag - switchConvert)); + } else if (byteIndex < validItem) { + return membit.isItem(eventFlagToAreaFlag(flag - itemConvert)); + } + return false; + }; + + const auto SetFlag = [&](uint16_t flag, bool set) -> void { + const auto byteIndex = getByteIndexFromFlag(flag); + if (set) { + if (byteIndex < validTbox) { + membit.onTbox(eventFlagToAreaFlag(flag - tboxConvert)); + } else if (byteIndex < validSwitch) { + membit.onSwitch(eventFlagToAreaFlag(flag - switchConvert)); + } else if (byteIndex < validItem) { + membit.onItem(eventFlagToAreaFlag(flag - itemConvert)); } - - ImGui::SameLine(); - - if (ImGui::Button("Load##LoadSaveFlags")) { - dComIfGs_getSave(stageNo); + } else { + if (byteIndex < validTbox) { + membit.offTbox(eventFlagToAreaFlag(flag - tboxConvert)); + } else if (byteIndex < validSwitch) { + membit.offSwitch(eventFlagToAreaFlag(flag - switchConvert)); + } else if (byteIndex < validItem) { + membit.offItem(eventFlagToAreaFlag(flag - itemConvert)); } } + }; + const auto LoadMultiByteFlag = [&](uint16_t flag) -> uint8_t { + const auto bitInds = getBitMaskFromFlag(flag); + const auto byteIndex = getByteIndexFromFlag(flag); + + const uint16_t startingMask = std::bit_floor(bitInds); + uint8_t val = 0; + for (uint16_t bitIndexMask = startingMask; (bitInds & bitIndexMask) != 0; + bitIndexMask >>= 1) + { + val <<= 1; + if (LoadFlag(makeEventFlag(byteIndex, bitInds & bitIndexMask))) { + val |= 1; + } + } + return val; + }; + + const auto SetMultiByteFlag = [&](uint16_t flag, uint8_t val) -> void { + const auto bitInds = getBitMaskFromFlag(flag); + const auto byteIndex = getByteIndexFromFlag(flag); + + const uint16_t startingMask = std::bit_floor(bitInds); + uint16_t valueMask = 1 << (getValueSize(flag) - 1); + + for (uint16_t bitIndexMask = startingMask; (bitInds & bitIndexMask) != 0; + bitIndexMask >>= 1, valueMask >>= 1) + { + SetFlag(makeEventFlag(byteIndex, bitInds & bitIndexMask), (val & valueMask) != 0); + } + }; + + const auto LoadSpreadMultiByte = [&](uint16_t high, uint16_t low) -> uint8_t { + if (low == AREA_FLAG_NONE) + return LoadMultiByteFlag(high); + return (LoadMultiByteFlag(high) << getValueSize(low)) | LoadMultiByteFlag(low); + }; + + const auto SetSpreadMultiByte = [&](uint16_t high, uint16_t low, uint8_t value) -> void { + if (low == AREA_FLAG_NONE) + return SetMultiByteFlag(high, value); + const auto lowerSize = getValueSize(low); + SetMultiByteFlag(high, value >> lowerSize); + SetMultiByteFlag(low, value & makeMask(lowerSize)); + }; + + auto iter = imguiAreaFlagLookup.find(areaIndex); + if (iter == imguiAreaFlagLookup.end()) return; + + auto& areaFlags = iter->second; + + static ImGuiTextFilter filter; + filter.Draw(); // Search bar + + ImVec2 flagTableSize = {700, 400}; + if (ImGui::BeginTable("Area Flags", 3, + ImGuiTableFlags_ScrollY | ImGuiTableFlags_ScrollX | + ImGuiTableFlags_Sortable, + flagTableSize)) + { + ImGui::TableSetupScrollFreeze(0, 1); + constexpr int COLUMN_FLAG = 0, COLUMN_BIT = 1, COLUMN_DESC = 2; + ImGui::TableSetupColumn("Flag"); + ImGui::TableSetupColumn("Byte:Bit"); + ImGui::TableSetupColumn("Description"); + ImGui::TableHeadersRow(); + + // if we're sorting by whether the flag is set or not, + // we want to re-sort whenever a flag updates, which means every frame cuz we don't + // know when it changes. otherwise only re-sort when the sort is dirty + if (auto* sort = ImGui::TableGetSortSpecs(); + sort != nullptr && sort->SpecsCount > 0 && + (sort->SpecsDirty || sort->Specs[0].ColumnIndex == COLUMN_FLAG)) + { + const auto column = sort->Specs[0].ColumnIndex; + const auto direction = sort->Specs[0].SortDirection; + + // if we're sorting by flags, do special sort, regular sort is bad for sorting + // bools it can swap values that are the same, and that causes constant + // reordering + if (column == COLUMN_FLAG) { + if (direction == ImGuiSortDirection_Ascending) { + sortByFlags(std::begin(areaFlags.bitFlags), std::end(areaFlags.bitFlags), + LoadFlag); + } else { + sortByFlags(std::rbegin(areaFlags.bitFlags), std::rend(areaFlags.bitFlags), + LoadFlag); + } + } else { + const auto cmp = [column](const EventAreaFlags& l, + const EventAreaFlags& r) -> bool { + switch (column) { + case COLUMN_DESC: + return l.description < r.description; + case COLUMN_BIT: + return l.flagID < r.flagID; + } + return false; + }; + + if (direction == ImGuiSortDirection_Ascending) { + std::sort(std::begin(areaFlags.bitFlags), std::end(areaFlags.bitFlags), + cmp); + } else { + std::sort(std::rbegin(areaFlags.bitFlags), std::rend(areaFlags.bitFlags), + cmp); + } + } + + sort->SpecsDirty = false; + } + + for (const auto& e : areaFlags.bitFlags) { + std::string formattedBitLocation = + fmt::format("{0:02X}:{1:02X}", e.byteIndex, e.bitIndex); + + if (!filter.PassFilter(e.description.c_str()) && + !filter.PassFilter(formattedBitLocation.c_str())) + { + continue; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + bool flag = LoadFlag(e.flagID); + if (ImGui::Checkbox(fmt::format("##_unused_area_flag_{}", e.flagID).c_str(), &flag)) { + SetFlag(e.flagID, flag); + } + + ImGui::TableNextColumn(); + ImGui::TextUnformatted(formattedBitLocation.c_str()); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(e.description.c_str()); + } + ImGui::EndTable(); + } + + for (const auto& multiByteFlag : areaFlags.multibyteFlags) { + auto flagValue = LoadSpreadMultiByte(multiByteFlag.highOrderflag, multiByteFlag.lowOrderflag); + + const char* currentVal = "UNKNOWN"; + + auto enumValIter = multiByteFlag.enumValues.find(flagValue); + if (enumValIter != multiByteFlag.enumValues.end()) { + currentVal = enumValIter->second; + } + + if (ImGui::BeginCombo(multiByteFlag.name, currentVal)) { + for (const auto& [val, name] : multiByteFlag.enumValues) { + if (ImGui::Selectable(name)) { + SetSpreadMultiByte(multiByteFlag.highOrderflag, multiByteFlag.lowOrderflag, val); + } + } + ImGui::EndCombo(); + } + } + + genCommonAreaFlags(membit); + } + + static void drawCurrentRegionFlags() + { + dSv_memBit_c& membit = g_dComIfG_gameInfo.info.mMemory.mBit; + auto* stageData = dComIfGp_getStageStagInfo(); + if (!stageData) + return; + uint8_t stageIndex = dStage_stagInfo_GetSaveTbl(stageData); + + genAreaFlagTable(stageIndex, membit); + + if (ImGui::TreeNode("Flag Matrix")) { + genMembitFlags("##TempSceneFlags", membit); + ImGui::TreePop(); + } + + stage_stag_info_class* pstag = dComIfGp_getStageStagInfo(); + if (pstag != nullptr) { + int stageNo = dStage_stagInfo_GetSaveTbl(pstag); + if (ImGui::Button("Save##SaveTempFlags")) { + dComIfGs_putSave(stageNo); + } + + ImGui::SameLine(); + + if (ImGui::Button("Load##LoadSaveFlags")) { + dComIfGs_getSave(stageNo); + } + } + } + + void ImGuiSaveEditor::drawFlagsTab() { + if (ImGui::TreeNode("Current Region Flags")) { + drawCurrentRegionFlags(); ImGui::TreePop(); } if (ImGui::TreeNode("Region Saved Flags")) { - static std::array regionNames = { - "Ordon", - "Hyrule Sewers", - "Faron", - "Eldin", - "Lanayru", - "Reserved", - "Hyrule Field", - "Sacred Grove", - "Snowpeak", - "Castle Town", - "Gerudo Desert", - "Fishing Pond", - "Reserved", - "Reserved", - "Reserved", - "Reserved", - "Forest Temple", - "Goron Mines", - "Lakebed Temple", - "Arbiter's Grounds", - "Snowpeak Ruins", - "Temple of Time", - "City in the Sky", - "Palace of Twilight", - "Hyrule Castle", - "Caves", - "Grottos", + static const std::map regionNames = { + { 0x00, "Ordon" }, + { 0x01, "Hyrule Sewers" }, + { 0x02, "Faron" }, + { 0x03, "Eldin" }, + { 0x04, "Lanayru" }, + { 0x06, "Hyrule Field" }, + { 0x07, "Sacred Grove" }, + { 0x08, "Snowpeak" }, + { 0x09, "Castle Town" }, + { 0x0A, "Gerudo Desert" }, + { 0x0B, "Fishing Pond" }, + { 0x10, "Forest Temple" }, + { 0x11, "Goron Mines" }, + { 0x12, "Lakebed Temple" }, + { 0x13, "Arbiter's Grounds" }, + { 0x14, "Snowpeak Ruins" }, + { 0x15, "Temple of Time" }, + { 0x16, "City in the Sky" }, + { 0x17, "Palace of Twilight" }, + { 0x18, "Hyrule Castle" }, + { 0x19, "Caves" }, + { 0x1A, "Lake Hylia Long Cave"}, + { 0x1B, "Grottos" } }; - if (ImGui::BeginCombo("Region", regionNames[m_selectedRegion])) { - for (int i = 0; i < regionNames.size(); i++) { - if (strcmp(regionNames[i], "Reserved") == 0) continue; + if (m_selectedRegion.name == nullptr) + { + const auto& firstRegion = *regionNames.find(0); + m_selectedRegion = { firstRegion.first, firstRegion.second }; + } - if (ImGui::Selectable(regionNames[i])) { - m_selectedRegion = i; + if (ImGui::BeginCombo("Region", m_selectedRegion.name)) { + for (const auto& [id, name] : regionNames) { + if (ImGui::Selectable(name)) { + m_selectedRegion = {id, name}; } } - ImGui::EndCombo(); } - dSv_memBit_c* membit = &dComIfGs_getSaveData()->mSave[m_selectedRegion].mBit; - if (membit != nullptr) { - genMembitFlags("##SaveSceneFlags", *membit); + dSv_memBit_c& membit = dComIfGs_getSaveData()->mSave[m_selectedRegion.id].mBit; + + genAreaFlagTable(m_selectedRegion.id, membit); + + if (ImGui::TreeNode("Flag Matrix")) { + genMembitFlags("##SaveSceneFlags", membit); + + ImGui::TreePop(); } ImGui::TreePop(); @@ -1531,7 +1790,9 @@ namespace dusk { } for (const auto& e : duskImguiEventFlags) { - if (!filter.PassFilter((e.location + "\n" + e.description + "\n" + e.flagName).c_str())) + if (!filter.PassFilter(e.location.c_str()) && + !filter.PassFilter(e.description.c_str()) && + !filter.PassFilter(e.flagName.c_str())) { continue; } diff --git a/src/dusk/imgui/ImGuiSaveEditor.hpp b/src/dusk/imgui/ImGuiSaveEditor.hpp index dc7c122079..a18262862f 100644 --- a/src/dusk/imgui/ImGuiSaveEditor.hpp +++ b/src/dusk/imgui/ImGuiSaveEditor.hpp @@ -21,7 +21,10 @@ namespace dusk { void drawConfigTab(); private: - int m_selectedRegion = 0; + struct { + uint8_t id; + const char* name; + } m_selectedRegion = {0, nullptr}; }; } diff --git a/src/dusk/iso_validate.cpp b/src/dusk/iso_validate.cpp index 0b4a19ae9c..a4e64c5b70 100644 --- a/src/dusk/iso_validate.cpp +++ b/src/dusk/iso_validate.cpp @@ -17,6 +17,11 @@ constexpr const char* TP_GAME_IDS[] = { "RZDK01", // Wii KOR }; +constexpr const char* PAL_GAME_IDS[] = { + "GZ2P01", // GCN PAL + "RZDP01", // Wii PAL +}; + constexpr const char* SUPPORTED_TP_GAME_IDS[] = { "GZ2E01", // GCN USA "GZ2P01", // GCN PAL @@ -96,6 +101,10 @@ ValidationError validate(const char* path) { NodHandleWrapper disc; const auto sdlStream = SDL_IOFromFile(path, "rb"); + if (sdlStream == nullptr) { + return ValidationError::IOError; + } + const NodDiscStream nod_stream { .user_data = sdlStream, .read_at = StreamReadAt, @@ -124,4 +133,30 @@ ValidationError validate(const char* path) { return ValidationError::Success; } +bool isPal(const char* path) { + NodHandleWrapper disc; + + const auto sdlStream = SDL_IOFromFile(path, "rb"); + if (sdlStream == nullptr) { + return false; + } + + const NodDiscStream nod_stream{ + .user_data = sdlStream, + .read_at = StreamReadAt, + .stream_len = StreamLength, + .close = StreamClose, + }; + + if (nod_disc_open_stream(&nod_stream, nullptr, &disc.handle) != NOD_RESULT_OK || disc.handle == nullptr) { + return false; + } + + NodDiscHeader header{}; + if (nod_disc_header(disc.handle, &header) != NOD_RESULT_OK) { + return false; + } + + return matches(header.game_id, PAL_GAME_IDS); +} } // namespace dusk::iso \ No newline at end of file diff --git a/src/dusk/iso_validate.hpp b/src/dusk/iso_validate.hpp index da1ef1f2a6..d961f052cd 100644 --- a/src/dusk/iso_validate.hpp +++ b/src/dusk/iso_validate.hpp @@ -13,6 +13,7 @@ namespace dusk::iso { }; ValidationError validate(const char* path); + bool isPal(const char* path); } #endif // DUSK_ISO_VALIDATE_HPP diff --git a/src/dusk/livesplit.cpp b/src/dusk/livesplit.cpp new file mode 100644 index 0000000000..cec765f223 --- /dev/null +++ b/src/dusk/livesplit.cpp @@ -0,0 +1,183 @@ +#if _WIN32 + #include + #include + using socket_t = SOCKET; + static void closeSocket(socket_t s) { closesocket(s); } +#else + #include + #include + #include + #include + #include + #include + using socket_t = int; + static void closeSocket(socket_t s) { close(s); } + #ifndef INVALID_SOCKET + #define INVALID_SOCKET -1 + #endif +#endif + +#include +#include "dusk/livesplit.h" +#include "f_op/f_op_overlap_mng.h" + +namespace dusk::speedrun { + +static bool running = false; +static uint64_t frameCount = 0; +static socket_t sock = INVALID_SOCKET; +static bool wasLoading = false; +static bool connected = false; +static bool connectPending = false; +static bool disconnectPending = false; + +static void sendCmd(const char* cmd) { + if (sock == INVALID_SOCKET) { + return; + } + + char msg[64]; + int len = snprintf(msg, sizeof(msg), "%s\r\n", cmd); + + if (send(sock, msg, len, 0) >= 0) { + if (!connected) { + connected = connectPending = true; + } + + return; + } + +#if _WIN32 + int err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK || err == WSAENOTCONN) { + return; + } +#else + if (errno == EAGAIN || errno == EWOULDBLOCK || errno == ENOTCONN) { + return; + } +#endif + + if (connected) disconnectPending = true; + closeSocket(sock); + sock = INVALID_SOCKET; + connected = connectPending = false; +} + +uint64_t getFrameCount() { + return frameCount; +} + +void onGameFrame() { + if (!running) { + return; + } + + bool loading = fopOvlpM_IsDoingReq() != 0; + + if (loading != wasLoading) { + sendCmd(loading ? "pausegametime" : "unpausegametime"); + wasLoading = loading; + } + + if (!loading) { + ++frameCount; + } +} + +void start() { + if (running) { + return; + } + + running = true; + frameCount = 0; + wasLoading = false; + sendCmd("initgametime"); + sendCmd("reset"); + sendCmd("starttimer"); +} + +void reset() { + running = false; + frameCount = 0; + wasLoading = false; + sendCmd("reset"); +} + +void connectLiveSplit(const char* host, int port) { +#if _WIN32 + WSADATA wd{}; WSAStartup(MAKEWORD(2, 2), &wd); +#endif + + if (sock != INVALID_SOCKET) { + closeSocket(sock); sock = INVALID_SOCKET; + } + + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + if (sock == INVALID_SOCKET) { + return; + } + +#if _WIN32 + u_long nb = 1; + ioctlsocket(sock, FIONBIO, &nb); +#else + fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK); +#endif + + sockaddr_in addr{}; addr.sin_family = AF_INET; + addr.sin_port = htons((uint16_t)port); + inet_pton(AF_INET, host, &addr.sin_addr); + connect(sock, (sockaddr*)&addr, sizeof(addr)); + sendCmd("initgametime"); +} + +void disconnectLiveSplit() { + if (sock != INVALID_SOCKET) { + closeSocket(sock); + sock = INVALID_SOCKET; + connected = false; + } +} + +bool consumeConnectedEvent() { bool v = connectPending; connectPending = false; return v; } +bool consumeDisconnectedEvent() { bool v = disconnectPending; disconnectPending = false; return v; } + +void updateLiveSplit() { + if (sock == INVALID_SOCKET) { + return; + } + + if (!connected) { + sendCmd("initgametime"); + return; + } + + if (!running) { + return; + } + + const uint64_t totalMs = frameCount * 1000 / 30; + const uint64_t totalSec = totalMs / 1000; + char cmd[32]; + + snprintf(cmd, sizeof(cmd), "setgametime %u:%02u:%02u.%03u", + (uint32_t)(totalSec / 3600), + (uint32_t)((totalSec / 60) % 60), + (uint32_t)(totalSec % 60), + (uint32_t)(totalMs % 1000) + ); + + sendCmd(cmd); +} + +void shutdown() { + disconnectLiveSplit(); +#if _WIN32 + WSACleanup(); +#endif +} + +} diff --git a/src/dusk/logging.cpp b/src/dusk/logging.cpp index 172059aea4..8b96fcffd5 100644 --- a/src/dusk/logging.cpp +++ b/src/dusk/logging.cpp @@ -1,5 +1,6 @@ #include "dusk/logging.h" #include +#include #include #include #include @@ -32,9 +33,33 @@ static constexpr std::string_view StubFragments[] = { }; namespace { -std::mutex g_logMutex; -FILE* g_logFile = nullptr; -std::string g_logFilePath; +// On macOS, std::mutex becomes poisoned when its dtor is run. +// We use this to check if the LogState is destroyed before attempting to acquire it. +std::atomic g_logStateAlive(true); + +struct LogState { + std::mutex mutex; + FILE* file = nullptr; + std::string filePath; + + ~LogState() { + CloseFile(); + g_logStateAlive.store(false, std::memory_order_release); + } + + void CloseFile() { + if (!g_logStateAlive.load(std::memory_order_acquire)) { + return; + } + std::lock_guard lock(mutex); + if (file != nullptr) { + std::fflush(file); + std::fclose(file); + file = nullptr; + } + } +}; +LogState g_logState; const char* LogLevelString(AuroraLogLevel level) { switch (level) { @@ -152,10 +177,10 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* m FILE* out = LogStreamForLevel(level); WriteLogLine(out, levelStr, module, message, len); - { - std::lock_guard lock(g_logMutex); - if (g_logFile != nullptr) { - WriteLogLine(g_logFile, levelStr, module, message, len); + if (g_logStateAlive.load(std::memory_order_acquire)) { + std::lock_guard lock(g_logState.mutex); + if (g_logState.file != nullptr) { + WriteLogLine(g_logState.file, levelStr, module, message, len); } } @@ -169,8 +194,11 @@ void aurora_log_callback(AuroraLogLevel level, const char* module, const char* m aurora::Module DuskLog("dusk"); void dusk::InitializeFileLogging(const std::filesystem::path& configDir, AuroraLogLevel logLevel) { - std::lock_guard lock(g_logMutex); - if (g_logFile != nullptr || configDir.empty()) { + if (!g_logStateAlive.load(std::memory_order_acquire)) { + return; + } + std::lock_guard lock(g_logState.mutex); + if (g_logState.file != nullptr || configDir.empty()) { return; } @@ -184,31 +212,30 @@ void dusk::InitializeFileLogging(const std::filesystem::path& configDir, AuroraL } const std::filesystem::path logPath = logsDir / MakeTimestampedLogName(); - g_logFile = std::fopen(logPath.string().c_str(), "wb"); - if (g_logFile == nullptr) { + g_logState.file = std::fopen(logPath.string().c_str(), "wb"); + if (g_logState.file == nullptr) { std::fprintf(stderr, "[WARNING | dusk] Failed to open log file '%s'\n", logPath.string().c_str()); return; } - g_logFilePath = logPath.string(); + g_logState.filePath = logPath.string(); aurora::g_config.logCallback = &aurora_log_callback; aurora::g_config.logLevel = logLevel; - WriteLogLine(g_logFile, "INFO", "dusk", "File logging initialized", 24); + WriteLogLine(g_logState.file, "INFO", "dusk", "File logging initialized", 24); } void dusk::ShutdownFileLogging() { - std::lock_guard lock(g_logMutex); - if (g_logFile == nullptr) { + if (!g_logStateAlive.load(std::memory_order_acquire)) { return; } - - std::fflush(g_logFile); - std::fclose(g_logFile); - g_logFile = nullptr; + g_logState.CloseFile(); } const char* dusk::GetLogFilePath() { - std::lock_guard lock(g_logMutex); - return g_logFilePath.empty() ? nullptr : g_logFilePath.c_str(); + if (!g_logStateAlive.load(std::memory_order_acquire)) { + return nullptr; + } + std::lock_guard lock(g_logState.mutex); + return g_logState.filePath.empty() ? nullptr : g_logState.filePath.c_str(); } diff --git a/src/dusk/settings.cpp b/src/dusk/settings.cpp index eb1d513ba9..9114bfbee3 100644 --- a/src/dusk/settings.cpp +++ b/src/dusk/settings.cpp @@ -17,6 +17,8 @@ UserSettings g_userSettings = { .soundEffectsVolume {"audio.soundEffectsVolume", 100}, .fanfareVolume {"audio.fanfareVolume", 100}, .enableReverb {"audio.enableReverb", true}, + .enableHrtf {"audio.enableHrtf", false}, + .menuSounds {"audio.menuSounds", true}, }, .game = { @@ -24,37 +26,40 @@ UserSettings g_userSettings = { // Quality of Life .enableQuickTransform {"game.enableQuickTransform", false}, - .hideTvSettingsScreen {"game.hideTvSettingsScreen", false}, - .skipWarningScreen {"game.skipWarningScreen", false}, + .hideTvSettingsScreen {"game.hideTvSettingsScreen", true}, + .skipWarningScreen {"game.skipWarningScreen", true}, .biggerWallets {"game.biggerWallets", false}, .noReturnRupees {"game.noReturnRupees", false}, .disableRupeeCutscenes {"game.disableRupeeCutscenes", false}, .noSwordRecoil {"game.noSwordRecoil", false}, .damageMultiplier {"game.damageMultiplier", 1}, - .noHeartDrops{"game.noHeartDrops", false}, + .noHeartDrops {"game.noHeartDrops", false}, .instantDeath {"game.instantDeath", false}, .fastClimbing {"game.fastClimbing", false}, .noMissClimbing {"game.noMissClimbing", false}, .fastTears {"game.fastTears", false}, + .no2ndFishForCat {"game.no2ndFishForCat", false}, .instantSaves {"game.instantSaves", false}, .instantText {"game.instantText", false}, .sunsSong {"game.sunsSong", false}, + .autoSave {"game.autoSave", false}, // Preferences .enableMirrorMode {"game.enableMirrorMode", false}, .disableMainHUD {"game.disableMainHUD", false}, .pauseOnFocusLost {"game.pauseOnFocusLost", false}, - .enableLinkDollRotation = {"game.enableLinkDollRotation", false }, - .enableAchievementNotifications {"game.enableAchievementNotifications", false}, + .enableLinkDollRotation = {"game.enableLinkDollRotation", false}, + .enableAchievementNotifications {"game.enableAchievementNotifications", true}, // Graphics - .bloomMode {"game.bloomMode", BloomMode::Classic}, + .bloomMode {"game.bloomMode", BloomMode::Dusk}, .bloomMultiplier {"game.bloomMultiplier", 1.0f}, .disableWaterRefraction {"game.disableWaterRefraction", false}, - .enableFrameInterpolation = {"game.enableFrameInterpolation", false}, + .enableFrameInterpolation {"game.enableFrameInterpolation", false}, .internalResolutionScale {"game.internalResolutionScale", 0}, .shadowResolutionMultiplier {"game.shadowResolutionMultiplier", 1}, .enableDepthOfField {"game.enableDepthOfField", true}, + .enableMapBackground {"game.enableMapBackground", true}, // Audio .noLowHpSound {"game.noLowHpSound", false}, @@ -74,6 +79,7 @@ UserSettings g_userSettings = { .invertCameraXAxis {"game.invertCameraXAxis", false}, .invertCameraYAxis {"game.invertCameraYAxis", false}, .freeCameraSensitivity {"game.freeCameraSensitivity", 1.0f}, + .debugFlyCam {"game.debugFlyCam", false}, // Cheats .infiniteHearts {"game.infiniteHearts", false}, @@ -95,7 +101,11 @@ UserSettings g_userSettings = { .restoreWiiGlitches {"game.restoreWiiGlitches", false}, // Controls - .enableTurboKeybind {"game.enableTurboKeybind", false} + .enableTurboKeybind {"game.enableTurboKeybind", false}, + + // Tools + .speedrunMode {"game.speedrunMode", false}, + .liveSplitEnabled {"game.liveSplitEnabled", false} }, .backend = { @@ -105,7 +115,6 @@ UserSettings g_userSettings = { .showPipelineCompilation {"backend.showPipelineCompilation", false}, .wasPresetChosen {"backend.wasPresetChosen", false}, .enableCrashReporting {"backend.enableCrashReporting", true}, - .duskMenuOpen {"backend.duskMenuOpen", false}, .cardFileType {"backend.cardFileType", static_cast(CARD_GCIFOLDER)} } }; @@ -127,6 +136,8 @@ void registerSettings() { Register(g_userSettings.audio.soundEffectsVolume); Register(g_userSettings.audio.fanfareVolume); Register(g_userSettings.audio.enableReverb); + Register(g_userSettings.audio.enableHrtf); + Register(g_userSettings.audio.menuSounds); // Game Register(g_userSettings.game.language); @@ -142,9 +153,11 @@ void registerSettings() { Register(g_userSettings.game.instantDeath); Register(g_userSettings.game.fastClimbing); Register(g_userSettings.game.fastTears); + Register(g_userSettings.game.no2ndFishForCat); Register(g_userSettings.game.instantSaves); Register(g_userSettings.game.instantText); Register(g_userSettings.game.sunsSong); + Register(g_userSettings.game.autoSave); Register(g_userSettings.game.enableMirrorMode); Register(g_userSettings.game.invertCameraXAxis); Register(g_userSettings.game.invertCameraYAxis); @@ -157,6 +170,7 @@ void registerSettings() { Register(g_userSettings.game.internalResolutionScale); Register(g_userSettings.game.shadowResolutionMultiplier); Register(g_userSettings.game.enableDepthOfField); + Register(g_userSettings.game.enableMapBackground); Register(g_userSettings.game.enableFastIronBoots); Register(g_userSettings.game.canTransformAnywhere); Register(g_userSettings.game.freeMagicArmor); @@ -167,6 +181,8 @@ void registerSettings() { Register(g_userSettings.game.noLowHpSound); Register(g_userSettings.game.midnasLamentNonStop); Register(g_userSettings.game.enableTurboKeybind); + Register(g_userSettings.game.speedrunMode); + Register(g_userSettings.game.liveSplitEnabled); Register(g_userSettings.game.fastSpinner); Register(g_userSettings.game.infiniteHearts); Register(g_userSettings.game.infiniteArrows); @@ -189,6 +205,7 @@ void registerSettings() { Register(g_userSettings.game.gyroInvertPitch); Register(g_userSettings.game.gyroInvertYaw); Register(g_userSettings.game.freeCamera); + Register(g_userSettings.game.debugFlyCam); Register(g_userSettings.backend.isoPath); Register(g_userSettings.backend.graphicsBackend); @@ -196,7 +213,6 @@ void registerSettings() { Register(g_userSettings.backend.showPipelineCompilation); Register(g_userSettings.backend.wasPresetChosen); Register(g_userSettings.backend.enableCrashReporting); - Register(g_userSettings.backend.duskMenuOpen); Register(g_userSettings.backend.cardFileType); } diff --git a/src/dusk/ui/achievements.cpp b/src/dusk/ui/achievements.cpp new file mode 100644 index 0000000000..93993bb380 --- /dev/null +++ b/src/dusk/ui/achievements.cpp @@ -0,0 +1,208 @@ +#include "achievements.hpp" + +#include "Z2AudioLib/Z2SeMgr.h" +#include "dusk/achievements.h" +#include "fmt/format.h" +#include "m_Do/m_Do_audio.h" +#include "nav_types.hpp" +#include "pane.hpp" + +namespace dusk::ui { +namespace { + +struct CategoryInfo { + AchievementCategory cat; + const char* label; +}; + +constexpr CategoryInfo kCategories[] = { + {AchievementCategory::Story, "Story"}, + {AchievementCategory::Collection, "Collection"}, + {AchievementCategory::Challenge, "Challenge"}, + {AchievementCategory::Minigame, "Minigame"}, + {AchievementCategory::Misc, "Misc"}, + {AchievementCategory::Glitched, "Glitched"}, +}; + +Rml::String build_achievement_info_rml(const Achievement& a) { + Rml::String s = fmt::format( + R"(
)" + R"({})" + R"({})" + R"(
)" + R"(

{}

)", + a.unlocked ? " unlocked" : "", + a.name, + a.unlocked ? " unlocked" : " locked", + a.unlocked ? "Unlocked" : "Locked", + a.description + ); + + if (a.isCounter) { + float fraction = a.goal > 0 ? float(a.progress) / float(a.goal) : 1.0f; + s += fmt::format( + R"()" + R"({} / {})", + fraction, + a.unlocked ? "progress-done" : "progress-ongoing", + a.progress, + a.goal + ); + } + + return s; +} + +class AchievementRow : public FluentComponent { +public: + AchievementRow(Rml::Element* parent, const Achievement& a) + : FluentComponent(createRowRoot(parent)) + { + auto& btn = add_child