diff --git a/mods/src/patches/parts/hotkeys.cc b/mods/src/patches/parts/hotkeys.cc index c9392f5b..8592d89f 100644 --- a/mods/src/patches/parts/hotkeys.cc +++ b/mods/src/patches/parts/hotkeys.cc @@ -1,4 +1,5 @@ #include "config.h" +#include "patches/ship_shortcut_hint.h" #include #include @@ -266,7 +267,18 @@ void ShortcutKeybindHint_UpdateText_Hook(auto original, void* _this) return; } - auto shortcut = MapKey::GetShortcutHint(mapping->primary); + auto hint_action = mapping->primary; + if (hint_action >= GameFunction::SelectShip1 && hint_action <= GameFunction::SelectShip8) { + // Use the same context array as FleetBarViewController::RequestSelect(int, bool), not the native dock index. + auto* bar = ObjectFinder::Get(); + auto* context = bar ? bar->CanvasContext() : nullptr; + auto* fleets = context ? context->FleetData() : nullptr; + hint_action = ShipShortcutHintAction(hint_action, fleets ? fleets->max_length : 0, [fleets](std::size_t position) { + auto* fleet = reinterpret_cast(fleets->vector)[position]; + return fleet ? fleet->Index : -1; + }); + } + auto shortcut = hint_action == GameFunction::Max ? std::string{} : MapKey::GetShortcutHint(hint_action); if (shortcut.empty() && mapping->fallback != GameFunction::Max) { shortcut = MapKey::GetShortcutHint(mapping->fallback); } diff --git a/mods/src/patches/ship_shortcut_hint.h b/mods/src/patches/ship_shortcut_hint.h new file mode 100644 index 00000000..671e74d5 --- /dev/null +++ b/mods/src/patches/ship_shortcut_hint.h @@ -0,0 +1,22 @@ +#pragma once + +#include "gamefunctions.h" + +#include + +// Native ship actions identify FleetPlayerData::Index. Mod selection passes a position in FleetBarContext::FleetData +// to RequestSelect instead; locked docks can leave gaps in the native indices. +template +GameFunction ShipShortcutHintAction(GameFunction native_action, std::size_t fleet_count, FleetIndexAt fleet_index_at) +{ + if (native_action < GameFunction::SelectShip1 || native_action > GameFunction::SelectShip8) + return native_action; + + const auto fleet_index = native_action - GameFunction::SelectShip1; + constexpr auto shortcut_count = GameFunction::SelectShip8 - GameFunction::SelectShip1 + 1; + for (std::size_t position = 0; position < fleet_count && position < shortcut_count; ++position) { + if (fleet_index_at(position) == fleet_index) + return static_cast(GameFunction::SelectShip1 + position); + } + return GameFunction::Max; +} diff --git a/mods/src/prime/FleetBarViewController.h b/mods/src/prime/FleetBarViewController.h index 87a9327a..c0d0ec9a 100644 --- a/mods/src/prime/FleetBarViewController.h +++ b/mods/src/prime/FleetBarViewController.h @@ -16,6 +16,12 @@ struct FleetBarContext { } public: + Il2CppArraySize* FleetData() + { + static auto property = get_class_helper().GetProperty("FleetData"); + return property.GetRaw(this); + } + void* __get_CurrentFleet() { static auto field = get_class_helper().GetProperty("CurrentFleet"); diff --git a/tests/shortcut_hint_cache.cc b/tests/shortcut_hint_cache.cc index fc23adb8..f91372de 100644 --- a/tests/shortcut_hint_cache.cc +++ b/tests/shortcut_hint_cache.cc @@ -2,6 +2,7 @@ // MapKey parsing, ModifierKey parsing, binding detection, and hint caching are production code. #include "patches/mapkey.h" #include "defaultconfig.h" +#include "patches/ship_shortcut_hint.h" #include #include @@ -41,6 +42,39 @@ void Check(bool condition, const char* message) int main() { + // A locked dock F leaves native G at the sixth selectable position (the reported 1,2,3,4,5,7 layout). + const std::array gapped_fleets{0, 1, 2, 3, 4, 6}; + auto ship_hint = [&](GameFunction action) { + return ShipShortcutHintAction(action, gapped_fleets.size(), [&](std::size_t i) { return gapped_fleets[i]; }); + }; + Check(ship_hint(GameFunction::SelectShip7) == GameFunction::SelectShip6, + "Native ship G must advertise the sixth mod shortcut when dock F is locked"); + Check(ship_hint(GameFunction::SelectShip5) == GameFunction::SelectShip5, "Earlier ship hints must stay unchanged"); + Check(ship_hint(GameFunction::SelectShip6) == GameFunction::Max, "Missing dock must not advertise a shortcut"); + Check(ship_hint(GameFunction::ShowGalaxy) == GameFunction::ShowGalaxy, "Non-ship hint mapping changed"); + Check(ShipShortcutHintAction(GameFunction::SelectShip1, 0, [](std::size_t) { return 0; }) == GameFunction::Max, + "Unavailable fleet context must not advertise a guessed ship shortcut"); + for (int index = 0; index < 8; ++index) { + auto action = static_cast(GameFunction::SelectShip1 + index); + Check(ShipShortcutHintAction(action, 8, [](std::size_t i) { return static_cast(i); }) == action, + "Contiguous eight-dock hints must keep their original shortcuts"); + } + const std::array reordered_fleets{7, 0, 2, -1, 6, 4, 1, 3, 5}; + auto reordered_hint = [&](GameFunction action) { + return ShipShortcutHintAction(action, reordered_fleets.size(), [&](std::size_t i) { return reordered_fleets[i]; }); + }; + Check(reordered_hint(GameFunction::SelectShip8) == GameFunction::SelectShip1, "Ship hints must follow bar order"); + Check(reordered_hint(GameFunction::SelectShip7) == GameFunction::SelectShip5, + "Null fleet entries must preserve the positions passed to RequestSelect"); + Check(reordered_hint(GameFunction::SelectShip6) == GameFunction::Max, + "Fleet beyond the eight supported mod shortcuts must not advertise a shortcut"); + Check(MapKey::GetShortcutHint(ship_hint(GameFunction::SelectShip7)).empty(), + "Unbound ship selection must not advertise a working shortcut"); + MapKey::AddMappedKey(GameFunction::SelectShip6, MapKey::Parse("CTRL-F8")); + MapKey::CacheShortcutHints(); + Check(MapKey::GetShortcutHint(ship_hint(GameFunction::SelectShip7)) == "^F8", + "Gapped ship hint must use the actual custom binding, not a hardcoded number"); + constexpr auto toggle = GameFunction::ToggleShortcutHints; constexpr auto galaxy = GameFunction::ShowGalaxy; Check(!MapKey::HasBinding(toggle), "Absent binding must not enable hints");