🎯 Características del Sistema

Característica Descripción
🌍 Por Environment/Bioma Cada bioma tiene su propio pronóstico de clima
⏰ Cambio por Hora El clima se selecciona automáticamente cada hora del día (24 horas)
🎲 Probabilístico WeatherForecast con pesos determina la probabilidad de cada clima
🎨 Colores Dinámicos Cielo, sol, luna, nubes cambian de color según la hora
☁️ Nubes Animadas Texturas de nubes con velocidad y colores variables
🌧️ Partículas Lluvia, nieve y otros efectos de partículas
🌫️ Niebla Densidad, color y falloff configurables por clima
🔄 Transiciones Suaves 10 segundos de transición entre climas (0.5s al unirse)

🏗️ Arquitectura del Sistema

// Flujo del sistema de clima

Environment (Bioma)
    └── WeatherForecast[] (por hora 0-23)
            └── { WeatherId, Weight }  // Probabilidades

WeatherPlugin
    ├── WeatherResource (estado actual)
    │       ├── forcedWeatherIndex
    │       └── environmentWeather (clima por bioma)
    │
    ├── WeatherTracker (componente en jugadores)
    │       └── Tracking del clima actual del jugador
    │
    └── WeatherSystem
            ├── WorldAddedSystem (init)
            ├── PlayerAddedSystem (añade tracker)
            ├── TickingSystem (actualiza cada hora)
            └── InvalidateWeatherAfterTeleport

☀️ Configuración de Weather

El asset Weather.java (867 líneas) define todos los aspectos visuales del clima:

Propiedades del Cielo

class Weather {
    String id;                        // "Clear", "Rain", "Storm"...
    
    // Colores del cielo (cambian según hora)
    TimeColorAlpha[] skyTopColors;    // Color superior del cielo
    TimeColorAlpha[] skyBottomColors; // Color inferior del cielo
    TimeColorAlpha[] skySunsetColors; // Colores del atardecer
    
    // Sol y Luna
    TimeColor[] sunColors;            // Color del sol
    TimeColorAlpha[] sunGlowColors;   // Resplandor del sol
    TimeFloat[] sunScales;            // Tamaño del sol
    TimeColorAlpha[] moonColors;      // Color de la luna
    TimeColorAlpha[] moonGlowColors;  // Resplandor de la luna
    TimeFloat[] moonScales;           // Tamaño de la luna
    DayTexture[] moons;               // Texturas de luna por día
    
    // Iluminación
    TimeColor[] sunlightColors;       // Color de la luz solar
    TimeFloat[] sunlightDampingMultiplier; // Atenuación
}

Nubes

class Cloud {
    String texture;           // Textura de las nubes
    TimeColorAlpha[] colors;  // Colores por hora del día
    TimeFloat[] speeds;       // Velocidad de movimiento por hora
}

// Múltiples capas de nubes posibles
Cloud[] clouds;

Niebla

// Configuración de niebla
TimeColor[] fogColors;          // Colores por hora
TimeFloat[] fogDensities;       // Densidad por hora
TimeFloat[] fogHeightFalloffs;  // Caída de altura
float[] fogDistance;            // [near, far] distancia
FogOptions fogOptions;          // Opciones adicionales

Efectos Adicionales

// Partículas de clima (lluvia, nieve)
WeatherParticle particle;

// Efecto de pantalla (overlay)
String screenEffect;
TimeColorAlpha[] screenEffectColors;

// Filtros de color
TimeColor[] colorFilters;

// Tinte del agua
TimeColor[] waterTints;

// Estrellas
String stars;

⏰ Sistema de Colores por Hora (TimeColor)

El clima utiliza arrays de TimeColor y TimeFloat para definir valores que cambian según la hora del día:

class TimeColor {
    float hour;   // Hora del día (0-24)
    Color color;  // Color RGB
}

class TimeFloat {
    float hour;   // Hora del día
    float value;  // Valor interpolado
}

// Ejemplo: cielo que cambia durante el día
"SkyTopColors": [
    { "Hour": 0,  "Color": "#1a1a2e" },   // Medianoche - oscuro
    { "Hour": 6,  "Color": "#ff7b54" },   // Amanecer - naranja
    { "Hour": 12, "Color": "#87ceeb" },   // Mediodía - azul cielo
    { "Hour": 18, "Color": "#ff6b6b" },   // Atardecer - rojo
    { "Hour": 24, "Color": "#1a1a2e" }    // Vuelta a medianoche
]
💡
Interpolación Automática
El sistema interpola automáticamente los valores entre las horas definidas, creando transiciones suaves durante el ciclo día/noche.

🎲 Sistema de Pronóstico (WeatherForecast)

Cada Environment (bioma) tiene un pronóstico de clima con probabilidades:

class WeatherForecast implements IWeightedElement {
    String weatherId;     // "Clear", "Rain", "Storm"
    double weight;        // Peso/probabilidad
}

// En Environment.java
Int2ObjectMap<IWeightedMap<WeatherForecast>> weatherForecasts;

IWeightedMap<WeatherForecast> getWeatherForecast(int hour) {
    // Devuelve el pronóstico para esa hora
    return weatherForecasts.get(hour);
}

Ejemplo de Probabilidades

// Bosque en hora 12 (mediodía)
"WeatherForecast": {
    "12": [
        { "WeatherId": "Clear",  "Weight": 60.0 },  // 60%
        { "WeatherId": "Cloudy", "Weight": 25.0 },  // 25%
        { "WeatherId": "Rain",   "Weight": 10.0 },  // 10%
        { "WeatherId": "Storm",  "Weight": 5.0  }   // 5%
    ]
}

🔄 WeatherSystem (Actualización)

class WeatherSystem {
    // Constantes de transición
    static final float JOIN_TRANSITION_SECONDS = 0.5f;
    static final float WEATHERCHANGE_TRANSITION_SECONDS = 10.0f;
    
    // TickingSystem - Se ejecuta cada tick
    void tick(float dt, Store store) {
        WeatherResource weatherRes = store.getResource(WEATHER_RESOURCE_TYPE);
        
        // Si hay un clima forzado, usarlo
        if (weatherRes.consumeForcedWeatherChange()) {
            // Forzar actualización inmediata
            return;
        }
        
        // Verificar si cambió la hora
        int currentHour = worldTimeResource.getCurrentHour();
        if (weatherRes.compareAndSwapHour(currentHour)) {
            // Nueva hora = seleccionar nuevo clima para cada bioma
            for (Environment env : Environment.getAssetMap()) {
                IWeightedMap forecast = env.getWeatherForecast(currentHour);
                int selectedWeather = forecast.get(random).getWeatherIndex();
                environmentWeather.put(envIndex, selectedWeather);
            }
        }
        
        // Actualizar jugadores cada segundo
        playerUpdateDelay -= dt;
        if (playerUpdateDelay <= 0) {
            playerUpdateDelay = 1.0f;
            updateAllPlayers();
        }
    }
}

💻 Comandos de Clima

Comando Descripción
/weather Comando base de clima
/weather get Muestra el clima actual
/weather set <id> Fuerza un clima específico
/weather reset Quita el clima forzado (vuelve a automático)
// WeatherResource.setForcedWeather()
public void setForcedWeather(@Nullable String forcedWeather) {
    this.previousForcedWeatherIndex = this.forcedWeatherIndex;
    this.forcedWeatherIndex = forcedWeather == null 
        ? 0  // Automático
        : Weather.getAssetMap().getIndex(forcedWeather);
}

🤖 Sensores de Clima para NPCs

Los NPCs pueden reaccionar al clima usando SensorWeather:

class SensorWeather extends Sensor {
    String[] weathers;  // Globs a matchear: "Rain*", "Storm"
    
    boolean evaluate(Role role, Store store) {
        int weatherIndex = role.getWorldSupport().getCurrentWeatherIndex(store);
        String weatherId = Weather.getAssetMap().getAsset(weatherIndex).getId();
        return matchesWeather(weatherId);
    }
    
    boolean matchesWeather(String weather) {
        for (String pattern : weathers) {
            if (StringUtil.isGlobMatching(pattern, weather)) {
                return true;
            }
        }
        return false;
    }
}

// Ejemplo de uso en NPC config:
"Sensors": {
    "IsRaining": {
        "Type": "Weather",
        "Weathers": ["Rain*", "Storm"]
    }
}
Casos de Uso
• NPCs que buscan refugio cuando llueve
• Mobs que solo aparecen durante tormentas
• Animales que cambian comportamiento según el clima

🐾 Spawning basado en Clima

El sistema de spawn puede usar el WeatherTracker para condicionar spawns:

// LocalSpawnControllerSystem usa WeatherTracker
WeatherTracker weatherTracker = store.getComponent(ref, WeatherTracker.getComponentType());
weatherTracker.updateEnvironment(transform, store);
int environmentIndex = weatherTracker.getEnvironmentId();

// Luego verifica condiciones de spawn por clima

🎯 WeatherTriggerCondition (Adventures)

El sistema de aventuras puede usar el clima como condición de trigger:

// WeatherTriggerCondition.java
// Permite crear objetivos que dependen del clima

// Ejemplo en Adventure config:
"Objectives": {
    "WaitForRain": {
        "TriggerCondition": {
            "Type": "Weather",
            "Weather": "Rain"
        }
    }
}

⚠️ Estabilidad y Problemas Comunes

Es posible experimentar inconsistencias en el clima debido a la arquitectura desacoplada de audio y video:

Problema Causa Técnica Solución / Nota
🫨 Clima Inestable El jugador cambia de bioma rápidamente. Cada cambio de bioma dispara una transición de 10s (WEATHERCHANGE_TRANSITION_SECONDS).
🔄 Transición Lenta Hardcoded en WeatherTracker Las transiciones normales duran 10s para evitar saltos bruscos de color en el cielo.
🤖 NPCs confundidos Uso de Globs en SensorWeather Un NPC puede detectar "Rain*" y reaccionar incluso si visualmente la transición apenas comienza.
⚠️
Nota para Modders
Para evitar el efecto de "sonido fantasma", asegúrate de que tus assets de Weather y AmbienceFX estén perfectamente sincronizados en sus IDs y condiciones.

🛠️ Extensión: WeatherFixer Plugin

Para solucionar de forma automática los problemas de desincronía, hemos desarrollado el plugin WeatherFixer basado en la arquitectura de Hytale.

Funcionalidades Clave

  • Auditoría ECS: Escanea todos los assets de clima al iniciar el servidor para encontrar climas con audio (AmbienceFX) pero sin partículas visuales.
  • Comando /wsync:
    • /wsync audit: Lanza un escaneo manual de inconsistencias.
    • /wsync transition <seg>: Ajusta la velocidad de cambio climático para reducir la inestabilidad.
// Fragmento de la Auditoría en WeatherAuditSystem.java
if (suggestsRain && !hasParticles) {
    LOGGER.warning("[!] Inconsistencia Detectada: Clima '" + id + "' parece ser lluvioso pero NO TIENE partículas.");
}

📁 Archivos Clave

  • 📄 Weather.java 867 líneas - Asset de configuración visual
  • 📄 WeatherPlugin.java 57 líneas - Plugin principal
  • 📄 WeatherSystem.java 190 líneas - Sistemas de actualización
  • 📄 WeatherResource.java 75 líneas - Estado del clima por mundo
  • 📄 WeatherForecast.java 57 líneas - Pronóstico con pesos
  • 📄 Cloud.java 69 líneas - Configuración de nubes
  • 📄 SensorWeather.java Sensor de clima para NPCs
  • 📄 WeatherCommand.java Comandos /weather