Index: ps/trunk/binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Attack.js (revision 20133)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Attack.js (revision 20134)
@@ -1,613 +1,612 @@
function Attack() {}
var g_AttackTypes = ["Melee", "Ranged", "Capture"];
Attack.prototype.bonusesSchema =
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
Attack.prototype.preferredClassesSchema =
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"";
Attack.prototype.restrictedClassesSchema =
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
"";
Attack.prototype.Schema =
"Controls the attack abilities and strengths of the unit." +
"" +
"" +
"10.0" +
"0.0" +
"5.0" +
"4.0" +
"1000" +
"" +
"" +
"pers" +
"Infantry" +
"1.5" +
"" +
"" +
"Cavalry Melee" +
"1.5" +
"" +
"" +
"Champion" +
"Cavalry Infantry" +
"" +
"" +
"0.0" +
"10.0" +
"0.0" +
"44.0" +
"20.0" +
"15.0" +
"800" +
"1600" +
"50.0" +
"2.5" +
"" +
"" +
"Cavalry" +
"2" +
"" +
"" +
"Champion" +
"" +
"Circular" +
"20" +
"false" +
"0.0" +
"10.0" +
"0.0" +
"" +
"" +
"" +
"1000.0" +
"0.0" +
"0.0" +
"4.0" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" + // TODO: it shouldn't be stretched
"" +
"" +
Attack.prototype.bonusesSchema +
Attack.prototype.preferredClassesSchema +
Attack.prototype.restrictedClassesSchema +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
""+
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
Attack.prototype.bonusesSchema +
Attack.prototype.preferredClassesSchema +
Attack.prototype.restrictedClassesSchema +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
Attack.prototype.bonusesSchema +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" + // TODO: it shouldn't be stretched
"" +
"" +
Attack.prototype.bonusesSchema +
Attack.prototype.preferredClassesSchema +
Attack.prototype.restrictedClassesSchema +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" + // TODO: how do these work?
Attack.prototype.bonusesSchema +
Attack.prototype.preferredClassesSchema +
Attack.prototype.restrictedClassesSchema +
"" +
"" +
"";
Attack.prototype.Init = function()
{
};
Attack.prototype.Serialize = null; // we have no dynamic state to save
Attack.prototype.GetAttackTypes = function(wantedTypes)
{
let types = g_AttackTypes.filter(type => !!this.template[type]);
if (!wantedTypes)
return types;
let wantedTypesReal = wantedTypes.filter(wtype => wtype.indexOf("!") != 0);
return types.filter(type => wantedTypes.indexOf("!" + type) == -1 &&
(!wantedTypesReal || !wantedTypesReal.length || wantedTypesReal.indexOf(type) != -1));
};
Attack.prototype.GetPreferredClasses = function(type)
{
if (this.template[type] && this.template[type].PreferredClasses &&
this.template[type].PreferredClasses._string)
return this.template[type].PreferredClasses._string.split(/\s+/);
return [];
};
Attack.prototype.GetRestrictedClasses = function(type)
{
if (this.template[type] && this.template[type].RestrictedClasses &&
this.template[type].RestrictedClasses._string)
return this.template[type].RestrictedClasses._string.split(/\s+/);
return [];
};
Attack.prototype.CanAttack = function(target, wantedTypes)
{
let cmpFormation = Engine.QueryInterface(target, IID_Formation);
if (cmpFormation)
return true;
let cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position);
let cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
if (!cmpThisPosition || !cmpTargetPosition || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld())
return false;
let cmpIdentity = QueryMiragedInterface(target, IID_Identity);
if (!cmpIdentity)
return false;
let targetClasses = cmpIdentity.GetClassesList();
if (targetClasses.indexOf("Domestic") != -1 && this.template.Slaughter &&
(!wantedTypes || !wantedTypes.filter(wType => wType.indexOf("!") != 0).length))
return true;
let cmpEntityPlayer = QueryOwnerInterface(this.entity);
let cmpTargetPlayer = QueryOwnerInterface(target);
if (!cmpTargetPlayer || !cmpEntityPlayer)
return false;
let types = this.GetAttackTypes(wantedTypes);
let entityOwner = cmpEntityPlayer.GetPlayerID();
let targetOwner = cmpTargetPlayer.GetPlayerID();
let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
// Check if the relative height difference is larger than the attack range
// If the relative height is bigger, it means they will never be able to
// reach each other, no matter how close they come.
let heightDiff = Math.abs(cmpThisPosition.GetHeightOffset() - cmpTargetPosition.GetHeightOffset());
for (let type of types)
{
if (type != "Capture" && !cmpEntityPlayer.IsEnemy(targetOwner))
continue;
if (type == "Capture" && (!cmpCapturable || !cmpCapturable.CanCapture(entityOwner)))
continue;
if (heightDiff > this.GetRange(type).max)
continue;
let restrictedClasses = this.GetRestrictedClasses(type);
if (!restrictedClasses.length)
return true;
if (!MatchesClassList(targetClasses, restrictedClasses))
return true;
}
return false;
};
/**
* Returns null if we have no preference or the lowest index of a preferred class.
*/
Attack.prototype.GetPreference = function(target)
{
let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
if (!cmpIdentity)
return undefined;
let targetClasses = cmpIdentity.GetClassesList();
let minPref = null;
for (let type of this.GetAttackTypes())
{
let preferredClasses = this.GetPreferredClasses(type);
for (let targetClass of targetClasses)
{
let pref = preferredClasses.indexOf(targetClass);
if (pref === 0)
return pref;
if (pref != -1 && (minPref === null || minPref > pref))
minPref = pref;
}
}
return minPref;
};
/**
* Get the full range of attack using all available attack types.
*/
Attack.prototype.GetFullAttackRange = function()
{
let ret = { "min": Infinity, "max": 0 };
for (let type of this.GetAttackTypes())
{
let range = this.GetRange(type);
ret.min = Math.min(ret.min, range.min);
ret.max = Math.max(ret.max, range.max);
}
return ret;
};
Attack.prototype.GetBestAttackAgainst = function(target, allowCapture)
{
let cmpFormation = Engine.QueryInterface(target, IID_Formation);
if (cmpFormation)
{
// TODO: Formation against formation needs review
let types = this.GetAttackTypes();
return g_AttackTypes.find(attack => types.indexOf(attack) != -1);
}
let cmpIdentity = Engine.QueryInterface(target, IID_Identity);
if (!cmpIdentity)
return undefined;
let targetClasses = cmpIdentity.GetClassesList();
let isTargetClass = className => targetClasses.indexOf(className) != -1;
// Always slaughter domestic animals instead of using a normal attack
if (isTargetClass("Domestic") && this.template.Slaughter)
return "Slaughter";
let types = this.GetAttackTypes().filter(type => !this.GetRestrictedClasses(type).some(isTargetClass));
// check if the target is capturable
let captureIndex = types.indexOf("Capture");
if (captureIndex != -1)
{
let cmpCapturable = QueryMiragedInterface(target, IID_Capturable);
let cmpPlayer = QueryOwnerInterface(this.entity);
if (allowCapture && cmpPlayer && cmpCapturable && cmpCapturable.CanCapture(cmpPlayer.GetPlayerID()))
return "Capture";
// not capturable, so remove this attack
types.splice(captureIndex, 1);
}
let isPreferred = className => this.GetPreferredClasses(className).some(isTargetClass);
return types.sort((a, b) =>
(types.indexOf(a) + (isPreferred(a) ? types.length : 0)) -
(types.indexOf(b) + (isPreferred(b) ? types.length : 0))).pop();
};
Attack.prototype.CompareEntitiesByPreference = function(a, b)
{
let aPreference = this.GetPreference(a);
let bPreference = this.GetPreference(b);
if (aPreference === null && bPreference === null) return 0;
if (aPreference === null) return 1;
if (bPreference === null) return -1;
return aPreference - bPreference;
};
Attack.prototype.GetTimers = function(type)
{
let prepare = +(this.template[type].PrepareTime || 0);
prepare = ApplyValueModificationsToEntity("Attack/" + type + "/PrepareTime", prepare, this.entity);
let repeat = +(this.template[type].RepeatTime || 1000);
repeat = ApplyValueModificationsToEntity("Attack/" + type + "/RepeatTime", repeat, this.entity);
return { "prepare": prepare, "repeat": repeat };
};
Attack.prototype.GetAttackStrengths = function(type)
{
// Work out the attack values with technology effects
let template = this.template[type];
let splash = "";
if (!template)
{
template = this.template[type.split(".")[0]].Splash;
splash = "/Splash";
}
let applyMods = damageType =>
ApplyValueModificationsToEntity("Attack/" + type + splash + "/" + damageType, +(template[damageType] || 0), this.entity);
if (type == "Capture")
return { "value": applyMods("Value") };
return {
"hack": applyMods("Hack"),
"pierce": applyMods("Pierce"),
"crush": applyMods("Crush")
};
};
Attack.prototype.GetSplashDamage = function(type)
{
if (!this.template[type].Splash)
return false;
let splash = this.GetAttackStrengths(type + ".Splash");
splash.friendlyFire = this.template[type].Splash.FriendlyFire != "false";
splash.shape = this.template[type].Splash.Shape;
return splash;
};
Attack.prototype.GetRange = function(type)
{
let max = +this.template[type].MaxRange;
max = ApplyValueModificationsToEntity("Attack/" + type + "/MaxRange", max, this.entity);
let min = +(this.template[type].MinRange || 0);
min = ApplyValueModificationsToEntity("Attack/" + type + "/MinRange", min, this.entity);
let elevationBonus = +(this.template[type].ElevationBonus || 0);
elevationBonus = ApplyValueModificationsToEntity("Attack/" + type + "/ElevationBonus", elevationBonus, this.entity);
return { "max": max, "min": min, "elevationBonus": elevationBonus };
};
Attack.prototype.GetBonusTemplate = function(type)
{
let template = this.template[type];
if (!template)
template = this.template[type.split(".")[0]].Splash;
- if (template.Bonuses)
- return clone(template.Bonuses);
- return null;
+
+ return template.Bonuses || null;
};
/**
* Attack the target entity. This should only be called after a successful range check,
* and should only be called after GetTimers().repeat msec has passed since the last
* call to PerformAttack.
*/
Attack.prototype.PerformAttack = function(type, target)
{
let attackerOwner = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
// If this is a ranged attack, then launch a projectile
if (type == "Ranged")
{
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
let turnLength = cmpTimer.GetLatestTurnLength()/1000;
// In the future this could be extended:
// * Obstacles like trees could reduce the probability of the target being hit
// * Obstacles like walls should block projectiles entirely
let horizSpeed = +this.template[type].ProjectileSpeed;
let gravity = +this.template[type].Gravity;
//horizSpeed /= 2; gravity /= 2; // slow it down for testing
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (!cmpPosition || !cmpPosition.IsInWorld())
return;
let selfPosition = cmpPosition.GetPosition();
let cmpTargetPosition = Engine.QueryInterface(target, IID_Position);
if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld())
return;
let targetPosition = cmpTargetPosition.GetPosition();
let previousTargetPosition = Engine.QueryInterface(target, IID_Position).GetPreviousPosition();
let targetVelocity = Vector3D.sub(targetPosition, previousTargetPosition).div(turnLength);
let timeToTarget = this.PredictTimeToTarget(selfPosition, horizSpeed, targetPosition, targetVelocity);
let predictedPosition = (timeToTarget !== false) ? Vector3D.mult(targetVelocity, timeToTarget).add(targetPosition) : targetPosition;
// Add inaccuracy based on spread.
let distanceModifiedSpread = ApplyValueModificationsToEntity("Attack/Ranged/Spread", +this.template.Ranged.Spread, this.entity) *
predictedPosition.horizDistanceTo(selfPosition) / 100;
let randNorm = randomNormal2D();
let offsetX = randNorm[0] * distanceModifiedSpread;
let offsetZ = randNorm[1] * distanceModifiedSpread;
let realTargetPosition = new Vector3D(predictedPosition.x + offsetX, targetPosition.y, predictedPosition.z + offsetZ);
// Recalculate when the missile will hit the target position.
let realHorizDistance = realTargetPosition.horizDistanceTo(selfPosition);
timeToTarget = realHorizDistance / horizSpeed;
let missileDirection = Vector3D.sub(realTargetPosition, selfPosition).div(realHorizDistance);
// Launch the graphical projectile.
let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
let id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity);
cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
let data = {
"type": type,
"attacker": this.entity,
"target": target,
"strengths": this.GetAttackStrengths(type),
"position": realTargetPosition,
"direction": missileDirection,
"projectileId": id,
"bonus": this.GetBonusTemplate(type),
"isSplash": false,
"attackerOwner": attackerOwner
};
if (this.template.Ranged.Splash)
{
data.friendlyFire = this.template.Ranged.Splash.FriendlyFire != "false";
data.radius = +this.template.Ranged.Splash.Range;
data.shape = this.template.Ranged.Splash.Shape;
data.isSplash = true;
data.splashStrengths = this.GetAttackStrengths(type + ".Splash");
data.splashBonus = this.GetBonusTemplate(type + ".Splash");
}
cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000, data);
}
else if (type == "Capture")
{
if (attackerOwner == -1)
return;
let multiplier = GetDamageBonus(target, this.GetBonusTemplate(type));
let cmpHealth = Engine.QueryInterface(target, IID_Health);
if (!cmpHealth || cmpHealth.GetHitpoints() == 0)
return;
multiplier *= cmpHealth.GetMaxHitpoints() / (0.1 * cmpHealth.GetMaxHitpoints() + 0.9 * cmpHealth.GetHitpoints());
let cmpCapturable = Engine.QueryInterface(target, IID_Capturable);
if (!cmpCapturable || !cmpCapturable.CanCapture(attackerOwner))
return;
let strength = this.GetAttackStrengths("Capture").value * multiplier;
if (cmpCapturable.Reduce(strength, attackerOwner) && IsOwnedByEnemyOfPlayer(attackerOwner, target))
Engine.PostMessage(target, MT_Attacked, {
"attacker": this.entity,
"target": target,
"type": type,
"damage": strength,
"attackerOwner": attackerOwner
});
}
else
{
// Melee attack - hurt the target immediately
cmpDamage.CauseDamage({
"strengths": this.GetAttackStrengths(type),
"target": target,
"attacker": this.entity,
"multiplier": GetDamageBonus(target, this.GetBonusTemplate(type)),
"type": type,
"attackerOwner": attackerOwner
});
}
};
/**
* Get the predicted time of collision between a projectile (or a chaser)
* and its target, assuming they both move in straight line at a constant speed.
* Vertical component of movement is ignored.
* @param {Vector3D} selfPosition - the 3D position of the projectile (or chaser).
* @param {number} horizSpeed - the horizontal speed of the projectile (or chaser).
* @param {Vector3D} targetPosition - the 3D position of the target.
* @param {Vector3D} targetVelocity - the 3D velocity vector of the target.
* @return {Vector3D|boolean} - the 3D predicted position or false if the collision will not happen.
*/
Attack.prototype.PredictTimeToTarget = function(selfPosition, horizSpeed, targetPosition, targetVelocity)
{
let relativePosition = new Vector3D.sub(targetPosition, selfPosition);
let a = targetVelocity.x * targetVelocity.x + targetVelocity.z * targetVelocity.z - horizSpeed * horizSpeed;
let b = relativePosition.x * targetVelocity.x + relativePosition.z * targetVelocity.z;
let c = relativePosition.x * relativePosition.x + relativePosition.z * relativePosition.z;
// The predicted time to reach the target is the smallest non negative solution
// (when it exists) of the equation a t^2 + 2 b t + c = 0.
// Using c>=0, we can straightly compute the right solution.
if (c == 0)
return 0;
let disc = b * b - a * c;
if (a < 0 || b < 0 && disc >= 0)
return c / (Math.sqrt(disc) - b);
return false;
};
Attack.prototype.OnValueModification = function(msg)
{
if (msg.component != "Attack")
return;
let cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
if (!cmpUnitAI)
return;
if (this.GetAttackTypes().some(type =>
msg.valueNames.indexOf("Attack/" + type + "/MaxRange") != -1))
cmpUnitAI.UpdateRangeQueries();
};
Engine.RegisterComponentType(IID_Attack, "Attack", Attack);
Index: ps/trunk/binaries/data/mods/public/simulation/components/DeathDamage.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/DeathDamage.js (revision 20133)
+++ ps/trunk/binaries/data/mods/public/simulation/components/DeathDamage.js (revision 20134)
@@ -1,95 +1,93 @@
function DeathDamage() {}
DeathDamage.prototype.bonusesSchema =
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
DeathDamage.prototype.Schema =
"When a unit or building is destroyed, it inflicts damage to nearby units." +
"" +
"Circular" +
"20" +
"false" +
"0.0" +
"10.0" +
"50.0" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
DeathDamage.prototype.bonusesSchema;
DeathDamage.prototype.Init = function()
{
};
DeathDamage.prototype.Serialize = null; // we have no dynamic state to save
DeathDamage.prototype.GetDeathDamageStrengths = function()
{
// Work out the damage values with technology effects
let applyMods = damageType =>
ApplyValueModificationsToEntity("DeathDamage/" + damageType, +(this.template[damageType] || 0), this.entity);
return {
"hack": applyMods("Hack"),
"pierce": applyMods("Pierce"),
"crush": applyMods("Crush")
};
};
DeathDamage.prototype.GetBonusTemplate = function()
{
- if (this.template.Bonuses)
- return clone(this.template.Bonuses);
- return null;
+ return this.template.Bonuses || null;
};
DeathDamage.prototype.CauseDeathDamage = function()
{
let cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
if (!cmpPosition || !cmpPosition.IsInWorld())
return;
let pos = cmpPosition.GetPosition2D();
let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
let owner = cmpOwnership.GetOwner();
if (owner == -1)
warn("Unit causing death damage does not have any owner.");
let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
let playersToDamage = cmpDamage.GetPlayersToDamage(owner, this.template.FriendlyFire);
let radius = ApplyValueModificationsToEntity("DeathDamage/Range", +this.template.Range, this.entity);
cmpDamage.CauseSplashDamage({
"attacker": this.entity,
"origin": pos,
"radius": radius,
"shape": this.template.Shape,
"strengths": this.GetDeathDamageStrengths(),
"splashBonus": this.GetBonusTemplate(),
"playersToDamage": playersToDamage,
"type": "Death",
"attackerOwner": owner
});
};
Engine.RegisterComponentType(IID_DeathDamage, "DeathDamage", DeathDamage);
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/setup.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/setup.js (revision 20133)
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/setup.js (revision 20134)
@@ -1,98 +1,112 @@
var g_NewIID = 1000; // some arbitrary not-yet-used number
var g_NewMTID = 1000; // some arbitrary not-yet-used number
var g_ComponentTypes = {};
var g_Components = {};
// Emulate some engine functions:
Engine.RegisterComponentType = function(iid, name, ctor)
{
TS_ASSERT(!g_ComponentTypes[name]);
- g_ComponentTypes[name] = { iid: iid, ctor: ctor };
+ g_ComponentTypes[name] = { "iid": iid, "ctor": ctor };
};
Engine.RegisterSystemComponentType = function(iid, name, ctor)
{
TS_ASSERT(!g_ComponentTypes[name]);
- g_ComponentTypes[name] = { iid: iid, ctor: ctor };
+ g_ComponentTypes[name] = { "iid": iid, "ctor": ctor };
};
Engine.RegisterInterface = function(name)
{
- global["IID_"+name] = g_NewIID++;
+ global["IID_" + name] = g_NewIID++;
};
Engine.RegisterMessageType = function(name)
{
- global["MT_"+name] = g_NewMTID++;
+ global["MT_" + name] = g_NewMTID++;
};
Engine.QueryInterface = function(ent, iid)
{
if (g_Components[ent] && g_Components[ent][iid])
return g_Components[ent][iid];
return null;
};
Engine.RegisterGlobal = function(name, value)
{
global[name] = value;
};
Engine.DestroyEntity = function(ent)
{
- for (var cid in g_Components[ent])
+ for (let cid in g_Components[ent])
{
- var cmp = g_Components[ent][cid];
+ let cmp = g_Components[ent][cid];
if (cmp && cmp.Deinit)
cmp.Deinit();
}
delete g_Components[ent];
// TODO: should send Destroy message
};
Engine.PostMessage = function(ent, iid, message)
{
// TODO: make this send a message if necessary
};
Engine.BroadcastMessage = function(iid, message)
{
// TODO: make this send a message if necessary
};
global.ResetState = function()
{
g_Components = {};
};
global.AddMock = function(ent, iid, mock)
{
if (!g_Components[ent])
g_Components[ent] = {};
g_Components[ent][iid] = mock;
};
global.DeleteMock = function(ent, iid)
{
if (!g_Components[ent])
g_Components[ent] = {};
delete g_Components[ent][iid];
};
global.ConstructComponent = function(ent, name, template)
{
- var cmp = new g_ComponentTypes[name].ctor();
- cmp.entity = ent;
- cmp.template = template;
+ let cmp = new g_ComponentTypes[name].ctor();
+
+ Object.defineProperties(cmp, {
+ "entity": {
+ "value": ent,
+ "configurable": false,
+ "enumerable": false,
+ "writable": false
+ },
+ "template": {
+ "value": template && deepfreeze(clone(template)),
+ "configurable": false,
+ "enumerable": false,
+ "writable": false
+ }
+ });
+
cmp.Init();
if (!g_Components[ent])
g_Components[ent] = {};
g_Components[ent][g_ComponentTypes[name].iid] = cmp;
return cmp;
};
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/setup_test.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/setup_test.js (nonexistent)
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/setup_test.js (revision 20134)
@@ -0,0 +1,24 @@
+Engine.RegisterInterface("TestSetup");
+
+function TestSetup() {};
+TestSetup.prototype.Init = function() {};
+
+Engine.RegisterSystemComponentType(IID_TestSetup, "TestSetup", TestSetup);
+let cmpTestSetup = ConstructComponent(SYSTEM_ENTITY, "TestSetup", { "property": "value" });
+
+function expectException(func)
+{
+ try {
+ func();
+ Engine.TS_FAIL("Missed exception at " + new Error().stack);
+ } catch (e) {}
+}
+
+expectException(() => { cmpTestSetup.template = "replacement forbidden"; });
+expectException(() => { cmpTestSetup.template.property = "modification forbidden"; });
+expectException(() => { cmpTestSetup.template.other_property = "insertion forbidden"; });
+expectException(() => { delete cmpTestSetup.entity; });
+expectException(() => { delete cmpTestSetup.template; });
+expectException(() => { delete cmpTestSetup.template.property; });
+
+TS_ASSERT_UNEVAL_EQUALS(cmpTestSetup.template, { "property": "value" });
Property changes on: ps/trunk/binaries/data/mods/public/simulation/components/tests/setup_test.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Attack.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Attack.js (revision 20133)
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Attack.js (revision 20134)
@@ -1,292 +1,288 @@
Engine.LoadHelperScript("DamageBonus.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/Auras.js");
Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Engine.LoadComponentScript("interfaces/Formation.js");
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("Attack.js");
let entityID = 903;
function attackComponentTest(defenderClass, isEnemy, test_function)
{
ResetState();
{
let playerEnt1 = 5;
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": () => playerEnt1
});
AddMock(playerEnt1, IID_Player, {
"GetPlayerID": () => 1,
"IsEnemy": () => isEnemy
});
}
let attacker = entityID;
AddMock(attacker, IID_Position, {
"IsInWorld": () => true,
"GetHeightOffset": () => 5,
"GetPosition2D": () => new Vector2D(1, 2)
});
AddMock(attacker, IID_Ownership, {
"GetOwner": () => 1
});
let cmpAttack = ConstructComponent(attacker, "Attack", {
"Melee" : {
"Hack": 11,
"Pierce": 5,
"Crush": 0,
"MinRange": 3,
"MaxRange": 5,
"PreferredClasses": {
"_string": "FemaleCitizen"
},
"RestrictedClasses": {
"_string": "Elephant Archer"
},
"Bonuses":
{
"BonusCav": {
"Classes": "Cavalry",
"Multiplier": 2
}
}
},
"Ranged" : {
"Hack": 0,
"Pierce": 10,
"Crush": 0,
"MinRange": 10,
"MaxRange": 80,
"PrepareTime": 300,
"RepeatTime": 500,
"ProjectileSpeed": 50,
"Gravity": 9.81,
"Spread": 2.5,
"PreferredClasses": {
"_string": "Archer"
},
"RestrictedClasses": {
"_string": "Elephant"
},
"Splash" : {
"Shape": "Circular",
"Range": 10,
"FriendlyFire": "false",
"Hack": 0.0,
"Pierce": 15.0,
"Crush": 35.0,
"Bonuses": {
"BonusCav": {
"Classes": "Cavalry",
"Multiplier": 3
}
}
}
},
"Capture" : {
"Value": 8,
"MaxRange": 10,
},
"Slaughter": {}
});
let defender = ++entityID;
AddMock(defender, IID_Identity, {
"GetClassesList": () => [defenderClass],
"HasClass": className => className == defenderClass
});
AddMock(defender, IID_Ownership, {
"GetOwner": () => 1
});
AddMock(defender, IID_Position, {
"IsInWorld": () => true,
"GetHeightOffset": () => 0
});
test_function(attacker, cmpAttack, defender);
}
// Validate template getter functions
attackComponentTest(undefined, true ,(attacker, cmpAttack, defender) => {
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(), ["Melee", "Ranged", "Capture"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes([]), ["Melee", "Ranged", "Capture"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Melee", "Ranged", "Capture"]), ["Melee", "Ranged", "Capture"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Melee", "Ranged"]), ["Melee", "Ranged"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Capture"]), ["Capture"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Melee", "!Melee"]), []);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["!Melee"]), ["Ranged", "Capture"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["!Melee", "!Ranged"]), ["Capture"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Capture", "!Ranged"]), ["Capture"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackTypes(["Capture", "Melee", "!Ranged"]), ["Melee", "Capture"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetPreferredClasses("Melee"), ["FemaleCitizen"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetRestrictedClasses("Melee"), ["Elephant", "Archer"]);
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetFullAttackRange(), { "min": 0, "max": 80 });
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Capture"), { "value": 8 });
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Ranged"), {
"hack": 0,
"pierce": 10,
"crush": 0
});
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetAttackStrengths("Ranged.Splash"), {
"hack": 0.0,
"pierce": 15.0,
"crush": 35.0
});
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetTimers("Ranged"), {
"prepare": 300,
"repeat": 500
});
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetTimers("Capture"), {
"prepare": 0,
"repeat": 1000
});
TS_ASSERT_UNEVAL_EQUALS(cmpAttack.GetSplashDamage("Ranged"), { "hack": 0, "pierce": 15, "crush": 35, "friendlyFire": false, "shape": "Circular" });
});
for (let className of ["Infantry", "Cavalry"])
attackComponentTest(className, true, (attacker, cmpAttack, defender) => {
TS_ASSERT_EQUALS(cmpAttack.GetBonusTemplate("Melee").BonusCav.Multiplier, 2);
- // Check that we don't leak data
- let bonus = cmpAttack.GetBonusTemplate("Melee");
- bonus.BonusCav.Multiplier = 2.7;
- TS_ASSERT_EQUALS(cmpAttack.GetBonusTemplate("Melee").BonusCav.Multiplier, 2);
TS_ASSERT(cmpAttack.GetBonusTemplate("Capture") === null);
let getAttackBonus = (t, e) => GetDamageBonus(e, cmpAttack.GetBonusTemplate(t));
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus("Melee", defender), className == "Cavalry" ? 2 : 1);
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus("Ranged", defender), 1);
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus("Ranged.Splash", defender), className == "Cavalry" ? 3 : 1);
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus("Capture", defender), 1);
TS_ASSERT_UNEVAL_EQUALS(getAttackBonus("Slaughter", defender), 1);
});
// CanAttack rejects elephant attack due to RestrictedClasses
attackComponentTest("Elephant", true, (attacker, cmpAttack, defender) => {
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender), false);
});
function testGetBestAttackAgainst(defenderClass, bestAttack, isBuilding = false)
{
attackComponentTest(defenderClass, true, (attacker, cmpAttack, defender) => {
if (isBuilding)
AddMock(defender, IID_Capturable, {
"CanCapture": playerID => {
TS_ASSERT_EQUALS(playerID, 1);
return true;
}
});
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender), true);
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, []), true);
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Ranged"]), true);
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Melee"]), true);
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Capture"]), isBuilding);
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Melee", "Capture"]), defenderClass != "Archer");
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Ranged", "Capture"]), true);
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Ranged", "!Melee"]), isBuilding || defenderClass == "Domestic");
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Melee", "!Melee"]), false);
let allowCapturing = [true];
if (!isBuilding)
allowCapturing.push(false);
for (let ac of allowCapturing)
TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ac), bestAttack);
});
attackComponentTest(defenderClass, false, (attacker, cmpAttack, defender) => {
if (isBuilding)
AddMock(defender, IID_Capturable, {
"CanCapture": playerID => {
TS_ASSERT_EQUALS(playerID, 1);
return true;
}
});
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender), isBuilding || defenderClass == "Domestic");
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, []), isBuilding || defenderClass == "Domestic");
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Ranged"]), false);
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Melee"]), isBuilding || defenderClass == "Domestic");
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Capture"]), isBuilding);
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Melee", "Capture"]), isBuilding);
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Ranged", "Capture"]), isBuilding);
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["!Ranged", "!Melee"]), isBuilding || defenderClass == "Domestic");
TS_ASSERT_EQUALS(cmpAttack.CanAttack(defender, ["Melee", "!Melee"]), false);
let allowCapturing = [true];
if (!isBuilding)
allowCapturing.push(false);
let attack;
if (defenderClass == "Domestic")
attack = "Slaughter";
else if (defenderClass == "Structure")
attack = "Capture";
for (let ac of allowCapturing)
TS_ASSERT_EQUALS(cmpAttack.GetBestAttackAgainst(defender, ac), bestAttack);
});
}
testGetBestAttackAgainst("FemaleCitizen", "Melee");
testGetBestAttackAgainst("Archer", "Ranged");
testGetBestAttackAgainst("Domestic", "Slaughter");
testGetBestAttackAgainst("Structure", "Capture", true);
function testPredictTimeToTarget(selfPosition, horizSpeed, targetPosition, targetVelocity)
{
ResetState();
let cmpAttack = ConstructComponent(1, "Attack", {});
let timeToTarget = cmpAttack.PredictTimeToTarget(selfPosition, horizSpeed, targetPosition, targetVelocity);
if (timeToTarget === false)
return;
// Position of the target after that time.
let targetPos = Vector3D.mult(targetVelocity, timeToTarget).add(targetPosition);
// Time that the projectile need to reach it.
let time = targetPos.horizDistanceTo(selfPosition) / horizSpeed;
TS_ASSERT_EQUALS(timeToTarget.toFixed(1), time.toFixed(1));
}
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(0, 0, 0), new Vector3D(0, 0, 0));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(0, 0, 0));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(1, 0, 0));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(4, 0, 0));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(16, 0, 0));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(-1, 0, 0));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(-4, 0, 0));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(-16, 0, 0));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(0, 0, 1));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(0, 0, 4));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(0, 0, 16));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(1, 0, 1));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(2, 0, 2));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(8, 0, 8));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(-1, 0, 1));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(-2, 0, 2));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(-8, 0, 8));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(4, 0, 2));
testPredictTimeToTarget(new Vector3D(0, 0, 0), 4, new Vector3D(20, 0, 0), new Vector3D(-4, 0, 2));
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js (revision 20133)
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_DeathDamage.js (revision 20134)
@@ -1,72 +1,74 @@
Engine.LoadHelperScript("DamageBonus.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Damage.js");
Engine.LoadComponentScript("interfaces/DeathDamage.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Engine.LoadComponentScript("DeathDamage.js");
let deadEnt = 60;
let player = 1;
ApplyValueModificationsToEntity = function(value, stat, ent)
{
if (value == "DeathDamage/Pierce" && ent == deadEnt)
return stat + 200;
return stat;
};
let template = {
"Shape": "Circular",
"Range": 10.7,
"FriendlyFire": "false",
"Hack": 0.0,
"Pierce": 15.0,
"Crush": 35.0
};
let modifiedDamage = {
"hack": 0.0,
"pierce": 215.0,
"crush": 35.0
};
let cmpDeathDamage = ConstructComponent(deadEnt, "DeathDamage", template);
let playersToDamage = [2, 3, 7];
let pos = new Vector2D(3, 4.2);
let result = {
"attacker": deadEnt,
"origin": pos,
"radius": template.Range,
"shape": template.Shape,
"strengths": modifiedDamage,
"splashBonus": null,
"playersToDamage": playersToDamage,
"type": "Death",
"attackerOwner": player
};
AddMock(SYSTEM_ENTITY, IID_Damage, {
"CauseSplashDamage": data => TS_ASSERT_UNEVAL_EQUALS(data, result),
"GetPlayersToDamage": (owner, friendlyFire) => playersToDamage
});
AddMock(deadEnt, IID_Position, {
"GetPosition2D": () => pos,
"IsInWorld": () => true
});
AddMock(deadEnt, IID_Ownership, {
"GetOwner": () => player
});
TS_ASSERT_UNEVAL_EQUALS(cmpDeathDamage.GetDeathDamageStrengths(), modifiedDamage);
cmpDeathDamage.CauseDeathDamage();
+// Test splash damage bonus
let splashBonus = { "BonusCav": { "Classes": "Cavalry", "Multiplier": 3 } };
-cmpDeathDamage.template.Bonuses = splashBonus;
+template.Bonuses = splashBonus;
+cmpDeathDamage = ConstructComponent(deadEnt, "DeathDamage", template);
result.splashBonus = splashBonus;
TS_ASSERT_UNEVAL_EQUALS(cmpDeathDamage.GetDeathDamageStrengths(), modifiedDamage);
cmpDeathDamage.CauseDeathDamage();
Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js (revision 20133)
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_GarrisonHolder.js (revision 20134)
@@ -1,174 +1,172 @@
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadComponentScript("interfaces/GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/Garrisonable.js");
Engine.LoadComponentScript("GarrisonHolder.js");
Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Auras.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/ProductionQueue.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/UnitAI.js");
Engine.LoadComponentScript("interfaces/RallyPoint.js");
const garrisonedEntitiesList = [25, 26, 27, 28, 29, 30, 31, 32, 33];
const garrisonHolderId = 15;
const unitToGarrisonId = 24;
const enemyUnitId = 34;
const player = 1;
const friendlyPlayer = 2;
const enemyPlayer = 3;
AddMock(garrisonHolderId, IID_Footprint, {
"PickSpawnPointBothPass": entity => new Vector3D(4, 3, 30),
"PickSpawnPoint": entity => new Vector3D(4, 3, 30)
});
AddMock(garrisonHolderId, IID_Ownership, {
"GetOwner": () => player
});
AddMock(garrisonHolderId, IID_Health, {
"GetHitpoints": () => 50,
"GetMaxHitpoints": () => 600
});
AddMock(player, IID_Player, {
"IsAlly": id => true,
"IsMutualAlly": id => true,
"GetPlayerID": () => player
});
AddMock(friendlyPlayer, IID_Player, {
"IsAlly": id => true,
"IsMutualAlly": id => true,
"GetPlayerID": () => friendlyPlayer
});
AddMock(SYSTEM_ENTITY, IID_Timer, {
"SetTimeout": (ent, iid, funcname, time, data) => 1
});
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": id => id
});
for (let i = 24; i <= 34; ++i)
{
AddMock(i, IID_Identity, {
"GetClassesList": () => "Infantry+Cavalry",
"GetSelectionGroupName": () => "mace_infantry_archer_a"
});
if (i < 28)
AddMock(i, IID_Ownership, {
"GetOwner": () => player
});
else if (i == 34)
AddMock(i, IID_Ownership, {
"GetOwner": () => enemyPlayer
});
else
AddMock(i, IID_Ownership, {
"GetOwner": () => friendlyPlayer
});
AddMock(i, IID_Garrisonable, {});
AddMock(i, IID_Position, {
"GetHeightOffset": () => 0,
"GetPosition": () => new Vector3D(4, 3, 25),
"GetRotation": () => new Vector3D(4, 0, 6),
"GetTurretParent": () => INVALID_ENTITY,
"IsInWorld": () => true,
"JumpTo": (posX, posZ) => {},
"MoveOutOfWorld": () => {},
"SetTurretParent": (entity, offset) => {},
"SetHeightOffset": height => {}
});
}
AddMock(33, IID_Identity, {
"GetClassesList": () => "Infantry+Cavalry",
"GetSelectionGroupName": () => "spart_infantry_archer_a"
});
let cmpGarrisonHolder = ConstructComponent(garrisonHolderId, "GarrisonHolder", {
"Max": 10,
"List": { "_string": "Infantry+Cavalry" },
"EjectHealth": 0.1,
"EjectClassesOnDestroy": { "_string": "Infantry" },
"BuffHeal": 1,
"LoadingRange": 2.1,
"Pickup": false,
"VisibleGarrisonPoints": {
"archer1": {
"X": 12,
"Y": 5,
"Z": 6
},
"archer2": {
"X": 15,
"Y": 5,
"Z": 6
}
}
});
cmpGarrisonHolder.AllowGarrisoning(true, "callerID1");
cmpGarrisonHolder.AllowGarrisoning(false, 5);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(unitToGarrisonId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Unload(unitToGarrisonId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsGarrisoningAllowed(), false);
cmpGarrisonHolder.AllowGarrisoning(true, 5);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsGarrisoningAllowed(), true);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetLoadingRange(), { "max": 2.1, "min": 0 });
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetHealRate(), 1);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetAllowedClasses(), "Infantry+Cavalry");
TS_ASSERT_EQUALS(cmpGarrisonHolder.GetCapacity(), 10);
TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 0);
TS_ASSERT_EQUALS(cmpGarrisonHolder.CanPickup(unitToGarrisonId), false);
-cmpGarrisonHolder.template.Pickup = true;
-TS_ASSERT_EQUALS(cmpGarrisonHolder.CanPickup(unitToGarrisonId), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.CanPickup(enemyUnitId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsFull(), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsAllowedToGarrison(enemyUnitId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsAllowedToGarrison(unitToGarrisonId), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.HasEnoughHealth(), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.PerformGarrison(unitToGarrisonId), false);
AddMock(garrisonHolderId, IID_Health, {
"GetHitpoints": () => 600,
"GetMaxHitpoints": () => 600
});
TS_ASSERT_EQUALS(cmpGarrisonHolder.HasEnoughHealth(), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(enemyUnitId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(unitToGarrisonId), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Eject(unitToGarrisonId), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(unitToGarrisonId), true);
for (let entity of garrisonedEntitiesList)
TS_ASSERT_EQUALS(cmpGarrisonHolder.Garrison(entity), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsFull(), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.CanPickup(unitToGarrisonId), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.UnloadTemplate("spart_infantry_archer_a", 2, false, false), true);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [24, 25, 26, 27, 28, 29, 30, 31, 32]);
TS_ASSERT_EQUALS(cmpGarrisonHolder.UnloadAllByOwner(friendlyPlayer, false), true);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [24, 25, 26, 27]);
TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 4);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsEjectable(25), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.Unload(25), true);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsEjectable(25), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.PerformEject([25], false), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.PerformEject([], false), true);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), [24, 26, 27]);
TS_ASSERT_EQUALS(cmpGarrisonHolder.GetGarrisonedEntitiesCount(), 3);
TS_ASSERT_EQUALS(cmpGarrisonHolder.IsFull(), false);
TS_ASSERT_EQUALS(cmpGarrisonHolder.UnloadAll(), true);
TS_ASSERT_UNEVAL_EQUALS(cmpGarrisonHolder.GetEntities(), []);
Index: ps/trunk/source/simulation2/components/tests/test_scripts.h
===================================================================
--- ps/trunk/source/simulation2/components/tests/test_scripts.h (revision 20133)
+++ ps/trunk/source/simulation2/components/tests/test_scripts.h (revision 20134)
@@ -1,85 +1,86 @@
/* Copyright (C) 2017 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "simulation2/system/ComponentTest.h"
#include "ps/Filesystem.h"
class TestComponentScripts : public CxxTest::TestSuite
{
public:
void setUp()
{
g_VFS = CreateVfs(20 * MiB);
g_VFS->Mount(L"", DataDir()/"mods"/"mod", VFS_MOUNT_MUST_EXIST);
g_VFS->Mount(L"", DataDir()/"mods"/"public", VFS_MOUNT_MUST_EXIST, 1); // ignore directory-not-found errors
CXeromyces::Startup();
}
void tearDown()
{
CXeromyces::Terminate();
g_VFS.reset();
}
static void load_script(const ScriptInterface& scriptInterface, const VfsPath& pathname)
{
CVFSFile file;
TS_ASSERT_EQUALS(file.Load(g_VFS, pathname), PSRETURN_OK);
CStr content = file.DecodeUTF8(); // assume it's UTF-8
TSM_ASSERT(L"Running script "+pathname.string(), scriptInterface.LoadScript(pathname, content));
}
static void Script_LoadComponentScript(ScriptInterface::CxPrivate* pCxPrivate, const VfsPath& pathname)
{
CComponentManager* componentManager = static_cast (pCxPrivate->pCBData);
TS_ASSERT(componentManager->LoadScript(VfsPath(L"simulation/components") / pathname));
}
static void Script_LoadHelperScript(ScriptInterface::CxPrivate* pCxPrivate, const VfsPath& pathname)
{
CComponentManager* componentManager = static_cast (pCxPrivate->pCBData);
TS_ASSERT(componentManager->LoadScript(VfsPath(L"simulation/helpers") / pathname));
}
void test_scripts()
{
if (!VfsFileExists(L"simulation/components/tests/setup.js"))
{
debug_printf("WARNING: Skipping component scripts tests (can't find binaries/data/mods/public/simulation/components/tests/setup.js)\n");
return;
}
VfsPaths paths;
TS_ASSERT_OK(vfs::GetPathnames(g_VFS, L"simulation/components/tests/", L"test_*.js", paths));
+ paths.push_back(VfsPath(L"simulation/components/tests/setup_test.js"));
for (const VfsPath& path : paths)
{
CSimContext context;
CComponentManager componentManager(context, g_ScriptRuntime, true);
ScriptTestSetup(componentManager.GetScriptInterface());
componentManager.GetScriptInterface().RegisterFunction ("LoadComponentScript");
componentManager.GetScriptInterface().RegisterFunction ("LoadHelperScript");
componentManager.LoadComponentTypes();
load_script(componentManager.GetScriptInterface(), L"simulation/components/tests/setup.js");
load_script(componentManager.GetScriptInterface(), path);
}
}
};