pokemon-showdown/data/mods/ssb/scripts.js
Guangcong Luo f3e45fbb72 Move server code to server/
Also move mods/ to data/mods/

This makes PS more monorepo-like. The intent is to further separate
the sim and the server code, but without fully committing to splitting
the repository itself.

We now support `./pokemon-showdown start` in addition to
`./pokemon-showdown`. I'm not clear which I want to be the default
yet.
2019-02-03 16:07:06 -06:00

321 lines
12 KiB
JavaScript

'use strict';
/** @type {ModdedBattleScriptsData} */
let BattleScripts = {
runMove(moveOrMoveName, pokemon, targetLoc, sourceEffect, zMove, externalMove) {
let target = this.getTarget(pokemon, zMove || moveOrMoveName, targetLoc);
let baseMove = this.getActiveMove(moveOrMoveName);
const pranksterBoosted = baseMove.pranksterBoosted;
if (!sourceEffect && baseMove.id !== 'struggle' && !zMove) {
let changedMove = this.runEvent('OverrideAction', pokemon, target, baseMove);
if (changedMove && changedMove !== true) {
baseMove = this.getActiveMove(changedMove);
if (pranksterBoosted) baseMove.pranksterBoosted = pranksterBoosted;
target = this.resolveTarget(pokemon, baseMove);
}
}
let move = zMove ? this.getActiveZMove(baseMove, pokemon) : baseMove;
if (!target && target !== false) target = this.resolveTarget(pokemon, move);
move.isExternal = externalMove;
this.setActiveMove(move, pokemon, target);
/* if (pokemon.moveThisTurn) {
// THIS IS PURELY A SANITY CHECK
// DO NOT TAKE ADVANTAGE OF THIS TO PREVENT A POKEMON FROM MOVING;
// USE this.cancelMove INSTEAD
this.debug('' + pokemon.id + ' INCONSISTENT STATE, ALREADY MOVED: ' + pokemon.moveThisTurn);
this.clearActiveMove(true);
return;
} */
let willTryMove = this.runEvent('BeforeMove', pokemon, target, move);
if (!willTryMove) {
this.runEvent('MoveAborted', pokemon, target, move);
this.clearActiveMove(true);
// The event 'BeforeMove' could have returned false or null
// false indicates that this counts as a move failing for the purpose of calculating Stomping Tantrum's base power
// null indicates the opposite, as the Pokemon didn't have an option to choose anything
pokemon.moveThisTurnResult = willTryMove;
return;
}
if (move.beforeMoveCallback) {
if (move.beforeMoveCallback.call(this, pokemon, target, move)) {
this.clearActiveMove(true);
pokemon.moveThisTurnResult = false;
return;
}
}
pokemon.lastDamage = 0;
let lockedMove;
if (!externalMove) {
lockedMove = this.runEvent('LockMove', pokemon);
if (lockedMove === true) lockedMove = false;
if (!lockedMove) {
if (!pokemon.deductPP(baseMove, null, target) && (move.id !== 'struggle')) {
this.add('cant', pokemon, 'nopp', move);
let gameConsole = [null, 'Game Boy', 'Game Boy', 'Game Boy Advance', 'DS', 'DS'][this.gen] || '3DS';
this.add('-hint', "This is not a bug, this is really how it works on the " + gameConsole + "; try it yourself if you don't believe us.");
this.clearActiveMove(true);
pokemon.moveThisTurnResult = false;
return;
}
} else {
sourceEffect = this.getEffect('lockedmove');
}
pokemon.moveUsed(move, targetLoc);
}
// Dancer Petal Dance hack
// TODO: implement properly
let noLock = externalMove && !pokemon.volatiles.lockedmove;
if (zMove) {
if (pokemon.illusion) {
this.singleEvent('End', this.getAbility('Illusion'), pokemon.abilityData, pokemon);
}
this.add('-zpower', pokemon);
// @ts-ignore pokemon.zMoveUsed only exists in this mod
pokemon.zMoveUsed = true;
}
let moveDidSomething = this.useMove(baseMove, pokemon, target, sourceEffect, zMove);
if (this.activeMove) move = this.activeMove;
this.singleEvent('AfterMove', move, null, pokemon, target, move);
this.runEvent('AfterMove', pokemon, target, move);
// Dancer's activation order is completely different from any other event, so it's handled separately
if (move.flags['dance'] && moveDidSomething && !move.isExternal) {
let dancers = [];
for (const side of this.sides) {
for (const currentPoke of side.active) {
if (!currentPoke || !currentPoke.hp || pokemon === currentPoke) continue;
if (currentPoke.hasAbility('dancer') && !currentPoke.isSemiInvulnerable()) {
dancers.push(currentPoke);
}
}
}
// Dancer activates in order of lowest speed stat to highest
// Ties go to whichever Pokemon has had the ability for the least amount of time
dancers.sort(function (a, b) { return -(b.stats['spe'] - a.stats['spe']) || b.abilityOrder - a.abilityOrder; });
for (const dancer of dancers) {
if (this.faintMessages()) break;
this.add('-activate', dancer, 'ability: Dancer');
this.runMove(move.id, dancer, 0, this.getAbility('dancer'), undefined, true);
}
}
if (noLock && pokemon.volatiles.lockedmove) delete pokemon.volatiles.lockedmove;
},
// Modded to allow unlimited mega evos
runMegaEvo(pokemon) {
const templateid = pokemon.canMegaEvo || pokemon.canUltraBurst;
if (!templateid) return false;
const side = pokemon.side;
// Pokémon affected by Sky Drop cannot mega evolve. Enforce it here for now.
for (const foeActive of side.foe.active) {
if (foeActive.volatiles['skydrop'] && foeActive.volatiles['skydrop'].source === pokemon) {
return false;
}
}
pokemon.formeChange(templateid, pokemon.getItem(), true);
// Limit mega evolution to once-per-Pokemon
pokemon.canMegaEvo = null;
this.runEvent('AfterMega', pokemon);
// E4 flint gains fire type when mega evolving
if (pokemon.name === 'E4 Flint' && !pokemon.illusion) this.add('-start', pokemon, 'typeadd', 'Fire');
return true;
},
getZMove(move, pokemon, skipChecks) {
let item = pokemon.getItem();
if (!skipChecks) {
if (!item.zMove) return;
if (item.zMoveUser && !item.zMoveUser.includes(pokemon.template.species)) return;
let moveData = pokemon.getMoveData(move);
if (!moveData || !moveData.pp) return; // Draining the PP of the base move prevents the corresponding Z-move from being used.
}
if (item.zMoveFrom) {
if (Array.isArray(item.zMoveFrom)) {
if (item.zMoveFrom.includes(move.name)) return /** @type {string} */ (item.zMove);
} else {
if (move.name === item.zMoveFrom) return /** @type {string} */ (item.zMove);
}
} else if (item.zMove === true) {
if (move.type === item.zMoveType) {
if (move.category === "Status") {
return move.name;
} else if (move.zMovePower) {
return this.zMoveTable[move.type];
}
}
}
},
getActiveZMove(move, pokemon) {
let zMove;
if (pokemon) {
let item = pokemon.getItem();
if (item.zMoveFrom && Array.isArray(item.zMoveFrom) ? item.zMoveFrom.includes(move.name) : item.zMoveFrom === move.name) {
// @ts-ignore
zMove = this.getActiveMove(item.zMove);
// @ts-ignore Hack for Snaquaza's Z move
zMove.baseMove = move;
zMove.isZPowered = true;
return zMove;
}
}
if (move.category === 'Status') {
zMove = this.getActiveMove(move);
zMove.isZ = true;
zMove.isZPowered = true;
return zMove;
}
zMove = this.getActiveMove(this.zMoveTable[move.type]);
// @ts-ignore
zMove.basePower = move.zMovePower;
zMove.category = move.category;
zMove.isZPowered = true;
return zMove;
},
// Modded to allow each Pokemon on a team to use a Z move once per battle
canZMove(pokemon) {
// @ts-ignore pokemon.zMoveUsed only exists in this mod
if (pokemon.zMoveUsed || (pokemon.transformed && (pokemon.template.isMega || pokemon.template.isPrimal || pokemon.template.forme === "Ultra"))) return;
let item = pokemon.getItem();
if (!item.zMove) return;
if (item.zMoveUser && !item.zMoveUser.includes(pokemon.template.species)) return;
let atLeastOne = false;
/**@type {AnyObject?[]} */
let zMoves = [];
for (const moveSlot of pokemon.moveSlots) {
if (moveSlot.pp <= 0) {
zMoves.push(null);
continue;
}
let move = this.getMove(moveSlot.move);
let zMoveName = this.getZMove(move, pokemon, true) || '';
if (zMoveName) {
let zMove = this.getMove(zMoveName);
if (!zMove.isZ && zMove.category === 'Status') zMoveName = "Z-" + zMoveName;
zMoves.push({move: zMoveName, target: zMove.target});
} else {
zMoves.push(null);
}
if (zMoveName) atLeastOne = true;
}
if (atLeastOne) return zMoves;
},
runZPower(move, pokemon) {
const zPower = this.getEffect('zpower');
if (move.category !== 'Status') {
this.attrLastMove('[zeffect]');
} else if (move.zMoveBoost) {
this.boost(move.zMoveBoost, pokemon, pokemon, zPower);
} else {
switch (move.zMoveEffect) {
case 'heal':
this.heal(pokemon.maxhp, pokemon, pokemon, zPower);
break;
case 'healhalf':
// For DragonWhale
this.heal(pokemon.maxhp / 2, pokemon, pokemon, zPower);
break;
case 'healreplacement':
move.self = {sideCondition: 'healreplacement'};
break;
case 'boostreplacement':
// For nui
move.self = {sideCondition: 'boostreplacement'};
break;
case 'clearnegativeboost':
/** @type {{[k: string]: number}} */
let boosts = {};
for (let i in pokemon.boosts) {
// @ts-ignore
if (pokemon.boosts[i] < 0) {
boosts[i] = 0;
}
}
pokemon.setBoost(boosts);
this.add('-clearnegativeboost', pokemon, '[zeffect]');
break;
case 'redirect':
pokemon.addVolatile('followme', pokemon, zPower);
break;
case 'crit2':
pokemon.addVolatile('focusenergy', pokemon, zPower);
break;
case 'curse':
if (pokemon.hasType('Ghost')) {
this.heal(pokemon.maxhp, pokemon, pokemon, zPower);
} else {
this.boost({atk: 1}, pokemon, pokemon, zPower);
}
}
}
},
setTerrain(status, source = null, sourceEffect = null) {
status = this.getEffect(status);
if (!sourceEffect && this.effect) sourceEffect = this.effect;
if (!source && this.event && this.event.target) source = this.event.target;
if (source === 'debug') source = this.p1.active[0];
if (!source) throw new Error(`setting terrain without a source`);
if (this.terrain === status.id) return false;
let prevTerrain = this.terrain;
let prevTerrainData = this.terrainData;
this.terrain = status.id;
this.terrainData = {id: status.id};
if (source) {
this.terrainData.source = source;
this.terrainData.sourcePosition = source.position;
}
if (status.duration) {
this.terrainData.duration = status.duration;
}
if (status.durationCallback) {
this.terrainData.duration = status.durationCallback.call(this, source, source, sourceEffect);
}
if (!this.singleEvent('Start', status, this.terrainData, this, source, sourceEffect)) {
this.terrain = prevTerrain;
this.terrainData = prevTerrainData;
return false;
}
// Always run a terrain end event to prevent a visual glitch with custom terrains
if (prevTerrain) this.singleEvent('End', this.getEffect(prevTerrain), prevTerrainData, this);
return true;
},
pokemon: {
getActionSpeed() {
let speed = this.getStat('spe', false, false);
if (speed > 10000) speed = 10000;
if (this.battle.getPseudoWeather('trickroom') || this.battle.getPseudoWeather('triviaroom') || this.battle.getPseudoWeather('alienwave')) {
speed = 0x2710 - speed;
}
return speed & 0x1FFF;
},
isGrounded(negateImmunity = false) {
if ('gravity' in this.battle.pseudoWeather) return true;
if ('ingrain' in this.volatiles && this.battle.gen >= 4) return true;
if ('smackdown' in this.volatiles) return true;
let item = (this.ignoringItem() ? '' : this.item);
if (item === 'ironball') return true;
// If a Fire/Flying type uses Burn Up and Roost, it becomes ???/Flying-type, but it's still grounded.
if (!negateImmunity && this.hasType('Flying') && !('roost' in this.volatiles)) return false;
if (this.hasAbility('levitate') && !this.battle.suppressingAttackEvents()) return null;
if ('magnetrise' in this.volatiles) return false;
if ('telekinesis' in this.volatiles) return false;
if ('triviaroom' in this.battle.pseudoWeather && this.name === 'Bimp' && !this.illusion) return false;
return item !== 'airballoon';
},
},
};
exports.BattleScripts = BattleScripts;