mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-03-21 17:50:29 -05:00
Implement new battle-text-parser
All battle text messages have been moved out of `src/battle.ts` and into its own file `data/text.js`. Code for handling this is in the new files `src/battle-log.ts` and `src/battle-text-parser.ts`. `data/text.js` is now extremely self-contained, and nearly ready for translation support! This is a significant modernization of battle.ts. In addition to moving messages out: Functions for getting names (`pokemon.getLowerName()` etc) have been removed. `battle.minorQueue` has been removed. Minor lines are now processed directly on the main queue, with a new `battle.waitForAnimations` flag to decide whether or not the main queue should wait for animations to finish before moving on to the next line. `battle.waitForResult()` and `battle.endPrevAction()` have been removed. These confusingly-named functions closed the messagebar (and flush the minor queue). They've been replaced with `scene.maybeCloseMessagebar()`. `pokemon.markMove()` and `pokemon.markAbility()` have been renamed `pokemon.rememberMove()` and `pokemon.rememberAbility()`.
This commit is contained in:
parent
bf002f9450
commit
fd89a66510
|
|
@ -5,6 +5,7 @@ node_modules/
|
|||
/js/battle.js
|
||||
/js/battledata.js
|
||||
/js/battle-log.js
|
||||
/js/battle-text-parser.js
|
||||
/js/battle-dex.js
|
||||
/js/battle-dex-data.js
|
||||
/js/battle-animations-moves.js
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -16,6 +16,7 @@ package-lock.json
|
|||
/js/battle.js
|
||||
/js/battledata.js
|
||||
/js/battle-log.js
|
||||
/js/battle-text-parser.js
|
||||
/js/battle-dex.js
|
||||
/js/battle-dex-data.js
|
||||
/js/battle-animations-moves.js
|
||||
|
|
|
|||
|
|
@ -60,7 +60,9 @@ if (!ignoreGraphics) {
|
|||
fs.writeFileSync(
|
||||
'data/graphics.js',
|
||||
fs.readFileSync('js/battle-animations.js') + '\n\n' +
|
||||
fs.readFileSync('js/battle-animations-moves.js')
|
||||
fs.readFileSync('js/battle-animations-moves.js') + '\n\n' +
|
||||
fs.readFileSync('data/text.js') + '\n\n' +
|
||||
fs.readFileSync('js/battle-text-parser.js')
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
1100
data/text.js
Normal file
1100
data/text.js
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -406,7 +406,13 @@ var BattleTooltips = (function () {
|
|||
var gender = pokemon.gender;
|
||||
if (gender) gender = ' <img src="' + Tools.resourcePrefix + 'fx/gender-' + gender.toLowerCase() + '.png" alt="' + gender + '" />';
|
||||
text = '<div class="tooltipinner"><div class="tooltip">';
|
||||
text += '<h2>' + pokemon.getFullName() + gender + (pokemon.level !== 100 ? ' <small>L' + pokemon.level + '</small>' : '') + '<br />';
|
||||
|
||||
var name = BattleLog.escapeHTML(pokemon.name);
|
||||
if (pokemon.species !== pokemon.name) {
|
||||
name += ' <small>(' + BattleLog.escapeHTML(pokemon.species) + ')</small>';
|
||||
}
|
||||
|
||||
text += '<h2>' + name + gender + (pokemon.level !== 100 ? ' <small>L' + pokemon.level + '</small>' : '') + '<br />';
|
||||
|
||||
var template = Tools.getTemplate(pokemon.getSpecies ? pokemon.getSpecies() : pokemon.species);
|
||||
if (pokemon.volatiles && pokemon.volatiles.formechange) {
|
||||
|
|
|
|||
|
|
@ -955,7 +955,6 @@
|
|||
pokemonData.getFormattedRange = Pokemon.prototype.getFormattedRange;
|
||||
pokemonData.getHPColorClass = Pokemon.prototype.getHPColorClass;
|
||||
pokemonData.getHPColor = Pokemon.prototype.getHPColor;
|
||||
pokemonData.getFullName = Pokemon.prototype.getFullName;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -924,6 +924,86 @@
|
|||
app.addPopup(UserPopup, {name: target});
|
||||
});
|
||||
}
|
||||
}, {
|
||||
parseChatMessage: function (message, name, timestamp, isHighlighted, $chatElem) {
|
||||
var showMe = !((Tools.prefs('chatformatting') || {}).hideme);
|
||||
var group = ' ';
|
||||
if (!/[A-Za-z0-9]/.test(name.charAt(0))) {
|
||||
// Backwards compatibility
|
||||
group = name.charAt(0);
|
||||
name = name.substr(1);
|
||||
}
|
||||
var color = BattleLog.hashColor(toId(name));
|
||||
var clickableName = '<small>' + BattleLog.escapeHTML(group) + '</small><span class="username" data-name="' + BattleLog.escapeHTML(name) + '">' + BattleLog.escapeHTML(name) + '</span>';
|
||||
var hlClass = isHighlighted ? ' highlighted' : '';
|
||||
var mineClass = (window.app && app.user && app.user.get('name') === name ? ' mine' : '');
|
||||
|
||||
var cmd = '';
|
||||
var target = '';
|
||||
if (message.charAt(0) === '/') {
|
||||
if (message.charAt(1) === '/') {
|
||||
message = message.slice(1);
|
||||
} else {
|
||||
var spaceIndex = message.indexOf(' ');
|
||||
cmd = (spaceIndex >= 0 ? message.slice(1, spaceIndex) : message.slice(1));
|
||||
if (spaceIndex >= 0) target = message.slice(spaceIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
switch (cmd) {
|
||||
case 'me':
|
||||
if (!showMe) return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em>/me' + BattleLog.parseMessage(' ' + target) + '</em></div>';
|
||||
return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">•</strong> <em>' + clickableName + '<i>' + BattleLog.parseMessage(' ' + target) + '</i></em></div>';
|
||||
case 'mee':
|
||||
if (!showMe) return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em>/me' + BattleLog.parseMessage(' ' + target).slice(1) + '</em></div>';
|
||||
return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">•</strong> <em>' + clickableName + '<i>' + BattleLog.parseMessage(' ' + target).slice(1) + '</i></em></div>';
|
||||
case 'invite':
|
||||
var roomid = toRoomid(target);
|
||||
return [
|
||||
'<div class="chat">' + timestamp + '<em>' + clickableName + ' invited you to join the room "' + roomid + '"</em></div>',
|
||||
'<div class="notice"><button name="joinRoom" value="' + roomid + '">Join ' + roomid + '</button></div>'
|
||||
];
|
||||
case 'announce':
|
||||
return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <span class="message-announce">' + BattleLog.parseMessage(target) + '</span></div>';
|
||||
case 'log':
|
||||
return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<span class="message-log">' + BattleLog.parseMessage(target) + '</span></div>';
|
||||
case 'data-pokemon':
|
||||
case 'data-item':
|
||||
case 'data-ability':
|
||||
case 'data-move':
|
||||
return '[outdated message type not supported]';
|
||||
case 'text':
|
||||
return '<div class="chat">' + BattleLog.parseMessage(target) + '</div>';
|
||||
case 'error':
|
||||
return '<div class="chat message-error">' + BattleLog.escapeHTML(target) + '</div>';
|
||||
case 'html':
|
||||
return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em>' + BattleLog.sanitizeHTML(target) + '</em></div>';
|
||||
case 'uhtml':
|
||||
case 'uhtmlchange':
|
||||
var parts = target.split(',');
|
||||
var $elements = $chatElem.find('div.uhtml-' + toId(parts[0]));
|
||||
var html = parts.slice(1).join(',');
|
||||
if (!html) {
|
||||
$elements.remove();
|
||||
} else if (!$elements.length) {
|
||||
$chatElem.append('<div class="chat uhtml-' + toId(parts[0]) + '">' + BattleLog.sanitizeHTML(html) + '</div>');
|
||||
} else if (cmd === 'uhtmlchange') {
|
||||
$elements.html(BattleLog.sanitizeHTML(html));
|
||||
} else {
|
||||
$elements.remove();
|
||||
$chatElem.append('<div class="chat uhtml-' + toId(parts[0]) + '">' + BattleLog.sanitizeHTML(html) + '</div>');
|
||||
}
|
||||
return '';
|
||||
case 'raw':
|
||||
return '<div class="chat">' + BattleLog.sanitizeHTML(target) + '</div>';
|
||||
default:
|
||||
// Not a command or unsupported. Parsed as a normal chat message.
|
||||
if (!name) {
|
||||
return '<div class="chat' + hlClass + '">' + timestamp + '<em>' + BattleLog.parseMessage(message) + '</em></div>';
|
||||
}
|
||||
return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em>' + BattleLog.parseMessage(message) + '</em></div>';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var FormatPopup = this.FormatPopup = this.Popup.extend({
|
||||
|
|
|
|||
|
|
@ -3366,9 +3366,6 @@ const BattleMoveAnims: AnimTable = {
|
|||
}, 'accel');
|
||||
},
|
||||
prepareAnim: BattleOtherAnims.chargestatus.anim,
|
||||
prepareMessage(pokemon) {
|
||||
return pokemon.getName() + ' is absorbing power!';
|
||||
}
|
||||
},
|
||||
magnetrise: {
|
||||
anim(scene, [attacker]) {
|
||||
|
|
@ -3991,9 +3988,6 @@ const BattleMoveAnims: AnimTable = {
|
|||
attacker.anim({opacity: 0, time: 50}, 'linear');
|
||||
scene.wait(200);
|
||||
},
|
||||
prepareMessage(pokemon) {
|
||||
return pokemon.getName() + ' vanished instantly!';
|
||||
}
|
||||
},
|
||||
bounce: {
|
||||
anim(scene, [attacker, defender]) {
|
||||
|
|
@ -4074,9 +4068,6 @@ const BattleMoveAnims: AnimTable = {
|
|||
time: 300
|
||||
}, 'linear');
|
||||
},
|
||||
prepareMessage(pokemon) {
|
||||
return pokemon.getName() + ' sprang up!';
|
||||
}
|
||||
},
|
||||
dig: {
|
||||
anim(scene, [attacker, defender]) {
|
||||
|
|
@ -4157,9 +4148,6 @@ const BattleMoveAnims: AnimTable = {
|
|||
time: 300
|
||||
}, 'linear');
|
||||
},
|
||||
prepareMessage(pokemon) {
|
||||
return pokemon.getName() + ' burrowed its way under the ground!';
|
||||
}
|
||||
},
|
||||
dive: {
|
||||
anim(scene, [attacker, defender]) {
|
||||
|
|
@ -4238,9 +4226,6 @@ const BattleMoveAnims: AnimTable = {
|
|||
time: 300
|
||||
}, 'swing');
|
||||
},
|
||||
prepareMessage(pokemon) {
|
||||
return pokemon.getName() + ' hid underwater!';
|
||||
}
|
||||
},
|
||||
fly: {
|
||||
anim: BattleOtherAnims.flight.anim,
|
||||
|
|
@ -4251,9 +4236,6 @@ const BattleMoveAnims: AnimTable = {
|
|||
time: 300
|
||||
}, 'linear');
|
||||
},
|
||||
prepareMessage(pokemon) {
|
||||
return pokemon.getName() + ' flew up high!';
|
||||
}
|
||||
},
|
||||
skydrop: {
|
||||
anim: BattleOtherAnims.contactattack.anim,
|
||||
|
|
@ -4269,9 +4251,6 @@ const BattleMoveAnims: AnimTable = {
|
|||
time: 300
|
||||
}, 'linear');
|
||||
},
|
||||
prepareMessage(pokemon, pokemon2) {
|
||||
return pokemon.getName() + ' took ' + pokemon2.getLowerName() + ' into the sky!';
|
||||
}
|
||||
},
|
||||
skullbash: {
|
||||
anim: BattleOtherAnims.contactattack.anim,
|
||||
|
|
@ -4281,9 +4260,6 @@ const BattleMoveAnims: AnimTable = {
|
|||
time: 300
|
||||
}, 'linear');
|
||||
},
|
||||
prepareMessage(pokemon) {
|
||||
return pokemon.getName() + ' tucked in its head!';
|
||||
}
|
||||
},
|
||||
skyattack: {
|
||||
anim: BattleOtherAnims.flight.anim,
|
||||
|
|
@ -4293,9 +4269,6 @@ const BattleMoveAnims: AnimTable = {
|
|||
time: 300
|
||||
}, 'linear');
|
||||
},
|
||||
prepareMessage(pokemon) {
|
||||
return pokemon.getName() + ' became cloaked in a harsh light!';
|
||||
}
|
||||
},
|
||||
hiddenpower: {
|
||||
anim(scene, [attacker, defender]) {
|
||||
|
|
@ -9171,9 +9144,6 @@ const BattleMoveAnims: AnimTable = {
|
|||
}, 'linear', 'explode');
|
||||
},
|
||||
prepareAnim: BattleOtherAnims.chargestatus.anim,
|
||||
prepareMessage(pokemon) {
|
||||
return pokemon.getName() + ' set a shell trap!';
|
||||
}
|
||||
},
|
||||
flamecharge: {
|
||||
anim(scene, [attacker, defender]) {
|
||||
|
|
@ -18806,9 +18776,6 @@ const BattleMoveAnims: AnimTable = {
|
|||
}, 'linear', 'explode');
|
||||
},
|
||||
prepareAnim: BattleOtherAnims.chargestatus.anim,
|
||||
prepareMessage(pokemon) {
|
||||
return pokemon.getName() + ' absorbed light!';
|
||||
}
|
||||
},
|
||||
solarblade: {
|
||||
anim(scene, [attacker, defender]) {
|
||||
|
|
@ -18932,9 +18899,6 @@ const BattleMoveAnims: AnimTable = {
|
|||
}, 'swing');
|
||||
},
|
||||
prepareAnim: BattleOtherAnims.chargestatus.anim,
|
||||
prepareMessage(pokemon) {
|
||||
return pokemon.getName() + ' absorbed light!';
|
||||
}
|
||||
},
|
||||
lightofruin: {
|
||||
anim(scene, [attacker, defender]) {
|
||||
|
|
@ -19294,9 +19258,6 @@ const BattleMoveAnims: AnimTable = {
|
|||
}, 'linear');
|
||||
},
|
||||
prepareAnim: BattleOtherAnims.selfstatus.anim,
|
||||
prepareMessage(pokemon) {
|
||||
return pokemon.getName() + ' became cloaked in a freezing light!';
|
||||
}
|
||||
},
|
||||
iceburn: {
|
||||
anim(scene, [attacker, defender]) {
|
||||
|
|
@ -19416,16 +19377,10 @@ const BattleMoveAnims: AnimTable = {
|
|||
}, 'linear');
|
||||
},
|
||||
prepareAnim: BattleOtherAnims.selfstatus.anim,
|
||||
prepareMessage(pokemon) {
|
||||
return pokemon.getName() + ' became cloaked in freezing air!';
|
||||
}
|
||||
},
|
||||
razorwind: {
|
||||
anim: null!,
|
||||
prepareAnim: BattleOtherAnims.selfstatus.anim,
|
||||
prepareMessage(pokemon) {
|
||||
return pokemon.getName() + ' whipped up a whirlwind!';
|
||||
}
|
||||
},
|
||||
overheat: {
|
||||
anim(scene, [attacker, defender]) {
|
||||
|
|
@ -30711,7 +30666,7 @@ BattleMoveAnims['flatter'] = {anim:BattleMoveAnims['attract'].anim};
|
|||
|
||||
BattleMoveAnims['armthrust'] = {anim:BattleMoveAnims['smellingsalts'].anim, multihit:true};
|
||||
|
||||
BattleMoveAnims['phantomforce'] = {anim:BattleMoveAnims['shadowforce'].anim, prepareAnim:BattleMoveAnims['shadowforce'].prepareAnim, prepareMessage:BattleMoveAnims['shadowforce'].prepareMessage};
|
||||
BattleMoveAnims['phantomforce'] = {anim:BattleMoveAnims['shadowforce'].anim, prepareAnim:BattleMoveAnims['shadowforce'].prepareAnim};
|
||||
BattleMoveAnims['shadowstrike'] = {anim:BattleMoveAnims['shadowforce'].anim};
|
||||
|
||||
BattleMoveAnims['smackdown'] = {anim:BattleMoveAnims['rockblast'].anim};
|
||||
|
|
|
|||
|
|
@ -115,27 +115,25 @@ class BattleScene {
|
|||
activeCount = 1;
|
||||
|
||||
numericId = 0;
|
||||
$frame: JQuery<HTMLElement>;
|
||||
$battle: JQuery<HTMLElement> = null!;
|
||||
$logFrame: JQuery<HTMLElement>;
|
||||
$options: JQuery<HTMLElement> = null!;
|
||||
$logPreempt: JQuery<HTMLElement> = null!;
|
||||
$log: JQuery<HTMLElement> = null!;
|
||||
$terrain: JQuery<HTMLElement> = null!;
|
||||
$weather: JQuery<HTMLElement> = null!;
|
||||
$bgEffect: JQuery<HTMLElement> = null!;
|
||||
$bg: JQuery<HTMLElement> = null!;
|
||||
$sprite: JQuery<HTMLElement> = null!;
|
||||
$sprites: [JQuery<HTMLElement>, JQuery<HTMLElement>] = [null!, null!];
|
||||
$spritesFront: [JQuery<HTMLElement>, JQuery<HTMLElement>] = [null!, null!];
|
||||
$stat: JQuery<HTMLElement> = null!;
|
||||
$fx: JQuery<HTMLElement> = null!;
|
||||
$leftbar: JQuery<HTMLElement> = null!;
|
||||
$rightbar: JQuery<HTMLElement> = null!;
|
||||
$turn: JQuery<HTMLElement> = null!;
|
||||
$messagebar: JQuery<HTMLElement> = null!;
|
||||
$delay: JQuery<HTMLElement> = null!;
|
||||
$hiddenMessage: JQuery<HTMLElement> = null!;
|
||||
$frame: JQuery;
|
||||
$battle: JQuery = null!;
|
||||
$options: JQuery = null!;
|
||||
log: BattleLog;
|
||||
$terrain: JQuery = null!;
|
||||
$weather: JQuery = null!;
|
||||
$bgEffect: JQuery = null!;
|
||||
$bg: JQuery = null!;
|
||||
$sprite: JQuery = null!;
|
||||
$sprites: [JQuery, JQuery] = [null!, null!];
|
||||
$spritesFront: [JQuery, JQuery] = [null!, null!];
|
||||
$stat: JQuery = null!;
|
||||
$fx: JQuery = null!;
|
||||
$leftbar: JQuery = null!;
|
||||
$rightbar: JQuery = null!;
|
||||
$turn: JQuery = null!;
|
||||
$messagebar: JQuery = null!;
|
||||
$delay: JQuery = null!;
|
||||
$hiddenMessage: JQuery = null!;
|
||||
|
||||
sideConditions = [{}, {}] as [{[id: string]: Sprite[]}, {[id: string]: Sprite[]}];
|
||||
|
||||
|
|
@ -146,7 +144,6 @@ class BattleScene {
|
|||
bgmNum = 0;
|
||||
preloadCache = {} as {[url: string]: HTMLImageElement};
|
||||
|
||||
autoScrollOnResume = false;
|
||||
messagebarOpen = false;
|
||||
interruptionCount = 1;
|
||||
curWeather = '';
|
||||
|
|
@ -161,11 +158,11 @@ class BattleScene {
|
|||
/** jQuery objects that need to finish animating */
|
||||
activeAnimations = $();
|
||||
|
||||
constructor(battle: Battle, $frame: JQuery<HTMLElement>, $logFrame: JQuery<HTMLElement>) {
|
||||
constructor(battle: Battle, $frame: JQuery, $logFrame: JQuery) {
|
||||
this.battle = battle;
|
||||
$frame.addClass('battle');
|
||||
this.$frame = $frame;
|
||||
this.$logFrame = $logFrame;
|
||||
this.log = new BattleLog($logFrame[0] as HTMLDivElement, this);
|
||||
|
||||
let numericId = 0;
|
||||
if (battle.id) {
|
||||
|
|
@ -187,16 +184,10 @@ class BattleScene {
|
|||
/////////////
|
||||
|
||||
if (this.$options) {
|
||||
this.$log.empty();
|
||||
this.$logPreempt.empty();
|
||||
this.log.reset();
|
||||
} else {
|
||||
this.$logFrame.empty();
|
||||
this.$options = $('<div class="battle-options"></div>');
|
||||
this.$log = $('<div class="inner" role="log"></div>');
|
||||
this.$logPreempt = $('<div class="inner-preempt"></div>');
|
||||
this.$logFrame.append(this.$options);
|
||||
this.$logFrame.append(this.$log);
|
||||
this.$logFrame.append(this.$logPreempt);
|
||||
$(this.log.elem).prepend(this.$options);
|
||||
}
|
||||
|
||||
// Battle frame
|
||||
|
|
@ -244,7 +235,7 @@ class BattleScene {
|
|||
this.$battle.append(this.$hiddenMessage);
|
||||
|
||||
if (this.battle.ignoreNicks) {
|
||||
this.$log.addClass('hidenicks');
|
||||
this.log.setHideNicks(true);
|
||||
this.$messagebar.addClass('hidenicks');
|
||||
this.$hiddenMessage.addClass('hidenicks');
|
||||
}
|
||||
|
|
@ -258,6 +249,8 @@ class BattleScene {
|
|||
this.pokemonTimeOffset = 0;
|
||||
this.curTerrain = '';
|
||||
this.curWeather = '';
|
||||
|
||||
this.log.battleParser!.perspective = this.battle.sidesSwitched ? 1 : 0;
|
||||
}
|
||||
|
||||
animationOff() {
|
||||
|
|
@ -265,7 +258,6 @@ class BattleScene {
|
|||
this.stopAnimation();
|
||||
|
||||
this.animating = false;
|
||||
this.autoScrollOnResume = (this.$logFrame.scrollTop()! + 60 >= this.$log.height()! + this.$logPreempt.height()! - this.$options.height()! - this.$logFrame.height()!);
|
||||
this.$messagebar.empty().css({
|
||||
opacity: 0,
|
||||
height: 0
|
||||
|
|
@ -289,9 +281,6 @@ class BattleScene {
|
|||
}
|
||||
this.updateWeather(true);
|
||||
this.resetTurn();
|
||||
if (this.autoScrollOnResume) {
|
||||
this.$logFrame.scrollTop(this.$log.height()! + this.$logPreempt.height()!);
|
||||
}
|
||||
this.resetSideConditions();
|
||||
}
|
||||
pause() {
|
||||
|
|
@ -474,7 +463,7 @@ class BattleScene {
|
|||
} as JQuery.PlainObject;
|
||||
}
|
||||
|
||||
waitFor(elem: JQuery<HTMLElement>) {
|
||||
waitFor(elem: JQuery) {
|
||||
this.activeAnimations = this.activeAnimations.add(elem);
|
||||
}
|
||||
|
||||
|
|
@ -497,24 +486,12 @@ class BattleScene {
|
|||
// Messagebar and log
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
|
||||
log(html: string, preempt?: boolean) {
|
||||
let willScroll = false;
|
||||
if (this.animating) willScroll = (this.$logFrame.scrollTop()! + 60 >= this.$log.height()! + this.$logPreempt.height()! - this.$options.height()! - this.$logFrame.height()!);
|
||||
if (preempt) {
|
||||
this.$logPreempt.append(html);
|
||||
} else {
|
||||
this.$log.append(html);
|
||||
}
|
||||
if (willScroll) {
|
||||
this.$logFrame.scrollTop(this.$log.height()! + this.$logPreempt.height()!);
|
||||
}
|
||||
}
|
||||
preemptCatchup() {
|
||||
this.$log.append(this.$logPreempt.children().first());
|
||||
this.log.preemptCatchup();
|
||||
}
|
||||
message(message: string, hiddenMessage?: string) {
|
||||
if (!this.messagebarOpen) {
|
||||
this.log('<div class="spacer battle-history"></div>');
|
||||
this.log.addSpacer();
|
||||
if (this.animating) {
|
||||
this.$messagebar.empty();
|
||||
this.$messagebar.css({
|
||||
|
|
@ -552,7 +529,14 @@ class BattleScene {
|
|||
this.waitFor($message);
|
||||
}
|
||||
this.messagebarOpen = true;
|
||||
this.log('<div class="battle-history">' + message + (hiddenMessage ? hiddenMessage : '') + '</div>');
|
||||
}
|
||||
maybeCloseMessagebar(args: Args, kwArgs: KWArgs) {
|
||||
if (this.log.battleParser!.sectionBreak(args, kwArgs)) {
|
||||
if (!this.messagebarOpen) return false;
|
||||
this.closeMessagebar();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
closeMessagebar() {
|
||||
if (this.messagebarOpen) {
|
||||
|
|
@ -563,17 +547,9 @@ class BattleScene {
|
|||
}, this.battle.messageFadeTime / this.acceleration);
|
||||
this.waitFor(this.$messagebar);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
unlink(userid: string, showRevealButton = false) {
|
||||
if (Tools.prefs('nounlink')) return;
|
||||
let $messages = $('.chatmessage-' + userid);
|
||||
if (!$messages.length) return;
|
||||
$messages.find('a').contents().unwrap();
|
||||
if (window.BattleRoom && showRevealButton) {
|
||||
$messages.hide().addClass('revealed').find('button').parent().remove();
|
||||
this.log('<div class="chatmessage-' + userid + '"><button name="toggleMessages" value="' + userid + '" class="subtle"><small>(' + $messages.length + ' line' + ($messages.length > 1 ? 's' : '') + ' from ' + userid + ' hidden)</small></button></div>');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// General updating
|
||||
|
|
@ -608,7 +584,6 @@ class BattleScene {
|
|||
const moveAnim = BattleMoveAnims[moveid];
|
||||
if (!moveAnim.prepareAnim) return;
|
||||
moveAnim.prepareAnim(this, [attacker.sprite, defender.sprite]);
|
||||
this.message('<small>' + moveAnim.prepareMessage!(attacker, defender) + '</small>');
|
||||
}
|
||||
|
||||
updateGen() {
|
||||
|
|
@ -630,6 +605,31 @@ class BattleScene {
|
|||
}
|
||||
}
|
||||
|
||||
getDetailsText(pokemon: Pokemon) {
|
||||
let name = pokemon.side && pokemon.side.n && (this.battle.ignoreOpponent || this.battle.ignoreNicks) ? pokemon.species : pokemon.name;
|
||||
if (name !== pokemon.species) {
|
||||
name += ' (' + pokemon.species + ')';
|
||||
}
|
||||
if (pokemon === pokemon.side.active[0]) {
|
||||
name += ' (active)';
|
||||
} else if (pokemon.fainted) {
|
||||
name += ' (fainted)';
|
||||
} else {
|
||||
let statustext = '';
|
||||
if (pokemon.hp !== pokemon.maxhp) {
|
||||
statustext += pokemon.hpDisplay();
|
||||
}
|
||||
if (pokemon.status) {
|
||||
if (statustext) statustext += '|';
|
||||
statustext += pokemon.status;
|
||||
}
|
||||
if (statustext) {
|
||||
name += ' (' + statustext + ')';
|
||||
}
|
||||
}
|
||||
return BattleLog.escapeHTML(name);
|
||||
}
|
||||
|
||||
updateSidebar(side: Side) {
|
||||
if (!this.animating) return;
|
||||
let pokemonhtml = '';
|
||||
|
|
@ -648,9 +648,11 @@ class BattleScene {
|
|||
} else if (!poke) {
|
||||
pokemonhtml += '<span class="picon" style="' + Tools.getPokemonIcon('pokeball') + '" title="Not revealed" aria-label="Not revealed"></span>';
|
||||
} else if (!poke.ident && this.battle.teamPreviewCount && this.battle.teamPreviewCount < side.pokemon.length) {
|
||||
pokemonhtml += '<span class="picon" style="' + Tools.getPokemonIcon(poke, !side.n) + ';opacity:0.6" title="' + poke.getFullName(true) + '" aria-label="' + poke.getFullName(true) + '"></span>';
|
||||
const details = this.getDetailsText(poke);
|
||||
pokemonhtml += '<span class="picon" style="' + Tools.getPokemonIcon(poke, !side.n) + ';opacity:0.6" title="' + details + '" aria-label="' + details + '"></span>';
|
||||
} else {
|
||||
pokemonhtml += '<span class="picon" style="' + Tools.getPokemonIcon(poke, !side.n) + '" title="' + poke.getFullName(true) + '" aria-label="' + poke.getFullName(true) + '"></span>';
|
||||
const details = this.getDetailsText(poke);
|
||||
pokemonhtml += '<span class="picon" style="' + Tools.getPokemonIcon(poke, !side.n) + '" title="' + details + '" aria-label="' + details + '"></span>';
|
||||
}
|
||||
if (i % 3 === 2) pokemonhtml += '</div><div class="teamicons">';
|
||||
}
|
||||
|
|
@ -729,7 +731,9 @@ class BattleScene {
|
|||
}
|
||||
side.totalPokemon = side.pokemon.length;
|
||||
if (textBuf) {
|
||||
this.log('<div class="chat battle-history"><strong>' + BattleLog.escapeHTML(side.name) + '\'s team:</strong> <em style="color:#445566;display:block;">' + BattleLog.escapeHTML(textBuf) + '</em></div>');
|
||||
this.log.addDiv('chat battle-history',
|
||||
'<strong>' + BattleLog.escapeHTML(side.name) + '\'s team:</strong> <em style="color:#445566;display:block;">' + BattleLog.escapeHTML(textBuf) + '</em>'
|
||||
);
|
||||
}
|
||||
this.$sprites[siden].html(buf + buf2);
|
||||
|
||||
|
|
@ -1395,7 +1399,7 @@ class BattleScene {
|
|||
BattleSound.pauseBgm();
|
||||
}
|
||||
destroy() {
|
||||
if (this.$logFrame) this.$logFrame.empty();
|
||||
this.log.destroy();
|
||||
if (this.$frame) this.$frame.empty();
|
||||
this.soundStop();
|
||||
this.battle = null!;
|
||||
|
|
@ -1427,7 +1431,7 @@ interface InitScenePos {
|
|||
|
||||
class Sprite {
|
||||
scene: BattleScene;
|
||||
$el: JQuery<HTMLElement> = null!;
|
||||
$el: JQuery = null!;
|
||||
sp: SpriteData;
|
||||
x: number;
|
||||
y: number;
|
||||
|
|
@ -1493,10 +1497,10 @@ class PokemonSprite extends Sprite {
|
|||
cryurl: string | undefined = undefined;
|
||||
|
||||
subsp: SpriteData | null = null;
|
||||
$sub: JQuery<HTMLElement> | null = null;
|
||||
$sub: JQuery | null = null;
|
||||
isSubActive = false;
|
||||
|
||||
$statbar: JQuery<HTMLElement> | null = null;
|
||||
$statbar: JQuery | null = null;
|
||||
isBackSprite: boolean;
|
||||
isMissedPokemon = false;
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ const BattleStatNames = { // proper style
|
|||
spe: 'Spe'
|
||||
};
|
||||
const BattleStats = {
|
||||
atk: 'Attack', def: 'Defense', spa: 'Special Attack', spd: 'Special Defense', spe: 'Speed', accuracy: 'accuracy', evasion: 'evasiveness', spc: 'Special'
|
||||
hp: 'HP', atk: 'Attack', def: 'Defense', spa: 'Special Attack', spd: 'Special Defense', spe: 'Speed', accuracy: 'accuracy', evasion: 'evasiveness', spc: 'Special'
|
||||
};
|
||||
|
||||
const baseSpeciesChart = [
|
||||
|
|
|
|||
|
|
@ -1,11 +1,296 @@
|
|||
/**
|
||||
* Battle log
|
||||
*
|
||||
*
|
||||
* An exercise in minimalism! This is a dependency of the client, which
|
||||
* requires IE9+ and uses Preact, and the replay player, which requires
|
||||
* IE7+ and uses jQuery. Therefore, this has to be compatible with IE7+
|
||||
* and use the DOM directly!
|
||||
*
|
||||
* Special thanks to PPK for QuirksMode.org, one of the few resources
|
||||
* available for how to do web development in these conditions.
|
||||
*
|
||||
* @author Guangcong Luo <guangcongluo@gmail.com>
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
class BattleLog {
|
||||
elem: HTMLDivElement;
|
||||
innerElem: HTMLDivElement;
|
||||
scene: BattleScene | null = null;
|
||||
preemptElem: HTMLDivElement = null!;
|
||||
atBottom = true;
|
||||
className: string;
|
||||
battleParser: BattleTextParser | null = null;
|
||||
/**
|
||||
* -1 = spectator: "Red sent out Pikachu!" "Blue's Eevee used Tackle!"
|
||||
* 0 = player 1: "Go! Pikachu!" "The opposing Eevee used Tackle!"
|
||||
* 1 = player 2: "Red sent out Pikachu!" "Eevee used Tackle!"
|
||||
*/
|
||||
perspective: -1 | 0 | 1 = -1;
|
||||
constructor(elem: HTMLDivElement, scene?: BattleScene) {
|
||||
this.elem = elem;
|
||||
|
||||
elem.setAttribute('role', 'log');
|
||||
elem.innerHTML = '';
|
||||
const innerElem = document.createElement('div');
|
||||
innerElem.className = 'inner';
|
||||
elem.appendChild(innerElem);
|
||||
this.innerElem = innerElem;
|
||||
this.className = elem.className;
|
||||
|
||||
if (scene) {
|
||||
this.scene = scene;
|
||||
const preemptElem = document.createElement('div');
|
||||
preemptElem.className = 'inner-preempt';
|
||||
elem.appendChild(preemptElem);
|
||||
this.preemptElem = preemptElem;
|
||||
this.battleParser = new BattleTextParser();
|
||||
}
|
||||
|
||||
elem.onscroll = this.onScroll;
|
||||
}
|
||||
onScroll = () => {
|
||||
const distanceFromBottom = this.elem.scrollHeight - this.elem.scrollTop - this.elem.clientHeight;
|
||||
this.atBottom = (distanceFromBottom < 30);
|
||||
};
|
||||
reset() {
|
||||
this.innerElem.innerHTML = '';
|
||||
this.atBottom = true;
|
||||
}
|
||||
destroy() {
|
||||
this.elem.onscroll = null;
|
||||
}
|
||||
setHideNicks(hideNicks: boolean) {
|
||||
this.elem.className = this.className + (hideNicks ? ' hidenicks' : '');
|
||||
}
|
||||
add(args: Args, kwArgs?: KWArgs, preempt?: boolean) {
|
||||
if (kwArgs && kwArgs.silent) return;
|
||||
let divClass = 'chat';
|
||||
let divHTML = '';
|
||||
switch (args[0]) {
|
||||
case 'chat': case 'c': case 'c:':
|
||||
let name, message;
|
||||
let battle = this.scene && this.scene.battle;
|
||||
if (args[0] === 'c:') {
|
||||
name = args[2];
|
||||
message = args[3];
|
||||
} else {
|
||||
name = args[1];
|
||||
message = args[2];
|
||||
}
|
||||
let rank = name.charAt(0);
|
||||
if (battle && battle.ignoreSpects && (rank === ' ' || rank === '+')) return;
|
||||
if (battle && battle.ignoreOpponent && (rank === '\u2605' || rank === '\u2606') && toUserid(name) !== app.user.get('userid')) return;
|
||||
if (window.app && app.ignore && app.ignore[toUserid(name)] && (rank === ' ' || rank === '+' || rank === '\u2605' || rank === '\u2606')) return;
|
||||
let isHighlighted = window.app && app.rooms && app.rooms[battle!.roomid].getHighlight(message);
|
||||
[divClass, divHTML] = this.parseChatMessage(message, name, '', isHighlighted);
|
||||
if (isHighlighted) {
|
||||
let notifyTitle = "Mentioned by " + name + " in " + battle!.roomid;
|
||||
app.rooms[battle!.roomid].notifyOnce(notifyTitle, "\"" + message + "\"", 'highlight');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'join': case 'j':
|
||||
divHTML = '<small>' + BattleLog.escapeHTML(args[1]) + ' joined.</small>';
|
||||
break;
|
||||
|
||||
case 'leave': case 'l':
|
||||
divHTML = '<small>' + BattleLog.escapeHTML(args[1]) + ' left.</small>';
|
||||
break;
|
||||
|
||||
case 'chatmsg': case '':
|
||||
divHTML = BattleLog.escapeHTML(args[1]);
|
||||
break;
|
||||
|
||||
case 'chatmsg-raw': case 'raw': case 'html':
|
||||
divHTML = BattleLog.sanitizeHTML(args[1]);
|
||||
break;
|
||||
|
||||
case 'error': case 'inactive': case 'inactiveoff':
|
||||
divClass = 'chat message-error';
|
||||
divHTML = BattleLog.escapeHTML(args[1]);
|
||||
break;
|
||||
|
||||
case 'bigerror':
|
||||
this.message('<div class="broadcast-red">' + BattleLog.escapeHTML(args[1]).replace(/\|/g, '<br />') + '</div>');
|
||||
return;
|
||||
|
||||
case 'pm':
|
||||
divHTML = '<strong>' + BattleLog.escapeHTML(args[1]) + ':</strong> <span class="message-pm"><i style="cursor:pointer" onclick="selectTab(\'lobby\');rooms.lobby.popupOpen(\'' + BattleLog.escapeHTML(args[2], true) + '\')">(Private to ' + BattleLog.escapeHTML(args[3]) + ')</i> ' + BattleLog.parseMessage(args[4]) + '</span>';
|
||||
break;
|
||||
|
||||
case 'askreg':
|
||||
this.addDiv('chat', '<div class="broadcast-blue"><b>Register an account to protect your ladder rating!</b><br /><button name="register" value="' + BattleLog.escapeHTML(args[1]) + '"><b>Register</b></button></div>');
|
||||
return;
|
||||
|
||||
case 'unlink':
|
||||
this.hideChatFrom(toId(args[2] || args[1]));
|
||||
return;
|
||||
|
||||
case 'debug':
|
||||
divClass = 'debug';
|
||||
divHTML = '<div class="chat"><small style="color:#999">[DEBUG] ' + BattleLog.escapeHTML(args[1]) + '.</small></div>';
|
||||
break;
|
||||
|
||||
case 'seed': case 'choice': case ':': case 'timer':
|
||||
case 'J': case 'L': case 'N': case 'n': case 'spectator': case 'spectatorleave':
|
||||
return;
|
||||
|
||||
default:
|
||||
this.addBattleMessage(args, kwArgs);
|
||||
return;
|
||||
}
|
||||
if (divHTML) this.addDiv(divClass, divHTML, preempt);
|
||||
}
|
||||
addBattleMessage(args: Args, kwArgs?: KWArgs) {
|
||||
switch (args[0]) {
|
||||
case 'warning':
|
||||
this.message('<strong>Warning:</strong> ' + BattleLog.escapeHTML(args[1]));
|
||||
this.message(`Bug? Report it to <a href="http://www.smogon.com/forums/showthread.php?t=3453192">the replay viewer's Smogon thread</a>`);
|
||||
if (this.scene) this.scene.wait(1000);
|
||||
return;
|
||||
|
||||
case 'variation':
|
||||
this.addDiv('', '<small>Variation: <em>' + BattleLog.escapeHTML(args[1]) + '</em></small>');
|
||||
break;
|
||||
|
||||
case 'rule':
|
||||
const ruleArgs = args[1].split(': ');
|
||||
this.addDiv('', '<small><em>' + BattleLog.escapeHTML(ruleArgs[0]) + (ruleArgs[1] ? ':' : '') + '</em> ' + BattleLog.escapeHTML(ruleArgs[1] || '') + '</small>');
|
||||
break;
|
||||
|
||||
case 'rated':
|
||||
this.addDiv('rated', '<strong>' + (BattleLog.escapeHTML(args[1]) || 'Rated battle') + '</strong>');
|
||||
break;
|
||||
|
||||
case 'tier':
|
||||
this.addDiv('', '<small>Format:</small> <br /><strong>' + BattleLog.escapeHTML(args[1]) + '</strong>');
|
||||
break;
|
||||
|
||||
case 'turn':
|
||||
const h2elem = document.createElement('h2');
|
||||
h2elem.className = 'battle-history';
|
||||
let turnMessage = this.battleParser!.parseLine(args, {}).trim();
|
||||
if (!turnMessage.startsWith('==') || !turnMessage.endsWith('==')) {
|
||||
throw new Error("Turn message must be a heading.");
|
||||
}
|
||||
turnMessage = turnMessage.slice(2, -2).trim();
|
||||
this.battleParser!.curLineSection = 'break';
|
||||
h2elem.innerHTML = BattleLog.escapeHTML(turnMessage);
|
||||
this.addNode(h2elem);
|
||||
break;
|
||||
|
||||
default:
|
||||
let line = null;
|
||||
if (this.battleParser) {
|
||||
line = this.battleParser.parseLine(args, kwArgs || {}, true);
|
||||
}
|
||||
if (line === null) {
|
||||
this.addDiv('chat message-error', 'Unrecognized: |' + BattleLog.escapeHTML(args.join('|')));
|
||||
return;
|
||||
}
|
||||
if (!line) return;
|
||||
this.message(...this.parseLogMessage(line));
|
||||
break;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* To avoid trolling with nicknames, we can't just run this through
|
||||
* parseMessage
|
||||
*/
|
||||
parseLogMessage(message: string): [string, string] {
|
||||
const messages = message.split('\n').map(line => {
|
||||
line = BattleLog.escapeHTML(line);
|
||||
line = line.replace(/\*\*(.*)\*\*/, '<strong>$1</strong>');
|
||||
line = line.replace(/\|\|([^\|]*)\|\|([^\|]*)\|\|/, '<abbr title="$1">$2</abbr>');
|
||||
if (line.startsWith(' ')) line = '<small>' + line.trim() + '</small>';
|
||||
return line;
|
||||
});
|
||||
return [
|
||||
messages.join('<br />'),
|
||||
messages.filter(message => !message.startsWith('<small>[')).join('<br />'),
|
||||
];
|
||||
}
|
||||
message(message: string, sceneMessage = message) {
|
||||
if (this.scene) this.scene.message(sceneMessage);
|
||||
this.addDiv('battle-history', message);
|
||||
}
|
||||
addNode(node: HTMLElement, preempt?: boolean) {
|
||||
(preempt ? this.preemptElem : this.innerElem).appendChild(node);
|
||||
if (this.atBottom) {
|
||||
this.elem.scrollTop = this.elem.scrollHeight;
|
||||
}
|
||||
}
|
||||
addDiv(className: string, innerHTML: string, preempt?: boolean) {
|
||||
const el = document.createElement('div');
|
||||
el.className = className;
|
||||
el.innerHTML = innerHTML;
|
||||
this.addNode(el);
|
||||
}
|
||||
addSpacer() {
|
||||
this.addDiv('spacer battle-history', '');
|
||||
}
|
||||
changeUhtml(id: string, html: string, forceAdd?: boolean) {
|
||||
id = toId(id);
|
||||
const classContains = ' uhtml-' + id + ' ';
|
||||
let elements = [] as HTMLDivElement[];
|
||||
for (const node of this.innerElem.childNodes as any) {
|
||||
if (node.className && (' ' + node.className + ' ').includes(classContains)) {
|
||||
elements.push(node);
|
||||
}
|
||||
}
|
||||
for (const node of this.preemptElem.childNodes as any) {
|
||||
if (node.className && (' ' + node.className + ' ').includes(classContains)) {
|
||||
elements.push(node);
|
||||
}
|
||||
}
|
||||
if (html && elements.length && !forceAdd) {
|
||||
for (const element of elements) {
|
||||
element.innerHTML = BattleLog.sanitizeHTML(html);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (const element of elements) {
|
||||
element.parentElement!.removeChild(element);
|
||||
}
|
||||
if (html) {
|
||||
this.addDiv('notice uhtml-' + id, BattleLog.sanitizeHTML(html));
|
||||
}
|
||||
}
|
||||
hideChatFrom(userid: ID, showRevealButton = true) {
|
||||
const classStart = 'chat chatmessage-' + userid + ' ';
|
||||
let lastNode;
|
||||
let count = 0;
|
||||
for (const node of this.innerElem.childNodes as any) {
|
||||
if (node.className && (node.className + ' ').startsWith(classStart)) {
|
||||
node.style.display = 'none';
|
||||
node.className = 'revealed ' + node.className;
|
||||
count++;
|
||||
}
|
||||
lastNode = node;
|
||||
}
|
||||
for (const node of this.preemptElem.childNodes as any) {
|
||||
if (node.className && (node.className + ' ').startsWith(classStart)) {
|
||||
node.style.display = 'none';
|
||||
node.className = 'revealed ' + node.className;
|
||||
count++;
|
||||
}
|
||||
lastNode = node;
|
||||
}
|
||||
if (!count || !showRevealButton) return;
|
||||
const button = document.createElement('button');
|
||||
button.name = 'toggleMessages';
|
||||
button.value = userid;
|
||||
button.className = 'subtle';
|
||||
button.innerHTML = '<small>(' + count + ' line' + (count > 1 ? 's' : '') + ' from ' + userid + ' hidden)</small>';
|
||||
lastNode.appendChild(document.createTextNode(' '));
|
||||
lastNode.appendChild(button);
|
||||
}
|
||||
preemptCatchup() {
|
||||
if (!this.preemptElem.firstChild) return;
|
||||
this.innerElem.appendChild(this.preemptElem.firstChild);
|
||||
}
|
||||
|
||||
static escapeFormat(formatid: string): string {
|
||||
let atIndex = formatid.indexOf('@@@');
|
||||
if (atIndex >= 0) {
|
||||
|
|
@ -77,16 +362,24 @@ class BattleLog {
|
|||
return this.colorCache[name];
|
||||
}
|
||||
|
||||
static parseChatMessage(message: string, name: string, timestamp: string, isHighlighted?: boolean, $chatElem?: any) {
|
||||
let showMe = !((Tools.prefs('chatformatting') || {}).hideme);
|
||||
static prefs(name: string) {
|
||||
// @ts-ignore
|
||||
if (window.Storage && Storage.prefs) return Storage.prefs(name);
|
||||
// @ts-ignore
|
||||
if (window.PS) return PS.prefs[name];
|
||||
return undefined;
|
||||
}
|
||||
|
||||
parseChatMessage(message: string, name: string, timestamp: string, isHighlighted?: boolean) {
|
||||
let showMe = !(BattleLog.prefs('chatformatting') || {}).hideme;
|
||||
let group = ' ';
|
||||
if (!/[A-Za-z0-9]/.test(name.charAt(0))) {
|
||||
// Backwards compatibility
|
||||
group = name.charAt(0);
|
||||
name = name.substr(1);
|
||||
}
|
||||
let color = this.hashColor(toId(name));
|
||||
let clickableName = '<small>' + this.escapeHTML(group) + '</small><span class="username" data-name="' + this.escapeHTML(name) + '">' + this.escapeHTML(name) + '</span>';
|
||||
let color = BattleLog.hashColor(toId(name));
|
||||
let clickableName = '<small>' + BattleLog.escapeHTML(group) + '</small><span class="username" data-name="' + BattleLog.escapeHTML(name) + '">' + BattleLog.escapeHTML(name) + '</span>';
|
||||
let hlClass = isHighlighted ? ' highlighted' : '';
|
||||
let mineClass = (window.app && app.user && app.user.get('name') === name ? ' mine' : '');
|
||||
|
||||
|
|
@ -104,99 +397,44 @@ class BattleLog {
|
|||
|
||||
switch (cmd) {
|
||||
case 'me':
|
||||
if (!showMe) return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em>/me' + this.parseMessage(' ' + target) + '</em></div>';
|
||||
return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">•</strong> <em>' + clickableName + '<i>' + this.parseMessage(' ' + target) + '</i></em></div>';
|
||||
if (!showMe) return ['chat chatmessage-' + toId(name) + hlClass + mineClass, timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em>/me' + BattleLog.parseMessage(' ' + target) + '</em>'];
|
||||
return ['chat chatmessage-' + toId(name) + hlClass + mineClass, timestamp + '<strong style="' + color + '">•</strong> <em>' + clickableName + '<i>' + BattleLog.parseMessage(' ' + target) + '</i></em>'];
|
||||
case 'mee':
|
||||
if (!showMe) return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em>/me' + this.parseMessage(' ' + target).slice(1) + '</em></div>';
|
||||
return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">•</strong> <em>' + clickableName + '<i>' + this.parseMessage(' ' + target).slice(1) + '</i></em></div>';
|
||||
if (!showMe) return ['chat chatmessage-' + toId(name) + hlClass + mineClass, timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em>/me' + BattleLog.parseMessage(' ' + target).slice(1) + '</em>'];
|
||||
return ['chat chatmessage-' + toId(name) + hlClass + mineClass, timestamp + '<strong style="' + color + '">•</strong> <em>' + clickableName + '<i>' + BattleLog.parseMessage(' ' + target).slice(1) + '</i></em>'];
|
||||
case 'invite':
|
||||
let roomid = toRoomid(target);
|
||||
return [
|
||||
'<div class="chat">' + timestamp + '<em>' + clickableName + ' invited you to join the room "' + roomid + '"</em></div>',
|
||||
'<div class="notice"><button name="joinRoom" value="' + roomid + '">Join ' + roomid + '</button></div>'
|
||||
];
|
||||
return ['chat', timestamp + '<em>' + clickableName + ' invited you to join the room "' + roomid + '"</em>' +
|
||||
'<div class="notice"><button name="joinRoom" value="' + roomid + '">Join ' + roomid + '</button></div>'];
|
||||
case 'announce':
|
||||
return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <span class="message-announce">' + this.parseMessage(target) + '</span></div>';
|
||||
return ['chat chatmessage-' + toId(name) + hlClass + mineClass, timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <span class="message-announce">' + BattleLog.parseMessage(target) + '</span>'];
|
||||
case 'log':
|
||||
return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<span class="message-log">' + this.parseMessage(target) + '</span></div>';
|
||||
return ['chat chatmessage-' + toId(name) + hlClass + mineClass, timestamp + '<span class="message-log">' + BattleLog.parseMessage(target) + '</span>'];
|
||||
case 'data-pokemon':
|
||||
let buf = '<li class="result">';
|
||||
let template = Tools.getTemplate(target);
|
||||
if (!template.abilities || !template.baseStats) return '[not supported in replays]';
|
||||
buf += '<span class="col numcol">' + (template.tier || Tools.getTemplate(template.baseSpecies).tier) + '</span> ';
|
||||
buf += '<span class="col iconcol"><span style="' + Tools.getPokemonIcon(template) + '"></span></span> ';
|
||||
buf += '<span class="col pokemonnamecol" style="white-space:nowrap"><a href="https://pokemonshowdown.com/dex/pokemon/' + template.id + '" target="_blank">' + template.species + '</a></span> ';
|
||||
buf += '<span class="col typecol">';
|
||||
if (template.types) for (let i = 0; i < template.types.length; i++) {
|
||||
buf += Tools.getTypeIcon(template.types[i]);
|
||||
}
|
||||
buf += '</span> ';
|
||||
buf += '<span style="float:left;min-height:26px">';
|
||||
if (template.abilities['1']) {
|
||||
buf += '<span class="col twoabilitycol">' + template.abilities['0'] + '<br />' + template.abilities['1'] + '</span>';
|
||||
} else {
|
||||
buf += '<span class="col abilitycol">' + template.abilities['0'] + '</span>';
|
||||
}
|
||||
if (template.abilities['S']) {
|
||||
buf += '<span class="col twoabilitycol' + (template.unreleasedHidden ? ' unreleasedhacol' : '') + '"><em>' + template.abilities['H'] + '<br />' + template.abilities['S'] + '</em></span>';
|
||||
} else if (template.abilities['H']) {
|
||||
buf += '<span class="col abilitycol' + (template.unreleasedHidden ? ' unreleasedhacol' : '') + '"><em>' + template.abilities['H'] + '</em></span>';
|
||||
} else {
|
||||
buf += '<span class="col abilitycol"></span>';
|
||||
}
|
||||
buf += '</span>';
|
||||
buf += '<span style="float:left;min-height:26px">';
|
||||
buf += '<span class="col statcol"><em>HP</em><br />' + template.baseStats.hp + '</span> ';
|
||||
buf += '<span class="col statcol"><em>Atk</em><br />' + template.baseStats.atk + '</span> ';
|
||||
buf += '<span class="col statcol"><em>Def</em><br />' + template.baseStats.def + '</span> ';
|
||||
buf += '<span class="col statcol"><em>SpA</em><br />' + template.baseStats.spa + '</span> ';
|
||||
buf += '<span class="col statcol"><em>SpD</em><br />' + template.baseStats.spd + '</span> ';
|
||||
buf += '<span class="col statcol"><em>Spe</em><br />' + template.baseStats.spe + '</span> ';
|
||||
let bst = 0;
|
||||
for (const i in template.baseStats) bst += template.baseStats[i as StatName];
|
||||
buf += '<span class="col bstcol"><em>BST<br />' + bst + '</em></span> ';
|
||||
buf += '</span>';
|
||||
buf += '</li>';
|
||||
return '<div class="message"><ul class="utilichart">' + buf + '<li style=\"clear:both\"></li></ul></div>';
|
||||
case 'data-item':
|
||||
if (!window.BattleSearch) return '[not supported in replays]';
|
||||
return '<div class="message"><ul class="utilichart">' + BattleSearch.renderItemRow(Tools.getItem(target), 0, 0) + '<li style=\"clear:both\"></li></ul></div>';
|
||||
case 'data-ability':
|
||||
if (!window.BattleSearch) return '[not supported in replays]';
|
||||
return '<div class="message"><ul class="utilichart">' + BattleSearch.renderAbilityRow(Tools.getAbility(target), 0, 0) + '<li style=\"clear:both\"></li></ul></div>';
|
||||
case 'data-move':
|
||||
if (!window.BattleSearch) return '[not supported in replays]';
|
||||
return '<div class="message"><ul class="utilichart">' + BattleSearch.renderMoveRow(Tools.getMove(target), 0, 0) + '<li style=\"clear:both\"></li></ul></div>';
|
||||
return ['chat message-error', '[outdated code no longer supported]'];
|
||||
case 'text':
|
||||
return '<div class="chat">' + this.parseMessage(target) + '</div>';
|
||||
return ['chat', BattleLog.parseMessage(target)];
|
||||
case 'error':
|
||||
return '<div class="chat message-error">' + this.escapeHTML(target) + '</div>';
|
||||
return ['chat message-error', BattleLog.escapeHTML(target)];
|
||||
case 'html':
|
||||
return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em>' + BattleLog.sanitizeHTML(target) + '</em></div>';
|
||||
return ['chat chatmessage-' + toId(name) + hlClass + mineClass, timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em>' + BattleLog.sanitizeHTML(target) + '</em>'];
|
||||
case 'uhtml':
|
||||
case 'uhtmlchange':
|
||||
let parts = target.split(',');
|
||||
let $elements = $chatElem.find('div.uhtml-' + toId(parts[0]));
|
||||
let html = parts.slice(1).join(',');
|
||||
if (!html) {
|
||||
$elements.remove();
|
||||
} else if (!$elements.length) {
|
||||
$chatElem.append('<div class="chat uhtml-' + toId(parts[0]) + '">' + BattleLog.sanitizeHTML(html) + '</div>');
|
||||
} else if (cmd === 'uhtmlchange') {
|
||||
$elements.html(BattleLog.sanitizeHTML(html));
|
||||
} else {
|
||||
$elements.remove();
|
||||
$chatElem.append('<div class="chat uhtml-' + toId(parts[0]) + '">' + BattleLog.sanitizeHTML(html) + '</div>');
|
||||
}
|
||||
return '';
|
||||
let html = parts.slice(1).join(',').trim();
|
||||
this.changeUhtml(parts[0], html, cmd === 'uhtml');
|
||||
return ['', ''];
|
||||
case 'raw':
|
||||
return '<div class="chat">' + BattleLog.sanitizeHTML(target) + '</div>';
|
||||
return ['chat', BattleLog.sanitizeHTML(target)];
|
||||
default:
|
||||
// Not a command or unsupported. Parsed as a normal chat message.
|
||||
if (!name) {
|
||||
return '<div class="chat' + hlClass + '">' + timestamp + '<em>' + this.parseMessage(message) + '</em></div>';
|
||||
return ['chat' + hlClass, timestamp + '<em>' + BattleLog.parseMessage(message) + '</em>'];
|
||||
}
|
||||
return '<div class="chat chatmessage-' + toId(name) + hlClass + mineClass + '">' + timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em>' + this.parseMessage(message) + '</em></div>';
|
||||
return ['chat chatmessage-' + toId(name) + hlClass + mineClass, timestamp + '<strong style="' + color + '">' + clickableName + ':</strong> <em>' + BattleLog.parseMessage(message) + '</em>'];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -207,7 +445,7 @@ class BattleLog {
|
|||
if (str.substr(0, 3) === '<< ') return this.escapeHTML(str);
|
||||
str = formatText(str);
|
||||
|
||||
let options = Tools.prefs('chatformatting') || {};
|
||||
let options = BattleLog.prefs('chatformatting') || {};
|
||||
|
||||
if (options.hidelinks) {
|
||||
str = str.replace(/<a[^>]*>/g, '<u>').replace(/<\/a>/g, '</u>');
|
||||
|
|
@ -484,3 +722,5 @@ class BattleLog {
|
|||
return 'data:text/plain;base64,' + encodeURIComponent(btoa(unescape(encodeURIComponent(this.createReplayFile(room)))));
|
||||
}
|
||||
}
|
||||
|
||||
exports.BattleLog = BattleLog;
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@ class BattleSceneStub {
|
|||
timeOffset: number = NaN;
|
||||
interruptionCount: number = NaN;
|
||||
messagebarOpen: boolean = false;
|
||||
log = {add: (args: Args, kwargs?: KWArgs) => {}} as BattleLog;
|
||||
|
||||
abilityActivateAnim(pokemon: Pokemon, result: string): void { }
|
||||
addPokemonSprite(pokemon: Pokemon) { return null!; }
|
||||
addSideCondition(siden: number, id: ID, instant?: boolean | undefined): void { }
|
||||
animationOff(): void { }
|
||||
animationOn(): void { }
|
||||
maybeCloseMessagebar(args: Args, kwArgs: KWArgs): boolean { return false; }
|
||||
closeMessagebar(): void { }
|
||||
damageAnim(pokemon: Pokemon, damage: string | number): void { }
|
||||
destroy(): void { }
|
||||
|
|
@ -20,7 +22,6 @@ class BattleSceneStub {
|
|||
healAnim(pokemon: Pokemon, damage: string | number): void { }
|
||||
hideJoinButtons(): void { }
|
||||
incrementTurn(): void { }
|
||||
log(html: string, preempt?: boolean | undefined): void { }
|
||||
message(message: string, hiddenMessage?: string | undefined): void { }
|
||||
pause(): void { }
|
||||
preemptCatchup(): void { }
|
||||
|
|
|
|||
902
src/battle-text-parser.ts
Normal file
902
src/battle-text-parser.ts
Normal file
|
|
@ -0,0 +1,902 @@
|
|||
declare const BattleText: {[id: string]: {[templateName: string]: string}};
|
||||
|
||||
type Args = [string, ...string[]];
|
||||
type KWArgs = {[kw: string]: string};
|
||||
|
||||
class BattleTextParser {
|
||||
p1 = "Player 1";
|
||||
p2 = "Player 2";
|
||||
perspective: 0 | 1;
|
||||
gen = 7;
|
||||
curLineSection: 'break' | 'preMajor' | 'major' | 'postMajor' = 'break';
|
||||
lowercaseRegExp: RegExp | null | undefined = undefined;
|
||||
|
||||
constructor(perspective: 0 | 1 = 0) {
|
||||
this.perspective = perspective;
|
||||
}
|
||||
|
||||
fixLowercase(input: string) {
|
||||
if (this.lowercaseRegExp === undefined) {
|
||||
const prefixes = ['pokemon', 'opposingPokemon', 'team', 'opposingTeam'].map(templateId => {
|
||||
const template = BattleText.default[templateId];
|
||||
if (template.charAt(0) === template.charAt(0).toUpperCase()) return '';
|
||||
const bracketIndex = template.indexOf('[');
|
||||
if (bracketIndex >= 0) return template.slice(0, bracketIndex);
|
||||
return template;
|
||||
}).filter(prefix => prefix);
|
||||
if (prefixes.length) {
|
||||
let buf = `((?:^|\n)(?: | \\\(| \\\[)?)(` +
|
||||
prefixes.map(BattleTextParser.escapeRegExp).join('|') +
|
||||
`)`;
|
||||
this.lowercaseRegExp = new RegExp(buf, 'g');
|
||||
} else {
|
||||
this.lowercaseRegExp = null;
|
||||
}
|
||||
}
|
||||
if (!this.lowercaseRegExp) return input;
|
||||
return input.replace(this.lowercaseRegExp, (match, p1, p2) => (
|
||||
p1 + p2.charAt(0).toUpperCase() + p2.slice(1)
|
||||
));
|
||||
}
|
||||
|
||||
static escapeRegExp(input: string) {
|
||||
return input.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
|
||||
}
|
||||
|
||||
pokemon(pokemon: string) {
|
||||
if (!pokemon) return '';
|
||||
let side;
|
||||
if (pokemon.startsWith('p1')) side = 0;
|
||||
else if (pokemon.startsWith('p2')) side = 1;
|
||||
else return '???pokemon:' + pokemon + '???';
|
||||
if (pokemon.charAt(3) === ':') pokemon = pokemon.slice(4).trim();
|
||||
else if (pokemon.charAt(2) === ':') pokemon = pokemon.slice(3).trim();
|
||||
else return '???pokemon:' + pokemon + '???';
|
||||
let template = BattleText.default[side === this.perspective ? 'pokemon' : 'opposingPokemon'];
|
||||
return template.replace('[NICKNAME]', pokemon);
|
||||
}
|
||||
|
||||
pokemonName(pokemon: string) {
|
||||
if (!pokemon) return '';
|
||||
if (!pokemon.startsWith('p1') && !pokemon.startsWith('p2')) return '???pokemon:' + pokemon + '???';
|
||||
if (pokemon.charAt(3) === ':') pokemon = pokemon.slice(4).trim();
|
||||
else if (pokemon.charAt(2) === ':') pokemon = pokemon.slice(3).trim();
|
||||
else return '???pokemon:' + pokemon + '???';
|
||||
return pokemon;
|
||||
}
|
||||
|
||||
pokemonFull(pokemon: string, details: string) {
|
||||
if (pokemon.startsWith('p1')) {}
|
||||
else if (pokemon.startsWith('p2')) {}
|
||||
else return '???pokemon:' + pokemon + '???';
|
||||
let nickname;
|
||||
if (pokemon.charAt(3) === ':') nickname = pokemon.slice(4).trim();
|
||||
else if (pokemon.charAt(2) === ':') nickname = pokemon.slice(3).trim();
|
||||
else return '???pokemon:' + pokemon + '???';
|
||||
|
||||
let species = details.split(',')[0];
|
||||
if (nickname === species) return [pokemon.slice(0, 2), `**${species}**`];
|
||||
return [pokemon.slice(0, 2), `${nickname} (**${species}**)`];
|
||||
}
|
||||
|
||||
trainer(side: string) {
|
||||
if (side === 'p1') return this.p1;
|
||||
if (side === 'p2') return this.p2;
|
||||
return '???side:' + side + '???';
|
||||
}
|
||||
|
||||
team(side: string) {
|
||||
if ((side === 'p1' && this.perspective === 0) ||
|
||||
(side === 'p2' && this.perspective === 1)) {
|
||||
return BattleText.default.team;
|
||||
}
|
||||
return BattleText.default.opposingTeam;
|
||||
}
|
||||
|
||||
own(side: string) {
|
||||
if (side === 'p1' && this.perspective === 0) return 'OWN';
|
||||
if (side === 'p2' && this.perspective === 1) return 'OWN';
|
||||
return '';
|
||||
}
|
||||
|
||||
effectId(effect?: string) {
|
||||
if (!effect) return '';
|
||||
if (effect.startsWith('item:') || effect.startsWith('move:')) {
|
||||
effect = effect.slice(5);
|
||||
} else if (effect.startsWith('ability:')) {
|
||||
effect = effect.slice(8);
|
||||
}
|
||||
return toId(effect);
|
||||
}
|
||||
|
||||
effect(effect?: string) {
|
||||
if (!effect) return '';
|
||||
if (effect.startsWith('item:') || effect.startsWith('move:')) {
|
||||
effect = effect.slice(5);
|
||||
} else if (effect.startsWith('ability:')) {
|
||||
effect = effect.slice(8);
|
||||
}
|
||||
return effect.trim();
|
||||
}
|
||||
|
||||
template(type: string, ...namespaces: (string | undefined)[]) {
|
||||
for (const namespace of namespaces) {
|
||||
if (!namespace) continue;
|
||||
if (namespace === 'OWN') {
|
||||
return BattleText.default[type + 'Own'] + '\n';
|
||||
}
|
||||
if (namespace === 'NODEFAULT') {
|
||||
return '';
|
||||
}
|
||||
let id = this.effectId(namespace);
|
||||
if (BattleText[id] && BattleText[id][type]) {
|
||||
if (BattleText[id][type].charAt(1) === '.') type = BattleText[id][type].slice(2) as ID;
|
||||
if (BattleText[id][type].charAt(0) === '#') id = BattleText[id][type].slice(1) as ID;
|
||||
return BattleText[id][type] + '\n';
|
||||
}
|
||||
}
|
||||
if (!BattleText.default[type]) return '';
|
||||
return BattleText.default[type] + '\n';
|
||||
}
|
||||
|
||||
maybeAbility(effect: string | undefined, holder: string) {
|
||||
if (!effect) return '';
|
||||
if (!effect.startsWith('ability:')) return '';
|
||||
return this.ability(effect.slice(8).trim(), holder);
|
||||
}
|
||||
|
||||
ability(name: string | undefined, holder: string) {
|
||||
if (!name) return '';
|
||||
return BattleText.default.abilityActivation.replace('[POKEMON]', this.pokemon(holder)).replace('[ABILITY]', this.effect(name)) + '\n';
|
||||
}
|
||||
|
||||
lineSection(args: Args, kwArgs: KWArgs) {
|
||||
const cmd = args[0];
|
||||
switch (cmd) {
|
||||
case 'done' : case 'turn':
|
||||
return 'break';
|
||||
case 'move' : case 'cant': case 'switch': case 'drag': case 'upkeep': case 'start': case '-mega':
|
||||
return 'major';
|
||||
case 'switchout': case 'faint':
|
||||
return 'preMajor';
|
||||
case '-zpower':
|
||||
return 'postMajor';
|
||||
case '-damage': {
|
||||
const id = this.effectId(kwArgs.from);
|
||||
if (id === 'confusion') return 'major';
|
||||
}
|
||||
case '-curestatus': {
|
||||
const id = this.effectId(kwArgs.from);
|
||||
if (id === 'naturalcure') return 'preMajor';
|
||||
}
|
||||
case '-start': {
|
||||
const id = this.effectId(kwArgs.from);
|
||||
if (id === 'protean') return 'preMajor';
|
||||
}
|
||||
case '-activate': {
|
||||
const id = this.effectId(kwArgs.from);
|
||||
if (id === 'confusion' || id === 'attract') return 'preMajor';
|
||||
}
|
||||
}
|
||||
return (cmd.charAt(0) === '-' ? 'postMajor' : '');
|
||||
}
|
||||
|
||||
sectionBreak(args: Args, kwArgs: KWArgs) {
|
||||
const prevSection = this.curLineSection;
|
||||
const curSection = this.lineSection(args, kwArgs);
|
||||
if (!curSection) return false;
|
||||
this.curLineSection = curSection;
|
||||
switch (curSection) {
|
||||
case 'break':
|
||||
if (prevSection !== 'break') return true;
|
||||
return false;
|
||||
case 'preMajor':
|
||||
case 'major':
|
||||
if (prevSection === 'postMajor' || prevSection === 'major') return true;
|
||||
return false;
|
||||
case 'postMajor':
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
parseLine(args: Args, kwArgs: KWArgs, noSectionBreak?: boolean) {
|
||||
let buf = !noSectionBreak && this.sectionBreak(args, kwArgs) ? '\n' : '';
|
||||
return buf + this.fixLowercase(this.parseLineInner(args, kwArgs) || '');
|
||||
}
|
||||
|
||||
parseLineInner(args: Args, kwArgs: KWArgs) {
|
||||
let cmd = args[0];
|
||||
switch (cmd) {
|
||||
case 'player': {
|
||||
const [, side, name] = args;
|
||||
if (side === 'p1' && name) {
|
||||
this.p1 = name;
|
||||
} else if (side === 'p2' && name) {
|
||||
this.p2 = name;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
case 'gen': {
|
||||
const [, number] = args;
|
||||
this.gen = parseInt(number, 10);
|
||||
return '';
|
||||
}
|
||||
|
||||
case 'turn': {
|
||||
const [, number] = args;
|
||||
return this.template('turn').replace('[NUMBER]', number) + '\n';
|
||||
}
|
||||
|
||||
case 'start': {
|
||||
return this.template('startBattle').replace('[TRAINER]', this.p1).replace('[TRAINER]', this.p2);
|
||||
}
|
||||
|
||||
case 'win': case 'tie': {
|
||||
const [, name] = args;
|
||||
if (cmd === 'tie' || !name) {
|
||||
return this.template('tieBattle').replace('[TRAINER]', this.p1).replace('[TRAINER]', this.p2);
|
||||
}
|
||||
return this.template('winBattle').replace('[TRAINER]', name);
|
||||
}
|
||||
|
||||
case 'switch': {
|
||||
const [, pokemon, details] = args;
|
||||
const [side, fullname] = this.pokemonFull(pokemon, details);
|
||||
const template = this.template('switchIn', this.own(side));
|
||||
return template.replace('[TRAINER]', this.trainer(side)).replace('[FULLNAME]', fullname);
|
||||
}
|
||||
|
||||
case 'drag': {
|
||||
const [, pokemon, details] = args;
|
||||
const [side, fullname] = this.pokemonFull(pokemon, details);
|
||||
const template = this.template('drag');
|
||||
return template.replace('[TRAINER]', this.trainer(side)).replace('[FULLNAME]', fullname);
|
||||
}
|
||||
|
||||
case 'detailschange': case '-transform': case '-formechange': {
|
||||
const [, pokemon, arg2, arg3] = args;
|
||||
let newSpecies = '';
|
||||
switch (cmd) {
|
||||
case 'detailschange': newSpecies = arg2.split(',')[0].trim(); break;
|
||||
case '-transform': newSpecies = arg3; break;
|
||||
case '-formechange': newSpecies = arg2; break;
|
||||
}
|
||||
let newSpeciesId = toId(newSpecies);
|
||||
let id = '';
|
||||
let templateName = 'transform';
|
||||
if (cmd !== '-transform') {
|
||||
switch (newSpeciesId) {
|
||||
case 'greninjaash': id = 'battlebond'; break;
|
||||
case 'mimikyubusted': id = 'disguise'; break;
|
||||
case 'zygardecomplete': id = 'powerconstruct'; break;
|
||||
case 'necrozmaultra': id = 'ultranecroziumz'; break;
|
||||
case 'darmanitanzen': id = 'zenmode'; break;
|
||||
case 'darmanitan': id = 'zenmode'; templateName = 'transformEnd'; break;
|
||||
case 'aegislashblade': id = 'stancechange'; break;
|
||||
case 'aegislash': id = 'stancechange'; templateName = 'transformEnd'; break;
|
||||
case 'wishiwashischool': id = 'schooling'; break;
|
||||
case 'wishiwashi': id = 'schooling'; templateName = 'transformEnd'; break;
|
||||
case 'miniormeteor': id = 'shieldsdown'; break;
|
||||
case 'minior': id = 'shieldsdown'; templateName = 'transformEnd'; break;
|
||||
}
|
||||
} else if (newSpecies) {
|
||||
id = 'transform';
|
||||
}
|
||||
const template = this.template(templateName, id, kwArgs.msg ? '' : 'NODEFAULT');
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[SPECIES]', newSpecies);
|
||||
}
|
||||
|
||||
case 'switchout': {
|
||||
const [, pokemon] = args;
|
||||
const side = pokemon.slice(0, 2);
|
||||
const template = this.template('switchOut', kwArgs.from, this.own(side));
|
||||
return template.replace('[TRAINER]', this.trainer(side)).replace('[NICKNAME]', this.pokemonName(pokemon));
|
||||
}
|
||||
|
||||
case 'faint': {
|
||||
const [, pokemon] = args;
|
||||
const template = this.template('faint');
|
||||
return template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case 'swap': {
|
||||
const [, pokemon, target] = args;
|
||||
if (!target || !isNaN(Number(target))) {
|
||||
const template = this.template('swapCenter');
|
||||
return template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
const template = this.template('swap');
|
||||
return template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[TARGET]', this.pokemon(target));
|
||||
}
|
||||
|
||||
case 'move': {
|
||||
const [, pokemon, move] = args;
|
||||
let line1 = '';
|
||||
if (kwArgs.zEffect) {
|
||||
line1 = this.template('zEffect').replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
const template = this.template('move', kwArgs.from);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[MOVE]', move);
|
||||
}
|
||||
|
||||
case 'cant': {
|
||||
let [, pokemon, effect, move] = args;
|
||||
let id = this.effectId(effect);
|
||||
switch (id) {
|
||||
case 'damp': case 'dazzling': case 'queenlymajesty':
|
||||
// thanks Marty
|
||||
[pokemon, kwArgs.of] = [kwArgs.of, pokemon];
|
||||
break;
|
||||
}
|
||||
const template = this.template('cant', effect);
|
||||
const line1 = this.maybeAbility(effect, kwArgs.of || pokemon);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[MOVE]', move);
|
||||
}
|
||||
|
||||
case 'faint': {
|
||||
const [, pokemon] = args;
|
||||
const template = this.template('faint');
|
||||
return template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case 'message': {
|
||||
let [, message] = args;
|
||||
return '' + message + '\n';
|
||||
}
|
||||
|
||||
case '-start': {
|
||||
let [, pokemon, effect, arg3] = args;
|
||||
const line1 = this.maybeAbility(effect, pokemon) || this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
let id = this.effectId(effect);
|
||||
if (id === 'typechange') {
|
||||
const template = this.template('typeChange', kwArgs.from);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[TYPE]', arg3).replace('[SOURCE]', this.pokemon(kwArgs.of));
|
||||
}
|
||||
if (id === 'typeadd') {
|
||||
const template = this.template('typeAdd', kwArgs.from);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[TYPE]', arg3);
|
||||
}
|
||||
if (id.startsWith('stockpile')) {
|
||||
const number = id.slice(9);
|
||||
const template = this.template('start', 'stockpile');
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[NUMBER]', number);
|
||||
}
|
||||
if (id.startsWith('perish')) {
|
||||
const number = id.slice(6);
|
||||
const template = this.template('activate', 'perishsong');
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[NUMBER]', number);
|
||||
}
|
||||
let templateId = 'start';
|
||||
if (kwArgs.already) templateId = 'alreadyStarted';
|
||||
if (kwArgs.fatigue) templateId = 'startFromFatigue';
|
||||
if (kwArgs.zeffect) templateId = 'startFromZEffect';
|
||||
if (kwArgs.damage) templateId = 'activate';
|
||||
if (kwArgs.block) templateId = 'block';
|
||||
if (kwArgs.upkeep) templateId = 'activate';
|
||||
let template = '';
|
||||
if (templateId === 'start' && kwArgs.from && kwArgs.from.startsWith('item:')) {
|
||||
template = this.template('startFromItem', effect);
|
||||
}
|
||||
if (!template) template = this.template(templateId, effect);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[EFFECT]', this.effect(kwArgs.from)).replace('[MOVE]', arg3).replace('[SOURCE]', this.pokemon(kwArgs.of));
|
||||
}
|
||||
|
||||
case '-end': {
|
||||
let [, pokemon, effect] = args;
|
||||
const line1 = this.maybeAbility(effect, pokemon) || this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
let id = this.effectId(effect);
|
||||
if (id === 'doomdesire' || id === 'futuresight') {
|
||||
const template = this.template('activate', effect);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[TARGET]', this.pokemon(kwArgs.of));
|
||||
}
|
||||
let templateId = 'end';
|
||||
let template = '';
|
||||
if (kwArgs.from && kwArgs.from.startsWith('item:')) {
|
||||
template = this.template('endFromItem', effect);
|
||||
}
|
||||
if (!template) template = this.template(templateId, effect);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[EFFECT]', this.effect(kwArgs.from)).replace('[SOURCE]', this.pokemon(kwArgs.of));
|
||||
}
|
||||
|
||||
case '-ability': {
|
||||
let [, pokemon, ability, oldAbility, arg4] = args;
|
||||
let line1 = '';
|
||||
if (oldAbility && (oldAbility.startsWith('p1') || oldAbility.startsWith('p2') || oldAbility === 'boost')) {
|
||||
arg4 = oldAbility;
|
||||
oldAbility = '';
|
||||
}
|
||||
if (oldAbility) line1 += this.ability(oldAbility, pokemon);
|
||||
line1 += this.ability(ability, pokemon);
|
||||
if (kwArgs.fail) {
|
||||
const template = this.template('block', ability);
|
||||
return line1 + template;
|
||||
}
|
||||
if (kwArgs.from) {
|
||||
const template = this.template('changeAbility', kwArgs.from);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ABILITY]', this.effect(ability)).replace('[SOURCE]', this.pokemon(kwArgs.of));
|
||||
}
|
||||
const id = this.effectId(ability);
|
||||
if (id === 'unnerve') {
|
||||
const template = this.template('start', ability);
|
||||
return line1 + template.replace('[TEAM]', this.team(arg4 && arg4.slice(0, 2)));
|
||||
}
|
||||
let templateId = 'start';
|
||||
if (id === 'anticipation' || id === 'sturdy') templateId = 'activate';
|
||||
const template = this.template(templateId, ability, 'NODEFAULT');
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-endability': {
|
||||
let [, pokemon, ability] = args;
|
||||
if (ability) return this.ability(ability, pokemon);
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
const template = this.template('start', 'Gastro Acid');
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-item': {
|
||||
let [, pokemon, item] = args;
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
const id = this.effectId(kwArgs.from);
|
||||
if (['thief', 'covet', 'bestow'].includes(id)) {
|
||||
const template = this.template('takeItem', kwArgs.from);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(item)).replace('[SOURCE]', this.pokemon(kwArgs.of));
|
||||
}
|
||||
if (id === 'frisk') {
|
||||
const template = this.template(kwArgs.of && pokemon && kwArgs.of !== pokemon ? 'activate' : 'activateNoTarget', "Frisk");
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(item)).replace('[TARGET]', this.pokemon(kwArgs.of));
|
||||
}
|
||||
if (kwArgs.from) {
|
||||
const template = this.template('addItem', kwArgs.from);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(item));
|
||||
}
|
||||
const template = this.template('start', item, 'NODEFAULT');
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-enditem': {
|
||||
let [, pokemon, item, arg3] = args;
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
if (kwArgs.eat) {
|
||||
const template = this.template('eatItem', kwArgs.from);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(item));
|
||||
}
|
||||
const id = this.effectId(kwArgs.from);
|
||||
if (id === 'gem') {
|
||||
const template = this.template('useGem', item);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(item)).replace('[MOVE]', kwArgs.move);
|
||||
}
|
||||
if (id === 'stealeat') {
|
||||
const template = this.template('removeItem', "Bug Bite");
|
||||
return line1 + template.replace('[SOURCE]', this.pokemon(kwArgs.of)).replace('[ITEM]', this.effect(item));
|
||||
}
|
||||
if (kwArgs.from) {
|
||||
const template = this.template('removeItem', kwArgs.from);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(item)).replace('[SOURCE]', this.pokemon(kwArgs.of));
|
||||
}
|
||||
const template = this.template('end', item, 'NODEFAULT');
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[TARGET]', this.pokemon(kwArgs.of));
|
||||
}
|
||||
|
||||
case '-status': {
|
||||
const [, pokemon, status] = args;
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
const template = this.template('start', status);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-curestatus': {
|
||||
const [, pokemon, status] = args;
|
||||
if (this.effectId(kwArgs.from) === 'naturalcure') {
|
||||
const template = this.template('activate', kwArgs.from);
|
||||
return template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
if (kwArgs.from && kwArgs.from.startsWith('item:')) {
|
||||
const template = this.template('endFromItem', status);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(kwArgs.from));
|
||||
}
|
||||
if (kwArgs.thaw) {
|
||||
const template = this.template('endFromMove', status);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[MOVE]', this.effect(kwArgs.from));
|
||||
}
|
||||
const template = this.template('end', status);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-cureteam': {
|
||||
return this.template('activate', kwArgs.from);
|
||||
}
|
||||
|
||||
case '-singleturn': case '-singlemove': {
|
||||
const [, pokemon, effect] = args;
|
||||
const line1 = this.maybeAbility(effect, kwArgs.of || pokemon) || this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
let id = this.effectId(effect);
|
||||
if (id === 'instruct') {
|
||||
const template = this.template('activate', effect);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(kwArgs.of)).replace('[TARGET]', this.pokemon(pokemon));
|
||||
}
|
||||
const template = this.template('start', effect);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[SOURCE]', this.pokemon(kwArgs.of));
|
||||
}
|
||||
|
||||
case '-sidestart': {
|
||||
let [, side, effect] = args;
|
||||
const template = this.template('start', effect);
|
||||
return template.replace('[TEAM]', this.team(side));
|
||||
}
|
||||
|
||||
case '-sideend': {
|
||||
let [, side, effect] = args;
|
||||
const template = this.template('end', effect);
|
||||
return template.replace('[TEAM]', this.team(side));
|
||||
}
|
||||
|
||||
case '-weather': {
|
||||
const [, weather] = args;
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of);
|
||||
if (!weather || weather === 'none') {
|
||||
return this.template('end', kwArgs.from);
|
||||
}
|
||||
if (kwArgs.upkeep) {
|
||||
return this.template('activate', weather);
|
||||
}
|
||||
if (!kwArgs.of) {
|
||||
return this.template('start', weather);
|
||||
}
|
||||
const template = this.template('startFromAbility', weather);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(kwArgs.of));
|
||||
}
|
||||
|
||||
case '-fieldstart': case '-fieldactivate': {
|
||||
const [, effect] = args;
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of);
|
||||
const templateId = cmd.slice(6);
|
||||
const template = this.template(templateId, effect, 'NODEFAULT');
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(kwArgs.of));
|
||||
}
|
||||
|
||||
case '-fieldend': {
|
||||
let [, effect] = args;
|
||||
return this.template('end', effect, 'NODEFAULT');
|
||||
}
|
||||
|
||||
case '-sethp': {
|
||||
let effect = kwArgs.from;
|
||||
return this.template('activate', effect);
|
||||
}
|
||||
|
||||
case '-message': {
|
||||
let [, message] = args;
|
||||
return ' ' + message + '\n';
|
||||
}
|
||||
|
||||
case '-hint': {
|
||||
let [, message] = args;
|
||||
return ' (' + message + ')\n';
|
||||
}
|
||||
|
||||
case '-activate': {
|
||||
let [, pokemon, effect, target, arg4, arg5] = args;
|
||||
let id = this.effectId(effect);
|
||||
if (id === 'celebrate') {
|
||||
return this.template('activate', 'celebrate').replace('[TRAINER]', this.trainer(pokemon.slice(0, 2)));
|
||||
}
|
||||
if (!arg5 && (id === 'spite' || id === 'skillswap')) {
|
||||
[target, arg4, arg5] = [pokemon, target, arg4];
|
||||
} else if (!arg4 && ['grudge', 'forewarn', 'magnitude', 'sketch', 'persistent', 'symbiosis', 'safetygoggles', 'matblock', 'safetygoggles'].includes(id)) {
|
||||
[target, arg4] = [pokemon, target];
|
||||
} else if (!target && ['hyperspacefury', 'hyperspacehole', 'phantomforce', 'shadowforce', 'feint'].includes(id)) {
|
||||
[pokemon, target] = [kwArgs.of, pokemon];
|
||||
if (!pokemon) pokemon = target;
|
||||
}
|
||||
if (!target) target = kwArgs.of || pokemon;
|
||||
|
||||
let line1 = this.maybeAbility(effect, pokemon) || this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
if (id === 'wonderguard') {
|
||||
return line1 + this.template('immune');
|
||||
}
|
||||
|
||||
if (['bind', 'wrap', 'clamp', 'whirlpool', 'firespin', 'magmastorm', 'sandtomb', 'infestation', 'charge', 'fairylock', 'trapped'].includes(id)) {
|
||||
const template = this.template('start', effect);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[SOURCE]', this.pokemon(kwArgs.of));
|
||||
}
|
||||
if (id === 'lockon' || id === 'mindreader') {
|
||||
const template = this.template('start', effect);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(kwArgs.of)).replace('[SOURCE]', this.pokemon(pokemon));
|
||||
}
|
||||
if (kwArgs.block) {
|
||||
return this.template('fail');
|
||||
}
|
||||
if (['ingrain', 'quickguard', 'wideguard', 'craftyshield', 'matblock', 'protect', 'mist', 'safeguard', 'electricterrain', 'mistyterrain', 'psychicterrain', 'telepathy', 'stickyhold', 'suctioncups', 'aromaveil', 'flowerveil', 'sweetveil', 'disguise', 'safetygoggles', 'protectivepads'].includes(id)) {
|
||||
const template = this.template('block', effect);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[MOVE]', arg4);
|
||||
}
|
||||
|
||||
let templateId = 'activate';
|
||||
if (id === 'forewarn' && pokemon === target) {
|
||||
templateId = 'activateNoTarget';
|
||||
}
|
||||
let template = this.template(templateId, effect, 'NODEFAULT');
|
||||
if (!template) {
|
||||
template = this.template('activate');
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[EFFECT]', this.effect(effect));
|
||||
}
|
||||
|
||||
if (id === 'brickbreak') {
|
||||
template = template.replace('[TEAM]', this.team(target.slice(0, 2)));
|
||||
}
|
||||
if (id === 'spite' || id === 'grudge' || id === 'forewarn' || id === 'sketch') {
|
||||
template = template.replace('[MOVE]', arg4).replace('[NUMBER]', arg5);
|
||||
}
|
||||
if (id === 'magnitude') {
|
||||
template = template.replace('[NUMBER]', arg4);
|
||||
}
|
||||
if (id === 'symbiosis') {
|
||||
template = template.replace('[ITEM]', arg4);
|
||||
}
|
||||
if (id === 'skillswap') {
|
||||
line1 += this.ability(arg4, pokemon);
|
||||
line1 += this.ability(arg5, target);
|
||||
}
|
||||
if (id === 'mummy') {
|
||||
line1 += this.ability(arg4, target);
|
||||
line1 += this.ability("Mummy", target);
|
||||
template = this.template('changeAbility', "Mummy");
|
||||
}
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[TARGET]', this.pokemon(target)).replace('[SOURCE]', this.pokemon(kwArgs.of));
|
||||
}
|
||||
|
||||
case '-prepare': {
|
||||
const [, pokemon, effect, target] = args;
|
||||
const template = this.template('prepare', effect);
|
||||
return template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[TARGET]', this.pokemon(target));
|
||||
}
|
||||
|
||||
case '-damage': {
|
||||
let [, pokemon, newHealth, percentage] = args;
|
||||
let template = this.template('damage', kwArgs.from, 'NODEFAULT');
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
const id = this.effectId(kwArgs.from);
|
||||
if (template) {
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
if (!kwArgs.from) {
|
||||
template = this.template(percentage ? 'damagePercentage' : 'damage');
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[PERCENTAGE]', percentage);
|
||||
}
|
||||
if (kwArgs.from.startsWith('item:')) {
|
||||
template = this.template('damageFromItem');
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[ITEM]', this.effect(kwArgs.from));
|
||||
}
|
||||
if (kwArgs.partiallytrapped || id === 'bind' || id === 'wrap') {
|
||||
template = this.template('damageFromPartialTrapping');
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[MOVE]', this.effect(kwArgs.from));
|
||||
}
|
||||
|
||||
template = this.template('damage');
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-heal': {
|
||||
let [, pokemon] = args;
|
||||
let template = this.template('heal', kwArgs.from, 'NODEFAULT');
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
if (template) {
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[SOURCE]', this.pokemon(kwArgs.of));
|
||||
}
|
||||
|
||||
if (kwArgs.from && !kwArgs.from.startsWith('ability:')) {
|
||||
template = this.template('healFromEffect');
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[EFFECT]', this.effect(kwArgs.from));
|
||||
}
|
||||
|
||||
template = this.template('heal');
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-boost': case '-unboost': {
|
||||
const [, pokemon, stat, number] = args;
|
||||
const statName = BattleStats[stat as StatName] || "stats";
|
||||
const amount = parseInt(number);
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
let templateId = cmd.slice(1);
|
||||
if (amount >= 3) templateId += '3';
|
||||
else if (amount >= 2) templateId += '2';
|
||||
else if (amount === 0) templateId += '0';
|
||||
if (amount && kwArgs.zeffect) {
|
||||
templateId += (kwArgs.multiple ? 'MultipleFromZEffect' : 'FromZEffect');
|
||||
} else if (amount && kwArgs.from && kwArgs.from.startsWith('item:')) {
|
||||
templateId += 'FromItem';
|
||||
}
|
||||
const template = this.template(templateId, kwArgs.from);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[STAT]', statName);
|
||||
}
|
||||
|
||||
case '-setboost': {
|
||||
const [, pokemon] = args;
|
||||
const effect = kwArgs.from;
|
||||
const line1 = this.maybeAbility(effect, kwArgs.of || pokemon);
|
||||
const template = this.template('boost', effect);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-swapboost': {
|
||||
const [, pokemon, target] = args;
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
const id = this.effectId(kwArgs.from);
|
||||
let templateId = 'swapBoost';
|
||||
if (id === 'guardswap') templateId = 'swapDefensiveBoost';
|
||||
if (id === 'powerswap') templateId = 'swapOffensiveBoost';
|
||||
const template = this.template(templateId, kwArgs.from);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[TARGET]', this.pokemon(target));
|
||||
}
|
||||
|
||||
case '-copyboost': {
|
||||
const [, pokemon, target] = args;
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
const template = this.template('copyBoost', kwArgs.from);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[TARGET]', this.pokemon(target));
|
||||
}
|
||||
|
||||
case '-clearboost': case '-clearpositiveboost': case '-clearnegativeboost': {
|
||||
const [, pokemon, source] = args;
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
let templateId = 'clearBoost';
|
||||
if (kwArgs.zeffect) templateId = 'clearBoostFromZEffect';
|
||||
const template = this.template(templateId, kwArgs.from);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[SOURCE]', this.pokemon(source));
|
||||
}
|
||||
|
||||
case '-invertboost': {
|
||||
const [, pokemon] = args;
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
const template = this.template('invertBoost', kwArgs.from);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-clearallboost': {
|
||||
return this.template('clearAllBoost', kwArgs.from);
|
||||
}
|
||||
|
||||
case '-crit': case '-supereffective': case '-resisted': {
|
||||
const [, pokemon] = args;
|
||||
let templateId = cmd.slice(1);
|
||||
if (templateId === 'supereffective') templateId = 'superEffective';
|
||||
if (kwArgs.spread) templateId += 'Spread';
|
||||
const template = this.template(templateId);
|
||||
return template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-fail': {
|
||||
let [, pokemon, effect, stat] = args;
|
||||
let id = this.effectId(effect);
|
||||
let blocker = this.effectId(kwArgs.from);
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
let templateId = 'block';
|
||||
if (['desolateland', 'primordialsea'].includes(blocker) &&
|
||||
!['sunnyday', 'raindance', 'sandstorm', 'hail'].includes(id)) {
|
||||
templateId = 'blockMove';
|
||||
} else if (blocker === 'uproar' && kwArgs.msg) {
|
||||
templateId = 'blockSelf';
|
||||
}
|
||||
let template = this.template(templateId, kwArgs.from);
|
||||
if (template) {
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
if (id === 'unboost') {
|
||||
template = this.template(stat ? 'failSingular' : 'fail', 'unboost');
|
||||
if (this.effectId(kwArgs.from) === 'flowerveil') {
|
||||
template = this.template('block', kwArgs.from);
|
||||
pokemon = kwArgs.of;
|
||||
}
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[STAT]', stat);
|
||||
}
|
||||
|
||||
templateId = 'fail';
|
||||
if (['brn', 'frz', 'par', 'psn', 'slp', 'substitute'].includes(id)) {
|
||||
templateId = 'alreadyStarted';
|
||||
}
|
||||
if (kwArgs.heavy) templateId = 'failTooHeavy';
|
||||
if (kwArgs.weak) templateId = 'fail';
|
||||
if (kwArgs.forme) templateId = 'failWrongForme';
|
||||
template = this.template(templateId);
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-immune': {
|
||||
let [, pokemon, effect] = args;
|
||||
const line1 = this.maybeAbility(effect, kwArgs.of || pokemon);
|
||||
let template = this.template('block', effect);
|
||||
if (!template) {
|
||||
const templateId = kwArgs.ohko ? 'immuneOHKO' : 'immune';
|
||||
template = this.template(pokemon ? templateId : 'immuneNoPokemon', effect);
|
||||
}
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-miss': {
|
||||
const [, source, pokemon] = args;
|
||||
const line1 = this.maybeAbility(kwArgs.from, kwArgs.of || pokemon);
|
||||
if (!pokemon) {
|
||||
const template = this.template('missNoPokemon');
|
||||
return line1 + template.replace('[SOURCE]', this.pokemon(source));
|
||||
}
|
||||
const template = this.template('miss');
|
||||
return line1 + template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-center': case '-ohko': case '-combine': {
|
||||
return this.template(cmd.slice(1));
|
||||
}
|
||||
|
||||
case '-nothing': {
|
||||
return this.template('activate', 'splash');
|
||||
}
|
||||
|
||||
case '-notarget': {
|
||||
return this.template('noTarget');
|
||||
}
|
||||
|
||||
case '-mega': case '-primal': {
|
||||
const [, pokemon, species, item] = args;
|
||||
let id = '';
|
||||
if (species === 'Rayquaza') id = 'dragonascent';
|
||||
if (!id && cmd === '-mega' && this.gen < 7) cmd = '-megaGen6';
|
||||
let template = this.template(cmd.slice(1));
|
||||
const pokemonName = this.pokemon(pokemon);
|
||||
if (cmd === '-mega') {
|
||||
const template2 = this.template('transformMega');
|
||||
template += template2.replace('[POKEMON]', pokemonName).replace('[SPECIES]', species);
|
||||
}
|
||||
return template.replace('[POKEMON]', pokemonName).replace('[ITEM]', item);
|
||||
}
|
||||
|
||||
case '-zpower': {
|
||||
const [, pokemon] = args;
|
||||
const template = this.template('zPower');
|
||||
return template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-burst': {
|
||||
const [, pokemon] = args;
|
||||
const template = this.template('activate', "Ultranecrozium Z");
|
||||
return template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-zbroken': {
|
||||
const [, pokemon] = args;
|
||||
const template = this.template('zBroken');
|
||||
return template.replace('[POKEMON]', this.pokemon(pokemon));
|
||||
}
|
||||
|
||||
case '-hitcount': {
|
||||
const [, number] = args;
|
||||
if (number === '1') {
|
||||
return this.template('hitCountSingular');
|
||||
}
|
||||
return this.template('hitCount').replace('[NUMBER]', number);
|
||||
}
|
||||
|
||||
case '-waiting': {
|
||||
const [, pokemon, target] = args;
|
||||
const template = this.template('activate', "Water Pledge");
|
||||
return template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[TARGET]', this.pokemon(target));
|
||||
}
|
||||
|
||||
case '-anim': {
|
||||
return '';
|
||||
}
|
||||
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.BattleTextParser = BattleTextParser;
|
||||
4429
src/battle.ts
4429
src/battle.ts
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user