diff --git a/FORK_MAINTENANCE.md b/FORK_MAINTENANCE.md new file mode 100644 index 0000000000..9d08219268 --- /dev/null +++ b/FORK_MAINTENANCE.md @@ -0,0 +1,52 @@ +# Fork Maintenance + +This fork uses two long-lived branches: + +- `master` is a pristine mirror of official EmptyEpsilon from `upstream/master`. +- `gwaland-main` is the version to build, run, and maintain with local custom changes. + +New custom features should branch from `gwaland-main`, not `master`. Upstream changes should be merged into `gwaland-main` with merge commits so the custom branch history remains intact. + +Do not rebase `gwaland-main` after it has been published. Rebasing would rewrite published history and make collaboration and future maintenance harder. + +The word "backport" is not quite right for this workflow. The maintained fork is merging or forward-porting upstream changes from official EmptyEpsilon into `gwaland-main`. + +## Remotes + +- `origin`: `gwaland/EmptyEpsilon` +- `upstream`: `daid/EmptyEpsilon` + +## Direct Upstream Update Workflow + +Use this when updating the maintained fork directly: + +```bash +git fetch upstream +git switch master +git merge --ff-only upstream/master +git push origin master + +git switch gwaland-main +git merge --no-ff master +git push origin gwaland-main +``` + +The `--ff-only` merge keeps `master` as a clean mirror of official EmptyEpsilon. The `--no-ff` merge into `gwaland-main` preserves an explicit merge point for upstream updates. + +## Pull-Request-Based Update Workflow + +Use this when you want to review upstream changes in a pull request before merging them into `gwaland-main`: + +```bash +git fetch upstream + +git switch master +git merge --ff-only upstream/master +git push origin master + +git switch gwaland-main +git switch -c sync/upstream-YYYY-MM-DD +git merge --no-ff master +``` + +After resolving any conflicts and validating the build, push the `sync/upstream-YYYY-MM-DD` branch and open a pull request into `gwaland-main`. diff --git a/NEW_STATES_PLAN.md b/NEW_STATES_PLAN.md new file mode 100644 index 0000000000..c6c978bfd3 --- /dev/null +++ b/NEW_STATES_PLAN.md @@ -0,0 +1,56 @@ +# Hardware Game-State Lighting Variables + +## Summary + +Add two numeric variables for `hardware.ini` mappings: + +- `RunningScenario`: `1.0` while a scenario is loaded/running, `0.0` otherwise. +- `ShuttingDown`: `1.0` only during final hardware shutdown flush, `0.0` during normal runtime. + +These variables should work on both the host/server and connected client stations so station-attached lighting can respond consistently. + +## Implementation + +- Add a replicated `GameGlobalInfo::scenario_running` boolean, initialized false. +- Set `scenario_running = true` after `startScenario()` successfully loads the scenario script. +- Set `scenario_running = false` from `reset()` and destroy paths. +- Expose `RunningScenario` in `HardwareController::getVariableValue()` from replicated `gameGlobalInfo->scenario_running`. +- Expose `ShuttingDown` in `HardwareController::getVariableValue()` from a local shutdown flag. +- Add `HardwareController::shutdown()` to set the shutdown flag, flush `update(0.0f)` once, then wait `100 ms`. +- Call the hardware shutdown flush after `engine->runMainLoop()` returns and before window/engine teardown. + +## Hardware.ini Behavior + +Existing syntax remains unchanged. + +Example usage: + +```ini +[state] +target = bridge_light +condition = RunningScenario +value = 1.0 + +[state] +target = menu_light +condition = RunningScenario == 0 +value = 1.0 + +[state] +target = exit_light +condition = ShuttingDown +value = 1.0 +``` + +`RunningScenario` uses the existing default `> 0` condition behavior when no operator is provided. `ShuttingDown` is intended for final/default exit lighting only, not for normal menu states. + +## Test Plan + +- Build the project to verify the replicated field and hardware controller changes compile. +- Use `VirtualOutputDevice` with a small `hardware.ini` to verify: + - `RunningScenario` is `0` at the main menu. + - `RunningScenario` becomes `1` after loading a scenario. + - `RunningScenario` returns to `0` after resetting/disconnecting back to non-scenario state. + - `ShuttingDown` activates the configured output during final application exit. +- For multiplayer behavior, start a host with a scenario and connect a client station; verify client-side hardware sees `RunningScenario == 1`. +- Confirm existing hardware variables such as `Always`, `HasShip`, and ship-system variables still behave unchanged. diff --git a/src/gameGlobalInfo.cpp b/src/gameGlobalInfo.cpp index cdf517710f..0bc362be8d 100644 --- a/src/gameGlobalInfo.cpp +++ b/src/gameGlobalInfo.cpp @@ -36,6 +36,7 @@ GameGlobalInfo::GameGlobalInfo() allow_main_screen_strategic_map = true; gm_control_code = ""; elapsed_time = 0.0f; + scenario_running = false; intercept_all_comms_to_gm = false; @@ -53,6 +54,7 @@ GameGlobalInfo::GameGlobalInfo() registerMemberReplication(&allow_main_screen_strategic_map); registerMemberReplication(&gm_control_code); registerMemberReplication(&elapsed_time, 0.1); + registerMemberReplication(&scenario_running); registerMemberReplication(&default_skybox); } @@ -281,6 +283,7 @@ void GameGlobalInfo::reset() script_environment_base = nullptr; elapsed_time = 0.0f; + scenario_running = false; callsign_counter = 0; global_message = ""; global_message_timeout = 0.0f; @@ -373,6 +376,8 @@ void GameGlobalInfo::startScenario(string filename, std::unordered_maprunFile(filename); LuaConsole::checkResult(res); + if (res.isOk()) + scenario_running = true; if (res.isOk() && main_scenario_script->isFunction("init")) { bool is_headless = !PreferencesManager::get("headless").empty(); diff --git a/src/gameGlobalInfo.h b/src/gameGlobalInfo.h index 62b02343f8..54fa24f1c6 100644 --- a/src/gameGlobalInfo.h +++ b/src/gameGlobalInfo.h @@ -58,6 +58,7 @@ class GameGlobalInfo : public MultiplayerObject, public Updatable string default_skybox = "default"; string gm_control_code; float elapsed_time; + bool scenario_running; string scenario; std::unordered_map scenario_settings; diff --git a/src/gui/gui2_togglebutton.cpp b/src/gui/gui2_togglebutton.cpp index 840f914d58..01fbb1f104 100644 --- a/src/gui/gui2_togglebutton.cpp +++ b/src/gui/gui2_togglebutton.cpp @@ -52,6 +52,10 @@ bool GuiToggleButton::getValue() const return value; } +void GuiToggleButton::toggle() { + onClick(); // trigger the click event used to toggle probe view vie key. +} + GuiToggleButton* GuiToggleButton::setValue(bool value) { if (this->value == value) diff --git a/src/gui/gui2_togglebutton.h b/src/gui/gui2_togglebutton.h index edcb2eed39..65474a6a13 100644 --- a/src/gui/gui2_togglebutton.h +++ b/src/gui/gui2_togglebutton.h @@ -20,6 +20,7 @@ class GuiToggleButton : public GuiButton bool getValue() const; GuiToggleButton* setValue(bool value); + void toggle(); private: void onClick(); }; diff --git a/src/gui/hotkeyConfig.cpp b/src/gui/hotkeyConfig.cpp index e3f8ad6f08..19a1ea1ceb 100644 --- a/src/gui/hotkeyConfig.cpp +++ b/src/gui/hotkeyConfig.cpp @@ -228,7 +228,9 @@ Keys::Keys() : {"WEAPONS_FIRE_TUBE16"}, }}, weapons_enemy_next_target("WEAPONS_TARGET_NEXT_ENEMY", "C"), + weapons_enemy_prev_target("WEAPONS_TARGET_PREV_ENEMY"), weapons_next_target("WEAPONS_TARGET_NEXT", "Z"), + weapons_prev_target("WEAPONS_TARGET_PREV"), weapons_toggle_shields("WEAPONS_SHIELDS_TOGGLE", "S"), weapons_enable_shields("WEAPONS_SHIELDS_ENABLE"), weapons_disable_shields("WEAPONS_SHIELDS_DISABLE"), @@ -248,7 +250,14 @@ Keys::Keys() : // Science crew screen science_scan_object("SCIENCE_SCAN_OBJECT", "S"), science_scan_abort("SCIENCE_SCAN_ABORT", "D"), + science_scan_toggle("SCIENCE_SCAN_TOGGLE"), + science_toggle_probe_view("SCIENCE_TOGGLE_PROBE_VIEW"), science_select_next_scannable("SCIENCE_SELECT_NEXT_SCANNABLE", "C"), + science_select_prev_scannable("SCIENCE_SELECT_PREV_SCANNABLE"), + science_next_target("SCIENCE_TARGET_NEXT"), + science_prev_target("SCIENCE_TARGET_PREV"), + science_enemy_next_target("SCIENCE_TARGET_NEXT_ENEMY"), + science_enemy_prev_target("SCIENCE_TARGET_PREV_ENEMY"), science_scan_param_increase{{ {"SCIENCE_SCAN_PARAM_INCREASE_1"}, {"SCIENCE_SCAN_PARAM_INCREASE_2"}, @@ -331,6 +340,14 @@ Keys::Keys() : relay_alert_level_none("RELAY_ALERT_NONE"), relay_alert_level_yellow("RELAY_ALERT_YELLOW"), relay_alert_level_red("RELAY_ALERT_RED"), + relay_next_target("RELAY_TARGET_NEXT"), + relay_prev_target("RELAY_TARGET_PREV"), + relay_enemy_next_target("RELAY_TARGET_NEXT_ENEMY"), + relay_enemy_prev_target("RELAY_TARGET_PREV_ENEMY"), + relay_next_hackable("RELAY_TARGET_NEXT_HACKABLE"), + relay_prev_hackable("RELAY_TARGET_PREV_HACKABLE"), + relay_next_probe("RELAY_TARGET_NEXT_PROBE"), + relay_prev_probe("RELAY_TARGET_PREV_PROBE"), // GM screen gm_delete("GM_DELETE", "Delete"), @@ -439,7 +456,9 @@ void Keys::init() weapons_fire_tube[n].setLabel(tr("hotkey_menu", "Weapons"), tr("hotkey_Weapons", "Fire tube {number}").format({{"number", string(n + 1)}})); } weapons_enemy_next_target.setLabel(tr("hotkey_menu", "Weapons"), tr("hotkey_Weapons", "Select next hostile target")); + weapons_enemy_prev_target.setLabel(tr("hotkey_menu", "Weapons"), tr("hotkey_Weapons", "Select previous hostile target")); weapons_next_target.setLabel(tr("hotkey_menu", "Weapons"), tr("hotkey_Weapons", "Select next target (any)")); + weapons_prev_target.setLabel(tr("hotkey_menu", "Weapons"), tr("hotkey_Weapons", "Select previous target (any)")); weapons_toggle_shields.setLabel(tr("hotkey_menu", "Weapons"), tr("hotkey_Weapons", "Toggle shields")); weapons_enable_shields.setLabel(tr("hotkey_menu", "Weapons"), tr("hotkey_Weapons", "Enable shields")); weapons_disable_shields.setLabel(tr("hotkey_menu", "Weapons"), tr("hotkey_Weapons", "Disable shields")); @@ -459,7 +478,14 @@ void Keys::init() // Science science_scan_object.setLabel(tr("hotkey_menu", "Science"), tr("hotkey_Science", "Scan object")); science_scan_abort.setLabel(tr("hotkey_menu", "Science"), tr("hotkey_Science", "Abort scan")); + science_scan_toggle.setLabel(tr("hotkey_menu", "Science"), tr("hotkey_Science", "Scan/Abort toggle")); + science_toggle_probe_view.setLabel(tr("hotkey_menu", "Science"), tr("hotkey_Science", "Toggle probe view")); science_select_next_scannable.setLabel(tr("hotkey_menu", "Science"), tr("hotkey_Science", "Select next scannable object")); + science_select_prev_scannable.setLabel(tr("hotkey_menu", "Science"), tr("hotkey_Science", "Select previous scannable object")); + science_next_target.setLabel(tr("hotkey_menu", "Science"), tr("hotkey_Science", "Select next target (any)")); + science_prev_target.setLabel(tr("hotkey_menu", "Science"), tr("hotkey_Science", "Select previous target (any)")); + science_enemy_next_target.setLabel(tr("hotkey_menu", "Science"), tr("hotkey_Science", "Select next hostile target")); + science_enemy_prev_target.setLabel(tr("hotkey_menu", "Science"), tr("hotkey_Science", "Select previous hostile target")); for (auto n = 0u; n < science_scan_param_increase.size(); n++) { science_scan_param_increase[n].setLabel(tr("hotkey_menu", "Science"), tr("hotkey_Science", "Scanning parameter {number} increase").format({{"number", string(n + 1)}})); @@ -526,6 +552,14 @@ void Keys::init() relay_alert_level_none.setLabel(tr("hotkey_menu", "Relay"), tr("hotkey_Relay", "Alert level: Normal")); relay_alert_level_yellow.setLabel(tr("hotkey_menu", "Relay"), tr("hotkey_Relay", "Alert level: Yellow")); relay_alert_level_red.setLabel(tr("hotkey_menu", "Relay"), tr("hotkey_Relay", "Alert level: Red")); + relay_next_target.setLabel(tr("hotkey_menu", "Relay"), tr("hotkey_Relay", "Select next target (any)")); + relay_prev_target.setLabel(tr("hotkey_menu", "Relay"), tr("hotkey_Relay", "Select previous target (any)")); + relay_enemy_next_target.setLabel(tr("hotkey_menu", "Relay"), tr("hotkey_Relay", "Select next hostile target")); + relay_enemy_prev_target.setLabel(tr("hotkey_menu", "Relay"), tr("hotkey_Relay", "Select previous hostile target")); + relay_next_hackable.setLabel(tr("hotkey_menu", "Relay"), tr("hotkey_Relay", "Select next hackable target")); + relay_prev_hackable.setLabel(tr("hotkey_menu", "Relay"), tr("hotkey_Relay", "Select previous hackable target")); + relay_next_probe.setLabel(tr("hotkey_menu", "Relay"), tr("hotkey_Relay", "Select next probe")); + relay_prev_probe.setLabel(tr("hotkey_menu", "Relay"), tr("hotkey_Relay", "Select previous probe")); // Cinematic view cinematic.init(); diff --git a/src/gui/hotkeyConfig.h b/src/gui/hotkeyConfig.h index a15a4ef57e..1fc736f406 100644 --- a/src/gui/hotkeyConfig.h +++ b/src/gui/hotkeyConfig.h @@ -100,7 +100,9 @@ class Keys std::array weapons_unload_tube; std::array weapons_fire_tube; sp::io::Keybinding weapons_enemy_next_target; + sp::io::Keybinding weapons_enemy_prev_target; sp::io::Keybinding weapons_next_target; + sp::io::Keybinding weapons_prev_target; sp::io::Keybinding weapons_toggle_shields; sp::io::Keybinding weapons_enable_shields; sp::io::Keybinding weapons_disable_shields; @@ -120,7 +122,14 @@ class Keys // Science screen binds sp::io::Keybinding science_scan_object; sp::io::Keybinding science_scan_abort; + sp::io::Keybinding science_scan_toggle; sp::io::Keybinding science_select_next_scannable; + sp::io::Keybinding science_toggle_probe_view; + sp::io::Keybinding science_select_prev_scannable; + sp::io::Keybinding science_next_target; + sp::io::Keybinding science_prev_target; + sp::io::Keybinding science_enemy_next_target; + sp::io::Keybinding science_enemy_prev_target; std::array science_scan_param_increase; std::array science_scan_param_decrease; std::array science_scan_param_set; @@ -159,6 +168,14 @@ class Keys sp::io::Keybinding relay_alert_level_none; sp::io::Keybinding relay_alert_level_yellow; sp::io::Keybinding relay_alert_level_red; + sp::io::Keybinding relay_next_target; + sp::io::Keybinding relay_prev_target; + sp::io::Keybinding relay_enemy_next_target; + sp::io::Keybinding relay_enemy_prev_target; + sp::io::Keybinding relay_next_hackable; + sp::io::Keybinding relay_prev_hackable; + sp::io::Keybinding relay_next_probe; + sp::io::Keybinding relay_prev_probe; // Cinematic view binds struct CinematicKeys { diff --git a/src/hardware/hardwareController.cpp b/src/hardware/hardwareController.cpp index 012325f867..f07bcbaec8 100644 --- a/src/hardware/hardwareController.cpp +++ b/src/hardware/hardwareController.cpp @@ -29,6 +29,9 @@ #include "hardwareMappingEffects.h" +#include +#include + HardwareController::~HardwareController() { for(HardwareOutputDevice* device : devices) @@ -268,6 +271,13 @@ void HardwareController::update(float delta) } } +void HardwareController::shutdown() +{ + shutting_down = true; + update(0.0f); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); +} + void HardwareController::createNewHardwareMappingState(int channel_number, std::unordered_map& settings) { string condition = settings["condition"]; @@ -377,6 +387,16 @@ bool HardwareController::getVariableValue(string variable_name, float& value) value = bool(ship) ? 1.0f : 0.0f; return true; } + if (variable_name == "RunningScenario") + { + value = gameGlobalInfo && gameGlobalInfo->scenario_running ? 1.0f : 0.0f; + return true; + } + if (variable_name == "ShuttingDown") + { + value = shutting_down ? 1.0f : 0.0f; + return true; + } SHIP_VARIABLE("Hull", Hull, 100.0f * c->current / c->max); SHIP_VARIABLE("FrontShield", Shields, c->entries.size() > 0 ? c->entries[0].percentage() : 0.0f); SHIP_VARIABLE("RearShield", Shields, c->entries.size() > 1 ? c->entries[1].percentage() : 0.0f); diff --git a/src/hardware/hardwareController.h b/src/hardware/hardwareController.h index 560ed501c1..244fb0f337 100644 --- a/src/hardware/hardwareController.h +++ b/src/hardware/hardwareController.h @@ -56,11 +56,13 @@ class HardwareController : public Updatable std::vector states; std::vector events; std::vector channels; + bool shutting_down = false; public: HardwareController() = default; ~HardwareController(); void loadConfiguration(string filename); + void shutdown(); virtual void update(float delta) override; diff --git a/src/main.cpp b/src/main.cpp index 82d44758ff..d3b614e09b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -275,6 +275,7 @@ int main(int argc, char** argv) } engine->runMainLoop(); + hardware_controller->shutdown(); // Set FSAA and fullscreen defaults from windowManager. if (windows.size() > 0) diff --git a/src/screenComponents/shipDestroyedPopup.cpp b/src/screenComponents/shipDestroyedPopup.cpp index 5a3ae5b0a2..2e1dea44f3 100644 --- a/src/screenComponents/shipDestroyedPopup.cpp +++ b/src/screenComponents/shipDestroyedPopup.cpp @@ -3,6 +3,7 @@ #include "playerInfo.h" #include "soundManager.h" #include "main.h" +#include "preferenceManager.h" #include "gui/gui2_overlay.h" #include "gui/gui2_canvas.h" @@ -33,8 +34,22 @@ void GuiShipDestroyedPopup::onDraw(sp::RenderTarget& target) { ship_destroyed_overlay->hide(); show_timeout.start(5.0); + return_timeout = {}; }else{ if (show_timeout.isExpired()) + { ship_destroyed_overlay->show(); + if (!PreferencesManager::get("autoconnect").empty()) + { + if (!return_timeout.isRunning()) + return_timeout.start(15.0); + if (return_timeout.isExpired()) + { + soundManager->stopMusic(); + returnToShipSelection(this->owner->getRenderLayer()); + this->owner->destroy(); + } + } + } } } diff --git a/src/screenComponents/shipDestroyedPopup.h b/src/screenComponents/shipDestroyedPopup.h index 34df09a9fe..6058b086db 100644 --- a/src/screenComponents/shipDestroyedPopup.h +++ b/src/screenComponents/shipDestroyedPopup.h @@ -15,6 +15,7 @@ class GuiShipDestroyedPopup : public GuiElement GuiOverlay* ship_destroyed_overlay; GuiCanvas* owner; sp::SystemTimer show_timeout; + sp::SystemTimer return_timeout; public: GuiShipDestroyedPopup(GuiCanvas* owner); diff --git a/src/screenComponents/targetsContainer.cpp b/src/screenComponents/targetsContainer.cpp index 303a6aa822..b233067d40 100644 --- a/src/screenComponents/targetsContainer.cpp +++ b/src/screenComponents/targetsContainer.cpp @@ -7,7 +7,6 @@ #include "components/radar.h" #include "ecs/query.h" - TargetsContainer::TargetsContainer() { waypoint_selection_index = -1; @@ -22,24 +21,15 @@ void TargetsContainer::clear() void TargetsContainer::add(sp::ecs::Entity obj) { - if (!obj) - return; - for(auto e : entries) - if (e == obj) - return; + if (!obj) return; + for (auto e : entries) if (e == obj) return; entries.push_back(obj); } void TargetsContainer::set(sp::ecs::Entity obj) { - if (obj) - { - entries = {obj}; - } - else - { - clear(); - } + if (obj) entries = {obj}; + else clear(); waypoint_selection_index = -1; } @@ -56,150 +46,302 @@ std::vector TargetsContainer::getTargets() sp::ecs::Entity TargetsContainer::get() { - if (entries.empty()) - return {}; + if (entries.empty()) return {}; return entries[0]; } +std::vector TargetsContainer::populateEntities(glm::vec2 position, float max_range, ESelectionType selection_type, std::function filter) +{ + std::vector entities; + + for (auto [entity, transform] : sp::ecs::Query()) + { + if (isValidTarget(entity, selection_type) + && glm::distance(position, transform.getPosition()) <= max_range + && filter(entity)) + { + entities.push_back(entity); + } + } + + sortByDistance(position, entities); + return entities; +} + +std::vector TargetsContainer::populateEntities(glm::vec2 position, float max_range, ESelectionType selection_type, KnownFriendOrFoe known_fof) +{ + std::vector entities; + + for (auto [entity, transform] : sp::ecs::Query()) + { + if (isValidTarget(entity, selection_type) + && glm::distance(position, transform.getPosition()) <= max_range) + { + switch (known_fof) + { + // Target any entity regardless of FoF state. + case KnownFriendOrFoe::Any: + entities.push_back(entity); + break; + // Target only entities with a known FoF state (skip unknown). + case KnownFriendOrFoe::Known: + if (isFoFKnown(entity)) entities.push_back(entity); + break; + // Target only entities whose FoF state is unknown (skip known). + case KnownFriendOrFoe::Unknown: + if (!isFoFKnown(entity)) entities.push_back(entity); + break; + // Target only entities whose FoF state is known friendly. + case KnownFriendOrFoe::KnownFriendly: + if (isFoFKnown(entity) && Faction::getRelation(my_spaceship, entity) == FactionRelation::Friendly) + entities.push_back(entity); + break; + // Target only entities whose FoF state is known non-friendly + // (neutral, hostile). + case KnownFriendOrFoe::KnownNonFriendly: + if (isFoFKnown(entity) && Faction::getRelation(my_spaceship, entity) != FactionRelation::Friendly) + entities.push_back(entity); + break; + // Target only entities whose FoF state is not known friendly + // (neutral, hostile, unknown). + case KnownFriendOrFoe::NotKnownFriendly: + { + const bool is_known = isFoFKnown(entity); + if (!is_known + || (is_known && Faction::getRelation(my_spaceship, entity) != FactionRelation::Friendly)) + { + entities.push_back(entity); + } + break; + } + // Target only entities whose FoF state is known neutral. + case KnownFriendOrFoe::KnownNeutral: + if (isFoFKnown(entity) && Faction::getRelation(my_spaceship, entity) == FactionRelation::Neutral) + entities.push_back(entity); + break; + // Target only entities whose FoF state is known non-neutral + // (friendly, hostile). + case KnownFriendOrFoe::KnownNonNeutral: + if (isFoFKnown(entity) && Faction::getRelation(my_spaceship, entity) != FactionRelation::Neutral) + entities.push_back(entity); + break; + // Target only entities whose FoF state is not known neutral + // (friendly, hostile, unknown). + case KnownFriendOrFoe::NotKnownNeutral: + { + const bool is_known = isFoFKnown(entity); + if (!is_known + || (is_known && Faction::getRelation(my_spaceship, entity) != FactionRelation::Neutral)) + { + entities.push_back(entity); + } + break; + } + // Target only entities whose FoF state is known hostile. + case KnownFriendOrFoe::KnownHostile: + if (isFoFKnown(entity) && Faction::getRelation(my_spaceship, entity) == FactionRelation::Enemy) + entities.push_back(entity); + break; + // Target only entities whose FoF state is known non-hostile + // (neutral, friendly). + case KnownFriendOrFoe::KnownNonHostile: + if (isFoFKnown(entity) && Faction::getRelation(my_spaceship, entity) != FactionRelation::Enemy) + entities.push_back(entity); + break; + // Target only entities whose FoF state is not known hostile + // (neutral, friendly, unknown). + case KnownFriendOrFoe::NotKnownHostile: + { + const bool is_known = isFoFKnown(entity); + if (!is_known + || (is_known && Faction::getRelation(my_spaceship, entity) != FactionRelation::Enemy)) + { + entities.push_back(entity); + } + break; + } + } + } + } + + sortByDistance(position, entities); + return entities; +} + void TargetsContainer::setToClosestTo(glm::vec2 position, float max_range, ESelectionType selection_type) { sp::ecs::Entity target; glm::vec2 target_position; for(auto entity : sp::TransformQuery::queryArea(position - glm::vec2(max_range, max_range), position + glm::vec2(max_range, max_range))) { + if (!isValidTarget(entity, selection_type)) continue; auto transform = entity.getComponent(); if (!transform) continue; - if (!isValidTarget(entity, selection_type)) continue; - if (!target || glm::length2(position - transform->getPosition()) < glm::length2(position - target_position)) { + if (!target || glm::length2(position - transform->getPosition()) < glm::length2(position - target_position)) + { target = entity; target_position = transform->getPosition(); } } - if (allow_waypoint_selection) { - if (auto waypoints = my_spaceship.getComponent()) { - for(size_t n=0; nwaypoints.size(); n++) + if (auto waypoints = my_spaceship.getComponent()) + { + for (size_t n = 0; n < waypoints->waypoints.size(); n++) { - if (glm::length2(waypoints->waypoints[n].position - position) < max_range*max_range) + if (glm::length2(waypoints->waypoints[n].position - position) < max_range * max_range + && (!target || glm::length2(position - waypoints->waypoints[n].position) < glm::length2(position - target_position))) { - if (!target || glm::length2(position - waypoints->waypoints[n].position) < glm::length2(position - target_position)) - { - clear(); - waypoint_selection_index = waypoints->waypoints[n].id; - return; - } + clear(); + waypoint_selection_index = waypoints->waypoints[n].id; + return; } } } } + set(target); } int TargetsContainer::getWaypointIndex() { auto waypoints = my_spaceship.getComponent(); - if (!waypoints || waypoint_selection_index < 0 || !waypoints->get(waypoint_selection_index)) + if (!waypoints + || waypoint_selection_index < 0 + || !waypoints->get(waypoint_selection_index)) + { waypoint_selection_index = -1; + } + return waypoint_selection_index; } void TargetsContainer::setWaypointIndex(int index) { auto waypoints = my_spaceship.getComponent(); - if (waypoints && waypoints->get(index)) - waypoint_selection_index = index; + if (waypoints && waypoints->get(index)) waypoint_selection_index = index; } -void TargetsContainer::setNext(glm::vec2 position, float max_range, ESelectionType selection_type) +bool TargetsContainer::isFoFKnown(sp::ecs::Entity entity) { - std::vector entities; - - for(auto [entity, transform] : sp::ecs::Query()) { - if(isValidTarget(entity, selection_type) && glm::distance(position, transform.getPosition()) <= max_range) { - entities.push_back(entity); - } - } + auto ss = entity.getComponent(); + if (!ss) return true; + return ss->getStateFor(my_spaceship) >= ScanState::State::FriendOrFoeIdentified; +} - sortByDistance(position, entities); - setNext(position, max_range, entities); +void TargetsContainer::setNext(glm::vec2 position, float max_range, ESelectionType selection_type, KnownFriendOrFoe known_fof) +{ + setNext(position, populateEntities(position, max_range, selection_type, known_fof)); } -void TargetsContainer::setNext(glm::vec2 position, float max_range, ESelectionType selection_type, FactionRelation relation) +void TargetsContainer::setPrev(glm::vec2 position, float max_range, ESelectionType selection_type, KnownFriendOrFoe known_fof) { - std::vector entities; - for(auto [entity, transform] : sp::ecs::Query()) { - if(isValidTarget(entity, selection_type) && glm::distance(position, transform.getPosition()) <= max_range && Faction::getRelation(my_spaceship, entity) == relation) { - entities.push_back(entity); - } - } + setPrev(position, populateEntities(position, max_range, selection_type, known_fof)); +} - sortByDistance(position, entities); - setNext(position, max_range, entities); +void TargetsContainer::setNext(glm::vec2 position, float max_range, ESelectionType selection_type, std::function filter) +{ + setNext(position, populateEntities(position, max_range, selection_type, filter)); } -void TargetsContainer::setNext(glm::vec2 position, float max_range, std::vector &entities) +void TargetsContainer::setPrev(glm::vec2 position, float max_range, ESelectionType selection_type, std::function filter) { - sp::ecs::Entity default_target; - sp::ecs::Entity current_target; - glm::vec2 default_target_position; + setPrev(position, populateEntities(position, max_range, selection_type, filter)); +} - for (auto entity : entities) { - auto transform = entity.getComponent(); +void TargetsContainer::setNext(glm::vec2 position, const std::vector& entities) +{ + // Find the first valid entity (closest in the distance-sorted list) for + // wrap-around. + sp::ecs::Entity first_valid; + for (auto entity : entities) + { + if (!entity.hasComponent()) continue; + first_valid = entity; + break; + } - if (!transform) - continue; + if (!first_valid) return; - // Start collecting nearest relevant entities in case we never run into a previous target - if (!default_target || - glm::length2(position - transform->getPosition()) < - glm::length2(position - default_target_position)) { - default_target = entity; - default_target_position = transform->getPosition(); - } + bool found_current = false; + for (auto entity : entities) + { + if (!entity.hasComponent()) continue; - // if we set a current target in the last iteration (condition below) - // the set the entity to be this next entity in the list. - if (current_target) { + // Select the entity after this one, or wrap to the closest if there + // isn't one. + if (found_current) + { set(entity); my_player_info->commandSetTarget(get()); return; } - if (get() == entity) { - current_target = entity; - } + if (get() == entity) + found_current = true; } - // If we didn't short-circuit because of an existing target above, set the - // target to be the default_target (closest to `position`) - set(default_target); + // Current target not in list or at end: select the first/closest entity. + set(first_valid); my_player_info->commandSetTarget(get()); } -void TargetsContainer::sortByDistance(glm::vec2 position, std::vector& entities) +void TargetsContainer::setPrev(glm::vec2 position, const std::vector& entities) { - sort(entities.begin(), entities.end(), [position](sp::ecs::Entity a, sp::ecs::Entity b) { - auto transform_a = a.getComponent(); - auto transform_b = b.getComponent(); - if (!transform_a) - return bool(transform_b); + // Find the last valid entity (furthest in the distance-sorted list) for + // wrap-around. + sp::ecs::Entity last_valid; + for (auto entity : entities) + { + if (!entity.hasComponent()) continue; + last_valid = entity; + } + + if (!last_valid) return; + + sp::ecs::Entity prev_entity; + for (auto entity : entities) + { + if (!entity.hasComponent()) continue; - if (!transform_b) - return bool(transform_a); + // Select the entity before this one, or wrap to the furthest if there + // isn't one. + if (get() == entity) + { + set(prev_entity ? prev_entity : last_valid); + my_player_info->commandSetTarget(get()); + return; + } + prev_entity = entity; + } - return glm::distance(position, transform_a->getPosition()) < glm::distance(position, transform_b->getPosition()); - }); + // Current target not in list: select the furthest entity. + set(last_valid); + my_player_info->commandSetTarget(get()); +} +void TargetsContainer::sortByDistance(glm::vec2 position, std::vector& entities) +{ + sort (entities.begin(), entities.end(), + [position](sp::ecs::Entity a, sp::ecs::Entity b) + { + auto transform_a = a.getComponent(); + auto transform_b = b.getComponent(); + if (!transform_a) return bool(transform_b); + if (!transform_b) return bool(transform_a); + + return glm::distance(position, transform_a->getPosition()) < glm::distance(position, transform_b->getPosition()); + } + ); } bool TargetsContainer::isValidTarget(sp::ecs::Entity entity, ESelectionType selection_type) { if (entity == my_spaceship) return false; - switch(selection_type) + switch (selection_type) { case Selectable: if (entity.hasComponent()) return true; @@ -217,5 +359,6 @@ bool TargetsContainer::isValidTarget(sp::ecs::Entity entity, ESelectionType sele if (entity.getComponent()) return true; break; } + return false; } diff --git a/src/screenComponents/targetsContainer.h b/src/screenComponents/targetsContainer.h index 3e792e1037..f17f4cc0ed 100644 --- a/src/screenComponents/targetsContainer.h +++ b/src/screenComponents/targetsContainer.h @@ -1,8 +1,8 @@ -#ifndef TARGETS_CONTAINER_H -#define TARGETS_CONTAINER_H +#pragma once #include "ecs/entity.h" #include "components/faction.h" +#include class TargetsContainer { @@ -14,6 +14,22 @@ class TargetsContainer Scannable, }; + enum class KnownFriendOrFoe + { + Any, + Known, + Unknown, + KnownFriendly, + KnownNonFriendly, + NotKnownFriendly, + KnownNeutral, + KnownNonNeutral, + NotKnownNeutral, + KnownHostile, + KnownNonHostile, + NotKnownHostile + }; + TargetsContainer(); void setAllowWaypointSelection() { allow_waypoint_selection = true; } @@ -28,17 +44,27 @@ class TargetsContainer void setWaypointIndex(int index); void setToClosestTo(glm::vec2 position, float max_range, ESelectionType selection_type); - void setNext(glm::vec2 position, float max_range, ESelectionType selection_type); - void setNext(glm::vec2 position, float max_range, ESelectionType selection_type, FactionRelation relation); + + // Select next/previous target by selection type, and optionally also by + // friend-or-foe state. + void setNext(glm::vec2 position, float max_range, ESelectionType selection_type, KnownFriendOrFoe known_fof = KnownFriendOrFoe::Any); + void setPrev(glm::vec2 position, float max_range, ESelectionType selection_type, KnownFriendOrFoe known_fof = KnownFriendOrFoe::Any); + // Select next/previous target by selection type and a function-defined + // filter. + void setNext(glm::vec2 position, float max_range, ESelectionType selection_type, std::function filter); + void setPrev(glm::vec2 position, float max_range, ESelectionType selection_type, std::function filter); private: std::vector entries; bool allow_waypoint_selection; int waypoint_selection_index; - void setNext(glm::vec2 position, float max_range, std::vector& entities); + bool isFoFKnown(sp::ecs::Entity entity); void sortByDistance(glm::vec2 position, std::vector& entities); bool isValidTarget(sp::ecs::Entity entity, ESelectionType selection_type); + // Return a vector of entities that match the given condition. + std::vector populateEntities(glm::vec2 position, float max_range, ESelectionType selection_type, KnownFriendOrFoe known_fof); + std::vector populateEntities(glm::vec2 position, float max_range, ESelectionType selection_type, std::function filter); + void setNext(glm::vec2 position, const std::vector& entities); + void setPrev(glm::vec2 position, const std::vector& entities); }; - -#endif//TARGETS_CONTAINER_H diff --git a/src/screens/crew1/singlePilotScreen.cpp b/src/screens/crew1/singlePilotScreen.cpp index 5aa90eac9e..8d46354b38 100644 --- a/src/screens/crew1/singlePilotScreen.cpp +++ b/src/screens/crew1/singlePilotScreen.cpp @@ -164,37 +164,83 @@ void SinglePilotScreen::onDraw(sp::RenderTarget& renderer) void SinglePilotScreen::onUpdate() { - if (my_spaceship && isVisible()) + if (!my_spaceship || !isVisible()) return; + + // Copied and pasted from Helms screen. + auto angle = (keys.helms_turn_right.getValue() - keys.helms_turn_left.getValue()) * 5.0f; + if (angle != 0.0f) { - auto angle = (keys.helms_turn_right.getValue() - keys.helms_turn_left.getValue()) * 5.0f; - if (angle != 0.0f) - { - if (auto transform = my_spaceship.getComponent()) - my_player_info->commandTargetRotation(transform->getRotation() + angle); - } + if (auto transform = my_spaceship.getComponent()) + my_player_info->commandTargetRotation(transform->getRotation() + angle); + } - if (keys.weapons_enemy_next_target.getDown()) + // Copied and pasted from Weapons screen. + // Target selection cycle keybinds. + // Select hostile targets. + if (keys.weapons_enemy_next_target.getDown()) + { + if (auto transform = my_spaceship.getComponent()) { - if (auto transform = my_spaceship.getComponent()) { - auto lrr = my_spaceship.getComponent(); - targets.setNext(transform->getPosition(), lrr ? lrr->short_range : 5000.0f, TargetsContainer::Targetable, FactionRelation::Enemy); - my_player_info->commandSetTarget(targets.get()); - } + auto lrr = my_spaceship.getComponent(); + targets.setNext( + transform->getPosition(), + lrr ? lrr->short_range : 5000.0f, + TargetsContainer::ESelectionType::Targetable, + TargetsContainer::KnownFriendOrFoe::KnownHostile + ); + my_player_info->commandSetTarget(targets.get()); } - if (keys.weapons_next_target.getDown()) + } + if (keys.weapons_enemy_prev_target.getDown()) + { + if (auto transform = my_spaceship.getComponent()) { - if (auto transform = my_spaceship.getComponent()) { - auto lrr = my_spaceship.getComponent(); - targets.setNext(transform->getPosition(), lrr ? lrr->short_range : 5000.0f, TargetsContainer::Targetable); - my_player_info->commandSetTarget(targets.get()); - } + auto lrr = my_spaceship.getComponent(); + targets.setPrev( + transform->getPosition(), + lrr ? lrr->short_range : 5000.0f, + TargetsContainer::ESelectionType::Targetable, + TargetsContainer::KnownFriendOrFoe::KnownHostile + ); + my_player_info->commandSetTarget(targets.get()); } + } - auto aim_adjust = keys.weapons_aim_left.getValue() - keys.weapons_aim_right.getValue(); - if (aim_adjust != 0.0f) + // Select any non-friendly target. + if (keys.weapons_next_target.getDown()) + { + if (auto transform = my_spaceship.getComponent()) + { + auto lrr = my_spaceship.getComponent(); + targets.setNext( + transform->getPosition(), + lrr ? lrr->short_range : 5000.0f, + TargetsContainer::ESelectionType::Targetable, + TargetsContainer::KnownFriendOrFoe::NotKnownFriendly + ); + my_player_info->commandSetTarget(targets.get()); + } + } + if (keys.weapons_prev_target.getDown()) + { + if (auto transform = my_spaceship.getComponent()) { - missile_aim->setValue(missile_aim->getValue() - 5.0f * aim_adjust); - tube_controls->setMissileTargetAngle(missile_aim->getValue()); + auto lrr = my_spaceship.getComponent(); + targets.setPrev( + transform->getPosition(), + lrr ? lrr->short_range : 5000.0f, + TargetsContainer::ESelectionType::Targetable, + TargetsContainer::KnownFriendOrFoe::NotKnownFriendly + ); + my_player_info->commandSetTarget(targets.get()); } } + + // Manual missile aiming keybinds. + auto aim_adjust = keys.weapons_aim_left.getValue() - keys.weapons_aim_right.getValue(); + if (aim_adjust != 0.0f) + { + missile_aim->setValue(missile_aim->getValue() - 5.0f * aim_adjust); + tube_controls->setMissileTargetAngle(missile_aim->getValue()); + } } diff --git a/src/screens/crew4/tacticalScreen.cpp b/src/screens/crew4/tacticalScreen.cpp index dd2280c1e2..6e36f14fc5 100644 --- a/src/screens/crew4/tacticalScreen.cpp +++ b/src/screens/crew4/tacticalScreen.cpp @@ -152,37 +152,83 @@ void TacticalScreen::onDraw(sp::RenderTarget& renderer) void TacticalScreen::onUpdate() { - if (my_spaceship && isVisible()) + if (!my_spaceship || !isVisible()) return; + + // Copied and pasted from Helms screen. + auto angle = (keys.helms_turn_right.getValue() - keys.helms_turn_left.getValue()) * 5.0f; + if (angle != 0.0f) { - auto angle = (keys.helms_turn_right.getValue() - keys.helms_turn_left.getValue()) * 5.0f; - if (angle != 0.0f) - { - if (auto transform = my_spaceship.getComponent()) - my_player_info->commandTargetRotation(transform->getRotation() + angle); - } + if (auto transform = my_spaceship.getComponent()) + my_player_info->commandTargetRotation(transform->getRotation() + angle); + } - if (keys.weapons_enemy_next_target.getDown()) + // Copied and pasted from Weapons screen. + // Target selection cycle keybinds. + // Select hostile targets. + if (keys.weapons_enemy_next_target.getDown()) + { + if (auto transform = my_spaceship.getComponent()) { - if (auto transform = my_spaceship.getComponent()) { - auto lrr = my_spaceship.getComponent(); - targets.setNext(transform->getPosition(), lrr ? lrr->short_range : 5000.0f, TargetsContainer::Targetable, FactionRelation::Enemy); - my_player_info->commandSetTarget(targets.get()); - } + auto lrr = my_spaceship.getComponent(); + targets.setNext( + transform->getPosition(), + lrr ? lrr->short_range : 5000.0f, + TargetsContainer::ESelectionType::Targetable, + TargetsContainer::KnownFriendOrFoe::KnownHostile + ); + my_player_info->commandSetTarget(targets.get()); } - if (keys.weapons_next_target.getDown()) + } + if (keys.weapons_enemy_prev_target.getDown()) + { + if (auto transform = my_spaceship.getComponent()) { - if (auto transform = my_spaceship.getComponent()) { - auto lrr = my_spaceship.getComponent(); - targets.setNext(transform->getPosition(), lrr ? lrr->short_range : 5000.0f, TargetsContainer::Targetable); - my_player_info->commandSetTarget(targets.get()); - } + auto lrr = my_spaceship.getComponent(); + targets.setPrev( + transform->getPosition(), + lrr ? lrr->short_range : 5000.0f, + TargetsContainer::ESelectionType::Targetable, + TargetsContainer::KnownFriendOrFoe::KnownHostile + ); + my_player_info->commandSetTarget(targets.get()); } + } - auto aim_adjust = keys.weapons_aim_left.getValue() - keys.weapons_aim_right.getValue(); - if (aim_adjust != 0.0f) + // Select any non-friendly target. + if (keys.weapons_next_target.getDown()) + { + if (auto transform = my_spaceship.getComponent()) + { + auto lrr = my_spaceship.getComponent(); + targets.setNext( + transform->getPosition(), + lrr ? lrr->short_range : 5000.0f, + TargetsContainer::ESelectionType::Targetable, + TargetsContainer::KnownFriendOrFoe::NotKnownFriendly + ); + my_player_info->commandSetTarget(targets.get()); + } + } + if (keys.weapons_prev_target.getDown()) + { + if (auto transform = my_spaceship.getComponent()) { - missile_aim->setValue(missile_aim->getValue() - 5.0f * aim_adjust); - tube_controls->setMissileTargetAngle(missile_aim->getValue()); + auto lrr = my_spaceship.getComponent(); + targets.setPrev( + transform->getPosition(), + lrr ? lrr->short_range : 5000.0f, + TargetsContainer::ESelectionType::Targetable, + TargetsContainer::KnownFriendOrFoe::NotKnownFriendly + ); + my_player_info->commandSetTarget(targets.get()); } } + + // Manual missile aiming keybinds. + auto aim_adjust = keys.weapons_aim_left.getValue() - keys.weapons_aim_right.getValue(); + if (aim_adjust != 0.0f) + { + missile_aim->setValue(missile_aim->getValue() - 5.0f * aim_adjust); + tube_controls->setMissileTargetAngle(missile_aim->getValue()); + } } diff --git a/src/screens/crew6/relayScreen.cpp b/src/screens/crew6/relayScreen.cpp index d7bd665eac..560e92b125 100644 --- a/src/screens/crew6/relayScreen.cpp +++ b/src/screens/crew6/relayScreen.cpp @@ -38,6 +38,22 @@ static bool canHack(sp::ecs::Entity entity) return Faction::getRelation(entity, my_spaceship) != FactionRelation::Friendly; } +static bool isVisibleOnRelay(sp::ecs::Entity entity) +{ + auto target_transform = entity.getComponent(); + if (!target_transform) return false; + for (auto [e, ssrr, transform] : sp::ecs::Query()) + { + if (Faction::getRelation(my_spaceship, e) != FactionRelation::Friendly) + continue; + float r = 5000.0f; + if (auto lrr = e.getComponent()) r = lrr->short_range; + if (glm::length2(transform.getPosition() - target_transform->getPosition()) < r * r) + return true; + } + return false; +} + RelayScreen::RelayScreen(GuiContainer* owner, bool allow_comms) : GuiOverlay(owner, "RELAY_SCREEN", GuiTheme::getColor("background")), mode(TargetSelection) { @@ -70,7 +86,7 @@ RelayScreen::RelayScreen(GuiContainer* owner, bool allow_comms) switch(mode) { case TargetSelection: - targets.setToClosestTo(position, 1000, TargetsContainer::Targetable); + targets.setToClosestTo(position, 1000, TargetsContainer::Selectable); break; case WaypointPlacement: if (my_spaceship) @@ -342,3 +358,87 @@ void RelayScreen::onDraw(sp::RenderTarget& renderer) delete_waypoint_button->setEnable(targets.getWaypointIndex() >= 0); } + +void RelayScreen::onUpdate() +{ + if (!my_spaceship || !isVisible()) return; + + if (auto transform = my_spaceship.getComponent()) + { + const float view_range = radar->getDistance() * 1.42f; + + // Select visible targetable entities. + if (keys.relay_next_target.getDown()) + targets.setNext(transform->getPosition(), view_range, TargetsContainer::Targetable, isVisibleOnRelay); + if (keys.relay_prev_target.getDown()) + targets.setPrev(transform->getPosition(), view_range, TargetsContainer::Targetable, isVisibleOnRelay); + + // Select visible hostile entities. + if (keys.relay_enemy_next_target.getDown()) + { + targets.setNext(transform->getPosition(), view_range, TargetsContainer::Targetable, + [](sp::ecs::Entity entity) + { + if (!isVisibleOnRelay(entity)) return false; + auto ss = entity.getComponent(); + bool fof_known = !ss || ss->getStateFor(my_spaceship) >= ScanState::State::FriendOrFoeIdentified; + return fof_known && Faction::getRelation(my_spaceship, entity) == FactionRelation::Enemy; + } + ); + } + if (keys.relay_enemy_prev_target.getDown()) + { + targets.setPrev(transform->getPosition(), view_range, TargetsContainer::Targetable, + [](sp::ecs::Entity entity) + { + if (!isVisibleOnRelay(entity)) return false; + auto ss = entity.getComponent(); + bool fof_known = !ss || ss->getStateFor(my_spaceship) >= ScanState::State::FriendOrFoeIdentified; + return fof_known && Faction::getRelation(my_spaceship, entity) == FactionRelation::Enemy; + } + ); + } + + // Select visible hackable entities. + if (keys.relay_next_hackable.getDown()) + { + targets.setNext(transform->getPosition(), view_range, TargetsContainer::Targetable, + [](sp::ecs::Entity entity) + { + return isVisibleOnRelay(entity) && canHack(entity); + } + ); + } + if (keys.relay_prev_hackable.getDown()) + { + targets.setPrev(transform->getPosition(), view_range, TargetsContainer::Targetable, + [](sp::ecs::Entity entity) + { + return isVisibleOnRelay(entity) && canHack(entity); + } + ); + } + + // Select player-launched probes. + if (keys.relay_next_probe.getDown()) + { + targets.setNext(transform->getPosition(), view_range, TargetsContainer::Selectable, + [](sp::ecs::Entity entity) + { + auto arl = entity.getComponent(); + return arl && arl->owner == my_spaceship; + } + ); + } + if (keys.relay_prev_probe.getDown()) + { + targets.setPrev(transform->getPosition(), view_range, TargetsContainer::Selectable, + [](sp::ecs::Entity entity) + { + auto arl = entity.getComponent(); + return arl && arl->owner == my_spaceship; + } + ); + } + } +} diff --git a/src/screens/crew6/relayScreen.h b/src/screens/crew6/relayScreen.h index 8de707344b..37d4d4f0fa 100644 --- a/src/screens/crew6/relayScreen.h +++ b/src/screens/crew6/relayScreen.h @@ -52,4 +52,5 @@ class RelayScreen : public GuiOverlay RelayScreen(GuiContainer* owner, bool allow_comms); virtual void onDraw(sp::RenderTarget& target) override; + virtual void onUpdate() override; }; diff --git a/src/screens/crew6/scienceScreen.cpp b/src/screens/crew6/scienceScreen.cpp index 22e05bc8fc..6b07c6e651 100644 --- a/src/screens/crew6/scienceScreen.cpp +++ b/src/screens/crew6/scienceScreen.cpp @@ -547,21 +547,51 @@ void ScienceScreen::onDraw(sp::RenderTarget& renderer) void ScienceScreen::onUpdate() { - if (my_spaceship) + if (!my_spaceship || !isVisible()) return; + + // Toggle probe view from the Science hotkey. + auto rl = my_spaceship.getComponent(); + if (keys.science_toggle_probe_view.getDown() && rl && rl->linked_entity) { - // Initiate a scan on scannable objects. - if (keys.science_scan_object.getDown() && - my_spaceship.hasComponent() && - my_spaceship.getComponent()->delay == 0.0f) + probe_view_button->toggle(); + } + + // Initiate a scan on scannable objects. + if (keys.science_scan_object.getDown() && + my_spaceship.hasComponent() && + my_spaceship.getComponent()->delay == 0.0f) + { + auto obj = targets.get(); + + // Allow scanning only if the object is scannable, and if the player + // isn't already scanning something. + auto scanstate = obj.getComponent(); + if (scanstate && scanstate->getStateFor(my_spaceship) != ScanState::State::FullScan) { - auto obj = targets.get(); + // Check for active radar link and validate the linked entity. + auto rl = my_spaceship.getComponent(); + if (rl && rl->linked_entity && rl->linked_entity.hasComponent() && probe_radar->isVisible()) + my_player_info->commandScan(obj, rl->linked_entity); + else + my_player_info->commandScan(obj); + return; + } + } - // Allow scanning only if the object is scannable, and if the player - // isn't already scanning something. + // Scan/Abort toggle: abort if scanning, otherwise start a scan. + if (keys.science_scan_toggle.getDown() && my_spaceship.hasComponent()) + { + auto scanner = my_spaceship.getComponent(); + if (scanner->delay > 0.0f) + { + my_player_info->commandScanCancel(); + } + else + { + auto obj = targets.get(); auto scanstate = obj.getComponent(); if (scanstate && scanstate->getStateFor(my_spaceship) != ScanState::State::FullScan) { - // Check for active radar link and validate the linked entity auto rl = my_spaceship.getComponent(); if (rl && rl->linked_entity && rl->linked_entity.hasComponent() && probe_radar->isVisible()) my_player_info->commandScan(obj, rl->linked_entity); @@ -570,16 +600,61 @@ void ScienceScreen::onUpdate() return; } } + } - // Cycle selection through scannable objects. - if (keys.science_select_next_scannable.getDown() && - my_spaceship.hasComponent() && - my_spaceship.getComponent()->delay == 0.0f) + // Cycle selectable entities. + if (auto transform = my_spaceship.getComponent()) + { + auto scanner = my_spaceship.getComponent(); + auto lrr = my_spaceship.getComponent(); + glm::vec2 scanner_position = transform->getPosition(); + float scanner_range = science_radar->getDistance(); + bool in_probe_view = false; + + if (rl && probe_view_button->getValue() && rl->linked_entity) { - if (auto transform = my_spaceship.getComponent()) { - auto lrr = my_spaceship.getComponent(); - targets.setNext(transform->getPosition(), lrr ? lrr->long_range : DEFAULT_MAX_ZOOM_DISTANCE, TargetsContainer::ESelectionType::Scannable); + if (auto probe_transform = rl->linked_entity.getComponent()) + { + scanner_position = probe_transform->getPosition(); + scanner_range = PROBE_ZOOM_DISTANCE; + in_probe_view = true; } } + + float short_range = lrr ? lrr->short_range : 5000.0f; + auto visibility_filter = [scanner_position, in_probe_view, short_range](sp::ecs::Entity entity) { + if (in_probe_view) + return true; + return !RadarBlockSystem::isRadarBlockedFrom(scanner_position, entity, short_range); + }; + + // Select previous/next scannable entity. + if (scanner && scanner->delay == 0.0f) + { + if (keys.science_select_next_scannable.getDown()) + targets.setNext(scanner_position, scanner_range, TargetsContainer::ESelectionType::Scannable, visibility_filter); + if (keys.science_select_prev_scannable.getDown()) + targets.setPrev(scanner_position, scanner_range, TargetsContainer::ESelectionType::Scannable, visibility_filter); + } + + // Select previous/next hostile entity. + auto hostile_filter = [visibility_filter](sp::ecs::Entity entity) { + if (!visibility_filter(entity)) + return false; + auto ss = entity.getComponent(); + if (ss && ss->getStateFor(my_spaceship) < ScanState::State::FriendOrFoeIdentified) + return false; + return Faction::getRelation(my_spaceship, entity) == FactionRelation::Enemy; + }; + if (keys.science_enemy_next_target.getDown()) + targets.setNext(scanner_position, scanner_range, TargetsContainer::ESelectionType::Selectable, hostile_filter); + if (keys.science_enemy_prev_target.getDown()) + targets.setPrev(scanner_position, scanner_range, TargetsContainer::ESelectionType::Selectable, hostile_filter); + + // Select previous/next selectable entity. + if (keys.science_next_target.getDown()) + targets.setNext(scanner_position, scanner_range, TargetsContainer::ESelectionType::Selectable, visibility_filter); + if (keys.science_prev_target.getDown()) + targets.setPrev(scanner_position, scanner_range, TargetsContainer::ESelectionType::Selectable, visibility_filter); } } diff --git a/src/screens/crew6/weaponsScreen.cpp b/src/screens/crew6/weaponsScreen.cpp index 998801605f..fa6e737264 100644 --- a/src/screens/crew6/weaponsScreen.cpp +++ b/src/screens/crew6/weaponsScreen.cpp @@ -142,29 +142,74 @@ void WeaponsScreen::onDraw(sp::RenderTarget& renderer) void WeaponsScreen::onUpdate() { - if (my_spaceship && isVisible()) + if (!my_spaceship || !isVisible()) return; + + // Target selection cycle keybinds. + // Select hostile targets. + if (keys.weapons_enemy_next_target.getDown()) { - if (keys.weapons_enemy_next_target.getDown()) + if (auto transform = my_spaceship.getComponent()) { - if (auto transform = my_spaceship.getComponent()) { - auto lrr = my_spaceship.getComponent(); - targets.setNext(transform->getPosition(), lrr ? lrr->short_range : 5000.0f, TargetsContainer::Targetable, FactionRelation::Enemy); - my_player_info->commandSetTarget(targets.get()); - } + auto lrr = my_spaceship.getComponent(); + targets.setNext( + transform->getPosition(), + lrr ? lrr->short_range : 5000.0f, + TargetsContainer::ESelectionType::Targetable, + TargetsContainer::KnownFriendOrFoe::KnownHostile + ); + my_player_info->commandSetTarget(targets.get()); } - if (keys.weapons_next_target.getDown()) + } + if (keys.weapons_enemy_prev_target.getDown()) + { + if (auto transform = my_spaceship.getComponent()) { - if (auto transform = my_spaceship.getComponent()) { - auto lrr = my_spaceship.getComponent(); - targets.setNext(transform->getPosition(), lrr ? lrr->short_range : 5000.0f, TargetsContainer::Targetable); - my_player_info->commandSetTarget(targets.get()); - } + auto lrr = my_spaceship.getComponent(); + targets.setPrev( + transform->getPosition(), + lrr ? lrr->short_range : 5000.0f, + TargetsContainer::ESelectionType::Targetable, + TargetsContainer::KnownFriendOrFoe::KnownHostile + ); + my_player_info->commandSetTarget(targets.get()); } - auto aim_adjust = keys.weapons_aim_left.getValue() - keys.weapons_aim_right.getValue(); - if (aim_adjust != 0.0f) + } + + // Select any non-friendly target. + if (keys.weapons_next_target.getDown()) + { + if (auto transform = my_spaceship.getComponent()) { - missile_aim->setValue(missile_aim->getValue() - 5.0f * aim_adjust); - tube_controls->setMissileTargetAngle(missile_aim->getValue()); + auto lrr = my_spaceship.getComponent(); + targets.setNext( + transform->getPosition(), + lrr ? lrr->short_range : 5000.0f, + TargetsContainer::ESelectionType::Targetable, + TargetsContainer::KnownFriendOrFoe::NotKnownFriendly + ); + my_player_info->commandSetTarget(targets.get()); } } + if (keys.weapons_prev_target.getDown()) + { + if (auto transform = my_spaceship.getComponent()) + { + auto lrr = my_spaceship.getComponent(); + targets.setPrev( + transform->getPosition(), + lrr ? lrr->short_range : 5000.0f, + TargetsContainer::ESelectionType::Targetable, + TargetsContainer::KnownFriendOrFoe::NotKnownFriendly + ); + my_player_info->commandSetTarget(targets.get()); + } + } + + // Manual missile aiming keybinds. + auto aim_adjust = keys.weapons_aim_left.getValue() - keys.weapons_aim_right.getValue(); + if (aim_adjust != 0.0f) + { + missile_aim->setValue(missile_aim->getValue() - 5.0f * aim_adjust); + tube_controls->setMissileTargetAngle(missile_aim->getValue()); + } }