Skip to content

Commit 94e3544

Browse files
committed
Add build progress tracking feature
1 parent c703222 commit 94e3544

File tree

6 files changed

+185
-8
lines changed

6 files changed

+185
-8
lines changed

emscripten/shell_index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,8 @@ <h3 class="app-name">Changelog</h3>
115115
"There are two kinds of splitters: one for which all outputs are equal, and one for which they are independent",
116116
"An invalid link for a sink will be displayed in orange",
117117
"You can set values using complex expressions such as (7.2 - 4.8) + 2 / (4/3 - 1/3)",
118+
"You can track your build progress by clicking the checkbox on the top right corner of craft nodes",
119+
"A craft node marked as built will be displayed with a green border",
118120
];
119121

120122
document.getElementById("tip").textContent = "Tip: " + tips[Math.floor(Math.random() * tips.length)];

ficsit-companion/include/app.hpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ class App
103103

104104
private:
105105
/// @brief Used in saved files to track when format change. Used to update files saved with previous versions
106-
static constexpr int SAVE_VERSION = 3;
106+
static constexpr int SAVE_VERSION = 4;
107107

108108
/// @brief Window id used for the Add Node popup
109109
static constexpr std::string_view add_node_popup_id = "Add Node";
@@ -125,6 +125,8 @@ class App
125125
/// @brief If true, will display power info with equal clocks on all machines in a node
126126
/// If false, it will compute the power for N machines at 100% + an underclocked machine
127127
bool power_equal_clocks = true;
128+
/// @brief If true, build progress bar and checkbox on craft nodes will not be displayed
129+
bool hide_build_progress = false;
128130
} settings;
129131

130132
/// @brief All nodes currently in the graph view

ficsit-companion/include/node.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ struct CraftNode : public PoweredNode
9393
const Recipe* recipe;
9494
/// @brief Technically it could be just an int, but FractionalNumber already has all string operations
9595
FractionalNumber num_somersloop;
96+
/// @brief Custom boolean that can be used to track progress on factory building
97+
bool built;
9698
};
9799

98100
struct GroupNode : public PoweredNode
@@ -108,6 +110,7 @@ struct GroupNode : public PoweredNode
108110
virtual bool HasVariablePower() const override;
109111
virtual void ComputePowerUsage() override;
110112
void PropagateRateToSubnodes();
113+
void SetBuiltState(const bool b);
111114

112115
private:
113116
void CreateInsOuts(const std::function<unsigned long long int()>& id_generator);
@@ -124,6 +127,7 @@ struct GroupNode : public PoweredNode
124127
/// @brief Cached value to avoid looping through all the nodes everytime
125128
bool variable_power;
126129
std::map<std::string, FractionalNumber> total_machines;
130+
std::map<std::string, FractionalNumber> built_machines;
127131
std::map<std::string, std::map<const Recipe*, FractionalNumber>> detailed_machines;
128132
std::map<const Recipe*, FractionalNumber> detailed_power_same_clock;
129133
std::map<const Recipe*, FractionalNumber> detailed_power_last_underclock;

ficsit-companion/src/app.cpp

Lines changed: 121 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ void App::LoadSettings()
157157
#else
158158
settings.hide_spoilers = false;
159159
#endif
160-
settings.hide_somersloop = json.contains("hide_somersloop") && json["hide_somersloop"].get<bool>(); // default true
160+
settings.hide_somersloop = json.contains("hide_somersloop") && json["hide_somersloop"].get<bool>(); // default false
161161
settings.unlocked_alts = {};
162162

163163
for (const auto& r : Data::Recipes())
@@ -168,6 +168,8 @@ void App::LoadSettings()
168168
}
169169
}
170170

171+
settings.hide_build_progress = !json.contains("hide_build_progress") || json["hide_build_progress"].get<bool>(); // default true
172+
171173
if (!content.has_value())
172174
{
173175
SaveSettings();
@@ -190,6 +192,8 @@ void App::SaveSettings() const
190192
}
191193
serialized["unlocked_alts"] = unlocked;
192194

195+
serialized["hide_build_progress"] = settings.hide_build_progress;
196+
193197
SaveFile(settings_file.data(), serialized.Dump());
194198
}
195199

@@ -1259,6 +1263,7 @@ void App::Render()
12591263
ax::NodeEditor::SetCurrentEditor(context);
12601264
ax::NodeEditor::PushStyleColor(ax::NodeEditor::StyleColor_Flow, ImColor(1.0f, 1.0f, 0.0f));
12611265
ax::NodeEditor::PushStyleColor(ax::NodeEditor::StyleColor_FlowMarker, ImColor(1.0f, 1.0f, 0.0f));
1266+
ax::NodeEditor::PushStyleVar(ax::NodeEditor::StyleVar_SelectedNodeBorderWidth, 5.0f);
12621267

12631268
ImGui::BeginChild("#left_panel", ImVec2(0.2f * ImGui::GetWindowSize().x, 0.0f), false, ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_NoNavInputs);
12641269
RenderLeftPanel();
@@ -1297,6 +1302,7 @@ void App::Render()
12971302
CustomKeyControl();
12981303

12991304
ax::NodeEditor::End();
1305+
ax::NodeEditor::PopStyleVar();
13001306
ax::NodeEditor::PopStyleColor();
13011307
ax::NodeEditor::PopStyleColor();
13021308

@@ -1578,6 +1584,33 @@ void App::RenderLeftPanel()
15781584
"If set, the power per node will be calculated assuming all machines are set at the same clock value\n"
15791585
"Otherwise, it will be calculated with machines at 100% and one last machine underclocked");
15801586
}
1587+
if (ImGui::Checkbox("Hide build progress", &settings.hide_build_progress))
1588+
{
1589+
SaveSettings();
1590+
}
1591+
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
1592+
{
1593+
ImGui::SetTooltip("%s", "If set, build checkmark on craft nodes and overall progress bars won't be displayed");
1594+
}
1595+
if (!settings.hide_build_progress)
1596+
{
1597+
ImGui::SameLine();
1598+
if (ImGui::Button("Reset progress"))
1599+
{
1600+
for (auto& n : nodes)
1601+
{
1602+
if (n->IsCraft())
1603+
{
1604+
static_cast<CraftNode*>(n.get())->built = false;
1605+
}
1606+
else if (n->IsGroup())
1607+
{
1608+
static_cast<GroupNode*>(n.get())->SetBuiltState(false);
1609+
}
1610+
}
1611+
}
1612+
}
1613+
15811614

15821615
if (ImGui::Button("Unlock all alt recipes"))
15831616
{
@@ -1612,6 +1645,9 @@ void App::RenderLeftPanel()
16121645
std::map<const Item*, FractionalNumber, ItemPtrCompare> outputs;
16131646
std::map<const Item*, FractionalNumber, ItemPtrCompare> intermediates;
16141647
std::map<std::string, FractionalNumber> total_machines;
1648+
FractionalNumber all_machines;
1649+
std::map<std::string, FractionalNumber> built_machines;
1650+
FractionalNumber all_built_machines;
16151651
std::map<std::string, std::map<const Recipe*, FractionalNumber, RecipePtrCompare>> detailed_machines;
16161652
FractionalNumber total_sink_points;
16171653
std::map<const Item*, FractionalNumber> detailed_sink_points;
@@ -1635,7 +1671,10 @@ void App::RenderLeftPanel()
16351671

16361672
const CraftNode* node = static_cast<const CraftNode*>(n.get());
16371673
total_machines[node->recipe->building->name] += node->current_rate;
1674+
all_machines += node->current_rate;
16381675
detailed_machines[node->recipe->building->name][node->recipe] += node->current_rate;
1676+
built_machines[node->recipe->building->name] += node->built ? node->current_rate : 0;
1677+
all_built_machines += node->built ? node->current_rate : 0;
16391678
total_power += settings.power_equal_clocks ? node->same_clock_power : node->last_underclock_power;
16401679
detailed_power[node->recipe] += settings.power_equal_clocks ? node->same_clock_power : node->last_underclock_power;
16411680
has_variable_power |= node->recipe->building->variable_power;
@@ -1658,6 +1697,12 @@ void App::RenderLeftPanel()
16581697
for (const auto& [k, v] : node->total_machines)
16591698
{
16601699
total_machines[k] += v;
1700+
all_machines += v;
1701+
}
1702+
for (const auto& [k, v] : node->built_machines)
1703+
{
1704+
built_machines[k] += v;
1705+
all_built_machines += v;
16611706
}
16621707
for (const auto& [k, v] : node->detailed_machines)
16631708
{
@@ -1700,6 +1745,46 @@ void App::RenderLeftPanel()
17001745
}
17011746
}
17021747

1748+
if (!settings.hide_build_progress)
1749+
{
1750+
ImGui::SeparatorText("Build Progress");
1751+
1752+
// Progress bar color
1753+
ImGui::PushStyleColor(ImGuiCol_::ImGuiCol_PlotHistogram, ImVec4(0, 0.5, 0, 1));
1754+
// No visible color change when hovered/click
1755+
ImGui::PushStyleColor(ImGuiCol_::ImGuiCol_HeaderHovered, ImVec4(0, 0, 0, 0));
1756+
ImGui::PushStyleColor(ImGuiCol_::ImGuiCol_HeaderActive, ImVec4(0, 0, 0, 0));
1757+
const bool display_build_details = ImGui::TreeNodeEx("##build_progress", ImGuiTreeNodeFlags_FramePadding | ImGuiTreeNodeFlags_SpanAvailWidth);
1758+
ImGui::PopStyleColor();
1759+
ImGui::PopStyleColor();
1760+
1761+
// Displayed over the TreeNodeEx element (same line)
1762+
ImGui::SameLine();
1763+
ImGui::ProgressBar(all_machines.GetNumerator() == 0 ? 0.0f : static_cast<float>((all_built_machines / all_machines).GetValue()));
1764+
float max_machine_name_width = 0.0f;
1765+
for (auto& [machine, f] : built_machines)
1766+
{
1767+
max_machine_name_width = std::max(max_machine_name_width, ImGui::CalcTextSize(machine.c_str()).x);
1768+
}
1769+
// Detailed list of recipes if the tree node is open
1770+
if (display_build_details)
1771+
{
1772+
ImGui::Indent();
1773+
for (auto& [machine, f] : built_machines)
1774+
{
1775+
ImGui::TextUnformatted(machine.c_str());
1776+
ImGui::SameLine();
1777+
ImGui::Dummy(ImVec2(max_machine_name_width - ImGui::CalcTextSize(machine.c_str()).x, 0.0f));
1778+
ImGui::SameLine();
1779+
ImGui::ProgressBar((f / total_machines[machine]).GetValue());
1780+
}
1781+
1782+
ImGui::Unindent();
1783+
ImGui::TreePop();
1784+
}
1785+
ImGui::PopStyleColor();
1786+
}
1787+
17031788
ImGui::SeparatorText(has_variable_power ? "Average Power Consumption" : "Power Consumption");
17041789
if (total_power.GetNumerator() != 0)
17051790
{
@@ -1996,6 +2081,14 @@ void App::RenderNodes()
19962081
ax::NodeEditor::PushStyleColor(ax::NodeEditor::StyleColor_NodeBorder, ImColor(255, 0, 0));
19972082
node_pushed_style += 1;
19982083
}
2084+
if (!settings.hide_build_progress && (
2085+
(node->IsCraft() && static_cast<CraftNode*>(node.get())->built) ||
2086+
(node->IsGroup() && static_cast<GroupNode*>(node.get())->built_machines == static_cast<GroupNode*>(node.get())->total_machines)
2087+
))
2088+
{
2089+
ax::NodeEditor::PushStyleColor(ax::NodeEditor::StyleColor_NodeBorder, ImColor(0, 255, 0));
2090+
node_pushed_style += 1;
2091+
}
19992092
ax::NodeEditor::BeginNode(node->id);
20002093
ImGui::PushID(node->id.AsPointer());
20012094
ImGui::BeginVertical("node");
@@ -2005,7 +2098,18 @@ void App::RenderNodes()
20052098
switch (node->GetKind())
20062099
{
20072100
case Node::Kind::Craft:
2008-
ImGui::TextUnformatted(static_cast<CraftNode*>(node.get())->recipe->display_name.c_str());
2101+
{
2102+
CraftNode* craft_node = static_cast<CraftNode*>(node.get());
2103+
ImGui::Spring(1.0f);
2104+
ImGui::TextUnformatted(craft_node->recipe->display_name.c_str());
2105+
ImGui::Spring(1.0f);
2106+
if (!settings.hide_build_progress)
2107+
{
2108+
ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_FramePadding, ImVec2(0, 0));
2109+
ImGui::Checkbox("##craft_built", &craft_node->built);
2110+
ImGui::PopStyleVar();
2111+
}
2112+
}
20092113
break;
20102114
case Node::Kind::Merger:
20112115
ImGui::TextUnformatted("Merger");
@@ -2021,11 +2125,23 @@ void App::RenderNodes()
20212125
break;
20222126
case Node::Kind::Group:
20232127
{
2128+
GroupNode* group_node = static_cast<GroupNode*>(node.get());
2129+
ImGui::Spring(1.0f);
20242130
ImGui::TextUnformatted("Group");
20252131
ImGui::Spring(0.0f);
2026-
std::string& name = static_cast<GroupNode*>(node.get())->name;
2027-
ImGui::SetNextItemWidth(std::max(ImGui::CalcTextSize(name.c_str()).x, ImGui::CalcTextSize("Name...").x) + ImGui::GetStyle().FramePadding.x * 4.0f);
2028-
ImGui::InputTextWithHint("##name", "Name...", &name);
2132+
ImGui::SetNextItemWidth(std::max(ImGui::CalcTextSize(group_node->name.c_str()).x, ImGui::CalcTextSize("Name...").x) + ImGui::GetStyle().FramePadding.x * 4.0f);
2133+
ImGui::InputTextWithHint("##name", "Name...", &group_node->name);
2134+
ImGui::Spring(1.0f);
2135+
if (!settings.hide_build_progress)
2136+
{
2137+
ImGui::PushStyleVar(ImGuiStyleVar_::ImGuiStyleVar_FramePadding, ImVec2(0, 0));
2138+
bool is_built = group_node->built_machines == group_node->total_machines;
2139+
if (ImGui::Checkbox("##group_built", &is_built))
2140+
{
2141+
group_node->SetBuiltState(is_built);
2142+
}
2143+
ImGui::PopStyleVar();
2144+
}
20292145
break;
20302146
}
20312147
}

ficsit-companion/src/node.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ Json::Value PoweredNode::Serialize() const
138138
}
139139

140140
CraftNode::CraftNode(const ax::NodeEditor::NodeId id, const Recipe* recipe, const std::function<unsigned long long int()>& id_generator) :
141-
PoweredNode(id), num_somersloop(0)
141+
PoweredNode(id), num_somersloop(0), built(false)
142142
{
143143
ChangeRecipe(recipe, id_generator);
144144
}
@@ -160,7 +160,7 @@ CraftNode::CraftNode(const ax::NodeEditor::NodeId id, const std::function<unsign
160160
}
161161
else
162162
{
163-
throw std::runtime_error("Unknown recip when loading craft node");
163+
throw std::runtime_error("Unknown recipe when loading craft node");
164164
}
165165

166166
num_somersloop = FractionalNumber(serialized["num_somersloop"].get<long long int>());
@@ -173,6 +173,7 @@ CraftNode::CraftNode(const ax::NodeEditor::NodeId id, const std::function<unsign
173173
{
174174
p->current_rate = p->base_rate * current_rate * (1 + num_somersloop * recipe->building->somersloop_mult);
175175
}
176+
built = serialized["built"].get<bool>();
176177
}
177178

178179
CraftNode::~CraftNode()
@@ -191,6 +192,7 @@ Json::Value CraftNode::Serialize() const
191192

192193
node["recipe"] = recipe->name;
193194
node["num_somersloop"] = num_somersloop.GetNumerator();
195+
node["built"] = built;
194196

195197
return node;
196198
}
@@ -509,6 +511,22 @@ void GroupNode::PropagateRateToSubnodes()
509511
}
510512
}
511513

514+
void GroupNode::SetBuiltState(const bool b)
515+
{
516+
for (size_t i = 0; i < nodes.size(); ++i)
517+
{
518+
if (nodes[i]->IsCraft())
519+
{
520+
static_cast<CraftNode*>(nodes[i].get())->built = b;
521+
}
522+
else if (nodes[i]->IsGroup())
523+
{
524+
static_cast<GroupNode*>(nodes[i].get())->SetBuiltState(b);
525+
}
526+
}
527+
UpdateDetails();
528+
}
529+
512530
void GroupNode::CreateInsOuts(const std::function<unsigned long long int()>& id_generator)
513531
{
514532
inputs.clear();
@@ -601,6 +619,7 @@ void GroupNode::CreateInsOuts(const std::function<unsigned long long int()>& id_
601619
void GroupNode::UpdateDetails()
602620
{
603621
total_machines = {};
622+
built_machines = {};
604623
detailed_machines = {};
605624
detailed_power_same_clock = {};
606625
detailed_power_last_underclock = {};
@@ -611,6 +630,7 @@ void GroupNode::UpdateDetails()
611630
{
612631
const CraftNode* node = static_cast<const CraftNode*>(n.get());
613632
total_machines[node->recipe->building->name] += node->current_rate;
633+
built_machines[node->recipe->building->name] += node->built ? node->current_rate : 0;
614634
detailed_machines[node->recipe->building->name][node->recipe] += node->current_rate;
615635
detailed_power_same_clock[node->recipe] += node->same_clock_power;
616636
detailed_power_last_underclock[node->recipe] += node->last_underclock_power;
@@ -622,6 +642,10 @@ void GroupNode::UpdateDetails()
622642
{
623643
total_machines[k] += v;
624644
}
645+
for (const auto& [k, v] : node->built_machines)
646+
{
647+
built_machines[k] += v;
648+
}
625649
for (const auto& [k, v] : node->detailed_machines)
626650
{
627651
for (const auto& [k2, v2] : v)

0 commit comments

Comments
 (0)