/*
* This file is part of the Illarion project.
*
* Copyright © 2015 - Illarion e.V.
*
* Illarion is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Illarion is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
package illarion.client.world;
import illarion.client.graphics.AnimationUtility;
import org.illarion.engine.graphic.Color;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.util.Random;
/**
* Weather control class. Generated and stores all effects caused by the weather.
*/
public final class Weather {
/**
* Value of the cloud overcast where it reaches the maximal amount of darken
* effect.
*/
private static final int CLOUD_LIMIT = 60;
/**
* The maximal possible value for clouds.
*/
private static final int CLOUDS_MAX = 100;
/**
* The minimal possible value for clouds.
*/
private static final int CLOUDS_MIN = 0;
/**
* The step value for the cloud changes.
*/
private static final int CLOUDS_STEP = 1;
/**
* The value the coverage of the visibility is reduced by due a flash.
*/
private static final int FLASH_COVERAGE = 40;
/**
* Time between a fast row of flashes.
*/
private static final int FLASH_WAIT = 4 * 1000;
/**
* Additional visibility coverage caused by fog.
*/
private static final float FOG_COVERAGE = 2.5f;
/**
* Biggest allowed value for the fog.
*/
private static final int FOG_MAXIMAL_VALUE = 100;
/**
* Smallest allowed value for the fog.
*/
private static final int FOG_MINIMAL_VALUE = 0;
/**
* The step value the fog changes per cycle. The higher the value the faster
* the fog approaches the target value.
*/
private static final int FOG_STEP = 4;
/**
* Hours one day in Illarion has.
*/
private static final int HOUR_PER_DAY = 24;
/**
* The Brightness share of the current outside brightness in case the
* character is inside a building.
*/
private static final float INSIDE_BRIGHTNESS = 0.6f;
/**
* The additional coverage caused by the light illumination.
*/
private static final int LIGHT_COLOR_COVERAGE = 12;
/**
* Maximal allowed value for {@link #lightning}.
*/
private static final int LIGHTNING_MAX = 100;
/**
* Minimal allowed value for {@link #lightning}.
*/
private static final int LIGHTNING_MIN = 0;
/**
* The instance of the logger that is used to write out the data.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(Weather.class);
/**
* General value for no effect in the used environment.
*/
private static final int NONE = -1;
/**
* Conversation between the internal storage and the server values for the precipitation strength value.
*/
private static final int PREC_CONVERSATION_VALUE = 5;
/**
* Maximal precipitation value send by the server.
*/
private static final int PREC_SERVER_MAX = 100;
/**
* Minimal precipitation value send by the server.
*/
private static final int PREC_SERVER_MIN = 0;
/**
* Changing speed of the precipitation types.
*/
private static final int PREC_STEP = 15;
/**
* Precipitation type rain.
*/
public static final int RAIN = 1;
/**
* Additional visibility coverage caused by rain.
*/
private static final int RAIN_COVERAGE = 25;
/**
* Precipitation type snow.
*/
private static final int SNOW = 2;
/**
* Additional visibility coverage caused by snow.
*/
private static final int SNOW_COVERAGE = 15;
/**
* Brightness at underground areas.
*/
private static final float UNDERGROUND_BRIGHT = 0.1f;
/**
* Conversation value for the calculation from the server value for the wind
* to the internal value for the wind.
*/
private static final int WIND_CONVERSATION_VALUE;
/**
* Maximal value for the wind gusts.
*/
private static final int WIND_GUST_MAX = 100;
/**
* Minimal value for the wind gusts.
*/
private static final int WIND_GUST_MIN = 0;
/**
* Maximal allowed value for the wind in the internal storage.
*/
private static final int WIND_INTERAL_MAX = 500;
/**
* Minimal allowed value for the wind in the internal storage.
*/
private static final int WIND_INTERAL_MIN = -500;
/**
* Maximal allowed value for the wind send by the server.
*/
private static final int WIND_SERVER_MAX = 100;
/**
* Minimal allowed value for the wind send by the server.
*/
private static final int WIND_SERVER_MIN = -100;
/**
* Speed the wind is interpolated with. A higher value means the it takes
* longer until the target value for the wind is reached.
*/
private static final int WIND_STEP = 8;
static {
WIND_CONVERSATION_VALUE = WIND_INTERAL_MAX / WIND_SERVER_MAX;
}
/**
* Color of the ambient light.
*/
@Nonnull
private final Color ambientLight;
/**
* The color the ambient light is approaching.
*/
@Nonnull
private final Color ambientTargetColor;
/**
* The current state of the clouds at the sky. Values between 0 for no
* clouds and 100 for fully clouded are possible.
*/
private int cloud;
/**
* The target state of the clouds at the sky. This is used together with
* {@link #cloud} for the smooth interpolation of the clouds.
*/
private int cloudTarget;
/**
* The current value of the fog. 0 for no fog, 100 for maximum fog.
*/
private int fog;
/**
* The target fog value. Has the same range as {@link #fog} and works as the
* smooth fading target for the intensity of the fog.
*/
private int fogTarget;
/**
* The strength of the wind gusts.
*/
private int gusts;
/**
* The target strength of the wind gusts.
*/
private int gustsTarget;
/**
* Current lighting state. A greater number causes lightings in shorter
* intervals. 0 for no lightnings and 100 for the shortest intervals of
* lightnings.
*/
private int lightning;
/**
* Time to the next flashes.
*/
private int nextFlash;
/**
* Time until the next gust.
*/
private int nextGust;
/**
* Time to the next thunder sound effect.
*/
private int nextThunder;
/**
* Flag if the player character is currently inside a house or a cave or
* outside. True means outside.
*/
private boolean outside;
/**
* The target strength of the rain. Values between {@code 0} and {@code 500}.
*/
private int rainTarget;
/**
* The target strength of the snow fall. Values between {@code 0} and {@code 500}.
*/
private int snowTarget;
/**
* The strength of the rain. Values between {@code 0} and {@code 500}.
*/
private int rain;
/**
* The strength of the snow fall. Values between {@code 0} and {@code 500}.
*/
private int snow;
/**
* The random value generator used by this class.
*/
private final Random rnd = new Random();
/**
* Amount of flashes shown in a short time interval. A real shown amount is
* the amount stored here / {@link #FLASH_WAIT}.
*/
private int showFlash;
/**
* Current effective wind value.
*/
private int wind;
/**
* The real target of the current wind. Used as smooth interpolation target
* for the effective {@link #wind} value.
*/
private int windTarget;
@Nonnull
private final AmbientLight ambientLightCalculation;
/**
* Default constructor. Prepare and active everything needed to show the
* weather.
*/
public Weather() {
ambientLightCalculation = new AmbientLight();
ambientLight = new Color(Color.BLACK);
ambientTargetColor = new Color(ambientLight);
}
public void shutdown() {
ambientLightCalculation.shutdown();
}
/**
* Calculate the current ambient light, depending on the time of day, the environment (inside,
* outside) and the current weather.
*/
private void calculateLight() {
// if we are underground it is simply very dark
if (World.getPlayer().getBaseLevel() < 0) {
// average brightness underground
ambientTargetColor.setRedf(UNDERGROUND_BRIGHT);
ambientTargetColor.setGreenf(UNDERGROUND_BRIGHT);
ambientTargetColor.setBluef(UNDERGROUND_BRIGHT);
return;
}
ambientLightCalculation.setOvercast(cloud / (double) CLOUDS_MAX);
ambientTargetColor.setColor(ambientLightCalculation.getCurrentAmbientLight());
// it is somewhat darker in buildings
if (!outside) {
ambientTargetColor.multiply(INSIDE_BRIGHTNESS);
ambientTargetColor.setAlpha(Color.MAX_INT_VALUE);
}
}
/**
* Update the current weather conditions to the next values. This function
* also performs the smooth change of the values.
*
* @param delta the time since the last call of this function
*/
private void changeWeather(int delta) {
calculateLight();
}
/**
* Get the current ambient light.
*
* @return the current ambient light
*/
@Nonnull
public Color getAmbientLight() {
return ambientLight;
}
/**
* Get the current value of the cloud coverage.
*
* @return the current cloud coverage
*/
public int getClouds() {
return cloud;
}
/**
* Get the current value of the fog.
*
* @return the current fog value
*/
public float getFog() {
return (float) fog / (float) FOG_MAXIMAL_VALUE;
}
/**
* Check if there is currently any fog.
*
* @return {@code true} in case its even slightly foggy
*/
public boolean isFog() {
return fog > 0;
}
/**
* Get the current intensity of the lightings.
*
* @return current lighting intensity
*/
public int getLighting() {
return lightning;
}
/**
* Check if it is currently raining.
*
* @return {@code true} in case its raining
*/
public boolean isRain() {
return getRain() > 0;
}
public int getRain() {
return rain / 5;
}
/**
* Determine how much the weather obstructs visibility.
*
* @return The obstruction of the visiblity caused by the weather
*/
public int getVisiblity() {
int coverage = 0;
if (outside) {
if ((showFlash > 0) && ((showFlash % FLASH_WAIT) != 0)) {
coverage -= FLASH_COVERAGE;
} else {
float lum = ambientLight.getLuminancef();
coverage += (int) ((1 - lum) * LIGHT_COLOR_COVERAGE);
}
if (rain > 0) {
coverage += rain / RAIN_COVERAGE;
}
if (snow > 0) {
coverage += snow / SNOW_COVERAGE;
}
coverage += (int) (fog / FOG_COVERAGE);
}
return coverage;
}
/**
* Get the current strength of the wind.
*
* @return the current wind value
*/
public int getWind() {
return wind / WIND_CONVERSATION_VALUE;
}
/**
* Get the current strength of the wind gusts.
*
* @return the current strength of the gusts
*/
public int getGusts() {
return gusts / WIND_CONVERSATION_VALUE;
}
/**
* Check if the players character is currently outside at the fresh air.
*
* @return true if the character is outside
*/
public boolean isOutside() {
return outside && (World.getPlayer().getBaseLevel() >= 0);
}
/**
* Set the new cloud cover value.
*
* @param newCloud new value for the clouds between {@link #CLOUDS_MIN} and
* {@link #CLOUDS_MAX}
*/
public void setCloud(int newCloud) {
if ((newCloud < CLOUDS_MIN) || (newCloud > CLOUDS_MAX)) {
LOGGER.warn("Illegal clounds value: " + newCloud);
return;
}
cloudTarget = newCloud;
}
/**
* Set the density of the fog in percent.
*
* @param newFog New value for the fog.
*/
public void setFog(int newFog) {
if ((newFog < FOG_MINIMAL_VALUE) || (newFog > FOG_MAXIMAL_VALUE)) {
LOGGER.warn("Illegal fog value: " + newFog);
return;
}
fogTarget = newFog;
}
/**
* Set the new lighting intensity value.
*
* @param newLightning New value for the lightnings between
* {@link #LIGHTNING_MIN} and {@link #LIGHTNING_MAX}
*/
public void setLightning(int newLightning) {
if ((lightning < LIGHTNING_MIN) || (lightning > LIGHTNING_MAX)) {
LOGGER.warn("Illegal lightning value: " + newLightning);
return;
}
lightning = newLightning;
}
/**
* Set if the players character is currently inside or outside.
*
* @param newOutside true if the character is outside
*/
public void setOutside(boolean newOutside) {
outside = newOutside;
}
/**
* Set the precipitation type and strength.
*
* @param type Type of precipitation. Possible values are {@link #RAIN} and
* {@link #SNOW}
* @param strength new precipitation strength value
*/
public void setPrecipitation(int type, int strength) {
if ((type < 0) || (type > 2) || (strength < PREC_SERVER_MIN) || (strength > PREC_SERVER_MAX)) {
LOGGER.warn("Illegal precipitation value: " + type + " strength: " + strength);
return;
}
if (type == RAIN) {
rainTarget = strength * PREC_CONVERSATION_VALUE;
snowTarget = 0;
} else if (type == SNOW) {
snowTarget = strength * PREC_CONVERSATION_VALUE;
rainTarget = 0;
}
}
/**
* Set the strength of the wind and the frequency and strength of the wind
* gusts.
*
* @param newWind the new value for the wind
* @param newGusts the new value for the wind gusts
*/
public void setWind(int newWind, int newGusts) {
if ((newWind < WIND_SERVER_MIN) || (newWind > WIND_SERVER_MAX) || (newGusts < WIND_GUST_MIN) ||
(newGusts > WIND_GUST_MAX)) {
LOGGER.warn("Illegal wind value: " + newWind + " gusts: " + newGusts);
return;
}
windTarget = newWind * WIND_CONVERSATION_VALUE;
gustsTarget = newGusts * WIND_CONVERSATION_VALUE;
}
private void animateWeather(int delta) {
int effectiveDelta = Math.max(1, delta / 5);
wind = AnimationUtility.approach(wind, windTarget, WIND_STEP, WIND_INTERAL_MIN, WIND_INTERAL_MAX, effectiveDelta);
gusts = AnimationUtility.approach(gusts, gustsTarget, WIND_STEP, 0, WIND_INTERAL_MAX, effectiveDelta);
cloud = AnimationUtility.approach(cloud, cloudTarget, CLOUDS_STEP, CLOUDS_MIN, CLOUDS_MAX, effectiveDelta);
fog = AnimationUtility.approach(fog, fogTarget, FOG_STEP, 0, 100, effectiveDelta);
rain = AnimationUtility.approach(rain, rainTarget, PREC_STEP, 0, 500, effectiveDelta);
snow = AnimationUtility.approach(snow, snowTarget, PREC_STEP, 0, 500, effectiveDelta);
if (AnimationUtility.approach(ambientLight, ambientTargetColor, effectiveDelta)) {
World.getMap().updateAmbientLight();
}
}
public void update(int delta) {
if (World.getClock().isSet()) {
changeWeather(delta);
animateWeather(delta);
}
}
}