/*
TODO - doxygen formating
*/
//! grouped gameplay effect widgets and their handling
class GameplayEffectWidgets extends GameplayEffectWidgets_base
{
protected ref Widget m_Root; //dummy parent node
protected ref map<int,ref Widget> m_Layouts;
protected ref set<ref Widget> m_UniqueLayouts;
protected ref GameplayEffectDataMap m_WidgetDataMap;
protected ref set<int> m_RunningEffects;
protected ref set<int> m_RunningEffectsPrevious;
protected ref array<int> m_UpdatingEffects;
protected ref array<ref Widget> m_UpdatedWidgetsCheck; //to make sure widgets are not updated over and over (case of multiple IDs sharing same widget set)
protected ref array<int> m_UpdatedWidgetSetsCheck; //to make sure sets are not updated over and over (case of multiple IDs sharing same widget set)
protected ref set<int> m_SuspendRequests;
protected ref map<int,typename> m_IDToTypeMap;
protected float m_TimeProgBreath;
protected float m_BreathMultStamina;
protected float m_BreathResidue;
//UserID's for widget containers that use something different from 'EffectWidgetsTypes' defaults
protected const int WIDGETSET_BREATH = 100;
//effect values
protected int m_BreathColor;
protected float m_BreathAlphaVal;
protected float m_FlashbangCoverAlphaVal;
void GameplayEffectWidgets()
{
m_Root = g_Game.GetWorkspace().CreateWidget(FrameWidgetTypeID,0,0,1.0,1.0,WidgetFlags.VISIBLE | WidgetFlags.HEXACTPOS | WidgetFlags.VEXACTPOS, 0xffffffff, 0);
m_Layouts = new map<int,ref Widget>;
m_UniqueLayouts = new set<ref Widget>;
m_WidgetDataMap = new GameplayEffectDataMap;
m_RunningEffects = new set<int>;
m_RunningEffectsPrevious = new set<int>;
m_UpdatingEffects = new array<int>;
m_UpdatedWidgetsCheck = new array<ref Widget>;
m_UpdatedWidgetSetsCheck = new array<int>;
m_SuspendRequests = new set<int>;
m_IDToTypeMap = new map<int,typename>;
m_TimeProgBreath = 0.0;
m_BreathMultStamina = 1.0;
Init();
UpdateVisibility();
}
void ~GameplayEffectWidgets()
{
for (int i = 0; i < m_Layouts.Count(); i++)
{
if (m_Layouts.GetElement(i))
m_Layouts.GetElement(i).Unlink();
}
}
////////////////////////////////////
//inits
protected void Init()
{
PairIDToTypes();
InitLayouts();
InitWidgetSets();
}
//! Links types to unique handler types, if needed. Vanilla stuff was already handled in the generic update, left that as it was. (naming!)
protected void PairIDToTypes()
{
m_IDToTypeMap.Insert(EffectWidgetsTypes.BLEEDING_LAYER,GameplayEffectsDataBleeding);
}
protected void InitLayouts()
{
RegisterLayouts("gui/layouts/gameplay/CameraEffects.layout",CompileEffectListing());
RegisterLayouts("gui/layouts/gameplay/BleedingEffects.layout",{EffectWidgetsTypes.BLEEDING_LAYER});
}
protected void InitWidgetSets()
{
InitWidgetSet(EffectWidgetsTypes.MASK_BREATH,true,WIDGETSET_BREATH);
InitWidgetSet(EffectWidgetsTypes.HELMET_BREATH,true,WIDGETSET_BREATH);
InitWidgetSet(EffectWidgetsTypes.MOTO_BREATH,true,WIDGETSET_BREATH);
InitWidgetSet(EffectWidgetsTypes.MASK_OCCLUDER);
InitWidgetSet(EffectWidgetsTypes.HELMET_OCCLUDER);
InitWidgetSet(EffectWidgetsTypes.HELMET2_OCCLUDER);
InitWidgetSet(EffectWidgetsTypes.MOTO_OCCLUDER);
InitWidgetSet(EffectWidgetsTypes.NVG_OCCLUDER);
InitWidgetSet(EffectWidgetsTypes.PUMPKIN_OCCLUDER,false,EffectWidgetsTypes.NVG_OCCLUDER);
InitWidgetSet(EffectWidgetsTypes.EYEPATCH_OCCLUDER);
InitWidgetSet(EffectWidgetsTypes.COVER_FLASHBANG);
InitWidgetSet(EffectWidgetsTypes.BLEEDING_LAYER,true);
}
////////////////////////////////////
/**
\brief Registers new layout and ties effect IDs to it
\note Order of layout creation matters, they get layered on top of each other. Within a single layout, widget priorities govern the widget order.
*/
protected void RegisterLayouts(string path, array<int> types)
{
Widget w = g_Game.GetWorkspace().CreateWidgets(path,m_Root,false);
m_UniqueLayouts.Insert(w);
w.Show(false);
foreach (int i : types)
{
m_Layouts.Set(i,w);
}
}
protected typename TranslateIDToType(int typeID)
{
return m_IDToTypeMap.Get(typeID);
}
override void RegisterGameplayEffectData(int id, Param p)
{
if (!m_WidgetDataMap.Get(id).DataInitialized())
{
m_WidgetDataMap.Get(id).RegisterData(p);
}
}
/**
\brief InitWidgetSet
\param type \p int ID of effect widget type
\param updating \p bool Marks widgets for 'PlayerBase.EOnFrame' updating
\param user_id_override \p int UserID of a widget to be used instead (allows multiple types to use single widget)
@code
InitWidgetSet(EffectWidgetsTypes.MASK_BREATH,true,BREATH);
@endcode
\note All child widgets MUST be of the same type if special functionality is required (ImageWidget etc.)
*/
protected void InitWidgetSet(int type, bool updating = false, int user_id_override = -1)
{
Widget parent = null;
if (user_id_override != -1)
{
parent = m_Layouts.Get(type).FindAnyWidgetById(user_id_override);
}
else
{
parent = m_Layouts.Get(type).FindAnyWidgetById(type);
}
if (!parent)
{
Print("InitWidgetSet | type: " + type + " - parent not found!");
return;
}
array<ref Widget> output;
Widget w = parent.GetChildren();
if (w)
{
output = new array<ref Widget>;
while (w)
{
w.Update();
w.Show(false,true);
output.Insert(w);
w = w.GetSibling();
}
if (parent.GetChildren())
{
typename handled_type = TranslateIDToType(type);
if ( handled_type )
{
CreateHandledClass(handled_type,output,type,user_id_override);
}
else
{
if (ImageWidget.Cast(parent.GetChildren()))
{
m_WidgetDataMap.Set(type, new GameplayEffectsDataImage(output,type,user_id_override) );
}
else
{
m_WidgetDataMap.Set(type, new GameplayEffectsData(output,type,user_id_override) );
}
}
}
if (updating)
m_UpdatingEffects.Insert(type);
}
}
bool CreateHandledClass(typename handled_type, array<ref Widget> input, int type, int user_override)
{
if (handled_type)
{
GameplayEffectsData data = GameplayEffectsData.Cast(handled_type.Spawn());
data.Init(input,type,m_Layouts.Get(type),user_override);
m_WidgetDataMap.Set(type, data);
return true;
}
return false;
}
//! returns all vanilla effects, nested in a vanilla layout. If using different layouts for custom effects, please register and link separately
array<int> CompileEffectListing()
{
array<int> ret = new array<int>;
ret.Insert(EffectWidgetsTypes.MASK_OCCLUDER);
ret.Insert(EffectWidgetsTypes.MASK_BREATH);
ret.Insert(EffectWidgetsTypes.HELMET_OCCLUDER);
ret.Insert(EffectWidgetsTypes.HELMET_BREATH);
ret.Insert(EffectWidgetsTypes.MOTO_OCCLUDER);
ret.Insert(EffectWidgetsTypes.MOTO_BREATH);
ret.Insert(EffectWidgetsTypes.COVER_FLASHBANG);
ret.Insert(EffectWidgetsTypes.NVG_OCCLUDER);
ret.Insert(EffectWidgetsTypes.PUMPKIN_OCCLUDER);
ret.Insert(EffectWidgetsTypes.EYEPATCH_OCCLUDER);
ret.Insert(EffectWidgetsTypes.HELMET2_OCCLUDER);
return ret;
}
protected void UpdateVisibility()
{
Widget w;
//Hide diff
int value;
int runningEffectCount = m_RunningEffects.Count();
bool runningEffectsPresent = runningEffectCount > 0;
GameplayEffectsData dta;
for (int i = 0; i < m_RunningEffectsPrevious.Count(); i++)
{
value = m_RunningEffectsPrevious.Get(i);
dta = m_WidgetDataMap.Get(value);
if (runningEffectCount < 1 || m_RunningEffects.Find(value) == -1)
{
if (dta.HasDefinedHandle())
{
dta.UpdateVisibility(false);
}
else
{
for (int j = 0; j < m_WidgetDataMap.Get(value).GetWidgetSet().Count(); j++)
{
w = m_WidgetDataMap.Get(value).GetWidgetSet().Get(j);
w.Show(false);
}
w.GetParent().Show(false);
}
}
}
//Show running effects
if (runningEffectsPresent)
{
value = 0;
for (i = 0; i < runningEffectCount; i++)
{
value = m_RunningEffects.Get(i);
dta = m_WidgetDataMap.Get(value);
if (dta.HasDefinedHandle())
{
dta.m_LayoutRoot.Show(true);
dta.UpdateVisibility(true);
}
else
{
for (j = 0; j < m_WidgetDataMap.Get(value).GetWidgetSet().Count(); j++)
{
w = m_WidgetDataMap.Get(value).GetWidgetSet().Get(j);
w.Update();
w.Show(true);
}
while (w) //dumb but necessary because of uncertain "visible" setting of the layout
{
w = w.GetParent();
if (w)
{
w.Show(true);
}
}
}
}
}
m_Root.Show(runningEffectsPresent && m_SuspendRequests.Count() < 1);
m_RunningEffectsPrevious.Clear();
}
override void AddActiveEffects(array<int> effects)
{
if (effects && effects.Count() > 0)
{
m_RunningEffectsPrevious.Copy(m_RunningEffects);
int value;
for (int i = 0; i < effects.Count(); i++)
{
value = effects.Get(i);
m_RunningEffects.Insert(value);
}
if (m_RunningEffectsPrevious.Count() != m_RunningEffects.Count())
{
UpdateVisibility();
}
}
}
override void RemoveActiveEffects(array<int> effects)
{
if (effects && effects.Count() > 0)
{
m_RunningEffectsPrevious.Copy(m_RunningEffects);
int count = effects.Count();
int value;
int idx;
for (int i = 0; i < count; ++i)
{
value = effects.Get(i);
idx = m_RunningEffects.Find(value);
if (idx != -1)
{
m_RunningEffects.Remove(idx);
}
}
if (m_RunningEffectsPrevious.Count() != m_RunningEffects.Count())
{
UpdateVisibility();
}
}
}
override void StopAllEffects()
{
m_Root.Show(false); //to avoid visual 'peeling'
if (IsAnyEffectRunning())
{
int count = m_RunningEffects.Count();
GameplayEffectsData data;
for (int i = 0; i < count; i++) //iterates over running metadata, in case anything requires its own stop handling
{
data = m_WidgetDataMap.Get(m_RunningEffects[i]);
data.ForceStop();
}
}
m_RunningEffectsPrevious.Copy(m_RunningEffects);
m_RunningEffects.Clear();
UpdateVisibility();
}
override bool IsAnyEffectRunning()
{
return m_RunningEffects && m_RunningEffects.Count() > 0;
}
override void AddSuspendRequest(int request_id)
{
m_SuspendRequests.Insert(request_id);
UpdateVisibility();
}
override void RemoveSuspendRequest(int request_id)
{
int idx = m_SuspendRequests.Find(request_id);
if (idx != -1)
{
m_SuspendRequests.Remove(idx);
}
UpdateVisibility();
}
override void ClearSuspendRequests()
{
m_SuspendRequests.Clear();
UpdateVisibility();
}
override int GetSuspendRequestCount()
{
return m_SuspendRequests.Count();
}
//! Usually called in course of an OnFrame update, can be manually called from elsewhere with parameters
override void UpdateWidgets(int type = -1, float timeSlice = 0, Param p = null, int handle = -1)
{
GameplayEffectsData dta;
array<ref Widget> widget_set;
if (type == EffectWidgetsTypes.ROOT)
{
HandleWidgetRoot(timeSlice,p,handle);
}
else if (type == -1) //update stuff from the m_UpdatingEffects
{
int type_widgetset = 0;
for (int i = 0; i < m_UpdatingEffects.Count(); i++)
{
if (m_RunningEffects.Find(m_UpdatingEffects.Get(i)) != -1)
{
type_widgetset = m_UpdatingEffects.Get(i);
dta = m_WidgetDataMap.Get(type_widgetset);
if (dta.HasDefinedHandle() && dta.DataInitialized())
{
dta.Update(timeSlice,p,handle); //calculate and apply
}
else
{
CalculateValues(type_widgetset,timeSlice,p,handle);
widget_set = dta.GetWidgetSet();
foreach (Widget w : widget_set)
{
if (w.IsVisibleHierarchy() && m_UpdatedWidgetsCheck.Find(w) == -1)
{
m_UpdatedWidgetsCheck.Insert(w);
ProcessWidgetUpdate(w,type_widgetset,timeSlice,p,handle);
}
}
}
}
}
}
else //update specific widget set
{
if (m_RunningEffects.Find(type) != -1) //only do if the effect is running (FPS stonks!)
{
dta = m_WidgetDataMap.Get(type);
if (dta.HasDefinedHandle() && dta.DataInitialized())
{
dta.Update(timeSlice,p,handle); //calculate and apply
}
else
{
CalculateValues(type,timeSlice,p,handle);
widget_set = dta.GetWidgetSet();
foreach (Widget w2 : widget_set)
{
if (w2.IsVisibleHierarchy() && m_UpdatedWidgetsCheck.Find(w2) == -1)
{
m_UpdatedWidgetsCheck.Insert(w2);
ProcessWidgetUpdate(w2,type,timeSlice,p,handle);
}
}
}
}
}
m_UpdatedWidgetsCheck.Clear();
m_UpdatedWidgetSetsCheck.Clear();
}
//! Only one calculation per unique WidgetSet
protected void CalculateValues(int type = -1, float timeSlice = 0, Param p = null, int handle = -1)
{
if (m_UpdatedWidgetSetsCheck.Find(m_WidgetDataMap.Get(type).GetWidgetSetID()) != -1)
{
//Print("skipped updating set ID " + m_WidgetDataMap.Get(type).GetWidgetSetID() + " | effect: " + type);
return;
}
switch (type)
{
case EffectWidgetsTypes.MOTO_BREATH:
case EffectWidgetsTypes.HELMET_BREATH:
case EffectWidgetsTypes.MASK_BREATH:
{
CalculateBreathEffect(timeSlice);
}
break;
case EffectWidgetsTypes.MOTO_OCCLUDER:
case EffectWidgetsTypes.EYEPATCH_OCCLUDER:
case EffectWidgetsTypes.HELMET_OCCLUDER:
case EffectWidgetsTypes.HELMET2_OCCLUDER:
case EffectWidgetsTypes.MASK_OCCLUDER:
{
CalculateOccluderEffect(type,timeSlice,p,handle);
}
break;
case EffectWidgetsTypes.COVER_FLASHBANG:
{
CalculateFlashbangEffect(type,timeSlice,p,handle);
}
break;
default:
return; //no need to calculate anything
break;
}
m_UpdatedWidgetSetsCheck.Insert(m_WidgetDataMap.Get(type).GetWidgetSetID());
}
protected void ProcessWidgetUpdate(Widget w, int type, float timeSlice = 0, Param p = null, int handle = -1)
{
switch (type)
{
case EffectWidgetsTypes.MOTO_BREATH:
case EffectWidgetsTypes.HELMET_BREATH:
case EffectWidgetsTypes.MASK_BREATH:
{
UpdateBreathEffect(ImageWidget.Cast(w));
}
break;
case EffectWidgetsTypes.MOTO_OCCLUDER:
case EffectWidgetsTypes.EYEPATCH_OCCLUDER:
case EffectWidgetsTypes.HELMET_OCCLUDER:
case EffectWidgetsTypes.HELMET2_OCCLUDER:
case EffectWidgetsTypes.MASK_OCCLUDER:
{
UpdateOccluderEffect(ImageWidget.Cast(w),type,timeSlice,p,handle);
}
break;
case EffectWidgetsTypes.COVER_FLASHBANG:
{
UpdateFlashbangEffect(ImageWidget.Cast(w));
}
break;
default:
//Print("---invalid widget type to update---");
break;
}
}
//-----------------------------------------
//specific widget 'handlers'
const float BREATH_HDR_MIN = 0.005; //dusk?
const float BREATH_HDR_MAX = 1.0; //dark?
const float BREATH_COLOR_MULT_MIN = 0.5;
const float BREATH_COLOR_MULT_MAX = 0.8;
//-----------------------------------------
//Breath
//-----------------------------------------
protected void CalculateBreathEffect(float timeSlice = 0, int type = -1, Param p = null)
{
float modifier = Math.Lerp(0.25, 0.5, m_BreathResidue);
float speed = timeSlice * modifier;
m_BreathResidue -= speed;
m_BreathResidue = Math.Clamp(m_BreathResidue,0,1);
float residue_final = Math.Lerp(0, 0.7, m_BreathResidue);
float hdr_mult;
hdr_mult = GetSceneHDRMul(0);
hdr_mult = Math.Clamp(hdr_mult,BREATH_HDR_MIN,BREATH_HDR_MAX);
hdr_mult = Math.InverseLerp(BREATH_HDR_MIN,BREATH_HDR_MAX,hdr_mult);
hdr_mult = Math.Lerp(BREATH_COLOR_MULT_MAX,BREATH_COLOR_MULT_MIN,hdr_mult);
m_BreathColor = ARGBF(0.0,1.0 * hdr_mult,1.0 * hdr_mult,1.0 * hdr_mult); //grayscaling of the image
m_BreathAlphaVal = Math.Lerp(m_BreathAlphaVal, residue_final, timeSlice);
}
protected void UpdateBreathEffect(ImageWidget w)
{
w.SetColor(m_BreathColor);
w.SetAlpha(m_BreathAlphaVal);
}
//-----------------------------------------
//Occluders
//-----------------------------------------
protected void CalculateOccluderEffect(int type, float timeSlice, Param p, int handle)
{
}
protected void UpdateOccluderEffect(ImageWidget w, int type, float timeSlice, Param p, int handle)
{
}
//-----------------------------------------
//Flashbang
//-----------------------------------------
protected void CalculateFlashbangEffect(int type, float timeSlice, Param p, int handle)
{
Param1<float> par = Param1<float>.Cast(p);
m_FlashbangCoverAlphaVal = par.param1;
/*if (m_FlashbangCoverAlphaVal <= 0.0)
{
RemoveActiveEffects({EffectWidgetsTypes.COVER_FLASHBANG});
return;
}*/
}
protected void UpdateFlashbangEffect(ImageWidget w)
{
w.SetAlpha(1 - m_FlashbangCoverAlphaVal);
}
//-----------------------------------------
//root handling (generic 'Widget', so mainly alpha and hiding?)
//-----------------------------------------
protected void HandleWidgetRoot(float timeSlice = 0, Param p = null, int handle = -1)
{
switch (handle)
{
default:
{
Param1<float> par = Param1<float>.Cast(p);
if (par)
{
float alpha_mod = Math.Clamp(par.param1,0.0,1.0);
m_Root.SetAlpha(alpha_mod);
}
}
break;
}
}
//-----------------------------------------
//! Generic update, called on frame from the player
override void Update(float timeSlice)
{
if (m_SuspendRequests.Count() > 0)
{
return;
}
UpdateWidgets(-1,timeSlice);
}
/*
override void SetBreathIntensityStamina(float stamina_cap, float stamina_current)
{
float stamina_normalized = Math.InverseLerp(0, stamina_cap, stamina_current);
stamina_normalized = Math.Clamp(stamina_normalized,0,1);
if ( stamina_normalized < STAMINA_SOUND_TR2 )
{
m_BreathMultStamina = 2.0;
}
else if ( stamina_normalized < STAMINA_SOUND_TR1 )
{
m_BreathMultStamina = 1.5;
}
else
{
m_BreathMultStamina = 1.0;
}
}
*/
override void OnVoiceEvent(float breathing_resistance01)
{
m_BreathResidue += Math.Lerp(0,0.35,breathing_resistance01);
m_BreathResidue = Math.Clamp(m_BreathResidue,0,1);
}
}