Skip to content

Commit

Permalink
Tuning: proof-of-concept addonpart conflict markers.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ohlidalp committed Apr 23, 2024
1 parent c91f285 commit 8575c74
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 3 deletions.
33 changes: 31 additions & 2 deletions source/main/gui/panels/GUI_TopMenubar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1611,9 +1611,12 @@ void TopMenubar::Draw(float dt)
for (CacheEntryPtr& addonpart_entry: tuning_addonparts)
{
ImGui::PushID(addonpart_entry->fname.c_str());

bool conflict = tuning_hovered_addonpart
&& (addonpart_entry != tuning_hovered_addonpart)
&& AddonPartUtility::CheckForAddonpartConflict(tuning_hovered_addonpart, addonpart_entry, tuning_conflicts);
bool used = TuneupUtil::isAddonPartUsed(tuneup_def, addonpart_entry->fname);
if (ImGui::Checkbox(addonpart_entry->dname.c_str(), &used))
ImVec2 checkbox_cursor = ImGui::GetCursorScreenPos();
if (ImGui::Checkbox(addonpart_entry->dname.c_str(), &used) && !conflict)
{
ModifyProjectRequest* req = new ModifyProjectRequest();
req->mpr_type = (used)
Expand All @@ -1623,6 +1626,22 @@ void TopMenubar::Draw(float dt)
req->mpr_target_actor = tuning_actor;
App::GetGameContext()->PushMessage(Message(MSG_EDI_MODIFY_PROJECT_REQUESTED, req));
}
// Draw conflict marker
if (conflict)
{
ImVec2 min = checkbox_cursor + ImGui::GetStyle().FramePadding;
ImVec2 max = min + ImVec2(ImGui::GetTextLineHeight(), ImGui::GetTextLineHeight());
ImGui::GetWindowDrawList()->AddRectFilled(min, max, ImColor(0.7f, 0.1f, 0.f));
}
// Record when checkbox is hovered - for drawing conflict markers
if (ImGui::IsItemHovered())
{
tuning_hovered_addonpart = addonpart_entry;
}
else if (tuning_hovered_addonpart == addonpart_entry)
{
tuning_hovered_addonpart = nullptr;
}
// Reload button
ImGui::SameLine();
ImGui::Dummy(ImVec2(10.f, 1.f));
Expand Down Expand Up @@ -2341,6 +2360,16 @@ void TopMenubar::RefreshTuningMenu()
tuning_saves.resetResults();
App::GetCacheSystem()->Query(tuning_saves);

// Refresh `tuning_conflicts` database ~ test addonparts each with each once.
tuning_conflicts.clear();
for (size_t i1 = 0; i1 < tuning_addonparts.size(); i1++)
{
for (size_t i2 = i1; i2 < tuning_addonparts.size(); i2++)
{
AddonPartUtility::RecordAddonpartConflicts(tuning_addonparts[i1], tuning_addonparts[i2], tuning_conflicts);
}
}

tuning_rwidget_cursorx_min = 0.f;
}
else if (!App::sim_tuning_enabled->getBool() || !current_actor)
Expand Down
5 changes: 4 additions & 1 deletion source/main/gui/panels/GUI_TopMenubar.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#pragma once

#include "AddonPartFileFormat.h"
#include "CacheSystem.h"
#include "RoRnet.h"

Expand Down Expand Up @@ -109,7 +110,8 @@ class TopMenubar

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

private:
Expand Down
84 changes: 84 additions & 0 deletions source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -567,3 +567,87 @@ void AddonPartUtility::ProcessTweakProp()
LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword()));
}
}

void AddonPartUtility::RecordAddonpartConflicts(CacheEntryPtr addonpart1, CacheEntryPtr addonpart2, AddonPartConflictVec& conflicts)
{
LOG(fmt::format("[RoR|Addonpart] -- Performing `RecordAddonpartConflicts()` between '{}' and '{}' ~ this involves generating dummy tuneups (hence messages below) --", addonpart1->fname, addonpart2->fname));

// Load both addonparts to dummy Tuneup instances
TuneupDefPtr dummy_t1 = new TuneupDef();
App::GetCacheSystem()->LoadResource(addonpart1);
AddonPartUtility util_t1;
util_t1.ResolveUnwantedAndTweakedElements(dummy_t1, addonpart1);

TuneupDefPtr dummy_t2 = new TuneupDef();
App::GetCacheSystem()->LoadResource(addonpart2);
AddonPartUtility util_t2;
util_t2.ResolveUnwantedAndTweakedElements(dummy_t2, addonpart2);

// NODE TWEAKS:
for (size_t i = 0; i < dummy_t1->node_tweaks.size(); i++)
{
NodeNum_t suspect = dummy_t1->node_tweaks[i].tnt_nodenum;
TuneupNodeTweak* offender = nullptr;
if (TuneupUtil::isNodeTweaked(dummy_t2, suspect, offender))
{
conflicts.push_back(AddonPartConflict{addonpart1, addonpart2, "addonpart_tweak_node", (int)suspect});
LOG(fmt::format("[RoR|Addonpart] Found conflict between '{}' and '{}' - node {} is tweaked by both", addonpart1->fname, addonpart2->fname, (int)suspect));
}
}

// WHEEL TWEAKS:
for (size_t i = 0; i < dummy_t1->wheel_tweaks.size(); i++)
{
WheelID_t suspect = dummy_t1->wheel_tweaks[i].twt_wheel_id;
TuneupWheelTweak* offender = nullptr;
if (TuneupUtil::isWheelTweaked(dummy_t2, suspect, offender))
{
conflicts.push_back(AddonPartConflict{addonpart1, addonpart2, "addonpart_tweak_wheel", (int)suspect});
LOG(fmt::format("[RoR|Addonpart] Found conflict between '{}' and '{}' - wheel {} is tweaked by both", addonpart1->fname, addonpart2->fname, (int)suspect));
}
}

// PROP TWEAKS:
for (size_t i = 0; i < dummy_t1->prop_tweaks.size(); i++)
{
PropID_t suspect = dummy_t1->prop_tweaks[i].tpt_prop_id;
TuneupPropTweak* offender = nullptr;
if (TuneupUtil::isPropTweaked(dummy_t2, suspect, offender))
{
conflicts.push_back(AddonPartConflict{addonpart1, addonpart2, "addonpart_tweak_prop", (int)suspect});
LOG(fmt::format("[RoR|Addonpart] Found conflict between '{}' and '{}' - prop {} is tweaked by both", addonpart1->fname, addonpart2->fname, (int)suspect));
}
}

// FLEXBODY TWEAKS:
for (size_t i = 0; i < dummy_t1->flexbody_tweaks.size(); i++)
{
FlexbodyID_t suspect = dummy_t1->flexbody_tweaks[i].tft_flexbody_id;
TuneupFlexbodyTweak* offender = nullptr;
if (TuneupUtil::isFlexbodyTweaked(dummy_t2, suspect, offender))
{
conflicts.push_back(AddonPartConflict{addonpart1, addonpart2, "addonpart_tweak_flexbody", (int)suspect});
LOG(fmt::format("[RoR|Addonpart] Found conflict between '{}' and '{}' - flexbody {} is tweaked by both", addonpart1->fname, addonpart2->fname, (int)suspect));
}
}

LOG(fmt::format("[RoR|Addonpart] -- Done with `RecordAddonpartConflicts()` between '{}' and '{}' --", addonpart1->fname, addonpart2->fname));
}

bool AddonPartUtility::CheckForAddonpartConflict(CacheEntryPtr addonpart1, CacheEntryPtr addonpart2, AddonPartConflictVec& conflicts)
{
if (!addonpart1 || !addonpart2)
{
return false;
}

for (AddonPartConflict& conflict: conflicts)
{
if ((conflict.atc_addonpart1 == addonpart1 && conflict.atc_addonpart2 == addonpart2) ||
(conflict.atc_addonpart1 == addonpart2 && conflict.atc_addonpart2 == addonpart1))
{
return true;
}
}
return false;
}
14 changes: 14 additions & 0 deletions source/main/resources/addonpart_fileformat/AddonPartFileFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@

namespace RoR {

struct AddonPartConflict //!< Conflict between two addonparts tweaking the same element
{
CacheEntryPtr atc_addonpart1;
CacheEntryPtr atc_addonpart2;
std::string atc_keyword;
int atc_element_id = -1;
};

typedef std::vector<AddonPartConflict> AddonPartConflictVec;

/// NOTE: Modcache processes this format directly using `RoR::GenericDocument`, see `RoR::CacheSystem::FillAddonPartDetailInfo()`
class AddonPartUtility
{
Expand All @@ -52,6 +62,10 @@ class AddonPartUtility

static void ResetUnwantedAndTweakedElements(TuneupDefPtr& tuneup);

static void RecordAddonpartConflicts(CacheEntryPtr addonpart1, CacheEntryPtr addonpart2, AddonPartConflictVec& conflicts);

static bool CheckForAddonpartConflict(CacheEntryPtr addonpart1, CacheEntryPtr addonpart2, AddonPartConflictVec& conflicts);

private:
// Helpers of `TransformToRigDefModule()`, they expect `m_context` to be in position:
void ProcessManagedMaterial();
Expand Down

0 comments on commit 8575c74

Please sign in to comment.