@@ -0,0 +1,82 @@ | |||
define([ | |||
'js/input' | |||
], function ( | |||
input | |||
) { | |||
const getCompareItem = msg => { | |||
const shiftDown = input.isKeyDown('shift', true); | |||
let item = msg.item; | |||
let items = window.player.inventory.items; | |||
let compare = null; | |||
if (item.slot) { | |||
compare = items.find(i => i.eq && i.slot === item.slot); | |||
// check special cases for mismatched weapon/offhand scenarios (only valid when comparing) | |||
if (!compare) { | |||
let equippedTwoHanded = items.find(i => i.eq && i.slot === 'twoHanded'); | |||
let equippedOneHanded = items.find(i => i.eq && i.slot === 'oneHanded'); | |||
let equippedOffhand = items.find(i => i.eq && i.slot === 'offHand'); | |||
if (item.slot === 'twoHanded') { | |||
if (!equippedOneHanded) | |||
compare = equippedOffhand; | |||
else if (!equippedOffhand) | |||
compare = equippedOneHanded; | |||
else { | |||
// compare against oneHanded and offHand combined by creating a virtual item that is the sum of the two | |||
compare = $.extend(true, {}, equippedOneHanded); | |||
compare.refItem = equippedOneHanded; | |||
for (let s in equippedOffhand.stats) { | |||
if (!compare.stats[s]) | |||
compare.stats[s] = 0; | |||
compare.stats[s] += equippedOffhand.stats[s]; | |||
} | |||
if (!compare.implicitStats) | |||
compare.implicitStats = []; | |||
(equippedOffhand.implicitStats || []).forEach(s => { | |||
let f = compare.implicitStats.find(i => i.stat === s.stat); | |||
if (!f) { | |||
compare.implicitStats.push({ | |||
stat: s.stat, | |||
value: s.value | |||
}); | |||
} else | |||
f.value += s.value; | |||
}); | |||
} | |||
} | |||
if (item.slot === 'oneHanded') | |||
compare = equippedTwoHanded; | |||
// this case is kind of ugly, but we don't want to go in when comparing an offHand to (oneHanded + empty offHand) - that should just use the normal compare which is offHand to empty | |||
if (item.slot === 'offHand' && equippedTwoHanded && shiftDown) { | |||
// since we're comparing an offhand to an equipped Twohander, we need to clone the 'spell' values over (setting damage to zero) so that we can properly display how much damage | |||
// the player would lose by switching to the offhand (which would remove the twoHander) | |||
// keep a reference to the original item for use in onHideToolTip | |||
let spellClone = $.extend(true, {}, equippedTwoHanded.spell); | |||
spellClone.name = ''; | |||
spellClone.values.damage = 0; | |||
let clone = $.extend(true, {}, item, { | |||
spell: spellClone | |||
}); | |||
clone.refItem = item; | |||
msg.item = clone; | |||
compare = equippedTwoHanded; | |||
} | |||
} | |||
} | |||
msg.compare = compare; | |||
}; | |||
return getCompareItem; | |||
}); |
@@ -0,0 +1,369 @@ | |||
define([ | |||
'js/input', | |||
'js/system/events', | |||
'js/misc/statTranslations', | |||
'ui/templates/tooltipItem/getCompareItem', | |||
'html!ui/templates/tooltipItem/templateTooltip' | |||
], function ( | |||
input, | |||
events, | |||
statTranslations, | |||
getCompareItem, | |||
tplTooltip | |||
) { | |||
let item = null; | |||
let compare = null; | |||
let shiftDown = null; | |||
let equipErrors = null; | |||
const percentageStats = [ | |||
'addCritChance', | |||
'addCritemultiplier', | |||
'addAttackCritChance', | |||
'addAttackCritemultiplier', | |||
'addSpellCritChance', | |||
'addSpellCritemultiplier', | |||
'sprintChance', | |||
'xpIncrease', | |||
'blockAttackChance', | |||
'blockSpellChance', | |||
'dodgeAttackChance', | |||
'dodgeSpellChance', | |||
'attackSpeed', | |||
'castSpeed', | |||
'itemQuantity', | |||
'magicFind', | |||
'catchChance', | |||
'catchSpeed', | |||
'fishRarity', | |||
'fishWeight', | |||
'fishItems' | |||
]; | |||
const getStatStringValue = (statName, statValue) => { | |||
let res = statValue; | |||
if (statName.indexOf('CritChance') > -1) | |||
res = res / 20; | |||
if (percentageStats.includes(statName) || statName.indexOf('Percent') > -1 || (statName.indexOf('element') === 0 && statName.indexOf('Resist') === -1)) | |||
res += '%'; | |||
return res + ''; | |||
}; | |||
const init = (_item, _compare, _shiftDown, _equipErrors) => { | |||
item = _item; | |||
compare = _compare; | |||
shiftDown = _shiftDown; | |||
equipErrors = _equipErrors; | |||
}; | |||
const helpers = { | |||
div: (className, children = '') => { | |||
if (children.join) | |||
children = children.join(''); | |||
return `<div class="${className}">${children}</div>`; | |||
}, | |||
name: () => { | |||
let itemName = item.name; | |||
if (item.quantity > 1) | |||
itemName += ' x' + item.quantity; | |||
return itemName; | |||
}, | |||
type: () => { | |||
if (!item.type || item.type === item.name) | |||
return; | |||
return item.type; | |||
}, | |||
power: () => { | |||
if (!item.power) | |||
return; | |||
return (new Array(item.power + 1)).join('+'); | |||
}, | |||
implicitStats: () => { | |||
if (!item.implicitStats) | |||
return; | |||
const tempImplicitStats = $.extend(true, [], item.implicitStats); | |||
if (compare && shiftDown && !item.eq) { | |||
const compareImplicitStats = (compare.implicitStats || []); | |||
tempImplicitStats.forEach(s => { | |||
const { stat, value } = s; | |||
const f = compareImplicitStats.find(c => c.stat === stat); | |||
if (!f) { | |||
s.value = `+${value}`; | |||
return; | |||
} | |||
const delta = value - f.value; | |||
if (delta > 0) | |||
s.value = `+${delta}`; | |||
else if (delta < 0) | |||
s.value = delta; | |||
}); | |||
compareImplicitStats.forEach(s => { | |||
if (tempImplicitStats.some(f => f.stat === s.stat)) | |||
return; | |||
tempImplicitStats.push({ | |||
stat: s.stat, | |||
value: -s.value | |||
}); | |||
}); | |||
} | |||
const html = tempImplicitStats | |||
.map(({ stat, value }) => { | |||
let statName = statTranslations.translate(stat); | |||
const prettyValue = getStatStringValue(statName, value); | |||
let rowClass = ''; | |||
if (compare) { | |||
if (prettyValue.indexOf('-') > -1) | |||
rowClass = 'loseStat'; | |||
else if (prettyValue.indexOf('+') > -1) | |||
rowClass = 'gainStat'; | |||
} | |||
return `<div class="${rowClass}">${prettyValue} ${statName}</div>`; | |||
}) | |||
.join(''); | |||
return html; | |||
}, | |||
stats: () => { | |||
const tempStats = $.extend(true, {}, item.stats); | |||
const enchantedStats = item.enchantedStats || {}; | |||
if (compare && shiftDown) { | |||
if (!item.eq) { | |||
const compareStats = compare.stats; | |||
for (let s in tempStats) { | |||
if (compareStats[s]) { | |||
const delta = tempStats[s] - compareStats[s]; | |||
if (delta > 0) | |||
tempStats[s] = '+' + delta; | |||
else if (delta < 0) | |||
tempStats[s] = delta; | |||
} else | |||
tempStats[s] = '+' + tempStats[s]; | |||
} | |||
for (let s in compareStats) { | |||
if (!tempStats[s]) | |||
tempStats[s] = -compareStats[s]; | |||
} | |||
} | |||
} else { | |||
Object.keys(tempStats).forEach(s => { | |||
if (enchantedStats[s]) { | |||
tempStats[s] -= enchantedStats[s]; | |||
if (tempStats[s] <= 0) | |||
delete tempStats[s]; | |||
tempStats['_' + s] = enchantedStats[s]; | |||
} | |||
}); | |||
} | |||
const html = Object.keys(tempStats) | |||
.map(s => { | |||
const isEnchanted = (s[0] === '_'); | |||
let statName = s; | |||
if (isEnchanted) | |||
statName = statName.substr(1); | |||
const prettyValue = getStatStringValue(statName, tempStats[s]); | |||
statName = statTranslations.translate(statName); | |||
let rowClass = ''; | |||
if (compare) { | |||
if (prettyValue.indexOf('-') > -1) | |||
rowClass = 'loseStat'; | |||
else if (prettyValue.indexOf('+') > -1) | |||
rowClass = 'gainStat'; | |||
} | |||
if (isEnchanted) | |||
rowClass += ' enchanted'; | |||
return `<div class="${rowClass}">${prettyValue} ${statName}</div>`; | |||
}) | |||
.sort((a, b) => { | |||
return (a.replace(' enchanted', '').length - b.replace(' enchanted', '').length); | |||
}) | |||
.sort((a, b) => { | |||
if (a.indexOf('enchanted') > -1 && b.indexOf('enchanted') === -1) | |||
return 1; | |||
else if (a.indexOf('enchanted') === -1 && b.indexOf('enchanted') > -1) | |||
return -1; | |||
return 0; | |||
}) | |||
.join(''); | |||
return html; | |||
}, | |||
effects: () => { | |||
if (item.description) | |||
return item.description; | |||
if (!item.effects || !item.effects.length || !item.effects[0].text || item.type === 'mtx') | |||
return; | |||
let html = ''; | |||
item.effects.forEach((e, i) => { | |||
html += e.text; | |||
if (i < item.effects.length - 1) | |||
html += '<br />'; | |||
}); | |||
return html; | |||
}, | |||
material: () => { | |||
if (item.material) | |||
return 'crafting material'; | |||
}, | |||
quest: () => { | |||
if (item.quest) | |||
return 'quest item'; | |||
}, | |||
spellName: () => { | |||
if (!item.spell || item.ability) | |||
return; | |||
return helpers.div(`spellName q${item.spell.quality}`, item.spell.name); | |||
}, | |||
damage: () => { | |||
if (!item.spell || !item.spell.values) | |||
return; | |||
const abilityValues = Object.entries(item.spell.values) | |||
.map(([k, v]) => { | |||
if (!compare || !shiftDown) | |||
return `${k}: ${v}<br/>`; | |||
let delta = v - compare.spell.values[k]; | |||
// adjust by EPSILON to handle float point imprecision, otherwise 3.15 - 2 = 1.14 or 2 - 3.15 = -1.14 | |||
// have to move away from zero by EPSILON, not a simple add | |||
if (delta >= 0) | |||
delta += Number.EPSILON; | |||
else | |||
delta -= Number.EPSILON; | |||
delta = ~~((delta) * 100) / 100; | |||
let rowClass = ''; | |||
if (delta > 0) { | |||
rowClass = 'gainDamage'; | |||
delta = '+' + delta; | |||
} else if (delta < 0) | |||
rowClass = 'loseDamage'; | |||
return `<div class="${rowClass}">${k}: ${delta}</div>`; | |||
}) | |||
.join(''); | |||
return abilityValues; | |||
}, | |||
requires: (className, children) => { | |||
if (!item.requires && !item.level && (!item.factions || !item.factions.length)) | |||
return; | |||
if (equipErrors.length) | |||
className += ' high-level'; | |||
return helpers.div(className, children); | |||
}, | |||
requireLevel: className => { | |||
if (!item.level) | |||
return; | |||
if (equipErrors.includes('level')) | |||
className += ' high-level'; | |||
const level = item.level.push ? `${item.level[0]} - ${item.level[1]}` : item.level; | |||
return helpers.div(className, `level: ${level}`); | |||
}, | |||
requireStats: className => { | |||
if (!item.requires || !item.requires[0]) | |||
return; | |||
if (equipErrors.includes('stats')) | |||
className += ' high-level'; | |||
let html = `${item.requires[0].stat}: ${item.requires[0].value}`; | |||
return helpers.div(className, html); | |||
}, | |||
requireFaction: () => { | |||
if (!item.factions) | |||
return; | |||
let htmlFactions = ''; | |||
item.factions.forEach((f, i) => { | |||
let htmlF = f.name + ': ' + f.tierName; | |||
if (f.noEquip) | |||
htmlF = '<font class="color-red">' + htmlF + '</font>'; | |||
htmlFactions += htmlF; | |||
if (i < item.factions.length - 1) | |||
htmlFactions += '<br />'; | |||
}); | |||
return htmlFactions; | |||
}, | |||
worth: () => { | |||
if (!item.worthText) | |||
return; | |||
return `<br />value: ${item.worthText}`; | |||
}, | |||
info: () => { | |||
if (item.material || item.quest || item.eq || item.ability) | |||
return; | |||
if (item.cd) | |||
return `cooldown: ${item.cd}`; | |||
else if (item.uses) | |||
return `uses: ${item.uses}`; | |||
else if (!shiftDown && compare) | |||
return '[shift] to compare'; | |||
else if (isMobile && compare && !shiftDown) | |||
return 'tap again to compare'; | |||
} | |||
}; | |||
return { | |||
init, | |||
helpers | |||
}; | |||
}); |
@@ -0,0 +1,97 @@ | |||
define([ | |||
'js/input', | |||
'js/system/events', | |||
'js/misc/statTranslations', | |||
'ui/templates/tooltipItem/helpers', | |||
'ui/templates/tooltipItem/getCompareItem' | |||
], function ( | |||
input, | |||
events, | |||
statTranslations, | |||
helpers, | |||
getCompareItem, | |||
tplTooltip | |||
) { | |||
const { init: initHelpers, helpers: g } = helpers; | |||
const buildTooltipHtml = ({ quality }) => { | |||
const html = [ | |||
g.div(`name q${quality}`, [ | |||
g.div('text', g.name()), | |||
g.div('type', g.type()), | |||
g.div('power', g.power()) | |||
]), | |||
g.div('implicitStats', g.implicitStats()), | |||
g.div('stats', g.stats()), | |||
g.div('effects', g.effects()), | |||
g.div('material', g.material()), | |||
g.div('quest', g.quest()), | |||
g.spellName(), | |||
g.div('damage', g.damage()), | |||
g.requires('requires', [ | |||
'requires', | |||
g.requireLevel('level'), | |||
g.requireStats('stats'), | |||
g.requireFaction('faction') | |||
]), | |||
g.div('worth', g.worth()), | |||
g.div('info', g.info()) | |||
] | |||
.filter(t => t !== null) | |||
.join(''); | |||
return html; | |||
}; | |||
const onShowItemTooltip = (ui, item, pos, canCompare, bottomAlign) => { | |||
let shiftDown = input.isKeyDown('shift', true); | |||
const equipErrors = window.player.inventory.equipItemErrors(item); | |||
const msg = { | |||
item, | |||
compare: null | |||
}; | |||
getCompareItem(msg); | |||
const useItem = item = msg.item; | |||
if (isMobile && useItem === ui.item) | |||
shiftDown = true; | |||
ui.item = useItem; | |||
const compare = canCompare ? msg.compare : null; | |||
ui.item = item; | |||
ui.removeButton(); | |||
initHelpers(item, compare, shiftDown, equipErrors); | |||
const contents = buildTooltipHtml(item); | |||
const el = ui.tooltip; | |||
el.html(contents); | |||
el.show(); | |||
if (pos) { | |||
if (bottomAlign) | |||
pos.y -= el.height(); | |||
//correct tooltips that are appearing offscreen | |||
// arbitrary constant -30 is there to stop resize code | |||
// completely squishing the popup | |||
if ((pos.x + el.width()) > window.innerWidth) | |||
pos.x = window.innerWidth - el.width() - 30; | |||
if ((pos.y + el.height()) > window.innerHeight) | |||
pos.y = window.innerHeight - el.height() - 30; | |||
el.css({ | |||
left: pos.x, | |||
top: pos.y | |||
}); | |||
} | |||
events.emit('onBuiltItemTooltip', ui.tooltip); | |||
}; | |||
return onShowItemTooltip; | |||
}); |
@@ -1 +1,104 @@ | |||
.uiTooltipItem>*{z-index:999999}.uiTooltipItem .tooltip{display:none;position:absolute;margin-left:-4px;margin-top:-4px;background-color:rgba(60,63,76,0.95);pointer-events:none;padding:10px;color:#f2f5f5;text-align:center;line-height:18px;max-width:300px}.uiTooltipItem .tooltip .name{margin-bottom:8px}.uiTooltipItem .tooltip .name .type{color:#7f9c9c}.uiTooltipItem .tooltip .name .power{color:#80f643;display:none}.uiTooltipItem .tooltip>.implicitStats{color:#b8c9c9;margin-bottom:8px;padding-bottom:8px;border-bottom:2px solid #505360}.uiTooltipItem .tooltip>.implicitStats .gainStat{color:#80f643}.uiTooltipItem .tooltip>.implicitStats .loseStat{color:#d43346}.uiTooltipItem .tooltip>.stats{color:#b8c9c9;margin-bottom:8px;padding-bottom:8px;border-bottom:2px solid #505360}.uiTooltipItem .tooltip>.stats .gainStat{color:#80f643}.uiTooltipItem .tooltip>.stats .loseStat{color:#d43346}.uiTooltipItem .tooltip>.stats .enchanted{color:#44cb95 !important}.uiTooltipItem .tooltip .effects{color:#f2f5f5;margin-bottom:8px}.uiTooltipItem .tooltip .faction{color:#7f9c9c;margin-bottom:8px}.uiTooltipItem .tooltip .requires{margin-top:8px;color:#7f9c9c}.uiTooltipItem .tooltip .requires.high-level{color:#d43346}.uiTooltipItem .tooltip .requires .high-level{color:#d43346}.uiTooltipItem .tooltip .requires>*:not(.high-level){color:#7f9c9c}.uiTooltipItem .tooltip .material{color:#7f9c9c;display:none}.uiTooltipItem .tooltip .quest{color:#7f9c9c;display:none}.uiTooltipItem .tooltip .info{color:#4f6666}.uiTooltipItem .tooltip .damage .gainDamage{color:#80f643}.uiTooltipItem .tooltip .damage .loseDamage{color:#d43346}.uiTooltipItem .tooltip .worth{display:none}.uiTooltipItem .tooltip .worth.no-afford{color:#d43346}.uiTooltipItem .tooltip.no-compare .info{display:none}.uiTooltipItem>.btn{position:absolute;background-color:#3fa7dd;color:#f2f5f5;padding:10px;text-align:center}.mobile .uiTooltipItem{z-index:9999999999}.mobile .uiTooltipItem>*{z-index:9999999999} | |||
.uiTooltipItem > * { | |||
z-index: 999999; | |||
} | |||
.uiTooltipItem .tooltip { | |||
display: none; | |||
position: absolute; | |||
margin-left: -4px; | |||
margin-top: -4px; | |||
background-color: rgba(60, 63, 76, 0.95); | |||
pointer-events: none; | |||
padding: 10px; | |||
color: #f2f5f5; | |||
text-align: center; | |||
line-height: 18px; | |||
max-width: 300px; | |||
} | |||
.uiTooltipItem .tooltip .name { | |||
margin-bottom: 8px; | |||
} | |||
.uiTooltipItem .tooltip .name .type { | |||
color: #7f9c9c; | |||
} | |||
.uiTooltipItem .tooltip .name .power { | |||
color: #80f643; | |||
} | |||
.uiTooltipItem .tooltip > .implicitStats { | |||
color: #b8c9c9; | |||
margin-bottom: 8px; | |||
padding-bottom: 8px; | |||
border-bottom: 2px solid #505360; | |||
} | |||
.uiTooltipItem .tooltip > .implicitStats .gainStat { | |||
color: #80f643; | |||
} | |||
.uiTooltipItem .tooltip > .implicitStats .loseStat { | |||
color: #d43346; | |||
} | |||
.uiTooltipItem .tooltip > .stats { | |||
color: #b8c9c9; | |||
margin-bottom: 8px; | |||
padding-bottom: 8px; | |||
border-bottom: 2px solid #505360; | |||
} | |||
.uiTooltipItem .tooltip > .stats .gainStat { | |||
color: #80f643; | |||
} | |||
.uiTooltipItem .tooltip > .stats .loseStat { | |||
color: #d43346; | |||
} | |||
.uiTooltipItem .tooltip > .stats .enchanted { | |||
color: #44cb95 !important; | |||
} | |||
.uiTooltipItem .tooltip .effects { | |||
color: #f2f5f5; | |||
margin-bottom: 8px; | |||
} | |||
.uiTooltipItem .tooltip .faction { | |||
color: #7f9c9c; | |||
margin-bottom: 8px; | |||
} | |||
.uiTooltipItem .tooltip .requires { | |||
margin-top: 8px; | |||
color: #7f9c9c; | |||
} | |||
.uiTooltipItem .tooltip .requires.high-level { | |||
color: #d43346; | |||
} | |||
.uiTooltipItem .tooltip .requires .high-level { | |||
color: #d43346; | |||
} | |||
.uiTooltipItem .tooltip .requires > *:not(.high-level) { | |||
color: #7f9c9c; | |||
} | |||
.uiTooltipItem .tooltip .material { | |||
color: #7f9c9c; | |||
} | |||
.uiTooltipItem .tooltip .quest { | |||
color: #7f9c9c; | |||
} | |||
.uiTooltipItem .tooltip .info { | |||
color: #4f6666; | |||
} | |||
.uiTooltipItem .tooltip .damage .gainDamage { | |||
color: #80f643; | |||
} | |||
.uiTooltipItem .tooltip .damage .loseDamage { | |||
color: #d43346; | |||
} | |||
.uiTooltipItem .tooltip .worth.no-afford { | |||
color: #d43346; | |||
} | |||
.uiTooltipItem > .btn { | |||
position: absolute; | |||
background-color: #3fa7dd; | |||
color: #f2f5f5; | |||
padding: 10px; | |||
text-align: center; | |||
} | |||
.mobile .uiTooltipItem { | |||
z-index: 9999999999; | |||
} | |||
.mobile .uiTooltipItem > * { | |||
z-index: 9999999999; | |||
} |
@@ -27,8 +27,8 @@ | |||
.power { | |||
color: @green; | |||
display: none; | |||
} | |||
} | |||
> .implicitStats { | |||
@@ -44,6 +44,7 @@ | |||
.loseStat { | |||
color: @red; | |||
} | |||
} | |||
> .stats { | |||
@@ -63,6 +64,7 @@ | |||
.enchanted { | |||
color: @tealC !important; | |||
} | |||
} | |||
.effects { | |||
@@ -90,17 +92,16 @@ | |||
> *:not(.high-level) { | |||
color: darken(@white, 40%); | |||
} | |||
} | |||
.material { | |||
color: darken(@white, 40%); | |||
display: none; | |||
} | |||
.quest { | |||
color: darken(@white, 40%); | |||
display: none; | |||
} | |||
} | |||
.info { | |||
color: darken(@white, 60%); | |||
@@ -114,19 +115,16 @@ | |||
.loseDamage { | |||
color: @red; | |||
} | |||
} | |||
.worth { | |||
display: none; | |||
&.no-afford { | |||
color: @red; | |||
} | |||
} | |||
&.no-compare .info { | |||
display: none; | |||
} | |||
} | |||
> .btn { | |||
@@ -136,6 +134,7 @@ | |||
padding: 10px; | |||
text-align: center; | |||
} | |||
} | |||
.mobile .uiTooltipItem { | |||
@@ -144,4 +143,5 @@ | |||
> * { | |||
z-index: 9999999999; | |||
} | |||
} |
@@ -1,20 +0,0 @@ | |||
<div class="name q$QUALITY$"> | |||
<div class="text">$NAME$</div> | |||
<div class="type">$TYPE$</div> | |||
<div class="power">$POWER$</div> | |||
</div> | |||
<div class="implicitStats">$IMPLICITSTATS$</div> | |||
<div class="stats">$STATS$</div> | |||
<div class="effects">$EFFECTS$</div> | |||
<div class="material">crafting material</div> | |||
<div class="quest">quest item</div> | |||
<div class="spellName">$SPELLNAME$</div> | |||
<div class="damage">$DAMAGE$</div> | |||
<div class="requires"> | |||
requires: | |||
<div class="level">level: $LEVEL$</div> | |||
<div class="stats">$ATTRIBUTE$: $ATTRIBUTEVALUE$</div> | |||
<div class="faction">faction: $faction$</div> | |||
</div> | |||
<div class="worth"></div> | |||
<div class="info"><br />[shift] to compare</div> |
@@ -1,42 +1,12 @@ | |||
define([ | |||
'js/system/events', | |||
'ui/templates/tooltipItem/onShowItemTooltip', | |||
'css!ui/templates/tooltipItem/styles', | |||
'html!ui/templates/tooltipItem/template', | |||
'html!ui/templates/tooltipItem/templateTooltip', | |||
'js/misc/statTranslations', | |||
'js/input' | |||
'html!ui/templates/tooltipItem/template' | |||
], function ( | |||
events, | |||
onShowItemTooltip, | |||
styles, | |||
template, | |||
tplTooltip, | |||
statTranslations, | |||
input | |||
template | |||
) { | |||
let percentageStats = [ | |||
'addCritChance', | |||
'addCritMultiplier', | |||
'addAttackCritChance', | |||
'addAttackCritMultiplier', | |||
'addSpellCritChance', | |||
'addSpellCritMultiplier', | |||
'sprintChance', | |||
'xpIncrease', | |||
'blockAttackChance', | |||
'blockSpellChance', | |||
'dodgeAttackChance', | |||
'dodgeSpellChance', | |||
'attackSpeed', | |||
'castSpeed', | |||
'itemQuantity', | |||
'magicFind', | |||
'catchChance', | |||
'catchSpeed', | |||
'fishRarity', | |||
'fishWeight', | |||
'fishItems' | |||
]; | |||
return { | |||
tpl: template, | |||
type: 'tooltipItem', | |||
@@ -47,83 +17,15 @@ define([ | |||
postRender: function () { | |||
this.tooltip = this.el.find('.tooltip'); | |||
this.onEvent('onShowItemTooltip', this.onShowItemTooltip.bind(this)); | |||
this.onEvent('onShowItemTooltip', onShowItemTooltip.bind(null, this)); | |||
this.onEvent('onHideItemTooltip', this.onHideItemTooltip.bind(this)); | |||
}, | |||
getCompareItem: function (msg) { | |||
const shiftDown = input.isKeyDown('shift', true); | |||
let item = msg.item; | |||
let items = window.player.inventory.items; | |||
let compare = null; | |||
if (item.slot) { | |||
compare = items.find(i => i.eq && i.slot === item.slot); | |||
// check special cases for mismatched weapon/offhand scenarios (only valid when comparing) | |||
if (!compare) { | |||
let equippedTwoHanded = items.find(i => i.eq && i.slot === 'twoHanded'); | |||
let equippedOneHanded = items.find(i => i.eq && i.slot === 'oneHanded'); | |||
let equippedOffhand = items.find(i => i.eq && i.slot === 'offHand'); | |||
if (item.slot === 'twoHanded') { | |||
if (!equippedOneHanded) | |||
compare = equippedOffhand; | |||
else if (!equippedOffhand) | |||
compare = equippedOneHanded; | |||
else { | |||
// compare against oneHanded and offHand combined by creating a virtual item that is the sum of the two | |||
compare = $.extend(true, {}, equippedOneHanded); | |||
compare.refItem = equippedOneHanded; | |||
for (let s in equippedOffhand.stats) { | |||
if (!compare.stats[s]) | |||
compare.stats[s] = 0; | |||
compare.stats[s] += equippedOffhand.stats[s]; | |||
} | |||
if (!compare.implicitStats) | |||
compare.implicitStats = []; | |||
(equippedOffhand.implicitStats || []).forEach(s => { | |||
let f = compare.implicitStats.find(i => i.stat === s.stat); | |||
if (!f) { | |||
compare.implicitStats.push({ | |||
stat: s.stat, | |||
value: s.value | |||
}); | |||
} else | |||
f.value += s.value; | |||
}); | |||
} | |||
} | |||
if (item.slot === 'oneHanded') | |||
compare = equippedTwoHanded; | |||
// this case is kind of ugly, but we don't want to go in when comparing an offHand to (oneHanded + empty offHand) - that should just use the normal compare which is offHand to empty | |||
if (item.slot === 'offHand' && equippedTwoHanded && shiftDown) { | |||
// since we're comparing an offhand to an equipped Twohander, we need to clone the 'spell' values over (setting damage to zero) so that we can properly display how much damage | |||
// the player would lose by switching to the offhand (which would remove the twoHander) | |||
// keep a reference to the original item for use in onHideToolTip | |||
let spellClone = $.extend(true, {}, equippedTwoHanded.spell); | |||
spellClone.name = ''; | |||
spellClone.values.damage = 0; | |||
let clone = $.extend(true, {}, item, { | |||
spell: spellClone | |||
}); | |||
clone.refItem = item; | |||
msg.item = clone; | |||
compare = equippedTwoHanded; | |||
} | |||
} | |||
} | |||
showWorth: function (canAfford) { | |||
this.tooltip.find('.worth').show(); | |||
msg.compare = compare; | |||
if (!canAfford) | |||
this.tooltip.find('.worth').addClass('no-afford'); | |||
}, | |||
onHideItemTooltip: function (item) { | |||
@@ -143,371 +45,6 @@ define([ | |||
this.removeButton(); | |||
}, | |||
onShowItemTooltip: function (item, pos, canCompare, bottomAlign) { | |||
this.removeButton(); | |||
let shiftDown = input.isKeyDown('shift', true); | |||
let msg = { | |||
item: item, | |||
compare: null | |||
}; | |||
this.getCompareItem(msg); | |||
let useItem = item = msg.item; | |||
if (isMobile && useItem === this.item) | |||
shiftDown = true; | |||
this.item = useItem; | |||
let compare = canCompare ? msg.compare : null; | |||
let tempStats = $.extend(true, {}, item.stats); | |||
let tempImplicitStats = $.extend(true, [], item.implicitStats); | |||
let enchantedStats = item.enchantedStats || {}; | |||
if (compare && shiftDown) { | |||
if (!item.eq) { | |||
let compareStats = compare.stats; | |||
for (let s in tempStats) { | |||
if (compareStats[s]) { | |||
let delta = tempStats[s] - compareStats[s]; | |||
if (delta > 0) | |||
tempStats[s] = '+' + delta; | |||
else if (delta < 0) | |||
tempStats[s] = delta; | |||
} else | |||
tempStats[s] = '+' + tempStats[s]; | |||
} | |||
for (let s in compareStats) { | |||
if (!tempStats[s]) | |||
tempStats[s] = -compareStats[s]; | |||
} | |||
let compareImplicitStats = (compare.implicitStats || []); | |||
tempImplicitStats.forEach(s => { | |||
let statValue = s.value; | |||
let f = compareImplicitStats.find(c => c.stat === s.stat); | |||
if (f) { | |||
let delta = statValue - f.value; | |||
if (delta > 0) | |||
s.value = '+' + delta; | |||
else if (delta < 0) | |||
s.value = delta; | |||
} else | |||
s.value = '+' + s.value; | |||
}); | |||
compareImplicitStats.forEach(s => { | |||
if (!tempImplicitStats.find(f => f.stat === s.stat)) { | |||
tempImplicitStats.push({ | |||
stat: s.stat, | |||
value: -s.value | |||
}); | |||
} | |||
}); | |||
} | |||
} else { | |||
Object.keys(tempStats).forEach(function (s) { | |||
if (enchantedStats[s]) { | |||
tempStats[s] -= enchantedStats[s]; | |||
if (tempStats[s] <= 0) | |||
delete tempStats[s]; | |||
tempStats['_' + s] = enchantedStats[s]; | |||
} | |||
}); | |||
} | |||
let stats = Object.keys(tempStats) | |||
.map(s => { | |||
let isEnchanted = (s[0] === '_'); | |||
let statName = s; | |||
if (isEnchanted) | |||
statName = statName.substr(1); | |||
let value = this.getStatValue(statName, tempStats[s]); | |||
statName = statTranslations.translate(statName); | |||
let row = value + ' ' + statName; | |||
let rowClass = ''; | |||
if (compare) { | |||
if (row.indexOf('-') > -1) | |||
rowClass = 'loseStat'; | |||
else if (row.indexOf('+') > -1) | |||
rowClass = 'gainStat'; | |||
} | |||
if (isEnchanted) | |||
rowClass += ' enchanted'; | |||
row = '<div class="' + rowClass + '">' + row + '</div>'; | |||
return row; | |||
}) | |||
.sort(function (a, b) { | |||
return (a.replace(' enchanted', '').length - b.replace(' enchanted', '').length); | |||
}) | |||
.sort(function (a, b) { | |||
if ((a.indexOf('enchanted') > -1) && (b.indexOf('enchanted') === -1)) | |||
return 1; | |||
else if ((a.indexOf('enchanted') === -1) && (b.indexOf('enchanted') > -1)) | |||
return -1; | |||
return 0; | |||
}) | |||
.join(''); | |||
let implicitStats = tempImplicitStats | |||
.map(s => { | |||
let statName = s.stat; | |||
let value = this.getStatValue(statName, s.value); | |||
statName = statTranslations.translate(statName); | |||
let row = value + ' ' + statName; | |||
let rowClass = ''; | |||
if (compare) { | |||
if (row.indexOf('-') > -1) | |||
rowClass = 'loseStat'; | |||
else if (row.indexOf('+') > -1) | |||
rowClass = 'gainStat'; | |||
} | |||
row = '<div class="' + rowClass + '">' + row + '</div>'; | |||
return row; | |||
}) | |||
.join(''); | |||
let itemName = item.name; | |||
if (item.quantity > 1) | |||
itemName += ' x' + item.quantity; | |||
let level = null; | |||
if (item.level) | |||
level = item.level.push ? item.level[0] + ' - ' + item.level[1] : item.level; | |||
let html = tplTooltip | |||
.replace('$NAME$', itemName) | |||
.replace('$QUALITY$', item.quality) | |||
.replace('$TYPE$', item.type) | |||
.replace('$SLOT$', item.slot) | |||
.replace('$IMPLICITSTATS$', implicitStats) | |||
.replace('$STATS$', stats) | |||
.replace('$LEVEL$', level); | |||
if (item.requires && item.requires[0]) { | |||
html = html | |||
.replace('$ATTRIBUTE$', item.requires[0].stat) | |||
.replace('$ATTRIBUTEVALUE$', item.requires[0].value); | |||
} | |||
if (item.power) | |||
html = html.replace('$POWER$', ' ' + (new Array(item.power + 1)).join('+')); | |||
if ((item.spell) && (item.spell.values)) { | |||
let abilityValues = ''; | |||
for (let p in item.spell.values) { | |||
if ((compare) && (shiftDown)) { | |||
let delta = item.spell.values[p] - compare.spell.values[p]; | |||
// adjust by EPSILON to handle float point imprecision, otherwise 3.15 - 2 = 1.14 or 2 - 3.15 = -1.14 | |||
// have to move away from zero by EPSILON, not a simple add | |||
if (delta >= 0) | |||
delta += Number.EPSILON; | |||
else | |||
delta -= Number.EPSILON; | |||
delta = ~~((delta) * 100) / 100; | |||
let rowClass = ''; | |||
if (delta > 0) { | |||
rowClass = 'gainDamage'; | |||
delta = '+' + delta; | |||
} else if (delta < 0) | |||
rowClass = 'loseDamage'; | |||
abilityValues += '<div class="' + rowClass + '">' + p + ': ' + delta + '</div>'; | |||
} else | |||
abilityValues += p + ': ' + item.spell.values[p] + '<br/>'; | |||
} | |||
if (!item.ability) | |||
abilityValues = abilityValues; | |||
html = html.replace('$DAMAGE$', abilityValues); | |||
} | |||
let tooltip = this.tooltip; | |||
tooltip.html(html); | |||
if (!item.level) | |||
tooltip.find('.level').hide(); | |||
else | |||
tooltip.find('.level').show(); | |||
if (!item.implicitStats) | |||
tooltip.find('.implicitStats').hide(); | |||
else | |||
tooltip.find('.implicitStats').show(); | |||
if (!item.requires) { | |||
if (!item.level && (!item.factions || !item.factions.length)) | |||
tooltip.find('.requires').hide(); | |||
else | |||
tooltip.find('.requires .stats').hide(); | |||
} else | |||
tooltip.find('.requires .stats').show(); | |||
if (!stats.length) | |||
tooltip.children('.stats').hide(); | |||
if ((!item.type) || (item.type === item.name)) | |||
tooltip.find('.type').hide(); | |||
else { | |||
tooltip.find('.type') | |||
.html(item.type) | |||
.show(); | |||
} | |||
if (item.power) | |||
tooltip.find('.power').show(); | |||
let equipErrors = window.player.inventory.equipItemErrors(item); | |||
equipErrors.forEach(function (e) { | |||
tooltip.find('.requires').addClass('high-level'); | |||
tooltip.find('.requires .' + e).addClass('high-level'); | |||
}, this); | |||
if ((item.material) || (item.quest)) { | |||
tooltip.find('.level').hide(); | |||
tooltip.find('.info').hide(); | |||
if (item.material) | |||
tooltip.find('.material').show(); | |||
else if (item.quest) | |||
tooltip.find('.quest').show(); | |||
} else if (item.eq) | |||
tooltip.find('.info').hide(); | |||
if (!item.ability) | |||
tooltip.find('.damage').hide(); | |||
else | |||
tooltip.find('.info').hide(); | |||
if (item.spell) { | |||
tooltip.find('.spellName') | |||
.html(item.spell.name) | |||
.addClass('q' + item.spell.quality) | |||
.show(); | |||
tooltip.find('.damage') | |||
.show(); | |||
if (item.ability) | |||
tooltip.find('.spellName').hide(); | |||
} else | |||
tooltip.find('.spellName').hide(); | |||
tooltip.find('.worth').html(item.worthText ? ('<br />value: ' + item.worthText) : ''); | |||
if (item.effects && item.effects[0].text && item.type !== 'mtx') { | |||
let htmlEffects = ''; | |||
item.effects.forEach(function (e, i) { | |||
htmlEffects += e.text; | |||
if (i < item.effects.length - 1) | |||
htmlEffects += '<br />'; | |||
}); | |||
this.find('.effects') | |||
.html(htmlEffects) | |||
.show(); | |||
} else if (item.description) { | |||
this.find('.effects') | |||
.html(item.description) | |||
.show(); | |||
} else | |||
this.find('.effects').hide(); | |||
if (item.type === 'Reward Card') { | |||
this.find('.spellName') | |||
.html('Set Size: ' + item.setSize) | |||
.show(); | |||
} | |||
if (item.factions) { | |||
let htmlFactions = ''; | |||
item.factions.forEach(function (f, i) { | |||
let htmlF = f.name + ': ' + f.tierName; | |||
if (f.noEquip) | |||
htmlF = '<font class="color-red">' + htmlF + '</font>'; | |||
htmlFactions += htmlF; | |||
if (i < item.factions.length - 1) | |||
htmlFactions += '<br />'; | |||
}); | |||
this.find('.faction') | |||
.html(htmlFactions) | |||
.show(); | |||
} else | |||
this.find('.faction').hide(); | |||
if (shiftDown || !compare) | |||
tooltip.find('.info').hide(); | |||
else if (isMobile && compare && !shiftDown) | |||
tooltip.find('.info').html('tap again to compare'); | |||
if (item.cd) { | |||
tooltip.find('.info') | |||
.html('cooldown: ' + item.cd) | |||
.show(); | |||
} else if (item.uses) { | |||
tooltip.find('.info') | |||
.html('uses: ' + item.uses) | |||
.show(); | |||
} | |||
this.tooltip | |||
.show(); | |||
if (pos) { | |||
if (bottomAlign) | |||
pos.y -= this.tooltip.height(); | |||
//correct tooltips that are appearing offscreen | |||
// arbitrary constant -30 is there to stop resize code | |||
// completely squishing the popup | |||
if ((pos.x + this.tooltip.width()) > window.innerWidth) | |||
pos.x = window.innerWidth - this.tooltip.width() - 30; | |||
if ((pos.y + this.tooltip.height()) > window.innerHeight) | |||
pos.y = window.innerHeight - this.tooltip.height() - 30; | |||
this.tooltip.css({ | |||
left: pos.x, | |||
top: pos.y | |||
}); | |||
} | |||
events.emit('onBuiltItemTooltip', this.tooltip); | |||
}, | |||
getStatValue: function (statName, statValue) { | |||
let res = statValue; | |||
if (statName.indexOf('CritChance') > -1) | |||
res = res / 20; | |||
if (percentageStats.includes(statName) || statName.indexOf('Percent') > -1 || (statName.indexOf('element') === 0 && statName.indexOf('Resist') === -1)) | |||
res += '%'; | |||
return res; | |||
}, | |||
showWorth: function (canAfford) { | |||
this.tooltip.find('.worth').show(); | |||
if (!canAfford) | |||
this.tooltip.find('.worth').addClass('no-afford'); | |||
}, | |||
addButton: function (label, cb) { | |||
let tt = this.tooltip; | |||
let pos = tt.offset(); | |||