Merge branch 'beta' into egg-move-scripts
9
.github/workflows/create-release.yml
vendored
@ -20,6 +20,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
create-release:
|
create-release:
|
||||||
if: github.repository == 'pagefaultgames/pokerogue' && (vars.BETA_DEPLOY_BRANCH == '' || ! startsWith(vars.BETA_DEPLOY_BRANCH, 'release'))
|
if: github.repository == 'pagefaultgames/pokerogue' && (vars.BETA_DEPLOY_BRANCH == '' || ! startsWith(vars.BETA_DEPLOY_BRANCH, 'release'))
|
||||||
|
timeout-minutes: 10
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed for github cli commands
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed for github cli commands
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -36,11 +37,13 @@ jobs:
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- uses: actions/create-github-app-token@v2
|
- uses: actions/create-github-app-token@v2
|
||||||
id: app-token
|
id: app-token
|
||||||
with:
|
with:
|
||||||
app-id: ${{ secrets.PAGEFAULT_APP_ID }}
|
app-id: ${{ secrets.PAGEFAULT_APP_ID }}
|
||||||
private-key: ${{ secrets.PAGEFAULT_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.PAGEFAULT_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@ -48,8 +51,10 @@ jobs:
|
|||||||
# Always base off of beta branch, regardless of the branch the workflow was triggered from.
|
# Always base off of beta branch, regardless of the branch the workflow was triggered from.
|
||||||
ref: beta
|
ref: beta
|
||||||
token: ${{ steps.app-token.outputs.token }}
|
token: ${{ steps.app-token.outputs.token }}
|
||||||
|
|
||||||
- name: Create release branch
|
- name: Create release branch
|
||||||
run: git checkout -b release
|
run: git checkout -b release
|
||||||
|
|
||||||
# In order to be able to open a PR into beta, we need the branch to have at least one change.
|
# In order to be able to open a PR into beta, we need the branch to have at least one change.
|
||||||
- name: Overwrite RELEASE file
|
- name: Overwrite RELEASE file
|
||||||
run: |
|
run: |
|
||||||
@ -58,11 +63,14 @@ jobs:
|
|||||||
echo "Release v${{ github.event.inputs.versionName }}" > RELEASE
|
echo "Release v${{ github.event.inputs.versionName }}" > RELEASE
|
||||||
git add RELEASE
|
git add RELEASE
|
||||||
git commit -m "Stage release v${{ github.event.inputs.versionName }}"
|
git commit -m "Stage release v${{ github.event.inputs.versionName }}"
|
||||||
|
|
||||||
- name: Push new branch
|
- name: Push new branch
|
||||||
run: git push origin release
|
run: git push origin release
|
||||||
|
|
||||||
# The repository variable is used by the deploy-beta workflow to determine whether to deploy from beta or release.
|
# The repository variable is used by the deploy-beta workflow to determine whether to deploy from beta or release.
|
||||||
- name: Set repository variable
|
- name: Set repository variable
|
||||||
run: GITHUB_TOKEN="${{ steps.app-token.outputs.token }}" gh variable set BETA_DEPLOY_BRANCH --body "release"
|
run: GITHUB_TOKEN="${{ steps.app-token.outputs.token }}" gh variable set BETA_DEPLOY_BRANCH --body "release"
|
||||||
|
|
||||||
- name: Create pull request to main
|
- name: Create pull request to main
|
||||||
run: |
|
run: |
|
||||||
gh pr create --base main \
|
gh pr create --base main \
|
||||||
@ -70,6 +78,7 @@ jobs:
|
|||||||
--title "Release v${{ github.event.inputs.versionName }} to main" \
|
--title "Release v${{ github.event.inputs.versionName }} to main" \
|
||||||
--body "This PR is for the release of v${{ github.event.inputs.versionName }}, and was created automatically by the GitHub Actions workflow invoked by ${{ github.actor }}" \
|
--body "This PR is for the release of v${{ github.event.inputs.versionName }}, and was created automatically by the GitHub Actions workflow invoked by ${{ github.actor }}" \
|
||||||
--draft
|
--draft
|
||||||
|
|
||||||
- name: Create pull request to beta
|
- name: Create pull request to beta
|
||||||
run: |
|
run: |
|
||||||
gh pr create --base beta \
|
gh pr create --base beta \
|
||||||
|
1
.github/workflows/deploy-beta.yml
vendored
@ -12,6 +12,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
if: github.repository == 'pagefaultgames/pokerogue' && github.ref_name == (vars.BETA_DEPLOY_BRANCH || 'beta')
|
if: github.repository == 'pagefaultgames/pokerogue' && github.ref_name == (vars.BETA_DEPLOY_BRANCH || 'beta')
|
||||||
|
timeout-minutes: 10
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
1
.github/workflows/deploy.yml
vendored
@ -11,6 +11,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
if: github.repository == 'pagefaultgames/pokerogue'
|
if: github.repository == 'pagefaultgames/pokerogue'
|
||||||
|
timeout-minutes: 10
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
5
.github/workflows/github-pages.yml
vendored
@ -6,11 +6,13 @@ on:
|
|||||||
- main
|
- main
|
||||||
- beta
|
- beta
|
||||||
- release
|
- release
|
||||||
|
- 'hotfix*'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- beta
|
- beta
|
||||||
- release
|
- release
|
||||||
|
- 'hotfix*'
|
||||||
merge_group:
|
merge_group:
|
||||||
types: [checks_requested]
|
types: [checks_requested]
|
||||||
|
|
||||||
@ -18,6 +20,7 @@ jobs:
|
|||||||
pages:
|
pages:
|
||||||
name: Github Pages
|
name: Github Pages
|
||||||
if: github.repository == 'pagefaultgames/pokerogue'
|
if: github.repository == 'pagefaultgames/pokerogue'
|
||||||
|
timeout-minutes: 10
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
env:
|
env:
|
||||||
api-dir: ./
|
api-dir: ./
|
||||||
@ -67,7 +70,7 @@ jobs:
|
|||||||
pnpm exec typedoc --out /tmp/docs --githubPages false --entryPoints ./src/
|
pnpm exec typedoc --out /tmp/docs --githubPages false --entryPoints ./src/
|
||||||
|
|
||||||
- name: Commit & Push docs
|
- name: Commit & Push docs
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push' && (github.ref_name == 'beta' || github.ref_name == 'main')
|
||||||
run: |
|
run: |
|
||||||
cd pokerogue_gh
|
cd pokerogue_gh
|
||||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
3
.github/workflows/linting.yml
vendored
@ -6,17 +6,20 @@ on:
|
|||||||
- main
|
- main
|
||||||
- beta
|
- beta
|
||||||
- release
|
- release
|
||||||
|
- 'hotfix*'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- beta
|
- beta
|
||||||
- release
|
- release
|
||||||
|
- 'hotfix*'
|
||||||
merge_group:
|
merge_group:
|
||||||
types: [checks_requested]
|
types: [checks_requested]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run-linters:
|
run-linters:
|
||||||
name: Run all linters
|
name: Run all linters
|
||||||
|
timeout-minutes: 10
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
1
.github/workflows/post-release-deleted.yml
vendored
@ -6,6 +6,7 @@ jobs:
|
|||||||
# Set the BETA_DEPLOY_BRANCH variable to beta when a release branch is deleted
|
# Set the BETA_DEPLOY_BRANCH variable to beta when a release branch is deleted
|
||||||
update-release-var:
|
update-release-var:
|
||||||
if: github.repository == 'pagefaultgames/pokerogue' && github.event.ref_type == 'branch' && github.event.ref == 'release'
|
if: github.repository == 'pagefaultgames/pokerogue' && github.event.ref_type == 'branch' && github.event.ref == 'release'
|
||||||
|
timeout-minutes: 5
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set BETA_DEPLOY_BRANCH to beta
|
- name: Set BETA_DEPLOY_BRANCH to beta
|
||||||
|
1
.github/workflows/test-shard-template.yml
vendored
@ -21,6 +21,7 @@ jobs:
|
|||||||
test:
|
test:
|
||||||
# We can't use dynmically named jobs until https://github.com/orgs/community/discussions/13261 is implemented
|
# We can't use dynmically named jobs until https://github.com/orgs/community/discussions/13261 is implemented
|
||||||
name: Shard
|
name: Shard
|
||||||
|
timeout-minutes: 10
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: ${{ !inputs.skip }}
|
if: ${{ !inputs.skip }}
|
||||||
steps:
|
steps:
|
||||||
|
5
.github/workflows/tests.yml
vendored
@ -6,17 +6,20 @@ on:
|
|||||||
- main
|
- main
|
||||||
- beta
|
- beta
|
||||||
- release
|
- release
|
||||||
|
- 'hotfix*'
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- beta
|
- beta
|
||||||
- release
|
- release
|
||||||
|
- 'hotfix*'
|
||||||
merge_group:
|
merge_group:
|
||||||
types: [checks_requested]
|
types: [checks_requested]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check-path-change-filter:
|
check-path-change-filter:
|
||||||
|
timeout-minutes: 5
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
pull-requests: read
|
pull-requests: read
|
||||||
@ -35,6 +38,8 @@ jobs:
|
|||||||
name: Run Tests
|
name: Run Tests
|
||||||
needs: check-path-change-filter
|
needs: check-path-change-filter
|
||||||
strategy:
|
strategy:
|
||||||
|
# don't stop upon 1 shard failing
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
shard: [1, 2, 3, 4, 5]
|
shard: [1, 2, 3, 4, 5]
|
||||||
uses: ./.github/workflows/test-shard-template.yml
|
uses: ./.github/workflows/test-shard-template.yml
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "pokemon-rogue-battle",
|
"name": "pokemon-rogue-battle",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.11.0",
|
"version": "1.10.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
|
Before Width: | Height: | Size: 207 B After Width: | Height: | Size: 151 B |
Before Width: | Height: | Size: 207 B After Width: | Height: | Size: 151 B |
Before Width: | Height: | Size: 207 B After Width: | Height: | Size: 151 B |
Before Width: | Height: | Size: 207 B After Width: | Height: | Size: 151 B |
@ -1 +1 @@
|
|||||||
Subproject commit 58fa5f9b6e94469017bfbe69bef992ed48ef5343
|
Subproject commit 102cbdcd924e2a7cdc7eab64d1ce79f6ec7604ff
|
@ -1,3 +1,7 @@
|
|||||||
self.addEventListener('install', function () {
|
self.addEventListener('install', function () {
|
||||||
console.log('Service worker installing...');
|
console.log('Service worker installing...');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.addEventListener('activate', (event) => {
|
||||||
|
event.waitUntil(self.clients.claim());
|
||||||
|
})
|
@ -1476,10 +1476,7 @@ export class BattleScene extends SceneBase {
|
|||||||
pokemon.resetBattleAndWaveData();
|
pokemon.resetBattleAndWaveData();
|
||||||
pokemon.resetTera();
|
pokemon.resetTera();
|
||||||
applyAbAttrs("PostBattleInitAbAttr", { pokemon });
|
applyAbAttrs("PostBattleInitAbAttr", { pokemon });
|
||||||
if (
|
if (pokemon.hasSpecies(SpeciesId.TERAPAGOS)) {
|
||||||
pokemon.hasSpecies(SpeciesId.TERAPAGOS) ||
|
|
||||||
(this.gameMode.isClassic && this.currentBattle.waveIndex > 180 && this.currentBattle.waveIndex <= 190)
|
|
||||||
) {
|
|
||||||
this.arena.playerTerasUsed = 0;
|
this.arena.playerTerasUsed = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,12 +53,6 @@ export const defaultStarterSpecies: SpeciesId[] = [
|
|||||||
SpeciesId.QUAXLY,
|
SpeciesId.QUAXLY,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const defaultStarterSpeciesAndEvolutions: SpeciesId[] = defaultStarterSpecies.flatMap(id => [
|
|
||||||
id,
|
|
||||||
(id + 1) as SpeciesId,
|
|
||||||
(id + 2) as SpeciesId,
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary
|
export const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -970,6 +970,8 @@ export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr {
|
|||||||
export interface PostMoveInteractionAbAttrParams extends AugmentMoveInteractionAbAttrParams {
|
export interface PostMoveInteractionAbAttrParams extends AugmentMoveInteractionAbAttrParams {
|
||||||
/** Stores the hit result of the move used in the interaction */
|
/** Stores the hit result of the move used in the interaction */
|
||||||
readonly hitResult: HitResult;
|
readonly hitResult: HitResult;
|
||||||
|
/** The amount of damage dealt in the interaction */
|
||||||
|
readonly damage: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PostDefendAbAttr extends AbAttr {
|
export class PostDefendAbAttr extends AbAttr {
|
||||||
@ -1079,20 +1081,16 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr {
|
|||||||
this.selfTarget = selfTarget;
|
this.selfTarget = selfTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
override canApply({ pokemon, opponent: attacker, move }: PostMoveInteractionAbAttrParams): boolean {
|
override canApply({ pokemon, opponent: attacker, move, damage }: PostMoveInteractionAbAttrParams): boolean {
|
||||||
const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate);
|
const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate);
|
||||||
const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1];
|
return this.condition(pokemon, attacker, move) && pokemon.hp <= hpGateFlat && pokemon.hp + damage > hpGateFlat;
|
||||||
const damageReceived = lastAttackReceived?.damage || 0;
|
|
||||||
return (
|
|
||||||
this.condition(pokemon, attacker, move) && pokemon.hp <= hpGateFlat && pokemon.hp + damageReceived > hpGateFlat
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override apply({ simulated, pokemon, opponent: attacker }: PostMoveInteractionAbAttrParams): void {
|
override apply({ simulated, pokemon, opponent }: PostMoveInteractionAbAttrParams): void {
|
||||||
if (!simulated) {
|
if (!simulated) {
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"StatStageChangePhase",
|
"StatStageChangePhase",
|
||||||
(this.selfTarget ? pokemon : attacker).getBattlerIndex(),
|
(this.selfTarget ? pokemon : opponent).getBattlerIndex(),
|
||||||
true,
|
true,
|
||||||
this.stats,
|
this.stats,
|
||||||
this.stages,
|
this.stages,
|
||||||
@ -1263,17 +1261,17 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
|
|||||||
this.turnCount = turnCount;
|
this.turnCount = turnCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
override canApply({ move, pokemon, opponent: attacker }: PostMoveInteractionAbAttrParams): boolean {
|
override canApply({ move, pokemon, opponent }: PostMoveInteractionAbAttrParams): boolean {
|
||||||
return (
|
return (
|
||||||
move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon }) &&
|
move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: opponent, target: pokemon }) &&
|
||||||
pokemon.randBattleSeedInt(100) < this.chance &&
|
pokemon.randBattleSeedInt(100) < this.chance &&
|
||||||
attacker.canAddTag(this.tagType)
|
opponent.canAddTag(this.tagType)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override apply({ simulated, opponent: attacker, move }: PostMoveInteractionAbAttrParams): void {
|
override apply({ pokemon, simulated, opponent, move }: PostMoveInteractionAbAttrParams): void {
|
||||||
if (!simulated) {
|
if (!simulated) {
|
||||||
attacker.addTag(this.tagType, this.turnCount, move.id, attacker.id);
|
opponent.addTag(this.tagType, this.turnCount, move.id, pokemon.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3014,41 +3012,44 @@ export class PostSummonFormChangeAbAttr extends PostSummonAbAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Attempts to copy a pokemon's ability */
|
/**
|
||||||
|
* Attempts to copy a pokemon's ability
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Hardcodes idiosyncrasies specific to trace, so should not be used for other abilities
|
||||||
|
* that might copy abilities in the future
|
||||||
|
* @sealed
|
||||||
|
*/
|
||||||
export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr {
|
export class PostSummonCopyAbilityAbAttr extends PostSummonAbAttr {
|
||||||
private target: Pokemon;
|
private target: Pokemon;
|
||||||
private targetAbilityName: string;
|
private targetAbilityName: string;
|
||||||
|
|
||||||
override canApply({ pokemon }: AbAttrBaseParams): boolean {
|
override canApply({ pokemon, simulated }: AbAttrBaseParams): boolean {
|
||||||
const targets = pokemon.getOpponents();
|
const targets = pokemon
|
||||||
|
.getOpponents()
|
||||||
|
.filter(t => t.getAbility().isCopiable || t.getAbility().id === AbilityId.WONDER_GUARD);
|
||||||
if (!targets.length) {
|
if (!targets.length) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let target: Pokemon;
|
let target: Pokemon;
|
||||||
if (targets.length > 1) {
|
// simulated call always chooses first target so as to not advance RNG
|
||||||
globalScene.executeWithSeedOffset(() => (target = randSeedItem(targets)), globalScene.currentBattle.waveIndex);
|
if (targets.length > 1 && !simulated) {
|
||||||
|
target = targets[randSeedInt(targets.length)];
|
||||||
} else {
|
} else {
|
||||||
target = targets[0];
|
target = targets[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
this.target = target;
|
||||||
!target!.getAbility().isCopiable &&
|
this.targetAbilityName = allAbilities[target.getAbility().id].name;
|
||||||
// Wonder Guard is normally uncopiable so has the attribute, but Trace specifically can copy it
|
|
||||||
!(pokemon.hasAbility(AbilityId.TRACE) && target!.getAbility().id === AbilityId.WONDER_GUARD)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.target = target!;
|
|
||||||
this.targetAbilityName = allAbilities[target!.getAbility().id].name;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
override apply({ pokemon, simulated }: AbAttrBaseParams): void {
|
override apply({ pokemon, simulated }: AbAttrBaseParams): void {
|
||||||
if (!simulated) {
|
// Protect against this somehow being called before canApply by ensuring target is defined
|
||||||
pokemon.setTempAbility(this.target!.getAbility());
|
if (!simulated && this.target) {
|
||||||
setAbilityRevealed(this.target!);
|
pokemon.setTempAbility(this.target.getAbility());
|
||||||
|
setAbilityRevealed(this.target);
|
||||||
pokemon.updateInfo();
|
pokemon.updateInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -937,7 +937,7 @@ class StealthRockTag extends DamagingTrapTag {
|
|||||||
|
|
||||||
protected override getTriggerMessage(pokemon: Pokemon): string {
|
protected override getTriggerMessage(pokemon: Pokemon): string {
|
||||||
return i18next.t("arenaTag:stealthRockActivateTrap", {
|
return i18next.t("arenaTag:stealthRockActivateTrap", {
|
||||||
pokemonName: getPokemonNameWithAffix(pokemon),
|
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +190,7 @@ export const speciesEggMoves = {
|
|||||||
[SpeciesId.WYNAUT]: [ MoveId.RECOVER, MoveId.SHED_TAIL, MoveId.TAUNT, MoveId.COMEUPPANCE ],
|
[SpeciesId.WYNAUT]: [ MoveId.RECOVER, MoveId.SHED_TAIL, MoveId.TAUNT, MoveId.COMEUPPANCE ],
|
||||||
[SpeciesId.SNORUNT]: [ MoveId.SPARKLY_SWIRL, MoveId.NASTY_PLOT, MoveId.EARTH_POWER, MoveId.BLOOD_MOON ],
|
[SpeciesId.SNORUNT]: [ MoveId.SPARKLY_SWIRL, MoveId.NASTY_PLOT, MoveId.EARTH_POWER, MoveId.BLOOD_MOON ],
|
||||||
[SpeciesId.SPHEAL]: [ MoveId.FLIP_TURN, MoveId.FREEZE_DRY, MoveId.SLACK_OFF, MoveId.STEAM_ERUPTION ],
|
[SpeciesId.SPHEAL]: [ MoveId.FLIP_TURN, MoveId.FREEZE_DRY, MoveId.SLACK_OFF, MoveId.STEAM_ERUPTION ],
|
||||||
[SpeciesId.CLAMPERL]: [ MoveId.SHELL_SIDE_ARM, MoveId.BOUNCY_BUBBLE, MoveId.FREEZE_DRY, MoveId.STEAM_ERUPTION ],
|
[SpeciesId.CLAMPERL]: [ MoveId.SHELL_SIDE_ARM, MoveId.SNIPE_SHOT, MoveId.GIGA_DRAIN, MoveId.BOUNCY_BUBBLE ],
|
||||||
[SpeciesId.RELICANTH]: [ MoveId.DRAGON_DANCE, MoveId.SHORE_UP, MoveId.WAVE_CRASH, MoveId.DIAMOND_STORM ],
|
[SpeciesId.RELICANTH]: [ MoveId.DRAGON_DANCE, MoveId.SHORE_UP, MoveId.WAVE_CRASH, MoveId.DIAMOND_STORM ],
|
||||||
[SpeciesId.LUVDISC]: [ MoveId.BATON_PASS, MoveId.HEART_SWAP, MoveId.GLITZY_GLOW, MoveId.REVIVAL_BLESSING ],
|
[SpeciesId.LUVDISC]: [ MoveId.BATON_PASS, MoveId.HEART_SWAP, MoveId.GLITZY_GLOW, MoveId.REVIVAL_BLESSING ],
|
||||||
[SpeciesId.BAGON]: [ MoveId.HEADLONG_RUSH, MoveId.FIRE_LASH, MoveId.DRAGON_DANCE, MoveId.DRAGON_DARTS ],
|
[SpeciesId.BAGON]: [ MoveId.HEADLONG_RUSH, MoveId.FIRE_LASH, MoveId.DRAGON_DANCE, MoveId.DRAGON_DARTS ],
|
||||||
|
@ -402,7 +402,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = {
|
|||||||
[SpeciesId.SPHEAL]: { 0: AbilityId.UNAWARE },
|
[SpeciesId.SPHEAL]: { 0: AbilityId.UNAWARE },
|
||||||
[SpeciesId.SEALEO]: { 0: AbilityId.UNAWARE },
|
[SpeciesId.SEALEO]: { 0: AbilityId.UNAWARE },
|
||||||
[SpeciesId.WALREIN]: { 0: AbilityId.UNAWARE },
|
[SpeciesId.WALREIN]: { 0: AbilityId.UNAWARE },
|
||||||
[SpeciesId.CLAMPERL]: { 0: AbilityId.DAUNTLESS_SHIELD },
|
[SpeciesId.CLAMPERL]: { 0: AbilityId.OVERCOAT },
|
||||||
[SpeciesId.GOREBYSS]: { 0: AbilityId.ARENA_TRAP },
|
[SpeciesId.GOREBYSS]: { 0: AbilityId.ARENA_TRAP },
|
||||||
[SpeciesId.HUNTAIL]: { 0: AbilityId.ARENA_TRAP },
|
[SpeciesId.HUNTAIL]: { 0: AbilityId.ARENA_TRAP },
|
||||||
[SpeciesId.RELICANTH]: { 0: AbilityId.PRIMORDIAL_SEA },
|
[SpeciesId.RELICANTH]: { 0: AbilityId.PRIMORDIAL_SEA },
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { defaultStarterSpecies } from "#app/constants";
|
||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
import { speciesStarterCosts } from "#balance/starters";
|
import { speciesStarterCosts } from "#balance/starters";
|
||||||
import { allMoves } from "#data/data-lists";
|
import { allMoves } from "#data/data-lists";
|
||||||
@ -76,7 +77,8 @@ export enum EvolutionItem {
|
|||||||
LEADERS_CREST
|
LEADERS_CREST
|
||||||
}
|
}
|
||||||
|
|
||||||
type TyrogueMove = MoveId.LOW_SWEEP | MoveId.MACH_PUNCH | MoveId.RAPID_SPIN;
|
const tyrogueMoves = [MoveId.LOW_SWEEP, MoveId.MACH_PUNCH, MoveId.RAPID_SPIN] as const;
|
||||||
|
type TyrogueMove = typeof tyrogueMoves[number];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pokemon Evolution tuple type consisting of:
|
* Pokemon Evolution tuple type consisting of:
|
||||||
@ -191,7 +193,7 @@ export class SpeciesEvolutionCondition {
|
|||||||
case EvoCondKey.WEATHER:
|
case EvoCondKey.WEATHER:
|
||||||
return cond.weather.includes(globalScene.arena.getWeatherType());
|
return cond.weather.includes(globalScene.arena.getWeatherType());
|
||||||
case EvoCondKey.TYROGUE:
|
case EvoCondKey.TYROGUE:
|
||||||
return pokemon.getMoveset(true).find(m => m.moveId as TyrogueMove)?.moveId === cond.move;
|
return pokemon.getMoveset(true).find(m => (tyrogueMoves as readonly MoveId[]) .includes(m.moveId))?.moveId === cond.move;
|
||||||
case EvoCondKey.NATURE:
|
case EvoCondKey.NATURE:
|
||||||
return cond.nature.includes(pokemon.getNature());
|
return cond.nature.includes(pokemon.getNature());
|
||||||
case EvoCondKey.RANDOM_FORM: {
|
case EvoCondKey.RANDOM_FORM: {
|
||||||
@ -1883,6 +1885,15 @@ export function initPokemonPrevolutions(): void {
|
|||||||
// TODO: This may cause funny business for double starters such as Pichu/Pikachu
|
// TODO: This may cause funny business for double starters such as Pichu/Pikachu
|
||||||
export const pokemonStarters: PokemonPrevolutions = {};
|
export const pokemonStarters: PokemonPrevolutions = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default species and all their evolutions
|
||||||
|
*/
|
||||||
|
export const defaultStarterSpeciesAndEvolutions: SpeciesId[] = defaultStarterSpecies.flatMap(id => {
|
||||||
|
const stage2ids = pokemonEvolutions[id]?.map(e => e.speciesId) ?? [];
|
||||||
|
const stage3ids = stage2ids.flatMap(s2id => pokemonEvolutions[s2id]?.map(e => e.speciesId) ?? []);
|
||||||
|
return [id, ...stage2ids, ...stage3ids];
|
||||||
|
});
|
||||||
|
|
||||||
export function initPokemonStarters(): void {
|
export function initPokemonStarters(): void {
|
||||||
const starterKeys = Object.keys(pokemonPrevolutions);
|
const starterKeys = Object.keys(pokemonPrevolutions);
|
||||||
starterKeys.forEach(pk => {
|
starterKeys.forEach(pk => {
|
||||||
|
@ -1058,8 +1058,7 @@ export class SeedTag extends SerializableBattlerTag {
|
|||||||
// Check which opponent to restore HP to
|
// Check which opponent to restore HP to
|
||||||
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
|
const source = pokemon.getOpponents().find(o => o.getBattlerIndex() === this.sourceIndex);
|
||||||
if (!source) {
|
if (!source) {
|
||||||
console.warn(`Failed to get source Pokemon for SeedTag lapse; id: ${this.sourceId}`);
|
return true;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancelled = new BooleanHolder(false);
|
const cancelled = new BooleanHolder(false);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { FixedBattleConfig } from "#app/battle";
|
import type { FixedBattleConfig } from "#app/battle";
|
||||||
import { getRandomTrainerFunc } from "#app/battle";
|
import { getRandomTrainerFunc } from "#app/battle";
|
||||||
import { defaultStarterSpeciesAndEvolutions } from "#app/constants";
|
import { defaultStarterSpeciesAndEvolutions } from "#balance/pokemon-evolutions";
|
||||||
import { speciesStarterCosts } from "#balance/starters";
|
import { speciesStarterCosts } from "#balance/starters";
|
||||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||||
import { AbilityAttr } from "#enums/ability-attr";
|
import { AbilityAttr } from "#enums/ability-attr";
|
||||||
|
@ -2325,6 +2325,13 @@ export class HealOnAllyAttr extends HealAttr {
|
|||||||
// Don't trigger if not targeting an ally
|
// Don't trigger if not targeting an ally
|
||||||
return target === user.getAlly() && super.canApply(user, target, _move, _args);
|
return target === user.getAlly() && super.canApply(user, target, _move, _args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override apply(user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
|
||||||
|
if (user.isOpponent(target)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return super.apply(user, target, _move, _args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3270,7 +3277,6 @@ export class DelayedAttackAttr extends OverrideMoveEffectAttr {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
user.pushMoveHistory({move: move.id, targets: [target.getBattlerIndex()], result: MoveResult.OTHER, useMode, turn: globalScene.currentBattle.turn})
|
|
||||||
user.pushMoveHistory({move: move.id, targets: [target.getBattlerIndex()], result: MoveResult.OTHER, useMode, turn: globalScene.currentBattle.turn})
|
user.pushMoveHistory({move: move.id, targets: [target.getBattlerIndex()], result: MoveResult.OTHER, useMode, turn: globalScene.currentBattle.turn})
|
||||||
// Queue up an attack on the given slot.
|
// Queue up an attack on the given slot.
|
||||||
globalScene.arena.positionalTagManager.addTag<PositionalTagType.DELAYED_ATTACK>({
|
globalScene.arena.positionalTagManager.addTag<PositionalTagType.DELAYED_ATTACK>({
|
||||||
|
@ -237,7 +237,7 @@ export const BerriesAboundEncounter: MysteryEncounter = MysteryEncounterBuilder.
|
|||||||
const config = globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
|
const config = globalScene.currentBattle.mysteryEncounter!.enemyPartyConfigs[0];
|
||||||
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
|
config.pokemonConfigs![0].tags = [BattlerTagType.MYSTERY_ENCOUNTER_POST_SUMMON];
|
||||||
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
|
config.pokemonConfigs![0].mysteryEncounterBattleEffects = (pokemon: Pokemon) => {
|
||||||
queueEncounterMessage(`${namespace}:option.2.boss_enraged`);
|
queueEncounterMessage(`${namespace}:option.2.bossEnraged`);
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
"StatStageChangePhase",
|
"StatStageChangePhase",
|
||||||
pokemon.getBattlerIndex(),
|
pokemon.getBattlerIndex(),
|
||||||
|
@ -249,7 +249,7 @@ async function tryApplyDigRewardItems() {
|
|||||||
await showEncounterText(
|
await showEncounterText(
|
||||||
i18next.t("battle:rewardGainCount", {
|
i18next.t("battle:rewardGainCount", {
|
||||||
modifierName: leftovers.name,
|
modifierName: leftovers.name,
|
||||||
count: 2,
|
count: 1,
|
||||||
}),
|
}),
|
||||||
null,
|
null,
|
||||||
undefined,
|
undefined,
|
||||||
|
@ -2,6 +2,7 @@ import { globalScene } from "#app/global-scene";
|
|||||||
import { allSpecies, modifierTypes } from "#data/data-lists";
|
import { allSpecies, modifierTypes } from "#data/data-lists";
|
||||||
import { getLevelTotalExp } from "#data/exp";
|
import { getLevelTotalExp } from "#data/exp";
|
||||||
import type { PokemonSpecies } from "#data/pokemon-species";
|
import type { PokemonSpecies } from "#data/pokemon-species";
|
||||||
|
import { AbilityId } from "#enums/ability-id";
|
||||||
import { Challenges } from "#enums/challenges";
|
import { Challenges } from "#enums/challenges";
|
||||||
import { ModifierTier } from "#enums/modifier-tier";
|
import { ModifierTier } from "#enums/modifier-tier";
|
||||||
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
|
||||||
@ -10,8 +11,9 @@ import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
|||||||
import { Nature } from "#enums/nature";
|
import { Nature } from "#enums/nature";
|
||||||
import { PartyMemberStrength } from "#enums/party-member-strength";
|
import { PartyMemberStrength } from "#enums/party-member-strength";
|
||||||
import { PlayerGender } from "#enums/player-gender";
|
import { PlayerGender } from "#enums/player-gender";
|
||||||
import { PokemonType } from "#enums/pokemon-type";
|
import { MAX_POKEMON_TYPE, PokemonType } from "#enums/pokemon-type";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import { TrainerType } from "#enums/trainer-type";
|
import { TrainerType } from "#enums/trainer-type";
|
||||||
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
import type { PlayerPokemon, Pokemon } from "#field/pokemon";
|
||||||
import type { PokemonHeldItemModifier } from "#modifiers/modifier";
|
import type { PokemonHeldItemModifier } from "#modifiers/modifier";
|
||||||
@ -219,6 +221,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
|||||||
await showEncounterText(`${namespace}:option.1.dreamComplete`);
|
await showEncounterText(`${namespace}:option.1.dreamComplete`);
|
||||||
|
|
||||||
await doNewTeamPostProcess(transformations);
|
await doNewTeamPostProcess(transformations);
|
||||||
|
globalScene.phaseManager.unshiftNew("PartyHealPhase", true);
|
||||||
setEncounterRewards({
|
setEncounterRewards({
|
||||||
guaranteedModifierTypeFuncs: [
|
guaranteedModifierTypeFuncs: [
|
||||||
modifierTypes.MEMORY_MUSHROOM,
|
modifierTypes.MEMORY_MUSHROOM,
|
||||||
@ -230,7 +233,7 @@ export const WeirdDreamEncounter: MysteryEncounter = MysteryEncounterBuilder.wit
|
|||||||
],
|
],
|
||||||
fillRemaining: false,
|
fillRemaining: false,
|
||||||
});
|
});
|
||||||
leaveEncounterWithoutBattle(true);
|
leaveEncounterWithoutBattle(false);
|
||||||
})
|
})
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
@ -431,6 +434,8 @@ function getTeamTransformations(): PokemonTransformation[] {
|
|||||||
newAbilityIndex,
|
newAbilityIndex,
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
transformation.newPokemon.teraType = randSeedInt(MAX_POKEMON_TYPE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return pokemonTransformations;
|
return pokemonTransformations;
|
||||||
@ -440,6 +445,8 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
|
|||||||
let atLeastOneNewStarter = false;
|
let atLeastOneNewStarter = false;
|
||||||
for (const transformation of transformations) {
|
for (const transformation of transformations) {
|
||||||
const previousPokemon = transformation.previousPokemon;
|
const previousPokemon = transformation.previousPokemon;
|
||||||
|
const oldHpRatio = previousPokemon.getHpRatio(true);
|
||||||
|
const oldStatus = previousPokemon.status;
|
||||||
const newPokemon = transformation.newPokemon;
|
const newPokemon = transformation.newPokemon;
|
||||||
const speciesRootForm = newPokemon.species.getRootSpeciesId();
|
const speciesRootForm = newPokemon.species.getRootSpeciesId();
|
||||||
|
|
||||||
@ -462,6 +469,19 @@ async function doNewTeamPostProcess(transformations: PokemonTransformation[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
newPokemon.calculateStats();
|
newPokemon.calculateStats();
|
||||||
|
if (oldHpRatio > 0) {
|
||||||
|
newPokemon.hp = Math.ceil(oldHpRatio * newPokemon.getMaxHp());
|
||||||
|
// Assume that the `status` instance can always safely be transferred to the new pokemon
|
||||||
|
// This is the case (as of version 1.10.4)
|
||||||
|
// Safeguard against COMATOSE here
|
||||||
|
if (!newPokemon.hasAbility(AbilityId.COMATOSE, false, true)) {
|
||||||
|
newPokemon.status = oldStatus;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newPokemon.hp = 0;
|
||||||
|
newPokemon.doSetStatus(StatusEffect.FAINT);
|
||||||
|
}
|
||||||
|
|
||||||
await newPokemon.updateInfo();
|
await newPokemon.updateInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,34 @@ export abstract class PhasePriorityQueue {
|
|||||||
public clear(): void {
|
public clear(): void {
|
||||||
this.queue.splice(0, this.queue.length);
|
this.queue.splice(0, this.queue.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to remove one or more Phases from the current queue.
|
||||||
|
* @param phaseFilter - The function to select phases for removal
|
||||||
|
* @param removeCount - The maximum number of phases to remove, or `all` to remove all matching phases;
|
||||||
|
* default `1`
|
||||||
|
* @returns The number of successfully removed phases
|
||||||
|
* @todo Remove this eventually once the patchwork bug this is used for is fixed
|
||||||
|
*/
|
||||||
|
public tryRemovePhase(phaseFilter: (phase: Phase) => boolean, removeCount: number | "all" = 1): number {
|
||||||
|
if (removeCount === "all") {
|
||||||
|
removeCount = this.queue.length;
|
||||||
|
} else if (removeCount < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let numRemoved = 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
const phaseIndex = this.queue.findIndex(phaseFilter);
|
||||||
|
if (phaseIndex === -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
this.queue.splice(phaseIndex, 1);
|
||||||
|
numRemoved++;
|
||||||
|
} while (numRemoved < removeCount && this.queue.length > 0);
|
||||||
|
|
||||||
|
return numRemoved;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -795,7 +795,7 @@ export class PokemonSpecies extends PokemonSpeciesForm implements Localizable {
|
|||||||
return Gender.GENDERLESS;
|
return Gender.GENDERLESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (randSeedFloat() <= this.malePercent) {
|
if (randSeedFloat() * 100 <= this.malePercent) {
|
||||||
return Gender.MALE;
|
return Gender.MALE;
|
||||||
}
|
}
|
||||||
return Gender.FEMALE;
|
return Gender.FEMALE;
|
||||||
|
@ -11,6 +11,7 @@ import type { MoveId } from "#enums/move-id";
|
|||||||
import type { Nature } from "#enums/nature";
|
import type { Nature } from "#enums/nature";
|
||||||
import type { PokemonType } from "#enums/pokemon-type";
|
import type { PokemonType } from "#enums/pokemon-type";
|
||||||
import type { SpeciesId } from "#enums/species-id";
|
import type { SpeciesId } from "#enums/species-id";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
import type { AttackMoveResult } from "#types/attack-move-result";
|
import type { AttackMoveResult } from "#types/attack-move-result";
|
||||||
import type { IllusionData } from "#types/illusion-data";
|
import type { IllusionData } from "#types/illusion-data";
|
||||||
import type { TurnMove } from "#types/turn-move";
|
import type { TurnMove } from "#types/turn-move";
|
||||||
@ -326,6 +327,14 @@ export class PokemonTurnData {
|
|||||||
public switchedInThisTurn = false;
|
public switchedInThisTurn = false;
|
||||||
public failedRunAway = false;
|
public failedRunAway = false;
|
||||||
public joinedRound = false;
|
public joinedRound = false;
|
||||||
|
/** Tracker for a pending status effect
|
||||||
|
*
|
||||||
|
* @remarks
|
||||||
|
* Set whenever {@linkcode Pokemon#trySetStatus} succeeds in order to prevent subsequent status effects
|
||||||
|
* from being applied. Necessary because the status is not actually set until the {@linkcode ObtainStatusEffectPhase} runs,
|
||||||
|
* which may not happen before another status effect is attempted to be applied.
|
||||||
|
*/
|
||||||
|
public pendingStatus: StatusEffect = StatusEffect.NONE;
|
||||||
/**
|
/**
|
||||||
* The amount of times this Pokemon has acted again and used a move in the current turn.
|
* The amount of times this Pokemon has acted again and used a move in the current turn.
|
||||||
* Used to make sure multi-hits occur properly when the user is
|
* Used to make sure multi-hits occur properly when the user is
|
||||||
|
@ -20,3 +20,6 @@ export enum PokemonType {
|
|||||||
FAIRY,
|
FAIRY,
|
||||||
STELLAR
|
STELLAR
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The largest legal value for a {@linkcode PokemonType} (includes Stellar) */
|
||||||
|
export const MAX_POKEMON_TYPE = PokemonType.STELLAR;
|
@ -454,7 +454,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
getNameToRender(useIllusion = true) {
|
getNameToRender(useIllusion = true) {
|
||||||
const illusion = this.summonData.illusion;
|
const illusion = this.summonData.illusion;
|
||||||
const name = useIllusion ? (illusion?.name ?? this.name) : this.name;
|
const name = useIllusion ? (illusion?.name ?? this.name) : this.name;
|
||||||
const nickname: string | undefined = useIllusion ? illusion?.nickname : this.nickname;
|
const nickname: string | undefined = useIllusion ? (illusion?.nickname ?? this.nickname) : this.nickname;
|
||||||
try {
|
try {
|
||||||
if (nickname) {
|
if (nickname) {
|
||||||
return decodeURIComponent(escape(atob(nickname))); // TODO: Remove `atob` and `escape`... eventually...
|
return decodeURIComponent(escape(atob(nickname))); // TODO: Remove `atob` and `escape`... eventually...
|
||||||
@ -1768,7 +1768,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* @returns Whether this Pokemon is currently fused with another species.
|
* @returns Whether this Pokemon is currently fused with another species.
|
||||||
*/
|
*/
|
||||||
isFusion(useIllusion = false): boolean {
|
isFusion(useIllusion = false): boolean {
|
||||||
return useIllusion ? !!this.summonData.illusion?.fusionSpecies : !!this.fusionSpecies;
|
return !!(useIllusion ? (this.summonData.illusion?.fusionSpecies ?? this.fusionSpecies) : this.fusionSpecies);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2221,8 +2221,16 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
return this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().hasAttr(attrType);
|
return this.hasPassive() && (!canApply || this.canApplyAbility(true)) && this.getPassiveAbility().hasAttr(attrType);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAbilityPriorities(): [number, number] {
|
/**
|
||||||
return [this.getAbility().postSummonPriority, this.getPassiveAbility().postSummonPriority];
|
* Return the ability priorities of the pokemon's ability and, if enabled, its passive ability
|
||||||
|
* @returns A tuple containing the ability priorities of the pokemon
|
||||||
|
*/
|
||||||
|
public getAbilityPriorities(): [number] | [activePriority: number, passivePriority: number] {
|
||||||
|
const abilityPriority = this.getAbility().postSummonPriority;
|
||||||
|
if (this.hasPassive()) {
|
||||||
|
return [abilityPriority, this.getPassiveAbility().postSummonPriority];
|
||||||
|
}
|
||||||
|
return [abilityPriority];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3062,14 +3070,17 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (this.level < levelMove[0]) {
|
if (this.level < levelMove[0]) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let weight = levelMove[0];
|
let weight = levelMove[0] + 20;
|
||||||
// Evolution Moves
|
// Evolution Moves
|
||||||
if (weight === EVOLVE_MOVE) {
|
if (levelMove[0] === EVOLVE_MOVE) {
|
||||||
weight = 50;
|
weight = 70;
|
||||||
}
|
}
|
||||||
// Assume level 1 moves with 80+ BP are "move reminder" moves and bump their weight. Trainers use actual relearn moves.
|
// Assume level 1 moves with 80+ BP are "move reminder" moves and bump their weight. Trainers use actual relearn moves.
|
||||||
if ((weight === 1 && allMoves[levelMove[1]].power >= 80) || (weight === RELEARN_MOVE && this.hasTrainer())) {
|
if (
|
||||||
weight = 40;
|
(levelMove[0] === 1 && allMoves[levelMove[1]].power >= 80) ||
|
||||||
|
(levelMove[0] === RELEARN_MOVE && this.hasTrainer())
|
||||||
|
) {
|
||||||
|
weight = 60;
|
||||||
}
|
}
|
||||||
if (!movePool.some(m => m[0] === levelMove[1]) && !allMoves[levelMove[1]].name.endsWith(" (N)")) {
|
if (!movePool.some(m => m[0] === levelMove[1]) && !allMoves[levelMove[1]].name.endsWith(" (N)")) {
|
||||||
movePool.push([levelMove[1], weight]);
|
movePool.push([levelMove[1], weight]);
|
||||||
@ -3099,11 +3110,11 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
if (compatible && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) {
|
if (compatible && !movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) {
|
||||||
if (tmPoolTiers[moveId] === ModifierTier.COMMON && this.level >= 15) {
|
if (tmPoolTiers[moveId] === ModifierTier.COMMON && this.level >= 15) {
|
||||||
movePool.push([moveId, 4]);
|
movePool.push([moveId, 24]);
|
||||||
} else if (tmPoolTiers[moveId] === ModifierTier.GREAT && this.level >= 30) {
|
} else if (tmPoolTiers[moveId] === ModifierTier.GREAT && this.level >= 30) {
|
||||||
movePool.push([moveId, 8]);
|
movePool.push([moveId, 28]);
|
||||||
} else if (tmPoolTiers[moveId] === ModifierTier.ULTRA && this.level >= 50) {
|
} else if (tmPoolTiers[moveId] === ModifierTier.ULTRA && this.level >= 50) {
|
||||||
movePool.push([moveId, 14]);
|
movePool.push([moveId, 34]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3113,7 +3124,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
const moveId = speciesEggMoves[this.species.getRootSpeciesId()][i];
|
const moveId = speciesEggMoves[this.species.getRootSpeciesId()][i];
|
||||||
if (!movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) {
|
if (!movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) {
|
||||||
movePool.push([moveId, 40]);
|
movePool.push([moveId, 60]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const moveId = speciesEggMoves[this.species.getRootSpeciesId()][3];
|
const moveId = speciesEggMoves[this.species.getRootSpeciesId()][3];
|
||||||
@ -3124,13 +3135,13 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
!allMoves[moveId].name.endsWith(" (N)") &&
|
!allMoves[moveId].name.endsWith(" (N)") &&
|
||||||
!this.isBoss()
|
!this.isBoss()
|
||||||
) {
|
) {
|
||||||
movePool.push([moveId, 30]);
|
movePool.push([moveId, 50]);
|
||||||
}
|
}
|
||||||
if (this.fusionSpecies) {
|
if (this.fusionSpecies) {
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
const moveId = speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][i];
|
const moveId = speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][i];
|
||||||
if (!movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) {
|
if (!movePool.some(m => m[0] === moveId) && !allMoves[moveId].name.endsWith(" (N)")) {
|
||||||
movePool.push([moveId, 40]);
|
movePool.push([moveId, 60]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const moveId = speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][3];
|
const moveId = speciesEggMoves[this.fusionSpecies.getRootSpeciesId()][3];
|
||||||
@ -3141,7 +3152,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
!allMoves[moveId].name.endsWith(" (N)") &&
|
!allMoves[moveId].name.endsWith(" (N)") &&
|
||||||
!this.isBoss()
|
!this.isBoss()
|
||||||
) {
|
) {
|
||||||
movePool.push([moveId, 30]);
|
movePool.push([moveId, 50]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3222,6 +3233,18 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
rand -= stabMovePool[index++][1];
|
rand -= stabMovePool[index++][1];
|
||||||
}
|
}
|
||||||
this.moveset.push(new PokemonMove(stabMovePool[index][0]));
|
this.moveset.push(new PokemonMove(stabMovePool[index][0]));
|
||||||
|
} else {
|
||||||
|
// If there are no damaging STAB moves, just force a random damaging move
|
||||||
|
const attackMovePool = baseWeights.filter(m => allMoves[m[0]].category !== MoveCategory.STATUS);
|
||||||
|
if (attackMovePool.length) {
|
||||||
|
const totalWeight = attackMovePool.reduce((v, m) => v + m[1], 0);
|
||||||
|
let rand = randSeedInt(totalWeight);
|
||||||
|
let index = 0;
|
||||||
|
while (rand > attackMovePool[index][1]) {
|
||||||
|
rand -= attackMovePool[index++][1];
|
||||||
|
}
|
||||||
|
this.moveset.push(new PokemonMove(attackMovePool[index][0], 0, 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (baseWeights.length > this.moveset.length && this.moveset.length < 4) {
|
while (baseWeights.length > this.moveset.length && this.moveset.length < 4) {
|
||||||
@ -4790,7 +4813,7 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (effect !== StatusEffect.FAINT) {
|
if (effect !== StatusEffect.FAINT) {
|
||||||
// Status-overriding moves (i.e. Rest) fail if their respective status already exists;
|
// Status-overriding moves (i.e. Rest) fail if their respective status already exists;
|
||||||
// all other moves fail if the target already has _any_ status
|
// all other moves fail if the target already has _any_ status
|
||||||
if (overrideStatus ? this.status?.effect === effect : this.status) {
|
if (overrideStatus ? this.status?.effect === effect : this.status || this.turnData.pendingStatus) {
|
||||||
this.queueStatusImmuneMessage(quiet, overrideStatus ? "overlap" : "other"); // having different status displays generic fail message
|
this.queueStatusImmuneMessage(quiet, overrideStatus ? "overlap" : "other"); // having different status displays generic fail message
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -4942,6 +4965,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
if (overrideStatus) {
|
if (overrideStatus) {
|
||||||
this.resetStatus(false);
|
this.resetStatus(false);
|
||||||
|
} else {
|
||||||
|
this.turnData.pendingStatus = effect;
|
||||||
}
|
}
|
||||||
|
|
||||||
globalScene.phaseManager.unshiftNew(
|
globalScene.phaseManager.unshiftNew(
|
||||||
@ -4961,6 +4986,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* Set this Pokemon's {@linkcode status | non-volatile status condition} to the specified effect.
|
* Set this Pokemon's {@linkcode status | non-volatile status condition} to the specified effect.
|
||||||
* @param effect - The {@linkcode StatusEffect} to set
|
* @param effect - The {@linkcode StatusEffect} to set
|
||||||
* @remarks
|
* @remarks
|
||||||
|
* Clears this pokemon's `pendingStatus` in its {@linkcode Pokemon.turnData | turnData}.
|
||||||
|
*
|
||||||
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
||||||
*/
|
*/
|
||||||
doSetStatus(effect: Exclude<StatusEffect, StatusEffect.SLEEP>): void;
|
doSetStatus(effect: Exclude<StatusEffect, StatusEffect.SLEEP>): void;
|
||||||
@ -4969,6 +4996,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* @param effect - {@linkcode StatusEffect.SLEEP}
|
* @param effect - {@linkcode StatusEffect.SLEEP}
|
||||||
* @param sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4
|
* @param sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4
|
||||||
* @remarks
|
* @remarks
|
||||||
|
* Clears this pokemon's `pendingStatus` in its {@linkcode Pokemon#turnData}.
|
||||||
|
*
|
||||||
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
||||||
*/
|
*/
|
||||||
doSetStatus(effect: StatusEffect.SLEEP, sleepTurnsRemaining?: number): void;
|
doSetStatus(effect: StatusEffect.SLEEP, sleepTurnsRemaining?: number): void;
|
||||||
@ -4978,6 +5007,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* @param sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4
|
* @param sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4
|
||||||
* and is unused for all non-sleep Statuses
|
* and is unused for all non-sleep Statuses
|
||||||
* @remarks
|
* @remarks
|
||||||
|
* Clears this pokemon's `pendingStatus` in its {@linkcode Pokemon#turnData}.
|
||||||
|
*
|
||||||
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
||||||
*/
|
*/
|
||||||
doSetStatus(effect: StatusEffect, sleepTurnsRemaining?: number): void;
|
doSetStatus(effect: StatusEffect, sleepTurnsRemaining?: number): void;
|
||||||
@ -4987,6 +5018,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
* @param sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4
|
* @param sleepTurnsRemaining - The number of turns to inflict sleep for; defaults to a random number between 2 and 4
|
||||||
* and is unused for all non-sleep Statuses
|
* and is unused for all non-sleep Statuses
|
||||||
* @remarks
|
* @remarks
|
||||||
|
* Clears this pokemon's `pendingStatus` in its {@linkcode Pokemon#turnData}.
|
||||||
|
*
|
||||||
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
* ⚠️ This method does **not** check for feasibility; that is the responsibility of the caller.
|
||||||
* @todo Make this and all related fields private and change tests to use a field-based helper or similar
|
* @todo Make this and all related fields private and change tests to use a field-based helper or similar
|
||||||
*/
|
*/
|
||||||
@ -4994,6 +5027,8 @@ export abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
effect: StatusEffect,
|
effect: StatusEffect,
|
||||||
sleepTurnsRemaining = effect !== StatusEffect.SLEEP ? 0 : this.randBattleSeedIntRange(2, 4),
|
sleepTurnsRemaining = effect !== StatusEffect.SLEEP ? 0 : this.randBattleSeedIntRange(2, 4),
|
||||||
): void {
|
): void {
|
||||||
|
// Reset any pending status
|
||||||
|
this.turnData.pendingStatus = StatusEffect.NONE;
|
||||||
switch (effect) {
|
switch (effect) {
|
||||||
case StatusEffect.POISON:
|
case StatusEffect.POISON:
|
||||||
case StatusEffect.TOXIC:
|
case StatusEffect.TOXIC:
|
||||||
|
@ -10,6 +10,10 @@ import InputTextPlugin from "phaser3-rex-plugins/plugins/inputtext-plugin";
|
|||||||
import TransitionImagePackPlugin from "phaser3-rex-plugins/templates/transitionimagepack/transitionimagepack-plugin";
|
import TransitionImagePackPlugin from "phaser3-rex-plugins/templates/transitionimagepack/transitionimagepack-plugin";
|
||||||
import UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin";
|
import UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin";
|
||||||
|
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
document.title += " (Beta)";
|
||||||
|
}
|
||||||
|
|
||||||
// Catch global errors and display them in an alert so users can report the issue.
|
// Catch global errors and display them in an alert so users can report the issue.
|
||||||
window.onerror = (_message, _source, _lineno, _colno, error) => {
|
window.onerror = (_message, _source, _lineno, _colno, error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -355,14 +355,23 @@ export class PhaseManager {
|
|||||||
if (this.phaseQueuePrependSpliceIndex > -1) {
|
if (this.phaseQueuePrependSpliceIndex > -1) {
|
||||||
this.clearPhaseQueueSplice();
|
this.clearPhaseQueueSplice();
|
||||||
}
|
}
|
||||||
if (this.phaseQueuePrepend.length) {
|
this.phaseQueue.unshift(...this.phaseQueuePrepend);
|
||||||
while (this.phaseQueuePrepend.length) {
|
this.phaseQueuePrepend.splice(0);
|
||||||
const poppedPhase = this.phaseQueuePrepend.pop();
|
|
||||||
if (poppedPhase) {
|
const unactivatedConditionalPhases: [() => boolean, Phase][] = [];
|
||||||
this.phaseQueue.unshift(poppedPhase);
|
// Check if there are any conditional phases queued
|
||||||
}
|
for (const [condition, phase] of this.conditionalQueue) {
|
||||||
|
// Evaluate the condition associated with the phase
|
||||||
|
if (condition()) {
|
||||||
|
// If the condition is met, add the phase to the phase queue
|
||||||
|
this.pushPhase(phase);
|
||||||
|
} else {
|
||||||
|
// If the condition is not met, re-add the phase back to the end of the conditional queue
|
||||||
|
unactivatedConditionalPhases.push([condition, phase]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.conditionalQueue = unactivatedConditionalPhases;
|
||||||
|
|
||||||
if (!this.phaseQueue.length) {
|
if (!this.phaseQueue.length) {
|
||||||
this.populatePhaseQueue();
|
this.populatePhaseQueue();
|
||||||
// Clear the conditionalQueue if there are no phases left in the phaseQueue
|
// Clear the conditionalQueue if there are no phases left in the phaseQueue
|
||||||
@ -371,24 +380,6 @@ export class PhaseManager {
|
|||||||
|
|
||||||
this.currentPhase = this.phaseQueue.shift() ?? null;
|
this.currentPhase = this.phaseQueue.shift() ?? null;
|
||||||
|
|
||||||
const unactivatedConditionalPhases: [() => boolean, Phase][] = [];
|
|
||||||
// Check if there are any conditional phases queued
|
|
||||||
while (this.conditionalQueue?.length) {
|
|
||||||
// Retrieve the first conditional phase from the queue
|
|
||||||
const conditionalPhase = this.conditionalQueue.shift();
|
|
||||||
// Evaluate the condition associated with the phase
|
|
||||||
if (conditionalPhase?.[0]()) {
|
|
||||||
// If the condition is met, add the phase to the phase queue
|
|
||||||
this.pushPhase(conditionalPhase[1]);
|
|
||||||
} else if (conditionalPhase) {
|
|
||||||
// If the condition is not met, re-add the phase back to the front of the conditional queue
|
|
||||||
unactivatedConditionalPhases.push(conditionalPhase);
|
|
||||||
} else {
|
|
||||||
console.warn("condition phase is undefined/null!", conditionalPhase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.conditionalQueue.push(...unactivatedConditionalPhases);
|
|
||||||
|
|
||||||
if (this.currentPhase) {
|
if (this.currentPhase) {
|
||||||
console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;");
|
console.log(`%cStart Phase ${this.currentPhase.constructor.name}`, "color:green;");
|
||||||
this.currentPhase.start();
|
this.currentPhase.start();
|
||||||
@ -520,6 +511,25 @@ export class PhaseManager {
|
|||||||
this.dynamicPhaseQueues[type].push(phase);
|
this.dynamicPhaseQueues[type].push(phase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to remove one or more Phases from the given DynamicPhaseQueue, removing the equivalent amount of {@linkcode ActivatePriorityQueuePhase}s from the queue.
|
||||||
|
* @param type - The {@linkcode DynamicPhaseType} to check
|
||||||
|
* @param phaseFilter - The function to select phases for removal
|
||||||
|
* @param removeCount - The maximum number of phases to remove, or `all` to remove all matching phases;
|
||||||
|
* default `1`
|
||||||
|
* @todo Remove this eventually once the patchwork bug this is used for is fixed
|
||||||
|
*/
|
||||||
|
public tryRemoveDynamicPhase(
|
||||||
|
type: DynamicPhaseType,
|
||||||
|
phaseFilter: (phase: Phase) => boolean,
|
||||||
|
removeCount: number | "all" = 1,
|
||||||
|
): void {
|
||||||
|
const numRemoved = this.dynamicPhaseQueues[type].tryRemovePhase(phaseFilter, removeCount);
|
||||||
|
for (let x = 0; x < numRemoved; x++) {
|
||||||
|
this.tryRemovePhase(p => p.is("ActivatePriorityQueuePhase"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unshifts the top phase from the corresponding dynamic queue onto {@linkcode phaseQueue}
|
* Unshifts the top phase from the corresponding dynamic queue onto {@linkcode phaseQueue}
|
||||||
* @param type {@linkcode DynamicPhaseType} The type of dynamic phase to start
|
* @param type {@linkcode DynamicPhaseType} The type of dynamic phase to start
|
||||||
|
@ -400,10 +400,17 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||||
* @param target - {@linkcode Pokemon} the current target of this phase's invoked move
|
* @param target - {@linkcode Pokemon} the current target of this phase's invoked move
|
||||||
* @param hitResult - The {@linkcode HitResult} of the attempted move
|
* @param hitResult - The {@linkcode HitResult} of the attempted move
|
||||||
|
* @param damage - The amount of damage dealt to the target in the interaction
|
||||||
* @param wasCritical - `true` if the move was a critical hit
|
* @param wasCritical - `true` if the move was a critical hit
|
||||||
*/
|
*/
|
||||||
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult, wasCritical = false): void {
|
protected applyOnGetHitAbEffects(
|
||||||
const params = { pokemon: target, opponent: user, move: this.move, hitResult };
|
user: Pokemon,
|
||||||
|
target: Pokemon,
|
||||||
|
hitResult: HitResult,
|
||||||
|
damage: number,
|
||||||
|
wasCritical = false,
|
||||||
|
): void {
|
||||||
|
const params = { pokemon: target, opponent: user, move: this.move, hitResult, damage };
|
||||||
applyAbAttrs("PostDefendAbAttr", params);
|
applyAbAttrs("PostDefendAbAttr", params);
|
||||||
|
|
||||||
if (wasCritical) {
|
if (wasCritical) {
|
||||||
@ -763,12 +770,12 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
|
|
||||||
this.triggerMoveEffects(MoveEffectTrigger.PRE_APPLY, user, target);
|
this.triggerMoveEffects(MoveEffectTrigger.PRE_APPLY, user, target);
|
||||||
|
|
||||||
const [hitResult, wasCritical] = this.applyMove(user, target, effectiveness);
|
const [hitResult, wasCritical, dmg] = this.applyMove(user, target, effectiveness);
|
||||||
|
|
||||||
// Apply effects to the user (always) and the target (if not blocked by substitute).
|
// Apply effects to the user (always) and the target (if not blocked by substitute).
|
||||||
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, true);
|
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, true);
|
||||||
if (!this.move.hitsSubstitute(user, target)) {
|
if (!this.move.hitsSubstitute(user, target)) {
|
||||||
this.applyOnTargetEffects(user, target, hitResult, firstTarget, wasCritical);
|
this.applyOnTargetEffects(user, target, hitResult, firstTarget, dmg, wasCritical);
|
||||||
}
|
}
|
||||||
if (this.lastHit) {
|
if (this.lastHit) {
|
||||||
globalScene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
|
globalScene.triggerPokemonFormChange(user, SpeciesFormChangePostMoveTrigger);
|
||||||
@ -788,9 +795,13 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||||
* @param target - The {@linkcode Pokemon} targeted by the move
|
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||||
* @param effectiveness - The effectiveness of the move against the target
|
* @param effectiveness - The effectiveness of the move against the target
|
||||||
* @returns The {@linkcode HitResult} of the move against the target and a boolean indicating whether the target was crit
|
* @returns The {@linkcode HitResult} of the move against the target, a boolean indicating whether the target was crit, and the amount of damage dealt
|
||||||
*/
|
*/
|
||||||
protected applyMoveDamage(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): [HitResult, boolean] {
|
protected applyMoveDamage(
|
||||||
|
user: Pokemon,
|
||||||
|
target: Pokemon,
|
||||||
|
effectiveness: TypeDamageMultiplier,
|
||||||
|
): [result: HitResult, critical: boolean, damage: number] {
|
||||||
const isCritical = target.getCriticalHitResult(user, this.move);
|
const isCritical = target.getCriticalHitResult(user, this.move);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -821,7 +832,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
const isOneHitKo = result === HitResult.ONE_HIT_KO;
|
const isOneHitKo = result === HitResult.ONE_HIT_KO;
|
||||||
|
|
||||||
if (!dmg) {
|
if (!dmg) {
|
||||||
return [result, false];
|
return [result, false, 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
target.lapseTags(BattlerTagLapseType.HIT);
|
target.lapseTags(BattlerTagLapseType.HIT);
|
||||||
@ -850,7 +861,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (damage <= 0) {
|
if (damage <= 0) {
|
||||||
return [result, isCritical];
|
return [result, isCritical, damage];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.isPlayer()) {
|
if (user.isPlayer()) {
|
||||||
@ -879,7 +890,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
globalScene.applyModifiers(DamageMoneyRewardModifier, true, user, new NumberHolder(damage));
|
globalScene.applyModifiers(DamageMoneyRewardModifier, true, user, new NumberHolder(damage));
|
||||||
}
|
}
|
||||||
|
|
||||||
return [result, isCritical];
|
return [result, isCritical, damage];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -932,12 +943,17 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
* @param user - The {@linkcode Pokemon} using this phase's invoked move
|
||||||
* @param target - The {@linkcode Pokemon} struck by the move
|
* @param target - The {@linkcode Pokemon} struck by the move
|
||||||
* @param effectiveness - The effectiveness of the move against the target
|
* @param effectiveness - The effectiveness of the move against the target
|
||||||
|
* @returns The {@linkcode HitResult} of the move against the target, a boolean indicating whether the target was crit, and the amount of damage dealt
|
||||||
*/
|
*/
|
||||||
protected applyMove(user: Pokemon, target: Pokemon, effectiveness: TypeDamageMultiplier): [HitResult, boolean] {
|
protected applyMove(
|
||||||
|
user: Pokemon,
|
||||||
|
target: Pokemon,
|
||||||
|
effectiveness: TypeDamageMultiplier,
|
||||||
|
): [HitResult, critical: boolean, damage: number] {
|
||||||
const moveCategory = user.getMoveCategory(target, this.move);
|
const moveCategory = user.getMoveCategory(target, this.move);
|
||||||
|
|
||||||
if (moveCategory === MoveCategory.STATUS) {
|
if (moveCategory === MoveCategory.STATUS) {
|
||||||
return [HitResult.STATUS, false];
|
return [HitResult.STATUS, false, 0];
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = this.applyMoveDamage(user, target, effectiveness);
|
const result = this.applyMoveDamage(user, target, effectiveness);
|
||||||
@ -960,6 +976,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
* @param target - The {@linkcode Pokemon} targeted by the move
|
* @param target - The {@linkcode Pokemon} targeted by the move
|
||||||
* @param hitResult - The {@linkcode HitResult} obtained from applying the move
|
* @param hitResult - The {@linkcode HitResult} obtained from applying the move
|
||||||
* @param firstTarget - `true` if the target is the first Pokemon hit by the attack
|
* @param firstTarget - `true` if the target is the first Pokemon hit by the attack
|
||||||
|
* @param damage - The amount of damage dealt to the target in the interaction
|
||||||
* @param wasCritical - `true` if the move was a critical hit
|
* @param wasCritical - `true` if the move was a critical hit
|
||||||
*/
|
*/
|
||||||
protected applyOnTargetEffects(
|
protected applyOnTargetEffects(
|
||||||
@ -967,6 +984,7 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
target: Pokemon,
|
target: Pokemon,
|
||||||
hitResult: HitResult,
|
hitResult: HitResult,
|
||||||
firstTarget: boolean,
|
firstTarget: boolean,
|
||||||
|
damage: number,
|
||||||
wasCritical = false,
|
wasCritical = false,
|
||||||
): void {
|
): void {
|
||||||
/** Does {@linkcode hitResult} indicate that damage was dealt to the target? */
|
/** Does {@linkcode hitResult} indicate that damage was dealt to the target? */
|
||||||
@ -979,8 +997,8 @@ export class MoveEffectPhase extends PokemonPhase {
|
|||||||
|
|
||||||
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false);
|
this.triggerMoveEffects(MoveEffectTrigger.POST_APPLY, user, target, firstTarget, false);
|
||||||
this.applyHeldItemFlinchCheck(user, target, dealsDamage);
|
this.applyHeldItemFlinchCheck(user, target, dealsDamage);
|
||||||
this.applyOnGetHitAbEffects(user, target, hitResult, wasCritical);
|
this.applyOnGetHitAbEffects(user, target, hitResult, damage, wasCritical);
|
||||||
applyAbAttrs("PostAttackAbAttr", { pokemon: user, opponent: target, move: this.move, hitResult });
|
applyAbAttrs("PostAttackAbAttr", { pokemon: user, opponent: target, move: this.move, hitResult, damage: damage });
|
||||||
|
|
||||||
// We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens
|
// We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens
|
||||||
if (!user.isPlayer() && this.move.is("AttackMove")) {
|
if (!user.isPlayer() && this.move.is("AttackMove")) {
|
||||||
|
@ -177,7 +177,7 @@ export class SelectModifierPhase extends BattlePhase {
|
|||||||
this.openModifierMenu(modifierType, cost, modifierSelectCallback);
|
this.openModifierMenu(modifierType, cost, modifierSelectCallback);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.applyModifier(modifierType.newModifier()!);
|
this.applyModifier(modifierType.newModifier()!, cost);
|
||||||
}
|
}
|
||||||
return cost === -1;
|
return cost === -1;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { globalScene } from "#app/global-scene";
|
import { globalScene } from "#app/global-scene";
|
||||||
|
import { DynamicPhaseType } from "#enums/dynamic-phase-type";
|
||||||
import { SwitchType } from "#enums/switch-type";
|
import { SwitchType } from "#enums/switch-type";
|
||||||
import { UiMode } from "#enums/ui-mode";
|
import { UiMode } from "#enums/ui-mode";
|
||||||
import { BattlePhase } from "#phases/battle-phase";
|
import { BattlePhase } from "#phases/battle-phase";
|
||||||
@ -75,8 +76,11 @@ export class SwitchPhase extends BattlePhase {
|
|||||||
if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) {
|
if (slotIndex >= globalScene.currentBattle.getBattlerCount() && slotIndex < 6) {
|
||||||
// Remove any pre-existing PostSummonPhase under the same field index.
|
// Remove any pre-existing PostSummonPhase under the same field index.
|
||||||
// Pre-existing PostSummonPhases may occur when this phase is invoked during a prompt to switch at the start of a wave.
|
// Pre-existing PostSummonPhases may occur when this phase is invoked during a prompt to switch at the start of a wave.
|
||||||
globalScene.phaseManager.tryRemovePhase(
|
// TODO: Separate the animations from `SwitchSummonPhase` and co. into another phase and use that on initial switch - this is a band-aid fix
|
||||||
|
globalScene.phaseManager.tryRemoveDynamicPhase(
|
||||||
|
DynamicPhaseType.POST_SUMMON,
|
||||||
p => p.is("PostSummonPhase") && p.player && p.fieldIndex === this.fieldIndex,
|
p => p.is("PostSummonPhase") && p.player && p.fieldIndex === this.fieldIndex,
|
||||||
|
"all",
|
||||||
);
|
);
|
||||||
const switchType = option === PartyOption.PASS_BATON ? SwitchType.BATON_PASS : this.switchType;
|
const switchType = option === PartyOption.PASS_BATON ? SwitchType.BATON_PASS : this.switchType;
|
||||||
globalScene.phaseManager.unshiftNew("SwitchSummonPhase", switchType, fieldIndex, slotIndex, this.doReturn);
|
globalScene.phaseManager.unshiftNew("SwitchSummonPhase", switchType, fieldIndex, slotIndex, this.doReturn);
|
||||||
|
@ -179,12 +179,11 @@ export class TurnStartPhase extends FieldPhase {
|
|||||||
// https://www.smogon.com/forums/threads/sword-shield-battle-mechanics-research.3655528/page-64#post-9244179
|
// https://www.smogon.com/forums/threads/sword-shield-battle-mechanics-research.3655528/page-64#post-9244179
|
||||||
|
|
||||||
phaseManager.pushNew("WeatherEffectPhase");
|
phaseManager.pushNew("WeatherEffectPhase");
|
||||||
|
phaseManager.pushNew("PositionalTagPhase");
|
||||||
phaseManager.pushNew("BerryPhase");
|
phaseManager.pushNew("BerryPhase");
|
||||||
|
|
||||||
/** Add a new phase to check who should be taking status damage */
|
|
||||||
phaseManager.pushNew("CheckStatusEffectPhase", moveOrder);
|
phaseManager.pushNew("CheckStatusEffectPhase", moveOrder);
|
||||||
|
|
||||||
phaseManager.pushNew("PositionalTagPhase");
|
|
||||||
phaseManager.pushNew("TurnEndPhase");
|
phaseManager.pushNew("TurnEndPhase");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1515,6 +1515,7 @@ export class GameData {
|
|||||||
switch (dataType) {
|
switch (dataType) {
|
||||||
case GameDataType.SYSTEM: {
|
case GameDataType.SYSTEM: {
|
||||||
dataStr = this.convertSystemDataStr(dataStr);
|
dataStr = this.convertSystemDataStr(dataStr);
|
||||||
|
dataStr = dataStr.replace(/"playTime":\d+/, `"playTime":${this.gameStats.playTime + 60}`);
|
||||||
const systemData = this.parseSystemData(dataStr);
|
const systemData = this.parseSystemData(dataStr);
|
||||||
valid = !!systemData.dexData && !!systemData.timestamp;
|
valid = !!systemData.dexData && !!systemData.timestamp;
|
||||||
break;
|
break;
|
||||||
|
@ -13,7 +13,7 @@ import { SettingsGamepadUiHandler } from "#ui/settings-gamepad-ui-handler";
|
|||||||
import { SettingsKeyboardUiHandler } from "#ui/settings-keyboard-ui-handler";
|
import { SettingsKeyboardUiHandler } from "#ui/settings-keyboard-ui-handler";
|
||||||
import { SettingsUiHandler } from "#ui/settings-ui-handler";
|
import { SettingsUiHandler } from "#ui/settings-ui-handler";
|
||||||
import { StarterSelectUiHandler } from "#ui/starter-select-ui-handler";
|
import { StarterSelectUiHandler } from "#ui/starter-select-ui-handler";
|
||||||
import type Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
|
|
||||||
type ActionKeys = Record<Button, () => void>;
|
type ActionKeys = Record<Button, () => void>;
|
||||||
|
|
||||||
@ -224,25 +224,26 @@ export class UiInputs {
|
|||||||
|
|
||||||
buttonSpeedChange(up = true): void {
|
buttonSpeedChange(up = true): void {
|
||||||
const settingGameSpeed = settingIndex(SettingKeys.Game_Speed);
|
const settingGameSpeed = settingIndex(SettingKeys.Game_Speed);
|
||||||
|
const settingOptions = Setting[settingGameSpeed].options;
|
||||||
|
let currentSetting = settingOptions.findIndex(item => item.value === globalScene.gameSpeed.toString());
|
||||||
|
// if current setting is -1, then the current game speed is not a valid option, so default to index 5 (3x)
|
||||||
|
if (currentSetting === -1) {
|
||||||
|
currentSetting = 5;
|
||||||
|
}
|
||||||
|
let direction: number;
|
||||||
if (up && globalScene.gameSpeed < 5) {
|
if (up && globalScene.gameSpeed < 5) {
|
||||||
globalScene.gameData.saveSetting(
|
direction = 1;
|
||||||
SettingKeys.Game_Speed,
|
|
||||||
Setting[settingGameSpeed].options.findIndex(item => item.label === `${globalScene.gameSpeed}x`) + 1,
|
|
||||||
);
|
|
||||||
if (globalScene.ui?.getMode() === UiMode.SETTINGS) {
|
|
||||||
(globalScene.ui.getHandler() as SettingsUiHandler).show([]);
|
|
||||||
}
|
|
||||||
} else if (!up && globalScene.gameSpeed > 1) {
|
} else if (!up && globalScene.gameSpeed > 1) {
|
||||||
|
direction = -1;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
globalScene.gameData.saveSetting(
|
globalScene.gameData.saveSetting(
|
||||||
SettingKeys.Game_Speed,
|
SettingKeys.Game_Speed,
|
||||||
Math.max(
|
Phaser.Math.Clamp(currentSetting + direction, 0, settingOptions.length - 1),
|
||||||
Setting[settingGameSpeed].options.findIndex(item => item.label === `${globalScene.gameSpeed}x`) - 1,
|
|
||||||
0,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
if (globalScene.ui?.getMode() === UiMode.SETTINGS) {
|
if (globalScene.ui?.getMode() === UiMode.SETTINGS) {
|
||||||
(globalScene.ui.getHandler() as SettingsUiHandler).show([]);
|
(globalScene.ui.getHandler() as SettingsUiHandler).show([]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -287,9 +287,6 @@ export abstract class BattleInfo extends Phaser.GameObjects.Container {
|
|||||||
2.5,
|
2.5,
|
||||||
);
|
);
|
||||||
this.splicedIcon.setVisible(pokemon.isFusion(true));
|
this.splicedIcon.setVisible(pokemon.isFusion(true));
|
||||||
if (!this.splicedIcon.visible) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.splicedIcon
|
this.splicedIcon
|
||||||
.on("pointerover", () =>
|
.on("pointerover", () =>
|
||||||
globalScene.ui.showTooltip(
|
globalScene.ui.showTooltip(
|
||||||
@ -323,6 +320,10 @@ export abstract class BattleInfo extends Phaser.GameObjects.Container {
|
|||||||
.setVisible(pokemon.isShiny())
|
.setVisible(pokemon.isShiny())
|
||||||
.setTint(getVariantTint(baseVariant));
|
.setTint(getVariantTint(baseVariant));
|
||||||
|
|
||||||
|
this.shinyIcon
|
||||||
|
.on("pointerover", () => globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor))
|
||||||
|
.on("pointerout", () => globalScene.ui.hideTooltip());
|
||||||
|
|
||||||
if (!this.shinyIcon.visible) {
|
if (!this.shinyIcon.visible) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -335,10 +336,6 @@ export abstract class BattleInfo extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
shinyDescriptor += ")";
|
shinyDescriptor += ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
this.shinyIcon
|
|
||||||
.on("pointerover", () => globalScene.ui.showTooltip("", i18next.t("common:shinyOnHover") + shinyDescriptor))
|
|
||||||
.on("pointerout", () => globalScene.ui.hideTooltip());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
initInfo(pokemon: Pokemon) {
|
initInfo(pokemon: Pokemon) {
|
||||||
|
@ -36,7 +36,7 @@ export class EnemyBattleInfo extends BattleInfo {
|
|||||||
override constructTypeIcons(): void {
|
override constructTypeIcons(): void {
|
||||||
this.type1Icon = globalScene.add.sprite(-15, -15.5, "pbinfo_enemy_type1").setName("icon_type_1").setOrigin(0);
|
this.type1Icon = globalScene.add.sprite(-15, -15.5, "pbinfo_enemy_type1").setName("icon_type_1").setOrigin(0);
|
||||||
this.type2Icon = globalScene.add.sprite(-15, -2.5, "pbinfo_enemy_type2").setName("icon_type_2").setOrigin(0);
|
this.type2Icon = globalScene.add.sprite(-15, -2.5, "pbinfo_enemy_type2").setName("icon_type_2").setOrigin(0);
|
||||||
this.type3Icon = globalScene.add.sprite(0, 15.5, "pbinfo_enemy_type3").setName("icon_type_3").setOrigin(0);
|
this.type3Icon = globalScene.add.sprite(0, -15.5, "pbinfo_enemy_type").setName("icon_type_3").setOrigin(0);
|
||||||
this.add([this.type1Icon, this.type2Icon, this.type3Icon]);
|
this.add([this.type1Icon, this.type2Icon, this.type3Icon]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ export class PlayerBattleInfo extends BattleInfo {
|
|||||||
override constructTypeIcons(): void {
|
override constructTypeIcons(): void {
|
||||||
this.type1Icon = globalScene.add.sprite(-139, -17, "pbinfo_player_type1").setName("icon_type_1").setOrigin(0);
|
this.type1Icon = globalScene.add.sprite(-139, -17, "pbinfo_player_type1").setName("icon_type_1").setOrigin(0);
|
||||||
this.type2Icon = globalScene.add.sprite(-139, -1, "pbinfo_player_type2").setName("icon_type_2").setOrigin(0);
|
this.type2Icon = globalScene.add.sprite(-139, -1, "pbinfo_player_type2").setName("icon_type_2").setOrigin(0);
|
||||||
this.type3Icon = globalScene.add.sprite(-154, -17, "pbinfo_player_type3").setName("icon_type_3").setOrigin(0);
|
this.type3Icon = globalScene.add.sprite(-154, -17, "pbinfo_player_type").setName("icon_type_3").setOrigin(0);
|
||||||
this.add([this.type1Icon, this.type2Icon, this.type3Icon]);
|
this.add([this.type1Icon, this.type2Icon, this.type3Icon]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -563,7 +563,7 @@ export class PartyUiHandler extends MessageUiHandler {
|
|||||||
const ui = this.getUi();
|
const ui = this.getUi();
|
||||||
const option = this.options[this.optionsCursor];
|
const option = this.options[this.optionsCursor];
|
||||||
|
|
||||||
if (option === PartyOption.TRANSFER) {
|
if (this.transferMode && option === PartyOption.TRANSFER) {
|
||||||
return this.processTransferOption();
|
return this.processTransferOption();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1021,7 +1021,8 @@ export class PartyUiHandler extends MessageUiHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Toggle item transfer mode to discard items or vice versa
|
// Toggle item transfer mode to discard items or vice versa
|
||||||
if (this.cursor === 7) {
|
// Prevent changing mode, when currently transfering an item
|
||||||
|
if (this.cursor === 7 && !this.transferMode) {
|
||||||
switch (this.partyUiMode) {
|
switch (this.partyUiMode) {
|
||||||
case PartyUiMode.DISCARD:
|
case PartyUiMode.DISCARD:
|
||||||
this.partyUiMode = PartyUiMode.MODIFIER_TRANSFER;
|
this.partyUiMode = PartyUiMode.MODIFIER_TRANSFER;
|
||||||
@ -1609,7 +1610,7 @@ export class PartyUiHandler extends MessageUiHandler {
|
|||||||
const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM];
|
const modifier = formChangeItemModifiers[option - PartyOption.FORM_CHANGE_ITEM];
|
||||||
optionName = `${modifier.active ? i18next.t("partyUiHandler:deactivate") : i18next.t("partyUiHandler:activate")} ${modifier.type.name}`;
|
optionName = `${modifier.active ? i18next.t("partyUiHandler:deactivate") : i18next.t("partyUiHandler:activate")} ${modifier.type.name}`;
|
||||||
} else if (option === PartyOption.UNPAUSE_EVOLUTION) {
|
} else if (option === PartyOption.UNPAUSE_EVOLUTION) {
|
||||||
optionName = `${pokemon.pauseEvolutions ? i18next.t("partyUiHandler:unpausedEvolution") : i18next.t("partyUiHandler:pauseEvolution")}`;
|
optionName = `${pokemon.pauseEvolutions ? i18next.t("partyUiHandler:unpauseEvolution") : i18next.t("partyUiHandler:pauseEvolution")}`;
|
||||||
} else {
|
} else {
|
||||||
if (this.localizedOptions.includes(option)) {
|
if (this.localizedOptions.includes(option)) {
|
||||||
optionName = i18next.t(`partyUiHandler:${toCamelCase(PartyOption[option])}`);
|
optionName = i18next.t(`partyUiHandler:${toCamelCase(PartyOption[option])}`);
|
||||||
@ -2040,12 +2041,13 @@ class PartySlot extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
if (this.pokemon.isShiny()) {
|
if (this.pokemon.isShiny()) {
|
||||||
const doubleShiny = this.pokemon.isDoubleShiny(false);
|
const doubleShiny = this.pokemon.isDoubleShiny(false);
|
||||||
|
const largeIconTint = doubleShiny ? this.pokemon.getBaseVariant() : this.pokemon.getVariant();
|
||||||
|
|
||||||
const shinyStar = globalScene.add
|
const shinyStar = globalScene.add
|
||||||
.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`)
|
.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`)
|
||||||
.setOrigin(0)
|
.setOrigin(0)
|
||||||
.setPositionRelative(this.slotName, shinyIconToNameOffset.x, shinyIconToNameOffset.y)
|
.setPositionRelative(this.slotName, shinyIconToNameOffset.x, shinyIconToNameOffset.y)
|
||||||
.setTint(getVariantTint(this.pokemon.getBaseVariant()));
|
.setTint(getVariantTint(largeIconTint));
|
||||||
slotInfoContainer.add(shinyStar);
|
slotInfoContainer.add(shinyStar);
|
||||||
|
|
||||||
if (doubleShiny) {
|
if (doubleShiny) {
|
||||||
|
@ -181,7 +181,7 @@ export class SaveSlotSelectUiHandler extends MessageUiHandler {
|
|||||||
ui.setOverlayMode(
|
ui.setOverlayMode(
|
||||||
UiMode.CONFIRM,
|
UiMode.CONFIRM,
|
||||||
() => {
|
() => {
|
||||||
globalScene.gameData.tryClearSession(cursor).then(response => {
|
globalScene.gameData.deleteSession(cursor).then(response => {
|
||||||
if (response[0] === false) {
|
if (response[0] === false) {
|
||||||
globalScene.reset(true);
|
globalScene.reset(true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -72,7 +72,7 @@ import {
|
|||||||
rgbHexToRgba,
|
rgbHexToRgba,
|
||||||
} from "#utils/common";
|
} from "#utils/common";
|
||||||
import type { StarterPreferences } from "#utils/data";
|
import type { StarterPreferences } from "#utils/data";
|
||||||
import { loadStarterPreferences, saveStarterPreferences } from "#utils/data";
|
import { deepCopy, loadStarterPreferences, saveStarterPreferences } from "#utils/data";
|
||||||
import { getPokemonSpeciesForm, getPokerusStarters } from "#utils/pokemon-utils";
|
import { getPokemonSpeciesForm, getPokerusStarters } from "#utils/pokemon-utils";
|
||||||
import { toCamelCase, toTitleCase } from "#utils/strings";
|
import { toCamelCase, toTitleCase } from "#utils/strings";
|
||||||
import { argbFromRgba } from "@material/material-color-utilities";
|
import { argbFromRgba } from "@material/material-color-utilities";
|
||||||
@ -1148,7 +1148,8 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
this.starterSelectContainer.setVisible(true);
|
this.starterSelectContainer.setVisible(true);
|
||||||
|
|
||||||
this.starterPreferences = loadStarterPreferences();
|
this.starterPreferences = loadStarterPreferences();
|
||||||
this.originalStarterPreferences = loadStarterPreferences();
|
// Deep copy the JSON (avoid re-loading from disk)
|
||||||
|
this.originalStarterPreferences = deepCopy(this.starterPreferences);
|
||||||
|
|
||||||
this.allSpecies.forEach((species, s) => {
|
this.allSpecies.forEach((species, s) => {
|
||||||
const icon = this.starterContainers[s].icon;
|
const icon = this.starterContainers[s].icon;
|
||||||
@ -1212,6 +1213,8 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
preferences: StarterPreferences,
|
preferences: StarterPreferences,
|
||||||
ignoreChallenge = false,
|
ignoreChallenge = false,
|
||||||
): StarterAttributes {
|
): StarterAttributes {
|
||||||
|
// if preferences for the species is undefined, set it to an empty object
|
||||||
|
preferences[species.speciesId] ??= {};
|
||||||
const starterAttributes = preferences[species.speciesId];
|
const starterAttributes = preferences[species.speciesId];
|
||||||
const { dexEntry, starterDataEntry: starterData } = this.getSpeciesData(species.speciesId, !ignoreChallenge);
|
const { dexEntry, starterDataEntry: starterData } = this.getSpeciesData(species.speciesId, !ignoreChallenge);
|
||||||
|
|
||||||
@ -1828,9 +1831,15 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
// The persistent starter data to apply e.g. candy upgrades
|
// The persistent starter data to apply e.g. candy upgrades
|
||||||
const persistentStarterData = globalScene.gameData.starterData[this.lastSpecies.speciesId];
|
const persistentStarterData = globalScene.gameData.starterData[this.lastSpecies.speciesId];
|
||||||
// The sanitized starter preferences
|
// The sanitized starter preferences
|
||||||
let starterAttributes = this.starterPreferences[this.lastSpecies.speciesId];
|
if (this.starterPreferences[this.lastSpecies.speciesId] === undefined) {
|
||||||
// The original starter preferences
|
this.starterPreferences[this.lastSpecies.speciesId] = {};
|
||||||
const originalStarterAttributes = this.originalStarterPreferences[this.lastSpecies.speciesId];
|
}
|
||||||
|
if (this.originalStarterPreferences[this.lastSpecies.speciesId] === undefined) {
|
||||||
|
this.originalStarterPreferences[this.lastSpecies.speciesId] = {};
|
||||||
|
}
|
||||||
|
// Bangs are safe here due to the above check
|
||||||
|
const starterAttributes = this.starterPreferences[this.lastSpecies.speciesId]!;
|
||||||
|
const originalStarterAttributes = this.originalStarterPreferences[this.lastSpecies.speciesId]!;
|
||||||
|
|
||||||
// this gets the correct pokemon cursor depending on whether you're in the starter screen or the party icons
|
// this gets the correct pokemon cursor depending on whether you're in the starter screen or the party icons
|
||||||
if (!this.starterIconsCursorObj.visible) {
|
if (!this.starterIconsCursorObj.visible) {
|
||||||
@ -2050,10 +2059,6 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
const option: OptionSelectItem = {
|
const option: OptionSelectItem = {
|
||||||
label: getNatureName(n, true, true, true, globalScene.uiTheme),
|
label: getNatureName(n, true, true, true, globalScene.uiTheme),
|
||||||
handler: () => {
|
handler: () => {
|
||||||
// update default nature in starter save data
|
|
||||||
if (!starterAttributes) {
|
|
||||||
starterAttributes = this.starterPreferences[this.lastSpecies.speciesId] = {};
|
|
||||||
}
|
|
||||||
starterAttributes.nature = n;
|
starterAttributes.nature = n;
|
||||||
originalStarterAttributes.nature = starterAttributes.nature;
|
originalStarterAttributes.nature = starterAttributes.nature;
|
||||||
this.clearText();
|
this.clearText();
|
||||||
@ -2095,28 +2100,22 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
const passiveAttr = starterData.passiveAttr;
|
const passiveAttr = starterData.passiveAttr;
|
||||||
if (passiveAttr & PassiveAttr.UNLOCKED) {
|
if (passiveAttr & PassiveAttr.UNLOCKED) {
|
||||||
// this is for enabling and disabling the passive
|
// this is for enabling and disabling the passive
|
||||||
if (!(passiveAttr & PassiveAttr.ENABLED)) {
|
const label = i18next.t(
|
||||||
|
passiveAttr & PassiveAttr.ENABLED
|
||||||
|
? "starterSelectUiHandler:disablePassive"
|
||||||
|
: "starterSelectUiHandler:enablePassive",
|
||||||
|
);
|
||||||
options.push({
|
options.push({
|
||||||
label: i18next.t("starterSelectUiHandler:enablePassive"),
|
label,
|
||||||
handler: () => {
|
|
||||||
starterData.passiveAttr |= PassiveAttr.ENABLED;
|
|
||||||
ui.setMode(UiMode.STARTER_SELECT);
|
|
||||||
this.setSpeciesDetails(this.lastSpecies);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
options.push({
|
|
||||||
label: i18next.t("starterSelectUiHandler:disablePassive"),
|
|
||||||
handler: () => {
|
handler: () => {
|
||||||
starterData.passiveAttr ^= PassiveAttr.ENABLED;
|
starterData.passiveAttr ^= PassiveAttr.ENABLED;
|
||||||
|
persistentStarterData.passiveAttr ^= PassiveAttr.ENABLED;
|
||||||
ui.setMode(UiMode.STARTER_SELECT);
|
ui.setMode(UiMode.STARTER_SELECT);
|
||||||
this.setSpeciesDetails(this.lastSpecies);
|
this.setSpeciesDetails(this.lastSpecies);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// if container.favorite is false, show the favorite option
|
// if container.favorite is false, show the favorite option
|
||||||
const isFavorite = starterAttributes?.favorite ?? false;
|
const isFavorite = starterAttributes?.favorite ?? false;
|
||||||
if (!isFavorite) {
|
if (!isFavorite) {
|
||||||
@ -3414,8 +3413,9 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
if (species) {
|
if (species) {
|
||||||
const defaultDexAttr = this.getCurrentDexProps(species.speciesId);
|
const defaultDexAttr = this.getCurrentDexProps(species.speciesId);
|
||||||
const defaultProps = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
|
const defaultProps = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
|
||||||
|
// Bang is correct due to the `?` before variant
|
||||||
const variant = this.starterPreferences[species.speciesId]?.variant
|
const variant = this.starterPreferences[species.speciesId]?.variant
|
||||||
? (this.starterPreferences[species.speciesId].variant as Variant)
|
? (this.starterPreferences[species.speciesId]!.variant as Variant)
|
||||||
: defaultProps.variant;
|
: defaultProps.variant;
|
||||||
const tint = getVariantTint(variant);
|
const tint = getVariantTint(variant);
|
||||||
this.pokemonShinyIcon.setFrame(getVariantIcon(variant)).setTint(tint);
|
this.pokemonShinyIcon.setFrame(getVariantIcon(variant)).setTint(tint);
|
||||||
@ -3640,7 +3640,9 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
|
|
||||||
if (starterIndex > -1) {
|
if (starterIndex > -1) {
|
||||||
props = globalScene.gameData.getSpeciesDexAttrProps(species, this.starterAttr[starterIndex]);
|
props = globalScene.gameData.getSpeciesDexAttrProps(species, this.starterAttr[starterIndex]);
|
||||||
this.setSpeciesDetails(species, {
|
this.setSpeciesDetails(
|
||||||
|
species,
|
||||||
|
{
|
||||||
shiny: props.shiny,
|
shiny: props.shiny,
|
||||||
formIndex: props.formIndex,
|
formIndex: props.formIndex,
|
||||||
female: props.female,
|
female: props.female,
|
||||||
@ -3648,7 +3650,9 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
abilityIndex: this.starterAbilityIndexes[starterIndex],
|
abilityIndex: this.starterAbilityIndexes[starterIndex],
|
||||||
natureIndex: this.starterNatures[starterIndex],
|
natureIndex: this.starterNatures[starterIndex],
|
||||||
teraType: this.starterTeras[starterIndex],
|
teraType: this.starterTeras[starterIndex],
|
||||||
});
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
const defaultAbilityIndex =
|
const defaultAbilityIndex =
|
||||||
starterAttributes?.ability ?? globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species);
|
starterAttributes?.ability ?? globalScene.gameData.getStarterSpeciesDefaultAbilityIndex(species);
|
||||||
@ -3665,7 +3669,9 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
props.formIndex = starterAttributes?.form ?? props.formIndex;
|
props.formIndex = starterAttributes?.form ?? props.formIndex;
|
||||||
props.female = starterAttributes?.female ?? props.female;
|
props.female = starterAttributes?.female ?? props.female;
|
||||||
|
|
||||||
this.setSpeciesDetails(species, {
|
this.setSpeciesDetails(
|
||||||
|
species,
|
||||||
|
{
|
||||||
shiny: props.shiny,
|
shiny: props.shiny,
|
||||||
formIndex: props.formIndex,
|
formIndex: props.formIndex,
|
||||||
female: props.female,
|
female: props.female,
|
||||||
@ -3673,7 +3679,9 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
abilityIndex: defaultAbilityIndex,
|
abilityIndex: defaultAbilityIndex,
|
||||||
natureIndex: defaultNature,
|
natureIndex: defaultNature,
|
||||||
teraType: starterAttributes?.tera,
|
teraType: starterAttributes?.tera,
|
||||||
});
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNullOrUndefined(props.formIndex)) {
|
if (!isNullOrUndefined(props.formIndex)) {
|
||||||
@ -3710,7 +3718,9 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
const defaultNature = globalScene.gameData.getSpeciesDefaultNature(species);
|
const defaultNature = globalScene.gameData.getSpeciesDefaultNature(species);
|
||||||
const props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
|
const props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
|
||||||
|
|
||||||
this.setSpeciesDetails(species, {
|
this.setSpeciesDetails(
|
||||||
|
species,
|
||||||
|
{
|
||||||
shiny: props.shiny,
|
shiny: props.shiny,
|
||||||
formIndex: props.formIndex,
|
formIndex: props.formIndex,
|
||||||
female: props.female,
|
female: props.female,
|
||||||
@ -3718,7 +3728,9 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
abilityIndex: defaultAbilityIndex,
|
abilityIndex: defaultAbilityIndex,
|
||||||
natureIndex: defaultNature,
|
natureIndex: defaultNature,
|
||||||
forSeen: true,
|
forSeen: true,
|
||||||
});
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
this.pokemonSprite.setTint(0x808080);
|
this.pokemonSprite.setTint(0x808080);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -3740,7 +3752,9 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
this.pokemonFormText.setVisible(false);
|
this.pokemonFormText.setVisible(false);
|
||||||
this.teraIcon.setVisible(false);
|
this.teraIcon.setVisible(false);
|
||||||
|
|
||||||
this.setSpeciesDetails(species!, {
|
this.setSpeciesDetails(
|
||||||
|
species!,
|
||||||
|
{
|
||||||
// TODO: is this bang correct?
|
// TODO: is this bang correct?
|
||||||
shiny: false,
|
shiny: false,
|
||||||
formIndex: 0,
|
formIndex: 0,
|
||||||
@ -3748,7 +3762,9 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
variant: 0,
|
variant: 0,
|
||||||
abilityIndex: 0,
|
abilityIndex: 0,
|
||||||
natureIndex: 0,
|
natureIndex: 0,
|
||||||
});
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
this.pokemonSprite.clearTint();
|
this.pokemonSprite.clearTint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3770,7 +3786,7 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
return { dexEntry: { ...copiedDexEntry }, starterDataEntry: { ...copiedStarterDataEntry } };
|
return { dexEntry: { ...copiedDexEntry }, starterDataEntry: { ...copiedStarterDataEntry } };
|
||||||
}
|
}
|
||||||
|
|
||||||
setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}): void {
|
setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}, save = true): void {
|
||||||
let { shiny, formIndex, female, variant, abilityIndex, natureIndex, teraType } = options;
|
let { shiny, formIndex, female, variant, abilityIndex, natureIndex, teraType } = options;
|
||||||
const forSeen: boolean = options.forSeen ?? false;
|
const forSeen: boolean = options.forSeen ?? false;
|
||||||
const oldProps = species ? globalScene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor) : null;
|
const oldProps = species ? globalScene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor) : null;
|
||||||
@ -4182,8 +4198,10 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
|
|
||||||
this.updateInstructions();
|
this.updateInstructions();
|
||||||
|
|
||||||
|
if (save) {
|
||||||
saveStarterPreferences(this.originalStarterPreferences);
|
saveStarterPreferences(this.originalStarterPreferences);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setTypeIcons(type1: PokemonType | null, type2: PokemonType | null): void {
|
setTypeIcons(type1: PokemonType | null, type2: PokemonType | null): void {
|
||||||
if (type1 !== null) {
|
if (type1 !== null) {
|
||||||
@ -4618,6 +4636,8 @@ export class StarterSelectUiHandler extends MessageUiHandler {
|
|||||||
clear(): void {
|
clear(): void {
|
||||||
super.clear();
|
super.clear();
|
||||||
|
|
||||||
|
saveStarterPreferences(this.originalStarterPreferences);
|
||||||
|
|
||||||
this.clearStarterPreferences();
|
this.clearStarterPreferences();
|
||||||
this.cursor = -1;
|
this.cursor = -1;
|
||||||
this.hideInstructions();
|
this.hideInstructions();
|
||||||
|
@ -430,20 +430,21 @@ export class SummaryUiHandler extends UiHandler {
|
|||||||
this.friendshipShadow.setCrop(0, 0, 16, 16 - 16 * ((this.pokemon?.friendship || 0) / 255));
|
this.friendshipShadow.setCrop(0, 0, 16, 16 - 16 * ((this.pokemon?.friendship || 0) / 255));
|
||||||
|
|
||||||
const doubleShiny = this.pokemon.isDoubleShiny(false);
|
const doubleShiny = this.pokemon.isDoubleShiny(false);
|
||||||
const baseVariant = this.pokemon.getBaseVariant(doubleShiny);
|
const bigIconVariant = doubleShiny ? this.pokemon.getBaseVariant(doubleShiny) : this.pokemon.getVariant();
|
||||||
|
|
||||||
this.shinyIcon.setPositionRelative(
|
this.shinyIcon.setPositionRelative(
|
||||||
this.nameText,
|
this.nameText,
|
||||||
this.nameText.displayWidth + (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0) + 1,
|
this.nameText.displayWidth + (this.splicedIcon.visible ? this.splicedIcon.displayWidth + 1 : 0) + 1,
|
||||||
3,
|
3,
|
||||||
);
|
);
|
||||||
this.shinyIcon.setTexture(`shiny_star${doubleShiny ? "_1" : ""}`);
|
this.shinyIcon
|
||||||
this.shinyIcon.setVisible(this.pokemon.isShiny(false));
|
.setTexture(`shiny_star${doubleShiny ? "_1" : ""}`)
|
||||||
this.shinyIcon.setTint(getVariantTint(baseVariant));
|
.setVisible(this.pokemon.isShiny(false))
|
||||||
|
.setTint(getVariantTint(bigIconVariant));
|
||||||
if (this.shinyIcon.visible) {
|
if (this.shinyIcon.visible) {
|
||||||
let shinyDescriptor = "";
|
let shinyDescriptor = "";
|
||||||
if (doubleShiny || baseVariant) {
|
if (doubleShiny || bigIconVariant) {
|
||||||
shinyDescriptor = " (" + getShinyDescriptor(baseVariant);
|
shinyDescriptor = " (" + getShinyDescriptor(bigIconVariant);
|
||||||
if (doubleShiny) {
|
if (doubleShiny) {
|
||||||
shinyDescriptor += "/" + getShinyDescriptor(this.pokemon.fusionVariant);
|
shinyDescriptor += "/" + getShinyDescriptor(this.pokemon.fusionVariant);
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,8 @@ export class TitleUiHandler extends OptionSelectUiHandler {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.appVersionText.setText("v" + version);
|
const betaText = import.meta.env.DEV ? " (Beta)" : "";
|
||||||
|
this.appVersionText.setText("v" + version + betaText);
|
||||||
|
|
||||||
const ui = this.getUi();
|
const ui = this.getUi();
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ import { AES, enc } from "crypto-js";
|
|||||||
* @param values - The object to be deep copied.
|
* @param values - The object to be deep copied.
|
||||||
* @returns A new object that is a deep copy of the input.
|
* @returns A new object that is a deep copy of the input.
|
||||||
*/
|
*/
|
||||||
export function deepCopy(values: object): object {
|
export function deepCopy<T extends object>(values: T): T {
|
||||||
// Convert the object to a JSON string and parse it back to an object to perform a deep copy
|
// Convert the object to a JSON string and parse it back to an object to perform a deep copy
|
||||||
return JSON.parse(JSON.stringify(values));
|
return JSON.parse(JSON.stringify(values));
|
||||||
}
|
}
|
||||||
@ -58,13 +58,28 @@ export function decrypt(data: string, bypassLogin: boolean): string {
|
|||||||
return AES.decrypt(data, saveKey).toString(enc.Utf8);
|
return AES.decrypt(data, saveKey).toString(enc.Utf8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an object has no properties of its own (its shape is `{}`). An empty array is considered a bare object.
|
||||||
|
* @param obj - Object to check
|
||||||
|
* @returns - Whether the object is bare
|
||||||
|
*/
|
||||||
|
export function isBareObject(obj: any): boolean {
|
||||||
|
if (typeof obj !== "object") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const _ in obj) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// the latest data saved/loaded for the Starter Preferences. Required to reduce read/writes. Initialize as "{}", since this is the default value and no data needs to be stored if present.
|
// the latest data saved/loaded for the Starter Preferences. Required to reduce read/writes. Initialize as "{}", since this is the default value and no data needs to be stored if present.
|
||||||
// if they ever add private static variables, move this into StarterPrefs
|
// if they ever add private static variables, move this into StarterPrefs
|
||||||
const StarterPrefers_DEFAULT: string = "{}";
|
const StarterPrefers_DEFAULT: string = "{}";
|
||||||
let StarterPrefers_private_latest: string = StarterPrefers_DEFAULT;
|
let StarterPrefers_private_latest: string = StarterPrefers_DEFAULT;
|
||||||
|
|
||||||
export interface StarterPreferences {
|
export interface StarterPreferences {
|
||||||
[key: number]: StarterAttributes;
|
[key: number]: StarterAttributes | undefined;
|
||||||
}
|
}
|
||||||
// called on starter selection show once
|
// called on starter selection show once
|
||||||
|
|
||||||
@ -74,11 +89,17 @@ export function loadStarterPreferences(): StarterPreferences {
|
|||||||
localStorage.getItem(`starterPrefs_${loggedInUser?.username}`) || StarterPrefers_DEFAULT),
|
localStorage.getItem(`starterPrefs_${loggedInUser?.username}`) || StarterPrefers_DEFAULT),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// called on starter selection clear, always
|
|
||||||
|
|
||||||
export function saveStarterPreferences(prefs: StarterPreferences): void {
|
export function saveStarterPreferences(prefs: StarterPreferences): void {
|
||||||
const pStr: string = JSON.stringify(prefs);
|
// Fastest way to check if an object has any properties (does no allocation)
|
||||||
|
if (isBareObject(prefs)) {
|
||||||
|
console.warn("Refusing to save empty starter preferences");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// no reason to store `{}` (for starters not customized)
|
||||||
|
const pStr: string = JSON.stringify(prefs, (_, value) => (isBareObject(value) ? undefined : value));
|
||||||
if (pStr !== StarterPrefers_private_latest) {
|
if (pStr !== StarterPrefers_private_latest) {
|
||||||
|
console.log("%cSaving starter preferences", "color: blue");
|
||||||
// something changed, store the update
|
// something changed, store the update
|
||||||
localStorage.setItem(`starterPrefs_${loggedInUser?.username}`, pStr);
|
localStorage.setItem(`starterPrefs_${loggedInUser?.username}`, pStr);
|
||||||
// update the latest prefs
|
// update the latest prefs
|
||||||
|
@ -35,13 +35,43 @@ describe("Abilities - Intimidate", () => {
|
|||||||
it("should lower all opponents' ATK by 1 stage on entry and switch", async () => {
|
it("should lower all opponents' ATK by 1 stage on entry and switch", async () => {
|
||||||
await game.classicMode.startBattle([SpeciesId.MIGHTYENA, SpeciesId.POOCHYENA]);
|
await game.classicMode.startBattle([SpeciesId.MIGHTYENA, SpeciesId.POOCHYENA]);
|
||||||
|
|
||||||
|
const [mightyena, poochyena] = game.scene.getPlayerParty();
|
||||||
|
|
||||||
const enemy = game.field.getEnemyPokemon();
|
const enemy = game.field.getEnemyPokemon();
|
||||||
expect(enemy.getStatStage(Stat.ATK)).toBe(-1);
|
expect(enemy.getStatStage(Stat.ATK)).toBe(-1);
|
||||||
|
expect(mightyena).toHaveAbilityApplied(AbilityId.INTIMIDATE);
|
||||||
|
|
||||||
game.doSwitchPokemon(1);
|
game.doSwitchPokemon(1);
|
||||||
await game.toNextTurn();
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
expect(poochyena.isActive()).toBe(true);
|
||||||
expect(enemy.getStatStage(Stat.ATK)).toBe(-2);
|
expect(enemy.getStatStage(Stat.ATK)).toBe(-2);
|
||||||
|
expect(poochyena).toHaveAbilityApplied(AbilityId.INTIMIDATE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should trigger once on initial switch prompt without cancelling opposing abilities", async () => {
|
||||||
|
await game.classicMode.runToSummon([SpeciesId.MIGHTYENA, SpeciesId.POOCHYENA]);
|
||||||
|
await game.classicMode.startBattleWithSwitch(1);
|
||||||
|
|
||||||
|
const [poochyena, mightyena] = game.scene.getPlayerParty();
|
||||||
|
expect(poochyena.species.speciesId).toBe(SpeciesId.POOCHYENA);
|
||||||
|
|
||||||
|
const enemy = game.field.getEnemyPokemon();
|
||||||
|
expect(enemy).toHaveStatStage(Stat.ATK, -1);
|
||||||
|
expect(poochyena).toHaveStatStage(Stat.ATK, -1);
|
||||||
|
|
||||||
|
expect(poochyena).toHaveAbilityApplied(AbilityId.INTIMIDATE);
|
||||||
|
expect(mightyena).not.toHaveAbilityApplied(AbilityId.INTIMIDATE);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should activate on reload with single party", async () => {
|
||||||
|
await game.classicMode.startBattle([SpeciesId.MIGHTYENA]);
|
||||||
|
|
||||||
|
expect(game.field.getEnemyPokemon()).toHaveStatStage(Stat.ATK, -1);
|
||||||
|
|
||||||
|
await game.reload.reloadSession();
|
||||||
|
|
||||||
|
expect(game.field.getEnemyPokemon()).toHaveStatStage(Stat.ATK, -1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should lower ATK of all opponents in a double battle", async () => {
|
it("should lower ATK of all opponents in a double battle", async () => {
|
||||||
|
@ -175,4 +175,27 @@ describe("Evolution", () => {
|
|||||||
expect(fourForm.evoFormKey).toBe("four"); // meanwhile, according to the pokemon-forms, the evoFormKey for a 4 family maushold is "four"
|
expect(fourForm.evoFormKey).toBe("four"); // meanwhile, according to the pokemon-forms, the evoFormKey for a 4 family maushold is "four"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("tyrogue should evolve if move is not in first slot", async () => {
|
||||||
|
game.override
|
||||||
|
.moveset([MoveId.TACKLE, MoveId.RAPID_SPIN, MoveId.LOW_KICK])
|
||||||
|
.enemySpecies(SpeciesId.GOLEM)
|
||||||
|
.enemyMoveset(MoveId.SPLASH)
|
||||||
|
.startingWave(41)
|
||||||
|
.startingLevel(19)
|
||||||
|
.enemyLevel(30);
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([SpeciesId.TYROGUE]);
|
||||||
|
|
||||||
|
const tyrogue = game.field.getPlayerPokemon();
|
||||||
|
|
||||||
|
const golem = game.field.getEnemyPokemon();
|
||||||
|
golem.hp = 1;
|
||||||
|
expect(golem.hp).toBe(1);
|
||||||
|
|
||||||
|
game.move.select(MoveId.TACKLE);
|
||||||
|
await game.phaseInterceptor.to("EndEvolutionPhase");
|
||||||
|
|
||||||
|
expect(tyrogue.species.speciesId).toBe(SpeciesId.HITMONTOP);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,6 @@ import { AbilityId } from "#enums/ability-id";
|
|||||||
import { MoveId } from "#enums/move-id";
|
import { MoveId } from "#enums/move-id";
|
||||||
import { SpeciesId } from "#enums/species-id";
|
import { SpeciesId } from "#enums/species-id";
|
||||||
import { DamageAnimPhase } from "#phases/damage-anim-phase";
|
import { DamageAnimPhase } from "#phases/damage-anim-phase";
|
||||||
import { TurnEndPhase } from "#phases/turn-end-phase";
|
|
||||||
import { GameManager } from "#test/test-utils/game-manager";
|
import { GameManager } from "#test/test-utils/game-manager";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
@ -54,7 +53,7 @@ describe("Items - Leftovers", () => {
|
|||||||
const leadHpAfterDamage = leadPokemon.hp;
|
const leadHpAfterDamage = leadPokemon.hp;
|
||||||
|
|
||||||
// Check if leftovers heal us
|
// Check if leftovers heal us
|
||||||
await game.phaseInterceptor.to(TurnEndPhase);
|
await game.phaseInterceptor.to("PokemonHealPhase");
|
||||||
expect(leadPokemon.hp).toBeGreaterThan(leadHpAfterDamage);
|
expect(leadPokemon.hp).toBeGreaterThan(leadHpAfterDamage);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -200,7 +200,7 @@ describe("Moves - Entry Hazards", () => {
|
|||||||
expect(enemy).toHaveTakenDamage(enemy.getMaxHp() * 0.125 * multi);
|
expect(enemy).toHaveTakenDamage(enemy.getMaxHp() * 0.125 * multi);
|
||||||
expect(game.textInterceptor.logs).toContain(
|
expect(game.textInterceptor.logs).toContain(
|
||||||
i18next.t("arenaTag:stealthRockActivateTrap", {
|
i18next.t("arenaTag:stealthRockActivateTrap", {
|
||||||
pokemonName: getPokemonNameWithAffix(enemy),
|
pokemonNameWithAffix: getPokemonNameWithAffix(enemy),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -61,4 +61,16 @@ describe("Moves - Pollen Puff", () => {
|
|||||||
|
|
||||||
expect(target.battleData.hitCount).toBe(2);
|
expect(target.battleData.hitCount).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Regression test for pollen puff healing an enemy after dealing damage
|
||||||
|
it("should not heal an enemy after dealing damage", async () => {
|
||||||
|
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
|
||||||
|
const target = game.field.getEnemyPokemon();
|
||||||
|
game.move.use(MoveId.POLLEN_PUFF);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
|
||||||
|
expect(target.hp).not.toBe(target.getMaxHp());
|
||||||
|
expect(game.phaseInterceptor.log).not.toContain("PokemonHealPhase");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
60
test/status-effects/general-status-effect.test.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { allAbilities } from "#data/data-lists";
|
||||||
|
import { AbilityId } from "#enums/ability-id";
|
||||||
|
import { MoveId } from "#enums/move-id";
|
||||||
|
import { SpeciesId } from "#enums/species-id";
|
||||||
|
import { StatusEffect } from "#enums/status-effect";
|
||||||
|
import { ObtainStatusEffectPhase } from "#phases/obtain-status-effect-phase";
|
||||||
|
import { GameManager } from "#test/test-utils/game-manager";
|
||||||
|
import type { PostAttackContactApplyStatusEffectAbAttr } from "#types/ability-types";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
|
||||||
|
|
||||||
|
describe("Status Effects - General", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.battleStyle("single")
|
||||||
|
.enemyLevel(5)
|
||||||
|
.enemySpecies(SpeciesId.MAGIKARP)
|
||||||
|
.enemyAbility(AbilityId.BALL_FETCH)
|
||||||
|
.enemyMoveset(MoveId.SPLASH)
|
||||||
|
.ability(AbilityId.BALL_FETCH);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("multiple status effects from the same interaction should not overwrite each other", async () => {
|
||||||
|
game.override.ability(AbilityId.POISON_TOUCH).moveset([MoveId.NUZZLE]);
|
||||||
|
await game.classicMode.startBattle([SpeciesId.PIKACHU]);
|
||||||
|
|
||||||
|
// Force poison touch to always apply
|
||||||
|
vi.spyOn(
|
||||||
|
allAbilities[AbilityId.POISON_TOUCH].getAttrs(
|
||||||
|
"PostAttackContactApplyStatusEffectAbAttr",
|
||||||
|
// expose chance, which is private, for testing purpose, but keep type safety otherwise
|
||||||
|
)[0] as unknown as Omit<PostAttackContactApplyStatusEffectAbAttr, "chance"> & { chance: number },
|
||||||
|
"chance",
|
||||||
|
"get",
|
||||||
|
).mockReturnValue(100);
|
||||||
|
const statusEffectPhaseSpy = vi.spyOn(ObtainStatusEffectPhase.prototype, "start");
|
||||||
|
|
||||||
|
game.move.select(MoveId.NUZZLE);
|
||||||
|
await game.toEndOfTurn();
|
||||||
|
|
||||||
|
expect(statusEffectPhaseSpy).toHaveBeenCalledOnce();
|
||||||
|
const enemy = game.field.getEnemyPokemon();
|
||||||
|
// This test does not care which status effect is applied, as long as one is.
|
||||||
|
expect(enemy.status?.effect).toBeOneOf([StatusEffect.POISON, StatusEffect.PARALYSIS]);
|
||||||
|
});
|
||||||
|
});
|
@ -1,6 +1,7 @@
|
|||||||
import { getGameMode } from "#app/game-mode";
|
import { getGameMode } from "#app/game-mode";
|
||||||
import overrides from "#app/overrides";
|
import overrides from "#app/overrides";
|
||||||
import { BattleStyle } from "#enums/battle-style";
|
import { BattleStyle } from "#enums/battle-style";
|
||||||
|
import { Button } from "#enums/buttons";
|
||||||
import { GameModes } from "#enums/game-modes";
|
import { GameModes } from "#enums/game-modes";
|
||||||
import { Nature } from "#enums/nature";
|
import { Nature } from "#enums/nature";
|
||||||
import type { SpeciesId } from "#enums/species-id";
|
import type { SpeciesId } from "#enums/species-id";
|
||||||
@ -100,4 +101,33 @@ export class ClassicModeHelper extends GameManagerHelper {
|
|||||||
await this.game.phaseInterceptor.to(CommandPhase);
|
await this.game.phaseInterceptor.to(CommandPhase);
|
||||||
console.log("==================[New Turn]==================");
|
console.log("==================[New Turn]==================");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queue inputs to switch at the start of the next battle, and then start it.
|
||||||
|
* @param pokemonIndex - The 0-indexed position of the party pokemon to switch to.
|
||||||
|
* Should never be called with 0 as that will select the currently active pokemon and freeze
|
||||||
|
* @returns A Promise that resolves once the battle has been started and the switch prompt resolved
|
||||||
|
* @todo Make this work for double battles
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* await game.classicMode.runToSummon([SpeciesId.MIGHTYENA, SpeciesId.POOCHYENA])
|
||||||
|
* await game.startBattleWithSwitch(1);
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
public async startBattleWithSwitch(pokemonIndex: number): Promise<void> {
|
||||||
|
this.game.scene.battleStyle = BattleStyle.SWITCH;
|
||||||
|
this.game.onNextPrompt(
|
||||||
|
"CheckSwitchPhase",
|
||||||
|
UiMode.CONFIRM,
|
||||||
|
() => {
|
||||||
|
this.game.scene.ui.getHandler().setCursor(0);
|
||||||
|
this.game.scene.ui.getHandler().processInput(Button.ACTION);
|
||||||
|
},
|
||||||
|
() => this.game.isCurrentPhase("CommandPhase") || this.game.isCurrentPhase("TurnInitPhase"),
|
||||||
|
);
|
||||||
|
this.game.doSelectPartyPokemon(pokemonIndex);
|
||||||
|
|
||||||
|
await this.game.phaseInterceptor.to("CommandPhase");
|
||||||
|
console.log("==================[New Battle (Initial Switch)]==================");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ import { NewBiomeEncounterPhase } from "#phases/new-biome-encounter-phase";
|
|||||||
import { NextEncounterPhase } from "#phases/next-encounter-phase";
|
import { NextEncounterPhase } from "#phases/next-encounter-phase";
|
||||||
import { PartyExpPhase } from "#phases/party-exp-phase";
|
import { PartyExpPhase } from "#phases/party-exp-phase";
|
||||||
import { PartyHealPhase } from "#phases/party-heal-phase";
|
import { PartyHealPhase } from "#phases/party-heal-phase";
|
||||||
|
import { PokemonHealPhase } from "#phases/pokemon-heal-phase";
|
||||||
import { PokemonTransformPhase } from "#phases/pokemon-transform-phase";
|
import { PokemonTransformPhase } from "#phases/pokemon-transform-phase";
|
||||||
import { PositionalTagPhase } from "#phases/positional-tag-phase";
|
import { PositionalTagPhase } from "#phases/positional-tag-phase";
|
||||||
import { PostGameOverPhase } from "#phases/post-game-over-phase";
|
import { PostGameOverPhase } from "#phases/post-game-over-phase";
|
||||||
@ -181,6 +182,7 @@ export class PhaseInterceptor {
|
|||||||
UnlockPhase,
|
UnlockPhase,
|
||||||
PostGameOverPhase,
|
PostGameOverPhase,
|
||||||
RevivalBlessingPhase,
|
RevivalBlessingPhase,
|
||||||
|
PokemonHealPhase,
|
||||||
];
|
];
|
||||||
|
|
||||||
private endBySetMode = [
|
private endBySetMode = [
|
||||||
|
@ -6,7 +6,7 @@ import { UiMode } from "#enums/ui-mode";
|
|||||||
import type { Pokemon } from "#field/pokemon";
|
import type { Pokemon } from "#field/pokemon";
|
||||||
import { GameManager } from "#test/test-utils/game-manager";
|
import { GameManager } from "#test/test-utils/game-manager";
|
||||||
import type { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler";
|
import type { ModifierSelectUiHandler } from "#ui/modifier-select-ui-handler";
|
||||||
import type { PartyUiHandler } from "#ui/party-ui-handler";
|
import { type PartyUiHandler, PartyUiMode } from "#ui/party-ui-handler";
|
||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
@ -169,4 +169,51 @@ describe("UI - Transfer Items", () => {
|
|||||||
expect(pokemon.getHeldItems().map(h => h.stackCount)).toEqual([2, 2]);
|
expect(pokemon.getHeldItems().map(h => h.stackCount)).toEqual([2, 2]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: This test breaks when running all tests on github. Fix this once hotfix period is over.
|
||||||
|
it.todo("should not allow changing to discard mode when transfering items", async () => {
|
||||||
|
let handler: PartyUiHandler | undefined;
|
||||||
|
|
||||||
|
const { resolve, promise } = Promise.withResolvers<void>();
|
||||||
|
|
||||||
|
game.onNextPrompt("SelectModifierPhase", UiMode.MODIFIER_SELECT, async () => {
|
||||||
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
const modifierHandler = game.scene.ui.getHandler() as ModifierSelectUiHandler;
|
||||||
|
|
||||||
|
modifierHandler.processInput(Button.DOWN);
|
||||||
|
modifierHandler.setCursor(1);
|
||||||
|
modifierHandler.processInput(Button.ACTION);
|
||||||
|
});
|
||||||
|
|
||||||
|
game.onNextPrompt("SelectModifierPhase", UiMode.PARTY, async () => {
|
||||||
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
handler = game.scene.ui.getHandler() as PartyUiHandler;
|
||||||
|
|
||||||
|
handler.setCursor(0);
|
||||||
|
handler.processInput(Button.ACTION);
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r, 100));
|
||||||
|
handler.processInput(Button.ACTION);
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
await promise;
|
||||||
|
expect(handler).toBeDefined();
|
||||||
|
if (handler) {
|
||||||
|
const partyMode = handler["partyUiMode"];
|
||||||
|
expect(partyMode).toBe(PartyUiMode.MODIFIER_TRANSFER);
|
||||||
|
|
||||||
|
handler.setCursor(7);
|
||||||
|
handler.processInput(Button.ACTION);
|
||||||
|
// Should not change mode to discard
|
||||||
|
expect(handler["partyUiMode"]).toBe(PartyUiMode.MODIFIER_TRANSFER);
|
||||||
|
|
||||||
|
handler.processInput(Button.CANCEL);
|
||||||
|
handler.setCursor(7);
|
||||||
|
handler.processInput(Button.ACTION);
|
||||||
|
// Should change mode to discard
|
||||||
|
expect(handler["partyUiMode"]).toBe(PartyUiMode.DISCARD);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
39
test/utils/data.test.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { deepCopy, isBareObject } from "#utils/data";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Utils - Data", () => {
|
||||||
|
describe("deepCopy", () => {
|
||||||
|
it("should create a deep copy of an object", () => {
|
||||||
|
const original = { a: 1, b: { c: 2 } };
|
||||||
|
const copy = deepCopy(original);
|
||||||
|
// ensure the references are different
|
||||||
|
expect(copy === original, "copied object should not compare equal").not;
|
||||||
|
expect(copy).toEqual(original);
|
||||||
|
// update copy's `a` to a different value and ensure original is unaffected
|
||||||
|
copy.a = 42;
|
||||||
|
expect(original.a, "adjusting property of copy should not affect original").toBe(1);
|
||||||
|
// update copy's nested `b.c` to a different value and ensure original is unaffected
|
||||||
|
copy.b.c = 99;
|
||||||
|
expect(original.b.c, "adjusting nested property of copy should not affect original").toBe(2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("isBareObject", () => {
|
||||||
|
it("should properly identify bare objects", () => {
|
||||||
|
expect(isBareObject({}), "{} should be considered bare");
|
||||||
|
expect(isBareObject(new Object()), "new Object() should be considered bare");
|
||||||
|
expect(isBareObject(Object.create(null)));
|
||||||
|
expect(isBareObject([]), "an empty array should be considered bare");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should properly reject non-objects", () => {
|
||||||
|
expect(isBareObject(new Date())).not;
|
||||||
|
expect(isBareObject(null)).not;
|
||||||
|
expect(isBareObject(42)).not;
|
||||||
|
expect(isBareObject("")).not;
|
||||||
|
expect(isBareObject(undefined)).not;
|
||||||
|
expect(isBareObject(() => {})).not;
|
||||||
|
expect(isBareObject(new (class A {})())).not;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -59,5 +59,12 @@
|
|||||||
},
|
},
|
||||||
// Exclude checking for script JS files as those are covered by the folder's `jsconfig.json`
|
// Exclude checking for script JS files as those are covered by the folder's `jsconfig.json`
|
||||||
"include": ["**/*.ts", "**/*.d.ts"],
|
"include": ["**/*.ts", "**/*.d.ts"],
|
||||||
"exclude": ["node_modules", "dist", "vite.config.ts", "vitest.config.ts", "vitest.workspace.ts"]
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist",
|
||||||
|
"vite.config.ts",
|
||||||
|
"vitest.config.ts",
|
||||||
|
"vitest.workspace.ts",
|
||||||
|
"public/service-worker.js"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|