/* 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 m_Layouts; protected ref set m_UniqueLayouts; protected ref GameplayEffectDataMap m_WidgetDataMap; protected ref set m_RunningEffects; protected ref set m_RunningEffectsPrevious; protected ref array m_UpdatingEffects; protected ref array m_UpdatedWidgetsCheck; //to make sure widgets are not updated over and over (case of multiple IDs sharing same widget set) protected ref array m_UpdatedWidgetSetsCheck; //to make sure sets are not updated over and over (case of multiple IDs sharing same widget set) protected ref set m_SuspendRequests; protected ref map 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 = GetGame().GetWorkspace().CreateWidget(FrameWidgetTypeID,0,0,1.0,1.0,WidgetFlags.VISIBLE | WidgetFlags.HEXACTPOS | WidgetFlags.VEXACTPOS, 0xffffffff, 0); m_Layouts = new map; m_UniqueLayouts = new set; m_WidgetDataMap = new GameplayEffectDataMap; m_RunningEffects = new set; m_RunningEffectsPrevious = new set; m_UpdatingEffects = new array; m_UpdatedWidgetsCheck = new array; m_UpdatedWidgetSetsCheck = new array; m_SuspendRequests = new set; m_IDToTypeMap = new map; m_TimeProgBreath = 0.0; m_BreathMultStamina = 1.0; PairIDToTypes(); RegisterLayouts("gui/layouts/gameplay/CameraEffects.layout",CompileEffectListing()); RegisterLayouts("gui/layouts/gameplay/BleedingEffects.layout",{EffectWidgetsTypes.BLEEDING_LAYER}); 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,false,EffectWidgetsTypes.MASK_OCCLUDER); InitWidgetSet(EffectWidgetsTypes.HELMET_OCCLUDER); InitWidgetSet(EffectWidgetsTypes.HELMET2_OCCLUDER); InitWidgetSet(EffectWidgetsTypes.MOTO_OCCLUDER); InitWidgetSet(EffectWidgetsTypes.NVG_OCCLUDER,false,EffectWidgetsTypes.NVG_OCCLUDER); InitWidgetSet(EffectWidgetsTypes.PUMPKIN_OCCLUDER,false,EffectWidgetsTypes.NVG_OCCLUDER); InitWidgetSet(EffectWidgetsTypes.EYEPATCH_OCCLUDER); InitWidgetSet(EffectWidgetsTypes.COVER_FLASHBANG); InitWidgetSet(EffectWidgetsTypes.BLEEDING_LAYER,true); UpdateVisibility(); } void ~GameplayEffectWidgets() { for (int i = 0; i < m_Layouts.Count(); i++) { if (m_Layouts.GetElement(i)) m_Layouts.GetElement(i).Unlink(); } } /** \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 types) { Widget w = GetGame().GetWorkspace().CreateWidgets(path,m_Root,false); m_UniqueLayouts.Insert(w); w.Show(false); foreach (int i : types) { m_Layouts.Set(i,w); } } protected void PairIDToTypes() { m_IDToTypeMap.Insert(EffectWidgetsTypes.BLEEDING_LAYER,GameplayEffectsDataBleeding); } 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 output; Widget w = parent.GetChildren(); if (w) { output = new array; 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 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 CompileEffectListing() { array ret = new array; 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 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 effects) { if (effects && effects.Count() > 0) { m_RunningEffectsPrevious.Copy(m_RunningEffects); int value; int idx; for (int i = 0; i < effects.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 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 par = Param1.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 par = Param1.Cast(p); if (par) { float alpha_mod = Math.Clamp(par.param1,0.0,1.0); //Print(alpha_mod); 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); } }