Arcana / node-dota2

A node-steam plugin for Dota 2.
MIT License
546 stars 191 forks source link

Bot not leaving postgame chat channel #663

Closed shturmann closed 2 years ago

shturmann commented 5 years ago

Hello, sorry for maybe a stupid question, but I was stumped.

I found a bot that works on binary logs, but I don’t quite understand how it works, because I’m not even a Node developer :)

The bot successfully enters Steam, Dota2, creates a lobby, sends invites and starts the game. But after that, it stops responding to the same event that should restart the creation of an invite-start lobby. It seems to me that this is due to the fact that the bot is "stuck" in the post-game chat channel, remaining on its listening, and the logic of the code considers that it is still busy.

Can you even tell me if I'm digging in that direction?

Here is the logic of the bot with my idiotic attempts to understand what is happening in it.

var steam = require("steam"),
    util = require("util"),
    fs = require("fs"),
    crypto = require("crypto"),
    dota2 = require("dota2"),
    MySQLEvents = require('mysql-events'),
    MySQL = require('mysql'),
    bots = [];

// Load config
global.config = (fs.existsSync("./local.config.js")) ? require("./local.config") : require("./config");

var mysql = MySQL.createConnection(global.config.mysql),
    mysqlEventWatcher = MySQLEvents(global.config.mysql);

// Select free bot from bot array
var selectFreeBot = function(cb){
    if(bots.length > 0){
        console.log(1);
        for(var i = 0; i < bots.length; i++){
            if(bots[i].free){
                cb(bots[i]);
                util.log(cb(bots[i]));
                return true;
            }
        }
        util.log('All bots busy. Waiting for free bot..');
        setTimeout(function(){
            selectFreeBot(cb);
        }, 10000);
    } else {
        console.log(2);
        util.log('List of bots empty');
        return false;
    }
};

// Bot initialization with config
var initBot = function(cfg){
    console.log(3);
    var bot = {
        id: cfg.id,
        config: {
            steam_name: cfg.steam_name,
            steam_user: cfg.steam_user,
            steam_pass: cfg.steam_pass,
            steam_guard_code: cfg.steam_guard_code,
            two_factor_code: cfg.two_factor_code
        },
        currentLobby: {
            id: null
        },
        free: true
    };

    bot.steamClient = new steam.SteamClient();
    bot.steamUser = new steam.SteamUser(bot.steamClient);
    bot.Dota2 = new dota2.Dota2Client(bot.steamClient, true);

    // Login, only passing authCode if it exists
    var logOnDetails = {
        "account_name": bot.config.steam_user,
        "password": bot.config.steam_pass
    };
    if (bot.config.steam_guard_code)
        logOnDetails.auth_code = bot.config.steam_guard_code;
    if (bot.config.two_factor_code)
        logOnDetails.two_factor_code = bot.config.two_factor_code;

    try {
        var sentry = fs.readFileSync('sentry');
        if (sentry.length)
            logOnDetails.sha_sentryfile = sentry;
    } catch (beef) {
        console.log(4);
        util.log('[Bot #' + bot.id + '] ' + "Cannae load the sentry. " + beef);
    }

    // Steam client handlers
    bot.steamClient.on('connected', function() {
        console.log(5);
        util.log('[Bot #' + bot.id + '] ' + 'Steam connected');
        bot.steamUser.logOn(logOnDetails);
    });

    bot.steamClient.on('logOnResponse', function (logonResp){
        console.log(6);
        if (logonResp.eresult == steam.EResult.OK) {
            console.log(7);
            util.log('[Bot #' + bot.id + '] ' + "Logged on");
            bot.Dota2.launch();
            bot.Dota2.on("ready", function() {
                console.log(8);
                console.log('[Bot #' + bot.id + '] ' + "Node-dota2 ready");
                var enoughPeople = false,
                    starting = false;
                var start = function () {
                    starting = true;
                    var remainingSeconds = global.config.remaining_before_match;
                    (function tick() {
                        console.log(9);
                        if (enoughPeople) {
                            console.log(10);
                            if (remainingSeconds > 0) {
                                console.log(11);
                                util.log('[Bot #' + bot.id + '] ' + "Starting in " + remainingSeconds + " seconds");
                                setTimeout(tick, 1000);
                                remainingSeconds--;
                            } else {
                                console.log(12);
                                // Launch lobby match
                                bot.Dota2.launchPracticeLobby(function (err, data) {
                                    console.log(13);
                                    util.log('[Bot #' + bot.id + '] ' + 'Game started');

                                    // Save status 'game started' to database
                                    mysql.query('update lobby set status = 1 ' +
                                                'where id = ' + bot.currentLobby.id, function(err, res){
                                        console.log(14);
                                        if(err) throw err;

                                        // Leave lobby when match started
                                        // bot.Dota2.leavePracticeLobby(function (err, body) {
                                        //     console.log('leavePracticeLobby///////////////////////////////////////////////////////////////////////////////');
                                        //     bot.free = true;
                                        //     bot.currentLobby.id = null;
                                        //     util.log('[Bot #' + bot.id + '] ' + 'Bot leaved');
                                        //     // bot.Dota2.abandonCurrentGame();
                                        // });
                                    });
                                });
                            }
                        } else {
                            console.log(15);
                            starting = false;
                            util.log('[Bot #' + bot.id + '] ' + "Aborting start: someone left");
                        }
                    })();
                };

                // Update lobby event handler
                bot.Dota2.on("practiceLobbyUpdate", function(lobby) {
                    console.log(16);
                    enoughPeople = (lobby.members.filter(function(e){return e.team === 0 || e.team === 1}).length >= global.config.match_users);
                    if(enoughPeople && !starting) {
                        console.log(17);
                        start();
                    }
                });
            });
            bot.Dota2.on("unready", function () {
                console.log(18);
                console.log('[Bot #' + bot.id + '] ' + "Node-dota2 unready.");
            });
        }
    });

    bot.steamClient.on('loggedOff', function (eresult){
        console.log(19);
        util.log('[Bot #' + bot.id + '] ' + "Logged off from Steam");
    });

    bot.steamClient.on('error', function (error){
        console.log(20);
        util.log('[Bot #' + bot.id + '] ' + "Connection closed by server: " + error);
        //bot.steamClient.connect();
    });

    bot.steamClient.on('servers', function (servers){
        console.log(21);
        util.log('[Bot #' + bot.id + '] ' + "Received servers");
        fs.writeFile('servers', JSON.stringify(servers), function(err){
            console.log(22);
            if (err){
                console.log(23);
                if (this.debug)
                    util.log('[Bot #' + bot.id + '] ' + "Error writing ");
            } else {
                console.log(24);
                if (this.debug)
                    util.log("");
            }
        });
    });

    bot.steamUser.on('updateMachineAuth', function(sentry, callback) {
        console.log(25);
        var hashedSentry = crypto.createHash('sha1').update(sentry.bytes).digest();
        fs.writeFileSync('sentry', hashedSentry);
        util.log('[Bot #' + bot.id + '] ' + "sentryfile saved");
        callback({
            sha_file: hashedSentry
        });
    });

    // Connect to Steam
    bot.steamClient.connect();

    // Add bot to array
    bots.push(bot);
};

// Start database watcher
var startDBWatcher = function(){
    console.log(26);
    util.log('Init database watcher');

    // Create database watcher for table 'lobby'
    mysqlEventWatcher.add(global.config.mysql.database + '.lobby', function (oldRow, newRow, event) {
        console.log(27);
        util.log("Init watcher on table `lobby`");
        // Row inserted
        if (oldRow === null) {
            console.log(28);
            // Select bot
            selectFreeBot(function (bot) {
                console.log(29);
                bot.free = false; // set bot as busy
                util.log('[Bot #' + bot.id + '] ' + 'Started');
                var lobbyId = newRow.fields.id,
                    lobbyName = 'Lobby_' + lobbyId,
                    lobbyPassword = generatePassword();
                bot.currentLobby.id = lobbyId;
                // Save lobby name and password to database, and status 'in process'
                mysql.query('update `lobby` set `name` = "' + lobbyName + '", ' +
                            '`password` = "' + lobbyPassword + '", ' +
                            'status = 2 ' +
                            'where `id` = ' + lobbyId, function (error, results, fields) {
                    if (error) throw error;

                    // Leave previous lobby for this bot and start new
                    bot.Dota2.leavePracticeLobby(function(err, data){
                        console.log(30);
                        if(!err) {
                            console.log(31);
                            // bot.Dota2.abandonCurrentGame(function(err, body){});

                            var createLobby = function(prop){
                                console.log(32);
                                // Create lobby with properties
                                bot.Dota2.createPracticeLobby(properties, function(err, data){
                                    console.log(properties, data, 'practice lobby///////////////////////////////////////')
                                    console.log(33);
                                    if (err) {
                                        console.log(34);
                                        bot.free = true;
                                        bot.currentLobby.id = null;
                                        util.log('[Bot #' + bot.id + '] ' + err + ' - ' + JSON.stringify(data));
                                    } else {
                                        console.log(35);
                                        util.log('[Bot #' + bot.id + '] ' + 'Lobby created');

                                        // For some reason the bot automatically joins the first slot. Kick him.
                                        bot.Dota2.practiceLobbyKickFromTeam(bot.Dota2.AccountID);

                                        // Select steam-id's from database
                                        mysql.query('select u.`steam-id` as stid ' +
                                            'from `lobby_user` lu ' +
                                            'join `user` u on u.`id` = lu.`user_id` ' +
                                            'where lu.`lobby_id` = ' + lobbyId, function (error, results, fields){
                                            if (error)
                                                throw error;

                                            // Send invites
                                            util.log('[Bot #' + bot.id + '] ' + 'Inviting users..');
                                            for(var i = 0; i < results.length; i++){
                                                bot.Dota2.inviteToLobby(results[i].stid);
                                            }
                                        });

                                        // Lobby timeout
                                        if(global.config.lobby_timeout !== 'none') {
                                            console.log(36);
                                            setTimeout(function () {
                                                console.log(37);
                                                // Save lobby status 'timeout lobby'
                                                mysql.query('update lobby set status = 3 ' +
                                                            'where id = ' + lobbyId, function(err, res){
                                                    if(err) throw err;

                                                    // Leave lobby
                                                    bot.Dota2.leavePracticeLobby(function (err, body) {
                                                        console.log(38);
                                                        console.log('leavePracticeLobby body/////////////////////////');
                                                        bot.free = true;
                                                        bot.currentLobby.id = null;
                                                        console.log(err, body, '////////////////////////////////////////');
                                                        bot.Dota2.abandonCurrentGame(function (err, body) {});
                                                        util.log('[Bot #' + bot.id + '] ' + 'Bot leave lobby by timeout');
                                                        bot.Dota2.exit();
                                                        // bot.Dota2.leaveAbandonedChat();
                                                    });

                                                });
                                            }, global.config.lobby_timeout * 300);
                                        }
                                    }
                                });
                            };

                            if(newRow.fields.settings_id){
                                console.log(40);

                                // Select lobby settings from database (leagueid)
                                mysql.query('select * from settings ' +
                                            'where id = ' + newRow.fields.settings_id, function (error, results, fields) {
                                    console.log(41);
                                    if (error) throw error;
                                    var properties = {
                                        leagueid: results.fields.leagueid
                                    };
                                    properties.game_name = lobbyName;
                                    properties.pass_key = lobbyPassword;
                                    createLobby(properties);
                                });
                            } else {
                                console.log(42);
                                // Default settings
                                var properties = global.config.lobby_preset;
                                properties.game_name = lobbyName;
                                properties.pass_key = lobbyPassword;
                                createLobby(properties);
                            }
                        } else {
                            console.log(43);
                            bot.free = true;
                            bot.currentLobby.id = null;
                        }
                    });
                });
            });
        }
    },
    );

    // Create database watcher for table 'bot'
    mysqlEventWatcher.add(global.config.mysql.database + '.bot', function (oldRow, newRow, event){
        util.log("Init watcher on table `bot`");
        console.log(44);
        // Row inserted
        if (oldRow === null) {
            console.log(45);
            initBot(newRow.fields);
        }
    });
};

// Stop database watcher
var stopDBWatcher = function () {
    console.log(46);
    mysqlEventWatcher.stop();
};

// Start app
(function init(){
    console.log(47);
    util.log("START");
    mysql.connect();
    // Init bots
    mysql.query('select * from bot', function (error, results, fields){
        console.log(48);
        if (error) throw error;
        util.log('Found ' + results.length + ' bots accounts');
        for(var i = 0; i < results.length; i++){
            initBot(results[i]);
        }

        // Start database watcher
        setTimeout(startDBWatcher, 5000);
    });

})();
process.on('SIGINT', function() {
    // Bots Steam logout
    console.log(49);
    setTimeout(function(){
        console.log(50);
        for(var i = 0; i < bots.length; i++){
            bots[i].Dota2.exit();

        }
    }, 1000);
    setTimeout(function(){
        console.log(51);
        for(var i = 0; i < bots.length; i++){
            bots[i].steamClient.disconnect();
        }
    }, 3000);
    setTimeout(function(){
        console.log(52);
        stopDBWatcher();
        mysql.end();
    }, 4000);
    setTimeout(function(){
        console.log(53);
        util.log("STOP");
        process.exit();
    }, 5000);
});

// Function generate password for lobby
var generatePassword = function(){
    console.log(54);
    var code = "";
    // Omitted characters that can look like others
    var possibleChars = ["B", "D", "E", "F", "G", "H", "C", "J", "K", "L", "M", "N", "P", "Q",
                         "R", "S", "T", "W", "X", "Y","Z", "2", "3", "5", "6", "7", "8", "9"];
    for (var i = 0; i < 20; i++) {
        code += possibleChars[Math.floor(Math.random() * possibleChars.length)];
    }
    return code;
};
shturmann commented 5 years ago

Even if i do bot.Dota2.exit(); in

bot.Dota2.leavePracticeLobby(function (err, body) {
                                                        console.log(38);
                                                        console.log('leavePracticeLobby body/////////////////////////');
                                                        bot.free = true;
                                                        bot.currentLobby.id = null;
                                                        console.log(err, body, '////////////////////////////////////////');
                                                        bot.Dota2.abandonCurrentGame(function (err, body) {});
                                                        util.log('[Bot #' + bot.id + '] ' + 'Bot leave lobby by timeout');
                                                        bot.Dota2.exit();
                                                        // bot.Dota2.leaveAbandonedChat();
                                                    });

bot still listening postgame channel O_O

27 Jun 13:19:59 - Sending match CMsgAbandonCurrentGame request
27 Jun 13:19:59 - GC not ready, please listen for the 'ready' event.
27 Jun 13:19:59 - [Bot #1] Bot leave lobby by timeout
27 Jun 13:19:59 - Exiting Dota 2
27 Jun 13:19:59 - Cache unsubscribed, 26199248896956068
27 Jun 13:20:08 - Chat channel PostGame_26199248896956068 has 1 person(s) online
27 Jun 13:20:08 - 76561198085031857 joined channel PostGame_26199248896956068
27 Jun 13:20:10 - Chat channel PostGame_26199248896956068 has 2 person(s) online

Crazy-Duck commented 5 years ago

Chat channels are separate from lobbies. Leaving a lobby does not disconnect you from the chat, you need to disconnect separately.