diff --git a/data/dynos.c.h b/data/dynos.c.h index 37db3eb10..09d23576b 100644 --- a/data/dynos.c.h +++ b/data/dynos.c.h @@ -10,9 +10,6 @@ void dynos_update_opt (void *pad); s32 dynos_gfx_import_texture (void **output, void *ptr, s32 tile, void *grapi, void **hashmap, void *pool, s32 *poolpos, s32 poolsize); void dynos_gfx_swap_animations(void *ptr); -#ifdef COOP -bool dynos_warp_to_level(s32 aLevel, s32 aArea, s32 aAct); -#endif - #endif #endif + \ No newline at end of file diff --git a/data/dynos.cpp.h b/data/dynos.cpp.h index 7a58d02f1..22d079a72 100644 --- a/data/dynos.cpp.h +++ b/data/dynos.cpp.h @@ -616,7 +616,6 @@ void DynOS_ReturnToMainMenu(); // Opt // -#ifndef COOP s32 DynOS_Opt_GetValue(const String &aName); void DynOS_Opt_SetValue(const String &aName, s32 aValue); void DynOS_Opt_AddAction(const String &aFuncName, bool (*aFuncPtr)(const char *), bool aOverwrite); @@ -629,7 +628,6 @@ void DynOS_Opt_LoadConfig(DynosOption *aMenu); void DynOS_Opt_SaveConfig(DynosOption *aMenu); void DynOS_Opt_DrawMenu(DynosOption *aCurrentOption, DynosOption *aCurrentMenu, DynosOption *aOptionsMenu, DynosOption *aDynosMenu); void DynOS_Opt_DrawPrompt(DynosOption *aCurrentMenu, DynosOption *aOptionsMenu, DynosOption *aDynosMenu); -#endif // // Gfx diff --git a/data/dynos_c.cpp b/data/dynos_c.cpp index a7feba1c2..d60093894 100644 --- a/data/dynos_c.cpp +++ b/data/dynos_c.cpp @@ -21,10 +21,4 @@ void dynos_gfx_swap_animations(void *ptr) { return DynOS_Gfx_SwapAnimations(ptr); } -#ifdef COOP -bool dynos_warp_to_level(s32 aLevel, s32 aArea, s32 aAct) { - return DynOS_Warp_ToLevel(aLevel, aArea, aAct); -} -#endif - } diff --git a/data/dynos_coop.c.h b/data/dynos_coop.c.h new file mode 100644 index 000000000..0f5c569cb --- /dev/null +++ b/data/dynos_coop.c.h @@ -0,0 +1,10 @@ +#ifdef COOP +#ifndef DYNOS_COOP_C_H +#define DYNOS_COOP_C_H +#ifndef __cplusplus + +bool dynos_warp_to_level(s32 aLevel, s32 aArea, s32 aAct); + +#endif +#endif +#endif diff --git a/data/dynos_coop_c.cpp b/data/dynos_coop_c.cpp new file mode 100644 index 000000000..5efd80554 --- /dev/null +++ b/data/dynos_coop_c.cpp @@ -0,0 +1,10 @@ +#ifdef COOP +#include "dynos.cpp.h" +extern "C" { + +bool dynos_warp_to_level(s32 aLevel, s32 aArea, s32 aAct) { + return DynOS_Warp_ToLevel(aLevel, aArea, aAct); +} + +} +#endif diff --git a/data/dynos_gfx_update.cpp b/data/dynos_gfx_update.cpp index 7e237dcdb..ae9c1bee8 100644 --- a/data/dynos_gfx_update.cpp +++ b/data/dynos_gfx_update.cpp @@ -107,6 +107,7 @@ void DynOS_Gfx_Update() { const Array &pDynosPacks = DynOS_Gfx_GetPacks(); for (s32 i = 0; i != pDynosPacks.Count(); ++i) { #ifdef COOP + // TODO: needs to be adjusted from djui _Enabled.Add(true); #else _Enabled.Add(DynOS_Opt_GetValue(String("dynos_pack_%d", i))); diff --git a/data/dynos_main.cpp b/data/dynos_main.cpp index 9bb903796..0d7da17c9 100644 --- a/data/dynos_main.cpp +++ b/data/dynos_main.cpp @@ -3,9 +3,7 @@ extern "C" { #include "sm64.h" #include "level_commands.h" #include "game/level_update.h" -#ifndef COOP #include "game/options_menu.h" -#endif #include "game/object_list_processor.h" extern s16 gMenuMode; extern s8 gDialogBoxState; @@ -19,9 +17,7 @@ extern void omm_opt_init(); // void DynOS_ReturnToMainMenu() { -#ifndef COOP optmenu_toggle(); -#endif level_set_transition(0, NULL); gDialogBoxState = 0; gMenuMode = -1; @@ -33,18 +29,10 @@ void DynOS_ReturnToMainMenu() { // DYNOS_AT_STARTUP void DynOS_Init() { - -#ifdef COOP - Array _Packs = DynOS_Gfx_Init(); - if (_Packs.Count() == 0) { - return; - } -#else #ifdef OMM_DEFINES_H omm_opt_init(); #endif DynOS_Opt_Init(); -#endif } // @@ -57,9 +45,7 @@ void DynOS_UpdateOpt(void *aPad) { DynOS_Warp_SetParam(gCurrLevelNum, -1); sDynosIsLevelEntry = false; } -#ifndef COOP DynOS_Opt_Update((OSContPad *) aPad); -#endif gPrevFrameObjectCount = 0; } diff --git a/data/dynos_opt.cpp b/data/dynos_opt.cpp new file mode 100644 index 000000000..364dc1bd7 --- /dev/null +++ b/data/dynos_opt.cpp @@ -0,0 +1,748 @@ +#include "dynos.cpp.h" +extern "C" { +#include "pc/configfile.h" +#include "audio/external.h" +#include "game/game_init.h" +#include "pc/controller/controller_keyboard.h" +#ifdef BETTERCAMERA +#include "game/bettercamera.h" +#endif +} + +// +// Data +// + +static DynosOption *sPrevOpt = NULL; +static DynosOption *sDynosMenu = NULL; +static DynosOption *sOptionsMenu = NULL; +static DynosOption *sCurrentMenu = NULL; +static DynosOption *sCurrentOpt = NULL; +extern s32 sBindingState; + +// +// Action list +// + +typedef bool (*DynosActionFunction)(const char *); +struct DynosAction : NoCopy { + String mFuncName; + DynosActionFunction mAction; +}; + +STATIC_STORAGE(Array, DynosActions); +#define sDynosActions __DynosActions() + +static DynosActionFunction DynOS_Opt_GetAction(const String& aFuncName) { + for (auto &_DynosAction : sDynosActions) { + if (_DynosAction->mFuncName == aFuncName) { + return _DynosAction->mAction; + } + } + return NULL; +} + +void DynOS_Opt_AddAction(const String& aFuncName, bool (*aFuncPtr)(const char *), bool aOverwrite) { + for (auto &_DynosAction : sDynosActions) { + if (_DynosAction->mFuncName == aFuncName) { + if (aOverwrite) { + _DynosAction->mAction = aFuncPtr; + } + return; + } + } + DynosAction *_DynosAction = New(); + _DynosAction->mFuncName = aFuncName; + _DynosAction->mAction = aFuncPtr; + sDynosActions.Add(_DynosAction); +} + +// +// Constructors +// + +static DynosOption *DynOS_Opt_GetExistingOption(DynosOption *aOpt, const String &aName) { + while (aOpt) { + if (aOpt->mName == aName) { + return aOpt; + } + if (aOpt->mType == DOPT_SUBMENU) { + DynosOption *_Opt = DynOS_Opt_GetExistingOption(aOpt->mSubMenu.mChild, aName); + if (_Opt) { + return _Opt; + } + } + aOpt = aOpt->mNext; + } + return NULL; +} + +static DynosOption *DynOS_Opt_NewOption(const String &aName, const String &aConfigName, const String &aLabel, const String &aTitle) { + + // Check if the option already exists + static DynosOption sDummyOpt; + if (DynOS_Opt_GetExistingOption(sDynosMenu, aName)) { + return &sDummyOpt; + } + + // Create a new option + DynosOption *_Opt = New(); + _Opt->mName = aName; + _Opt->mConfigName = aConfigName; + _Opt->mLabel = { aLabel, NULL }; + _Opt->mTitle = { aTitle, NULL }; + _Opt->mDynos = true; + if (sPrevOpt == NULL) { // The very first option + _Opt->mPrev = NULL; + _Opt->mNext = NULL; + _Opt->mParent = NULL; + sDynosMenu = _Opt; + } else { + if (sPrevOpt->mType == DOPT_SUBMENU && sPrevOpt->mSubMenu.mEmpty) { // First option of a sub-menu + _Opt->mPrev = NULL; + _Opt->mNext = NULL; + _Opt->mParent = sPrevOpt; + sPrevOpt->mSubMenu.mChild = _Opt; + sPrevOpt->mSubMenu.mEmpty = false; + } else { + _Opt->mPrev = sPrevOpt; + _Opt->mNext = NULL; + _Opt->mParent = sPrevOpt->mParent; + sPrevOpt->mNext = _Opt; + } + } + sPrevOpt = _Opt; + return _Opt; +} + +static void DynOS_Opt_EndSubMenu() { + if (sPrevOpt && sPrevOpt->mParent) { + if (sPrevOpt->mType == DOPT_SUBMENU && sPrevOpt->mSubMenu.mEmpty) { // ENDMENU command following a SUBMENU command + sPrevOpt->mSubMenu.mEmpty = false; + } else { + sPrevOpt = sPrevOpt->mParent; + } + } +} + +static void DynOS_Opt_CreateSubMenu(const String &aName, const String &aLabel, const String &aTitle) { + DynosOption *_Opt = DynOS_Opt_NewOption(aName, "", aLabel, aTitle); + _Opt->mType = DOPT_SUBMENU; + _Opt->mSubMenu.mChild = NULL; + _Opt->mSubMenu.mEmpty = true; +} + +static void DynOS_Opt_CreateToggle(const String &aName, const String &aConfigName, const String &aLabel, s32 aValue) { + DynosOption *_Opt = DynOS_Opt_NewOption(aName, aConfigName, aLabel, aLabel); + _Opt->mType = DOPT_TOGGLE; + _Opt->mToggle.mTog = New(); + *_Opt->mToggle.mTog = (bool) aValue; +} + +static void DynOS_Opt_CreateScroll(const String &aName, const String &aConfigName, const String &aLabel, s32 aMin, s32 aMax, s32 aStep, s32 aValue) { + DynosOption *_Opt = DynOS_Opt_NewOption(aName, aConfigName, aLabel, aLabel); + _Opt->mType = DOPT_SCROLL; + _Opt->mScroll.mMin = aMin; + _Opt->mScroll.mMax = aMax; + _Opt->mScroll.mStep = aStep; + _Opt->mScroll.mValue = New(); + *_Opt->mScroll.mValue = aValue; +} + +static void DynOS_Opt_CreateChoice(const String &aName, const String &aConfigName, const String &aLabel, const Array& aChoices, s32 aValue) { + DynosOption *_Opt = DynOS_Opt_NewOption(aName, aConfigName, aLabel, aLabel); + _Opt->mType = DOPT_CHOICE; + _Opt->mChoice.mIndex = New(); + *_Opt->mChoice.mIndex = aValue; + for (const auto &_Choice : aChoices) { + _Opt->mChoice.mChoices.Add({ _Choice, NULL }); + } +} + +static void DynOS_Opt_CreateButton(const String &aName, const String &aLabel, const String& aFuncName) { + DynosOption *_Opt = DynOS_Opt_NewOption(aName, "", aLabel, aLabel); + _Opt->mType = DOPT_BUTTON; + _Opt->mButton.mFuncName = aFuncName; +} + +static void DynOS_Opt_CreateBind(const String &aName, const String &aConfigName, const String &aLabel, u32 aMask, u32 aBind0, u32 aBind1, u32 aBind2) { + DynosOption *_Opt = DynOS_Opt_NewOption(aName, aConfigName, aLabel, aLabel); + _Opt->mType = DOPT_BIND; + _Opt->mBind.mMask = aMask; + _Opt->mBind.mBinds = New(3); + _Opt->mBind.mBinds[0] = aBind0; + _Opt->mBind.mBinds[1] = aBind1; + _Opt->mBind.mBinds[2] = aBind2; + _Opt->mBind.mIndex = 0; +} + +// +// Loop through DynosOptions +// + +DynosOption *DynOS_Opt_Loop(DynosOption *aOpt, DynosLoopFunc aFunc, void *aData) { + while (aOpt) { + if (aFunc(aOpt, aData)) { + return aOpt; + } else if (aOpt->mType == DOPT_SUBMENU) { + DynosOption *_Opt = DynOS_Opt_Loop(aOpt->mSubMenu.mChild, aFunc, aData); + if (_Opt) { + return _Opt; + } + } + aOpt = aOpt->mNext; + } + return NULL; +} + +// +// Get/Set values +// + +static bool DynOS_Opt_Get(DynosOption *aOpt, void *aData) { + return aOpt->mName == (const char *) aData; +} + +s32 DynOS_Opt_GetValue(const String &aName) { + DynosOption *_Opt = DynOS_Opt_Loop(sDynosMenu, DynOS_Opt_Get, (void *) aName.begin()); + if (_Opt) { + switch (_Opt->mType) { + case DOPT_TOGGLE: return *_Opt->mToggle.mTog; + case DOPT_CHOICE: return *_Opt->mChoice.mIndex; + case DOPT_CHOICELEVEL: return *_Opt->mChoice.mIndex; + case DOPT_CHOICEAREA: return *_Opt->mChoice.mIndex; + case DOPT_CHOICESTAR: return *_Opt->mChoice.mIndex; + case DOPT_CHOICEPARAM: return *_Opt->mChoice.mIndex; + case DOPT_SCROLL: return *_Opt->mScroll.mValue; + default: break; + } + } + return 0; +} + +void DynOS_Opt_SetValue(const String &aName, s32 aValue) { + DynosOption *_Opt = DynOS_Opt_Loop(sDynosMenu, DynOS_Opt_Get, (void *) aName.begin()); + if (_Opt) { + switch (_Opt->mType) { + case DOPT_TOGGLE: *_Opt->mToggle.mTog = aValue; break; + case DOPT_CHOICE: *_Opt->mChoice.mIndex = aValue; break; + case DOPT_CHOICELEVEL: *_Opt->mChoice.mIndex = aValue; break; + case DOPT_CHOICEAREA: *_Opt->mChoice.mIndex = aValue; break; + case DOPT_CHOICESTAR: *_Opt->mChoice.mIndex = aValue; break; + case DOPT_CHOICEPARAM: *_Opt->mChoice.mIndex = aValue; break; + case DOPT_SCROLL: *_Opt->mScroll.mValue = aValue; break; + default: break; + } + } +} + +// +// Processing +// + +#define SOUND_DYNOS_SAVED (SOUND_MENU_MARIO_CASTLE_WARP2 | (0xFF << 8)) +#define SOUND_DYNOS_SELECT (SOUND_MENU_CHANGE_SELECT | (0xF8 << 8)) +#define SOUND_DYNOS_OK (SOUND_MENU_CHANGE_SELECT | (0xF8 << 8)) +#define SOUND_DYNOS_CANCEL (SOUND_MENU_CAMERA_BUZZ | (0xFC << 8)) + +enum { + INPUT_LEFT, + INPUT_RIGHT, + INPUT_A, + INPUT_Z +}; + +enum { + RESULT_NONE, + RESULT_OK, + RESULT_CANCEL +}; + +static s32 DynOS_Opt_ProcessInput(DynosOption *aOpt, s32 input) { + switch (aOpt->mType) { + case DOPT_TOGGLE: + if (input == INPUT_LEFT) { + *aOpt->mToggle.mTog = false; + return RESULT_OK; + } + if (input == INPUT_RIGHT) { + *aOpt->mToggle.mTog = true; + return RESULT_OK; + } + if (input == INPUT_A) { + *aOpt->mToggle.mTog = !(*aOpt->mToggle.mTog); + return RESULT_OK; + } + break; + + case DOPT_CHOICE: + if (input == INPUT_LEFT) { + *aOpt->mChoice.mIndex = (*aOpt->mChoice.mIndex + aOpt->mChoice.mChoices.Count() - 1) % (aOpt->mChoice.mChoices.Count()); + return RESULT_OK; + } + if (input == INPUT_RIGHT || input == INPUT_A) { + *aOpt->mChoice.mIndex = (*aOpt->mChoice.mIndex + 1) % (aOpt->mChoice.mChoices.Count()); + return RESULT_OK; + } + break; + + case DOPT_CHOICELEVEL: + if (input == INPUT_LEFT) { + *aOpt->mChoice.mIndex = (*aOpt->mChoice.mIndex + DynOS_Level_GetCount() - 1) % (DynOS_Level_GetCount()); + return RESULT_OK; + } + if (input == INPUT_RIGHT || input == INPUT_A) { + *aOpt->mChoice.mIndex = (*aOpt->mChoice.mIndex + 1) % (DynOS_Level_GetCount()); + return RESULT_OK; + } + break; + + case DOPT_CHOICEAREA: + if (input == INPUT_LEFT) { + *aOpt->mChoice.mIndex = (*aOpt->mChoice.mIndex + 3) % (4); + return RESULT_OK; + } + if (input == INPUT_RIGHT || input == INPUT_A) { + *aOpt->mChoice.mIndex = (*aOpt->mChoice.mIndex + 1) % (4); + return RESULT_OK; + } + break; + + case DOPT_CHOICESTAR: + if (input == INPUT_LEFT) { + *aOpt->mChoice.mIndex = (*aOpt->mChoice.mIndex + 5) % (6); + return RESULT_OK; + } + if (input == INPUT_RIGHT || input == INPUT_A) { + *aOpt->mChoice.mIndex = (*aOpt->mChoice.mIndex + 1) % (6); + return RESULT_OK; + } + break; + + case DOPT_CHOICEPARAM: + if (input == INPUT_LEFT) { + *aOpt->mChoice.mIndex = (*aOpt->mChoice.mIndex + 4) % (5); + return RESULT_OK; + } + if (input == INPUT_RIGHT || input == INPUT_A) { + *aOpt->mChoice.mIndex = (*aOpt->mChoice.mIndex + 1) % (5); + return RESULT_OK; + } + break; + + case DOPT_SCROLL: + if (input == INPUT_LEFT) { + *aOpt->mScroll.mValue = MAX(aOpt->mScroll.mMin, *aOpt->mScroll.mValue - aOpt->mScroll.mStep * (gPlayer1Controller->buttonDown & A_BUTTON ? 5 : 1)); + return RESULT_OK; + } + if (input == INPUT_RIGHT) { + *aOpt->mScroll.mValue = MIN(aOpt->mScroll.mMax, *aOpt->mScroll.mValue + aOpt->mScroll.mStep * (gPlayer1Controller->buttonDown & A_BUTTON ? 5 : 1)); + return RESULT_OK; + } + break; + + case DOPT_BIND: + if (input == INPUT_LEFT) { + aOpt->mBind.mIndex = MAX(0, aOpt->mBind.mIndex - 1); + return RESULT_OK; + } + if (input == INPUT_RIGHT) { + aOpt->mBind.mIndex = MIN(2, aOpt->mBind.mIndex + 1); + return RESULT_OK; + } + if (input == INPUT_Z) { + aOpt->mBind.mBinds[aOpt->mBind.mIndex] = VK_INVALID; + return RESULT_OK; + } + if (input == INPUT_A) { + aOpt->mBind.mBinds[aOpt->mBind.mIndex] = VK_INVALID; + sBindingState = 1; + controller_get_raw_key(); + return RESULT_OK; + } + break; + + case DOPT_BUTTON: + if (input == INPUT_A) { + DynosActionFunction _Action = DynOS_Opt_GetAction(aOpt->mButton.mFuncName); + if (_Action != NULL && _Action(aOpt->mName.begin())) { + return RESULT_OK; + } + return RESULT_CANCEL; + } + break; + + case DOPT_SUBMENU: + if (input == INPUT_A) { + if (aOpt->mSubMenu.mChild != NULL) { + sCurrentOpt = aOpt->mSubMenu.mChild; + return RESULT_OK; + } + return RESULT_CANCEL; + } + break; + } + return RESULT_NONE; +} + +static void DynOS_Opt_Open(DynosOption *aMenu) { + play_sound(SOUND_DYNOS_SELECT, gDefaultSoundArgs); + sCurrentMenu = aMenu; + sCurrentOpt = aMenu; +} + +static void DynOS_Opt_Close(bool aPlaySavedSfx) { + if (sCurrentMenu != NULL) { + if (aPlaySavedSfx) { + play_sound(SOUND_DYNOS_SAVED, gDefaultSoundArgs); + } +#ifdef BETTERCAMERA + newcam_init_settings(); +#endif + controller_reconfigure(); + configfile_save(configfile_name()); + DynOS_Opt_SaveConfig(sDynosMenu); + sCurrentMenu = NULL; + } +} + +static void DynOS_Opt_ProcessInputs() { + static s32 sStickTimer = 0; + static bool sPrevStick = 0; + + // Stick values + f32 _StickX = gPlayer1Controller->stickX; + f32 _StickY = gPlayer1Controller->stickY; + if (absx(_StickX) > 60 || absx(_StickY) > 60) { + if (sStickTimer == 0) { + sStickTimer = (sPrevStick ? 2 : 9); + } else { + _StickX = 0; + _StickY = 0; + sStickTimer--; + } + sPrevStick = true; + } else { + sStickTimer = 0; + sPrevStick = false; + } + + // Key binding + if (sBindingState != 0) { + u32 _Key = (sCurrentOpt->mDynos ? (u32) DynOS_Opt_ControllerGetKeyPressed() : controller_get_raw_key()); + if (_Key != VK_INVALID) { + play_sound(SOUND_DYNOS_SELECT, gDefaultSoundArgs); + sCurrentOpt->mBind.mBinds[sCurrentOpt->mBind.mIndex] = _Key; + sBindingState = false; + } + return; + } + + if (sCurrentMenu != NULL) { + + // Up + if (_StickY > +60) { + if (sCurrentOpt->mPrev != NULL) { + sCurrentOpt = sCurrentOpt->mPrev; + } else { + while (sCurrentOpt->mNext) sCurrentOpt = sCurrentOpt->mNext; + } + play_sound(SOUND_DYNOS_SELECT, gDefaultSoundArgs); + return; + } + + // Down + if (_StickY < -60) { + if (sCurrentOpt->mNext != NULL) { + sCurrentOpt = sCurrentOpt->mNext; + } else { + while (sCurrentOpt->mPrev) sCurrentOpt = sCurrentOpt->mPrev; + } + play_sound(SOUND_DYNOS_SELECT, gDefaultSoundArgs); + return; + } + + // Left + if (_StickX < -60) { + switch (DynOS_Opt_ProcessInput(sCurrentOpt, INPUT_LEFT)) { + case RESULT_OK: play_sound(SOUND_DYNOS_OK, gDefaultSoundArgs); break; + case RESULT_CANCEL: play_sound(SOUND_DYNOS_CANCEL, gDefaultSoundArgs); break; + case RESULT_NONE: break; + } + return; + } + + // Right + if (_StickX > +60) { + switch (DynOS_Opt_ProcessInput(sCurrentOpt, INPUT_RIGHT)) { + case RESULT_OK: play_sound(SOUND_DYNOS_OK, gDefaultSoundArgs); break; + case RESULT_CANCEL: play_sound(SOUND_DYNOS_CANCEL, gDefaultSoundArgs); break; + case RESULT_NONE: break; + } + return; + } + + // A + if (gPlayer1Controller->buttonPressed & A_BUTTON) { + switch (DynOS_Opt_ProcessInput(sCurrentOpt, INPUT_A)) { + case RESULT_OK: play_sound(SOUND_DYNOS_OK, gDefaultSoundArgs); break; + case RESULT_CANCEL: play_sound(SOUND_DYNOS_CANCEL, gDefaultSoundArgs); break; + case RESULT_NONE: break; + } + return; + } + + // B + if (gPlayer1Controller->buttonPressed & B_BUTTON) { + if (sCurrentOpt->mParent != NULL) { + sCurrentOpt = sCurrentOpt->mParent; + play_sound(SOUND_DYNOS_SELECT, gDefaultSoundArgs); + } else { + DynOS_Opt_Close(true); + } + return; + } + + // Z + if (gPlayer1Controller->buttonPressed & Z_TRIG) { + switch (DynOS_Opt_ProcessInput(sCurrentOpt, INPUT_Z)) { + case RESULT_OK: play_sound(SOUND_DYNOS_OK, gDefaultSoundArgs); break; + case RESULT_CANCEL: play_sound(SOUND_DYNOS_CANCEL, gDefaultSoundArgs); break; + case RESULT_NONE: + if (sCurrentMenu == sDynosMenu) { + DynOS_Opt_Close(true); + } else { + DynOS_Opt_Open(sDynosMenu); + } break; + } + return; + } + + // R + if (gPlayer1Controller->buttonPressed & R_TRIG) { + if (sCurrentMenu == sOptionsMenu) { + DynOS_Opt_Close(true); + } else { + DynOS_Opt_Open(sOptionsMenu); + } + return; + } + + // Start + if (gPlayer1Controller->buttonPressed & START_BUTTON) { + DynOS_Opt_Close(true); + return; + } + } else if (gPlayer1Controller->buttonPressed & R_TRIG) { + DynOS_Opt_Open(sOptionsMenu); + } else if (gPlayer1Controller->buttonPressed & Z_TRIG) { + DynOS_Opt_Open(sDynosMenu); + } +} + +// +// Init +// + +static void DynOS_Opt_CreateWarpToLevelSubMenu() { + DynOS_Opt_CreateSubMenu("dynos_warp_to_level_submenu", "Warp to Level", "WARP TO LEUEL"); + + // Level select + { + DynosOption *aOpt = DynOS_Opt_NewOption("dynos_warp_level", "", "Level Select", ""); + aOpt->mType = DOPT_CHOICELEVEL; + aOpt->mChoice.mIndex = New(); + *aOpt->mChoice.mIndex = 0; + } + + // Area select + { + DynosOption *aOpt = DynOS_Opt_NewOption("dynos_warp_area", "", "Area Select", ""); + aOpt->mType = DOPT_CHOICEAREA; + aOpt->mChoice.mIndex = New(); + *aOpt->mChoice.mIndex = 0; + } + + // Star select + { + DynosOption *aOpt = DynOS_Opt_NewOption("dynos_warp_act", "", "Star Select", ""); + aOpt->mType = DOPT_CHOICESTAR; + aOpt->mChoice.mIndex = New(); + *aOpt->mChoice.mIndex = 0; + } + + // Param select + { + DynosOption *aOpt = DynOS_Opt_NewOption("dynos_warp_param", "", "Param Select", ""); + aOpt->mType = DOPT_CHOICEPARAM; + aOpt->mChoice.mIndex = New(); + *aOpt->mChoice.mIndex = 0; + } + + DynOS_Opt_CreateButton("dynos_warp_to_level", "Warp", "DynOS_Opt_WarpToLevel"); + DynOS_Opt_EndSubMenu(); +} + +static void DynOS_Opt_CreateWarpToCastleSubMenu() { + DynOS_Opt_CreateSubMenu("dynos_warp_to_castle_submenu", "Warp to Castle", "WARP TO CASTLE"); + + // Level select + { + DynosOption *aOpt = DynOS_Opt_NewOption("dynos_warp_castle", "", "Level Exit", ""); + aOpt->mType = DOPT_CHOICELEVEL; + aOpt->mChoice.mIndex = New(); + *aOpt->mChoice.mIndex = 0; + } + + DynOS_Opt_CreateButton("dynos_warp_to_castle", "Warp", "DynOS_Opt_WarpToCastle"); + DynOS_Opt_EndSubMenu(); +} + +static u32 DynOS_Opt_GetHash(const String& aStr) { + u32 _Hash = 5381u; + for (char c : aStr) { _Hash += c + (_Hash << 5); } + return _Hash; +} + +static void DynOS_Opt_CreateModelPacksSubMenu() { + Array _Packs = DynOS_Gfx_Init(); + if (_Packs.Count() == 0) { + return; + } + + DynOS_Opt_CreateSubMenu("dynos_model_loader_submenu", "Model Packs", "MODEL PACKS"); + for (s32 i = 0; i != _Packs.Count(); ++i) { + DynOS_Opt_CreateToggle(String("dynos_pack_%d", i), String("dynos_pack_%08X", DynOS_Opt_GetHash(_Packs[i])), _Packs[i], false); + } + DynOS_Opt_CreateButton("dynos_packs_disable_all", "Disable all packs", "DynOS_Opt_DisableAllPacks"); + DynOS_Opt_EndSubMenu(); +} + +void DynOS_Opt_Init() { + +#ifdef COOP + DynOS_Gfx_Init(); +#else + // Convert options menu + DynOS_Opt_InitVanilla(sOptionsMenu); + + // Warp to level + DynOS_Opt_CreateWarpToLevelSubMenu(); + + // Warp to castle + DynOS_Opt_CreateWarpToCastleSubMenu(); + + // Restart level + DynOS_Opt_CreateButton("dynos_restart_level", "Restart Level", "DynOS_Opt_RestartLevel"); + + // Exit level + DynOS_Opt_CreateButton("dynos_exit_level", "Exit Level", "DynOS_Opt_ExitLevel"); + + // Return to main menu + DynOS_Opt_CreateButton("dynos_return_to_main_menu", "Return to Main Menu", "DynOS_Opt_ReturnToMainMenu"); + + // Model loader + DynOS_Opt_CreateModelPacksSubMenu(); +#endif + + // Init config + DynOS_Opt_LoadConfig(sDynosMenu); +} + +// +// Update +// + +void DynOS_Opt_Update(OSContPad *aPad) { + DynOS_Opt_Loop(sDynosMenu, DynOS_Opt_ControllerUpdate, (void *) aPad); + if (DynOS_IsTransitionActive()) { + aPad->button = 0; + aPad->stick_x = 0; + aPad->stick_y = 0; + aPad->ext_stick_x = 0; + aPad->ext_stick_y = 0; + } +} + +// +// Hijack +// This is C code +// + +extern "C" { + +u8 optmenu_open = 0; + +void optmenu_toggle(void) { + DynOS_Opt_Close(false); + optmenu_open = 0; +} + +void optmenu_draw(void) { + DynOS_Opt_DrawMenu(sCurrentOpt, sCurrentMenu, sOptionsMenu, sDynosMenu); +} + +void optmenu_draw_prompt(void) { + DynOS_Opt_DrawPrompt(sCurrentMenu, sOptionsMenu, sDynosMenu); + DynOS_Opt_ProcessInputs(); + optmenu_open = (sCurrentMenu != NULL); +} + +void optmenu_check_buttons(void) { +} + +} + +// +// Built-in options +// + +#define DYNOS_DEFINE_ACTION(func) \ +DYNOS_AT_STARTUP static void DynOS_Opt_AddAction_##func() { \ + DynOS_Opt_AddAction(#func, func, false); \ +} + +#ifndef COOP + +static bool DynOS_Opt_ReturnToMainMenu(UNUSED const char *optName) { + DynOS_ReturnToMainMenu(); + return true; +} +DYNOS_DEFINE_ACTION(DynOS_Opt_ReturnToMainMenu); + +static bool DynOS_Opt_WarpToLevel(UNUSED const char *optName) { + s32 _Level = DynOS_Level_GetList()[DynOS_Opt_GetValue("dynos_warp_level")]; + s32 _Area = DynOS_Opt_GetValue("dynos_warp_area") + 1; + s32 _Act = DynOS_Opt_GetValue("dynos_warp_act") + 1; + return DynOS_Warp_ToLevel(_Level, _Area, _Act); +} +DYNOS_DEFINE_ACTION(DynOS_Opt_WarpToLevel); + +static bool DynOS_Opt_WarpToCastle(UNUSED const char *optName) { + s32 _Level = DynOS_Level_GetList()[DynOS_Opt_GetValue("dynos_warp_castle")]; + return DynOS_Warp_ToCastle(_Level); +} +DYNOS_DEFINE_ACTION(DynOS_Opt_WarpToCastle); + +static bool DynOS_Opt_RestartLevel(UNUSED const char *optName) { + return DynOS_Warp_RestartLevel(); +} +DYNOS_DEFINE_ACTION(DynOS_Opt_RestartLevel); + +static bool DynOS_Opt_ExitLevel(UNUSED const char *optName) { + return DynOS_Warp_ExitLevel(30); +} +DYNOS_DEFINE_ACTION(DynOS_Opt_ExitLevel); + +static bool DynOS_Opt_DisableAllPacks(UNUSED const char *optName) { + const Array &pDynosPacks = DynOS_Gfx_GetPacks(); + for (s32 i = 0; i != pDynosPacks.Count(); ++i) { + DynOS_Opt_SetValue(String("dynos_pack_%d", i), false); + } + return true; +} +DYNOS_DEFINE_ACTION(DynOS_Opt_DisableAllPacks); + +#endif + +#undef DYNOS_DEFINE_ACTION diff --git a/data/dynos_opt_config.cpp b/data/dynos_opt_config.cpp new file mode 100644 index 000000000..a3cc02d8b --- /dev/null +++ b/data/dynos_opt_config.cpp @@ -0,0 +1,62 @@ +#include "dynos.cpp.h" + +extern DynosOption *DynOS_Opt_Loop(DynosOption *aOpt, DynosLoopFunc aFunc, void *aData); + +static bool DynOS_Opt_ReadConfig(DynosOption *aOpt, void *aData) { + return (aOpt->mConfigName == (const char *) aData); +} + +void DynOS_Opt_LoadConfig(DynosOption *aMenu) { + SysPath _Filename = fstring("%s/%s", DYNOS_USER_FOLDER, DYNOS_CONFIG_FILENAME); + FILE *_File = fopen(_Filename.c_str(), "r"); + if (_File) { + char _Buffer[1024]; + while (fgets(_Buffer, 1024, _File)) { + + // Option strings + char *_NameBegin = _Buffer; + char *_DataBegin = strchr(_NameBegin, '='); + if (_NameBegin && _DataBegin) { + *(_DataBegin++) = 0; + + // Option name + String _OptName = String(_NameBegin); + DynosOption *_Opt = DynOS_Opt_Loop(aMenu, DynOS_Opt_ReadConfig, (void *) _OptName.begin()); + if (_Opt) { + + // Option values + switch (_Opt->mType) { + case DOPT_TOGGLE: sscanf(_DataBegin, "%hhu\n", &_Opt->mToggle.mTog[0]); break; + case DOPT_CHOICE: sscanf(_DataBegin, "%d\n", &_Opt->mChoice.mIndex[0]); break; + case DOPT_SCROLL: sscanf(_DataBegin, "%d\n", &_Opt->mScroll.mValue[0]); break; + case DOPT_BIND: sscanf(_DataBegin, "%04X;%04X;%04X\n", &_Opt->mBind.mBinds[0], &_Opt->mBind.mBinds[1], &_Opt->mBind.mBinds[2]); break; + } + } + } + } + fclose(_File); + } +} + +static bool DynOS_Opt_WriteConfig(DynosOption *aOpt, void *aData) { + if (aOpt->mConfigName.Length() != 0 && + aOpt->mConfigName != "null" && + aOpt->mConfigName != "NULL") { + switch (aOpt->mType) { + case DOPT_TOGGLE: fprintf((FILE *) aData, "%s=%hhu\n", aOpt->mConfigName.begin(), aOpt->mToggle.mTog[0]); break; + case DOPT_CHOICE: fprintf((FILE *) aData, "%s=%d\n", aOpt->mConfigName.begin(), aOpt->mChoice.mIndex[0]); break; + case DOPT_SCROLL: fprintf((FILE *) aData, "%s=%d\n", aOpt->mConfigName.begin(), aOpt->mScroll.mValue[0]); break; + case DOPT_BIND: fprintf((FILE *) aData, "%s=%04X;%04X;%04X\n", aOpt->mConfigName.begin(), aOpt->mBind.mBinds[0], aOpt->mBind.mBinds[1], aOpt->mBind.mBinds[2]); break; + } + } + return false; +} + +void DynOS_Opt_SaveConfig(DynosOption *aMenu) { + SysPath _Filename = fstring("%s/%s", DYNOS_USER_FOLDER, DYNOS_CONFIG_FILENAME); + FILE *_File = fopen(_Filename.c_str(), "w"); + if (_File) { + DynOS_Opt_Loop(aMenu, DynOS_Opt_WriteConfig, (void *) _File); + fclose(_File); + } +} diff --git a/data/dynos_opt_cont.cpp b/data/dynos_opt_cont.cpp new file mode 100644 index 000000000..f88f57dc0 --- /dev/null +++ b/data/dynos_opt_cont.cpp @@ -0,0 +1,70 @@ +#include "dynos.cpp.h" +extern "C" { +#include "pc/controller/controller_api.h" +} + +static bool DynOS_Opt_ControllerIsKeyDown(s32 aCont, s32 aKey) { + + // Keyboard + if (aCont == 0 && aKey >= 0 && aKey < SDL_NUM_SCANCODES) { + return SDL_GetKeyboardState(NULL)[aKey]; + } + + // Game Controller + else if (aKey >= 0x1000) { + + // Button + s32 _Button = (aKey - 0x1000); + if (_Button < SDL_CONTROLLER_BUTTON_MAX) { + return SDL_GameControllerGetButton(SDL_GameControllerOpen(aCont - 1), SDL_GameControllerButton(_Button)); + } + + // Axis + s32 _Axis = (aKey - 0x1000 - SDL_CONTROLLER_BUTTON_MAX); + if (_Axis < SDL_CONTROLLER_AXIS_MAX * 2) { + s32 _AxisValue = SDL_GameControllerGetAxis(SDL_GameControllerOpen(aCont - 1), SDL_GameControllerAxis(_Axis / 2)); + if (_Axis & 1) return (_AxisValue < (SHRT_MIN / 2)); + else return (_AxisValue > (SHRT_MAX / 2)); + } + } + // Invalid + return false; +} + +#define MAX_CONTS 8 +bool DynOS_Opt_ControllerUpdate(DynosOption *aOpt, void *aData) { + if (aOpt->mType == DOPT_BIND) { + OSContPad *pad = (OSContPad *) aData; + for (s32 _Cont = 0; _Cont < MAX_CONTS; ++_Cont) + for (s32 _Bind = 0; _Bind < 3; ++_Bind) { + pad->button |= aOpt->mBind.mMask * DynOS_Opt_ControllerIsKeyDown(_Cont, aOpt->mBind.mBinds[_Bind]); + } + } + return false; +} + +#define MAX_GKEYS (SDL_CONTROLLER_BUTTON_MAX + SDL_CONTROLLER_AXIS_MAX * 2) +s32 sBindingState = 0; // 0 = No bind, 1 = Wait for all keys released, 2 = Return first pressed key +s32 DynOS_Opt_ControllerGetKeyPressed() { + + // Keyboard + for (s32 _Key = 0; _Key < SDL_NUM_SCANCODES; ++_Key) { + if (DynOS_Opt_ControllerIsKeyDown(0, _Key)) { + if (sBindingState == 1) return VK_INVALID; + return _Key; + } + } + + // Game Controller + for (s32 _Cont = 1; _Cont < MAX_CONTS; ++_Cont) + for (s32 _Key = 0; _Key < MAX_GKEYS; ++_Key) { + if (DynOS_Opt_ControllerIsKeyDown(_Cont, _Key + 0x1000)) { + if (sBindingState == 1) return VK_INVALID; + return _Key + 0x1000; + } + } + + // No key + sBindingState = 2; + return VK_INVALID; +} diff --git a/data/dynos_opt_render.cpp b/data/dynos_opt_render.cpp new file mode 100644 index 000000000..d182e5e32 --- /dev/null +++ b/data/dynos_opt_render.cpp @@ -0,0 +1,309 @@ +#include "dynos.cpp.h" +extern "C" { +#include "course_table.h" +#include "game/game_init.h" +#include "game/ingame_menu.h" +#include "game/segment2.h" +#include "pc/controller/controller_api.h" +#include "gfx_dimensions.h" +} + +extern s32 sBindingState; + +#define DYNOS_TEXT_DYNOS_MENU { "DYNOS MENU", NULL } +#define DYNOS_TEXT_A { "([A]) >", NULL } +#define DYNOS_TEXT_OPEN_LEFT { "[Z] DynOS", NULL } +#define DYNOS_TEXT_CLOSE_LEFT { "[Z] Return", NULL } +#define DYNOS_TEXT_OPTIONS_MENU { "OPTIONS", NULL } +#define DYNOS_TEXT_DISABLED { "Disabled", NULL } +#define DYNOS_TEXT_ENABLED { "Enabled", NULL } +#define DYNOS_TEXT_NONE { "NONE", NULL } +#define DYNOS_TEXT_DOT_DOT_DOT { "...", NULL } +#define DYNOS_TEXT_OPEN_RIGHT { "[R] Options", NULL } +#define DYNOS_TEXT_CLOSE_RIGHT { "[R] Return", NULL } + +static void RenderString(const u8 *aStr64, s32 aX, s32 aY) { + create_dl_translation_matrix(MENU_MTX_PUSH, aX, aY, 0); + for (; *aStr64 != DIALOG_CHAR_TERMINATOR; ++aStr64) { + if (*aStr64 != DIALOG_CHAR_SPACE) { + void **fontLUT = (void **) segmented_to_virtual(main_font_lut); + void *packedTexture = segmented_to_virtual(fontLUT[*aStr64]); + gDPPipeSync(gDisplayListHead++); + gDPSetTextureImage(gDisplayListHead++, G_IM_FMT_IA, G_IM_SIZ_16b, 1, VIRTUAL_TO_PHYSICAL(packedTexture)); + gSPDisplayList(gDisplayListHead++, dl_ia_text_tex_settings); + } + create_dl_translation_matrix(MENU_MTX_NOPUSH, DynOS_String_WidthChar64(*aStr64), 0, 0); + } + gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); +} + +static void PrintString(const Label& aLabel, s32 aX, s32 aY, u32 aFrontColorRGBA, u32 aBackColorRGBA, bool aAlignLeft) { + const u8 *_Str64 = (aLabel.second ? aLabel.second : DynOS_String_Convert(aLabel.first.begin(), false)); + + gSPDisplayList(gDisplayListHead++, dl_ia_text_begin); + if ((aBackColorRGBA & 0xFF) != 0) { + gDPSetEnvColor(gDisplayListHead++, ((aBackColorRGBA >> 24) & 0xFF), ((aBackColorRGBA >> 16) & 0xFF), ((aBackColorRGBA >> 8) & 0xFF), ((aBackColorRGBA >> 0) & 0xFF)); + if (aAlignLeft) { + RenderString(_Str64, GFX_DIMENSIONS_FROM_LEFT_EDGE(aX) + 1, aY - 1); + } else { + RenderString(_Str64, GFX_DIMENSIONS_FROM_RIGHT_EDGE(aX + DynOS_String_Width(_Str64) - 1), aY - 1); + } + } + if ((aFrontColorRGBA & 0xFF) != 0) { + gDPSetEnvColor(gDisplayListHead++, ((aFrontColorRGBA >> 24) & 0xFF), ((aFrontColorRGBA >> 16) & 0xFF), ((aFrontColorRGBA >> 8) & 0xFF), ((aFrontColorRGBA >> 0) & 0xFF)); + if (aAlignLeft) { + RenderString(_Str64, GFX_DIMENSIONS_FROM_LEFT_EDGE(aX), aY); + } else { + RenderString(_Str64, GFX_DIMENSIONS_FROM_RIGHT_EDGE(aX + DynOS_String_Width(_Str64)), aY); + } + } + gSPDisplayList(gDisplayListHead++, dl_ia_text_end); + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); +} + +static void PrintBox(s32 aX, s32 aY, s32 aWidth, s32 aHeight, u32 aColorRGBA, bool aAlignLeft) { + if ((aColorRGBA && 0xFF) != 0) { + Mtx *_Matrix = (Mtx *) alloc_display_list(sizeof(Mtx)); + if (!_Matrix) return; + if (aAlignLeft) { + create_dl_translation_matrix(MENU_MTX_PUSH, GFX_DIMENSIONS_FROM_LEFT_EDGE(aX), aY + aHeight, 0); + } else { + create_dl_translation_matrix(MENU_MTX_PUSH, GFX_DIMENSIONS_FROM_RIGHT_EDGE(aX + aWidth), aY + aHeight, 0); + } + guScale(_Matrix, (f32) aWidth / 130.f, (f32) aHeight / 80.f, 1.f); + gSPMatrix(gDisplayListHead++, VIRTUAL_TO_PHYSICAL(_Matrix), G_MTX_MODELVIEW | G_MTX_MUL | G_MTX_NOPUSH); + gDPSetEnvColor(gDisplayListHead++, ((aColorRGBA >> 24) & 0xFF), ((aColorRGBA >> 16) & 0xFF), ((aColorRGBA >> 8) & 0xFF), ((aColorRGBA >> 0) & 0xFF)); + gSPDisplayList(gDisplayListHead++, dl_draw_text_bg_box); + gSPPopMatrix(gDisplayListHead++, G_MTX_MODELVIEW); + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); + } +} + +static const char *IntToString(const char *fmt, s32 x) { + static char sBuffer[16]; + snprintf(sBuffer, 16, fmt, x); + return sBuffer; +} + +#define get_label(opt) (opt->mLabel) +#define get_title(opt) (opt->mTitle) +#define get_choice(opt) (opt->mChoice.mChoices[*opt->mChoice.mIndex]) +#define get_dec_number(n) { "", DynOS_String_Convert(IntToString("%d", n), false) } +#define get_hex_number(n) { "", DynOS_String_Convert(IntToString("%04X", n), false) } +#define get_level(opt) { "", DynOS_Level_GetName(DynOS_Level_GetList()[*opt->mChoice.mIndex], true, true) } +#define get_star(opt) { "", DynOS_Level_GetActName(DynOS_Level_GetList()[DynOS_Opt_GetValue("dynos_warp_level")], *opt->mChoice.mIndex + 1, true, true) } +#define get_param(opt) { DynOS_Warp_GetParamName(DynOS_Level_GetList()[DynOS_Opt_GetValue("dynos_warp_level")], *opt->mChoice.mIndex), NULL } + +static s32 GetCurrentOptionCount(DynosOption *aCurrentOpt) { + s32 _Count = 0; + while (aCurrentOpt->mPrev) { aCurrentOpt = aCurrentOpt->mPrev; } + while (aCurrentOpt) { aCurrentOpt = aCurrentOpt->mNext; _Count++; } + return _Count; +} + +static s32 GetCurrentOptionIndex(DynosOption *aCurrentOpt) { + s32 _Index = 0; + while (aCurrentOpt->mPrev) { aCurrentOpt = aCurrentOpt->mPrev; _Index++; } + return _Index; +} + +#define PREV(opt) (opt == NULL ? NULL : opt->mPrev) +#define NEXT(opt) (opt == NULL ? NULL : opt->mNext) +static DynosOption **GetCurrentOptions(DynosOption *aCurrentOpt) { + static DynosOption *sOptionList[13]; + + sOptionList[6] = aCurrentOpt; + sOptionList[5] = PREV(sOptionList[6]); + sOptionList[4] = PREV(sOptionList[5]); + sOptionList[3] = PREV(sOptionList[4]); + sOptionList[2] = PREV(sOptionList[3]); + sOptionList[1] = PREV(sOptionList[2]); + sOptionList[0] = PREV(sOptionList[1]); + sOptionList[7] = NEXT(sOptionList[6]); + sOptionList[8] = NEXT(sOptionList[7]); + sOptionList[9] = NEXT(sOptionList[8]); + sOptionList[10] = NEXT(sOptionList[9]); + sOptionList[11] = NEXT(sOptionList[10]); + sOptionList[12] = NEXT(sOptionList[11]); + + s32 _StartIndex = 12, _EndIndex = 0; + for (s32 i = 0; i != 13; ++i) { + if (sOptionList[i] != NULL) { + _StartIndex = MIN(_StartIndex, i); + _EndIndex = MAX(_EndIndex, i); + } + } + + if (_EndIndex - _StartIndex < 7) { + return &sOptionList[_StartIndex]; + } + if (_EndIndex <= 9) { + return &sOptionList[_EndIndex - 6]; + } + if (_StartIndex >= 3) { + return &sOptionList[_StartIndex]; + } + return &sOptionList[3]; +} +#undef PREV +#undef NEXT + +#define COLOR_WHITE 0xFFFFFFFF +#define COLOR_BLACK 0x000000FF +#define COLOR_GRAY 0xA0A0A0FF +#define COLOR_DARK_GRAY 0x808080FF +#define COLOR_SELECT 0x80E0FFFF +#define COLOR_SELECT_BOX 0x00FFFF20 +#define COLOR_ENABLED 0x20E020FF +#define COLOR_DISABLED 0xFF2020FF +#define OFFSET_FROM_LEFT_EDGE (20.f * sqr(GFX_DIMENSIONS_ASPECT_RATIO)) +#define OFFSET_FROM_RIGHT_EDGE (20.f * sqr(GFX_DIMENSIONS_ASPECT_RATIO)) +#define SCROLL_BAR_SIZE ((s32) (45.f * GFX_DIMENSIONS_ASPECT_RATIO)) + +static void DynOS_Opt_DrawOption(DynosOption *aOpt, DynosOption *aCurrentOpt, s32 aY) { + if (aOpt == NULL) { + return; + } + + // Selected box + if (aOpt == aCurrentOpt) { + u8 _Alpha = (u8) ((coss(gGlobalTimer * 0x800) + 1.f) * 0x20); + PrintBox(OFFSET_FROM_LEFT_EDGE - 4, aY - 2, GFX_DIMENSIONS_FROM_RIGHT_EDGE(OFFSET_FROM_RIGHT_EDGE) - GFX_DIMENSIONS_FROM_LEFT_EDGE(OFFSET_FROM_LEFT_EDGE) + 8, 20, COLOR_SELECT_BOX + _Alpha, 1); + } + + // Label + if (aOpt == aCurrentOpt) { + PrintString(get_label(aOpt), OFFSET_FROM_LEFT_EDGE, aY, COLOR_SELECT, COLOR_BLACK, 1); + } else { + PrintString(get_label(aOpt), OFFSET_FROM_LEFT_EDGE, aY, COLOR_WHITE, COLOR_BLACK, 1); + } + + // Values + switch (aOpt->mType) { + case DOPT_TOGGLE: { + if (*aOpt->mToggle.mTog) { + PrintString(DYNOS_TEXT_ENABLED, OFFSET_FROM_RIGHT_EDGE, aY, COLOR_ENABLED, COLOR_BLACK, 0); + } else { + PrintString(DYNOS_TEXT_DISABLED, OFFSET_FROM_RIGHT_EDGE, aY, COLOR_DISABLED, COLOR_BLACK, 0); + } + } break; + + case DOPT_CHOICE: { + PrintString(get_choice(aOpt), OFFSET_FROM_RIGHT_EDGE, aY, aOpt == aCurrentOpt ? COLOR_SELECT : COLOR_WHITE, COLOR_BLACK, 0); + } break; + + case DOPT_CHOICELEVEL: { + PrintString(get_level(aOpt), OFFSET_FROM_RIGHT_EDGE, aY, aOpt == aCurrentOpt ? COLOR_SELECT : COLOR_WHITE, COLOR_BLACK, 0); + } break; + + case DOPT_CHOICEAREA: { + s32 _Level = DynOS_Level_GetList()[DynOS_Opt_GetValue("dynos_warp_level")]; + s32 _Area = *aOpt->mChoice.mIndex + 1; + const u8 *_Name = DynOS_Level_GetAreaName(_Level, _Area, true); + if (DynOS_Level_GetWarpEntry(_Level, _Area)) { + PrintString({ "", _Name }, OFFSET_FROM_RIGHT_EDGE, aY, aOpt == aCurrentOpt ? COLOR_SELECT : COLOR_WHITE, COLOR_BLACK, 0); + } else { + PrintString({ "", _Name }, OFFSET_FROM_RIGHT_EDGE, aY, COLOR_GRAY, COLOR_BLACK, 0); + } + } break; + + case DOPT_CHOICESTAR: { + s32 _Course = DynOS_Level_GetCourse(DynOS_Level_GetList()[DynOS_Opt_GetValue("dynos_warp_level")]); + if (_Course >= COURSE_MIN && _Course <= COURSE_STAGES_MAX) { + PrintString(get_star(aOpt), OFFSET_FROM_RIGHT_EDGE, aY, aOpt == aCurrentOpt ? COLOR_SELECT : COLOR_WHITE, COLOR_BLACK, 0); + } + } break; + + case DOPT_CHOICEPARAM: { + PrintString(get_param(aOpt), OFFSET_FROM_RIGHT_EDGE, aY, aOpt == aCurrentOpt ? COLOR_SELECT : COLOR_WHITE, COLOR_BLACK, 0); + } break; + + case DOPT_SCROLL: { + s32 _Width = (s32) (SCROLL_BAR_SIZE * (f32) (*aOpt->mScroll.mValue - aOpt->mScroll.mMin) / (f32) (aOpt->mScroll.mMax - aOpt->mScroll.mMin)); + PrintString(get_dec_number(*aOpt->mScroll.mValue), OFFSET_FROM_RIGHT_EDGE, aY, aOpt == aCurrentOpt ? COLOR_SELECT : COLOR_WHITE, COLOR_BLACK, 0); + PrintBox(OFFSET_FROM_RIGHT_EDGE + 28, aY + 4, SCROLL_BAR_SIZE + 2, 8, COLOR_DARK_GRAY, 0); + PrintBox(OFFSET_FROM_RIGHT_EDGE + 29 + SCROLL_BAR_SIZE - _Width, aY + 5, _Width, 6, aOpt == aCurrentOpt ? COLOR_SELECT : COLOR_WHITE, 0); + } break; + + case DOPT_BIND: { + for (s32 i = 0; i != 3; ++i) { + u32 _Bind = aOpt->mBind.mBinds[i]; + if (aOpt == aCurrentOpt && i == aOpt->mBind.mIndex) { + if (sBindingState != 0) { + PrintString(DYNOS_TEXT_DOT_DOT_DOT, OFFSET_FROM_RIGHT_EDGE + (2 - i) * 36, aY, COLOR_SELECT, COLOR_BLACK, 0); + } else if (_Bind == VK_INVALID) { + PrintString(DYNOS_TEXT_NONE, OFFSET_FROM_RIGHT_EDGE + (2 - i) * 36, aY, COLOR_SELECT, COLOR_BLACK, 0); + } else { + PrintString(get_hex_number(_Bind), OFFSET_FROM_RIGHT_EDGE + (2 - i) * 36, aY, COLOR_SELECT, COLOR_BLACK, 0); + } + } else { + if (_Bind == VK_INVALID) { + PrintString(DYNOS_TEXT_NONE, OFFSET_FROM_RIGHT_EDGE + (2 - i) * 36, aY, COLOR_GRAY, COLOR_BLACK, 0); + } else { + PrintString(get_hex_number(_Bind), OFFSET_FROM_RIGHT_EDGE + (2 - i) * 36, aY, COLOR_WHITE, COLOR_BLACK, 0); + } + } + } + } break; + + case DOPT_BUTTON: { + } break; + + case DOPT_SUBMENU: { + if (aOpt == aCurrentOpt) { + PrintString(DYNOS_TEXT_A, OFFSET_FROM_RIGHT_EDGE, aY, COLOR_SELECT, COLOR_BLACK, 0); + } + } break; + } +} + +void DynOS_Opt_DrawMenu(DynosOption *aCurrentOption, DynosOption *aCurrentMenu, DynosOption *aOptionsMenu, DynosOption *aDynosMenu) { + if (aCurrentMenu == NULL) { + return; + } + + // Colorful label + Label _Title; + if (aCurrentOption->mParent) { + _Title = get_title(aCurrentOption->mParent); + } else if (aCurrentMenu == aDynosMenu) { + _Title = DYNOS_TEXT_DYNOS_MENU; + } else if (aCurrentMenu == aOptionsMenu) { + _Title = DYNOS_TEXT_OPTIONS_MENU; + } + gSPDisplayList(gDisplayListHead++, dl_rgba16_text_begin); + gDPSetEnvColor(gDisplayListHead++, 255, 255, 255, 255); + if (!_Title.second) _Title.second = DynOS_String_Convert(_Title.first.begin(), false); + print_hud_lut_string(HUD_LUT_GLOBAL, (SCREEN_WIDTH / 2 - DynOS_String_Length(_Title.second) * 6), 40, _Title.second); + gSPDisplayList(gDisplayListHead++, dl_rgba16_text_end); + + // Display options + DynosOption **_Options = GetCurrentOptions(aCurrentOption); + for (s32 i = 0; i != 7; ++i) { + DynOS_Opt_DrawOption(_Options[i], aCurrentOption, 156 - 20 * i); + } + + // Scroll bar + s32 _OptCount = GetCurrentOptionCount(aCurrentOption); + s32 _OptIndex = GetCurrentOptionIndex(aCurrentOption); + if (_OptCount > 7) { + s32 _Height = (s32) (134.f * sqrtf(1.f / (_OptCount - 6))); + s32 _Y = 37 + (134 - _Height) * (1.f - MAX(0.f, MIN(1.f, (f32)(_OptIndex - 3) / (f32)(_OptCount - 6)))); + PrintBox(OFFSET_FROM_RIGHT_EDGE - 16, 36, 8, 136, COLOR_DARK_GRAY, 0); + PrintBox(OFFSET_FROM_RIGHT_EDGE - 15, _Y, 6, _Height, COLOR_WHITE, 0); + } +} + +#define PROMPT_OFFSET (56.25f * GFX_DIMENSIONS_ASPECT_RATIO) +void DynOS_Opt_DrawPrompt(DynosOption *aCurrentMenu, DynosOption *aOptionsMenu, DynosOption *aDynosMenu) { + if (aCurrentMenu == aOptionsMenu) { + PrintString(DYNOS_TEXT_OPEN_LEFT, PROMPT_OFFSET, 212, COLOR_WHITE, COLOR_BLACK, 1); + PrintString(DYNOS_TEXT_CLOSE_RIGHT, PROMPT_OFFSET, 212, COLOR_WHITE, COLOR_BLACK, 0); + } else if (aCurrentMenu == aDynosMenu) { + PrintString(DYNOS_TEXT_CLOSE_LEFT, PROMPT_OFFSET, 212, COLOR_WHITE, COLOR_BLACK, 1); + PrintString(DYNOS_TEXT_OPEN_RIGHT, PROMPT_OFFSET, 212, COLOR_WHITE, COLOR_BLACK, 0); + } else { + PrintString(DYNOS_TEXT_OPEN_LEFT, PROMPT_OFFSET, 212, COLOR_WHITE, COLOR_BLACK, 1); + PrintString(DYNOS_TEXT_OPEN_RIGHT, PROMPT_OFFSET, 212, COLOR_WHITE, COLOR_BLACK, 0); + } +} diff --git a/data/dynos_opt_vanilla.cpp b/data/dynos_opt_vanilla.cpp new file mode 100644 index 000000000..2e17c1360 --- /dev/null +++ b/data/dynos_opt_vanilla.cpp @@ -0,0 +1,159 @@ +#include "dynos.cpp.h" + +static DynosOption *sPrevOpt = NULL; +static DynosOption *sOptionsMenu = NULL; + +// +// Vanilla actions +// + +typedef void (*VanillaActionFunction)(struct Option *, s32); +typedef struct VanillaAction { + String mFuncName; + VanillaActionFunction mAction; +} VanillaAction; + +STATIC_STORAGE(Array, VanillaActions); +#define sVanillaActions __VanillaActions() + +static VanillaActionFunction DynOS_Opt_GetVanillaAction(const String& aFuncName) { + for (auto &_DynosAction : sVanillaActions) { + if (_DynosAction->mFuncName == aFuncName) { + return _DynosAction->mAction; + } + } + return NULL; +} + +static void DynOS_Opt_AddVanillaAction(const String& aFuncName, void (*aFuncPtr)(struct Option *, s32)) { + for (auto &_DynosAction : sVanillaActions) { + if (_DynosAction->mFuncName == aFuncName) { + return; + } + } + VanillaAction *_DynosAction = New(); + _DynosAction->mFuncName = aFuncName; + _DynosAction->mAction = aFuncPtr; + sVanillaActions.Add(_DynosAction); +} + +static bool DynOS_Opt_CallVanillaAction(const char *aOptName) { + VanillaActionFunction _Func = DynOS_Opt_GetVanillaAction(aOptName); + if (_Func) { + _Func(NULL, 0); + return true; + } + return false; +} + +// +// Convert classic options menu into DynOS menu +// + +static DynosOption *DynOS_Opt_ConvertOption(const u8 *aLabel, const u8 *aTitle) { + static u32 sOptIdx = 0; + DynosOption *_Opt = New(); + _Opt->mName = String("vanilla_opt_%08X", sOptIdx++); + _Opt->mConfigName = ""; + _Opt->mLabel = { "", aLabel }; + _Opt->mTitle = { "", aTitle }; + _Opt->mDynos = false; + if (sPrevOpt == NULL) { // The very first option + _Opt->mPrev = NULL; + _Opt->mNext = NULL; + _Opt->mParent = NULL; + sOptionsMenu = _Opt; + } else { + if (sPrevOpt->mType == DOPT_SUBMENU && sPrevOpt->mSubMenu.mEmpty) { // First option of a sub-menu + _Opt->mPrev = NULL; + _Opt->mNext = NULL; + _Opt->mParent = sPrevOpt; + sPrevOpt->mSubMenu.mChild = _Opt; + sPrevOpt->mSubMenu.mEmpty = false; + } else { + _Opt->mPrev = sPrevOpt; + _Opt->mNext = NULL; + _Opt->mParent = sPrevOpt->mParent; + sPrevOpt->mNext = _Opt; + } + } + sPrevOpt = _Opt; + return _Opt; +} + +static void DynOS_Opt_EndSubMenu() { + if (sPrevOpt) { + if (sPrevOpt->mType == DOPT_SUBMENU && sPrevOpt->mSubMenu.mEmpty) { // ENDMENU command following a SUBMENU command + sPrevOpt->mSubMenu.mEmpty = false; + } else { + sPrevOpt = sPrevOpt->mParent; + } + } +} + +static void DynOS_Opt_ConvertSubMenu(const u8 *aLabel, const u8 *aTitle) { + DynosOption *_Opt = DynOS_Opt_ConvertOption(aLabel, aTitle); + _Opt->mType = DOPT_SUBMENU; + _Opt->mSubMenu.mChild = NULL; + _Opt->mSubMenu.mEmpty = true; +} + +static void DynOS_Opt_ConvertToggle(const u8 *aLabel, bool *pValue) { + DynosOption *_Opt = DynOS_Opt_ConvertOption(aLabel, aLabel); + _Opt->mType = DOPT_TOGGLE; + _Opt->mToggle.mTog = (bool *) pValue; +} + +static void DynOS_Opt_ConvertScroll(const u8 *aLabel, s32 aMin, s32 aMax, s32 aStep, u32 *pValue) { + DynosOption *_Opt = DynOS_Opt_ConvertOption(aLabel, aLabel); + _Opt->mType = DOPT_SCROLL; + _Opt->mScroll.mMin = aMin; + _Opt->mScroll.mMax = aMax; + _Opt->mScroll.mStep = aStep; + _Opt->mScroll.mValue = (s32 *) pValue; +} + +static void DynOS_Opt_ConvertChoice(const u8 *aLabel, const u8 **aChoices, s32 aCount, u32 *pValue) { + DynosOption *_Opt = DynOS_Opt_ConvertOption(aLabel, aLabel); + _Opt->mType = DOPT_CHOICE; + _Opt->mChoice.mIndex = (s32 *) pValue; + for (s32 i = 0; i != aCount; ++i) { + _Opt->mChoice.mChoices.Add({ "", aChoices[i] }); + } +} + +static void DynOS_Opt_ConvertButton(const u8 *aLabel, VanillaActionFunction aAction) { + DynosOption *_Opt = DynOS_Opt_ConvertOption(aLabel, aLabel); + _Opt->mType = DOPT_BUTTON; + _Opt->mButton.mFuncName = "DynOS_Opt_CallVanillaAction"; + DynOS_Opt_AddVanillaAction(_Opt->mName, aAction); +} + +static void DynOS_Opt_ConvertBind(const u8 *aLabel, u32 *pBinds) { + DynosOption *_Opt = DynOS_Opt_ConvertOption(aLabel, aLabel); + _Opt->mType = DOPT_BIND; + _Opt->mBind.mMask = 0; + _Opt->mBind.mBinds = pBinds; + _Opt->mBind.mIndex = 0; +} + +#ifndef COOP +extern "C" { +extern void dynos_opt_convert_vanilla_main_menu(); +void dynos_opt_end_submenu() { return DynOS_Opt_EndSubMenu(); } +void dynos_opt_convert_submenu(const u8 *label, const u8 *title) { return DynOS_Opt_ConvertSubMenu(label, title); } +void dynos_opt_convert_toggle(const u8 *label, bool *bval) { return DynOS_Opt_ConvertToggle(label, bval); } +void dynos_opt_convert_scroll(const u8 *label, s32 min, s32 max, s32 step, u32 *uval) { return DynOS_Opt_ConvertScroll(label, min, max, step, uval); } +void dynos_opt_convert_choice(const u8 *label, const u8 **choices, s32 numChoices, u32 *uval) { return DynOS_Opt_ConvertChoice(label, choices, numChoices, uval); } +void dynos_opt_convert_button(const u8 *label, void *action) { return DynOS_Opt_ConvertButton(label, (VanillaActionFunction) action); } +void dynos_opt_convert_bind(const u8 *label, u32 *uval) { return DynOS_Opt_ConvertBind(label, uval); } +} +#endif +void DynOS_Opt_InitVanilla(DynosOption *&aOptionsMenu) { + sPrevOpt = NULL; +#ifndef COOP + dynos_opt_convert_vanilla_main_menu(); +#endif + DynOS_Opt_AddAction("DynOS_Opt_CallVanillaAction", DynOS_Opt_CallVanillaAction, true); + aOptionsMenu = sOptionsMenu; +} diff --git a/data/dynos_opt_vanilla_c.c b/data/dynos_opt_vanilla_c.c new file mode 100644 index 000000000..d9cb37962 --- /dev/null +++ b/data/dynos_opt_vanilla_c.c @@ -0,0 +1,72 @@ + +#ifndef COOP +// Not my problem +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wsizeof-pointer-div" +#pragma GCC diagnostic ignored "-Wdiscarded-qualifiers" +#pragma GCC diagnostic ignored "-Wpointer-sign" +#pragma GCC diagnostic ignored "-Wsign-compare" +#define optmenu_toggle optmenu_toggle_unused +#define optmenu_draw optmenu_draw_unused +#define optmenu_draw_prompt optmenu_draw_prompt_unused +#define optmenu_check_buttons optmenu_check_buttons_unused +#define optmenu_open optmenu_open_unused +#define DYNOS_INL +#include "game/options_menu.c" +#undef DYNOS_INL +#undef optmenu_toggle +#undef optmenu_draw +#undef optmenu_draw_prompt +#undef optmenu_check_buttons +#undef optmenu_open +#pragma GCC diagnostic pop +// Now, that's my problem + +extern void dynos_opt_end_submenu(); +extern void dynos_opt_convert_submenu(const u8 *label, const u8 *title); +extern void dynos_opt_convert_toggle(const u8 *label, bool *bval); +extern void dynos_opt_convert_scroll(const u8 *label, s32 min, s32 max, s32 step, u32 *uval); +extern void dynos_opt_convert_choice(const u8 *label, const u8 **choices, s32 numChoices, u32 *uval); +extern void dynos_opt_convert_button(const u8 *label, void *action); +extern void dynos_opt_convert_bind(const u8 *label, u32 *uval); + +static void dynos_opt_convert_menu(struct SubMenu *submenu) { + for (s32 i = 0; i != submenu->numOpts; ++i) { + struct Option *opt = &submenu->opts[i]; + switch (opt->type) { + case OPT_TOGGLE: + dynos_opt_convert_toggle(opt->label, opt->bval); + break; + + case OPT_CHOICE: + dynos_opt_convert_choice(opt->label, opt->choices, opt->numChoices, opt->uval); + break; + + case OPT_SCROLL: + dynos_opt_convert_scroll(opt->label, opt->scrMin, opt->scrMax, opt->scrStep, opt->uval); + break; + + case OPT_SUBMENU: + dynos_opt_convert_submenu(opt->label, opt->nextMenu->label); + dynos_opt_convert_menu(opt->nextMenu); + dynos_opt_end_submenu(); + break; + + case OPT_BIND: + dynos_opt_convert_bind(opt->label, opt->uval); + break; + + case OPT_BUTTON: + dynos_opt_convert_button(opt->label, opt->actionFn); + break; + + default: + break; + } + } +} + +void dynos_opt_convert_vanilla_main_menu() { + dynos_opt_convert_menu(&menuMain); +} +#endif \ No newline at end of file diff --git a/data/dynos_warps.cpp b/data/dynos_warps.cpp index c8411108e..9151592f1 100644 --- a/data/dynos_warps.cpp +++ b/data/dynos_warps.cpp @@ -10,9 +10,7 @@ extern "C" { #include "game/level_update.h" #include "game/sound_init.h" #include "game/object_list_processor.h" -#ifndef COOP #include "game/options_menu.h" -#endif extern s8 gDialogBoxState; extern s16 gMenuMode; extern s32 gWdwWaterLevelSet; @@ -25,10 +23,8 @@ extern void set_play_mode(s16); // Data // -#ifndef COOP s32 gDDDBowsersSub = -1; s32 gDDDPoles = -1; -#endif static s32 sDynosWarpLevelNum = -1; static s32 sDynosWarpAreaNum = -1; static s32 sDynosWarpActNum = -1; @@ -64,9 +60,7 @@ bool DynOS_Warp_ExitLevel(s32 aDelay) { } // Close the pause menu if it was open -#ifndef COOP optmenu_toggle(); -#endif level_set_transition(0, NULL); gDialogBoxState = 0; gMenuMode = -1; @@ -98,9 +92,7 @@ bool DynOS_Warp_ToCastle(s32 aLevel) { } // Close the pause menu if it was open -#ifndef COOP optmenu_toggle(); -#endif level_set_transition(0, NULL); gDialogBoxState = 0; gMenuMode = -1; @@ -155,7 +147,6 @@ void DynOS_Warp_SetParam(s32 aLevel, s32 aIndex) { sDynosWarpPrevParamIndex = aIndex; } -#ifndef COOP switch (aLevel) { case LEVEL_DDD: switch (aIndex) { @@ -195,7 +186,6 @@ void DynOS_Warp_SetParam(s32 aLevel, s32 aIndex) { } break; } -#endif } // @@ -209,9 +199,7 @@ static void *DynOS_Warp_UpdateWarp(void *aCmd, bool aIsLevelInitDone) { if (sDynosWarpTargetArea == -1) { // Close the pause menu if it was open -#ifndef COOP optmenu_toggle(); -#endif level_set_transition(0, NULL); gDialogBoxState = 0; gMenuMode = -1; @@ -323,13 +311,11 @@ static void *DynOS_Warp_UpdateWarp(void *aCmd, bool aIsLevelInitDone) { } } -#ifndef COOP // Reset DDD settings to default if (gCurrCourseNum == COURSE_NONE) { gDDDBowsersSub = -1; gDDDPoles = -1; } -#endif return NULL; } diff --git a/include/types.h b/include/types.h index 4dfebce16..40c460793 100644 --- a/include/types.h +++ b/include/types.h @@ -7,6 +7,7 @@ #include #include "macros.h" #include "data/dynos.c.h" +#include "data/dynos_coop.c.h" #include "pc/network/version.h" // Certain functions are marked as having return values, but do not diff --git a/src/game/ingame_menu.c b/src/game/ingame_menu.c index e8baccdb9..c7017f263 100644 --- a/src/game/ingame_menu.c +++ b/src/game/ingame_menu.c @@ -2821,6 +2821,12 @@ s16 render_pause_courses_and_castle(void) { if (gPlayer1Controller->buttonPressed & R_TRIG) djui_panel_pause_create(NULL); +#ifndef COOP + // call into DynOS's menu system + optmenu_draw(); + optmenu_draw_prompt(); +#endif + return 0; } diff --git a/src/game/options_menu.h b/src/game/options_menu.h new file mode 100644 index 000000000..9a1743518 --- /dev/null +++ b/src/game/options_menu.h @@ -0,0 +1,8 @@ +#ifndef OPTIONS_MENU_H +#define OPTIONS_MENU_H + +void optmenu_draw(void); +void optmenu_draw_prompt(void); +void optmenu_toggle(void); + +#endif \ No newline at end of file