Skip to content

Commit

Permalink
✉️ MSG_EDI_MODIFY_PROJECT_REQUESTED and UI.
Browse files Browse the repository at this point in the history
This EDI message now hosts all tuneup edits. See enum `ModifyProjectRequestType` in CacheSystem.h.
The ActorModifyRequest types INSTALL/REMOVE_ADDONPART_AND_RELOAD were replaced by ModifyProjectRequest types TUNEUP_USE_ADDONPART_SET/RESET.
The TopMenubar/Tuning UI now has list of props with checkboxes to toggle them. The toggle immediatelly modifies the tuneup and respawns the actor.
All tuneup edit types are handled by `CacheSystem::ModifyProject()`. The code was moved from `GameContext::ModifyActor()`;
  • Loading branch information
ohlidalp committed Oct 29, 2023
1 parent 3df3813 commit a3f6db5
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 97 deletions.
1 change: 1 addition & 0 deletions source/main/Application.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ enum MsgType
MSG_EDI_RELOAD_BUNDLE_REQUESTED, //!< Payload = RoR::CacheEntryPtr* (owner)
MSG_EDI_UNLOAD_BUNDLE_REQUESTED, //!< Payload = RoR::CacheEntryPtr* (owner)
MSG_EDI_CREATE_PROJECT_REQUESTED, //!< Payload = RoR::CreateProjectRequest* (owner)
MSG_EDI_MODIFY_PROJECT_REQUESTED, //!< Payload = RoR::UpdateProjectRequest* (owner)
};

const char* MsgTypeToString(MsgType type);
Expand Down
60 changes: 5 additions & 55 deletions source/main/GameContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,56 +397,6 @@ void GameContext::ModifyActor(ActorModifyRequest& rq)
// Load our actor again, but only after all actors are deleted.
this->ChainMessage(Message(MSG_SIM_SPAWN_ACTOR_REQUESTED, (void*)srq));
}
else if (rq.amr_type == ActorModifyRequest::Type::INSTALL_ADDONPART_AND_RELOAD)
{
CacheEntryPtr entry = App::GetCacheSystem()->FindEntryByFilename(LT_AllBeam, /*partial=*/false, actor->ar_filename);
if (!entry)
{
Str<500> msg; msg <<"Cannot reload vehicle; file '" << actor->ar_filename << "' not found in ModCache.";
App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_ERROR, msg.ToCStr());
return;
}

// Make sure the actor has a default .tuneup project assigned. If not, create it.
CacheEntryPtr tuneup_entry = actor->getUsedTuneup();
if (!tuneup_entry)
{
CreateProjectRequest req;
req.cpr_create_tuneup = true;
req.cpr_source_entry = entry;
req.cpr_name = fmt::format("Tuned {}", actor->getTruckName());

tuneup_entry = App::GetCacheSystem()->CreateProject(&req);
}

// Add the requested addonpart to the TuneupDef document.
tuneup_entry->tuneup_def->use_addonparts.push_back(rq.amr_addonpart->fname);

// If this is the auto-generated tuneup, immediatelly update the .tuneup file (user-saved tuneups are only modified on demand).
if (tuneup_entry->categoryid == CID_TuneupsAuto)
{
Ogre::DataStreamPtr datastream = Ogre::ResourceGroupManager::getSingleton().openResource(tuneup_entry->fname, tuneup_entry->resource_group);
RoR::TuneupParser::ExportTuneup(datastream, tuneup_entry->tuneup_def);
}

// Create spawn request while actor still exists
// Note we don't use `ActorModifyRequest::Type::RELOAD` because we don't need the bundle reloaded.
ActorSpawnRequest* srq = new ActorSpawnRequest;
srq->asr_position = Ogre::Vector3(actor->getPosition().x, actor->getMinHeight(), actor->getPosition().z);
srq->asr_rotation = Ogre::Quaternion(Ogre::Degree(270) - Ogre::Radian(actor->getRotation()), Ogre::Vector3::UNIT_Y);
srq->asr_config = actor->getSectionConfig();
srq->asr_skin_entry = actor->getUsedSkin();
srq->asr_tuneup_entry = tuneup_entry;
srq->asr_cache_entry = entry;
srq->asr_debugview = (int)actor->GetGfxActor()->GetDebugView();
srq->asr_origin = ActorSpawnRequest::Origin::USER;

// Remove the actor
this->PushMessage(Message(MSG_SIM_DELETE_ACTOR_REQUESTED, (void*)new ActorPtr(actor)));

// Load our actor again, but only after it was deleted.
this->ChainMessage(Message(MSG_SIM_SPAWN_ACTOR_REQUESTED, (void*)srq));
}
}

void GameContext::DeleteActor(ActorPtr actor)
Expand Down Expand Up @@ -735,11 +685,11 @@ void GameContext::OnLoaderGuiApply(LoaderType type, CacheEntryPtr entry, std::st
case LT_AddonPart:
if (m_player_actor)
{
ActorModifyRequest* req = new ActorModifyRequest();
req->amr_actor = m_player_actor->ar_instance_id;
req->amr_addonpart = entry;
req->amr_type = ActorModifyRequest::Type::INSTALL_ADDONPART_AND_RELOAD;
this->PushMessage(Message(MSG_SIM_MODIFY_ACTOR_REQUESTED, req));
ModifyProjectRequest* req = new ModifyProjectRequest();
req->mpr_type = ModifyProjectRequestType::TUNEUP_USE_ADDONPART_SET;
req->mpr_subject = entry->fname;
req->mpr_target_actor = m_player_actor;
App::GetGameContext()->PushMessage(Message(MSG_EDI_MODIFY_PROJECT_REQUESTED, req));
}
break;

Expand Down
93 changes: 63 additions & 30 deletions source/main/gui/panels/GUI_TopMenubar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1458,59 +1458,92 @@ void TopMenubar::Update()
else
{
ImGui::TextDisabled(_LC("TopMenubar", "Used parts:"));
std::string remove_addonpart;
for (const std::string& addonpart: tuneup_entry->tuneup_def->use_addonparts)
{
ImGui::PushID(addonpart.c_str());

ImGui::PushStyleColor(ImGuiCol_Text, RED_TEXT);
if (ImGui::Button(" X "))
{
remove_addonpart = addonpart;
ModifyProjectRequest* req = new ModifyProjectRequest();
req->mpr_type = ModifyProjectRequestType::TUNEUP_USE_ADDONPART_RESET;
req->mpr_subject = addonpart;
req->mpr_target_actor = actor;
App::GetGameContext()->PushMessage(Message(MSG_EDI_MODIFY_PROJECT_REQUESTED, req));
}
ImGui::PopStyleColor();
ImGui::SameLine();
ImGui::Text("%s", addonpart.c_str());

ImGui::PopID(); // addonpart.c_str()
}
}

if (remove_addonpart != "")
if (ImGui::Button(_LC("TopMenubar", "Select parts")))
{
CacheEntryPtr actor_entry = App::GetCacheSystem()->FindEntryByFilename(LT_AllBeam, /*partial:*/false, actor->getTruckFileName());
if (actor_entry && !actor_entry->deleted)
{
ActorModifyRequest* req = new ActorModifyRequest();
req->amr_actor = actor;
req->amr_type = ActorModifyRequest::Type::REMOVE_ADDONPART_AND_RELOAD;
req->amr_addonpart_fname = remove_addonpart;
req->amr_addonpart = App::GetCacheSystem()->FindEntryByFilename(LT_AddonPart, /*partial:*/false, remove_addonpart);
if (req->amr_addonpart)
{
App::GetGameContext()->PushMessage(Message(MSG_SIM_MODIFY_ACTOR_REQUESTED, req));
}
else
{
App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_WARNING,
fmt::format(_LC("TopMenubar", "Addon part '{}' not found (likely uninstalled). Removing from the part list.")));
}
Message m(MSG_GUI_OPEN_SELECTOR_REQUESTED);
m.payload = new LoaderType(LT_AddonPart);
m.description = actor_entry->guid;
App::GetGameContext()->PushMessage(m);
}
else
{
App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_ERROR,
fmt::format(_LC("TopMenubar", "Cannot add parts to '{}' - No valid mod cache entry"), actor->getTruckName()));
}
}
}

ImGui::Separator();
ImGui::Separator();

if (ImGui::Button(_LC("TopMenubar", "Select parts")))
{
CacheEntryPtr actor_entry = App::GetCacheSystem()->FindEntryByFilename(LT_AllBeam, /*partial:*/false, actor->getTruckFileName());
if (actor_entry && !actor_entry->deleted)
ImGui::TextDisabled(_LC("TopMenubar", "Default props:"));
// First draw the removed props
for (const std::string& meshname: tuneup_entry->tuneup_def->remove_props)
{
Message m(MSG_GUI_OPEN_SELECTOR_REQUESTED);
m.payload = new LoaderType(LT_AddonPart);
m.description = actor_entry->guid;
App::GetGameContext()->PushMessage(m);
ImGui::PushID(meshname.c_str());

bool propEnabled = false;
if (ImGui::Checkbox(meshname.c_str(), &propEnabled))
{
ModifyProjectRequest* req = new ModifyProjectRequest();
req->mpr_type = ModifyProjectRequestType::TUNEUP_REMOVE_PROP_RESET;
req->mpr_subject = meshname;
req->mpr_target_actor = actor;
App::GetGameContext()->PushMessage(Message(MSG_EDI_MODIFY_PROJECT_REQUESTED, req));
}

ImGui::PopID(); // meshname
}
else
// Then draw existing props (skip aero navlights and dashboard frankenprop)
for (Prop const& p: actor->GetGfxActor()->getProps())
{
App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_INFO, Console::CONSOLE_SYSTEM_ERROR,
fmt::format(_LC("TopMenubar", "Cannot add parts to '{}' - No valid mod cache entry"), actor->getTruckName()));
if (p.pp_beacon_type == 'L' || p.pp_beacon_type == 'R' || p.pp_beacon_type == 'w')
{
continue; // skip special prop - aerial nav light
}
else if (p.pp_wheel_mesh_obj)
{
continue; // skip special prop: dashboard + dirwheel
}
else if (p.pp_mesh_obj->getLoadedMesh()) // Skip broken props
{
std::string meshname = p.pp_mesh_obj->getLoadedMesh()->getName();
ImGui::PushID(meshname.c_str());

bool propEnabled = true;
if (ImGui::Checkbox(meshname.c_str(), &propEnabled))
{
ModifyProjectRequest* req = new ModifyProjectRequest();
req->mpr_type = ModifyProjectRequestType::TUNEUP_REMOVE_PROP_SET;
req->mpr_subject = meshname;
req->mpr_target_actor = actor;
App::GetGameContext()->PushMessage(Message(MSG_EDI_MODIFY_PROJECT_REQUESTED, req));
}

ImGui::PopID(); // meshname
}
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions source/main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,14 @@ int main(int argc, char *argv[])
break;
}

case MSG_EDI_MODIFY_PROJECT_REQUESTED:
{
ModifyProjectRequest* request = static_cast<ModifyProjectRequest*>(m.payload);
App::GetCacheSystem()->ModifyProject(request);
delete request;
break;
}

default:;
}

Expand Down
5 changes: 3 additions & 2 deletions source/main/physics/ActorSpawner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#include "GfxScene.h"
#include "Console.h"
#include "InputEngine.h"
#include "Language.h"
#include "MeshObject.h"
#include "PointColDetector.h"
#include "Renderdash.h"
Expand Down Expand Up @@ -129,12 +130,12 @@ void ActorSpawner::ConfigureAddonParts(CacheEntryPtr& tuneup_entry)
}
else
{
this->AddMessage(Message::TYPE_WARNING, fmt::format(_L("Could not load addon part '{}' (file '{}')", addonpart_entry->dname, addonpart_entry->fname)));
this->AddMessage(Message::TYPE_WARNING, fmt::format(_L("Could not load addon part '{}' (file '{}')"), addonpart_entry->dname, addonpart_entry->fname));
}
}
else
{
this->AddMessage(Message::TYPE_WARNING, fmt::format(_L("Requested addon part '{}' is not installed", addonpart_entry->dname, addonpart_entry->fname)));
this->AddMessage(Message::TYPE_WARNING, fmt::format(_L("Requested addon part '{}' is not installed"), addonpart_entry->dname, addonpart_entry->fname));
}
}
}
Expand Down
2 changes: 0 additions & 2 deletions source/main/physics/SimData.h
Original file line number Diff line number Diff line change
Expand Up @@ -837,8 +837,6 @@ struct ActorModifyRequest
SOFT_RESET,
RESTORE_SAVED,
WAKE_UP,
INSTALL_ADDONPART_AND_RELOAD,
REMOVE_ADDONPART_AND_RELOAD,
};

ActorInstanceID_t amr_actor = ACTORINSTANCEID_INVALID;// not ActorPtr because it's not thread-safe
Expand Down
82 changes: 81 additions & 1 deletion source/main/resources/CacheSystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

#include "CacheSystem.h"

#include <OgreException.h>
#include "Actor.h"
#include "Application.h"
#include "SimData.h"
#include "ContentManager.h"
Expand All @@ -46,6 +46,7 @@
#include "TuneupFileFormat.h"
#include "Utils.h"

#include <OgreException.h>
#include <OgreFileSystem.h>
#include <OgreFileSystemLayer.h>
#include <rapidjson/document.h>
Expand Down Expand Up @@ -1593,6 +1594,85 @@ CacheEntryPtr CacheSystem::CreateProject(CreateProjectRequest* request)
}
}

void CacheSystem::ModifyProject(ModifyProjectRequest* request)
{
CacheEntryPtr actor_entry = this->FindEntryByFilename(LT_AllBeam, /*partial=*/false, request->mpr_target_actor->getTruckFileName());
if (!actor_entry)
{
Str<500> msg; msg <<"Cannot modify project; actor '" << request->mpr_subject << "' not found in ModCache.";
App::GetConsole()->putMessage(Console::CONSOLE_MSGTYPE_ACTOR, Console::CONSOLE_SYSTEM_ERROR, msg.ToCStr());
return;
}

// Make sure the actor has a default .tuneup project assigned. If not, create it.
CacheEntryPtr tuneup_entry = request->mpr_target_actor->getUsedTuneup();
if (!tuneup_entry)
{
CreateProjectRequest req;
req.cpr_create_tuneup = true;
req.cpr_source_entry = actor_entry;
req.cpr_name = fmt::format("Tuned {}", request->mpr_target_actor->getTruckName());

tuneup_entry = App::GetCacheSystem()->CreateProject(&req); // Do it synchronously
}

switch (request->mpr_type)
{
case ModifyProjectRequestType::TUNEUP_USE_ADDONPART_SET:
// Add the addonpart to the TuneupDef document.
tuneup_entry->tuneup_def->use_addonparts.push_back(request->mpr_subject);
break;

case ModifyProjectRequestType::TUNEUP_USE_ADDONPART_RESET:
// Erase the addonpart from the TuneupDef document.
RoR::EraseIf(tuneup_entry->tuneup_def->use_addonparts, [request](const std::string& name) { return name == request->mpr_subject; });
break;

case ModifyProjectRequestType::TUNEUP_REMOVE_PROP_SET:
// Add the prop to the 'remove_props' in TuneupDef document.
tuneup_entry->tuneup_def->remove_props.insert(request->mpr_subject);
break;

case ModifyProjectRequestType::TUNEUP_REMOVE_PROP_RESET:
// Erase the prop from 'remove_props' in the TuneupDef document.
tuneup_entry->tuneup_def->remove_props.erase(request->mpr_subject);
break;

default:
break;
}

// If this is the auto-generated tuneup, immediatelly update the .tuneup file (user-saved tuneups are only modified on demand).
if (tuneup_entry->categoryid == CID_TuneupsAuto)
{
Ogre::DataStreamPtr datastream = Ogre::ResourceGroupManager::getSingleton().createResource(tuneup_entry->fname, tuneup_entry->resource_group, /*overwrite:*/true);
Str<200> header;
header
<< "// This is a '.tuneup' mod - it's similar to '.skin' mod but deals with '.addonpart' mods.\n"
<< "// See https://github.com/RigsOfRods/rigs-of-rods/pull/3096#issuecomment-1783976601\n\n";
datastream->write(header.GetBuffer(), header.GetLength());
RoR::TuneupParser::ExportTuneup(datastream, tuneup_entry->tuneup_def);
}

// Create spawn request while actor still exists
// Note we don't use `ActorModifyRequest::Type::RELOAD` because we don't need the bundle reloaded.
ActorSpawnRequest* srq = new ActorSpawnRequest;
srq->asr_position = Ogre::Vector3(request->mpr_target_actor->getPosition().x, request->mpr_target_actor->getMinHeight(), request->mpr_target_actor->getPosition().z);
srq->asr_rotation = Ogre::Quaternion(Ogre::Degree(270) - Ogre::Radian(request->mpr_target_actor->getRotation()), Ogre::Vector3::UNIT_Y);
srq->asr_config = request->mpr_target_actor->getSectionConfig();
srq->asr_skin_entry = request->mpr_target_actor->getUsedSkin();
srq->asr_tuneup_entry = tuneup_entry;
srq->asr_cache_entry = actor_entry;
srq->asr_debugview = (int)request->mpr_target_actor->GetGfxActor()->GetDebugView();
srq->asr_origin = ActorSpawnRequest::Origin::USER;

// Remove the actor
App::GetGameContext()->PushMessage(Message(MSG_SIM_DELETE_ACTOR_REQUESTED, (void*)new ActorPtr(request->mpr_target_actor)));

// Load our actor again, but only after it was deleted.
App::GetGameContext()->ChainMessage(Message(MSG_SIM_SPAWN_ACTOR_REQUESTED, (void*)srq));
}

size_t CacheSystem::Query(CacheQuery& query)
{
Ogre::StringUtil::toLowerCase(query.cqy_search_string);
Expand Down
17 changes: 17 additions & 0 deletions source/main/resources/CacheSystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,22 @@ struct CreateProjectRequest
bool cpr_create_tuneup; //!< Overrides project type to "tuneup", adds a .tuneup file only.
};

enum class ModifyProjectRequestType
{
NONE,
TUNEUP_USE_ADDONPART_SET,
TUNEUP_USE_ADDONPART_RESET,
TUNEUP_REMOVE_PROP_SET,
TUNEUP_REMOVE_PROP_RESET,
};

struct ModifyProjectRequest
{
ActorPtr mpr_target_actor;
ModifyProjectRequestType mpr_type = ModifyProjectRequestType::NONE;
std::string mpr_subject;
};

/// A content database
/// MOTIVATION:
/// RoR users usually have A LOT of content installed. Traversing it all on every game startup would be a pain.
Expand Down Expand Up @@ -249,6 +265,7 @@ class CacheSystem
std::string ActorTypeToName(ActorType driveable);

CacheEntryPtr CreateProject(CreateProjectRequest* request); //!< Creates subdirectory in 'My Games\Rigs of Rods\projects', pre-populates it with files and adds modcache entry.
void ModifyProject(ModifyProjectRequest* request);

private:

Expand Down
Loading

0 comments on commit a3f6db5

Please sign in to comment.