Source: Resistance.js

/**
 * @class
 */
function Resistance() {}

/**
 * Builds a RelaxRNG schema of possible attack effects.
 *
 * @return {string} - RelaxNG schema string.
 */
Resistance.prototype.BuildResistanceSchema = function()
{
	return "" +
		"<oneOrMore>" +
			"<choice>" +
				"<element name='Damage'>" +
					"<oneOrMore>" +
						"<element a:help='Resistance against any number of damage types affecting health.'>" +
							"<anyName/>" +
							"<ref name='nonNegativeDecimal'/>" +
						"</element>" +
					"</oneOrMore>" +
				"</element>" +
				"<element name='Capture' a:help='Resistance against Capture attacks.'>" +
					"<ref name='nonNegativeDecimal'/>" +
				"</element>" +
				"<element name='ApplyStatus' a:help='Resistance against StatusEffects.'>" +
					"<oneOrMore>" +
						"<element a:help='Resistance against any number of status effects.'>" +
							"<anyName/>" +
							"<interleave>" +
								"<optional>" +
									"<element name='Duration' a:help='The reduction in duration of the status. The normal duration time is multiplied by this factor.'>" +
										"<ref name='nonNegativeDecimal'/>" +
									"</element>" +
								"</optional>" +
								"<optional>" +
									"<element name='BlockChance' a:help='The chance of blocking the status. In the interval [0,1].'><ref name='nonNegativeDecimal'/></element>" +
								"</optional>" +
							"</interleave>" +
						"</element>" +
					"</oneOrMore>" +
				"</element>" +
			"</choice>" +
		"</oneOrMore>";
};

Resistance.prototype.Schema =
	"<a:help>Controls the damage resistance of the unit.</a:help>" +
	"<a:example>" +
		"<Foundation>" +
			"<Damage>" +
				"<Hack>10.0</Hack>" +
				"<Pierce>0.0</Pierce>" +
				"<Crush>5.0</Crush>" +
			"</Damage>" +
			"<Capture>10</Capture>" +
		"</Foundation>" +
		"<Entity>" +
			"<Damage>" +
				"<Poison>5</Poison>" +
			"</Damage>" +
		"</Entity>" +
	"</a:example>" +
	"<zeroOrMore>" +
		"<choice>" +
			"<element name='Foundation' a:help='Resistance of an unfinished structure (i.e. a foundation).'>" +
				Resistance.prototype.BuildResistanceSchema() +
			"</element>" +
			"<element name='Entity' a:help='Resistance of an entity.'>" +
				Resistance.prototype.BuildResistanceSchema() +
			"</element>" +
		"</choice>" +
	"</zeroOrMore>";

Resistance.prototype.Init = function()
{
	this.invulnerable = false;
	this.attackers = new Set();
};

Resistance.prototype.IsInvulnerable = function()
{
	return this.invulnerable;
};

/**
 * @param {number} attacker - The entity ID of the attacker to add.
 * @return {boolean} - Whether the attacker was added sucessfully.
 */
Resistance.prototype.AddAttacker = function(attacker)
{
	if (this.attackers.has(attacker))
		return false;

	this.attackers.add(attacker);
	return true;
};

/**
 * @param {number} attacker - The entity ID of the attacker to remove.
 * @return {boolean} - Whether the attacker was attacking us previously.
 */
Resistance.prototype.RemoveAttacker = function(attacker)
{
	return this.attackers.delete(attacker);
};

Resistance.prototype.SetInvulnerability = function(invulnerability)
{
	this.invulnerable = invulnerability;
	Engine.PostMessage(this.entity, MT_InvulnerabilityChanged, { "entity": this.entity, "invulnerability": invulnerability });
};

/**
 * Calculate the effective resistance of an entity to a particular effect.
 * ToDo: Support resistance against status effects.
 * @param {string} effectType - The type of attack effect the resistance has to be calculated for (e.g. "Damage", "Capture").
 * @return {Object} - An object of the type { "Damage": { "Crush": number, "Hack": number }, "Capture": number }.
 */
Resistance.prototype.GetEffectiveResistanceAgainst = function(effectType)
{
	let ret = {};

	let template = this.GetResistanceOfForm(Engine.QueryInterface(this.entity, IID_Foundation) ? "Foundation" : "Entity");
	if (template[effectType])
		ret[effectType] = template[effectType];

	return ret;
};

/**
 * Get all separate resistances for showing in the GUI.
 * @return {Object} - All resistances ordered by type.
 */
Resistance.prototype.GetFullResistance = function()
{
	let ret = {};
	for (let entityForm in this.template)
		ret[entityForm] = this.GetResistanceOfForm(entityForm);

	return ret;
};

/**
 * Get the resistance of a particular type, i.e. Foundation or Entity.
 * @param {string} entityForm - The form of the entity to query.
 * @return {Object} - An object containing the resistances.
 */
Resistance.prototype.GetResistanceOfForm = function(entityForm)
{
	let ret = {};
	let template = this.template && this.template[entityForm];
	if (!template)
		return ret;

	if (template.Damage)
	{
		ret.Damage = {};
		for (let damageType in template.Damage)
			ret.Damage[damageType] = ApplyValueModificationsToEntity("Resistance/" + entityForm + "/Damage/" + damageType, +this.template[entityForm].Damage[damageType], this.entity);
	}

	if (template.Capture)
		ret.Capture = ApplyValueModificationsToEntity("Resistance/" + entityForm + "/Capture", +this.template[entityForm].Capture, this.entity);

	if (template.ApplyStatus)
	{
		ret.ApplyStatus = {};
		for (let effect in template.ApplyStatus)
			ret.ApplyStatus[effect] = {
				"duration": ApplyValueModificationsToEntity("Resistance/" + entityForm + "/ApplyStatus/" + effect + "/Duration", +(template.ApplyStatus[effect].Duration || 1), this.entity),
				"blockChance": ApplyValueModificationsToEntity("Resistance/" + entityForm + "/ApplyStatus/" + effect + "/BlockChance", +(template.ApplyStatus[effect].BlockChance || 0), this.entity)
			};
	}

	return ret;
};

Resistance.prototype.OnOwnershipChanged = function(msg)
{
	if (msg.to === INVALID_PLAYER)
		for (let attacker of this.attackers)
			Engine.QueryInterface(attacker, IID_Attack)?.StopAttacking("TargetInvalidated");
};


function ResistanceMirage() {}
ResistanceMirage.prototype.Init = function(cmpResistance)
{
	this.invulnerable = cmpResistance.invulnerable;
	this.isFoundation = !!Engine.QueryInterface(cmpResistance.entity, IID_Foundation);
	this.resistanceOfForm = {};
	for (const entityForm in cmpResistance.template)
		this.resistanceOfForm[entityForm] = cmpResistance.GetResistanceOfForm(entityForm);
	this.attackers = new Set();
};

ResistanceMirage.prototype.IsInvulnerable = Resistance.prototype.IsInvulnerable;
ResistanceMirage.prototype.AddAttacker = Resistance.prototype.AddAttacker;
ResistanceMirage.prototype.RemoveAttacker = Resistance.prototype.RemoveAttacker;

ResistanceMirage.prototype.GetEffectiveResistanceAgainst = function(entityForm)
{
	return this.GetResistanceOfForm(this.isFoundation ? "Foundation" : "Entity");
};

ResistanceMirage.prototype.GetResistanceOfForm = function(entityForm)
{
	return this.resistanceOfForm[entityForm] || {};
};

ResistanceMirage.prototype.GetFullResistance = function()
{
	return this.resistanceOfForm;
};

Engine.RegisterGlobal("ResistanceMirage", ResistanceMirage);

Resistance.prototype.Mirage = function()
{
	const mirage = new ResistanceMirage();
	mirage.Init(this);
	return mirage;
};

Engine.RegisterComponentType(IID_Resistance, "Resistance", Resistance);