diff --git a/resources/gui/default.theme.txt b/resources/gui/default.theme.txt index e1823bc164..74f7690227 100644 --- a/resources/gui/default.theme.txt +++ b/resources/gui/default.theme.txt @@ -58,6 +58,33 @@ color: #000000D0 } } + [ship_waypoint_set2] { + image: waypoint.png + [ship_waypoint_set2.background] { + color: #FF4040D0 + } + [ship_waypoint_set2.text] { + color: #000000D0 + } + } + [ship_waypoint_set3] { + image: waypoint.png + [ship_waypoint_set3.background] { + color: #40FF40D0 + } + [ship_waypoint_set3.text] { + color: #000000D0 + } + } + [ship_waypoint_set4] { + image: waypoint.png + [ship_waypoint_set4.background] { + color: #40FFFFD0 + } + [ship_waypoint_set4.text] { + color: #000000D0 + } + } [radar] { font: gui/fonts/BigShouldersDisplay-ExtraBold.ttf diff --git a/scripts/api/entity/playerspaceship.lua b/scripts/api/entity/playerspaceship.lua index bc3a2c910f..da613131fd 100644 --- a/scripts/api/entity/playerspaceship.lua +++ b/scripts/api/entity/playerspaceship.lua @@ -34,33 +34,54 @@ function PlayerSpaceship() end --- Returns the coordinates of a waypoint with the given index that's been set by this player ship. ---- Waypoints are 1-indexed. ---- Example: ---- x, y = ship:getWaypoint(1) -function Entity:getWaypoint(index) - if self.components.waypoints and index > 0 and index <= #self.components.waypoints then - local wp = self.components.waypoints[index] - return wp.x, wp.y +--- Waypoints are 1-indexed. Optional set_id (1-4, default 1) selects the waypoint set. +--- Example: +--- x, y = ship:getWaypoint(1) -- wp 1 from set 1 +--- x, y = ship:getWaypoint(1, 2) -- wp 1 from set 2 +function Entity:getWaypoint(index, set_id) + set_id = set_id or 1 + if self.components.waypoints then + local n = 0 + for _, wp in ipairs(self.components.waypoints) do + if wp.set_id == set_id then + n = n + 1 + if n == index then return wp.x, wp.y end + end + end end return 0, 0 end --- Returns the numeric label for the given waypoint index. ---- Waypoints are 1-indexed. ---- Example: ---- id = ship:getWaypointID(1) -function Entity:getWaypointID(index) - if self.components.waypoints and index > 0 and index <= #self.components.waypoints then - local wp = self.components.waypoints[index] - return wp.id +--- Waypoints are 1-indexed. Optional set_id (1-4, default 1) selects the waypoint set. +--- Example: +--- id = ship:getWaypointID(1) -- wp 1 from set 1 +--- id = ship:getWaypointID(1, 2) -- wp 1 from set 2 +function Entity:getWaypointID(index, set_id) + set_id = set_id or 1 + if self.components.waypoints then + local n = 0 + for _, wp in ipairs(self.components.waypoints) do + if wp.set_id == set_id then + n = n + 1 + if n == index then return wp.id end + end + end end return 0 end --- Returns the total number of active waypoints owned by this player ship. +--- Optional set_id (1-4, default 1) selects the waypoint set. --- Example: ---- ship:getWaypointCount() -function Entity:getWaypointCount() - if self.components.waypoints then return #self.components.waypoints end - return 0 +--- ship:getWaypointCount() -- count in set 1 +--- ship:getWaypointCount(2) -- count in set 2 +function Entity:getWaypointCount(set_id) + set_id = set_id or 1 + if not self.components.waypoints then return 0 end + local count = 0 + for _, wp in ipairs(self.components.waypoints) do + if wp.set_id == set_id then count = count + 1 end + end + return count end --- Returns this player ship's EAlertLevel. --- Returns "Normal", "YELLOW ALERT", "RED ALERT", which differ from the valid values for commandSetAlertLevel(). @@ -610,27 +631,40 @@ function Entity:commandSetShieldFrequency(index) return self end --- Commands this player ship to add a waypoint at the given coordinates. ---- This respects the 9-waypoint limit and won't add more waypoints if 9 already exist. +--- This respects the 9-waypoint limit per set and won't add more waypoints if 9 already exist. +--- Optional set_id (1-4, default 1) selects the waypoint set. +--- Example: +--- ship:commandAddWaypoint(1000, 2000) -- add wp to set 1 +--- ship:commandAddWaypoint(1000, 2000, 2) -- add wp to set 2 +function Entity:commandAddWaypoint(x, y, set_id) + commandAddWaypoint(self, x, y, set_id or 1) + return self +end +--- Commands this player ship to remove the waypoint with the given ID. +--- Optional set_id (1-4, default 1) selects the waypoint set. --- Example: ---- ship:commandAddWaypoint(1000, 2000) -function Entity:commandAddWaypoint(x, y) - commandAddWaypoint(self, x, y) +--- ship:commandRemoveWaypoint(1) -- remove wp 1 from set 1 +--- ship:commandRemoveWaypoint(1, 2) -- remove wp 1 from set 2 +function Entity:commandRemoveWaypoint(index, set_id) + commandRemoveWaypoint(self, index, set_id or 1) return self end ---- Commands this player ship to remove the waypoint with the given index. ---- This uses a 0-index, while waypoints are numbered on player screens with a 1-index. +--- Commands this player ship to move the waypoint with the given ID to the given coordinates. +--- Optional set_id (1-4, default 1) selects the waypoint set. --- Example: ---- ship:commandRemoveWaypoint(0) -- removes waypoint 1 -function Entity:commandRemoveWaypoint(index) - commandRemoveWaypoint(self, index) +--- ship:commandMoveWaypoint(1, -1000, -2000) -- move wp 1 from set 1 +--- ship:commandMoveWaypoint(1, -1000, -2000, 2) -- move wp 1 from set 2 +function Entity:commandMoveWaypoint(index, x, y, set_id) + commandMoveWaypoint(self, index, x, y, set_id or 1) return self end ---- Commands this player ship to move the waypoint with the given index to the given coordinates. ---- This uses a 0-index, while waypoints are numbered on player screens with a 1-index. +--- Commands this player ship to set or clear the route flag for a waypoint set. +--- Optional set_id (1-4, default 1) selects the waypoint set. --- Example: ---- ship:commandMoveWaypoint(0,-1000,-2000) -- moves waypoint 1 to -1000,-2000 -function Entity:commandMoveWaypoint(index, x, y) - commandMoveWaypoint(self, index, x, y) +--- ship:commandSetWaypointRoute(true) -- make set 1 a route +--- ship:commandSetWaypointRoute(false, 2) -- remove routes from set 2 +function Entity:commandSetWaypointRoute(is_route, set_id) + commandSetWaypointRoute(self, is_route, set_id or 1) return self end --- Commands this player ship to activate its self-destruct sequence. diff --git a/src/components/player.cpp b/src/components/player.cpp index 7bc6346eeb..6e5a1273bc 100644 --- a/src/components/player.cpp +++ b/src/components/player.cpp @@ -28,17 +28,22 @@ string alertLevelToLocaleString(AlertLevel level) } } -int Waypoints::addNew(glm::vec2 position) +int Waypoints::addNew(glm::vec2 position, int set_id) { - if (waypoints.size() == 9) + if (set_id < 1 || set_id > MAX_SETS) return -1; + int count = 0; + for (auto& p : waypoints) + if (p.set_id == set_id) count++; + if (count >= 9) return -1; - for(int id=1; id<10; id++) { + for (int id = 1; id < 10; id++) + { bool used = false; - for(auto& p : waypoints) - if (p.id == id) - used = true; - if (!used) { - waypoints.push_back({id, position}); + for (auto& p : waypoints) + if (p.id == id && p.set_id == set_id) used = true; + if (!used) + { + waypoints.push_back({id, set_id, position}); dirty = true; return id; } @@ -46,10 +51,12 @@ int Waypoints::addNew(glm::vec2 position) return -1; } -void Waypoints::move(int id, glm::vec2 position) +void Waypoints::move(int id, glm::vec2 position, int set_id) { - for(auto& p : waypoints) { - if (p.id == id) { + for (auto& p : waypoints) + { + if (p.id == id && p.set_id == set_id) + { p.position = position; dirty = true; return; @@ -57,16 +64,22 @@ void Waypoints::move(int id, glm::vec2 position) } } -void Waypoints::remove(int id) +void Waypoints::remove(int id, int set_id) { - waypoints.erase(std::remove_if(waypoints.begin(), waypoints.end(), [id](auto& p) { return p.id == id; }), waypoints.end()); + waypoints.erase(std::remove_if(waypoints.begin(), waypoints.end(), [id, set_id](auto& p) { return p.id == id && p.set_id == set_id; }), waypoints.end()); dirty = true; } -std::optional Waypoints::get(int id) +std::optional Waypoints::get(int id, int set_id) { - for(auto& p : waypoints) - if (p.id == id) - return p.position; + for (auto& p : waypoints) + if (p.id == id && p.set_id == set_id) return p.position; return {}; +} + +void Waypoints::setRoute(bool value, int set_id) +{ + if (set_id < 1 || set_id > MAX_SETS) return; + is_route[set_id - 1] = value; + dirty = true; } \ No newline at end of file diff --git a/src/components/player.h b/src/components/player.h index a389272fb0..2540e2c949 100644 --- a/src/components/player.h +++ b/src/components/player.h @@ -1,5 +1,6 @@ #pragma once +#include #include "stringImproved.h" #include "crewPosition.h" @@ -50,17 +51,22 @@ class PlayerControl class Waypoints { public: + static constexpr int MAX_SETS = 4; bool dirty = true; + // IDs are 1-9 within sets 1-4 struct Point { int id; + int set_id; glm::vec2 position; }; std::vector waypoints; + std::array is_route{}; - int addNew(glm::vec2 position); - void move(int id, glm::vec2 position); - void remove(int id); - std::optional get(int id); + int addNew(glm::vec2 position, int set_id = 1); + void move(int id, glm::vec2 position, int set_id = 1); + void remove(int id, int set_id = 1); + std::optional get(int id, int set_id = 1); + void setRoute(bool value, int set_id = 1); }; string alertLevelToString(AlertLevel level); diff --git a/src/gameGlobalInfo.cpp b/src/gameGlobalInfo.cpp index cdf517710f..d156a007ae 100644 --- a/src/gameGlobalInfo.cpp +++ b/src/gameGlobalInfo.cpp @@ -31,6 +31,8 @@ GameGlobalInfo::GameGlobalInfo() hacking_games = HG_All; use_beam_shield_frequencies = true; use_system_damage = true; + enable_multiple_waypoint_sets = false; + enable_waypoint_routes = false; allow_main_screen_tactical_radar = true; allow_main_screen_long_range_radar = true; allow_main_screen_strategic_map = true; @@ -48,6 +50,8 @@ GameGlobalInfo::GameGlobalInfo() registerMemberReplication(&victory_faction); registerMemberReplication(&use_beam_shield_frequencies); registerMemberReplication(&use_system_damage); + registerMemberReplication(&enable_multiple_waypoint_sets); + registerMemberReplication(&enable_waypoint_routes); registerMemberReplication(&allow_main_screen_tactical_radar); registerMemberReplication(&allow_main_screen_long_range_radar); registerMemberReplication(&allow_main_screen_strategic_map); diff --git a/src/gameGlobalInfo.h b/src/gameGlobalInfo.h index 62b02343f8..b5b46a0c09 100644 --- a/src/gameGlobalInfo.h +++ b/src/gameGlobalInfo.h @@ -52,6 +52,8 @@ class GameGlobalInfo : public MultiplayerObject, public Updatable EHackingGames hacking_games; bool use_beam_shield_frequencies; bool use_system_damage; + bool enable_multiple_waypoint_sets; + bool enable_waypoint_routes; bool allow_main_screen_tactical_radar; bool allow_main_screen_long_range_radar; bool allow_main_screen_strategic_map; diff --git a/src/gui/hotkeyConfig.cpp b/src/gui/hotkeyConfig.cpp index e0857f423b..ec739b8496 100644 --- a/src/gui/hotkeyConfig.cpp +++ b/src/gui/hotkeyConfig.cpp @@ -318,6 +318,7 @@ Keys::Keys() : gm_delete("GM_DELETE", "Delete"), gm_clipboardcopy("GM_CLIPBOARD_COPY", "F5"), gm_show_callsigns("GM_SHOW_CALLSIGNS", "C"), + gm_show_waypoints("GM_SHOW_WAYPOINTS", "W"), // Spectator screen spectator_show_callsigns("SPECTATOR_SHOW_CALLSIGNS", "C") @@ -513,6 +514,7 @@ void Keys::init() gm_delete.setLabel(tr("hotkey_menu", "GM screen"), tr("hotkey_GM", "Delete")); gm_clipboardcopy.setLabel(tr("hotkey_menu", "GM screen"), tr("hotkey_GM", "Copy to clipboard")); gm_show_callsigns.setLabel(tr("hotkey_menu", "GM screen"), tr("hotkey_GM", "Show callsigns (GM)")); + gm_show_waypoints.setLabel(tr("hotkey_menu", "GM screen"), tr("hotkey_GM", "Show waypoints (GM)")); // Spectator screen spectator_show_callsigns.setLabel(tr("hotkey_menu", "Spectator view"), tr("hotkey_Spectator", "Show callsigns (spectator)")); diff --git a/src/gui/hotkeyConfig.h b/src/gui/hotkeyConfig.h index 67d2172338..591df870a6 100644 --- a/src/gui/hotkeyConfig.h +++ b/src/gui/hotkeyConfig.h @@ -200,6 +200,7 @@ class Keys sp::io::Keybinding gm_delete; sp::io::Keybinding gm_clipboardcopy; sp::io::Keybinding gm_show_callsigns; + sp::io::Keybinding gm_show_waypoints; // Spectator screen binds sp::io::Keybinding spectator_show_callsigns; diff --git a/src/menus/shipSelectionScreen.cpp b/src/menus/shipSelectionScreen.cpp index a3325a8fc2..9345615eff 100644 --- a/src/menus/shipSelectionScreen.cpp +++ b/src/menus/shipSelectionScreen.cpp @@ -338,7 +338,10 @@ ShipSelectionScreen::ShipSelectionScreen() if (game_server) { auto extra_settings_panel = new GuiPanel(this, ""); - extra_settings_panel->setSize(600, 325)->setPosition(0, 0, sp::Alignment::Center)->hide(); + extra_settings_panel + ->setSize(600.0f, 375.0f) + ->setPosition(0.0f, 0.0f, sp::Alignment::Center) + ->hide(); auto extra_settings = new GuiElement(extra_settings_panel, ""); extra_settings->setSize(GuiElement::GuiSizeMax, GuiElement::GuiSizeMax)->setMargins(25)->setAttribute("layout", "vertical"); // Science scan complexity selector. @@ -376,6 +379,30 @@ ShipSelectionScreen::ShipSelectionScreen() gameGlobalInfo->use_system_damage = value == 1; }))->setValue(gameGlobalInfo->use_system_damage)->setSize(275, GuiElement::GuiSizeMax)->setPosition(0, 0, sp::Alignment::CenterRight); + // Waypoint settings row. + row = new GuiElement(extra_settings, ""); + row + ->setSize(GuiElement::GuiSizeMax, 50.0f) + ->setAttribute("layout", "horizontal"); + + (new GuiToggleButton(row, "GAME_MULTI_WP_SETS_TOGGLE", tr("Multiple waypoint sets"), + [](bool value) + { + gameGlobalInfo->enable_multiple_waypoint_sets = value == 1; + } + ))->setValue(gameGlobalInfo->enable_multiple_waypoint_sets) + ->setSize(275.0f, GuiElement::GuiSizeMax) + ->setPosition(0.0f, 0.0f, sp::Alignment::CenterLeft); + + (new GuiToggleButton(row, "GAME_WP_ROUTES_TOGGLE", tr("Waypoint routes"), + [](bool value) + { + gameGlobalInfo->enable_waypoint_routes = value == 1; + } + ))->setValue(gameGlobalInfo->enable_waypoint_routes) + ->setSize(275.0f, GuiElement::GuiSizeMax) + ->setPosition(0.0f, 0.0f, sp::Alignment::CenterRight); + auto close_button = new GuiButton(extra_settings_panel, "", tr("Close"), [this, extra_settings_panel](){ extra_settings_panel->hide(); container->show(); diff --git a/src/multiplayer/player.cpp b/src/multiplayer/player.cpp index ea0bd81a98..f883399991 100644 --- a/src/multiplayer/player.cpp +++ b/src/multiplayer/player.cpp @@ -2,8 +2,24 @@ #include "multiplayer.h" namespace sp::io { - static inline DataBuffer& operator << (DataBuffer& packet, const Waypoints::Point& p) { return packet << p.id << p.position; } - static inline DataBuffer& operator >> (DataBuffer& packet, Waypoints::Point& p) { packet >> p.id >> p.position; return packet; } + static inline DataBuffer& operator << (DataBuffer& packet, const Waypoints::Point& p) { return packet << p.id << p.set_id << p.position; } + static inline DataBuffer& operator >> (DataBuffer& packet, Waypoints::Point& p) { packet >> p.id >> p.set_id >> p.position; return packet; } + + static inline DataBuffer& operator << (DataBuffer& packet, const std::array& arr) + { + uint8_t bits = 0; + for (int i = 0; i < Waypoints::MAX_SETS; i++) + if (arr[i]) bits |= (1 << i); + return packet << bits; + } + static inline DataBuffer& operator >> (DataBuffer& packet, std::array& arr) + { + uint8_t bits; + packet >> bits; + for (int i = 0; i < Waypoints::MAX_SETS; i++) + arr[i] = (bits >> i) & 1; + return packet; + } } @@ -18,4 +34,5 @@ BASIC_REPLICATION_IMPL(PlayerControlReplication, PlayerControl) BASIC_REPLICATION_IMPL(WaypointsReplication, Waypoints) REPLICATE_VECTOR_IF_DIRTY(waypoints, dirty); + BASIC_REPLICATION_FIELD(is_route); } diff --git a/src/playerInfo.cpp b/src/playerInfo.cpp index 54ef190b6d..27f699bccb 100644 --- a/src/playerInfo.cpp +++ b/src/playerInfo.cpp @@ -104,6 +104,7 @@ static const uint16_t CMD_CUSTOM_FUNCTION = 0x0029; static const uint16_t CMD_TURN_SPEED = 0x002A; static const uint16_t CMD_CREW_SET_TARGET = 0x002B; static const uint16_t CMD_ABORT_JUMP = 0x002C; +static const uint16_t CMD_SET_WAYPOINT_ROUTE = 0x002D; //Pre-ship commands static const uint16_t CMD_UPDATE_CREW_POSITION = 0x0101; @@ -406,24 +407,31 @@ void PlayerInfo::commandSetShieldFrequency(int32_t frequency) sendClientCommand(packet); } -void PlayerInfo::commandAddWaypoint(glm::vec2 position) +void PlayerInfo::commandAddWaypoint(glm::vec2 position, int32_t set_id) { sp::io::DataBuffer packet; - packet << CMD_ADD_WAYPOINT << position; + packet << CMD_ADD_WAYPOINT << position << set_id; sendClientCommand(packet); } -void PlayerInfo::commandRemoveWaypoint(int32_t index) +void PlayerInfo::commandRemoveWaypoint(int32_t index, int32_t set_id) { sp::io::DataBuffer packet; - packet << CMD_REMOVE_WAYPOINT << index; + packet << CMD_REMOVE_WAYPOINT << index << set_id; sendClientCommand(packet); } -void PlayerInfo::commandMoveWaypoint(int32_t index, glm::vec2 position) +void PlayerInfo::commandMoveWaypoint(int32_t index, glm::vec2 position, int32_t set_id) { sp::io::DataBuffer packet; - packet << CMD_MOVE_WAYPOINT << index << position; + packet << CMD_MOVE_WAYPOINT << index << position << set_id; + sendClientCommand(packet); +} + +void PlayerInfo::commandSetWaypointRoute(bool is_route, int32_t set_id) +{ + sp::io::DataBuffer packet; + packet << CMD_SET_WAYPOINT_ROUTE << is_route << set_id; sendClientCommand(packet); } @@ -881,19 +889,21 @@ void PlayerInfo::onReceiveClientCommand(int32_t client_id, sp::io::DataBuffer& p case CMD_ADD_WAYPOINT: { glm::vec2 position{}; - packet >> position; + int32_t set_id = 1; + packet >> position >> set_id; auto wp = ship.getComponent(); if (wp) { - wp->addNew(position); + wp->addNew(position, set_id); } } break; case CMD_REMOVE_WAYPOINT: { int32_t id; - packet >> id; + int32_t set_id = 1; + packet >> id >> set_id; if (auto wp = ship.getComponent()) { - wp->remove(id); + wp->remove(id, set_id); } } break; @@ -901,9 +911,20 @@ void PlayerInfo::onReceiveClientCommand(int32_t client_id, sp::io::DataBuffer& p { int32_t id; glm::vec2 position{}; - packet >> id >> position; + int32_t set_id = 1; + packet >> id >> position >> set_id; + if (auto wp = ship.getComponent()) { + wp->move(id, position, set_id); + } + } + break; + case CMD_SET_WAYPOINT_ROUTE: + { + bool is_route; + int32_t set_id = 1; + packet >> is_route >> set_id; if (auto wp = ship.getComponent()) { - wp->move(id, position); + wp->setRoute(is_route, set_id); } } break; diff --git a/src/playerInfo.h b/src/playerInfo.h index 1161669f2b..b3b60c7156 100644 --- a/src/playerInfo.h +++ b/src/playerInfo.h @@ -66,9 +66,10 @@ class PlayerInfo : public MultiplayerObject void commandSetBeamFrequency(int32_t frequency); void commandSetBeamSystemTarget(ShipSystem::Type system); void commandSetShieldFrequency(int32_t frequency); - void commandAddWaypoint(glm::vec2 position); - void commandRemoveWaypoint(int32_t index); - void commandMoveWaypoint(int32_t index, glm::vec2 position); + void commandAddWaypoint(glm::vec2 position, int32_t set_id = 1); + void commandRemoveWaypoint(int32_t index, int32_t set_id = 1); + void commandMoveWaypoint(int32_t index, glm::vec2 position, int32_t set_id = 1); + void commandSetWaypointRoute(bool is_route, int32_t set_id); void commandActivateSelfDestruct(); void commandCancelSelfDestruct(); void commandConfirmDestructCode(int8_t index, uint32_t code); diff --git a/src/screenComponents/radarView.cpp b/src/screenComponents/radarView.cpp index b7cd948856..b4f32b14c8 100644 --- a/src/screenComponents/radarView.cpp +++ b/src/screenComponents/radarView.cpp @@ -28,6 +28,7 @@ #include "radarView.h" #include "missileTubeControls.h" #include "targetsContainer.h" +#include "components/name.h" #include "gui/theme.h" @@ -81,7 +82,16 @@ GuiRadarView::GuiRadarView(GuiContainer* owner, string id, TargetsContainer* tar radar_sector_grid_style(theme->getStyle("radar.sector_grid")), ship_waypoint_style(theme->getStyle("ship_waypoint")), ship_waypoint_background_style(theme->getStyle("ship_waypoint.background")), - ship_waypoint_text_style(theme->getStyle("ship_waypoint.text")) + ship_waypoint_text_style(theme->getStyle("ship_waypoint.text")), + ship_waypoint_set2_style(theme->getStyle("ship_waypoint_set2")), + ship_waypoint_set2_background_style(theme->getStyle("ship_waypoint_set2.background")), + ship_waypoint_set2_text_style(theme->getStyle("ship_waypoint_set2.text")), + ship_waypoint_set3_style(theme->getStyle("ship_waypoint_set3")), + ship_waypoint_set3_background_style(theme->getStyle("ship_waypoint_set3.background")), + ship_waypoint_set3_text_style(theme->getStyle("ship_waypoint_set3.text")), + ship_waypoint_set4_style(theme->getStyle("ship_waypoint_set4")), + ship_waypoint_set4_background_style(theme->getStyle("ship_waypoint_set4.background")), + ship_waypoint_set4_text_style(theme->getStyle("ship_waypoint_set4.text")) { } @@ -117,7 +127,16 @@ GuiRadarView::GuiRadarView(GuiContainer* owner, string id, float distance, Targe radar_sector_grid_style(theme->getStyle("radar.sector_grid")), ship_waypoint_style(theme->getStyle("ship_waypoint")), ship_waypoint_background_style(theme->getStyle("ship_waypoint.background")), - ship_waypoint_text_style(theme->getStyle("ship_waypoint.text")) + ship_waypoint_text_style(theme->getStyle("ship_waypoint.text")), + ship_waypoint_set2_style(theme->getStyle("ship_waypoint_set2")), + ship_waypoint_set2_background_style(theme->getStyle("ship_waypoint_set2.background")), + ship_waypoint_set2_text_style(theme->getStyle("ship_waypoint_set2.text")), + ship_waypoint_set3_style(theme->getStyle("ship_waypoint_set3")), + ship_waypoint_set3_background_style(theme->getStyle("ship_waypoint_set3.background")), + ship_waypoint_set3_text_style(theme->getStyle("ship_waypoint_set3.text")), + ship_waypoint_set4_style(theme->getStyle("ship_waypoint_set4")), + ship_waypoint_set4_background_style(theme->getStyle("ship_waypoint_set4.background")), + ship_waypoint_set4_text_style(theme->getStyle("ship_waypoint_set4.text")) { } @@ -264,7 +283,11 @@ void GuiRadarView::onDraw(sp::RenderTarget& renderer) glStencilFunc(GL_EQUAL, as_mask(RadarStencil::RadarBounds), as_mask(RadarStencil::RadarBounds)); // Always draw waypoints, if enabled. - if (show_waypoints) drawWaypoints(renderer); + if (show_waypoints) + { + if (show_game_master_data) drawAllPlayerWaypoints(renderer); + else drawWaypoints(renderer); + } // Always draw heading indicators, if enabled. if (show_heading_indicators) drawHeadingIndicators(renderer); @@ -485,38 +508,127 @@ void GuiRadarView::drawGhostDots(sp::RenderTarget& renderer) } } -void GuiRadarView::drawWaypoints(sp::RenderTarget& renderer) +void GuiRadarView::drawWaypointSetForShip(sp::RenderTarget& renderer, Waypoints* waypoints, int set_id, + const GuiThemeStyle* sprite_style, const GuiThemeStyle* bg_style, const GuiThemeStyle* text_style, + bool show_route, const string& owner_label, glm::u8vec4 owner_label_color) { - auto waypoints = my_spaceship.getComponent(); - if (!waypoints) - return; + const auto& waypoint_sprite = sprite_style->get(getState()).texture; + const auto& waypoint_color = bg_style->get(getState()).color; + const auto& waypoint_text_style = text_style->get(getState()); + auto font = waypoint_text_style.font; + if (!font) font = bold_font; glm::vec2 radar_screen_center(rect.position.x + rect.size.x / 2.0f, rect.position.y + rect.size.y / 2.0f); - for(unsigned int n=0; nwaypoints.size(); n++) + // Draw route line if enabled + if (show_route && waypoints->is_route[set_id - 1]) { - auto screen_position = worldToScreen(waypoints->waypoints[n].position); + std::vector set_points; - const auto& waypoint_sprite = ship_waypoint_style->get(getState()).texture; - const auto& waypoint_color = ship_waypoint_background_style->get(getState()).color; - const auto& waypoint_text_style = ship_waypoint_text_style->get(getState()); - auto font = waypoint_text_style.font; - // Fallback to bold font - if (!font) font = bold_font; + for (auto& p : waypoints->waypoints) + if (p.set_id == set_id) set_points.push_back(&p); + + std::sort(set_points.begin(), set_points.end(), + [](auto* a, auto* b) + { + return a->id < b->id; + } + ); + glm::u8vec4 route_color(waypoint_color.r, waypoint_color.g, waypoint_color.b, waypoint_color.a / 2); + for (size_t i = 1; i < set_points.size(); i++) + { + auto p0 = worldToScreen(set_points[i - 1]->position); + auto p1 = worldToScreen(set_points[i]->position); + renderer.drawLine(p0, p1, route_color); + } + } + + // Draw waypoint sprites + for (auto& wp : waypoints->waypoints) + { + if (wp.set_id != set_id) continue; + auto screen_position = worldToScreen(wp.position); + glm::u8vec4 label_color = (owner_label_color.a != 0) ? owner_label_color : waypoint_text_style.color; renderer.drawSprite(waypoint_sprite, screen_position - glm::vec2(0, 10), 20, waypoint_color); - renderer.drawText(sp::Rect(screen_position.x, screen_position.y - 10, 0, 0), string(waypoints->waypoints[n].id), sp::Alignment::Center, 14, font, waypoint_text_style.color); + renderer.drawText(sp::Rect(screen_position.x, screen_position.y - 10, 0, 0), string(wp.id), sp::Alignment::Center, 14, font, waypoint_text_style.color); + if (!owner_label.empty()) + renderer.drawText(sp::Rect(screen_position.x, screen_position.y + 4.0f, 0.0f, 0.0f), owner_label, sp::Alignment::Center, 14, font, label_color); if (style != Rectangular && glm::length(screen_position - radar_screen_center) > std::min(rect.size.x, rect.size.y) * 0.5f) { screen_position = radar_screen_center + ((screen_position - radar_screen_center) / glm::length(screen_position - radar_screen_center) * std::min(rect.size.x, rect.size.y) * 0.4f); - renderer.drawRotatedSprite(waypoint_sprite, screen_position, 20, vec2ToAngle(screen_position - radar_screen_center) - 90, waypoint_color); - renderer.drawText(sp::Rect(screen_position.x, screen_position.y, 0, 0), string(waypoints->waypoints[n].id), sp::Alignment::Center, 14, font, waypoint_text_style.color); + renderer.drawText(sp::Rect(screen_position.x, screen_position.y, 0, 0), string(wp.id), sp::Alignment::Center, 14, font, waypoint_text_style.color); + if (!owner_label.empty()) + renderer.drawText(sp::Rect(screen_position.x, screen_position.y + 14.0f, 0.0f, 0.0f), owner_label, sp::Alignment::Center, 14, font, label_color); } } } +void GuiRadarView::drawWaypoints(sp::RenderTarget& renderer) +{ + auto waypoints = my_spaceship.getComponent(); + if (!waypoints) return; + + bool show_route = gameGlobalInfo && gameGlobalInfo->enable_waypoint_routes; + int max_sets = (gameGlobalInfo && gameGlobalInfo->enable_multiple_waypoint_sets) ? Waypoints::MAX_SETS : 1; + + const GuiThemeStyle* bg_styles[Waypoints::MAX_SETS] = { + ship_waypoint_background_style, + ship_waypoint_set2_background_style, + ship_waypoint_set3_background_style, + ship_waypoint_set4_background_style, + }; + const GuiThemeStyle* sprite_styles[Waypoints::MAX_SETS] = { + ship_waypoint_style, + ship_waypoint_set2_style, + ship_waypoint_set3_style, + ship_waypoint_set4_style, + }; + const GuiThemeStyle* text_styles[Waypoints::MAX_SETS] = { + ship_waypoint_text_style, + ship_waypoint_set2_text_style, + ship_waypoint_set3_text_style, + ship_waypoint_set4_text_style, + }; + + for (int s = 1; s <= max_sets; s++) + drawWaypointSetForShip(renderer, waypoints, s, sprite_styles[s - 1], bg_styles[s - 1], text_styles[s - 1], show_route, ""); +} + +void GuiRadarView::drawAllPlayerWaypoints(sp::RenderTarget& renderer) +{ + bool show_route = gameGlobalInfo && gameGlobalInfo->enable_waypoint_routes; + int max_sets = (gameGlobalInfo && gameGlobalInfo->enable_multiple_waypoint_sets) ? Waypoints::MAX_SETS : 1; + + const GuiThemeStyle* bg_styles[Waypoints::MAX_SETS] = { + ship_waypoint_background_style, + ship_waypoint_set2_background_style, + ship_waypoint_set3_background_style, + ship_waypoint_set4_background_style, + }; + const GuiThemeStyle* sprite_styles[Waypoints::MAX_SETS] = { + ship_waypoint_style, + ship_waypoint_set2_style, + ship_waypoint_set3_style, + ship_waypoint_set4_style, + }; + const GuiThemeStyle* text_styles[Waypoints::MAX_SETS] = { + ship_waypoint_text_style, + ship_waypoint_set2_text_style, + ship_waypoint_set3_text_style, + ship_waypoint_set4_text_style, + }; + + constexpr glm::u8vec4 gm_owner_label_color{255, 255, 255, 208}; + for (auto [entity, waypoints, cs] : sp::ecs::Query()) + { + for (int s = 1; s <= max_sets; s++) + drawWaypointSetForShip(renderer, &waypoints, s, sprite_styles[s - 1], bg_styles[s - 1], text_styles[s - 1], show_route, cs.callsign, gm_owner_label_color); + } +} + void GuiRadarView::drawRangeIndicators(sp::RenderTarget& renderer) { if (range_indicator_step_size < 1.0f) @@ -782,7 +894,7 @@ void GuiRadarView::drawTargets(sp::RenderTarget& renderer) auto waypoints = my_spaceship.getComponent(); if (my_spaceship && waypoints && targets->getWaypointIndex() > -1) { - if (auto waypoint_position = waypoints->get(targets->getWaypointIndex())) { + if (auto waypoint_position = waypoints->get(targets->getWaypointIndex(), targets->getWaypointSetId())) { auto object_position_on_screen = worldToScreen(waypoint_position.value()); renderer.drawSprite("redicule.png", object_position_on_screen - glm::vec2{0, 10}, 48); diff --git a/src/screenComponents/radarView.h b/src/screenComponents/radarView.h index 28cb3afa83..e7c4841d0c 100644 --- a/src/screenComponents/radarView.h +++ b/src/screenComponents/radarView.h @@ -6,6 +6,7 @@ class GuiMissileTubeControls; class TargetsContainer; class GuiThemeStyle; +class Waypoints; class GuiRadarView : public GuiElement { @@ -76,6 +77,15 @@ class GuiRadarView : public GuiElement const GuiThemeStyle* ship_waypoint_style; const GuiThemeStyle* ship_waypoint_background_style; const GuiThemeStyle* ship_waypoint_text_style; + const GuiThemeStyle* ship_waypoint_set2_style; + const GuiThemeStyle* ship_waypoint_set2_background_style; + const GuiThemeStyle* ship_waypoint_set2_text_style; + const GuiThemeStyle* ship_waypoint_set3_style; + const GuiThemeStyle* ship_waypoint_set3_background_style; + const GuiThemeStyle* ship_waypoint_set3_text_style; + const GuiThemeStyle* ship_waypoint_set4_style; + const GuiThemeStyle* ship_waypoint_set4_background_style; + const GuiThemeStyle* ship_waypoint_set4_text_style; public: GuiRadarView(GuiContainer* owner, string id, TargetsContainer* targets); GuiRadarView(GuiContainer* owner, string id, float distance, TargetsContainer* targets); @@ -91,6 +101,7 @@ class GuiRadarView : public GuiElement GuiRadarView* disableGhostDots() { show_ghost_dots = false; return this; } GuiRadarView* enableWaypoints() { show_waypoints = true; return this; } GuiRadarView* disableWaypoints() { show_waypoints = false; return this; } + bool getWaypoints() { return show_waypoints; } GuiRadarView* enableTargetProjections(GuiMissileTubeControls* missile_tube_controls) { show_target_projection = true; this->missile_tube_controls = missile_tube_controls; return this; } GuiRadarView* disableTargetProjections() { show_target_projection = false; return this; } GuiRadarView* enableMissileTubeIndicators() { show_missile_tubes = true; return this; } @@ -138,6 +149,8 @@ class GuiRadarView : public GuiElement void drawFriendlyNotVisibleAreas(sp::RenderTarget& target); void drawGhostDots(sp::RenderTarget& target); void drawWaypoints(sp::RenderTarget& target); + void drawAllPlayerWaypoints(sp::RenderTarget& target); + void drawWaypointSetForShip(sp::RenderTarget& renderer, Waypoints* waypoints, int set_id, const GuiThemeStyle* sprite_style, const GuiThemeStyle* bg_style, const GuiThemeStyle* text_style, bool show_route, const string& owner_label = "", glm::u8vec4 owner_label_color = {0,0,0,0}); void drawRangeIndicators(sp::RenderTarget& target); void drawTargetProjections(sp::RenderTarget& target); void drawMissileTubes(sp::RenderTarget& target); diff --git a/src/screenComponents/targetsContainer.cpp b/src/screenComponents/targetsContainer.cpp index 303a6aa822..c99f5d15e1 100644 --- a/src/screenComponents/targetsContainer.cpp +++ b/src/screenComponents/targetsContainer.cpp @@ -11,12 +11,14 @@ TargetsContainer::TargetsContainer() { waypoint_selection_index = -1; + waypoint_selection_set_id = 1; allow_waypoint_selection = false; } void TargetsContainer::clear() { waypoint_selection_index = -1; + waypoint_selection_set_id = 1; entries.clear(); } @@ -41,11 +43,13 @@ void TargetsContainer::set(sp::ecs::Entity obj) clear(); } waypoint_selection_index = -1; + waypoint_selection_set_id = 1; } void TargetsContainer::set(const std::vector& objs) { waypoint_selection_index = -1; + waypoint_selection_set_id = 1; entries = objs; } @@ -89,6 +93,7 @@ void TargetsContainer::setToClosestTo(glm::vec2 position, float max_range, ESele { clear(); waypoint_selection_index = waypoints->waypoints[n].id; + waypoint_selection_set_id = waypoints->waypoints[n].set_id; return; } } @@ -101,16 +106,24 @@ void TargetsContainer::setToClosestTo(glm::vec2 position, float max_range, ESele 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_set_id)) waypoint_selection_index = -1; return waypoint_selection_index; } -void TargetsContainer::setWaypointIndex(int index) +int TargetsContainer::getWaypointSetId() +{ + return waypoint_selection_set_id; +} + +void TargetsContainer::setWaypointIndex(int index, int set_id) { auto waypoints = my_spaceship.getComponent(); - if (waypoints && waypoints->get(index)) + if (waypoints && waypoints->get(index, set_id)) + { waypoint_selection_index = index; + waypoint_selection_set_id = set_id; + } } void TargetsContainer::setNext(glm::vec2 position, float max_range, ESelectionType selection_type) diff --git a/src/screenComponents/targetsContainer.h b/src/screenComponents/targetsContainer.h index 3e792e1037..3945474883 100644 --- a/src/screenComponents/targetsContainer.h +++ b/src/screenComponents/targetsContainer.h @@ -25,7 +25,8 @@ class TargetsContainer std::vector getTargets(); sp::ecs::Entity get(); int getWaypointIndex(); - void setWaypointIndex(int index); + int getWaypointSetId(); + void setWaypointIndex(int index, int set_id = 1); void setToClosestTo(glm::vec2 position, float max_range, ESelectionType selection_type); void setNext(glm::vec2 position, float max_range, ESelectionType selection_type); @@ -35,6 +36,7 @@ class TargetsContainer std::vector entries; bool allow_waypoint_selection; int waypoint_selection_index; + int waypoint_selection_set_id; void setNext(glm::vec2 position, float max_range, std::vector& entities); void sortByDistance(glm::vec2 position, std::vector& entities); diff --git a/src/screens/crew4/operationsScreen.cpp b/src/screens/crew4/operationsScreen.cpp index a09adb6c99..ccae3246b4 100644 --- a/src/screens/crew4/operationsScreen.cpp +++ b/src/screens/crew4/operationsScreen.cpp @@ -6,6 +6,7 @@ #include "gui/gui2_keyvaluedisplay.h" #include "gui/gui2_togglebutton.h" +#include "gui/gui2_selector.h" #include "components/scanning.h" #include "components/radar.h" @@ -28,21 +29,23 @@ OperationScreen::OperationScreen(GuiContainer* owner) [this](sp::io::Pointer::Button button, glm::vec2 position) { // Down // If not our ship, or if we're scanning, ignore clicks. auto science_scanner = my_spaceship.getComponent(); - if (science_scanner && science_scanner->delay > 0.0f) - return; + if (science_scanner && science_scanner->delay > 0.0f) return; // If we're in target selection mode, there's a waypoint, and this // is our ship... if (mode == TargetSelection && science->targets.getWaypointIndex() > -1 && my_spaceship) { - if (auto waypoints = my_spaceship.getComponent()) { + if (auto waypoints = my_spaceship.getComponent()) + { // ... and we select something near a waypoint, switch to move // waypoint mode. - if (auto waypoint_position = waypoints->get(science->targets.getWaypointIndex())) { + if (auto waypoint_position = waypoints->get(science->targets.getWaypointIndex(), science->targets.getWaypointSetId())) + { if (glm::length(waypoint_position.value() - position) < 1000.0f) { mode = MoveWaypoint; drag_waypoint_index = science->targets.getWaypointIndex(); + drag_waypoint_set = science->targets.getWaypointSetId(); } } } @@ -52,7 +55,7 @@ OperationScreen::OperationScreen(GuiContainer* owner) [this](glm::vec2 position) { // Drag // If we're dragging a waypoint, move it. if (mode == MoveWaypoint && my_spaceship) - my_player_info->commandMoveWaypoint(drag_waypoint_index, position); + my_player_info->commandMoveWaypoint(drag_waypoint_index, position, drag_waypoint_set); }, [this](glm::vec2 position) { // Up switch(mode) @@ -62,13 +65,13 @@ OperationScreen::OperationScreen(GuiContainer* owner) break; case WaypointPlacement: if (my_spaceship) - my_player_info->commandAddWaypoint(position); + my_player_info->commandAddWaypoint(position, active_waypoint_set); mode = TargetSelection; place_waypoint_button->setValue(false); break; case MoveWaypoint: mode = TargetSelection; - science->targets.setWaypointIndex(drag_waypoint_index); + science->targets.setWaypointIndex(drag_waypoint_index, drag_waypoint_set); break; } }, @@ -78,7 +81,35 @@ OperationScreen::OperationScreen(GuiContainer* owner) ); science->science_radar->setAutoRotating(PreferencesManager::get("operations_radar_lock","0")=="1"); - // Limited relay functions: comms and waypoints. + // Left column: waypoint set selector and route toggle. + auto waypoint_set_controls = new GuiElement(science->radar_view, "WAYPOINT_SET_CONTROLS"); + waypoint_set_controls + ->setPosition(-480.0f, -20.0f, sp::Alignment::BottomRight) + ->setSize(200.0f, 100.0f) + ->setAttribute("layout", "verticalbottom"); + + // Waypoint set selector, shown only when multiple sets are enabled. + waypoint_set_selector = new GuiSelector(waypoint_set_controls, "WAYPOINT_SET_SELECTOR", + [this](int index, string value) + { + active_waypoint_set = index + 1; + } + ); + waypoint_set_selector + ->setOptions({tr("Waypoint set 1"), tr("Waypoint set 2"), tr("Waypoint set 3"), tr("Waypoint set 4")}) + ->setSelectionIndex(0) + ->setSize(GuiElement::GuiSizeMax, 50.0f); + + // Route toggle, shown only when waypoint routes are enabled. + route_toggle = new GuiToggleButton(waypoint_set_controls, "WAYPOINT_ROUTE_TOGGLE", tr("Show as route"), + [this](bool value) + { + if (my_spaceship) my_player_info->commandSetWaypointRoute(value, active_waypoint_set); + } + ); + route_toggle->setSize(200.0f, 50.0f); + + // Right column: comms and waypoint placement/deletion. GuiElement* relay_functions = new GuiElement(science->radar_view, "RELAY_FUNCTIONS"); relay_functions ->setPosition(-270.0f, -20.0f, sp::Alignment::BottomRight) @@ -99,7 +130,7 @@ OperationScreen::OperationScreen(GuiContainer* owner) [this]() { if (my_spaceship && science->targets.getWaypointIndex() >= 0) - my_player_info->commandRemoveWaypoint(science->targets.getWaypointIndex()); + my_player_info->commandRemoveWaypoint(science->targets.getWaypointIndex(), science->targets.getWaypointSetId()); } ); delete_waypoint_button->setSize(200.0f, 50.0f); @@ -112,7 +143,6 @@ OperationScreen::OperationScreen(GuiContainer* owner) info_reputation->setTextSize(20)->setSize(200, 40); // Scenario clock display. - info_clock = new GuiKeyValueDisplay(stats, "INFO_CLOCK", 0.55f, tr("Clock") + ":", ""); info_clock->setTextSize(20)->setSize(200, 40); @@ -139,4 +169,21 @@ void OperationScreen::onDraw(sp::RenderTarget& target) info_reputation->hide(); info_clock->hide(); } + + // Show/hide waypoint set selector and route toggle + waypoint_set_selector->setVisible(gameGlobalInfo->enable_multiple_waypoint_sets); + route_toggle->setVisible(gameGlobalInfo->enable_waypoint_routes); + + if (!gameGlobalInfo->enable_multiple_waypoint_sets && active_waypoint_set != 1) + { + active_waypoint_set = 1; + waypoint_set_selector->setSelectionIndex(0); + } + + // Sync route toggle + if (gameGlobalInfo->enable_waypoint_routes) + { + if (auto wp = my_spaceship.getComponent()) + route_toggle->setValue(wp->is_route[active_waypoint_set - 1]); + } } diff --git a/src/screens/crew4/operationsScreen.h b/src/screens/crew4/operationsScreen.h index 55243b217d..33b0bfb88f 100644 --- a/src/screens/crew4/operationsScreen.h +++ b/src/screens/crew4/operationsScreen.h @@ -2,9 +2,11 @@ #include "gui/gui2_overlay.h" +class GuiElement; class GuiOverlay; class GuiKeyValueDisplay; class GuiButton; +class GuiSelector; class GuiToggleButton; class ScienceScreen; @@ -20,6 +22,8 @@ class OperationScreen : public GuiOverlay EMode mode; int drag_waypoint_index; + int drag_waypoint_set; + int active_waypoint_set = 1; ScienceScreen* science; @@ -28,6 +32,8 @@ class OperationScreen : public GuiOverlay GuiToggleButton* place_waypoint_button; GuiButton* delete_waypoint_button; + GuiSelector* waypoint_set_selector; + GuiToggleButton* route_toggle; glm::vec2 mouse_down_position{0, 0}; public: diff --git a/src/screens/crew6/relayScreen.cpp b/src/screens/crew6/relayScreen.cpp index d7bd665eac..f886582314 100644 --- a/src/screens/crew6/relayScreen.cpp +++ b/src/screens/crew6/relayScreen.cpp @@ -43,75 +43,88 @@ RelayScreen::RelayScreen(GuiContainer* owner, bool allow_comms) { targets.setAllowWaypointSelection(); radar = new GuiRadarView(this, "RELAY_RADAR", MAX_ZOOM_DISTANCE, &targets); - radar->longRange()->enableWaypoints()->enableCallsigns()->setStyle(GuiRadarView::Rectangular)->setFogOfWarStyle(GuiRadarView::FriendlysShortRangeFogOfWar); - radar->setAutoCentering(false); - radar->setPosition(0, 0, sp::Alignment::TopLeft)->setSize(GuiElement::GuiSizeMax, GuiElement::GuiSizeMax); - radar->setCallbacks( - [this](sp::io::Pointer::Button button, glm::vec2 position) { //down - if (mode == TargetSelection && targets.getWaypointIndex() > -1) { - if (auto waypoints = my_spaceship.getComponent()) { - if (auto waypoint_position = waypoints->get(targets.getWaypointIndex())) { - if (glm::length(waypoint_position.value() - position) < 1000.0f) { - mode = MoveWaypoint; - drag_waypoint_index = targets.getWaypointIndex(); + radar + ->longRange() + ->enableWaypoints() + ->enableCallsigns() + ->setStyle(GuiRadarView::Rectangular) + ->setFogOfWarStyle(GuiRadarView::FriendlysShortRangeFogOfWar) + ->setAutoCentering(false) + ->setCallbacks( + [this](sp::io::Pointer::Button button, glm::vec2 position) + { // Down + if (mode == TargetSelection && targets.getWaypointIndex() > -1) + { + if (auto waypoints = my_spaceship.getComponent()) + { + if (auto waypoint_position = waypoints->get(targets.getWaypointIndex(), targets.getWaypointSetId())) + { + if (glm::length(waypoint_position.value() - position) < 1000.0f) + { + mode = MoveWaypoint; + drag_waypoint_index = targets.getWaypointIndex(); + drag_waypoint_set = targets.getWaypointSetId(); + } } } } + mouse_down_position = position; + }, + [this](glm::vec2 position) + { // Drag + if (mode == TargetSelection) + radar->setViewPosition(radar->getViewPosition() - (position - mouse_down_position)); + if (mode == MoveWaypoint && my_spaceship) + my_player_info->commandMoveWaypoint(drag_waypoint_index, position, drag_waypoint_set); + }, + [this](glm::vec2 position) + { // Up + switch(mode) + { + case TargetSelection: + targets.setToClosestTo(position, 1000, TargetsContainer::Targetable); + break; + case WaypointPlacement: + if (my_spaceship) my_player_info->commandAddWaypoint(position, active_waypoint_set); + mode = TargetSelection; + option_buttons->show(); + cancel_button->hide(); + break; + case MoveWaypoint: + mode = TargetSelection; + targets.setWaypointIndex(drag_waypoint_index, drag_waypoint_set); + break; + case LaunchProbe: + if (my_spaceship) my_player_info->commandLaunchProbe(position); + mode = TargetSelection; + option_buttons->show(); + cancel_button->hide(); + break; + } + }, + [this](float value, glm::vec2 position) + { // Wheel + // Calculate the new zoom level. + const float view_distance = std::clamp( + radar->getDistance() * (1.0f - value * 0.1f), + MIN_ZOOM_DISTANCE, + MAX_ZOOM_DISTANCE + ); + + // Get the world coordinates under the pointer before zooming. + const glm::vec2 world_position_before_zoom = radar->screenToWorld(position); + + // Set the new zoom level. + radar->setDistance(view_distance); + zoom_slider->setValue(view_distance); + + // Adjust the radar's view position to keep the world coordinates + // under the pointer consistent. + radar->setViewPosition(radar->getViewPosition() + world_position_before_zoom - radar->screenToWorld(position)); } - mouse_down_position = position; - }, - [this](glm::vec2 position) { //drag - if (mode == TargetSelection) - radar->setViewPosition(radar->getViewPosition() - (position - mouse_down_position)); - if (mode == MoveWaypoint && my_spaceship) - my_player_info->commandMoveWaypoint(drag_waypoint_index, position); - }, - [this](glm::vec2 position) { //up - switch(mode) - { - case TargetSelection: - targets.setToClosestTo(position, 1000, TargetsContainer::Targetable); - break; - case WaypointPlacement: - if (my_spaceship) - my_player_info->commandAddWaypoint(position); - mode = TargetSelection; - option_buttons->show(); - cancel_button->hide(); - break; - case MoveWaypoint: - mode = TargetSelection; - targets.setWaypointIndex(drag_waypoint_index); - break; - case LaunchProbe: - if (my_spaceship) - my_player_info->commandLaunchProbe(position); - mode = TargetSelection; - option_buttons->show(); - cancel_button->hide(); - break; - } - }, - [this](float value, glm::vec2 position) { // wheel - // Calculate the new zoom level. - const float view_distance = std::clamp( - radar->getDistance() * (1.0f - value * 0.1f), - MIN_ZOOM_DISTANCE, - MAX_ZOOM_DISTANCE - ); - - // Get the world coordinates under the pointer before zooming. - const glm::vec2 world_position_before_zoom = radar->screenToWorld(position); - - // Set the new zoom level. - radar->setDistance(view_distance); - zoom_slider->setValue(view_distance); - - // Adjust the radar's view position to keep the world coordinates - // under the pointer consistent. - radar->setViewPosition(radar->getViewPosition() + world_position_before_zoom - radar->screenToWorld(position)); - } - ); + ) + ->setPosition(0.0f, 0.0f, sp::Alignment::TopLeft) + ->setSize(GuiElement::GuiSizeMax, GuiElement::GuiSizeMax); if (auto transform = my_spaceship.getComponent()) radar->setViewPosition(transform->getPosition()); @@ -171,19 +184,46 @@ RelayScreen::RelayScreen(GuiContainer* owner, bool allow_comms) link_to_science_button->setSize(GuiElement::GuiSizeMax, 50)->setVisible(my_spaceship.hasComponent() && my_spaceship.hasComponent() && my_spaceship.hasComponent()); // Manage waypoints. - (new GuiButton(option_buttons, "WAYPOINT_PLACE_BUTTON", tr("Place waypoint"), [this]() { - mode = WaypointPlacement; - option_buttons->hide(); - cancel_button->setText(tr("Cancel waypoint"))->show(); - }))->setSize(GuiElement::GuiSizeMax, 50); + (new GuiButton(option_buttons, "WAYPOINT_PLACE_BUTTON", tr("Place waypoint"), + [this]() + { + mode = WaypointPlacement; + option_buttons->hide(); + cancel_button + ->setText(tr("Cancel waypoint")) + ->show(); + } + ))->setSize(GuiElement::GuiSizeMax, 50.0f); + + delete_waypoint_button = new GuiButton(option_buttons, "WAYPOINT_DELETE_BUTTON", tr("Delete waypoint"), + [this]() + { + if (my_spaceship && targets.getWaypointIndex() >= 0) + my_player_info->commandRemoveWaypoint(targets.getWaypointIndex(), targets.getWaypointSetId()); + } + ); + delete_waypoint_button->setSize(GuiElement::GuiSizeMax, 50.0f); - delete_waypoint_button = new GuiButton(option_buttons, "WAYPOINT_DELETE_BUTTON", tr("Delete waypoint"), [this]() { - if (my_spaceship && targets.getWaypointIndex() >= 0) + // Waypoint set selector, shown only when multiple sets are enabled. + waypoint_set_selector = new GuiSelector(option_buttons, "WAYPOINT_SET_SELECTOR", + [this](int index, string value) { - my_player_info->commandRemoveWaypoint(targets.getWaypointIndex()); + active_waypoint_set = index + 1; } - }); - delete_waypoint_button->setSize(GuiElement::GuiSizeMax, 50); + ); + waypoint_set_selector + ->setOptions({tr("Waypoint set 1"), tr("Waypoint set 2"), tr("Waypoint set 3"), tr("Waypoint set 4")}) + ->setSelectionIndex(0) + ->setSize(GuiElement::GuiSizeMax, 50.0f); + + // Route toggle, shown only when server allows routes. + route_toggle = new GuiToggleButton(option_buttons, "WAYPOINT_ROUTE_TOGGLE", tr("Show as route"), + [this](bool value) + { + if (my_spaceship) my_player_info->commandSetWaypointRoute(value, active_waypoint_set); + } + ); + route_toggle->setSize(GuiElement::GuiSizeMax, 50.0f); // Launch probe button. launch_probe_button = new GuiButton(option_buttons, "LAUNCH_PROBE_BUTTON", tr("Launch probe"), [this]() { @@ -341,4 +381,21 @@ void RelayScreen::onDraw(sp::RenderTarget& renderer) } delete_waypoint_button->setEnable(targets.getWaypointIndex() >= 0); + + // Show/hide waypoint set selector and route toggle based on global settings + waypoint_set_selector->setVisible(gameGlobalInfo->enable_multiple_waypoint_sets); + route_toggle->setVisible(gameGlobalInfo->enable_waypoint_routes); + + if (!gameGlobalInfo->enable_multiple_waypoint_sets && active_waypoint_set != 1) + { + active_waypoint_set = 1; + waypoint_set_selector->setSelectionIndex(0); + } + + // Sync route toggle from current ship state + if (my_spaceship && gameGlobalInfo->enable_waypoint_routes) + { + if (auto wp = my_spaceship.getComponent()) + route_toggle->setValue(wp->is_route[active_waypoint_set - 1]); + } } diff --git a/src/screens/crew6/relayScreen.h b/src/screens/crew6/relayScreen.h index 8de707344b..3a682465a4 100644 --- a/src/screens/crew6/relayScreen.h +++ b/src/screens/crew6/relayScreen.h @@ -1,14 +1,17 @@ #pragma once +#include #include "screenComponents/targetsContainer.h" #include "gui/gui2_overlay.h" class GuiButton; +class GuiElement; class GuiHackingDialog; class GuiKeyValueDisplay; class GuiLabel; class GuiRadarView; class GuiRadarZoomSlider; +class GuiSelector; class GuiSlider; class GuiToggleButton; @@ -28,6 +31,9 @@ class RelayScreen : public GuiOverlay TargetsContainer targets; int drag_waypoint_index; + int drag_waypoint_set; + int active_waypoint_set = 1; + GuiRadarView* radar; GuiKeyValueDisplay* info_callsign; @@ -42,6 +48,8 @@ class RelayScreen : public GuiOverlay GuiButton* delete_waypoint_button; GuiButton* launch_probe_button; GuiToggleButton* center_button; + GuiSelector* waypoint_set_selector; + GuiToggleButton* route_toggle; GuiRadarZoomSlider* zoom_slider; diff --git a/src/screens/gm/gameMasterScreen.cpp b/src/screens/gm/gameMasterScreen.cpp index 1ae3651274..c823b4c2e1 100644 --- a/src/screens/gm/gameMasterScreen.cpp +++ b/src/screens/gm/gameMasterScreen.cpp @@ -283,6 +283,125 @@ GameMasterScreen::GameMasterScreen(RenderLayer* render_layer) }))->setTextSize(20)->setSize(GuiElement::GuiSizeMax, 30); (new GuiLabel(order_layout, "ORDERS_LABEL", tr("Orders:"), 20))->addBackground()->setSize(GuiElement::GuiSizeMax, 30); + // Player ship waypoint controls (shown when a player ship is selected) + gm_player_waypoint_layout = new GuiElement(this, "GM_WP_LAYOUT"); + gm_player_waypoint_layout + ->setPosition(-20.0f, -240.0f, sp::Alignment::BottomRight) + ->setSize(300.0f, GuiElement::GuiSizeMax) + ->hide() + ->setAttribute("layout", "verticalbottom"); + + gm_delete_waypoint_button = new GuiToggleButton(gm_player_waypoint_layout, "GM_WP_DELETE", tr("button", "Delete waypoint"), + [this](bool value) + { + // Cancel add mode when entering delete mode. + gm_delete_waypoint_mode = value; + if (value && gm_add_waypoint_mode) + { + gm_add_waypoint_mode = false; + gm_waypoint_target_ship = {}; + gm_add_waypoint_button->setValue(false); + } + } + ); + gm_delete_waypoint_button + ->setTextSize(20.0f) + ->setSize(GuiElement::GuiSizeMax, 30.0f); + + // Toggle button to enter/exit waypoint placement mode for the selected player ship. + gm_add_waypoint_button = new GuiToggleButton(gm_player_waypoint_layout, "GM_WP_ADD", tr("button", "Add waypoint"), + [this](bool value) + { + if (value) + { + // Enter waypoint placement mode for the selected player ship. + for (auto entity : targets.getTargets()) + { + if (entity.hasComponent()) + { + gm_waypoint_target_ship = entity; + gm_add_waypoint_mode = true; + // Cancel delete mode if active. + if (gm_delete_waypoint_mode) + { + gm_delete_waypoint_mode = false; + gm_delete_waypoint_button->setValue(false); + } + return; + } + } + // No player ship found; revert toggle. + gm_add_waypoint_button->setValue(false); + } + else + { + gm_add_waypoint_mode = false; + gm_waypoint_target_ship = {}; + } + } + ); + gm_add_waypoint_button + ->setTextSize(20.0f) + ->setSize(GuiElement::GuiSizeMax, 30.0f); + + gm_route_toggle = new GuiToggleButton(gm_player_waypoint_layout, "GM_WP_ROUTE", tr("Draw route for this waypoint set"), + [this](bool value) + { + for (auto entity : targets.getTargets()) + { + if (entity.hasComponent()) + { + if (auto wp = entity.getComponent()) + wp->setRoute(value, gm_waypoint_set); + break; + } + } + } + ); + gm_route_toggle + ->setTextSize(20.0f) + ->setSize(GuiElement::GuiSizeMax, 30.0f); + + gm_waypoint_set_selector = new GuiSelector(gm_player_waypoint_layout, "GM_WP_SET", + [this](int index, string value) + { + gm_waypoint_set = index + 1; + // Sync route toggle when set changes + for (auto entity : targets.getTargets()) + { + if (entity.hasComponent()) + { + if (auto wp = entity.getComponent()) + gm_route_toggle->setValue(wp->is_route[gm_waypoint_set - 1]); + break; + } + } + } + ); + gm_waypoint_set_selector + ->setTextSize(20.0f) + ->setOptions({tr("Waypoint set 1"), tr("Waypoint set 2"), tr("Waypoint set 3"), tr("Waypoint set 4")}) + ->setSelectionIndex(0) + ->setSize(GuiElement::GuiSizeMax, 30.0f); + + // Toggle button to show waypoints. + gm_show_waypoints_button = new GuiToggleButton(gm_player_waypoint_layout, "GM_WP_SHOW", tr("button", "Show waypoints"), + [this](bool value) + { + if (value) + main_radar->enableWaypoints(); + else + main_radar->disableWaypoints(); + } + ); + gm_show_waypoints_button + ->setTextSize(20.0f) + ->setSize(GuiElement::GuiSizeMax, 30.0f); + + (new GuiLabel(gm_player_waypoint_layout, "GM_WP_LABEL", tr("Waypoints"), 20.0f)) + ->addBackground() + ->setSize(GuiElement::GuiSizeMax, 30.0f); + chat_layer = new GuiElement(this, ""); chat_layer->setPosition(0, 0)->setSize(GuiElement::GuiSizeMax, GuiElement::GuiSizeMax); @@ -370,9 +489,18 @@ void GameMasterScreen::update(float delta) if (keys.gm_show_callsigns.getDown()) main_radar->showCallsigns(!main_radar->getCallsigns()); + // Toggle waypoint visibility. + if (keys.gm_show_waypoints.getDown()) + { + if (main_radar->getWaypoints()) + main_radar->disableWaypoints(); + else + main_radar->enableWaypoints(); + } + bool has_object = false; has_cpu_ship = false; - bool has_player_ship = false; + has_player_ship = false; // Add and remove entries from the player ship list. for (auto [entity, pc] : sp::ecs::Query()) @@ -421,6 +549,38 @@ void GameMasterScreen::update(float delta) order_layout->setVisible(has_cpu_ship); player_comms_hail->setVisible(has_player_ship); + // Manage waypoint mode state. + if (!has_player_ship && gm_delete_waypoint_mode) + { + gm_delete_waypoint_mode = false; + gm_delete_waypoint_button->setValue(false); + } + gm_player_waypoint_layout->setVisible(has_player_ship); + gm_add_waypoint_button->setVisible(main_radar->getWaypoints()); + gm_delete_waypoint_button->setVisible(main_radar->getWaypoints()); + gm_route_toggle->setVisible(gameGlobalInfo->enable_waypoint_routes && main_radar->getWaypoints()); + gm_waypoint_set_selector->setVisible(gameGlobalInfo->enable_multiple_waypoint_sets && main_radar->getWaypoints()); + + if (!gameGlobalInfo->enable_multiple_waypoint_sets && gm_waypoint_set != 1) + { + gm_waypoint_set = 1; + gm_waypoint_set_selector->setSelectionIndex(0); + } + + // Sync route toggle from the selected player ship. + if (has_player_ship) + { + for (auto entity : targets.getTargets()) + { + if (entity.hasComponent()) + { + if (auto wp = entity.getComponent()) + gm_route_toggle->setValue(wp->is_route[gm_waypoint_set - 1]); + break; + } + } + } + // Update mission clock info_clock->setValue(gameGlobalInfo->getMissionTime()); @@ -607,6 +767,45 @@ void GameMasterScreen::onMouseDown(sp::io::Pointer::Button button, glm::vec2 pos { if (click_and_drag_state != ClickAndDragState::None) return; + if (button == sp::io::Pointer::Button::Left && main_radar->getWaypoints()) + { + // Check if the click is near any player ship waypoint. + float min_drag_distance = main_radar->getDistance() / 450.0f * 10.0f; + glm::vec2 click_screen = main_radar->worldToScreen(position); + int max_sets = (gameGlobalInfo && gameGlobalInfo->enable_multiple_waypoint_sets) ? Waypoints::MAX_SETS : 1; + for (auto [entity, waypoints] : sp::ecs::Query()) + { + for (auto& wp : waypoints.waypoints) + { + if (wp.set_id < 1 || wp.set_id > max_sets) continue; + if (gm_delete_waypoint_mode) + { + // Delete mode uses icon and label size for hitbox. + glm::vec2 wp_screen = main_radar->worldToScreen(wp.position); + glm::vec2 delta = click_screen - wp_screen; + if (delta.x >= -30.0f && delta.x <= 30.0f && delta.y >= -22.0f && delta.y <= 18.0f) + { + waypoints.remove(wp.id, wp.set_id); + return; + } + } + else if (glm::length(wp.position - position) < min_drag_distance) + { + gm_drag_waypoint_id = wp.id; + gm_drag_waypoint_set = wp.set_id; + gm_drag_waypoint_ship = entity; + drag_start_position = position; + drag_previous_position = position; + return; + } + } + } + } + + // While placing or deleting waypoints, don't enter any other drag/select states. + if (gm_add_waypoint_mode || gm_delete_waypoint_mode) + return; + if (button == sp::io::Pointer::Button::Right) { if (has_cpu_ship) click_and_drag_state = ClickAndDragState::DragViewOrOrder; @@ -636,6 +835,15 @@ void GameMasterScreen::onMouseDown(sp::io::Pointer::Button button, glm::vec2 pos void GameMasterScreen::onMouseDrag(glm::vec2 position) { + // Handle waypoint dragging. + if (gm_drag_waypoint_id >= 0) + { + if (auto wp = gm_drag_waypoint_ship.getComponent()) + wp->move(gm_drag_waypoint_id, position, gm_drag_waypoint_set); + drag_previous_position = position; + return; + } + switch(click_and_drag_state) { case ClickAndDragState::DragViewOrOrder: @@ -676,6 +884,32 @@ void GameMasterScreen::onMouseDrag(glm::vec2 position) void GameMasterScreen::onMouseUp(glm::vec2 position) { + // Finish waypoint drag. + if (gm_drag_waypoint_id >= 0) + { + if (auto wp = gm_drag_waypoint_ship.getComponent()) + wp->move(gm_drag_waypoint_id, position, gm_drag_waypoint_set); + gm_drag_waypoint_id = -1; + gm_drag_waypoint_set = -1; + gm_drag_waypoint_ship = {}; + return; + } + + // GM waypoint placement for a selected player ship. + if (gm_add_waypoint_mode && gm_waypoint_target_ship) + { + bool set_full = false; + if (auto wp = gm_waypoint_target_ship.getComponent()) + set_full = (wp->addNew(position, gm_waypoint_set) < 0); + if (set_full) + { + gm_add_waypoint_mode = false; + gm_waypoint_target_ship = {}; + gm_add_waypoint_button->setValue(false); + } + return; + } + auto mods = SDL_GetModState(); const bool shift_down = mods & KMOD_SHIFT; const bool ctrl_down = mods & KMOD_CTRL; diff --git a/src/screens/gm/gameMasterScreen.h b/src/screens/gm/gameMasterScreen.h index a62c75ba0b..3e2818b278 100644 --- a/src/screens/gm/gameMasterScreen.h +++ b/src/screens/gm/gameMasterScreen.h @@ -106,6 +106,24 @@ class GameMasterScreen : public GuiCanvas, public Updatable { return GMCursorMode(unsigned(a) & unsigned(b)); } bool has_cpu_ship = false; + bool has_player_ship = false; + + // GM waypoint placement/deletion state + bool gm_add_waypoint_mode = false; + bool gm_delete_waypoint_mode = false; + int gm_waypoint_set = 1; + sp::ecs::Entity gm_waypoint_target_ship; + int gm_drag_waypoint_id = -1; + int gm_drag_waypoint_set = -1; + sp::ecs::Entity gm_drag_waypoint_ship; + + // GM player-waypoint controls + GuiElement* gm_player_waypoint_layout; + GuiSelector* gm_waypoint_set_selector; + GuiToggleButton* gm_show_waypoints_button; + GuiToggleButton* gm_add_waypoint_button; + GuiToggleButton* gm_route_toggle; + GuiToggleButton* gm_delete_waypoint_button; GuiButton* create_button; GuiButton* cancel_action_button; diff --git a/src/script.cpp b/src/script.cpp index 13f587c1ac..8a38f802c6 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -1067,26 +1067,26 @@ void luaCommandSetShieldFrequency(sp::ecs::Entity ship, int frequency) { } } -static void luaCommandAddWaypoint(sp::ecs::Entity ship, float x, float y) { - if (my_player_info && my_player_info->ship == ship) { my_player_info->commandAddWaypoint({x, y}); return; } - if (auto wp = ship.getComponent()) { - wp->addNew({x, y}); - } +static void luaCommandAddWaypoint(sp::ecs::Entity ship, float x, float y, int set_id = 1) { + if (my_player_info && my_player_info->ship == ship) { my_player_info->commandAddWaypoint({x, y}, set_id); return; } + if (auto wp = ship.getComponent()) + wp->addNew({x, y}, set_id); } -static void luaCommandRemoveWaypoint(sp::ecs::Entity ship, int index) { - if (my_player_info && my_player_info->ship == ship) { my_player_info->commandRemoveWaypoint(index); return; } - auto wp = ship.getComponent(); - if (wp && index >= 0 && index < int(wp->waypoints.size())) { - wp->waypoints.erase(wp->waypoints.begin() + index); - wp->dirty = true; - } +static void luaCommandRemoveWaypoint(sp::ecs::Entity ship, int index, int set_id = 1) { + if (my_player_info && my_player_info->ship == ship) { my_player_info->commandRemoveWaypoint(index, set_id); return; } + if (auto wp = ship.getComponent()) + wp->remove(index, set_id); } -static void luaCommandMoveWaypoint(sp::ecs::Entity ship, int index, float x, float y) { - if (my_player_info && my_player_info->ship == ship) { my_player_info->commandMoveWaypoint(index, {x, y}); return; } - if (auto wp = ship.getComponent()) { - wp->move(index, {x, y}); - } +static void luaCommandMoveWaypoint(sp::ecs::Entity ship, int index, float x, float y, int set_id = 1) { + if (my_player_info && my_player_info->ship == ship) { my_player_info->commandMoveWaypoint(index, {x, y}, set_id); return; } + if (auto wp = ship.getComponent()) + wp->move(index, {x, y}, set_id); +} +static void luaCommandSetWaypointRoute(sp::ecs::Entity ship, bool is_route, int set_id = 1) { + if (my_player_info && my_player_info->ship == ship) { my_player_info->commandSetWaypointRoute(is_route, set_id); return; } + if (auto wp = ship.getComponent()) + wp->setRoute(is_route, set_id); } static void luaCommandActivateSelfDestruct(sp::ecs::Entity ship) { if (my_player_info && my_player_info->ship == ship) { my_player_info->commandActivateSelfDestruct(); return; } @@ -1553,6 +1553,13 @@ bool setupScriptEnvironment(sp::script::Environment& env) /// Example: /// commandMoveWaypoint(getPlayerShip(-1), 0, 15000, -5000) -- move the first waypoint to 15000, -5000 env.setGlobal("commandMoveWaypoint", &luaCommandMoveWaypoint); + /// void commandSetWaypointRoute(entity ship, bool is_route, integer set_id = 1) + /// Sets or clears the route flag for a waypoint set on this entity. + /// Optional set_id (1-4, default 1) selects the waypoint set. + /// Examples: + /// commandSetWaypointRoute(getPlayerShip(-1), true) -- make set 1 a route + /// commandSetWaypointRoute(getPlayerShip(-1), false, 2) -- remove routes from set 2 + env.setGlobal("commandSetWaypointRoute", &luaCommandSetWaypointRoute); /// void commandActivateSelfDestruct(entity ship) /// Activates the self-destruct sequence for the given ship. /// Crew members must confirm the sequence with commandConfirmDestructCode() before it proceeds. diff --git a/src/script/components.cpp b/src/script/components.cpp index ac7f991696..5afe42e173 100644 --- a/src/script/components.cpp +++ b/src/script/components.cpp @@ -366,6 +366,7 @@ void initComponentScriptBindings() sp::script::ComponentHandler::name("waypoints"); BIND_ARRAY_DIRTY_FLAG(Waypoints, waypoints, dirty); BIND_ARRAY_DIRTY_FLAG_MEMBER_NAMED(Waypoints, waypoints, "id", id, dirty); + BIND_ARRAY_DIRTY_FLAG_MEMBER_NAMED(Waypoints, waypoints, "set_id", set_id, dirty); BIND_ARRAY_DIRTY_FLAG_MEMBER_NAMED(Waypoints, waypoints, "x", position.x, dirty); BIND_ARRAY_DIRTY_FLAG_MEMBER_NAMED(Waypoints, waypoints, "y", position.y, dirty); sp::script::ComponentHandler::name("share_short_range_radar");