(function(exports, $) { // this is a useful global var teams; var TeambuilderRoom = exports.TeambuilderRoom = exports.Room.extend({ type: 'teambuilder', initialize: function() { teams = Storage.teams; // left menu this.$el.addClass('ps-room-light').addClass('scrollable'); app.on('init:loadteams', this.update, this); this.update(); }, focus: function() { this.buildMovelists(); if (this.curTeam) { this.curTeam.iconCache = '!'; Storage.activeSetList = this.curSetList; } }, blur: function() { if (this.saveFlag) { this.saveFlag = false; app.user.trigger('saveteams'); } }, events: { // team changes 'change input.teamnameedit': 'teamNameChange', 'change select[name=format]': 'formatChange', 'change input[name=nickname]': 'nicknameChange', // details 'change .detailsform input': 'detailsChange', // stats 'keyup .statform input.numform': 'statChange', 'input .statform input[type=number].numform': 'statChange', 'change select[name=nature]': 'natureChange', // teambuilder events 'click .utilichart a': 'chartClick', 'keydown .chartinput': 'chartKeydown', 'keyup .chartinput': 'chartKeyup', 'focus .chartinput': 'chartFocus', 'change .chartinput': 'chartChange', // drag/drop 'click .team': 'edit', 'mouseover .team': 'mouseOverTeam', 'mouseout .team': 'mouseOutTeam', 'dragstart .team': 'dragStartTeam', 'dragend .team': 'dragEndTeam', 'dragenter .team': 'dragEnterTeam', // clipboard 'click .teambuilder-clipboard-data .result': 'clipboardResultSelect', 'click .teambuilder-clipboard-data': 'clipboardExpand', 'blur .teambuilder-clipboard-data': 'clipboardShrink' }, dispatchClick: function(e) { e.preventDefault(); e.stopPropagation(); if (this[e.currentTarget.value]) this[e.currentTarget.value].call(this, e); }, saveTeams: function() { // save and return Storage.saveTeams(); app.user.trigger('saveteams'); this.update(); }, back: function() { if (this.exportMode) { if (this.curTeam) this.curTeam.team = Storage.packTeam(this.curSetList); this.exportMode = false; Storage.saveTeams(); } else if (this.curSet) { app.clearGlobalListeners(); this.curSet = null; Storage.saveTeams(); } else if (this.curTeam) { this.curTeam.team = Storage.packTeam(this.curSetList); this.curTeam.iconCache = ''; Storage.saveTeam(this.curTeam); this.curTeam = null; Storage.activeSetList = this.curSetList = null; } else { return; } app.user.trigger('saveteams'); this.update(); }, // the teambuilder has three views: // - team list (curTeam falsy) // - team view (curTeam exists, curSet falsy) // - set view (curTeam exists, curSet exists) curTeam: null, curTeamLoc: 0, curSet: null, curSetLoc: 0, exportMode: false, update: function() { teams = Storage.teams; if (this.curTeam) { if (this.curSet) { return this.updateSetView(); } return this.updateTeamView(); } return this.updateTeamList(); }, /********************************************************* * Team list view *********************************************************/ deletedTeam: null, deletedTeamLoc: -1, updateTeamList: function() { var teams = Storage.teams; var buf = ''; this.deletedSet = null; this.deletedSetLoc = -1; if (this.exportMode) { buf = '
'; buf += '
'; this.$el.html(buf); return; } if (!teams) { buf = '

lol zarel this is a horrible teambuilder

'; buf += '

that\'s because we\'re not done loading it...

'; this.$el.html(buf); return; } buf = '

y\'know strawberries have more potassium than bananas

'; buf += '

that doesn\'t have anything to do with teambuilding!

'; buf += '

fiiiiiiine press Ctrl+F to find teams by pokemon name

'; buf += this.clipboardHTML(); buf += ''; if (window.nodewebkit) { buf += ' '; } else { buf += ''; buf += '

Clearing your cookies (specifically, localStorage) will delete your teams.

If you want to clear your cookies or localStorage, you can use the Backup/Restore feature to save your teams as text first.

'; } buf += '
'; this.$el.html(buf); }, // button actions revealFolder: function() { Storage.revealFolder(); }, reloadTeamsFolder: function() { Storage.nwLoadTeams(); }, edit: function(i) { if (i && i.currentTarget) { i = $(i.currentTarget).data('value'); } i = +i; this.curTeam = teams[i]; this.curTeam.iconCache = '!'; Storage.activeSetList = this.curSetList = Storage.unpackTeam(this.curTeam.team); this.curTeamIndex = i; this.update(); }, "delete": function(i) { i = +i; this.deletedTeamLoc = i; this.deletedTeam = teams.splice(i, 1)[0]; Storage.deleteTeam(this.deletedTeam); this.update(); }, undoDelete: function() { if (this.deletedTeamLoc >= 0) { teams.splice(this.deletedTeamLoc, 0, this.deletedTeam); var undeletedTeam = this.deletedTeam; this.deletedTeam = null; this.deletedTeamLoc = -1; Storage.saveTeam(undeletedTeam); this.update(); } }, saveBackup: function() { Storage.importTeam(this.$('.teamedit textarea').val(), true); teams = Storage.teams; Storage.saveAllTeams(); this.back(); }, "new": function() { var newTeam = { name: 'Untitled '+(teams.length+1), format: '', team: '', iconCache: '' }; teams.push(newTeam); this.edit(teams.length-1); }, newTop: function() { var newTeam = { name: 'Untitled '+(teams.length+1), format: '', team: '', iconCache: '' }; teams.unshift(newTeam); this.edit(0); }, "import": function() { if (this.exportMode) return this.back(); this.exportMode = true; if (!this.curTeam) { this['new'](); } else { this.update(); } }, backup: function() { this.curTeam = null; this.curSetList = null; this.exportMode = true; this.update(); }, // drag and drop // because of a bug in Chrome and Webkit: // https://code.google.com/p/chromium/issues/detail?id=410328 // we can't use CSS :hover mouseOverTeam: function(e) { e.currentTarget.className = 'team team-hover'; }, mouseOutTeam: function(e) { e.currentTarget.className = 'team'; }, dragStartTeam: function(e) { var target = e.currentTarget; var dataTransfer = e.originalEvent.dataTransfer; dataTransfer.effectAllowed = 'move'; dataTransfer.setData("text/plain", "Team " + e.currentTarget.dataset.value); app.dragging = e.currentTarget; app.draggingLoc = parseInt(e.currentTarget.dataset.value); elOffset = $(e.currentTarget).offset(); app.draggingOffsetX = e.originalEvent.pageX - elOffset.left; app.draggingOffsetY = e.originalEvent.pageY - elOffset.top; setTimeout(function() { $(e.currentTarget).parent().addClass('dragging'); }, 0); }, dragEndTeam: function(e) { app.dragging = null; var originalLoc = parseInt(e.currentTarget.dataset.value); if (isNaN(originalLoc)) { throw new Error("drag failed"); return; } var newLoc = Math.floor(app.draggingLoc); if (app.draggingLoc < originalLoc) newLoc += 1; if (newLoc !== originalLoc) { var team = Storage.teams[originalLoc]; Storage.teams.splice(originalLoc, 1); Storage.teams.splice(newLoc, 0, team); } // possibly half-works-around a hover issue in this.$('.teamlist').css('pointer-events', 'none'); $(e.currentTarget).parent().removeClass('dragging'); // We're going to try to animate the team settling into its new position // This would be really straightforward if not for the confluence of several // different browser bugs: // http://stackoverflow.com/questions/20482233/x-and-y-values-in-html5-drag-events-are-inconsistent-across-browsers if (e.originalEvent.dataTransfer.dropEffect === 'move' && (e.originalEvent.pageX || e.originalEvent.pageY)) { // We have a pageX and a pageY to work with var finalPos = $(e.currentTarget).offset(); this.updateTeamList(); var $newTeamEl = this.$('.team[data-value=' + newLoc + ']'); // console.log('x,y = ' + [e.originalEvent.x, e.originalEvent.y]); // console.log('screenX,screenY = ' + [e.originalEvent.screenX, e.originalEvent.screenY]); // console.log('clientX,clientY = ' + [e.originalEvent.clientX, e.originalEvent.clientY]); // console.log('pageX,pageY = ' + [e.originalEvent.pageX, e.originalEvent.pageY]); var finalOffset; if (navigator.userAgent.indexOf("Chrome/") >= 0) { // Hopefully Chrome // event.pageX|pageY are relative to the bottom left corner of the draggable in Chrome ?? // (x-0, y-50) gives the top left corner // Let's just hope this is the standard and we won't have to redo it for IE or something finalOffset = [e.originalEvent.pageX - 0 - finalPos.left, e.originalEvent.pageY - 50 - finalPos.top]; } else if (navigator.userAgent.indexOf("Safari/") >= 0) { // Hopefully Safari // Safari is even weirder, it appears to be the mouse position relative to a spot exactly // 63 pixels above the bottom of the viewport... I have no clue where the 63 comes from so // I'm hoping it's the same on other computers finalOffset = [e.originalEvent.pageX - app.draggingOffsetX - finalPos.left, $('body').height() - 63 - e.originalEvent.pageY - app.draggingOffsetY - finalPos.top]; } if (finalOffset) { $newTeamEl.css('transform', 'translate(' + finalOffset[0] + 'px, ' + finalOffset[1] + 'px)'); setTimeout(function() { $newTeamEl.css('transition', 'transform 0.15s'); // it's 2015 and Safari doesn't support unprefixed transition!!! $newTeamEl.css('-webkit-transition', '-webkit-transform 0.15s'); $newTeamEl.css('transform', 'translate(0px, 0px)'); }); } } else { // browser doesn't support event.pageX|pageY for dragend // (namely, Firefox) this.updateTeamList(); } }, dragEnterTeam: function(e) { if (app.dragging) { if (e.currentTarget === app.dragging) { e.preventDefault(); return; } var hoverLoc = parseInt(e.currentTarget.dataset.value); if (app.draggingLoc > hoverLoc) { // dragging up $(e.currentTarget).parent().before($(app.dragging).parent()); app.draggingLoc = parseInt(e.currentTarget.dataset.value) - 0.5; } else { // dragging down $(e.currentTarget).parent().after($(app.dragging).parent()); app.draggingLoc = parseInt(e.currentTarget.dataset.value) + 0.5; } } }, /********************************************************* * Team view *********************************************************/ updateTeamView: function() { this.curChartName = ''; this.curChartType = ''; var buf = ''; if (this.exportMode) { buf = '
'; buf += '
'; } else { buf = '
'; buf += '
'; buf += '
    '; buf += '
  1. ' + this.clipboardHTML() + '
  2. '; var i=0; if (this.curSetList.length && !this.curSetList[this.curSetList.length-1].species) { this.curSetList.splice(this.curSetList.length-1, 1); } if (exports.BattleFormats) { buf += '
  3. '; } if (!this.curSetList.length) { buf += '
  4. you have no pokemon lol
  5. '; } for (i=0; i'; if (!set.species) { if (this.deletedSet) { buf += '
    '; } buf += '
    '; buf += ''; return buf; } buf += '
    '; buf += '
    '; buf += ''; buf += '
    '; buf += '
    '; // icon var itemicon = ''; if (set.item) { var item = Tools.getItem(set.item); itemicon = ''; } buf += '
    '+itemicon+'
    '; // details buf += '
    '; buf += '
    '; buf += '
    '; buf += '
    '; buf += '
    '; buf += '
    '; // moves if (!set.moves) set.moves = []; buf += '
    '; buf += '
    '; buf += '
    '; buf += '
    '; buf += '
    '; buf += '
    '; // stats buf += '
    '; buf += '
    '; return buf; }, saveImport: function() { Storage.activeSetList = this.curSetList = Storage.importTeam(this.$('.teamedit textarea').val()); this.back(); }, addPokemon: function() { if (!this.curTeam) return; var team = this.curSetList; if (!team.length || team[team.length-1].species) { var newPokemon = { name: '', species: '', item: '', nature: '', evs: {}, ivs: {}, moves: [] }; team.push(newPokemon); } this.curSet = team[team.length-1]; this.curSetLoc = team.length-1; this.curChartName = ''; this.update(); this.$('input[name=pokemon]').select(); }, pastePokemon: function(i, btn) { if (!this.curTeam) return; var team = this.curSetList; if (team.length >= 6) return; if (!this.clipboardCount()) return; if (team.push($.extend(true, {}, this.clipboard[0])) >= 6) { $(btn).css('display', 'none'); } this.update(); this.save(); }, saveFlag: false, save: function() { this.saveFlag = true; Storage.saveTeams(); }, teamNameChange: function(e) { this.curTeam.name = ($.trim(e.currentTarget.value) || 'Untitled '+(this.curTeamLoc+1)); e.currentTarget.value = this.curTeam.name; this.save(); }, formatChange: function(e) { this.curTeam.format = e.currentTarget.value; this.save(); if (this.curTeam.format.substr(0, 4) === 'gen5' && !Tools.loadedSpriteData['bw']) Tools.loadSpriteData('bw'); }, nicknameChange: function(e) { var i = +$(e.currentTarget).closest('li').attr('value'); var team = this.curSetList[i]; var name = $.trim(e.currentTarget.value) || team.species; e.currentTarget.value = team.name = name; this.save(); }, // clipboard clipboard: [], clipboardCount: function() { return this.clipboard.length; }, clipboardVisible: function() { return !!this.clipboardCount(); }, clipboardHTML: function() { var buf = ''; buf += '
    '; buf += '
    Clipboard:
    '; buf += '
    ' + this.clipboardInnerHTML() + '
    '; buf += '
    '; if (this.curTeam && this.curSetList.length < 6) { buf += ''; } buf += ''; buf += '
    '; buf += '
    '; return buf; }, clipboardInnerHTMLCache: '', clipboardInnerHTML: function() { if (this.clipboardInnerHTMLCache) { return this.clipboardInnerHTMLCache; } var buf = ''; for (var i = 0; i < this.clipboardCount(); i++) { var res = this.clipboard[i]; var pokemon = Tools.getTemplate(res.species); buf += '
    '; buf += '
    '; buf += '' + (pokemon.species === pokemon.baseSpecies ? pokemon.species : (pokemon.baseSpecies + '-' + pokemon.species.substr(pokemon.baseSpecies.length + 1) + '')) + '
    '; buf += '
    ' + (res.ability || 'No ability') + '
    ' + (res.item || 'No item') + '
    '; buf += '
    '; for (var j = 0; j < 4; j++) { if (!(j & 1)) { buf += ''; } buf += (res.moves[j] || 'No move') + (!(j & 1) ? '
    ' : ''); if (j & 1) { buf += '
    '; } } buf += '
    '; buf += '
    '; } this.clipboardInnerHTMLCache = buf; return buf; }, clipboardUpdate: function() { this.clipboardInnerHTMLCache = ''; $('.teambuilder-clipboard-data').html(this.clipboardInnerHTML()); }, clipboardExpanded: false, clipboardExpand: function() { var $clipboard = $('.teambuilder-clipboard-data'); $clipboard.animate({ height: this.clipboardCount() * 28 }, 500, function() { setTimeout(function() { $clipboard.focus(); }, 100); }); setTimeout(function() { this.clipboardExpanded = true; }.bind(this), 10); }, clipboardShrink: function() { var $clipboard = $('.teambuilder-clipboard-data'); $clipboard.animate({ height: 26 }, 500); setTimeout(function() { this.clipboardExpanded = false; }.bind(this), 10); }, clipboardResultSelect: function(e) { if (!this.clipboardExpanded) return; e.stopPropagation(); var target = +($(e.target).closest('.result').data('id')); if (target === -1) { this.clipboardShrink(); this.clipboardRemoveAll(); return; } this.clipboard.unshift(this.clipboard.splice(target, 1)[0]); this.clipboardUpdate(); this.clipboardShrink(); }, clipboardAdd: function(set) { if (this.clipboard.unshift(set) > 6) { // we don't want the clipboard so big that it lags the teambuilder this.clipboard.pop(); } this.clipboardUpdate(); if (this.clipboardCount() === 1) { var $clipboard = $('.teambuilder-clipboard-container').css('opacity', 0); $clipboard.slideDown(250, function () { $clipboard.animate({ opacity: 1 }, 250); }); } }, clipboardRemoveAll: function() { this.clipboard = []; var self = this; var $clipboard = $('.teambuilder-clipboard-container'); $clipboard.animate({ opacity: 0 }, 250, function () { $clipboard.slideUp(250, function() { self.clipboardUpdate(); }); }); }, // copy/import/export/move/delete copySet: function(i, button) { i = +($(button).closest('li').attr('value')); this.clipboardAdd($.extend(true, {}, this.curSetList[i])); button.blur(); }, wasViewingPokemon: false, importSet: function(i, button) { i = +($(button).closest('li').attr('value')); this.wasViewingPokemon = true; if (!this.curSet) { this.wasViewingPokemon = false; this.selectPokemon(i); } this.$('li').find('input, button').prop('disabled', true); this.$chart.hide(); this.$('.teambuilder-pokemon-import') .show() .find('textarea') .val(Storage.exportTeam([this.curSet]).trim()) .focus(); }, closePokemonImport: function(force) { if (!this.wasViewingPokemon) return this.back(); var $li = this.$('li'); var i = +($li.attr('value')); this.$('.teambuilder-pokemon-import').hide(); this.$chart.show(); if (force === true) return this.selectPokemon(i); $li.find('input, button').prop('disabled', false); }, savePokemonImport: function(i) { i = +(this.$('li').attr('value')); this.curSet = Storage.importTeam(this.$('.pokemonedit').val())[0]; // since we just destroyed the reference... this.curSetList[i] = this.curSet; this.closePokemonImport(true); }, moveSet: function(i, button) { i = +($(button).closest('li').attr('value')); app.addPopup(MovePopup, { i: i, team: this.curSetList }); }, deleteSet: function(i, button) { i = +($(button).closest('li').attr('value')); this.deletedSetLoc = i; this.deletedSet = this.curSetList.splice(i, 1)[0]; if (this.curSet) { this.addPokemon(); } else { this.update(); } this.save(); }, undeleteSet: function() { if (this.deletedSet) { var loc = this.deletedSetLoc; this.curSetList.splice(loc, 0, this.deletedSet); this.deletedSet = null; this.deletedSetLoc = -1; this.save(); if (this.curSet) { this.curSetLoc = loc; this.curSet = this.curSetList[loc]; this.curChartName = ''; this.update(); this.updateChart(); } else { this.update(); } } }, /********************************************************* * Set view *********************************************************/ updateSetView: function() { // pokemon var buf = '
    '; buf += '
    '; buf += '
    '; buf += this.renderTeambar(); buf += '
    '; // pokemon buf += '
    '; buf += '
      '; buf += this.renderSet(this.curSet, this.curSetLoc); buf += '
    '; buf += '
    '; // results this.chartPrevSearch = '[init]'; buf += '
    '; // import/export buf += '
    '; buf += '
    '; buf += ''; buf += '
    '; this.$el.html(buf); this.$chart = this.$('.teambuilder-results'); }, updateSetTop: function() { this.$('.teambar').html(this.renderTeambar()); this.$('.teamchart').first().html(this.renderSet(this.curSet, this.curSetLoc)); }, renderTeambar: function() { var buf = ''; var isAdd = false; if (this.curSetList.length && !this.curSetList[this.curSetList.length-1].name && this.curSetLoc !== this.curSetList.length-1) { this.curSetList.splice(this.curSetList.length-1, 1); } for (var i=0; i'; if (!set.name) { buf += ' '; isAdd = true; } else if (i == this.curSetLoc) { buf += ' '; } else { buf += ' '; } } if (this.curSetList.length < 6 && !isAdd) { buf += ' '; } return buf; }, updatePokemonSprite: function() { var set = this.curSet; if (!set) return; var shiny = (set.shiny ? '-shiny' : ''); var sprite = Tools.getTemplate(set.species).spriteid; if (BattlePokemonSprites && BattlePokemonSprites[sprite] && BattlePokemonSprites[sprite].front && BattlePokemonSprites[sprite].front.anif && set.gender === 'F') { sprite += '-f'; } this.$('.setcol-icon').css('background-image', Tools.getTeambuilderSprite(set).substr(17)); this.$('.pokemonicon-'+this.curSetLoc).css('background', Tools.getIcon(set).substr(11)); var item = Tools.getItem(set.item); if (item.id) { this.$('.setcol-icon .itemicon').css('background', Tools.getItemIcon(item).substr(11)); } else { this.$('.setcol-icon .itemicon').css('background', 'none'); } this.updateStatGraph(); }, updateStatGraph: function() { var set = this.curSet; if (!set) return; var stats = {hp:'',atk:'',def:'',spa:'',spd:'',spe:''}; // stat cell var buf = ' EV'; for (var stat in stats) { stats[stat] = this.getStat(stat, set); var ev = ''+(set.evs[stat] || '')+''; if (BattleNatures[set.nature] && BattleNatures[set.nature].plus === stat) { ev += '+'; } else if (BattleNatures[set.nature] && BattleNatures[set.nature].minus === stat) { ev += ''; } var width = stats[stat]*75/504; if (stat == 'hp') width = stats[stat]*75/704; if (width > 75) width = 75; var color = Math.floor(stats[stat]*180/714); if (color > 360) color = 360; buf += ' '+ev+''; } this.$('button[name=stats]').html(buf); if (this.curChartType !== 'stats') return; buf = '
    '; for (var stat in stats) { buf += '
    '+stats[stat]+'
    '; } this.$chart.find('.statscol').html(buf); buf = '
    '; var totalev = 0; for (var stat in stats) { var width = stats[stat]*180/504; if (stat == 'hp') width = stats[stat]*180/704; if (width > 179) width = 179; var color = Math.floor(stats[stat]*180/714); if (color > 360) color = 360; buf += '
    '; totalev += (set.evs[stat]||0); } buf += '
    Remaining:
    '; this.$chart.find('.graphcol').html(buf); if (totalev <= 510) { this.$chart.find('.totalev').html(''+(totalev>508?0:508-totalev)+''); } else { this.$chart.find('.totalev').html(''+(510-totalev)+''); } this.$chart.find('select[name=nature]').val(set.nature||'Serious'); }, curChartType: '', curChartName: '', updateChart: function() { var type = this.curChartType; app.clearGlobalListeners(); if (type === 'stats') { this.updateStatForm(); return; } if (type === 'details') { this.updateDetailsForm(); return; } // cache movelist ref var speciesid = toId(this.curSet.species); var g6 = (this.curTeam.format && (this.curTeam.format === 'vgc2014' || this.curTeam.format === 'vgc2015')); this.applyMovelist(g6, speciesid); 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))); }, 10); }, updateChartTimeout: null, updateChartDelayed: function() { // cache movelist ref var speciesid = toId(this.curSet.species); var g6 = (this.curTeam.format && (this.curTeam.format === 'vgc2014' || this.curTeam.format === 'vgc2015')); 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))); }, 200); }, selectPokemon: function(i) { i = +i; var set = this.curSetList[i]; if (set) { this.curSet = set; this.curSetLoc = i; var name = this.curChartName || 'details'; if (name === 'details' || name === 'stats') { this.update(); this.updateChart(); } else { this.curChartName = ''; this.update(); this.$('input[name='+name+']').select(); } } }, stats: function(i, button) { if (!this.curSet) this.selectPokemon($(button).closest('li').val()); this.curChartName = 'stats'; this.curChartType = 'stats'; this.updateStatForm(); }, details: function(i, button) { if (!this.curSet) this.selectPokemon($(button).closest('li').val()); this.curChartName = 'details'; this.curChartType = 'details'; this.updateDetailsForm(); }, /********************************************************* * Set stat form *********************************************************/ plus: '', minus: '', updateStatForm: function(setGuessed) { var buf = ''; var set = this.curSet; var baseStats = Tools.getTemplate(this.curSet.species).baseStats; buf += '

    EVs

    '; buf += '
    '; var role = this.guessRole(); var guessedEVs = {}; var guessedPlus = ''; var guessedMinus = ''; buf += '

    Suggested spread:'; if (role === '?') { buf += ' (Please choose 4 moves to get a suggested spread)

    '; } else { guessedEVs = this.guessEVs(role); guessedPlus = guessedEVs.plusStat; delete guessedEVs.plusStat; guessedMinus = guessedEVs.minusStat; delete guessedEVs.minusStat; buf += '

    '; //buf += ' ('+role+' | bulk: phys '+Math.round(this.moveCount.physicalBulk/1000)+' + spec '+Math.round(this.moveCount.specialBulk/1000)+' = '+Math.round(this.moveCount.bulk/1000)+')'; } if (setGuessed) { set.evs = guessedEVs; this.plus = guessedPlus; this.minus = guessedMinus; this.updateNature(); this.save(); this.updateStatGraph(); this.natureChange(); return; } var stats = {hp:'',atk:'',def:'',spa:'',spd:'',spe:''}; if (!set) return; var nature = BattleNatures[set.nature || 'Serious']; if (!nature) nature = {}; // label column buf += '
    '; buf += '
    '; buf += '
    '; buf += '
    Base
    '; for (var i in stats) { buf += '
    '+baseStats[i]+'
    '; } buf += '
    '; buf += '
    '; for (var i in stats) { stats[i] = this.getStat(i); var width = stats[i]*180/504; if (i=='hp') width = Math.floor(stats[i]*180/704); if (width > 179) width = 179; var color = Math.floor(stats[i]*180/714); if (color>360) color = 360; buf += '
    '; } buf += '
    Remaining:
    '; buf += '
    '; buf += '
    EVs
    '; var totalev = 0; this.plus = ''; this.minus = ''; for (var i in stats) { var width = stats[i]*200/504; if (i=='hp') width = stats[i]*200/704; if (width > 200) width = 200; var val = ''+(set.evs[i]||''); if (nature.plus === i) { val += '+'; this.plus = i; } if (nature.minus === i) { val += '-'; this.minus = i; } buf += '
    '; totalev += (set.evs[i]||0); } if (totalev <= 510) { buf += '
    '+(totalev>508?0:508-totalev)+'
    '; } else { buf += '
    '+(510-totalev)+'
    '; } buf += '
    '; buf += '
    '; for (var i in stats) { buf += '
    '; } buf += '
    '; buf += '
    IVs
    '; var totalev = 0; if (!set.ivs) set.ivs = {}; for (var i in stats) { if (typeof set.ivs[i] === 'undefined' || isNaN(set.ivs[i])) set.ivs[i] = 31; var val = ''+(set.ivs[i]); buf += '
    '; } buf += '
    '; buf += '
    '; for (var i in stats) { buf += '
    '+stats[i]+'
    '; } buf += '
    '; buf += '

    Nature:

    '; buf += '

    Protip: You can also set natures by typing "+" and "-" next to a stat.

    '; buf += '
    '; this.$chart.html(buf); var self = this; this.suppressSliderCallback = true; app.clearGlobalListeners(); this.$chart.find('.evslider').slider({ from: 0, to: 252, step: 4, skin: 'round_plastic', onstatechange: function(val) { if (!self.suppressSliderCallback) self.statSlide(val, this); }, callback: function() { self.save(); } }); this.suppressSliderCallback = false; }, setStatFormGuesses: function() { this.updateStatForm(true); }, setSlider: function(stat, val) { this.suppressSliderCallback = true; this.$chart.find('input[name=evslider-'+stat+']').slider('value', val||0); this.suppressSliderCallback = false; }, updateNature: function() { var set = this.curSet; if (!set) return; if (this.plus === '' || this.minus === '') { set.nature = 'Serious'; } else { for (var i in BattleNatures) { if (BattleNatures[i].plus === this.plus && BattleNatures[i].minus === this.minus) { set.nature = i; break; } } } }, statChange: function(e) { var inputName = ''; inputName = e.currentTarget.name; var val = Math.abs(parseInt(e.currentTarget.value, 10)); var set = this.curSet; if (!set) return; if (inputName.substr(0,5) === 'stat-') { // EV // Handle + and - var stat = inputName.substr(5); if (!set.evs) set.evs = {}; var lastchar = e.currentTarget.value.charAt(e.target.value.length-1); var firstchar = e.currentTarget.value.charAt(0); var natureChange = true; if ((lastchar === '+' || firstchar === '+') && stat !== 'hp') { if (this.plus && this.plus !== stat) this.$chart.find('input[name=stat-'+this.plus+']').val(set.evs[this.plus]||''); this.plus = stat; } else if ((lastchar === '-' || lastchar === "\u2212" || firstchar === '-' || firstchar === "\u2212") && stat !== 'hp') { if (this.minus && this.minus !== stat) this.$chart.find('input[name=stat-'+this.minus+']').val(set.evs[this.minus]||''); this.minus = stat; } else if (this.plus === stat) { this.plus = ''; } else if (this.minus === stat) { this.minus = ''; } else { natureChange = false; } if (natureChange) { this.updateNature(); } // cap if (val > 252) val = 252; if (val < 0) val = 0; if (set.evs[stat] !== val || natureChange) { set.evs[stat] = val; this.setSlider(stat, val); this.updateStatGraph(); } } else { // IV var stat = inputName.substr(3); if (val > 31 || isNaN(val)) val = 31; if (val < 0) val = 0; if (!set.ivs) set.ivs = {}; if (set.ivs[stat] !== val) { set.ivs[stat] = val; this.updateStatGraph(); } } this.save(); }, statSlide: function(val, slider) { var stat = slider.inputNode[0].name.substr(9); var set = this.curSet; if (!set) return; val = +val; if (!this.ignoreEVLimits && set.evs) { var total = 0; for (var i in set.evs) { total += (i===stat?val:set.evs[i]); } if (total > 508 && val - total + 508 >= 0) { // don't allow dragging beyond 508 EVs val = val - total + 508; // make sure val is a legal value val = +val; if (!val || val <= 0) val = 0; if (val > 252) val = 252; // Don't try this at home. // I am a trained professional. slider.o.pointers[0].set(val); } } if (!set.evs) set.evs = {}; set.evs[stat] = val; val = ''+(val||'')+(this.plus===stat?'+':'')+(this.minus===stat?'-':''); this.$('input[name=stat-'+stat+']').val(val); this.updateStatGraph(); }, natureChange: function(e) { var set = this.curSet; if (!set) return; if (e) { set.nature = e.currentTarget.value; } this.plus = ''; this.minus = ''; var nature = BattleNatures[set.nature||'Serious']; for (var i in BattleStatNames) { var val = ''+(set.evs[i]||''); if (nature.plus === i) { this.plus = i; val += '+'; } if (nature.minus === i) { this.minus = i; val += '-'; } this.$chart.find('input[name=stat-'+i+']').val(val); if (!e) this.setSlider(i, set.evs[i]); } this.save(); this.updateStatGraph(); }, /********************************************************* * Set details form *********************************************************/ updateDetailsForm: function() { var buf = ''; var set = this.curSet; var template = Tools.getTemplate(set.species); if (!set) return; buf += '

    Details

    '; buf += '
    '; buf += '
    '; buf += '
    '; if (template.gender) { var genderTable = {'M': "Male", 'F': "Female", 'N': "Genderless"}; buf += genderTable[template.gender]; } else { buf += ' '; buf += ' '; buf += ''; } buf += '
    '; buf += '
    '; buf += '
    '; buf += ' '; buf += ''; buf += '
    '; buf += '
    '; this.$chart.html(buf); }, detailsChange: function() { var set = this.curSet; if (!set) return; // level var level = parseInt(this.$chart.find('input[name=level]').val(),10); if (!level || level > 100 || level < 1) level = 100; if (level !== 100 || set.level) set.level = level; // happiness var happiness = parseInt(this.$chart.find('input[name=happiness]').val(),10); if (happiness > 255) happiness = 255; if (happiness < 0) happiness = 255; set.happiness = happiness; if (set.happiness === 255) delete set.happiness; // shiny var shiny = (this.$chart.find('input[name=shiny]:checked').val() === 'yes'); if (shiny) { set.shiny = true; } else { delete set.shiny; } var gender = this.$chart.find('input[name=gender]:checked').val(); if (gender === 'M' || gender === 'F') { set.gender = gender; } else { delete set.gender; } // update details cell var buf = ''; var GenderChart = { 'M': 'Male', 'F': 'Female', 'N': '—' }; buf += ''+(set.level||100)+''; buf += ''+GenderChart[set.gender||'N']+''; buf += ''+(typeof set.happiness==='number'?set.happiness:255)+''; buf += ''+(set.shiny?'Yes':'No')+''; this.$('button[name=details]').html(buf); this.save(); this.updatePokemonSprite(); }, /********************************************************* * Set charts *********************************************************/ arrangeCallback: { pokemon: function(pokemon) { if (!pokemon) { if (this.curTeam) { if (this.curTeam.format === 'ubers' || this.curTeam.format === 'anythinggoes') return ['Uber','OU','BL','UU','BL2','RU','BL3','NU','BL4','PU','NFE','LC Uber','LC']; if (this.curTeam.format === 'ou') return ['OU','BL','UU','BL2','RU','BL3','NU','BL4','PU','NFE','LC Uber','LC']; if (this.curTeam.format === 'cap') return ['CAP','OU','BL','UU','BL2','RU','BL3','NU','BL4','PU','NFE','LC Uber','LC']; if (this.curTeam.format === 'uu') return ['UU','BL2','RU','BL3','NU','BL4','PU','NFE','LC Uber','LC']; if (this.curTeam.format === 'ru') return ['RU','BL3','NU','BL4','PU','NFE','LC Uber','LC']; if (this.curTeam.format === 'nu') return ['NU','BL4','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','UU','BL2','RU','BL3','NU','BL4','PU','NFE','LC Uber','LC','Unreleased','CAP']; } var tierData = exports.BattleFormatsData[toId(pokemon.species)]; if (!tierData) return 'Illegal'; return tierData.tier; }, item: function(item) { if (!item) return ['Items']; return 'Items'; }, ability: function(ability) { if (!this.curSet) return; if (!ability) return ['Abilities', 'Hidden Ability']; var template = Tools.getTemplate(this.curSet.species); if (!template.abilities) return 'Abilities'; if (ability.name === template.abilities['0']) return 'Abilities'; if (ability.name === template.abilities['1']) return 'Abilities'; if (ability.name === template.abilities['H']) return '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', ability: 'ability', move1: 'move', move2: 'move', move3: 'move', move4: 'move', stats: 'stats', details: 'details' }, chartClick: function(e) { this.chartSet($(e.currentTarget).data('name'), 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; 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))); var val = Chart.firstResult; this.chartSet(val, true); return; } }, chartKeyup: function() { this.updateChartDelayed(); }, chartFocus: function(e) { var $target = $(e.currentTarget); var name = e.currentTarget.name; var type = this.chartTypes[name]; $target.removeClass('incomplete'); if (this.curChartName === name) return; if (!this.curSet) { var i = +$target.closest('li').prop('value'); this.curSet = this.curSetList[i]; this.curSetLoc = i; this.update(); if (type === 'stats' || type === 'details') { this.$('button[name='+name+']').click(); } else { this.$('input[name='+name+']').select(); } return; } this.curChartName = name; this.curChartType = type; this.updateChart(); }, 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)); var val = Chart.firstResult; var id = toId(e.currentTarget.value); if (toId(val) !== id) { $(e.currentTarget).addClass('incomplete'); return; } this.chartSet(val); }, chartSet: function(val, selectNext) { var inputName = this.curChartName; var id = toId(val); this.$('input[name='+inputName+']').val(val).removeClass('incomplete'); switch (inputName) { case 'pokemon': this.setPokemon(val, selectNext); break; case 'item': this.curSet.item = val; this.updatePokemonSprite(); if (selectNext) this.$('input[name=ability]').select(); break; case 'ability': this.curSet.ability = val; if (selectNext) this.$('input[name=move1]').select(); break; case 'move1': this.curSet.moves[0] = val; this.chooseMove(val); if (selectNext) this.$('input[name=move2]').select(); break; case 'move2': if (!this.curSet.moves[0]) this.curSet.moves[0] = ''; this.curSet.moves[1] = val; this.chooseMove(val); this.$('input[name=move3]').select(); break; case 'move3': if (!this.curSet.moves[0]) this.curSet.moves[0] = ''; if (!this.curSet.moves[1]) this.curSet.moves[1] = ''; this.curSet.moves[2] = val; this.chooseMove(val); if (selectNext) this.$('input[name=move4]').select(); break; case 'move4': if (!this.curSet.moves[0]) this.curSet.moves[0] = ''; if (!this.curSet.moves[1]) this.curSet.moves[1] = ''; if (!this.curSet.moves[2]) this.curSet.moves[2] = ''; this.curSet.moves[3] = val; this.chooseMove(val); if (selectNext) this.stats(); break; } this.save(); }, chooseMove: function(move) { var set = this.curSet; if (!set) return; if (move.substr(0,13) === 'Hidden Power ') { set.ivs = {}; for (var i in exports.BattleTypeChart[move.substr(13)].HPivs) { set.ivs[i] = exports.BattleTypeChart[move.substr(13)].HPivs[i]; } var moves = this.curSet.moves; for (var i = 0; i < moves.length; ++i) { if (moves[i] === 'Gyro Ball' || moves[i] === 'Trick Room') set.ivs['spe'] = set.ivs['spe'] % 4; } } else if (move === 'Gyro Ball' || move === 'Trick Room') { var hasHiddenPower = false; var moves = this.curSet.moves; for (var i = 0; i < moves.length; ++i) { if (moves[i].substr(0,13) === 'Hidden Power ') hasHiddenPower = true; } set.ivs['spe'] = hasHiddenPower ? set.ivs['spe'] % 4 : 0; } else if (move === 'Return') { this.curSet.happiness = 255; } else if (move === 'Frustration') { this.curSet.happiness = 0; } }, setPokemon: function(val, selectNext) { var set = this.curSet; var template = Tools.getTemplate(val); var newPokemon = !set.species; if (!template.exists || set.species === template.species) return; set.name = template.species; set.species = val; if (set.level) delete set.level; if (this.curTeam && (this.curTeam.format === 'lc' || this.curTeam.format === 'gen5lc')) set.level = 5; if (set.gender) delete set.gender; if (template.gender && template.gender !== 'N') set.gender = template.gender; if (set.happiness) delete set.happiness; if (set.shiny) delete set.shiny; set.item = ''; set.ability = template.abilities['0']; set.moves = []; set.evs = {}; set.ivs = {}; set.nature = ''; this.updateSetTop(); if (selectNext) this.$('input[name=item]').select(); }, /********************************************************* * Utility functions *********************************************************/ // EV guesser guessRole: function() { var set = this.curSet; if (!set) return '?'; if (!set.moves) return '?'; var moveCount = { 'Physical': 0, 'Special': 0, 'PhysicalOffense': 0, 'SpecialOffense': 0, 'PhysicalSetup': 0, 'SpecialSetup': 0, 'Support': 0, 'Setup': 0, 'Restoration': 0, 'Offense': 0, 'Stall': 0, 'SpecialStall': 0, 'PhysicalStall': 0, 'Ultrafast': 0 }; var hasMove = {}; var template = Tools.getTemplate(set.species || set.name); var stats = template.baseStats; var itemid = toId(set.item); var abilityid = toId(set.ability); if (set.moves.length < 4 && template.id !== 'unown' && template.id !== 'ditto') return '?'; for (var i=0,len=set.moves.length; i 95); var physicalBulk = (stats.hp+75)*(stats.def+87); var specialBulk = (stats.hp+75)*(stats.spd+87); if (hasMove['willowisp'] || hasMove['acidarmor'] || hasMove['irondefense'] || hasMove['cottonguard']) { physicalBulk *= 1.6; moveCount['PhysicalStall']++; } else if (hasMove['scald'] || hasMove['bulkup'] || hasMove['coil'] || hasMove['cosmicpower']) { physicalBulk *= 1.3; if (hasMove['scald']) { // partial stall goes in reverse moveCount['SpecialStall']++; } else { moveCount['PhysicalStall']++; } } if (abilityid === 'flamebody') physicalBulk *= 1.1; if (hasMove['calmmind'] || hasMove['quiverdance'] || hasMove['geomancy']) { specialBulk *= 1.3; moveCount['SpecialStall']++; } if (template.id === 'tyranitar') specialBulk *= 1.5; if (hasMove['bellydrum']) { physicalBulk *= 0.6; specialBulk *= 0.6; } if (moveCount['Restoration']) { physicalBulk *= 1.5; specialBulk *= 1.5; } else if (hasMove['painsplit'] && hasMove['substitute']) { // SubSplit isn't generally a stall set moveCount['Stall']--; } else if (hasMove['painsplit'] || hasMove['rest']) { physicalBulk *= 1.4; specialBulk *= 1.4; } if ((hasMove['bodyslam'] || hasMove['thunder']) && abilityid === 'serenegrace' || hasMove['thunderwave']) { physicalBulk *= 1.1; specialBulk *= 1.1; } if ((hasMove['ironhead'] || hasMove['airslash']) && abilityid === 'serenegrace') { physicalBulk *= 1.1; specialBulk *= 1.1; } if (hasMove['gigadrain'] || hasMove['drainpunch'] || hasMove['hornleech']) { physicalBulk *= 1.15; specialBulk *= 1.15; } if (itemid === 'leftovers' || itemid === 'blacksludge') { physicalBulk *= 1 + 0.1*(1+moveCount['Stall']/1.5); specialBulk *= 1 + 0.1*(1+moveCount['Stall']/1.5); } if (hasMove['leechseed']) { physicalBulk *= 1 + 0.1*(1+moveCount['Stall']/1.5); specialBulk *= 1 + 0.1*(1+moveCount['Stall']/1.5); } if ((itemid === 'flameorb' || itemid === 'toxicorb') && abilityid !== 'magicguard') { if (itemid === 'toxicorb' && abilityid === 'poisonheal') { physicalBulk *= 1 + 0.1*(2+moveCount['Stall']); specialBulk *= 1 + 0.1*(2+moveCount['Stall']); } else { physicalBulk *= 0.8; specialBulk *= 0.8; } } if (itemid === 'lifeorb') { physicalBulk *= 0.7; specialBulk *= 0.7; } if (abilityid === 'multiscale' || abilityid === 'magicguard' || abilityid === 'regenerator') { physicalBulk *= 1.4; specialBulk *= 1.4; } if (itemid === 'eviolite') { physicalBulk *= 1.5; specialBulk *= 1.5; } if (itemid === 'assaultvest') specialBulk *= 1.5; var bulk = physicalBulk + specialBulk; if (bulk < 46000 && stats.spe >= 70) isFast = true; moveCount['bulk'] = bulk; moveCount['physicalBulk'] = physicalBulk; moveCount['specialBulk'] = specialBulk; if (hasMove['agility'] || hasMove['dragondance'] || hasMove['quiverdance'] || hasMove['rockpolish'] || hasMove['shellsmash'] || hasMove['flamecharge']) { isFast = true; } else if (abilityid === 'unburden' || abilityid === 'speedboost' || abilityid === 'motordrive') { isFast = true; moveCount['Ultrafast'] = 1; } else if (abilityid === 'chlorophyll' || abilityid === 'swiftswim' || abilityid === 'sandrush') { isFast = true; moveCount['Ultrafast'] = 2; } if (hasMove['agility'] || hasMove['shellsmash'] || hasMove['autotomize'] || hasMove['shiftgear'] || hasMove['rockpolish']) moveCount['Ultrafast'] = 2; moveCount['Fast'] = isFast?1:0; this.moveCount = moveCount; this.hasMove = hasMove; if (template.id === 'ditto') return abilityid==='imposter'?'Physically Defensive':'Fast Bulky Support'; if (template.id === 'shedinja') return 'Fast Physical Sweeper'; if (itemid === 'choiceband' && moveCount['PhysicalAttack'] >= 2) { if (!isFast) return 'Bulky Band'; return 'Fast Band'; } else if (itemid === 'choicespecs' && moveCount['SpecialAttack'] >= 2) { if (!isFast) return 'Bulky Specs'; return 'Fast Specs'; } else if (itemid === 'choicescarf') { if (moveCount['PhysicalAttack'] === 0) return 'Special Scarf'; if (moveCount['SpecialAttack'] === 0) return 'Physical Scarf'; if (moveCount['PhysicalAttack'] > moveCount['SpecialAttack']) return 'Physical Biased Mixed Scarf'; if (moveCount['PhysicalAttack'] < moveCount['SpecialAttack']) return 'Special Biased Mixed Scarf'; if (stats.atk < stats.spa) return 'Special Biased Mixed Scarf'; return 'Physical Biased Mixed Scarf'; } if (template.id === 'unown') return 'Fast Special Sweeper'; if (moveCount['PhysicalStall'] && moveCount['Restoration']) { return 'Specially Defensive'; } if (moveCount['SpecialStall'] && moveCount['Restoration'] && itemid !== 'lifeorb') { return 'Physically Defensive'; } var offenseBias = ''; if (stats.spa > stats.atk && moveCount['Special'] > 1) offenseBias = 'Special'; else if (stats.spa > stats.atk && moveCount['Special'] > 1) offenseBias = 'Special'; else if (moveCount['Special'] > moveCount['Physical']) offenseBias = 'Special'; else offenseBias = 'Physical'; var offenseStat = stats[offenseBias === 'Special'?'spa':'atk']; if (moveCount['Stall'] + moveCount['Support']/2 <= 2 && bulk < 135000 && moveCount[offenseBias] >= 1.5) { if (isFast) { if (bulk > 80000 && !moveCount['Ultrafast']) return 'Bulky '+offenseBias+' Sweeper'; return 'Fast '+offenseBias+' Sweeper'; } else { if (moveCount[offenseBias] >= 3 || moveCount['Stall'] <= 0) { return 'Bulky '+offenseBias+' Sweeper'; } } } if (isFast && abilityid !== 'prankster') { if (stats.spe > 100 || bulk < 55000 || moveCount['Ultrafast']) { return 'Fast Bulky Support'; } } if (moveCount['SpecialStall']) return 'Physically Defensive'; if (moveCount['PhysicalStall']) return 'Specially Defensive'; if (template.id === 'blissey' || template.id === 'chansey') return 'Physically Defensive'; if (specialBulk >= physicalBulk) return 'Specially Defensive'; return 'Physically Defensive'; }, ensureMinEVs: function(evs, stat, min, evTotal) { if (!evs[stat]) evs[stat] = 0; var diff = min - evs[stat]; if (diff <= 0) return evTotal; if (evTotal <= 504) { var change = Math.min(508 - evTotal, diff); evTotal += change; evs[stat] += change; diff -= change; } if (diff <= 0) return evTotal; var evPriority = {def:1, spd:1, hp:1, atk:1, spa:1, spe:1}; for (var i in evPriority) { if (i === stat) continue; if (evs[i] && evs[i] > 128) { evs[i] -= diff; evs[stat] += diff; return evTotal; } } return evTotal; // can't do it :( }, ensureMaxEVs: function(evs, stat, min, evTotal) { if (!evs[stat]) evs[stat] = 0; var diff = evs[stat] - min; if (diff <= 0) return evTotal; evs[stat] -= diff; evTotal -= diff; return evTotal; // can't do it :( }, guessEVs: function(role) { var set = this.curSet; if (!set) return {}; var template = Tools.getTemplate(set.species || set.name); var stats = template.baseStats; var hasMove = this.hasMove; var moveCount = this.moveCount; var evs = {}; var plusStat = ''; var minusStat = ''; var statChart = { 'Bulky Band': ['atk', 'hp'], 'Fast Band': ['spe', 'atk'], 'Bulky Specs': ['spa', 'hp'], 'Fast Specs': ['spe', 'spa'], 'Physical Scarf': ['spe', 'atk'], 'Special Scarf': ['spe', 'spa'], 'Physical Biased Mixed Scarf': ['spe', 'atk'], 'Special Biased Mixed Scarf': ['spe', 'spa'], 'Fast Physical Sweeper': ['spe', 'atk'], 'Fast Special Sweeper': ['spe', 'spa'], 'Bulky Physical Sweeper': ['atk', 'hp'], 'Bulky Special Sweeper': ['spa', 'hp'], 'Fast Bulky Support': ['spe', 'hp'], 'Physically Defensive': ['def', 'hp'], 'Specially Defensive': ['spd', 'hp'] }; plusStat = statChart[role][0]; if (role === 'Fast Bulky Support') moveCount['Ultrafast'] = 0; if (plusStat === 'spe' && (moveCount['Ultrafast'] || evs['spe'] < 252)) { if (statChart[role][1] === 'atk' || statChart[role][1] == 'spa') { plusStat = statChart[role][1]; } else if (moveCount['Physical'] >= 3) { plusStat = 'atk'; } else if (stats.spd > stats.def) { plusStat = 'spd'; } else { plusStat = 'def'; } } this.ignoreEVLimits = (this.curTeam.format === 'classichackmons'); if (this.curTeam && this.ignoreEVLimits) { evs = {hp:252, atk:252, def:252, spa:252, spd:252, spe:252}; if (hasMove['gyroball'] || hasMove['trickroom']) delete evs.spe; } else { if (!statChart[role]) return {}; var evTotal = 0; var i = statChart[role][0]; var stat = this.getStat(i, null, 252, plusStat===i?1.1:1.0); var ev = 252; while (stat <= this.getStat(i, null, ev-4, plusStat===i?1.1:1.0)) ev -= 4; evs[i] = ev; evTotal += ev; var i = statChart[role][1]; if (i === 'hp' && set.level && set.level < 20) i = 'spd'; var stat = this.getStat(i, null, 252, plusStat===i?1.1:1.0); var ev = 252; if (i === 'hp' && (hasMove['substitute'] || hasMove['transform']) && stat == Math.floor(stat/4)*4) stat -= 1; while (stat <= this.getStat(i, null, ev-4, plusStat===i?1.1:1.0)) ev -= 4; if (ev < 0) ev = 0; evs[i] = ev; evTotal += ev; if (set.item !== 'Leftovers' && set.item !== 'Black Sludge') { var hpParity = 1; // 1 = should be odd, 0 = should be even if ((hasMove['substitute'] || hasMove['bellydrum']) && (set.item||'').slice(-5) === 'Berry') { hpParity = 0; } if (this.getStat('hp', null, evs['hp'] || 0, 1) % 2 != hpParity) { if (evs['hp']) { evs['hp'] -= 4; evTotal -= 4; } else { evs['hp'] = 4; evTotal += 4; } } } if (template.id === 'tentacruel') evTotal = this.ensureMinEVs(evs, 'spe', 16, evTotal); if (template.id === 'skarmory') evTotal = this.ensureMinEVs(evs, 'spe', 24, evTotal); if (template.id === 'jirachi') evTotal = this.ensureMinEVs(evs, 'spe', 32, evTotal); if (template.id === 'celebi') evTotal = this.ensureMinEVs(evs, 'spe', 36, evTotal); if (template.id === 'volcarona') evTotal = this.ensureMinEVs(evs, 'spe', 52, evTotal); if (template.id === 'gliscor') evTotal = this.ensureMinEVs(evs, 'spe', 72, evTotal); if (stats.spe == 97) evTotal = this.ensureMaxEVs(evs, 'spe', 220, evTotal); if (template.id === 'dragonite' && evs['hp']) evTotal = this.ensureMaxEVs(evs, 'spe', 220, evTotal); if (evTotal < 508) { var remaining = 508 - evTotal; if (remaining > 252) remaining = 252; if (!evs['atk'] && moveCount['PhysicalAttack']) { evs['atk'] = remaining; } else if (!evs['spa'] && moveCount['SpecialAttack']) { evs['spa'] = remaining; } else if (stats.hp == 1 && !evs['def']) { evs['def'] = remaining; } else if (stats.def === stats.spd && !evs['spd']) { evs['spd'] = remaining; } else if (!evs['spd']) { evs['spd'] = remaining; } else if (!evs['def']) { evs['def'] = remaining; } } } if (hasMove['gyroball'] || hasMove['trickroom']) { minusStat = 'spe'; } else if (!moveCount['PhysicalAttack']) { minusStat = 'atk'; } else if (!moveCount['SpecialAttack']) { minusStat = 'spa'; } else if (stats.def > stats.spd) { minusStat = 'spd'; } else { minusStat = 'def'; } if (plusStat === minusStat) { minusStat = (plusStat==='spe' ? 'spd' : 'spe'); } evs.plusStat = plusStat; evs.minusStat = minusStat; return evs; }, // Stat calculator getStat: function(stat, set, evOverride, natureOverride) { if (!set) set = this.curSet; if (!set) return 0; if (!set.ivs) set.ivs = { hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31 }; if (!set.evs) set.evs = { hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0 }; // do this after setting set.evs because it's assumed to exist // after getStat is run var template = Tools.getTemplate(set.species); if (!template.exists) return 0; if (!set.level) set.level = 100; if (typeof set.ivs[stat] === 'undefined') set.ivs[stat] = 31; if (stat === 'hp') { if (template.baseStats['hp'] === 1) return 1; return Math.floor(Math.floor(2*template.baseStats['hp']+(set.ivs['hp']||0)+Math.floor((evOverride||set.evs['hp']||0)/4)+100)*set.level / 100 + 10); } var val = Math.floor(Math.floor(2*template.baseStats[stat]+(set.ivs[stat]||0)+Math.floor((evOverride||set.evs[stat]||0)/4))*set.level / 100 + 5); if (natureOverride) { val *= natureOverride; } else if (BattleNatures[set.nature] && BattleNatures[set.nature].plus === stat) { val *= 1.1; } else if (BattleNatures[set.nature] && BattleNatures[set.nature].minus === stat) { val *= 0.9; } return Math.floor(val); }, // 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) !== '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]; } }, destroy: function() { app.clearGlobalListeners(); Room.prototype.destroy.call(this); } }); var MovePopup = exports.MovePopup = Popup.extend({ initialize: function(data) { var buf = '
      '; this.i = data.i; this.team = data.team; for (var i=0; i Move here'; } buf += ' '+Tools.escapeHTML(set.name)+''; } if (i !== data.i && i !== data.i+1) { buf += '
    • '; } buf += '
    '; this.$el.html(buf); }, moveHere: function(i) { this.close(); i = +i; var movedSet = this.team.splice(this.i, 1)[0]; if (i > this.i) i--; this.team.splice(i, 0, movedSet); app.rooms['teambuilder'].save(); if (app.rooms['teambuilder'].curSet) { app.rooms['teambuilder'].curSetLoc = i; app.rooms['teambuilder'].update(); app.rooms['teambuilder'].updateChart(); } else { app.rooms['teambuilder'].update(); } } }); })(window, jQuery);