(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.
';
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 += '
';
}
}
var newButtonText = "New Team";
if (filterFolder) newButtonText = "New Team in folder";
if (filterFormat && filterFormat !== 'gen6') {
newButtonText = "New " + Tools.escapeFormat(filterFormat) + " Team";
}
buf += '';
buf += '
';
var atLeastOne = false;
try {
if (!window.localStorage && !window.nodewebkit) buf += '
== CAN\'T SAVE == Your browser doesn\'t support localStorage and can\'t save teams! Update to a newer browser.
';
} catch (e) {
buf += '
== CAN\'T SAVE == Cookies are disabled so you can\'t save teams! Enable them in your browser settings.
';
}
if (Storage.cantSave) buf += '
== CAN\'T SAVE == You hit your browser\'s limit for team storage! Please backup them and delete some of them. Your teams won\'t be saved until you\'re under the limit again.
';
if (!teams.length) {
if (this.deletedTeamLoc >= 0) {
buf += '';
}
buf += '
you don\'t have any teams lol
';
} else {
for (var i = 0; i < teams.length + 1; i++) {
if (i === this.deletedTeamLoc) {
if (!atLeastOne) atLeastOne = true;
buf += '';
}
if (i >= teams.length) break;
var team = teams[i];
if (team && !team.team && team.team !== '') {
team = null;
}
if (!team) {
buf += '
Error: A corrupted team was dropped
';
teams.splice(i, 1);
i--;
if (this.deletedTeamLoc && this.deletedTeamLoc > i) this.deletedTeamLoc--;
continue;
}
if (filterFormat && filterFormat !== (team.format || 'gen6')) continue;
if (filterFolder !== undefined && filterFolder !== team.folder) continue;
if (!atLeastOne) atLeastOne = true;
var formatText = '';
if (team.format) {
formatText = '[' + team.format + '] ';
}
if (team.folder) formatText += team.folder + '/';
// teams are
s rather than
';
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 += '
';
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.i = data.i;
this.team = data.team;
for (var i = 0; i < data.team.length; i++) {
var set = data.team[i];
if (i !== data.i && i !== data.i + 1) {
buf += '
Move here
';
}
buf += '
' + Tools.escapeHTML(set.name) + '
';
}
if (i !== data.i && i !== data.i + 1) {
buf += '
Move here
';
}
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 = '';
this.$el.html(buf);
},
submit: function (data) {
this.room.deleteFolder(this.folder, !!this.$('input[name=addname]')[0].checked);
this.close();
}
});
})(window, jQuery);