Compare commits

...

122 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
126 changed files with 7683 additions and 1766 deletions

7
.gitignore vendored
View File

@ -25,4 +25,11 @@ Makefile
AppDir/
*.AppImage
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

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

35
debian/changelog vendored Normal file
View File

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

17
debian/control vendored Normal file
View File

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

3
debian/lolautoaccept.install vendored Normal file
View File

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

4
debian/rules vendored Executable file
View File

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

1
debian/source/format vendored Normal file
View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 380 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

View File

@ -2,6 +2,7 @@
struct Args {
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=Utility;

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,98 +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
DEFINES += X11SUPPORT=1
LIBS += -lX11
$$PWD/lolautoaccept.png.commands = inkscape -z -e $$PWD/lolautoaccept.png -w 512 -h 512 $$PWD/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
appimg.depends = $$PWD/linuxdeploy-x86_64.AppImage $${TARGET} $$PWD/lolautoaccept.png
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 lolautoaccept.png -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 $$PWD/lolautoaccept.png
QMAKE_EXTRA_TARGETS += appimg linuxdeploy-x86_64.AppImage resources/lolautoaccept.png
QMAKE_CLEAN += $$PWD/linuxdeploy-x86_64.AppImage $$PWD/lolautoaccept.png
QMAKE_CLEAN += linuxdeploy-x86_64.AppImage resources/lolautoaccept.png
}
win32 {
INCLUDEPATH += $$PWD/../curl/include/
LIBS += $$PWD/../curl/lib/libbrotlicommon.a $$PWD/../curl/lib/libbrotlidec.a $$PWD/../curl/lib/libcrypto.a $$PWD/../curl/lib/libcurl.a $$PWD/../curl/lib/libcurl.dll.a $$PWD/../curl/lib/libgsasl.a $$PWD/../curl/lib/libidn2.a $$PWD/../curl/lib/libnghttp2.a $$PWD/../curl/lib/libnghttp3.a $$PWD/../curl/lib/libngtcp2.a $$PWD/../curl/lib/libngtcp2_crypto_openssl.a $$PWD/../curl/lib/libssh2.a $$PWD/../curl/lib/libssl.a $$PWD/../curl/lib/libz.a $$PWD/../curl/lib/libzstd.a
# to create the ico: convert -density 300 -define icon:auto-resize=256,128,96,64,48,32,16 -background none resources/lolautoaccept.svg resources/lolautoaccept.ico
RC_ICONS = resources/lolautoaccept.ico
}
# Default rules for deployment.
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

@ -17,10 +17,19 @@
<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 -->
<text x="48" y="786" fill="black" font-size="768px" clip-path="url(#text-circle-cutoff)" font-family="monospace">
<!-- 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>
</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" />

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

16
resources/res.qrc Normal file
View File

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

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

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

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

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

View File

@ -9,6 +9,7 @@ Args parseArgs(int argc, char** argv) {
while (1) {
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,81 +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);
QIcon icon(QString::fromStdString(base + "lolautoaccept.png"));
win.setWindowIcon(icon);
MainWindow win;
while(!lolaa.init()) {
QMessageBox warn;
warn.setWindowIcon(icon);
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