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 @@ - + + +