Release v1.7.0.0

New Features:

- Added Coordinate Calibration - manually calibrate click positions for unsupported resolutions, UI scales, or custom Windows DPI settings via addon options
- Calibration values are saved persistently and applied globally to all click sequences

UI Improvements:

- Moved "Current template" indicator inside the Templates tab - also resolves window stretching on certain resolutions
- Added hint below delay settings to suggest calibration if clicks miss

Technical Changes:

- Fixed implicit size_t to int conversion warning in template list rendering
This commit is contained in:
Azrub 2026-04-02 00:53:28 +02:00
parent a334cadd83
commit d035d14587
6 changed files with 203 additions and 46 deletions

View file

@ -2,10 +2,17 @@
#include <string> #include <string>
#include "CoordinateUtils.h" #include "CoordinateUtils.h"
#include "../Common/Globals.h" #include "../Common/Globals.h"
#include "../Config/ConfigManager.h"
void CoordinateUtils::GetScaledCoordinates(int baseX, int baseY, int* scaledX, int* scaledY) { void CoordinateUtils::GetScaledCoordinates(int baseX, int baseY, int* scaledX, int* scaledY) {
if (!g_api) return; if (!g_api) return;
if (g_state.hasCalibration) {
*scaledX = (int)(baseX * g_state.calibratedScaleX);
*scaledY = (int)(baseY * g_state.calibratedScaleY);
return;
}
if (g_nexusLink && g_nexusLink->Width > 0 && g_nexusLink->Height > 0) { if (g_nexusLink && g_nexusLink->Width > 0 && g_nexusLink->Height > 0) {
float uiScale = g_nexusLink->Scaling; float uiScale = g_nexusLink->Scaling;
float dpiScaleX, dpiScaleY; float dpiScaleX, dpiScaleY;
@ -173,4 +180,74 @@ void CoordinateUtils::ClickAtScaled(int baseX, int baseY, int delay) {
Sleep(10); Sleep(10);
SendMessage(gameWindow, WM_LBUTTONUP, 0, lParam); SendMessage(gameWindow, WM_LBUTTONUP, 0, lParam);
Sleep(delay); Sleep(delay);
}
void CoordinateUtils::StartCalibration() {
g_state.calibrationMode = true;
g_state.showUI = false;
if (g_api) {
g_api->Log(ELogLevel_INFO, "GolemHelper",
"Calibration started - interact with the Boon Console and click 'Adjust Self'");
}
}
void CoordinateUtils::CaptureCalibrationPoint() {
POINT mousePos;
GetCursorPos(&mousePos);
const float REF_BASE_X = 830.0f;
const float REF_BASE_Y = 262.0f;
g_state.calibratedScaleX = mousePos.x / REF_BASE_X;
g_state.calibratedScaleY = mousePos.y / REF_BASE_Y;
g_state.hasCalibration = true;
g_state.calibrationMode = false;
ConfigManager::SaveCustomDelaySettings();
if (g_api) {
char buffer[256];
sprintf_s(buffer,
"Calibration saved: click=(%ld, %ld) scaleX=%.4f scaleY=%.4f",
mousePos.x, mousePos.y,
g_state.calibratedScaleX, g_state.calibratedScaleY);
g_api->Log(ELogLevel_INFO, "GolemHelper", buffer);
g_api->UI.SendAlert("Calibration saved!");
}
}
void CoordinateUtils::ResetCalibration() {
g_state.hasCalibration = false;
g_state.calibrationMode = false;
g_state.calibratedScaleX = 1.0f;
g_state.calibratedScaleY = 1.0f;
ConfigManager::SaveCustomDelaySettings();
if (g_api) {
g_api->Log(ELogLevel_INFO, "GolemHelper", "Calibration reset - back to auto-scaling");
g_api->UI.SendAlert("Calibration reset to default");
}
}
void CoordinateUtils::UpdateCalibrationCapture() {
if (!g_state.calibrationMode) return;
if (GetAsyncKeyState(VK_ESCAPE) & 0x8000) {
g_state.calibrationMode = false;
if (g_api) g_api->UI.SendAlert("Calibration cancelled");
return;
}
static bool s_wasPressed = false;
bool isPressed = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0;
if (isPressed && !s_wasPressed) {
if (g_mumbleData && g_mumbleData->Context.IsGameFocused) {
CaptureCalibrationPoint();
}
}
s_wasPressed = isPressed;
} }

View file

@ -5,4 +5,9 @@ public:
static void GetScaledCoordinates(int baseX, int baseY, int* scaledX, int* scaledY); static void GetScaledCoordinates(int baseX, int baseY, int* scaledX, int* scaledY);
static void DebugMousePosition(); static void DebugMousePosition();
static void ClickAtScaled(int baseX, int baseY, int delay = 25); static void ClickAtScaled(int baseX, int baseY, int delay = 25);
static void StartCalibration();
static void CaptureCalibrationPoint();
static void ResetCalibration();
static void UpdateCalibrationCapture();
}; };

View file

@ -155,6 +155,12 @@ struct GolemHelperState {
bool quickAccessVisible = false; bool quickAccessVisible = false;
unsigned int lastMapID = 0; unsigned int lastMapID = 0;
// Calibration
bool calibrationMode = false;
bool hasCalibration = false;
float calibratedScaleX = 1.0f;
float calibratedScaleY = 1.0f;
std::vector<GolemTemplate> templates; std::vector<GolemTemplate> templates;
int selectedTemplateIndex = -1; int selectedTemplateIndex = -1;
int lastUserTemplateIndex = -1; int lastUserTemplateIndex = -1;

View file

@ -24,6 +24,9 @@ void ConfigManager::SaveCustomDelaySettings() {
configFile << "alwaysHideIcon=" << (g_state.alwaysHideIcon ? "1" : "0") << std::endl; configFile << "alwaysHideIcon=" << (g_state.alwaysHideIcon ? "1" : "0") << std::endl;
configFile << "autoShowHideUI=" << (g_state.autoShowHideUI ? "1" : "0") << std::endl; configFile << "autoShowHideUI=" << (g_state.autoShowHideUI ? "1" : "0") << std::endl;
configFile << "alwaysLoadLastSettings=" << (g_state.alwaysLoadLastSettings ? "1" : "0") << std::endl; configFile << "alwaysLoadLastSettings=" << (g_state.alwaysLoadLastSettings ? "1" : "0") << std::endl;
configFile << "hasCalibration=" << (g_state.hasCalibration ? "1" : "0") << std::endl;
configFile << "calibratedScaleX=" << g_state.calibratedScaleX << std::endl;
configFile << "calibratedScaleY=" << g_state.calibratedScaleY << std::endl;
configFile.close(); configFile.close();
@ -84,6 +87,23 @@ void ConfigManager::LoadCustomDelaySettings() {
else if (key == "alwaysLoadLastSettings") { else if (key == "alwaysLoadLastSettings") {
g_state.alwaysLoadLastSettings = (value == "1"); g_state.alwaysLoadLastSettings = (value == "1");
} }
else if (key == "hasCalibration") {
g_state.hasCalibration = (value == "1");
}
else if (key == "calibratedScaleX") {
try {
float v = std::stof(value);
if (v > 0.1f && v < 10.0f) g_state.calibratedScaleX = v;
}
catch (...) {}
}
else if (key == "calibratedScaleY") {
try {
float v = std::stof(value);
if (v > 0.1f && v < 10.0f) g_state.calibratedScaleY = v;
}
catch (...) {}
}
} }
configFile.close(); configFile.close();

View file

@ -40,7 +40,7 @@ void Load(AddonAPI* aApi) {
MapUtils::UpdateQuickAccessVisibility(); MapUtils::UpdateQuickAccessVisibility();
g_api->Log(ELogLevel_INFO, "GolemHelper", "=== GolemHelper v1.6.0.0 Loaded ==="); g_api->Log(ELogLevel_INFO, "GolemHelper", "=== GolemHelper v1.7.0.0 Loaded ===");
g_api->Log(ELogLevel_INFO, "GolemHelper", "<c=#00ff00>GolemHelper addon</c> loaded successfully!"); g_api->Log(ELogLevel_INFO, "GolemHelper", "<c=#00ff00>GolemHelper addon</c> loaded successfully!");
} }
@ -67,7 +67,7 @@ extern "C" __declspec(dllexport) AddonDefinition* GetAddonDef() {
def.Signature = -424248; def.Signature = -424248;
def.APIVersion = NEXUS_API_VERSION; def.APIVersion = NEXUS_API_VERSION;
def.Name = "GolemHelper"; def.Name = "GolemHelper";
def.Version = { 1, 6, 0, 0 }; def.Version = { 1, 7, 0, 0 };
def.Author = "Azrub"; def.Author = "Azrub";
def.Description = "Automates the process of setting optimal boon and golem configurations in the training area"; def.Description = "Automates the process of setting optimal boon and golem configurations in the training area";
def.Load = Load; def.Load = Load;

View file

@ -5,11 +5,14 @@
#include "../Config/ConfigManager.h" #include "../Config/ConfigManager.h"
#include "../Utils/MapUtils.h" #include "../Utils/MapUtils.h"
#include "../Config/TemplateManager.h" #include "../Config/TemplateManager.h"
#include "../Automation/CoordinateUtils.h"
#include "../Dependencies/imgui/imgui.h" #include "../Dependencies/imgui/imgui.h"
void UIManager::RenderUI() { void UIManager::RenderUI() {
MapUtils::UpdateQuickAccessVisibility(); MapUtils::UpdateQuickAccessVisibility();
CoordinateUtils::UpdateCalibrationCapture();
if (!g_state.showUI) return; if (!g_state.showUI) return;
ImGui::SetNextWindowSize(ImVec2(450, 600), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(450, 600), ImGuiCond_FirstUseEver);
@ -17,7 +20,7 @@ void UIManager::RenderUI() {
if (ImGui::Begin("GolemHelper", &g_state.showUI, ImGuiWindowFlags_AlwaysAutoResize)) { if (ImGui::Begin("GolemHelper", &g_state.showUI, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::TextColored(ImVec4(0.2f, 0.8f, 1.0f, 1.0f), "GolemHelper v1.6.0.0"); ImGui::TextColored(ImVec4(0.2f, 0.8f, 1.0f, 1.0f), "GolemHelper v1.7.0.0");
ImGui::Separator(); ImGui::Separator();
if (ImGui::BeginTabBar("GolemHelperTabs", ImGuiTabBarFlags_None)) { if (ImGui::BeginTabBar("GolemHelperTabs", ImGuiTabBarFlags_None)) {
@ -28,46 +31,6 @@ void UIManager::RenderUI() {
} }
if (ImGui::BeginTabItem("Templates")) { if (ImGui::BeginTabItem("Templates")) {
ImGui::SameLine();
ImGui::SetCursorPosX(ImGui::GetWindowWidth() - 130);
std::string currentTemplateName = "None";
bool foundMatchingTemplate = false;
for (const auto& temp : g_state.templates) {
if (temp.isQuickDps == g_state.isQuickDps &&
temp.isAlacDps == g_state.isAlacDps &&
temp.environmentDamage == g_state.environmentDamage &&
temp.envDamageLevel == g_state.envDamageLevel &&
temp.skipBurning == g_state.skipBurning &&
temp.skipConfusion == g_state.skipConfusion &&
temp.skipSlow == g_state.skipSlow &&
temp.addImmobilize == g_state.addImmobilize &&
temp.addBlind == g_state.addBlind &&
temp.fiveBleedingStacks == g_state.fiveBleedingStacks &&
temp.hitboxType == g_state.hitboxType &&
temp.addResistance == g_state.addResistance &&
temp.addStability == g_state.addStability &&
temp.skipAegis == g_state.skipAegis) {
currentTemplateName = temp.name;
foundMatchingTemplate = true;
break;
}
}
ImGui::Text("Current: ");
ImGui::SameLine();
if (foundMatchingTemplate) {
std::string truncatedName = currentTemplateName;
if (truncatedName.length() > 8) {
truncatedName = truncatedName.substr(0, 6) + "..";
}
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "%s", truncatedName.c_str());
}
else {
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "None");
}
RenderTemplatesTab(); RenderTemplatesTab();
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
@ -264,6 +227,7 @@ void UIManager::RenderSettingsTab() {
ImGui::Spacing(); ImGui::Spacing();
ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.2f, 1.0f), "Increase delays if clicks fail"); ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.2f, 1.0f), "Increase delays if clicks fail");
ImGui::TextColored(ImVec4(0.8f, 0.8f, 0.2f, 1.0f), "or calibrate in Options");
if (oldStepDelay != g_state.stepDelay || oldInitialDelay != g_state.initialDelay) { if (oldStepDelay != g_state.stepDelay || oldInitialDelay != g_state.initialDelay) {
ConfigManager::SaveCustomDelaySettings(); ConfigManager::SaveCustomDelaySettings();
@ -329,6 +293,41 @@ void UIManager::RenderSettingsTab() {
} }
void UIManager::RenderTemplatesTab() { void UIManager::RenderTemplatesTab() {
std::string currentTemplateName = "None";
bool foundMatchingTemplate = false;
for (const auto& temp : g_state.templates) {
if (temp.isQuickDps == g_state.isQuickDps &&
temp.isAlacDps == g_state.isAlacDps &&
temp.environmentDamage == g_state.environmentDamage &&
temp.envDamageLevel == g_state.envDamageLevel &&
temp.skipBurning == g_state.skipBurning &&
temp.skipConfusion == g_state.skipConfusion &&
temp.skipSlow == g_state.skipSlow &&
temp.addImmobilize == g_state.addImmobilize &&
temp.addBlind == g_state.addBlind &&
temp.fiveBleedingStacks == g_state.fiveBleedingStacks &&
temp.hitboxType == g_state.hitboxType &&
temp.addResistance == g_state.addResistance &&
temp.addStability == g_state.addStability &&
temp.skipAegis == g_state.skipAegis) {
currentTemplateName = temp.name;
foundMatchingTemplate = true;
break;
}
}
ImGui::Spacing();
ImGui::Text("Current: ");
ImGui::SameLine();
if (foundMatchingTemplate) {
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "%s", currentTemplateName.c_str());
}
else {
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "None");
}
ImGui::Separator();
ImGui::Spacing(); ImGui::Spacing();
if (ImGui::Button("Apply Boons", ImVec2(110, 0))) { if (ImGui::Button("Apply Boons", ImVec2(110, 0))) {
@ -407,7 +406,7 @@ void UIManager::RenderTemplatesTab() {
} }
ImGui::SetNextItemWidth(170); ImGui::SetNextItemWidth(170);
if (ImGui::Combo("##templateList", &currentUserIndex, userTemplateNames.data(), userTemplateNames.size())) { if (ImGui::Combo("##templateList", &currentUserIndex, userTemplateNames.data(), (int)userTemplateNames.size())) {
g_state.selectedTemplateIndex = userTemplateIndices[currentUserIndex]; g_state.selectedTemplateIndex = userTemplateIndices[currentUserIndex];
g_state.lastUserTemplateIndex = userTemplateIndices[currentUserIndex]; g_state.lastUserTemplateIndex = userTemplateIndices[currentUserIndex];
} }
@ -535,7 +534,7 @@ void UIManager::RenderTemplatesTab() {
TemplateManager::LoadTemplate(templateIndex); TemplateManager::LoadTemplate(templateIndex);
g_state.selectedTemplateIndex = -1; g_state.selectedTemplateIndex = -1;
} }
if (i < 2) ImGui::SameLine(0, 5);; if (i < 2) ImGui::SameLine(0, 5);
} }
} }
@ -556,7 +555,7 @@ void UIManager::RenderTemplatesTab() {
TemplateManager::LoadTemplate(templateIndex); TemplateManager::LoadTemplate(templateIndex);
g_state.selectedTemplateIndex = -1; g_state.selectedTemplateIndex = -1;
} }
if (i == 3) ImGui::SameLine(0, 5);; if (i == 3) ImGui::SameLine(0, 5);
} }
} }
} }
@ -588,4 +587,54 @@ void UIManager::RenderOptions() {
} }
ImGui::Checkbox("Enable debug mode", &g_state.debugMode); ImGui::Checkbox("Enable debug mode", &g_state.debugMode);
ImGui::Spacing();
ImGui::Separator();
ImGui::PushStyleColor(ImGuiCol_Header, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_HeaderHovered, ImVec4(0.3f, 0.5f, 0.7f, 0.8f));
if (ImGui::CollapsingHeader("Coordinate Calibration")) {
ImGui::Spacing();
if (g_state.calibrationMode) {
ImGui::TextColored(ImVec4(1.0f, 0.85f, 0.0f, 1.0f), "! Calibration active !");
ImGui::Spacing();
ImGui::TextWrapped("1. Interact with the Boon Console (press F)");
ImGui::TextWrapped("2. Hover your mouse over the middle of \"Adjust Self\" and click.");
ImGui::Spacing();
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "The addon window is hidden.");
ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "Press ESC to cancel.");
}
else {
if (g_state.hasCalibration) {
ImGui::TextColored(ImVec4(0.2f, 1.0f, 0.4f, 1.0f), "Status: Calibrated");
ImGui::Text("scaleX: %.4f scaleY: %.4f",
g_state.calibratedScaleX, g_state.calibratedScaleY);
}
else {
ImGui::TextColored(ImVec4(0.75f, 0.75f, 0.75f, 1.0f), "Status: Auto-scaling (default)");
}
ImGui::Spacing();
if (ImGui::Button("Calibrate", ImVec2(120, 0))) {
CoordinateUtils::StartCalibration();
}
if (g_state.hasCalibration) {
ImGui::SameLine(0, 6);
if (ImGui::Button("Reset to Default", ImVec2(120, 0))) {
CoordinateUtils::ResetCalibration();
}
}
ImGui::Spacing();
ImGui::TextDisabled("Interact with the Boon Console, then hover over");
ImGui::TextDisabled("the middle of \"Adjust Self\" and click.");
}
ImGui::Spacing();
}
ImGui::PopStyleColor(2);
} }