Compare commits

...

106 Commits

Author SHA1 Message Date
mrbesen 2b4d718481
check aspect ratio 2023-10-21 21:30:25 +02:00
mrbesen b009411f55
windows compat 2023-09-06 21:17:05 +02:00
mrbesen 16e1813f95
start "hide league client" feature 2023-09-05 22:20:06 +02:00
mrbesen 90c328aa4b
bump verison 2023-09-02 16:29:37 +02:00
mrbesen d316c2d24a
disable dodge button by default 2023-09-02 16:16:46 +02:00
mrbesen a1dc7f1011
update language files 2023-09-02 14:43:44 +02:00
mrbesen 1a353a5a21
reload clientrunes when they are changed 2023-09-02 14:35:53 +02:00
mrbesen 86b91774a7
fix Runeeditor corrupting runes 2023-09-02 14:35:35 +02:00
mrbesen 2b92291fa4
fix response parsing of edit and create RunePage 2023-09-02 14:35:16 +02:00
mrbesen c2d198b1c7
add a loading window for the rune editor 2023-09-02 14:34:53 +02:00
mrbesen 5bdb21e268
fix compile warnings 2023-09-02 12:01:41 +02:00
mrbesen b1c4739138
add dodge button 2023-09-02 11:55:02 +02:00
mrbesen 2cacbfe27f
add --access flag for debugging 2023-09-02 11:52:18 +02:00
Oliver 5d0795dbd0
dd: simplify code 2023-08-29 00:18:24 +02:00
Oliver 9f2b175502
dd: use move semantics 2023-08-29 00:18:08 +02:00
Oliver 2fdb7544c8
fix compile warning 2023-08-29 00:17:40 +02:00
Oliver 12a2d71762
debian fix changelog mail 2023-08-28 22:14:33 +02:00
mrbesen 01e9a42482
add loading screen; make RestClient a QObject 2023-08-27 16:51:19 +02:00
mrbesen e7a10f2921
remove debug logging 2023-06-19 21:22:44 +02:00
mrbesen 7d5f61dba8
implement subgroups in rune editor 2023-06-19 20:58:55 +02:00
mrbesen df78570e51
select Rune Style 2023-06-13 23:02:38 +02:00
mrbesen a9fa26346f
rune editor with runeaspektbuttongroups 2023-06-13 22:40:55 +02:00
mrbesen 201f3665b3
fix restclient corrupting requests 2023-06-12 20:19:37 +02:00
mrbesen 61c64def43
remove runepage from lolaa; corrupted QString fix; cellid changed fix 2023-06-11 21:06:32 +02:00
mrbesen adcf715694
runepage icons fuzzy matching 2023-06-11 20:36:56 +02:00
mrbesen b919a64ae6
move to QString 2023-05-31 22:22:23 +02:00
mrbesen 42ff827caf
update translations 2023-05-21 12:42:22 +02:00
mrbesen 4535495b09
new icons 2023-05-21 12:42:08 +02:00
mrbesen e9f99b2323
move delete to bottom 2023-05-21 11:57:21 +02:00
mrbesen bc63e4be72
windows compat 2023-05-04 20:13:07 +02:00
mrbesen 96d57e8b8a
no rich text 2023-05-02 21:40:53 +02:00
mrbesen 17b244ec81
import and export runes 2023-05-02 21:31:17 +02:00
mrbesen 5aff7f431a
fix autowriteTextbox 2023-05-01 22:36:25 +02:00
mrbesen 14ec8a711b
add runeeditor 2023-05-01 22:36:11 +02:00
mrbesen ee7a11b0ef
champion images in runepages 2023-05-01 01:23:08 +02:00
mrbesen 67175bc446
update link 2023-05-01 00:22:02 +02:00
mrbesen d375c590f8
auto sync runes 2023-04-30 22:10:21 +02:00
mrbesen 41de5a8652
fix id alignment problem when deleting aa runes 2023-04-30 16:26:46 +02:00
mrbesen 67d5614be3
restoring runepages 2023-04-30 16:20:13 +02:00
mrbesen 4aa35ff4e1
storing runepages 2023-04-23 23:54:09 +02:00
mrbesen ef36280894
rune manager preperations 2023-04-23 19:13:49 +02:00
mrbesen 6ffdf23085
make LolAutoAccept a QObject 2023-02-26 13:50:57 +01:00
mrbesen 5f3ff9e292
fix endsWith bug 2023-02-26 12:37:30 +01:00
mrbesen e67c18f2ce
update version and changelog 2023-02-05 13:53:26 +01:00
mrbesen 0aca8969f9
change behavior of autowrite: only write re-write on rising edge 2023-02-05 13:47:09 +01:00
mrbesen aa982d3798
automatically embed translations with qmake and cleanup of pro file 2023-02-05 13:46:26 +01:00
mrbesen dd125d3183
adjust stagesettings ui; fix Enable Pick / Ban Button 2023-01-22 23:46:03 +01:00
mrbesen 152873d860
auto save configuration 2022-12-16 23:09:24 +01:00
mrbesen 5fc50a524c
add tab icons 2022-12-16 22:44:10 +01:00
mrbesen 3f1c264818
use new logger version with qt support 2022-10-26 22:01:03 +02:00
mrbesen 49bdc60071
changelog 2022-10-11 20:22:49 +02:00
mrbesen bc6f80c187
update language 2022-10-10 21:40:36 +02:00
mrbesen ec4566f76a
copyright label as single label 2022-10-10 21:38:35 +02:00
mrbesen 7cd1795155
add version, copyright info, link to release page, update translation 2022-10-08 18:38:01 +02:00
mrbesen 53d642f507
fix the not working checkboxes 2022-10-02 23:46:11 +02:00
mrbesen dd01c70230
fixed default page 2022-09-21 16:50:06 +02:00
mrbesen af63c975c7
autowrite implemented 2022-09-21 13:47:08 +02:00
mrbesen 7c96b8b188
update lolautoaccept.desktop 2022-09-12 23:41:29 +02:00
mrbesen 6365ef1d6f
refresh on reorder 2022-09-12 21:39:50 +02:00
Oliver d0d4638ca9 fix(debian): lintian warnings (#2)
Reviewed-on: #2
Co-authored-by: Oliver <okaestne@gitea.mrbesen.de>
Co-committed-by: Oliver <okaestne@gitea.mrbesen.de>
2022-09-11 22:08:54 +02:00
MrBesen 24cd4f71a3 Merge pull request 'debian packaging' (#1) from okaestne/lolautoaccept:debian-pkg into master
Reviewed-on: #1
2022-09-06 00:05:56 +02:00
Oliver d27c4c9371
debian packaging 2022-09-05 23:36:27 +02:00
mrbesen 47504fdd41
remove unnecessary check 2022-09-05 17:14:16 +02:00
mrbesen d178716463
updated Log version 2022-09-05 17:06:10 +02:00
mrbesen ea1d72514c
add ico file for exe 2022-09-05 16:41:55 +02:00
mrbesen 9c60193f24
convert the text to polygon 2022-09-05 16:27:14 +02:00
mrbesen 6ab09c8caf
add qrc_res.cpp to gitignore 2022-09-05 15:50:01 +02:00
mrbesen 4618a20a33
updated translations 2022-09-05 15:49:43 +02:00
mrbesen 7789e7cfbb
use resource file for translation and icon 2022-09-05 15:33:40 +02:00
mrbesen a6f391cc4b disbale certchecks on windows (for now) 2022-08-28 11:32:54 +02:00
mrbesen 226c031ecf
Tooltip for champrows 2022-08-28 01:43:14 +02:00
mrbesen 71853c65d6
use rsvg-convert instead of inkscape 2022-08-28 00:28:51 +02:00
mrbesen 738364aa13
update translation 2022-08-28 00:28:29 +02:00
MrBesen 94020e5a04 add smitewarn checkbox to ui 2022-08-26 13:47:21 +02:00
MrBesen 58795ad66e smite warning 2022-08-26 02:12:25 +02:00
MrBesen 0d43798a1c
fix client access for linux 2022-08-24 19:24:18 +02:00
MrBesen c8341ef491 reduce logging 2022-08-24 16:28:15 +02:00
MrBesen 5bb58008c7 windows compatability 2022-08-24 16:12:03 +02:00
mrbesen 4fae1740e9
fixed compile error and warning 2022-08-20 21:46:44 +02:00
Oliver 03e4017e7f
mimimi
Co-authored-by: MrBesen <mrbesen@users.noreply.github.com>
2022-08-20 21:42:30 +02:00
mrbesen ffc83a175f
remove getLog(), add GameSelectPhase "GAME_STARTING" 2022-08-18 13:02:37 +02:00
mrbesen 65b91f8d5a
detecting closed client 2022-07-29 00:05:22 +02:00
mrbesen 97a822746e
datadragon: more logging, do not refresh cache when it was read 2022-07-17 01:22:31 +02:00
mrbesen bb4555585e
remove unsed h file 2022-07-17 01:17:10 +02:00
mrbesen e316b2bfe0
remove imgs 2022-07-17 00:58:06 +02:00
mrbesen 6d931be725
added default values 2022-07-17 00:49:07 +02:00
mrbesen 716a2e4067
allow for a default position 2022-07-17 00:21:15 +02:00
mrbesen ece3d162e8
more logging in blitzAPI 2022-07-17 00:20:02 +02:00
mrbesen c2203b4b5d
resolve runestyle names 2022-07-10 15:56:09 +02:00
mrbesen 1cb3134c8e
refactor 2022-07-10 15:19:25 +02:00
mrbesen 4716e48cbf
RunePage class added and refactored; find matching runepages 2022-07-10 14:32:44 +02:00
mrbesen c03b123af0
applying runes working 2022-07-10 01:51:42 +02:00
mrbesen 4bcdf80fab
debug-log option in debugging 2022-07-10 01:51:22 +02:00
mrbesen d8e8e1c459
fix currentcounter in stage, show runes 2022-07-09 16:23:09 +02:00
mrbesen d3379ab794
wip blitzapi, load runes, set runes 2022-07-09 01:01:51 +02:00
mrbesen e893852bc1
fixed memleak and uninitilized value 2022-07-05 23:56:09 +02:00
mrbesen e2f04637f2
cache champdata 2022-07-05 23:45:28 +02:00
mrbesen 5dd822d332
prefetch images 2022-07-05 19:39:16 +02:00
mrbesen d9ad8cb464
removed some logging 2022-07-05 17:49:55 +02:00
mrbesen abbbaf00aa
updated translation abd fixed typo 2022-07-05 17:49:45 +02:00
mrbesen a4312e1570
update Readme 2022-07-04 23:23:38 +02:00
mrbesen bce9dfb793
debug build 2022-07-04 23:23:20 +02:00
mrbesen 94d2869c51
multiple tabs working 2022-07-04 22:59:48 +02:00
mrbesen 68c7ce92d9
new config working 2022-07-04 17:50:18 +02:00
mrbesen 0276f83f3f
new ui stuff wip 2022-07-04 00:31:41 +02:00
mrbesen 1cc36ae0ee
get QStrings 2022-07-03 19:05:22 +02:00
111 changed files with 6423 additions and 1222 deletions

7
.gitignore vendored
View File

@ -25,4 +25,11 @@ Makefile
AppDir/ AppDir/
*.AppImage *.AppImage
qrc_res.cpp
debian/*
!debian/source
!debian/changelog
!debian/control
!debian/lolautoaccept.install
!debian/rules

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "thirdparty/Log"] [submodule "thirdparty/Log"]
path = thirdparty/Log path = thirdparty/Log
url = https://git.okaestne.de/okaestne/Log.git url = https://git.mrbesen.de/MrBesen/Log.git

2
.vscode/launch.json vendored
View File

@ -30,7 +30,7 @@
"type": "cppdbg", "type": "cppdbg",
"request": "launch", "request": "launch",
"program": "${workspaceFolder}/lolautoaccept", "program": "${workspaceFolder}/lolautoaccept",
"args": [], "args": ["--debug-log"],
"stopAtEntry": false, "stopAtEntry": false,
"cwd": "${workspaceFolder}", "cwd": "${workspaceFolder}",
"environment": [], "environment": [],

View File

@ -2,8 +2,8 @@
This is a tool, that tries to automatically accept a league of legends game. This is a tool, that tries to automatically accept a league of legends game.
It is developed on linux and there is no effort to port it to windows. It is developed on linux and there is no effort to port it to windows.
It works by taking a screenshot every second, analysing it using opencv and clicking on `accept` using XInputSimulator. It works by accessing the launcher using the LCU-API more about that api can be found [here](http://www.mingweisamuel.com/lcu-schema/tool/).
It has problems when the champion you want to play is banned or picked by someone else. I only tested this in normals and ranked. (No Aram, Custom, URF, ....) I only tested this in normals and ranked (No Aram, Custom, URF, ....) but you may want to give it a try.
## Prebuilt Binary ## Prebuilt Binary
There is a prebuild AppImage that should work on every linux x86_64 computer. There is a prebuild AppImage that should work on every linux x86_64 computer.
@ -13,16 +13,9 @@ There is a prebuild AppImage that should work on every linux x86_64 computer.
## Dependencies ## Dependencies
- Qt5 - Qt5
- opencv4 - libcurl
- X11
- [XInputSimulator](https://github.com/a3f/XInputSimulator.git) (is a submodule)
- [Log](https://git.okaestne.de/okaestne/Log) (is a submodule) - [Log](https://git.okaestne.de/okaestne/Log) (is a submodule)
## Notes
* The Program should be able to detect the buttons no matter the client-language. But it was heavily tested with _german_.
* The program has troubles detecting the button, when the mouse is hovering it. (Dont hover the launcher with your mouse.)
* The launcher should be in 1280x720 (It might work with other resolutions, but they are not tested and are known to have potential problems).
* Needs X11 and probably does not work with wayland (ubuntu). But i did not try that.
## Compile ## Compile
Be sure to clone with submodules: Be sure to clone with submodules:
@ -31,8 +24,6 @@ git clone --recurse-submodules https://git.mrbesen.de/MrBesen/lolautoaccept.git
``` ```
Then in its root folder: Then in its root folder:
``` ```
./makeXInputSimulator.sh
qmake qmake
# build the appimage # build the appimage

35
debian/changelog vendored Normal file
View File

@ -0,0 +1,35 @@
lolautoaccept (0.0.8) focal; urgency=medium
* add Rune Tab to manage runes
* add RuneEditor to edit runes
* add Loading window
* fix Bug that caused to many processes to be inspected for Lol-Client informations
* add --access flag for debugging and testing
* add Dodge Button
-- MrBesen <> Sat, 02 Sep 2023 16:28:31 +0200
lolautoaccept (0.0.7) unstable; urgency=medium
* add tab icons
* embed translations with qt
* adjust stagesettings ui
* add autosave for configurations
* change to log-version with qt-support
-- MrBesen <> Sun, 05 Feb 2023 13:48:39 +0100
lolautoaccept (0.0.6) unstable; urgency=medium
[ MrBesen Tue, 11 Oct 2022 20:17:00 +0200 ]
* fix the default tab
* fix the `enable pick` and `enable ban` buttons
* add autowrite - automatically send a text message in the champselect lobby
-- MrBesen <> Tue, 11 Oct 2022 20:17:54 +0200
lolautoaccept (0.0.5) unstable; urgency=medium
* Initial release.
-- Oliver <git@oliver-kaestner.de> Mon, 05 Sep 2022 01:42:50 +0200

17
debian/control vendored Normal file
View File

@ -0,0 +1,17 @@
Source: lolautoaccept
Section: games
Priority: optional
Maintainer: MrBesen <mrbesen@mrbesen.de>
Build-Depends: debhelper-compat (= 12),
libcurl4-openssl-dev,
librsvg2-bin,
qtbase5-dev,
qttools5-dev-tools
Standards-Version: 4.5.0
Package: lolautoaccept
Architecture: any
Depends: ${misc:Depends},
${shlibs:Depends},
Description: League of Legends Auto-Acceptor
Can accept games, pick and ban champions and more.

3
debian/lolautoaccept.install vendored Normal file
View File

@ -0,0 +1,3 @@
lolautoaccept usr/bin/
resources/lolautoaccept.desktop usr/share/applications/
resources/lolautoaccept.svg usr/share/icons/hicolor/scalable/apps/

4
debian/rules vendored Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/make -f
%:
dh $@

1
debian/source/format vendored Normal file
View File

@ -0,0 +1 @@
3.0 (native)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -2,6 +2,7 @@
struct Args { struct Args {
int debugLog = 0; // cast to bool later int debugLog = 0; // cast to bool later
int access = 0;
}; };
Args parseArgs(int argc, char** argv); Args parseArgs(int argc, char** argv);

30
include/blitzapi.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <vector>
#include <QJsonObject>
#include "restclient.h"
#include "runepage.h"
#include "position.h"
class BlitzAPI : public RestClient {
Q_OBJECT
public:
BlitzAPI();
struct ChampionInfo {
std::vector<uint32_t> skillorder;
RunePage runepage;
// items?
ChampionInfo();
explicit ChampionInfo(const QJsonObject&);
};
ChampionInfo getChampionInfo(uint32_t championID, Position p, uint32_t enemyChampionID = 0); // TODO: add more parameters: Queue (Ranked 5x5)
private:
};

19
include/champcache.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <string>
#include <QJsonDocument>
// This file caches the champion metadata
class ChampCache {
public:
ChampCache();
QString getVersion();
QJsonDocument getChamps();
void saveChamps(QJsonDocument doc, const QString& version);
private:
QString basefolder;
uint64_t maxage = 86400; // is in seconds
};

View File

@ -10,7 +10,7 @@ public:
explicit ChampRow(QListWidget *parent = nullptr); explicit ChampRow(QListWidget *parent = nullptr);
~ChampRow(); ~ChampRow();
void setChamp(const std::string& name, uint32_t id, QPixmap icon); void setChamp(const DataDragon::ChampData& cd, QPixmap icon);
QString getChamp() const; QString getChamp() const;
uint32_t getChampID() const; uint32_t getChampID() const;
QPixmap getIcon(); QPixmap getIcon();

View File

@ -1,28 +1,26 @@
#pragma once #pragma once
#include <string> #include <istream>
#include <memory> #include <memory>
#include <vector>
#include <QString>
class ClientAccess { class ClientAccess {
ClientAccess(); ClientAccess();
ClientAccess(const std::string& token, uint16_t port);
public: public:
static std::shared_ptr<ClientAccess> find(bool uselockfile = true); ClientAccess(const QString& token, uint16_t port);
private: static std::shared_ptr<ClientAccess> find();
// returns the name and value of a argument or empty string if it could not be parsed
static std::string parseArg(const std::string& input, std::string& value);
static std::shared_ptr<ClientAccess> findUsingArgs(const std::vector<std::string>& cmdline);
static std::shared_ptr<ClientAccess> findUsingLockfile(const std::vector<std::string>& cmdline, pid_t pid);
public: public:
std::string getBasicAuth() const; QString getBasicAuth() const;
uint16_t getPort() const; uint16_t getPort() const;
std::string getURL() const; QString getURL() const;
private: private:
std::string authcode; QString authcode;
uint16_t port = 0; uint16_t port = 0;
}; };
std::shared_ptr<ClientAccess> createFromLockfile(std::istream& lockfile);

View File

@ -1,9 +1,17 @@
#pragma once #pragma once
#include "clientaccess.h" #include "clientaccess.h"
#include "datadragonimagecache.h"
#include "memoryimagecache.h"
#include "position.h"
#include "restclient.h" #include "restclient.h"
#include "runeaspekt.h"
#include "runepage.h"
#include "runestyle.h"
class ClientAPI : public RestClient { class ClientAPI : public RestClient {
Q_OBJECT
public: public:
enum class ReadyCheckState : uint32_t { enum class ReadyCheckState : uint32_t {
INVALID = 0, INVALID = 0,
@ -31,25 +39,16 @@ public:
ENDOFGAME, ENDOFGAME,
TERMINATEDINERROR, TERMINATEDINERROR,
}; };
static GameflowPhase toGameflowPhase(const std::string&); static GameflowPhase toGameflowPhase(const QString&);
enum class ChampSelectPhase : uint32_t { enum class ChampSelectPhase : uint32_t {
INVALID = 0, INVALID = 0,
GAME_STARTING,
PLANNING, PLANNING,
BAN_PICK, BAN_PICK,
FINALIZATION FINALIZATION
}; };
static ChampSelectPhase toChampSelectPhase(const std::string& str); static ChampSelectPhase toChampSelectPhase(const QString& str);
enum class Position : uint32_t {
INVALID = 0,
TOP,
MIDDLE,
BOTTOM,
JUNGLE,
UTILITY
};
static Position toPosition(const std::string& str);
enum class ChampSelectActionType : uint32_t { enum class ChampSelectActionType : uint32_t {
INVALID = 0, INVALID = 0,
@ -57,14 +56,14 @@ public:
PICK, PICK,
TEN_BANS_REVEAL, TEN_BANS_REVEAL,
}; };
static ChampSelectActionType toChampSelectActionType(const std::string& str); static ChampSelectActionType toChampSelectActionType(const QString& str);
struct TimerInfo { struct TimerInfo {
int64_t adjustedTimeLeftInPhase; int64_t adjustedTimeLeftInPhase = 0;
int64_t internalNowInEpochMs; int64_t internalNowInEpochMs = 0;
bool isefinite; bool isefinite = false;
ChampSelectPhase phase; ChampSelectPhase phase = ChampSelectPhase::INVALID;
int64_t totalTimeInPhase; int64_t totalTimeInPhase = 0;
bool valid = false; bool valid = false;
@ -74,12 +73,12 @@ public:
struct PlayerInfo { struct PlayerInfo {
int64_t summonerid = 0; // to test validity -> test if this is not null int64_t summonerid = 0; // to test validity -> test if this is not null
std::string gameName; QString gameName;
std::string name; QString name;
std::string statusMessage; QString statusMessage;
// lol specific // lol specific
std::string puuid; QString puuid;
uint32_t level = 0; uint32_t level = 0;
}; };
@ -97,11 +96,13 @@ public:
}; };
struct ChampSelectCell { struct ChampSelectCell {
Position position; Position position = Position::INVALID;
int32_t cellID = 0; int32_t cellID = 0;
int32_t championID = 0; int32_t championID = 0;
int32_t championPickIntentID = 0; int32_t championPickIntentID = 0;
int64_t summonerID = 0; int64_t summonerID = 0;
int64_t spell1Id = 0; // 4 = flash, 6 = ghost, 7 = heal, 11 = smite, 12 = teleport, 13 = klarheitz, 14 = ignite, 32 = snowball
int64_t spell2Id = 0;
ChampSelectCell(); ChampSelectCell();
explicit ChampSelectCell(const QJsonObject& json); explicit ChampSelectCell(const QJsonObject& json);
@ -137,6 +138,53 @@ public:
operator bool(); operator bool();
}; };
struct RunePage {
uint64_t id = 0;
uint64_t lastmodified = 0;
QString name;
bool isDeleteable = true;
bool isEditable = true;
bool isActive = false; // what is the difference between active and current????
bool isCurrent = false;
bool isValid = true;
uint32_t order = 0; // position in the ui
::RunePage runepage;
RunePage();
explicit RunePage(const QJsonObject& json);
};
struct Message {
QString body;
QString fromId;
QString fromPid;
int64_t fromSummonerId;
QString id;
bool isHistorical;
QString timestamp;
QString type; // known types: chat (1:1), customGame, championSelect, groupchat
Message();
explicit Message(const QJsonObject& json);
};
struct Conversation {
QString gameName;
QString gameTag;
QString id;
bool isMuted;
std::shared_ptr<Message> lastMessage;
QString name;
QString password;
QString pid;
QString targetRegion;
QString type;
int64_t unreadMessageCount;
Conversation();
explicit Conversation(const QJsonObject& json);
};
ClientAPI(const ClientAccess& access); ClientAPI(const ClientAccess& access);
~ClientAPI(); ~ClientAPI();
@ -147,23 +195,46 @@ public:
ChampSelectSession getChampSelectSession(); ChampSelectSession getChampSelectSession();
bool setChampSelectAction(int32_t actionid, int32_t champid, bool completed); bool setChampSelectAction(int32_t actionid, int32_t champid, bool completed);
PlayerInfo getSelf(); PlayerInfo getSelf();
std::vector<std::string> getLog(); void dodge();
std::vector<int32_t> getBannableChampIDs(); std::vector<int32_t> getBannableChampIDs();
std::vector<int32_t> getPickableChampIDs(); std::vector<int32_t> getPickableChampIDs();
TimerInfo getTimerInfo(); TimerInfo getTimerInfo();
protected: // chats
std::vector<Conversation> getAllConversations();
Message sendMessage(const QString& chatid, const QString& messagebody);
// rune stuff
RunePage getCurrentRunePage();
std::vector<RunePage> getAllRunePages();
bool selectRunePage(uint64_t id);
bool editRunePage(const RunePage& page);
bool createRunePage(const RunePage& page);
bool deleteRunePage(uint64_t id);
std::vector<RuneAspekt> getAllRuneAspekts();
std::vector<RuneStyle> getAllRuneStyles();
const QString& getRuneStyleByID(uint32_t id);
QPixmap getImageResource(QString path);
private: private:
ClientAccess access; ClientAccess access;
MemoryImageCache memImageCache;
DataDragonImageCache imageCache;
}; };
std::ostream& operator<<(std::ostream&, const ClientAPI::ReadyCheckState&); #define DEFINEOPERATOR(CLASS) \
std::ostream& operator<<(std::ostream&, const ClientAPI::GameflowPhase&); std::ostream& operator<<(std::ostream&, const ClientAPI::CLASS&); \
std::ostream& operator<<(std::ostream&, const ClientAPI::ChampSelectPhase&); QDebug operator<<(QDebug, const ClientAPI::CLASS&);
std::ostream& operator<<(std::ostream&, const ClientAPI::Position&);
std::ostream& operator<<(std::ostream&, const ClientAPI::ChampSelectActionType&); DEFINEOPERATOR(ReadyCheckState)
DEFINEOPERATOR(GameflowPhase)
DEFINEOPERATOR(ChampSelectPhase)
DEFINEOPERATOR(ChampSelectActionType)
#undef DEFINEOPERATOR

33
include/clipboardpopup.h Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#include <QDialog>
namespace Ui {
class ClipboardPopup;
}
class ClipboardPopup : public QDialog {
Q_OBJECT
public:
enum class Direction {
Paste,
Copy
};
explicit ClipboardPopup(Direction dir, QWidget* parent = nullptr);
~ClipboardPopup();
void setText(QString text);
QString getText() const;
private slots:
void textPasted();
void copyButton();
private:
Ui::ClipboardPopup *ui;
Direction direction;
int lastKnownTextSize = 0;
};

View File

@ -1,7 +1,11 @@
#pragma once #pragma once
#include <memory>
#include <QJsonObject> #include <QJsonObject>
#include "position.h"
#include "runepage.h"
class Config { class Config {
public: public:
struct StageConfig { struct StageConfig {
@ -9,8 +13,38 @@ public:
StageConfig(const QJsonObject&); StageConfig(const QJsonObject&);
operator QJsonObject() const; operator QJsonObject() const;
std::vector<std::string> champs; std::vector<QString> champs;
bool enabled; bool enabled = false;
};
struct PositionConfig {
PositionConfig();
PositionConfig(const QJsonObject&);
operator QJsonObject() const;
Position position; // top, bot, sup,...
StageConfig ban;
StageConfig pick;
};
struct RunePageConfig {
RunePageConfig();
RunePageConfig(QString name, const RunePage& rp);
RunePageConfig(const QJsonObject&);
operator QJsonObject() const;
QString name;
RunePage runepage;
};
struct GeneralRunePageConfig {
GeneralRunePageConfig();
GeneralRunePageConfig(const QJsonObject&);
operator QJsonObject() const;
bool autoSync;
std::vector<std::shared_ptr<RunePageConfig>> runePages;
}; };
struct RootConfig { struct RootConfig {
@ -18,10 +52,15 @@ public:
RootConfig(const QJsonObject&); RootConfig(const QJsonObject&);
operator QJsonObject() const; operator QJsonObject() const;
StageConfig prepick; std::shared_ptr<Config::PositionConfig> getPositionConfig(Position position);
StageConfig ban;
StageConfig pick; std::vector<std::shared_ptr<PositionConfig>> positionConfigs;
GeneralRunePageConfig runepagesConfig;
bool enabledAutoAccept; bool enabledAutoAccept;
bool enabledSmiteWarn;
bool enabledAutoWrite;
QString autoWriteText;
}; };
Config(); Config();
@ -32,7 +71,7 @@ public:
RootConfig& getConfig(); RootConfig& getConfig();
private: private:
std::string configFolderPath; QString configFolderPath;
std::string configFilePath; QString configFilePath;
RootConfig root; RootConfig root;
}; };

View File

@ -3,21 +3,26 @@
#include <condition_variable> #include <condition_variable>
#include <functional> #include <functional>
#include <mutex> #include <mutex>
#include <string> #include <set>
#include <thread> #include <QString>
#include <vector> #include <vector>
#include <QPixmap> #include <QPixmap>
#include "datadragonimagecache.h" #include "datadragonimagecache.h"
#include "champcache.h"
#include "memoryimagecache.h" #include "memoryimagecache.h"
#include "restclient.h" #include "restclient.h"
class QThread;
class DataDragon : public RestClient { class DataDragon : public RestClient {
Q_OBJECT
public: public:
using notifyImgfunc_t = std::function<void(QPixmap)>; using notifyImgfunc_t = std::function<void(QPixmap)>;
DataDragon(const std::string& locale); DataDragon(const QString& locale);
~DataDragon(); ~DataDragon();
DataDragon(const DataDragon&) = delete; DataDragon(const DataDragon&) = delete;
DataDragon& operator=(const DataDragon&) = delete; DataDragon& operator=(const DataDragon&) = delete;
@ -27,11 +32,11 @@ public:
ChampData(); ChampData();
ChampData(const QJsonObject& source); ChampData(const QJsonObject& source);
std::string name; QString name;
std::string id; QString id;
int key; int key = 0;
std::string partype; QString partype;
std::string title; QString title;
}; };
enum class ImageType { enum class ImageType {
@ -41,50 +46,68 @@ public:
}; };
// might block until version is available // might block until version is available
const std::string& getVersion(); const QString& getVersion();
// might block until champ data is available // might block until champ data is available
const std::vector<ChampData>& getChamps(); const std::vector<ChampData>& getChamps();
// might block until image is downloaded // might block until image is downloaded
QPixmap getImage(const std::string& champid, ImageType imgtype = ImageType::SQUARE); QPixmap getImage(const QString& champid, ImageType imgtype = ImageType::SQUARE, bool writeMemcache = true);
void getImageAsnyc(const std::string& champid, notifyImgfunc_t func, ImageType imgtype = ImageType::SQUARE); void getImageAsnyc(const QString& champid, notifyImgfunc_t func, ImageType imgtype = ImageType::SQUARE);
// might block until champ data is available // might block until champ data is available
const ChampData& getBestMatchingChamp(const std::string& name, int* count = nullptr); const ChampData& getBestMatchingChamp(const QString& name, int* count = nullptr);
std::vector<const ChampData*> getMatchingChamp(const std::string& name, uint32_t limit = 25); std::vector<const ChampData*> getMatchingChamp(const QString& name, uint32_t limit = 25);
const ChampData* getChampByID(uint32_t id);
std::vector<uint32_t> resolveChampIDs(const std::vector<QString>& champnames);
void startThread();
static const ChampData EMPTYCHAMP; static const ChampData EMPTYCHAMP;
protected:
std::string getImageUrl(const std::string& champid, ImageType type);
std::string getCDNString() const;
public slots:
void stop();
signals:
// loading progress in 0.0 - 1.0
void loading(float);
// which champion is currently prefretched
void fetchingChamp(QString);
protected:
QString getImageUrl(const QString& champid, ImageType type);
QString getCDNString() const;
void prefetchChampImage(const QString& champid, ImageType imgtype = ImageType::SQUARE);
void getVersionInternal(); void getVersionInternal();
void getChampsInternal(); void getChampsInternal();
void startThread();
void stopThread(); void stopThread();
void stopAndJoinThread(); void stopAndJoinThread();
void threadLoop(); void threadLoop();
std::string locale; QString locale;
std::string version; QString version;
std::vector<ChampData> champs; std::vector<ChampData> champs;
std::mutex cachedatamutex; std::mutex cachedatamutex;
std::condition_variable cachedatacv; std::condition_variable cachedatacv;
std::set<QString> notDownloadedImages; // the champions of which the square image is not downloaded yet. Is used to download them on idle
private: private:
struct Task { struct Task {
std::string champid; QString champid;
notifyImgfunc_t func; notifyImgfunc_t func;
ImageType type; ImageType type;
}; };
DataDragonImageCache cache[3]; DataDragonImageCache cache[3];
ChampCache champCache;
MemoryImageCache memcache; MemoryImageCache memcache;
std::list<Task> tasks; std::list<Task> tasks;
std::mutex tasksmutex; std::mutex tasksmutex;
std::condition_variable tasksnotemptycv; std::condition_variable tasksnotemptycv;
std::thread bgthread; QThread* bgthread;
bool shouldrun = true; bool shouldrun = true;
}; };
std::ostream& operator<<(std::ostream& str, const DataDragon::ChampData& cd); std::ostream& operator<<(std::ostream& str, const DataDragon::ChampData& cd);

View File

@ -1,19 +1,20 @@
#pragma once #pragma once
#include <string> #include <QString>
#include <QByteArray> #include <QByteArray>
#include <QPixmap> #include <QPixmap>
class DataDragonImageCache { class DataDragonImageCache {
public: public:
DataDragonImageCache(const std::string& folderextra, const std::string& imageext = ".jpg"); DataDragonImageCache(const QString& folderextra, const QString& imageext = ".jpg");
~DataDragonImageCache(); ~DataDragonImageCache();
QPixmap getImage(const std::string& name); bool hasImage(const QString& name);
void addImageRaw(const QByteArray& arr, const std::string& name); QPixmap getImage(const QString& name);
void addImageRaw(const QByteArray& arr, const QString& name);
private: private:
std::string getFilepath(const std::string& name) const; QString getFilepath(const QString& name) const;
std::string cacheDir; QString cacheDir;
std::string imageext; // file extention including dot QString imageext; // file extention including dot
}; };

16
include/defer.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
// from: https://www.gingerbill.org/article/2015/08/19/defer-in-cpp/
#include <functional>
struct privDefer {
std::function<void()> f;
privDefer(std::function<void()> f) : f(f) {}
~privDefer() { f(); }
};
#define DEFER_1(x, y) x##y
#define DEFER_2(x, y) DEFER_1(x, y)
#define DEFER_3(x) DEFER_2(x, __COUNTER__)
#define defer(code) auto DEFER_3(_defer_) = privDefer([&](){code;})

View File

@ -3,9 +3,13 @@
// stuff required for file handling // stuff required for file handling
#include <string> #include <string>
#include <QString>
// create a directory and its parents // create a directory and its parents
bool mkdirs(const std::string& path); bool mkdirs(const QString& path);
// get $HOME or a useful default value // get $HOME or a useful default value
std::string getHome(); QString getHome();
// folder for caching example: $HOME/.cache/lolautoaccept/
QString getCache();

View File

@ -12,15 +12,24 @@ T convert(const QJsonValue& val) {
template<> template<>
int convert(const QJsonValue& val); int convert(const QJsonValue& val);
template<>
uint32_t convert(const QJsonValue& val);
template<> template<>
int64_t convert(const QJsonValue& val); int64_t convert(const QJsonValue& val);
template<> template<>
std::string convert(const QJsonValue& val); uint64_t convert(const QJsonValue& val);
template<>
QString convert(const QJsonValue& val);
template<> template<>
bool convert(const QJsonValue& val); bool convert(const QJsonValue& val);
template<>
QString convert(const QJsonValue& val);
template<typename T> template<typename T>
T getValue(const QJsonObject& obj, const char* key, const T& def = {}) { T getValue(const QJsonObject& obj, const char* key, const T& def = {}) {
auto it = obj.constFind(key); auto it = obj.constFind(key);

36
include/loadingwindow.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef LOADINGWINDOW_H
#define LOADINGWINDOW_H
#include <QWidget>
class QCloseEvent;
namespace Ui {
class LoadingWindow;
}
class LoadingWindow : public QWidget {
Q_OBJECT
public:
explicit LoadingWindow( QWidget* parent = nullptr );
~LoadingWindow();
public slots:
void setChampion(QString championName);
void setText(QString text);
// should be 0.0 to 1.0
void setProgress(float val);
signals:
void closed();
protected:
virtual void closeEvent(QCloseEvent*) override;
private:
Ui::LoadingWindow* ui;
};
#endif // LOADINGWINDOW_H

View File

@ -2,10 +2,20 @@
#include <thread> #include <thread>
#include <memory> #include <memory>
#include <mutex>
#include <QObject>
#include "blitzapi.h"
#include "clientapi.h" #include "clientapi.h"
#include "config.h"
#include "datadragon.h"
#include "runepage.h"
#include "runestyle.h"
class LolAutoAccept : public QObject {
Q_OBJECT
class LolAutoAccept {
protected: protected:
struct Stage { struct Stage {
Stage(); Stage();
@ -16,43 +26,89 @@ protected:
uint32_t currentOffset = 0; uint32_t currentOffset = 0;
}; };
std::vector<Stage*> stages; std::mutex stagesMutex; // protects stagesvector
std::vector<Stage> stages;
Position currentPosition = Position::INVALID;
bool currentPositionSet = false;
uint32_t lastPickedChamp = 0;
int32_t lastCellId = -1; // last cellid -> if changed -> reset
Config::RootConfig& config;
DataDragon& dd;
bool shouldrun = false; bool shouldrun = false;
std::thread lolaathread; std::thread lolaathread;
std::shared_ptr<ClientAPI> clientapi; std::shared_ptr<ClientAPI> clientapi;
int64_t summonerid = -1; BlitzAPI blitzapi;
std::vector<RuneAspekt> runeaspekts;
std::vector<RuneStyle> runestyles;
ClientAPI::GameflowPhase lastPhase;
bool dodgeNow = false;
bool nextApplyRunes = false;
bool smiteWarnEnabled = true;
bool autoWriteTextEnabled = false;
bool autoWriteTextDone = false;
QString autoWriteText;
QString chatid; // the chatid of the chat from the champselect
std::chrono::time_point<std::chrono::system_clock> lastMessageSent;
public: public:
enum class State { enum class State {
LOBBY = 0, LOBBY = 0,
PREPICK = 1, BAN = 1,
BAN = 2, PICK = 2,
PICK = 3,
GAME = 4
}; };
LolAutoAccept(); enum class Status {
~LolAutoAccept(); Off,
Running,
Failed
};
Q_ENUM(Status)
LolAutoAccept(Config::RootConfig& config, DataDragon& dd, QObject* parent = nullptr);
virtual ~LolAutoAccept();
void setChamps(const std::vector<uint32_t>& champs, State s); void setChamps(const std::vector<uint32_t>& champs, State s);
void setEnabled(bool b, State s); void setEnabled(bool b, State s);
void setSmiteWarn(bool b);
bool init(); // returns true on success bool init(); // returns true on success
void run(); void run();
void stop(); void stop();
Status getStatus();
void reload(); // reload the config, when something was changed
const std::vector<RuneAspekt>& getRuneAspekts();
const std::vector<RuneStyle>& getRuneStyles();
void setAutoWriteText(bool enabled, const QString& text = {});
public slots:
void dodge();
signals:
void statusChanged(LolAutoAccept::Status); // new status: 0 = off, 1 = on, 2 = failed
void positionChanged(Position);
void dodgePossible(bool); // true = the dodge button is available
private: private:
void stopJoinThread(); void stopJoinThread();
void innerRun(); void innerRun();
void resetPickOffsets();
void resetAllOffsets(); void resetAllOffsets();
void applyConfigToStage(Stage& stage, const Config::StageConfig& stageconf);
void loadPosition(Position pos);
uint32_t getChampOfState(State s); uint32_t getChampOfState(State s);
void nextChampOfState(State s); void nextChampOfState(State s);
static bool isChampIntentofTeammate(uint32_t champid, const ClientAPI::ChampSelectSession& session); static bool isChampIntentofTeammate(uint32_t champid, const ClientAPI::ChampSelectSession& session);
static bool isChampBanned(uint32_t champid, const ClientAPI::ChampSelectSession& session);
using ownactions_t = std::vector<ClientAPI::ChampSelectAction>; using ownactions_t = std::vector<ClientAPI::ChampSelectAction>;
ownactions_t getOwnActions(int32_t cellid, const std::vector<ClientAPI::ChampSelectAction> actions); ownactions_t getOwnActions(int32_t cellid, const std::vector<ClientAPI::ChampSelectAction> actions);
@ -60,5 +116,11 @@ private:
void banPhase(const ownactions_t& ownactions, const ClientAPI::ChampSelectSession& session); void banPhase(const ownactions_t& ownactions, const ClientAPI::ChampSelectSession& session);
void pickPhase(const ownactions_t& ownactions); void pickPhase(const ownactions_t& ownactions);
void phase(const ownactions_t& ownactions, ClientAPI::ChampSelectActionType type, State s, bool complete, std::function<bool(uint32_t)> filter = {}); void phase(const ownactions_t& ownactions, ClientAPI::ChampSelectActionType type, State s, bool complete, std::function<bool(uint32_t)> filter = {});
static int32_t getBestRunePage(const std::vector<ClientAPI::RunePage>& allpages);
static int32_t getMatchingRunePage(const RunePage& rp, const std::vector<ClientAPI::RunePage>& allpages);
void champSelect(); void champSelect();
}; void smiteWarning(const std::vector<ClientAPI::ChampSelectCell>& cells);
const QString& getChatid();
};
Q_DECLARE_METATYPE(LolAutoAccept::Status)

View File

@ -12,36 +12,61 @@ QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; } namespace Ui { class MainWindow; }
QT_END_NAMESPACE QT_END_NAMESPACE
class QMessageBox;
class QTimer;
class LoadingWindow;
class X11Helper;
class MainWindow : public QMainWindow { class MainWindow : public QMainWindow {
Q_OBJECT Q_OBJECT
public: public:
MainWindow(LolAutoAccept& lolaa, QWidget *parent = nullptr); MainWindow(QWidget *parent = nullptr);
~MainWindow(); ~MainWindow();
protected: protected:
virtual void closeEvent(QCloseEvent* event) override; virtual void closeEvent(QCloseEvent* event) override;
virtual void resizeEvent(QResizeEvent *event) override;
signals:
void requestTabChange(int tabindex);
public slots:
void resetSaveTimer();
private slots: private slots:
void loadingStatus(float);
void toggleLeagueVisibility();
void toggleMainswitch(bool); void toggleMainswitch(bool);
void aatoggled(bool); void aatoggled(bool);
void pptoggled(bool); void smitewarntoggled(bool);
void ppedited();
void bantoggled(bool); void tabtoggled(Position, LolAutoAccept::State, bool);
void banedited(); void tabchanged(Position, LolAutoAccept::State);
void picktoggled(bool);
void pickedited(); void autoWriteChanged();
void saveConfig();
void initDone();
// returns empty string on no match
void onPosChange(Position newpos); // to trigger the signal from a QObject
void lolaaStatusChanged(LolAutoAccept::Status); // get triggerd, when the autoacceptor fails (lost connection)
private: private:
// returns empty string on no match bool loading;
const DataDragon::ChampData& getBestMatchingChamp(const std::string& name);
Ui::MainWindow *ui; Ui::MainWindow *ui;
LolAutoAccept& lolaa; QTimer* saveTimer;
std::thread lolaathread; std::thread lolaathread;
DataDragon dd; DataDragon dd;
Config conf; Config conf;
LolAutoAccept lolaa;
LoadingWindow* lwin;
QMessageBox* dodgeQuestion;
X11Helper* x11Helper;
}; };
#endif // MAINWINDOW_H #endif // MAINWINDOW_H

View File

@ -1,27 +1,27 @@
#pragma once #pragma once
#include <functional> #include <functional>
#include <string> #include <QString>
#include <QPixmap> #include <QPixmap>
class MemoryImageCache { class MemoryImageCache {
public: public:
MemoryImageCache(size_t maxsize = 25); MemoryImageCache(size_t maxsize = 25);
void addImage(QPixmap, const std::string& title, int type); void addImage(QPixmap, const QString& title, int type);
QPixmap getImage(const std::string& title, int type); QPixmap getImage(const QString& title, int type);
private: private:
void cleanUp(); void cleanUp();
struct CachedImage { struct CachedImage {
time_t lastaccessed = 0; time_t lastaccessed = 0;
QPixmap imageref; QPixmap imageref;
std::string title; QString title;
int type; int type;
bool operator<(const CachedImage& other) const; bool operator<(const CachedImage& other) const;
}; };
static std::function<bool(const CachedImage&)> getImageMatcher(const std::string& title, int type); static std::function<bool(const CachedImage&)> getImageMatcher(const QString& title, int type);
std::list<CachedImage> cache; std::list<CachedImage> cache;
size_t maxsize; size_t maxsize;

21
include/position.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
#include <QDebug>
#include <QVariant>
enum class Position : uint32_t {
INVALID = 0,
TOP,
JUNGLE,
MIDDLE,
BOTTOM,
UTILITY
};
Position toPosition(const QString& str);
QString toString(Position p);
QString toShortString(Position p);
std::ostream& operator<<(std::ostream&, const Position&);
QDebug operator<<(QDebug , const Position&);
Q_DECLARE_METATYPE(Position)

View File

@ -1,12 +1,22 @@
#pragma once #pragma once
#include <string>
#include <QJsonDocument>
#include <curl/curl.h> #include <curl/curl.h>
class RestClient { #include <QObject>
#include <QJsonDocument>
#include <QString>
#ifdef Q_OS_WIN
#undef DELETE
#endif
class RestClient : public QObject {
Q_OBJECT
public: public:
RestClient(const std::string& base); RestClient(const QString& base);
RestClient(const RestClient&) = delete;
virtual ~RestClient(); virtual ~RestClient();
enum class Method { enum class Method {
@ -17,15 +27,28 @@ public:
DELETE DELETE
}; };
protected: struct WebException {
QByteArray requestRaw(const std::string& url, Method m = Method::GET, const std::string& data = {}); CURLcode curlresponse = CURLE_OK;
QJsonDocument request(const std::string& url, Method m = Method::GET, const std::string& data = {});
void enableDebugging(bool enabled = true);
std::string baseurl; WebException(CURLcode c = CURLE_OK);
};
protected:
QByteArray requestRaw(const QString& url, Method m = Method::GET, const QString& data = {});
QJsonDocument request(const QString& url, Method m = Method::GET, const QString& data = {});
void enableDebugging(bool enabled = true);
QString escape(const QString& in) const;
QString baseurl;
CURL* curl = nullptr; // the curl (does curling) CURL* curl = nullptr; // the curl (does curling)
std::string basicauth; // basic auth code (user:pw) or empty string to disable QString basicauth; // basic auth code (user:pw) or empty string to disable
bool disableCertCheck = false; #ifdef WIN32
bool disableCertCheck = true;
#else
bool disableCertCheck = false;
#endif
}; };
const char* toString(RestClient::Method);

18
include/runeaspekt.h Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <cstdint>
#include <string>
class QJsonObject;
struct RuneAspekt {
uint32_t id = 0;
QString name;
QString shortDesc;
QString longDesc;
QString tooltip;
QString iconPath;
RuneAspekt();
explicit RuneAspekt(const QJsonObject& json);
};

View File

@ -0,0 +1,42 @@
#pragma once
#include <QPushButton>
#include <vector>
namespace Ui {
class RuneAspektButton;
}
class RuneAspektButtonGroup;
class RuneAspektButton : public QPushButton {
Q_OBJECT
public:
explicit RuneAspektButton(QWidget* parent = nullptr);
~RuneAspektButton();
void setAspektId(uint32_t id);
void setButtonGroup(RuneAspektButtonGroup* group);
bool isSelected() const;
signals:
void aspektToggled(int aspekt);
public slots:
void buttonPressed();
void dataChanged(); // triggers a refresh
void checkSelection(uint32_t aspekt); // only used for rune styles
private slots:
void setShowSelection(bool selected); // show/hide the red border
public:
uint32_t aspektId = 0;
private:
Ui::RuneAspektButton* ui;
RuneAspektButtonGroup* group = nullptr;
};

View File

@ -0,0 +1,39 @@
#pragma once
#include <cstdint>
#include <QObject>
#include <QVector>
class RuneAspektButton;
class RuneAspektButtonGroup : public QObject {
Q_OBJECT
public:
RuneAspektButtonGroup(QObject* parent, uint32_t size);
virtual ~RuneAspektButtonGroup();
void addButton(RuneAspektButton* button);
void setSelectedRunes(const QVector<int>& newRunes);
constexpr const QVector<int>& getSelectedRunes() const { return selectedRune; }
constexpr uint32_t getSize() const { return size; }
void setSubgroups(const QVector<QVector<int>>& newSubgroups);
static const int INVALID_ASPEKT_ID;
signals:
void changed(); // signal that the group was changed -> all buttons should refresh
private slots:
void buttonPressed(int aspektId);
private:
// try to fetch a aspektId, that is in selectedRune and in the same subgroup as aspektId
// return 0 when no suitable candidate is found
int getOtherSubgroupMemeber(int aspketId);
QVector<int> selectedRune;
QVector<QVector<int>> subgroups; // might be empty
uint32_t size = 0;
};

42
include/runedisplay.h Normal file
View File

@ -0,0 +1,42 @@
#pragma once
#include <QWidget>
#include "runeaspekt.h"
#include "runepage.h"
#include "runestyle.h"
namespace Ui {
class RuneDisplay;
}
class RuneDisplay : public QWidget {
Q_OBJECT
public:
explicit RuneDisplay(QWidget *parent = nullptr);
~RuneDisplay();
void setRuneMeta(const std::vector<RuneAspekt>& runeinfo);
void setStyles(const std::vector<RuneStyle>& styleinfos);
void setRunes(const RunePage& rp);
private slots:
void applyRunesClicked();
signals:
void applyRunes();
private:
void updateText();
QString getRuneText(uint32_t id);
QString getRuneStyleByID(uint32_t id);
Ui::RuneDisplay *ui;
RunePage runepage;
std::vector<RuneAspekt> runeinfo;
std::vector<RuneStyle> runestyles;
};

65
include/runeeditor.h Normal file
View File

@ -0,0 +1,65 @@
#pragma once
#include <vector>
#include <QDialog>
#include <QVector>
#include "runeaspekt.h"
#include "runepage.h"
#include "runestyle.h"
namespace Ui {
class RuneEditor;
}
class ClientAPI;
class RuneAspektButton;
class RuneAspektButtonGroup;
class QGridLayout;
class RuneEditor : public QDialog {
Q_OBJECT
public:
explicit RuneEditor(QWidget* parent = nullptr);
~RuneEditor();
void setClient(ClientAPI& client);
void setRunepage(const ::RunePage& rp);
void selectStyle(uint32_t id);
void selectSubStyle(uint32_t id);
void clearLayout(QLayout* layout);
void setName(QString text);
QString getName() const;
const RunePage& getRunepage();
signals:
void selectionChanged();
void selectPrimary(int aspektId);
void selectSecondary(int aspektId);
private:
const RuneStyle* getRuneStyle(uint32_t id) const;
RuneAspektButton* createStyleButton(const RuneStyle& rs, bool selected);
RuneAspektButton* createAspektButton(uint32_t perk);
RuneAspektButton* createButtonFromResource(QString resource);
void fillRuneStyle(QGridLayout* target, const RuneStyle& rs);
QString fixString(QString text);
Ui::RuneEditor* ui;
ClientAPI* client = nullptr;
::RunePage runepage;
std::vector<RuneAspekt> aspekts;
std::vector<RuneStyle> styles;
// 0 = keystone, 1-3 = main runes, 4 = sub runes (2), 5-7 = stats
QVector<RuneAspektButtonGroup*> groups;
};

54
include/runemanager.h Normal file
View File

@ -0,0 +1,54 @@
#pragma once
#include <memory>
#include <QWidget>
#include "config.h"
#include "runeaspekt.h"
#include "runestyle.h"
namespace Ui {
class RuneManager;
}
class ClientAPI;
class DataDragon;
class QListWidgetItem;
class QTimer;
class RuneManager : public QWidget {
Q_OBJECT
public:
explicit RuneManager(QWidget* parent = nullptr);
~RuneManager();
void setConfig(Config& config);
void setDataDragon(DataDragon& dd);
private slots:
void loadRunes();
void reloadClientRunes();
void setRunesEnabled(bool enabled);
void saveRunePageClient(int id, QString name, const RunePage& rp);
void saveRunePageAA(int id, QString name, const RunePage& rp);
void deleteRunepageClient(int id);
void deleteRunepageAA(int id);
void autoSyncToggled();
private:
void syncRunes();
void reloadAARunes();
Ui::RuneManager* ui;
std::shared_ptr<ClientAPI> client;
Config* config = nullptr;
QTimer* initialLoadTimer = nullptr;
std::vector<RuneAspekt> runeInfo;
std::vector<RuneStyle> runeStyles;
};

26
include/runepage.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <cstdint>
#include <ostream>
#include <vector>
#include <QDebug>
#include <QJsonObject>
// represents a runepage
struct RunePage {
public:
uint32_t primaryStyle = 0;
uint32_t secondaryStyle = 0;
std::vector<uint32_t> selectedAspects; // all selected aspekts (should be exactly 9)
RunePage();
bool operator==(const RunePage& rp) const;
operator bool() const; // check if this runepage is valid (this does not check semantic validity, only if the values are set as they supposed to be)
operator QJsonObject() const;
RunePage(const QJsonObject& obj);
};
std::ostream& operator<<(std::ostream&, const RunePage&);
QDebug operator<<(QDebug, const RunePage&);

73
include/runepagelist.h Normal file
View File

@ -0,0 +1,73 @@
#pragma once
#include <vector>
#include <QListWidget>
#include "clientapi.h"
#include "config.h"
#include "datadragon.h"
#include "runeaspekt.h"
#include "runestyle.h"
namespace Ui {
class RunePageList;
}
class DropEvent;
class ClientAPI;
class RunePageList : public QListWidget {
Q_OBJECT
public:
static const uint32_t RoleId = Qt::UserRole;
static const uint32_t RolePointer = Qt::UserRole + 1;
explicit RunePageList(QWidget* parent = nullptr);
~RunePageList();
constexpr void setIsClient(bool b) { isClient = b; }
constexpr void setClient(ClientAPI& client) { this->client = &client; }
constexpr void setOther(QListWidget* other) { this->other = other; }
constexpr void setDataDragon(DataDragon& dd) { this->dd = &dd; }
void loadRunePages(const std::vector<ClientAPI::RunePage>& pages);
void loadRunePages(const std::vector<std::shared_ptr<Config::RunePageConfig>>& pages);
void setRuneInfos(const std::vector<RuneAspekt>& runeInfo, const std::vector<RuneStyle>& runeStyles);
signals:
void runepageChanged(int id, QString name, const RunePage& rp);
void runepageDeleted(int id);
protected:
virtual void dropEvent(QDropEvent* event) override;
private slots:
void itemChangedCallback(QListWidgetItem* item);
void openContextMenu(const QPoint&);
void deleteCurrentItem();
void editCurrentItem();
void duplicateCurrentItem();
void exportCurrentItem();
void importItem();
private:
void clearItems();
void addRunepageItem(QString name, int id, const ::RunePage& rp, bool isCurrent = false);
const DataDragon::ChampData& findChamp(const QString& name);
QString getRuneDescription(const ::RunePage& runepage);
QString getRuneText(uint32_t id);
QString getRuneStyleByID(uint32_t id);
const std::vector<RuneAspekt>* runeInfo = nullptr;
const std::vector<RuneStyle>* runeStyles = nullptr;
Ui::RunePageList* ui;
QListWidget* other = nullptr;
DataDragon* dd = nullptr;
ClientAPI* client = nullptr;
bool isClient;
};

32
include/runestyle.h Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
// fwd.
class QJsonObject;
struct RuneStyleSlot {
std::vector<int> perks;
QString type;
RuneStyleSlot();
RuneStyleSlot(const QJsonObject& json);
};
struct RuneStyle {
uint32_t id;
QString name;
QString iconPath;
QString tooltip;
std::vector<int> allowedSubStyles;
QString idName;
std::vector<RuneStyleSlot> runeSlots;
RuneStyle();
explicit RuneStyle(const QJsonObject& json);
};

View File

@ -1,29 +0,0 @@
#pragma once
#include <cstdint>
#include <vector>
class ScaleableInputs {
public:
struct Point {
uint32_t x;
uint32_t y;
};
private:
double xScale = 1;
double yScale = 1;
double xOffset = 0;
double yOffset = 0;
std::vector<Point> points;
public:
void addPoint(Point p);
void setScale(double x, double y);
void setOffset(double x, double y);
Point get(uint32_t nr) const;
};

53
include/settingstab.h Normal file
View File

@ -0,0 +1,53 @@
#ifndef SETTINGSTAB_H
#define SETTINGSTAB_H
#include <QWidget>
#include "config.h"
#include "datadragon.h"
#include "lolautoaccept.h"
#include "stagesettings.h"
namespace Ui {
class SettingsTab;
}
class SettingsTab : public QWidget {
Q_OBJECT
Q_PROPERTY(Position position MEMBER position READ getPosition)
public:
explicit SettingsTab(QWidget *parent = nullptr);
~SettingsTab();
void setup(Config::PositionConfig& conf, DataDragon* dd = nullptr);
std::vector<StageSettings::SelectedChamp> getChamps(LolAutoAccept::State s) const;
bool getState(LolAutoAccept::State s) const;
void setChamps(LolAutoAccept::State s, const std::vector<QString>&);
void setState(LolAutoAccept::State s, bool b);
Position getPosition() const;
private slots:
void banToggled(bool);
void banChampsChanged();
void pickToggled(bool);
void pickChampsChanged();
signals:
void changed(Position p, LolAutoAccept::State s);
void toggled(Position p, LolAutoAccept::State s, bool newstate);
private:
StageSettings* getStage(LolAutoAccept::State s) const;
Ui::SettingsTab *ui;
Config::PositionConfig* conf;
DataDragon* dd = nullptr;
Position position = Position::INVALID;
};
#endif // SETTINGSTAB_H

View File

@ -3,6 +3,7 @@
#include <QWidget> #include <QWidget>
#include "config.h"
#include "datadragon.h" #include "datadragon.h"
namespace Ui { namespace Ui {
@ -26,16 +27,18 @@ public:
void setState(bool); void setState(bool);
struct SelectedChamp { struct SelectedChamp {
SelectedChamp(std::string name, uint32_t id); SelectedChamp(QString name, uint32_t id);
std::string name; QString name;
uint32_t id; uint32_t id;
}; };
std::vector<SelectedChamp> getChampions() const; std::vector<SelectedChamp> getChampions() const;
void setChampions(const std::vector<std::string>& champs); void setChampions(const std::vector<QString>& champs);
void setDataDragon(DataDragon* dd); void setDataDragon(DataDragon* dd);
void addChamp(const std::string& champname, uint32_t id, QPixmap icon); void addChamp(const DataDragon::ChampData& cd, QPixmap icon);
void loadConfig(Config::StageConfig& c);
private slots: private slots:
void toggledinternal(int state); void toggledinternal(int state);
@ -49,7 +52,7 @@ signals:
private: private:
// delete all items // delete all items
void resolveAndAddChamp(const std::string& name, bool emitchange = false); void resolveAndAddChamp(const QString& name, bool emitchange = false);
void clear(); void clear();
void updateEnabled(); void updateEnabled();

35
include/x11helper.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef X11HELPER_H
#define X11HELPER_H
#include <QObject>
using Window = unsigned long;
struct _XDisplay;
using Display = struct _XDisplay;
class X11Helper : public QObject
{
Q_OBJECT
public:
static const Window InvalidWindow;
static const bool IsSupported;
explicit X11Helper(QObject* parent = nullptr);
virtual ~X11Helper();
Window findWindow(const QString& name, float aspektRatio = 0.0);
public slots:
void map(Window win);
void unmap(Window win);
void setMap(Window win, bool b);
private:
Window searchWindows(Window top, const QString& search, float aspektRatio);
#ifdef X11SUPPORT
Display* disp;
#endif
};
#endif // X11HELPER_H

View File

@ -1,6 +0,0 @@
[Desktop Entry]
Type=Application
Name=LoLAutoAccept
Exec=lolautoaccept
Icon=lolautoaccept
Categories=Utility;

View File

@ -3,6 +3,15 @@ QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++17 CONFIG += c++17
# debugging
CONFIG += debug
MOC_DIR = build/generated/
UI_DIR = build/ui/
RCC_DIR = build/rcc/
OBJECTS_DIR = build/objects/
unix:LIBS += -lcurl -pthread -lrt unix:LIBS += -lcurl -pthread -lrt
# The following define makes your compiler emit warnings if you use # The following define makes your compiler emit warnings if you use
@ -10,12 +19,17 @@ unix:LIBS += -lcurl -pthread -lrt
# depend on your compiler). Please consult the documentation of the # depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it. # deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS DEFINES += QT_DEPRECATED_WARNINGS
DEFINES += LOG_ENABLEQT=1
# You can also make your code fail to compile if it uses deprecated APIs. # You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line. # In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt. # You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
DEFINES += LOLAA_VERSION=\\\"0.0.8\\\"
QMAKE_CXXFLAGS += -Wall -Wpedantic -Wextra
# parameters: var, prepend, append # parameters: var, prepend, append
defineReplace(prependAll) { defineReplace(prependAll) {
for(a,$$1):result += $$2$${a}$$3 for(a,$$1):result += $$2$${a}$$3
@ -24,97 +38,133 @@ defineReplace(prependAll) {
SOURCES += \ SOURCES += \
src/arg.cpp \ src/arg.cpp \
src/blitzapi.cpp \
src/champcache.cpp \
src/championsearch.cpp \ src/championsearch.cpp \
src/champrow.cpp \ src/champrow.cpp \
src/clientaccess.cpp \ src/clientaccess.cpp \
src/clientapi_json.cpp \ src/clientapi_json.cpp \
src/clientapi.cpp \ src/clientapi.cpp \
src/clipboardpopup.cpp \
src/config.cpp \ src/config.cpp \
src/datadragon.cpp \ src/datadragon.cpp \
src/datadragonimagecache.cpp \ src/datadragonimagecache.cpp \
src/files.cpp \ src/files.cpp \
src/json.cpp \ src/json.cpp \
src/loadingwindow.cpp \
src/lolautoaccept.cpp \ src/lolautoaccept.cpp \
src/main.cpp \ src/main.cpp \
src/mainwindow.cpp \ src/mainwindow.cpp \
src/memoryimagecache.cpp \ src/memoryimagecache.cpp \
src/restclient.cpp \ src/restclient.cpp \
src/runeaspektbutton.cpp \
src/runeaspektbuttongroup.cpp \
src/runedisplay.cpp \
src/runeeditor.cpp \
src/runemanager.cpp \
src/runepage.cpp \
src/runepagelist.cpp \
src/settingstab.cpp \
src/stagesettings.cpp \ src/stagesettings.cpp \
src/x11helper.cpp \
thirdparty/Log/Log.cpp thirdparty/Log/Log.cpp
# mainwindow.cpp # platform specific implementations
win32:SOURCES += src/clientaccess_windows.cpp src/x11helper_other.cpp
unix:SOURCES += src/clientaccess_linux.cpp src/x11helper_x11.cpp
HEADERS += \ HEADERS += \
include/arg.h \ include/arg.h \
include/blitzapi.h \
include/champcache.h \
include/championsearch.h \ include/championsearch.h \
include/champrow.h \ include/champrow.h \
include/clientaccess.h \ include/clientaccess.h \
include/clientapi.h \ include/clientapi.h \
include/clipboardpopup.h \
include/config.h \ include/config.h \
include/datadragon.h \ include/datadragon.h \
include/datadragonimagecache.h \ include/datadragonimagecache.h \
include/defer.h \
include/files.h \ include/files.h \
include/json.h \ include/json.h \
include/loadingwindow.h \
include/lolautoaccept.h \ include/lolautoaccept.h \
include/mainwindow.h \ include/mainwindow.h \
include/memoryimagecache.h \ include/memoryimagecache.h \
include/restclient.h \ include/restclient.h \
include/runeaspektbutton.h \
include/runeaspektbuttongroup.h \
include/runedisplay.h \
include/runeeditor.h \
include/runemanager.h \
include/runepage.h \
include/runepagelist.h \
include/settingstab.h \
include/stagesettings.h \ include/stagesettings.h \
include/x11helper.h \
thirdparty/Log/Log.h thirdparty/Log/Log.h
# mainwindow.h
MOC_DIR = build/generated/
UI_DIR = ui/
OBJECTS_DIR = build/
FORMS += \ FORMS += \
ui/championsearch.ui \ ui/championsearch.ui \
ui/clipboardpopup.ui \
ui/loadingwindow.ui \
ui/mainwindow.ui \ ui/mainwindow.ui \
ui/runeaspektbutton.ui \
ui/runedisplay.ui \
ui/runeeditor.ui \
ui/runemanager.ui \
ui/runepagelist.ui \
ui/settingstab.ui \
ui/stagesettings.ui ui/stagesettings.ui
INCLUDEPATH += $$PWD/include/ \ INCLUDEPATH += $$PWD/include/ \
$$PWD/thirdparty/Log/ $$PWD/thirdparty/Log/
#TRANSLATIONS += \ # translations
# ts/de_DE.ts \
# ts/en.ts
LANGUAGES = de_DE en LANGUAGES = de_DE en
CONFIG += lrelease embed_translations
TRANSLATIONS = $$prependAll(LANGUAGES, $$PWD/ts/, .ts) TRANSLATIONS = $$prependAll(LANGUAGES, $$PWD/resources/ts/, .ts)
TRANSLATIONSQM = $$prependAll(LANGUAGES, $$PWD/ts/, .qm)
makelang.commands = lrelease $$_PRO_FILE_ updatelang.commands = lupdate -locations none $$_PRO_FILE_
updatelang.commands = lupdate $$_PRO_FILE_ QMAKE_EXTRA_TARGETS += updatelang
QMAKE_EXTRA_TARGETS += makelang updatelang
PRE_TARGETDEPS += makelang
QMAKE_CLEAN += $$TRANSLATIONSQM
# build AppImage # build AppImage
unix { unix {
DEFINES += X11SUPPORT=1
LIBS += -lX11
linuxdeploy-x86_64.AppImage.commands = wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod u+x linuxdeploy-x86_64.AppImage linuxdeploy-x86_64.AppImage.commands = wget https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage && chmod u+x linuxdeploy-x86_64.AppImage
lolautoaccept.png.depends = lolautoaccept.svg resources/lolautoaccept.png.depends = resources/lolautoaccept.svg
lolautoaccept.png.commands = inkscape -z -elolautoaccept.png -w 512 -h 512 lolautoaccept.svg resources/lolautoaccept.png.commands = rsvg-convert -w 512 -h 512 resources/lolautoaccept.svg -o resources/lolautoaccept.png
appimg.depends = linuxdeploy-x86_64.AppImage $${TARGET} lolautoaccept.png appimg.depends = linuxdeploy-x86_64.AppImage $${TARGET} resources/lolautoaccept.png
appimg.commands = rm -rf AppDir ; \ appimg.commands = rm -rf AppDir ; \
mkdir -p AppDir/ts AppDir/imgs; \ mkdir -p AppDir/ts ; \
cp ./ts/*.qm ./AppDir/ts ; \ ./linuxdeploy-x86_64.AppImage --appdir=AppDir -e lolautoaccept -i resources/lolautoaccept.png -d resources/lolautoaccept.desktop --output appimage
cp ./imgs/*.png ./AppDir/imgs; \
./linuxdeploy-x86_64.AppImage --appdir=AppDir -e lolautoaccept -i lolautoaccept.png -d lolautoaccept.desktop --output appimage
QMAKE_EXTRA_TARGETS += appimg linuxdeploy-x86_64.AppImage lolautoaccept.png QMAKE_EXTRA_TARGETS += appimg linuxdeploy-x86_64.AppImage resources/lolautoaccept.png
QMAKE_CLEAN += linuxdeploy-x86_64.AppImage lolautoaccept.png QMAKE_CLEAN += linuxdeploy-x86_64.AppImage resources/lolautoaccept.png
}
win32 {
INCLUDEPATH += $$PWD/../curl/include/
LIBS += $$PWD/../curl/lib/libbrotlicommon.a $$PWD/../curl/lib/libbrotlidec.a $$PWD/../curl/lib/libcrypto.a $$PWD/../curl/lib/libcurl.a $$PWD/../curl/lib/libcurl.dll.a $$PWD/../curl/lib/libgsasl.a $$PWD/../curl/lib/libidn2.a $$PWD/../curl/lib/libnghttp2.a $$PWD/../curl/lib/libnghttp3.a $$PWD/../curl/lib/libngtcp2.a $$PWD/../curl/lib/libngtcp2_crypto_openssl.a $$PWD/../curl/lib/libssh2.a $$PWD/../curl/lib/libssl.a $$PWD/../curl/lib/libz.a $$PWD/../curl/lib/libzstd.a
# to create the ico: convert -density 300 -define icon:auto-resize=256,128,96,64,48,32,16 -background none resources/lolautoaccept.svg resources/lolautoaccept.ico
RC_ICONS = resources/lolautoaccept.ico
} }
# Default rules for deployment. # Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin else: unix:!android: target.path = /usr/bin
!isEmpty(target.path): INSTALLS += target !isEmpty(target.path): INSTALLS += target
# https://wiki.qt.io/Automating_generation_of_qm_files # https://wiki.qt.io/Automating_generation_of_qm_files
RESOURCES += \
resources/res.qrc

5
resources/icons/bot.svg Normal file
View File

@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path d="M27.333 27.333H5.196l4.406-4.786h12.945V9.602l4.786-4.406v22.137z"></path>
<path fill-opacity=".4"
d="M4 4h20.94l-5.041 4.188H8.187v11.628l-4.188 4.526V4zm14.359 14.359h-5.983v-5.983h5.983v5.983z"></path>
</svg>

After

Width:  |  Height:  |  Size: 308 B

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1024"
height="1024"
viewBox="0 0 270.93333 270.93334"
version="1.1"
id="svg8"
sodipodi:docname="delete.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.7"
inkscape:cx="451.15206"
inkscape:cy="659.53498"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:pagecheckerboard="true"
borderlayer="true"
inkscape:window-width="2528"
inkscape:window-height="1381"
inkscape:window-x="1952"
inkscape:window-y="28"
inkscape:window-maximized="1"
showguides="false" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-26.06665)">
<rect
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#fffffb;stroke-width:16.93333244;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
id="rect1013"
width="142.55424"
height="161.20285"
x="64.189545"
y="100.63498" />
<rect
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#fffffb;stroke-width:16.93333435;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
id="rect1015"
width="179.60214"
height="20.944307"
x="45.665596"
y="79.216049"
ry="6.992558"
rx="0" />
<rect
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#fffffb;stroke-width:16.93333244;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
id="rect1017"
width="30.616072"
height="17.764881"
x="120.15863"
y="61.228806"
ry="6.9925671" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1024"
height="1024"
viewBox="0 0 270.93333 270.93334"
version="1.1"
id="svg8"
sodipodi:docname="duplicate.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.49497475"
inkscape:cx="-137.33323"
inkscape:cy="973.74201"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:pagecheckerboard="true"
borderlayer="true"
inkscape:window-width="2528"
inkscape:window-height="1381"
inkscape:window-x="1952"
inkscape:window-y="28"
inkscape:window-maximized="1"
showguides="false" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-26.06665)">
<rect
style="opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:16.93333435;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
id="rect865-3"
width="127.58046"
height="172.03047"
x="112.739"
y="98.764053"
inkscape:label="new" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:64;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 130.59961,81.945312 c -17.66933,0.0061 -31.99042,14.330669 -31.992188,31.999998 v 650.19727 c 0.0018,17.66933 14.322858,31.99392 31.992188,32 H 362.0957 v -64 H 162.60156 V 145.94727 h 418.19727 v 64.8125 h 64.00195 v -96.81446 c -0.002,-17.672379 -14.32762,-31.99823 -32,-31.999998 z"
transform="matrix(0.26458333,0,0,0.26458333,0,26.06665)"
id="rect1910"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccccccccc" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

66
resources/icons/edit.svg Normal file
View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1024"
height="1024"
viewBox="0 0 270.93333 270.93334"
version="1.1"
id="svg8"
sodipodi:docname="edit.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.49497475"
inkscape:cx="267.73794"
inkscape:cy="973.74201"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:pagecheckerboard="true"
borderlayer="true"
inkscape:window-width="2528"
inkscape:window-height="1381"
inkscape:window-x="1952"
inkscape:window-y="28"
inkscape:window-maximized="1"
showguides="false" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-26.06665)">
<path
style="opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
d="M 191.78519,59.101843 46.234389,204.65213 92.347853,250.76559 237.89814,105.21479 Z M 42.32145,212.71572 l -9.286255,51.24907 51.250619,-9.28677 z"
id="rect838"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1024"
height="1024"
viewBox="0 0 270.93333 270.93334"
version="1.1"
id="svg8"
sodipodi:docname="export.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.49497475"
inkscape:cx="89.911358"
inkscape:cy="1128.7865"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:pagecheckerboard="true"
borderlayer="true"
inkscape:window-width="2528"
inkscape:window-height="1381"
inkscape:window-x="1952"
inkscape:window-y="28"
inkscape:window-maximized="1"
showguides="false" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-26.06665)">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:36.93648148;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 43.435547,231.03711 c -10.201227,-3.4e-4 -18.471041,8.26947 -18.470703,18.4707 v 731.05664 c -3.44e-4,10.20123 8.269472,18.47105 18.470703,18.47071 H 774.49219 c 10.20123,3.4e-4 18.47104,-8.26948 18.4707,-18.47071 V 249.50781 c 3.4e-4,-10.20123 -8.26947,-18.47104 -18.4707,-18.4707 h -52.1543 c -3.3615,2.96416 -6.64075,6.0031 -9.81641,9.13867 -8.67079,8.56135 -16.71538,17.79803 -24.0996,27.79297 h 67.60937 V 962.0957 H 61.904297 V 267.96875 H 613.25586 c 6.92944,-13.03955 14.61015,-25.35573 23.02734,-36.93164 z"
transform="matrix(0.26458333,0,0,0.26458333,0,26.06665)"
id="rect909"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.46666718;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 159.95132,132.72143 C 165.7499,90.045345 191.29939,68.478702 229.47904,60.410947"
id="path911"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#fffffb;stroke-width:8.46666718;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 221.23258,38.481818 42.41883,18.747657 -41.87698,27.147964"
id="path913"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc"
inkscape:label="ArrowTop" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="1024"
height="1024"
viewBox="0 0 270.93333 270.93334"
version="1.1"
id="svg8"
sodipodi:docname="import.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.49497475"
inkscape:cx="-490.64446"
inkscape:cy="895.44453"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:pagecheckerboard="true"
borderlayer="true"
inkscape:window-width="2528"
inkscape:window-height="1381"
inkscape:window-x="1952"
inkscape:window-y="28"
inkscape:window-maximized="1"
showguides="false" />
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-26.06665)">
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:36.93648148;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
d="m 43.435547,231.03711 a 18.470088,18.470088 0 0 0 -18.470703,18.4707 v 731.05664 a 18.470088,18.470088 0 0 0 18.470703,18.47071 H 774.49219 a 18.470088,18.470088 0 0 0 18.4707,-18.47071 V 249.50781 a 18.470088,18.470088 0 0 0 -18.4707,-18.4707 h -18.48047 c -14.21343,10.60781 -27.51005,22.92882 -39.94922,36.93164 h 39.96875 V 962.0957 H 61.904297 V 267.96875 H 635.49805 c 8.74524,-13.01432 18.04176,-25.34765 27.92187,-36.93164 z"
transform="matrix(0.26458333,0,0,0.26458333,0,26.06665)"
id="rect909"
inkscape:connector-curvature="0" />
<path
style="fill:none;fill-rule:evenodd;stroke:#ffffff;stroke-width:8.46666718;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 253.90605,66.613651 C 211.10887,61.789428 183.94424,81.282492 166.77618,116.32578"
id="path911"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<path
style="fill:none;fill-rule:evenodd;stroke:#fffffb;stroke-width:8.46666718;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 147.53278,102.9623 7.79389,45.71747 36.57285,-33.95761"
id="path913"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc"
inkscape:label="ArrowTop" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

5
resources/icons/jgl.svg Normal file
View File

@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path
d="m16.21 29.333-7.875-7.955A18.22 18.22 0 0 0 4 10.09c6.93 2.606 8.04 7.818 8.04 7.818 1.245-6.091-3.39-15.242-3.39-15.242 13.305 16.652 7.56 26.667 7.56 26.667zM16.57 13a37.966 37.966 0 0 1 6.765-10.333 49.874 49.874 0 0 0-4.365 15.5s-1.02-3.591-2.4-5.167zM28 9.879c-9.315 5.576-8.325 15.515-8.325 15.515l4.185-4.258C23.71 13.03 28 9.878 28 9.878z">
</path>
</svg>

After

Width:  |  Height:  |  Size: 460 B

6
resources/icons/mid.svg Normal file
View File

@ -0,0 +1,6 @@
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path d="M5.333 26.667v-4.364l16.97-16.97h4.364v4.364l-16.97 16.97H5.333z"></path>
<path fill-opacity=".4"
d="m19.394 5.333-3.879 3.879H9.212v6.303l-3.879 3.879V5.333h14.061zm-6.788 21.334 3.879-3.879h6.303v-6.303l3.879-3.879v14.061H12.606z">
</path>
</svg>

After

Width:  |  Height:  |  Size: 346 B

5
resources/icons/sup.svg Normal file
View File

@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path
d="M19.03 4h-6.061l-1.061 1.417 4.091 5.037 4.091-5.037L19.029 4zm-7.878 4.88H2.667c1.167 1.144 2.514 2.221 3.939 2.991.572.185 1.068.313 1.667.315h2.273l-2.424 2.991 3.939 1.889 1.515-5.667-2.424-2.519zm9.696 0h8.485c-1.168 1.143-2.515 2.222-3.939 2.991-.572.185-1.068.313-1.667.315h-2.273l2.424 2.991-3.939 1.889-1.515-5.667 2.424-2.519zm-1.666 15.268-2.424-12.593a.863.863 0 0 1-.758.63.87.87 0 0 1-.758-.63l-2.424 12.593L16 26.667l3.182-2.519z">
</path>
</svg>

After

Width:  |  Height:  |  Size: 558 B

6
resources/icons/top.svg Normal file
View File

@ -0,0 +1,6 @@
<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path d="M4 4h22.137l-4.406 4.786H8.786v12.945L4 26.137V4z"></path>
<path fill-opacity=".4"
d="M27.333 27.333H6.393l5.041-4.188h11.712V10.918l4.188-4.526v20.94zM12.974 12.974h5.983v5.983h-5.983v-5.983z">
</path>
</svg>

After

Width:  |  Height:  |  Size: 307 B

12
resources/lolautoaccept.desktop Executable file
View File

@ -0,0 +1,12 @@
[Desktop Entry]
Type=Application
Name=LoLAutoAccept
Exec=lolautoaccept
Icon=lolautoaccept
Version=1.5
Categories=Game;Utility
Terminal=false
Hidden=false
Keywords=lol;league of legends;lolaa;
SingleMainWindow=true
Comment=Automatically accept LoL games, pick and ban champions, and more.

BIN
resources/lolautoaccept.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

View File

@ -17,10 +17,19 @@
<circle cx="512" cy="512" r="508" fill="none" stroke-width="4" stroke="black" /> <circle cx="512" cy="512" r="508" fill="none" stroke-width="4" stroke="black" />
<circle cx="512" cy="512" r="500" fill="none" stroke-width="4" stroke="black" /> <circle cx="512" cy="512" r="500" fill="none" stroke-width="4" stroke="black" />
<!-- A A --> <!-- A A (imagemagick convert does not like text in svg)-->
<text x="48" y="786" fill="black" font-size="768px" clip-path="url(#text-circle-cutoff)" font-family="monospace"> <!--text x="48" y="786" fill="black" font-size="768px" clip-path="url(#text-circle-cutoff)" font-family="monospace">
AA AA
</text> </text-->
<!-- left A -->
<polygon points="234,226 324,226 496,785 418,785 377,639 180,639 139,785 62,785" clip-path="url(#text-circle-cutoff)" />
<polygon points="278,293 279,293 358,578 199,578" style="fill:#005e84;" />
<!-- right A -->
<polygon points="697,226 787,226 959,785 881,785 840,639 643,639 602,785 525,785" clip-path="url(#text-circle-cutoff)" />
<polygon points="741,293 742,293 821,578 662,578" style="fill:#005e84;" />
<!-- Pointer --> <!-- Pointer -->
<polygon points="512,786 354,226 500,226 488,48 536,48 524,226 670,226" /> <polygon points="512,786 354,226 500,226 488,48 536,48 524,226 670,226" />

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

16
resources/res.qrc Normal file
View File

@ -0,0 +1,16 @@
<RCC>
<qresource prefix="/">
<file>lolautoaccept.png</file>
<file>lolautoaccept.svg</file>
<file>icons/top.svg</file>
<file>icons/sup.svg</file>
<file>icons/mid.svg</file>
<file>icons/jgl.svg</file>
<file>icons/bot.svg</file>
<file>icons/delete.svg</file>
<file>icons/duplicate.svg</file>
<file>icons/edit.svg</file>
<file>icons/export.svg</file>
<file>icons/import.svg</file>
</qresource>
</RCC>

291
resources/ts/de_DE.ts Normal file
View File

@ -0,0 +1,291 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de_DE">
<context>
<name>ChampionSearch</name>
<message>
<source>Champion Search</source>
<translation>Champion Suche</translation>
</message>
<message>
<source>Champion:</source>
<translation>Champion:</translation>
</message>
</context>
<context>
<name>ClipboardPopup</name>
<message>
<source>Clipboard Text</source>
<translation>Zwischenablage</translation>
</message>
<message>
<source>Copy To Clipboard</source>
<translation>In Zwischenablage kopieren</translation>
</message>
<message>
<source>Paste here</source>
<translation>Hier einfügen</translation>
</message>
</context>
<context>
<name>LoadingWindow</name>
<message>
<source>LoL-Auto-Accept</source>
<translation>LoL-Auto-Accept</translation>
</message>
<message>
<source>Loading Champion: %0</source>
<translation>Lade Champion: %0</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<source>LoL-Auto-Accept</source>
<translation>LoL-Auto-Accept</translation>
</message>
<message>
<source>Mainswitch</source>
<translation>Hauptschalter</translation>
</message>
<message>
<source>Enable LoL-Auto-Accept</source>
<translation>Spiel automatisch annehmen</translation>
</message>
<message>
<source>Spam &quot;smite&quot; in the chat when there is not exactly 1 player with smite equiped in champ select</source>
<translation>Wenn nicht genau 1 Spieler Smite ausgewählt hat, wird &quot;smite&quot; in den Chat gespammt</translation>
</message>
<message>
<source>Auto Accept</source>
<translation>Auto Annehmen</translation>
</message>
<message>
<source>Write a Text as soon as you are in a champ select lobby.</source>
<translation>Einen Text schreiben, sobald du in der Champion Auswahl bist.</translation>
</message>
<message>
<source>Auto Write</source>
<translation>Automatisch Schreiben</translation>
</message>
<message>
<source>This controls the connection to the LoL client. As long as this is off, no interactions with the LoL client take place.</source>
<translation>Dies kontrolliert die Verbindung zum LoL Client. Solange dies aus ist, wird nicht mit dem LoL Client interagiert.</translation>
</message>
<message>
<source>Enable Smite Warning</source>
<translation>Smite Warnung</translation>
</message>
<message>
<source>Developed by MrBesen</source>
<translation type="vanished">Entwickelt von MrBesen</translation>
</message>
<message>
<source>autowriteText</source>
<translation>Zu schreibender Text</translation>
</message>
<message>
<source>This Tab is used, when you are in a gamemode with no fixed roles</source>
<translation>Dieser Tab wird verwendet, wenn der Gamemode keine festen Rollen hat</translation>
</message>
<message>
<source>Default</source>
<translation>Default</translation>
</message>
<message>
<source>Top</source>
<translation>Top</translation>
</message>
<message>
<source>Jungle</source>
<translation>Jungle</translation>
</message>
<message>
<source>Middle</source>
<translation></translation>
</message>
<message>
<source>Bottom</source>
<translation></translation>
</message>
<message>
<source>Support</source>
<translation>Support</translation>
</message>
<message>
<source>Dodge without closing the client.
You will still be punished.</source>
<translation>Dodgen ohne den Client zu schließen
Du wirst trotzdem bestraft.</translation>
</message>
<message>
<source>Dodge</source>
<translation>Dodge</translation>
</message>
<message>
<source>Runes</source>
<translation>Runen</translation>
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Developed by MrBesen&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://lolautoacceptor.mrbesen.de/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#007af4;&quot;&gt;Webseite&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Version: %1&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Entwickelt von MrBesen&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://lolautoacceptor.mrbesen.de/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#007af4;&quot;&gt;Webseite&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Version: %1&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>League of Legends Client not found!</source>
<translation>League of Legends Client nicht gefunden!</translation>
</message>
<message>
<source>Auto-Acceptor started!</source>
<translation></translation>
</message>
<message>
<source>Auto-Acceptor stoped!</source>
<translation>Auto Acceptor gestoppt!</translation>
</message>
<message>
<source>Dodge?</source>
<translation>Dodgen?</translation>
</message>
<message>
<source>Are you sure you want to dodge?</source>
<translation>Bist du dir sicher, dass du dodgen möchtest?</translation>
</message>
<message>
<source>Auto-Acceptor failed!</source>
<translation>Auto-Acceptor fehlgeschlagen!</translation>
</message>
</context>
<context>
<name>QWidget</name>
<message>
<source>Champion: %1
Type: %2
Title: %3
ID: %4</source>
<translation>Champion: %1
Typ: %2
Titel: %3
ID: %4</translation>
</message>
</context>
<context>
<name>RuneDisplay</name>
<message>
<source>Form</source>
<translation></translation>
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a Runepage and modify it to this runes.&lt;br/&gt;The page used is the first that:&lt;/p&gt;&lt;ol style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;matches this Runes&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;has a name starting with &amp;quot;AA:&amp;quot;&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;is currently selected&lt;/li&gt;&lt;/ol&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Eine Runenseite auswählen und entsprechend modifizieren.&lt;br/&gt;Die ausgewählte Seite ist die erste, die die erste der Eigenschaften erfüllt:&lt;/p&gt;&lt;ol style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;schon die richtigen Runen enthält&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;einen Namen der mit &amp;quot;AA:&amp;quot; anfängt hat&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;gerade ausgewählt ist&lt;/li&gt;&lt;/ol&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Apply Runes</source>
<translation>Runen Anwenden</translation>
</message>
</context>
<context>
<name>RuneEditor</name>
<message>
<source>Runepage Editor</source>
<translation>Runenseiten Editor</translation>
</message>
</context>
<context>
<name>RuneManager</name>
<message>
<source>Runes in the Client</source>
<translation>Runen im Client</translation>
</message>
<message>
<source>Runes in the Autoacceptor</source>
<translation>Runen im Autoacceptor</translation>
</message>
<message>
<source>Reload</source>
<translation>Neuladen</translation>
</message>
<message>
<source>Runes from the client get copied to the autoacceptor automatically.</source>
<translation>Runen vom Client werden automatisch in den Autoacceptor kopiert.</translation>
</message>
<message>
<source>Auto Copy Runes</source>
<translation>Auto Runen kopieren</translation>
</message>
</context>
<context>
<name>RunePageList</name>
<message>
<source>Edit</source>
<translation>Bearbeiten</translation>
</message>
<message>
<source>Duplicate</source>
<translation>Dublizieren</translation>
</message>
<message>
<source>Export</source>
<translation>Exportieren</translation>
</message>
<message>
<source>Import</source>
<translation>Importerien</translation>
</message>
<message>
<source>Delete</source>
<translation>Löschen</translation>
</message>
<message>
<source>Loading runes</source>
<translation>Lade Runnen</translation>
</message>
<message>
<source>with</source>
<translation>mit</translation>
</message>
</context>
<context>
<name>SettingsTab</name>
<message>
<source>Ban</source>
<translation>Bannen</translation>
</message>
<message>
<source>Pick</source>
<translation>Picken</translation>
</message>
</context>
<context>
<name>StageSettings</name>
<message>
<source>Champion:</source>
<translation type="vanished">Champion:</translation>
</message>
<message>
<source>Enable %1</source>
<translation>Aktiviere %1</translation>
</message>
<message>
<source>Champions matched: %1</source>
<translation type="vanished">Übereinstimmende Champions: %1</translation>
</message>
<message>
<source>Champion: %1
Type: %2
Title: %3
ID: %4</source>
<translation type="vanished">Champion: %1
Typ: %2
Titel: %3
ID: %4</translation>
</message>
<message>
<source>Add Champion</source>
<translation>Champion hinzufügen</translation>
</message>
<message>
<source>Remove Champion</source>
<translation>Champion entfernen</translation>
</message>
</context>
</TS>

291
resources/ts/en.ts Normal file
View File

@ -0,0 +1,291 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_US">
<context>
<name>ChampionSearch</name>
<message>
<source>Champion Search</source>
<translation>Champion Search</translation>
</message>
<message>
<source>Champion:</source>
<translation>Champion:</translation>
</message>
</context>
<context>
<name>ClipboardPopup</name>
<message>
<source>Clipboard Text</source>
<translation>Clipboard Text</translation>
</message>
<message>
<source>Copy To Clipboard</source>
<translation>Copy To Clipboard</translation>
</message>
<message>
<source>Paste here</source>
<translation>Paste here</translation>
</message>
</context>
<context>
<name>LoadingWindow</name>
<message>
<source>LoL-Auto-Accept</source>
<translation>LoL-Auto-Accept</translation>
</message>
<message>
<source>Loading Champion: %0</source>
<translation>Loading Champion: %0</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<source>LoL-Auto-Accept</source>
<translation>LoL-Auto-Accept</translation>
</message>
<message>
<source>Mainswitch</source>
<translation>Mainswitch</translation>
</message>
<message>
<source>Enable LoL-Auto-Accept</source>
<translation>Automatically accept game</translation>
</message>
<message>
<source>Spam &quot;smite&quot; in the chat when there is not exactly 1 player with smite equiped in champ select</source>
<translation>Spam &quot;smite&quot; in the chat when there is not exactly 1 player with smite equiped in champ select</translation>
</message>
<message>
<source>Auto Accept</source>
<translation>Auto Accept</translation>
</message>
<message>
<source>Write a Text as soon as you are in a champ select lobby.</source>
<translation>Write a Text as soon as you are in the champ select lobby.</translation>
</message>
<message>
<source>Auto Write</source>
<translation>Auto write</translation>
</message>
<message>
<source>This controls the connection to the LoL client. As long as this is off, no interactions with the LoL client take place.</source>
<translation>This controls the connection to the LoL client. As long as this is off, no interactions with the LoL client take place.</translation>
</message>
<message>
<source>Enable Smite Warning</source>
<translation>Enable Smite Warning</translation>
</message>
<message>
<source>Developed by MrBesen</source>
<translation type="vanished">Developed by MrBesen</translation>
</message>
<message>
<source>autowriteText</source>
<translation>Text to autowrite</translation>
</message>
<message>
<source>This Tab is used, when you are in a gamemode with no fixed roles</source>
<translation>This Tab is used, when you are in a gamemode with no fixed roles</translation>
</message>
<message>
<source>Default</source>
<translation>Default</translation>
</message>
<message>
<source>Top</source>
<translation>Top</translation>
</message>
<message>
<source>Jungle</source>
<translation>Jungle</translation>
</message>
<message>
<source>Middle</source>
<translation></translation>
</message>
<message>
<source>Bottom</source>
<translation></translation>
</message>
<message>
<source>Support</source>
<translation>Support</translation>
</message>
<message>
<source>Dodge without closing the client.
You will still be punished.</source>
<translation>Dodge without closing the client.
You will still be punished.</translation>
</message>
<message>
<source>Dodge</source>
<translation>Dodge</translation>
</message>
<message>
<source>Runes</source>
<translation>Runes</translation>
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Developed by MrBesen&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://lolautoacceptor.mrbesen.de/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#007af4;&quot;&gt;Webseite&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Version: %1&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Developed by MrBesen&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://lolautoacceptor.mrbesen.de/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#007af4;&quot;&gt;Webseite&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;p&gt;Version: %1&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>League of Legends Client not found!</source>
<translation>League of Legends Client not found!</translation>
</message>
<message>
<source>Auto-Acceptor started!</source>
<translation>Auto-Acceptor started!</translation>
</message>
<message>
<source>Auto-Acceptor stoped!</source>
<translation>Auto-Acceptor stopped!</translation>
</message>
<message>
<source>Dodge?</source>
<translation>Dodge?</translation>
</message>
<message>
<source>Are you sure you want to dodge?</source>
<translation>Are you sure you want to dodge?</translation>
</message>
<message>
<source>Auto-Acceptor failed!</source>
<translation>Auto-Acceptor failed!</translation>
</message>
</context>
<context>
<name>QWidget</name>
<message>
<source>Champion: %1
Type: %2
Title: %3
ID: %4</source>
<translation>Champion: %1
Type: %2
Title: %3
ID: %4</translation>
</message>
</context>
<context>
<name>RuneDisplay</name>
<message>
<source>Form</source>
<translation></translation>
</message>
<message>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a Runepage and modify it to this runes.&lt;br/&gt;The page used is the first that:&lt;/p&gt;&lt;ol style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;matches this Runes&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;has a name starting with &amp;quot;AA:&amp;quot;&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;is currently selected&lt;/li&gt;&lt;/ol&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a Runepage and modify it to this runes.&lt;br/&gt;The page used is the first that:&lt;/p&gt;&lt;ol style=&quot;margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; -qt-list-indent: 1;&quot;&gt;&lt;li style=&quot; margin-top:12px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;matches this Runes&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;has a name starting with &amp;quot;AA:&amp;quot;&lt;/li&gt;&lt;li style=&quot; margin-top:0px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;is currently selected&lt;/li&gt;&lt;/ol&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>Apply Runes</source>
<translation>Apply Runes</translation>
</message>
</context>
<context>
<name>RuneEditor</name>
<message>
<source>Runepage Editor</source>
<translation>Runepage Editor</translation>
</message>
</context>
<context>
<name>RuneManager</name>
<message>
<source>Runes in the Client</source>
<translation>Runes in the Client</translation>
</message>
<message>
<source>Runes in the Autoacceptor</source>
<translation>Runes in the Autoacceptor</translation>
</message>
<message>
<source>Reload</source>
<translation>Reload</translation>
</message>
<message>
<source>Runes from the client get copied to the autoacceptor automatically.</source>
<translation>Runes from the client get copied to the autoacceptor automatically.</translation>
</message>
<message>
<source>Auto Copy Runes</source>
<translation>Auto copy Runes</translation>
</message>
</context>
<context>
<name>RunePageList</name>
<message>
<source>Edit</source>
<translation>Edit</translation>
</message>
<message>
<source>Duplicate</source>
<translation>Duplicate</translation>
</message>
<message>
<source>Export</source>
<translation>Export</translation>
</message>
<message>
<source>Import</source>
<translation>Import</translation>
</message>
<message>
<source>Delete</source>
<translation>Delete</translation>
</message>
<message>
<source>Loading runes</source>
<translation>Loading runes</translation>
</message>
<message>
<source>with</source>
<translation>with</translation>
</message>
</context>
<context>
<name>SettingsTab</name>
<message>
<source>Ban</source>
<translation>Ban</translation>
</message>
<message>
<source>Pick</source>
<translation>Pick</translation>
</message>
</context>
<context>
<name>StageSettings</name>
<message>
<source>Champion:</source>
<translation type="vanished">Champion:</translation>
</message>
<message>
<source>Enable %1</source>
<translation>Enable %1</translation>
</message>
<message>
<source>Champions matched: %1</source>
<translation type="vanished">Champions matched: %1</translation>
</message>
<message>
<source>Champion: %1
Type: %2
Title: %3
ID: %4</source>
<translation type="vanished">Champion: %1
Type: %2
Title: %3
ID: %4</translation>
</message>
<message>
<source>Add Champion</source>
<translation>Add Champion</translation>
</message>
<message>
<source>Remove Champion</source>
<translation>Remove Champion</translation>
</message>
</context>
</TS>

View File

@ -9,6 +9,7 @@ Args parseArgs(int argc, char** argv) {
while (1) { while (1) {
static struct option long_options[] = { static struct option long_options[] = {
{"debug-log", no_argument, &a.debugLog, 1}, {"debug-log", no_argument, &a.debugLog, 1},
{"access", no_argument, &a.access, 1},
{0, 0, 0, 0} {0, 0, 0, 0}
}; };
/* getopt_long stores the option index here. */ /* getopt_long stores the option index here. */

140
src/blitzapi.cpp Normal file
View File

@ -0,0 +1,140 @@
#include "blitzapi.h"
#include <QJsonArray>
#include <QJsonDocument>
#include <Log.h>
#include "json.h"
// curl 'https://league-champion-aggregate.iesdev.com/graphql?query=query%20ChampionBuilds%28%24championId%3AInt%21%2C%24queue%3AQueue%21%2C%24role%3ARole%2C%24opponentChampionId%3AInt%2C%24key%3AChampionBuildKey%29%7BchampionBuildStats%28championId%3A%24championId%2Cqueue%3A%24queue%2Crole%3A%24role%2CopponentChampionId%3A%24opponentChampionId%2Ckey%3A%24key%29%7BchampionId%20opponentChampionId%20queue%20role%20builds%7BcompletedItems%7Bgames%20index%20averageIndex%20itemId%20wins%7Dgames%20mythicId%20mythicAverageIndex%20primaryRune%20runes%7Bgames%20index%20runeId%20wins%20treeId%7DskillOrders%7Bgames%20skillOrder%20wins%7DstartingItems%7Bgames%20startingItemIds%20wins%7DsummonerSpells%7Bgames%20summonerSpellIds%20wins%7Dwins%7D%7D%7D&variables=%7B%22championId%22%3A25%2C%22role%22%3A%22SUPPORT%22%2C%22queue%22%3A%22RANKED_SOLO_5X5%22%2C%22opponentChampionId%22%3Anull%2C%22key%22%3A%22PUBLIC%22%7D' -H 'Accept: application/json'
// query=query ChampionBuilds($championId:Int!,$queue:Queue!,$role:Role,$opponentChampionId:Int,$key:ChampionBuildKey){championBuildStats(championId:$championId,queue:$queue,role:$role,opponentChampionId:$opponentChampionId,key:$key){championId opponentChampionId queue role builds{completedItems{games index averageIndex itemId wins}games mythicId mythicAverageIndex primaryRune runes{games index runeId wins treeId}skillOrders{games skillOrder wins}startingItems{games startingItemIds wins}summonerSpells{games summonerSpellIds wins}wins}}}
// &variables={"championId":25,"role":"SUPPORT","queue":"RANKED_SOLO_5X5","opponentChampionId":null,"key":"PUBLIC"}
static const QString POSITIONNAMES[] = {"INVALID", "TOP", "JUNGLE", "MIDDLE", "BOTTOM", "SUPPORT"};
BlitzAPI::BlitzAPI() : RestClient("https://league-champion-aggregate.iesdev.com/graphql?") {}
BlitzAPI::ChampionInfo::ChampionInfo() {}
BlitzAPI::ChampionInfo::ChampionInfo(const QJsonObject& json) {
// kill order stuff
auto skillordersref = json["skillOrders"];
if(skillordersref.isArray()) {
QJsonArray arr = skillordersref.toArray();
if(!arr.empty()) {
auto skillorderref = arr.at(0);
if(skillorderref.isObject()) {
QJsonObject skillorder = skillorderref.toObject();
QJsonValueRef realorderref = skillorder["skillOrder"];
if(realorderref.isArray()) {
QJsonArray realorder = realorderref.toArray();
this->skillorder.reserve(realorder.size());
for(auto it : realorder) {
if(it.isDouble()) {
this->skillorder.push_back(it.toDouble());
}
}
}
}
}
}
// runes
// TODO: create a diffrent algorithm to choose wich runes should be picked, insted of just the first one
QJsonValue runesarrref = json["runes"];
std::vector<uint32_t>& runes = runepage.selectedAspects;
if(runesarrref.isArray()) {
QJsonArray runesarr = runesarrref.toArray();
runes.clear();
runes.resize(9, 0);// a list of runes, that are taken (the first one is a speare for the primary one)
for(auto it : runesarr) {
if(!it.isObject()) continue;
QJsonObject rune = it.toObject();
uint32_t index = rune["index"].toInt();
if(index <= 7) {
if(runes.at(index+1) == 0) { // index not set yet
auto runeid = rune["runeId"];
if(runeid.isDouble()) {
runes.at(index+1) = runeid.toDouble();
qDebug() << "found rune: index: " << index << " +1 set to: " << runes.at(index+1);
if(index == 0) {
runepage.primaryStyle = rune["treeId"].toInt();
} else if(index == 3) {
runepage.secondaryStyle = rune["treeId"].toInt();
}
}
}
}
}
}
// add the primary rune
runes.at(0) = getValue<uint32_t>(json, "primaryRune");
}
BlitzAPI::ChampionInfo BlitzAPI::getChampionInfo(uint32_t championID, Position p, uint32_t enemyChampionID) {
QJsonObject vars;
vars["championId"] = (int) championID;
if(p != Position::INVALID) {
vars["role"] = POSITIONNAMES[(int) p];
}
vars["queue"] = "RANKED_SOLO_5X5";
if(enemyChampionID == 0)
vars["opponentChampionId"] = QJsonValue::Null;
else
vars["opponentChampionId"] = (int) enemyChampionID;
vars["key"] = "PUBLIC"; // ? what does this do?
QJsonDocument jvars(vars);
const QString variables = jvars.toJson(QJsonDocument::Compact);
const QString query = "query ChampionBuilds($championId:Int!,$queue:Queue!,$role:Role,$opponentChampionId:Int,$key:ChampionBuildKey){championBuildStats(championId:$championId,queue:$queue,role:$role,opponentChampionId:$opponentChampionId,key:$key){championId opponentChampionId queue role builds{completedItems{games index averageIndex itemId wins}games mythicId mythicAverageIndex primaryRune runes{games index runeId wins treeId}skillOrders{games skillOrder wins}startingItems{games startingItemIds wins}summonerSpells{games summonerSpellIds wins}wins}}}";
const QString requeststr = "query=" + escape(query) + "&variables=" + escape(variables);
qDebug() << "GetChampionInfo requestVariables: " << variables << " requeststr: " << requeststr;
QJsonDocument doc;
try {
doc = request(requeststr);
} catch(RestClient::WebException& e) {
return {};
}
if(!doc.isObject()) {
// error
qCritical() << "could not get ChampionInfo. Returned Response: " << doc.toJson();
return {};
}
qDebug() << "championinfo Response: " << doc.toJson().trimmed();
QJsonObject obj = doc.object();
QJsonValueRef dataref = obj["data"];
if(!dataref.isObject()) return {};
QJsonObject data = dataref.toObject();
QJsonValueRef buildstatsref = data["championBuildStats"];
if(!buildstatsref.isObject()) return{};
QJsonObject buildstats = buildstatsref.toObject();
QJsonValueRef buildsref = buildstats["builds"];
if(!buildsref.isArray()) return {};
QJsonArray builds = buildsref.toArray();
if(builds.size() > 0) {
// just take the first
QJsonValue buildval = builds.at(0);
if(buildval.isObject()) {
return (ChampionInfo) buildval.toObject();
}
}
return {};
}

54
src/champcache.cpp Normal file
View File

@ -0,0 +1,54 @@
#include "champcache.h"
#include "files.h"
#include <QFile>
#include <QFileInfo>
#include <QDateTime>
#include <Log.h>
ChampCache::ChampCache() {
basefolder = getCache();
}
// the age of f in seconds
static qint64 ageOfFile(QFile& f) {
QFileInfo info(f);
return info.lastModified().secsTo(QDateTime::currentDateTime());
}
QString ChampCache::getVersion() {
QFile versionfile(basefolder + "version");
if(ageOfFile(versionfile) < (qint64) maxage) {
versionfile.open(QFile::ReadOnly);
return versionfile.readAll();
}
return {}; // empty string
}
QJsonDocument ChampCache::getChamps() {
QFile champsfile(basefolder + "champs.json");
if(ageOfFile(champsfile) < (qint64) maxage) {
champsfile.open(QFile::ReadOnly);
QByteArray bytes = champsfile.readAll();
QJsonDocument doc = QJsonDocument::fromJson(bytes);
return doc;
}
return {}; // empty document
}
void ChampCache::saveChamps(QJsonDocument doc, const QString& version) {
QByteArray arr = doc.toJson();
QFile champsfile(basefolder + "champs.json");
champsfile.open(QFile::WriteOnly | QFile::Truncate);
champsfile.write(arr);
QFile versionfile(basefolder + "version");
versionfile.open(QFile::WriteOnly | QFile::Truncate);
versionfile.write(version.toLocal8Bit());
versionfile.close();
qInfo() << "saved Champs and version Cache";
}

View File

@ -20,17 +20,17 @@ ChampRow* ChampionSearch::getSearchResult() {
} }
void ChampionSearch::searchChanged(QString str) { void ChampionSearch::searchChanged(QString str) {
Log::info << "champion search: " << str.toStdString(); qInfo() << "champion search: " << str;
auto champs = dd->getMatchingChamp(str.toStdString()); const std::vector<const DataDragon::ChampData*> champs = dd->getMatchingChamp(str);
Log::info << "found " << champs.size() << " champs"; qInfo() << "found " << champs.size() << " champs";
clear(); clear();
for(auto it : champs) { for(auto cd : champs) {
dd->getImageAsnyc(it->id, [this, it](QPixmap img) { dd->getImageAsnyc(cd->id, [this, cd](QPixmap img) {
auto cr = new ChampRow(); auto cr = new ChampRow();
cr->setChamp(it->name, it->key, img); cr->setChamp(*cd, img);
ui->championList->addItem(cr); ui->championList->addItem(cr);
}); });
} }

View File

@ -6,11 +6,13 @@ ChampRow::ChampRow(QListWidget* parent) : QListWidgetItem(parent) {
ChampRow::~ChampRow() { ChampRow::~ChampRow() {
} }
void ChampRow::setChamp(const std::string& name, uint32_t id, QPixmap icon) { void ChampRow::setChamp(const DataDragon::ChampData& cd, QPixmap icon) {
setText(QString::fromStdString(name)); setText(cd.name);
champid = id; champid = cd.key;
this->icon = icon; this->icon = icon;
setIcon(QIcon(icon)); setIcon(QIcon(icon));
setToolTip(QWidget::tr("Champion: %1\nType: %2\nTitle: %3\nID: %4").arg(cd.name).arg(cd.partype).arg(cd.title).arg(cd.key));
} }
QString ChampRow::getChamp() const { QString ChampRow::getChamp() const {

View File

@ -1,187 +1,47 @@
#include "clientaccess.h" #include "clientaccess.h"
#include <cstring> #include <fstream>
#include <dirent.h> #include <iomanip>
#include <fcntl.h>
#include <sys/types.h> #include <QDebug>
#include <sys/stat.h> #include <QStringList>
#include <sys/mman.h>
#include <unistd.h> ClientAccess::ClientAccess() {}
#include <algorithm> ClientAccess::ClientAccess(const QString& token, uint16_t port) : authcode(token), port(port) {}
#include <fstream>
#include <iomanip> std::shared_ptr<ClientAccess> createFromLockfile(std::istream& lockfile) {
#include <string>
#include <vector> std::string content;
std::getline(lockfile, content);
#include <Log.h> QStringList parts = QString::fromStdString(content).split(':');
static bool endsWith(const std::string& all, const std::string& end) { if(parts.size() != 5) {
return all.rfind(end) == all.size() - end.size(); qCritical() << "lockfile contained " << parts.size() << " parts, expected 5";
} return {};
}
// reads a procfile into a vector (strings are \0 seperated)
static std::vector<std::string> readProcFile(const std::string& path) { const QString portstr = parts.at(2);
std::ifstream in(path); const QString token = parts.at(3);
std::vector<std::string> out;
std::string line; // try to parse port
while(std::getline(in, line, '\0')) { bool success = false;
if(!line.empty()) { uint16_t port = portstr.toUInt(&success);
out.push_back(line); if(!success) {
} qCritical() << "could not parse port: " << portstr;
} return nullptr;
return out; }
} return std::shared_ptr<ClientAccess>(new ClientAccess(token, port));
}
ClientAccess::ClientAccess() {}
QString ClientAccess::getBasicAuth() const {
ClientAccess::ClientAccess(const std::string& token, uint16_t port) : authcode(token), port(port) {} return "riot:" + authcode;
}
std::shared_ptr<ClientAccess> ClientAccess::find(bool uselockfile) {
uint16_t ClientAccess::getPort() const {
DIR* procdir = opendir("/proc"); return port;
if(!procdir) return nullptr; }
dirent* entry = nullptr; QString ClientAccess::getURL() const {
while ((entry = readdir(procdir)) != NULL) { return "https://127.0.0.1:" + QString::number(port) + "/";
if (entry->d_type != DT_DIR) continue; }
std::string name(entry->d_name);
pid_t pid = -1;
try {
pid = std::stoi(name);
} catch(std::exception& e) {
// could not parse -> not a pid
continue;
}
// get info on the exe
std::string cmdfile = "/proc/" + std::to_string(pid) + "/cmdline";
std::vector<std::string> args = readProcFile(cmdfile);
// Log::debug << "process: " << pid << " has " << args.size() << " args";
if(args.empty()) continue;
std::string& exename = args.at(0);
if(endsWith(exename, "LeagueClientUx.exe")) {
Log::info << "LeagueClientUx.exe found";
std::shared_ptr<ClientAccess> out;
if(uselockfile) {
out = findUsingLockfile(args, pid);
} else {
out = findUsingArgs(args);
}
if(out) {
return out;
}
}
}
closedir(procdir);
return nullptr;
}
std::string ClientAccess::parseArg(const std::string& input, std::string& value) {
if(input.find("--") != 0) return {};
size_t pos = input.find('=');
if(pos == std::string::npos) return {};
value = input.substr(pos+1);
return input.substr(2, pos-2);
}
std::shared_ptr<ClientAccess> ClientAccess::findUsingArgs(const std::vector<std::string>& cmdline) {
// parse args
std::shared_ptr<ClientAccess> access(new ClientAccess());
for(const std::string& arg : cmdline) {
std::string value;
std::string argname = parseArg(arg, value);
if(argname == "riotclient-auth-token") {
access->authcode = value;
} else if(argname == "riotclient-app-port") {
try {
access->port = std::stoi(value);
} catch(std::exception& e) {
Log::warn << "could not parse port: " << std::quoted(value);
}
}
}
if(access->port > 0 && !access->authcode.empty()) {
return access;
}
return nullptr;
}
std::shared_ptr<ClientAccess> ClientAccess::findUsingLockfile(const std::vector<std::string>& cmdline, pid_t pid) {
// get WINEPREFIX env
std::vector<std::string> envs = readProcFile("/proc/" + std::to_string(pid) + "/environ");
const static std::string WINEPREFIX = "WINEPREFIX=";
// find WINEPREFIX
auto found = std::find_if(envs.begin(), envs.end(), [](const std::string& s) {
return s.find(WINEPREFIX) == 0;
});
// WINEPREFIX env not present
if(found == envs.end()) {
Log::debug << "WINEPREFIX environment variable not set";
return {};
}
const std::string wineprefix = found->substr(WINEPREFIX.size());
const std::string binarypath = cmdline.at(0);
std::string gamefolder = binarypath.substr(2, binarypath.rfind('/')-1); // remove the "C:" and the name of the binary
// TODO: gamefoldre could contain '\' so replacing every occurance with '/' would be a good idea
const std::string lockfilepath = wineprefix + "/drive_c/" + gamefolder + "/lockfile";
Log::debug << "lockfilepath: " << std::quoted(lockfilepath);
// read lockfile
std::ifstream lockfile(lockfilepath);
std::vector<std::string> parts;
std::string content;
while(std::getline(lockfile, content, ':')) {
parts.push_back(content);
}
if(parts.size() != 5) {
Log::error << "lockfile contained " << parts.size() << " parts, expected 5";
return {};
}
const std::string portstr = parts.at(2);
const std::string token = parts.at(3);
// try to parse port
try {
uint16_t port = std::stoi(portstr);
return std::shared_ptr<ClientAccess>(new ClientAccess(token, port));
} catch(std::exception& e) {
Log::error << "could not parse port: " << std::quoted(portstr);
}
return {};
}
std::string ClientAccess::getBasicAuth() const {
return "riot:" + authcode;
}
uint16_t ClientAccess::getPort() const {
return port;
}
std::string ClientAccess::getURL() const {
return "https://127.0.0.1:" + std::to_string(port) + "/";
}

120
src/clientaccess_linux.cpp Normal file
View File

@ -0,0 +1,120 @@
#include "clientaccess.h"
#include <cstring>
# include <sys/mman.h>
# include <dirent.h>
# include <fcntl.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <unistd.h>
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <string>
#include <vector>
#include <Log.h>
#include "defer.h"
static const QString CLIENTNAME = "LeagueClientUx.exe"; // returns the name and value of a argument or empty string if it could not be parsed
static std::shared_ptr<ClientAccess> findUsingLockfile(const std::vector<QString>& cmdline, pid_t pid);
// reads a procfile into a vector (strings are \0 seperated)
static std::vector<QString> readProcFile(const QString& path) {
std::ifstream in(path.toStdString());
std::vector<QString> out;
std::string line;
while(std::getline(in, line, '\0')) {
if(!line.empty()) {
out.push_back(QString::fromStdString(line));
}
}
return out;
}
// test server
#if 0
std::shared_ptr<ClientAccess> ClientAccess::find() {
return std::make_shared<ClientAccess>("password", 4443);
}
#else
std::shared_ptr<ClientAccess> ClientAccess::find() {
DIR* procdir = opendir("/proc");
if(!procdir) return nullptr;
defer( closedir(procdir) );
dirent* entry = nullptr;
while ((entry = readdir(procdir)) != NULL) {
if (entry->d_type != DT_DIR) continue;
QString name(entry->d_name);
pid_t pid = -1;
bool success = false;
pid = name.toULong(&success);
if(!success) {
// could not parse -> not a pid
continue;
}
// get info on the exe
QString cmdfile = "/proc/" + QString::number(pid) + "/cmdline";
std::vector<QString> args = readProcFile(cmdfile);
// qDebug() << "process: " << pid << " has " << args.size() << " args";
if(args.empty()) continue;
QString& exename = args.at(0);
if(exename.endsWith(CLIENTNAME)) {
qInfo() << CLIENTNAME << " found: " << exename;
std::shared_ptr<ClientAccess> out;
out = findUsingLockfile(args, pid);
if(out) {
return out;
}
}
}
return nullptr;
}
#endif
std::shared_ptr<ClientAccess> findUsingLockfile(const std::vector<QString>& cmdline, pid_t pid) {
// get WINEPREFIX env
std::vector<QString> envs = readProcFile("/proc/" + QString::number(pid) + "/environ");
const static QString WINEPREFIX = "WINEPREFIX=";
// find WINEPREFIX
auto found = std::find_if(envs.begin(), envs.end(), [](const QString& s) {
return s.startsWith(WINEPREFIX);
});
// WINEPREFIX env not present
if(found == envs.end()) {
qDebug() << "WINEPREFIX environment variable not set";
return {};
}
const QString wineprefix = found->remove(0, WINEPREFIX.size());
const QString binarypath = cmdline.at(0);
QString gamefolder = binarypath.mid(2, binarypath.lastIndexOf('/')-1); // remove the "C:" and the name of the binary
// TODO: gamefoldre could contain '\' so replacing every occurance with '/' would be a good idea
const QString lockfilepath = wineprefix + "/drive_c/" + gamefolder + "/lockfile";
qDebug() << "lockfilepath: " << lockfilepath;
// read lockfile
std::ifstream lockfile(lockfilepath.toStdString());
return createFromLockfile(lockfile);
}

View File

@ -0,0 +1,114 @@
#include "clientaccess.h"
#include <cstring>
# include <windows.h>
# include <tlhelp32.h>
# include <tchar.h>
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <string>
#include <vector>
#include <Log.h>
#include "defer.h"
static const QString CLIENTNAME = "LeagueClientUx.exe";
static QString narrow(WCHAR* str, size_t len) {
QString out;
out.reserve(len);
for(uint32_t i = 0; i < len && str[i]; ++i) {
out.append(1, (char) str[i]);
}
return out;
}
static QString getProcessPath(DWORD dwPID) {
// Take a snapshot of all modules in the specified process.
HANDLE hModuleSnap = CreateToolhelp32Snapshot( TH32CS_SNAPMODULE, dwPID );
if(hModuleSnap == INVALID_HANDLE_VALUE) {
qCritical() << "CreateToolhelp32Snapshot (of modules) failed";
return {}; // empty string
}
defer( CloseHandle(hModuleSnap) );
// Set the size of the structure before using it.
MODULEENTRY32 me32;
me32.dwSize = sizeof(MODULEENTRY32);
// Retrieve information about the first module,
// and exit if unsuccessful
if( !Module32First( hModuleSnap, &me32 ) ) {
qCritical() << "Module32First";
return {};
}
return narrow((WCHAR*) me32.szExePath, sizeof(me32.szExePath));
}
static std::shared_ptr<ClientAccess> findUsingLockfile(PROCESSENTRY32& proc) {
const QString exepath = getProcessPath(proc.th32ProcessID);
Log::note << "exepath: " << exepath;
// lockfile path
const QString lockfilepath = exepath.substr(0, exepath.rfind('\\')+1) + "lockfile"; // possible out of bounds
qDebug() << "Lockfile: " << lockfilepath;
std::ifstream lockfile(lockfilepath);
if(!lockfile) {
qCritical() << "lockfile could not be opend";
return nullptr;
}
return createFromLockfile(lockfile);
}
std::shared_ptr<ClientAccess> ClientAccess::find() {
// example code: https://docs.microsoft.com/de-de/windows/win32/toolhelp/taking-a-snapshot-and-viewing-processes
// Take a snapshot of all processes in the system.
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
qCritical() << "CreateToolhelp32Snapshot (of processes) failed";
return nullptr;
}
defer( CloseHandle(hProcessSnap) );
PROCESSENTRY32 pe32;
// Set the size of the structure before using it.
pe32.dwSize = sizeof(PROCESSENTRY32);
// Retrieve information about the first process,
// and exit if unsuccessful
if (!Process32First(hProcessSnap, &pe32)) {
qCritical() << "Process32First failed"; // show cause of failure
return nullptr;
}
// Now walk the snapshot of processes, and
// display information about each process in turn
do {
QString exename = narrow((WCHAR*) pe32.szExeFile, sizeof(pe32.szExeFile));
// Log::note << "found process: " << exename;
if(exename == CLIENTNAME) {
qInfo() << CLIENTNAME << " found";
std::shared_ptr<ClientAccess> out;
out = findUsingLockfile(pe32);
if(out) {
return out;
}
}
} while(Process32Next(hProcessSnap, &pe32));
return nullptr;
}

View File

@ -9,7 +9,7 @@
#include "json.h" #include "json.h"
ClientAPI::ClientAPI(const ClientAccess& ca) : RestClient(ca.getURL()), access(ca) { ClientAPI::ClientAPI(const ClientAccess& ca) : RestClient(ca.getURL()), access(ca), memImageCache(40), imageCache("runes", "") {
basicauth = ca.getBasicAuth(); basicauth = ca.getBasicAuth();
disableCertCheck = true; disableCertCheck = true;
// enableDebugging(); // enableDebugging();
@ -30,9 +30,9 @@ ClientAPI::GameflowPhase ClientAPI::getGameflowPhase() {
// it is just a json-string no object // it is just a json-string no object
QByteArray data = requestRaw("lol-gameflow/v1/gameflow-phase"); QByteArray data = requestRaw("lol-gameflow/v1/gameflow-phase");
std::string datastr = data.toStdString(); QString datastr = QString::fromLocal8Bit(data);
if (data.size() > 2) { if (data.size() > 2) {
datastr = datastr.substr(1, datastr.size() -2); datastr = datastr.mid(1, datastr.size() -2);
return toGameflowPhase(datastr); return toGameflowPhase(datastr);
} }
@ -63,25 +63,25 @@ bool ClientAPI::setChampSelectAction(int32_t actionid, int32_t champid, bool com
requestj["completed"] = completed; requestj["completed"] = completed;
requestj["id"] = actionid; requestj["id"] = actionid;
QJsonDocument requestdoc(requestj); QJsonDocument requestdoc(requestj);
const std::string requeststr = requestdoc.toJson(QJsonDocument::JsonFormat::Compact).toStdString(); const QString requeststr = QString::fromLocal8Bit(requestdoc.toJson(QJsonDocument::JsonFormat::Compact));
Log::debug << "requeststr: " << requeststr; qDebug().noquote() << "requeststr: " << requeststr;
QJsonDocument doc = request("lol-champ-select/v1/session/actions/" + std::to_string(actionid), Method::PATCH, requeststr); QJsonDocument doc = request("lol-champ-select/v1/session/actions/" + QString::number(actionid), Method::PATCH, requeststr);
std::string error; QString error;
if(doc.isObject()) { if(doc.isObject()) {
QJsonObject obj = doc.object(); QJsonObject obj = doc.object();
auto errref = obj["errorCode"]; auto errref = obj["errorCode"];
auto msgref = obj["message"]; auto msgref = obj["message"];
if(errref.isString()) { if(errref.isString()) {
error = errref.toString().toStdString() + " "; error = errref.toString() + " ";
} }
if(msgref.isString()) { if(msgref.isString()) {
error += msgref.toString().toStdString(); error += msgref.toString();
} }
} }
Log::note << "patching action: " << actionid << " error: " << error; qDebug() << "patching action: " << actionid << " error: " << error;
return error.empty(); return error.isEmpty();
} }
ClientAPI::PlayerInfo ClientAPI::getSelf() { ClientAPI::PlayerInfo ClientAPI::getSelf() {
@ -91,14 +91,15 @@ ClientAPI::PlayerInfo ClientAPI::getSelf() {
QJsonObject obj = doc.object(); QJsonObject obj = doc.object();
PlayerInfo info; PlayerInfo info;
info.gameName = getValue<std::string>(obj, "gameName"); info.gameName = getValue<QString>(obj, "gameName");
info.name = getValue<std::string>(obj, "name"); info.name = getValue<QString>(obj, "name");
info.statusMessage = getValue<std::string>(obj, "statusMessage", ""); info.statusMessage = getValue<QString>(obj, "statusMessage", "");
info.summonerid = getValue<uint64_t>(obj, "summonerId");
auto lolref = obj["lol"]; auto lolref = obj["lol"];
if(lolref.isObject()) { if(lolref.isObject()) {
QJsonObject lol = lolref.toObject(); QJsonObject lol = lolref.toObject();
info.puuid = getValue<std::string>(lol, "puuid"); info.puuid = getValue<QString>(lol, "puuid");
info.level = getValue<int32_t>(lol, "level"); info.level = getValue<int32_t>(lol, "level");
} }
@ -107,23 +108,9 @@ ClientAPI::PlayerInfo ClientAPI::getSelf() {
return {}; return {};
} }
std::vector<std::string> ClientAPI::getLog() { void ClientAPI::dodge() {
std::vector<std::string> out; QJsonDocument doc = request("lol-login/v1/session/invoke?destination=lcdsServiceProxy&method=call&args=[\"\",\"teambuilder-draft\",\"quitV2\", \"\"]", Method::POST, "");
QJsonDocument doc = request("LoggingGetEntries"); qDebug() << "dodge result:" << doc;
if(doc.isArray()) {
QJsonArray arr = doc.array();
for(auto it : arr) {
std::string message;
if(it.isObject()) {
QJsonObject logobj = it.toObject();
message = getValue<std::string>(logobj, "severity");
message += getValue<std::string>(logobj, "message");
}
out.push_back(message);
}
}
return out;
} }
static std::vector<int32_t> fromArrayToVector(const QJsonArray& arr) { static std::vector<int32_t> fromArrayToVector(const QJsonArray& arr) {
@ -165,3 +152,223 @@ ClientAPI::TimerInfo ClientAPI::getTimerInfo() {
return (TimerInfo) obj; return (TimerInfo) obj;
} }
std::vector<ClientAPI::Conversation> ClientAPI::getAllConversations() {
std::vector<Conversation> out;
QJsonDocument doc = request("lol-chat/v1/conversations");
if(doc.isArray()) {
QJsonArray arr = doc.array();
out.reserve(arr.size());
for(auto elem : arr) {
if(elem.isObject()) {
out.push_back((Conversation) elem.toObject());
}
}
}
return out;
}
ClientAPI::Message ClientAPI::sendMessage(const QString& chatid, const QString& messagebody) {
QJsonObject requestj;
requestj["body"] = messagebody;
QJsonDocument requestdoc(requestj);
const QString requeststr = QString::fromLocal8Bit(requestdoc.toJson(QJsonDocument::JsonFormat::Compact));
qDebug().noquote() << "requeststr: " << requeststr;
QJsonDocument doc = request("lol-chat/v1/conversations/" + chatid + "/messages", Method::POST, requeststr);
QString error;
if(doc.isObject()) {
QJsonObject obj = doc.object();
auto errref = obj["errorCode"];
auto msgref = obj["message"];
if(errref.isString()) {
error = errref.toString() + " ";
}
if(msgref.isString()) {
error += msgref.toString();
}
if(error.isEmpty()) {
return (Message) obj;
}
}
qDebug() << "send message error: " << error;
return {};
}
ClientAPI::RunePage ClientAPI::getCurrentRunePage() {
QJsonDocument doc = request("lol-perks/v1/currentpage");
if(!doc.isObject()) return {};
return (RunePage) doc.object();
}
std::vector<ClientAPI::RunePage> ClientAPI::getAllRunePages() {
QJsonDocument doc = request("lol-perks/v1/pages");
if(!doc.isArray()) return {};
QJsonArray arr = doc.array();
std::vector<RunePage> out;
out.reserve(arr.size());
for(auto it : arr) {
if(it.isObject()) {
out.push_back((RunePage) it.toObject());
}
}
return out;
}
bool ClientAPI::selectRunePage(uint64_t id) {
QJsonDocument doc = request("lol-perks/v1/currentpage", Method::PUT, QString::number(id));
if(doc.isEmpty()) return true; // ok
// error
qWarning() << "error selecting runepage: " << id << " " << doc.toJson();
return false;
}
bool ClientAPI::editRunePage(const RunePage& page) {
QJsonObject pagereq;
pagereq["name"] = page.name;
pagereq["primaryStyleId"] = (int) page.runepage.primaryStyle;
pagereq["subStyleId"] = (int) page.runepage.secondaryStyle;
QJsonArray selected;
for(uint32_t sel : page.runepage.selectedAspects) {
selected.push_back((int) sel);
}
pagereq["selectedPerkIds"] = selected;
QJsonDocument reqdoc(pagereq);
const QString requestdocstr = QString::fromLocal8Bit(reqdoc.toJson(QJsonDocument::JsonFormat::Compact));
qInfo().noquote() << "requeststr: " << requestdocstr;
QJsonDocument doc = request("lol-perks/v1/pages/" + QString::number(page.id), Method::PUT, requestdocstr);
if(doc.isObject() && doc["isValid"].isBool() && doc["isValid"].toBool()) return true; // ok
// error
qWarning() << "error editing runepage: " << page.id << " " << doc.toJson();
return false;
}
bool ClientAPI::createRunePage(const RunePage& page) {
QJsonObject pagereq;
pagereq["name"] = page.name;
pagereq["primaryStyleId"] = (int) page.runepage.primaryStyle;
pagereq["subStyleId"] = (int) page.runepage.secondaryStyle;
QJsonArray selected;
for(uint32_t sel : page.runepage.selectedAspects) {
selected.push_back((int) sel);
}
pagereq["selectedPerkIds"] = selected;
QJsonDocument reqdoc(pagereq);
const QString requestdocstr = reqdoc.toJson(QJsonDocument::JsonFormat::Compact);
qInfo() << "requeststr: " << requestdocstr;
QJsonDocument doc = request("lol-perks/v1/pages/", Method::POST, requestdocstr);
if(doc.isObject() && doc["isValid"].isBool() && doc["isValid"].toBool()) return true; // ok
// error
qWarning() << "error creating runepage: " << page.name << " " << doc.toJson();
return false;
}
bool ClientAPI::deleteRunePage(uint64_t id) {
QJsonDocument doc = request("lol-perks/v1/pages/" + QString::number(id), Method::DELETE);
if(doc.isEmpty()) return true; // ok
// error
qWarning() << "error deleteing runepage: " << id << " " << doc.toJson();
return false;
}
std::vector<RuneAspekt> ClientAPI::getAllRuneAspekts() {
QJsonDocument doc = request("lol-perks/v1/perks");
if(!doc.isArray()) {
qWarning() << __PRETTY_FUNCTION__ << " doc is not array";
return {};
}
QJsonArray arr = doc.array();
std::vector<RuneAspekt> out;
out.reserve(arr.size());
for(auto it : arr) {
if(it.isObject()) {
out.push_back((RuneAspekt) it.toObject());
}
}
return out;
}
std::vector<RuneStyle> ClientAPI::getAllRuneStyles() {
QJsonDocument doc = request("lol-perks/v1/styles");
if(!doc.isArray()) {
qWarning() << __PRETTY_FUNCTION__ << " doc is not array";
return {};
}
QJsonArray arr = doc.array();
std::vector<RuneStyle> out;
out.reserve(arr.size());
for(auto it : arr) {
if(it.isObject()) {
out.push_back((RuneStyle) it.toObject());
}
}
return out;
}
QPixmap ClientAPI::getImageResource(QString path) {
if(path.isEmpty()) return {};
QString simplePath;
{
QString simplePathQ = path;
simplePath = simplePathQ.replace('/', '_');
}
// query mem cache
QPixmap img = memImageCache.getImage(path, 0);
if(!img.isNull()) return img;
// query HDD cache
img = imageCache.getImage(simplePath);
if(!img.isNull()) {
// update mem cache
memImageCache.addImage(img, path, 0);
return img;
}
qInfo() << "requesting: " << path;
QByteArray arr = requestRaw(path);
QPixmap out;
out.loadFromData(arr);
// store HDD cache
imageCache.addImageRaw(arr, simplePath);
// store memchache
memImageCache.addImage(out, path, 0);
return out;
}

View File

@ -1,6 +1,5 @@
#include "clientapi.h" #include "clientapi.h"
#include <iomanip>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
@ -10,18 +9,19 @@
// calculate the size of a constant sized array // calculate the size of a constant sized array
#define ARRSIZE(ARRNAME) (sizeof(ARRNAME) / sizeof(ARRNAME[0])) #define ARRSIZE(ARRNAME) (sizeof(ARRNAME) / sizeof(ARRNAME[0]))
#define ARR(NAME, ...) static const std::string NAME ## Names[] = {__VA_ARGS__}; \ #define ARR(NAME, ...) static const QString NAME ## Names[] = {__VA_ARGS__}; \
static const uint32_t NAME ## NamesCount = ARRSIZE(NAME ## Names); static const uint32_t NAME ## NamesCount = ARRSIZE(NAME ## Names);
ARR(ReadyCheckState, "Invalid", "None", "InProgress", "Accepted", "Declined"); ARR(ReadyCheckState, "Invalid", "None", "InProgress", "Accepted", "Declined")
ARR(GameflowPhase, "None", "Lobby", "Matchmaking", "CheckedIntoTournament", "ReadyCheck", "ChampSelect", "GameStart", "FailedToLaunch", "InProgress", "Reconnect", "WaitingForStats", "PreEndOfGame", "EndOfGame", "TerminatedInError"); ARR(GameflowPhase, "None", "Lobby", "Matchmaking", "CheckedIntoTournament", "ReadyCheck", "ChampSelect", "GameStart", "FailedToLaunch", "InProgress", "Reconnect", "WaitingForStats", "PreEndOfGame", "EndOfGame", "TerminatedInError")
ARR(ChampSelectPhase, "Invalid", "PLANNING", "BAN_PICK", "FINALIZATION"); ARR(ChampSelectPhase, "Invalid", "GAME_STARTING", "PLANNING", "BAN_PICK", "FINALIZATION")
ARR(Position, "Invalid", "top", "middle", "bottom", "jungle", "utility"); ARR(Position, "Invalid", "top", "jungle", "middle", "bottom", "utility")
ARR(ChampSelectActionType, "Invalid", "ban", "pick", "ten_bans_reveal"); ARR(ShortPosition, "", "Top", "Jgl", "Mid", "Bot", "Sup")
ARR(ChampSelectActionType, "Invalid", "ban", "pick", "ten_bans_reveal")
template<typename T> template<typename T>
static T mapEnum(const std::string& input, const std::string* names, uint32_t count, T defaul) { static T mapEnum(const QString& input, const QString* names, uint32_t count, T defaul) {
if(input.empty()) return defaul; if(input.isEmpty()) return defaul;
for (uint32_t i = 0; i < count; ++i) { for (uint32_t i = 0; i < count; ++i) {
if (names[i] == input) { if (names[i] == input) {
@ -29,16 +29,28 @@ static T mapEnum(const std::string& input, const std::string* names, uint32_t co
} }
} }
Log::warn << "no mapping of enum-string: " << std::quoted(input); qWarning() << "no mapping of enum-string: " << input << " using default: " << defaul << " type: " << typeid(T).name();
return defaul; return defaul;
} }
#define MAPENUM(VAR, ENUM, DEFAULT) \ #define MAPENUM(VAR, ENUM, DEFAULT) \
mapEnum(VAR, ENUM ## Names, ENUM ## NamesCount, ClientAPI:: ENUM :: DEFAULT) mapEnum(VAR, ENUM ## Names, ENUM ## NamesCount, ClientAPI:: ENUM :: DEFAULT)
template<typename T>
static std::vector<T> readVector(QJsonArray arr) {
std::vector<T> out;
out.reserve(arr.size());
for(auto it : arr) {
out.push_back(convert<T>((QJsonValue) it));
}
return out;
}
ClientAPI::ReadyCheckState ClientAPI::toReadyCheckState(const QJsonObject& obj) { ClientAPI::ReadyCheckState ClientAPI::toReadyCheckState(const QJsonObject& obj) {
std::string searchState = getValue<std::string>(obj, "state", "Invalid"); QString searchState = getValue<QString>(obj, "state", "Invalid");
std::string playerresponse = getValue<std::string>(obj, "playerResponse", "None"); QString playerresponse = getValue<QString>(obj, "playerResponse", "None");
ClientAPI::ReadyCheckState response = MAPENUM(playerresponse, ReadyCheckState, NONE); ClientAPI::ReadyCheckState response = MAPENUM(playerresponse, ReadyCheckState, NONE);
@ -52,19 +64,27 @@ ClientAPI::ReadyCheckState ClientAPI::toReadyCheckState(const QJsonObject& obj)
return response; return response;
} }
ClientAPI::GameflowPhase ClientAPI::toGameflowPhase(const std::string& str) { ClientAPI::GameflowPhase ClientAPI::toGameflowPhase(const QString& str) {
return MAPENUM(str, GameflowPhase, NONE); return MAPENUM(str, GameflowPhase, NONE);
} }
ClientAPI::ChampSelectPhase ClientAPI::toChampSelectPhase(const std::string& str) { ClientAPI::ChampSelectPhase ClientAPI::toChampSelectPhase(const QString& str) {
return MAPENUM(str, ChampSelectPhase, INVALID); return MAPENUM(str, ChampSelectPhase, INVALID);
} }
ClientAPI::Position ClientAPI::toPosition(const std::string& str) { Position toPosition(const QString& str) {
return MAPENUM(str, Position, INVALID); return mapEnum(str, PositionNames, PositionNamesCount, Position::INVALID);
} }
ClientAPI::ChampSelectActionType ClientAPI::toChampSelectActionType(const std::string& str) { QString toString(Position p) {
return PositionNames[(int) p];
}
QString toShortString(Position p) {
return ShortPositionNames[(int) p];
}
ClientAPI::ChampSelectActionType ClientAPI::toChampSelectActionType(const QString& str) {
return MAPENUM(str, ChampSelectActionType, INVALID); return MAPENUM(str, ChampSelectActionType, INVALID);
} }
@ -74,7 +94,7 @@ ClientAPI::TimerInfo::TimerInfo(const QJsonObject& obj) {
internalNowInEpochMs = getValue<int64_t>(obj, "internalNowInEpochMs", 0); internalNowInEpochMs = getValue<int64_t>(obj, "internalNowInEpochMs", 0);
isefinite = getValue<bool>(obj, "isefinite", 0); isefinite = getValue<bool>(obj, "isefinite", 0);
phase = MAPENUM(getValue<std::string>(obj, "phase", "Invalid"), ChampSelectPhase, INVALID); phase = MAPENUM(getValue<QString>(obj, "phase", "Invalid"), ChampSelectPhase, INVALID);
totalTimeInPhase = getValue<int64_t>(obj, "totalTimeInPhase", 0); totalTimeInPhase = getValue<int64_t>(obj, "totalTimeInPhase", 0);
} }
@ -87,17 +107,19 @@ ClientAPI::ChampSelectAction::ChampSelectAction(const QJsonObject& json) {
id = getValue<int32_t>(json, "id"); id = getValue<int32_t>(json, "id");
isAllyAction = getValue<bool>(json, "isAllyAction"); isAllyAction = getValue<bool>(json, "isAllyAction");
isInProgress = getValue<bool>(json, "isInProgress"); isInProgress = getValue<bool>(json, "isInProgress");
const std::string typestr = getValue<std::string>(json, "type", "Invalid"); const QString typestr = getValue<QString>(json, "type", "Invalid");
type = toChampSelectActionType(typestr); type = toChampSelectActionType(typestr);
} }
ClientAPI::ChampSelectCell::ChampSelectCell() {} ClientAPI::ChampSelectCell::ChampSelectCell() {}
ClientAPI::ChampSelectCell::ChampSelectCell(const QJsonObject& json) { ClientAPI::ChampSelectCell::ChampSelectCell(const QJsonObject& json) {
position = toPosition(getValue<std::string>(json, "assignedPosition")); position = toPosition(getValue<QString>(json, "assignedPosition"));
cellID = getValue<int32_t>(json, "cellId"); cellID = getValue<int32_t>(json, "cellId");
championID = getValue<int32_t>(json, "championId"); championID = getValue<int32_t>(json, "championId");
championPickIntentID = getValue<int32_t>(json, "championPickIntent"); championPickIntentID = getValue<int32_t>(json, "championPickIntent");
summonerID = getValue<int32_t>(json, "summonerId"); summonerID = getValue<int32_t>(json, "summonerId");
spell1Id = getValue<int64_t>(json, "spell1Id", 0);
spell2Id = getValue<int64_t>(json, "spell2Id", 0);
} }
std::vector<ClientAPI::ChampSelectCell> ClientAPI::loadAllInfos(const QJsonArray& arr) { std::vector<ClientAPI::ChampSelectCell> ClientAPI::loadAllInfos(const QJsonArray& arr) {
@ -166,16 +188,122 @@ ClientAPI::ChampSelectSession::operator bool() {
return gameid != 0; return gameid != 0;
} }
ClientAPI::RunePage::RunePage() {}
ClientAPI::RunePage::RunePage(const QJsonObject& json) {
id = getValue<int32_t>(json, "id", 0);
lastmodified = getValue<int64_t>(json, "lastModified", 0);
runepage.primaryStyle = getValue<int32_t>(json, "primaryStyleId", 0);
runepage.secondaryStyle = getValue<int32_t>(json, "subStyleId", 0);
name = getValue<QString>(json, "name");
isDeleteable = getValue<bool>(json, "isDeletable", false);
isEditable = getValue<bool>(json, "isEditable", false);
isActive = getValue<bool>(json, "isActive", false);
isCurrent = getValue<bool>(json, "current", false);
isValid = getValue<bool>(json, "isValid", false);
order = getValue<int32_t>(json, "order", 0);
auto selectedref = json["selectedPerkIds"];
if(selectedref.isArray()) {
runepage.selectedAspects = readVector<uint32_t>(selectedref.toArray());
}
}
RuneStyleSlot::RuneStyleSlot() {}
RuneStyleSlot::RuneStyleSlot(const QJsonObject& json) {
type = getValue<QString>(json, "type");
auto perksj = json["perks"];
if(perksj.isArray()) {
perks = readVector<int>(perksj.toArray());
}
}
RuneStyle::RuneStyle() {}
RuneStyle::RuneStyle(const QJsonObject& json) {
id = getValue<int32_t>(json, "id", 0);
name = getValue<QString>(json, "name");
iconPath = getValue<QString>(json, "iconPath");
tooltip = getValue<QString>(json, "tooltip");
idName = getValue<QString>(json, "idName");
auto subStylesRef = json["allowedSubStyles"];
if(subStylesRef.isArray()) {
allowedSubStyles = readVector<int>(subStylesRef.toArray());
}
auto runeSlotsRef = json["slots"];
if(runeSlotsRef.isArray()) {
runeSlots = readVector<RuneStyleSlot>(runeSlotsRef.toArray());
}
}
RuneAspekt::RuneAspekt() {}
RuneAspekt::RuneAspekt(const QJsonObject& json) {
id = getValue<int32_t>(json, "id", 0);
name = getValue<QString>(json, "name");
shortDesc = getValue<QString>(json, "shortDesc");
longDesc = getValue<QString>(json, "longDesc");
tooltip = getValue<QString>(json, "tooltip");
iconPath = getValue<QString>(json, "iconPath");
}
ClientAPI::Message::Message() {}
ClientAPI::Message::Message(const QJsonObject& json) {
body = getValue<QString>(json, "body");
fromId = getValue<QString>(json, "fromId");
fromPid = getValue<QString>(json, "fromPid");
fromSummonerId = getValue<int64_t>(json, "fromSummonerId");
id = getValue<QString>(json, "id");
isHistorical = getValue<bool>(json, "isHistorical", true);
timestamp = getValue<QString>(json, "timestamp");
type = getValue<QString>(json, "type");
}
ClientAPI::Conversation::Conversation() {}
ClientAPI::Conversation::Conversation(const QJsonObject& json) : lastMessage(nullptr) {
gameName = getValue<QString>(json, "gameName");
gameTag = getValue<QString>(json, "gameTag");
id = getValue<QString>(json, "id");
isMuted = getValue<bool>(json, "isMuted");
name = getValue<QString>(json, "name");
password = getValue<QString>(json, "password");
pid = getValue<QString>(json, "pid");
targetRegion = getValue<QString>(json, "targetRegion");
type = getValue<QString>(json, "type");
unreadMessageCount = getValue<int64_t>(json, "unreadMessageCount");
auto msgref = json["lastMessage"];
if(msgref.isObject()) {
lastMessage = std::make_shared<Message>(msgref.toObject());
}
}
#define PRINTENUM(ENUMNAME) \ #define PRINTENUM(ENUMNAME) \
std::ostream& operator<<(std::ostream& str, const ClientAPI:: ENUMNAME & state) { \ std::ostream& operator<<(std::ostream& str, const ClientAPI:: ENUMNAME & state) { \
assert(((int) state) < ENUMNAME ## NamesCount); \ assert(((uint32_t) state) < ENUMNAME ## NamesCount); \
return str << ENUMNAME ## Names[(int) state] << " (" << (int) state << ')'; \ return str << ENUMNAME ## Names[(uint32_t) state].toStdString() << " (" << (uint32_t) state << ')'; \
} \
\
QDebug operator<<(QDebug str, const ClientAPI:: ENUMNAME & state) { \
assert(((uint32_t) state) < ENUMNAME ## NamesCount); \
return str << ENUMNAME ## Names[(uint32_t) state] << " (" << (uint32_t) state << ')'; \
} }
PRINTENUM(ReadyCheckState) PRINTENUM(ReadyCheckState)
PRINTENUM(GameflowPhase) PRINTENUM(GameflowPhase)
PRINTENUM(ChampSelectPhase) PRINTENUM(ChampSelectPhase)
PRINTENUM(Position)
PRINTENUM(ChampSelectActionType) PRINTENUM(ChampSelectActionType)
// not using macro because its not in ClientAPI
std::ostream& operator<<(std::ostream& str, const Position & state) {
assert(((uint32_t) state) < PositionNamesCount);
return str << PositionNames[(uint32_t) state].toStdString() << " (" << (uint32_t) state << ')';
}
QDebug operator<<(QDebug str, const Position & state) {
assert(((uint32_t) state) < PositionNamesCount);
return str << PositionNames[(uint32_t) state] << " (" << (uint32_t) state << ')';
}

57
src/clipboardpopup.cpp Normal file
View File

@ -0,0 +1,57 @@
#include "clipboardpopup.h"
#include "ui_clipboardpopup.h"
#include <QClipboard>
ClipboardPopup::ClipboardPopup(Direction dir, QWidget* parent) : QDialog(parent), ui(new Ui::ClipboardPopup), direction(dir) {
ui->setupUi(this);
if(direction == Direction::Paste) {
this->ui->copyButton->hide();
this->ui->text->setPlaceholderText(ClipboardPopup::tr("Paste here"));
this->ui->buttonBox->setStandardButtons(QDialogButtonBox::Cancel);
QObject::connect(this->ui->text, &QTextEdit::textChanged, this, &ClipboardPopup::textPasted);
} else {
// copy
this->ui->text->setReadOnly(true);
}
QObject::connect(this->ui->copyButton, &QPushButton::pressed, this, &ClipboardPopup::copyButton);
}
ClipboardPopup::~ClipboardPopup() {
delete this->ui;
}
void ClipboardPopup::textPasted() {
int newTextSize = this->getText().size();
if(newTextSize - 1 > lastKnownTextSize && newTextSize > 1) {
// asume that something was pasted
accept();
return;
}
if(newTextSize > 0) {
this->ui->buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok);
}
lastKnownTextSize = newTextSize;
}
void ClipboardPopup::setText(QString text) {
this->ui->text->setText(text);
this->ui->text->selectAll();
}
QString ClipboardPopup::getText() const {
return this->ui->text->toPlainText();
}
void ClipboardPopup::copyButton() {
QClipboard* clip = qApp->clipboard();
clip->setText(this->ui->text->toPlainText());
// close this dialog
accept();
}

View File

@ -9,11 +9,17 @@
#include "json.h" #include "json.h"
#ifdef WIN32
#define CONFPATH "lolautoacceptor/"
#else
#define CONFPATH ".config/lolautoaccept/"
#endif
Config::StageConfig::StageConfig() : enabled(false) {} Config::StageConfig::StageConfig() : enabled(false) {}
Config::StageConfig::StageConfig(const QJsonObject& j) { Config::StageConfig::StageConfig(const QJsonObject& j) {
if(j["champ"].isString()) { if(j["champ"].isString()) {
champs.push_back(getValue<std::string>(j, "champ", "")); champs.push_back(getValue<QString>(j, "champ"));
} }
if(j["champs"].isArray()) { if(j["champs"].isArray()) {
QJsonArray jchamps = j["champs"].toArray(); QJsonArray jchamps = j["champs"].toArray();
@ -21,7 +27,7 @@ Config::StageConfig::StageConfig(const QJsonObject& j) {
if(jchamp.isString()) { if(jchamp.isString()) {
QString jchampstr = jchamp.toString(); QString jchampstr = jchamp.toString();
if(!jchampstr.isEmpty()) { if(!jchampstr.isEmpty()) {
champs.push_back(jchampstr.toStdString()); champs.push_back(jchampstr);
} }
} }
} }
@ -33,9 +39,9 @@ Config::StageConfig::operator QJsonObject() const {
QJsonObject out; QJsonObject out;
QJsonArray jchamps; QJsonArray jchamps;
for(const std::string& champ : champs) { for(const QString& champ : champs) {
if(!champ.empty()) { if(!champ.isEmpty()) {
jchamps.push_back(QString::fromStdString(champ)); jchamps.push_back(champ);
} }
} }
@ -45,70 +51,186 @@ Config::StageConfig::operator QJsonObject() const {
return out; return out;
} }
Config::RootConfig::RootConfig() {} Config::PositionConfig::PositionConfig() {}
Config::PositionConfig::PositionConfig(const QJsonObject& j) {
Config::RootConfig::RootConfig(const QJsonObject& j) {
prepick = getValue<Config::StageConfig>(j, "prepick");
ban = getValue<Config::StageConfig>(j, "ban"); ban = getValue<Config::StageConfig>(j, "ban");
pick = getValue<Config::StageConfig>(j, "pick"); pick = getValue<Config::StageConfig>(j, "pick");
enabledAutoAccept = getValue(j, "enabledAutoAccept", false); position = toPosition(getValue<QString>(j, "position"));
if((int) position < 0 || position > Position::UTILITY) {
qWarning() << "invalid config value \"position\"";
position = Position::MIDDLE;
}
}
Config::PositionConfig::operator QJsonObject() const {
QJsonObject out;
out["ban"] = (QJsonObject) ban;
out["pick"] = (QJsonObject) pick;
out["position"] = toString(position);
return out;
}
Config::RunePageConfig::RunePageConfig() {}
Config::RunePageConfig::RunePageConfig(const QJsonObject& j) {
name = getValue<QString>(j, "name");
runepage = getValue<::RunePage>(j, "runepage");
}
Config::RunePageConfig::operator QJsonObject() const {
QJsonObject out;
out["name"] = name;
out["runepage"] = static_cast<QJsonObject>(runepage);
return out;
}
Config::RootConfig::RootConfig() {}
Config::RunePageConfig::RunePageConfig(QString name, const RunePage& rp) : name(name), runepage(rp) {}
Config::GeneralRunePageConfig::GeneralRunePageConfig() {}
Config::GeneralRunePageConfig::GeneralRunePageConfig(const QJsonObject& j) {
auto runepagesRef = j["pages"];
if(runepagesRef.isArray()) {
QJsonArray jpages = runepagesRef.toArray();
runePages.reserve(jpages.size());
for(QJsonValue val : jpages) {
if(val.isObject()) {
runePages.push_back(std::make_shared<RunePageConfig>(val.toObject()));
}
}
}
autoSync = getValue(j, "autosync", true);
}
Config::GeneralRunePageConfig::operator QJsonObject() const {
QJsonObject out;
QJsonArray runepagesArr;
for(std::shared_ptr<RunePageConfig> rp : runePages) {
runepagesArr.push_back((QJsonObject) *rp);
}
out.insert("pages", runepagesArr);
out.insert("autosync", autoSync);
return out;
}
Config::RootConfig::RootConfig(const QJsonObject& j) {
if(j.contains("version")) {
int version = j["version"].toInt();
if(version > 1) {
// error
qCritical() << "config version is not known: " << version << " using the config might corrupt it.";
// TODO: make backup of config or something
}
// add migrations here if required
}
auto jposref = j["positions"];
if(jposref.isArray()) {
QJsonArray jpos = jposref.toArray();
positionConfigs.reserve(jpos.size());
for(QJsonValue val : jpos) {
if(val.isObject()) {
positionConfigs.push_back(std::make_shared<Config::PositionConfig>(val.toObject())); // implicit cast to PositionConfig
}
}
}
runepagesConfig = getValue<GeneralRunePageConfig>(j, "runepages");
enabledAutoAccept = getValue(j, "enabledAutoAccept", true);
enabledSmiteWarn = getValue(j, "enabledSmiteWarn", true);
enabledAutoWrite = getValue(j, "enabledAutoWrite", false);
autoWriteText = getValue<QString>(j, "autoWriteText");
} }
Config::RootConfig::operator QJsonObject() const { Config::RootConfig::operator QJsonObject() const {
QJsonObject out; QJsonObject out;
out.insert("prepick", (QJsonObject) prepick); QJsonArray positionarr;
out.insert("ban", (QJsonObject) ban); for(auto pos : positionConfigs) {
out.insert("pick", (QJsonObject) pick); positionarr.push_back((QJsonObject) *pos.get());
}
out["positions"] = positionarr;
out["runepages"] = (QJsonObject) runepagesConfig;
out.insert("enabledAutoAccept", enabledAutoAccept); out.insert("enabledAutoAccept", enabledAutoAccept);
out.insert("enabledSmiteWarn", enabledSmiteWarn);
out.insert("enabledAutoWrite", enabledAutoWrite);
out.insert("autoWriteText", autoWriteText);
out.insert("version", 1);
return out; return out;
} }
std::shared_ptr<Config::PositionConfig> Config::RootConfig::getPositionConfig(Position position) {
// find existing configuration with given position
for(auto posc : positionConfigs) {
if(posc->position == position) {
return posc;
}
}
// add a new config
auto posconfig = std::make_shared<Config::PositionConfig>();
positionConfigs.push_back(posconfig);
posconfig->position = position;
return posconfig;
}
Config::Config() { Config::Config() {
configFolderPath = getHome() + ".config/lolautoaccept/"; configFolderPath = getHome() + CONFPATH;
configFilePath = configFolderPath + "config.json"; configFilePath = configFolderPath + "config.json";
mkdirs(configFolderPath);
} }
Config::~Config() {} Config::~Config() {}
bool Config::load() { bool Config::load() {
QFile conffile(QString::fromStdString(configFilePath)); QFile conffile(configFilePath);
if(!conffile.open(QIODevice::ReadOnly)) { if(!conffile.open(QIODevice::ReadOnly)) {
Log::error << "could not open configfile: " << configFilePath; qCritical() << "could not open configfile: " << configFilePath;
return false; return false;
} }
QJsonParseError err; QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(conffile.readAll(), &err); QJsonDocument doc = QJsonDocument::fromJson(conffile.readAll(), &err);
if(err.error != QJsonParseError::NoError) { if(err.error != QJsonParseError::NoError) {
Log::error << "config parse error: " << err.errorString().toStdString() << " position: " << err.offset; qCritical() << "config parse error: " << err.errorString() << " position: " << err.offset;
return false; return false;
} }
if(doc.isObject()) { if(doc.isObject()) {
// implicit cast
root = doc.object(); root = doc.object();
Log::info << "config loaded";
qInfo() << "config loaded";
return true; return true;
} }
Log::error << "config is not a json object!"; qCritical() << "config is not a json object!";
return false; return false;
} }
void Config::save() { void Config::save() {
Log::note << "Config::save()";
mkdirs(configFolderPath); mkdirs(configFolderPath);
QFile conffile(QString::fromStdString(configFilePath)); QFile conffile(configFilePath);
if(!conffile.open(QIODevice::WriteOnly)) { if(!conffile.open(QIODevice::WriteOnly)) {
Log::error << "could not open configfile: " << configFilePath; qCritical() << "could not open configfile: " << configFilePath;
return; return;
} }
QJsonDocument doc; QJsonDocument doc;
doc.setObject(root); doc.setObject(root);
conffile.write(doc.toJson()); conffile.write(doc.toJson());
Log::info << "config saved"; qInfo() << "config saved";
} }
Config::RootConfig& Config::getConfig() { Config::RootConfig& Config::getConfig() {

View File

@ -6,18 +6,20 @@
#include <curl/easy.h> #include <curl/easy.h>
#include <Log.h> #include <Log.h>
#include <QEventLoop>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include <QThread>
#include <algorithm> // std::max, champ matching #include <algorithm> // std::max, champ matching
#include "json.h" #include "json.h"
static const std::string BASEURL = "https://ddragon.leagueoflegends.com/"; static const QString BASEURL = "https://ddragon.leagueoflegends.com/";
const DataDragon::ChampData DataDragon::EMPTYCHAMP; const DataDragon::ChampData DataDragon::EMPTYCHAMP;
DataDragon::DataDragon(const std::string& locale) : RestClient(BASEURL), locale(locale), cache({{"square", ".png"}, {"loading", "_0.jpg"}, {"splash", "_0.jpg"}}) { DataDragon::DataDragon(const QString& locale) : RestClient(BASEURL), locale(locale), cache{{"square", ".png"}, {"loading", "_0.jpg"}, {"splash", "_0.jpg"}} {
startThread(); this->setObjectName("DataDragon");
} }
DataDragon::~DataDragon() { DataDragon::~DataDragon() {
@ -27,16 +29,16 @@ DataDragon::~DataDragon() {
DataDragon::ChampData::ChampData() : key(-1) {} DataDragon::ChampData::ChampData() : key(-1) {}
DataDragon::ChampData::ChampData(const QJsonObject& source) { DataDragon::ChampData::ChampData(const QJsonObject& source) {
name = getValue<std::string>(source, "name", ""); name = getValue<QString>(source, "name", "");
id = getValue<std::string>(source, "id", ""); id = getValue<QString>(source, "id", "");
key = getValue<int>(source, "key", -1); key = getValue<int>(source, "key", -1);
partype = getValue<std::string>(source, "partype", ""); partype = getValue<QString>(source, "partype", "");
title = getValue<std::string>(source, "title", ""); title = getValue<QString>(source, "title", "");
} }
const std::string& DataDragon::getVersion() { const QString& DataDragon::getVersion() {
std::unique_lock lock(cachedatamutex); std::unique_lock lock(cachedatamutex);
while(version.empty() && shouldrun) { while(version.isEmpty() && shouldrun) {
cachedatacv.wait(lock); cachedatacv.wait(lock);
} }
return version; return version;
@ -50,8 +52,8 @@ const std::vector<DataDragon::ChampData>& DataDragon::getChamps() {
return champs; return champs;
} }
QPixmap DataDragon::getImage(const std::string& champid, ImageType imgtype) { QPixmap DataDragon::getImage(const QString& champid, ImageType imgtype, bool writeMemcache) {
if(champid.empty()) return {}; if(champid.isEmpty()) return {};
// query mem cache // query mem cache
QPixmap img = memcache.getImage(champid, (int) imgtype); QPixmap img = memcache.getImage(champid, (int) imgtype);
@ -65,35 +67,44 @@ QPixmap DataDragon::getImage(const std::string& champid, ImageType imgtype) {
return img; return img;
} }
const std::string url = getImageUrl(champid, imgtype); const QString url = getImageUrl(champid, imgtype);
if(url.empty()) return {}; if(url.isEmpty()) return {};
QByteArray arr = requestRaw(url);
QByteArray arr;
try {
arr = requestRaw(url);
} catch(RestClient::WebException& e) {}
if(arr.isEmpty()) { if(arr.isEmpty()) {
Log::error << "image could not be loaded"; qCritical() << "image could not be loaded";
return {}; return {};
} }
// propably an error // propably an error
if(arr.size() < 1000) { if(arr.size() < 1000) {
Log::info << "small icon url: " << url; qInfo() << "small icon url: " << url;
Log::info << "content: " << std::string(arr.data(), arr.size()); qInfo() << "content: " << QString::fromLocal8Bit(arr);
return {}; return {};
} }
// store HDD cache // store HDD cache
cache[(int) imgtype].addImageRaw(arr, champid); cache[(int) imgtype].addImageRaw(arr, champid);
// remove from notDownloadedList
notDownloadedImages.erase(champid);
QPixmap decodedImage; QPixmap decodedImage;
decodedImage.loadFromData(arr); decodedImage.loadFromData(arr);
// store mem cache // store mem cache
memcache.addImage(decodedImage, champid, (int) imgtype); if(writeMemcache) {
memcache.addImage(decodedImage, champid, (int) imgtype);
}
return decodedImage; return decodedImage;
} }
void DataDragon::getImageAsnyc(const std::string& champid, notifyImgfunc_t func, ImageType imgtype) { void DataDragon::getImageAsnyc(const QString& champid, notifyImgfunc_t func, ImageType imgtype) {
if(!func) return; if(!func) return;
{ {
@ -103,44 +114,40 @@ void DataDragon::getImageAsnyc(const std::string& champid, notifyImgfunc_t func,
tasksnotemptycv.notify_one(); tasksnotemptycv.notify_one();
} }
static std::string toLower(const std::string& in) { static int startinglength(const QString& original, const QString& prefix) {
return QString::fromStdString(in).toLower().toStdString(); int i = 0;
}
static size_t startinglength(const std::string& original, const std::string& prefix) {
size_t i = 0;
for(; i < original.size() && i < prefix.size(); ++i) { for(; i < original.size() && i < prefix.size(); ++i) {
if(original.substr(0, i+1) != prefix.substr(0, i+1)) { if(original.left(i+1) != prefix.left(i+1)) {
return i; return i;
} }
} }
return i; return i;
} }
static size_t matchChamp(const DataDragon::ChampData& champ, const std::string& lower) { static size_t matchChamp(const DataDragon::ChampData& champ, const QString& lower) {
std::string lowerid = toLower(champ.id); QString lowerid = champ.id.toLower();
std::string lowername = toLower(champ.name); QString lowername = champ.name.toLower();
if(lowerid == lower || lowername == lower) { if(lowerid == lower || lowername == lower) {
return lower.size(); return lower.size();
} }
size_t lengtha = startinglength(lowerid, lower); int lengtha = startinglength(lowerid, lower);
size_t lengthb = startinglength(lowername, lower); int lengthb = startinglength(lowername, lower);
return std::max(lengtha, lengthb); return qMax(lengtha, lengthb);
} }
const DataDragon::ChampData& DataDragon::getBestMatchingChamp(const std::string& name, int* count) { const DataDragon::ChampData& DataDragon::getBestMatchingChamp(const QString& name, int* count) {
getChamps(); getChamps();
// for now: just check for a perfect hit // for now: just check for a perfect hit
std::string lower = toLower(name); QString lower = name.toLower();
int bestmatchingoffset = -1; int bestmatchingoffset = -1;
size_t bestmatchinglength = 0; int bestmatchinglength = 0;
uint32_t bestmatchingcount = 0; uint32_t bestmatchingcount = 0;
for(size_t offset = 0; offset < champs.size(); ++offset) { for(size_t offset = 0; offset < champs.size(); ++offset) {
const ChampData& it = champs.at(offset); const ChampData& it = champs.at(offset);
size_t match = matchChamp(it, lower); int match = matchChamp(it, lower);
if(match > bestmatchinglength) { if(match > bestmatchinglength) {
bestmatchinglength = match; bestmatchinglength = match;
bestmatchingcount = 1; bestmatchingcount = 1;
@ -162,12 +169,12 @@ const DataDragon::ChampData& DataDragon::getBestMatchingChamp(const std::string&
return EMPTYCHAMP; return EMPTYCHAMP;
} }
std::vector<const DataDragon::ChampData*> DataDragon::getMatchingChamp(const std::string& name, uint32_t limit) { std::vector<const DataDragon::ChampData*> DataDragon::getMatchingChamp(const QString& name, uint32_t limit) {
if(limit == 0) return {}; if(limit == 0) return {};
if(name.empty()) return {}; if(name.isEmpty()) return {};
getChamps(); getChamps();
std::string lower = toLower(name); QString lower = name.toLower();
std::vector<size_t> matches; std::vector<size_t> matches;
matches.resize(champs.size()); matches.resize(champs.size());
@ -192,10 +199,50 @@ std::vector<const DataDragon::ChampData*> DataDragon::getMatchingChamp(const std
return out; return out;
} }
std::string DataDragon::getImageUrl(const std::string& champid, ImageType type) { const DataDragon::ChampData* DataDragon::getChampByID(uint32_t id) {
getChamps();
auto it = std::find_if(champs.begin(), champs.end(), [id](const ChampData& cd) { return cd.key == (int) id; });
// nothing found
if(it == champs.end()) return nullptr;
return &*it;
}
std::vector<uint32_t> DataDragon::resolveChampIDs(const std::vector<QString>& champnames) {
std::vector<uint32_t> out;
out.reserve(champnames.size());
std::transform(champnames.begin(), champnames.end(), std::insert_iterator(out, out.begin()), [this](const QString& champname) {
auto cd = getBestMatchingChamp(champname);
return cd.key; // might be 0 (invalid)
});
return out;
}
void DataDragon::startThread() {
shouldrun = true;
bgthread = QThread::create(&DataDragon::threadLoop, this);
bgthread->setObjectName("DataDragonThread");
bgthread->start();
this->moveToThread(bgthread);
}
void DataDragon::stop() {
qDebug() << "stop DataDragon";
shouldrun = false;
std::unique_lock lock(tasksmutex);
tasks.clear();
notDownloadedImages.clear(); // this is a possible race condition!
}
QString DataDragon::getImageUrl(const QString& champid, ImageType type) {
switch(type) { switch(type) {
case ImageType::SQUARE: { case ImageType::SQUARE: {
if(getVersion().empty()) { if(getVersion().isEmpty()) {
return {}; return {};
} }
@ -212,64 +259,82 @@ std::string DataDragon::getImageUrl(const std::string& champid, ImageType type)
return {}; return {};
} }
std::string DataDragon::getCDNString() const { QString DataDragon::getCDNString() const {
return "cdn/" + version + "/"; return "cdn/" + version + "/";
} }
void DataDragon::prefetchChampImage(const QString& champid, ImageType imgtype) {
if(!cache[(int) imgtype].hasImage(champid)) {
qDebug() << "prefetch " << champid << " type: " << (int) imgtype;
getImage(champid, imgtype, false);
}
}
void DataDragon::getVersionInternal() { void DataDragon::getVersionInternal() {
std::unique_lock lock(cachedatamutex); std::unique_lock lock(cachedatamutex);
if(!version.empty()) return; if(!version.isEmpty()) return;
QJsonDocument jversions = request("api/versions.json"); version = champCache.getVersion();
if(jversions.isArray()) { if(!version.isEmpty()) return;
QJsonArray jverarr = jversions.array();
if(!jverarr.empty()) {
version = jverarr.at(0).toString().toStdString();
Log::info << "got League version: " << version;
lock.unlock(); try {
cachedatacv.notify_all(); QJsonDocument jversions = request("api/versions.json");
return; if(jversions.isArray()) {
QJsonArray jverarr = jversions.array();
if(!jverarr.empty()) {
version = jverarr.at(0).toString();
qInfo() << "got League version: " << version;
lock.unlock();
cachedatacv.notify_all();
return;
}
} }
} qCritical() << "error parsing version object";
Log::error << "error parsing version object"; } catch(RestClient::WebException& e) {}
} }
void DataDragon::getChampsInternal() { void DataDragon::getChampsInternal() {
std::unique_lock lock(cachedatamutex); std::unique_lock lock(cachedatamutex);
if(!champs.empty() && version.empty()) { if(!champs.empty() && version.isEmpty()) {
return; return;
} }
QJsonDocument jchamps = request(getCDNString() + "data/" + locale + "/champion.json"); QJsonDocument jchamps = champCache.getChamps();
if(jchamps.isEmpty()) { bool cacheSuccessfull = !jchamps.isEmpty();
// try again with default locale if(!cacheSuccessfull) {
locale = "en_US"; try {
jchamps = request(getCDNString() + "data/" + locale + "/champion.json"); jchamps = request(getCDNString() + "data/" + locale + "/champion.json");
if(jchamps.isEmpty()) {
// try again with default locale
locale = "en_US";
jchamps = request(getCDNString() + "data/" + locale + "/champion.json");
}
} catch(RestClient::WebException& e) {}
} }
if(jchamps.isObject()) { if(jchamps.isObject()) {
// save to cache
if(!cacheSuccessfull) {
champCache.saveChamps(jchamps, version);
}
QJsonObject obj = jchamps.object(); QJsonObject obj = jchamps.object();
auto it = obj.constFind("data"); auto it = obj.constFind("data");
if(it != obj.constEnd() && it.value().isObject()) { if(it != obj.constEnd() && it->isObject()) {
QJsonObject jchampsdata = it.value().toObject(); for(auto&& champdata : it->toObject()) {
for(auto champit = jchampsdata.constBegin(); champit != jchampsdata.constEnd(); champit++) { if(champdata.isObject()) {
if(champit.value().isObject()) { auto& dataobj = champs.emplace_back(champdata.toObject());
champs.emplace_back(champit.value().toObject()); notDownloadedImages.insert(dataobj.id);
} }
} }
} }
} }
Log::info << "loaded " << champs.size() << " champs"; qInfo() << "loaded " << champs.size() << " champs from cache: " << cacheSuccessfull;
lock.unlock(); lock.unlock();
cachedatacv.notify_all(); cachedatacv.notify_all();
} }
void DataDragon::startThread() {
shouldrun = true;
bgthread = std::thread(&DataDragon::threadLoop, this);
}
void DataDragon::stopThread() { void DataDragon::stopThread() {
shouldrun = false; shouldrun = false;
tasksnotemptycv.notify_all(); tasksnotemptycv.notify_all();
@ -278,11 +343,12 @@ void DataDragon::stopThread() {
void DataDragon::stopAndJoinThread() { void DataDragon::stopAndJoinThread() {
stopThread(); stopThread();
if(bgthread.joinable()) bgthread->wait();
bgthread.join();
} }
void DataDragon::threadLoop() { void DataDragon::threadLoop() {
QEventLoop loop;
// init version and champ list // init version and champ list
getVersionInternal(); getVersionInternal();
getChampsInternal(); getChampsInternal();
@ -293,20 +359,54 @@ void DataDragon::threadLoop() {
{ {
std::unique_lock lock(tasksmutex); std::unique_lock lock(tasksmutex);
if(tasks.empty()) { if(tasks.empty()) {
tasksnotemptycv.wait(lock); if(notDownloadedImages.empty()) {
tasksnotemptycv.wait(lock);
} else {
Log::note << "DataDragon background thread is idleing - prefetching champion images TODO: " << notDownloadedImages.size();
while(shouldrun && !notDownloadedImages.empty() && tasks.empty()) {
lock.unlock();
auto it = notDownloadedImages.begin();
auto champid = std::move(notDownloadedImages.extract(it).value());
prefetchChampImage(champid, ImageType::SQUARE);
emit this->loading(1.0 - (notDownloadedImages.size() / (float) champs.size()));
emit this->fetchingChamp(champid);
lock.lock();
}
if(notDownloadedImages.empty() && tasks.empty()) {
// everything prefetched, but nothing more to do
static bool once = false;
if(!once) {
once = true;
Log::note << "all champs are prefetched now";
emit this->loading( 1.0 );
}
tasksnotemptycv.wait(lock);
}
}
} }
if(tasks.empty()) continue; if(tasks.empty()) continue;
t = tasks.front(); t = tasks.front();
tasks.pop_front(); tasks.pop_front();
} }
loop.processEvents();
QPixmap img = getImage(t.champid, t.type); QPixmap img = getImage(t.champid, t.type);
t.func(img); t.func(img);
} }
}
qDebug() << "DataDragon Thread terminated";
}
std::ostream& operator<<(std::ostream& str, const DataDragon::ChampData& cd) { std::ostream& operator<<(std::ostream& str, const DataDragon::ChampData& cd) {
return str << "[n: " << cd.name << " " << " k: " << cd.key << " id: " << cd.id << "]"; return str << "[n: " << cd.name.toStdString() << " " << " k: " << cd.key << " id: " << cd.id.toStdString() << "]";
} }

View File

@ -7,24 +7,29 @@
#include "files.h" #include "files.h"
DataDragonImageCache::DataDragonImageCache(const std::string& folderextra, const std::string& imageext) : imageext(imageext) { DataDragonImageCache::DataDragonImageCache(const QString& folderextra, const QString& imageext) : imageext(imageext) {
// init cache dir // init cache dir
cacheDir = getHome() + ".cache/lolautoaccept/" + folderextra + "/"; cacheDir = getCache() + folderextra + "/";
mkdirs(cacheDir); mkdirs(cacheDir);
} }
DataDragonImageCache::~DataDragonImageCache() {} DataDragonImageCache::~DataDragonImageCache() {}
QPixmap DataDragonImageCache::getImage(const std::string& name) { bool DataDragonImageCache::hasImage(const QString& name) {
return QPixmap(QString::fromStdString(getFilepath(name))); QFile file(getFilepath(name));
return file.size() > 1024; // a image with less than 1KiB? assume it would be readable (r-Permissions)
} }
void DataDragonImageCache::addImageRaw(const QByteArray& arr, const std::string& name) { QPixmap DataDragonImageCache::getImage(const QString& name) {
QFile file(QString::fromStdString(getFilepath(name))); return QPixmap(getFilepath(name));
}
void DataDragonImageCache::addImageRaw(const QByteArray& arr, const QString& name) {
QFile file(getFilepath(name));
file.open(QIODevice::WriteOnly); file.open(QIODevice::WriteOnly);
file.write(arr); file.write(arr);
} }
std::string DataDragonImageCache::getFilepath(const std::string& name) const { QString DataDragonImageCache::getFilepath(const QString& name) const {
return cacheDir + name + imageext; return cacheDir + name + imageext;
} }

View File

@ -2,28 +2,42 @@
#include <sys/stat.h> #include <sys/stat.h>
#include <QDir>
#include <Log.h> #include <Log.h>
bool mkdirs(const std::string& path) { #ifdef WIN32
size_t offset = 0; #define CACHEPATH "lolautoacceptor/cache/"
while(offset < path.size()) { #else
offset = path.find('/', offset+1); #define CACHEPATH ".cache/lolautoaccept/"
int res = mkdir(path.substr(0, offset).c_str(), S_IRWXU | S_IRWXG); // 770 #endif
if(res == -1 && errno != EEXIST) {
// mkdirs failed
return false;
}
}
return true; #ifdef WIN32
}
QString getHome() {
std::string getHome() { const char* homevar = getenv("appdata");
const char* homevar = getenv("HOME");
if(homevar == nullptr) { if(homevar == nullptr) {
Log::warn << "$HOME is not set! Defaulting to ./"; qWarning() << "%appdata% is not set! Defaulting to ./";
return "./"; return "./";
} }
return std::string(homevar) + "/"; return QString(homevar) + "/";
} }
#else
QString getHome() {
const char* homevar = getenv("HOME");
if(homevar == nullptr) {
qWarning() << "$HOME is not set! Defaulting to ./";
return "./";
}
return QString(homevar) + "/";
}
#endif
bool mkdirs(const QString& path) {
return QDir::root().mkpath(path);
}
QString getCache() {
return getHome() + CACHEPATH;
}

View File

@ -7,6 +7,13 @@ int convert(const QJsonValue& val) {
return val.toInt(); return val.toInt();
} }
template<>
uint32_t convert(const QJsonValue& val) {
if(val.isString())
return val.toString().toUInt();
return (uint32_t) val.toDouble();
}
template<> template<>
int64_t convert(const QJsonValue& val) { int64_t convert(const QJsonValue& val) {
if(val.isString()) if(val.isString())
@ -14,11 +21,23 @@ int64_t convert(const QJsonValue& val) {
return (int64_t) val.toDouble(); return (int64_t) val.toDouble();
} }
template<>
uint64_t convert(const QJsonValue& val) {
if(val.isString())
return val.toString().toULongLong();
return (uint64_t) val.toDouble();
}
template<> template<>
std::string convert(const QJsonValue& val) { std::string convert(const QJsonValue& val) {
return val.toString().toStdString(); return val.toString().toStdString();
} }
template<>
QString convert(const QJsonValue& val) {
return val.toString();
}
template<> template<>
bool convert(const QJsonValue& val) { bool convert(const QJsonValue& val) {
if(val.isString()) if(val.isString())

27
src/loadingwindow.cpp Normal file
View File

@ -0,0 +1,27 @@
#include "loadingwindow.h"
#include "ui_loadingwindow.h"
LoadingWindow::LoadingWindow(QWidget* parent) : QWidget(parent), ui(new Ui::LoadingWindow) {
ui->setupUi(this);
}
LoadingWindow::~LoadingWindow() {
delete ui;
}
void LoadingWindow::setChampion(QString championName) {
this->setText(LoadingWindow::tr("Loading Champion: %0").arg(championName));
}
void LoadingWindow::setText(QString text) {
ui->label->setText(text);
}
void LoadingWindow::setProgress(float val) {
ui->progressBar->setValue(val * 100);
}
void LoadingWindow::closeEvent(QCloseEvent* event) {
QWidget::closeEvent(event);
emit this->closed();
}

View File

@ -1,46 +1,52 @@
#include "lolautoaccept.h" #include "lolautoaccept.h"
#include <algorithm>
#include <thread> #include <thread>
#include <Log.h> #include <Log.h>
#include <QDebug>
LolAutoAccept::Stage::Stage() {} LolAutoAccept::Stage::Stage() {}
LolAutoAccept::Stage::~Stage() {} LolAutoAccept::Stage::~Stage() {}
LolAutoAccept::LolAutoAccept() { LolAutoAccept::LolAutoAccept(Config::RootConfig& config, DataDragon& dd, QObject* parent) : QObject(parent), config(config), dd(dd) {
stages.reserve(4); qRegisterMetaType<LolAutoAccept::Status>();
stages.push_back(new Stage()); // accept
stages.push_back(new Stage()); // prepick std::lock_guard lock(stagesMutex);
stages.push_back(new Stage()); // ban stages.resize(3); // accept, ban, pick
stages.push_back(new Stage()); // pick
} }
LolAutoAccept::~LolAutoAccept() { LolAutoAccept::~LolAutoAccept() {
stopJoinThread(); stopJoinThread();
for(auto s : stages) {
delete s;
}
} }
void LolAutoAccept::setChamps(const std::vector<uint32_t>& champs, State s) { void LolAutoAccept::setChamps(const std::vector<uint32_t>& champs, State s) {
if(s == State::LOBBY || s >= State::GAME) { if(s == State::LOBBY || s >= State::PICK) {
Log::warn << "setChamps() called on invalid State"; qWarning() << "setChamps() called on invalid State";
return; return;
} }
qDebug() << "LolAutoAccept::setChamps";
stages.at((int) s)->champids = champs; {
stages.at((int) s)->currentOffset = 0; std::lock_guard lock(stagesMutex);
Log::info << "set champs on state: " << (int) s << " count: " << champs.size(); stages.at((int) s).champids = champs;
stages.at((int) s).currentOffset = 0;
}
qInfo() << "set champs on state: " << (int) s << " count: " << champs.size();
} }
void LolAutoAccept::setEnabled(bool b, State s) { void LolAutoAccept::setEnabled(bool b, State s) {
if(s >= State::GAME) { if(s > State::PICK) {
Log::warn << "setEnabled() called on invalid State"; qWarning() << "setEnabled() called on invalid State";
return; return;
} }
stages.at((int) s)->enabled = b; std::lock_guard lock(stagesMutex);
stages.at((int) s).enabled = b;
}
void LolAutoAccept::setSmiteWarn(bool b) {
smiteWarnEnabled = b;
} }
bool LolAutoAccept::init() { bool LolAutoAccept::init() {
@ -50,8 +56,7 @@ bool LolAutoAccept::init() {
if(ca) { if(ca) {
clientapi = std::make_shared<ClientAPI>(*ca.get()); clientapi = std::make_shared<ClientAPI>(*ca.get());
auto selfinfo = clientapi->getSelf(); auto selfinfo = clientapi->getSelf();
summonerid = selfinfo.summonerid; qInfo() << "selfinfo: gameName: " << selfinfo.gameName << " name: " << selfinfo.name << " summonerid: " << selfinfo.summonerid << " statusMessage: " << selfinfo.statusMessage << " puuid: " << selfinfo.puuid << " level: " << selfinfo.level;
Log::info << "selfinfo: gameName: " << selfinfo.gameName << " name: " << selfinfo.name << " summonerid: " << selfinfo.summonerid << " statusMessage: " << selfinfo.statusMessage << " puuid: " << selfinfo.puuid << " level: " << selfinfo.level;
} }
return (bool) clientapi; return (bool) clientapi;
@ -70,6 +75,51 @@ void LolAutoAccept::stop() {
shouldrun = false; shouldrun = false;
} }
LolAutoAccept::Status LolAutoAccept::getStatus() {
return shouldrun ? Status::Running : Status::Off;
}
void LolAutoAccept::reload() {
Log::note << "reload LolAutoAccept";
if(currentPositionSet)
loadPosition(currentPosition);
}
const std::vector<RuneAspekt>& LolAutoAccept::getRuneAspekts() {
if(runeaspekts.empty()) {
if(clientapi) {
runeaspekts = clientapi->getAllRuneAspekts();
qInfo() << "Loaded " << runeaspekts.size() << " rune aspekts";
}
}
return runeaspekts;
}
const std::vector<RuneStyle>& LolAutoAccept::getRuneStyles() {
if(runestyles.empty()) {
if(clientapi) {
runestyles = clientapi->getAllRuneStyles();
qInfo() << "Loaded " << runestyles.size() << " rune styles";
}
}
return runestyles;
}
void LolAutoAccept::setAutoWriteText(bool enabled, const QString& text) {
if ( enabled && !autoWriteTextEnabled ) {
// only re-write on rising edge
autoWriteTextDone = false;
}
autoWriteTextEnabled = enabled;
autoWriteText = text;
}
void LolAutoAccept::dodge() {
dodgeNow = true;
}
void LolAutoAccept::stopJoinThread() { void LolAutoAccept::stopJoinThread() {
stop(); stop();
@ -82,72 +132,164 @@ void LolAutoAccept::stopJoinThread() {
void LolAutoAccept::innerRun() { void LolAutoAccept::innerRun() {
shouldrun = true; shouldrun = true;
while(shouldrun) { try {
auto start = std::chrono::high_resolution_clock::now(); auto convs = clientapi->getAllConversations();
qInfo() << "got " << convs.size() << " conversations";
auto phase = clientapi->getGameflowPhase(); emit statusChanged(Status::Running);
Log::info << "current Gameflowphase: " << phase; while(shouldrun) {
uint32_t extrasleep = 800;
auto start = std::chrono::high_resolution_clock::now();
// do processing auto phase = clientapi->getGameflowPhase();
if(phase == ClientAPI::GameflowPhase::READYCHECK) { qInfo() << "current Gameflowphase: " << phase;
if(stages.at(0)->enabled) { // auto accept enabled
auto state = clientapi->getReadyCheckState();
Log::info << "readychack state: " << state; // do processing
if(state == ClientAPI::ReadyCheckState::INPROGRESS) { if(phase == ClientAPI::GameflowPhase::LOBBY) {
Log::info << "auto accepting"; resetAllOffsets();
clientapi->acceptMatch(); } else if(phase == ClientAPI::GameflowPhase::MATCHMAKING) {
extrasleep = 200;
resetAllOffsets();
} else if(phase == ClientAPI::GameflowPhase::READYCHECK) {
if(stages.at(0).enabled) { // auto accept enabled
auto state = clientapi->getReadyCheckState();
qInfo() << "readychack state: " << state;
if(state == ClientAPI::ReadyCheckState::INPROGRESS) {
qInfo() << "auto accepting";
clientapi->acceptMatch();
}
} }
resetAllOffsets();
extrasleep = 0; // no extra sleep
} else if(phase == ClientAPI::GameflowPhase::CHAMPSELECT) {
champSelect();
extrasleep = 0; // no extra sleep
// first time champselect phase -> enable dodge button
if(lastPhase != phase) {
dodgeNow = false;
emit this->dodgePossible(true);
} else if (dodgeNow) {
// this makes sure that the event comes after the phase was entered and is not lingering from before
dodgeNow = false;
clientapi->dodge();
}
} else if(phase == ClientAPI::GameflowPhase::INPROGRESS) {
extrasleep = 30000; // 30s bonus sleep
resetAllOffsets();
} else if(phase == ClientAPI::GameflowPhase::ENDOFGAME) {
extrasleep = 2000; // 2 s bonus sleep
resetAllOffsets();
} else if(phase == ClientAPI::GameflowPhase::PREENDOFGAME) {
extrasleep = 4000; // 4 s bonus sleep
resetAllOffsets();
} else if(phase == ClientAPI::GameflowPhase::WAITINGFORSTATS) {
extrasleep = 4000; // 4 s bonus sleep
resetAllOffsets();
} else if(phase == ClientAPI::GameflowPhase::NONE) {
extrasleep = 10000; // 10 s bonus sleep - no lobby
resetAllOffsets();
} }
resetAllOffsets();
} else if(phase == ClientAPI::GameflowPhase::CHAMPSELECT) { // change phase to non champselect phase -> disable dodge button
champSelect(); if(phase != ClientAPI::GameflowPhase::CHAMPSELECT && lastPhase != phase) {
emit this->dodgePossible(false);
}
lastPhase = phase;
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> dur = (end-start);
//if(dur.count() > 1e-5)
Log::note << "iteration took: " << (dur.count() * 1000) << " ms extrasleep: " << extrasleep;
std::this_thread::sleep_for(std::chrono::milliseconds(400 + extrasleep));
} }
emit statusChanged(Status::Off);
} catch(RestClient::WebException& e) {
qCritical() << "WebException catched: " << e.curlresponse;
if(e.curlresponse == CURLE_COULDNT_CONNECT) {
// free clientapi
clientapi.reset();
auto end = std::chrono::high_resolution_clock::now(); // disable this thread
std::chrono::duration<double> dur = (end-start); shouldrun = false;
//if(dur.count() > 1e-5)
Log::note << "iteration took: " << (dur.count() * 1000) << " ms";
std::this_thread::sleep_for(std::chrono::milliseconds(1200)); }
// notify the ui
emit statusChanged(Status::Failed);
} }
} }
void LolAutoAccept::resetPickOffsets() {
for(Stage& stage : stages) {
stage.currentOffset = 0;
}
lastPickedChamp = 0;
}
void LolAutoAccept::resetAllOffsets() { void LolAutoAccept::resetAllOffsets() {
for(Stage* stage : stages) { resetPickOffsets();
stage->currentOffset = 0; currentPosition = Position::INVALID;
} currentPositionSet = false;
chatid.clear();
autoWriteTextDone = false;
dodgeNow = false;
}
void LolAutoAccept::applyConfigToStage(Stage& stage, const Config::StageConfig& stageconf) {
stage.champids = dd.resolveChampIDs(stageconf.champs);
stage.enabled = stageconf.enabled;
stage.currentOffset = 0;
}
void LolAutoAccept::loadPosition(Position pos) {
Log::trace << __PRETTY_FUNCTION__ << " pos: " << pos;
// reinit the stages
std::lock_guard lock(stagesMutex);
stages.resize(1); // first stage does not change
stages.resize(3);
std::shared_ptr<Config::PositionConfig> posconf = config.getPositionConfig(pos);
applyConfigToStage(stages.at((int) State::BAN), posconf->ban);
applyConfigToStage(stages.at((int) State::PICK), posconf->pick);
currentPosition = pos;
currentPositionSet = true;
} }
uint32_t LolAutoAccept::getChampOfState(State s) { uint32_t LolAutoAccept::getChampOfState(State s) {
assert(((int) s) >= 0 && s <= State::PICK); assert(s > State::LOBBY && s <= State::PICK);
auto stage = stages[(int) s]; Stage& stage = stages[(int) s];
uint32_t size = stage->champids.size(); uint32_t size = stage.champids.size();
if(stage->currentOffset >= size) { if(stage.currentOffset >= size) {
// no champ to try left // no champ to try left
Log::warn << "no champ left at stage: " << (int) s; qWarning() << "no champ left at stage: " << (int) s;
Log::warn << "stage size: " << stage->champids.size(); qWarning() << "stage size: " << stage.champids.size();
return 0; return 0;
} }
return stage->champids[stage->currentOffset]; return stage.champids[stage.currentOffset];
} }
void LolAutoAccept::nextChampOfState(State s) { void LolAutoAccept::nextChampOfState(State s) {
assert(((int) s) >= 0 && s <= State::PICK); assert(s > State::LOBBY && s <= State::PICK);
auto stage = stages[(int) s]; Stage& stage = stages[(int) s];
uint32_t size = stage->champids.size(); uint32_t size = stage.champids.size();
stage->currentOffset++; stage.currentOffset++;
if(stage->currentOffset >= size) { if(stage.currentOffset >= size) {
// no champ to try left // no champ to try left
Log::warn << "no champ left at stage: " << (int) s; qWarning() << "no champ left at stage: " << (int) s;
Log::warn << "stage size: " << stage->champids.size(); qWarning() << "stage size: " << stage.champids.size();
stage->currentOffset = 0; stage.currentOffset = 0;
return; return;
} }
} }
@ -155,7 +297,16 @@ void LolAutoAccept::nextChampOfState(State s) {
bool LolAutoAccept::isChampIntentofTeammate(uint32_t champid, const ClientAPI::ChampSelectSession& session) { bool LolAutoAccept::isChampIntentofTeammate(uint32_t champid, const ClientAPI::ChampSelectSession& session) {
for(const ClientAPI::ChampSelectCell& player : session.myTeam) { for(const ClientAPI::ChampSelectCell& player : session.myTeam) {
if(player.championID == (int32_t) champid || player.championPickIntentID == (int32_t) champid) { if(player.championID == (int32_t) champid || player.championPickIntentID == (int32_t) champid) {
Log::info << "player " << player.cellID << " @ " << player.position << " wants to play " << champid; qInfo() << "player " << player.cellID << " @ " << toString(player.position) << " wants to play " << champid;
return true;
}
}
return false;
}
bool LolAutoAccept::isChampBanned(uint32_t champid, const ClientAPI::ChampSelectSession& session) {
for(const ClientAPI::ChampSelectAction& act : session.actions) {
if(act.type == ClientAPI::ChampSelectActionType::BAN && act.championID == (int32_t) champid && act.completed) {
return true; return true;
} }
} }
@ -166,7 +317,7 @@ LolAutoAccept::ownactions_t LolAutoAccept::getOwnActions(int32_t cellid, const s
ownactions_t out; ownactions_t out;
for(const auto& it : actions) { for(const auto& it : actions) {
if(it.actorCellID == cellid) { if(it.actorCellID == cellid) {
Log::debug << "ownaction: id: " << it.id << " champid: " << it.championID << " completed: " << it.completed << " type: " << it.type; qDebug() << "ownaction: id: " << it.id << " champid: " << it.championID << " completed: " << it.completed << " type: " << it.type;
if(!it.completed) { // completed cant be interacted anyways, so just ignore them. if(!it.completed) { // completed cant be interacted anyways, so just ignore them.
out.push_back(it); out.push_back(it);
} }
@ -176,12 +327,12 @@ LolAutoAccept::ownactions_t LolAutoAccept::getOwnActions(int32_t cellid, const s
} }
void LolAutoAccept::prepickPhase(const ownactions_t& ownactions) { void LolAutoAccept::prepickPhase(const ownactions_t& ownactions) {
phase(ownactions, ClientAPI::ChampSelectActionType::PICK, State::PREPICK, false); phase(ownactions, ClientAPI::ChampSelectActionType::PICK, State::PICK, false);
} }
void LolAutoAccept::banPhase(const ownactions_t& ownactions, const ClientAPI::ChampSelectSession& session) { void LolAutoAccept::banPhase(const ownactions_t& ownactions, const ClientAPI::ChampSelectSession& session) {
phase(ownactions, ClientAPI::ChampSelectActionType::BAN, State::BAN, true, [session](uint32_t champid) { phase(ownactions, ClientAPI::ChampSelectActionType::BAN, State::BAN, true, [session](uint32_t champid) {
return !isChampIntentofTeammate(champid, session); return !isChampIntentofTeammate(champid, session) && !isChampBanned(champid, session);
}); });
} }
@ -190,22 +341,28 @@ void LolAutoAccept::pickPhase(const ownactions_t& ownactions) {
} }
void LolAutoAccept::phase(const ownactions_t& ownactions, ClientAPI::ChampSelectActionType type, State s, bool complete, std::function<bool(uint32_t)> filter) { void LolAutoAccept::phase(const ownactions_t& ownactions, ClientAPI::ChampSelectActionType type, State s, bool complete, std::function<bool(uint32_t)> filter) {
if ( !( stages.at((int) s).enabled ) ) {
qDebug() << (int) s << " stage is disabled. skipping";
return;
}
for(auto it : ownactions) { for(auto it : ownactions) {
if(it.type == type) { if(it.type == type) {
Log::info << type << " action anvailable: " << it.id << " champid: " << it.championID; qInfo() << type << " action anvailable: " << it.id << " champid: " << it.championID;
uint32_t champid = getChampOfState(s); uint32_t champid = getChampOfState(s);
if(filter) { if(filter) {
// filter says no // filter says no
if(!filter(champid)) { if(!filter(champid)) {
Log::trace << "champid: " << champid << " filter says no - next champ";
nextChampOfState(s); nextChampOfState(s);
return; return;
} }
} }
if(it.championID != (int32_t) champid || !it.completed) { if((it.championID != (int32_t) champid || !it.completed) && champid != 0) {
// try to prepick a champion // try to prepick a champion
Log::info << "try to pick champ: " << champid; qInfo() << "try to pick champ: " << champid;
if(!clientapi->setChampSelectAction(it.id, champid, complete)) { if(!clientapi->setChampSelectAction(it.id, champid, complete)) {
nextChampOfState(s); nextChampOfState(s);
@ -216,29 +373,81 @@ void LolAutoAccept::phase(const ownactions_t& ownactions, ClientAPI::ChampSelect
} }
} }
int32_t LolAutoAccept::getBestRunePage(const std::vector<ClientAPI::RunePage>& pages) {
qDebug() << "searching RunePages: " << pages.size();
for(uint32_t i = 0; i < pages.size(); ++i) {
qDebug() << i << ": " << pages[i].id << " " << pages[i].name << " " << pages[i].isCurrent;
}
// search for a rune page with a name that starts with "AA: "
for(uint32_t i = 0; i < pages.size(); ++i) {
const ClientAPI::RunePage& rp = pages.at(i);
if(rp.name.size() >= 4 && rp.name.left(4) == "AA: ") {
return i;
}
}
// search for a selected rune page
for(uint32_t i = 0; i < pages.size(); ++i) {
const ClientAPI::RunePage& rp = pages.at(i);
if(rp.isCurrent) {
return i;
}
}
return -1;
}
int32_t LolAutoAccept::getMatchingRunePage(const RunePage& rp, const std::vector<ClientAPI::RunePage>& allpages) {
auto it = std::find_if(allpages.begin(), allpages.end(), [rp](const ClientAPI::RunePage& r){ return r.runepage == rp; });
return (it == allpages.end()) ? -1 : it - allpages.begin();
}
void LolAutoAccept::champSelect() { void LolAutoAccept::champSelect() {
auto session = clientapi->getChampSelectSession(); auto session = clientapi->getChampSelectSession();
int32_t cellid = session.localPlayerCellId; const int32_t cellid = session.localPlayerCellId;
if(cellid != lastCellId && lastCellId != -1) {
resetPickOffsets();
}
lastCellId = cellid;
// find own cellid info // find own cellid info
const ClientAPI::ChampSelectCell* me = nullptr; const ClientAPI::ChampSelectCell* me = nullptr;
uint32_t pickedChamp = 0;
for(const auto& it : session.myTeam) { for(const auto& it : session.myTeam) {
if(it.cellID == cellid) { if(it.cellID == cellid) {
me = &it; me = &it;
pickedChamp = it.championID;
break; break;
} }
} }
ClientAPI::Position pos = me ? me->position : ClientAPI::Position::INVALID; Position pos = me ? me->position : Position::INVALID;
Log::debug << "cellid: " << cellid << " position: " << pos << " counter: " << session.counter << " timer: timeleftip: " << session.timer.adjustedTimeLeftInPhase << " totaltimephase: " << session.timer.totalTimeInPhase; // check if runes need adjustment
if(pickedChamp != lastPickedChamp) {
qInfo() << "picked champ changed from: " << lastPickedChamp << " to: " << pickedChamp;
lastPickedChamp = pickedChamp;
}
// reload config based on position if changed
if(pos != currentPosition || !currentPositionSet) {
Log::note << "LolAutoAccept reloading config for position: " << pos << " because it was: " << currentPosition;
loadPosition(pos);
emit positionChanged(pos);
}
qDebug() << "cellid: " << cellid << " position: " << toString(pos) << " counter: " << session.counter << " timer: timeleftip: " << session.timer.adjustedTimeLeftInPhase << " totaltimephase: " << session.timer.totalTimeInPhase;
// find actions for own cell // find actions for own cell
auto ownactions = getOwnActions(cellid, session.actions); auto ownactions = getOwnActions(cellid, session.actions);
Log::debug << "ownactions: " << ownactions.size(); qDebug() << "ownactions: " << ownactions.size();
// try to prepick champ // try to prepick champ
Log::info << "champselectphase: " << session.timer.phase; qInfo() << "champselectphase: " << session.timer.phase;
std::lock_guard lock(stagesMutex);
if(session.timer.phase == ClientAPI::ChampSelectPhase::PLANNING) { if(session.timer.phase == ClientAPI::ChampSelectPhase::PLANNING) {
prepickPhase(ownactions); prepickPhase(ownactions);
} else if(session.timer.phase == ClientAPI::ChampSelectPhase::BAN_PICK) { } else if(session.timer.phase == ClientAPI::ChampSelectPhase::BAN_PICK) {
@ -246,5 +455,59 @@ void LolAutoAccept::champSelect() {
pickPhase(ownactions); pickPhase(ownactions);
} else if(session.timer.phase == ClientAPI::ChampSelectPhase::FINALIZATION) { } else if(session.timer.phase == ClientAPI::ChampSelectPhase::FINALIZATION) {
// trade? // trade?
// check for smite
if(smiteWarnEnabled) {
smiteWarning(session.myTeam);
}
}
// check for autowriteText
if(autoWriteTextEnabled && !autoWriteTextDone && !autoWriteText.isEmpty()) {
const QString& chatid = getChatid();
if(!chatid.isEmpty()) {
clientapi->sendMessage(chatid, autoWriteText);
autoWriteTextDone = true;
}
} }
} }
void LolAutoAccept::smiteWarning(const std::vector<ClientAPI::ChampSelectCell>& cells) {
uint32_t smiteCount = 0;
for(const ClientAPI::ChampSelectCell& member : cells) {
qInfo() << "position: " << toString(member.position) << " spells: " << member.spell1Id << " " << member.spell2Id;
smiteCount += (member.spell1Id == 11 || member.spell2Id == 11);
}
if(smiteCount != 1) {
// check timeout
std::chrono::time_point<std::chrono::system_clock> currenttime = std::chrono::system_clock::now();
if((currenttime - lastMessageSent) < std::chrono::seconds(2)) {
return;
}
qInfo() << "smite warning: " << smiteCount;
const QString& chatid = getChatid();
if(!chatid.isEmpty()) {
clientapi->sendMessage(chatid, "smite");
lastMessageSent = currenttime;
}
}
}
const QString& LolAutoAccept::getChatid() {
if(chatid.isEmpty()) {
std::vector<ClientAPI::Conversation> convs = clientapi->getAllConversations();
qInfo() << "got " << convs.size() << " conversations";
for(const ClientAPI::Conversation& conv : convs) {
qInfo() << " id: " << conv.id << " type: " << conv.type << " name: " << conv.name << " gameName: " << conv.gameName;
if(conv.type == "championSelect" && conv.name.isEmpty()) {
qInfo() << "groupchat found";
chatid = conv.id;
return chatid;
}
}
}
return chatid; //might be empty string
}

View File

@ -8,67 +8,47 @@
#include <QTranslator> #include <QTranslator>
#include <Log.h> #include <Log.h>
#include <QDebug>
#include "arg.h" #include "arg.h"
#include "mainwindow.h"
#include "lolautoaccept.h"
#include "clientaccess.h" #include "clientaccess.h"
#include "clientapi.h" #include "clientapi.h"
#include "mainwindow.h"
static std::string getBaseString(char** argv) {
std::string base;
char* appbase = getenv("APPDIR");
if(appbase) {
return std::string(appbase) + '/';
}
char* cresolved = realpath(argv[0], NULL);
std::string resolved(cresolved);
free(cresolved);
return resolved.substr(0, resolved.rfind('/')+1);
}
int main(int argc, char** argv) { int main(int argc, char** argv) {
Log::init(); Log::init();
Log::setConsoleLogLevel(Log::Level::INFO); Log::setConsoleLogLevel(Log::Level::info);
#if __unix__ #if __unix__
Log::setColoredOutput(true); Log::setColoredOutput(true);
#endif #endif
if(argc == 0) {
Log::fatal << "arg[0] is not set";
return 1;
}
Args args = parseArgs(argc, argv); Args args = parseArgs(argc, argv);
if(args.debugLog) { if(args.debugLog) {
Log::setConsoleLogLevel(Log::Level::TRACE); Log::setConsoleLogLevel(Log::Level::trace);
Log::addLogfile("log.txt", Log::Level::TRACE); Log::addLogfile("log.txt", Log::Level::trace);
Log::debug << "debug Log enabled"; qDebug() << "debug Log enabled";
} }
Log::info << "Hello, World!"; qInfo() << "Hello, World!";
Log::note << "Using Locale: " << QLocale().name().toStdString(); qInfo() << "Using Locale: " << QLocale().name();
std::string base = getBaseString(argv); if(args.access) {
Log::info << "appbase: " << base; auto access = ClientAccess::find();
qInfo() << "Access: port=" << access->getPort() << " basicAuth=" << access->getBasicAuth();
return 0;
}
LolAutoAccept lolaa;
QApplication app(argc, argv); QApplication app(argc, argv);
QTranslator translator; QTranslator translator;
if(translator.load(QLocale().name(), QString::fromStdString(base + "ts"))) { if(translator.load(QLocale().name(), ":/i18n")) {
app.installTranslator(&translator); app.installTranslator(&translator);
} else { } else {
Log::warn << "translation not found"; qWarning() << "translation not found";
} }
MainWindow win(lolaa); MainWindow win;
QIcon icon(QString::fromStdString(base + "lolautoaccept.png"));
win.setWindowIcon(icon);
win.show();
int ret = app.exec(); int ret = app.exec();
Log::stop(); Log::stop();
return ret; return ret;
} }

View File

@ -1,130 +1,238 @@
#include "mainwindow.h" #include "mainwindow.h"
#include "ui_mainwindow.h" #include "ui_mainwindow.h"
#include <QApplication>
#include <QTimer>
#include <QMessageBox>
#include <Log.h> #include <Log.h>
static void applySetting(const Config::StageConfig& sc, StageSettings* ss) { #include "loadingwindow.h"
ss->setState(sc.enabled); #include "x11helper.h"
ss->setChampions(sc.champs);
}
MainWindow::MainWindow(LolAutoAccept& lolaa, QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), lolaa(lolaa), dd(QLocale().name().toStdString()) { #ifdef X11SUPPORT
#define INIT_X11HELPER new X11Helper(this)
#else
#define INIT_X11HELPER nullptr
#endif
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), loading(true), ui(new Ui::MainWindow), saveTimer(new QTimer(this)), dd(QLocale().name()),
lolaa(conf.getConfig(), dd), lwin(new LoadingWindow(nullptr)),
dodgeQuestion(new QMessageBox(QMessageBox::Icon::Warning, MainWindow::tr("Dodge?"), MainWindow::tr("Are you sure you want to dodge?"), QMessageBox::Cancel | QMessageBox::Yes, this)),
x11Helper(INIT_X11HELPER) {
ui->setupUi(this); ui->setupUi(this);
ui->hideLeague->setEnabled(X11Helper::IsSupported);
QObject::connect(&dd, &DataDragon::fetchingChamp, lwin, &LoadingWindow::setChampion);
QObject::connect(&dd, &DataDragon::loading, lwin, &LoadingWindow::setProgress);
QObject::connect(&dd, &DataDragon::loading, this, &MainWindow::loadingStatus);
QObject::connect(lwin, &LoadingWindow::closed, &dd, &DataDragon::stop);
QObject::connect(lwin, &LoadingWindow::closed, qApp, &QCoreApplication::quit, Qt::ConnectionType::QueuedConnection);
dd.startThread();
QObject::connect(&lolaa, &LolAutoAccept::dodgePossible, ui->dodgeBtn, &QAbstractButton::setEnabled);
QObject::connect(ui->dodgeBtn, &QAbstractButton::pressed, dodgeQuestion, &QMessageBox::show);
QObject::connect(dodgeQuestion, &QMessageBox::accepted, &lolaa, &LolAutoAccept::dodge);
QObject::connect(&lolaa, &LolAutoAccept::statusChanged, this, &MainWindow::lolaaStatusChanged);
QObject::connect(&lolaa, &LolAutoAccept::positionChanged, this, &MainWindow::onPosChange);
QObject::connect(ui->hideLeague, &QCheckBox::stateChanged, this, &MainWindow::toggleLeagueVisibility);
saveTimer->setInterval(std::chrono::minutes(1));
saveTimer->setSingleShot(true);
QObject::connect(saveTimer, &QTimer::timeout, this, &MainWindow::saveConfig);
ui->copyrightlabel->setText(ui->copyrightlabel->text().arg(LOLAA_VERSION));
conf.load(); conf.load();
Config::RootConfig& rc = conf.getConfig();
ui->prepickstage->setDataDragon(&dd);
ui->banstage->setDataDragon(&dd);
ui->pickstage->setDataDragon(&dd);
const Config::RootConfig& rc = conf.getConfig();
ui->enableAll->setChecked(rc.enabledAutoAccept); ui->enableAll->setChecked(rc.enabledAutoAccept);
lolaa.setEnabled(rc.enabledAutoAccept, LolAutoAccept::State::LOBBY); lolaa.setEnabled(rc.enabledAutoAccept, LolAutoAccept::State::LOBBY);
applySetting(rc.prepick, ui->prepickstage);
applySetting(rc.ban, ui->banstage); ui->enableSmiteWarning->setChecked(rc.enabledSmiteWarn);
applySetting(rc.pick, ui->pickstage); lolaa.setSmiteWarn(rc.enabledSmiteWarn);
ui->enableAutoWrite->setChecked(rc.enabledAutoWrite);
ui->autoWriteText->setText(rc.autoWriteText);
lolaa.setAutoWriteText(rc.enabledAutoWrite, rc.autoWriteText);
resizeEvent(nullptr); resizeEvent(nullptr);
lwin->show();
} }
MainWindow::~MainWindow() { MainWindow::~MainWindow() {
lolaa.stop(); lolaa.stop();
conf.save(); conf.save();
delete ui; delete this->ui;
} }
void MainWindow::closeEvent([[maybe_unused]] QCloseEvent* event) { void MainWindow::closeEvent([[maybe_unused]] QCloseEvent* event) {
lolaa.stop();
conf.save(); conf.save();
} }
void MainWindow::resizeEvent([[maybe_unused]] QResizeEvent *event) { void MainWindow::resetSaveTimer() {
ui->verticalLayoutWidget->setMinimumSize(ui->centralwidget->size()); saveTimer->start();
ui->verticalLayoutWidget->setMaximumSize(ui->centralwidget->size()); qDebug() << "resetTimer";
ui->verticalLayoutWidget->setMinimumSize(ui->centralwidget->size()); }
void MainWindow::loadingStatus(float f) {
if(f >= 1.0 && lwin) {
// loading complete
// for all tabs - set their config and datadragon
Config::RootConfig& rc = conf.getConfig();
for(int32_t tabnr = 0; tabnr < ui->tabWidget->count(); ++tabnr) {
SettingsTab* tab = (SettingsTab*) ui->tabWidget->widget(tabnr);
tab->setup(*rc.getPositionConfig(tab->getPosition()), &dd);
QObject::connect(tab, &SettingsTab::changed, this, &MainWindow::tabchanged);
QObject::connect(tab, &SettingsTab::toggled, this, &MainWindow::tabtoggled);
}
// load runepage images
ui->runesPage->setDataDragon(dd);
ui->runesPage->setConfig(conf);
// switch from loading window to main window
lwin->hide();
this->show();
lwin->deleteLater();
lwin = nullptr;
// a timer to delay the loading flag a short time until all other signals are processed
QTimer::singleShot(std::chrono::milliseconds(1), this, &MainWindow::initDone);
}
}
void MainWindow::toggleLeagueVisibility() {
if(x11Helper) {
const bool shouldBeHidden = ui->hideLeague->isChecked();
Window win = x11Helper->findWindow("League of Legends", 1280.0/720.0);
qInfo() << "LeagueClient win id:" << win;
if(win != 0) {
x11Helper->setMap(win, shouldBeHidden);
} else {
// TODO: show error in status bar?
// TODO: reset checkbox to unchecked?
}
}
} }
void MainWindow::toggleMainswitch(bool state) { void MainWindow::toggleMainswitch(bool state) {
Log::info << "mainswitch toggled: " << state; qDebug() << "mainswitch toggled: " << state;
if(state) { if(state) {
ui->mainswitch->setCheckState(Qt::CheckState::PartiallyChecked);
ui->mainswitch->setEnabled(false);
// make sure the changes to the mainswitch are rendered, before going into the blocking call
QCoreApplication::processEvents();
// TODO: make this non blocking
if(!lolaa.init()) { if(!lolaa.init()) {
Log::error << "League Client not found!"; qCritical() << "League Client not found!";
ui->statusbar->showMessage(tr("League of Legends Client not found!")); ui->statusbar->showMessage(tr("League of Legends Client not found!"));
ui->mainswitch->setCheckState(Qt::CheckState::Unchecked); ui->mainswitch->setCheckState(Qt::CheckState::Unchecked);
ui->mainswitch->setEnabled(true);
return; return;
} }
lolaa.run(); lolaa.run();
ui->statusbar->showMessage(tr("Auto-Acceptor started!"));
} else { } else {
lolaa.stop(); lolaa.stop();
ui->statusbar->showMessage(tr("Auto-Acceptor stoped!")); ui->mainswitch->setCheckState(Qt::CheckState::PartiallyChecked);
ui->mainswitch->setEnabled(false);
} }
} }
void MainWindow::aatoggled(bool state) { void MainWindow::aatoggled(bool state) {
Log::info << "enableAll checkbox toggled " << state; if( loading ) return;
qDebug() << "enableAll checkbox toggled " << state;
lolaa.setEnabled(state, LolAutoAccept::State::LOBBY); lolaa.setEnabled(state, LolAutoAccept::State::LOBBY);
conf.getConfig().enabledAutoAccept = state; conf.getConfig().enabledAutoAccept = state;
resetSaveTimer();
} }
static std::vector<std::string> toChampionNameList(const std::vector<StageSettings::SelectedChamp>& scs) { void MainWindow::smitewarntoggled(bool state) {
std::vector<std::string> out; if( loading ) return;
for(const StageSettings::SelectedChamp& sc : scs) { qDebug() << "smitewarn checkbox toggled " << state;
out.push_back(sc.name);
lolaa.setSmiteWarn(state);
conf.getConfig().enabledSmiteWarn = state;
resetSaveTimer();
}
void MainWindow::tabtoggled(Position p, LolAutoAccept::State s, bool state) {
if( loading ) return;
qDebug() << "checkbox toggled " << state << " position: " << p << " state: " << (int) s;
lolaa.setEnabled(state, s);
lolaa.reload();
resetSaveTimer();
}
void MainWindow::tabchanged(Position p, LolAutoAccept::State s) {
if( loading ) return;
qDebug() << "edited position: " << p << " state: " << (int) s;
lolaa.reload();
resetSaveTimer();
}
void MainWindow::autoWriteChanged() {
if( loading ) return;
bool enabled = ui->enableAutoWrite->isChecked();
const QString text = ui->autoWriteText->toPlainText();
lolaa.setAutoWriteText(enabled, text);
conf.getConfig().enabledAutoWrite = enabled;
conf.getConfig().autoWriteText = text;
resetSaveTimer();
}
void MainWindow::saveConfig() {
conf.save();
}
void MainWindow::initDone() {
loading = false;
qDebug() << "loading done";
}
void MainWindow::onPosChange(Position newpos) {
assert(newpos <= Position::UTILITY);
emit requestTabChange((int) newpos);
}
void MainWindow::lolaaStatusChanged(LolAutoAccept::Status status) {
qDebug() << "new status: " << (int) status;
switch(status) {
case LolAutoAccept::Status::Off:
this->ui->statusbar->showMessage(tr("Auto-Acceptor stoped!"));
break;
case LolAutoAccept::Status::Running:
this->ui->statusbar->showMessage(tr("Auto-Acceptor started!"));
break;
case LolAutoAccept::Status::Failed:
this->ui->statusbar->showMessage(tr("Auto-Acceptor failed!"));
break;
} }
return out; this->ui->mainswitch->setEnabled(true);
} this->ui->mainswitch->setChecked(status == LolAutoAccept::Status::Running);
static std::vector<uint32_t> toChampionIDList(const std::vector<StageSettings::SelectedChamp>& scs) {
std::vector<uint32_t> out;
for(const StageSettings::SelectedChamp& sc : scs) {
out.push_back(sc.id);
}
return out;
}
void MainWindow::pptoggled(bool state) {
Log::info << "enablePrePick checkbox toggled " << state;
lolaa.setEnabled(state, LolAutoAccept::State::PREPICK);
conf.getConfig().prepick.enabled = state;
}
void MainWindow::ppedited() {
Log::info << "prepick edited";
auto champs = ui->prepickstage->getChampions();
lolaa.setChamps(toChampionIDList(champs), LolAutoAccept::State::PREPICK);
conf.getConfig().prepick.champs = toChampionNameList(champs);
}
void MainWindow::bantoggled(bool state) {
Log::info << "enableBan checkbox toggled " << state;
lolaa.setEnabled(state, LolAutoAccept::State::BAN);
conf.getConfig().ban.enabled = state;
}
void MainWindow::banedited() {
Log::info << "ban edited";
auto champs = ui->banstage->getChampions();
lolaa.setChamps(toChampionIDList(champs), LolAutoAccept::State::BAN);
conf.getConfig().ban.champs = toChampionNameList(champs);
}
void MainWindow::picktoggled(bool state) {
Log::info << "enablePick checkbox toggled " << state;
lolaa.setEnabled(state, LolAutoAccept::State::PICK);
conf.getConfig().pick.enabled = state;
}
void MainWindow::pickedited() {
Log::info << "pick edited";
auto champs = ui->pickstage->getChampions();
lolaa.setChamps(toChampionIDList(champs), LolAutoAccept::State::PICK);
conf.getConfig().pick.champs = toChampionNameList(champs);
} }

View File

@ -5,7 +5,7 @@
MemoryImageCache::MemoryImageCache(size_t maxsize) : maxsize(maxsize) {} MemoryImageCache::MemoryImageCache(size_t maxsize) : maxsize(maxsize) {}
void MemoryImageCache::addImage(QPixmap img, const std::string& title, int type) { void MemoryImageCache::addImage(QPixmap img, const QString& title, int type) {
auto it = std::find_if(cache.begin(), cache.end(), getImageMatcher(title, type)); auto it = std::find_if(cache.begin(), cache.end(), getImageMatcher(title, type));
if(it == cache.end()) { if(it == cache.end()) {
// insert new // insert new
@ -19,7 +19,7 @@ void MemoryImageCache::addImage(QPixmap img, const std::string& title, int type)
it->imageref = img; it->imageref = img;
} }
QPixmap MemoryImageCache::getImage(const std::string& title, int type) { QPixmap MemoryImageCache::getImage(const QString& title, int type) {
auto it = std::find_if(cache.begin(), cache.end(), getImageMatcher(title, type)); auto it = std::find_if(cache.begin(), cache.end(), getImageMatcher(title, type));
if(it == cache.end()) { if(it == cache.end()) {
return {}; return {};
@ -40,7 +40,7 @@ bool MemoryImageCache::CachedImage::operator<(const MemoryImageCache::CachedImag
return lastaccessed < other.lastaccessed; return lastaccessed < other.lastaccessed;
} }
std::function<bool(const MemoryImageCache::CachedImage&)> MemoryImageCache::getImageMatcher(const std::string& title, int type) { std::function<bool(const MemoryImageCache::CachedImage&)> MemoryImageCache::getImageMatcher(const QString& title, int type) {
return [title, type](const MemoryImageCache::CachedImage& img) -> bool { return [title, type](const MemoryImageCache::CachedImage& img) -> bool {
return img.type == type && img.title == title; return img.type == type && img.title == title;
}; };

View File

@ -33,6 +33,7 @@ static void dump(const char* text, FILE* stream, unsigned char* ptr, size_t size
static int my_trace(CURL* handle, curl_infotype type, char* data, size_t size, void* userp) { static int my_trace(CURL* handle, curl_infotype type, char* data, size_t size, void* userp) {
const char* text; const char* text;
(void) handle; /* prevent compiler warning */ (void) handle; /* prevent compiler warning */
(void) userp;
switch (type) { switch (type) {
case CURLINFO_TEXT: case CURLINFO_TEXT:
@ -62,7 +63,7 @@ static int my_trace(CURL* handle, curl_infotype type, char* data, size_t size, v
return 0; return 0;
} }
RestClient::RestClient(const std::string& base) : baseurl(base) { RestClient::RestClient(const QString& base) : baseurl(base) {
curl = curl_easy_init(); curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, arrayWriteCallback); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, arrayWriteCallback);
@ -73,30 +74,34 @@ RestClient::~RestClient() {
curl = nullptr; curl = nullptr;
} }
QByteArray RestClient::requestRaw(const std::string& url, Method m, const std::string& data) { RestClient::WebException::WebException(CURLcode c) : curlresponse(c) {
}
QByteArray RestClient::requestRaw(const QString& url, Method m, const QString& data) {
if (!curl) return {}; if (!curl) return {};
std::string requrl = baseurl + url; QString requrl = baseurl + url;
QByteArray ba; //buffer const QByteArray reqArr = requrl.toLocal8Bit();
// std::cout << "[DEBUG] requrl is: " << requrl << std::endl; curl_easy_setopt(curl, CURLOPT_URL, reqArr.data());
curl_easy_setopt(curl, CURLOPT_URL, requrl.c_str());
// curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); //Prevent "longjmp causes uninitialized stack frame" bug const QByteArray basicArr = basicauth.toLocal8Bit();
// set callback data curl_easy_setopt(curl, CURLOPT_USERPWD, basicArr.data());
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ba);
curl_easy_setopt(curl, CURLOPT_USERPWD, basicauth.c_str());
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
if (disableCertCheck) { if (disableCertCheck) {
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
} }
// restore default HTTP Options
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, NULL);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, NULL);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
// curl header // curl header
struct curl_slist* headerlist = NULL; struct curl_slist* headerlist = NULL;
headerlist = curl_slist_append(headerlist, "Accept: application/json"); headerlist = curl_slist_append(headerlist, "Accept: application/json");
const QByteArray dataArr = data.toLocal8Bit();
switch (m) { switch (m) {
default: default:
@ -104,26 +109,25 @@ QByteArray RestClient::requestRaw(const std::string& url, Method m, const std::s
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); break; curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); break;
case Method::POST: case Method::POST:
curl_easy_setopt(curl, CURLOPT_POST, 1L); curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, dataArr.data());
headerlist = curl_slist_append(headerlist, "Content-Type: application/json"); if(!dataArr.isEmpty()) {
headerlist = curl_slist_append(headerlist, "Content-Type: application/json");
}
break; break;
case Method::PUT: case Method::PUT:
curl_easy_setopt(curl, CURLOPT_PUT, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str());
headerlist = curl_slist_append(headerlist, "Content-Type: application/json");
break;
case Method::PATCH: case Method::PATCH:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PATCH"); case Method::DELETE:
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data.c_str()); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, toString(m)); // to use the POSTFIELDS (do not use CURLOPT_PUT, it does not support POSTFIELDS)
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, dataArr.data());
headerlist = curl_slist_append(headerlist, "Content-Type: application/json"); headerlist = curl_slist_append(headerlist, "Content-Type: application/json");
break; break;
case Method::DELETE:
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); break;
} }
if (headerlist) { curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
} QByteArray ba; //buffer
// set callback data
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ba);
CURLcode res = curl_easy_perform(curl); CURLcode res = curl_easy_perform(curl);
if (headerlist) { if (headerlist) {
@ -134,24 +138,26 @@ QByteArray RestClient::requestRaw(const std::string& url, Method m, const std::s
if (res == CURLE_HTTP_RETURNED_ERROR) { if (res == CURLE_HTTP_RETURNED_ERROR) {
long responsecode = -1; long responsecode = -1;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responsecode); curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responsecode);
Log::warn << "API request failed: " << baseurl << " " << url << " -> " << responsecode; qWarning() << "API request failed: " << baseurl << " " << url << " -> " << responsecode;
} } else {
else { qWarning() << "API request failed: " << baseurl << " " << url << " " << curl_easy_strerror(res);
Log::warn << "API request failed: " << baseurl << " " << url << " " << curl_easy_strerror(res); if(res == CURLE_COULDNT_CONNECT) {
throw WebException(res);
}
} }
return {}; return {};
} }
return ba; return ba;
} }
QJsonDocument RestClient::request(const std::string& url, Method m, const std::string& data) { QJsonDocument RestClient::request(const QString& url, Method m, const QString& data) {
QByteArray arr = requestRaw(url, m, data); QByteArray arr = requestRaw(url, m, data);
if (arr.isEmpty()) return {}; if (arr.isEmpty()) return {};
QJsonParseError err; QJsonParseError err;
QJsonDocument parsed = QJsonDocument::fromJson(arr, &err); QJsonDocument parsed = QJsonDocument::fromJson(arr, &err);
if (parsed.isNull() || err.error != QJsonParseError::NoError) { if (parsed.isNull() || err.error != QJsonParseError::NoError) {
Log::error << "API Json parse error " << err.errorString().toStdString() << " offset: " << err.offset; qCritical() << "API Json parse error " << err.errorString() << " offset: " << err.offset;
return {}; return {};
} }
@ -168,3 +174,16 @@ void RestClient::enableDebugging(bool enabled) {
curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L); curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L);
} }
} }
QString RestClient::escape(const QString& in) const {
char* e = curl_easy_escape(curl, in.toLocal8Bit().data(), in.length());
QString esc(e);
curl_free(e);
return esc;
}
const char* toString(RestClient::Method m) {
static const char* MethodNames[] = {"GET", "POST", "PUT", "PATCH", "DELETE"};
return MethodNames[(int) m];
}

55
src/runeaspektbutton.cpp Normal file
View File

@ -0,0 +1,55 @@
#include "runeaspektbutton.h"
#include "ui_runeaspektbutton.h"
#include <QDebug>
#include "runeaspektbuttongroup.h"
RuneAspektButton::RuneAspektButton(QWidget* parent) : QPushButton(parent), ui(new Ui::RuneAspektButton) {
ui->setupUi(this);
QObject::connect(this, &QPushButton::pressed, this, &RuneAspektButton::buttonPressed);
}
RuneAspektButton::~RuneAspektButton() {
delete this->ui;
}
void RuneAspektButton::setAspektId(uint32_t id) {
aspektId = id;
dataChanged();
}
void RuneAspektButton::setButtonGroup(RuneAspektButtonGroup* group) {
this->group = group;
}
bool RuneAspektButton::isSelected() const {
return group && group->getSelectedRunes().contains(aspektId);
}
void RuneAspektButton::buttonPressed() {
emit aspektToggled(aspektId);
}
void RuneAspektButton::dataChanged() {
bool selection = isSelected();
qDebug() << text() << " datachanged - isSelected: " << selection;
setShowSelection(selection);
}
void RuneAspektButton::checkSelection(uint32_t aspekt) {
qDebug() << "checkSelection: " << text() << aspekt << aspektId;
setShowSelection(aspekt == this->aspektId);
}
void RuneAspektButton::setShowSelection(bool selected) {
if(selected) {
setStyleSheet("border: 1px solid red;");
} else {
setStyleSheet("");
}
}

View File

@ -0,0 +1,73 @@
#include "runeaspektbuttongroup.h"
#include "runeaspektbutton.h"
#include <QDebug>
const int RuneAspektButtonGroup::INVALID_ASPEKT_ID = 0;
RuneAspektButtonGroup::RuneAspektButtonGroup(QObject* parent, uint32_t size) : QObject(parent), selectedRune(), size(size) {
selectedRune.reserve(size*2);
}
RuneAspektButtonGroup::~RuneAspektButtonGroup() {}
void RuneAspektButtonGroup::addButton(RuneAspektButton* button) {
QObject::connect(this, &RuneAspektButtonGroup::changed, button, &RuneAspektButton::dataChanged);
QObject::connect(button, &RuneAspektButton::aspektToggled, this, &RuneAspektButtonGroup::buttonPressed);
button->setButtonGroup(this);
}
void RuneAspektButtonGroup::setSelectedRunes(const QVector<int>& newRunes) {
selectedRune = newRunes;
qDebug() << "selectedRunes changed to: " << selectedRune << " refesching buttons";
emit changed();
}
void RuneAspektButtonGroup::setSubgroups(const QVector<QVector<int>>& newSubgroups) {
subgroups = newSubgroups;
}
void RuneAspektButtonGroup::buttonPressed(int aspektId) {
if(selectedRune.contains(aspektId)) {
selectedRune.removeAll(aspektId);
} else {
// check subgroups first
int otherSubgroupMember = getOtherSubgroupMemeber(aspektId);
if(otherSubgroupMember != INVALID_ASPEKT_ID) {
// collision in subgroup -> remove other member
selectedRune.removeAll(otherSubgroupMember);
}
selectedRune.push_back(aspektId);
if((uint32_t) selectedRune.size() > size) {
selectedRune.removeFirst();
}
}
emit changed();
}
int RuneAspektButtonGroup::getOtherSubgroupMemeber(int aspektId) {
if(subgroups.empty()) {
return INVALID_ASPEKT_ID;
}
for(const QVector<int>& subgroup : subgroups) {
if(subgroup.contains(aspektId)) {
for(const int& subgroupMember : subgroup) {
if(aspektId != subgroupMember && selectedRune.contains(subgroupMember)) {
return subgroupMember;
}
}
break;
}
}
return INVALID_ASPEKT_ID;
}

70
src/runedisplay.cpp Normal file
View File

@ -0,0 +1,70 @@
#include "runedisplay.h"
#include "ui_runedisplay.h"
#include <QTextStream>
const static QString EMPTY;
RuneDisplay::RuneDisplay(QWidget *parent) : QWidget(parent), ui(new Ui::RuneDisplay) {
ui->setupUi(this);
}
RuneDisplay::~RuneDisplay() {
delete ui;
}
void RuneDisplay::setRuneMeta(const std::vector<RuneAspekt>& ri) {
runeinfo = ri;
}
void RuneDisplay::setStyles(const std::vector<RuneStyle>& styleinfos) {
runestyles = styleinfos;
}
void RuneDisplay::setRunes(const RunePage& rp) {
runepage = rp;
updateText();
}
void RuneDisplay::applyRunesClicked() {
emit applyRunes();
}
void RuneDisplay::updateText() {
QString outStr;
QTextStream out(&outStr);
if(! (bool) runepage) {
ui->runetext->setText("");
ui->applyRunesBtn->setEnabled(false);
return;
}
out << getRuneStyleByID(runepage.primaryStyle) << " with " << getRuneStyleByID(runepage.secondaryStyle) << '\n';
for(uint32_t rune : runepage.selectedAspects) {
out << getRuneText(rune) << '\n';
}
ui->runetext->setText(outStr);
ui->applyRunesBtn->setEnabled(true);
}
QString RuneDisplay::getRuneText(uint32_t id) {
for(const RuneAspekt& ra : runeinfo) {
if(ra.id == id) {
return ra.name;
}
}
return "(" + QString::number(id) + ")";
}
QString RuneDisplay::getRuneStyleByID(uint32_t id) {
auto it = std::find_if(runestyles.begin(), runestyles.end(), [id](const RuneStyle& rs) { return rs.id == id; });
if(it == runestyles.end()) {
return '(' + QString::number(id) + ')';
}
return it->name;
}

281
src/runeeditor.cpp Normal file
View File

@ -0,0 +1,281 @@
#include "runeeditor.h"
#include "ui_runeeditor.h"
#include <QByteArray>
#include <QDebug>
#include <QGridLayout>
#include <QPixmap>
#include <QPushButton>
#include "clientapi.h"
#include "runeaspektbutton.h"
#include "runeaspektbuttongroup.h"
RuneEditor::RuneEditor(QWidget* parent) : QDialog(parent), ui(new Ui::RuneEditor), groups(8, nullptr) {
ui->setupUi(this);
QObject::connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &RuneEditor::accept);
QObject::connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &RuneEditor::reject);
}
RuneEditor::~RuneEditor() {
delete this->ui;
}
void RuneEditor::setClient(ClientAPI& client) {
this->client = &client;
if(!this->client) return;
try {
// build ui
aspekts = client.getAllRuneAspekts();
styles = client.getAllRuneStyles();
for(const RuneStyle& rs : styles) {
RuneAspektButton* runeStyleBtn = createStyleButton(rs, rs.id == runepage.primaryStyle);
if(!runeStyleBtn) continue;
QObject::connect(runeStyleBtn, &QPushButton::pressed, [this, id = rs.id](){
selectStyle(id);
});
QObject::connect(this, &RuneEditor::selectPrimary, runeStyleBtn, &RuneAspektButton::checkSelection);
ui->style->addWidget(runeStyleBtn);
}
} catch(RestClient::WebException& e) {
qCritical() << "webexception: " << e.curlresponse;
if(e.curlresponse == CURLE_COULDNT_CONNECT) {
this->client = nullptr;
}
}
}
void RuneEditor::setRunepage(const ::RunePage& rp) {
qInfo() << "runepage: " << rp.selectedAspects.size();
selectStyle(rp.primaryStyle);
selectSubStyle(rp.secondaryStyle);
runepage = rp;
uint32_t offset = 0;
for(RuneAspektButtonGroup* group : groups) {
if(!group) continue;
QVector<int> selected(group->getSize(), 0);
for(uint32_t i = 0; i < group->getSize(); ++i) {
selected.replace(i, rp.selectedAspects.at(offset));
offset++;
}
group->setSelectedRunes(selected);
}
emit selectionChanged();
}
void RuneEditor::selectStyle(uint32_t id) {
if(runepage.primaryStyle == id) return;
const RuneStyle* style = getRuneStyle(id);
if(style) {
runepage.primaryStyle = id;
runepage.secondaryStyle = 0;
runepage.selectedAspects.clear();
runepage.selectedAspects.resize(9, 0);
emit selectPrimary(id);
clearLayout(ui->substyle);
clearLayout(ui->stylePerks);
clearLayout(ui->substylePerks);
// populate substyles
for(int subStyleId : style->allowedSubStyles) {
const RuneStyle* substyle = getRuneStyle(subStyleId);
RuneAspektButton* subStyleBtn = createStyleButton(*substyle, false);
if(!subStyleBtn) continue;
QObject::connect(subStyleBtn, &QPushButton::pressed, [this, subStyleId](){
selectSubStyle(subStyleId);
});
QObject::connect(this, &RuneEditor::selectSecondary, subStyleBtn, &RuneAspektButton::checkSelection);
ui->substyle->addWidget(subStyleBtn);
}
// populate perks
fillRuneStyle(ui->stylePerks, *style);
}
}
void RuneEditor::selectSubStyle(uint32_t id) {
if(runepage.secondaryStyle == id) return;
const RuneStyle* substyle = getRuneStyle(id);
if(substyle) {
runepage.secondaryStyle = id;
emit selectSecondary(id);
clearLayout(ui->substylePerks);
delete groups.at(4);
QVector<QVector<int>> subgroups;
subgroups.reserve(substyle->runeSlots.size());
// populate perks
RuneAspektButtonGroup* group = new RuneAspektButtonGroup(this, 2);
for(size_t idx = 0; idx < substyle->runeSlots.size(); ++idx) {
const RuneStyleSlot& rss = substyle->runeSlots.at(idx);
QVector<int> subgroup;
subgroup.reserve(rss.perks.size());
if(rss.type != "kMixedRegularSplashable") continue;
for(int perkNr = 0; perkNr < (int) rss.perks.size(); ++perkNr) {
uint32_t perk = rss.perks.at(perkNr);
subgroup.append((int) perk);
RuneAspektButton* aspektBtn = createAspektButton(perk);
if(!aspektBtn) continue;
group->addButton(aspektBtn);
ui->substylePerks->addWidget(aspektBtn, idx, perkNr);
}
subgroups.append(subgroup);
}
group->setSubgroups(subgroups);
groups.replace(4, group);
}
}
void RuneEditor::clearLayout(QLayout* layout) {
while(layout->count()) {
QLayoutItem* item = layout->takeAt(0);
delete item->widget();
delete item;
}
}
void RuneEditor::setName(QString text) {
ui->runepageName->setText(text);
}
QString RuneEditor::getName() const {
return ui->runepageName->text();
}
const RunePage& RuneEditor::getRunepage() {
runepage.selectedAspects.clear();
runepage.selectedAspects.resize(9, 0);
uint_fast8_t index = 0;
for(const RuneAspektButtonGroup* group : groups) {
if(!group) continue;
const QVector<int>& selected = group->getSelectedRunes();
for(uint32_t i = 0; i < group->getSize(); ++i) {
runepage.selectedAspects.at(index) = (selected.at(i));
++index;
}
}
return runepage;
}
const RuneStyle* RuneEditor::getRuneStyle(uint32_t id) const {
auto it = std::find_if(styles.cbegin(), styles.cend(), [id](const RuneStyle& rs) {
return rs.id == id;
});
return it == styles.cend() ? nullptr : &*it;
}
RuneAspektButton* RuneEditor::createStyleButton(const RuneStyle& rs, bool selected) {
RuneAspektButton* styleBtn = createButtonFromResource(rs.iconPath);
if(!styleBtn) return nullptr;
styleBtn->setText(rs.name);
styleBtn->setToolTip(rs.tooltip);
styleBtn->setAspektId(rs.id);
if(selected) {
styleBtn->setStyleSheet("border: 1px solid red;");
}
return styleBtn;
}
RuneAspektButton* RuneEditor::createAspektButton(uint32_t perk) {
auto itFound = std::find_if(aspekts.cbegin(), aspekts.cend(), [perk](const RuneAspekt& ra) {
return ra.id == perk;
});
if(itFound == aspekts.cend()) {
return nullptr;
}
RuneAspektButton* aspektBtn = createButtonFromResource(itFound->iconPath);
aspektBtn->setText(itFound->name);
aspektBtn->setToolTip(itFound->tooltip);
aspektBtn->setAspektId(itFound->id);
return aspektBtn;
}
RuneAspektButton* RuneEditor::createButtonFromResource(QString resource) {
if(!client) {
return nullptr;
}
QPixmap icon;
try {
icon = client->getImageResource(resource.remove(0, 1));
} catch(RestClient::WebException& e) {
qCritical() << "webexception: " << e.curlresponse;
if(e.curlresponse == CURLE_COULDNT_CONNECT) {
client = nullptr;
}
}
RuneAspektButton* rab = new RuneAspektButton(this);
rab->setIcon(icon);
return rab;
}
void RuneEditor::fillRuneStyle(QGridLayout* target, const RuneStyle& rs) {
for(size_t idx = 0; idx < rs.runeSlots.size(); ++idx) {
const RuneStyleSlot& rss = rs.runeSlots.at(idx);
RuneAspektButtonGroup* group = new RuneAspektButtonGroup(this, 1);
for(int perkNr = 0; perkNr < (int) rss.perks.size(); ++perkNr) {
uint32_t perk = rss.perks.at(perkNr);
RuneAspektButton* aspektBtn = createAspektButton(perk);
if(!aspektBtn) continue;
group->addButton(aspektBtn);
target->addWidget(aspektBtn, idx, perkNr);
}
groups.replace(idx + (idx > 3), group);
}
}
QString RuneEditor::fixString(QString text) {
return text.replace("&nbsp;", "").replace("</?.*?>", "");
}

259
src/runemanager.cpp Normal file
View File

@ -0,0 +1,259 @@
#include "runemanager.h"
#include "ui_runemanager.h"
#include <QDebug>
#include <QListWidgetItem>
#include <QTimer>
#include "clientapi.h"
RuneManager::RuneManager(QWidget* parent) : QWidget(parent), ui(new Ui::RuneManager) {
ui->setupUi(this);
ui->listClientRunes->setIsClient(true);
ui->listaaRunes->setIsClient(false);
ui->listClientRunes->setOther(ui->listaaRunes);
ui->listaaRunes->setOther(ui->listClientRunes);
QObject::connect(ui->listaaRunes, &RunePageList::runepageChanged, this, &RuneManager::saveRunePageAA);
QObject::connect(ui->listClientRunes, &RunePageList::runepageChanged, this, &RuneManager::saveRunePageClient);
QObject::connect(ui->listaaRunes, &RunePageList::runepageDeleted, this, &RuneManager::deleteRunepageAA);
QObject::connect(ui->listClientRunes, &RunePageList::runepageDeleted, this, &RuneManager::deleteRunepageClient);
QObject::connect(ui->chkAutoCopy, &QCheckBox::clicked, this, &RuneManager::autoSyncToggled);
initialLoadTimer = new QTimer(this);
QObject::connect(initialLoadTimer, &QTimer::timeout, this, &RuneManager::loadRunes);
initialLoadTimer->setInterval(std::chrono::milliseconds(1));
initialLoadTimer->setSingleShot(true);
initialLoadTimer->start();
}
RuneManager::~RuneManager() {
delete this->ui;
}
void RuneManager::setConfig(Config& config) {
this->config = &config;
Config::GeneralRunePageConfig& rpc = config.getConfig().runepagesConfig;
ui->listaaRunes->loadRunePages(rpc.runePages);
ui->chkAutoCopy->setChecked(rpc.autoSync);
if(rpc.autoSync) {
syncRunes();
}
}
void RuneManager::setDataDragon(DataDragon& dd) {
ui->listaaRunes->setDataDragon(dd);
ui->listClientRunes->setDataDragon(dd);
}
void RuneManager::loadRunes() {
if(initialLoadTimer) {
initialLoadTimer->deleteLater();
initialLoadTimer = nullptr;
}
this->ui->btnRetry->setEnabled(false);
QCoreApplication::processEvents();
if(!client) {
auto ca = ClientAccess::find();
if(ca) {
client = std::make_shared<ClientAPI>(*ca.get());
}
}
if(client) {
try {
// load meta data
runeInfo = client->getAllRuneAspekts();
QCoreApplication::processEvents();
runeStyles = client->getAllRuneStyles();
QCoreApplication::processEvents();
ui->listClientRunes->setClient(*client);
ui->listaaRunes->setClient(*client);
this->ui->listClientRunes->setRuneInfos(runeInfo, runeStyles);
this->ui->listaaRunes->setRuneInfos(runeInfo, runeStyles);
// load runepages
reloadClientRunes();
// reload runepages - so they ids can get their names
reloadAARunes();
// check if autosync is enabled
if(config && config->getConfig().runepagesConfig.autoSync) {
syncRunes();
}
} catch(RestClient::WebException& e) {
qCritical() << "webexception: " << e.curlresponse;
if(e.curlresponse == CURLE_COULDNT_CONNECT) {
client.reset();
}
}
}
setRunesEnabled(!!client); // cast to bool
this->ui->btnRetry->setEnabled(true);
}
void RuneManager::reloadClientRunes() {
if(client) {
const std::vector<ClientAPI::RunePage> runePages = client->getAllRunePages();
ui->listClientRunes->loadRunePages(runePages);
}
}
void RuneManager::setRunesEnabled(bool enabled) {
this->ui->lblClientRunes->setEnabled(enabled);
this->ui->listClientRunes->setEnabled(enabled);
}
void RuneManager::saveRunePageClient(int id, QString name, const RunePage& rp) {
if(client) {
ClientAPI::RunePage newPage;
newPage.name = name;
newPage.runepage = rp;
newPage.id = id;
try {
if(id == -1) {
// create new page
if(!client->createRunePage(newPage)) {
// TODO: some error occured
}
} else {
// edit existing page
if(!client->editRunePage(newPage)) {
// TODO: some error occured
}
}
reloadClientRunes();
} catch(RestClient::WebException& e) {
qCritical() << "webexception: " << e.curlresponse;
if(e.curlresponse == CURLE_COULDNT_CONNECT) {
client.reset();
setRunesEnabled(false);
}
}
}
}
void RuneManager::saveRunePageAA(int id, QString name, const RunePage& rp) {
if(!config) return;
Config::RootConfig& rc = config->getConfig();
auto& pages = rc.runepagesConfig.runePages;
if(id == -1) {
// int newId = pages.size();
pages.push_back(std::make_shared<Config::RunePageConfig>(name, rp));
} else {
if((int) pages.size() > id && id >= 0) {
pages.at(id)->runepage = rp;
pages.at(id)->name = name;
} else {
// unkown id
qWarning() << "unknown runepage id:" << id;
}
}
config->save();
// reload runes
ui->listaaRunes->loadRunePages(pages);
}
void RuneManager::deleteRunepageClient(int id) {
if(client) {
try {
if(!client->deleteRunePage(id)) {
// TODO: some error occured
}
} catch(RestClient::WebException& e) {
qCritical() << "webexception: " << e.curlresponse;
if(e.curlresponse == CURLE_COULDNT_CONNECT) {
client.reset();
setRunesEnabled(false);
}
}
}
}
void RuneManager::deleteRunepageAA(int id) {
if(!config) return;
Config::RootConfig& rc = config->getConfig();
auto& pages = rc.runepagesConfig.runePages;
if((int) pages.size() > id && id >= 0) {
pages.erase(pages.begin() + id);
config->save();
ui->listaaRunes->loadRunePages(pages);
} else {
// unkown id
qWarning() << "unknown runepage id:" << id;
}
}
void RuneManager::autoSyncToggled() {
bool autoSync = (ui->chkAutoCopy->isChecked());
if(config) {
config->getConfig().runepagesConfig.autoSync = autoSync;
config->save();
}
if(autoSync) {
syncRunes();
}
}
void RuneManager::syncRunes() {
qInfo() << "syncing" << ui->listClientRunes->count() << "runes";
std::vector<std::shared_ptr<Config::RunePageConfig>>& configs = config->getConfig().runepagesConfig.runePages;
bool changed = false;
for(int i = 0; i < ui->listClientRunes->count(); ++i) {
const QListWidgetItem* item = ui->listClientRunes->item(i);
QString name = item->text();
const RunePage* rp = (RunePage*) item->data(RunePageList::RolePointer).toULongLong();
auto itFound = std::find_if(configs.cbegin(), configs.cend(), [name, rp](const std::shared_ptr<Config::RunePageConfig>& rpc){
return rpc->name == name && *rp == rpc->runepage;
});
if(itFound == configs.cend()) {
// no duplicate found -> add it
configs.push_back(std::make_shared<Config::RunePageConfig>(name, *rp));
changed = true;
}
}
if(changed) {
config->save();
ui->listaaRunes->loadRunePages(configs);
}
}
void RuneManager::reloadAARunes() {
if(!config) return;
const auto& pages = config->getConfig().runepagesConfig.runePages;
// reload runes
ui->listaaRunes->loadRunePages(pages);
}

59
src/runepage.cpp Normal file
View File

@ -0,0 +1,59 @@
#include "runepage.h"
#include <QJsonArray>
#include <algorithm>
#include "json.h"
RunePage::RunePage() {}
bool RunePage::operator==(const RunePage& rp) const {
if(primaryStyle == rp.primaryStyle && secondaryStyle == rp.secondaryStyle && selectedAspects.size() == rp.selectedAspects.size()) {
return std::is_permutation(selectedAspects.begin(), selectedAspects.end(), rp.selectedAspects.begin());
}
return false;
}
RunePage::operator bool() const {
return primaryStyle != 0 && secondaryStyle != 0 && selectedAspects.size() == 9;
}
RunePage::operator QJsonObject() const {
QJsonObject obj;
obj.insert("primary", (int) primaryStyle);
obj.insert("secondary", (int) secondaryStyle);
QJsonArray aspects;
for(uint32_t aspect : selectedAspects) {
aspects.push_back((int) aspect);
}
obj.insert("aspects", aspects);
return obj;
}
RunePage::RunePage(const QJsonObject& obj) :
primaryStyle(getValue(obj, "primary", 0)),
secondaryStyle(getValue(obj, "secondary", 0))
{
if(obj.contains("aspects") && obj["aspects"].isArray() && obj["aspects"].toArray().size() == 9) {
selectedAspects.clear();
selectedAspects.reserve(9);
QJsonArray arr = obj["aspects"].toArray();
for(QJsonValueRef aspect : arr) {
if(aspect.isDouble()) {
selectedAspects.push_back(aspect.toDouble());
}
}
}
}
std::ostream& operator<<(std::ostream& str, const RunePage& rp) {
return str << "Primary: " << rp.primaryStyle << " Secondary: " << rp.secondaryStyle << " aspects: " << rp.selectedAspects.size();
}
QDebug operator<<(QDebug str, const RunePage& rp) {
return str << "Primary: " << rp.primaryStyle << " Secondary: " << rp.secondaryStyle << " aspects: " << rp.selectedAspects.size();
}

315
src/runepagelist.cpp Normal file
View File

@ -0,0 +1,315 @@
#include "runepagelist.h"
#include "ui_runepagelist.h"
#include <QDebug>
#include <QDropEvent>
#include <QMenu>
#include <QMimeData>
#include <QTextStream>
#include "clipboardpopup.h"
#include "loadingwindow.h"
#include "runeeditor.h"
RunePageList::RunePageList(QWidget* parent) : QListWidget(parent), ui(new Ui::RunePageList) {
ui->setupUi(this);
QObject::connect(this, &QListWidget::itemChanged, this, &RunePageList::itemChangedCallback);
QObject::connect(this, &QListWidget::customContextMenuRequested, this, &RunePageList::openContextMenu);
}
RunePageList::~RunePageList() {
delete this->ui;
}
void RunePageList::loadRunePages(const std::vector<ClientAPI::RunePage>& pages) {
clearItems();
for(const ClientAPI::RunePage& rp : pages) {
addRunepageItem(rp.name, rp.id, rp.runepage, rp.isCurrent);
}
}
void RunePageList::loadRunePages(const std::vector<std::shared_ptr<Config::RunePageConfig>>& pages) {
clearItems();
for(size_t i = 0; i < pages.size(); ++i) {
std::shared_ptr<Config::RunePageConfig> rp = pages.at(i);
addRunepageItem(rp->name, i, rp->runepage);
}
}
void RunePageList::setRuneInfos(const std::vector<RuneAspekt>& runeInfo, const std::vector<RuneStyle>& runeStyles) {
this->runeInfo = &runeInfo;
this->runeStyles = &runeStyles;
}
void RunePageList::dropEvent(QDropEvent* event) {
if(event->source() == nullptr || event->source() != other) {
event->ignore();
return;
}
auto selected = other->selectedItems();
if(selected.size() != 1) {
event->ignore();
return;
}
QListWidgetItem* item = selected.at(0);
// compare rune pages for duplicates?
// QListWidget::dropEvent(event);
// save change
QString name = item->text();
const RunePage* oldPage = (RunePage*) item->data(RolePointer).toULongLong();
emit runepageChanged(-1, name, *oldPage);
}
void RunePageList::itemChangedCallback(QListWidgetItem* item) {
int pageId = item->data(RunePageList::RoleId).toUInt();
QString newName = item->text();
const ::RunePage* page = (::RunePage*) item->data(RunePageList::RolePointer).toULongLong();
emit runepageChanged(pageId, newName, *page);
}
void RunePageList::openContextMenu(const QPoint& pos) {
QPoint globalPos = mapToGlobal(pos);
QMenu menu;
menu.addAction(QIcon(":/icons/edit.svg"), RunePageList::tr("Edit"), this, &RunePageList::editCurrentItem);
menu.addAction(QIcon(":/icons/duplicate.svg"), RunePageList::tr("Duplicate"), this, &RunePageList::duplicateCurrentItem);
menu.addAction(QIcon(":/icons/export.svg"), RunePageList::tr("Export"), this, &RunePageList::exportCurrentItem);
menu.addAction(QIcon(":/icons/import.svg"), RunePageList::tr("Import"), this, &RunePageList::importItem);
menu.addAction(QIcon(":/icons/delete.svg"), RunePageList::tr("Delete"), this, &RunePageList::deleteCurrentItem);
menu.exec(globalPos);
}
void RunePageList::deleteCurrentItem() {
QListWidgetItem* item = currentItem();
if (item) {
uint32_t id = item->data(RoleId).toUInt();
RunePage* page = (RunePage*) item->data(RolePointer).toULongLong();
removeItemWidget(item);
delete item;
delete page;
emit runepageDeleted(id);
}
}
void RunePageList::editCurrentItem() {
QListWidgetItem* item = currentItem();
if(!item) return;
RunePage* rp = (RunePage*) item->data(RolePointer).toULongLong();
const uint32_t id = item->data(RoleId).toUInt();
LoadingWindow lw;
lw.setText(RunePageList::tr("Loading runes"));
lw.setProgress(0.5f);
lw.show();
// make sure the Loading window is rendered
QApplication::processEvents();
RuneEditor re;
re.setName(item->text());
QApplication::processEvents();
re.setClient(*client);
QApplication::processEvents();
re.setRunepage(*rp);
lw.close();
int result = re.exec();
// check result - save
if(result == QDialog::Accepted) {
// update config
emit runepageChanged(id, re.getName(), re.getRunepage());
}
}
void RunePageList::duplicateCurrentItem() {
QListWidgetItem* item = currentItem();
if(!item) return;
const RunePage* rp = (RunePage*) item->data(RolePointer).toULongLong();
QString name = item->text();
static const QRegularExpression regex(".*(\\d)+$");
QRegularExpressionMatchIterator regexIt = regex.globalMatch(name);
int num = 0;
if(regexIt.hasNext()) {
QRegularExpressionMatch match = regexIt.next();
QStringRef ref = match.capturedRef(1);
name.chop(ref.size());
num = ref.toInt();
}
name += QString::number(num+1);
emit runepageChanged(-1, name, *rp);
}
void RunePageList::exportCurrentItem() {
QListWidgetItem* item = currentItem();
if(!item) return;
const RunePage* rp = (RunePage*) item->data(RolePointer).toULongLong();
Config::RunePageConfig rpc;
rpc.name = item->text();
rpc.runepage = *rp;
QJsonDocument rpcDoc(rpc); // cast to QJsonObject
QByteArray jsonBytes = rpcDoc.toJson(QJsonDocument::Compact).toBase64();
QString runePageString = QString::fromLocal8Bit(jsonBytes);
ClipboardPopup popup(ClipboardPopup::Direction::Copy);
popup.setText(runePageString);
popup.exec();
}
void RunePageList::importItem() {
ClipboardPopup popup(ClipboardPopup::Direction::Paste);
if(popup.exec() != QDialog::Accepted) {
return;
}
QString text = popup.getText();
QByteArray jsonBytes = QByteArray::fromBase64(text.toLocal8Bit());
QJsonDocument rpcDoc = QJsonDocument::fromJson(jsonBytes);
if(rpcDoc.isObject()) {
QJsonObject rpcJson = rpcDoc.object();
Config::RunePageConfig rpc = rpcJson; // implicit cast
if(rpc.name.isEmpty() || !rpc.runepage) {
// invalid
return;
}
emit runepageChanged(-1, rpc.name, rpc.runepage);
}
}
void RunePageList::clearItems() {
while(count()) {
QListWidgetItem* item = takeItem(0);
delete (RunePage*) item->data(RolePointer).toULongLong();
delete item;
}
clear();
}
void RunePageList::addRunepageItem(QString name, int id, const ::RunePage& rp, bool isCurrent) {
QListWidgetItem* item = new QListWidgetItem(name);
item->setData(RoleId, (uint) id);
item->setData(RolePointer, (qulonglong) new ::RunePage(rp));
const DataDragon::ChampData& champData = findChamp(name);
if(champData.key != -1) {
QPixmap iamge = dd->getImage(champData.id);
item->setIcon(iamge);
}
QString tooltipStr;
if(id != -1) {
tooltipStr = QString("id: %0\n").arg(id);
}
tooltipStr += getRuneDescription(rp);
item->setToolTip(tooltipStr);
item->setFlags(item->flags() | Qt::ItemIsEditable);
if(isCurrent) {
item->setSelected(true);
}
addItem(item);
}
const DataDragon::ChampData& RunePageList::findChamp(const QString& name) {
if(!dd) {
return DataDragon::EMPTYCHAMP;
}
// try direct
int count = 0;
const DataDragon::ChampData& directChampData = dd->getBestMatchingChamp(name, &count);
if(directChampData.key != -1) {
return directChampData;
}
// not specific
if(count > 1) {
return DataDragon::EMPTYCHAMP;
}
// try for substrings
static const QRegularExpression splittingRegex("\\W+");
QStringList list = name.split(splittingRegex, QString::SplitBehavior::SkipEmptyParts);
QSet<int> matchedIds;
const DataDragon::ChampData* lastMatched = nullptr;
for(const QString& entry : list) {
count = 0;
const DataDragon::ChampData& splitChampData = dd->getBestMatchingChamp(entry, &count);
if(count == 1) {
matchedIds.insert(splitChampData.key);
lastMatched = &splitChampData;
} else if(count > 1) {
// not specific
return DataDragon::EMPTYCHAMP;
}
}
if(lastMatched && matchedIds.size() == 1) {
return *lastMatched;
}
// not specific or not found
return DataDragon::EMPTYCHAMP;
}
QString RunePageList::getRuneDescription(const ::RunePage& runepage) {
QString outStr;
outStr.reserve(100);
QTextStream out(&outStr);
if(! (bool) runepage) {
return {};
}
out << getRuneStyleByID(runepage.primaryStyle) << ' ' << RunePageList::tr("with") << ' ' << getRuneStyleByID(runepage.secondaryStyle);
for(uint32_t rune : runepage.selectedAspects) {
out << '\n' << getRuneText(rune);
}
return outStr;
}
QString RunePageList::getRuneText(uint32_t id) {
if(runeInfo != nullptr) {
for(const RuneAspekt& ra : *runeInfo) {
if(ra.id == id) {
return ra.name;
}
}
}
return QString("(%0)").arg(id);
}
QString RunePageList::getRuneStyleByID(uint32_t id) {
if(runeStyles != nullptr) {
auto it = std::find_if(runeStyles->begin(), runeStyles->end(), [id](const RuneStyle& rs) { return rs.id == id; });
if(it != runeStyles->end()) {
return it->name;
}
}
return QString("(%0)").arg(id);
}

88
src/settingstab.cpp Normal file
View File

@ -0,0 +1,88 @@
#include "settingstab.h"
#include "ui_settingstab.h"
#include <algorithm>
#include <Log.h>
static std::vector<QString> toChampionNameList(const std::vector<StageSettings::SelectedChamp>& scs) {
std::vector<QString> out;
out.reserve(scs.size());
std::transform(scs.begin(), scs.end(), std::insert_iterator(out, out.begin()), [](const StageSettings::SelectedChamp& sc) { return sc.name; });
return out;
}
SettingsTab::SettingsTab(QWidget *parent) : QWidget(parent), ui(new Ui::SettingsTab) {
ui->setupUi(this);
}
SettingsTab::~SettingsTab() {
delete ui;
}
void SettingsTab::setup(Config::PositionConfig& conf, DataDragon* dd) {
this->conf = &conf;
this->dd = dd;
ui->banstage->setDataDragon(dd);
ui->pickstage->setDataDragon(dd);
// load config
ui->banstage->loadConfig(conf.ban);
ui->pickstage->loadConfig(conf.pick);
}
std::vector<StageSettings::SelectedChamp> SettingsTab::getChamps(LolAutoAccept::State s) const {
return getStage(s)->getChampions();
}
bool SettingsTab::getState(LolAutoAccept::State s) const {
auto stage = getStage(s);
return stage->getState();
}
void SettingsTab::setChamps(LolAutoAccept::State s, const std::vector<QString>& c) {
getStage(s)->setChampions(c);
}
void SettingsTab::setState(LolAutoAccept::State s, bool b) {
auto stage = getStage(s);
return stage->setState(b);
}
Position SettingsTab::getPosition() const {
return position;
}
void SettingsTab::banToggled(bool b) {
conf->ban.enabled = b;
emit toggled(position, LolAutoAccept::State::BAN, b);
}
void SettingsTab::banChampsChanged() {
conf->ban.champs = toChampionNameList(ui->banstage->getChampions());
emit changed(position, LolAutoAccept::State::BAN);
}
void SettingsTab::pickToggled(bool b) {
conf->pick.enabled = b;
emit toggled(position, LolAutoAccept::State::PICK, b);
}
void SettingsTab::pickChampsChanged() {
auto champs = ui->pickstage->getChampions();
auto champnames = toChampionNameList(champs);
conf->pick.champs.swap(champnames);
emit changed(position, LolAutoAccept::State::BAN);
}
StageSettings* SettingsTab::getStage(LolAutoAccept::State s) const {
switch(s) {
case LolAutoAccept::State::BAN: return ui->banstage;
case LolAutoAccept::State::PICK: return ui->pickstage;
default: break;
}
assert(false); // "invalid" stage (Lobby or Game)
return nullptr;
}

View File

@ -10,6 +10,10 @@
StageSettings::StageSettings(QWidget *parent) : QWidget(parent), ui(new Ui::StageSettings) { StageSettings::StageSettings(QWidget *parent) : QWidget(parent), ui(new Ui::StageSettings) {
ui->setupUi(this); ui->setupUi(this);
auto champsmodel = ui->championList->model();
QObject::connect(champsmodel, &QAbstractItemModel::rowsMoved, this, &StageSettings::moved);
} }
StageSettings::~StageSettings() { StageSettings::~StageSettings() {
@ -35,23 +39,23 @@ void StageSettings::setState(bool b) {
updateEnabled(); updateEnabled();
} }
StageSettings::SelectedChamp::SelectedChamp(std::string name, uint32_t id) : name(name), id(id) {} StageSettings::SelectedChamp::SelectedChamp(QString name, uint32_t id) : name(name), id(id) {}
std::vector<StageSettings::SelectedChamp> StageSettings::getChampions() const { std::vector<StageSettings::SelectedChamp> StageSettings::getChampions() const {
std::vector<SelectedChamp> out; std::vector<SelectedChamp> out;
out.reserve(ui->championList->count()); out.reserve(ui->championList->count());
for(int32_t i = 0; i < ui->championList->count(); ++i) { for(int32_t i = 0; i < ui->championList->count(); ++i) {
ChampRow* cr = (ChampRow*) ui->championList->item(i); ChampRow* cr = (ChampRow*) ui->championList->item(i);
out.emplace_back(cr->getChamp().toStdString(), cr->getChampID()); out.emplace_back(cr->getChamp(), cr->getChampID());
} }
return out; return out;
} }
void StageSettings::setChampions(const std::vector<std::string>& champs) { void StageSettings::setChampions(const std::vector<QString>& champs) {
clear(); clear();
// add new champs // add new champs
for(const std::string& champ : champs) { for(const QString& champ : champs) {
resolveAndAddChamp(champ, champ == champs.back()); resolveAndAddChamp(champ, champ == champs.back());
} }
} }
@ -60,11 +64,16 @@ void StageSettings::setDataDragon(DataDragon* dd_) {
dd = dd_; dd = dd_;
} }
void StageSettings::addChamp(const std::string& champname, uint32_t id, QPixmap icon) { void StageSettings::addChamp(const DataDragon::ChampData& cd, QPixmap icon) {
auto champr = new ChampRow(); auto champr = new ChampRow();
ui->championList->addItem(champr); ui->championList->addItem(champr);
champr->setChamp(champname, id, icon); champr->setChamp(cd, icon);
}
void StageSettings::loadConfig(Config::StageConfig& c) {
setChampions(c.champs);
setState(c.enabled);
} }
void StageSettings::toggledinternal(int state) { void StageSettings::toggledinternal(int state) {
@ -78,7 +87,7 @@ void StageSettings::addChamp() {
// popup with champion search // popup with champion search
ChampionSearch cs(dd, this); ChampionSearch cs(dd, this);
int res = cs.exec(); int res = cs.exec();
Log::info << "championsearch result: " << res; qInfo() << "championsearch result: " << res;
if(res == QDialog::Accepted) { if(res == QDialog::Accepted) {
auto cr = cs.getSearchResult(); auto cr = cs.getSearchResult();
if(cr) { if(cr) {
@ -101,19 +110,19 @@ void StageSettings::moved() {
emit championsChanged(); emit championsChanged();
} }
void StageSettings::resolveAndAddChamp(const std::string& name, bool emitchange) { void StageSettings::resolveAndAddChamp(const QString& name, bool emitchange) {
if(dd) { if(dd) {
int count = 0; int count = 0;
auto cd = dd->getBestMatchingChamp(name, &count); auto cd = dd->getBestMatchingChamp(name, &count);
dd->getImageAsnyc(cd.id, [this, name, cd, emitchange](QPixmap img) { dd->getImageAsnyc(cd.id, [this, name, cd, emitchange](QPixmap img) {
addChamp(name, cd.key, img); addChamp(cd, img);
if(emitchange) { if(emitchange) {
emit championsChanged(); emit championsChanged();
} }
}); });
} else { } else {
Log::error << __PRETTY_FUNCTION__ << " Datadragon not set!"; qCritical() << __PRETTY_FUNCTION__ << " Datadragon not set!";
} }
} }

3
src/x11helper.cpp Normal file
View File

@ -0,0 +1,3 @@
#include "x11helper.h"
const Window X11Helper::InvalidWindow = 0;

25
src/x11helper_other.cpp Normal file
View File

@ -0,0 +1,25 @@
#include "x11helper.h"
#include <X11/Xlib.h>
const bool X11Helper::IsSupported = false;
X11Helper::X11Helper(QObject *parent) : QObject(parent) {
}
X11Helper::~X11Helper() {}
Window X11Helper::findWindow(const QString&, float) {
return InvalidWindow;
}
Window X11Helper::searchWindows(Window, const QString&, float) {
return InvalidWindow;
}
void X11Helper::map(Window win) {}
void X11Helper::unmap(Window win) {}
void X11Helper::setMap(Window win, bool b) {}

77
src/x11helper_x11.cpp Normal file
View File

@ -0,0 +1,77 @@
#include "x11helper.h"
#include <X11/Xlib.h>
const bool X11Helper::IsSupported = true;
X11Helper::X11Helper(QObject *parent) : QObject(parent), disp(XOpenDisplay(nullptr)) {
}
X11Helper::~X11Helper() {
XCloseDisplay(this->disp);
}
Window X11Helper::findWindow(const QString& name, float aspektRatio) {
return searchWindows(DefaultRootWindow(disp), name, aspektRatio);
}
Window X11Helper::searchWindows(Window top, const QString& search, float aspektRatio) {
{
char* window_name;
if (XFetchName(disp, top, &window_name)) {
QString winName(window_name);
XFree(window_name);
if (search == winName) {
if(aspektRatio == 0.0) {
return top; // dont look for kids
}
XWindowAttributes attribs;
if(XGetWindowAttributes(disp, top, &attribs)) {
const float winAspektRation = attribs.width / (float) attribs.height;
if(qAbs(winAspektRation - aspektRatio) < aspektRatio/10.0) {
return top;
}
}
}
}
}
Window* children = nullptr;
Window dummy;
unsigned int nchildren;
if (!XQueryTree(disp, top, &dummy, &dummy, &children, &nchildren)) {
return InvalidWindow;
}
for (unsigned int i = 0; i < nchildren; i++) {
Window res = searchWindows(children[i], search, aspektRatio);
if(res != InvalidWindow) {
XFree((char*) children);
return res;
}
}
if (children) {
XFree((char*) children);
}
return InvalidWindow;
}
void X11Helper::map(Window win) {
XMapRaised(disp, win);
}
void X11Helper::unmap(Window win) {
XUnmapWindow(disp, win);
}
void X11Helper::setMap(Window win, bool b) {
if(b) {
map(win);
} else {
unmap(win);
}
}

2
thirdparty/Log vendored

@ -1 +1 @@
Subproject commit 96d10d6e72ddaffd8b688db5ef2705e38aff3a75 Subproject commit 9a43d50969ecc34d9a3bc8944bc2182e55f7a712

View File

@ -1,113 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="de_DE">
<context>
<name>ChampionSearch</name>
<message>
<location filename="../ui/championsearch.ui" line="14"/>
<source>Champion Search</source>
<translation>Champion Suche</translation>
</message>
<message>
<location filename="../ui/championsearch.ui" line="20"/>
<source>Champion:</source>
<translation>Champion:</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../ui/mainwindow.ui" line="20"/>
<source>LoL-Auto-Accept</source>
<translation>LoL-Auto-Accept</translation>
</message>
<message>
<location filename="../ui/mainwindow.ui" line="45"/>
<source>Mainswitch</source>
<translation>Hautpschalter</translation>
</message>
<message>
<location filename="../ui/mainwindow.ui" line="52"/>
<source>Enable LoL-Auto-Accept</source>
<translation>Spiel automatisch annehmen</translation>
</message>
<message>
<location filename="../ui/mainwindow.ui" line="74"/>
<source>Pre Pick</source>
<translation>Pre Pick</translation>
</message>
<message>
<location filename="../ui/mainwindow.ui" line="118"/>
<source>Pick</source>
<translation>Pick</translation>
</message>
<message>
<source>PrePick</source>
<translation type="vanished">Pre Pick</translation>
</message>
<message>
<source>Enable PrePick</source>
<translation type="vanished">Aktivire Pre Pick</translation>
</message>
<message>
<source>Champion:</source>
<translation type="vanished">Champion:</translation>
</message>
<message>
<location filename="../ui/mainwindow.ui" line="96"/>
<source>Ban</source>
<translation>Bannen</translation>
</message>
<message>
<location filename="../src/mainwindow.cpp" line="53"/>
<source>League of Legends Client not found!</source>
<translation>League of Legends Client nicht gefunden!</translation>
</message>
<message>
<location filename="../src/mainwindow.cpp" line="59"/>
<source>Auto-Acceptor started!</source>
<translation></translation>
</message>
<message>
<location filename="../src/mainwindow.cpp" line="62"/>
<source>Auto-Acceptor stoped!</source>
<translation>Auto Acceptor gestoppt!</translation>
</message>
</context>
<context>
<name>StageSettings</name>
<message>
<source>Champion:</source>
<translation type="vanished">Champion:</translation>
</message>
<message>
<location filename="../src/stagesettings.cpp" line="25"/>
<source>Enable %1</source>
<translation>Aktiviere %1</translation>
</message>
<message>
<source>Champions matched: %1</source>
<translation type="vanished">Übereinstimmende Champions: %1</translation>
</message>
<message>
<source>Champion: %1
Type: %2
Title: %3
ID: %4</source>
<translation type="vanished">Champion: %1
Typ: %2
Titel: %3
ID: %4</translation>
</message>
<message>
<location filename="../ui/stagesettings.ui" line="74"/>
<source>Add Champion</source>
<translation>Champion hinzufügen</translation>
</message>
<message>
<location filename="../ui/stagesettings.ui" line="87"/>
<source>Remove Champion</source>
<translation>Champion entfernen</translation>
</message>
</context>
</TS>

113
ts/en.ts
View File

@ -1,113 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="en_US">
<context>
<name>ChampionSearch</name>
<message>
<location filename="../ui/championsearch.ui" line="14"/>
<source>Champion Search</source>
<translation>Champion Search</translation>
</message>
<message>
<location filename="../ui/championsearch.ui" line="20"/>
<source>Champion:</source>
<translation>Champion:</translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../ui/mainwindow.ui" line="20"/>
<source>LoL-Auto-Accept</source>
<translation>LoL-Auto-Accept</translation>
</message>
<message>
<location filename="../ui/mainwindow.ui" line="45"/>
<source>Mainswitch</source>
<translation>Mainswitch</translation>
</message>
<message>
<location filename="../ui/mainwindow.ui" line="52"/>
<source>Enable LoL-Auto-Accept</source>
<translation>Automatically accept game</translation>
</message>
<message>
<location filename="../ui/mainwindow.ui" line="74"/>
<source>Pre Pick</source>
<translation>Pre Pick</translation>
</message>
<message>
<location filename="../ui/mainwindow.ui" line="118"/>
<source>Pick</source>
<translation>Pick</translation>
</message>
<message>
<source>PrePick</source>
<translation type="vanished">Pre Pick</translation>
</message>
<message>
<source>Enable PrePick</source>
<translation type="vanished">Enable Pre Pick</translation>
</message>
<message>
<source>Champion:</source>
<translation type="vanished">Champion:</translation>
</message>
<message>
<location filename="../ui/mainwindow.ui" line="96"/>
<source>Ban</source>
<translation>Ban</translation>
</message>
<message>
<location filename="../src/mainwindow.cpp" line="53"/>
<source>League of Legends Client not found!</source>
<translation>League of Legends Client not found!</translation>
</message>
<message>
<location filename="../src/mainwindow.cpp" line="59"/>
<source>Auto-Acceptor started!</source>
<translation>Auto-Acceptor started!</translation>
</message>
<message>
<location filename="../src/mainwindow.cpp" line="62"/>
<source>Auto-Acceptor stoped!</source>
<translation>Auto-Acceptor stopped!</translation>
</message>
</context>
<context>
<name>StageSettings</name>
<message>
<source>Champion:</source>
<translation type="vanished">Champion:</translation>
</message>
<message>
<location filename="../src/stagesettings.cpp" line="25"/>
<source>Enable %1</source>
<translation>Enable %1</translation>
</message>
<message>
<source>Champions matched: %1</source>
<translation type="vanished">Champions matched: %1</translation>
</message>
<message>
<source>Champion: %1
Type: %2
Title: %3
ID: %4</source>
<translation type="vanished">Champion: %1
Type: %2
Title: %3
ID: %4</translation>
</message>
<message>
<location filename="../ui/stagesettings.ui" line="74"/>
<source>Add Champion</source>
<translation>Add Champion</translation>
</message>
<message>
<location filename="../ui/stagesettings.ui" line="87"/>
<source>Remove Champion</source>
<translation>Remove Champion</translation>
</message>
</context>
</TS>

Some files were not shown because too many files have changed in this diff Show More