diff --git a/neo/framework/Dhewm3SettingsMenu.cpp b/neo/framework/Dhewm3SettingsMenu.cpp index abf9cd00..aea69882 100644 --- a/neo/framework/Dhewm3SettingsMenu.cpp +++ b/neo/framework/Dhewm3SettingsMenu.cpp @@ -2,14 +2,54 @@ #include "Common.h" #include "CVarSystem.h" +#include "idlib/LangDict.h" + +#include "UsercmdGen.h" // key bindings +//#include "Game.h" // idGameEdit to access entity definitions (player, weapons) +#include "DeclEntityDef.h" + #include "sys/sys_imgui.h" #ifndef IMGUI_DISABLE namespace { +const char* GetLocalizedString( const char* id, const char* fallback ) +{ + if ( id == nullptr || id[0] == '\0' ) { + return fallback; + } + const char* ret = common->GetLanguageDict()->GetString( id ); + if ( ret == nullptr || ret[0] == '\0' + || ( ret[0] == '#' && idStr::Cmpn( ret, STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ) ) { + ret = fallback; + } + return ret; +} + +static void AddTooltip( const char* text ) +{ + if ( ImGui::BeginItemTooltip() ) + { + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted( text ); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +static void AddDescrTooltip( const char* description ) +{ + if ( description != nullptr ) { + ImGui::SameLine(); + ImGui::TextDisabled( "(?)" ); + AddTooltip( description ); + } +} + static void AddCVarOptionTooltips( const idCVar& cvar, const char* desc = nullptr ) { +#if 0 if (ImGui::BeginItemTooltip()) { ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); @@ -17,15 +57,9 @@ static void AddCVarOptionTooltips( const idCVar& cvar, const char* desc = nullpt ImGui::PopTextWrapPos(); ImGui::EndTooltip(); } - ImGui::SameLine(); - ImGui::TextDisabled("(?)"); - if (ImGui::BeginItemTooltip()) - { - ImGui::PushTextWrapPos( ImGui::GetFontSize() * 35.0f ); - ImGui::TextUnformatted( desc ? desc : cvar.GetDescription() ); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } +#endif + AddTooltip( cvar.GetName() ); + AddDescrTooltip( desc ? desc : cvar.GetDescription() ); } enum OptionType { @@ -126,24 +160,27 @@ struct CVarOption { static void InitOptions(CVarOption options[], size_t numOptions) { - for( int i=0; i %s", command.c_str(), displayName.c_str() ); + if ( description ) { + AddDescrTooltip( description ); + } + } + } +}; + +const idDict* GetEntityDefDict( const char* name ) +{ + const idDecl* decl = declManager->FindType( DECL_ENTITYDEF, name, false ); + const idDeclEntityDef* entDef = static_cast( decl ); + return (entDef != nullptr) ? &entDef->dict : nullptr; +} + +static idList bindingEntries; +static idList obscureBindingEntries; + +static void InitBindingEntries() +{ + bindingEntries.Clear(); + + const BindingEntryTemplate betsMoveLookAttack[] = { + { "_forward", "Forward" , "#str_02100" }, + { "_back", "Backpedal" , "#str_02101" }, + { "_moveLeft", "Move Left" , "#str_02102" }, + { "_moveRight", "Move Right" , "#str_02103" }, + { "_moveUp", "Jump" , "#str_02104" }, + { "_moveDown", "Crouch" , "#str_02105" }, + { "_left", "Turn Left" , "#str_02106" }, + { "_right", "Turn Right" , "#str_02107" }, + + { "_speed", "Sprint" , "#str_02109" }, + + { "_strafe", "Strafe" , "#str_02108" }, + + { "_lookUp", "Look Up" , "#str_02116" }, + { "_lookDown", "Look Down" , "#str_02117" }, + + { "_mlook", "Mouse Look" , "#str_02118", "only really relevant if in_freeLook = 0" }, + { "_impulse18", "Center View", "#str_02119" }, + + { nullptr, "Attack" , "#str_02112" }, + + { "_attack", "Attack" , "#str_02112" }, + { "_impulse13", "Reload" , "#str_02115" }, + { "_impulse14", "Prev. Weapon" , "#str_02113" }, + { "_impulse15", "Next Weapon" , "#str_02114" }, + { "_zoom", "Zoom View" , "#str_02120" }, + { "clientDropWeapon", "Drop Weapon", "#str_04071" }, + + // also the heading for weapons, but the weapons entries are generated below.. + { nullptr, "Weapons" , "#str_01416" }, + }; + + const BindingEntryTemplate betsOther[] = { + { nullptr, "Other" , "#str_04064" }, // TODO: or "#str_02406" "Misc" + + { "_impulse19", "PDA / Score" , "#str_04066" }, + { "dhewm3Settings", "dhewm3 settings menu", nullptr }, + { "savegame quick", "Quick Save" , "#str_04067" }, + { "loadgame quick", "Quick Load" , "#str_04068" }, + { "screenshot", "Screenshot" , "#str_04069" }, + { "clientMessageMode", "Chat" , "#str_02068" }, + { "clientMessageMode 1", "Team Chat" , "#str_02122" }, + { "_impulse20", "Toggle Team" , "#str_04070" }, + { "_impulse22", "Spectate" , "#str_02125" }, + { "_impulse17", "Ready" , "#str_02126" }, + { "_impulse28", "Vote Yes" , "#str_02127" }, + { "_impulse29", "Vote No" , "#str_02128" }, + { "_impulse40", "Use Vehicle" , nullptr }, + }; + + int numReserve = IM_ARRAYSIZE(betsMoveLookAttack) + IM_ARRAYSIZE(betsOther); + numReserve += 16; // up to 14 weapons + weaponheading + moveLookHeading + if(bindingEntries.NumAllocated() < numReserve) { + bindingEntries.Resize( numReserve ); + } + + idStr moveLookHeading = GetLocalizedString( "#str_02404", "Move" ); + moveLookHeading += " / "; + moveLookHeading += GetLocalizedString( "#str_02403", "Look" ); + + bindingEntries.Append( BindingEntry( moveLookHeading ) ); + + for ( const BindingEntryTemplate& bet : betsMoveLookAttack ) { + bindingEntries.Append( BindingEntry( bet ) ); + } + + const idDict* playerDict = GetEntityDefDict( "player_doommarine" ); + const idDict* playerDictMP = GetEntityDefDict( "player_doommarine_mp" ); + + for ( int i = 0; i <= 13; ++i ) { + int weapNum = i; + int impulseNum = i; + if (i == 13) { + // Hack: D3XP uses def_weapon18 for (MP-only) weapon_chainsaw + // and the corresponding impulse is _impulse17 + // (otherwise def_weaponX corresponds to _impulseX) + weapNum = 18; + impulseNum = 27; + } + + idStr defWeapName = idStr::Format( "def_weapon%d", weapNum ); + + const char* weapName = playerDict->GetString( defWeapName, nullptr ); + if ( (weapName == nullptr || weapName[0] == '\0') && playerDictMP != nullptr ) { + weapName = playerDictMP->GetString( defWeapName, nullptr ); + } + + // TODO: could also handle weapontoggles, in playerDict(MP): + // "weapontoggle1" "2,1" // _impulse1 toggles between def_weapon2 and def_weapon1 + // "weapontoggle4" "5,4" // _impulse4 toggles between def_weapon5 and def_weapon4 + + // note: weapon_PDA is skipped, because the generic open PDA action is _impulse19 + if ( weapName != nullptr && weapName[0] != '\0' + && idStr::Icmp( weapName, "weapon_pda" ) != 0 ) { + const idDict* weapDict = GetEntityDefDict( weapName ); + if ( weapDict != nullptr ) { + const char* displayName = weapDict->GetString( "inv_name", nullptr ); + if ( displayName == nullptr ) { + displayName = weapName; + } else if ( idStr::Cmpn( displayName, STRTABLE_ID, STRTABLE_ID_LENGTH ) == 0 ) { + displayName = GetLocalizedString( displayName, weapName ); + } + bindingEntries.Append( BindingEntry( idStr::Format("_impulse%d", impulseNum), displayName ) ); + } + } + } + + for ( const BindingEntryTemplate& bet : betsOther ) { + bindingEntries.Append( BindingEntry( bet ) ); + } + + // TODO: could instead save bindingEntries.Num() as "firstObscureIndex" + // and put all the obscure bindings into the same list + // => makes handling the code ("what action are we currently trying to bind?") + // more uniform and thus simpler + + obscureBindingEntries.Clear(); + obscureBindingEntries.Resize(43); + obscureBindingEntries.Append( BindingEntry( "_impulse16", "_impulse16" ) ); + obscureBindingEntries.Append( BindingEntry( "_impulse21", "_impulse21" ) ); + // _impulse22 is "spectate", handled in "Other" section + obscureBindingEntries.Append( BindingEntry( "_impulse23", "_impulse23" ) ); + obscureBindingEntries.Append( BindingEntry( "_impulse24", "_impulse24" ) ); + obscureBindingEntries.Append( BindingEntry( "_impulse25", "_impulse25 / midnight CTF light", + "In RoE's Capture The Flag with si_midnight = 2, this appears to toggle some kind of light" ) ); + for ( int i=26; i <= 63; ++i ) { + if ( i==40 ) // _impulse40 is "use vehicle", handled above in "Other" section + continue; + + idStr impName = idStr::Format( "_impulse%d", i ); + obscureBindingEntries.Append( BindingEntry( impName, impName ) ); + } + + // player.def defines, in player_base, used by player_doommarine and player_doommarine_mp (and player_doommarine_ctf), + // "def_weapon0" "weapon_fists", "def_weapon1" "weapon_pistol" etc + // => get all those definitions (up to MAX_WEAPONS=16) from Player, and then + // get the entities for the corresponding keys ("weapon_fists" etc), + // which should have an entry like "inv_name" "Pistol" (could also be #str_00100207 though!) + + // hardcorps uses: idCVar pm_character("pm_character", "0", CVAR_GAME | CVAR_BOOL, "Change Player character. 1 = Scarlet. 0 = Doom Marine"); + // but I guess (hope) they use the same weapons.. +} + +static void DrawBindingsMenu() +{ + for( BindingEntry& be : bindingEntries ) { + be.Draw(); + } + bool showObscImp = ImGui::CollapsingHeader( "Obscure Impulses" ); + AddDescrTooltip( "_impulseXY commands that are usually unused, but might be used by some mods, e.g. for additional weapons" ); + if (showObscImp) { + for( BindingEntry& be : obscureBindingEntries ) { + be.Draw(); + } + } +} + } //anon namespace // called from D3::ImGuiHooks::NewFrame() (if this window is enabled) @@ -199,11 +467,11 @@ void Com_DrawDhewm3SettingsMenu() ImGuiTabBarFlags tab_bar_flags = ImGuiTabBarFlags_None; if (ImGui::BeginTabBar("SettingsTabBar", tab_bar_flags)) { - /*if (ImGui::BeginTabItem("Control Bindings")) + if (ImGui::BeginTabItem("Control Bindings")) { - ImGui::Text("This is the Broccoli tab!\nblah blah blah blah blah"); + DrawBindingsMenu(); ImGui::EndTabItem(); - }*/ + } if (ImGui::BeginTabItem("Control Options")) { DrawOptions( controlOptions, IM_ARRAYSIZE(controlOptions) ); @@ -211,17 +479,17 @@ void Com_DrawDhewm3SettingsMenu() } if (ImGui::BeginTabItem("Game Options")) { - ImGui::Text("This is the Cucumber tab!\nblah blah blah blah blah"); + ImGui::Text("This is the Game Options tab!\nblah blah blah blah blah"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Video Options")) { - ImGui::Text("This is the Cucumber tab!\nblah blah blah blah blah"); + ImGui::Text("This is the Video tab!\nblah blah blah blah blah"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Audio Options")) { - ImGui::Text("This is the Cucumber tab!\nblah blah blah blah blah"); + ImGui::Text("This is the Audio tab!\nblah blah blah blah blah"); ImGui::EndTabItem(); } @@ -237,6 +505,7 @@ void Com_DrawDhewm3SettingsMenu() void Com_InitDhewm3SettingsMenu() { InitOptions( controlOptions, IM_ARRAYSIZE(controlOptions) ); + InitBindingEntries(); } void Com_Dhewm3Settings_f( const idCmdArgs &args )