mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-06 00:12:16 +02:00
Merge branch 'refs/heads/beta' into beta_fusion_names
# Conflicts: # src/locales/de/config.ts # src/locales/en/config.ts # src/locales/es/config.ts # src/locales/fr/config.ts # src/locales/it/config.ts # src/locales/ko/config.ts # src/locales/pt_BR/config.ts # src/locales/zh_CN/config.ts # src/locales/zh_TW/config.ts
This commit is contained in:
commit
e0c8773f30
224
docs/enemy-ai.md
Normal file
224
docs/enemy-ai.md
Normal file
@ -0,0 +1,224 @@
|
||||
# EnemyCommandPhase: How Enemy Pokémon Decide What to Do
|
||||
|
||||
## Step 1: Should the Enemy Pokémon Switch?
|
||||
|
||||
When battling an enemy Trainer, the first decision the enemy needs to make is whether or not to switch an active Pokémon with another Pokémon in their party. This decision is primarily made by comparing **matchup scores** between each Pokémon in the enemy's party.
|
||||
|
||||
### Calculating Matchup Scores
|
||||
|
||||
The core function for matchup score calculation can be found in `src/field/pokemon.ts`, within the `Pokemon` class:
|
||||
|
||||
```ts
|
||||
getMatchupScore(pokemon: Pokemon): number;
|
||||
```
|
||||
|
||||
This computes the source Pokémon's matchup score against the Pokémon passed by argument using the formula
|
||||
|
||||
$$\text{MUScore} = (\text{atkScore}+\text{defScore}) * \text{hpDiffRatio} $$
|
||||
|
||||
where
|
||||
- $\text{atkScore}$ is the combined effectiveness of the source Pokémon's types against the opposing Pokémon's defensive typing: $\prod_{\text{types}} \text{typeEffectiveness}(\text{type}, \text{oppPokemon})$. $\text{typeEffectiveness}$ is 1 when the type deals neutral damage to the opposing Pokémon's defensive typing, 2 when the type deals super effective damage, and so on. $atkScore$ is also increased by 25 percent if the source Pokémon has a higher Speed stat than the opposing Pokémon.
|
||||
- $\text{defScore}$ is the inverse of the opposing Pokémon's $\text{atkScore}$ against the source Pokémon's defensive typing, or $(\prod_{\text{types}} \text{typeEffectiveness}(\text{type}, \text{sourcePokemon}))^{-1}$. Unlike $\text{atkScore}$, $\text{defScore}$ is capped at a maximum score of 4.
|
||||
- $\text{hpDiffRatio}= \text{sourceHpRatio}-\text{oppHpRatio}+1$. This is further multiplied by 1.5 if the source Pokémon has a higher Speed stat than the opposing Pokémon; however, $\text{hpDiffRatio}$ cannot be higher than 1.
|
||||
|
||||
The maximum possible matchup score a Pokémon could have against a single opponent is $(16+16)\times 2=64$, which occurs when
|
||||
- the Pokémon hits its opponent for 4x super effective damage with both of its types.
|
||||
- the Pokémon is immune to or resists both of the opponent's types by 4x.
|
||||
- the Pokémon is at max HP while the opponent's HP ratio is near zero.
|
||||
|
||||
In most situations, though, a Pokémon's matchup score against an opponent will be at most 16, which is equivalent to having two super effective types and resisting both of the opponent's types with the same HP ratios as before.
|
||||
|
||||
The minimum possible matchup score a Pokémon could have against a single opponent is near zero, which occurs when the Pokémon's HP ratio is near zero while the opponent is at max HP. However, a Pokémon's matchup score can also be very low when its type(s) are 4x weak to and/or resisted by its opponent's types.
|
||||
|
||||
### Determining Switches in EnemyCommandPhase
|
||||
|
||||
The `EnemyCommandPhase` follows this process to determine whether or not an enemy Pokémon should switch on each turn during a Trainer battle.
|
||||
|
||||
1. If the Pokémon has a move already queued (e.g. they are recharging after using Hyper Beam), or they are trapped (e.g. by Bind or Arena Trap), skip to resolving a `FIGHT` command (see next section).
|
||||
2. For each Pokémon in the enemy's party, [compute their matchup scores](#calculating-matchup-scores) against the active player Pokémon. If there are two active player Pokémon in the battle, add their matchup scores together.
|
||||
3. Take the party member with the highest matchup score and apply a multiplier to the score that reduces the score based on how frequently the enemy trainer has switched Pokémon in the current battle.
|
||||
- The multiplier scales off of a counter that increments when the enemy trainer chooses to switch a Pokémon and decrements when they choose to use a move.
|
||||
4. Compare the result of Step 3 with the active enemy Pokémon's matchup score. If the party member's matchup score is at least three times that of the active Pokémon, switch to that party member.
|
||||
- "Boss" trainers only require the party member's matchup score to be at least two times that of the active Pokémon, so they are more likely to switch than other trainers. The full list of boss trainers in the game is as follows:
|
||||
- All gym leaders, Elite 4 members, and Champions
|
||||
- All Evil Team leaders
|
||||
- The last three Rival Fights (on waves 95, 145, and 195)
|
||||
5. If the enemy decided to switch, send a switch `turnCommand` and end this `EnemyCommandPhase`; otherwise, move on to resolving a `FIGHT` enemy command.
|
||||
|
||||
## Step 2: Selecting a Move
|
||||
|
||||
At this point, the enemy (a wild or trainer Pokémon) has decided against switching and instead will use a move from its moveset. However, it still needs to figure out which move to use and, if applicable, which target to use the move against. The logic for determining an enemy's next move and target is contained within two methods: `EnemyPokemon.getNextMove()` and `EnemyPokemon.getNextTargets()` in `src/field/pokemon.ts`.
|
||||
|
||||
### Choosing a Move with `getNextMove()`
|
||||
|
||||
In `getNextMove()`, the enemy Pokémon chooses a move to use in the following steps:
|
||||
1. If the Pokémon has a move in its Move Queue (e.g. the second turn of a charging move), and the queued move is still usable, use that move against the given target.
|
||||
2. Filter out any moves it can't use within its moveset. The remaining moves make up the enemy's **move pool** for the turn.
|
||||
1. A move can be unusable if it has no PP left or it has been disabled by another move or effect
|
||||
2. If the enemy's move pool is empty, use Struggle.
|
||||
3. Calculate the **move score** of each move in the enemy's move pool.
|
||||
1. A move's move score is equivalent to the move's maximum **target score** among all of the move's possible targets on the field ([more on this later](#calculating-move-and-target-scores)).
|
||||
2. A move's move score is set to -20 if at least one of these conditions are met:
|
||||
- The move is unimplemented (or, more precisely, the move's name ends with " (N)").
|
||||
- Conditions for the move to succeed are not met (unless the move is Sucker Punch, Upper Hand, or Thunderclap, as those moves' conditions can't be resolved before the turn starts).
|
||||
- The move's target scores are 0 or `NaN` for each target. In this case, the game assumes the target score calculation for that move is unimplemented.
|
||||
4. Sort the move pool in descending order of move scores.
|
||||
5. From here, the enemy's move selection varies based on its `aiType`. If the enemy is a Boss Pokémon or has a Trainer, it uses the `SMART` AI type; otherwise, it uses the `SMART_RANDOM` AI type.
|
||||
1. Let $m_i$ be the *i*-th move in the sorted move pool $M$:
|
||||
- If `aiType === SMART_RANDOM`, the enemy has a 5/8 chance of selecting $m_0$ and a 3/8 chance of advancing to the next best move $m_1$, where it then repeats this roll. This process stops when a move is selected or the last move in the move pool is reached.
|
||||
- If `aiType === SMART`, a similar loop is used to decide between selecting the move $m_i$ and advancing to the next iteration with the move $m_{i+1}$. However, instead of using a flat probability, the following conditions need to be met to advance from selecting $m_i$ to $m_{i+1}$:
|
||||
- $\text{sign}(s_i) = \text{sign}(s_{i+1})$, where $s_i$ is the move score of $m_i$.
|
||||
- $\text{randInt}(0, 100) < \text{round}(\frac{s_{i+1}}{s_i}\times 50)$. In other words: if the scores of $m_i$ and $m_{i+1}$ have the same sign, the chance to advance to the next iteration with $m_{i+1}$ is proportional to how close the scores are to each other. The probability to advance to the next iteration is at most 50 percent (when $s_i$ and $s_{i+1}$ are equal).
|
||||
6. The enemy will use the move selected in Step 5 against the target(s) with the highest [**target selection score (TSS)**](#choosing-targets-with-getnexttargets)
|
||||
|
||||
### Calculating Move and Target Scores
|
||||
|
||||
As part of the move selection process, the enemy Pokémon must compute a **target score (TS)** for each legal target for each move in its move pool. The base target score for all moves is a combination of the move's **user benefit score (UBS)** and **target benefit score (TBS)**.
|
||||
|
||||

|
||||
|
||||
A move's UBS and TBS are computed with the respective functions in the `Move` class:
|
||||
|
||||
```ts
|
||||
getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer;
|
||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer;
|
||||
```
|
||||
|
||||
Logically, these functions are very similar – they add up their respective benefit scores from each of the move's attributes (as determined by `attr.getUserBenefitScore`, and `attr.getTargetBenefitScore`, respectively) and return the total benefit score. However, there are two key functional differences in how the UBS and TBS of a move are handled:
|
||||
1. In addition to influencing move selection, a move's TBS also influences target selection for that move, whereas UBS has no influence.
|
||||
2. When evaluating the target score of a move against an opposing Pokémon, the move's TBS is multiplied by -1, whereas the move's UBS does not change. For this reason, move attributes return negative values for their TBS to reward using the move against an enemy.
|
||||
|
||||
#### Calculating Target Benefit Score (TBS) for Attack Moves
|
||||
|
||||
In addition to the base score from `Move.getTargetBenefitScore()`, attack moves calculate an `attackScore` which influences the move's TBS based on the following properties:
|
||||
- The move's power (after the move's `VariablePowerAttrs` are applied)
|
||||
- The move's type effectiveness against the target (note that this also accounts for type immunities from abilities such as Levitate and field effects such as Strong Winds).
|
||||
- The move's category (Physical/Special), and whether the user has a higher Attack or Special Attack stat.
|
||||
|
||||
More specifically, the following steps are taken to compute the move's `attackScore`:
|
||||
1. Compute a multiplier based on the move's type effectiveness:
|
||||
|
||||
%7D%5C%5C-2&&%5Ctext%7Botherwise%7D%5C%5C%5Cend%7Bmatrix%7D%5Cright.)
|
||||
2. Compute a multiplier based on the move's category and the user's offensive stats:
|
||||
1. Compute the user's offensive stat ratio:
|
||||
|
||||

|
||||
2. Compute the stat-based multiplier:
|
||||
|
||||

|
||||
3. Calculate the move's `attackScore`:
|
||||
|
||||
$\text{attackScore} = (\text{typeMult}\times \text{statMult})+\lfloor \frac{\text{power}}{5} \rfloor$
|
||||
|
||||
The maximum total multiplier in `attackScore` ($\text{typeMult}\times \text{statMult}$) is 4, which occurs for attacks that are super effective against the target and are categorically aligned with the user's offensive stats (e.g. the move is physical, and the user has much higher Attack than Sp. Atk). The minimum total multiplier of -4 occurs (somewhat confusingly) for attacks that are not super effective but are categorically aligned with the user's offensive stats.
|
||||
|
||||
The attack move's total TBS, then, is $\text{TBS}=\text{baseScore}-\text{attackScore}$, where $\text{baseScore}$ is the result of `Move.getTargetBenefitScore()`.
|
||||
|
||||
#### Calculating Target Score (TS) for Attack Moves
|
||||
|
||||
The final step to calculate an attack move's target score (TS) is to multiply the base target score by the move's type effectiveness and STAB (if it applies):
|
||||
- If the target is an enemy, the corresponding TS is multiplied by the move's type effectiveness against the enemy (e.g. 2 if the move is super effective), then by 1.5 if the move shares a type with the user.
|
||||
- If the target is an ally, the TS is divided by these factors instead.
|
||||
- If $\text{TS}=0$ after these multipliers are applied, the TS is set to -20 for the current target.
|
||||
|
||||
### Choosing Targets with `getNextTargets()`
|
||||
|
||||
The enemy's target selection for single-target moves works in a very similar way to its move selection. Each potential target is given a **target selection score (TSS)** which is based on the move's [target benefit score](#calculating-move-and-target-scores) for that target:
|
||||
|
||||

|
||||
|
||||
Once the TSS is calculated for each target, the target is selected as follows:
|
||||
1. Sort the targets (indexes) in decreasing order of their target selection scores (or weights). Let $t_i$ be the index of the *i*-th target in the sorted list, and let $w_i$ be that target's corresponding TSS.
|
||||
2. Normalize the weights. Let $w_n$ be the lowest-weighted target in the sorted list, then:
|
||||
|
||||

|
||||
3. Remove all weights from the list such that $W_i < \frac{W_0}{2}$
|
||||
4. Generate a random integer $R=\text{rand}(0, W_{\text{total}})$ where $W_{\text{total}}$ is the sum of all the remaining weights after Step 3.
|
||||
5. For each target $(t_i, W_i)$,
|
||||
1. if $R \le \sum_{j=0}^{i} W_i$, or if $t_i$ is the last target in the list, **return** $t_i$
|
||||
2. otherwise, advance to the next target $t_{i+1}$ and repeat this check.
|
||||
|
||||
Once the target is selected, the enemy has successfully determined its next action for the turn, and its corresponding `EnemyCommandPhase` ends. From here, the `TurnStartPhase` processes the enemy's commands alongside the player's commands and begins to resolve the turn.
|
||||
|
||||
## An Example in Battle
|
||||
|
||||
Suppose you enter a single battle against an enemy trainer with the following Pokémon in their party:
|
||||
|
||||
1. An [Excadrill](https://bulbapedia.bulbagarden.net/wiki/Excadrill_(Pok%C3%A9mon)) with the Ability Sand Force and the following moveset
|
||||
1. Earthquake
|
||||
2. Iron Head
|
||||
3. Crush Claw
|
||||
4. Swords Dance
|
||||
2. A [Heatmor](https://bulbapedia.bulbagarden.net/wiki/Heatmor_(Pok%C3%A9mon)) with the Ability Flash Fire and the following moveset
|
||||
1. Fire Lash
|
||||
2. Inferno
|
||||
3. Hone Claws
|
||||
4. Shadow Claw
|
||||
|
||||
The enemy trainer leads with their Heatmor, and you lead with a [Dachsbun](https://bulbapedia.bulbagarden.net/wiki/Dachsbun_(Pok%C3%A9mon)) with the Ability Well-Baked Body. We'll cover the enemy's behavior over the next two turns.
|
||||
|
||||
### Turn 1
|
||||
|
||||
To determine whether the enemy should switch Pokémon, it first calculates each party member's matchup scores against the player's Dachsbun:
|
||||
|
||||
$$\text{MUScore} = (\text{atkScore}+\text{defScore}) * \text{hpDiffRatio} $$
|
||||
- Defensively, Heatmor's Fire typing resists Dachsbun's Fairy typing, so its `defScore` is 2. However, because of Dachsbun's Fire immunity granted by Well-Baked Body, Heatmor's `atkScore` against Dachsbun is 0. With both Pokémon at maximum HP, Heatmor's total matchup score is 2.
|
||||
- Excadrill's Steel typing also resists Fairy, so its `defScore` is also 2. In this case, though, Steel is also super effective against Fairy, so Excadrill's base `atkScore` is 2. If Excadrill outspeeds Dachsbun (possibly due to it having a +Spd nature or holding a Carbos), its `atkScore` is further increased to 2.5. Since both Pokémon are at maximum HP, Excadrill's total matchup score is 4 (or 4.5 if it outspeeds).
|
||||
|
||||
Based on the enemy party's matchup scores, whether or not the trainer switches out Heatmor for Excadrill depends on the trainer's type. The difference in matchup scores is enough to cause a switch to Excadrill for boss trainers (e.g. gym leaders) but not for regular trainers. For this example, we'll assume the trainer is a boss and, therefore, decides to switch to Excadrill on this turn.
|
||||
|
||||
### Turn 2
|
||||
|
||||
Now that the enemy Pokémon with the best matchup score is on the field (assuming it survives Dachsbun's attack on the last turn), the enemy will now decide to have Excadrill use one of its moves. Assuming all of its moves are usable, we'll go through the target score calculations for each move:
|
||||
|
||||
- **Earthquake**: In a single battle, this move is just a 100-power Ground-type physical attack with no additional effects. With no additional benefit score from attributes, the move's base target score against the player's Dachsbun is just the `attackScore` from `AttackMove.getTargetBenefitScore()`. In this case, Earthquake's `attackScore` is given by
|
||||
|
||||
$\text{attackScore}=(\text{typeMult}\times \text{statMult}) + \lfloor \frac{\text{power}}{5} \rfloor = -2\times 2 + 20 = 16$
|
||||
|
||||
Here, `typeMult` is -2 because the move is not super effective, and `statMult` is 2 because Excadrill's Attack is significantly higher than its Sp. Atk. Accounting for STAB thanks to Excadrill's typing, the final target score for this move is **24**
|
||||
|
||||
- **Iron Head**: This move is an 80-power Steel-type physical attack with an additional chance to cause the target to flinch. With these properties, Iron Head has a user benefit score of 0 and a target benefit score given by
|
||||
|
||||
$\text{TBS}=\text{getTargetBenefitScore(FlinchAttr)}-\text{attackScore}$
|
||||
|
||||
Under its current implementation, the target benefit score of `FlinchAttr` is -5. Calculating the move's `attackScore`, we get:
|
||||
|
||||
$\text{attackScore}=(\text{typeMult}\times \text{statMult}) + \lfloor \frac{\text{power}}{5} \rfloor = 2\times 2 + 16 = 20$
|
||||
|
||||
Note that `typeMult` in this case is 2 because Iron Head is super effective (or better) against Dachsbun. With the move's UBS at 0, the base target score calculation against Dachsbun simplifies to
|
||||
|
||||
$\text{TS}=-\text{TBS}=-(-5-20)=25$
|
||||
|
||||
We then need to apply a 2x multiplier for the move's type effectiveness and a 1.5x multiplier since STAB applies. After applying these multipliers, the final score for this move is **75**.
|
||||
|
||||
- **Swords Dance**: As a non-attacking move, this move's benefit score is derived entirely from the sum of its attributes' benefit scores. Swords Dance's `StatChangeAttr` has a user benefit score of 0 and a target benefit score that, in this case, simplifies to
|
||||
|
||||
$\text{TBS}=4\times \text{levels} + (-2\times \text{sign(levels)})$
|
||||
|
||||
where `levels` is the number of stat stages added by the attribute (in this case, +2). The final score for this move is **6** (Note: because this move is self-targeted, we don't flip the sign of TBS when computing the target score).
|
||||
|
||||
- **Crush Claw**: This move is a 75-power Normal-type physical attack with a 50 percent chance to lower the target's Defense by one stage. The additional effect is implemented by the same `StatChangeAttr` as Swords Dance, so we can use the same formulas from before to compute the total TBS and base target score.
|
||||
|
||||
$\text{TBS}=\text{getTargetBenefitScore(StatChangeAttr)}-\text{attackScore}$
|
||||
|
||||
$\text{TBS}=(-4 + 2)-(-2\times 2 + \lfloor \frac{75}{5} \rfloor)=-2-11=-13$
|
||||
|
||||
$\text{TS}=-\text{TBS}=13$
|
||||
|
||||
This move is neutral against Dachsbun and isn't boosted by STAB from Excadrill, so we don't need to apply any extra multipliers. The final score for this move is **13**.
|
||||
|
||||
We now have a sorted move pool in decreasing order of move scores:
|
||||
1. Iron Head (**75**)
|
||||
2. Earthquake (**24**)
|
||||
3. Crush Claw (**13**)
|
||||
4. Swords Dance (**6**)
|
||||
|
||||
Since no other score is at least half that of Iron Head's score, the enemy AI automatically chooses to use Iron Head against Dachsbun at this point.
|
||||
|
||||
## Guidelines for Implementing Benefit Scores
|
||||
|
||||
When implementing a new move attribute, it's important to override `MoveAttr`'s `getUserBenefitScore` and `getTargetBenefitScore` functions to ensure that the enemy AI can accurately determine when and how to use moves with that attribute. Here are a few basic specifications you should adhere to when implementing benefit scores for a new attribute:
|
||||
- A move's **user benefit score (UBS)** incentivizes (or discourages) the move's usage in general. A positive UBS gives the move more incentive to be used, while a negative UBS gives the move less incentive.
|
||||
- A move's **target benefit score (TBS)** incentivizes (or discourages) the move's usage on a specific target. A positive TBS indicates the move is better used on the user or its allies, while a negative TBS indicates the move is better used on enemies.
|
||||
- **The total benefit score (UBS + TBS) of a move should never be 0.** The move selection algorithm assumes the move's benefit score is unimplemented if the total score is 0 and penalizes the move's usage as a result. With status moves especially, it's important to have some form of implementation among the move's attributes to avoid this scenario.
|
||||
- **Score functions that use formulas should include comments.** If your attribute requires complex logic or formulas to calculate benefit scores, please add comments to explain how the logic works and its intended effect on the enemy's decision making.
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 7.3 KiB |
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 7.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 5.8 KiB After Width: | Height: | Size: 6.1 KiB |
@ -3265,6 +3265,11 @@
|
||||
1,
|
||||
1
|
||||
],
|
||||
"217": [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"229": [
|
||||
0,
|
||||
1,
|
||||
@ -6658,6 +6663,11 @@
|
||||
1,
|
||||
1
|
||||
],
|
||||
"217": [
|
||||
1,
|
||||
1,
|
||||
1
|
||||
],
|
||||
"229": [
|
||||
0,
|
||||
1,
|
||||
|
38
public/images/pokemon/variant/back/female/217.json
Normal file
38
public/images/pokemon/variant/back/female/217.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"0": {
|
||||
"422919": "112114",
|
||||
"7b7b8c": "7b7b8c",
|
||||
"101010": "101010",
|
||||
"945221": "2f6324",
|
||||
"ffffff": "ffffff",
|
||||
"634229": "1d3d26",
|
||||
"b5b5bd": "b5b5bd",
|
||||
"f7c563": "fecd85",
|
||||
"c59c4a": "cd9343",
|
||||
"dedede": "dedede"
|
||||
},
|
||||
"1": {
|
||||
"422919": "2d0e1f",
|
||||
"7b7b8c": "7b7b8c",
|
||||
"101010": "101010",
|
||||
"945221": "8c2a37",
|
||||
"ffffff": "ffffff",
|
||||
"634229": "6b1d38",
|
||||
"b5b5bd": "b5b5bd",
|
||||
"f7c563": "f2cab8",
|
||||
"c59c4a": "c48e81",
|
||||
"dedede": "dedede"
|
||||
},
|
||||
"2": {
|
||||
"422919": "111433",
|
||||
"7b7b8c": "7b7b8c",
|
||||
"101010": "101010",
|
||||
"945221": "323760",
|
||||
"ffffff": "ffffff",
|
||||
"634229": "1e2249",
|
||||
"b5b5bd": "b5b5bd",
|
||||
"f7c563": "5ccaf2",
|
||||
"c59c4a": "45a2f9",
|
||||
"dedede": "dedede"
|
||||
}
|
||||
}
|
50
public/images/pokemon/variant/female/217.json
Normal file
50
public/images/pokemon/variant/female/217.json
Normal file
@ -0,0 +1,50 @@
|
||||
{
|
||||
"0": {
|
||||
"7b7b8c": "7b7b8c",
|
||||
"101010": "101010",
|
||||
"634229": "1d3d26",
|
||||
"ffffff": "ffffff",
|
||||
"945221": "2f6324",
|
||||
"422919": "112114",
|
||||
"dedede": "dedede",
|
||||
"bd7342": "6a8a46",
|
||||
"ffef84": "f7ffa5",
|
||||
"b5b5bd": "b5b5bd",
|
||||
"c59c4a": "ceb552",
|
||||
"f7c563": "f7de7b",
|
||||
"841931": "a52942",
|
||||
"de3a5a": "ef526b"
|
||||
},
|
||||
"1": {
|
||||
"7b7b8c": "7b7b8c",
|
||||
"101010": "101010",
|
||||
"634229": "6b1d38",
|
||||
"ffffff": "ffffff",
|
||||
"945221": "8c2a37",
|
||||
"422919": "2d0e1f",
|
||||
"dedede": "dedede",
|
||||
"bd7342": "b74543",
|
||||
"ffef84": "f9eddb",
|
||||
"b5b5bd": "b5b5bd",
|
||||
"c59c4a": "c48e81",
|
||||
"f7c563": "f2cab8",
|
||||
"841931": "841931",
|
||||
"de3a5a": "de3a5a"
|
||||
},
|
||||
"2": {
|
||||
"7b7b8c": "7b7b8c",
|
||||
"101010": "101010",
|
||||
"634229": "1e2249",
|
||||
"ffffff": "ffffff",
|
||||
"945221": "323760",
|
||||
"422919": "111433",
|
||||
"dedede": "dedede",
|
||||
"bd7342": "46527a",
|
||||
"ffef84": "adf2f7",
|
||||
"b5b5bd": "b5b5bd",
|
||||
"c59c4a": "45a2f9",
|
||||
"f7c563": "5ccaf2",
|
||||
"841931": "a52942",
|
||||
"de3a5a": "ef526b"
|
||||
}
|
||||
}
|
@ -2540,6 +2540,7 @@ export class PreApplyBattlerTagAbAttr extends AbAttr {
|
||||
*/
|
||||
export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr {
|
||||
private immuneTagType: BattlerTagType;
|
||||
private battlerTag: BattlerTag;
|
||||
|
||||
constructor(immuneTagType: BattlerTagType) {
|
||||
super();
|
||||
@ -2550,6 +2551,7 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr {
|
||||
applyPreApplyBattlerTag(pokemon: Pokemon, passive: boolean, tag: BattlerTag, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
if (tag.tagType === this.immuneTagType) {
|
||||
cancelled.value = true;
|
||||
this.battlerTag = tag;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -2560,7 +2562,7 @@ export class PreApplyBattlerTagImmunityAbAttr extends PreApplyBattlerTagAbAttr {
|
||||
return i18next.t("abilityTriggers:battlerTagImmunity", {
|
||||
pokemonNameWithAffix: getPokemonNameWithAffix(pokemon),
|
||||
abilityName,
|
||||
battlerTagName: (args[0] as BattlerTag).getDescriptor()
|
||||
battlerTagName: this.battlerTag.getDescriptor()
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -4837,7 +4839,7 @@ export function initAbilities() {
|
||||
.attr(MoveTypeChangeAttr, Type.ICE, 1.2, (user, target, move) => move.type === Type.NORMAL),
|
||||
new Ability(Abilities.SWEET_VEIL, 6)
|
||||
.attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.SLEEP)
|
||||
.attr(BattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
||||
.attr(UserFieldBattlerTagImmunityAbAttr, BattlerTagType.DROWSY)
|
||||
.ignorable()
|
||||
.partial(), // Mold Breaker ally should not be affected by Sweet Veil
|
||||
new Ability(Abilities.STANCE_CHANGE, 6)
|
||||
|
@ -3837,26 +3837,18 @@ export class IvyCudgelTypeAttr extends VariableMoveTypeAttr {
|
||||
|
||||
switch (form) {
|
||||
case 1: // Wellspring Mask
|
||||
move.type = Type.WATER;
|
||||
break;
|
||||
case 2: // Hearthflame Mask
|
||||
move.type = Type.FIRE;
|
||||
break;
|
||||
case 3: // Cornerstone Mask
|
||||
move.type = Type.ROCK;
|
||||
break;
|
||||
case 4: // Teal Mask Tera
|
||||
move.type = Type.GRASS;
|
||||
break;
|
||||
case 5: // Wellspring Mask Tera
|
||||
move.type = Type.WATER;
|
||||
break;
|
||||
case 2: // Hearthflame Mask
|
||||
case 6: // Hearthflame Mask Tera
|
||||
move.type = Type.FIRE;
|
||||
break;
|
||||
case 3: // Cornerstone Mask
|
||||
case 7: // Cornerstone Mask Tera
|
||||
move.type = Type.ROCK;
|
||||
break;
|
||||
case 4: // Teal Mask Tera
|
||||
default:
|
||||
move.type = Type.GRASS;
|
||||
break;
|
||||
@ -4427,12 +4419,6 @@ export class ProtectAttr extends AddBattlerTagAttr {
|
||||
}
|
||||
}
|
||||
|
||||
export class EndureAttr extends ProtectAttr {
|
||||
constructor() {
|
||||
super(BattlerTagType.ENDURING);
|
||||
}
|
||||
}
|
||||
|
||||
export class IgnoreAccuracyAttr extends AddBattlerTagAttr {
|
||||
constructor() {
|
||||
super(BattlerTagType.IGNORE_ACCURACY, true, false, 2);
|
||||
@ -6549,7 +6535,7 @@ export function initMoves() {
|
||||
.attr(HitHealAttr)
|
||||
.triageMove(),
|
||||
new SelfStatusMove(Moves.ENDURE, Type.NORMAL, -1, 10, -1, 4, 2)
|
||||
.attr(EndureAttr),
|
||||
.attr(ProtectAttr, BattlerTagType.ENDURING),
|
||||
new StatusMove(Moves.CHARM, Type.FAIRY, 100, 20, -1, 0, 2)
|
||||
.attr(StatChangeAttr, BattleStat.ATK, -2),
|
||||
new AttackMove(Moves.ROLLOUT, Type.ROCK, MoveCategory.PHYSICAL, 30, 90, 20, -1, 0, 2)
|
||||
|
@ -9,6 +9,7 @@ import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import { TimeOfDay } from "#enums/time-of-day";
|
||||
import { getPokemonNameWithAffix } from "#app/messages.js";
|
||||
import i18next from "i18next";
|
||||
|
||||
export enum FormChangeItem {
|
||||
NONE,
|
||||
@ -357,22 +358,21 @@ export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
|
||||
export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: SpeciesFormChange, preName: string): string {
|
||||
const isMega = formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1;
|
||||
const isGmax = formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1;
|
||||
const isEmax = formChange.formKey.indexOf("eternamax") > -1;
|
||||
const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1;
|
||||
const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey;
|
||||
const prefix = !pokemon.isPlayer() ? pokemon.hasTrainer() ? "Foe " : "Wild " : "Your ";
|
||||
if (isMega) {
|
||||
return `${prefix}${preName} Mega Evolved\ninto ${pokemon.name}!`;
|
||||
return i18next.t("battlePokemonForm:megaChange", { preName, pokemonName: pokemon.name });
|
||||
}
|
||||
if (isGmax) {
|
||||
return `${prefix}${preName} Gigantamaxed\ninto ${pokemon.name}!`;
|
||||
return i18next.t("battlePokemonForm:gigantamaxChange", { preName, pokemonName: pokemon.name });
|
||||
}
|
||||
if (isEmax) {
|
||||
return `${prefix}${preName} Eternamaxed\ninto ${pokemon.name}!`;
|
||||
return i18next.t("battlePokemonForm:eternamaxChange", { preName, pokemonName: pokemon.name });
|
||||
}
|
||||
if (isRevert) {
|
||||
return `${prefix}${getPokemonNameWithAffix(pokemon)} reverted\nto its original form!`;
|
||||
return i18next.t("battlePokemonForm:revertChange", { pokemonName: getPokemonNameWithAffix(pokemon) });
|
||||
}
|
||||
return `${prefix}${preName} changed form!`;
|
||||
return i18next.t("battlePokemonForm:formChange", { preName });
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -387,7 +387,7 @@ export abstract class PokemonSpeciesForm {
|
||||
for (const moveId of moveset) {
|
||||
if (speciesEggMoves.hasOwnProperty(rootSpeciesId)) {
|
||||
const eggMoveIndex = speciesEggMoves[rootSpeciesId].findIndex(m => m === moveId);
|
||||
if (eggMoveIndex > -1 && eggMoves & Math.pow(2, eggMoveIndex)) {
|
||||
if (eggMoveIndex > -1 && (eggMoves & (1 << eggMoveIndex))) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -582,7 +582,7 @@ export default class PokemonSpecies extends PokemonSpeciesForm implements Locali
|
||||
}
|
||||
|
||||
if (key) {
|
||||
return i18next.t(`pokemonForm:${key}`, {pokemonName: this.name});
|
||||
return i18next.t(`battlePokemonForm:${key}`, {pokemonName: this.name});
|
||||
}
|
||||
}
|
||||
return this.name;
|
||||
|
@ -23,7 +23,7 @@ import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoo
|
||||
import { WeatherType } from "../data/weather";
|
||||
import { TempBattleStat } from "../data/temp-battle-stat";
|
||||
import { ArenaTagSide, WeakenMoveScreenTag } from "../data/arena-tag";
|
||||
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AddSecondStrikeAbAttr, IgnoreOpponentEvasionAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr } from "../data/ability";
|
||||
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AddSecondStrikeAbAttr, IgnoreOpponentEvasionAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr } from "../data/ability";
|
||||
import PokemonData from "../system/pokemon-data";
|
||||
import { BattlerIndex } from "../battle";
|
||||
import { Mode } from "../ui/ui";
|
||||
@ -1251,19 +1251,39 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return multiplier;
|
||||
}
|
||||
|
||||
getMatchupScore(pokemon: Pokemon): number {
|
||||
/**
|
||||
* Computes the given Pokemon's matchup score against this Pokemon.
|
||||
* In most cases, this score ranges from near-zero to 16, but the maximum possible matchup score is 64.
|
||||
* @param opponent {@linkcode Pokemon} The Pokemon to compare this Pokemon against
|
||||
* @returns A score value based on how favorable this Pokemon is when fighting the given Pokemon
|
||||
*/
|
||||
getMatchupScore(opponent: Pokemon): number {
|
||||
const types = this.getTypes(true);
|
||||
const enemyTypes = pokemon.getTypes(true, true);
|
||||
const outspeed = (this.isActive(true) ? this.getBattleStat(Stat.SPD, pokemon) : this.getStat(Stat.SPD)) <= pokemon.getBattleStat(Stat.SPD, this);
|
||||
let atkScore = pokemon.getAttackTypeEffectiveness(types[0], this) * (outspeed ? 1.25 : 1);
|
||||
let defScore = 1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[0], pokemon), 0.25);
|
||||
const enemyTypes = opponent.getTypes(true, true);
|
||||
/** Is this Pokemon faster than the opponent? */
|
||||
const outspeed = (this.isActive(true) ? this.getBattleStat(Stat.SPD, opponent) : this.getStat(Stat.SPD)) >= opponent.getBattleStat(Stat.SPD, this);
|
||||
/**
|
||||
* Based on how effective this Pokemon's types are offensively against the opponent's types.
|
||||
* This score is increased by 25 percent if this Pokemon is faster than the opponent.
|
||||
*/
|
||||
let atkScore = opponent.getAttackTypeEffectiveness(types[0], this) * (outspeed ? 1.25 : 1);
|
||||
/**
|
||||
* Based on how effectively this Pokemon defends against the opponent's types.
|
||||
* This score cannot be higher than 4.
|
||||
*/
|
||||
let defScore = 1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[0], opponent), 0.25);
|
||||
if (types.length > 1) {
|
||||
atkScore *= pokemon.getAttackTypeEffectiveness(types[1], this);
|
||||
atkScore *= opponent.getAttackTypeEffectiveness(types[1], this);
|
||||
}
|
||||
if (enemyTypes.length > 1) {
|
||||
defScore *= (1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[1], pokemon), 0.25));
|
||||
defScore *= (1 / Math.max(this.getAttackTypeEffectiveness(enemyTypes[1], opponent), 0.25));
|
||||
}
|
||||
let hpDiffRatio = this.getHpRatio() + (1 - pokemon.getHpRatio());
|
||||
/**
|
||||
* Based on this Pokemon's HP ratio compared to that of the opponent.
|
||||
* This ratio is multiplied by 1.5 if this Pokemon outspeeds the opponent;
|
||||
* however, the final ratio cannot be higher than 1.
|
||||
*/
|
||||
let hpDiffRatio = this.getHpRatio() + (1 - opponent.getHpRatio());
|
||||
if (outspeed) {
|
||||
hpDiffRatio = Math.min(hpDiffRatio * 1.5, 1);
|
||||
}
|
||||
@ -1389,8 +1409,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
return false;
|
||||
}
|
||||
|
||||
const rand1 = Utils.binToDec(Utils.decToBin(this.id).substring(0, 16));
|
||||
const rand2 = Utils.binToDec(Utils.decToBin(this.id).substring(16, 32));
|
||||
const rand1 = (this.id & 0xFFFF0000) >>> 16;
|
||||
const rand2 = (this.id & 0x0000FFFF);
|
||||
|
||||
const E = this.scene.gameData.trainerId ^ this.scene.gameData.secretId;
|
||||
const F = rand1 ^ rand2;
|
||||
@ -2243,7 +2263,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
const newTag = getBattlerTag(tagType, turnCount, sourceMove, sourceId);
|
||||
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
applyPreApplyBattlerTagAbAttrs(PreApplyBattlerTagAbAttr, this, newTag, cancelled);
|
||||
applyPreApplyBattlerTagAbAttrs(BattlerTagImmunityAbAttr, this, newTag, cancelled);
|
||||
|
||||
const userField = this.getAlliedField();
|
||||
userField.forEach(pokemon => applyPreApplyBattlerTagAbAttrs(UserFieldBattlerTagImmunityAbAttr, pokemon, newTag, cancelled));
|
||||
@ -3665,7 +3685,13 @@ export class EnemyPokemon extends Pokemon {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the move this Pokemon will use on the next turn, as well as
|
||||
* the Pokemon the move will target.
|
||||
* @returns this Pokemon's next move in the format {move, moveTargets}
|
||||
*/
|
||||
getNextMove(): QueuedMove {
|
||||
// If this Pokemon has a move already queued, return it.
|
||||
const queuedMove = this.getMoveQueue().length
|
||||
? this.getMoveset().find(m => m.moveId === this.getMoveQueue()[0].move)
|
||||
: null;
|
||||
@ -3678,11 +3704,15 @@ export class EnemyPokemon extends Pokemon {
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out any moves this Pokemon cannot use
|
||||
const movePool = this.getMoveset().filter(m => m.isUsable(this));
|
||||
// If no moves are left, use Struggle. Otherwise, continue with move selection
|
||||
if (movePool.length) {
|
||||
// If there's only 1 move in the move pool, use it.
|
||||
if (movePool.length === 1) {
|
||||
return { move: movePool[0].moveId, targets: this.getNextTargets(movePool[0].moveId) };
|
||||
}
|
||||
// If a move is forced because of Encore, use it.
|
||||
const encoreTag = this.getTag(EncoreTag) as EncoreTag;
|
||||
if (encoreTag) {
|
||||
const encoreMove = movePool.find(m => m.moveId === encoreTag.moveId);
|
||||
@ -3691,11 +3721,16 @@ export class EnemyPokemon extends Pokemon {
|
||||
}
|
||||
}
|
||||
switch (this.aiType) {
|
||||
case AiType.RANDOM:
|
||||
case AiType.RANDOM: // No enemy should spawn with this AI type in-game
|
||||
const moveId = movePool[this.scene.randBattleSeedInt(movePool.length)].moveId;
|
||||
return { move: moveId, targets: this.getNextTargets(moveId) };
|
||||
case AiType.SMART_RANDOM:
|
||||
case AiType.SMART:
|
||||
/**
|
||||
* Move selection is based on the move's calculated "benefit score" against the
|
||||
* best possible target(s) (as determined by {@linkcode getNextTargets}).
|
||||
* For more information on how benefit scores are calculated, see `docs/enemy-ai.md`.
|
||||
*/
|
||||
const moveScores = movePool.map(() => 0);
|
||||
const moveTargets = Object.fromEntries(movePool.map(m => [ m.moveId, this.getNextTargets(m.moveId) ]));
|
||||
for (const m in movePool) {
|
||||
@ -3712,14 +3747,26 @@ export class EnemyPokemon extends Pokemon {
|
||||
}
|
||||
|
||||
const target = this.scene.getField()[mt];
|
||||
/**
|
||||
* The "target score" of a move is given by the move's user benefit score + the move's target benefit score.
|
||||
* If the target is an ally, the target benefit score is multiplied by -1.
|
||||
*/
|
||||
let targetScore = move.getUserBenefitScore(this, target, move) + move.getTargetBenefitScore(this, target, move) * (mt < BattlerIndex.ENEMY === this.isPlayer() ? 1 : -1);
|
||||
if (Number.isNaN(targetScore)) {
|
||||
console.error(`Move ${move.name} returned score of NaN`);
|
||||
targetScore = 0;
|
||||
}
|
||||
/**
|
||||
* If this move is unimplemented, or the move is known to fail when used, set its
|
||||
* target score to -20
|
||||
*/
|
||||
if ((move.name.endsWith(" (N)") || !move.applyConditions(this, target, move)) && ![Moves.SUCKER_PUNCH, Moves.UPPER_HAND, Moves.THUNDERCLAP].includes(move.id)) {
|
||||
targetScore = -20;
|
||||
} else if (move instanceof AttackMove) {
|
||||
/**
|
||||
* Attack moves are given extra multipliers to their base benefit score based on
|
||||
* the move's type effectiveness against the target and whether the move is a STAB move.
|
||||
*/
|
||||
const effectiveness = target.getAttackMoveEffectiveness(this, pokemonMove);
|
||||
if (target.isPlayer() !== this.isPlayer()) {
|
||||
targetScore *= effectiveness;
|
||||
@ -3732,13 +3779,14 @@ export class EnemyPokemon extends Pokemon {
|
||||
targetScore /= 1.5;
|
||||
}
|
||||
}
|
||||
/** If a move has a base benefit score of 0, its benefit score is assumed to be unimplemented at this point */
|
||||
if (!targetScore) {
|
||||
targetScore = -20;
|
||||
}
|
||||
}
|
||||
targetScores.push(targetScore);
|
||||
}
|
||||
|
||||
// When a move has multiple targets, its score is equal to the maximum target score across all targets
|
||||
moveScore += Math.max(...targetScores);
|
||||
|
||||
// could make smarter by checking opponent def/spdef
|
||||
@ -3747,6 +3795,7 @@ export class EnemyPokemon extends Pokemon {
|
||||
|
||||
console.log(moveScores);
|
||||
|
||||
// Sort the move pool in decreasing order of move score
|
||||
const sortedMovePool = movePool.slice(0);
|
||||
sortedMovePool.sort((a, b) => {
|
||||
const scoreA = moveScores[movePool.indexOf(a)];
|
||||
@ -3755,10 +3804,12 @@ export class EnemyPokemon extends Pokemon {
|
||||
});
|
||||
let r = 0;
|
||||
if (this.aiType === AiType.SMART_RANDOM) {
|
||||
// Has a 5/8 chance to select the best move, and a 3/8 chance to advance to the next best move (and repeat this roll)
|
||||
while (r < sortedMovePool.length - 1 && this.scene.randBattleSeedInt(8) >= 5) {
|
||||
r++;
|
||||
}
|
||||
} else if (this.aiType === AiType.SMART) {
|
||||
// The chance to advance to the next best move increases when the compared moves' scores are closer to each other.
|
||||
while (r < sortedMovePool.length - 1 && (moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) >= 0
|
||||
&& this.scene.randBattleSeedInt(100) < Math.round((moveScores[movePool.indexOf(sortedMovePool[r + 1])] / moveScores[movePool.indexOf(sortedMovePool[r])]) * 50)) {
|
||||
r++;
|
||||
@ -3772,15 +3823,25 @@ export class EnemyPokemon extends Pokemon {
|
||||
return { move: Moves.STRUGGLE, targets: this.getNextTargets(Moves.STRUGGLE) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the Pokemon the given move would target if used by this Pokemon
|
||||
* @param moveId {@linkcode Moves} The move to be used
|
||||
* @returns The indexes of the Pokemon the given move would target
|
||||
*/
|
||||
getNextTargets(moveId: Moves): BattlerIndex[] {
|
||||
const moveTargets = getMoveTargets(this, moveId);
|
||||
const targets = this.scene.getField(true).filter(p => moveTargets.targets.indexOf(p.getBattlerIndex()) > -1);
|
||||
// If the move is multi-target, return all targets' indexes
|
||||
if (moveTargets.multiple) {
|
||||
return targets.map(p => p.getBattlerIndex());
|
||||
}
|
||||
|
||||
const move = allMoves[moveId];
|
||||
|
||||
/**
|
||||
* Get the move's target benefit score against each potential target.
|
||||
* For allies, this score is multiplied by -1.
|
||||
*/
|
||||
const benefitScores = targets
|
||||
.map(p => [ p.getBattlerIndex(), move.getTargetBenefitScore(this, p, move) * (p.isPlayer() === this.isPlayer() ? 1 : -1) ]);
|
||||
|
||||
@ -3804,12 +3865,14 @@ export class EnemyPokemon extends Pokemon {
|
||||
let targetWeights = sortedBenefitScores.map(s => s[1]);
|
||||
const lowestWeight = targetWeights[targetWeights.length - 1];
|
||||
|
||||
// If the lowest target weight (i.e. benefit score) is negative, add abs(lowestWeight) to all target weights
|
||||
if (lowestWeight < 1) {
|
||||
for (let w = 0; w < targetWeights.length; w++) {
|
||||
targetWeights[w] += Math.abs(lowestWeight - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any targets whose weights are less than half the max of the target weights from consideration
|
||||
const benefitCutoffIndex = targetWeights.findIndex(s => s < targetWeights[0] / 2);
|
||||
if (benefitCutoffIndex > -1) {
|
||||
targetWeights = targetWeights.slice(0, benefitCutoffIndex);
|
||||
@ -3824,6 +3887,11 @@ export class EnemyPokemon extends Pokemon {
|
||||
return total;
|
||||
}, 0);
|
||||
|
||||
/**
|
||||
* Generate a random number from 0 to (totalWeight-1),
|
||||
* then select the first target whose cumulative weight (with all previous targets' weights)
|
||||
* is greater than that random number.
|
||||
*/
|
||||
const randValue = this.scene.randBattleSeedInt(totalWeight);
|
||||
let targetIndex: integer;
|
||||
|
||||
|
@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = {
|
||||
"postSummonSwordOfRuin": "Unheilsschwert von {{pokemonNameWithAffix}} schwächt {{statName}} aller Pokémon im Umkreis!",
|
||||
"postSummonTabletsOfRuin": "Unheilstafeln von {{pokemonNameWithAffix}} schwächt {{statName}} aller Pokémon im Umkreis!",
|
||||
"postSummonBeadsOfRuin": "Unheilsjuwelen von {{pokemonNameWithAffix}} schwächt {{statName}} aller Pokémon im Umkreis!",
|
||||
"preventBerryUse": "{{pokemonNameWithAffix}} kriegt vor Anspannung keine Beeren mehr runter!",
|
||||
} as const;
|
||||
|
@ -36,7 +36,7 @@ import { move } from "./move";
|
||||
import { nature } from "./nature";
|
||||
import { pokeball } from "./pokeball";
|
||||
import { pokemon } from "./pokemon";
|
||||
import { pokemonForm } from "./pokemon-form";
|
||||
import { pokemonForm, battlePokemonForm } from "./pokemon-form";
|
||||
import { fusionAffixes } from "./pokemon-fusion-affixes";
|
||||
import { pokemonInfo } from "./pokemon-info";
|
||||
import { pokemonInfoContainer } from "./pokemon-info-container";
|
||||
@ -63,6 +63,7 @@ export const deConfig = {
|
||||
battle: battle,
|
||||
battleInfo: battleInfo,
|
||||
battleMessageUiHandler: battleMessageUiHandler,
|
||||
battlePokemonForm: battlePokemonForm,
|
||||
battlerTags: battlerTags,
|
||||
berry: berry,
|
||||
bgmName: bgmName,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { SimpleTranslationEntries } from "#app/interfaces/locales";
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
export const battlePokemonForm: SimpleTranslationEntries = {
|
||||
// Battle forms
|
||||
"mega": "Mega-{{pokemonName}}",
|
||||
"mega-x": "Mega-{{pokemonName}} X",
|
||||
@ -9,6 +9,14 @@ export const pokemonForm: SimpleTranslationEntries = {
|
||||
"gigantamax": "G-Dyna-{{pokemonName}}",
|
||||
"eternamax": "U-Dyna-{{pokemonName}}",
|
||||
|
||||
"megaChange": "{{preName}} hat sich zu {{pokemonName}} mega-entwickelt!",
|
||||
"gigantamaxChange": "{{preName}} hat sich zu {{pokemonName}} gigadynamaximiert!",
|
||||
"eternamaxChange": "{{preName}} hat sich zu {{pokemonName}} unendynamaximiert!",
|
||||
"revertChange": "{{pokemonName}} hat seine ursprüngliche Form zurückerlangt!",
|
||||
"formChange": "{{preName}} hat seine Form geändert!",
|
||||
} as const;
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Starters forms
|
||||
// 1G
|
||||
"pikachuCosplay": "Cosplay",
|
||||
|
@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = {
|
||||
"postSummonSwordOfRuin": "{{pokemonNameWithAffix}}'s Sword of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
|
||||
"postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}'s Tablets of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
|
||||
"postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}'s Beads of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
|
||||
"preventBerryUse": "{{pokemonNameWithAffix}} is too\nnervous to eat berries!",
|
||||
} as const;
|
||||
|
@ -39,7 +39,7 @@ import { nature } from "./nature";
|
||||
import { partyUiHandler } from "./party-ui-handler";
|
||||
import { pokeball } from "./pokeball";
|
||||
import { pokemon } from "./pokemon";
|
||||
import { pokemonForm } from "./pokemon-form";
|
||||
import { pokemonForm, battlePokemonForm } from "./pokemon-form";
|
||||
import { fusionAffixes } from "./pokemon-fusion-affixes";
|
||||
import { pokemonInfo } from "./pokemon-info";
|
||||
import { pokemonInfoContainer } from "./pokemon-info-container";
|
||||
@ -63,6 +63,7 @@ export const enConfig = {
|
||||
battle: battle,
|
||||
battleInfo: battleInfo,
|
||||
battleMessageUiHandler: battleMessageUiHandler,
|
||||
battlePokemonForm: battlePokemonForm,
|
||||
battlerTags: battlerTags,
|
||||
berry: berry,
|
||||
bgmName: bgmName,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { SimpleTranslationEntries } from "#app/interfaces/locales";
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Battle forms
|
||||
export const battlePokemonForm: SimpleTranslationEntries = {
|
||||
"mega": "Mega {{pokemonName}}",
|
||||
"mega-x": "Mega {{pokemonName}} X",
|
||||
"mega-y": "Mega {{pokemonName}} Y",
|
||||
@ -9,6 +8,14 @@ export const pokemonForm: SimpleTranslationEntries = {
|
||||
"gigantamax": "G-Max {{pokemonName}}",
|
||||
"eternamax": "E-Max {{pokemonName}}",
|
||||
|
||||
"megaChange": "{{preName}} Mega Evolved\ninto {{pokemonName}}!",
|
||||
"gigantamaxChange": "{{preName}} Gigantamaxed\ninto {{pokemonName}}!",
|
||||
"eternamaxChange": "{{preName}} Eternamaxed\ninto {{pokemonName}}!",
|
||||
"revertChange": "{{pokemonName}} reverted\nto its original form!",
|
||||
"formChange": "{{preName}} changed form!",
|
||||
} as const;
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Starters forms
|
||||
// 1G
|
||||
"pikachuCosplay": "Cosplay",
|
||||
|
@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = {
|
||||
"postSummonSwordOfRuin": "{{pokemonNameWithAffix}}'s Sword of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
|
||||
"postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}'s Tablets of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
|
||||
"postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}'s Beads of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
|
||||
"preventBerryUse": "{{pokemonNameWithAffix}} está muy nervioso y no puede comer bayas!",
|
||||
} as const;
|
||||
|
@ -36,7 +36,7 @@ import { move } from "./move";
|
||||
import { nature } from "./nature";
|
||||
import { pokeball } from "./pokeball";
|
||||
import { pokemon } from "./pokemon";
|
||||
import { pokemonForm } from "./pokemon-form";
|
||||
import { pokemonForm, battlePokemonForm } from "./pokemon-form";
|
||||
import { fusionAffixes } from "./pokemon-fusion-affixes";
|
||||
import { pokemonInfo } from "./pokemon-info";
|
||||
import { pokemonInfoContainer } from "./pokemon-info-container";
|
||||
@ -63,6 +63,7 @@ export const esConfig = {
|
||||
battle: battle,
|
||||
battleInfo: battleInfo,
|
||||
battleMessageUiHandler: battleMessageUiHandler,
|
||||
battlePokemonForm: battlePokemonForm,
|
||||
battlerTags: battlerTags,
|
||||
berry: berry,
|
||||
bgmName: bgmName,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { SimpleTranslationEntries } from "#app/interfaces/locales";
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Battle forms
|
||||
export const battlePokemonForm: SimpleTranslationEntries = {
|
||||
"mega": "Mega {{pokemonName}}",
|
||||
"mega-x": "Mega {{pokemonName}} X",
|
||||
"mega-y": "Mega {{pokemonName}} Y",
|
||||
@ -9,6 +8,14 @@ export const pokemonForm: SimpleTranslationEntries = {
|
||||
"gigantamax": "G-Max {{pokemonName}}",
|
||||
"eternamax": "E-Max {{pokemonName}}",
|
||||
|
||||
"megaChange": "{{preName}} Mega Evolved\ninto {{pokemonName}}!",
|
||||
"gigantamaxChange": "{{preName}} Gigantamaxed\ninto {{pokemonName}}!",
|
||||
"eternamaxChange": "{{preName}} Eternamaxed\ninto {{pokemonName}}!",
|
||||
"revertChange": "{{pokemonName}} reverted\nto its original form!",
|
||||
"formChange": "{{preName}} changed form!",
|
||||
} as const;
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Starters forms
|
||||
// 1G
|
||||
"pikachuCosplay": "Coqueta",
|
||||
|
@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = {
|
||||
"postSummonSwordOfRuin": "L’Épée du Fléau de {{pokemonNameWithAffix}}\naffaiblit la {{statName}} des Pokémon alentour !",
|
||||
"postSummonTabletsOfRuin": "Le Bois du Fléau de {{pokemonNameWithAffix}}\naffaiblit l’{{statName}} des Pokémon alentour !",
|
||||
"postSummonBeadsOfRuin": "Les Perles du Fléau de {{pokemonNameWithAffix}}\naffaiblissent la {{statName}} des Pokémon alentour !",
|
||||
"preventBerryUse": "{{pokemonNameWithAffix}} est tendu\net ne peut plus manger de Baies !",
|
||||
} as const;
|
||||
|
@ -22,7 +22,7 @@ export const PGMachv: AchievementTranslationEntries = {
|
||||
name: "Banquier",
|
||||
},
|
||||
"10M_MONEY": {
|
||||
name: "Évadé·e fiscal·e",
|
||||
name: "Évadé fiscal",
|
||||
},
|
||||
|
||||
"DamageAchv": {
|
||||
@ -45,7 +45,7 @@ export const PGMachv: AchievementTranslationEntries = {
|
||||
description: "Soigner {{healAmount}} {{HP}} en une fois avec une capacité, un talent ou un objet tenu",
|
||||
},
|
||||
"250_HEAL": {
|
||||
name: "Infirmier·ère",
|
||||
name: "Infirmier",
|
||||
},
|
||||
"1000_HEAL": {
|
||||
name: "Médecin",
|
||||
@ -74,19 +74,19 @@ export const PGMachv: AchievementTranslationEntries = {
|
||||
description: "Accumuler un total de {{ribbonAmount}} Rubans",
|
||||
},
|
||||
"10_RIBBONS": {
|
||||
name: "Maitre·sse de la Ligue",
|
||||
name: "Maitre de la Ligue",
|
||||
},
|
||||
"25_RIBBONS": {
|
||||
name: "Super Maitre·sse de la Ligue",
|
||||
name: "Super Maitre de la Ligue",
|
||||
},
|
||||
"50_RIBBONS": {
|
||||
name: "Hyper Maitre·sse de la Ligue",
|
||||
name: "Hyper Maitre de la Ligue",
|
||||
},
|
||||
"75_RIBBONS": {
|
||||
name: "Rogue Maitre·sse de la Ligue",
|
||||
name: "Rogue Maitre de la Ligue",
|
||||
},
|
||||
"100_RIBBONS": {
|
||||
name: "Master Maitre·sse de la Ligue",
|
||||
name: "Master Maitre de la Ligue",
|
||||
},
|
||||
|
||||
"TRANSFER_MAX_BATTLE_STAT": {
|
||||
@ -166,7 +166,7 @@ export const PGMachv: AchievementTranslationEntries = {
|
||||
description: "Avoir des IV parfaits sur un Pokémon",
|
||||
},
|
||||
"CLASSIC_VICTORY": {
|
||||
name: "Invaincu·e",
|
||||
name: "Invaincu",
|
||||
description: "Terminer le jeu en mode classique",
|
||||
},
|
||||
|
||||
@ -267,4 +267,267 @@ export const PGMachv: AchievementTranslationEntries = {
|
||||
} as const;
|
||||
|
||||
// Achievement translations for the when the player character is female (it for now uses the same translations as the male version)
|
||||
export const PGFachv: AchievementTranslationEntries = PGMachv;
|
||||
export const PGFachv: AchievementTranslationEntries = {
|
||||
"Achievements": {
|
||||
name: "Succès",
|
||||
},
|
||||
"Locked": {
|
||||
name: "Verrouillé",
|
||||
},
|
||||
|
||||
"MoneyAchv": {
|
||||
description: "Récolter un total de {{moneyAmount}} ₽",
|
||||
},
|
||||
"10K_MONEY": {
|
||||
name: "Épargnante",
|
||||
},
|
||||
"100K_MONEY": {
|
||||
name: "Je possède des thunes",
|
||||
},
|
||||
"1M_MONEY": {
|
||||
name: "Banquière",
|
||||
},
|
||||
"10M_MONEY": {
|
||||
name: "Évadée fiscale",
|
||||
},
|
||||
|
||||
"DamageAchv": {
|
||||
description: "Infliger {{damageAmount}} de dégâts en un coup",
|
||||
},
|
||||
"250_DMG": {
|
||||
name: "Caïd",
|
||||
},
|
||||
"1000_DMG": {
|
||||
name: "Boxeuse",
|
||||
},
|
||||
"2500_DMG": {
|
||||
name: "Distributrice de pains",
|
||||
},
|
||||
"10000_DMG": {
|
||||
name: "One Punch Woman",
|
||||
},
|
||||
|
||||
"HealAchv": {
|
||||
description: "Soigner {{healAmount}} {{HP}} en une fois avec une capacité, un talent ou un objet tenu",
|
||||
},
|
||||
"250_HEAL": {
|
||||
name: "Infirmière",
|
||||
},
|
||||
"1000_HEAL": {
|
||||
name: "Médecin",
|
||||
},
|
||||
"2500_HEAL": {
|
||||
name: "Clerc",
|
||||
},
|
||||
"10000_HEAL": {
|
||||
name: "Centre Pokémon",
|
||||
},
|
||||
|
||||
"LevelAchv": {
|
||||
description: "Monter un Pokémon au N.{{level}}",
|
||||
},
|
||||
"LV_100": {
|
||||
name: "Et c’est pas fini !",
|
||||
},
|
||||
"LV_250": {
|
||||
name: "Élite",
|
||||
},
|
||||
"LV_1000": {
|
||||
name: "Vers l’infini et au-delà",
|
||||
},
|
||||
|
||||
"RibbonAchv": {
|
||||
description: "Accumuler un total de {{ribbonAmount}} Rubans",
|
||||
},
|
||||
"10_RIBBONS": {
|
||||
name: "Maitresse de la Ligue",
|
||||
},
|
||||
"25_RIBBONS": {
|
||||
name: "Super Maitresse de la Ligue",
|
||||
},
|
||||
"50_RIBBONS": {
|
||||
name: "Hyper Maitresse de la Ligue",
|
||||
},
|
||||
"75_RIBBONS": {
|
||||
name: "Rogue Maitresse de la Ligue",
|
||||
},
|
||||
"100_RIBBONS": {
|
||||
name: "Master Maitresse de la Ligue",
|
||||
},
|
||||
|
||||
"TRANSFER_MAX_BATTLE_STAT": {
|
||||
name: "Travail d’équipe",
|
||||
description: "Utiliser Relais avec au moins une statistique montée à fond",
|
||||
},
|
||||
"MAX_FRIENDSHIP": {
|
||||
name: "Copinage",
|
||||
description: "Atteindre le niveau de bonheur maximal avec un Pokémon",
|
||||
},
|
||||
"MEGA_EVOLVE": {
|
||||
name: "Mégamorph",
|
||||
description: "Méga-évoluer un Pokémon",
|
||||
},
|
||||
"GIGANTAMAX": {
|
||||
name: "Kaijū",
|
||||
description: "Gigamaxer un Pokémon",
|
||||
},
|
||||
"TERASTALLIZE": {
|
||||
name: "J’aime les STAB",
|
||||
description: "Téracristalliser un Pokémon",
|
||||
},
|
||||
"STELLAR_TERASTALLIZE": {
|
||||
name: "Le type enfoui",
|
||||
description: "Téracristalliser un Pokémon en type Stellaire",
|
||||
},
|
||||
"SPLICE": {
|
||||
name: "Infinite Fusion",
|
||||
description: "Fusionner deux Pokémon avec le Pointeau ADN",
|
||||
},
|
||||
"MINI_BLACK_HOLE": {
|
||||
name: "Item-stellar",
|
||||
description: "Obtenir un Mini Trou Noir",
|
||||
},
|
||||
"CATCH_MYTHICAL": {
|
||||
name: "Fabuleux",
|
||||
description: "Capturer un Pokémon fabuleux",
|
||||
},
|
||||
"CATCH_SUB_LEGENDARY": {
|
||||
name: "(Semi-)Légendaire",
|
||||
description: "Capturer un Pokémon semi-légendaire",
|
||||
},
|
||||
"CATCH_LEGENDARY": {
|
||||
name: "Légendaire",
|
||||
description: "Capturer un Pokémon légendaire",
|
||||
},
|
||||
"SEE_SHINY": {
|
||||
name: "Chromatique",
|
||||
description: "Trouver un Pokémon sauvage chromatique",
|
||||
},
|
||||
"SHINY_PARTY": {
|
||||
name: "Shasseuse",
|
||||
description: "Avoir une équipe exclusivement composée de Pokémon chromatiques",
|
||||
},
|
||||
"HATCH_MYTHICAL": {
|
||||
name: "Œuf fabuleux",
|
||||
description: "Obtenir un Pokémon fabuleux dans un Œuf",
|
||||
},
|
||||
"HATCH_SUB_LEGENDARY": {
|
||||
name: "Œuf semi-légendaire",
|
||||
description: "Obtenir un Pokémon semi-légendaire dans un Œuf",
|
||||
},
|
||||
"HATCH_LEGENDARY": {
|
||||
name: "Œuf légendaire",
|
||||
description: "Obtenir un Pokémon légendaire dans un Œuf",
|
||||
},
|
||||
"HATCH_SHINY": {
|
||||
name: "Œuf chromatique",
|
||||
description: "Obtenir un Pokémon chromatique dans un Œuf",
|
||||
},
|
||||
"HIDDEN_ABILITY": {
|
||||
name: "Potentiel enfoui",
|
||||
description: "Capturer un Pokémon possédant un talent caché",
|
||||
},
|
||||
"PERFECT_IVS": {
|
||||
name: "Certificat d’authenticité",
|
||||
description: "Avoir des IV parfaits sur un Pokémon",
|
||||
},
|
||||
"CLASSIC_VICTORY": {
|
||||
name: "Invaincue",
|
||||
description: "Terminer le jeu en mode classique",
|
||||
},
|
||||
|
||||
"MONO_GEN_ONE": {
|
||||
name: "Le rival originel",
|
||||
description: "Terminer un challenge avec uniquement des Pokémon de 1re génération.",
|
||||
},
|
||||
"MONO_GEN_TWO": {
|
||||
name: "Entre tradition et modernité",
|
||||
description: "Terminer un challenge avec uniquement des Pokémon de 2e génération.",
|
||||
},
|
||||
"MONO_GEN_THREE": {
|
||||
name: "Too much water ?",
|
||||
description: "Terminer un challenge avec uniquement des Pokémon de 3e génération.",
|
||||
},
|
||||
"MONO_GEN_FOUR": {
|
||||
name: "Réellement la plus difficile ?",
|
||||
description: "Terminer un challenge avec uniquement des Pokémon de 4e génération.",
|
||||
},
|
||||
"MONO_GEN_FIVE": {
|
||||
name: "Recast complet",
|
||||
description: "Terminer un challenge avec uniquement des Pokémon de 5e génération.",
|
||||
},
|
||||
"MONO_GEN_SIX": {
|
||||
name: "Aristocrate",
|
||||
description: "Terminer un challenge avec uniquement des Pokémon de 6e génération.",
|
||||
},
|
||||
"MONO_GEN_SEVEN": {
|
||||
name: "Seulement techniquement",
|
||||
description: "Terminer un challenge avec uniquement des Pokémon de 7e génération.",
|
||||
},
|
||||
"MONO_GEN_EIGHT": {
|
||||
name: "L’heure de gloire",
|
||||
description: "Terminer un challenge avec uniquement des Pokémon de 8e génération.",
|
||||
},
|
||||
"MONO_GEN_NINE": {
|
||||
name: "Ça va, c’était EZ",
|
||||
description: "Terminer un challenge avec uniquement des Pokémon de 9e génération.",
|
||||
},
|
||||
|
||||
"MonoType": {
|
||||
description: "Terminer un challenge en monotype {{type}}.",
|
||||
},
|
||||
"MONO_NORMAL": {
|
||||
name: "Extraordinairement banal",
|
||||
},
|
||||
"MONO_FIGHTING": {
|
||||
name: "Je connais le kung-fu",
|
||||
},
|
||||
"MONO_FLYING": {
|
||||
name: "Angry Birds",
|
||||
},
|
||||
"MONO_POISON": {
|
||||
name: "Touche moi je t’empoisonne !",
|
||||
},
|
||||
"MONO_GROUND": {
|
||||
name: "Prévisions : Séisme",
|
||||
},
|
||||
"MONO_ROCK": {
|
||||
name: "Comme un roc",
|
||||
},
|
||||
"MONO_BUG": {
|
||||
name: "Une chenille !",
|
||||
},
|
||||
"MONO_GHOST": {
|
||||
name: "SOS Fantômes",
|
||||
},
|
||||
"MONO_STEEL": {
|
||||
name: "De type Acier !",
|
||||
},
|
||||
"MONO_FIRE": {
|
||||
name: "Allumer le feu",
|
||||
},
|
||||
"MONO_WATER": {
|
||||
name: "Vacances en Bretagne",
|
||||
},
|
||||
"MONO_GRASS": {
|
||||
name: "Ne pas toucher !",
|
||||
},
|
||||
"MONO_ELECTRIC": {
|
||||
name: "À la masse",
|
||||
},
|
||||
"MONO_PSYCHIC": {
|
||||
name: "Grocervo",
|
||||
},
|
||||
"MONO_ICE": {
|
||||
name: "Froid comme la glace",
|
||||
},
|
||||
"MONO_DRAGON": {
|
||||
name: "Légendes du club, ou presque",
|
||||
},
|
||||
"MONO_DARK": {
|
||||
name: "Ça va lui passer",
|
||||
},
|
||||
"MONO_FAIRY": {
|
||||
name: "Hey ! Listen !",
|
||||
},
|
||||
} as const;
|
||||
|
@ -36,7 +36,7 @@ import { move } from "./move";
|
||||
import { nature } from "./nature";
|
||||
import { pokeball } from "./pokeball";
|
||||
import { pokemon } from "./pokemon";
|
||||
import { pokemonForm } from "./pokemon-form";
|
||||
import { pokemonForm, battlePokemonForm } from "./pokemon-form";
|
||||
import { fusionAffixes } from "./pokemon-fusion-affixes";
|
||||
import { pokemonInfo } from "./pokemon-info";
|
||||
import { pokemonInfoContainer } from "./pokemon-info-container";
|
||||
@ -63,6 +63,7 @@ export const frConfig = {
|
||||
battle: battle,
|
||||
battleInfo: battleInfo,
|
||||
battleMessageUiHandler: battleMessageUiHandler,
|
||||
battlePokemonForm: battlePokemonForm,
|
||||
battlerTags: battlerTags,
|
||||
berry: berry,
|
||||
bgmName: bgmName,
|
||||
|
@ -18,10 +18,10 @@ export const menuUiHandler: SimpleTranslationEntries = {
|
||||
"exportSlotSelect": "Sélectionnez l’emplacement depuis lequel exporter les données.",
|
||||
"importData": "Importer données",
|
||||
"exportData": "Exporter données",
|
||||
"linkDiscord": "Link Discord",
|
||||
"unlinkDiscord": "Unlink Discord",
|
||||
"linkGoogle": "Link Google",
|
||||
"unlinkGoogle": "Unlink Google",
|
||||
"linkDiscord": "Lier à Discord",
|
||||
"unlinkDiscord": "Délier Discord",
|
||||
"linkGoogle": "Lier à Google",
|
||||
"unlinkGoogle": "Délier Google",
|
||||
"cancel": "Retour",
|
||||
"losingProgressionWarning": "Vous allez perdre votre progression depuis le début du combat. Continuer ?",
|
||||
"noEggs": "Vous ne faites actuellement\néclore aucun Œuf !"
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { SimpleTranslationEntries } from "#app/interfaces/locales";
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Battle forms
|
||||
export const battlePokemonForm: SimpleTranslationEntries = {
|
||||
"mega": "Méga-{{pokemonName}}",
|
||||
"mega-x": "Méga-{{pokemonName}} X",
|
||||
"mega-y": "Méga-{{pokemonName}} Y",
|
||||
@ -9,6 +8,14 @@ export const pokemonForm: SimpleTranslationEntries = {
|
||||
"gigantamax": "{{pokemonName}} Gigamax",
|
||||
"eternamax": "{{pokemonName}} Infinimax",
|
||||
|
||||
"megaChange": "{{preName}} méga-évolue\nen {{pokemonName}} !",
|
||||
"gigantamaxChange": "{{preName}} se gigamaxe\nen {{pokemonName}} !",
|
||||
"eternamaxChange": "{{preName}} devient\n{{pokemonName}} !",
|
||||
"revertChange": "{{pokemonName}} retourne\nà sa forme initiale !",
|
||||
"formChange": "{{preName}} change de forme !",
|
||||
} as const;
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Starters forms
|
||||
// 1G
|
||||
"pikachuCosplay": "Cosplayeur",
|
||||
|
@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = {
|
||||
"postSummonSwordOfRuin": "{{pokemonNameWithAffix}}'s Sword of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
|
||||
"postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}'s Tablets of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
|
||||
"postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}'s Beads of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
|
||||
"preventBerryUse": "{{pokemonNameWithAffix}} non riesce a\nmangiare le bacche per l'agitazione!",
|
||||
} as const;
|
||||
|
@ -36,7 +36,7 @@ import { move } from "./move";
|
||||
import { nature } from "./nature";
|
||||
import { pokeball } from "./pokeball";
|
||||
import { pokemon } from "./pokemon";
|
||||
import { pokemonForm } from "./pokemon-form";
|
||||
import { pokemonForm, battlePokemonForm } from "./pokemon-form";
|
||||
import { fusionAffixes } from "./pokemon-fusion-affixes";
|
||||
import { pokemonInfo } from "./pokemon-info";
|
||||
import { pokemonInfoContainer } from "./pokemon-info-container";
|
||||
@ -63,6 +63,7 @@ export const itConfig = {
|
||||
battle: battle,
|
||||
battleInfo: battleInfo,
|
||||
battleMessageUiHandler: battleMessageUiHandler,
|
||||
battlePokemonForm: battlePokemonForm,
|
||||
battlerTags: battlerTags,
|
||||
berry: berry,
|
||||
bgmName: bgmName,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { SimpleTranslationEntries } from "#app/interfaces/locales";
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Battle forms
|
||||
export const battlePokemonForm: SimpleTranslationEntries = {
|
||||
"mega": "Mega {{pokemonName}}",
|
||||
"mega-x": "Mega {{pokemonName}} X",
|
||||
"mega-y": "Mega {{pokemonName}} Y",
|
||||
@ -9,6 +8,14 @@ export const pokemonForm: SimpleTranslationEntries = {
|
||||
"gigantamax": "GigaMax {{pokemonName}}",
|
||||
"eternamax": "EternaMax {{pokemonName}}",
|
||||
|
||||
"megaChange": "{{preName}} si evolve\nin {{pokemonName}}!",
|
||||
"gigantamaxChange": "{{preName}} si Gigamaxxizza\nin {{pokemonName}}!",
|
||||
"eternamaxChange": "{{preName}} si Dynamaxxa infinitamente\nin {{pokemonName}}!",
|
||||
"revertChange": "{{pokemonName}} è tornato\nalla sua forma originaria!",
|
||||
"formChange": "{{preName}} ha cambiato forma!",
|
||||
} as const;
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Starters forms
|
||||
// 1G
|
||||
"pikachuCosplay": "Cosplay",
|
||||
|
@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = {
|
||||
"postSummonSwordOfRuin": "{{pokemonNameWithAffix}}의 재앙의검에 의해\n주위의 {{statName}}[[가]] 약해졌다!",
|
||||
"postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}의 재앙의목간에 의해\n주위의 {{statName}}[[가]] 약해졌다!",
|
||||
"postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}의 재앙의구슬에 의해\n주위의 {{statName}}[[가]] 약해졌다!",
|
||||
"preventBerryUse": "{{pokemonNameWithAffix}}[[는]] 긴장해서\n나무열매를 먹을 수 없게 되었다!",
|
||||
} as const;
|
||||
|
@ -1,6 +1,3 @@
|
||||
import { pokemonForm } from "./pokemon-form";
|
||||
// import { common } from "#app/locales/ko/common.js";
|
||||
// import { settings } from "#app/locales/ko/settings.js";
|
||||
import { ability } from "./ability";
|
||||
import { abilityTriggers } from "./ability-trigger";
|
||||
import { arenaFlyout } from "./arena-flyout";
|
||||
@ -37,9 +34,9 @@ import { modifier } from "./modifier";
|
||||
import { modifierType } from "./modifier-type";
|
||||
import { move } from "./move";
|
||||
import { nature } from "./nature";
|
||||
import { partyUiHandler } from "./party-ui-handler";
|
||||
import { pokeball } from "./pokeball";
|
||||
import { pokemon } from "./pokemon";
|
||||
import { pokemonForm, battlePokemonForm } from "./pokemon-form";
|
||||
import { fusionAffixes } from "./pokemon-fusion-affixes";
|
||||
import { pokemonInfo } from "./pokemon-info";
|
||||
import { pokemonInfoContainer } from "./pokemon-info-container";
|
||||
@ -52,6 +49,7 @@ import { titles, trainerClasses, trainerNames } from "./trainers";
|
||||
import { tutorial } from "./tutorial";
|
||||
import { voucher } from "./voucher";
|
||||
import { terrain, weather } from "./weather";
|
||||
import { partyUiHandler } from "./party-ui-handler";
|
||||
import { settings } from "./settings.js";
|
||||
import { common } from "./common.js";
|
||||
import { modifierSelectUiHandler } from "./modifier-select-ui-handler";
|
||||
@ -65,6 +63,7 @@ export const koConfig = {
|
||||
battle: battle,
|
||||
battleInfo: battleInfo,
|
||||
battleMessageUiHandler: battleMessageUiHandler,
|
||||
battlePokemonForm: battlePokemonForm,
|
||||
battlerTags: battlerTags,
|
||||
berry: berry,
|
||||
bgmName: bgmName,
|
||||
@ -94,7 +93,6 @@ export const koConfig = {
|
||||
modifierType: modifierType,
|
||||
move: move,
|
||||
nature: nature,
|
||||
partyUiHandler: partyUiHandler,
|
||||
pokeball: pokeball,
|
||||
pokemon: pokemon,
|
||||
pokemonForm: pokemonForm,
|
||||
@ -114,6 +112,7 @@ export const koConfig = {
|
||||
tutorial: tutorial,
|
||||
voucher: voucher,
|
||||
weather: weather,
|
||||
partyUiHandler: partyUiHandler,
|
||||
modifierSelectUiHandler: modifierSelectUiHandler,
|
||||
moveTriggers: moveTriggers
|
||||
};
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { SimpleTranslationEntries } from "#app/interfaces/locales";
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Battle forms
|
||||
export const battlePokemonForm: SimpleTranslationEntries = {
|
||||
"mega": "메가{{pokemonName}}",
|
||||
"mega-x": "메가{{pokemonName}}X",
|
||||
"mega-y": "메가{{pokemonName}}Y",
|
||||
@ -9,6 +8,14 @@ export const pokemonForm: SimpleTranslationEntries = {
|
||||
"gigantamax": "거다이맥스 {{pokemonName}}",
|
||||
"eternamax": "무한다이맥스 {{pokemonName}}",
|
||||
|
||||
"megaChange": "{{preName}}[[는]]\n{{pokemonName}}[[로]] 메가진화했다!",
|
||||
"gigantamaxChange": "{{preName}}[[는]]\n{{pokemonName}}가 되었다!",
|
||||
"eternamaxChange": "{{preName}}[[는]]\n{{pokemonName}}가 되었다!",
|
||||
"revertChange": "{{pokemonName}}[[는]]\n원래 모습으로 되돌아왔다!",
|
||||
"formChange": "{{preName}}[[는]]\n다른 모습으로 변화했다!",
|
||||
} as const;
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Starters forms
|
||||
// 1G
|
||||
"pikachuCosplay": "옷갈아입기",
|
||||
|
@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = {
|
||||
"postSummonSwordOfRuin": "Sword of Ruin de {{pokemonNameWithAffix}} reduziu a {{statName}}\nde todos os Pokémon em volta!",
|
||||
"postSummonTabletsOfRuin": "Tablets of Ruin de {{pokemonNameWithAffix}} reduziu o {{statName}}\nde todos os Pokémon em volta!",
|
||||
"postSummonBeadsOfRuin": "Beads of Ruin de {{pokemonNameWithAffix}} reduziu a {{statName}}\nde todos os Pokémon em volta!",
|
||||
"preventBerryUse": "{{pokemonNameWithAffix}} está nervoso\ndemais para comer frutas!",
|
||||
} as const;
|
||||
|
@ -1,50 +1,50 @@
|
||||
import { SimpleTranslationEntries } from "#app/interfaces/locales";
|
||||
|
||||
export const arenaTag: SimpleTranslationEntries = {
|
||||
"yourTeam": "your team",
|
||||
"opposingTeam": "the opposing team",
|
||||
"arenaOnRemove": "{{moveName}}'s effect wore off.",
|
||||
"arenaOnRemovePlayer": "{{moveName}}'s effect wore off\non your side.",
|
||||
"arenaOnRemoveEnemy": "{{moveName}}'s effect wore off\non the foe's side.",
|
||||
"mistOnAdd": "{{pokemonNameWithAffix}}'s team became\nshrouded in mist!",
|
||||
"mistApply": "The mist prevented\nthe lowering of stats!",
|
||||
"reflectOnAdd": "Reflect reduced the damage of physical moves.",
|
||||
"reflectOnAddPlayer": "Reflect reduced the damage of physical moves on your side.",
|
||||
"reflectOnAddEnemy": "Reflect reduced the damage of physical moves on the foe's side.",
|
||||
"lightScreenOnAdd": "Light Screen reduced the damage of special moves.",
|
||||
"lightScreenOnAddPlayer": "Light Screen reduced the damage of special moves on your side.",
|
||||
"lightScreenOnAddEnemy": "Light Screen reduced the damage of special moves on the foe's side.",
|
||||
"auroraVeilOnAdd": "Aurora Veil reduced the damage of moves.",
|
||||
"auroraVeilOnAddPlayer": "Aurora Veil reduced the damage of moves on your side.",
|
||||
"auroraVeilOnAddEnemy": "Aurora Veil reduced the damage of moves on the foe's side.",
|
||||
"conditionalProtectOnAdd": "{{moveName}} protected team!",
|
||||
"conditionalProtectOnAddPlayer": "{{moveName}} protected your team!",
|
||||
"conditionalProtectOnAddEnemy": "{{moveName}} protected the\nopposing team!",
|
||||
"conditionalProtectApply": "{{moveName}} protected {{pokemonNameWithAffix}}!",
|
||||
"matBlockOnAdd": "{{pokemonNameWithAffix}} intends to flip up a mat\nand block incoming attacks!",
|
||||
"wishTagOnAdd": "{{pokemonNameWithAffix}}'s wish\ncame true!",
|
||||
"mudSportOnAdd": "Electricity's power was weakened!",
|
||||
"mudSportOnRemove": "The effects of Mud Sport\nhave faded.",
|
||||
"waterSportOnAdd": "Fire's power was weakened!",
|
||||
"waterSportOnRemove": "The effects of Water Sport\nhave faded.",
|
||||
"spikesOnAdd": "{{moveName}} were scattered\nall around {{opponentDesc}}'s feet!",
|
||||
"spikesActivateTrap": "{{pokemonNameWithAffix}} is hurt\nby the spikes!",
|
||||
"toxicSpikesOnAdd": "{{moveName}} were scattered\nall around {{opponentDesc}}'s feet!",
|
||||
"toxicSpikesActivateTrapPoison": "{{pokemonNameWithAffix}} absorbed the {{moveName}}!",
|
||||
"stealthRockOnAdd": "Pointed stones float in the air\naround {{opponentDesc}}!",
|
||||
"stealthRockActivateTrap": "Pointed stones dug into\n{{pokemonNameWithAffix}}!",
|
||||
"stickyWebOnAdd": "A {{moveName}} has been laid out on the ground around the opposing team!",
|
||||
"stickyWebActivateTrap": "The opposing {{pokemonName}} was caught in a sticky web!",
|
||||
"trickRoomOnAdd": "{{pokemonNameWithAffix}} twisted\nthe dimensions!",
|
||||
"trickRoomOnRemove": "The twisted dimensions\nreturned to normal!",
|
||||
"gravityOnAdd": "Gravity intensified!",
|
||||
"gravityOnRemove": "Gravity returned to normal!",
|
||||
"tailwindOnAdd": "The Tailwind blew from behind team!",
|
||||
"tailwindOnAddPlayer": "The Tailwind blew from behind\nyour team!",
|
||||
"tailwindOnAddEnemy": "The Tailwind blew from behind\nthe opposing team!",
|
||||
"tailwindOnRemove": "Team's Tailwind petered out!",
|
||||
"tailwindOnRemovePlayer": "Your team's Tailwind petered out!",
|
||||
"tailwindOnRemoveEnemy": "The opposing team's Tailwind petered out!",
|
||||
"happyHourOnAdd": "Everyone is caught up in the happy atmosphere!",
|
||||
"happyHourOnRemove": "The atmosphere returned to normal.",
|
||||
"yourTeam": "sua equipe",
|
||||
"opposingTeam": "a equipe adversária",
|
||||
"arenaOnRemove": "O efeito de {{moveName}} acabou.",
|
||||
"arenaOnRemovePlayer": "O efeito de {{moveName}} acabou\nem sua equipe.",
|
||||
"arenaOnRemoveEnemy": "O efeito de {{moveName}} acabou\nna equipe adversária.",
|
||||
"mistOnAdd": "A equipe de {{pokemonNameWithAffix}} está protegida\npela névoa!",
|
||||
"mistApply": "A névoa previne\na diminuição de atributos!",
|
||||
"reflectOnAdd": "Reflect reduziu o dano de movimentos físicos.",
|
||||
"reflectOnAddPlayer": "Reflect reduziu o dano de movimentos físicos em sua equipe.",
|
||||
"reflectOnAddEnemy": "Reflect reduziu o dano de movimentos físicos na equipe adversária.",
|
||||
"lightScreenOnAdd": "Light Screen reduziu o dano de movimentos especiais.",
|
||||
"lightScreenOnAddPlayer": "Light Screen reduziu o dano de movimentos especiais em sua equipe.",
|
||||
"lightScreenOnAddEnemy": "Light Screen reduziu o dano de movimentos especiais na equipe adversária.",
|
||||
"auroraVeilOnAdd": "Aurora Veil reduziu o dano de movimentos.",
|
||||
"auroraVeilOnAddPlayer": "Aurora Veil reduziu o dano de movimentos em sua equipe.",
|
||||
"auroraVeilOnAddEnemy": "Aurora Veil reduziu o dano de movimentos na equipe adversária.",
|
||||
"conditionalProtectOnAdd": "{{moveName}} protegeu a equipe!",
|
||||
"conditionalProtectOnAddPlayer": "{{moveName}} protegeu sua equipe!",
|
||||
"conditionalProtectOnAddEnemy": "{{moveName}} protegeu a\nequipe adversária!",
|
||||
"conditionalProtectApply": "{{moveName}} protegeu {{pokemonNameWithAffix}}!",
|
||||
"matBlockOnAdd": "{{pokemonNameWithAffix}} pretende levantar um tapete\npara bloquear ataques!",
|
||||
"wishTagOnAdd": "O desejo de {{pokemonNameWithAffix}}\nfoi concedido!",
|
||||
"mudSportOnAdd": "O poder de movimentos elétricos foi enfraquecido!",
|
||||
"mudSportOnRemove": "Os efeitos de Mud Sport\nsumiram.",
|
||||
"waterSportOnAdd": "O poder de movimentos de fogo foi enfraquecido!",
|
||||
"waterSportOnRemove": "Os efeitos de Water Sport\nsumiram.",
|
||||
"spikesOnAdd": "{{moveName}} foram espalhados\nno chão ao redor de {{opponentDesc}}!",
|
||||
"spikesActivateTrap": "{{pokemonNameWithAffix}} foi ferido\npelos espinhos!",
|
||||
"toxicSpikesOnAdd": "{{moveName}} foram espalhados\nno chão ao redor de {{opponentDesc}}!",
|
||||
"toxicSpikesActivateTrapPoison": "{{pokemonNameWithAffix}} absorveu os {{moveName}}!",
|
||||
"stealthRockOnAdd": "Pedras pontiagudas estão flutuando\nao redor de {{opponentDesc}}!",
|
||||
"stealthRockActivateTrap": "{{pokemonNameWithAffix}} foi atingido\npor pedras pontiagudas!",
|
||||
"stickyWebOnAdd": "Uma {{moveName}} foi espalhada no chão ao redor da equipe adversária!",
|
||||
"stickyWebActivateTrap": "{{pokemonName}} adversário foi pego por uma Sticky Web!",
|
||||
"trickRoomOnAdd": "{{pokemonNameWithAffix}} distorceu\nas dimensões!",
|
||||
"trickRoomOnRemove": "As dimensões distorcidas\nretornaram ao normal!",
|
||||
"gravityOnAdd": "A gravidade aumentou!",
|
||||
"gravityOnRemove": "A gravidade retornou ao normal!",
|
||||
"tailwindOnAdd": "O Tailwind soprou de trás de sua equipe!",
|
||||
"tailwindOnAddPlayer": "O Tailwind soprou de trás de\nsua equipe!",
|
||||
"tailwindOnAddEnemy": "O Tailwind soprou de trás da\nequipe adversária!",
|
||||
"tailwindOnRemove": "O Tailwind de sua equipe acabou!",
|
||||
"tailwindOnRemovePlayer": "O Tailwind de sua equipe acabou!",
|
||||
"tailwindOnRemoveEnemy": "O Tailwind da equipe adversária acabou!",
|
||||
"happyHourOnAdd": "Todos foram envolvidos por uma atmosfera alegre!",
|
||||
"happyHourOnRemove": "A atmosfera retornou ao normal.",
|
||||
} as const;
|
||||
|
@ -39,7 +39,7 @@ import { nature } from "./nature";
|
||||
import { partyUiHandler } from "./party-ui-handler";
|
||||
import { pokeball } from "./pokeball";
|
||||
import { pokemon } from "./pokemon";
|
||||
import { pokemonForm } from "./pokemon-form";
|
||||
import { pokemonForm, battlePokemonForm } from "./pokemon-form";
|
||||
import { fusionAffixes } from "./pokemon-fusion-affixes";
|
||||
import { pokemonInfo } from "./pokemon-info";
|
||||
import { pokemonInfoContainer } from "./pokemon-info-container";
|
||||
@ -63,6 +63,7 @@ export const ptBrConfig = {
|
||||
battle: battle,
|
||||
battleInfo: battleInfo,
|
||||
battleMessageUiHandler: battleMessageUiHandler,
|
||||
battlePokemonForm: battlePokemonForm,
|
||||
battlerTags: battlerTags,
|
||||
berry: berry,
|
||||
bgmName: bgmName,
|
||||
|
@ -10,5 +10,5 @@ export const modifier: SimpleTranslationEntries = {
|
||||
"turnHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} foi absorvido(a)\npelo {{typeName}} de {{pokemonName}}!",
|
||||
"contactHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} foi pego(a)\npela {{typeName}} de {{pokemonName}}!",
|
||||
"enemyTurnHealApply": "{{pokemonNameWithAffix}}\nrestaurou um pouco de seus PS!",
|
||||
"bypassSpeedChanceApply": "{{pokemonName}} se move mais rápido que o normal graças a sua {{itemName}}!",
|
||||
"bypassSpeedChanceApply": "{{pokemonName}} se move mais rápido que o normal graças à sua {{itemName}}!",
|
||||
} as const;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { SimpleTranslationEntries } from "#app/interfaces/locales";
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Battle forms
|
||||
export const battlePokemonForm: SimpleTranslationEntries = {
|
||||
"mega": "Mega {{pokemonName}}",
|
||||
"mega-x": "Mega {{pokemonName}} X",
|
||||
"mega-y": "Mega {{pokemonName}} Y",
|
||||
@ -9,6 +8,15 @@ export const pokemonForm: SimpleTranslationEntries = {
|
||||
"gigantamax": "G-Max {{pokemonName}}",
|
||||
"eternamax": "E-Max {{pokemonName}}",
|
||||
|
||||
"megaChange": "{{preName}} Mega Evoluiu\npara {{pokemonName}}!",
|
||||
"gigantamaxChange": "{{preName}} Gigantamaxou\npara {{pokemonName}}!",
|
||||
"eternamaxChange": "{{preName}} Eternamaxou\npara {{pokemonName}}!",
|
||||
"revertChange": "{{pokemonName}} voltou\npara sua forma original!",
|
||||
"formChange": "{{preName}} mudou de forma!",
|
||||
} as const;
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
|
||||
// Starters forms
|
||||
// 1G
|
||||
"pikachuCosplay": "Cosplay",
|
||||
|
@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = {
|
||||
"postSummonSwordOfRuin": "{{pokemonNameWithAffix}}的灾祸之剑\n令周围的宝可梦的{{statName}}减弱了!",
|
||||
"postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}的灾祸之简\n令周围的宝可梦的{{statName}}减弱了!",
|
||||
"postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}的灾祸之玉\n令周围的宝可梦的{{statName}}减弱了!",
|
||||
"preventBerryUse": "{{pokemonNameWithAffix}}因太紧张\n而无法食用树果!",
|
||||
} as const;
|
||||
|
@ -36,7 +36,7 @@ import { move } from "./move";
|
||||
import { nature } from "./nature";
|
||||
import { pokeball } from "./pokeball";
|
||||
import { pokemon } from "./pokemon";
|
||||
import { pokemonForm } from "./pokemon-form";
|
||||
import { pokemonForm, battlePokemonForm } from "./pokemon-form";
|
||||
import { fusionAffixes } from "./pokemon-fusion-affixes";
|
||||
import { pokemonInfo } from "./pokemon-info";
|
||||
import { pokemonInfoContainer } from "./pokemon-info-container";
|
||||
@ -63,6 +63,7 @@ export const zhCnConfig = {
|
||||
battle: battle,
|
||||
battleInfo: battleInfo,
|
||||
battleMessageUiHandler: battleMessageUiHandler,
|
||||
battlePokemonForm: battlePokemonForm,
|
||||
battlerTags: battlerTags,
|
||||
berry: berry,
|
||||
bgmName: bgmName,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { SimpleTranslationEntries } from "#app/interfaces/locales";
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Battle forms
|
||||
export const battlePokemonForm: SimpleTranslationEntries = {
|
||||
"mega": "Mega {{pokemonName}}",
|
||||
"mega-x": "Mega {{pokemonName}} X",
|
||||
"mega-y": "Mega {{pokemonName}} Y",
|
||||
@ -9,6 +8,14 @@ export const pokemonForm: SimpleTranslationEntries = {
|
||||
"gigantamax": "超极巨{{pokemonName}}",
|
||||
"eternamax": "无极巨{{pokemonName}}",
|
||||
|
||||
"megaChange": "{{preName}}超级进化成了\n{{pokemonName}}!",
|
||||
"gigantamaxChange": "{{preName}}超极巨化成了\n{{pokemonName}}!",
|
||||
"eternamaxChange": "{{preName}}无极巨化成了\n{{pokemonName}}!",
|
||||
"revertChange": "{{pokemonName}}变回了\n原本的样子!",
|
||||
"formChange": "{{preName}}变成其他样子了。",
|
||||
} as const;
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Starters forms
|
||||
// 1G
|
||||
"pikachuCosplay": "服装",
|
||||
|
@ -59,4 +59,5 @@ export const abilityTriggers: SimpleTranslationEntries = {
|
||||
"postSummonSwordOfRuin": "{{pokemonNameWithAffix}}'s Sword of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
|
||||
"postSummonTabletsOfRuin": "{{pokemonNameWithAffix}}'s Tablets of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
|
||||
"postSummonBeadsOfRuin": "{{pokemonNameWithAffix}}'s Beads of Ruin lowered the {{statName}}\nof all surrounding Pokémon!",
|
||||
"preventBerryUse": "{{pokemonNameWithAffix}}因太緊張\n而無法食用樹果!",
|
||||
} as const;
|
||||
|
@ -36,7 +36,7 @@ import { move } from "./move";
|
||||
import { nature } from "./nature";
|
||||
import { pokeball } from "./pokeball";
|
||||
import { pokemon } from "./pokemon";
|
||||
import { pokemonForm } from "./pokemon-form";
|
||||
import { pokemonForm, battlePokemonForm } from "./pokemon-form";
|
||||
import { fusionAffixes } from "./pokemon-fusion-affixes";
|
||||
import { pokemonInfo } from "./pokemon-info";
|
||||
import { pokemonInfoContainer } from "./pokemon-info-container";
|
||||
@ -63,6 +63,7 @@ export const zhTwConfig = {
|
||||
battle: battle,
|
||||
battleInfo: battleInfo,
|
||||
battleMessageUiHandler: battleMessageUiHandler,
|
||||
battlePokemonForm: battlePokemonForm,
|
||||
battlerTags: battlerTags,
|
||||
berry: berry,
|
||||
bgmName: bgmName,
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { SimpleTranslationEntries } from "#app/interfaces/locales";
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Battle forms
|
||||
export const battlePokemonForm: SimpleTranslationEntries = {
|
||||
"mega": "Mega {{pokemonName}}",
|
||||
"mega-x": "Mega {{pokemonName}} X",
|
||||
"mega-y": "Mega {{pokemonName}} Y",
|
||||
@ -9,6 +8,14 @@ export const pokemonForm: SimpleTranslationEntries = {
|
||||
"gigantamax": "G-Max {{pokemonName}}",
|
||||
"eternamax": "E-Max {{pokemonName}}",
|
||||
|
||||
"megaChange": "{{preName}}超級進化成了\n{{pokemonName}}!",
|
||||
"gigantamaxChange": "{{preName}}超極巨化成了\n{{pokemonName}}!",
|
||||
"eternamaxChange": "{{preName}}無極巨化成了\n{{pokemonName}}!",
|
||||
"revertChange": "{{pokemonName}}變回了\n原本的樣子!",
|
||||
"formChange": "{{preName}}變為其他樣子了。",
|
||||
} as const;
|
||||
|
||||
export const pokemonForm: SimpleTranslationEntries = {
|
||||
// Starters forms
|
||||
// 1G
|
||||
"pikachuCosplay": "Cosplay",
|
||||
|
@ -1576,11 +1576,11 @@ export class PokemonNatureChangeModifier extends ConsumablePokemonModifier {
|
||||
const pokemon = args[0] as Pokemon;
|
||||
pokemon.natureOverride = this.nature;
|
||||
let speciesId = pokemon.species.speciesId;
|
||||
pokemon.scene.gameData.dexData[speciesId].natureAttr |= Math.pow(2, this.nature + 1);
|
||||
pokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1);
|
||||
|
||||
while (pokemonPrevolutions.hasOwnProperty(speciesId)) {
|
||||
speciesId = pokemonPrevolutions[speciesId];
|
||||
pokemon.scene.gameData.dexData[speciesId].natureAttr |= Math.pow(2, this.nature + 1);
|
||||
pokemon.scene.gameData.dexData[speciesId].natureAttr |= 1 << (this.nature + 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -2334,7 +2334,7 @@ export abstract class HeldItemTransferModifier extends PokemonHeldItemModifier {
|
||||
* @see {@linkcode modifierTypes[MINI_BLACK_HOLE]}
|
||||
*/
|
||||
export class TurnHeldItemTransferModifier extends HeldItemTransferModifier {
|
||||
readonly isTransferrable: boolean = false;
|
||||
readonly isTransferrable: boolean = true;
|
||||
constructor(type: ModifierType, pokemonId: integer, stackCount?: integer) {
|
||||
super(type, pokemonId, stackCount);
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import { ModifierTier } from "./modifier/modifier-tier";
|
||||
import { FusePokemonModifierType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, RememberMoveModifierType, TmModifierType, getDailyRunStarterModifiers, getEnemyBuffModifierForWave, getModifierType, getPlayerModifierTypeOptions, getPlayerShopModifierTypeOptionsForWave, modifierTypes, regenerateModifierPoolThresholds } from "./modifier/modifier-type";
|
||||
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
|
||||
import { BattlerTagLapseType, CenterOfAttentionTag, EncoreTag, ProtectedTag, SemiInvulnerableTag, TrappedTag } from "./data/battler-tags";
|
||||
import { getPokemonMessage, getPokemonNameWithAffix } from "./messages";
|
||||
import { getPokemonNameWithAffix } from "./messages";
|
||||
import { Starter } from "./ui/starter-select-ui-handler";
|
||||
import { Gender } from "./data/gender";
|
||||
import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
|
||||
@ -2168,6 +2168,15 @@ export class CommandPhase extends FieldPhase {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase for determining an enemy AI's action for the next turn.
|
||||
* During this phase, the enemy decides whether to switch (if it has a trainer)
|
||||
* or to use a move from its moveset.
|
||||
*
|
||||
* For more information on how the Enemy AI works, see docs/enemy-ai.md
|
||||
* @see {@linkcode Pokemon.getMatchupScore}
|
||||
* @see {@linkcode EnemyPokemon.getNextMove}
|
||||
*/
|
||||
export class EnemyCommandPhase extends FieldPhase {
|
||||
protected fieldIndex: integer;
|
||||
|
||||
@ -2186,6 +2195,15 @@ export class EnemyCommandPhase extends FieldPhase {
|
||||
|
||||
const trainer = battle.trainer;
|
||||
|
||||
/**
|
||||
* If the enemy has a trainer, decide whether or not the enemy should switch
|
||||
* to another member in its party.
|
||||
*
|
||||
* This block compares the active enemy Pokemon's {@linkcode Pokemon.getMatchupScore | matchup score}
|
||||
* against the active player Pokemon with the enemy party's other non-fainted Pokemon. If a party
|
||||
* member's matchup score is 3x the active enemy's score (or 2x for "boss" trainers),
|
||||
* the enemy will switch to that Pokemon.
|
||||
*/
|
||||
if (trainer && !enemyPokemon.getMoveQueue().length) {
|
||||
const opponents = enemyPokemon.getOpponents();
|
||||
|
||||
@ -2217,6 +2235,7 @@ export class EnemyCommandPhase extends FieldPhase {
|
||||
}
|
||||
}
|
||||
|
||||
/** Select a move to use (and a target to use it against, if applicable) */
|
||||
const nextMove = enemyPokemon.getNextMove();
|
||||
|
||||
this.scene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] =
|
||||
@ -2410,7 +2429,7 @@ export class BerryPhase extends FieldPhase {
|
||||
pokemon.getOpponents().map((opp) => applyAbAttrs(PreventBerryUseAbAttr, opp, cancelled));
|
||||
|
||||
if (cancelled.value) {
|
||||
pokemon.scene.queueMessage(getPokemonMessage(pokemon, " is too\nnervous to eat berries!"));
|
||||
pokemon.scene.queueMessage(i18next.t("abilityTriggers:preventBerryUse", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }));
|
||||
} else {
|
||||
this.scene.unshiftPhase(
|
||||
new CommonAnimPhase(this.scene, pokemon.getBattlerIndex(), pokemon.getBattlerIndex(), CommonAnim.USE_ITEM)
|
||||
@ -3789,11 +3808,10 @@ export class FaintPhase extends PokemonPhase {
|
||||
const nonFaintedPartyMemberCount = nonFaintedLegalPartyMembers.length;
|
||||
if (!nonFaintedPartyMemberCount) {
|
||||
this.scene.unshiftPhase(new GameOverPhase(this.scene));
|
||||
} else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !nonFaintedLegalPartyMembers[0].isActive(true))) {
|
||||
this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false));
|
||||
}
|
||||
if (nonFaintedPartyMemberCount === 1 && this.scene.currentBattle.double) {
|
||||
} else if (nonFaintedPartyMemberCount === 1 && this.scene.currentBattle.double) {
|
||||
this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true));
|
||||
} else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount()) {
|
||||
this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false));
|
||||
}
|
||||
} else {
|
||||
this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex));
|
||||
@ -4450,9 +4468,10 @@ export class SwitchPhase extends BattlePhase {
|
||||
|
||||
start() {
|
||||
super.start();
|
||||
const availablePartyMembers = this.scene.getParty().filter(p => !p.isFainted());
|
||||
|
||||
// Skip modal switch if impossible
|
||||
if (this.isModal && !this.scene.getParty().filter(p => p.isAllowedInBattle() && !p.isActive(true)).length) {
|
||||
if (this.isModal && (!availablePartyMembers.filter(p => !p.isActive(true)).length || (!this.scene.currentBattle.started && availablePartyMembers.length === 1))) {
|
||||
return super.end();
|
||||
}
|
||||
|
||||
@ -4462,7 +4481,7 @@ export class SwitchPhase extends BattlePhase {
|
||||
}
|
||||
|
||||
// Override field index to 0 in case of double battle where 2/3 remaining legal party members fainted at once
|
||||
const fieldIndex = this.scene.currentBattle.getBattlerCount() === 1 || this.scene.getParty().filter(p => p.isAllowedInBattle()).length > 1 ? this.fieldIndex : 0;
|
||||
const fieldIndex = this.scene.currentBattle.getBattlerCount() === 1 || availablePartyMembers.length > 1 ? this.fieldIndex : 0;
|
||||
|
||||
this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => {
|
||||
if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) {
|
||||
|
@ -41,7 +41,6 @@ import { Moves } from "#enums/moves";
|
||||
import { PlayerGender } from "#enums/player-gender";
|
||||
import { Species } from "#enums/species";
|
||||
import { applyChallenges, ChallengeType } from "#app/data/challenge.js";
|
||||
import { Abilities } from "#app/enums/abilities.js";
|
||||
|
||||
export const defaultStarterSpecies: Species[] = [
|
||||
Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE,
|
||||
@ -350,7 +349,7 @@ export class GameData {
|
||||
this.scene.ui.savingIcon.show();
|
||||
const data = this.getSystemSaveData();
|
||||
|
||||
const maxIntAttrValue = Math.pow(2, 31);
|
||||
const maxIntAttrValue = 0x80000000;
|
||||
const systemData = JSON.stringify(data, (k: any, v: any) => typeof v === "bigint" ? v <= maxIntAttrValue ? Number(v) : v.toString() : v);
|
||||
|
||||
localStorage.setItem(`data_${loggedInUser.username}`, encrypt(systemData, bypassLogin));
|
||||
@ -852,14 +851,6 @@ export class GameData {
|
||||
const handleSessionData = async (sessionDataStr: string) => {
|
||||
try {
|
||||
const sessionData = this.parseSessionData(sessionDataStr);
|
||||
for (let i = 0; i <= 5; i++) {
|
||||
const speciesToCheck = getPokemonSpecies(sessionData.party[i]?.species);
|
||||
if (sessionData.party[i]?.abilityIndex === 1) {
|
||||
if (speciesToCheck.ability1 === speciesToCheck.ability2 && speciesToCheck.abilityHidden !== Abilities.NONE && speciesToCheck.abilityHidden !== speciesToCheck.ability1) {
|
||||
sessionData.party[i].abilityIndex = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve(sessionData);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
@ -1172,7 +1163,7 @@ export class GameData {
|
||||
}
|
||||
const sessionData = useCachedSession ? this.parseSessionData(decrypt(localStorage.getItem(`sessionData${scene.sessionSlotId ? scene.sessionSlotId : ""}_${loggedInUser.username}`), bypassLogin)) : this.getSessionSaveData(scene);
|
||||
|
||||
const maxIntAttrValue = Math.pow(2, 31);
|
||||
const maxIntAttrValue = 0x80000000;
|
||||
const systemData = useCachedSystem ? this.parseSystemData(decrypt(localStorage.getItem(`data_${loggedInUser.username}`), bypassLogin)) : this.getSystemSaveData();
|
||||
|
||||
const request = {
|
||||
@ -1377,7 +1368,7 @@ export class GameData {
|
||||
const entry = data[defaultStarterSpecies[ds]] as DexEntry;
|
||||
entry.seenAttr = defaultStarterAttr;
|
||||
entry.caughtAttr = defaultStarterAttr;
|
||||
entry.natureAttr = Math.pow(2, defaultStarterNatures[ds] + 1);
|
||||
entry.natureAttr = 1 << (defaultStarterNatures[ds] + 1);
|
||||
for (const i in entry.ivs) {
|
||||
entry.ivs[i] = 10;
|
||||
}
|
||||
@ -1444,10 +1435,10 @@ export class GameData {
|
||||
dexEntry.caughtAttr |= dexAttr;
|
||||
if (speciesStarters.hasOwnProperty(species.speciesId)) {
|
||||
this.starterData[species.speciesId].abilityAttr |= pokemon.abilityIndex !== 1 || pokemon.species.ability2
|
||||
? Math.pow(2, pokemon.abilityIndex)
|
||||
? 1 << pokemon.abilityIndex
|
||||
: AbilityAttr.ABILITY_HIDDEN;
|
||||
}
|
||||
dexEntry.natureAttr |= Math.pow(2, pokemon.nature + 1);
|
||||
dexEntry.natureAttr |= 1 << (pokemon.nature + 1);
|
||||
|
||||
const hasPrevolution = pokemonPrevolutions.hasOwnProperty(species.speciesId);
|
||||
const newCatch = !caughtAttr;
|
||||
@ -1483,7 +1474,7 @@ export class GameData {
|
||||
}
|
||||
|
||||
if (!hasPrevolution && (!pokemon.scene.gameMode.isDaily || hasNewAttr || fromEgg)) {
|
||||
this.addStarterCandy(species, (1 * (pokemon.isShiny() ? 5 * Math.pow(2, pokemon.variant || 0) : 1)) * (fromEgg || pokemon.isBoss() ? 2 : 1));
|
||||
this.addStarterCandy(species, (1 * (pokemon.isShiny() ? 5 * (1 << (pokemon.variant ?? 0)) : 1)) * (fromEgg || pokemon.isBoss() ? 2 : 1));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1554,7 +1545,7 @@ export class GameData {
|
||||
this.starterData[speciesId].eggMoves = 0;
|
||||
}
|
||||
|
||||
const value = Math.pow(2, eggMoveIndex);
|
||||
const value = 1 << eggMoveIndex;
|
||||
|
||||
if (this.starterData[speciesId].eggMoves & value) {
|
||||
resolve(false);
|
||||
@ -1646,7 +1637,7 @@ export class GameData {
|
||||
getSpeciesDefaultNature(species: PokemonSpecies): Nature {
|
||||
const dexEntry = this.dexData[species.speciesId];
|
||||
for (let n = 0; n < 25; n++) {
|
||||
if (dexEntry.natureAttr & Math.pow(2, n + 1)) {
|
||||
if (dexEntry.natureAttr & (1 << (n + 1))) {
|
||||
return n as Nature;
|
||||
}
|
||||
}
|
||||
@ -1654,7 +1645,7 @@ export class GameData {
|
||||
}
|
||||
|
||||
getSpeciesDefaultNatureAttr(species: PokemonSpecies): integer {
|
||||
return Math.pow(2, this.getSpeciesDefaultNature(species));
|
||||
return 1 << (this.getSpeciesDefaultNature(species));
|
||||
}
|
||||
|
||||
getDexAttrLuck(dexAttr: bigint): integer {
|
||||
@ -1664,7 +1655,7 @@ export class GameData {
|
||||
getNaturesForAttr(natureAttr: integer): Nature[] {
|
||||
const ret: Nature[] = [];
|
||||
for (let n = 0; n < 25; n++) {
|
||||
if (natureAttr & Math.pow(2, n + 1)) {
|
||||
if (natureAttr & (1 << (n + 1))) {
|
||||
ret.push(n);
|
||||
}
|
||||
}
|
||||
@ -1706,7 +1697,7 @@ export class GameData {
|
||||
}
|
||||
|
||||
getFormAttr(formIndex: integer): bigint {
|
||||
return BigInt(Math.pow(2, 7 + formIndex));
|
||||
return BigInt(1 << (7 + formIndex));
|
||||
}
|
||||
|
||||
consolidateDexData(dexData: DexData): void {
|
||||
@ -1716,7 +1707,7 @@ export class GameData {
|
||||
entry.hatchedCount = 0;
|
||||
}
|
||||
if (!entry.hasOwnProperty("natureAttr") || (entry.caughtAttr && !entry.natureAttr)) {
|
||||
entry.natureAttr = this.defaultDexData[k].natureAttr || Math.pow(2, Utils.randInt(25, 1));
|
||||
entry.natureAttr = this.defaultDexData[k].natureAttr || (1 << Utils.randInt(25, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { SPLASH_ONLY } from "../utils/testUtils";
|
||||
import { removeEnemyHeldItems, SPLASH_ONLY } from "../utils/testUtils";
|
||||
|
||||
describe("Moves - Octolock", () => {
|
||||
describe("integration tests", () => {
|
||||
@ -32,15 +32,16 @@ describe("Moves - Octolock", () => {
|
||||
|
||||
game.override.enemySpecies(Species.RATTATA);
|
||||
game.override.enemyMoveset(SPLASH_ONLY);
|
||||
game.override.enemyAbility(Abilities.NONE);
|
||||
game.override.enemyAbility(Abilities.BALL_FETCH);
|
||||
|
||||
game.override.startingLevel(2000);
|
||||
game.override.moveset([Moves.OCTOLOCK, Moves.SPLASH]);
|
||||
game.override.ability(Abilities.NONE);
|
||||
game.override.ability(Abilities.BALL_FETCH);
|
||||
});
|
||||
|
||||
it("Reduces DEf and SPDEF by 1 each turn", { timeout: 10000 }, async () => {
|
||||
await game.startBattle([Species.GRAPPLOCT]);
|
||||
removeEnemyHeldItems(game.scene);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyField();
|
||||
|
||||
@ -62,6 +63,7 @@ describe("Moves - Octolock", () => {
|
||||
|
||||
it("Traps the target pokemon", { timeout: 10000 }, async () => {
|
||||
await game.startBattle([Species.GRAPPLOCT]);
|
||||
removeEnemyHeldItems(game.scene);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyField();
|
||||
|
||||
|
@ -288,6 +288,36 @@ export default class PartyUiHandler extends MessageUiHandler {
|
||||
const pokemon = this.scene.getParty()[this.cursor];
|
||||
if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode && option !== PartyOption.CANCEL) {
|
||||
this.startTransfer();
|
||||
|
||||
let ableToTransfer: string;
|
||||
for (let p = 0; p < this.scene.getParty().length; p++) { // this fore look goes through each of the party pokemon
|
||||
const newPokemon = this.scene.getParty()[p];
|
||||
// this next line gets all of the transferable items from pokemon [p]; it does this by getting all the held modifiers that are transferable and checking to see if they belong to pokemon [p]
|
||||
const getTransferrableItemsFromPokemon = (newPokemon: PlayerPokemon) =>
|
||||
this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).isTransferrable && (m as PokemonHeldItemModifier).pokemonId === newPokemon.id) as PokemonHeldItemModifier[];
|
||||
// this next bit checks to see if the the selected item from the original transfer pokemon exists on the new pokemon [p]; this returns undefined if the new pokemon doesn't have the item at all, otherwise it returns the pokemonHeldItemModifier for that item
|
||||
const matchingModifier = newPokemon.scene.findModifier(m => m instanceof PokemonHeldItemModifier && m.pokemonId === newPokemon.id && m.matchType(getTransferrableItemsFromPokemon(pokemon)[this.transferOptionCursor])) as PokemonHeldItemModifier;
|
||||
const partySlot = this.partySlots.filter(m => m.getPokemon() === newPokemon)[0]; // this gets pokemon [p] for us
|
||||
if (p !== this.transferCursor) { // this skips adding the able/not able labels on the pokemon doing the transfer
|
||||
if (matchingModifier) { // if matchingModifier exists then the item exists on the new pokemon
|
||||
if (matchingModifier.getMaxStackCount(this.scene) === matchingModifier.stackCount) { // checks to see if the stack of items is at max stack; if so, set the description label to "Not able"
|
||||
ableToTransfer = "Not able";
|
||||
} else { // if the pokemon isn't at max stack, make the label "Able"
|
||||
ableToTransfer = "Able";
|
||||
}
|
||||
} else { // if matchingModifier doesn't exist, that means the pokemon doesn't have any of the item, and we need to show "Able"
|
||||
ableToTransfer = "Able";
|
||||
}
|
||||
} else { // this else relates to the transfer pokemon. We set the text to be blank so there's no "Able"/"Not able" text
|
||||
ableToTransfer = "";
|
||||
}
|
||||
partySlot.slotHpBar.setVisible(false);
|
||||
partySlot.slotHpOverlay.setVisible(false);
|
||||
partySlot.slotHpText.setVisible(false);
|
||||
partySlot.slotDescriptionLabel.setText(ableToTransfer);
|
||||
partySlot.slotDescriptionLabel.setVisible(true);
|
||||
}
|
||||
|
||||
this.clearOptions();
|
||||
ui.playSelect();
|
||||
return true;
|
||||
@ -947,6 +977,12 @@ export default class PartyUiHandler extends MessageUiHandler {
|
||||
this.transferMode = false;
|
||||
this.transferAll = false;
|
||||
this.partySlots[this.transferCursor].setTransfer(false);
|
||||
for (let i = 0; i < this.partySlots.length; i++) {
|
||||
this.partySlots[i].slotDescriptionLabel.setVisible(false);
|
||||
this.partySlots[i].slotHpBar.setVisible(true);
|
||||
this.partySlots[i].slotHpOverlay.setVisible(true);
|
||||
this.partySlots[i].slotHpText.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
doRelease(slotIndex: integer): void {
|
||||
@ -1041,6 +1077,12 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
|
||||
private slotBg: Phaser.GameObjects.Image;
|
||||
private slotPb: Phaser.GameObjects.Sprite;
|
||||
public slotName: Phaser.GameObjects.Text;
|
||||
public slotHpBar: Phaser.GameObjects.Image;
|
||||
public slotHpOverlay: Phaser.GameObjects.Sprite;
|
||||
public slotHpText: Phaser.GameObjects.Text;
|
||||
public slotDescriptionLabel: Phaser.GameObjects.Text; // this is used to show text instead of the HP bar i.e. for showing "Able"/"Not Able" for TMs when you try to learn them
|
||||
|
||||
|
||||
private pokemonIcon: Phaser.GameObjects.Container;
|
||||
private iconAnimHandler: PokemonIconAnimHandler;
|
||||
@ -1057,6 +1099,10 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
this.setup(partyUiMode, tmMoveId);
|
||||
}
|
||||
|
||||
getPokemon(): PlayerPokemon {
|
||||
return this.pokemon;
|
||||
}
|
||||
|
||||
setup(partyUiMode: PartyUiMode, tmMoveId: Moves) {
|
||||
const battlerCount = (this.scene as BattleScene).currentBattle.getBattlerCount();
|
||||
|
||||
@ -1095,19 +1141,19 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
|
||||
nameSizeTest.destroy();
|
||||
|
||||
const slotName = addTextObject(this.scene, 0, 0, displayName, TextStyle.PARTY);
|
||||
slotName.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 21 : 24, this.slotIndex >= battlerCount ? 2 : 10);
|
||||
slotName.setOrigin(0, 0);
|
||||
this.slotName = addTextObject(this.scene, 0, 0, displayName, TextStyle.PARTY);
|
||||
this.slotName.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 21 : 24, this.slotIndex >= battlerCount ? 2 : 10);
|
||||
this.slotName.setOrigin(0, 0);
|
||||
|
||||
const slotLevelLabel = this.scene.add.image(0, 0, "party_slot_overlay_lv");
|
||||
slotLevelLabel.setPositionRelative(slotName, 8, 12);
|
||||
slotLevelLabel.setPositionRelative(this.slotName, 8, 12);
|
||||
slotLevelLabel.setOrigin(0, 0);
|
||||
|
||||
const slotLevelText = addTextObject(this.scene, 0, 0, this.pokemon.level.toString(), this.pokemon.level < (this.scene as BattleScene).getMaxExpLevel() ? TextStyle.PARTY : TextStyle.PARTY_RED);
|
||||
slotLevelText.setPositionRelative(slotLevelLabel, 9, 0);
|
||||
slotLevelText.setOrigin(0, 0.25);
|
||||
|
||||
slotInfoContainer.add([ slotName, slotLevelLabel, slotLevelText ]);
|
||||
slotInfoContainer.add([this.slotName, slotLevelLabel, slotLevelText ]);
|
||||
|
||||
const genderSymbol = getGenderSymbol(this.pokemon.getGender(true));
|
||||
|
||||
@ -1118,7 +1164,7 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
if (this.slotIndex >= battlerCount) {
|
||||
slotGenderText.setPositionRelative(slotLevelLabel, 36, 0);
|
||||
} else {
|
||||
slotGenderText.setPositionRelative(slotName, 76 - (this.pokemon.fusionSpecies ? 8 : 0), 3);
|
||||
slotGenderText.setPositionRelative(this.slotName, 76 - (this.pokemon.fusionSpecies ? 8 : 0), 3);
|
||||
}
|
||||
slotGenderText.setOrigin(0, 0.25);
|
||||
|
||||
@ -1132,7 +1178,7 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
if (this.slotIndex >= battlerCount) {
|
||||
splicedIcon.setPositionRelative(slotLevelLabel, 36 + (genderSymbol ? 8 : 0), 0.5);
|
||||
} else {
|
||||
splicedIcon.setPositionRelative(slotName, 76, 3.5);
|
||||
splicedIcon.setPositionRelative(this.slotName, 76, 3.5);
|
||||
}
|
||||
|
||||
slotInfoContainer.add(splicedIcon);
|
||||
@ -1152,7 +1198,7 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
|
||||
const shinyStar = this.scene.add.image(0, 0, `shiny_star_small${doubleShiny ? "_1" : ""}`);
|
||||
shinyStar.setOrigin(0, 0);
|
||||
shinyStar.setPositionRelative(slotName, -9, 3);
|
||||
shinyStar.setPositionRelative(this.slotName, -9, 3);
|
||||
shinyStar.setTint(getVariantTint(!doubleShiny ? this.pokemon.getVariant() : this.pokemon.variant));
|
||||
|
||||
slotInfoContainer.add(shinyStar);
|
||||
@ -1167,24 +1213,40 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
}
|
||||
}
|
||||
|
||||
this.slotHpBar = this.scene.add.image(0, 0, "party_slot_hp_bar");
|
||||
this.slotHpBar.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 72 : 8, this.slotIndex >= battlerCount ? 6 : 31);
|
||||
this.slotHpBar.setOrigin(0, 0);
|
||||
this.slotHpBar.setVisible(false);
|
||||
|
||||
const hpRatio = this.pokemon.getHpRatio();
|
||||
|
||||
this.slotHpOverlay = this.scene.add.sprite(0, 0, "party_slot_hp_overlay", hpRatio > 0.5 ? "high" : hpRatio > 0.25 ? "medium" : "low");
|
||||
this.slotHpOverlay.setPositionRelative(this.slotHpBar, 16, 2);
|
||||
this.slotHpOverlay.setOrigin(0, 0);
|
||||
this.slotHpOverlay.setScale(hpRatio, 1);
|
||||
this.slotHpOverlay.setVisible(false);
|
||||
|
||||
this.slotHpText = addTextObject(this.scene, 0, 0, `${this.pokemon.hp}/${this.pokemon.getMaxHp()}`, TextStyle.PARTY);
|
||||
this.slotHpText.setPositionRelative(this.slotHpBar, this.slotHpBar.width - 3, this.slotHpBar.height - 2);
|
||||
this.slotHpText.setOrigin(1, 0);
|
||||
this.slotHpText.setVisible(false);
|
||||
|
||||
this.slotDescriptionLabel = addTextObject(this.scene, 0, 0, "", TextStyle.MESSAGE);
|
||||
this.slotDescriptionLabel.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 94 : 32, this.slotIndex >= battlerCount ? 16 : 46);
|
||||
this.slotDescriptionLabel.setOrigin(0, 1);
|
||||
this.slotDescriptionLabel.setVisible(false);
|
||||
|
||||
slotInfoContainer.add([this.slotHpBar, this.slotHpOverlay, this.slotHpText, this.slotDescriptionLabel]);
|
||||
|
||||
if (partyUiMode !== PartyUiMode.TM_MODIFIER) {
|
||||
const slotHpBar = this.scene.add.image(0, 0, "party_slot_hp_bar");
|
||||
slotHpBar.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 72 : 8, this.slotIndex >= battlerCount ? 6 : 31);
|
||||
slotHpBar.setOrigin(0, 0);
|
||||
|
||||
const hpRatio = this.pokemon.getHpRatio();
|
||||
|
||||
const slotHpOverlay = this.scene.add.sprite(0, 0, "party_slot_hp_overlay", hpRatio > 0.5 ? "high" : hpRatio > 0.25 ? "medium" : "low");
|
||||
slotHpOverlay.setPositionRelative(slotHpBar, 16, 2);
|
||||
slotHpOverlay.setOrigin(0, 0);
|
||||
slotHpOverlay.setScale(hpRatio, 1);
|
||||
|
||||
const slotHpText = addTextObject(this.scene, 0, 0, `${this.pokemon.hp}/${this.pokemon.getMaxHp()}`, TextStyle.PARTY);
|
||||
slotHpText.setPositionRelative(slotHpBar, slotHpBar.width - 3, slotHpBar.height - 2);
|
||||
slotHpText.setOrigin(1, 0);
|
||||
|
||||
slotInfoContainer.add([ slotHpBar, slotHpOverlay, slotHpText ]);
|
||||
this.slotDescriptionLabel.setVisible(false);
|
||||
this.slotHpBar.setVisible(true);
|
||||
this.slotHpOverlay.setVisible(true);
|
||||
this.slotHpText.setVisible(true);
|
||||
} else {
|
||||
this.slotHpBar.setVisible(false);
|
||||
this.slotHpOverlay.setVisible(false);
|
||||
this.slotHpText.setVisible(false);
|
||||
let slotTmText: string;
|
||||
switch (true) {
|
||||
case (this.pokemon.compatibleTms.indexOf(tmMoveId) === -1):
|
||||
@ -1198,11 +1260,9 @@ class PartySlot extends Phaser.GameObjects.Container {
|
||||
break;
|
||||
}
|
||||
|
||||
const slotTmLabel = addTextObject(this.scene, 0, 0, slotTmText, TextStyle.MESSAGE);
|
||||
slotTmLabel.setPositionRelative(slotBg, this.slotIndex >= battlerCount ? 94 : 32, this.slotIndex >= battlerCount ? 16 : 46);
|
||||
slotTmLabel.setOrigin(0, 1);
|
||||
this.slotDescriptionLabel.setText(slotTmText);
|
||||
this.slotDescriptionLabel.setVisible(true);
|
||||
|
||||
slotInfoContainer.add(slotTmLabel);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,7 +216,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
|
||||
this.pokemonGenderText.setShadowColor(getGenderColor(pokemon.gender, true));
|
||||
this.pokemonGenderText.setVisible(true);
|
||||
|
||||
const newGender = BigInt(Math.pow(2, pokemon.gender)) * DexAttr.MALE;
|
||||
const newGender = BigInt(1 << pokemon.gender) * DexAttr.MALE;
|
||||
this.pokemonGenderNewText.setText("(+)");
|
||||
this.pokemonGenderNewText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme));
|
||||
this.pokemonGenderNewText.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme));
|
||||
@ -229,7 +229,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
|
||||
if (pokemon.species.forms?.[pokemon.formIndex]?.formName) {
|
||||
this.pokemonFormLabelText.setVisible(true);
|
||||
this.pokemonFormText.setVisible(true);
|
||||
const newForm = BigInt(Math.pow(2, pokemon.formIndex)) * DexAttr.DEFAULT_FORM;
|
||||
const newForm = BigInt(1 << pokemon.formIndex) * DexAttr.DEFAULT_FORM;
|
||||
|
||||
if ((newForm & caughtAttr) === BigInt(0)) {
|
||||
this.pokemonFormLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme));
|
||||
@ -266,7 +266,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
|
||||
*/
|
||||
const opponentPokemonOneNormalAbility = (pokemon.species.getAbilityCount() === 2);
|
||||
const opponentPokemonAbilityIndex = (opponentPokemonOneNormalAbility && pokemon.abilityIndex === 1) ? 2 : pokemon.abilityIndex;
|
||||
const opponentPokemonAbilityAttr = Math.pow(2, opponentPokemonAbilityIndex);
|
||||
const opponentPokemonAbilityAttr = 1 << opponentPokemonAbilityIndex;
|
||||
|
||||
const rootFormHasHiddenAbility = pokemon.scene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr & opponentPokemonAbilityAttr;
|
||||
|
||||
@ -281,7 +281,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
|
||||
this.pokemonNatureText.setText(getNatureName(pokemon.getNature(), true, false, false, this.scene.uiTheme));
|
||||
|
||||
const dexNatures = pokemon.scene.gameData.dexData[pokemon.species.speciesId].natureAttr;
|
||||
const newNature = Math.pow(2, pokemon.nature + 1);
|
||||
const newNature = 1 << (pokemon.nature + 1);
|
||||
|
||||
if (!(dexNatures & newNature)) {
|
||||
this.pokemonNatureLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme));
|
||||
@ -305,8 +305,8 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container {
|
||||
this.pokemonShinyIcon.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip(null, `${i18next.t("common:shinyOnHover")}${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, true));
|
||||
this.pokemonShinyIcon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip());
|
||||
|
||||
const newShiny = BigInt(Math.pow(2, (pokemon.shiny ? 1 : 0)));
|
||||
const newVariant = BigInt(Math.pow(2, pokemon.variant + 4));
|
||||
const newShiny = BigInt(1 << (pokemon.shiny ? 1 : 0));
|
||||
const newVariant = BigInt(1 << (pokemon.variant + 4));
|
||||
|
||||
this.pokemonShinyNewIcon.setText("(+)");
|
||||
this.pokemonShinyNewIcon.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme));
|
||||
|
@ -1025,16 +1025,17 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
const maxColumns = 9;
|
||||
const maxRows = 9;
|
||||
const numberOfStarters = this.filteredStarterContainers.length;
|
||||
const numOfRows = Math.ceil(numberOfStarters / 9);
|
||||
const currentRow = Math.floor(this.cursor / 9);
|
||||
const onScreenFirstIndex = this.scrollCursor * 9; // this is first starter index on the screen
|
||||
const onScreenLastIndex = Math.min(onScreenFirstIndex + 9*9, numberOfStarters) - 1; // this is the last starter index on the screen
|
||||
const numOfRows = Math.ceil(numberOfStarters / maxColumns);
|
||||
const currentRow = Math.floor(this.cursor / maxColumns);
|
||||
const onScreenFirstIndex = this.scrollCursor * maxColumns; // this is first starter index on the screen
|
||||
const onScreenLastIndex = Math.min(this.filteredStarterContainers.length - 1, onScreenFirstIndex + maxRows * maxColumns - 1); // this is the last starter index on the screen
|
||||
const onScreenNumberOfStarters = onScreenLastIndex - onScreenFirstIndex + 1;
|
||||
const onScreenNumberOfRows = Math.ceil(onScreenNumberOfStarters / 9);
|
||||
// const onScreenFirstRow = Math.floor(onScreenFirstIndex / 9);
|
||||
const onScreenCurrentRow = Math.floor((this.cursor - onScreenFirstIndex) / 9);
|
||||
|
||||
const onScreenNumberOfRows = Math.ceil(onScreenNumberOfStarters / maxColumns);
|
||||
// const onScreenFirstRow = Math.floor(onScreenFirstIndex / maxColumns);
|
||||
const onScreenCurrentRow = Math.floor((this.cursor - onScreenFirstIndex) / maxColumns);
|
||||
|
||||
// console.log("this.cursor: ", this.cursor, "this.scrollCursor" , this.scrollCursor, "numberOfStarters: ", numberOfStarters, "numOfRows: ", numOfRows, "currentRow: ", currentRow, "onScreenFirstIndex: ", onScreenFirstIndex, "onScreenLastIndex: ", onScreenLastIndex, "onScreenNumberOfStarters: ", onScreenNumberOfStarters, "onScreenNumberOfRow: ", onScreenNumberOfRows, "onScreenCurrentRow: ", onScreenCurrentRow);
|
||||
|
||||
@ -1965,6 +1966,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.starterContainer.forEach(container => {
|
||||
container.setVisible(false);
|
||||
|
||||
container.cost = this.scene.gameData.getSpeciesStarterValue(container.species.speciesId);
|
||||
|
||||
// First, ensure you have the caught attributes for the species else default to bigint 0
|
||||
const caughtVariants = this.scene.gameData.dexData[container.species.speciesId]?.caughtAttr || BigInt(0);
|
||||
|
||||
@ -2057,6 +2060,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
updateScroll = () => {
|
||||
const maxColumns = 9;
|
||||
const maxRows = 9;
|
||||
const onScreenFirstIndex = this.scrollCursor * maxColumns;
|
||||
const onScreenLastIndex = Math.min(this.filteredStarterContainers.length - 1, onScreenFirstIndex + maxRows * maxColumns -1);
|
||||
|
||||
this.starterSelectScrollBar.setPage(this.scrollCursor);
|
||||
|
||||
@ -2064,69 +2069,70 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.filteredStarterContainers.forEach((container, i) => {
|
||||
const pos = calcStarterPosition(i, this.scrollCursor);
|
||||
container.setPosition(pos.x, pos.y);
|
||||
|
||||
if (i < (maxRows + this.scrollCursor) * maxColumns && i >= this.scrollCursor * maxColumns) {
|
||||
container.setVisible(true);
|
||||
} else {
|
||||
if (i < onScreenFirstIndex || i > onScreenLastIndex) {
|
||||
container.setVisible(false);
|
||||
}
|
||||
|
||||
if (this.pokerusSpecies.includes(container.species)) {
|
||||
this.pokerusCursorObjs[pokerusCursorIndex].setPosition(pos.x - 1, pos.y + 1);
|
||||
|
||||
if (i < (maxRows + this.scrollCursor) * maxColumns && i >= this.scrollCursor * maxColumns) {
|
||||
this.pokerusCursorObjs[pokerusCursorIndex].setVisible(true);
|
||||
} else {
|
||||
if (this.pokerusSpecies.includes(container.species)) {
|
||||
this.pokerusCursorObjs[pokerusCursorIndex].setPosition(pos.x - 1, pos.y + 1);
|
||||
this.pokerusCursorObjs[pokerusCursorIndex].setVisible(false);
|
||||
pokerusCursorIndex++;
|
||||
}
|
||||
pokerusCursorIndex++;
|
||||
}
|
||||
|
||||
if (this.starterSpecies.includes(container.species)) {
|
||||
this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setPosition(pos.x - 1, pos.y + 1);
|
||||
|
||||
if (i < (maxRows + this.scrollCursor) * maxColumns && i >= this.scrollCursor * maxColumns) {
|
||||
this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setVisible(true);
|
||||
} else {
|
||||
if (this.starterSpecies.includes(container.species)) {
|
||||
this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setPosition(pos.x - 1, pos.y + 1);
|
||||
this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setVisible(false);
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
container.setVisible(true);
|
||||
|
||||
const speciesId = container.species.speciesId;
|
||||
this.updateStarterValueLabel(container);
|
||||
|
||||
container.label.setVisible(true);
|
||||
const speciesVariants = speciesId && this.scene.gameData.dexData[speciesId].caughtAttr & DexAttr.SHINY
|
||||
? [ DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3 ].filter(v => !!(this.scene.gameData.dexData[speciesId].caughtAttr & v))
|
||||
: [];
|
||||
for (let v = 0; v < 3; v++) {
|
||||
const hasVariant = speciesVariants.length > v;
|
||||
container.shinyIcons[v].setVisible(hasVariant);
|
||||
if (hasVariant) {
|
||||
container.shinyIcons[v].setTint(getVariantTint(speciesVariants[v] === DexAttr.DEFAULT_VARIANT ? 0 : speciesVariants[v] === DexAttr.VARIANT_2 ? 1 : 2));
|
||||
}
|
||||
}
|
||||
|
||||
container.starterPassiveBgs.setVisible(!!this.scene.gameData.starterData[speciesId].passiveAttr);
|
||||
container.hiddenAbilityIcon.setVisible(!!this.scene.gameData.dexData[speciesId].caughtAttr && !!(this.scene.gameData.starterData[speciesId].abilityAttr & 4));
|
||||
container.classicWinIcon.setVisible(this.scene.gameData.starterData[speciesId].classicWinCount > 0);
|
||||
|
||||
// 'Candy Icon' mode
|
||||
if (this.scene.candyUpgradeDisplay === 0) {
|
||||
|
||||
if (!starterColors[speciesId]) {
|
||||
// Default to white if no colors are found
|
||||
starterColors[speciesId] = [ "ffffff", "ffffff" ];
|
||||
if (this.pokerusSpecies.includes(container.species)) {
|
||||
this.pokerusCursorObjs[pokerusCursorIndex].setPosition(pos.x - 1, pos.y + 1);
|
||||
this.pokerusCursorObjs[pokerusCursorIndex].setVisible(true);
|
||||
pokerusCursorIndex++;
|
||||
}
|
||||
|
||||
// Set the candy colors
|
||||
container.candyUpgradeIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][0])));
|
||||
container.candyUpgradeOverlayIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][1])));
|
||||
if (this.starterSpecies.includes(container.species)) {
|
||||
this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setPosition(pos.x - 1, pos.y + 1);
|
||||
this.starterCursorObjs[this.starterSpecies.indexOf(container.species)].setVisible(true);
|
||||
}
|
||||
|
||||
this.setUpgradeIcon(container);
|
||||
} else if (this.scene.candyUpgradeDisplay === 1) {
|
||||
container.candyUpgradeIcon.setVisible(false);
|
||||
container.candyUpgradeOverlayIcon.setVisible(false);
|
||||
const speciesId = container.species.speciesId;
|
||||
this.updateStarterValueLabel(container);
|
||||
|
||||
container.label.setVisible(true);
|
||||
const speciesVariants = speciesId && this.scene.gameData.dexData[speciesId].caughtAttr & DexAttr.SHINY
|
||||
? [ DexAttr.DEFAULT_VARIANT, DexAttr.VARIANT_2, DexAttr.VARIANT_3 ].filter(v => !!(this.scene.gameData.dexData[speciesId].caughtAttr & v))
|
||||
: [];
|
||||
for (let v = 0; v < 3; v++) {
|
||||
const hasVariant = speciesVariants.length > v;
|
||||
container.shinyIcons[v].setVisible(hasVariant);
|
||||
if (hasVariant) {
|
||||
container.shinyIcons[v].setTint(getVariantTint(speciesVariants[v] === DexAttr.DEFAULT_VARIANT ? 0 : speciesVariants[v] === DexAttr.VARIANT_2 ? 1 : 2));
|
||||
}
|
||||
}
|
||||
|
||||
container.starterPassiveBgs.setVisible(!!this.scene.gameData.starterData[speciesId].passiveAttr);
|
||||
container.hiddenAbilityIcon.setVisible(!!this.scene.gameData.dexData[speciesId].caughtAttr && !!(this.scene.gameData.starterData[speciesId].abilityAttr & 4));
|
||||
container.classicWinIcon.setVisible(this.scene.gameData.starterData[speciesId].classicWinCount > 0);
|
||||
|
||||
// 'Candy Icon' mode
|
||||
if (this.scene.candyUpgradeDisplay === 0) {
|
||||
|
||||
if (!starterColors[speciesId]) {
|
||||
// Default to white if no colors are found
|
||||
starterColors[speciesId] = [ "ffffff", "ffffff" ];
|
||||
}
|
||||
|
||||
// Set the candy colors
|
||||
container.candyUpgradeIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][0])));
|
||||
container.candyUpgradeOverlayIcon.setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][1])));
|
||||
|
||||
this.setUpgradeIcon(container);
|
||||
} else if (this.scene.candyUpgradeDisplay === 1) {
|
||||
container.candyUpgradeIcon.setVisible(false);
|
||||
container.candyUpgradeOverlayIcon.setVisible(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -2607,7 +2613,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
this.speciesStarterMoves.push(...levelMoves.filter(lm => lm[0] > 0 && lm[0] <= 5).map(lm => lm[1]));
|
||||
if (speciesEggMoves.hasOwnProperty(species.speciesId)) {
|
||||
for (let em = 0; em < 4; em++) {
|
||||
if (this.scene.gameData.starterData[species.speciesId].eggMoves & Math.pow(2, em)) {
|
||||
if (this.scene.gameData.starterData[species.speciesId].eggMoves & (1 << em)) {
|
||||
this.speciesStarterMoves.push(speciesEggMoves[species.speciesId][em]);
|
||||
}
|
||||
}
|
||||
@ -2619,7 +2625,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
? speciesMoveData as StarterMoveset
|
||||
: (speciesMoveData as StarterFormMoveData)[formIndex]
|
||||
: null;
|
||||
const availableStarterMoves = this.speciesStarterMoves.concat(speciesEggMoves.hasOwnProperty(species.speciesId) ? speciesEggMoves[species.speciesId].filter((_, em: integer) => this.scene.gameData.starterData[species.speciesId].eggMoves & Math.pow(2, em)) : []);
|
||||
const availableStarterMoves = this.speciesStarterMoves.concat(speciesEggMoves.hasOwnProperty(species.speciesId) ? speciesEggMoves[species.speciesId].filter((_, em: integer) => this.scene.gameData.starterData[species.speciesId].eggMoves & (1 << em)) : []);
|
||||
this.starterMoveset = (moveData || (this.speciesStarterMoves.slice(0, 4) as StarterMoveset)).filter(m => availableStarterMoves.find(sm => sm === m)) as StarterMoveset;
|
||||
// Consolidate move data if it contains an incompatible move
|
||||
if (this.starterMoveset.length < 4 && this.starterMoveset.length < availableStarterMoves.length) {
|
||||
@ -2676,7 +2682,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
|
||||
for (let em = 0; em < 4; em++) {
|
||||
const eggMove = hasEggMoves ? allMoves[speciesEggMoves[species.speciesId][em]] : null;
|
||||
const eggMoveUnlocked = eggMove && this.scene.gameData.starterData[species.speciesId].eggMoves & Math.pow(2, em);
|
||||
const eggMoveUnlocked = eggMove && this.scene.gameData.starterData[species.speciesId].eggMoves & (1 << em);
|
||||
this.pokemonEggMoveBgs[em].setFrame(Type[eggMove ? eggMove.type : Type.UNKNOWN].toString().toLowerCase());
|
||||
this.pokemonEggMoveLabels[em].setText(eggMove && eggMoveUnlocked ? eggMove.name : "???");
|
||||
}
|
||||
@ -2717,6 +2723,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
|
||||
const props = this.scene.gameData.getSpeciesDexAttrProps(species, currentDexAttr);
|
||||
this.starterIcons[s].setTexture(species.getIconAtlasKey(props.formIndex, props.shiny, props.variant));
|
||||
this.starterIcons[s].setFrame(species.getIconId(props.female, props.formIndex, props.shiny, props.variant));
|
||||
this.checkIconId(this.starterIcons[s], species, props.female, props.formIndex, props.shiny, props.variant);
|
||||
if (s >= index) {
|
||||
this.starterCursorObjs[s].setPosition(this.starterCursorObjs[s + 1].x, this.starterCursorObjs[s + 1].y);
|
||||
this.starterCursorObjs[s].setVisible(this.starterCursorObjs[s + 1].visible);
|
||||
|
46
src/utils.ts
46
src/utils.ts
@ -165,40 +165,20 @@ export function getPlayTimeString(totalSeconds: integer): string {
|
||||
return `${days.padStart(2, "0")}:${hours.padStart(2, "0")}:${minutes.padStart(2, "0")}:${seconds.padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
export function binToDec(input: string): integer {
|
||||
const place: integer[] = [];
|
||||
const binary: string[] = [];
|
||||
|
||||
let decimalNum = 0;
|
||||
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
binary.push(input[i]);
|
||||
place.push(Math.pow(2, i));
|
||||
decimalNum += place[i] * parseInt(binary[i]);
|
||||
}
|
||||
|
||||
return decimalNum;
|
||||
}
|
||||
|
||||
export function decToBin(input: integer): string {
|
||||
let bin = "";
|
||||
let intNum = input;
|
||||
while (intNum > 0) {
|
||||
bin = intNum % 2 ? `1${bin}` : `0${bin}`;
|
||||
intNum = Math.floor(intNum * 0.5);
|
||||
}
|
||||
|
||||
return bin;
|
||||
}
|
||||
|
||||
export function getIvsFromId(id: integer): integer[] {
|
||||
/**
|
||||
* Generates IVs from a given {@linkcode id} by extracting 5 bits at a time
|
||||
* starting from the least significant bit up to the 30th most significant bit.
|
||||
* @param id 32-bit number
|
||||
* @returns An array of six numbers corresponding to 5-bit chunks from {@linkcode id}
|
||||
*/
|
||||
export function getIvsFromId(id: number): number[] {
|
||||
return [
|
||||
binToDec(decToBin(id).substring(0, 5)),
|
||||
binToDec(decToBin(id).substring(5, 10)),
|
||||
binToDec(decToBin(id).substring(10, 15)),
|
||||
binToDec(decToBin(id).substring(15, 20)),
|
||||
binToDec(decToBin(id).substring(20, 25)),
|
||||
binToDec(decToBin(id).substring(25, 30))
|
||||
(id & 0x3E000000) >>> 25,
|
||||
(id & 0x01F00000) >>> 20,
|
||||
(id & 0x000F8000) >>> 15,
|
||||
(id & 0x00007C00) >>> 10,
|
||||
(id & 0x000003E0) >>> 5,
|
||||
(id & 0x0000001F)
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user