var isAlreadyRunning = false;
// disable particle effects - this drastically reduces the game's memory leak
if (window.g_Minigame !== undefined) {
window.g_Minigame.CurrentScene().DoClickEffect = function() {};
window.g_Minigame.CurrentScene().SpawnEmitter = function(emitter) {
emitter.emit = false;
return emitter;
}
}
// disable enemy flinching animation when they get hit
if (window.CEnemy !== undefined) {
window.CEnemy.prototype.TakeDamage = function() {};
window.CEnemySpawner.prototype.TakeDamage = function() {};
window.CEnemyBoss.prototype.TakeDamage = function() {};
}
if (thingTimer !== undefined) {
window.clearTimeout(thingTimer);
}
function doTheThing() {
if (isAlreadyRunning || g_Minigame === undefined || !g_Minigame.CurrentScene().m_bRunning || !g_Minigame.CurrentScene().m_rgPlayerTechTree) {
return;
}
isAlreadyRunning = true;
goToLaneWithBestTarget();
useGoodLuckCharmIfRelevant();
useMedicsIfRelevant();
useClusterBombIfRelevant();
useNapalmIfRelevant();
// TODO use abilities if available and a suitable target exists
// - Tactical Nuke on a Spawner if below 50% and above 25% of its health
// - Cluster Bomb and Napalm if the current lane has a spawner and 2+ creeps
// - Metal Detector if a boss, miniboss, or spawner death is imminent (predicted in > 2 and < 7 seconds)
// - Morale Booster if available and lane has > 2 live enemies
// - Decrease Cooldowns if another player used a long-cooldown ability < 10 seconds ago (any ability but Medics or a consumable)
// TODO purchase abilities and upgrades intelligently
attemptRespawn();
isAlreadyRunning = false;
}
var ABILITIES = {
"GOOD_LUCK": 6,
"MEDIC": 7,
"METAL_DETECTOR": 8,
"COOLDOWN": 9,
"NUKE": 10,
"CLUSTER_BOMB": 11,
"NAPALM": 12
};
var ITEMS = {
"REVIVE": 13,
"GOLD_RAIN": 17,
"GOD_MODE": 21,
"REFLECT_DAMAGE":24
}
function goToLaneWithBestTarget() {
// 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 ENEMY_TYPE = {
"SPAWNER":0,
"CREEP":1,
"BOSS":2,
"MINIBOSS":3,
"TREASURE":4
}
// 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 skippingSpawner = false;
var skippedSpawnerLane = 0;
var skippedSpawnerTarget = 0;
for (var k = 0; !targetFound && k < enemyTypePriority.length; k++) {
var enemies = [];
// gather all the enemies of the specified type.
for (var i = 0; i < 3; i++) {
for (var j = 0; j < 4; j++) {
var enemy = g_Minigame.CurrentScene().GetEnemy(i, j);
if (enemy && enemy.m_data.type == enemyTypePriority[k]) {
enemies[enemies.length] = enemy;
}
}
}
// target the enemy of the specified type with the lowest hp
for (var i = 0; i < enemies.length; i++) {
if (enemies[i] && !enemies[i].m_bIsDestroyed) {
if(lowHP < 1 || enemies[i].m_flDisplayedHP < lowHP) {
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 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)
if (enemyTypePriority[k] == ENEMY_TYPE.SPAWNER && lowPercentageHP > spawnerOKThreshold) {
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 (g_Minigame.CurrentScene().m_nExpectedLane != lowLane) {
//console.log('switching langes');
g_Minigame.CurrentScene().TryChangeLane(lowLane);
}
// target the chosen enemy
if (g_Minigame.CurrentScene().m_nTarget != lowTarget) {
//console.log('switching targets');
g_Minigame.CurrentScene().TryChangeTarget(lowTarget);
}
}
}
function useMedicsIfRelevant() {
var myMaxHealth = g_Minigame.CurrentScene().m_rgPlayerTechTree.max_hp;
// check if health is below 50%
var hpPercent = g_Minigame.CurrentScene().m_rgPlayerData.hp / myMaxHealth;
if (hpPercent > 0.5 || g_Minigame.CurrentScene().m_rgPlayerData.hp < 1) {
return; // no need to heal - HP is above 50% or already dead
}
// check if Medics is purchased and cooled down
if (hasPurchasedAbility(ABILITIES.MEDIC) && !isAbilityCoolingDown(ABILITIES.MEDIC)) {
// Medics is purchased, cooled down, and needed. Trigger it.
console.log('Medics is purchased, cooled down, and needed. Trigger it.');
triggerAbility(ABILITIES.MEDIC);
} else if (hasItem(ITEMS.GOD_MODE) && !isAbilityCoolingDown(ITEMS.GOD_MODE)) {
console.log('We have god mode, cooled down, and needed. Trigger it.');
triggerItem(ITEMS.GOD_MODE);
}
};
// Use Good Luck Charm if doable
function useGoodLuckCharmIfRelevant() {
// check if Good Luck Charms is purchased and cooled down
if (hasPurchasedAbility(ABILITIES.GOOD_LUCK)) {
if (isAbilityCoolingDown(ABILITIES.GOOD_LUCK)) {
return;
}
// Good Luck Charms is purchased, cooled down, and needed. Trigger it.
console.log('Good Luck Charms is purchased, cooled down, and needed. Trigger it.');
triggerAbility(ABILITIES.GOOD_LUCK);
}
}
function useClusterBombIfRelevant() {
//Check if Cluster Bomb is purchased and cooled down
if (hasPurchasedAbility(11)) {
if (isAbilityCoolingDown(11)) {
return;
}
//Check lane has monsters to explode
var currentLane = g_Minigame.CurrentScene().m_nExpectedLane;
var enemyCount = 0;
var enemySpawnerExists = false;
//Count each slot in lane
for (var i = 0; i < 4; i++) {
var enemy = g_Minigame.CurrentScene().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) {
triggerAbility(11);
}
}
}
function useNapalmIfRelevant() {
//Check if Napalm is purchased and cooled down
if (hasPurchasedAbility(12)) {
if (isAbilityCoolingDown(12)) {
return;
}
//Check lane has monsters to burn
var currentLane = g_Minigame.CurrentScene().m_nExpectedLane;
var enemyCount = 0;
var enemySpawnerExists = false;
//Count each slot in lane
for (var i = 0; i < 4; i++) {
var enemy = g_Minigame.CurrentScene().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) {
triggerAbility(12);
}
}
}
//If player is dead, call respawn method
function attemptRespawn() {
if ((g_Minigame.CurrentScene().m_bIsDead) &&
((g_Minigame.CurrentScene().m_rgPlayerData.time_died * 1000) + 5000) < (new Date().getTime())) {
RespawnPlayer();
}
}
function isAbilityActive(abilityId) {
return g_Minigame.CurrentScene().bIsAbilityActive(abilityId);
}
function hasItem(itemId) {
for ( var i = 0; i < g_Minigame.CurrentScene().m_rgPlayerTechTree.ability_items.length; ++i ) {
var abilityItem = g_Minigame.CurrentScene().m_rgPlayerTechTree.ability_items;
if (abilityItem.ability == itemId) {
return true;
}
}
return false;
}
function isAbilityCoolingDown(abilityId) {
return g_Minigame.CurrentScene().GetCooldownForAbility(abilityId) > 0;
}
function hasPurchasedAbility(abilityId) {
// each bit in unlocked_abilities_bitfield corresponds to an ability.
// the above condition checks if the ability's bit is set or cleared. I.e. it checks if
// the player has purchased the specified ability.
return (1 << abilityId) & g_Minigame.CurrentScene().m_rgPlayerTechTree.unlocked_abilities_bitfield;
}
function triggerItem(itemId) {
var elem = document.getElementById('abilityitem' + itemId);
if (elem && elem.childElements() && elem.childElements().length >= 1) {
g_Minigame.CurrentScene().TryAbility(document.getElementById('abilityitem' + itemId).childElements()[0]);
}
}
function triggerAbility(abilityId) {
var elem = document.getElementById('ability' + abilityId);
if (elem && elem.childElements() && elem.childElements().length >= 1) {
g_Minigame.CurrentScene().TryAbility(document.getElementById('ability' + abilityId).childElements()[0]);
}
}
var thingTimer = window.setInterval(doTheThing, 1000);