feat: Added Achievements (#1230)
This PR adds Achievements to ImHex that serve as both a guide and a fun way to learn more about ImHex and reverse engineering
2
.github/workflows/build.yml
vendored
@ -614,7 +614,7 @@ jobs:
|
||||
# Fedora cmake build (in imhex.spec)
|
||||
- name: 📦 Build RPM
|
||||
run: |
|
||||
fedpkg --path $GITHUB_WORKSPACE --release ${{ matrix.mock_release }} mockbuild --enable-network -N --root $GITHUB_WORKSPACE/mock.cfg
|
||||
fedpkg --path $GITHUB_WORKSPACE --release ${{ matrix.mock_release }} mockbuild --enable-network -N --root $GITHUB_WORKSPACE/mock.cfg extra_args -- -v
|
||||
|
||||
- name: 🟩 Move and rename finished RPM
|
||||
run: |
|
||||
|
@ -15,6 +15,7 @@ set(LIBIMHEX_SOURCES
|
||||
source/api/project_file_manager.cpp
|
||||
source/api/theme_manager.cpp
|
||||
source/api/layout_manager.cpp
|
||||
source/api/achievement_manager.cpp
|
||||
|
||||
source/data_processor/attribute.cpp
|
||||
source/data_processor/link.cpp
|
||||
|
257
lib/libimhex/include/hex/api/achievement_manager.hpp
Normal file
@ -0,0 +1,257 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <span>
|
||||
|
||||
#include <hex/api/event.hpp>
|
||||
#include <imgui.h>
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
|
||||
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<std::string> &getRequirements() const {
|
||||
return this->m_requirements;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::vector<std::string> &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<const u8*>(this->m_iconData.data()), this->m_iconData.size());
|
||||
|
||||
return this->m_icon;
|
||||
}
|
||||
|
||||
Achievement& setIcon(std::span<const std::byte> data) {
|
||||
this->m_iconData.reserve(data.size());
|
||||
for (auto &byte : data)
|
||||
this->m_iconData.emplace_back(static_cast<u8>(byte));
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Achievement& setIcon(std::span<const u8> data) {
|
||||
this->m_iconData.assign(data.begin(), data.end());
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Achievement& setIcon(std::vector<u8> data) {
|
||||
this->m_iconData = std::move(data);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Achievement& setIcon(std::vector<std::byte> data) {
|
||||
this->m_iconData.reserve(data.size());
|
||||
for (auto &byte : data)
|
||||
this->m_iconData.emplace_back(static_cast<u8>(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<void(Achievement &)> &callback) {
|
||||
this->m_clickCallback = callback;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::function<void(Achievement &)> &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<std::string> m_requirements, m_visibilityRequirements;
|
||||
|
||||
std::function<void(Achievement &)> m_clickCallback;
|
||||
|
||||
std::vector<u8> 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<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();
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
template<std::derived_from<Achievement> T = Achievement>
|
||||
static Achievement& addTemporaryAchievement(auto && ... args) {
|
||||
auto &achievement = addAchievement(std::forward<decltype(args)>(args)...);
|
||||
|
||||
achievement.m_temporary = true;
|
||||
|
||||
return achievement;
|
||||
}
|
||||
|
||||
static void unlockAchievement(const std::string &unlocalizedCategory, const std::string &unlocalizedName);
|
||||
|
||||
static std::unordered_map<std::string, std::unordered_map<std::string, std::unique_ptr<Achievement>>>& getAchievements();
|
||||
|
||||
static std::unordered_map<std::string, std::vector<AchievementNode*>>& getAchievementStartNodes(bool rebuild = true);
|
||||
static std::unordered_map<std::string, std::list<AchievementNode>>& getAchievementNodes(bool rebuild = true);
|
||||
|
||||
static void loadProgress();
|
||||
static void storeProgress();
|
||||
|
||||
static void clear();
|
||||
static void clearTemporary();
|
||||
|
||||
private:
|
||||
static void achievementAdded();
|
||||
};
|
||||
|
||||
}
|
@ -23,7 +23,11 @@
|
||||
#define EVENT_DEF(event_name, ...) EVENT_DEF_IMPL(event_name, #event_name, true, __VA_ARGS__)
|
||||
#define EVENT_DEF_NO_LOG(event_name, ...) EVENT_DEF_IMPL(event_name, #event_name, false, __VA_ARGS__)
|
||||
|
||||
|
||||
/* Forward declarations */
|
||||
struct GLFWwindow;
|
||||
namespace hex { class Achievement; }
|
||||
|
||||
|
||||
namespace hex {
|
||||
|
||||
@ -202,6 +206,7 @@ namespace hex {
|
||||
EVENT_DEF(EventStoreContentDownloaded, const std::fs::path&);
|
||||
EVENT_DEF(EventStoreContentRemoved, const std::fs::path&);
|
||||
EVENT_DEF(EventImHexClosing);
|
||||
EVENT_DEF(EventAchievementUnlocked, const Achievement&);
|
||||
|
||||
/**
|
||||
* @brief Called when a project has been loaded
|
||||
|
223
lib/libimhex/source/api/achievement_manager.cpp
Normal file
@ -0,0 +1,223 @@
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
namespace hex {
|
||||
|
||||
std::unordered_map<std::string, std::unordered_map<std::string, std::unique_ptr<Achievement>>> &AchievementManager::getAchievements() {
|
||||
static std::unordered_map<std::string, std::unordered_map<std::string, std::unique_ptr<Achievement>>> achievements;
|
||||
|
||||
return achievements;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::list<AchievementManager::AchievementNode>>& AchievementManager::getAchievementNodes(bool rebuild) {
|
||||
static std::unordered_map<std::string, std::list<AchievementNode>> nodeCategoryStorage;
|
||||
|
||||
if (!nodeCategoryStorage.empty() || !rebuild)
|
||||
return nodeCategoryStorage;
|
||||
|
||||
nodeCategoryStorage.clear();
|
||||
|
||||
// Add all achievements to the node storage
|
||||
for (auto &[categoryName, achievements] : getAchievements()) {
|
||||
auto &nodes = nodeCategoryStorage[categoryName];
|
||||
|
||||
for (auto &[achievementName, achievement] : achievements) {
|
||||
nodes.emplace_back(achievement.get());
|
||||
}
|
||||
}
|
||||
|
||||
return nodeCategoryStorage;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, std::vector<AchievementManager::AchievementNode*>>& AchievementManager::getAchievementStartNodes(bool rebuild) {
|
||||
static std::unordered_map<std::string, std::vector<AchievementNode*>> startNodes;
|
||||
|
||||
if (!startNodes.empty() || !rebuild)
|
||||
return startNodes;
|
||||
|
||||
auto &nodeCategoryStorage = getAchievementNodes();
|
||||
|
||||
startNodes.clear();
|
||||
|
||||
// Add all parents and children to the nodes
|
||||
for (auto &[categoryName, achievements] : nodeCategoryStorage) {
|
||||
for (auto &achievementNode : achievements) {
|
||||
for (auto &requirement : achievementNode.achievement->getRequirements()) {
|
||||
for (auto &[requirementCategoryName, requirementAchievements] : nodeCategoryStorage) {
|
||||
auto iter = std::find_if(requirementAchievements.begin(), requirementAchievements.end(), [&requirement](auto &node) {
|
||||
return node.achievement->getUnlocalizedName() == requirement;
|
||||
});
|
||||
|
||||
if (iter != requirementAchievements.end()) {
|
||||
achievementNode.parents.emplace_back(&*iter);
|
||||
iter->children.emplace_back(&achievementNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &requirement : achievementNode.achievement->getVisibilityRequirements()) {
|
||||
for (auto &[requirementCategoryName, requirementAchievements] : nodeCategoryStorage) {
|
||||
auto iter = std::find_if(requirementAchievements.begin(), requirementAchievements.end(), [&requirement](auto &node) {
|
||||
return node.achievement->getUnlocalizedName() == requirement;
|
||||
});
|
||||
|
||||
if (iter != requirementAchievements.end()) {
|
||||
achievementNode.visibilityParents.emplace_back(&*iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto &[categoryName, achievements] : nodeCategoryStorage) {
|
||||
for (auto &achievementNode : achievements) {
|
||||
if (!achievementNode.hasParents()) {
|
||||
startNodes[categoryName].emplace_back(&achievementNode);
|
||||
}
|
||||
|
||||
for (const auto &parent : achievementNode.parents) {
|
||||
if (parent->achievement->getUnlocalizedCategory() != achievementNode.achievement->getUnlocalizedCategory())
|
||||
startNodes[categoryName].emplace_back(&achievementNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return startNodes;
|
||||
}
|
||||
|
||||
void AchievementManager::unlockAchievement(const std::string &unlocalizedCategory, const std::string &unlocalizedName) {
|
||||
auto &categories = getAchievements();
|
||||
|
||||
auto categoryIter = categories.find(unlocalizedCategory);
|
||||
if (categoryIter == categories.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &[categoryName, achievements] = *categoryIter;
|
||||
|
||||
auto achievementIter = achievements.find(unlocalizedName);
|
||||
|
||||
if (achievementIter == achievements.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto &nodes = getAchievementNodes()[categoryName];
|
||||
|
||||
for (const auto &node : nodes) {
|
||||
auto &achievement = node.achievement;
|
||||
|
||||
if (achievement->getUnlocalizedCategory() != unlocalizedCategory) {
|
||||
continue;
|
||||
}
|
||||
if (achievement->getUnlocalizedName() != unlocalizedName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (node.achievement->isUnlocked()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto &requirement : node.parents) {
|
||||
if (!requirement->achievement->isUnlocked()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
achievement->setUnlocked(true);
|
||||
|
||||
if (achievement->isUnlocked())
|
||||
EventManager::post<EventAchievementUnlocked>(*achievement);
|
||||
}
|
||||
}
|
||||
|
||||
void AchievementManager::clear() {
|
||||
getAchievements().clear();
|
||||
getAchievementStartNodes(false).clear();
|
||||
getAchievementNodes(false).clear();
|
||||
}
|
||||
|
||||
void AchievementManager::clearTemporary() {
|
||||
auto &categories = getAchievements();
|
||||
for (auto &[categoryName, achievements] : categories) {
|
||||
std::erase_if(achievements, [](auto &data) {
|
||||
auto &[achievementName, achievement] = data;
|
||||
return achievement->isTemporary();
|
||||
});
|
||||
}
|
||||
|
||||
std::erase_if(categories, [](auto &data) {
|
||||
auto &[categoryName, achievements] = data;
|
||||
return achievements.empty();
|
||||
});
|
||||
|
||||
getAchievementStartNodes(false).clear();
|
||||
getAchievementNodes(false).clear();
|
||||
}
|
||||
|
||||
void AchievementManager::achievementAdded() {
|
||||
getAchievementStartNodes(false).clear();
|
||||
getAchievementNodes(false).clear();
|
||||
}
|
||||
|
||||
constexpr static auto AchievementsFile = "achievements.json";
|
||||
|
||||
void AchievementManager::loadProgress() {
|
||||
for (const auto &directory : fs::getDefaultPaths(fs::ImHexPath::Config)) {
|
||||
auto path = directory / AchievementsFile;
|
||||
|
||||
if (!wolv::io::fs::exists(path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
wolv::io::File file(path, wolv::io::File::Mode::Read);
|
||||
|
||||
if (!file.isValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
auto json = nlohmann::json::parse(file.readString());
|
||||
|
||||
for (const auto &[categoryName, achievements] : getAchievements()) {
|
||||
for (const auto &[achievementName, achievement] : achievements) {
|
||||
try {
|
||||
achievement->setProgress(json[categoryName][achievementName]);
|
||||
} catch (const std::exception &e) {
|
||||
log::warn("Failed to load achievement progress for '{}::{}': {}", categoryName, achievementName, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
log::error("Failed to load achievements: {}", e.what());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void AchievementManager::storeProgress() {
|
||||
for (const auto &directory : fs::getDefaultPaths(fs::ImHexPath::Config)) {
|
||||
auto path = directory / AchievementsFile;
|
||||
|
||||
wolv::io::File file(path, wolv::io::File::Mode::Create);
|
||||
|
||||
if (!file.isValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
nlohmann::json json;
|
||||
|
||||
for (const auto &[categoryName, achievements] : getAchievements()) {
|
||||
json[categoryName] = nlohmann::json::object();
|
||||
|
||||
for (const auto &[achievementName, achievement] : achievements) {
|
||||
json[categoryName][achievementName] = achievement->getProgress();
|
||||
}
|
||||
}
|
||||
|
||||
file.writeString(json.dump(4));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -93,8 +93,10 @@ namespace hex {
|
||||
this->m_initializePluginFunction();
|
||||
} catch (const std::exception &e) {
|
||||
log::error("Plugin '{}' threw an exception on init: {}", pluginName, e.what());
|
||||
return false;
|
||||
} catch (...) {
|
||||
log::error("Plugin '{}' threw an exception on init", pluginName);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
|
@ -88,7 +88,13 @@ namespace hex {
|
||||
|
||||
bool Tar::contains(const std::fs::path &path) {
|
||||
mtar_header_t header;
|
||||
return mtar_find(&this->m_ctx, path.string().c_str(), &header) == MTAR_ESUCCESS;
|
||||
|
||||
auto fixedPath = path.string();
|
||||
#if defined(OS_WINDOWS)
|
||||
std::replace(fixedPath.begin(), fixedPath.end(), '\\', '/');
|
||||
#endif
|
||||
|
||||
return mtar_find(&this->m_ctx, fixedPath.c_str(), &header) == MTAR_ESUCCESS;
|
||||
}
|
||||
|
||||
std::string Tar::getOpenErrorString(){
|
||||
|
@ -35,8 +35,8 @@ namespace ImGui {
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
|
||||
#if defined(GL_UNPACK_ROW_LENGTH)
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include <hex/api/theme_manager.hpp>
|
||||
#include <hex/api/plugin_manager.hpp>
|
||||
#include <hex/api/layout_manager.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
|
||||
#include <hex/ui/view.hpp>
|
||||
#include <hex/ui/popup.hpp>
|
||||
@ -402,6 +403,8 @@ namespace hex::init {
|
||||
|
||||
ThemeManager::reset();
|
||||
|
||||
AchievementManager::getAchievements().clear();
|
||||
|
||||
ProjectFile::getHandlers().clear();
|
||||
ProjectFile::getProviderHandlers().clear();
|
||||
ProjectFile::setProjectFunctions(nullptr, nullptr);
|
||||
@ -454,13 +457,14 @@ namespace hex::init {
|
||||
}
|
||||
|
||||
// Make sure there's only one built-in plugin
|
||||
builtinPlugins++;
|
||||
if (builtinPlugins > 1) continue;
|
||||
|
||||
// Initialize the plugin
|
||||
if (!plugin.initializePlugin()) {
|
||||
log::error("Failed to initialize plugin {}", wolv::util::toUTF8String(plugin.getPath().filename()));
|
||||
loadErrors++;
|
||||
} else {
|
||||
builtinPlugins++;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ add_imhex_plugin(
|
||||
source/content/recent.cpp
|
||||
source/content/file_handlers.cpp
|
||||
source/content/project.cpp
|
||||
source/content/achievements.cpp
|
||||
|
||||
source/content/providers/file_provider.cpp
|
||||
source/content/providers/gdb_provider.cpp
|
||||
@ -64,6 +65,7 @@ add_imhex_plugin(
|
||||
source/content/views/view_find.cpp
|
||||
source/content/views/view_theme_manager.cpp
|
||||
source/content/views/view_logs.cpp
|
||||
source/content/views/view_achievements.cpp
|
||||
|
||||
source/content/helpers/math_evaluator.cpp
|
||||
source/content/helpers/notification.cpp
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include <fonts/codicons_font.h>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
class PopupTextInput : public Popup<PopupTextInput> {
|
||||
|
43
plugins/builtin/include/content/views/view_achievements.hpp
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <hex.hpp>
|
||||
#include <hex/ui/view.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
class ViewAchievements : public View {
|
||||
public:
|
||||
ViewAchievements();
|
||||
~ViewAchievements() override;
|
||||
|
||||
void drawContent() override;
|
||||
void drawAlwaysVisible() override;
|
||||
|
||||
[[nodiscard]] bool isAvailable() const override { return true; }
|
||||
[[nodiscard]] bool hasViewMenuItemEntry() const override { return false; }
|
||||
|
||||
[[nodiscard]] ImVec2 getMinSize() const override {
|
||||
return scaled({ 800, 600 });
|
||||
}
|
||||
|
||||
[[nodiscard]] ImVec2 getMaxSize() const override {
|
||||
return scaled({ 1600, 1200 });
|
||||
}
|
||||
|
||||
private:
|
||||
ImVec2 drawAchievementTree(ImDrawList *drawList, const AchievementManager::AchievementNode * prevNode, const std::vector<AchievementManager::AchievementNode*> &nodes, ImVec2 position);
|
||||
|
||||
private:
|
||||
bool m_viewOpen = false;
|
||||
|
||||
std::list<const Achievement*> m_achievementUnlockQueue;
|
||||
const Achievement *m_currAchievement = nullptr;
|
||||
const Achievement *m_achievementToGoto = nullptr;
|
||||
float m_achievementUnlockQueueTimer = -1;
|
||||
bool m_showPopup = true;
|
||||
|
||||
ImVec2 m_offset;
|
||||
};
|
||||
|
||||
}
|
BIN
plugins/builtin/romfs/assets/achievements/abacus.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
plugins/builtin/romfs/assets/achievements/adhesive-bandage.png
Normal file
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 5.8 KiB |
BIN
plugins/builtin/romfs/assets/achievements/bookmark-tabs.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
plugins/builtin/romfs/assets/achievements/bookmark.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
plugins/builtin/romfs/assets/achievements/brain.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
plugins/builtin/romfs/assets/achievements/briefcase.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 4.7 KiB |
BIN
plugins/builtin/romfs/assets/achievements/clipboard.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
plugins/builtin/romfs/assets/achievements/cloud.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
plugins/builtin/romfs/assets/achievements/collision-symbol.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
plugins/builtin/romfs/assets/achievements/copy.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
After Width: | Height: | Size: 9.2 KiB |
BIN
plugins/builtin/romfs/assets/achievements/fortune-cookie.png
Normal file
After Width: | Height: | Size: 13 KiB |
BIN
plugins/builtin/romfs/assets/achievements/frame-with-picture.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
plugins/builtin/romfs/assets/achievements/hammer-and-pick.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
plugins/builtin/romfs/assets/achievements/hammer-and-wrench.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
plugins/builtin/romfs/assets/achievements/hammer.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
BIN
plugins/builtin/romfs/assets/achievements/hourglass.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
plugins/builtin/romfs/assets/achievements/linked-paperclips.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
plugins/builtin/romfs/assets/achievements/open-book.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
plugins/builtin/romfs/assets/achievements/package.png
Normal file
After Width: | Height: | Size: 9.8 KiB |
BIN
plugins/builtin/romfs/assets/achievements/page-facing-up.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
plugins/builtin/romfs/assets/achievements/pencil.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
After Width: | Height: | Size: 9.1 KiB |
BIN
plugins/builtin/romfs/assets/achievements/ring.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
plugins/builtin/romfs/assets/achievements/water-wave.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
plugins/builtin/romfs/assets/achievements/wrench.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
@ -4,6 +4,61 @@
|
||||
"country": "United States",
|
||||
"fallback": true,
|
||||
"translations": {
|
||||
"hex.builtin.achievement.starting_out": "Starting out",
|
||||
"hex.builtin.achievement.starting_out.docs.name": "RTFM",
|
||||
"hex.builtin.achievement.starting_out.docs.desc": "Open the documentation by selecting Help -> Documentation from the menu bar.",
|
||||
"hex.builtin.achievement.starting_out.open_file.name": "The inner workings",
|
||||
"hex.builtin.achievement.starting_out.open_file.desc": "Open a file by dragging it onto ImHex or by selecting File -> Open from the menu bar.",
|
||||
"hex.builtin.achievement.starting_out.save_project.name": "Keeping this",
|
||||
"hex.builtin.achievement.starting_out.save_project.desc": "Save a project by selecting File -> Save Project from the menu bar.",
|
||||
"hex.builtin.achievement.hex_editor": "Hex Editor",
|
||||
"hex.builtin.achievement.hex_editor.select_byte.name": "You and you and you",
|
||||
"hex.builtin.achievement.hex_editor.select_byte.desc": "Select multiple bytes in the Hex Editor by clicking and dragging over them.",
|
||||
"hex.builtin.achievement.hex_editor.create_bookmark.name": "Building a library",
|
||||
"hex.builtin.achievement.hex_editor.create_bookmark.desc": "Create a bookmark by right-clicking on a byte and selecting Bookmark from the context menu.",
|
||||
"hex.builtin.achievement.hex_editor.open_new_view.name": "Seeing double",
|
||||
"hex.builtin.achievement.hex_editor.open_new_view.desc": "Open a new view by clicking on the 'Open new view' button in a bookmark.",
|
||||
"hex.builtin.achievement.hex_editor.modify_byte.name": "Edit the hex",
|
||||
"hex.builtin.achievement.hex_editor.modify_byte.desc": "Modify a byte by double-clicking on it and then entering the new value.",
|
||||
"hex.builtin.achievement.hex_editor.copy_as.name": "Copy that",
|
||||
"hex.builtin.achievement.hex_editor.copy_as.desc": "Copy bytes as a C++ array by selecting Copy As -> C++ Array from the context menu.",
|
||||
"hex.builtin.achievement.hex_editor.create_patch.name": "ROM Hacks",
|
||||
"hex.builtin.achievement.hex_editor.create_patch.desc": "Create a IPS patch for the use in other tools by selecting the Export option in the File menu.",
|
||||
"hex.builtin.achievement.hex_editor.fill.name": "Flood fill",
|
||||
"hex.builtin.achievement.hex_editor.fill.desc": "Fill a region with a byte by selecting Fill from the context menu.",
|
||||
"hex.builtin.achievement.patterns": "Patterns",
|
||||
"hex.builtin.achievement.patterns.place_menu.name": "Easy Patterns",
|
||||
"hex.builtin.achievement.patterns.place_menu.desc": "Place a pattern of any built-in type in your data by right-clicking on a byte and using the 'Place pattern' option.",
|
||||
"hex.builtin.achievement.patterns.load_existing.name": "Hey, I know this one",
|
||||
"hex.builtin.achievement.patterns.load_existing.desc": "Load a pattern that has been created by someone else by using the 'File -> Import' menu.",
|
||||
"hex.builtin.achievement.patterns.modify_data.name": "Edit the pattern",
|
||||
"hex.builtin.achievement.patterns.modify_data.desc": "Modify the underlying bytes of a pattern by double-clicking its value in the pattern data view and entering a new value.",
|
||||
"hex.builtin.achievement.patterns.data_inspector.name": "Inspector Gadget",
|
||||
"hex.builtin.achievement.patterns.data_inspector.desc": "Create a custom data inspector entry using the pattern language. You can find how to do that in the documentation.",
|
||||
"hex.builtin.achievement.find": "Finding",
|
||||
"hex.builtin.achievement.find.find_strings.name": "One Ring to find them",
|
||||
"hex.builtin.achievement.find.find_strings.desc": "Locate all strings in your file by using the Find view in 'Strings' mode.",
|
||||
"hex.builtin.achievement.find.find_specific_string.name": "Too Many Items",
|
||||
"hex.builtin.achievement.find.find_specific_string.desc": "Refine your search by searching for occurrences of a specific string by using the 'Sequences' mode.",
|
||||
"hex.builtin.achievement.find.find_numeric.name": "About ... that much",
|
||||
"hex.builtin.achievement.find.find_numeric.desc": "Search for numeric values between 250 and 1000 by using the 'Numeric Value' mode.",
|
||||
"hex.builtin.achievement.data_processor": "Data Processor",
|
||||
"hex.builtin.achievement.data_processor.place_node.name": "Look at all these nodes",
|
||||
"hex.builtin.achievement.data_processor.place_node.desc": "Place any built-in node in the data processor by right-clicking on the workspace and selecting a node from the context menu.",
|
||||
"hex.builtin.achievement.data_processor.create_connection.name": "I feel a connection here",
|
||||
"hex.builtin.achievement.data_processor.create_connection.desc": "Connect two nodes by dragging a connection from one node to another.",
|
||||
"hex.builtin.achievement.data_processor.modify_data.name": "Decode this",
|
||||
"hex.builtin.achievement.data_processor.modify_data.desc": "Preprocess the displayed bytes by using the built-in Read and Write Data Access nodes.",
|
||||
"hex.builtin.achievement.data_processor.custom_node.name": "Building my own!",
|
||||
"hex.builtin.achievement.data_processor.custom_node.desc": "Create a custom node by selecting 'Custom -> New Node' from the context menu and simplify your existing pattern by moving nodes into it.",
|
||||
"hex.builtin.achievement.misc": "Miscellaneous",
|
||||
"hex.builtin.achievement.misc.analyze_file.name": "owo wat dis?",
|
||||
"hex.builtin.achievement.misc.analyze_file.desc": "Analyze the bytes of your data by using the 'Analyze' option in the Data Information view.",
|
||||
"hex.builtin.achievement.misc.download_from_store.name": "There's an app for that",
|
||||
"hex.builtin.achievement.misc.download_from_store.desc": "Download any item from the Content Store",
|
||||
"hex.builtin.achievement.misc.create_hash.name": "Hash browns",
|
||||
"hex.builtin.achievement.misc.create_hash.desc": "Create a new hash function in the Hash view by selecting the type, giving it a name and clicking on the Plus button next to it.",
|
||||
|
||||
"hex.builtin.command.calc.desc": "Calculator",
|
||||
"hex.builtin.command.cmd.desc": "Command",
|
||||
"hex.builtin.command.cmd.result": "Run command '{0}'",
|
||||
@ -603,6 +658,10 @@
|
||||
"hex.builtin.tools.wiki_explain.invalid_response": "Invalid response from Wikipedia!",
|
||||
"hex.builtin.tools.wiki_explain.results": "Results",
|
||||
"hex.builtin.tools.wiki_explain.search": "Search",
|
||||
"hex.builtin.view.achievements.name": "Achievements",
|
||||
"hex.builtin.view.achievements.unlocked": "Achievement Unlocked!",
|
||||
"hex.builtin.view.achievements.unlocked_count": "Unlocked",
|
||||
"hex.builtin.view.achievements.click": "Click here",
|
||||
"hex.builtin.view.bookmarks.address": "0x{0:02X} - 0x{1:02X}",
|
||||
"hex.builtin.view.bookmarks.button.jump": "Jump to",
|
||||
"hex.builtin.view.bookmarks.button.remove": "Remove",
|
||||
@ -961,9 +1020,12 @@
|
||||
"hex.builtin.welcome.learn.latest.desc": "Read ImHex's current changelog",
|
||||
"hex.builtin.welcome.learn.latest.link": "https://github.com/WerWolv/ImHex/releases/latest",
|
||||
"hex.builtin.welcome.learn.latest.title": "Latest Release",
|
||||
"hex.builtin.welcome.learn.pattern.desc": "Learn how to write ImHex patterns with our extensive documentation",
|
||||
"hex.builtin.welcome.learn.pattern.link": "https://imhex.werwolv.net/docs",
|
||||
"hex.builtin.welcome.learn.pattern.desc": "Learn how to write ImHex patterns",
|
||||
"hex.builtin.welcome.learn.pattern.link": "https://docs.werwolv.net/pattern-language/",
|
||||
"hex.builtin.welcome.learn.pattern.title": "Pattern Language Documentation",
|
||||
"hex.builtin.welcome.learn.imhex.desc": "Learn the basics of ImHex with our extensive documentation",
|
||||
"hex.builtin.welcome.learn.imhex.link": "https://docs.werwolv.net/imhex/",
|
||||
"hex.builtin.welcome.learn.imhex.title": "ImHex Documentation",
|
||||
"hex.builtin.welcome.learn.plugins.desc": "Extend ImHex with additional features using plugins",
|
||||
"hex.builtin.welcome.learn.plugins.link": "https://github.com/WerWolv/ImHex/wiki/Plugins-Development-Guide",
|
||||
"hex.builtin.welcome.learn.plugins.title": "Plugins API",
|
||||
|
348
plugins/builtin/source/content/achievements.cpp
Normal file
@ -0,0 +1,348 @@
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
#include <hex/api/project_file_manager.hpp>
|
||||
|
||||
#include <hex/helpers/crypto.hpp>
|
||||
|
||||
#include <content/popups/popup_notification.hpp>
|
||||
#include <content/popups/popup_text_input.hpp>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <romfs/romfs.hpp>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
namespace {
|
||||
|
||||
class AchievementStartingOut : public Achievement {
|
||||
public:
|
||||
explicit AchievementStartingOut(std::string unlocalizedName) : Achievement("hex.builtin.achievement.starting_out", std::move(unlocalizedName)) { }
|
||||
};
|
||||
|
||||
class AchievementHexEditor : public Achievement {
|
||||
public:
|
||||
explicit AchievementHexEditor(std::string unlocalizedName) : Achievement("hex.builtin.achievement.hex_editor", std::move(unlocalizedName)) { }
|
||||
};
|
||||
|
||||
class AchievementPatterns : public Achievement {
|
||||
public:
|
||||
explicit AchievementPatterns(std::string unlocalizedName) : Achievement("hex.builtin.achievement.patterns", std::move(unlocalizedName)) { }
|
||||
};
|
||||
|
||||
class AchievementDataProcessor : public Achievement {
|
||||
public:
|
||||
explicit AchievementDataProcessor(std::string unlocalizedName) : Achievement("hex.builtin.achievement.data_processor", std::move(unlocalizedName)) { }
|
||||
};
|
||||
|
||||
class AchievementFind : public Achievement {
|
||||
public:
|
||||
explicit AchievementFind(std::string unlocalizedName) : Achievement("hex.builtin.achievement.find", std::move(unlocalizedName)) { }
|
||||
};
|
||||
|
||||
class AchievementMisc : public Achievement {
|
||||
public:
|
||||
explicit AchievementMisc(std::string unlocalizedName) : Achievement("hex.builtin.achievement.misc", std::move(unlocalizedName)) { }
|
||||
};
|
||||
|
||||
void registerGettingStartedAchievements() {
|
||||
AchievementManager::addAchievement<AchievementStartingOut>("hex.builtin.achievement.starting_out.docs.name")
|
||||
.setDescription("hex.builtin.achievement.starting_out.docs.desc")
|
||||
.setIcon(romfs::get("assets/achievements/open-book.png").span());
|
||||
|
||||
AchievementManager::addAchievement<AchievementStartingOut>("hex.builtin.achievement.starting_out.open_file.name")
|
||||
.setDescription("hex.builtin.achievement.starting_out.open_file.desc")
|
||||
.setIcon(romfs::get("assets/achievements/page-facing-up.png").span());
|
||||
|
||||
AchievementManager::addAchievement<AchievementStartingOut>("hex.builtin.achievement.starting_out.save_project.name")
|
||||
.setDescription("hex.builtin.achievement.starting_out.save_project.desc")
|
||||
.setIcon(romfs::get("assets/achievements/card-index-dividers.png").span())
|
||||
.addRequirement("hex.builtin.achievement.starting_out.open_file.name");
|
||||
|
||||
|
||||
AchievementManager::addAchievement<AchievementStartingOut>("hex.builtin.achievement.starting_out.crash.name")
|
||||
.setDescription("hex.builtin.achievement.starting_out.crash.desc")
|
||||
.setIcon(romfs::get("assets/achievements/collision-symbol.png").span())
|
||||
.setInvisible();
|
||||
}
|
||||
|
||||
void registerHexEditorAchievements() {
|
||||
AchievementManager::addAchievement<AchievementHexEditor>("hex.builtin.achievement.hex_editor.select_byte.name")
|
||||
.setDescription("hex.builtin.achievement.hex_editor.select_byte.desc")
|
||||
.setIcon(romfs::get("assets/achievements/bookmark-tabs.png").span())
|
||||
.addRequirement("hex.builtin.achievement.starting_out.open_file.name");
|
||||
|
||||
AchievementManager::addAchievement<AchievementHexEditor>("hex.builtin.achievement.hex_editor.open_new_view.name")
|
||||
.setDescription("hex.builtin.achievement.hex_editor.open_new_view.desc")
|
||||
.setIcon(romfs::get("assets/achievements/frame-with-picture.png").span())
|
||||
.addRequirement("hex.builtin.achievement.hex_editor.create_bookmark.name");
|
||||
|
||||
AchievementManager::addAchievement<AchievementHexEditor>("hex.builtin.achievement.hex_editor.modify_byte.name")
|
||||
.setDescription("hex.builtin.achievement.hex_editor.modify_byte.desc")
|
||||
.setIcon(romfs::get("assets/achievements/pencil.png").span())
|
||||
.addRequirement("hex.builtin.achievement.hex_editor.select_byte.name")
|
||||
.addVisibilityRequirement("hex.builtin.achievement.hex_editor.select_byte.name");
|
||||
|
||||
AchievementManager::addAchievement<AchievementHexEditor>("hex.builtin.achievement.hex_editor.copy_as.name")
|
||||
.setDescription("hex.builtin.achievement.hex_editor.copy_as.desc")
|
||||
.setIcon(romfs::get("assets/achievements/copy.png").span())
|
||||
.addRequirement("hex.builtin.achievement.hex_editor.modify_byte.name");
|
||||
|
||||
AchievementManager::addAchievement<AchievementHexEditor>("hex.builtin.achievement.hex_editor.create_patch.name")
|
||||
.setDescription("hex.builtin.achievement.hex_editor.create_patch.desc")
|
||||
.setIcon(romfs::get("assets/achievements/adhesive-bandage.png").span())
|
||||
.addRequirement("hex.builtin.achievement.hex_editor.modify_byte.name");
|
||||
|
||||
AchievementManager::addAchievement<AchievementHexEditor>("hex.builtin.achievement.hex_editor.fill.name")
|
||||
.setDescription("hex.builtin.achievement.hex_editor.fill.desc")
|
||||
.setIcon(romfs::get("assets/achievements/water-wave.png").span())
|
||||
.addRequirement("hex.builtin.achievement.hex_editor.select_byte.name")
|
||||
.addVisibilityRequirement("hex.builtin.achievement.hex_editor.select_byte.name");
|
||||
|
||||
AchievementManager::addAchievement<AchievementHexEditor>("hex.builtin.achievement.hex_editor.create_bookmark.name")
|
||||
.setDescription("hex.builtin.achievement.hex_editor.create_bookmark.desc")
|
||||
.setIcon(romfs::get("assets/achievements/bookmark.png").span())
|
||||
.addRequirement("hex.builtin.achievement.hex_editor.select_byte.name")
|
||||
.addVisibilityRequirement("hex.builtin.achievement.hex_editor.select_byte.name");
|
||||
}
|
||||
|
||||
void registerPatternsAchievements() {
|
||||
AchievementManager::addAchievement<AchievementPatterns>("hex.builtin.achievement.patterns.place_menu.name")
|
||||
.setDescription("hex.builtin.achievement.patterns.place_menu.desc")
|
||||
.setIcon(romfs::get("assets/achievements/clipboard.png").span())
|
||||
.addRequirement("hex.builtin.achievement.hex_editor.select_byte.name");
|
||||
|
||||
AchievementManager::addAchievement<AchievementPatterns>("hex.builtin.achievement.patterns.load_existing.name")
|
||||
.setDescription("hex.builtin.achievement.patterns.load_existing.desc")
|
||||
.setIcon(romfs::get("assets/achievements/hourglass.png").span())
|
||||
.addRequirement("hex.builtin.achievement.patterns.place_menu.name");
|
||||
|
||||
AchievementManager::addAchievement<AchievementPatterns>("hex.builtin.achievement.patterns.modify_data.name")
|
||||
.setDescription("hex.builtin.achievement.patterns.modify_data.desc")
|
||||
.setIcon(romfs::get("assets/achievements/hammer.png").span())
|
||||
.addRequirement("hex.builtin.achievement.patterns.place_menu.name");
|
||||
|
||||
|
||||
AchievementManager::addAchievement<AchievementPatterns>("hex.builtin.achievement.patterns.data_inspector.name")
|
||||
.setDescription("hex.builtin.achievement.patterns.data_inspector.desc")
|
||||
.setIcon(romfs::get("assets/achievements/eye-in-speech-bubble.png").span())
|
||||
.addRequirement("hex.builtin.achievement.hex_editor.select_byte.name");
|
||||
}
|
||||
|
||||
|
||||
void registerFindAchievements() {
|
||||
AchievementManager::addAchievement<AchievementFind>("hex.builtin.achievement.find.find_strings.name")
|
||||
.setDescription("hex.builtin.achievement.find.find_strings.desc")
|
||||
.setIcon(romfs::get("assets/achievements/ring.png").span())
|
||||
.addRequirement("hex.builtin.achievement.starting_out.open_file.name");
|
||||
|
||||
AchievementManager::addAchievement<AchievementFind>("hex.builtin.achievement.find.find_specific_string.name")
|
||||
.setDescription("hex.builtin.achievement.find.find_specific_string.desc")
|
||||
.setIcon(romfs::get("assets/achievements/right-pointing-magnifying-glass.png").span())
|
||||
.addRequirement("hex.builtin.achievement.find.find_strings.name");
|
||||
|
||||
AchievementManager::addAchievement<AchievementFind>("hex.builtin.achievement.find.find_numeric.name")
|
||||
.setDescription("hex.builtin.achievement.find.find_numeric.desc")
|
||||
.setIcon(romfs::get("assets/achievements/abacus.png").span())
|
||||
.addRequirement("hex.builtin.achievement.find.find_strings.name");
|
||||
}
|
||||
|
||||
void registerDataProcessorAchievements() {
|
||||
AchievementManager::addAchievement<AchievementDataProcessor>("hex.builtin.achievement.data_processor.place_node.name")
|
||||
.setDescription("hex.builtin.achievement.data_processor.place_node.desc")
|
||||
.setIcon(romfs::get("assets/achievements/cloud.png").span())
|
||||
.addRequirement("hex.builtin.achievement.starting_out.open_file.name");
|
||||
|
||||
AchievementManager::addAchievement<AchievementDataProcessor>("hex.builtin.achievement.data_processor.create_connection.name")
|
||||
.setDescription("hex.builtin.achievement.data_processor.create_connection.desc")
|
||||
.setIcon(romfs::get("assets/achievements/linked-paperclips.png").span())
|
||||
.addRequirement("hex.builtin.achievement.data_processor.place_node.name");
|
||||
|
||||
AchievementManager::addAchievement<AchievementDataProcessor>("hex.builtin.achievement.data_processor.modify_data.name")
|
||||
.setDescription("hex.builtin.achievement.data_processor.modify_data.desc")
|
||||
.setIcon(romfs::get("assets/achievements/hammer-and-pick.png").span())
|
||||
.addRequirement("hex.builtin.achievement.data_processor.create_connection.name");
|
||||
|
||||
AchievementManager::addAchievement<AchievementDataProcessor>("hex.builtin.achievement.data_processor.custom_node.name")
|
||||
.setDescription("hex.builtin.achievement.data_processor.custom_node.desc")
|
||||
.setIcon(romfs::get("assets/achievements/wrench.png").span())
|
||||
.addRequirement("hex.builtin.achievement.data_processor.create_connection.name");
|
||||
}
|
||||
|
||||
void registerMiscAchievements() {
|
||||
AchievementManager::addAchievement<AchievementMisc>("hex.builtin.achievement.misc.analyze_file.name")
|
||||
.setDescription("hex.builtin.achievement.misc.analyze_file.desc")
|
||||
.setIcon(romfs::get("assets/achievements/brain.png").span())
|
||||
.addRequirement("hex.builtin.achievement.starting_out.open_file.name");
|
||||
|
||||
AchievementManager::addAchievement<AchievementMisc>("hex.builtin.achievement.misc.download_from_store.name")
|
||||
.setDescription("hex.builtin.achievement.misc.download_from_store.desc")
|
||||
.setIcon(romfs::get("assets/achievements/package.png").span())
|
||||
.addRequirement("hex.builtin.achievement.starting_out.open_file.name");
|
||||
|
||||
AchievementManager::addAchievement<AchievementMisc>("hex.builtin.achievement.misc.create_hash.name")
|
||||
.setDescription("hex.builtin.achievement.misc.create_hash.desc")
|
||||
.setIcon(romfs::get("assets/achievements/fortune-cookie.png").span())
|
||||
.addRequirement("hex.builtin.achievement.starting_out.open_file.name");
|
||||
}
|
||||
|
||||
|
||||
void registerEvents() {
|
||||
EventManager::subscribe<EventRegionSelected>([](const auto ®ion) {
|
||||
if (region.getSize() > 1)
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.select_byte.name");
|
||||
});
|
||||
|
||||
EventManager::subscribe<EventBookmarkCreated>([](const auto&) {
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.create_bookmark.name");
|
||||
});
|
||||
|
||||
EventManager::subscribe<EventPatchCreated>([](u64, u8, u8) {
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.modify_byte.name");
|
||||
});
|
||||
|
||||
|
||||
EventManager::subscribe<EventImHexStartupFinished>(AchievementManager::loadProgress);
|
||||
EventManager::subscribe<EventImHexClosing>(AchievementManager::storeProgress);
|
||||
|
||||
// Clear temporary achievements when last provider is closed
|
||||
EventManager::subscribe<EventProviderChanged>([](hex::prv::Provider *oldProvider, hex::prv::Provider *newProvider) {
|
||||
hex::unused(oldProvider);
|
||||
if (newProvider == nullptr) {
|
||||
AchievementManager::clearTemporary();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void registerChallengeAchievementHandlers() {
|
||||
static std::string challengeAchievement;
|
||||
static std::string challengeDescription;
|
||||
|
||||
static std::map<std::string, std::vector<u8>> icons;
|
||||
|
||||
ProjectFile::registerHandler({
|
||||
.basePath = "challenge",
|
||||
.required = false,
|
||||
.load = [](const std::fs::path &basePath, Tar &tar) {
|
||||
if (!tar.contains(basePath / "achievements.json") || !tar.contains(basePath / "description.txt"))
|
||||
return true;
|
||||
|
||||
challengeAchievement = tar.readString(basePath / "achievements.json");
|
||||
challengeDescription = tar.readString(basePath / "description.txt");
|
||||
|
||||
nlohmann::json unlockedJson;
|
||||
if (tar.contains(basePath / "unlocked.json")) {
|
||||
unlockedJson = nlohmann::json::parse(tar.readString(basePath / "unlocked.json"));
|
||||
}
|
||||
|
||||
try {
|
||||
auto json = nlohmann::json::parse(challengeAchievement);
|
||||
|
||||
if (json.contains("achievements")) {
|
||||
for (const auto &achievement : json["achievements"]) {
|
||||
auto &newAchievement = AchievementManager::addTemporaryAchievement<Achievement>("hex.builtin.achievement.challenge", achievement["name"])
|
||||
.setDescription(achievement["description"]);
|
||||
|
||||
if (achievement.contains("icon")) {
|
||||
if (const auto &icon = achievement["icon"]; icon.is_string() && !icon.is_null()) {
|
||||
auto iconPath = icon.get<std::string>();
|
||||
|
||||
auto data = tar.readVector(basePath / iconPath);
|
||||
newAchievement.setIcon(data);
|
||||
icons[iconPath] = std::move(data);
|
||||
}
|
||||
}
|
||||
|
||||
if (achievement.contains("requirements")) {
|
||||
if (const auto &requirements = achievement["requirements"]; requirements.is_array()) {
|
||||
for (const auto &requirement : requirements) {
|
||||
newAchievement.addRequirement(requirement.get<std::string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (achievement.contains("visibility_requirements")) {
|
||||
if (const auto &requirements = achievement["visibility_requirements"]; requirements.is_array()) {
|
||||
for (const auto &requirement : requirements) {
|
||||
newAchievement.addVisibilityRequirement(requirement.get<std::string>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (achievement.contains("password")) {
|
||||
if (const auto &password = achievement["password"]; password.is_string() && !password.is_null()) {
|
||||
newAchievement.setClickCallback([password = password.get<std::string>()](Achievement &achievement) {
|
||||
if (password.empty())
|
||||
achievement.setUnlocked(true);
|
||||
else
|
||||
PopupTextInput::open("Enter Password", "Enter the password to unlock this achievement", [password, &achievement](const std::string &input) {
|
||||
if (input == password)
|
||||
achievement.setUnlocked(true);
|
||||
else
|
||||
PopupInfo::open("The password you entered was incorrect.");
|
||||
});
|
||||
});
|
||||
|
||||
if (unlockedJson.contains("achievements") && unlockedJson["achievements"].is_array()) {
|
||||
for (const auto &unlockedAchievement : unlockedJson["achievements"]) {
|
||||
if (unlockedAchievement.is_string() && unlockedAchievement.get<std::string>() == achievement["name"].get<std::string>()) {
|
||||
newAchievement.setUnlocked(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const nlohmann::json::exception &e) {
|
||||
log::error("Failed to load challenge project: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
PopupInfo::open(challengeDescription);
|
||||
|
||||
|
||||
return true;
|
||||
},
|
||||
.store = [](const std::fs::path &basePath, Tar &tar) {
|
||||
if (!challengeAchievement.empty())
|
||||
tar.writeString(basePath / "achievements.json", challengeAchievement);
|
||||
if (!challengeDescription.empty())
|
||||
tar.writeString(basePath / "description.txt", challengeDescription);
|
||||
|
||||
for (const auto &[iconPath, data] : icons) {
|
||||
tar.writeVector(basePath / iconPath, data);
|
||||
}
|
||||
|
||||
nlohmann::json unlockedJson;
|
||||
|
||||
unlockedJson["achievements"] = nlohmann::json::array();
|
||||
for (const auto &[categoryName, achievements] : AchievementManager::getAchievements()) {
|
||||
for (const auto &[achievementName, achievement] : achievements) {
|
||||
if (achievement->isTemporary() && achievement->isUnlocked()) {
|
||||
unlockedJson["achievements"].push_back(achievementName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tar.writeString(basePath / "unlocked.json", unlockedJson.dump(4));
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void registerAchievements() {
|
||||
registerGettingStartedAchievements();
|
||||
registerHexEditorAchievements();
|
||||
registerPatternsAchievements();
|
||||
registerFindAchievements();
|
||||
registerDataProcessorAchievements();
|
||||
registerMiscAchievements();
|
||||
|
||||
registerEvents();
|
||||
registerChallengeAchievementHandlers();
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
#include <hex/api/content_registry.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
|
||||
#include <hex/providers/provider.hpp>
|
||||
#include <hex/providers/buffered_reader.hpp>
|
||||
@ -48,6 +49,8 @@ namespace hex::plugin::builtin {
|
||||
});
|
||||
|
||||
ContentRegistry::DataFormatter::add("hex.builtin.view.hex_editor.copy.cpp", [](prv::Provider *provider, u64 offset, size_t size) {
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.copy_as.name");
|
||||
|
||||
return formatLanguageArray(provider, offset, size, hex::format("constexpr std::array<uint8_t, {0}> data = {{", size), "0x{0:02X}, ", "};");
|
||||
});
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
#include <hex/api/content_registry.hpp>
|
||||
#include <hex/api/localization.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
|
||||
#include <hex/data_processor/node.hpp>
|
||||
|
||||
#include <hex/api/localization.hpp>
|
||||
#include <hex/helpers/crypto.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <hex/helpers/logger.hpp>
|
||||
@ -467,6 +469,10 @@ namespace hex::plugin::builtin {
|
||||
const auto &address = this->getIntegerOnInput(0);
|
||||
const auto &data = this->getBufferOnInput(1);
|
||||
|
||||
if (!data.empty()) {
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.data_processor", "hex.builtin.achievement.data_processor.modify_data.name");
|
||||
}
|
||||
|
||||
this->setOverlayData(address, data);
|
||||
}
|
||||
};
|
||||
|
@ -5,6 +5,8 @@
|
||||
|
||||
#include <hex/api/project_file_manager.hpp>
|
||||
#include <hex/api/localization.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
|
||||
#include <hex/providers/provider.hpp>
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
|
||||
@ -160,6 +162,9 @@ namespace hex::plugin::builtin {
|
||||
resetPath.release();
|
||||
}
|
||||
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.starting_out", "hex.builtin.achievement.starting_out.save_project.name");
|
||||
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include <hex/api/localization.hpp>
|
||||
#include <hex/api/project_file_manager.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
@ -247,6 +248,8 @@ namespace hex::plugin::builtin {
|
||||
|
||||
this->m_file.close();
|
||||
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.starting_out", "hex.builtin.achievement.starting_out.open_file.name");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "content/views/view_find.hpp"
|
||||
#include "content/views/view_theme_manager.hpp"
|
||||
#include "content/views/view_logs.hpp"
|
||||
#include "content/views/view_achievements.hpp"
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
@ -46,6 +47,7 @@ namespace hex::plugin::builtin {
|
||||
ContentRegistry::Views::add<ViewFind>();
|
||||
ContentRegistry::Views::add<ViewThemeManager>();
|
||||
ContentRegistry::Views::add<ViewLogs>();
|
||||
ContentRegistry::Views::add<ViewAchievements>();
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
#include "content/views/view_about.hpp"
|
||||
|
||||
#include <hex/api/content_registry.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
#include <hex/helpers/fs.hpp>
|
||||
@ -23,7 +24,8 @@ namespace hex::plugin::builtin {
|
||||
ContentRegistry::Interface::addMenuItemSeparator({ "hex.builtin.menu.help" }, 2000);
|
||||
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.view.help.documentation" }, 3000, Shortcut::None, [] {
|
||||
hex::openWebpage("https://imhex.werwolv.net/docs");
|
||||
hex::openWebpage("https://docs.werwolv.net/imhex");
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.starting_out", "hex.builtin.achievement.starting_out.docs.name");
|
||||
});
|
||||
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.help", "hex.builtin.menu.help.ask_for_help" }, 4000, CTRLCMD + SHIFT + Keys::D, [] {
|
||||
|
378
plugins/builtin/source/content/views/view_achievements.cpp
Normal file
@ -0,0 +1,378 @@
|
||||
#include "content/views/view_achievements.hpp"
|
||||
|
||||
#include <hex/api/content_registry.hpp>
|
||||
|
||||
#include <wolv/utils/guards.hpp>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <imgui_internal.h>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
ViewAchievements::ViewAchievements() : View("hex.builtin.view.achievements.name") {
|
||||
ContentRegistry::Interface::addMenuItem({ "hex.builtin.menu.extras", "hex.builtin.view.achievements.name" }, 2600, Shortcut::None, [&, this] {
|
||||
this->m_viewOpen = true;
|
||||
this->getWindowOpenState() = true;
|
||||
});
|
||||
|
||||
EventManager::subscribe<EventAchievementUnlocked>(this, [this](const Achievement &achievement) {
|
||||
this->m_achievementUnlockQueue.push_back(&achievement);
|
||||
});
|
||||
|
||||
this->m_showPopup = bool(ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.achievement_popup", 1));
|
||||
}
|
||||
|
||||
ViewAchievements::~ViewAchievements() {
|
||||
EventManager::unsubscribe<EventAchievementUnlocked>(this);
|
||||
}
|
||||
|
||||
void drawAchievement(ImDrawList *drawList, const AchievementManager::AchievementNode *node, ImVec2 position) {
|
||||
const auto achievementSize = scaled({ 50, 50 });
|
||||
|
||||
auto &achievement = *node->achievement;
|
||||
|
||||
const auto borderColor = [&] {
|
||||
if (achievement.isUnlocked())
|
||||
return ImGui::GetCustomColorU32(ImGuiCustomCol_ToolbarYellow, 1.0F);
|
||||
else if (node->isUnlockable())
|
||||
return ImGui::GetColorU32(ImGuiCol_Button, 1.0F);
|
||||
else
|
||||
return ImGui::GetColorU32(ImGuiCol_PlotLines, 1.0F);
|
||||
}();
|
||||
|
||||
const auto fillColor = [&] {
|
||||
if (achievement.isUnlocked())
|
||||
return ImGui::GetColorU32(ImGuiCol_FrameBg, 1.0F) | 0xFF000000;
|
||||
else if (node->isUnlockable())
|
||||
return (u32(ImColor(ImLerp(ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled), ImGui::GetStyleColorVec4(ImGuiCol_Text), sinf(ImGui::GetTime() * 6.0F) * 0.5F + 0.5F))) & 0x00FFFFFF) | 0x80000000;
|
||||
else
|
||||
return ImGui::GetColorU32(ImGuiCol_TextDisabled, 0.5F);
|
||||
}();
|
||||
|
||||
if (achievement.isUnlocked()) {
|
||||
drawList->AddRectFilled(position, position + achievementSize, fillColor, 5_scaled, 0);
|
||||
drawList->AddRect(position, position + achievementSize, borderColor, 5_scaled, 0, 2_scaled);
|
||||
} else {
|
||||
drawList->AddRectFilled(position, position + achievementSize, ImGui::GetColorU32(ImGuiCol_WindowBg) | 0xFF000000, 5_scaled, 0);
|
||||
}
|
||||
|
||||
if (const auto &icon = achievement.getIcon(); icon.isValid()) {
|
||||
ImVec2 iconSize;
|
||||
if (icon.getSize().x > icon.getSize().y) {
|
||||
iconSize.x = achievementSize.x;
|
||||
iconSize.y = iconSize.x / icon.getSize().x * icon.getSize().y;
|
||||
} else {
|
||||
iconSize.y = achievementSize.y;
|
||||
iconSize.x = iconSize.y / icon.getSize().y * icon.getSize().x;
|
||||
}
|
||||
|
||||
iconSize *= 0.7F;
|
||||
|
||||
ImVec2 margin = (achievementSize - iconSize) / 2.0F;
|
||||
drawList->AddImage(icon, position + margin, position + margin + iconSize);
|
||||
}
|
||||
|
||||
if (!achievement.isUnlocked()) {
|
||||
drawList->AddRectFilled(position, position + achievementSize, fillColor, 5_scaled, 0);
|
||||
drawList->AddRect(position, position + achievementSize, borderColor, 5_scaled, 0, 2_scaled);
|
||||
}
|
||||
|
||||
auto tooltipPos = position + ImVec2(achievementSize.x, 0);
|
||||
auto tooltipSize = achievementSize * ImVec2(4, 0);
|
||||
|
||||
if (ImGui::IsMouseHoveringRect(position, position + achievementSize)) {
|
||||
ImGui::SetNextWindowPos(tooltipPos);
|
||||
ImGui::SetNextWindowSize(tooltipSize);
|
||||
if (ImGui::BeginTooltip()) {
|
||||
if (achievement.isBlacked() && !achievement.isUnlocked()) {
|
||||
ImGui::TextUnformatted("[ ??? ]");
|
||||
} else {
|
||||
ImGui::BeginDisabled(!achievement.isUnlocked());
|
||||
ImGui::TextUnformatted(LangEntry(achievement.getUnlocalizedName()));
|
||||
|
||||
if (auto requiredProgress = achievement.getRequiredProgress(); requiredProgress > 1) {
|
||||
ImGui::ProgressBar(float(achievement.getProgress()) / float(requiredProgress + 1), ImVec2(achievementSize.x * 4, 5_scaled), "");
|
||||
}
|
||||
|
||||
bool separator = false;
|
||||
|
||||
if (achievement.getClickCallback() && !achievement.isUnlocked()) {
|
||||
ImGui::Separator();
|
||||
separator = true;
|
||||
|
||||
ImGui::TextFormattedColored(ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarYellow), "[ {} ]", LangEntry("hex.builtin.view.achievements.click"));
|
||||
}
|
||||
|
||||
if (const auto &desc = achievement.getUnlocalizedDescription(); !desc.empty()) {
|
||||
if (!separator)
|
||||
ImGui::Separator();
|
||||
else
|
||||
ImGui::NewLine();
|
||||
|
||||
ImGui::TextFormattedWrapped("{}", LangEntry(desc));
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
if (!achievement.isUnlocked() && ImGui::IsMouseClicked(ImGuiMouseButton_Left)) {
|
||||
if (ImGui::GetIO().KeyShift) {
|
||||
#if defined (DEBUG)
|
||||
AchievementManager::unlockAchievement(node->achievement->getUnlocalizedCategory(), node->achievement->getUnlocalizedName());
|
||||
#endif
|
||||
} else {
|
||||
if (auto clickCallback = achievement.getClickCallback(); clickCallback)
|
||||
clickCallback(achievement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void drawOverlay(ImDrawList *drawList, ImVec2 windowMin, ImVec2 windowMax, const std::string &currCategory) {
|
||||
auto &achievements = AchievementManager::getAchievements()[currCategory];
|
||||
auto unlockedCount = std::count_if(achievements.begin(), achievements.end(), [](const auto &entry) {
|
||||
const auto &[name, achievement] = entry;
|
||||
return achievement->isUnlocked();
|
||||
});
|
||||
|
||||
auto invisibleCount = std::count_if(achievements.begin(), achievements.end(), [](const auto &entry) {
|
||||
const auto &[name, achievement] = entry;
|
||||
return achievement->isInvisible();
|
||||
});
|
||||
|
||||
auto unlockedText = hex::format("{}: {} / {}{}", "hex.builtin.view.achievements.unlocked_count"_lang, unlockedCount, achievements.size() - invisibleCount, invisibleCount > 0 ? "+" : " ");
|
||||
|
||||
auto &style = ImGui::GetStyle();
|
||||
auto overlaySize = ImGui::CalcTextSize(unlockedText.c_str()) + style.ItemSpacing + style.WindowPadding * 2.0F;
|
||||
auto padding = scaled({ 10, 10 });
|
||||
|
||||
auto overlayPos = ImVec2(windowMax.x - overlaySize.x - padding.x, windowMin.y + padding.y);
|
||||
|
||||
drawList->AddRectFilled(overlayPos, overlayPos + overlaySize, ImGui::GetColorU32(ImGuiCol_WindowBg, 0.8F));
|
||||
drawList->AddRect(overlayPos, overlayPos + overlaySize, ImGui::GetColorU32(ImGuiCol_Border));
|
||||
|
||||
ImGui::SetCursorScreenPos(overlayPos + padding);
|
||||
ImGui::BeginGroup();
|
||||
|
||||
ImGui::TextUnformatted(unlockedText.c_str());
|
||||
|
||||
ImGui::EndGroup();
|
||||
}
|
||||
|
||||
void drawBackground(ImDrawList *drawList, ImVec2 min, ImVec2 max, ImVec2 offset) {
|
||||
const auto patternSize = scaled({ 10, 10 });
|
||||
|
||||
const auto darkColor = ImGui::GetColorU32(ImGuiCol_TableRowBg);
|
||||
const auto lightColor = ImGui::GetColorU32(ImGuiCol_TableRowBgAlt);
|
||||
|
||||
drawList->AddRect(min, max, ImGui::GetColorU32(ImGuiCol_Border), 0.0F, 0, 1.0_scaled);
|
||||
|
||||
bool light = false;
|
||||
bool prevStart = false;
|
||||
for (float x = min.x + offset.x; x < max.x; x += i32(patternSize.x)) {
|
||||
if (prevStart == light)
|
||||
light = !light;
|
||||
prevStart = light;
|
||||
|
||||
for (float y = min.y + offset.y; y < max.y; y += i32(patternSize.y)) {
|
||||
drawList->AddRectFilled({ x, y }, { x + patternSize.x, y + patternSize.y }, light ? lightColor : darkColor);
|
||||
light = !light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImVec2 ViewAchievements::drawAchievementTree(ImDrawList *drawList, const AchievementManager::AchievementNode * prevNode, const std::vector<AchievementManager::AchievementNode*> &nodes, ImVec2 position) {
|
||||
ImVec2 maxPos = position;
|
||||
for (auto &node : nodes) {
|
||||
if (node->achievement->isInvisible() && !node->achievement->isUnlocked())
|
||||
continue;
|
||||
|
||||
if (!node->visibilityParents.empty()) {
|
||||
bool visible = true;
|
||||
for (auto &parent : node->visibilityParents) {
|
||||
if (!parent->achievement->isUnlocked()) {
|
||||
visible = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!visible)
|
||||
continue;
|
||||
}
|
||||
|
||||
drawList->ChannelsSetCurrent(1);
|
||||
|
||||
if (prevNode != nullptr) {
|
||||
if (prevNode->achievement->getUnlocalizedCategory() != node->achievement->getUnlocalizedCategory())
|
||||
continue;
|
||||
|
||||
auto start = prevNode->position + scaled({ 25, 25 });
|
||||
auto end = position + scaled({ 25, 25 });
|
||||
auto middle = ((start + end) / 2.0F) - scaled({ 50, 0 });
|
||||
|
||||
const auto color = [prevNode]{
|
||||
if (prevNode->achievement->isUnlocked())
|
||||
return ImGui::GetColorU32(ImGuiCol_Text) | 0xFF000000;
|
||||
else
|
||||
return ImGui::GetColorU32(ImGuiCol_TextDisabled) | 0xFF000000;
|
||||
}();
|
||||
|
||||
drawList->AddBezierQuadratic(start, middle, end, color, 2_scaled);
|
||||
|
||||
if (this->m_achievementToGoto != nullptr) {
|
||||
if (this->m_achievementToGoto == node->achievement) {
|
||||
this->m_offset = position - scaled({ 100, 100 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawList->ChannelsSetCurrent(2);
|
||||
|
||||
drawAchievement(drawList, node, position);
|
||||
|
||||
node->position = position;
|
||||
auto newMaxPos = drawAchievementTree(drawList, node, node->children, position + scaled({ 150, 0 }));
|
||||
if (newMaxPos.x > maxPos.x)
|
||||
maxPos.x = newMaxPos.x;
|
||||
if (newMaxPos.y > maxPos.y)
|
||||
maxPos.y = newMaxPos.y;
|
||||
|
||||
position.y = maxPos.y + 100_scaled;
|
||||
}
|
||||
|
||||
return maxPos;
|
||||
}
|
||||
|
||||
void ViewAchievements::drawContent() {
|
||||
if (ImGui::Begin(View::toWindowName("hex.builtin.view.achievements.name").c_str(), &this->m_viewOpen, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDocking)) {
|
||||
if (ImGui::BeginTabBar("##achievement_categories")) {
|
||||
auto &startNodes = AchievementManager::getAchievementStartNodes();
|
||||
|
||||
std::vector<std::string> categories;
|
||||
for (const auto &[categoryName, achievements] : startNodes) {
|
||||
categories.push_back(categoryName);
|
||||
}
|
||||
|
||||
std::reverse(categories.begin(), categories.end());
|
||||
|
||||
for (const auto &categoryName : categories) {
|
||||
const auto &achievements = startNodes[categoryName];
|
||||
|
||||
bool visible = false;
|
||||
for (const auto &achievement : achievements) {
|
||||
if (achievement->isUnlocked() || achievement->isUnlockable()) {
|
||||
visible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!visible)
|
||||
continue;
|
||||
|
||||
ImGuiTabItemFlags flags = ImGuiTabItemFlags_None;
|
||||
|
||||
if (this->m_achievementToGoto != nullptr) {
|
||||
if (this->m_achievementToGoto->getUnlocalizedCategory() == categoryName) {
|
||||
flags |= ImGuiTabItemFlags_SetSelected;
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::BeginTabItem(LangEntry(categoryName), nullptr, flags)) {
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
|
||||
const auto cursorPos = ImGui::GetCursorPos();
|
||||
const auto windowPos = ImGui::GetWindowPos() + ImVec2(0, cursorPos.y);
|
||||
const auto windowSize = ImGui::GetWindowSize() - ImVec2(0, cursorPos.y);;
|
||||
const float borderSize = 20.0_scaled;
|
||||
|
||||
const auto windowPadding = ImGui::GetStyle().WindowPadding;
|
||||
const auto innerWindowPos = windowPos + ImVec2(borderSize, borderSize);
|
||||
const auto innerWindowSize = windowSize - ImVec2(borderSize * 2, borderSize * 2) - ImVec2(0, ImGui::GetTextLineHeightWithSpacing());
|
||||
drawList->PushClipRect(innerWindowPos, innerWindowPos + innerWindowSize, true);
|
||||
|
||||
drawList->ChannelsSplit(4);
|
||||
|
||||
drawList->ChannelsSetCurrent(0);
|
||||
|
||||
drawBackground(drawList, innerWindowPos, innerWindowPos + innerWindowSize, this->m_offset);
|
||||
auto maxPos = drawAchievementTree(drawList, nullptr, achievements, innerWindowPos + scaled({ 100, 100 }) + this->m_offset);
|
||||
|
||||
drawList->ChannelsSetCurrent(3);
|
||||
|
||||
drawOverlay(drawList, innerWindowPos, innerWindowPos + innerWindowSize, categoryName);
|
||||
|
||||
drawList->ChannelsMerge();
|
||||
|
||||
if (ImGui::IsMouseHoveringRect(innerWindowPos, innerWindowPos + innerWindowSize)) {
|
||||
auto dragDelta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Left);
|
||||
this->m_offset += dragDelta;
|
||||
ImGui::ResetMouseDragDelta(ImGuiMouseButton_Left);
|
||||
}
|
||||
|
||||
this->m_offset = -ImClamp(-this->m_offset, { 0, 0 }, ImMax(maxPos - innerWindowPos - innerWindowSize, { 0, 0 }));
|
||||
|
||||
drawList->PopClipRect();
|
||||
|
||||
ImGui::SetCursorScreenPos(innerWindowPos + ImVec2(0, innerWindowSize.y + windowPadding.y));
|
||||
ImGui::BeginGroup();
|
||||
{
|
||||
if (ImGui::Checkbox("Show popup", &this->m_showPopup))
|
||||
ContentRegistry::Settings::write("hex.builtin.setting.interface", "hex.builtin.setting.interface.achievement_popup", i64(this->m_showPopup));
|
||||
}
|
||||
ImGui::EndGroup();
|
||||
|
||||
ImGui::EndTabItem();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTabBar();
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
this->getWindowOpenState() = this->m_viewOpen;
|
||||
|
||||
this->m_achievementToGoto = nullptr;
|
||||
}
|
||||
|
||||
void ViewAchievements::drawAlwaysVisible() {
|
||||
if (this->m_achievementUnlockQueueTimer >= 0 && this->m_showPopup) {
|
||||
this->m_achievementUnlockQueueTimer -= ImGui::GetIO().DeltaTime;
|
||||
|
||||
if (this->m_currAchievement != nullptr) {
|
||||
|
||||
const ImVec2 windowSize = scaled({ 200, 55 });
|
||||
ImGui::SetNextWindowPos(ImHexApi::System::getMainWindowPosition() + ImVec2 { ImHexApi::System::getMainWindowSize().x - windowSize.x - 100_scaled, 0 });
|
||||
ImGui::SetNextWindowSize(windowSize);
|
||||
if (ImGui::Begin("##achievement_unlocked", nullptr, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoInputs)) {
|
||||
ImGui::TextFormattedColored(ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarYellow), "{}", "hex.builtin.view.achievements.unlocked"_lang);
|
||||
|
||||
ImGui::Image(this->m_currAchievement->getIcon(), scaled({ 20, 20 }));
|
||||
ImGui::SameLine();
|
||||
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextFormattedWrapped("{}", LangEntry(this->m_currAchievement->getUnlocalizedName()));
|
||||
|
||||
if (ImGui::IsWindowHovered() && ImGui::IsMouseReleased(ImGuiMouseButton_Left)) {
|
||||
this->m_viewOpen = true;
|
||||
this->getWindowOpenState() = this->m_viewOpen;
|
||||
this->m_achievementToGoto = this->m_currAchievement;
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
} else {
|
||||
this->m_achievementUnlockQueueTimer = -1.0F;
|
||||
this->m_currAchievement = nullptr;
|
||||
if (!this->m_achievementUnlockQueue.empty()) {
|
||||
this->m_currAchievement = this->m_achievementUnlockQueue.front();
|
||||
this->m_achievementUnlockQueue.pop_front();
|
||||
this->m_achievementUnlockQueueTimer = 2.5F;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <hex/api/content_registry.hpp>
|
||||
#include <hex/api/project_file_manager.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
#include <hex/api/task.hpp>
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
@ -289,8 +290,10 @@ namespace hex::plugin::builtin {
|
||||
auto newProvider = ImHexApi::Provider::createProvider("hex.builtin.provider.view", true);
|
||||
if (auto *viewProvider = dynamic_cast<ViewProvider*>(newProvider); viewProvider != nullptr) {
|
||||
viewProvider->setProvider(region.getStartAddress(), region.getSize(), provider);
|
||||
if (viewProvider->open())
|
||||
if (viewProvider->open()) {
|
||||
EventManager::post<EventProviderOpened>(viewProvider);
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.open_new_view.name");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -4,8 +4,7 @@
|
||||
|
||||
#include <hex/providers/provider.hpp>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
#include <hex/helpers/logger.hpp>
|
||||
|
||||
#include <pl/pattern_language.hpp>
|
||||
@ -144,6 +143,8 @@ namespace hex::plugin::builtin {
|
||||
editingFunction,
|
||||
false
|
||||
});
|
||||
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.patterns", "hex.builtin.achievement.patterns.data_inspector.name");
|
||||
} catch (const pl::core::err::EvaluatorError::Exception &error) {
|
||||
log::error("Failed to get value of pattern '{}': {}", pattern->getDisplayName(), error.what());
|
||||
}
|
||||
|
@ -2,12 +2,11 @@
|
||||
#include "content/popups/popup_notification.hpp"
|
||||
|
||||
#include <hex/api/content_registry.hpp>
|
||||
|
||||
#include <hex/helpers/logger.hpp>
|
||||
#include <hex/api/project_file_manager.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
|
||||
#include <hex/providers/provider.hpp>
|
||||
#include <hex/api/project_file_manager.hpp>
|
||||
|
||||
#include <hex/helpers/logger.hpp>
|
||||
|
||||
#include <imnodes.h>
|
||||
#include <imnodes_internal.h>
|
||||
@ -166,6 +165,8 @@ namespace hex::plugin::builtin {
|
||||
editing = ImGui::IsItemActive();
|
||||
|
||||
if (ImGui::Button("hex.builtin.nodes.custom.custom.edit"_lang, ImVec2(200_scaled, ImGui::GetTextLineHeightWithSpacing()))) {
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.data_processor", "hex.builtin.achievement.data_processor.custom_node.name");
|
||||
|
||||
this->m_dataProcessor->getWorkspaceStack().push_back(&this->m_workspace);
|
||||
|
||||
this->m_requiresAttributeUpdate = true;
|
||||
@ -595,6 +596,7 @@ namespace hex::plugin::builtin {
|
||||
ImNodes::SetNodeScreenSpacePos(node->getId(), this->m_rightClickedCoords);
|
||||
workspace.nodes.push_back(std::move(node));
|
||||
ImHexApi::Provider::markDirty();
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.data_processor", "hex.builtin.achievement.data_processor.place_node.name");
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
@ -818,6 +820,8 @@ namespace hex::plugin::builtin {
|
||||
|
||||
fromAttr->addConnectedAttribute(newLink.getId(), toAttr);
|
||||
toAttr->addConnectedAttribute(newLink.getId(), fromAttr);
|
||||
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.data_processor", "hex.builtin.achievement.data_processor.create_connection.name");
|
||||
} while (false);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include "content/views/view_find.hpp"
|
||||
|
||||
#include <hex/api/imhex_api.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
|
||||
#include <hex/providers/buffered_reader.hpp>
|
||||
|
||||
#include <array>
|
||||
@ -439,6 +441,16 @@ namespace hex::plugin::builtin {
|
||||
void ViewFind::runSearch() {
|
||||
Region searchRegion = this->m_searchSettings.region;
|
||||
|
||||
if (this->m_searchSettings.mode == SearchSettings::Mode::Strings)
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.find", "hex.builtin.achievement.find.find_strings.name");
|
||||
else if (this->m_searchSettings.mode == SearchSettings::Mode::Sequence)
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.find", "hex.builtin.achievement.find.find_specific_string.name");
|
||||
else if (this->m_searchSettings.mode == SearchSettings::Mode::Value) {
|
||||
if (this->m_searchSettings.value.inputMin == "250" && this->m_searchSettings.value.inputMax == "1000")
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.find", "hex.builtin.achievement.find.find_specific_string.name");
|
||||
}
|
||||
|
||||
|
||||
this->m_searchTask = TaskManager::createTask("hex.builtin.view.find.searching", searchRegion.getSize(), [this, settings = this->m_searchSettings, searchRegion](auto &task) {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "content/providers/memory_file_provider.hpp"
|
||||
|
||||
#include <hex/api/project_file_manager.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
|
||||
#include <hex/ui/popup.hpp>
|
||||
#include <hex/helpers/crypto.hpp>
|
||||
|
||||
@ -176,8 +178,10 @@ namespace hex::plugin::builtin {
|
||||
|
||||
ImGui::BeginDisabled(this->m_newHashName.empty() || this->m_selectedHash == nullptr);
|
||||
if (ImGui::IconButton(ICON_VS_ADD, ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
||||
if (this->m_selectedHash != nullptr)
|
||||
if (this->m_selectedHash != nullptr) {
|
||||
this->m_hashFunctions->push_back(this->m_selectedHash->create(this->m_newHashName));
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.misc", "hex.builtin.achievement.misc.create_hash.name");
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
|
@ -2,10 +2,13 @@
|
||||
|
||||
#include <hex/api/content_registry.hpp>
|
||||
#include <hex/api/keybinding.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <hex/providers/buffered_reader.hpp>
|
||||
#include <hex/helpers/crypto.hpp>
|
||||
#include <hex/api/project_file_manager.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <hex/helpers/crypto.hpp>
|
||||
|
||||
#include <hex/providers/buffered_reader.hpp>
|
||||
|
||||
#include <content/providers/view_provider.hpp>
|
||||
#include <content/helpers/math_evaluator.hpp>
|
||||
@ -540,6 +543,8 @@ namespace hex::plugin::builtin {
|
||||
auto remainingSize = std::min<size_t>(size - i, bytes.size());
|
||||
provider->write(provider->getBaseAddress() + address + i, bytes.data(), remainingSize);
|
||||
}
|
||||
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.hex_editor", "hex.builtin.achievement.hex_editor.fill.name");
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "content/views/view_information.hpp"
|
||||
|
||||
#include <hex/api/content_registry.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
|
||||
#include <hex/providers/provider.hpp>
|
||||
#include <hex/providers/buffered_reader.hpp>
|
||||
@ -64,6 +65,8 @@ namespace hex::plugin::builtin {
|
||||
}
|
||||
|
||||
void ViewInformation::analyze() {
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.misc", "hex.builtin.achievement.misc.analyze_file.name");
|
||||
|
||||
this->m_analyzerTask = TaskManager::createTask("hex.builtin.view.information.analyzing", 0, [this](auto &task) {
|
||||
auto provider = ImHexApi::Provider::get();
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
#include "content/views/view_pattern_editor.hpp"
|
||||
|
||||
#include <hex/api/content_registry.hpp>
|
||||
#include <hex/api/project_file_manager.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
|
||||
#include <pl/patterns/pattern.hpp>
|
||||
#include <pl/core/preprocessor.hpp>
|
||||
@ -11,7 +13,6 @@
|
||||
|
||||
#include <hex/helpers/fs.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <hex/api/project_file_manager.hpp>
|
||||
#include <hex/helpers/magic.hpp>
|
||||
#include <hex/helpers/binary_pattern.hpp>
|
||||
|
||||
@ -1086,6 +1087,7 @@ namespace hex::plugin::builtin {
|
||||
auto selection = ImHexApi::HexEditor::getSelection();
|
||||
|
||||
appendEditorText(hex::format("{0} {0}_at_0x{1:02X} @ 0x{1:02X};", type, selection->getStartAddress()));
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.patterns", "hex.builtin.achievement.patterns.place_menu.name");
|
||||
}
|
||||
|
||||
void ViewPatternEditor::appendArray(const std::string &type, size_t size) {
|
||||
@ -1115,6 +1117,7 @@ namespace hex::plugin::builtin {
|
||||
PopupFileChooser::open(paths, std::vector<nfdfilteritem_t>{ { "Pattern File", "hexpat" } }, false,
|
||||
[this, provider](const std::fs::path &path) {
|
||||
this->loadPatternFile(path, provider);
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.patterns", "hex.builtin.achievement.patterns.load_existing.name");
|
||||
});
|
||||
}, ImHexApi::Provider::isValid);
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
#include "content/views/view_store.hpp"
|
||||
#include <hex/api/theme_manager.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
#include <hex/api_urls.hpp>
|
||||
|
||||
#include <hex/api/content_registry.hpp>
|
||||
@ -93,6 +94,7 @@ namespace hex::plugin::builtin {
|
||||
} else if (!entry.installed) {
|
||||
if (ImGui::Button("hex.builtin.view.store.download"_lang, buttonSize)) {
|
||||
entry.downloading = this->download(category.path, entry.fileName, entry.link, false);
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.misc", "hex.builtin.achievement.misc.download_from_store.name");
|
||||
}
|
||||
} else {
|
||||
if (ImGui::Button("hex.builtin.view.store.remove"_lang, buttonSize)) {
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <hex/api/plugin_manager.hpp>
|
||||
#include <hex/api/theme_manager.hpp>
|
||||
#include <hex/api/layout_manager.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
#include <hex/api_urls.hpp>
|
||||
#include <hex/ui/view.hpp>
|
||||
#include <hex/helpers/fs.hpp>
|
||||
@ -284,6 +285,10 @@ namespace hex::plugin::builtin {
|
||||
{
|
||||
if (ImGui::DescriptionButton("hex.builtin.welcome.learn.latest.title"_lang, "hex.builtin.welcome.learn.latest.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0)))
|
||||
hex::openWebpage("hex.builtin.welcome.learn.latest.link"_lang);
|
||||
if (ImGui::DescriptionButton("hex.builtin.welcome.learn.imhex.title"_lang, "hex.builtin.welcome.learn.imhex.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0))) {
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.starting_out", "hex.builtin.achievement.starting_out.docs.name");
|
||||
hex::openWebpage("hex.builtin.welcome.learn.imhex.link"_lang);
|
||||
}
|
||||
if (ImGui::DescriptionButton("hex.builtin.welcome.learn.pattern.title"_lang, "hex.builtin.welcome.learn.pattern.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0)))
|
||||
hex::openWebpage("hex.builtin.welcome.learn.pattern.link"_lang);
|
||||
if (ImGui::DescriptionButton("hex.builtin.welcome.learn.plugins.title"_lang, "hex.builtin.welcome.learn.plugins.desc"_lang, ImVec2(ImGui::GetContentRegionAvail().x * 0.8F, 0)))
|
||||
@ -527,6 +532,10 @@ namespace hex::plugin::builtin {
|
||||
if (showTipOfTheDay)
|
||||
PopupTipOfTheDay::open();
|
||||
}
|
||||
|
||||
if (hasCrashed) {
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.starting_out", "hex.builtin.achievement.starting_out.crash.name");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ namespace hex::plugin::builtin {
|
||||
void registerNetworkEndpoints();
|
||||
void registerFileHandlers();
|
||||
void registerProjectHandlers();
|
||||
void registerAchievements();
|
||||
|
||||
void addFooterItems();
|
||||
void addTitleBarButtons();
|
||||
@ -96,6 +97,7 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") {
|
||||
registerFileHandlers();
|
||||
registerProjectHandlers();
|
||||
registerCommandForwarders();
|
||||
registerAchievements();
|
||||
|
||||
addFooterItems();
|
||||
addTitleBarButtons();
|
||||
|
@ -21,8 +21,10 @@
|
||||
|
||||
#include <hex/api/imhex_api.hpp>
|
||||
#include <hex/api/content_registry.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <hex/api/achievement_manager.hpp>
|
||||
#include <hex/api/localization.hpp>
|
||||
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <content/helpers/math_evaluator.hpp>
|
||||
|
||||
#include <imgui.h>
|
||||
@ -332,6 +334,7 @@ namespace hex::plugin::builtin::ui {
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
|
||||
this->m_editingPattern = &pattern;
|
||||
this->m_editingPatternOffset = pattern.getOffset();
|
||||
AchievementManager::unlockAchievement("hex.builtin.achievement.patterns", "hex.builtin.achievement.patterns.modify_data.name");
|
||||
}
|
||||
|
||||
ImGui::SameLine(0, 0);
|
||||
|