Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions scripts/api/entity/spaceobject.lua
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,70 @@ function Entity:scanningChannelDepth()
if self.components.scan_state then return self.components.scan_state.depth end
return 0
end
--- Sets a per-entity hacking difficulty override for this entity, replacing the global hacking difficulty for this target.
--- Valid values are between 0 (easiest) and 3 (hardest), matching the global setting range.
--- Pass -1 to clear the override and use the global hacking difficulty instead.
--- Example: entity:setHackingDifficulty(3)
function Entity:setHackingDifficulty(difficulty)
if difficulty < 0 then
local games = self.components.hacking_target and self.components.hacking_target.games or -1
-- If neither difficulty nor game type is customized, remove the HackingTarget component.
if games < 0 then
self.components.hacking_target = nil
else
self.components.hacking_target = {difficulty=-1}
end
else
self.components.hacking_target = {difficulty=difficulty}
end
return self
end
--- Returns the per-entity hacking difficulty override for this entity.
--- Returns a number between 0 (easiest) and 3 (hardest), or -1 if no override is set and the global hacking difficulty applies.
--- Example:
--- entity:setHackingDifficulty(3)
--- entity:getHackingDifficulty() -- returns 3
function Entity:getHackingDifficulty()
if self.components.hacking_target then return self.components.hacking_target.difficulty end
return -1
end
--- Sets a per-entity hacking game override for this entity, replacing the global hacking game setting for this target.
--- Valid values are "mines", "lights", or "all". Pass nil to clear the override.
--- Example: entity:setHackingGame("mines")
function Entity:setHackingGame(game)
local games = -1
if game == "mines" then games = 0
elseif game == "lights" then games = 1
elseif game == "all" then games = 2
end
-- If neither difficulty nor game type is customized, remove the HackingTarget component.
if games < 0 then
local difficulty = self.components.hacking_target and self.components.hacking_target.difficulty or -1
if difficulty < 0 then
self.components.hacking_target = nil
else
self.components.hacking_target = {games=-1}
end
else
self.components.hacking_target = {games=games}
end
return self
end
--- Returns the per-entity hacking game override for this entity.
--- Returns "mines", "lights", or "all", or nil if no override is set.
--- Example:
--- entity:setHackingGame("mines")
--- entity:getHackingGame() -- returns "mines"
function Entity:getHackingGame()
if self.components.hacking_target then
local g = self.components.hacking_target.games
if g == 0 then return "mines"
elseif g == 1 then return "lights"
elseif g == 2 then return "all"
end
end
return nil
end
--- Defines whether all factions consider this entity as having been scanned.
--- Only ship entities are created in an unscanned state. Other entities are created as fully scanned.
--- If false, all factions treat this entity as unscanned.
Expand Down
15 changes: 14 additions & 1 deletion src/components/hacking.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
#pragma once

// Component to indicate that we can hack other ships.
// Component to indicate that this entity can hack other entities.
class HackingDevice
{
public:
float effectiveness = 0.5f;
};

// Component on hackable entities to store any per-entity overrides for
// difficulty and hacking minigame type.
class HackingTarget
{
public:
// Difficulty values map to the global range (0-3). A value of -1 uses the
// global hacking_difficulty instead.
int difficulty = -1;
// Maps to an EHackingGames value. Value of -1 uses global hacking_games
// instead.
int games = -1;
};
1 change: 1 addition & 0 deletions src/init/ecs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ void initSystemsAndComponents()
sp::ecs::MultiplayerReplication::registerComponentReplication<FactionInfoReplication>();
sp::ecs::MultiplayerReplication::registerComponentReplication<GravityReplication>();
sp::ecs::MultiplayerReplication::registerComponentReplication<HackingDeviceReplication>();
sp::ecs::MultiplayerReplication::registerComponentReplication<HackingTargetReplication>();
sp::ecs::MultiplayerReplication::registerComponentReplication<HullReplication>();
sp::ecs::MultiplayerReplication::registerComponentReplication<ImpulseEngineReplication>();
sp::ecs::MultiplayerReplication::registerComponentReplication<InternalRoomsReplication>();
Expand Down
5 changes: 5 additions & 0 deletions src/multiplayer/hacking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,9 @@

BASIC_REPLICATION_IMPL(HackingDeviceReplication, HackingDevice)
BASIC_REPLICATION_FIELD(effectiveness);
}

BASIC_REPLICATION_IMPL(HackingTargetReplication, HackingTarget)
BASIC_REPLICATION_FIELD(difficulty);
BASIC_REPLICATION_FIELD(games);
}
1 change: 1 addition & 0 deletions src/multiplayer/hacking.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
#include "components/hacking.h"

BASIC_REPLICATION_CLASS(HackingDeviceReplication, HackingDevice);
BASIC_REPLICATION_CLASS(HackingTargetReplication, HackingTarget);
19 changes: 17 additions & 2 deletions src/screenComponents/hackingDialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
#include <memory>
#include <algorithm>

#include "components/hacking.h"

#include "gui/gui2_panel.h"
#include "gui/gui2_label.h"
#include "gui/gui2_listbox.h"
Expand Down Expand Up @@ -158,14 +160,27 @@ void GuiHackingDialog::onMiniGameComplete(bool success)
status_label->setText(success ? tr("Hacking SUCCESS!") : tr("Hacking FAILURE!"));
}

void GuiHackingDialog::getNewGame() {
void GuiHackingDialog::getNewGame()
{
// Apply difficulty and game type settings in this priority order:
// - Per-entity overrides via HackingTarget component
// - If none, use GameGlobalInfo settings
// - If none, use global defaults
int difficulty = 2;
EHackingGames games = HG_All;
if (gameGlobalInfo) {

if (gameGlobalInfo)
{
difficulty = gameGlobalInfo->hacking_difficulty;
games = gameGlobalInfo->hacking_games;
}

if (auto ht = target.getComponent<HackingTarget>())
{
if (ht->difficulty >= 0) difficulty = ht->difficulty;
if (ht->games >= 0) games = EHackingGames(ht->games);
}

const string lights_help = tr("To successfully hack this system, you must fully illuminate all binary countermeasure nodes.\n\nSelect a node in the grid to toggle its state between off and on. Doing so also toggles the state of adjacent nodes immediately above, below, and to the selected node's sides. Continue toggling nodes until every node is illuminated.\n\nYou can make an unlimited number of moves, but seek efficient solutions to best aid your crewmates. Inexperienced hackers might chase dark nodes from top row to the bottom row by selecting the node immediately below each dark node. More experienced intrusion specialists might identify more efficient solutions.\n\nClick the Reset button to reset the field, or select a system to attempt a different intrusion method.");
const string mine_help = tr("To successfully hack this system, you must apply a systematic process of elimination to identify sensitive data nodes within a grid without disturbing them.\n\nSelect a node in the grid to reveal it. If you reveal a sensitive node, the hacking interface marks it with an X. You can safely reveal one sensitive node, but revealing a second sensitive node alerts the system being hacked and disconnects your intrusion attempt.\n\nAn empty node lights up, and if no sensitive nodes surround it, any other adjacent nodes that also lack a nearby sensitive node automatically reveal themselves.\n\nIf a sensitive node is immediately adjacent to or diagonal from the revealed node, a number indicates how many of the surrounding nodes are sensitive. Seek patterns in the numbers that surround unrevealed nodes to determine which unrevealed nodes are sensitive.\n\nClick the Reset button to reset the field, or select a system to attempt a different intrusion method.");

Expand Down
57 changes: 57 additions & 0 deletions src/screens/gm/tweak.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,63 @@ GuiEntityTweak::GuiEntityTweak(GuiContainer* owner)
ADD_PAGE(tr("tweak-tab", "Hacking"), HackingDevice);
ADD_NUM_TEXT_TWEAK(tr("tweak-text", "Effectiveness:"), HackingDevice, effectiveness);

ADD_PAGE(tr("tweak-tab", "Hacking target"), HackingTarget);
{
auto row = new GuiElement(new_page->tweaks, "");
row
->setSize(GuiElement::GuiSizeMax, 30.0f)
->setAttribute("layout", "horizontal");

(new GuiLabel(row, "", tr("tweak-text", "Difficulty:"), 20.0f))
->setAlignment(sp::Alignment::CenterRight)
->setSize(GuiElement::GuiSizeMax, 30.0f);

auto ui = new GuiSelectorTweak(row, "",
[this](int index, string value)
{
if (auto v = entity.getComponent<HackingTarget>())
v->difficulty = index - 1;
}
);
ui->addEntry(tr("hacking", "Global default"), "");
ui->addEntry(tr("hacking", "Simple"), "");
ui->addEntry(tr("hacking", "Normal"), "");
ui->addEntry(tr("hacking", "Difficult"), "");
ui->addEntry(tr("hacking", "Fiendish"), "");
ui->update_func = [this]() -> int {
if (auto v = entity.getComponent<HackingTarget>())
return v->difficulty + 1;
return 0;
};
}
{
auto row = new GuiElement(new_page->tweaks, "");
row
->setSize(GuiElement::GuiSizeMax, 30.0f)
->setAttribute("layout", "horizontal");

(new GuiLabel(row, "", tr("tweak-text", "Game:"), 20.0f))
->setAlignment(sp::Alignment::CenterRight)
->setSize(GuiElement::GuiSizeMax, 30.0f);

auto ui = new GuiSelectorTweak(row, "",
[this](int index, string value)
{
if (auto v = entity.getComponent<HackingTarget>())
v->games = index - 1;
}
);
ui->addEntry(tr("hacking", "Global default"), "");
ui->addEntry(tr("hacking", "Mine"), "");
ui->addEntry(tr("hacking", "Lights"), "");
ui->addEntry(tr("hacking", "All"), "");
ui->update_func = [this]() -> int {
if (auto v = entity.getComponent<HackingTarget>())
return v->games + 1;
return 0;
};
}

ADD_PAGE(tr("tweak-tab", "Self-destruct"), SelfDestruct);
ADD_BOOL_TWEAK(tr("tweak-text", "Active:"), SelfDestruct, active);
ADD_NUM_TEXT_TWEAK(tr("tweak-text", "Countdown:"), SelfDestruct, countdown);
Expand Down
3 changes: 3 additions & 0 deletions src/script/components.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,9 @@ void initComponentScriptBindings()
BIND_MEMBER(PlayerControl, allowed_positions);
sp::script::ComponentHandler<HackingDevice>::name("hacking_device");
BIND_MEMBER(HackingDevice, effectiveness);
sp::script::ComponentHandler<HackingTarget>::name("hacking_target");
BIND_MEMBER(HackingTarget, difficulty);
BIND_MEMBER(HackingTarget, games);
sp::script::ComponentHandler<ShipLog>::name("ship_log");

sp::script::ComponentHandler<MoveTo>::name("move_to");
Expand Down
Loading