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('
').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';
}