Skip to content

Commit

Permalink
Release 0.9.9.18 (#595)
Browse files Browse the repository at this point in the history
* Setup release 0.9.9.18

* Minimap transparency
Closes #594

* Toggle to display exploration percentage
Closes #593
  • Loading branch information
AlexMacocian authored Feb 18, 2024
1 parent 3d9106a commit 65830f9
Show file tree
Hide file tree
Showing 18 changed files with 529 additions and 15 deletions.
6 changes: 6 additions & 0 deletions Daybreak.GWCA/header/TitleInfoModule.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#pragma once
#include "httplib.h"

namespace Daybreak::Modules::TitleInfoModule {
void GetTitleInfo(const httplib::Request& req, httplib::Response& res);
}
19 changes: 19 additions & 0 deletions Daybreak.GWCA/header/payloads/TitleInfoPayload.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once
#include <cstdint>
#include <json.hpp>

using json = nlohmann::json;

namespace Daybreak {
struct TitleInfoPayload {
uint32_t TitleId = 0;
uint32_t TitleTierId = 0;
uint32_t CurrentTier = 0;
uint32_t CurrentPoints = 0;
uint32_t PointsNeededNextRank = 0;
bool IsPercentageBased = false;
std::string TitleName = "";
};

void to_json(json& j, const TitleInfoPayload& p);
}
164 changes: 164 additions & 0 deletions Daybreak.GWCA/source/TitleInfoModule.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#include "pch.h"
#include "TitleInfoModule.h"
#include "payloads/TitleInfoPayload.h"
#include <GWCA/GameContainers/Array.h>
#include <GWCA/Managers/GameThreadMgr.h>
#include <GWCA/Managers/MapMgr.h>
#include <GWCA/Managers/PlayerMgr.h>
#include <GWCA/Constants/AgentIDs.h>
#include <GWCA/GameEntities/Title.h>
#include <GWCA/Context/WorldContext.h>
#include <GWCA/Constants/Constants.h>
#include <GWCA/Managers/UIMgr.h>
#include <future>
#include <json.hpp>
#include <tuple>
#include <queue>
#include <Windows.h>
#include <cstdint>
#include <limits>
#include "Utils.h"

namespace Daybreak::Modules::TitleInfoModule {
const int MaxTries = 20;
std::vector<std::tuple<TitleInfoPayload, std::promise<TitleInfoPayload>*, std::wstring*, int>> WaitingList;
std::queue<std::tuple<uint32_t, std::promise<TitleInfoPayload>>*> PromiseQueue;
std::mutex GameThreadMutex;
GW::HookEntry GameThreadHook;
volatile bool initialized = false;

std::wstring* GetAsyncName(uint32_t titleTierIndex) {
const auto worldContext = GW::GetWorldContext();
const auto tiers = &worldContext->title_tiers;
const auto tier = &tiers->at(titleTierIndex);
auto description = new std::wstring();
GW::UI::AsyncDecodeStr(tier->tier_name_enc, description);
return description;
}

TitleInfoPayload GetPayload(uint32_t id) {
TitleInfoPayload payload;
if (!GW::Map::GetIsMapLoaded()) {
return payload;
}

const auto title = GW::PlayerMgr::GetTitleTrack((GW::Constants::TitleID)id);
if (!title) {
return payload;
}

if (title->current_points == 0 &&
title->current_title_tier_index == 0 &&
title->next_title_tier_index == 0 &&
title->max_title_rank == 0) {
return payload;
}

const auto tierIndex = title->current_title_tier_index;
const auto worldContext = GW::GetWorldContext();
const auto tiers = &worldContext->title_tiers;
const auto tier = &tiers->at(tierIndex);

payload.TitleId = id;
payload.TitleTierId = tierIndex;
payload.CurrentPoints = title->current_points;
payload.PointsNeededNextRank = title->points_needed_next_rank;
payload.IsPercentageBased = title->is_percentage_based();
payload.CurrentTier = tier->tier_number;
return payload;
}

void EnsureInitialized() {
GameThreadMutex.lock();
if (!initialized) {
GW::GameThread::RegisterGameThreadCallback(&GameThreadHook, [&](GW::HookStatus*) {
while (!PromiseQueue.empty()) {
auto promiseRequest = PromiseQueue.front();
std::promise<TitleInfoPayload> &promise = std::get<1>(*promiseRequest);
uint32_t id = std::get<0>(*promiseRequest);
PromiseQueue.pop();
try {
const auto payload = GetPayload(id);
if (payload.TitleId == 0 &&
payload.CurrentPoints == 0 &&
payload.PointsNeededNextRank == 0 &&
payload.TitleTierId == 0) {
TitleInfoPayload payload;
promise.set_value(payload);
continue;
}

auto name = GetAsyncName(payload.TitleTierId);
if (!name) {
continue;
}

WaitingList.emplace_back(payload, &promise, name, 0);
}
catch (...) {
TitleInfoPayload payload;
promise.set_value(payload);
}
}

for (auto i = 0; i < WaitingList.size(); ) {

Check warning on line 104 in Daybreak.GWCA/source/TitleInfoModule.cpp

View workflow job for this annotation

GitHub Actions / build (x86)

'<': signed/unsigned mismatch

Check warning on line 104 in Daybreak.GWCA/source/TitleInfoModule.cpp

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

'<': signed/unsigned mismatch

Check warning on line 104 in Daybreak.GWCA/source/TitleInfoModule.cpp

View workflow job for this annotation

GitHub Actions / build (x86)

'<': signed/unsigned mismatch

Check warning on line 104 in Daybreak.GWCA/source/TitleInfoModule.cpp

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

'<': signed/unsigned mismatch
auto item = &WaitingList[i];
auto name = std::get<2>(*item);
auto& tries = std::get<3>(*item);
if (name->empty() &&
tries < MaxTries) {
tries += 1;
i++;
continue;
}

auto promise = std::get<1>(*item);
auto payload = std::get<0>(*item);
payload.TitleName = Utils::WStringToString(*name);
WaitingList.erase(WaitingList.begin() + i);
delete(name);
promise->set_value(payload);
}
});

initialized = true;
}

GameThreadMutex.unlock();
}

void GetTitleInfo(const httplib::Request& req, httplib::Response& res) {
auto callbackEntry = new GW::HookEntry;
uint32_t id = 0;
auto it = req.params.find("id");
if (it == req.params.end()) {
res.status = 400;
res.set_content("Missing id parameter", "text/plain");
return;
}
else {
auto idStr = it->second;
size_t pos = 0;
auto result = std::stoul(idStr, &pos);
if (pos != idStr.size()) {
res.status = 400;
res.set_content("Invalid id parameter", "text/plain");
return;
}

id = static_cast<uint32_t>(result);
}

auto response = new std::tuple<uint32_t, std::promise<TitleInfoPayload>>();
std::get<0>(*response) = id;
std::promise<TitleInfoPayload>& promise = std::get<1>(*response);

EnsureInitialized();
PromiseQueue.emplace(response);

json responsePayload = promise.get_future().get();
delete callbackEntry;
delete response;
res.set_content(responsePayload.dump(), "text/json");
}
}
2 changes: 2 additions & 0 deletions Daybreak.GWCA/source/dllmain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "EntityNameModule.h"
#include "GameStateModule.h"
#include "ItemNameModule.h"
#include "TitleInfoModule.h"
#include <mutex>

volatile bool initialized;
Expand Down Expand Up @@ -72,6 +73,7 @@ static DWORD WINAPI StartHttpServer(LPVOID)
http::server::Get("/session", Daybreak::Modules::SessionModule::GetSessionInfo);
http::server::Get("/entities/name", Daybreak::Modules::EntityNameModule::GetName);
http::server::Get("/items/name", Daybreak::Modules::ItemNameModule::GetName);
http::server::Get("/titles/info", Daybreak::Modules::TitleInfoModule::GetTitleInfo);
http::server::StartServer();
return 0;
}
Expand Down
20 changes: 20 additions & 0 deletions Daybreak.GWCA/source/payloads/TitleInfoPayload.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <cstdint>
#include <json.hpp>
#include <payloads/TitleInfoPayload.h>

using json = nlohmann::json;

namespace Daybreak {
void to_json(json& j, const TitleInfoPayload& p) {
j = json
{
{"TitleId", p.TitleId},
{"TitleTierId", p.TitleTierId},
{"TitleName", p.TitleName},
{"CurrentPoints", p.CurrentPoints},
{"PointsNeededNextRank", p.PointsNeededNextRank},
{"IsPercentageBased", p.IsPercentageBased},
{"CurrentTier", p.CurrentTier}
};
}
}
8 changes: 8 additions & 0 deletions Daybreak/Configuration/Options/FocusViewOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,14 @@ public sealed class FocusViewOptions
[OptionName(Name = "Minimap Rotation", Description = "When enabled, the minimap will rotate according to the player camera")]
public bool MinimapRotationEnabled { get; set; } = true;

[JsonProperty(nameof(MinimapTransparency))]
[OptionName(Name = "Minimap Transparency", Description = "When enabled, the minimap window will be transparent")]
public bool MinimapTransparency { get; set; } = true;

[JsonProperty(nameof(ShowCartoProgress))]
[OptionName(Name = "Show Cartographer Progress", Description = "When enabled, the minimap will show live cartographer progress")]
public bool ShowCartoProgress { get; set; } = false;

[JsonProperty(nameof(BrowserHistory))]
[OptionIgnore]
public BrowserHistory BrowserHistory { get; set; } = new();
Expand Down
2 changes: 1 addition & 1 deletion Daybreak/Daybreak.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<LangVersion>preview</LangVersion>
<ApplicationIcon>Daybreak.ico</ApplicationIcon>
<IncludePackageReferencesDuringMarkupCompilation>true</IncludePackageReferencesDuringMarkupCompilation>
<Version>0.9.9.17</Version>
<Version>0.9.9.18</Version>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<UserSecretsId>cfb2a489-db80-448d-a969-80270f314c46</UserSecretsId>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
Expand Down
16 changes: 12 additions & 4 deletions Daybreak/Launch/MinimapWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,29 @@
ShowSystemMenuOnRightClick="False"
ShowInTaskbar="True"
ShowCloseButton="True"
Background="Transparent"
AllowsTransparency="True"
Title="Daybreak Minimap"
Topmost="{Binding ElementName=_this, Path=Pinned, Mode=OneWay}"
x:Name="_this"
Height="450" Width="800">
<Grid>
<Grid Background="{StaticResource Daybreak.Brushes.Background}">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Rectangle Fill="{StaticResource MahApps.Brushes.ThemeBackground}"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Grid.RowSpan="2"
Visibility="{Binding ElementName=_this, Path=Opaque, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<ContentPresenter Grid.RowSpan="2"
Content="{Binding ElementName=_this, Path=Content, Mode=OneWay}"/>
<Rectangle Height="30"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
Grid.Row="0"
Fill="Transparent"
MouseLeftButtonDown="Grid_MouseLeftButtonDown" />
<buttons:HighlightButton Height="30"
Width="50"
Expand All @@ -53,10 +63,8 @@
Stroke="{StaticResource MahApps.Brushes.ThemeForeground}"
Visibility="{Binding Path=Pinned, RelativeSource={RelativeSource AncestorType={x:Type launch:MinimapWindow}}, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Grid>

</buttons:HighlightButton.ButtonContent>
</buttons:HighlightButton>
<ContentPresenter Grid.Row="1"
Content="{Binding ElementName=_this, Path=Content, Mode=OneWay}"/>
</Grid>
</mah:MetroWindow>
2 changes: 2 additions & 0 deletions Daybreak/Launch/MinimapWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ namespace Daybreak.Launch;
/// </summary>
public partial class MinimapWindow : MetroWindow
{
[GenerateDependencyProperty]
private bool opaque = false;
[GenerateDependencyProperty]
private bool pinned = false;
[GenerateDependencyProperty]
Expand Down
64 changes: 64 additions & 0 deletions Daybreak/Models/FocusView/CartoProgressContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Daybreak.Models.Guildwars;
using System.ComponentModel;

namespace Daybreak.Models.FocusView;
public sealed class CartoProgressContext : INotifyPropertyChanged
{
private TitleInformationExtended? titleInformationExtended;
private double percentage;
private Continent? continent;
private string? resolvedName;
private string? titleName;

public event PropertyChangedEventHandler? PropertyChanged;

public TitleInformationExtended? TitleInformationExtended
{
get => this.titleInformationExtended;
set
{
this.titleInformationExtended = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.TitleInformationExtended)));
}
}

public double Percentage
{
get => this.percentage;
set
{
this.percentage = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Percentage)));
}
}

public Continent? Continent
{
get => this.continent;
set
{
this.continent = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.Continent)));
}
}

public string? ResolvedName
{
get => this.resolvedName;
set
{
this.resolvedName = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.ResolvedName)));
}
}

public string? TitleName
{
get => this.titleName;
set
{
this.titleName = value;
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(this.TitleName)));
}
}
}
6 changes: 3 additions & 3 deletions Daybreak/Models/Guildwars/Title.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public sealed class Title : IWikiEntity
{
public static readonly Title None = new() { Id = 0xFF, };
public static readonly Title Hero = new() { Id = 0, Name = "Hero", WikiUrl = "https://wiki.guildwars.com/wiki/Hero_(title)", Tiers = ["Hero", "Fierce Hero", "Mighty Hero", "Deadly Hero", "Terrifying Hero", "Conquering Hero", "Subjugating Hero", "Vanquishing Hero", "Renowed Hero", "Illustrious Hero", "Eminent Hero", "King's Hero", "Emperor's Hero", "Balthazar's Hero", "Legendary Hero"] };
public static readonly Title TyrianCartographer = new() { Id = 1, Name = "Cartographer", WikiUrl = "https://wiki.guildwars.com/wiki/Cartographer", Tiers = ["Tyrian Explorer", "Tyrian Pathfinder", "Tyrian Trailblazer", "Tyrian Cartographer", "Tyrian Master Cartographer", "Tyrian Grandmaster Cartographer"] };
public static readonly Title CanthanCartographer = new() { Id = 2, Name = "Cartographer", WikiUrl = "https://wiki.guildwars.com/wiki/Cartographer", Tiers = ["Canthan Explorer", "Canthan Pathfinder", "Canthan Trailblazer", "Canthan Cartographer", "Canthan Master Cartographer", "Canthan Grandmaster Cartographer"] };
public static readonly Title TyrianCartographer = new() { Id = 1, Name = "Tyrian Cartographer", WikiUrl = "https://wiki.guildwars.com/wiki/Cartographer", Tiers = ["Tyrian Explorer", "Tyrian Pathfinder", "Tyrian Trailblazer", "Tyrian Cartographer", "Tyrian Master Cartographer", "Tyrian Grandmaster Cartographer"] };
public static readonly Title CanthanCartographer = new() { Id = 2, Name = "Canthan Cartographer", WikiUrl = "https://wiki.guildwars.com/wiki/Cartographer", Tiers = ["Canthan Explorer", "Canthan Pathfinder", "Canthan Trailblazer", "Canthan Cartographer", "Canthan Master Cartographer", "Canthan Grandmaster Cartographer"] };
public static readonly Title Gladiator = new() { Id = 3, Name = "Gladiator", WikiUrl = "https://wiki.guildwars.com/wiki/Gladiator", Tiers = ["Gladiator", "Fierce Gladiator", "Mighty Gladiator", "Deadly Gladiator", "Terrifying Gladiator", "Conquering Gladiator", "Subjugating Gladiator", "Vanquishing Gladiator", "King's Gladiator", "Emperor's Gladiator", "Balthazar's Gladiator", "Legendary Gladiator"] };
public static readonly Title Champion = new() { Id = 4, Name = "Champion", WikiUrl = "https://wiki.guildwars.com/wiki/Champion", Tiers = ["Champion", "Fierce Champion", "Mighty Champion", "Deadly Champion", "Terrifying Champion", "Conquering Champion", "Subjugating Champion", "Vanquishing Champion", "King's Champion", "Emperor's Champion", "Balthazar's Champion", "Legendary Champion"] };
public static readonly Title Kurzick = new() { Id = 5, Name = "Faction Allegiance", WikiUrl = "https://wiki.guildwars.com/wiki/Allegiance_rank", Tiers = ["Kurzick Supporter", "Friend of the Kurzicks", "Companion of the Kurzicks", "Ally of the Kurzicks", "Sentinel of the Kurzicks", "Steward of the Kurzicks", "Defender of the Kurzicks", "Warden of the Kurzicks", "Bastion of the Kurzicks", "Champion of the Kurzicks", "Hero of the Kurzicks", "Savior of the Kurzicks"] };
Expand All @@ -25,7 +25,7 @@ public sealed class Title : IWikiEntity
public static readonly Title Lucky = new() { Id = 15, Name = "Lucky", WikiUrl = "https://wiki.guildwars.com/wiki/Lucky_and_Unlucky", Tiers = ["Charmed", "Lucky", "Favored", "Prosperous", "Golden", "Blessed by Fate"] };
public static readonly Title Unlucky = new() { Id = 16, Name = "Unlucky", WikiUrl = "https://wiki.guildwars.com/wiki/Lucky_and_Unlucky", Tiers = ["Hapless", "Unlucky", "Unfavored", "Tragic", "Wretched", "Jinxed", "Cursed by Fate"] };
public static readonly Title Sunspear = new() { Id = 17, Name = "Sunspear", WikiUrl = "https://wiki.guildwars.com/wiki/Sunspear_rank", Tiers = ["Sunspear Sergeant", "Sunspear Master Sergeant", "Second Spear", "First Spear", "Sunspear Captain", "Sunspear Commander", "Sunspear General", "Sunspear Castellan", "Spearmarshal", "Legendary Spearmarshal"] };
public static readonly Title ElonianCartographer = new() { Id = 18, Name = "Cartographer", WikiUrl = "https://wiki.guildwars.com/wiki/Cartographer", Tiers = ["Elonian Explorer", "Elonian Pathfinder", "Elonian Trailblazer", "Elonian Cartographer", "Elonian Master Cartographer", "Elonian Grandmaster Cartographer"] };
public static readonly Title ElonianCartographer = new() { Id = 18, Name = "Elonian Cartographer", WikiUrl = "https://wiki.guildwars.com/wiki/Cartographer", Tiers = ["Elonian Explorer", "Elonian Pathfinder", "Elonian Trailblazer", "Elonian Cartographer", "Elonian Master Cartographer", "Elonian Grandmaster Cartographer"] };
public static readonly Title ProtectorElona = new() { Id = 19, Name = "Protector of Elona", WikiUrl = "https://wiki.guildwars.com/wiki/Protector", Tiers = ["Protector of Elona"] };
public static readonly Title Lightbringer = new() { Id = 20, Name = "Lightbringer", WikiUrl = "https://wiki.guildwars.com/wiki/Lightbringer_rank", Tiers = ["Lightbringer", "Adept Lightbringer", "Brave Lightbringer", "Mighty Lightbringer", "Conquering Lightbringer", "Vanquishing Lightbringer", "Revered Lightbringer", "Holy Lightbringer"] };
public static readonly Title LegendaryDefenderOfAscalon = new() { Id = 21, Name = "Defender of Ascalon", WikiUrl = "https://wiki.guildwars.com/wiki/Defender_of_Ascalon", Tiers = ["Legendary Defender of Ascalon"] };
Expand Down
Loading

0 comments on commit 65830f9

Please sign in to comment.