1
0
mirror of synced 2024-12-15 01:01:15 +01:00
ImHex/lib/libimhex/include/hex/api/achievement_manager.hpp

421 lines
14 KiB
C++
Raw Normal View History

#pragma once
2023-11-30 14:40:07 +01:00
#include <hex.hpp>
#include <functional>
2023-11-30 11:23:12 +01:00
#include <list>
#include <memory>
#include <span>
#include <string>
2023-11-30 14:40:07 +01:00
#include <unordered_map>
#include <utility>
#include <vector>
#include <imgui.h>
#include <hex/ui/imgui_imhex_extensions.h>
#include <hex/api/localization_manager.hpp>
namespace hex {
class AchievementManager;
class Achievement {
public:
explicit Achievement(UnlocalizedString unlocalizedCategory, UnlocalizedString unlocalizedName) : m_unlocalizedCategory(std::move(unlocalizedCategory)), m_unlocalizedName(std::move(unlocalizedName)) { }
2023-08-26 12:54:52 +02:00
/**
* @brief Returns the unlocalized name of the achievement
* @return Unlocalized name of the achievement
*/
[[nodiscard]] const UnlocalizedString &getUnlocalizedName() const {
2023-12-19 13:10:25 +01:00
return m_unlocalizedName;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Returns the unlocalized category of the achievement
* @return Unlocalized category of the achievement
*/
[[nodiscard]] const UnlocalizedString &getUnlocalizedCategory() const {
2023-12-19 13:10:25 +01:00
return m_unlocalizedCategory;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Returns whether the achievement is unlocked
* @return Whether the achievement is unlocked
*/
[[nodiscard]] bool isUnlocked() const {
2023-12-19 13:10:25 +01:00
return m_progress == m_maxProgress;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Sets the description of the achievement
* @param description Description of the achievement
* @return Reference to the achievement
*/
Achievement& setDescription(std::string description) {
2023-12-19 13:10:25 +01:00
m_unlocalizedDescription = std::move(description);
return *this;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Adds a requirement to the achievement. The achievement will only be unlockable if all requirements are unlocked.
* @param requirement Unlocalized name of the requirement
* @return Reference to the achievement
*/
Achievement& addRequirement(std::string requirement) {
2023-12-19 13:10:25 +01:00
m_requirements.emplace_back(std::move(requirement));
return *this;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Adds a visibility requirement to the achievement. The achievement will only be visible if all requirements are unlocked.
* @param requirement Unlocalized name of the requirement
* @return Reference to the achievement
*/
Achievement& addVisibilityRequirement(std::string requirement) {
2023-12-19 13:10:25 +01:00
m_visibilityRequirements.emplace_back(std::move(requirement));
return *this;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Marks the achievement as blacked. Blacked achievements are visible but their name and description are hidden.
* @return Reference to the achievement
*/
Achievement& setBlacked() {
2023-12-19 13:10:25 +01:00
m_blacked = true;
return *this;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Marks the achievement as invisible. Invisible achievements are not visible at all.
* @return Reference to the achievement
*/
Achievement& setInvisible() {
2023-12-19 13:10:25 +01:00
m_invisible = true;
return *this;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Returns whether the achievement is blacked
* @return Whether the achievement is blacked
*/
[[nodiscard]] bool isBlacked() const {
2023-12-19 13:10:25 +01:00
return m_blacked;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Returns whether the achievement is invisible
* @return Whether the achievement is invisible
*/
[[nodiscard]] bool isInvisible() const {
2023-12-19 13:10:25 +01:00
return m_invisible;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Returns the list of requirements of the achievement
* @return List of requirements of the achievement
*/
[[nodiscard]] const std::vector<std::string> &getRequirements() const {
2023-12-19 13:10:25 +01:00
return m_requirements;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Returns the list of visibility requirements of the achievement
* @return List of visibility requirements of the achievement
*/
[[nodiscard]] const std::vector<std::string> &getVisibilityRequirements() const {
2023-12-19 13:10:25 +01:00
return m_visibilityRequirements;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Returns the unlocalized description of the achievement
* @return Unlocalized description of the achievement
*/
[[nodiscard]] const UnlocalizedString &getUnlocalizedDescription() const {
2023-12-19 13:10:25 +01:00
return m_unlocalizedDescription;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Returns the icon of the achievement
* @return Icon of the achievement
*/
[[nodiscard]] const ImGuiExt::Texture &getIcon() const {
2023-12-19 13:10:25 +01:00
if (m_iconData.empty())
return m_icon;
2023-12-19 13:10:25 +01:00
if (m_icon.isValid())
return m_icon;
2023-12-19 13:10:25 +01:00
m_icon = ImGuiExt::Texture(m_iconData.data(), m_iconData.size(), ImGuiExt::Texture::Filter::Linear);
2023-12-19 13:10:25 +01:00
return m_icon;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Sets the icon of the achievement
* @param data Icon data
* @return Reference to the achievement
*/
Achievement& setIcon(std::span<const std::byte> data) {
2023-12-19 13:10:25 +01:00
m_iconData.reserve(data.size());
for (auto &byte : data)
2023-12-19 13:10:25 +01:00
m_iconData.emplace_back(static_cast<u8>(byte));
return *this;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Sets the icon of the achievement
* @param data Icon data
* @return Reference to the achievement
*/
Achievement& setIcon(std::span<const u8> data) {
2023-12-19 13:10:25 +01:00
m_iconData.assign(data.begin(), data.end());
return *this;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Sets the icon of the achievement
* @param data Icon data
* @return Reference to the achievement
*/
Achievement& setIcon(std::vector<u8> data) {
2023-12-19 13:10:25 +01:00
m_iconData = std::move(data);
return *this;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Sets the icon of the achievement
* @param data Icon data
* @return Reference to the achievement
*/
Achievement& setIcon(const std::vector<std::byte> &data) {
2023-12-19 13:10:25 +01:00
m_iconData.reserve(data.size());
for (auto &byte : data)
2023-12-19 13:10:25 +01:00
m_iconData.emplace_back(static_cast<u8>(byte));
return *this;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Specifies the required progress to unlock the achievement. This is the number of times this achievement has to be triggered to unlock it. The default is 1.
* @param progress Required progress
* @return Reference to the achievement
*/
Achievement& setRequiredProgress(u32 progress) {
2023-12-19 13:10:25 +01:00
m_maxProgress = progress;
return *this;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Returns the required progress to unlock the achievement
* @return Required progress to unlock the achievement
*/
[[nodiscard]] u32 getRequiredProgress() const {
2023-12-19 13:10:25 +01:00
return m_maxProgress;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Returns the current progress of the achievement
* @return Current progress of the achievement
*/
[[nodiscard]] u32 getProgress() const {
2023-12-19 13:10:25 +01:00
return m_progress;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Sets the callback to call when the achievement is clicked
* @param callback Callback to call when the achievement is clicked
*/
void setClickCallback(const std::function<void(Achievement &)> &callback) {
2023-12-19 13:10:25 +01:00
m_clickCallback = callback;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Returns the callback to call when the achievement is clicked
* @return Callback to call when the achievement is clicked
*/
[[nodiscard]] const std::function<void(Achievement &)> &getClickCallback() const {
2023-12-19 13:10:25 +01:00
return m_clickCallback;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Returns whether the achievement is temporary. Temporary achievements have been added by challenge projects for example and will be removed when the project is closed.
* @return Whether the achievement is temporary
*/
[[nodiscard]] bool isTemporary() const {
2023-12-19 13:10:25 +01:00
return m_temporary;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Sets whether the achievement is unlocked
* @param unlocked Whether the achievement is unlocked
*/
void setUnlocked(bool unlocked) {
if (unlocked) {
2023-12-19 13:10:25 +01:00
if (m_progress < m_maxProgress)
m_progress++;
} else {
2023-12-19 13:10:25 +01:00
m_progress = 0;
}
}
protected:
void setProgress(u32 progress) {
2023-12-19 13:10:25 +01:00
m_progress = progress;
}
private:
UnlocalizedString m_unlocalizedCategory, m_unlocalizedName;
UnlocalizedString m_unlocalizedDescription;
bool m_blacked = false;
bool m_invisible = false;
std::vector<std::string> m_requirements, m_visibilityRequirements;
std::function<void(Achievement &)> m_clickCallback;
std::vector<u8> m_iconData;
mutable ImGuiExt::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<AchievementNode*> children, parents;
std::vector<AchievementNode*> 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();
}
};
2023-08-26 12:54:52 +02:00
/**
* @brief Adds a new achievement
* @tparam T Type of the achievement
* @param args Arguments to pass to the constructor of the achievement
* @return Reference to the achievement
*/
template<std::derived_from<Achievement> T = Achievement>
static Achievement& addAchievement(auto && ... args) {
auto newAchievement = std::make_unique<T>(std::forward<decltype(args)>(args)...);
const auto &category = newAchievement->getUnlocalizedCategory();
const auto &name = newAchievement->getUnlocalizedName();
auto [categoryIter, categoryInserted] = getAchievements().insert({ category, std::unordered_map<std::string, std::unique_ptr<Achievement>>{} });
auto &[categoryKey, achievements] = *categoryIter;
auto [achievementIter, achievementInserted] = achievements.insert({ name, std::move(newAchievement) });
auto &[achievementKey, achievement] = *achievementIter;
achievementAdded();
return *achievement;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Adds a new temporary achievement
* @tparam T Type of the achievement
* @param args Arguments to pass to the constructor of the achievement
* @return Reference to the achievement
*/
template<std::derived_from<Achievement> T = Achievement>
static Achievement& addTemporaryAchievement(auto && ... args) {
2023-11-10 20:47:08 +01:00
auto &achievement = addAchievement<T>(std::forward<decltype(args)>(args)...);
achievement.m_temporary = true;
return achievement;
}
2023-08-26 12:54:52 +02:00
/**
* @brief Unlocks an achievement
* @param unlocalizedCategory Unlocalized category of the achievement
* @param unlocalizedName Unlocalized name of the achievement
*/
static void unlockAchievement(const UnlocalizedString &unlocalizedCategory, const UnlocalizedString &unlocalizedName);
2023-08-26 12:54:52 +02:00
/**
* @brief Returns all registered achievements
* @return All achievements
*/
static std::unordered_map<std::string, std::unordered_map<std::string, std::unique_ptr<Achievement>>>& getAchievements();
2023-08-26 12:54:52 +02:00
/**
* @brief Returns all achievement start nodes
* @note Start nodes are all nodes that don't have any parents
* @param rebuild Whether to rebuild the list of start nodes
* @return All achievement start nodes
*/
static std::unordered_map<std::string, std::vector<AchievementNode*>>& getAchievementStartNodes(bool rebuild = true);
2023-08-26 12:54:52 +02:00
/**
* @brief Returns all achievement nodes
* @param rebuild Whether to rebuild the list of nodes
* @return All achievement nodes
*/
static std::unordered_map<std::string, std::list<AchievementNode>>& getAchievementNodes(bool rebuild = true);
2023-08-26 12:54:52 +02:00
/**
* @brief Loads the progress of all achievements from the achievements save file
*/
static void loadProgress();
2023-08-26 12:54:52 +02:00
/**
* @brief Stores the progress of all achievements to the achievements save file
*/
static void storeProgress();
2023-08-26 12:54:52 +02:00
/**
* @brief Removes all registered achievements from the tree
*/
static void clear();
2023-08-26 12:54:52 +02:00
/**
* @brief Removes all temporary achievements from the tree
*/
static void clearTemporary();
/**
* \brief Returns the current progress of all achievements
* \return A pair containing the number of unlocked achievements and the total number of achievements
*/
static std::pair<u32, u32> getProgress();
private:
static void achievementAdded();
};
}