mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-09-19 14:55:24 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
83429e264a | ||
|
|
ffdf374cdd | ||
|
|
aed34608ee | ||
|
|
18981e131d | ||
|
|
fe0a77d89d | ||
|
|
dbfd555f0d | ||
|
|
85d4ac3a29 | ||
|
|
dcb983d3de | ||
|
|
ba1e4348ad | ||
|
|
04bc7cffc6 | ||
|
|
01b74a936b | ||
|
|
654b6bc10f | ||
|
|
c9e6b9f626 | ||
|
|
554f1aea9b | ||
|
|
6fb817855c | ||
|
|
b3f5fadfe3 | ||
|
|
9275b7a7f8 | ||
|
|
2d06ff85b8 | ||
|
|
07d06313d9 | ||
|
|
546409130c | ||
|
|
82436872f8 | ||
|
|
ad6f76af0c | ||
|
|
8f0ad91a22 | ||
|
|
6a70c5b7cc | ||
|
|
5f5d67fdf2 | ||
|
|
150b41647a | ||
|
|
66e52e7324 | ||
|
|
23328240a9 | ||
|
|
3f06e9486d | ||
|
|
52a8e912e9 | ||
|
|
641aa8250e | ||
|
|
faa47f32c7 | ||
|
|
e3dac780df | ||
|
|
dfc0f12150 | ||
|
|
638fb38fa0 | ||
|
|
4d6a06ccdf | ||
|
|
3228604876 | ||
|
|
44879924d6 | ||
|
|
6b7478f6ae | ||
|
|
f54d95c1e7 | ||
|
|
5315a45a98 | ||
|
|
8575d970a3 | ||
|
|
5c611eca4f | ||
|
|
71aa37b965 | ||
|
|
0f1b1cc83d | ||
|
|
198c1e6eb3 | ||
|
|
e7c9111a1f | ||
|
|
1191e996db | ||
|
|
19de3c99f9 | ||
|
|
d857b6dc26 | ||
|
|
68b5b79035 | ||
|
|
318032d998 | ||
|
|
483860532f | ||
|
|
694dbe82a9 | ||
|
|
a936f24e32 | ||
|
|
a2ec87ced2 | ||
|
|
48955a7dd7 | ||
|
|
9d7c6c29b0 | ||
|
|
902d210dfd | ||
|
|
57867851a9 | ||
|
|
ca82cc92bf | ||
|
|
569ce1e656 | ||
|
|
a96c6c3f66 | ||
|
|
28c3b2f8c1 | ||
|
|
deeccd81b4 | ||
|
|
6a27245ae8 | ||
|
|
6f4063abb7 | ||
|
|
f307bcd665 | ||
|
|
02ca41ba6d | ||
|
|
a0ebc87b7c | ||
|
|
d21c55deb0 | ||
|
|
50a8131ec2 | ||
|
|
e70cfa3d99 | ||
|
|
4c0c45f1c7 | ||
|
|
a9e819a83a | ||
|
|
65830f9125 | ||
|
|
3d9106addf | ||
|
|
2863032d9a | ||
|
|
aada189b1b | ||
|
|
22ea4de66e | ||
|
|
9859617a74 | ||
|
|
0629c1929d | ||
|
|
6ec45396c3 | ||
|
|
b76a9b80fb | ||
|
|
f4bc58bbf7 | ||
|
|
09fdbb6f95 | ||
|
|
8ee7ed23e4 | ||
|
|
8d8a78cf88 | ||
|
|
f0652231cb | ||
|
|
d9aa760256 | ||
|
|
a65f375255 | ||
|
|
3ee58e2711 | ||
|
|
9b3b083aec | ||
|
|
ecb9b8f172 | ||
|
|
5765c27240 | ||
|
|
c53456112a | ||
|
|
ccea21886e | ||
|
|
37be8de5ac | ||
|
|
107e43a15f |
@@ -46,6 +46,8 @@ jobs:
|
||||
run: |
|
||||
$changeLog = git log --no-merges --pretty="%h - %s (%an)<br />" ${{ env.LatestReleaseTag }}..HEAD
|
||||
echo "::set-env name=Changelog::$changeLog"
|
||||
mkdir Publish
|
||||
echo $changeLog > .\Publish\changelog.txt
|
||||
env:
|
||||
LatestReleaseTag: ${{steps.getLatestTag.outputs.tag}}
|
||||
|
||||
@@ -55,7 +57,7 @@ jobs:
|
||||
dotnet-version: '8.x'
|
||||
|
||||
- name: Setup MSBuild.exe
|
||||
uses: microsoft/setup-msbuild@v1.3.1
|
||||
uses: microsoft/setup-msbuild@v2.0.0
|
||||
|
||||
- name: Fetch submodules
|
||||
run: |
|
||||
@@ -110,7 +112,7 @@ jobs:
|
||||
shell: pwsh
|
||||
|
||||
- name: Create release draft
|
||||
uses: Xotl/cool-github-releases@v1.1.8
|
||||
uses: Xotl/cool-github-releases@v1.1.10
|
||||
with:
|
||||
mode: update
|
||||
tag_name: v${{ env.Version }}
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
dotnet-version: '8.x'
|
||||
|
||||
- name: Setup MSBuild.exe
|
||||
uses: microsoft/setup-msbuild@v1.3.1
|
||||
uses: microsoft/setup-msbuild@v2.0.0
|
||||
|
||||
- name: Fetch submodules
|
||||
run: |
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
run: cmake -S . -B build -A Win32
|
||||
|
||||
- name: Execute Unit Tests
|
||||
run: dotnet test $env:Test_Project_Path --property:SolutionDir=$env:GITHUB_WORKSPACE
|
||||
run: dotnet test $env:Test_Project_Path --property:SolutionDir=$env:GITHUB_WORKSPACE -p:ParallelizeTestCollections=false
|
||||
|
||||
- name: Restore the Wpf application to populate the obj folder
|
||||
run: msbuild $env:Solution_Path /t:Restore /p:Configuration=$env:Configuration /p:RuntimeIdentifier=$env:RuntimeIdentifier --property:SolutionDir=$env:GITHUB_WORKSPACE
|
||||
|
||||
+3
-1
@@ -362,4 +362,6 @@ MigrationBackup/
|
||||
.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
FodyWeavers.xsd
|
||||
|
||||
.idea
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="SharpCompress" Version="0.35.0" />
|
||||
<PackageReference Include="SharpCompress" Version="0.37.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -2,17 +2,28 @@
|
||||
# project specific logic here.
|
||||
#
|
||||
|
||||
project(Daybreak.GWCA)
|
||||
project(Daybreak.GWCA VERSION 1.0.0)
|
||||
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
message(FATAL_ERROR "You are configuring a 64bit build, this is not supported. Run cmake with `-A Win32`")
|
||||
endif()
|
||||
|
||||
set(VERSION_MAJOR 0)
|
||||
set(VERSION_MINOR 9)
|
||||
set(VERSION_PATCH 9)
|
||||
set(VERSION_TWEAK 41)
|
||||
|
||||
set(VERSION_RC "${CMAKE_CURRENT_BINARY_DIR}/version.rc")
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in" "${VERSION_RC}" @ONLY)
|
||||
|
||||
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_SOURCE_DIR}/bin")
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(Daybreak.GWCA_FOLDER "${CMAKE_CURRENT_LIST_DIR}/")
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
|
||||
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}/bin/install)
|
||||
add_compile_options(/MP /permissive-)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/")
|
||||
|
||||
add_library(Daybreak.GWCA SHARED)
|
||||
@@ -23,11 +34,13 @@ file(GLOB SOURCES
|
||||
"header/*.h*"
|
||||
"header/*/*.h")
|
||||
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${SOURCES})
|
||||
target_sources(Daybreak.GWCA PRIVATE ${SOURCES})
|
||||
source_group("Generated Files" FILES ${VERSION_RC})
|
||||
target_sources(Daybreak.GWCA PRIVATE ${SOURCES} ${VERSION_RC})
|
||||
target_compile_features(Daybreak.GWCA PUBLIC cxx_std_17)
|
||||
target_compile_definitions(Daybreak.GWCA PRIVATE
|
||||
$<$<CONFIG:Debug>:BUILD_TYPE_DEBUG>
|
||||
$<$<CONFIG:Release>:BUILD_TYPE_RELEASE>
|
||||
NOMINMAX
|
||||
)
|
||||
|
||||
target_precompile_headers(Daybreak.GWCA PRIVATE "header/pch.h")
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <httplib.h>
|
||||
#include <GWCA/Managers/GameThreadMgr.h>
|
||||
#include <thread>
|
||||
#include <json.hpp>
|
||||
#include <semaphore>
|
||||
#include <future>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
using nlohmann::json;
|
||||
|
||||
namespace Daybreak {
|
||||
template <typename TPayload, typename TContext>
|
||||
class DaybreakModule {
|
||||
protected:
|
||||
std::future<std::optional<TPayload>> EnqueueWithReturn(const TContext context) {
|
||||
std::shared_ptr<std::promise<std::optional<TPayload>>> promise = std::make_shared<std::promise<std::optional<TPayload>>>();
|
||||
std::future<std::optional<TPayload>> ret = promise->get_future();
|
||||
GW::GameThread::Enqueue([this, context, promise]() {
|
||||
try {
|
||||
const std::optional<TPayload> result = this->GetPayload(context);
|
||||
promise->set_value(result);
|
||||
}
|
||||
catch (...) {
|
||||
promise->set_exception(std::current_exception());
|
||||
}
|
||||
});
|
||||
|
||||
return ret;
|
||||
}
|
||||
virtual std::optional<TContext> GetContext(const httplib::Request& req, httplib::Response& res) = 0;
|
||||
virtual bool CanReturn(const httplib::Request& req, httplib::Response& res, const TPayload& payload) {
|
||||
return true;
|
||||
}
|
||||
virtual std::tuple<std::string, std::string> ReturnPayload(TPayload payload) {
|
||||
return std::make_tuple(static_cast<json>(payload).dump(), "application/json");
|
||||
}
|
||||
virtual void OnNoValue(httplib::Response& res) {
|
||||
res.status = 500;
|
||||
res.body = "No payload";
|
||||
}
|
||||
virtual std::optional<TPayload> GetPayload(const TContext context) = 0;
|
||||
public:
|
||||
virtual std::string ApiUri() = 0;
|
||||
void HandleApiCall(const httplib::Request& req, httplib::Response& res) {
|
||||
const std::optional<TContext> context = this->GetContext(req, res);
|
||||
if (!context.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::future<std::optional<TPayload>> future = this->EnqueueWithReturn(context.value());
|
||||
std::optional<TPayload> payload = future.get();
|
||||
if (!payload.has_value()) {
|
||||
this->OnNoValue(res);
|
||||
return;
|
||||
}
|
||||
|
||||
while (!this->CanReturn(req, res, payload.value())) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string> contentResponse = this->ReturnPayload(payload.value());
|
||||
res.status = 200;
|
||||
res.set_content(std::get<0>(contentResponse), std::get<1>(contentResponse));
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,18 @@
|
||||
#pragma once
|
||||
#include "httplib.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <payloads/NamePayload.h>
|
||||
|
||||
namespace Daybreak::Modules::EntityNameModule {
|
||||
void GetName(const httplib::Request& req, httplib::Response& res);
|
||||
namespace Daybreak::Modules {
|
||||
class EntityNameModule : public DaybreakModule<NamePayload, uint32_t> {
|
||||
public:
|
||||
std::string ApiUri() override;
|
||||
protected:
|
||||
std::optional<uint32_t> GetContext(const httplib::Request& req, httplib::Response& res) override;
|
||||
std::optional<NamePayload> GetPayload(uint32_t context) override;
|
||||
bool CanReturn(const httplib::Request& req, httplib::Response& res, const NamePayload& payload) override;
|
||||
std::tuple<std::string, std::string> ReturnPayload(NamePayload payload) override;
|
||||
private:
|
||||
std::wstring name;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
#pragma once
|
||||
#include "httplib.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <payloads/GamePayload.h>
|
||||
|
||||
namespace Daybreak::Modules::GameModule {
|
||||
void GetGameInfo(const httplib::Request&, httplib::Response& res);
|
||||
namespace Daybreak::Modules {
|
||||
class GameModule : public DaybreakModule<GamePayload, uint32_t> {
|
||||
public:
|
||||
std::string ApiUri() override;
|
||||
protected:
|
||||
std::optional<uint32_t> GetContext(const httplib::Request& req, httplib::Response& res) override;
|
||||
std::optional<GamePayload> GetPayload(const uint32_t) override;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
#pragma once
|
||||
#include "httplib.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <payloads/GameStatePayload.h>
|
||||
|
||||
namespace Daybreak::Modules::GameStateModule {
|
||||
void GetGameStateInfo(const httplib::Request&, httplib::Response& res);
|
||||
namespace Daybreak::Modules {
|
||||
class GameStateModule : public DaybreakModule<GameStatePayload, uint32_t> {
|
||||
public:
|
||||
std::string ApiUri() override;
|
||||
protected:
|
||||
std::optional<uint32_t> GetContext(const httplib::Request& req, httplib::Response& res) override;
|
||||
std::optional<GameStatePayload> GetPayload(const uint32_t) override;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
#pragma once
|
||||
#include "httplib.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <payloads/InventoryPayload.h>
|
||||
|
||||
namespace Daybreak::Modules::InventoryModule {
|
||||
void GetInventoryInfo(const httplib::Request&, httplib::Response& res);
|
||||
namespace Daybreak::Modules {
|
||||
class InventoryModule : public DaybreakModule<InventoryPayload, uint32_t> {
|
||||
protected:
|
||||
std::optional<uint32_t> GetContext(const httplib::Request& req, httplib::Response& res) override;
|
||||
std::optional<InventoryPayload> GetPayload(const uint32_t) override;
|
||||
public:
|
||||
std::string ApiUri() override;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,19 @@
|
||||
#pragma once
|
||||
#include "httplib.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <payloads/NamePayload.h>
|
||||
#include <GWCA/GameEntities/Item.h>
|
||||
|
||||
namespace Daybreak::Modules::ItemNameModule {
|
||||
void GetName(const httplib::Request& req, httplib::Response& res);
|
||||
namespace Daybreak::Modules {
|
||||
class ItemNameModule : public DaybreakModule<NamePayload, GW::Item*> {
|
||||
public:
|
||||
std::string ApiUri() override;
|
||||
protected:
|
||||
std::optional<GW::Item*> GetContext(const httplib::Request& req, httplib::Response& res) override;
|
||||
std::optional<NamePayload> GetPayload(GW::Item* context) override;
|
||||
bool CanReturn(const httplib::Request& req, httplib::Response& res, const NamePayload& payload) override;
|
||||
std::tuple<std::string, std::string> ReturnPayload(NamePayload payload) override;
|
||||
private:
|
||||
std::wstring name;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
#pragma once
|
||||
#include "httplib.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <payloads/LoginPayload.h>
|
||||
|
||||
namespace Daybreak::Modules::LoginModule {
|
||||
void GetLoginInfo(const httplib::Request&, httplib::Response& res);
|
||||
namespace Daybreak::Modules {
|
||||
class LoginModule : public DaybreakModule<LoginPayload, uint32_t>{
|
||||
public:
|
||||
std::string ApiUri() override;
|
||||
protected:
|
||||
std::optional<uint32_t> GetContext(const httplib::Request& req, httplib::Response& res) override;
|
||||
std::optional<LoginPayload> GetPayload(const uint32_t) override;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
#pragma once
|
||||
#include "httplib.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <payloads/MainPlayer.h>
|
||||
|
||||
namespace Daybreak::Modules::MainPlayerModule {
|
||||
void GetMainPlayer(const httplib::Request&, httplib::Response& res);
|
||||
namespace Daybreak::Modules {
|
||||
class MainPlayerModule : public DaybreakModule<MainPlayer, uint32_t> {
|
||||
public:
|
||||
std::string ApiUri() override;
|
||||
protected:
|
||||
std::optional<uint32_t> GetContext(const httplib::Request& req, httplib::Response& res) override;
|
||||
std::optional<MainPlayer> GetPayload(const uint32_t) override;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
#pragma once
|
||||
#include "httplib.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <payloads/MapPayload.h>
|
||||
|
||||
namespace Daybreak::Modules::MapModule {
|
||||
void GetMapInfo(const httplib::Request&, httplib::Response& res);
|
||||
namespace Daybreak::Modules {
|
||||
class MapModule : public DaybreakModule<MapPayload, uint32_t> {
|
||||
public:
|
||||
std::string ApiUri() override;
|
||||
protected:
|
||||
std::optional<uint32_t> GetContext(const httplib::Request& req, httplib::Response& res) override;
|
||||
std::optional<MapPayload> GetPayload(const uint32_t) override;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
#pragma once
|
||||
#include "httplib.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <payloads/PathingMetadataPayload.h>
|
||||
|
||||
namespace Daybreak::Modules::PathingMetadataModule {
|
||||
void GetPathingMetadata(const httplib::Request&, httplib::Response& res);
|
||||
namespace Daybreak::Modules {
|
||||
class PathingMetadataModule : public DaybreakModule<PathingMetadataPayload, uint32_t> {
|
||||
public:
|
||||
std::string ApiUri() override;
|
||||
protected:
|
||||
std::optional<uint32_t> GetContext(const httplib::Request& req, httplib::Response& res) override;
|
||||
std::optional<PathingMetadataPayload> GetPayload(const uint32_t) override;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
#pragma once
|
||||
#include "httplib.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <payloads/PathingPayload.h>
|
||||
|
||||
namespace Daybreak::Modules::PathingModule {
|
||||
void GetPathingData(const httplib::Request&, httplib::Response& res);
|
||||
namespace Daybreak::Modules {
|
||||
class PathingModule : public DaybreakModule<PathingPayload, uint32_t> {
|
||||
public:
|
||||
std::string ApiUri() override;
|
||||
protected:
|
||||
std::optional<uint32_t> GetContext(const httplib::Request& req, httplib::Response& res) override;
|
||||
std::optional<PathingPayload> GetPayload(const uint32_t) override;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
#pragma once
|
||||
#include "httplib.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <payloads/PreGamePayload.h>
|
||||
|
||||
namespace Daybreak::Modules::PreGameModule {
|
||||
void GetPreGameInfo(const httplib::Request&, httplib::Response& res);
|
||||
namespace Daybreak::Modules {
|
||||
class PreGameModule : public DaybreakModule<PreGamePayload, uint32_t> {
|
||||
public:
|
||||
std::string ApiUri() override;
|
||||
protected:
|
||||
std::optional<uint32_t> GetContext(const httplib::Request& req, httplib::Response& res) override;
|
||||
std::optional<PreGamePayload> GetPayload(const uint32_t) override;
|
||||
};
|
||||
}
|
||||
@@ -8,5 +8,6 @@ namespace http {
|
||||
void StopServer();
|
||||
void SetLogger(httplib::Logger logger);
|
||||
void Get(const std::string& pattern, httplib::Server::Handler handler);
|
||||
void Post(const std::string& pattern, httplib::Server::Handler handler);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
#pragma once
|
||||
#include "httplib.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <payloads/SessionPayload.h>
|
||||
|
||||
namespace Daybreak::Modules::SessionModule {
|
||||
void GetSessionInfo(const httplib::Request&, httplib::Response& res);
|
||||
namespace Daybreak::Modules {
|
||||
class SessionModule : public DaybreakModule<SessionPayload, uint32_t> {
|
||||
public:
|
||||
std::string ApiUri() override;
|
||||
protected:
|
||||
std::optional<uint32_t> GetContext(const httplib::Request& req, httplib::Response& res) override;
|
||||
std::optional<SessionPayload> GetPayload(const uint32_t) override;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "httplib.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <TitleInfoModule.h>
|
||||
#include <payloads/TitleInfoPayload.h>
|
||||
#include <tuple>
|
||||
#include <string>
|
||||
|
||||
namespace Daybreak::Modules {
|
||||
class TitleInfoModule : public DaybreakModule<TitleInfoPayload, uint32_t> {
|
||||
public:
|
||||
std::string ApiUri() override;
|
||||
protected:
|
||||
std::optional<TitleInfoPayload> GetPayload(const uint32_t context) override;
|
||||
std::tuple<std::string, std::string> ReturnPayload(TitleInfoPayload payload) override;
|
||||
bool CanReturn(const httplib::Request& req, httplib::Response& res, const TitleInfoPayload& payload) override;
|
||||
std::optional<uint32_t> GetContext(const httplib::Request& req, httplib::Response& res) override;
|
||||
private:
|
||||
std::wstring name;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,14 @@
|
||||
#pragma once
|
||||
#include "httplib.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <payloads/UserPayload.h>
|
||||
|
||||
namespace Daybreak::Modules::UserModule {
|
||||
void GetUserInfo(const httplib::Request&, httplib::Response& res);
|
||||
namespace Daybreak::Modules {
|
||||
class UserModule : public DaybreakModule<UserPayload, uint32_t> {
|
||||
public:
|
||||
std::string ApiUri() override;
|
||||
protected:
|
||||
std::optional<uint32_t> GetContext(const httplib::Request& req, httplib::Response& res) override;
|
||||
std::optional<UserPayload> GetPayload(const uint32_t) override;
|
||||
};
|
||||
}
|
||||
@@ -3,5 +3,6 @@
|
||||
|
||||
namespace Daybreak::Utils {
|
||||
std::string WStringToString(const std::wstring& wstr);
|
||||
std::wstring StringToWString(const std::string& str);
|
||||
bool StringToInt(const std::string& str, int& outValue);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
#include "httplib.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <string>
|
||||
|
||||
namespace Daybreak::Modules {
|
||||
class WhisperModule : public DaybreakModule<bool, std::wstring> {
|
||||
public:
|
||||
std::string ApiUri() override;
|
||||
protected:
|
||||
std::optional<std::wstring> GetContext(const httplib::Request& req, httplib::Response& res) override;
|
||||
std::optional<bool> GetPayload(const std::wstring message) override;
|
||||
std::tuple<std::string, std::string> ReturnPayload(bool) override;
|
||||
};
|
||||
}
|
||||
+8220
-8254
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace Daybreak {
|
||||
struct Camera {
|
||||
float Yaw;
|
||||
float Pitch;
|
||||
};
|
||||
|
||||
void to_json(json& j, const Camera& p);
|
||||
}
|
||||
@@ -2,11 +2,13 @@
|
||||
#include <cstdint>
|
||||
#include <json.hpp>
|
||||
#include <payloads/StatePayload.h>
|
||||
#include <payloads/Camera.h>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace Daybreak {
|
||||
struct GameStatePayload {
|
||||
Camera Camera;
|
||||
std::list<StatePayload> States;
|
||||
};
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Daybreak {
|
||||
uint32_t EntityAllegiance = 0;
|
||||
float Health;
|
||||
float Energy;
|
||||
float RotationAngle;
|
||||
};
|
||||
|
||||
void to_json(json& j, const LivingEntity& p);
|
||||
|
||||
@@ -6,7 +6,7 @@ using json = nlohmann::json;
|
||||
|
||||
namespace Daybreak {
|
||||
struct PreGamePayload {
|
||||
int ChosenCharacterIndex;
|
||||
int ChosenCharacterIndex = -1;
|
||||
std::list<std::string> Characters;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Daybreak {
|
||||
uint32_t State = 0;
|
||||
float Health = 0;
|
||||
float Energy = 0;
|
||||
float RotationAngle = 0;
|
||||
};
|
||||
|
||||
void to_json(json& j, const StatePayload& p);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -16,90 +16,20 @@
|
||||
#include <limits>
|
||||
#include "Utils.h"
|
||||
|
||||
namespace Daybreak::Modules::EntityNameModule {
|
||||
std::vector<std::tuple<uint32_t, std::promise<NamePayload>*, std::wstring*>> WaitingList;
|
||||
std::queue<std::tuple<uint32_t, std::promise<NamePayload>>*> PromiseQueue;
|
||||
std::mutex GameThreadMutex;
|
||||
GW::HookEntry GameThreadHook;
|
||||
volatile bool initialized = false;
|
||||
|
||||
std::wstring* GetAsyncName(uint32_t id) {
|
||||
NamePayload namePayload;
|
||||
auto agent = GW::Agents::GetAgentByID(id);
|
||||
if (!agent) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto agentLiving = agent->GetAsAgentLiving();
|
||||
if (!agentLiving) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto name = new std::wstring();
|
||||
if (!GW::Agents::AsyncGetAgentName(agentLiving, *name)) {
|
||||
delete(name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return name;
|
||||
namespace Daybreak::Modules {
|
||||
std::string EntityNameModule::ApiUri()
|
||||
{
|
||||
return "/entities/name";
|
||||
}
|
||||
|
||||
void EnsureInitialized() {
|
||||
GameThreadMutex.lock();
|
||||
if (!initialized) {
|
||||
GW::GameThread::RegisterGameThreadCallback(&GameThreadHook, [&](GW::HookStatus*) {
|
||||
while (!PromiseQueue.empty()) {
|
||||
auto promiseRequest = PromiseQueue.front();
|
||||
std::promise<NamePayload> &promise = std::get<1>(*promiseRequest);
|
||||
uint32_t id = std::get<0>(*promiseRequest);
|
||||
PromiseQueue.pop();
|
||||
try {
|
||||
auto name = GetAsyncName(id);
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
WaitingList.emplace_back(id, &promise, name);
|
||||
}
|
||||
catch (...) {
|
||||
NamePayload payload;
|
||||
promise.set_value(payload);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto i = 0; i < WaitingList.size(); ) {
|
||||
auto item = &WaitingList[i];
|
||||
auto name = std::get<2>(*item);
|
||||
if (name->empty()) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto promise = std::get<1>(*item);
|
||||
auto id = std::get<0>(*item);
|
||||
WaitingList.erase(WaitingList.begin() + i);
|
||||
NamePayload payload;
|
||||
payload.Id = id;
|
||||
payload.Name = Daybreak::Utils::WStringToString(*name);
|
||||
delete(name);
|
||||
promise->set_value(payload);
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
GameThreadMutex.unlock();
|
||||
}
|
||||
|
||||
void GetName(const httplib::Request& req, httplib::Response& res) {
|
||||
auto callbackEntry = new GW::HookEntry;
|
||||
std::optional<uint32_t> EntityNameModule::GetContext(const httplib::Request& req, httplib::Response& res)
|
||||
{
|
||||
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;
|
||||
return std::optional<uint32_t>();
|
||||
}
|
||||
else {
|
||||
auto idStr = it->second;
|
||||
@@ -108,22 +38,42 @@ namespace Daybreak::Modules::EntityNameModule {
|
||||
if (pos != idStr.size()) {
|
||||
res.status = 400;
|
||||
res.set_content("Invalid id parameter", "text/plain");
|
||||
return;
|
||||
return std::optional<uint32_t>();
|
||||
}
|
||||
|
||||
id = static_cast<uint32_t>(result);
|
||||
}
|
||||
|
||||
auto response = new std::tuple<uint32_t, std::promise<NamePayload>>();
|
||||
std::get<0>(*response) = id;
|
||||
std::promise<NamePayload>& promise = std::get<1>(*response);
|
||||
return id;
|
||||
}
|
||||
|
||||
EnsureInitialized();
|
||||
PromiseQueue.emplace(response);
|
||||
std::optional<NamePayload> EntityNameModule::GetPayload(const uint32_t context)
|
||||
{
|
||||
NamePayload namePayload;
|
||||
auto agent = GW::Agents::GetAgentByID(context);
|
||||
if (!agent) {
|
||||
return std::optional<NamePayload>();
|
||||
}
|
||||
|
||||
json responsePayload = promise.get_future().get();
|
||||
delete callbackEntry;
|
||||
delete response;
|
||||
res.set_content(responsePayload.dump(), "text/json");
|
||||
auto agentLiving = agent->GetAsAgentLiving();
|
||||
if (!agentLiving) {
|
||||
return std::optional<NamePayload>();
|
||||
}
|
||||
|
||||
if (!GW::Agents::AsyncGetAgentName(agentLiving, this->name)) {
|
||||
return std::optional<NamePayload>();
|
||||
}
|
||||
|
||||
namePayload.Id = context;
|
||||
return namePayload;
|
||||
}
|
||||
|
||||
bool EntityNameModule::CanReturn(const httplib::Request& req, httplib::Response& res, const NamePayload& payload) {
|
||||
return !name.empty();
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string> EntityNameModule::ReturnPayload(NamePayload payload) {
|
||||
payload.Name = Daybreak::Utils::WStringToString(this->name);
|
||||
return std::make_tuple(static_cast<json>(payload).dump(), "application/json");
|
||||
}
|
||||
}
|
||||
+323
-351
@@ -26,365 +26,374 @@
|
||||
#include <algorithm>
|
||||
#include "Utils.h"
|
||||
|
||||
namespace Daybreak::Modules::GameModule {
|
||||
std::queue<std::promise<GamePayload>*> PromiseQueue;
|
||||
std::mutex GameThreadMutex;
|
||||
GW::HookEntry GameThreadHook;
|
||||
volatile bool initialized = false;
|
||||
namespace Daybreak::Modules {
|
||||
namespace GameModuleInternal {
|
||||
std::list<QuestMetadata> GetQuestLog() {
|
||||
std::list<QuestMetadata> questMetadatas;
|
||||
const auto questLog = GW::QuestMgr::GetQuestLog();
|
||||
if (!questLog) {
|
||||
return questMetadatas;
|
||||
}
|
||||
|
||||
for (const auto& quest : *questLog) {
|
||||
QuestMetadata metadata;
|
||||
metadata.FromId = (uint32_t)quest.map_from;
|
||||
metadata.ToId = (uint32_t)quest.map_to;
|
||||
metadata.Id = (uint32_t)quest.quest_id;
|
||||
metadata.PosX = quest.marker.x;
|
||||
metadata.PosY = quest.marker.y;
|
||||
questMetadatas.push_back(metadata);
|
||||
}
|
||||
|
||||
std::list<QuestMetadata> GetQuestLog() {
|
||||
std::list<QuestMetadata> questMetadatas;
|
||||
auto questLog = GW::QuestMgr::GetQuestLog();
|
||||
if (!questLog) {
|
||||
return questMetadatas;
|
||||
}
|
||||
|
||||
for (auto& quest : *questLog) {
|
||||
QuestMetadata metadata;
|
||||
metadata.FromId = (uint32_t)quest.map_from;
|
||||
metadata.ToId = (uint32_t)quest.map_to;
|
||||
metadata.Id = (uint32_t)quest.quest_id;
|
||||
metadata.PosX = quest.marker.x;
|
||||
metadata.PosY = quest.marker.y;
|
||||
questMetadatas.push_back(metadata);
|
||||
}
|
||||
|
||||
return questMetadatas;
|
||||
}
|
||||
|
||||
std::vector<GW::MapAgent> GetMapEntities() {
|
||||
std::vector<GW::MapAgent> entities;
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
for (auto& agent : worldContext->map_agents) {
|
||||
entities.push_back(agent);
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
std::list<GW::PartyAttribute> GetPartyAttributes() {
|
||||
std::list<GW::PartyAttribute> attributes;
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
for (auto& attribute : worldContext->attributes) {
|
||||
attributes.push_back(attribute);
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
std::list<GW::ProfessionState> GetProfessions() {
|
||||
std::list<GW::ProfessionState> professions;
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
for (auto& profession : worldContext->party_profession_states) {
|
||||
professions.push_back(profession);
|
||||
}
|
||||
|
||||
return professions;
|
||||
}
|
||||
|
||||
std::list<GW::AgentLiving> GetLivingAgents() {
|
||||
std::list<GW::AgentLiving> agentList;
|
||||
const GW::AgentArray* agents_ptr = GW::Agents::GetAgentArray();
|
||||
GW::AgentArray* agents = GW::Agents::GetAgentArray();
|
||||
if (!agents) {
|
||||
return agentList;
|
||||
}
|
||||
|
||||
for (auto* a : *agents) {
|
||||
const GW::AgentLiving* agent = a ? a->GetAsAgentLiving() : nullptr;
|
||||
if (agent) {
|
||||
agentList.push_back(*agent);
|
||||
}
|
||||
}
|
||||
|
||||
return agentList;
|
||||
}
|
||||
|
||||
std::vector<GW::TitleTier> GetTitleTiers() {
|
||||
std::vector<GW::TitleTier> titles;
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
for (auto& title : worldContext->title_tiers) {
|
||||
titles.push_back(title);
|
||||
}
|
||||
|
||||
return titles;
|
||||
}
|
||||
|
||||
std::list<GW::Title> GetTitles() {
|
||||
std::list<GW::Title> titles;
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
for (auto& title : worldContext->titles) {
|
||||
titles.push_back(title);
|
||||
}
|
||||
|
||||
return titles;
|
||||
}
|
||||
|
||||
std::list<Temp::GWPlayer> GetPlayers() {
|
||||
std::list<Temp::GWPlayer> players;
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
Temp::GWPlayer *tempPlayerArray = (Temp::GWPlayer*)worldContext->players.m_buffer;
|
||||
if (!tempPlayerArray) {
|
||||
return players;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < worldContext->players.m_size; i++) {
|
||||
if (tempPlayerArray->agent_id != 0) {
|
||||
players.push_back(*tempPlayerArray);
|
||||
}
|
||||
tempPlayerArray++;
|
||||
}
|
||||
|
||||
return players;
|
||||
}
|
||||
|
||||
std::map<uint32_t, std::list<uint32_t>> GetSkillbars() {
|
||||
std::map<uint32_t, std::list<uint32_t>> skillbarMap;
|
||||
auto skillbars = GW::SkillbarMgr::GetSkillbarArray();
|
||||
if (!skillbars) {
|
||||
return skillbarMap;
|
||||
}
|
||||
|
||||
for (auto& skillbar : *skillbars) {
|
||||
std::list<uint32_t> skillList;
|
||||
skillList.push_back((uint32_t)skillbar.skills[0].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[1].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[2].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[3].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[4].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[5].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[6].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[7].skill_id);
|
||||
skillbarMap[skillbar.agent_id] = skillList;
|
||||
}
|
||||
|
||||
return skillbarMap;
|
||||
}
|
||||
|
||||
std::list<LivingEntity> GetLivingEntities(
|
||||
std::list<GW::AgentLiving> agents) {
|
||||
std::list<LivingEntity> entities;
|
||||
for (auto& agent : agents) {
|
||||
LivingEntity entity;
|
||||
entity.PrimaryProfessionId = agent.primary;
|
||||
entity.SecondaryProfessionId = agent.secondary;
|
||||
entity.EntityState = agent.type_map;
|
||||
entity.EntityAllegiance = (uint32_t)agent.allegiance;
|
||||
entity.NpcDefinition = agent.player_number;
|
||||
entity.Id = agent.agent_id;
|
||||
entity.Timer = agent.timer;
|
||||
entity.Level = agent.level;
|
||||
entity.PosX = agent.pos.x;
|
||||
entity.PosY = agent.pos.y;
|
||||
entity.Health = agent.hp;
|
||||
entity.Energy = agent.energy;
|
||||
if (agent.primary == 0) {
|
||||
const auto npc = GW::Agents::GetNPCByID(agent.player_number);
|
||||
if (npc) {
|
||||
entity.PrimaryProfessionId = npc->primary;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
entities.push_back(entity);
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
void PopulatePlayer(
|
||||
Player& player,
|
||||
int playerId,
|
||||
GW::ProfessionState* professionState,
|
||||
std::list<uint32_t> skillbar,
|
||||
GW::PartyAttribute* partyAttribute,
|
||||
GW::AgentLiving* agent) {
|
||||
|
||||
std::list<uint32_t> unlockedProfessions;
|
||||
if (professionState) {
|
||||
for (auto i = 0; i < 11; i++) {
|
||||
auto prof = (GW::Constants::Profession)i;
|
||||
if (professionState->IsProfessionUnlocked(prof)) {
|
||||
unlockedProfessions.push_back(i);
|
||||
}
|
||||
std::vector<GW::MapAgent> GetMapEntities() {
|
||||
std::vector<GW::MapAgent> entities;
|
||||
const auto worldContext = GW::GetWorldContext();
|
||||
for (const auto& agent : worldContext->map_agents) {
|
||||
entities.push_back(agent);
|
||||
}
|
||||
|
||||
unlockedProfessions.push_back((uint32_t)professionState->primary);
|
||||
return entities;
|
||||
}
|
||||
|
||||
Build build;
|
||||
std::list<Attribute> attributes;
|
||||
if (partyAttribute) {
|
||||
for (const auto& partyAttr : partyAttribute->attribute) {
|
||||
if (((uint32_t)partyAttr.id < 0) ||
|
||||
((uint32_t)partyAttr.id > 44) ||
|
||||
partyAttr.level <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Attribute attribute;
|
||||
attribute.Id = (uint32_t)partyAttr.id;
|
||||
attribute.BaseLevel = partyAttr.level_base;
|
||||
attribute.ActualLevel = partyAttr.level;
|
||||
attribute.DecrementPoints = partyAttr.decrement_points;
|
||||
attribute.IncrementPoints = partyAttr.increment_points;
|
||||
std::list<GW::PartyAttribute> GetPartyAttributes() {
|
||||
std::list<GW::PartyAttribute> attributes;
|
||||
const auto worldContext = GW::GetWorldContext();
|
||||
for (const auto& attribute : worldContext->attributes) {
|
||||
attributes.push_back(attribute);
|
||||
}
|
||||
|
||||
build.Attributes = attributes;
|
||||
return attributes;
|
||||
}
|
||||
|
||||
std::list<GW::ProfessionState> GetProfessions() {
|
||||
std::list<GW::ProfessionState> professions;
|
||||
const auto worldContext = GW::GetWorldContext();
|
||||
for (const auto& profession : worldContext->party_profession_states) {
|
||||
professions.push_back(profession);
|
||||
}
|
||||
|
||||
return professions;
|
||||
}
|
||||
|
||||
std::list<GW::AgentLiving> GetLivingAgents() {
|
||||
std::list<GW::AgentLiving> agentList;
|
||||
const GW::AgentArray* agents_ptr = GW::Agents::GetAgentArray();
|
||||
GW::AgentArray* agents = GW::Agents::GetAgentArray();
|
||||
if (!agents) {
|
||||
return agentList;
|
||||
}
|
||||
|
||||
for (const auto* a : *agents) {
|
||||
const GW::AgentLiving* agent = a ? a->GetAsAgentLiving() : nullptr;
|
||||
if (agent) {
|
||||
agentList.push_back(*agent);
|
||||
}
|
||||
}
|
||||
|
||||
return agentList;
|
||||
}
|
||||
|
||||
std::vector<GW::TitleTier> GetTitleTiers() {
|
||||
std::vector<GW::TitleTier> titles;
|
||||
const auto worldContext = GW::GetWorldContext();
|
||||
for (const auto& title : worldContext->title_tiers) {
|
||||
titles.push_back(title);
|
||||
}
|
||||
|
||||
return titles;
|
||||
}
|
||||
|
||||
std::list<GW::Title> GetTitles() {
|
||||
std::list<GW::Title> titles;
|
||||
const auto worldContext = GW::GetWorldContext();
|
||||
for (const auto& title : worldContext->titles) {
|
||||
titles.push_back(title);
|
||||
}
|
||||
|
||||
return titles;
|
||||
}
|
||||
|
||||
std::list<Temp::GWPlayer> GetPlayers() {
|
||||
std::list<Temp::GWPlayer> players;
|
||||
const auto worldContext = GW::GetWorldContext();
|
||||
Temp::GWPlayer* tempPlayerArray = (Temp::GWPlayer*)worldContext->players.m_buffer;
|
||||
if (!tempPlayerArray) {
|
||||
return players;
|
||||
}
|
||||
|
||||
for (auto i = 0U; i < worldContext->players.m_size; i++) {
|
||||
if (tempPlayerArray->agent_id != 0) {
|
||||
players.push_back(*tempPlayerArray);
|
||||
}
|
||||
tempPlayerArray++;
|
||||
}
|
||||
|
||||
return players;
|
||||
}
|
||||
|
||||
std::map<uint32_t, std::list<uint32_t>> GetSkillbars() {
|
||||
std::map<uint32_t, std::list<uint32_t>> skillbarMap;
|
||||
const auto skillbars = GW::SkillbarMgr::GetSkillbarArray();
|
||||
if (!skillbars) {
|
||||
return skillbarMap;
|
||||
}
|
||||
|
||||
for (const auto& skillbar : *skillbars) {
|
||||
std::list<uint32_t> skillList;
|
||||
skillList.push_back((uint32_t)skillbar.skills[0].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[1].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[2].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[3].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[4].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[5].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[6].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[7].skill_id);
|
||||
skillbarMap[skillbar.agent_id] = skillList;
|
||||
}
|
||||
|
||||
return skillbarMap;
|
||||
}
|
||||
|
||||
std::list<LivingEntity> GetLivingEntities(
|
||||
std::list<GW::AgentLiving> agents) {
|
||||
std::list<LivingEntity> entities;
|
||||
for (const auto& agent : agents) {
|
||||
LivingEntity entity;
|
||||
entity.PrimaryProfessionId = agent.primary;
|
||||
entity.SecondaryProfessionId = agent.secondary;
|
||||
entity.EntityState = agent.type_map;
|
||||
entity.EntityAllegiance = (uint32_t)agent.allegiance;
|
||||
entity.NpcDefinition = agent.player_number;
|
||||
entity.Id = agent.agent_id;
|
||||
entity.Timer = agent.timer;
|
||||
entity.Level = agent.level;
|
||||
entity.PosX = agent.pos.x;
|
||||
entity.PosY = agent.pos.y;
|
||||
entity.Health = agent.hp;
|
||||
entity.Energy = agent.energy;
|
||||
entity.RotationAngle = agent.rotation_angle;
|
||||
if (agent.primary == 0) {
|
||||
const auto npc = GW::Agents::GetNPCByID(agent.player_number);
|
||||
if (npc) {
|
||||
entity.PrimaryProfessionId = npc->primary;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
entities.push_back(entity);
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
void PopulatePlayer(
|
||||
Player& player,
|
||||
int playerId,
|
||||
GW::ProfessionState* professionState,
|
||||
std::list<uint32_t> skillbar,
|
||||
GW::PartyAttribute* partyAttribute,
|
||||
GW::AgentLiving* agent) {
|
||||
|
||||
std::list<uint32_t> unlockedProfessions;
|
||||
if (professionState) {
|
||||
for (auto i = 0; i < 11; i++) {
|
||||
const auto prof = (GW::Constants::Profession)i;
|
||||
if (professionState->IsProfessionUnlocked(prof)) {
|
||||
unlockedProfessions.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
unlockedProfessions.push_back((uint32_t)professionState->primary);
|
||||
}
|
||||
|
||||
Build build;
|
||||
std::list<Attribute> attributes;
|
||||
if (partyAttribute) {
|
||||
for (const auto& partyAttr : partyAttribute->attribute) {
|
||||
if (((uint32_t)partyAttr.id < 0) ||
|
||||
((uint32_t)partyAttr.id > 44) ||
|
||||
partyAttr.level <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Attribute attribute;
|
||||
attribute.Id = (uint32_t)partyAttr.id;
|
||||
attribute.BaseLevel = partyAttr.level_base;
|
||||
attribute.ActualLevel = partyAttr.level;
|
||||
attribute.DecrementPoints = partyAttr.decrement_points;
|
||||
attribute.IncrementPoints = partyAttr.increment_points;
|
||||
attributes.push_back(attribute);
|
||||
}
|
||||
|
||||
build.Attributes = attributes;
|
||||
if (agent) {
|
||||
build.Primary = (uint32_t)agent->primary;
|
||||
build.Secondary = (uint32_t)agent->secondary;
|
||||
}
|
||||
else if (professionState) {
|
||||
build.Primary = (uint32_t)professionState->primary;
|
||||
build.Secondary = (uint32_t)professionState->secondary;
|
||||
}
|
||||
|
||||
build.Skills = skillbar;
|
||||
player.Build = build;
|
||||
}
|
||||
|
||||
player.UnlockedProfession = unlockedProfessions;
|
||||
player.Id = playerId;
|
||||
if (agent) {
|
||||
build.Primary = (uint32_t)agent->primary;
|
||||
build.Secondary = (uint32_t)agent->secondary;
|
||||
player.PosX = agent->pos.x;
|
||||
player.PosY = agent->pos.y;
|
||||
player.Timer = agent->timer;
|
||||
player.Level = agent->level;
|
||||
player.NpcDefinition = agent->player_number;
|
||||
player.PrimaryProfessionId = (uint32_t)agent->primary;
|
||||
player.SecondaryProfessionId = (uint32_t)agent->secondary;
|
||||
player.Health = agent->hp * (agent->max_hp > 0 ? agent->max_hp : 1);
|
||||
player.Energy = agent->energy * (agent->max_energy > 0 ? agent->max_energy : 1);
|
||||
player.RotationAngle = agent->rotation_angle;
|
||||
}
|
||||
else if (professionState) {
|
||||
build.Primary = (uint32_t)professionState->primary;
|
||||
build.Secondary = (uint32_t)professionState->secondary;
|
||||
player.PrimaryProfessionId = (uint32_t)professionState->primary;
|
||||
player.SecondaryProfessionId = (uint32_t)professionState->secondary;
|
||||
}
|
||||
}
|
||||
|
||||
void PopulateWorldPlayer(
|
||||
std::list<GW::Title> titles,
|
||||
std::vector<GW::TitleTier> titleTiers,
|
||||
WorldPlayer& player,
|
||||
int playerId,
|
||||
Temp::GWPlayer* gwPlayer,
|
||||
GW::ProfessionState* professionState,
|
||||
std::list<uint32_t> skillbar,
|
||||
GW::PartyAttribute* partyAttribute,
|
||||
GW::AgentLiving* agent) {
|
||||
|
||||
PopulatePlayer(player, playerId, professionState, skillbar, partyAttribute, agent);
|
||||
if (!gwPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
build.Skills = skillbar;
|
||||
player.Build = build;
|
||||
}
|
||||
GW::Title gwTitle;
|
||||
Title title;
|
||||
int titleId = 0;
|
||||
std::wstring nameString(gwPlayer->name);
|
||||
const auto name = Daybreak::Utils::WStringToString(nameString);
|
||||
player.Name = name;
|
||||
|
||||
player.UnlockedProfession = unlockedProfessions;
|
||||
player.Id = playerId;
|
||||
if (agent) {
|
||||
player.PosX = agent->pos.x;
|
||||
player.PosY = agent->pos.y;
|
||||
player.Timer = agent->timer;
|
||||
player.Level = agent->level;
|
||||
player.NpcDefinition = agent->player_number;
|
||||
player.PrimaryProfessionId = (uint32_t)agent->primary;
|
||||
player.SecondaryProfessionId = (uint32_t)agent->secondary;
|
||||
player.Health = agent->hp * (agent->max_hp > 0 ? agent->max_hp : 1);
|
||||
player.Energy = agent->energy * (agent->max_energy > 0 ? agent->max_energy : 1);
|
||||
}
|
||||
else if (professionState) {
|
||||
player.PrimaryProfessionId = (uint32_t)professionState->primary;
|
||||
player.SecondaryProfessionId = (uint32_t)professionState->secondary;
|
||||
}
|
||||
}
|
||||
|
||||
void PopulateWorldPlayer(
|
||||
std::list<GW::Title> titles,
|
||||
std::vector<GW::TitleTier> titleTiers,
|
||||
WorldPlayer& player,
|
||||
int playerId,
|
||||
Temp::GWPlayer* gwPlayer,
|
||||
GW::ProfessionState* professionState,
|
||||
std::list<uint32_t> skillbar,
|
||||
GW::PartyAttribute* partyAttribute,
|
||||
GW::AgentLiving* agent) {
|
||||
|
||||
PopulatePlayer(player, playerId, professionState, skillbar, partyAttribute, agent);
|
||||
if (!gwPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
GW::Title gwTitle;
|
||||
Title title;
|
||||
int titleId = 0;
|
||||
std::wstring nameString(gwPlayer->name);
|
||||
auto name = Daybreak::Utils::WStringToString(nameString);
|
||||
player.Name = name;
|
||||
|
||||
for (const auto &t : titles) {
|
||||
if (t.current_title_tier_index == gwPlayer->active_title_tier) {
|
||||
gwTitle = t;
|
||||
break;
|
||||
if (titles.empty() ||
|
||||
titleTiers.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
titleId++;
|
||||
for (const auto& t : titles) {
|
||||
if (t.current_title_tier_index == gwPlayer->active_title_tier) {
|
||||
gwTitle = t;
|
||||
break;
|
||||
}
|
||||
|
||||
titleId++;
|
||||
}
|
||||
|
||||
if (gwTitle.current_title_tier_index >= titleTiers.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto titleTier = titleTiers[gwTitle.current_title_tier_index];
|
||||
title.CurrentPoints = gwTitle.current_points;
|
||||
title.IsPercentage = gwTitle.is_percentage_based();
|
||||
title.PointsForCurrentRank = gwTitle.points_needed_current_rank;
|
||||
title.PointsForNextRank = titleTier.tier_number == gwTitle.max_title_rank ? gwTitle.current_points : gwTitle.points_needed_next_rank;
|
||||
title.TierNumber = titleTier.tier_number;
|
||||
title.MaxTierNumber = gwTitle.max_title_rank;
|
||||
title.Id = titleId;
|
||||
|
||||
player.Title = title;
|
||||
}
|
||||
|
||||
if (gwTitle.current_title_tier_index >= titleTiers.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto titleTier = titleTiers[gwTitle.current_title_tier_index];
|
||||
title.CurrentPoints = gwTitle.current_points;
|
||||
title.IsPercentage = gwTitle.is_percentage_based();
|
||||
title.PointsForCurrentRank = gwTitle.points_needed_current_rank;
|
||||
title.PointsForNextRank = titleTier.tier_number == gwTitle.max_title_rank ? gwTitle.current_points : gwTitle.points_needed_next_rank;
|
||||
title.TierNumber = titleTier.tier_number;
|
||||
title.MaxTierNumber = gwTitle.max_title_rank;
|
||||
title.Id = titleId;
|
||||
|
||||
player.Title = title;
|
||||
}
|
||||
|
||||
void PopulateMainPlayer(
|
||||
MainPlayer& player,
|
||||
int activeQuestId,
|
||||
std::list<QuestMetadata> questLog,
|
||||
std::list<GW::Title> titles,
|
||||
std::vector<GW::TitleTier> titleTiers,
|
||||
int playerId,
|
||||
Temp::GWPlayer* gwPlayer,
|
||||
GW::ProfessionState* professionState,
|
||||
std::list<uint32_t> skillbar,
|
||||
GW::PartyAttribute* partyAttribute,
|
||||
GW::AgentLiving* agent) {
|
||||
PopulateWorldPlayer(titles, titleTiers, player, playerId, gwPlayer, professionState, skillbar, partyAttribute, agent);
|
||||
player.CurrentQuest = activeQuestId;
|
||||
player.QuestLog = questLog;
|
||||
player.HardModeUnlocked = GW::GetWorldContext()->is_hard_mode_unlocked == 1;
|
||||
player.Experience = GW::GetWorldContext()->experience;
|
||||
player.Morale = GW::GetWorldContext()->morale;
|
||||
if (agent) {
|
||||
player.CurrentEnergy = agent->max_energy * agent->energy;
|
||||
player.CurrentHp = agent->max_hp * agent->hp;
|
||||
player.MaxEnergy = agent->max_energy;
|
||||
player.MaxHp = agent->max_hp;
|
||||
void PopulateMainPlayer(
|
||||
MainPlayer& player,
|
||||
int activeQuestId,
|
||||
std::list<QuestMetadata> questLog,
|
||||
std::list<GW::Title> titles,
|
||||
std::vector<GW::TitleTier> titleTiers,
|
||||
int playerId,
|
||||
Temp::GWPlayer* gwPlayer,
|
||||
GW::ProfessionState* professionState,
|
||||
std::list<uint32_t> skillbar,
|
||||
GW::PartyAttribute* partyAttribute,
|
||||
GW::AgentLiving* agent) {
|
||||
PopulateWorldPlayer(titles, titleTiers, player, playerId, gwPlayer, professionState, skillbar, partyAttribute, agent);
|
||||
player.CurrentQuest = activeQuestId;
|
||||
player.QuestLog = questLog;
|
||||
player.HardModeUnlocked = GW::GetWorldContext()->is_hard_mode_unlocked == 1;
|
||||
player.Experience = GW::GetWorldContext()->experience;
|
||||
player.Morale = GW::GetWorldContext()->morale;
|
||||
if (agent) {
|
||||
player.CurrentEnergy = agent->max_energy * agent->energy;
|
||||
player.CurrentHp = agent->max_hp * agent->hp;
|
||||
player.MaxEnergy = agent->max_energy;
|
||||
player.MaxHp = agent->max_hp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GamePayload GetPayload() {
|
||||
std::string GameModule::ApiUri()
|
||||
{
|
||||
return "/game";
|
||||
}
|
||||
|
||||
std::optional<uint32_t> GameModule::GetContext(const httplib::Request& req, httplib::Response& res)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
std::optional<GamePayload> GameModule::GetPayload(uint32_t)
|
||||
{
|
||||
GamePayload gamePayload;
|
||||
gamePayload.TargetId = 0;
|
||||
if (!GW::Map::GetIsMapLoaded()) {
|
||||
return gamePayload;
|
||||
}
|
||||
|
||||
|
||||
gamePayload.TargetId = GW::Agents::GetTargetId();
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
auto activeQuestId = GW::QuestMgr::GetActiveQuestId();
|
||||
auto questLog = GetQuestLog();
|
||||
auto skillbars = GetSkillbars();
|
||||
auto questLog = GameModuleInternal::GetQuestLog();
|
||||
auto skillbars = GameModuleInternal::GetSkillbars();
|
||||
if (skillbars.empty()) {
|
||||
return gamePayload;
|
||||
}
|
||||
|
||||
auto agents = GetLivingAgents();
|
||||
auto agents = GameModuleInternal::GetLivingAgents();
|
||||
if (agents.empty()) {
|
||||
return gamePayload;
|
||||
}
|
||||
|
||||
auto titles = GetTitles();
|
||||
if (titles.empty()) {
|
||||
return gamePayload;
|
||||
}
|
||||
auto titles = GameModuleInternal::GetTitles();
|
||||
|
||||
auto titleTiers = GetTitleTiers();
|
||||
if (titleTiers.empty()) {
|
||||
return gamePayload;
|
||||
}
|
||||
auto titleTiers = GameModuleInternal::GetTitleTiers();
|
||||
|
||||
auto players = GetPlayers();
|
||||
auto players = GameModuleInternal::GetPlayers();
|
||||
if (players.empty()) {
|
||||
return gamePayload;
|
||||
}
|
||||
|
||||
auto attributes = GetPartyAttributes();
|
||||
auto attributes = GameModuleInternal::GetPartyAttributes();
|
||||
if (attributes.empty()) {
|
||||
return gamePayload;
|
||||
}
|
||||
|
||||
auto professions = GetProfessions();
|
||||
auto professions = GameModuleInternal::GetProfessions();
|
||||
if (professions.empty()) {
|
||||
return gamePayload;
|
||||
}
|
||||
|
||||
auto mapAgents = GetMapEntities();
|
||||
auto mapAgents = GameModuleInternal::GetMapEntities();
|
||||
if (mapAgents.empty()) {
|
||||
return gamePayload;
|
||||
}
|
||||
@@ -392,7 +401,7 @@ namespace Daybreak::Modules::GameModule {
|
||||
auto playerAgentId = GW::Agents::GetPlayerId();
|
||||
|
||||
std::list<MapIcon> mapIcons;
|
||||
for (const auto &icon : worldContext->mission_map_icons) {
|
||||
for (const auto& icon : worldContext->mission_map_icons) {
|
||||
MapIcon mapIcon;
|
||||
mapIcon.Id = icon.model_id;
|
||||
mapIcon.Affiliation = icon.option;
|
||||
@@ -400,11 +409,11 @@ namespace Daybreak::Modules::GameModule {
|
||||
mapIcon.PosY = icon.Y;
|
||||
mapIcons.push_back(mapIcon);
|
||||
}
|
||||
|
||||
|
||||
gamePayload.MapIcons = mapIcons;
|
||||
|
||||
std::list<Player> partyMembers;
|
||||
for (auto &prof : professions) {
|
||||
for (auto& prof : professions) {
|
||||
if (prof.agent_id == playerAgentId) {
|
||||
continue;
|
||||
}
|
||||
@@ -434,7 +443,7 @@ namespace Daybreak::Modules::GameModule {
|
||||
attrPtr = &*foundAttr;
|
||||
}
|
||||
|
||||
PopulatePlayer(player, prof.agent_id, &prof, skillbars[prof.agent_id], attrPtr, agentPtr);
|
||||
GameModuleInternal::PopulatePlayer(player, prof.agent_id, &prof, skillbars[prof.agent_id], attrPtr, agentPtr);
|
||||
partyMembers.push_back(player);
|
||||
}
|
||||
gamePayload.Party = partyMembers;
|
||||
@@ -480,11 +489,11 @@ namespace Daybreak::Modules::GameModule {
|
||||
if (foundMainGwPlayer == players.end()) {
|
||||
mainGwPlayer = nullptr;
|
||||
}
|
||||
else{
|
||||
else {
|
||||
mainGwPlayer = &*foundMainGwPlayer;
|
||||
}
|
||||
|
||||
PopulateMainPlayer(mainPlayer, (uint32_t)activeQuestId, questLog, titles, titleTiers, playerAgentId, mainGwPlayer, mainProfPtr, skillbars[playerAgentId], mainAttrPtr, mainAgentPtr);
|
||||
GameModuleInternal::PopulateMainPlayer(mainPlayer, (uint32_t)activeQuestId, questLog, titles, titleTiers, playerAgentId, mainGwPlayer, mainProfPtr, skillbars[playerAgentId], mainAttrPtr, mainAgentPtr);
|
||||
gamePayload.MainPlayer = mainPlayer;
|
||||
|
||||
std::list<WorldPlayer> worldPlayers;
|
||||
@@ -507,7 +516,7 @@ namespace Daybreak::Modules::GameModule {
|
||||
|
||||
std::list<uint32_t> skills;
|
||||
auto foundSkillbar = skillbars.find(gwPlayer.agent_id);
|
||||
if (foundSkillbar != skillbars.end()){
|
||||
if (foundSkillbar != skillbars.end()) {
|
||||
skills = foundSkillbar->second;
|
||||
}
|
||||
|
||||
@@ -536,7 +545,7 @@ namespace Daybreak::Modules::GameModule {
|
||||
}
|
||||
|
||||
auto* tempGwPlayer = &gwPlayer;
|
||||
PopulateWorldPlayer(titles, titleTiers, worldPlayer, gwPlayer.agent_id, tempGwPlayer, worldProfPtr, skills, worldAttrPtr, worldAgentPtr);
|
||||
GameModuleInternal::PopulateWorldPlayer(titles, titleTiers, worldPlayer, gwPlayer.agent_id, tempGwPlayer, worldProfPtr, skills, worldAttrPtr, worldAgentPtr);
|
||||
worldPlayers.push_back(worldPlayer);
|
||||
}
|
||||
|
||||
@@ -564,45 +573,8 @@ namespace Daybreak::Modules::GameModule {
|
||||
livingEntities.push_back(agentLiving);
|
||||
}
|
||||
|
||||
auto livingEntityList = GetLivingEntities(livingEntities);
|
||||
auto livingEntityList = GameModuleInternal::GetLivingEntities(livingEntities);
|
||||
gamePayload.LivingEntities = livingEntityList;
|
||||
return gamePayload;
|
||||
}
|
||||
|
||||
void EnsureInitialized() {
|
||||
GameThreadMutex.lock();
|
||||
if (!initialized) {
|
||||
GW::GameThread::RegisterGameThreadCallback(&GameThreadHook, [&](GW::HookStatus*) {
|
||||
while (!PromiseQueue.empty()) {
|
||||
auto promise = PromiseQueue.front();
|
||||
PromiseQueue.pop();
|
||||
try {
|
||||
auto payload = GetPayload();
|
||||
promise->set_value(payload);
|
||||
}
|
||||
catch (...) {
|
||||
GamePayload payload;
|
||||
promise->set_value(payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
GameThreadMutex.unlock();
|
||||
}
|
||||
|
||||
void GetGameInfo(const httplib::Request&, httplib::Response& res) {
|
||||
auto callbackEntry = new GW::HookEntry;
|
||||
auto response = new std::promise<GamePayload>;
|
||||
|
||||
EnsureInitialized();
|
||||
PromiseQueue.emplace(response);
|
||||
json responsePayload = response->get_future().get();
|
||||
|
||||
delete callbackEntry;
|
||||
delete response;
|
||||
res.set_content(responsePayload.dump(), "text/json");
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
#include "GameStateModule.h"
|
||||
#include <GWCA/Managers/GameThreadMgr.h>
|
||||
#include <GWCA/Managers/AgentMgr.h>
|
||||
#include <GWCA/Managers/CameraMgr.h>
|
||||
#include <GWCA/GameEntities/Agent.h>
|
||||
#include <GWCA/GameEntities/Camera.h>
|
||||
#include <GWCA/Managers/MapMgr.h>
|
||||
#include <future>
|
||||
#include <payloads/GameStatePayload.h>
|
||||
@@ -10,97 +12,72 @@
|
||||
#include <queue>
|
||||
#include <algorithm>
|
||||
|
||||
namespace Daybreak::Modules::GameStateModule {
|
||||
std::queue<std::promise<GameStatePayload>*> PromiseQueue;
|
||||
std::mutex GameThreadMutex;
|
||||
GW::HookEntry GameThreadHook;
|
||||
volatile bool initialized = false;
|
||||
namespace Daybreak::Modules {
|
||||
namespace GameStateModuleInternal {
|
||||
std::list<GW::AgentLiving> GetLivingAgents() {
|
||||
std::list<GW::AgentLiving> agentList;
|
||||
const GW::AgentArray* agents_ptr = GW::Agents::GetAgentArray();
|
||||
GW::AgentArray* agents = GW::Agents::GetAgentArray();
|
||||
if (!agents) {
|
||||
return agentList;
|
||||
}
|
||||
|
||||
for (const auto* a : *agents) {
|
||||
const GW::AgentLiving* agent = a ? a->GetAsAgentLiving() : nullptr;
|
||||
if (agent) {
|
||||
agentList.push_back(*agent);
|
||||
}
|
||||
}
|
||||
|
||||
std::list<GW::AgentLiving> GetLivingAgents() {
|
||||
std::list<GW::AgentLiving> agentList;
|
||||
const GW::AgentArray* agents_ptr = GW::Agents::GetAgentArray();
|
||||
GW::AgentArray* agents = GW::Agents::GetAgentArray();
|
||||
if (!agents) {
|
||||
return agentList;
|
||||
}
|
||||
|
||||
for (auto* a : *agents) {
|
||||
const GW::AgentLiving* agent = a ? a->GetAsAgentLiving() : nullptr;
|
||||
if (agent) {
|
||||
agentList.push_back(*agent);
|
||||
std::list<StatePayload> GetStates(
|
||||
std::list<GW::AgentLiving> agents) {
|
||||
std::list<StatePayload> states;
|
||||
for (auto& agent : agents) {
|
||||
StatePayload state;
|
||||
state.Id = agent.agent_id;
|
||||
state.PosX = agent.pos.x;
|
||||
state.PosY = agent.pos.y;
|
||||
state.State = agent.type_map;
|
||||
state.Health = agent.hp * (agent.max_hp > 0 ? agent.max_hp : 1);
|
||||
state.Energy = agent.energy * (agent.max_energy > 0 ? agent.max_energy : 1);
|
||||
state.RotationAngle = agent.rotation_angle;
|
||||
states.push_back(state);
|
||||
}
|
||||
}
|
||||
|
||||
return agentList;
|
||||
return states;
|
||||
}
|
||||
}
|
||||
|
||||
std::list<StatePayload> GetStates(
|
||||
std::list<GW::AgentLiving> agents) {
|
||||
std::list<StatePayload> states;
|
||||
for (auto& agent : agents) {
|
||||
StatePayload state;
|
||||
state.Id = agent.agent_id;
|
||||
state.PosX = agent.pos.x;
|
||||
state.PosY = agent.pos.y;
|
||||
state.State = agent.type_map;
|
||||
state.Health = agent.hp * (agent.max_hp > 0 ? agent.max_hp : 1);
|
||||
state.Energy = agent.energy * (agent.max_energy > 0 ? agent.max_energy : 1);
|
||||
states.push_back(state);
|
||||
}
|
||||
|
||||
return states;
|
||||
}
|
||||
|
||||
GameStatePayload GetPayload() {
|
||||
std::optional<GameStatePayload> GameStateModule::GetPayload(uint32_t) {
|
||||
GameStatePayload gamePayload;
|
||||
if (!GW::Map::GetIsMapLoaded()) {
|
||||
return gamePayload;
|
||||
}
|
||||
|
||||
auto agents = GetLivingAgents();
|
||||
auto agents = GameStateModuleInternal::GetLivingAgents();
|
||||
if (agents.empty()) {
|
||||
return gamePayload;
|
||||
}
|
||||
|
||||
auto states = GetStates(agents);
|
||||
auto states = GameStateModuleInternal::GetStates(agents);
|
||||
gamePayload.States = states;
|
||||
|
||||
auto camera = GW::CameraMgr::GetCamera();
|
||||
Daybreak::Camera cameraPayload{};
|
||||
cameraPayload.Pitch = camera->pitch;
|
||||
cameraPayload.Yaw = camera->yaw;
|
||||
gamePayload.Camera = cameraPayload;
|
||||
return gamePayload;
|
||||
}
|
||||
|
||||
void EnsureInitialized() {
|
||||
GameThreadMutex.lock();
|
||||
if (!initialized) {
|
||||
GW::GameThread::RegisterGameThreadCallback(&GameThreadHook, [&](GW::HookStatus*) {
|
||||
while (!PromiseQueue.empty()) {
|
||||
auto promise = PromiseQueue.front();
|
||||
PromiseQueue.pop();
|
||||
try {
|
||||
auto payload = GetPayload();
|
||||
promise->set_value(payload);
|
||||
}
|
||||
catch (...) {
|
||||
GameStatePayload payload;
|
||||
promise->set_value(payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
GameThreadMutex.unlock();
|
||||
std::string GameStateModule::ApiUri() {
|
||||
return "/game/state";
|
||||
}
|
||||
|
||||
void GetGameStateInfo(const httplib::Request&, httplib::Response& res) {
|
||||
auto callbackEntry = new GW::HookEntry;
|
||||
auto response = new std::promise<GameStatePayload>;
|
||||
|
||||
EnsureInitialized();
|
||||
PromiseQueue.emplace(response);
|
||||
json responsePayload = response->get_future().get();
|
||||
|
||||
delete callbackEntry;
|
||||
delete response;
|
||||
res.set_content(responsePayload.dump(), "text/json");
|
||||
std::optional<uint32_t> GameStateModule::GetContext(const httplib::Request& req, httplib::Response& res) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -6,111 +6,85 @@
|
||||
#include <GWCA/Managers/ItemMgr.h>
|
||||
#include <GWCA/GameEntities/Item.h>
|
||||
#include <future>
|
||||
#include <payloads/InventoryPayload.h>
|
||||
#include <payloads/inventoryPayload.h>
|
||||
#include <json.hpp>
|
||||
#include <queue>
|
||||
|
||||
namespace Daybreak::Modules::InventoryModule {
|
||||
std::queue<std::promise<InventoryPayload>*> PromiseQueue;
|
||||
std::mutex GameThreadMutex;
|
||||
GW::HookEntry GameThreadHook;
|
||||
volatile bool initialized = false;
|
||||
namespace Daybreak::Modules {
|
||||
namespace InventoryModuleInternal {
|
||||
Daybreak::Bag GetBag(GW::Bag* bag) {
|
||||
Bag retBag;
|
||||
if (!bag) {
|
||||
return retBag;
|
||||
}
|
||||
|
||||
for (const auto& item : bag->items) {
|
||||
BagContent bagContent;
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bagContent.Id = item->model_id;
|
||||
bagContent.Slot = item->slot;
|
||||
bagContent.Count = item->quantity;
|
||||
auto modifier = item->mod_struct;
|
||||
for (auto i = 0U; i < item->mod_struct_size; i++) {
|
||||
bagContent.Modifiers.push_back((uint32_t)(modifier->mod));
|
||||
modifier++;
|
||||
}
|
||||
|
||||
retBag.Items.push_back(bagContent);
|
||||
}
|
||||
|
||||
Daybreak::Bag GetBag(GW::Bag* bag) {
|
||||
Bag retBag;
|
||||
if (!bag) {
|
||||
return retBag;
|
||||
}
|
||||
|
||||
for (const auto &item : bag->items) {
|
||||
BagContent bagContent;
|
||||
if (!item) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bagContent.Id = item->model_id;
|
||||
bagContent.Slot = item->slot;
|
||||
bagContent.Count = item->quantity;
|
||||
auto modifier = item->mod_struct;
|
||||
for (auto i = 0; i < item->mod_struct_size; i++) {
|
||||
bagContent.Modifiers.push_back((uint32_t)(modifier->mod));
|
||||
modifier++;
|
||||
}
|
||||
|
||||
retBag.Items.push_back(bagContent);
|
||||
}
|
||||
|
||||
return retBag;
|
||||
}
|
||||
|
||||
InventoryPayload GetPayload() {
|
||||
std::optional<InventoryPayload> InventoryModule::GetPayload(const uint32_t) {
|
||||
InventoryPayload inventoryPayload;
|
||||
if (!GW::Map::GetIsMapLoaded()) {
|
||||
return inventoryPayload;
|
||||
}
|
||||
|
||||
inventoryPayload.GoldInStorage = GW::Items::GetGoldAmountInStorage();
|
||||
inventoryPayload.GoldOnCharacter = GW::Items::GetGoldAmountOnCharacter();
|
||||
auto inventory = GW::GetItemContext()->inventory;
|
||||
inventoryPayload.Backpack = GetBag(inventory->backpack);
|
||||
inventoryPayload.BeltPouch = (GetBag(inventory->belt_pouch));
|
||||
inventoryPayload.Bags.push_back(GetBag(inventory->bag1));
|
||||
inventoryPayload.Bags.push_back(GetBag(inventory->bag2));
|
||||
inventoryPayload.EquipmentPack = (GetBag(inventory->equipment_pack));
|
||||
inventoryPayload.MaterialStorage = (GetBag(inventory->material_storage));
|
||||
inventoryPayload.UnclaimedItems = (GetBag(inventory->unclaimed_items));
|
||||
inventoryPayload.EquippedItems = (GetBag(inventory->equipped_items));
|
||||
inventoryPayload.StoragePanes.push_back(GetBag(inventory->storage1));
|
||||
inventoryPayload.StoragePanes.push_back(GetBag(inventory->storage2));
|
||||
inventoryPayload.StoragePanes.push_back(GetBag(inventory->storage3));
|
||||
inventoryPayload.StoragePanes.push_back(GetBag(inventory->storage4));
|
||||
inventoryPayload.StoragePanes.push_back(GetBag(inventory->storage5));
|
||||
inventoryPayload.StoragePanes.push_back(GetBag(inventory->storage6));
|
||||
inventoryPayload.StoragePanes.push_back(GetBag(inventory->storage7));
|
||||
inventoryPayload.StoragePanes.push_back(GetBag(inventory->storage8));
|
||||
inventoryPayload.StoragePanes.push_back(GetBag(inventory->storage9));
|
||||
inventoryPayload.StoragePanes.push_back(GetBag(inventory->storage10));
|
||||
inventoryPayload.StoragePanes.push_back(GetBag(inventory->storage11));
|
||||
inventoryPayload.StoragePanes.push_back(GetBag(inventory->storage12));
|
||||
inventoryPayload.StoragePanes.push_back(GetBag(inventory->storage13));
|
||||
inventoryPayload.StoragePanes.push_back(GetBag(inventory->storage14));
|
||||
if (!inventory) {
|
||||
return inventoryPayload;
|
||||
}
|
||||
|
||||
inventoryPayload.GoldInStorage = GW::Items::GetGoldAmountInStorage();
|
||||
inventoryPayload.GoldOnCharacter = GW::Items::GetGoldAmountOnCharacter();
|
||||
inventoryPayload.Backpack = InventoryModuleInternal::GetBag(inventory->backpack);
|
||||
inventoryPayload.BeltPouch = (InventoryModuleInternal::GetBag(inventory->belt_pouch));
|
||||
inventoryPayload.Bags.push_back(InventoryModuleInternal::GetBag(inventory->bag1));
|
||||
inventoryPayload.Bags.push_back(InventoryModuleInternal::GetBag(inventory->bag2));
|
||||
inventoryPayload.EquipmentPack = (InventoryModuleInternal::GetBag(inventory->equipment_pack));
|
||||
inventoryPayload.MaterialStorage = (InventoryModuleInternal::GetBag(inventory->material_storage));
|
||||
inventoryPayload.UnclaimedItems = (InventoryModuleInternal::GetBag(inventory->unclaimed_items));
|
||||
inventoryPayload.EquippedItems = (InventoryModuleInternal::GetBag(inventory->equipped_items));
|
||||
inventoryPayload.StoragePanes.push_back(InventoryModuleInternal::GetBag(inventory->storage1));
|
||||
inventoryPayload.StoragePanes.push_back(InventoryModuleInternal::GetBag(inventory->storage2));
|
||||
inventoryPayload.StoragePanes.push_back(InventoryModuleInternal::GetBag(inventory->storage3));
|
||||
inventoryPayload.StoragePanes.push_back(InventoryModuleInternal::GetBag(inventory->storage4));
|
||||
inventoryPayload.StoragePanes.push_back(InventoryModuleInternal::GetBag(inventory->storage5));
|
||||
inventoryPayload.StoragePanes.push_back(InventoryModuleInternal::GetBag(inventory->storage6));
|
||||
inventoryPayload.StoragePanes.push_back(InventoryModuleInternal::GetBag(inventory->storage7));
|
||||
inventoryPayload.StoragePanes.push_back(InventoryModuleInternal::GetBag(inventory->storage8));
|
||||
inventoryPayload.StoragePanes.push_back(InventoryModuleInternal::GetBag(inventory->storage9));
|
||||
inventoryPayload.StoragePanes.push_back(InventoryModuleInternal::GetBag(inventory->storage10));
|
||||
inventoryPayload.StoragePanes.push_back(InventoryModuleInternal::GetBag(inventory->storage11));
|
||||
inventoryPayload.StoragePanes.push_back(InventoryModuleInternal::GetBag(inventory->storage12));
|
||||
inventoryPayload.StoragePanes.push_back(InventoryModuleInternal::GetBag(inventory->storage13));
|
||||
inventoryPayload.StoragePanes.push_back(InventoryModuleInternal::GetBag(inventory->storage14));
|
||||
return inventoryPayload;
|
||||
}
|
||||
|
||||
void EnsureInitialized() {
|
||||
GameThreadMutex.lock();
|
||||
if (!initialized) {
|
||||
GW::GameThread::RegisterGameThreadCallback(&GameThreadHook, [&](GW::HookStatus*) {
|
||||
while (!PromiseQueue.empty()) {
|
||||
auto promise = PromiseQueue.front();
|
||||
PromiseQueue.pop();
|
||||
try {
|
||||
auto payload = GetPayload();
|
||||
promise->set_value(payload);
|
||||
}
|
||||
catch(...){
|
||||
InventoryPayload payload;
|
||||
promise->set_value(payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
GameThreadMutex.unlock();
|
||||
std::optional<uint32_t> InventoryModule::GetContext(const httplib::Request& req, httplib::Response& res)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GetInventoryInfo(const httplib::Request&, httplib::Response& res) {
|
||||
auto callbackEntry = new GW::HookEntry;
|
||||
auto response = new std::promise<InventoryPayload>;
|
||||
|
||||
EnsureInitialized();
|
||||
PromiseQueue.emplace(response);
|
||||
json responsePayload = response->get_future().get();
|
||||
|
||||
delete callbackEntry;
|
||||
delete response;
|
||||
res.set_content(responsePayload.dump(), "text/json");
|
||||
std::string InventoryModule::ApiUri()
|
||||
{
|
||||
return "/inventory";
|
||||
}
|
||||
}
|
||||
@@ -14,112 +14,43 @@
|
||||
#include <limits>
|
||||
#include <Utils.h>
|
||||
|
||||
namespace Daybreak::Modules::ItemNameModule {
|
||||
std::vector<std::tuple<uint32_t, std::promise<NamePayload>*, std::wstring*>> WaitingList;
|
||||
std::queue<std::tuple<uint32_t, std::vector<uint32_t>, std::promise<NamePayload>>*> PromiseQueue;
|
||||
std::mutex GameThreadMutex;
|
||||
GW::HookEntry GameThreadHook;
|
||||
volatile bool initialized = false;
|
||||
namespace Daybreak::Modules {
|
||||
namespace ItemNameModuleInternal {
|
||||
std::vector<std::string> SplitAndRemoveSpaces(const std::string& s) {
|
||||
std::vector<std::string> result;
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
|
||||
std::wstring* GetAsyncName(uint32_t id, std::vector<uint32_t> modifiers) {
|
||||
GW::ItemModifier parsedModifiers[64];
|
||||
for (auto i = 0; i < modifiers.size(); i++) {
|
||||
GW::ItemModifier parsedModifier;
|
||||
const auto mod = modifiers.at(i);
|
||||
parsedModifier.mod = mod;
|
||||
parsedModifiers[i] = parsedModifier;
|
||||
while (getline(ss, item, ',')) {
|
||||
item.erase(std::remove(item.begin(), item.end(), ' '), item.end());
|
||||
result.push_back(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
auto item = GW::Items::GetItemByModelIdAndModifiers(id, parsedModifiers, modifiers.size(), 1, 23);
|
||||
if (!item) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto name = new std::wstring();
|
||||
GW::Items::AsyncGetItemName(item, *name);
|
||||
return name;
|
||||
}
|
||||
|
||||
void EnsureInitialized() {
|
||||
GameThreadMutex.lock();
|
||||
if (!initialized) {
|
||||
GW::GameThread::RegisterGameThreadCallback(&GameThreadHook, [&](GW::HookStatus*) {
|
||||
while (!PromiseQueue.empty()) {
|
||||
auto promiseRequest = PromiseQueue.front();
|
||||
std::promise<NamePayload> &promise = std::get<2>(*promiseRequest);
|
||||
uint32_t id = std::get<0>(*promiseRequest);
|
||||
auto modifiers = std::get<1>(*promiseRequest);
|
||||
PromiseQueue.pop();
|
||||
try {
|
||||
auto name = GetAsyncName(id, modifiers);
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
WaitingList.emplace_back(id, &promise, name);
|
||||
}
|
||||
catch (...) {
|
||||
NamePayload payload;
|
||||
promise.set_value(payload);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < WaitingList.size(); ) {
|
||||
auto item = &WaitingList[i];
|
||||
auto name = std::get<2>(*item);
|
||||
if (name->empty()) {
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto promise = std::get<1>(*item);
|
||||
auto id = std::get<0>(*item);
|
||||
WaitingList.erase(WaitingList.begin() + i);
|
||||
NamePayload payload;
|
||||
payload.Id = id;
|
||||
payload.Name = Daybreak::Utils::WStringToString(*name);
|
||||
delete(name);
|
||||
promise->set_value(payload);
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
GameThreadMutex.unlock();
|
||||
std::string ItemNameModule::ApiUri() {
|
||||
return "/items/name";
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitAndRemoveSpaces(const std::string& s) {
|
||||
std::vector<std::string> result;
|
||||
std::stringstream ss(s);
|
||||
std::string item;
|
||||
|
||||
while (getline(ss, item, ',')) {
|
||||
item.erase(std::remove(item.begin(), item.end(), ' '), item.end());
|
||||
result.push_back(item);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void GetName(const httplib::Request& req, httplib::Response& res) {
|
||||
auto callbackEntry = new GW::HookEntry;
|
||||
std::optional<GW::Item*> ItemNameModule::GetContext(const httplib::Request& req, httplib::Response& res) {
|
||||
uint32_t id = 0;
|
||||
std::vector<uint32_t> modifiers;
|
||||
|
||||
|
||||
auto id_it = req.params.find("id");
|
||||
if (id_it == req.params.end()) {
|
||||
res.status = 400;
|
||||
res.set_content("Missing id parameter", "text/plain");
|
||||
return;
|
||||
return std::optional<GW::Item*>();
|
||||
}
|
||||
else {
|
||||
auto& idStr = id_it->second;
|
||||
int parsedId;
|
||||
if (!Daybreak::Utils::StringToInt(idStr, parsedId)){
|
||||
if (!Daybreak::Utils::StringToInt(idStr, parsedId)) {
|
||||
res.status = 400;
|
||||
res.set_content("Invalid id parameter", "text/plain");
|
||||
return;
|
||||
return std::optional<GW::Item*>();
|
||||
}
|
||||
|
||||
id = static_cast<uint32_t>(parsedId);
|
||||
@@ -129,34 +60,56 @@ namespace Daybreak::Modules::ItemNameModule {
|
||||
if (mod_it == req.params.end()) {
|
||||
res.status = 400;
|
||||
res.set_content("Missing modifiers parameter", "text/plain");
|
||||
return;
|
||||
return std::optional<GW::Item*>();
|
||||
}
|
||||
else {
|
||||
auto& modifiersStr = mod_it->second;
|
||||
const auto tokens = SplitAndRemoveSpaces(modifiersStr);
|
||||
const auto tokens = ItemNameModuleInternal::SplitAndRemoveSpaces(modifiersStr);
|
||||
for (const auto& token : tokens) {
|
||||
int parsedModifier;
|
||||
if (!Daybreak::Utils::StringToInt(token, parsedModifier)) {
|
||||
res.status = 400;
|
||||
res.set_content("Invalid modifier parameter", "text/plain");
|
||||
return;
|
||||
return std::optional<GW::Item*>();
|
||||
}
|
||||
|
||||
modifiers.push_back(static_cast<uint32_t>(parsedModifier));
|
||||
}
|
||||
}
|
||||
|
||||
auto response = new std::tuple<uint32_t, std::vector<uint32_t>, std::promise<NamePayload>>();
|
||||
std::get<0>(*response) = id;
|
||||
std::promise<NamePayload>& promise = std::get<2>(*response);
|
||||
std::get<1>(*response) = modifiers;
|
||||
GW::ItemModifier parsedModifiers[64];
|
||||
for (auto i = 0U; i < modifiers.size(); i++) {
|
||||
GW::ItemModifier parsedModifier;
|
||||
const auto mod = modifiers.at(i);
|
||||
parsedModifier.mod = mod;
|
||||
parsedModifiers[i] = parsedModifier;
|
||||
}
|
||||
|
||||
EnsureInitialized();
|
||||
PromiseQueue.emplace(response);
|
||||
auto item = GW::Items::GetItemByModelIdAndModifiers(id, parsedModifiers, modifiers.size(), 1, 23);
|
||||
if (!item) {
|
||||
return std::optional<GW::Item*>();
|
||||
}
|
||||
|
||||
json responsePayload = promise.get_future().get();
|
||||
delete callbackEntry;
|
||||
delete response;
|
||||
res.set_content(responsePayload.dump(), "text/json");
|
||||
return item;
|
||||
}
|
||||
|
||||
std::optional<NamePayload> ItemNameModule::GetPayload(GW::Item* context) {
|
||||
NamePayload payload;
|
||||
if (!context) {
|
||||
return std::optional<NamePayload>();
|
||||
}
|
||||
|
||||
payload.Id = context->item_id;
|
||||
GW::Items::AsyncGetItemName(context, this->name);
|
||||
return payload;
|
||||
}
|
||||
|
||||
bool ItemNameModule::CanReturn(const httplib::Request& req, httplib::Response& res, const NamePayload& payload) {
|
||||
return !name.empty();
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string> ItemNameModule::ReturnPayload(NamePayload payload) {
|
||||
payload.Name = Daybreak::Utils::WStringToString(this->name);
|
||||
return std::make_tuple(static_cast<json>(payload).dump(), "application/json");
|
||||
}
|
||||
}
|
||||
@@ -9,15 +9,14 @@
|
||||
#include <queue>
|
||||
#include "Utils.h"
|
||||
|
||||
namespace Daybreak::Modules::LoginModule {
|
||||
std::queue<std::promise<LoginPayload>*> PromiseQueue;
|
||||
std::mutex GameThreadMutex;
|
||||
GW::HookEntry GameThreadHook;
|
||||
volatile bool initialized = false;
|
||||
|
||||
LoginPayload GetPayload() {
|
||||
auto context = GW::GetCharContext();
|
||||
namespace Daybreak::Modules {
|
||||
std::optional<LoginPayload> LoginModule::GetPayload(const uint32_t) {
|
||||
const auto context = GW::GetCharContext();
|
||||
LoginPayload loginPayload;
|
||||
if (!context) {
|
||||
return loginPayload;
|
||||
}
|
||||
|
||||
std::wstring playerEmail(context->player_email);
|
||||
std::wstring playerName(context->player_name);
|
||||
loginPayload.Email = Daybreak::Utils::WStringToString(playerEmail);
|
||||
@@ -26,40 +25,12 @@ namespace Daybreak::Modules::LoginModule {
|
||||
return loginPayload;
|
||||
}
|
||||
|
||||
void EnsureInitialized() {
|
||||
GameThreadMutex.lock();
|
||||
if (!initialized) {
|
||||
GW::GameThread::RegisterGameThreadCallback(&GameThreadHook, [&](GW::HookStatus*) {
|
||||
while (!PromiseQueue.empty()) {
|
||||
auto promise = PromiseQueue.front();
|
||||
PromiseQueue.pop();
|
||||
try {
|
||||
auto payload = GetPayload();
|
||||
promise->set_value(payload);
|
||||
}
|
||||
catch (...) {
|
||||
LoginPayload payload;
|
||||
promise->set_value(payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
GameThreadMutex.unlock();
|
||||
std::optional<uint32_t> LoginModule::GetContext(const httplib::Request& req, httplib::Response& res) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GetLoginInfo(const httplib::Request&, httplib::Response& res) {
|
||||
auto callbackEntry = new GW::HookEntry;
|
||||
auto response = new std::promise<LoginPayload>;
|
||||
|
||||
EnsureInitialized();
|
||||
PromiseQueue.emplace(response);
|
||||
json responsePayload = response->get_future().get();
|
||||
|
||||
delete callbackEntry;
|
||||
delete response;
|
||||
res.set_content(responsePayload.dump(), "text/json");
|
||||
std::string LoginModule::ApiUri()
|
||||
{
|
||||
return "/login";
|
||||
}
|
||||
}
|
||||
@@ -26,375 +26,401 @@
|
||||
#include <algorithm>
|
||||
#include "Utils.h"
|
||||
|
||||
namespace Daybreak::Modules::MainPlayerModule {
|
||||
std::queue<std::promise<MainPlayer>*> PromiseQueue;
|
||||
std::mutex GameThreadMutex;
|
||||
GW::HookEntry GameThreadHook;
|
||||
volatile bool initialized = false;
|
||||
namespace Daybreak::Modules {
|
||||
namespace MainPlayerModuleInternal {
|
||||
std::list<QuestMetadata> GetQuestLog() {
|
||||
std::list<QuestMetadata> questMetadatas;
|
||||
const auto questLog = GW::QuestMgr::GetQuestLog();
|
||||
if (!questLog) {
|
||||
return questMetadatas;
|
||||
}
|
||||
|
||||
for (auto& quest : *questLog) {
|
||||
QuestMetadata metadata;
|
||||
metadata.FromId = (uint32_t)quest.map_from;
|
||||
metadata.ToId = (uint32_t)quest.map_to;
|
||||
metadata.Id = (uint32_t)quest.quest_id;
|
||||
metadata.PosX = quest.marker.x;
|
||||
metadata.PosY = quest.marker.y;
|
||||
questMetadatas.push_back(metadata);
|
||||
}
|
||||
|
||||
std::list<QuestMetadata> GetQuestLog() {
|
||||
std::list<QuestMetadata> questMetadatas;
|
||||
auto questLog = GW::QuestMgr::GetQuestLog();
|
||||
if (!questLog) {
|
||||
return questMetadatas;
|
||||
}
|
||||
|
||||
for (auto& quest : *questLog) {
|
||||
QuestMetadata metadata;
|
||||
metadata.FromId = (uint32_t)quest.map_from;
|
||||
metadata.ToId = (uint32_t)quest.map_to;
|
||||
metadata.Id = (uint32_t)quest.quest_id;
|
||||
metadata.PosX = quest.marker.x;
|
||||
metadata.PosY = quest.marker.y;
|
||||
questMetadatas.push_back(metadata);
|
||||
}
|
||||
|
||||
return questMetadatas;
|
||||
}
|
||||
|
||||
std::vector<GW::MapAgent> GetMapEntities() {
|
||||
std::vector<GW::MapAgent> entities;
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
for (auto& agent : worldContext->map_agents) {
|
||||
entities.push_back(agent);
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
std::list<GW::PartyAttribute> GetPartyAttributes() {
|
||||
std::list<GW::PartyAttribute> attributes;
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
for (auto& attribute : worldContext->attributes) {
|
||||
attributes.push_back(attribute);
|
||||
}
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
std::list<GW::ProfessionState> GetProfessions() {
|
||||
std::list<GW::ProfessionState> professions;
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
for (auto& profession : worldContext->party_profession_states) {
|
||||
professions.push_back(profession);
|
||||
}
|
||||
|
||||
return professions;
|
||||
}
|
||||
|
||||
std::list<GW::AgentLiving> GetLivingAgents() {
|
||||
std::list<GW::AgentLiving> agentList;
|
||||
const GW::AgentArray* agents_ptr = GW::Agents::GetAgentArray();
|
||||
GW::AgentArray* agents = GW::Agents::GetAgentArray();
|
||||
if (!agents) {
|
||||
return agentList;
|
||||
}
|
||||
|
||||
for (auto* a : *agents) {
|
||||
const GW::AgentLiving* agent = a ? a->GetAsAgentLiving() : nullptr;
|
||||
if (agent) {
|
||||
agentList.push_back(*agent);
|
||||
}
|
||||
}
|
||||
|
||||
return agentList;
|
||||
}
|
||||
|
||||
std::list<GW::NPC> GetNpcs() {
|
||||
std::list<GW::NPC> npcs;
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
for (auto& npc : worldContext->npcs) {
|
||||
npcs.push_back(npc);
|
||||
}
|
||||
|
||||
return npcs;
|
||||
}
|
||||
|
||||
std::vector<GW::TitleTier> GetTitleTiers() {
|
||||
std::vector<GW::TitleTier> titles;
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
for (auto& title : worldContext->title_tiers) {
|
||||
titles.push_back(title);
|
||||
}
|
||||
|
||||
return titles;
|
||||
}
|
||||
|
||||
std::list<GW::Title> GetTitles() {
|
||||
std::list<GW::Title> titles;
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
for (auto& title : worldContext->titles) {
|
||||
titles.push_back(title);
|
||||
}
|
||||
|
||||
return titles;
|
||||
}
|
||||
|
||||
std::list<Temp::GWPlayer> GetPlayers() {
|
||||
std::list<Temp::GWPlayer> players;
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
Temp::GWPlayer *tempPlayerArray = (Temp::GWPlayer*)worldContext->players.m_buffer;
|
||||
if (!tempPlayerArray) {
|
||||
return players;
|
||||
}
|
||||
|
||||
for (auto i = 0; i < worldContext->players.m_size; i++) {
|
||||
if (tempPlayerArray->agent_id != 0) {
|
||||
players.push_back(*tempPlayerArray);
|
||||
}
|
||||
tempPlayerArray++;
|
||||
}
|
||||
|
||||
return players;
|
||||
}
|
||||
|
||||
std::map<uint32_t, std::list<uint32_t>> GetSkillbars() {
|
||||
std::map<uint32_t, std::list<uint32_t>> skillbarMap;
|
||||
auto skillbars = GW::SkillbarMgr::GetSkillbarArray();
|
||||
if (!skillbars) {
|
||||
return skillbarMap;
|
||||
}
|
||||
|
||||
for (auto& skillbar : *skillbars) {
|
||||
std::list<uint32_t> skillList;
|
||||
skillList.push_back((uint32_t)skillbar.skills[0].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[1].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[2].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[3].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[4].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[5].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[6].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[7].skill_id);
|
||||
skillbarMap[skillbar.agent_id] = skillList;
|
||||
}
|
||||
|
||||
return skillbarMap;
|
||||
}
|
||||
|
||||
std::list<LivingEntity> GetLivingEntities(
|
||||
std::list<GW::AgentLiving> agents) {
|
||||
std::list<LivingEntity> entities;
|
||||
for (auto& agent : agents) {
|
||||
LivingEntity entity;
|
||||
entity.PrimaryProfessionId = agent.primary;
|
||||
entity.SecondaryProfessionId = agent.secondary;
|
||||
entity.EntityState = agent.type_map;
|
||||
entity.EntityAllegiance = (uint32_t)agent.allegiance;
|
||||
entity.NpcDefinition = agent.player_number;
|
||||
entity.Id = agent.agent_id;
|
||||
entity.Timer = agent.timer;
|
||||
entity.Level = agent.level;
|
||||
entity.PosX = agent.pos.x;
|
||||
entity.PosY = agent.pos.y;
|
||||
entities.push_back(entity);
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
void PopulatePlayer(
|
||||
Player& player,
|
||||
int playerId,
|
||||
GW::ProfessionState* professionState,
|
||||
std::list<uint32_t> skillbar,
|
||||
GW::PartyAttribute* partyAttribute,
|
||||
GW::AgentLiving* agent) {
|
||||
|
||||
std::list<uint32_t> unlockedProfessions;
|
||||
if (professionState) {
|
||||
for (auto i = 0; i < 11; i++) {
|
||||
auto prof = (GW::Constants::Profession)i;
|
||||
if (professionState->IsProfessionUnlocked(prof)) {
|
||||
unlockedProfessions.push_back(i);
|
||||
}
|
||||
std::vector<GW::MapAgent> GetMapEntities() {
|
||||
std::vector<GW::MapAgent> entities;
|
||||
const auto worldContext = GW::GetWorldContext();
|
||||
if (!worldContext) {
|
||||
return entities;
|
||||
}
|
||||
|
||||
unlockedProfessions.push_back((uint32_t)professionState->primary);
|
||||
for (auto& agent : worldContext->map_agents) {
|
||||
entities.push_back(agent);
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
Build build;
|
||||
std::list<Attribute> attributes;
|
||||
if (partyAttribute) {
|
||||
for (const auto& partyAttr : partyAttribute->attribute) {
|
||||
if (((uint32_t)partyAttr.id < 0) ||
|
||||
((uint32_t)partyAttr.id > 44) ||
|
||||
partyAttr.level <= 0) {
|
||||
continue;
|
||||
}
|
||||
std::list<GW::PartyAttribute> GetPartyAttributes() {
|
||||
std::list<GW::PartyAttribute> attributes;
|
||||
const auto worldContext = GW::GetWorldContext();
|
||||
if (!worldContext) {
|
||||
return attributes;
|
||||
}
|
||||
|
||||
Attribute attribute;
|
||||
attribute.Id = (uint32_t)partyAttr.id;
|
||||
attribute.BaseLevel = partyAttr.level_base;
|
||||
attribute.ActualLevel = partyAttr.level;
|
||||
attribute.DecrementPoints = partyAttr.decrement_points;
|
||||
attribute.IncrementPoints = partyAttr.increment_points;
|
||||
for (auto& attribute : worldContext->attributes) {
|
||||
attributes.push_back(attribute);
|
||||
}
|
||||
|
||||
build.Attributes = attributes;
|
||||
return attributes;
|
||||
}
|
||||
|
||||
std::list<GW::ProfessionState> GetProfessions() {
|
||||
std::list<GW::ProfessionState> professions;
|
||||
const auto worldContext = GW::GetWorldContext();
|
||||
if (!worldContext) {
|
||||
return professions;
|
||||
}
|
||||
|
||||
for (auto& profession : worldContext->party_profession_states) {
|
||||
professions.push_back(profession);
|
||||
}
|
||||
|
||||
return professions;
|
||||
}
|
||||
|
||||
std::list<GW::AgentLiving> GetLivingAgents() {
|
||||
std::list<GW::AgentLiving> agentList;
|
||||
const GW::AgentArray* agents = GW::Agents::GetAgentArray();
|
||||
if (!agents) {
|
||||
return agentList;
|
||||
}
|
||||
|
||||
for (auto* a : *agents) {
|
||||
const GW::AgentLiving* agent = a ? a->GetAsAgentLiving() : nullptr;
|
||||
if (agent) {
|
||||
agentList.push_back(*agent);
|
||||
}
|
||||
}
|
||||
|
||||
return agentList;
|
||||
}
|
||||
|
||||
std::list<GW::NPC> GetNpcs() {
|
||||
std::list<GW::NPC> npcs;
|
||||
const auto worldContext = GW::GetWorldContext();
|
||||
if (!worldContext) {
|
||||
return npcs;
|
||||
}
|
||||
|
||||
for (auto& npc : worldContext->npcs) {
|
||||
npcs.push_back(npc);
|
||||
}
|
||||
|
||||
return npcs;
|
||||
}
|
||||
|
||||
std::vector<GW::TitleTier> GetTitleTiers() {
|
||||
std::vector<GW::TitleTier> titles;
|
||||
const auto worldContext = GW::GetWorldContext();
|
||||
if (!worldContext) {
|
||||
return titles;
|
||||
}
|
||||
|
||||
for (auto& title : worldContext->title_tiers) {
|
||||
titles.push_back(title);
|
||||
}
|
||||
|
||||
return titles;
|
||||
}
|
||||
|
||||
std::list<GW::Title> GetTitles() {
|
||||
std::list<GW::Title> titles;
|
||||
const auto worldContext = GW::GetWorldContext();
|
||||
if (!worldContext) {
|
||||
return titles;
|
||||
}
|
||||
|
||||
for (auto& title : worldContext->titles) {
|
||||
titles.push_back(title);
|
||||
}
|
||||
|
||||
return titles;
|
||||
}
|
||||
|
||||
std::list<Temp::GWPlayer> GetPlayers() {
|
||||
std::list<Temp::GWPlayer> players;
|
||||
const auto worldContext = GW::GetWorldContext();
|
||||
if (!worldContext) {
|
||||
return players;
|
||||
}
|
||||
|
||||
Temp::GWPlayer* tempPlayerArray = (Temp::GWPlayer*)worldContext->players.m_buffer;
|
||||
if (!tempPlayerArray) {
|
||||
return players;
|
||||
}
|
||||
|
||||
for (auto i = 0U; i < worldContext->players.m_size; i++) {
|
||||
if (tempPlayerArray->agent_id != 0) {
|
||||
players.push_back(*tempPlayerArray);
|
||||
}
|
||||
tempPlayerArray++;
|
||||
}
|
||||
|
||||
return players;
|
||||
}
|
||||
|
||||
std::map<uint32_t, std::list<uint32_t>> GetSkillbars() {
|
||||
std::map<uint32_t, std::list<uint32_t>> skillbarMap;
|
||||
auto skillbars = GW::SkillbarMgr::GetSkillbarArray();
|
||||
if (!skillbars) {
|
||||
return skillbarMap;
|
||||
}
|
||||
|
||||
for (auto& skillbar : *skillbars) {
|
||||
std::list<uint32_t> skillList;
|
||||
skillList.push_back((uint32_t)skillbar.skills[0].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[1].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[2].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[3].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[4].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[5].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[6].skill_id);
|
||||
skillList.push_back((uint32_t)skillbar.skills[7].skill_id);
|
||||
skillbarMap[skillbar.agent_id] = skillList;
|
||||
}
|
||||
|
||||
return skillbarMap;
|
||||
}
|
||||
|
||||
std::list<LivingEntity> GetLivingEntities(
|
||||
std::list<GW::AgentLiving> agents) {
|
||||
std::list<LivingEntity> entities;
|
||||
for (auto& agent : agents) {
|
||||
LivingEntity entity;
|
||||
entity.PrimaryProfessionId = agent.primary;
|
||||
entity.SecondaryProfessionId = agent.secondary;
|
||||
entity.EntityState = agent.type_map;
|
||||
entity.EntityAllegiance = (uint32_t)agent.allegiance;
|
||||
entity.NpcDefinition = agent.player_number;
|
||||
entity.Id = agent.agent_id;
|
||||
entity.Timer = agent.timer;
|
||||
entity.Level = agent.level;
|
||||
entity.PosX = agent.pos.x;
|
||||
entity.PosY = agent.pos.y;
|
||||
entities.push_back(entity);
|
||||
}
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
void PopulatePlayer(
|
||||
Player& player,
|
||||
int playerId,
|
||||
GW::ProfessionState* professionState,
|
||||
std::list<uint32_t> skillbar,
|
||||
GW::PartyAttribute* partyAttribute,
|
||||
GW::AgentLiving* agent) {
|
||||
|
||||
std::list<uint32_t> unlockedProfessions;
|
||||
if (professionState) {
|
||||
for (auto i = 0; i < 11; i++) {
|
||||
auto prof = (GW::Constants::Profession)i;
|
||||
if (professionState->IsProfessionUnlocked(prof)) {
|
||||
unlockedProfessions.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
unlockedProfessions.push_back((uint32_t)professionState->primary);
|
||||
}
|
||||
|
||||
Build build;
|
||||
std::list<Attribute> attributes;
|
||||
if (partyAttribute) {
|
||||
for (const auto& partyAttr : partyAttribute->attribute) {
|
||||
if (((uint32_t)partyAttr.id < 0) ||
|
||||
((uint32_t)partyAttr.id > 44) ||
|
||||
partyAttr.level <= 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Attribute attribute;
|
||||
attribute.Id = (uint32_t)partyAttr.id;
|
||||
attribute.BaseLevel = partyAttr.level_base;
|
||||
attribute.ActualLevel = partyAttr.level;
|
||||
attribute.DecrementPoints = partyAttr.decrement_points;
|
||||
attribute.IncrementPoints = partyAttr.increment_points;
|
||||
attributes.push_back(attribute);
|
||||
}
|
||||
|
||||
build.Attributes = attributes;
|
||||
if (agent) {
|
||||
build.Primary = (uint32_t)agent->primary;
|
||||
build.Secondary = (uint32_t)agent->secondary;
|
||||
}
|
||||
else if (professionState) {
|
||||
build.Primary = (uint32_t)professionState->primary;
|
||||
build.Secondary = (uint32_t)professionState->secondary;
|
||||
}
|
||||
|
||||
build.Skills = skillbar;
|
||||
player.Build = build;
|
||||
}
|
||||
|
||||
player.UnlockedProfession = unlockedProfessions;
|
||||
player.Id = playerId;
|
||||
if (agent) {
|
||||
build.Primary = (uint32_t)agent->primary;
|
||||
build.Secondary = (uint32_t)agent->secondary;
|
||||
player.PosX = agent->pos.x;
|
||||
player.PosY = agent->pos.y;
|
||||
player.Timer = agent->timer;
|
||||
player.Level = agent->level;
|
||||
player.NpcDefinition = agent->player_number;
|
||||
player.PrimaryProfessionId = (uint32_t)agent->primary;
|
||||
player.SecondaryProfessionId = (uint32_t)agent->secondary;
|
||||
player.Health = agent->hp * (agent->max_hp > 0 ? agent->max_hp : 1);
|
||||
player.Energy = agent->energy * (agent->max_energy > 0 ? agent->max_energy : 1);
|
||||
player.RotationAngle = agent->rotation_angle;
|
||||
}
|
||||
else if (professionState) {
|
||||
build.Primary = (uint32_t)professionState->primary;
|
||||
build.Secondary = (uint32_t)professionState->secondary;
|
||||
player.PrimaryProfessionId = (uint32_t)professionState->primary;
|
||||
player.SecondaryProfessionId = (uint32_t)professionState->secondary;
|
||||
}
|
||||
}
|
||||
|
||||
void PopulateWorldPlayer(
|
||||
std::list<GW::Title> titles,
|
||||
std::vector<GW::TitleTier> titleTiers,
|
||||
WorldPlayer& player,
|
||||
int playerId,
|
||||
Temp::GWPlayer* gwPlayer,
|
||||
GW::ProfessionState* professionState,
|
||||
std::list<uint32_t> skillbar,
|
||||
GW::PartyAttribute* partyAttribute,
|
||||
GW::AgentLiving* agent) {
|
||||
|
||||
PopulatePlayer(player, playerId, professionState, skillbar, partyAttribute, agent);
|
||||
if (!gwPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
build.Skills = skillbar;
|
||||
player.Build = build;
|
||||
}
|
||||
GW::Title gwTitle;
|
||||
Title title;
|
||||
int titleId = 0;
|
||||
std::wstring gwPlayerName(gwPlayer->name);
|
||||
auto name = Daybreak::Utils::WStringToString(gwPlayerName);
|
||||
player.Name = name;
|
||||
|
||||
player.UnlockedProfession = unlockedProfessions;
|
||||
player.Id = playerId;
|
||||
if (agent) {
|
||||
player.PosX = agent->pos.x;
|
||||
player.PosY = agent->pos.y;
|
||||
player.Timer = agent->timer;
|
||||
player.Level = agent->level;
|
||||
player.NpcDefinition = agent->player_number;
|
||||
player.PrimaryProfessionId = (uint32_t)agent->primary;
|
||||
player.SecondaryProfessionId = (uint32_t)agent->secondary;
|
||||
player.Health = agent->hp * (agent->max_hp > 0 ? agent->max_hp : 1);
|
||||
player.Energy = agent->energy * (agent->max_energy > 0 ? agent->max_energy : 1);
|
||||
}
|
||||
else if (professionState) {
|
||||
player.PrimaryProfessionId = (uint32_t)professionState->primary;
|
||||
player.SecondaryProfessionId = (uint32_t)professionState->secondary;
|
||||
}
|
||||
}
|
||||
|
||||
void PopulateWorldPlayer(
|
||||
std::list<GW::Title> titles,
|
||||
std::vector<GW::TitleTier> titleTiers,
|
||||
WorldPlayer& player,
|
||||
int playerId,
|
||||
Temp::GWPlayer* gwPlayer,
|
||||
GW::ProfessionState* professionState,
|
||||
std::list<uint32_t> skillbar,
|
||||
GW::PartyAttribute* partyAttribute,
|
||||
GW::AgentLiving* agent) {
|
||||
|
||||
PopulatePlayer(player, playerId, professionState, skillbar, partyAttribute, agent);
|
||||
if (!gwPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
GW::Title gwTitle;
|
||||
Title title;
|
||||
int titleId = 0;
|
||||
std::wstring gwPlayerName(gwPlayer->name);
|
||||
auto name = Daybreak::Utils::WStringToString(gwPlayerName);
|
||||
player.Name = name;
|
||||
|
||||
for (const auto &t : titles) {
|
||||
if (t.current_title_tier_index == gwPlayer->active_title_tier) {
|
||||
gwTitle = t;
|
||||
break;
|
||||
if (titles.empty() ||
|
||||
titleTiers.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
titleId++;
|
||||
for (const auto& t : titles) {
|
||||
if (t.current_title_tier_index == gwPlayer->active_title_tier) {
|
||||
gwTitle = t;
|
||||
break;
|
||||
}
|
||||
|
||||
titleId++;
|
||||
}
|
||||
|
||||
if (gwTitle.current_title_tier_index >= titleTiers.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto titleTier = titleTiers[gwTitle.current_title_tier_index];
|
||||
title.CurrentPoints = gwTitle.current_points;
|
||||
title.IsPercentage = gwTitle.is_percentage_based();
|
||||
title.PointsForCurrentRank = gwTitle.points_needed_current_rank;
|
||||
title.PointsForNextRank = titleTier.tier_number == gwTitle.max_title_rank ? gwTitle.current_points : gwTitle.points_needed_next_rank;
|
||||
title.TierNumber = titleTier.tier_number;
|
||||
title.MaxTierNumber = gwTitle.max_title_rank;
|
||||
title.Id = titleId;
|
||||
|
||||
player.Title = title;
|
||||
}
|
||||
|
||||
if (gwTitle.current_title_tier_index >= titleTiers.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto titleTier = titleTiers[gwTitle.current_title_tier_index];
|
||||
title.CurrentPoints = gwTitle.current_points;
|
||||
title.IsPercentage = gwTitle.is_percentage_based();
|
||||
title.PointsForCurrentRank = gwTitle.points_needed_current_rank;
|
||||
title.PointsForNextRank = titleTier.tier_number == gwTitle.max_title_rank ? gwTitle.current_points : gwTitle.points_needed_next_rank;
|
||||
title.TierNumber = titleTier.tier_number;
|
||||
title.MaxTierNumber = gwTitle.max_title_rank;
|
||||
title.Id = titleId;
|
||||
|
||||
player.Title = title;
|
||||
}
|
||||
|
||||
void PopulateMainPlayer(
|
||||
MainPlayer& player,
|
||||
int activeQuestId,
|
||||
std::list<QuestMetadata> questLog,
|
||||
std::list<GW::Title> titles,
|
||||
std::vector<GW::TitleTier> titleTiers,
|
||||
int playerId,
|
||||
Temp::GWPlayer* gwPlayer,
|
||||
GW::ProfessionState* professionState,
|
||||
std::list<uint32_t> skillbar,
|
||||
GW::PartyAttribute* partyAttribute,
|
||||
GW::AgentLiving* agent) {
|
||||
PopulateWorldPlayer(titles, titleTiers, player, playerId, gwPlayer, professionState, skillbar, partyAttribute, agent);
|
||||
player.CurrentQuest = activeQuestId;
|
||||
player.QuestLog = questLog;
|
||||
player.HardModeUnlocked = GW::GetWorldContext()->is_hard_mode_unlocked == 1;
|
||||
player.Experience = GW::GetWorldContext()->experience;
|
||||
player.Morale = GW::GetWorldContext()->morale;
|
||||
if (agent) {
|
||||
player.CurrentEnergy = agent->max_energy * agent->energy;
|
||||
player.CurrentHp = agent->max_hp * agent->hp;
|
||||
player.MaxEnergy = agent->max_energy;
|
||||
player.MaxHp = agent->max_hp;
|
||||
void PopulateMainPlayer(
|
||||
MainPlayer& player,
|
||||
int activeQuestId,
|
||||
std::list<QuestMetadata> questLog,
|
||||
std::list<GW::Title> titles,
|
||||
std::vector<GW::TitleTier> titleTiers,
|
||||
int playerId,
|
||||
Temp::GWPlayer* gwPlayer,
|
||||
GW::ProfessionState* professionState,
|
||||
std::list<uint32_t> skillbar,
|
||||
GW::PartyAttribute* partyAttribute,
|
||||
GW::AgentLiving* agent) {
|
||||
PopulateWorldPlayer(titles, titleTiers, player, playerId, gwPlayer, professionState, skillbar, partyAttribute, agent);
|
||||
player.CurrentQuest = activeQuestId;
|
||||
player.QuestLog = questLog;
|
||||
player.HardModeUnlocked = GW::GetWorldContext()->is_hard_mode_unlocked == 1;
|
||||
player.Experience = GW::GetWorldContext()->experience;
|
||||
player.Morale = GW::GetWorldContext()->morale;
|
||||
if (agent) {
|
||||
player.CurrentEnergy = agent->max_energy * agent->energy;
|
||||
player.CurrentHp = agent->max_hp * agent->hp;
|
||||
player.MaxEnergy = agent->max_energy;
|
||||
player.MaxHp = agent->max_hp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MainPlayer GetPayload() {
|
||||
MainPlayer gamePayload;
|
||||
std::optional<MainPlayer> MainPlayerModule::GetPayload(const uint32_t) {
|
||||
MainPlayer mainPlayer;
|
||||
if (!GW::Map::GetIsMapLoaded()) {
|
||||
return gamePayload;
|
||||
return mainPlayer;
|
||||
}
|
||||
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
if (!worldContext) {
|
||||
return mainPlayer;
|
||||
}
|
||||
|
||||
auto activeQuestId = GW::QuestMgr::GetActiveQuestId();
|
||||
auto questLog = GetQuestLog();
|
||||
auto skillbars = GetSkillbars();
|
||||
auto questLog = MainPlayerModuleInternal::GetQuestLog();
|
||||
auto skillbars = MainPlayerModuleInternal::GetSkillbars();
|
||||
if (skillbars.empty()) {
|
||||
return gamePayload;
|
||||
return mainPlayer;
|
||||
}
|
||||
|
||||
auto agents = GetLivingAgents();
|
||||
auto agents = MainPlayerModuleInternal::GetLivingAgents();
|
||||
if (agents.empty()) {
|
||||
return gamePayload;
|
||||
return mainPlayer;
|
||||
}
|
||||
|
||||
auto titles = GetTitles();
|
||||
if (titles.empty()) {
|
||||
return gamePayload;
|
||||
}
|
||||
auto titles = MainPlayerModuleInternal::GetTitles();
|
||||
|
||||
auto titleTiers = GetTitleTiers();
|
||||
if (titleTiers.empty()) {
|
||||
return gamePayload;
|
||||
}
|
||||
auto titleTiers = MainPlayerModuleInternal::GetTitleTiers();
|
||||
|
||||
auto players = GetPlayers();
|
||||
auto players = MainPlayerModuleInternal::GetPlayers();
|
||||
if (players.empty()) {
|
||||
return gamePayload;
|
||||
return mainPlayer;
|
||||
}
|
||||
|
||||
auto npcs = GetNpcs();
|
||||
auto npcs = MainPlayerModuleInternal::GetNpcs();
|
||||
if (npcs.empty()) {
|
||||
return gamePayload;
|
||||
return mainPlayer;
|
||||
}
|
||||
|
||||
auto attributes = GetPartyAttributes();
|
||||
auto attributes = MainPlayerModuleInternal::GetPartyAttributes();
|
||||
if (attributes.empty()) {
|
||||
return gamePayload;
|
||||
return mainPlayer;
|
||||
}
|
||||
|
||||
auto professions = GetProfessions();
|
||||
auto professions = MainPlayerModuleInternal::GetProfessions();
|
||||
if (professions.empty()) {
|
||||
return gamePayload;
|
||||
return mainPlayer;
|
||||
}
|
||||
|
||||
auto mapAgents = GetMapEntities();
|
||||
auto mapAgents = MainPlayerModuleInternal::GetMapEntities();
|
||||
if (mapAgents.empty()) {
|
||||
return gamePayload;
|
||||
return mainPlayer;
|
||||
}
|
||||
auto playerAgentId = GW::Agents::GetPlayerId();
|
||||
|
||||
|
||||
MainPlayer mainPlayer;
|
||||
auto foundMainAttr = std::find_if(attributes.begin(), attributes.end(), [&](GW::PartyAttribute attribute) {
|
||||
return playerAgentId == attribute.agent_id;
|
||||
});
|
||||
@@ -439,44 +465,17 @@ namespace Daybreak::Modules::MainPlayerModule {
|
||||
mainGwPlayer = &*foundMainGwPlayer;
|
||||
}
|
||||
|
||||
PopulateMainPlayer(mainPlayer, (uint32_t)activeQuestId, questLog, titles, titleTiers, playerAgentId, mainGwPlayer, mainProfPtr, skillbars[playerAgentId], mainAttrPtr, mainAgentPtr);
|
||||
MainPlayerModuleInternal::PopulateMainPlayer(mainPlayer, (uint32_t)activeQuestId, questLog, titles, titleTiers, playerAgentId, mainGwPlayer, mainProfPtr, skillbars[playerAgentId], mainAttrPtr, mainAgentPtr);
|
||||
return mainPlayer;
|
||||
}
|
||||
|
||||
void EnsureInitialized() {
|
||||
GameThreadMutex.lock();
|
||||
if (!initialized) {
|
||||
GW::GameThread::RegisterGameThreadCallback(&GameThreadHook, [&](GW::HookStatus*) {
|
||||
while (!PromiseQueue.empty()) {
|
||||
auto promise = PromiseQueue.front();
|
||||
PromiseQueue.pop();
|
||||
try {
|
||||
auto payload = GetPayload();
|
||||
promise->set_value(payload);
|
||||
}
|
||||
catch (...) {
|
||||
MainPlayer payload;
|
||||
promise->set_value(payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
GameThreadMutex.unlock();
|
||||
std::optional<uint32_t> MainPlayerModule::GetContext(const httplib::Request& req, httplib::Response& res)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void GetMainPlayer(const httplib::Request&, httplib::Response& res) {
|
||||
auto callbackEntry = new GW::HookEntry;
|
||||
auto response = new std::promise<MainPlayer>;
|
||||
|
||||
EnsureInitialized();
|
||||
PromiseQueue.emplace(response);
|
||||
json responsePayload = response->get_future().get();
|
||||
|
||||
delete callbackEntry;
|
||||
delete response;
|
||||
res.set_content(responsePayload.dump(), "text/json");
|
||||
std::string MainPlayerModule::ApiUri()
|
||||
{
|
||||
return "/game/mainplayer";
|
||||
}
|
||||
}
|
||||
@@ -7,21 +7,24 @@
|
||||
#include <json.hpp>
|
||||
#include <queue>
|
||||
|
||||
namespace Daybreak::Modules::MapModule {
|
||||
std::queue<std::promise<MapPayload>*> PromiseQueue;
|
||||
std::mutex GameThreadMutex;
|
||||
GW::HookEntry GameThreadHook;
|
||||
volatile bool initialized = false;
|
||||
|
||||
MapPayload GetPayload() {
|
||||
namespace Daybreak::Modules {
|
||||
std::optional<MapPayload> MapModule::GetPayload(const uint32_t) {
|
||||
MapPayload mapPayload;
|
||||
auto isLoaded = GW::Map::GetIsMapLoaded();
|
||||
if (!isLoaded) {
|
||||
return mapPayload;
|
||||
}
|
||||
|
||||
auto instanceType = GW::Map::GetInstanceType();
|
||||
auto instanceTime = GW::Map::GetInstanceTime();
|
||||
auto mapInfo = GW::Map::GetCurrentMapInfo();
|
||||
auto isInCinematic = GW::Map::GetIsInCinematic();
|
||||
auto region = GW::Map::GetRegion();
|
||||
auto mapId = GW::Map::GetMapID();
|
||||
MapPayload mapPayload;
|
||||
if (!mapInfo) {
|
||||
return mapPayload;
|
||||
}
|
||||
|
||||
mapPayload.Campaign = static_cast<uint32_t>(mapInfo->campaign);
|
||||
mapPayload.Continent = static_cast<uint32_t>(mapInfo->continent);
|
||||
mapPayload.Region = mapInfo->region;
|
||||
@@ -33,40 +36,13 @@ namespace Daybreak::Modules::MapModule {
|
||||
return mapPayload;
|
||||
}
|
||||
|
||||
void EnsureInitialized() {
|
||||
GameThreadMutex.lock();
|
||||
if (!initialized) {
|
||||
GW::GameThread::RegisterGameThreadCallback(&GameThreadHook, [&](GW::HookStatus*) {
|
||||
while (!PromiseQueue.empty()) {
|
||||
auto promise = PromiseQueue.front();
|
||||
PromiseQueue.pop();
|
||||
try {
|
||||
auto payload = GetPayload();
|
||||
promise->set_value(payload);
|
||||
}
|
||||
catch (...) {
|
||||
MapPayload payload;
|
||||
promise->set_value(payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
GameThreadMutex.unlock();
|
||||
std::string MapModule::ApiUri()
|
||||
{
|
||||
return "/map";
|
||||
}
|
||||
|
||||
void GetMapInfo(const httplib::Request&, httplib::Response& res) {
|
||||
auto callbackEntry = new GW::HookEntry;
|
||||
auto response = new std::promise<MapPayload>;
|
||||
|
||||
EnsureInitialized();
|
||||
PromiseQueue.emplace(response);
|
||||
json responsePayload = response->get_future().get();
|
||||
|
||||
delete callbackEntry;
|
||||
delete response;
|
||||
res.set_content(responsePayload.dump(), "text/json");
|
||||
std::optional<uint32_t> MapModule::GetContext(const httplib::Request& req, httplib::Response& res)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,14 @@
|
||||
#include "PathingMetadataModule.h"
|
||||
#include <GWCA/Managers/GameThreadMgr.h>
|
||||
#include <GWCA/Managers/MapMgr.h>
|
||||
#include <GWCA/GameEntities/Pathing.h>
|
||||
#include <future>
|
||||
#include <payloads/PathingMetadataPayload.h>
|
||||
#include <json.hpp>
|
||||
#include <queue>
|
||||
#include <GWCA/GameEntities/Pathing.h>
|
||||
|
||||
namespace Daybreak::Modules::PathingMetadataModule {
|
||||
std::queue<std::promise<PathingMetadataPayload>*> PromiseQueue;
|
||||
std::mutex GameThreadMutex;
|
||||
GW::HookEntry GameThreadHook;
|
||||
volatile bool initialized = false;
|
||||
|
||||
PathingMetadataPayload GetPayload() {
|
||||
namespace Daybreak::Modules {
|
||||
std::optional<PathingMetadataPayload> PathingMetadataModule::GetPayload(const uint32_t) {
|
||||
PathingMetadataPayload pathingPayload;
|
||||
pathingPayload.TrapezoidCount = 0;
|
||||
if (!GW::Map::GetIsMapLoaded()) {
|
||||
@@ -22,8 +17,12 @@ namespace Daybreak::Modules::PathingMetadataModule {
|
||||
}
|
||||
|
||||
auto pathingMap = GW::Map::GetPathingMap();
|
||||
if (!pathingMap) {
|
||||
return pathingPayload;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (int i = 0; i < pathingMap->size(); i++) {
|
||||
for (auto i = 0U; i < pathingMap->size(); i++) {
|
||||
auto gwPathingMap = pathingMap->at(i);
|
||||
count += gwPathingMap.trapezoid_count;
|
||||
}
|
||||
@@ -32,40 +31,13 @@ namespace Daybreak::Modules::PathingMetadataModule {
|
||||
return pathingPayload;
|
||||
}
|
||||
|
||||
void EnsureInitialized() {
|
||||
GameThreadMutex.lock();
|
||||
if (!initialized) {
|
||||
GW::GameThread::RegisterGameThreadCallback(&GameThreadHook, [&](GW::HookStatus*) {
|
||||
while (!PromiseQueue.empty()) {
|
||||
auto promise = PromiseQueue.front();
|
||||
PromiseQueue.pop();
|
||||
try {
|
||||
auto payload = GetPayload();
|
||||
promise->set_value(payload);
|
||||
}
|
||||
catch (...) {
|
||||
PathingMetadataPayload payload;
|
||||
promise->set_value(payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
GameThreadMutex.unlock();
|
||||
std::string PathingMetadataModule::ApiUri()
|
||||
{
|
||||
return "/pathing/metadata";
|
||||
}
|
||||
|
||||
void GetPathingMetadata(const httplib::Request&, httplib::Response& res) {
|
||||
auto callbackEntry = new GW::HookEntry;
|
||||
auto response = new std::promise<PathingMetadataPayload>;
|
||||
|
||||
EnsureInitialized();
|
||||
PromiseQueue.emplace(response);
|
||||
json responsePayload = response->get_future().get();
|
||||
|
||||
delete callbackEntry;
|
||||
delete response;
|
||||
res.set_content(responsePayload.dump(), "text/json");
|
||||
std::optional<uint32_t> PathingMetadataModule::GetContext(const httplib::Request& req, httplib::Response& res)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,8 @@
|
||||
#include <queue>
|
||||
#include <GWCA/GameEntities/Pathing.h>
|
||||
|
||||
namespace Daybreak::Modules::PathingModule {
|
||||
std::queue<std::promise<PathingPayload>*> PromiseQueue;
|
||||
std::mutex GameThreadMutex;
|
||||
GW::HookEntry GameThreadHook;
|
||||
volatile bool initialized = false;
|
||||
|
||||
PathingPayload GetPayload() {
|
||||
namespace Daybreak::Modules {
|
||||
std::optional<PathingPayload> PathingModule::GetPayload(const uint32_t) {
|
||||
PathingPayload pathingPayload;
|
||||
pathingPayload.Trapezoids.clear();
|
||||
pathingPayload.AdjacencyList.clear();
|
||||
@@ -23,11 +18,15 @@ namespace Daybreak::Modules::PathingModule {
|
||||
}
|
||||
|
||||
auto pathingMap = GW::Map::GetPathingMap();
|
||||
if (!pathingMap) {
|
||||
return pathingPayload;
|
||||
}
|
||||
|
||||
std::list<PathingTrapezoid> trapezoids;
|
||||
std::map<int, std::list<int>> adjacencyMap;
|
||||
for (int i = 0; i < pathingMap->size(); i++) {
|
||||
for (auto i = 0U; i < pathingMap->size(); i++) {
|
||||
auto gwPathingMap = pathingMap->at(i);
|
||||
for (int j = 0; j < gwPathingMap.trapezoid_count; j++) {
|
||||
for (auto j = 0U; j < gwPathingMap.trapezoid_count; j++) {
|
||||
auto gwTrapezoid = gwPathingMap.trapezoids[j];
|
||||
PathingTrapezoid trapezoid;
|
||||
trapezoid.Id = gwTrapezoid.id;
|
||||
@@ -59,40 +58,13 @@ namespace Daybreak::Modules::PathingModule {
|
||||
return pathingPayload;
|
||||
}
|
||||
|
||||
void EnsureInitialized() {
|
||||
GameThreadMutex.lock();
|
||||
if (!initialized) {
|
||||
GW::GameThread::RegisterGameThreadCallback(&GameThreadHook, [&](GW::HookStatus*) {
|
||||
while (!PromiseQueue.empty()) {
|
||||
auto promise = PromiseQueue.front();
|
||||
PromiseQueue.pop();
|
||||
try {
|
||||
auto payload = GetPayload();
|
||||
promise->set_value(payload);
|
||||
}
|
||||
catch (...) {
|
||||
PathingPayload payload;
|
||||
promise->set_value(payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
GameThreadMutex.unlock();
|
||||
std::string PathingModule::ApiUri()
|
||||
{
|
||||
return "/pathing";
|
||||
}
|
||||
|
||||
void GetPathingData(const httplib::Request&, httplib::Response& res) {
|
||||
auto callbackEntry = new GW::HookEntry;
|
||||
auto response = new std::promise<PathingPayload>;
|
||||
|
||||
EnsureInitialized();
|
||||
PromiseQueue.emplace(response);
|
||||
json responsePayload = response->get_future().get();
|
||||
|
||||
delete callbackEntry;
|
||||
delete response;
|
||||
res.set_content(responsePayload.dump(), "text/json");
|
||||
std::optional<uint32_t> PathingModule::GetContext(const httplib::Request& req, httplib::Response& res)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -1,74 +1,50 @@
|
||||
#include "pch.h"
|
||||
#include "MapModule.h"
|
||||
#include <PreGameModule.h>
|
||||
#include <GWCA/Managers/GameThreadMgr.h>
|
||||
#include <GWCA/Managers/MapMgr.h>
|
||||
#include <future>
|
||||
#include <GWCA/Context/PreGameContext.h>
|
||||
#include <payloads/PreGamePayload.h>
|
||||
#include <json.hpp>
|
||||
#include <queue>
|
||||
#include <GWCA/GWCA.h>
|
||||
#include <GWCA/Context/PreGameContext.h>
|
||||
|
||||
namespace Daybreak::Modules::PreGameModule {
|
||||
std::queue<std::promise<PreGamePayload>*> PromiseQueue;
|
||||
std::mutex GameThreadMutex;
|
||||
GW::HookEntry GameThreadHook;
|
||||
volatile bool initialized = false;
|
||||
|
||||
PreGamePayload GetPayload() {
|
||||
namespace Daybreak::Modules {
|
||||
std::optional<PreGamePayload> PreGameModule::GetPayload(const uint32_t) {
|
||||
PreGamePayload preGamePayload;
|
||||
if (GW::Map::GetIsMapLoaded()) {
|
||||
if (GW::Map::GetIsMapLoaded())
|
||||
{
|
||||
preGamePayload.Characters.clear();
|
||||
preGamePayload.ChosenCharacterIndex = 0;
|
||||
return preGamePayload;
|
||||
}
|
||||
|
||||
auto context = GW::GetPreGameContext();
|
||||
for (const auto& loginChar : context->chars) {
|
||||
std::string charName(20, '\0');
|
||||
auto length = std::wcstombs(&charName[0], loginChar.character_name, 20);
|
||||
charName.resize(length);
|
||||
preGamePayload.Characters.push_back(charName);
|
||||
const auto context = GW::GetPreGameContext();
|
||||
if (!context)
|
||||
{
|
||||
return preGamePayload;
|
||||
}
|
||||
|
||||
for (const auto& loginChar : context->chars)
|
||||
{
|
||||
char charName[20];
|
||||
int result = WideCharToMultiByte(CP_UTF8, 0, loginChar.character_name, -1, charName, sizeof(charName), NULL, NULL);
|
||||
if (result == 0)
|
||||
{
|
||||
// handle error, use GetLastError() to get more info
|
||||
}
|
||||
|
||||
preGamePayload.Characters.emplace_back(charName);
|
||||
}
|
||||
|
||||
preGamePayload.ChosenCharacterIndex = context->index_1;
|
||||
return preGamePayload;
|
||||
}
|
||||
|
||||
void EnsureInitialized() {
|
||||
GameThreadMutex.lock();
|
||||
if (!initialized) {
|
||||
GW::GameThread::RegisterGameThreadCallback(&GameThreadHook, [&](GW::HookStatus*) {
|
||||
while (!PromiseQueue.empty()) {
|
||||
auto promise = PromiseQueue.front();
|
||||
PromiseQueue.pop();
|
||||
try {
|
||||
auto payload = GetPayload();
|
||||
promise->set_value(payload);
|
||||
}
|
||||
catch (...) {
|
||||
PreGamePayload payload;
|
||||
promise->set_value(payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
GameThreadMutex.unlock();
|
||||
std::string PreGameModule::ApiUri()
|
||||
{
|
||||
return "/pregame";
|
||||
}
|
||||
|
||||
void GetPreGameInfo(const httplib::Request&, httplib::Response& res) {
|
||||
auto callbackEntry = new GW::HookEntry;
|
||||
auto response = new std::promise<PreGamePayload>;
|
||||
|
||||
EnsureInitialized();
|
||||
PromiseQueue.emplace(response);
|
||||
json responsePayload = response->get_future().get();
|
||||
|
||||
delete callbackEntry;
|
||||
delete response;
|
||||
res.set_content(responsePayload.dump(), "text/json");
|
||||
std::optional<uint32_t> PreGameModule::GetContext(const httplib::Request& req, httplib::Response& res)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,10 @@ namespace http {
|
||||
server.Get(pattern, handler);
|
||||
}
|
||||
|
||||
void Post(const std::string& pattern, httplib::Server::Handler handler) {
|
||||
server.Post(pattern, handler);
|
||||
}
|
||||
|
||||
void SetLogger(httplib::Logger logger) {
|
||||
server.set_logger(logger);
|
||||
}
|
||||
@@ -62,6 +66,9 @@ namespace http {
|
||||
int port = FindPort();
|
||||
if (port > 0) {
|
||||
std::cout << "Initializing server on port " << port << std::endl;
|
||||
server.set_read_timeout(1, 0);
|
||||
server.set_write_timeout(1, 0);
|
||||
server.set_idle_interval(0, 100000);
|
||||
server.listen("0.0.0.0", port);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -9,13 +9,8 @@
|
||||
#include <GWCA/Context/AgentContext.h>
|
||||
#include <GWCA/Context/WorldContext.h>
|
||||
|
||||
namespace Daybreak::Modules::SessionModule {
|
||||
std::queue<std::promise<SessionPayload>*> PromiseQueue;
|
||||
std::mutex GameThreadMutex;
|
||||
GW::HookEntry GameThreadHook;
|
||||
volatile bool initialized = false;
|
||||
|
||||
SessionPayload GetPayload() {
|
||||
namespace Daybreak::Modules {
|
||||
std::optional<SessionPayload> SessionModule::GetPayload(const uint32_t) {
|
||||
SessionPayload sessionPayload;
|
||||
sessionPayload.MapId = (uint32_t)GW::Map::GetMapID();
|
||||
sessionPayload.InstanceType = (uint32_t)GW::Map::GetInstanceType();
|
||||
@@ -37,40 +32,13 @@ namespace Daybreak::Modules::SessionModule {
|
||||
return sessionPayload;
|
||||
}
|
||||
|
||||
void EnsureInitialized() {
|
||||
GameThreadMutex.lock();
|
||||
if (!initialized) {
|
||||
GW::GameThread::RegisterGameThreadCallback(&GameThreadHook, [&](GW::HookStatus*) {
|
||||
while (!PromiseQueue.empty()) {
|
||||
auto promise = PromiseQueue.front();
|
||||
PromiseQueue.pop();
|
||||
try {
|
||||
auto payload = GetPayload();
|
||||
promise->set_value(payload);
|
||||
}
|
||||
catch (...) {
|
||||
SessionPayload payload;
|
||||
promise->set_value(payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
GameThreadMutex.unlock();
|
||||
std::string SessionModule::ApiUri()
|
||||
{
|
||||
return "/session";
|
||||
}
|
||||
|
||||
void GetSessionInfo(const httplib::Request&, httplib::Response& res) {
|
||||
auto callbackEntry = new GW::HookEntry;
|
||||
auto response = new std::promise<SessionPayload>;
|
||||
|
||||
EnsureInitialized();
|
||||
PromiseQueue.emplace(response);
|
||||
json responsePayload = response->get_future().get();
|
||||
|
||||
delete callbackEntry;
|
||||
delete response;
|
||||
res.set_content(responsePayload.dump(), "text/json");
|
||||
std::optional<uint32_t> SessionModule::GetContext(const httplib::Request& req, httplib::Response& res)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
#include "pch.h"
|
||||
#include "TitleInfoModule.h"
|
||||
#include "payloads/TitleInfoPayload.h"
|
||||
#include <DaybreakModule.h>
|
||||
#include <Utils.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>
|
||||
|
||||
namespace Daybreak::Modules {
|
||||
std::string TitleInfoModule::ApiUri()
|
||||
{
|
||||
return "/titles/info";
|
||||
}
|
||||
|
||||
std::optional<TitleInfoPayload> TitleInfoModule::GetPayload(const uint32_t context) {
|
||||
TitleInfoPayload payload;
|
||||
if (!GW::Map::GetIsMapLoaded()) {
|
||||
return std::optional<TitleInfoPayload>();
|
||||
}
|
||||
|
||||
const auto title = GW::PlayerMgr::GetTitleTrack((GW::Constants::TitleID)context);
|
||||
if (!title) {
|
||||
return std::optional<TitleInfoPayload>();
|
||||
}
|
||||
|
||||
if (title->current_points == 0 &&
|
||||
title->current_title_tier_index == 0 &&
|
||||
title->next_title_tier_index == 0 &&
|
||||
title->max_title_rank == 0) {
|
||||
return std::optional<TitleInfoPayload>();
|
||||
}
|
||||
|
||||
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 = context;
|
||||
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;
|
||||
GW::UI::AsyncDecodeStr(tier->tier_name_enc, &this->name);
|
||||
return payload;
|
||||
}
|
||||
|
||||
bool TitleInfoModule::CanReturn(const httplib::Request& req, httplib::Response& res, const TitleInfoPayload& payload) {
|
||||
return !name.empty();
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string> TitleInfoModule::ReturnPayload(TitleInfoPayload payload) {
|
||||
payload.TitleName = Daybreak::Utils::WStringToString(name);
|
||||
return std::make_tuple(static_cast<json>(payload).dump(), "application/json");
|
||||
}
|
||||
|
||||
std::optional<uint32_t> TitleInfoModule::GetContext(const httplib::Request& req, httplib::Response& res) {
|
||||
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 std::optional<uint32_t>();
|
||||
}
|
||||
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 std::optional<uint32_t>();
|
||||
}
|
||||
|
||||
id = static_cast<uint32_t>(result);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
}
|
||||
@@ -9,20 +9,26 @@
|
||||
#include <GWCA/Context/CharContext.h>
|
||||
#include <GWCA/Context/WorldContext.h>
|
||||
|
||||
namespace Daybreak::Modules::UserModule {
|
||||
std::queue<std::promise<UserPayload>*> PromiseQueue;
|
||||
std::mutex GameThreadMutex;
|
||||
GW::HookEntry GameThreadHook;
|
||||
volatile bool initialized = false;
|
||||
|
||||
UserPayload GetPayload() {
|
||||
namespace Daybreak::Modules {
|
||||
std::optional<UserPayload> UserModule::GetPayload(const uint32_t) {
|
||||
UserPayload userPayload;
|
||||
auto charContext = GW::GetCharContext();
|
||||
auto worldContext = GW::GetWorldContext();
|
||||
std::string emailstr(64, '\0');
|
||||
auto length = std::wcstombs(&emailstr[0], charContext->player_email, 64);
|
||||
emailstr.resize(length);
|
||||
userPayload.Email = emailstr;
|
||||
if (!charContext) {
|
||||
return userPayload;
|
||||
}
|
||||
|
||||
if (!worldContext) {
|
||||
return userPayload;
|
||||
}
|
||||
|
||||
char emailStr[64];
|
||||
int result = WideCharToMultiByte(CP_UTF8, 0, charContext->player_email, -1, emailStr, sizeof(emailStr), NULL, NULL);
|
||||
if (result == 0) {
|
||||
// handle error, use GetLastError() to get more info
|
||||
}
|
||||
|
||||
userPayload.Email = emailStr;
|
||||
userPayload.CurrentKurzickPoints = worldContext->current_kurzick;
|
||||
userPayload.CurrentLuxonPoints = worldContext->current_luxon;
|
||||
userPayload.CurrentImperialPoints = worldContext->current_imperial;
|
||||
@@ -41,40 +47,13 @@ namespace Daybreak::Modules::UserModule {
|
||||
return userPayload;
|
||||
}
|
||||
|
||||
void EnsureInitialized() {
|
||||
GameThreadMutex.lock();
|
||||
if (!initialized) {
|
||||
GW::GameThread::RegisterGameThreadCallback(&GameThreadHook, [&](GW::HookStatus*) {
|
||||
while (!PromiseQueue.empty()) {
|
||||
auto promise = PromiseQueue.front();
|
||||
PromiseQueue.pop();
|
||||
try {
|
||||
auto payload = GetPayload();
|
||||
promise->set_value(payload);
|
||||
}
|
||||
catch (...) {
|
||||
UserPayload payload;
|
||||
promise->set_value(payload);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
GameThreadMutex.unlock();
|
||||
std::string UserModule::ApiUri()
|
||||
{
|
||||
return "/user";
|
||||
}
|
||||
|
||||
void GetUserInfo(const httplib::Request&, httplib::Response& res) {
|
||||
auto callbackEntry = new GW::HookEntry;
|
||||
auto response = new std::promise<UserPayload>;
|
||||
|
||||
EnsureInitialized();
|
||||
PromiseQueue.emplace(response);
|
||||
json responsePayload = response->get_future().get();
|
||||
|
||||
delete callbackEntry;
|
||||
delete response;
|
||||
res.set_content(responsePayload.dump(), "text/json");
|
||||
std::optional<uint32_t> UserModule::GetContext(const httplib::Request& req, httplib::Response& res)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,16 @@
|
||||
#include <cstdint>
|
||||
|
||||
namespace Daybreak::Utils {
|
||||
std::wstring StringToWString(const std::string& str) {
|
||||
if (str.empty()) return std::wstring();
|
||||
|
||||
int size_needed = MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), NULL, 0);
|
||||
std::wstring wstrTo(size_needed, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, &str[0], (int)str.size(), &wstrTo[0], size_needed);
|
||||
|
||||
return wstrTo;
|
||||
}
|
||||
|
||||
std::string WStringToString(const std::wstring& wstr)
|
||||
{
|
||||
if (wstr.empty()) return std::string();
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "pch.h"
|
||||
#include "WhisperModule.h"
|
||||
#include <GWCA/Managers/GameThreadMgr.h>
|
||||
#include <GWCA/Managers/MapMgr.h>
|
||||
#include <GWCA/Managers/ChatMgr.h>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <Utils.h>
|
||||
|
||||
namespace Daybreak::Modules {
|
||||
std::optional<bool> WhisperModule::GetPayload(std::wstring message) {
|
||||
if (!GW::Map::GetIsMapLoaded()) {
|
||||
return std::optional<bool>();
|
||||
}
|
||||
|
||||
GW::Chat::WriteChat(GW::Chat::CHANNEL_WHISPER, message.c_str(), L"Daybreak", false);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string WhisperModule::ApiUri()
|
||||
{
|
||||
return "/whisper";
|
||||
}
|
||||
|
||||
std::optional<std::wstring> WhisperModule::GetContext(const httplib::Request& req, httplib::Response& res)
|
||||
{
|
||||
auto wMessage = Utils::StringToWString(req.body);
|
||||
return wMessage;
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string> WhisperModule::ReturnPayload(bool) {
|
||||
return std::make_tuple("", "text/plain");
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,8 @@
|
||||
#include "EntityNameModule.h"
|
||||
#include "GameStateModule.h"
|
||||
#include "ItemNameModule.h"
|
||||
#include "TitleInfoModule.h"
|
||||
#include "WhisperModule.h"
|
||||
#include <mutex>
|
||||
|
||||
volatile bool initialized;
|
||||
@@ -56,22 +58,40 @@ static DWORD WINAPI StartHttpServer(LPVOID)
|
||||
// This is a new thread so you should only initialize GWCA and setup the hook on the game thread.
|
||||
// When the game thread hook is setup (i.e. SetRenderCallback), you should do the next operations
|
||||
// on the game from within the game thread.
|
||||
const auto titleInfoModule = new Daybreak::Modules::TitleInfoModule();
|
||||
const auto entityNameModule = new Daybreak::Modules::EntityNameModule();
|
||||
const auto gameModule = new Daybreak::Modules::GameModule();
|
||||
const auto gameStateModule = new Daybreak::Modules::GameStateModule();
|
||||
const auto inventoryModule = new Daybreak::Modules::InventoryModule();
|
||||
const auto itemNameModule = new Daybreak::Modules::ItemNameModule();
|
||||
const auto loginModule = new Daybreak::Modules::LoginModule();
|
||||
const auto mainPlayerModule = new Daybreak::Modules::MainPlayerModule();
|
||||
const auto mapModule = new Daybreak::Modules::MapModule();
|
||||
const auto pathingMetadataModule = new Daybreak::Modules::PathingMetadataModule();
|
||||
const auto pathingModule = new Daybreak::Modules::PathingModule();
|
||||
const auto preGameModule = new Daybreak::Modules::PreGameModule();
|
||||
const auto sessionModule = new Daybreak::Modules::SessionModule();
|
||||
const auto userModule = new Daybreak::Modules::UserModule();
|
||||
const auto whisperModule = new Daybreak::Modules::WhisperModule();
|
||||
|
||||
http::server::SetLogger(http::ConsoleLogger);
|
||||
http::server::Get("/alive", http::modules::HandleAlive);
|
||||
http::server::Get("/id", http::modules::HandleProcessId);
|
||||
http::server::Get("/map", Daybreak::Modules::MapModule::GetMapInfo);
|
||||
http::server::Get("/login", Daybreak::Modules::LoginModule::GetLoginInfo);
|
||||
http::server::Get("/pathing/metadata", Daybreak::Modules::PathingMetadataModule::GetPathingMetadata);
|
||||
http::server::Get("/pathing", Daybreak::Modules::PathingModule::GetPathingData);
|
||||
http::server::Get("/pregame", Daybreak::Modules::PreGameModule::GetPreGameInfo);
|
||||
http::server::Get("/user", Daybreak::Modules::UserModule::GetUserInfo);
|
||||
http::server::Get("/game", Daybreak::Modules::GameModule::GetGameInfo);
|
||||
http::server::Get("/inventory", Daybreak::Modules::InventoryModule::GetInventoryInfo);
|
||||
http::server::Get("/game/mainplayer", Daybreak::Modules::MainPlayerModule::GetMainPlayer);
|
||||
http::server::Get("/game/state", Daybreak::Modules::GameStateModule::GetGameStateInfo);
|
||||
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(mapModule->ApiUri(), [&mapModule](const httplib::Request& req, httplib::Response& res) { mapModule->HandleApiCall(req, res); });
|
||||
http::server::Get(loginModule->ApiUri(), [&loginModule](const httplib::Request& req, httplib::Response& res) { loginModule->HandleApiCall(req, res); });
|
||||
http::server::Get(pathingMetadataModule->ApiUri(), [&pathingMetadataModule](const httplib::Request& req, httplib::Response& res) { pathingMetadataModule->HandleApiCall(req, res); });
|
||||
http::server::Get(pathingModule->ApiUri(), [&pathingModule](const httplib::Request& req, httplib::Response& res) { pathingModule->HandleApiCall(req, res); });
|
||||
http::server::Get(preGameModule->ApiUri(), [&preGameModule](const httplib::Request& req, httplib::Response& res) { preGameModule->HandleApiCall(req, res); });
|
||||
http::server::Get(userModule->ApiUri(), [&userModule](const httplib::Request& req, httplib::Response& res) { userModule->HandleApiCall(req, res); });
|
||||
http::server::Get(gameModule->ApiUri(), [&gameModule](const httplib::Request& req, httplib::Response& res) { gameModule->HandleApiCall(req, res); });
|
||||
http::server::Get(inventoryModule->ApiUri(), [&inventoryModule](const httplib::Request& req, httplib::Response& res) { inventoryModule->HandleApiCall(req, res); });
|
||||
http::server::Get(mainPlayerModule->ApiUri(), [&mainPlayerModule](const httplib::Request& req, httplib::Response& res) { mainPlayerModule->HandleApiCall(req, res); });
|
||||
http::server::Get(gameStateModule->ApiUri(), [&gameStateModule](const httplib::Request& req, httplib::Response& res) { gameStateModule->HandleApiCall(req, res); });
|
||||
http::server::Get(sessionModule->ApiUri(), [&sessionModule](const httplib::Request& req, httplib::Response& res) { sessionModule->HandleApiCall(req, res); });
|
||||
http::server::Get(entityNameModule->ApiUri(), [&entityNameModule](const httplib::Request& req, httplib::Response& res) { entityNameModule->HandleApiCall(req, res); });
|
||||
http::server::Get(itemNameModule->ApiUri(), [&itemNameModule](const httplib::Request& req, httplib::Response& res) { itemNameModule->HandleApiCall(req, res); });
|
||||
http::server::Get(titleInfoModule->ApiUri(), [&titleInfoModule](const httplib::Request& req, httplib::Response& res) { titleInfoModule->HandleApiCall(req, res); });
|
||||
http::server::Post(whisperModule->ApiUri(), [&whisperModule](const httplib::Request& req, httplib::Response& res) { whisperModule->HandleApiCall(req, res); });
|
||||
http::server::StartServer();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
#include <payloads/Camera.h>
|
||||
#include <cstdint>
|
||||
#include <json.hpp>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace Daybreak {
|
||||
void to_json(json& j, const Camera& p) {
|
||||
j = json
|
||||
{
|
||||
{"Yaw", p.Yaw},
|
||||
{"Pitch", p.Pitch}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ namespace Daybreak {
|
||||
void to_json(json& j, const GameStatePayload& p) {
|
||||
j = json
|
||||
{
|
||||
{"Camera", p.Camera},
|
||||
{"States", p.States},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ namespace Daybreak {
|
||||
{"EntityState", p.EntityState},
|
||||
{"EntityAllegiance", p.EntityAllegiance},
|
||||
{"Health", p.Health},
|
||||
{"Energy", p.Energy}
|
||||
{"Energy", p.Energy},
|
||||
{"RotationAngle", p.RotationAngle}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,8 @@ namespace Daybreak {
|
||||
{"PosY", p.PosY},
|
||||
{"State", p.State},
|
||||
{"Health", p.Health},
|
||||
{"Energy", p.Energy}
|
||||
{"Energy", p.Energy},
|
||||
{"RotationAngle", p.RotationAngle}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
#include "pch.h"
|
||||
#include <iostream>
|
||||
#include <windows.h>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
|
||||
namespace PipeServer {
|
||||
// Function to handle a single client connection
|
||||
void HandleClient(HANDLE hPipe)
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
char buffer[1024];
|
||||
DWORD bytesRead;
|
||||
|
||||
if (!ReadFile(hPipe, buffer, sizeof(buffer), &bytesRead, NULL) || bytesRead == 0)
|
||||
{
|
||||
break; // Error or client closed the connection
|
||||
}
|
||||
|
||||
// Process data received from the client (e.g., display or manipulate it)
|
||||
std::cout << "Received from client: " << buffer << std::endl;
|
||||
|
||||
// Send a response back to the client (optional)
|
||||
const char* response = "Hello from server!";
|
||||
DWORD bytesWritten;
|
||||
WriteFile(hPipe, response, static_cast<DWORD>(strlen(response) + 1), &bytesWritten, NULL);
|
||||
}
|
||||
|
||||
// Close the pipe handle when the client disconnects
|
||||
CloseHandle(hPipe);
|
||||
}
|
||||
|
||||
void StartServer()
|
||||
{
|
||||
DWORD processId = GetCurrentProcessId();
|
||||
// Create a buffer to store the formatted process ID as a wide string
|
||||
wchar_t processIdStr[12]; // Assuming DWORD can be represented in 10 characters
|
||||
|
||||
// Format the process ID into the buffer as a wide string
|
||||
swprintf_s(processIdStr, L"%u", processId);
|
||||
std::wstring pipeName = L"\\\\.\\pipe\\Daybreak";
|
||||
pipeName += processIdStr;
|
||||
|
||||
HANDLE hPipe = CreateNamedPipeW(
|
||||
pipeName.c_str(), // Use the generated pipe name
|
||||
PIPE_ACCESS_DUPLEX,
|
||||
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
|
||||
1,
|
||||
1024, 1024,
|
||||
1000,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (hPipe == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
std::cerr << "Error: CreateNamedPipe failed with error code " << GetLastError() << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::cout << "Created named pipe ";
|
||||
wprintf(L"%s\n", pipeName.c_str());
|
||||
std::cout << "Waiting for clients to connect..." << std::endl;
|
||||
// Wait for client connections and handle them in separate threads
|
||||
std::vector<std::thread> clientThreads;
|
||||
HANDLE io_event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
OVERLAPPED overlapped;
|
||||
if (io_event == NULL) {
|
||||
std::cout << "Failed to create event" << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
overlapped.hEvent = io_event;
|
||||
if (ConnectNamedPipe(hPipe, &overlapped))
|
||||
{
|
||||
// A client has connected, create a thread to handle it
|
||||
std::cout << "Client connected" << std::endl;
|
||||
clientThreads.emplace_back(HandleClient, hPipe);
|
||||
clientThreads.back().detach();
|
||||
}
|
||||
else {
|
||||
DWORD err = GetLastError();
|
||||
std::cout << "Failed to connect named pipe " << err << std::endl;
|
||||
if (err == ERROR_PIPE_CONNECTED) {
|
||||
std::cout << "Pipe connection was successful. Client connected" << std::endl;
|
||||
clientThreads.emplace_back(HandleClient, hPipe);
|
||||
clientThreads.back().detach();
|
||||
}
|
||||
|
||||
if (err == ERROR_IO_PENDING)
|
||||
{
|
||||
std::cout << "Error IO Pending" << std::endl;
|
||||
}
|
||||
if (err == ERROR_PIPE_LISTENING)
|
||||
{
|
||||
std::cout << "Error Pipe Listening" << std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(hPipe); // Close the server pipe when done
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#include <winver.h>
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_PATCH@,@VERSION_TWEAK@
|
||||
PRODUCTVERSION @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_PATCH@,@VERSION_TWEAK@
|
||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS VS_FF_DEBUG
|
||||
#else
|
||||
FILEFLAGS 0x0L
|
||||
#endif
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
FILETYPE VFT_DLL
|
||||
FILESUBTYPE VFT2_UNKNOWN
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "gwdevhub"
|
||||
VALUE "FileDescription", "Daybreak.GWCA"
|
||||
VALUE "FileVersion", "@VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@.@VERSION_TWEAK@"
|
||||
VALUE "InternalName", "Daybreak.GWCA"
|
||||
VALUE "LegalCopyright", ""
|
||||
VALUE "OriginalFilename", "Daybreak.GWCA.dll"
|
||||
VALUE "ProductName", "Daybreak.GWCA"
|
||||
VALUE "ProductVersion", "@VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@.@VERSION_TWEAK@"
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 0x4B0
|
||||
END
|
||||
END
|
||||
@@ -113,22 +113,6 @@ else
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("Deleting browser caches");
|
||||
try
|
||||
{
|
||||
Directory.Delete("BrowserData", true);
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
}
|
||||
try
|
||||
{
|
||||
Directory.Delete("Daybreak.exe.WebView2", true);
|
||||
}
|
||||
catch(Exception)
|
||||
{
|
||||
}
|
||||
|
||||
Console.WriteLine("Launching application");
|
||||
var process = new Process
|
||||
{
|
||||
|
||||
@@ -9,15 +9,15 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.1.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.1.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="3.3.1" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="3.3.1" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NSubstitute" Version="5.1.0" />
|
||||
<PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.16">
|
||||
<PackageReference Include="NSubstitute.Analyzers.CSharp" Version="1.0.17">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -14,7 +14,7 @@ public abstract class ItemTestsBase<T>
|
||||
protected abstract IEnumerable<T> AllItems { get; }
|
||||
|
||||
[TestMethod]
|
||||
public void InscriptionList_Should_ContainOnlyUniqueItems()
|
||||
public void List_Should_ContainOnlyUniqueItems()
|
||||
{
|
||||
var itemMap = new Dictionary<T, int>();
|
||||
|
||||
@@ -45,7 +45,7 @@ public abstract class ItemTestsBase<T>
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void InscriptionList_Should_ContainAllNpcs()
|
||||
public void List_Should_ContainAllItems()
|
||||
{
|
||||
var itemMap = new Dictionary<T, bool>();
|
||||
var definedItemFields = typeof(T).GetFields(BindingFlags.Static | BindingFlags.Public).Where(f => f.FieldType == typeof(T));
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
using Daybreak.Services.ApplicationArguments;
|
||||
using Daybreak.Tests.Services.Models;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
using NSubstitute;
|
||||
using Slim;
|
||||
|
||||
namespace Daybreak.Tests.Services;
|
||||
|
||||
[TestClass]
|
||||
public class ApplicationArgumentServiceTests
|
||||
{
|
||||
private readonly ServiceManager serviceManager = new();
|
||||
private readonly ApplicationArgumentService applicationArgumentService;
|
||||
|
||||
public ApplicationArgumentServiceTests()
|
||||
{
|
||||
this.applicationArgumentService = new ApplicationArgumentService(this.serviceManager, Substitute.For<ILogger<ApplicationArgumentService>>());
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void HandleArguments_ExpectedArguments_HandlesCorrectly()
|
||||
{
|
||||
this.serviceManager.RegisterSingleton<TestArgumentHandler>();
|
||||
this.serviceManager.RegisterSingleton<TestArgumentHandler2>();
|
||||
|
||||
this.applicationArgumentService.HandleArguments("-r so me arg -c uments he re and there".Split(" "));
|
||||
|
||||
this.serviceManager.GetRequiredService<TestArgumentHandler>().Called.Should().BeTrue();
|
||||
this.serviceManager.GetRequiredService<TestArgumentHandler2>().Called.Should().BeTrue();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void HandleArguments_NotEnoughArguments_DoesNotCallHandlers()
|
||||
{
|
||||
this.serviceManager.RegisterScoped<TestArgumentHandler>();
|
||||
this.serviceManager.RegisterScoped<TestArgumentHandler2>();
|
||||
|
||||
this.applicationArgumentService.HandleArguments("-r so me arg -c uments he re".Split(" "));
|
||||
|
||||
this.serviceManager.GetRequiredService<TestArgumentHandler>().Called.Should().BeTrue();
|
||||
this.serviceManager.GetRequiredService<TestArgumentHandler2>().Called.Should().BeFalse();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void RegisterHandler_RegistersHandler()
|
||||
{
|
||||
this.applicationArgumentService.RegisterArgumentHandler<TestArgumentHandler>();
|
||||
|
||||
var action = this.serviceManager.GetRequiredService<TestArgumentHandler>;
|
||||
|
||||
action.Should().NotThrow();
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,8 @@ namespace Daybreak.Tests.Services;
|
||||
[TestClass]
|
||||
public class BuildTemplateManagerTests
|
||||
{
|
||||
private const string EncodedTemplate = "OwBk0texXNu0Dj/z+TDzBj+TN4AE";
|
||||
private const string EncodedSingleTemplate = "OwBk0texXNu0Dj/z+TDzBj+TN4AE";
|
||||
private const string EncodedTeamTemplate = "OQJUAMxOEMLnw0nDXQGY6aw2B OQNCA8wDP9B8DyzmOUi1veC OggjYxXTIPWbp5krZfxEAAAoD OwUTM4HDn5gc9TJSh6xddmETA";
|
||||
private IBuildTemplateManager buildTemplateManager;
|
||||
|
||||
[TestInitialize]
|
||||
@@ -21,35 +22,258 @@ public class BuildTemplateManagerTests
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestDecode()
|
||||
public void TestSingleDecode()
|
||||
{
|
||||
var build = this.buildTemplateManager.DecodeTemplate(EncodedTemplate);
|
||||
build.Primary.Should().Be(Profession.Assassin);
|
||||
build.Secondary.Should().Be(Profession.None);
|
||||
build.Attributes.Count.Should().Be(4);
|
||||
build.Attributes[1].Attribute.Should().Be(Attribute.DaggerMastery);
|
||||
build.Attributes[1].Points.Should().Be(11);
|
||||
build.Attributes[2].Attribute.Should().Be(Attribute.DeadlyArts);
|
||||
build.Attributes[2].Points.Should().Be(1);
|
||||
build.Attributes[3].Attribute.Should().Be(Attribute.ShadowArts);
|
||||
build.Attributes[3].Points.Should().Be(5);
|
||||
build.Attributes[0].Attribute.Should().Be(Attribute.CriticalStrikes);
|
||||
build.Attributes[0].Points.Should().Be(11);
|
||||
build.Skills.Count.Should().Be(8);
|
||||
build.Skills[0].Should().Be(Skill.UnsuspectingStrike);
|
||||
build.Skills[1].Should().Be(Skill.WildStrike);
|
||||
build.Skills[2].Should().Be(Skill.CriticalStrike);
|
||||
build.Skills[3].Should().Be(Skill.MoebiusStrike);
|
||||
build.Skills[4].Should().Be(Skill.DeathBlossom);
|
||||
build.Skills[5].Should().Be(Skill.CriticalEye);
|
||||
build.Skills[6].Should().Be(Skill.CriticalAgility);
|
||||
build.Skills[7].Should().Be(Skill.CriticalDefenses);
|
||||
var build = this.buildTemplateManager.DecodeTemplate(EncodedSingleTemplate);
|
||||
var singleBuildEntry = build.As<SingleBuildEntry>();
|
||||
singleBuildEntry.Should().NotBeNull();
|
||||
singleBuildEntry.Primary.Should().Be(Profession.Assassin);
|
||||
singleBuildEntry.Secondary.Should().Be(Profession.None);
|
||||
singleBuildEntry.Attributes.Count.Should().Be(4);
|
||||
singleBuildEntry.Attributes[1].Attribute.Should().Be(Attribute.DaggerMastery);
|
||||
singleBuildEntry.Attributes[1].Points.Should().Be(11);
|
||||
singleBuildEntry.Attributes[2].Attribute.Should().Be(Attribute.DeadlyArts);
|
||||
singleBuildEntry.Attributes[2].Points.Should().Be(1);
|
||||
singleBuildEntry.Attributes[3].Attribute.Should().Be(Attribute.ShadowArts);
|
||||
singleBuildEntry.Attributes[3].Points.Should().Be(5);
|
||||
singleBuildEntry.Attributes[0].Attribute.Should().Be(Attribute.CriticalStrikes);
|
||||
singleBuildEntry.Attributes[0].Points.Should().Be(11);
|
||||
singleBuildEntry.Skills.Count.Should().Be(8);
|
||||
singleBuildEntry.Skills[0].Should().Be(Skill.UnsuspectingStrike);
|
||||
singleBuildEntry.Skills[1].Should().Be(Skill.WildStrike);
|
||||
singleBuildEntry.Skills[2].Should().Be(Skill.CriticalStrike);
|
||||
singleBuildEntry.Skills[3].Should().Be(Skill.MoebiusStrike);
|
||||
singleBuildEntry.Skills[4].Should().Be(Skill.DeathBlossom);
|
||||
singleBuildEntry.Skills[5].Should().Be(Skill.CriticalEye);
|
||||
singleBuildEntry.Skills[6].Should().Be(Skill.CriticalAgility);
|
||||
singleBuildEntry.Skills[7].Should().Be(Skill.CriticalDefenses);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestEncode()
|
||||
public void TestTeamDecode()
|
||||
{
|
||||
var build = new Build()
|
||||
var build = this.buildTemplateManager.DecodeTemplate(EncodedTeamTemplate);
|
||||
var teamBuildEntry = build.As<TeamBuildEntry>();
|
||||
teamBuildEntry.Should().NotBeNull();
|
||||
teamBuildEntry.Builds.Should().HaveCount(4);
|
||||
|
||||
var firstBuild = teamBuildEntry.Builds[0];
|
||||
firstBuild.Primary.Should().Be(Profession.Mesmer);
|
||||
firstBuild.Secondary.Should().Be(Profession.Ranger);
|
||||
firstBuild.Attributes.Should().BeEquivalentTo(
|
||||
[
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.FastCasting,
|
||||
Points = 6
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.DominationMagic,
|
||||
Points = 11
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.IllusionMagic,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.InspirationMagic,
|
||||
Points = 2
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.BeastMastery,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.Marksmanship,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.WildernessSurvival,
|
||||
Points = 12
|
||||
},
|
||||
]);
|
||||
firstBuild.Skills.Should().BeEquivalentTo(
|
||||
[
|
||||
Skill.EnergySurge,
|
||||
Skill.Mistrust,
|
||||
Skill.CryofFrustration,
|
||||
Skill.PowerSpike,
|
||||
Skill.PowerDrain,
|
||||
Skill.UnnaturalSignet,
|
||||
Skill.Empathy,
|
||||
Skill.QuickeningZephyr
|
||||
]);
|
||||
|
||||
var secondBuild = teamBuildEntry.Builds[1];
|
||||
secondBuild.Primary.Should().Be(Profession.Mesmer);
|
||||
secondBuild.Secondary.Should().Be(Profession.Monk);
|
||||
secondBuild.Attributes.Should().BeEquivalentTo(
|
||||
[
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.FastCasting,
|
||||
Points = 12
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.DominationMagic,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.IllusionMagic,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.InspirationMagic,
|
||||
Points = 12
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.HealingPrayers,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.SmitingPrayers,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.ProtectionPrayers,
|
||||
Points = 0
|
||||
},
|
||||
]);
|
||||
secondBuild.Skills.Should().BeEquivalentTo(
|
||||
[
|
||||
Skill.SymbolicCelerity,
|
||||
Skill.MantraofInscriptions,
|
||||
Skill.KeystoneSignet,
|
||||
Skill.SignetofClumsiness,
|
||||
Skill.UnnaturalSignet,
|
||||
Skill.BaneSignet,
|
||||
Skill.CastigationSignet,
|
||||
Skill.SignetofRage
|
||||
]);
|
||||
|
||||
var thirdBuild = teamBuildEntry.Builds[2];
|
||||
thirdBuild.Primary.Should().Be(Profession.Ranger);
|
||||
thirdBuild.Secondary.Should().Be(Profession.Ritualist);
|
||||
thirdBuild.Attributes.Should().BeEquivalentTo(
|
||||
[
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.Expertise,
|
||||
Points = 3
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.BeastMastery,
|
||||
Points = 12
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.Marksmanship,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.WildernessSurvival,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.ChannelingMagic,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.Communing,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.RestorationMagic,
|
||||
Points = 12
|
||||
},
|
||||
]);
|
||||
thirdBuild.Skills.Should().BeEquivalentTo(
|
||||
[
|
||||
Skill.XinraesWeapon,
|
||||
Skill.MendBodyandSoul,
|
||||
Skill.SpiritLight,
|
||||
Skill.GhostmirrorLight,
|
||||
Skill.FleshofMyFlesh,
|
||||
Skill.ResurrectionSignet,
|
||||
Skill.NoSkill,
|
||||
Skill.EdgeofExtinction
|
||||
]);
|
||||
|
||||
var fourthBuild = teamBuildEntry.Builds[3];
|
||||
fourthBuild.Primary.Should().Be(Profession.Monk);
|
||||
fourthBuild.Secondary.Should().Be(Profession.Mesmer);
|
||||
fourthBuild.Attributes.Should().BeEquivalentTo(
|
||||
[
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.DivineFavor,
|
||||
Points = 3
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.HealingPrayers,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.SmitingPrayers,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.ProtectionPrayers,
|
||||
Points = 12
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.DominationMagic,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.IllusionMagic,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.InspirationMagic,
|
||||
Points = 12
|
||||
},
|
||||
]);
|
||||
fourthBuild.Skills.Should().BeEquivalentTo(
|
||||
[
|
||||
Skill.ProtectiveBond,
|
||||
Skill.PurifyingVeil,
|
||||
Skill.BlessedSignet,
|
||||
Skill.MantraofRecall,
|
||||
Skill.ProtectiveSpirit,
|
||||
Skill.ShieldofAbsorption,
|
||||
Skill.ReversalofFortune,
|
||||
Skill.Resurrect
|
||||
]);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestSingleEncode()
|
||||
{
|
||||
var build = new SingleBuildEntry()
|
||||
{
|
||||
Primary = Profession.Assassin,
|
||||
Secondary = Profession.None,
|
||||
@@ -90,6 +314,227 @@ public class BuildTemplateManagerTests
|
||||
};
|
||||
|
||||
var encoded = this.buildTemplateManager.EncodeTemplate(build);
|
||||
encoded.Should().Be(EncodedTemplate);
|
||||
encoded.Should().Be(EncodedSingleTemplate);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TestTeamEncode()
|
||||
{
|
||||
var build = new TeamBuildEntry
|
||||
{
|
||||
Builds = [
|
||||
new SingleBuildEntry
|
||||
{
|
||||
Primary = Profession.Mesmer,
|
||||
Secondary = Profession.Ranger,
|
||||
Attributes = [
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.FastCasting,
|
||||
Points = 6
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.DominationMagic,
|
||||
Points = 11
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.IllusionMagic,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.InspirationMagic,
|
||||
Points = 2
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.BeastMastery,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.Marksmanship,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.WildernessSurvival,
|
||||
Points = 12
|
||||
},
|
||||
],
|
||||
Skills = [
|
||||
Skill.EnergySurge,
|
||||
Skill.Mistrust,
|
||||
Skill.CryofFrustration,
|
||||
Skill.PowerSpike,
|
||||
Skill.PowerDrain,
|
||||
Skill.UnnaturalSignet,
|
||||
Skill.Empathy,
|
||||
Skill.QuickeningZephyr
|
||||
]
|
||||
},
|
||||
new SingleBuildEntry
|
||||
{
|
||||
Primary = Profession.Mesmer,
|
||||
Secondary = Profession.Monk,
|
||||
Attributes = [
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.FastCasting,
|
||||
Points = 12
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.DominationMagic,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.IllusionMagic,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.InspirationMagic,
|
||||
Points = 12
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.HealingPrayers,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.SmitingPrayers,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.ProtectionPrayers,
|
||||
Points = 0
|
||||
},
|
||||
],
|
||||
Skills = [
|
||||
Skill.SymbolicCelerity,
|
||||
Skill.MantraofInscriptions,
|
||||
Skill.KeystoneSignet,
|
||||
Skill.SignetofClumsiness,
|
||||
Skill.UnnaturalSignet,
|
||||
Skill.BaneSignet,
|
||||
Skill.CastigationSignet,
|
||||
Skill.SignetofRage
|
||||
]
|
||||
},
|
||||
new SingleBuildEntry
|
||||
{
|
||||
Primary = Profession.Ranger,
|
||||
Secondary = Profession.Ritualist,
|
||||
Attributes = [
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.Expertise,
|
||||
Points = 3
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.BeastMastery,
|
||||
Points = 12
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.Marksmanship,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.WildernessSurvival,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.ChannelingMagic,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.Communing,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.RestorationMagic,
|
||||
Points = 12
|
||||
},
|
||||
],
|
||||
Skills = [
|
||||
Skill.XinraesWeapon,
|
||||
Skill.MendBodyandSoul,
|
||||
Skill.SpiritLight,
|
||||
Skill.GhostmirrorLight,
|
||||
Skill.FleshofMyFlesh,
|
||||
Skill.ResurrectionSignet,
|
||||
Skill.NoSkill,
|
||||
Skill.EdgeofExtinction
|
||||
]
|
||||
},
|
||||
new SingleBuildEntry
|
||||
{
|
||||
Primary = Profession.Monk,
|
||||
Secondary = Profession.Mesmer,
|
||||
Attributes = [
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.DivineFavor,
|
||||
Points = 3
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.HealingPrayers,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.SmitingPrayers,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.ProtectionPrayers,
|
||||
Points = 12
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.DominationMagic,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.IllusionMagic,
|
||||
Points = 0
|
||||
},
|
||||
new AttributeEntry
|
||||
{
|
||||
Attribute = Attribute.InspirationMagic,
|
||||
Points = 12
|
||||
},
|
||||
],
|
||||
Skills = [
|
||||
Skill.ProtectiveBond,
|
||||
Skill.PurifyingVeil,
|
||||
Skill.BlessedSignet,
|
||||
Skill.MantraofRecall,
|
||||
Skill.ProtectiveSpirit,
|
||||
Skill.ShieldofAbsorption,
|
||||
Skill.ReversalofFortune,
|
||||
Skill.Resurrect
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var encoded = this.buildTemplateManager.EncodeTemplate(build);
|
||||
encoded.Should().Be(EncodedTeamTemplate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ public class JsonLoggerProviderTests
|
||||
[TestMethod]
|
||||
public void LoggerLogsAndReaderReads()
|
||||
{
|
||||
this.logsManager.DeleteLogs();
|
||||
var logger = this.loggerProvider.CreateLogger("SomeCategory");
|
||||
logger.LogInformation("Logging some stuff");
|
||||
logger.LogError("Logging some error");
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
using Daybreak.Services.ApplicationArguments.ArgumentHandling;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace Daybreak.Tests.Services.Models;
|
||||
public sealed class TestArgumentHandler : IArgumentHandler
|
||||
{
|
||||
public string Identifier => "-r";
|
||||
public int ExpectedArgumentCount => 3;
|
||||
public bool Called { get; private set; }
|
||||
|
||||
public void HandleArguments(string[] args)
|
||||
{
|
||||
args.Should().BeEquivalentTo(["so", "me", "arg"]);
|
||||
this.Called = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using Daybreak.Services.ApplicationArguments.ArgumentHandling;
|
||||
using FluentAssertions;
|
||||
|
||||
namespace Daybreak.Tests.Services.Models;
|
||||
public sealed class TestArgumentHandler2 : IArgumentHandler
|
||||
{
|
||||
public string Identifier => "-c";
|
||||
public int ExpectedArgumentCount => 5;
|
||||
public bool Called { get; private set; }
|
||||
|
||||
public void HandleArguments(string[] args)
|
||||
{
|
||||
args.Should().BeEquivalentTo(["uments", "he", "re", "and", "there"]);
|
||||
this.Called = true;
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -70,8 +70,8 @@ Global
|
||||
{C911C1C2-6566-419C-89C7-F802EB3FE709}.Release|x86.Build.0 = Release|x86
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x64.Build.0 = Debug|x64
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x64.ActiveCfg = Debug|x86
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x64.Build.0 = Debug|x86
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Debug|x86.Build.0 = Debug|x86
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -82,8 +82,8 @@ Global
|
||||
{4E2BB805-135D-4F02-8C53-3D8B6876D323}.Release|x86.Build.0 = Release|x86
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x64.Build.0 = Debug|x64
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x86.ActiveCfg = Debug|x64
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Debug|x86.Build.0 = Debug|x64
|
||||
{CA60F9AC-A496-4DB6-970E-ECE73B051C05}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
@@ -20,12 +20,12 @@ public sealed class RowAutoMargin : Behavior<Grid>
|
||||
}
|
||||
|
||||
// TODO #367: Super hacky code below. To be reworked
|
||||
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
|
||||
public void RecalculateRows()
|
||||
{
|
||||
/*
|
||||
* Tag each child with the desired row. Skip children already tagged
|
||||
*/
|
||||
foreach(var child in this.AssociatedObject.Children.OfType<FrameworkElement>())
|
||||
foreach (var child in this.AssociatedObject.Children.OfType<FrameworkElement>())
|
||||
{
|
||||
if (child.Tag is int desiredRow)
|
||||
{
|
||||
@@ -72,9 +72,12 @@ public sealed class RowAutoMargin : Behavior<Grid>
|
||||
var addedRows = 0;
|
||||
for (var i = 0; i < this.AssociatedObject.RowDefinitions.Count - 1; i++)
|
||||
{
|
||||
if (this.AssociatedObject.RowDefinitions[i + addedRows].Height.Value > 0)
|
||||
var rowDefinition = this.AssociatedObject.RowDefinitions[i];
|
||||
if (rowDefinition.Height.Value > 0 &&
|
||||
(rowDefinition.Tag is not string tagString ||
|
||||
tagString is not GeneratedRowsTag))
|
||||
{
|
||||
this.AssociatedObject.RowDefinitions.Insert(i + 1,
|
||||
this.AssociatedObject.RowDefinitions.Insert(i + 1,
|
||||
new RowDefinition
|
||||
{
|
||||
Height = new GridLength(this.Margin, GridUnitType.Pixel),
|
||||
@@ -89,9 +92,13 @@ public sealed class RowAutoMargin : Behavior<Grid>
|
||||
}
|
||||
}
|
||||
|
||||
i++;
|
||||
addedRows++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
this.RecalculateRows();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using System.Collections.Generic;
|
||||
namespace Daybreak.Configuration.Options;
|
||||
|
||||
[OptionsIgnore]
|
||||
[OptionsSynchronizationIgnore]
|
||||
[OptionsName(Name = "Credentials")]
|
||||
internal sealed class CredentialManagerOptions
|
||||
{
|
||||
[JsonProperty(nameof(ProtectedLoginCredentials))]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Daybreak.Attributes;
|
||||
using Daybreak.Configuration.FocusView;
|
||||
using Daybreak.Models.Browser;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Daybreak.Configuration.Options;
|
||||
@@ -49,7 +50,19 @@ public sealed class FocusViewOptions
|
||||
[OptionName(Name = "Energy Display Mode", Description = "Sets how should the energy display show the information")]
|
||||
public PointsDisplay EnergyDisplay { get; set; }
|
||||
|
||||
[JsonProperty(nameof(BrowserUrl))]
|
||||
[JsonProperty(nameof(MinimapRotationEnabled))]
|
||||
[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 string? BrowserUrl { get; set; } = "https://wiki.guildwars.com/wiki/Main_Page";
|
||||
public BrowserHistory BrowserHistory { get; set; } = new();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@ namespace Daybreak.Configuration.Options;
|
||||
[OptionsName(Name = "Launcher")]
|
||||
public sealed class LauncherOptions
|
||||
{
|
||||
[JsonProperty(nameof(DynamicBackgrounds))]
|
||||
[OptionName(Name = "Dyamic Backgrounds", Description = "If enabled, Daybreak will periodically change the background image. Otherwise, Daybreak will show a plain background")]
|
||||
[OptionSynchronizationIgnore]
|
||||
public bool DynamicBackgrounds { get; set; } = true;
|
||||
|
||||
[JsonProperty(nameof(SetGuildwarsWindowSizeOnLaunch))]
|
||||
[OptionName(Name = "Set GuildWars Window Size On Launch", Description = "Sets the GuildWars window size and position on launch")]
|
||||
[OptionSynchronizationIgnore]
|
||||
@@ -56,4 +61,8 @@ public sealed class LauncherOptions
|
||||
[JsonProperty(nameof(BetaUpdate))]
|
||||
[OptionName(Name = "Beta Update", Description = "If true, the launcher will use the new update procedure")]
|
||||
public bool BetaUpdate { get; set; } = true;
|
||||
|
||||
[JsonProperty(nameof(AutoBackupSettings))]
|
||||
[OptionName(Name = "Auto Backup Settings", Description = "If true, the launcher will attempt to backup settings periodically")]
|
||||
public bool AutoBackupSettings { get; set; } = false;
|
||||
}
|
||||
|
||||
@@ -10,4 +10,8 @@ internal sealed class MemoryReaderOptions
|
||||
[OptionRange<double>(MinValue = 0, MaxValue = 1000)]
|
||||
[OptionName(Name = "Memory Reader Frequency", Description = "Measured in ms. Sets how often should the launcher polls information from the game. Actual frequency is capped by the memory reading speed")]
|
||||
public double MemoryReaderFrequency { get; set; } = 0;
|
||||
|
||||
[JsonProperty(nameof(AllowWhispers))]
|
||||
[OptionName(Name = "Allow Whispers", Description = "If enabled, Daybreak will be allowed to send whispers to the player. Mainly used in notifications")]
|
||||
public bool AllowWhispers { get; set; } = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using Daybreak.Attributes;
|
||||
|
||||
namespace Daybreak.Configuration.Options;
|
||||
|
||||
[OptionsIgnore]
|
||||
[OptionsName(Name = "Minimap Window")]
|
||||
[OptionsSynchronizationIgnore]
|
||||
public sealed class MinimapWindowOptions
|
||||
{
|
||||
public bool Pinned { get; set; }
|
||||
public double X { get; set; }
|
||||
public double Y { get; set; }
|
||||
public double Width { get; set; }
|
||||
public double Height { get; set; }
|
||||
public double DpiX { get; set; }
|
||||
public double DpiY { get; set; }
|
||||
}
|
||||
@@ -16,5 +16,5 @@ internal sealed class PriceHistoryOptions : ILiteCollectionOptions<TraderQuoteDT
|
||||
|
||||
public TimeSpan UpdateInterval { get; set; } = TimeSpan.FromHours(1);
|
||||
|
||||
public Dictionary<string, DateTime> ItemHistoryMetadata { get; set; } = [];
|
||||
public Dictionary<string, DateTime> PriceHistoryMetadata { get; set; } = [];
|
||||
}
|
||||
|
||||
@@ -82,6 +82,10 @@ using Daybreak.Services.DirectSong;
|
||||
using Daybreak.Views.Onboarding.DirectSong;
|
||||
using Daybreak.Services.SevenZip;
|
||||
using Daybreak.Services.ReShade.Notifications;
|
||||
using Daybreak.Services.UBlockOrigin;
|
||||
using Daybreak.Services.Browser;
|
||||
using Daybreak.Services.ApplicationArguments;
|
||||
using Daybreak.Services.ApplicationArguments.ArgumentHandling;
|
||||
|
||||
namespace Daybreak.Configuration;
|
||||
|
||||
@@ -155,6 +159,10 @@ public class ProjectConfiguration : PluginConfigurationBase
|
||||
.RegisterHttpClient<UModService>()
|
||||
.WithMessageHandler(this.SetupLoggingAndMetrics<UModService>)
|
||||
.WithDefaultRequestHeadersSetup(this.SetupDaybreakUserAgent)
|
||||
.Build()
|
||||
.RegisterHttpClient<UBlockOriginService>()
|
||||
.WithMessageHandler(this.SetupLoggingAndMetrics<UBlockOriginService>)
|
||||
.WithDefaultRequestHeadersSetup(this.SetupDaybreakUserAgent)
|
||||
.Build();
|
||||
}
|
||||
|
||||
@@ -190,19 +198,19 @@ public class ProjectConfiguration : PluginConfigurationBase
|
||||
services.AddSingleton<IPostUpdateActionProducer>(sp => sp.GetRequiredService<PostUpdateActionManager>());
|
||||
services.AddSingleton<IPostUpdateActionProvider>(sp => sp.GetRequiredService<PostUpdateActionManager>());
|
||||
services.AddSingleton<IMenuService, MenuService>();
|
||||
services.AddSingleton<IMenuServiceInitializer, MenuService>(sp => sp.GetRequiredService<IMenuService>().As<MenuService>()!);
|
||||
services.AddSingleton<IMenuServiceInitializer, MenuService>(sp => sp.GetRequiredService<IMenuService>().Cast<MenuService>());
|
||||
services.AddSingleton<ILiteDatabase, LiteDatabase>(sp => new LiteDatabase("Daybreak.db"));
|
||||
services.AddSingleton<IMutexHandler, MutexHandler>();
|
||||
services.AddSingleton<IShortcutManager, ShortcutManager>();
|
||||
services.AddSingleton<IMetricsService, MetricsService>();
|
||||
services.AddSingleton<IStartupActionProducer, StartupActionManager>();
|
||||
services.AddSingleton<IOptionsProducer, OptionsManager>(sp => sp.GetRequiredService<IOptionsManager>().As<OptionsManager>()!);
|
||||
services.AddSingleton<IOptionsUpdateHook, OptionsManager>(sp => sp.GetRequiredService<IOptionsManager>().As<OptionsManager>()!);
|
||||
services.AddSingleton<IOptionsProvider, OptionsManager>(sp => sp.GetRequiredService<IOptionsManager>().As<OptionsManager>()!);
|
||||
services.AddSingleton<IOptionsProducer, OptionsManager>(sp => sp.GetRequiredService<IOptionsManager>().Cast<OptionsManager>());
|
||||
services.AddSingleton<IOptionsUpdateHook, OptionsManager>(sp => sp.GetRequiredService<IOptionsManager>().Cast<OptionsManager>());
|
||||
services.AddSingleton<IOptionsProvider, OptionsManager>(sp => sp.GetRequiredService<IOptionsManager>().Cast<OptionsManager>());
|
||||
services.AddSingleton<IThemeManager, ThemeManager>();
|
||||
services.AddSingleton<INotificationService, NotificationService>();
|
||||
services.AddSingleton<INotificationProducer, NotificationService>(sp => sp.GetRequiredService<INotificationService>().As<NotificationService>()!);
|
||||
services.AddSingleton<INotificationHandlerProducer, NotificationService>(sp => sp.GetRequiredService<INotificationService>().As<NotificationService>()!);
|
||||
services.AddSingleton<INotificationProducer, NotificationService>(sp => sp.GetRequiredService<INotificationService>().Cast<NotificationService>());
|
||||
services.AddSingleton<INotificationHandlerProducer, NotificationService>(sp => sp.GetRequiredService<INotificationService>().Cast<NotificationService>());
|
||||
services.AddSingleton<ILiveChartInitializer, LiveChartInitializer>();
|
||||
services.AddSingleton<IImageCache, ImageCache>();
|
||||
services.AddSingleton<ISoundService, SoundService>();
|
||||
@@ -220,6 +228,8 @@ public class ProjectConfiguration : PluginConfigurationBase
|
||||
services.AddSingleton<ISevenZipExtractor, SevenZipExtractor>();
|
||||
services.AddSingleton<IGraphClient, GraphClient>();
|
||||
services.AddSingleton<IOptionsSynchronizationService, OptionsSynchronizationService>();
|
||||
services.AddScoped<IBrowserExtensionsManager, BrowserExtensionsManager>();
|
||||
services.AddScoped<IBrowserExtensionsProducer, BrowserExtensionsManager>(sp => sp.GetRequiredService<IBrowserExtensionsManager>().Cast<BrowserExtensionsManager>());
|
||||
services.AddScoped<ICredentialManager, CredentialManager>();
|
||||
services.AddScoped<IApplicationLauncher, ApplicationLauncher>();
|
||||
services.AddScoped<IScreenshotProvider, ScreenshotProvider>();
|
||||
@@ -253,6 +263,10 @@ public class ProjectConfiguration : PluginConfigurationBase
|
||||
services.AddScoped<IToolboxClient, ToolboxClient>();
|
||||
services.AddScoped<IProcessInjector, ProcessInjector>();
|
||||
services.AddScoped<ILaunchConfigurationService, LaunchConfigurationService>();
|
||||
services.AddScoped<IBrowserHistoryManager, BrowserHistoryManager>();
|
||||
services.AddScoped<IEventService, EventService>();
|
||||
services.AddScoped<IApplicationArgumentService, ApplicationArgumentService>();
|
||||
services.AddScoped<IArgumentHandlerProducer, IApplicationArgumentService>(sp => sp.GetRequiredService<IApplicationArgumentService>());
|
||||
}
|
||||
|
||||
public override void RegisterViews(IViewProducer viewProducer)
|
||||
@@ -264,7 +278,7 @@ public class ProjectConfiguration : PluginConfigurationBase
|
||||
viewProducer.RegisterView<UpdateView>();
|
||||
viewProducer.RegisterView<AccountsView>();
|
||||
viewProducer.RegisterView<ExecutablesView>();
|
||||
viewProducer.RegisterView<BuildTemplateView>();
|
||||
viewProducer.RegisterView<SingleBuildTemplateView>();
|
||||
viewProducer.RegisterView<BuildsListView>();
|
||||
viewProducer.RegisterView<RequestElevationView>();
|
||||
viewProducer.RegisterView<RequestDelevationView>();
|
||||
@@ -321,6 +335,9 @@ public class ProjectConfiguration : PluginConfigurationBase
|
||||
viewProducer.RegisterView<DirectSongOnboardingEntryView>();
|
||||
viewProducer.RegisterView<DirectSongSwitchView>();
|
||||
viewProducer.RegisterView<SettingsSynchronizationView>();
|
||||
viewProducer.RegisterView<TeamBuildTemplateView>();
|
||||
viewProducer.RegisterView<EventCalendarView>();
|
||||
viewProducer.RegisterView<UpdateConfirmationView>();
|
||||
}
|
||||
|
||||
public override void RegisterStartupActions(IStartupActionProducer startupActionProducer)
|
||||
@@ -330,6 +347,9 @@ public class ProjectConfiguration : PluginConfigurationBase
|
||||
startupActionProducer.RegisterAction<RenameInstallerAction>();
|
||||
startupActionProducer.RegisterAction<FixSymbolicLinkStartupAction>();
|
||||
startupActionProducer.RegisterAction<UpdateUModAction>();
|
||||
startupActionProducer.RegisterAction<FixPriceHistoryEntries>();
|
||||
startupActionProducer.RegisterAction<CredentialsOptionsMigrator>();
|
||||
startupActionProducer.RegisterAction<BrowserHistorySizeEnforcer>();
|
||||
}
|
||||
|
||||
public override void RegisterPostUpdateActions(IPostUpdateActionProducer postUpdateActionProducer)
|
||||
@@ -423,6 +443,7 @@ public class ProjectConfiguration : PluginConfigurationBase
|
||||
optionsProducer.RegisterOptions<CredentialManagerOptions>();
|
||||
optionsProducer.RegisterOptions<LaunchConfigurationServiceOptions>();
|
||||
optionsProducer.RegisterOptions<DirectSongOptions>();
|
||||
optionsProducer.RegisterOptions<MinimapWindowOptions>();
|
||||
}
|
||||
|
||||
public override void RegisterNotificationHandlers(INotificationHandlerProducer notificationHandlerProducer)
|
||||
@@ -433,6 +454,7 @@ public class ProjectConfiguration : PluginConfigurationBase
|
||||
notificationHandlerProducer.RegisterNotificationHandler<FixSymbolicLinkNotificationHandler>();
|
||||
notificationHandlerProducer.RegisterNotificationHandler<UpdateNotificationHandler>();
|
||||
notificationHandlerProducer.RegisterNotificationHandler<ReShadeConfigChangedHandler>();
|
||||
notificationHandlerProducer.RegisterNotificationHandler<NavigateToCalendarViewHandler>();
|
||||
}
|
||||
|
||||
public override void RegisterMods(IModsManager modsManager)
|
||||
@@ -446,6 +468,18 @@ public class ProjectConfiguration : PluginConfigurationBase
|
||||
modsManager.RegisterMod<IDirectSongService, DirectSongService>(singleton: true);
|
||||
}
|
||||
|
||||
public override void RegisterBrowserExtensions(IBrowserExtensionsProducer browserExtensionsProducer)
|
||||
{
|
||||
browserExtensionsProducer.ThrowIfNull();
|
||||
browserExtensionsProducer.RegisterExtension<UBlockOriginService>();
|
||||
}
|
||||
|
||||
public override void RegisterLaunchArgumentHandlers(IArgumentHandlerProducer argumentHandlerProducer)
|
||||
{
|
||||
argumentHandlerProducer.ThrowIfNull();
|
||||
argumentHandlerProducer.RegisterArgumentHandler<AutoLaunchArgumentHandler>();
|
||||
}
|
||||
|
||||
private void RegisterLiteCollections(IServiceCollection services)
|
||||
{
|
||||
this.RegisterLiteCollection<Models.Log, LoggingOptions>(services);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Controls.Buttons"
|
||||
xmlns:glyphs="clr-namespace:Daybreak.Controls.Glyphs"
|
||||
mc:Ignorable="d"
|
||||
x:Name="_this"
|
||||
Cursor="Hand"
|
||||
@@ -13,13 +14,13 @@
|
||||
Highlight="{StaticResource MahApps.Brushes.Accent}"
|
||||
Clicked="HighlightButton_Clicked">
|
||||
<local:CircularButton.Content>
|
||||
<Viewbox Stretch="Fill" Width="60" Height="60">
|
||||
<Grid Width="25" Height="25">
|
||||
<Path Data="m13,20a1,1 0 0 1 -1,-1l0,-12a1,1 0 0 1 2,0l0,12a1,1 0 0 1 -1,1z"
|
||||
Fill="{StaticResource MahApps.Brushes.ThemeForeground}"></Path>
|
||||
<Path Data="m19,14l-12,0a1,1 0 0 1 0,-2l12,0a1,1 0 0 1 0,2z"
|
||||
Fill="{StaticResource MahApps.Brushes.ThemeForeground}"></Path>
|
||||
</Grid>
|
||||
<Viewbox>
|
||||
<glyphs:PlusGlyph Foreground="{StaticResource MahApps.Brushes.ThemeForeground}"
|
||||
Height="50"
|
||||
Width="50"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left"
|
||||
Margin="0, 0, 14, 14"/>
|
||||
</Viewbox>
|
||||
</local:CircularButton.Content>
|
||||
</local:CircularButton>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Controls.Buttons"
|
||||
xmlns:glyphs="clr-namespace:Daybreak.Controls.Glyphs"
|
||||
mc:Ignorable="d"
|
||||
x:Name="_this"
|
||||
Cursor="Hand"
|
||||
@@ -14,10 +15,7 @@
|
||||
Clicked="HighlightButton_Clicked">
|
||||
<local:CircularButton.Content>
|
||||
<Viewbox Stretch="Fill" Width="60" Height="60">
|
||||
<Grid Width="30" Height="30" Margin="-2, 0, 0 ,0">
|
||||
<Path Fill="{DynamicResource MahApps.Brushes.ThemeForeground}" VerticalAlignment="Top" HorizontalAlignment="Left"
|
||||
Data="m18.87,9.41l-5.58,5.59l5.58,5.59a1,1 0 0 1 0,1.41l0,0a1,1 0 0 1 -1.41,0l-6.36,-6.36a0.91,0.91 0 0 1 0,-1.28l6.36,-6.36a1,1 0 0 1 1.41,0l0,0a1,1 0 0 1 0,1.41z"></Path>
|
||||
</Grid>
|
||||
<glyphs:ArrowGlyph />
|
||||
</Viewbox>
|
||||
</local:CircularButton.Content>
|
||||
</local:CircularButton>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
mc:Ignorable="d"
|
||||
Loaded="UserControl_Loaded"
|
||||
Unloaded="UserControl_Unloaded"
|
||||
PreviewMouseDown="Browser_PreviewMouseDown"
|
||||
x:Name="_this"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
@@ -51,11 +52,11 @@
|
||||
<WrapPanel>
|
||||
<buttons:BackButton Height="30" Width="30"
|
||||
Clicked="BackButton_Clicked" Margin="5" Cursor="Hand"
|
||||
IsEnabled="{Binding ElementName=WebBrowser, Path=CanGoBack, Mode=OneWay}"
|
||||
IsEnabled="{Binding ElementName=_this, Path=BrowserHistoryManager.CanGoBack, Mode=OneWay}"
|
||||
ToolTip="Go back"/>
|
||||
<buttons:BackButton Height="30" Width="30"
|
||||
Clicked="ForwardButton_Clicked" Margin="5" Cursor="Hand"
|
||||
IsEnabled="{Binding ElementName=WebBrowser, Path=CanGoForward, Mode=OneWay}"
|
||||
IsEnabled="{Binding ElementName=_this, Path=BrowserHistoryManager.CanGoForward, Mode=OneWay}"
|
||||
ToolTip="Go forward">
|
||||
<buttons:BackButton.RenderTransform>
|
||||
<RotateTransform Angle="180" CenterX="15" CenterY="15"></RotateTransform>
|
||||
@@ -94,6 +95,7 @@
|
||||
<buttons:MaximizeButton Height="30" Width="30" Margin="5"
|
||||
Foreground="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
IsEnabled="{Binding ElementName=_this, Path=BrowserSupported, Mode=OneWay}"
|
||||
Visibility="{Binding ElementName=_this, Path=MaximizeButtonVisible, Mode=OneWay, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
Clicked="MaximizeButton_Clicked"
|
||||
ToolTip="Maximize"/>
|
||||
</StackPanel>
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
using Daybreak.Configuration.Options;
|
||||
using Daybreak.Models;
|
||||
using Daybreak.Models.Browser;
|
||||
using Daybreak.Models.Builds;
|
||||
using Daybreak.Models.Guildwars;
|
||||
using Daybreak.Services.Browser;
|
||||
using Daybreak.Services.BuildTemplates;
|
||||
using Daybreak.Utils;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Configuration;
|
||||
using System.Core.Extensions;
|
||||
using System.Diagnostics;
|
||||
@@ -21,6 +24,7 @@ using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Extensions;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Daybreak.Controls;
|
||||
|
||||
@@ -39,10 +43,10 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
private const string BrowserSearchLink = $"https://www.google.com/search?q={BrowserSearchPlaceholder}";
|
||||
private const string BrowserDownloadLink = "https://developer.microsoft.com/en-us/microsoft-edge/webview2/";
|
||||
|
||||
private static readonly Regex WebAddressRegex = new("^((http|ftp|https)://)?([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?", RegexOptions.Compiled);
|
||||
private static readonly object Lock = new();
|
||||
private static readonly Regex WebAddressRegex = BuildWebAddressRegex();
|
||||
private static readonly SemaphoreSlim SemaphoreSlim = new(1, 1);
|
||||
|
||||
private static CoreWebView2Environment? CoreWebView2Environment;
|
||||
private static CoreWebView2Environment? CoreWebView2Environment;
|
||||
|
||||
public event EventHandler<string>? FavoriteUriChanged;
|
||||
public event EventHandler? MaximizeClicked;
|
||||
@@ -50,12 +54,14 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
public event EventHandler<DownloadPayload>? DownloadingFile;
|
||||
public event EventHandler<string>? DownloadedFile;
|
||||
|
||||
private readonly HashSet<ulong> domNavigationIds = [];
|
||||
private readonly Task initializationTask;
|
||||
private readonly IHttpClient<ChromiumBrowserWrapper> httpClient;
|
||||
private readonly ILiveOptions<BrowserOptions> liveOptions;
|
||||
private readonly ILogger<ChromiumBrowserWrapper> logger;
|
||||
private readonly IBuildTemplateManager buildTemplateManager;
|
||||
|
||||
private readonly IBrowserExtensionsManager browserExtensionsManager;
|
||||
|
||||
[GenerateDependencyProperty(InitialValue = true)]
|
||||
private bool canDownloadBuild;
|
||||
[GenerateDependencyProperty(InitialValue = true)]
|
||||
@@ -86,6 +92,8 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
private bool showBrowserDisabledMessage;
|
||||
[GenerateDependencyProperty(InitialValue = true)]
|
||||
private bool showDownloadsDialog = true;
|
||||
[GenerateDependencyProperty(InitialValue = true)]
|
||||
private bool maximizeButtonVisible = true;
|
||||
|
||||
private bool browserInitialized = false;
|
||||
|
||||
@@ -95,8 +103,13 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
set => this.SetValue(AddressProperty, value);
|
||||
}
|
||||
|
||||
public IBrowserHistoryManager BrowserHistoryManager { get; }
|
||||
|
||||
public ChromiumBrowserWrapper()
|
||||
: this(Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IHttpClient<ChromiumBrowserWrapper>>(),
|
||||
: this(
|
||||
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IBrowserHistoryManager>(),
|
||||
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IBrowserExtensionsManager>(),
|
||||
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IHttpClient<ChromiumBrowserWrapper>>(),
|
||||
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<ILiveOptions<BrowserOptions>>(),
|
||||
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IBuildTemplateManager>(),
|
||||
Launch.Launcher.Instance.ApplicationServiceProvider.GetRequiredService<ILogger<ChromiumBrowserWrapper>>())
|
||||
@@ -104,18 +117,22 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
}
|
||||
|
||||
public ChromiumBrowserWrapper(
|
||||
IBrowserHistoryManager historyManager,
|
||||
IBrowserExtensionsManager browserExtensionsManager,
|
||||
IHttpClient<ChromiumBrowserWrapper> httpClient,
|
||||
ILiveOptions<BrowserOptions> liveOptions,
|
||||
IBuildTemplateManager buildTemplateManager,
|
||||
ILogger<ChromiumBrowserWrapper> logger)
|
||||
{
|
||||
this.BrowserHistoryManager = historyManager.ThrowIfNull();
|
||||
this.browserExtensionsManager = browserExtensionsManager.ThrowIfNull();
|
||||
this.httpClient = httpClient.ThrowIfNull();
|
||||
this.liveOptions = liveOptions.ThrowIfNull();
|
||||
this.buildTemplateManager = buildTemplateManager.ThrowIfNull();
|
||||
this.logger = logger.ThrowIfNull();
|
||||
this.InitializeEnvironment();
|
||||
this.InitializeComponent();
|
||||
|
||||
this.BrowserHistoryManager.InitializeHistoryManager(this);
|
||||
this.initializationTask = Task.Run(this.InitializeBrowserSafe);
|
||||
}
|
||||
|
||||
@@ -149,24 +166,22 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (Lock)
|
||||
if (CoreWebView2Environment is not null)
|
||||
{
|
||||
if (CoreWebView2Environment is not null)
|
||||
{
|
||||
this.BrowserSupported = true;
|
||||
return;
|
||||
}
|
||||
|
||||
CoreWebView2Environment ??= System.Extensions.TaskExtensions.RunSync(() => CoreWebView2Environment.CreateAsync(null, "BrowserData", new CoreWebView2EnvironmentOptions
|
||||
{
|
||||
EnableTrackingPrevention = true,
|
||||
AllowSingleSignOnUsingOSPrimaryAccount = true
|
||||
}));
|
||||
|
||||
this.BrowserSupported = true;
|
||||
return;
|
||||
}
|
||||
|
||||
CoreWebView2Environment ??= System.Extensions.TaskExtensions.RunSync(() => CoreWebView2Environment.CreateAsync(null, "BrowserData", new CoreWebView2EnvironmentOptions
|
||||
{
|
||||
EnableTrackingPrevention = true,
|
||||
AllowSingleSignOnUsingOSPrimaryAccount = true,
|
||||
AreBrowserExtensionsEnabled = true,
|
||||
}));
|
||||
|
||||
this.BrowserSupported = true;
|
||||
}
|
||||
catch(Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
this.logger!.LogWarning($"Browser initialization failed. Details: {e}");
|
||||
this.BrowserSupported = false;
|
||||
@@ -177,10 +192,7 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
{
|
||||
await this.Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
while (!Monitor.TryEnter(Lock))
|
||||
{
|
||||
await Task.Delay(100);
|
||||
}
|
||||
await SemaphoreSlim.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
@@ -189,7 +201,7 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
}
|
||||
finally
|
||||
{
|
||||
Monitor.Exit(Lock);
|
||||
SemaphoreSlim.Release();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -201,12 +213,18 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
if (!this.BrowserSupported ||
|
||||
!this.BrowserEnabled)
|
||||
{
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.WebBrowser.CoreWebView2 is not null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.ShowBrowserDisabledMessage = false;
|
||||
await this.WebBrowser.EnsureCoreWebView2Async(CoreWebView2Environment);
|
||||
await this.WebBrowser.EnsureCoreWebView2Async(CoreWebView2Environment).ConfigureAwait(true);
|
||||
await this.browserExtensionsManager.InitializeBrowserEnvironment(this.WebBrowser.CoreWebView2!.Profile, CoreWebView2Environment!.BrowserVersionString).ConfigureAwait(true);
|
||||
if (this.Address is not null &&
|
||||
Uri.TryCreate(this.Address, UriKind.RelativeOrAbsolute, out var uri))
|
||||
{
|
||||
@@ -217,6 +235,21 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
this.AddressBarReadonly = this.liveOptions!.Value.AddressBarReadonly;
|
||||
this.CanDownloadBuild = this.liveOptions.Value.DynamicBuildLoading;
|
||||
this.WebBrowser.CoreWebView2.NewWindowRequested += (browser, args) => args.Handled = true;
|
||||
this.WebBrowser.KeyDown += (sender, e) =>
|
||||
{
|
||||
if ((e.Key == Key.Left && Keyboard.Modifiers == ModifierKeys.Alt) ||
|
||||
(e.Key == Key.BrowserBack))
|
||||
{
|
||||
this.BrowserHistoryManager.GoBack();
|
||||
e.Handled = true; // Prevent default behavior
|
||||
}
|
||||
else if ((e.Key == Key.Right && Keyboard.Modifiers == ModifierKeys.Alt) ||
|
||||
(e.Key == Key.BrowserForward))
|
||||
{
|
||||
this.BrowserHistoryManager.GoForward();
|
||||
e.Handled = true; // Prevent default behavior
|
||||
}
|
||||
};
|
||||
this.WebBrowser.NavigationStarting += (browser, args) =>
|
||||
{
|
||||
if (this.CanNavigate is false && args.Uri != this.Address)
|
||||
@@ -271,6 +304,13 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
};
|
||||
this.WebBrowser.CoreWebView2.DOMContentLoaded += async (browser, args) =>
|
||||
{
|
||||
if (this.domNavigationIds.Contains(args.NavigationId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this.domNavigationIds.Add(args.NavigationId);
|
||||
await this.WebBrowser.CoreWebView2.ExecuteScriptAsync(Scripts.CaptureNavigationButtons);
|
||||
if (this.CanDownloadBuild)
|
||||
{
|
||||
await this.WebBrowser.CoreWebView2.ExecuteScriptAsync(Scripts.SendSelectionOnContextMenu);
|
||||
@@ -376,12 +416,12 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
{
|
||||
payload = args.WebMessageAsJson.Deserialize<BrowserPayload>();
|
||||
}
|
||||
catch(Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
this.logger!.LogError(e, $"Exception encountered when deserializing {nameof(BrowserPayload)}");
|
||||
}
|
||||
|
||||
if (payload?.Key == BrowserPayload.PayloadKeys.ContextMenu)
|
||||
if (payload?.Key is BrowserPayload.PayloadKeys.ContextMenu)
|
||||
{
|
||||
var contextMenuPayload = args.WebMessageAsJson.Deserialize<BrowserPayload<OnContextMenuPayload>>();
|
||||
var maybeTemplate = contextMenuPayload?.Value?.Selection?.Trim();
|
||||
@@ -411,12 +451,20 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
this.ContextMenu.IsOpen = true;
|
||||
});
|
||||
}
|
||||
catch(Exception e)
|
||||
catch (Exception e)
|
||||
{
|
||||
this.logger!.LogWarning($"Exception when decoding template {maybeTemplate}. Details {e}");
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (payload?.Key is BrowserPayload.PayloadKeys.XButton1Pressed)
|
||||
{
|
||||
this.BrowserHistoryManager.GoBack();
|
||||
}
|
||||
else if (payload?.Key is BrowserPayload.PayloadKeys.XButton2Pressed)
|
||||
{
|
||||
this.BrowserHistoryManager.GoForward();
|
||||
}
|
||||
}
|
||||
|
||||
private void DownloadOperation_StateChanged(object? sender, object? e)
|
||||
@@ -439,7 +487,7 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
|
||||
private void LoadBuildTemplateButton_Click(object sender, EventArgs e)
|
||||
{
|
||||
var build = this.ContextMenu.DataContext.As<Build>();
|
||||
var build = this.ContextMenu.DataContext.As<IBuildEntry>();
|
||||
this.ContextMenu.IsOpen = false;
|
||||
if (build is null)
|
||||
{
|
||||
@@ -461,26 +509,24 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
return;
|
||||
}
|
||||
|
||||
this.BrowserHistoryManager.UnInitializeHistoryManager();
|
||||
this.WebBrowser?.Dispose();
|
||||
this.browserInitialized = false;
|
||||
}
|
||||
|
||||
private void BackButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
this.WebBrowser.GoBack();
|
||||
this.Address = this.WebBrowser.Source.ToString();
|
||||
this.BrowserHistoryManager.GoBack();
|
||||
}
|
||||
|
||||
private void ForwardButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
this.WebBrowser.GoForward();
|
||||
this.Address = this.WebBrowser.Source.ToString();
|
||||
this.BrowserHistoryManager.GoForward();
|
||||
}
|
||||
|
||||
private void RefreshGlyph_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
this.WebBrowser.Reload();
|
||||
this.Address = this.WebBrowser.Source.ToString();
|
||||
this.BrowserHistoryManager.Reload();
|
||||
}
|
||||
|
||||
private void CancelGlyph_Clicked(object sender, EventArgs e)
|
||||
@@ -504,6 +550,11 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
this.MaximizeClicked?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void Browser_PreviewMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void CheckFavoriteAddress()
|
||||
{
|
||||
if (this.Address == this.FavoriteAddress)
|
||||
@@ -541,4 +592,7 @@ public partial class ChromiumBrowserWrapper : UserControl
|
||||
Uri.TryCreate(address, UriKind.Absolute, out var uri);
|
||||
return uri?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
[GeneratedRegex("^((http|ftp|https)://)?([\\w_-]+(?:(?:\\.[\\w_-]+)+))([\\w.,@?^=%&:/~+#-]*[\\w@?^=%&/~+#-])?", RegexOptions.Compiled)]
|
||||
private static partial Regex BuildWebAddressRegex();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<UserControl x:Class="Daybreak.Controls.DropDownButton"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Controls"
|
||||
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
|
||||
xmlns:glyphs="clr-namespace:Daybreak.Controls.Glyphs"
|
||||
xmlns:converters="clr-namespace:Daybreak.Converters"
|
||||
mc:Ignorable="d"
|
||||
x:Name="_this"
|
||||
Tag="{Binding RelativeSource={RelativeSource Self}}"
|
||||
MouseRightButtonDown="IgnoreRightMouseButton"
|
||||
MouseRightButtonUp="IgnoreRightMouseButton"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<UserControl.Resources>
|
||||
<converters:BooleanToVisibilityConverter x:Key="InverseBooleanToVisibilityConverter" TriggerValue="True"/>
|
||||
</UserControl.Resources>
|
||||
<UserControl.ContextMenu>
|
||||
<ContextMenu Placement="Bottom">
|
||||
<ContextMenu.Template>
|
||||
<ControlTemplate>
|
||||
<local:DropDownButtonContextMenu
|
||||
Items="{Binding PlacementTarget.Tag.Items, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
|
||||
ItemTemplate="{Binding PlacementTarget.Tag.ItemTemplate, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
|
||||
Background="{Binding PlacementTarget.Tag.Background, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
|
||||
Width="{Binding PlacementTarget.Tag.ActualWidth, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
|
||||
ItemClicked="DropDownButtonContextMenu_ItemClicked"/>
|
||||
</ControlTemplate>
|
||||
</ContextMenu.Template>
|
||||
</ContextMenu>
|
||||
</UserControl.ContextMenu>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<buttons:HighlightButton Grid.Column="0"
|
||||
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
|
||||
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalContentAlignment="Center"
|
||||
HorizontalContentAlignment="Center"
|
||||
Clicked="MainButton_Clicked">
|
||||
<buttons:HighlightButton.ButtonContent>
|
||||
<ContentControl ContentTemplate="{Binding ItemTemplate, RelativeSource={RelativeSource AncestorType={x:Type local:DropDownButton}}}"
|
||||
Content="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType={x:Type local:DropDownButton}}}" />
|
||||
</buttons:HighlightButton.ButtonContent>
|
||||
</buttons:HighlightButton>
|
||||
<buttons:HighlightButton Grid.Column="1"
|
||||
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
|
||||
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
Clicked="ArrowButton_Clicked">
|
||||
<buttons:HighlightButton.ButtonContent>
|
||||
<glyphs:ArrowGlyph Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
|
||||
Height="30"
|
||||
Width="30"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalAlignment="Center"
|
||||
RenderTransformOrigin="0.5 0.5">
|
||||
<glyphs:ArrowGlyph.RenderTransform>
|
||||
<RotateTransform Angle="270"/>
|
||||
</glyphs:ArrowGlyph.RenderTransform>
|
||||
</glyphs:ArrowGlyph>
|
||||
</buttons:HighlightButton.ButtonContent>
|
||||
</buttons:HighlightButton>
|
||||
<Rectangle Opacity="0.2"
|
||||
Fill="Black"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
Visibility="{Binding ElementName=_this, Path=ClickEnabled, Mode=OneWay, Converter={StaticResource InverseBooleanToVisibilityConverter}}"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Extensions;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Daybreak.Controls;
|
||||
/// <summary>
|
||||
/// Interaction logic for DropdownButton.xaml
|
||||
/// </summary>
|
||||
public partial class DropDownButton : UserControl
|
||||
{
|
||||
[GenerateDependencyProperty]
|
||||
private DataTemplate itemTemplate = default!;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private object selectedItem = default!;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private IEnumerable items = default!;
|
||||
|
||||
[GenerateDependencyProperty(InitialValue = true)]
|
||||
private bool clickEnabled = true;
|
||||
|
||||
public event EventHandler<object> Clicked = default!;
|
||||
public event EventHandler<object> SelectionChanged = default!;
|
||||
|
||||
public DropDownButton()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
private void MainButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
this.Clicked?.Invoke(this, this.SelectedItem);
|
||||
}
|
||||
|
||||
private void ArrowButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
var contextMenu = this.ContextMenu;
|
||||
contextMenu.PlacementTarget = this;
|
||||
contextMenu.IsOpen = true;
|
||||
}
|
||||
|
||||
private void DropDownButtonContextMenu_ItemClicked(object _, object e)
|
||||
{
|
||||
this.SelectedItem = e;
|
||||
this.ContextMenu.IsOpen = false;
|
||||
this.SelectionChanged?.Invoke(this, e);
|
||||
}
|
||||
|
||||
private void IgnoreRightMouseButton(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton is MouseButton.Right)
|
||||
{
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<UserControl x:Class="Daybreak.Controls.DropDownButtonContextMenu"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Controls"
|
||||
xmlns:buttons="clr-namespace:Daybreak.Controls.Buttons"
|
||||
mc:Ignorable="d"
|
||||
x:Name="_this"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<ItemsControl ItemsSource="{Binding ElementName=_this, Path=Items, Mode=OneWay}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<buttons:HighlightButton HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
Tag="{Binding Mode=OneWay}"
|
||||
Clicked="HighlightButton_Clicked">
|
||||
<buttons:HighlightButton.ButtonContent>
|
||||
<ContentControl DataContext="{Binding DataContext, RelativeSource={RelativeSource AncestorType={x:Type buttons:HighlightButton}}}"
|
||||
ContentTemplate="{Binding ItemTemplate, RelativeSource={RelativeSource AncestorType={x:Type local:DropDownButtonContextMenu}}}"/>
|
||||
</buttons:HighlightButton.ButtonContent>
|
||||
</buttons:HighlightButton>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,32 @@
|
||||
using Daybreak.Controls.Buttons;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Extensions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Extensions;
|
||||
|
||||
namespace Daybreak.Controls;
|
||||
/// <summary>
|
||||
/// Interaction logic for DropDownButtonContextMenu.xaml
|
||||
/// </summary>
|
||||
public partial class DropDownButtonContextMenu : UserControl
|
||||
{
|
||||
[GenerateDependencyProperty]
|
||||
private DataTemplate itemTemplate = default!;
|
||||
|
||||
[GenerateDependencyProperty]
|
||||
private IEnumerable items = default!;
|
||||
|
||||
public event EventHandler<object> ItemClicked = default!;
|
||||
|
||||
public DropDownButtonContextMenu()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
|
||||
private void HighlightButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
this.ItemClicked?.Invoke(this, sender.Cast<HighlightButton>().DataContext);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
@@ -27,7 +28,7 @@
|
||||
HorizontalAlignment="Left" />
|
||||
<buttons:HighlightButton
|
||||
Grid.Column="1"
|
||||
Title="Edit Build"
|
||||
Title="Export Build"
|
||||
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
|
||||
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
|
||||
Margin="10, 0, 10, 0"
|
||||
@@ -40,6 +41,19 @@
|
||||
ToolTip="Load current build"/>
|
||||
<buttons:HighlightButton
|
||||
Grid.Column="2"
|
||||
Title="Export Team Build"
|
||||
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
|
||||
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
|
||||
Margin="10, 0, 10, 0"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalContentAlignment="Center"
|
||||
Cursor="Hand"
|
||||
FontSize="18"
|
||||
MouseLeftButtonDown="EditTeamBuild_MouseLeftButtonDown"
|
||||
ToolTip="Load current team build"/>
|
||||
<buttons:HighlightButton
|
||||
Grid.Column="3"
|
||||
Title="Meta Builds"
|
||||
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
|
||||
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
using Daybreak.Models.Guildwars;
|
||||
using Daybreak.Services.BuildTemplates;
|
||||
using Daybreak.Services.Navigation;
|
||||
using Daybreak.Services.Scanner;
|
||||
using Daybreak.Views;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NAudio.CoreAudioApi;
|
||||
using System;
|
||||
using System.Core.Extensions;
|
||||
using System.Threading;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Daybreak.Controls.FocusViewComponents;
|
||||
@@ -14,6 +17,7 @@ namespace Daybreak.Controls.FocusViewComponents;
|
||||
/// </summary>
|
||||
public partial class MainPlayerInformationComponent : UserControl
|
||||
{
|
||||
private readonly IGuildwarsMemoryCache guildwarsMemoryCache;
|
||||
private readonly IBuildTemplateManager buildTemplateManager;
|
||||
private readonly IViewManager viewManager;
|
||||
|
||||
@@ -21,15 +25,18 @@ public partial class MainPlayerInformationComponent : UserControl
|
||||
|
||||
public MainPlayerInformationComponent()
|
||||
: this(
|
||||
Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IGuildwarsMemoryCache>(),
|
||||
Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IBuildTemplateManager>(),
|
||||
Launcher.Instance.ApplicationServiceProvider.GetRequiredService<IViewManager>())
|
||||
{
|
||||
}
|
||||
|
||||
public MainPlayerInformationComponent(
|
||||
IGuildwarsMemoryCache guildwarsMemoryCache,
|
||||
IBuildTemplateManager buildTemplateManager,
|
||||
IViewManager viewManager)
|
||||
{
|
||||
this.guildwarsMemoryCache = guildwarsMemoryCache.ThrowIfNull();
|
||||
this.buildTemplateManager = buildTemplateManager.ThrowIfNull();
|
||||
this.viewManager = viewManager.ThrowIfNull();
|
||||
this.InitializeComponent();
|
||||
@@ -89,9 +96,46 @@ public partial class MainPlayerInformationComponent : UserControl
|
||||
|
||||
if (mainPlayer.CurrentBuild is Build build)
|
||||
{
|
||||
var buildEntry = this.buildTemplateManager.CreateBuild();
|
||||
buildEntry.Build = build;
|
||||
this.viewManager.ShowView<BuildTemplateView>(buildEntry);
|
||||
var buildEntry = this.buildTemplateManager.CreateSingleBuild();
|
||||
buildEntry.Primary = build.Primary;
|
||||
buildEntry.Secondary = build.Secondary;
|
||||
buildEntry.Attributes = build.Attributes;
|
||||
buildEntry.Skills = build.Skills;
|
||||
this.viewManager.ShowView<SingleBuildTemplateView>(buildEntry);
|
||||
}
|
||||
}
|
||||
|
||||
private async void EditTeamBuild_MouseLeftButtonDown(object _, System.Windows.Input.MouseButtonEventArgs e)
|
||||
{
|
||||
var gameData = await this.guildwarsMemoryCache.ReadGameData(CancellationToken.None);
|
||||
if (gameData is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var teamBuild = this.buildTemplateManager.CreateTeamBuild();
|
||||
teamBuild.Builds.Clear();
|
||||
var mainPlayerBuild = this.buildTemplateManager.CreateSingleBuild();
|
||||
mainPlayerBuild.Primary = gameData.MainPlayer?.CurrentBuild?.Primary!;
|
||||
mainPlayerBuild.Secondary = gameData.MainPlayer?.CurrentBuild?.Secondary!;
|
||||
mainPlayerBuild.Attributes = gameData.MainPlayer?.CurrentBuild?.Attributes!;
|
||||
mainPlayerBuild.Skills = gameData.MainPlayer?.CurrentBuild?.Skills!;
|
||||
teamBuild.Builds.Add(mainPlayerBuild);
|
||||
foreach (var player in gameData.Party!)
|
||||
{
|
||||
if (player.CurrentBuild is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var singleBuildEntry = this.buildTemplateManager.CreateSingleBuild();
|
||||
singleBuildEntry.Primary = player.CurrentBuild.Primary;
|
||||
singleBuildEntry.Secondary = player.CurrentBuild.Secondary;
|
||||
singleBuildEntry.Attributes = player.CurrentBuild.Attributes;
|
||||
singleBuildEntry.Skills = player.CurrentBuild.Skills;
|
||||
teamBuild.Builds.Add(singleBuildEntry);
|
||||
}
|
||||
|
||||
this.viewManager.ShowView<TeamBuildTemplateView>(teamBuild);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<UserControl x:Class="Daybreak.Controls.Glyphs.ArrowGlyph"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Controls.Glyphs"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Viewbox>
|
||||
<Grid Width="30" Height="30" Margin="-2, 0, 0 ,0">
|
||||
<Path Fill="{DynamicResource MahApps.Brushes.ThemeForeground}" VerticalAlignment="Top" HorizontalAlignment="Left"
|
||||
Data="m18.87,9.41l-5.58,5.59l5.58,5.59a1,1 0 0 1 0,1.41l0,0a1,1 0 0 1 -1.41,0l-6.36,-6.36a0.91,0.91 0 0 1 0,-1.28l6.36,-6.36a1,1 0 0 1 1.41,0l0,0a1,1 0 0 1 0,1.41z"></Path>
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Daybreak.Controls.Glyphs;
|
||||
/// <summary>
|
||||
/// Interaction logic for ArrowGlyph.xaml
|
||||
/// </summary>
|
||||
public partial class ArrowGlyph : UserControl
|
||||
{
|
||||
public ArrowGlyph()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<UserControl x:Class="Daybreak.Controls.Glyphs.PersonGlyph"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Controls.Glyphs"
|
||||
mc:Ignorable="d"
|
||||
x:Name="_this"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<Viewbox>
|
||||
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
Data="m12.5,11c3.03757,0 5.5,-2.46243 5.5,-5.5c0,-3.03757 -2.46243,-5.5 -5.5,-5.5c-3.03757,0 -5.5,2.46243 -5.5,5.5c0,3.03757 2.46243,5.5 5.5,5.5l0,0zm12.5,12l0,0l0,0l-25,0c0.74635,-6.19634 6.05817,-11 12.5,-11c6.44183,0 11.75365,4.80366 12.5,11l0,0z"/>
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Daybreak.Controls.Glyphs;
|
||||
/// <summary>
|
||||
/// Interaction logic for PersonGlyph.xaml
|
||||
/// </summary>
|
||||
public partial class PersonGlyph : UserControl
|
||||
{
|
||||
public PersonGlyph()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<UserControl x:Class="Daybreak.Controls.Glyphs.PinGlyph"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Controls.Glyphs"
|
||||
x:Name="_this"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Viewbox>
|
||||
<Grid>
|
||||
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
Data="m15.0058,27.53079a1,1 0 0 1 -0.71,-0.29l-11.53,-11.53a1,1 0 0 1 0,-1.42l1.52,-1.51a6.06,6.06 0 0 1 6.37,-1.43l5.41,-5.41l-0.94,-0.94a1,1 0 0 1 0,-1.41l3.29,-3.3a1,1 0 0 1 1.41,0l9.89,9.89a1,1 0 0 1 0,1.41l-3.3,3.29a1,1 0 0 1 -1.41,0l-0.94,-0.94l-5.41,5.41a6.08,6.08 0 0 1 -1.43,6.37l-1.51,1.52a1,1 0 0 1 -0.71,0.29zm-10.12,-12.53l10.12,10.12l0.81,-0.81a4.12,4.12 0 0 0 0.77,-4.75a1,1 0 0 1 0.18,-1.15l6.59,-6.59a1,1 0 0 1 1.42,0l0.94,0.94l1.88,-1.88l-8.47,-8.47l-1.88,1.88l0.94,0.94a1,1 0 0 1 0,1.42l-6.59,6.59a1,1 0 0 1 -1.15,0.18a4.12,4.12 0 0 0 -4.75,0.77l-0.81,0.81z"/>
|
||||
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
Data="m1.0058,30.00079a1,1 0 0 1 -0.71,-0.29a1,1 0 0 1 0,-1.42l8.24,-8.23a1,1 0 0 1 1.41,1.41l-8.23,8.24a1,1 0 0 1 -0.71,0.29z"/>
|
||||
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
Data="m24.0658,13.53079a1,1 0 0 1 -0.71,-0.29l-2.47,-2.47a1,1 0 1 1 1.42,-1.42l2.47,2.47a1,1 0 0 1 0,1.42a1,1 0 0 1 -0.71,0.29z"/>
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Daybreak.Controls.Glyphs;
|
||||
/// <summary>
|
||||
/// Interaction logic for PinGlyph.xaml
|
||||
/// </summary>
|
||||
public partial class PinGlyph : UserControl
|
||||
{
|
||||
public PinGlyph()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<UserControl x:Class="Daybreak.Controls.Glyphs.PlusGlyph"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Controls.Glyphs"
|
||||
x:Name="_this"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Viewbox Stretch="Fill">
|
||||
<Grid>
|
||||
<Path Data="m13,20a1,1 0 0 1 -1,-1l0,-12a1,1 0 0 1 2,0l0,12a1,1 0 0 1 -1,1z"
|
||||
Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></Path>
|
||||
<Path Data="m19,14l-12,0a1,1 0 0 1 0,-2l12,0a1,1 0 0 1 0,2z"
|
||||
Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"></Path>
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Daybreak.Controls.Glyphs;
|
||||
/// <summary>
|
||||
/// Interaction logic for PlusGlyph.xaml
|
||||
/// </summary>
|
||||
public partial class PlusGlyph : UserControl
|
||||
{
|
||||
public PlusGlyph()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<UserControl x:Class="Daybreak.Controls.Glyphs.TeamGlyph"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Daybreak.Controls.Glyphs"
|
||||
x:Name="_this"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="450" d:DesignWidth="800">
|
||||
<Grid>
|
||||
<Viewbox>
|
||||
<Grid>
|
||||
<Ellipse Margin="97,25,0,0"
|
||||
Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left"
|
||||
Width="13"
|
||||
Height="13"/>
|
||||
<Ellipse Margin="20,25,0,0"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Left"
|
||||
Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
Width="13"
|
||||
Height="13"/>
|
||||
<Ellipse Margin="0,18,0,0"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Center"
|
||||
Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
Width="18"
|
||||
Height="18"/>
|
||||
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
Data="m34.6,51.17l0,0q-0.43,0.61 -0.84,1.24l0,0a35.82,35.82 0 0 0 -5.76,19.53l72,0a35.82,35.82 0 0 0 -5.71,-19.44l0,0q-0.41,-0.63 -0.84,-1.24l0,0a36,36 0 0 0 -58.8,0l-0.05,-0.09z"/>
|
||||
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
Data="m0,63.94l20.74,0a44,44 0 0 1 13.72,-24.58a26,26 0 0 0 -34.46,24.58z"/>
|
||||
<Path Fill="{Binding ElementName=_this, Path=Foreground, Mode=OneWay}"
|
||||
Data="m102,37.94a26,26 0 0 0 -8.46,1.42a44,44 0 0 1 13.72,24.58l20.74,0a26,26 0 0 0 -26,-26z"/>
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Daybreak.Controls.Glyphs;
|
||||
/// <summary>
|
||||
/// Interaction logic for TeamGlyph.xaml
|
||||
/// </summary>
|
||||
public partial class TeamGlyph : UserControl
|
||||
{
|
||||
public TeamGlyph()
|
||||
{
|
||||
this.InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ public partial class LivingEntityContextMenu : UserControl
|
||||
this.PrimaryProfessionVisible = false;
|
||||
}
|
||||
|
||||
await this.guildwarsMemoryReader.EnsureInitialized(context.GuildWarsApplicationLaunchContext!.GuildWarsProcess, CancellationToken.None);
|
||||
await this.guildwarsMemoryReader.EnsureInitialized(context.GuildWarsApplicationLaunchContext!.ProcessId, CancellationToken.None);
|
||||
this.EntityName = await this.guildwarsMemoryReader.GetEntityName(context.LivingEntity!, CancellationToken.None).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,13 @@
|
||||
Cursor="Hand"
|
||||
Clicked="CopyGuildwarsButton_Clicked"
|
||||
ToolTip="Copy Guild Wars from an existing installation"/>
|
||||
<buttons:MenuButton Title="Event Calendar"
|
||||
Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
|
||||
HighlightColor="{DynamicResource MahApps.Brushes.Accent}"
|
||||
Height="30"
|
||||
Cursor="Hand"
|
||||
Clicked="CalendarButton_Clicked"
|
||||
ToolTip="Show current and upcoming events"/>
|
||||
</local:ExpandableMenuSection.Children>
|
||||
</local:ExpandableMenuSection>
|
||||
<Grid>
|
||||
|
||||
@@ -163,6 +163,11 @@ public partial class MenuList : UserControl
|
||||
this.viewManager.ShowView<TradeAlertsView>();
|
||||
}
|
||||
|
||||
private void CalendarButton_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
this.viewManager.ShowView<EventCalendarView>();
|
||||
}
|
||||
|
||||
private void UserControl_Loaded(object sender, System.Windows.RoutedEventArgs e)
|
||||
{
|
||||
this.cancellationTokenSource = new CancellationTokenSource();
|
||||
@@ -180,13 +185,13 @@ public partial class MenuList : UserControl
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var unopenedNotifications = this.notificationStorage.GetPendingNotifications().ToList();
|
||||
var unopenedNotifications = this.notificationStorage.GetPendingNotifications();
|
||||
if (unopenedNotifications.Any())
|
||||
{
|
||||
await this.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
this.ShowingNotificationCount = true;
|
||||
this.NotificationCount = unopenedNotifications.Count;
|
||||
this.NotificationCount = unopenedNotifications.Count();
|
||||
});
|
||||
}
|
||||
else
|
||||
|
||||
@@ -68,8 +68,13 @@
|
||||
</ContextMenu>
|
||||
</UserControl.Resources>
|
||||
<Grid>
|
||||
<Image x:Name="MapDrawingHost" VerticalAlignment="Top" HorizontalAlignment="Left" Stretch="Fill"/>
|
||||
<Image x:Name="EntitiesDrawingHost" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
|
||||
<Grid RenderTransformOrigin="0.5 0.5">
|
||||
<Grid.RenderTransform>
|
||||
<RotateTransform x:Name="RotateTransform" Angle="{Binding ElementName=_this, Path=Angle, Mode=OneWay}"/>
|
||||
</Grid.RenderTransform>
|
||||
<Image x:Name="MapDrawingHost" VerticalAlignment="Top" HorizontalAlignment="Left" Stretch="Fill" />
|
||||
<Image x:Name="EntitiesDrawingHost" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
|
||||
</Grid>
|
||||
<Grid Background="{DynamicResource Daybreak.Brushes.Background}"
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Right"
|
||||
@@ -79,15 +84,5 @@
|
||||
Clicked="MaximizeButton_Clicked"
|
||||
ToolTip="Maximize"/>
|
||||
</Grid>
|
||||
<WrapPanel Background="{DynamicResource Daybreak.Brushes.Background}"
|
||||
VerticalAlignment="Top"
|
||||
HorizontalAlignment="Right" >
|
||||
<TextBlock Text="{Binding ElementName=_this, Path=TargetEntityId, Mode=OneWay}" Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
|
||||
FontSize="16" Padding="5" />
|
||||
<TextBlock Text=" - " Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
|
||||
FontSize="16" Padding="5" />
|
||||
<TextBlock Text="{Binding ElementName=_this, Path=TargetEntityModelId, Mode=OneWay}" Foreground="{DynamicResource MahApps.Brushes.ThemeForeground}"
|
||||
FontSize="16" Padding="5" />
|
||||
</WrapPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -78,6 +78,10 @@ public partial class GuildwarsMinimap : UserControl
|
||||
[GenerateDependencyProperty]
|
||||
private bool controlsVisible;
|
||||
[GenerateDependencyProperty]
|
||||
private bool canRotate;
|
||||
[GenerateDependencyProperty]
|
||||
private double angle;
|
||||
[GenerateDependencyProperty]
|
||||
private int targetEntityId;
|
||||
[GenerateDependencyProperty]
|
||||
private int targetEntityModelId;
|
||||
@@ -163,12 +167,14 @@ public partial class GuildwarsMinimap : UserControl
|
||||
this.GameData.MainPlayer!.Position = mainPlayerState?.Position ?? new Position();
|
||||
this.GameData.MainPlayer!.CurrentHealth = mainPlayerState?.Health ?? 0;
|
||||
this.GameData.MainPlayer!.CurrentEnergy = mainPlayerState?.Energy ?? 0;
|
||||
this.GameData.MainPlayer!.RotationAngle = mainPlayerState?.RotationAngle ?? 0;
|
||||
foreach (var worldPlayer in this.GameData.WorldPlayers!)
|
||||
{
|
||||
var worldPlayerState = this.GameState.States?.FirstOrDefault(state => state.Id == worldPlayer.Id);
|
||||
worldPlayer.Position = worldPlayerState?.Position ?? new Position();
|
||||
worldPlayer.CurrentHealth = worldPlayerState?.Health ?? 0;
|
||||
worldPlayer.CurrentEnergy = worldPlayerState?.Energy ?? 0;
|
||||
worldPlayer.RotationAngle = worldPlayerState?.RotationAngle ?? 0;
|
||||
}
|
||||
|
||||
foreach (var partyPlayer in this.GameData.Party!)
|
||||
@@ -177,6 +183,7 @@ public partial class GuildwarsMinimap : UserControl
|
||||
partyPlayer.Position = partyPlayerState?.Position ?? new Position();
|
||||
partyPlayer.CurrentHealth = partyPlayerState?.Health ?? 0;
|
||||
partyPlayer.CurrentEnergy = partyPlayerState?.Energy ?? 0;
|
||||
partyPlayer.RotationAngle = partyPlayerState?.RotationAngle ?? 0;
|
||||
}
|
||||
|
||||
foreach (var entity in this.GameData.LivingEntities!)
|
||||
@@ -186,7 +193,10 @@ public partial class GuildwarsMinimap : UserControl
|
||||
entity.State = state?.State ?? LivingEntityState.Unknown;
|
||||
entity.Health = state?.Health ?? 0;
|
||||
entity.Energy = state?.Energy ?? 0;
|
||||
entity.RotationAngle = state?.RotationAngle ?? 0;
|
||||
}
|
||||
|
||||
this.Angle = this.CanRotate ? (this.GameState.Camera.Yaw - (Math.PI / 2)) * (180 / Math.PI) : 0;
|
||||
}
|
||||
|
||||
private void UpdateGameData()
|
||||
@@ -341,6 +351,7 @@ public partial class GuildwarsMinimap : UserControl
|
||||
|
||||
this.CalculatePathsToObjectives();
|
||||
|
||||
var angleRads = -(Math.PI / 180 * this.Angle);
|
||||
var foregroundColor = this.FindResource("MahApps.Colors.ThemeBackground").Cast<Color>();
|
||||
bitmap.Clear(Colors.Transparent);
|
||||
using var context = bitmap.GetBitmapContext();
|
||||
@@ -357,9 +368,9 @@ public partial class GuildwarsMinimap : UserControl
|
||||
this.drawingService.DrawEngagementArea(bitmap, this.GameData);
|
||||
this.drawingService.DrawMainPlayerPositionHistory(bitmap, this.mainPlayerPositionHistory);
|
||||
this.drawingService.DrawPaths(bitmap, this.pathfindingCache);
|
||||
this.drawingService.DrawQuestObjectives(bitmap, this.GameData.MainPlayer?.QuestLog ?? []);
|
||||
this.drawingService.DrawMapIcons(bitmap, this.GameData.MapIcons ?? []);
|
||||
this.drawingService.DrawEntities(bitmap, this.GameData, this.TargetEntityId);
|
||||
this.drawingService.DrawQuestObjectives(bitmap, this.GameData.MainPlayer?.QuestLog ?? [], angleRads);
|
||||
this.drawingService.DrawMapIcons(bitmap, this.GameData.MapIcons ?? [], angleRads);
|
||||
this.drawingService.DrawEntities(bitmap, this.GameData, this.TargetEntityId, angleRads);
|
||||
bitmap.Unlock();
|
||||
this.drawingLatency.Record(sw.ElapsedMilliseconds);
|
||||
}
|
||||
@@ -368,8 +379,10 @@ public partial class GuildwarsMinimap : UserControl
|
||||
{
|
||||
var x = (int)((entity.Position!.Value.X - this.originPoint.X) * this.Zoom);
|
||||
var y = 0 - (int)((entity.Position!.Value.Y - this.originPoint.Y) * this.Zoom);
|
||||
|
||||
return Math.Pow(mousePosition.X - x, 2) + Math.Pow(mousePosition.Y - y, 2) < Math.Pow(EntitySize * this.Zoom, 2);
|
||||
var entityPoint = new Point(x, y);
|
||||
var centerPoint = new Point(this.ActualWidth / 2, this.ActualHeight / 2);
|
||||
var finalEntityPoint = RotatePointAroundPivot(entityPoint, centerPoint, this.Angle);
|
||||
return Math.Pow(mousePosition.X - finalEntityPoint.X, 2) + Math.Pow(mousePosition.Y - finalEntityPoint.Y, 2) < Math.Pow(EntitySize * this.Zoom, 2);
|
||||
}
|
||||
|
||||
private void DragMinimap()
|
||||
@@ -379,9 +392,14 @@ public partial class GuildwarsMinimap : UserControl
|
||||
return;
|
||||
}
|
||||
|
||||
var rad = -this.Angle * Math.PI / 180;
|
||||
var mousePosition = Mouse.GetPosition(this);
|
||||
var offset = mousePosition - this.initialClickPoint;
|
||||
var transformedOffset = new Vector((offset.X * Math.Cos(rad)) - (offset.Y * Math.Sin(rad)),
|
||||
(offset.X * Math.Sin(rad)) + (offset.Y * Math.Cos(rad)));
|
||||
this.initialClickPoint = mousePosition;
|
||||
this.offsetRevert = 0;
|
||||
this.originOffset = mousePosition - this.initialClickPoint;
|
||||
this.originOffset += transformedOffset;
|
||||
this.offsetRevertTime = DateTime.Now + this.offsetRevertDelay;
|
||||
this.UpdateGameData();
|
||||
}
|
||||
@@ -518,7 +536,7 @@ public partial class GuildwarsMinimap : UserControl
|
||||
|
||||
private void GuildwarsMinimap_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
this.initialClickPoint = Mouse.GetPosition(this) - this.originOffset;
|
||||
this.initialClickPoint = Mouse.GetPosition(this);
|
||||
this.offsetRevert = 0;
|
||||
this.offsetRevertTime = DateTime.Now + this.offsetRevertDelay;
|
||||
this.dragging = true;
|
||||
@@ -694,7 +712,7 @@ public partial class GuildwarsMinimap : UserControl
|
||||
{
|
||||
var delta = e.Delta > 0 ? 0.1 : -0.1;
|
||||
var previousZoom = this.Zoom;
|
||||
var newZoom = this.Zoom + this.Zoom * delta;
|
||||
var newZoom = this.Zoom + (this.Zoom * delta);
|
||||
this.originOffset *= newZoom / previousZoom;
|
||||
this.initialClickPoint = new Point(
|
||||
this.initialClickPoint.X * newZoom / previousZoom,
|
||||
@@ -785,6 +803,25 @@ public partial class GuildwarsMinimap : UserControl
|
||||
}
|
||||
}
|
||||
|
||||
private static Point RotatePointAroundPivot(Point point, Point pivot, double angle)
|
||||
{
|
||||
// Translate the point to the pivot point
|
||||
point = new Point(point.X - pivot.X, point.Y - pivot.Y);
|
||||
|
||||
// Convert angle to radians for the rotation
|
||||
double radians = Math.PI / 180 * angle;
|
||||
|
||||
// Rotate the point around the pivot
|
||||
double rotatedX = (point.X * Math.Cos(radians)) - (point.Y * Math.Sin(radians));
|
||||
double rotatedY = (point.X * Math.Sin(radians)) + (point.Y * Math.Cos(radians));
|
||||
|
||||
// Translate the point back
|
||||
rotatedX += pivot.X;
|
||||
rotatedY += pivot.Y;
|
||||
|
||||
return new Point(rotatedX, rotatedY);
|
||||
}
|
||||
|
||||
private static bool PositionsCollide(Position position1, Position position2)
|
||||
{
|
||||
var a = PositionRadius + PositionRadius;
|
||||
|
||||
@@ -43,7 +43,7 @@ public partial class PlayerContextMenu : UserControl
|
||||
return;
|
||||
}
|
||||
|
||||
await this.guildwarsMemoryReader.EnsureInitialized(context.GuildWarsApplicationLaunchContext!.GuildWarsProcess, CancellationToken.None);
|
||||
await this.guildwarsMemoryReader.EnsureInitialized(context.GuildWarsApplicationLaunchContext!.ProcessId, CancellationToken.None);
|
||||
this.PlayerName = await this.guildwarsMemoryReader.GetEntityName(context.Player!, CancellationToken.None).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
<RowDefinition Height="auto"></RowDefinition>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"></ColumnDefinition>
|
||||
@@ -31,7 +30,6 @@
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock FontSize="16" Text="Username:" Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" Margin="5" Grid.Row="0" HorizontalAlignment="Right" VerticalAlignment="Center"></TextBlock>
|
||||
<TextBlock FontSize="16" Text="Password:" Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" Margin="5" Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Center"></TextBlock>
|
||||
<TextBlock FontSize="16" Text="Character name:" Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" Margin="5" Grid.Row="2" HorizontalAlignment="Right" VerticalAlignment="Center"></TextBlock>
|
||||
<TextBox Text="{Binding ElementName=_this, Path=Username, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" Background="Transparent"
|
||||
BorderThickness="1" HorizontalAlignment="Stretch" Grid.Row="0" Grid.Column="1"
|
||||
FontSize="16" TextChanged="UsernameTextbox_TextChanged" Margin="5"
|
||||
@@ -40,10 +38,6 @@
|
||||
BorderThickness="1" HorizontalAlignment="Stretch" Grid.Row="1" Grid.Column="1"
|
||||
FontSize="16" PasswordChanged="Passwordbox_PasswordChanged" Margin="5"
|
||||
ToolTip="Password" />
|
||||
<TextBox Text="{Binding ElementName=_this, Path=CharacterName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" Background="Transparent"
|
||||
BorderThickness="1" HorizontalAlignment="Stretch" Grid.Row="2" Grid.Column="1"
|
||||
FontSize="16" TextChanged="CharacterNameTextbox_TextChanged" Margin="5"
|
||||
ToolTip="Character name" />
|
||||
</Grid>
|
||||
<buttons:BinButton Grid.Column="2" Height="30" Width="30" Foreground="{StaticResource MahApps.Brushes.ThemeForeground}" VerticalAlignment="Center" Margin="5, 5, 5, 5"
|
||||
Clicked="BinButton_Clicked"
|
||||
|
||||
@@ -21,8 +21,6 @@ public partial class AccountTemplate : UserControl
|
||||
private string username = string.Empty;
|
||||
[GenerateDependencyProperty]
|
||||
private string password = string.Empty;
|
||||
[GenerateDependencyProperty]
|
||||
private string characterName = string.Empty;
|
||||
|
||||
public AccountTemplate()
|
||||
{
|
||||
@@ -36,7 +34,6 @@ public partial class AccountTemplate : UserControl
|
||||
{
|
||||
this.PasswordBox.Password = loginCredentials.Password;
|
||||
this.Username = loginCredentials.Username;
|
||||
this.CharacterName = loginCredentials.CharacterName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,11 +47,6 @@ public partial class AccountTemplate : UserControl
|
||||
this.DataContext.As<LoginCredentials>()!.Username = this.Username;
|
||||
}
|
||||
|
||||
private void CharacterNameTextbox_TextChanged(object sender, EventArgs e)
|
||||
{
|
||||
this.DataContext.As<LoginCredentials>()!.CharacterName = this.CharacterName;
|
||||
}
|
||||
|
||||
private void Passwordbox_PasswordChanged(object sender, EventArgs e)
|
||||
{
|
||||
this.Password = sender.As<PasswordBox>()?.Password;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user