// ==UserScript==
// @name LWM Combat Info
// @name:ru HWM Combat Info
// @namespace https://greasyfork.org/en/users/731199-thirdwater
// @description Displays extra information during combats
// @description:ru Displays extra information during combats
// @match *://www.lordswm.com/war.php?*
// @match *://www.heroeswm.ru/war.php?*
// @run-at document-end
// @grant none
// @version 0.1
// ==/UserScript==
/*global $ */ // So we can use easyTooltip.js (and also get jQuery for free)
(function($) {
'use strict';
function main() {
// LWM Combat Info script
/*
* Config
*/
var text = {
stack_size: {
en: function(current, max) {
return "Stack size: " + current + "/" + max;
},
ru: function(current, max) {
return "Stack size: " + current + "/" + max;
},
},
stack_hp: {
en: function(current, max) {
return "Total hit points: " + current + "/" + max;
},
ru: function(current, max) {
return "Total hit points: " + current + "/" + max;
},
},
stack_initiative: {
en: function(atb_position) {
return "ATB position: " + atb_position;
},
ru: function(atb_position) {
return "ATB position: " + atb_position;
},
},
hero_factions: {
en: function(faction_levels) {
var table = hero_factions_template(faction_levels);
return table.outerHTML;
},
ru: function(faction_levels) {
var table = hero_factions_template(faction_levels);
return table.outerHTML;
},
},
hero_level: {
en: function(ap) {
return "AP: " + ap;
},
ru: function(ap) {
return "AP: " + ap;
},
},
hero_initiative: {
en: function(atb_position) {
return "ATB position: " + atb_position;
},
ru: function(atb_position) {
return "ATB position: " + atb_position;
},
},
hero_title: {
en: function(current, max) {
return "Army hit points: " + current + "/" + max;
},
ru: function(current, max) {
return "Army hit points: " + current + "/" + max;
},
},
};
var lwm_interface = {
domain: {
en: "https://www.lordswm.com/",
ru: "https://www.heroeswm.ru/",
},
tooltip_class: 'for_tooltip',
get_tooltip_description_container: function() {
return document.getElementById('caster_desc').parentNode;
},
/*global war_scr */
stack_object: window.stage[war_scr].obj,
get_current_stack: function() {
/*global cur_unit_info */
var stack_id = cur_unit_info;
return lwm_interface.stack_object[stack_id];
},
event_source: [
document.getElementById(war_scr),
document.getElementById('btn_effects_info'),
document.getElementById('btn_effects_info_hero'),
document.getElementById('btn_effects_info_creature'),
],
hero_info_element: document.getElementById('win_InfoHero'),
unit_info_element: document.getElementById('win_InfoCreature'),
effect_info_element: document.getElementById('win_InfoCreatureEffect'),
get_stack_size_element: function(page) {
if (page === 1) {
return document.getElementById('cre_info_head').children[0].children[0];
} else if (page === 2) {
return document.getElementById('effects_info_head').children[0].children[0];
}
},
get_creature_name_node: function(page) {
if (page === 1) {
return document.getElementById('cre_info_head').children[0].firstChild;
} else if (page === 2) {
return document.getElementById('effects_info_head').children[0].firstChild;
}
},
stack_hp_element: document.getElementById('cre_info_health').parentNode,
stack_initiative_element: document.getElementById('cre_info_init').parentNode,
get_hero_factions_element: function(page) {
if (page === 1) {
return document.getElementById('hero_info_head').firstChild;
} else if (page === 2) {
return document.getElementById('effects_info_head').firstChild;
}
},
get_hero_level_element: function(page) {
if (page === 1) {
return document.getElementById('hero_info_head').lastChild;
} else if (page === 2) {
return document.getElementById('effects_info_head').lastChild;
}
},
hero_initiative_element: document.getElementById('hero_info_init').parentNode,
get_hero_title_element: function(page) {
if (page === 1) {
return document.getElementById('hero_info_head').children[1];
} else if (page === 2) {
return document.getElementById('effects_info_head').children[1];
}
},
faction_icon_link: {
knight: 'https://dcdn.lordswm.com/i/combat/factions_icons/faction_knight.png?v=4',
necromancer: 'https://dcdn.lordswm.com/i/combat/factions_icons/faction_necromancer.png?v=4',
wizard: 'https://dcdn.lordswm.com/i/combat/factions_icons/faction_mage.png?v=4',
elf: 'https://dcdn.lordswm.com/i/combat/factions_icons/faction_elveon.png?v=4',
barbarian: 'https://dcdn.lordswm.com/i/combat/factions_icons/faction_barbarian.png?v=4',
dark_elf: 'https://dcdn.lordswm.com/i/combat/factions_icons/faction_darkelves.png?v=4',
demon: 'https://dcdn.lordswm.com/i/combat/factions_icons/faction_demon.png?v=4',
dwarf: 'https://dcdn.lordswm.com/i/combat/factions_icons/faction_dwarf.png?v=4',
tribal: 'https://dcdn.lordswm.com/i/combat/factions_icons/faction_steppebarbarian.png?v=4',
pharaoh: 'https://dcdn.lordswm.com/i/combat/factions_icons/faction_egypt.png?v=4',
},
creature_infocard_page: 'army_info.php?name=',
// Potential bug; need to find all creatures with different combat/infocard names.
creature_infocard_name: {
// We only store the creatures with different code names for combat and infocard.
// Knight
'paesant': 'peasant',
'crossbowman': 'crossman',
'swordman': 'squire',
'knight': 'cavalier',
// Necromancer
'sceleton': 'skeleton',
'sceletonarcher': 'skeletonarcher',
// Wizard
'gargoly': 'stone_gargoyle',
'golem': 'iron_golem',
'rakshas': 'rakshasa_rani',
// Elf
'pp': 'pixel',
'dryad_': 'dryad',
'bladedancer': 'wardancer',
'winddancer': 'wdancer',
'hunterelf': 'masterhunter',
'dd_': 'druid',
'ddeld': 'druideld',
// Barbarian
'hobwolfrider': 'wolfraider',
'roc': 'rocbird',
'firebird_': 'firebird',
'cyclopod_': 'cyclopod',
'cyclopshaman': 'shamancyclop',
'abehemoth': 'ancientbehemoth',
'bbehemoth': 'cursedbenemoth',
// Dark Elf
'minotaurguard_': 'minotaurguard',
'lizardrider': 'darkrider',
'witch': 'shadow_witch',
// Demon
'hdemon': 'horneddemon',
'fdemon': 'hornedoverseer',
'demondog': 'hellhound',
'firehound': 'hotdog',
'succub': 'succubus',
'succubusm': 'succubusmis',
'nightmare': 'hellcharger',
'stallion': 'nightmare',
'hellstallion': 'hellkon',
'pitfiend_': 'pitfiend',
'pitlord_': 'pitlord',
'pitspawn': 'pity',
// Pharaoh
'zhrica': 'priestmoon',
'zhricaup': 'priestsun',
},
};
var tooltip = {
stack_size: {
class: 'stack_size_tooltip',
description_id: 'stack_size_desc',
},
stack_hp: {
class: 'stack_hp_tooltip',
description_id: 'stack_hp_desc',
},
stack_initiative: {
class: 'stack_initiative_tooltip',
description_id: 'stack_initiative_desc',
},
hero_factions: {
class: 'hero_factions_tooltip',
description_id: 'hero_factions_desc',
},
hero_level: {
class: 'hero_level_tooltip',
description_id: 'hero_level_desc',
},
hero_initiative: {
class: 'hero_initiative_tooltip',
description_id: 'hero_initiative_desc',
},
hero_title: {
class: 'hero_title_tooltip',
description_id: 'hero_title_desc',
},
};
/*
* Utilities
*/
function is_visible(element) {
// Note element.style.display only works for explicit display in the style attribute.
return window.getComputedStyle(element).display !== 'none';
}
function is_hero(stack) {
return stack.hero !== undefined;
}
function get_army_stacks(owner_index) {
var army_stacks = [];
for (var index in lwm_interface.stack_object) {
var stack = lwm_interface.stack_object[index];
if (!is_hero(stack) && stack.owner === owner_index) {
army_stacks.push(stack);
}
}
return army_stacks;
}
/*
* Tooltips
*/
function addEasyTooltip(component) {
$('.' + component.class).easyTooltip({
useElement: component.description_id,
});
}
function add_tooltip_descriptions() {
var container_element = lwm_interface.get_tooltip_description_container();
for (var component in tooltip) {
var description_id = tooltip[component].description_id;
var description_element = document.createElement('div');
description_element.id = description_id;
description_element.style = 'display: none;';
container_element.append(description_element);
}
}
function add_static_tooltip_components() {
var stack_hp_element = lwm_interface.stack_hp_element;
stack_hp_element.classList.add(lwm_interface.tooltip_class);
stack_hp_element.classList.add(tooltip.stack_hp.class);
addEasyTooltip(tooltip.stack_hp);
var stack_initiative_element = lwm_interface.stack_initiative_element;
stack_initiative_element.classList.add(lwm_interface.tooltip_class);
stack_initiative_element.classList.add(tooltip.stack_initiative.class);
addEasyTooltip(tooltip.stack_initiative);
var hero_initiative_element = lwm_interface.hero_initiative_element;
hero_initiative_element.classList.add(lwm_interface.tooltip_class);
hero_initiative_element.classList.add(tooltip.hero_initiative.class);
addEasyTooltip(tooltip.hero_initiative);
}
// Some elements are being added/modified/removed by the LWM scripts on the fly,
// so we have to re-apply these tooltips whenever appropriate.
function add_stack_size_tooltip(page) {
var stack_size_element = lwm_interface.get_stack_size_element(page);
if (stack_size_element === undefined) {
return;
}
stack_size_element.classList.add(lwm_interface.tooltip_class);
stack_size_element.classList.add(tooltip.stack_size.class);
addEasyTooltip(tooltip.stack_size);
}
function add_creature_link(page, stack) {
var creature_name_node = lwm_interface.get_creature_name_node(page);
var combat_name = stack.filename.slice(0, -3); // see also: anim and lname
var infocard_name = combat_name;
if (combat_name in lwm_interface.creature_infocard_name) {
infocard_name = lwm_interface.creature_infocard_name[combat_name];
}
var link_element = document.createElement('a');
link_element.innerHTML = creature_name_node.textContent;
link_element.target = '_blank';
link_element.href = lwm_interface.domain.en + lwm_interface.creature_infocard_page + infocard_name;
creature_name_node.replaceWith(link_element);
}
function add_hero_factions_tooltip(page) {
var hero_factions_element = lwm_interface.get_hero_factions_element(page);
hero_factions_element.classList.add(lwm_interface.tooltip_class);
hero_factions_element.classList.add(tooltip.hero_factions.class);
addEasyTooltip(tooltip.hero_factions);
}
function add_hero_level_tooltip(page) {
var hero_level_element = lwm_interface.get_hero_level_element(page);
hero_level_element.classList.add(lwm_interface.tooltip_class);
hero_level_element.classList.add(tooltip.hero_level.class);
addEasyTooltip(tooltip.hero_level);
}
function add_hero_title_tooltip(page) {
var hero_title_element = lwm_interface.get_hero_title_element(page);
hero_title_element.classList.add(lwm_interface.tooltip_class);
hero_title_element.classList.add(tooltip.hero_title.class);
addEasyTooltip(tooltip.hero_title);
}
function hero_factions_template(faction_levels) {
var table = document.createElement('table');
table.style = 'margin: 5px;';
for (var faction in faction_levels) {
var faction_level = faction_levels[faction];
var row = document.createElement('tr');
var icon_cell = document.createElement('td');
icon_cell.style = 'background: radial-gradient(rgba(180, 180, 180, 0.6), rgba(36, 38, 39, 0)); display: inline-block; margin-right: 30px; padding-top: 4px; padding-left: 4px; padding-right: 4px;';
var level_cell = document.createElement('td');
level_cell.style = 'font-size: 14px;';
level_cell.align = 'right';
level_cell.innerHTML = faction_level;
if (faction in lwm_interface.faction_icon_link) {
var icon_element = document.createElement('img');
icon_element.src = lwm_interface.faction_icon_link[faction];
icon_element.style.width = '16px';
icon_element.style.height = '16px';
icon_cell.append(icon_element);
row.append(icon_cell);
row.append(level_cell);
table.append(row);
} else if (faction_level > 0) {
icon_cell.innerHTML = "?";
icon_cell.align = 'center';
icon_cell.style.width = '16px';
row.append(icon_cell);
row.append(level_cell);
table.append(row);
}
}
return table;
}
/*
* Data
*/
function update_hero_factions(stack) {
var owner_index = stack.owner;
var factions_array = window.umelka[owner_index];
var factions = {
knight: factions_array[1],
necromancer: factions_array[2],
wizard: factions_array[3],
elf: factions_array[4],
barbarian: factions_array[5],
dark_elf: factions_array[6],
demon: factions_array[7],
dwarf: factions_array[8],
tribal: factions_array[9],
pharaoh: factions_array[10],
extra_1: factions_array[11],
extra_2: factions_array[12],
};
var description = document.getElementById(tooltip.hero_factions.description_id);
description.innerHTML = text.hero_factions.en(factions);
}
function update_hero_level(stack) {
var ap = parseInt(stack.data_string.match(/exp[\d]+/)[0].slice(4));
if (!ap) {
ap = 0;
}
var description = document.getElementById(tooltip.hero_level.description_id);
description.innerHTML = text.hero_level.en(ap);
}
function update_hero_initiative(stack) {
var atb_position = stack.nowinit;
var description = document.getElementById(tooltip.hero_initiative.description_id);
description.innerHTML = text.hero_initiative.en(atb_position);
}
function update_hero_title(stack) {
var owner_index = stack.owner;
var army_stacks = get_army_stacks(owner_index);
var army_max_hp = 0;
var army_current_hp = 0;
//console.log("");
for (var stack_index in army_stacks) {
var army_stack = army_stacks[stack_index];
// consider first 7 as a filter?
if (army_stack.summoned2) {
// summoned2 - demon gate, tde summon
continue;
}
var max_stack_size = army_stack.maxnumber;
var current_stack_size = army_stack.nownumber;
var original_max_hp = army_stack.realhealth;
var reduced_max_hp = army_stack.maxhealth;
var current_hp = army_stack.nowhealth;
if (original_max_hp !== reduced_max_hp) {
army_current_hp = "?";
army_max_hp = "?";
break;
}
var stack_original_max_hp = max_stack_size * original_max_hp;
var stack_current_hp_base = Math.max(0, current_stack_size - 1) * reduced_max_hp;
var stack_current_hp_head = current_hp;
var stack_current_hp = stack_current_hp_base + stack_current_hp_head;
//console.log(army_stack.nametxt + ": " + stack_current_hp + "/" + stack_original_max_hp);
army_max_hp += stack_original_max_hp;
army_current_hp += stack_current_hp;
}
var description = document.getElementById(tooltip.hero_title.description_id);
description.innerHTML = text.hero_title.en(army_current_hp, army_max_hp);
}
// Bug during settlement:
// Splitted stacks will still have their original max stack size before the split.
function update_stack_size(stack) {
var max_stack_size = stack.maxnumber;
var current_stack_size = stack.nownumber;
var description = document.getElementById(tooltip.stack_size.description_id);
description.innerHTML = text.stack_size.en(current_stack_size, max_stack_size);
}
function update_stack_hp(stack) {
var max_stack_size = stack.maxnumber;
var current_stack_size = stack.nownumber;
var original_max_hp = stack.realhealth;
var reduced_max_hp = stack.maxhealth;
var current_hp = stack.nowhealth;
var stack_original_max_hp = max_stack_size * original_max_hp;
var stack_current_hp_base = (current_stack_size - 1) * reduced_max_hp;
var stack_current_hp_head = current_hp;
var stack_current_hp = stack_current_hp_base + stack_current_hp_head;
if (original_max_hp !== reduced_max_hp) {
stack_current_hp = "?";
stack_original_max_hp = "?";
}
var description = document.getElementById(tooltip.stack_hp.description_id);
description.innerHTML = text.stack_hp.en(stack_current_hp, stack_original_max_hp);
}
function update_stack_initiative(stack) {
var atb_position = stack.nowinit;
var description = document.getElementById(tooltip.stack_initiative.description_id);
description.innerHTML = text.stack_initiative.en(atb_position);
}
/*
* Event Handling
*/
function on_hero_info(stack) {
update_hero_factions(stack);
update_hero_title(stack);
update_hero_level(stack);
}
function on_hero_info_page1(stack) {
add_hero_factions_tooltip(1);
add_hero_title_tooltip(1);
add_hero_level_tooltip(1);
update_hero_initiative(stack);
}
function on_hero_info_page2(stack) {
add_hero_factions_tooltip(2);
add_hero_title_tooltip(2);
add_hero_level_tooltip(2);
}
function on_unit_info(stack) {
update_stack_size(stack);
}
function on_unit_info_page1(stack) {
add_stack_size_tooltip(1);
add_creature_link(1, stack);
update_stack_hp(stack);
update_stack_initiative(stack);
}
function on_unit_info_page2(stack) {
add_stack_size_tooltip(2);
add_creature_link(2, stack);
}
function dispatch_event(event) {
var stack = lwm_interface.get_current_stack();
if (!stack) {
return;
}
if (is_hero(stack)) {
on_hero_info(stack);
} else {
on_unit_info(stack);
}
if (is_visible(lwm_interface.hero_info_element)) {
on_hero_info_page1(stack);
} else if (is_visible(lwm_interface.unit_info_element)) {
on_unit_info_page1(stack);
} else if (is_visible(lwm_interface.effect_info_element)) {
if (is_hero(stack)) {
on_hero_info_page2(stack);
} else {
on_unit_info_page2(stack);
}
}
}
function on_trigger(event) {
// Wait a bit for LWM script to update the windows.
setTimeout(function() {
dispatch_event(event);
}, 100);
}
/*
* Main Setup
*/
add_tooltip_descriptions();
add_static_tooltip_components();
lwm_interface.event_source.forEach(function(source, index) {
source.addEventListener('mouseup', on_trigger);
});
}
// Inject main into the document so we can access window attributes defined by LWM.
// See: https://stackoverflow.com/a/10828021/
var script = document.createElement('script');
script.type = "text/javascript";
script.textContent = "window.addEventListener('load', " + main.toString() + ", false);";
document.body.appendChild(script);
})($);