Last active
May 23, 2018 15:59
-
-
Save Spandamn/a5d0f7495e15d831c63e0c48cf876ae2 to your computer and use it in GitHub Desktop.
mods/linked/scripts.js for Linked
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'use strict'; | |
exports.BattleScripts = { | |
runMove: function (move, pokemon, targetLoc, sourceEffect, zMove, externalMove) { | |
let target = this.getTarget(pokemon, zMove || move, targetLoc); | |
if (!sourceEffect && toId(move) !== 'struggle' && !zMove) { | |
let changedMove = this.runEvent('OverrideAction', pokemon, target, move); | |
if (changedMove && changedMove !== true) { | |
move = changedMove; | |
target = null; | |
} | |
} | |
let baseMove = this.getMove(move); | |
move = zMove ? this.getZMoveCopy(baseMove, pokemon) : baseMove; | |
if (!target && target !== false) target = this.resolveTarget(pokemon, move); | |
// copy the priority for Quick Guard | |
if (zMove) move.priority = baseMove.priority; | |
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) { | |
if (pokemon.volatiles['twoturnmove'] && pokemon.volatiles['twoturnmove'].move === move.id) { // Linked mod | |
pokemon.removeVolatile('twoturnmove'); | |
} | |
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); | |
pokemon.side.zMoveUsed = true; | |
} | |
let moveDidSomething = this.useMove(baseMove, pokemon, target, sourceEffect, zMove); | |
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(baseMove.id, dancer, 0, this.getAbility('dancer'), undefined, true); | |
} | |
} | |
if (noLock && pokemon.volatiles.lockedmove) delete pokemon.volatiles.lockedmove; | |
}, | |
resolveAction: function (action, midTurn = false) { | |
if (!action) throw new Error(`Action not passed to resolveAction`); | |
if (!action.side && action.pokemon) action.side = action.pokemon.side; | |
if (!action.move && action.moveid) action.move = this.getMoveCopy(action.moveid); | |
if (!action.choice && action.move) action.choice = 'move'; | |
if (!action.priority && action.priority !== 0) { | |
let priorities = { | |
'beforeTurn': 100, | |
'beforeTurnMove': 99, | |
'switch': 7, | |
'runUnnerve': 7.3, | |
'runSwitch': 7.2, | |
'runPrimal': 7.1, | |
'instaswitch': 101, | |
'megaEvo': 6.9, | |
'residual': -100, | |
'team': 102, | |
'start': 101, | |
}; | |
if (action.choice in priorities) { | |
action.priority = priorities[action.choice]; | |
} | |
} | |
if (!midTurn) { | |
if (action.choice === 'move') { | |
if (!action.zmove && action.move.beforeTurnCallback) { | |
this.addToQueue({choice: 'beforeTurnMove', pokemon: action.pokemon, move: action.move, targetLoc: action.targetLoc}); | |
} | |
if (action.mega) { | |
// TODO: Check that the Pokémon is not affected by Sky Drop. | |
// (This is currently being done in `runMegaEvo`). | |
this.addToQueue({ | |
choice: 'megaEvo', | |
pokemon: action.pokemon, | |
}); | |
} | |
let linkedMoves = action.pokemon.getLinkedMoves(); | |
if (linkedMoves.length && !linkedMoves.disabled && !action.pokemon.getItem().isChoice && !action.zmove) { | |
let decisionMove = toId(action.move); | |
if (linkedMoves.includes(decisionMove)) { | |
// flag the move as linked here | |
action.linked = linkedMoves.map(moveId => this.getMoveCopy(moveId)); | |
let linkedOtherMove = action.linked[1 - linkedMoves.indexOf(decisionMove)]; | |
if (linkedOtherMove.beforeTurnCallback) { | |
this.addToQueue({choice: 'beforeTurnMove', pokemon: action.pokemon, move: linkedOtherMove, targetLoc: action.targetLoc}); | |
} | |
} | |
} | |
} else if (action.choice === 'switch' || action.choice === 'instaswitch') { | |
if (typeof action.pokemon.switchFlag === 'string') { | |
action.sourceEffect = this.getEffect(action.pokemon.switchFlag); | |
} | |
action.pokemon.switchFlag = false; | |
if (!action.speed) action.speed = action.pokemon.getActionSpeed(); | |
} | |
} | |
let deferPriority = this.gen >= 7 && action.mega && action.mega !== 'done'; | |
if (action.move) { | |
let target = null; | |
action.move = this.getMoveCopy(action.move); | |
if (!action.targetLoc) { | |
target = this.resolveTarget(action.pokemon, action.move); | |
action.targetLoc = this.getTargetLoc(target, action.pokemon); | |
} | |
if (!action.priority && !deferPriority) { | |
let move = action.move; | |
if (action.zmove) { | |
// @ts-ignore | |
let zMoveName = this.getZMove(action.move, action.pokemon, true); | |
let zMove = this.getMove(zMoveName); | |
if (zMove.exists) { | |
move = zMove; | |
} | |
} | |
let priority = move.priority; | |
let linkedMoves = action.pokemon.getLinkedMoves(); | |
let linkIndex = -1; | |
if (linkedMoves.length && !linkedMoves.disabled && !move.isZ && (linkIndex = linkedMoves.indexOf(toId(action.move))) >= 0) { | |
let linkedActions = action.linked || linkedMoves.map(moveId => this.getMoveCopy(moveId)); | |
let altMove = linkedActions[1 - linkIndex]; | |
let thisPriority = this.runEvent('ModifyPriority', action.pokemon, target, linkedActions[linkIndex], priority); | |
let otherPriority = this.runEvent('ModifyPriority', action.pokemon, target, altMove, altMove.priority); | |
priority = Math.min(thisPriority, otherPriority); | |
action.priority = priority; | |
if (this.gen > 5) { | |
// Gen 6+: Quick Guard blocks moves with artificially enhanced priority. | |
// This also applies to Psychic Terrain. | |
linkedActions[linkIndex].priority = priority; | |
altMove.priority = priority; | |
} | |
} else { | |
priority = this.runEvent('ModifyPriority', action.pokemon, target, move, priority); | |
action.priority = priority; | |
if (this.gen > 5) { | |
// Gen 6+: Quick Guard blocks moves with artificially enhanced priority. | |
// This also applies to Psychic Terrain. | |
action.move.priority = priority; | |
} | |
} | |
} | |
} | |
if (!action.speed) { | |
if ((action.choice === 'switch' || action.choice === 'instaswitch') && action.target) { | |
action.speed = action.target.getActionSpeed(); | |
} else if (!action.pokemon) { | |
action.speed = 1; | |
} else if (!deferPriority) { | |
action.speed = action.pokemon.getActionSpeed(); | |
} | |
} | |
return /** @type {any} */ (action); | |
}, | |
runAction: function (action) { | |
// returns whether or not we ended in a callback | |
switch (action.choice) { | |
case 'start': { | |
// I GIVE UP, WILL WRESTLE WITH EVENT SYSTEM LATER | |
let format = this.getFormat(); | |
// Remove Pokémon duplicates remaining after `team` decisions. | |
this.p1.pokemon = this.p1.pokemon.slice(0, this.p1.pokemonLeft); | |
this.p2.pokemon = this.p2.pokemon.slice(0, this.p2.pokemonLeft); | |
if (format.teamLength && format.teamLength.battle) { | |
// Trim the team: not all of the Pokémon brought to Preview will battle. | |
this.p1.pokemon = this.p1.pokemon.slice(0, format.teamLength.battle); | |
this.p1.pokemonLeft = this.p1.pokemon.length; | |
this.p2.pokemon = this.p2.pokemon.slice(0, format.teamLength.battle); | |
this.p2.pokemonLeft = this.p2.pokemon.length; | |
} | |
this.add('start'); | |
for (let pos = 0; pos < this.p1.active.length; pos++) { | |
this.switchIn(this.p1.pokemon[pos], pos); | |
} | |
for (let pos = 0; pos < this.p2.active.length; pos++) { | |
this.switchIn(this.p2.pokemon[pos], pos); | |
} | |
for (const pokemon of this.p1.pokemon) { | |
this.singleEvent('Start', this.getEffect(pokemon.species), pokemon.speciesData, pokemon); | |
} | |
for (const pokemon of this.p2.pokemon) { | |
this.singleEvent('Start', this.getEffect(pokemon.species), pokemon.speciesData, pokemon); | |
} | |
this.midTurn = true; | |
break; | |
} | |
case 'move': | |
if (!action.pokemon.isActive) return false; | |
if (action.pokemon.fainted) return false; | |
// This is where moves are linked | |
if (action.linked) { | |
let linkedMoves = action.linked; | |
for (let i = linkedMoves.length - 1; i >= 0; i--) { | |
let pseudoAction = {choice: 'move', move: linkedMoves[i], targetLoc: action.targetLoc, pokemon: action.pokemon, targetPosition: action.targetPosition, targetSide: action.targetSide}; | |
this.queue.unshift(pseudoAction); | |
} | |
return; | |
} | |
// @ts-ignore | |
this.runMove(action.move, action.pokemon, action.targetLoc, action.sourceEffect, action.zmove); | |
break; | |
case 'megaEvo': | |
// @ts-ignore | |
this.runMegaEvo(action.pokemon); | |
break; | |
case 'beforeTurnMove': { | |
if (!action.pokemon.isActive) return false; | |
if (action.pokemon.fainted) return false; | |
this.debug('before turn callback: ' + action.move.id); | |
let target = this.getTarget(action.pokemon, action.move, action.targetLoc); | |
if (!target) return false; | |
if (!action.move.beforeTurnCallback) throw new Error(`beforeTurnMove has no beforeTurnCallback`); | |
action.move.beforeTurnCallback.call(this, action.pokemon, target); | |
break; | |
} | |
case 'event': | |
// @ts-ignore Easier than defining a custom event attribute tbh | |
this.runEvent(action.event, action.pokemon); | |
break; | |
case 'team': { | |
action.pokemon.side.pokemon.splice(action.index, 0, action.pokemon); | |
action.pokemon.position = action.index; | |
// we return here because the update event would crash since there are no active pokemon yet | |
return; | |
} | |
case 'pass': | |
return; | |
case 'instaswitch': | |
case 'switch': | |
if (action.choice === 'switch' && action.pokemon.status && this.data.Abilities.naturalcure) { | |
this.singleEvent('CheckShow', this.getAbility('naturalcure'), null, action.pokemon); | |
} | |
if (action.pokemon.hp) { | |
action.pokemon.beingCalledBack = true; | |
const sourceEffect = action.sourceEffect; | |
// @ts-ignore | |
if (sourceEffect && sourceEffect.selfSwitch === 'copyvolatile') { | |
action.pokemon.switchCopyFlag = true; | |
} | |
if (!action.pokemon.switchCopyFlag) { | |
this.runEvent('BeforeSwitchOut', action.pokemon); | |
if (this.gen >= 5) { | |
this.eachEvent('Update'); | |
} | |
} | |
if (!this.runEvent('SwitchOut', action.pokemon)) { | |
// Warning: DO NOT interrupt a switch-out | |
// if you just want to trap a pokemon. | |
// To trap a pokemon and prevent it from switching out, | |
// (e.g. Mean Look, Magnet Pull) use the 'trapped' flag | |
// instead. | |
// Note: Nothing in BW or earlier interrupts | |
// a switch-out. | |
break; | |
} | |
} | |
action.pokemon.illusion = null; | |
this.singleEvent('End', this.getAbility(action.pokemon.ability), action.pokemon.abilityData, action.pokemon); | |
if (!action.pokemon.hp && !action.pokemon.fainted) { | |
// a pokemon fainted from Pursuit before it could switch | |
if (this.gen <= 4) { | |
// in gen 2-4, the switch still happens | |
action.priority = -101; | |
this.queue.unshift(action); | |
this.add('-hint', 'Pursuit target fainted, switch continues in gen 2-4'); | |
break; | |
} | |
// in gen 5+, the switch is cancelled | |
this.debug('A Pokemon can\'t switch between when it runs out of HP and when it faints'); | |
break; | |
} | |
if (action.target.isActive) { | |
this.add('-hint', 'Switch failed; switch target is already active'); | |
break; | |
} | |
if (action.choice === 'switch' && action.pokemon.activeTurns === 1) { | |
for (const foeActive of action.pokemon.side.foe.active) { | |
if (foeActive.isStale >= 2) { | |
action.pokemon.isStaleCon++; | |
action.pokemon.isStaleSource = 'switch'; | |
break; | |
} | |
} | |
} | |
this.switchIn(action.target, action.pokemon.position, action.sourceEffect); | |
break; | |
case 'runUnnerve': | |
this.singleEvent('PreStart', action.pokemon.getAbility(), action.pokemon.abilityData, action.pokemon); | |
break; | |
case 'runSwitch': | |
this.runEvent('SwitchIn', action.pokemon); | |
if (this.gen <= 2 && !action.pokemon.side.faintedThisTurn && action.pokemon.draggedIn !== this.turn) this.runEvent('AfterSwitchInSelf', action.pokemon); | |
if (!action.pokemon.hp) break; | |
action.pokemon.isStarted = true; | |
if (!action.pokemon.fainted) { | |
this.singleEvent('Start', action.pokemon.getAbility(), action.pokemon.abilityData, action.pokemon); | |
action.pokemon.abilityOrder = this.abilityOrder++; | |
this.singleEvent('Start', action.pokemon.getItem(), action.pokemon.itemData, action.pokemon); | |
} | |
delete action.pokemon.draggedIn; | |
break; | |
case 'runPrimal': | |
if (!action.pokemon.transformed) this.singleEvent('Primal', action.pokemon.getItem(), action.pokemon.itemData, action.pokemon); | |
break; | |
case 'shift': { | |
if (!action.pokemon.isActive) return false; | |
if (action.pokemon.fainted) return false; | |
action.pokemon.activeTurns--; | |
this.swapPosition(action.pokemon, 1); | |
for (const foeActive of action.pokemon.side.foe.active) { | |
if (foeActive.isStale >= 2) { | |
action.pokemon.isStaleCon++; | |
action.pokemon.isStaleSource = 'switch'; | |
break; | |
} | |
} | |
break; | |
} | |
case 'beforeTurn': | |
this.eachEvent('BeforeTurn'); | |
break; | |
case 'residual': | |
this.add(''); | |
this.clearActiveMove(true); | |
this.updateSpeed(); | |
this.residualEvent('Residual'); | |
this.add('upkeep'); | |
break; | |
} | |
// phazing (Roar, etc) | |
for (const pokemon of this.p1.active) { | |
if (pokemon.forceSwitchFlag) { | |
if (pokemon.hp) this.dragIn(pokemon.side, pokemon.position); | |
pokemon.forceSwitchFlag = false; | |
} | |
} | |
for (const pokemon of this.p2.active) { | |
if (pokemon.forceSwitchFlag) { | |
if (pokemon.hp) this.dragIn(pokemon.side, pokemon.position); | |
pokemon.forceSwitchFlag = false; | |
} | |
} | |
this.clearActiveMove(); | |
// fainting | |
this.faintMessages(); | |
if (this.ended) return true; | |
// switching (fainted pokemon, U-turn, Baton Pass, etc) | |
if (!this.queue.length || (this.gen <= 3 && ['move', 'residual'].includes(this.queue[0].choice))) { | |
// in gen 3 or earlier, switching in fainted pokemon is done after | |
// every move, rather than only at the end of the turn. | |
this.checkFainted(); | |
} else if (action.choice === 'megaEvo' && this.gen >= 7) { | |
this.eachEvent('Update'); | |
// In Gen 7, the action order is recalculated for a Pokémon that mega evolves. | |
const moveIndex = this.queue.findIndex(queuedAction => queuedAction.pokemon === action.pokemon && queuedAction.choice === 'move'); | |
if (moveIndex >= 0) { | |
const moveAction = /** @type {MoveAction} */ (this.queue.splice(moveIndex, 1)[0]); | |
moveAction.mega = 'done'; | |
this.insertQueue(moveAction, true); | |
} | |
return false; | |
} else if (this.queue.length && this.queue[0].choice === 'instaswitch') { | |
return false; | |
} | |
let p1switch = this.p1.active.some(mon => mon && !!mon.switchFlag); | |
let p2switch = this.p2.active.some(mon => mon && !!mon.switchFlag); | |
if (p1switch && !this.canSwitch(this.p1)) { | |
for (const pokemon of this.p1.active) { | |
pokemon.switchFlag = false; | |
} | |
p1switch = false; | |
} | |
if (p2switch && !this.canSwitch(this.p2)) { | |
for (const pokemon of this.p2.active) { | |
pokemon.switchFlag = false; | |
} | |
p2switch = false; | |
} | |
if (p1switch || p2switch) { | |
if (this.gen >= 5) { | |
this.eachEvent('Update'); | |
} | |
this.makeRequest('switch'); | |
return true; | |
} | |
this.eachEvent('Update'); | |
return false; | |
}, | |
pokemon: { | |
moveUsed: function (move, targetLoc) { | |
let lastMove = this.moveThisTurn ? [this.moveThisTurn, move] : move; | |
this.lastMove = lastMove; | |
this.moveThisTurn = lastMove; | |
this.lastMoveTargetLoc = targetLoc; | |
}, | |
getLastMoveAbsolute: function () { // used | |
if (Array.isArray(this.lastMove)) return this.lastMove[1]; | |
return this.lastMove; | |
}, | |
checkMoveThisTurn: function (move) { | |
const moveId = toId(move); | |
if (Array.isArray(this.moveThisTurn)) return this.moveThisTurn.some(moveUsed => moveUsed.id === moveId); | |
return this.moveThisTurn && this.moveThisTurn.id === moveId; | |
}, | |
getLinkedMoves: function () { | |
let linkedMoves = this.moveSlots.slice(0, 2); | |
if (linkedMoves.length !== 2 || linkedMoves[0].pp <= 0 || linkedMoves[1].pp <= 0) return []; | |
let ret = [linkedMoves[0].id, linkedMoves[1].id]; | |
// Disabling effects which won't abort execution of moves already added to battle event loop. | |
if (!this.ateBerry && ret.includes('belch')) { | |
ret.disabled = true; | |
} else if (this.hasItem('assaultvest') && (this.battle.getMove(ret[0]).category === 'Status' || this.battle.getMove(ret[1]).category === 'Status')) { | |
ret.disabled = true; | |
} | |
return ret; | |
}, | |
hasLinkedMove: function (move) { | |
move = toId(move); | |
let linkedMoves = this.getLinkedMoves(); | |
if (!linkedMoves.length) return; | |
return linkedMoves[0] === move || linkedMoves[1] === move; | |
}, | |
}, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment