mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-04-25 16:14:01 -05:00
The client Battle class API has been pretty old and crusty, so this
updates it to be saner.
The constructor now takes an options object. Any setting you'd want to
initialize with is now a constructor option, instead of needing to call
methods after the constructor.
(Deprecated settings `roomid` and `joinButtons` still need to be set
separately.)
The old callback system is removed. It's replaced with a subscription
system vaguely resembling `PSStreamModel`. Any callbacks only intended
to be used by the warstory generator are removed (anyone who wants to
write their own warstory generator should extend `BattleSceneStub`
instead).
Battles no longer start paused. You can still start them paused by
passing `paused: true` as an option.
Playback state tracking had a bunch of rearrangement:
- `playbackState` no longer exists; state should be directly read from
`paused`, `atQueueEnd`, `turn`, and `seeking`.
- `turn` is now initialized to `-1`. `-1` now means "we haven't reached
`|teampreview|` or `|start|` yet". Reaching those sets turn to `0`.
- "Fast forwarding" and "seeking" are now consistently named "seeking".
- `seeking` tracks seek state; changes from `fastForward`:
- `null` means not seeking (replaces `0`)
- `0` means seeking the start (replaces `0.5`)
- `Infinity` means seeking the end (replaces `-1`)
- `fastForward` deprecated and replaced with `seeking`
- `fastForwardTo()` deprecated and replaced with `seekTurn()`
- `resultWaiting` is removed (it's unused)
- The "activity queue" has been renamed the "step queue", which means
some renamed properties:
- `activityQueue` to `stepQueue`
- `activityStep` to `currentStep`
- `nextActivity()` to `nextStep()`
- new property: `atQueueEnd` to track if animation has caught up to the
end of the step queue (replaces checking `playbackState`)
- new property/option: `isReplay` - will automatically set `ended` when
reaching the end of a replay (stopping music and showing a message),
if the replay was saved before the end of the battle
- both replay players (`replay.pokemonshowdown.com` and downloaded
files) have been rewritten to use an observer system, instead of the
previous manual updating
- `reset(true)` has been renamed `resetStep()`
185 lines
7.4 KiB
JavaScript
185 lines
7.4 KiB
JavaScript
window.exports = window;
|
|
|
|
function linkStyle(url) {
|
|
var linkEl = document.createElement('link');
|
|
linkEl.rel = 'stylesheet';
|
|
linkEl.href = url;
|
|
document.head.appendChild(linkEl);
|
|
}
|
|
function requireScript(url) {
|
|
var scriptEl = document.createElement('script');
|
|
scriptEl.src = url;
|
|
document.head.appendChild(scriptEl);
|
|
}
|
|
|
|
linkStyle('https://play.pokemonshowdown.com/style/font-awesome.css?');
|
|
linkStyle('https://play.pokemonshowdown.com/style/battle.css?a7');
|
|
linkStyle('https://play.pokemonshowdown.com/style/replay.css?a7');
|
|
linkStyle('https://play.pokemonshowdown.com/style/utilichart.css?a7');
|
|
|
|
requireScript('https://play.pokemonshowdown.com/js/lib/ps-polyfill.js');
|
|
requireScript('https://play.pokemonshowdown.com/config/config.js?a7');
|
|
requireScript('https://play.pokemonshowdown.com/js/lib/jquery-1.11.0.min.js');
|
|
requireScript('https://play.pokemonshowdown.com/js/lib/lodash.compat.js');
|
|
requireScript('https://play.pokemonshowdown.com/js/lib/html-sanitizer-minified.js');
|
|
requireScript('https://play.pokemonshowdown.com/js/battle-sound.js');
|
|
requireScript('https://play.pokemonshowdown.com/js/battledata.js?a7');
|
|
requireScript('https://play.pokemonshowdown.com/data/pokedex-mini.js?a7');
|
|
requireScript('https://play.pokemonshowdown.com/data/pokedex-mini-bw.js?a7');
|
|
requireScript('https://play.pokemonshowdown.com/data/graphics.js?a7');
|
|
requireScript('https://play.pokemonshowdown.com/data/pokedex.js?a7');
|
|
requireScript('https://play.pokemonshowdown.com/data/moves.js?a7');
|
|
requireScript('https://play.pokemonshowdown.com/data/abilities.js?a7');
|
|
requireScript('https://play.pokemonshowdown.com/data/items.js?a7');
|
|
requireScript('https://play.pokemonshowdown.com/data/teambuilder-tables.js?a7');
|
|
requireScript('https://play.pokemonshowdown.com/js/battle-tooltips.js?a7');
|
|
requireScript('https://play.pokemonshowdown.com/js/battle.js?a7');
|
|
|
|
var Replays = {
|
|
battle: null,
|
|
muted: false,
|
|
init: function () {
|
|
this.$el = $('.wrapper');
|
|
if (!this.$el.length) {
|
|
$('body').append('<div class="wrapper replay-wrapper" style="max-width:1180px;margin:0 auto"><div class="battle"></div><div class="battle-log"></div><div class="replay-controls"></div><div class="replay-controls-2"></div>');
|
|
this.$el = $('.wrapper');
|
|
}
|
|
|
|
var id = $('input[name=replayid]').val() || '';
|
|
var log = ($('script.battle-log-data').text() || '').replace(/\\\//g, '/');
|
|
|
|
var self = this;
|
|
this.$el.on('click', '.chooser button', function (e) {
|
|
self.clickChangeSetting(e);
|
|
});
|
|
this.$el.on('click', 'button', function (e) {
|
|
var action = $(e.currentTarget).data('action');
|
|
if (action) self[action]();
|
|
});
|
|
|
|
this.battle = new Battle({
|
|
id: id,
|
|
$frame: this.$('.battle'),
|
|
$logFrame: this.$('.battle-log'),
|
|
log: log.split('\n'),
|
|
isReplay: true,
|
|
paused: true,
|
|
});
|
|
|
|
this.$('.replay-controls-2').html('<div class="chooser leftchooser speedchooser"> <em>Speed:</em> <div><button class="sel" value="fast">Fast</button><button value="normal">Normal</button><button value="slow">Slow</button><button value="reallyslow">Really Slow</button></div> </div> <div class="chooser colorchooser"> <em>Color scheme:</em> <div><button class="sel" value="light">Light</button><button value="dark">Dark</button></div> </div> <div class="chooser soundchooser" style="display:none"> <em>Music:</em> <div><button class="sel" value="on">On</button><button value="off">Off</button></div> </div>');
|
|
|
|
// this works around a WebKit/Blink bug relating to float layout
|
|
var rc2 = this.$('.replay-controls-2')[0];
|
|
// eslint-disable-next-line no-self-assign
|
|
if (rc2) rc2.innerHTML = rc2.innerHTML;
|
|
|
|
if (window.HTMLAudioElement) $('.soundchooser, .startsoundchooser').show();
|
|
this.update();
|
|
this.battle.subscribe(function (state) { self.update(state); });
|
|
},
|
|
"$": function (sel) {
|
|
return this.$el.find(sel);
|
|
},
|
|
clickChangeSetting: function (e) {
|
|
e.preventDefault();
|
|
var $chooser = $(e.currentTarget).closest('.chooser');
|
|
var value = e.currentTarget.value;
|
|
this.changeSetting($chooser, value, $(e.currentTarget));
|
|
},
|
|
changeSetting: function (type, value, valueElem) {
|
|
var $chooser;
|
|
if (typeof type === 'string') {
|
|
$chooser = this.$('.' + type + 'chooser');
|
|
} else {
|
|
$chooser = type;
|
|
type = '';
|
|
if ($chooser.hasClass('colorchooser')) {
|
|
type = 'color';
|
|
} else if ($chooser.hasClass('soundchooser')) {
|
|
type = 'sound';
|
|
} else if ($chooser.hasClass('speedchooser')) {
|
|
type = 'speed';
|
|
}
|
|
}
|
|
if (!valueElem) valueElem = $chooser.find('button[value=' + value + ']');
|
|
|
|
$chooser.find('button').removeClass('sel');
|
|
valueElem.addClass('sel');
|
|
|
|
switch (type) {
|
|
case 'color':
|
|
if (value === 'dark') {
|
|
$(document.body).addClass('dark');
|
|
} else {
|
|
$(document.body).removeClass('dark');
|
|
}
|
|
break;
|
|
|
|
case 'sound':
|
|
// remember this is reversed: sound[off] === muted[true]
|
|
this.muted = (value === 'off');
|
|
this.battle.setMute(this.muted);
|
|
this.$('.startsoundchooser').remove();
|
|
break;
|
|
|
|
case 'speed':
|
|
var speedTable = {
|
|
fast: 8,
|
|
normal: 800,
|
|
slow: 2500,
|
|
reallyslow: 5000
|
|
};
|
|
this.battle.messageDelay = speedTable[value];
|
|
break;
|
|
}
|
|
},
|
|
update: function (state) {
|
|
if (state === 'error') {
|
|
var m = /^([a-z0-9]+)-[a-z0-9]+-[0-9]+$/.exec(this.battle.id);
|
|
if (m) {
|
|
this.battle.log('<hr /><div class="chat">This replay was uploaded from a third-party server (<code>' + BattleLog.escapeHTML(m[1]) + '</code>). It contains errors.</div><div class="chat">Replays uploaded from third-party servers can contain errors if the server is running custom code, or the server operator has otherwise incorrectly configured their server.</div>', true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (BattleSound.muted && !this.muted) this.changeSetting('sound', 'off');
|
|
|
|
if (this.battle.paused) {
|
|
var resetDisabled = !this.battle.started ? ' disabled' : '';
|
|
this.$('.replay-controls').html('<button data-action="play"><i class="fa fa-play"></i> Play</button><button data-action="reset"' + resetDisabled + '><i class="fa fa-undo"></i> Reset</button> <button data-action="rewind"><i class="fa fa-step-backward"></i> Last turn</button><button data-action="ff"><i class="fa fa-step-forward"></i> Next turn</button> <button data-action="ffto"><i class="fa fa-fast-forward"></i> Go to turn...</button> <button data-action="switchSides"><i class="fa fa-random"></i> Switch sides</button>');
|
|
} else {
|
|
this.$('.replay-controls').html('<button data-action="pause"><i class="fa fa-pause"></i> Pause</button><button data-action="reset"><i class="fa fa-undo"></i> Reset</button> <button data-action="rewind"><i class="fa fa-step-backward"></i> Last turn</button><button data-action="ff"><i class="fa fa-step-forward"></i> Next turn</button> <button data-action="ffto"><i class="fa fa-fast-forward"></i> Go to turn...</button> <button data-action="switchSides"><i class="fa fa-random"></i> Switch sides</button>');
|
|
}
|
|
},
|
|
pause: function () {
|
|
this.battle.pause();
|
|
},
|
|
play: function () {
|
|
this.battle.play();
|
|
},
|
|
reset: function () {
|
|
this.battle.reset();
|
|
},
|
|
ff: function () {
|
|
this.battle.skipTurn();
|
|
},
|
|
rewind: function () {
|
|
this.battle.seekTurn(this.battle.turn - 1);
|
|
},
|
|
ffto: function () {
|
|
var turn = prompt('Turn?');
|
|
if (!turn.trim()) return;
|
|
if (turn === 'e' || turn === 'end' || turn === 'f' || turn === 'finish') turn = Infinity;
|
|
turn = Number(turn);
|
|
if (isNaN(turn) || turn < 0) alert("Invalid turn");
|
|
this.battle.seekTurn(turn);
|
|
},
|
|
switchSides: function () {
|
|
this.battle.switchSides();
|
|
},
|
|
};
|
|
|
|
window.onload = function () {
|
|
Replays.init();
|
|
};
|