Truffle Pig Public

Finding all the tasty truffles for you! (Public Edition)

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Truffle Pig Public
// @namespace    Truffle Pig Public
// @version      0.9.1
// @description  Finding all the tasty truffles for you! (Public Edition)
// @author       Arimas
// @match        https://agma.io/
// @icon         https://www.google.com/s2/favicons?domain=agma.io
// @grant        none
// ==/UserScript==
 
(function() {
    'use strict';
 
var trufflePigPublic = {
    // Mass := cell-area * factor
    MASS_AREA_FACTOR: 0.0031828408,
 
    // How many times must a cell be bigger to eat another cell?
    CELL_EATING_FACTOR: 1.3, // TODO: Check!
 
    // Amount of mass at spawn. ATTENTION: The mass can be either this or less than this!
    MAX_START_SPAWN_MASS: 141,
 
    // How big must a cell be to pick up a coin? 126-134???
    COIN_PICKUP_MASS: 126,
 
    // If true, bot will automatically respawn when dead
    autoRespawn: true,
 
    // If true, mouse movements by the user will override the movement of the bot
    allowUserOverride: true,
 
    // Last time (in milliseconds) when the user controlled the bot ( = moved the mouse)
    userControlledAt: null,
 
    // If set to true, show debugging information. Do not change this after init!
    debug: false,
 
    // Int - current player position as it is used by the camera (midpoint of all my cells)
    x: null,
 
    // Int - current player position as it is used by the camera (midpoint of all my cells)
    y: null,
 
    // Is unknown so will be figured out while playing (prob main room borders could be detected  though)
    mapWidth: 0,
 
    mapHeight: 0,
 
    // Maximum number of cells (pieces) a player can have (we dont really know that due to diff. servers so have to learn it on the fly)
    maxCells: 64,
 
    // My cells - with x, y, mass, radius, area. NOTE: The smallest cell has index 0, the biggest has the highest index!
    cells: [],
 
    // My cells, but only temporary. Will be copied into the cells array once its filled
    tempCells: [],
 
    // Visible coins
    coins: [],
 
    // Coinbs, but only tewmporarytemporary. Will be copied into the coins array once its filled.
    tempCoins: [],
 
    // Int - current total player mass (incorrect when the player is in portals!)
    mass: 0,
 
    // Int - temporary current total player mass, will be copied to the mass property
    tempMass: 0,
 
    // Is the bot currently alive?
    alive: false,
 
    // URL of my skin
    skinUrl: null,
 
    // Zoom factor
    zoom: null,
 
    // If true bot doesnt do anything
    stopped: false,
 
    // Time when the bot was initialized
    startedAt: null,
 
    // Last time when the bot spawned
    spawnedAt: null,
 
    // Last time (in milliseconds) when we tried to split
    splitAt: null,
 
    // Counter for the main loop iterations
    iteration: 0,
 
    // Is the bot respawning right now? (This is a process that needs several seconds to complete)
    respawning: false,
 
    // Saves the official key bindings
    hotkeys: null,
 
    startCoins: 0,
 
    // Original drawImage() function
    originalDrawImage: null,
 
    /**
     * Start the bot
     */
    init: function() {
      var self = this;
      window.ventron = this;
 
      this.startedAt = new Date();
 
      this.skinUrl = this.getSkinUrl();
      if (this.skinUrl == 'https://agma.io/skins/0_lo.png') {
        alert('No skin chosen - bot does not work. Pick skin and reload page.');
        return;
      }
 
      if (this.debug) {
        var $crosshair = $('<div id="bot-crosshair" style="position: fixed; left: 50%; top: 50%; width: 4px; height: 4px; margin-left: -2px; margin-top: -2px; background-color: red; z-index: 999"></div>');
        $('body').append($crosshair);
      }
 
      setFixedZoom(true);
 
      var agmaSettings = JSON.parse(localStorage.getItem('settings'));
      if (agmaSettings.fixedZoomScale > 0.4) {
        alert('Please zoom out a bit.');
      }
 
        this.startCoins = this.getCoins();
 
      this.originalDrawImage = CanvasRenderingContext2D.prototype.drawImage;
      CanvasRenderingContext2D.prototype.drawImage = this.drawImage;
 
      $(document).mousemove(function(event) {
        // Synthetic events are those we create when moving the virtual mouse pointer
        if (! event.synthetic && self.allowUserOverride) {
          self.userControlledAt = Date.now();
        }
      });
 
      // If the shop close button is clicked, get and save my skin URL - it may have been changed
      $('#shopModalDialog .close').click(function() {
        setTimeout(function() {
          self.skinUrl = self.getSkinUrl();
        }, 100);
      });
 
      $('#chtbox').keydown(function(event) {
        if (event.keyCode == 13) {
          if (self.checkChatBox()) {
            $('#chtbox').val('');
          }
        }
      });
 
      this.hotkeys = JSON.parse(localStorage.getItem('hotkeys'));
 
      window.addEventListener('keypress', function(event)
      {
        // Do nothing if a menu is open
        if (document.getElementById('overlays').style.display !== 'none' || document.getElementById('advert').style.display !== 'none') {
          return;
        }
        // Ignore text input fields
        if (document.activeElement.type === 'text' || document.activeElement.type === 'password') {
          return;
        }
      });
 
      if (this.debug) {
        this.$debugOutput = $('<div style="position: fixed; left: 250px; top: 75px; z-index: 9999; color: #3e3e3e; pointer-events: none">');
        $('body').append(this.$debugOutput);
      }
 
      var originalRequest = window.requestAnimationFrame;
      window.requestAnimationFrame = function (callback) {
        var result = originalRequest.apply(this, arguments);
 
        self.mass = self.tempMass;
        self.cells = self.tempCells;
        self.coins = self.tempCoins;
        self.run.apply(self);
        self.tempMass = 0;
        self.tempCells = [];
        self.tempCoins = [];
 
        return result;
      };
      originalRequest(this.run.bind(this));
 
        let message = '🐷  Truffle Pig Public is ready! The truffles will be yours.';
        self.swal(
                'Truffle Pig Public Bot',
                message + '<br><br><b>ATTENTION</b>: Bot needs a skin to be put on.<br>  Bot works best on Solo AGF.<br> Type <i>/bot help</i> for help!<br> Bot cant split while you type in the chat!');
      console.log('%' + message, 'background-color: black; color: pink; font-weight: bold; padding:5px;');
 
    },
 
    /**
     * Main method. Once started, it is running in a never ending loop.
     */
    run: function() {
      var agmaSettings = JSON.parse(localStorage.getItem('settings'));
      this.zoom = agmaSettings.fixedZoomScale;
 
        if (this.stopped) {
            return;
        }
 
      if (this.isDeathPopupVisible()) {
        this.alive = false;
      }
 
      if (this.autoRespawn && ! this.alive) {
        this.respawn();
      }
 
      if (this.alive) {
 
 
 
 
          if (this.mass > 0 && this.mass < 0.75 * this.MAX_START_SPAWN_MASS && ! this.respawning &&
              (this.coins.length == 0 || this.mass < this.COIN_PICKUP_MASS)) {
              this.respawn(); // No return - respawn does not always work!!
          }
          if (this.spawnedAt !== null && Date.now() - this.spawnedAt > 30000 && this.coins.length == 0) {
               this.respawn();
          }
          if (this.spawnedAt !== null && Date.now() - this.spawnedAt > 120000) {
               this.respawn();
          }
 
          if (! this.collectCoin()) {
 
 
              if (this.cells.length < 16) {
                  self.macroSplit();
              }
 
                  let angle = null;
                  if (Date.now() - this.startedAt > 3 * 60 * 1000 && Date.now() - this.changedTargetAt > 1000) {
 
                       if (this.x < 0.03 * this.mapWidth) {
                        this.angle = this.getRandomInt(0 + 30,180 - 30);
                       }
                      if (this.x > 0.97 * this.mapWidth) {
                          this.angle = this.getRandomInt(180 + 30,359 - 30);
                      }
                      if (this.y < 0.03 * this.mapHeight) {
                          this.angle = this.getRandomInt(90 + 30,270 - 30);
                      }
                      if (this.y > 0.97 * this.mapHeight) {
                          this.angle = this.getRandomInt(270 + 30, 360 + 90 - 30) % 360;
                      }
                  }
              if (this.changedTargetAt === undefined || Date.now() - this.changedTargetAt > 3000 ||angle !== null) {
                  if (Date.now() - this.startedAt > 3 * 60 * 1000) {
 
                       if (this.x < 0.1 * this.mapWidth) {
                           this.angle = this.getRandomInt(0 + 30,180 - 30);
                       }
                       if (this.x > 0.9 * this.mapWidth) {
                           this.angle = this.getRandomInt(180 + 30,359 - 30);
                       }
                       if (this.y < 0.1 * this.mapHeight) {
                           this.angle = this.getRandomInt(90 + 30,270 - 30);
                       }
                       if (this.y > 0.9 * this.mapHeight) {
                           this.angle = this.getRandomInt(270 + 30, 360 + 90 - 30) % 360;
                       }
 
                  }
                   if (angle === null) {
                       angle = this.getRandomInt(1,359);
                   }
 
                  this.steerAngle(angle);
                  this.changedTargetAt = Date.now();
              }
          }
 
      }
 
      this.iteration++;
    },
 
    /**
     * This overwrites the original drawImage function. This allows us to get all the drawImage() calls,
     * with coordinates of the images, so we can get the position of things on the map, for instance of cells.
     */
    drawImage: function (image, sourceX, sourceY, sourceWidth, sourceHeight, targetX, targetY, targetWidth, targetHeight) {
      var self = window.ventron;
      var radius, mass;
 
      if (this.canvas.id === 'canvas') {
        // Detect myself (one of my cells)
        if (self.skinUrl && (image.src == self.skinUrl.replace('_lo.', '.') || image.src == self.skinUrl)) {
          self.alive = true;
          if (self.spawnedAt === null) {
            self.spawnedAt = Date.now();
          }
 
          radius = sourceWidth / 2;
          mass = self.getCellMass(radius);
 
          var x = parseInt(sourceX + radius);
          var y = parseInt(sourceY + radius);
 
            if (x > self.mapWidth) {
                self.mapWidth = x;
            }
            if (y > self.mapHeight) {
                self.mapHeight = y;
            }
 
          self.tempMass += mass;
 
          self.cloaked = (this.globalAlpha < 1);
 
          self.tempCells.push({ x: x, y: y, mass: mass, radius: radius });
 
          self.x = 0;
          self.y = 0;
          self.tempCells.forEach(function(cell) {
            self.x += cell.x;
            self.y += cell.y;
          });
          self.x /= self.tempCells.length;
          self.y /= self.tempCells.length;
 
            if (self.tempCells.length > self.maxCells) {
                self.maxCells = self.tempCells.length
            }
 
          if (self.debug) {
            //var $crosshair = $("#bot-crosshair");
            //$("#bot-crosshair").css('left', self.getScreenPosX(sourceX));
            //$("#bot-crosshair").css('top', self.getScreenPosY(sourceY));
 
            self.$debugOutput.text('x: ' + parseInt(self.x) + ', y: ' + parseInt(self.y) + ', mass: ' + parseInt(self.tempMass) + ', cells: ' + self.tempCells.length);
          }
        }
 
        // Detect coin
        if ((image.src == 'https://agma.io/skins/objects/9_lo.png?v=1' || image.src == 'https://agma.io/skins/objects/9.png?v=1')) {
          let matrix = this.getTransform() ;
            self.tempCoins.push({x: self.getGamePosX(matrix.e), y: self.getGamePosY(matrix.f)});
        }
      }
 
      return self.originalDrawImage.apply(this, arguments);
    },
 
      collectCoin: function() {
          // Eat coins
          if (this.coins.length > 0 && this.mass > this.COIN_PICKUP_MASS) {
              // Find the closest coin
              let coin = null, minDistance = Number.MAX_VALUE;
              this.coins.forEach(function(inspectedCoin) {
                  let distance = self.getDistance(self.x, self.y, inspectedCoin.x, inspectedCoin.y);
                  if (distance < minDistance) {
                      minDistance = distance;
                      coin = inspectedCoin;
                  }
              });
 
              if (minDistance < 100 && this.cells.length > 1) {
                  self.merge();
              } else {
                  let myScreenX = this.getScreenPosX(this.x), myScreenY = this.getScreenPosY(this.y);
                  let screenDistance = self.getDistance(myScreenX, myScreenY, this.getScreenPosX(coin.x), this.getScreenPosX(coin.y));
                  self.steer(coin.x, coin.y, screenDistance + 50);
              }
 
 
 
              if (this.coinsMode && Date.now() - this.splitAt > 500) {
                  if (this.cells.length === 1 && this.mass > 2 * this.COIN_PICKUP_MASS && minDistance > 50) {
                      self.split();
                  } else {
                     // if (this.cells.length === 1 && this.mass > 4 * this.COIN_PICKUP_MASS && minDistance > 100) {
                      //    self.doubleSplit();
                      //}
                  }
              }
 
              return true;
          }
          return false;
      },
 
    merge: function(targetX, targetY)
    {
      var mouseX, mouseY;
      if (targetX === undefined || targetY === undefined) {
        mouseX = window.innerWidth / 2 + this.getRandomInt(-15, 15);
        mouseY = window.innerHeight / 2 + this.getRandomInt(-15, 15);
      } else {
        mouseX = this.getScreenPosX(targetX);
        mouseY = this.getScreenPosY(targetY);
      }
 
      $('canvas').trigger($.Event('mousemove', {clientX: mouseX, clientY: mouseY, synthetic: true}));
    },
 
    /**
     * Moves the bot directly in the direction of given coordinates.
     * Won't avoid obstacles, won't use pathfinding.
     * It does not matter of the coordinates are on the map or not.
     */
    steer: function(targetX, targetY, screenDistance, sourceX, sourceY) {
      if (typeof screenDistance === 'undefined') {
        screenDistance = Math.ceil(Math.max(window.innerWidth, window.innerHeight) / 2);
      }
 
      if (sourceX === undefined || sourceY === undefined) {
        sourceX = this.x;
        sourceY = this.y;
      }
 
      var angle = this.getAngle(sourceX, sourceY, targetX, targetY);
 
      this.steerAngle(angle, screenDistance);
    },
 
    /**
     * Moves the bot directly in the direction of a given angle.
     * Won't avoid obstacles, won't use pathfinding.
     * It does not matter of the coordinates are on the map or not.
     */
    steerAngle: function(angle, screenDistance) {
      if (typeof screenDistance === 'undefined') {
        screenDistance = Math.ceil(Math.max(window.innerWidth, window.innerHeight) / 2);
      }
 
      var mouseX = window.innerWidth / 2 + Math.sin(angle * Math.PI / 180) * screenDistance;
      var mouseY = window.innerHeight / 2 - Math.cos(angle * Math.PI / 180) * screenDistance;
 
      $('canvas').trigger($.Event('mousemove', {clientX: mouseX, clientY: mouseY, synthetic: true}));
    },
 
    /**
     * Moves the bot directly in the direction of given game world coordinates,
     * by moving the (virtual) mouse pointer to that position on the screen.
     * Won't avoid obstacles, won't use pathfinding.
     * It does not matter of the coordinates are on the map or not.
     */
    steerToGamePos: function(gameTargetX, gameTargetY) {
      var mouseX = this.getScreenPosX(gameTargetX);
      var mouseY = this.getScreenPosY(gameTargetY);
 
      $('canvas').trigger($.Event('mousemove', {clientX: mouseX, clientY: mouseY, synthetic: true}));
    },
 
    /**
     * Tries to make the bot split by sending the splitting key.
     * Only works if the the bot is alive, has enough mass, 2+ pieces and not too many pieces.
     * ATTENTION: Split does not happen immediately but with a short delay!
     */
    split: function() {
      $('body').trigger($.Event('keydown', { keyCode: this.hotkeys.Space.c}));
      $('body').trigger($.Event('keyup', { keyCode: this.hotkeys.Space.c}));
      this.splitAt = Date.now();
    },
 
    /**
     * Tries to make the bot double split by sending the double split key.
     * Only works if the the bot is alive, has enough mass, 2+ pieces and not too many pieces.
     */
    doubleSplit: function() {
      $('body').trigger($.Event('keydown', { keyCode: this.hotkeys.D.c}));
      $('body').trigger($.Event('keyup', { keyCode: this.hotkeys.D.c}));
      this.splitAt = Date.now();
    },
 
    /**
     * Tries to make the bot triple split by sending the triple split key.
     * Only works if the the bot is alive, has enough mass, 2+ pieces and not too many pieces.
     */
    tripleSplit: function() {
      $('body').trigger($.Event('keydown', { keyCode: this.hotkeys.T.c}));
      $('body').trigger($.Event('keyup', { keyCode: this.hotkeys.T.c}));
      this.splitAt = Date.now();
    },
 
    /**
     * Tries to make the bot macro split (16 split) by sending the macro split key.
     * Only works if the the bot is alive, has enough mass, 2+ pieces and not too many pieces.
     */
    macroSplit: function() {
      $('body').trigger($.Event('keydown', { keyCode: this.hotkeys.Z.c}));
      $('body').trigger($.Event('keyup', { keyCode: this.hotkeys.Z.c}));
      this.splitAt = Date.now();
    },
 
    /**
     * Respawns the bot/player. Uses the respawn key if the bot is alive, uses the menu otherwise.
     */
    respawn: function() {
      self = this;
 
      if (self.respawning) {
        return;
      }
      self.respawning = true;
 
      if (this.spawnedAt !== null) {
        this.spawnedAt = null; // Will be set in the draw method
      }
 
      this.splitAt = null;
      this.ejectedAt = null;
 
      if (self.alive) {
        window.onkeydown({keyCode: this.hotkeys.M.c});
        window.onkeyup({keyCode: this.hotkeys.M.c});
        self.respawning = false;
      } else {
        if (self.isDeathPopupVisible()) {
          // The ad cannot be closed immediately
          setTimeout(function() {
            closeAdvert();
 
            self.spawn();
          }, 2800);
        } else {
          self.spawn();
        }
      }
    },
 
    /**
     * Spawns the bot by using the menu. This does not work if the bot is alive,
     * use respawn() in that case!
     */
    spawn: function() {
      self = this;
 
      var performSpawn = function() {
        setNick(document.getElementById('nick').value);
        self.spawnedAt = null; // Will be set in the draw method
 
        // Respawning doesn't happen immediately, so wait a little bit
        setTimeout(function() {
          self.respawning = false;
        }, 500);
      };
 
      // Spawning is not possible immediately so we check if we have to wait
      if ($('#playBtn').css('opacity') < 1) {
        setTimeout(function() {
          performSpawn();
        }, 2400);
      } else {
        performSpawn();
      }
    },
 
    /**
     * Displays a message at the top of the browser window, for a couple of seconds
     */
    message: function(message) {
      var curser = document.querySelector('#curser');
 
      curser.textContent = message;
      curser.style.display = 'block';
 
      window.setTimeout(function() {
        curser.style.display = 'none';
      }, 5000);
    },
 
    /**
     * Show a sweet alert (modal/popup) with a given title and message.
     */
    swal: function (title, message, html) {
      if (html === undefined) {
        html = true;
      }
      window.swal({
        title: '📢 <span class="miracle-primary-color-font">' + title + '</span>',
        text: message,
        html: html
      });
    },
 
    /**
     * Checks if there is a bot command in the chat bot (text input field).
     * If that is the case, tries to execute the command.
     * Note: The command won't be sent as a chat message.
     */
    checkChatBox: function() {
      var self = this;
      var text = $('#chtbox').val();
 
      if (text.substr(0, 5) == '/bot ') {
        var command = text.substr(5);
 
        // Function context = window.ventron
        var execCommand = function() {
          switch (command) {
            case 'start':
                  this.stopped = false;
                  this.startCoins = 0;
                  this.startedAt = new Date();
                  this.message('Bot started!');
              break;
            case 'stop':
                  this.stopped = true;
                  this.message('Bot stopped!');
              break;
            case 'coins':
                  let coins = (this.getCoins() - this.startCoins);
                  let avg = Math.round(coins / ((Date.now() - this.startedAt) / 1000 / 60));
                  this.swal(coins + ' coins collected! ' + avg + ' per minute, ' + (avg * 60) + ' per hour.');
                  break;
            case 'info':
                  this.stopped = false;
                  this.swal('Map width: ' + self.mapWidth + ', map height: ' + self.mapHeight);
              break;
            case 'help':
            default:
              this.swal('These commands are available: start, stop, coins, info');
          }
        }
 
        setTimeout(execCommand.bind(self), 1);
 
        return true;
      }
    },
 
    getCoins: function()
    {
       return 1 * $('#coinsTopLeft').text().replace(/\s/g, '');
    },
 
    /**
     * Calculates the (float) mass of a cell by its radius
     * ATTENTION: It's important that this function returns a float!
     * If the bot is split and small, the mass will be 0 else!
     */
    getCellMass: function(radius) {
      var area = Math.PI * radius * radius;
      var mass = area * this.MASS_AREA_FACTOR;
 
      return mass;
    },
 
    /**
     * Calculates the radius of a cell by its mass
     */
    getCellRadius: function(mass) {
      var area = mass / this.MASS_AREA_FACTOR;
      var radius = Math.sqrt(area / Math.PI);
 
      return radius;
    },
 
    /**
     * Returns the (float) distance between two points (coordinates)
     */
    getDistance: function(x1, y1, x2, y2) {
      return Math.hypot(x1 - x2, y1 - y2);
    },
 
    /**
     * Returns the 360-angle between two points 8coordinates), starting by the first point.
     * 0° means the second point is in the north of the first point.
     * The returned angle will always be >= 0 and < 360.
     */
    getAngle: function(x1, y1, x2, y2) {
      var angle = Math.atan2(y2 - y1, x2 - x1); // range (-PI, PI]
      angle *= 180 / Math.PI; // rads to degs, range (-180, 180]
      angle += 90;
 
      if (angle < 0) angle = 360 + angle; // range [0, 360)
      if (angle >= 360) angle = 360 - angle; // range [0, 360)
 
      return angle;
    },
 
    /**
     * Adds angle2 to angle1 and returns the resulting angle.
     */
    addAngle: function(angle1, angle2) {
      var angle = angle1 + angle2;
 
      angle %= 360;
      if (angle < 0) angle += 360;
 
      return angle;
    },
 
    /**
     * Returns the (absolute) difference between two angles.
     * The minimum difference will be 0, the maximum difference will be 180.
     */
    getAngleDiff: function(angle1, angle2) {
      var diff = angle1 - angle2;
 
      diff = Math.abs(diff);
 
      if (diff > 180) {
        diff = 360 - diff;
      }
 
      return diff;
    },
 
    /**
     * Transforms and returns an x coordinate on the screen to an x coordinate in the game.
     */
    getGamePosX: function(screenPosX) {
      return this.x - (window.innerWidth / 2 - screenPosX) / this.zoom;
    },
 
    /**
     * Transforms and returns an y coordinate on the screen to an y coordinate in the game.
     */
    getGamePosY: function(screenPosY) {
      return this.y - (window.innerHeight / 2 - screenPosY) / this.zoom;
    },
 
    /**
     * Transforms and returns an x coordinate in the game to an x coordinate on the screen.
     */
    getScreenPosX: function(gamePosX) {
      return - (- this.zoom * (gamePosX - this.x) - window.innerWidth / 2)
    },
 
    /**
     * Transforms and returns an y coordinate in the game to an y coordinate on the screen.
     */
    getScreenPosY: function(gamePosY) {
      return - (- this.zoom * (gamePosY - this.y) - window.innerHeight / 2)
    },
 
    /**
     * Returns a random integer between min (inclusive) and max (exclusive)
     * Source: MDN
     */
    getRandomInt: function(min, max) {
      return parseInt(Math.random() * (max - min) + min);
    },
 
    /**
     * Returns true if the popup that is displayed after we die is currently visible
     */
    isDeathPopupVisible: function() {
      var displayingAd = (document.getElementById('advert').style.display == 'block');
      return displayingAd;
    },
 
    /**
     * Returns the time since the bot was initialized in seconds
     */
    getTimeSinceStart: function() {
      var endDate = new Date();
      return (endDate.getTime() - startDate.getTime()) / 1000;
    },
 
    /**
     * Returns the URI of my skin or null if not skin has been set.
     * Use this.skinUrl() to get it.
     */
    getSkinUrl: function() {
      var skinUrlRaw = $('#skinExampleMenu').css('background-image');
 
      var parts = skinUrlRaw.split('"');
 
      if (parts.length != 3) {
        return null;
      } else {
        return parts[1];
      }
    },
}
 
 
    let start = function() {
        if (document.readyState === "complete") {
            // We need to have a delay, because the skin preview in the game menu is not loaded right away and also we have to wait for auto login
            setTimeout(function() {
                trufflePigPublic.init();
            }, 4000);
        } else {
            setTimeout(start, 1000);
        }
    };
    start();
 
})();