From 7fa72c1d2040941a05f7d7d6b5e8566ea56af382 Mon Sep 17 00:00:00 2001 From: Guangcong Luo Date: Mon, 21 Dec 2015 21:58:32 -0600 Subject: [PATCH] Convert teambuilder from utilichart.js to search.js The teambuilder is now running on search.js! Not all new features I'd like to be in are in, but we've now reached feature parity, and most of the bugs I found during testing have been fixed. New in search.js is on-demand DOM loading, which basically means much faster performance because instead of trying to load every single row of e.g. the pokemon list at once, we just load the part that's visible, and load the rest only when you scroll it into view. Also new in search.js is a dexsearch-like feature, replacing the old details-search system. The new filter system is simpler and more powerful and has the same API as /dexsearch, although not all the more advanced dexsearch features are supported. On-demand DOM loading makes teambuilder loading pretty much completely instantaneous. There are other small differences in how selection of pokemon/items/abilities/moves works, but it should overall make more sense. --- .eslintrc.js | 4 +- githooks/build-indexes | 48 ++++++ index.template.html | 4 +- js/battledata.js | 8 +- js/client-teambuilder.js | 283 +++++++++++++++------------------- js/search.js | 323 +++++++++++++++++++++++++++++---------- style/client.css | 7 +- style/utilichart.css | 37 +++++ testclient.html | 4 +- 9 files changed, 465 insertions(+), 253 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 2f83eb384..9c820e10c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,10 +19,10 @@ module.exports = { "BattleAbilities": false, "BattleAliases": false, "BattleBackdrops": false, "BattleBackdropsFour": false, "BattleBackdropsThree": false, "BattleEffects": false, "BattleFormats": false, "BattleFormatsData": false, "BattleLearnsets": false, "BattleItems": false, "BattleMoveAnims": false, "BattleMovedex": false, "BattleNatures": false, "BattleOtherAnims": false, "BattlePokedex": false,"BattlePokemonSprites": false, "BattlePokemonSpritesBW": false, "BattleSearchCountIndex": false, "BattleSearchIndex": false, - "BattleSearchIndexOffset": false, "BattleSearchIndexType": false, "BattleStatIDs": false, "BattleStatNames": false, "BattleStats": false, "BattleStatusAnims": false, "BattleStatuses": false, + "BattleSearchIndexOffset": false, "BattleSearchIndexType": false, "BattleStatIDs": false, "BattleStatNames": false, "BattleStats": false, "BattleStatusAnims": false, "BattleStatuses": false, "BattleTeambuilderTable": false, // Generic global variables - "Config": false, "Chart": false, "soundManager": false, "Storage": false, "Tools": false, + "Config": false, "BattleSearch": false, "soundManager": false, "Storage": false, "Tools": false, "app": false, "toId": false, "toRoomid": false, "toUserid": false, "toName": false, "hashColor": false, "MD5": false, "ChatHistory": false, "Topbar": false, "UserList": false, diff --git a/githooks/build-indexes b/githooks/build-indexes index c90318ced..1f4bf185c 100755 --- a/githooks/build-indexes +++ b/githooks/build-indexes @@ -103,3 +103,51 @@ const fs = require("fs"); } console.log("DONE"); + +/********************************************************* + * Build teambuilder-tables.js + *********************************************************/ + +process.stdout.write("Building `data/teambuilder-tables.js`... "); + +{ + const BattleTeambuilderTable = {}; + + let buf = '// automatically built with githooks/build-indexes\n\n'; + + const pokemon = Object.keys(Tools.data.Pokedex); + pokemon.sort(); + const tierTable = {}; + for (const id of pokemon) { + const tier = Tools.getTemplate(id).tier; + + if (!tierTable[tier]) tierTable[tier] = []; + tierTable[tier].push(id); + } + + const tiers = BattleTeambuilderTable.tiers = []; + const formatSlices = BattleTeambuilderTable.formatSlices = {}; + + const tierOrder = ["Uber", "OU", "BL", "(OU)", "UU", "BL2", "RU", "BL3", "NU", "BL4", "PU", "NFE", "LC Uber", "LC"]; + + for (const tier of tierOrder) { + if (tier === "OU" || tier === "Uber" || tier === "UU" || tier === "RU" || tier === "NU" || tier === "PU" || tier === "LC") { + formatSlices[tier] = tiers.length; + } + if (!tierTable[tier]) continue; + if (tier.charAt(0) === '(') { + tiers.push(['header', tier.slice(1, -1) + " by technicality"]); + } else if (tier === "NFE") { + tiers.push(['header', "NFEs not in a higher tier"]); + } else { + tiers.push(['header', tier]); + } + tiers.push(...tierTable[tier]); + } + + buf += 'exports.BattleTeambuilderTable = ' + JSON.stringify(BattleTeambuilderTable) + ';\n\n'; + + fs.writeFileSync('data/teambuilder-tables.js', buf); +} + +console.log("DONE"); diff --git a/index.template.html b/index.template.html index 5667b9096..da9269fc2 100644 --- a/index.template.html +++ b/index.template.html @@ -164,7 +164,9 @@ ga('send', 'pageview'); - + + + diff --git a/js/battledata.js b/js/battledata.js index 20824a55f..abdb23fe8 100644 --- a/js/battledata.js +++ b/js/battledata.js @@ -409,16 +409,16 @@ var Tools = { return '
' + timestamp + '' + clickableName + ': ' + Tools.parseMessage(target) + '
'; case 'data-pokemon': if (!window.Chart) return ''; - return '
'; + return '
'; case 'data-item': if (!window.Chart) return ''; - return '
'; + return '
'; case 'data-ability': if (!window.Chart) return ''; - return '
'; + return '
'; case 'data-move': if (!window.Chart) return ''; - return '
'; + return '
'; case 'text': return '
' + Tools.escapeHTML(target) + '
'; case 'error': diff --git a/js/client-teambuilder.js b/js/client-teambuilder.js index 33477b439..20732bb2f 100644 --- a/js/client-teambuilder.js +++ b/js/client-teambuilder.js @@ -17,7 +17,6 @@ this.update(); }, focus: function () { - this.buildMovelists(); if (this.curTeam) { this.curTeam.iconCache = '!'; this.curTeam.gen = this.getGen(this.curTeam.format); @@ -49,7 +48,7 @@ 'keydown .chartinput': 'chartKeydown', 'keyup .chartinput': 'chartKeyup', 'focus .chartinput': 'chartFocus', - 'change .chartinput': 'chartChange', + 'blur .chartinput': 'chartChange', // drag/drop 'click .team': 'edit', @@ -1384,6 +1383,15 @@ this.$el.html('
' + buf + '
'); if ($(window).width() < 640) this.show(); this.$chart = this.$('.teambuilder-results'); + this.search = new BattleSearch(this.$chart, this.$chart); + var self = this; + // fun fact: Backbone DOM events don't support scroll... + // I guess scroll doesn't bubble like other events + this.$chart.on('scroll', function () { + if (self.curChartType in self.searchChartTypes) { + self.search.updateScroll(); + } + }); }, updateSetTop: function () { this.$('.teambar').html(this.renderTeambar()); @@ -1493,46 +1501,57 @@ }, curChartType: '', curChartName: '', - updateChart: function () { + searchChartTypes: { + pokemon: 'pokemon', + ability: 'abilities', + move: 'moves', + item: 'items' + }, + updateChart: function (pokemonChanged, wasIncomplete) { var type = this.curChartType; app.clearGlobalListeners(); if (type === 'stats') { + this.search.qType = null; + this.search.qName = null; this.updateStatForm(); return; } if (type === 'details') { + this.search.qType = null; + this.search.qName = null; this.updateDetailsForm(); return; } - // cache movelist ref - var speciesid = toId(this.curSet.species); - var g6 = (this.curTeam.format && this.curTeam.format === 'vgc2016'); - this.applyMovelist(g6, speciesid); + var $inputEl = this.$('input[name=' + this.curChartName + ']'); + var q = $inputEl.val(); - this.$chart.html('Loading ' + this.curChartType + '...'); - var self = this; - if (this.updateChartTimeout) clearTimeout(this.updateChartTimeout); - this.updateChartTimeout = setTimeout(function () { - self.updateChartTimeout = null; - if (self.curChartType === 'stats' || self.curChartType === 'details' || !self.curChartName) return; - self.$chart.html(Chart.chart(self.$('input[name=' + self.curChartName + ']').val(), self.curChartType, true, _.bind(self.arrangeCallback[self.curChartType], self), null, self.curTeam.gen)); - }, 10); - }, - updateChartTimeout: null, - updateChartDelayed: function () { - // cache movelist ref - var speciesid = toId(this.curSet.species); - var g6 = (this.curTeam.format && this.curTeam.format === 'vgc2016'); - this.applyMovelist(g6, speciesid); - - var self = this; - if (this.updateChartTimeout) clearTimeout(this.updateChartTimeout); - this.updateChartTimeout = setTimeout(function () { - self.updateChartTimeout = null; - if (self.curChartType === 'stats' || self.curChartType === 'details') return; - self.$chart.html(Chart.chart(self.$('input[name=' + self.curChartName + ']').val(), self.curChartType, false, _.bind(self.arrangeCallback[self.curChartType], self), null, self.curTeam.gen)); - }, 200); + if (pokemonChanged || this.search.qName !== this.curChartName) { + var cur = {}; + if (type === 'move') { + cur[toId(this.$('input[name=move1]').val())] = 1; + cur[toId(this.$('input[name=move2]').val())] = 1; + cur[toId(this.$('input[name=move3]').val())] = 1; + cur[toId(this.$('input[name=move4]').val())] = 1; + } else { + cur[toId(q)] = 1; + } + if (type !== this.search.qType) { + this.$chart.scrollTop(0); + } + this.search.$inputEl = $inputEl; + this.search.setType(type, this.curTeam.format, this.curSet, cur); + this.qInitial = q; + this.search.qName = this.curChartName; + if (wasIncomplete) { + this.search.find(q); + if (this.search.q) this.$chart.find('a').first().addClass('hover'); + } + } else if (q !== this.qInitial) { + this.qInitial = undefined; + this.search.find(q); + if (this.search.q) this.$chart.find('a').first().addClass('hover'); + } }, selectPokemon: function (i) { i = +i; @@ -1540,14 +1559,17 @@ if (set) { this.curSet = set; this.curSetLoc = i; - var name = this.curChartName || 'details'; - if (name === 'details' || name === 'stats') { + if (!this.curChartName) { + this.curChartName = 'details'; + this.curChartType = 'details'; + } + if (this.curChartType in this.searchChartTypes) { this.update(); - this.updateChart(); + this.updateChart(true); + this.$('input[name=' + this.curChartName + ']').select(); } else { - this.curChartName = ''; this.update(); - this.$('input[name=' + name + ']').select(); + this.updateChart(true); } } }, @@ -1555,13 +1577,13 @@ if (!this.curSet) this.selectPokemon($(button).closest('li').val()); this.curChartName = 'stats'; this.curChartType = 'stats'; - this.updateStatForm(); + this.updateChart(); }, details: function (i, button) { if (!this.curSet) this.selectPokemon($(button).closest('li').val()); this.curChartName = 'details'; this.curChartType = 'details'; - this.updateDetailsForm(); + this.updateChart(); }, /********************************************************* @@ -2007,68 +2029,6 @@ * Set charts *********************************************************/ - arrangeCallback: { - pokemon: function (pokemon) { - if (!pokemon) { - if (this.curTeam) { - if (this.curTeam.format === 'ubers' || this.curTeam.format === 'anythinggoes') return ['Uber', 'OU', 'BL', 'OU only when combining mega and non-mega usage', 'UU', 'BL2', 'UU only when combining mega and non-mega usage', 'RU', 'BL3', 'RU only when combining mega and non-mega usage', 'NU', 'BL4', 'NU only when combining mega and non-mega usage', 'PU', 'NFE', 'LC Uber', 'LC']; - if (this.curTeam.format === 'ou') return ['OU', 'BL', 'OU only when combining mega and non-mega usage', 'UU', 'BL2', 'UU only when combining mega and non-mega usage', 'RU', 'BL3', 'RU only when combining mega and non-mega usage', 'NU', 'BL4', 'NU only when combining mega and non-mega usage', 'PU', 'NFE', 'LC Uber', 'LC']; - if (this.curTeam.format === 'cap') return ['CAP', 'OU', 'BL', 'OU only when combining mega and non-mega usage', 'UU', 'BL2', 'UU only when combining mega and non-mega usage', 'RU', 'BL3', 'RU only when combining mega and non-mega usage', 'NU', 'BL4', 'NU only when combining mega and non-mega usage', 'PU', 'NFE', 'LC Uber', 'LC']; - if (this.curTeam.format === 'uu') return ['UU', 'BL2', 'UU only when combining mega and non-mega usage', 'RU', 'BL3', 'RU only when combining mega and non-mega usage', 'NU', 'BL4', 'NU only when combining mega and non-mega usage', 'PU', 'NFE', 'LC Uber', 'LC']; - if (this.curTeam.format === 'ru') return ['RU', 'BL3', 'RU only when combining mega and non-mega usage', 'NU', 'BL4', 'NU only when combining mega and non-mega usage', 'PU', 'NFE', 'LC Uber', 'LC']; - if (this.curTeam.format === 'nu') return ['NU', 'BL4', 'NU only when combining mega and non-mega usage', 'PU', 'NFE', 'LC Uber', 'LC']; - if (this.curTeam.format === 'pu') return ['PU', 'NFE', 'LC Uber', 'LC']; - if (this.curTeam.format === 'lc') return ['LC']; - } - return ['OU', 'Uber', 'BL', 'OU only when combining mega and non-mega usage', 'UU', 'BL2', 'UU only when combining mega and non-mega usage', 'RU', 'BL3', 'RU only when combining mega and non-mega usage', 'NU', 'BL4', 'NU only when combining mega and non-mega usage', 'PU', 'NFE', 'LC Uber', 'LC', 'Unreleased', 'CAP']; - } - var speciesid = toId(pokemon.species); - var tierData = exports.BattleFormatsData[speciesid]; - if (!tierData) return 'Illegal'; - var displayTier = {"(OU)": "OU only when combining mega and non-mega usage", "(UU)": "UU only when combining mega and non-mega usage", "(RU)": "RU only when combining mega and non-mega usage", "(NU)": "NU only when combining mega and non-mega usage"}; - if (tierData.tier in displayTier) { - return displayTier[tierData.tier]; - } - return tierData.tier; - }, - item: function (item) { - if (!item) return ['Items']; - return 'Items'; - }, - ability: function (ability) { - if (!this.curSet) return; - var template = Tools.getTemplate(this.curSet.species); - var isMega = false; - if (template.forme.substr(0, 4) === 'Mega' && this.curTeam.format !== 'balancedhackmons') { - if (!ability) return ['Pre-Mega Abilities', 'Pre-Mega Hidden Ability']; - isMega = true; - template = Tools.getTemplate(template.baseSpecies); - } - if (!ability) return ['Abilities', 'Hidden Ability']; - if (!template.abilities) return 'Abilities'; - if (ability.name === template.abilities['0']) return isMega ? 'Pre-Mega Abilities' : 'Abilities'; - if (ability.name === template.abilities['1']) return isMega ? 'Pre-Mega Abilities' : 'Abilities'; - if (ability.name === template.abilities['H']) return isMega ? 'Pre-Mega Hidden Ability' : 'Hidden Ability'; - if (!this.curTeam || this.curTeam.format !== 'balancedhackmons') return 'Illegal'; - }, - move: function (move) { - if (!this.curSet) return; - if (!move) return ['Usable Moves', 'Moves', 'Usable Sketch Moves', 'Sketch Moves']; - var movelist = this.movelist; - if (!movelist) return 'Illegal'; - if (!movelist[move.id]) { - if (movelist['sketch'] && move.id !== 'chatter' && move.id !== 'struggle') { - if (move.isViable) return 'Usable Sketch Moves'; - return 'Sketch Moves'; - } - if (!this.curTeam || this.curTeam.format !== 'balancedhackmons') return 'Illegal'; - } - var speciesid = toId(this.curSet.species); - if (move.isViable) return 'Usable Moves'; - return 'Moves'; - } - }, - chartTypes: { pokemon: 'pokemon', item: 'item', @@ -2081,30 +2041,61 @@ details: 'details' }, chartClick: function (e) { - this.chartSet($(e.currentTarget).data('name'), true); + if (this.search.addFilter(e.currentTarget)) { + this.$('input[name=' + this.curChartName + ']').val('').select(); + this.search.find(''); + return; + } + var val = $(e.currentTarget).data('entry').split(':')[1]; + this.chartSet(val, true); }, chartKeydown: function (e) { var modifier = (e.shiftKey || e.ctrlKey || e.altKey || e.metaKey || e.cmdKey); if (e.keyCode === 13 || (e.keyCode === 9 && !modifier)) { - if (!this.arrangeCallback[this.curChartType]) return; + if (!(this.curChartType in this.searchChartTypes)) return; e.stopPropagation(); e.preventDefault(); - var name = e.currentTarget.name; - this.$chart.html(Chart.chart(e.currentTarget.value, this.curChartType, false, _.bind(this.arrangeCallback[this.curChartType], this), null, this.curTeam.gen)); - var val = Chart.firstResult; + this.search.find(e.currentTarget.value); + if (!this.search.q) return; + var $firstResult = this.$chart.find('a').first(); + if (this.search.addFilter($firstResult[0])) { + $(e.currentTarget).val('').select(); + this.search.find(''); + return; + } + var val = $firstResult.data('entry').split(':')[1]; this.chartSet(val, true); return; + } else if (e.keyCode === 27 || e.keyCode === 8) { // esc, backspace + if (!e.currentTarget.value && this.search.removeFilter()) { + this.search.find(''); + return; + } + } else if (e.keyCode === 188) { + var $firstResult = this.$chart.find('a').first(); + if (!this.search.q) return; + if (this.search.addFilter($firstResult[0])) { + e.preventDefault(); + e.stopPropagation(); + $(e.currentTarget).val('').select(); + this.search.find(''); + return; + } } }, chartKeyup: function () { - this.updateChartDelayed(); + this.updateChart(); }, chartFocus: function (e) { var $target = $(e.currentTarget); var name = e.currentTarget.name; var type = this.chartTypes[name]; - $target.removeClass('incomplete'); + var wasIncomplete = false; + if ($target.hasClass('incomplete')) { + wasIncomplete = true; + $target.removeClass('incomplete'); + } if (this.curChartName === name) return; @@ -2123,21 +2114,32 @@ this.curChartName = name; this.curChartType = type; - this.updateChart(); + this.updateChart(false, wasIncomplete); }, chartChange: function (e) { var name = e.currentTarget.name; - var type = this.chartTypes[name]; - var arrange = null; - if (this.arrangeCallback[this.curChartType]) { - arrange = _.bind(this.arrangeCallback[this.curChartType], this); - } - this.$chart.html(Chart.chart(e.currentTarget.value, type, false, arrange, null, this.curTeam.gen)); - var val = Chart.firstResult; + if (this.curChartName !== name) return; var id = toId(e.currentTarget.value); - if (toId(val) !== id) { - $(e.currentTarget).addClass('incomplete'); - return; + var val = ''; + switch (name) { + case 'pokemon': + val = (id in BattlePokedex ? BattlePokedex[id].name : ''); + break; + case 'ability': + val = (id in BattleAbilities ? BattleAbilities[id].name : ''); + break; + case 'item': + val = (id in BattleItems ? BattleItems[id].name : ''); + break; + case 'move1': case 'move2': case 'move3': case 'move4': + val = (id in BattleMovedex ? BattleMovedex[id].name : ''); + break; + } + if (!val) { + if (name === 'pokemon' || name === 'ability' || id) { + $(e.currentTarget).addClass('incomplete'); + return; + } } this.chartSet(val); }, @@ -2167,7 +2169,7 @@ if (!this.curSet.moves[0]) this.curSet.moves[0] = ''; this.curSet.moves[1] = val; this.chooseMove(val); - this.$('input[name=move3]').select(); + if (selectNext) this.$('input[name=move3]').select(); break; case 'move3': if (!this.curSet.moves[0]) this.curSet.moves[0] = ''; @@ -2216,7 +2218,10 @@ var set = this.curSet; var template = Tools.getTemplate(val); var newPokemon = !set.species; - if (!template.exists || set.species === template.species) return; + if (!template.exists || set.species === template.species) { + if (selectNext) this.$('input[name=item]').select(); + return; + } set.name = template.species; set.species = val; @@ -2728,46 +2733,6 @@ // initialization - buildMovelists: function () { - if (Tools.movelists) return; - if (!window.BattlePokedex) return; - Tools.movelists = {}; - Tools.g6movelists = {}; - for (var pokemon in window.BattlePokedex) { - var template = Tools.getTemplate(pokemon); - var moves = {}; - var g6moves = {}; - var alreadyChecked = {}; - do { - alreadyChecked[template.speciesid] = true; - if (template.learnset) { - for (var l in template.learnset) { - moves[l] = true; - if (template.learnset[l].length) g6moves[l] = true; - } - } - if (template.speciesid === 'shaymin') { - template = Tools.getTemplate('shayminsky'); - } else if (toId(template.baseSpecies) !== toId(template.species) && toId(template.baseSpecies) !== 'pikachu' && toId(template.baseSpecies) !== 'wormadam' && toId(template.baseSpecies) !== 'kyurem') { - template = Tools.getTemplate(template.baseSpecies); - } else { - template = Tools.getTemplate(template.prevo); - } - } while (template && template.species && !alreadyChecked[template.speciesid]); - Tools.movelists[pokemon] = moves; - Tools.g6movelists[pokemon] = g6moves; - } - }, - applyMovelist: function (g6only, speciesid) { - this.buildMovelists(); - if (!Tools.movelists) { - this.movelist = false; - } else if (g6only) { - this.movelist = Tools.g6movelists[speciesid]; - } else { - this.movelist = Tools.movelists[speciesid]; - } - }, getGen: function (format) { format = '' + format; if (format.substr(0, 3) !== 'gen') return 6; diff --git a/js/search.js b/js/search.js index 3cf7b0435..2422c68ae 100644 --- a/js/search.js +++ b/js/search.js @@ -21,12 +21,16 @@ this.q = undefined; // uninitialized this.qType = ''; this.defaultResultSet = null; + this.legalityFilter = null; + this.legalityLabel = "Illegal"; this.exactMatch = false; this.resultSet = null; this.filters = null; this.renderedIndex = 0; this.renderingDone = true; + this.cur = {}; + this.$inputEl = null; var self = this; this.$el.on('mouseover', '.more-autoscroll', function () { @@ -40,6 +44,21 @@ self.updateScroll(true); } }); + this.$el.on('click', '.filter', function (e) { + e.preventDefault(); + if (!self.filters) return; + // delete filter + for (var i = 0; i < self.filters.length; i++) { + if (e.currentTarget.value === self.filters[i].join(':')) { + self.filters.splice(i, 1); + if (!self.filters.length) self.filters = null; + self.q = undefined; + self.find(''); + break; + } + } + if (self.$inputEl) self.$inputEl.focus(); + }); } Search.prototype.$ = function (query) { @@ -78,10 +97,16 @@ this.resultSet = null; var qType = this.qType; if (!query) { + if (!this.filters && this.defaultResultSet) { + this.renderedIndex = 0; + this.renderingDone = false; + this.updateScroll(); + return true; + } if (qType === 'pokemon') { this.allPokemon(); return true; - } else if (qType === 'moves') { + } else if (qType === 'move') { this.allMoves(); return true; } @@ -93,9 +118,9 @@ var qTypeIndex = -1; switch (qType) { case 'pokemon': qTypeIndex = 0; break; - case 'moves': qTypeIndex = 2; break; - case 'items': qTypeIndex = 3; break; - case 'abilities': qTypeIndex = 4; break; + case 'move': qTypeIndex = 2; break; + case 'item': qTypeIndex = 3; break; + case 'ability': qTypeIndex = 4; break; } var qFilterType = ''; @@ -107,6 +132,10 @@ } var i = Search.getClosest(query); + if (query in BattleAliases) { + query = toId(BattleAliases[query]); + i = Search.getClosest(query); + } if (!BattleSearchIndex[i]) i--; if (BattleSearchIndex[i - 1] && BattleSearchIndex[i - 1] === query) i--; this.exactMatch = (query === BattleSearchIndex[i]); @@ -117,34 +146,37 @@ var nearMatch = (BattleSearchIndex[i].substr(0, query.length) !== query); this.renderingDone = false; if (nearMatch && i) i--; - for (var j = 0; j < 15; j++) { - var id = BattleSearchIndex[i + j]; - var type = BattleSearchIndexType[i + j]; + for (var count = 0; count < 15; i++) { + var id = BattleSearchIndex[i]; + var type = BattleSearchIndexType[i]; var matchLength = query.length; if (!id) break; if (id.substr(0, query.length) !== query) { this.renderingDone = true; - if (!(nearMatch && j <= 1)) break; + if (!(nearMatch && count <= 1)) break; matchLength = 0; } else { - matchLength += (BattleSearchIndexOffset[i + j][matchLength - 1] || '0').charCodeAt(0) - 48; + matchLength += (BattleSearchIndexOffset[i][matchLength - 1] || '0').charCodeAt(0) - 48; } var typeIndex = typeTable[type]; if (qType === 'pokemon' && (typeIndex === 3 || typeIndex > 5)) continue; - if (qType === 'moves' && (typeIndex !== 6 && typeIndex > 2)) continue; + if (qType === 'move' && (typeIndex !== 6 && typeIndex > 2)) continue; + if ((qType === 'ability' || qType === 'item') && typeIndex !== qTypeIndex) continue; if (qFilterType === 'type' && typeIndex !== 1) continue; var errorMessage = ''; if (qType && qTypeIndex !== typeIndex) errorMessage = 'Filter'; + else if (this.legalityFilter && !(id in this.legalityFilter)) errorMessage = '' + this.legalityLabel + ''; - if (j === 0 && this.exactMatch) { + if (count === 0 && this.exactMatch) { topbufIndex = typeIndex; } if (!bufs[typeIndex]) bufs[typeIndex] = '
  • ' + typeName[type] + '

  • '; bufs[typeIndex] += Search.renderRow(id, type, 0, matchLength, errorMessage); + count++; } var topbuf = ''; @@ -162,32 +194,29 @@ if (nearMatch && !qType) topbuf = '
  • No exact match found. The closest matches alphabetically are:
  • ' + topbuf; - this.el.innerHTML = '' + (this.renderingDone ? '' : ''); + this.el.innerHTML = '' + (this.renderingDone || qType ? '' : ''); return true; }; - Search.prototype.addFilter = function (name, result) { + Search.prototype.addFilter = function (node) { + if (!node.dataset.entry) return; + var entry = node.dataset.entry.split(':'); if (this.qType === 'pokemon') { - result = result.split('/'); - if (result[0] !== 'types' && result[0] !== 'moves' && result[0] !== 'abilities' && result[0] !== 'egggroups') return; - if (result[0] === 'types') result[1] = name; - if (result[0] === 'abilities') result[1] = name; - if (result[0] === 'egggroups') result[1] = name; + if (entry[0] !== 'type' && entry[0] !== 'move' && entry[0] !== 'ability' && entry[0] !== 'egggroup') return; + if (entry[0] === 'move') entry[1] = toId(entry[1]); if (!this.filters) this.filters = []; this.q = undefined; for (var i = 0; i < this.filters.length; i++) { - if (this.filters[i][0] === result[0] && this.filters[i][1] === result[1]) { + if (this.filters[i][0] === entry[0] && this.filters[i][1] === entry[1]) { return true; } } - this.filters.push(result); + this.filters.push(entry); return true; - } else if (this.qType === 'moves') { - result = result.split('/'); - if (result[0] !== 'types' && result[0] !== 'categories' && result[0] !== 'pokemon') return; - if (result[0] === 'types') result[1] = name; - if (result[0] === 'categories') result[1] = name; + } else if (this.qType === 'move') { + if (entry[0] !== 'type' && entry[0] !== 'category' && entry[0] !== 'pokemon') return; + if (entry[0] === 'pokemon') entry[1] = toId(entry[1]); if (!this.filters) this.filters = []; - this.filters.push(result); + this.filters.push(entry); this.q = undefined; return true; } @@ -300,34 +329,35 @@ this.updateScroll(); }; Search.prototype.getFilterText = function (q) { - var buf = 'Filters: '; + var buf = '

    Filters: '; for (var i = 0; i < this.filters.length; i++) { - if (i) buf += ', '; var text = this.filters[i][1]; - if (this.filters[i][0] === 'moves') text = Tools.getMove(text).name; + if (this.filters[i][0] === 'move') text = Tools.getMove(text).name; if (this.filters[i][0] === 'pokemon') text = Tools.getTemplate(text).name; - buf += '' + text + ''; + buf += ' '; } - if (!q) buf += ' (backspace = delete filter)'; - return buf; + if (!q) buf += '(backspace = delete filter)'; + return buf + '

    '; }; Search.prototype.filteredPokemon = function () { var resultSet = [['html', this.getFilterText(), 0]]; resultSet.push(['header', "Filtered results", 0]); + var illegalResultSet = []; var filters = this.filters; for (var id in BattlePokedex) { var template = BattlePokedex[id]; + if (template.exists === false) continue; for (var i = 0; i < filters.length; i++) { - if (filters[i][0] === 'types') { + if (filters[i][0] === 'type') { var type = filters[i][1]; if (template.types[0] !== type && template.types[1] !== type) break; - } else if (filters[i][0] === 'egggroups') { + } else if (filters[i][0] === 'egggroup') { var egggroup = filters[i][1]; if (template.eggGroups[0] !== egggroup && template.eggGroups[1] !== egggroup) break; - } else if (filters[i][0] === 'abilities') { + } else if (filters[i][0] === 'ability') { var ability = filters[i][1]; if (template.abilities['0'] !== ability && template.abilities['1'] !== ability && template.abilities['H'] !== ability) break; - } else if (filters[i][0] === 'moves') { + } else if (filters[i][0] === 'move') { var learned = false; var learnsetid = id; while (true) { @@ -345,9 +375,13 @@ } } if (i < filters.length) continue; - resultSet.push(['pokemon', id, 0]); + if (this.legalityFilter && !(id in this.legalityFilter)) { + illegalResultSet.push(['pokemon', id, -1]); + } else { + resultSet.push(['pokemon', id, 0]); + } } - this.resultSet = resultSet; + this.resultSet = resultSet.concat(illegalResultSet); this.renderedIndex = 0; this.renderingDone = false; this.updateScroll(); @@ -355,13 +389,15 @@ Search.prototype.filteredMoves = function () { var resultSet = [['html', this.getFilterText(), 0]]; resultSet.push(['header', "Filtered results", 0]); + var illegalResultSet = []; var filters = this.filters; for (var id in BattleMovedex) { var move = BattleMovedex[id]; + if (move.exists === false) continue; for (var i = 0; i < filters.length; i++) { - if (filters[i][0] === 'types') { + if (filters[i][0] === 'type') { if (move.type !== filters[i][1]) break; - } else if (filters[i][0] === 'categories') { + } else if (filters[i][0] === 'category') { if (move.category !== filters[i][1]) break; } else if (filters[i][0] === 'pokemon') { var learned = false; @@ -381,9 +417,13 @@ } } if (i < filters.length) continue; - resultSet.push(['move', id, 0]); + if (this.legalityFilter && !(id in this.legalityFilter)) { + illegalResultSet.push(['move', id, -1]); + } else { + resultSet.push(['move', id, 0]); + } } - this.resultSet = resultSet; + this.resultSet = resultSet.concat(illegalResultSet); this.renderedIndex = 0; this.renderingDone = false; this.updateScroll(); @@ -397,29 +437,142 @@ if (forceAdd && finalIndex < i + 20) finalIndex = i + 20; if (finalIndex <= i) return; + var resultSet = this.resultSet || this.defaultResultSet; var buf = ''; while (i < finalIndex) { - if (!this.resultSet[i]) { + if (!resultSet[i]) { this.renderingDone = true; break; } - var row = this.resultSet[i]; + var row = resultSet[i]; - buf += Search.renderRow(row[1], row[0], 0, row[2]); + var errorMessage = ''; + if (row[2] < 0) { + errorMessage = '' + this.legalityLabel + ''; + row[2] = 0; + } + + buf += Search.renderRow(row[1], row[0], 0, row[2], errorMessage, row[1] in this.cur ? ' class="cur"' : ''); i++; } if (!this.renderedIndex) { - this.el.innerHTML = ''; - // (this.renderingDone ? '' : ''); + this.el.innerHTML = ''; } else { $(this.el.firstChild).append(buf); - // if (this.renderingDone) { - // this.$el.find('li.more').parent().remove(); - // } } this.renderedIndex = i; }; + Search.prototype.setType = function (qType, format, set, cur) { + if (!format) format = ''; + if (this.qType !== qType) this.filters = null; + this.qType = qType; + this.q = undefined; + this.cur = cur || {}; + this.legalityFilter = {}; + this.legalityLabel = "Illegal"; + var gen = 6; + if (format.slice(0, 3) === 'gen') gen = (Number(format.charAt(3)) || 6); + var requirePentagon = (format === 'vgc2016'); + var template; + this.resultSet = null; + this.defaultResultSet = null; + + switch (qType) { + case 'pokemon': + if (!BattleTeambuilderTable.tierSet) { + BattleTeambuilderTable.tierSet = BattleTeambuilderTable.tiers.map(function (r) { + if (typeof r === 'string') return ['pokemon', r, 0]; + return [r[0], r[1], 0]; + }); + BattleTeambuilderTable.tiers = null; + } + var tierSet = BattleTeambuilderTable.tierSet; + if (format === 'ou') tierSet = tierSet.slice(BattleTeambuilderTable.formatSlices.OU); + if (format === 'uu') tierSet = tierSet.slice(BattleTeambuilderTable.formatSlices.UU); + if (format === 'ru') tierSet = tierSet.slice(BattleTeambuilderTable.formatSlices.RU); + if (format === 'nu') tierSet = tierSet.slice(BattleTeambuilderTable.formatSlices.NU); + if (format === 'lc') tierSet = tierSet.slice(BattleTeambuilderTable.formatSlices.LC); + this.defaultResultSet = tierSet; + this.legalityLabel = "Banned"; + break; + + case 'item': + var itemSet = [['header', "Items", 0]]; + for (var id in BattleItems) { + itemSet.push(['item', id, 0]); + } + this.defaultResultSet = itemSet; + this.legalityFilter = null; + break; + + case 'ability': + template = Tools.getTemplate(set.species); + var abilitySet = [['header', "Abilities", 0]]; + if (template.isMega) { + abilitySet.unshift(['html', '

    Will be ' + Tools.escapeHTML(template.abilities['0']) + ' after Mega Evolving.

    ']); + template = Tools.getTemplate(template.baseSpecies); + } + abilitySet.push(['ability', toId(template.abilities['0']), 0]); + if (template.abilities['1']) { + abilitySet.push(['ability', toId(template.abilities['1']), 0]); + } + if (template.abilities['H']) { + abilitySet.push(['header', "Hidden Ability", 0]); + abilitySet.push(['ability', toId(template.abilities['H']), 0]); + } + this.defaultResultSet = abilitySet; + break; + + case 'move': + template = Tools.getTemplate(set.species); + var moves = []; + while (true) { + var learnsetTemplate = BattleLearnsets[toId(template.baseSpecies)]; + if (learnsetTemplate) { + for (var l in learnsetTemplate.learnset) { + if (requirePentagon && !learnsetTemplate.learnset[l].length) continue; + if (moves.indexOf(l) >= 0) continue; + moves.push(l); + if (l === 'hiddenpower') { + moves.push('hiddenpowerbug', 'hiddenpowerdark', 'hiddenpowerdragon', 'hiddenpowerelectric', 'hiddenpowerfighting', 'hiddenpowerfire', 'hiddenpowerflying', 'hiddenpowerghost', 'hiddenpowergrass', 'hiddenpowerground', 'hiddenpowerice', 'hiddenpowerpoison', 'hiddenpowerpsychic', 'hiddenpowerrock', 'hiddenpowersteel', 'hiddenpowerwater'); + } + } + } + if (template.baseSpecies && !learnsetTemplate) template = BattlePokedex[toId(template.baseSpecies)]; + else if (template.prevo) template = BattlePokedex[template.prevo]; + else break; + } + moves.sort(); + + var usableMoves = []; + var uselessMoves = []; + for (var i = 0; i < moves.length; i++) { + var id = moves[i]; + if (BattleMovedex[id] && BattleMovedex[id].isViable) { + if (!usableMoves.length) usableMoves.push(['header', "Moves", 0]); + usableMoves.push(['move', id, 0]); + } else { + if (!uselessMoves.length) uselessMoves.push(['header', "Usually useless moves", 0]); + uselessMoves.push(['move', id, 0]); + } + } + this.defaultResultSet = usableMoves.concat(uselessMoves); + break; + } + + if (this.legalityFilter) { + for (var i = 0; i < this.defaultResultSet.length; i++) { + if (this.defaultResultSet[i][0] !== 'header') { + this.legalityFilter[this.defaultResultSet[i][1]] = 1; + } + } + } + + this.renderedIndex = 0; + this.renderingDone = false; + this.find(''); + }; Search.getClosest = function (query) { // binary search through the index! @@ -439,13 +592,13 @@ return left; }; - // - // Rendering functions - // - // These are all static! - // +/********************************************************* + * Rendering functions + *********************************************************/ - Search.renderRow = function (id, type, matchStart, matchLength, errorMessage) { + // These are all static! + + Search.renderRow = function (id, type, matchStart, matchLength, errorMessage, attrs) { // errorMessage = '' + errorMessage + ''; switch (type) { case 'html': @@ -454,16 +607,16 @@ return '
  • ' + id + '

  • '; case 'pokemon': var pokemon = BattlePokedex[id]; - return Search.renderPokemonRow(pokemon, matchStart, matchLength, errorMessage); + return Search.renderPokemonRow(pokemon, matchStart, matchLength, errorMessage, attrs); case 'move': var move = BattleMovedex[id]; - return Search.renderMoveRow(move, matchStart, matchLength, errorMessage); + return Search.renderMoveRow(move, matchStart, matchLength, errorMessage, attrs); case 'item': var item = BattleItems[id]; - return Search.renderItemRow(item, matchStart, matchLength, errorMessage); + return Search.renderItemRow(item, matchStart, matchLength, errorMessage, attrs); case 'ability': var ability = BattleAbilities[id]; - return Search.renderAbilityRow(ability, matchStart, matchLength, errorMessage); + return Search.renderAbilityRow(ability, matchStart, matchLength, errorMessage, attrs); case 'type': var type = {name: id[0].toUpperCase() + id.substr(1)}; return Search.renderTypeRow(type, matchStart, matchLength, errorMessage); @@ -487,10 +640,11 @@ } return 'Error: not found'; }; - Search.renderPokemonRow = function (pokemon, matchStart, matchLength, errorMessage) { - var attrs = ''; - if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'pokemon/' + toId(pokemon.species) + '" data-target="push"'; - var buf = '
  • '; + Search.renderPokemonRow = function (pokemon, matchStart, matchLength, errorMessage, attrs) { + if (!attrs) attrs = ''; + var id = toId(pokemon.species); + if (Search.urlRoot) attrs += ' href="' + Search.urlRoot + 'pokemon/' + id + '" data-target="push"'; + var buf = '
  • '; // number buf += '' + (pokemon.num >= 0 ? pokemon.num : 'CAP') + ' '; @@ -545,7 +699,7 @@ if (!ability) continue; if (i === '1') buf += '
    '; - if (i === 'H') ability = '' + pokemon.abilities[i] + ''; + if (i === 'H') ability = '' + pokemon.abilities[i] + ''; buf += ability; } if (!pokemon.abilities['H']) buf += ''; @@ -572,7 +726,7 @@ Search.renderTaggedPokemonRowInner = function (pokemon, tag, errorMessage) { var attrs = ''; if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'pokemon/' + toId(pokemon.species) + '" data-target="push"'; - var buf = ''; + var buf = ''; // tag buf += '' + tag + ' '; @@ -638,10 +792,11 @@ return buf; }; - Search.renderItemRow = function (item, matchStart, matchLength, errorMessage) { - var attrs = ''; - if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'items/' + toId(item.name) + '" data-target="push"'; - var buf = '
  • '; + Search.renderItemRow = function (item, matchStart, matchLength, errorMessage, attrs) { + if (!attrs) attrs = ''; + var id = toId(item.name); + if (Search.urlRoot) attrs += ' href="' + Search.urlRoot + 'items/' + id + '" data-target="push"'; + var buf = '
  • '; // icon buf += ''; @@ -668,10 +823,11 @@ return buf; }; - Search.renderAbilityRow = function (ability, matchStart, matchLength, errorMessage) { - var attrs = ''; - if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'abilities/' + toId(ability.name) + '" data-target="push"'; - var buf = '
  • '; + Search.renderAbilityRow = function (ability, matchStart, matchLength, errorMessage, attrs) { + if (!attrs) attrs = ''; + var id = toId(ability.name); + if (Search.urlRoot) attrs += ' href="' + Search.urlRoot + 'abilities/' + id + '" data-target="push"'; + var buf = '
  • '; // name var name = ability.name; @@ -692,10 +848,11 @@ return buf; }; - Search.renderMoveRow = function (move, matchStart, matchLength, errorMessage) { - var attrs = ''; - if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'moves/' + toId(move.name) + '" data-target="push"'; - var buf = '
  • '; + Search.renderMoveRow = function (move, matchStart, matchLength, errorMessage, attrs) { + if (!attrs) attrs = ''; + var id = toId(move.name); + if (Search.urlRoot) attrs += ' href="' + Search.urlRoot + 'moves/' + id + '" data-target="push"'; + var buf = '
  • '; // name var name = move.name; @@ -744,7 +901,7 @@ Search.renderMoveRowInner = function (move, errorMessage) { var attrs = ''; if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'moves/' + toId(move.name) + '" data-target="push"'; - var buf = ''; + var buf = ''; // name var name = move.name; @@ -779,7 +936,7 @@ Search.renderTaggedMoveRow = function (move, tag, errorMessage) { var attrs = ''; if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'moves/' + toId(move.name) + '" data-target="push"'; - var buf = '
  • '; + var buf = '
  • '; // tag buf += '' + tag + ' '; @@ -817,7 +974,7 @@ Search.renderTypeRow = function (type, matchStart, matchLength, errorMessage) { var attrs = ''; if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'types/' + toId(type.name) + '" data-target="push"'; - var buf = '
  • '; + var buf = '
  • '; // name var name = type.name; @@ -844,7 +1001,7 @@ Search.renderCategoryRow = function (category, matchStart, matchLength, errorMessage) { var attrs = ''; if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'categories/' + toId(category.name) + '" data-target="push"'; - var buf = '
  • '; + var buf = '
  • '; // name var name = category.name; @@ -871,7 +1028,7 @@ Search.renderEggGroupRow = function (egggroup, matchStart, matchLength, errorMessage) { var attrs = ''; if (Search.urlRoot) attrs = ' href="' + Search.urlRoot + 'egggroups/' + toId(egggroup.name) + '" data-target="push"'; - var buf = '
  • '; + var buf = '
  • '; // name var name = egggroup.name; diff --git a/style/client.css b/style/client.css index db722ecd6..265951e0c 100644 --- a/style/client.css +++ b/style/client.css @@ -2726,12 +2726,13 @@ a.ilink.yours { border-radius: 4px; cursor: pointer; } -.teambuilder-results .result a:hover { +.teambuilder-results .result a:hover, +.teambuilder-results .result a.hover { border-color: #D8D8D8; background: #F8F8F8; } -.teambuilder-results .firstresult a, -.teambuilder-results .firstresult a:hover { +.teambuilder-results .result a.cur:hover, +.teambuilder-results .result a.cur.hover { border-color: #CCCCCC; background: #F2F2F2; } diff --git a/style/utilichart.css b/style/utilichart.css index a1a55ebb8..27cbfb92f 100644 --- a/style/utilichart.css +++ b/style/utilichart.css @@ -15,12 +15,42 @@ margin: 0 5px 1px 5px; text-decoration: none; } +.utilichart li > a.cur { + border-color: #CCCCCC; + background: #F2F2F2; +} +.utilichart .filter { + padding:1px 3px; + border-radius: 3px; + border: 1px solid #777; + background: #EEE; + font-size: 9pt; + font-family: Verdana, sans-serif; + font-weight: bold; + cursor: pointer; +} +.utilichart .filter:hover { + color: #777777; + text-decoration: line-through; +} +.utilichart .filter i { + color: #999999; +} +.utilichart .filter:hover i { + color: #BB2222; +} .resultheader, .utilichart li.resultheader, .utilichart .result { height: 32px; padding: 1px 0 0 0; } +.result p, +.resultheader p, +.utilichart .result p { + padding: 7px 0 0 8px; + margin: 0; +} .utilichart h3, .dexentry h3, .resultheader h3 { @@ -185,6 +215,13 @@ width: 90px; text-align: center; } +.utilichart .hiddenabilitycol { + font-style: italic; +} +.utilichart .unreleasedhiddenabilitycol { + font-style: italic; + text-decoration: line-through; +} .utilichart b { color: #4488CC; text-decoration: underline; diff --git a/testclient.html b/testclient.html index e7b162811..ec64db6bb 100644 --- a/testclient.html +++ b/testclient.html @@ -116,7 +116,9 @@ - + + +