// ==UserScript==
// @name Ye Olde Megajump
// @namespace https://github.com/YeOldeWH/MonsterMinigameWormholeWarp
// @description A script that runs the Steam Monster Minigame for you. Now with megajump. Brought to you by the Ye Olde Wormhole Schemers and DannyDaemonic
// @version 8.0.3
// @match *://steamcommunity.com/minigame/towerattack*
// @match *://steamcommunity.com//minigame/towerattack*
// @grant none
// @updateURL https://raw.githubusercontent.com/YeOldeWH/MonsterMinigameWormholeWarp/master/autoplay.noUpdate.user.js
// @downloadURL https://raw.githubusercontent.com/YeOldeWH/MonsterMinigameWormholeWarp/master/autoplay.noUpdate.user.js
// ==/UserScript==
// IMPORTANT: Update the @version property above to a higher number such as 1.1 and 1.2 when you update the script! Otherwise, Tamper / Greasemonkey users will not update automatically.
(function(w) {
"use strict";
// OPTIONS
var clickRate = 20;
var logLevel = 1; // 5 is the most verbose, 0 disables all log
var wormholeOn100 = 1;
var likeNewOn100 = 0;
var medicOn100 = 1;
var clicksOnBossLevel = 0;
var upgThreshold = 100;
var minAbilityUsePercent = 0.3;
var enableAutoClicker = true;
var enableAutoUpgradeHP = true;
var enableAutoUpgradeClick = true;
var enableAutoUpgradeDPS = false;
var enableAutoUpgradeElemental = true;
var enableAutoPurchase = true;
var enableAutoBadgePurchase = true;
var removeInterface = getPreferenceBoolean("removeInterface", true); // get rid of a bunch of pointless DOM
var removeParticles = getPreferenceBoolean("removeParticles", true);
var removeFlinching = getPreferenceBoolean("removeFlinching", true);
var removeCritText = getPreferenceBoolean("removeCritText", false);
var removeAllText = getPreferenceBoolean("removeAllText", false);
var enableFingering = getPreferenceBoolean("enableFingering", true);
var disableRenderer = getPreferenceBoolean("disableRenderer", true);
var enableTrollTrack = getPreferenceBoolean("enableTrollTrack", false);
var enableElementLock = getPreferenceBoolean("enableElementLock", true);
var enableAutoRefresh = true;
var enableChen = getPreferenceBoolean("enableChen", false);
var autoRefreshMinutes = 30;
var autoRefreshMinutesRandomDelay = 10;
var autoRefreshSecondsCheckLoadedDelay = 30;
var predictTicks = 0;
var predictJumps = 0;
var predictLastWormholesUpdate = 0;
// Auto refresh handler delay
var autoRefreshDuringBossDelayTotal = 0; // Total delay already passed
var autoRefreshDuringBossDelay = 60000; // Delay during the boss (ms)
var autoRefreshDuringBossDelayStep = 2500; // Delay 'step' (until we try again)
// DO NOT MODIFY
var isPastFirstRun = false;
var isAlreadyRunning = false;
var refreshTimer = null;
var currentClickRate = enableAutoClicker ? clickRate : 0;
var lastLevel = 0;
var lastLevelInfo = [{
level: 0,
levelsGained: 0,
timeStarted: 0,
timeTakenInSeconds: 0
}];
var _previousEnemyHP = [0];
var _previousDPS = [0];
var _tickInfo = [];
var approxYOWHClients = 0;
var skipsLastJump = 0;
var updateSkips = false;
var trt_oldCrit = function() {};
var trt_oldPush = function() {};
var trt_oldRender = function() {};
var ELEMENTS = {
LockedElement: -1
};
var UPGRADES = {
LIGHT_ARMOR: 0,
AUTO_FIRE_CANNON: 1,
ARMOR_PIERCING_ROUND: 2,
DAMAGE_TO_FIRE_MONSTERS: 3,
DAMAGE_TO_WATER_MONSTERS: 4,
DAMAGE_TO_AIR_MONSTERS: 5,
DAMAGE_TO_EARTH_MONSTERS: 6,
LUCKY_SHOT: 7,
HEAVY_ARMOR: 8,
ADVANCED_TARGETING: 9,
EXPLOSIVE_ROUNDS: 10,
MEDICS: 11,
MORALE_BOOSTER: 12,
GOOD_LUCK_CHARMS: 13,
METAL_DETECTOR: 14,
DECREASE_COOLDOWNS: 15,
TACTICAL_NUKE: 16,
CLUSTER_BOMB: 17,
NAPALM: 18,
BOSS_LOOT: 19,
ENERGY_SHIELDS: 20,
FARMING_EQUIPMENT: 21,
RAILGUN: 22,
PERSONAL_TRAINING: 23,
AFK_EQUIPMENT: 24,
NEW_MOUSE_BUTTON: 25,
CYBERNETIC_ENHANCEMENTS: 26,
LEVEL_1_SENTRY_GUN: 27,
TITANIUM_MOUSE_BUTTON: 28
};
var ABILITIES = {
FIRE_WEAPON: 1,
CHANGE_LANE: 2,
RESPAWN: 3,
CHANGE_TARGET: 4,
MORALE_BOOSTER: 5,
GOOD_LUCK_CHARMS: 6,
MEDICS: 7,
METAL_DETECTOR: 8,
DECREASE_COOLDOWNS: 9,
TACTICAL_NUKE: 10,
CLUSTER_BOMB: 11,
NAPALM: 12,
RESURRECTION: 13,
CRIPPLE_SPAWNER: 14,
CRIPPLE_MONSTER: 15,
MAX_ELEMENTAL_DAMAGE: 16,
RAINING_GOLD: 17,
CRIT: 18,
PUMPED_UP: 19,
THROW_MONEY_AT_SCREEN: 20,
GOD_MODE: 21,
TREASURE: 22,
STEAL_HEALTH: 23,
REFLECT_DAMAGE: 24,
FEELING_LUCKY: 25,
WORMHOLE: 26,
LIKE_NEW: 27
};
var ENEMY_TYPE = {
SPAWNER: 0,
CREEP: 1,
BOSS: 2,
MINIBOSS: 3,
TREASURE: 4
};
var BOSS_DISABLED_ABILITIES = [
ABILITIES.MORALE_BOOSTER,
ABILITIES.GOOD_LUCK_CHARMS,
ABILITIES.TACTICAL_NUKE,
ABILITIES.CLUSTER_BOMB,
ABILITIES.NAPALM,
ABILITIES.CRIT,
ABILITIES.CRIPPLE_SPAWNER,
ABILITIES.CRIPPLE_MONSTER,
ABILITIES.MAX_ELEMENTAL_DAMAGE,
ABILITIES.REFLECT_DAMAGE,
ABILITIES.STEAL_HEALTH,
ABILITIES.THROW_MONEY_AT_SCREEN
];
var CONTROL = {
speedThreshold: 2000,
rainingRounds: 100,
disableGoldRainLevels: 500,
rainingSafeRounds: 9
};
var GAME_STATUS = {
LOBBY: 1,
RUNNING: 2,
OVER: 3
};
var wormholesUsedOnLevel = 0;
// Try to disable particles straight away,
// if not yet available, they will be disabled in firstRun
disableParticles();
// Define custom getters for document.hidden and the prefixed versions, so the game
// doesn't stop ticking in the background.
if (Object.defineProperty) {
var props = ['hidden', 'webkitHidden', 'mozHidden', 'msHidden'];
for (var i = 0; i < props.length; ++i)
Object.defineProperty(document, props[i], {value: false});
}
if(!getPreferenceBoolean("alertShown", false)) {
w.ShowAlertDialog(
'Ye Olde Megajump',
'
This dialog will be shown just once, so please read through it.
' +
'This script does not lag your game,
we are limiting it to 1 frame per second to lower CPU usage.
' +
'We have multiple options to configure this script, and disabling FPS limiter is one of them.
' +
'You can report issues on GitHub
' +
'Thanks and have fun!
'
).done(function(strButton) {
setPreference("alertShown", true);
});
}
function s() {
return w.g_Minigame.m_CurrentScene;
}
function firstRun() {
advLog("Starting YOWH Script.", 1);
trt_oldCrit = s().DoCritEffect;
trt_oldPush = s().m_rgClickNumbers.push;
trt_oldRender = w.g_Minigame.Render;
toggleFingering();
if(enableElementLock) {
lockElements();
}
if (enableAutoRefresh) {
autoRefreshPage(autoRefreshMinutes);
}
toggleRenderer();
// disable particle effects - this drastically reduces the game's memory leak
disableParticles();
// disable enemy flinching animation when they get hit
if(removeFlinching && w.CEnemy) {
w.CEnemy.prototype.TakeDamage = function() {};
w.CEnemySpawner.prototype.TakeDamage = function() {};
w.CEnemyBoss.prototype.TakeDamage = function() {};
}
if(removeCritText) {
toggleCritText();
}
if(removeAllText) {
toggleAllText();
}
// style
var styleNode = document.createElement('style');
styleNode.type = 'text/css';
var styleText = [
// Page content
".pagecontent {padding: 0}",
// Align abilities to the left
"#abilitiescontainer {text-align: left;}",
// Activitylog and ability list
"#activeinlanecontainer {padding-left: 10px;}",
"#activeinlanecontainer:hover {height: auto; background-image: radial-gradient(circle farthest-corner at 32px 0px, rgba(0,124,182,0.1), #11111C); padding-bottom: 10px; position:absolute; z-index: 1;}",
"#activeinlanecontainer:hover + #activitylog {margin-top: 88px;}",
"#activitylog {margin-top: 20px}",
// Hide leave game button
".leave_game_btn {display: none;}",
// Option menu
".game_options {height: auto;}",
".game_options .toggle_sfx_btn {margin: 6px 7px 0px 2px; float: right;}",
".game_options .toggle_music_btn {margin-right: 2px; float: right;}",
".options_box {background-color: #000; width: 940px; padding: 12px; box-shadow: 2px 2px 0px rgba(0, 0, 0, 0.6); color: #EDEDED; margin: 4px auto 0; overflow: auto; float: left;}",
".options_box span.asterisk {color: #FF5252; font-size: 24px; line-height: 4px; vertical-align: bottom;}",
".options_column {-moz-column-count: 2; -webkit-column-count: 2; column-count: 2; width: 50%; float: left;}",
".options_column label {display: inline-block;}",
".options_column input {float: left;}",
".options_column input[type=number] {margin: 6px 5px 0 0; padding: 2px 0px 0px 4px;}",
".options_column input[name=setLogLevel] {width: 25px;}",
".options_column span.asterisk {line-height: 14px;}",
// Element lock box
".lock_elements_box {width: 165px; top: -76px; left: 303px; box-sizing: border-box; line-height: 1rem; padding: 7px 10px; position: absolute; color: #EDEDED;}",
// Breadcrumbs
".breadcrumbs {color: #bbb;}",
".bc_span {text-shadow: 1px 1px 0px rgba( 0, 0, 0, 0.3 );}",
".bc_room {color: #ACE191;}",
".bc_level {color: #FFA07A;}",
".bc_time {color: #9AC0FF;}",
".bc_worms {color: #FFF79A;}",
// Adjustments for hard to see areas on the new background
"#upgradesscroll, #activityscroll {opacity: 0.75;}",
".teamhealth {background: rgba( 240, 240, 255, 0.2 );}",
"#upgrades .title_upgrates {color: #67C;}",
// Always show ability count
".abilitytemplate > a > .abilityitemquantity {visibility: visible; pointer-events: none;}",
".tv_ui {background-image: url(http://i.imgur.com/vM1gTFY.gif);}",
"#Chen {position: absolute; bottom: 0px; left: 770px; width: 286px; height: 250px; background-image: url(//i.imgur.com/xMbQChA.png); }",
""
];
styleNode.textContent = styleText.join("");
document.head.appendChild(styleNode);
if( removeInterface ) {
var node = document.getElementById("global_header");
if (node && node.parentNode) {
node.parentNode.removeChild( node );
}
node = document.getElementById("footer");
if (node && node.parentNode) {
node.parentNode.removeChild( node );
}
node = document.getElementById("footer_spacer");
if (node && node.parentNode) {
node.parentNode.removeChild( node );
}
document.body.style.backgroundPosition = "0 0";
}
originalUpdateLog = CUI.prototype.UpdateLog;
// Set to match preferences
toggleTrackTroll();
toggleChen();
// Add cool background
$J('body.flat_page.game').css('background-image', 'url(http://i.imgur.com/P8TB236.jpg)');
// Add "players in game" label
var titleActivity = document.querySelector( '.title_activity' );
var playersInGame = document.createElement( 'span' );
playersInGame.innerHTML = '0/1500 Players in room
';
titleActivity.insertBefore(playersInGame, titleActivity.firstChild);
ELEMENTS.PlayersInGame = document.getElementById("players_in_game");
var info_box = document.querySelector(".leave_game_helper");
info_box.className = "options_box";
var options_menu = document.querySelector(".game_options");
var sfx_btn = document.querySelector(".toggle_sfx_btn");
options_menu.insertBefore(info_box, sfx_btn);
info_box.innerHTML = 'OPTIONS' + ((typeof GM_info !== "undefined") ? ' (v' + GM_info.script.version + ')' : '') + '
Settings marked with a * requires a refresh to take effect.
';
var options1 = document.createElement("div");
options1.className = "options_column";
options1.appendChild(makeCheckBox("removeInterface", "Remove interface", removeInterface, handleEvent, true));
options1.appendChild(makeCheckBox("removeParticles", "Remove particle effects", removeParticles, handleEvent, true));
options1.appendChild(makeCheckBox("removeFlinching", "Remove flinching effects", removeFlinching, handleEvent, true));
options1.appendChild(makeCheckBox("removeCritText", "Remove crit text", removeCritText, toggleCritText, false));
options1.appendChild(makeCheckBox("removeAllText", "Remove all text", removeAllText, toggleAllText, false));
options1.appendChild(makeCheckBox("disableRenderer", "Limit frames per second to increase performance", disableRenderer, toggleRenderer, false));
options1.appendChild(makeCheckBox("enableChen", "Honk Honk?", enableChen, toggleChen, false));
info_box.appendChild(options1);
var options2 = document.createElement("div");
options2.className = "options_column";
if (typeof GM_info !== "undefined") {
options2.appendChild(makeCheckBox("enableAutoRefresh", "Enable AutoRefresh (mitigate memory leak)", enableAutoRefresh, toggleAutoRefresh, false));
}
options2.appendChild(makeCheckBox("enableFingering", "Enable targeting pointer", enableFingering, toggleFingering, false));
options2.appendChild(makeCheckBox("enableTrollTrack", "Enable tracking trolls", enableTrollTrack, toggleTrackTroll, false));
options2.appendChild(makeNumber("setLogLevel", "Change the log level (you shouldn't need to touch this)", logLevel, 0, 5, updateLogLevel));
info_box.appendChild(options2);
//Elemental upgrades lock
var ab_box = document.getElementById("abilities");
var lock_elements_box = document.createElement("div");
lock_elements_box.className = "lock_elements_box";
lock_elements_box.title = "To maximise team damage players should max only one element. But distributions of elements through people should be equal. So we calculated your element using your unique ID. Upgrade your element to make maximum performance or disable this checkbox.";
var lock_elements_checkbox = makeCheckBox("enableElementLock", "Lock element upgrades for more team dps", enableElementLock, toggleElementLock, false);
lock_elements_box.appendChild(lock_elements_checkbox);
ab_box.appendChild(lock_elements_box);
// Easter egg
$J('').insertAfter($J('.tv_ui'));
enhanceTooltips();
isPastFirstRun = true;
}
// Valve's update
var originalUpdateLog = null;
// The trolltrack
var localUpdateLog = function( rgLaneLog ) {
var abilities = this.m_Game.m_rgTuningData.abilities;
var level = getGameLevel();
if( !this.m_Game.m_rgPlayerTechTree ) return;
var nHighestTime = 0;
for( var i=rgLaneLog.length-1; i >= 0; i--) {
var rgEntry = rgLaneLog[i];
if( isNaN( rgEntry.time ) ) rgEntry.time = this.m_nActionLogTime + 1;
if( rgEntry.time <= this.m_nActionLogTime ) continue;
// If performance concerns arise move the level check out and swap switch for if.
switch( rgEntry.type ) {
case 'ability':
if ( (level % 100 !== 0 && [26].indexOf(rgEntry.ability) > -1) || (level % 100 === 0 && [10, 11, 12, 15, 20].indexOf(rgEntry.ability) > -1) ) {
var ele = this.m_eleUpdateLogTemplate.clone();
$J(ele).data('abilityid', rgEntry.ability);
$J('.name', ele).text(rgEntry.actor_name).attr("style", "color: red; font-weight: bold;");
$J('.ability', ele).text(abilities[rgEntry.ability].name + " on level " + level);
$J('img', ele).attr('src', g_rgIconMap['ability_' + rgEntry.ability].icon);
$J(ele).v_tooltip({tooltipClass: 'ta_tooltip', location: 'top'});
this.m_eleUpdateLogContainer[0].insertBefore(ele[0], this.m_eleUpdateLogContainer[0].firstChild);
advLog(rgEntry.actor_name + " used " + s().m_rgTuningData.abilities[ rgEntry.ability ].name + " on level " + level, 1);
if(rgEntry.ability == ABILITIES.WORMHOLE) {
wormholesUsedOnLevel++;
}
}
break;
default:
console.log("Unknown action log type: %s", rgEntry.type);
console.log(rgEntry);
}
if(rgEntry.time > nHighestTime) nHighestTime = rgEntry.time;
}
if( nHighestTime > this.m_nActionLogTime ) this.m_nActionLogTime = nHighestTime;
var e = this.m_eleUpdateLogContainer[0];
while(e.children.length > 20 ) {
e.children[e.children.length-1].remove();
}
};
function disableParticles() {
if (w.CSceneGame) {
w.CSceneGame.prototype.DoScreenShake = function() {};
if(removeParticles) {
w.CSceneGame.prototype.SpawnEmitter = function(emitter) {
emitter.emit = false;
return emitter;
};
var particles = s().m_rgActiveParticles;
if(particles) {
if (particles[ 7 ]) {
particles[ 7 ][0].emit = false;
particles[ 7 ][1].emit = false;
}
if (particles[ 5 ]) {
particles[ 5 ][0].emit = false;
}
if (particles[ 6 ]) {
particles[ 6 ][0].emit = false;
particles[ 6 ][1].emit = false;
}
if (particles[ 8 ]) {
particles[ 8 ][0].emit = false;
particles[ 8 ][1].emit = false;
particles[ 8 ][2].emit = false;
}
if (particles[ 9 ]) {
particles[ 9 ][0].emit = false;
particles[ 9 ][1].emit = false;
}
}
}
}
}
function getTimeleft() {
var cTime = new Date();
var cHours = cTime.getUTCHours();
var cMins = cTime.getUTCMinutes();
var timeLeft = 60 - cMins;
if (cHours == 15) {
return timeLeft;
}
return 61;
}
function unshiftIntoCircularBuffer(buffer, bufferSize, value) {
buffer.unshift(value);
if (buffer.length > bufferSize) {
buffer.pop();
}
}
var _jumpHistory = [];
var defaultEntryRemoved = false;
function updateLevelAndDPSTracker() {
var levelHasChanged = lastLevelInfo[0].level !== getGameLevel();
if (levelHasChanged) {
unshiftIntoCircularBuffer(lastLevelInfo, 1000,
{ level: getGameLevel(),
levelsGained: -1,
timeStarted: s().m_rgGameData.timestamp,
timeTakenInSeconds: -1,
total_enemy_hp: getTotalLevelEnemyStats().max_hp,
level_dps: 0
});
var previousLevel = lastLevelInfo[1];
previousLevel.levelsGained = getGameLevel() - previousLevel.level;
previousLevel.timeTakenInSeconds = s().m_rgGameData.timestamp - previousLevel.timeStarted;
}
// This is a weird place for this I know, but I couldn't think of anywhere better in my sleep deprived state
updateCurrentDPS(levelHasChanged);
if (levelHasChanged) {
previousLevel.level_dps = getAverageDPS();
if (!defaultEntryRemoved) {
defaultEntryRemoved = true;
lastLevelInfo.pop();
}
}
}
function updateCurrentDPS(levelHasChanged) {
var skipped_dps = 0;
if (levelHasChanged) {
var previousLevel = lastLevelInfo[1];
var skipped_dps = (previousLevel.levelsGained * previousLevel.total_enemy_hp) / previousLevel.timeTakenInSeconds;
}
var current_hp = getTotalLevelEnemyStats().current_hp;
var dps = Math.max(current_hp - _previousEnemyHP[0], 0);
unshiftIntoCircularBuffer(_previousDPS, 10, (dps + skipped_dps) || 0);
unshiftIntoCircularBuffer(_previousEnemyHP, 10, current_hp);
}
function getAverageDPS() {
return _previousDPS.reduce(function (a, b) {
return a + b;
}) / _previousDPS.length;
}
function getTotalLevelEnemyStats() {
var current_hp = 0;
var max_hp = 0;
s().m_rgGameData.lanes.map(function (lane) {
lane.enemies.map(function(enemy) {
current_hp += enemy.hp
max_hp += enemy.max_hp
});
});
return {current_hp: current_hp, max_hp: max_hp}
}
var predictedLevelHits = 0;
var predictedLevelMisses = 0;
function updateNextLevelPrediction() {
unshiftIntoCircularBuffer(_tickInfo, 1000,
{
tick: s().m_rgGameData.timestamp,
level: getGameLevel(),
tickVelocity: levelsPerSec(),
predictedLevel: getNextPredictedLevel()
});
var currentLevel = _tickInfo[0].level;
var predictedCurrentLevel = (_tickInfo[1] && _tickInfo[1].predictedLevel) || 0;
if (currentLevel !== predictedCurrentLevel) {
console.log("Prediction off by ", currentLevel - predictedCurrentLevel);
predictedLevelMisses++;
} else {
predictedLevelHits++;
}
}
function getPredictedLevelAccuracy() {
return (predictedLevelHits / (predictedLevelHits + predictedLevelMisses))
}
function getLevelForTick(tick) {
var info = _tickInfo.filter(function(i) { return i.tick === tick; })[0];
if (info) {
return info.level;
}
return 0;
}
function averageWormholesPerBoss() {
var bosses = lastLevelInfo.filter(function(i) { return isBossLevel(i.level); })
return bosses.map(function(i) { return i.levelsGained }).reduce(function(curr, level) { return curr + level}, 0) / bosses.length
}
function averageBossesPerSecond() {
var bosses = lastLevelInfo.filter(function(i) { return isBossLevel(i.level); })
if (bosses.length > 1) {
return (bosses.length / (bosses[0].timeStarted - bosses[bosses.length-1].timeStarted));
}
return 0;
}
var scriptStartedInfo = {level: 0, time: 0};
function getLevelsPerSecondSinceStart() {
return (getGameLevel() - scriptStartedInfo.level) / (s().m_rgGameData.timestamp - scriptStartedInfo.time);
}
function getNextPredictedLevel() {
var levelVelocity = Math.round(_tickInfo
.filter(function(i) { return i.level === getGameLevel(); })
.map(function(i) { return i.tickVelocity; })
.reduce(function(curr, velocity) { return curr + velocity; }, 0));
var wormholes = $J.unique(
s().m_rgActionLog
.filter(function(entry) { return entry.ability === ABILITIES.WORMHOLE; })
.filter(function(entry) { return getLevelForTick(entry.time) === getGameLevel(); })
.map(function(entry) { return entry.actor + ":" + entry.time; })
).reduce(function(curr, key) {
return curr + 1;
}, 0);
return getGameLevel() + levelVelocity + wormholes;
}
function MainLoop() {
var status = s().m_rgGameData.status;
if(status != GAME_STATUS.RUNNING) {
if(disableRenderer) {
s().Tick();
}
return;
}
var level = getGameLevel();
updateLevelAndDPSTracker();
updateApproxYOWHClients();
updateNextLevelPrediction();
if (scriptStartedInfo.level === 0) {
scriptStartedInfo.level = level;
scriptStartedInfo.time = s().m_rgGameData.timestamp;
}
if (!isAlreadyRunning) {
isAlreadyRunning = true;
if( level !== lastLevel ) {
// Clear any unsent abilities still in the queue when our level changes
s().m_rgAbilityQueue.clear();
// update skips if applicable
if (updateSkips) {
skipsLastJump = level - lastLevel;
updateSkips = false;
}
wormholesUsedOnLevel = 0;
}
if (level % 100 == 0) {
// On a WH level, jump everyone with wormholes to lane 0, unless there is a boss there, in which case jump to lane 1.
var targetLane = 0;
// Check lane 0, enemy 0 to see if it's a boss
var enemyData = s().GetEnemy(0, 0).m_data;
if(typeof enemyData !== "undefined"){
var enemyType = enemyData.type;
if(enemyType == ENEMY_TYPE.BOSS) {
advLog('In lane 0, there is a boss, avoiding', 4);
targetLane = 1;
var enemyDataLaneOne = s().GetEnemy(1, 0).m_data;
var enemyDataLaneTwo = s().GetEnemy(2, 0).m_data;
if(typeof enemyDataLaneOne != "undefined" && typeof enemyDataLaneTwo == "undefined"){
//Lane 1 has monsters. Lane 2 is empty. Switch to lane 2 instead.
targetLane = 2;
}
}
}
if( s().m_rgPlayerData.current_lane != targetLane ) {
advLog('Moving player to wormhole lane ' + targetLane, 4);
s().TryChangeLane(targetLane); // put everyone in the same lane
}
updateSkips = true;
// GoGo Chen
honkingIntenstifys(true);
} else {
goToLaneWithBestTarget(level);
honkingIntenstifys(false);
}
attemptRespawn();
if (level % 100 !== 0 && w.SteamDB_Wormhole_Timer) {
w.clearInterval(w.SteamDB_Wormhole_Timer);
w.SteamDB_Wormhole_Timer = false;
}
var timeLeft = getTimeleft(); // Time left in minutes
if(level % 100 == 0){
useAbilitiesAt100();
} else if(timeLeft <= 15) {
useAllAbilities();
} else {
useAbilities(level);
}
updatePlayersInGame();
if( level !== lastLevel ) {
if (level % 100 === 0) {
enableAbility(ABILITIES.WORMHOLE);
enableAbility(ABILITIES.LIKE_NEW);
} else {
disableAbility(ABILITIES.WORMHOLE);
disableAbility(ABILITIES.LIKE_NEW);
}
lastLevel = level;
updateLevelInfoTitle(level);
refreshPlayerData();
}
// only AutoUpgrade after we've spend all badge points
if(s().m_rgPlayerTechTree) {
if(s().m_rgPlayerTechTree.badge_points === 0) {
useAutoUpgrade();
useAutoPurchaseAbilities();
}
else {
useAutoBadgePurchase();
}
}
var absoluteCurrentClickRate = 0;
if(currentClickRate > 0) {
var levelRainingMod = level % CONTROL.rainingRounds;
absoluteCurrentClickRate = level > CONTROL.speedThreshold && (levelRainingMod === 0 || 3 >= (CONTROL.rainingRounds - levelRainingMod)) ? 0 : currentClickRate;
var wormholesOnLine = getActiveAbilityLaneCount(ABILITIES.WORMHOLE);
var levelsUntilBoss = (CONTROL.rainingRounds - (level % CONTROL.rainingRounds));
if(levelsUntilBoss < 10 && (wormholesOnLine > levelsUntilBoss || wormholesUsedOnLevel > levelsUntilBoss)) {
advLog("Too much wormholes for throttle back. WH: " + wormholesOnLine + " / "+ wormholesUsedOnLevel+" > lvl: " + levelsUntilBoss, 4);
absoluteCurrentClickRate = currentClickRate;
}
else {
absoluteCurrentClickRate = currentClickRate;
// throttle back as we approach
var levelsPer = levelsPerSecRaw();
if (level > 100000 && levelsPer != 0) { // sanity check
var ticksTillBoss = (level % 100) / levelsPerSec()
if (ticksTillBoss <= 15) {
absoluteCurrentClickRate = Math.round(currentClickRate * (Math.pow(1.333, -(15-ticksTillBoss)) * 39 / 40 + 0.1));
}
}
}
//If at the boss level, dont click at all
if (level % CONTROL.rainingRounds == 0) {
absoluteCurrentClickRate = clicksOnBossLevel;
}
s().m_nClicks += absoluteCurrentClickRate;
}
s().m_nLastTick = false;
w.g_msTickRate = 1000;
var damagePerClick = s().CalculateDamage(
s().m_rgPlayerTechTree.damage_per_click,
s().m_rgGameData.lanes[s().m_rgPlayerData.current_lane].element
);
advLog("Ticked. Current clicks per second: " + absoluteCurrentClickRate + ". Current damage per second: " + (damagePerClick * absoluteCurrentClickRate), 4);
if(disableRenderer) {
s().Tick();
requestAnimationFrame(function() {
w.g_Minigame.Renderer.render(s().m_Container);
});
}
isAlreadyRunning = false;
if( absoluteCurrentClickRate > 0) {
var enemy = s().GetEnemy(
s().m_rgPlayerData.current_lane,
s().m_rgPlayerData.target);
if (enemy) {
displayText(
enemy.m_Sprite.position.x - (enemy.m_nLane * 440),
enemy.m_Sprite.position.y - 52,
"-" + w.FormatNumberForDisplay((damagePerClick * absoluteCurrentClickRate), 5),
"#aaf"
);
if( s().m_rgStoredCrits.length > 0 ) {
var rgDamage = s().m_rgStoredCrits.reduce(function(a,b) {
return a + b;
});
s().m_rgStoredCrits.length = 0;
s().DoCritEffect( rgDamage, enemy.m_Sprite.position.x - (enemy.m_nLane * 440), enemy.m_Sprite.position.y + 17, 'Crit!' );
}
var goldPerClickPercentage = s().m_rgGameData.lanes[s().m_rgPlayerData.current_lane].active_player_ability_gold_per_click;
if (goldPerClickPercentage > 0 && enemy.m_data.hp > 0) {
var goldPerSecond = enemy.m_data.gold * goldPerClickPercentage * absoluteCurrentClickRate;
s().ClientOverride('player_data', 'gold', s().m_rgPlayerData.gold + goldPerSecond);
s().ApplyClientOverrides('player_data', true);
advLog(
"Raining gold ability is active in current lane. Percentage per click: " + goldPerClickPercentage
+ "%. Approximately gold per second: " + goldPerSecond,
4
);
displayText(
enemy.m_Sprite.position.x - (enemy.m_nLane * 440),
enemy.m_Sprite.position.y - 17,
"+" + w.FormatNumberForDisplay(goldPerSecond, 5),
"#e1b21e"
);
}
}
}
}
}
function useAutoBadgePurchase() {
if(!enableAutoBadgePurchase) { return; }
// id = ability
// ratio = how much of the remaining badges to spend
//Note: this isn't an actual ratio, because badge points get reduced and the values don't add to 1
//For now, this is not a problem, but for stylistic reasons, should eventually be changed.
//Regular users buy ratio
if (likeNewOn100 == 1 && wormholeOn100 == 1) {
// High Level, "Waste Not" Users
var abilityPriorityList = [
{ id: ABILITIES.WORMHOLE, ratio: 0.5 },
{ id: ABILITIES.LIKE_NEW, ratio: 1 },
{ id: ABILITIES.CRIT, ratio: 1 },
{ id: ABILITIES.TREASURE, ratio: 1 },
{ id: ABILITIES.PUMPED_UP, ratio: 1 },
];
} else if (likeNewOn100 == 1) {
// Like New Buyers
var abilityPriorityList = [
{ id: ABILITIES.WORMHOLE, ratio: 0 },
{ id: ABILITIES.LIKE_NEW, ratio: 1 },
{ id: ABILITIES.CRIT, ratio: 1 },
{ id: ABILITIES.TREASURE, ratio: 1 },
{ id: ABILITIES.PUMPED_UP, ratio: 1 },
];
} else {
// Regular User Buy Ratio
var abilityPriorityList = [
{ id: ABILITIES.WORMHOLE, ratio: 1 },
{ id: ABILITIES.LIKE_NEW, ratio: 0 },
{ id: ABILITIES.CRIT, ratio: 1 },
{ id: ABILITIES.TREASURE, ratio: 1 },
{ id: ABILITIES.PUMPED_UP, ratio: 1 },
];
}
var badgePoints = s().m_rgPlayerTechTree.badge_points;
var abilityData = s().m_rgTuningData.abilities;
var abilityPurchaseQueue = [];
for (var i = 0; i < abilityPriorityList.length; i++) {
var id = abilityPriorityList[i].id;
var ratio = abilityPriorityList[i].ratio;
var cost = abilityData[id].badge_points_cost;
var portion = parseInt(badgePoints * ratio);
badgePoints -= portion;
while(portion >= cost) {
abilityPurchaseQueue.push(id);
portion -= cost;
}
badgePoints += portion;
}
s().m_rgPurchaseItemsQueue = s().m_rgPurchaseItemsQueue.concat(abilityPurchaseQueue);
s().m_UI.UpdateSpendBadgePointsDialog();
}
function toggleAutoBadgePurchase(event) {
var value = enableAutoBadgePurchase;
if(event !== undefined) {
value = handleCheckBox(event);
}
enableAutoBadgePurchase = value;
}
function useAllAbilities() {
for(var key in ABILITIES) {
if(ABILITIES[key] == ABILITIES.WORMHOLE) { continue; }
if(ABILITIES[key] == ABILITIES.LIKE_NEW) { continue; }
tryUsingAbility(ABILITIES[key]);
}
}
function isBossLevel(level) {
return level % 100 === 0
}
function updateApproxYOWHClients() {
var APPROXIMATE_WH_PER_PERSON_PER_SECOND = 10;
if (lastLevelInfo.length < 2) {
return;
}
var lastLevel = lastLevelInfo[1].level;
if (isBossLevel(lastLevel)) {
var levelsJumped = getGameLevel() - lastLevel;
var bossLevelTime = lastLevelInfo[1].timeTakenInSeconds;
var possiblyInaccurateCount = Math.round(levelsJumped / (bossLevelTime * APPROXIMATE_WH_PER_PERSON_PER_SECOND));
if (possiblyInaccurateCount < 1500) {
approxYOWHClients = possiblyInaccurateCount;
} else {
console.log("Inaccurate count of YOWH Clients: ",possiblyInaccurateCount,
", levelsJumped: ", levelsJumped,
", bossLevelTime: ", bossLevelTime,
", lastLevelInfo", lastLevelInfo);
}
}
}
function levelsPerSecRaw() {
if (lastLevelInfo.length < 2) {
return 0;
}
var timeSpentOnBosses = 0;
var levelsGainedFromBosses = 0;
lastLevelInfo.filter(function(levelInfo) {
return isBossLevel(levelInfo.level);
}).map(function(levelInfo) {
timeSpentOnBosses += levelInfo.timeTakenInSeconds;
levelsGainedFromBosses += levelInfo.levelsGained;
})
return (((getGameLevel() - lastLevelInfo.slice(-1).pop().level - levelsGainedFromBosses)
/ (s().m_rgGameData.timestamp - lastLevelInfo.slice(-1).pop().timeStarted - timeSpentOnBosses)) * 1000 ) / 1000;
}
function levelsPerSec() {
if (lastLevelInfo.length < 2) {
return 0;
}
var timeSpentOnBosses = 0;
var levelsGainedFromBosses = 0;
lastLevelInfo.slice(0, 10).filter(function(levelInfo) {
return isBossLevel(levelInfo.level);
}).map(function(levelInfo) {
timeSpentOnBosses += levelInfo.timeTakenInSeconds;
levelsGainedFromBosses += levelInfo.levelsGained;
})
return ((getGameLevel() - lastLevelInfo.slice(0, 10).slice(-1).pop().level - levelsGainedFromBosses)
/ (s().m_rgGameData.timestamp - lastLevelInfo.slice(0,10).slice(-1).pop().timeStarted - timeSpentOnBosses) * 1000 ) / 1000;
}
//at level 100 spam WH, Like New, and medics, based on your role
function useAbilitiesAt100() {
if (wormholeOn100 && !w.SteamDB_Wormhole_Timer) {
advLog("At level % 100 = 0, forcing the use of wormholes nonstop", 2);
// Fire one off now, so we don't wait the interval
if (bHaveItem(ABILITIES.WORMHOLE)) triggerAbility(ABILITIES.WORMHOLE);
w.SteamDB_Wormhole_Timer = w.setInterval(function(){
if (getGameLevel() % 100 !== 0) {
// We're not on a *00 level anymore, stop!!
w.clearInterval(w.SteamDB_Wormhole_Timer);
w.SteamDB_Wormhole_Timer = false;
return;
}
if (bHaveItem(ABILITIES.WORMHOLE)) triggerAbility(ABILITIES.WORMHOLE); //wormhole
}, 100);
/* ^ DO NOT TOUCH THIS. The fundamental idea of this strat is that we get as many wormhole
jumps in as we can when we hit a boss level. The server seems capable of taking a max of ~10wh/s
feel free to use the following (hasty) code to manually verify in console:
// 1. Note down number of remaining wormholes
var dateStart = Date.now(); var interval = setInterval(function() { g_Server.UseAbilities($J.noop, $J.noop, {requested_abilities: [{ability: 26}]}); }, 100);
// 2. Count 10 roughly 10 mississippi's (or however long you want), you will get the exact time taken between start and stop
clearInterval(interval); console.log("Time: ", (dateStop - dateStart) / 1000);
// => 13.002
// 3. Click on any ability that can be used to refresh the abilities table from the server
// 4. Subtract the old wormhole count from the new wormhole count and divide by the time taken to get wh/s
Generally wh/s will be somewhere between 2-8 - I've never seen it go higher than 9.5, hence the 100ms resolution.
Changing this to 50ms will not give you 20 wh/s.
Changing this to 1000ms will leave wormholes on the table, so to speak.
Yes, there will be overflow, but this a GOOD thing. a few wormholes that overflow and get us *closer* to the
next level x00 boss helps!
*/
}
//This should equate to approximately 1.8 Like News per second
if (likeNewOn100) {
advLog("At level % 100 = 0, forcing the use of a like new", 2);
tryUsingAbility(ABILITIES.LIKE_NEW, false, true); //like new
} else {
// if people have LIKE_NEWs, there's no harm in letting them use them
if (Math.random() <= 0.05) {
tryUsingAbility(ABILITIES.LIKE_NEW, false, true);
}
}
if (medicOn100) {
advLog("At level % 100 = 0, forcing the use of a medic", 2);
tryUsingAbility(ABILITIES.MEDICS, false, true); //medics
}
}
function useAutoPurchaseAbilities() {
if(!enableAutoPurchase || autoupgrade_update_hilight) { return; }
var elms = document.querySelectorAll(".container_purchase > div:not([class~='cantafford'])");
if(elms.length === 0) { return; }
var pData = s().m_rgPlayerData;
[].forEach.call(elms, function(elm) {
if(elm.style.display !== "") { return; }
var idx = parseInt(elm.id.split('_')[1]);
if(s().GetUpgradeCost(idx) < pData.gold) {
s().TryUpgrade(elm.querySelector('.link'));
}
});
}
var autoupgrade_update_hilight = true;
var autoupgrade_hp_threshold = 0;
function useAutoUpgrade() {
if(!enableAutoUpgradeDPS
&& !enableAutoUpgradeClick
&& !enableAutoUpgradeHP
&& !enableAutoUpgradeElemental
) {
autoupgrade_update_hilight = false;
return;
}
// fixes hiligh when we tick before elements are created
if(!document.querySelector('.container_upgrades')
|| !document.querySelector('.container_upgrades').hasChildNodes()
) {
return;
}
var upg_order = [
UPGRADES.ARMOR_PIERCING_ROUND,
UPGRADES.LIGHT_ARMOR,
UPGRADES.AUTO_FIRE_CANNON,
UPGRADES.LUCKY_SHOT,
];
if(enableAutoUpgradeElemental && ELEMENTS.LockedElement !== -1) { upg_order.push(ELEMENTS.LockedElement+3); }
var upg_map = {};
upg_order.forEach(function(i) { upg_map[i] = {}; });
var pData = s().m_rgPlayerData;
var pTree = s().m_rgPlayerTechTree;
var cache = s().m_UI.m_rgElementCache;
// calculate hp threshold based on mob dps
var mob = s().m_rgEnemies[0];
if(!!mob) {
var threshold = mob.m_data.dps * 300 * 2.5;
if(threshold > autoupgrade_hp_threshold) {
autoupgrade_hp_threshold = threshold;
}
}
var upg_enabled = [
enableAutoUpgradeClick && s().m_rgGameData.level > upgThreshold,
enableAutoUpgradeHP && pTree.max_hp < Math.max(100000, autoupgrade_hp_threshold),
enableAutoUpgradeDPS && s().m_rgGameData.level > upgThreshold,
];
// loop over all upgrades and find the most cost effective ones
s().m_rgTuningData.upgrades.forEach(function(upg, idx) {
if(upg_map.hasOwnProperty(upg.type)) {
var cost = s().GetUpgradeCost(idx) / parseFloat(upg.multiplier);
if(!upg_map[upg.type].hasOwnProperty('idx') || upg_map[upg.type].cost_per_mult > cost) {
if(upg.hasOwnProperty('required_upgrade') && s().GetUpgradeLevel(upg.required_upgrade) < upg.required_upgrade_level) { return; }
upg_map[upg.type] = {
'idx': idx,
'cost_per_mult': cost,
};
}
}
});
// do hilighting if needed
if(autoupgrade_update_hilight) {
autoupgrade_update_hilight = false;
// clear all currently hilighted
[].forEach.call(document.querySelectorAll('[id^="upgr_"] .info'),
function(elm) { elm.style.color = ''; });
// hilight targets
[].forEach.call(document.querySelectorAll(Object.keys(upg_map).map(function(i) {
if(i > UPGRADES.ARMOR_PIERCING_ROUND) {
return "#nonexistant";
} else {
return "#upgr_" + upg_map[i].idx + " .info";
}
})
.join(",")),
function(elm) { elm.style.setProperty('color', '#E1B21E', 'important'); });
}
// do upgrading
for(var i = 0; i < upg_order.length; i++ ) {
if(!upg_enabled[i] || upg_order[i] > UPGRADES.ARMOR_PIERCING_ROUND) { continue; }
// prioritize click upgrades over DPS ones, unless they are more cost effective
if(upg_order[i] === UPGRADES.AUTO_FIRE_CANNON && enableAutoUpgradeClick) {
if(upg_map[UPGRADES.AUTO_FIRE_CANNON].cost_per_mult > upg_map[UPGRADES.ARMOR_PIERCING_ROUND].cost_per_mult / 4) { continue; }
}
var tree = upg_map[upg_order[i]];
// upgrade crit/elemental when necessary
if(upg_order[i] === UPGRADES.ARMOR_PIERCING_ROUND) {
if(upg_map[UPGRADES.LUCKY_SHOT].cost_per_mult < upg_map[UPGRADES.ARMOR_PIERCING_ROUND].cost_per_mult) {
tree = upg_map[UPGRADES.LUCKY_SHOT];
}
else if(enableAutoUpgradeElemental
&& upg_map.hasOwnProperty(ELEMENTS.LockedElement+3)
&& upg_map[ELEMENTS.LockedElement+3].cost_per_mult < upg_map[UPGRADES.ARMOR_PIERCING_ROUND].cost_per_mult) {
tree = upg_map[ELEMENTS.LockedElement+3];
}
}
var key = 'upgr_' + tree.idx;
if(s().GetUpgradeCost(tree.idx) < pData.gold && cache.hasOwnProperty(key)) {
var elm = cache[key];
// valve pls...
s().TryUpgrade(!!elm.find ? elm.find('.link')[0] : elm.querySelector('.link'));
autoupgrade_update_hilight = true;
}
}
}
function toggleAutoUpgradeDPS(event) {
var value = enableAutoUpgradeDPS;
if(event !== undefined) {
value = handleCheckBox(event);
}
enableAutoUpgradeDPS = value;
}
function toggleAutoUpgradeClick(event) {
var value = enableAutoUpgradeClick;
if(event !== undefined) {
value = handleCheckBox(event);
}
enableAutoUpgradeClick = value;
}
function toggleAutoUpgradeHP(event) {
var value = enableAutoUpgradeHP;
if(event !== undefined) {
value = handleCheckBox(event);
}
enableAutoUpgradeHP = value;
}
function toggleAutoUpgradeElemental(event) {
var value = enableAutoUpgradeElemental;
if(event !== undefined) {
value = handleCheckBox(event);
}
enableAutoUpgradeElemental = value;
}
function toggleAutoPurchase(event) {
var value = enableAutoPurchase;
if(event !== undefined) {
value = handleCheckBox(event);
}
enableAutoPurchase = value;
}
function refreshPlayerData() {
advLog("Refreshing player data", 2);
w.g_Server.GetPlayerData(
function(rgResult) {
var instance = s();
if( rgResult.response.player_data ) {
instance.m_rgPlayerData = rgResult.response.player_data;
instance.ApplyClientOverrides('player_data');
instance.ApplyClientOverrides('ability');
instance.ApplyClientOverrides('upgrades');
}
if( rgResult.response.tech_tree ) {
instance.m_rgPlayerTechTree = rgResult.response.tech_tree;
if( rgResult.response.tech_tree.upgrades ) {
instance.m_rgPlayerUpgrades = w.V_ToArray( rgResult.response.tech_tree.upgrades );
} else {
instance.m_rgPlayerUpgrades = [];
}
}
instance.OnReceiveUpdate();
},
function() {},
true
);
}
function makeNumber(name, desc, value, min, max, listener) {
var label = document.createElement("label");
var description = document.createTextNode(desc);
var number = document.createElement("input");
number.type = "number";
number.name = name;
number.value = value;
number.min = min;
number.max = max;
number.onchange = listener;
w[number.name] = number;
label.appendChild(number);
label.appendChild(description);
label.appendChild(document.createElement("br"));
return label;
}
function makeDropdown(name, desc, value, values, listener) {
var label = document.createElement("label");
var description = document.createTextNode(desc);
var drop = document.createElement("select");
for(var k in values) {
var choice = document.createElement("option");
choice.value = values[k];
choice.textContent = k;
if(values[k] == value) {
choice.selected = true;
}
drop.appendChild(choice);
}
drop.name = name;
drop.style.marginRight = "5px";
drop.onchange = listener;
label.appendChild(drop);
label.appendChild(description);
label.appendChild(document.createElement("br"));
return label;
}
function makeCheckBox(name, desc, state, listener, reqRefresh) {
var asterisk = document.createElement('span');
asterisk.className = "asterisk";
asterisk.appendChild(document.createTextNode("*"));
var label = document.createElement("label");
var description = document.createTextNode(desc);
var checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.name = name;
checkbox.checked = state;
checkbox.onclick = listener;
w[checkbox.name] = checkbox.checked;
label.appendChild(checkbox);
label.appendChild(description);
if(reqRefresh) {
label.appendChild(asterisk);
}
label.appendChild(document.createElement("br"));
return label;
}
function handleEvent(event) {
handleCheckBox(event);
}
function handleCheckBox(event) {
var checkbox = event.target;
setPreference(checkbox.name, checkbox.checked);
w[checkbox.name] = checkbox.checked;
return checkbox.checked;
}
function toggleAutoClicker(event) {
var value = enableAutoClicker;
if(event !== undefined) {
value = handleCheckBox(event);
}
if(value) {
currentClickRate = clickRate;
} else {
currentClickRate = 0;
}
}
function toggleFingering(event) {
var value = enableFingering;
w.CSceneGame.prototype.ClearNewPlayer = function(){};
if(!s().m_spriteFinger) {
w.WebStorage.SetLocal('mg_how2click', 0);
s().CheckNewPlayer();
w.WebStorage.SetLocal('mg_how2click', 1);
}
if(event !== undefined) {
value = handleCheckBox(event);
}
if(value) {
s().m_containerParticles.addChild(s().m_spriteFinger);
} else {
s().m_containerParticles.removeChild(s().m_spriteFinger);
}
document.getElementById('newplayer').style.display = 'none';
}
function toggleAutoRefresh(event) {
var value = enableAutoRefresh;
if(event !== undefined) {
value = handleCheckBox(event);
}
if(value) {
autoRefreshPage(autoRefreshMinutes);
} else {
clearTimeout(refreshTimer);
}
}
function toggleRenderer(event) {
var value = disableRenderer;
if (event !== undefined) {
value = disableRenderer = handleCheckBox(event);
}
var ticker = w.PIXI.ticker.shared;
if (!value) {
ticker.autoStart = true;
ticker.start();
w.g_Minigame.Render = trt_oldRender;
w.g_Minigame.Render();
} else {
ticker.autoStart = false;
ticker.stop();
w.g_Minigame.Render = function() {};
}
}
var oldTvBg = "";
function toggleChen(event) {
var value = enableChen;
if(event !== undefined) {
value = handleCheckBox(event);
}
enableChen = value;
if (enableChen) {
addChen();
honkingIntenstifys(isBossLevel(getGameLevel()));
oldTvBg = w.$J('.tv_ui').css('background-image');
w.$J('.tv_ui').css('background-image', 'url(//i.imgur.com/9wmTsxr.png)');
} else {
w.$J('.tv_ui').css('background-image', oldTvBg);
honkingIntenstifys(false);
}
}
function addChen() {
var chenDiv = document.querySelector("#Chen");
if (!chenDiv) { // We can only handle one Chen D:
var chenHTML = document.createElement('div');
chenHTML.id = "Chen";
document.querySelector("#uicontainer > div.tv_ui").appendChild(chenHTML);
}
}
function honkingIntenstifys(isBoss, hide) {
var chenDiv = document.querySelector("#Chen");
if (chenDiv && enableChen && isBoss) {
chenDiv.style.backgroundImage = "url(//i.imgur.com/eGnE1cD.gif)";
} else if (chenDiv && enableChen) {
chenDiv.style.backgroundImage = "url(//i.imgur.com/xMbQChA.png)";
}
if (chenDiv && !enableChen) {
chenDiv.style.visibility = "hidden";
} else if (chenDiv) {
chenDiv.style.visibility = "visible";
}
}
function autoRefreshPage(autoRefreshMinutes){
var timerValue = (autoRefreshMinutes + autoRefreshMinutesRandomDelay * Math.random()) * 60 * 1000;
refreshTimer = setTimeout(function() {
autoRefreshHandler();
}, timerValue);
}
function autoRefreshHandler() {
// Only skip on % 100 levels when it's been less than the maximum delay specified.
if(getGameLevel() % 100 === 0 && autoRefreshDuringBossDelayTotal < autoRefreshDuringBossDelay) {
advLog('Not refreshing (boss level)', 5);
autoRefreshDuringBossDelayTotal += autoRefreshDuringBossDelayStep;
setTimeout(autoRefreshHandler, autoRefreshDuringBossDelayStep);
} else {
advLog('Refreshing (not a boss level)', 5);
w.location.reload(true);
}
}
function toggleElementLock(event) {
var value = enableElementLock;
if(event !== undefined) {
value = handleCheckBox(event);
}
if(value) {
lockElements();
} else {
unlockElements();
}
}
function toggleCritText(event) {
var value = removeCritText;
if(event !== undefined) {
value = handleCheckBox(event);
}
if (value) {
// Replaces the entire crit display function.
s().DoCritEffect = function() {};
} else {
s().DoCritEffect = trt_oldCrit;
}
}
function toggleAllText(event) {
var value = removeAllText;
if(event !== undefined) {
value = handleCheckBox(event);
}
if (value) {
// Replaces the entire text function.
s().m_rgClickNumbers.push = function(elem){
elem.container.removeChild(elem);
};
} else {
s().m_rgClickNumbers.push = trt_oldPush;
}
}
function toggleTrackTroll(event) {
var value = enableTrollTrack;
if(event !== undefined) {
value = handleCheckBox(event);
}
if(value) {
CUI.prototype.UpdateLog = localUpdateLog;
} else {
CUI.prototype.UpdateLog = originalUpdateLog;
}
}
function updateLogLevel(event) {
if(event !== undefined) {
logLevel = event.target.value;
}
}
function setPreference(key, value) {
try {
if(localStorage !== 'undefined') {
localStorage.setItem('steamdb-minigame-wormholers/' + key, value);
}
} catch (e) {
console.log(e); // silently ignore error
}
}
function getPreference(key, defaultValue) {
try {
if(localStorage !== 'undefined') {
var result = localStorage.getItem('steamdb-minigame-wormholers/' + key);
return (result !== null ? result : defaultValue);
}
} catch (e) {
console.log(e); // silently ignore error
return defaultValue;
}
}
function getPreferenceBoolean(key, defaultValue) {
return (getPreference(key, defaultValue.toString()) == "true");
}
function unlockElements() {
var fire = document.querySelector("a.link.element_upgrade_btn[data-type=\"3\"]");
var water = document.querySelector("a.link.element_upgrade_btn[data-type=\"4\"]");
var air = document.querySelector("a.link.element_upgrade_btn[data-type=\"5\"]");
var earth = document.querySelector("a.link.element_upgrade_btn[data-type=\"6\"]");
var elems = [fire, water, air, earth];
for (var i=0; i < elems.length; i++) {
elems[i].style.visibility = "visible";
}
}
//I'm sorry of the way I name things. This function predicts jumps on a warp boss level, returns the value.
function estimateJumps() {
var level = getGameLevel();
var wormholesNow = 0;
//Gather total wormholes active.
for (var i = 0; i <= 2; i++) {
if (typeof w.g_Minigame.m_CurrentScene.m_rgLaneData[i].abilities[26] !== 'undefined') {
wormholesNow += w.g_Minigame.m_CurrentScene.m_rgLaneData[i].abilities[26];
}
}
//During baws round fc
if (level % CONTROL.rainingRounds == 0)
{
if (predictLastWormholesUpdate !== wormholesNow)
{
predictTicks++;
predictJumps += wormholesNow;
predictLastWormholesUpdate = wormholesNow;
}
}
else
{
predictTicks = 0;
predictJumps = 0;
predictLastWormholesUpdate = 0;
return 0;
}
return predictJumps / predictTicks * (s().m_rgGameData.timestamp - s().m_rgGameData.timestamp_level_start);
}
function lockElements() {
var elementMultipliers = [
s().m_rgPlayerTechTree.damage_multiplier_fire,
s().m_rgPlayerTechTree.damage_multiplier_water,
s().m_rgPlayerTechTree.damage_multiplier_air,
s().m_rgPlayerTechTree.damage_multiplier_earth
];
var elem = (parseInt(w.g_steamID.slice(-3), 10) + parseInt(w.g_GameID, 10)) % 4;
// If more than two elements are leveled to 3 or higher, do not enable lock
var leveled = 0;
var lastLeveled = -1;
for (var i=0; i < elementMultipliers.length; i++){
advLog("Element " + i + " is at level " + (elementMultipliers[i]-1)/1.5, 3);
if ((elementMultipliers[i]-1)/1.5 >= 3) {
leveled++;
// Only used if there is only one so overwriting it doesn't matter
lastLeveled = i;
}
}
if (leveled >= 2) {
advLog("More than 2 elementals leveled to 3 or above, not locking.", 1);
} else if (leveled == 1) {
advLog("Found existing lock on " + lastLeveled + ", locking to it.", 1);
lockToElement(lastLeveled);
} else {
advLog("Locking to element " + elem + " as chosen by SteamID", 1);
lockToElement(elem);
}
}
function lockToElement(element) {
var fire = document.querySelector("a.link.element_upgrade_btn[data-type=\"3\"]");
var water = document.querySelector("a.link.element_upgrade_btn[data-type=\"4\"]");
var air = document.querySelector("a.link.element_upgrade_btn[data-type=\"5\"]");
var earth = document.querySelector("a.link.element_upgrade_btn[data-type=\"6\"]");
var elems = [fire, water, air, earth];
for (var i=0; i < elems.length; i++) {
if (i === element) {
continue;
}
elems[i].style.visibility = "hidden";
}
ELEMENTS.LockedElement = element;
}
function displayText(x, y, strText, color) {
var text = new w.PIXI.Text(strText, {font: "35px 'Press Start 2P'", fill: color, stroke: '#000', strokeThickness: 2 });
text.x = x;
text.y = y;
s().m_containerUI.addChild( text );
text.container = s().m_containerUI;
var e = new w.CEasingSinOut( text.y, -200, 1000 );
e.parent = text;
text.m_easeY = e;
e = new w.CEasingSinOut( 2, -2, 1000 );
e.parent = text;
text.m_easeAlpha = e;
s().m_rgClickNumbers.push(text);
}
function updatePlayersInGame() {
var laneData = s().m_rgLaneData;
var totalPlayers =
laneData[ 0 ].players +
laneData[ 1 ].players +
laneData[ 2 ].players;
ELEMENTS.PlayersInGame.textContent = totalPlayers + "/1500";
}
function fixActiveCapacityUI() {
w.$J('.tv_ui').css('background-image', 'url(//i.imgur.com/9R0436k.gif)');
w.$J('#activeinlanecontainer').css('height', '154px');
w.$J('#activitycontainer').css('height', '270px');
w.$J('#activityscroll').css('height', '270px');
}
function goToLaneWithBestTarget(level) {
// We can overlook spawners if all spawners are 40% hp or higher and a creep is under 10% hp
var spawnerOKThreshold = 0.4;
var creepSnagThreshold = 0.1;
var targetFound = false;
var lowHP = 0;
var lowLane = 0;
var lowTarget = 0;
var lowPercentageHP = 0;
var preferredLane = -1;
var preferredTarget = -1;
// determine which lane and enemy is the optimal target
var enemyTypePriority = [
ENEMY_TYPE.TREASURE,
ENEMY_TYPE.BOSS,
ENEMY_TYPE.MINIBOSS,
ENEMY_TYPE.SPAWNER,
ENEMY_TYPE.CREEP
];
var i;
var skippingSpawner = false;
var skippedSpawnerLane = 0;
var skippedSpawnerTarget = 0;
var targetIsTreasure = false;
var targetIsBoss = false;
for (var k = 0; !targetFound && k < enemyTypePriority.length; k++) {
targetIsTreasure = (enemyTypePriority[k] == ENEMY_TYPE.TREASURE);
targetIsBoss = (enemyTypePriority[k] == ENEMY_TYPE.BOSS);
var enemies = [];
// gather all the enemies of the specified type.
for (i = 0; i < 3; i++) {
for (var j = 0; j < 4; j++) {
var enemy = s().GetEnemy(i, j);
if (enemy && enemy.m_data.type == enemyTypePriority[k]) {
enemies[enemies.length] = enemy;
}
}
}
//Prefer lane with raining gold, unless current enemy target is a treasure or boss.
if(!targetIsTreasure && !targetIsBoss) {
var potential = 0;
// Loop through lanes by elemental preference
var sortedLanes = sortLanesByElementals();
for(var notI = 0; notI < sortedLanes.length; notI++) {
// Maximize compability with upstream
i = sortedLanes[notI];
// ignore if lane is empty
if(s().m_rgGameData.lanes[i].dps === 0) {
continue;
}
var stacks = 0;
if(typeof s().m_rgLaneData[i].abilities[ABILITIES.RAINING_GOLD] != 'undefined') {
stacks = s().m_rgLaneData[i].abilities[ABILITIES.RAINING_GOLD];
advLog('[Gold rain] stacks: ' + stacks, 5);
for(var m = 0; m < s().m_rgEnemies.length; m++){
if(s().m_rgEnemies[m].m_nLane != i){
continue;
}
advLog("[Gold rain] An enemy exists in raining gold lane: " + (i + 1), 5);
var enemyGold = s().m_rgEnemies[m].m_data.gold;
if(stacks * enemyGold > potential) {
potential = stacks * enemyGold;
preferredTarget = s().m_rgEnemies[m].m_nID;
preferredLane = i;
}
}
advLog("[Gold rain] preferredLane: " + preferredLane, 5);
advLog("[Gold rain] preferredTarget: " + preferredTarget, 5);
}
}
}
// target the enemy of the specified type with the lowest hp
var mostHPDone = 0;
for (i = 0; i < enemies.length; i++) {
if (enemies[i] && !enemies[i].m_bIsDestroyed) {
// Only select enemy and lane if the preferedLane matches the potential enemy lane
if(lowHP < 1 || enemies[i].m_flDisplayedHP < lowHP) {
var element = s().m_rgGameData.lanes[enemies[i].m_nLane].element;
var dmg = s().CalculateDamage(
s().m_rgPlayerTechTree.dps,
element
);
if(mostHPDone <= dmg) {
mostHPDone = dmg;
} else {
continue;
}
targetFound = true;
lowHP = enemies[i].m_flDisplayedHP;
lowLane = enemies[i].m_nLane;
lowTarget = enemies[i].m_nID;
}
var percentageHP = enemies[i].m_flDisplayedHP / enemies[i].m_data.max_hp;
if (lowPercentageHP === 0 || percentageHP < lowPercentageHP) {
lowPercentageHP = percentageHP;
}
}
}
if(preferredLane != -1 && preferredTarget != -1){
lowLane = preferredLane;
lowTarget = preferredTarget;
advLog('Switching to a lane with best raining gold benefit', 2);
}
// If we just finished looking at spawners,
// AND none of them were below our threshold,
// remember them and look for low creeps (so don't quit now)
// Don't skip spawner if lane has raining gold
if ((enemyTypePriority[k] == ENEMY_TYPE.SPAWNER && lowPercentageHP > spawnerOKThreshold) && preferredLane == -1) {
skippedSpawnerLane = lowLane;
skippedSpawnerTarget = lowTarget;
skippingSpawner = true;
targetFound = false;
}
// If we skipped a spawner and just finished looking at creeps,
// AND the lowest was above our snag threshold,
// just go back to the spawner!
if (skippingSpawner && enemyTypePriority[k] == ENEMY_TYPE.CREEP && lowPercentageHP > creepSnagThreshold ) {
lowLane = skippedSpawnerLane;
lowTarget = skippedSpawnerTarget;
}
}
// go to the chosen lane
if (targetFound) {
if (s().m_nExpectedLane != lowLane) {
advLog('Switching to lane' + lowLane, 3);
s().TryChangeLane(lowLane);
}
// target the chosen enemy
if (s().m_nTarget != lowTarget) {
advLog('Switching targets', 3);
s().TryChangeTarget(lowTarget);
}
}
var levelRainingMod = level % CONTROL.rainingRounds;
// Prevent attack abilities and items if up against a boss or treasure minion
if (targetIsTreasure || (level < CONTROL.speedThreshold || levelRainingMod === 0 || CONTROL.rainingSafeRounds >= (CONTROL.rainingRounds - levelRainingMod))) {
BOSS_DISABLED_ABILITIES.forEach(disableAbility);
} else {
BOSS_DISABLED_ABILITIES.forEach(enableAbility);
}
// Disable raining gold for the first levels
if(level < CONTROL.rainingRounds) {
disableAbility(ABILITIES.RAINING_GOLD);
} else {
enableAbility(ABILITIES.RAINING_GOLD);
}
disableAbility(ABILITIES.REFLECT_DAMAGE);
disableAbility(ABILITIES.TACTICAL_NUKE);
}
function hasMaxCriticalOnLane() {
var goodLuckCharms = getActiveAbilityLaneCount(ABILITIES.GOOD_LUCK_CHARMS);
var crit = getActiveAbilityLaneCount(ABILITIES.CRIT);
var totalCritical = goodLuckCharms + crit;
return totalCritical >= 99;
}
function useAbilities(level)
{
var currentLane = s().m_nExpectedLane;
var i = 0;
var enemyCount = 0;
var enemySpawnerExists = false;
var enemySpawnerHealthPercent = false;
var enemy = false;
var enemyBossHealthPercent = 0;
// If we have spare WH's fire them
enableAbility(ABILITIES.WORMHOLE);
var wormholeButton = getAbilityButton(ABILITIES.WORMHOLE);
if (getNextPredictedLevel() % 100 < 90
&& wormholeButton
&& wormholeButton.quantity > 15000) {
if (bHaveItem(ABILITIES.WORMHOLE)) {
triggerAbility(ABILITIES.WORMHOLE);
}
}
// Cripple Monster
if(canUseAbility(ABILITIES.CRIPPLE_MONSTER)) {
if (level > CONTROL.speedThreshold && level % CONTROL.rainingRounds !== 0 && level % 10 === 0) {
enemy = s().GetEnemy(s().m_rgPlayerData.current_lane, s().m_rgPlayerData.target);
if (enemy && enemy.m_data.type == ENEMY_TYPE.BOSS) {
enemyBossHealthPercent = enemy.m_flDisplayedHP / enemy.m_data.max_hp;
if (enemyBossHealthPercent>0.5){
advLog("Cripple Monster available and used on boss", 2);
triggerAbility(ABILITIES.CRIPPLE_MONSTER);
}
}
}
}
// Medic & Pumped Up
if (tryUsingAbility(ABILITIES.PUMPED_UP)){
// Pumped Up is purchased, cooled down, and needed. Trigger it.
advLog('Pumped up is always good.', 2);
}
else
{
// check if Medics is purchased and cooled down
if (tryUsingAbility(ABILITIES.MEDICS)) {
advLog('BadMedic is purchased, cooled down. Trigger it.', 2);
}
if(level > 5000 && tryUsingAbility(ABILITIES.REFLECT_DAMAGE)) {
advLog('We have reflect damage, cooled down. Trigger it.', 2);
}
else if(level > 2500 && tryUsingAbility(ABILITIES.STEAL_HEALTH)) {
advLog('We have steal health, cooled down. Trigger it.', 2);
}
else if (tryUsingAbility(ABILITIES.GOD_MODE)) {
advLog('We have god mode, cooled down. Trigger it.', 2);
}
}
var levelRainingMod = level % CONTROL.rainingRounds;
if(levelRainingMod === 0) {
//advLog('Trying to rain and enable click after a while...', 1);
tryUsingAbility(ABILITIES.DECREASE_COOLDOWNS, true);
tryUsingAbility(ABILITIES.RAINING_GOLD);
}
// Skip doing any damage x levels before upcoming wormhole round
if(CONTROL.rainingSafeRounds >= (CONTROL.rainingRounds - levelRainingMod)) {
tryUsingAbility(ABILITIES.RESURRECTION, true);
return;
}
// Good Luck Charms / Crit
if(!hasMaxCriticalOnLane())
{
if (tryUsingAbility(ABILITIES.CRIT)){
// Crits is purchased, cooled down, and needed. Trigger it.
advLog('Crit chance is always good.', 3);
}
}
if(!hasMaxCriticalOnLane())
{
// check if Good Luck Charms is purchased and cooled down
if (tryUsingAbility(ABILITIES.GOOD_LUCK_CHARMS)) {
advLog('Good Luck Charms is purchased, cooled down, and needed. Trigger it.', 2);
}
}
// Cluster Bomb
if (canUseAbility(ABILITIES.CLUSTER_BOMB)) {
//Check lane has monsters to explode
enemyCount = 0;
enemySpawnerExists = false;
//Count each slot in lane
for (i = 0; i < 4; i++) {
enemy = s().GetEnemy(currentLane, i);
if (enemy) {
enemyCount++;
if (enemy.m_data.type === 0) {
enemySpawnerExists = true;
}
}
}
//Bombs away if spawner and 2+ other monsters
if (enemySpawnerExists && enemyCount >= 3) {
if (!tryUsingAbility(ABILITIES.DECREASE_COOLDOWNS, true)) {
triggerAbility(ABILITIES.CLUSTER_BOMB);
}
}
}
// Napalm
if (canUseAbility(ABILITIES.NAPALM)) {
//Check lane has monsters to burn
enemyCount = 0;
enemySpawnerExists = false;
//Count each slot in lane
for (i = 0; i < 4; i++) {
enemy = s().GetEnemy(currentLane, i);
if (enemy) {
enemyCount++;
if (enemy.m_data.type === 0) {
enemySpawnerExists = true;
}
}
}
//Burn them all if spawner and 2+ other monsters
if (enemySpawnerExists && enemyCount >= 3) {
if (!tryUsingAbility(ABILITIES.DECREASE_COOLDOWNS, true)) {
triggerAbility(ABILITIES.NAPALM);
}
}
}
// Morale Booster
if (canUseAbility(ABILITIES.MORALE_BOOSTER)) {
var numberOfWorthwhileEnemies = 0;
for(i = 0; i < s().m_rgGameData.lanes[s().m_nExpectedLane].enemies.length; i++) {
//Worthwhile enemy is when an enamy has a current hp value of at least 1,000,000
if(s().m_rgGameData.lanes[s().m_nExpectedLane].enemies[i].hp > 1000000) {
numberOfWorthwhileEnemies++;
}
}
if(numberOfWorthwhileEnemies >= 2) {
// Moral Booster is purchased, cooled down, and needed. Trigger it.
advLog('Moral Booster is purchased, cooled down, and needed. Trigger it.', 2);
triggerAbility(ABILITIES.MORALE_BOOSTER);
}
}
// Tactical Nuke
if(canUseAbility(ABILITIES.TACTICAL_NUKE) && (level % 100 !== 0) && (level % 100) <= 97) {
enemy = s().GetEnemy(s().m_rgPlayerData.current_lane, s().m_rgPlayerData.target);
// check whether current target is a boss
if (enemy && enemy.m_data.type == ENEMY_TYPE.BOSS) {
if (level >= CONTROL.speedThreshold) { // Start nuking bosses at level CONTROL.speedThreshold
enemyBossHealthPercent = enemy.m_flDisplayedHP / enemy.m_data.max_hp;
// Use Nuke on boss with >= 50% HP but only if Raining Gold is not active in the lane
if (enemyBossHealthPercent >= 0.5 && getActiveAbilityLaneCount(ABILITIES.RAINING_GOLD) <= 0) {
if (!tryUsingAbility(ABILITIES.DECREASE_COOLDOWNS, true)) {
advLog("Tactical Nuke is purchased, cooled down, and needed. Nuke 'em.", 2);
triggerAbility(ABILITIES.TACTICAL_NUKE);
}
}
}
}
else {
//Check that the lane has a spawner and record it's health percentage
//Count each slot in lane
for (i = 0; i < 4; i++) {
enemy = s().GetEnemy(currentLane, i);
if (enemy && enemy.m_data.type == ENEMY_TYPE.SPAWNER) {
enemySpawnerHealthPercent = enemy.m_flDisplayedHP / enemy.m_data.max_hp;
// If there is a spawner and it's health is between 60% and 30%, nuke it!
if (enemySpawnerHealthPercent < 0.6 && enemySpawnerHealthPercent > 0.3) {
if (!tryUsingAbility(ABILITIES.DECREASE_COOLDOWNS, true)) {
advLog("Tactical Nuke is purchased, cooled down, and needed. Nuke 'em.", 2);
triggerAbility(ABILITIES.TACTICAL_NUKE);
}
}
break; // No reason to continue the loop after finding a spawner
}
}
}
}
// Cripple Spawner
if(canUseAbility(ABILITIES.CRIPPLE_SPAWNER)) {
//Check that the lane has a spawner and record it's health percentage
enemySpawnerExists = false;
enemySpawnerHealthPercent = 0.0;
//Count each slot in lane
for (i = 0; i < 4; i++) {
enemy = s().GetEnemy(currentLane, i);
if (enemy) {
if (enemy.m_data.type === 0) {
enemySpawnerExists = true;
enemySpawnerHealthPercent = enemy.m_flDisplayedHP / enemy.m_data.max_hp;
}
}
}
// If there is a spawner and it's health is above 95%, cripple it!
if (enemySpawnerExists && enemySpawnerHealthPercent > 0.95) {
advLog("Cripple Spawner available, and needed. Cripple 'em.", 2);
triggerAbility(ABILITIES.CRIPPLE_SPAWNER);
}
}
// Gold Rain
if (canUseAbility(ABILITIES.RAINING_GOLD)) {
// only use if the speed threshold has not been reached,
// or it's a designated gold round after the threshold
if (level > CONTROL.disableGoldRainLevels && (level < CONTROL.speedThreshold || level % CONTROL.rainingRounds === 0)) {
enemy = s().GetEnemy(s().m_rgPlayerData.current_lane, s().m_rgPlayerData.target);
// check if current target is a boss, otherwise its not worth using the gold rain
if (enemy && enemy.m_data.type == ENEMY_TYPE.BOSS) {
enemyBossHealthPercent = enemy.m_flDisplayedHP / enemy.m_data.max_hp;
if (enemyBossHealthPercent >= 0.6 || level % CONTROL.rainingRounds === 0) { // We want sufficient time for the gold rain to be applicable
// Gold Rain is purchased, cooled down, and needed. Trigger it.
advLog('Gold rain is purchased and cooled down, Triggering it on boss', 2);
triggerAbility(ABILITIES.RAINING_GOLD);
}
}
}
}
// Metal Detector
if(canUseAbility(ABILITIES.METAL_DETECTOR)) {
enemy = s().GetEnemy(s().m_rgPlayerData.current_lane, s().m_rgPlayerData.target);
// check if current target is a boss, otherwise we won't use metal detector
if (enemy && enemy.m_data.type == ENEMY_TYPE.BOSS) {
enemyBossHealthPercent = enemy.m_flDisplayedHP / enemy.m_data.max_hp;
// we want to use metal detector at 25% hp, or even less
if (enemyBossHealthPercent <= 0.25) { // We want sufficient time for the metal detector to be applicable
// Metal Detector is purchased, cooled down, and needed. Trigger it.
advLog('Metal Detector is purchased and cooled down, Triggering it on boss', 2);
triggerAbility(ABILITIES.METAL_DETECTOR);
}
}
}
// Treasure
if (canUseAbility(ABILITIES.TREASURE)) {
// check if current level is higher than 50
if (level > 50) {
enemy = s().GetTargetedEnemy();
// check if current target is a boss, otherwise we won't use metal detector
if (enemy && enemy.type == ENEMY_TYPE.BOSS) {
enemyBossHealthPercent = enemy.hp / enemy.max_hp;
// we want to use Treasure at 25% hp, or even less
if (enemyBossHealthPercent <= 0.25) { // We want sufficient time for the metal detector to be applicable
// Treasure is purchased, cooled down, and needed. Trigger it.
advLog('Treasure is purchased and cooled down, triggering it.', 2);
triggerAbility(ABILITIES.TREASURE);
}
}
}
else {
// Treasure is purchased, cooled down, and needed. Trigger it.
advLog('Treasure is purchased and cooled down, triggering it.', 2);
triggerAbility(ABILITIES.TREASURE);
}
}
// Max Elemental
if (tryUsingAbility(ABILITIES.MAX_ELEMENTAL_DAMAGE, true)) {
// Max Elemental Damage is purchased, cooled down, and needed. Trigger it.
advLog('Max Elemental Damage is purchased and cooled down, triggering it.', 2);
}
// Resurrect
if(level % 10 === 9 && tryUsingAbility(ABILITIES.RESURRECTION)) {
// Resurrect is purchased and we are using it.
advLog('Triggered Resurrect.');
}
}
function attemptRespawn() {
if ((s().m_bIsDead) && ((s().m_rgPlayerData.time_died) + 5) < (s().m_nTime)) {
w.RespawnPlayer();
}
}
function bHaveItem(itemId) {
var items = s().m_rgPlayerTechTree.ability_items;
for(var i = 0; i < items.length; ++i) {
if(items[i].ability == itemId) {
return true;
}
}
return false;
}
function canUseAbility(abilityId, forceAbility) {
if(!s().bHaveAbility(abilityId) && !bHaveItem(abilityId)) {
return false;
}
return s().GetCooldownForAbility(abilityId) <= 0 && (isAbilityEnabled(abilityId) || forceAbility);
}
function tryUsingAbility(itemId, checkInLane, forceAbility) {
if (!canUseAbility(itemId, forceAbility)) {
return false;
}
if (checkInLane && getActiveAbilityTimeout(itemId) >= getCurrentTime()) {
return false;
}
var level = getGameLevel();
var levelsPer = levelsPerSec();
var needs_to_be_blocked = false;
var two_digit_level = level % 100;
var needs_to_be_blocked = (BOSS_DISABLED_ABILITIES.indexOf(itemId) != -1);
// must not use any damaging ability on boss levels
if (two_digit_level == 0 && needs_to_be_blocked) {
return false;
}
// don't let good luck charm run up to x100 levels
if (itemId === ABILITIES.GOOD_LUCK_CHARMS && two_digit_level + levelsPer * 20 > 100) {
return false;
}
// Randomly Don't use this ability when we're getting close to the boss
// This avoids overflow damage
if (two_digit_level > 50 && needs_to_be_blocked) {
// Calculate current ability usage rate
var nextTickLevel = Math.ceil(level + levelsPer);
var nextWHLevel = Math.ceil(nextTickLevel / 100)*100;
var abilityRate = Math.min( 1, Math.sqrt( nextWHLevel - nextTickLevel )/10 + minAbilityUsePercent );
if( Math.random() < (1 - abilityRate) ) {
advLog('Rate limited ability - not using');
return false;
}
}
triggerAbility(itemId);
return true;
}
function getAbilityButton(ability) {
return s().m_rgPlayerTechTree.ability_items.filter(function(item) { return item.ability === ability; })[0]
}
function triggerAbility(abilityId) {
if (abilityId === ABILITIES.WORMHOLE) {
// Fire this bad boy off immediately
g_Server.UseAbilities(function() {
var wormholeButton = getAbilityButton(ABILITIES.WORMHOLE);
if (wormholeButton) {
wormholeButton.quantity--;
}
},
function() {
advLog('lost a wormhole :(', 2);
}, {requested_abilities: [{ability: ABILITIES.WORMHOLE}]});
} else {
s().m_rgAbilityQueue.push({'ability': abilityId});
}
var nCooldownDuration = s().m_rgTuningData.abilities[abilityId].cooldown;
s().ClientOverride('ability', abilityId, Math.floor(Date.now() / 1000) + nCooldownDuration);
s().ApplyClientOverrides('ability', true);
}
function toggleAbilityVisibility(abilityId, show) {
var vis = show === true ? "visible" : "hidden";
var elem = document.getElementById('ability_' + abilityId);
// temporary
if(!elem) {
elem = document.getElementById('abilityitem_' + abilityId);
}
if (elem && elem.childElements() && elem.childElements().length >= 1) {
elem.childElements()[0].style.visibility = vis;
}
}
function disableAbility(abilityId) {
toggleAbilityVisibility(abilityId, false);
}
function enableAbility(abilityId) {
toggleAbilityVisibility(abilityId, true);
}
function isAbilityEnabled(abilityId) {
var elem = document.getElementById('ability_' + abilityId);
// temporary
if(!elem) {
elem = document.getElementById('abilityitem_' + abilityId);
}
if (elem && elem.childElements() && elem.childElements().length >= 1) {
return elem.childElements()[0].style.visibility !== "hidden";
}
return false;
}
function getActiveAbilityTimeout(ability) {
var timeout = 0;
var abilities = s().m_rgGameData.lanes[s().m_rgPlayerData.current_lane].active_player_abilities;
var count = 0;
for(var i = 0; i < abilities.length; i++) {
if(abilities[i].ability == ability && abilities[i].timestamp_done > timeout) {
timeout = abilities[i].timestamp_done;
}
}
return timeout;
}
function getActiveAbilityLaneCount(ability) {
var now = getCurrentTime();
var abilities = s().m_rgGameData.lanes[s().m_rgPlayerData.current_lane].active_player_abilities;
var count = 0;
for(var i = 0; i < abilities.length; i++) {
if(abilities[i].ability == ability && abilities[i].timestamp_done > now) {
count++;
}
}
return count;
}
function sortLanesByElementals() {
var elementPriorities = [
s().m_rgPlayerTechTree.damage_multiplier_fire,
s().m_rgPlayerTechTree.damage_multiplier_water,
s().m_rgPlayerTechTree.damage_multiplier_air,
s().m_rgPlayerTechTree.damage_multiplier_earth
];
var lanes = s().m_rgGameData.lanes;
var lanePointers = [];
for (var i = 0; i < lanes.length; i++) {
lanePointers[i] = i;
}
lanePointers.sort(function(a, b) {
return elementPriorities[lanes[b].element - 1] - elementPriorities[lanes[a].element - 1];
});
advLog("Lane IDs : " + lanePointers[0] + " " + lanePointers[1] + " " + lanePointers[2], 4);
advLog("Elements : " + lanes[lanePointers[0]].element + " " + lanes[lanePointers[1]].element + " " + lanes[lanePointers[2]].element, 4);
return lanePointers;
}
function getCurrentTime() {
return s().m_rgGameData.timestamp;
}
function advLog(msg, lvl) {
if (lvl <= logLevel) {
console.log(msg);
}
}
if(w.SteamDB_Minigame_Timer) {
w.clearInterval(w.SteamDB_Minigame_Timer);
}
w.SteamDB_Minigame_Timer = w.setInterval(function(){
if (w.g_Minigame
&& s().m_bRunning
&& s().m_rgPlayerTechTree
&& s().m_rgGameData) {
w.clearInterval(w.SteamDB_Minigame_Timer);
firstRun();
w.SteamDB_Minigame_Timer = w.setInterval(MainLoop, 1000);
}
}, 1000);
// reload page if game isn't fully loaded, regardless of autoRefresh setting
w.setTimeout(function() {
// m_rgGameData is 'undefined' if stuck at 97/97 or below
if (!w.g_Minigame
|| !w.g_Minigame.m_CurrentScene
|| !w.g_Minigame.m_CurrentScene.m_rgGameData) {
w.location.reload(true);
}
}, autoRefreshSecondsCheckLoadedDelay * 1000);
appendBreadcrumbsTitleInfo();
function enhanceTooltips() {
var trt_oldTooltip = w.fnTooltipUpgradeDesc;
w.fnTooltipUpgradeDesc = function(context){
var $context = w.$J(context);
var desc = $context.data('desc');
var strOut = desc;
var multiplier = parseFloat( $context.data('multiplier') );
switch( $context.data('upgrade_type') ) {
case 2: // Type for click damage. All tiers.
strOut = trt_oldTooltip(context);
var currentCritMultiplier = s().m_rgPlayerTechTree.damage_multiplier_crit;
var currentCrit = s().m_rgPlayerTechTree.damage_per_click * currentCritMultiplier;
var newCrit = s().m_rgTuningData.player.damage_per_click * (s().m_rgPlayerTechTree.damage_per_click_multiplier + multiplier) * currentCritMultiplier;
strOut += '
Crit Click: ' + w.FormatNumberForDisplay( currentCrit ) + ' => ' + w.FormatNumberForDisplay( newCrit );
break;
case 7: // Lucky Shot's type.
var currentMultiplier = s().m_rgPlayerTechTree.damage_multiplier_crit;
var newMultiplier = currentMultiplier + multiplier;
var dps = s().m_rgPlayerTechTree.dps;
var clickDamage = s().m_rgPlayerTechTree.damage_per_click;
strOut += '
You can have multiple crits in a second. The server combines them into one.';
strOut += '
Crit Percentage: ' + (s().m_rgPlayerTechTree.crit_percentage * 100).toFixed(1) + '%';
strOut += '
Critical Damage Multiplier:';
strOut += '
Current: ' + ( currentMultiplier ) + 'x';
strOut += '
Next Level: ' + ( newMultiplier ) + 'x';
strOut += '
Damage with one crit:';
strOut += '
DPS: ' + w.FormatNumberForDisplay( currentMultiplier * dps ) + ' => ' + w.FormatNumberForDisplay( newMultiplier * dps );
strOut += '
Click: ' + w.FormatNumberForDisplay( currentMultiplier * clickDamage ) + ' => ' + w.FormatNumberForDisplay( newMultiplier * clickDamage );
strOut += '
Base Increased By: ' + w.FormatNumberForDisplay(multiplier) + 'x';
break;
case 9: // Boss Loot Drop's type
var bossLootChance = s().m_rgPlayerTechTree.boss_loot_drop_percentage * 100;
strOut += '
Boss Loot Drop Rate:';
strOut += '
Current: ' + bossLootChance.toFixed(0) + '%';
strOut += '
Next Level: ' + (bossLootChance + multiplier * 100).toFixed(0) + '%';
strOut += '
Base Increased By: ' + w.FormatNumberForDisplay(multiplier * 100) + '%';
break;
default:
return trt_oldTooltip(context);
}
return strOut;
};
var trt_oldElemTooltip = w.fnTooltipUpgradeElementDesc;
w.fnTooltipUpgradeElementDesc = function (context) {
var strOut = trt_oldElemTooltip(context);
var $context = w.$J(context);
//var upgrades = s().m_rgTuningData.upgrades.slice(0);
// Element Upgrade index 3 to 6
var idx = $context.data('type');
// Is the current tooltip for the recommended element?
var isRecommendedElement = (ELEMENTS.LockedElement == idx - 3);
if (isRecommendedElement){
strOut += "
This is your recommended element. Please upgrade this.";
if (w.enableElementLock){
strOut += "
Other elements are LOCKED to prevent accidentally upgrading.";
}
} else if (-1 != ELEMENTS.LockedElement) {
strOut += "
This is NOT your recommended element. DO NOT upgrade this.";
}
return strOut;
};
}
function getGameLevel() {
return s().m_rgGameData.level + 1;
}
function countdown(time) {
var hours = 0;
var minutes = 0;
for (var i = 0; i < 24; i++) {
if (time >= 3600) {
time = time - 3600;
hours = hours + 1;
}
}
for (var j = 0; j < 60; j++) {
if (time >= 60) {
time = time - 60;
minutes = minutes + 1;
}
}
return {hours : hours, minutes : minutes};
}
function expectedLevel(level) {
var time = Math.floor(s().m_nTime) % 86400;
time = time - 16*3600;
if (time < 0) {
time = time + 86400;
}
var remaining_time = 86400 - time;
var passed_time = getCurrentTime() - s().m_rgGameData.timestamp_game_start;
var expected_level = Math.floor(((level/passed_time)*remaining_time)+level);
var likely_level = Math.floor((expected_level - level)/Math.log(3))+ level;
return {expected_level : expected_level, likely_level : likely_level, remaining_time : remaining_time};
}
function appendBreadcrumbsTitleInfo() {
var breadcrumbs = document.querySelector('.breadcrumbs');
if(!breadcrumbs) {
return;
}
var element = document.createElement('span');
element.textContent = ' > ';
breadcrumbs.appendChild(element);
element = document.createElement('span');
element.className = "bc_span bc_room";
element.textContent = 'Room ' + w.g_GameID;
breadcrumbs.appendChild(element);
element = document.createElement('span');
element.textContent = ' > ';
breadcrumbs.appendChild(element);
element = document.createElement('span');
element.className = "bc_span bc_level";
element.textContent = 'Level: 0';
breadcrumbs.appendChild(element);
ELEMENTS.ExpectedLevel = element;
element = document.createElement('span');
element.textContent = ' > ';
breadcrumbs.appendChild(element);
element = document.createElement('span');
element.className = "bc_span bc_time";
element.textContent = 'Remaining Time: 0 hours, 0 minutes';
breadcrumbs.appendChild(element);
ELEMENTS.RemainingTime = element;
element = document.createElement('span');
element.textContent = ' > ';
breadcrumbs.appendChild(element);
element = document.createElement('span');
element.className = "bc_span bc_worms";
element.textContent = 'Wormhole Activity: 0';
breadcrumbs.appendChild(element);
ELEMENTS.WormholesJumped = element;
}
function updateLevelInfoTitle(level)
{
var exp_lvl = expectedLevel(level);
var rem_time = countdown(exp_lvl.remaining_time);
ELEMENTS.ExpectedLevel.textContent = 'Level: ' + level +
', Levels/second: ' + Math.round(levelsPerSec() * 1000) / 1000 +
', Prediction Accuracy: ' + Math.round(getPredictedLevelAccuracy() * 100) + "%" +
', Version Levels/s: ' + Math.round(getLevelsPerSecondSinceStart()) +
', WH/Boss: ' + Math.round(averageWormholesPerBoss()) +
', Seconds/Boss\': ' + Math.round(1 / averageBossesPerSecond())
ELEMENTS.RemainingTime.textContent = 'Remaining Time: ' + rem_time.hours + ' hours, ' + rem_time.minutes + ' minutes';
ELEMENTS.WormholesJumped.textContent = 'Wormhole Activity: ' + (skipsLastJump.toLocaleString ? skipsLastJump.toLocaleString() : skipsLastJump);
}
}(window));