mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-05-23 00:17:08 -05:00
Default backgrounds are still left-aligned, mainly for Horizon and Waterfall which are designed for their left side to be visible, but custom backgrounds are now centered on the assumption that that's what most users want.
1467 lines
44 KiB
JavaScript
1467 lines
44 KiB
JavaScript
Config.origindomain = 'play.pokemonshowdown.com';
|
|
// `defaultserver` specifies the server to use when the domain name in the
|
|
// address bar is `Config.origindomain`.
|
|
Config.defaultserver = {
|
|
id: 'showdown',
|
|
host: 'sim.smogon.com',
|
|
port: 443,
|
|
httpport: 8000,
|
|
altport: 80,
|
|
registered: true
|
|
};
|
|
|
|
function Storage() {}
|
|
|
|
Storage.initialize = function () {
|
|
if (window.nodewebkit) {
|
|
window.fs = require('fs');
|
|
|
|
this.initDirectory();
|
|
this.startLoggingChat = this.nwStartLoggingChat;
|
|
this.stopLoggingChat = this.nwStopLoggingChat;
|
|
this.logChat = this.nwLogChat;
|
|
}
|
|
Storage.initPrefs();
|
|
};
|
|
|
|
/*********************************************************
|
|
* Background
|
|
*********************************************************/
|
|
|
|
// Backgrounds are handled separately from other prefs because
|
|
// they're server-specific and should be loaded faster
|
|
|
|
Storage.bg = {
|
|
id: '',
|
|
changeCount: 0,
|
|
set: function (bgUrl, bgid, noSave) {
|
|
if (!this.load(bgUrl, bgid)) {
|
|
this.extractMenuColors(bgUrl, bgid, noSave);
|
|
} else if (bgid) {
|
|
try {
|
|
localStorage.setItem('showdown_bg', bgUrl + '\n' + bgid);
|
|
} catch (e) {}
|
|
} else {
|
|
try {
|
|
localStorage.removeItem('showdown_bg');
|
|
} catch (e) {}
|
|
}
|
|
},
|
|
/**
|
|
* Load a background. Returns true if hues were loaded, or false if
|
|
* they still need to be extracted using Color Thief.
|
|
*/
|
|
load: function (bgUrl, bgid, hues) {
|
|
this.id = bgid;
|
|
if (!bgid) {
|
|
if (location.host === 'smogtours.psim.us') {
|
|
bgid = 'shaymin';
|
|
} else if (location.host === 'play.pokemonshowdown.com') {
|
|
bgid = ['horizon', 'ocean', 'waterfall', 'shaymin', 'charizards'][Math.floor(Math.random() * 5)];
|
|
} else {
|
|
$(document.body).css({
|
|
background: '',
|
|
'background-size': ''
|
|
});
|
|
$('#mainmenubuttoncolors').remove();
|
|
return true;
|
|
}
|
|
bgUrl = Tools.resourcePrefix + 'fx/client-bg-' + bgid + '.jpg';
|
|
}
|
|
var background;
|
|
if (bgUrl.charAt(0) === '#') {
|
|
background = bgUrl;
|
|
} else if (bgid !== 'custom') {
|
|
background = '#546bac url(' + bgUrl + ') no-repeat left center fixed';
|
|
} else {
|
|
background = '#546bac url(' + bgUrl + ') no-repeat center center fixed';
|
|
}
|
|
$(document.body).css({
|
|
background: background,
|
|
'background-size': 'cover'
|
|
});
|
|
this.changeCount++;
|
|
|
|
if (!hues) switch (bgid) {
|
|
case 'horizon':
|
|
hues = ["318.87640449438203,35.177865612648226%", "216,46.2962962962963%", "221.25,32.25806451612904%", "197.8021978021978,52.60115606936417%", "232.00000000000003,19.480519480519483%", "228.38709677419354,60.7843137254902%"];
|
|
break;
|
|
case 'ocean':
|
|
hues = ["82.8169014084507,34.63414634146342%", "216.16438356164383,29.55465587044534%", "212.92682926829266,59.42028985507245%", "209.18918918918916,57.51295336787566%", "199.2857142857143,48.275862068965495%", "213.11999999999998,55.06607929515419%"];
|
|
break;
|
|
case 'waterfall':
|
|
hues = ["119.31034482758622,37.66233766233767%", "184.36363636363635,23.012552301255226%", "108.92307692307692,37.14285714285714%", "70.34482758620689,20.567375886524818%", "98.39999999999998,36.76470588235296%", "140,38.18181818181818%"];
|
|
break;
|
|
case 'shaymin':
|
|
hues = ["39.000000000000064,21.7391304347826%", "170.00000000000003,2.380952380952378%", "157.5,11.88118811881188%", "174.78260869565216,12.041884816753928%", "185.00000000000003,12.76595744680851%", "20,5.660377358490567%"];
|
|
break;
|
|
case 'charizards':
|
|
hues = ["37.159090909090914,74.57627118644066%", "10.874999999999998,70.79646017699115%", "179.51612903225808,52.10084033613446%", "20.833333333333336,36.73469387755102%", "192.3076923076923,80.41237113402063%", "210,29.629629629629633%"];
|
|
break;
|
|
}
|
|
if (!hues && bgUrl.charAt(0) === '#') {
|
|
var r = parseInt(bgUrl.slice(1, 3), 16) / 255;
|
|
var g = parseInt(bgUrl.slice(3, 5), 16) / 255;
|
|
var b = parseInt(bgUrl.slice(5, 7), 16) / 255;
|
|
var hs = this.getHueSat(r, g, b);
|
|
hues = [hs, hs, hs, hs, hs, hs];
|
|
}
|
|
if (hues) {
|
|
this.loadHues(hues);
|
|
}
|
|
return !!hues;
|
|
},
|
|
loadHues: function (hues) {
|
|
$('#mainmenubuttoncolors').remove();
|
|
var cssBuf = '';
|
|
for (var i = 0; i < 6; i++) {
|
|
var n = i + 1;
|
|
var hs = hues[i];
|
|
cssBuf += '.mainmenuwrapper .button.mainmenu' + n + ' { background: linear-gradient(to bottom, hsl(' + hs + ',72%), hsl(' + hs + ',52%)); border-color: hsl(' + hs + ',40%); }\n';
|
|
cssBuf += '.mainmenuwrapper .button.mainmenu' + n + ':hover { background: linear-gradient(to bottom, hsl(' + hs + ',62%), hsl(' + hs + ',42%)); border-color: hsl(' + hs + ',21%); }\n';
|
|
cssBuf += '.mainmenuwrapper .button.mainmenu' + n + ':active { background: linear-gradient(to bottom, hsl(' + hs + ',42%), hsl(' + hs + ',58%)); border-color: hsl(' + hs + ',21%); }\n';
|
|
}
|
|
$('head').append('<style id="mainmenubuttoncolors">' + cssBuf + '</style>');
|
|
},
|
|
extractMenuColors: function (bgUrl, bgid, noSave) {
|
|
var changeCount = this.changeCount;
|
|
// We need the image object to load it on a canvas to detect the main color.
|
|
var img = new Image();
|
|
img.onload = function () {
|
|
// in case ColorThief throws from canvas,
|
|
// or localStorage throws
|
|
try {
|
|
var colorThief = new ColorThief();
|
|
var colors = colorThief.getPalette(img, 6);
|
|
|
|
var hues = [];
|
|
if (!colors) {
|
|
hues = ['0, 0%', '0, 0%', '0, 0%', '0, 0%', '0, 0%', '0, 0%'];
|
|
} else {
|
|
for (var i = 0; i < 6; i++) {
|
|
var color = colors[i];
|
|
var hs = Storage.bg.getHueSat(color[0] / 255, color[1] / 255, color[2] / 255);
|
|
hues.unshift(hs);
|
|
}
|
|
}
|
|
Storage.bg.loadHues(hues);
|
|
if (!noSave && Storage.bg.changeCount === changeCount) {
|
|
localStorage.setItem('showdown_bg', bgUrl + '\n' + Storage.bg.id + '\n' + hues.join('\n'));
|
|
}
|
|
} catch (e) {}
|
|
};
|
|
img.src = bgUrl;
|
|
},
|
|
getHueSat: function (r, g, b) {
|
|
var max = Math.max(r, g, b);
|
|
var min = Math.min(r, g, b);
|
|
var h;
|
|
var s;
|
|
var l = (max + min) / 2;
|
|
if (max === min) {
|
|
h = s = 0;
|
|
return '0, 0%';
|
|
} else {
|
|
var d = max - min;
|
|
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
switch (max) {
|
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
|
case g: h = (b - r) / d + 2; break;
|
|
case b: h = (r - g) / d + 4; break;
|
|
}
|
|
h /= 6;
|
|
}
|
|
return '' + (h * 360) + ',' + (s * 100) + '%';
|
|
}
|
|
};
|
|
|
|
try {
|
|
var bg = localStorage.getItem('showdown_bg').split('\n');
|
|
if (bg.length >= 2) {
|
|
Storage.bg.load(bg[0], bg[1]);
|
|
if (bg.length === 8) Storage.bg.loadHues(bg.slice(2));
|
|
}
|
|
} catch (e) {}
|
|
|
|
if (!Storage.bg.id) {
|
|
Storage.bg.load();
|
|
}
|
|
|
|
/*********************************************************
|
|
* Prefs
|
|
*********************************************************/
|
|
|
|
// Prefs are canonically stored in showdown_prefs in localStorage
|
|
// in the origin https://play.pokemonshowdown.com
|
|
|
|
// We try loading things from the origin, anyway, in case third-party
|
|
// localStorage is banned, and since prefs are cached in other
|
|
// places in certain cases.
|
|
|
|
Storage.origin = 'https://play.pokemonshowdown.com';
|
|
|
|
Storage.prefs = function (prop, value, save) {
|
|
if (value === undefined) {
|
|
// get preference
|
|
return this.prefs.data[prop];
|
|
}
|
|
// set preference
|
|
if (value === null) {
|
|
delete this.prefs.data[prop];
|
|
} else if (this.prefs.data[prop] === value) {
|
|
return false; // no need to save
|
|
} else {
|
|
this.prefs.data[prop] = value;
|
|
}
|
|
if (save !== false) this.prefs.save();
|
|
return true;
|
|
};
|
|
|
|
Storage.prefs.data = {};
|
|
try {
|
|
if (window.localStorage) {
|
|
Storage.prefs.data = $.parseJSON(localStorage.getItem('showdown_prefs')) || {};
|
|
}
|
|
} catch (e) {}
|
|
|
|
Storage.prefs.save = function () {
|
|
if (!window.localStorage) return;
|
|
try {
|
|
localStorage.setItem('showdown_prefs', $.toJSON(this.data));
|
|
} catch (e) {}
|
|
};
|
|
|
|
Storage.whenPrefsLoaded = Tools.makeLoadTracker();
|
|
Storage.whenTeamsLoaded = Tools.makeLoadTracker();
|
|
Storage.whenAppLoaded = Tools.makeLoadTracker();
|
|
|
|
Storage.initPrefs = function () {
|
|
Storage.loadTeams();
|
|
if (Config.testclient) {
|
|
return this.initTestClient();
|
|
} else if (document.location.origin === Storage.origin) {
|
|
// Same origin, everything can be kept as default
|
|
Config.server = Config.server || Config.defaultserver;
|
|
this.whenPrefsLoaded.load();
|
|
if (!window.nodewebkit) this.whenTeamsLoaded.load();
|
|
return;
|
|
}
|
|
|
|
// Cross-origin
|
|
|
|
if (!('postMessage' in window)) {
|
|
// browser does not support cross-document messaging
|
|
return Storage.whenAppLoaded(function (app) {
|
|
app.trigger('init:unsupported');
|
|
});
|
|
}
|
|
|
|
$(window).on('message', Storage.onMessage);
|
|
|
|
if (document.location.hostname !== Config.origindomain) {
|
|
$(
|
|
'<iframe src="https://play.pokemonshowdown.com/crossdomain.php?host=' +
|
|
encodeURIComponent(document.location.hostname) +
|
|
'&path=' + encodeURIComponent(document.location.pathname.substr(1)) +
|
|
'" style="display: none;"></iframe>'
|
|
).appendTo('body');
|
|
} else {
|
|
Config.server = Config.server || Config.defaultserver;
|
|
$(
|
|
'<iframe src="https://play.pokemonshowdown.com/crossprotocol.html?v1.1" style="display: none;"></iframe>'
|
|
).appendTo('body');
|
|
}
|
|
};
|
|
|
|
Storage.crossOriginFrame = null;
|
|
Storage.crossOriginRequests = {};
|
|
Storage.crossOriginRequestCount = 0;
|
|
Storage.onMessage = function ($e) {
|
|
var e = $e.originalEvent;
|
|
if (e.origin !== Storage.origin) return;
|
|
|
|
Storage.crossOriginFrame = e.source;
|
|
var data = e.data;
|
|
switch (data.charAt(0)) {
|
|
case 'c':
|
|
Config.server = $.parseJSON(data.substr(1));
|
|
if (Config.server.registered && Config.server.id !== 'showdown' && Config.server.id !== 'smogtours') {
|
|
var $link = $('<link rel="stylesheet" ' +
|
|
'href="//play.pokemonshowdown.com/customcss.php?server=' +
|
|
encodeURIComponent(Config.server.id) + '" />');
|
|
$('head').append($link);
|
|
}
|
|
break;
|
|
case 'p':
|
|
var newData = $.parseJSON(data.substr(1));
|
|
if (newData) Storage.prefs.data = newData;
|
|
Storage.prefs.save = function () {
|
|
var prefData = $.toJSON(this.data);
|
|
Storage.postCrossOriginMessage('P' + prefData);
|
|
|
|
// in Safari, cross-origin local storage is apparently treated as session
|
|
// storage, so mirror the storage in the current origin just in case
|
|
try {
|
|
localStorage.setItem('showdown_prefs', prefData);
|
|
} catch (e) {}
|
|
};
|
|
Storage.whenPrefsLoaded.load();
|
|
break;
|
|
case 't':
|
|
if (window.nodewebkit) return;
|
|
var oldTeams;
|
|
if (Storage.teams && Storage.teams.length) {
|
|
// Teams are still stored in the old location; merge them with the
|
|
// new teams.
|
|
oldTeams = Storage.teams;
|
|
}
|
|
Storage.loadPackedTeams(data.substr(1));
|
|
Storage.saveTeams = function () {
|
|
var packedTeams = Storage.packAllTeams(Storage.teams);
|
|
Storage.postCrossOriginMessage('T' + packedTeams);
|
|
|
|
// in Safari, cross-origin local storage is apparently treated as session
|
|
// storage, so mirror the storage in the current origin just in case
|
|
if (document.location.hostname === Config.origindomain) {
|
|
try {
|
|
localStorage.setItem('showdown_teams_local', packedTeams);
|
|
} catch (e) {}
|
|
}
|
|
};
|
|
if (oldTeams) {
|
|
Storage.teams = Storage.teams.concat(oldTeams);
|
|
Storage.saveTeams();
|
|
localStorage.removeItem('showdown_teams');
|
|
}
|
|
if (data === 'tnull' && !Storage.teams.length) {
|
|
Storage.loadPackedTeams(localStorage.getItem('showdown_teams_local'));
|
|
}
|
|
Storage.whenTeamsLoaded.load();
|
|
break;
|
|
case 'a':
|
|
if (data === 'a0') {
|
|
Storage.noThirdParty = true;
|
|
Storage.whenTeamsLoaded.load();
|
|
Storage.whenPrefsLoaded.load();
|
|
}
|
|
if (!window.nodewebkit) {
|
|
// for whatever reason, Node-Webkit doesn't let us make remote
|
|
// Ajax requests or something. Oh well, making them direct
|
|
// isn't a problem, either.
|
|
$.get = function (uri, data, callback, type) {
|
|
var idx = Storage.crossOriginRequestCount++;
|
|
Storage.crossOriginRequests[idx] = callback;
|
|
Storage.postCrossOriginMessage('R' + JSON.stringify([uri, data, idx, type]));
|
|
};
|
|
$.post = function (uri, data, callback, type) {
|
|
var idx = Storage.crossOriginRequestCount++;
|
|
Storage.crossOriginRequests[idx] = callback;
|
|
Storage.postCrossOriginMessage('S' + JSON.stringify([uri, data, idx, type]));
|
|
};
|
|
}
|
|
break;
|
|
case 'r':
|
|
var reqData = JSON.parse(data.slice(1));
|
|
var idx = reqData[0];
|
|
if (Storage.crossOriginRequests[idx]) {
|
|
Storage.crossOriginRequests[idx](reqData[1]);
|
|
delete Storage.crossOriginRequests[idx];
|
|
}
|
|
break;
|
|
}
|
|
};
|
|
Storage.postCrossOriginMessage = function (data) {
|
|
return Storage.crossOriginFrame.postMessage(data, Storage.origin);
|
|
};
|
|
|
|
// Test client
|
|
|
|
Storage.initTestClient = function () {
|
|
Config.server = Config.server || Config.defaultserver;
|
|
Storage.whenTeamsLoaded.load();
|
|
Storage.whenAppLoaded(function (app) {
|
|
$.get = function (uri, data, callback, type) {
|
|
if (type === 'html') {
|
|
uri += '&testclient';
|
|
}
|
|
if (data) {
|
|
uri += '?testclient';
|
|
for (var i in data) {
|
|
uri += '&' + i + '=' + encodeURIComponent(data[i]);
|
|
}
|
|
}
|
|
if (uri[0] === '/') { // relative URI
|
|
uri = Tools.resourcePrefix + uri.substr(1);
|
|
}
|
|
app.addPopup(ProxyPopup, {uri: uri, callback: callback});
|
|
};
|
|
$.post = function (/*uri, data, callback, type*/) {
|
|
app.addPopupMessage('The requested action is not supported by testclient.html. Please complete this action in the official client instead.');
|
|
};
|
|
Storage.whenPrefsLoaded.load();
|
|
});
|
|
};
|
|
|
|
/*********************************************************
|
|
* Teams
|
|
*********************************************************/
|
|
|
|
/**
|
|
* Teams are normally loaded from `localStorage`.
|
|
* If the client isn't running on `play.pokemonshowdown.com`, though,
|
|
* teams are received from `crossdomain.php` instead.
|
|
*/
|
|
|
|
Storage.teams = null;
|
|
|
|
Storage.loadTeams = function () {
|
|
if (window.nodewebkit) {
|
|
return;
|
|
}
|
|
this.teams = [];
|
|
if (window.localStorage) {
|
|
Storage.loadPackedTeams(localStorage.getItem('showdown_teams'));
|
|
}
|
|
};
|
|
|
|
Storage.loadPackedTeams = function (buffer) {
|
|
try {
|
|
this.teams = Storage.unpackAllTeams(buffer);
|
|
} catch (e) {
|
|
Storage.whenAppLoaded(function (app) {
|
|
app.addPopup(Popup, {
|
|
type: 'modal',
|
|
htmlMessage: "Your teams are corrupt and could not be loaded. :( We may be able to recover a team from this data:<br /><textarea rows=\"10\" cols=\"60\">" + Tools.escapeHTML(buffer) + "</textarea>"
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
Storage.saveTeams = function () {
|
|
if (window.localStorage) {
|
|
Storage.cantSave = false;
|
|
try {
|
|
localStorage.setItem('showdown_teams', Storage.packAllTeams(this.teams));
|
|
} catch (e) {
|
|
if (e.code === DOMException.QUOTA_EXCEEDED_ERR) {
|
|
Storage.cantSave = true;
|
|
} else {
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
Storage.getPackedTeams = function () {
|
|
var packedTeams = '';
|
|
try {
|
|
packedTeams = localStorage.getItem('showdown_teams');
|
|
} catch (e) {}
|
|
if (packedTeams) return packedTeams;
|
|
return Storage.packAllTeams(this.teams);
|
|
};
|
|
|
|
Storage.saveTeam = function () {
|
|
this.saveTeams();
|
|
};
|
|
|
|
Storage.deleteTeam = function () {
|
|
this.saveTeams();
|
|
};
|
|
|
|
Storage.saveAllTeams = function () {
|
|
this.saveTeams();
|
|
};
|
|
|
|
/*********************************************************
|
|
* Team importing and exporting
|
|
*********************************************************/
|
|
|
|
Storage.unpackAllTeams = function (buffer) {
|
|
if (!buffer) return [];
|
|
|
|
if (buffer.charAt(0) === '[' && $.trim(buffer).indexOf('\n') < 0) {
|
|
// old format
|
|
return JSON.parse(buffer).map(function (oldTeam) {
|
|
return {
|
|
name: oldTeam.name || '',
|
|
format: oldTeam.format || '',
|
|
team: Storage.packTeam(oldTeam.team),
|
|
iconCache: ''
|
|
};
|
|
});
|
|
}
|
|
|
|
return buffer.split('\n').map(function (line) {
|
|
var pipeIndex = line.indexOf('|');
|
|
if (pipeIndex < 0) return;
|
|
var bracketIndex = line.indexOf(']');
|
|
if (bracketIndex > pipeIndex) bracketIndex = -1;
|
|
return {
|
|
name: line.slice(bracketIndex + 1, pipeIndex),
|
|
format: bracketIndex > 0 ? line.slice(0, bracketIndex) : '',
|
|
team: line.slice(pipeIndex + 1),
|
|
iconCache: ''
|
|
};
|
|
}).filter(function (v) { return v; });
|
|
};
|
|
|
|
Storage.packAllTeams = function (teams) {
|
|
return teams.map(function (team) {
|
|
return (team.format ? '' + team.format + ']' : '') + team.name + '|' + Storage.getPackedTeam(team);
|
|
}).join('\n');
|
|
};
|
|
|
|
Storage.packTeam = function (team) {
|
|
var buf = '';
|
|
if (!team) return '';
|
|
|
|
for (var i = 0; i < team.length; i++) {
|
|
var set = team[i];
|
|
if (buf) buf += ']';
|
|
|
|
// name
|
|
buf += (set.name || set.species);
|
|
|
|
// species
|
|
var id = toId(set.species || set.name);
|
|
buf += '|' + (toId(set.name || set.species) === id ? '' : id);
|
|
|
|
// item
|
|
buf += '|' + toId(set.item);
|
|
|
|
// ability
|
|
var template = Tools.getTemplate(set.species || set.name);
|
|
var abilities = template.abilities;
|
|
id = toId(set.ability);
|
|
if (abilities) {
|
|
if (id == toId(abilities['0'])) {
|
|
buf += '|';
|
|
} else if (id === toId(abilities['1'])) {
|
|
buf += '|1';
|
|
} else if (id === toId(abilities['H'])) {
|
|
buf += '|H';
|
|
} else {
|
|
buf += '|' + id;
|
|
}
|
|
} else {
|
|
buf += '|' + id;
|
|
}
|
|
|
|
// moves
|
|
if (set.moves) {
|
|
buf += '|' + set.moves.map(toId).join(',');
|
|
} else {
|
|
buf += '|';
|
|
}
|
|
|
|
// nature
|
|
buf += '|' + (set.nature || '');
|
|
|
|
// evs
|
|
var evs = '|';
|
|
if (set.evs) {
|
|
evs = '|' + (set.evs['hp'] || '') + ',' + (set.evs['atk'] || '') + ',' + (set.evs['def'] || '') + ',' + (set.evs['spa'] || '') + ',' + (set.evs['spd'] || '') + ',' + (set.evs['spe'] || '');
|
|
}
|
|
if (evs === '|,,,,,') {
|
|
buf += '|';
|
|
} else {
|
|
buf += evs;
|
|
}
|
|
|
|
// gender
|
|
if (set.gender && set.gender !== template.gender) {
|
|
buf += '|' + set.gender;
|
|
} else {
|
|
buf += '|';
|
|
}
|
|
|
|
// ivs
|
|
var ivs = '|';
|
|
if (set.ivs) {
|
|
ivs = '|' + (set.ivs['hp'] === 31 || set.ivs['hp'] === undefined ? '' : set.ivs['hp']) + ',' + (set.ivs['atk'] === 31 || set.ivs['atk'] === undefined ? '' : set.ivs['atk']) + ',' + (set.ivs['def'] === 31 || set.ivs['def'] === undefined ? '' : set.ivs['def']) + ',' + (set.ivs['spa'] === 31 || set.ivs['spa'] === undefined ? '' : set.ivs['spa']) + ',' + (set.ivs['spd'] === 31 || set.ivs['spd'] === undefined ? '' : set.ivs['spd']) + ',' + (set.ivs['spe'] === 31 || set.ivs['spe'] === undefined ? '' : set.ivs['spe']);
|
|
}
|
|
if (ivs === '|,,,,,') {
|
|
buf += '|';
|
|
} else {
|
|
buf += ivs;
|
|
}
|
|
|
|
// shiny
|
|
if (set.shiny) {
|
|
buf += '|S';
|
|
} else {
|
|
buf += '|';
|
|
}
|
|
|
|
// level
|
|
if (set.level && set.level != 100) {
|
|
buf += '|' + set.level;
|
|
} else {
|
|
buf += '|';
|
|
}
|
|
|
|
// happiness
|
|
if (set.happiness !== undefined && set.happiness !== 255) {
|
|
buf += '|' + set.happiness;
|
|
} else {
|
|
buf += '|';
|
|
}
|
|
}
|
|
|
|
return buf;
|
|
};
|
|
|
|
Storage.fastUnpackTeam = function (buf) {
|
|
if (!buf) return [];
|
|
|
|
var team = [];
|
|
var i = 0, j = 0;
|
|
|
|
while (true) {
|
|
var set = {};
|
|
team.push(set);
|
|
|
|
// name
|
|
j = buf.indexOf('|', i);
|
|
set.name = buf.substring(i, j);
|
|
i = j + 1;
|
|
|
|
// species
|
|
j = buf.indexOf('|', i);
|
|
set.species = buf.substring(i, j) || set.name;
|
|
i = j + 1;
|
|
|
|
// item
|
|
j = buf.indexOf('|', i);
|
|
set.item = buf.substring(i, j);
|
|
i = j + 1;
|
|
|
|
// ability
|
|
j = buf.indexOf('|', i);
|
|
var ability = buf.substring(i, j);
|
|
var template = Tools.getTemplate(set.species);
|
|
set.ability = (template.abilities && ability in {'':1, 0:1, 1:1, H:1} ? template.abilities[ability || '0'] : ability);
|
|
i = j + 1;
|
|
|
|
// moves
|
|
j = buf.indexOf('|', i);
|
|
set.moves = buf.substring(i, j).split(',');
|
|
i = j + 1;
|
|
|
|
// nature
|
|
j = buf.indexOf('|', i);
|
|
set.nature = buf.substring(i, j);
|
|
if (set.nature === 'undefined') set.nature = undefined;
|
|
i = j + 1;
|
|
|
|
// evs
|
|
j = buf.indexOf('|', i);
|
|
if (j !== i) {
|
|
var evs = buf.substring(i, j).split(',');
|
|
set.evs = {
|
|
hp: Number(evs[0]) || 0,
|
|
atk: Number(evs[1]) || 0,
|
|
def: Number(evs[2]) || 0,
|
|
spa: Number(evs[3]) || 0,
|
|
spd: Number(evs[4]) || 0,
|
|
spe: Number(evs[5]) || 0
|
|
};
|
|
}
|
|
i = j + 1;
|
|
|
|
// gender
|
|
j = buf.indexOf('|', i);
|
|
if (i !== j) set.gender = buf.substring(i, j);
|
|
i = j + 1;
|
|
|
|
// ivs
|
|
j = buf.indexOf('|', i);
|
|
if (j !== i) {
|
|
var ivs = buf.substring(i, j).split(',');
|
|
set.ivs = {
|
|
hp: ivs[0] === '' ? 31 : Number(ivs[0]),
|
|
atk: ivs[1] === '' ? 31 : Number(ivs[1]),
|
|
def: ivs[2] === '' ? 31 : Number(ivs[2]),
|
|
spa: ivs[3] === '' ? 31 : Number(ivs[3]),
|
|
spd: ivs[4] === '' ? 31 : Number(ivs[4]),
|
|
spe: ivs[5] === '' ? 31 : Number(ivs[5])
|
|
};
|
|
}
|
|
i = j + 1;
|
|
|
|
// shiny
|
|
j = buf.indexOf('|', i);
|
|
if (i !== j) set.shiny = true;
|
|
i = j + 1;
|
|
|
|
// level
|
|
j = buf.indexOf('|', i);
|
|
if (i !== j) set.level = parseInt(buf.substring(i, j), 10);
|
|
i = j + 1;
|
|
|
|
// happiness
|
|
j = buf.indexOf(']', i);
|
|
if (j < 0) {
|
|
if (buf.substring(i)) {
|
|
set.happiness = Number(buf.substring(i));
|
|
}
|
|
break;
|
|
}
|
|
if (i !== j) set.happiness = Number(buf.substring(i, j));
|
|
i = j + 1;
|
|
}
|
|
|
|
return team;
|
|
};
|
|
|
|
Storage.unpackTeam = function (buf) {
|
|
if (!buf) return [];
|
|
|
|
var team = [];
|
|
var i = 0, j = 0;
|
|
|
|
while (true) {
|
|
var set = {};
|
|
team.push(set);
|
|
|
|
// name
|
|
j = buf.indexOf('|', i);
|
|
set.name = buf.substring(i, j);
|
|
i = j + 1;
|
|
|
|
// species
|
|
j = buf.indexOf('|', i);
|
|
set.species = Tools.getTemplate(buf.substring(i, j)).name || set.name;
|
|
i = j + 1;
|
|
|
|
// item
|
|
j = buf.indexOf('|', i);
|
|
set.item = Tools.getItem(buf.substring(i, j)).name;
|
|
i = j + 1;
|
|
|
|
// ability
|
|
j = buf.indexOf('|', i);
|
|
var ability = Tools.getAbility(buf.substring(i, j)).name;
|
|
var template = Tools.getTemplate(set.species);
|
|
set.ability = (template.abilities && ability in {'':1, 0:1, 1:1, H:1} ? template.abilities[ability || '0'] : ability);
|
|
i = j + 1;
|
|
|
|
// moves
|
|
j = buf.indexOf('|', i);
|
|
set.moves = buf.substring(i, j).split(',').map(function (moveid) {
|
|
return Tools.getMove(moveid).name;
|
|
});
|
|
i = j + 1;
|
|
|
|
// nature
|
|
j = buf.indexOf('|', i);
|
|
set.nature = buf.substring(i, j);
|
|
if (set.nature === 'undefined') set.nature = undefined;
|
|
i = j + 1;
|
|
|
|
// evs
|
|
j = buf.indexOf('|', i);
|
|
if (j !== i) {
|
|
var evs = buf.substring(i, j).split(',');
|
|
set.evs = {
|
|
hp: Number(evs[0]) || 0,
|
|
atk: Number(evs[1]) || 0,
|
|
def: Number(evs[2]) || 0,
|
|
spa: Number(evs[3]) || 0,
|
|
spd: Number(evs[4]) || 0,
|
|
spe: Number(evs[5]) || 0
|
|
};
|
|
}
|
|
i = j + 1;
|
|
|
|
// gender
|
|
j = buf.indexOf('|', i);
|
|
if (i !== j) set.gender = buf.substring(i, j);
|
|
i = j + 1;
|
|
|
|
// ivs
|
|
j = buf.indexOf('|', i);
|
|
if (j !== i) {
|
|
var ivs = buf.substring(i, j).split(',');
|
|
set.ivs = {
|
|
hp: ivs[0] === '' ? 31 : Number(ivs[0]),
|
|
atk: ivs[1] === '' ? 31 : Number(ivs[1]),
|
|
def: ivs[2] === '' ? 31 : Number(ivs[2]),
|
|
spa: ivs[3] === '' ? 31 : Number(ivs[3]),
|
|
spd: ivs[4] === '' ? 31 : Number(ivs[4]),
|
|
spe: ivs[5] === '' ? 31 : Number(ivs[5])
|
|
};
|
|
}
|
|
i = j + 1;
|
|
|
|
// shiny
|
|
j = buf.indexOf('|', i);
|
|
if (i !== j) set.shiny = true;
|
|
i = j + 1;
|
|
|
|
// level
|
|
j = buf.indexOf('|', i);
|
|
if (i !== j) set.level = parseInt(buf.substring(i, j), 10);
|
|
i = j + 1;
|
|
|
|
// happiness
|
|
j = buf.indexOf(']', i);
|
|
if (j < 0) {
|
|
if (buf.substring(i)) {
|
|
set.happiness = Number(buf.substring(i));
|
|
}
|
|
break;
|
|
}
|
|
if (i !== j) set.happiness = Number(buf.substring(i, j));
|
|
i = j + 1;
|
|
}
|
|
|
|
return team;
|
|
};
|
|
|
|
Storage.packedTeamNames = function (buf) {
|
|
if (!buf) return '';
|
|
|
|
var team = [];
|
|
var i = 0;
|
|
|
|
while (true) {
|
|
var name = buf.substring(i, buf.indexOf('|', i));
|
|
i = buf.indexOf('|', i) + 1;
|
|
|
|
team.push(buf.substring(i, buf.indexOf('|', i)) || name);
|
|
|
|
for (var k = 0; k < 9; k++) {
|
|
i = buf.indexOf('|', i) + 1;
|
|
}
|
|
|
|
i = buf.indexOf(']', i) + 1;
|
|
|
|
if (i < 1) break;
|
|
}
|
|
|
|
return team;
|
|
};
|
|
|
|
Storage.packedTeamIcons = function (buf) {
|
|
if (!buf) return '<em>(empty team)</em>';
|
|
|
|
return this.packedTeamNames(buf).map(function (species) {
|
|
return '<span class="pokemonicon" style="' + Tools.getIcon(species) + ';float:left;overflow:visible"><span style="font-size:0px">' + toId(species) + '</span></span>';
|
|
}).join('');
|
|
};
|
|
|
|
Storage.getTeamIcons = function (team) {
|
|
if (team.iconCache === '!') {
|
|
// an icon cache of '!' means that not only are the icons not cached,
|
|
// but the packed team isn't guaranteed to be updated to the latest
|
|
// changes from the teambuilder, either.
|
|
|
|
// we use Storage.activeSetList instead of reading from
|
|
// app.rooms.teambuilder.curSetList because the teambuilder
|
|
// room may have been closed by the time we need to get
|
|
// a packed team.
|
|
team.team = Storage.packTeam(Storage.activeSetList);
|
|
if ('teambuilder' in app.rooms) {
|
|
return Storage.packedTeamIcons(team.team);
|
|
}
|
|
Storage.activeSetList = null;
|
|
team.iconCache = Storage.packedTeamIcons(team.team);
|
|
} else if (!team.iconCache) {
|
|
team.iconCache = Storage.packedTeamIcons(team.team);
|
|
}
|
|
return team.iconCache;
|
|
};
|
|
|
|
Storage.getPackedTeam = function (team) {
|
|
if (!team) return null;
|
|
if (team.iconCache === '!') {
|
|
// see the same case in Storage.getTeamIcons
|
|
team.team = Storage.packTeam(Storage.activeSetList);
|
|
if (!('teambuilder' in app.rooms)) {
|
|
Storage.activeSetList = null;
|
|
team.iconCache = '';
|
|
}
|
|
}
|
|
if (typeof team.team !== 'string') {
|
|
// should never happen
|
|
team.team = Storage.packTeam(team.team);
|
|
}
|
|
return team.team;
|
|
};
|
|
|
|
Storage.importTeam = function (text, teams) {
|
|
var text = text.split("\n");
|
|
var team = [];
|
|
var curSet = null;
|
|
if (teams === true) {
|
|
Storage.teams = [];
|
|
teams = Storage.teams;
|
|
} else if (text.length === 1 || (text.length === 2 && !text[1])) {
|
|
return Storage.unpackTeam(text[0]);
|
|
}
|
|
for (var i = 0; i < text.length; i++) {
|
|
var line = $.trim(text[i]);
|
|
if (line === '' || line === '---') {
|
|
curSet = null;
|
|
} else if (line.substr(0, 3) === '===' && teams) {
|
|
team = [];
|
|
line = $.trim(line.substr(3, line.length - 6));
|
|
var format = '';
|
|
var bracketIndex = line.indexOf(']');
|
|
if (bracketIndex >= 0) {
|
|
format = line.substr(1, bracketIndex - 1);
|
|
line = $.trim(line.substr(bracketIndex + 1));
|
|
}
|
|
if (teams.length) {
|
|
teams[teams.length - 1].team = Storage.packTeam(teams[teams.length - 1].team);
|
|
}
|
|
teams.push({
|
|
name: line,
|
|
format: format,
|
|
team: team,
|
|
iconCache: ''
|
|
});
|
|
} else if (!curSet) {
|
|
curSet = {name: '', species: '', gender: ''};
|
|
team.push(curSet);
|
|
var atIndex = line.lastIndexOf(' @ ');
|
|
if (atIndex !== -1) {
|
|
curSet.item = line.substr(atIndex + 3);
|
|
if (toId(curSet.item) === 'noitem') curSet.item = '';
|
|
line = line.substr(0, atIndex);
|
|
}
|
|
if (line.substr(line.length - 4) === ' (M)') {
|
|
curSet.gender = 'M';
|
|
line = line.substr(0, line.length - 4);
|
|
}
|
|
if (line.substr(line.length - 4) === ' (F)') {
|
|
curSet.gender = 'F';
|
|
line = line.substr(0, line.length - 4);
|
|
}
|
|
var parenIndex = line.lastIndexOf(' (');
|
|
if (line.substr(line.length - 1) === ')' && parenIndex !== -1) {
|
|
line = line.substr(0, line.length - 1);
|
|
curSet.species = Tools.getTemplate(line.substr(parenIndex + 2)).name;
|
|
line = line.substr(0, parenIndex);
|
|
curSet.name = line;
|
|
} else {
|
|
curSet.species = Tools.getTemplate(line).name;
|
|
curSet.name = curSet.species;
|
|
}
|
|
} else if (line.substr(0, 7) === 'Trait: ') {
|
|
line = line.substr(7);
|
|
curSet.ability = line;
|
|
} else if (line.substr(0, 9) === 'Ability: ') {
|
|
line = line.substr(9);
|
|
curSet.ability = line;
|
|
} else if (line === 'Shiny: Yes') {
|
|
curSet.shiny = true;
|
|
} else if (line.substr(0, 7) === 'Level: ') {
|
|
line = line.substr(7);
|
|
curSet.level = +line;
|
|
} else if (line.substr(0, 11) === 'Happiness: ') {
|
|
line = line.substr(11);
|
|
curSet.happiness = +line;
|
|
} else if (line.substr(0, 9) === 'Ability: ') {
|
|
line = line.substr(9);
|
|
curSet.ability = line;
|
|
} else if (line.substr(0, 5) === 'EVs: ') {
|
|
line = line.substr(5);
|
|
var evLines = line.split('/');
|
|
curSet.evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
|
|
for (var j = 0; j < evLines.length; j++) {
|
|
var evLine = $.trim(evLines[j]);
|
|
var spaceIndex = evLine.indexOf(' ');
|
|
if (spaceIndex === -1) continue;
|
|
var statid = BattleStatIDs[evLine.substr(spaceIndex + 1)];
|
|
var statval = parseInt(evLine.substr(0, spaceIndex), 10);
|
|
if (!statid) continue;
|
|
curSet.evs[statid] = statval;
|
|
}
|
|
} else if (line.substr(0, 5) === 'IVs: ') {
|
|
line = line.substr(5);
|
|
var ivLines = line.split(' / ');
|
|
curSet.ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
|
|
for (var j = 0; j < ivLines.length; j++) {
|
|
var ivLine = ivLines[j];
|
|
var spaceIndex = ivLine.indexOf(' ');
|
|
if (spaceIndex === -1) continue;
|
|
var statid = BattleStatIDs[ivLine.substr(spaceIndex + 1)];
|
|
var statval = parseInt(ivLine.substr(0, spaceIndex), 10);
|
|
if (!statid) continue;
|
|
if (isNaN(statval)) statval = 31;
|
|
curSet.ivs[statid] = statval;
|
|
}
|
|
} else if (line.match(/^[A-Za-z]+ (N|n)ature/)) {
|
|
var natureIndex = line.indexOf(' Nature');
|
|
if (natureIndex === -1) natureIndex = line.indexOf(' nature');
|
|
if (natureIndex === -1) continue;
|
|
line = line.substr(0, natureIndex);
|
|
if (line !== 'undefined') curSet.nature = line;
|
|
} else if (line.substr(0, 1) === '-' || line.substr(0, 1) === '~') {
|
|
line = line.substr(1);
|
|
if (line.substr(0, 1) === ' ') line = line.substr(1);
|
|
if (!curSet.moves) curSet.moves = [];
|
|
if (line.substr(0, 14) === 'Hidden Power [') {
|
|
var hptype = line.substr(14, line.length - 15);
|
|
line = 'Hidden Power ' + hptype;
|
|
if (!curSet.ivs && window.BattleTypeChart) {
|
|
curSet.ivs = {};
|
|
for (var stat in window.BattleTypeChart[hptype].HPivs) {
|
|
curSet.ivs[stat] = window.BattleTypeChart[hptype].HPivs[stat];
|
|
}
|
|
}
|
|
}
|
|
if (line === 'Frustration') {
|
|
curSet.happiness = 0;
|
|
}
|
|
curSet.moves.push(line);
|
|
}
|
|
}
|
|
if (teams && teams.length) {
|
|
teams[teams.length - 1].team = Storage.packTeam(teams[teams.length - 1].team);
|
|
}
|
|
return team;
|
|
};
|
|
Storage.exportAllTeams = function () {
|
|
var buf = '';
|
|
for (var i = 0, len = Storage.teams.length; i < len; i++) {
|
|
var team = Storage.teams[i];
|
|
buf += '=== ' + (team.format ? '[' + team.format + '] ' : '') + team.name + ' ===\n\n';
|
|
buf += Storage.exportTeam(team.team);
|
|
buf += '\n';
|
|
}
|
|
return buf;
|
|
};
|
|
Storage.exportTeam = function (team) {
|
|
if (!team) return "";
|
|
if (typeof team === 'string') {
|
|
if (team.indexOf('\n') >= 0) return team;
|
|
team = Storage.unpackTeam(team);
|
|
}
|
|
var text = '';
|
|
for (var i = 0; i < team.length; i++) {
|
|
var curSet = team[i];
|
|
if (curSet.name !== curSet.species) {
|
|
text += '' + curSet.name + ' (' + curSet.species + ')';
|
|
} else {
|
|
text += '' + curSet.species;
|
|
}
|
|
if (curSet.gender === 'M') text += ' (M)';
|
|
if (curSet.gender === 'F') text += ' (F)';
|
|
if (curSet.item) {
|
|
text += ' @ ' + curSet.item;
|
|
}
|
|
text += " \n";
|
|
if (curSet.ability) {
|
|
text += 'Ability: ' + curSet.ability + " \n";
|
|
}
|
|
if (curSet.level && curSet.level != 100) {
|
|
text += 'Level: ' + curSet.level + " \n";
|
|
}
|
|
if (curSet.shiny) {
|
|
text += 'Shiny: Yes \n';
|
|
}
|
|
if (typeof curSet.happiness === 'number' && curSet.happiness !== 255 && !isNaN(curSet.happiness)) {
|
|
text += 'Happiness: ' + curSet.happiness + " \n";
|
|
}
|
|
var first = true;
|
|
if (curSet.evs) {
|
|
for (var j in BattleStatNames) {
|
|
if (!curSet.evs[j]) continue;
|
|
if (first) {
|
|
text += 'EVs: ';
|
|
first = false;
|
|
} else {
|
|
text += ' / ';
|
|
}
|
|
text += '' + curSet.evs[j] + ' ' + BattleStatNames[j];
|
|
}
|
|
}
|
|
if (!first) {
|
|
text += " \n";
|
|
}
|
|
if (curSet.nature) {
|
|
text += '' + curSet.nature + ' Nature' + " \n";
|
|
}
|
|
var first = true;
|
|
if (curSet.ivs) {
|
|
var defaultIvs = true;
|
|
var hpType = false;
|
|
for (var j = 0; j < curSet.moves.length; j++) {
|
|
var move = curSet.moves[j];
|
|
if (move.substr(0, 13) === 'Hidden Power ' && move.substr(0, 14) !== 'Hidden Power [') {
|
|
hpType = move.substr(13);
|
|
if (!exports.BattleTypeChart[hpType].HPivs) {
|
|
alert("That is not a valid Hidden Power type.");
|
|
continue;
|
|
}
|
|
for (var stat in BattleStatNames) {
|
|
if ((curSet.ivs[stat] === undefined ? 31 : curSet.ivs[stat]) !== (exports.BattleTypeChart[hpType].HPivs[stat] || 31)) {
|
|
defaultIvs = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (defaultIvs && !hpType) {
|
|
for (var stat in BattleStatNames) {
|
|
if (curSet.ivs[stat] !== 31 && typeof curSet.ivs[stat] !== undefined) {
|
|
defaultIvs = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!defaultIvs) {
|
|
for (var stat in BattleStatNames) {
|
|
if (typeof curSet.ivs[stat] === 'undefined' || isNaN(curSet.ivs[stat]) || curSet.ivs[stat] == 31) continue;
|
|
if (first) {
|
|
text += 'IVs: ';
|
|
first = false;
|
|
} else {
|
|
text += ' / ';
|
|
}
|
|
text += '' + curSet.ivs[stat] + ' ' + BattleStatNames[stat];
|
|
}
|
|
}
|
|
}
|
|
if (!first) {
|
|
text += " \n";
|
|
}
|
|
if (curSet.moves && curSet.moves) for (var j = 0; j < curSet.moves.length; j++) {
|
|
var move = curSet.moves[j];
|
|
if (move.substr(0, 13) === 'Hidden Power ') {
|
|
move = move.substr(0, 13) + '[' + move.substr(13) + ']';
|
|
}
|
|
text += '- ' + move + " \n";
|
|
}
|
|
text += "\n";
|
|
}
|
|
return text;
|
|
};
|
|
|
|
/*********************************************************
|
|
* Replay files
|
|
*********************************************************/
|
|
|
|
// Replay files are .html files that display a replay for a battle.
|
|
|
|
// The .html files mainly contain replay log data; the actual replay
|
|
// player is downloaded online. Also included is a textual log and
|
|
// some minimal CSS to make it look pretty, for offline viewing.
|
|
|
|
// This strategy helps keep the replay file reasonably small; of
|
|
// the 30 KB or so for a 50-turn battle, around 10 KB is the log
|
|
// data, and around 20 KB is the textual log.
|
|
|
|
// The actual replay player is downloaded from replay-embed.js,
|
|
// which handles loading all the necessary resources for turning the log
|
|
// data into a playable replay.
|
|
|
|
// Battle log data is stored in and loaded from a
|
|
// <script type="text/plain" class="battle-log-data"> tag.
|
|
|
|
// replay-embed.js is loaded through a cache-buster that rotates daily.
|
|
// This allows pretty much anything about the replay viewer to be
|
|
// updated as desired.
|
|
|
|
Storage.createReplayFile = function (room) {
|
|
var battle = room.battle;
|
|
var replayid = room.id.slice(7);
|
|
if (Config.server.id !== 'showdown') {
|
|
if (!Config.server.registered) {
|
|
replayid = 'unregisteredserver-' + replayid;
|
|
} else {
|
|
replayid = Config.server.id + '-' + replayid;
|
|
}
|
|
}
|
|
battle.fastForwardTo(-1);
|
|
var buf = '<!DOCTYPE html>\n';
|
|
buf += '<meta charset="utf-8" />\n';
|
|
buf += '<!-- version 1 -->\n';
|
|
buf += '<title>' + Tools.escapeHTML(battle.tier) + ' replay: ' + Tools.escapeHTML(battle.p1.name) + ' vs. ' + Tools.escapeHTML(battle.p2.name) + '</title>\n';
|
|
buf += '<style>\n';
|
|
buf += 'html,body {font-family:Verdana, sans-serif;font-size:10pt;margin:0;padding:0;}body{padding:12px 0;} .battle-log {font-family:Verdana, sans-serif;font-size:10pt;} .battle-log-inline {border:1px solid #AAAAAA;background:#EEF2F5;color:black;max-width:640px;margin:0 auto 80px;padding-bottom:5px;} .battle-log .inner {padding:4px 8px 0px 8px;} .battle-log .inner-preempt {padding:0 8px 4px 8px;} .battle-log .inner-after {margin-top:0.5em;} .battle-log h2 {margin:0.5em -8px;padding:4px 8px;border:1px solid #AAAAAA;background:#E0E7EA;border-left:0;border-right:0;font-family:Verdana, sans-serif;font-size:13pt;} .battle-log .chat {vertical-align:middle;padding:3px 0 3px 0;font-size:8pt;} .battle-log .chat strong {color:#40576A;} .battle-log .chat em {padding:1px 4px 1px 3px;color:#000000;font-style:normal;} .chat.mine {background:rgba(0,0,0,0.05);margin-left:-8px;margin-right:-8px;padding-left:8px;padding-right:8px;} .spoiler {color:#BBBBBB;background:#BBBBBB;padding:0px 3px;} .spoiler:hover, .spoiler:active, .spoiler-shown {color:#000000;background:#E2E2E2;padding:0px 3px;} .spoiler a {color:#BBBBBB;} .spoiler:hover a, .spoiler:active a, .spoiler-shown a {color:#2288CC;} .chat code, .chat .spoiler:hover code, .chat .spoiler:active code, .chat .spoiler-shown code {border:1px solid #C0C0C0;background:#EEEEEE;color:black;padding:0 2px;} .chat .spoiler code {border:1px solid #CCCCCC;background:#CCCCCC;color:#CCCCCC;} .battle-log .rated {padding:3px 4px;} .battle-log .rated strong {color:white;background:#89A;padding:1px 4px;border-radius:4px;} .spacer {margin-top:0.5em;} .message-announce {background:#6688AA;color:white;padding:1px 4px 2px;} .message-announce a, .broadcast-green a, .broadcast-blue a, .broadcast-red a {color:#DDEEFF;} .broadcast-green {background-color:#559955;color:white;padding:2px 4px;} .broadcast-blue {background-color:#6688AA;color:white;padding:2px 4px;} .infobox {border:1px solid #6688AA;padding:2px 4px;} .infobox-limited {max-height:200px;overflow:auto;overflow-x:hidden;} .broadcast-red {background-color:#AA5544;color:white;padding:2px 4px;} .message-learn-canlearn {font-weight:bold;color:#228822;text-decoration:underline;} .message-learn-cannotlearn {font-weight:bold;color:#CC2222;text-decoration:underline;} .message-effect-weak {font-weight:bold;color:#CC2222;} .message-effect-resist {font-weight:bold;color:#6688AA;} .message-effect-immune {font-weight:bold;color:#666666;} .message-learn-list {margin-top:0;margin-bottom:0;} .message-throttle-notice, .message-error {color:#992222;} .message-overflow, .chat small.message-overflow {font-size:0pt;} .message-overflow::before {font-size:9pt;content:\'...\';} .subtle {color:#3A4A66;}\n';
|
|
buf += '</style>\n';
|
|
buf += '<div class="wrapper replay-wrapper" style="max-width:1180px;margin:0 auto">\n';
|
|
buf += '<input type="hidden" name="replayid" value="' + replayid + '" />\n';
|
|
buf += '<div class="battle"></div><div class="battle-log"></div><div class="replay-controls"></div><div class="replay-controls-2"></div>\n';
|
|
buf += '<h1 style="font-weight:normal;text-align:center"><strong>' + Tools.escapeHTML(battle.tier) + '</strong><br /><a href="http://pokemonshowdown.com/users/' + toId(battle.p1.name) + '" class="subtle" target="_blank">' + Tools.escapeHTML(battle.p1.name) + '</a> vs. <a href="http://pokemonshowdown.com/users/' + toId(battle.p2.name) + '" class="subtle" target="_blank">' + Tools.escapeHTML(battle.p2.name) + '</a></h1>\n';
|
|
buf += '<script type="text/plain" class="battle-log-data">' + battle.activityQueue.join('\n').replace(/\//g, '\\/') + '</script>\n';
|
|
buf += '</div>\n';
|
|
buf += '<div class="battle-log battle-log-inline"><div class="inner">' + battle.logElem.html() + '</div></div>\n';
|
|
buf += '</div>\n';
|
|
buf += '<script>\n';
|
|
buf += 'var daily = Math.floor(Date.now()/1000/60/60/24);document.write(\'<script src="https://play.pokemonshowdown.com/js/replay-embed.js?version\'+daily+\'"></\'+\'script>\');\n';
|
|
buf += '</script>\n';
|
|
return buf;
|
|
};
|
|
|
|
Storage.createReplayFileHref = function (room) {
|
|
return 'data:text/plain;base64,' + encodeURIComponent(window.btoa(unescape(encodeURIComponent(Storage.createReplayFile(room)))));
|
|
};
|
|
|
|
/*********************************************************
|
|
* Node-webkit
|
|
*********************************************************/
|
|
|
|
Storage.initDirectory = function () {
|
|
var self = this;
|
|
|
|
var dir = process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH;
|
|
if (!(dir.charAt(dir.length - 1) in {'/': 1, '\\': 1})) dir += '/';
|
|
fs.stat(dir + 'Documents', function (err, stats) {
|
|
if (err || !stats.isDirectory()) {
|
|
fs.stat(dir + 'My Documents', function (err, stats) {
|
|
if (err || !stats.isDirectory()) {
|
|
self.documentsDir = dir;
|
|
} else {
|
|
self.documentsDir = dir + 'My Documents/';
|
|
}
|
|
self.initDirectory2();
|
|
});
|
|
} else {
|
|
self.documentsDir = dir + 'Documents/';
|
|
self.initDirectory2();
|
|
}
|
|
});
|
|
};
|
|
|
|
Storage.initDirectory2 = function () {
|
|
var self = this;
|
|
fs.mkdir(self.documentsDir + 'My Games', function () {
|
|
fs.mkdir(self.documentsDir + 'My Games/Pokemon Showdown', function () {
|
|
fs.stat(self.documentsDir + 'My Games/Pokemon Showdown', function (err, stats) {
|
|
if (err) return;
|
|
if (stats.isDirectory()) {
|
|
self.dir = self.documentsDir + 'My Games/Pokemon Showdown/';
|
|
fs.mkdir(self.dir + 'Logs', function () {});
|
|
fs.mkdir(self.dir + 'Teams', function () {});
|
|
|
|
// load teams
|
|
self.nwLoadTeams();
|
|
self.saveAllTeams = self.nwSaveAllTeams;
|
|
self.saveTeam = self.nwSaveTeam;
|
|
self.deleteTeam = self.nwDeleteTeam;
|
|
|
|
// logging
|
|
if (Tools.prefs('logchat')) self.startLoggingChat();
|
|
}
|
|
});
|
|
});
|
|
});
|
|
};
|
|
|
|
Storage.revealFolder = function () {
|
|
gui.Shell.openItem(this.dir);
|
|
};
|
|
|
|
// teams
|
|
|
|
Storage.nwLoadTeams = function () {
|
|
var self = this;
|
|
var localApp = window.app;
|
|
fs.readdir(this.dir + 'Teams', function (err, files) {
|
|
if (err) return;
|
|
self.teams = [];
|
|
self.nwTeamsLeft = files.length;
|
|
if (!self.nwTeamsLeft) {
|
|
self.nwFinishedLoadingTeams(localApp);
|
|
}
|
|
for (var i = 0; i < files.length; i++) {
|
|
self.nwLoadTeamFile(files[i], localApp);
|
|
}
|
|
});
|
|
};
|
|
|
|
Storage.nwLoadTeamFile = function (filename, localApp) {
|
|
var self = this;
|
|
var line = filename;
|
|
if (line.substr(line.length - 4).toLowerCase() === '.txt') {
|
|
line = line.substr(0, line.length - 4);
|
|
} else {
|
|
// not a team file
|
|
self.nwTeamsLeft--;
|
|
if (!self.nwTeamsLeft) {
|
|
self.nwFinishedLoadingTeams(localApp);
|
|
}
|
|
return;
|
|
}
|
|
var format = '';
|
|
var bracketIndex = line.indexOf(']');
|
|
if (bracketIndex >= 0) {
|
|
format = line.substr(1, bracketIndex - 1);
|
|
line = $.trim(line.substr(bracketIndex + 1));
|
|
}
|
|
fs.readFile(this.dir + 'Teams/' + filename, function (err, data) {
|
|
if (!err) {
|
|
self.teams.push({
|
|
name: line,
|
|
format: format,
|
|
team: Storage.packTeam(Storage.importTeam('' + data)),
|
|
iconCache: '',
|
|
filename: filename
|
|
});
|
|
self.nwTeamsLeft--;
|
|
if (!self.nwTeamsLeft) {
|
|
self.nwFinishedLoadingTeams(localApp);
|
|
}
|
|
}
|
|
});
|
|
};
|
|
|
|
Storage.nwFinishedLoadingTeams = function (app) {
|
|
this.teams.sort(this.teamCompare);
|
|
Storage.whenTeamsLoaded.load();
|
|
};
|
|
|
|
Storage.teamCompare = function (a, b) {
|
|
if (a.name > b.name) return 1;
|
|
if (a.name < b.name) return -1;
|
|
return 0;
|
|
};
|
|
|
|
Storage.nwDeleteAllTeams = function (callback) {
|
|
var self = this;
|
|
fs.readdir(this.dir + 'Teams', function (err, files) {
|
|
if (err) return;
|
|
self.nwTeamsLeft = files.length;
|
|
if (!self.nwTeamsLeft) {
|
|
callback();
|
|
return;
|
|
}
|
|
for (var i = 0; i < files.length; i++) {
|
|
self.nwDeleteTeamFile(files[i], callback);
|
|
}
|
|
});
|
|
};
|
|
|
|
Storage.nwDeleteTeamFile = function (filename, callback) {
|
|
var self = this;
|
|
var line = filename;
|
|
if (line.substr(line.length - 4).toLowerCase() === '.txt') {
|
|
line = line.substr(0, line.length - 4);
|
|
} else {
|
|
// not a team file
|
|
self.nwTeamsLeft--;
|
|
if (!self.nwTeamsLeft) callback();
|
|
return;
|
|
}
|
|
fs.unlink(this.dir + 'Teams/' + filename, function (err) {
|
|
self.nwTeamsLeft--;
|
|
if (!self.nwTeamsLeft) callback();
|
|
});
|
|
};
|
|
|
|
Storage.nwSaveTeam = function (team) {
|
|
if (!team) return;
|
|
var filename = team.name + '.txt';
|
|
if (team.format) filename = '[' + team.format + '] ' + filename;
|
|
filename = $.trim(filename).replace(/[\\\/]+/g, '');
|
|
|
|
if (team.filename && filename !== team.filename) {
|
|
fs.unlink(this.dir + 'Teams/' + team.filename, function () {});
|
|
}
|
|
team.filename = filename;
|
|
fs.writeFile(this.dir + 'Teams/' + filename, Storage.exportTeam(team.team).replace(/\n/g, '\r\n'));
|
|
};
|
|
|
|
Storage.nwDeleteTeam = function (team) {
|
|
if (team.filename) {
|
|
fs.unlink(this.dir + 'Teams/' + team.filename, function () {});
|
|
delete team.filename;
|
|
}
|
|
};
|
|
|
|
Storage.nwSaveAllTeams = function () {
|
|
var self = this;
|
|
this.nwDeleteAllTeams(function () {
|
|
self.nwDoSaveAllTeams();
|
|
});
|
|
};
|
|
|
|
Storage.nwDoSaveAllTeams = function () {
|
|
for (var i = 0; i < this.teams.length; i++) {
|
|
var team = this.teams[i];
|
|
var filename = team.name + '.txt';
|
|
if (team.format) filename = '[' + team.format + '] ' + filename;
|
|
filename = $.trim(filename).replace(/[\\\/]+/g, '');
|
|
|
|
team.filename = filename;
|
|
fs.writeFile(this.dir + 'Teams/' + filename, Storage.exportTeam(team.team).replace(/\n/g, '\r\n'));
|
|
}
|
|
};
|
|
|
|
// logs
|
|
|
|
Storage.getLogMonth = function () {
|
|
var now = new Date();
|
|
var month = '' + (now.getMonth() + 1);
|
|
if (month.length < 2) month = '0' + month;
|
|
return '' + now.getFullYear() + '-' + month;
|
|
};
|
|
Storage.nwStartLoggingChat = function () {
|
|
var self = this;
|
|
if (!self.documentsDir) return; // too early; initDirectory2 will call us when it's time
|
|
if (self.loggingChat) return;
|
|
// callback hell! ^_^
|
|
fs.mkdir(self.dir + 'Logs', function () {
|
|
self.chatLogFdMonth = self.getLogMonth();
|
|
fs.mkdir(self.dir + 'Logs/' + self.chatLogFdMonth, function () {
|
|
fs.stat(self.dir + 'Logs/' + self.chatLogFdMonth, function (err, stats) {
|
|
if (err) return;
|
|
if (stats.isDirectory()) {
|
|
self.loggingChat = true;
|
|
self.chatLogStreams = {};
|
|
}
|
|
});
|
|
});
|
|
});
|
|
};
|
|
Storage.nwStopLoggingChat = function () {
|
|
if (!this.loggingChat) return;
|
|
this.loggingChat = false;
|
|
var streams = this.chatLogStreams;
|
|
this.chatLogStreams = null;
|
|
for (var i in streams) {
|
|
streams[i].end();
|
|
}
|
|
};
|
|
Storage.nwLogChat = function (roomid, line) {
|
|
roomid = toRoomid(roomid);
|
|
var self = this;
|
|
if (!this.loggingChat) return;
|
|
var chatLogFdMonth = this.getLogMonth();
|
|
if (chatLogFdMonth !== this.chatLogFdMonth) {
|
|
this.chatLogFdMonth = chatLogFdMonth;
|
|
var streams = this.chatLogStreams;
|
|
this.chatLogStreams = {};
|
|
for (var i in streams) {
|
|
streams[i].end();
|
|
}
|
|
}
|
|
|
|
var now = new Date();
|
|
var hours = '' + now.getHours();
|
|
if (hours.length < 2) hours = '0' + hours;
|
|
var minutes = '' + now.getMinutes();
|
|
if (minutes.length < 2) minutes = '0' + minutes;
|
|
var timestamp = '[' + hours + ':' + minutes + '] ';
|
|
|
|
if (!this.chatLogStreams[roomid]) {
|
|
this.chatLogStreams[roomid] = fs.createWriteStream(this.dir + 'Logs/' + chatLogFdMonth + '/' + roomid + '.txt', {flags: 'a'});
|
|
this.chatLogStreams[roomid].write('\n\n\nLog starting ' + now + '\n\n');
|
|
}
|
|
this.chatLogStreams[roomid].write(timestamp + line + '\n');
|
|
};
|
|
|
|
// saving
|
|
|
|
Storage.startLoggingChat = function () {};
|
|
Storage.stopLoggingChat = function () {};
|
|
Storage.logChat = function () {};
|
|
|
|
Storage.initialize();
|