(function (exports, $) { // this is a useful global var teams; 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', 'change .detailsform select': 'detailsChange', 'submit .detailsform': 'detailsChange', 'click .changeform' : 'altForm', 'click .altform' : 'altForm', // stats 'keyup .statform input.numform': 'statChange', 'input .statform input[type=number].numform': 'statChange', 'change select[name=nature]': 'natureChange', 'change select[name=ivspread]': 'ivSpreadChange', 'change .evslider': 'statSlided', 'input .evslider': 'statSlide', // teambuilder events 'click .utilichart a': 'chartClick', 'keydown .chartinput': 'chartKeydown', 'keyup .chartinput': 'chartKeyup', 'focus .chartinput': 'chartFocus', 'blur .chartinput': 'chartChange', 'keyup .searchinput': 'searchChange', // 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); }, back: function () { if (this.exportMode) { if (this.curTeam) { this.curTeam.team = Storage.packTeam(this.curSetList); Storage.saveTeam(this.curTeam); } this.exportMode = false; } else if (this.curSet) { this.curSet = null; Storage.saveTeam(this.curTeam); } 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 // 'gen8' - show teams with no format // '/' - show teams with no folder curFolder: '', curFolderKeep: '', curSearchVal: '', exportMode: false, update: function () { teams = Storage.teams; if (this.curTeam) { this.ignoreEVLimits = (this.curTeam.gen < 3 || this.curTeam.format === 'gen7balancedhackmons' || this.curTeam.format === 'gen8metronomebattle' || this.curTeam.format.endsWith('norestrictions')); 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) { if (this.curFolder) { buf = '
'; buf += '
'; } else { buf = '
'; buf += '
'; } this.$el.html(buf); this.$('.teamedit textarea').focus().select(); return; } if (!Storage.whenTeamsLoaded.isLoaded) { if (Storage.whenTeamsLoaded.error === 'stalled') { 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 += '

'; } else if (Storage.whenTeamsLoaded.error) { buf = '

We got an error trying to load teams: ' + Storage.whenTeamsLoaded.error.message + '.

'; buf += '

This might be because you didn\'t give us permission to load teams: on macOS, this is in System Preferences → Security & Privacy → Privacy → Files and Folders → Pokemon Showdown

'; } else { buf = '

lol zarel this is a horrible teambuilder

'; buf += '

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

'; } 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 = 'gen8'; } 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 === 'gen8') { folders.push('B~'); continue; } switch (format.slice(0, 4)) { case 'gen1': format = 'I' + format.slice(4); break; case 'gen2': format = 'H' + format.slice(4); break; case 'gen3': format = 'G' + format.slice(4); break; case 'gen4': format = 'F' + format.slice(4); break; case 'gen5': format = 'E' + format.slice(4); break; case 'gen6': format = 'D' + format.slice(4); break; case 'gen7': format = 'C' + format.slice(4); break; case 'gen8': format = 'B' + format.slice(4); break; case 'gen9': format = 'A' + format.slice(4); break; default: format = 'X' + 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 'I': newGen = '1'; break; case 'H': newGen = '2'; break; case 'G': newGen = '3'; break; case 'F': newGen = '4'; break; case 'E': newGen = '5'; break; case 'D': newGen = '6'; break; case 'C': newGen = '7'; break; case 'B': newGen = '8'; break; case 'A': newGen = '9'; break; case 'X': newGen = 'X'; break; case 'Z': newGen = '/'; break; } if (gen !== newGen) { gen = newGen; if (gen === '/') { buf += formatFolderBuf; formatFolderBuf = ''; buf += '
'; buf += '

Folders

'; } else if (gen === 'X') { buf += '

???

'; } else { buf += '

Gen ' + gen + '

'; } } var formatName; if (gen === '/') { formatName = format.slice(1); format = formatName + '/'; if (formatName === '~') { formatName = '(uncategorized)'; format = '/'; } else { formatName = BattleLog.escapeHTML(formatName); } buf += '
' : '">') + '
' + formatName + '
' + (this.curFolder === format ? '
' : ''); continue; } formatName = format.slice(1); if (formatName === '~') formatName = ''; format = 'gen' + newGen + formatName; if (format.length === 4) 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 = ''; // filterFolder === undefined: show teams in any folder // filterFolder === '': show only teams that don't have a folder var filterFolder; 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 !== 'gen8') { newButtonText = "New " + BattleLog.escapeFormat(filterFormat) + " Team"; } buf += '

' + '

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

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

Clearing your cookies (specifically, localStorage) will delete your teams. Browsers sometimes randomly clear cookies - you should back up your teams or use the desktop client if you want to make sure you don\'t lose them.

'; 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.

'; var self = this; if (navigator.storage && navigator.storage.persisted) { navigator.storage.persisted().then(function (state) { self.updatePersistence(state); }); } } else { buf += ''; } var $pane = this.$('.teampane'); $pane.html(buf); if (resetScroll) { $pane.scrollTop(0); } else if (this.teamScrollPos) { $pane.scrollTop(this.teamScrollPos); this.teamScrollPos = 0; } //reset focus to searchbar var teamSearchBar = this.$("#teamSearchBar"); var strLength = teamSearchBar.val().length; if (strLength) { teamSearchBar.focus(); teamSearchBar[0].setSelectionRange(strLength, strLength); } }, updatePersistence: function (state) { if (state) { this.$('.storage-warning').html(''); return; } }, 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); }, 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) { this.teamScrollPos = this.$('.teampane').scrollTop(); 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 (atTop) { var newTeam = this.createTeam(); teams.push(newTeam); this.edit(teams.length - 1); }, newTop: function (i) { var newTeam = this.createTeam(i ? teams[i] : null); 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); }, createTeam: function (orig) { var newTeam; if (orig) { newTeam = { name: 'Copy of ' + orig.name, format: orig.format, team: orig.team, folder: orig.folder, iconCache: '' }; } else { var format = this.curFolder || 'gen8'; var folder = ''; if (format && format.charAt(format.length - 1) === '/') { folder = format.slice(0, -1); format = 'gen8'; } newTeam = { name: 'Untitled ' + (teams.length + 1), format: format, team: '', folder: folder, iconCache: '' }; } // work around Opera 42-45 crashing when persist() is called if (navigator.storage && navigator.storage.persist && !/ OPR\/4[0-5]/.test(navigator.userAgent)) { var self = this; navigator.storage.persist().then(function (state) { self.updatePersistence(state); }); } return newTeam; }, "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(); }, pokepasteExport: function () { document.getElementById("pasteData").value = Storage.exportTeam(this.curSetList); document.getElementById("pasteTitle").value = this.curTeam.name; document.getElementById("pasteAuthor").value = app.user.get('name'); document.getElementById("pokepasteForm").submit(); }, // 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 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://" + Config.routes.client + "/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.draggingRoom = this.id; 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; 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'); if (app.draggingFolder) { var $folder = $(app.draggingFolder); app.draggingFolder = null; var $plusOneFolder = $folder.find('.plusonefolder'); $folder.removeClass('active'); if (!$plusOneFolder.length) { $folder.prepend('+1'); } else { var count = Number($plusOneFolder.text().substr(1)) + 1; $plusOneFolder.text('+' + count); } var format = $folder.data('value'); if (format.slice(-1) === '/') { team.folder = format.slice(0, -1); } else { team.format = format; } 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.draggingRoom = this.id; app.draggingLoc = Storage.teams.length; app.draggingOffsetX = 180; app.draggingOffsetY = 25; }, 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); if (format && format.slice(0, 3) !== 'gen') format = 'gen6' + format; 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 += '
    '; 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); } var isGenericFormat = function (formatName) { if (!formatName) return true; if (/^gen\d+$/.test(formatName)) return true; return false; }; if (exports.BattleFormats) { buf += '
    3. '; buf += ''; var btnClass = 'button' + (!this.curSetList.length ? ' disabled' : ''); 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 += '
    '; buf += ''; buf += ''; buf += ''; buf += '
    '; buf += '
    '; } this.$el.html('
    ' + buf + '
    '); this.$(".teamedit textarea").focus().select(); if ($(window).width() < 640) this.show(); }, renderSet: function (set, i) { var species = Dex.getSpecies(set.species); var isLetsGo = this.curTeam.format.includes('letsgo'); var isNatDex = this.curTeam.format.includes('nationaldex'); var buf = '
  • '; if (!set.species) { if (this.deletedSet) { buf += '
    '; } buf += '
    '; buf += '
    '; buf += '
  • '; return buf; } buf += '
    '; buf += '
    '; buf += ''; buf += '
    '; buf += '
    '; // icon buf += '
    '; if (species.cosmeticFormes) { buf += '
    '; } else { buf += '
    '; } buf += '
    '; // details buf += '
    '; buf += '
    '; // item/type icons buf += '
    '; buf += '
    '; var itemicon = ''; if (set.item) { var item = Dex.getItem(set.item); itemicon = ''; } buf += itemicon; buf += '
    '; buf += '
    '; var types = species.types; var table = (this.curTeam.gen < 7 ? BattleTeambuilderTable['gen' + this.curTeam.gen] : null); if (table && species.id in table.overrideType) types = table.overrideType[species.id].split('/'); if (types) { for (var i = 0; i < types.length; i++) buf += Dex.getTypeIcon(types[i]); } buf += '
    '; buf += '
    '; // if (this.curTeam.gen > 1 && !isLetsGo) buf += '
    '; if (this.curTeam.gen > 1) buf += '
    '; if (this.curTeam.gen > 2 && !isLetsGo) buf += '
    '; buf += '
    '; // moves if (!set.moves) set.moves = []; buf += '
    '; buf += '
    '; buf += '
    '; buf += '
    '; buf += '
    '; buf += '
    '; // stats buf += '
    '; buf += '
    '; return buf; }, saveImport: function () { var text = this.$('.teamedit textarea').val(); var url = this.importableUrl(text); if (url) { this.$('.teamedit textarea, .teamedit .savebutton').attr('disabled', true); var self = this; $.ajax({ type: 'GET', url: url, success: function (data) { Storage.activeSetList = self.curSetList = Storage.importTeam(data); self.$('.teamedit textarea, .teamedit .savebutton').attr('disabled', null); self.back(); }, error: function () { app.addPopupMessage("Could not fetch a team from this URL. Make sure you copied the full link, or paste the team in by hand."); self.$('.teamedit textarea, .teamedit .savebutton').attr('disabled', null); } }); } else { Storage.activeSetList = this.curSetList = Storage.importTeam(text); this.back(); } }, importableUrl: function (value) { var match = value.match(/^https?:\/\/(pokepast\.es|gist\.github(?:usercontent)?\.com)\/(.*)\s*$/); if (!match) return; var host = match[1]; var path = match[2]; switch (host) { case 'pokepast.es': return 'https://pokepast.es/' + path.replace(/\/.*/, '') + '/raw'; default: // gist var split = path.split('/'); return split.length < 2 ? undefined : 'https://gist.githubusercontent.com/' + split[0] + '/' + split[1] + '/raw'; } }, 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; if (this.curTeam) { Storage.saveTeam(this.curTeam); } else { Storage.saveTeams(); } }, validate: function () { var format = this.curTeam.format || 'gen7anythinggoes'; if (!this.curSetList.length) { app.addPopupMessage("You need at least one Pokémon to validate."); return; } if (window.BattleFormats && BattleFormats[format] && BattleFormats[format].battleFormat) { format = BattleFormats[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, ''); } if (name.indexOf('|') >= 0) { app.addPopupMessage("Names can't contain the character |, since they're used for storing teams."); 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 && !Dex.loadedSpriteData['bw']) Dex.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).replace(/\|/g, ''); 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 species = Dex.getSpecies(res.species); buf += '
    '; buf += '
    '; buf += '' + (species.name === species.baseSpecies ? BattleLog.escapeHTML(species.name) : (BattleLog.escapeHTML(species.baseSpecies) + '-' + BattleLog.escapeHTML(species.name.substr(species.baseSpecies.length + 1)) + '')) + '
    '; buf += '
    ' + (BattleLog.escapeHTML(res.ability) || 'No ability') + '
    ' + (BattleLog.escapeHTML(res.item) || 'No item') + '
    '; buf += '
    '; for (var j = 0; j < 4; j++) { if (!(j & 1)) { buf += ''; } buf += (BattleLog.escapeHTML(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() * 34}, 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: 32}, 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(); this.getSmogonSets(); }, getSmogonSets: function () { this.$('.teambuilder-pokemon-import .teambuilder-import-smogon-sets').empty(); var format = this.curTeam.format; // If we don't have a specific format, don't try and guess which sets to use. if (format.match(/gen\d$/)) return; var self = this; this.smogonSets = this.smogonSets || {}; if (this.smogonSets[format] !== undefined) { this.importSetButtons(); return; } // We fetch this as 'text' and JSON.parse it ourserves in order to have consistent behavior // between the localdev CORS helper and the real jQuery.get function, which would already parse // this into an object based on the content-type header. $.get('https://' + Config.routes.client + '/data/sets/' + format + '.json', {}, function (data) { try { self.smogonSets[format] = JSON.parse(data); } catch (e) { // An error occured. Mark this as false, so that we don't try to reimport sets for this format // in the future. self.smogonSets[format] = false; } self.importSetButtons(); }, 'text'); }, importSetButtons: function () { var formatSets = this.smogonSets[this.curTeam.format]; var species = this.curSet.species; var $setDiv = this.$('.teambuilder-pokemon-import .teambuilder-import-smogon-sets'); $setDiv.empty(); if (!formatSets) return; var sets = $.extend({}, formatSets['dex'][species], formatSets['stats'][species]); $setDiv.text('Sample sets: '); for (var set in sets) { $setDiv.append(''); } $setDiv.append(' (Smogon analysis)'); }, importSmogonSet: function (i, button) { var formatSets = this.smogonSets[this.curTeam.format]; var species = this.curSet.species; var setName = this.$(button).text(); var smogonSet = formatSets['dex'][species][setName] || formatSets['stats'][species][setName]; var curSet = $.extend({}, this.curSet, smogonSet); var text = Storage.exportTeam([curSet]); this.$('.teambuilder-pokemon-import .pokemonedit').val(text); }, 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 += '
    '; 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', Dex.getTeambuilderSprite(set, this.curTeam.gen)); this.$('.pokemonicon-' + this.curSetLoc).css('background', Dex.getPokemonIcon(set).substr(11)); var item = Dex.getItem(set.item); if (item.id) { this.$('.setcol-details .itemicon').css('background', Dex.getItemIcon(item).substr(11)); } else { this.$('.setcol-details .itemicon').css('background', 'none'); } this.updateStatGraph(); }, updateStatGraph: function () { var set = this.curSet; if (!set) return; var stats = {hp:'', atk:'', def:'', spa:'', spd:'', spe:''}; var supportsEVs = !this.curTeam.format.includes('letsgo'); // stat cell var buf = ' ' + (supportsEVs ? 'EV' : 'AV') + ''; 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 && supportsEVs) buf += '
    Remaining:
    '; this.$chart.find('.graphcol').html(buf); if (this.curTeam.gen <= 2) return; if (supportsEVs) { 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; 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 = {}; cur[toID(q)] = 1; // make sure selected one is first 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; } if (type !== this.search.qType) { this.$chart.scrollTop(0); } this.search.$inputEl = $inputEl; this.search.setType(type, this.curTeam.format || 'gen8', 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 (s) { var species = Dex.getSpecies(s); var format = this.curTeam && this.curTeam.format; var smogdexid = toID(species.baseSpecies); if (species.id === 'meowstic') { smogdexid = 'meowstic-m'; } else if (species.forme) { switch (species.baseSpecies) { case 'Alcremie': case 'Basculin': case 'Burmy': case 'Castform': case 'Cherrim': case 'Deerling': case 'Flabebe': case 'Floette': case 'Florges': case 'Furfrou': case 'Gastrodon': case 'Genesect': case 'Keldeo': case 'Mimikyu': case 'Minior': case 'Pikachu': case 'Polteageist': case 'Sawsbuck': case 'Shellos': case 'Sinistea': case 'Vivillon': break; default: smogdexid += '-' + toID(species.forme); break; } } var generationNumber = 8; if (format.substr(0, 3) === 'gen') { var number = format.charAt(3); if ('1' <= number && number <= '6') { generationNumber = +number; format = format.substr(4); } } var generation = ['rb', 'gs', 'rs', 'dp', 'bw', 'xy', 'sm', 'ss'][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' || format === 'monotype' || format === 'mixandmega' || format === 'nfe' || format === 'nationaldex' || format === 'stabmons' || format === '1v1' || format === 'almostanyability') { smogdexid += '/' + format; } else if (format === 'balancedhackmons') { smogdexid += 'bh'; } else if (format === 'nationaldexag' || format === 'anythinggoes') { smogdexid += 'ag'; } return 'http://smogon.com/dex/' + generation + '/pokemon/' + smogdexid + '/'; }, updateStatForm: function (setGuessed) { var buf = ''; var set = this.curSet; var species = Dex.forGen(this.curTeam.gen).getSpecies(this.curSet.species); var baseStats = species.baseStats; buf += '

    EVs

    '; buf += '
    '; var guess = new BattleStatGuesser(this.curTeam.format).guess(set); var role = guess.role; var guessedEVs = guess.evs; var guessedPlus = guess.plusStat; var guessedMinus = guess.minusStat; buf += '

    Guessed spread:'; if (role === '?') { buf += ' (Please choose 4 moves to get a guessed spread) (Smogon analysis)

    '; } else { buf += ' (Smogon analysis)

    '; // buf += ' (' + role + ' | bulk: phys ' + Math.round(guess.moveCount.physicalBulk/1000) + ' + spec ' + Math.round(guess.moveCount.specialBulk/1000) + ' = ' + Math.round(guess.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 = {}; var supportsEVs = !this.curTeam.format.includes('letsgo'); // var supportsAVs = !supportsEVs && this.curTeam.format.endsWith('norestrictions'); var defaultEV = this.curTeam.gen <= 2 ? 252 : 0; var maxEV = supportsEVs ? 252 : 200; var stepEV = supportsEVs ? 4 : 1; // 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 && supportsEVs) buf += '
    Remaining:
    '; buf += '
    '; buf += '
    ' + (supportsEVs ? 'EVs' : 'AVs') + '
    '; var totalev = 0; this.plus = ''; this.minus = ''; for (var i in stats) { var val; val = '' + ((set.evs[i] === undefined ? defaultEV : 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 && supportsEVs) { var maxTotalEVs = 510; if (totalev <= maxTotalEVs) { buf += '
    ' + (totalev > (maxTotalEVs - 2) ? 0 : (maxTotalEVs - 2) - totalev) + '
    '; } else { buf += '
    ' + (maxTotalEVs - 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 += '
    '; } var hpType = ''; if (set.moves) { for (var i = 0; i < set.moves.length; i++) { var moveid = toID(set.moves[i]); if (moveid.slice(0, 11) === 'hiddenpower') { hpType = moveid.slice(11); } } } if (hpType && !this.canHyperTrain(set)) { var hpIVs; switch (hpType) { case 'dark': hpIVs = ['111111']; break; case 'dragon': hpIVs = ['011111', '101111', '110111']; break; case 'ice': hpIVs = ['010111', '100111', '111110']; break; case 'psychic': hpIVs = ['011110', '101110', '110110']; break; case 'electric': hpIVs = ['010110', '100110', '111011']; break; case 'grass': hpIVs = ['011011', '101011', '110011']; break; case 'water': hpIVs = ['100011', '111010']; break; case 'fire': hpIVs = ['101010', '110010']; break; case 'steel': hpIVs = ['100010', '111101']; break; case 'ghost': hpIVs = ['101101', '110101']; break; case 'bug': hpIVs = ['100101', '111100', '101100']; break; case 'rock': hpIVs = ['001100', '110100', '100100']; break; case 'ground': hpIVs = ['000100', '111001', '101001']; break; case 'poison': hpIVs = ['001001', '110001', '100001']; break; case 'flying': hpIVs = ['000001', '111000', '101000']; break; case 'fighting': hpIVs = ['001000', '110000', '100000']; break; } buf += '
    '; } else { 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); }, setStatFormGuesses: function () { this.updateStatForm(true); }, setSlider: function (stat, val) { this.$chart.find('input[name=evslider-' + stat + ']').val(val || 0); }, 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 supportsEVs = !this.curTeam.format.includes('letsgo'); var supportsAVs = !supportsEVs && this.curTeam.format.endsWith('norestrictions'); 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.ignoreEVLimits) { var evNum = supportsEVs ? 252 : supportsAVs ? 200 : 0; if (set.evs['hp'] === undefined) set.evs['hp'] = evNum; if (set.evs['atk'] === undefined) set.evs['atk'] = evNum; if (set.evs['def'] === undefined) set.evs['def'] = evNum; if (set.evs['spa'] === undefined) set.evs['spa'] = evNum; if (set.evs['spd'] === undefined) set.evs['spd'] = evNum; if (set.evs['spe'] === undefined) set.evs['spe'] = evNum; } 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.updateIVs(); this.updateStatGraph(); } } this.save(); }, updateIVs: function () { var set = this.curSet; if (!set.moves || this.canHyperTrain(set)) return; var hasHiddenPower = false; for (var i = 0; i < set.moves.length; i++) { if (toID(set.moves[i]).slice(0, 11) === 'hiddenpower') { hasHiddenPower = true; break; } } if (!hasHiddenPower) return; var hpTypes = ['Fighting', 'Flying', 'Poison', 'Ground', 'Rock', 'Bug', 'Ghost', 'Steel', 'Fire', 'Water', 'Grass', 'Electric', 'Psychic', 'Ice', 'Dragon', 'Dark']; var hpType; if (this.curTeam.gen <= 2) { var hpDV = Math.floor(set.ivs.hp / 2); var atkDV = Math.floor(set.ivs.atk / 2); var defDV = Math.floor(set.ivs.def / 2); var speDV = Math.floor(set.ivs.spe / 2); var spcDV = Math.floor(set.ivs.spa / 2); hpType = hpTypes[4 * (atkDV % 4) + (defDV % 4)]; var expectedHpDV = (atkDV % 2) * 8 + (defDV % 2) * 4 + (speDV % 2) * 2 + (spcDV % 2); if (expectedHpDV !== hpDV) { set.ivs.hp = expectedHpDV * 2; if (set.ivs.hp === 30) set.ivs.hp = 31; this.$chart.find('input[name=iv-hp]').val(expectedHpDV); } } else { var hpTypeX = 0; var i = 1; var stats = {hp: 31, atk: 31, def: 31, spe: 31, spa: 31, spd: 31}; for (var s in stats) { if (set.ivs[s] === undefined) set.ivs[s] = 31; hpTypeX += i * (set.ivs[s] % 2); i *= 2; } hpType = hpTypes[Math.floor(hpTypeX * 15 / 63)]; } for (var i = 0; i < set.moves.length; i++) { if (toID(set.moves[i]).slice(0, 11) === 'hiddenpower') { set.moves[i] = "Hidden Power " + hpType; if (i < 4) this.$('input[name=move' + (i + 1) + ']').val("Hidden Power " + hpType); } } }, statSlide: function (e) { var slider = e.currentTarget; var stat = slider.name.substr(9); var set = this.curSet; if (!set) return; var val = +slider.value; var originalVal = val; var result = this.getStat(stat, set, val); var supportsEVs = !this.curTeam.format.includes('letsgo'); var supportsAVs = !supportsEVs && this.curTeam.format.endsWith('norestrictions'); if (supportsEVs) { while (val > 0 && this.getStat(stat, set, val - 4) == result) val -= 4; } if (supportsEVs && !this.ignoreEVLimits && set.evs) { var total = 0; for (var i in set.evs) { total += (i === stat ? val : set.evs[i]); } var totalLimit = 508; var limit = 252; if (total > totalLimit && val - total + totalLimit >= 0) { // don't allow dragging beyond 508 EVs val = val - total + totalLimit; // make sure val is a legal value val = +val; if (!val || val <= 0) val = 0; if (val > limit) val = limit; } } // Don't try this at home. // I am a trained professional. if (val !== originalVal) slider.value = val; if (!set.evs) set.evs = {}; if (this.ignoreEVLimits) { var evNum = supportsEVs ? 252 : supportsAVs ? 200 : 0; if (set.evs['hp'] === undefined) set.evs['hp'] = evNum; if (set.evs['atk'] === undefined) set.evs['atk'] = evNum; if (set.evs['def'] === undefined) set.evs['def'] = evNum; if (set.evs['spa'] === undefined) set.evs['spa'] = evNum; if (set.evs['spd'] === undefined) set.evs['spd'] = evNum; if (set.evs['spe'] === undefined) set.evs['spe'] = evNum; } set.evs[stat] = val; val = '' + (val || '') + (this.plus === stat ? '+' : '') + (this.minus === stat ? '-' : ''); this.$('input[name=stat-' + stat + ']').val(val); this.updateStatGraph(); }, statSlided: function (e) { this.statSlide(e); this.save(); }, 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(); }, ivSpreadChange: function (e) { var set = this.curSet; if (!set) return; var spread = e.currentTarget.value.split('/'); if (!set.ivs) set.ivs = {}; if (spread.length !== 6) return; var stats = ['hp', 'atk', 'def', 'spa', 'spd', 'spe']; for (var i = 0; i < 6; i++) { this.$chart.find('input[name=iv-' + stats[i] + ']').val(spread[i]); set.ivs[stats[i]] = parseInt(spread[i], 10); } $(e.currentTarget).val(''); this.save(); this.updateStatGraph(); }, /********************************************************* * Set details form *********************************************************/ updateDetailsForm: function () { var buf = ''; var set = this.curSet; var isLetsGo = this.curTeam.format.includes('letsgo'); var isNatDex = this.curTeam.gen === 8 && this.curTeam.format.includes('nationaldex'); var species = Dex.getSpecies(set.species); if (!set) return; buf += '

    Details

    '; buf += '
    '; buf += '
    '; if (this.curTeam.gen > 1) { buf += '
    '; if (species.gender && this.curTeam.format.indexOf('hackmons') < 0) { var genderTable = {'M': "Male", 'F': "Female", 'N': "Genderless"}; buf += genderTable[species.gender]; } else { buf += ' '; buf += ' '; if (this.curTeam.format.indexOf('hackmons') < 0) { buf += ''; } else { buf += ''; } } buf += '
    '; if (isLetsGo) { buf += '
    '; } else { if (this.curTeam.gen < 8 || isNatDex) buf += '
    '; } buf += '
    '; buf += ' '; buf += ''; buf += '
    '; } if (this.curTeam.gen > 2) { buf += ''; } if (!isLetsGo && (this.curTeam.gen === 7 || isNatDex)) { buf += '
    '; } buf += '
    '; if (species.cosmeticFormes) { buf += ''; } this.$chart.html(buf); }, detailsChange: function (e) { e.preventDefault(); e.stopPropagation(); var set = this.curSet; if (!set) return; var isLetsGo = this.curTeam.format.includes('letsgo'); var isNatDex = this.curTeam.format.includes('nationaldex'); // 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 (isNaN(happiness)) happiness = 255; 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; } // gender var gender = this.$chart.find('input[name=gender]:checked').val(); if (gender === 'M' || gender === 'F') { set.gender = gender; } else { delete set.gender; } // pokeball var pokeball = this.$chart.find('select[name=pokeball]').val(); if (pokeball && Dex.forGen(this.curTeam.gen).getItem(pokeball).isPokeball) { set.pokeball = pokeball; } else { delete set.pokeball; } // HP type var hpType = this.$chart.find('select[name=hptype]').val(); if (hpType && exports.BattleTypeChart[hpType]) { set.hpType = hpType; } else { delete set.hpType; } // 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'] + ''; if (isLetsGo) { buf += '70'; } else { if (this.curTeam.gen < 8 || isNatDex) buf += '' + (typeof set.happiness === 'number' ? set.happiness : 255) + ''; } buf += '' + (set.shiny ? 'Yes' : 'No') + ''; if (!isLetsGo && (this.curTeam.gen < 8 || isNatDex)) buf += '' + (set.hpType || 'Dark') + ''; } this.$('button[name=details]').html(buf); this.save(); this.updatePokemonSprite(); }, altForm: function (e) { var set = this.curSet; var i = 0; if (!set) { i = +$(e.currentTarget).closest('li').attr('value'); set = this.curSetList[i]; } app.addPopup(AltFormPopup, {curSet: set, index: i, room: this}); }, /********************************************************* * 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 entry = $(e.currentTarget).data('entry'); var val = entry.slice(entry.indexOf("|") + 1); if (this.curChartType === 'move' && e.currentTarget.className === 'cur') { // clicked a move, remove it if we already have it var moves = []; for (var i = 1; i <= 4; i++) { var $inputEl = this.$('input[name=move' + i + ']'); var curVal = $inputEl.val(); if (curVal === val) { this.unChooseMove(curVal); $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; this.updateChart(); var $firstResult = this.$chart.find('a.hover'); e.stopPropagation(); e.preventDefault(); if (!$firstResult.length) { this.chartChange(e, true); return; } if (this.search.addFilter($firstResult[0])) { $(e.currentTarget).val('').select(); this.search.find(''); return; } var entry = $firstResult.data('entry'); var val = entry.slice(entry.indexOf("|") + 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, selectNext) { var name = e.currentTarget.name; if (this.curChartName !== name) return; var id = toID(e.currentTarget.value); if (id in BattleAliases) id = toID(BattleAliases[id]); var val = ''; switch (name) { case 'pokemon': val = (id in BattlePokedex ? Dex.getSpecies(e.currentTarget.value).name : ''); break; case 'ability': if (id in BattleItems && this.curTeam.format == "gen8dualwielding") { val = BattleItems[id].name; } else if (id in BattleMovedex && this.curTeam.format == "gen8trademarked") { val = BattleMovedex[id].name; } else { val = (id in BattleAbilities ? BattleAbilities[id].name : ''); } break; case 'item': if (id in BattleMovedex && this.curTeam.format == "gen8fortemons") { val = BattleMovedex[id].name; } else { 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, selectNext); }, searchChange: function (e) { // 91 for right CMD / 93 for left CMD / 17 for CTL if (e.keyCode !== 91 && e.keyCode !== 93 && e.keyCode !== 17) { this.curSearchVal = e.currentTarget.value; this.updateTeamList(); } }, chartSetCustom: function (val) { val = toID(val); if (val === 'cathy') { var set = this.curSet; set.name = "Cathy"; set.species = 'Trevenant'; delete set.level; var baseFormat = this.curTeam.format; if (baseFormat.substr(0, 3) === 'gen') baseFormat = baseFormat.substr(4); if (baseFormat.substr(0, 8) === 'pokebank') baseFormat = baseFormat.substr(8); if (this.curTeam && this.curTeam.format) { if (baseFormat === 'battlespotsingles' || baseFormat === 'battlespotdoubles' || baseFormat.substr(0, 3) === 'vgc') set.level = 50; if (baseFormat.substr(0, 2) === 'lc') set.level = 5; } set.gender = 'F'; if (set.happiness) delete set.happiness; if (set.shiny) delete set.shiny; set.item = 'Starf Berry'; set.ability = 'Harvest'; set.moves = ['Substitute', 'Horn Leech', 'Earthquake', 'Phantom Force']; set.evs = {hp: 36, atk: 252, def: 0, spa: 0, spd: 0, spe: 220}; set.ivs = {}; set.nature = 'Jolly'; this.updateSetTop(); this.$(!this.$('input[name=item]').length ? (this.$('input[name=ability]').length ? 'input[name=ability]' : 'input[name=move1]') : 'input[name=item]').select(); return true; } if (val === 'citizensnips' || val === 'snips') { var set = this.curSet; set.name = "citizen snips"; set.species = 'Drapion'; delete set.level; var baseFormat = this.curTeam.format; if (baseFormat.substr(0, 3) === 'gen') baseFormat = baseFormat.substr(4); if (baseFormat.substr(0, 8) === 'pokebank') baseFormat = baseFormat.substr(8); if (this.curTeam && this.curTeam.format) { if (baseFormat === 'battlespotsingles' || baseFormat === 'battlespotdoubles' || baseFormat.substr(0, 3) === 'vgc') set.level = 50; if (baseFormat.substr(0, 2) === 'lc') set.level = 5; } if (set.happiness) delete set.happiness; if (set.shiny) delete set.shiny; set.item = 'Leftovers'; set.ability = 'Battle Armor'; set.moves = ['Acupressure', 'Knock Off', 'Rest', 'Sleep Talk']; set.evs = {hp: 248, atk: 0, def: 96, spa: 0, spd: 108, spe: 56}; set.ivs = {}; set.nature = 'Impish'; this.updateSetTop(); this.$(!this.$('input[name=item]').length ? (this.$('input[name=ability]').length ? 'input[name=ability]' : 'input[name=move1]') : 'input[name=item]').select(); return true; } }, chartSet: function (val, selectNext) { var inputName = this.curChartName; var input = this.$('input[name=' + inputName + ']'); if (this.chartSetCustom(input.val())) return; input.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.unChooseMove(this.curSet.moves[0]); 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.unChooseMove(this.curSet.moves[1]); 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.unChooseMove(this.curSet.moves[2]); 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.unChooseMove(this.curSet.moves[3]); this.curSet.moves[3] = val; this.chooseMove(val); if (selectNext) { this.stats(); this.$('button.setstats').focus(); } break; } this.save(); }, unChooseMove: function (moveName) { var set = this.curSet; if (!moveName || !set || this.curTeam.format === 'gen7hiddentype') return; if (moveName.substr(0, 13) === 'Hidden Power ') { if (set.ivs) { for (var i in set.ivs) { if (set.ivs[i] === 30) set.ivs[i] = 31; if (set.ivs[i] <= 3) set.ivs[i] = 0; } } } var resetSpeed = false; if (moveName === 'Gyro Ball') { resetSpeed = true; } this.chooseMove('', resetSpeed); }, canHyperTrain: function (set) { if (this.curTeam.gen < 7 || this.curTeam.format === 'gen7hiddentype') return false; var format = this.curTeam.format; if (!set.level || set.level === 100) return true; if (format.substr(0, 3) === 'gen') format = format.substr(4); if (format.substr(0, 10) === 'battlespot' || format.substr(0, 3) === 'vgc' || format === 'ultrasinnohclassic') { if (set.level === 50) return true; } return false; }, chooseMove: function (moveName, resetSpeed) { var set = this.curSet; if (!set) return; var gen = this.curTeam.gen; var minSpe; if (resetSpeed) minSpe = false; if (moveName.substr(0, 13) === 'Hidden Power ') { if (!this.canHyperTrain(set)) { var hpType = moveName.substr(13); set.ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31}; if (this.curTeam.gen > 2) { for (var i in exports.BattleTypeChart[hpType].HPivs) { set.ivs[i] = exports.BattleTypeChart[hpType].HPivs[i]; } } else { for (var i in exports.BattleTypeChart[hpType].HPdvs) { set.ivs[i] = exports.BattleTypeChart[hpType].HPdvs[i] * 2; } var atkDV = Math.floor(set.ivs.atk / 2); var defDV = Math.floor(set.ivs.def / 2); var speDV = Math.floor(set.ivs.spe / 2); var spcDV = Math.floor(set.ivs.spa / 2); var expectedHpDV = (atkDV % 2) * 8 + (defDV % 2) * 4 + (speDV % 2) * 2 + (spcDV % 2); set.ivs.hp = expectedHpDV * 2; if (set.ivs.hp === 30) set.ivs.hp = 31; } } } else if (moveName === 'Return') { this.curSet.happiness = 255; } else if (moveName === 'Frustration') { this.curSet.happiness = 0; } else if (moveName === 'Gyro Ball') { minSpe = true; } if (this.curTeam.format === 'gen7hiddentype') return; var minAtk = true; if (set.ability === 'Battle Bond') minAtk = false; // only available through an event with 31 Atk IVs var hpModulo = (this.curTeam.gen >= 6 ? 2 : 4); var hasHiddenPower = false; var moves = set.moves; for (var i = 0; i < moves.length; ++i) { if (!moves[i]) continue; if (moves[i].substr(0, 13) === 'Hidden Power ') hasHiddenPower = true; var move = Dex.forGen(this.curTeam.gen).getMove(moves[i]); if (move.id === 'transform') { hasHiddenPower = true; // A Pokemon with Transform can copy another Pokemon that knows Hidden Power var hasMoveBesidesTransform = false; for (var j = 0; j < moves.length; ++j) { if (j !== i && moves[j]) { hasMoveBesidesTransform = true; break; } } if (!hasMoveBesidesTransform) minAtk = false; } else if (move.category === 'Physical' && !move.damage && !move.ohko && move.id !== 'rapidspin' && move.id !== 'foulplay' && move.id !== 'endeavor' && move.id !== 'counter' && move.id !== 'bodypress') { minAtk = false; } else if (move.id === 'metronome' || move.id === 'assist' || move.id === 'copycat' || move.id === 'mefirst') { minAtk = false; } if (minSpe === false && moveName === 'Gyro Ball') { minSpe = undefined; } } if (!set.ivs) { if (minSpe === undefined && (!minAtk || gen < 3)) return; set.ivs = {}; } if (!set.ivs['spe'] && set.ivs['spe'] !== 0) set.ivs['spe'] = 31; if (minSpe) { // min Spe set.ivs['spe'] = (hasHiddenPower ? set.ivs['spe'] % hpModulo : 0); } else if (minSpe === false) { // max Spe set.ivs['spe'] = (hasHiddenPower ? 30 + (set.ivs['spe'] % 2) : 31); } if (gen < 3) return; if (!set.ivs['atk'] && set.ivs['atk'] !== 0) set.ivs['atk'] = 31; if (minAtk) { // min Atk set.ivs['atk'] = (hasHiddenPower ? set.ivs['atk'] % hpModulo : 0); } else { // max Atk set.ivs['atk'] = (hasHiddenPower ? 30 + (set.ivs['atk'] % 2) : 31); } }, setPokemon: function (val, selectNext) { var set = this.curSet; var species = Dex.forGen(this.curTeam.gen).getSpecies(val); if (!species.exists || set.species === species.name) { 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) { var baseFormat = this.curTeam.format; var format = window.BattleFormats && window.BattleFormats[baseFormat]; if (baseFormat.substr(0, 3) === 'gen') baseFormat = baseFormat.substr(4); if (baseFormat.substr(0, 8) === 'pokebank') baseFormat = baseFormat.substr(8); if (this.curTeam && this.curTeam.format) { if (baseFormat.substr(0, 10) === 'battlespot' && baseFormat.substr(0, 19) != 'battlespotspecial13' || baseFormat.substr(0, 3) === 'vgc') set.level = 50; if (baseFormat.substr(0, 2) === 'lc' || baseFormat.substr(0, 5) === 'caplc') set.level = 5; if (baseFormat.substr(0, 19) === 'battlespotspecial17') set.level = 1; if (format && format.teambuilderLevel) { set.level = format.teambuilderLevel; } } } if (set.gender) delete set.gender; if (species.gender && species.gender !== 'N') set.gender = species.gender; if (set.happiness) delete set.happiness; if (set.shiny) delete set.shiny; if (this.curTeam.format.indexOf('hackmons') < 0) { set.item = (species.requiredItem || ''); } else { set.item = ''; } set.ability = species.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 *********************************************************/ // Stat calculator getStat: function (stat, set, evOverride, natureOverride) { var supportsEVs = !this.curTeam.format.includes('letsgo'); var supportsAVs = !supportsEVs; 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 species = Dex.forGen(this.curTeam.gen).getSpecies(set.species); if (!species.exists) return 0; if (!set.level) set.level = 100; if (typeof set.ivs[stat] === 'undefined') set.ivs[stat] = 31; var baseStat = species.baseStats[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; if (!supportsEVs) return Math.floor(Math.floor(2 * baseStat + iv + 100) * set.level / 100 + 10) + (supportsAVs ? ev : 0); 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 (!supportsEVs) { val = Math.floor(Math.floor(2 * baseStat + iv) * 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; } if (!supportsEVs) { var friendshipValue = Math.floor((70 / 255 / 10 + 1) * 100); val = Math.floor(val) * friendshipValue / 100 + (supportsAVs ? ev : 0); } return Math.floor(val); }, // initialization getGen: function (format) { format = '' + format; if (!format) return 7; if (format.substr(0, 3) !== 'gen') return 6; return parseInt(format.substr(3, 1), 10) || 6; } }); 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(); } }); var AltFormPopup = this.AltFormPopup = Popup.extend({ type: 'semimodal', initialize: function (data) { this.room = data.room; this.curSet = data.curSet; this.chartIndex = data.index; var species = Dex.getSpecies(this.curSet.species); var baseid = toID(species.baseSpecies); var forms = [baseid].concat(species.cosmeticFormes.map(toID)); var spriteDir = Dex.resourcePrefix + 'sprites/'; var spriteSize = 96; var spriteDim = 'width: 96px; height: 96px;'; var gen = {1:'gen1', 2:'gen2', 3:'gen3', 4:'gen4', 5:'gen5', 6:'dex', 7:'dex', 8:'dex'}[Math.max(this.room.curTeam.gen, species.gen)]; if (Dex.prefs('nopastgens')) gen = 'dex'; if (Dex.prefs('bwgfx') && gen === 'dex') gen = 'gen5'; spriteDir += gen; if (gen === 'dex') { spriteSize = 120; spriteDim = 'width: 120px; height: 120px;'; } var buf = ''; buf += '

    Pick a variant or

    '; buf += '
    '; var formCount = forms.length; for (var i = 0; i < formCount; i++) { var formid = forms[i].substring(baseid.length); var form = (formid ? formid[0].toUpperCase() + formid.slice(1) : ''); var offset = '-' + (((i - 1) % 7) * spriteSize) + 'px -' + (Math.floor((i - 1) / 7) * spriteSize) + 'px'; buf += ''; } buf += '
    '; var height = Math.ceil(formCount / 7); var width = Math.ceil(formCount / height); this.$el.html(buf).css({'max-width': (4 + spriteSize) * width, 'height': 42 + (4 + spriteSize) * height}); }, setForm: function (form) { var species = Dex.getSpecies(this.curSet.species); if (form && form !== species.form) { this.curSet.species = Dex.getSpecies(species.baseSpecies + form).name; } else if (!form) { this.curSet.species = species.baseSpecies; } this.close(); if (this.room.curSet) { this.room.updatePokemonSprite(); } else { this.room.update(); } this.room.$('input[name=pokemon]').eq(this.chartIndex).val(this.curSet.species); this.room.curTeam.team = Storage.packTeam(this.room.curSetList); Storage.saveTeam(this.room.curTeam); } }); })(window, jQuery);