// Ryzom - MMORPG Framework // 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 . #include "stdpch.h" // #include "game_share/light_cycle.h" #include "game_share/time_weather_season/weather_predict.h" // #include "client_sheets/weather_function_params_sheet.h" // #include "weather.h" #include "weather_manager_client.h" #include "weather_setup_client.h" #include "precipitation.h" #include "continent.h" #include "sheet_manager.h" #include "sound_manager.h" #include "client_cfg.h" H_AUTO_DECL(RZ_WeatherManagerClient) using namespace NLMISC; using namespace std; extern NL3D::UScene *Scene; extern NL3D::ULandscape *Landscape; //================================================================================================ CWeatherManagerClient::CWeatherManagerClient() : _WindDir(0, 0, 0), _WeatherValue(0), _ThunderLevel(0), _ThunderStrike(false), _LastEvalHour(0), _LastEvalDay(0), _LocalPrecipitationFactor(0.f) { } //================================================================================================ void CWeatherManagerClient::init() { // Get the list of the weather setups sheets std::vector result; CSheetId::buildIdVector(result, "weather_setup"); std::vector wsSheets; std::vector wsSheetsNames; for(uint k = 0; k < result.size(); ++k) { CEntitySheet *sheet = SheetMngr.get(result[k]); if (sheet && sheet->type() == CEntitySheet::WEATHER_SETUP) { //nlwarning("Sheet name = %s, weather setup = %s", result[k].toString().c_str(), NLMISC::CStringMapper::unmap((dynamic_cast(sheet))->SetupName).c_str()); wsSheetsNames.push_back(result[k].toString()); wsSheets.push_back(dynamic_cast(sheet)); } } init(wsSheets, wsSheetsNames); } //================================================================================================ CWeatherSetup *CWeatherManagerClient::newWeatherSetup() const { // client version of weather setup return new CWeatherSetupClient; } //================================================================================================ void CWeatherManagerClient::setupLoaded(CWeatherSetup *setup) { CWeatherSetupClient *wsc = NLMISC::safe_cast(setup); wsc->WeatherStateClient.setup(setup->WeatherState, _PrecipitationMap); } //================================================================================================ void CWeatherManagerClient::init(const std::vector &sheets, const std::vector &sheetNames) { CWeatherManager::init(sheets, sheetNames); initPrecipitationFXs(); } //================================================================================================ void CWeatherManagerClient::initPrecipitationFXs() { CPrecipitationDesc desc; for(TPrecipitationMap::iterator it = _PrecipitationMap.begin(); it != _PrecipitationMap.end(); ++it) { desc.FxName = it->first + ".ps"; desc.ReleasableModel = true; desc.GridSize = 7; desc.UseBBoxSize = true; it->second.init(desc); } } //================================================================================================ /** Update a vector of precipitation with the given pos */ static void updatePrecipitationVect(std::vector &vect, const NLMISC::CMatrix &camMat, NLPACS::UGlobalRetriever *gr) { H_AUTO_USE(RZ_WeatherManagerClient) for(std::vector::iterator it = vect.begin(); it != vect.end(); ++it) { (*it)->update(camMat, gr); } } //================================================================================================ void CWeatherManagerClient::update(uint64 day, float hour, const CWeatherContext &wc) { H_AUTO_USE(RZ_WeatherManagerClient) // get the weather value for the current date nlassert(wc.WFP); float weatherValue = ::getBlendedWeather(day, hour, *(wc.WFP), wc.WF); // build current weather state EGSPD::CSeason::TSeason season = CRyzomTime::getSeasonByDay((uint32)day); // manualUpdateImpl(day, hour, wc, weatherValue, season); _LastEvalHour = hour; _LastEvalDay = day; } //================================================================================================ void CWeatherManagerClient::update(uint64 day, float hour, const CWeatherContext &wc, const NLMISC::CMatrix &camMat, const CContinent &continent) { H_AUTO_USE(RZ_WeatherManagerClient) // get the weather value for the current date nlassert(wc.WFP); float weatherValue = ::getBlendedWeather(day, hour, *(wc.WFP), wc.WF); // calculate next weather cycle and ingame hour for it uint64 cycle = ((day * wc.WFP->DayLength) + (uint) hour) / wc.WFP->CycleLength; uint64 cycleDay = ((cycle + 1) * wc.WFP->CycleLength) / wc.WFP->DayLength; _NextWeatherHour = ((cycle + 1) * wc.WFP->CycleLength) % wc.WFP->DayLength; _NextWeatherValue = ::getBlendedWeather(cycleDay, _NextWeatherHour, *(wc.WFP), wc.WF); // build current weather state EGSPD::CSeason::TSeason season = CRyzomTime::getSeasonByDay((uint32)day); // manualUpdateImpl(day, hour, wc, weatherValue, season, camMat, continent); _LastEvalHour = hour; _LastEvalDay = day; } //================================================================================================ void CWeatherManagerClient::manualUpdate(uint64 day, float hour, const CWeatherContext &wc, float weatherValue, EGSPD::CSeason::TSeason season, const NLMISC::CMatrix &camMat, const CContinent &continent) { manualUpdateImpl(day, hour, wc, weatherValue, season, camMat, continent); _LastEvalHour = hour; _LastEvalDay = day; } //================================================================================================ void CWeatherManagerClient::manualUpdateImpl(uint64 day, float hour, const CWeatherContext &wc, float weatherValue, EGSPD::CSeason::TSeason season, const NLMISC::CMatrix &camMat, const CContinent &continent) { H_AUTO_USE(RZ_WeatherManagerClient) if (!wc.WF) return; manualUpdateImpl(day, hour, wc, weatherValue, season); setupFXs(camMat, wc.GR, continent); setupWind(&(wc.WF[season])); float scaledWeatherValue = weatherValue * (wc.WF[season].getNumWeatherSetups() - 1); updateThunder(day, hour, wc, true, scaledWeatherValue, season); // Sound stuff if (SoundMngr != 0) { const CWeatherSetup *floorSetup, *ceilSetup; float blendFactor; wc.WF[season].getClosestWeatherSetups(scaledWeatherValue, floorSetup, ceilSetup, blendFactor); float userVarClouds1 = 0.f; float userVarClouds2 = 0.f; float userVarStorm = 0.f; // static TStringId strClouds1 = CStringMapper::map("clouds1"); static TStringId strClouds2 = CStringMapper::map("clouds2"); static TStringId strHumidity1 = CStringMapper::map("humidity1"); static TStringId strHumidity2 = CStringMapper::map("humidity2"); static TStringId strStorm= CStringMapper::map("storm"); // contrib from previous setup if (_CurrWeatherState.FirstSetupName == strClouds1 || _CurrWeatherState.FirstSetupName == strHumidity1) { if (floorSetup && !floorSetup->WeatherState.FXInfos.empty() && !floorSetup->WeatherState.FXInfos[0].Name.empty()) { userVarClouds1 += (1.f - _CurrWeatherState.BlendFactor) * _LocalPrecipitationFactor * floorSetup->WeatherState.FXInfos[0].Ratio; } } else if (_CurrWeatherState.FirstSetupName == strClouds2 || _CurrWeatherState.FirstSetupName == strHumidity2) { if (floorSetup && !floorSetup->WeatherState.FXInfos.empty() && !floorSetup->WeatherState.FXInfos[0].Name.empty()) { userVarClouds2 += (1.f - _CurrWeatherState.BlendFactor) * _LocalPrecipitationFactor * floorSetup->WeatherState.FXInfos[0].Ratio; } } else if (_CurrWeatherState.FirstSetupName == strStorm) { if (floorSetup && !floorSetup->WeatherState.FXInfos.empty() && floorSetup->WeatherState.FXInfos[0].Name.empty()) { userVarClouds2 += (1.f - _CurrWeatherState.BlendFactor) * _LocalPrecipitationFactor * floorSetup->WeatherState.FXInfos[0].Ratio; } userVarStorm += (1.f - _CurrWeatherState.BlendFactor) * _LocalPrecipitationFactor; } // contrib from next setup if (_CurrWeatherState.SecondSetupName == strClouds1 || _CurrWeatherState.SecondSetupName == strHumidity1) { if (ceilSetup && !ceilSetup->WeatherState.FXInfos.empty() && !ceilSetup->WeatherState.FXInfos[0].Name.empty()) { userVarClouds1 += _CurrWeatherState.BlendFactor * _LocalPrecipitationFactor * ceilSetup->WeatherState.FXInfos[0].Ratio; } } else if (_CurrWeatherState.SecondSetupName == strClouds2 || _CurrWeatherState.SecondSetupName == strHumidity2) { if (ceilSetup && !ceilSetup->WeatherState.FXInfos.empty() && !ceilSetup->WeatherState.FXInfos[0].Name.empty()) { userVarClouds2 += _CurrWeatherState.BlendFactor * _LocalPrecipitationFactor * ceilSetup->WeatherState.FXInfos[0].Ratio; } } else if (_CurrWeatherState.SecondSetupName == strStorm) { if (ceilSetup && !ceilSetup->WeatherState.FXInfos.empty() && !ceilSetup->WeatherState.FXInfos[0].Name.empty()) { userVarClouds2 += _CurrWeatherState.BlendFactor * _LocalPrecipitationFactor * ceilSetup->WeatherState.FXInfos[0].Ratio; } userVarStorm += _CurrWeatherState.BlendFactor * _LocalPrecipitationFactor; } // update vars SoundMngr->getMixer()->setUserVar(strClouds1, 1.f - (1.f - userVarClouds1) * (1.f - userVarClouds1) * (1.f - userVarClouds1)); SoundMngr->getMixer()->setUserVar(strClouds2, 1.f - (1.f - userVarClouds2) * (1.f - userVarClouds2) * (1.f - userVarClouds2)); SoundMngr->getMixer()->setUserVar(strStorm, 1.f - (1.f - userVarStorm) * (1.f - userVarStorm) * (1.f - userVarStorm)); } } //================================================================================================ void CWeatherManagerClient::manualUpdate(uint64 day, float hour, const CWeatherContext &wc, float weatherValue, EGSPD::CSeason::TSeason season) { manualUpdateImpl(day, hour, wc, weatherValue, season); _LastEvalHour = hour; _LastEvalDay = day; } //================================================================================================ void CWeatherManagerClient::manualUpdateImpl(uint64 day, float hour, const CWeatherContext &wc, float weatherValue, EGSPD::CSeason::TSeason season) { H_AUTO_USE(RZ_WeatherManagerClient) if (!wc.WF) return; _WeatherValue = weatherValue; nlassert(season < EGSPD::CSeason::Invalid); float scaledWeatherValue = weatherValue * (wc.WF[season].getNumWeatherSetups() - 1); const CWeatherSetup *floorSetup, *ceilSetup; float blendFactor; wc.WF[season].getClosestWeatherSetups(scaledWeatherValue, floorSetup, ceilSetup, blendFactor); if (floorSetup && ceilSetup) { // blend general part CWeatherState::blend(_CurrWeatherState, floorSetup->WeatherState, ceilSetup->WeatherState, blendFactor); // blend client specific part CWeatherStateClient::blend(_CurrWeatherStateClient, safe_cast(floorSetup)->WeatherStateClient, safe_cast(ceilSetup)->WeatherStateClient, blendFactor); } } //================================================================================================ void CWeatherManagerClient::setupWind(const CWeatherFunction *wf) { H_AUTO_USE(RZ_WeatherManagerClient) float wi = _CurrWeatherState.WindIntensity; NLMISC::clamp(_CurrWeatherState.WindIntensity, 0.f, 1.f); // TEMP // wind for static & personnal FXs NL3D::UParticleSystemInstance::setGlobalUserParamValue("WIND", wi); // wind for vegetables if (Landscape) { float vegetWindFreq; float vegetBendIntensity; float vegetBendOffset; if (!wf) { static const float windBendMin = 0.5f; // static for tests in debug mode static const float startWindBendMin = 0.6f; vegetWindFreq = 5.f * wi; vegetBendIntensity = wi; vegetBendOffset = wi < startWindBendMin ? 0.f : windBendMin * (wi - startWindBendMin) / (1.f - startWindBendMin); } else { vegetWindFreq = wf->VegetableMinWindFrequency + (wf->VegetableMaxWindFrequency - wf->VegetableMinWindFrequency) * wi; vegetBendIntensity = wf->VegetableMinBendIntensity + (wf->VegetableMaxBendIntensity - wf->VegetableMinBendIntensity) * wi; if (wi < wf->VegetableWindIntensityThatStartBendOffset) { vegetBendOffset = 0.f; } else { vegetBendOffset = wf->VegetableMaxBendOffset * (wi - wf->VegetableWindIntensityThatStartBendOffset) / (1.f - wf->VegetableWindIntensityThatStartBendOffset); } } Landscape->setVegetableWind(_WindDir, vegetWindFreq, vegetBendIntensity, vegetBendOffset); } // wind for trees if (Scene) { float windTree; if (wf) { windTree = wf->TreeMinWindIntensity + (wf->TreeMaxWindIntensity - wf->TreeMinWindIntensity) * wi; } else { windTree = 0.1f + 0.9f * wi; } Scene->setGlobalWindPower(windTree); } } //================================================================================================ void CWeatherManagerClient::setupFXs(const NLMISC::CMatrix &camMat, NLPACS::UGlobalRetriever *gr, const CContinent &continent) { H_AUTO_USE(RZ_WeatherManagerClient) static TPrecipitationVect askedPrecipitations; static TPrecipitationVect releasablePrecipitations; // Read the precipitation local ratio CVector pos = camMat.getPos(); CRGBAF localNoPrecipitation = continent.FogMap.getMapValue (CFogMapBuild::NoPrecipitation, pos.x, pos.y, CRGBAF(0.f, 0.f, 0.f, 0.f)); _LocalPrecipitationFactor = 1.f - localNoPrecipitation.R; // uint numAskedFXs = (uint)_CurrWeatherStateClient.FXs.size(); askedPrecipitations.resize(numAskedFXs); uint k; for(k = 0; k < numAskedFXs; ++k) { askedPrecipitations[k] = _CurrWeatherStateClient.FXs[k].Precipitation; _CurrWeatherStateClient.FXs[k].Precipitation->setStrenght (_CurrWeatherStateClient.FXs[k].Ratio * _LocalPrecipitationFactor); } std::sort(askedPrecipitations.begin(), askedPrecipitations.end()); // sort em for std::set_ // See which FXs where asked to be removed, but are now needed again (remove them from the waiting/shutting_down list) releasablePrecipitations.clear(); std::set_difference(_WaitingPrecipitations.begin(), _WaitingPrecipitations.end(), askedPrecipitations.begin(), askedPrecipitations.end(), std::back_inserter(releasablePrecipitations)); /* if (!releasablePrecipitations.empty()) { for(uint k = 0; k < releasablePrecipitations.size(); ++k) { nlinfo("Shutting down precipitation reused : %s", releasablePrecipitations[k]->getDesc().FxName.c_str() ); } } */ _WaitingPrecipitations.swap(releasablePrecipitations); // see which FXs are to be removed // NB : we are working on vectors, but they are likely to be very smalls releasablePrecipitations.clear(); std::set_difference(_ActivePrecipitations.begin(), _ActivePrecipitations.end(), askedPrecipitations.begin(), askedPrecipitations.end(), std::back_inserter(releasablePrecipitations)); // for(k = 0; k < releasablePrecipitations.size(); ++k) { releasablePrecipitations[k]->setStrenght(0); } /* if (!releasablePrecipitations.empty()) { for(uint k = 0; k < releasablePrecipitations.size(); ++k) { nlinfo("Precipitation put in shutting down list : %s", releasablePrecipitations[k]->getDesc().FxName.c_str() ); } } */ // put in the waiting precipitation list _WaitingPrecipitations.insert(_WaitingPrecipitations.end(), releasablePrecipitations.begin(), releasablePrecipitations.end()); // set new active FX list _ActivePrecipitations.swap(askedPrecipitations); // tmp for debug : dump precipitation list if it has changed /* if (_ActivePrecipitations.size() != askedPrecipitations.size() || !std::equal(_ActivePrecipitations.begin(), _ActivePrecipitations.end(), askedPrecipitations.begin()) ) { for(uint k = 0; k < _ActivePrecipitations.size(); ++k) { nlinfo("New precipitation list : %s", _ActivePrecipitations[k]->getDesc().FxName.c_str() ); } } */ // update precipitations updatePrecipitationVect(_ActivePrecipitations, camMat, gr); // update waiting precipitations updatePrecipitationVect(_WaitingPrecipitations, camMat, gr); // Remove waiting precipitation that are not running anymore (no more particles) TPrecipitationVect::iterator lastValid = std::remove_if(_WaitingPrecipitations.begin(), _WaitingPrecipitations.end(), std::not1(std::mem_fun(&CPrecipitation::isRunning))); _WaitingPrecipitations.erase(lastValid, _WaitingPrecipitations.end()); } //================================================================================================ void CWeatherManagerClient::setWindDir(const NLMISC::CVector &dir) { H_AUTO_USE(RZ_WeatherManagerClient) _WindDir.set(dir.x, dir.y, 0.f); _WindDir.normalize(); NL3D::UParticleSystemInstance::setGlobalVectorValue("WIND", _WindDir); } //================================================================================================ void CWeatherManagerClient::computeCloudState(uint64 day, float hour, const CWeatherContext &wc, CCloudState &dest) const { H_AUTO_USE(RZ_WeatherManagerClient) if (!wc.WF) { dest = CCloudState(); return; } nlassert(wc.WFP); float weatherValue = ::getBlendedWeather(day, hour, *(wc.WFP), wc.WF); // build current weather state uint season = CRyzomTime::getSeasonByDay((uint32)day); computeCloudState(weatherValue, (EGSPD::CSeason::TSeason) season, dest, wc.WF); } //================================================================================================ void CWeatherManagerClient::computeCloudState(float weatherValue, EGSPD::CSeason::TSeason season, CCloudState &dest, const CWeatherFunction wf[EGSPD::CSeason::Invalid]) const { H_AUTO_USE(RZ_WeatherManagerClient) if (!wf) { dest = CCloudState(); return; } nlassert(season < EGSPD::CSeason::Invalid); wf[season].getCloudState(weatherValue * (wf[season].getNumWeatherSetups() - 1), dest); } //================================================================================================ void CWeatherManagerClient::release() { for(TPrecipitationMap::iterator it = _PrecipitationMap.begin(); it != _PrecipitationMap.end(); ++it) { it->second.release(); } NLMISC::contReset(_PrecipitationMap); NLMISC::contReset(_ActivePrecipitations); NLMISC::contReset(_WaitingPrecipitations); CWeatherManager::release(); _CurrWeatherStateClient = CWeatherStateClient(); } //================================================================================================ void CWeatherManagerClient::drawPrecipitationClipGrids(NL3D::UDriver &drv) const { for(TPrecipitationVect::const_iterator it = _ActivePrecipitations.begin(); it != _ActivePrecipitations.end(); ++it) { (*it)->drawClipGrid(drv); } } //================================================================================================ // alias for the thunder time measure typedef CWeatherManagerClient::CThunderTimeMeasure CThunderTime; static inline bool operator == (const CThunderTime &lhs, const CThunderTime &rhs) { return lhs.Cycle == rhs.Cycle && lhs.SubCycle == rhs.SubCycle; } static inline bool operator != (const CThunderTime &lhs, const CThunderTime &rhs) { return !(lhs == rhs); } /** Eval the thunder function value at the given date */ static float evalThunderFunction(const CThunderTime &tt) { H_AUTO_USE(RZ_WeatherManagerClient) // cache previous result static CThunderTime lastTime; static float lastValue; if (tt != lastTime) { CRandom rnd; rnd.srand((uint32) (tt.Cycle & 0xFFFFFFFF)); sint32 dummy = rnd.rand(); dummy = rnd.rand(); dummy = rnd.rand(); float v0 = rnd.frand(1.f); rnd.srand((uint32) ((tt.Cycle + 1) & 0xFFFFFFFF)); dummy = rnd.rand(); dummy = rnd.rand(); dummy = rnd.rand(); float v1 = rnd.frand(1.f); lastValue = tt.SubCycle * v1 + (1.f - tt.SubCycle) * v0; lastTime = tt; } return lastValue; } /** Convert a day / hour to a CThunderTime */ static inline void toThunderTime(uint64 day, float hour, CThunderTime &dest, const CWeatherContext &wc) { H_AUTO_USE(RZ_WeatherManagerClient) // convert day part to seconds nlassert(wc.LC); nlassert(wc.WFP); double timeInSeconds = day * (double) wc.LC->RealDayLength + ((double) hour / wc.LC->NumHours) * wc.LC->RealDayLength; // convert date in second to the thunder cycle date double cycle = timeInSeconds / wc.WFP->MinThunderPeriod; dest.Cycle = (uint64) cycle; dest.SubCycle = (float) fmod(cycle, 1); } /** Convert CThunderTime cycle number to a day / hour pair */ static inline void toGlobalTime(uint64 &day, float &hour, uint64 cycle, const CWeatherContext &wc) { H_AUTO_USE(RZ_WeatherManagerClient) nlassert(wc.WFP); nlassert(wc.LC); // convert to global time in seconds double timeInSeconds = cycle * (double) wc.WFP->MinThunderPeriod; double dDay; dDay = timeInSeconds / wc.LC->RealDayLength; day = (uint64) floor(dDay); hour = (float) fmod((float)dDay, wc.LC->NumHours); } /** Difference between 2 thunder time, expressed in thunder cycles */ static inline float diff(const CThunderTime &t0, const CThunderTime &t1) { H_AUTO_USE(RZ_WeatherManagerClient) if (t0.Cycle == t1.Cycle) return t1.SubCycle - t0.SubCycle; return 1.f - t0.SubCycle + t1.SubCycle + (float) (t1.Cycle - t0.Cycle - 1); } /** Add the given number of cycle to a CThunderTime */ static inline void add(CThunderTime &dest, float duration) { H_AUTO_USE(RZ_WeatherManagerClient) dest.Cycle += (uint64) floorf(duration); dest.SubCycle += fmodf(duration, 1.f); // add fractionnal part dest.Cycle += (uint64) floorf(dest.SubCycle); // add carry dest.SubCycle = fmodf(dest.SubCycle, 1.f); } // blend between 2 thunder times static inline void blend(CThunderTime &dest, const CThunderTime &t0, const CThunderTime &t1, float blendFactor) { H_AUTO_USE(RZ_WeatherManagerClient) dest = t0; add(dest, blendFactor * diff(t0, t1)); } //================================================================================================ /** Generate a new thunder strike if the weather function goes over the threshold * \return true if a strike wa generated */ bool CWeatherManagerClient::updateThunderState(CThunderTime &t0, CThunderTime &t1, float thunderThreshold) { H_AUTO_USE(RZ_WeatherManagerClient) float thunderValue0 = evalThunderFunction(t0); float thunderValue1 = evalThunderFunction(t1); if (thunderValue0 <= thunderThreshold && thunderValue1 > thunderThreshold) { // The value went over the threshold -> generate a thunder strike // See at which date the strike started exactly float blendFactor = (thunderThreshold - thunderValue0) / (thunderValue1 - thunderValue0); // set the strike date blend(_ThunderStrikeDate, t0, t1, blendFactor); _ThunderStrike = true; return true; } return false; } //================================================================================================ void CWeatherManagerClient::updateThunder(uint64 day, float hour, const CWeatherContext &wc, bool manual, float manualWeatherValue, EGSPD::CSeason::TSeason manualSeason) { H_AUTO_USE(RZ_WeatherManagerClient) // we use a random function that trigger thunder. // A thunder strike happens when the function is above of a threshold. // The function needs to get back below the threshold before another thunder strike can happen // first, build the 2 dates between which we should evaluate the thunder value; CThunderTime t0; CThunderTime t1; float manualThunderThreshold = 0.f; if (manual) { if (wc.WF) { manualThunderThreshold = 1.f - 0.5f * wc.WF[manualSeason].getThunderIntensity(manualWeatherValue); } else { manualThunderThreshold = 0.5f; } } toThunderTime(_LastEvalDay, _LastEvalHour, t0, wc); // start date toThunderTime(day, hour, t1, wc); // end date if (diff(t0, t1) > 1) // we don't need to look further that 1 thunder cycle to the past, because there can be only one thunder strike every other cycles. { t0.Cycle = t1.Cycle - 1; t0.SubCycle = t1.SubCycle; } if (t0.Cycle == t1.Cycle) // we stay in the same cycle, so the function is linear { float threshold = manual ? manualThunderThreshold : getThunderThreshold(t0.Cycle, wc); updateThunderState(t0, t1, threshold); } else { // Change of thunder cycle between the 2 dates CThunderTime middleTime; middleTime.Cycle = t1.Cycle; middleTime.SubCycle = 0.f; float threshold = manual ? manualThunderThreshold : getThunderThreshold(t0.Cycle,wc); bool strike = updateThunderState(t0, middleTime, threshold); // eval end of previous cycle if (!strike) // we are sure there won't be another strike during the next cycle { threshold = manual ? manualThunderThreshold : getThunderThreshold(t1.Cycle,wc); updateThunderState(middleTime, t1, threshold); // eval start of current cycle } } // update the thunder value nlassert(wc.WFP); if (_ThunderStrike) { // if too much time has ellapsed since the last thunder strike, disable it float timeEllapsedSinceStrike = diff(_ThunderStrikeDate, t1) * wc.WFP->MinThunderPeriod ; if (timeEllapsedSinceStrike >= wc.WFP->ThunderLength) { _ThunderStrike = false; _ThunderLevel = 0.f; } else { _ThunderLevel = wc.WFP->ThunderLength != 0.f ? 1.f - (timeEllapsedSinceStrike / wc.WFP->ThunderLength) : 0.f; } } } //================================================================================================ float CWeatherManagerClient::getThunderIntensity(uint64 day, float hour, const CWeatherContext &wc) { H_AUTO_USE(RZ_WeatherManagerClient) if (!wc.WF) return 0.f; nlassert(wc.WFP); float weatherValue = ::getBlendedWeather(day, hour, *(wc.WFP), wc.WF); uint season = CRyzomTime::getSeasonByDay((uint32)day); return wc.WF[season].getThunderIntensity(weatherValue * (wc.WF[season].getNumWeatherSetups() - 1)); } //================================================================================================ float CWeatherManagerClient::getThunderThreshold(uint64 thunderCycle, const CWeatherContext &wc) { H_AUTO_USE(RZ_WeatherManagerClient) // convert the thunder cycle to a day / hour pair uint64 day; float hour; toGlobalTime(day, hour, thunderCycle, wc); return 1.f - 0.5f * getThunderIntensity(day, hour, wc); } // *************************************************************************** CWeatherContext::CWeatherContext () { H_AUTO_USE(RZ_WeatherManagerClient) if (WeatherFunctionParams == NULL) WeatherFunctionParams = new CWeatherFunctionParamsSheet; WFP = WeatherFunctionParams; LC = &WorldLightCycle; WF = NULL; GR = NULL; } // ***************************************************************************