Skip to content

Commit

Permalink
Tuning: Working save/load/delete of tuneups.
Browse files Browse the repository at this point in the history
  • Loading branch information
ohlidalp committed Nov 2, 2023
1 parent 8df56a8 commit ace8640
Show file tree
Hide file tree
Showing 13 changed files with 279 additions and 86 deletions.
10 changes: 7 additions & 3 deletions source/main/GameContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -236,10 +236,14 @@ ActorPtr GameContext::SpawnActor(ActorSpawnRequest& rq)

LOG(" ===== LOADING VEHICLE: " + rq.asr_filename);

if (rq.asr_cache_entry != nullptr)
if (rq.asr_cache_entry)
{
rq.asr_filename = rq.asr_cache_entry->fname;
}
else
{
rq.asr_cache_entry = App::GetCacheSystem()->FindEntryByFilename(LT_AllBeam, /*partial:*/false, rq.asr_filename);
}

RigDef::DocumentPtr def = m_actor_manager.FetchActorDef(
rq.asr_filename, rq.asr_origin == ActorSpawnRequest::Origin::TERRN_DEF);
Expand Down Expand Up @@ -402,8 +406,8 @@ void GameContext::ModifyActor(ActorModifyRequest& rq)
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 = actor->getUsedTuneup();
srq->asr_skin_entry = actor->getUsedSkinEntry();
srq->asr_tuneup_entry = actor->getUsedTuneupEntry();
srq->asr_cache_entry= entry;
srq->asr_debugview = (int)actor->GetGfxActor()->GetDebugView();
srq->asr_origin = ActorSpawnRequest::Origin::USER;
Expand Down
35 changes: 35 additions & 0 deletions source/main/gui/GUIUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -434,3 +434,38 @@ void RoR::ImDrawModifierKeyHighlighted(OIS::KeyCode key)
ImGui::EndChildFrame();
ImGui::PopStyleVar(); // FramePadding
}

bool RoR::ImButtonHoldToConfirm(const std::string& btn_caption, bool small, float time_limit, float& time_left_var, float dt)
{

bool btn_pressed = false;
if (small)
btn_pressed = ImGui::SmallButton(btn_caption.c_str());
else
btn_pressed = ImGui::Button(btn_caption.c_str());

if (btn_pressed)
{
time_left_var = time_limit; // Initialize
}
else
{
if (ImGui::IsItemActive() && time_left_var > 0.f)
{
time_left_var -= dt;
if (time_left_var > 0.f)
{
ImGui::BeginTooltip();
std::string text = _L("Hold to confirm");
ImGui::TextDisabled(text.c_str());
ImGui::ProgressBar(time_left_var/time_limit, ImVec2(ImGui::CalcTextSize(text.c_str()).x, 8.f), "");
ImGui::EndTooltip();
}
else
{
return true;
}
}
}
return false;
}
3 changes: 3 additions & 0 deletions source/main/gui/GUIUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,7 @@ void ImTerminateComboboxString(std::string& target);
void ImDrawEventHighlighted(events input_event);
void ImDrawModifierKeyHighlighted(OIS::KeyCode key);

// Draws button which must be held for a period to report "clicked" - shows a tooltip with countdown progressbar.
bool ImButtonHoldToConfirm(const std::string& btn_caption, bool small, float time_limit, float& time_left_var, float dt);

} // namespace RoR
113 changes: 70 additions & 43 deletions source/main/gui/panels/GUI_TopMenubar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1449,7 +1449,7 @@ void TopMenubar::Draw(float dt)
else
{
ROR_ASSERT(tuning_actor->getUsedActorEntry());
CacheEntryPtr& tuneup_entry = tuning_actor->getUsedTuneup();
CacheEntryPtr& tuneup_entry = tuning_actor->getUsedTuneupEntry();
ROR_ASSERT(tuneup_entry); // Created by `GameContext::SpawnActor()`
ROR_ASSERT(tuneup_entry->resource_group != "");
ROR_ASSERT(tuneup_entry->tuneup_def != nullptr);
Expand All @@ -1462,86 +1462,108 @@ void TopMenubar::Draw(float dt)
ImGui::PushID(tuneup_result.cqr_entry->fname.c_str());

ImGui::Bullet();

// Load button (with tuneup name)
ImGui::SameLine();
if (ImGui::SmallButton(_LC("Tuning", "Load")))
if (ImGui::Button(tuneup_result.cqr_entry->dname.c_str()))
{
// Instead of loading with the saved tuneup directly, first overwrite the auto-generated one with the saved one
// and then reload with the autogenerated, so subsequent editing doesn't modify the save until user saves again.
// -------------------------------------------------------------------------------------------------------------
ModifyProjectRequest* req = new ModifyProjectRequest();
req->mpr_type = ModifyProjectRequestType::PROJECT_LOAD_TUNEUP;
req->mpr_subject = tuneup_result.cqr_entry->fname;
req->mpr_target_actor = tuning_actor;
App::GetGameContext()->PushMessage(Message(MSG_EDI_MODIFY_PROJECT_REQUESTED, req));
// Why 'MODIFY_PROJECT_REQUESTED' for loading?
// Instead of loading with the saved tuneup directly, we keep the autogenerated and sync it with the save.
// That way, subsequent editing doesn't modify the save until user saves again.
}

// Delete button (right-aligned)
ImGui::SameLine();
ImGui::Text("%s", tuneup_result.cqr_entry->dname.c_str());
ImGui::SameLine();
if (ImGui::SmallButton(_LC("Tuning", "Delete")))
if (tuning_delbtn_cursorx_min < ImGui::GetCursorPosX()) // Make sure button won't draw over item name
tuning_delbtn_cursorx_min = ImGui::GetCursorPosX();
std::string delbtn_text = _LC("Tuning", "Delete");
float delbtn_w = ImGui::CalcTextSize(delbtn_text.c_str()).x + ImGui::GetStyle().FramePadding.x * 2;
float delbtn_cursorx = ImGui::GetWindowContentRegionWidth() - delbtn_w;
if (delbtn_cursorx < tuning_delbtn_cursorx_min)
delbtn_cursorx = tuning_delbtn_cursorx_min;
ImGui::SetCursorPosX(delbtn_cursorx);
ImGui::PushStyleColor(ImGuiCol_Button, TUNING_HOLDTOCONFIRM_COLOR);
bool delbtn_pressed = RoR::ImButtonHoldToConfirm(delbtn_text, /*small:*/true,
TUNING_HOLDTOCONFIRM_TIMELIMIT, /*[by ref]:*/tuning_holdtoconfirm_time_left, dt);
ImGui::PopStyleColor(); //ImGuiCol_Button
if (delbtn_pressed)
{
tuning_deletebtn_time_left = TUNING_DELETEBTN_TIMELIMIT;
}
else
{
if (ImGui::IsItemActive() && tuning_deletebtn_time_left > 0.f)
{
tuning_deletebtn_time_left -= dt;
if (tuning_deletebtn_time_left > 0.f)
{
ImGui::BeginTooltip();
ImGui::ProgressBar(tuning_deletebtn_time_left/TUNING_DELETEBTN_TIMELIMIT);
ImGui::EndTooltip();
}
else
{
App::GetGameContext()->PushMessage(
Message(MSG_EDI_DELETE_PROJECT_REQUESTED, new CacheEntryPtr(tuneup_result.cqr_entry)));
}
}
App::GetGameContext()->PushMessage(Message(MSG_EDI_DELETE_PROJECT_REQUESTED, new CacheEntryPtr(tuneup_result.cqr_entry)));
}
}

// WORKING TUNEUP
ImGui::Separator();
ImGui::AlignTextToFramePadding();
ImGui::TextDisabled(_LC("Tuning", "Working tuneup"));
if (tuning_savebox_visible)
{
ImGui::Separator();
if (ImGui::SmallButton(_LC("Tuning", "Cancel")))
{
tuning_savebox_visible = false;
}
ImGui::InputText(_LC("Tuning", "Name"), tuning_savebox_buf.GetBuffer(), tuning_savebox_buf.GetCapacity());

if (ImGui::Button(_LC("Tuning","Save")))
{
CreateProjectRequest* req = new CreateProjectRequest();
req->cpr_type = tuning_savebox_overwrite
? CreateProjectRequestType::SAVE_TUNEUP_OVERWRITE
: CreateProjectRequestType::SAVE_TUNEUP_ONLY_NEW;
req->cpr_overwrite = tuning_savebox_overwrite;
req->cpr_type = CreateProjectRequestType::SAVE_TUNEUP;
req->cpr_source_actor = tuning_actor;
req->cpr_source_entry = tuning_actor->getUsedActorEntry();
req->cpr_name = tuning_savebox_buf.ToCStr();
App::GetGameContext()->PushMessage(Message(MSG_EDI_CREATE_PROJECT_REQUESTED, req));
}
ImGui::SameLine();
ImGui::Checkbox(_LC("Tuning", "Overwrite"), &tuning_savebox_overwrite);

// Cancel button (right-aligned)
std::string cancelbtn_text = _LC("Tuning", "Cancel");
float cancelbtn_w = ImGui::CalcTextSize(cancelbtn_text.c_str()).x + ImGui::GetStyle().FramePadding.x * 2;
ImGui::SetCursorPosX(ImGui::GetWindowContentRegionWidth() - cancelbtn_w);
if (ImGui::SmallButton(_LC("Tuning", "Cancel")))
{
tuning_savebox_visible = false;
}
ImGui::Separator();
}
else
{
if (ImGui::Button(_LC("Tuning", "Save current")))
ImGui::SameLine();
if (ImGui::Button(_LC("Tuning", "Save as...")))
{
tuning_savebox_visible = true;
}
}

ImGui::Separator();
// Reset button (right-aligned)
ImGui::SameLine();
ImGui::AlignTextToFramePadding();
std::string resetbtn_text = _LC("Tuning", "Reset");
float delbtn_w = ImGui::CalcTextSize(resetbtn_text.c_str()).x + ImGui::GetStyle().FramePadding.x * 2;
ImGui::SetCursorPosX(ImGui::GetWindowContentRegionWidth() - delbtn_w);
ImGui::PushStyleColor(ImGuiCol_Button, TUNING_HOLDTOCONFIRM_COLOR);
bool resetbtn_pressed = ImButtonHoldToConfirm(resetbtn_text, /*small:*/false,
TUNING_HOLDTOCONFIRM_TIMELIMIT, /*[by ref]:*/tuning_holdtoconfirm_time_left, dt);
ImGui::PopStyleColor(); //ImGuiCol_Button
if (resetbtn_pressed)
{
ModifyProjectRequest* request = new ModifyProjectRequest();
request->mpr_target_actor = tuning_actor;
request->mpr_type = ModifyProjectRequestType::PROJECT_RESET_TUNEUP;
App::GetGameContext()->PushMessage(Message(MSG_EDI_MODIFY_PROJECT_REQUESTED, request));
}
}

// ADDONPARTS

ImGui::SetNextItemOpen(true, ImGuiCond_FirstUseEver);
std::string addonparts_title = fmt::format(_LC("TopMenubar", "Addon parts ({})##tuningAddons"), tuning_addonparts.cqy_results.size());
std::string addonparts_title = fmt::format(_LC("TopMenubar", "Addon parts ({})"), tuning_addonparts.cqy_results.size());
if (ImGui::CollapsingHeader(addonparts_title.c_str()))
{
for (CacheQueryResult& addonpart_result: tuning_addonparts.cqy_results)
{
bool used = tuning_actor->getUsedTuneup()->tuneup_def->use_addonparts.count(addonpart_result.cqr_entry->fname) > 0;
bool used = tuning_actor->getUsedTuneupEntry()->tuneup_def->use_addonparts.count(addonpart_result.cqr_entry->fname) > 0;
if (ImGui::Checkbox(addonpart_result.cqr_entry->dname.c_str(), &used))
{
ModifyProjectRequest* req = new ModifyProjectRequest();
Expand All @@ -1564,7 +1586,7 @@ void TopMenubar::Draw(float dt)

// Draw props
size_t total_props = tuneup_entry->tuneup_def->remove_props.size() + tuning_actor->GetGfxActor()->getProps().size();
std::string props_title = fmt::format(_LC("TopMenubar", "Props ({})##tuningProps"), total_props);
std::string props_title = fmt::format(_LC("TopMenubar", "Props ({})"), total_props);
if (ImGui::CollapsingHeader(props_title.c_str()))
{
// First draw the removed props
Expand Down Expand Up @@ -1619,7 +1641,7 @@ void TopMenubar::Draw(float dt)

// Ditto for flexbodies
size_t total_flexbodies = tuneup_entry->tuneup_def->remove_flexbodies.size() + tuning_actor->GetGfxActor()->GetFlexbodies().size();
std::string flexbodies_title = fmt::format(_LC("TopMenubar", "Flexbodies ({})##tuningFlexbodies"), total_flexbodies);
std::string flexbodies_title = fmt::format(_LC("TopMenubar", "Flexbodies ({})"), total_flexbodies);
if (ImGui::CollapsingHeader(flexbodies_title.c_str()))
{
// Draw removed flexbodies
Expand Down Expand Up @@ -2114,8 +2136,10 @@ void TopMenubar::GetPresets()
void TopMenubar::RefreshTuningMenu()
{
const ActorPtr& current_actor = App::GetGameContext()->GetPlayerActor();
if (current_actor && tuning_actor != current_actor)
if (current_actor && (tuning_actor != current_actor || tuning_force_refresh))
{
ROR_ASSERT(current_actor->getUsedActorEntry());

tuning_addonparts.cqy_filter_type = LT_AddonPart;
tuning_addonparts.cqy_filter_guid = current_actor->getUsedActorEntry()->guid;
tuning_addonparts.cqy_results.clear();
Expand All @@ -2126,6 +2150,9 @@ void TopMenubar::RefreshTuningMenu()
tuning_saves.cqy_filter_category_id = CID_TuneupsUser; // Exclude auto-generated entries
tuning_saves.cqy_results.clear();
App::GetCacheSystem()->Query(tuning_saves);

tuning_delbtn_cursorx_min = 0.f;
}
tuning_actor = current_actor;
}

9 changes: 7 additions & 2 deletions source/main/gui/panels/GUI_TopMenubar.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,15 +104,20 @@ class TopMenubar
Str<200> tuning_savebox_buf; //!< Buffer for tuneup name to be saved
bool tuning_savebox_visible = false; //!< User pressed 'save active' to open savebox.
bool tuning_savebox_overwrite = false; //!< Status of "Overwrite?" checkbox
const float TUNING_DELETEBTN_TIMELIMIT = 2.f; //!< Delete button must be held for several sec to confirm.
float tuning_deletebtn_time_left = 0.f; //!< Delete button must be held for several sec to confirm.
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.
float tuning_holdtoconfirm_time_left = 0.f; //!< Delete button must be held for several sec to confirm.
bool tuning_force_refresh = false;
float tuning_delbtn_cursorx_min = 0.f; //!< Avoid drawing 'Delete' button over saved tuneup names.
void RefreshTuningMenu();

private:
void DrawActorListSinglePlayer();
void DrawMpUserToActorList(RoRnet::UserInfo &user); // Multiplayer
void DrawSpecialStateBox(float top_offset);



ImVec2 m_open_menu_hoverbox_min;
ImVec2 m_open_menu_hoverbox_max;
TopMenu m_open_menu = TopMenu::TOPMENU_NONE;
Expand Down
8 changes: 8 additions & 0 deletions source/main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1032,6 +1032,14 @@ int main(int argc, char *argv[])
break;
}

case MSG_EDI_DELETE_PROJECT_REQUESTED:
{
CacheEntryPtr* entry_ptr = static_cast<CacheEntryPtr*>(m.payload);
App::GetCacheSystem()->DeleteProject(*entry_ptr);
delete entry_ptr;
break;
}

default:;
}

Expand Down
12 changes: 2 additions & 10 deletions source/main/physics/Actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4585,20 +4585,12 @@ CacheEntryPtr& Actor::getUsedActorEntry()
return m_used_actor_entry;
}

CacheEntryPtr& Actor::getUsedSkin()
CacheEntryPtr& Actor::getUsedSkinEntry()
{
return m_used_skin_entry;
}
void Actor::setUsedSkin(CacheEntryPtr& skin)
{
m_used_skin_entry = skin;
}

CacheEntryPtr& Actor::getUsedTuneup()
CacheEntryPtr& Actor::getUsedTuneupEntry()
{
return m_used_tuneup_entry;
}
void Actor::setUsedTuneup(CacheEntryPtr& tuneup)
{
m_used_tuneup_entry = tuneup;
}
6 changes: 2 additions & 4 deletions source/main/physics/Actor.h
Original file line number Diff line number Diff line change
Expand Up @@ -218,10 +218,8 @@ class Actor : public RefCountingObject<Actor>
int getInstanceId() { return ar_instance_id; }
// not exported to scripting:
CacheEntryPtr& getUsedActorEntry(); //!< The actor entry itself.
CacheEntryPtr& getUsedSkin();
void setUsedSkin(CacheEntryPtr& skin);
CacheEntryPtr& getUsedTuneup();
void setUsedTuneup(CacheEntryPtr& tuneup);
CacheEntryPtr& getUsedSkinEntry();
CacheEntryPtr& getUsedTuneupEntry();
bool isPreloadedWithTerrain() const { return m_preloaded_with_terrain; };
std::vector<authorinfo_t> getAuthors();
std::vector<std::string> getDescription();
Expand Down
2 changes: 1 addition & 1 deletion source/main/physics/ActorSpawner.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1559,7 +1559,7 @@ void ActorSpawner::ProcessMinimass(RigDef::Minimass & def)
void ActorSpawner::ProcessProp(RigDef::Prop & def)
{
// Check if removed via .tuneup
CacheEntryPtr& tuneup_entry = m_actor->getUsedTuneup();
CacheEntryPtr& tuneup_entry = m_actor->getUsedTuneupEntry();
if (tuneup_entry && tuneup_entry->tuneup_def->remove_props.find(def.mesh_name) != tuneup_entry->tuneup_def->remove_props.end())
{
LOG(fmt::format("{}: Prop '{}' removed by tuneup '{}'", m_actor->ar_filename, def.mesh_name, tuneup_entry->fname));
Expand Down
Loading

0 comments on commit ace8640

Please sign in to comment.