From fae9af4a09481392274335f87557609e7424d5ac Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Sat, 13 Apr 2024 01:48:56 +0200 Subject: [PATCH 01/19] Tuning: fixed typo in 'protected' check for props. --- .../main/resources/addonpart_fileformat/AddonPartFileFormat.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp index 7590296ded..b3975eefe2 100644 --- a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp +++ b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp @@ -522,7 +522,7 @@ void AddonPartUtility::ProcessTweakProp() m_context->isTokString(8)) // media { const int prop_id = (int)m_context->getTokFloat(1); - if (!m_tuneup->isFlexbodyProtected(prop_id)) + if (!m_tuneup->isPropProtected(prop_id)) { if (m_tuneup->prop_tweaks.find(prop_id) == m_tuneup->prop_tweaks.end()) { From 9ba31a1b33403cbca439f25623910e622c6acd24 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Sat, 13 Apr 2024 02:14:39 +0200 Subject: [PATCH 02/19] Tuning: corrected logs about processing tweaks from addonparts. INFO log about adding tweak from addonpart to tuneup was changed from "tweaking *** with params" to "Scheduling tweak for *** with params" to clarify it's not applied yet. INFO log about withdrawing tweak from tuneup due to another addonpart trying to tweak the same element was changed from "conflict of tweaks at ***" to WARNING "Resetting tweaks for *** due to conflict" The idea was that 2 or more addonparts cannot tweak the same element. There is a flaw in this logic though: if there are 3 conflicting addonparts, the second one will reset the first one, but the third one will pass. And so on. Additionally this commit fixes terminology in the logs: addonpart keywords are now called 'directives', not 'elements' ... because elements mean the things actors consist of (wheels/props/flexbodies/nodes etc...). --- .../AddonPartFileFormat.cpp | 72 +++++++++---------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp index b3975eefe2..1e4d7076e0 100644 --- a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp +++ b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp @@ -124,8 +124,8 @@ std::shared_ptr AddonPartUtility::TransformToRigDefModule(Cach void AddonPartUtility::ResolveUnwantedAndTweakedElements(TuneupDefPtr& tuneup, CacheEntryPtr& addonpart_entry) { - // Evaluates 'addonpart_unwanted_*' elements, respecting 'protected_*' directives in the tuneup. - // Also handles 'addonpart_tweak_*' elements, resolving possible conflicts among used parts. + // Evaluates 'addonpart_unwanted_*' directives, respecting 'protected_*' directives in the tuneup. + // Also handles 'addonpart_tweak_*' directives, resolving possible conflicts among used addonparts. // --------------------------------------------------------------------------------------------- App::GetCacheSystem()->LoadResource(addonpart_entry); @@ -325,18 +325,18 @@ void AddonPartUtility::ProcessUnwantedProp() if (!m_tuneup->isPropProtected((PropID_t)m_context->getTokFloat(1))) { m_tuneup->unwanted_props.insert((PropID_t)m_context->getTokFloat(1)); - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': marking prop '{}' as UNWANTED", + LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': marking prop '{}' as UNWANTED", m_addonpart_entry->fname, m_context->getTokKeyword(), (int)m_context->getTokFloat(1))); } else { - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': skipping prop '{}' because it's marked PROTECTED", + LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping prop '{}' because it's marked PROTECTED", m_addonpart_entry->fname, m_context->getTokKeyword(), m_context->getTokString(1))); } } else { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', element '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); + LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); } } @@ -349,18 +349,18 @@ void AddonPartUtility::ProcessUnwantedFlexbody() if (!m_tuneup->isFlexbodyProtected((FlexbodyID_t)m_context->getTokFloat(1))) { m_tuneup->unwanted_flexbodies.insert((FlexbodyID_t)m_context->getTokFloat(1)); - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': marking flexbody '{}' as UNWANTED", + LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': marking flexbody '{}' as UNWANTED", m_addonpart_entry->fname, m_context->getTokKeyword(), (int)m_context->getTokFloat(1))); } else { - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': skipping flexbody '{}' because it's marked PROTECTED", + LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping flexbody '{}' because it's marked PROTECTED", m_addonpart_entry->fname, m_context->getTokKeyword(), m_context->getTokString(1))); } } else { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', element '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); + LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); } } @@ -386,29 +386,29 @@ void AddonPartUtility::ProcessTweakWheel() if (!stop && m_context->isTokFloat(6)) { data.twt_rim_radius = m_context->getTokFloat(6); } else { stop=true; } m_tuneup->wheel_tweaks.insert(std::make_pair(wheel_id, data)); - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': tweaking wheel {}" + LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': Sheduling tweak for wheel '{}'" " with params {{ media1={}, media2={}, side={}, tire_radius={}, rim_radius={} }}", m_addonpart_entry->fname, m_context->getTokKeyword(), wheel_id, data.twt_media[0], data.twt_media[1], (char)data.twt_side, data.twt_tire_radius, data.twt_rim_radius)); } else if (m_tuneup->wheel_tweaks[wheel_id].twt_origin != m_addonpart_entry->fname) { - m_tuneup->wheel_tweaks.erase(wheel_id); - - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': Conflict of tweaks at wheel '{}', addon parts '{}' and '{}'", + LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': Resetting tweaks for wheel '{}' due to conflict with '{}'", m_addonpart_entry->fname, m_context->getTokKeyword(), wheel_id, - m_tuneup->wheel_tweaks[wheel_id].twt_origin, m_addonpart_entry->fname)); + m_tuneup->wheel_tweaks[wheel_id].twt_origin)); + + m_tuneup->wheel_tweaks.erase(wheel_id); } } else { - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': skipping wheel '{}' because it's marked PROTECTED", + LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping wheel '{}' because it's marked PROTECTED", m_addonpart_entry->fname, m_context->getTokKeyword(), (int)m_context->getTokFloat(1))); } } else { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', element '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); + LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); } } @@ -431,29 +431,29 @@ void AddonPartUtility::ProcessTweakNode() data.tnt_pos.z = m_context->getTokFloat(4); m_tuneup->node_tweaks.insert(std::make_pair(nodenum, data)); - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': tweaking node {}" + LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': Scheduling tweak for node '{}'" " with params {{ x={}, y={}, z={} }}", m_addonpart_entry->fname, m_context->getTokKeyword(), nodenum, data.tnt_pos.x, data.tnt_pos.y, data.tnt_pos.z)); } else if (m_tuneup->node_tweaks[nodenum].tnt_origin != m_addonpart_entry->fname) { - m_tuneup->node_tweaks.erase(nodenum); - - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': Conflict of tweaks at node '{}', addon parts '{}' and '{}'", + LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': Resetting tweaks for node '{}' due to conflict with '{}'", m_addonpart_entry->fname, m_context->getTokKeyword(), nodenum, - m_tuneup->node_tweaks[nodenum].tnt_origin, m_addonpart_entry->fname)); + m_tuneup->node_tweaks[nodenum].tnt_origin)); + + m_tuneup->node_tweaks.erase(nodenum); } } else { - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': skipping node '{}' because it's marked PROTECTED", + LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping node '{}' because it's marked PROTECTED", m_addonpart_entry->fname, m_context->getTokKeyword(), nodenum)); } } else { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', element '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); + LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); } } @@ -484,7 +484,7 @@ void AddonPartUtility::ProcessTweakFlexbody() data.tft_media = m_context->getTokString(8); m_tuneup->flexbody_tweaks.insert(std::make_pair(flexbody_id, data)); - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': tweaking flexbody {}" + LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': Scheduling tweak for flexbody '{}'" " with params {{ offsetX={}, offsetY={}, offsetZ={}, rotX={}, rotY={}, rotZ={}, media={} }}", m_addonpart_entry->fname, m_context->getTokKeyword(), flexbody_id, data.tft_offset.x, data.tft_offset.y, data.tft_offset.z, @@ -492,22 +492,22 @@ void AddonPartUtility::ProcessTweakFlexbody() } else if (m_tuneup->flexbody_tweaks[flexbody_id].tft_origin != m_addonpart_entry->fname) { - m_tuneup->flexbody_tweaks.erase(flexbody_id); - - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': Conflict of tweaks at flexbody '{}', addon parts '{}' and '{}'", + LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': Resetting tweaks for flexbody '{}' due to conflict with '{}'", m_addonpart_entry->fname, m_context->getTokKeyword(), flexbody_id, - m_tuneup->flexbody_tweaks[flexbody_id].tft_origin, m_addonpart_entry->fname)); + m_tuneup->flexbody_tweaks[flexbody_id].tft_origin)); + + m_tuneup->flexbody_tweaks.erase(flexbody_id); } } else { - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': skipping flexbody '{}' because it's marked PROTECTED", + LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping flexbody '{}' because it's marked PROTECTED", m_addonpart_entry->fname, m_context->getTokKeyword(), (int)m_context->getTokFloat(1))); } } else { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', element '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); + LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); } } @@ -540,7 +540,7 @@ void AddonPartUtility::ProcessTweakProp() if (m_context->isTokString(9)) data.tpt_media[1] = m_context->getTokString(9); // <== Optional Media2 is specific for prop m_tuneup->prop_tweaks.insert(std::make_pair(prop_id, data)); - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': tweaking prop {}" + LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': Scheduling tweak for prop '{}'" " with params {{ media1={}, offsetX={}, offsetY={}, offsetZ={}, rotX={}, rotY={}, rotZ={}, media2={} }}", m_addonpart_entry->fname, m_context->getTokKeyword(), prop_id, data.tpt_media[0], data.tpt_offset.x, data.tpt_offset.y, data.tpt_offset.z, @@ -549,21 +549,21 @@ void AddonPartUtility::ProcessTweakProp() } else if (m_tuneup->prop_tweaks[prop_id].tpt_origin != m_addonpart_entry->fname) { - m_tuneup->prop_tweaks.erase(prop_id); - - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': Conflict of tweaks at prop '{}', addon parts '{}' and '{}'", + LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': Resetting tweaks for prop '{}' due to conflict with '{}'", m_addonpart_entry->fname, m_context->getTokKeyword(), prop_id, - m_tuneup->prop_tweaks[prop_id].tpt_origin, m_addonpart_entry->fname)); + m_tuneup->prop_tweaks[prop_id].tpt_origin)); + + m_tuneup->prop_tweaks.erase(prop_id); } } else { - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', element '{}': skipping prop '{}' because it's marked PROTECTED", + LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping prop '{}' because it's marked PROTECTED", m_addonpart_entry->fname, m_context->getTokKeyword(), (int)m_context->getTokFloat(1))); } } else { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', element '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); + LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); } } From a358909bc3f112c6653d5a3db0e7ce6a5e87e74f Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Sat, 13 Apr 2024 12:17:54 +0200 Subject: [PATCH 03/19] :bug: Fixed FlexbodyDebug UI 'hide other' not working for flexbodies. Broken in 9551d053bfb2cc4527c87213ab95b43fc044993b - since this commit flexbodies got a dynamic visibility control, exactly the same as props. From user (truck fileformat) perspective, it was the same already but the update pattern was different, and this commit unified it. The FlexbodyDebug UI didn't reflect this however. In addition to fixing the glitch, this commit also unifies code for the visibility handling - same variables, same constants, just different prefixes. Codechanges: * GfxData: cosmetic changes to CAMERA_MODE constants - added typedef * FlexBody: replaced single visibility mode field with _active+_orig fields, same as props have. Also using the same coding style as props do. * GfxActor: cosmetic change - use the new variables in flexbody. * ActorSpawner: set flexbody visibility via the new vars. --- source/main/gfx/GfxActor.cpp | 7 +++---- source/main/gfx/GfxData.h | 15 +++++++++------ source/main/gui/panels/GUI_FlexbodyDebug.cpp | 15 ++++++++++++++- source/main/physics/ActorSpawner.cpp | 4 +++- source/main/physics/flex/FlexBody.cpp | 1 + source/main/physics/flex/FlexBody.h | 12 +++++++----- 6 files changed, 37 insertions(+), 17 deletions(-) diff --git a/source/main/gfx/GfxActor.cpp b/source/main/gfx/GfxActor.cpp index a9b389f101..309c89fd70 100644 --- a/source/main/gfx/GfxActor.cpp +++ b/source/main/gfx/GfxActor.cpp @@ -3010,10 +3010,9 @@ void RoR::GfxActor::UpdateFlexbodies() for (FlexBody* fb: m_flexbodies) { - // Update visibility - fb->setVisible( - fb->getCameraMode() == CAMERA_MODE_ALWAYS_VISIBLE - || fb->getCameraMode() == m_simbuf.simbuf_cur_cinecam); + // Update visibility (same logic as props) + const bool visible = (fb->fb_camera_mode_active == CAMERA_MODE_ALWAYS_VISIBLE || fb->fb_camera_mode_active == m_simbuf.simbuf_cur_cinecam); + fb->setVisible(visible); // Update visible on background thread if (fb->isVisible()) diff --git a/source/main/gfx/GfxData.h b/source/main/gfx/GfxData.h index e445d9679a..f8272ed927 100644 --- a/source/main/gfx/GfxData.h +++ b/source/main/gfx/GfxData.h @@ -120,9 +120,10 @@ enum class WheelSide: char }; // Dynamic visibility control (value 0 and higher is cinecam index) - common to 'props' and 'flexbodies' -static const int CAMERA_MODE_ALWAYS_HIDDEN = -3; -static const int CAMERA_MODE_ALWAYS_VISIBLE = -2; -static const int CAMERA_MODE_3RDPERSON_ONLY = -1; +typedef int CameraMode_t; +static CameraMode_t CAMERA_MODE_ALWAYS_HIDDEN = -3; +static CameraMode_t CAMERA_MODE_ALWAYS_VISIBLE = -2; +static CameraMode_t CAMERA_MODE_3RDPERSON_ONLY = -1; enum ShifterPropAnim { @@ -171,9 +172,11 @@ struct Prop std::string pp_media[2]; //!< Redundant, for Tuning UI. Media1 = prop mesh name, Media2 = steeringwheel mesh/beaconprop flare mat. std::vector pp_animations; - // Visibility control - int pp_camera_mode_active = CAMERA_MODE_ALWAYS_VISIBLE; //!< Dynamic visibility mode {0 and higher = cinecam index} - int pp_camera_mode_orig = CAMERA_MODE_ALWAYS_VISIBLE; //!< Dynamic visibility mode {0 and higher = cinecam index} + /// @name Visibility control (same as flexbody - see file FlexBody.h) + /// @{ + CameraMode_t pp_camera_mode_active = CAMERA_MODE_ALWAYS_VISIBLE; //!< Dynamic visibility mode {0 and higher = cinecam index} + CameraMode_t pp_camera_mode_orig = CAMERA_MODE_ALWAYS_VISIBLE; //!< Dynamic visibility mode {0 and higher = cinecam index} + /// @} // Special prop - steering wheel MeshObject* pp_wheel_mesh_obj = nullptr; diff --git a/source/main/gui/panels/GUI_FlexbodyDebug.cpp b/source/main/gui/panels/GUI_FlexbodyDebug.cpp index ddd42f5595..b58d5f9ca5 100644 --- a/source/main/gui/panels/GUI_FlexbodyDebug.cpp +++ b/source/main/gui/panels/GUI_FlexbodyDebug.cpp @@ -426,6 +426,9 @@ void FlexbodyDebug::DrawDebugView(FlexBody* flexbody, Prop* prop, NodeNum_t node void FlexbodyDebug::UpdateVisibility() { + // Both flexbodies and props use the same dynamic visibility mode, see `CameraMode_t` typedef and constants in file GfxData.h + // --------------------------------------------------------------------------------------------------------------------------- + ActorPtr actor = App::GetGameContext()->GetPlayerActor(); if (!actor) { @@ -442,13 +445,18 @@ void FlexbodyDebug::UpdateVisibility() { prop.pp_camera_mode_active = CAMERA_MODE_ALWAYS_HIDDEN; } + // Override flexbody dynamic visibility mode + for (FlexBody* flexbody: actor->GetGfxActor()->GetFlexbodies()) + { + flexbody->fb_camera_mode_active = CAMERA_MODE_ALWAYS_HIDDEN; + } // Then re-display what we need manually. auto& flexbody_vec = actor->GetGfxActor()->GetFlexbodies(); const int combo_flexbody_selection = m_combo_selection; if (combo_flexbody_selection >= 0 && combo_flexbody_selection < (int)flexbody_vec.size()) { - flexbody_vec[combo_flexbody_selection]->setVisible(true); + flexbody_vec[combo_flexbody_selection]->fb_camera_mode_active = CAMERA_MODE_ALWAYS_VISIBLE; } auto& prop_vec = actor->GetGfxActor()->getProps(); @@ -472,6 +480,11 @@ void FlexbodyDebug::UpdateVisibility() { prop.pp_camera_mode_active = prop.pp_camera_mode_orig; } + // Restore flexbody dynamic visibility mode + for (FlexBody* flexbody: actor->GetGfxActor()->GetFlexbodies()) + { + flexbody->fb_camera_mode_active = flexbody->fb_camera_mode_orig; + } } } diff --git a/source/main/physics/ActorSpawner.cpp b/source/main/physics/ActorSpawner.cpp index 9cc1667c31..1e59a72c57 100644 --- a/source/main/physics/ActorSpawner.cpp +++ b/source/main/physics/ActorSpawner.cpp @@ -1562,7 +1562,9 @@ void ActorSpawner::ProcessFlexbody(RigDef::Flexbody& def) if (flexbody == nullptr) return; // Error already logged - flexbody->setCameraMode(def.camera_settings.mode); + // Dynamic visibility - same as with props + flexbody->fb_camera_mode_orig = def.camera_settings.mode; + flexbody->fb_camera_mode_active = def.camera_settings.mode; m_actor->m_gfx_actor->m_flexbodies.emplace_back(flexbody); } diff --git a/source/main/physics/flex/FlexBody.cpp b/source/main/physics/flex/FlexBody.cpp index d52d0ffca9..faf14b7e9d 100644 --- a/source/main/physics/flex/FlexBody.cpp +++ b/source/main/physics/flex/FlexBody.cpp @@ -567,6 +567,7 @@ bool FlexBody::isVisible() const void FlexBody::setVisible(bool visible) { + // Scene node is NULL if disabled via addonpart/tuneup. if (m_scene_node) m_scene_node->setVisible(visible); } diff --git a/source/main/physics/flex/FlexBody.h b/source/main/physics/flex/FlexBody.h index 2e147c750d..31d99e6600 100644 --- a/source/main/physics/flex/FlexBody.h +++ b/source/main/physics/flex/FlexBody.h @@ -25,6 +25,7 @@ #include "Application.h" #include "Locator_t.h" #include "SimData.h" +#include "GfxData.h" #include "RigDef_File.h" #include "Utils.h" @@ -61,6 +62,12 @@ class FlexBody typedef int FlexBodyPlaceholder_t; static const FlexBodyPlaceholder_t TUNING_PLACEHOLDER = -11; + /// @name Visibility control (same as prop - see file GfxData.h) + /// @{ + CameraMode_t fb_camera_mode_active = CAMERA_MODE_ALWAYS_VISIBLE; //!< Dynamic visibility mode {0 and higher = cinecam index} + CameraMode_t fb_camera_mode_orig = CAMERA_MODE_ALWAYS_VISIBLE; //!< Dynamic visibility mode {0 and higher = cinecam index} + /// @} + FlexBody(FlexBodyPlaceholder_t, FlexbodyID_t id, const std::string& orig_meshname); ~FlexBody(); @@ -68,11 +75,6 @@ class FlexBody void updateBlend(); void writeBlend(); - /// Visibility control - /// @param mode {-2 = always, -1 = 3rdPerson only, 0+ = cinecam index} - void setCameraMode(int mode) { m_camera_mode = mode; }; - int getCameraMode() { return m_camera_mode; }; - void computeFlexbody(); //!< Updates mesh deformation; works on CPU using local copy of vertex data. void updateFlexbodyVertexBuffers(); From f42d93f37811d5ab86adf4d3d257e691290bade9 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Tue, 23 Apr 2024 02:29:37 +0200 Subject: [PATCH 04/19] 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. --- source/main/gui/panels/GUI_TopMenubar.cpp | 33 +++++++- source/main/gui/panels/GUI_TopMenubar.h | 5 +- .../AddonPartFileFormat.cpp | 84 +++++++++++++++++++ .../AddonPartFileFormat.h | 14 ++++ 4 files changed, 133 insertions(+), 3 deletions(-) diff --git a/source/main/gui/panels/GUI_TopMenubar.cpp b/source/main/gui/panels/GUI_TopMenubar.cpp index 8c1f5de7e3..2a7cae77e5 100644 --- a/source/main/gui/panels/GUI_TopMenubar.cpp +++ b/source/main/gui/panels/GUI_TopMenubar.cpp @@ -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) @@ -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)); @@ -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) diff --git a/source/main/gui/panels/GUI_TopMenubar.h b/source/main/gui/panels/GUI_TopMenubar.h index 25279d3d25..517a8a06cc 100644 --- a/source/main/gui/panels/GUI_TopMenubar.h +++ b/source/main/gui/panels/GUI_TopMenubar.h @@ -25,6 +25,7 @@ #pragma once +#include "AddonPartFileFormat.h" #include "CacheSystem.h" #include "RoRnet.h" @@ -109,7 +110,8 @@ class TopMenubar // Tuning menu ActorPtr tuning_actor; //!< Detecting actor change to update cached values. - std::vector tuning_addonparts; //!< Addonparts of current actor, both matched by GUID and force-installed by user via [browse all] button. + std::vector 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. @@ -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: diff --git a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp index 1e4d7076e0..39edb349b0 100644 --- a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp +++ b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp @@ -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; +} diff --git a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h index 95184d81f7..96d4b76c71 100644 --- a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h +++ b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h @@ -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 AddonPartConflictVec; + /// NOTE: Modcache processes this format directly using `RoR::GenericDocument`, see `RoR::CacheSystem::FillAddonPartDetailInfo()` class AddonPartUtility { @@ -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(); From 5bce7114185933186054cbd178afdc4d6b34f06a Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Tue, 23 Apr 2024 09:05:17 +0200 Subject: [PATCH 05/19] Tuning: disabled for multiplayer. --- source/main/GameContext.cpp | 2 +- source/main/gui/panels/GUI_TopMenubar.cpp | 22 ++++++++++++++++++---- source/main/gui/panels/GUI_TopMenubar.h | 2 ++ source/main/main.cpp | 5 ++++- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/source/main/GameContext.cpp b/source/main/GameContext.cpp index ed4cb3d44d..01ea8a26ab 100644 --- a/source/main/GameContext.cpp +++ b/source/main/GameContext.cpp @@ -243,7 +243,7 @@ ActorPtr GameContext::SpawnActor(ActorSpawnRequest& rq) } } - if (App::sim_tuning_enabled->getBool()) + if (App::sim_tuning_enabled->getBool() && (App::mp_state->getEnum() != MpState::CONNECTED)) { if (rq.asr_tuneup_entry) { diff --git a/source/main/gui/panels/GUI_TopMenubar.cpp b/source/main/gui/panels/GUI_TopMenubar.cpp index 2a7cae77e5..8233112f5a 100644 --- a/source/main/gui/panels/GUI_TopMenubar.cpp +++ b/source/main/gui/panels/GUI_TopMenubar.cpp @@ -151,13 +151,13 @@ void TopMenubar::Draw(float dt) ImGui::CalcTextSize(settings_title.c_str()).x + ImGui::CalcTextSize(tools_title.c_str()).x; - if (App::mp_state->getEnum() != MpState::CONNECTED) + if (this->IsMenuEnabled(TopMenu::TOPMENU_AI)) { menubar_num_buttons += 1; menubar_content_width += ImGui::CalcTextSize(ai_title.c_str()).x; } - if (App::sim_tuning_enabled->getBool()) + if (this->IsMenuEnabled(TopMenu::TOPMENU_TUNING)) { menubar_num_buttons += 1; menubar_content_width += ImGui::CalcTextSize(tuning_title.c_str()).x; @@ -202,7 +202,7 @@ void TopMenubar::Draw(float dt) // The 'Tuning' button - only shown if enabled ImVec2 tuning_cursor = ImVec2(0, 0); - if (App::sim_tuning_enabled->getBool()) + if (this->IsMenuEnabled(TopMenu::TOPMENU_TUNING)) { ImGui::SameLine(); tuning_cursor = ImGui::GetCursorPos(); @@ -215,7 +215,7 @@ void TopMenubar::Draw(float dt) // The 'AI' button - only shown in singleplayer ImVec2 ai_cursor = ImVec2(0, 0); - if (App::mp_state->getEnum() != MpState::CONNECTED) + if (this->IsMenuEnabled(TopMenu::TOPMENU_AI)) { ImGui::SameLine(); ai_cursor = ImGui::GetCursorPos(); @@ -2317,6 +2317,7 @@ void TopMenubar::RefreshTuningMenu() { const ActorPtr& current_actor = App::GetGameContext()->GetPlayerActor(); if (App::sim_tuning_enabled->getBool() + && (App::mp_state->getEnum() != MpState::CONNECTED) && current_actor && (tuning_actor != current_actor || tuning_force_refresh)) { @@ -2468,3 +2469,16 @@ void TopMenubar::DrawTuningForceRemoveControls(const int subject_id, const std:: } } + +bool TopMenubar::IsMenuEnabled(TopMenu which) +{ + switch (which) + { + case TopMenu::TOPMENU_AI: + return App::mp_state->getEnum() != MpState::CONNECTED; + case TopMenu::TOPMENU_TUNING: + return App::sim_tuning_enabled->getBool() && (App::mp_state->getEnum() != MpState::CONNECTED); + default: + return true; + } +} diff --git a/source/main/gui/panels/GUI_TopMenubar.h b/source/main/gui/panels/GUI_TopMenubar.h index 517a8a06cc..8de7c2be8b 100644 --- a/source/main/gui/panels/GUI_TopMenubar.h +++ b/source/main/gui/panels/GUI_TopMenubar.h @@ -124,6 +124,8 @@ class TopMenubar void RefreshTuningMenu(); private: + bool IsMenuEnabled(TopMenu which); + void DrawActorListSinglePlayer(); void DrawMpUserToActorList(RoRnet::UserInfo &user); // Multiplayer void DrawSpecialStateBox(float top_offset); diff --git a/source/main/main.cpp b/source/main/main.cpp index 7637135830..2e2749f38b 100644 --- a/source/main/main.cpp +++ b/source/main/main.cpp @@ -1035,7 +1035,10 @@ int main(int argc, char *argv[]) case MSG_EDI_MODIFY_PROJECT_REQUESTED: { ModifyProjectRequest* request = static_cast(m.payload); - App::GetCacheSystem()->ModifyProject(request); + if (App::mp_state->getEnum() != MpState::CONNECTED) // Do not allow tuning in multiplayer + { + App::GetCacheSystem()->ModifyProject(request); + } delete request; break; } From 24156881b66f72f6ae44a40c7a92225bff09da35 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Tue, 23 Apr 2024 23:52:39 +0200 Subject: [PATCH 06/19] Tuning: added :envelope: MSG_GUI_REFRESH_TUNING_MENU_REQUESTED This removes the huge UI lag on first open of the tuning menu, which was caused by loading all addonparts to evaluate conflicts. But the lag isnt gone - it now happens upon spawning the actor. It could only be fully eliminated by caching the tweaked element IDs directly in `CacheEntry`, something I'll eventually do but not now. Codechanges: * CacheSystem: added dummy TuneupDef with addonpart data to CacheEntry. * AddonPartFileFormat: added silent mode, using the new CacheEntry field. * GameContext: also push MSG_SIM_REFRESH_TUNING_MENU_REQUESTED after spawning new actor and seating player there. Deleted duplicate code in SpawnActor() --- source/main/Application.h | 1 + source/main/GameContext.cpp | 20 ++-- source/main/gui/panels/GUI_TopMenubar.cpp | 2 +- source/main/gui/panels/GUI_TopMenubar.h | 1 - source/main/main.cpp | 6 ++ source/main/resources/CacheSystem.h | 1 + .../AddonPartFileFormat.cpp | 98 +++++++++++-------- .../AddonPartFileFormat.h | 5 +- .../tuneup_fileformat/TuneupFileFormat.h | 3 + 9 files changed, 83 insertions(+), 54 deletions(-) diff --git a/source/main/Application.h b/source/main/Application.h index e7cac5ace9..b34aee3962 100644 --- a/source/main/Application.h +++ b/source/main/Application.h @@ -126,6 +126,7 @@ enum MsgType MSG_GUI_SHOW_MESSAGE_BOX_REQUESTED, //!< Payload = MessageBoxConfig* (owner) MSG_GUI_DOWNLOAD_PROGRESS, MSG_GUI_DOWNLOAD_FINISHED, + MSG_GUI_REFRESH_TUNING_MENU_REQUESTED, // Editing MSG_EDI_MODIFY_GROUNDMODEL_REQUESTED, //!< Payload = RoR::ground_model_t* (weak) MSG_EDI_ENTER_TERRN_EDITOR_REQUESTED, diff --git a/source/main/GameContext.cpp b/source/main/GameContext.cpp index 01ea8a26ab..3c3cb5dc6d 100644 --- a/source/main/GameContext.cpp +++ b/source/main/GameContext.cpp @@ -272,6 +272,7 @@ ActorPtr GameContext::SpawnActor(ActorSpawnRequest& rq) #endif //SOCKETW ActorPtr fresh_actor = m_actor_manager.CreateNewActor(rq, def); + bool fresh_actor_seat_player = false; // lock slide nodes after spawning the actor? if (def->slide_nodes_connect_instantly) @@ -284,7 +285,7 @@ ActorPtr GameContext::SpawnActor(ActorSpawnRequest& rq) m_last_spawned_actor = fresh_actor; if (fresh_actor->ar_driveable != NOT_DRIVEABLE) { - this->PushMessage(Message(MSG_SIM_SEAT_PLAYER_REQUESTED, static_cast(new ActorPtr(fresh_actor)))); + fresh_actor_seat_player = true; } if (rq.asr_spawnbox == nullptr) { @@ -298,13 +299,7 @@ ActorPtr GameContext::SpawnActor(ActorSpawnRequest& rq) fresh_actor->ar_num_nodes > 0 && App::diag_preset_veh_enter->getBool()) { - this->PushMessage(Message(MSG_SIM_SEAT_PLAYER_REQUESTED, static_cast(new ActorPtr(fresh_actor)))); - } - if (fresh_actor->ar_driveable != NOT_DRIVEABLE && - fresh_actor->ar_num_nodes > 0 && - App::cli_preset_veh_enter->getBool()) - { - this->PushMessage(Message(MSG_SIM_SEAT_PLAYER_REQUESTED, static_cast(new ActorPtr(fresh_actor)))); + fresh_actor_seat_player = true; } } else if (rq.asr_origin == ActorSpawnRequest::Origin::TERRN_DEF) @@ -347,10 +342,17 @@ ActorPtr GameContext::SpawnActor(ActorSpawnRequest& rq) rq.asr_origin != ActorSpawnRequest::Origin::NETWORK && rq.asr_enter) { - this->PushMessage(Message(MSG_SIM_SEAT_PLAYER_REQUESTED, static_cast(new ActorPtr(fresh_actor)))); + fresh_actor_seat_player = true; } } + if (fresh_actor_seat_player) + { + this->PushMessage(Message(MSG_SIM_SEAT_PLAYER_REQUESTED, new ActorPtr(fresh_actor))); + // Loading all addonparts to resolve conflicts is slow and would cause huge UI lag if not forced now. Do it right after player was seated in the actor. + this->ChainMessage(Message(MSG_GUI_REFRESH_TUNING_MENU_REQUESTED)); + } + return fresh_actor; } diff --git a/source/main/gui/panels/GUI_TopMenubar.cpp b/source/main/gui/panels/GUI_TopMenubar.cpp index 8233112f5a..adb3f4c731 100644 --- a/source/main/gui/panels/GUI_TopMenubar.cpp +++ b/source/main/gui/panels/GUI_TopMenubar.cpp @@ -2319,7 +2319,7 @@ void TopMenubar::RefreshTuningMenu() if (App::sim_tuning_enabled->getBool() && (App::mp_state->getEnum() != MpState::CONNECTED) && current_actor - && (tuning_actor != current_actor || tuning_force_refresh)) + && (tuning_actor != current_actor)) { ROR_ASSERT(current_actor->getUsedActorEntry()); diff --git a/source/main/gui/panels/GUI_TopMenubar.h b/source/main/gui/panels/GUI_TopMenubar.h index 8de7c2be8b..301f5bb061 100644 --- a/source/main/gui/panels/GUI_TopMenubar.h +++ b/source/main/gui/panels/GUI_TopMenubar.h @@ -118,7 +118,6 @@ class TopMenubar bool tuning_savebox_overwrite = false; //!< Status of "Overwrite?" checkbox const ImVec4 TUNING_HOLDTOCONFIRM_COLOR = ImVec4(0.25f, 0.15f, 0.2f, 0.5f); 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(); diff --git a/source/main/main.cpp b/source/main/main.cpp index 2e2749f38b..1fff5b53a9 100644 --- a/source/main/main.cpp +++ b/source/main/main.cpp @@ -911,6 +911,12 @@ int main(int argc, char *argv[]) App::GetGuiManager()->RepositorySelector.DownloadFinished(); break; + case MSG_GUI_REFRESH_TUNING_MENU_REQUESTED: + { + App::GetGuiManager()->TopMenubar.RefreshTuningMenu(); + break; + } + // -- Editing events -- case MSG_EDI_MODIFY_GROUNDMODEL_REQUESTED: diff --git a/source/main/resources/CacheSystem.h b/source/main/resources/CacheSystem.h index 7a864b8bed..14a11b53ac 100644 --- a/source/main/resources/CacheSystem.h +++ b/source/main/resources/CacheSystem.h @@ -92,6 +92,7 @@ class CacheEntry: public RefCountingObject RigDef::DocumentPtr actor_def; //!< Cached actor definition (aka truckfile) after first spawn. std::shared_ptr skin_def; //!< Cached skin info, added on first use or during cache rebuild RoR::TuneupDefPtr tuneup_def; //!< Cached tuning info, added on first use or during cache rebuild + RoR::TuneupDefPtr addonpart_data_only; //!< Cached addonpart data (dummy tuneup), only used for evaluating conflicts, see `AddonPartUtility::RecordAddonpartConflicts()` // TBD: Make Terrn2Def a RefcountingObjectPtr<> and cache it here too. // following all TRUCK detail information: diff --git a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp index 39edb349b0..8d3ac3e9d9 100644 --- a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp +++ b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp @@ -33,7 +33,7 @@ using namespace RoR; using namespace RigDef; -AddonPartUtility::AddonPartUtility() +AddonPartUtility::AddonPartUtility(bool silent_mode) { // Inits `RefCountingObjectPtr<>` (CacheEntryPtr, GenericDocumentPtr) - shouldn't be in header. } @@ -325,18 +325,18 @@ void AddonPartUtility::ProcessUnwantedProp() if (!m_tuneup->isPropProtected((PropID_t)m_context->getTokFloat(1))) { m_tuneup->unwanted_props.insert((PropID_t)m_context->getTokFloat(1)); - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': marking prop '{}' as UNWANTED", + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': marking prop '{}' as UNWANTED", m_addonpart_entry->fname, m_context->getTokKeyword(), (int)m_context->getTokFloat(1))); } else { - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping prop '{}' because it's marked PROTECTED", + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping prop '{}' because it's marked PROTECTED", m_addonpart_entry->fname, m_context->getTokKeyword(), m_context->getTokString(1))); } } else { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); + this->Log(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); } } @@ -349,18 +349,18 @@ void AddonPartUtility::ProcessUnwantedFlexbody() if (!m_tuneup->isFlexbodyProtected((FlexbodyID_t)m_context->getTokFloat(1))) { m_tuneup->unwanted_flexbodies.insert((FlexbodyID_t)m_context->getTokFloat(1)); - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': marking flexbody '{}' as UNWANTED", + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': marking flexbody '{}' as UNWANTED", m_addonpart_entry->fname, m_context->getTokKeyword(), (int)m_context->getTokFloat(1))); } else { - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping flexbody '{}' because it's marked PROTECTED", + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping flexbody '{}' because it's marked PROTECTED", m_addonpart_entry->fname, m_context->getTokKeyword(), m_context->getTokString(1))); } } else { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); + this->Log(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); } } @@ -386,14 +386,14 @@ void AddonPartUtility::ProcessTweakWheel() if (!stop && m_context->isTokFloat(6)) { data.twt_rim_radius = m_context->getTokFloat(6); } else { stop=true; } m_tuneup->wheel_tweaks.insert(std::make_pair(wheel_id, data)); - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': Sheduling tweak for wheel '{}'" + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': Sheduling tweak for wheel '{}'" " with params {{ media1={}, media2={}, side={}, tire_radius={}, rim_radius={} }}", m_addonpart_entry->fname, m_context->getTokKeyword(), wheel_id, data.twt_media[0], data.twt_media[1], (char)data.twt_side, data.twt_tire_radius, data.twt_rim_radius)); } else if (m_tuneup->wheel_tweaks[wheel_id].twt_origin != m_addonpart_entry->fname) { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': Resetting tweaks for wheel '{}' due to conflict with '{}'", + this->Log(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': Resetting tweaks for wheel '{}' due to conflict with '{}'", m_addonpart_entry->fname, m_context->getTokKeyword(), wheel_id, m_tuneup->wheel_tweaks[wheel_id].twt_origin)); @@ -402,13 +402,13 @@ void AddonPartUtility::ProcessTweakWheel() } else { - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping wheel '{}' because it's marked PROTECTED", + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping wheel '{}' because it's marked PROTECTED", m_addonpart_entry->fname, m_context->getTokKeyword(), (int)m_context->getTokFloat(1))); } } else { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); + this->Log(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); } } @@ -431,14 +431,14 @@ void AddonPartUtility::ProcessTweakNode() data.tnt_pos.z = m_context->getTokFloat(4); m_tuneup->node_tweaks.insert(std::make_pair(nodenum, data)); - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': Scheduling tweak for node '{}'" + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': Scheduling tweak for node '{}'" " with params {{ x={}, y={}, z={} }}", m_addonpart_entry->fname, m_context->getTokKeyword(), nodenum, data.tnt_pos.x, data.tnt_pos.y, data.tnt_pos.z)); } else if (m_tuneup->node_tweaks[nodenum].tnt_origin != m_addonpart_entry->fname) { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': Resetting tweaks for node '{}' due to conflict with '{}'", + this->Log(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': Resetting tweaks for node '{}' due to conflict with '{}'", m_addonpart_entry->fname, m_context->getTokKeyword(), nodenum, m_tuneup->node_tweaks[nodenum].tnt_origin)); @@ -447,13 +447,13 @@ void AddonPartUtility::ProcessTweakNode() } else { - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping node '{}' because it's marked PROTECTED", + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping node '{}' because it's marked PROTECTED", m_addonpart_entry->fname, m_context->getTokKeyword(), nodenum)); } } else { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); + this->Log(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); } } @@ -484,7 +484,7 @@ void AddonPartUtility::ProcessTweakFlexbody() data.tft_media = m_context->getTokString(8); m_tuneup->flexbody_tweaks.insert(std::make_pair(flexbody_id, data)); - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': Scheduling tweak for flexbody '{}'" + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': Scheduling tweak for flexbody '{}'" " with params {{ offsetX={}, offsetY={}, offsetZ={}, rotX={}, rotY={}, rotZ={}, media={} }}", m_addonpart_entry->fname, m_context->getTokKeyword(), flexbody_id, data.tft_offset.x, data.tft_offset.y, data.tft_offset.z, @@ -492,7 +492,7 @@ void AddonPartUtility::ProcessTweakFlexbody() } else if (m_tuneup->flexbody_tweaks[flexbody_id].tft_origin != m_addonpart_entry->fname) { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': Resetting tweaks for flexbody '{}' due to conflict with '{}'", + this->Log(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': Resetting tweaks for flexbody '{}' due to conflict with '{}'", m_addonpart_entry->fname, m_context->getTokKeyword(), flexbody_id, m_tuneup->flexbody_tweaks[flexbody_id].tft_origin)); @@ -501,13 +501,13 @@ void AddonPartUtility::ProcessTweakFlexbody() } else { - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping flexbody '{}' because it's marked PROTECTED", + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping flexbody '{}' because it's marked PROTECTED", m_addonpart_entry->fname, m_context->getTokKeyword(), (int)m_context->getTokFloat(1))); } } else { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); + this->Log(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); } } @@ -540,7 +540,7 @@ void AddonPartUtility::ProcessTweakProp() if (m_context->isTokString(9)) data.tpt_media[1] = m_context->getTokString(9); // <== Optional Media2 is specific for prop m_tuneup->prop_tweaks.insert(std::make_pair(prop_id, data)); - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': Scheduling tweak for prop '{}'" + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': Scheduling tweak for prop '{}'" " with params {{ media1={}, offsetX={}, offsetY={}, offsetZ={}, rotX={}, rotY={}, rotZ={}, media2={} }}", m_addonpart_entry->fname, m_context->getTokKeyword(), prop_id, data.tpt_media[0], data.tpt_offset.x, data.tpt_offset.y, data.tpt_offset.z, @@ -549,7 +549,7 @@ void AddonPartUtility::ProcessTweakProp() } else if (m_tuneup->prop_tweaks[prop_id].tpt_origin != m_addonpart_entry->fname) { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': Resetting tweaks for prop '{}' due to conflict with '{}'", + this->Log(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': Resetting tweaks for prop '{}' due to conflict with '{}'", m_addonpart_entry->fname, m_context->getTokKeyword(), prop_id, m_tuneup->prop_tweaks[prop_id].tpt_origin)); @@ -558,13 +558,13 @@ void AddonPartUtility::ProcessTweakProp() } else { - LOG(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping prop '{}' because it's marked PROTECTED", + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping prop '{}' because it's marked PROTECTED", m_addonpart_entry->fname, m_context->getTokKeyword(), (int)m_context->getTokFloat(1))); } } else { - LOG(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); + this->Log(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); } } @@ -572,23 +572,29 @@ void AddonPartUtility::RecordAddonpartConflicts(CacheEntryPtr addonpart1, CacheE { 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(); + // Make sure both addonparts are loaded and cached. App::GetCacheSystem()->LoadResource(addonpart1); - AddonPartUtility util_t1; - util_t1.ResolveUnwantedAndTweakedElements(dummy_t1, addonpart1); + if (!addonpart1->addonpart_data_only) + { + addonpart1->addonpart_data_only = new TuneupDef(); + AddonPartUtility util(/*silent mode:*/true); + util.ResolveUnwantedAndTweakedElements(addonpart1->addonpart_data_only, addonpart1); + } - TuneupDefPtr dummy_t2 = new TuneupDef(); App::GetCacheSystem()->LoadResource(addonpart2); - AddonPartUtility util_t2; - util_t2.ResolveUnwantedAndTweakedElements(dummy_t2, addonpart2); + if (!addonpart2->addonpart_data_only) + { + addonpart2->addonpart_data_only = new TuneupDef(); + AddonPartUtility util(/*silent mode:*/true); + util.ResolveUnwantedAndTweakedElements(addonpart2->addonpart_data_only, addonpart2); + } // NODE TWEAKS: - for (size_t i = 0; i < dummy_t1->node_tweaks.size(); i++) + for (size_t i = 0; i < addonpart1->addonpart_data_only->node_tweaks.size(); i++) { - NodeNum_t suspect = dummy_t1->node_tweaks[i].tnt_nodenum; + NodeNum_t suspect = addonpart1->addonpart_data_only->node_tweaks[i].tnt_nodenum; TuneupNodeTweak* offender = nullptr; - if (TuneupUtil::isNodeTweaked(dummy_t2, suspect, offender)) + if (TuneupUtil::isNodeTweaked(addonpart2->addonpart_data_only, 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)); @@ -596,11 +602,11 @@ void AddonPartUtility::RecordAddonpartConflicts(CacheEntryPtr addonpart1, CacheE } // WHEEL TWEAKS: - for (size_t i = 0; i < dummy_t1->wheel_tweaks.size(); i++) + for (size_t i = 0; i < addonpart1->addonpart_data_only->wheel_tweaks.size(); i++) { - WheelID_t suspect = dummy_t1->wheel_tweaks[i].twt_wheel_id; + WheelID_t suspect = addonpart1->addonpart_data_only->wheel_tweaks[i].twt_wheel_id; TuneupWheelTweak* offender = nullptr; - if (TuneupUtil::isWheelTweaked(dummy_t2, suspect, offender)) + if (TuneupUtil::isWheelTweaked(addonpart2->addonpart_data_only, 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)); @@ -608,11 +614,11 @@ void AddonPartUtility::RecordAddonpartConflicts(CacheEntryPtr addonpart1, CacheE } // PROP TWEAKS: - for (size_t i = 0; i < dummy_t1->prop_tweaks.size(); i++) + for (size_t i = 0; i < addonpart1->addonpart_data_only->prop_tweaks.size(); i++) { - PropID_t suspect = dummy_t1->prop_tweaks[i].tpt_prop_id; + PropID_t suspect = addonpart1->addonpart_data_only->prop_tweaks[i].tpt_prop_id; TuneupPropTweak* offender = nullptr; - if (TuneupUtil::isPropTweaked(dummy_t2, suspect, offender)) + if (TuneupUtil::isPropTweaked(addonpart2->addonpart_data_only, 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)); @@ -620,11 +626,11 @@ void AddonPartUtility::RecordAddonpartConflicts(CacheEntryPtr addonpart1, CacheE } // FLEXBODY TWEAKS: - for (size_t i = 0; i < dummy_t1->flexbody_tweaks.size(); i++) + for (size_t i = 0; i < addonpart1->addonpart_data_only->flexbody_tweaks.size(); i++) { - FlexbodyID_t suspect = dummy_t1->flexbody_tweaks[i].tft_flexbody_id; + FlexbodyID_t suspect = addonpart1->addonpart_data_only->flexbody_tweaks[i].tft_flexbody_id; TuneupFlexbodyTweak* offender = nullptr; - if (TuneupUtil::isFlexbodyTweaked(dummy_t2, suspect, offender)) + if (TuneupUtil::isFlexbodyTweaked(addonpart2->addonpart_data_only, 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)); @@ -651,3 +657,11 @@ bool AddonPartUtility::CheckForAddonpartConflict(CacheEntryPtr addonpart1, Cache } return false; } + +void AddonPartUtility::Log(const std::string& text) +{ + if (!m_silent_mode) + { + LOG(text); + } +} diff --git a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h index 96d4b76c71..5d56ac8b20 100644 --- a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h +++ b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h @@ -49,7 +49,7 @@ typedef std::vector AddonPartConflictVec; class AddonPartUtility { public: - AddonPartUtility(); + AddonPartUtility(bool silent_mode = false); ~AddonPartUtility(); /// transforms the addonpart to `RigDef::File::Module` (fake 'section/end_section') used for spawning. @@ -81,6 +81,8 @@ class AddonPartUtility void ProcessUnwantedProp(); void ProcessUnwantedFlexbody(); + void Log(const std::string& text); + // Shared state: GenericDocumentPtr m_document; GenericDocContextPtr m_context; @@ -90,6 +92,7 @@ class AddonPartUtility RigDef::ManagedMaterialsOptions m_managedmaterials_options; // ResolveUnwantedAndTweakedElements() state: TuneupDefPtr m_tuneup; + bool m_silent_mode; }; }; // namespace RoR diff --git a/source/main/resources/tuneup_fileformat/TuneupFileFormat.h b/source/main/resources/tuneup_fileformat/TuneupFileFormat.h index 092ad50394..6553651600 100644 --- a/source/main/resources/tuneup_fileformat/TuneupFileFormat.h +++ b/source/main/resources/tuneup_fileformat/TuneupFileFormat.h @@ -79,6 +79,9 @@ struct TuneupFlexbodyTweak //!< Data of 'addonpart_tweak_flexbody std::string tft_origin; //!< Addonpart filename }; +/// Dual purpose: +/// 1. representing a .tuneup file, see `CacheEntry::tuneup_def` (the obvious use) +/// 2. holding addonpart data for conflict resolution, see `CacheEntry::addonpart_data_only` (an additional hack) struct TuneupDef: public RefCountingObject { /// @name General info From fba8ff109257292a711b6267a7a21e4853956363 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Wed, 24 Apr 2024 01:46:49 +0200 Subject: [PATCH 07/19] Tuning: Finalized markers for conflicting addonparts. Gray X = conflict with installed addonpart. Red [] = conflict with mouse-hovered addonpart (regardless of being installed or not) --- source/main/gui/panels/GUI_TopMenubar.cpp | 56 +++++++++++++++++++---- source/main/gui/panels/GUI_TopMenubar.h | 1 + 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/source/main/gui/panels/GUI_TopMenubar.cpp b/source/main/gui/panels/GUI_TopMenubar.cpp index adb3f4c731..1847cb5fec 100644 --- a/source/main/gui/panels/GUI_TopMenubar.cpp +++ b/source/main/gui/panels/GUI_TopMenubar.cpp @@ -1608,15 +1608,19 @@ void TopMenubar::Draw(float dt) std::string addonparts_title = fmt::format(_LC("TopMenubar", "Addon parts ({})"), tuning_addonparts.size()); if (ImGui::CollapsingHeader(addonparts_title.c_str())) { - for (CacheEntryPtr& addonpart_entry: tuning_addonparts) + for (size_t i = 0; i < tuning_addonparts.size(); i++) { + CacheEntryPtr& addonpart_entry = tuning_addonparts[i]; + ImGui::PushID(addonpart_entry->fname.c_str()); - bool conflict = tuning_hovered_addonpart + const bool conflict_w_hovered = 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); - ImVec2 checkbox_cursor = ImGui::GetCursorScreenPos(); - if (ImGui::Checkbox(addonpart_entry->dname.c_str(), &used) && !conflict) + const ImVec2 checkbox_cursor = ImGui::GetCursorScreenPos(); + if (ImGui::Checkbox(addonpart_entry->dname.c_str(), &used) + && !conflict_w_hovered + && !tuning_addonparts_conflict_w_used[i]) { ModifyProjectRequest* req = new ModifyProjectRequest(); req->mpr_type = (used) @@ -1626,12 +1630,25 @@ 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) + // Draw conflict markers + if (tuning_addonparts_conflict_w_used[i]) + { + // Gray-ish X inside the checkbox + const float square_sz = ImGui::GetFrameHeight(); + const ImVec2 min = checkbox_cursor + ImGui::GetStyle().FramePadding*1.4f; + const ImVec2 max = checkbox_cursor + (ImVec2(square_sz, square_sz) - ImGui::GetStyle().FramePadding*1.5f); + const ImColor X_COLOR(0.5f, 0.48f, 0.45f); + ImGui::GetWindowDrawList()->AddLine(min, max, X_COLOR, 4.f); + ImGui::GetWindowDrawList()->AddLine(ImVec2(min.x, max.y), ImVec2(max.x, min.y), X_COLOR, 4.f); + } + if (conflict_w_hovered) { - 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)); + // Red unrounded square around the checkbox + const float square_sz = ImGui::GetFrameHeight(); + const ImVec2 min = checkbox_cursor; + const ImVec2 max = checkbox_cursor + ImVec2(square_sz + 0.5f, square_sz); + const ImColor SQ_COLOR(0.7f, 0.1f, 0.f); + ImGui::GetWindowDrawList()->AddRect(min, max, SQ_COLOR, 0.f, ImDrawCornerFlags_None, 3.f); } // Record when checkbox is hovered - for drawing conflict markers if (ImGui::IsItemHovered()) @@ -2361,7 +2378,7 @@ void TopMenubar::RefreshTuningMenu() tuning_saves.resetResults(); App::GetCacheSystem()->Query(tuning_saves); - // Refresh `tuning_conflicts` database ~ test addonparts each with each once. + // Refresh `tuning_conflicts` database ~ test eligible addonparts each with each once. tuning_conflicts.clear(); for (size_t i1 = 0; i1 < tuning_addonparts.size(); i1++) { @@ -2371,6 +2388,25 @@ void TopMenubar::RefreshTuningMenu() } } + // Refresh `tuning_addonparts_conflicting` listing ~ test used addonparts against unused. + tuning_addonparts_conflict_w_used.clear(); + tuning_addonparts_conflict_w_used.resize(tuning_addonparts.size(), false); + if (current_actor->getWorkingTuneupDef()) + { + for (const std::string& use_addonpart_fname: tuning_actor->getWorkingTuneupDef()->use_addonparts) + { + CacheEntryPtr use_addonpart_entry = App::GetCacheSystem()->FindEntryByFilename(LT_AddonPart, /*partial:*/false, use_addonpart_fname); + for (size_t i = 0; i < tuning_addonparts.size(); i++) + { + if (tuning_addonparts[i] != use_addonpart_entry) + { + tuning_addonparts_conflict_w_used[i] = tuning_addonparts_conflict_w_used[i] + || AddonPartUtility::CheckForAddonpartConflict(tuning_addonparts[i], use_addonpart_entry, tuning_conflicts); + } + } + } + } + tuning_rwidget_cursorx_min = 0.f; } else if (!App::sim_tuning_enabled->getBool() || !current_actor) diff --git a/source/main/gui/panels/GUI_TopMenubar.h b/source/main/gui/panels/GUI_TopMenubar.h index 301f5bb061..4cec3f36c3 100644 --- a/source/main/gui/panels/GUI_TopMenubar.h +++ b/source/main/gui/panels/GUI_TopMenubar.h @@ -111,6 +111,7 @@ class TopMenubar // Tuning menu ActorPtr tuning_actor; //!< Detecting actor change to update cached values. std::vector tuning_addonparts; //!< Addonparts eligible for current actor, both matched by GUID and force-installed by user via [browse all] button. + std::vector tuning_addonparts_conflict_w_used; //!< 1:1 with `tuning_addonparts`; True means the eligible (not used) addonpart conflicts with an used addonpart. 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 From 9305f0f242c712ccbf8f2e464c8b9debf3e14647 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Wed, 24 Apr 2024 21:07:04 +0200 Subject: [PATCH 08/19] Tuning: added `addonpart_filename` directive (optional whitelist) The modder can add any number of `addonpart_filename ` directives (case-insensitive). If none are specified, any filename goes. Matching by GUID is still the primary method, this only helps in cases where the GUID is shared by too many trucks (not uncommon). This commit also fixes the GUID filtering being case-sensitive in all cases (trucks, skins, addonparts), even though the search string is always case-insensitive. Cache file format was set to dummy value; will be properly bumped when https://github.com/RigsOfRods/rigs-of-rods/pull/3151 is merged. --- source/main/gui/panels/GUI_TopMenubar.cpp | 1 + source/main/resources/CacheSystem.cpp | 43 +++++++++++++++++++++-- source/main/resources/CacheSystem.h | 14 +++++--- 3 files changed, 51 insertions(+), 7 deletions(-) diff --git a/source/main/gui/panels/GUI_TopMenubar.cpp b/source/main/gui/panels/GUI_TopMenubar.cpp index 1847cb5fec..c0bd96b201 100644 --- a/source/main/gui/panels/GUI_TopMenubar.cpp +++ b/source/main/gui/panels/GUI_TopMenubar.cpp @@ -2349,6 +2349,7 @@ void TopMenubar::RefreshTuningMenu() CacheQuery query_addonparts; query_addonparts.cqy_filter_type = LT_AddonPart; query_addonparts.cqy_filter_guid = current_actor->getUsedActorEntry()->guid; + query_addonparts.cqy_filter_target_filename = current_actor->getTruckFileName(); // Addonparts without any filenames listed will just pass. App::GetCacheSystem()->Query(query_addonparts); for (CacheQueryResult& res: query_addonparts.cqy_results) { diff --git a/source/main/resources/CacheSystem.cpp b/source/main/resources/CacheSystem.cpp index ce38174ca3..a7fb62c1c7 100644 --- a/source/main/resources/CacheSystem.cpp +++ b/source/main/resources/CacheSystem.cpp @@ -334,6 +334,12 @@ void CacheSystem::ImportEntryFromJson(rapidjson::Value& j_entry, CacheEntryPtr & { out_entry->addonpart_guids.insert(j_addonguid.GetString()); } + + // Addon part suggested mod filenames + for (rapidjson::Value& j_addonfname: j_entry["addonpart_filenames"].GetArray()) + { + out_entry->addonpart_filenames.insert(j_addonfname.GetString()); + } } CacheValidity CacheSystem::LoadCacheFileJson() @@ -609,6 +615,13 @@ void CacheSystem::ExportEntryToJson(rapidjson::Value& j_entries, rapidjson::Docu } j_entry.AddMember("addonpart_guids", j_addonguids, j_doc.GetAllocator()); + rapidjson::Value j_addonfnames(rapidjson::kArrayType); + for (std::string const & ag: entry->addonpart_filenames) + { + j_addonfnames.PushBack(rapidjson::StringRef(ag.c_str()), j_doc.GetAllocator()); + } + j_entry.AddMember("addonpart_filenames", j_addonfnames, j_doc.GetAllocator()); + // Add entry to list j_entries.PushBack(j_entry, j_doc.GetAllocator()); } @@ -900,6 +913,7 @@ void CacheSystem::FillTruckDetailInfo(CacheEntryPtr& entry, Ogre::DataStreamPtr if (def->root_module->guid.size() > 0) { entry->guid = def->root_module->guid[def->root_module->guid.size() - 1].guid; + Ogre::StringUtil::toLowerCase(entry->guid); } entry->fileformatversion = 0; if (def->root_module->fileformatversion.size() > 0) @@ -1142,6 +1156,8 @@ void CacheSystem::FillSkinDetailInfo(CacheEntryPtr &entry, std::shared_ptrdescription = skin_def->description; entry->categoryid = -1; entry->skin_def = skin_def; // Needed to generate preview image + + Ogre::StringUtil::toLowerCase(entry->guid); } void CacheSystem::FillAddonPartDetailInfo(CacheEntryPtr &entry, Ogre::DataStreamPtr ds) @@ -1163,7 +1179,15 @@ void CacheSystem::FillAddonPartDetailInfo(CacheEntryPtr &entry, Ogre::DataStream } else if (ctx->isTokKeyword() && ctx->getTokKeyword() == "addonpart_guid") { - entry->addonpart_guids.insert(ctx->getTokString(1)); + std::string guid = ctx->getTokString(1); + Ogre::StringUtil::toLowerCase(guid); + entry->addonpart_guids.insert(guid); + } + else if (ctx->isTokKeyword() && ctx->getTokKeyword() == "addonpart_filename") + { + std::string fname = ctx->getTokString(1); + Ogre::StringUtil::toLowerCase(fname); + entry->addonpart_filenames.insert(fname); } ctx->seekNextLine(); @@ -1217,6 +1241,8 @@ void CacheSystem::FillTuneupDetailInfo(CacheEntryPtr &entry, TuneupDefPtr& tuneu entry->description = tuneup_def->description; entry->categoryid = tuneup_def->category_id; entry->tuneup_def = tuneup_def; // Needed to generate preview image + + Ogre::StringUtil::toLowerCase(entry->guid); } void CacheSystem::LoadAssetPack(CacheEntryPtr& target_entry, Ogre::String const & assetpack_filename) @@ -1917,6 +1943,8 @@ void CacheSystem::DeleteProject(CacheEntryPtr& entry) size_t CacheSystem::Query(CacheQuery& query) { Ogre::StringUtil::toLowerCase(query.cqy_search_string); + Ogre::StringUtil::toLowerCase(query.cqy_filter_guid); + Ogre::StringUtil::toLowerCase(query.cqy_filter_target_filename); std::time_t cur_time = std::time(nullptr); for (CacheEntryPtr& entry: m_entries) { @@ -1931,6 +1959,17 @@ size_t CacheSystem::Query(CacheQuery& query) } } + // Filter by target filename (currently only `addonpart_filenames`); pass items which have no target filenames listed. + if (query.cqy_filter_target_filename != "") + { + if (entry->fext == "addonpart" + && entry->addonpart_filenames.size() > 0 + && entry->addonpart_filenames.count(query.cqy_filter_target_filename) == 0) + { + continue; + } + } + // Filter by entry type bool add = false; if (entry->fext == "terrn2") @@ -2017,7 +2056,7 @@ size_t CacheSystem::Query(CacheQuery& query) match = this->Match(score, entry->fname, query.cqy_search_string, 100); break; - default: // CacheSearchMethod::NONE + default: // CacheSearchMethod:: match = true; break; }; diff --git a/source/main/resources/CacheSystem.h b/source/main/resources/CacheSystem.h index 14a11b53ac..acd5723b98 100644 --- a/source/main/resources/CacheSystem.h +++ b/source/main/resources/CacheSystem.h @@ -39,7 +39,7 @@ #include #define CACHE_FILE "mods.cache" -#define CACHE_FILE_FORMAT 13 +#define CACHE_FILE_FORMAT 3151 // TBD bump to 14 when merging pull request #3151 #define CACHE_FILE_FRESHNESS 86400 // 60*60*24 = one day namespace RoR { @@ -74,8 +74,7 @@ class CacheEntry: public RefCountingObject std::time_t addtimestamp; //!< timestamp when this file was added to the cache Ogre::String uniqueid; //!< file's unique id - Ogre::String guid; //!< global unique id. Type "addonpart" leaves this empty. - std::set addonpart_guids; //!< GUIDs of all vehicles this addonpart is used in. Empty for non-addonpart entries. + Ogre::String guid; //!< global unique id. Type "addonpart" leaves this empty and uses `addonpart_guids`. int version; //!< file's version std::string resource_bundle_type; //!< Archive type recognized by OGRE resource system: 'FileSystem' or 'Zip' @@ -95,6 +94,10 @@ class CacheEntry: public RefCountingObject RoR::TuneupDefPtr addonpart_data_only; //!< Cached addonpart data (dummy tuneup), only used for evaluating conflicts, see `AddonPartUtility::RecordAddonpartConflicts()` // TBD: Make Terrn2Def a RefcountingObjectPtr<> and cache it here too. + // following all ADDONPART detail information: + std::set addonpart_guids; //!< GUIDs of all vehicles this addonpart is used with. + std::set addonpart_filenames; //!< File names of all vehicles this addonpart is used with. If empty, any filename goes. + // following all TRUCK detail information: Ogre::String description; Ogre::String tags; @@ -165,7 +168,7 @@ struct CacheQueryResult enum class CacheSearchMethod // Always case-insensitive { - NONE, //!< No searching + NONE, //!< Ignore the search string and find all FULLTEXT, //!< Partial match in: name, filename, description, author name/mail GUID, //!< Partial match in: guid AUTHORS, //!< Partial match in: author name/email @@ -177,7 +180,8 @@ struct CacheQuery { RoR::LoaderType cqy_filter_type = RoR::LoaderType::LT_None; int cqy_filter_category_id = CacheCategoryId::CID_All; - std::string cqy_filter_guid; //!< Exact match; leave empty to disable + std::string cqy_filter_guid; //!< Exact match (case-insensitive); leave empty to disable + std::string cqy_filter_target_filename; //!< Exact match (case-insensitive); leave empty to disable (currently only used with addonparts) CacheSearchMethod cqy_search_method = CacheSearchMethod::NONE; std::string cqy_search_string; From 4b6734b63d5367b05794273b57371d5cc711212d Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Fri, 26 Apr 2024 03:36:03 +0200 Subject: [PATCH 09/19] Tuning: added flare Adding/Removing (not Tweaking yet) This was an exercise in extending the Tuning system. Took 2 hours, 35 minutes (including writing the long HOWTO below). * An `addonpart_unwanted_flare` directive was added. * Addonparts now support 'flares' and 'flares2' (flares3 aren't supported). * Note that tweaks for flares were not added, as adding/removing seems to be enough. Codechanges (HOWTO add a new feature to the Tuning system [if you're a daring programmer] - changes are ordered to intuitively form the picture): * ForwardDeclarations.h: added typedef FlareID_t ~ this isn't technically necessary but makes the code more pleasant to read. * TuneupFileFormat.h: added unwanted/forece_remove/protected flare lists to the TuneupDef document, and helper functions to TuneupUtil. See //comments to understand their meaning. ~ I just duplicated the same code as props already use, just changed the ID type to flares. * AddonPartFileFormat: added support for defining 'flares' and 'flares2' in the .addonpart file. ~ Since the syntax is the same as in 'truck' fileformat and the output object is also the same, this basically means copypasting `RigDef::Parser::ParseFlaresUnified()` (see file RigDef_Parser.cpp) and modifying it to fit. A slight twist is that I didn't use an unified function but created separate 'flares' and 'flares2' functions. This is because 'flares3' is separate either way. FIXUP: I forgot to update the condition around `block = keyword;`, causing the parser to interpret all flare lines as whatever was before (flexbodies in my case). * CacheSystem: added PROTECTED + FORCEREMOVE SET/RESET ModifyProjectRequest types. ~ in RoR, everything important is done via message queue. Tuning changes are done by MSG_EDI_MODIFY_PROJECT_REQUESTED. * GUI_TopMenubar.cpp: added "Flares" section ~ pretty much a copypaste of existing Flexbodies section code above, just modified to fit: changed the loop to read flares instead of flexbodies, added code to compose name of the flare, and change the ModifyProjectRequestType constants. * ActorSpawner: if the flare is removed by Tuning system, just create a placeholder. ~ This is the moment of truth. When spawning, the tuning changes must be evaluated, but to maintain consistent lists of elements in the Tuning menu (and other tools), the unwanted/forceRemoved elements are left around as inactive 'placeholders'. In this case, it's a complete flare object, just without the visual flare billboard and light. * SimData.h: just // comments about tuning * GfxActor: skip placeholder flares when updating visuals. * DashBoardManager: added helper function to convert linkID back to the name - needed for the Tuning UI menu to display link name for 'd' (dashboard) flares. --- source/main/ForwardDeclarations.h | 3 + source/main/gfx/GfxActor.cpp | 6 + source/main/gui/DashBoardManager.cpp | 12 ++ source/main/gui/DashBoardManager.h | 1 + source/main/gui/panels/GUI_TopMenubar.cpp | 50 ++++++++ source/main/physics/ActorSpawner.cpp | 18 ++- source/main/physics/SimData.h | 6 +- source/main/resources/CacheSystem.cpp | 20 ++++ source/main/resources/CacheSystem.h | 4 + .../AddonPartFileFormat.cpp | 110 +++++++++++++++++- .../AddonPartFileFormat.h | 4 + .../tuneup_fileformat/TuneupFileFormat.cpp | 7 ++ .../tuneup_fileformat/TuneupFileFormat.h | 21 +++- 13 files changed, 248 insertions(+), 14 deletions(-) diff --git a/source/main/ForwardDeclarations.h b/source/main/ForwardDeclarations.h index 941e9712b3..83b5490329 100644 --- a/source/main/ForwardDeclarations.h +++ b/source/main/ForwardDeclarations.h @@ -61,6 +61,9 @@ namespace RoR typedef int FlexbodyID_t; //!< Index to `GfxActor::m_flexbodies`, `use RoR::FLEXBODYID_INVALID` as empty value static const FlexbodyID_t FLEXBODYID_INVALID = -1; + typedef int FlareID_t; //!< Index into `Actor::ar_flares`, use `RoR::FLAREID_INVALID` as empty value + static const FlareID_t FLAREID_INVALID = -1; + class Actor; class ActorManager; class ActorSpawner; diff --git a/source/main/gfx/GfxActor.cpp b/source/main/gfx/GfxActor.cpp index 309c89fd70..e8514ce0e1 100644 --- a/source/main/gfx/GfxActor.cpp +++ b/source/main/gfx/GfxActor.cpp @@ -3085,6 +3085,12 @@ void RoR::GfxActor::UpdateFlares(float dt_sec, bool is_player) for (int i=0; iar_flares[i]; + + // Skip placeholder flares (removed via Tuning system) + if (!flare.bbs) + { + continue; + } this->SetMaterialFlareOn(i, flare.intensity > 0.3); diff --git a/source/main/gui/DashBoardManager.cpp b/source/main/gui/DashBoardManager.cpp index 5f3f8b0803..dc02ee6cd1 100644 --- a/source/main/gui/DashBoardManager.cpp +++ b/source/main/gui/DashBoardManager.cpp @@ -158,6 +158,18 @@ int DashBoardManager::getLinkIDForName(Ogre::String& str) return -1; } +std::string DashBoardManager::getLinkNameForID(DashData id) +{ + if (id > 0 && id < DD_MAX) + { + return data[id].name; + } + else + { + return ""; + } +} + int DashBoardManager::loadDashBoard(Ogre::String filename, bool textureLayer) { diff --git a/source/main/gui/DashBoardManager.h b/source/main/gui/DashBoardManager.h index 1d586c0c9b..8f649f610e 100644 --- a/source/main/gui/DashBoardManager.h +++ b/source/main/gui/DashBoardManager.h @@ -226,6 +226,7 @@ class DashBoardManager int getDataType(size_t key) { return data[key].type; }; int getLinkIDForName(Ogre::String& str); + std::string getLinkNameForID(DashData id); int loadDashBoard(Ogre::String filename, bool textureLayer); diff --git a/source/main/gui/panels/GUI_TopMenubar.cpp b/source/main/gui/panels/GUI_TopMenubar.cpp index c0bd96b201..81e21e83a3 100644 --- a/source/main/gui/panels/GUI_TopMenubar.cpp +++ b/source/main/gui/panels/GUI_TopMenubar.cpp @@ -29,6 +29,7 @@ #include "Actor.h" #include "ActorManager.h" #include "CameraManager.h" +#include "DashBoardManager.h" #include "FlexBody.h" #include "GameContext.h" #include "GfxScene.h" @@ -1858,6 +1859,55 @@ void TopMenubar::Draw(float dt) ImGui::PopID(); // i } } + + // Draw flares + size_t total_flares = tuning_actor->ar_flares.size(); + std::string flares_title = fmt::format(_LC("Tuning", "Flares ({})"), total_flares); + if (ImGui::CollapsingHeader(flares_title.c_str())) + { + // Draw all flares (those removed by addonparts are also present as placeholders) + for (FlareID_t flareid = 0; flareid < (int)tuning_actor->ar_flares.size(); flareid++) + { + ImGui::PushID(flareid); + ImGui::AlignTextToFramePadding(); + + this->DrawTuningBoxedSubjectIdInline(flareid); + + // Compose flare description string + const FlareType flaretype = tuning_actor->ar_flares[flareid].fl_type; + std::string flarename; + if (flaretype == FlareType::USER) + { + int controlnumber = tuning_actor->ar_flares[flareid].controlnumber + 1; // Convert range 0-9 to 1-10 + flarename = fmt::format("{} {}", (char)flaretype, controlnumber); + } + else if (flaretype == FlareType::DASHBOARD) + { + std::string linkname = tuning_actor->ar_dashboard->getLinkNameForID((DashData)tuning_actor->ar_flares[flareid].dashboard_link); + flarename = fmt::format("{} {}", (char)flaretype, linkname); + } + else + { + flarename = fmt::format("{}", (char)flaretype); + } + + this->DrawTuningForceRemoveControls( + flareid, + flarename, + tuneup_def && tuneup_def->isFlareUnwanted(flareid), + tuneup_def && tuneup_def->isFlareForceRemoved(flareid), + ModifyProjectRequestType::TUNEUP_FORCEREMOVE_FLARE_SET, + ModifyProjectRequestType::TUNEUP_FORCEREMOVE_FLARE_RESET); + + this->DrawTuningProtectedChkRightAligned( + flareid, + tuneup_def && tuneup_def->isFlareProtected(flareid), + ModifyProjectRequestType::TUNEUP_PROTECTED_FLARE_SET, + ModifyProjectRequestType::TUNEUP_PROTECTED_FLARE_RESET); + + ImGui::PopID(); // flareid + } + } } m_open_menu_hoverbox_min = menu_pos - MENU_HOVERBOX_PADDING; diff --git a/source/main/physics/ActorSpawner.cpp b/source/main/physics/ActorSpawner.cpp index 1e59a72c57..32c4520858 100644 --- a/source/main/physics/ActorSpawner.cpp +++ b/source/main/physics/ActorSpawner.cpp @@ -2106,10 +2106,15 @@ void ActorSpawner::ProcessFlare3(RigDef::Flare3 & def) void ActorSpawner::ProcessFlare2(RigDef::Flare2 & def) { + // This processes both 'flares' and 'flares2' (the parser auto-imports 'flares' as `RigDef::Flare2`) + // ------------------------------------------------------------------------------------------------- + if (m_actor->m_flares_mode == GfxFlaresMode::NONE) { return; } int blink_delay = def.blink_delay_milis; float size = def.size; + const FlareID_t flare_id = static_cast(m_actor->ar_flares.size()); + const bool is_placeholder = TuneupUtil::isFlareAnyhowRemoved(m_actor->getWorkingTuneupDef(), flare_id); /* Backwards compatibility */ if (blink_delay == -2) @@ -2187,9 +2192,12 @@ void ActorSpawner::ProcessFlare2(RigDef::Flare2 & def) } /* Visuals */ - flare.snode = m_flares_parent_scenenode->createChildSceneNode(this->ComposeName("flareX", (int)m_actor->ar_flares.size())); - std::string flare_name = this->ComposeName("Flare", static_cast(m_actor->ar_flares.size())); - flare.bbs = App::GetGfxScene()->GetSceneManager()->createBillboardSet(flare_name, 1); + std::string flare_name = this->ComposeName("Flare", flare_id); + if (!is_placeholder) + { + flare.snode = m_flares_parent_scenenode->createChildSceneNode(this->ComposeName("flareX", flare_id)); + flare.bbs = App::GetGfxScene()->GetSceneManager()->createBillboardSet(flare_name, 1); + } // Backwards compatibility: // before 't' (tail light) was introduced in 2022, tail lights were indicated as 'f' (headlight) + custom material. @@ -2242,7 +2250,7 @@ void ActorSpawner::ProcessFlare2(RigDef::Flare2 & def) flare.intensity = 1.f; flare.light = nullptr; - if ((App::gfx_flares_mode->getEnum() >= GfxFlaresMode::CURR_VEHICLE_HEAD_ONLY) && size > 0.001) + if ((App::gfx_flares_mode->getEnum() >= GfxFlaresMode::CURR_VEHICLE_HEAD_ONLY) && size > 0.001 && !is_placeholder) { if (flare.fl_type == FlareType::HEADLIGHT) { @@ -2275,7 +2283,7 @@ void ActorSpawner::ProcessFlare2(RigDef::Flare2 & def) flare.light->setCastShadows(false); } } - if ((App::gfx_flares_mode->getEnum() >= GfxFlaresMode::ALL_VEHICLES_ALL_LIGHTS) && size > 0.001) + if ((App::gfx_flares_mode->getEnum() >= GfxFlaresMode::ALL_VEHICLES_ALL_LIGHTS) && size > 0.001 && !is_placeholder) { if (flare.fl_type == FlareType::TAIL_LIGHT) { diff --git a/source/main/physics/SimData.h b/source/main/physics/SimData.h index 342806a6d0..1fe05ea6df 100644 --- a/source/main/physics/SimData.h +++ b/source/main/physics/SimData.h @@ -631,9 +631,9 @@ struct flare_t float offsetx; float offsety; float offsetz; - Ogre::SceneNode *snode; - Ogre::BillboardSet *bbs; - Ogre::Light *light; + Ogre::SceneNode *snode = nullptr; + Ogre::BillboardSet *bbs = nullptr; //!< This remains `nullptr` if removed via `addonpart_unwanted_flare` or Tuning UI. + Ogre::Light *light = nullptr; FlareType fl_type; int controlnumber; //!< Only 'u' type flares, valid values 0-9, maps to EV_TRUCK_LIGHTTOGGLE01 to 10. int dashboard_link; //!< Only 'd' type flares, valid values are DD_* diff --git a/source/main/resources/CacheSystem.cpp b/source/main/resources/CacheSystem.cpp index a7fb62c1c7..60b9c995a7 100644 --- a/source/main/resources/CacheSystem.cpp +++ b/source/main/resources/CacheSystem.cpp @@ -1797,6 +1797,16 @@ void CacheSystem::ModifyProject(ModifyProjectRequest* request) request->mpr_target_actor->getWorkingTuneupDef()->force_remove_props.erase(request->mpr_subject_id); break; + case ModifyProjectRequestType::TUNEUP_FORCEREMOVE_FLARE_SET: + request->mpr_target_actor->ensureWorkingTuneupDef(); + request->mpr_target_actor->getWorkingTuneupDef()->force_remove_flares.insert(request->mpr_subject_id); + break; + + case ModifyProjectRequestType::TUNEUP_FORCEREMOVE_FLARE_RESET: + request->mpr_target_actor->ensureWorkingTuneupDef(); + request->mpr_target_actor->getWorkingTuneupDef()->force_remove_flares.erase(request->mpr_subject_id); + break; + case ModifyProjectRequestType::TUNEUP_FORCEREMOVE_FLEXBODY_SET: request->mpr_target_actor->ensureWorkingTuneupDef(); request->mpr_target_actor->getWorkingTuneupDef()->force_remove_flexbodies.insert(request->mpr_subject_id); @@ -1847,6 +1857,16 @@ void CacheSystem::ModifyProject(ModifyProjectRequest* request) request->mpr_target_actor->getWorkingTuneupDef()->protected_wheels.erase(request->mpr_subject_id); break; + case ModifyProjectRequestType::TUNEUP_PROTECTED_FLARE_SET: + request->mpr_target_actor->ensureWorkingTuneupDef(); + request->mpr_target_actor->getWorkingTuneupDef()->protected_flares.insert(request->mpr_subject_id); + break; + + case ModifyProjectRequestType::TUNEUP_PROTECTED_FLARE_RESET: + request->mpr_target_actor->ensureWorkingTuneupDef(); + request->mpr_target_actor->getWorkingTuneupDef()->protected_flares.erase(request->mpr_subject_id); + break; + case ModifyProjectRequestType::PROJECT_LOAD_TUNEUP: { // Instead of loading with the saved tuneup directly, keep the autogenerated and sync it with the save. diff --git a/source/main/resources/CacheSystem.h b/source/main/resources/CacheSystem.h index acd5723b98..98bdc36bf7 100644 --- a/source/main/resources/CacheSystem.h +++ b/source/main/resources/CacheSystem.h @@ -237,12 +237,16 @@ enum class ModifyProjectRequestType TUNEUP_FORCEREMOVE_FLEXBODY_RESET, //!< 'subject_id' is flexbody ID. TUNEUP_FORCED_WHEEL_SIDE_SET, //!< 'subject_id' is wheel ID, 'value_int' is RoR::WheelSide TUNEUP_FORCED_WHEEL_SIDE_RESET, //!< 'subject_id' is wheel ID. + TUNEUP_FORCEREMOVE_FLARE_SET, //!< 'subject_id' is flare ID. + TUNEUP_FORCEREMOVE_FLARE_RESET, //!< 'subject_id' is flare ID. TUNEUP_PROTECTED_PROP_SET, //!< 'subject_id' is prop ID. TUNEUP_PROTECTED_PROP_RESET, //!< 'subject_id' is prop ID. TUNEUP_PROTECTED_FLEXBODY_SET, //!< 'subject_id' is flexbody ID. TUNEUP_PROTECTED_FLEXBODY_RESET, //!< 'subject_id' is flexbody ID. TUNEUP_PROTECTED_WHEEL_SET, //!< 'subject_id' is wheel ID. TUNEUP_PROTECTED_WHEEL_RESET, //!< 'subject_id' is wheel ID. + TUNEUP_PROTECTED_FLARE_SET, //!< 'subject_id' is flare ID. + TUNEUP_PROTECTED_FLARE_RESET, //!< 'subject_id' is flare ID. PROJECT_LOAD_TUNEUP, //!< 'subject' is tuneup filename. This overwrites the auto-generated tuneup with the save. PROJECT_RESET_TUNEUP, //!< 'subject' is empty. This resets the auto-generated tuneup to orig. values. }; diff --git a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp index 8d3ac3e9d9..f66a3025c8 100644 --- a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp +++ b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp @@ -87,7 +87,9 @@ std::shared_ptr AddonPartUtility::TransformToRigDefModule(Cach } else if (keyword == Keyword::MANAGEDMATERIALS || keyword == Keyword::PROPS - || keyword == Keyword::FLEXBODIES) + || keyword == Keyword::FLEXBODIES + || keyword == Keyword::FLARES + || keyword == Keyword::FLARES2) { block = keyword; m_context->seekNextLine(); @@ -104,6 +106,8 @@ std::shared_ptr AddonPartUtility::TransformToRigDefModule(Cach case Keyword::MANAGEDMATERIALS: this->ProcessManagedMaterial(); break; case Keyword::PROPS: this->ProcessProp(); break; case Keyword::FLEXBODIES: this->ProcessFlexbody(); break; + case Keyword::FLARES: this->ProcessFlare(); break; + case Keyword::FLARES2: this->ProcessFlare2(); break; default: break; } } @@ -149,6 +153,8 @@ void AddonPartUtility::ResolveUnwantedAndTweakedElements(TuneupDefPtr& tuneup, C this->ProcessUnwantedProp(); else if (m_context->getTokKeyword() == "addonpart_unwanted_flexbody" ) this->ProcessUnwantedFlexbody(); + else if (m_context->getTokKeyword() == "addonpart_unwanted_flare" ) + this->ProcessUnwantedFlare(); else if (m_context->getTokKeyword() == "addonpart_tweak_wheel") this->ProcessTweakWheel(); else if (m_context->getTokKeyword() == "addonpart_tweak_node") @@ -179,6 +185,7 @@ void AddonPartUtility::ResetUnwantedAndTweakedElements(TuneupDefPtr& tuneup) // Unwanted tuneup->unwanted_flexbodies.clear(); tuneup->unwanted_props.clear(); + tuneup->unwanted_flares.clear(); // Tweaked tuneup->node_tweaks.clear(); @@ -314,6 +321,83 @@ void AddonPartUtility::ProcessFlexbody() m_module->flexbodies.push_back(def); } +void AddonPartUtility::ProcessFlare() +{ + int n = m_context->countLineArgs(); + if (n < 5) + { + App::GetConsole()->putMessage( + Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_WARNING, + fmt::format("Error parsing addonpart file '{}': flare has only {} arguments, expected {}", m_addonpart_entry->fname, n, 5)); + return; + } + + Flare2 def; // We auto-import 'flares' as 'flares2', leaving the `offset.z` at 0. + int importflags = Node::Ref::REGULAR_STATE_IS_VALID | Node::Ref::REGULAR_STATE_IS_NUMBERED; + def.reference_node = Node::Ref("", (unsigned int)m_context->getTokFloat(0), importflags, 0); + def.node_axis_x = Node::Ref("", (unsigned int)m_context->getTokFloat(1), importflags, 0); + def.node_axis_y = Node::Ref("", (unsigned int)m_context->getTokFloat(2), importflags, 0); + def.offset.x = m_context->getTokFloat(3); + def.offset.y = m_context->getTokFloat(4); + + if (n > 5) def.type = (FlareType)m_context->getTokString(5)[0]; + + if (n > 6) + { + switch (def.type) + { + case FlareType::USER: def.control_number = (int)m_context->getTokFloat(6); break; + case FlareType::DASHBOARD: def.dashboard_link = m_context->getTokString(6); break; + default: break; + } + } + + if (n > 7) { def.blink_delay_milis = (int)m_context->getTokFloat(7); } + if (n > 8) { def.size = m_context->getTokFloat(8); } + if (n > 9) { def.material_name = m_context->getTokString(9); } + + m_module->flares2.push_back(def); +} + +void AddonPartUtility::ProcessFlare2() +{ + int n = m_context->countLineArgs(); + if (n < 6) + { + App::GetConsole()->putMessage( + Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_WARNING, + fmt::format("Error parsing addonpart file '{}': flare2 has only {} arguments, expected {}", m_addonpart_entry->fname, n, 6)); + return; + } + + Flare2 def; + int importflags = Node::Ref::REGULAR_STATE_IS_VALID | Node::Ref::REGULAR_STATE_IS_NUMBERED; + def.reference_node = Node::Ref("", (unsigned int)m_context->getTokFloat(0), importflags, 0); + def.node_axis_x = Node::Ref("", (unsigned int)m_context->getTokFloat(1), importflags, 0); + def.node_axis_y = Node::Ref("", (unsigned int)m_context->getTokFloat(2), importflags, 0); + def.offset.x = m_context->getTokFloat(3); + def.offset.y = m_context->getTokFloat(4); + def.offset.z = m_context->getTokFloat(5); // <-- Specific to 'flares2' (the only difference) + + if (n > 6) def.type = (FlareType)m_context->getTokString(6)[0]; + + if (n > 7) + { + switch (def.type) + { + case FlareType::USER: def.control_number = (int)m_context->getTokFloat(7); break; + case FlareType::DASHBOARD: def.dashboard_link = m_context->getTokString(7); break; + default: break; + } + } + + if (n > 8) { def.blink_delay_milis = (int)m_context->getTokFloat(8); } + if (n > 9) { def.size = m_context->getTokFloat(9); } + if (n > 10) { def.material_name = m_context->getTokString(10); } + + m_module->flares2.push_back(def); +} + // Helpers of `ResolveUnwantedAndTweakedElements()`, they expect `m_context` to be in position: void AddonPartUtility::ProcessUnwantedProp() @@ -364,6 +448,30 @@ void AddonPartUtility::ProcessUnwantedFlexbody() } } +void AddonPartUtility::ProcessUnwantedFlare() +{ + ROR_ASSERT(m_context->getTokKeyword() == "addonpart_unwanted_flare"); // also asserts !EOF and TokenType::KEYWORD + + if (m_context->isTokFloat(1)) + { + if (!m_tuneup->isFlareProtected((FlareID_t)m_context->getTokFloat(1))) + { + m_tuneup->unwanted_flexbodies.insert((FlareID_t)m_context->getTokFloat(1)); + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': marking flare '{}' as UNWANTED", + m_addonpart_entry->fname, m_context->getTokKeyword(), (int)m_context->getTokFloat(1))); + } + else + { + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping flare '{}' because it's marked PROTECTED", + m_addonpart_entry->fname, m_context->getTokKeyword(), m_context->getTokString(1))); + } + } + else + { + this->Log(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); + } +} + void AddonPartUtility::ProcessTweakWheel() { ROR_ASSERT(m_context->getTokKeyword() == "addonpart_tweak_wheel"); // also asserts !EOF and TokenType::KEYWORD diff --git a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h index 5d56ac8b20..4127215b31 100644 --- a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h +++ b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h @@ -72,6 +72,9 @@ class AddonPartUtility void ProcessDirectiveSetManagedMaterialsOptions(); void ProcessProp(); void ProcessFlexbody(); + void ProcessFlare(); + void ProcessFlare2(); + void ProcessFlare3(); void ProcessTweakWheel(); void ProcessTweakNode(); void ProcessTweakFlexbody(); @@ -80,6 +83,7 @@ class AddonPartUtility // Helpers of `ResolveUnwantedAndTweakedElements()`, they expect `m_context` to be in position: void ProcessUnwantedProp(); void ProcessUnwantedFlexbody(); + void ProcessUnwantedFlare(); void Log(const std::string& text); diff --git a/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp b/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp index a787e7e351..a41557f8f9 100644 --- a/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp +++ b/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp @@ -445,6 +445,13 @@ bool RoR::TuneupUtil::isFlexbodyTweaked(TuneupDefPtr& tuneup_def, FlexbodyID_t f return false; } +// > flare +bool RoR::TuneupUtil::isFlareAnyhowRemoved(TuneupDefPtr& tuneup_def, FlareID_t flare_id) +{ + return tuneup_def + && (tuneup_def->isFlareUnwanted(flare_id) || tuneup_def->isFlareForceRemoved(flare_id)); +} + bool RoR::TuneupUtil::isAddonPartUsed(TuneupDefPtr& tuneup_entry, const std::string& filename) { return tuneup_entry diff --git a/source/main/resources/tuneup_fileformat/TuneupFileFormat.h b/source/main/resources/tuneup_fileformat/TuneupFileFormat.h index 6553651600..cf3d5a495a 100644 --- a/source/main/resources/tuneup_fileformat/TuneupFileFormat.h +++ b/source/main/resources/tuneup_fileformat/TuneupFileFormat.h @@ -105,6 +105,7 @@ struct TuneupDef: public RefCountingObject std::map flexbody_tweaks; //!< Mesh name, offset and rotation overrides via 'addonpart_tweak_flexbody' std::set unwanted_props; //!< 'addonpart_unwanted_prop' directives. std::set unwanted_flexbodies; //!< 'addonpart_unwanted_flexbody' directives. + std::set unwanted_flares; //!< 'addonpart_unwanted_flare' directives. /// @} /// @name UI-controlled forced changes (override addonparts) @@ -112,14 +113,16 @@ struct TuneupDef: public RefCountingObject std::set force_remove_props; //!< UI overrides std::set force_remove_flexbodies; //!< UI overrides std::map force_wheel_sides; //!< UI overrides + std::set force_remove_flares; //!< User unticked an UI checkbox in Tuning menu, section Flares. /// @} /// @name UI-controlled protection from addonpart tweaks /// @{ std::set protected_nodes; //!< Nodes that cannot be altered via 'addonpart_tweak_node' std::set protected_wheels; //!< Wheels that cannot be altered via 'addonpart_tweak_wheel' - std::set protected_props; //!< Props which cannot be altered via 'addonpart_tweak_prop' or 'addonpart_remove_prop' directive. - std::set protected_flexbodies; //!< Flexbodies which cannot be removed via 'addonpart_tweak_flexbody' or 'addonpart_remove_flexbody' directive. + std::set protected_props; //!< Props which cannot be altered via 'addonpart_tweak_prop' or 'addonpart_unwanted_prop' directive. + std::set protected_flexbodies; //!< Flexbodies which cannot be altered via 'addonpart_tweak_flexbody' or 'addonpart_unwanted_flexbody' directive. + std::set protected_flares; //!< Flares which cannot be altered via 'addonpart_unwanted_flare' directive. /// @} TuneupDefPtr clone(); @@ -127,16 +130,18 @@ struct TuneupDef: public RefCountingObject /// @name Protection helpers /// @{ - bool isPropProtected(PropID_t propid) { return protected_props.find(propid) != protected_props.end(); } - bool isFlexbodyProtected(FlexbodyID_t flexbodyid) { return protected_flexbodies.find(flexbodyid) != protected_flexbodies.end(); } - bool isWheelProtected(int wheelid) const { return protected_wheels.find(wheelid) != protected_wheels.end(); } + bool isPropProtected(PropID_t propid) const { return protected_props.find(propid) != protected_props.end(); } + bool isFlexbodyProtected(FlexbodyID_t flexbodyid) const { return protected_flexbodies.find(flexbodyid) != protected_flexbodies.end(); } + bool isWheelProtected(WheelID_t wheelid) const { return protected_wheels.find(wheelid) != protected_wheels.end(); } bool isNodeProtected(NodeNum_t nodenum) const { return protected_nodes.find(nodenum) != protected_nodes.end(); } + bool isFlareProtected(FlareID_t flareid) const { return protected_flares.find(flareid) != protected_flares.end(); } /// @} /// @name Unwanted-state helpers /// @{ bool isPropUnwanted(PropID_t propid) { return unwanted_props.find(propid) != unwanted_props.end(); } bool isFlexbodyUnwanted(FlexbodyID_t flexbodyid) { return unwanted_flexbodies.find(flexbodyid) != unwanted_flexbodies.end(); } + bool isFlareUnwanted(FlareID_t flareid) { return unwanted_flares.find(flareid) != unwanted_flares.end(); } /// @} /// @name Forced-state helpers @@ -144,6 +149,7 @@ struct TuneupDef: public RefCountingObject bool isPropForceRemoved(PropID_t propid) { return force_remove_props.find(propid) != force_remove_props.end(); } bool isFlexbodyForceRemoved(FlexbodyID_t flexbodyid) { return force_remove_flexbodies.find(flexbodyid) != force_remove_flexbodies.end(); } bool isWheelSideForced(WheelID_t wheelid, WheelSide& out_val) const; + bool isFlareForceRemoved(FlareID_t flareid) { return force_remove_flares.find(flareid) != force_remove_flares.end(); } /// @} }; @@ -195,6 +201,11 @@ class TuneupUtil static bool isFlexbodyTweaked(TuneupDefPtr& tuneup_entry, FlexbodyID_t flexbody_id, TuneupFlexbodyTweak*& out_tweak); /// @} + /// @name Flare helpers + /// @{ + static bool isFlareAnyhowRemoved(TuneupDefPtr& tuneup_def, FlareID_t flare_id); + /// @} + private: static void ParseTuneupAttribute(const std::string& line, TuneupDefPtr& tuneup_def); From 92cb5af86a7bb0240b73b7769e6e4536e890d457 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Sat, 27 Apr 2024 01:41:11 +0200 Subject: [PATCH 10/19] Tuning: added exhausts removal (`_unwanted` or UI) To understand code changes, find previous commit which adds flares to the tuning system - it provides detailed checklist for making such change. --- source/main/ForwardDeclarations.h | 3 ++ source/main/gui/panels/GUI_TopMenubar.cpp | 31 +++++++++++++++++ source/main/physics/ActorSpawner.cpp | 33 +++++++++++-------- source/main/physics/SimData.h | 5 +-- source/main/resources/CacheSystem.cpp | 20 +++++++++++ source/main/resources/CacheSystem.h | 8 +++-- .../AddonPartFileFormat.cpp | 26 +++++++++++++++ .../AddonPartFileFormat.h | 2 +- .../tuneup_fileformat/TuneupFileFormat.cpp | 7 ++++ .../tuneup_fileformat/TuneupFileFormat.h | 11 +++++++ 10 files changed, 127 insertions(+), 19 deletions(-) diff --git a/source/main/ForwardDeclarations.h b/source/main/ForwardDeclarations.h index 83b5490329..6341becfd1 100644 --- a/source/main/ForwardDeclarations.h +++ b/source/main/ForwardDeclarations.h @@ -64,6 +64,9 @@ namespace RoR typedef int FlareID_t; //!< Index into `Actor::ar_flares`, use `RoR::FLAREID_INVALID` as empty value static const FlareID_t FLAREID_INVALID = -1; + typedef int ExhaustID_t; //!< Index into `Actor::exhausts`, use `RoR::EXHAUSTID_INVALID` as empty value + static const ExhaustID_t EXHAUSTID_INVALID = -1; + class Actor; class ActorManager; class ActorSpawner; diff --git a/source/main/gui/panels/GUI_TopMenubar.cpp b/source/main/gui/panels/GUI_TopMenubar.cpp index 81e21e83a3..06c4421418 100644 --- a/source/main/gui/panels/GUI_TopMenubar.cpp +++ b/source/main/gui/panels/GUI_TopMenubar.cpp @@ -1908,6 +1908,37 @@ void TopMenubar::Draw(float dt) ImGui::PopID(); // flareid } } + + // Draw exhausts + size_t total_exhausts = tuning_actor->exhausts.size(); + std::string exhausts_title = fmt::format(_LC("Tuning", "Exhausts ({})"), total_exhausts); + if (ImGui::CollapsingHeader(exhausts_title.c_str())) + { + // Draw all exhausts (those removed by addonparts are also present as placeholders) + for (ExhaustID_t exhaustid = 0; exhaustid < (int)tuning_actor->exhausts.size(); exhaustid++) + { + ImGui::PushID(exhaustid); + ImGui::AlignTextToFramePadding(); + + this->DrawTuningBoxedSubjectIdInline(exhaustid); + + this->DrawTuningForceRemoveControls( + exhaustid, + tuning_actor->exhausts[exhaustid].particleSystemName, + tuneup_def && tuneup_def->isExhaustUnwanted(exhaustid), + tuneup_def && tuneup_def->isExhaustForceRemoved(exhaustid), + ModifyProjectRequestType::TUNEUP_FORCEREMOVE_EXHAUST_SET, + ModifyProjectRequestType::TUNEUP_FORCEREMOVE_EXHAUST_RESET); + + this->DrawTuningProtectedChkRightAligned( + exhaustid, + tuneup_def && tuneup_def->isExhaustProtected(exhaustid), + ModifyProjectRequestType::TUNEUP_PROTECTED_EXHAUST_SET, + ModifyProjectRequestType::TUNEUP_PROTECTED_EXHAUST_RESET); + + ImGui::PopID(); // exhaustid + } + } } m_open_menu_hoverbox_min = menu_pos - MENU_HOVERBOX_PADDING; diff --git a/source/main/physics/ActorSpawner.cpp b/source/main/physics/ActorSpawner.cpp index 32c4520858..79e0c199cb 100644 --- a/source/main/physics/ActorSpawner.cpp +++ b/source/main/physics/ActorSpawner.cpp @@ -1282,32 +1282,37 @@ void ActorSpawner::ProcessExhaust(RigDef::Exhaust & def) return; } + const ExhaustID_t exhaust_id = (ExhaustID_t)m_actor->exhausts.size(); exhaust_t exhaust; exhaust.emitterNode = this->GetNodeIndexOrThrow(def.reference_node); exhaust.directionNode = this->GetNodeIndexOrThrow(def.direction_node); std::string template_name = def.particle_name; - if (template_name.empty() || template_name == "default") + if (template_name == "" || template_name == "default") { template_name = "tracks/Smoke"; // defined in `particles/smoke.particle` } + exhaust.particleSystemName = template_name; - std::string name = this->ComposeName(template_name.c_str(), (int)m_actor->exhausts.size()); - exhaust.smoker = this->CreateParticleSystem(name, template_name); - if (exhaust.smoker == nullptr) + if (!TuneupUtil::isExhaustAnyhowRemoved(m_actor->getWorkingTuneupDef(), exhaust_id)) { - std::stringstream msg; - msg << "Failed to create particle system '" << name << "' (template: '" << template_name <<"')"; - AddMessage(Message::TYPE_ERROR, msg.str()); - return; - } + std::string name = this->ComposeName(template_name.c_str(), (int)m_actor->exhausts.size()); + exhaust.smoker = this->CreateParticleSystem(name, template_name); + if (exhaust.smoker == nullptr) + { + std::stringstream msg; + msg << "Failed to create particle system '" << name << "' (template: '" << template_name <<"')"; + AddMessage(Message::TYPE_ERROR, msg.str()); + return; + } - exhaust.smokeNode = m_particles_parent_scenenode->createChildSceneNode(this->ComposeName("exhaust", (int)m_actor->exhausts.size())); - exhaust.smokeNode->attachObject(exhaust.smoker); - exhaust.smokeNode->setPosition(m_actor->ar_nodes[exhaust.emitterNode].AbsPosition); + exhaust.smokeNode = m_particles_parent_scenenode->createChildSceneNode(this->ComposeName("exhaust", (int)m_actor->exhausts.size())); + exhaust.smokeNode->attachObject(exhaust.smoker); + exhaust.smokeNode->setPosition(m_actor->ar_nodes[exhaust.emitterNode].AbsPosition); - m_actor->m_gfx_actor->SetNodeHot(exhaust.emitterNode, true); - m_actor->m_gfx_actor->SetNodeHot(exhaust.directionNode, true); + m_actor->m_gfx_actor->SetNodeHot(exhaust.emitterNode, true); + m_actor->m_gfx_actor->SetNodeHot(exhaust.directionNode, true); + } m_actor->exhausts.push_back(exhaust); } diff --git a/source/main/physics/SimData.h b/source/main/physics/SimData.h index 1fe05ea6df..8a1542d344 100644 --- a/source/main/physics/SimData.h +++ b/source/main/physics/SimData.h @@ -650,8 +650,9 @@ struct exhaust_t { NodeNum_t emitterNode = NODENUM_INVALID; NodeNum_t directionNode = NODENUM_INVALID; - Ogre::SceneNode *smokeNode; - Ogre::ParticleSystem* smoker; + Ogre::SceneNode *smokeNode = nullptr; + Ogre::ParticleSystem* smoker = nullptr; //!< This remains `nullptr` if removed via `addonpart_unwanted_exhaust` or Tuning UI. + std::string particleSystemName; //!< Name in .particle file ~ for display in Tuning UI. }; diff --git a/source/main/resources/CacheSystem.cpp b/source/main/resources/CacheSystem.cpp index 60b9c995a7..74879b215e 100644 --- a/source/main/resources/CacheSystem.cpp +++ b/source/main/resources/CacheSystem.cpp @@ -1827,6 +1827,16 @@ void CacheSystem::ModifyProject(ModifyProjectRequest* request) request->mpr_target_actor->getWorkingTuneupDef()->force_wheel_sides.erase(request->mpr_subject_id); break; + case ModifyProjectRequestType::TUNEUP_FORCEREMOVE_EXHAUST_SET: + request->mpr_target_actor->ensureWorkingTuneupDef(); + request->mpr_target_actor->getWorkingTuneupDef()->force_remove_exhausts.insert(request->mpr_subject_id); + break; + + case ModifyProjectRequestType::TUNEUP_FORCEREMOVE_EXHAUST_RESET: + request->mpr_target_actor->ensureWorkingTuneupDef(); + request->mpr_target_actor->getWorkingTuneupDef()->force_remove_exhausts.erase(request->mpr_subject_id); + break; + case ModifyProjectRequestType::TUNEUP_PROTECTED_PROP_SET: request->mpr_target_actor->ensureWorkingTuneupDef(); request->mpr_target_actor->getWorkingTuneupDef()->protected_props.insert(request->mpr_subject_id); @@ -1867,6 +1877,16 @@ void CacheSystem::ModifyProject(ModifyProjectRequest* request) request->mpr_target_actor->getWorkingTuneupDef()->protected_flares.erase(request->mpr_subject_id); break; + case ModifyProjectRequestType::TUNEUP_PROTECTED_EXHAUST_SET: + request->mpr_target_actor->ensureWorkingTuneupDef(); + request->mpr_target_actor->getWorkingTuneupDef()->protected_exhausts.insert(request->mpr_subject_id); + break; + + case ModifyProjectRequestType::TUNEUP_PROTECTED_EXHAUST_RESET: + request->mpr_target_actor->ensureWorkingTuneupDef(); + request->mpr_target_actor->getWorkingTuneupDef()->protected_exhausts.erase(request->mpr_subject_id); + break; + case ModifyProjectRequestType::PROJECT_LOAD_TUNEUP: { // Instead of loading with the saved tuneup directly, keep the autogenerated and sync it with the save. diff --git a/source/main/resources/CacheSystem.h b/source/main/resources/CacheSystem.h index 98bdc36bf7..e4209690b3 100644 --- a/source/main/resources/CacheSystem.h +++ b/source/main/resources/CacheSystem.h @@ -237,8 +237,10 @@ enum class ModifyProjectRequestType TUNEUP_FORCEREMOVE_FLEXBODY_RESET, //!< 'subject_id' is flexbody ID. TUNEUP_FORCED_WHEEL_SIDE_SET, //!< 'subject_id' is wheel ID, 'value_int' is RoR::WheelSide TUNEUP_FORCED_WHEEL_SIDE_RESET, //!< 'subject_id' is wheel ID. - TUNEUP_FORCEREMOVE_FLARE_SET, //!< 'subject_id' is flare ID. - TUNEUP_FORCEREMOVE_FLARE_RESET, //!< 'subject_id' is flare ID. + TUNEUP_FORCEREMOVE_FLARE_SET, //!< 'subject_id' is flare ID. + TUNEUP_FORCEREMOVE_FLARE_RESET, //!< 'subject_id' is flare ID. + TUNEUP_FORCEREMOVE_EXHAUST_SET, //!< 'subject_id' is exhaust ID. + TUNEUP_FORCEREMOVE_EXHAUST_RESET, //!< 'subject_id' is exhaust ID. TUNEUP_PROTECTED_PROP_SET, //!< 'subject_id' is prop ID. TUNEUP_PROTECTED_PROP_RESET, //!< 'subject_id' is prop ID. TUNEUP_PROTECTED_FLEXBODY_SET, //!< 'subject_id' is flexbody ID. @@ -247,6 +249,8 @@ enum class ModifyProjectRequestType TUNEUP_PROTECTED_WHEEL_RESET, //!< 'subject_id' is wheel ID. TUNEUP_PROTECTED_FLARE_SET, //!< 'subject_id' is flare ID. TUNEUP_PROTECTED_FLARE_RESET, //!< 'subject_id' is flare ID. + TUNEUP_PROTECTED_EXHAUST_SET, //!< 'subject_id' is exhaust ID. + TUNEUP_PROTECTED_EXHAUST_RESET, //!< 'subject_id' is exhaust ID. PROJECT_LOAD_TUNEUP, //!< 'subject' is tuneup filename. This overwrites the auto-generated tuneup with the save. PROJECT_RESET_TUNEUP, //!< 'subject' is empty. This resets the auto-generated tuneup to orig. values. }; diff --git a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp index f66a3025c8..8944491914 100644 --- a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp +++ b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp @@ -155,6 +155,8 @@ void AddonPartUtility::ResolveUnwantedAndTweakedElements(TuneupDefPtr& tuneup, C this->ProcessUnwantedFlexbody(); else if (m_context->getTokKeyword() == "addonpart_unwanted_flare" ) this->ProcessUnwantedFlare(); + else if (m_context->getTokKeyword() == "addonpart_unwanted_exhaust" ) + this->ProcessUnwantedExhaust(); else if (m_context->getTokKeyword() == "addonpart_tweak_wheel") this->ProcessTweakWheel(); else if (m_context->getTokKeyword() == "addonpart_tweak_node") @@ -472,6 +474,30 @@ void AddonPartUtility::ProcessUnwantedFlare() } } +void AddonPartUtility::ProcessUnwantedExhaust() +{ + ROR_ASSERT(m_context->getTokKeyword() == "addonpart_unwanted_exhaust"); // also asserts !EOF and TokenType::KEYWORD + + if (m_context->isTokFloat(1)) + { + if (!m_tuneup->isExhaustProtected((ExhaustID_t)m_context->getTokFloat(1))) + { + m_tuneup->unwanted_exhausts.insert((ExhaustID_t)m_context->getTokFloat(1)); + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': marking exhaust '{}' as UNWANTED", + m_addonpart_entry->fname, m_context->getTokKeyword(), (int)m_context->getTokFloat(1))); + } + else + { + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping exhaust '{}' because it's marked PROTECTED", + m_addonpart_entry->fname, m_context->getTokKeyword(), m_context->getTokString(1))); + } + } + else + { + this->Log(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); + } +} + void AddonPartUtility::ProcessTweakWheel() { ROR_ASSERT(m_context->getTokKeyword() == "addonpart_tweak_wheel"); // also asserts !EOF and TokenType::KEYWORD diff --git a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h index 4127215b31..58736cd403 100644 --- a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h +++ b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h @@ -74,7 +74,6 @@ class AddonPartUtility void ProcessFlexbody(); void ProcessFlare(); void ProcessFlare2(); - void ProcessFlare3(); void ProcessTweakWheel(); void ProcessTweakNode(); void ProcessTweakFlexbody(); @@ -84,6 +83,7 @@ class AddonPartUtility void ProcessUnwantedProp(); void ProcessUnwantedFlexbody(); void ProcessUnwantedFlare(); + void ProcessUnwantedExhaust(); void Log(const std::string& text); diff --git a/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp b/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp index a41557f8f9..a9bf3d0340 100644 --- a/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp +++ b/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp @@ -452,6 +452,13 @@ bool RoR::TuneupUtil::isFlareAnyhowRemoved(TuneupDefPtr& tuneup_def, FlareID_t f && (tuneup_def->isFlareUnwanted(flare_id) || tuneup_def->isFlareForceRemoved(flare_id)); } +// > exhaust +bool RoR::TuneupUtil::isExhaustAnyhowRemoved(TuneupDefPtr& tuneup_def, ExhaustID_t exhaust_id) +{ + return tuneup_def + && (tuneup_def->isExhaustUnwanted(exhaust_id) || tuneup_def->isExhaustForceRemoved(exhaust_id)); +} + bool RoR::TuneupUtil::isAddonPartUsed(TuneupDefPtr& tuneup_entry, const std::string& filename) { return tuneup_entry diff --git a/source/main/resources/tuneup_fileformat/TuneupFileFormat.h b/source/main/resources/tuneup_fileformat/TuneupFileFormat.h index cf3d5a495a..f97150eb38 100644 --- a/source/main/resources/tuneup_fileformat/TuneupFileFormat.h +++ b/source/main/resources/tuneup_fileformat/TuneupFileFormat.h @@ -106,6 +106,7 @@ struct TuneupDef: public RefCountingObject std::set unwanted_props; //!< 'addonpart_unwanted_prop' directives. std::set unwanted_flexbodies; //!< 'addonpart_unwanted_flexbody' directives. std::set unwanted_flares; //!< 'addonpart_unwanted_flare' directives. + std::set unwanted_exhausts; //!< 'addonpart_unwanted_exhaust' directives. /// @} /// @name UI-controlled forced changes (override addonparts) @@ -114,6 +115,7 @@ struct TuneupDef: public RefCountingObject std::set force_remove_flexbodies; //!< UI overrides std::map force_wheel_sides; //!< UI overrides std::set force_remove_flares; //!< User unticked an UI checkbox in Tuning menu, section Flares. + std::set force_remove_exhausts; //!< User unticked an UI checkbox in Tuning menu, section Exhausts. /// @} /// @name UI-controlled protection from addonpart tweaks @@ -123,6 +125,7 @@ struct TuneupDef: public RefCountingObject std::set protected_props; //!< Props which cannot be altered via 'addonpart_tweak_prop' or 'addonpart_unwanted_prop' directive. std::set protected_flexbodies; //!< Flexbodies which cannot be altered via 'addonpart_tweak_flexbody' or 'addonpart_unwanted_flexbody' directive. std::set protected_flares; //!< Flares which cannot be altered via 'addonpart_unwanted_flare' directive. + std::set protected_exhausts; //!< Exhausts which cannot be altered via 'addonpart_unwanted_exhaust' directive. /// @} TuneupDefPtr clone(); @@ -135,6 +138,7 @@ struct TuneupDef: public RefCountingObject bool isWheelProtected(WheelID_t wheelid) const { return protected_wheels.find(wheelid) != protected_wheels.end(); } bool isNodeProtected(NodeNum_t nodenum) const { return protected_nodes.find(nodenum) != protected_nodes.end(); } bool isFlareProtected(FlareID_t flareid) const { return protected_flares.find(flareid) != protected_flares.end(); } + bool isExhaustProtected(ExhaustID_t exhaustid) const { return protected_exhausts.find(exhaustid) != protected_exhausts.end(); } /// @} /// @name Unwanted-state helpers @@ -142,6 +146,7 @@ struct TuneupDef: public RefCountingObject bool isPropUnwanted(PropID_t propid) { return unwanted_props.find(propid) != unwanted_props.end(); } bool isFlexbodyUnwanted(FlexbodyID_t flexbodyid) { return unwanted_flexbodies.find(flexbodyid) != unwanted_flexbodies.end(); } bool isFlareUnwanted(FlareID_t flareid) { return unwanted_flares.find(flareid) != unwanted_flares.end(); } + bool isExhaustUnwanted(ExhaustID_t exhaustid) { return unwanted_exhausts.find(exhaustid) != unwanted_exhausts.end(); } /// @} /// @name Forced-state helpers @@ -150,6 +155,7 @@ struct TuneupDef: public RefCountingObject bool isFlexbodyForceRemoved(FlexbodyID_t flexbodyid) { return force_remove_flexbodies.find(flexbodyid) != force_remove_flexbodies.end(); } bool isWheelSideForced(WheelID_t wheelid, WheelSide& out_val) const; bool isFlareForceRemoved(FlareID_t flareid) { return force_remove_flares.find(flareid) != force_remove_flares.end(); } + bool isExhaustForceRemoved(ExhaustID_t exhaustid) { return force_remove_exhausts.find(exhaustid) != force_remove_exhausts.end(); } /// @} }; @@ -206,6 +212,11 @@ class TuneupUtil static bool isFlareAnyhowRemoved(TuneupDefPtr& tuneup_def, FlareID_t flare_id); /// @} + /// @name Exhaust helpers + /// @{ + static bool isExhaustAnyhowRemoved(TuneupDefPtr& tuneup_def, ExhaustID_t exhaust_id); + /// @} + private: static void ParseTuneupAttribute(const std::string& line, TuneupDefPtr& tuneup_def); From 670f61b996780c76622ab0412547fa0a28ccfdfb Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Sat, 27 Apr 2024 02:44:27 +0200 Subject: [PATCH 11/19] :bug: Fixed GenericDoc discarding NAKED strings that start like bools. If OPTION_ALLOW_NAKED_STRINGS was used, strings starting like true/false, for example f or t alone, would get discarded. --- source/main/utils/GenericFileFormat.cpp | 55 +++++++++++++------------ 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/source/main/utils/GenericFileFormat.cpp b/source/main/utils/GenericFileFormat.cpp index 0e816be27f..898e959ad8 100644 --- a/source/main/utils/GenericFileFormat.cpp +++ b/source/main/utils/GenericFileFormat.cpp @@ -69,6 +69,7 @@ struct DocumentParser void ProcessChar(const char c); void ProcessEOF(); + void ProcessSeparatorWithinBool(); void BeginToken(const char c); void UpdateComment(const char c); @@ -580,6 +581,28 @@ void DocumentParser::UpdateNumber(const char c) } } +void DocumentParser::ProcessSeparatorWithinBool() +{ + this->DiscontinueBool(); + switch (partial_tok_type) + { + case PartialToken::KEYWORD: + this->FlushStringishToken(TokenType::KEYWORD); + break; + case PartialToken::STRING_NAKED: + this->FlushStringishToken(TokenType::STRING); + break; + default: + // Discard token + tok.push_back('\0'); + App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_WARNING, + fmt::format("{}, line {}, pos {}: discarding incomplete boolean token '{}'", datastream->getName(), line_num, line_pos, tok.data())); + tok.clear(); + partial_tok_type = PartialToken::NONE; + break; + } +} + void DocumentParser::UpdateBool(const char c) { switch (c) @@ -590,22 +613,12 @@ void DocumentParser::UpdateBool(const char c) case ' ': case ',': case '\t': - // Discard token - tok.push_back('\0'); - App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_WARNING, - fmt::format("{}, line {}, pos {}: discarding incomplete boolean token '{}'", datastream->getName(), line_num, line_pos, tok.data())); - tok.clear(); - partial_tok_type = PartialToken::NONE; + this->ProcessSeparatorWithinBool(); line_pos++; break; case '\n': - // Discard token - tok.push_back('\0'); - App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_WARNING, - fmt::format("{}, line {}, pos {}: discarding incomplete boolean token '{}'", datastream->getName(), line_num, line_pos, tok.data())); - tok.clear(); - partial_tok_type = PartialToken::NONE; + this->ProcessSeparatorWithinBool(); // Break line doc.tokens.push_back({ TokenType::LINEBREAK, 0.f }); line_num++; @@ -615,16 +628,11 @@ void DocumentParser::UpdateBool(const char c) case ':': if (options & GenericDocument::OPTION_ALLOW_SEPARATOR_COLON) { - // Discard token - tok.push_back('\0'); - App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_WARNING, - fmt::format("{}, line {}, pos {}: discarding incomplete boolean token '{}'", datastream->getName(), line_num, line_pos, tok.data())); - tok.clear(); - partial_tok_type = PartialToken::NONE; + this->ProcessSeparatorWithinBool(); } else { - partial_tok_type = PartialToken::GARBAGE; + this->DiscontinueBool(); tok.push_back(c); } line_pos++; @@ -633,16 +641,11 @@ void DocumentParser::UpdateBool(const char c) case '=': if (options & GenericDocument::OPTION_ALLOW_SEPARATOR_EQUALS) { - // Discard token - tok.push_back('\0'); - App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_WARNING, - fmt::format("{}, line {}, pos {}: discarding incomplete boolean token '{}'", datastream->getName(), line_num, line_pos, tok.data())); - tok.clear(); - partial_tok_type = PartialToken::NONE; + this->ProcessSeparatorWithinBool(); } else { - partial_tok_type = PartialToken::GARBAGE; + this->DiscontinueBool(); tok.push_back(c); } line_pos++; From 31f31831d4dbf328037ef47f00877676ccbdc1c0 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Sat, 27 Apr 2024 18:44:44 +0200 Subject: [PATCH 12/19] Tuning: fixed flares not loading material in addonpart ZIP. Of course I could't get it right the first time. The mechanism I added is the same thing that props and flexbodies already use. Search fulltext for `*_rg_override`. --- source/main/physics/ActorSpawner.cpp | 3 ++- .../resources/addonpart_fileformat/AddonPartFileFormat.cpp | 4 ++++ source/main/resources/rig_def_fileformat/RigDef_File.h | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/source/main/physics/ActorSpawner.cpp b/source/main/physics/ActorSpawner.cpp index 79e0c199cb..12e7654436 100644 --- a/source/main/physics/ActorSpawner.cpp +++ b/source/main/physics/ActorSpawner.cpp @@ -2245,7 +2245,8 @@ void ActorSpawner::ProcessFlare2(RigDef::Flare2 & def) } } - Ogre::MaterialPtr material = this->FindOrCreateCustomizedMaterial(material_name, m_custom_resource_group); + std::string material_rg = (def._material_rg_override != "") ? def._material_rg_override : m_actor->getTruckFileResourceGroup(); + Ogre::MaterialPtr material = this->FindOrCreateCustomizedMaterial(material_name, material_rg); if (!material.isNull()) { flare.bbs->setMaterial(material); diff --git a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp index 8944491914..906e069298 100644 --- a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp +++ b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp @@ -358,6 +358,8 @@ void AddonPartUtility::ProcessFlare() if (n > 8) { def.size = m_context->getTokFloat(8); } if (n > 9) { def.material_name = m_context->getTokString(9); } + def._material_rg_override = m_addonpart_entry->resource_group; + m_module->flares2.push_back(def); } @@ -397,6 +399,8 @@ void AddonPartUtility::ProcessFlare2() if (n > 9) { def.size = m_context->getTokFloat(9); } if (n > 10) { def.material_name = m_context->getTokString(10); } + def._material_rg_override = m_addonpart_entry->resource_group; + m_module->flares2.push_back(def); } diff --git a/source/main/resources/rig_def_fileformat/RigDef_File.h b/source/main/resources/rig_def_fileformat/RigDef_File.h index d35a28c4bf..f1c349893a 100644 --- a/source/main/resources/rig_def_fileformat/RigDef_File.h +++ b/source/main/resources/rig_def_fileformat/RigDef_File.h @@ -879,6 +879,7 @@ struct Flare2 // Used for both 'flares' and 'flares2' sections int blink_delay_milis = -2; float size = -1.f; Ogre::String material_name; + Ogre::String _material_rg_override; //!< Needed for addonparts }; struct Flare3: public Flare2 From dd273da30df44f90db2bc5a41b3958b289a91870 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Sat, 27 Apr 2024 23:10:48 +0200 Subject: [PATCH 13/19] Tuning: fixed crash after saving tuneup (on menu reload) Null pointer access :/ I removed the confusing variable `current_actor` altogether - now there's only `tuning_actor`. --- source/main/gui/panels/GUI_TopMenubar.cpp | 28 ++++++++++++----------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/source/main/gui/panels/GUI_TopMenubar.cpp b/source/main/gui/panels/GUI_TopMenubar.cpp index 06c4421418..bdbe8c0423 100644 --- a/source/main/gui/panels/GUI_TopMenubar.cpp +++ b/source/main/gui/panels/GUI_TopMenubar.cpp @@ -2413,24 +2413,27 @@ void TopMenubar::RefreshAiPresets() void TopMenubar::RefreshTuningMenu() { - const ActorPtr& current_actor = App::GetGameContext()->GetPlayerActor(); + // Updates/resets the tuning menu for the current vehicle driven by player (if any). + // ------------------------------------------------------------------------------- + if (App::sim_tuning_enabled->getBool() && (App::mp_state->getEnum() != MpState::CONNECTED) - && current_actor - && (tuning_actor != current_actor)) + && App::GetGameContext()->GetPlayerActor() + && (tuning_actor != App::GetGameContext()->GetPlayerActor())) { - ROR_ASSERT(current_actor->getUsedActorEntry()); + tuning_actor = App::GetGameContext()->GetPlayerActor(); + ROR_ASSERT(tuning_actor->getUsedActorEntry()); tuning_addonparts.clear(); tuning_saves.resetResults(); // Addonparts matched by GUID - if (current_actor->getUsedActorEntry()->guid != "") + if (tuning_actor->getUsedActorEntry()->guid != "") { CacheQuery query_addonparts; query_addonparts.cqy_filter_type = LT_AddonPart; - query_addonparts.cqy_filter_guid = current_actor->getUsedActorEntry()->guid; - query_addonparts.cqy_filter_target_filename = current_actor->getTruckFileName(); // Addonparts without any filenames listed will just pass. + query_addonparts.cqy_filter_guid = tuning_actor->getUsedActorEntry()->guid; + query_addonparts.cqy_filter_target_filename = tuning_actor->getTruckFileName(); // Addonparts without any filenames listed will just pass. App::GetCacheSystem()->Query(query_addonparts); for (CacheQueryResult& res: query_addonparts.cqy_results) { @@ -2439,9 +2442,9 @@ void TopMenubar::RefreshTuningMenu() } // Addonparts force-installed via [browse all] button; watch for duplicates. - if (current_actor->getWorkingTuneupDef()) + if (tuning_actor->getWorkingTuneupDef()) { - for (std::string const& use_addonpart_fname: current_actor->getWorkingTuneupDef()->use_addonparts) + for (std::string const& use_addonpart_fname: tuning_actor->getWorkingTuneupDef()->use_addonparts) { CacheEntryPtr entry = App::GetCacheSystem()->FindEntryByFilename(LT_AddonPart, /*partial:*/false, use_addonpart_fname); if (entry) @@ -2455,7 +2458,7 @@ void TopMenubar::RefreshTuningMenu() } tuning_saves.cqy_filter_type = LT_Tuneup; - tuning_saves.cqy_filter_guid = current_actor->getUsedActorEntry()->guid; + tuning_saves.cqy_filter_guid = tuning_actor->getUsedActorEntry()->guid; tuning_saves.cqy_filter_category_id = CID_Tuneups; // Exclude auto-generated entries tuning_saves.resetResults(); App::GetCacheSystem()->Query(tuning_saves); @@ -2473,7 +2476,7 @@ void TopMenubar::RefreshTuningMenu() // Refresh `tuning_addonparts_conflicting` listing ~ test used addonparts against unused. tuning_addonparts_conflict_w_used.clear(); tuning_addonparts_conflict_w_used.resize(tuning_addonparts.size(), false); - if (current_actor->getWorkingTuneupDef()) + if (tuning_actor->getWorkingTuneupDef()) { for (const std::string& use_addonpart_fname: tuning_actor->getWorkingTuneupDef()->use_addonparts) { @@ -2491,13 +2494,12 @@ void TopMenubar::RefreshTuningMenu() tuning_rwidget_cursorx_min = 0.f; } - else if (!App::sim_tuning_enabled->getBool() || !current_actor) + else if (!App::sim_tuning_enabled->getBool() || !App::GetGameContext()->GetPlayerActor()) { tuning_addonparts.clear(); tuning_saves.resetResults(); tuning_actor = nullptr; } - tuning_actor = current_actor; } void TopMenubar::DrawTuningProtectedChkRightAligned(int subject_id, bool protectchk_value, ModifyProjectRequestType request_type_set, ModifyProjectRequestType request_type_reset) From e8313cc1f5bd1c4f702c1356b5bd53538797ac4b Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Sat, 27 Apr 2024 23:31:32 +0200 Subject: [PATCH 14/19] Tuning: UI touch: right-aligned addonpart [Reload] buttons. --- source/main/gui/panels/GUI_TopMenubar.cpp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/source/main/gui/panels/GUI_TopMenubar.cpp b/source/main/gui/panels/GUI_TopMenubar.cpp index bdbe8c0423..1153e7a937 100644 --- a/source/main/gui/panels/GUI_TopMenubar.cpp +++ b/source/main/gui/panels/GUI_TopMenubar.cpp @@ -1611,7 +1611,7 @@ void TopMenubar::Draw(float dt) { for (size_t i = 0; i < tuning_addonparts.size(); i++) { - CacheEntryPtr& addonpart_entry = tuning_addonparts[i]; + const CacheEntryPtr& addonpart_entry = tuning_addonparts[i]; ImGui::PushID(addonpart_entry->fname.c_str()); const bool conflict_w_hovered = tuning_hovered_addonpart @@ -1660,11 +1660,17 @@ void TopMenubar::Draw(float dt) { tuning_hovered_addonpart = nullptr; } - // Reload button + // Reload button (right-aligned) ImGui::SameLine(); - ImGui::Dummy(ImVec2(10.f, 1.f)); - ImGui::SameLine(); - if (ImGui::SmallButton(_LC("Tuning", "Reload"))) + if (tuning_rwidget_cursorx_min < ImGui::GetCursorPosX()) // Make sure button won't draw over save button + tuning_rwidget_cursorx_min = ImGui::GetCursorPosX(); + ImGui::AlignTextToFramePadding(); + std::string reloadbtn_text = _LC("Tuning", "Reload"); + const float reloadbtn_w = ImGui::CalcTextSize(reloadbtn_text.c_str()).x + ImGui::GetStyle().FramePadding.x * 2; + const float reloadbtn_cursorx = std::max(ImGui::GetWindowContentRegionWidth() - reloadbtn_w, tuning_rwidget_cursorx_min); + ImGui::SetCursorPosX(reloadbtn_cursorx); + const bool reloadbtn_pressed = ImGui::SmallButton(reloadbtn_text.c_str()); + if (reloadbtn_pressed) { // Create spawn request while actor still exists // Note we don't use `ActorModifyRequest::Type::RELOAD` because we don't need the bundle reloaded. From 33259c72d4ff165fcbd3af202064d5bb3551a149 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Thu, 9 May 2024 02:09:00 +0200 Subject: [PATCH 15/19] Tuning: Removed redundant `_override_rg` mechanic. This fixes 55e98ca23611f6c802a163875b114e4e28adfb85 which introduced `_somemedia_override_rg` fields to props and flexbodies (later followed by flares), alghough there was already a more elegant solution - the `RigDef::Module::origin_addonpart` field used by managedmaterials (created along with the very inception of the AddonPart system in b1f4d23ce6d57c0ebbf1e75cedc8bf1458f2b47e). I couldn't determine why I created this redundant logic in the first place. Either way, This commit unifies the logic around the `origin_addonpart` mechanic. --- source/main/physics/Actor.cpp | 2 +- source/main/physics/ActorSpawner.cpp | 29 +++++++++++++------ source/main/physics/ActorSpawner.h | 1 + .../AddonPartFileFormat.cpp | 6 ---- .../rig_def_fileformat/RigDef_File.h | 3 -- 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/source/main/physics/Actor.cpp b/source/main/physics/Actor.cpp index 26bbd1452c..d57c20ad6e 100644 --- a/source/main/physics/Actor.cpp +++ b/source/main/physics/Actor.cpp @@ -4591,7 +4591,7 @@ void Actor::UpdatePropAnimInputEvents() std::string Actor::getTruckFileResourceGroup() { - return m_gfx_actor->GetResourceGroup(); + return m_used_actor_entry->resource_group; } CacheEntryPtr& Actor::getUsedActorEntry() diff --git a/source/main/physics/ActorSpawner.cpp b/source/main/physics/ActorSpawner.cpp index 12e7654436..8dcdaf9430 100644 --- a/source/main/physics/ActorSpawner.cpp +++ b/source/main/physics/ActorSpawner.cpp @@ -1551,7 +1551,6 @@ void ActorSpawner::ProcessFlexbody(RigDef::Flexbody& def) try { - std::string mesh_rg = (def._mesh_rg_override != "") ? def._mesh_rg_override : m_actor->getTruckFileResourceGroup(); auto* flexbody = m_flex_factory.CreateFlexBody( (FlexbodyID_t)m_actor->m_gfx_actor->m_flexbodies.size(), this->GetNodeIndexOrThrow(def.reference_node), @@ -1561,7 +1560,7 @@ void ActorSpawner::ProcessFlexbody(RigDef::Flexbody& def) TuneupUtil::getTweakedFlexbodyRotation(m_actor->getWorkingTuneupDef(), flexbody_id, def.rotation), node_indices, TuneupUtil::getTweakedFlexbodyMedia(m_actor->getWorkingTuneupDef(), flexbody_id, 0, def.mesh_name), - TuneupUtil::getTweakedFlexbodyMediaRG(m_actor->getWorkingTuneupDef(), flexbody_id, 0, mesh_rg) + TuneupUtil::getTweakedFlexbodyMediaRG(m_actor->getWorkingTuneupDef(), flexbody_id, 0, this->GetCurrentElementMediaRG()) ); if (flexbody == nullptr) @@ -1647,14 +1646,13 @@ void ActorSpawner::ProcessProp(RigDef::Prop & def) { steering_wheel_offset = def.special_prop_dashboard.offset; } - std::string media1_rg = (def._mesh_rg_override != "") ? def._mesh_rg_override : m_actor->getTruckFileResourceGroup(); prop.pp_wheel_rot_degree = def.special_prop_dashboard.rotation_angle; prop.pp_wheel_scene_node = m_props_parent_scenenode->createChildSceneNode(this->ComposeName("steering wheel @ prop", prop_id)); prop.pp_wheel_pos = steering_wheel_offset; prop.pp_media[1] = TuneupUtil::getTweakedPropMedia(m_actor->getWorkingTuneupDef(), prop_id, 1, def.special_prop_dashboard.mesh_name); prop.pp_wheel_mesh_obj = new MeshObject( prop.pp_media[1], - TuneupUtil::getTweakedPropMediaRG(m_actor->getWorkingTuneupDef(), prop_id, 1, media1_rg), + TuneupUtil::getTweakedPropMediaRG(m_actor->getWorkingTuneupDef(), prop_id, 1, this->GetCurrentElementMediaRG()), this->ComposeName("steering wheel entity @ prop", prop_id), prop.pp_wheel_scene_node ); @@ -1662,12 +1660,11 @@ void ActorSpawner::ProcessProp(RigDef::Prop & def) } /* CREATE THE PROP */ - std::string media0_rg = (def._mesh_rg_override != "") ? def._mesh_rg_override : m_actor->getTruckFileResourceGroup(); prop.pp_scene_node = m_props_parent_scenenode->createChildSceneNode(this->ComposeName("prop", prop_id)); prop.pp_media[0] = TuneupUtil::getTweakedPropMedia(m_actor->getWorkingTuneupDef(), prop_id, 0, def.mesh_name); prop.pp_mesh_obj = new MeshObject(//def.mesh_name, resource_group, instance_name, prop.pp_scene_node); prop.pp_media[0], - TuneupUtil::getTweakedPropMediaRG(m_actor->getWorkingTuneupDef(), prop_id, 0, media0_rg), + TuneupUtil::getTweakedPropMediaRG(m_actor->getWorkingTuneupDef(), prop_id, 0, this->GetCurrentElementMediaRG()), this->ComposeName("prop entity", prop_id), prop.pp_scene_node); @@ -2245,8 +2242,7 @@ void ActorSpawner::ProcessFlare2(RigDef::Flare2 & def) } } - std::string material_rg = (def._material_rg_override != "") ? def._material_rg_override : m_actor->getTruckFileResourceGroup(); - Ogre::MaterialPtr material = this->FindOrCreateCustomizedMaterial(material_name, material_rg); + Ogre::MaterialPtr material = this->FindOrCreateCustomizedMaterial(material_name, this->GetCurrentElementMediaRG()); if (!material.isNull()) { flare.bbs->setMaterial(material); @@ -2368,7 +2364,7 @@ void ActorSpawner::ProcessManagedMaterial(RigDef::ManagedMaterial & def) } // Check all textures exist - std::string resource_group = (m_current_module->origin_addonpart) ? m_current_module->origin_addonpart->resource_group : m_custom_resource_group; + std::string resource_group = this->GetCurrentElementMediaRG(); if (!Ogre::ResourceGroupManager::getSingleton().resourceExists(resource_group, def.diffuse_map)) { this->AddMessage(Message::TYPE_WARNING, "Skipping managed material, missing texture file: " + def.diffuse_map); @@ -7335,3 +7331,18 @@ void ActorSpawner::CreateMaterialFlare(int flareid, Ogre::MaterialPtr m) m_actor->m_gfx_actor->m_flare_materials.push_back(binding); } + +std::string ActorSpawner::GetCurrentElementMediaRG() +{ + // Media (Textures/Materials/meshes) can be either in AddonPart bundle or the vehicle bundle. + // ========================================================================================= + + if (m_current_module->origin_addonpart) + { + return m_current_module->origin_addonpart->resource_group; + } + else + { + return m_actor->getTruckFileResourceGroup(); + } +} diff --git a/source/main/physics/ActorSpawner.h b/source/main/physics/ActorSpawner.h index ebd20f9e0b..f4101977b4 100644 --- a/source/main/physics/ActorSpawner.h +++ b/source/main/physics/ActorSpawner.h @@ -388,6 +388,7 @@ class ActorSpawner Ogre::MaterialPtr InstantiateManagedMaterial(Ogre::String const & rg_name, Ogre::String const & source_name, Ogre::String const & clone_name); void CreateCabVisual(); void CreateMaterialFlare(int flare_index, Ogre::MaterialPtr mat); + std::string GetCurrentElementMediaRG(); //!< Where to load media from (the addonpart's bundle or vehicle's bundle?) /// @param rim_ratio Percentual size of the rim. void CreateWheelVisuals( diff --git a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp index 906e069298..c3193a6a0a 100644 --- a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp +++ b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp @@ -265,7 +265,6 @@ void AddonPartUtility::ProcessProp() def.rotation.z = m_context->getTokFloat(8); def.mesh_name = m_context->getTokString(9); - def._mesh_rg_override = m_addonpart_entry->resource_group; m_module->props.push_back(def); } @@ -296,7 +295,6 @@ void AddonPartUtility::ProcessFlexbody() def.rotation.z = m_context->getTokFloat(8); def.mesh_name = m_context->getTokString(9); - def._mesh_rg_override = m_addonpart_entry->resource_group; m_context->seekNextLine(); @@ -358,8 +356,6 @@ void AddonPartUtility::ProcessFlare() if (n > 8) { def.size = m_context->getTokFloat(8); } if (n > 9) { def.material_name = m_context->getTokString(9); } - def._material_rg_override = m_addonpart_entry->resource_group; - m_module->flares2.push_back(def); } @@ -399,8 +395,6 @@ void AddonPartUtility::ProcessFlare2() if (n > 9) { def.size = m_context->getTokFloat(9); } if (n > 10) { def.material_name = m_context->getTokString(10); } - def._material_rg_override = m_addonpart_entry->resource_group; - m_module->flares2.push_back(def); } diff --git a/source/main/resources/rig_def_fileformat/RigDef_File.h b/source/main/resources/rig_def_fileformat/RigDef_File.h index f1c349893a..952423ef47 100644 --- a/source/main/resources/rig_def_fileformat/RigDef_File.h +++ b/source/main/resources/rig_def_fileformat/RigDef_File.h @@ -879,7 +879,6 @@ struct Flare2 // Used for both 'flares' and 'flares2' sections int blink_delay_milis = -2; float size = -1.f; Ogre::String material_name; - Ogre::String _material_rg_override; //!< Needed for addonparts }; struct Flare3: public Flare2 @@ -895,7 +894,6 @@ struct Flexbody Ogre::Vector3 offset = Ogre::Vector3::ZERO; Ogre::Vector3 rotation = Ogre::Vector3::ZERO; Ogre::String mesh_name; - Ogre::String _mesh_rg_override; //!< Needed for addonparts std::list animations; std::vector node_list_to_import; std::vector node_list; @@ -1105,7 +1103,6 @@ struct Prop Ogre::Vector3 offset = Ogre::Vector3::ZERO; Ogre::Vector3 rotation = Ogre::Vector3::ZERO; Ogre::String mesh_name; - Ogre::String _mesh_rg_override; //!< Needed for addonparts std::list animations; CameraSettings camera_settings; SpecialProp special = SpecialProp::NONE; From 7d0b118514d0d161ac6cf8167d2c0a17c255ee17 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Wed, 8 May 2024 00:00:57 +0200 Subject: [PATCH 16/19] Tuning: added managedmaterial tweaking. Another exercise in extending the Tuning system. Took ~5 hours (2 hours just Spawner - resolving texture tweaks was tricky) * An `addonpart_unwanted_managedmaterial` directive was added. * An `addonpart_tweak_managedmaterial []` directive was added. Unlike all other elements which are referenced by ID, managedmaterials are referenced by name because it must be unique anyway. Codechanges ~HOWTO add a new feature to the Tuning system [if you're a daring programmer] changes are ordered to intuitively form the picture (this is an updated version of the 'flares' guide) ================================================== * TuneupFileFormat.h: - added struct TuneupManagedMatTweak ~ same pattern as other tweaks. - added unwanted/forece_remove/protected flare lists to the TuneupDef document, and helper functions to TuneupUtil. See //comments to understand their meaning. ~ I followed the same pattern as other elements use, except I used plain `std::string` for an ID. * TuneupFileFormat.cpp: - implemented all new functions, in same manner as nodes/flexbodies/props already do. No special features. * AddonPartFileFormat: - Added ProcessTweaked***() and ProcessUnwanted***() helper functions for managedmats. - Extended `ResolveUnwantedAndTweakedElements()` to use them. * CacheSystem: - added PROTECTED + FORCEREMOVE SET/RESET ModifyProjectRequest types. ~ in RoR, everything important is done via message queue. Tuning changes are done by MSG_EDI_MODIFY_PROJECT_REQUESTED. ~ Unlike other elements which use `subject_id` field (integer), these use `subject` (string) field, see //!< comments. * GUI_TopMenubar.cpp: - Added magic constant TUNING_SUBJECTID_USE_NAME to be able to use the helper funcs `DrawTuning{ForceRemove/Protected}Controls` with name (subject) instead of ID (subject_id) - added "Managed Materials" section ~ pretty much a copypaste of the Exhausts section code above, just modified to fit: changed the loop to read managedmats instead of exhausts, added code to compose name of the flare, and use the new SUBJECTID_USE_NAME constant. * ActorSpawner: - created `AssignManagedMaterialTexture()` to resolve Tweaks and check if texture exists - Helper for `ProcessManagedMaterial()`. - if the managed material is removed by Tuning system, just create a placeholder. ~ This is the moment of truth. When spawning, the tuning changes must be evaluated, but to maintain consistent lists of elements in the Tuning menu (and other tools), the unwanted/forceRemoved elements are left around as inactive 'placeholders'. In this case, it's a clone of "tracks/transred" material from builtin resources. --- source/main/gui/panels/GUI_TopMenubar.cpp | 60 +++++++++++- source/main/gui/panels/GUI_TopMenubar.h | 3 +- source/main/physics/ActorSpawner.cpp | 95 ++++++++++++++----- source/main/physics/ActorSpawner.h | 1 + source/main/resources/CacheSystem.cpp | 20 ++++ source/main/resources/CacheSystem.h | 4 + .../AddonPartFileFormat.cpp | 71 ++++++++++++++ .../AddonPartFileFormat.h | 6 +- .../tuneup_fileformat/TuneupFileFormat.cpp | 77 +++++++++++++++ .../tuneup_fileformat/TuneupFileFormat.h | 24 +++++ 10 files changed, 328 insertions(+), 33 deletions(-) diff --git a/source/main/gui/panels/GUI_TopMenubar.cpp b/source/main/gui/panels/GUI_TopMenubar.cpp index 1153e7a937..04c5fbee90 100644 --- a/source/main/gui/panels/GUI_TopMenubar.cpp +++ b/source/main/gui/panels/GUI_TopMenubar.cpp @@ -1945,6 +1945,37 @@ void TopMenubar::Draw(float dt) ImGui::PopID(); // exhaustid } } + + // Draw managed materials + size_t total_materials = tuning_actor->ar_managed_materials.size(); + std::string materials_title = fmt::format(_LC("Tuning", "Managed Materials ({})"), total_materials); + if (ImGui::CollapsingHeader(materials_title.c_str())) + { + // Draw all materials (those removed by addonparts are also present as placeholders) + for (auto mm_pair: tuning_actor->ar_managed_materials) + { + const std::string& material_name = mm_pair.first; + ImGui::PushID(material_name.c_str()); + ImGui::AlignTextToFramePadding(); + + this->DrawTuningForceRemoveControls( + TUNING_SUBJECTID_USE_NAME, + material_name, + tuneup_def && tuneup_def->isManagedMatUnwanted(material_name), + tuneup_def && tuneup_def->isManagedMatForceRemoved(material_name), + ModifyProjectRequestType::TUNEUP_FORCEREMOVE_MANAGEDMAT_SET, + ModifyProjectRequestType::TUNEUP_FORCEREMOVE_MANAGEDMAT_RESET); + + this->DrawTuningProtectedChkRightAligned( + TUNING_SUBJECTID_USE_NAME, + tuneup_def && tuneup_def->isManagedMatProtected(material_name), + ModifyProjectRequestType::TUNEUP_PROTECTED_MANAGEDMAT_SET, + ModifyProjectRequestType::TUNEUP_PROTECTED_MANAGEDMAT_RESET, + material_name); + + ImGui::PopID(); // material_name.c_str() + } + } } m_open_menu_hoverbox_min = menu_pos - MENU_HOVERBOX_PADDING; @@ -2508,7 +2539,7 @@ void TopMenubar::RefreshTuningMenu() } } -void TopMenubar::DrawTuningProtectedChkRightAligned(int subject_id, bool protectchk_value, ModifyProjectRequestType request_type_set, ModifyProjectRequestType request_type_reset) +void TopMenubar::DrawTuningProtectedChkRightAligned(const int subject_id, bool protectchk_value, ModifyProjectRequestType request_type_set, ModifyProjectRequestType request_type_reset, const std::string& subject /* ="" */) { // > resolve the alignment ImGui::SameLine(); @@ -2531,7 +2562,14 @@ void TopMenubar::DrawTuningProtectedChkRightAligned(int subject_id, bool protect { ModifyProjectRequest* request = new ModifyProjectRequest(); request->mpr_target_actor = tuning_actor; - request->mpr_subject_id = subject_id; + if (subject_id == TUNING_SUBJECTID_USE_NAME) + { + request->mpr_subject = subject; + } + else + { + request->mpr_subject_id = subject_id; + } request->mpr_type = (protectchk_value) ? request_type_set : request_type_reset; App::GetGameContext()->PushMessage(Message(MSG_EDI_MODIFY_PROJECT_REQUESTED, request)); } @@ -2581,7 +2619,14 @@ void TopMenubar::DrawTuningForceRemoveControls(const int subject_id, const std:: { ModifyProjectRequest* req = new ModifyProjectRequest(); req->mpr_type = request_type_set; - req->mpr_subject_id = subject_id; + if (subject_id == TUNING_SUBJECTID_USE_NAME) + { + req->mpr_subject = name; + } + else + { + req->mpr_subject_id = subject_id; + } req->mpr_target_actor = tuning_actor; App::GetGameContext()->PushMessage(Message(MSG_EDI_MODIFY_PROJECT_REQUESTED, req)); } @@ -2589,7 +2634,14 @@ void TopMenubar::DrawTuningForceRemoveControls(const int subject_id, const std:: { ModifyProjectRequest* req = new ModifyProjectRequest(); req->mpr_type = request_type_reset; - req->mpr_subject_id = subject_id; + if (subject_id == TUNING_SUBJECTID_USE_NAME) + { + req->mpr_subject = name; + } + else + { + req->mpr_subject_id = subject_id; + } req->mpr_target_actor = tuning_actor; App::GetGameContext()->PushMessage(Message(MSG_EDI_MODIFY_PROJECT_REQUESTED, req)); } diff --git a/source/main/gui/panels/GUI_TopMenubar.h b/source/main/gui/panels/GUI_TopMenubar.h index 4cec3f36c3..d731a7f72c 100644 --- a/source/main/gui/panels/GUI_TopMenubar.h +++ b/source/main/gui/panels/GUI_TopMenubar.h @@ -54,6 +54,7 @@ class TopMenubar const ImVec4 ORANGE_TEXT = ImVec4(0.9f, 0.6f, 0.0f, 1.f); const ImVec4 RED_TEXT = ImVec4(1.00f, 0.00f, 0.00f, 1.f); const ImVec2 MENU_HOVERBOX_PADDING = ImVec2(25.f, 10.f); + const int TUNING_SUBJECTID_USE_NAME = -2; enum class TopMenu { TOPMENU_NONE, TOPMENU_SIM, TOPMENU_ACTORS, TOPMENU_SAVEGAMES, TOPMENU_SETTINGS, TOPMENU_TOOLS, TOPMENU_AI, TOPMENU_TUNING }; enum class StateBox { STATEBOX_NONE, STATEBOX_REPLAY, STATEBOX_RACE, STATEBOX_LIVE_REPAIR, STATEBOX_QUICK_REPAIR }; @@ -132,7 +133,7 @@ class TopMenubar // Tuning menu helpers void DrawTuningBoxedSubjectIdInline(int subject_id); - void DrawTuningProtectedChkRightAligned(int subject_id, bool is_protected, ModifyProjectRequestType request_type_set, ModifyProjectRequestType request_type_reset); + void DrawTuningProtectedChkRightAligned(const int subject_id, bool is_protected, ModifyProjectRequestType request_type_set, ModifyProjectRequestType request_type_reset, const std::string& subject = ""); void DrawTuningForceRemoveControls(const int subject_id, const std::string& name, const bool is_unwanted, const bool is_force_removed, ModifyProjectRequestType request_type_set, ModifyProjectRequestType request_type_reset); ImVec2 m_open_menu_hoverbox_min; diff --git a/source/main/physics/ActorSpawner.cpp b/source/main/physics/ActorSpawner.cpp index 8dcdaf9430..f5e886e57a 100644 --- a/source/main/physics/ActorSpawner.cpp +++ b/source/main/physics/ActorSpawner.cpp @@ -2357,6 +2357,12 @@ Ogre::MaterialPtr ActorSpawner::InstantiateManagedMaterial(const Ogre::String& r void ActorSpawner::ProcessManagedMaterial(RigDef::ManagedMaterial & def) { + // This is how textures map between `RigDef::Document` (*.truck etc...) and `RoR::TuneupDef` (*.tuneup): + // def.diffuse_map ~~ tuneup.media[0] + // def.specular_map ~~ tuneup.media[1] + // def.damaged_diffuse_map ~~ tuneup.media[2] + // ========================================================================== + if (m_managed_materials.find(def.name) != m_managed_materials.end()) { this->AddMessage(Message::TYPE_ERROR, "Duplicate managed material name: '" + def.name + "'. Ignoring definition..."); @@ -2400,7 +2406,12 @@ void ActorSpawner::ProcessManagedMaterial(RigDef::ManagedMaterial & def) std::string custom_name = this->ComposeName(def.name); Ogre::MaterialPtr material; - if (def.type == RigDef::ManagedMaterialType::FLEXMESH_STANDARD || def.type == RigDef::ManagedMaterialType::FLEXMESH_TRANSPARENT) + if (TuneupUtil::isManagedMatAnyhowRemoved(m_actor->getWorkingTuneupDef(), def.name)) + { + // Create a placeholder material + material = Ogre::MaterialManager::getSingleton().getByName("tracks/transred")->clone(custom_name, /*changeGroup:*/true, resource_group); + } + else if (def.type == RigDef::ManagedMaterialType::FLEXMESH_STANDARD || def.type == RigDef::ManagedMaterialType::FLEXMESH_TRANSPARENT) { std::string mat_name_base = (def.type == RigDef::ManagedMaterialType::FLEXMESH_STANDARD) @@ -2428,16 +2439,16 @@ void ActorSpawner::ProcessManagedMaterial(RigDef::ManagedMaterial & def) if (App::gfx_alt_actor_materials->getBool()) { - material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map")->setTextureName(def.diffuse_map); - material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Dmg_Diffuse_Map")->setTextureName(def.damaged_diffuse_map); - material->getTechnique("BaseTechnique")->getPass("SpecularMapping1")->getTextureUnitState("SpecularMapping1_Tex")->setTextureName(def.specular_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map"), def.name, 0, def.diffuse_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Dmg_Diffuse_Map"), def.name, 2, def.damaged_diffuse_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("SpecularMapping1")->getTextureUnitState("SpecularMapping1_Tex"), def.name, 1, def.specular_map); } else { - material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map")->setTextureName(def.diffuse_map); - material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Dmg_Diffuse_Map")->setTextureName(def.damaged_diffuse_map); - material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Specular_Map")->setTextureName(def.specular_map); - material->getTechnique("BaseTechnique")->getPass("Specular")->getTextureUnitState("Specular_Map")->setTextureName(def.specular_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map"), def.name, 0, def.diffuse_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Dmg_Diffuse_Map"), def.name, 2, def.damaged_diffuse_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Specular_Map"), def.name, 1, def.specular_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("Specular")->getTextureUnitState("Specular_Map"), def.name, 1, def.specular_map); } } else @@ -2448,8 +2459,8 @@ void ActorSpawner::ProcessManagedMaterial(RigDef::ManagedMaterial & def) { return; } - material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map")->setTextureName(def.diffuse_map); - material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Dmg_Diffuse_Map")->setTextureName(def.damaged_diffuse_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map"), def.name, 0, def.diffuse_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Dmg_Diffuse_Map"), def.name, 2, def.damaged_diffuse_map); } } else @@ -2473,14 +2484,14 @@ void ActorSpawner::ProcessManagedMaterial(RigDef::ManagedMaterial & def) if (App::gfx_alt_actor_materials->getBool()) { - material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map")->setTextureName(def.diffuse_map); - material->getTechnique("BaseTechnique")->getPass("SpecularMapping1")->getTextureUnitState("SpecularMapping1_Tex")->setTextureName(def.specular_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map"), def.name, 0, def.diffuse_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("SpecularMapping1")->getTextureUnitState("SpecularMapping1_Tex"), def.name, 1, def.specular_map); } else { - material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map")->setTextureName(def.diffuse_map); - material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Specular_Map")->setTextureName(def.specular_map); - material->getTechnique("BaseTechnique")->getPass("Specular")->getTextureUnitState("Specular_Map")->setTextureName(def.specular_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map") , def.name, 0, def.diffuse_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Specular_Map"), def.name, 1, def.specular_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("Specular")->getTextureUnitState("Specular_Map") , def.name, 1, def.specular_map); } } else @@ -2491,7 +2502,7 @@ void ActorSpawner::ProcessManagedMaterial(RigDef::ManagedMaterial & def) { return; } - material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map")->setTextureName(def.diffuse_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map"), def.name, 0, def.diffuse_map); } } } @@ -2521,14 +2532,14 @@ void ActorSpawner::ProcessManagedMaterial(RigDef::ManagedMaterial & def) if (App::gfx_alt_actor_materials->getBool()) { - material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map")->setTextureName(def.diffuse_map); - material->getTechnique("BaseTechnique")->getPass("SpecularMapping1")->getTextureUnitState("SpecularMapping1_Tex")->setTextureName(def.specular_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map"), def.name, 0, def.diffuse_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("SpecularMapping1")->getTextureUnitState("SpecularMapping1_Tex"), def.name, 1, def.specular_map); } else { - material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map")->setTextureName(def.diffuse_map); - material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Specular_Map")->setTextureName(def.specular_map); - material->getTechnique("BaseTechnique")->getPass("Specular")->getTextureUnitState("Specular_Map")->setTextureName(def.specular_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map") ,def.name, 0, def.diffuse_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Specular_Map"),def.name, 1, def.specular_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("Specular")->getTextureUnitState("Specular_Map") ,def.name, 1, def.specular_map); } } else @@ -2539,12 +2550,13 @@ void ActorSpawner::ProcessManagedMaterial(RigDef::ManagedMaterial & def) { return; } - material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map")->setTextureName(def.diffuse_map); + this->AssignManagedMaterialTexture(material->getTechnique("BaseTechnique")->getPass("BaseRender")->getTextureUnitState("Diffuse_Map"), def.name, 0, def.diffuse_map); } } - if (def.type != RigDef::ManagedMaterialType::INVALID) + if (!TuneupUtil::isManagedMatAnyhowRemoved(m_actor->getWorkingTuneupDef(), def.name) + && def.type != RigDef::ManagedMaterialType::INVALID) { if (def.options.double_sided) { @@ -2563,8 +2575,6 @@ void ActorSpawner::ProcessManagedMaterial(RigDef::ManagedMaterial & def) } } - /* Finalize */ - material->compile(); m_managed_materials.insert(std::make_pair(def.name, material)); } @@ -6565,8 +6575,16 @@ Ogre::MaterialPtr ActorSpawner::FindOrCreateCustomizedMaterial(const std::string } lookup_entry.material = orig_mat->clone(this->ComposeName(orig_mat->getName()), true, mat_lookup_rg); + /* + 02:53:47: [RoR|Actor|Error] (Keyword: managedmaterials) Ogre::ItemIdentityException::ItemIdentityException: Texture Pointer is empty. in TextureUnitState::setTexture at C:\Users\Petr\.conan2\p\b\ogre3ff3740bbb9785\b\OgreMain\src\OgreTextureUnitState.cpp (line 271) + 02:55:17: [RoR|ContentManager] Skipping resource with duplicate name: 'pushbar1 (gavrilmv4.truck [Instance ID 1])' (origin: '') + */ + ROR_ASSERT(lookup_entry.material); } + // Register the substitute + m_material_substitutions.insert(std::make_pair(mat_lookup_name, lookup_entry)); + // Finally, query texture replacements - .skin and builtins for (auto& technique: lookup_entry.material->getTechniques()) { @@ -6627,7 +6645,7 @@ Ogre::MaterialPtr ActorSpawner::FindOrCreateCustomizedMaterial(const std::string } // passes } // techniques - m_material_substitutions.insert(std::make_pair(mat_lookup_name, lookup_entry)); // Register the substitute + return lookup_entry.material; } catch (Ogre::Exception& e) @@ -7346,3 +7364,28 @@ std::string ActorSpawner::GetCurrentElementMediaRG() return m_actor->getTruckFileResourceGroup(); } } + +void ActorSpawner::AssignManagedMaterialTexture(Ogre::TextureUnitState* tus, const std::string & mm_name, int media_id, const std::string& tex_name) +{ + // Helper for `ProcessManagedMaterial()`, resolves tweaks + // ====================================================== + + try + { + ROR_ASSERT(tus); + if (tus) + { + Ogre::TexturePtr tex = Ogre::TextureManager::getSingleton().load( + TuneupUtil::getTweakedManagedMatMedia(m_actor->getWorkingTuneupDef(), mm_name, media_id, tex_name), + TuneupUtil::getTweakedManagedMatMediaRG(m_actor->getWorkingTuneupDef(), mm_name, media_id, this->GetCurrentElementMediaRG())); + + if (tex) + { + tus->setTexture(tex); + } + } + } + catch (...) // Exception is already logged by OGRE + { + } +} diff --git a/source/main/physics/ActorSpawner.h b/source/main/physics/ActorSpawner.h index f4101977b4..15caf4e5a2 100644 --- a/source/main/physics/ActorSpawner.h +++ b/source/main/physics/ActorSpawner.h @@ -389,6 +389,7 @@ class ActorSpawner void CreateCabVisual(); void CreateMaterialFlare(int flare_index, Ogre::MaterialPtr mat); std::string GetCurrentElementMediaRG(); //!< Where to load media from (the addonpart's bundle or vehicle's bundle?) + void AssignManagedMaterialTexture(Ogre::TextureUnitState* tus, const std::string & mm_name, int media_id, const std::string& tex_name); //!< Helper for `ProcessManagedMaterial()` /// @param rim_ratio Percentual size of the rim. void CreateWheelVisuals( diff --git a/source/main/resources/CacheSystem.cpp b/source/main/resources/CacheSystem.cpp index 74879b215e..6b6d3bd95e 100644 --- a/source/main/resources/CacheSystem.cpp +++ b/source/main/resources/CacheSystem.cpp @@ -1837,6 +1837,16 @@ void CacheSystem::ModifyProject(ModifyProjectRequest* request) request->mpr_target_actor->getWorkingTuneupDef()->force_remove_exhausts.erase(request->mpr_subject_id); break; + case ModifyProjectRequestType::TUNEUP_FORCEREMOVE_MANAGEDMAT_SET: + request->mpr_target_actor->ensureWorkingTuneupDef(); + request->mpr_target_actor->getWorkingTuneupDef()->force_remove_managedmats.insert(request->mpr_subject); + break; + + case ModifyProjectRequestType::TUNEUP_FORCEREMOVE_MANAGEDMAT_RESET: + request->mpr_target_actor->ensureWorkingTuneupDef(); + request->mpr_target_actor->getWorkingTuneupDef()->force_remove_managedmats.erase(request->mpr_subject); + break; + case ModifyProjectRequestType::TUNEUP_PROTECTED_PROP_SET: request->mpr_target_actor->ensureWorkingTuneupDef(); request->mpr_target_actor->getWorkingTuneupDef()->protected_props.insert(request->mpr_subject_id); @@ -1887,6 +1897,16 @@ void CacheSystem::ModifyProject(ModifyProjectRequest* request) request->mpr_target_actor->getWorkingTuneupDef()->protected_exhausts.erase(request->mpr_subject_id); break; + case ModifyProjectRequestType::TUNEUP_PROTECTED_MANAGEDMAT_SET: + request->mpr_target_actor->ensureWorkingTuneupDef(); + request->mpr_target_actor->getWorkingTuneupDef()->protected_managedmats.insert(request->mpr_subject); + break; + + case ModifyProjectRequestType::TUNEUP_PROTECTED_MANAGEDMAT_RESET: + request->mpr_target_actor->ensureWorkingTuneupDef(); + request->mpr_target_actor->getWorkingTuneupDef()->protected_managedmats.erase(request->mpr_subject); + break; + case ModifyProjectRequestType::PROJECT_LOAD_TUNEUP: { // Instead of loading with the saved tuneup directly, keep the autogenerated and sync it with the save. diff --git a/source/main/resources/CacheSystem.h b/source/main/resources/CacheSystem.h index e4209690b3..8ab6e130f8 100644 --- a/source/main/resources/CacheSystem.h +++ b/source/main/resources/CacheSystem.h @@ -241,6 +241,8 @@ enum class ModifyProjectRequestType TUNEUP_FORCEREMOVE_FLARE_RESET, //!< 'subject_id' is flare ID. TUNEUP_FORCEREMOVE_EXHAUST_SET, //!< 'subject_id' is exhaust ID. TUNEUP_FORCEREMOVE_EXHAUST_RESET, //!< 'subject_id' is exhaust ID. + TUNEUP_FORCEREMOVE_MANAGEDMAT_SET, //!< 'subject' is managed material name. + TUNEUP_FORCEREMOVE_MANAGEDMAT_RESET,//!< 'subject' is managed material name. TUNEUP_PROTECTED_PROP_SET, //!< 'subject_id' is prop ID. TUNEUP_PROTECTED_PROP_RESET, //!< 'subject_id' is prop ID. TUNEUP_PROTECTED_FLEXBODY_SET, //!< 'subject_id' is flexbody ID. @@ -251,6 +253,8 @@ enum class ModifyProjectRequestType TUNEUP_PROTECTED_FLARE_RESET, //!< 'subject_id' is flare ID. TUNEUP_PROTECTED_EXHAUST_SET, //!< 'subject_id' is exhaust ID. TUNEUP_PROTECTED_EXHAUST_RESET, //!< 'subject_id' is exhaust ID. + TUNEUP_PROTECTED_MANAGEDMAT_SET, //!< 'subject' is managed material name. + TUNEUP_PROTECTED_MANAGEDMAT_RESET, //!< 'subject' is managed material name. PROJECT_LOAD_TUNEUP, //!< 'subject' is tuneup filename. This overwrites the auto-generated tuneup with the save. PROJECT_RESET_TUNEUP, //!< 'subject' is empty. This resets the auto-generated tuneup to orig. values. }; diff --git a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp index c3193a6a0a..5777a3cf05 100644 --- a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp +++ b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp @@ -157,6 +157,8 @@ void AddonPartUtility::ResolveUnwantedAndTweakedElements(TuneupDefPtr& tuneup, C this->ProcessUnwantedFlare(); else if (m_context->getTokKeyword() == "addonpart_unwanted_exhaust" ) this->ProcessUnwantedExhaust(); + else if (m_context->getTokKeyword() == "addonpart_unwanted_managedmaterial") + this->ProcessUnwantedManagedMat(); else if (m_context->getTokKeyword() == "addonpart_tweak_wheel") this->ProcessTweakWheel(); else if (m_context->getTokKeyword() == "addonpart_tweak_node") @@ -165,6 +167,8 @@ void AddonPartUtility::ResolveUnwantedAndTweakedElements(TuneupDefPtr& tuneup, C this->ProcessTweakProp(); else if (m_context->getTokKeyword() == "addonpart_tweak_flexbody") this->ProcessTweakFlexbody(); + else if (m_context->getTokKeyword() == "addonpart_tweak_managedmaterial") + this->ProcessTweakManagedMat(); } m_context->seekNextLine(); @@ -496,6 +500,31 @@ void AddonPartUtility::ProcessUnwantedExhaust() } } +void AddonPartUtility::ProcessUnwantedManagedMat() +{ + ROR_ASSERT(m_context->getTokKeyword() == "addonpart_unwanted_managedmaterial"); // also asserts !EOF and TokenType::KEYWORD + + if (m_context->isTokString(1)) + { + std::string mat_name = m_context->getTokString(1); + if (!m_tuneup->isManagedMatProtected(mat_name)) + { + m_tuneup->unwanted_managedmats.insert(mat_name); + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': marking managedmaterial '{}' as UNWANTED", + m_addonpart_entry->fname, m_context->getTokKeyword(), mat_name)); + } + else + { + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping managedmaterial '{}' because it's marked PROTECTED", + m_addonpart_entry->fname, m_context->getTokKeyword(), mat_name)); + } + } + else + { + this->Log(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': bad arguments", m_addonpart_entry->fname, m_context->getTokKeyword())); + } +} + void AddonPartUtility::ProcessTweakWheel() { ROR_ASSERT(m_context->getTokKeyword() == "addonpart_tweak_wheel"); // also asserts !EOF and TokenType::KEYWORD @@ -700,6 +729,48 @@ void AddonPartUtility::ProcessTweakProp() } } +void AddonPartUtility::ProcessTweakManagedMat() +{ + ROR_ASSERT(m_context->getTokKeyword() == "addonpart_tweak_managedmaterial"); // also asserts !EOF and TokenType::KEYWORD + + if (m_context->isTokString(1) && m_context->isTokString(2)) + { + const std::string& mat_name = m_context->getTokString(1); + if (!m_tuneup->isManagedMatProtected(mat_name)) + { + if (m_tuneup->managedmat_tweaks.find(mat_name) == m_tuneup->managedmat_tweaks.end()) + { + TuneupManagedMatTweak data; + bool stop=false; + data.tmt_origin = m_addonpart_entry->fname; + data.tmt_name = mat_name; + data.tmt_type = m_context->getTokString(2); + if (!stop && m_context->isTokString(3)) { data.tmt_media[0] = m_context->getTokString(3); } else {stop=true;} + if (!stop && m_context->isTokString(4)) { data.tmt_media[1] = m_context->getTokString(4); } else {stop=true;} + if (!stop && m_context->isTokString(5)) { data.tmt_media[2] = m_context->getTokString(5); } else {stop=true;} + m_tuneup->managedmat_tweaks.insert(std::make_pair(mat_name, data)); + + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': Scheduling tweak for managed material '{}'" + " with params {{ type={}, media1={}, media2={}, media3={} }}", + m_addonpart_entry->fname, m_context->getTokKeyword(), mat_name, data.tmt_type, data.tmt_media[0], data.tmt_media[1], data.tmt_media[2])); + } + else if (m_tuneup->managedmat_tweaks[mat_name].tmt_origin != m_addonpart_entry->fname) + { + this->Log(fmt::format("[RoR|Addonpart] WARNING: file '{}', directive '{}': Resetting tweaks for managed material '{}' due to conflict with '{}'", + m_addonpart_entry->fname, m_context->getTokKeyword(), mat_name, + m_tuneup->managedmat_tweaks[mat_name].tmt_origin)); + + m_tuneup->managedmat_tweaks.erase(mat_name); + } + } + else + { + this->Log(fmt::format("[RoR|Addonpart] INFO: file '{}', directive '{}': skipping managed material '{}' because it's marked PROTECTED", + m_addonpart_entry->fname, m_context->getTokKeyword(), mat_name)); + } + } +} + 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)); diff --git a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h index 58736cd403..86e9b31c69 100644 --- a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h +++ b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.h @@ -74,16 +74,18 @@ class AddonPartUtility void ProcessFlexbody(); void ProcessFlare(); void ProcessFlare2(); + + // Helpers of `ResolveUnwantedAndTweakedElements()`, they expect `m_context` to be in position: void ProcessTweakWheel(); void ProcessTweakNode(); void ProcessTweakFlexbody(); void ProcessTweakProp(); - - // Helpers of `ResolveUnwantedAndTweakedElements()`, they expect `m_context` to be in position: + void ProcessTweakManagedMat(); void ProcessUnwantedProp(); void ProcessUnwantedFlexbody(); void ProcessUnwantedFlare(); void ProcessUnwantedExhaust(); + void ProcessUnwantedManagedMat(); void Log(const std::string& text); diff --git a/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp b/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp index a9bf3d0340..ddf8337a4f 100644 --- a/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp +++ b/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp @@ -459,6 +459,83 @@ bool RoR::TuneupUtil::isExhaustAnyhowRemoved(TuneupDefPtr& tuneup_def, ExhaustID && (tuneup_def->isExhaustUnwanted(exhaust_id) || tuneup_def->isExhaustForceRemoved(exhaust_id)); } +// > managedmaterials +bool RoR::TuneupUtil::isManagedMatAnyhowRemoved(TuneupDefPtr& tuneup_def, const std::string& material_name) +{ + return tuneup_def + && (tuneup_def->isManagedMatUnwanted(material_name) || tuneup_def->isManagedMatForceRemoved(material_name)); +} + +bool RoR::TuneupUtil::isManagedMatTweaked(TuneupDefPtr& tuneup_def, const std::string& material_name, TuneupManagedMatTweak*& out_tweak) +{ + if (tuneup_def) + { + auto itor = tuneup_def->managedmat_tweaks.find(material_name); + if (itor != tuneup_def->managedmat_tweaks.end()) + { + out_tweak = &itor->second; + return true; + } + } + + return false; +} + +std::string RoR::TuneupUtil::getTweakedManagedMatType(TuneupDefPtr& tuneup_def, const std::string& material_name, const std::string& orig_val) +{ + TuneupManagedMatTweak* tweak = nullptr; + if (TuneupUtil::isManagedMatTweaked(tuneup_def, material_name, tweak)) + { + ROR_ASSERT(tweak); + return (tweak->tmt_type != "") ? tweak->tmt_type : orig_val; + } + else + { + return orig_val; + } +} + +std::string RoR::TuneupUtil::getTweakedManagedMatMedia(TuneupDefPtr& tuneup_def, const std::string& material_name, int media_idx, const std::string& orig_val) +{ + TuneupManagedMatTweak* tweak = nullptr; + if (TuneupUtil::isManagedMatTweaked(tuneup_def, material_name, tweak)) + { + ROR_ASSERT(tweak); + ROR_ASSERT(tweak->tmt_media.size() > media_idx); + return (tweak->tmt_media[media_idx] != "") ? tweak->tmt_media[media_idx] : orig_val; + } + else + { + return orig_val; + } +} + +std::string RoR::TuneupUtil::getTweakedManagedMatMediaRG(TuneupDefPtr& tuneup_def, const std::string& material_name, int media_idx, const std::string& orig_val) +{ + TuneupManagedMatTweak* tweak = nullptr; + if (TuneupUtil::isManagedMatTweaked(tuneup_def, material_name, tweak)) + { + ROR_ASSERT(tweak); + ROR_ASSERT(tweak->tmt_media.size() > media_idx); + if (tweak->tmt_media[media_idx] != "") + { + // Find the tweak addonpart + CacheEntryPtr addonpart_entry = App::GetCacheSystem()->FindEntryByFilename(LT_AddonPart, /*partial:*/false, tweak->tmt_origin); + if (addonpart_entry) + { + return addonpart_entry->resource_group; + } + else + { + LOG(fmt::format("[RoR|Tuneup] WARN managedmaterial '{}': Addonpart '{}' not found in modcache!", material_name, tweak->tmt_origin)); + return orig_val; + } + } + } + + return orig_val; +} + bool RoR::TuneupUtil::isAddonPartUsed(TuneupDefPtr& tuneup_entry, const std::string& filename) { return tuneup_entry diff --git a/source/main/resources/tuneup_fileformat/TuneupFileFormat.h b/source/main/resources/tuneup_fileformat/TuneupFileFormat.h index f97150eb38..cfcf4d8a5e 100644 --- a/source/main/resources/tuneup_fileformat/TuneupFileFormat.h +++ b/source/main/resources/tuneup_fileformat/TuneupFileFormat.h @@ -79,6 +79,14 @@ struct TuneupFlexbodyTweak //!< Data of 'addonpart_tweak_flexbody std::string tft_origin; //!< Addonpart filename }; +struct TuneupManagedMatTweak //!< Data of 'addonpart_tweak_managedmaterial []' +{ + std::string tmt_name; //!< Arg#1, required + std::string tmt_type; //!< Arg#2, required + std::array tmt_media; //!< Arg#3, required, Arg#4, optional, Arg#5, optional + std::string tmt_origin; //!< Addonpart filename +}; + /// Dual purpose: /// 1. representing a .tuneup file, see `CacheEntry::tuneup_def` (the obvious use) /// 2. holding addonpart data for conflict resolution, see `CacheEntry::addonpart_data_only` (an additional hack) @@ -103,10 +111,12 @@ struct TuneupDef: public RefCountingObject std::map wheel_tweaks; //!< Mesh name and radius overrides via 'addonpart_tweak_wheel' std::map prop_tweaks; //!< Mesh name(s), offset and rotation overrides via 'addonpart_tweak_prop' std::map flexbody_tweaks; //!< Mesh name, offset and rotation overrides via 'addonpart_tweak_flexbody' + std::map managedmat_tweaks; //!< Managed material overrides via 'addonpart_tweak_managedmaterial' std::set unwanted_props; //!< 'addonpart_unwanted_prop' directives. std::set unwanted_flexbodies; //!< 'addonpart_unwanted_flexbody' directives. std::set unwanted_flares; //!< 'addonpart_unwanted_flare' directives. std::set unwanted_exhausts; //!< 'addonpart_unwanted_exhaust' directives. + std::set unwanted_managedmats; //!< 'addonpart_unwanted_managedmaterial' directives. /// @} /// @name UI-controlled forced changes (override addonparts) @@ -116,6 +126,7 @@ struct TuneupDef: public RefCountingObject std::map force_wheel_sides; //!< UI overrides std::set force_remove_flares; //!< User unticked an UI checkbox in Tuning menu, section Flares. std::set force_remove_exhausts; //!< User unticked an UI checkbox in Tuning menu, section Exhausts. + std::set force_remove_managedmats;//!< User unticked an UI checkbox in Tuning menu, section Managed Materials. /// @} /// @name UI-controlled protection from addonpart tweaks @@ -126,6 +137,7 @@ struct TuneupDef: public RefCountingObject std::set protected_flexbodies; //!< Flexbodies which cannot be altered via 'addonpart_tweak_flexbody' or 'addonpart_unwanted_flexbody' directive. std::set protected_flares; //!< Flares which cannot be altered via 'addonpart_unwanted_flare' directive. std::set protected_exhausts; //!< Exhausts which cannot be altered via 'addonpart_unwanted_exhaust' directive. + std::set protected_managedmats; //!< Managed materials which cannot be altered via 'addonpart_tweak_managedmaterial' directive. /// @} TuneupDefPtr clone(); @@ -139,6 +151,7 @@ struct TuneupDef: public RefCountingObject bool isNodeProtected(NodeNum_t nodenum) const { return protected_nodes.find(nodenum) != protected_nodes.end(); } bool isFlareProtected(FlareID_t flareid) const { return protected_flares.find(flareid) != protected_flares.end(); } bool isExhaustProtected(ExhaustID_t exhaustid) const { return protected_exhausts.find(exhaustid) != protected_exhausts.end(); } + bool isManagedMatProtected(const std::string& matname) const { return protected_managedmats.find(matname) != protected_managedmats.end(); } /// @} /// @name Unwanted-state helpers @@ -147,6 +160,7 @@ struct TuneupDef: public RefCountingObject bool isFlexbodyUnwanted(FlexbodyID_t flexbodyid) { return unwanted_flexbodies.find(flexbodyid) != unwanted_flexbodies.end(); } bool isFlareUnwanted(FlareID_t flareid) { return unwanted_flares.find(flareid) != unwanted_flares.end(); } bool isExhaustUnwanted(ExhaustID_t exhaustid) { return unwanted_exhausts.find(exhaustid) != unwanted_exhausts.end(); } + bool isManagedMatUnwanted(const std::string& matname) { return unwanted_managedmats.find(matname) != unwanted_managedmats.end(); } /// @} /// @name Forced-state helpers @@ -156,6 +170,7 @@ struct TuneupDef: public RefCountingObject bool isWheelSideForced(WheelID_t wheelid, WheelSide& out_val) const; bool isFlareForceRemoved(FlareID_t flareid) { return force_remove_flares.find(flareid) != force_remove_flares.end(); } bool isExhaustForceRemoved(ExhaustID_t exhaustid) { return force_remove_exhausts.find(exhaustid) != force_remove_exhausts.end(); } + bool isManagedMatForceRemoved(const std::string& matname) { return force_remove_managedmats.find(matname) != force_remove_managedmats.end(); } /// @} }; @@ -217,6 +232,15 @@ class TuneupUtil static bool isExhaustAnyhowRemoved(TuneupDefPtr& tuneup_def, ExhaustID_t exhaust_id); /// @} + /// @name Managed material helpers + /// @{ + static bool isManagedMatAnyhowRemoved(TuneupDefPtr& tuneup_def, const std::string& matname); + static std::string getTweakedManagedMatType(TuneupDefPtr& tuneup_def, const std::string& matname, const std::string& orig_val); + static std::string getTweakedManagedMatMedia(TuneupDefPtr& tuneup_def, const std::string& matname, int media_idx, const std::string& orig_val); + static std::string getTweakedManagedMatMediaRG(TuneupDefPtr& tuneup_def, const std::string& matname, int media_idx, const std::string& orig_val); + static bool isManagedMatTweaked(TuneupDefPtr& tuneup_def, const std::string& matname, TuneupManagedMatTweak*& out_tweak); + /// @} + private: static void ParseTuneupAttribute(const std::string& line, TuneupDefPtr& tuneup_def); From ae9d73836a53f29af0be3597987704c38cb0c692 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Mon, 27 May 2024 13:06:07 +0200 Subject: [PATCH 17/19] Tuning: fixed loading flat files in root content-dirs. --- source/main/resources/CacheSystem.cpp | 28 +++++++++++++++++++++++- source/main/resources/CacheSystem.h | 5 ++++- source/main/resources/ContentManager.cpp | 28 ++++++++++-------------- 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/source/main/resources/CacheSystem.cpp b/source/main/resources/CacheSystem.cpp index 6b6d3bd95e..23a249e21a 100644 --- a/source/main/resources/CacheSystem.cpp +++ b/source/main/resources/CacheSystem.cpp @@ -139,6 +139,13 @@ CacheSystem::CacheSystem() m_known_extensions.push_back("addonpart"); m_known_extensions.push_back("tuneup"); m_known_extensions.push_back("assetpack"); + + // register the dirs + m_content_dirs.push_back("mods"); + m_content_dirs.push_back("packs"); + m_content_dirs.push_back("terrains"); + m_content_dirs.push_back("vehicles"); + m_content_dirs.push_back("projects"); } void CacheSystem::LoadModCache(CacheValidity validity) @@ -1359,6 +1366,21 @@ void CacheSystem::LoadSupplementaryDocuments(CacheEntryPtr& entry) } } +bool CacheSystem::IsPathContentDirRoot(const std::string& path) const +{ + // Helper for `LoadResource()` because OGRE's 'readOnly' flag, see explanation in `ContentManager::InitModCache()` + // -------------------------------------------------------------------------------------------------------------- + + for (const std::string& cdir: m_content_dirs) + { + if (path == PathCombine(App::sys_user_dir->getStr(), cdir)) + { + return true; + } + } + return false; +} + void CacheSystem::LoadResource(CacheEntryPtr& entry) { if (!entry) @@ -1372,7 +1394,10 @@ void CacheSystem::LoadResource(CacheEntryPtr& entry) } Ogre::String group = CacheSystem::ComposeResourceGroupName(entry); - bool readonly = entry->resource_bundle_type == "Zip"; // Make "FileSystem" (directory) bundles writable. Default is read-only. + + // Make "FileSystem" (directory) bundles writable (Default is read-only), except if it's a root directory. + // See explanation of `readOnly` OGRE flag in `ContentManager::InitModCache()`. + bool readonly = entry->resource_bundle_type == "Zip" || this->IsPathContentDirRoot(entry->resource_bundle_path); bool recursive = false; // Load now. @@ -2160,3 +2185,4 @@ bool CacheQueryResult::operator<(CacheQueryResult const& other) const return cqr_score < other.cqr_score; } + diff --git a/source/main/resources/CacheSystem.h b/source/main/resources/CacheSystem.h index 8ab6e130f8..b64963cb12 100644 --- a/source/main/resources/CacheSystem.h +++ b/source/main/resources/CacheSystem.h @@ -328,7 +328,7 @@ class CacheSystem Ogre::String GetPrettyName(Ogre::String fname); std::string ActorTypeToName(ActorType driveable); - + const std::vector& GetContentDirs() const { return m_content_dirs; } private: @@ -378,12 +378,15 @@ class CacheSystem bool Match(size_t& out_score, std::string data, std::string const& query, size_t ); + bool IsPathContentDirRoot(const std::string& path) const; + bool m_loaded = false; std::time_t m_update_time; //!< Ensures that all inserted files share the same timestamp std::string m_filenames_hash_loaded; //!< hash from cachefile, for quick update detection std::string m_filenames_hash_generated; //!< stores hash over the content, for quick update detection std::vector m_entries; std::vector m_known_extensions; //!< the extensions we track in the cache system + std::vector m_content_dirs; //!< the various mod directories we track in the cache system std::set m_resource_paths; //!< A temporary list of existing resource paths std::map m_categories = { // these are the category numbers from the repository. do not modify them! diff --git a/source/main/resources/ContentManager.cpp b/source/main/resources/ContentManager.cpp index 98c04196d9..b47bc259e1 100644 --- a/source/main/resources/ContentManager.cpp +++ b/source/main/resources/ContentManager.cpp @@ -260,9 +260,6 @@ void ContentManager::InitModCache(CacheValidity validity) App::sys_cache_dir->getStr(), "FileSystem", RGN_CACHE, /*recursive=*/false, /*readOnly=*/false); ResourceGroupManager::getSingleton().addResourceLocation( App::sys_thumbnails_dir->getStr(), "FileSystem", RGN_REPO, /*recursive=*/false, /*readOnly=*/false); - std::string user = App::sys_user_dir->getStr(); - std::string base = App::sys_process_dir->getStr(); - std::string objects = PathCombine("resources", "beamobjects.zip"); // Add top-level ZIPs/directories to RGN_CONTENT (non-recursive) @@ -271,13 +268,13 @@ void ContentManager::InitModCache(CacheValidity validity) std::string extra_mod_path = App::app_extra_mod_path->getStr(); ResourceGroupManager::getSingleton().addResourceLocation(extra_mod_path , "FileSystem", RGN_CONTENT); } - ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(user, "mods") , "FileSystem", RGN_CONTENT); - ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(user, "packs") , "FileSystem", RGN_CONTENT); - ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(user, "terrains"), "FileSystem", RGN_CONTENT); - ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(user, "vehicles"), "FileSystem", RGN_CONTENT); - ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(user, "projects"), "FileSystem", RGN_CONTENT); - ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(base, "content") , "FileSystem", RGN_CONTENT); - ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(base, objects) , "Zip" , RGN_CONTENT); + for (const std::string& dirname : App::GetCacheSystem()->GetContentDirs()) + { + ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(App::sys_user_dir->getStr(), dirname), "FileSystem", RGN_CONTENT); + } + ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(App::sys_process_dir->getStr(), "content") , "FileSystem", RGN_CONTENT); + std::string objects = PathCombine("resources", "beamobjects.zip"); + ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(App::sys_process_dir->getStr(), objects) , "Zip" , RGN_CONTENT); // Create RGN_TEMP in recursive mode to find all subdirectories. @@ -287,12 +284,11 @@ void ContentManager::InitModCache(CacheValidity validity) std::string extra_mod_path = App::app_extra_mod_path->getStr(); ResourceGroupManager::getSingleton().addResourceLocation(extra_mod_path , "FileSystem", RGN_TEMP, true); } - ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(user, "mods") , "FileSystem", RGN_TEMP, true); - ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(user, "packs") , "FileSystem", RGN_TEMP, true); - ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(user, "terrains"), "FileSystem", RGN_TEMP, true); - ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(user, "vehicles"), "FileSystem", RGN_TEMP, true); - ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(user, "projects"), "FileSystem", RGN_TEMP, true); - ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(base, "content") , "FileSystem", RGN_TEMP, true); + for (const std::string& dirname : App::GetCacheSystem()->GetContentDirs()) + { + ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(App::sys_user_dir->getStr(), dirname), "FileSystem", RGN_TEMP, true); + } + ResourceGroupManager::getSingleton().addResourceLocation(PathCombine(App::sys_process_dir->getStr(), "content") , "FileSystem", RGN_TEMP, true); // Traverse RGN_TEMP and add all subdirectories to RGN_CONTENT. // (TBD: why not just make RGN_CONTENT itself recursive? -- ohlidalp, 10/2023) From 14cffb5e0729cbae33d1399122c0740b27f9e38c Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Sun, 26 May 2024 19:56:41 +0200 Subject: [PATCH 18/19] Tuning: added matching tuneups by filename (on top of GUID) There is a new field in the .tuneup format, 'filename'. It should really be 'associated_filename', but this would be confusing with 'guid' (which should really be 'associated_guid') and changing that would be confusing with .skin format which also uses 'guid' and cannot be changed because #compatibility. C'est la vie. --- source/main/gui/panels/GUI_TopMenubar.cpp | 1 + source/main/resources/CacheSystem.cpp | 20 ++++++++++++++++++- source/main/resources/CacheSystem.h | 7 +++++-- .../tuneup_fileformat/TuneupFileFormat.cpp | 3 +++ .../tuneup_fileformat/TuneupFileFormat.h | 3 ++- 5 files changed, 30 insertions(+), 4 deletions(-) diff --git a/source/main/gui/panels/GUI_TopMenubar.cpp b/source/main/gui/panels/GUI_TopMenubar.cpp index 04c5fbee90..0aeaa65036 100644 --- a/source/main/gui/panels/GUI_TopMenubar.cpp +++ b/source/main/gui/panels/GUI_TopMenubar.cpp @@ -2496,6 +2496,7 @@ void TopMenubar::RefreshTuningMenu() tuning_saves.cqy_filter_type = LT_Tuneup; tuning_saves.cqy_filter_guid = tuning_actor->getUsedActorEntry()->guid; + tuning_saves.cqy_filter_target_filename = tuning_actor->getTruckFileName(); tuning_saves.cqy_filter_category_id = CID_Tuneups; // Exclude auto-generated entries tuning_saves.resetResults(); App::GetCacheSystem()->Query(tuning_saves); diff --git a/source/main/resources/CacheSystem.cpp b/source/main/resources/CacheSystem.cpp index 23a249e21a..1bc837817f 100644 --- a/source/main/resources/CacheSystem.cpp +++ b/source/main/resources/CacheSystem.cpp @@ -347,6 +347,9 @@ void CacheSystem::ImportEntryFromJson(rapidjson::Value& j_entry, CacheEntryPtr & { out_entry->addonpart_filenames.insert(j_addonfname.GetString()); } + + // Tuneup details + out_entry->tuneup_associated_filename = j_entry["tuneup_associated_filename"].GetString(); } CacheValidity CacheSystem::LoadCacheFileJson() @@ -629,6 +632,9 @@ void CacheSystem::ExportEntryToJson(rapidjson::Value& j_entries, rapidjson::Docu } j_entry.AddMember("addonpart_filenames", j_addonfnames, j_doc.GetAllocator()); + // Tuneup details + j_entry.AddMember("tuneup_associated_filename", rapidjson::StringRef(entry->tuneup_associated_filename.c_str()), j_doc.GetAllocator()); + // Add entry to list j_entries.PushBack(j_entry, j_doc.GetAllocator()); } @@ -1248,8 +1254,10 @@ void CacheSystem::FillTuneupDetailInfo(CacheEntryPtr &entry, TuneupDefPtr& tuneu entry->description = tuneup_def->description; entry->categoryid = tuneup_def->category_id; entry->tuneup_def = tuneup_def; // Needed to generate preview image + entry->tuneup_associated_filename = tuneup_def->filename; Ogre::StringUtil::toLowerCase(entry->guid); + Ogre::StringUtil::toLowerCase(entry->tuneup_associated_filename); } void CacheSystem::LoadAssetPack(CacheEntryPtr& target_entry, Ogre::String const & assetpack_filename) @@ -1667,6 +1675,9 @@ CacheEntryPtr CacheSystem::CreateProject(CreateProjectRequest* request) project_entry->fext = "tuneup"; // Tell modcache what it is. project_entry->categoryid = CID_Tuneups; // For display in modcache project_entry->guid = request->cpr_source_entry->guid; // For lookup of tuneups by vehicle GUID. + Ogre::StringUtil::toLowerCase(project_entry->guid); + project_entry->tuneup_associated_filename = request->cpr_source_entry->fname; // For additional filtering of results (GUID marks a family, not individual mod). + Ogre::StringUtil::toLowerCase(project_entry->tuneup_associated_filename); } else { @@ -1693,6 +1704,7 @@ CacheEntryPtr CacheSystem::CreateProject(CreateProjectRequest* request) TuneupDefPtr tuneup = request->cpr_source_actor->getWorkingTuneupDef()->clone(); tuneup->guid = request->cpr_source_entry->guid; // For lookup of tuneups by vehicle GUID. + tuneup->filename = request->cpr_source_entry->fname; // For additional filtering of results (GUID marks a family, not individual mod). tuneup->name = request->cpr_name; tuneup->description = request->cpr_description; tuneup->thumbnail = request->cpr_source_entry->filecachename; @@ -2044,7 +2056,7 @@ size_t CacheSystem::Query(CacheQuery& query) } } - // Filter by target filename (currently only `addonpart_filenames`); pass items which have no target filenames listed. + // Filter by target filename; pass items which have no target filenames listed. if (query.cqy_filter_target_filename != "") { if (entry->fext == "addonpart" @@ -2053,6 +2065,12 @@ size_t CacheSystem::Query(CacheQuery& query) { continue; } + else if (entry->fext == "tuneup" + && entry->tuneup_associated_filename != "" + && entry->tuneup_associated_filename != query.cqy_filter_target_filename) + { + continue; + } } // Filter by entry type diff --git a/source/main/resources/CacheSystem.h b/source/main/resources/CacheSystem.h index b64963cb12..23e222d320 100644 --- a/source/main/resources/CacheSystem.h +++ b/source/main/resources/CacheSystem.h @@ -39,7 +39,7 @@ #include #define CACHE_FILE "mods.cache" -#define CACHE_FILE_FORMAT 3151 // TBD bump to 14 when merging pull request #3151 +#define CACHE_FILE_FORMAT 14 #define CACHE_FILE_FRESHNESS 86400 // 60*60*24 = one day namespace RoR { @@ -74,7 +74,7 @@ class CacheEntry: public RefCountingObject std::time_t addtimestamp; //!< timestamp when this file was added to the cache Ogre::String uniqueid; //!< file's unique id - Ogre::String guid; //!< global unique id. Type "addonpart" leaves this empty and uses `addonpart_guids`. + Ogre::String guid; //!< global unique id; Type "addonpart" leaves this empty and uses `addonpart_guids`; Always lowercase. int version; //!< file's version std::string resource_bundle_type; //!< Archive type recognized by OGRE resource system: 'FileSystem' or 'Zip' @@ -98,6 +98,9 @@ class CacheEntry: public RefCountingObject std::set addonpart_guids; //!< GUIDs of all vehicles this addonpart is used with. std::set addonpart_filenames; //!< File names of all vehicles this addonpart is used with. If empty, any filename goes. + // following all TUNEUP detail information: + std::string tuneup_associated_filename; //!< Value of 'filename' field in the tuneup file; always lowercase. + // following all TRUCK detail information: Ogre::String description; Ogre::String tags; diff --git a/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp b/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp index ddf8337a4f..525257ce8c 100644 --- a/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp +++ b/source/main/resources/tuneup_fileformat/TuneupFileFormat.cpp @@ -47,6 +47,7 @@ TuneupDefPtr TuneupDef::clone() ret->author_name = this->author_name ; //std::string ret->author_id = this->author_id ; //int ret->category_id = this->category_id ; //CacheCategoryId + ret->filename = this->filename ; //std::string // addonparts ret->use_addonparts = this->use_addonparts ; @@ -619,6 +620,7 @@ void RoR::TuneupUtil::ParseTuneupAttribute(const std::string& line, TuneupDefPtr if (attrib == "category_id" && params.size() == 2) { tuneup_def->category_id = (CacheCategoryId)PARSEINT(params[1]); return; } if (attrib == "guid" && params.size() >= 2) { tuneup_def->guid = params[1]; Ogre::StringUtil::trim(tuneup_def->guid); Ogre::StringUtil::toLowerCase(tuneup_def->guid); return; } if (attrib == "name" && params.size() >= 2) { tuneup_def->name = params[1]; Ogre::StringUtil::trim(tuneup_def->name); return; } + if (attrib == "filename" && params.size() >= 2) { tuneup_def->filename = params[1]; Ogre::StringUtil::trim(tuneup_def->filename); return; } // Addonparts and extracted data if (attrib == "use_addonpart" && params.size() == 2) { tuneup_def->use_addonparts.insert(params[1]); return; } @@ -646,6 +648,7 @@ void RoR::TuneupUtil::ExportTuneup(Ogre::DataStreamPtr& stream, TuneupDefPtr& tu buf << "\tauthor_id = " << tuneup->author_id << "\n"; buf << "\tcategory_id = " << (int)tuneup->category_id << "\n"; buf << "\tguid = " << tuneup->guid << "\n"; + buf << "\tfilename = " << tuneup->filename << "\n"; buf << "\n"; // Addonparts and extracted data: diff --git a/source/main/resources/tuneup_fileformat/TuneupFileFormat.h b/source/main/resources/tuneup_fileformat/TuneupFileFormat.h index cfcf4d8a5e..e0e027950e 100644 --- a/source/main/resources/tuneup_fileformat/TuneupFileFormat.h +++ b/source/main/resources/tuneup_fileformat/TuneupFileFormat.h @@ -95,7 +95,8 @@ struct TuneupDef: public RefCountingObject /// @name General info /// @{ std::string name; - std::string guid; + std::string guid; //!< target vehicle GUID + std::string filename; //!< target vehicle filename std::string thumbnail; std::string description; std::string author_name; From fda6b28b8701c6eb2f5f4dfbcafac6bc7137e929 Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Tue, 28 May 2024 08:30:29 +0200 Subject: [PATCH 19/19] Tuning: Fixed false positives in addonpart conflict check. Cause: bad loop code in AddonPartFileFormat.cpp, function `RecordAddonpartConflicts()`, which inserted garbage into `_tweaks` arrays. For example if the addonpart only tweaked prop 6, there should be just the item 6 in the set, but it ended up being garbage items 0-5 and valid 6. Why? This is a backwards compatibility quirk of C++: when you ask a `std::set<>` for non-existent element via `[]` brackets, it inserts a dummy element and gives it to you. And because these loops went "from 0 to number of elements", gradually, as repeated conflict resolutions happened, garbage built up. Fixed by looping using iterators which only retrieve actually existing elements. --- source/main/gui/panels/GUI_TopMenubar.cpp | 5 ++++- .../addonpart_fileformat/AddonPartFileFormat.cpp | 16 ++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/source/main/gui/panels/GUI_TopMenubar.cpp b/source/main/gui/panels/GUI_TopMenubar.cpp index 0aeaa65036..f99c4a665c 100644 --- a/source/main/gui/panels/GUI_TopMenubar.cpp +++ b/source/main/gui/panels/GUI_TopMenubar.cpp @@ -2507,7 +2507,10 @@ void TopMenubar::RefreshTuningMenu() { for (size_t i2 = i1; i2 < tuning_addonparts.size(); i2++) { - AddonPartUtility::RecordAddonpartConflicts(tuning_addonparts[i1], tuning_addonparts[i2], tuning_conflicts); + if (i1 != i2) + { + AddonPartUtility::RecordAddonpartConflicts(tuning_addonparts[i1], tuning_addonparts[i2], tuning_conflicts); + } } } diff --git a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp index 5777a3cf05..ff21e48af8 100644 --- a/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp +++ b/source/main/resources/addonpart_fileformat/AddonPartFileFormat.cpp @@ -793,9 +793,9 @@ void AddonPartUtility::RecordAddonpartConflicts(CacheEntryPtr addonpart1, CacheE } // NODE TWEAKS: - for (size_t i = 0; i < addonpart1->addonpart_data_only->node_tweaks.size(); i++) + for (auto& i_pair: addonpart1->addonpart_data_only->node_tweaks) { - NodeNum_t suspect = addonpart1->addonpart_data_only->node_tweaks[i].tnt_nodenum; + NodeNum_t suspect = i_pair.second.tnt_nodenum; TuneupNodeTweak* offender = nullptr; if (TuneupUtil::isNodeTweaked(addonpart2->addonpart_data_only, suspect, offender)) { @@ -805,9 +805,9 @@ void AddonPartUtility::RecordAddonpartConflicts(CacheEntryPtr addonpart1, CacheE } // WHEEL TWEAKS: - for (size_t i = 0; i < addonpart1->addonpart_data_only->wheel_tweaks.size(); i++) + for (auto& i_pair: addonpart1->addonpart_data_only->wheel_tweaks) { - WheelID_t suspect = addonpart1->addonpart_data_only->wheel_tweaks[i].twt_wheel_id; + WheelID_t suspect = i_pair.second.twt_wheel_id; TuneupWheelTweak* offender = nullptr; if (TuneupUtil::isWheelTweaked(addonpart2->addonpart_data_only, suspect, offender)) { @@ -817,9 +817,9 @@ void AddonPartUtility::RecordAddonpartConflicts(CacheEntryPtr addonpart1, CacheE } // PROP TWEAKS: - for (size_t i = 0; i < addonpart1->addonpart_data_only->prop_tweaks.size(); i++) + for (auto& i_pair:addonpart1->addonpart_data_only->prop_tweaks) { - PropID_t suspect = addonpart1->addonpart_data_only->prop_tweaks[i].tpt_prop_id; + PropID_t suspect = i_pair.second.tpt_prop_id; TuneupPropTweak* offender = nullptr; if (TuneupUtil::isPropTweaked(addonpart2->addonpart_data_only, suspect, offender)) { @@ -829,9 +829,9 @@ void AddonPartUtility::RecordAddonpartConflicts(CacheEntryPtr addonpart1, CacheE } // FLEXBODY TWEAKS: - for (size_t i = 0; i < addonpart1->addonpart_data_only->flexbody_tweaks.size(); i++) + for (auto& i_pair: addonpart1->addonpart_data_only->flexbody_tweaks) { - FlexbodyID_t suspect = addonpart1->addonpart_data_only->flexbody_tweaks[i].tft_flexbody_id; + FlexbodyID_t suspect = i_pair.second.tft_flexbody_id; TuneupFlexbodyTweak* offender = nullptr; if (TuneupUtil::isFlexbodyTweaked(addonpart2->addonpart_data_only, suspect, offender)) {