enum EBoatSpeed { UNSET, SLOWEST, SLOW, MEDIUM, FAST } class EffectBoatWaterFront : EffectBoatWaterBase { protected const int EMITORS_FAST = 9; // emitors below this ID only play when fast enough void EffectBoatWaterFront() { m_EmitorCount = 13; m_SpeedSlow = 5; m_SpeedMedium = 20; m_SpeedFast = 40; SetParticleState( ParticleList.BOAT_WATER_FRONT ); } override protected void UpdateSpeedState(Particle ptc, float speed) { EBoatSpeed speedStatePast = m_SpeedState; if (speed > m_SpeedFast) m_SpeedState = EBoatSpeed.FAST; else if (speed > m_SpeedMedium) m_SpeedState = EBoatSpeed.MEDIUM; else if (speed > m_SpeedSlow) m_SpeedState = EBoatSpeed.SLOW; else m_SpeedState = EBoatSpeed.SLOWEST; if (m_SpeedState == speedStatePast) return; for (int i = 0; i < m_EmitorCount; i++) { if (i == 5 || i == 6) { if (m_SpeedState <= EBoatSpeed.MEDIUM) // fast drops EnableEmitor(ptc, i, false); else EnableEmitor(ptc, i, true); } else if (i == 9 || i == 10) // slow drops { if (m_SpeedState != EBoatSpeed.MEDIUM) EnableEmitor(ptc, i, false); else EnableEmitor(ptc, i, true); } else if (i < 5 || i == 7 || i == 8) // rest of front ptc { if (m_SpeedState <= EBoatSpeed.SLOW) EnableEmitor(ptc, i, false); else EnableEmitor(ptc, i, true); } } } override void Update(float timeSlice = 0) { if (m_Boat.GetCurrentGear() == 0) { if (IsPlaying()) { GetParticle().SetParticleParam(EmitorParam.BIRTH_RATE, 0); GetParticle().SetParticleParam(EmitorParam.BIRTH_RATE_RND, 0); Stop(); } return; } super.Update(timeSlice); Particle ptc = GetParticle(); float lerp1, lerp2; vector velocity = dBodyGetVelocityAt(m_Boat, m_Boat.CoordToParent(GetLocalPosition())); float speed = velocity.Normalize() * 3.6; // to km/h UpdateSpeedState(ptc, speed); UpdatePosToSeaLevel(timeSlice, -0.2); lerp1 = Math.InverseLerp(0, m_SpeedFast, speed); lerp2 = Math.InverseLerp(m_SpeedMedium - 5, m_SpeedFast + 10, speed); for (int i = 0; i < m_EmitorCount; i++) { if (i == 11 || i == 12) // waves { ptc.SetParameter(i, EmitorParam.VELOCITY, ptc.GetParameterOriginal(i, EmitorParam.VELOCITY) * lerp1); } else if (i < 5 || i == 7 || i == 8) // splashes { if (m_SpeedState > EBoatSpeed.SLOW) { if (GetGame().GetWaterDepth(m_Boat.CoordToParent(m_MemPointPos)) < -0.1) // when the front of the boat is above water, disable the emitor EnableEmitor(ptc, i, false); else EnableEmitor(ptc, i, true); ptc.SetParameter(i, EmitorParam.SIZE, ptc.GetParameterOriginal(i, EmitorParam.SIZE) * lerp2); } } } } } class EffectBoatWaterBack : EffectBoatWaterBase { protected const int EMITORS_FAST = 5; // emitors below this ID only play when fast enough void EffectBoatWaterBack() { m_EmitorCount = 10; m_SpeedSlow = 25; m_SpeedMedium = 50; m_SpeedFast = 150; SetParticleState( ParticleList.BOAT_WATER_BACK ); } override protected void UpdateSpeedState(Particle ptc, float speed) { EBoatSpeed speedStatePast = m_SpeedState; if (speed > m_SpeedFast) m_SpeedState = EBoatSpeed.FAST; else if (speed > m_SpeedMedium) m_SpeedState = EBoatSpeed.MEDIUM; else if (speed > m_SpeedSlow) m_SpeedState = EBoatSpeed.SLOW; else m_SpeedState = EBoatSpeed.SLOWEST; if (m_SpeedState == speedStatePast) return; for (int i = 0; i < m_EmitorCount; i++) { if (i < EMITORS_FAST) // big splash { if (m_SpeedState <= EBoatSpeed.SLOW) EnableEmitor(ptc, i, false); else EnableEmitor(ptc, i, true); } if (i >= EMITORS_FAST && i != 9) { if (m_SpeedState == EBoatSpeed.SLOWEST) EnableEmitor(ptc, i, false); else EnableEmitor(ptc, i, true); } } } override void Update(float timeSlice = 0) { if (m_Boat.GetCurrentGear() == 0) { if (IsPlaying()) { GetParticle().SetParticleParam(EmitorParam.BIRTH_RATE, 0); GetParticle().SetParticleParam(EmitorParam.BIRTH_RATE_RND, 0); Stop(); }; return; } super.Update(timeSlice); Particle ptc = GetParticle(); float speed = m_Boat.PropellerGetAngularVelocity(); UpdateSpeedState(ptc, speed); float lerp = Math.InverseLerp(0, m_SpeedFast, speed); for (int i = 0; i < m_EmitorCount; i++) { if (i < EMITORS_FAST && m_SpeedState > EBoatSpeed.SLOW) { ptc.SetParameter(i, EmitorParam.SIZE, ptc.GetParameterOriginal(i, EmitorParam.SIZE) * lerp); ptc.SetParameter(i, EmitorParam.BIRTH_RATE, ptc.GetParameterOriginal(i, EmitorParam.BIRTH_RATE) * lerp); } } } } class EffectBoatWaterSide : EffectBoatWaterBase { protected const int EMITORS_FAST = 3; // emitors below this ID only play when fast enough void EffectBoatWaterSide() { m_EmitorCount = 5; m_SpeedSlow = 5; m_SpeedMedium = 20; m_SpeedFast = 40; SetParticleState( ParticleList.BOAT_WATER_SIDE ); } override protected void UpdateSpeedState(Particle ptc, float speed) { EBoatSpeed speedStatePast = m_SpeedState; if (speed > m_SpeedFast) m_SpeedState = EBoatSpeed.FAST; else if (speed > m_SpeedMedium) m_SpeedState = EBoatSpeed.MEDIUM; else if (speed > m_SpeedSlow) m_SpeedState = EBoatSpeed.SLOW; else m_SpeedState = EBoatSpeed.SLOWEST; if (m_SpeedState == speedStatePast) return; for (int i = 0; i < m_EmitorCount; i++) { if (i < EMITORS_FAST) // splashes { if (m_SpeedState <= EBoatSpeed.SLOW) EnableEmitor(ptc, i, false); else EnableEmitor(ptc, i, true); } } } override void Update(float timeSlice = 0) { super.Update(timeSlice); Particle ptc = GetParticle(); vector velocity = dBodyGetVelocityAt(m_Boat, m_Boat.CoordToParent(GetLocalPosition())); float speed = velocity.Normalize() * 3.6; // to km/h UpdateSpeedState(ptc, speed); UpdatePosToSeaLevel(timeSlice, -0.2); float lerp = Math.InverseLerp(0, m_SpeedFast, speed); for (int i = 0; i < m_EmitorCount; i++) { if (i < EMITORS_FAST) // splashes { if (m_SpeedState > EBoatSpeed.SLOW) ptc.SetParameter(i, EmitorParam.SIZE, ptc.GetParameterOriginal(i, EmitorParam.SIZE) * lerp); } else // waves { ptc.SetParameter(i, EmitorParam.SIZE, ptc.GetParameterOriginal(i, EmitorParam.SIZE) * lerp); ptc.SetParameter(i, EmitorParam.VELOCITY, ptc.GetParameterOriginal(i, EmitorParam.VELOCITY) * lerp); } } } } class EffectBoatWaterBase : EffectParticle { const float POS_UPDATE_THROTTLE = 0.2; // seconds, controls how often can boat update somewhat expensive particle local reposition protected int m_EmitorCount; protected int m_SpeedSlow; protected int m_SpeedMedium; protected int m_SpeedFast; protected float m_PosUpdateTimer; protected EBoatSpeed m_SpeedState; protected BoatScript m_Boat; protected vector m_MemPointPos; override void AttachTo(Object obj, vector local_pos = "0 0 0", vector local_ori = "0 0 0", bool force_rotation_to_world = false) { m_Boat = BoatScript.Cast(obj); m_MemPointPos = local_pos; super.AttachTo(obj, local_pos, local_ori, force_rotation_to_world); } void SetParticleState(int state) { bool was_playing = IsPlaying(); Stop(); SetParticleID(state); if (was_playing) { Start(); // resume effect } } protected void EnableEmitor(Particle ptc, int id, bool enable) { if (enable) { ptc.SetParameter(id, EmitorParam.BIRTH_RATE, ptc.GetParameterOriginal(id, EmitorParam.BIRTH_RATE)); ptc.SetParameter(id, EmitorParam.BIRTH_RATE_RND, ptc.GetParameterOriginal(id, EmitorParam.BIRTH_RATE_RND)); } else { ptc.SetParameter(id, EmitorParam.BIRTH_RATE, 0); ptc.SetParameter(id, EmitorParam.BIRTH_RATE_RND, 0); } } // Update on state change protected void UpdateSpeedState(Particle ptc, float speed) {} // Update runs every frame void Update(float timeSlice = 0) { if (!IsPlaying()) { m_SpeedState = EBoatSpeed.UNSET; // reinit Start(); } }; // Adjust position to sea level protected void UpdatePosToSeaLevel(float timeSlice = 0, float surfaceOffset = 0) { m_PosUpdateTimer += timeSlice; if (m_PosUpdateTimer > POS_UPDATE_THROTTLE) { m_PosUpdateTimer = 0; vector posAdjusted = m_Boat.CoordToParent(m_MemPointPos); posAdjusted[1] = GetGame().SurfaceGetSeaLevel() + surfaceOffset; posAdjusted = m_Boat.CoordToLocal(posAdjusted); SetCurrentLocalPosition(posAdjusted); } } }