Source: common/colorFades.js

/**
 * Some functions to make color fades on GUI elements (f.e. used for hero and group icons)
 */

/**
 * Used for storing information about color fades
 */
var g_ColorFade = {};

var g_FadeAttackUnit = {

	// How many ticks should first blinking phase be
	"blinkingTicks": 50,

	// How often should the color be changed during the blinking phase
	"blinkingChangeInterval": 5,

	// How fast should blue and green part of the color change
	"gbcolorChangeRate": 3,

	// When should the fade out start using the opacity
	"fadeOutStart": 100,

	// How fast should opacity change
	"opacityChangeRate": 3
};

function getInitColorFadeRGB()
{
	return { "r": 0, "g": 0, "b": 0, "o": 100 };
}

/**
 * Starts fading a color of a GUI object using the sprite argument.
 *
 * @param {string} name - name of the object which color should be faded
 * @param {number} tickInterval - interval in ms when the next color change should be made
 * @param {number} duration - maximum duration of the complete fade (if 0 it runs until it is stopped)
 * @param {function} fun_colorTransform - function which transform the colors
 * @param {boolean} [restartable] - if false, the fade can not be restarted; default: true
 * @param {function} [fun_smoothRestart] - a function, which returns a smooth tick counter, if the fade should be started;
 *                   arguments: [var data]; must return false, if smooth restart was not possible and true, if it was ok
 */
function startColorFade(name, tickInterval, duration, fun_colorTransform, restartable = true, fun_smoothRestart = false)
{
	var overlay = Engine.GetGUIObjectByName(name);
	if (!overlay)
		return;

	// check, if fade overlay was started just now
	if (!isColorFadeRunning(name))
	{
		overlay.hidden = false;

		// store the values into a var to make it more flexible (can be changed from every function)
		g_ColorFade[name] = {
			"timerId": -1,
			"tickInterval": tickInterval,
			"duration": duration,
			"fun_colorTransform": fun_colorTransform,
			"restartable": restartable,
			"fun_smoothRestart": fun_smoothRestart,
			"tickCounter": 0,
			"justStopAtExternCall": duration == 0,
			"stopFade": false,
			"rgb": getInitColorFadeRGB()
		};

		// start with fading
		fadeColorTick(name);
	}
	else if (restartable)
	{
		restartColorFade(name);
		return;
	}
}

/**
 * Changes the color on tick.
 *
 * @param {string} name - name of the object which color should be faded
 */
function fadeColorTick(name)
{
	if (!isColorFadeRunning(name))
		return;

	var overlay = Engine.GetGUIObjectByName(name);
	if (!overlay)
		return;
	var data = g_ColorFade[name];

	// change the color
	data.fun_colorTransform(data);

	overlay.sprite = "color:" + rgbToGuiColor(data.rgb, data.rgb.o);

	// recusive call, if duration is positive
	if (!data.stopFade && (data.justStopAtExternCall || data.duration - (data.tickInterval * data.tickCounter) > 0))
	{
		var id = setTimeout(function() { fadeColorTick(name); }, data.tickInterval);
		data.timerId = id;
		data.tickCounter++;
	}
	else
	{
		overlay.hidden = true;
		stopColorFade(name);
	}
}

/**
 * Checks, if a color fade on that object is running.
 *
 * @param {string} name - name of the object which color fade should be checked
 * @returns {boolean} - true a running fade was found
 */
function isColorFadeRunning(name)
{
	return name in g_ColorFade;
}

/**
 * Stops fading a color.
 *
 * @param {string} name - name of the object which color fade should be stopped
 * @param {boolean} hideOverlay - hides the overlay, if true [default: true]
 * @returns {boolean} true a running fade was stopped
 */
function stopColorFade(name, hideOverlay = true)
{
	// check, if a color fade is running
	if (!isColorFadeRunning(name))
		return false;

	// delete the timer
	clearTimeout(g_ColorFade[name].timerId);
	delete g_ColorFade[name];

	// get the overlay and hide it
	if (hideOverlay)
	{
		var overlay = Engine.GetGUIObjectByName(name);
		if (overlay)
			overlay.hidden = true;
	}
	return true;
}

/**
 * Restarts a color fade using the parameters stored in g_ColorFade.
 * @param {string} - Name of the object whose color should be faded.
 */
function restartColorFade(name)
{
	let data = g_ColorFade[name];

	// If fade can be restarted smoothly, stop it, and restart it.
	if (!data.fun_smoothRestart)
	{
		stopColorFade(name, false);
		startColorFade(name, data.changeInterval, data.duration, data.fun_colorTransform, data.restartable, data.fun_smoothRestart);
	}
	// Call was too late.
	else if (!data.fun_smoothRestart(data))
	{
		// Set RGB start values.
		data.rgb = getInitColorFadeRGB();
		data.tickCounter = 0;
	}
}

function colorFade_attackUnit(data)
{
	var rgb = data.rgb;

	// init color
	if (data.tickCounter == 0)
		rgb.r = 175;

	// blinking
	if (data.tickCounter < g_FadeAttackUnit.blinkingTicks)
	{
		// slow that process down
		if (data.tickCounter % g_FadeAttackUnit.blinkingChangeInterval != 0)
			return;

		rgb.g = rgb.g == 0 ? 255 : 0;
	}
	// wait a short time and then color fade from red to grey to nothing
	else if (data.tickCounter >= g_FadeAttackUnit.blinkingTicks + g_FadeAttackUnit.blinkingChangeInterval)
	{
		rgb.g += Math.round(g_FadeAttackUnit.gbcolorChangeRate * Math.sqrt(data.tickCounter - g_FadeAttackUnit.blinkingTicks));
		if (rgb.g > 255)
			rgb.g = 255;

		// start with fading it out
		if (rgb.g > g_FadeAttackUnit.fadeOutStart)
			rgb.o = rgb.o > g_FadeAttackUnit.opacityChangeRate ? rgb.o -= g_FadeAttackUnit.opacityChangeRate : 0;
		// check for end
		if (rgb.o == 0)
			data.stopFade = true;
	}
	rgb.b = rgb.g;
}

function smoothColorFadeRestart_attackUnit(data)
{
	// check, if in blinking phase
	if (data.tickCounter < g_FadeAttackUnit.blinkingTicks)
	{
		data.tickCounter %= g_FadeAttackUnit.blinkingChangeInterval * 2;
		return true;
	}
	return false;
}