diff --git a/build-tools/build-indexes b/build-tools/build-indexes index b78efc97e..681eb6184 100755 --- a/build-tools/build-indexes +++ b/build-tools/build-indexes @@ -434,6 +434,7 @@ function buildTeambuilderTables() { const tierTable = {}; const overrideTier = {}; const ubersUUBans = {}; + const ndDoublesBans = {}; const monotypeBans = {}; const nonstandardMoves = []; for (const id of pokemon) { @@ -548,8 +549,14 @@ function buildTeambuilderTables() { if (ubersUU.exists && Dex.formats.getRuleTable(ubersUU).isBannedSpecies(species)) { ubersUUBans[species.id] = 1; } + const ndDoubles = Dex.formats.get(gen + 'nationaldexdoubles'); + if (ndDoubles.exists && Dex.formats.getRuleTable(ndDoubles).isBannedSpecies(species)) { + ndDoublesBans[species.id] = 1; + } + } + if (genNum >= 5) { const mono = Dex.formats.get(gen + (isNatDex ? 'nationaldex' : '') + 'monotype'); - if (Dex.formats.getRuleTable(mono).isBannedSpecies(species)) { + if (mono.exists && Dex.formats.getRuleTable(mono).isBannedSpecies(species)) { monotypeBans[species.id] = 1; } } @@ -570,6 +577,7 @@ function buildTeambuilderTables() { BattleTeambuilderTable['gen' + genNum + 'natdex'].tiers = tiers; BattleTeambuilderTable['gen' + genNum + 'natdex'].overrideTier = overrideTier; BattleTeambuilderTable['gen' + genNum + 'natdex'].items = items; + BattleTeambuilderTable['gen' + genNum + 'natdex'].ndDoublesBans = ndDoublesBans; BattleTeambuilderTable['gen' + genNum + 'natdex'].monotypeBans = monotypeBans; BattleTeambuilderTable['gen' + genNum + 'natdex'].formatSlices = formatSlices; } else if (isMetBattle) { @@ -1016,7 +1024,7 @@ function buildTeambuilderTables() { // Client relevant data that should be overriden by past gens and mods const overrideSpeciesKeys = ['abilities', 'baseStats', 'cosmeticFormes', 'isNonstandard', 'requiredItems', 'types', 'unreleasedHidden']; const overrideMoveKeys = ['accuracy', 'basePower', 'category', 'desc', 'flags', 'isNonstandard', 'noSketch', 'pp', 'priority', 'shortDesc', 'target', 'type', 'viable']; - const overrideAbilityKeys = ['desc', 'isNonstandard', 'rating', 'shortDesc']; + const overrideAbilityKeys = ['desc', 'flags', 'isNonstandard', 'rating', 'shortDesc']; const overrideItemKeys = ['desc', 'isNonstandard', 'rating', 'shortDesc']; // @@ -1652,6 +1660,7 @@ function buildMoves() { const move = Dex.moves.get(Moves[id].name); if (move.desc) Moves[id].desc = move.desc; if (move.shortDesc) Moves[id].shortDesc = move.shortDesc; + if (move.basePowerCallback) Moves[id].basePowerCallback = true; } const buf = 'exports.BattleMovedex = ' + es3stringify(Moves) + ';'; fs.writeFileSync('play.pokemonshowdown.com/data/moves.js', buf); diff --git a/play.pokemonshowdown.com/.htaccess b/play.pokemonshowdown.com/.htaccess index bdc899847..7d782014c 100644 --- a/play.pokemonshowdown.com/.htaccess +++ b/play.pokemonshowdown.com/.htaccess @@ -36,7 +36,7 @@ RewriteRule ^adminrequests?\/?$ https://www.smogon.com/forums/threads/names-pass RewriteCond %{HTTP_HOST} ^play\.pokemonshowdown\.com$ [NC] RewriteRule ^forgotpassword\/?$ https://www.smogon.com/forums/threads/names-passwords-rooms-and-servers-contacting-upper-staff.3538721/post-6227626/ [R=302,L] RewriteCond %{HTTP_HOST} ^play\.pokemonshowdown\.com$ [NC] -RewriteRule ^bugs?(reports?)?\/?$ https://www.smogon.com/forums/threads/3663703/ [R=302,L] +RewriteRule ^bugs?(reports?)?\/?$ https://www.smogon.com/forums/ps-bug-report-form/ [R=302,L] RewriteCond %{HTTP_HOST} ^play\.pokemonshowdown\.com$ [NC] RewriteRule ^rules?\/?$ https://pokemonshowdown.com/rules [R=302,L] RewriteCond %{HTTP_HOST} ^play\.pokemonshowdown\.com$ [NC] diff --git a/play.pokemonshowdown.com/js/client.js b/play.pokemonshowdown.com/js/client.js index 5e7e978ee..cade9fe09 100644 --- a/play.pokemonshowdown.com/js/client.js +++ b/play.pokemonshowdown.com/js/client.js @@ -1037,7 +1037,7 @@ function toId() { var replayLink = 'https://' + Config.routes.replays + '/' + replayid; $.ajax(replayLink + '.json', {dataType: 'json'}).done(function (replay) { if (replay) { - var title = replay.p1 + ' vs. ' + replay.p2; + var title = replay.players[0] + ' vs. ' + replay.players[1]; app.receive('>battle-' + replayid + '\n|init|battle\n|title|' + title + '\n' + replay.log); app.receive('>battle-' + replayid + '\n|expire|Open replay in new tab'); } else { @@ -1500,7 +1500,9 @@ function toId() { if (roomEl && roomEl.id) { var roomid = roomEl.id.slice(5); window.app.renameRoom(roomid, target); - window.app.rooms[target].join(); + if (window.app.rooms[target]) { + window.app.rooms[target].join(); + } e.preventDefault(); e.stopPropagation(); e.stopImmediatePropagation(); @@ -2555,6 +2557,10 @@ function toId() { this.$el.html('

' + (data.htmlMessage || BattleLog.parseMessage(data.message)) + '

' + (data.buttons || '') + '

').css('max-width', data.maxWidth || 480); }, + copyText: function (value, target) { + app.curRoom.copyText(value, target); + }, + dispatchClickButton: function (e) { var target = e.currentTarget; if (target.name) { diff --git a/play.pokemonshowdown.com/js/search.js b/play.pokemonshowdown.com/js/search.js index 6c5a39a66..97a78878b 100644 --- a/play.pokemonshowdown.com/js/search.js +++ b/play.pokemonshowdown.com/js/search.js @@ -385,7 +385,10 @@ } buf += 'Spe
' + stats.spe + '
'; var bst = 0; - for (i in stats) bst += stats[i]; + for (i in stats) { + if (i === 'spd' && gen === 1) continue; + bst += stats[i]; + } buf += 'BST
' + bst + '
'; buf += ''; diff --git a/play.pokemonshowdown.com/src/battle-animations-moves.ts b/play.pokemonshowdown.com/src/battle-animations-moves.ts index 0c8eaf081..2859c8fec 100644 --- a/play.pokemonshowdown.com/src/battle-animations-moves.ts +++ b/play.pokemonshowdown.com/src/battle-animations-moves.ts @@ -2622,6 +2622,9 @@ export const BattleMoveAnims: AnimTable = { spikyshield: { anim: BattleOtherAnims.selfstatus.anim, }, + burningbulwark: { + anim: BattleOtherAnims.selfstatus.anim, + }, banefulbunker: { anim(scene, [attacker]) { scene.backgroundEffect('linear-gradient(#440044 30%, #000000', 600, 0.2); @@ -6951,7 +6954,7 @@ export const BattleMoveAnims: AnimTable = { lick: { anim: BattleOtherAnims.contactattack.anim, }, - vicegrip: { + visegrip: { anim: BattleOtherAnims.contactattack.anim, }, headbutt: { @@ -22864,6 +22867,221 @@ export const BattleMoveAnims: AnimTable = { }, prepareAnim: BattleOtherAnims.chargestatus.anim, }, + electroshot: { + anim(scene, [attacker, defender]) { + let xstep = (defender.x - attacker.x) / 5; + let ystep = (defender.x - 200 - attacker.x) / 5; + let zstep = (defender.z - attacker.z) / 5; + + scene.backgroundEffect('#000000', 900, 0.5); + + for (let i = 0; i < 5; i++) { + scene.showEffect('electroball', { + x: attacker.x + xstep * (i + 1), + y: (attacker.y + 200) + ystep * (i + 1), + z: attacker.z + zstep * (i + 1), + scale: 0.7, + opacity: 0.6, + time: 40 * i + 300, + }, { + opacity: 0, + time: 100 * i + 500, + }, 'linear', '', {filter: 'hue-rotate(120deg)'}); + } + + scene.showEffect('electroball', { + x: attacker.x, + y: attacker.y, + z: attacker.z, + scale: 0.75, + opacity: 0.6, + }, { + x: attacker.x, + y: attacker.y + 200, + z: attacker.z, + scale: 1.25, + opacity: 0, + time: 200, + }, 'decel', '', {filter: 'hue-rotate(120deg)'}); + scene.showEffect('wisp', { + x: attacker.x, + y: attacker.y, + z: attacker.z, + scale: 1, + opacity: 0.6, + }, { + x: attacker.x, + y: attacker.y + 200, + z: attacker.z, + scale: 1.5, + opacity: 0, + time: 200, + }, 'decel'); + + scene.showEffect('electroball', { + x: attacker.x, + y: attacker.y + 200, + z: attacker.z, + scale: 0.4, + opacity: 0.6, + time: 300, + }, { + x: defender.x + 30, + y: defender.y + 30, + z: defender.z, + scale: 0.6, + opacity: 0.3, + time: 500, + }, 'linear', 'explode', {filter: 'hue-rotate(120deg)'}); + scene.showEffect('electroball', { + x: attacker.x, + y: attacker.y + 200, + z: attacker.z, + scale: 0.4, + opacity: 0.6, + time: 375, + }, { + x: defender.x + 20, + y: defender.y - 30, + z: defender.z, + scale: 0.6, + opacity: 0.3, + time: 575, + }, 'linear', 'explode'); + scene.showEffect('electroball', { + x: attacker.x, + y: attacker.y + 200, + z: attacker.z, + scale: 0.4, + opacity: 0.6, + time: 425, + }, { + x: defender.x - 10, + y: defender.y + 10, + z: defender.z, + scale: 0.6, + opacity: 0.3, + time: 625, + }, 'linear', 'explode'); + scene.showEffect('electroball', { + x: attacker.x, + y: attacker.y + 200, + z: attacker.z, + scale: 0.4, + opacity: 0.6, + time: 450, + }, { + x: defender.x - 30, + y: defender.y, + z: defender.z, + scale: 0.6, + opacity: 0.3, + time: 650, + }, 'linear', 'explode', {filter: 'hue-rotate(120deg)'}); + scene.showEffect('electroball', { + x: attacker.x, + y: attacker.y + 200, + z: attacker.z, + scale: 0.4, + opacity: 0.6, + time: 500, + }, { + x: defender.x + 10, + y: defender.y - 10, + z: defender.z, + scale: 0.6, + opacity: 0.3, + time: 700, + }, 'linear', 'explode', {filter: 'hue-rotate(120deg)'}); + scene.showEffect('electroball', { + x: attacker.x, + y: attacker.y + 200, + z: attacker.z, + scale: 0.4, + opacity: 0.6, + time: 575, + }, { + x: defender.x - 20, + y: defender.y, + z: defender.z, + scale: 0.6, + opacity: 0.3, + time: 775, + }, 'linear', 'explode'); + }, + prepareAnim(scene, [attacker]) { + scene.showEffect('electroball', { + x: attacker.x - 60, + y: attacker.y + 40, + z: attacker.z, + scale: 0.7, + opacity: 0.7, + time: 0, + }, { + x: attacker.x, + y: attacker.y, + scale: 0.2, + opacity: 0.2, + time: 300, + }, 'linear', 'fade', {filter: 'hue-rotate(120deg)'}); + scene.showEffect('electroball', { + x: attacker.x + 60, + y: attacker.y - 5, + z: attacker.z, + scale: 0.7, + opacity: 0.7, + time: 100, + }, { + x: attacker.x, + y: attacker.y, + scale: 0.2, + opacity: 0.2, + time: 300, + }, 'linear', 'fade', {filter: 'hue-rotate(120deg)'}); + scene.showEffect('electroball', { + x: attacker.x - 30, + y: attacker.y + 60, + z: attacker.z, + scale: 0.7, + opacity: 0.7, + time: 100, + }, { + x: attacker.x, + y: attacker.y, + scale: 0.2, + opacity: 0.2, + time: 400, + }, 'linear', 'fade', {filter: 'hue-rotate(120deg)'}); + scene.showEffect('electroball', { + x: attacker.x + 20, + y: attacker.y - 50, + z: attacker.z, + scale: 0.7, + opacity: 0.7, + time: 100, + }, { + x: attacker.x, + y: attacker.y, + scale: 0.2, + opacity: 0.2, + time: 400, + }, 'linear', 'fade', {filter: 'hue-rotate(120deg)'}); + scene.showEffect('electroball', { + x: attacker.x - 70, + y: attacker.y - 50, + z: attacker.z, + scale: 0.7, + opacity: 0.7, + time: 200, + }, { + x: attacker.x, + y: attacker.y, + scale: 0.2, + opacity: 0.2, + time: 500, + }, 'linear', 'fade', {filter: 'hue-rotate(120deg)'}); + }, + }, solarblade: { anim(scene, [attacker, defender]) { let xstep = 0; @@ -35893,7 +36111,3 @@ BattleMoveAnims['trailblaze'] = {anim: BattleMoveAnims['powerwhip'].anim}; BattleMoveAnims['tripledive'] = {anim: BattleMoveAnims['dive'].anim}; BattleMoveAnims['hydrosteam'] = {anim: BattleMoveAnims['steameruption'].anim}; BattleMoveAnims['psyblade'] = {anim: BattleMoveAnims['psychocut'].anim}; -BattleMoveAnims['electroshot'] = { - anim: BattleMoveAnims['zapcannon'].anim, - prepareAnim: BattleOtherAnims.lightstatus.anim, -}; diff --git a/play.pokemonshowdown.com/src/battle-dex-data.ts b/play.pokemonshowdown.com/src/battle-dex-data.ts index 9a98ec0d2..37739e185 100644 --- a/play.pokemonshowdown.com/src/battle-dex-data.ts +++ b/play.pokemonshowdown.com/src/battle-dex-data.ts @@ -433,8 +433,8 @@ const BattlePokemonIconIndexes: {[id: string]: number} = { // alt forms with duplicate icons greninjabond: 658, gumshoostotem: 735, - raticatealolatotem: 1020 + 120, - marowakalolatotem: 1020 + 136, + raticatealolatotem: 1032 + 120, + marowakalolatotem: 1032 + 136, araquanidtotem: 752, lurantistotem: 754, salazzletotem: 758, @@ -1048,7 +1048,7 @@ type NatureName = 'Adamant' | 'Bashful' | 'Bold' | 'Brave' | 'Calm' | 'Careful' 'Quiet' | 'Quirky' | 'Rash' | 'Relaxed' | 'Sassy' | 'Serious' | 'Timid'; type StatNameExceptHP = 'atk' | 'def' | 'spa' | 'spd' | 'spe'; type TypeName = 'Normal' | 'Fighting' | 'Flying' | 'Poison' | 'Ground' | 'Rock' | 'Bug' | 'Ghost' | 'Steel' | - 'Fire' | 'Water' | 'Grass' | 'Electric' | 'Psychic' | 'Ice' | 'Dragon' | 'Dark' | 'Fairy' | '???'; + 'Fire' | 'Water' | 'Grass' | 'Electric' | 'Psychic' | 'Ice' | 'Dragon' | 'Dark' | 'Fairy' | 'Stellar' | '???'; type StatusName = 'par' | 'psn' | 'frz' | 'slp' | 'brn'; type BoostStatName = 'atk' | 'def' | 'spa' | 'spd' | 'spe' | 'evasion' | 'accuracy' | 'spc'; type GenderName = 'M' | 'F' | 'N'; @@ -1376,6 +1376,25 @@ class Move implements Effect { } } +interface AbilityFlags { + /** Can be suppressed by Mold Breaker and related effects */ + breakable?: 1; + /** Ability can't be suppressed by e.g. Gastro Acid or Neutralizing Gas */ + cantsuppress?: 1; + /** Role Play fails if target has this Ability */ + failroleplay?: 1; + /** Skill Swap fails if either the user or target has this Ability */ + failskillswap?: 1; + /** Entrainment fails if user has this Ability */ + noentrain?: 1; + /** Receiver and Power of Alchemy will not activate if an ally faints with this Ability */ + noreceiver?: 1; + /** Trace cannot copy this Ability */ + notrace?: 1; + /** Disables the Ability if the user is Transformed */ + notransform?: 1; +} + class Ability implements Effect { // effect readonly effectType = 'Ability'; @@ -1389,7 +1408,7 @@ class Ability implements Effect { readonly desc: string; readonly rating: number; - readonly isPermanent: boolean; + readonly flags: AbilityFlags; readonly isNonstandard: boolean; constructor(id: ID, name: string, data: any) { @@ -1403,7 +1422,7 @@ class Ability implements Effect { this.shortDesc = data.shortDesc || data.desc || ''; this.desc = data.desc || data.shortDesc || ''; this.rating = data.rating || 1; - this.isPermanent = !!data.isPermanent; + this.flags = data.flags || {}; this.isNonstandard = !!data.isNonstandard; if (!this.gen) { if (this.num >= 234) { diff --git a/play.pokemonshowdown.com/src/battle-dex-search.ts b/play.pokemonshowdown.com/src/battle-dex-search.ts index 74688f1e9..780de6e34 100644 --- a/play.pokemonshowdown.com/src/battle-dex-search.ts +++ b/play.pokemonshowdown.com/src/battle-dex-search.ts @@ -718,8 +718,10 @@ abstract class BattleTypedSearch { this.dex = Dex.mod('gen7letsgo' as ID); } if (format.includes('nationaldex') || format.startsWith('nd') || format.includes('natdex')) { - format = (format.startsWith('nd') ? format.slice(2) : - format.includes('natdex') ? format.slice(6) : format.slice(11)) as ID; + if (format !== 'nationaldexdoubles') { + format = (format.startsWith('nd') ? format.slice(2) : + format.includes('natdex') ? format.slice(6) : format.slice(11)) as ID; + } this.formatType = 'natdex'; if (!format) format = 'ou' as ID; } @@ -1037,7 +1039,7 @@ class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> { table['gen' + dex.gen + 'doubles'] && dex.gen > 4 && this.formatType !== 'letsgo' && this.formatType !== 'bdspdoubles' && this.formatType !== 'ssdlc1doubles' && this.formatType !== 'predlcdoubles' && - this.formatType !== 'svdlc1doubles' && + this.formatType !== 'svdlc1doubles' && !this.formatType?.includes('natdex') && ( format.includes('doubles') || format.includes('triples') || format === 'freeforall' || format.startsWith('ffa') || @@ -1095,8 +1097,9 @@ class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> { } let tierSet: SearchRow[] = table.tierSet; let slices: {[k: string]: number} = table.formatSlices; - if (format === 'ubers' || format === 'uber' || format === 'ubersuu') tierSet = tierSet.slice(slices.Uber); - else if (isVGCOrBS || (isHackmons && dex.gen === 9 && !this.formatType)) { + if (format === 'ubers' || format === 'uber' || format === 'ubersuu' || format === 'nationaldexdoubles') { + tierSet = tierSet.slice(slices.Uber); + } else if (isVGCOrBS || (isHackmons && dex.gen === 9 && !this.formatType)) { if (format.endsWith('series13') || isHackmons) { // Show Mythicals } else if ( @@ -1108,7 +1111,7 @@ class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> { tierSet = tierSet.slice(slices.Regular); } } else if (format === 'ou') tierSet = tierSet.slice(slices.OU); - else if (format === 'uu') tierSet = tierSet.slice(slices.UU); + else if (format === 'uu' || (format === 'ru' && dex.gen === 3)) tierSet = tierSet.slice(slices.UU); else if (format === 'ru') tierSet = tierSet.slice(slices.RU || slices.UU); else if (format === 'nu') tierSet = tierSet.slice(slices.NU || slices.RU || slices.UU); else if (format === 'pu') tierSet = tierSet.slice(slices.PU || slices.NU); @@ -1150,6 +1153,12 @@ class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> { return true; }); } + if (format === 'nationaldexdoubles' && table.ndDoublesBans) { + tierSet = tierSet.filter(([type, id]) => { + if (id in table.ndDoublesBans) return false; + return true; + }); + } if (dex.gen >= 5) { if ((format === 'monotype' || format.startsWith('monothreat')) && table.monotypeBans) { tierSet = tierSet.filter(([type, id]) => { @@ -1253,8 +1262,12 @@ class BattlePokemonSearch extends BattleTypedSearch<'pokemon'> { } const base1 = this.dex.species.get(id1).baseStats; const base2 = this.dex.species.get(id2).baseStats; - const bst1 = base1.hp + base1.atk + base1.def + base1.spa + base1.spd + base1.spe; - const bst2 = base2.hp + base2.atk + base2.def + base2.spa + base2.spd + base2.spe; + let bst1 = base1.hp + base1.atk + base1.def + base1.spa + base1.spd + base1.spe; + let bst2 = base2.hp + base2.atk + base2.def + base2.spa + base2.spd + base2.spe; + if (this.dex.gen === 1) { + bst1 -= base1.spd; + bst2 -= base2.spd; + } return (bst2 - bst1) * sortOrder; }); } else if (sortCol === 'name') { diff --git a/play.pokemonshowdown.com/src/battle-log.ts b/play.pokemonshowdown.com/src/battle-log.ts index 2709fab9f..81150be8c 100644 --- a/play.pokemonshowdown.com/src/battle-log.ts +++ b/play.pokemonshowdown.com/src/battle-log.ts @@ -915,11 +915,13 @@ export class BattleLog { const src = getAttrib('src') || ''; // Google's ToS requires a minimum of 200x200 - let width = '320'; - let height = '200'; - if (window.innerWidth >= 400) { - width = '400'; - height = '225'; + let width = getAttrib('width') || '0'; + let height = getAttrib('height') || '0'; + if (Number(width) < 200) { + width = window.innerWidth >= 400 ? '400' : '320'; + } + if (Number(height) < 200) { + height = window.innerWidth >= 400 ? '225' : '200'; } const videoId = /(?:\?v=|\/embed\/)([A-Za-z0-9_\-]+)/.exec(src)?.[1]; if (!videoId) return {tagName: 'img', attribs: ['alt', `invalid src for `]}; @@ -935,6 +937,7 @@ export class BattleLog { 'width', width, 'height', height, 'src', `https://www.youtube.com/embed/${videoId}?enablejsapi=1&playsinline=1${time ? `&start=${time}` : ''}`, 'frameborder', '0', 'allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture', 'allowfullscreen', 'allowfullscreen', + 'time', (time || 0) + "", ], }; } else if (tagName === 'formatselect') { @@ -1068,7 +1071,8 @@ export class BattleLog { static initYoutubePlayer(idx: number) { const id = `youtube-iframe-${idx}`; const loadPlayer = () => { - if (!$(`#${id}`).length) return; + const el = $(`#${id}`); + if (!el.length) return; const player = new window.YT.Player(id, { events: { onStateChange: (event: any) => { @@ -1081,7 +1085,12 @@ export class BattleLog { }, }, }); + const time = Number(el.attr('time')); + if (time) { + player.seekTo(time); + } this.players[idx - 1] = player; + }; // wait for html element to be in DOM this.ensureYoutube().then(() => { diff --git a/play.pokemonshowdown.com/src/battle-tooltips.ts b/play.pokemonshowdown.com/src/battle-tooltips.ts index 6f2a3fe52..f126d9683 100644 --- a/play.pokemonshowdown.com/src/battle-tooltips.ts +++ b/play.pokemonshowdown.com/src/battle-tooltips.ts @@ -492,6 +492,7 @@ class BattleTooltips { Flying: "Supersonic Skystrike", Ground: "Tectonic Rage", Fairy: "Twinkle Tackle", + Stellar: "", "???": "", }; @@ -514,6 +515,7 @@ class BattleTooltips { Flying: "Max Airstream", Ground: "Max Quake", Fairy: "Max Starfall", + Stellar: "", "???": "", }; @@ -1124,7 +1126,7 @@ class BattleTooltips { stats.spa = Math.floor(stats.spa * 1.5); } if (ability === 'orichalcumpulse') { - stats.atk = Math.floor(stats.atk * 1.3); + stats.atk = Math.floor(stats.atk * 1.3333); } let allyActive = clientPokemon?.side.active; if (allyActive) { @@ -1189,7 +1191,7 @@ class BattleTooltips { speedModifiers.push(2); } if (ability === 'hadronengine') { - stats.spa = Math.floor(stats.spa * 1.3); + stats.spa = Math.floor(stats.spa * 1.3333); } } if (item === 'choicespecs' && !clientPokemon?.volatiles['dynamax']) { @@ -1464,8 +1466,8 @@ class BattleTooltips { if (move.id === 'terablast' && pokemon.terastallized) { moveType = pokemon.terastallized as TypeName; } - if (move.id === 'terastarstorm' && pokemon.terastallized === 'Stellar') { - moveType = pokemon.terastallized as TypeName; + if (move.id === 'terastarstorm' && pokemon.getSpeciesForme() === 'Terapagos-Stellar') { + moveType = 'Stellar'; } // Aura Wheel as Morpeko-Hangry changes the type to Dark @@ -1881,9 +1883,6 @@ class BattleTooltips { if (['psn', 'tox'].includes(pokemon.status) && move.category === 'Physical') { value.abilityModify(1.5, "Toxic Boost"); } - if (this.battle.gen > 2 && serverPokemon.status === 'brn' && move.id !== 'facade' && move.category === 'Physical') { - if (!value.tryAbility("Guts")) value.modify(0.5, 'Burn'); - } if (['Rock', 'Ground', 'Steel'].includes(moveType) && this.battle.weather === 'sandstorm') { if (value.tryAbility("Sand Force")) value.weatherModify(1.3, "Sandstorm", "Sand Force"); } @@ -2017,6 +2016,11 @@ class BattleTooltips { value.set(60, 'Tera type BP minimum'); } + // Burn isn't really a base power modifier, so it needs to be applied after the Tera BP floor + if (this.battle.gen > 2 && serverPokemon.status === 'brn' && move.id !== 'facade' && move.category === 'Physical') { + if (!value.tryAbility("Guts")) value.modify(0.5, 'Burn'); + } + if ( move.id === 'steelroller' && !this.battle.hasPseudoWeather('Electric Terrain') && diff --git a/play.pokemonshowdown.com/src/battle.ts b/play.pokemonshowdown.com/src/battle.ts index a71e270ee..70836f784 100644 --- a/play.pokemonshowdown.com/src/battle.ts +++ b/play.pokemonshowdown.com/src/battle.ts @@ -476,7 +476,7 @@ export class Pokemon implements PokemonDetails, PokemonHealth { } getTypes(serverPokemon?: ServerPokemon, preterastallized = false): [ReadonlyArray, TypeName | ''] { let types: ReadonlyArray; - if (this.terastallized && !preterastallized) { + if (!preterastallized && this.terastallized && this.terastallized !== 'Stellar') { types = [this.terastallized as TypeName]; } else if (this.volatiles.typechange) { types = this.volatiles.typechange[1].split('/'); @@ -521,11 +521,14 @@ export class Pokemon implements PokemonDetails, PokemonHealth { return !this.getTypeList(serverPokemon).includes('Flying'); } effectiveAbility(serverPokemon?: ServerPokemon) { - if (this.fainted || this.volatiles['gastroacid']) return ''; const ability = this.side.battle.dex.abilities.get( serverPokemon?.ability || this.ability || serverPokemon?.baseAbility || '' ); - if (this.side.battle.ngasActive() && !ability.isPermanent) { + if ( + this.fainted || + (this.volatiles['transform'] && ability.flags['notransform']) || + (!ability.flags['cantsuppress'] && (this.side.battle.ngasActive() || this.volatiles['gastroacid'])) + ) { return ''; } return ability.name; @@ -1243,6 +1246,7 @@ export class Battle { } return pokemonList; } + // Used in Pokemon#effectiveAbility over abilityActive to prevent infinite recursion ngasActive() { for (const active of this.getAllActive()) { if (active.ability === 'Neutralizing Gas' && !active.volatiles['gastroacid']) { @@ -1253,12 +1257,9 @@ export class Battle { } abilityActive(abilities: string | string[]) { if (typeof abilities === 'string') abilities = [abilities]; - if (this.ngasActive()) { - abilities = abilities.filter(a => this.dex.abilities.get(a).isPermanent); - if (!abilities.length) return false; - } + abilities = abilities.map(toID); for (const active of this.getAllActive()) { - if (abilities.includes(active.ability) && !active.volatiles['gastroacid']) { + if (abilities.includes(toID(active.effectiveAbility()))) { return true; } } @@ -2919,7 +2920,6 @@ export class Battle { target!.side.removeSideCondition('Reflect'); target!.side.removeSideCondition('LightScreen'); break; - case 'hyperdrill': case 'hyperspacefury': case 'hyperspacehole': case 'phantomforce': diff --git a/play.pokemonshowdown.com/style/sim-types.css b/play.pokemonshowdown.com/style/sim-types.css index 18f988f9d..9f2381dc5 100644 --- a/play.pokemonshowdown.com/style/sim-types.css +++ b/play.pokemonshowdown.com/style/sim-types.css @@ -393,3 +393,19 @@ background: linear-gradient(to bottom, hsl(310,41%,77%), hsl(310,41%,83%)); border-color: hsl(310,41%,41%); } + +.movemenu button.type-Stellar { + background: linear-gradient(90deg, hsl(330, 65%, 65%), hsl(126, 71%, 73%), hsl(231, 100%, 77%)); + border-color: linear-gradient(90deg, hsl(330, 66%, 58%), hsl(126, 79%, 65%), hsl(231, 98%, 65%)); +} +.movemenu button.type-Stellar small { + color: hsl(244, 24%, 24%); +} +.movemenu button.type-Stellar:hover { + background: linear-gradient(90deg, hsl(330, 65%, 62%), hsl(126, 71%, 67%), hsl(231, 100%, 68%)); + border-color: linear-gradient(90deg, hsl(330, 66%, 58%), hsl(126, 79%, 65%), hsl(231, 98%, 65%)); +} +.movemenu button.type-Stellar:active, .movemenu button.type-Stellar.pressed { + background: linear-gradient(90deg, hsl(330, 65%, 45%), hsl(126, 71%, 73%), hsl(231, 100%, 86%)); + border-color: linear-gradient(90deg, hsl(330, 66%, 58%), hsl(126, 79%, 65%), hsl(231, 98%, 65%)); +} diff --git a/pokemonshowdown.com/.htaccess b/pokemonshowdown.com/.htaccess index 58266a864..5b6b17f26 100644 --- a/pokemonshowdown.com/.htaccess +++ b/pokemonshowdown.com/.htaccess @@ -92,7 +92,7 @@ RewriteRule ^help\/?$ https://play.pokemonshowdown.com/help [R=302,L] RewriteRule ^rooms?suggestions?\/?$ https://www.smogon.com/forums/threads/room-suggestions-are-no-longer-being-taken.3522130/ [R=302,L] RewriteRule ^suggestions?\/?$ https://www.smogon.com/forums/forums/suggestions.517/ [R=302,L] RewriteRule ^adminrequests?\/?$ https://www.smogon.com/forums/threads/names-rooms-and-servers-contacting-senior-staff.3538721/ [R=302,L] -RewriteRule ^bugs?(reports?)?\/?$ https://www.smogon.com/forums/threads/3663703/ [R=302,L] +RewriteRule ^bugs?(reports?)?\/?$ https://www.smogon.com/forums/ps-bug-report-form/ [R=302,L] RewriteRule ^faq\/?$ https://www.smogon.com/forums/posts/6774128/ [R=302,L] RewriteRule ^whatismyip$ whatismyip.php [L,QSA] diff --git a/pokemonshowdown.com/users.php b/pokemonshowdown.com/users.php index 6a69d89e8..5ffc1a384 100644 --- a/pokemonshowdown.com/users.php +++ b/pokemonshowdown.com/users.php @@ -463,7 +463,7 @@

;_;7

'; }