diff --git a/scripts/api/entity/spaceobject.lua b/scripts/api/entity/spaceobject.lua index d8c77cf404..93d6874722 100644 --- a/scripts/api/entity/spaceobject.lua +++ b/scripts/api/entity/spaceobject.lua @@ -467,3 +467,48 @@ function Entity:onDestroyed(callback) if self.components.hull then self.components.hull.on_destruction = callback end return self end +--- Defines a function to call when a scan is initiated against this entity. +--- The callback receives three parameters: +--- - target: The entity being scanned (the entity this callback is registered on) +--- - scanner: The entity performing the scan (player ship or probe) +--- - source: The entity that initiated the scan command (player ship, or probe if scan was initiated via radar link) +--- Example: +--- obj:onScanInitiated(function(target, scanner, source) +--- print(target:getCallSign() .. " is being scanned by " .. scanner:getCallSign()) +--- if source ~= scanner then +--- print("(Scan initiated via radar-linked probe)") +--- end +--- end) +function Entity:onScanInitiated(callback) + if self.components.scan_state then self.components.scan_state.on_scan_initiated = callback end + return self +end +--- Defines a function to call when a scan is completed against this entity. +--- The callback receives three parameters: +--- - target: The entity being scanned (the entity this callback is registered on) +--- - scanner: The entity performing the scan (player ship or probe) +--- - source: The entity that initiated the scan command (player ship, or probe if scan was initiated via radar link) +--- Example: +--- obj:onScanCompleted(function(target, scanner, source) +--- print(target:getCallSign() .. " has been scanned by " .. scanner:getCallSign()) +--- if source ~= scanner then +--- print("(Scan completed via radar-linked probe)") +--- end +--- end) +function Entity:onScanCompleted(callback) + if self.components.scan_state then self.components.scan_state.on_scan_completed = callback end + return self +end +--- Defines a function to call when a scan is cancelled against this entity. +--- The callback receives three parameters: +--- - target: The entity being scanned (the entity this callback is registered on) +--- - scanner: The entity performing the scan (player ship or probe) +--- - source: The entity that initiated the scan command (player ship, or probe if scan was initiated via radar link) +--- Example: +--- obj:onScanCancelled(function(target, scanner, source) +--- print("Scan of " .. target:getCallSign() .. " was cancelled") +--- end) +function Entity:onScanCancelled(callback) + if self.components.scan_state then self.components.scan_state.on_scan_cancelled = callback end + return self +end diff --git a/src/components/scanning.h b/src/components/scanning.h index b3969026c0..30b984a246 100644 --- a/src/components/scanning.h +++ b/src/components/scanning.h @@ -1,6 +1,7 @@ #pragma once #include "ecs/entity.h" +#include "script/callback.h" #include @@ -35,6 +36,10 @@ class ScanState void setStateFor(sp::ecs::Entity entity, State state); State getStateForFaction(sp::ecs::Entity entity); void setStateForFaction(sp::ecs::Entity entity, State state); + + sp::script::Callback on_scan_initiated; + sp::script::Callback on_scan_completed; + sp::script::Callback on_scan_cancelled; }; class ScienceDescription @@ -52,4 +57,5 @@ class ScienceScanner float delay = 0.0f; // When a delay based scan is done, this will count down. float max_scanning_delay = 6.0f; sp::ecs::Entity target; + sp::ecs::Entity source; }; \ No newline at end of file diff --git a/src/multiplayer/scanning.cpp b/src/multiplayer/scanning.cpp index a0a8f99893..ece0c59733 100644 --- a/src/multiplayer/scanning.cpp +++ b/src/multiplayer/scanning.cpp @@ -30,4 +30,5 @@ BASIC_REPLICATION_IMPL(ScienceScannerReplication, ScienceScanner) BASIC_REPLICATION_FIELD(delay); BASIC_REPLICATION_FIELD(max_scanning_delay); BASIC_REPLICATION_FIELD(target); + BASIC_REPLICATION_FIELD(source); } diff --git a/src/playerInfo.cpp b/src/playerInfo.cpp index ac57decafe..25facf5b13 100644 --- a/src/playerInfo.cpp +++ b/src/playerInfo.cpp @@ -272,10 +272,10 @@ void PlayerInfo::commandMainScreenOverlay(MainScreenOverlay mainScreen) sendClientCommand(packet); } -void PlayerInfo::commandScan(sp::ecs::Entity object) +void PlayerInfo::commandScan(sp::ecs::Entity object, sp::ecs::Entity scan_source) { sp::io::DataBuffer packet; - packet << CMD_SCAN_OBJECT << object; + packet << CMD_SCAN_OBJECT << object << scan_source; sendClientCommand(packet); } @@ -702,17 +702,27 @@ void PlayerInfo::onReceiveClientCommand(int32_t client_id, sp::io::DataBuffer& p packet >> mso; if (auto pc = ship.getComponent()) pc->main_screen_overlay = mso; - }break; + } break; case CMD_SCAN_OBJECT: { sp::ecs::Entity e; - packet >> e; + sp::ecs::Entity source; + packet >> e >> source; if (auto scanner = ship.getComponent()) { scanner->delay = scanner->max_scanning_delay; scanner->target = e; + if (source) scanner->source = source; + else scanner->source = ship; + + // Fire onScanInitiated callback + if (auto ss = e.getComponent()) + { + if (ss->on_scan_initiated) + LuaConsole::checkResult(ss->on_scan_initiated.call(e, ship, scanner->source)); + } } } break; @@ -720,8 +730,18 @@ void PlayerInfo::onReceiveClientCommand(int32_t client_id, sp::io::DataBuffer& p ScanningSystem::scanningFinished(ship); break; case CMD_SCAN_CANCEL: - if (auto ss = ship.getComponent()) { - ss->target = {}; + if (auto scanner = ship.getComponent()) { + // Fire onScanCancelled callback + if (scanner->target) + { + if (auto ss = scanner->target.getComponent()) + { + if (ss->on_scan_cancelled) + LuaConsole::checkResult(ss->on_scan_cancelled.call(scanner->target, ship, scanner->source)); + } + } + scanner->target = {}; + scanner->source = {}; } break; case CMD_SET_SYSTEM_POWER_REQUEST: diff --git a/src/playerInfo.h b/src/playerInfo.h index 0548addd1b..0e89c84356 100644 --- a/src/playerInfo.h +++ b/src/playerInfo.h @@ -49,7 +49,7 @@ class PlayerInfo : public MultiplayerObject void commandSetShields(bool enabled); void commandMainScreenSetting(MainScreenSetting mainScreen); void commandMainScreenOverlay(MainScreenOverlay mainScreen); - void commandScan(sp::ecs::Entity object); + void commandScan(sp::ecs::Entity object, sp::ecs::Entity link_source = sp::ecs::Entity()); void commandSetSystemPowerRequest(ShipSystem::Type system, float power_level); void commandSetSystemCoolantRequest(ShipSystem::Type system, float coolant_level); void commandDock(sp::ecs::Entity station); diff --git a/src/screenComponents/scanTargetButton.cpp b/src/screenComponents/scanTargetButton.cpp index d2ea8be91d..ad06c63799 100644 --- a/src/screenComponents/scanTargetButton.cpp +++ b/src/screenComponents/scanTargetButton.cpp @@ -3,6 +3,7 @@ #include "targetsContainer.h" #include "gui/gui2_button.h" #include "gui/gui2_progressbar.h" +#include "components/radar.h" #include "components/scanning.h" #include "components/target.h" #include "i18n.h" @@ -11,10 +12,22 @@ GuiScanTargetButton::GuiScanTargetButton(GuiContainer* owner, string id, TargetsContainer* targets) : GuiElement(owner, id), targets(targets) { - button = new GuiButton(this, id + "_BUTTON", tr("scienceButton", "Scan"), [this]() { - if (my_spaceship && this->targets && this->targets->get()) - my_player_info->commandScan(this->targets->get()); - }); + button = new GuiButton(this, id + "_BUTTON", tr("scienceButton", "Scan"), + [this]() + { + if (my_spaceship && this->targets && this->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()) + my_player_info->commandScan(this->targets->get(), rl->linked_entity); + else + my_player_info->commandScan(this->targets->get()); + } + + + } + ); button->setSize(GuiElement::GuiSizeMax, GuiElement::GuiSizeMax); progress = new GuiProgressbar(this, id + "_PROGRESS", 0, 6.0f, 0.0); progress->setSize(GuiElement::GuiSizeMax, GuiElement::GuiSizeMax); diff --git a/src/screenComponents/scanTargetButton.h b/src/screenComponents/scanTargetButton.h index 9dec4d2cd9..a0305986bc 100644 --- a/src/screenComponents/scanTargetButton.h +++ b/src/screenComponents/scanTargetButton.h @@ -1,5 +1,4 @@ -#ifndef SCAN_TARGET_BUTTON_H -#define SCAN_TARGET_BUTTON_H +#pragma once #include "gui/gui2_element.h" @@ -19,5 +18,3 @@ class GuiScanTargetButton : public GuiElement virtual void onUpdate() override; virtual void onDraw(sp::RenderTarget& target) override; }; - -#endif//SCAN_TARGET_BUTTON_H diff --git a/src/screens/crew6/scienceScreen.cpp b/src/screens/crew6/scienceScreen.cpp index 94d628c5da..a5f2856fc4 100644 --- a/src/screens/crew6/scienceScreen.cpp +++ b/src/screens/crew6/scienceScreen.cpp @@ -74,7 +74,8 @@ ScienceScreen::ScienceScreen(GuiContainer* owner, CrewPosition crew_position) probe_radar->setPosition(120, 0, sp::Alignment::CenterLeft)->setSize(900,GuiElement::GuiSizeMax)->hide(); probe_radar->setAutoCentering(false)->longRange()->enableWaypoints()->enableCallsigns()->enableHeadingIndicators()->setStyle(GuiRadarView::Circular)->setFogOfWarStyle(GuiRadarView::NoFogOfWar); probe_radar->setCallbacks( - [this](sp::io::Pointer::Button button, glm::vec2 position) { + [this](sp::io::Pointer::Button button, glm::vec2 position) + { if (auto scanner = my_spaceship.getComponent()) if (scanner->delay > 0.0f) return; @@ -188,22 +189,27 @@ ScienceScreen::ScienceScreen(GuiContainer* owner, CrewPosition crew_position) database_view->hide()->setSize(GuiElement::GuiSizeMax, GuiElement::GuiSizeMax); // Probe view button - probe_view_button = new GuiToggleButton(radar_view, "PROBE_VIEW", tr("scienceButton", "Probe View"), [this](bool value){ - auto rl = my_spaceship.getComponent(); - if (value && rl && rl->linked_entity) + probe_view_button = new GuiToggleButton(radar_view, "PROBE_VIEW", tr("scienceButton", "Probe View"), + [this](bool value) { - auto transform = rl->linked_entity.getComponent(); - if (transform) { - science_radar->hide(); - probe_radar->show(); - probe_radar->setViewPosition(transform->getPosition())->show(); + auto rl = my_spaceship.getComponent(); + if (value && rl && rl->linked_entity) + { + if (auto transform = rl->linked_entity.getComponent()) + { + science_radar->hide(); + probe_radar->show(); + probe_radar->setViewPosition(transform->getPosition())->show(); + } + } + else + { + probe_view_button->setValue(false); + science_radar->show(); + probe_radar->hide(); } - }else{ - probe_view_button->setValue(false); - science_radar->show(); - probe_radar->hide(); } - }); + ); probe_view_button->setPosition(20, -120, sp::Alignment::BottomLeft)->setSize(200, 50)->disable(); // Draw the zoom slider. @@ -529,7 +535,12 @@ void ScienceScreen::onUpdate() auto scanstate = obj.getComponent(); if (scanstate && scanstate->getStateFor(my_spaceship) != ScanState::State::FullScan) { - my_player_info->commandScan(obj); + // 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; } } diff --git a/src/screens/crew6/scienceScreen.h b/src/screens/crew6/scienceScreen.h index 47bc855006..d101b2af04 100644 --- a/src/screens/crew6/scienceScreen.h +++ b/src/screens/crew6/scienceScreen.h @@ -1,5 +1,4 @@ -#ifndef SCIENCE_SCREEN_H -#define SCIENCE_SCREEN_H +#pragma once #include "screenComponents/targetsContainer.h" #include "gui/gui2_overlay.h" @@ -68,5 +67,3 @@ class ScienceScreen : public GuiOverlay float previous_long_range_radar=0; float previous_short_range_radar=0; }; - -#endif//SCIENCE_SCREEN_H diff --git a/src/script/components.cpp b/src/script/components.cpp index 2d64e25e64..1552ca63fb 100644 --- a/src/script/components.cpp +++ b/src/script/components.cpp @@ -623,6 +623,9 @@ void initComponentScriptBindings() BIND_MEMBER(ScanState, allow_simple_scan); BIND_MEMBER(ScanState, complexity); BIND_MEMBER(ScanState, depth); + BIND_MEMBER(ScanState, on_scan_initiated); + BIND_MEMBER(ScanState, on_scan_completed); + BIND_MEMBER(ScanState, on_scan_cancelled); BIND_ARRAY_DIRTY_FLAG(ScanState, per_faction, per_faction_dirty); BIND_ARRAY_DIRTY_FLAG_MEMBER(ScanState, per_faction, faction, per_faction_dirty); BIND_ARRAY_DIRTY_FLAG_MEMBER(ScanState, per_faction, state, per_faction_dirty); diff --git a/src/systems/scanning.cpp b/src/systems/scanning.cpp index e055598548..7464d7bc27 100644 --- a/src/systems/scanning.cpp +++ b/src/systems/scanning.cpp @@ -2,50 +2,62 @@ #include "components/scanning.h" #include "gameGlobalInfo.h" #include "multiplayer_server.h" +#include "menus/luaConsole.h" #include void ScanningSystem::update(float delta) { - for(auto [entity, scanner] : sp::ecs::Query()) { - if (auto ss = scanner.target.getComponent()) { - // If the scan setting or a target's scan complexity is none/0, - // complete the scan after a delay. + for (auto [entity, scanner] : sp::ecs::Query()) + { + // If the scan setting or a target's scan complexity is none/0, + // complete the scan after a delay. + if (auto ss = scanner.target.getComponent()) + { if (ss->complexity == 0 || (ss->complexity < 0 && gameGlobalInfo->scanning_complexity == SC_None)) { + // If scan has just started, fire the onScanInitiated callback. + if (scanner.delay == scanner.max_scanning_delay && ss->on_scan_initiated) + LuaConsole::checkResult(ss->on_scan_initiated.call(scanner.target, entity, scanner.source)); + scanner.delay -= delta; - if (scanner.delay < 0 && game_server) + if (scanner.delay < 0.0f && game_server) scanningFinished(entity); } - }else{ - // Otherwise, ignore the scanning_delay setting. - scanner.delay = 0.0; } + // Otherwise, ignore the scanning_delay setting. + else scanner.delay = 0.0f; } } -void ScanningSystem::scanningFinished(sp::ecs::Entity source) +void ScanningSystem::scanningFinished(sp::ecs::Entity command_source) { - auto scanner = source.getComponent(); + auto scanner = command_source.getComponent(); if (!scanner) return; - auto ss = scanner->target.getComponent(); - if (ss) { - switch(ss->getStateFor(source)) { + + if (auto ss = scanner->target.getComponent()) + { + switch(ss->getStateFor(command_source)) + { case ScanState::State::NotScanned: case ScanState::State::FriendOrFoeIdentified: if (ss->allow_simple_scan) - ss->setStateFor(source, ScanState::State::SimpleScan); + ss->setStateFor(command_source, ScanState::State::SimpleScan); else - ss->setStateFor(source, ScanState::State::FullScan); + ss->setStateFor(command_source, ScanState::State::FullScan); break; case ScanState::State::SimpleScan: - ss->setStateFor(source, ScanState::State::FullScan); + ss->setStateFor(command_source, ScanState::State::FullScan); break; case ScanState::State::FullScan: break; } + + LuaConsole::checkResult(ss->on_scan_completed.call(scanner->target, command_source, scanner->source)); } + scanner->target = {}; - scanner->delay = 0.0; + scanner->source = {}; + scanner->delay = 0.0f; }