From 1e11549b5b471e734c519d2f824c62baddeb541d Mon Sep 17 00:00:00 2001 From: Josef Wittmann Date: Thu, 13 Mar 2025 18:34:28 +0100 Subject: [PATCH 01/13] Allow to configure timings --- FastAerialTrainer/FastAerialTrainer.cpp | 133 +++++++++++++++++---- FastAerialTrainer/FastAerialTrainer.h | 26 ++-- FastAerialTrainer/FastAerialTrainerGUI.cpp | 102 ++++++++++++++-- 3 files changed, 214 insertions(+), 47 deletions(-) diff --git a/FastAerialTrainer/FastAerialTrainer.cpp b/FastAerialTrainer/FastAerialTrainer.cpp index b37b98f..13158c2 100644 --- a/FastAerialTrainer/FastAerialTrainer.cpp +++ b/FastAerialTrainer/FastAerialTrainer.cpp @@ -2,6 +2,7 @@ #include "FastAerialTrainer.h" #include +#include BAKKESMOD_PLUGIN(FastAerialTrainer, "FastAerialTrainer", plugin_version, PLUGINTYPE_FREEPLAY); @@ -25,6 +26,47 @@ static std::string to_string(LinearColor col) + std::to_string((int)col.A) + ")"; } +std::vector FastAerialTrainer::SplitString(std::string str) +{ + std::vector values; + std::istringstream stream(str); + std::string value; + + while (std::getline(stream, value, ',')) + values.push_back(strtof(value.c_str(), NULL)); + + return values; +}; +std::vector FastAerialTrainer::BuildRanges(std::vector values, std::vector colors) +{ + std::vector ranges; + + for (int i = 0; i < std::min(colors.size(), values.size() - 1); i++) + { + ranges.push_back({ + .min = values[i], + .max = values[i + 1], + .color = colors[i] + }); + } + + return ranges; +}; +std::string FastAerialTrainer::RangesToString(std::vector ranges) +{ + std::string result; + + if (ranges.empty()) + return result; + + result = std::to_string(ranges[0].min); + + for (auto& range : ranges) + result += "," + std::to_string(range.max); + + return result; +}; + void FastAerialTrainer::onLoad() { // This line is required for `LOG` to work and must be before any use of `LOG()`. @@ -63,8 +105,6 @@ void FastAerialTrainer::onLoad() registerPercentCvar(GUI_POSITION_RELATIVE_X, GuiPositionRelative.X); registerPercentCvar(GUI_POSITION_RELATIVE_Y, GuiPositionRelative.Y); registerFloatCvar(GUI_SIZE, GuiSize); - registerIntCvar(GUI_JUMP_MAX, JumpDuration_HighestValue); - registerIntCvar(GUI_DOUBLE_JUMP_MAX, DoubleJumpDuration_HighestValue); registerPercentCvar(GUI_PREVIEW_OPACTIY, GuiColorPreviewOpacity); registerBoolCvar(GUI_DRAW_PITCH_HISTORY, GuiDrawPitchHistory); registerBoolCvar(GUI_DRAW_BOOST_HISTORY, GuiDrawBoostHistory); @@ -75,7 +115,32 @@ void FastAerialTrainer::onLoad() registerColorCvar(GUI_COLOR_WARNING, GuiColorWarning); registerColorCvar(GUI_COLOR_FAILURE, GuiColorFailure); registerColorCvar(GUI_COLOR_HISTORY, GuiPitchHistoryColor); - + persistentStorage->RegisterPersistentCvar(GUI_JUMP_RANGES, RangesToString(JumpDurationRanges), "", false) + .addOnValueChanged([&](std::string oldValue, CVarWrapper cvar) + { + JumpDurationRanges = BuildRanges( + SplitString(cvar.getStringValue()), + { + &GuiColorFailure, + &GuiColorWarning, + &GuiColorSuccess, + &GuiColorWarning, + &GuiColorFailure + } + ); + }); + persistentStorage->RegisterPersistentCvar(GUI_DOUBLE_JUMP_RANGES, RangesToString(DoubleJumpDurationRanges), "", false) + .addOnValueChanged([&](std::string oldValue, CVarWrapper cvar) + { + DoubleJumpDurationRanges = BuildRanges( + SplitString(cvar.getStringValue()), + { + &GuiColorSuccess, + &GuiColorWarning, + &GuiColorFailure + } + ); + }); gameWrapper->RegisterDrawable( [this](CanvasWrapper canvas) @@ -251,15 +316,15 @@ void FastAerialTrainer::RenderCanvas(CanvasWrapper canvas) ScreenSize = canvas.GetSize(); DrawBar( - canvas, "Hold First Jump: ", HoldFirstJumpDuration * 1000, (float)JumpDuration_HighestValue, + canvas, "Hold First Jump: ", HoldFirstJumpDuration * 1000, GuiPosition(), BarSize(), - GuiColorBackground, JumpDuration_RangeList + GuiColorBackground, JumpDurationRanges ); DrawBar( - canvas, "Time to Double Jump: ", TimeBetweenFirstAndDoubleJump * 1000, (float)DoubleJumpDuration_HighestValue, + canvas, "Time to Double Jump: ", TimeBetweenFirstAndDoubleJump * 1000, GuiPosition() + Offset(), BarSize(), - GuiColorBackground, DoubleJumpDuration_RangeList + GuiColorBackground, DoubleJumpDurationRanges ); canvas.SetColor(GuiColorBorder); @@ -278,49 +343,69 @@ void FastAerialTrainer::RenderCanvas(CanvasWrapper canvas) } void FastAerialTrainer::DrawBar( - CanvasWrapper& canvas, std::string text, float value, float maxValue, + CanvasWrapper& canvas, std::string text, float value, Vector2F barPos, Vector2F barSize, LinearColor backgroundColor, std::vector& colorRanges ) { + if (colorRanges.empty()) + return; + + float minValue = colorRanges.front().min; + float maxValue = colorRanges.back().max; + auto valueToPosition = [&](float value) + { + auto ratio = (value - minValue) / (maxValue - minValue); + return barSize.X * std::clamp(ratio, 0.f, 1.f); + }; + // Draw background canvas.SetPosition(barPos); canvas.SetColor(backgroundColor); canvas.FillBox(barSize); + for (Range& range : colorRanges) { LinearColor preview = *range.color; preview.A *= GuiColorPreviewOpacity; canvas.SetColor(preview); - float left = std::min(range.min / maxValue, 1.f); - float width = std::min((range.max - range.min) / maxValue, 1.f - left); - canvas.SetPosition(barPos + Vector2F{ left * barSize.X, 0 }); - canvas.FillBox(Vector2F{ width * barSize.X, barSize.Y }); + + float left = valueToPosition(range.min); + float right = valueToPosition(range.max); + float width = right - left; + canvas.SetPosition(barPos + Vector2F{ left, 0 }); + canvas.FillBox(Vector2F{ width, barSize.Y }); } // Draw colored bar - canvas.SetPosition(barPos); - for (Range& range : colorRanges) + if (value < minValue) + canvas.SetColor(*colorRanges.front().color); + else if (value >= maxValue) + canvas.SetColor(*colorRanges.back().color); + else { - if (range.min <= value && value <= range.max) + for (Range& range : colorRanges) { - canvas.SetColor(*range.color); + if (range.min <= value && value < range.max) + canvas.SetColor(*range.color); } } - float result = std::min(1.f, value / maxValue); - canvas.FillBox(Vector2F{ barSize.X * result, barSize.Y }); + canvas.SetPosition(barPos); + canvas.FillBox(Vector2F{ valueToPosition(value), barSize.Y }); - // Draw border + // Draw separators canvas.SetColor(GuiColorBorder); canvas.SetPosition(barPos); canvas.DrawBox(barSize); - for (Range& range : colorRanges) + + std::set values; + for (Range& range : colorRanges) values.insert({ range.min, range.max }); + for (float value : values) { - canvas.SetColor(GuiColorBorder); - float result = range.max / maxValue; - if (result < 1.f) + if (minValue < value && value < maxValue) { - canvas.SetPosition(barPos + Vector2{ (int)(result * barSize.X), 0 }); + canvas.SetColor(GuiColorBorder); + canvas.SetPosition(barPos + Vector2F{ valueToPosition(value), 0 }); canvas.FillBox(Vector2F{ 2, barSize.Y }); } } diff --git a/FastAerialTrainer/FastAerialTrainer.h b/FastAerialTrainer/FastAerialTrainer.h index ce7e43e..631d73b 100644 --- a/FastAerialTrainer/FastAerialTrainer.h +++ b/FastAerialTrainer/FastAerialTrainer.h @@ -20,8 +20,8 @@ constexpr auto GUI_PREVIEW_OPACTIY = "fast_aerial_trainer_gui_preview_opacity"; constexpr auto GUI_COLOR_SUCCESS = "fast_aerial_trainer_gui_color_success"; constexpr auto GUI_COLOR_WARNING = "fast_aerial_trainer_gui_color_warning"; constexpr auto GUI_COLOR_FAILURE = "fast_aerial_trainer_gui_color_failure"; -constexpr auto GUI_JUMP_MAX = "fast_aerial_trainer_gui_jump_max"; -constexpr auto GUI_DOUBLE_JUMP_MAX = "fast_aerial_trainer_gui_double_jump_max"; +constexpr auto GUI_JUMP_RANGES = "fast_aerial_trainer_gui_jump_ranges"; +constexpr auto GUI_DOUBLE_JUMP_RANGES = "fast_aerial_trainer_gui_double_jump_ranges"; constexpr auto GUI_DRAW_PITCH_HISTORY = "fast_aerial_trainer_gui_draw_pitch_history"; constexpr auto GUI_DRAW_BOOST_HISTORY = "fast_aerial_trainer_gui_draw_boost_history"; constexpr auto GUI_COLOR_HISTORY = "fast_aerial_trainer_gui_color_history"; @@ -29,8 +29,8 @@ constexpr auto GUI_SHOW_FIRST_INPUT_WARNING = "fast_aerial_trainer_gui_first_inp struct Range { - int min; - int max; + float min; + float max; LinearColor* color; }; @@ -93,22 +93,18 @@ class FastAerialTrainer : public BakkesMod::Plugin::BakkesModPlugin, public Sett LinearColor GuiColorSuccess = LinearColor(0, 0, 255, 210); LinearColor GuiColorWarning = LinearColor(255, 255, 0, 210); LinearColor GuiColorFailure = LinearColor(255, 0, 0, 210); - std::vector JumpDuration_RangeList = - { + std::vector JumpDurationRanges = { Range{ 0, 180, &GuiColorFailure }, Range{ 180, 195, &GuiColorWarning }, Range{ 195, 225, &GuiColorSuccess }, Range{ 225, 260, &GuiColorWarning }, - Range{ 260, INT_MAX, &GuiColorFailure } + Range{ 260, 300, &GuiColorFailure } }; - int JumpDuration_HighestValue = 300; - std::vector DoubleJumpDuration_RangeList = - { + std::vector DoubleJumpDurationRanges = { Range{ 0, 75, &GuiColorSuccess }, Range{ 75, 110, &GuiColorWarning }, - Range{ 110, INT_MAX, &GuiColorFailure } + Range{ 110, 130, &GuiColorFailure } }; - int DoubleJumpDuration_HighestValue = 130; LinearColor GuiPitchHistoryColor = LinearColor(240, 240, 240, 255); bool GuiDrawPitchHistory = true; bool GuiDrawBoostHistory = true; @@ -124,11 +120,15 @@ class FastAerialTrainer : public BakkesMod::Plugin::BakkesModPlugin, public Sett void OnTick(CarWrapper car, ControllerInput* input); void RenderCanvas(CanvasWrapper canvas); - void DrawBar(CanvasWrapper& canvas, std::string text, float value, float maxValue, Vector2F barPos, Vector2F barSize, LinearColor backgroundColor, std::vector& colorRanges); + void DrawBar(CanvasWrapper& canvas, std::string text, float value, Vector2F barPos, Vector2F barSize, LinearColor backgroundColor, std::vector& colorRanges); void DrawPitchHistory(CanvasWrapper& canvas); void DrawBoostHistory(CanvasWrapper& canvas); void RenderFirstInputWarning(CanvasWrapper& canvas); + std::vector SplitString(std::string str); + std::vector BuildRanges(std::vector values, std::vector colors); + std::string RangesToString(std::vector ranges); + virtual void onLoad(); virtual void onUnload(); diff --git a/FastAerialTrainer/FastAerialTrainerGUI.cpp b/FastAerialTrainer/FastAerialTrainerGUI.cpp index 3d406b8..f9a6635 100644 --- a/FastAerialTrainer/FastAerialTrainerGUI.cpp +++ b/FastAerialTrainer/FastAerialTrainerGUI.cpp @@ -1,21 +1,24 @@ #include "pch.h" #include "FastAerialTrainer.h" -static bool ColorPicker(const char* label, LinearColor& color) { +static bool ColorPicker(const char* label, LinearColor& color) +{ LinearColor col = color / 255; bool retVal = ImGui::ColorEdit4(label, &col.R, ImGuiColorEditFlags_AlphaBar); color = col * 255; return retVal; } -static bool ColorPickerWithoutAlpha(const char* label, LinearColor& color) { +static bool ColorPickerWithoutAlpha(const char* label, LinearColor& color) +{ LinearColor col = color / 255; bool retVal = ImGui::ColorEdit3(label, &col.R); color = col * 255; return retVal; } -static bool PercentageSlider(const char* label, float& value, float max = 1.f) { +static bool PercentageSlider(const char* label, float& value, float max = 1.f) +{ float x = value * 100; bool retVal = ImGui::SliderFloat(label, &x, 0, 100 * max, "%.1f %%"); value = x / 100; @@ -38,10 +41,10 @@ void FastAerialTrainer::RenderSettings() if (PercentageSlider("GUI Position Y", GuiPositionRelative.Y)) cvarManager->getCvar(GUI_POSITION_RELATIVE_Y).setValue(GuiPositionRelative.Y); - if (ImGui::SliderFloat("GUI Size", &GuiSize, 0, ScreenSize.X, "%.0f")) + if (ImGui::SliderFloat("GUI Size", &GuiSize, 0, ScreenSize.X, "%.0f pixels")) cvarManager->getCvar(GUI_SIZE).setValue(GuiSize); if (ImGui::IsItemHovered()) - ImGui::SetTooltip("For clearest text use multiples of 350."); + ImGui::SetTooltip("For clearest text use multiples of 350 pixels."); if (ColorPicker("Border and Text Color", GuiColorBorder)) cvarManager->getCvar(GUI_BORDER_COLOR).setValue(GuiColorBorder); @@ -61,11 +64,90 @@ void FastAerialTrainer::RenderSettings() if (ColorPicker("Failure Color", GuiColorFailure)) cvarManager->getCvar(GUI_COLOR_FAILURE).setValue(GuiColorFailure); - if (ImGui::DragInt("First Jump Hold - Highest Value", &JumpDuration_HighestValue, 1.f, 1, INT_MAX, "%d ms")) - cvarManager->getCvar(GUI_JUMP_MAX).setValue(JumpDuration_HighestValue); - - if (ImGui::DragInt("Time to Double Jump - Highest Value", &DoubleJumpDuration_HighestValue, 1.f, 1, INT_MAX, "%d ms")) - cvarManager->getCvar(GUI_DOUBLE_JUMP_MAX).setValue(DoubleJumpDuration_HighestValue); + float speed = 0.1f; + const char* format = "%.1f ms"; + float width = 150; + float spacing = 20; + + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + ImGui::Text("First Jump Timing"); + ImGui::Spacing(); + { + auto& ranges = JumpDurationRanges; + auto& cvar = GUI_JUMP_RANGES; + bool changed = false; + auto Input = [&](const char* label, float* value, float min, float max) + { + if (ImGui::DragFloat(label, value, speed, min, max, format)) + changed = true; + }; + + ImGui::PushID("FirstJumpTiming"); + ImGui::PushItemWidth(width); + + ImGui::BeginGroup(); + Input("Success Low", &ranges[1].max, ranges[0].max, ranges[2].max); + Input("Success High", &ranges[2].max, ranges[1].max, ranges[3].max); + ImGui::EndGroup(); ImGui::SameLine(0, spacing); ImGui::BeginGroup(); + Input("Warning Low", &ranges[0].max, ranges[0].min, ranges[1].max); + Input("Warning High", &ranges[3].max, ranges[2].max, ranges[4].max); + ImGui::EndGroup(); ImGui::SameLine(0, spacing); ImGui::BeginGroup(); + Input("Failure Low", &ranges[0].min, 0, ranges[0].max); + Input("Failure High", &ranges[4].max, ranges[3].max, FLT_MAX); + ImGui::EndGroup(); + + ImGui::Spacing(); + if (ImGui::Button("Reset to default")) + cvarManager->getCvar(cvar).ResetToDefault(); + + ImGui::PopItemWidth(); + ImGui::PopID(); + + if (changed) + cvarManager->getCvar(cvar).setValue(RangesToString(ranges)); + } + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); + ImGui::Text("Double Jump Timing"); + ImGui::Spacing(); + { + auto& ranges = DoubleJumpDurationRanges; + auto& cvar = GUI_DOUBLE_JUMP_RANGES; + bool changed = false; + auto Input = [&](const char* label, float* value, float min, float max) + { + if (ImGui::DragFloat(label, value, speed, min, max, format)) + changed = true; + }; + + ImGui::PushID("DoubleJumpTiming"); + ImGui::PushItemWidth(width); + + ImGui::BeginGroup(); + Input("Success Low", &ranges[0].min, 0, ranges[0].max); + Input("Success High", &ranges[0].max, ranges[0].min, ranges[1].max); + ImGui::EndGroup(); ImGui::SameLine(0, spacing); ImGui::BeginGroup(); + Input("Warning High", &ranges[1].max, ranges[0].max, ranges[2].max); + ImGui::EndGroup(); ImGui::SameLine(0, spacing); ImGui::BeginGroup(); + Input("Failure High", &ranges[2].max, ranges[1].max, FLT_MAX); + ImGui::EndGroup(); + + ImGui::Spacing(); + if (ImGui::Button("Reset to default")) + cvarManager->getCvar(cvar).ResetToDefault(); + + ImGui::PopItemWidth(); + ImGui::PopID(); + + if (changed) + cvarManager->getCvar(cvar).setValue(RangesToString(ranges)); + } + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); if (ImGui::Checkbox("Draw Pitch History", &GuiDrawPitchHistory)) cvarManager->getCvar(GUI_DRAW_PITCH_HISTORY).setValue(GuiDrawPitchHistory); From bb0f4cedce2c27d680e64f3a6750ce6e4a2badf6 Mon Sep 17 00:00:00 2001 From: Josef Wittmann Date: Sun, 16 Mar 2025 18:33:42 +0100 Subject: [PATCH 02/13] Extract RangeList (WIP) --- FastAerialTrainer/FastAerialTrainer.cpp | 142 +++++++++++---------- FastAerialTrainer/FastAerialTrainer.h | 56 +++++--- FastAerialTrainer/FastAerialTrainerGUI.cpp | 10 +- 3 files changed, 117 insertions(+), 91 deletions(-) diff --git a/FastAerialTrainer/FastAerialTrainer.cpp b/FastAerialTrainer/FastAerialTrainer.cpp index 13158c2..9e7c2c9 100644 --- a/FastAerialTrainer/FastAerialTrainer.cpp +++ b/FastAerialTrainer/FastAerialTrainer.cpp @@ -26,47 +26,6 @@ static std::string to_string(LinearColor col) + std::to_string((int)col.A) + ")"; } -std::vector FastAerialTrainer::SplitString(std::string str) -{ - std::vector values; - std::istringstream stream(str); - std::string value; - - while (std::getline(stream, value, ',')) - values.push_back(strtof(value.c_str(), NULL)); - - return values; -}; -std::vector FastAerialTrainer::BuildRanges(std::vector values, std::vector colors) -{ - std::vector ranges; - - for (int i = 0; i < std::min(colors.size(), values.size() - 1); i++) - { - ranges.push_back({ - .min = values[i], - .max = values[i + 1], - .color = colors[i] - }); - } - - return ranges; -}; -std::string FastAerialTrainer::RangesToString(std::vector ranges) -{ - std::string result; - - if (ranges.empty()) - return result; - - result = std::to_string(ranges[0].min); - - for (auto& range : ranges) - result += "," + std::to_string(range.max); - - return result; -}; - void FastAerialTrainer::onLoad() { // This line is required for `LOG` to work and must be before any use of `LOG()`. @@ -115,31 +74,17 @@ void FastAerialTrainer::onLoad() registerColorCvar(GUI_COLOR_WARNING, GuiColorWarning); registerColorCvar(GUI_COLOR_FAILURE, GuiColorFailure); registerColorCvar(GUI_COLOR_HISTORY, GuiPitchHistoryColor); - persistentStorage->RegisterPersistentCvar(GUI_JUMP_RANGES, RangesToString(JumpDurationRanges), "", false) + persistentStorage->RegisterPersistentCvar(GUI_JUMP_RANGES, JumpDurationRanges.ValuesToString(), "", false) .addOnValueChanged([&](std::string oldValue, CVarWrapper cvar) { - JumpDurationRanges = BuildRanges( - SplitString(cvar.getStringValue()), - { - &GuiColorFailure, - &GuiColorWarning, - &GuiColorSuccess, - &GuiColorWarning, - &GuiColorFailure - } - ); + auto values = RangeList::SplitString(cvar.getStringValue()); + JumpDurationRanges.UpdateValues(values); }); - persistentStorage->RegisterPersistentCvar(GUI_DOUBLE_JUMP_RANGES, RangesToString(DoubleJumpDurationRanges), "", false) + persistentStorage->RegisterPersistentCvar(GUI_DOUBLE_JUMP_RANGES, DoubleJumpDurationRanges.ValuesToString(), "", false) .addOnValueChanged([&](std::string oldValue, CVarWrapper cvar) { - DoubleJumpDurationRanges = BuildRanges( - SplitString(cvar.getStringValue()), - { - &GuiColorSuccess, - &GuiColorWarning, - &GuiColorFailure - } - ); + auto values = RangeList::SplitString(cvar.getStringValue()); + DoubleJumpDurationRanges.UpdateValues(values); }); gameWrapper->RegisterDrawable( @@ -345,14 +290,14 @@ void FastAerialTrainer::RenderCanvas(CanvasWrapper canvas) void FastAerialTrainer::DrawBar( CanvasWrapper& canvas, std::string text, float value, Vector2F barPos, Vector2F barSize, - LinearColor backgroundColor, std::vector& colorRanges + LinearColor backgroundColor, RangeList& colorRanges ) { - if (colorRanges.empty()) + if (colorRanges.GetRanges().empty()) return; - float minValue = colorRanges.front().min; - float maxValue = colorRanges.back().max; + float minValue = colorRanges.GetRanges().front().min; + float maxValue = colorRanges.GetRanges().back().max; auto valueToPosition = [&](float value) { auto ratio = (value - minValue) / (maxValue - minValue); @@ -364,7 +309,7 @@ void FastAerialTrainer::DrawBar( canvas.SetColor(backgroundColor); canvas.FillBox(barSize); - for (Range& range : colorRanges) + for (Range& range : colorRanges.GetRanges()) { LinearColor preview = *range.color; preview.A *= GuiColorPreviewOpacity; @@ -379,12 +324,12 @@ void FastAerialTrainer::DrawBar( // Draw colored bar if (value < minValue) - canvas.SetColor(*colorRanges.front().color); + canvas.SetColor(*colorRanges.GetRanges().front().color); else if (value >= maxValue) - canvas.SetColor(*colorRanges.back().color); + canvas.SetColor(*colorRanges.GetRanges().back().color); else { - for (Range& range : colorRanges) + for (Range& range : colorRanges.GetRanges()) { if (range.min <= value && value < range.max) canvas.SetColor(*range.color); @@ -399,7 +344,7 @@ void FastAerialTrainer::DrawBar( canvas.DrawBox(barSize); std::set values; - for (Range& range : colorRanges) values.insert({ range.min, range.max }); + for (Range& range : colorRanges.GetRanges()) values.insert({ range.min, range.max }); for (float value : values) { if (minValue < value && value < maxValue) @@ -541,3 +486,60 @@ void FastAerialTrainer::onUnload() { // nothing to unload... } + +RangeList::RangeList(std::vector values, std::vector colors) +{ + if (colors.size() != values.size() - 1) + LOG("Constructing RangeList: Number of values and colors don't match!"); + + for (int i = 0; i < std::min(colors.size(), values.size() - 1); i++) + { + ranges.push_back( + { + .min = values[i], + .max = values[i + 1], + .color = colors[i] + } + ); + } +} + +void RangeList::UpdateValues(std::vector values) +{ + auto size = std::min(values.size(), ranges.size() + 1); + for (int i = 0; i < size; i++) + { + if (i > 0) ranges[i - 1].max = values[i]; + if (i < size - 1) ranges[i].min = values[i]; + } +} +std::vector& RangeList::GetRanges() +{ + return ranges; +} + +std::string RangeList::ValuesToString() +{ + std::string result; + + if (ranges.empty()) + return result; + + result = std::to_string(ranges[0].min); + + for (auto& range : ranges) + result += "," + std::to_string(range.max); + + return result; +} +std::vector RangeList::SplitString(std::string str) +{ + std::vector values; + std::istringstream stream(str); + std::string value; + + while (std::getline(stream, value, ',')) + values.push_back(strtof(value.c_str(), NULL)); + + return values; +} \ No newline at end of file diff --git a/FastAerialTrainer/FastAerialTrainer.h b/FastAerialTrainer/FastAerialTrainer.h index 631d73b..abbeb52 100644 --- a/FastAerialTrainer/FastAerialTrainer.h +++ b/FastAerialTrainer/FastAerialTrainer.h @@ -41,6 +41,26 @@ struct InputHistoryItem bool jumped; }; +class RangeList +{ +private: + std::vector ranges; + // total min,max + // get color (or range) for value + // make sure it's always ordered + // iterate ranges + // get all stops (no duplicates) + +public: + RangeList(std::vector values, std::vector colors); + + void UpdateValues(std::vector values); + std::vector& GetRanges(); + + std::string ValuesToString(); + static std::vector SplitString(std::string str); +}; + class FastAerialTrainer : public BakkesMod::Plugin::BakkesModPlugin, public SettingsWindowBase { std::shared_ptr persistentStorage; @@ -93,18 +113,24 @@ class FastAerialTrainer : public BakkesMod::Plugin::BakkesModPlugin, public Sett LinearColor GuiColorSuccess = LinearColor(0, 0, 255, 210); LinearColor GuiColorWarning = LinearColor(255, 255, 0, 210); LinearColor GuiColorFailure = LinearColor(255, 0, 0, 210); - std::vector JumpDurationRanges = { - Range{ 0, 180, &GuiColorFailure }, - Range{ 180, 195, &GuiColorWarning }, - Range{ 195, 225, &GuiColorSuccess }, - Range{ 225, 260, &GuiColorWarning }, - Range{ 260, 300, &GuiColorFailure } - }; - std::vector DoubleJumpDurationRanges = { - Range{ 0, 75, &GuiColorSuccess }, - Range{ 75, 110, &GuiColorWarning }, - Range{ 110, 130, &GuiColorFailure } - }; + RangeList JumpDurationRanges = RangeList( + { 0, 180, 195, 225, 260, 300 }, + { + &GuiColorFailure, + &GuiColorWarning, + &GuiColorSuccess, + &GuiColorWarning, + &GuiColorFailure + } + ); + RangeList DoubleJumpDurationRanges = RangeList( + { 0, 75, 110, 130 }, + { + &GuiColorSuccess, + &GuiColorWarning, + &GuiColorFailure + } + ); LinearColor GuiPitchHistoryColor = LinearColor(240, 240, 240, 255); bool GuiDrawPitchHistory = true; bool GuiDrawBoostHistory = true; @@ -120,15 +146,11 @@ class FastAerialTrainer : public BakkesMod::Plugin::BakkesModPlugin, public Sett void OnTick(CarWrapper car, ControllerInput* input); void RenderCanvas(CanvasWrapper canvas); - void DrawBar(CanvasWrapper& canvas, std::string text, float value, Vector2F barPos, Vector2F barSize, LinearColor backgroundColor, std::vector& colorRanges); + void DrawBar(CanvasWrapper& canvas, std::string text, float value, Vector2F barPos, Vector2F barSize, LinearColor backgroundColor, RangeList& colorRanges); void DrawPitchHistory(CanvasWrapper& canvas); void DrawBoostHistory(CanvasWrapper& canvas); void RenderFirstInputWarning(CanvasWrapper& canvas); - std::vector SplitString(std::string str); - std::vector BuildRanges(std::vector values, std::vector colors); - std::string RangesToString(std::vector ranges); - virtual void onLoad(); virtual void onUnload(); diff --git a/FastAerialTrainer/FastAerialTrainerGUI.cpp b/FastAerialTrainer/FastAerialTrainerGUI.cpp index f9a6635..11e6ff0 100644 --- a/FastAerialTrainer/FastAerialTrainerGUI.cpp +++ b/FastAerialTrainer/FastAerialTrainerGUI.cpp @@ -75,7 +75,8 @@ void FastAerialTrainer::RenderSettings() ImGui::Text("First Jump Timing"); ImGui::Spacing(); { - auto& ranges = JumpDurationRanges; + auto& rangeList = JumpDurationRanges; + auto& ranges = rangeList.GetRanges(); auto& cvar = GUI_JUMP_RANGES; bool changed = false; auto Input = [&](const char* label, float* value, float min, float max) @@ -106,7 +107,7 @@ void FastAerialTrainer::RenderSettings() ImGui::PopID(); if (changed) - cvarManager->getCvar(cvar).setValue(RangesToString(ranges)); + cvarManager->getCvar(cvar).setValue(rangeList.ValuesToString()); } ImGui::Spacing(); ImGui::Separator(); @@ -114,7 +115,8 @@ void FastAerialTrainer::RenderSettings() ImGui::Text("Double Jump Timing"); ImGui::Spacing(); { - auto& ranges = DoubleJumpDurationRanges; + auto& rangeList = DoubleJumpDurationRanges; + auto& ranges = rangeList.GetRanges(); auto& cvar = GUI_DOUBLE_JUMP_RANGES; bool changed = false; auto Input = [&](const char* label, float* value, float min, float max) @@ -143,7 +145,7 @@ void FastAerialTrainer::RenderSettings() ImGui::PopID(); if (changed) - cvarManager->getCvar(cvar).setValue(RangesToString(ranges)); + cvarManager->getCvar(cvar).setValue(rangeList.ValuesToString()); } ImGui::Spacing(); ImGui::Separator(); From e4e970e2e1370145fd6cd414528a1766badced6e Mon Sep 17 00:00:00 2001 From: Josef Wittmann Date: Sun, 16 Mar 2025 18:37:44 +0100 Subject: [PATCH 03/13] Fix UI inaccuracies when scaling --- FastAerialTrainer/FastAerialTrainer.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/FastAerialTrainer/FastAerialTrainer.cpp b/FastAerialTrainer/FastAerialTrainer.cpp index 9e7c2c9..77f977f 100644 --- a/FastAerialTrainer/FastAerialTrainer.cpp +++ b/FastAerialTrainer/FastAerialTrainer.cpp @@ -339,9 +339,12 @@ void FastAerialTrainer::DrawBar( canvas.FillBox(Vector2F{ valueToPosition(value), barSize.Y }); // Draw separators + // `DrawBox()` always draws lines with width `2`. + // By offsetting the box by one pixel (half the line width), we avoid gaps and overlaps. + auto offset = 1; canvas.SetColor(GuiColorBorder); - canvas.SetPosition(barPos); - canvas.DrawBox(barSize); + canvas.SetPosition(barPos - offset); + canvas.DrawBox(barSize + (2 * offset)); std::set values; for (Range& range : colorRanges.GetRanges()) values.insert({ range.min, range.max }); @@ -350,8 +353,9 @@ void FastAerialTrainer::DrawBar( if (minValue < value && value < maxValue) { canvas.SetColor(GuiColorBorder); - canvas.SetPosition(barPos + Vector2F{ valueToPosition(value), 0 }); - canvas.FillBox(Vector2F{ 2, barSize.Y }); + auto start = barPos + Vector2F{ valueToPosition(value), 0 }; + auto end = start + Vector2F{ 0, barSize.Y }; + canvas.DrawLine(start, end, 2); // Line width `2` to match `DrawBox()`. } } From 49dc6cec5295fc65f10374d982353cc0d8792fea Mon Sep 17 00:00:00 2001 From: Josef Wittmann Date: Sun, 16 Mar 2025 19:55:09 +0100 Subject: [PATCH 04/13] Extract rendering range picker and don't modify RangeList directly --- FastAerialTrainer/FastAerialTrainer.cpp | 120 +++++++++------ FastAerialTrainer/FastAerialTrainer.h | 20 +-- FastAerialTrainer/FastAerialTrainerGUI.cpp | 161 ++++++++++----------- 3 files changed, 162 insertions(+), 139 deletions(-) diff --git a/FastAerialTrainer/FastAerialTrainer.cpp b/FastAerialTrainer/FastAerialTrainer.cpp index 77f977f..cd8e212 100644 --- a/FastAerialTrainer/FastAerialTrainer.cpp +++ b/FastAerialTrainer/FastAerialTrainer.cpp @@ -293,11 +293,11 @@ void FastAerialTrainer::DrawBar( LinearColor backgroundColor, RangeList& colorRanges ) { - if (colorRanges.GetRanges().empty()) + if (colorRanges.IsEmpty()) return; - float minValue = colorRanges.GetRanges().front().min; - float maxValue = colorRanges.GetRanges().back().max; + float minValue = colorRanges.GetTotalMin(); + float maxValue = colorRanges.GetTotalMax(); auto valueToPosition = [&](float value) { auto ratio = (value - minValue) / (maxValue - minValue); @@ -323,18 +323,7 @@ void FastAerialTrainer::DrawBar( } // Draw colored bar - if (value < minValue) - canvas.SetColor(*colorRanges.GetRanges().front().color); - else if (value >= maxValue) - canvas.SetColor(*colorRanges.GetRanges().back().color); - else - { - for (Range& range : colorRanges.GetRanges()) - { - if (range.min <= value && value < range.max) - canvas.SetColor(*range.color); - } - } + canvas.SetColor(*colorRanges.GetColorForValue(value)); canvas.SetPosition(barPos); canvas.FillBox(Vector2F{ valueToPosition(value), barSize.Y }); @@ -346,17 +335,16 @@ void FastAerialTrainer::DrawBar( canvas.SetPosition(barPos - offset); canvas.DrawBox(barSize + (2 * offset)); - std::set values; - for (Range& range : colorRanges.GetRanges()) values.insert({ range.min, range.max }); - for (float value : values) + auto values = colorRanges.GetValues(); + std::set uniqueValues = std::set(values.begin(), values.end()); + for (float value : uniqueValues) { - if (minValue < value && value < maxValue) - { - canvas.SetColor(GuiColorBorder); - auto start = barPos + Vector2F{ valueToPosition(value), 0 }; - auto end = start + Vector2F{ 0, barSize.Y }; - canvas.DrawLine(start, end, 2); // Line width `2` to match `DrawBox()`. - } + if (value <= minValue || maxValue <= value) continue; + + auto start = barPos + Vector2F{ valueToPosition(value), 0 }; + auto end = start + Vector2F{ 0, barSize.Y }; + canvas.SetColor(GuiColorBorder); + canvas.DrawLine(start, end, 2); // Line width `2` to match `DrawBox()`. } // Draw text @@ -494,9 +482,38 @@ void FastAerialTrainer::onUnload() RangeList::RangeList(std::vector values, std::vector colors) { if (colors.size() != values.size() - 1) + { LOG("Constructing RangeList: Number of values and colors don't match!"); + return; + } + + this->values = values; + this->colors = colors; +} +void RangeList::UpdateValues(std::vector values) +{ + if (values.size() != this->values.size()) + { + LOG("Updating RangeList: Number of values don't match!"); + return; + } + + this->values = values; +} +void RangeList::UpdateValue(int index, float value) +{ + if (index < 0 || index >= values.size()) return; + + float prevValue = index - 1 < 0 ? FLT_MIN : values[index - 1]; + float nextValue = index + 1 >= values.size() ? FLT_MAX : values[index + 1]; + + values[index] = std::clamp(value, prevValue, nextValue); +} +std::vector RangeList::GetRanges() +{ + std::vector ranges; - for (int i = 0; i < std::min(colors.size(), values.size() - 1); i++) + for (int i = 0; i < colors.size(); i++) { ranges.push_back( { @@ -506,33 +523,48 @@ RangeList::RangeList(std::vector values, std::vector colors } ); } -} -void RangeList::UpdateValues(std::vector values) + return ranges; +} +std::vector RangeList::GetValues() { - auto size = std::min(values.size(), ranges.size() + 1); - for (int i = 0; i < size; i++) - { - if (i > 0) ranges[i - 1].max = values[i]; - if (i < size - 1) ranges[i].min = values[i]; - } + return values; } -std::vector& RangeList::GetRanges() +bool RangeList::IsEmpty() { - return ranges; + return colors.empty(); +} +float RangeList::GetTotalMin() +{ + if (values.empty()) return 0; + return values.front(); } +float RangeList::GetTotalMax() +{ + if (values.empty()) return FLT_MAX; + return values.back(); +} +LinearColor* RangeList::GetColorForValue(float value) +{ + if (IsEmpty()) + return nullptr; + for (int i = 1; i < values.size(); i++) + { + if (value < values[i]) + return colors[i - 1]; + } + return colors.back(); +} std::string RangeList::ValuesToString() { std::string result; - if (ranges.empty()) - return result; - - result = std::to_string(ranges[0].min); - - for (auto& range : ranges) - result += "," + std::to_string(range.max); + for (int i = 0; i < values.size(); i++) + { + if (i > 0) result += ","; + result += std::to_string(values[i]); + } return result; } @@ -543,7 +575,9 @@ std::vector RangeList::SplitString(std::string str) std::string value; while (std::getline(stream, value, ',')) + { values.push_back(strtof(value.c_str(), NULL)); + } return values; } \ No newline at end of file diff --git a/FastAerialTrainer/FastAerialTrainer.h b/FastAerialTrainer/FastAerialTrainer.h index abbeb52..30ebf30 100644 --- a/FastAerialTrainer/FastAerialTrainer.h +++ b/FastAerialTrainer/FastAerialTrainer.h @@ -2,8 +2,6 @@ #include "GuiBase.h" #include "bakkesmod/plugin/bakkesmodplugin.h" -#include "bakkesmod/plugin/pluginwindow.h" -#include "bakkesmod/plugin/PluginSettingsWindow.h" #include "PersistentStorage.h" #include "version.h" @@ -44,18 +42,21 @@ struct InputHistoryItem class RangeList { private: - std::vector ranges; - // total min,max - // get color (or range) for value - // make sure it's always ordered - // iterate ranges - // get all stops (no duplicates) + std::vector values; + std::vector colors; public: RangeList(std::vector values, std::vector colors); void UpdateValues(std::vector values); - std::vector& GetRanges(); + void UpdateValue(int index, float value); + std::vector GetRanges(); + std::vector GetValues(); + + bool IsEmpty(); + float GetTotalMin(); + float GetTotalMax(); + LinearColor* GetColorForValue(float value); std::string ValuesToString(); static std::vector SplitString(std::string str); @@ -155,4 +156,5 @@ class FastAerialTrainer : public BakkesMod::Plugin::BakkesModPlugin, public Sett virtual void onUnload(); void RenderSettings() override; + void RenderRangePicker(RangeList& rangeList, const char* cvar, std::vector labels); }; diff --git a/FastAerialTrainer/FastAerialTrainerGUI.cpp b/FastAerialTrainer/FastAerialTrainerGUI.cpp index 11e6ff0..53d91dc 100644 --- a/FastAerialTrainer/FastAerialTrainerGUI.cpp +++ b/FastAerialTrainer/FastAerialTrainerGUI.cpp @@ -25,6 +25,13 @@ static bool PercentageSlider(const char* label, float& value, float max = 1.f) return retVal; } +static void SpacedSeparator() +{ + ImGui::Spacing(); + ImGui::Separator(); + ImGui::Spacing(); +} + void FastAerialTrainer::RenderSettings() { if (ImGui::Checkbox("Enable Plugin", &PluginEnabled)) @@ -64,93 +71,6 @@ void FastAerialTrainer::RenderSettings() if (ColorPicker("Failure Color", GuiColorFailure)) cvarManager->getCvar(GUI_COLOR_FAILURE).setValue(GuiColorFailure); - float speed = 0.1f; - const char* format = "%.1f ms"; - float width = 150; - float spacing = 20; - - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - ImGui::Text("First Jump Timing"); - ImGui::Spacing(); - { - auto& rangeList = JumpDurationRanges; - auto& ranges = rangeList.GetRanges(); - auto& cvar = GUI_JUMP_RANGES; - bool changed = false; - auto Input = [&](const char* label, float* value, float min, float max) - { - if (ImGui::DragFloat(label, value, speed, min, max, format)) - changed = true; - }; - - ImGui::PushID("FirstJumpTiming"); - ImGui::PushItemWidth(width); - - ImGui::BeginGroup(); - Input("Success Low", &ranges[1].max, ranges[0].max, ranges[2].max); - Input("Success High", &ranges[2].max, ranges[1].max, ranges[3].max); - ImGui::EndGroup(); ImGui::SameLine(0, spacing); ImGui::BeginGroup(); - Input("Warning Low", &ranges[0].max, ranges[0].min, ranges[1].max); - Input("Warning High", &ranges[3].max, ranges[2].max, ranges[4].max); - ImGui::EndGroup(); ImGui::SameLine(0, spacing); ImGui::BeginGroup(); - Input("Failure Low", &ranges[0].min, 0, ranges[0].max); - Input("Failure High", &ranges[4].max, ranges[3].max, FLT_MAX); - ImGui::EndGroup(); - - ImGui::Spacing(); - if (ImGui::Button("Reset to default")) - cvarManager->getCvar(cvar).ResetToDefault(); - - ImGui::PopItemWidth(); - ImGui::PopID(); - - if (changed) - cvarManager->getCvar(cvar).setValue(rangeList.ValuesToString()); - } - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - ImGui::Text("Double Jump Timing"); - ImGui::Spacing(); - { - auto& rangeList = DoubleJumpDurationRanges; - auto& ranges = rangeList.GetRanges(); - auto& cvar = GUI_DOUBLE_JUMP_RANGES; - bool changed = false; - auto Input = [&](const char* label, float* value, float min, float max) - { - if (ImGui::DragFloat(label, value, speed, min, max, format)) - changed = true; - }; - - ImGui::PushID("DoubleJumpTiming"); - ImGui::PushItemWidth(width); - - ImGui::BeginGroup(); - Input("Success Low", &ranges[0].min, 0, ranges[0].max); - Input("Success High", &ranges[0].max, ranges[0].min, ranges[1].max); - ImGui::EndGroup(); ImGui::SameLine(0, spacing); ImGui::BeginGroup(); - Input("Warning High", &ranges[1].max, ranges[0].max, ranges[2].max); - ImGui::EndGroup(); ImGui::SameLine(0, spacing); ImGui::BeginGroup(); - Input("Failure High", &ranges[2].max, ranges[1].max, FLT_MAX); - ImGui::EndGroup(); - - ImGui::Spacing(); - if (ImGui::Button("Reset to default")) - cvarManager->getCvar(cvar).ResetToDefault(); - - ImGui::PopItemWidth(); - ImGui::PopID(); - - if (changed) - cvarManager->getCvar(cvar).setValue(rangeList.ValuesToString()); - } - ImGui::Spacing(); - ImGui::Separator(); - ImGui::Spacing(); - if (ImGui::Checkbox("Draw Pitch History", &GuiDrawPitchHistory)) cvarManager->getCvar(GUI_DRAW_PITCH_HISTORY).setValue(GuiDrawPitchHistory); @@ -162,4 +82,71 @@ void FastAerialTrainer::RenderSettings() if (ImGui::Checkbox("Show First Input Warning in Custom Training", &GuiShowFirstInputWarning)) cvarManager->getCvar(GUI_SHOW_FIRST_INPUT_WARNING).setValue(GuiShowFirstInputWarning); + + SpacedSeparator(); + + ImGui::PushID("FirstJump"); + ImGui::Text("First Jump Timing"); + ImGui::Spacing(); + RenderRangePicker( + JumpDurationRanges, + GUI_JUMP_RANGES, + { + "Failure Low", + "Warning Low", + "Success Low", + "Success High", + "Warning High", + "Failure High" + } + ); + ImGui::PopID(); + + SpacedSeparator(); + + ImGui::PushID("DoubleJump"); + ImGui::Text("Double Jump Timing"); + ImGui::Spacing(); + RenderRangePicker( + DoubleJumpDurationRanges, + GUI_DOUBLE_JUMP_RANGES, + { + "Success Low", + "Success High", + "Warning High", + "Failure High" + } + ); + ImGui::PopID(); } + +void FastAerialTrainer::RenderRangePicker(RangeList& rangeList, const char* cvar, std::vector labels) +{ + float speed = 0.1f; + const char* format = "%.1f ms"; + float width = 200; + float spacing = 20; + + auto values = rangeList.GetValues(); + + ImGui::PushItemWidth(width); + + for (int i = 0; i < labels.size(); i++) + { + float value = values[i]; + float min = i - 1 < 0 ? 0 : values[i - 1]; + float max = i + 1 >= values.size() ? FLT_MAX : values[i + 1]; + + if (ImGui::DragFloat(labels[i], &value, speed, min, max, format)) + { + rangeList.UpdateValue(i, value); + cvarManager->getCvar(cvar).setValue(rangeList.ValuesToString()); + } + } + + ImGui::Spacing(); + if (ImGui::Button("Reset to default")) + cvarManager->getCvar(cvar).ResetToDefault(); + + ImGui::PopItemWidth(); +} \ No newline at end of file From d9800d62db42e017f5a6bd55fe4f874c96965f44 Mon Sep 17 00:00:00 2001 From: Josef Wittmann Date: Sun, 16 Mar 2025 20:00:01 +0100 Subject: [PATCH 05/13] Extract RangeList --- FastAerialTrainer/FastAerialTrainer.cpp | 103 --------------- FastAerialTrainer/FastAerialTrainer.h | 31 +---- FastAerialTrainer/FastAerialTrainer.vcxproj | 2 + .../FastAerialTrainer.vcxproj.filters | 6 + FastAerialTrainer/RangeList.cpp | 119 ++++++++++++++++++ FastAerialTrainer/RangeList.h | 36 ++++++ 6 files changed, 164 insertions(+), 133 deletions(-) create mode 100644 FastAerialTrainer/RangeList.cpp create mode 100644 FastAerialTrainer/RangeList.h diff --git a/FastAerialTrainer/FastAerialTrainer.cpp b/FastAerialTrainer/FastAerialTrainer.cpp index cd8e212..8d1c80c 100644 --- a/FastAerialTrainer/FastAerialTrainer.cpp +++ b/FastAerialTrainer/FastAerialTrainer.cpp @@ -478,106 +478,3 @@ void FastAerialTrainer::onUnload() { // nothing to unload... } - -RangeList::RangeList(std::vector values, std::vector colors) -{ - if (colors.size() != values.size() - 1) - { - LOG("Constructing RangeList: Number of values and colors don't match!"); - return; - } - - this->values = values; - this->colors = colors; -} -void RangeList::UpdateValues(std::vector values) -{ - if (values.size() != this->values.size()) - { - LOG("Updating RangeList: Number of values don't match!"); - return; - } - - this->values = values; -} -void RangeList::UpdateValue(int index, float value) -{ - if (index < 0 || index >= values.size()) return; - - float prevValue = index - 1 < 0 ? FLT_MIN : values[index - 1]; - float nextValue = index + 1 >= values.size() ? FLT_MAX : values[index + 1]; - - values[index] = std::clamp(value, prevValue, nextValue); -} -std::vector RangeList::GetRanges() -{ - std::vector ranges; - - for (int i = 0; i < colors.size(); i++) - { - ranges.push_back( - { - .min = values[i], - .max = values[i + 1], - .color = colors[i] - } - ); - } - - return ranges; -} -std::vector RangeList::GetValues() -{ - return values; -} -bool RangeList::IsEmpty() -{ - return colors.empty(); -} -float RangeList::GetTotalMin() -{ - if (values.empty()) return 0; - return values.front(); -} -float RangeList::GetTotalMax() -{ - if (values.empty()) return FLT_MAX; - return values.back(); -} -LinearColor* RangeList::GetColorForValue(float value) -{ - if (IsEmpty()) - return nullptr; - - for (int i = 1; i < values.size(); i++) - { - if (value < values[i]) - return colors[i - 1]; - } - return colors.back(); -} -std::string RangeList::ValuesToString() -{ - std::string result; - - for (int i = 0; i < values.size(); i++) - { - if (i > 0) result += ","; - result += std::to_string(values[i]); - } - - return result; -} -std::vector RangeList::SplitString(std::string str) -{ - std::vector values; - std::istringstream stream(str); - std::string value; - - while (std::getline(stream, value, ',')) - { - values.push_back(strtof(value.c_str(), NULL)); - } - - return values; -} \ No newline at end of file diff --git a/FastAerialTrainer/FastAerialTrainer.h b/FastAerialTrainer/FastAerialTrainer.h index 30ebf30..d082949 100644 --- a/FastAerialTrainer/FastAerialTrainer.h +++ b/FastAerialTrainer/FastAerialTrainer.h @@ -3,6 +3,7 @@ #include "GuiBase.h" #include "bakkesmod/plugin/bakkesmodplugin.h" #include "PersistentStorage.h" +#include "RangeList.h" #include "version.h" constexpr auto plugin_version = stringify(VERSION_MAJOR) "." stringify(VERSION_MINOR) "." stringify(VERSION_PATCH) "." stringify(VERSION_BUILD); @@ -25,13 +26,6 @@ constexpr auto GUI_DRAW_BOOST_HISTORY = "fast_aerial_trainer_gui_draw_boost_hist constexpr auto GUI_COLOR_HISTORY = "fast_aerial_trainer_gui_color_history"; constexpr auto GUI_SHOW_FIRST_INPUT_WARNING = "fast_aerial_trainer_gui_first_input_warning"; -struct Range -{ - float min; - float max; - LinearColor* color; -}; - struct InputHistoryItem { float pitch; @@ -39,29 +33,6 @@ struct InputHistoryItem bool jumped; }; -class RangeList -{ -private: - std::vector values; - std::vector colors; - -public: - RangeList(std::vector values, std::vector colors); - - void UpdateValues(std::vector values); - void UpdateValue(int index, float value); - std::vector GetRanges(); - std::vector GetValues(); - - bool IsEmpty(); - float GetTotalMin(); - float GetTotalMax(); - LinearColor* GetColorForValue(float value); - - std::string ValuesToString(); - static std::vector SplitString(std::string str); -}; - class FastAerialTrainer : public BakkesMod::Plugin::BakkesModPlugin, public SettingsWindowBase { std::shared_ptr persistentStorage; diff --git a/FastAerialTrainer/FastAerialTrainer.vcxproj b/FastAerialTrainer/FastAerialTrainer.vcxproj index bd5d31f..f14f264 100644 --- a/FastAerialTrainer/FastAerialTrainer.vcxproj +++ b/FastAerialTrainer/FastAerialTrainer.vcxproj @@ -111,6 +111,7 @@ + @@ -132,6 +133,7 @@ + diff --git a/FastAerialTrainer/FastAerialTrainer.vcxproj.filters b/FastAerialTrainer/FastAerialTrainer.vcxproj.filters index 044fddd..65174b2 100644 --- a/FastAerialTrainer/FastAerialTrainer.vcxproj.filters +++ b/FastAerialTrainer/FastAerialTrainer.vcxproj.filters @@ -75,6 +75,9 @@ Source Files + + Source Files + @@ -137,6 +140,9 @@ Header Files + + Header Files + diff --git a/FastAerialTrainer/RangeList.cpp b/FastAerialTrainer/RangeList.cpp new file mode 100644 index 0000000..e4a65e6 --- /dev/null +++ b/FastAerialTrainer/RangeList.cpp @@ -0,0 +1,119 @@ +#include "pch.h" +#include "RangeList.h" + +#include +#include +#include + +RangeList::RangeList(std::vector values, std::vector colors) +{ + if (colors.size() != values.size() - 1) + { + LOG("Constructing RangeList: Number of values and colors don't match!"); + return; + } + + this->values = values; + this->colors = colors; +} + +void RangeList::UpdateValues(std::vector values) +{ + if (values.size() != this->values.size()) + { + LOG("Updating RangeList: Number of values don't match!"); + return; + } + + this->values = values; +} + +void RangeList::UpdateValue(int index, float value) +{ + if (index < 0 || index >= values.size()) return; + + float prevValue = index - 1 < 0 ? FLT_MIN : values[index - 1]; + float nextValue = index + 1 >= values.size() ? FLT_MAX : values[index + 1]; + + values[index] = std::clamp(value, prevValue, nextValue); +} + +std::vector RangeList::GetRanges() +{ + std::vector ranges; + + for (int i = 0; i < colors.size(); i++) + { + ranges.push_back( + { + .min = values[i], + .max = values[i + 1], + .color = colors[i] + } + ); + } + + return ranges; +} + +std::vector RangeList::GetValues() +{ + return values; +} + +bool RangeList::IsEmpty() +{ + return colors.empty(); +} + +float RangeList::GetTotalMin() +{ + if (values.empty()) return 0; + return values.front(); +} + +float RangeList::GetTotalMax() +{ + if (values.empty()) return FLT_MAX; + return values.back(); +} + +LinearColor* RangeList::GetColorForValue(float value) +{ + if (IsEmpty()) + return nullptr; + + for (int i = 1; i < values.size(); i++) + { + if (value < values[i]) + return colors[i - 1]; + } + return colors.back(); +} + +std::string RangeList::ValuesToString() +{ + std::string result; + + for (int i = 0; i < values.size(); i++) + { + if (i > 0) result += ","; + result += std::to_string(values[i]); + } + + return result; +} + +std::vector RangeList::SplitString(std::string str) +{ + std::vector values; + std::istringstream stream(str); + std::string value; + + while (std::getline(stream, value, ',')) + { + values.push_back(strtof(value.c_str(), NULL)); + } + + return values; +} diff --git a/FastAerialTrainer/RangeList.h b/FastAerialTrainer/RangeList.h new file mode 100644 index 0000000..0442c83 --- /dev/null +++ b/FastAerialTrainer/RangeList.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include "bakkesmod/wrappers/wrapperstructs.h" + +struct Range +{ + float min; + float max; + LinearColor* color; +}; + +class RangeList +{ +private: + std::vector values; + std::vector colors; + +public: + RangeList(std::vector values, std::vector colors); + + void UpdateValues(std::vector values); + void UpdateValue(int index, float value); + std::vector GetRanges(); + std::vector GetValues(); + + bool IsEmpty(); + float GetTotalMin(); + float GetTotalMax(); + LinearColor* GetColorForValue(float value); + + std::string ValuesToString(); + static std::vector SplitString(std::string str); +}; From 175bc24dd0b09d058e545d20249a019a8fea02a9 Mon Sep 17 00:00:00 2001 From: Josef Wittmann Date: Sun, 16 Mar 2025 20:03:31 +0100 Subject: [PATCH 06/13] Add hover text to drag --- FastAerialTrainer/FastAerialTrainerGUI.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/FastAerialTrainer/FastAerialTrainerGUI.cpp b/FastAerialTrainer/FastAerialTrainerGUI.cpp index 53d91dc..0c9aa21 100644 --- a/FastAerialTrainer/FastAerialTrainerGUI.cpp +++ b/FastAerialTrainer/FastAerialTrainerGUI.cpp @@ -142,6 +142,8 @@ void FastAerialTrainer::RenderRangePicker(RangeList& rangeList, const char* cvar rangeList.UpdateValue(i, value); cvarManager->getCvar(cvar).setValue(rangeList.ValuesToString()); } + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Drag or ctrl-click to edit"); } ImGui::Spacing(); From 0b5b55904eca2abef84e9333d72a54585f0ddd4f Mon Sep 17 00:00:00 2001 From: Josef Wittmann Date: Sun, 16 Mar 2025 20:12:04 +0100 Subject: [PATCH 07/13] Show cvars in console --- FastAerialTrainer/FastAerialTrainer.cpp | 34 ++++++++++++------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/FastAerialTrainer/FastAerialTrainer.cpp b/FastAerialTrainer/FastAerialTrainer.cpp index 8d1c80c..2630b17 100644 --- a/FastAerialTrainer/FastAerialTrainer.cpp +++ b/FastAerialTrainer/FastAerialTrainer.cpp @@ -35,29 +35,39 @@ void FastAerialTrainer::onLoad() auto registerIntCvar = [this](std::string label, int& value) { - persistentStorage->RegisterPersistentCvar(label, std::to_string(value), "", false) + persistentStorage->RegisterPersistentCvar(label, std::to_string(value), "", true) .addOnValueChanged([&](std::string oldValue, CVarWrapper cvar) { value = cvar.getIntValue(); }); }; auto registerFloatCvar = [this](std::string label, float& value) { - persistentStorage->RegisterPersistentCvar(label, std::to_string(value), "", false) + persistentStorage->RegisterPersistentCvar(label, std::to_string(value), "", true) .addOnValueChanged([&](std::string oldValue, CVarWrapper cvar) { value = cvar.getFloatValue(); }); }; auto registerPercentCvar = [this](std::string label, float& value) { - persistentStorage->RegisterPersistentCvar(label, std::to_string(value), "", false, true, 0.0f, true, 1.0f) + persistentStorage->RegisterPersistentCvar(label, std::to_string(value), "", true, true, 0.0f, true, 1.0f) .addOnValueChanged([&](std::string oldValue, CVarWrapper cvar) { value = cvar.getFloatValue(); }); }; auto registerBoolCvar = [this](std::string label, bool& value) { - persistentStorage->RegisterPersistentCvar(label, std::to_string(value), "", false) + persistentStorage->RegisterPersistentCvar(label, std::to_string(value), "", true) .addOnValueChanged([&](std::string oldValue, CVarWrapper cvar) { value = cvar.getBoolValue(); }); }; auto registerColorCvar = [this](std::string label, LinearColor& value) { - persistentStorage->RegisterPersistentCvar(label, to_string(value), "", false) + persistentStorage->RegisterPersistentCvar(label, to_string(value), "", true) .addOnValueChanged([&](std::string oldValue, CVarWrapper cvar) { value = cvar.getColorValue(); }); }; + auto registerRangeListCvar = [this](std::string label, RangeList& rangeList) + { + persistentStorage->RegisterPersistentCvar(label, rangeList.ValuesToString(), "", true) + .addOnValueChanged([&](std::string oldValue, CVarWrapper cvar) + { + auto values = RangeList::SplitString(cvar.getStringValue()); + rangeList.UpdateValues(values); + } + ); + }; registerBoolCvar(PLUGIN_ENABLED, PluginEnabled); registerFloatCvar(RECORD_AFTER_DOUBLE_JUMP, RecordingAfterDoubleJump); @@ -74,18 +84,8 @@ void FastAerialTrainer::onLoad() registerColorCvar(GUI_COLOR_WARNING, GuiColorWarning); registerColorCvar(GUI_COLOR_FAILURE, GuiColorFailure); registerColorCvar(GUI_COLOR_HISTORY, GuiPitchHistoryColor); - persistentStorage->RegisterPersistentCvar(GUI_JUMP_RANGES, JumpDurationRanges.ValuesToString(), "", false) - .addOnValueChanged([&](std::string oldValue, CVarWrapper cvar) - { - auto values = RangeList::SplitString(cvar.getStringValue()); - JumpDurationRanges.UpdateValues(values); - }); - persistentStorage->RegisterPersistentCvar(GUI_DOUBLE_JUMP_RANGES, DoubleJumpDurationRanges.ValuesToString(), "", false) - .addOnValueChanged([&](std::string oldValue, CVarWrapper cvar) - { - auto values = RangeList::SplitString(cvar.getStringValue()); - DoubleJumpDurationRanges.UpdateValues(values); - }); + registerRangeListCvar(GUI_JUMP_RANGES, JumpDurationRanges); + registerRangeListCvar(GUI_DOUBLE_JUMP_RANGES, DoubleJumpDurationRanges); gameWrapper->RegisterDrawable( [this](CanvasWrapper canvas) From 1c7c9cbb8a86aa5528de2baba19dff66f5256d62 Mon Sep 17 00:00:00 2001 From: Josef Wittmann Date: Sun, 16 Mar 2025 20:32:34 +0100 Subject: [PATCH 08/13] Allow toggling different parts of the UI --- FastAerialTrainer/FastAerialTrainer.cpp | 85 +++++++++++++--------- FastAerialTrainer/FastAerialTrainer.h | 19 +++-- FastAerialTrainer/FastAerialTrainerGUI.cpp | 27 ++++--- 3 files changed, 83 insertions(+), 48 deletions(-) diff --git a/FastAerialTrainer/FastAerialTrainer.cpp b/FastAerialTrainer/FastAerialTrainer.cpp index 2630b17..af14a4a 100644 --- a/FastAerialTrainer/FastAerialTrainer.cpp +++ b/FastAerialTrainer/FastAerialTrainer.cpp @@ -75,8 +75,11 @@ void FastAerialTrainer::onLoad() registerPercentCvar(GUI_POSITION_RELATIVE_Y, GuiPositionRelative.Y); registerFloatCvar(GUI_SIZE, GuiSize); registerPercentCvar(GUI_PREVIEW_OPACTIY, GuiColorPreviewOpacity); - registerBoolCvar(GUI_DRAW_PITCH_HISTORY, GuiDrawPitchHistory); - registerBoolCvar(GUI_DRAW_BOOST_HISTORY, GuiDrawBoostHistory); + registerBoolCvar(GUI_SHOW_FIRST_JUMP, GuiShowFirstJump); + registerBoolCvar(GUI_SHOW_DOUBLE_JUMP, GuiShowDoubleJump); + registerBoolCvar(GUI_SHOW_PITCH_AMOUNT, GuiShowPitchAmount); + registerBoolCvar(GUI_DRAW_PITCH_HISTORY, GuiShowPitchHistory); + registerBoolCvar(GUI_DRAW_BOOST_HISTORY, GuiShowBoostHistory); registerBoolCvar(GUI_SHOW_FIRST_INPUT_WARNING, GuiShowFirstInputWarning); registerColorCvar(GUI_BORDER_COLOR, GuiColorBorder); registerColorCvar(GUI_BACKGROUND_COLOR, GuiColorBackground); @@ -260,31 +263,54 @@ void FastAerialTrainer::RenderCanvas(CanvasWrapper canvas) { ScreenSize = canvas.GetSize(); - DrawBar( - canvas, "Hold First Jump: ", HoldFirstJumpDuration * 1000, - GuiPosition(), BarSize(), - GuiColorBackground, JumpDurationRanges - ); + Vector2F position = GuiPosition(); - DrawBar( - canvas, "Time to Double Jump: ", TimeBetweenFirstAndDoubleJump * 1000, - GuiPosition() + Offset(), BarSize(), - GuiColorBackground, DoubleJumpDurationRanges - ); + if (GuiShowFirstJump) + { + DrawBar( + canvas, "Hold First Jump: ", HoldFirstJumpDuration * 1000, + position, BarSize(), + GuiColorBackground, JumpDurationRanges + ); + position += Offset(); + } - canvas.SetColor(GuiColorBorder); - canvas.SetPosition(GuiPosition() + (Offset() * 2)); - float JoystickBackDurationPercentage = !TotalRecordingDuration ? 0.f : 100.f * HoldingJoystickBackDuration / TotalRecordingDuration; - canvas.DrawString("Pitch Up Amount: " + toPrecision(JoystickBackDurationPercentage, 1) + "%", FontSize(), FontSize()); + if (GuiShowDoubleJump) + { + DrawBar( + canvas, "Time to Double Jump: ", TimeBetweenFirstAndDoubleJump * 1000, + position, BarSize(), + GuiColorBackground, DoubleJumpDurationRanges + ); + position += Offset(); + } + + if (GuiShowPitchAmount) + { + canvas.SetColor(GuiColorBorder); + canvas.SetPosition(position); + float JoystickBackDurationPercentage = !TotalRecordingDuration ? 0.f : 100.f * HoldingJoystickBackDuration / TotalRecordingDuration; + canvas.DrawString("Pitch Up Amount: " + toPrecision(JoystickBackDurationPercentage, 1) + "%", FontSize(), FontSize()); - if (GuiDrawPitchHistory) - DrawPitchHistory(canvas); + position += Offset() * 0.6f; + } - if (GuiDrawBoostHistory) - DrawBoostHistory(canvas); + if (GuiShowPitchHistory) + { + DrawPitchHistory(canvas, position); + position += Offset() * 1.2f; + } + + if (GuiShowBoostHistory) + { + DrawBoostHistory(canvas, position); + position += Offset() * 0.6f; + } if (GuiShowFirstInputWarning) - RenderFirstInputWarning(canvas); + { + RenderFirstInputWarning(canvas, position); + } } void FastAerialTrainer::DrawBar( @@ -360,11 +386,11 @@ static void DrawCenteredText(CanvasWrapper canvas, std::string text, float fontS canvas.DrawString(text, fontSize, fontSize); } -void FastAerialTrainer::DrawPitchHistory(CanvasWrapper& canvas) +void FastAerialTrainer::DrawPitchHistory(CanvasWrapper& canvas, Vector2F position) { float borderWidth = 2; float textWidth = 45 * FontSize(); - Vector2F topLeft = GuiPosition() + (Offset() * 2.6f) + Vector2F{ borderWidth, 0 }; + Vector2F topLeft = position + Vector2F{ borderWidth, 0 }; Vector2F innerBoxSize = Vector2F{ GuiSize, GuiSize / 10 }; canvas.SetColor(GuiColorBorder); @@ -422,12 +448,11 @@ void FastAerialTrainer::DrawPitchHistory(CanvasWrapper& canvas) } } -void FastAerialTrainer::DrawBoostHistory(CanvasWrapper& canvas) +void FastAerialTrainer::DrawBoostHistory(CanvasWrapper& canvas, Vector2F position) { float borderWidth = 2; float textWidth = 45 * FontSize(); - Vector2F offset = Offset() * (GuiDrawPitchHistory ? 3.8f : 2.6f); - Vector2F topLeft = GuiPosition() + offset + Vector2F{ borderWidth, 0 }; + Vector2F topLeft = position + Vector2F{ borderWidth, 0 }; Vector2F innerBoxSize = BarSize(); canvas.SetColor(GuiColorBorder); @@ -458,17 +483,11 @@ void FastAerialTrainer::DrawBoostHistory(CanvasWrapper& canvas) } } -void FastAerialTrainer::RenderFirstInputWarning(CanvasWrapper& canvas) +void FastAerialTrainer::RenderFirstInputWarning(CanvasWrapper& canvas, Vector2F position) { if (!gameWrapper->IsInCustomTraining()) return; if (TrainingStartTime >= HoldFirstJumpStartTime) return; - float offset = 2.6f; - if (GuiDrawPitchHistory) offset += 1.2f; - if (GuiDrawBoostHistory) offset += 0.6f; - - Vector2F position = GuiPosition() + Offset() * offset; - canvas.SetColor(GuiColorBorder); canvas.SetPosition(position); canvas.DrawString("Jump was not first input!", FontSize(), FontSize()); diff --git a/FastAerialTrainer/FastAerialTrainer.h b/FastAerialTrainer/FastAerialTrainer.h index d082949..5a58531 100644 --- a/FastAerialTrainer/FastAerialTrainer.h +++ b/FastAerialTrainer/FastAerialTrainer.h @@ -19,11 +19,14 @@ constexpr auto GUI_PREVIEW_OPACTIY = "fast_aerial_trainer_gui_preview_opacity"; constexpr auto GUI_COLOR_SUCCESS = "fast_aerial_trainer_gui_color_success"; constexpr auto GUI_COLOR_WARNING = "fast_aerial_trainer_gui_color_warning"; constexpr auto GUI_COLOR_FAILURE = "fast_aerial_trainer_gui_color_failure"; +constexpr auto GUI_COLOR_HISTORY = "fast_aerial_trainer_gui_color_history"; constexpr auto GUI_JUMP_RANGES = "fast_aerial_trainer_gui_jump_ranges"; constexpr auto GUI_DOUBLE_JUMP_RANGES = "fast_aerial_trainer_gui_double_jump_ranges"; +constexpr auto GUI_SHOW_FIRST_JUMP = "fast_aerial_trainer_gui_show_first_jump"; +constexpr auto GUI_SHOW_DOUBLE_JUMP = "fast_aerial_trainer_gui_show_double_jump"; +constexpr auto GUI_SHOW_PITCH_AMOUNT = "fast_aerial_trainer_gui_show_pitch_amount"; constexpr auto GUI_DRAW_PITCH_HISTORY = "fast_aerial_trainer_gui_draw_pitch_history"; constexpr auto GUI_DRAW_BOOST_HISTORY = "fast_aerial_trainer_gui_draw_boost_history"; -constexpr auto GUI_COLOR_HISTORY = "fast_aerial_trainer_gui_color_history"; constexpr auto GUI_SHOW_FIRST_INPUT_WARNING = "fast_aerial_trainer_gui_first_input_warning"; struct InputHistoryItem @@ -104,8 +107,12 @@ class FastAerialTrainer : public BakkesMod::Plugin::BakkesModPlugin, public Sett } ); LinearColor GuiPitchHistoryColor = LinearColor(240, 240, 240, 255); - bool GuiDrawPitchHistory = true; - bool GuiDrawBoostHistory = true; + + bool GuiShowFirstJump = true; + bool GuiShowDoubleJump = true; + bool GuiShowPitchAmount = true; + bool GuiShowPitchHistory = true; + bool GuiShowBoostHistory = true; bool GuiShowFirstInputWarning = true; @@ -119,9 +126,9 @@ class FastAerialTrainer : public BakkesMod::Plugin::BakkesModPlugin, public Sett void RenderCanvas(CanvasWrapper canvas); void DrawBar(CanvasWrapper& canvas, std::string text, float value, Vector2F barPos, Vector2F barSize, LinearColor backgroundColor, RangeList& colorRanges); - void DrawPitchHistory(CanvasWrapper& canvas); - void DrawBoostHistory(CanvasWrapper& canvas); - void RenderFirstInputWarning(CanvasWrapper& canvas); + void DrawPitchHistory(CanvasWrapper& canvas, Vector2F position); + void DrawBoostHistory(CanvasWrapper& canvas, Vector2F position); + void RenderFirstInputWarning(CanvasWrapper& canvas, Vector2F position); virtual void onLoad(); virtual void onUnload(); diff --git a/FastAerialTrainer/FastAerialTrainerGUI.cpp b/FastAerialTrainer/FastAerialTrainerGUI.cpp index 0c9aa21..27728bc 100644 --- a/FastAerialTrainer/FastAerialTrainerGUI.cpp +++ b/FastAerialTrainer/FastAerialTrainerGUI.cpp @@ -53,6 +53,24 @@ void FastAerialTrainer::RenderSettings() if (ImGui::IsItemHovered()) ImGui::SetTooltip("For clearest text use multiples of 350 pixels."); + if (ImGui::Checkbox("Show First Jump Timing", &GuiShowFirstJump)) + cvarManager->getCvar(GUI_SHOW_FIRST_JUMP).setValue(GuiShowFirstJump); + + if (ImGui::Checkbox("Show Double Jump Timing", &GuiShowDoubleJump)) + cvarManager->getCvar(GUI_SHOW_DOUBLE_JUMP).setValue(GuiShowDoubleJump); + + if (ImGui::Checkbox("Show Pitch Amount Text", &GuiShowPitchAmount)) + cvarManager->getCvar(GUI_SHOW_PITCH_AMOUNT).setValue(GuiShowPitchAmount); + + if (ImGui::Checkbox("Draw Pitch History", &GuiShowPitchHistory)) + cvarManager->getCvar(GUI_DRAW_PITCH_HISTORY).setValue(GuiShowPitchHistory); + + if (ImGui::Checkbox("Draw Boost History", &GuiShowBoostHistory)) + cvarManager->getCvar(GUI_DRAW_BOOST_HISTORY).setValue(GuiShowBoostHistory); + + if (ImGui::Checkbox("Show First Input Warning in Custom Training", &GuiShowFirstInputWarning)) + cvarManager->getCvar(GUI_SHOW_FIRST_INPUT_WARNING).setValue(GuiShowFirstInputWarning); + if (ColorPicker("Border and Text Color", GuiColorBorder)) cvarManager->getCvar(GUI_BORDER_COLOR).setValue(GuiColorBorder); @@ -71,18 +89,9 @@ void FastAerialTrainer::RenderSettings() if (ColorPicker("Failure Color", GuiColorFailure)) cvarManager->getCvar(GUI_COLOR_FAILURE).setValue(GuiColorFailure); - if (ImGui::Checkbox("Draw Pitch History", &GuiDrawPitchHistory)) - cvarManager->getCvar(GUI_DRAW_PITCH_HISTORY).setValue(GuiDrawPitchHistory); - - if (ImGui::Checkbox("Draw Boost History", &GuiDrawBoostHistory)) - cvarManager->getCvar(GUI_DRAW_BOOST_HISTORY).setValue(GuiDrawBoostHistory); - if (ColorPickerWithoutAlpha("Pitch/Boost History Color", GuiPitchHistoryColor)) cvarManager->getCvar(GUI_COLOR_HISTORY).setValue(GuiPitchHistoryColor); - if (ImGui::Checkbox("Show First Input Warning in Custom Training", &GuiShowFirstInputWarning)) - cvarManager->getCvar(GUI_SHOW_FIRST_INPUT_WARNING).setValue(GuiShowFirstInputWarning); - SpacedSeparator(); ImGui::PushID("FirstJump"); From c11d310f98bdcab7c0238fa83017eec544512cec Mon Sep 17 00:00:00 2001 From: Josef Wittmann Date: Sun, 16 Mar 2025 20:53:31 +0100 Subject: [PATCH 09/13] Add backdrop color --- FastAerialTrainer/FastAerialTrainer.cpp | 29 +++++++++++++--------- FastAerialTrainer/FastAerialTrainer.h | 6 +++-- FastAerialTrainer/FastAerialTrainerGUI.cpp | 5 +++- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/FastAerialTrainer/FastAerialTrainer.cpp b/FastAerialTrainer/FastAerialTrainer.cpp index af14a4a..89b29c8 100644 --- a/FastAerialTrainer/FastAerialTrainer.cpp +++ b/FastAerialTrainer/FastAerialTrainer.cpp @@ -83,6 +83,7 @@ void FastAerialTrainer::onLoad() registerBoolCvar(GUI_SHOW_FIRST_INPUT_WARNING, GuiShowFirstInputWarning); registerColorCvar(GUI_BORDER_COLOR, GuiColorBorder); registerColorCvar(GUI_BACKGROUND_COLOR, GuiColorBackground); + registerColorCvar(GUI_BACKDROP_COLOR, GuiColorBackdrop); registerColorCvar(GUI_COLOR_SUCCESS, GuiColorSuccess); registerColorCvar(GUI_COLOR_WARNING, GuiColorWarning); registerColorCvar(GUI_COLOR_FAILURE, GuiColorFailure); @@ -265,6 +266,14 @@ void FastAerialTrainer::RenderCanvas(CanvasWrapper canvas) Vector2F position = GuiPosition(); + { + Vector2F margin = { 0.02f, 0.04f }; + Vector2F size = Vector2F{ GuiSize, GuiHeight }; + canvas.SetPosition(position - (size * margin)); + canvas.SetColor(GuiColorBackdrop); + canvas.FillBox(size * ((margin * 2.f) + 1.f)); + } + if (GuiShowFirstJump) { DrawBar( @@ -307,10 +316,16 @@ void FastAerialTrainer::RenderCanvas(CanvasWrapper canvas) position += Offset() * 0.6f; } - if (GuiShowFirstInputWarning) + if (GuiShowFirstInputWarning && gameWrapper->IsInCustomTraining() && TrainingStartTime < HoldFirstJumpStartTime) { - RenderFirstInputWarning(canvas, position); + canvas.SetColor(GuiColorBorder); + canvas.SetPosition(position); + canvas.DrawString("Jump was not first input!", FontSize(), FontSize()); + + position += Offset() * 0.6f; } + + GuiHeight = position.Y - GuiPosition().Y; } void FastAerialTrainer::DrawBar( @@ -483,16 +498,6 @@ void FastAerialTrainer::DrawBoostHistory(CanvasWrapper& canvas, Vector2F positio } } -void FastAerialTrainer::RenderFirstInputWarning(CanvasWrapper& canvas, Vector2F position) -{ - if (!gameWrapper->IsInCustomTraining()) return; - if (TrainingStartTime >= HoldFirstJumpStartTime) return; - - canvas.SetColor(GuiColorBorder); - canvas.SetPosition(position); - canvas.DrawString("Jump was not first input!", FontSize(), FontSize()); -} - void FastAerialTrainer::onUnload() { // nothing to unload... diff --git a/FastAerialTrainer/FastAerialTrainer.h b/FastAerialTrainer/FastAerialTrainer.h index 5a58531..f4da1c5 100644 --- a/FastAerialTrainer/FastAerialTrainer.h +++ b/FastAerialTrainer/FastAerialTrainer.h @@ -15,6 +15,7 @@ constexpr auto GUI_POSITION_RELATIVE_Y = "fast_aerial_trainer_gui_pos_rel_y"; constexpr auto GUI_SIZE = "fast_aerial_trainer_gui_size"; constexpr auto GUI_BORDER_COLOR = "fast_aerial_trainer_gui_border_color"; constexpr auto GUI_BACKGROUND_COLOR = "fast_aerial_trainer_gui_background_color"; +constexpr auto GUI_BACKDROP_COLOR = "fast_aerial_trainer_gui_backdrop_color"; constexpr auto GUI_PREVIEW_OPACTIY = "fast_aerial_trainer_gui_preview_opacity"; constexpr auto GUI_COLOR_SUCCESS = "fast_aerial_trainer_gui_color_success"; constexpr auto GUI_COLOR_WARNING = "fast_aerial_trainer_gui_color_warning"; @@ -72,6 +73,7 @@ class FastAerialTrainer : public BakkesMod::Plugin::BakkesModPlugin, public Sett Vector2F GuiPositionRelative = { 0.5, 0.01 }; Vector2 ScreenSize = { 1920, 1080 }; float GuiSize = 700; + float GuiHeight = 0; // dynamically computed Vector2F BarSize() { return { GuiSize, GuiSize / 24.f }; } Vector2F Offset() { return { 0, GuiSize / 10.f }; } float FontSize() { return GuiSize / 350.f; } @@ -84,7 +86,8 @@ class FastAerialTrainer : public BakkesMod::Plugin::BakkesModPlugin, public Sett } float GuiColorPreviewOpacity = 0.2f; LinearColor GuiColorBorder = LinearColor(255, 255, 255, 255); - LinearColor GuiColorBackground = LinearColor(255, 255, 255, 128); + LinearColor GuiColorBackground = LinearColor(255, 255, 255, 127); + LinearColor GuiColorBackdrop = LinearColor(127, 127, 127, 0); LinearColor GuiColorSuccess = LinearColor(0, 0, 255, 210); LinearColor GuiColorWarning = LinearColor(255, 255, 0, 210); LinearColor GuiColorFailure = LinearColor(255, 0, 0, 210); @@ -128,7 +131,6 @@ class FastAerialTrainer : public BakkesMod::Plugin::BakkesModPlugin, public Sett void DrawBar(CanvasWrapper& canvas, std::string text, float value, Vector2F barPos, Vector2F barSize, LinearColor backgroundColor, RangeList& colorRanges); void DrawPitchHistory(CanvasWrapper& canvas, Vector2F position); void DrawBoostHistory(CanvasWrapper& canvas, Vector2F position); - void RenderFirstInputWarning(CanvasWrapper& canvas, Vector2F position); virtual void onLoad(); virtual void onUnload(); diff --git a/FastAerialTrainer/FastAerialTrainerGUI.cpp b/FastAerialTrainer/FastAerialTrainerGUI.cpp index 27728bc..5fb4540 100644 --- a/FastAerialTrainer/FastAerialTrainerGUI.cpp +++ b/FastAerialTrainer/FastAerialTrainerGUI.cpp @@ -74,9 +74,12 @@ void FastAerialTrainer::RenderSettings() if (ColorPicker("Border and Text Color", GuiColorBorder)) cvarManager->getCvar(GUI_BORDER_COLOR).setValue(GuiColorBorder); - if (ColorPicker("Background Color", GuiColorBackground)) + if (ColorPicker("Bar Background Color", GuiColorBackground)) cvarManager->getCvar(GUI_BACKGROUND_COLOR).setValue(GuiColorBackground); + if (ColorPicker("Backdrop Color", GuiColorBackdrop)) + cvarManager->getCvar(GUI_BACKDROP_COLOR).setValue(GuiColorBackdrop); + if (PercentageSlider("Color Preview Opacity", GuiColorPreviewOpacity, 0.5)) cvarManager->getCvar(GUI_PREVIEW_OPACTIY).setValue(GuiColorPreviewOpacity); From 4c9992efca9cc40a3cc2bf874a40cce196cc5b3e Mon Sep 17 00:00:00 2001 From: Josef Wittmann Date: Sun, 16 Mar 2025 22:42:39 +0100 Subject: [PATCH 10/13] Allow to show pitch down in history --- FastAerialTrainer/FastAerialTrainer.cpp | 118 +++++++++++++++------ FastAerialTrainer/FastAerialTrainer.h | 2 + FastAerialTrainer/FastAerialTrainerGUI.cpp | 3 + 3 files changed, 93 insertions(+), 30 deletions(-) diff --git a/FastAerialTrainer/FastAerialTrainer.cpp b/FastAerialTrainer/FastAerialTrainer.cpp index 89b29c8..2f91604 100644 --- a/FastAerialTrainer/FastAerialTrainer.cpp +++ b/FastAerialTrainer/FastAerialTrainer.cpp @@ -79,6 +79,7 @@ void FastAerialTrainer::onLoad() registerBoolCvar(GUI_SHOW_DOUBLE_JUMP, GuiShowDoubleJump); registerBoolCvar(GUI_SHOW_PITCH_AMOUNT, GuiShowPitchAmount); registerBoolCvar(GUI_DRAW_PITCH_HISTORY, GuiShowPitchHistory); + registerBoolCvar(GUI_SHOW_PITCH_DOWN_IN_HISTORY, GuiShowPitchDownInHistory); registerBoolCvar(GUI_DRAW_BOOST_HISTORY, GuiShowBoostHistory); registerBoolCvar(GUI_SHOW_FIRST_INPUT_WARNING, GuiShowFirstInputWarning); registerColorCvar(GUI_BORDER_COLOR, GuiColorBorder); @@ -243,7 +244,7 @@ void FastAerialTrainer::OnTick(CarWrapper car, ControllerInput* input) if (DoubleJumpPossible || InAfterDoubleJumpRecording) { float sensitivity = gameWrapper->GetSettings().GetGamepadSettings().AirControlSensitivity; - float intensity = std::min(1.f, sensitivity * input->Pitch); + float intensity = std::clamp(sensitivity * input->Pitch, -1.f, 1.f); float duration = now - LastTickTime; HoldingJoystickBackDuration += intensity * duration; TotalRecordingDuration += duration; @@ -422,44 +423,101 @@ void FastAerialTrainer::DrawPitchHistory(CanvasWrapper& canvas, Vector2F positio canvas.SetPosition(topLeft - borderWidth); canvas.DrawBox(innerBoxSize + (2 * borderWidth)); - int size = (int)InputHistory.size(); - int i = 0; - InputHistoryItem previousInput{}; - for (InputHistoryItem currentInput : InputHistory) + if (GuiShowPitchDownInHistory) { - if (i > 0) + canvas.SetColor(GuiColorBorder); + Vector2F start = topLeft - Vector2F{ borderWidth / 2.f, 0.f } + innerBoxSize * Vector2F{ 0.f, 0.5f }; + Vector2F end = start + Vector2F{ borderWidth, 0.f } + innerBoxSize * Vector2F{ 1.f, 0.f }; + canvas.DrawLine(start, end, borderWidth); + } + + // Note: `FillTriangle` ignores transparency, so we can only use opaque colors here. + canvas.SetColor(GuiPitchHistoryColor); + auto FillTriangle = [&](Vector2F p1, Vector2F p2, Vector2F p3) + { + canvas.FillTriangle( + p1 * innerBoxSize + topLeft, + p2 * innerBoxSize + topLeft, + p3 * innerBoxSize + topLeft + ); + }; + auto FillBox = [&](Vector2F p1, Vector2F p2) { - float startX = (float)(i - 1) / (size - 1); - float endX = (float)i / (size - 1); + canvas.SetPosition(p1 * innerBoxSize + topLeft); + canvas.FillBox((p2 - p1) * innerBoxSize); + }; + int historySize = (int)InputHistory.size(); + for (int i = 1; i < historySize; i++) + { + auto& previousInput = InputHistory[i - 1]; + auto& currentInput = InputHistory[i]; + + // Which value should represent zero in the graph. + // All x,y values are between 0 and 1, where (0,0) is top left. + float zero; + float startX = (float)(i - 1) / (historySize - 1); + float endX = (float)i / (historySize - 1); + float startY; + float endY; + + if (GuiShowPitchDownInHistory) + { + zero = 0.5f; + startY = 0.5f * (1.f - previousInput.pitch); + endY = 0.5f * (1.f - currentInput.pitch); + } + else + { + zero = 1.f; + startY = 1.f - std::clamp(previousInput.pitch, 0.f, 1.f); + endY = 1.f - std::clamp(currentInput.pitch, 0.f, 1.f); + } - float startY = 1.f - std::clamp(previousInput.pitch, 0.f, 1.f); - float endY = 1.f - std::clamp(currentInput.pitch, 0.f, 1.f); + Vector2F start = Vector2F{ startX, startY }; + Vector2F end = Vector2F{ endX, endY }; + if (startY < zero && endY < zero) // Above zero + { // Draw a right triangle from `start` to `end` and a rectangle beneath it. - Vector2F start = innerBoxSize * Vector2F{ startX, startY }; - Vector2F end = innerBoxSize * Vector2F{ endX, endY }; - Vector2F base = start.Y > end.Y ? Vector2F{ end.X, start.Y } : Vector2F{ start.X, end.Y }; - - canvas.SetColor(GuiPitchHistoryColor); // Note: `FillTriangle` ignores transparency. - canvas.FillTriangle(base + topLeft, start + topLeft, end + topLeft); - canvas.SetPosition(Vector2F{ start.X, std::max(start.Y, end.Y) } + topLeft); - canvas.FillBox(Vector2F{ end.X - start.X, innerBoxSize.Y - std::max(start.Y, end.Y) }); + Vector2F base = startY > endY ? Vector2F{ endX, startY } : Vector2F{ startX, endY }; + + FillTriangle(base, start, end); + FillBox(Vector2F{ startX, std::max(startY, endY) }, Vector2F{ endX, zero }); } - previousInput = currentInput; - i++; - } + else if (startY > zero && endY > zero) // Below zero + { + // Draw a right triangle from `start` to `end` and a rectangle above it. + Vector2F base = startY < endY ? Vector2F{ endX, startY } : Vector2F{ startX, endY }; - i = 0; - for (InputHistoryItem input : InputHistory) - { - if (input.jumped) + FillTriangle(base, start, end); + FillBox(Vector2F{ startX, zero }, Vector2F{ endX, std::min(startY, endY) }); + } + else // Through zero { - canvas.SetColor(GuiColorBorder); - Vector2F start = topLeft + Vector2F{ (float)i / size * innerBoxSize.X, 0.f }; - Vector2F end = start + Vector2F{ 0, innerBoxSize.Y }; - canvas.DrawLine(start, start + Vector2F{ 0,innerBoxSize.Y }, 2 * borderWidth); + // Draw two right triangles from `start` to `end`. + auto slope = (endY - startY) / (endX - startX); + auto centerX = startX + (zero - startY) / slope; + + Vector2F baseLeft = Vector2F{ startX, zero }; + Vector2F baseCenter = Vector2F{ centerX , zero }; + Vector2F baseRight = Vector2F{ endX, zero }; + + FillTriangle(start, baseLeft, baseCenter); + FillTriangle(end, baseRight, baseCenter); } - i++; + } + + for (int i = 0; i < historySize; i++) + { + auto& input = InputHistory[i]; + + if (!input.jumped) + continue; + + canvas.SetColor(GuiColorBorder); + Vector2F start = topLeft + Vector2F{ (float)i / historySize * innerBoxSize.X, 0.f }; + Vector2F end = start + Vector2F{ 0, innerBoxSize.Y }; + canvas.DrawLine(start, end, 2 * borderWidth); } } diff --git a/FastAerialTrainer/FastAerialTrainer.h b/FastAerialTrainer/FastAerialTrainer.h index f4da1c5..7e1d036 100644 --- a/FastAerialTrainer/FastAerialTrainer.h +++ b/FastAerialTrainer/FastAerialTrainer.h @@ -27,6 +27,7 @@ constexpr auto GUI_SHOW_FIRST_JUMP = "fast_aerial_trainer_gui_show_first_jump"; constexpr auto GUI_SHOW_DOUBLE_JUMP = "fast_aerial_trainer_gui_show_double_jump"; constexpr auto GUI_SHOW_PITCH_AMOUNT = "fast_aerial_trainer_gui_show_pitch_amount"; constexpr auto GUI_DRAW_PITCH_HISTORY = "fast_aerial_trainer_gui_draw_pitch_history"; +constexpr auto GUI_SHOW_PITCH_DOWN_IN_HISTORY = "fast_aerial_trainer_gui_show_pitch_down_in_history"; constexpr auto GUI_DRAW_BOOST_HISTORY = "fast_aerial_trainer_gui_draw_boost_history"; constexpr auto GUI_SHOW_FIRST_INPUT_WARNING = "fast_aerial_trainer_gui_first_input_warning"; @@ -115,6 +116,7 @@ class FastAerialTrainer : public BakkesMod::Plugin::BakkesModPlugin, public Sett bool GuiShowDoubleJump = true; bool GuiShowPitchAmount = true; bool GuiShowPitchHistory = true; + bool GuiShowPitchDownInHistory = false; bool GuiShowBoostHistory = true; bool GuiShowFirstInputWarning = true; diff --git a/FastAerialTrainer/FastAerialTrainerGUI.cpp b/FastAerialTrainer/FastAerialTrainerGUI.cpp index 5fb4540..e3119dd 100644 --- a/FastAerialTrainer/FastAerialTrainerGUI.cpp +++ b/FastAerialTrainer/FastAerialTrainerGUI.cpp @@ -65,6 +65,9 @@ void FastAerialTrainer::RenderSettings() if (ImGui::Checkbox("Draw Pitch History", &GuiShowPitchHistory)) cvarManager->getCvar(GUI_DRAW_PITCH_HISTORY).setValue(GuiShowPitchHistory); + if (ImGui::Checkbox("Show Pitch Down in History", &GuiShowPitchDownInHistory)) + cvarManager->getCvar(GUI_SHOW_PITCH_DOWN_IN_HISTORY).setValue(GuiShowPitchDownInHistory); + if (ImGui::Checkbox("Draw Boost History", &GuiShowBoostHistory)) cvarManager->getCvar(GUI_DRAW_BOOST_HISTORY).setValue(GuiShowBoostHistory); From a947b3dcdae2f922129d3ddbb86fd2e0a831556d Mon Sep 17 00:00:00 2001 From: Josef Wittmann Date: Sun, 16 Mar 2025 23:12:00 +0100 Subject: [PATCH 11/13] Treat dodging like double-jumping This makes sense for accidential flipping, since one can still see the timing for the jump and pitch inputs afterwards. --- FastAerialTrainer/FastAerialTrainer.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/FastAerialTrainer/FastAerialTrainer.cpp b/FastAerialTrainer/FastAerialTrainer.cpp index 2f91604..82a564b 100644 --- a/FastAerialTrainer/FastAerialTrainer.cpp +++ b/FastAerialTrainer/FastAerialTrainer.cpp @@ -141,10 +141,7 @@ void FastAerialTrainer::onLoad() } ); - // Double jump - gameWrapper->HookEventWithCaller( - "Function CarComponent_DoubleJump_TA.Active.BeginState", - [this](CarComponentWrapper component, void* params, std::string eventName) + auto OnSecondJump = [this](CarComponentWrapper component) { if (IsInReplay || !IsActive()) return; if (!IsLocalCar(component.GetCar())) return; @@ -154,6 +151,24 @@ void FastAerialTrainer::onLoad() DoubleJumpPressedTime = GetCurrentTime(); DoubleJumpPossible = false; + }; + + // Double jump + gameWrapper->HookEventWithCaller( + "Function CarComponent_DoubleJump_TA.Active.BeginState", + [this, OnSecondJump](CarComponentWrapper component, ...) + { + OnSecondJump(component); + } + ); + + // Dodge + gameWrapper->HookEventWithCaller( + "Function CarComponent_Dodge_TA.Active.BeginState", + [this, OnSecondJump](CarComponentWrapper component, ...) + { + // Treat dodging the same as double-jumping, since players might dodge by accident. + OnSecondJump(component); } ); From 6ddf1a1c167346592aed1fb6a006c99bb0ae7a9a Mon Sep 17 00:00:00 2001 From: Josef Wittmann Date: Sun, 16 Mar 2025 23:20:41 +0100 Subject: [PATCH 12/13] Calculate pitch up only between jumps --- FastAerialTrainer/FastAerialTrainer.cpp | 19 ++++++++++--------- FastAerialTrainer/FastAerialTrainer.h | 5 ++--- FastAerialTrainer/FastAerialTrainerGUI.cpp | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/FastAerialTrainer/FastAerialTrainer.cpp b/FastAerialTrainer/FastAerialTrainer.cpp index 82a564b..5e04449 100644 --- a/FastAerialTrainer/FastAerialTrainer.cpp +++ b/FastAerialTrainer/FastAerialTrainer.cpp @@ -133,10 +133,9 @@ void FastAerialTrainer::onLoad() DoubleJumpPossible = true; DoubleJumpPressedTime = 0; TimeBetweenFirstAndDoubleJump = 0; - TotalRecordingDuration = 0; - HoldingJoystickBackDuration = 0; + TicksBetweenJumps = 0; + PitchUpBetweenJumps = 0; - LastTickTime = now; InputHistory.clear(); } ); @@ -260,12 +259,14 @@ void FastAerialTrainer::OnTick(CarWrapper car, ControllerInput* input) { float sensitivity = gameWrapper->GetSettings().GetGamepadSettings().AirControlSensitivity; float intensity = std::clamp(sensitivity * input->Pitch, -1.f, 1.f); - float duration = now - LastTickTime; - HoldingJoystickBackDuration += intensity * duration; - TotalRecordingDuration += duration; - LastTickTime = now; InputHistory.push_back({ intensity, (bool)input->HoldingBoost, (bool)input->Jumped }); + + if (DoubleJumpPossible) + { + TicksBetweenJumps += 1; + PitchUpBetweenJumps += intensity; + } } } @@ -314,8 +315,8 @@ void FastAerialTrainer::RenderCanvas(CanvasWrapper canvas) { canvas.SetColor(GuiColorBorder); canvas.SetPosition(position); - float JoystickBackDurationPercentage = !TotalRecordingDuration ? 0.f : 100.f * HoldingJoystickBackDuration / TotalRecordingDuration; - canvas.DrawString("Pitch Up Amount: " + toPrecision(JoystickBackDurationPercentage, 1) + "%", FontSize(), FontSize()); + float PitchUpBetweenJumpsAmount = TicksBetweenJumps == 0 ? 0 : PitchUpBetweenJumps / TicksBetweenJumps; + canvas.DrawString("Pitch Up Between Jumps: " + toPrecision(100 * PitchUpBetweenJumpsAmount, 1) + "%", FontSize(), FontSize()); position += Offset() * 0.6f; } diff --git a/FastAerialTrainer/FastAerialTrainer.h b/FastAerialTrainer/FastAerialTrainer.h index 7e1d036..7f501ef 100644 --- a/FastAerialTrainer/FastAerialTrainer.h +++ b/FastAerialTrainer/FastAerialTrainer.h @@ -60,10 +60,9 @@ class FastAerialTrainer : public BakkesMod::Plugin::BakkesModPlugin, public Sett bool DoubleJumpPossible = false; float DoubleJumpPressedTime = 0; float TimeBetweenFirstAndDoubleJump = 0; - float HoldingJoystickBackDuration = 0; - float TotalRecordingDuration = 0; + float PitchUpBetweenJumps = 0; + float TicksBetweenJumps = 0; - float LastTickTime = 0; std::vector InputHistory; float TrainingStartTime = 0; diff --git a/FastAerialTrainer/FastAerialTrainerGUI.cpp b/FastAerialTrainer/FastAerialTrainerGUI.cpp index e3119dd..fdac29a 100644 --- a/FastAerialTrainer/FastAerialTrainerGUI.cpp +++ b/FastAerialTrainer/FastAerialTrainerGUI.cpp @@ -59,7 +59,7 @@ void FastAerialTrainer::RenderSettings() if (ImGui::Checkbox("Show Double Jump Timing", &GuiShowDoubleJump)) cvarManager->getCvar(GUI_SHOW_DOUBLE_JUMP).setValue(GuiShowDoubleJump); - if (ImGui::Checkbox("Show Pitch Amount Text", &GuiShowPitchAmount)) + if (ImGui::Checkbox("Show Pitch Up Amount Between Jumps", &GuiShowPitchAmount)) cvarManager->getCvar(GUI_SHOW_PITCH_AMOUNT).setValue(GuiShowPitchAmount); if (ImGui::Checkbox("Draw Pitch History", &GuiShowPitchHistory)) From 395746775e2be56ff87660d4464c248bdaf70ee3 Mon Sep 17 00:00:00 2001 From: Josef Wittmann Date: Sun, 16 Mar 2025 23:25:56 +0100 Subject: [PATCH 13/13] Release 2.5.0 --- CHANGELOG.md | 10 ++++++++++ FastAerialTrainer/version.h | 4 ++-- README.md | 3 +-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ca555..3a0fad0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 2.5.0 + +- Allow configuring timings +- Show pitch down in history (as an option) +- Also record inputs after dodging (to also see timings/inputs when backflipping) +- Calculate pitch up amount only between jumps +- Allow toggling different parts of the UI +- Draw a backdrop behind to whole UI (fully opaque by default) +- Show cvars in console + ## 2.4.0 - Split pitch and boost history diff --git a/FastAerialTrainer/version.h b/FastAerialTrainer/version.h index c3938fe..e1b36ae 100644 --- a/FastAerialTrainer/version.h +++ b/FastAerialTrainer/version.h @@ -1,7 +1,7 @@ #pragma once #define VERSION_MAJOR 2 -#define VERSION_MINOR 4 -#define VERSION_PATCH 1 +#define VERSION_MINOR 5 +#define VERSION_PATCH 0 #define VERSION_BUILD 1 #define stringify(a) stringify_(a) diff --git a/README.md b/README.md index 8b31964..ed5e79a 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Faster is better. ### Pitch Up Amount -Measures how good your pitch back input was. It takes into account aerial sensitivity. +Measures your pitch back input between leaving the ground and double-jumping. It takes into account aerial sensitivity. Higher is better: 100% means you pitched upwards fully. 50% could mean you pitched back half for the whole duration or you pitched back fully for half the time. ### Pitch History @@ -59,7 +59,6 @@ A nice training pack to check your progress on fast aerials is ["Fast Aerial Tes Having an issue with the plugin? Found a bug? Missing a feature? Open a new issue on GitHub and I'll look into it. - ## Changelog See [CHANGELOG.md](./CHANGELOG.md).