From e0dc8a5dd69a19e100420716cc7f23e011dda3bb Mon Sep 17 00:00:00 2001 From: Roman Chistokhodov Date: Sun, 21 Jul 2024 16:03:02 +0300 Subject: [PATCH] Added weather effects: env_rain, env_snow, func_rainvolume and func_snowvolume. --- cl_dll/CMakeLists.txt | 1 + cl_dll/cdll_int.cpp | 3 + cl_dll/cl_util.h | 2 + cl_dll/environment.cpp | 1405 ++++++++++++++++++++++++++ cl_dll/environment.h | 165 +++ cl_dll/hl/hl_weapons.cpp | 4 + cl_dll/hud.cpp | 18 + cl_dll/hud_msg.cpp | 4 + cl_dll/particleman/CBaseParticle.cpp | 16 +- cl_dll/tri.cpp | 4 + cl_dll/util.cpp | 34 + dlls/effects.cpp | 640 ++++++++++++ dlls/player.cpp | 6 + dlls/util.h | 9 +- fgd/halflife.fgd | 192 ++++ game_shared/fx_flags.h | 32 + game_shared/hull_types.h | 13 + 17 files changed, 2531 insertions(+), 17 deletions(-) create mode 100644 cl_dll/environment.cpp create mode 100644 cl_dll/environment.h create mode 100644 game_shared/hull_types.h diff --git a/cl_dll/CMakeLists.txt b/cl_dll/CMakeLists.txt index 5c4fa9037c..4d11a27736 100644 --- a/cl_dll/CMakeLists.txt +++ b/cl_dll/CMakeLists.txt @@ -103,6 +103,7 @@ set (CLDLL_SOURCES death.cpp demo.cpp entity.cpp + environment.cpp ev_common.cpp events.cpp flashlight.cpp diff --git a/cl_dll/cdll_int.cpp b/cl_dll/cdll_int.cpp index fd75b24039..12eecbcb4b 100644 --- a/cl_dll/cdll_int.cpp +++ b/cl_dll/cdll_int.cpp @@ -46,6 +46,8 @@ #include "IParticleMan_Active.h" #include "CBaseParticle.h" +#include "environment.h" + IParticleMan *g_pParticleMan = NULL; void CL_LoadParticleMan( void ); @@ -418,6 +420,7 @@ int DLLEXPORT HUD_VidInit( void ) if (g_pParticleMan) { g_pParticleMan->ResetParticles(); + g_Environment.Reset(); } #endif diff --git a/cl_dll/cl_util.h b/cl_dll/cl_util.h index ce0cd25ae3..d4ba6df2c4 100644 --- a/cl_dll/cl_util.h +++ b/cl_dll/cl_util.h @@ -170,6 +170,8 @@ void VectorScale( const float *in, float scale, float *out ); float VectorNormalize( float *v ); void VectorInverse( float *v ); +float UTIL_ApproachAngle( float target, float value, float speed ); + // extern vec3_t vec3_origin; extern float vec3_origin[3]; diff --git a/cl_dll/environment.cpp b/cl_dll/environment.cpp new file mode 100644 index 0000000000..9321cbaa11 --- /dev/null +++ b/cl_dll/environment.cpp @@ -0,0 +1,1405 @@ +#include + +#include "environment.h" + +#include "parsemsg.h" + +#include "hud.h" +#include "cl_util.h" +#include "event_api.h" + +#include "particleman.h" +#include "r_studioint.h" +#include "triangleapi.h" + +#include "pm_shared.h" +#include "pm_defs.h" + +#include "hull_types.h" +#include "fx_flags.h" + +extern engine_studio_api_t IEngineStudio; + +extern Vector g_vPlayerVelocity; +extern const Vector g_vecZero; + +extern cvar_t* cl_weather; + +constexpr const char* RAINDROP_DEFAULT_SPRITE = "sprites/effects/rain.spr"; +constexpr const char* WINDPUFF_DEFAULT_SPRITE = "sprites/gas_puff_01.spr"; +constexpr const char* SPLASH_DEFAULT_SPRITE = "sprites/wsplash3.spr"; +constexpr const char* RIPPLE_DEFAULT_SPRITE = "sprites/effects/ripple.spr"; +constexpr const char* SNOWFLAKE_DEFAULT_SPRITE = "sprites/effects/snowflake.spr"; + +constexpr int DEFAULT_WEATHER_INTENSITY = 150; +constexpr int DEFAULT_PRECIPITATION_MIN_HEIGHT = 100; +constexpr int DEFAULT_PRECIPITATION_MAX_HEIGHT = 300; + +CEnvironment g_Environment; + +static int ParticleLightModeToFlag(int lightMode) +{ + switch (lightMode) { + case PARTICLE_LIGHT_NONE: + return LIGHT_NONE; + case PARTICLE_LIGHT_COLOR: + return LIGHT_COLOR; + case PARTICLE_LIGHT_INTENSITY: + return LIGHT_INTENSITY; + default: + return 0; + } +} + +static void AddRandomHorizontalDistance(Vector& vecOrigin, float distance, bool distanceIsRadius) +{ + if (distanceIsRadius) + { + const float radius = Com_RandomFloat( -distance, distance ); + const float angle = Com_RandomFloat(0.0f, M_PI * 2); + vecOrigin.x += radius * cos(angle); + vecOrigin.y += radius * sin(angle); + } + else + { + vecOrigin.x += Com_RandomFloat( -distance, distance ); + vecOrigin.y += Com_RandomFloat( -distance, distance ); + } +} + +float ParticleParams::GetSize() const +{ + if (maxSize > minSize) + return Com_RandomFloat(minSize, maxSize); + return minSize; +} + +WeatherData::WeatherData(): + entIndex(0), + flags(0), + intensity(DEFAULT_WEATHER_INTENSITY), + distance(400), + minHeight(DEFAULT_PRECIPITATION_MIN_HEIGHT), + maxHeight(DEFAULT_PRECIPITATION_MAX_HEIGHT), + location(0.0f,0.0f,0.0f), + absmin(0.0f,0.0f,0.0f), + absmax(0.0f,0.0f,0.0f), + updateTime(0.0f) +{} + +bool WeatherData::IsLocalizedPoint() const +{ + return (flags & SF_WEATHER_LOCALIZED) != 0; +} + +bool WeatherData::IsVolume() const +{ + return (flags & WEATHER_BRUSH_ENTITY) != 0; +} + +bool WeatherData::AllowIndoors() const +{ + return (flags & SF_WEATHER_ALLOW_INDOOR) != 0; +} + +bool WeatherData::DistanceIsRadius() const +{ + return (flags & SF_WEATHER_DISTANCE_IS_RADIUS) != 0; +} + +bool WeatherData::NeedsPVSCheck() const +{ + return IsLocalizedPoint() || IsVolume(); +} + +float WeatherData::RandomHeight() const +{ + if (maxHeight > minHeight) + return Com_RandomFloat(minHeight, maxHeight); + return minHeight; +} + +Vector WeatherData::GetWeatherOrigin(const Vector &globalWeatherOrigin) const +{ + if (IsVolume()) + { + Vector result = (absmin + absmax)/2.0f; + result.z = absmin.z + 16.0f; + return result; + } + if (IsLocalizedPoint()) + return location; + return globalWeatherOrigin; +} + +void WeatherData::GetBoundingBox(const Vector &weatherOrigin, Vector &mins, Vector &maxs) const +{ + if (IsVolume()) + { + mins = absmin; + maxs = absmax; + } + else + { + mins = weatherOrigin - Vector(distance, distance, 0); + maxs = weatherOrigin + Vector(distance, distance, Q_max(minHeight, maxHeight)); + } +} + +Vector WeatherData::GetRandomOrigin(const Vector& weatherOrigin) const +{ + Vector vecOrigin; + if (IsVolume()) + { + vecOrigin.x = Com_RandomFloat(absmin.x, absmax.x); + vecOrigin.y = Com_RandomFloat(absmin.y, absmax.y); + const float minZ = absmin.z + (absmax.z - absmin.z) / 3.0f; + vecOrigin.z = Com_RandomFloat(minZ, absmax.z); + } + else + { + vecOrigin = weatherOrigin; + AddRandomHorizontalDistance(vecOrigin, distance, DistanceIsRadius()); + vecOrigin.z += RandomHeight(); + } + return vecOrigin; +} + +RainData::RainData(): + WeatherData(), + rainSprite(nullptr), + windPuffSprite(nullptr), + splashSprite(nullptr), + rippleSprite(nullptr) +{ + raindropParticleParams.minSize = raindropParticleParams.maxSize = 2.0f; + raindropParticleParams.color = Vector(255.0f, 255.0f, 255.0f); + raindropParticleParams.brightness = 128; + raindropParticleParams.renderMode = kRenderTransAlpha; + raindropParticleParams.lightFlag = LIGHT_NONE; + raindropStretchY = 40.0f; + + raindropMinSpeed = 500.0f; + raindropMaxSpeed = 1800.0f; + raindropLife = 1.0f; + + windParticleParams.minSize = 50.0f; + windParticleParams.maxSize = 75.0f; + windParticleParams.color = Vector(128.0f, 128.0f, 128.0f); + windParticleParams.brightness = 128; + windParticleParams.renderMode = kRenderTransAlpha; + windParticleParams.lightFlag = LIGHT_NONE; + + windpuffLife = 6.0f; + + splashParticleParams.minSize = 20; + splashParticleParams.maxSize = 25; + splashParticleParams.color = Vector(255.0f, 255.0f, 255.0f); + splashParticleParams.brightness = 150; + splashParticleParams.renderMode = kRenderTransAdd; + splashParticleParams.lightFlag = LIGHT_INTENSITY; + + rippleParticleParams.minSize = rippleParticleParams.maxSize = 15.0f; + rippleParticleParams.color = Vector(255.0f, 255.0f, 255.0f); + rippleParticleParams.brightness = 150; + rippleParticleParams.renderMode = kRenderTransAdd; + rippleParticleParams.lightFlag = LIGHT_INTENSITY; +} + +float RainData::GetRaindropFallingSpeed() const +{ + float speed1 = fabs(raindropMinSpeed); + float speed2 = fabs(raindropMaxSpeed); + + float minSpeed = speed1 < speed2 ? speed1 : speed2; + float maxSpeed = speed1 > speed2 ? speed1 : speed2; + + if (maxSpeed > minSpeed) + return Com_RandomFloat(minSpeed, maxSpeed); + return minSpeed; +} + +bool RainData::RaindropsAffectedByWind() const +{ + return !(flags & SF_RAIN_NOT_AFFECTED_BY_WIND); +} + +bool RainData::WindPuffsAllowed() const +{ + return !(flags & SF_RAIN_NO_WIND); +} + +bool RainData::SplashesAllowed() const +{ + return !(flags & SF_RAIN_NO_SPLASHES); +} + +bool RainData::RipplesAllowed() const +{ + return !(flags & SF_RAIN_NO_RIPPLES); +} + +SnowData::SnowData(): + WeatherData(), + snowSprite(nullptr) +{ + distance = 300; + + snowflakeParticleParams.minSize = 2.0f; + snowflakeParticleParams.maxSize = 2.5f; + snowflakeParticleParams.color = Vector(128.0f, 128.0f, 128.0f); + snowflakeParticleParams.brightness = 130.0f; + snowflakeParticleParams.renderMode = kRenderTransAdd; + snowflakeParticleParams.lightFlag = LIGHT_NONE; + snowflakeInitialBrightness = 1; + snowflakeMinSpeed = 100; + snowflakeMaxSpeed = 200; + snowflakeLife = 3.0f; +} + +float SnowData::GetSnowflakeFallingSpeed() const +{ + float speed1 = fabs(snowflakeMinSpeed); + float speed2 = fabs(snowflakeMaxSpeed); + + float minSpeed = speed1 < speed2 ? speed1 : speed2; + float maxSpeed = speed1 > speed2 ? speed1 : speed2; + + if (maxSpeed > minSpeed) + return Com_RandomFloat(minSpeed, maxSpeed); + return minSpeed; +} + +bool SnowData::SnowflakesAffectedByWind() const +{ + return !(flags & SF_SNOW_NOT_AFFECTED_BY_WIND); +} + +class CPartRainDrop : public CBaseParticle +{ +public: + CPartRainDrop() = default; + void Think( float flTime ) override; + void Touch( Vector pos, Vector normal, int index ) override; + + bool m_splashAllowed = true; + bool m_rippleAllowed = true; + ParticleParams m_splashParams; + ParticleParams m_rippleParams; + model_t* m_pRainSplash = nullptr; + model_t* m_pRipple = nullptr; + +private: + bool m_bTouched = false; +}; + +void CPartRainDrop::Think( float flTime ) +{ + Vector vecViewAngles; + Vector vecForward, vecRight, vecUp; + + gEngfuncs.GetViewAngles( vecViewAngles ); + + AngleVectors( vecViewAngles, vecForward, vecRight, vecUp ); + + m_vAngles.y = vecViewAngles.y; + m_vAngles.z = atan( DotProduct( m_vVelocity, vecRight ) / m_vVelocity.z ) * ( 180.0 / M_PI ); + + if( m_flBrightness < 155.0f ) + m_flBrightness += 6.5f; + + CBaseParticle::Think( flTime ); +} + +void CPartRainDrop::Touch( Vector pos, Vector normal, int index ) +{ + if( m_bTouched ) + { + return; + } + + m_bTouched = true; + + Vector vecStart = m_vOrigin; + vecStart.z += 32.0f; + + pmtrace_t trace; + + { + Vector vecEnd = m_vOrigin; + vecEnd.z -= 16.0f; + + gEngfuncs.pEventAPI->EV_PlayerTrace( vecStart, vecEnd, PM_WORLD_ONLY, -1, &trace ); + } + + Vector vecNormal; + + vecNormal.x = normal.x; + vecNormal.y = normal.y; + vecNormal.z = -normal.z; + + Vector vecAngles; + + VectorAngles( vecNormal, vecAngles ); + + if( gEngfuncs.PM_PointContents( trace.endpos, nullptr ) == gEngfuncs.PM_PointContents( vecStart, nullptr ) ) + { + if (m_splashAllowed && m_pRainSplash) + { + CBaseParticle* pParticle = new CBaseParticle(); + + model_t* pRainSplash = m_pRainSplash; + + pParticle->InitializeSprite( m_vOrigin + normal, Vector( 90.0f, 0.0f, 0.0f ), pRainSplash, m_splashParams.GetSize(), m_splashParams.brightness ); + + pParticle->m_iRendermode = m_splashParams.renderMode; + + pParticle->m_flMass = 1.0f; + pParticle->m_flGravity = 0.1f; + + pParticle->SetCullFlag( CULL_PVS ); + pParticle->SetLightFlag( m_splashParams.lightFlag ); + + pParticle->m_vColor = m_splashParams.color; + + pParticle->m_iNumFrames = pRainSplash->numframes - 1; + pParticle->m_iFramerate = Com_RandomLong( 30, 45 ); + pParticle->m_flDieTime = gEngfuncs.GetClientTime() + 0.3f; + pParticle->SetCollisionFlags( TRI_ANIMATEDIE ); + pParticle->SetRenderFlag( RENDER_FACEPLAYER ); + } + } + else + { + if (m_rippleAllowed && m_pRipple) + { + Vector vecBegin = vecStart; + Vector vecEnd = trace.endpos; + + Vector vecDist = vecEnd - vecBegin; + + Vector vecHalf; + + Vector vecNewBegin; + + while( vecDist.Length() > 4.0 ) + { + vecDist = vecDist * 0.5; + + vecHalf = vecBegin + vecDist; + + if( gEngfuncs.PM_PointContents( vecBegin, nullptr ) == gEngfuncs.PM_PointContents( vecHalf, nullptr ) ) + { + vecBegin = vecHalf; + vecNewBegin = vecHalf; + } + else + { + vecEnd = vecHalf; + vecNewBegin = vecBegin; + } + + vecDist = vecEnd - vecNewBegin; + } + + CBaseParticle* pParticle = new CBaseParticle(); + + pParticle->InitializeSprite( vecBegin, vecAngles, m_pRipple, m_rippleParams.GetSize(), m_rippleParams.brightness ); + + pParticle->m_iRendermode = m_rippleParams.renderMode; + pParticle->m_flScaleSpeed = 1.0f; + pParticle->m_vColor = m_rippleParams.color; + pParticle->SetCullFlag( CULL_PVS ); + pParticle->SetLightFlag( m_rippleParams.lightFlag ); + pParticle->m_flFadeSpeed = 2.0f; + pParticle->m_flDieTime = gEngfuncs.GetClientTime() + 2.0f; + } + } +} + +class CPartWind : public CBaseParticle +{ +public: + CPartWind() = default; + + void Think( float flTime ) override; +}; + +void CPartWind::Think( float flTime ) +{ + if( m_flDieTime - flTime <= 3.0 ) + { + if( m_flBrightness > 0.0 ) + { + m_flBrightness -= ( flTime - m_flTimeCreated ) * 0.4; + } + + if( m_flBrightness < 0.0 ) + { + m_flBrightness = 0; + flTime = m_flDieTime = gEngfuncs.GetClientTime(); + } + } + else + { + if( m_flBrightness < 105.0 ) + { + m_flBrightness += ( flTime - m_flTimeCreated ) * 5.0 + 4.0; + } + } + + CBaseParticle::Think( flTime ); +} + +class CPartSnowFlake : public CBaseParticle +{ +public: + CPartSnowFlake() = default; + void Think( float flTime ) override; + void Touch( Vector pos, Vector normal, int index ) override; + +public: + bool m_bSpiral; + float m_flSpiralTime; + int m_targetBrightness; + +private: + bool m_bTouched = false; +}; + +void CPartSnowFlake::Think( float flTime ) +{ + if( m_flBrightness < m_targetBrightness && !m_bTouched ) + m_flBrightness += 4.5; + + if (m_flBrightness > 255.0f) + m_flBrightness = 255.0f; + + Fade( flTime ); + Spin( flTime ); + + if( m_flSpiralTime <= gEngfuncs.GetClientTime() ) + { + m_bSpiral = !m_bSpiral; + + m_flSpiralTime = gEngfuncs.GetClientTime() + Com_RandomLong( 2, 4 ); + } + + if( m_bSpiral && !m_bTouched ) + { + const float flDelta = flTime - g_Environment.GetOldTime(); + + const float flSpin = sin( flTime * 5.0 + reinterpret_cast( this ) ); + + m_vOrigin = m_vOrigin + m_vVelocity * flDelta; + + m_vOrigin.x += ( flSpin * flSpin ) * 0.3; + } + else + { + CalculateVelocity( flTime ); + } + + CheckCollision( flTime ); +} + +void CPartSnowFlake::Touch( Vector pos, Vector normal, int index ) +{ + if( m_bTouched ) + { + return; + } + + m_bTouched = true; + + SetRenderFlag( RENDER_FACEPLAYER ); + + m_flOriginalBrightness = m_flBrightness; + + m_vVelocity = g_vecZero; + + //m_iRendermode = kRenderTransAdd; + + m_flFadeSpeed = 0; + m_flScaleSpeed = 0; + m_flDampingTime = 0; + m_iFrame = 0; + m_flMass = 1.0; + m_flGravity = 0; + + //m_vColor.x = m_vColor.y = m_vColor.z = 128.0; + + m_flDieTime = gEngfuncs.GetClientTime() + 0.5; + + m_flTimeCreated = gEngfuncs.GetClientTime(); +} + +void CEnvironment::Initialize() +{ + Reset(); + + m_flWeatherValue = cl_weather->value; +} + +void CEnvironment::Reset() +{ + m_pRainSprite = nullptr; + m_pGasPuffSprite = nullptr; + m_pRainSplash = nullptr; + m_pRipple = nullptr; + m_pSnowSprite = nullptr; + + m_rains.clear(); + m_snows.clear(); + + m_vecWeatherOrigin = g_vecZero; + + m_vecWind.x = Com_RandomFloat( -80.0f, 80.0f ); + m_vecWind.y = Com_RandomFloat( -80.0f, 80.0f ); + m_vecWind.z = 0; + + m_vecDesiredWindDirection.x = Com_RandomFloat( -80.0f, 80.0f ); + m_vecDesiredWindDirection.y = Com_RandomFloat( -80.0f, 80.0f ); + m_vecDesiredWindDirection.z = 0; + + m_flNextWindChangeTime = gEngfuncs.GetClientTime(); +} + +void CEnvironment::Update() +{ + Vector vecOrigin = gHUD.m_vecOrigin; + + if( g_iUser1 > 0 && g_iUser1 != OBS_ROAMING ) + { + if( cl_entity_t* pFollowing = gEngfuncs.GetEntityByIndex( g_iUser2 ) ) + { + vecOrigin = pFollowing->origin; + } + } + + vecOrigin.z += 36.0f; + + if( cl_weather->value > 3.0 ) + { + gEngfuncs.Cvar_SetValue( "cl_weather", 3.0 ); + } + + m_flWeatherValue = cl_weather->value; + + if( !IEngineStudio.IsHardware() ) + m_flWeatherValue = 0; + + m_vecWeatherOrigin = vecOrigin; + + if (ShouldUpdateWind()) + UpdateWind(); + + const float clientTime = gEngfuncs.GetClientTime(); + + for (auto& rain : m_rains) + { + if (rain.updateTime <= clientTime) + { + UpdateRain(rain); + rain.updateTime = clientTime + 0.3f; + } + } + for (auto& snow : m_snows) + { + if (snow.updateTime <= clientTime) + { + UpdateSnow(snow); + snow.updateTime = clientTime + 0.7f; + } + } + + m_flOldTime = clientTime; +} + +bool CEnvironment::ShouldUpdateWind() const +{ + for (const auto& rain : m_rains) + { + if (rain.RaindropsAffectedByWind() || rain.WindPuffsAllowed()) + return true; + } + for(const auto& snow : m_snows) + { + if (snow.SnowflakesAffectedByWind()) + return true; + } + return false; +} + +void CEnvironment::UpdateWind() +{ + if( m_flNextWindChangeTime <= gEngfuncs.GetClientTime() ) + { + m_vecDesiredWindDirection.x = Com_RandomFloat( -80.0f, 80.0f ); + m_vecDesiredWindDirection.y = Com_RandomFloat( -80.0f, 80.0f ); + m_vecDesiredWindDirection.z = 0; + + m_flNextWindChangeTime = gEngfuncs.GetClientTime() + Com_RandomFloat( 15.0f, 30.0f ); + + m_flDesiredWindSpeed = m_vecDesiredWindDirection.Length(); + Vector vecDir = m_vecDesiredWindDirection.Normalize(); + + if( vecDir.x == 0.0f && vecDir.y == 0.0f ) + { + m_flIdealYaw = 0; + } + else + { + m_flIdealYaw = floor( atan2( vecDir.y, vecDir.x ) * ( 180.0 / M_PI ) ); + + if( m_flIdealYaw < 0.0f ) + m_flIdealYaw += 360.0f; + } + } + + Vector vecWindDir = m_vecWind; + + vecWindDir = vecWindDir.Normalize(); + + Vector vecAngles; + + VectorAngles( vecWindDir, vecAngles ); + + float flYaw; + + if( vecAngles.y < 0.0f ) + { + flYaw = 120 * ( 3 * static_cast( floor( vecAngles.y / 360.0 ) ) + 3 ); + } + else + { + if( vecAngles.y < 360.0f ) + { + flYaw = vecAngles.y; + } + else + { + flYaw = vecAngles.y - ( 360 * floor( vecAngles.y / 360.0 ) ); + } + } + + if( m_flIdealYaw != flYaw ) + { + const float flSpeed = ( gEngfuncs.GetClientTime() - m_flOldTime ) * 0.5 * 10.0; + vecAngles.y = UTIL_ApproachAngle( m_flIdealYaw, flYaw, flSpeed ); + } + + Vector vecNewWind; + + AngleVectors( vecAngles, vecNewWind, nullptr, nullptr ); + + m_vecWind = vecNewWind * m_flDesiredWindSpeed; +} + +void CEnvironment::UpdateRain(const RainData& rainData) +{ + const float rainIntensity = rainData.intensity * m_flWeatherValue; + if( rainIntensity > 0.0f ) + { + int iWindParticle = 0; + + Vector vecEndPos; + pmtrace_t trace; + + Vector weatherOrigin = rainData.GetWeatherOrigin(m_vecWeatherOrigin); + + int rainDropCount = 0; + int windParticleCount = 0; + + const bool allowIndoors = rainData.AllowIndoors(); + const bool windParticlesEnabled = rainData.WindPuffsAllowed(); + + bool inPvs = true; + if (rainData.NeedsPVSCheck()) + { + Vector pvsMins; + Vector pvsMaxs; + rainData.GetBoundingBox(weatherOrigin, pvsMins, pvsMaxs); + inPvs = gEngfuncs.pTriAPI->BoxInPVS( pvsMins, pvsMaxs ) != 0; + } + + // Optimization: don't create localized rain particles when not in PVS + if (!inPvs) + return; + + for( size_t uiIndex = 0; static_cast( uiIndex ) < rainIntensity; ++uiIndex ) + { + Vector vecOrigin = rainData.GetRandomOrigin(weatherOrigin); + + vecEndPos.x = vecOrigin.x + ( ( Com_RandomLong( 0, 5 ) > 2 ) ? g_vPlayerVelocity.x : -g_vPlayerVelocity.x ); + vecEndPos.y = vecOrigin.y + g_vPlayerVelocity.y; + vecEndPos.z = 8000.0f; + + gEngfuncs.pEventAPI->EV_SetTraceHull( large_hull ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecOrigin, vecEndPos, PM_WORLD_ONLY, -1, &trace ); + const char* pszTexture = gEngfuncs.pEventAPI->EV_TraceTexture( trace.ent, vecOrigin, trace.endpos ); + + if( allowIndoors || (pszTexture && strncmp( pszTexture, "sky", 3 ) == 0) ) + { + CPartRainDrop* rainParticle = CreateRaindrop( vecOrigin, rainData ); + if (rainParticle) + rainDropCount++; + + if (windParticlesEnabled) + { + if( iWindParticle == 15 ) + { + iWindParticle = 1; + + Vector vecWindOrigin; + vecWindOrigin.x = vecOrigin.x; + vecWindOrigin.y = vecOrigin.y; + vecWindOrigin.z = weatherOrigin.z; + + vecEndPos.z = 8000.0f; + + gEngfuncs.pEventAPI->EV_SetTraceHull( large_hull ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecWindOrigin, vecEndPos, PM_WORLD_ONLY, -1, &trace ); + pszTexture = gEngfuncs.pEventAPI->EV_TraceTexture( trace.ent, vecOrigin, trace.endpos ); + + if( allowIndoors || (pszTexture && strncmp( pszTexture, "sky", 3 ) == 0) ) + { + vecEndPos.z = -8000.0f; + + gEngfuncs.pEventAPI->EV_SetTraceHull( large_hull ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecWindOrigin, vecEndPos, PM_WORLD_ONLY, -1, &trace ); + + CPartWind* windParticle = CreateWindParticle( trace.endpos, rainData ); + if (windParticle) + windParticleCount++; + } + } + else + { + ++iWindParticle; + } + } + } + } + + //gEngfuncs.Con_Printf("Rain drops spawned: %d. Wind particles spawned: %d\n", rainDropCount, windParticleCount); + } +} + +void CEnvironment::UpdateSnow(const SnowData& snowData) +{ + const float snowIntensity = snowData.intensity * m_flWeatherValue; + if( snowIntensity > 0.0f ) + { + Vector vecEndPos; + pmtrace_t trace; + + Vector weatherOrigin = snowData.GetWeatherOrigin(m_vecWeatherOrigin); + + const bool allowIndoors = snowData.AllowIndoors(); + + bool inPvs = true; + if (snowData.NeedsPVSCheck()) + { + Vector pvsMins; + Vector pvsMaxs; + snowData.GetBoundingBox(weatherOrigin, pvsMins, pvsMaxs); + inPvs = gEngfuncs.pTriAPI->BoxInPVS( pvsMins, pvsMaxs ) != 0; + } + + // Optimization: don't create localized snow particles when not in PVS + if (!inPvs) + return; + + for( size_t uiIndex = 0; static_cast( uiIndex ) < 150.0f * m_flWeatherValue; ++uiIndex ) + { + Vector vecOrigin = snowData.GetRandomOrigin(weatherOrigin); + + vecEndPos.x = vecOrigin.x + ( ( Com_RandomLong( 0, 5 ) > 2 ) ? g_vPlayerVelocity.x : -g_vPlayerVelocity.x ); + vecEndPos.y = vecOrigin.y + g_vPlayerVelocity.y; + vecEndPos.z = 8000.0f; + + gEngfuncs.pEventAPI->EV_SetTraceHull( large_hull ); + gEngfuncs.pEventAPI->EV_PlayerTrace( vecOrigin, vecEndPos, PM_WORLD_ONLY, -1, &trace ); + const char* pszTexture = gEngfuncs.pEventAPI->EV_TraceTexture( trace.ent, vecOrigin, trace.endpos ); + + if( allowIndoors || (pszTexture && strncmp( pszTexture, "sky", 3 ) == 0) ) + { + CreateSnowFlake( vecOrigin, snowData ); + } + } + } +} + +CPartRainDrop* CEnvironment::CreateRaindrop( const Vector& vecOrigin, const RainData& rainData ) +{ + if( !rainData.rainSprite ) + { + return nullptr; + } + + CPartRainDrop* pParticle = new CPartRainDrop(); + pParticle->m_splashAllowed = rainData.SplashesAllowed(); + pParticle->m_rippleAllowed = rainData.RipplesAllowed(); + pParticle->m_splashParams = rainData.splashParticleParams; + pParticle->m_rippleParams = rainData.rippleParticleParams; + pParticle->m_pRainSplash = rainData.splashSprite; + pParticle->m_pRipple = rainData.rippleSprite; + + pParticle->InitializeSprite( vecOrigin, g_vecZero, rainData.rainSprite, rainData.raindropParticleParams.GetSize(), rainData.raindropParticleParams.brightness ); + + strcpy( pParticle->m_szClassname, "particle_rain" ); + + pParticle->m_flStretchY = rainData.raindropStretchY; + + if (rainData.RaindropsAffectedByWind()) + { + pParticle->m_vVelocity.x = m_vecWind.x * Com_RandomFloat( 1.0f, 2.0f ); + pParticle->m_vVelocity.y = m_vecWind.y * Com_RandomFloat( 1.0f, 2.0f ); + } + else + { + pParticle->m_vVelocity.x = pParticle->m_vVelocity.y = 0.0f; + } + + pParticle->m_vVelocity.z = -rainData.GetRaindropFallingSpeed(); + + pParticle->SetCollisionFlags( TRI_COLLIDEWORLD | TRI_COLLIDEKILL | TRI_WATERTRACE ); + + pParticle->m_flGravity = 0; + + pParticle->SetCullFlag( CULL_PVS ); + pParticle->SetLightFlag( rainData.raindropParticleParams.lightFlag ); + + pParticle->m_iRendermode = rainData.raindropParticleParams.renderMode; + + pParticle->m_vColor = rainData.raindropParticleParams.color; + + pParticle->m_flDieTime = gEngfuncs.GetClientTime() + rainData.raindropLife; + + return pParticle; +} + +CPartWind* CEnvironment::CreateWindParticle( const Vector& vecOrigin, const RainData& rainData ) +{ + if( !rainData.windPuffSprite ) + { + return nullptr; + } + + CPartWind* pParticle = new CPartWind(); + + Vector vecPartOrigin = vecOrigin; + + float particleSize = rainData.windParticleParams.GetSize(); + + vecPartOrigin.z += particleSize * 0.3f; + + pParticle->InitializeSprite( + vecPartOrigin, g_vecZero, + rainData.windPuffSprite, + particleSize, + rainData.windParticleParams.brightness ); + + pParticle->m_iNumFrames = rainData.windPuffSprite->numframes; + + strcpy( pParticle->m_szClassname, "wind_particle" ); + + //pParticle->m_iFrame = UTIL_RandomLong( m_pGasPuffSprite->numframes / 2, m_pGasPuffSprite->numframes ); + + pParticle->m_vVelocity.x = m_vecWind.x / Com_RandomFloat( 1.0f, 2.0f ); + pParticle->m_vVelocity.y = m_vecWind.y / Com_RandomFloat( 1.0f, 2.0f ); + + if( Com_RandomFloat( 0.0, 1.0 ) < 0.1 ) + { + pParticle->m_vVelocity.x *= 0.5; + pParticle->m_vVelocity.y *= 0.5; + } + + pParticle->SetCollisionFlags( TRI_COLLIDEWORLD ); + pParticle->m_flGravity = 0; + + pParticle->m_iRendermode = rainData.windParticleParams.renderMode; + + pParticle->SetCullFlag( CULL_PVS ); + pParticle->SetLightFlag( rainData.windParticleParams.lightFlag ); + pParticle->SetRenderFlag( RENDER_FACEPLAYER ); + + pParticle->m_vAVelocity.z = Com_RandomFloat( -1.0, 1.0 ); + + pParticle->m_flScaleSpeed = 0.4; + pParticle->m_flDampingTime = 0; + + pParticle->m_iFrame = 0; + + pParticle->m_flMass = 1.0f; + pParticle->m_flBounceFactor = 0; + pParticle->m_vColor = rainData.windParticleParams.color; + + pParticle->m_flFadeSpeed = -1.0f; + + pParticle->m_flDieTime = gEngfuncs.GetClientTime() + rainData.windpuffLife; + + return pParticle; +} + +void CEnvironment::CreateSnowFlake( const Vector& vecOrigin, const SnowData& snowData ) +{ + if( !snowData.snowSprite ) + { + return; + } + + CPartSnowFlake* pParticle = new CPartSnowFlake(); + pParticle->m_targetBrightness = snowData.snowflakeParticleParams.brightness; + + pParticle->InitializeSprite( + vecOrigin, g_vecZero, + snowData.snowSprite, + snowData.snowflakeParticleParams.GetSize(), snowData.snowflakeInitialBrightness ); + + strcpy( pParticle->m_szClassname, "snow_particle" ); + + pParticle->m_iNumFrames = snowData.snowSprite->numframes; + + if (snowData.SnowflakesAffectedByWind()) + { + pParticle->m_vVelocity.x = m_vecWind.x / Com_RandomFloat( 1.0, 2.0 ); + pParticle->m_vVelocity.y = m_vecWind.y / Com_RandomFloat( 1.0, 2.0 ); + } + else + { + pParticle->m_vVelocity.x = pParticle->m_vVelocity.y = 0.0f; + } + + pParticle->m_vVelocity.z = -snowData.GetSnowflakeFallingSpeed(); + + pParticle->SetCollisionFlags( TRI_COLLIDEWORLD ); + + const float flFrac = Com_RandomFloat( 0.0, 1.0 ); + + if( flFrac >= 0.1 ) + { + if( flFrac < 0.2 ) + { + pParticle->m_vVelocity.z = -65.0; + } + else if( flFrac < 0.3 ) + { + pParticle->m_vVelocity.z = -75.0; + } + } + else + { + pParticle->m_vVelocity.x *= 0.5; + pParticle->m_vVelocity.y *= 0.5; + } + + pParticle->m_iRendermode = snowData.snowflakeParticleParams.renderMode; + + pParticle->SetCullFlag( CULL_PVS ); + pParticle->SetLightFlag( snowData.snowflakeParticleParams.lightFlag ); + pParticle->SetRenderFlag( RENDER_FACEPLAYER ); + + pParticle->m_flScaleSpeed = 0; + pParticle->m_flDampingTime = 0; + pParticle->m_iFrame = 0; + pParticle->m_flMass = 1.0; + + pParticle->m_flGravity = 0; + pParticle->m_flBounceFactor = 0; + + pParticle->m_vColor = snowData.snowflakeParticleParams.color; + + pParticle->m_flDieTime = gEngfuncs.GetClientTime() + snowData.snowflakeLife; + + pParticle->m_bSpiral = Com_RandomLong( 0, 1 ) != 0; + + pParticle->m_flSpiralTime = gEngfuncs.GetClientTime() + Com_RandomLong( 2, 4 ); +} + +static void ReadWeatherData(WeatherData& data) +{ + const short intensity = READ_SHORT(); + if (intensity > 0) + data.intensity = intensity; + + if (data.IsVolume()) + { + data.absmin.x = READ_COORD(); + data.absmin.y = READ_COORD(); + data.absmin.z = READ_COORD(); + + data.absmax.x = READ_COORD(); + data.absmax.y = READ_COORD(); + data.absmax.z = READ_COORD(); + } + else + { + const short distance = READ_SHORT(); + if (distance > 0) + data.distance = distance; + + const short minHeight = READ_SHORT(); + if (minHeight > 0) + data.minHeight = minHeight; + + const short maxHeight = READ_SHORT(); + if (maxHeight > 0) + data.maxHeight = maxHeight; + + if (data.IsLocalizedPoint()) + { + data.location.x = READ_COORD(); + data.location.y = READ_COORD(); + data.location.z = READ_COORD(); + } + } +} + +int CEnvironment::MsgFunc_Rain(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + const int entIndex = READ_LONG(); + const int rainFlags = READ_SHORT(); + + if (!(rainFlags & SF_RAIN_ACTIVE)) + { + RemoveRain(entIndex); + return 1; + } + + RainData rainData; + rainData.entIndex = entIndex; + rainData.flags = rainFlags; + + ReadWeatherData(rainData); + + // Raindrops + { + const short raindropWidth = READ_SHORT(); + const short raindropHeight = READ_SHORT(); + + if (raindropWidth > 0) + { + rainData.raindropParticleParams.minSize = rainData.raindropParticleParams.maxSize = raindropWidth; + } + if (raindropHeight > 0) + { + const float raindropSize = raindropWidth > 0 ? raindropWidth : rainData.raindropParticleParams.minSize; + rainData.raindropStretchY = raindropHeight / raindropSize; + } + + const int raindropRenderMode = READ_BYTE(); + if (raindropRenderMode > 0) + rainData.raindropParticleParams.renderMode = raindropRenderMode; + + const int raindropBrightness = READ_BYTE(); + if (raindropBrightness > 0) + rainData.raindropParticleParams.brightness = raindropBrightness; + + const int raindropRed = READ_BYTE(); + const int raindropGreen = READ_BYTE(); + const int raindropBlue = READ_BYTE(); + + if (raindropRed + raindropGreen + raindropBlue > 0) + rainData.raindropParticleParams.color = Vector(raindropRed, raindropGreen, raindropBlue); + + const int rainLightMode = READ_BYTE(); + if (rainLightMode) + rainData.raindropParticleParams.lightFlag = ParticleLightModeToFlag(rainLightMode); + + const int raindropMinSpeed = READ_SHORT(); + const int raindropMaxSpeed = READ_SHORT(); + + if (raindropMinSpeed) + rainData.raindropMinSpeed = raindropMinSpeed; + if (raindropMaxSpeed) + rainData.raindropMaxSpeed = raindropMaxSpeed; + + const float raindropLife = READ_SHORT() / 100.0f; + if (raindropLife > 0) + rainData.raindropLife = raindropLife; + } + + // Wind puffs + { + const short windpuffMinSize = READ_SHORT(); + if (windpuffMinSize > 0) + rainData.windParticleParams.minSize = windpuffMinSize; + + const short windpuffMaxSize = READ_SHORT(); + if (windpuffMaxSize > 0) + rainData.windParticleParams.maxSize = windpuffMaxSize; + + const int windPuffRenderMode = READ_BYTE(); + if (windPuffRenderMode > 0) + rainData.windParticleParams.renderMode = windPuffRenderMode; + + const int windPuffBrightness = READ_BYTE(); + if (windPuffBrightness > 0) + rainData.windParticleParams.brightness = windPuffBrightness; + + const int windPuffRed = READ_BYTE(); + const int windPuffGreen = READ_BYTE(); + const int windPuffBlue = READ_BYTE(); + + if (windPuffRed + windPuffGreen + windPuffBlue > 0) + rainData.windParticleParams.color = Vector(windPuffRed, windPuffGreen, windPuffBlue); + + const int windPuffLightMode = READ_BYTE(); + if (windPuffLightMode) + rainData.windParticleParams.lightFlag = ParticleLightModeToFlag(windPuffLightMode); + + const float windpuffLife = READ_SHORT() / 100.0f; + if (windpuffLife > 0) + rainData.windpuffLife = windpuffLife; + } + + // Rain splash + { + const short splashMinSize = READ_SHORT(); + if (splashMinSize > 0) + rainData.splashParticleParams.minSize = splashMinSize; + + const short splashMaxSize = READ_SHORT(); + if (splashMaxSize > 0) + rainData.splashParticleParams.maxSize = splashMaxSize; + + const int splashRenderMode = READ_BYTE(); + if (splashRenderMode > 0) + rainData.splashParticleParams.renderMode = splashRenderMode; + + const int splashBrightness = READ_BYTE(); + if (splashBrightness > 0) + rainData.splashParticleParams.brightness = splashBrightness; + + const int splashRed = READ_BYTE(); + const int splashGreen = READ_BYTE(); + const int splashBlue = READ_BYTE(); + + if (splashRed + splashGreen + splashBlue > 0) + rainData.splashParticleParams.color = Vector(splashRed, splashGreen, splashBlue); + + const int splashLightMode = READ_BYTE(); + if (splashLightMode) + rainData.splashParticleParams.lightFlag = ParticleLightModeToFlag(splashLightMode); + } + + // Ripple + { + const short rippleSize = READ_SHORT(); + if (rippleSize > 0) + rainData.rippleParticleParams.minSize = rainData.rippleParticleParams.maxSize = rippleSize; + + const int rippleRenderMode = READ_BYTE(); + if (rippleRenderMode > 0) + rainData.rippleParticleParams.renderMode = rippleRenderMode; + + const int rippleBrightness = READ_BYTE(); + if (rippleBrightness > 0) + rainData.rippleParticleParams.brightness = rippleBrightness; + + const int rippleRed = READ_BYTE(); + const int rippleGreen = READ_BYTE(); + const int rippleBlue = READ_BYTE(); + + if (rippleRed + rippleGreen + rippleBlue > 0) + rainData.rippleParticleParams.color = Vector(rippleRed, rippleGreen, rippleBlue); + + const int rippleLightMode = READ_BYTE(); + if (rippleLightMode) + rainData.rippleParticleParams.lightFlag = ParticleLightModeToFlag(rippleLightMode); + } + + // Sprites + + const char* raindropSprite = READ_STRING(); + if (raindropSprite && *raindropSprite) + { + rainData.rainSprite = LoadSprite(raindropSprite); + } + else + { + if (!m_pRainSprite) + m_pRainSprite = LoadSprite(RAINDROP_DEFAULT_SPRITE); + rainData.rainSprite = m_pRainSprite; + } + + const char* windpuffSprite = READ_STRING(); + if (rainData.WindPuffsAllowed()) + { + if (windpuffSprite && *windpuffSprite) + { + rainData.windPuffSprite = LoadSprite(windpuffSprite); + } + else + { + if (!m_pGasPuffSprite) + m_pGasPuffSprite = LoadSprite(WINDPUFF_DEFAULT_SPRITE); + rainData.windPuffSprite = m_pGasPuffSprite; + } + } + + const char* splashSprite = READ_STRING(); + if (rainData.SplashesAllowed()) + { + if (splashSprite && *splashSprite) + { + rainData.splashSprite = LoadSprite(splashSprite); + } + else + { + if (!m_pRainSplash) + m_pRainSplash = LoadSprite(SPLASH_DEFAULT_SPRITE); + rainData.splashSprite = m_pRainSplash; + } + } + + const char* rippleSprite = READ_STRING(); + if (rainData.RipplesAllowed()) + { + if (rippleSprite && *rippleSprite) + { + rainData.rippleSprite = LoadSprite(rippleSprite); + } + else + { + if (!m_pRipple) + m_pRipple = LoadSprite(RIPPLE_DEFAULT_SPRITE); + rainData.rippleSprite = m_pRipple; + } + } + + AddRain(rainData); + + return 1; +} + +int CEnvironment::MsgFunc_Snow(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + const int entIndex = READ_LONG(); + const int snowFlags = READ_SHORT(); + + if (!(snowFlags & SF_SNOW_ACTIVE)) + { + RemoveSnow(entIndex); + return 1; + } + + SnowData snowData; + snowData.entIndex = entIndex; + snowData.flags = snowFlags; + + ReadWeatherData(snowData); + + // Snowflakes + { + const short snowflakeMinSize = READ_SHORT(); + if (snowflakeMinSize > 0) + snowData.snowflakeParticleParams.minSize = snowflakeMinSize; + + const short snowflakeMaxSize = READ_SHORT(); + if (snowflakeMaxSize > 0) + snowData.snowflakeParticleParams.maxSize = snowflakeMaxSize; + + const int snowflakeRenderMode = READ_BYTE(); + if (snowflakeRenderMode > 0) + snowData.snowflakeParticleParams.renderMode = snowflakeRenderMode; + + const int snowflakeBrightness = READ_BYTE(); + if (snowflakeBrightness > 0) + snowData.snowflakeParticleParams.brightness = snowflakeBrightness; + + const int snowflakeRed = READ_BYTE(); + const int snowflakeGreen = READ_BYTE(); + const int snowflakeBlue = READ_BYTE(); + + if (snowflakeRed + snowflakeGreen + snowflakeBlue > 0) + snowData.snowflakeParticleParams.color = Vector(snowflakeRed, snowflakeGreen, snowflakeBlue); + + const int snowLightMode = READ_BYTE(); + if (snowLightMode) + snowData.snowflakeParticleParams.lightFlag = ParticleLightModeToFlag(snowLightMode); + + const int snowflakeMinSpeed = READ_SHORT(); + const int snowflakeMaxSpeed = READ_SHORT(); + + if (snowflakeMinSpeed) + snowData.snowflakeMinSpeed = snowflakeMinSpeed; + if (snowflakeMaxSpeed) + snowData.snowflakeMaxSpeed = snowflakeMaxSpeed; + + const float snowflakeLife = READ_SHORT() / 100.0f; + if (snowflakeLife > 0) + snowData.snowflakeLife = snowflakeLife; + } + + const char* snowflakeSprite = READ_STRING(); + if (snowflakeSprite && *snowflakeSprite) + { + snowData.snowSprite = LoadSprite(snowflakeSprite); + } + else + { + if (!m_pSnowSprite) + m_pSnowSprite = LoadSprite(SNOWFLAKE_DEFAULT_SPRITE); + snowData.snowSprite = m_pSnowSprite; + } + + AddSnow(snowData); + + return 1; +} + +void CEnvironment::RemoveRain(int entIndex) +{ + auto it = std::find_if(m_rains.begin(), m_rains.end(), [&](const RainData& data) { + return data.entIndex == entIndex; + }); + if (it != m_rains.end()) + m_rains.erase(it); +} + +void CEnvironment::AddRain(const RainData &rainData) +{ + auto it = std::find_if(m_rains.begin(), m_rains.end(), [&](const RainData& data) { + return data.entIndex == rainData.entIndex; + }); + if (it != m_rains.end()) + { + gEngfuncs.Con_Printf("Rain with index %d already exists!\n", rainData.entIndex); + return; + } + m_rains.push_back(rainData); +} + +void CEnvironment::RemoveSnow(int entIndex) +{ + auto it = std::find_if(m_snows.begin(), m_snows.end(), [&](const SnowData& data) { + return data.entIndex == entIndex; + }); + if (it != m_snows.end()) + m_snows.erase(it); +} + +void CEnvironment::AddSnow(const SnowData &snowData) +{ + auto it = std::find_if(m_snows.begin(), m_snows.end(), [&](const SnowData& data) { + return data.entIndex == snowData.entIndex; + }); + if (it != m_snows.end()) + { + gEngfuncs.Con_Printf("Snow with index %d already exists!\n", snowData.entIndex); + return; + } + m_snows.push_back(snowData); +} + +model_t* CEnvironment::LoadSprite(const char* spriteName) +{ + return const_cast(gEngfuncs.GetSpritePointer(gEngfuncs.pfnSPR_Load(spriteName))); +} diff --git a/cl_dll/environment.h b/cl_dll/environment.h new file mode 100644 index 0000000000..87494dcf7b --- /dev/null +++ b/cl_dll/environment.h @@ -0,0 +1,165 @@ +#pragma once +#ifndef ENVIRONMENT_H +#define ENVIRONMENT_H + +#include "wrect.h" +#include "cl_dll.h" +#include "com_model.h" + +#include + +class CPartRainDrop; +class CPartWind; + +struct ParticleParams +{ + float minSize; + float maxSize; + Vector color; + byte brightness; + byte renderMode; + short lightFlag; + + float GetSize() const; +}; + +struct WeatherData +{ + WeatherData(); + + int entIndex; + int flags; + + short intensity; + short distance; + + short minHeight; + short maxHeight; + + Vector location; + + Vector absmin; + Vector absmax; + + float updateTime; + + bool IsLocalizedPoint() const; + bool IsVolume() const; + bool AllowIndoors() const; + bool DistanceIsRadius() const; + bool NeedsPVSCheck() const; + float RandomHeight() const; + + Vector GetWeatherOrigin(const Vector& globalWeatherOrigin) const; + void GetBoundingBox(const Vector& weatherOrigin, Vector& mins, Vector& maxs) const; + Vector GetRandomOrigin(const Vector& weatherOrigin) const; +}; + +struct RainData : public WeatherData +{ + RainData(); + + ParticleParams raindropParticleParams; + float raindropStretchY; + float raindropMinSpeed; + float raindropMaxSpeed; + float raindropLife; + + float GetRaindropFallingSpeed() const; + + ParticleParams windParticleParams; + float windpuffLife; + + ParticleParams splashParticleParams; + ParticleParams rippleParticleParams; + + bool RaindropsAffectedByWind() const; + bool WindPuffsAllowed() const; + bool SplashesAllowed() const; + bool RipplesAllowed() const; + + model_t* rainSprite; + model_t* windPuffSprite; + model_t* splashSprite; + model_t* rippleSprite; +}; + +struct SnowData : public WeatherData +{ + SnowData(); + + ParticleParams snowflakeParticleParams; + byte snowflakeInitialBrightness; + float snowflakeMinSpeed; + float snowflakeMaxSpeed; + float snowflakeLife; + + float GetSnowflakeFallingSpeed() const; + + bool SnowflakesAffectedByWind() const; + + model_t* snowSprite; +}; + +class CEnvironment +{ +public: + CEnvironment() = default; + + float GetOldTime() const { return m_flOldTime; } + + void Initialize(); + void Reset(); + void Update(); + + int MsgFunc_Rain(const char *pszName, int iSize, void *pbuf); + int MsgFunc_Snow(const char *pszName, int iSize, void *pbuf); + +private: + void RemoveRain(int entIndex); + void AddRain(const RainData& rainData); + void RemoveSnow(int entIndex); + void AddSnow(const SnowData& snowData); + + bool ShouldUpdateWind() const; + void UpdateWind(); + void UpdateRain(const RainData& rainData); + void UpdateSnow(const SnowData& snowData); + + CPartRainDrop* CreateRaindrop(const Vector& vecOrigin, const RainData& rainData); + CPartWind* CreateWindParticle(const Vector& vecOrigin, const RainData& rainData); + void CreateSnowFlake(const Vector& vecOrigin, const SnowData& snowData); + + model_t* LoadSprite(const char* spriteName); +private: + Vector m_vecWeatherOrigin; + + model_t* m_pRainSprite = nullptr; + model_t* m_pGasPuffSprite = nullptr; + model_t* m_pRainSplash = nullptr; + model_t* m_pRipple = nullptr; + model_t* m_pSnowSprite = nullptr; + + Vector m_vecWind; + + Vector m_vecDesiredWindDirection; + + float m_flDesiredWindSpeed; + float m_flNextWindChangeTime; + + float m_flOldTime; + + float m_flIdealYaw; + float m_flWeatherValue; + + std::vector m_rains; + std::vector m_snows; + +private: + CEnvironment( const CEnvironment& ) = delete; + CEnvironment& operator=( const CEnvironment& ) = delete; +}; + +extern CEnvironment g_Environment; + +#endif diff --git a/cl_dll/hl/hl_weapons.cpp b/cl_dll/hl/hl_weapons.cpp index 6928dd7d48..5097b8f85b 100644 --- a/cl_dll/hl/hl_weapons.cpp +++ b/cl_dll/hl/hl_weapons.cpp @@ -50,6 +50,8 @@ static CBasePlayerWeapon *g_pWpns[MAX_WEAPONS]; float g_flApplyVel = 0.0; int g_irunninggausspred = 0; +Vector g_vPlayerVelocity; + vec3_t previousorigin; // HLDM Weapon placeholder entities. @@ -793,6 +795,8 @@ void HUD_WeaponsPostThink( local_state_s *from, local_state_s *to, usercmd_t *cm player.m_flNextAmmoBurn = from->client.fuser2; player.m_flAmmoStartCharge = from->client.fuser3; + g_vPlayerVelocity = player.pev->velocity; + // Point to current weapon object if( from->client.m_iId ) { diff --git a/cl_dll/hud.cpp b/cl_dll/hud.cpp index 66d32b0627..e40647543f 100644 --- a/cl_dll/hud.cpp +++ b/cl_dll/hud.cpp @@ -31,6 +31,8 @@ #include "demo.h" #include "demo_api.h" +#include "environment.h" + hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll team_info_t g_TeamInfo[MAX_TEAMS + 1]; @@ -237,6 +239,8 @@ cvar_t *cl_grenadephysics = NULL; cvar_t* cl_weapon_sparks = NULL; cvar_t* cl_weapon_wallpuff = NULL; +cvar_t* cl_weather = NULL; + cvar_t* cl_muzzlelight = NULL; cvar_t* cl_muzzlelight_monsters = NULL; @@ -280,6 +284,16 @@ int __MsgFunc_SetFog(const char *pszName, int iSize, void *pbuf) return gHUD.MsgFunc_SetFog( pszName, iSize, pbuf ); } +int __MsgFunc_Rain(const char *pszName, int iSize, void *pbuf) +{ + return g_Environment.MsgFunc_Rain( pszName, iSize, pbuf ); +} + +int __MsgFunc_Snow(const char *pszName, int iSize, void *pbuf) +{ + return g_Environment.MsgFunc_Snow( pszName, iSize, pbuf ); +} + //LRC int __MsgFunc_KeyedDLight(const char *pszName, int iSize, void *pbuf) { @@ -567,6 +581,8 @@ void CHud::Init( void ) HOOK_MESSAGE( Concuss ); HOOK_MESSAGE( Items ); HOOK_MESSAGE( SetFog ); + HOOK_MESSAGE( Rain ); + HOOK_MESSAGE( Snow ); HOOK_MESSAGE( KeyedDLight ); HOOK_MESSAGE( WallPuffs ); @@ -642,6 +658,8 @@ void CHud::Init( void ) CreateBooleanCvarConditionally(cl_weapon_sparks, "cl_weapon_sparks", clientFeatures.weapon_sparks); CreateBooleanCvarConditionally(cl_weapon_wallpuff, "cl_weapon_wallpuff", clientFeatures.weapon_wallpuff); + cl_weather = CVAR_CREATE( "cl_weather", "1", FCVAR_ARCHIVE ); + CreateBooleanCvarConditionally(cl_muzzlelight, "cl_muzzlelight", clientFeatures.muzzlelight); cl_muzzlelight_monsters = CVAR_CREATE( "cl_muzzlelight_monsters", "0", FCVAR_ARCHIVE ); diff --git a/cl_dll/hud_msg.cpp b/cl_dll/hud_msg.cpp index 9edf0e7f2e..76c1d4361b 100644 --- a/cl_dll/hud_msg.cpp +++ b/cl_dll/hud_msg.cpp @@ -22,6 +22,8 @@ #include "r_efx.h" #include "arraysize.h" +#include "environment.h" + #define MAX_CLIENTS 32 extern BEAM *pBeam; @@ -84,6 +86,8 @@ void CHud::MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ) //Probably not a good place to put this. pBeam = pBeam2 = NULL; pFlare = NULL; // Vit_amiN: clear egon's beam flare + + g_Environment.Initialize(); } int CHud::MsgFunc_SetFog( const char *pszName, int iSize, void *pbuf ) diff --git a/cl_dll/particleman/CBaseParticle.cpp b/cl_dll/particleman/CBaseParticle.cpp index b1c642adcd..4cbb375e97 100644 --- a/cl_dll/particleman/CBaseParticle.cpp +++ b/cl_dll/particleman/CBaseParticle.cpp @@ -16,8 +16,6 @@ #include "hud.h" #include "cl_util.h" -#include - #include "event_api.h" #include "triangleapi.h" @@ -28,7 +26,7 @@ #include "pm_defs.h" #include "pmtrace.h" -const Vector g_vecZero = Vector(0,0,0); +extern const Vector g_vecZero; void CBaseParticle::InitializeSprite(Vector org, Vector normal, model_s* sprite, float size, float brightness) { @@ -186,15 +184,15 @@ void CBaseParticle::Draw() } else if ((m_iRenderFlags & LIGHT_COLOR) != 0) { - resultColor.x = vColor.x / (m_vColor.x * 255); - resultColor.y = vColor.y / (m_vColor.y * 255); - resultColor.z = vColor.z / (m_vColor.z * 255); + resultColor.x = (vColor.x * m_vColor.x / 255.0f); + resultColor.y = (vColor.y * m_vColor.y / 255.0f); + resultColor.z = (vColor.z * m_vColor.z / 255.0f); } else if ((m_iRenderFlags & LIGHT_INTENSITY) != 0) { - resultColor.x = intensity / (m_vColor.x * 255); - resultColor.y = intensity / (m_vColor.y * 255); - resultColor.z = intensity / (m_vColor.z * 255); + resultColor.x = (intensity * m_vColor.x / 255.0f); + resultColor.y = (intensity * m_vColor.y / 255.0f); + resultColor.z = (intensity * m_vColor.z / 255.0f); } resultColor.x = clampValue(resultColor.x, 0.f, 255.f); diff --git a/cl_dll/tri.cpp b/cl_dll/tri.cpp index d92fa29204..59a285f125 100644 --- a/cl_dll/tri.cpp +++ b/cl_dll/tri.cpp @@ -31,6 +31,7 @@ extern "C" #if USE_PARTICLEMAN #include "com_model.h" #include "particleman.h" +#include "environment.h" #endif //#define TEST_IT 1 @@ -185,6 +186,9 @@ void DLLEXPORT HUD_DrawTransparentTriangles( void ) #if USE_PARTICLEMAN if ( g_pParticleMan ) + { g_pParticleMan->Update(); + g_Environment.Update(); + } #endif } diff --git a/cl_dll/util.cpp b/cl_dll/util.cpp index d194b90f49..178989a26b 100644 --- a/cl_dll/util.cpp +++ b/cl_dll/util.cpp @@ -133,3 +133,37 @@ HSPRITE LoadSprite( const char *pszName ) return SPR_Load( sz ); } + +static float UTIL_AngleMod( float a ) +{ + a = fmod( a, 360.0f ); + if( a < 0 ) + a += 360; + return a; +} + +float UTIL_ApproachAngle( float target, float value, float speed ) +{ + target = UTIL_AngleMod( target ); + value = UTIL_AngleMod( value ); + + float delta = target - value; + + // Speed is assumed to be positive + if( speed < 0 ) + speed = -speed; + + if( delta < -180 ) + delta += 360; + else if( delta > 180 ) + delta -= 360; + + if( delta > speed ) + value += speed; + else if( delta < -speed ) + value -= speed; + else + value = target; + + return value; +} diff --git a/dlls/effects.cpp b/dlls/effects.cpp index b4db9fe733..de4e7c72ed 100644 --- a/dlls/effects.cpp +++ b/dlls/effects.cpp @@ -4628,6 +4628,646 @@ void CEnvFog::SendDataToOne(CBaseEntity *pClient, Vector col, int iFadeTime, int LINK_ENTITY_TO_CLASS( env_fog, CEnvFog ) +extern int gmsgRain; + +class CEnvRain : public CBaseEntity +{ +public: + void Spawn(); + int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void KeyValue(KeyValueData *pkvd); + void Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value); + void SendMessages(CBaseEntity* pClient); + bool IsActive() const { + if (FBitSet(pev->spawnflags, SF_RAIN_ACTIVE)) + return true; + return false; + } + void SetActive(bool active) { + if (active) + SetBits(pev->spawnflags, SF_RAIN_ACTIVE); + else + ClearBits(pev->spawnflags, SF_RAIN_ACTIVE); + } + void SendRain(CBaseEntity* pClient = NULL); + void SendClearRain(); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + short m_intensity; + short m_distance; + + short m_minHeight; + short m_maxHeight; + + short m_raindropWidth; + short m_raindropHeight; + + byte m_raindropLightMode; + short m_raindropMinSpeed; + short m_raindropMaxSpeed; + float m_raindropLife; + + short m_windPuffMinSize; + short m_windPuffMaxSize; + short m_windPuffRenderMode; + short m_windPuffBrightness; + Vector m_windPuffColor; + byte m_windPuffLightMode; + float m_windPuffLife; + + short m_splashMinSize; + short m_splashMaxSize; + short m_splashRenderMode; + short m_splashBrightness; + Vector m_splashColor; + byte m_splashLightMode; + + short m_rippleSize; + short m_rippleRenderMode; + short m_rippleBrightness; + Vector m_rippleColor; + byte m_rippleLightMode; + + string_t m_raindropSprite; + string_t m_windpuffSprite; + string_t m_splashSprite; + string_t m_rippleSprite; +}; + +LINK_ENTITY_TO_CLASS( env_rain, CEnvRain ) + +TYPEDESCRIPTION CEnvRain::m_SaveData[] = +{ + DEFINE_FIELD( CEnvRain, m_intensity, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_distance, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_minHeight, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_maxHeight, FIELD_SHORT ), + + DEFINE_FIELD( CEnvRain, m_raindropWidth, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_raindropHeight, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_raindropLightMode, FIELD_CHARACTER ), + DEFINE_FIELD( CEnvRain, m_raindropMinSpeed, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_raindropMaxSpeed, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_raindropLife, FIELD_FLOAT ), + + DEFINE_FIELD( CEnvRain, m_windPuffMinSize, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_windPuffMaxSize, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_windPuffRenderMode, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_windPuffBrightness, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_windPuffColor, FIELD_VECTOR ), + DEFINE_FIELD( CEnvRain, m_windPuffLightMode, FIELD_CHARACTER ), + DEFINE_FIELD( CEnvRain, m_windPuffLife, FIELD_FLOAT ), + + DEFINE_FIELD( CEnvRain, m_splashMinSize, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_splashMaxSize, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_splashRenderMode, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_splashBrightness, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_splashColor, FIELD_VECTOR ), + DEFINE_FIELD( CEnvRain, m_splashLightMode, FIELD_CHARACTER ), + + DEFINE_FIELD( CEnvRain, m_rippleSize, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_rippleRenderMode, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_rippleBrightness, FIELD_SHORT ), + DEFINE_FIELD( CEnvRain, m_rippleColor, FIELD_VECTOR ), + DEFINE_FIELD( CEnvRain, m_rippleLightMode, FIELD_CHARACTER ), + + DEFINE_FIELD( CEnvRain, m_raindropSprite, FIELD_STRING ), + DEFINE_FIELD( CEnvRain, m_windpuffSprite, FIELD_STRING ), + DEFINE_FIELD( CEnvRain, m_splashSprite, FIELD_STRING ), + DEFINE_FIELD( CEnvRain, m_rippleSprite, FIELD_STRING ), +}; +IMPLEMENT_SAVERESTORE( CEnvRain, CBaseEntity ) + +void CEnvRain::Spawn() +{ + pev->effects |= EF_NODRAW; + if (FStringNull(pev->targetname)) + pev->spawnflags |= SF_RAIN_ACTIVE; +} + +void CEnvRain::KeyValue(KeyValueData *pkvd) +{ + if (FStrEq(pkvd->szKeyName, "intensity")) + { + m_intensity = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "distance")) + { + m_distance = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "min_height")) + { + m_minHeight = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "max_height")) + { + m_maxHeight = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "raindrop_width")) + { + m_raindropWidth = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "raindrop_height")) + { + m_raindropHeight = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "raindrop_light_mode")) + { + m_raindropLightMode = (byte)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "raindrop_min_speed")) + { + m_raindropMinSpeed = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "raindrop_max_speed")) + { + m_raindropMaxSpeed = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "raindrop_life")) + { + m_raindropLife = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "windpuff_min_size")) + { + m_windPuffMinSize = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "windpuff_max_size")) + { + m_windPuffMaxSize = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "windpuff_rendermode")) + { + m_windPuffRenderMode = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "windpuff_renderamt")) + { + m_windPuffBrightness = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "windpuff_rendercolor")) + { + UTIL_StringToVector(m_windPuffColor, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "windpuff_light_mode")) + { + m_windPuffLightMode = (byte)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "windpuff_life")) + { + m_windPuffLife = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "splash_min_size")) + { + m_splashMinSize = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "splash_max_size")) + { + m_splashMaxSize = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "splash_rendermode")) + { + m_splashRenderMode = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "splash_renderamt")) + { + m_splashBrightness = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "splash_rendercolor")) + { + UTIL_StringToVector(m_splashColor, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "splash_light_mode")) + { + m_splashLightMode = (byte)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "ripple_size")) + { + m_rippleSize = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "ripple_rendermode")) + { + m_rippleRenderMode = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "ripple_renderamt")) + { + m_rippleBrightness = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "ripple_rendercolor")) + { + UTIL_StringToVector(m_rippleColor, pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "ripple_light_mode")) + { + m_rippleLightMode = (byte)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "raindrop_sprite")) + { + m_raindropSprite = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "windpuff_sprite")) + { + m_windpuffSprite = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "splash_sprite")) + { + m_splashSprite = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "ripple_sprite")) + { + m_rippleSprite = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue(pkvd); +} + +void CEnvRain::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value) +{ + bool active = IsActive(); + if (ShouldToggle(useType, active)) + { + if (active) + { + SetActive(false); + SendClearRain(); + } + else + { + SetActive(true); + SendRain(); + } + } +} + +void CEnvRain::SendMessages(CBaseEntity *pClient) +{ + if (!IsActive()) + return; + SendRain(pClient); +} + +void CEnvRain::SendRain(CBaseEntity *pClient) +{ + const int msgType = pClient ? MSG_ONE : MSG_ALL; + edict_t* pClientEdict = pClient ? pClient->edict() : NULL; + + int flags = pev->spawnflags; + + MESSAGE_BEGIN(msgType, gmsgRain, NULL, pClientEdict); + WRITE_LONG(entindex()); + WRITE_SHORT(flags); + WRITE_SHORT(m_intensity); + + if (FBitSet(pev->spawnflags, RAIN_BRUSH_ENTITY)) + { + WRITE_COORD(pev->absmin.x); + WRITE_COORD(pev->absmin.y); + WRITE_COORD(pev->absmin.z); + WRITE_COORD(pev->absmax.x); + WRITE_COORD(pev->absmax.y); + WRITE_COORD(pev->absmax.z); + } + else + { + WRITE_SHORT(m_distance); + WRITE_SHORT(m_minHeight); + WRITE_SHORT(m_maxHeight); + + if (FBitSet(pev->spawnflags, SF_RAIN_LOCALIZED)) + { + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z); + } + } + + WRITE_SHORT(m_raindropWidth); + WRITE_SHORT(m_raindropHeight); + WRITE_BYTE(pev->rendermode); + WRITE_BYTE(pev->renderamt); + WRITE_BYTE((int)pev->rendercolor.x); + WRITE_BYTE((int)pev->rendercolor.y); + WRITE_BYTE((int)pev->rendercolor.z); + WRITE_BYTE(m_raindropLightMode); + WRITE_SHORT(m_raindropMinSpeed); + WRITE_SHORT(m_raindropMaxSpeed); + WRITE_SHORT((short)(m_raindropLife * 100)); + + WRITE_SHORT(m_windPuffMinSize); + WRITE_SHORT(m_windPuffMaxSize); + WRITE_BYTE(m_windPuffRenderMode); + WRITE_BYTE(m_windPuffBrightness); + WRITE_BYTE((int)m_windPuffColor.x); + WRITE_BYTE((int)m_windPuffColor.y); + WRITE_BYTE((int)m_windPuffColor.z); + WRITE_BYTE(m_windPuffLightMode); + WRITE_SHORT((short)(m_windPuffLife * 100)); + + WRITE_SHORT(m_splashMinSize); + WRITE_SHORT(m_splashMaxSize); + WRITE_BYTE(m_splashRenderMode); + WRITE_BYTE(m_splashBrightness); + WRITE_BYTE((int)m_splashColor.x); + WRITE_BYTE((int)m_splashColor.y); + WRITE_BYTE((int)m_splashColor.z); + WRITE_BYTE(m_splashLightMode); + + WRITE_SHORT(m_rippleSize); + WRITE_BYTE(m_rippleRenderMode); + WRITE_BYTE(m_rippleBrightness); + WRITE_BYTE((int)m_rippleColor.x); + WRITE_BYTE((int)m_rippleColor.y); + WRITE_BYTE((int)m_rippleColor.z); + WRITE_BYTE(m_rippleLightMode); + + WRITE_STRING(m_raindropSprite ? STRING(m_raindropSprite) : ""); + WRITE_STRING(m_windpuffSprite ? STRING(m_windpuffSprite) : ""); + WRITE_STRING(m_splashSprite ? STRING(m_splashSprite) : ""); + WRITE_STRING(m_rippleSprite ? STRING(m_rippleSprite) : ""); + MESSAGE_END(); +} + +void CEnvRain::SendClearRain() +{ + MESSAGE_BEGIN(MSG_ALL, gmsgRain); + WRITE_LONG(entindex()); + WRITE_SHORT(0); + MESSAGE_END(); +} + +class CFuncRainVolume : public CEnvRain +{ +public: + void Spawn() { + CEnvRain::Spawn(); + SET_MODEL( ENT( pev ), STRING( pev->model ) ); + pev->spawnflags |= RAIN_BRUSH_ENTITY; + } +}; + +LINK_ENTITY_TO_CLASS( func_rainvolume, CFuncRainVolume ) + +extern int gmsgSnow; + +class CEnvSnow : public CBaseEntity +{ +public: + void Spawn(); + int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void KeyValue(KeyValueData *pkvd); + void Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value); + void SendMessages(CBaseEntity* pClient); + bool IsActive() const { + if (FBitSet(pev->spawnflags, SF_SNOW_ACTIVE)) + return true; + return false; + } + void SetActive(bool active) { + if (active) + SetBits(pev->spawnflags, SF_SNOW_ACTIVE); + else + ClearBits(pev->spawnflags, SF_SNOW_ACTIVE); + } + void SendSnow(CBaseEntity* pClient = NULL); + void SendClearSnow(); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + short m_intensity; + short m_distance; + + short m_minHeight; + short m_maxHeight; + + short m_snowflakeMinSize; + short m_snowflakeMaxSize; + byte m_snowflakeLightMode; + short m_snowflakeMinSpeed; + short m_snowflakeMaxSpeed; + float m_snowflakeLife; + + string_t m_snowflakeSprite; +}; + +LINK_ENTITY_TO_CLASS( env_snow, CEnvSnow ) + +TYPEDESCRIPTION CEnvSnow::m_SaveData[] = +{ + DEFINE_FIELD( CEnvSnow, m_intensity, FIELD_SHORT ), + DEFINE_FIELD( CEnvSnow, m_distance, FIELD_SHORT ), + DEFINE_FIELD( CEnvSnow, m_minHeight, FIELD_SHORT ), + DEFINE_FIELD( CEnvSnow, m_maxHeight, FIELD_SHORT ), + + DEFINE_FIELD( CEnvSnow, m_snowflakeMinSize, FIELD_SHORT ), + DEFINE_FIELD( CEnvSnow, m_snowflakeMaxSize, FIELD_SHORT ), + DEFINE_FIELD( CEnvSnow, m_snowflakeLightMode, FIELD_CHARACTER ), + DEFINE_FIELD( CEnvSnow, m_snowflakeMinSpeed, FIELD_SHORT ), + DEFINE_FIELD( CEnvSnow, m_snowflakeMaxSpeed, FIELD_SHORT ), + DEFINE_FIELD( CEnvSnow, m_snowflakeLife, FIELD_FLOAT ), +}; +IMPLEMENT_SAVERESTORE( CEnvSnow, CBaseEntity ) + +void CEnvSnow::Spawn() +{ + pev->effects |= EF_NODRAW; + if (FStringNull(pev->targetname)) + pev->spawnflags |= SF_SNOW_ACTIVE; +} + +void CEnvSnow::KeyValue(KeyValueData *pkvd) +{ + if (FStrEq(pkvd->szKeyName, "intensity")) + { + m_intensity = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "distance")) + { + m_distance = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "min_height")) + { + m_minHeight = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "max_height")) + { + m_maxHeight = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "snowflake_min_size")) + { + m_snowflakeMinSize = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "snowflake_max_size")) + { + m_snowflakeMaxSize = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "snowflake_light_mode")) + { + m_snowflakeLightMode = (byte)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "snowflake_min_speed")) + { + m_snowflakeMinSpeed = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "snowflake_max_speed")) + { + m_snowflakeMaxSpeed = (short)atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "snowflake_life")) + { + m_snowflakeLife = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "raindrop_sprite")) + { + m_snowflakeSprite = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue(pkvd); +} + +void CEnvSnow::Use(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value) +{ + bool active = IsActive(); + if (ShouldToggle(useType, active)) + { + if (active) + { + SetActive(false); + SendClearSnow(); + } + else + { + SetActive(true); + SendSnow(); + } + } +} + +void CEnvSnow::SendMessages(CBaseEntity *pClient) +{ + if (!IsActive()) + return; + SendSnow(pClient); +} + +void CEnvSnow::SendSnow(CBaseEntity *pClient) +{ + const int msgType = pClient ? MSG_ONE : MSG_ALL; + edict_t* pClientEdict = pClient ? pClient->edict() : NULL; + + int flags = pev->spawnflags; + + MESSAGE_BEGIN(msgType, gmsgSnow, NULL, pClientEdict); + WRITE_LONG(entindex()); + WRITE_SHORT(flags); + WRITE_SHORT(m_intensity); + + if (FBitSet(pev->spawnflags, SNOW_BRUSH_ENTITY)) + { + WRITE_COORD(pev->absmin.x); + WRITE_COORD(pev->absmin.y); + WRITE_COORD(pev->absmin.z); + WRITE_COORD(pev->absmax.x); + WRITE_COORD(pev->absmax.y); + WRITE_COORD(pev->absmax.z); + } + else + { + WRITE_SHORT(m_distance); + WRITE_SHORT(m_minHeight); + WRITE_SHORT(m_maxHeight); + + if (FBitSet(pev->spawnflags, SF_SNOW_LOCALIZED)) + { + WRITE_COORD(pev->origin.x); + WRITE_COORD(pev->origin.y); + WRITE_COORD(pev->origin.z); + } + } + + WRITE_SHORT(m_snowflakeMinSize); + WRITE_SHORT(m_snowflakeMaxSize); + WRITE_BYTE(pev->rendermode); + WRITE_BYTE(pev->renderamt); + WRITE_BYTE((int)pev->rendercolor.x); + WRITE_BYTE((int)pev->rendercolor.y); + WRITE_BYTE((int)pev->rendercolor.z); + WRITE_BYTE(m_snowflakeLightMode); + WRITE_SHORT(m_snowflakeMinSpeed); + WRITE_SHORT(m_snowflakeMaxSpeed); + WRITE_SHORT((short)(m_snowflakeLife * 100)); + + WRITE_STRING(m_snowflakeSprite ? STRING(m_snowflakeSprite) : ""); + MESSAGE_END(); +} + +void CEnvSnow::SendClearSnow() +{ + MESSAGE_BEGIN(MSG_ALL, gmsgSnow); + WRITE_LONG(entindex()); + WRITE_SHORT(0); + MESSAGE_END(); +} + +class CFuncSnowVolume : public CEnvSnow +{ +public: + void Spawn() { + CEnvSnow::Spawn(); + SET_MODEL( ENT( pev ), STRING( pev->model ) ); + pev->spawnflags |= SNOW_BRUSH_ENTITY; + } +}; + +LINK_ENTITY_TO_CLASS( func_snowvolume, CFuncSnowVolume ) + #define SF_BEAMTRAIL_OFF 1 class CEnvBeamTrail : public CPointEntity { diff --git a/dlls/player.cpp b/dlls/player.cpp index 2438f82c93..6391313ddd 100644 --- a/dlls/player.cpp +++ b/dlls/player.cpp @@ -236,6 +236,9 @@ int gmsgCaption = 0; int gmsgInventory = 0; +int gmsgRain = 0; +int gmsgSnow = 0; + static CFollowingMonster* CanRecruit(CBaseEntity* pFriend, CBasePlayer* player) { if (!pFriend->IsFullyAlive()) @@ -328,6 +331,9 @@ void LinkUserMessages( void ) gmsgCaption = REG_USER_MSG("Caption", -1); gmsgInventory = REG_USER_MSG("Inventory", -1); + + gmsgRain = REG_USER_MSG("Rain", -1); + gmsgSnow = REG_USER_MSG("Snow", -1); } LINK_ENTITY_TO_CLASS( player, CBasePlayer ) diff --git a/dlls/util.h b/dlls/util.h index 1d767fe3b3..c1e2ed62d5 100644 --- a/dlls/util.h +++ b/dlls/util.h @@ -17,6 +17,7 @@ #define UTIL_H #include "extdll.h" +#include "hull_types.h" // // Misc utility code // @@ -299,14 +300,6 @@ typedef enum extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, edict_t *pentIgnore, TraceResult *ptr); extern void UTIL_TraceLine (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, IGNORE_GLASS ignoreGlass, edict_t *pentIgnore, TraceResult *ptr); -enum -{ - point_hull = 0, - human_hull = 1, - large_hull = 2, - head_hull = 3 -}; - extern void UTIL_TraceHull (const Vector &vecStart, const Vector &vecEnd, IGNORE_MONSTERS igmon, int hullNumber, edict_t *pentIgnore, TraceResult *ptr); extern TraceResult UTIL_GetGlobalTrace (void); extern void UTIL_TraceModel (const Vector &vecStart, const Vector &vecEnd, int hullNumber, edict_t *pentModel, TraceResult *ptr); diff --git a/fgd/halflife.fgd b/fgd/halflife.fgd index 978b434c9a..ad30ec386a 100644 --- a/fgd/halflife.fgd +++ b/fgd/halflife.fgd @@ -1902,6 +1902,198 @@ ] ] +@BaseClass = RainFlags +[ + spawnflags(flags) = + [ + 1: "Start Active" : 0 + 2: "Allow indoor" : 0 : "Allow rain even under the roof" + 4: "Raindrops not affected by wind": 0 + 32: "No wind particles" : 0 : "Disable wind puff particles" + 64: "No rain splashes" : 0 : "Disable rain splashes on the ground" + 128: "No ripples" : 0 : "Disable ripples on the water" + ] +] + +@BaseClass = RainBaseParams +[ + intensity(integer) : "Rain intensity" : : "The relative rain intensity the number of raindrops depend on. Default is 150. The result also depends on the user's cl_weather value." +] + +@BaseClass = EnvRainParams +[ + distance(integer) : "Rain max distance" : : "Maximum distance the raindrops appear in. Default is 400" + min_height(integer) : "Precipitation min height" : : "Minimum height where the raindrops spawn. Default is 100" + max_height(integer) : "Precipitation max height" : : "Maximum height where the raindrops spawn. Default is 300" +] + +@BaseClass = RainParticlesParams +[ + raindrop_sprite(sprite) : "Raindrop custom sprite" + raindrop_width(integer) : "Raindrop width (units)" + raindrop_height(integer) : "Raindrop tallness (units)" + rendermode(choices) : "Raindrop render mode" = + [ + 0: "Default" + 1: "Color" + 2: "Texture" + 3: "Glow" + 4: "Solid" + 5: "Additive" + ] + renderamt(integer) : "Raindrop brightness [1-255]" + rendercolor(color255) : "Raindrop color" + + raindrop_light_mode(choices) : "Raindrop light mode" = + [ + 0: "Default" + 1: "Light doesn't affect color" + 2: "Light color affects color" + 3: "Light intensity affects color" + ] + + raindrop_min_speed(integer) : "Raindrop min falling speed" + raindrop_max_speed(integer) : "Raindrop max falling speed" + raindrop_life(string) : "Raindrop max life (seconds)" + + windpuff_sprite(sprite) : "Windpuff custom sprite" + windpuff_min_size(integer) : "Wind puff min size (units)" + windpuff_max_size(integer) : "Wind puff max size (units)" + windpuff_rendermode(choices) : "Wind puff render mode" = + [ + 0: "Default" + 1: "Color" + 2: "Texture" + 3: "Glow" + 4: "Solid" + 5: "Additive" + ] + windpuff_renderamt(integer) : "Wind puff brightness [1-255]" + windpuff_rendercolor(color255) : "Wind puff color" + windpuff_light_mode(choices) : "Wind puff light mode" = + [ + 0: "Default" + 1: "Light doesn't affect color" + 2: "Light color affects color" + 3: "Light intensity affects color" + ] + windpuff_life(string) : "Wind puff life (seconds)" + + splash_sprite(sprite) : "Rainsplash custom sprite" + splash_min_size(integer) : "Rainsplash min size (units)" + splash_max_size(integer) : "Rainsplash max size (units)" + splash_rendermode(choices) : "Rainsplash render mode" = + [ + 0: "Default" + 1: "Color" + 2: "Texture" + 3: "Glow" + 4: "Solid" + 5: "Additive" + ] + splash_renderamt(integer) : "Rainsplash brightness [1-255]" + splash_rendercolor(color255) : "Rainsplash color" + splash_light_mode(choices) : "Rainsplash light mode" = + [ + 0: "Default" + 1: "Light doesn't affect color" + 2: "Light color affects color" + 3: "Light intensity affects color" + ] + + ripple_sprite(sprite) : "Ripple custom sprite" + ripple_size(integer) : "Ripple size (units)" + ripple_rendermode(choices) : "Ripple render mode" = + [ + 0: "Default" + 1: "Color" + 2: "Texture" + 3: "Glow" + 4: "Solid" + 5: "Additive" + ] + ripple_renderamt(integer) : "Ripple brightness [1-255]" + ripple_rendercolor(color255) : "Ripple color" + ripple_light_mode(choices) : "Ripple light mode" = + [ + 0: "Default" + 1: "Light doesn't affect color" + 2: "Light color affects color" + 3: "Light intensity affects color" + ] +] + +@PointClass base(Targetname, RainFlags, RainBaseParams, EnvRainParams, RainParticlesParams) = env_rain : "Similar to rain in Counter Strike, but with configurable options" +[ + spawnflags(flags) = + [ + 8: "Localized" : 0 : "Spawn raindrops above the env_rain entity. By default raindrops spawn above players (i.e. globally)" + 16: "Distance is radius" : 0 : "By default 'distance' means half of the square area side the raindrops will spawn in. This flag makes treat distance as circle radius." + ] +] + +@SolidClass base(Targetname, RainFlags, RainBaseParams, RainParticlesParams) = func_rainvolume : "Brush version of env_rain, localized in the designated area. The rain height and distance depend on the brush size." +[] + +@BaseClass = SnowFlags +[ + spawnflags(flags) = + [ + 1: "Start Active" : 0 + 2: "Allow indoor" : 0 : "Allow snow even under the roof" + 4: "Snowflakes not affected by wind": 0 + 8: "Localized" : 0 : "Spawn snowflakes above the env_snow entity. By default snowflakes spawn above players (i.e. globally)" + 16: "Distance is radius" : 0 : "By default 'distance' means half of the square area side the snowflakes will spawn in. This flag makes treat distance as circle radius." + ] +] + +@BaseClass = SnowBaseParams +[ + intensity(integer) : "Snow intensity" : : "The relative snow intensity the number of snowflakes depend on. Default is 150. The result also depends on the user's cl_weather value." +] + +@BaseClass = EnvSnowParams +[ + distance(integer) : "Snow max distance" : : "Maximum distance the snowflakes appear in. Default is 300" + min_height(integer) : "Precipitation min height" : : "Minimum height where the snowflakes spawn. Default is 100" + max_height(integer) : "Precipitation max height" : : "Maximum height where the snowflakes spawn. Default is 300" +] + +@BaseClass = SnowParticlesParams +[ + snowflake_sprite(sprite) : "Snowflake custom sprite" + snowflake_min_size(integer) : "Snowflake min size (units)" + snowflake_max_size(integer) : "Snowflake max size (units)" + rendermode(choices) : "Snowflake render mode" = + [ + 0: "Default" + 1: "Color" + 2: "Texture" + 3: "Glow" + 4: "Solid" + 5: "Additive" + ] + renderamt(integer) : "Snowflake brightness [1-255]" + rendercolor(color255) : "Snowflake color" + + snowflake_light_mode(choices) : "Snowflake light mode" = + [ + 0: "Default" + 1: "Light doesn't affect color" + 2: "Light color affects color" + 3: "Light intensity affects color" + ] + + snowflake_min_speed(integer) : "Snowflake min falling speed" + snowflake_max_speed(integer) : "Snowflake max falling speed" + snowflake_life(string) : "Snowflake max life (seconds)" +] + +@PointClass base(Targetname, SnowFlags, SnowBaseParams, EnvSnowParams, SnowParticlesParams) = env_snow : "Similar to snow in Counter Strike, but with configurable options" [] + +@SolidClass base(Targetname, SnowFlags, SnowBaseParams, SnowParticlesParams) = func_snowvolume : "Brush version of env_rain, localized in the designated area. The rain height and distance depend on the brush size." +[] + @PointClass base(Targetname, RenderFields) size(-16 -16 -16, 16 16 16) color(100 100 0) = env_render : "Render Controls" [ target(target_destination) : "Target to affect [LE]" diff --git a/game_shared/fx_flags.h b/game_shared/fx_flags.h index 700d27b7dd..cf083f2786 100644 --- a/game_shared/fx_flags.h +++ b/game_shared/fx_flags.h @@ -15,4 +15,36 @@ #define SMOKER_FLAG_FADE_SPRITE (1 << 1) #define SMOKER_FLAG_SCALE_VALUE_IS_NORMAL (1 << 2) +#define SF_WEATHER_ACTIVE (1 << 0) +#define SF_WEATHER_ALLOW_INDOOR (1 << 1) +#define SF_WEATHER_NOT_AFFECTED_BY_WIND (1 << 2) +#define SF_WEATHER_LOCALIZED (1 << 3) +#define SF_WEATHER_DISTANCE_IS_RADIUS (1 << 4) +#define WEATHER_BRUSH_ENTITY (1 << 12) + +#define SF_RAIN_ACTIVE SF_WEATHER_ACTIVE +#define SF_RAIN_ALLOW_INDOOR SF_WEATHER_ALLOW_INDOOR +#define SF_RAIN_NOT_AFFECTED_BY_WIND SF_WEATHER_NOT_AFFECTED_BY_WIND +#define SF_RAIN_LOCALIZED SF_WEATHER_LOCALIZED +#define SF_RAIN_DISTANCE_IS_RADIUS SF_WEATHER_DISTANCE_IS_RADIUS +#define SF_RAIN_NO_WIND (1 << 5) +#define SF_RAIN_NO_SPLASHES (1 << 6) +#define SF_RAIN_NO_RIPPLES (1 << 7) +#define RAIN_BRUSH_ENTITY WEATHER_BRUSH_ENTITY + +#define SF_SNOW_ACTIVE SF_WEATHER_ACTIVE +#define SF_SNOW_ALLOW_INDOOR SF_WEATHER_ALLOW_INDOOR +#define SF_SNOW_NOT_AFFECTED_BY_WIND SF_WEATHER_NOT_AFFECTED_BY_WIND +#define SF_SNOW_LOCALIZED SF_WEATHER_LOCALIZED +#define SF_SNOW_DISTANCE_IS_RADIUS SF_WEATHER_DISTANCE_IS_RADIUS +#define SNOW_BRUSH_ENTITY WEATHER_BRUSH_ENTITY + +enum +{ + PARTICLE_LIGHT_DEFAULT = 0, + PARTICLE_LIGHT_NONE, + PARTICLE_LIGHT_COLOR, + PARTICLE_LIGHT_INTENSITY, +}; + #endif diff --git a/game_shared/hull_types.h b/game_shared/hull_types.h new file mode 100644 index 0000000000..b0edcee604 --- /dev/null +++ b/game_shared/hull_types.h @@ -0,0 +1,13 @@ +#pragma once +#ifndef HULL_TYPES_H +#define HULL_TYPES_H + +enum +{ + point_hull = 0, + human_hull = 1, + large_hull = 2, + head_hull = 3 +}; + +#endif