diff --git a/js/client-battle.js b/js/client-battle.js index c1a96640d..c60df1d62 100644 --- a/js/client-battle.js +++ b/js/client-battle.js @@ -7,7 +7,6 @@ minMainWidth: 956, maxWidth: 1180, initialize: function (data) { - this.me = {}; this.choice = undefined; /** are move/switch/team-preview controls currently being shown? */ this.controlsShown = false; @@ -335,6 +334,9 @@ if (this.request) { // TODO: investigate when to do this this.updateSide(); + if (this.request.ally) { + this.addAlly(this.request.ally); + } act = this.request.requestType; if (this.request.side) { @@ -382,7 +384,7 @@ if (this.request.forceSwitch !== true) { var faintedLength = _.filter(this.request.forceSwitch, function (fainted) {return fainted;}).length; - var freedomDegrees = faintedLength - _.filter(switchables.slice(this.battle.nearSide.active.length), function (mon) {return !mon.fainted;}).length; + var freedomDegrees = faintedLength - _.filter(switchables.slice(this.battle.pokemonControlled), function (mon) {return !mon.fainted;}).length; this.choice.freedomDegrees = Math.max(freedomDegrees, 0); this.choice.canSwitch = faintedLength - this.choice.freedomDegrees; } @@ -404,6 +406,9 @@ done: 0, count: 1 }; + if (this.battle.gameType === 'multi') { + this.choice.count = 1; + } if (this.battle.gameType === 'doubles') { this.choice.count = 2; } @@ -516,7 +521,8 @@ } var moveTarget = this.choice ? this.choice.moveTarget : ''; - var pos = this.choice.choices.length - (type === 'movetarget' ? 1 : 0); + var pos = this.choice.choices.length; + if (type === 'movetarget') pos--; var hpRatio = switchables[pos].hp / switchables[pos].maxhp; @@ -555,10 +561,12 @@ if (type === 'movetarget') { requestTitle += 'At who? '; + var activePos = this.battle.mySide.n > 1 ? pos + this.battle.pokemonControlled : pos; + var targetMenus = ['', '']; var nearActive = this.battle.nearSide.active; var farActive = this.battle.farSide.active; - var farSlot = farActive.length - 1 - pos; + var farSlot = farActive.length - 1 - activePos; for (var i = farActive.length - 1; i >= 0; i--) { var pokemon = farActive[i]; @@ -587,9 +595,9 @@ if (moveTarget === 'adjacentFoe') { disabled = true; } else if (moveTarget === 'normal' || moveTarget === 'adjacentAlly' || moveTarget === 'adjacentAllyOrSelf') { - if (Math.abs(pos - i) > 1) disabled = true; + if (Math.abs(activePos - i) > 1) disabled = true; } - if (moveTarget !== 'adjacentAllyOrSelf' && pos == i) disabled = true; + if (moveTarget !== 'adjacentAllyOrSelf' && activePos == i) disabled = true; if (disabled) { targetMenus[1] += ' '; @@ -615,7 +623,8 @@ var hasMoves = false; var moveMenu = ''; var movebuttons = ''; - var typeValueTracker = new ModifiableValue(this.battle, this.battle.nearSide.active[pos], this.battle.myPokemon[pos]); + var activePos = this.battle.mySide.n > 1 ? pos + this.battle.pokemonControlled : pos; + var typeValueTracker = new ModifiableValue(this.battle, this.battle.nearSide.active[activePos], this.battle.myPokemon[pos]); var currentlyDynamaxed = (!canDynamax && maxMoves); for (var i = 0; i < curActive.moves.length; i++) { var moveData = curActive.moves[i]; @@ -627,7 +636,7 @@ if (move.id === 'Recharge') move.type = '–'; if (name.substr(0, 12) === 'Hidden Power') name = 'Hidden Power'; var moveType = this.tooltips.getMoveType(move, typeValueTracker)[0]; - var tooltipArgs = 'move|' + moveData.move + '|' + pos; + var tooltipArgs = 'move|' + moveData.move + '|' + activePos; if (moveData.disabled) { movebuttons += ' '; } else { party += ' '; } } + if (this.battle.mySide.ally) party += this.displayAllyParty(); + return party; + }, + displayAllyParty: function () { + var party = ''; + if (!this.battle.mySide.ally) return; + var allyParty = this.battle.myAllyPokemon; + for (var i = 0; i < allyParty.length; i++) { + var pokemon = allyParty[i]; + pokemon.name = pokemon.ident.substr(4); + var tooltipArgs = 'allypokemon|' + i; + party += ' '; + } return party; }, updateSwitchControls: function (type) { @@ -760,7 +781,7 @@ // TODO? hpbar requestTitle += "Which Pokémon will it switch in for?"; var controls = '
'; - for (var i = 0; i < nearActive.length; i++) { + for (var i = 0; i < this.battle.pokemonControlled; i++) { var pokemon = this.battle.myPokemon[i]; var tooltipArgs = 'switchpokemon|' + i; if (pokemon && !pokemon.fainted || this.choice.switchOutFlags[i]) { @@ -789,8 +810,8 @@ for (var i = 0; i < switchables.length; i++) { var pokemon = switchables[i]; var tooltipArgs = 'switchpokemon|' + i; - if (pokemon.fainted || i < this.battle.nearSide.active.length || this.choice.switchFlags[i]) { - switchMenu += '
`; } pokemonhtml = '
' + pokemonhtml + '
'; + const ratinghtml = side.rating ? ` title="Rating: ${BattleLog.escapeHTML(side.rating)}"` : ``; + let posStr = side.isFar ? 'far' : 'near'; + if (isAlly) posStr += '2'; + return `
${BattleLog.escapeHTML(side.name)}
${pokemonhtml}
`; + } + updateSidebar(side: Side) { + if (side.n > 1) side = side.ally; const $sidebar = (side.isFar ? this.$rightbar : this.$leftbar); + let sidebarhtml = this.getSidebarHTML(side) + (side.ally ? this.getSidebarHTML(side.ally, true) : ''); if (side.name) { - const ratinghtml = side.rating ? ` title="Rating: ${BattleLog.escapeHTML(side.rating)}"` : ``; - $sidebar.html(`
${BattleLog.escapeHTML(side.name)}
${pokemonhtml}
`); + $sidebar.html(sidebarhtml); $sidebar.find('.trainer').css('opacity', 1); } else { $sidebar.find('.trainer').css('opacity', 0.4); } } updateSidebars() { - for (const side of this.battle.sides) this.updateSidebar(side); + this.updateSidebar(this.battle.nearSide); + this.updateSidebar(this.battle.farSide); } updateStatbars() { for (const side of this.battle.sides) { @@ -713,17 +721,33 @@ class BattleScene { } } - teamPreviewEnd() { - for (let siden = 0; siden < 2; siden++) { - this.$sprites[siden].empty(); - this.battle.sides[siden].updateSprites(); + resetSides(skipEmpty?: boolean) { + if (!skipEmpty) { + for (const $spritesContainer of this.$sprites) { + $spritesContainer.empty(); + } + } + for (const side of this.battle.sides) { + side.z = (side.isFar ? 200 : 0); + side.missedPokemon?.sprite?.destroy(); + + side.missedPokemon = { + sprite: new PokemonSprite(null, { + x: side.leftof(-100), + y: side.y, + z: side.z, + opacity: 0, + }, this, side.isFar), + } as any; + + side.missedPokemon.sprite.isMissedPokemon = true; } } teamPreview() { let newBGNum = 0; - for (let siden = 0; siden < 2; siden++) { - const side = this.battle.sides[siden]; - const spriteIndex = +this.battle.sidesSwitched ^ siden; + for (let siden = 0; siden < 2 || (this.battle.gameType === 'multi' && siden < 4); siden++) { + let side = this.battle.sides[siden]; + const spriteIndex = +this.battle.sidesSwitched ^ (siden % 2); let textBuf = ''; let buf = ''; let buf2 = ''; @@ -1426,20 +1450,6 @@ class BattleScene { afterMove(pokemon: Pokemon) { return pokemon.sprite.afterMove(); } - updateSpritesForSide(side: Side) { - side.missedPokemon?.sprite?.destroy(); - - side.missedPokemon = { - sprite: new PokemonSprite(null, { - x: side.leftof(-100), - y: side.y, - z: side.z, - opacity: 0, - }, this, side.isFar), - } as any; - - side.missedPokemon.sprite.isMissedPokemon = true; - } // Misc ///////////////////////////////////////////////////////////////////// diff --git a/src/battle-scene-stub.ts b/src/battle-scene-stub.ts index 31005f6f5..9f58dc1b3 100644 --- a/src/battle-scene-stub.ts +++ b/src/battle-scene-stub.ts @@ -42,8 +42,9 @@ class BattleSceneStub { runStatusAnim(moveid: ID, participants: Pokemon[]): void { } startAnimations(): void { } teamPreview(): void { } - teamPreviewEnd(): void { } + resetSides(): void { } updateGen(): void { } + getSidebarHTML(side: Side) { } updateSidebar(side: Side): void { } updateSidebars(): void { } updateStatbars(): void { } @@ -68,7 +69,6 @@ class BattleSceneStub { anim(pokemon: Pokemon, end: ScenePos, transition?: string) { } beforeMove(pokemon: Pokemon) { } afterMove(pokemon: Pokemon) { } - updateSpritesForSide(side: Side) { } unlink(userid: string, showRevealButton = false) { } } diff --git a/src/battle-text-parser.ts b/src/battle-text-parser.ts index 4dbb33483..cbfa22b7e 100644 --- a/src/battle-text-parser.ts +++ b/src/battle-text-parser.ts @@ -12,16 +12,20 @@ declare const BattleText: {[id: string]: {[templateName: string]: string}}; type Args = [string, ...string[]]; type KWArgs = {[kw: string]: string}; +type SideID = 'p1' | 'p2' | 'p3' | 'p4'; class BattleTextParser { p1 = "Player 1"; p2 = "Player 2"; - perspective: 0 | 1; + p3 = "Player 3"; + p4 = "Player 4"; + perspective: SideID; gen = 7; + turn = 0; curLineSection: 'break' | 'preMajor' | 'major' | 'postMajor' = 'break'; lowercaseRegExp: RegExp | null | undefined = undefined; - constructor(perspective: 0 | 1 = 0) { + constructor(perspective: SideID = 'p1') { this.perspective = perspective; } @@ -226,7 +230,7 @@ class BattleTextParser { pokemonName = (pokemon: string) => { if (!pokemon) return ''; - if (!pokemon.startsWith('p1') && !pokemon.startsWith('p2')) return `???pokemon:${pokemon}???`; + if (!pokemon.startsWith('p')) return `???pokemon:${pokemon}???`; if (pokemon.charAt(3) === ':') return pokemon.slice(4).trim(); else if (pokemon.charAt(2) === ':') return pokemon.slice(3).trim(); return `???pokemon:${pokemon}???`; @@ -234,14 +238,11 @@ class BattleTextParser { pokemon(pokemon: string) { if (!pokemon) return ''; - let side; - switch (pokemon.slice(0, 2)) { - case 'p1': side = 0; break; - case 'p2': side = 1; break; - default: return `???pokemon:${pokemon}???`; - } + let side = pokemon.slice(0, 2); + if (!['p1', 'p2', 'p3', 'p4'].includes(side)) return `???pokemon:${pokemon}???`; const name = this.pokemonName(pokemon); - const template = BattleText.default[side === this.perspective ? 'pokemon' : 'opposingPokemon']; + const isNear = side === this.perspective || side === BattleTextParser.allyID(side as SideID); + const template = BattleText.default[isNear ? 'pokemon' : 'opposingPokemon']; return template.replace('[NICKNAME]', name); } @@ -257,20 +258,30 @@ class BattleTextParser { side = side.slice(0, 2); if (side === 'p1') return this.p1; if (side === 'p2') return this.p2; + if (side === 'p3') return this.p3; + if (side === 'p4') return this.p4; return `???side:${side}???`; } - team(side: string, isFar: 0 | 1 = 0) { + static allyID(sideid: SideID): SideID | '' { + if (sideid === 'p1') return 'p3'; + if (sideid === 'p2') return 'p4'; + if (sideid === 'p3') return 'p1'; + if (sideid === 'p4') return 'p2'; + return ''; + } + + team(side: string, isFar: boolean = false) { side = side.slice(0, 2); - if (side === (this.perspective === isFar ? 'p1' : 'p2')) { - return BattleText.default.team; + if (side === this.perspective || side === BattleTextParser.allyID(side as SideID)) { + return isFar ? BattleText.default.team : BattleText.default.opposingTeam; } - return BattleText.default.opposingTeam; + return !isFar ? BattleText.default.team : BattleText.default.opposingTeam; } own(side: string) { side = side.slice(0, 2); - if (side === (this.perspective === 0 ? 'p1' : 'p2')) { + if (side === this.perspective) { return 'OWN'; } return ''; @@ -278,7 +289,7 @@ class BattleTextParser { party(side: string) { side = side.slice(0, 2); - if (side === (this.perspective === 0 ? 'p1' : 'p2')) { + if (side === this.perspective || side === BattleTextParser.allyID(side as SideID)) { return BattleText.default.party; } return BattleText.default.opposingParty; @@ -347,7 +358,7 @@ class BattleTextParser { switch (cmd) { case 'done' : case 'turn': return 'break'; - case 'move' : case 'cant': case 'switch': case 'drag': case 'upkeep': case 'start': case '-mega': + case 'move' : case 'cant': case 'switch': case 'drag': case 'upkeep': case 'start': case '-mega': case '-candynamax': return 'major'; case 'switchout': case 'faint': return 'preMajor'; @@ -409,6 +420,10 @@ class BattleTextParser { this.p1 = name; } else if (side === 'p2' && name) { this.p2 = name; + } else if (side === 'p3' && name) { + this.p3 = name; + } else if (side === 'p4' && name) { + this.p4 = name; } return ''; } @@ -421,6 +436,7 @@ class BattleTextParser { case 'turn': { const [, num] = args; + this.turn = Number.parseInt(num, 10); return this.template('turn').replace('[NUMBER]', num) + '\n'; } @@ -529,6 +545,18 @@ class BattleTextParser { return line1 + template.replace('[POKEMON]', this.pokemon(pokemon)).replace('[MOVE]', move); } + case '-candynamax': { + let [, side] = args; + const own = this.own(side); + let template = ''; + if (this.turn === 1) { + if (own) template = this.template('canDynamax', own); + } else { + template = this.template('canDynamax', own); + } + return template.replace('[TRAINER]', this.trainer(side)); + } + case 'message': { let [, message] = args; return '' + message + '\n'; @@ -609,7 +637,7 @@ class BattleTextParser { const id = BattleTextParser.effectId(ability); if (id === 'unnerve') { const template = this.template('start', ability); - return line1 + template.replace('[TEAM]', this.team(pokemon.slice(0, 2), 1)); + return line1 + template.replace('[TEAM]', this.team(pokemon.slice(0, 2), true)); } let templateId = 'start'; if (id === 'anticipation' || id === 'sturdy') templateId = 'activate'; diff --git a/src/battle-tooltips.ts b/src/battle-tooltips.ts index 11a88203e..920e99916 100644 --- a/src/battle-tooltips.ts +++ b/src/battle-tooltips.ts @@ -271,11 +271,11 @@ class BattleTooltips { case 'zmove': case 'maxmove': { // move|MOVE|ACTIVEPOKEMON|[GMAXMOVE] let move = this.battle.dex.getMove(args[1]); - let index = parseInt(args[2], 10); - let pokemon = this.battle.nearSide.active[index]; - let serverPokemon = this.battle.myPokemon![index]; + let teamIndex = parseInt(args[2], 10); + let pokemon = this.battle.nearSide.active[teamIndex + this.battle.pokemonControlled * Math.floor(this.battle.mySide!.n / 2)]; let gmaxMove = args[3] ? this.battle.dex.getMove(args[3]) : undefined; if (!pokemon) return false; + let serverPokemon = this.battle.myPokemon![teamIndex]; buf = this.showMoveTooltip(move, type, pokemon, serverPokemon, gmaxMove); break; } @@ -307,6 +307,22 @@ class BattleTooltips { let sideIndex = parseInt(args[1], 10); let side = this.battle.sides[+this.battle.sidesSwitched ^ sideIndex]; let activeIndex = parseInt(args[2], 10); + if (this.battle.gameType === 'multi') { + if (activeIndex >= side.active.length) return; + if (activeIndex && side.sideid !== 'p3' && side.sideid !== 'p4') side = side.ally; + if (!activeIndex && (side.sideid === 'p3' || side.sideid === 'p4')) side = side.ally; + let pokemon = side.active[activeIndex]; + if (!pokemon) return; + let serverPokemon = null; + if (side === this.battle.mySide && this.battle.myPokemon) { + serverPokemon = this.battle.myPokemon[0]; + } else if (side === this.battle.mySide!.ally && this.battle.myAllyPokemon) { + serverPokemon = this.battle.myAllyPokemon[0]; + } + if (!pokemon) return false; + buf = this.showPokemonTooltip(pokemon, serverPokemon, true); + break; + } let pokemon = side.active[activeIndex]; let serverPokemon = null; if (sideIndex === 0 && this.battle.myPokemon) { @@ -319,16 +335,30 @@ class BattleTooltips { case 'switchpokemon': { // switchpokemon|POKEMON // mouse over switchable pokemon // serverPokemon definitely exists, sidePokemon maybe - let side = this.battle.mySide; + // let side = this.battle.mySide; let activeIndex = parseInt(args[1], 10); let pokemon = null; - if (activeIndex < side.active.length) { + /* if (activeIndex < side.active.length && activeIndex < this.battle.pokemonControlled) { pokemon = side.active[activeIndex]; - } + if (pokemon && pokemon.side === side.ally) pokemon = null; + } */ let serverPokemon = this.battle.myPokemon![activeIndex]; buf = this.showPokemonTooltip(pokemon, serverPokemon); break; } + case 'allypokemon': { // allypokemon|POKEMON + // mouse over ally's pokemon in multi battles + // serverPokemon definitely exists, sidePokemon maybe + // let side = this.battle.mySide.ally; + let activeIndex = parseInt(args[1], 10); + let pokemon = null; + /*if (activeIndex < side.pokemon.length) { + pokemon = side.pokemon[activeIndex] || side.ally ? side.ally.pokemon[activeIndex] : null; + }*/ + let serverPokemon = this.battle.myAllyPokemon ? this.battle.myAllyPokemon[activeIndex] : null; + buf = this.showPokemonTooltip(pokemon, serverPokemon); + break; + } case 'field': { buf = this.showFieldTooltip(); break; @@ -1838,8 +1868,8 @@ class BattleTooltips { // this will only be available if the ability announced itself in some way let allyAbility = Dex.getAbility(ally.ability).name; // otherwise fall back on the original set data sent from the server - if (!allyAbility && this.battle.myPokemon) { - allyAbility = Dex.getAbility(this.battle.myPokemon[ally.slot].ability).name; + if (!allyAbility && this.battle.myAllyPokemon) { + allyAbility = Dex.getAbility(this.battle.myAllyPokemon[ally.slot].ability).name; } return allyAbility; } diff --git a/src/battle.ts b/src/battle.ts index bc58cc38c..9befe4ac1 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -577,9 +577,11 @@ class Side { battle: Battle; name = ''; id = ''; + sideid: SideID; n: number; isFar: boolean; foe: Side = null!; + ally: Side = null!; avatar: string = 'unknown'; rating: string = ''; totalPokemon = 6; @@ -597,11 +599,11 @@ class Side { /** [effectName, levels, minDuration, maxDuration] */ sideConditions: {[id: string]: [string, number, number, number]} = {}; - constructor(battle: Battle, n: number, isOpp?: boolean) { + constructor(battle: Battle, n: number) { this.battle = battle; this.n = n; - this.isFar = isOpp || !!n; - this.updateSprites(); + this.sideid = ['p1', 'p2', 'p3', 'p4'][n] as SideID; + this.isFar = !!(n % 2); } rollTrainerSprites() { @@ -630,13 +632,8 @@ class Side { } reset() { this.clearPokemon(); - this.updateSprites(); this.sideConditions = {}; } - updateSprites() { - this.z = (this.isFar ? 200 : 0); - this.battle.scene.updateSpritesForSide(this); - } setAvatar(avatar: string) { this.avatar = avatar; } @@ -1026,13 +1023,17 @@ class Battle { pseudoWeather = [] as WeatherState[]; weatherTimeLeft = 0; weatherMinTimeLeft = 0; - mySide: Side = null!; + mySide: Side | null = null; nearSide: Side = null!; farSide: Side = null!; p1: Side = null!; p2: Side = null!; + p3?: Side = null!; + p4?: Side = null!; + pokemonControlled = 0; + sides: Side[] = null!; myPokemon: ServerPokemon[] | null = null; - sides: [Side, Side] = [null!, null!]; + myAllyPokemon: ServerPokemon[] | null = null; lastMove = ''; gen = 8; @@ -1040,7 +1041,7 @@ class Battle { teamPreviewCount = 0; speciesClause = false; tier = ''; - gameType: 'singles' | 'doubles' | 'triples' = 'singles'; + gameType: 'singles' | 'doubles' | 'triples' | 'multi' = 'singles'; rated: string | boolean = false; isBlitz = false; endLastTurnPending = false; @@ -1154,6 +1155,7 @@ class Battle { if (side) side.reset(); } this.myPokemon = null; + this.myAllyPokemon = null; // DOM state this.scene.reset(); @@ -1176,6 +1178,8 @@ class Battle { this.farSide = null!; this.p1 = null!; this.p2 = null!; + this.p3 = null!; + this.p4 = null!; } log(args: Args, kwArgs?: KWArgs, preempt?: boolean) { @@ -1192,14 +1196,16 @@ class Battle { setSidesSwitched(sidesSwitched: boolean) { this.sidesSwitched = sidesSwitched; if (this.sidesSwitched) { - this.nearSide = this.mySide = this.p2; + this.nearSide = this.p2; this.farSide = this.p1; } else { - this.nearSide = this.mySide = this.p1; + this.nearSide = this.p1; this.farSide = this.p2; } this.nearSide.isFar = false; + if (this.nearSide.ally) this.nearSide.ally.isFar = false; this.farSide.isFar = true; + if (this.farSide.ally) this.farSide.ally.isFar = true; // nothing else should need updating - don't call this function after sending out pokemon } @@ -2824,7 +2830,7 @@ class Battle { this.scene.afterMove(poke); break; } - case '-hint': case '-message': { + case '-hint': case '-message': case '-candynamax': { this.log(args, kwArgs); break; } @@ -2940,23 +2946,15 @@ class Battle { let siden = -1; let slot = -1; // if there is an explicit slot for this pokemon - let slotChart: {[k: string]: number} = {a: 0, b: 1, c: 2, d: 3, e: 4, f: 5}; - if (name.substr(0, 4) === 'p2: ' || name === 'p2') { - siden = this.p2.n; - name = name.substr(4); - } else if (name.substr(0, 4) === 'p1: ' || name === 'p1') { - siden = this.p1.n; - name = name.substr(4); - } else if (name.substr(0, 2) === 'p2' && name.substr(3, 2) === ': ') { - slot = slotChart[name.substr(2, 1)]; - siden = this.p2.n; - name = name.substr(5); - pokemonid = 'p2: ' + name; - } else if (name.substr(0, 2) === 'p1' && name.substr(3, 2) === ': ') { - slot = slotChart[name.substr(2, 1)]; - siden = this.p1.n; - name = name.substr(5); - pokemonid = 'p1: ' + name; + if (/^p[1-9]($|: )/.test(name)) { + siden = parseInt(name.charAt(1), 10) - 1; + name = name.slice(4); + } else if (/^p[1-9][a-f]: /.test(name)) { + const slotChart: {[k: string]: number} = {a: 0, b: 1, c: 2, d: 3, e: 4, f: 5}; + siden = parseInt(name.charAt(1), 10) - 1; + slot = slotChart[name.charAt(2)]; + name = name.slice(5); + pokemonid = `p${siden + 1}: ${name}`; } return {name, siden, slot, pokemonid}; } @@ -3035,8 +3033,10 @@ class Battle { return null; } getSide(sidename: string): Side { - if (sidename === 'p1' || sidename.substr(0, 3) === 'p1:') return this.p1; - if (sidename === 'p2' || sidename.substr(0, 3) === 'p2:') return this.p2; + if (sidename === 'p1' || sidename.startsWith('p1:')) return this.p1; + if (sidename === 'p2' || sidename.startsWith('p2:')) return this.p2; + if ((sidename === 'p3' || sidename.startsWith('p3:')) && this.p3) return this.p3; + if ((sidename === 'p4' || sidename.startsWith('p4:')) && this.p4) return this.p4; if (this.nearSide.id === sidename) return this.nearSide; if (this.farSide.id === sidename) return this.farSide; if (this.nearSide.name === sidename) return this.nearSide; @@ -3071,9 +3071,9 @@ class Battle { runMajor(args: Args, kwArgs: KWArgs, preempt?: boolean) { switch (args[0]) { case 'start': { - this.scene.teamPreviewEnd(); this.nearSide.active[0] = null; this.farSide.active[0] = null; + this.scene.resetSides(); this.start(); break; } @@ -3103,8 +3103,22 @@ class Battle { this.gameType = args[1] as any; switch (args[1]) { default: - this.nearSide.active = [null]; - this.farSide.active = [null]; + for (const side of this.sides) side.active = [null]; + break; + case 'multi': + this.pokemonControlled = 1; + this.p3 = new Side(this, 2); + this.p4 = new Side(this, 3); + this.p3.foe = this.p4.ally = this.p2; + this.p3.ally = this.p4.foe = this.p1; + this.p1.ally = this.p3; + this.p2.ally = this.p4; + this.p3.isFar = this.p1.isFar; + this.p4.isFar = this.p2.isFar; + this.sides = [this.p1, this.p2, this.p3, this.p4]; + // intentionally sync p1/p3 and p2/p4's active arrays + this.p1.active = this.p3.active = [null, null]; + this.p2.active = this.p4.active = [null, null]; break; case 'doubles': this.nearSide.active = [null, null]; @@ -3116,7 +3130,9 @@ class Battle { this.farSide.active = [null, null, null]; break; } + if (!this.pokemonControlled) this.pokemonControlled = this.nearSide.active.length; this.scene.updateGen(); + this.scene.resetSides(); break; } case 'rule': { @@ -3221,9 +3237,9 @@ class Battle { side.setName(args[2]); if (args[3]) side.setAvatar(args[3]); if (args[4]) side.rating = args[4]; - this.scene.updateSidebar(side); if (this.joinButtons) this.scene.hideJoinButtons(); this.log(args); + this.scene.updateSidebar(side); break; } case 'teamsize': { diff --git a/src/panel-battle.tsx b/src/panel-battle.tsx index 71ca6f937..b0538a4a1 100644 --- a/src/panel-battle.tsx +++ b/src/panel-battle.tsx @@ -10,6 +10,8 @@ type BattleDesc = { minElo?: number | string, p1?: string, p2?: string, + p3?: string, + p4?: string, }; class BattlesRoom extends PSRoom { diff --git a/style/battle.css b/style/battle.css index c6533c645..9e45ac0af 100644 --- a/style/battle.css +++ b/style/battle.css @@ -315,11 +315,22 @@ License: GPLv2 position: absolute; left: 0; right: 0; - bottom: 102px; + bottom: 75px; } -.rightbar .trainer { + +.trainer-near { + bottom: 75px; +} +.trainer-far { bottom: 232px; } +.trainer-near2 { + bottom: 232px; +} +.trainer-far2 { + bottom: 75px; +} + .trainer strong { display: block; font-size: 8pt; diff --git a/style/client.css b/style/client.css index ea713352f..094b1aa73 100644 --- a/style/client.css +++ b/style/client.css @@ -1851,6 +1851,13 @@ a.ilink.yours { width: 640px; background: #EEF2F5; } +.ps-room .ally-pokemon { + position: absolute; + top: 280px; + left: 0; + width: 640px; + background: #EEF2F5; +} .battle-chat-toggle { display: none; } @@ -1946,6 +1953,7 @@ a.ilink.yours { left: 0; display: block; } +.allyTeam button, .shiftselect button, .moveselect button, .switchselect button { @@ -1970,6 +1978,7 @@ a.ilink.yours { color: #445588; cursor: default; } +.allyparty button, .switchmenu button { position: relative; display: block; @@ -1982,11 +1991,14 @@ a.ilink.yours { white-space: pre; overflow: hidden; } +.allyparty button .picon, .switchmenu button .picon { float: left; margin: -6px -3px -6px -4px; opacity: 0.8; } + +.allyparty button .hpbar, .switchmenu button .hpbar { position: absolute; display: block; @@ -2000,6 +2012,7 @@ a.ilink.yours { /* The declaration order of these three hpbar* classes is significant. */ +.allyparty button .hpbar span, .switchmenu button .hpbar span { display: block; height: 1px; @@ -2007,10 +2020,14 @@ a.ilink.yours { border-top: 1px solid #3C0; border-radius: 1px; } + +.allyparty button .hpbar-yellow span, .switchmenu button .hpbar-yellow span { border-top-color: #a5aa53; background-color: #a2a822; } + +.allyparty button .hpbar-red span, .switchmenu button .hpbar-red span { border-top-color: #faa; background-color: #f55; @@ -2018,6 +2035,7 @@ a.ilink.yours { /****************/ +.allyparty button .status, .switchmenu button .status { position: absolute; display: block; @@ -2098,13 +2116,14 @@ a.ilink.yours { background: #E5E5E5; color: black; } - +.allyparty, .switchmenu, .movemenu { display: block; margin-right: -10px; padding-left: 4px; } +.allyparty button, .switchmenu button, .movemenu button { position: relative; @@ -2122,12 +2141,14 @@ a.ilink.yours { background: #e3e3e3; background: linear-gradient(to bottom, #f6f6f6, #e3e3e3); } +.allyparty button:hover, .switchmenu button:hover, .movemenu button:hover { background: #cfcfcf; background: linear-gradient(to bottom, #f2f2f2, #cfcfcf); border-color: #606060; } +.allyparty button:active, .switchmenu button:active, .movemenu button:active, .switchmenu button.pressed, @@ -2135,6 +2156,8 @@ a.ilink.yours { background: linear-gradient(to bottom, #cfcfcf, #f2f2f2); } +.allyparty button.disabled, +.allyparty button:disabled, .switchmenu button.disabled, .switchmenu button:disabled, .movemenu button:disabled { @@ -2181,6 +2204,7 @@ a.ilink.yours { display: none; padding: 0 75px 0 85px; } + .allyparty, .switchmenu { display: none; max-width: 325px; @@ -2192,6 +2216,7 @@ a.ilink.yours { left: 20px; bottom: 20px; } + .allyTeam, .switchselect { position: absolute; right: 20px;