diff --git a/src/@types/sprite-types.ts b/src/@types/sprite-types.ts new file mode 100644 index 00000000000..de329268826 --- /dev/null +++ b/src/@types/sprite-types.ts @@ -0,0 +1,5 @@ +/** + * Tuple type representing a set of RGB color values. + * Values should be in integer form (inside the interval `[0, 255]`). + */ +export type RGBArray = [r: number, g: number, b: number]; diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 289c9a8f051..59da590e9b0 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -299,7 +299,11 @@ export class BattleScene extends SceneBase { public seed: string; public waveSeed: string; public waveCycleOffset: number; - public offsetGym: boolean; + /** + * Whether to offset Gym Leader waves by 10 (30, 50, 70 instead of 20, 40, 60). + * Determined at the start of the run, and is unused for non-Classic game modes. + */ + public offsetGym = false; public damageNumberHandler: DamageNumberHandler; private spriteSparkleHandler: PokemonSpriteSparkleHandler; diff --git a/src/data/terrain.ts b/src/data/terrain.ts index 315ed919e03..7c37c04e5c4 100644 --- a/src/data/terrain.ts +++ b/src/data/terrain.ts @@ -4,6 +4,7 @@ import { PokemonType } from "#enums/pokemon-type"; import type { Pokemon } from "#field/pokemon"; import type { Move } from "#moves/move"; import { isFieldTargeted, isSpreadMove } from "#moves/move-utils"; +import type { RGBArray } from "#types/sprite-types"; import i18next from "i18next"; export enum TerrainType { @@ -93,7 +94,7 @@ export function getTerrainName(terrainType: TerrainType): string { return ""; } -export function getTerrainColor(terrainType: TerrainType): [number, number, number] { +export function getTerrainColor(terrainType: TerrainType): RGBArray { switch (terrainType) { case TerrainType.MISTY: return [232, 136, 200]; diff --git a/src/field/arena.ts b/src/field/arena.ts index 3e214ff1ea7..f58f3e9ee7a 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -35,6 +35,7 @@ import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEven import type { Pokemon } from "#field/pokemon"; import { FieldEffectModifier } from "#modifiers/modifier"; import type { Move } from "#moves/move"; +import type { RGBArray } from "#types/sprite-types"; import type { AbstractConstructor } from "#types/type-helpers"; import { type Constructor, NumberHolder, randSeedInt } from "#utils/common"; import { getPokemonSpecies } from "#utils/pokemon-utils"; @@ -543,22 +544,24 @@ export class Arena { } } - getTimeOfDay(): TimeOfDay { + public getTimeOfDay(): TimeOfDay { switch (this.biomeType) { case BiomeId.ABYSS: return TimeOfDay.NIGHT; } - const waveCycle = ((globalScene.currentBattle?.waveIndex || 0) + globalScene.waveCycleOffset) % 40; + if (Overrides.TIME_OF_DAY_OVERRIDE !== null) { + return Overrides.TIME_OF_DAY_OVERRIDE; + } + + const waveCycle = ((globalScene.currentBattle?.waveIndex ?? 0) + globalScene.waveCycleOffset) % 40; if (waveCycle < 15) { return TimeOfDay.DAY; } - if (waveCycle < 20) { return TimeOfDay.DUSK; } - if (waveCycle < 35) { return TimeOfDay.NIGHT; } @@ -566,6 +569,10 @@ export class Arena { return TimeOfDay.DAWN; } + /** + * @returns Whether the current biome takes place "outdoors" + * (for the purposes of time of day tints) + */ isOutside(): boolean { switch (this.biomeType) { case BiomeId.SEABED: @@ -584,23 +591,7 @@ export class Arena { } } - overrideTint(): [number, number, number] { - switch (Overrides.ARENA_TINT_OVERRIDE) { - case TimeOfDay.DUSK: - return [98, 48, 73].map(c => Math.round((c + 128) / 2)) as [number, number, number]; - case TimeOfDay.NIGHT: - return [64, 64, 64]; - case TimeOfDay.DAWN: - case TimeOfDay.DAY: - default: - return [128, 128, 128]; - } - } - - getDayTint(): [number, number, number] { - if (Overrides.ARENA_TINT_OVERRIDE !== null) { - return this.overrideTint(); - } + getDayTint(): RGBArray { switch (this.biomeType) { case BiomeId.ABYSS: return [64, 64, 64]; @@ -609,24 +600,15 @@ export class Arena { } } - getDuskTint(): [number, number, number] { - if (Overrides.ARENA_TINT_OVERRIDE) { - return this.overrideTint(); - } + getDuskTint(): RGBArray { if (!this.isOutside()) { return [0, 0, 0]; } - switch (this.biomeType) { - default: - return [98, 48, 73].map(c => Math.round((c + 128) / 2)) as [number, number, number]; - } + return [113, 88, 101]; } - getNightTint(): [number, number, number] { - if (Overrides.ARENA_TINT_OVERRIDE) { - return this.overrideTint(); - } + getNightTint(): RGBArray { switch (this.biomeType) { case BiomeId.ABYSS: case BiomeId.SPACE: @@ -638,10 +620,7 @@ export class Arena { return [64, 64, 64]; } - switch (this.biomeType) { - default: - return [48, 48, 98]; - } + return [48, 48, 98]; } setIgnoreAbilities(ignoreAbilities: boolean, ignoringEffectSource: BattlerIndex | null = null): void { diff --git a/src/overrides.ts b/src/overrides.ts index 3f61196f0b4..dd84e6887a8 100644 --- a/src/overrides.ts +++ b/src/overrides.ts @@ -76,7 +76,13 @@ class DefaultOverrides { readonly BATTLE_STYLE_OVERRIDE: BattleStyle | null = null; readonly STARTING_WAVE_OVERRIDE: number = 0; readonly STARTING_BIOME_OVERRIDE: BiomeId | null = null; - readonly ARENA_TINT_OVERRIDE: TimeOfDay | null = null; + /** + * Overrides the Time of Day for the given biome. + * Set to `null` to disable. + * @remarks + * Will also influence field sprite tint coloration. + */ + readonly TIME_OF_DAY_OVERRIDE: Exclude | null = null; /** Multiplies XP gained by this value including 0. Set to null to ignore the override. */ readonly XP_MULTIPLIER_OVERRIDE: number | null = null; /** diff --git a/src/pipelines/field-sprite.ts b/src/pipelines/field-sprite.ts index 9132fd585ec..04b1e4c0f0d 100644 --- a/src/pipelines/field-sprite.ts +++ b/src/pipelines/field-sprite.ts @@ -1,5 +1,8 @@ import { globalScene } from "#app/global-scene"; -import { getTerrainColor, TerrainType } from "#data/terrain"; +import Overrides from "#app/overrides"; +import { getTerrainColor } from "#data/terrain"; +import { TimeOfDay } from "#enums/time-of-day"; +import type { RGBArray } from "#types/sprite-types"; import { getCurrentTime } from "#utils/common"; import Phaser from "phaser"; import fieldSpriteFragShader from "./glsl/field-sprite-frag-shader.frag?raw"; @@ -30,15 +33,17 @@ export class FieldSpritePipeline extends Phaser.Renderer.WebGL.Pipelines.MultiPi const sprite = gameObject as Phaser.GameObjects.Sprite | Phaser.GameObjects.NineSlice; const data = sprite.pipelineData; - const ignoreTimeTint = data["ignoreTimeTint"] as boolean; - const terrainColorRatio = (data["terrainColorRatio"] as number) || 0; + const ignoreTimeTint = !!data["ignoreTimeTint"]; + const terrainColorRatio = (data["terrainColorRatio"] as number) ?? 0; const time = globalScene.currentBattle?.waveIndex ? ((globalScene.currentBattle.waveIndex + globalScene.waveCycleOffset) % 40) / 40 // ((new Date().getSeconds() * 1000 + new Date().getMilliseconds()) % 10000) / 10000 : getCurrentTime(); + this.set1f("time", time); - this.set1i("ignoreTimeTint", ignoreTimeTint ? 1 : 0); - this.set1i("isOutside", globalScene.arena.isOutside() ? 1 : 0); + this.setBoolean("ignoreTimeTint", ignoreTimeTint); + this.setBoolean("isOutside", globalScene.arena.isOutside()); + this.set3fv("overrideTint", overrideTint()); this.set3fv( "dayTint", globalScene.arena.getDayTint().map(c => c / 255), @@ -53,7 +58,7 @@ export class FieldSpritePipeline extends Phaser.Renderer.WebGL.Pipelines.MultiPi ); this.set3fv( "terrainColor", - getTerrainColor(globalScene.arena.terrain?.terrainType || TerrainType.NONE).map(c => c / 255), + getTerrainColor(globalScene.arena.getTerrainType()).map(c => c / 255), ); this.set1f("terrainColorRatio", terrainColorRatio); } @@ -64,3 +69,21 @@ export class FieldSpritePipeline extends Phaser.Renderer.WebGL.Pipelines.MultiPi } } } + +/** + * Override the current arena tint based on the Time of day override + * @returns The overriden tint colors as an RGB array. + */ +function overrideTint(): RGBArray { + switch (Overrides.TIME_OF_DAY_OVERRIDE) { + case TimeOfDay.DAY: + case TimeOfDay.DAWN: + return globalScene.arena.getDayTint(); + case TimeOfDay.DUSK: + return globalScene.arena.getDuskTint(); + case TimeOfDay.NIGHT: + return globalScene.arena.getNightTint(); + default: + return [0, 0, 0]; + } +} diff --git a/src/pipelines/glsl/field-sprite-frag-shader.frag b/src/pipelines/glsl/field-sprite-frag-shader.frag index f11f6308808..6bb82f55531 100644 --- a/src/pipelines/glsl/field-sprite-frag-shader.frag +++ b/src/pipelines/glsl/field-sprite-frag-shader.frag @@ -20,8 +20,9 @@ varying float outTintEffect; varying vec4 outTint; uniform float time; -uniform int ignoreTimeTint; -uniform int isOutside; +uniform bool ignoreTimeTint; +uniform bool isOutside; +uniform vec3 overrideTint; uniform vec3 dayTint; uniform vec3 duskTint; uniform vec3 nightTint; @@ -142,10 +143,12 @@ void main() { } /* Apply day/night tint */ - if (color.a > 0.0 && ignoreTimeTint == 0) { + if (color.a > 0.0 && ignoreTimeTint) { vec3 dayNightTint; - if (time < 0.25) { + if (any(lessThan(vec3(0.0), overrideTint))) { + dayNightTint = overrideTint; + } else if (time < 0.25) { dayNightTint = dayTint; } else if (isOutside == 0 && time < 0.5) { dayNightTint = mix(dayTint, nightTint, (time - 0.25) / 0.25); @@ -166,10 +169,13 @@ void main() { color = vec4(blendHardLight(color.rgb, dayNightTint), color.a); } - if (terrainColorRatio > 0.0 && (1.0 - terrainColorRatio) < outTexCoord.y) { - if (color.a > 0.0 && (terrainColor.r > 0.0 || terrainColor.g > 0.0 || terrainColor.b > 0.0)) { - color.rgb = mix(color.rgb, blendHue(color.rgb, terrainColor), 1.0); - } + if ( + terrainColorRatio > 0.0 + && (1.0 - terrainColorRatio) < outTexCoord.y + && color.a > 0.0 + && (any(lessThan(vec3(0.0), terrainColor))) + ) { + color.rgb = mix(color.rgb, blendHue(color.rgb, terrainColor), 1.0); } gl_FragColor = color; diff --git a/src/pipelines/glsl/sprite-frag-shader.frag b/src/pipelines/glsl/sprite-frag-shader.frag index e2c7f0d0e84..880ea5d7bb3 100644 --- a/src/pipelines/glsl/sprite-frag-shader.frag +++ b/src/pipelines/glsl/sprite-frag-shader.frag @@ -21,14 +21,15 @@ varying float outTintEffect; varying vec4 outTint; uniform float time; -uniform int ignoreTimeTint; -uniform int isOutside; +uniform bool ignoreTimeTint; +uniform bool isOutside; +uniform vec3 overrideTint; uniform vec3 dayTint; uniform vec3 duskTint; uniform vec3 nightTint; uniform float teraTime; uniform vec3 teraColor; -uniform int hasShadow; +uniform bool hasShadow; uniform int yCenter; uniform float fieldScale; uniform float vCutoff; @@ -187,7 +188,7 @@ void main() { // Multiply texture tint 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 && all(lessThan(vec3(0.0), teraColor))) { 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)); vec4 teraCol = texture2D(uMainSampler[1], teraTexCoord); @@ -221,10 +222,12 @@ void main() { color.rgb += tone.rgb * (color.a / 255.0); /* Apply day/night tint */ - if (color.a > 0.0 && ignoreTimeTint == 0) { + if (color.a > 0.0 && !ignoreTimeTint) { vec3 dayNightTint; - if (time < 0.25) { + if (any(lessThan(overrideTint, vec3(0.0)))) { + dayNightTint = overrideTint; + } else if (time < 0.25) { dayNightTint = dayTint; } else if (isOutside == 0 && time < 0.5) { dayNightTint = mix(dayTint, nightTint, (time - 0.25) / 0.25); @@ -245,7 +248,7 @@ void main() { color.rgb = blendHardLight(color.rgb, dayNightTint); } - if (hasShadow == 1) { + if (hasShadow) { float width = size.x - (yOffset / 2.0); float spriteX = ((floor(outPosition.x / fieldScale) - relPosition.x) / width) + 0.5; diff --git a/src/pipelines/sprite.ts b/src/pipelines/sprite.ts index 65f8007e2f8..ca7d234c0f9 100644 --- a/src/pipelines/sprite.ts +++ b/src/pipelines/sprite.ts @@ -27,7 +27,7 @@ export class SpritePipeline extends FieldSpritePipeline { this.set1f("teraTime", 0); this.set3fv("teraColor", [0, 0, 0]); - this.set1i("hasShadow", 0); + this.setBoolean("hasShadow", false); this.set1i("yCenter", 0); this.set2f("relPosition", 0, 0); this.set2f("texFrameUv", 0, 0); @@ -73,7 +73,7 @@ export class SpritePipeline extends FieldSpritePipeline { "teraColor", teraColor.map(c => c / 255), ); - this.set1i("hasShadow", hasShadow ? 1 : 0); + this.setBoolean("hasShadow", hasShadow); this.set1i("yCenter", sprite.originY === 0.5 ? 1 : 0); this.set1f("fieldScale", field?.scale || 1); this.set2f("relPosition", position[0], position[1]);