#pragma once #include #include #include #include #include #include #include #include #include #include namespace hex { class AchievementManager; class Achievement { public: explicit Achievement(std::string unlocalizedCategory, std::string unlocalizedName) : m_unlocalizedCategory(std::move(unlocalizedCategory)), m_unlocalizedName(std::move(unlocalizedName)) { } [[nodiscard]] const std::string &getUnlocalizedName() const { return this->m_unlocalizedName; } [[nodiscard]] const std::string &getUnlocalizedCategory() const { return this->m_unlocalizedCategory; } [[nodiscard]] bool isUnlocked() const { return this->m_progress == this->m_maxProgress; } Achievement& setDescription(std::string description) { this->m_unlocalizedDescription = std::move(description); return *this; } Achievement& addRequirement(std::string requirement) { this->m_requirements.emplace_back(std::move(requirement)); return *this; } Achievement& addVisibilityRequirement(std::string requirement) { this->m_visibilityRequirements.emplace_back(std::move(requirement)); return *this; } Achievement& setBlacked() { this->m_blacked = true; return *this; } Achievement& setInvisible() { this->m_invisible = true; return *this; } [[nodiscard]] bool isBlacked() const { return this->m_blacked; } [[nodiscard]] bool isInvisible() const { return this->m_invisible; } [[nodiscard]] const std::vector &getRequirements() const { return this->m_requirements; } [[nodiscard]] const std::vector &getVisibilityRequirements() const { return this->m_visibilityRequirements; } [[nodiscard]] const std::string &getUnlocalizedDescription() const { return this->m_unlocalizedDescription; } [[nodiscard]] const ImGui::Texture &getIcon() const { if (this->m_iconData.empty()) return this->m_icon; if (this->m_icon.isValid()) return m_icon; this->m_icon = ImGui::Texture(reinterpret_cast(this->m_iconData.data()), this->m_iconData.size()); return this->m_icon; } Achievement& setIcon(std::span data) { this->m_iconData.reserve(data.size()); for (auto &byte : data) this->m_iconData.emplace_back(static_cast(byte)); return *this; } Achievement& setIcon(std::span data) { this->m_iconData.assign(data.begin(), data.end()); return *this; } Achievement& setIcon(std::vector data) { this->m_iconData = std::move(data); return *this; } Achievement& setIcon(std::vector data) { this->m_iconData.reserve(data.size()); for (auto &byte : data) this->m_iconData.emplace_back(static_cast(byte)); return *this; } Achievement& setRequiredProgress(u32 progress) { this->m_maxProgress = progress; return *this; } [[nodiscard]] u32 getRequiredProgress() const { return this->m_maxProgress; } [[nodiscard]] u32 getProgress() const { return this->m_progress; } void setClickCallback(const std::function &callback) { this->m_clickCallback = callback; } [[nodiscard]] const std::function &getClickCallback() const { return this->m_clickCallback; } [[nodiscard]] bool isTemporary() const { return this->m_temporary; } void setUnlocked(bool unlocked) { if (unlocked) { if (this->m_progress < this->m_maxProgress) this->m_progress++; } else { this->m_progress = 0; } } protected: void setProgress(u32 progress) { this->m_progress = progress; } private: std::string m_unlocalizedCategory, m_unlocalizedName; std::string m_unlocalizedDescription; bool m_blacked = false; bool m_invisible = false; std::vector m_requirements, m_visibilityRequirements; std::function m_clickCallback; std::vector m_iconData; mutable ImGui::Texture m_icon; u32 m_progress = 0; u32 m_maxProgress = 1; bool m_temporary = false; friend class AchievementManager; }; class AchievementManager { public: AchievementManager() = delete; struct AchievementNode { Achievement *achievement; std::vector children, parents; std::vector visibilityParents; ImVec2 position; [[nodiscard]] bool hasParents() const { return !this->parents.empty(); } [[nodiscard]] bool isUnlockable() const { return std::all_of(this->parents.begin(), this->parents.end(), [](auto &parent) { return parent->achievement->isUnlocked(); }); } [[nodiscard]] bool isVisible() const { return std::all_of(this->visibilityParents.begin(), this->visibilityParents.end(), [](auto &parent) { return parent->achievement->isUnlocked(); }); } [[nodiscard]] bool isUnlocked() const { return this->achievement->isUnlocked(); } }; template T = Achievement> static Achievement& addAchievement(auto && ... args) { auto newAchievement = std::make_unique(std::forward(args)...); const auto &category = newAchievement->getUnlocalizedCategory(); const auto &name = newAchievement->getUnlocalizedName(); auto [categoryIter, categoryInserted] = getAchievements().insert({ category, std::unordered_map>{} }); auto &[categoryKey, achievements] = *categoryIter; auto [achievementIter, achievementInserted] = achievements.insert({ name, std::move(newAchievement) }); auto &[achievementKey, achievement] = *achievementIter; achievementAdded(); return *achievement; } template T = Achievement> static Achievement& addTemporaryAchievement(auto && ... args) { auto &achievement = addAchievement(std::forward(args)...); achievement.m_temporary = true; return achievement; } static void unlockAchievement(const std::string &unlocalizedCategory, const std::string &unlocalizedName); static std::unordered_map>>& getAchievements(); static std::unordered_map>& getAchievementStartNodes(bool rebuild = true); static std::unordered_map>& getAchievementNodes(bool rebuild = true); static void loadProgress(); static void storeProgress(); static void clear(); static void clearTemporary(); private: static void achievementAdded(); }; }