Start modularizing some battle files

Progress is very incomplete, but this is mostly an experiment to
establish this as possible at all.

What's going on here is that we're using `remove-import-export` so
the `import` and `export` statements just disappear after compiling
(everything's still a global).

This allows us to piecewise convert files to modules.

So we're just using TypeScript to keep track of dependencies, and
also to make it easier to use these files in other projects later on.

I've tried to avoid circular dependencies, but there's one between
`battle-animations` and `battle-animations-moves`.
This commit is contained in:
Guangcong Luo 2021-07-17 21:07:08 -05:00
parent 5458936f2e
commit b228d488c2
8 changed files with 88 additions and 61 deletions

View File

@ -9,7 +9,9 @@
* @license CC0-1.0 * @license CC0-1.0
*/ */
const BattleMoveAnims: AnimTable = { import {AnimTable, BattleOtherAnims} from './battle-animations';
export const BattleMoveAnims: AnimTable = {
taunt: { taunt: {
anim(scene, [attacker, defender]) { anim(scene, [attacker, defender]) {
BattleOtherAnims.dance.anim(scene, [attacker, defender]); BattleOtherAnims.dance.anim(scene, [attacker, defender]);

View File

@ -11,6 +11,12 @@
* @license MIT * @license MIT
*/ */
import type {Battle, Pokemon, Side, WeatherState} from './battle';
import type {BattleSceneStub} from './battle-scene-stub';
import {BattleMoveAnims} from './battle-animations-moves';
import {BattleLog} from './battle-log';
import {BattleBGM, BattleSound} from './battle-sound';
/* /*
Most of this file is: CC0 (public domain) Most of this file is: CC0 (public domain)
@ -30,7 +36,7 @@ This license DOES NOT extend to any other files in this repository.
*/ */
class BattleScene { export class BattleScene implements BattleSceneStub {
battle: Battle; battle: Battle;
animating = true; animating = true;
acceleration = 1; acceleration = 1;
@ -234,7 +240,7 @@ class BattleScene {
} else { } else {
this.$frame.append('<div class="playbutton"><button name="play"><i class="fa fa-play"></i> Play</button><br /><br /><button name="play-muted" class="startsoundchooser" style="font-size:10pt;display:none">Play (music off)</button></div>'); this.$frame.append('<div class="playbutton"><button name="play"><i class="fa fa-play"></i> Play</button><br /><br /><button name="play-muted" class="startsoundchooser" style="font-size:10pt;display:none">Play (music off)</button></div>');
this.$frame.find('div.playbutton button[name=play-muted]').click(() => { this.$frame.find('div.playbutton button[name=play-muted]').click(() => {
this.battle.setMute(true); this.setMute(true);
this.battle.play(); this.battle.play();
}); });
} }
@ -244,6 +250,9 @@ class BattleScene {
this.$frame.find('div.playbutton').remove(); this.$frame.find('div.playbutton').remove();
this.updateBgm(); this.updateBgm();
} }
setMute(muted: boolean) {
BattleSound.setMute(muted);
}
wait(time: number) { wait(time: number) {
if (!this.animating) return; if (!this.animating) return;
this.timeOffset += time; this.timeOffset += time;
@ -589,7 +598,7 @@ class BattleScene {
} else { } else {
let statustext = ''; let statustext = '';
if (pokemon.hp !== pokemon.maxhp) { if (pokemon.hp !== pokemon.maxhp) {
statustext += Pokemon.getHPText(pokemon); statustext += pokemon.getHPText();
} }
if (pokemon.status) { if (pokemon.status) {
if (statustext) statustext += '|'; if (statustext) statustext += '|';
@ -1643,7 +1652,7 @@ class BattleScene {
} }
} }
interface ScenePos { export interface ScenePos {
/** - left, + right */ /** - left, + right */
x?: number; x?: number;
/** - down, + up */ /** - down, + up */
@ -1669,7 +1678,7 @@ interface InitScenePos {
display?: string; display?: string;
} }
class Sprite { export class Sprite {
scene: BattleScene; scene: BattleScene;
$el: JQuery = null!; $el: JQuery = null!;
sp: SpriteData; sp: SpriteData;
@ -1732,7 +1741,7 @@ class Sprite {
} }
} }
class PokemonSprite extends Sprite { export class PokemonSprite extends Sprite {
// HTML strings are constructed from this table and stored back in it to cache them // HTML strings are constructed from this table and stored back in it to cache them
protected static statusTable: {[id: string]: [string, 'good' | 'bad' | 'neutral'] | null | string} = { protected static statusTable: {[id: string]: [string, 'good' | 'bad' | 'neutral'] | null | string} = {
formechange: null, formechange: null,
@ -2795,7 +2804,7 @@ interface AnimData {
prepareAnim?(scene: BattleScene, args: PokemonSprite[]): void; prepareAnim?(scene: BattleScene, args: PokemonSprite[]): void;
residualAnim?(scene: BattleScene, args: PokemonSprite[]): void; residualAnim?(scene: BattleScene, args: PokemonSprite[]): void;
} }
type AnimTable = {[k: string]: AnimData}; export type AnimTable = {[k: string]: AnimData};
const BattleEffects: {[k: string]: SpriteData} = { const BattleEffects: {[k: string]: SpriteData} = {
wisp: { wisp: {
@ -3112,7 +3121,7 @@ const BattleBackdrops = [
'bg-skypillar.jpg', 'bg-skypillar.jpg',
]; ];
const BattleOtherAnims: AnimTable = { export const BattleOtherAnims: AnimTable = {
hitmark: { hitmark: {
anim(scene, [attacker]) { anim(scene, [attacker]) {
scene.showEffect('hitmark', { scene.showEffect('hitmark', {
@ -5698,7 +5707,7 @@ const BattleOtherAnims: AnimTable = {
}, },
}, },
}; };
const BattleStatusAnims: AnimTable = { export const BattleStatusAnims: AnimTable = {
brn: { brn: {
anim(scene, [attacker]) { anim(scene, [attacker]) {
scene.showEffect('fireball', { scene.showEffect('fireball', {

View File

@ -13,7 +13,17 @@
* @license MIT * @license MIT
*/ */
class BattleLog { import type {BattleScene} from './battle-animations';
// Caja
declare const html4: any;
declare const html: any;
// defined in battle-log-misc
declare function MD5(input: string): string;
declare function formatText(input: string, isTrusted?: boolean): string;
export class BattleLog {
elem: HTMLDivElement; elem: HTMLDivElement;
innerElem: HTMLDivElement; innerElem: HTMLDivElement;
scene: BattleScene | null = null; scene: BattleScene | null = null;
@ -358,7 +368,7 @@ class BattleLog {
addSpacer() { addSpacer() {
this.addDiv('spacer battle-history', '<br />'); this.addDiv('spacer battle-history', '<br />');
} }
changeUhtml(id: string, html: string, forceAdd?: boolean) { changeUhtml(id: string, htmlSrc: string, forceAdd?: boolean) {
id = toID(id); id = toID(id);
const classContains = ' uhtml-' + id + ' '; const classContains = ' uhtml-' + id + ' ';
let elements = [] as HTMLDivElement[]; let elements = [] as HTMLDivElement[];
@ -374,9 +384,9 @@ class BattleLog {
} }
} }
} }
if (html && elements.length && !forceAdd) { if (htmlSrc && elements.length && !forceAdd) {
for (const element of elements) { for (const element of elements) {
element.innerHTML = BattleLog.sanitizeHTML(html); element.innerHTML = BattleLog.sanitizeHTML(htmlSrc);
} }
this.updateScroll(); this.updateScroll();
return; return;
@ -384,11 +394,11 @@ class BattleLog {
for (const element of elements) { for (const element of elements) {
element.parentElement!.removeChild(element); element.parentElement!.removeChild(element);
} }
if (!html) return; if (!htmlSrc) return;
if (forceAdd) { if (forceAdd) {
this.addDiv('notice uhtml-' + id, BattleLog.sanitizeHTML(html)); this.addDiv('notice uhtml-' + id, BattleLog.sanitizeHTML(htmlSrc));
} else { } else {
this.prependDiv('notice uhtml-' + id, BattleLog.sanitizeHTML(html)); this.prependDiv('notice uhtml-' + id, BattleLog.sanitizeHTML(htmlSrc));
} }
} }
hideChatFrom(userid: ID, showRevealButton = true, lineCount = 0) { hideChatFrom(userid: ID, showRevealButton = true, lineCount = 0) {
@ -634,8 +644,8 @@ class BattleLog {
case 'uhtml': case 'uhtml':
case 'uhtmlchange': case 'uhtmlchange':
let parts = target.split(','); let parts = target.split(',');
let html = parts.slice(1).join(',').trim(); let htmlSrc = parts.slice(1).join(',').trim();
this.changeUhtml(parts[0], html, cmd === 'uhtml'); this.changeUhtml(parts[0], htmlSrc, cmd === 'uhtml');
return ['', '']; return ['', ''];
case 'raw': case 'raw':
return ['chat', BattleLog.sanitizeHTML(target)]; return ['chat', BattleLog.sanitizeHTML(target)];

View File

@ -1,4 +1,8 @@
class BattleSceneStub { import type {Pokemon, Side} from './battle';
import type {ScenePos, PokemonSprite} from './battle-animations';
import type {BattleLog} from './battle-log';
export class BattleSceneStub {
animating: boolean = false; animating: boolean = false;
acceleration: number = NaN; acceleration: number = NaN;
gen: number = NaN; gen: number = NaN;
@ -10,12 +14,12 @@ class BattleSceneStub {
log: BattleLog = {add: (args: Args, kwargs?: KWArgs) => {}} as any; log: BattleLog = {add: (args: Args, kwargs?: KWArgs) => {}} as any;
abilityActivateAnim(pokemon: Pokemon, result: string): void { } abilityActivateAnim(pokemon: Pokemon, result: string): void { }
addPokemonSprite(pokemon: Pokemon) { return null!; } addPokemonSprite(pokemon: Pokemon): PokemonSprite { return null!; }
addSideCondition(siden: number, id: ID, instant?: boolean | undefined): void { } addSideCondition(siden: number, id: ID, instant?: boolean | undefined): void { }
animationOff(): void { } animationOff(): void { }
animationOn(): void { } animationOn(): void { }
maybeCloseMessagebar(args: Args, kwArgs: KWArgs): boolean { return false; } maybeCloseMessagebar(args: Args, kwArgs: KWArgs): boolean { return false; }
closeMessagebar(): void { } closeMessagebar(): boolean { return false; }
damageAnim(pokemon: Pokemon, damage: string | number): void { } damageAnim(pokemon: Pokemon, damage: string | number): void { }
destroy(): void { } destroy(): void { }
finishAnimations(): JQuery.Promise<JQuery<HTMLElement>, any, any> | undefined { return void(0); } finishAnimations(): JQuery.Promise<JQuery<HTMLElement>, any, any> | undefined { return void(0); }
@ -25,6 +29,7 @@ class BattleSceneStub {
updateAcceleration(): void { } updateAcceleration(): void { }
message(message: string, hiddenMessage?: string | undefined): void { } message(message: string, hiddenMessage?: string | undefined): void { }
pause(): void { } pause(): void { }
setMute(muted: boolean): void { }
preemptCatchup(): void { } preemptCatchup(): void { }
removeSideCondition(siden: number, id: ID): void { } removeSideCondition(siden: number, id: ID): void { }
reset(): void { } reset(): void { }
@ -68,7 +73,6 @@ class BattleSceneStub {
anim(pokemon: Pokemon, end: ScenePos, transition?: string) { } anim(pokemon: Pokemon, end: ScenePos, transition?: string) { }
beforeMove(pokemon: Pokemon) { } beforeMove(pokemon: Pokemon) { }
afterMove(pokemon: Pokemon) { } afterMove(pokemon: Pokemon) { }
unlink(userid: string, showRevealButton = false) { }
} }
if (typeof require === 'function') { if (typeof require === 'function') {

View File

@ -1,5 +1,5 @@
class BattleBGM { export class BattleBGM {
/** /**
* May be shared with other BGM objects: every battle has its own BattleBGM * May be shared with other BGM objects: every battle has its own BattleBGM
* object, but two battles with the same music will have the same HTMLAudioElement * object, but two battles with the same music will have the same HTMLAudioElement
@ -100,7 +100,7 @@ class BattleBGM {
} }
} }
const BattleSound = new class { export const BattleSound = new class {
soundCache: {[url: string]: HTMLAudioElement | undefined} = {}; soundCache: {[url: string]: HTMLAudioElement | undefined} = {};
bgm: BattleBGM[] = []; bgm: BattleBGM[] = [];

View File

@ -27,13 +27,17 @@
* @license MIT * @license MIT
*/ */
/** [id, element?, ...misc] */ import {BattleSceneStub} from './battle-scene-stub';
type EffectState = any[] & {0: ID}; import {BattleLog} from './battle-log';
/** [name, minTimeLeft, maxTimeLeft] */ import {BattleScene, PokemonSprite, BattleStatusAnims} from './battle-animations';
type WeatherState = [string, number, number];
type HPColor = 'r' | 'y' | 'g';
class Pokemon implements PokemonDetails, PokemonHealth { /** [id, element?, ...misc] */
export type EffectState = any[] & {0: ID};
/** [name, minTimeLeft, maxTimeLeft] */
export type WeatherState = [string, number, number];
export type HPColor = 'r' | 'y' | 'g';
export class Pokemon implements PokemonDetails, PokemonHealth {
name = ''; name = '';
speciesForme = ''; speciesForme = '';
@ -570,6 +574,9 @@ class Pokemon implements PokemonDetails, PokemonHealth {
} }
return percentage * maxWidth / 100; return percentage * maxWidth / 100;
} }
getHPText(precision = 1) {
return Pokemon.getHPText(this, precision);
}
static getHPText(pokemon: PokemonHealth, precision = 1) { static getHPText(pokemon: PokemonHealth, precision = 1) {
if (pokemon.maxhp === 100) return pokemon.hp + '%'; if (pokemon.maxhp === 100) return pokemon.hp + '%';
if (pokemon.maxhp !== 48) return (100 * pokemon.hp / pokemon.maxhp).toFixed(precision) + '%'; if (pokemon.maxhp !== 48) return (100 * pokemon.hp / pokemon.maxhp).toFixed(precision) + '%';
@ -583,7 +590,7 @@ class Pokemon implements PokemonDetails, PokemonHealth {
} }
} }
class Side { export class Side {
battle: Battle; battle: Battle;
name = ''; name = '';
id = ''; id = '';
@ -932,7 +939,7 @@ class Side {
} }
} }
interface PokemonDetails { export interface PokemonDetails {
details: string; details: string;
name: string; name: string;
speciesForme: string; speciesForme: string;
@ -942,14 +949,14 @@ interface PokemonDetails {
ident: string; ident: string;
searchid: string; searchid: string;
} }
interface PokemonHealth { export interface PokemonHealth {
hp: number; hp: number;
maxhp: number; maxhp: number;
hpcolor: HPColor | ''; hpcolor: HPColor | '';
status: StatusName | 'tox' | '' | '???'; status: StatusName | 'tox' | '' | '???';
fainted?: boolean; fainted?: boolean;
} }
interface ServerPokemon extends PokemonDetails, PokemonHealth { export interface ServerPokemon extends PokemonDetails, PokemonHealth {
ident: string; ident: string;
details: string; details: string;
condition: string; condition: string;
@ -976,8 +983,8 @@ interface ServerPokemon extends PokemonDetails, PokemonHealth {
gigantamax: string | false; gigantamax: string | false;
} }
class Battle { export class Battle {
scene: BattleScene | BattleSceneStub; scene: BattleSceneStub;
sidesSwitched = false; sidesSwitched = false;
@ -3669,7 +3676,7 @@ class Battle {
} }
setMute(mute: boolean) { setMute(mute: boolean) {
BattleSound.setMute(mute); this.scene.setMute(mute);
} }
} }

40
src/globals.d.ts vendored
View File

@ -1,14 +1,5 @@
// dex data
///////////
// dependencies
///////////////
// Caja
declare var html4: any;
declare var html: any;
// data
///////
declare var BattlePokedex: any; declare var BattlePokedex: any;
declare var BattleMovedex: any; declare var BattleMovedex: any;
@ -16,21 +7,9 @@ declare var BattleAbilities: any;
declare var BattleItems: any; declare var BattleItems: any;
declare var BattleAliases: any; declare var BattleAliases: any;
declare var BattleStatuses: any; declare var BattleStatuses: any;
// declare var BattleMoveAnims: any;
// declare var BattleStatusAnims: any;
// declare var BattleOtherAnims: any;
// declare var BattleBackdrops: any;
// declare var BattleBackdropsThree: any;
// declare var BattleBackdropsFour: any;
// declare var BattleBackdropsFive: any;
// declare var BattleEffects: any;
declare var BattlePokemonSprites: any; declare var BattlePokemonSprites: any;
declare var BattlePokemonSpritesBW: any; declare var BattlePokemonSpritesBW: any;
// defined in battle-log-misc
declare function MD5(input: string): string;
declare function formatText(input: string, isTrusted?: boolean): string;
// PS globals // PS globals
///////////// /////////////
@ -43,3 +22,18 @@ declare var app: {user: AnyObject, rooms: AnyObject, ignore?: AnyObject};
interface Window { interface Window {
[k: string]: any; [k: string]: any;
} }
// Temporary globals (exported from modules, used by non-module files)
// When creating now module files, these should all be commented out
// to make sure they're not being used globally in modules.
declare var Battle: typeof import('./battle').Battle;
type Battle = import('./battle').Battle;
declare var BattleScene: typeof import('./battle-animations').BattleScene;
type BattleScene = import('./battle-animations').BattleScene;
declare var Pokemon: typeof import('./battle').Pokemon;
type Pokemon = import('./battle').Pokemon;
type ServerPokemon = import('./battle').ServerPokemon;
declare var BattleLog: typeof import('./battle-log').BattleLog;
type BattleLog = import('./battle-log').BattleLog;

View File

@ -36,6 +36,7 @@
"no-unnecessary-initializer": false, "no-unnecessary-initializer": false,
"object-literal-sort-keys": false, "object-literal-sort-keys": false,
"object-literal-key-quotes": false, "object-literal-key-quotes": false,
"ordered-imports": false,
"trailing-comma": [ "trailing-comma": [
true, true,
{ {