//------------------------------------------------------------- class SpookyEventWind : SpookyEventBase { override protected void Init() { SetCoolDown(65); m_SoundSet = "SpookyArea_WhistlingWind_SoundSet"; } override protected bool CanDo(PlayerBase player, TStringVectorMap surfaceTypes) { return player.IsSoundInsideBuilding(); } override protected void Do(PlayerBase player) { //put additional code here } } //------------------------------------------------------------- class SpookyEventWhisper : SpookyEventBase { override protected void Init() { SetCoolDown(80); m_SoundSet = "SpookyArea_Whispering_SoundSet"; } override protected void Do(PlayerBase player) { //put additional code here } } //------------------------------------------------------------- class SpookyEventSteps : SpookyEventBase { override protected void Init() { SetCoolDown(40); m_SoundSet = "SpookyArea_RunOnConcrete_SoundSet"; m_Surfaces = {"stone", "gravel", "concrete", "wood", "asphalt", "tiles", "textile"}; } override protected void Do(PlayerBase player) { //put additional code here } } //------------------------------------------------------------- class SpookyEventRustle : SpookyEventBase { override protected void Init() { SetCoolDown(60); m_SoundSet = "SpookyArea_IntenseFoliageRustle_SoundSet"; m_Surfaces = {"grass", "dirt", "forest", "soil"}; } override protected bool CanDo(PlayerBase player, TStringVectorMap surfaceTypes) { return !player.IsSoundInsideBuilding(); } override protected void Do(PlayerBase player) { vector soundPos = GetSoundPos(player); string secondarySoundSet = "SpookyArea_Hedgehog_SoundSet"; if (Math.RandomBool()) { secondarySoundSet = "SpookyArea_Badger_Voice_SoundSet"; } EffectSound sound = SEffectManager.PlaySound(secondarySoundSet, soundPos); sound.SetAutodestroy( true ); } } //------------------------------------------------------------- //------------------------------------------------------------- //------------------------------------------------------------- class SpookyEventBase { //internal params, do not set manually protected float m_PerformedTimestamp;//internal, marks the time this event was last performed so that it can guruantee the cooldown period before playing it again protected int m_Cooldown;//how much time needs to elapse between this event can be performed again in seconds //the params bellow can be set in the 'Init' method protected string m_SoundSet;//if set, will play this soundset when performing the event protected ref TStringArray m_Surfaces;//if set, the player needs to detect this surface for the event to be considered valid protected vector m_MatchingSurfacePos;//position of the first matching surface void SpookyEventBase() { Init(); } protected void Init(); protected vector GetMatchingSurfacePos(TStringArray surfaces, TStringVectorMap gatheredSurfaces) { for (int i = 0; i < gatheredSurfaces.Count(); i++) { string currSurface = gatheredSurfaces.GetKey(i); foreach (string s:surfaces) { if (currSurface.Contains(s)) return gatheredSurfaces.Get(currSurface); } } return vector.Zero; } protected void SetCoolDown(float secs) { m_Cooldown = secs * 1000; } protected bool HasSurfaces() { return (m_Surfaces && m_Surfaces.Count() > 0); } protected bool CanDo(PlayerBase player, TStringVectorMap surfaceTypes) { return true; } protected void Do(PlayerBase player); //internal, do not override, use 'CanDo' instead bool CanPerform(PlayerBase player, float currentTime, TStringVectorMap surfaceTypes) { bool surfaceCheckResult = 1; if (HasSurfaces()) { m_MatchingSurfacePos = GetMatchingSurfacePos(m_Surfaces, surfaceTypes); surfaceCheckResult = m_MatchingSurfacePos != vector.Zero; } return ( currentTime > (m_PerformedTimestamp + m_Cooldown) && surfaceCheckResult && CanDo(player, surfaceTypes) ); } //internal, do not override, use 'Do' instead void Perform(PlayerBase player, float currentTime, TStringVectorMap gatheredSurfaces) { #ifdef DIAG_DEVELOPER if (DiagMenu.GetBool(DiagMenuIDs.TRIGGER_DEBUG)) Print("Performing " + this); #endif m_PerformedTimestamp = currentTime; if (m_SoundSet) { vector soundPos = GetSoundPos(player); EffectSound sound = SEffectManager.PlaySound(m_SoundSet, soundPos); sound.SetAutodestroy( true ); #ifdef DIAG_DEVELOPER if (DiagMenu.GetBool(DiagMenuIDs.TRIGGER_DEBUG)) Debug.DrawSphere(soundPos , 0.15,Colors.YELLOW, ShapeFlags.NOZBUFFER); #endif } Do(player); } protected vector GetSoundPos(PlayerBase player) { vector soundPos; if (HasSurfaces()) { soundPos = m_MatchingSurfacePos; } else { float distance = Math.RandomFloatInclusive(5,15); vector randomDir = vector.RandomDir2D() * distance; vector playerPos = player.GetPosition(); soundPos = playerPos + randomDir; } return soundPos; } } //---------------------------------------------------------------------------------------------------------- class SpookyTriggerEventsHandler { protected ref array m_SoundEvents; protected PlayerBase m_Player; protected float m_TimeAccu; protected const float CONSECUTIVE_EVENTS_COOLDOWN = 20;//min delay in seconds before two events protected const float EVENT_CHECK_FREQUENCY = 2;//when not in cooldown, the rate at which we query the events protected const float FIRST_EVENT_CHECK_DELAY = 15;//the delay between the first event check when we first enter the contaminated area protected const float SURFACE_CHECK_POINT_DISTANCE = 2;//additional checks for surface are performed at this distance from the player protected float m_NextEventCheck = FIRST_EVENT_CHECK_DELAY; void SpookyTriggerEventsHandler(notnull PlayerBase player) { if (!m_SoundEvents) { m_SoundEvents = new array(); RegisterEvents(); } m_Player = player; } void ~SpookyTriggerEventsHandler() { m_SoundEvents = null; } protected void RegisterEvents() { m_SoundEvents.Insert(new SpookyEventWind()); m_SoundEvents.Insert(new SpookyEventWhisper()); m_SoundEvents.Insert(new SpookyEventSteps()); m_SoundEvents.Insert(new SpookyEventRustle()); } void Update(float deltaTime) { m_TimeAccu += deltaTime; if (m_TimeAccu > m_NextEventCheck) { if (SelectEvent()) m_NextEventCheck = CONSECUTIVE_EVENTS_COOLDOWN; else m_NextEventCheck = EVENT_CHECK_FREQUENCY; m_TimeAccu = 0; } } protected void GatherSurfaces(notnull TStringVectorMap gatheredGurfaces) { gatheredGurfaces.Clear(); vector playerPos = m_Player.GetPosition(); TVectorArray positions = new TVectorArray(); positions.Insert(playerPos); positions.Insert(playerPos + ("0 0 1" * SURFACE_CHECK_POINT_DISTANCE)); positions.Insert(playerPos + ("0 0 -1" * SURFACE_CHECK_POINT_DISTANCE)); positions.Insert(playerPos + ("1 0 0" * SURFACE_CHECK_POINT_DISTANCE)); positions.Insert(playerPos + ("-1 0 0" * SURFACE_CHECK_POINT_DISTANCE)); foreach (vector pos : positions) { string surfaceType; GetGame().SurfaceGetType3D(pos[0],pos[1], pos[2], surfaceType); if (!gatheredGurfaces.Contains(surfaceType)) gatheredGurfaces.Insert(surfaceType, pos); } #ifdef DIAG_DEVELOPER if (DiagMenu.GetBool(DiagMenuIDs.TRIGGER_DEBUG)) { foreach (vector p:positions) Debug.DrawLine(p, p + "0 10 0", COLOR_BLUE,ShapeFlags.NOZBUFFER); } #endif } protected bool SelectEvent() { TStringVectorMap gatheredSurfaces = new TStringVectorMap(); GatherSurfaces(gatheredSurfaces);//this can be optimized by calling this on demand from an event, as none events might be eligible at this point, so we might be doing this in vain #ifdef DIAG_DEVELOPER if (DiagMenu.GetBool(DiagMenuIDs.TRIGGER_DEBUG)) { for(int i = 0; i < gatheredSurfaces.Count(); i++) { Print(gatheredSurfaces.GetKey(i)); Print(gatheredSurfaces.Get(gatheredSurfaces.GetKey(i))); } Print("--------------------------------------------------------------------"); } #endif array validEvents = new array(); float currentTime = GetGame().GetTime(); foreach (SpookyEventBase spookyEvent:m_SoundEvents) { if (spookyEvent.CanPerform(m_Player, currentTime, gatheredSurfaces)) validEvents.Insert(spookyEvent); } //validEvents.Debug(); SpookyEventBase selectedEvent; if (validEvents.Count() > 0) { int randIndex = Math.RandomIntInclusive(0, validEvents.Count() - 1); selectedEvent = validEvents[randIndex]; } if (selectedEvent) { selectedEvent.Perform(m_Player, currentTime, gatheredSurfaces); return true; } return false; } } //!this entity gets attached to each player while present in the spooky area class SpookyPlayerStalker : ScriptedEntity { protected ref UniversalTemperatureSource m_UTSource; protected ref UniversalTemperatureSourceSettings m_UTSSettings; protected ref UniversalTemperatureSourceLambdaConstant m_UTSLConstant; override void EEInit() { super.EEInit(); if (GetGame().IsServer() || !GetGame().IsMultiplayer()) { m_UTSSettings = new UniversalTemperatureSourceSettings(); m_UTSSettings.m_Updateable = true; m_UTSSettings.m_UpdateInterval = 3; m_UTSSettings.m_TemperatureItemCap = -20; m_UTSSettings.m_TemperatureCap = -20; m_UTSSettings.m_RangeFull = 2; m_UTSSettings.m_RangeMax = 2; m_UTSSettings.m_ManualUpdate = false; m_UTSSettings.m_IsWorldOverriden = false; m_UTSLConstant = new UniversalTemperatureSourceLambdaConstant(); m_UTSource = new UniversalTemperatureSource(this, m_UTSSettings, m_UTSLConstant); m_UTSource.SetActive(true); } } }