#include "lolautoaccept.h" #include #include #include LolAutoAccept::Stage::Stage() {} LolAutoAccept::Stage::~Stage() {} LolAutoAccept::LolAutoAccept(Config::RootConfig& config, DataDragon& dd, onfailed_func fail, onposchange_func onposch, onruneschange_func onrunch) : config(config), dd(dd), onPoschange(onposch), onRuneschange(onrunch), onFailed(fail) { std::lock_guard lock(stagesMutex); stages.resize(3); // accept, ban, pick } LolAutoAccept::~LolAutoAccept() { stopJoinThread(); } void LolAutoAccept::setChamps(const std::vector& champs, State s) { if(s == State::LOBBY || s >= State::PICK) { Log::warn << "setChamps() called on invalid State"; return; } Log::debug << "LolAutoAccept::setChamps"; { std::lock_guard lock(stagesMutex); stages.at((int) s).champids = champs; stages.at((int) s).currentOffset = 0; } Log::info << "set champs on state: " << (int) s << " count: " << champs.size(); } void LolAutoAccept::setEnabled(bool b, State s) { if(s >= State::PICK) { Log::warn << "setEnabled() called on invalid State"; return; } std::lock_guard lock(stagesMutex); stages.at((int) s).enabled = b; } void LolAutoAccept::setSmiteWarn(bool b) { smiteWarnEnabled = b; } bool LolAutoAccept::init() { if(clientapi) return true; auto ca = ClientAccess::find(); if(ca) { clientapi = std::make_shared(*ca.get()); auto selfinfo = clientapi->getSelf(); Log::info << "selfinfo: gameName: " << selfinfo.gameName << " name: " << selfinfo.name << " summonerid: " << selfinfo.summonerid << " statusMessage: " << selfinfo.statusMessage << " puuid: " << selfinfo.puuid << " level: " << selfinfo.level; } return (bool) clientapi; } void LolAutoAccept::run() { // make sure its not running stopJoinThread(); if(!clientapi) return; // no client api lolaathread = std::thread(&LolAutoAccept::innerRun, this); } void LolAutoAccept::stop() { shouldrun = false; } void LolAutoAccept::reload() { Log::note << "reload LolAutoAccept"; if(currentPositionSet) loadPosition(currentPosition); } const std::vector& LolAutoAccept::getRuneAspekts() { if(runeaspekts.empty()) { if(clientapi) { runeaspekts = clientapi->getAllRuneAspekts(); Log::info << "Loaded " << runeaspekts.size() << " rune aspekts"; } } return runeaspekts; } const std::vector& LolAutoAccept::getRuneStyles() { if(runestyles.empty()) { if(clientapi) { runestyles = clientapi->getAllRuneStyles(); Log::info << "Loaded " << runestyles.size() << " rune styles"; } } return runestyles; } void LolAutoAccept::applyRunes() { nextApplyRunes = true; } void LolAutoAccept::setOnRuneChangeFunc(onruneschange_func on) { onRuneschange = on; } void LolAutoAccept::setAutoWriteText(bool enabled, const std::string& text) { autoWriteTextEnabled = enabled; autoWriteTextDone = false; autoWriteText = text; } void LolAutoAccept::stopJoinThread() { stop(); if(lolaathread.joinable()) { lolaathread.join(); } resetAllOffsets(); } void LolAutoAccept::innerRun() { shouldrun = true; try { auto convs = clientapi->getAllConversations(); Log::info << "got " << convs.size() << " conversations"; while(shouldrun) { uint32_t extrasleep = 800; auto start = std::chrono::high_resolution_clock::now(); auto phase = clientapi->getGameflowPhase(); Log::info << "current Gameflowphase: " << phase; // do processing if(phase == ClientAPI::GameflowPhase::LOBBY) { resetAllOffsets(); resetRunes(); } else if(phase == ClientAPI::GameflowPhase::MATCHMAKING) { extrasleep = 200; resetAllOffsets(); resetRunes(); } else if(phase == ClientAPI::GameflowPhase::READYCHECK) { if(stages.at(0).enabled) { // auto accept enabled auto state = clientapi->getReadyCheckState(); Log::info << "readychack state: " << state; if(state == ClientAPI::ReadyCheckState::INPROGRESS) { Log::info << "auto accepting"; clientapi->acceptMatch(); } } resetAllOffsets(); extrasleep = 0; // no extra sleep } else if(phase == ClientAPI::GameflowPhase::CHAMPSELECT) { champSelect(); extrasleep = 0; // no extra sleep } else if(phase == ClientAPI::GameflowPhase::INPROGRESS) { extrasleep = 30000; // 30s bonus sleep resetAllOffsets(); resetRunes(); } else if(phase == ClientAPI::GameflowPhase::ENDOFGAME) { extrasleep = 2000; // 2 s bonus sleep resetAllOffsets(); resetRunes(); } else if(phase == ClientAPI::GameflowPhase::PREENDOFGAME) { extrasleep = 4000; // 4 s bonus sleep resetAllOffsets(); resetRunes(); } else if(phase == ClientAPI::GameflowPhase::WAITINGFORSTATS) { extrasleep = 4000; // 4 s bonus sleep resetAllOffsets(); resetRunes(); } else if(phase == ClientAPI::GameflowPhase::NONE) { extrasleep = 10000; // 10 s bonus sleep - no lobby resetAllOffsets(); resetRunes(); } auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration 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)); } } catch(RestClient::WebException& e) { Log::error << "WebException catched: " << e.curlresponse; if(e.curlresponse == CURLE_COULDNT_CONNECT) { // free clientapi clientapi.reset(); // disable this thread shouldrun = false; // notify the ui if(onFailed) { onFailed(); } } } } void LolAutoAccept::resetAllOffsets() { for(Stage& stage : stages) { stage.currentOffset = 0; } currentPosition = Position::INVALID; currentPositionSet = false; lastPickedChamp = 0; chatid.clear(); autoWriteTextDone = false; } void LolAutoAccept::resetRunes() { if(onRuneschange) { onRuneschange({}); } nextApplyRunes = false; } void LolAutoAccept::applyConfigToStage(Stage& stage, const Config::StageConfig& stageconf) { stage.champids = dd.resolveChampIDs(stageconf.champs); stage.enabled = stage.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 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 Log::warn << "no champ left at stage: " << (int) s; Log::warn << "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 Log::warn << "no champ left at stage: " << (int) s; Log::warn << "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) { Log::info << "player " << player.cellID << " @ " << 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 actions) { ownactions_t out; for(const auto& it : actions) { if(it.actorCellID == cellid) { Log::debug << "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 filter) { for(auto it : ownactions) { if(it.type == type) { Log::info << 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) { // try to prepick a champion Log::info << "try to pick champ: " << champid; if(!clientapi->setChampSelectAction(it.id, champid, complete)) { nextChampOfState(s); } return; } } } } int32_t LolAutoAccept::getBestRunePage(const std::vector& pages) { Log::debug << "searching RunePages: " << pages.size(); for(uint32_t i = 0; i < pages.size(); ++i) { Log::debug << 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.substr(0, 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& 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(); int32_t cellid = session.localPlayerCellId; // find own cellid info const ClientAPI::ChampSelectCell* me = nullptr; uint32_t pickedChamp = 0; for(const auto& it : session.myTeam) { if(it.cellID == cellid) { me = ⁢ pickedChamp = it.championID; break; } } Position pos = me ? me->position : Position::INVALID; // check if runes need adjustment if(pickedChamp != lastPickedChamp) { Log::info << "picked champ changed from: " << lastPickedChamp << " to: " << pickedChamp; lastPickedChamp = pickedChamp; // update runes if(onRuneschange) { auto champinfo = blitzapi.getChampionInfo(pickedChamp, pos); // TODO: add detection for enemy champ Log::info << "champinfo aquired: " << champinfo.runepage; onRuneschange(champinfo.runepage); } } // reload config based on position if changed if(pos != currentPosition) { Log::note << "LolAutoAccept reloading config for position: " << pos << " because it was: " << currentPosition; loadPosition(pos); if(onPoschange) { onPoschange(pos); } } Log::debug << "cellid: " << cellid << " position: " << pos << " counter: " << session.counter << " timer: timeleftip: " << session.timer.adjustedTimeLeftInPhase << " totaltimephase: " << session.timer.totalTimeInPhase; // find actions for own cell auto ownactions = getOwnActions(cellid, session.actions); Log::debug << "ownactions: " << ownactions.size(); // try to prepick champ Log::info << "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); } } if(nextApplyRunes) { applyRunes_internal(pickedChamp, pos); } // check for autowriteText if(autoWriteTextEnabled && !autoWriteTextDone && !autoWriteText.empty()) { const std::string& chatid = getChatid(); if(!chatid.empty()) { clientapi->sendMessage(chatid, autoWriteText); autoWriteTextDone = true; } } } void LolAutoAccept::smiteWarning(const std::vector& cells) { uint32_t smiteCount = 0; for(const ClientAPI::ChampSelectCell& member : cells) { Log::info << "position: " << member.position << " spells: " << member.spell1Id << " " << member.spell2Id; smiteCount += (member.spell1Id == 11 || member.spell2Id == 11); } if(smiteCount != 1) { // check timeout std::chrono::time_point currenttime = std::chrono::system_clock::now(); if((currenttime - lastMessageSent) < std::chrono::seconds(2)) { return; } Log::info << "smite warning: " << smiteCount; const std::string& chatid = getChatid(); if(!chatid.empty()) { clientapi->sendMessage(chatid, "smite"); lastMessageSent = currenttime; } } } const std::string& LolAutoAccept::getChatid() { if(chatid.empty()) { std::vector convs = clientapi->getAllConversations(); Log::info << "got " << convs.size() << " conversations"; for(const ClientAPI::Conversation& conv : convs) { Log::info << " id: " << conv.id << " type: " << conv.type << " name: " << conv.name << " gameName: " << conv.gameName; if(conv.type == "championSelect" && conv.name.empty()) { Log::info << "groupchat found"; chatid = conv.id; return chatid; } } } return chatid; //might be empty string } void LolAutoAccept::applyRunes_internal(uint32_t champid, Position pos) { nextApplyRunes = false; Log::note << "apply runes"; // get recommended runes and stuff auto champinfo = blitzapi.getChampionInfo(champid, pos); Log::info << "fetched champion info runes: " << champinfo.runepage; // choose page auto pages = clientapi->getAllRunePages(); // check for page that allready contains the settings int32_t choosepage = getMatchingRunePage(champinfo.runepage, pages); if(choosepage != -1) { ClientAPI::RunePage& page = pages.at(choosepage); Log::info << "matching runepage found, selecting: " << page.id << " " << page.name; if(clientapi->selectRunePage(page.id)) { return; } Log::warn << "selecting runepage failed"; } // find page to replace int32_t pageoffset = getBestRunePage(pages); if(pageoffset < 0) { Log::warn << "no rune page found!"; return; } // replace the page ClientAPI::RunePage& rp = pages.at(pageoffset); Log::info << "replace runepage id: " << rp.id << " old-name: " << rp.name; // resolve champion name for the runepage name ClientAPI::RunePage newpage; auto champ = dd.getChampByID(champid); const std::string champname = champ ? champ->name : std::to_string(champid); newpage.id = rp.id; newpage.name = "AA: " + champname + " " + toShortString(pos); // TODO: make role "Utility" resolve to "Support"? newpage.runepage = champinfo.runepage; clientapi->editRunePage(newpage); //select runepage if(!rp.isCurrent) { Log::info << "page is not selected, selecting now (id: " << newpage.id << ')'; clientapi->selectRunePage(newpage.id); } Log::info << "runepage done"; }