DIFF

VehicleVFXComponent.c

class VehicleVFXData
{
    int m_ParticleId = -1;
    int m_EffectId = -1;

    vector m_Position;
    vector m_Orientation;

    ref EffectParticle m_Effect;
    ref ParticleParamsOverrideData m_ParticleParamsData;

    protected int	m_LastPlayedTime = -1;		//!< g_Game.GetTime() (ms) when this effect last started; -1 = never played
    protected float	m_CooldownSec    = 0;		//!< minimum seconds required between playbacks (0 = no guard)

    void VehicleVFXData()
    {
        m_ParticleParamsData = new ParticleParamsOverrideData();
    }
    
    void SetPosition(vector position)
    {
        m_Position = position;
    }

    // --------------------------------------------------------------------------------

    //! set the cooldown window (in seconds); 0 disables the guard
    void SetCooldown(float seconds)
    {
        m_CooldownSec = seconds;
    }
	
    // --------------------------------------------------------------------------------

    float GetCooldown()
    {
        return m_CooldownSec;
    }
	
    // --------------------------------------------------------------------------------

    //! true if enough time has elapsed since the last playback (or it never played / no cooldown set)
    bool CanPlay()
    {
        if (m_CooldownSec <= 0 || m_LastPlayedTime == -1)
            return true;

        //! g_Game.GetTime() is in milliseconds -> compare against cooldown converted to ms
        return (g_Game.GetTime() - m_LastPlayedTime) >= (int)(m_CooldownSec * 1000);
    }
	
    // --------------------------------------------------------------------------------

    void MarkPlayed()
    {
        m_LastPlayedTime = g_Game.GetTime();
    }
	
    // --------------------------------------------------------------------------------

    float GetRemainingCooldown()
    {
        if (m_CooldownSec <= 0 || m_LastPlayedTime == -1)
            return 0;

        float elapsed = (g_Game.GetTime() - m_LastPlayedTime) * 0.001;
        float remaining = m_CooldownSec - elapsed;
        if (remaining > 0)
			return remaining;
		
		return 0.0;
    }

    // --------------------------------------------------------------------------------

    void ResetCooldown()
    {
        m_LastPlayedTime = -1;
    }
}

class VehicleVFXDataParams
{
    vector m_Position;
    vector m_Orientation;
    float  m_CooldownSec;

    void VehicleVFXDataParams(vector position = vector.Zero, vector orientation = vector.Up, float cooldownSec = 0.0)
    {
        m_Position 		= position;
        m_Orientation 	= orientation;
        m_CooldownSec	= cooldownSec;
    }
}

class VehicleVFXComponent
{
    private bool m_SurfaceInfoOverriden;
    private EntityAI m_Parent;
    private SurfaceInfo m_SurfaceInfo;

    protected ref map<int, ref VehicleVFXData> m_EffectsMap;					//!< keyed by EVehicleVFXEffect
    protected ref map<int, ref array<ref VehicleVFXData>> m_EffectGroupsMap;	//!< keyed by EVehicleVFXEffectGroup

    void VehicleVFXComponent(notnull EntityAI parent)
    {
        m_Parent = parent;

        m_EffectsMap = new map<int, ref VehicleVFXData>();
        m_EffectGroupsMap = new map<int, ref array<ref VehicleVFXData>>();
    }

    // --------------------------------------------------------------------------------

    vector ConvertMemPointToPosition(string memoryPointName)
    {
        if (m_Parent.MemoryPointExists(memoryPointName))
            return m_Parent.GetMemoryPointPos(memoryPointName);

        return vector.Zero;
    }

    // --------------------------------------------------------------------------------

    //! register single instance of effect
    void RegisterEffect(EVehicleVFXEffect effectId, typename effectTypename, VehicleVFXDataParams params)
    {
        if (!m_EffectsMap.Contains(effectId))
            m_EffectsMap.Set(effectId, CreateEffectData(effectTypename, params));
        #ifdef DIAG_DEVELOPER
        else
            ErrorEx(string.Format("Effect '%1' is already registered.", EnumTools.EnumToString(EVehicleVFXEffect, effectId)));
        #endif
    }

    // --------------------------------------------------------------------------------

    //! register multiple instances of single type of effect owned by the given group
    void RegisterEffectGroup(EVehicleVFXEffectGroup groupId, typename effectTypename, int count, VehicleVFXDataParams params)
    {
        if (!m_EffectGroupsMap.Contains(groupId))
        {
            array<ref VehicleVFXData> groupData = new array<ref VehicleVFXData>();
            for (int i = 0; i < count; ++i)
                groupData.Insert(CreateEffectData(effectTypename, params));

            m_EffectGroupsMap.Insert(groupId, groupData);
        }
        #ifdef DIAG_DEVELOPER
        else
            ErrorEx(string.Format("Group for '%1' is already registered", EnumTools.EnumToString(EVehicleVFXEffectGroup, groupId)));
        #endif
    }

    // --------------------------------------------------------------------------------

    protected VehicleVFXData CreateEffectData(typename effectTypename, VehicleVFXDataParams params)
    {
        VehicleVFXData data = new VehicleVFXData();
        data.m_Position 	= params.m_Position;
        data.m_Orientation	= params.m_Orientation;
        data.m_Effect 		= EffectParticle.Cast(effectTypename.Spawn());

        data.SetCooldown(params.m_CooldownSec);

        return data;
    }

    // --------------------------------------------------------------------------------

    protected void PlayEffectData(notnull VehicleVFXData data, EntityAI parent)
    {
        //! cooldown guard - refuse to (re)start until the window elapses
        if (!data.CanPlay())
            return;

        SurfaceInfo surfaceInfo = m_SurfaceInfo;
        if (!m_SurfaceInfoOverriden)
        {
            SurfaceDetectionParameters surfaceParams  = new SurfaceDetectionParameters();
            surfaceParams.syncMode = UseObjectsMode.NoLock;
            SurfaceDetectionResult surfaceResult = new SurfaceDetectionResult();
            surfaceParams.position = parent.GetPosition();
            g_Game.GetSurface(surfaceParams, surfaceResult);
            surfaceInfo = surfaceResult.surface;
        }


        if(data.m_EffectId == -1)
        {
            //! do not play - mostly for dynamicaly created/placed particles
            if (data.m_Position == "-1 -1 -1")
                return;

            data.m_Effect.ParticleParamsOverride(data.m_ParticleParamsData);
            data.m_EffectId = SEffectManager.PlayOnObject(data.m_Effect, parent, data.m_Position, data.m_Orientation);

            //! stamp the playback time so the cooldown guard starts counting
            data.MarkPlayed();
        }
        else
        {
            //! created only - start playback
            if (!data.m_Effect.IsPlaying())
            {
                data.m_Effect.Start();
                data.m_ParticleParamsData.ClearAll();

                //! stamp on restart too
                data.MarkPlayed();
            }

            data.m_Effect.SelectFromSurface(surfaceInfo);

            Particle particle = Particle.Cast(data.m_Effect.GetParticle());
            if (particle)
            {
                data.m_Effect.ParticleParamsOverride(data.m_ParticleParamsData);
            }
        }
    }

    // --------------------------------------------------------------------------------

    protected void StopEffectData(VehicleVFXData data)
    {
        if (data && data.m_Effect)
        {
            data.m_Effect.Stop();
            data.m_EffectId = -1;
            data.m_ParticleParamsData.ClearAll();
        }
    }

    // --------------------------------------------------------------------------------

    protected bool IsEffectDataPlaying(VehicleVFXData data)
    {
        if (data && data.m_Effect)
            return data.m_Effect.IsPlaying();

        return false;
    }

    // --------------------------------------------------------------------------------

    protected bool CanPlayEffectData(VehicleVFXData data)
    {
        if (data)
            return data.CanPlay();

        return false;
    }

    // --------------------------------------------------------------------------------

    protected void SetEffectDataPosition(VehicleVFXData data, vector position)
    {
        if (data && data.m_Effect)
        {
            data.m_Position = position;
            data.m_Effect.SetCurrentLocalPosition(position);
        }
    }

    // --------------------------------------------------------------------------------
    //! single effect API
    // --------------------------------------------------------------------------------

    void PlayEffect(EVehicleVFXEffect effectId, EntityAI parent = null)
    {
        if (!parent)
            parent = m_Parent;

        VehicleVFXData data = m_EffectsMap.Get(effectId);
        if (data)
            PlayEffectData(data, parent);
    }

    // --------------------------------------------------------------------------------

    void StopEffect(EVehicleVFXEffect effectId)
    {
        StopEffectData(m_EffectsMap.Get(effectId));
    }

    // --------------------------------------------------------------------------------

    void StopAllEffects()
    {
        foreach (int effectId, VehicleVFXData data : m_EffectsMap)
            StopEffectData(data);

        foreach (int groupId, array<ref VehicleVFXData> groupData : m_EffectGroupsMap)
        {
            foreach (VehicleVFXData groupEffectData : groupData)
                StopEffectData(groupEffectData);
        }
    }

    // --------------------------------------------------------------------------------

    void SetEffectCooldown(EVehicleVFXEffect effectId, float seconds)
    {
        VehicleVFXData data = m_EffectsMap.Get(effectId);
        if (data)
            data.SetCooldown(seconds);
        #ifdef DIAG_DEVELOPER
        else
            ErrorEx(string.Format("Cannot set cooldown, effect '%1' does not exist.", EnumTools.EnumToString(EVehicleVFXEffect, effectId)));
        #endif
    }

    // --------------------------------------------------------------------------------

    bool CanPlayEffect(EVehicleVFXEffect effectId)
    {
        return CanPlayEffectData(m_EffectsMap.Get(effectId));
    }

    // --------------------------------------------------------------------------------

    float GetEffectRemainingCooldown(EVehicleVFXEffect effectId)
    {
        VehicleVFXData data = m_EffectsMap.Get(effectId);
        if (data)
            return data.GetRemainingCooldown();

        return 0;
    }

    // --------------------------------------------------------------------------------

    void ResetEffectCooldown(EVehicleVFXEffect effectId)
    {
        VehicleVFXData data = m_EffectsMap.Get(effectId);
        if (data)
            data.ResetCooldown();
    }

    // --------------------------------------------------------------------------------

    bool IsEffectPlayed(EVehicleVFXEffect effectId)
    {
        return IsEffectDataPlaying(m_EffectsMap.Get(effectId));
    }

    // --------------------------------------------------------------------------------

    void SetEffectPosition(EVehicleVFXEffect effectId, vector position)
    {
        VehicleVFXData data = m_EffectsMap.Get(effectId);
        if (!data)
        {
            ErrorEx(string.Format("Effect '%1' does not exist. Isn't it group? Consider use of SetEffectGroupPositions", EnumTools.EnumToString(EVehicleVFXEffect, effectId)));
            return;
        }

        SetEffectDataPosition(data, position);
    }

    // --------------------------------------------------------------------------------

    void SetEffectParticleState(EVehicleVFXEffect effectId, int particleId)
    {
        VehicleVFXData data = m_EffectsMap.Get(effectId);
        if (data && data.m_Effect)
            data.m_Effect.SetParticleState(particleId);
    }

    // --------------------------------------------------------------------------------

    ref ParticleParamsOverrideData GetParticleParamsOverrideData(EVehicleVFXEffect effectId)
    {
        VehicleVFXData data = m_EffectsMap.Get(effectId);
        if (data && data.m_ParticleParamsData)
            return data.m_ParticleParamsData;

        #ifdef DIAG_DEVELOPER
        ErrorEx(string.Format("No particle params override data set for %1", EnumTools.EnumToString(EVehicleVFXEffect, effectId)));
        #endif

        return null;
    }

    // --------------------------------------------------------------------------------
    //! effect group API
    // --------------------------------------------------------------------------------

    void PlayEffectGroup(EVehicleVFXEffectGroup groupId, EntityAI parent = null)
    {
        if (!parent)
            parent = m_Parent;

        array<ref VehicleVFXData> groupData = m_EffectGroupsMap.Get(groupId);
        if (!groupData)
            return;

        foreach (VehicleVFXData data : groupData)
            PlayEffectData(data, parent);
    }

    // --------------------------------------------------------------------------------

    void StopEffectGroup(EVehicleVFXEffectGroup groupId)
    {
        array<ref VehicleVFXData> groupData = m_EffectGroupsMap.Get(groupId);
        if (!groupData)
            return;

        foreach (VehicleVFXData data : groupData)
            StopEffectData(data);
    }

    // --------------------------------------------------------------------------------

    void SetEffectGroupCooldown(EVehicleVFXEffectGroup groupId, float seconds)
    {
        array<ref VehicleVFXData> groupData = m_EffectGroupsMap.Get(groupId);
        if (!groupData)
        {
            #ifdef DIAG_DEVELOPER
            ErrorEx(string.Format("Cannot set cooldown, group '%1' does not exist.", EnumTools.EnumToString(EVehicleVFXEffectGroup, groupId)));
            #endif
            return;
        }

        foreach (VehicleVFXData data : groupData)
            data.SetCooldown(seconds);
    }

    // --------------------------------------------------------------------------------

    //! true only when every member of the group can play
    bool CanPlayEffectGroup(EVehicleVFXEffectGroup groupId)
    {
        array<ref VehicleVFXData> groupData = m_EffectGroupsMap.Get(groupId);
        if (!groupData)
            return false;

        foreach (VehicleVFXData data : groupData)
        {
            if (!CanPlayEffectData(data))
                return false;
        }

        return true;
    }

    // --------------------------------------------------------------------------------

    //! longest remaining cooldown across the group members
    float GetEffectGroupRemainingCooldown(EVehicleVFXEffectGroup groupId)
    {
        array<ref VehicleVFXData> groupData = m_EffectGroupsMap.Get(groupId);
        if (!groupData)
            return 0;

        float maxRemaining = 0;
        foreach (VehicleVFXData data : groupData)
        {
            float remaining = data.GetRemainingCooldown();
            if (remaining > maxRemaining)
                maxRemaining = remaining;
        }

        return maxRemaining;
    }

    // --------------------------------------------------------------------------------

    void ResetEffectGroupCooldown(EVehicleVFXEffectGroup groupId)
    {
        array<ref VehicleVFXData> groupData = m_EffectGroupsMap.Get(groupId);
        if (!groupData)
            return;

        foreach (VehicleVFXData data : groupData)
            data.ResetCooldown();
    }

    // --------------------------------------------------------------------------------

    //! true when any member of the group is playing
    bool IsEffectGroupPlayed(EVehicleVFXEffectGroup groupId)
    {
        array<ref VehicleVFXData> groupData = m_EffectGroupsMap.Get(groupId);
        if (!groupData)
            return false;

        foreach (VehicleVFXData data : groupData)
        {
            if (IsEffectDataPlaying(data))
                return true;
        }

        return false;
    }

    // --------------------------------------------------------------------------------

    void SetEffectGroupPositions(EVehicleVFXEffectGroup groupId, TVectorArray positions)
    {
        array<ref VehicleVFXData> groupData = m_EffectGroupsMap.Get(groupId);
        if (!groupData || !positions)
            return;

        int count = Math.Min(groupData.Count(), positions.Count());
        for (int i = 0; i < count; ++i)
            SetEffectDataPosition(groupData[i], positions[i]);
    }

    // --------------------------------------------------------------------------------

    void SetEffectGroupParticleState(EVehicleVFXEffectGroup groupId, int particleId)
    {
        array<ref VehicleVFXData> groupData = m_EffectGroupsMap.Get(groupId);
        if (!groupData)
            return;

        foreach (VehicleVFXData data : groupData)
        {
            if (data.m_Effect)
                data.m_Effect.SetParticleState(particleId);
        }
    }

    // --------------------------------------------------------------------------------

    ref ParticleParamsOverrideData GetEffectGroupParticleParamsOverrideData(EVehicleVFXEffectGroup groupId)
    {
        array<ref VehicleVFXData> groupData = m_EffectGroupsMap.Get(groupId);
        if (groupData)
        {
            foreach (VehicleVFXData data : groupData)
            {
                if (data && data.m_ParticleParamsData)
                    return data.m_ParticleParamsData;
            }
        }

        #ifdef DIAG_DEVELOPER
        ErrorEx(string.Format("No particle params override data set for group %1", EnumTools.EnumToString(EVehicleVFXEffectGroup, groupId)));
        #endif

        return null;
    }

    // --------------------------------------------------------------------------------

    void SetSurfaceInfo(SurfaceInfo surfaceInfo)
    {
        if (surfaceInfo)
        {
            m_SurfaceInfoOverriden = true;
            m_SurfaceInfo = surfaceInfo;

            return;
        }

        m_SurfaceInfoOverriden = false;
    }

    // --------------------------------------------------------------------------------

    #ifdef DIAG_DEVELOPER
    void ListGroups()
    {
        foreach (int groupId, array<ref VehicleVFXData> groupData : m_EffectGroupsMap)
            Print(string.Format("[VFX] group '%1' members=%2", EnumTools.EnumToString(EVehicleVFXEffectGroup, groupId), groupData.Count()));
    }

    // --------------------------------------------------------------------------------

    //! Dumps the cooldown state of every registered effect to the log.
    void ListCooldowns()
    {
        foreach (int effectId, VehicleVFXData data : m_EffectsMap)
            Print(string.Format("[VFX] '%1' cooldown=%2s canPlay=%3 remaining=%4s", EnumTools.EnumToString(EVehicleVFXEffect, effectId), data.GetCooldown(), data.CanPlay(), data.GetRemainingCooldown()));

        foreach (int groupId, array<ref VehicleVFXData> groupData : m_EffectGroupsMap)
        {
            foreach (int index, VehicleVFXData groupEffectData : groupData)
                Print(string.Format("[VFX] group '%1'[%2] cooldown=%3s canPlay=%4 remaining=%5s", EnumTools.EnumToString(EVehicleVFXEffectGroup, groupId), index, groupEffectData.GetCooldown(), groupEffectData.CanPlay(), groupEffectData.GetRemainingCooldown()));
        }
    }
    #endif
}