class DustEffectManager
{
protected static ref DustEffectManager m_Instance = NULL;
static DustEffectManager GetDustEffectManager()
{
if(!m_Instance)
m_Instance = new DustEffectManager();
return m_Instance;
}
PlayerBase m_Player;
protected bool m_EnableCloudEffect;
protected int m_CloudEffectId;
protected float m_CloudEffectBaseDistance;
protected float m_CloudEffectCooldown;
protected float m_CloudEffectCooldownMin;
protected float m_CloudEffectCooldownMax;
protected float m_CloudEffectThreshold;
protected float m_CloudEffectDurationMin;
protected float m_CloudEffectDurationMax;
protected float m_CloudEffectDuration;
protected bool m_CloudEffectReadFromSurface;
protected bool m_EnableGroundEffect;
protected float m_GroundEffectRadius;
protected float m_GroundEffectThreshold;
protected float m_GroundEffectIntensity;
protected float m_TimeFromLastGroundEffect;
protected float m_GroundEffectWindWeight;
protected bool m_GroundEffectReadFromSurface;
protected bool m_GroundEffectTestEndPoint;
protected float m_GroundEffectLifetimeMin;
protected float m_GroundEffectLifetimeMax;
protected float m_GroundEffectSizeMin;
protected float m_GroundEffectSizeMax;
protected bool m_EnableCeilingDustEffect = true; //'on' by default
protected ref static map<int, string> m_ParticleIDEffectPairing; //<particleID, effect type>
protected const float CLOUD_EFFECT_ORIGIN_HEIGHT = 1.5;
protected const float CLOUD_EFFECT_SPEED_ADJUSTMENT = 3.5;
protected const float GROUND_EFFECT_SPEED_COEFICIENT = 20;
protected const float GROUND_EFFECT_SPEED_ADJUSTMENT = 3;
protected const float GROUND_EFFECT_END_TEST_DISTANCE = 3;
protected const float GROUND_EFFECT_WIND_BIAS_SPREAD_SCALE = 0.6;
protected const float GROUND_EFFECT_WIND_BIAS_VARIATION_RANGE = 0.4;
float m_Intensity;
float m_TimeFromLastEffect;
float m_ActualGroundEffectPeriod;
const float BASE_FREQUENCY = 500;
//Cloud
const float CLOUD_BASE_FREQUENCY = 10;
const float CLOUD_BASE_DURATION = 20;
protected float m_TimeEffectCloud;
protected float m_ActualDurationCloud;
protected bool m_ActiveCloudEffect;
void DustEffectManager()
{
}
void Init(PlayerBase player)
{
m_Player = player;
DustEffectsSettings dustEffectsSettings = GetGame().GetMission().GetWorldData().GetDustEffectsSettings();
if (dustEffectsSettings.m_GroundEffectEnable)
{
InitGroundEffect(dustEffectsSettings);
SetGroundEffectEnable(true);
}
else
{
SetGroundEffectEnable(false);
}
if (dustEffectsSettings.m_CloudEffectEnable)
{
InitCloudEffect(dustEffectsSettings);
SetCloudEffectEnable(true);
}
else
{
SetCloudEffectEnable(false);
}
//InitGroundEffect(50, 5, 0, 0);
//InitCloudEffect(20, 1, 1, 0);
InitCeilingEffect();
}
void InitGroundEffect(DustEffectsSettings dustEffectsSettings)
{
m_EnableGroundEffect = true;
SetGroundEffectThreshold(dustEffectsSettings.m_GroundEffectMinWindStrength);
SetGroundEffectIntensity(dustEffectsSettings.m_GroundEffectFrequency);
SetGroundEffectRadius(dustEffectsSettings.m_GroundEffectDistance);
SetGroundEffectWindWeight(dustEffectsSettings.m_GroundEffectWindWeight);
SetGroundEffectEndPointTest(dustEffectsSettings.m_GroundEffectEndPoindTest);
m_GroundEffectLifetimeMin = dustEffectsSettings.m_GroundEffectLifetimeMin;
m_GroundEffectLifetimeMax = dustEffectsSettings.m_GroundEffectLifetimeMax;
m_GroundEffectSizeMin = dustEffectsSettings.m_GroundEffectSizeMin;
m_GroundEffectSizeMax = dustEffectsSettings.m_GroundEffectSizeMax;
m_GroundEffectReadFromSurface = true;
m_TimeFromLastGroundEffect = 0.0;
}
void InitCloudEffect(DustEffectsSettings dustEffectsSettings)
{
m_EnableCloudEffect = true;
SetCloudEffectDistance(dustEffectsSettings.m_CloudEffectDistance);
SetCloudEffectCooldownMin(dustEffectsSettings.m_CloudEffectCooldownMin);
SetCloudEffectCooldownMax(dustEffectsSettings.m_CloudEffectCooldownMax);
SetCloudEffectCooldown(dustEffectsSettings.m_CloudEffectCooldownMin);
SetCloudEffectDurationMin(dustEffectsSettings.m_CloudEffectDurationMin);
SetCloudEffectDurationMax(dustEffectsSettings.m_CloudEffectDurationMax);
SetCloudEffectDuration(dustEffectsSettings.m_CloudEffectDurationMin);
SetCloudEffectThreshold(dustEffectsSettings.m_CloudEffectMinWindStrength);
SetCloudEffectReadFromSurfaceEnable(dustEffectsSettings.m_CloudEffectFromSurface);
m_ActiveCloudEffect = false;
m_TimeEffectCloud = 0.0;
m_CloudEffectReadFromSurface = true;
m_CloudEffectId = -1;
}
protected void InitCeilingEffect()
{
m_ParticleIDEffectPairing = new map<int, string>();
RegisterCeilingPair(ParticleList.CEILING_WOOD, "CeilingDustEffectWood");
}
protected void RegisterCeilingPair(int particleID, string effectType)
{
m_ParticleIDEffectPairing.Set(particleID, effectType);
}
void Update(float deltaT)
{
if (m_EnableGroundEffect || m_EnableCloudEffect)
{
EUndergroundPresence presence = m_Player.GetUndergroundPresence();
bool temporaryDisable = false;
if (presence != EUndergroundPresence.NONE)
{
StopCloudEffect();
}
else
{
vector wind = GetGame().GetWeather().GetWind();
float windStr = wind.Normalize();
if(m_EnableGroundEffect)
UpdateGroundEffect(deltaT, windStr);
if(m_EnableCloudEffect)
UpdateCloudEffect(deltaT, windStr);
}
}
}
void UpdateGroundEffect(float deltaT, float windStrength)
{
if(windStrength > m_GroundEffectThreshold)
{
m_TimeFromLastGroundEffect += deltaT;
float tresholdModificator = 1 + ((windStrength - 10) * m_GroundEffectWindWeight / 10);
if((m_TimeFromLastGroundEffect * tresholdModificator) > m_ActualGroundEffectPeriod)
{
CreateGroundEffect();
m_TimeFromLastGroundEffect = 0.0;
}
}
}
void UpdateCloudEffect(float deltaT, float windStrength)
{
m_TimeEffectCloud += deltaT;
if( m_ActiveCloudEffect )
{
if( m_TimeEffectCloud > m_CloudEffectDuration )
{
StopCloudEffect();
SetCloudEffectCooldown(ComputeCloudEffectCooldown());
m_TimeEffectCloud = 0;
}
else
{
UpdateActiveCloudEffect();
}
}
else
{
if( windStrength > m_CloudEffectThreshold )
{
if( m_TimeEffectCloud > m_CloudEffectCooldown )
{
SetCloudEffectDuration(ComputeCloudEffectDuration());
CreateCloudEffect();
m_TimeEffectCloud = 0;
}
}
}
}
void UpdateActiveCloudEffect()
{
if (SEffectManager.IsEffectExist(m_CloudEffectId))
{
Effect effect = SEffectManager.GetEffectByID(m_CloudEffectId);
vector dustPosition;
vector dustRotation;
CalculateCloudEffectPosRot(dustPosition, dustRotation);
effect.SetLocalPosition(dustPosition);
effect.SetAttachedLocalOri(dustRotation);
}
}
protected void CreateGroundEffect()
{
bool canCreateEffect = true;
int particleID = ParticleList.NONE;
vector wind = g_Game.GetWeather().GetWind();
float windStrength = wind.Normalize();
vector spawnPoint = GetGroundEffectPotentialPosition();
SurfaceInfo surfaceInfo;
float spawnPointHeight;
surfaceInfo = g_Game.GetSurfaceInfoOn(spawnPoint[0], spawnPoint[2], spawnPointHeight);
spawnPoint[1] = spawnPointHeight;
if (m_GroundEffectTestEndPoint)
{
vector endPoint;
endPoint = spawnPoint + (wind * GROUND_EFFECT_END_TEST_DISTANCE);
SurfaceInfo endPointSurfaceInfo;
float endPointSurfaceHeight;
endPointSurfaceInfo = g_Game.GetSurfaceInfoOn(endPoint[0], endPoint[2], endPointSurfaceHeight);
if (surfaceInfo.GetSurfaceType() != endPointSurfaceInfo.GetSurfaceType())
{
canCreateEffect = false;
}
}
if (canCreateEffect)
{
particleID = surfaceInfo.GetGroundEffectParticleId();
if (particleID == ParticleList.NONE || particleID == ParticleList.INVALID)
{
canCreateEffect = false;
}
}
if (canCreateEffect)
{
spawnPoint[1] = GetGame().SurfaceY(spawnPoint[0],spawnPoint[2]);
ParticleSource source = ParticleManager.GetInstance().PlayInWorldEx(particleID, null, spawnPoint, "0 0 0", true);
float output;
GetParticleParm(source, -1, EmitorParam.LIFETIME, output);
GetParticleParm(source, -1, EmitorParam.SIZE, output);
if (m_GroundEffectLifetimeMin >= 0 && m_GroundEffectLifetimeMax >= 0)
SetParticleParm(source, -1, EmitorParam.LIFETIME, ComputeWindBiasedValue(windStrength, m_GroundEffectLifetimeMin, m_GroundEffectLifetimeMax));
if (m_GroundEffectSizeMin >= 0 && m_GroundEffectSizeMax >= 0)
SetParticleParm(source, -1, EmitorParam.SIZE, ComputeWindBiasedValue(windStrength, m_GroundEffectSizeMin, m_GroundEffectSizeMax));
vector dustOrientation = wind;
dustOrientation[0] = wind[0];
dustOrientation[1] = wind[2];
float yaw = dustOrientation.VectorToYaw();
vector surfaceNormal = GetGame().SurfaceGetNormal(spawnPoint[0],spawnPoint[2]);
float dotWithForward = vector.Dot(surfaceNormal, wind);
float pitch = Math.Atan2(-dotWithForward, surfaceNormal[1]) * Math.RAD2DEG;
dustOrientation[0] = -yaw;
dustOrientation[1] = 0;
dustOrientation[2] = -pitch;
source.SetOrientation(dustOrientation);
}
}
protected vector GetGroundEffectPotentialPosition()
{
vector PlayerPos = m_Player.GetPosition();
vector PlayerSpeed = GetPlayerSpeed();
float PlayerSpeedStr = PlayerSpeed.Normalize();
PlayerSpeed[1] = PlayerSpeed[2];
float yaw = PlayerSpeed.VectorToYaw();
float coef = 1 + (PlayerSpeedStr / GROUND_EFFECT_SPEED_COEFICIENT);
float x = Math.RandomFloat(-coef * m_GroundEffectRadius, coef * m_GroundEffectRadius) + (PlayerSpeedStr * GROUND_EFFECT_SPEED_ADJUSTMENT);
float z = Math.RandomFloat(-m_GroundEffectRadius / coef, m_GroundEffectRadius / coef);
vector point = Vector(x, 0.0, z);
point = vector.RotateAroundZeroDeg(point, vector.Up, -yaw);
return PlayerPos + point;
}
protected vector GetPlayerSpeed()
{
vector playerSpeed;
if (m_Player.IsInVehicle())
{
HumanCommandVehicle vehCommand = m_Player.GetCommand_Vehicle();
if (vehCommand)
{
Transport trans = vehCommand.GetTransport();
if (trans)
{
playerSpeed = trans.GetSpeed();
}
}
}
else
{
m_Player.PhysicsGetVelocity(playerSpeed);
}
return playerSpeed;
}
protected bool CreateCloudEffect()
{
if (!SEffectManager.IsEffectExist(m_CloudEffectId))
{
SurfaceInfo surfaceInfo = g_Game.GetSurfaceInfoUnderObject(m_Player);
EffCloudDust eff = new EffCloudDust();
if (m_CloudEffectReadFromSurface)
eff.SelectFromSurface(surfaceInfo);
else
eff.SetDefault();
vector dustPosition;
vector dustRotation;
CalculateCloudEffectPosRot(dustPosition, dustRotation);
m_CloudEffectId = SEffectManager.PlayOnObject(eff, m_Player, dustPosition, dustRotation, true);
}
m_ActiveCloudEffect = true;
return true;
}
void CalculateCloudEffectPosRot(out vector cloudPositon, out vector cloudRotation)
{
vector wind = GetGame().GetWeather().GetWind();
float windStr = wind.Normalize();
//Rotation calculation
vector rotation = wind;
rotation[1] = rotation[2];
float yaw = rotation.VectorToYaw();
rotation[0] = -yaw;
rotation[1] = 0;
rotation[2] = 0;
//End Roration calculation
//Position calculation
vector playerSpeed = GetPlayerSpeed();
float playerSpeedStr = playerSpeed.Normalize();
float CloudEffectDistance = m_CloudEffectBaseDistance;
vector pos = -(wind * m_CloudEffectBaseDistance);
pos = pos + (playerSpeed * playerSpeedStr * CLOUD_EFFECT_SPEED_ADJUSTMENT);
pos[1] + CLOUD_EFFECT_ORIGIN_HEIGHT;
//End position calculation
cloudPositon = pos;
cloudRotation = rotation;
}
void StopCloudEffect()
{
if(SEffectManager.IsEffectExist( m_CloudEffectId ))
{
SEffectManager.Stop(m_CloudEffectId);
}
m_CloudEffectId = -1;
m_ActiveCloudEffect = false;
}
//#ifdef DIAG_DEVELOPER
void SetCloudEffectEnable(bool enable)
{
#ifdef DIAG_DEVELOPER
#ifndef SERVER
DiagMenu.SetValue(DiagMenuIDs.MISC_DUST_EFFECTS_CLOUD_ENABLE, enable);
#endif
#endif
m_EnableCloudEffect = enable;
StopCloudEffect();
}
bool IsCloudEffectEnable()
{
return m_EnableCloudEffect;
}
void SetGroundEffectEnable(bool enable)
{
#ifdef DIAG_DEVELOPER
#ifndef SERVER
DiagMenu.SetValue(DiagMenuIDs.MISC_DUST_EFFECTS_GROUND_ENABLE, enable);
#endif
#endif
m_EnableGroundEffect = enable;
}
bool IsGroundEffectEnable()
{
return m_EnableGroundEffect;
}
void SetCloudEffectThreshold(float threshold)
{
#ifdef DIAG_DEVELOPER
#ifndef SERVER
DiagMenu.SetRangeValue(DiagMenuIDs.MISC_DUST_EFFECTS_CLOUD_THRESHOLD, threshold);
#endif
#endif
m_CloudEffectThreshold = threshold;
}
float GetCloudEffectThreshold()
{
return m_CloudEffectThreshold;
}
void SetCloudEffectDistance(float distance)
{
#ifdef DIAG_DEVELOPER
#ifndef SERVER
DiagMenu.SetRangeValue(DiagMenuIDs.MISC_DUST_EFFECTS_CLOUD_DISTANCE, distance);
#endif
#endif
m_CloudEffectBaseDistance = distance;
}
float GetCloudEffectDistance()
{
return m_CloudEffectBaseDistance;
}
float GetCloudEffectCooldownMin()
{
return m_CloudEffectCooldownMin;
}
void SetCloudEffectCooldownMin(float cooldown)
{
m_CloudEffectCooldownMin = cooldown;
}
void SetCloudEffectCooldownMax(float cooldown)
{
m_CloudEffectCooldownMax = cooldown;
}
void SetCloudEffectCooldown(float cooldown)
{
m_CloudEffectCooldown = cooldown;
}
float GetCloudEffectCooldown()
{
return m_CloudEffectCooldown;
}
float ComputeCloudEffectCooldown()
{
float randMulti = Math.RandomFloat01();
float cooldown = m_CloudEffectCooldownMin + (m_CloudEffectCooldownMax - m_CloudEffectCooldownMin) * randMulti;
return cooldown;
}
float GetCloudEffectDurationMin()
{
return m_CloudEffectDurationMin;
}
void SetCloudEffectDurationMin(float duration)
{
m_CloudEffectDurationMin = duration;
}
void SetCloudEffectDurationMax(float duration)
{
m_CloudEffectDurationMax = duration;
}
void SetCloudEffectDuration(float duration)
{
m_CloudEffectDuration = duration;
}
float GetCloudEffectDuration()
{
return m_CloudEffectDuration;
}
float ComputeCloudEffectDuration()
{
float randMulti = Math.RandomFloat01();
float duration = m_CloudEffectDurationMin + (m_CloudEffectDurationMax - m_CloudEffectDurationMin) * randMulti;
return duration;
}
void SetCloudEffectReadFromSurfaceEnable(bool enable)
{
#ifdef DIAG_DEVELOPER
#ifndef SERVER
DiagMenu.SetValue(DiagMenuIDs.MISC_DUST_EFFECTS_CLOUD_GETFROMSURFACE, enable);
#endif
#endif
m_CloudEffectReadFromSurface = enable;
}
bool IsCloudEffectReadFromSurfaceEnable()
{
return m_CloudEffectReadFromSurface;
}
void SetGroundEffectThreshold(float threshold)
{
#ifdef DIAG_DEVELOPER
#ifndef SERVER
DiagMenu.SetRangeValue(DiagMenuIDs.MISC_DUST_EFFECTS_GROUND_THRESHOLD, threshold);
#endif
#endif
m_GroundEffectThreshold = threshold;
}
float GetGroundEffectThreshold()
{
return m_GroundEffectThreshold;
}
void SetGroundEffectIntensity(float intensity)
{
#ifdef DIAG_DEVELOPER
#ifndef SERVER
DiagMenu.SetRangeValue(DiagMenuIDs.MISC_DUST_EFFECTS_GROUND_INTESITY, intensity);
#endif
#endif
m_GroundEffectIntensity = intensity;
int intensityLevel = m_GroundEffectIntensity / 10;
int intensityMod = Math.ModFloat( m_GroundEffectIntensity, 10);
float frequency = Math.Pow(10, - intensityLevel);
m_ActualGroundEffectPeriod = frequency * (10 - intensityMod);
}
float GetGroundEffectIntensity()
{
return m_GroundEffectIntensity;
}
void SetGroundEffectRadius(float radius)
{
#ifdef DIAG_DEVELOPER
#ifndef SERVER
DiagMenu.SetRangeValue(DiagMenuIDs.MISC_DUST_EFFECTS_GROUND_RADIUS, radius);
#endif
#endif
m_GroundEffectRadius = radius;
}
float GetGroundEffectRadius()
{
return m_GroundEffectRadius;
}
void SetGroundEffectWindWeight(float windWeight)
{
#ifdef DIAG_DEVELOPER
#ifndef SERVER
DiagMenu.SetRangeValue(DiagMenuIDs.MISC_DUST_EFFECTS_GROUND_WINDWEIGHT, windWeight);
#endif
#endif
m_GroundEffectWindWeight = windWeight / 10;
}
void SetGroundEffectEndPointTest(bool endPointtest)
{
m_GroundEffectTestEndPoint = endPointtest;
}
float GetGroundEffectWindWeight()
{
return m_GroundEffectWindWeight * 10;
}
void SetGroundEffectReadFromSurfaceEnable(bool enable)
{
#ifdef DIAG_DEVELOPER
#ifndef SERVER
DiagMenu.SetValue(DiagMenuIDs.MISC_DUST_EFFECTS_GROUND_GETFROMSURFACE, enable);
#endif
#endif
m_GroundEffectReadFromSurface = enable;
}
bool IsGroundEffectReadFromSurfaceEnable()
{
return m_GroundEffectReadFromSurface;
}
// Returns a value in [minVal, maxVal] biased toward max by wind strength.
// At low wind: result is closer to min with high spread.
// At high wind: result is closer to max with lower spread.
protected float ComputeWindBiasedValue(float windStrength, float minVal, float maxVal)
{
float windT = Math.Clamp(windStrength / GROUND_EFFECT_SPEED_COEFICIENT, 0, 1);
float spreadScale = 1 - windT * GROUND_EFFECT_WIND_BIAS_SPREAD_SCALE;
float variation = Math.RandomFloat(-GROUND_EFFECT_WIND_BIAS_VARIATION_RANGE, GROUND_EFFECT_WIND_BIAS_VARIATION_RANGE) * spreadScale;
float t = Math.Clamp(windT + variation, 0, 1);
return minVal + t * (maxVal - minVal);
}
void SetGroundEffectLifetimeRange(float minLifetime, float maxLifetime)
{
m_GroundEffectLifetimeMin = minLifetime;
m_GroundEffectLifetimeMax = maxLifetime;
}
float GetGroundEffectLifetimeMin()
{
return m_GroundEffectLifetimeMin;
}
float GetGroundEffectLifetimeMax()
{
return m_GroundEffectLifetimeMax;
}
void SetGroundEffectSizeRange(float minSize, float maxSize)
{
m_GroundEffectSizeMin = minSize;
m_GroundEffectSizeMax = maxSize;
}
float GetGroundEffectSizeMin()
{
return m_GroundEffectSizeMin;
}
float GetGroundEffectSizeMax()
{
return m_GroundEffectSizeMax;
}
//-----------------------------------------
//ceiling dust part
//! optional adjustment of frequency by particleID
static void AdjustCeilingDustEffectFrequency(string surfaceType, int particleID, inout int min, inout int max)
{
switch (particleID)
{
case ParticleList.CEILING_WOOD:
{
min = 5;
max = 10;
return;
}
}
}
//! Returns effect type paired to the particleID
static string GetCeilingEffectType(int particleID)
{
string ret = m_ParticleIDEffectPairing.Get(particleID);
if (ret == string.Empty)
{
ErrorEx("No 'CeilingDustEffectBase'-type paired to particle ID " + particleID + ", using default value. Please add registration into the 'InitCeilingEffect' method.");
ret = "CeilingDustEffecter";
}
return ret;
}
int IsCeilingDustEffectEnabled()
{
return m_EnableCeilingDustEffect;
}
void SetCeilingDustEffectEnable(bool enable)
{
m_EnableCeilingDustEffect = enable;
}
//-----------------------------------------
//#endif
}