<commit_before>// ==UserScript==
// @name [SteamDB] Monster Minigame Script
// @namespace https://github.com/SteamDatabase/steamSummerMinigame
// @description A script that runs the Steam Monster Minigame for you.
// @version 4.4.7
// @match *://steamcommunity.com/minigame/towerattack*
// @match *://steamcommunity.com//minigame/towerattack*
// @grant none
// @updateURL https://raw.githubusercontent.com/SteamDatabase/steamSummerMinigame/master/autoPlay.user.js
// @downloadURL https://raw.githubusercontent.com/SteamDatabase/steamSummerMinigame/master/autoPlay.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 nukeBeforeReset = getPreferenceBoolean("nukeBeforeReset", true);

var enableAutoClicker = getPreferenceBoolean("enableAutoClicker", true);

var enableAutoUpgradeDPS = getPreferenceBoolean("enableAutoUpgradeDPS", false);
var enableAutoUpgradeClick = getPreferenceBoolean("enableAutoUpgradeClick", false);

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 enableElementLock = getPreferenceBoolean("enableElementLock", true);

var enableAutoRefresh = getPreferenceBoolean("enableAutoRefresh", typeof GM_info !== "undefined");

var autoRefreshMinutes = 30;
var autoRefreshMinutesRandomDelay = 10;
var autoRefreshSecondsCheckLoadedDelay = 30;

// DO NOT MODIFY
var isAlreadyRunning = false;
var refreshTimer = null;
var currentClickRate = clickRate;
var lockedElement = -1;
var lastLevel = 0;
var trt_oldCrit = function() {};
var trt_oldPush = function() {};
var trt_oldRender = function() {};
var ELEMENTS = {};

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: 5000,
	rainingRounds: 250
};

var GAME_STATUS = {
	LOBBY: 1,
	RUNNING: 2,
	OVER: 3
};

// Try to disable particles straight away,
// if not yet available, they will be disabled in firstRun
disableParticles();

function s() {
	return w.g_Minigame.m_CurrentScene;
}

function firstRun() {
	advLog("Starting SteamDB's Steam Summer 2015 Monster Minigame Script.", 1);

	trt_oldCrit = s().DoCritEffect;
	trt_oldPush = s().m_rgClickNumbers.push;
	trt_oldRender = w.g_Minigame.Render;

	if(enableFingering) {
		startFingering();
	}

	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();
	}

	var node = document.getElementById("abilities");

	if( node ) {
		node.style.textAlign = 'left';
	}

	if( removeInterface ) {
		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 );
		}
		node = document.querySelector(".pagecontent");
		if (node) {
			node.style.paddingBottom = 0;
		}
		document.body.style.backgroundPosition = "0 0";
	}

	// Add "players in game" label
	var titleActivity = document.querySelector( '.title_activity' );
	var playersInGame = document.createElement( 'span' );
	playersInGame.innerHTML = '<span id=\"players_in_game\">0/1500</span>&nbsp;Players in room<br>';
	titleActivity.insertBefore(playersInGame, titleActivity.firstChild);
	ELEMENTS.PlayersInGame = document.getElementById("players_in_game");

	// Fix alignment of acvititylog and expand list of active abilities on hover
	var abilities_extra_styles = document.createElement('style');
	abilities_extra_styles.type = 'text/css';
	abilities_extra_styles.textContent = '#activeinlanecontainer:hover {height:auto;background:rgba(50,50,50,0.9);padding-bottom:10px;position:absolute;z-index:1} #activeinlanecontainer:hover + #activitylog {margin-top:88px} #activitylog {margin-top: 20px}';
	document.getElementsByTagName('head')[0].appendChild(abilities_extra_styles);

	// space for option menu
	var options_menu = document.querySelector(".game_options");
	var sfx_btn = document.querySelector(".toggle_sfx_btn");
	sfx_btn.style.marginLeft = "2px";
	sfx_btn.style.marginRight = "7px";
	sfx_btn.style.cssFloat = "right";
	sfx_btn.style.styleFloat = "right";
	var music_btn = document.querySelector(".toggle_music_btn");
	music_btn.style.marginRight = "2px";
	music_btn.style.cssFloat = "right";
	music_btn.style.styleFloat = "right";
	var leave_btn = document.querySelector(".leave_game_btn");
	leave_btn.style.display = "none";

	var info_box = document.querySelector(".leave_game_helper");
	var pagecontent = document.querySelector(".pagecontent");
	pagecontent.style.padding = "0";
	options_menu.insertBefore(info_box, sfx_btn);

	info_box.innerHTML = '<b>OPTIONS</b>' + ((typeof GM_info !==  "undefined") ? ' (v' + GM_info.script.version + ')' : '') + '<br>Settings marked with a <span style="color:#FF5252;font-size:22px;line-height:4px;vertical-align:bottom;">*</span> requires a refresh to take effect.<hr>';

	// reset the CSS for the info box for aesthetics
	info_box.className = "options_box";
	info_box.style.backgroundColor = "#000000";
	info_box.style.width = "800px";
	info_box.style.padding = "12px";
	info_box.style.boxShadow = "2px 2px 0 rgba( 0, 0, 0, 0.6 )";
	info_box.style.color = "#ededed";
	info_box.style.margin = "2px auto";
	info_box.style.overflow = "auto";
	info_box.style.cssFloat = "left";
	info_box.style.styleFloat = "left";

	var options1 = document.createElement("div");
	options1.style["-moz-column-count"] = 2;
	options1.style["-webkit-column-count"] = 2;
	options1.style["column-count"] = 2;
	options1.style.width = "50%";
	options1.style.float = "left";

	options1.appendChild(makeCheckBox("enableAutoClicker", "Enable autoclicker", enableAutoClicker, toggleAutoClicker, false));
	options1.appendChild(makeCheckBox("enableAutoUpgradeDPS", "Enable AutoUpgrade DPS", enableAutoUpgradeDPS, toggleAutoUpgradeDPS, false));
	options1.appendChild(makeCheckBox("enableAutoUpgradeClick", "Enable AutoUpgrade Clicks", enableAutoUpgradeClick, toggleAutoUpgradeClick, false));
	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));

	info_box.appendChild(options1);

	var options2 = document.createElement("div");
	options2.style["-moz-column-count"] = 2;
	options2.style["-webkit-column-count"] = 2;
	options2.style["column-count"] = 2;
	options2.style.width = "50%";
	options2.style.float = "left";

	if (typeof GM_info !==  "undefined") {
		options2.appendChild(makeCheckBox("enableAutoRefresh", "Enable auto-refresh (mitigate memory leak)", enableAutoRefresh, toggleAutoRefresh, false));
	}

	options2.appendChild(makeCheckBox("enableFingering", "Enable targeting pointer", enableFingering, handleEvent,true));
	options2.appendChild(makeCheckBox("nukeBeforeReset", "Spam abilities 1 hour before game end", nukeBeforeReset, handleEvent, true));
	options2.appendChild(makeNumber("setLogLevel", "Change the log level (you shouldn't need to touch this)", "25px", 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.style.width = "165px";
	lock_elements_box.style.top = "-76px";
	lock_elements_box.style.left = "303px";
	lock_elements_box.style.boxSizing = "border-box";
	lock_elements_box.style.lineHeight = "1rem";
	lock_elements_box.style.padding = "7px 10px";
	lock_elements_box.style.position = "absolute";
	lock_elements_box.style.color = "#ededed";
	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);

	enhanceTooltips();
}

function disableParticles() {
	if (w.CSceneGame) {
		w.CSceneGame.prototype.DoScreenShake = function() {};

		if(removeParticles) {
			w.CSceneGame.prototype.SpawnEmitter = function(emitter) {
				emitter.emit = false;
				return emitter;
			};
		}
	}
}

function isNearEndGame() {
	var cTime = new Date();
	var cHours = cTime.getUTCHours();
	var cMins = cTime.getUTCMinutes();
	var timeLeft = 60 - cMins;
	if (cHours == 15 && timeLeft <= 60) {
		return true;
	}
	else {
		return false;
	}
}

function MainLoop() {
	var status = s().m_rgGameData.status;
	if(status != GAME_STATUS.RUNNING)
	{
		return;
	}
	
	var level = s().m_rgGameData.level + 1;


	if (!isAlreadyRunning) {
		isAlreadyRunning = true;

		goToLaneWithBestTarget(level);

		attemptRespawn();

		useAbilities(level);

		updatePlayersInGame();

		if( level !== lastLevel ) {
			lastLevel = level;
			updateLevelInfoTitle(level);
			refreshPlayerData();
		}

		useAutoUpgrade();

		s().m_nClicks += currentClickRate;
		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: " + currentClickRate + ". Current damage per second: " + (damagePerClick * currentClickRate), 4);

		if(disableRenderer) {
			s().Tick();

			requestAnimationFrame(function() {
				w.g_Minigame.Renderer.render(s().m_Container);
			});
		}

		isAlreadyRunning = false;

		if( currentClickRate > 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 * currentClickRate), 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 * currentClickRate;

					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"
					);
				}
			}
		}
		
		// Prune older entries (for real)
		var e = s().m_rgActionLog;
		if(e.length > 20)
		{
			s().m_rgActionLog = e.slice(-20);
		}
	}
}

function useAutoUpgrade() {
	if(!enableAutoUpgradeDPS && !enableAutoUpgradeClick) { return; }

	// upg_map will contain the most cost effctive upgrades for each type
	var upg_map = {
		0: {
			idx: -1,
			gold_per_mult: 0
		},
		1: {
			idx: -1,
			gold_per_mult: 0
		},
		2: {
			idx: -1,
			gold_per_mult: 0
		},
	};

	var p_upg = s().m_rgPlayerUpgrades;

	// loop over all upgrades and find the most cost effective ones
	s().m_rgTuningData.upgrades.forEach(function(upg, idx) {
		if(upg.type in upg_map) {

			var cost = s().GetUpgradeCost(idx) / parseFloat(upg.multiplier);

			if(upg_map[upg.type].idx == -1 || 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;
				upg_map[upg.type].cost_per_mult = cost;
			}
		}
	});

	var key = '';
	var cache = s().m_UI.m_rgElementCache;

	if(enableAutoUpgradeDPS) {
		key = 'upgr_' + upg_map[UPGRADES.AUTO_FIRE_CANNON].idx;
		if(cache.hasOwnProperty(key)) { s().TryUpgrade(cache[key].find('.link')[0]); }
	}

	if(enableAutoUpgradeClick) {
		key = 'upgr_' + upg_map[UPGRADES.ARMOR_PIERCING_ROUND].idx;
		if(cache.hasOwnProperty(key)) { s().TryUpgrade(cache[key].find('.link')[0]); }
	}
}

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 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');<commit_msg>AutoUpgrade: click priority, HP toggle, hilight<commit_after>// ==UserScript==
// @name [SteamDB] Monster Minigame Script
// @namespace https://github.com/SteamDatabase/steamSummerMinigame
// @description A script that runs the Steam Monster Minigame for you.
// @version 4.4.7
// @match *://steamcommunity.com/minigame/towerattack*
// @match *://steamcommunity.com//minigame/towerattack*
// @grant none
// @updateURL https://raw.githubusercontent.com/SteamDatabase/steamSummerMinigame/master/autoPlay.user.js
// @downloadURL https://raw.githubusercontent.com/SteamDatabase/steamSummerMinigame/master/autoPlay.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 nukeBeforeReset = getPreferenceBoolean("nukeBeforeReset", true);

var enableAutoClicker = getPreferenceBoolean("enableAutoClicker", true);

var enableAutoUpgradeHP = getPreferenceBoolean("enableAutoUpgradeHP", true);
var enableAutoUpgradeClick = getPreferenceBoolean("enableAutoUpgradeClick", false);
var enableAutoUpgradeDPS = getPreferenceBoolean("enableAutoUpgradeDPS", false);

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 enableElementLock = getPreferenceBoolean("enableElementLock", true);

var enableAutoRefresh = getPreferenceBoolean("enableAutoRefresh", typeof GM_info !== "undefined");

var autoRefreshMinutes = 30;
var autoRefreshMinutesRandomDelay = 10;
var autoRefreshSecondsCheckLoadedDelay = 30;

// DO NOT MODIFY
var isAlreadyRunning = false;
var refreshTimer = null;
var currentClickRate = clickRate;
var lockedElement = -1;
var lastLevel = 0;
var trt_oldCrit = function() {};
var trt_oldPush = function() {};
var trt_oldRender = function() {};
var ELEMENTS = {};

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: 5000,
	rainingRounds: 250
};

var GAME_STATUS = {
	LOBBY: 1,
	RUNNING: 2,
	OVER: 3
};

// Try to disable particles straight away,
// if not yet available, they will be disabled in firstRun
disableParticles();

function s() {
	return w.g_Minigame.m_CurrentScene;
}

function firstRun() {
	advLog("Starting SteamDB's Steam Summer 2015 Monster Minigame Script.", 1);

	trt_oldCrit = s().DoCritEffect;
	trt_oldPush = s().m_rgClickNumbers.push;
	trt_oldRender = w.g_Minigame.Render;

	if(enableFingering) {
		startFingering();
	}

	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();
	}

	var node = document.getElementById("abilities");

	if( node ) {
		node.style.textAlign = 'left';
	}

	if( removeInterface ) {
		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 );
		}
		node = document.querySelector(".pagecontent");
		if (node) {
			node.style.paddingBottom = 0;
		}
		document.body.style.backgroundPosition = "0 0";
	}

	// Add "players in game" label
	var titleActivity = document.querySelector( '.title_activity' );
	var playersInGame = document.createElement( 'span' );
	playersInGame.innerHTML = '<span id=\"players_in_game\">0/1500</span>&nbsp;Players in room<br>';
	titleActivity.insertBefore(playersInGame, titleActivity.firstChild);
	ELEMENTS.PlayersInGame = document.getElementById("players_in_game");

	// Fix alignment of acvititylog and expand list of active abilities on hover
	var abilities_extra_styles = document.createElement('style');
	abilities_extra_styles.type = 'text/css';
	abilities_extra_styles.textContent = '#activeinlanecontainer:hover {height:auto;background:rgba(50,50,50,0.9);padding-bottom:10px;position:absolute;z-index:1} #activeinlanecontainer:hover + #activitylog {margin-top:88px} #activitylog {margin-top: 20px}';
	document.getElementsByTagName('head')[0].appendChild(abilities_extra_styles);

	// space for option menu
	var options_menu = document.querySelector(".game_options");
	var sfx_btn = document.querySelector(".toggle_sfx_btn");
	sfx_btn.style.marginLeft = "2px";
	sfx_btn.style.marginRight = "7px";
	sfx_btn.style.cssFloat = "right";
	sfx_btn.style.styleFloat = "right";
	var music_btn = document.querySelector(".toggle_music_btn");
	music_btn.style.marginRight = "2px";
	music_btn.style.cssFloat = "right";
	music_btn.style.styleFloat = "right";
	var leave_btn = document.querySelector(".leave_game_btn");
	leave_btn.style.display = "none";

	var info_box = document.querySelector(".leave_game_helper");
	var pagecontent = document.querySelector(".pagecontent");
	pagecontent.style.padding = "0";
	options_menu.insertBefore(info_box, sfx_btn);

	info_box.innerHTML = '<b>OPTIONS</b>' + ((typeof GM_info !==  "undefined") ? ' (v' + GM_info.script.version + ')' : '') + '<br>Settings marked with a <span style="color:#FF5252;font-size:22px;line-height:4px;vertical-align:bottom;">*</span> requires a refresh to take effect.<hr>';

	// reset the CSS for the info box for aesthetics
	info_box.className = "options_box";
	info_box.style.backgroundColor = "#000000";
	info_box.style.width = "800px";
	info_box.style.padding = "12px";
	info_box.style.boxShadow = "2px 2px 0 rgba( 0, 0, 0, 0.6 )";
	info_box.style.color = "#ededed";
	info_box.style.margin = "2px auto";
	info_box.style.overflow = "auto";
	info_box.style.cssFloat = "left";
	info_box.style.styleFloat = "left";

	var options1 = document.createElement("div");
	options1.style["-moz-column-count"] = 2;
	options1.style["-webkit-column-count"] = 2;
	options1.style["column-count"] = 2;
	options1.style.width = "50%";
	options1.style.float = "left";

	options1.appendChild(makeCheckBox("enableAutoClicker", "Enable autoclicker", enableAutoClicker, toggleAutoClicker, false));
	options1.appendChild(makeCheckBox("enableAutoUpgradeHP", "Enable AutoUpgrade HP (up to 500k HP)", enableAutoUpgradeHP, toggleAutoUpgradeHP, false));
	options1.appendChild(makeCheckBox("enableAutoUpgradeClick", "Enable AutoUpgrade Clicks", enableAutoUpgradeClick, toggleAutoUpgradeClick, false));
	options1.appendChild(makeCheckBox("enableAutoUpgradeDPS", "Enable AutoUpgrade DPS", enableAutoUpgradeDPS, toggleAutoUpgradeDPS, false));
	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));

	info_box.appendChild(options1);

	var options2 = document.createElement("div");
	options2.style["-moz-column-count"] = 2;
	options2.style["-webkit-column-count"] = 2;
	options2.style["column-count"] = 2;
	options2.style.width = "50%";
	options2.style.float = "left";

	if (typeof GM_info !==  "undefined") {
		options2.appendChild(makeCheckBox("enableAutoRefresh", "Enable auto-refresh (mitigate memory leak)", enableAutoRefresh, toggleAutoRefresh, false));
	}

	options2.appendChild(makeCheckBox("enableFingering", "Enable targeting pointer", enableFingering, handleEvent,true));
	options2.appendChild(makeCheckBox("nukeBeforeReset", "Spam abilities 1 hour before game end", nukeBeforeReset, handleEvent, true));
	options2.appendChild(makeNumber("setLogLevel", "Change the log level (you shouldn't need to touch this)", "25px", 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.style.width = "165px";
	lock_elements_box.style.top = "-76px";
	lock_elements_box.style.left = "303px";
	lock_elements_box.style.boxSizing = "border-box";
	lock_elements_box.style.lineHeight = "1rem";
	lock_elements_box.style.padding = "7px 10px";
	lock_elements_box.style.position = "absolute";
	lock_elements_box.style.color = "#ededed";
	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);

	enhanceTooltips();
}

function disableParticles() {
	if (w.CSceneGame) {
		w.CSceneGame.prototype.DoScreenShake = function() {};

		if(removeParticles) {
			w.CSceneGame.prototype.SpawnEmitter = function(emitter) {
				emitter.emit = false;
				return emitter;
			};
		}
	}
}

function isNearEndGame() {
	var cTime = new Date();
	var cHours = cTime.getUTCHours();
	var cMins = cTime.getUTCMinutes();
	var timeLeft = 60 - cMins;
	if (cHours == 15 && timeLeft <= 60) {
		return true;
	}
	else {
		return false;
	}
}

function MainLoop() {
	var status = s().m_rgGameData.status;
	if(status != GAME_STATUS.RUNNING)
	{
		return;
	}
	
	var level = s().m_rgGameData.level + 1;


	if (!isAlreadyRunning) {
		isAlreadyRunning = true;

		goToLaneWithBestTarget(level);

		attemptRespawn();

		useAbilities(level);

		updatePlayersInGame();

		if( level !== lastLevel ) {
			lastLevel = level;
			updateLevelInfoTitle(level);
			refreshPlayerData();
		}

		useAutoUpgrade();

		s().m_nClicks += currentClickRate;
		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: " + currentClickRate + ". Current damage per second: " + (damagePerClick * currentClickRate), 4);

		if(disableRenderer) {
			s().Tick();

			requestAnimationFrame(function() {
				w.g_Minigame.Renderer.render(s().m_Container);
			});
		}

		isAlreadyRunning = false;

		if( currentClickRate > 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 * currentClickRate), 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 * currentClickRate;

					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"
					);
				}
			}
		}
		
		// Prune older entries (for real)
		var e = s().m_rgActionLog;
		if(e.length > 20)
		{
			s().m_rgActionLog = e.slice(-20);
		}
	}
}

var autoupgrade_update_hilight = true;

function useAutoUpgrade() {
	if(!enableAutoUpgradeDPS && !enableAutoUpgradeClick && !enableAutoUpgradeHP) { return; }

	var upg_order = [
		UPGRADES.ARMOR_PIERCING_ROUND,
		UPGRADES.LIGHT_ARMOR,
		UPGRADES.AUTO_FIRE_CANNON,
	];
	var upg_map = {};

	upg_order.forEach(function(i) { upg_map[i] = {}; });
	var pData = s().m_rgPlayerData;
	var cache = s().m_UI.m_rgElementCache;
	var upg_enabled = [
		enableAutoUpgradeClick,
		enableAutoUpgradeHP && pData.hp < 500000,
		enableAutoUpgradeDPS,
	];

	// 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) {
				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]) { continue; }

		// prioritize click upgrades over DPS ones, unless they are more cost effective
		if(upg_order[i] === UPGRADES.AUTO_FIRE_CANNON) {
			if(upg_map[UPGRADES.AUTO_FIRE_CANNON].cost_per_mult < upg_map[UPGRADES.ARMOR_PIERCING_ROUND].cost_per_mult * 10) { continue; }
		}

		var tree = upg_map[upg_order[i]];
		var key = 'upgr_' + tree.idx;

		if(s().GetUpgradeCost(tree.idx) < pData.gold && cache.hasOwnProperty(key)) {
			s().TryUpgrade(cache[key].find('.link')[0]);
			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 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');
{'fraction_non_alphanumeric': 0.1307174367309032, 'fraction_numerical': 0.0174425354074762, 'mean_word_length': 5.099663657284475, 'pattern_counts': {'":': 0, '<': 29, '<?xml version=': 0, '>': 33, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6563952', 'n_tokens_mistral': 13438, 'n_tokens_neox': 12112, 'n_words': 2354}