diff --git a/src/components/game/map/Effects.vue b/src/components/game/map/Effects.vue index d9448c9..d57447b 100644 --- a/src/components/game/map/Effects.vue +++ b/src/components/game/map/Effects.vue @@ -10,8 +10,30 @@ import { useMapStore } from '@/stores/mapStore' import { Scene } from 'phavuer' import { onBeforeUnmount, onMounted, ref, watch } from 'vue' +// Types +interface LightConfig { + SUNRISE_HOUR: number + SUNSET_HOUR: number + DAY_STRENGTH: number + NIGHT_STRENGTH: number + TRANSITION_HOURS: number +} + +interface EffectObjects { + light: Phaser.GameObjects.Graphics | null + rain: Phaser.GameObjects.Particles.ParticleEmitter | null + fog: Phaser.GameObjects.Sprite | null +} + +interface EffectValues { + light?: number + rain?: number + fog?: number + [key: string]: number | undefined +} + // Constants -const LIGHT_CONFIG = { +const LIGHT_CONFIG: LightConfig = { SUNRISE_HOUR: 6, SUNSET_HOUR: 20, DAY_STRENGTH: 100, @@ -19,134 +41,143 @@ const LIGHT_CONFIG = { TRANSITION_HOURS: 3 } -// Stores and refs +// Composables +const useEffects = () => { + const effects = ref({ + light: null, + rain: null, + fog: null + }) + + const initializeEffects = (scene: Phaser.Scene) => { + effects.value.light = scene.add.graphics().setDepth(1000) + + effects.value.rain = scene.add + .particles(0, 0, 'raindrop', { + x: { min: 0, max: window.innerWidth }, + y: -50, + quantity: 5, + lifespan: 2000, + speedY: { min: 300, max: 500 }, + scale: { start: 0.005, end: 0.005 }, + alpha: { start: 0.5, end: 0 }, + blendMode: 'ADD' + }) + .setDepth(900) + effects.value.rain.stop() + + effects.value.fog = scene.add + .sprite(window.innerWidth / 2, window.innerHeight / 2, 'fog') + .setScale(2) + .setAlpha(0) + .setDepth(950) + } + + const applyEffects = (effectValues: EffectValues) => { + if (effects.value.light) { + const darkness = 1 - (effectValues.light ?? 0) / 100 + effects.value.light.clear().fillStyle(0x000000, darkness).fillRect(0, 0, window.innerWidth, window.innerHeight) + } + + if (effects.value.rain) { + if (effectValues.rain) { + effects.value.rain.start().setQuantity(effectValues.rain / 10) + } else { + effects.value.rain.stop() + } + } + + if (effects.value.fog && effectValues.fog !== undefined) { + effects.value.fog.setAlpha(effectValues.fog / 100) + } + } + + const handleResize = () => { + if (effects.value.rain) { + effects.value.rain.updateConfig({ x: { min: 0, max: window.innerWidth } }) + } + if (effects.value.fog) { + effects.value.fog.setPosition(window.innerWidth / 2, window.innerHeight / 2) + } + } + + return { effects, initializeEffects, applyEffects, handleResize } +} + +// Store instances const gameStore = useGameStore() const mapStore = useMapStore() const mapStorage = new MapStorage() + +// State const sceneRef = ref(null) const mapObject = ref(null) - -// Effect objects -const effects = { - light: ref(null), - rain: ref(null), - fog: ref(null) -} - -// Weather state const weatherState = ref({ rainPercentage: 0, fogDensity: 0 }) -// Scene setup -const preloadScene = (scene: Phaser.Scene) => { - scene.load.image('raindrop', 'assets/raindrop.png') - scene.load.image('fog', 'assets/fog.png') -} +// Effects management +const { effects, initializeEffects, applyEffects, handleResize } = useEffects() -const createScene = (scene: Phaser.Scene) => { - sceneRef.value = scene - initializeEffects(scene) - setupSocketListeners() -} - -const loadMap = async () => { - if (!mapStore.mapId) return - mapObject.value = await mapStorage.get(mapStore.mapId) -} - -// Watch for mapId changes and load map when it's available -watch( - () => mapStore.mapId, - async (newMapId) => { - if (newMapId) { - await loadMap() - updateScene() - } - }, - { immediate: true } -) - -const initializeEffects = (scene: Phaser.Scene) => { - // Light - effects.light.value = scene.add.graphics().setDepth(1000) - - // Rain - effects.rain.value = scene.add - .particles(0, 0, 'raindrop', { - x: { min: 0, max: window.innerWidth }, - y: -50, - quantity: 5, - lifespan: 2000, - speedY: { min: 300, max: 500 }, - scale: { start: 0.005, end: 0.005 }, - alpha: { start: 0.5, end: 0 }, - blendMode: 'ADD' - }) - .setDepth(900) - effects.rain.value.stop() - - // Fog - effects.fog.value = scene.add - .sprite(window.innerWidth / 2, window.innerHeight / 2, 'fog') - .setScale(2) - .setAlpha(0) - .setDepth(950) -} - -// Effect updates -const updateScene = () => { - const timeBasedLight = calculateLightStrength(gameStore.world.date) - const mapEffects = mapObject.value?.mapEffects?.reduce( - (acc, curr) => ({ - ...acc, - [curr.effect]: curr.strength - }), - {} - ) as { [key: string]: number } - - const finalEffects = { ...mapEffects, light: timeBasedLight, rain: weatherState.value.rainPercentage, fog: weatherState.value.fogDensity } - applyEffects(finalEffects) -} - -const applyEffects = (effectValues: any) => { - if (effects.light.value) { - const darkness = 1 - effectValues.light / 100 - effects.light.value.clear().fillStyle(0x000000, darkness).fillRect(0, 0, window.innerWidth, window.innerHeight) - } - - if (effects.rain.value) { - if (effectValues.rain.value) effects.rain.value.start().setQuantity(effectValues.rain / 10) - else effects.rain.value.stop() - } - - if (effectValues.fog.value && effects.fog.value) effects.fog.value.setAlpha(effectValues.fog / 100) -} - -// Linear Interpolation -// moves a value between x and y where a is the percentage of progress the value has made in moving from x to y -// a = 0 return x -// a = 0.5 return (x+y)/2 -// a = 1 return y -const lerp = (x: number, y: number, a: number) => x * (1 - a) + y * a +// Utility functions +const lerp = (x: number, y: number, a: number): number => x * (1 - a) + y * a const calculateLightStrength = (time: Date): number => { const hour = time.getHours() const minute = time.getMinutes() - const totalMinutes = (hour - (LIGHT_CONFIG.SUNSET_HOUR - 2)) * 60 + minute - - //Transition from daylight to night - if (hour >= LIGHT_CONFIG.SUNSET_HOUR - LIGHT_CONFIG.TRANSITION_HOURS && hour < LIGHT_CONFIG.SUNSET_HOUR) return lerp(LIGHT_CONFIG.DAY_STRENGTH, LIGHT_CONFIG.NIGHT_STRENGTH, (hour + minute / 60 - (LIGHT_CONFIG.SUNSET_HOUR - LIGHT_CONFIG.TRANSITION_HOURS)) / LIGHT_CONFIG.TRANSITION_HOURS) - //Transition from sunrise to morning - else if (hour >= LIGHT_CONFIG.SUNRISE_HOUR && hour < LIGHT_CONFIG.SUNRISE_HOUR + LIGHT_CONFIG.TRANSITION_HOURS) return lerp(LIGHT_CONFIG.NIGHT_STRENGTH, LIGHT_CONFIG.DAY_STRENGTH, (hour + minute / 60 - LIGHT_CONFIG.SUNRISE_HOUR) / LIGHT_CONFIG.TRANSITION_HOURS) - else if (hour > LIGHT_CONFIG.SUNRISE_HOUR && hour < LIGHT_CONFIG.SUNSET_HOUR) return LIGHT_CONFIG.DAY_STRENGTH - else return LIGHT_CONFIG.NIGHT_STRENGTH + if (hour >= LIGHT_CONFIG.SUNSET_HOUR - LIGHT_CONFIG.TRANSITION_HOURS && hour < LIGHT_CONFIG.SUNSET_HOUR) { + return lerp(LIGHT_CONFIG.DAY_STRENGTH, LIGHT_CONFIG.NIGHT_STRENGTH, (hour + minute / 60 - (LIGHT_CONFIG.SUNSET_HOUR - LIGHT_CONFIG.TRANSITION_HOURS)) / LIGHT_CONFIG.TRANSITION_HOURS) + } else if (hour >= LIGHT_CONFIG.SUNRISE_HOUR && hour < LIGHT_CONFIG.SUNRISE_HOUR + LIGHT_CONFIG.TRANSITION_HOURS) { + return lerp(LIGHT_CONFIG.NIGHT_STRENGTH, LIGHT_CONFIG.DAY_STRENGTH, (hour + minute / 60 - LIGHT_CONFIG.SUNRISE_HOUR) / LIGHT_CONFIG.TRANSITION_HOURS) + } else if (hour > LIGHT_CONFIG.SUNRISE_HOUR && hour < LIGHT_CONFIG.SUNSET_HOUR) { + return LIGHT_CONFIG.DAY_STRENGTH + } + return LIGHT_CONFIG.NIGHT_STRENGTH } -// Socket and window handlers -const setupSocketListeners = () => { +// Scene handlers +const preloadScene = (scene: Phaser.Scene): void => { + scene.load.image('raindrop', 'assets/raindrop.png') + scene.load.image('fog', 'assets/fog.png') +} + +const createScene = (scene: Phaser.Scene): void => { + sceneRef.value = scene + initializeEffects(scene) + setupSocketListeners() +} + +const updateScene = (): void => { + const timeBasedLight = calculateLightStrength(gameStore.world.date) + const mapEffects = + mapObject.value?.mapEffects?.reduce>( + (acc, curr) => ({ + ...acc, + [curr.effect]: curr.strength + }), + {} + ) ?? {} + + const finalEffects: EffectValues = { + ...mapEffects, + light: timeBasedLight, + rain: weatherState.value.rainPercentage, + fog: weatherState.value.fogDensity + } + + applyEffects(finalEffects) +} + +// Map management +const loadMap = async (): Promise => { + if (!mapStore.mapId) return + mapObject.value = await mapStorage.get(mapStore.mapId) +} + +// Socket handlers +const setupSocketListeners = (): void => { gameStore.connection?.emit('weather', (response: WeatherState) => { weatherState.value = response updateScene() @@ -160,12 +191,17 @@ const setupSocketListeners = () => { gameStore.connection?.on('date', updateScene) } -const handleResize = () => { - if (effects.rain.value) effects.rain.value.updateConfig({ x: { min: 0, max: window.innerWidth } }) - if (effects.fog.value) effects.fog.value.setPosition(window.innerWidth / 2, window.innerHeight / 2) -} +// Watchers +watch( + () => mapStore.mapId, + async (newMapId) => { + if (newMapId) { + await loadMap() + updateScene() + } + } +) -// Lifecycle watch( () => mapObject.value, () => { @@ -173,6 +209,7 @@ watch( } ) +// Lifecycle hooks onMounted(() => window.addEventListener('resize', handleResize)) onBeforeUnmount(() => {