mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-05-09 04:23:01 -05:00
The utilichart-relevant code has been overhauled for simplicity. Utilichart results and headers are now both 33 pixels tall, total. Headers are slightly larger, to make them fill the space better. The larger font size makes them more header-like, anyway. Results are two pixels taller than before, giving them enough room for gen 6 pokemon icons (not enabled in this commit), and in general aiding performance slightly (fewer result rows on the screen at a time). Making them both 33 pixels allows height to be computed precisely as 33*rows, which is relevant for result rows to be inserted into the DOM on-demand. On-demand DOM insertion has previously been implemented in pokedex.js, but the new on-demand DOM code in search.js works slightly differently. It makes stronger assumptions (that the only bottleneck is DOM insertion speed, and that ctrl+f without scrolling to the bottom doesn't need to be supported, and that result rows and result headers are always 33 pixels tall), which makes for much simpler code, and is presumably slightly faster because of this. The new on-demand DOM code is surfaced by an "All results" button in the pokedex (pokemonshowdown.com/dex/), for basic testing, but the long-term plan is for it to be used in the teambuilder for pokemon/etc selection, to massively increase performance. Nearly two years after I wrote search.js to be a better utilichart API than utilichart.js, we'll soon finally start using it in place of utilichart.js in the main sim teambuilder!
528 lines
17 KiB
JavaScript
528 lines
17 KiB
JavaScript
function BattleChart() {
|
|
var self = this;
|
|
|
|
this.firstResult = '';
|
|
this.exactResult = false;
|
|
this.lastSearch = '[init]';
|
|
|
|
// I know, I know, lots of memory
|
|
// but these are arrays of pointers, so it's not _horrible_
|
|
// and this let me cache sorting
|
|
this.pokemon = [];
|
|
for (var i in BattlePokedex) {
|
|
BattlePokedex[i].name = BattlePokedex[i].species;
|
|
this.pokemon.push(BattlePokedex[i]);
|
|
}
|
|
this.items = [];
|
|
for (var i in BattleItems) {
|
|
this.items.push(BattleItems[i]);
|
|
}
|
|
this.abilities = [];
|
|
for (var i in BattleAbilities) {
|
|
this.abilities.push(BattleAbilities[i]);
|
|
}
|
|
this.moves = [];
|
|
for (var i in BattleMovedex) {
|
|
this.moves.push(BattleMovedex[i]);
|
|
}
|
|
|
|
this.lastPokemonSort = null;
|
|
this.lastItemSort = null;
|
|
this.lastAbilitySort = null;
|
|
this.lastMoveSort = null;
|
|
|
|
this.illegalBuckets = {
|
|
'Illegal': 'Illegal'
|
|
};
|
|
|
|
this.selectCallback = null;
|
|
this.select = function (x) {
|
|
if (self.selectCallback) self.selectCallback(x);
|
|
};
|
|
|
|
this.row = function (thing, attrs, match, isFirst, dataCommand, gen) {
|
|
if (isFirst) {
|
|
self.firstResult = thing.name;
|
|
}
|
|
attrs = attrs || '';
|
|
switch (match.thingType) {
|
|
case 'pokemon':
|
|
return self.pokemonRow(thing, attrs, match, isFirst, dataCommand, gen);
|
|
case 'item':
|
|
return self.itemRow(thing, attrs, match, isFirst, dataCommand);
|
|
case 'ability':
|
|
return self.abilityRow(thing, attrs, match, isFirst, dataCommand);
|
|
case 'move':
|
|
return self.moveRow(thing, attrs, match, isFirst, dataCommand, gen);
|
|
}
|
|
};
|
|
this.pokemonRow = function (pokemon, attrs, match, isFirst, dataCommand, gen) {
|
|
var tag = dataCommand ? 'div' : 'a';
|
|
var text = '<li class="result' + (isFirst ? ' firstresult' : '') + '"><' + tag + attrs + ' data-name="' + Tools.escapeHTML(pokemon.species) + '">';
|
|
|
|
var tier = pokemon.tier;
|
|
if (!tier) tier = Tools.getTemplate(pokemon.baseSpecies || pokemon.species).tier || 'Illegal';
|
|
|
|
text += '<span class="col numcol">' + tier + '</span> ';
|
|
|
|
var name = Tools.escapeHTML(pokemon.name);
|
|
|
|
if (pokemon.forme && pokemon.baseSpecies) name = pokemon.baseSpecies;
|
|
if (match.name) {
|
|
name = name.substr(0, match.name.start) + '<b>' + name.substr(match.name.start, match.name.end - match.name.start) + '</b>' + name.substr(match.name.end);
|
|
}
|
|
if (pokemon.forme && pokemon.baseSpecies) {
|
|
if (match.name && match.name.end > pokemon.baseSpecies.length) {
|
|
if (match.name.start < pokemon.baseSpecies.length + 1) match.name.start = pokemon.baseSpecies.length + 1;
|
|
name += '<small>-' + pokemon.forme.substr(0, match.name.start - (pokemon.baseSpecies.length + 1)) + '<b>' + pokemon.name.substr(match.name.start, match.name.end - match.name.start) + '</b>' + pokemon.name.substr(match.name.end) + '</small>';
|
|
} else {
|
|
name += '<small>-' + pokemon.forme + '</small>';
|
|
}
|
|
}
|
|
if (dataCommand) {
|
|
name = '<a href="https://pokemonshowdown.com/dex/pokemon/' + pokemon.id + '" target="_blank">' + name + '</a>';
|
|
}
|
|
|
|
/* if (match.name)
|
|
{
|
|
name = name.substr(0, match.name.start) + '<b>' + name.substr(match.name.start, match.name.end-match.name.start) + '</b>' + name.substr(match.name.end);
|
|
} */
|
|
text += '<span class="col iconcol">';
|
|
text += '<span style="' + Tools.getIcon(pokemon) + '"></span>';
|
|
text += '</span> ';
|
|
text += '<span class="col pokemonnamecol" style="white-space:nowrap">' + name + '</span> ';
|
|
|
|
if (self.illegalBuckets[match.bucket]) {
|
|
text += '<span class="col illegalcol"><em>' + self.illegalBuckets[match.bucket] + '</em></span> ';
|
|
text += '</a></li>';
|
|
return text;
|
|
}
|
|
|
|
text += '<span class="col typecol">';
|
|
if (pokemon.types) for (var i = 0; i < pokemon.types.length; i++) {
|
|
text += Tools.getTypeIcon(pokemon.types[i], (match.type && match.type[i]));
|
|
}
|
|
text += '</span> ';
|
|
|
|
if (!gen || gen > 2) {
|
|
text += '<span style="float:left;min-height:26px">';
|
|
if (pokemon.abilities['1']) {
|
|
text += '<span class="col twoabilitycol">';
|
|
} else {
|
|
text += '<span class="col abilitycol">';
|
|
}
|
|
for (var i in pokemon.abilities) {
|
|
var ability = pokemon.abilities[i];
|
|
if (!ability) continue;
|
|
|
|
if (i === '1') text += '<br />';
|
|
if (match.ability && match.ability[i]) {
|
|
ability = ability.substr(0, match.ability[i].start) + '<b>' + ability.substr(match.ability[i].start, match.ability[i].end - match.ability[i].start) + '</b>' + ability.substr(match.ability[i].end);
|
|
}
|
|
if (i == 'H') {
|
|
ability = '</span><span class="col abilitycol"><em>' + (pokemon.unreleasedHidden ? '<s>' + ability + '</s>' : ability) + '</em>';
|
|
}
|
|
text += ability;
|
|
}
|
|
if (!pokemon.abilities['H']) text += '</span><span class="col abilitycol">';
|
|
text += '</span>';
|
|
text += '</span>';
|
|
}
|
|
|
|
text += '<span style="float:left;min-height:26px">';
|
|
text += '<span class="col statcol"><em>HP</em><br />' + pokemon.baseStats.hp + '</span> ';
|
|
text += '<span class="col statcol"><em>Atk</em><br />' + pokemon.baseStats.atk + '</span> ';
|
|
text += '<span class="col statcol"><em>Def</em><br />' + pokemon.baseStats.def + '</span> ';
|
|
if (!gen || gen > 1) {
|
|
text += '<span class="col statcol"><em>SpA</em><br />' + pokemon.baseStats.spa + '</span> ';
|
|
text += '<span class="col statcol"><em>SpD</em><br />' + pokemon.baseStats.spd + '</span> ';
|
|
} else {
|
|
text += '<span class="col statcol"><em>Spc</em><br />' + pokemon.baseStats.spa + '</span> ';
|
|
}
|
|
text += '<span class="col statcol"><em>Spe</em><br />' + pokemon.baseStats.spe + '</span> ';
|
|
var bst = 0;
|
|
for (i in pokemon.baseStats) bst += pokemon.baseStats[i];
|
|
text += '<span class="col bstcol"><em>BST<br />' + bst + '</em></span> ';
|
|
text += '</span>';
|
|
|
|
text += '</' + tag + '></li>';
|
|
|
|
return text;
|
|
};
|
|
this.itemRow = function (item, attrs, match, isFirst) {
|
|
var text = '<li class="result' + (isFirst ? ' firstresult' : '') + '"><a' + attrs + ' data-name="' + Tools.escapeHTML(item.name) + '">';
|
|
|
|
var url = item.name.toLowerCase().replace(/ /g, '-').replace(/[^a-z-]+/g, '');
|
|
url = '/sprites/itemicons/' + url + '.png';
|
|
|
|
text += '<span class="col itemiconcol">';
|
|
text += '<span style="' + Tools.getItemIcon(item) + '"></span>';
|
|
text += '</span> ';
|
|
|
|
var name = Tools.escapeHTML(item.name);
|
|
|
|
if (match.name) {
|
|
name = name.substr(0, match.name.start) + '<b>' + name.substr(match.name.start, match.name.end - match.name.start) + '</b>' + name.substr(match.name.end);
|
|
}
|
|
text += '<span class="col namecol">' + name + '</span> ';
|
|
|
|
if (self.illegalBuckets[match.bucket]) {
|
|
text += '<span class="col illegalcol"><em>' + self.illegalBuckets[match.bucket] + '</em></span> ';
|
|
text += '</a></li>';
|
|
return text;
|
|
}
|
|
|
|
text += '<span class="col itemdesccol">' + Tools.escapeHTML(item.shortDesc || item.desc) + '</span> ';
|
|
|
|
text += '</a></li>';
|
|
|
|
return text;
|
|
};
|
|
this.abilityRow = function (ability, attrs, match, isFirst) {
|
|
var text = '<li class="result' + (isFirst ? ' firstresult' : '') + '"><a' + attrs + ' data-name="' + Tools.escapeHTML(ability.name) + '">';
|
|
|
|
var name = Tools.escapeHTML(ability.name);
|
|
if (match.name) {
|
|
name = name.substr(0, match.name.start) + '<b>' + name.substr(match.name.start, match.name.end - match.name.start) + '</b>' + name.substr(match.name.end);
|
|
}
|
|
text += '<span class="col namecol">' + name + '</span> ';
|
|
|
|
if (self.illegalBuckets[match.bucket]) {
|
|
text += '<span class="col illegalcol"><em>' + self.illegalBuckets[match.bucket] + '</em></span> ';
|
|
text += '</a></li>';
|
|
return text;
|
|
}
|
|
|
|
text += '<span class="col abilitydesccol">' + Tools.escapeHTML(ability.shortDesc || ability.desc) + '</span> ';
|
|
|
|
text += '</a></li>';
|
|
|
|
return text;
|
|
};
|
|
this.moveRow = function (move, attrs, match, isFirst, gen) {
|
|
var text = '<li class="result' + (isFirst ? ' firstresult' : '') + '"><a' + attrs + ' data-name="' + Tools.escapeHTML(move.name) + '">';
|
|
|
|
var name = Tools.escapeHTML(move.name);
|
|
var hplen = 'Hidden Power'.length;
|
|
if (name.substr(0, hplen) === 'Hidden Power') name = 'Hidden Power';
|
|
if (match.name) {
|
|
name = name.substr(0, match.name.start) + '<b>' + name.substr(match.name.start, match.name.end - match.name.start) + '</b>' + name.substr(match.name.end);
|
|
}
|
|
if (move.name.substr(0, hplen) === 'Hidden Power') {
|
|
if (match.name && match.name.end > hplen) {
|
|
if (match.name.start < hplen + 1) match.name.start = hplen + 1;
|
|
name += '<small> ' + move.name.substr(hplen + 1, match.name.start - (hplen + 1)) + '<b>' + move.name.substr(match.name.start, match.name.end - match.name.start) + '</b>' + move.name.substr(match.name.end) + '</small>';
|
|
} else {
|
|
name += '<small> ' + move.name.substr(hplen + 1) + '</small>';
|
|
}
|
|
}
|
|
text += '<span class="col movenamecol">' + name + '</span> ';
|
|
|
|
if (self.illegalBuckets[match.bucket]) {
|
|
text += '<span class="col illegalcol"><em>' + self.illegalBuckets[match.bucket] + '</em></span> ';
|
|
text += '</a></li>';
|
|
return text;
|
|
}
|
|
|
|
text += '<span class="col typecol">';
|
|
text += Tools.getTypeIcon(move.type, match.type);
|
|
text += '<img src="' + Tools.resourcePrefix + 'sprites/categories/' + move.category + '.png" alt="' + move.category + '" height="14" width="32"' + (match.category ? ' class="b"' : '') + ' />';
|
|
text += '</span> ';
|
|
|
|
text += '<span class="col labelcol">' + (move.category !== 'Status' ? ('<em>Power</em><br />' + (move.basePower || '—')) : '') + '</span> ';
|
|
text += '<span class="col widelabelcol"><em>Accuracy</em><br />' + (move.accuracy && move.accuracy !== true ? move.accuracy + '%' : '—') + '</span> ';
|
|
text += '<span class="col pplabelcol"><em>PP</em><br />' + ((move.pp !== 1 && !move.noPPBoosts) ? move.pp * 8 / 5 : move.pp) + '</span> ';
|
|
|
|
text += '<span class="col movedesccol">' + Tools.escapeHTML(move.shortDesc || move.desc) + '</span> ';
|
|
|
|
text += '</a></li>';
|
|
|
|
return text;
|
|
};
|
|
this.chart = function (searchTerm, type, init, thisArrange, thisSort, gen) {
|
|
if (!searchTerm) searchTerm = '';
|
|
else searchTerm = searchTerm.toLowerCase();
|
|
if (!init && searchTerm === self.lastSearch) return;
|
|
self.lastSearch = searchTerm;
|
|
|
|
var chartData = {
|
|
exact: {},
|
|
start: {},
|
|
contains: {},
|
|
illegalstart: {},
|
|
illegalcontains: {},
|
|
fullstart: {},
|
|
illegalfullstart: {},
|
|
other: {}
|
|
};
|
|
thisArrange = thisArrange || self.defaultArrange;
|
|
thisSort = thisSort || self.defaultSort;
|
|
|
|
self.firstResult = '';
|
|
self.exactResult = false;
|
|
|
|
var buckets = thisArrange();
|
|
var things = [];
|
|
|
|
switch (type) {
|
|
case 'pokemon':
|
|
if (thisSort !== self.lastPokemonSort) {
|
|
self.pokemon.sort(thisSort);
|
|
self.lastPokemonSort = thisSort;
|
|
}
|
|
if (gen > 0) {
|
|
things = self.pokemon.filter(function (mon) {return Tools.getTemplate(mon.speciesid).gen <= gen;});
|
|
} else {
|
|
things = self.pokemon;
|
|
}
|
|
break;
|
|
case 'item':
|
|
if (thisSort !== self.lastItemSort) {
|
|
self.items.sort(thisSort);
|
|
self.lastItemSort = thisSort;
|
|
}
|
|
if (gen === 1) {
|
|
things = [];
|
|
} else if (gen > 0) {
|
|
things = self.items.filter(function (item) {return Tools.getItem(item.id).gen <= gen;});
|
|
} else {
|
|
things = self.items;
|
|
}
|
|
break;
|
|
case 'ability':
|
|
if (thisSort !== self.lastAbilitySort) {
|
|
self.abilities.sort(thisSort);
|
|
self.lastAbilitySort = thisSort;
|
|
}
|
|
if (gen === 1 || gen === 2) {
|
|
things = [];
|
|
} else if (gen > 0) {
|
|
things = self.abilities.filter(function (ability) {return Tools.getAbility(ability.id).gen <= gen;});
|
|
} else {
|
|
things = self.abilities;
|
|
}
|
|
break;
|
|
case 'move':
|
|
if (thisSort !== self.lastMoveSort) {
|
|
self.moves.sort(thisSort);
|
|
self.lastMoveSort = thisSort;
|
|
}
|
|
if (gen > 0) {
|
|
things = self.moves.filter(function (move) {return Tools.getMove(move.id).gen <= gen;});
|
|
} else {
|
|
things = self.moves;
|
|
}
|
|
break;
|
|
}
|
|
|
|
for (var i = 0; i < things.length; i++) {
|
|
var thing = things[i];
|
|
if (type === 'pokemon' && !thing.species || thing.jp) continue;
|
|
|
|
// fill our buckets!
|
|
var bucket = thisArrange(thing);
|
|
|
|
var match = {thing: thing, thingType: type, bucket: bucket};
|
|
var matchType = 'other';
|
|
var index = -1;
|
|
|
|
if (searchTerm) {
|
|
index = thing.name.toLowerCase().indexOf(searchTerm);
|
|
|
|
if (index === 0) {
|
|
matchType = 'start';
|
|
match.name = {
|
|
start: index,
|
|
end: index + searchTerm.length
|
|
};
|
|
if (searchTerm.length === thing.name.length) {
|
|
self.exactResult = thing.name;
|
|
matchType = 'exact';
|
|
}
|
|
} else if (index >= 0) {
|
|
matchType = 'contains';
|
|
match.name = {
|
|
start: index,
|
|
end: index + searchTerm.length
|
|
};
|
|
} else if (type === 'pokemon') {
|
|
index = thing.types[0].toLowerCase().indexOf(searchTerm);
|
|
|
|
if (index == 0) {
|
|
matchType = 'fullstart';
|
|
match.type = {0:{
|
|
start: 0,
|
|
end: searchTerm.length
|
|
}};
|
|
}
|
|
|
|
if (thing.types[1]) {
|
|
index = thing.types[1].toLowerCase().indexOf(searchTerm);
|
|
|
|
if (index == 0) {
|
|
matchType = 'fullstart';
|
|
match.type = {1:{
|
|
start: 0,
|
|
end: searchTerm.length
|
|
}};
|
|
}
|
|
}
|
|
|
|
index = thing.abilities['0'].toLowerCase().indexOf(searchTerm);
|
|
if (index == 0) {
|
|
matchType = 'fullstart';
|
|
match.ability = {0:{
|
|
start: 0,
|
|
end: searchTerm.length
|
|
}};
|
|
}
|
|
|
|
if (thing.abilities['1']) {
|
|
index = thing.abilities['1'].toLowerCase().indexOf(searchTerm);
|
|
if (index == 0) {
|
|
matchType = 'fullstart';
|
|
match.ability = {1:{
|
|
start: 0,
|
|
end: searchTerm.length
|
|
}};
|
|
}
|
|
}
|
|
|
|
if (thing.abilities['H']) {
|
|
index = thing.abilities['H'].toLowerCase().indexOf(searchTerm);
|
|
if (index == 0) {
|
|
matchType = 'fullstart';
|
|
match.ability = {H:{
|
|
start: 0,
|
|
end: searchTerm.length
|
|
}};
|
|
}
|
|
}
|
|
} else if (type === 'move') {
|
|
index = thing.type.toLowerCase().indexOf(searchTerm);
|
|
|
|
if (index == 0) {
|
|
matchType = 'fullstart';
|
|
match.type = {
|
|
start: 0,
|
|
end: searchTerm.length
|
|
};
|
|
}
|
|
|
|
index = thing.category.toLowerCase().indexOf(searchTerm);
|
|
|
|
if (index == 0) {
|
|
matchType = 'fullstart';
|
|
match.category = {
|
|
start: 0,
|
|
end: searchTerm.length
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
if (self.illegalBuckets[bucket]) {
|
|
if (matchType === 'start' || matchType === 'contains' || matchType === 'fullstart') {
|
|
matchType = 'illegal' + matchType;
|
|
}
|
|
}
|
|
|
|
if (!chartData[matchType][bucket]) chartData[matchType][bucket] = [];
|
|
chartData[matchType][bucket].push(match);
|
|
}
|
|
|
|
// construct a chart from the filled buckets!
|
|
|
|
var text = '<ul class="utilichart">';
|
|
|
|
var firstMatch = true;
|
|
var noNameMatch = false;
|
|
|
|
var matchTypes = ['exact', 'start', 'contains', 'illegalstart', 'illegalcontains'];
|
|
for (var mtIndex = 0; mtIndex < 5; mtIndex++) {
|
|
var matchType = matchTypes[mtIndex];
|
|
for (var i in chartData[matchType]) {
|
|
if (!chartData[matchType][i]) continue;
|
|
for (var j = 0; j < chartData[matchType][i].length; j++) {
|
|
var match = chartData[matchType][i][j];
|
|
if (firstMatch) {
|
|
text += '<li class="resultheader"><h3>Matches</h3></li>';
|
|
}
|
|
text += self.row(match.thing, '', match, firstMatch, null, gen);
|
|
firstMatch = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (searchTerm && firstMatch) {
|
|
text += '<li class="resultheader"><h3>Matches</h3></li><li><em>No matches</em></li>';
|
|
firstMatch = false;
|
|
noNameMatch = true;
|
|
}
|
|
|
|
firstMatch = true;
|
|
|
|
for (var i in chartData.fullstart) {
|
|
if (!chartData.fullstart[i]) continue;
|
|
for (var j = 0; j < chartData.fullstart[i].length; j++) {
|
|
var match = chartData.fullstart[i][j];
|
|
if (firstMatch) {
|
|
text += '<li class="resultheader"><h3>Details Matches</h3></li>';
|
|
}
|
|
text += self.row(match.thing, '', match, firstMatch && noNameMatch, null, gen);
|
|
firstMatch = false;
|
|
}
|
|
}
|
|
for (var i in chartData.illegalfullstart) {
|
|
if (!chartData.illegalfullstart[i]) continue;
|
|
for (var j = 0; j < chartData.illegalfullstart[i].length; j++) {
|
|
var match = chartData.illegalfullstart[i][j];
|
|
if (firstMatch) {
|
|
text += '<li class="resultheader"><h3>Details Matches</h3></li>';
|
|
}
|
|
text += self.row(match.thing, '', match, firstMatch && noNameMatch, null, gen);
|
|
firstMatch = false;
|
|
}
|
|
}
|
|
|
|
if (!searchTerm || init) {
|
|
for (var i = 0; i < buckets.length; i++) {
|
|
var ib = buckets[i];
|
|
if (!chartData.other[ib]) continue;
|
|
firstMatch = true;
|
|
for (var j = 0; j < chartData.other[ib].length; j++) {
|
|
var match = chartData.other[ib][j];
|
|
if (firstMatch) {
|
|
text += '<li class="resultheader"><h3>' + buckets[i] + '</h3></li>';
|
|
firstMatch = false;
|
|
}
|
|
text += self.row(match.thing, '', match, null, null, gen);
|
|
}
|
|
}
|
|
}
|
|
|
|
text += '</ul>';
|
|
return text;
|
|
};
|
|
|
|
this.defaultArrange = function (thing) {
|
|
if (!thing) {
|
|
// we want the list of buckets
|
|
return ['All'];
|
|
}
|
|
return 'All';
|
|
};
|
|
this.defaultSort = function (a, b) {
|
|
/* if (!a.num) a.num = 1000;
|
|
if (!b.num) b.num = 1000;
|
|
if (a.num != b.num)
|
|
{
|
|
return a.num - b.num;
|
|
} */
|
|
if (a.name === b.name) return 0;
|
|
return (a.name > b.name ? 1 : -1);
|
|
};
|
|
}
|
|
|
|
var Chart = new BattleChart();
|