Merge branch 'beta' into held-item-refactor

This commit is contained in:
Wlowscha 2025-04-17 22:36:26 +02:00 committed by GitHub
commit 054dfcf35a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 156 additions and 149 deletions

View File

@ -6701,7 +6701,7 @@ export function initAbilities() {
new Ability(Abilities.BAD_DREAMS, 4) new Ability(Abilities.BAD_DREAMS, 4)
.attr(PostTurnHurtIfSleepingAbAttr), .attr(PostTurnHurtIfSleepingAbAttr),
new Ability(Abilities.PICKPOCKET, 5) new Ability(Abilities.PICKPOCKET, 5)
.attr(PostDefendStealHeldItemAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT)) .attr(PostDefendStealHeldItemAbAttr, (target, user, move) => move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user, target}))
.condition(getSheerForceHitDisableAbCondition()), .condition(getSheerForceHitDisableAbCondition()),
new Ability(Abilities.SHEER_FORCE, 5) new Ability(Abilities.SHEER_FORCE, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 1.3) .attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 1.3)
@ -7051,7 +7051,7 @@ export function initAbilities() {
new Ability(Abilities.BATTERY, 7) new Ability(Abilities.BATTERY, 7)
.attr(AllyMoveCategoryPowerBoostAbAttr, [ MoveCategory.SPECIAL ], 1.3), .attr(AllyMoveCategoryPowerBoostAbAttr, [ MoveCategory.SPECIAL ], 1.3),
new Ability(Abilities.FLUFFY, 7) new Ability(Abilities.FLUFFY, 7)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 0.5) .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user, target}), 0.5)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.FIRE, 2) .attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.FIRE, 2)
.ignorable(), .ignorable(),
new Ability(Abilities.DAZZLING, 7) new Ability(Abilities.DAZZLING, 7)
@ -7060,7 +7060,7 @@ export function initAbilities() {
new Ability(Abilities.SOUL_HEART, 7) new Ability(Abilities.SOUL_HEART, 7)
.attr(PostKnockOutStatStageChangeAbAttr, Stat.SPATK, 1), .attr(PostKnockOutStatStageChangeAbAttr, Stat.SPATK, 1),
new Ability(Abilities.TANGLING_HAIR, 7) new Ability(Abilities.TANGLING_HAIR, 7)
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), Stat.SPD, -1, false), .attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user, target}), Stat.SPD, -1, false),
new Ability(Abilities.RECEIVER, 7) new Ability(Abilities.RECEIVER, 7)
.attr(CopyFaintedAllyAbilityAbAttr) .attr(CopyFaintedAllyAbilityAbAttr)
.uncopiable(), .uncopiable(),

View File

@ -52,6 +52,7 @@ export enum BattlerTagLapseType {
MOVE_EFFECT, MOVE_EFFECT,
TURN_END, TURN_END,
HIT, HIT,
/** Tag lapses AFTER_HIT, applying its effects even if the user faints */
AFTER_HIT, AFTER_HIT,
CUSTOM, CUSTOM,
} }
@ -498,7 +499,13 @@ export class BeakBlastChargingTag extends BattlerTag {
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.AFTER_HIT) { if (lapseType === BattlerTagLapseType.AFTER_HIT) {
const phaseData = getMoveEffectPhaseData(pokemon); const phaseData = getMoveEffectPhaseData(pokemon);
if (phaseData?.move.hasFlag(MoveFlags.MAKES_CONTACT)) { if (
phaseData?.move.doesFlagEffectApply({
flag: MoveFlags.MAKES_CONTACT,
user: phaseData.attacker,
target: pokemon,
})
) {
phaseData.attacker.trySetStatus(StatusEffect.BURN, true, pokemon); phaseData.attacker.trySetStatus(StatusEffect.BURN, true, pokemon);
} }
return true; return true;
@ -1611,19 +1618,50 @@ export class ProtectedTag extends BattlerTag {
} }
} }
/** Base class for `BattlerTag`s that block damaging moves but not status moves */ /** Class for `BattlerTag`s that apply some effect when hit by a contact move */
export class DamageProtectedTag extends ProtectedTag {} export class ContactProtectedTag extends ProtectedTag {
/**
* Function to call when a contact move hits the pokemon with this tag.
* @param _attacker - The pokemon using the contact move
* @param _user - The pokemon that is being attacked and has the tag
* @param _move - The move used by the attacker
*/
onContact(_attacker: Pokemon, _user: Pokemon) {}
/**
* Lapse the tag and apply `onContact` if the move makes contact and
* `lapseType` is custom, respecting the move's flags and the pokemon's
* abilities, and whether the lapseType is custom.
*
* @param pokemon - The pokemon with the tag
* @param lapseType - The type of lapse to apply. If this is not {@linkcode BattlerTagLapseType.CUSTOM CUSTOM}, no effect will be applied.
* @returns Whether the tag continues to exist after the lapse.
*/
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = super.lapse(pokemon, lapseType);
const moveData = getMoveEffectPhaseData(pokemon);
if (
lapseType === BattlerTagLapseType.CUSTOM &&
moveData &&
moveData.move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: moveData.attacker, target: pokemon })
) {
this.onContact(moveData.attacker, pokemon);
}
return ret;
}
}
/** /**
* `BattlerTag` class for moves that block damaging moves damage the enemy if the enemy's move makes contact * `BattlerTag` class for moves that block damaging moves damage the enemy if the enemy's move makes contact
* Used by {@linkcode Moves.SPIKY_SHIELD} * Used by {@linkcode Moves.SPIKY_SHIELD}
*/ */
export class ContactDamageProtectedTag extends ProtectedTag { export class ContactDamageProtectedTag extends ContactProtectedTag {
private damageRatio: number; private damageRatio: number;
constructor(sourceMove: Moves, damageRatio: number) { constructor(sourceMove: Moves, damageRatio: number) {
super(sourceMove, BattlerTagType.SPIKY_SHIELD); super(sourceMove, BattlerTagType.SPIKY_SHIELD);
this.damageRatio = damageRatio; this.damageRatio = damageRatio;
} }
@ -1636,22 +1674,46 @@ export class ContactDamageProtectedTag extends ProtectedTag {
this.damageRatio = source.damageRatio; this.damageRatio = source.damageRatio;
} }
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { /**
const ret = super.lapse(pokemon, lapseType); * Damage the attacker by `this.damageRatio` of the target's max HP
* @param attacker - The pokemon using the contact move
if (lapseType === BattlerTagLapseType.CUSTOM) { * @param user - The pokemon that is being attacked and has the tag
const effectPhase = globalScene.getCurrentPhase(); */
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { override onContact(attacker: Pokemon, user: Pokemon): void {
const attacker = effectPhase.getPokemon(); const cancelled = new BooleanHolder(false);
if (!attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) { applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled);
if (!cancelled.value) {
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), { attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
result: HitResult.INDIRECT, result: HitResult.INDIRECT,
}); });
} }
} }
}
/** Base class for `BattlerTag`s that block damaging moves but not status moves */
export class DamageProtectedTag extends ContactProtectedTag {}
export class ContactSetStatusProtectedTag extends DamageProtectedTag {
/**
* @param sourceMove The move that caused the tag to be applied
* @param tagType The type of the tag
* @param statusEffect The status effect to apply to the attacker
*/
constructor(
sourceMove: Moves,
tagType: BattlerTagType,
private statusEffect: StatusEffect,
) {
super(sourceMove, tagType);
} }
return ret; /**
* Set the status effect on the attacker
* @param attacker - The pokemon using the contact move
* @param user - The pokemon that is being attacked and has the tag
*/
override onContact(attacker: Pokemon, user: Pokemon): void {
attacker.trySetStatus(this.statusEffect, true, user);
} }
} }
@ -1674,68 +1736,19 @@ export class ContactStatStageChangeProtectedTag extends DamageProtectedTag {
* When given a battler tag or json representing one, load the data for it. * When given a battler tag or json representing one, load the data for it.
* @param {BattlerTag | any} source A battler tag * @param {BattlerTag | any} source A battler tag
*/ */
loadTag(source: BattlerTag | any): void { override loadTag(source: BattlerTag | any): void {
super.loadTag(source); super.loadTag(source);
this.stat = source.stat; this.stat = source.stat;
this.levels = source.levels; this.levels = source.levels;
} }
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean { /**
const ret = super.lapse(pokemon, lapseType); * Initiate the stat stage change on the attacker
* @param attacker - The pokemon using the contact move
if (lapseType === BattlerTagLapseType.CUSTOM) { * @param user - The pokemon that is being attacked and has the tag
const effectPhase = globalScene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
globalScene.unshiftPhase(new StatStageChangePhase(attacker.getBattlerIndex(), false, [this.stat], this.levels));
}
}
return ret;
}
}
export class ContactPoisonProtectedTag extends ProtectedTag {
constructor(sourceMove: Moves) {
super(sourceMove, BattlerTagType.BANEFUL_BUNKER);
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = super.lapse(pokemon, lapseType);
if (lapseType === BattlerTagLapseType.CUSTOM) {
const effectPhase = globalScene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
attacker.trySetStatus(StatusEffect.POISON, true, pokemon);
}
}
return ret;
}
}
/**
* `BattlerTag` class for moves that block damaging moves and burn the enemy if the enemy's move makes contact
* Used by {@linkcode Moves.BURNING_BULWARK}
*/ */
export class ContactBurnProtectedTag extends DamageProtectedTag { override onContact(attacker: Pokemon, _user: Pokemon): void {
constructor(sourceMove: Moves) { globalScene.unshiftPhase(new StatStageChangePhase(attacker.getBattlerIndex(), false, [this.stat], this.levels));
super(sourceMove, BattlerTagType.BURNING_BULWARK);
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = super.lapse(pokemon, lapseType);
if (lapseType === BattlerTagLapseType.CUSTOM) {
const effectPhase = globalScene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
attacker.trySetStatus(StatusEffect.BURN, true);
}
}
return ret;
} }
} }
@ -3518,9 +3531,9 @@ export function getBattlerTag(
case BattlerTagType.SILK_TRAP: case BattlerTagType.SILK_TRAP:
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1); return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1);
case BattlerTagType.BANEFUL_BUNKER: case BattlerTagType.BANEFUL_BUNKER:
return new ContactPoisonProtectedTag(sourceMove); return new ContactSetStatusProtectedTag(sourceMove, tagType, StatusEffect.POISON);
case BattlerTagType.BURNING_BULWARK: case BattlerTagType.BURNING_BULWARK:
return new ContactBurnProtectedTag(sourceMove); return new ContactSetStatusProtectedTag(sourceMove, tagType, StatusEffect.BURN);
case BattlerTagType.ENDURING: case BattlerTagType.ENDURING:
return new EnduringTag(tagType, BattlerTagLapseType.TURN_END, sourceMove); return new EnduringTag(tagType, BattlerTagLapseType.TURN_END, sourceMove);
case BattlerTagType.ENDURE_TOKEN: case BattlerTagType.ENDURE_TOKEN:

View File

@ -128,6 +128,7 @@ import {
TarShotTag, TarShotTag,
AutotomizedTag, AutotomizedTag,
PowerTrickTag, PowerTrickTag,
type GrudgeTag,
} from "../data/battler-tags"; } from "../data/battler-tags";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import { import {
@ -4759,15 +4760,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
new FaintPhase( new FaintPhase(
this.getBattlerIndex(), this.getBattlerIndex(),
false, false,
destinyTag,
grudgeTag,
source, source,
), ),
); );
this.destroySubstitute(); this.destroySubstitute();
this.lapseTag(BattlerTagType.COMMANDED); this.lapseTag(BattlerTagType.COMMANDED);
this.resetSummonData();
} }
return result; return result;
@ -4829,7 +4827,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
); );
this.destroySubstitute(); this.destroySubstitute();
this.lapseTag(BattlerTagType.COMMANDED); this.lapseTag(BattlerTagType.COMMANDED);
this.resetSummonData();
} }
return damage; return damage;
} }
@ -4997,6 +4994,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false; return false;
} }
/**@overload */
getTag(tagType: BattlerTagType.GRUDGE): GrudgeTag | nil;
/** @overload */ /** @overload */
getTag(tagType: BattlerTagType): BattlerTag | nil; getTag(tagType: BattlerTagType): BattlerTag | nil;

View File

@ -9,7 +9,6 @@ import {
PostKnockOutAbAttr, PostKnockOutAbAttr,
PostVictoryAbAttr, PostVictoryAbAttr,
} from "#app/data/abilities/ability"; } from "#app/data/abilities/ability";
import type { DestinyBondTag, GrudgeTag } from "#app/data/battler-tags";
import { BattlerTagLapseType } from "#app/data/battler-tags"; import { BattlerTagLapseType } from "#app/data/battler-tags";
import { battleSpecDialogue } from "#app/data/dialogue"; import { battleSpecDialogue } from "#app/data/dialogue";
import { allMoves, PostVictoryStatStageChangeAttr } from "#app/data/moves/move"; import { allMoves, PostVictoryStatStageChangeAttr } from "#app/data/moves/move";
@ -32,6 +31,7 @@ import { ToggleDoublePositionPhase } from "./toggle-double-position-phase";
import { VictoryPhase } from "./victory-phase"; import { VictoryPhase } from "./victory-phase";
import { isNullOrUndefined } from "#app/utils"; import { isNullOrUndefined } from "#app/utils";
import { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters"; import { FRIENDSHIP_LOSS_FROM_FAINT } from "#app/data/balance/starters";
import { BattlerTagType } from "#enums/battler-tag-type";
export class FaintPhase extends PokemonPhase { export class FaintPhase extends PokemonPhase {
/** /**
@ -39,33 +39,15 @@ export class FaintPhase extends PokemonPhase {
*/ */
private preventEndure: boolean; private preventEndure: boolean;
/**
* Destiny Bond tag belonging to the currently fainting Pokemon, if applicable
*/
private destinyTag?: DestinyBondTag | null;
/**
* Grudge tag belonging to the currently fainting Pokemon, if applicable
*/
private grudgeTag?: GrudgeTag | null;
/** /**
* The source Pokemon that dealt fatal damage * The source Pokemon that dealt fatal damage
*/ */
private source?: Pokemon; private source?: Pokemon;
constructor( constructor(battlerIndex: BattlerIndex, preventEndure = false, source?: Pokemon) {
battlerIndex: BattlerIndex,
preventEndure = false,
destinyTag?: DestinyBondTag | null,
grudgeTag?: GrudgeTag | null,
source?: Pokemon,
) {
super(battlerIndex); super(battlerIndex);
this.preventEndure = preventEndure; this.preventEndure = preventEndure;
this.destinyTag = destinyTag;
this.grudgeTag = grudgeTag;
this.source = source; this.source = source;
} }
@ -74,13 +56,12 @@ export class FaintPhase extends PokemonPhase {
const faintPokemon = this.getPokemon(); const faintPokemon = this.getPokemon();
if (!isNullOrUndefined(this.destinyTag) && !isNullOrUndefined(this.source)) { if (this.source) {
this.destinyTag.lapse(this.source, BattlerTagLapseType.CUSTOM); faintPokemon.getTag(BattlerTagType.DESTINY_BOND)?.lapse(this.source, BattlerTagLapseType.CUSTOM);
faintPokemon.getTag(BattlerTagType.GRUDGE)?.lapse(faintPokemon, BattlerTagLapseType.CUSTOM, this.source);
} }
if (!isNullOrUndefined(this.grudgeTag) && !isNullOrUndefined(this.source)) { faintPokemon.resetSummonData();
this.grudgeTag.lapse(faintPokemon, BattlerTagLapseType.CUSTOM, this.source);
}
if (!this.preventEndure) { if (!this.preventEndure) {
const instantReviveModifier = globalScene.applyModifier( const instantReviveModifier = globalScene.applyModifier(

View File

@ -627,18 +627,20 @@ export class MoveEffectPhase extends PokemonPhase {
* @param hitResult - The {@linkcode HitResult} of the attempted move * @param hitResult - The {@linkcode HitResult} of the attempted move
* @returns a `Promise` intended to be passed into a `then()` call. * @returns a `Promise` intended to be passed into a `then()` call.
*/ */
protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult): void { protected applyOnGetHitAbEffects(user: Pokemon, target: Pokemon, hitResult: HitResult) {
const hitsSubstitute = this.move.getMove().hitsSubstitute(user, target);
if (!target.isFainted() || target.canApplyAbility()) { if (!target.isFainted() || target.canApplyAbility()) {
applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult); applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move.getMove(), hitResult);
if (!this.move.getMove().hitsSubstitute(user, target)) { if (!hitsSubstitute) {
if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) { if (!user.isPlayer() && this.move.getMove() instanceof AttackMove) {
globalScene.applyShuffledModifiers(EnemyAttackStatusEffectChanceModifier, false, target); globalScene.applyShuffledModifiers(EnemyAttackStatusEffectChanceModifier, false, target);
} }
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
} }
} }
if (!hitsSubstitute) {
target.lapseTags(BattlerTagLapseType.AFTER_HIT);
}
} }
/** /**

View File

@ -31,9 +31,9 @@ uniform vec2 texSize;
uniform float yOffset; uniform float yOffset;
uniform float yShadowOffset; uniform float yShadowOffset;
uniform vec4 tone; uniform vec4 tone;
uniform ivec4 baseVariantColors[32]; uniform vec4 baseVariantColors[32];
uniform vec4 variantColors[32]; uniform vec4 variantColors[32];
uniform ivec4 spriteColors[32]; uniform vec4 spriteColors[32];
uniform ivec4 fusionSpriteColors[32]; uniform ivec4 fusionSpriteColors[32];
const vec3 lumaF = vec3(.299, .587, .114); const vec3 lumaF = vec3(.299, .587, .114);
@ -69,7 +69,6 @@ float hue2rgb(float f1, float f2, float hue) {
vec3 rgb2hsl(vec3 color) { vec3 rgb2hsl(vec3 color) {
vec3 hsl; vec3 hsl;
float fmin = min(min(color.r, color.g), color.b); float fmin = min(min(color.r, color.g), color.b);
float fmax = max(max(color.r, color.g), color.b); float fmax = max(max(color.r, color.g), color.b);
float delta = fmax - fmin; float delta = fmax - fmin;
@ -152,34 +151,23 @@ vec3 hsv2rgb(vec3 c) {
void main() { void main() {
vec4 texture = texture2D(uMainSampler[0], outTexCoord); vec4 texture = texture2D(uMainSampler[0], outTexCoord);
ivec4 colorInt = ivec4(texture*255.0);
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
if (baseVariantColors[i][3] == 0) if (baseVariantColors[i].a == 0.0)
break; break;
// abs value is broken in this version of gles with highp if (texture.a > 0.0 && all(lessThan(abs(texture.rgb - baseVariantColors[i].rgb), vec3(1.0/255.0)))) {
ivec3 diffs = ivec3(
(colorInt.r > baseVariantColors[i].r) ? colorInt.r - baseVariantColors[i].r : baseVariantColors[i].r - colorInt.r,
(colorInt.g > baseVariantColors[i].g) ? colorInt.g - baseVariantColors[i].g : baseVariantColors[i].g - colorInt.g,
(colorInt.b > baseVariantColors[i].b) ? colorInt.b - baseVariantColors[i].b : baseVariantColors[i].b - colorInt.b
);
// Set color threshold to be within 3 points for each channel
bvec3 threshold = lessThan(diffs, ivec3(3));
if (texture.a > 0.0 && all(threshold)) {
texture.rgb = variantColors[i].rgb; texture.rgb = variantColors[i].rgb;
break; break;
} }
} }
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
if (spriteColors[i][3] == 0) if (spriteColors[i][3] == 0.0)
break; break;
if (texture.a > 0.0 && colorInt.r == spriteColors[i].r && colorInt.g == spriteColors[i].g && colorInt.b == spriteColors[i].b) { if (texture.a > 0.0 && all(lessThan(abs(texture.rgb - spriteColors[i].rgb), vec3(1.0/255.0)))) {
vec3 fusionColor = vec3(float(fusionSpriteColors[i].r) / 255.0, float(fusionSpriteColors[i].g) / 255.0, float(fusionSpriteColors[i].b) / 255.0); vec3 fusionColor = vec3(fusionSpriteColors[i].rgb) / 255.0;
vec3 bg = vec3(spriteColors[i].rgb) / 255.0; vec3 bg = spriteColors[i].rgb;
float gray = (bg.r + bg.g + bg.b) / 3.0; float gray = (bg.r + bg.g + bg.b) / 3.0;
bg = vec3(gray, gray, gray); bg = vec3(gray);
vec3 fg = fusionColor; vec3 fg = fusionColor;
texture.rgb = mix(1.0 - 2.0 * (1.0 - bg) * (1.0 - fg), 2.0 * bg * fg, step(bg, vec3(0.5))); texture.rgb = mix(1.0 - 2.0 * (1.0 - bg) * (1.0 - fg), 2.0 * bg * fg, step(bg, vec3(0.5)));
break; break;
@ -192,7 +180,7 @@ void main() {
vec4 color = texture * texel; vec4 color = texture * texel;
if (color.a > 0.0 && teraColor.r > 0.0 && teraColor.g > 0.0 && teraColor.b > 0.0) { if (color.a > 0.0 && teraColor.r > 0.0 && teraColor.g > 0.0 && teraColor.b > 0.0) {
vec2 relUv = vec2((outTexCoord.x - texFrameUv.x) / (size.x / texSize.x), (outTexCoord.y - texFrameUv.y) / (size.y / texSize.y)); vec2 relUv = (outTexCoord.xy - texFrameUv.xy) / (size.xy / texSize.xy);
vec2 teraTexCoord = vec2(relUv.x * (size.x / 200.0), relUv.y * (size.y / 120.0)); vec2 teraTexCoord = vec2(relUv.x * (size.x / 200.0), relUv.y * (size.y / 120.0));
vec4 teraCol = texture2D(uMainSampler[1], teraTexCoord); vec4 teraCol = texture2D(uMainSampler[1], teraTexCoord);
float floorValue = 86.0 / 255.0; float floorValue = 86.0 / 255.0;
@ -265,8 +253,8 @@ void main() {
if ((spriteY >= 0.9 && (color.a == 0.0 || yOverflow))) { if ((spriteY >= 0.9 && (color.a == 0.0 || yOverflow))) {
float shadowSpriteY = (spriteY - 0.9) * (1.0 / 0.15); float shadowSpriteY = (spriteY - 0.9) * (1.0 / 0.15);
if (distance(vec2(spriteX, shadowSpriteY), vec2(0.5, 0.5)) < 0.5) { if (distance(vec2(spriteX, shadowSpriteY), vec2(0.5)) < 0.5) {
color = vec4(vec3(0.0, 0.0, 0.0), 0.5); color = vec4(vec3(0.0), 0.5);
} else if (yOverflow) { } else if (yOverflow) {
discard; discard;
} }

View File

@ -11,7 +11,6 @@ attribute float inTintEffect;
attribute vec4 inTint; attribute vec4 inTint;
varying vec2 outTexCoord; varying vec2 outTexCoord;
varying vec2 outtexFrameUv;
varying float outTexId; varying float outTexId;
varying vec2 outPosition; varying vec2 outPosition;
varying float outTintEffect; varying float outTintEffect;

View File

@ -101,7 +101,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
flatSpriteColors.splice( flatSpriteColors.splice(
flatSpriteColors.length, flatSpriteColors.length,
0, 0,
...(c < spriteColors.length ? spriteColors[c] : emptyColors), ...(c < spriteColors.length ? spriteColors[c].map(x => x / 255.0) : emptyColors),
); );
flatFusionSpriteColors.splice( flatFusionSpriteColors.splice(
flatFusionSpriteColors.length, flatFusionSpriteColors.length,
@ -110,7 +110,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
); );
} }
this.set4iv("spriteColors", flatSpriteColors.flat()); this.set4fv("spriteColors", flatSpriteColors.flat());
this.set4iv("fusionSpriteColors", flatFusionSpriteColors.flat()); this.set4iv("fusionSpriteColors", flatFusionSpriteColors.flat());
} }
} }
@ -146,7 +146,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
if (c < baseColors.length) { if (c < baseColors.length) {
const baseColor = Array.from(Object.values(rgbHexToRgba(baseColors[c]))); const baseColor = Array.from(Object.values(rgbHexToRgba(baseColors[c])));
const variantColor = Array.from(Object.values(rgbHexToRgba(variantColors[variant][baseColors[c]]))); const variantColor = Array.from(Object.values(rgbHexToRgba(variantColors[variant][baseColors[c]])));
flatBaseColors.splice(flatBaseColors.length, 0, ...baseColor); flatBaseColors.splice(flatBaseColors.length, 0, ...baseColor.map(c => c / 255.0));
flatVariantColors.splice(flatVariantColors.length, 0, ...variantColor.map(c => c / 255.0)); flatVariantColors.splice(flatVariantColors.length, 0, ...variantColor.map(c => c / 255.0));
} else { } else {
flatBaseColors.splice(flatBaseColors.length, 0, ...emptyColors); flatBaseColors.splice(flatBaseColors.length, 0, ...emptyColors);
@ -160,7 +160,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
} }
} }
this.set4iv("baseVariantColors", flatBaseColors.flat()); this.set4fv("baseVariantColors", flatBaseColors.flat());
this.set4fv("variantColors", flatVariantColors.flat()); this.set4fv("variantColors", flatVariantColors.flat());
} }

View File

@ -405,8 +405,11 @@ export function deltaRgb(rgb1: number[], rgb2: number[]): number {
return Math.ceil(Math.sqrt(2 * drp2 + 4 * dgp2 + 3 * dbp2 + (t * (drp2 - dbp2)) / 256)); return Math.ceil(Math.sqrt(2 * drp2 + 4 * dgp2 + 3 * dbp2 + (t * (drp2 - dbp2)) / 256));
} }
// Extract out the rgb values from a hex string
const hexRegex = /^([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i;
export function rgbHexToRgba(hex: string) { export function rgbHexToRgba(hex: string) {
const color = hex.match(/^([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i) ?? ["000000", "00", "00", "00"]; const color = hex.match(hexRegex) ?? ["000000", "00", "00", "00"];
return { return {
r: Number.parseInt(color[1], 16), r: Number.parseInt(color[1], 16),
g: Number.parseInt(color[2], 16), g: Number.parseInt(color[2], 16),

View File

@ -38,7 +38,7 @@ describe("Moves - Beak Blast", () => {
}); });
it("should add a charge effect that burns attackers on contact", async () => { it("should add a charge effect that burns attackers on contact", async () => {
await game.startBattle([Species.BLASTOISE]); await game.classicMode.startBattle([Species.BLASTOISE]);
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
@ -55,7 +55,7 @@ describe("Moves - Beak Blast", () => {
it("should still charge and burn opponents if the user is sleeping", async () => { it("should still charge and burn opponents if the user is sleeping", async () => {
game.override.statusEffect(StatusEffect.SLEEP); game.override.statusEffect(StatusEffect.SLEEP);
await game.startBattle([Species.BLASTOISE]); await game.classicMode.startBattle([Species.BLASTOISE]);
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
@ -72,7 +72,7 @@ describe("Moves - Beak Blast", () => {
it("should not burn attackers that don't make contact", async () => { it("should not burn attackers that don't make contact", async () => {
game.override.enemyMoveset([Moves.WATER_GUN]); game.override.enemyMoveset([Moves.WATER_GUN]);
await game.startBattle([Species.BLASTOISE]); await game.classicMode.startBattle([Species.BLASTOISE]);
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
@ -89,7 +89,7 @@ describe("Moves - Beak Blast", () => {
it("should only hit twice with Multi-Lens", async () => { it("should only hit twice with Multi-Lens", async () => {
game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]); game.override.startingHeldItems([{ name: "MULTI_LENS", count: 1 }]);
await game.startBattle([Species.BLASTOISE]); await game.classicMode.startBattle([Species.BLASTOISE]);
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
@ -102,7 +102,7 @@ describe("Moves - Beak Blast", () => {
it("should be blocked by Protect", async () => { it("should be blocked by Protect", async () => {
game.override.enemyMoveset([Moves.PROTECT]); game.override.enemyMoveset([Moves.PROTECT]);
await game.startBattle([Species.BLASTOISE]); await game.classicMode.startBattle([Species.BLASTOISE]);
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!; const enemyPokemon = game.scene.getEnemyPokemon()!;
@ -116,4 +116,25 @@ describe("Moves - Beak Blast", () => {
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp()); expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
expect(leadPokemon.getTag(BattlerTagType.BEAK_BLAST_CHARGING)).toBeUndefined(); expect(leadPokemon.getTag(BattlerTagType.BEAK_BLAST_CHARGING)).toBeUndefined();
}); });
it("should still burn the enemy if the user is knocked out", async () => {
game.override.ability(Abilities.BALL_FETCH);
await game.classicMode.startBattle([Species.MAGIKARP, Species.MAGIKARP]);
const enemyPokemon = game.scene.getEnemyPokemon()!;
const user = game.scene.getPlayerPokemon()!;
user.hp = 1;
game.move.select(Moves.BEAK_BLAST);
await game.phaseInterceptor.to("BerryPhase", false);
expect(enemyPokemon.status?.effect).toBe(StatusEffect.BURN);
});
it("should not burn a long reach enemy that hits the user with a contact move", async () => {
game.override.enemyAbility(Abilities.LONG_REACH);
game.override.enemyMoveset([Moves.FALSE_SWIPE]).enemyLevel(100);
await game.classicMode.startBattle([Species.MAGIKARP]);
game.move.select(Moves.BEAK_BLAST);
await game.phaseInterceptor.to("BerryPhase", false);
const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon.status?.effect).not.toBe(StatusEffect.BURN);
});
}); });