diff --git a/src/settings/SettingsIO.cpp b/src/settings/SettingsIO.cpp index 744a06c3..544ef471 100644 --- a/src/settings/SettingsIO.cpp +++ b/src/settings/SettingsIO.cpp @@ -22,7 +22,7 @@ bool settings::SettingsWriter::saveTo(std::string path, bool autosave) { logging::Info("cat_save: Saving to %s", path.c_str()); } - + this->only_changed = true; stream.open(path, std::ios::out); diff --git a/src/visual/CMakeLists.txt b/src/visual/CMakeLists.txt index e8a375f3..837cba12 100755 --- a/src/visual/CMakeLists.txt +++ b/src/visual/CMakeLists.txt @@ -11,8 +11,13 @@ set(files "${CMAKE_CURRENT_LIST_DIR}/atlas.cpp" target_sources(cathook PRIVATE ${files}) list(REMOVE_ITEM ignore_files ${files}) set(ignore_files ${ignore_files} CACHE INTERNAL "") + if(EnableGUI) add_subdirectory(menu) + set(files "${CMAKE_CURRENT_LIST_DIR}/GUISnow.cpp") + target_sources(cathook PRIVATE ${files}) + list(REMOVE_ITEM ignore_files ${files}) + set(ignore_files ${ignore_files} CACHE INTERNAL "") endif() if (EnableImGuiDrawing) diff --git a/src/visual/GUISnow.cpp b/src/visual/GUISnow.cpp new file mode 100644 index 00000000..45a2bef6 --- /dev/null +++ b/src/visual/GUISnow.cpp @@ -0,0 +1,197 @@ + +#include "common.hpp" +#include "drawing.hpp" +#include "Menu.hpp" +#include "GuiInterface.hpp" + +namespace snowflakes +{ + +// Control variables +// Base Speed the flakes spawn at +static settings::Float min_snowflake_speed_x{ "visual.snowflakes.min-speed.x", "0.0f" }; +static settings::Float max_snowflake_speed_x{ "visual.snowflakes.max-speed.x", "00.0f" }; +static settings::Float min_snowflake_speed_y{ "visual.snowflakes.min-speed.y", "10.0f" }; +static settings::Float max_snowflake_speed_y{ "visual.snowflakes.max-speed.y", "150.0f" }; + +// Speed Variance +static settings::Float snowflake_speed_deviation_x{ "visual.snowflakes.speed-variance.x", "5.0f" }; +static settings::Float snowflake_speed_deviation_y{ "visual.snowflakes.speed-variance.y", "30.0f" }; + +// Snowflake size +static settings::Float snowflake_size{ "visual.snowflakes.size", "16.0f" }; + +// Snowflake Amount +static settings::Int snowflake_amount{ "visual.snowflakes.count", "150" }; + +// Static timer to mark start of starting cathook. We use this to avoid a high floating point inprecision on timers +static std::chrono::milliseconds start_ms = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + +// Snowflake class +class Snowflake +{ + // Position + float x{}; + float y{}; + // Fall speed (pixel/s) + float x_speed{}; + float y_speed{}; + // Fall speed variation (in pixels) + float x_speed_variation{}; + float y_speed_variation{}; + // Neccessary to make snowflakes not depend on frames + float last_update_time{}; + // Static timer to mark start of spawning the flake. We use this to avoid a high floating point inprecision on timers + std::chrono::milliseconds start_ms = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + +public: + // Needed for std::find + constexpr bool operator==(const Snowflake &rhs) const + { + // Ignore the warnings, this is perfectly valid syntax in this case + return x == rhs.x && y == rhs.y && x_speed == rhs.x_speed && x_speed_variation == rhs.x_speed_variation && y_speed == rhs.y_speed && y_speed_variation == rhs.y_speed_variation; + } + + // Init the snowflake + void Init(float x, float y, float x_speed, float y_speed, float x_speed_variation = 0.0f, float y_speed_variation = 0.0f) + { + // Starting point + this->x = x; + this->y = y; + // Base fall speed (pixel/s) + this->x_speed = x_speed; + this->y_speed = y_speed; + // Variation amount (pixel/s) + this->x_speed_variation = x_speed_variation; + this->y_speed_variation = y_speed_variation; + + // Update time + std::chrono::milliseconds ms = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()) - start_ms; + auto cur_time = ms.count() / 1000.0f; + this->last_update_time = cur_time; + } + // Update x and y coords + void Update(std::chrono::milliseconds current_time) + { + // Every snowflake has it's own one of these, so use it. + current_time -= start_ms; + auto cur_time = current_time.count() / 1000.0f; + float variation_x, variation_y; + + // Update speed + + // We want a smooth variation, so let's use sine/cosine + variation_x = (sin(DEG2RAD(cur_time * 180.0f)) * x_speed_variation); + // The +1 and /2.0f is so snowflakes don't just freeze and stop moving + variation_y = (cos(DEG2RAD(cur_time * 180.0f) + 1) * y_speed_variation / 2.0f); + + // Move this speed in pixels/s + this->x += (x_speed + variation_x) * (cur_time - this->last_update_time); + this->y += (y_speed + variation_y) * (cur_time - this->last_update_time); + + // Update last draw time accordingly + this->last_update_time = cur_time; + } + // Draw the Snowflake + void Draw() const + { + draw::RectangleTextured(this->x, this->y, *snowflake_size, *snowflake_size, colors::white, textures::atlas().texture, 257, 0, 16, 16, 0.0f); + } + // Is the Snowflake off-screen? + bool IsOffScreen() const + { + // We don't care too much about snowflakes going off of the left/right of the screen + return y - *snowflake_size > draw::height || y < -10.0f; + } +}; + +// Snowflake Manager +class SnowflakeManager +{ + std::vector snowflakes; + + // Remove a(n off-screen) snowflake + void RemoveSnowflake(Snowflake &flake) + { + auto pos = std::find(snowflakes.begin(), snowflakes.end(), flake); + if (pos != snowflakes.end()) + snowflakes.erase(pos); + } + +public: + // Add a Snowflake to the vector + void AddSnowflake() + { + snowflakes.resize(snowflakes.size() + 1); + Snowflake &flake = snowflakes[snowflakes.size() - 1]; + + // Random position at top of screen + float flake_x = RandFloatRange(0.0f, draw::width); + float flake_y = 0.0f; + + // Random Speed + float flake_speed_x = RandFloatRange(*min_snowflake_speed_x, *max_snowflake_speed_x); + float flake_speed_y = RandFloatRange(*min_snowflake_speed_y, *max_snowflake_speed_y); + + // Init flake + flake.Init(flake_x, flake_y, flake_speed_x, flake_speed_y, *snowflake_speed_deviation_x, *snowflake_speed_deviation_y); + } + + // Update and Draw the snowflakes + void UpdateAndDraw() + { + std::chrono::milliseconds ms = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); + + // We want to update snowflakes regardless if we're in the menu or not + for (auto &flake : snowflakes) + { + flake.Update(ms); + // Remove offscreen flakes + if (flake.IsOffScreen()) + RemoveSnowflake(flake); + // Invalid menu/Not in Menu + else if (zerokernel::Menu::instance && !zerokernel::Menu::instance->isInGame()) + flake.Draw(); + } + if (snowflakes.size() < static_cast(*snowflake_amount)) + AddSnowflake(); + } + // Clear all snowflakes + void Clear() + { + snowflakes.clear(); + } +}; + +static SnowflakeManager snow_manager; +void DrawSnowflakes() +{ + snow_manager.UpdateAndDraw(); +} + +template void rvarCallback(settings::VariableBase &, T) +{ + snow_manager.Clear(); +} + +static InitRoutine init([]() { + time_t theTime = time(nullptr); + struct tm *aTime = localtime(&theTime); + + int day = aTime->tm_mday; + int month = aTime->tm_mon + 1; // Month is 0 - 11, add 1 to get a jan-dec 1-12 concept + + // We only want to draw around christmas time, let's use 12th of december+ til 12th of january + if ((month == 12 && day >= 12) || (month == 1 && day <= 12)) + { + // Very early to not draw over the menu + EC::Register(EC::Draw, DrawSnowflakes, "draw_snowflakes", EC::ec_priority::very_early); + min_snowflake_speed_x.installChangeCallback(rvarCallback); + max_snowflake_speed_x.installChangeCallback(rvarCallback); + min_snowflake_speed_y.installChangeCallback(rvarCallback); + max_snowflake_speed_y.installChangeCallback(rvarCallback); + snowflake_speed_deviation_x.installChangeCallback(rvarCallback); + snowflake_speed_deviation_y.installChangeCallback(rvarCallback); + } +}); +} // namespace snowflakes