You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ryzom-core/code/ryzom/client/src/weather.cpp

351 lines
12 KiB
C++

// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program 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.
//
// This program 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "stdpch.h"
#include "light_cycle_manager.h"
#include "weather_manager_client.h"
#include "game_share/light_cycle.h"
#include "game_share/time_weather_season/weather_predict.h"
#include "precipitation.h"
#include "sheet_manager.h"
#include "client_cfg.h"
#include "continent.h"
#include "time_client.h"
#include "interface_v3/interface_manager.h"
//
#include "nel/misc/sheet_id.h"
#include "nel/3d/u_particle_system_instance.h"
//
#include "client_sheets/weather_function_params_sheet.h"
//
#include "r2/editor.h"
H_AUTO_DECL(RZ_Weather)
CLightCycle WorldLightCycle;
CLightCycleManager LightCycleManager;
CWeatherManagerClient WeatherManager;
CWeatherFunctionParamsSheet *WeatherFunctionParams = NULL;
float ManualWeatherValue = 0.f;
EGSPD::CSeason::TSeason ManualSeasonValue = EGSPD::CSeason::Spring;
bool ForceTrueWeatherValue = false;
float DayNightCycleHour = 12.f;
float ForcedDayNightCycleHour = -1.0f;
EGSPD::CSeason::TSeason CurrSeason = EGSPD::CSeason::Invalid;
EGSPD::CSeason::TSeason StartupSeason = EGSPD::CSeason::Invalid;
extern NLPACS::UGlobalRetriever *GR;
using namespace NLMISC;
void releaseWeather()
{
if( WeatherFunctionParams )
delete WeatherFunctionParams;
WeatherFunctionParams = NULL;
}
void buildLightCycleDesc(CLightCycleDesc &dest,EGSPD::CSeason::TSeason season)
{
if (season >= EGSPD::CSeason::Invalid)
{
nlwarning("Invalid season");
return;
}
dest.NumHours = WorldLightCycle.NumHours;
dest.RealDayLength = WorldLightCycle.RealDayLength;
dest.MaxNumColorSteps = WorldLightCycle.MaxNumColorSteps;
const CSeasonLightCycle &slc = WorldLightCycle.SeasonLightCycle[season];
dest.NightTransitionStartHour = slc.DayToDuskHour;
dest.NightTransitionEndHour = slc.NightHour;
dest.DawnTransitionStartHour = slc.NightToDayHour;
dest.DawnTransitionEndHour = slc.DayHour;
float intervalDayNight = slc.NightHour >= slc.DayToDuskHour ? slc.NightHour - slc.DayToDuskHour : WorldLightCycle.NumHours - slc.DayToDuskHour + slc.NightHour;
float intervalDayDusk = slc.DuskToNightHour >= slc.DayToDuskHour ? slc.DuskToNightHour - slc.DayToDuskHour : WorldLightCycle.NumHours - slc.DayToDuskHour + slc.DuskToNightHour;
if (intervalDayDusk != 0.f) dest.DuskRatio = intervalDayDusk / intervalDayNight;
else dest.DuskRatio = 0.5f;
}
void loadWorldLightCycle()
{
CEntitySheet *sheet = SheetMngr.get(CSheetId("ryzom.light_cycle"));
nlassert(sheet);
if (sheet->type() != CEntitySheet::LIGHT_CYCLE)
{
nlwarning("Bad type for light cycle sheet");
return;
}
CLightCycleSheet *lightCycleSheet = (CLightCycleSheet *) sheet;
WorldLightCycle = lightCycleSheet->LightCycle;
}
void loadWeatherFunctionParams()
{
if (WeatherFunctionParams == NULL) WeatherFunctionParams = new CWeatherFunctionParamsSheet;
if (ClientCfg.ManualWeatherSetup)
{
WeatherFunctionParams->CWeatherFunctionParamsSheetBase::build("ryzom.weather_function_params");
}
else
{
// Now, just get it from the sheet manager
std::vector <CSheetId> result;
CSheetId::buildIdVector(result, "weather_function_params");
if (result.empty())
{
nlwarning("Couldn't load weather_function_params");
return;
}
if (result.size() != 1)
{
nlwarning("Multiple .weather_function_params sheets available, taking first");
}
CEntitySheet *sheet = SheetMngr.get(result[0]);
if (sheet->type() != CEntitySheet::WEATHER_FUNCTION_PARAMS)
{
nlwarning("Bad type for weather_function_params sheet");
return;
}
WeatherFunctionParams = (CWeatherFunctionParamsSheet *) sheet;
}
}
// ***************************************************************************
void updateWeatherManager(CContinent *continent)
{
H_AUTO_USE(RZ_Weather)
// build a weather context
CWeatherContext wc;
wc.GR = NULL;
wc.LC = &WorldLightCycle;
wc.WF = continent->WeatherFunction;
// Update the weather manager
// NB nico : even when light hour is forced, we use the real time to compute the weather (so that the weather will reflect the real one, even if hour & light is fixed)
if(ClientCfg.ManualWeatherSetup && !ForceTrueWeatherValue)
{
WeatherManager.manualUpdate(RT.getRyzomDay(), (float) RT.getRyzomTime(), wc, ManualWeatherValue, CurrSeason);
}
else
{
WeatherManager.update(RT.getRyzomDay(), (float) RT.getRyzomTime(), wc);
}
}
// ***************************************************************************
void updateWeatherManager(const NLMISC::CMatrix &camMatrix, CContinent *continent)
{
H_AUTO_USE(RZ_Weather)
// build a weather context
CWeatherContext wc;
wc.GR = GR;
if(continent)
wc.WF = continent->WeatherFunction;
else
wc.WF = NULL;
if (continent)
{
if(ClientCfg.ManualWeatherSetup && !ForceTrueWeatherValue)
{
WeatherManager.manualUpdate(RT.getRyzomDay(), (float) RT.getRyzomTime(), wc, ManualWeatherValue, CurrSeason, camMatrix, *continent);
}
else
{
// NB nico : even when light hour is forced, we use the real time to compute the weather (so that the weather will reflect the real one, even if hour & light is fixed)
WeatherManager.update(RT.getRyzomDay(), RT.getRyzomTime(), wc, camMatrix, *continent);
}
}
}
///////////////////
// WEATHER VALUE //
///////////////////
float LocalServerWeather = 0.f; // 'local' server driven weather, trying to reach the weather value given in the database as time passes (for smooth transition)
float ServerWeatherBlendFactor = 0.f; // blend factor between server driven weather & local computed pseudo random weather (based on the current date, gives same result for all clients)
bool ServerDrivenWeather = false;
const float WEATHER_BLEND_SPEED = 1.f / 8.f; // number of seconds to blend betwen weather states
// ***************************************************************************
static uint16 getServerWeather()
{
CInterfaceManager *im = CInterfaceManager::getInstance();
CCDBNodeLeaf *node = im->getDbProp("SERVER:WEATHER:VALUE");
if (!node) return 0;
return (uint16) node->getValue16();
}
// ***************************************************************************
void updateDBDrivenWeatherValue()
{
uint16 dbWeather = getServerWeather();
if (dbWeather == 0)
{
// not server driven
ServerDrivenWeather = false;
NLMISC::incrementalBlend(ServerWeatherBlendFactor, 0.f, WEATHER_BLEND_SPEED * DT);
}
else
{
float targetWeather = (dbWeather - 1) / 1022.f;
if (!ServerDrivenWeather)
{
// just switched to server driven weather ?
LocalServerWeather = targetWeather;
ServerDrivenWeather = true;
}
// slowly blend from predicted weather to server driven weather
NLMISC::incrementalBlend(ServerWeatherBlendFactor, 1.f, WEATHER_BLEND_SPEED * DT);
// slowly blend between server driven weathers as values in the db changes
NLMISC::incrementalBlend(LocalServerWeather, targetWeather, WEATHER_BLEND_SPEED * DT);
}
}
// ***************************************************************************
float getBlendedWeather(uint64 day,
float hour,
const CWeatherFunctionParamsSheetBase &wfp,
const CWeatherFunction wf[EGSPD::CSeason::Invalid]
)
{
return NLMISC::blend(CPredictWeather::predictWeather(day, hour, wfp, wf), LocalServerWeather, ServerWeatherBlendFactor);
}
uint8 getSeasonDBValue()
{
CInterfaceManager *im = CInterfaceManager::getInstance();
CCDBNodeLeaf *serverSeason = im->getDbProp("SERVER:SEASON:VALUE");
return serverSeason ? serverSeason->getValue8() : 0;
}
////////////
// SEASON //
////////////
uint8 ServerSeasonValue = 0;
bool ServerSeasonReceived = false;
static bool WaitingServerSeason = false;
static R2::CEditor::TMode OldR2EDMode = R2::CEditor::NotInitialized;
static EGSPD::CSeason::TSeason LastSeason = EGSPD::CSeason::Unknown;
// ***************************************************************************
EGSPD::CSeason::TSeason computeCurrSeason()
{
// Note : when season isn't imposed by the server or by the editor, we use
// the season found in StartupSeason
// This mean that when the season change due to time, it won't be updated on the client
// This is a deliberate choice to avoid possibly disadvantaging freeze on the client while in combat
// To have this done correctly, the server should be aware of the change, and freeze action accordingly.
// When entering a new area, the 'StartupSeason' variable is updated however, because the player is safe at that time.
// (see Continent::select)
if (!ClientCfg.R2EDEnabled || !ClientCfg.Local) // ... Standard client
{
// standard case
if (ServerSeasonValue != 0)
{
ServerSeasonReceived = false;
// server driven season
LastSeason = (EGSPD::CSeason::TSeason) ((ServerSeasonValue - 1) & 3);
}
else
{
LastSeason = ClientCfg.ManualWeatherSetup ? ManualSeasonValue : StartupSeason;
}
return LastSeason;
}
// When we change act( different location) the Server TP the player at the good location
// So the manual setting of season is useless (R2::getEditor().getSeason()) because
// there is a huge risk to (Change to 2 times season)
// eg Act1 Season1, Island1, Act2 Season2, Island2
// If server tp occurs before, client received change of Season: there will be a unecessary TP
// When in edition mode, the editor tell what is the season (it is local and given
// by each act of the current scenario, so ignore server value here...)
if (R2::getEditor().getMode() == R2::CEditor::EditionMode)
{
OldR2EDMode = R2::getEditor().getMode();
switch(R2::getEditor().getSeason())
{
case R2::CEditor::Automatic: LastSeason = StartupSeason; break;
case R2::CEditor::Spring: LastSeason = EGSPD::CSeason::Spring; break;
case R2::CEditor::Summer: LastSeason = EGSPD::CSeason::Summer; break;
case R2::CEditor::Autumn: LastSeason = EGSPD::CSeason::Autumn; break;
case R2::CEditor::Winter: LastSeason = EGSPD::CSeason::Winter; break;
case R2::CEditor::UnknownSeason:
if (LastSeason == EGSPD::CSeason::Unknown)
{
// don't change the season unless this is the first call
LastSeason = StartupSeason; // default
}
break;
default:
nlassert(0);
break;
}
}
// Leaving edit -> Test 'mode' or GoingToTest 'mode' :
if (OldR2EDMode == R2::CEditor::EditionMode)
{
// a transition from edition mode to test mode has been detected :
// At some point the server will impulse the current season, be it the same than the current one
// Until this occur, we just retain the last season that was set in the editor, because
// season change are VERY costly (several seconds freeze) and we don't want the season to change twice in a row just because we don't
// know its real value at this point.
WaitingServerSeason = true;
}
OldR2EDMode = R2::getEditor().getMode();
if (WaitingServerSeason && !ServerSeasonReceived)
{
return LastSeason;
}
ServerSeasonReceived = false;
WaitingServerSeason = false;
// back to standard case
// At this point we ignore the 'ClientCfg.ManualWeatherSetup' flag, however, because it may be set
// when the "change weather" dialog is shown, but we only want to preview weather change, not the season change.
if (ServerSeasonValue != 0)
{
// server driven season
LastSeason = (EGSPD::CSeason::TSeason) ((ServerSeasonValue - 1) & 3);
}
else
{
LastSeason = StartupSeason;
}
return LastSeason;
}