Skip to content

Commit 8575c74

Browse files
committed
Tuning: proof-of-concept addonpart conflict markers.
This implements proactive scanning for addonpart conflicts when the menu is refreshed. This incurs a big lag when opening the menu and likely a lot of spam in RoR.log, but it works. Currently it only displays red squares as conflict markers when one addonpart is hovered. The original conflict resolution logic is still present in `ResolveUnwantedAndTweakedElements()` - when a conflict is found, tweeaks for that elements are reset. But this is flawed, if there's an odd number of conflicting tweaks, the last one will pass. I'll remove it in next commits.
1 parent c91f285 commit 8575c74

File tree

4 files changed

+133
-3
lines changed

4 files changed

+133
-3
lines changed

source/main/gui/panels/GUI_TopMenubar.cpp

+31-2
Original file line numberDiff line numberDiff line change
@@ -1611,9 +1611,12 @@ void TopMenubar::Draw(float dt)
16111611
for (CacheEntryPtr& addonpart_entry: tuning_addonparts)
16121612
{
16131613
ImGui::PushID(addonpart_entry->fname.c_str());
1614-
1614+
bool conflict = tuning_hovered_addonpart
1615+
&& (addonpart_entry != tuning_hovered_addonpart)
1616+
&& AddonPartUtility::CheckForAddonpartConflict(tuning_hovered_addonpart, addonpart_entry, tuning_conflicts);
16151617
bool used = TuneupUtil::isAddonPartUsed(tuneup_def, addonpart_entry->fname);
1616-
if (ImGui::Checkbox(addonpart_entry->dname.c_str(), &used))
1618+
ImVec2 checkbox_cursor = ImGui::GetCursorScreenPos();
1619+
if (ImGui::Checkbox(addonpart_entry->dname.c_str(), &used) && !conflict)
16171620
{
16181621
ModifyProjectRequest* req = new ModifyProjectRequest();
16191622
req->mpr_type = (used)
@@ -1623,6 +1626,22 @@ void TopMenubar::Draw(float dt)
16231626
req->mpr_target_actor = tuning_actor;
16241627
App::GetGameContext()->PushMessage(Message(MSG_EDI_MODIFY_PROJECT_REQUESTED, req));
16251628
}
1629+
// Draw conflict marker
1630+
if (conflict)
1631+
{
1632+
ImVec2 min = checkbox_cursor + ImGui::GetStyle().FramePadding;
1633+
ImVec2 max = min + ImVec2(ImGui::GetTextLineHeight(), ImGui::GetTextLineHeight());
1634+
ImGui::GetWindowDrawList()->AddRectFilled(min, max, ImColor(0.7f, 0.1f, 0.f));
1635+
}
1636+
// Record when checkbox is hovered - for drawing conflict markers
1637+
if (ImGui::IsItemHovered())
1638+
{
1639+
tuning_hovered_addonpart = addonpart_entry;
1640+
}
1641+
else if (tuning_hovered_addonpart == addonpart_entry)
1642+
{
1643+
tuning_hovered_addonpart = nullptr;
1644+
}
16261645
// Reload button
16271646
ImGui::SameLine();
16281647
ImGui::Dummy(ImVec2(10.f, 1.f));
@@ -2341,6 +2360,16 @@ void TopMenubar::RefreshTuningMenu()
23412360
tuning_saves.resetResults();
23422361
App::GetCacheSystem()->Query(tuning_saves);
23432362

2363+
// Refresh `tuning_conflicts` database ~ test addonparts each with each once.
2364+
tuning_conflicts.clear();
2365+
for (size_t i1 = 0; i1 < tuning_addonparts.size(); i1++)
2366+
{
2367+
for (size_t i2 = i1; i2 < tuning_addonparts.size(); i2++)
2368+
{
2369+
AddonPartUtility::RecordAddonpartConflicts(tuning_addonparts[i1], tuning_addonparts[i2], tuning_conflicts);
2370+
}
2371+
}
2372+
23442373
tuning_rwidget_cursorx_min = 0.f;
23452374
}
23462375
else if (!App::sim_tuning_enabled->getBool() || !current_actor)

source/main/gui/panels/GUI_TopMenubar.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
#pragma once
2727

28+
#include "AddonPartFileFormat.h"
2829
#include "CacheSystem.h"
2930
#include "RoRnet.h"
3031

@@ -109,7 +110,8 @@ class TopMenubar
109110

110111
// Tuning menu
111112
ActorPtr tuning_actor; //!< Detecting actor change to update cached values.
112-
std::vector<CacheEntryPtr> tuning_addonparts; //!< Addonparts of current actor, both matched by GUID and force-installed by user via [browse all] button.
113+
std::vector<CacheEntryPtr> tuning_addonparts; //!< Addonparts eligible for current actor, both matched by GUID and force-installed by user via [browse all] button.
114+
AddonPartConflictVec tuning_conflicts; //!< Conflicts between eligible addonparts tweaking the same element.
113115
CacheQuery tuning_saves; //!< Tuneups saved by user, with category ID `RoR::CID_AddonpartUser`
114116
Str<200> tuning_savebox_buf; //!< Buffer for tuneup name to be saved
115117
bool tuning_savebox_visible = false; //!< User pressed 'save active' to open savebox.
@@ -118,6 +120,7 @@ class TopMenubar
118120
const float TUNING_HOLDTOCONFIRM_TIMELIMIT = 1.5f; //!< Delete button must be held for several sec to confirm.
119121
bool tuning_force_refresh = false;
120122
float tuning_rwidget_cursorx_min = 0.f; //!< Avoid drawing right-side widgets ('Delete' button or 'Protected' chk) over saved tuneup names.
123+
CacheEntryPtr tuning_hovered_addonpart;
121124
void RefreshTuningMenu();
122125

123126
private:

source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp

+84
Original file line numberDiff line numberDiff line change
@@ -567,3 +567,87 @@ void AddonPartUtility::ProcessTweakProp()
567567
LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword()));
568568
}
569569
}
570+
571+
void AddonPartUtility::RecordAddonpartConflicts(CacheEntryPtr addonpart1, CacheEntryPtr addonpart2, AddonPartConflictVec& conflicts)
572+
{
573+
LOG(fmt::format("[RoR|Addonpart] -- Performing `RecordAddonpartConflicts()` between '{}' and '{}' ~ this involves generating dummy tuneups (hence messages below) --", addonpart1->fname, addonpart2->fname));
574+
575+
// Load both addonparts to dummy Tuneup instances
576+
TuneupDefPtr dummy_t1 = new TuneupDef();
577+
App::GetCacheSystem()->LoadResource(addonpart1);
578+
AddonPartUtility util_t1;
579+
util_t1.ResolveUnwantedAndTweakedElements(dummy_t1, addonpart1);
580+
581+
TuneupDefPtr dummy_t2 = new TuneupDef();
582+
App::GetCacheSystem()->LoadResource(addonpart2);
583+
AddonPartUtility util_t2;
584+
util_t2.ResolveUnwantedAndTweakedElements(dummy_t2, addonpart2);
585+
586+
// NODE TWEAKS:
587+
for (size_t i = 0; i < dummy_t1->node_tweaks.size(); i++)
588+
{
589+
NodeNum_t suspect = dummy_t1->node_tweaks[i].tnt_nodenum;
590+
TuneupNodeTweak* offender = nullptr;
591+
if (TuneupUtil::isNodeTweaked(dummy_t2, suspect, offender))
592+
{
593+
conflicts.push_back(AddonPartConflict{addonpart1, addonpart2, "addonpart_tweak_node", (int)suspect});
594+
LOG(fmt::format("[RoR|Addonpart] Found conflict between '{}' and '{}' - node {} is tweaked by both", addonpart1->fname, addonpart2->fname, (int)suspect));
595+
}
596+
}
597+
598+
// WHEEL TWEAKS:
599+
for (size_t i = 0; i < dummy_t1->wheel_tweaks.size(); i++)
600+
{
601+
WheelID_t suspect = dummy_t1->wheel_tweaks[i].twt_wheel_id;
602+
TuneupWheelTweak* offender = nullptr;
603+
if (TuneupUtil::isWheelTweaked(dummy_t2, suspect, offender))
604+
{
605+
conflicts.push_back(AddonPartConflict{addonpart1, addonpart2, "addonpart_tweak_wheel", (int)suspect});
606+
LOG(fmt::format("[RoR|Addonpart] Found conflict between '{}' and '{}' - wheel {} is tweaked by both", addonpart1->fname, addonpart2->fname, (int)suspect));
607+
}
608+
}
609+
610+
// PROP TWEAKS:
611+
for (size_t i = 0; i < dummy_t1->prop_tweaks.size(); i++)
612+
{
613+
PropID_t suspect = dummy_t1->prop_tweaks[i].tpt_prop_id;
614+
TuneupPropTweak* offender = nullptr;
615+
if (TuneupUtil::isPropTweaked(dummy_t2, suspect, offender))
616+
{
617+
conflicts.push_back(AddonPartConflict{addonpart1, addonpart2, "addonpart_tweak_prop", (int)suspect});
618+
LOG(fmt::format("[RoR|Addonpart] Found conflict between '{}' and '{}' - prop {} is tweaked by both", addonpart1->fname, addonpart2->fname, (int)suspect));
619+
}
620+
}
621+
622+
// FLEXBODY TWEAKS:
623+
for (size_t i = 0; i < dummy_t1->flexbody_tweaks.size(); i++)
624+
{
625+
FlexbodyID_t suspect = dummy_t1->flexbody_tweaks[i].tft_flexbody_id;
626+
TuneupFlexbodyTweak* offender = nullptr;
627+
if (TuneupUtil::isFlexbodyTweaked(dummy_t2, suspect, offender))
628+
{
629+
conflicts.push_back(AddonPartConflict{addonpart1, addonpart2, "addonpart_tweak_flexbody", (int)suspect});
630+
LOG(fmt::format("[RoR|Addonpart] Found conflict between '{}' and '{}' - flexbody {} is tweaked by both", addonpart1->fname, addonpart2->fname, (int)suspect));
631+
}
632+
}
633+
634+
LOG(fmt::format("[RoR|Addonpart] -- Done with `RecordAddonpartConflicts()` between '{}' and '{}' --", addonpart1->fname, addonpart2->fname));
635+
}
636+
637+
bool AddonPartUtility::CheckForAddonpartConflict(CacheEntryPtr addonpart1, CacheEntryPtr addonpart2, AddonPartConflictVec& conflicts)
638+
{
639+
if (!addonpart1 || !addonpart2)
640+
{
641+
return false;
642+
}
643+
644+
for (AddonPartConflict& conflict: conflicts)
645+
{
646+
if ((conflict.atc_addonpart1 == addonpart1 && conflict.atc_addonpart2 == addonpart2) ||
647+
(conflict.atc_addonpart1 == addonpart2 && conflict.atc_addonpart2 == addonpart1))
648+
{
649+
return true;
650+
}
651+
}
652+
return false;
653+
}

source/main/resources/addonpart_fileformat/AddonPartFileFormat.h

+14
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@
3535

3636
namespace RoR {
3737

38+
struct AddonPartConflict //!< Conflict between two addonparts tweaking the same element
39+
{
40+
CacheEntryPtr atc_addonpart1;
41+
CacheEntryPtr atc_addonpart2;
42+
std::string atc_keyword;
43+
int atc_element_id = -1;
44+
};
45+
46+
typedef std::vector<AddonPartConflict> AddonPartConflictVec;
47+
3848
/// NOTE: Modcache processes this format directly using `RoR::GenericDocument`, see `RoR::CacheSystem::FillAddonPartDetailInfo()`
3949
class AddonPartUtility
4050
{
@@ -52,6 +62,10 @@ class AddonPartUtility
5262

5363
static void ResetUnwantedAndTweakedElements(TuneupDefPtr& tuneup);
5464

65+
static void RecordAddonpartConflicts(CacheEntryPtr addonpart1, CacheEntryPtr addonpart2, AddonPartConflictVec& conflicts);
66+
67+
static bool CheckForAddonpartConflict(CacheEntryPtr addonpart1, CacheEntryPtr addonpart2, AddonPartConflictVec& conflicts);
68+
5569
private:
5670
// Helpers of `TransformToRigDefModule()`, they expect `m_context` to be in position:
5771
void ProcessManagedMaterial();

0 commit comments

Comments
 (0)