DIFF

BunkerBroadcastScheduler.c

class BunkerBroadcastScheduler : Managed
{
	protected bool m_IsActivePeriod;
	protected int m_BatchSelectionTimer;
	protected bool m_BatchSelected;
	protected int m_BatchLifetimeTimer;
	
	protected ref TStringArray m_ReportData;
	protected ref TStringArray m_MorseOutputData;

	protected BunkerBroadcastHandler m_Handler;
	protected BunkerBroadcastConfigInstance m_Config;
	protected BunkerBroadcastPersistenceStorage m_PersitenceStorage;
	
	protected ref BunkerComponentsArray m_ActiveBunkers;
	protected ref ScriptInvoker m_OnAskForNewBunkers;
	
	private int m_LastReportHour, m_LastReportMinute;
	private int m_LastReportDifferenceMinutes;
	
	private bool m_ForceBroadcast;
	
	private static ref BunkerBroadcastScheduler m_Instance;
	
	private ref WorldTimeTracker m_WorldTimeTracker;
	
	static BunkerBroadcastScheduler GetInstance(notnull BunkerBroadcastHandler handler)
	{
		if (!m_Instance)
			m_Instance = new BunkerBroadcastScheduler(handler);
		
		return m_Instance;
	}
	
	private void BunkerBroadcastScheduler(notnull BunkerBroadcastHandler handler)
	{
		m_Handler = handler;
	
		m_PersitenceStorage = BunkerBroadcastPersistenceStorage.GetInstance();
		m_ActiveBunkers 	= new BunkerComponentsArray();
		m_ReportData 		= new TStringArray();
		m_MorseOutputData   = new TStringArray();

		m_OnAskForNewBunkers = new ScriptInvoker();

		m_Config = CfgBunkerBroadcastHandler.GetData();
		
		m_WorldTimeTracker = new WorldTimeTracker();
		
		if (m_PersitenceStorage.IsLoaded())
		{
			LoadLastReportTimeFromStorage();
			m_LastReportDifferenceMinutes = GetDifferenceFromLastReport();
			PrintToRPT("Difference in minutes: " + m_LastReportDifferenceMinutes);
			// TODO@RR force broadcast on server restart if time condition passes
			//if (m_LastReportDifferenceMinutes > THRESHOLD(time calculated from the overall period and number of broadcasts))
			if (m_LastReportDifferenceMinutes > 10)
				m_ForceBroadcast = true;
		}
		else
			PrintToRPT(string.Format("storage does not exist - hour:%1 minute:%2", m_LastReportHour, m_LastReportMinute));
		
		if (m_Handler)
			m_Handler.GetOnActiveBunkersSelected().Insert(BatchSelected);
	}
	
	void ~BunkerBroadcastScheduler()
	{
		if (m_Handler)
			m_Handler.GetOnActiveBunkersSelected().Remove(BatchSelected);
	}
	
	// --------------------------------------------------------------------------------
	
	ScriptInvoker GetOnAskForNewBunkers()
	{
		return m_OnAskForNewBunkers;
	}
	
	// --------------------------------------------------------------------------------

	void CheckActivePeriod()
	{
	    int year, month, day, hour, minute;
	    GetGame().GetWorld().GetDate(year, month, day, hour, minute);
	
	    // Convert to minutes from midnight for simpler comparisons
	    int now = hour * 60 + minute;
	
	    foreach (BunkerBroadcastPeriod period : m_Config.schedulerSettings.periods)
	    {
	        int start = period.GetStartHour() * 60 + period.GetStartMinute();
	        int end   = period.GetEndHour() * 60 + period.GetEndMinute();
	
	        bool active = false;
	
	        if (start < end)
	        {
	            // Same-day range: [start, end]
	            active = (now >= start) && (now <= end);
	        }
	        else if (start > end)
	        {
	            // Wrap-around range: [start, 24h) U [0, end]
	            active = (now >= start) || (now <= end);
	        }
	        else
	        {
	            // start == end
	            // If minutes equal, interpret as full-day active; otherwise zero-length window (inactive)
	            active = true;
	        }
	
	        if (active)
	        {
	            m_IsActivePeriod = true;
	            return;
	        }
	    }
	
	    m_IsActivePeriod = false;
	}

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

	bool IsActivePeriod()
	{
		return m_IsActivePeriod;
	}

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

	void OnUpdate(float timeslice)
	{
		if (!m_IsActivePeriod)
		{
			m_BatchSelected = false;
			return;
		}
		
		int timeTrackerDeltaMinutes;
		m_WorldTimeTracker.TryPollDelta(timeslice, timeTrackerDeltaMinutes);
		m_BatchLifetimeTimer += timeTrackerDeltaMinutes;
		//! batch lifetime control
		if (m_BatchLifetimeTimer >= m_Config.schedulerSettings.batchLifetimeLength)
		{
			m_BatchSelected = false;
			SelectNewBunkers();
			m_BatchLifetimeTimer = 0;
		}

		//! batch not yet selected and distributed
		if (!m_BatchSelected)
		{
			m_BatchSelectionTimer += timeTrackerDeltaMinutes;
			//! fixed bunker selection call each 5mins if no batch is selected
			if(m_BatchSelectionTimer >= 5)
			{
				#ifdef ENABLE_LOGGING
				Log("Enforcing batch selection - inactivity");
				#endif
				m_BatchSelectionTimer = 0;
				SelectNewBunkers();
			}
		}

		if (m_ForceBroadcast || m_LastReportDifferenceMinutes >= m_Config.schedulerSettings.reportInterval)
		{
			if (SendReport(m_ReportData, m_MorseOutputData))
				UpdatePersistentData();
			
			m_ForceBroadcast = false;
		}
		
		m_LastReportDifferenceMinutes = GetDifferenceFromLastReport();
	}

	// --------------------------------------------------------------------------------
	
	private void SelectNewBunkers()
	{
		m_OnAskForNewBunkers.Invoke();
	}

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

	private void BatchSelected()
	{
		m_BatchSelected = true;
		m_ActiveBunkers = m_Handler.GetActiveBunkers();
		UpdatePersistentData();

		m_ReportData = PrepareReport();
		if (SendReport(m_ReportData, m_MorseOutputData))
			UpdatePersistentData();
	}

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

	private TStringArray PrepareReport()
	{
		TStringArray results = new TStringArray();
		foreach (BunkerBroadcastComponentBase active : m_ActiveBunkers)
		{
			TIntArray mapCoords = MapNavigationBehaviour.OrderedPositionNumbersFromGridCoords(active.GetPosition());
			TIntArray xCoords = mapCoords.Slice(0, 2);
			TIntArray yCoords = mapCoords.Slice(3, 5);
			TStringArray xCoordsString = new TStringArray();
			TStringArray yCoordsString = new TStringArray();
			foreach(int ix : xCoords)
				xCoordsString.Insert(ix.ToString());
			
			foreach(int iy : yCoords)
				yCoordsString.Insert(iy.ToString());
			
			string mapCoordsConverted;
			mapCoordsConverted = string.Join("", xCoordsString);
			mapCoordsConverted += string.Join("", yCoordsString);
			results.Insert(mapCoordsConverted);
			results.Insert(active.GetLockCode());
		}
		
		return results;
	}
	
	// --------------------------------------------------------------------------------
	
	private bool SendReport(TStringArray data, TStringArray morseData)
	{
		if (data.Count() == 0)
		{
			//#ifdef ENABLE_LOGGING
			//Log("Nothing to report.");
			//#endif
			return false;
		}
		
		BunkerBroadcastManager bbm = GetGame().GetBunkerBroadcastManager();
		if (bbm) 
		{			
			bbm.SetMessageSpeed(
				m_Config.playbackSettings.dotDuration,
				m_Config.playbackSettings.dashDuration,
				m_Config.playbackSettings.symbolGapDuration,
				m_Config.playbackSettings.wordGapDuration,
			);
			bbm.SetPauseBetweenBunkers(m_Config.playbackSettings.pauseBetweenBunkers);
			bbm.SetBroadcastBunkerRepeatCount(m_Config.playbackSettings.bunkerRepetitions);
			bbm.SetBroadcastTotalRepeatCount(m_Config.playbackSettings.messageRepetitions);
		
			bbm.BroadcastTextAsMorse(m_ReportData, m_MorseOutputData);
			
			#ifdef ENABLE_LOGGING
			Log("Reporting active bunkers:");
			int i;
			for (i = 0; i < data.Count(); i += 2)
			{
				Log(string.Format("position:%1 code:%2", data[i], data[i+1]), true);
			}
			for (i = 0; i < m_MorseOutputData.Count(); i += 2)
			{
				Log(string.Format("Morse position:%1 code:%2", m_MorseOutputData[i], m_MorseOutputData[i+1]), true);
			}
			
			Log("Report sent");
			#endif
			
			return true;
		}

		return false;
	}

	// --------------------------------------------------------------------------------
	
	private void UpdatePersistentData()
	{
		int year, month, day, hour, minute;
		g_Game.GetWorld().GetDate(year, month, day, hour, minute);

		m_PersitenceStorage.m_SchedulerData.m_LastReportHour 	= hour;
		m_PersitenceStorage.m_SchedulerData.m_LastReportMinute 	= minute;
		
		TIntStringMap activeBunkerTempMap = new TIntStringMap();
		m_PersitenceStorage.m_HandlerData.m_LastActiveBunkers.Clear();
		foreach (BunkerBroadcastComponentBase bunker : m_ActiveBunkers)
		{
			activeBunkerTempMap.Insert(bunker.GetId(), bunker.GetLockCode());
		}
		
		m_PersitenceStorage.m_HandlerData.m_LastActiveBunkers.Copy(activeBunkerTempMap);
		m_PersitenceStorage.Save();

		m_LastReportHour 	= hour;
		m_LastReportMinute 	= minute;
		
		m_LastReportDifferenceMinutes = 0.0;
	}

	// --------------------------------------------------------------------------------
	
	private void LoadLastReportTimeFromStorage()
	{
		m_LastReportHour 	= m_PersitenceStorage.m_SchedulerData.m_LastReportHour;
		m_LastReportMinute 	= m_PersitenceStorage.m_SchedulerData.m_LastReportMinute;
		PrintToRPT(string.Format("storage loaded - hour:%1 minute:%2", m_LastReportHour, m_LastReportMinute));
	}

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

	private int GetDifferenceFromLastReport()
	{	
		int year, month, day, hour, minute;
		GetGame().GetWorld().GetDate(year, month, day, hour, minute);
		
		int prevTotalMinutes = m_LastReportHour * 60 + m_LastReportMinute;
		int currTotalMinutes = hour * 60 + minute;
		
		int minuteDiff = currTotalMinutes - prevTotalMinutes;
		
		// Handle wrap-around (e.g., prev=23:55, curr=00:10 → +15m)
		if (minuteDiff < -720) // -12 hours or more, must be a forward rollover
		    minuteDiff += 1440; // add 1 day
		else if (minuteDiff > 720) // +12 hours or more, must be a backward rollover
		    minuteDiff -= 1440; // subtract 1 day

		return minuteDiff;
	}
	
	#ifdef ENABLE_LOGGING
	private static void Log(string message, bool indented = false)
	{
		#ifdef DIAG_DEVELOPER
		if (BunkerBroadcastHandler.m_LoggingEnabled || LogManager.IsRadioGameplayLogEnabled())
		#else
		if (LogManager.IsRadioGameplayLogEnabled())
		#endif
		{
			string indentation = "";
			if (indented)
				indentation = " \\__";

			Debug.RadioGameplayLog(string.Format("%1 %2", indentation, message), m_Instance.ClassName(), "n/a", "n/a", "n/a");
		}
	}
	#endif	
}