Compare commits

...

126 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
mrbesen d918a936f4
fix qmake file 2022-07-03 17:07:27 +02:00
mrbesen 2b4ce87830
updatelang and fix typo 2022-07-03 17:07:17 +02:00
mrbesen 1f4f4b52bc
layouting problems fixed 2022-07-03 16:44:07 +02:00
mrbesen 99858e7fae
updated translations 2022-07-03 15:32:14 +02:00
mrbesen 4feb5dc7ae
dnt ban ally champions 2022-07-03 14:50:43 +02:00
mrbesen 9e95bd6562
picking and banning of multiple champions working 2022-07-03 12:01:51 +02:00
mrbesen 4de7c57a46
store and restore settings 2022-07-03 00:31:13 +02:00
mrbesen f35b8d6719
champion search and ordering basic ui working 2022-07-02 22:25:20 +02:00
mrbesen ffb980e672
new ui wip 2022-07-02 17:44:29 +02:00
mrbesen 9f1449d7f5
removed XInputSimulator 2022-07-02 17:41:02 +02:00
mrbesen 5ac909e53a
pick and ban working 2022-07-02 13:21:09 +02:00
mrbesen 9e0b15cff2
prepick working 2022-07-02 12:36:38 +02:00
mrbesen 69cc172f12
getSelf 2022-07-01 17:35:05 +02:00
mrbesen 500b12f756
step to REST api 2022-06-29 23:09:01 +02:00
mrbesen 07ee1df44e
restclient improved 2022-06-27 23:18:27 +02:00
mrbesen 00f6cc4a7d
clientapi basics 2022-06-27 20:45:01 +02:00
mrbesen c1e92c1706
setWindowIcon 2022-05-21 00:29:36 +02:00
mrbesen 2b9010334c
added icon 2022-05-21 00:12:52 +02:00
MrBesen 09a50682f9
updated readme 2022-05-14 18:37:39 +02:00
mrbesen cc07bd74de
update readme 2022-05-14 17:55:49 +02:00
126 changed files with 7739 additions and 1759 deletions

8
.gitignore vendored
View File

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

5
.gitmodules vendored
View File

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

2
.vscode/launch.json vendored
View File

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

View File

@ -1,20 +1,40 @@
# lolautoaccept
This is a tool, that tries to automatically accept a league of legends game.
It is developed on linux mint and there is no effort to port it to other distributions or even windows.
It works by taking a screenshot every second, analysing it using opencv and clicking on accept using XInputSimulator.
It is developed on linux and there is no effort to port it to windows.
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/).
I only tested this in normals and ranked (No Aram, Custom, URF, ....) but you may want to give it a try.
### Dependencies
- opencv4
- [XInputSimulator](https://github.com/a3f/XInputSimulator.git)
## Prebuilt Binary
There is a prebuild AppImage that should work on every linux x86_64 computer.
1. Download: [here](https://git.mrbesen.de/MrBesen/lolautoaccept/releases)
2. give execute rights `chmod +x LoLAutoAccept-*-x86_64.AppImage`
3. execute - dubble click or `./LoLAutoAccept-*-x86_64.AppImage`
## Dependencies
- Qt5
- libcurl
- [Log](https://git.okaestne.de/okaestne/Log) (is a submodule)
### Running
just execute it.
The launcher should be in german (ignoring the text will be implemented soon).
## Compile
Be sure to clone with submodules:
```
git clone --recurse-submodules https://git.mrbesen.de/MrBesen/lolautoaccept.git
```
Then in its root folder:
```
qmake
The program has troubles detecting the button, when the mouse is hovering it.
# build the appimage
make -j appimg
The launcher should be in 1280x720 (this will be worked on soon).
# or just the main binary
make -j
```
You can get better Logging with:
```
./lolautoaccept --debug-log
```
(This will also create a `log.txt`)

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 {
int debugLog = 0; // cast to bool later
int access = 0;
};
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
};

32
include/championsearch.h Normal file
View File

@ -0,0 +1,32 @@
#ifndef CHAMPIONSEARCH_H
#define CHAMPIONSEARCH_H
#include <QDialog>
#include "datadragon.h"
#include "champrow.h"
namespace Ui {
class ChampionSearch;
}
class ChampionSearch : public QDialog {
Q_OBJECT
public:
explicit ChampionSearch(DataDragon* dd, QWidget *parent = nullptr);
~ChampionSearch();
// does not return the same result on multiple calls! awsner needs to be deleted
ChampRow* getSearchResult();
private slots:
void searchChanged(QString);
private:
void clear();
Ui::ChampionSearch *ui;
DataDragon* dd;
};
#endif // CHAMPIONSEARCH_H

20
include/champrow.h Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <QListWidgetItem>
#include "datadragon.h"
class ChampRow : public QListWidgetItem {
public:
explicit ChampRow(QListWidget *parent = nullptr);
~ChampRow();
void setChamp(const DataDragon::ChampData& cd, QPixmap icon);
QString getChamp() const;
uint32_t getChampID() const;
QPixmap getIcon();
private:
uint32_t champid = -1;
QPixmap icon;
};

26
include/clientaccess.h Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <istream>
#include <memory>
#include <QString>
class ClientAccess {
ClientAccess();
public:
ClientAccess(const QString& token, uint16_t port);
static std::shared_ptr<ClientAccess> find();
public:
QString getBasicAuth() const;
uint16_t getPort() const;
QString getURL() const;
private:
QString authcode;
uint16_t port = 0;
};
std::shared_ptr<ClientAccess> createFromLockfile(std::istream& lockfile);

240
include/clientapi.h Normal file
View File

@ -0,0 +1,240 @@
#pragma once
#include "clientaccess.h"
#include "datadragonimagecache.h"
#include "memoryimagecache.h"
#include "position.h"
#include "restclient.h"
#include "runeaspekt.h"
#include "runepage.h"
#include "runestyle.h"
class ClientAPI : public RestClient {
Q_OBJECT
public:
enum class ReadyCheckState : uint32_t {
INVALID = 0,
NONE,
INPROGRESS,
Accepted,
DECLINED
};
static ReadyCheckState toReadyCheckState(const QJsonObject& json);
enum class GameflowPhase : uint32_t {
NONE = 0,
LOBBY,
MATCHMAKING,
CHECKEDINTOTOURNAMENT,
READYCHECK,
CHAMPSELECT,
GAMESTART,
FAILEDTOLAUNCH,
INPROGRESS,
RECONNECT,
WAITINGFORSTATS,
PREENDOFGAME,
ENDOFGAME,
TERMINATEDINERROR,
};
static GameflowPhase toGameflowPhase(const QString&);
enum class ChampSelectPhase : uint32_t {
INVALID = 0,
GAME_STARTING,
PLANNING,
BAN_PICK,
FINALIZATION
};
static ChampSelectPhase toChampSelectPhase(const QString& str);
enum class ChampSelectActionType : uint32_t {
INVALID = 0,
BAN,
PICK,
TEN_BANS_REVEAL,
};
static ChampSelectActionType toChampSelectActionType(const QString& str);
struct TimerInfo {
int64_t adjustedTimeLeftInPhase = 0;
int64_t internalNowInEpochMs = 0;
bool isefinite = false;
ChampSelectPhase phase = ChampSelectPhase::INVALID;
int64_t totalTimeInPhase = 0;
bool valid = false;
TimerInfo();
explicit TimerInfo(const QJsonObject& json);
};
struct PlayerInfo {
int64_t summonerid = 0; // to test validity -> test if this is not null
QString gameName;
QString name;
QString statusMessage;
// lol specific
QString puuid;
uint32_t level = 0;
};
struct ChampSelectAction {
int32_t actorCellID = -1;
int32_t championID = 0;
int32_t id = 0;
bool completed = false;
bool isAllyAction = true;
bool isInProgress = false;
ChampSelectActionType type = ChampSelectActionType::INVALID;
ChampSelectAction();
explicit ChampSelectAction(const QJsonObject& json);
};
struct ChampSelectCell {
Position position = Position::INVALID;
int32_t cellID = 0;
int32_t championID = 0;
int32_t championPickIntentID = 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();
explicit ChampSelectCell(const QJsonObject& json);
};
static std::vector<ChampSelectCell> loadAllInfos(const QJsonArray& arr);
struct ChampSelectSession {
std::vector<ChampSelectAction> actions;
int32_t counter = 0; // ??
int64_t gameid = 0;
bool allowDuplicatePick = false;
bool allowRerolling = false;
bool allowSkinSelection = true;
bool hasSimultaneousBans = true;
bool hasSimultaneousPicks = false;
bool isCustomGame = false;
bool isSpectating = false;
bool skipChampionSelect = false;
int32_t rerollsRemaining = 0;
int32_t localPlayerCellId = 0;
std::vector<ChampSelectCell> myTeam;
std::vector<ChampSelectCell> theirTeam;
TimerInfo timer;
// std::vector<> trades; // TODO
ChampSelectSession();
explicit ChampSelectSession(const QJsonObject& json);
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();
ReadyCheckState getReadyCheckState();
GameflowPhase getGameflowPhase();
void acceptMatch();
void declineMatch();
ChampSelectSession getChampSelectSession();
bool setChampSelectAction(int32_t actionid, int32_t champid, bool completed);
PlayerInfo getSelf();
void dodge();
std::vector<int32_t> getBannableChampIDs();
std::vector<int32_t> getPickableChampIDs();
TimerInfo getTimerInfo();
// 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:
ClientAccess access;
MemoryImageCache memImageCache;
DataDragonImageCache imageCache;
};
#define DEFINEOPERATOR(CLASS) \
std::ostream& operator<<(std::ostream&, const ClientAPI::CLASS&); \
QDebug operator<<(QDebug, const ClientAPI::CLASS&);
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
#include <memory>
#include <QJsonObject>
#include "position.h"
#include "runepage.h"
class Config {
public:
struct StageConfig {
@ -9,8 +13,38 @@ public:
StageConfig(const QJsonObject&);
operator QJsonObject() const;
std::string champ;
bool enabled;
std::vector<QString> champs;
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 {
@ -18,10 +52,15 @@ public:
RootConfig(const QJsonObject&);
operator QJsonObject() const;
StageConfig prepick;
StageConfig ban;
StageConfig pick;
std::shared_ptr<Config::PositionConfig> getPositionConfig(Position position);
std::vector<std::shared_ptr<PositionConfig>> positionConfigs;
GeneralRunePageConfig runepagesConfig;
bool enabledAutoAccept;
bool enabledSmiteWarn;
bool enabledAutoWrite;
QString autoWriteText;
};
Config();
@ -32,7 +71,7 @@ public:
RootConfig& getConfig();
private:
std::string configFolderPath;
std::string configFilePath;
QString configFolderPath;
QString configFilePath;
RootConfig root;
};

View File

@ -3,21 +3,26 @@
#include <condition_variable>
#include <functional>
#include <mutex>
#include <string>
#include <thread>
#include <set>
#include <QString>
#include <vector>
#include <curl/curl.h>
#include <QJsonDocument>
#include <opencv2/opencv.hpp>
#include <QPixmap>
#include "datadragonimagecache.h"
#include "champcache.h"
#include "memoryimagecache.h"
#include "restclient.h"
class QThread;
class DataDragon : public RestClient {
Q_OBJECT
class DataDragon {
public:
using notifyImgfunc_t = std::function<void(cv::Mat)>;
using notifyImgfunc_t = std::function<void(QPixmap)>;
DataDragon(const std::string& locale);
DataDragon(const QString& locale);
~DataDragon();
DataDragon(const DataDragon&) = delete;
DataDragon& operator=(const DataDragon&) = delete;
@ -27,11 +32,11 @@ public:
ChampData();
ChampData(const QJsonObject& source);
std::string name;
std::string id;
int key;
std::string partype;
std::string title;
QString name;
QString id;
int key = 0;
QString partype;
QString title;
};
enum class ImageType {
@ -41,52 +46,68 @@ public:
};
// might block until version is available
const std::string& getVersion();
const QString& getVersion();
// might block until champ data is available
const std::vector<ChampData>& getChamps();
// might block until image is downloaded
cv::Mat getImage(const std::string& champid, ImageType imgtype = ImageType::SQUARE);
void getImageAsnyc(const std::string& champid, notifyImgfunc_t func, ImageType imgtype = ImageType::SQUARE);
QPixmap getImage(const QString& champid, ImageType imgtype = ImageType::SQUARE, bool writeMemcache = true);
void getImageAsnyc(const QString& champid, notifyImgfunc_t func, ImageType imgtype = ImageType::SQUARE);
// 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 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;
protected:
std::string getImageUrl(const std::string& champid, ImageType type);
std::string getCDNString() const;
QByteArray requestRaw(const std::string& url);
QJsonDocument request(const std::string& url);
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 getChampsInternal();
void startThread();
void stopThread();
void stopAndJoinThread();
void threadLoop();
std::string locale;
std::string version;
QString locale;
QString version;
std::vector<ChampData> champs;
std::mutex cachedatamutex;
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:
struct Task {
std::string champid;
QString champid;
notifyImgfunc_t func;
ImageType type;
};
CURL* curl = nullptr; // the curl (does curling)
DataDragonImageCache cache[3];
ChampCache champCache;
MemoryImageCache memcache;
std::list<Task> tasks;
std::mutex tasksmutex;
std::condition_variable tasksnotemptycv;
std::thread bgthread;
QThread* bgthread;
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
#include <string>
#include <QString>
#include <QByteArray>
#include <opencv2/opencv.hpp>
#include <QPixmap>
class DataDragonImageCache {
public:
DataDragonImageCache(const std::string& folderextra, const std::string& imageext = ".jpg");
DataDragonImageCache(const QString& folderextra, const QString& imageext = ".jpg");
~DataDragonImageCache();
cv::Mat getImage(const std::string& name);
void addImageRaw(const QByteArray& arr, const std::string& name);
bool hasImage(const QString& name);
QPixmap getImage(const QString& name);
void addImageRaw(const QByteArray& arr, const QString& name);
private:
std::string getFilepath(const std::string& name) const;
QString getFilepath(const QString& name) const;
std::string cacheDir;
std::string imageext; // file extention including dot
QString cacheDir;
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

@ -1,21 +0,0 @@
#include "screen.h"
class FakeScreen : public ScreenShot {
private:
int xoffset = 0;
int yoffset = 0;
int xsize = 0;
int ysize = 0;
public:
FakeScreen(int xoffset = 0, int yoffset = 0, int xsize = 0, int ysize = 0);
~FakeScreen();
virtual void take(cv::Mat& cvimg) override;
virtual void operator() (cv::Mat& cvimg) override;
virtual int getXOffset() const override;
virtual int getYOffset() const override;
virtual double getXScale() const override;
virtual double getYScale() const override;
};

View File

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

View File

@ -13,11 +13,23 @@ template<>
int convert(const QJsonValue& val);
template<>
std::string convert(const QJsonValue& val);
uint32_t convert(const QJsonValue& val);
template<>
int64_t convert(const QJsonValue& val);
template<>
uint64_t convert(const QJsonValue& val);
template<>
QString convert(const QJsonValue& val);
template<>
bool convert(const QJsonValue& val);
template<>
QString convert(const QJsonValue& val);
template<typename T>
T getValue(const QJsonObject& obj, const char* key, const T& def = {}) {
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

@ -1,87 +1,126 @@
#pragma once
#include "scaleableinputs.h"
#include "screen.h"
#include "matcher.h"
#include <xinputsimulator.h>
#include <thread>
#include <memory>
#include <mutex>
#include <QObject>
#include "blitzapi.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:
struct Stage {
Stage(const std::string& matchertmpl);
Stage();
virtual ~Stage();
Matcher matcher;
std::string champ; // not every stage has a champ
std::vector<uint32_t> champids; // not every stage has a champ
bool enabled = false;
virtual void action(LolAutoAccept& lolaa) = 0;
virtual bool process(LolAutoAccept& lolaa, cv::Mat& img);
uint32_t currentOffset = 0;
};
struct CooldownStage : public Stage {
CooldownStage(const std::string& matchertmpl);
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
time_t lastused = 0;
uint32_t cooldown = 30; // how long until this stage can be used again (seconds)
virtual bool process(LolAutoAccept& lolaa, cv::Mat& img) override;
};
struct AcceptStage : public Stage {
AcceptStage();
virtual void action(LolAutoAccept& lolaa) override;
};
struct PrePickStage : public CooldownStage {
PrePickStage();
virtual void action(LolAutoAccept& lolaa) override;
};
struct BanStage : public CooldownStage {
BanStage();
virtual void action(LolAutoAccept& lolaa) override;
};
struct PickStage : public CooldownStage {
PickStage();
virtual void action(LolAutoAccept& lolaa) override;
};
ScreenShot* screen = nullptr;
std::vector<Stage*> stages;
XInputSimulator& sim;
ScaleableInputs inputs;
Config::RootConfig& config;
DataDragon& dd;
bool shouldrun = false;
std::thread lolaathread;
void performClick(uint32_t nr, bool movemouseaway = true);
void enterSearch(const std::string& text);
void pickFirst(const std::string& search);
std::shared_ptr<ClientAPI> clientapi;
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:
enum class State {
LOBBY = 0,
PREPICK = 1,
BAN = 2,
PICK = 3,
GAME = 4
BAN = 1,
PICK = 2,
};
LolAutoAccept();
~LolAutoAccept();
enum class Status {
Off,
Running,
Failed
};
Q_ENUM(Status)
void setChamp(const std::string& champ, State s);
LolAutoAccept(Config::RootConfig& config, DataDragon& dd, QObject* parent = nullptr);
virtual ~LolAutoAccept();
void setChamps(const std::vector<uint32_t>& champs, State s);
void setEnabled(bool b, State s);
void setSmiteWarn(bool b);
bool init(); // returns true on success
void run();
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:
void stopJoinThread();
void innerRun();
};
void resetPickOffsets();
void resetAllOffsets();
void applyConfigToStage(Stage& stage, const Config::StageConfig& stageconf);
void loadPosition(Position pos);
uint32_t getChampOfState(State s);
void nextChampOfState(State s);
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>;
ownactions_t getOwnActions(int32_t cellid, const std::vector<ClientAPI::ChampSelectAction> actions);
void prepickPhase(const ownactions_t& ownactions);
void banPhase(const ownactions_t& ownactions, const ClientAPI::ChampSelectSession& session);
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 = {});
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 smiteWarning(const std::vector<ClientAPI::ChampSelectCell>& cells);
const QString& getChatid();
};
Q_DECLARE_METATYPE(LolAutoAccept::Status)

View File

@ -12,35 +12,61 @@ QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class QMessageBox;
class QTimer;
class LoadingWindow;
class X11Helper;
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(LolAutoAccept& lolaa, QWidget *parent = nullptr);
MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
virtual void closeEvent(QCloseEvent* event) override;
signals:
void requestTabChange(int tabindex);
public slots:
void resetSaveTimer();
private slots:
void loadingStatus(float);
void toggleLeagueVisibility();
void toggleMainswitch(bool);
void aatoggled(bool);
void pptoggled(bool);
void ppedited(const QString&);
void bantoggled(bool);
void banedited(const QString&);
void picktoggled(bool);
void pickedited(const QString&);
void smitewarntoggled(bool);
void tabtoggled(Position, LolAutoAccept::State, bool);
void tabchanged(Position, LolAutoAccept::State);
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:
// returns empty string on no match
const DataDragon::ChampData& getBestMatchingChamp(const std::string& name);
bool loading;
Ui::MainWindow *ui;
LolAutoAccept& lolaa;
QTimer* saveTimer;
std::thread lolaathread;
DataDragon dd;
Config conf;
LolAutoAccept lolaa;
LoadingWindow* lwin;
QMessageBox* dodgeQuestion;
X11Helper* x11Helper;
};
#endif // MAINWINDOW_H

View File

@ -1,41 +0,0 @@
#pragma once
#include <opencv2/opencv.hpp>
class Matcher {
private:
cv::Mat templ;
cv::Mat mask;
int32_t posx = -1;
int32_t posy = -1;
static std::string pathbase;
public:
static void setPathBase(const std::string& pa);
Matcher(const std::string& filename);
Matcher(const cv::Mat& templ);
~Matcher();
// instead of searching for a match, just look at this position
void setOffset(int32_t x, int32_t y);
struct Match {
bool doesMatch = false;
int x = 0, y = 0;
int width = 0;
int height = 0;
bool operator==(const Match&) const;
bool operator!=(const Match&) const;
};
Match match(const cv::Mat& img);
private:
Match matchAll(const cv::Mat& img);
Match matchPos(const cv::Mat& img);
// when the template has a alpha channel try to create a mask from that
void maskFromTemplate();
};

View File

@ -1,27 +1,27 @@
#pragma once
#include <functional>
#include <string>
#include <opencv2/opencv.hpp>
#include <QString>
#include <QPixmap>
class MemoryImageCache {
public:
MemoryImageCache(size_t maxsize = 25);
void addImage(cv::Mat, const std::string& title, int type);
cv::Mat getImage(const std::string& title, int type);
void addImage(QPixmap, const QString& title, int type);
QPixmap getImage(const QString& title, int type);
private:
void cleanUp();
struct CachedImage {
time_t lastaccessed = 0;
cv::Mat imageref;
std::string title;
QPixmap imageref;
QString title;
int type;
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;
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)

54
include/restclient.h Normal file
View File

@ -0,0 +1,54 @@
#pragma once
#include <curl/curl.h>
#include <QObject>
#include <QJsonDocument>
#include <QString>
#ifdef Q_OS_WIN
#undef DELETE
#endif
class RestClient : public QObject {
Q_OBJECT
public:
RestClient(const QString& base);
RestClient(const RestClient&) = delete;
virtual ~RestClient();
enum class Method {
GET,
POST,
PUT,
PATCH,
DELETE
};
struct WebException {
CURLcode curlresponse = CURLE_OK;
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)
QString basicauth; // basic auth code (user:pw) or empty string to disable
#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;
};

View File

@ -1,89 +0,0 @@
#pragma once
#include <opencv2/opencv.hpp>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/extensions/XShm.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#undef Bool
#undef CursorShape
#undef None
#undef KeyPress
#undef KeyRelease
#undef ButtonPress
#undef ButtonRelease
#undef MotionNotify
#undef EnterNotify
#undef LeaveNotify
#undef FocusIn
#undef FocusOut
#undef KeymapNotify
#undef Expose
#undef GraphicsExpose
#undef NoExpose
#undef VisibilityNotify
#undef CreateNotify
#undef DestroyNotify
#undef UnmapNotify
#undef MapNotify
#undef MapRequest
#undef ReparentNotify
#undef ConfigureNotify
#undef ConfigureRequest
#undef GravityNotify
#undef ResizeRequest
#undef CirculateNotify
#undef CirculateRequest
#undef PropertyNotify
#undef SelectionClear
#undef SelectionRequest
#undef SelectionNotify
#undef ColormapNotify
#undef ClientMessage
#undef MappingNotify
#undef GenericEvent
#undef LASTEvent
#undef FontChange
class ScreenShot {
private:
Display* display = nullptr;
Window window;
XWindowAttributes wattrib;
XImage* ximg = nullptr;
XShmSegmentInfo shminfo;
bool init;
bool closeDisp = true;
bool valid = false;
std::string windowname;
bool updateAttrib();
bool initImg();
public:
static std::vector<ScreenShot*> getWindows(const std::string& name);
static const uint32_t DEFAULTWIDTH;
static const uint32_t DEFAULTHEIGHT;
ScreenShot();
ScreenShot(Display* d, Window w);
virtual ~ScreenShot();
virtual void take(cv::Mat& cvimg);
virtual void operator() (cv::Mat& cvimg);
constexpr operator bool () const {
return valid;
}
virtual int getXOffset() const;
virtual int getYOffset() const;
virtual double getXScale() const;
virtual double getYScale() 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 "config.h"
#include "datadragon.h"
namespace Ui {
@ -14,7 +15,6 @@ class StageSettings : public QWidget {
Q_PROPERTY(QString name READ getName WRITE setName)
Q_PROPERTY(bool state READ getState WRITE setState NOTIFY toggled)
Q_PROPERTY(QString champion READ getChampion WRITE setChampion NOTIFY championChanged)
public:
explicit StageSettings(QWidget *parent = nullptr);
@ -26,29 +26,38 @@ public:
bool getState() const;
void setState(bool);
QString getChampion() const;
void setChampion(const QString& str);
struct SelectedChamp {
SelectedChamp(QString name, uint32_t id);
QString name;
uint32_t id;
};
std::vector<SelectedChamp> getChampions() const;
void setChampions(const std::vector<QString>& champs);
void setDataDragon(DataDragon* dd);
void addChamp(const DataDragon::ChampData& cd, QPixmap icon);
void resizeEvent(QResizeEvent *event) override;
void loadConfig(Config::StageConfig& c);
private slots:
void championChangedinternal(const QString& str);
void toggledinternal(int state);
void addChamp();
void removeChamp();
void moved();
signals:
void toggled(bool);
void championChanged(const QString&);
void championsChanged();
private:
void rescaleImage();
void applyChampion(const DataDragon::ChampData& cd);
// delete all items
void resolveAndAddChamp(const QString& name, bool emitchange = false);
void clear();
void updateEnabled();
Ui::StageSettings *ui;
DataDragon* dd = nullptr;
int currentdisplayedChampKey = -1;
cv::Mat img;
};
#endif // STAGESETTINGS_H

View File

@ -1,5 +0,0 @@
#pragma once
#include <opencv2/opencv.hpp>
void debugImage(cv::Mat img, const std::string& name = "");

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=Audio;

View File

@ -3,19 +3,33 @@ QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++17
unix:LIBS += thirdparty/XInputSimulator/build/libXInputSimulator.a -lX11 -lXtst -lXext -lxcb -lXau -lcurl -pthread -lXdmcp -lrt `pkg-config opencv4 --libs`
# debugging
CONFIG += debug
MOC_DIR = build/generated/
UI_DIR = build/ui/
RCC_DIR = build/rcc/
OBJECTS_DIR = build/objects/
unix:LIBS += -lcurl -pthread -lrt
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
DEFINES += LOG_ENABLEQT=1
# You can also make your code fail to compile if it uses deprecated APIs.
# 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.
#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
defineReplace(prependAll) {
for(a,$$1):result += $$2$${a}$$3
@ -24,97 +38,133 @@ defineReplace(prependAll) {
SOURCES += \
src/arg.cpp \
src/blitzapi.cpp \
src/champcache.cpp \
src/championsearch.cpp \
src/champrow.cpp \
src/clientaccess.cpp \
src/clientapi_json.cpp \
src/clientapi.cpp \
src/clipboardpopup.cpp \
src/config.cpp \
src/datadragon.cpp \
src/datadragonimagecache.cpp \
src/fakescreen.cpp \
src/files.cpp \
src/json.cpp \
src/lolautoaccept_accept.cpp \
src/lolautoaccept_ban.cpp \
src/lolautoaccept_pick.cpp \
src/lolautoaccept_prepick.cpp \
src/loadingwindow.cpp \
src/lolautoaccept.cpp \
src/main.cpp \
src/mainwindow.cpp \
src/matcher.cpp \
src/memoryimagecache.cpp \
src/scaleableinputs.cpp \
src/screen.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/x11helper.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 += \
include/arg.h \
include/blitzapi.h \
include/champcache.h \
include/championsearch.h \
include/champrow.h \
include/clientaccess.h \
include/clientapi.h \
include/clipboardpopup.h \
include/config.h \
include/datadragon.h \
include/datadragonimagecache.h \
include/fakescreen.h \
include/defer.h \
include/files.h \
include/json.h \
include/loadingwindow.h \
include/lolautoaccept.h \
include/mainwindow.h \
include/matcher.h \
include/memoryimagecache.h \
include/scaleableinputs.h \
include/screen.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/x11helper.h \
thirdparty/Log/Log.h
# mainwindow.h
MOC_DIR = build/generated/
UI_DIR = ui/
OBJECTS_DIR = build/
FORMS += \
ui/championsearch.ui \
ui/clipboardpopup.ui \
ui/loadingwindow.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
INCLUDEPATH += $$PWD/include/ \
$$PWD/thirdparty/Log/ \
/usr/include/opencv4/opencv \
/usr/include/opencv4
#TRANSLATIONS += \
# ts/de_DE.ts \
# ts/en.ts
$$PWD/thirdparty/Log/
# translations
LANGUAGES = de_DE en
CONFIG += lrelease embed_translations
TRANSLATIONS = $$prependAll(LANGUAGES, $$PWD/ts/, .ts)
TRANSLATIONSQM = $$prependAll(LANGUAGES, $$PWD/ts/, .qm)
TRANSLATIONS = $$prependAll(LANGUAGES, $$PWD/resources/ts/, .ts)
makelang.commands = lrelease $$_PRO_FILE_
updatelang.commands = lupdate $$_PRO_FILE_
QMAKE_EXTRA_TARGETS += makelang updatelang
PRE_TARGETDEPS += makelang
QMAKE_CLEAN += $$TRANSLATIONSQM
updatelang.commands = lupdate -locations none $$_PRO_FILE_
QMAKE_EXTRA_TARGETS += updatelang
# build AppImage
unix {
$$PWD/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
$$MOC_DIR/lolautoaccept.svg.commands = touch $$MOC_DIR/lolautoaccept.svg
DEFINES += X11SUPPORT=1
LIBS += -lX11
appimg.depends = $$PWD/linuxdeploy-x86_64.AppImage $${TARGET} $$MOC_DIR/lolautoaccept.svg
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
resources/lolautoaccept.png.depends = resources/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} resources/lolautoaccept.png
appimg.commands = rm -rf AppDir ; \
mkdir -p AppDir/ts AppDir/imgs; \
cp $$PWD/ts/*.qm ./AppDir/ts ; \
cp $$PWD/imgs/*.png ./AppDir/imgs; \
./linuxdeploy-x86_64.AppImage --appdir=AppDir -e lolautoaccept -i $$MOC_DIR/lolautoaccept.svg -d lolautoaccept.desktop --output appimage
mkdir -p AppDir/ts ; \
./linuxdeploy-x86_64.AppImage --appdir=AppDir -e lolautoaccept -i resources/lolautoaccept.png -d resources/lolautoaccept.desktop --output appimage
QMAKE_EXTRA_TARGETS += appimg $$PWD/linuxdeploy-x86_64.AppImage $$MOC_DIR/lolautoaccept.svg
QMAKE_EXTRA_TARGETS += appimg linuxdeploy-x86_64.AppImage resources/lolautoaccept.png
QMAKE_CLEAN += $$PWD/linuxdeploy-x86_64.AppImage $$MOC_DIR/lolautoaccept.svg
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.
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
# https://wiki.qt.io/Automating_generation_of_qm_files
RESOURCES += \
resources/res.qrc

View File

@ -1,7 +0,0 @@
#!/bin/bash
rm -rf thirdparty/XInputSimulator/build
mkdir -p thirdparty/XInputSimulator/build
cd thirdparty/XInputSimulator/build/
cmake ../XInputSimulator/
make -j

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

@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" height="1024" width="1024">
<defs>
<clipPath id="text-circle-cutoff">
<circle cx="512" cy="512" r="494" fill="none" />
</clipPath>
<clipPath id="circles-text-cutoff">
<rect width="1024" height="386" y="786" />
<rect width="448" height="1024" y="0" />
<rect width="448" height="1024" y="0" x="576" />
</clipPath>
</defs>
<!-- background - the blue of the accept button -->
<circle cx="512" cy="512" r="510" fill="#005e84" />
<!-- edge -->
<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" />
<!-- 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">
AA
</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 -->
<polygon points="512,786 354,226 500,226 488,48 536,48 524,226 670,226" />
<!-- click effect -->
<ellipse rx="128" ry="32" cx="512" cy="786" fill="none" stroke="black" stroke-width="6" clip-path="url(#circles-text-cutoff)" />
<ellipse rx="232" ry="58" cx="512" cy="786" fill="none" stroke="black" stroke-width="4" clip-path="url(#circles-text-cutoff)" />
<ellipse rx="332" ry="83" cx="512" cy="786" fill="none" stroke="black" stroke-width="2" clip-path="url(#circles-text-cutoff)" />
</svg>

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) {
static struct option long_options[] = {
{"debug-log", no_argument, &a.debugLog, 1},
{"access", no_argument, &a.access, 1},
{0, 0, 0, 0}
};
/* 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";
}

43
src/championsearch.cpp Normal file
View File

@ -0,0 +1,43 @@
#include "championsearch.h"
#include "ui_championsearch.h"
#include <Log.h>
ChampionSearch::ChampionSearch(DataDragon* dd, QWidget *parent) : QDialog(parent), ui(new Ui::ChampionSearch), dd(dd) {
ui->setupUi(this);
}
ChampionSearch::~ChampionSearch() {
delete ui;
}
ChampRow* ChampionSearch::getSearchResult() {
if(ui->championList->count() == 1) {
return (ChampRow*) ui->championList->takeItem(0);
}
int row = ui->championList->currentRow();
return (ChampRow*) ui->championList->takeItem(row);
}
void ChampionSearch::searchChanged(QString str) {
qInfo() << "champion search: " << str;
const std::vector<const DataDragon::ChampData*> champs = dd->getMatchingChamp(str);
qInfo() << "found " << champs.size() << " champs";
clear();
for(auto cd : champs) {
dd->getImageAsnyc(cd->id, [this, cd](QPixmap img) {
auto cr = new ChampRow();
cr->setChamp(*cd, img);
ui->championList->addItem(cr);
});
}
}
void ChampionSearch::clear() {
while(ui->championList->count()) {
delete ui->championList->takeItem(0);
}
}

28
src/champrow.cpp Normal file
View File

@ -0,0 +1,28 @@
#include "champrow.h"
ChampRow::ChampRow(QListWidget* parent) : QListWidgetItem(parent) {
}
ChampRow::~ChampRow() {
}
void ChampRow::setChamp(const DataDragon::ChampData& cd, QPixmap icon) {
setText(cd.name);
champid = cd.key;
this->icon = 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 {
return text();
}
uint32_t ChampRow::getChampID() const {
return champid;
}
QPixmap ChampRow::getIcon() {
return icon;
}

47
src/clientaccess.cpp Normal file
View File

@ -0,0 +1,47 @@
#include "clientaccess.h"
#include <fstream>
#include <iomanip>
#include <QDebug>
#include <QStringList>
ClientAccess::ClientAccess() {}
ClientAccess::ClientAccess(const QString& token, uint16_t port) : authcode(token), port(port) {}
std::shared_ptr<ClientAccess> createFromLockfile(std::istream& lockfile) {
std::string content;
std::getline(lockfile, content);
QStringList parts = QString::fromStdString(content).split(':');
if(parts.size() != 5) {
qCritical() << "lockfile contained " << parts.size() << " parts, expected 5";
return {};
}
const QString portstr = parts.at(2);
const QString token = parts.at(3);
// try to parse port
bool success = false;
uint16_t port = portstr.toUInt(&success);
if(!success) {
qCritical() << "could not parse port: " << portstr;
return nullptr;
}
return std::shared_ptr<ClientAccess>(new ClientAccess(token, port));
}
QString ClientAccess::getBasicAuth() const {
return "riot:" + authcode;
}
uint16_t ClientAccess::getPort() const {
return port;
}
QString ClientAccess::getURL() const {
return "https://127.0.0.1:" + QString::number(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;
}

374
src/clientapi.cpp Normal file
View File

@ -0,0 +1,374 @@
#include "clientapi.h"
#include <cassert>
#include <iomanip>
#include <QJsonArray>
#include <QJsonObject>
#include <Log.h>
#include "json.h"
ClientAPI::ClientAPI(const ClientAccess& ca) : RestClient(ca.getURL()), access(ca), memImageCache(40), imageCache("runes", "") {
basicauth = ca.getBasicAuth();
disableCertCheck = true;
// enableDebugging();
}
ClientAPI::~ClientAPI() {}
ClientAPI::ReadyCheckState ClientAPI::getReadyCheckState() {
QJsonDocument doc = request("lol-matchmaking/v1/ready-check");
if (doc.isObject()) {
return toReadyCheckState(doc.object());
}
return ClientAPI::ReadyCheckState::NONE;
}
ClientAPI::GameflowPhase ClientAPI::getGameflowPhase() {
// it is just a json-string no object
QByteArray data = requestRaw("lol-gameflow/v1/gameflow-phase");
QString datastr = QString::fromLocal8Bit(data);
if (data.size() > 2) {
datastr = datastr.mid(1, datastr.size() -2);
return toGameflowPhase(datastr);
}
return ClientAPI::GameflowPhase::NONE;
}
void ClientAPI::acceptMatch() {
request("lol-matchmaking/v1/ready-check/accept", Method::POST);
}
void ClientAPI::declineMatch() {
request("lol-matchmaking/v1/ready-check/decline", Method::POST);
}
ClientAPI::ChampSelectSession ClientAPI::getChampSelectSession() {
QJsonDocument doc = request("lol-champ-select/v1/session");
if(doc.isObject()) {
return (ChampSelectSession) doc.object();
}
return {};
}
bool ClientAPI::setChampSelectAction(int32_t actionid, int32_t champid, bool completed) {
QJsonObject requestj;
requestj["championId"] = champid;
requestj["completed"] = completed;
requestj["id"] = actionid;
QJsonDocument requestdoc(requestj);
const QString requeststr = QString::fromLocal8Bit(requestdoc.toJson(QJsonDocument::JsonFormat::Compact));
qDebug().noquote() << "requeststr: " << requeststr;
QJsonDocument doc = request("lol-champ-select/v1/session/actions/" + QString::number(actionid), Method::PATCH, 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();
}
}
qDebug() << "patching action: " << actionid << " error: " << error;
return error.isEmpty();
}
ClientAPI::PlayerInfo ClientAPI::getSelf() {
QJsonDocument doc = request("lol-chat/v1/me");
if(doc.isObject()) {
QJsonObject obj = doc.object();
PlayerInfo info;
info.gameName = getValue<QString>(obj, "gameName");
info.name = getValue<QString>(obj, "name");
info.statusMessage = getValue<QString>(obj, "statusMessage", "");
info.summonerid = getValue<uint64_t>(obj, "summonerId");
auto lolref = obj["lol"];
if(lolref.isObject()) {
QJsonObject lol = lolref.toObject();
info.puuid = getValue<QString>(lol, "puuid");
info.level = getValue<int32_t>(lol, "level");
}
return info;
}
return {};
}
void ClientAPI::dodge() {
QJsonDocument doc = request("lol-login/v1/session/invoke?destination=lcdsServiceProxy&method=call&args=[\"\",\"teambuilder-draft\",\"quitV2\", \"\"]", Method::POST, "");
qDebug() << "dodge result:" << doc;
}
static std::vector<int32_t> fromArrayToVector(const QJsonArray& arr) {
std::vector<int32_t> out;
out.reserve(arr.size());
for (auto it = arr.begin(); it != arr.end(); ++it) {
if (it->isDouble())
out.push_back(it->toInt());
}
return out;
}
std::vector<int32_t> ClientAPI::getBannableChampIDs() {
QJsonDocument doc = request("lol-champ-select/v1/bannable-champion-ids");
if (doc.isArray()) {
return fromArrayToVector(doc.array());
}
return {}; // empty vector
}
std::vector<int32_t> ClientAPI::getPickableChampIDs() {
QJsonDocument doc = request("lol-champ-select/v1/pickable-champion-ids");
if (doc.isArray()) {
return fromArrayToVector(doc.array());
}
return {}; // empty vector
}
ClientAPI::TimerInfo ClientAPI::getTimerInfo() {
QJsonDocument doc = request("lol-champ-select/v1/session/timer");
if (!doc.isObject()) return {};
QJsonObject obj = doc.object();
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;
}

309
src/clientapi_json.cpp Normal file
View File

@ -0,0 +1,309 @@
#include "clientapi.h"
#include <QJsonArray>
#include <QJsonObject>
#include <Log.h>
#include "json.h"
// calculate the size of a constant sized array
#define ARRSIZE(ARRNAME) (sizeof(ARRNAME) / sizeof(ARRNAME[0]))
#define ARR(NAME, ...) static const QString NAME ## Names[] = {__VA_ARGS__}; \
static const uint32_t NAME ## NamesCount = ARRSIZE(NAME ## Names);
ARR(ReadyCheckState, "Invalid", "None", "InProgress", "Accepted", "Declined")
ARR(GameflowPhase, "None", "Lobby", "Matchmaking", "CheckedIntoTournament", "ReadyCheck", "ChampSelect", "GameStart", "FailedToLaunch", "InProgress", "Reconnect", "WaitingForStats", "PreEndOfGame", "EndOfGame", "TerminatedInError")
ARR(ChampSelectPhase, "Invalid", "GAME_STARTING", "PLANNING", "BAN_PICK", "FINALIZATION")
ARR(Position, "Invalid", "top", "jungle", "middle", "bottom", "utility")
ARR(ShortPosition, "", "Top", "Jgl", "Mid", "Bot", "Sup")
ARR(ChampSelectActionType, "Invalid", "ban", "pick", "ten_bans_reveal")
template<typename T>
static T mapEnum(const QString& input, const QString* names, uint32_t count, T defaul) {
if(input.isEmpty()) return defaul;
for (uint32_t i = 0; i < count; ++i) {
if (names[i] == input) {
return (T) i;
}
}
qWarning() << "no mapping of enum-string: " << input << " using default: " << defaul << " type: " << typeid(T).name();
return defaul;
}
#define MAPENUM(VAR, 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) {
QString searchState = getValue<QString>(obj, "state", "Invalid");
QString playerresponse = getValue<QString>(obj, "playerResponse", "None");
ClientAPI::ReadyCheckState response = MAPENUM(playerresponse, ReadyCheckState, NONE);
if(response == ClientAPI::ReadyCheckState::NONE) {
auto sstate = MAPENUM(searchState, ReadyCheckState, INVALID);
if(sstate == ClientAPI::ReadyCheckState::INPROGRESS) {
return sstate;
}
}
return response;
}
ClientAPI::GameflowPhase ClientAPI::toGameflowPhase(const QString& str) {
return MAPENUM(str, GameflowPhase, NONE);
}
ClientAPI::ChampSelectPhase ClientAPI::toChampSelectPhase(const QString& str) {
return MAPENUM(str, ChampSelectPhase, INVALID);
}
Position toPosition(const QString& str) {
return mapEnum(str, PositionNames, PositionNamesCount, Position::INVALID);
}
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);
}
ClientAPI::TimerInfo::TimerInfo() {}
ClientAPI::TimerInfo::TimerInfo(const QJsonObject& obj) {
adjustedTimeLeftInPhase = getValue<int64_t>(obj, "adjustedTimeLeftInPhase", 0);
internalNowInEpochMs = getValue<int64_t>(obj, "internalNowInEpochMs", 0);
isefinite = getValue<bool>(obj, "isefinite", 0);
phase = MAPENUM(getValue<QString>(obj, "phase", "Invalid"), ChampSelectPhase, INVALID);
totalTimeInPhase = getValue<int64_t>(obj, "totalTimeInPhase", 0);
}
ClientAPI::ChampSelectAction::ChampSelectAction() {}
ClientAPI::ChampSelectAction::ChampSelectAction(const QJsonObject& json) {
actorCellID = getValue<int32_t>(json, "actorCellId");
championID = getValue<int32_t>(json, "championId");
completed = getValue<bool>(json, "completed");
id = getValue<int32_t>(json, "id");
isAllyAction = getValue<bool>(json, "isAllyAction");
isInProgress = getValue<bool>(json, "isInProgress");
const QString typestr = getValue<QString>(json, "type", "Invalid");
type = toChampSelectActionType(typestr);
}
ClientAPI::ChampSelectCell::ChampSelectCell() {}
ClientAPI::ChampSelectCell::ChampSelectCell(const QJsonObject& json) {
position = toPosition(getValue<QString>(json, "assignedPosition"));
cellID = getValue<int32_t>(json, "cellId");
championID = getValue<int32_t>(json, "championId");
championPickIntentID = getValue<int32_t>(json, "championPickIntent");
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> out;
out.reserve(arr.size()); // should usualy be 5
for(auto it = arr.constBegin(); it != arr.constEnd(); ++it) {
if(it->isObject()) {
out.push_back((ChampSelectCell) it->toObject());
}
}
return out;
}
ClientAPI::ChampSelectSession::ChampSelectSession() {}
ClientAPI::ChampSelectSession::ChampSelectSession(const QJsonObject& json) {
// loading actions
auto actionsref = json["actions"];
// its an array of array id ChampSelectAction
if(actionsref.isArray()) {
QJsonArray jactions = actionsref.toArray();
for(auto jact = jactions.constBegin(); jact != jactions.constEnd(); ++jact) {
if(!jact->isArray()) continue;
QJsonArray jactarr = jact->toArray();
for(auto it = jactarr.constBegin(); it != jactarr.constEnd(); ++it) {
if(it->isObject()) {
actions.push_back((ChampSelectAction) it->toObject());
}
}
}
}
counter = getValue<int32_t>(json, "counter");
gameid = getValue<int64_t>(json, "gameId");
allowDuplicatePick = getValue(json, "allowDuplicatePicks", false);
allowRerolling = getValue(json, "allowRerolling", false);
allowSkinSelection = getValue(json, "allowSkinSelection", true);
hasSimultaneousBans = getValue(json, "hasSimultaneousBans", false);
hasSimultaneousPicks = getValue(json, "hasSimultaneousPicks", true);
isCustomGame = getValue(json, "isCustomGame", false);
isSpectating = getValue(json, "isSpectating", false);
skipChampionSelect = getValue(json, "skipChampionSelect", false);
rerollsRemaining = getValue(json, "rerollsRemaining", 0);
localPlayerCellId = getValue(json, "localPlayerCellId", -1);
auto timerref = json["timer"];
if(timerref.isObject()) {
timer = (TimerInfo) timerref.toObject();
}
// load cellinfo
auto myteamref = json["myTeam"];
if(myteamref.isArray()) {
myTeam = loadAllInfos(myteamref.toArray());
}
auto theirteamref = json["theirTeam"];
if(theirteamref.isArray()) {
theirTeam = loadAllInfos(theirteamref.toArray());
}
}
ClientAPI::ChampSelectSession::operator bool() {
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) \
std::ostream& operator<<(std::ostream& str, const ClientAPI:: ENUMNAME & state) { \
assert(((uint32_t) state) < ENUMNAME ## NamesCount); \
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(GameflowPhase)
PRINTENUM(ChampSelectPhase)
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

@ -3,91 +3,234 @@
#include "files.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonArray>
#include <Log.h>
#include "json.h"
#ifdef WIN32
#define CONFPATH "lolautoacceptor/"
#else
#define CONFPATH ".config/lolautoaccept/"
#endif
Config::StageConfig::StageConfig() : enabled(false) {}
Config::StageConfig::StageConfig(const QJsonObject& j) {
champ = getValue<std::string>(j, "champ", "");
if(j["champ"].isString()) {
champs.push_back(getValue<QString>(j, "champ"));
}
if(j["champs"].isArray()) {
QJsonArray jchamps = j["champs"].toArray();
for(auto jchamp : jchamps) {
if(jchamp.isString()) {
QString jchampstr = jchamp.toString();
if(!jchampstr.isEmpty()) {
champs.push_back(jchampstr);
}
}
}
}
enabled = getValue(j, "enabled", false);
}
Config::StageConfig::operator QJsonObject() const {
QJsonObject out;
out.insert("champ", QString::fromStdString(champ));
QJsonArray jchamps;
for(const QString& champ : champs) {
if(!champ.isEmpty()) {
jchamps.push_back(champ);
}
}
out.insert("champs", jchamps);
out.insert("enabled", enabled);
return out;
}
Config::PositionConfig::PositionConfig() {}
Config::PositionConfig::PositionConfig(const QJsonObject& j) {
ban = getValue<Config::StageConfig>(j, "ban");
pick = getValue<Config::StageConfig>(j, "pick");
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) {
prepick = getValue<Config::StageConfig>(j, "prepick");
ban = getValue<Config::StageConfig>(j, "ban");
pick = getValue<Config::StageConfig>(j, "pick");
enabledAutoAccept = getValue(j, "enabledAutoAccept", false);
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 {
QJsonObject out;
out.insert("prepick", (QJsonObject) prepick);
out.insert("ban", (QJsonObject) ban);
out.insert("pick", (QJsonObject) pick);
QJsonArray positionarr;
for(auto pos : positionConfigs) {
positionarr.push_back((QJsonObject) *pos.get());
}
out["positions"] = positionarr;
out["runepages"] = (QJsonObject) runepagesConfig;
out.insert("enabledAutoAccept", enabledAutoAccept);
out.insert("enabledSmiteWarn", enabledSmiteWarn);
out.insert("enabledAutoWrite", enabledAutoWrite);
out.insert("autoWriteText", autoWriteText);
out.insert("version", 1);
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() {
configFolderPath = getHome() + ".config/lolautoaccept/";
configFolderPath = getHome() + CONFPATH;
configFilePath = configFolderPath + "config.json";
mkdirs(configFolderPath);
}
Config::~Config() {}
bool Config::load() {
QFile conffile(QString::fromStdString(configFilePath));
QFile conffile(configFilePath);
if(!conffile.open(QIODevice::ReadOnly)) {
Log::error << "could not open configfile: " << configFilePath;
qCritical() << "could not open configfile: " << configFilePath;
return false;
}
QJsonParseError err;
QJsonDocument doc = QJsonDocument::fromJson(conffile.readAll(), &err);
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;
}
if(doc.isObject()) {
// implicit cast
root = doc.object();
Log::info << "config loaded";
qInfo() << "config loaded";
return true;
}
Log::error << "config is not a json object!";
qCritical() << "config is not a json object!";
return false;
}
void Config::save() {
Log::note << "Config::save()";
mkdirs(configFolderPath);
QFile conffile(QString::fromStdString(configFilePath));
QFile conffile(configFilePath);
if(!conffile.open(QIODevice::WriteOnly)) {
Log::error << "could not open configfile: " << configFilePath;
qCritical() << "could not open configfile: " << configFilePath;
return;
}
QJsonDocument doc;
doc.setObject(root);
conffile.write(doc.toJson());
Log::info << "config saved";
qInfo() << "config saved";
}
Config::RootConfig& Config::getConfig() {

View File

@ -6,52 +6,39 @@
#include <curl/easy.h>
#include <Log.h>
#include <QEventLoop>
#include <QJsonArray>
#include <QJsonObject>
#include <QThread>
#include <algorithm> // std::max
#include <algorithm> // std::max, champ matching
#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;
static size_t arrayWriteCallback(char* contents, size_t size, size_t nmemb, void* userdata) {
if(userdata) {
QByteArray* arr = (QByteArray*) userdata;
arr->append(contents, size * nmemb);
return size * nmemb;
}
return 0;
}
DataDragon::DataDragon(const std::string& locale) : locale(locale), cache({{"square", ".png"}, {"loading", "_0.jpg"}, {"splash", "_0.jpg"}}) {
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, arrayWriteCallback);
startThread();
DataDragon::DataDragon(const QString& locale) : RestClient(BASEURL), locale(locale), cache{{"square", ".png"}, {"loading", "_0.jpg"}, {"splash", "_0.jpg"}} {
this->setObjectName("DataDragon");
}
DataDragon::~DataDragon() {
stopAndJoinThread();
curl_easy_cleanup(curl);
}
DataDragon::ChampData::ChampData() : key(-1) {}
DataDragon::ChampData::ChampData(const QJsonObject& source) {
name = getValue<std::string>(source, "name", "");
id = getValue<std::string>(source, "id", "");
name = getValue<QString>(source, "name", "");
id = getValue<QString>(source, "id", "");
key = getValue<int>(source, "key", -1);
partype = getValue<std::string>(source, "partype", "");
title = getValue<std::string>(source, "title", "");
partype = getValue<QString>(source, "partype", "");
title = getValue<QString>(source, "title", "");
}
const std::string& DataDragon::getVersion() {
const QString& DataDragon::getVersion() {
std::unique_lock lock(cachedatamutex);
while(version.empty() && shouldrun) {
while(version.isEmpty() && shouldrun) {
cachedatacv.wait(lock);
}
return version;
@ -65,53 +52,59 @@ const std::vector<DataDragon::ChampData>& DataDragon::getChamps() {
return champs;
}
cv::Mat DataDragon::getImage(const std::string& champid, ImageType imgtype) {
if(champid.empty()) return {};
QPixmap DataDragon::getImage(const QString& champid, ImageType imgtype, bool writeMemcache) {
if(champid.isEmpty()) return {};
// query mem cache
cv::Mat img = memcache.getImage(champid, (int) imgtype);
if(!img.empty()) return img;
QPixmap img = memcache.getImage(champid, (int) imgtype);
if(!img.isNull()) return img;
// query HDD cache
img = cache[(int) imgtype].getImage(champid);
if(!img.empty()) {
if(!img.isNull()) {
// update mem cache
memcache.addImage(img, champid, (int) imgtype);
return img;
}
const std::string url = getImageUrl(champid, imgtype);
if(url.empty()) return {};
const QString url = getImageUrl(champid, imgtype);
if(url.isEmpty()) return {};
QByteArray arr = requestRaw(url);
QByteArray arr;
try {
arr = requestRaw(url);
} catch(RestClient::WebException& e) {}
if(arr.isEmpty()) {
Log::error << "image could not be loaded";
qCritical() << "image could not be loaded";
return {};
}
// propably an error
if(arr.size() < 1000) {
Log::info << "small icon url: " << url;
Log::info << "content: " << std::string(arr.data(), arr.size());
qInfo() << "small icon url: " << url;
qInfo() << "content: " << QString::fromLocal8Bit(arr);
return {};
}
// store HDD cache
cache[(int) imgtype].addImageRaw(arr, champid);
// convert byte array to image data
cv::Mat rawData(1, arr.size(), CV_8UC1, (void*) arr.data());
// remove from notDownloadedList
notDownloadedImages.erase(champid);
cv::Mat decodedImage = cv::imdecode(rawData, cv::IMREAD_COLOR);
cv::cvtColor(decodedImage, decodedImage, cv::COLOR_BGR2RGB);
QPixmap decodedImage;
decodedImage.loadFromData(arr);
// store mem cache
memcache.addImage(decodedImage, champid, (int) imgtype);
if(writeMemcache) {
memcache.addImage(decodedImage, champid, (int) imgtype);
}
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;
{
@ -121,44 +114,40 @@ void DataDragon::getImageAsnyc(const std::string& champid, notifyImgfunc_t func,
tasksnotemptycv.notify_one();
}
static std::string toLower(const std::string& in) {
return QString::fromStdString(in).toLower().toStdString();
}
static size_t startinglength(const std::string& original, const std::string& prefix) {
size_t i = 0;
static int startinglength(const QString& original, const QString& prefix) {
int i = 0;
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;
}
static size_t matchChamp(const DataDragon::ChampData& champ, const std::string& lower) {
std::string lowerid = toLower(champ.id);
std::string lowername = toLower(champ.name);
static size_t matchChamp(const DataDragon::ChampData& champ, const QString& lower) {
QString lowerid = champ.id.toLower();
QString lowername = champ.name.toLower();
if(lowerid == lower || lowername == lower) {
return lower.size();
}
size_t lengtha = startinglength(lowerid, lower);
size_t lengthb = startinglength(lowername, lower);
int lengtha = startinglength(lowerid, 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();
// for now: just check for a perfect hit
std::string lower = toLower(name);
QString lower = name.toLower();
int bestmatchingoffset = -1;
size_t bestmatchinglength = 0;
int bestmatchinglength = 0;
uint32_t bestmatchingcount = 0;
for(size_t offset = 0; offset < champs.size(); ++offset) {
const ChampData& it = champs.at(offset);
size_t match = matchChamp(it, lower);
int match = matchChamp(it, lower);
if(match > bestmatchinglength) {
bestmatchinglength = match;
bestmatchingcount = 1;
@ -180,10 +169,80 @@ const DataDragon::ChampData& DataDragon::getBestMatchingChamp(const std::string&
return EMPTYCHAMP;
}
std::string DataDragon::getImageUrl(const std::string& champid, ImageType type) {
std::vector<const DataDragon::ChampData*> DataDragon::getMatchingChamp(const QString& name, uint32_t limit) {
if(limit == 0) return {};
if(name.isEmpty()) return {};
getChamps();
QString lower = name.toLower();
std::vector<size_t> matches;
matches.resize(champs.size());
std::transform(champs.begin(), champs.end(), std::insert_iterator(matches, matches.begin()), std::bind(&matchChamp, std::placeholders::_1, lower));
// find smallest element
std::vector<size_t> matches_sort = matches;
std::nth_element(matches_sort.begin(), matches_sort.begin(), matches_sort.end(), std::greater{});
size_t border = matches_sort.at(0);
Log::trace << "border: " << border;
std::vector<const ChampData*> out;
out.reserve(limit);
// take all with a match higher than that
for(uint32_t i = 0; i < matches.size() && out.size() < limit; ++i) {
if(matches[i] >= border) {
out.push_back(&champs[i]);
}
}
return out;
}
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) {
case ImageType::SQUARE: {
if(getVersion().empty()) {
if(getVersion().isEmpty()) {
return {};
}
@ -200,105 +259,82 @@ std::string DataDragon::getImageUrl(const std::string& champid, ImageType type)
return {};
}
std::string DataDragon::getCDNString() const {
QString DataDragon::getCDNString() const {
return "cdn/" + version + "/";
}
QByteArray DataDragon::requestRaw(const std::string& url) {
if(!curl) return {};
std::string requrl = BASEURL + url;
QByteArray ba; //buffer
// std::cout << "[DEBUG] requrl is: " << requrl << std::endl;
curl_easy_setopt(curl, CURLOPT_URL, requrl.c_str());
// curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); //Prevent "longjmp causes uninitialized stack frame" bug
// set callback data
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ba);
// Check for errors
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
if(res == CURLE_HTTP_RETURNED_ERROR) {
long responsecode = -1;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responsecode);
Log::warn << "DataDragon request failed: " << url << " -> " << responsecode ;
} else {
Log::warn << "DataDragon request failed: " << url << " " << curl_easy_strerror(res);
}
return {};
void DataDragon::prefetchChampImage(const QString& champid, ImageType imgtype) {
if(!cache[(int) imgtype].hasImage(champid)) {
qDebug() << "prefetch " << champid << " type: " << (int) imgtype;
getImage(champid, imgtype, false);
}
return ba;
}
QJsonDocument DataDragon::request(const std::string& url) {
QByteArray arr = requestRaw(url);
if(arr.isEmpty()) return {};
QJsonParseError err;
QJsonDocument parsed = QJsonDocument::fromJson(arr, &err);
if(parsed.isNull() || err.error != QJsonParseError::NoError) {
Log::error << "DataDragon Json parse error " << err.errorString().toStdString() << " offset: " << err.offset;
return {};
}
return parsed;
}
void DataDragon::getVersionInternal() {
std::unique_lock lock(cachedatamutex);
if(!version.empty()) return;
if(!version.isEmpty()) return;
QJsonDocument jversions = request("api/versions.json");
if(jversions.isArray()) {
QJsonArray jverarr = jversions.array();
if(!jverarr.empty()) {
version = jverarr.at(0).toString().toStdString();
Log::info << "got League version: " << version;
version = champCache.getVersion();
if(!version.isEmpty()) return;
lock.unlock();
cachedatacv.notify_all();
return;
try {
QJsonDocument jversions = request("api/versions.json");
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;
}
}
}
Log::error << "error parsing version object";
qCritical() << "error parsing version object";
} catch(RestClient::WebException& e) {}
}
void DataDragon::getChampsInternal() {
std::unique_lock lock(cachedatamutex);
if(!champs.empty() && version.empty()) {
if(!champs.empty() && version.isEmpty()) {
return;
}
QJsonDocument 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");
QJsonDocument jchamps = champCache.getChamps();
bool cacheSuccessfull = !jchamps.isEmpty();
if(!cacheSuccessfull) {
try {
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()) {
// save to cache
if(!cacheSuccessfull) {
champCache.saveChamps(jchamps, version);
}
QJsonObject obj = jchamps.object();
auto it = obj.constFind("data");
if(it != obj.constEnd() && it.value().isObject()) {
QJsonObject jchampsdata = it.value().toObject();
for(auto champit = jchampsdata.constBegin(); champit != jchampsdata.constEnd(); champit++) {
if(champit.value().isObject()) {
champs.emplace_back(champit.value().toObject());
if(it != obj.constEnd() && it->isObject()) {
for(auto&& champdata : it->toObject()) {
if(champdata.isObject()) {
auto& dataobj = champs.emplace_back(champdata.toObject());
notDownloadedImages.insert(dataobj.id);
}
}
}
}
Log::info << "loaded " << champs.size() << " champs";
qInfo() << "loaded " << champs.size() << " champs from cache: " << cacheSuccessfull;
lock.unlock();
cachedatacv.notify_all();
}
void DataDragon::startThread() {
shouldrun = true;
bgthread = std::thread(&DataDragon::threadLoop, this);
}
void DataDragon::stopThread() {
shouldrun = false;
tasksnotemptycv.notify_all();
@ -307,11 +343,12 @@ void DataDragon::stopThread() {
void DataDragon::stopAndJoinThread() {
stopThread();
if(bgthread.joinable())
bgthread.join();
bgthread->wait();
}
void DataDragon::threadLoop() {
QEventLoop loop;
// init version and champ list
getVersionInternal();
getChampsInternal();
@ -322,20 +359,54 @@ void DataDragon::threadLoop() {
{
std::unique_lock lock(tasksmutex);
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;
t = tasks.front();
tasks.pop_front();
}
cv::Mat img = getImage(t.champid, t.type);
loop.processEvents();
QPixmap img = getImage(t.champid, t.type);
t.func(img);
}
}
qDebug() << "DataDragon Thread terminated";
}
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

@ -1,32 +1,35 @@
#include "datadragonimagecache.h"
#include <QFile>
#include <QPixmap>
#include <Log.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
cacheDir = getHome() + ".cache/lolautoaccept/" + folderextra + "/";
cacheDir = getCache() + folderextra + "/";
mkdirs(cacheDir);
}
DataDragonImageCache::~DataDragonImageCache() {}
cv::Mat DataDragonImageCache::getImage(const std::string& name) {
auto img = cv::imread(getFilepath(name));
if(!img.empty())
cv::cvtColor(img, img, cv::COLOR_BGR2RGB);
return img;
bool DataDragonImageCache::hasImage(const QString& 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) {
QFile file(QString::fromStdString(getFilepath(name)));
QPixmap DataDragonImageCache::getImage(const QString& name) {
return QPixmap(getFilepath(name));
}
void DataDragonImageCache::addImageRaw(const QByteArray& arr, const QString& name) {
QFile file(getFilepath(name));
file.open(QIODevice::WriteOnly);
file.write(arr);
}
std::string DataDragonImageCache::getFilepath(const std::string& name) const {
QString DataDragonImageCache::getFilepath(const QString& name) const {
return cacheDir + name + imageext;
}

View File

@ -1,38 +0,0 @@
#include "fakescreen.h"
FakeScreen::FakeScreen(int xoffset, int yoffset, int xsize, int ysize) : ScreenShot(),
xoffset(xoffset), yoffset(yoffset), xsize(xsize), ysize(ysize) {
}
FakeScreen::~FakeScreen() {}
void FakeScreen::take(cv::Mat& cvimg) {
// take screenshot from entire screen using base class
cv::Mat entireScreen;
ScreenShot::take(entireScreen);
// only return part of the screenshot
entireScreen({yoffset, yoffset + ysize}, {xoffset, xoffset + xsize}).copyTo(cvimg);
}
void FakeScreen::operator() (cv::Mat& cvimg) {
take(cvimg);
}
int FakeScreen::getXOffset() const {
return xoffset;
}
int FakeScreen::getYOffset() const {
return yoffset;
}
double FakeScreen::getXScale() const {
return xsize / (double) DEFAULTWIDTH;
}
double FakeScreen::getYScale() const {
return ysize / (double) DEFAULTHEIGHT;
}

View File

@ -2,28 +2,42 @@
#include <sys/stat.h>
#include <QDir>
#include <Log.h>
bool mkdirs(const std::string& path) {
size_t offset = 0;
while(offset < path.size()) {
offset = path.find('/', offset+1);
int res = mkdir(path.substr(0, offset).c_str(), S_IRWXU | S_IRWXG); // 770
if(res == -1 && errno != EEXIST) {
// mkdirs failed
return false;
}
}
#ifdef WIN32
#define CACHEPATH "lolautoacceptor/cache/"
#else
#define CACHEPATH ".cache/lolautoaccept/"
#endif
return true;
}
#ifdef WIN32
std::string getHome() {
const char* homevar = getenv("HOME");
QString getHome() {
const char* homevar = getenv("appdata");
if(homevar == nullptr) {
Log::warn << "$HOME is not set! Defaulting to ./";
qWarning() << "%appdata% is not set! Defaulting to ./";
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,11 +7,37 @@ int convert(const QJsonValue& val) {
return val.toInt();
}
template<>
uint32_t convert(const QJsonValue& val) {
if(val.isString())
return val.toString().toUInt();
return (uint32_t) val.toDouble();
}
template<>
int64_t convert(const QJsonValue& val) {
if(val.isString())
return val.toString().toLongLong();
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<>
std::string convert(const QJsonValue& val) {
return val.toString().toStdString();
}
template<>
QString convert(const QJsonValue& val) {
return val.toString();
}
template<>
bool convert(const QJsonValue& val) {
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,164 +1,72 @@
#include "lolautoaccept.h"
#include <algorithm>
#include <thread>
#include <Log.h>
#include "util.h"
#include "fakescreen.h"
#include <QDebug>
// set to 1 to use the test screenshot feature
#define TESTSCREEN 0
void debugImage(cv::Mat img, const std::string& name) {
if(img.channels() > 3) {
std::vector<cv::Mat> channels(4);
cv::split(img, channels);
channels.resize(3); // drop alpha channel
cv::merge(channels, img);
}
time_t t = time(0);
cv::imwrite("debugimages/" + name + std::to_string(t) + ".png", img);
}
LolAutoAccept::Stage::Stage(const std::string& matchertmpl) : matcher(matchertmpl) {}
LolAutoAccept::Stage::Stage() {}
LolAutoAccept::Stage::~Stage() {}
bool LolAutoAccept::Stage::process(LolAutoAccept& lolaa, cv::Mat& img) {
if(enabled) {
auto match = matcher.match(img);
if(match.doesMatch) {
action(lolaa);
return true;
}
}
return false;
}
LolAutoAccept::LolAutoAccept(Config::RootConfig& config, DataDragon& dd, QObject* parent) : QObject(parent), config(config), dd(dd) {
qRegisterMetaType<LolAutoAccept::Status>();
LolAutoAccept::CooldownStage::CooldownStage(const std::string& matchertmpl) : Stage(matchertmpl) {}
bool LolAutoAccept::CooldownStage::process(LolAutoAccept& lolaa, cv::Mat& img) {
if((time(0) - lastused) > cooldown) {
if(Stage::process(lolaa, img)) {
lastused = time(0);
return true;
}
}
return false;
}
void LolAutoAccept::performClick(uint32_t nr, bool movemouseaway) {
inputs.setOffset(screen->getXOffset(), screen->getYOffset());
inputs.setScale(screen->getXScale(), screen->getYScale());
auto p = inputs.get(nr);
Log::info << "click " << nr << " @ " << p.x << " " << p.y;
sim.mouseMoveTo(p.x, p.y);
std::this_thread::sleep_for(std::chrono::milliseconds(170));
sim.mouseClick(XIS::LEFT_MOUSE_BUTTON);
// move mouse away
if(movemouseaway) {
std::this_thread::sleep_for(std::chrono::milliseconds(170));
p = inputs.get(0);
sim.mouseMoveTo(p.x, p.y);
}
}
void LolAutoAccept::enterSearch(const std::string& text) {
performClick(2, false); // click searchbox
Log::debug << "enter text: " << text;
sim.keySequence(text);
std::this_thread::sleep_for(std::chrono::milliseconds(750));
}
void LolAutoAccept::pickFirst(const std::string& search) {
enterSearch(search);
performClick(3, false); // first champion
}
LolAutoAccept::LolAutoAccept() : sim(XInputSimulator::getInstance()) {
// click positions in 1280x720 scale
inputs.addPoint({0, 0}); // zero zero
inputs.addPoint({645, 560}); // accept game
inputs.addPoint({775, 105}); // search box
inputs.addPoint({380, 160}); // first champ
inputs.addPoint({638, 608}); // pick/ban champ
stages.reserve(4);
stages.push_back(new AcceptStage());
stages.push_back(new PrePickStage());
stages.push_back(new BanStage());
stages.push_back(new PickStage());
std::lock_guard lock(stagesMutex);
stages.resize(3); // accept, ban, pick
}
LolAutoAccept::~LolAutoAccept() {
stopJoinThread();
for(auto s : stages) {
delete s;
}
delete screen;
screen = nullptr;
}
void LolAutoAccept::setChamp(const std::string& champ, State s) {
if(s == State::LOBBY || s >= State::GAME) {
Log::warn << "setChamp() called on invalid State";
void LolAutoAccept::setChamps(const std::vector<uint32_t>& champs, State s) {
if(s == State::LOBBY || s >= State::PICK) {
qWarning() << "setChamps() called on invalid State";
return;
}
qDebug() << "LolAutoAccept::setChamps";
stages.at((int) s)->champ = champ;
{
std::lock_guard lock(stagesMutex);
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) {
if(s >= State::GAME) {
Log::warn << "setEnabled() called on invalid State";
if(s > State::PICK) {
qWarning() << "setEnabled() called on invalid State";
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() {
if(screen)
return true;
if(clientapi) return true;
// get window
#if TESTSCREEN == 0
auto wins = ScreenShot::getWindows("League of Legends");
if(wins.size() != 1) {
Log::fatal << "invalid count of windows found: " << wins.size();
for(auto w : wins) {
delete w;
}
return false;
auto ca = ClientAccess::find();
if(ca) {
clientapi = std::make_shared<ClientAPI>(*ca.get());
auto selfinfo = clientapi->getSelf();
qInfo() << "selfinfo: gameName: " << selfinfo.gameName << " name: " << selfinfo.name << " summonerid: " << selfinfo.summonerid << " statusMessage: " << selfinfo.statusMessage << " puuid: " << selfinfo.puuid << " level: " << selfinfo.level;
}
screen = (wins.at(0)); // just take the first
#else
// testing whole screen, but take a part of it, so it behaves like a focused screenshot
screen = new FakeScreen(1, 683, 1280, 720);
#endif
// testing: whole screen:
// screen = new ScreenShot();
return true;
return (bool) clientapi;
}
void LolAutoAccept::run() {
// make sure its not running
stopJoinThread();
if(!screen) return;
if(!clientapi) return; // no client api
lolaathread = std::thread(&LolAutoAccept::innerRun, this);
}
@ -167,38 +75,439 @@ void LolAutoAccept::stop() {
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() {
stop();
if(lolaathread.joinable()) {
lolaathread.join();
}
resetAllOffsets();
}
void LolAutoAccept::innerRun() {
shouldrun = true;
while(shouldrun) {
auto start = std::chrono::high_resolution_clock::now();
try {
auto convs = clientapi->getAllConversations();
qInfo() << "got " << convs.size() << " conversations";
cv::Mat img;
screen->take(img);
cv::resize(img, img, cv::Size(ScreenShot::DEFAULTWIDTH, ScreenShot::DEFAULTHEIGHT));
emit statusChanged(Status::Running);
while(shouldrun) {
uint32_t extrasleep = 800;
auto start = std::chrono::high_resolution_clock::now();
for(size_t i = 0; i < stages.size(); ++i) {
Log::trace << "processing stage " << i;
Stage* stage = stages.at(i);
if(stage->process(*this, img)) {
Log::debug << "stage successful: " << i;
break;
auto phase = clientapi->getGameflowPhase();
qInfo() << "current Gameflowphase: " << phase;
// do processing
if(phase == ClientAPI::GameflowPhase::LOBBY) {
resetAllOffsets();
} 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();
}
// change phase to non champselect phase -> disable dodge button
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();
std::chrono::duration<double> dur = (end-start);
if(dur.count() > 1e-5)
Log::info << "iteration took: " << (dur.count() * 1000) << " ms";
// disable this thread
shouldrun = false;
std::this_thread::sleep_for(std::chrono::milliseconds(800));
}
// notify the ui
emit statusChanged(Status::Failed);
}
}
void LolAutoAccept::resetPickOffsets() {
for(Stage& stage : stages) {
stage.currentOffset = 0;
}
lastPickedChamp = 0;
}
void LolAutoAccept::resetAllOffsets() {
resetPickOffsets();
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) {
assert(s > State::LOBBY && s <= State::PICK);
Stage& stage = stages[(int) s];
uint32_t size = stage.champids.size();
if(stage.currentOffset >= size) {
// no champ to try left
qWarning() << "no champ left at stage: " << (int) s;
qWarning() << "stage size: " << stage.champids.size();
return 0;
}
return stage.champids[stage.currentOffset];
}
void LolAutoAccept::nextChampOfState(State s) {
assert(s > State::LOBBY && s <= State::PICK);
Stage& stage = stages[(int) s];
uint32_t size = stage.champids.size();
stage.currentOffset++;
if(stage.currentOffset >= size) {
// no champ to try left
qWarning() << "no champ left at stage: " << (int) s;
qWarning() << "stage size: " << stage.champids.size();
stage.currentOffset = 0;
return;
}
}
bool LolAutoAccept::isChampIntentofTeammate(uint32_t champid, const ClientAPI::ChampSelectSession& session) {
for(const ClientAPI::ChampSelectCell& player : session.myTeam) {
if(player.championID == (int32_t) champid || player.championPickIntentID == (int32_t) 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 false;
}
LolAutoAccept::ownactions_t LolAutoAccept::getOwnActions(int32_t cellid, const std::vector<ClientAPI::ChampSelectAction> actions) {
ownactions_t out;
for(const auto& it : actions) {
if(it.actorCellID == cellid) {
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.
out.push_back(it);
}
}
}
return out;
}
void LolAutoAccept::prepickPhase(const ownactions_t& ownactions) {
phase(ownactions, ClientAPI::ChampSelectActionType::PICK, State::PICK, false);
}
void LolAutoAccept::banPhase(const ownactions_t& ownactions, const ClientAPI::ChampSelectSession& session) {
phase(ownactions, ClientAPI::ChampSelectActionType::BAN, State::BAN, true, [session](uint32_t champid) {
return !isChampIntentofTeammate(champid, session) && !isChampBanned(champid, session);
});
}
void LolAutoAccept::pickPhase(const ownactions_t& ownactions) {
phase(ownactions, ClientAPI::ChampSelectActionType::PICK, State::PICK, true);
}
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) {
if(it.type == type) {
qInfo() << type << " action anvailable: " << it.id << " champid: " << it.championID;
uint32_t champid = getChampOfState(s);
if(filter) {
// filter says no
if(!filter(champid)) {
Log::trace << "champid: " << champid << " filter says no - next champ";
nextChampOfState(s);
return;
}
}
if((it.championID != (int32_t) champid || !it.completed) && champid != 0) {
// try to prepick a champion
qInfo() << "try to pick champ: " << champid;
if(!clientapi->setChampSelectAction(it.id, champid, complete)) {
nextChampOfState(s);
}
return;
}
}
}
}
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() {
auto session = clientapi->getChampSelectSession();
const int32_t cellid = session.localPlayerCellId;
if(cellid != lastCellId && lastCellId != -1) {
resetPickOffsets();
}
lastCellId = cellid;
// find own cellid info
const ClientAPI::ChampSelectCell* me = nullptr;
uint32_t pickedChamp = 0;
for(const auto& it : session.myTeam) {
if(it.cellID == cellid) {
me = &it;
pickedChamp = it.championID;
break;
}
}
Position pos = me ? me->position : Position::INVALID;
// 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
auto ownactions = getOwnActions(cellid, session.actions);
qDebug() << "ownactions: " << ownactions.size();
// try to prepick champ
qInfo() << "champselectphase: " << session.timer.phase;
std::lock_guard lock(stagesMutex);
if(session.timer.phase == ClientAPI::ChampSelectPhase::PLANNING) {
prepickPhase(ownactions);
} else if(session.timer.phase == ClientAPI::ChampSelectPhase::BAN_PICK) {
banPhase(ownactions, session);
pickPhase(ownactions);
} else if(session.timer.phase == ClientAPI::ChampSelectPhase::FINALIZATION) {
// 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

@ -1,12 +0,0 @@
#include "lolautoaccept.h"
LolAutoAccept::AcceptStage::AcceptStage() : Stage("imgs/accept.png") {
matcher.setOffset(539, 529);
}
void LolAutoAccept::AcceptStage::action(LolAutoAccept& lolaa) {
lolaa.performClick(1); // accept Game
// security sleep
std::this_thread::sleep_for(std::chrono::seconds(1));
}

View File

@ -1,17 +0,0 @@
#include "lolautoaccept.h"
LolAutoAccept::BanStage::BanStage() : CooldownStage("imgs/ban.png") {
matcher.setOffset(1232, 90);
cooldown = 30;
}
void LolAutoAccept::BanStage::action(LolAutoAccept& lolaa) {
if(!champ.empty()) {
lolaa.pickFirst(champ);
std::this_thread::sleep_for(std::chrono::milliseconds(1800));
// click "ban"
lolaa.performClick(4);
}
}

View File

@ -1,17 +0,0 @@
#include "lolautoaccept.h"
LolAutoAccept::PickStage::PickStage() : CooldownStage("imgs/pick.png") {
matcher.setOffset(538, 587);
cooldown = 30;
}
void LolAutoAccept::PickStage::action(LolAutoAccept& lolaa) {
if(!champ.empty()) {
lolaa.pickFirst(champ);
std::this_thread::sleep_for(std::chrono::milliseconds(1800));
// click "accept"
lolaa.performClick(4);
}
}

View File

@ -1,11 +0,0 @@
#include "lolautoaccept.h"
LolAutoAccept::PrePickStage::PrePickStage() : CooldownStage("imgs/arrowdown.png") {
matcher.setOffset(615, 617);
cooldown = 15;
}
void LolAutoAccept::PrePickStage::action(LolAutoAccept& lolaa) {
if(!champ.empty())
lolaa.pickFirst(champ);
}

View File

@ -8,78 +8,47 @@
#include <QTranslator>
#include <Log.h>
#include <QDebug>
#include "arg.h"
#include "clientaccess.h"
#include "clientapi.h"
#include "mainwindow.h"
#include "lolautoaccept.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) {
Log::init();
Log::setConsoleLogLevel(Log::Level::INFO);
Log::setConsoleLogLevel(Log::Level::info);
#if __unix__
Log::setColoredOutput(true);
#endif
if(argc == 0) {
Log::fatal << "arg[0] is not set";
return 1;
}
Args args = parseArgs(argc, argv);
if(args.debugLog) {
Log::setConsoleLogLevel(Log::Level::TRACE);
Log::addLogfile("log.txt", Log::Level::TRACE);
Log::debug << "debug Log enabled";
Log::setConsoleLogLevel(Log::Level::trace);
Log::addLogfile("log.txt", Log::Level::trace);
qDebug() << "debug Log enabled";
}
Log::info << "Hello, World!";
Log::note << "Using Locale: " << QLocale().name().toStdString();
qInfo() << "Hello, World!";
qInfo() << "Using Locale: " << QLocale().name();
std::string base = getBaseString(argv);
Log::info << "appbase: " << base;
Matcher::setPathBase(base);
if(args.access) {
auto access = ClientAccess::find();
qInfo() << "Access: port=" << access->getPort() << " basicAuth=" << access->getBasicAuth();
return 0;
}
LolAutoAccept lolaa;
QApplication app(argc, argv);
QTranslator translator;
if(translator.load(QLocale().name(), QString::fromStdString(base + "ts"))) {
if(translator.load(QLocale().name(), ":/i18n")) {
app.installTranslator(&translator);
} else {
Log::warn << "translation not found";
qWarning() << "translation not found";
}
MainWindow win(lolaa);
MainWindow win;
while(!lolaa.init()) {
QMessageBox warn;
warn.setWindowTitle(MainWindow::tr("LoL-Auto-Accept"));
warn.setText(QMessageBox::tr("League of Legends Client not found!"));
warn.setInformativeText(QMessageBox::tr("Open the client and retry or close."));
warn.setStandardButtons(QMessageBox::Retry | QMessageBox::Close);
warn.setDefaultButton(QMessageBox::Retry);
warn.setIcon(QMessageBox::Warning);
if(warn.exec() == QMessageBox::Close) {
Log::stop();
return 0;
}
}
win.show();
int ret = app.exec();
Log::stop();
return ret;
}
}

View File

@ -1,98 +1,238 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QApplication>
#include <QTimer>
#include <QMessageBox>
#include <Log.h>
static void applySetting(const Config::StageConfig& sc, StageSettings* ss) {
ss->setState(sc.enabled);
ss->setChampion(QString::fromStdString(sc.champ));
}
#include "loadingwindow.h"
#include "x11helper.h"
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->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();
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);
lolaa.setEnabled(rc.enabledAutoAccept, LolAutoAccept::State::LOBBY);
applySetting(rc.prepick, ui->prepickstage);
applySetting(rc.ban, ui->banstage);
applySetting(rc.pick, ui->pickstage);
ui->enableSmiteWarning->setChecked(rc.enabledSmiteWarn);
lolaa.setSmiteWarn(rc.enabledSmiteWarn);
ui->enableAutoWrite->setChecked(rc.enabledAutoWrite);
ui->autoWriteText->setText(rc.autoWriteText);
lolaa.setAutoWriteText(rc.enabledAutoWrite, rc.autoWriteText);
resizeEvent(nullptr);
lwin->show();
}
MainWindow::~MainWindow() {
lolaa.stop();
conf.save();
delete ui;
delete this->ui;
}
void MainWindow::closeEvent([[maybe_unused]] QCloseEvent* event) {
lolaa.stop();
conf.save();
}
void MainWindow::resetSaveTimer() {
saveTimer->start();
qDebug() << "resetTimer";
}
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) {
Log::info << "mainswitch toggled: " << state;
qDebug() << "mainswitch toggled: " << 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()) {
Log::error << "League Client not found!";
qCritical() << "League Client not found!";
ui->statusbar->showMessage(tr("League of Legends Client not found!"));
ui->mainswitch->setCheckState(Qt::CheckState::Unchecked);
ui->mainswitch->setEnabled(true);
return;
}
lolaa.run();
ui->statusbar->showMessage(tr("Auto-Acceptor started!"));
} else {
lolaa.stop();
ui->statusbar->showMessage(tr("Auto-Acceptor stoped!"));
ui->mainswitch->setCheckState(Qt::CheckState::PartiallyChecked);
ui->mainswitch->setEnabled(false);
}
}
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);
conf.getConfig().enabledAutoAccept = state;
resetSaveTimer();
}
void MainWindow::pptoggled(bool state) {
Log::info << "enablePrePick checkbox toggled " << state;
lolaa.setEnabled(state, LolAutoAccept::State::PREPICK);
conf.getConfig().prepick.enabled = state;
void MainWindow::smitewarntoggled(bool state) {
if( loading ) return;
qDebug() << "smitewarn checkbox toggled " << state;
lolaa.setSmiteWarn(state);
conf.getConfig().enabledSmiteWarn = state;
resetSaveTimer();
}
void MainWindow::ppedited(const QString& b) {
Log::info << "prepick edited: " << b.toStdString();
lolaa.setChamp(b.toStdString(), LolAutoAccept::State::PREPICK);
conf.getConfig().prepick.champ = b.toStdString();
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::bantoggled(bool state) {
Log::info << "enableBan checkbox toggled " << state;
lolaa.setEnabled(state, LolAutoAccept::State::BAN);
conf.getConfig().ban.enabled = state;
void MainWindow::tabchanged(Position p, LolAutoAccept::State s) {
if( loading ) return;
qDebug() << "edited position: " << p << " state: " << (int) s;
lolaa.reload();
resetSaveTimer();
}
void MainWindow::banedited(const QString& b) {
Log::info << "ban edited: " << b.toStdString();
lolaa.setChamp(b.toStdString(), LolAutoAccept::State::BAN);
conf.getConfig().ban.champ = b.toStdString();
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::picktoggled(bool state) {
Log::info << "enablePick checkbox toggled " << state;
lolaa.setEnabled(state, LolAutoAccept::State::PICK);
conf.getConfig().pick.enabled = state;
void MainWindow::saveConfig() {
conf.save();
}
void MainWindow::pickedited(const QString& b) {
Log::info << "pick edited: " << b.toStdString();
lolaa.setChamp(b.toStdString(), LolAutoAccept::State::PICK);
conf.getConfig().pick.champ = b.toStdString();
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;
}
this->ui->mainswitch->setEnabled(true);
this->ui->mainswitch->setChecked(status == LolAutoAccept::Status::Running);
}

View File

@ -1,102 +0,0 @@
#include "matcher.h"
#include <Log.h>
#include "util.h"
std::string Matcher::pathbase;
void Matcher::setPathBase(const std::string& pa) {
pathbase = pa;
}
Matcher::Matcher(const std::string& filename) {
templ = cv::imread(pathbase + filename, cv::IMREAD_UNCHANGED); // unchanged so alpha channel does not get dropped
maskFromTemplate();
}
Matcher::Matcher(const cv::Mat& templ) {
templ.copyTo(this->templ);
maskFromTemplate();
}
Matcher::~Matcher() {}
void Matcher::setOffset(int32_t x, int32_t y) {
posx = x;
posy = y;
}
bool Matcher::Match::operator==(const Match& other) const {
return doesMatch == other.doesMatch && x == other.x && y == other.y && width == other.width && height == other.height;
}
bool Matcher::Match::operator!=(const Match& other) const {
return !(*this == other);
}
Matcher::Match Matcher::match(const cv::Mat& img) {
// remove Alpha channel from input
cv::Mat converted;
cv::cvtColor(img, converted, cv::COLOR_RGBA2RGB);
if(posx < 0 || posy < 0) {
return matchAll(converted);
}
return matchPos(converted);
}
// https://stackoverflow.com/a/43133263/4399859
Matcher::Match Matcher::matchAll(const cv::Mat& img) {
// create out mat
int result_cols = img.cols - templ.cols + 1;
int result_rows = img.rows - templ.rows + 1;
cv::Mat out(result_rows, result_cols, CV_32FC1);
Log::info << "match size: " << result_cols << " " << result_cols;
// match
cv::matchTemplate(img, templ, out, cv::TM_CCORR_NORMED, mask);
double minVal; double maxVal;
cv::Point minLoc; cv::Point maxLoc;
cv::minMaxLoc( out, &minVal, &maxVal, &minLoc, &maxLoc, cv::Mat());
Log::debug << "pixelcount: " << (templ.cols * templ.rows) << " minVal: " << minVal << " maxVal: " << maxVal << " minLoc: " << minLoc.x << " " << minLoc.y << " maxLoc: " << maxLoc.x << " " << maxLoc.y;
bool matched = maxVal > 0.95;
return {matched, maxLoc.x, maxLoc.y, templ.cols, templ.rows};
}
Matcher::Match Matcher::matchPos(const cv::Mat& img) {
cv::Mat matchpart = img(cv::Range(posy, posy + templ.rows), cv::Range(posx, posx + templ.cols));
// create out mat (only one pxl)
cv::Mat out(1, 1, CV_32FC1);
// match
cv::matchTemplate(matchpart, templ, out, cv::TM_CCORR_NORMED, mask);
float val = out.at<float>({0, 0});
bool matched = val > 0.95;
Log::debug << "val: " << val;
return {matched, posx, posy, templ.cols, templ.rows};
}
void Matcher::maskFromTemplate() {
if(templ.channels() == 4) {
// split channels
std::vector<cv::Mat> split;
cv::split(templ, split);
// template without alpha channel
cv::merge(std::vector(split.begin(), split.end()-1), templ);
// 3*alpha channel
cv::Mat alphaChannel = split.at(3);
alphaChannel.convertTo(alphaChannel, CV_8U);
cv::merge(std::vector(3, alphaChannel), mask);
}
}

View File

@ -5,7 +5,7 @@
MemoryImageCache::MemoryImageCache(size_t maxsize) : maxsize(maxsize) {}
void MemoryImageCache::addImage(cv::Mat 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));
if(it == cache.end()) {
// insert new
@ -19,7 +19,7 @@ void MemoryImageCache::addImage(cv::Mat img, const std::string& title, int type)
it->imageref = img;
}
cv::Mat 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));
if(it == cache.end()) {
return {};
@ -40,7 +40,7 @@ bool MemoryImageCache::CachedImage::operator<(const MemoryImageCache::CachedImag
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 img.type == type && img.title == title;
};

189
src/restclient.cpp Normal file
View File

@ -0,0 +1,189 @@
#include "restclient.h"
#include <iomanip>
#include <Log.h>
static size_t arrayWriteCallback(char* contents, size_t size, size_t nmemb, void* userdata) {
if (userdata) {
QByteArray* arr = (QByteArray*) userdata;
arr->append(contents, size * nmemb);
return size * nmemb;
}
return 0;
}
static void dump(const char* text, FILE* stream, unsigned char* ptr, size_t size) {
const unsigned int width = 0x40;
fprintf(stream, "%s, %10.10lu bytes (0x%8.8lx)\n", text, (unsigned long) size, (unsigned long) size);
for (size_t i = 0; i < size; i += width) {
fprintf(stream, "%4.4lx: ", (unsigned long) i);
for (size_t c = 0; (c < width) && (i + c < size); c++) {
/* check for 0D0A; if found, skip past and start a new line of output */
fprintf(stream, "%c", (ptr[i + c] >= 0x20) && (ptr[i + c] < 0x80) ? ptr[i + c] : '.');
}
fputc('\n', stream); /* newline */
}
fflush(stream);
}
static int my_trace(CURL* handle, curl_infotype type, char* data, size_t size, void* userp) {
const char* text;
(void) handle; /* prevent compiler warning */
(void) userp;
switch (type) {
case CURLINFO_TEXT:
fprintf(stderr, "== Info: %s", data);
/* FALLTHROUGH */
default: /* in case a new one is introduced to shock us */
return 0;
case CURLINFO_HEADER_OUT:
text = "=> Send header";
break;
case CURLINFO_DATA_OUT:
text = "=> Send data";
break;
case CURLINFO_HEADER_IN:
text = "<= Recv header";
break;
case CURLINFO_DATA_IN:
text = "<= Recv data";
break;
case CURLINFO_SSL_DATA_OUT:
case CURLINFO_SSL_DATA_IN:
return 0;
}
dump(text, stderr, (unsigned char*) data, size);
return 0;
}
RestClient::RestClient(const QString& base) : baseurl(base) {
curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, arrayWriteCallback);
}
RestClient::~RestClient() {
curl_easy_cleanup(curl);
curl = nullptr;
}
RestClient::WebException::WebException(CURLcode c) : curlresponse(c) {
}
QByteArray RestClient::requestRaw(const QString& url, Method m, const QString& data) {
if (!curl) return {};
QString requrl = baseurl + url;
const QByteArray reqArr = requrl.toLocal8Bit();
curl_easy_setopt(curl, CURLOPT_URL, reqArr.data());
const QByteArray basicArr = basicauth.toLocal8Bit();
curl_easy_setopt(curl, CURLOPT_USERPWD, basicArr.data());
curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
if (disableCertCheck) {
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_POSTFIELDS, NULL);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
// curl header
struct curl_slist* headerlist = NULL;
headerlist = curl_slist_append(headerlist, "Accept: application/json");
const QByteArray dataArr = data.toLocal8Bit();
switch (m) {
default:
case Method::GET:
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); break;
case Method::POST:
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, dataArr.data());
if(!dataArr.isEmpty()) {
headerlist = curl_slist_append(headerlist, "Content-Type: application/json");
}
break;
case Method::PUT:
case Method::PATCH:
case Method::DELETE:
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");
break;
}
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);
if (headerlist) {
curl_slist_free_all(headerlist);
}
// Check for errors
if (res != CURLE_OK) {
if (res == CURLE_HTTP_RETURNED_ERROR) {
long responsecode = -1;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &responsecode);
qWarning() << "API request failed: " << baseurl << " " << url << " -> " << responsecode;
} else {
qWarning() << "API request failed: " << baseurl << " " << url << " " << curl_easy_strerror(res);
if(res == CURLE_COULDNT_CONNECT) {
throw WebException(res);
}
}
return {};
}
return ba;
}
QJsonDocument RestClient::request(const QString& url, Method m, const QString& data) {
QByteArray arr = requestRaw(url, m, data);
if (arr.isEmpty()) return {};
QJsonParseError err;
QJsonDocument parsed = QJsonDocument::fromJson(arr, &err);
if (parsed.isNull() || err.error != QJsonParseError::NoError) {
qCritical() << "API Json parse error " << err.errorString() << " offset: " << err.offset;
return {};
}
return parsed;
}
void RestClient::enableDebugging(bool enabled) {
// enable debugging
if(enabled) {
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, my_trace);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
} else {
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, NULL);
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;
}

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