(function (exports, $) { // this is a useful global var teams; var TeambuilderRoom = exports.TeambuilderRoom = exports.Room.extend({ type: 'teambuilder', title: 'Teambuilder', initialize: function () { teams = Storage.teams; // left menu this.$el.addClass('ps-room-light').addClass('scrollable'); if (!Storage.whenTeamsLoaded.isLoaded) { Storage.whenTeamsLoaded(this.update, this); } this.update(); }, focus: function () { if (this.curTeam) { this.curTeam.iconCache = '!'; this.curTeam.gen = this.getGen(this.curTeam.format); Storage.activeSetList = this.curSetList; } }, blur: function () { if (this.saveFlag) { this.saveFlag = false; app.user.trigger('saveteams'); } }, events: { // team changes 'change input.teamnameedit': 'teamNameChange', 'click button.formatselect': 'selectFormat', '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', 'blur .chartinput': 'chartChange', // drag/drop 'click .team': 'edit', 'click .selectFolder': 'selectFolder', 'mouseover .team': 'mouseOverTeam', 'mouseout .team': 'mouseOutTeam', 'dragstart .team': 'dragStartTeam', 'dragend .team': 'dragEndTeam', 'dragenter .team': 'dragEnterTeam', 'dragenter .folder .selectFolder': 'dragEnterFolder', 'dragleave .folder .selectFolder': 'dragLeaveFolder', 'dragexit .folder .selectFolder': 'dragExitFolder', // 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](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 = ''; var team = this.curTeam; this.curTeam = null; Storage.activeSetList = this.curSetList = null; Storage.saveTeam(team); } 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, // curFolder will have '/' at the end if it's a folder, but // it will be alphanumeric (so guaranteed no '/') if it's a // format // Special values: // '' - show all // 'gen6' - show teams with no format // '/' - show teams with no folder curFolder: '', curFolderKeep: '', exportMode: false, update: function () { teams = Storage.teams; if (this.curTeam) { this.ignoreEVLimits = (this.curTeam.gen < 3); if (this.curSet) { return this.updateSetView(); } return this.updateTeamView(); } return this.updateTeamInterface(); }, /********************************************************* * Team list view *********************************************************/ deletedTeam: null, deletedTeamLoc: -1, updateTeamInterface: function () { this.deletedSet = null; this.deletedSetLoc = -1; var buf = ''; if (this.exportMode) { buf = '
'; buf += '
'; this.$el.html(buf); this.$('.teamedit textarea').focus().select(); return; } if (!Storage.whenTeamsLoaded.isLoaded) { if (!Storage.whenTeamsLoaded.isStalled) { buf = '

lol zarel this is a horrible teambuilder

'; buf += '

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

'; } else { buf = '

We\'re having some trouble loading teams securely.

'; buf += '

This is sometimes caused by antiviruses like Avast and BitDefender.

'; buf += '

If you\'re using Firefox and an antivirus: Your antivirus is trying to scan your teams, and a recent Firefox update doesn\'t let it. Turn off HTTPS scanning in your antivirus or uninstall your antivirus, and your teams will come back.

'; buf += '

You can use the teambuilder insecurely, but any teams you\'ve saved securely won\'t be there.

'; buf += '

'; } this.$el.html(buf); return; } // folderpane buf = '
'; buf += '
'; // teampane buf += '
'; buf += '
'; this.$el.html(buf); this.updateFolderList(); this.updateTeamList(); }, insecureUse: function () { Storage.whenTeamsLoaded.load(); this.updateTeamInterface(); }, updateFolderList: function () { var buf = '
'; buf += '
' : '">') + '
(all)
' + (!this.curFolder ? '
' : ''); var folderTable = {}; var folders = []; if (Storage.teams) for (var i = -2; i < Storage.teams.length; i++) { if (i >= 0) { var folder = Storage.teams[i].folder; if (folder && !((folder + '/') in folderTable)) { folders.push('Z' + folder); folderTable[folder + '/'] = 1; if (!('/' in folderTable)) { folders.push('Z~'); folderTable['/'] = 1; } } } var format; if (i === -2) { format = this.curFolderKeep; } else if (i === -1) { format = this.curFolder; } else { format = Storage.teams[i].format; if (!format) format = 'gen6'; } if (!format) continue; if (format in folderTable) continue; folderTable[format] = 1; if (format.slice(-1) === '/') { folders.push('Z' + (format.slice(0, -1) || '~')); if (!('/' in folderTable)) { folders.push('Z~'); folderTable['/'] = 1; } continue; } if (format === 'gen6') { folders.push('A~'); continue; } switch (format.slice(0, 4)) { case 'gen1': format = 'F' + format.slice(4); break; case 'gen2': format = 'E' + format.slice(4); break; case 'gen3': format = 'D' + format.slice(4); break; case 'gen4': format = 'C' + format.slice(4); break; case 'gen5': format = 'B' + format.slice(4); break; default: format = 'A' + format; break; } folders.push(format); } folders.sort(); var gen = ''; var formatFolderBuf = '
'; formatFolderBuf += '
(add format folder)
'; for (var i = 0; i < folders.length; i++) { var format = folders[i]; var newGen; switch (format.charAt(0)) { case 'F': newGen = '1'; break; case 'E': newGen = '2'; break; case 'D': newGen = '3'; break; case 'C': newGen = '4'; break; case 'B': newGen = '5'; break; case 'A': newGen = '6'; break; case 'Z': newGen = '/'; break; } if (gen !== newGen) { gen = newGen; if (gen === '/') { buf += formatFolderBuf; formatFolderBuf = ''; buf += '
'; buf += '

Folders

'; } else { buf += '

Gen ' + gen + '

'; } } if (gen === '/') { formatName = format.slice(1); format = formatName + '/'; if (formatName === '~') { formatName = '(uncategorized)'; format = '/'; } else { formatName = Tools.escapeHTML(formatName); } buf += '
' : '">') + '
' + formatName + '
' + (this.curFolder === format ? '
' : ''); continue; } var formatName = format.slice(1); if (formatName === '~') formatName = ''; if (newGen === '6' && formatName) { format = formatName; } else { format = 'gen' + newGen + formatName; } if (format === 'gen6') formatName = '(uncategorized)'; // folders are
s rather than
'; this.$('.folderpane').html(buf); }, updateTeamList: function (resetScroll) { var teams = Storage.teams; var buf = ''; // teampane buf += this.clipboardHTML(); var filterFormat = ''; var filterFolder = undefined; if (!this.curFolder) { buf += '

Hi

'; buf += '

Did you have a good day?

'; buf += '

'; buf += '

All teams

'; } else { if (this.curFolder.slice(-1) === '/') { filterFolder = this.curFolder.slice(0, -1); if (filterFolder) { buf += '

' + filterFolder + '

'; } else { buf += '

Teams not in any folders

'; } } else { filterFormat = this.curFolder; buf += '

' + filterFormat + '

'; } } var newButtonText = "New Team"; if (filterFolder) newButtonText = "New Team in folder"; if (filterFormat && filterFormat !== 'gen6') { newButtonText = "New " + Tools.escapeFormat(filterFormat) + " Team"; } buf += '

'; buf += ''; if (atLeastOne) { buf += '

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

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

'; buf += ''; buf += '

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

'; } else { buf += ''; } var $pane = this.$('.teampane'); $pane.html(buf); if (resetScroll) $pane.scrollTop(0); }, greeting: function (answer, button) { var buf = '

' + $(button).html() + '

'; if (answer === 'N') { buf += '

Aww, that\'s too bad. :( I hope playing on Pokémon Showdown today can help cheer you up!

'; } else if (answer === 'Y') { buf += '

Cool! I just added some pretty cool teambuilder features, so I\'m pretty happy, too. Did you know you can drag and drop teams to different format-folders? You can also drag and drop them to and from your computer (works best in Chrome).

'; buf += '

'; } else if (answer === 'W') { buf += '

Oh, I\'m Zarel! I made a Credits button for this...

'; buf += ''; buf += '

Isn\'t it pretty? Matches your background and everything. It used to be in the Main Menu but we had to get rid of it to save space.

'; buf += '

Speaking of, you should try .'; buf += '

'; } else if (answer === 'B') { buf += '

I paid good money for those icons! I need to get my money\'s worth!

'; buf += '

'; } else if (answer === 'WR') { buf += '

No, they were free. That just makes it easier to get my money\'s worth. Let\'s play rock paper scissors!

'; buf += '

'; } else if (answer[0] === 'R') { buf += '

I play laser, I win.

'; buf += '

'; } else if (answer === 'SP') { buf += '

Okay, sure. I warn you, I\'m using the same RNG that makes Stone Edge miss for you.

'; buf += '

'; } else if (answer === 'SP3') { buf += '

'; } else if (answer === 'SP5') { buf += '

'; } else if (answer[0] === 'P') { var rpsChart = { R: 'rock', P: 'paper', S: 'scissors', L: 'lizard', K: 'spock' }; var rpsWinChart = { SP: 'cuts', SL: 'decapitates', PR: 'covers', PK: 'disproves', RL: 'crushes', RS: 'crushes', LK: 'poisons', LP: 'eats', KS: 'smashes', KR: 'vaporizes' }; var my = ['R', 'P', 'S', 'L', 'K'][Math.floor(Math.random() * Number(answer[2]))]; var your = answer[1]; buf += '

I play ' + rpsChart[my] + '!

'; if ((my + your) in rpsWinChart) { buf += '

And ' + rpsChart[my] + ' ' + rpsWinChart[my + your] + ' ' + rpsChart[your] + ', so I win!

'; } else if ((your + my) in rpsWinChart) { buf += '

But ' + rpsChart[your] + ' ' + rpsWinChart[your + my] + ' ' + rpsChart[my] + ', so you win...

'; } else { buf += '

We played the same thing, so it\'s a tie.

'; } if (!this.rpsScores || !this.rpsScores.length) { this.rpsScores = ['pi', '$3.50', '9.80665 m/s2', '28°C', '百万点', '0.0000174', 'priceless MasterCard', '127.0.0.1', 'C−, see me after class']; } var score = this.rpsScores.splice(Math.floor(Math.random() * this.rpsScores.length), 1)[0]; buf += '

Score: ' + score + '

'; buf += '

'; } else if (answer === 'YC') { buf += '

Okay, then I play peace sign , everyone signs a peace treaty, ending the war and ushering in a new era of prosperity.

'; buf += '

'; } $(button).parent().replaceWith(buf); }, credits: function () { app.addPopup(CreditsPopup); }, background: function () { app.addPopup(CustomBackgroundPopup); }, selectFolder: function (format) { if (format && format.currentTarget) { var e = format; format = $(e.currentTarget).data('value'); e.preventDefault(); if (format === '+') { e.stopImmediatePropagation(); var self = this; app.addPopup(FormatPopup, {format: '', sourceEl: e.currentTarget, selectType: 'teambuilder', onselect: function (newFormat) { self.selectFolder(newFormat); }}); return; } if (format === '++') { e.stopImmediatePropagation(); var self = this; // app.addPopupPrompt("Folder name:", "Create folder", function (newFormat) { // self.selectFolder(newFormat + '/'); // }); app.addPopup(PromptPopup, {message: "Folder name:", button: "Create folder", sourceEl: e.currentTarget, callback: function (name) { name = $.trim(name); if (name.indexOf('/') >= 0 || name.indexOf('\\') >= 0) { app.addPopupMessage("Names can't contain slashes, since they're used as a folder separator."); name = name.replace(/[\\\/]/g, ''); } if (!name) return; self.selectFolder(name + '/'); }}); return; } } else { this.curFolderKeep = format; } this.curFolder = (format === 'all' ? '' : format); this.updateFolderList(); this.updateTeamList(true); }, renameFolder: function () { if (!this.curFolder) return; if (this.curFolder.slice(-1) !== '/') return; var oldFolder = this.curFolder.slice(0, -1); var self = this; app.addPopup(PromptPopup, {message: "Folder name:", button: "Rename folder", value: oldFolder, callback: function (name) { name = $.trim(name); if (name.indexOf('/') >= 0 || name.indexOf('\\') >= 0) { app.addPopupMessage("Names can't contain slashes, since they're used as a folder separator."); name = name.replace(/[\\\/]/g, ''); } if (!name) return; if (name === oldFolder) return; for (var i = 0; i < Storage.teams.length; i++) { var team = Storage.teams[i]; if (team.folder !== oldFolder) continue; team.folder = name; if (window.nodewebkit) Storage.saveTeam(team); } if (!window.nodewebkit) Storage.saveTeams(); self.selectFolder(name + '/'); }}); }, promptDeleteFolder: function () { app.addPopup(DeleteFolderPopup, {folder: this.curFolder, room: this}); }, deleteFolder: function (format, addName) { if (format.slice(-1) !== '/') return; var oldFolder = format.slice(0, -1); if (this.curFolderKeep === oldFolder) { this.curFolderKeep = ''; } for (var i = 0; i < Storage.teams.length; i++) { var team = Storage.teams[i]; if (team.folder !== oldFolder) continue; team.folder = ''; if (addName) team.name = oldFolder + ' ' + team.name; if (window.nodewebkit) Storage.saveTeam(team); } if (!window.nodewebkit) Storage.saveTeams(); this.selectFolder('/'); }, show: function () { Room.prototype.show.apply(this, arguments); var $teamwrapper = this.$('.teamwrapper'); var width = $(window).width(); if (!$teamwrapper.length) return; if (width < 640) { var scale = (width / 640); $teamwrapper.css('transform', 'scale(' + scale + ')'); $teamwrapper.addClass('scaled'); } else { $teamwrapper.css('transform', 'none'); $teamwrapper.removeClass('scaled'); } }, // 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 = '!'; this.curTeam.gen = this.getGen(this.curTeam.format); 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]; for (var room in app.rooms) { var selection = app.rooms[room].$('button.teamselect').val(); if (!selection || selection === 'random') continue; var obj = app.rooms[room].id === "" ? app.rooms[room] : app.rooms[room].tournamentBox; if (i < obj.curTeamIndex) { obj.curTeamIndex--; } else if (i === obj.curTeamIndex) { obj.curTeamIndex = -1; } } Storage.deleteTeam(this.deletedTeam); app.user.trigger('saveteams'); this.updateTeamList(); }, undoDelete: function () { if (this.deletedTeamLoc >= 0) { teams.splice(this.deletedTeamLoc, 0, this.deletedTeam); for (var room in app.rooms) { var selection = app.rooms[room].$('button.teamselect').val(); if (!selection || selection === 'random') continue; var obj = app.rooms[room].id === "" ? app.rooms[room] : app.rooms[room].tournamentBox; if (this.deletedTeamLoc < obj.curTeamIndex + 1) { obj.curTeamIndex++; } else if (obj.curTeamIndex === -1) { obj.curTeamIndex = this.deletedTeamLoc; } } var undeletedTeam = this.deletedTeam; this.deletedTeam = null; this.deletedTeamLoc = -1; Storage.saveTeam(undeletedTeam); app.user.trigger('saveteams'); this.update(); } }, saveBackup: function () { Storage.deleteAllTeams(); Storage.importTeam(this.$('.teamedit textarea').val(), true); teams = Storage.teams; Storage.saveAllTeams(); for (var room in app.rooms) { var selection = app.rooms[room].$('button.teamselect').val(); if (!selection || selection === 'random') continue; var obj = app.rooms[room].id === "" ? app.rooms[room] : app.rooms[room].tournamentBox; obj.curTeamIndex = 0; } this.back(); }, "new": function () { var format = this.curFolder; var folder = ''; if (format && format.charAt(format.length - 1) === '/') { folder = format.slice(0, -1); format = ''; } var newTeam = { name: 'Untitled ' + (teams.length + 1), format: format, team: '', folder: folder, iconCache: '' }; teams.push(newTeam); this.edit(teams.length - 1); }, newTop: function () { var format = this.curFolder; var folder = ''; if (format && format.charAt(format.length - 1) === '/') { folder = format.slice(0, -1); format = ''; } var newTeam = { name: 'Untitled ' + (teams.length + 1), format: format, team: '', folder: folder, iconCache: '' }; teams.unshift(newTeam); for (var room in app.rooms) { var selection = app.rooms[room].$('button.teamselect').val(); if (!selection || selection === 'random') continue; var obj = app.rooms[room].id === "" ? app.rooms[room] : app.rooms[room].tournamentBox; obj.curTeamIndex++; } 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 = 'copyMove'; dataTransfer.setData("text/plain", "Team " + e.currentTarget.dataset.value); var team = Storage.teams[e.currentTarget.dataset.value]; var filename = team.name; if (team.format) filename = '[' + team.format + '] ' + filename; filename = $.trim(filename).replace(/[\\\/]+/g, '') + '.txt'; var urlprefix = "data:text/plain;base64,"; if (document.location.protocol === 'https:') { // Chrome is dumb and doesn't support data URLs in HTTPS urlprefix = "https://play.pokemonshowdown.com/action.php?act=dlteam&team="; } var contents = Storage.exportTeam(team.team).replace(/\n/g, '\r\n'); var downloadurl = "text/plain:" + filename + ":" + urlprefix + encodeURIComponent(window.btoa(unescape(encodeURIComponent(contents)))); console.log(downloadurl); dataTransfer.setData("DownloadURL", downloadurl); app.dragging = e.currentTarget; app.draggingLoc = parseInt(e.currentTarget.dataset.value, 10); var elOffset = $(e.currentTarget).offset(); app.draggingOffsetX = e.originalEvent.pageX - elOffset.left; app.draggingOffsetY = e.originalEvent.pageY - elOffset.top; app.draggingRoom = this.id; this.finalOffset = null; setTimeout(function () { $(e.currentTarget).parent().addClass('dragging'); }, 0); }, dragEndTeam: function (e) { this.finishDrop(); }, finishDrop: function () { var teamEl = app.dragging; app.dragging = null; var originalLoc = parseInt(teamEl.dataset.value, 10); if (isNaN(originalLoc)) { throw new Error("drag failed"); } var newLoc = Math.floor(app.draggingLoc); if (app.draggingLoc < originalLoc) newLoc += 1; var team = Storage.teams[originalLoc]; var edited = false; if (newLoc !== originalLoc) { Storage.teams.splice(originalLoc, 1); Storage.teams.splice(newLoc, 0, team); for (var room in app.rooms) { var selection = app.rooms[room].$('button.teamselect').val(); if (!selection || selection === 'random') continue; var obj = app.rooms[room].id === "" ? app.rooms[room] : app.rooms[room].tournamentBox; if (originalLoc === obj.curTeamIndex) { obj.curTeamIndex = newLoc; } else if (originalLoc > obj.curTeamIndex && newLoc <= obj.curTeamIndex) { obj.curTeamIndex++; } else if (originalLoc < obj.curTeamIndex && newLoc >= obj.curTeamIndex) { obj.curTeamIndex--; } } edited = true; } // possibly half-works-around a hover issue in this.$('.teamlist').css('pointer-events', 'none'); $(teamEl).parent().removeClass('dragging'); var format = this.curFolder; if (app.draggingFolder) { var $folder = $(app.draggingFolder); app.draggingFolder = null; $folder.removeClass('active').prepend('+1'); format = $folder.data('value'); if (format.slice(-1) === '/') { team.folder = format.slice(0, -1); } else { team.format = format; } edited = true; this.updateTeamList(); } else { if (format.slice(-1) === '/') { team.folder = format.slice(0, -1); edited = true; } this.updateTeamList(); } if (edited) { Storage.saveTeam(team); app.user.trigger('saveteams'); } // We're going to try to animate the team settling into its new position if (this.finalOffset) { // event.pageY and event.pageX are buggy on literally every browser: // in Chrome: // event.pageX|pageY is the position of the bottom left corner of the draggable, instead // of the mouse position // in Safari: // window.innerHeight * 2 - window.outerHeight - event.pageY is the mouse position // No, I don't understand what's going on, either, but unsurprisingly this fails utterly // if the page is zoomed. // in Firefox: // event.pageX|pageY are straight-up unsupported // if you don't believe me, uncomment and see for yourself: // 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]); // Because of this, we're just going to steal the values from the drop event, where // everything is sane. var $newTeamEl = this.$('.team[data-value=' + newLoc + ']'); if (!$newTeamEl.length) return; var finalPos = $newTeamEl.offset(); $newTeamEl.css('transform', 'translate(' + (this.finalOffset[0] - finalPos.left) + 'px, ' + (this.finalOffset[1] - finalPos.top) + '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)'); }); } }, dragEnterTeam: function (e) { if (!app.dragging) return; var $draggingLi = $(app.dragging).parent(); this.dragLeaveFolder(); if (e.currentTarget === app.dragging) { e.preventDefault(); return; } var hoverLoc = parseInt(e.currentTarget.dataset.value, 10); if (app.draggingLoc > hoverLoc) { // dragging up $(e.currentTarget).parent().before($draggingLi); app.draggingLoc = parseInt(e.currentTarget.dataset.value, 10) - 0.5; } else { // dragging down $(e.currentTarget).parent().after($draggingLi); app.draggingLoc = parseInt(e.currentTarget.dataset.value, 10) + 0.5; } }, dragEnterFolder: function (e) { if (!app.dragging) return; this.dragLeaveFolder(); if (e.currentTarget === app.draggingFolder) { return; } var format = e.currentTarget.dataset.value; if (format === '+' || format === '++' || format === 'all' || format === this.curFolder) { return; } if (parseInt(app.dragging.dataset.value, 10) >= Storage.teams.length && format.slice(-1) !== '/') { // dragging a team file, already has a known format return; } app.draggingFolder = e.currentTarget; $(app.draggingFolder).addClass('active'); // amusing note: using .detach() instead of .hide() will make `dragend` not fire $(app.dragging).parent().hide(); }, dragLeaveFolder: function (e) { // sometimes there's a race condition and dragEnter happens before dragLeave if (e && e.currentTarget !== app.draggingFolder) return; if (!app.dragging || !app.draggingFolder) return; $(app.draggingFolder).removeClass('active'); app.draggingFolder = null; $(app.dragging).parent().show(); }, defaultDragEnterTeam: function (e) { var dataTransfer = e.originalEvent.dataTransfer; if (!dataTransfer) return; if (dataTransfer.types.indexOf && dataTransfer.types.indexOf('Files') === -1) return; if (dataTransfer.types.contains && !dataTransfer.types.contains('Files')) return; if (dataTransfer.files[0] && dataTransfer.files[0].name.slice(-4) !== '.txt') return; // We're dragging a file! It might be a team! if (app.curFolder && app.curFolder.slice(-1) !== '/') { this.selectFolder('all'); } this.$('.teamlist').append('
  • '); app.dragging = this.$('.dragging .team')[0]; app.draggingLoc = Storage.teams.length; app.draggingOffsetX = 180; app.draggingOffsetY = 25; app.draggingRoom = this.id; }, defaultDropTeam: function (e) { if (e.originalEvent.dataTransfer.files && e.originalEvent.dataTransfer.files[0]) { var file = e.originalEvent.dataTransfer.files[0]; var name = file.name; if (name.slice(-4) !== '.txt') { app.dragging = null; this.updateTeamList(); app.addPopupMessage("Your file is not a valid team. Team files are .txt files."); return; } var reader = new FileReader(); var self = this; reader.onload = function (e) { var team; try { team = Storage.packTeam(Storage.importTeam(e.target.result)); } catch (err) { app.addPopupMessage("Your file is not a valid team."); self.updateTeamList(); return; } var name = file.name; if (name.slice(name.length - 4).toLowerCase() === '.txt') { name = name.substr(0, name.length - 4); } var format = ''; var bracketIndex = name.indexOf(']'); if (bracketIndex >= 0) { format = name.substr(1, bracketIndex - 1); name = $.trim(name.substr(bracketIndex + 1)); } Storage.teams.push({ name: name, format: format, team: team, folder: '', iconCache: '' }); self.finishDrop(); }; reader.readAsText(file); } this.finalOffset = [e.originalEvent.pageX - app.draggingOffsetX, e.originalEvent.pageY - app.draggingOffsetY]; }, /********************************************************* * 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. '; buf += ''; buf += '
    4. '; } if (!this.curSetList.length) { buf += '
    5. you have no pokemon lol
    6. '; } for (i = 0; i < this.curSetList.length; i++) { if (this.curSetList.length < 6 && this.deletedSet && i === this.deletedSetLoc) { buf += '
    7. '; } buf += this.renderSet(this.curSetList[i], i); } if (this.deletedSet && i === this.deletedSetLoc) { buf += '
    8. '; } if (i === 0) { buf += '
    9. '; } if (i < 6) { buf += '
    10. '; } buf += '
    '; buf += '
    '; } this.$el.html('
    ' + buf + '
    '); this.$(".teamedit textarea").focus().select(); if ($(window).width() < 640) this.show(); }, renderSet: function (set, i) { var template = Tools.getTemplate(set.species); var buf = '
  • '; if (!set.species) { if (this.deletedSet) { buf += '
    '; } 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 += '
    '; if (this.curTeam.gen > 1) buf += '
    '; if (this.curTeam.gen > 2) 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(); }, validate: function () { var format = this.curTeam.format; if (!format) { app.addPopupMessage('You need to pick a format to validate your team.'); return; } if (window.BattleFormats && BattleFormats[this.curTeam.format] && BattleFormats[this.curTeam.format].hasBattleFormat) { format = BattleFormats[this.curTeam.format].battleFormat; } app.sendTeam(this.curTeam); app.send('/vtm ' + format); }, teamNameChange: function (e) { var name = ($.trim(e.currentTarget.value) || 'Untitled ' + (this.curTeamLoc + 1)); if (name.indexOf('/') >= 0 || name.indexOf('\\') >= 0) { app.addPopupMessage("Names can't contain slashes, since they're used as a folder separator."); name = name.replace(/[\\\/]/g, ''); } this.curTeam.name = name; e.currentTarget.value = name; this.save(); }, format: function (format, button) { if (!window.BattleFormats) { return; } var self = this; app.addPopup(FormatPopup, {format: format, sourceEl: button, selectType: 'teambuilder', onselect: function (newFormat) { self.changeFormat(newFormat); }}); }, changeFormat: function (format) { this.curTeam.format = format; this.curTeam.gen = this.getGen(this.curTeam.format); this.save(); if (this.curTeam.gen === 5 && !Tools.loadedSpriteData['bw']) Tools.loadSpriteData('bw'); this.update(); }, nicknameChange: function (e) { var i = +$(e.currentTarget).closest('li').attr('value'); var set = this.curSetList[i]; var name = $.trim(e.currentTarget.value); e.currentTarget.value = set.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.preventDefault(); 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() .select(); }, 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')); var curSet = Storage.importTeam(this.$('.pokemonedit').val())[0]; if (curSet) { this.curSet = curSet; this.curSetList[i] = curSet; } this.closePokemonImport(true); }, moveSet: function (i, button) { i = +($(button).closest('li').attr('value')); app.addPopup(MoveSetPopup, { 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 + '
    '); 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()); 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].species && this.curSetLoc !== this.curSetList.length - 1) { this.curSetList.splice(this.curSetList.length - 1, 1); } for (var i = 0; i < this.curSetList.length; i++) { var set = this.curSetList[i]; var pokemonicon = ''; if (!set.species) { 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; this.$('.setchart').attr('style', Tools.getTeambuilderSprite(set, this.curTeam.gen)); this.$('.pokemonicon-' + this.curSetLoc).css('background', Tools.getPokemonIcon(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'; var defaultEV = (this.curTeam.gen > 2 ? 0 : 252); for (var stat in stats) { if (stat === 'spd' && this.curTeam.gen === 1) continue; stats[stat] = this.getStat(stat, set); var ev = (set.evs[stat] === undefined ? defaultEV : set.evs[stat]); var evBuf = '' + (ev === defaultEV ? '' : ev) + ''; if (BattleNatures[set.nature] && BattleNatures[set.nature].plus === stat) { evBuf += '+'; } else if (BattleNatures[set.nature] && BattleNatures[set.nature].minus === stat) { evBuf += ''; } 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 += ' ' + evBuf + ''; } this.$('button[name=stats]').html(buf); if (this.curChartType !== 'stats') return; buf = '
    '; for (var stat in stats) { if (stat === 'spd' && this.curTeam.gen === 1) continue; buf += '
    ' + stats[stat] + '
    '; } this.$chart.find('.statscol').html(buf); buf = '
    '; var totalev = 0; for (var stat in stats) { if (stat === 'spd' && this.curTeam.gen === 1) continue; 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); } if (this.curTeam.gen > 2) buf += '
    Remaining:
    '; this.$chart.find('.graphcol').html(buf); if (this.curTeam.gen <= 2) return; var maxEv = 510; if (totalev <= maxEv) { this.$chart.find('.totalev').html('' + (totalev > (maxEv - 2) ? 0 : (maxEv - 2) - totalev) + ''); } else { this.$chart.find('.totalev').html('' + (maxEv - totalev) + ''); } this.$chart.find('select[name=nature]').val(set.nature || 'Serious'); }, curChartType: '', curChartName: '', 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; } var $inputEl = this.$('input[name=' + this.curChartName + ']'); var q = $inputEl.val(); 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) { if (this.search.find(q)) { if (this.search.q) this.$chart.find('a').first().addClass('hover'); } } } else if (q !== this.qInitial) { this.qInitial = undefined; if (this.search.find(q)) { if (this.search.q) this.$chart.find('a').first().addClass('hover'); } } }, selectPokemon: function (i) { i = +i; var set = this.curSetList[i]; if (set) { this.curSet = set; this.curSetLoc = i; if (!this.curChartName) { this.curChartName = 'details'; this.curChartType = 'details'; } if (this.curChartType in this.searchChartTypes) { this.update(); this.updateChart(true); this.$('input[name=' + this.curChartName + ']').select(); } else { this.update(); this.updateChart(true); } } }, stats: function (i, button) { if (!this.curSet) this.selectPokemon($(button).closest('li').val()); this.curChartName = 'stats'; this.curChartType = 'stats'; this.updateChart(); }, details: function (i, button) { if (!this.curSet) this.selectPokemon($(button).closest('li').val()); this.curChartName = 'details'; this.curChartType = 'details'; this.updateChart(); }, /********************************************************* * Set stat form *********************************************************/ plus: '', minus: '', smogdexLink: function (template) { var template = Tools.getTemplate(template); var format = this.curTeam && this.curTeam.format; var smogdexid = toId(template.baseSpecies); if (template.isNonstandard) { return 'http://www.smogon.com/cap/pokemon/strategies/' + smogdexid; } if (template.speciesid === 'meowstic') { smogdexid = 'meowstic-m'; } else if (template.speciesid === 'hoopaunbound') { smogdexid = 'hoopa-alt'; } else if (smogdexid === 'rotom' || smogdexid === 'deoxys' || smogdexid === 'kyurem' || smogdexid === 'giratina' || smogdexid === 'shaymin' || smogdexid === 'tornadus' || smogdexid === 'thundurus' || smogdexid === 'landorus' || smogdexid === 'pumpkaboo' || smogdexid === 'gourgeist' || smogdexid === 'arceus' || smogdexid === 'meowstic') { if (template.forme) smogdexid += '-' + toId(template.forme); } var generationNumber = 6; if (format.substr(0, 3) === 'gen') { var number = format.charAt(3); if ('1' <= number && number <= '5') { generationNumber = +number; format = format.substr(4); } } var generation = ['rb', 'gs', 'rs', 'dp', 'bw', 'xy'][generationNumber - 1]; if (format === 'battlespotdoubles') { smogdexid += '/vgc15'; } else if (format === 'doublesou' || format === 'doublesuu') { smogdexid += '/doubles'; } else if (format === 'ou' || format === 'uu' || format === 'ru' || format === 'nu' || format === 'pu' || format === 'lc') { smogdexid += '/' + format; } return 'http://smogon.com/dex/' + generation + '/pokemon/' + smogdexid + '/'; }, getBaseStats: function (template) { var baseStats = template.baseStats; var gen = this.curTeam.gen; if (gen < 6) { var overrideStats = BattleTeambuilderTable['gen' + gen].overrideStats[template.id]; if (overrideStats || gen === 1) { baseStats = { hp: template.baseStats.hp, atk: template.baseStats.atk, def: template.baseStats.def, spa: template.baseStats.spa, spd: template.baseStats.spd, spe: template.baseStats.spe }; } if (overrideStats) { if ('hp' in overrideStats) baseStats.hp = overrideStats.hp; if ('atk' in overrideStats) baseStats.atk = overrideStats.atk; if ('def' in overrideStats) baseStats.def = overrideStats.def; if ('spa' in overrideStats) baseStats.spa = overrideStats.spa; if ('spd' in overrideStats) baseStats.spd = overrideStats.spd; if ('spe' in overrideStats) baseStats.spe = overrideStats.spe; } if (gen === 1) baseStats.spd = 0; } return baseStats; }, updateStatForm: function (setGuessed) { var buf = ''; var set = this.curSet; var template = Tools.getTemplate(this.curSet.species); var baseStats = this.getBaseStats(template); 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) (Smogon analysis)

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

    '; //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 (this.curTeam.gen === 1) delete stats.spd; if (!set) return; var nature = BattleNatures[set.nature || 'Serious']; if (!nature) nature = {}; // label column buf += '
    '; buf += '
    '; if (this.curTeam.gen === 1) { buf += '
    '; } else { 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 += '
    '; } if (this.curTeam.gen > 2) 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; if (this.curTeam.gen > 2) { val = '' + (set.evs[i] || ''); } else { val = (set.evs[i] === undefined ? '252' : '' + 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 (this.curTeam.gen > 2) { var maxEv = 510; if (totalev <= maxEv) { buf += '
    ' + (totalev > (maxEv - 2) ? 0 : (maxEv - 2) - totalev) + '
    '; } else { buf += '
    ' + (maxEv - totalev) + '
    '; } } buf += '
    '; buf += '
    '; for (var i in stats) { if (i === 'spd' && this.curTeam.gen === 1) continue; buf += '
    '; } buf += '
    '; if (this.curTeam.gen > 2) { buf += '
    IVs
    '; if (!set.ivs) set.ivs = {}; for (var i in stats) { if (set.ivs[i] === undefined || isNaN(set.ivs[i])) set.ivs[i] = 31; var val = '' + (set.ivs[i]); buf += '
    '; } buf += '
    '; } else { buf += '
    DVs
    '; if (!set.ivs) set.ivs = {}; for (var i in stats) { if (set.ivs[i] === undefined || isNaN(set.ivs[i])) set.ivs[i] = 31; var val = '' + Math.floor(set.ivs[i] / 2); buf += '
    '; } buf += '
    '; } buf += '
    '; for (var i in stats) { buf += '
    ' + stats[i] + '
    '; } buf += '
    '; if (this.curTeam.gen > 2) { 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); 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 || isNaN(val)) val = 0; if (set.evs[stat] !== val || natureChange) { set.evs[stat] = val; if (this.curTeam.gen <= 2) { if (set.evs['hp'] === undefined) set.evs['hp'] = 252; if (set.evs['atk'] === undefined) set.evs['atk'] = 252; if (set.evs['def'] === undefined) set.evs['def'] = 252; if (set.evs['spa'] === undefined) set.evs['spa'] = 252; if (set.evs['spd'] === undefined) set.evs['spd'] = 252; if (set.evs['spe'] === undefined) set.evs['spe'] = 252; } this.setSlider(stat, val); this.updateStatGraph(); } } else { // IV var stat = inputName.substr(3); if (this.curTeam.gen <= 2) { val *= 2; if (val === 30) val = 31; } 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; var originalVal = val; var result = this.getStat(stat, set, val); while (val && this.getStat(stat, set, val - 4) == result) val -= 4; 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. if (val !== originalVal) slider.o.pointers[0].set(val); if (!set.evs) set.evs = {}; if (this.curTeam.gen <= 2) { if (set.evs['hp'] === undefined) set.evs['hp'] = 252; if (set.evs['atk'] === undefined) set.evs['atk'] = 252; if (set.evs['def'] === undefined) set.evs['def'] = 252; if (set.evs['spa'] === undefined) set.evs['spa'] = 252; if (set.evs['spd'] === undefined) set.evs['spd'] = 252; if (set.evs['spe'] === undefined) set.evs['spe'] = 252; } 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 += '
    '; if (this.curTeam.gen > 1) { 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) + ''; if (this.curTeam.gen > 1) { 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 *********************************************************/ chartTypes: { pokemon: 'pokemon', item: 'item', ability: 'ability', move1: 'move', move2: 'move', move3: 'move', move4: 'move', stats: 'stats', details: 'details' }, chartClick: function (e) { 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]; if (this.curChartType === 'move' && e.currentTarget.className === 'cur') { // clicked a move, remove it if we already have it var $emptyEl; var moves = []; for (var i = 1; i <= 4; i++) { var $inputEl = this.$('input[name=move' + i + ']'); var curVal = $inputEl.val(); if (curVal === val) { $inputEl.val(''); delete this.search.cur[toId(val)]; } else if (curVal) { moves.push(curVal); } } if (moves.length < 4) { this.$('input[name=move1]').val(moves[0] || ''); this.$('input[name=move2]').val(moves[1] || ''); this.$('input[name=move3]').val(moves[2] || ''); this.$('input[name=move4]').val(moves[3] || ''); this.$('input[name=move' + (1 + moves.length) + ']').focus(); return; } } 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)) { // enter/tab if (!(this.curChartType in this.searchChartTypes)) return; var $firstResult = this.$chart.find('a.hover'); if (!$firstResult.length) return; e.stopPropagation(); e.preventDefault(); 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 === 38) { // up e.preventDefault(); e.stopPropagation(); var $active = this.$chart.find('a.hover'); if (!$active.length) return this.$chart.find('a').first().addClass('hover'); var $li = $active.parent().prev(); while ($li[0] && $li[0].firstChild.tagName !== 'A') $li = $li.prev(); if ($li[0] && $li.children()[0]) { $active.removeClass('hover'); $active = $li.children(); $active.addClass('hover'); } } else if (e.keyCode === 40) { // down e.preventDefault(); e.stopPropagation(); var $active = this.$chart.find('a.hover'); if (!$active.length) return this.$chart.find('a').first().addClass('hover'); var $li = $active.parent().next(); while ($li[0] && $li[0].firstChild.tagName !== 'A') $li = $li.next(); if ($li[0] && $li.children()[0]) { $active.removeClass('hover'); $active = $li.children(); $active.addClass('hover'); } } 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.updateChart(); }, chartFocus: function (e) { var $target = $(e.currentTarget); var name = e.currentTarget.name; var type = this.chartTypes[name]; var wasIncomplete = false; if ($target.hasClass('incomplete')) { wasIncomplete = true; $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(false, wasIncomplete); }, chartChange: function (e) { var name = e.currentTarget.name; if (this.curChartName !== name) return; var id = toId(e.currentTarget.value); var val = ''; switch (name) { case 'pokemon': val = (id in BattlePokedex ? BattlePokedex[id].species : ''); 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); }, 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.$(this.$('input[name=ability]').length ? 'input[name=ability]' : 'input[name=move1]').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); if (selectNext) 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 = {}; if (this.curTeam.gen > 2) { for (var i in exports.BattleTypeChart[move.substr(13)].HPivs) { set.ivs[i] = exports.BattleTypeChart[move.substr(13)].HPivs[i]; } } else { for (var i in exports.BattleTypeChart[move.substr(13)].HPdvs) { set.ivs[i] = exports.BattleTypeChart[move.substr(13)].HPdvs[i] * 2; } } 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) { if (selectNext) this.$('input[name=item]').select(); return; } set.name = ""; set.species = val; if (set.level) delete set.level; if (this.curTeam && this.curTeam.format) { if (this.curTeam.format.substr(0, 10) === 'battlespot' || this.curTeam.format.substr(0, 3) === 'vgc') set.level = 50; if (this.curTeam.format.substr(0, 2) === 'lc' || this.curTeam.format === 'gen5lc' || this.curTeam.format === 'gen4lc') 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; if (this.curTeam.format !== 'balancedhackmons') { set.item = (template.requiredItem || ''); } else { set.item = ''; } set.ability = template.abilities['0']; set.moves = []; set.evs = {}; set.ivs = {}; set.nature = ''; this.updateSetTop(); if (selectNext) this.$(set.item || !this.$('input[name=item]').length ? (this.$('input[name=ability]').length ? 'input[name=ability]' : 'input[name=move1]') : '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 = this.getBaseStats(template); var itemid = toId(set.item); var abilityid = toId(set.ability); if (set.moves.length < 4 && template.id !== 'unown' && template.id !== 'ditto' && this.curTeam.gen > 2) return '?'; for (var i = 0, len = set.moves.length; i < len; i++) { var move = Tools.getMove(set.moves[i]); hasMove[move.id] = 1; if (move.category === 'Status') { if (move.id === 'batonpass' || move.id === 'healingwish' || move.id === 'lunardance') { moveCount['Support']++; } else if (move.id === 'naturepower') { moveCount['Special']++; } else if (move.id === 'protect' || move.id === 'detect' || move.id === 'spikyshield' || move.id === 'kingsshield') { moveCount['Stall']++; } else if (move.id === 'wish') { moveCount['Restoration']++; moveCount['Stall']++; moveCount['Support']++; } else if (move.heal) { moveCount['Restoration']++; moveCount['Stall']++; } else if (move.target === 'self') { if (move.id === 'agility' || move.id === 'rockpolish' || move.id === 'shellsmash' || move.id === 'growth' || move.id === 'workup') { moveCount['PhysicalSetup']++; moveCount['SpecialSetup']++; } else if (move.id === 'dragondance' || move.id === 'swordsdance' || move.id === 'coil' || move.id === 'bulkup' || move.id === 'curse' || move.id === 'bellydrum') { moveCount['PhysicalSetup']++; } else if (move.id === 'nastyplot' || move.id === 'tailglow' || move.id === 'quiverdance' || move.id === 'calmmind' || move.id === 'geomancy') { moveCount['SpecialSetup']++; } if (move.id === 'substitute') moveCount['Stall']++; moveCount['Setup']++; } else { if (move.id === 'toxic' || move.id === 'leechseed' || move.id === 'willowisp') moveCount['Stall']++; moveCount['Support']++; } } else if (move.id === 'counter' || move.id === 'endeavor' || move.id === 'metalburst' || move.id === 'mirrorcoat' || move.id === 'rapidspin') { moveCount['Support']++; } else if (move.id === 'nightshade' || move.id === 'seismictoss' || move.id === 'superfang' || move.id === 'foulplay' || move.id === 'finalgambit') { moveCount['Offense']++; } else if (move.id === 'fellstinger') { moveCount['PhysicalSetup']++; moveCount['Setup']++; } else { moveCount[move.category]++; moveCount['Offense']++; if (move.id === 'knockoff') moveCount['Support']++; if (move.id === 'scald' || move.id === 'voltswitch' || move.id === 'uturn') moveCount[move.category] -= 0.2; } } if (hasMove['batonpass']) moveCount['Support'] += moveCount['Setup']; moveCount['PhysicalAttack'] = moveCount['Physical']; moveCount['Physical'] += moveCount['PhysicalSetup']; moveCount['SpecialAttack'] = moveCount['Special']; moveCount['Special'] += moveCount['SpecialSetup']; if (hasMove['dragondance'] || hasMove['quiverdance']) moveCount['Ultrafast'] = 1; var isFast = (stats.spe > 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.atk > stats.spa && moveCount['Physical'] > 1) offenseBias = 'Physical'; 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 = this.getBaseStats(template); 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'; } } 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; if (this.curTeam.gen === 1) delete evs.spd; if (this.curTeam.gen < 3) return evs; } 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 (ev > 0 && 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 (ev > 0 && stat <= this.getStat(i, null, ev - 4, plusStat === i ? 1.1 : 1.0)) ev -= 4; 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; } var hp = evs['hp'] || 0; while (hp < 252 && evTotal < 508 && this.getStat('hp', null, hp, 1) % 2 !== hpParity) { hp += 4; evTotal += 4; } while (hp > 0 && this.getStat('hp', null, hp, 1) % 2 !== hpParity) { hp -= 4; evTotal -= 4; } while (hp > 0 && this.getStat('hp', null, hp - 4, 1) % 2 === hpParity) { hp -= 4; evTotal -= 4; } if (hp || evs['hp']) evs['hp'] = hp; } 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; i = null; if (!evs['atk'] && moveCount['PhysicalAttack'] >= 1) { i = 'atk'; } else if (!evs['spa'] && moveCount['SpecialAttack'] >= 1) { i = 'spa'; } else if (stats.hp == 1 && !evs['def']) { i = 'def'; } else if (stats.def === stats.spd && !evs['spd']) { i = 'spd'; } else if (!evs['spd']) { i = 'spd'; } else if (!evs['def']) { i = 'def'; } if (i) { ev = remaining; stat = this.getStat(i, null, ev); while (ev > 0 && stat === this.getStat(i, null, ev - 4)) ev -= 4; if (ev) evs[i] = ev; remaining -= ev; } if (remaining && !evs['spe']) { ev = remaining; stat = this.getStat('spe', null, ev); while (ev > 0 && stat === this.getStat('spe', null, ev - 4)) ev -= 4; if (ev) evs['spe'] = ev; } } } if (hasMove['gyroball'] || hasMove['trickroom']) { minusStat = 'spe'; } else if (!moveCount['PhysicalAttack']) { minusStat = 'atk'; } else if (moveCount['SpecialAttack'] < 1) { minusStat = 'spa'; } else if (moveCount['PhysicalAttack'] < 1) { minusStat = 'atk'; } 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 = {}; // 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; var baseStat = (this.getBaseStats(template))[stat]; var iv = (set.ivs[stat] || 0); if (this.curTeam.gen <= 2) iv &= 30; var ev = set.evs[stat]; if (evOverride !== undefined) ev = evOverride; if (ev === undefined) ev = (this.curTeam.gen > 2 ? 0 : 252); if (stat === 'hp') { if (baseStat === 1) return 1; return Math.floor(Math.floor(2 * baseStat + iv + Math.floor(ev / 4) + 100) * set.level / 100 + 10); } var val = Math.floor(Math.floor(2 * baseStat + iv + Math.floor(ev / 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 getGen: function (format) { format = '' + format; if (format.substr(0, 3) !== 'gen') return 6; return parseInt(format.substr(3, 1), 10) || 6; }, destroy: function () { app.clearGlobalListeners(); Room.prototype.destroy.call(this); } }); var MoveSetPopup = exports.MoveSetPopup = Popup.extend({ initialize: function (data) { var 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(); } } }); var DeleteFolderPopup = this.DeleteFolderPopup = Popup.extend({ type: 'semimodal', initialize: function (data) { this.room = data.room; this.folder = data.folder; var buf = '

    Remove "' + data.folder.slice(0, -1) + '"?

    '; buf += '

    '; this.$el.html(buf); }, submit: function (data) { this.room.deleteFolder(this.folder, !!this.$('input[name=addname]')[0].checked); this.close(); } }); })(window, jQuery);