Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/theb_mechanical_siege_fireraiser.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/theb_mechanical_siege_fireraiser.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/theb_mechanical_siege_fireraiser.xml (revision 22184)
@@ -1,41 +1,44 @@
50.0
0.0
50.0
12
8.0
- 10.0
- 9.81
2000
2000
- 2.0
0
+
+ 10.0
+ 2.0
+ 9.81
+
+
Circular
20
true
200.0
200.0
200.0
4.5
Fire Raiser
Pyrobolos
units/hele_mechanical_siege_lithobolos.png
60
units/thebans/siege_fireraiser.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml (revision 22184)
@@ -1,78 +1,81 @@
0.0
35.0
0.0
45.0
0.0
- 75.0
- 9.81
1000
2000
- 2.0
0
+
+ 75.0
+ 2.0
+ 9.81
+
+
Ship Human
2
10
1
Infantry Cavalry
2
20
125
50
10.0
20
0
FemaleCitizen Infantry Healer Dog
Support Infantry Cavalry Dog
0
10
true
800
Light Warship
Ranged Warship
phase_town
Garrison units for transport and to increase firepower.
75
25
15
attack/impact/arrow_metal.xml
attack/weapon/arrowfly.xml
6.0
0.5
6.0
1.55
1.55
Index: ps/trunk/binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Attack.js (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Attack.js (revision 22184)
@@ -1,693 +1,694 @@
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" +
"1000" +
"" +
"" +
"Cavalry" +
"2" +
"" +
"" +
"" +
+ "50.0" +
+ "2.5" +
"props/units/weapons/rock_flaming.xml" +
"props/units/weapons/rock_explosion.xml" +
"0.1" +
"" +
"Champion" +
"" +
"Circular" +
"20" +
"false" +
"0.0" +
"10.0" +
"0.0" +
"" +
"" +
"" +
"1000.0" +
"0.0" +
"0.0" +
"4.0" +
"" +
"" +
"" +
"" +
"" +
DamageTypes.BuildSchema("damage strength") +
"" +
"" +
"" +
"" +
"" + // TODO: it shouldn't be stretched
"" +
"" +
Attack.prototype.bonusesSchema +
Attack.prototype.preferredClassesSchema +
Attack.prototype.restrictedClassesSchema +
"" +
"" +
"" +
"" +
"" +
"" +
DamageTypes.BuildSchema("damage strength") +
"" +
"" +
""+
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
"" +
- Attack.prototype.bonusesSchema +
- Attack.prototype.preferredClassesSchema +
- Attack.prototype.restrictedClassesSchema +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
- "" +
"" +
"" +
"" +
"" +
"" +
"" +
DamageTypes.BuildSchema("damage strength") +
Attack.prototype.bonusesSchema +
"" +
"" +
"" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ Attack.prototype.bonusesSchema +
+ Attack.prototype.preferredClassesSchema +
+ Attack.prototype.restrictedClassesSchema +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" + // TODO: it shouldn't be stretched
"" +
"" +
Attack.prototype.bonusesSchema +
Attack.prototype.preferredClassesSchema +
Attack.prototype.restrictedClassesSchema +
"" +
"" +
"" +
"" +
"" +
"" +
DamageTypes.BuildSchema("damage strength") +
"" + // 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 cmpHealth = QueryMiragedInterface(target, IID_Health);
let targetClasses = cmpIdentity.GetClassesList();
if (targetClasses.indexOf("Domestic") != -1 && this.template.Slaughter && cmpHealth && cmpHealth.GetHitpoints() &&
(!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) || !cmpHealth || !cmpHealth.GetHitpoints()))
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") };
let ret = {};
for (let damageType of DamageTypes.GetTypes())
ret[damageType] = applyMods(damageType);
return ret;
};
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;
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;
+ let horizSpeed = +this.template[type].Projectile.Speed;
+ let gravity = +this.template[type].Projectile.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) *
+ let distanceModifiedSpread = ApplyValueModificationsToEntity("Attack/Ranged/Spread", +this.template[type].Projectile.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 actorName = "";
let impactActorName = "";
let impactAnimationLifetime = 0;
- if (this.template.Ranged.Projectile)
- {
- actorName = this.template.Ranged.Projectile.ActorName || "";
- impactActorName = this.template.Ranged.Projectile.ImpactActorName || "";
- impactAnimationLifetime = this.template.Ranged.Projectile.ImpactAnimationLifetime || 0;
- }
- let launchPoint = selfPosition.clone();
- // TODO: remove this when all the ranged unit templates are updated with Projectile/Launchpoint
- launchPoint.y += 3;
+ actorName = this.template[type].Projectile.ActorName || "";
+ impactActorName = this.template[type].Projectile.ImpactActorName || "";
+ impactAnimationLifetime = this.template[type].Projectile.ImpactAnimationLifetime || 0;
+
+ // TODO: Use unit rotation to implement x/z offsets.
+ let deltaLaunchPoint = new Vector3D(0, this.template[type].Projectile.LaunchPoint["@y"], 0.0);
+ let launchPoint = Vector3D.add(selfPosition, deltaLaunchPoint);
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
if (cmpVisual)
{
// if the projectile definition is missing from the template
// then fallback to the projectile name and launchpoint in the visual actor
if (!actorName)
actorName = cmpVisual.GetProjectileActor();
let visualActorLaunchPoint = cmpVisual.GetProjectileLaunchPoint();
if (visualActorLaunchPoint.length() > 0)
launchPoint = visualActorLaunchPoint;
}
let id = cmpProjectileManager.LaunchProjectileAtPoint(launchPoint, realTargetPosition, horizSpeed, gravity, actorName, impactActorName, impactAnimationLifetime);
let attackImpactSound = "";
let cmpSound = Engine.QueryInterface(this.entity, IID_Sound);
if (cmpSound)
attackImpactSound = cmpSound.GetSoundGroup("attack_impact_" + type.toLowerCase());
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,
"attackImpactSound": attackImpactSound
};
- if (this.template.Ranged.Splash)
+ if (this.template[type].Splash)
{
- data.friendlyFire = this.template.Ranged.Splash.FriendlyFire != "false";
- data.radius = +this.template.Ranged.Splash.Range;
- data.shape = this.template.Ranged.Splash.Shape;
+ data.friendlyFire = this.template[type].Splash.FriendlyFire != "false";
+ data.radius = +this.template[type].Splash.Range;
+ data.shape = this.template[type].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 + +this.template.Ranged.Delay, data);
+ cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000 + +this.template[type].Delay, data);
}
else if (type == "Capture")
{
if (attackerOwner == INVALID_PLAYER)
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();
};
Attack.prototype.GetRangeOverlays = function()
{
if (!this.template.Ranged || !this.template.Ranged.RangeOverlay)
return [];
let range = this.GetRange("Ranged");
let rangeOverlays = [];
for (let i in range)
if ((i == "min" || i == "max") && range[i])
rangeOverlays.push({
"radius": range[i],
"texture": this.template.Ranged.RangeOverlay.LineTexture,
"textureMask": this.template.Ranged.RangeOverlay.LineTextureMask,
"thickness": +this.template.Ranged.RangeOverlay.LineThickness,
});
return rangeOverlays;
};
Engine.RegisterComponentType(IID_Attack, "Attack", Attack);
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 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Attack.js (revision 22184)
@@ -1,300 +1,302 @@
Engine.LoadHelperScript("DamageBonus.js");
Engine.LoadHelperScript("DamageTypes.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Auras.js");
Engine.LoadComponentScript("interfaces/Capturable.js");
Engine.LoadComponentScript("interfaces/Formation.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.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,
+ "Projectile": {
+ "Speed": 10,
+ "Spread": 2,
+ "Gravity": 1
+ }
"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
});
AddMock(defender, IID_Health, {
"GetHitpoints": () => 100
});
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);
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_Damage.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Damage.js (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Damage.js (revision 22184)
@@ -1,537 +1,550 @@
Engine.LoadHelperScript("DamageBonus.js");
Engine.LoadHelperScript("DamageTypes.js");
Engine.LoadHelperScript("Player.js");
Engine.LoadHelperScript("Sound.js");
Engine.LoadHelperScript("ValueModification.js");
Engine.LoadComponentScript("interfaces/Attack.js");
Engine.LoadComponentScript("interfaces/AttackDetection.js");
Engine.LoadComponentScript("interfaces/AuraManager.js");
Engine.LoadComponentScript("interfaces/Damage.js");
Engine.LoadComponentScript("interfaces/DamageReceiver.js");
Engine.LoadComponentScript("interfaces/Health.js");
Engine.LoadComponentScript("interfaces/Loot.js");
Engine.LoadComponentScript("interfaces/Player.js");
Engine.LoadComponentScript("interfaces/Promotion.js");
Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("Attack.js");
Engine.LoadComponentScript("Damage.js");
Engine.LoadComponentScript("Timer.js");
function Test_Generic()
{
ResetState();
let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer");
cmpTimer.OnUpdate({ turnLength: 1 });
let attacker = 11;
let atkPlayerEntity = 1;
let attackerOwner = 6;
- let cmpAttack = ConstructComponent(attacker, "Attack", { "Ranged": { "ProjectileSpeed": 500, "Gravity": 9.81, "Spread": 0.5, "MaxRange": 50, "MinRange": 0, "Delay": 0 } } );
+ let cmpAttack = ConstructComponent(attacker, "Attack",
+ {
+ "Ranged": {
+ "MaxRange": 50,
+ "MinRange": 0,
+ "Delay": 0,
+ "Projectile": {
+ "Speed": 75.0,
+ "Spread": 0.5,
+ "Gravity": 9.81,
+ "LaunchPoint": { "@y": 3 }
+ }
+ }
+ });
let damage = 5;
let target = 21;
let targetOwner = 7;
let targetPos = new Vector3D(3, 0, 3);
let type = "Melee";
let damageTaken = false;
cmpAttack.GetAttackStrengths = attackType => ({ "hack": 0, "pierce": 0, "crush": damage });
let data = {
"attacker": attacker,
"target": target,
"type": "Melee",
"strengths": { "hack": 0, "pierce": 0, "crush": damage },
"multiplier": 1.0,
"attackerOwner": attackerOwner,
"position": targetPos,
"isSplash": false,
"projectileId": 9,
"direction": new Vector3D(1,0,0)
};
AddMock(atkPlayerEntity, IID_Player, {
"GetEnemies": () => [targetOwner]
});
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": id => atkPlayerEntity,
"GetAllPlayers": () => [0, 1, 2, 3, 4]
});
AddMock(SYSTEM_ENTITY, IID_ProjectileManager, {
"RemoveProjectile": () => {},
"LaunchProjectileAtPoint": (ent, pos, speed, gravity) => {},
});
AddMock(target, IID_Position, {
"GetPosition": () => targetPos,
"GetPreviousPosition": () => targetPos,
"GetPosition2D": () => Vector2D.From(targetPos),
"IsInWorld": () => true,
});
AddMock(target, IID_Health, {});
AddMock(target, IID_DamageReceiver, {
"TakeDamage": (strengths, multiplier) => { damageTaken = true; return { "killed": false, "change": -multiplier * strengths.crush }; },
});
Engine.PostMessage = function(ent, iid, message)
{
TS_ASSERT_UNEVAL_EQUALS({ "attacker": attacker, "target": target, "type": type, "damage": damage, "attackerOwner": attackerOwner }, message);
};
AddMock(target, IID_Footprint, {
"GetShape": () => ({ "type": "circle", "radius": 20 }),
});
AddMock(attacker, IID_Ownership, {
"GetOwner": () => attackerOwner,
});
AddMock(attacker, IID_Position, {
"GetPosition": () => new Vector3D(2, 0, 3),
"GetRotation": () => new Vector3D(1, 2, 3),
"IsInWorld": () => true,
});
function TestDamage()
{
cmpTimer.OnUpdate({ turnLength: 1 });
TS_ASSERT(damageTaken);
damageTaken = false;
}
cmpDamage.CauseDamage(data);
TestDamage();
type = data.type = "Ranged";
cmpDamage.CauseDamage(data);
TestDamage();
// Check for damage still being dealt if the attacker dies
cmpAttack.PerformAttack("Ranged", target);
Engine.DestroyEntity(attacker);
TestDamage();
atkPlayerEntity = 1;
AddMock(atkPlayerEntity, IID_Player, {
"GetEnemies": () => [2, 3]
});
TS_ASSERT_UNEVAL_EQUALS(cmpDamage.GetPlayersToDamage(atkPlayerEntity, true), [0, 1, 2, 3, 4]);
TS_ASSERT_UNEVAL_EQUALS(cmpDamage.GetPlayersToDamage(atkPlayerEntity, false), [2, 3]);
}
Test_Generic();
function TestLinearSplashDamage()
{
ResetState();
Engine.PostMessage = (ent, iid, message) => {};
const attacker = 50;
const attackerOwner = 1;
const origin = new Vector2D(0, 0);
let data = {
"attacker": attacker,
"origin": origin,
"radius": 10,
"shape": "Linear",
"strengths": { "hack" : 100, "pierce" : 0, "crush": 0 },
"direction": new Vector3D(1, 747, 0),
"playersToDamage": [2],
"type": "Ranged",
"attackerOwner": attackerOwner
};
let fallOff = function(x,y)
{
return (1 - x * x / (data.radius * data.radius)) * (1 - 25 * y * y / (data.radius * data.radius));
};
let hitEnts = new Set();
let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
"ExecuteQueryAroundPos": () => [60, 61, 62],
});
AddMock(60, IID_Position, {
"GetPosition2D": () => new Vector2D(2.2, -0.4),
});
AddMock(61, IID_Position, {
"GetPosition2D": () => new Vector2D(0, 0),
});
AddMock(62, IID_Position, {
"GetPosition2D": () => new Vector2D(5, 2),
});
AddMock(60, IID_DamageReceiver, {
"TakeDamage": (strengths, multiplier) => {
hitEnts.add(60);
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(2.2, -0.4));
return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
AddMock(61, IID_DamageReceiver, {
"TakeDamage": (strengths, multiplier) => {
hitEnts.add(61);
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(0, 0));
return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
AddMock(62, IID_DamageReceiver, {
"TakeDamage": (strengths, multiplier) => {
hitEnts.add(62);
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 0);
return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
cmpDamage.CauseSplashDamage(data);
TS_ASSERT(hitEnts.has(60));
TS_ASSERT(hitEnts.has(61));
TS_ASSERT(hitEnts.has(62));
hitEnts.clear();
data.direction = new Vector3D(0.6, 747, 0.8);
AddMock(60, IID_DamageReceiver, {
"TakeDamage": (strengths, multiplier) => {
hitEnts.add(60);
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(1, 2));
return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
cmpDamage.CauseSplashDamage(data);
TS_ASSERT(hitEnts.has(60));
TS_ASSERT(hitEnts.has(61));
TS_ASSERT(hitEnts.has(62));
hitEnts.clear();
}
TestLinearSplashDamage();
function TestCircularSplashDamage()
{
ResetState();
Engine.PostMessage = (ent, iid, message) => {};
const radius = 10;
let fallOff = function(r)
{
return 1 - r * r / (radius * radius);
};
let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
"ExecuteQueryAroundPos": () => [60, 61, 62, 64],
});
AddMock(60, IID_Position, {
"GetPosition2D": () => new Vector2D(3, 4),
});
AddMock(61, IID_Position, {
"GetPosition2D": () => new Vector2D(0, 0),
});
AddMock(62, IID_Position, {
"GetPosition2D": () => new Vector2D(3.6, 3.2),
});
AddMock(63, IID_Position, {
"GetPosition2D": () => new Vector2D(10, -10),
});
// Target on the frontier of the shape
AddMock(64, IID_Position, {
"GetPosition2D": () => new Vector2D(9, -4),
});
AddMock(60, IID_DamageReceiver, {
"TakeDamage": (strengths, multiplier) => {
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(0));
return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
AddMock(61, IID_DamageReceiver, {
"TakeDamage": (strengths, multiplier) => {
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(5));
return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
AddMock(62, IID_DamageReceiver, {
"TakeDamage": (strengths, multiplier) => {
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100 * fallOff(1));
return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
AddMock(63, IID_DamageReceiver, {
"TakeDamage": (strengths, multiplier) => {
TS_ASSERT(false);
}
});
AddMock(64, IID_DamageReceiver, {
"TakeDamage": (strengths, multiplier) => {
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 0);
return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
cmpDamage.CauseSplashDamage({
"attacker": 50,
"origin": new Vector2D(3, 4),
"radius": radius,
"shape": "Circular",
"strengths": { "hack" : 100, "pierce" : 0, "crush": 0 },
"playersToDamage": [2],
"type": "Ranged",
"attackerOwner": 1
});
}
TestCircularSplashDamage();
function Test_MissileHit()
{
ResetState();
Engine.PostMessage = (ent, iid, message) => {};
let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
let target = 60;
let targetOwner = 1;
let targetPos = new Vector3D(3, 10, 0);
let hitEnts = new Set();
AddMock(SYSTEM_ENTITY, IID_Timer, {
"GetLatestTurnLength": () => 500
});
const radius = 10;
let data = {
"type": "Ranged",
"attacker": 70,
"target": 60,
"strengths": { "hack": 0, "pierce": 100, "crush": 0 },
"position": targetPos,
"direction": new Vector3D(1, 0, 0),
"projectileId": 9,
"bonus": undefined,
"isSplash": false,
"attackerOwner": 1
};
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": id => id == 1 ? 10 : 11,
"GetAllPlayers": () => [0, 1]
});
AddMock(SYSTEM_ENTITY, IID_ProjectileManager, {
"RemoveProjectile": () => {},
"LaunchProjectileAtPoint": (ent, pos, speed, gravity) => {},
});
AddMock(60, IID_Position, {
"GetPosition": () => targetPos,
"GetPreviousPosition": () => targetPos,
"GetPosition2D": () => Vector2D.From(targetPos),
"IsInWorld": () => true,
});
AddMock(60, IID_Health, {});
AddMock(60, IID_DamageReceiver, {
"TakeDamage": (strengths, multiplier) => {
hitEnts.add(60);
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100);
return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
AddMock(60, IID_Footprint, {
"GetShape": () => ({ "type": "circle", "radius": 20 }),
});
AddMock(70, IID_Ownership, {
"GetOwner": () => 1,
});
AddMock(70, IID_Position, {
"GetPosition": () => new Vector3D(0, 0, 0),
"GetRotation": () => new Vector3D(0, 0, 0),
"IsInWorld": () => true,
});
AddMock(10, IID_Player, {
"GetEnemies": () => [2]
});
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(60));
hitEnts.clear();
// The main target is not hit but another one is hit.
AddMock(60, IID_Position, {
"GetPosition": () => new Vector3D(900, 10, 0),
"GetPreviousPosition": () => new Vector3D(900, 10, 0),
"GetPosition2D": () => new Vector2D(900, 0),
"IsInWorld": () => true,
});
AddMock(60, IID_DamageReceiver, {
"TakeDamage": (strengths, multiplier) => {
TS_ASSERT_EQUALS(false);
return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
"ExecuteQueryAroundPos": () => [61]
});
AddMock(61, IID_Position, {
"GetPosition": () => targetPos,
"GetPreviousPosition": () => targetPos,
"GetPosition2D": () => Vector2D.from3D(targetPos),
"IsInWorld": () => true,
});
AddMock(61, IID_Health, {});
AddMock(61, IID_DamageReceiver, {
"TakeDamage": (strengths, multiplier) => {
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 100);
hitEnts.add(61);
return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
AddMock(61, IID_Footprint, {
"GetShape": () => ({ "type": "circle", "radius": 20 }),
});
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
hitEnts.clear();
// Add a splash damage.
data.friendlyFire = false;
data.radius = 10;
data.shape = "Circular";
data.isSplash = true;
data.splashStrengths = { "hack": 0, "pierce": 0, "crush": 200 };
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
"ExecuteQueryAroundPos": () => [61, 62]
});
let dealtDamage = 0;
AddMock(61, IID_DamageReceiver, {
"TakeDamage": (strengths, multiplier) => {
dealtDamage += multiplier * (strengths.hack + strengths.pierce + strengths.crush);
hitEnts.add(61);
return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
AddMock(62, IID_Position, {
"GetPosition": () => new Vector3D(8, 10, 0),
"GetPreviousPosition": () => new Vector3D(8, 10, 0),
"GetPosition2D": () => new Vector2D(8, 0),
"IsInWorld": () => true,
});
AddMock(62, IID_Health, {});
AddMock(62, IID_DamageReceiver, {
"TakeDamage": (strengths, multiplier) => {
TS_ASSERT_EQUALS(multiplier * (strengths.hack + strengths.pierce + strengths.crush), 200 * 0.75);
hitEnts.add(62);
return { "killed": false, "change": -multiplier * (strengths.hack + strengths.pierce + strengths.crush) };
}
});
AddMock(62, IID_Footprint, {
"GetShape": () => ({ "type": "circle", "radius": 20 }),
});
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 100 + 200);
dealtDamage = 0;
TS_ASSERT(hitEnts.has(62));
hitEnts.clear();
// Add some hard counters bonus.
Engine.DestroyEntity(62);
AddMock(SYSTEM_ENTITY, IID_RangeManager, {
"ExecuteQueryAroundPos": () => [61]
});
let bonus= { "BonusCav": { "Classes": "Cavalry", "Multiplier": 400 } };
let splashBonus = { "BonusCav": { "Classes": "Cavalry", "Multiplier": 10000 } };
AddMock(61, IID_Identity, {
"HasClass": cl => cl == "Cavalry"
});
data.bonus = bonus;
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 400 * 100 + 200);
dealtDamage = 0;
hitEnts.clear();
data.splashBonus = splashBonus;
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 400 * 100 + 10000 * 200);
dealtDamage = 0;
hitEnts.clear();
data.bonus = undefined;
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 100 + 10000 * 200);
dealtDamage = 0;
hitEnts.clear();
data.bonus = null;
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 100 + 10000 * 200);
dealtDamage = 0;
hitEnts.clear();
data.bonus = {};
cmpDamage.MissileHit(data, 0);
TS_ASSERT(hitEnts.has(61));
TS_ASSERT_EQUALS(dealtDamage, 100 + 10000 * 200);
dealtDamage = 0;
hitEnts.clear();
}
Test_MissileHit();
Index: ps/trunk/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_minor_test.xml (revision 22184)
@@ -1,48 +1,50 @@
0.0
25.0
0.0
50.0
1.0
- 75.0
1200
2000
+
+ 75.0
+
3
0.5
8.0
athen
Market
Settlement
Minor Greek Polis
This is a minor Greek city.
-units/{civ}_support_female_citizen
campaigns/army_mace_hero_alexander
campaigns/army_mace_standard
true
150
35000
campaigns/structures/hellenes/settlement_curtainwall.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_test.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_test.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/campaigns/campaign_city_test.xml (revision 22184)
@@ -1,49 +1,51 @@
0.0
25.0
0.0
50.0
1.0
- 75.0
1200
2000
+
+ 75.0
+
5
0.5
8.0
athen
Market
Settlement
Greek Polis
This is a major Greek city.
-units/{civ}_support_female_citizen
campaigns/army_mace_hero_alexander
campaigns/army_mace_standard
units/{civ}_support_trader
true
300
35000
campaigns/structures/hellenes/settlement_curtainwall.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml (revision 22184)
@@ -1,124 +1,127 @@
-5.0
-5.0
-1.0
1.0
5.0
1.0
0.0
25.0
0.0
60.0
0.0
- 75.0
- 9.81
1200
2000
- 1.5
0
+
+ 75.0
+ 1.5
+ 9.81
+
+
outline_border.png
outline_border_mask.png
0.175
1
15
1
neutral enemy
ArmyCamp
ArmyCamp
80
1500
10.0
3.0
5
250
500
0
12.0
40
Support Infantry Cavalry Siege
1
6
2500
decay|rubble/rubble_rome_sb
rome
Entrenched Army Camp
Castrum Vallum
ArmyCamp ConquestCritical
structures/roman_camp.png
Build in neutral or enemy territory. Construct siege weapons and train citizen-soldiers. Heal garrisoned units slowly.
100
100
0.7
units/{civ}_infantry_swordsman_b
units/{civ}_infantry_spearman_a
units/{civ}_infantry_javelinist_b
units/{civ}_cavalry_spearman_b
units/{civ}_mechanical_siege_ballista_packed
units/{civ}_mechanical_siege_scorpio_packed
units/{civ}_mechanical_siege_oxybeles_packed
units/{civ}_mechanical_siege_lithobolos_packed
units/{civ}_mechanical_siege_ram
units/{civ}_mechanical_siege_tower
interface/complete/building/complete_broch.xml
37.5
60
structures/romans/camp.xml
structures/fndn_8x8.xml
29.5
8
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml (revision 22184)
@@ -1,133 +1,136 @@
FemaleCitizen
140
190
100
5
5
5
15
3
0.0
12.0
0.0
72.0
0.0
- 75.0
- 9.81
1200
2000
- 1.5
0
+
+ 75.0
+ 1.5
+ 9.81
+
+
Human
outline_border.png
outline_border_mask.png
0.175
3
1
own neutral
CivilCentre
CivilCentre
200
2500
5.0
20
500
0
500
500
500
8.0
20
0.1
Unit
Support Infantry Cavalry
1
1
3000
decay|rubble/rubble_stone_6x6
Civic Center
Build to acquire large tracts of territory. Train citizens.
Defensive CivCentre
CivilCentre
structures/civic_centre.png
200
0
200
200
200
0.8
units/{civ}_support_female_citizen
phase_town_{civ}
phase_city_{civ}
unlock_spies
spy_counter
food wood stone metal
true
interface/complete/building/complete_civ_center.xml
interface/alarm/alarm_alert_0.xml
interface/alarm/alarm_alert_1.xml
true
140
10000
90
structures/fndn_6x6.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_tower.xml (revision 22184)
@@ -1,68 +1,71 @@
0
0
0
- 75.0
- 9.81
1200
2000
- 1.5
0
+
+ 75.0
+ 1.5
+ 9.81
+
+
Human
outline_border.png
outline_border_mask.png
0.175
1
1
Infantry
DefenseTower
DefenseTower
60
0.1
Unit
Support Infantry
0
2
1000
decay|rubble/rubble_stone_2x2
Tower
0.7
interface/complete/building/complete_tower.xml
6.0
0.6
18.0
80
structures/fndn_2x2.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_tower.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_tower.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defensive_wall_tower.xml (revision 22184)
@@ -1,105 +1,108 @@
0.0
8.0
0.0
72.0
12.0
- 75.0
- 9.81
1200
2000
- 1.5
0
+
+ 75.0
+ 1.5
+ 9.81
+
+
Human
outline_border.png
outline_border_mask.png
0.175
0
1
Infantry
land-shore
Wall
1200
80
90
8.0
2
0.1
Unit
Support Infantry
0
2
4000
decay|rubble/rubble_stone_wall_tower
Wall Turret
Shoots arrows. Garrison to defend a city wall against attackers.
StoneWall Tower
structures/tower.png
phase_town
0
15
0.8
pair_walls_01
4.5
interface/complete/building/complete_tower.xml
20.0
5.0
false
20
65535
60
structures/fndn_2x2.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml (revision 22184)
@@ -1,112 +1,115 @@
5
5
3
0.0
16.0
0.0
72.0
0.0
- 75.0
- 9.81
1200
2000
- 1.5
0
+
+ 75.0
+ 1.5
+ 9.81
+
+
Human
outline_border.png
outline_border_mask.png
0.175
3
1
Fortress
Fortress
80
4000
10.0
10
500
0
1000
8.0
20
0.075
Support Infantry Cavalry Siege
6
4200
decay|rubble/rubble_stone_6x6
Fortress
Train heroes, champions, and siege weapons. Research siege weapon improvements.
Defensive Fortress GarrisonFortress
City
structures/fortress.png
phase_city
100
0
65
0.8
units/{civ}_mechanical_siege_ballista_packed
units/{civ}_mechanical_siege_scorpio_packed
units/{civ}_mechanical_siege_oxybeles_packed
units/{civ}_mechanical_siege_lithobolos_packed
units/{civ}_mechanical_siege_polybolos_packed
units/{civ}_mechanical_siege_ram
units/{civ}_mechanical_siege_tower
attack_soldiers_will
interface/complete/building/complete_fortress.xml
100
80
structures/fndn_6x6.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml (revision 22184)
@@ -1,29 +1,32 @@
0
9.0
0
16.0
0.0
- 75.0
- 9.81
1000
1500
- 3.0
0
+
+ 75.0
+ 3.0
+ 9.81
+
+
Human
100
Ranged Cavalry
Ranged
special/formations/skirmish
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged_archer.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged_archer.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged_archer.xml (revision 22184)
@@ -1,25 +1,27 @@
0
7.0
0
72.0
0.0
- 75.0
500
1000
- 3.0
+
+ 75.0
+ 3.0
+
50
Archer
Cavalry Archer
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged_javelinist.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged_javelinist.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged_javelinist.xml (revision 22184)
@@ -1,25 +1,27 @@
0
18.0
0
28.0
0.0
- 62.5
750
1250
- 4.0
+
+ 62.5
+ 4.0
+
50
Javelin
Cavalry Skirmisher
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_archer.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_archer.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_archer.xml (revision 22184)
@@ -1,36 +1,39 @@
0
14.0
0
76.0
0.0
- 75.0
- 9.81
500
1000
- 1.0
0
+
+ 75.0
+ 1.0
+ 9.81
+
+
Human
100
Ranged Archer
Champion Cavalry Archer.
special/formations/skirmish
actor/fauna/death/death_horse.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_javelinist.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_javelinist.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_javelinist.xml (revision 22184)
@@ -1,36 +1,39 @@
0.0
36.0
0.0
32.0
0.0
- 62.5
- 9.81
750
1250
- 1.0
0
+
+ 62.5
+ 1.0
+ 9.81
+
+
Human
100
Ranged Javelin
Champion Cavalry Skirmisher
special/formations/skirmish
actor/fauna/death/death_horse.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml (revision 22184)
@@ -1,40 +1,43 @@
0
6.5
0
76
0.0
- 75.0
- 9.81
300
500
- 1.0
0
+
+ 75.0
+ 1.0
+ 9.81
+
+
Human
100
120
Ranged Archer
Champion Archer
special/formations/skirmish
1.2
1.2
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelinist.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelinist.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelinist.xml (revision 22184)
@@ -1,40 +1,43 @@
0
26.0
0
28.0
0.0
- 62.5
- 9.81
500
1000
- 1.0
0
+
+ 62.5
+ 1.0
+ 9.81
+
+
Human
100
120
Ranged Javelin
Champion Skirmisher
special/formations/skirmish
1.75
1.75
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml (revision 22184)
@@ -1,26 +1,29 @@
0
35
0
80.0
0.0
- 75.0
- 9.81
1200
2000
- 0.5
0
+
+ 75.0
+ 0.5
+ 9.81
+
+
Human
Ranged Archer
Hero Cavalry Archer
special/formations/skirmish
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_javelinist.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_javelinist.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_javelinist.xml (revision 22184)
@@ -1,39 +1,42 @@
0.0
60.0
0.0
36.0
0.0
- 62.5
- 9.81
750
1250
- 0.5
0
+
+ 62.5
+ 0.5
+ 9.81
+
+
Human
3.0
1500
special/formations/skirmish
Hero Cavalry Skirmisher
Javelin Ranged
1.05
1.05
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml (revision 22184)
@@ -1,26 +1,29 @@
0
8.0
0
80
0.0
- 75.0
- 9.81
200
300
- 0.5
0
+
+ 75.0
+ 0.5
+ 9.81
+
+
Human
Hero Archer
Ranged Archer
special/formations/skirmish
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelinist.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelinist.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelinist.xml (revision 22184)
@@ -1,26 +1,29 @@
0
50.0
0
32.0
0.0
- 62.5
- 9.81
600
1000
- 0.5
0
+
+ 62.5
+ 0.5
+ 9.81
+
+
Human
Ranged Javelin
Hero Skirmisher
special/formations/skirmish
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml (revision 22184)
@@ -1,34 +1,37 @@
1
1
10
0
1.5
0
10.0
0.0
- 75.0
- 9.81
750
1250
- 3.0
0
+
+ 75.0
+ 3.0
+ 9.81
+
+
Human
50
Ranged
Ranged
special/formations/skirmish
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_archer.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_archer.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_archer.xml (revision 22184)
@@ -1,39 +1,41 @@
1
1
0
6.0
0
72.0
0.0
- 75.0
600
1000
- 3.0
+
+ 75.0
+ 3.0
+
50
50
Archer
Archer
5
1.1
1.1
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_javelinist.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_javelinist.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_javelinist.xml (revision 22184)
@@ -1,38 +1,40 @@
1
1
0
16.0
0
24.0
0.0
- 62.5
750
1250
- 4.0
+
+ 62.5
+ 4.0
+
50
Javelin
Skirmisher
5
1.4
1.4
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_slinger.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_slinger.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged_slinger.xml (revision 22184)
@@ -1,39 +1,41 @@
1
1
0
9.5
1.0
48.0
0.0
- 62.5
500
1000
- 3.0
+
+ 62.5
+ 3.0
+
30
20
Sling
Slinger
5
1.2
1.2
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml (revision 22184)
@@ -1,89 +1,92 @@
0.0
10.0
100.0
72.0
10.0
- 37.5
- 9.81
2000
5000
- 4.0
0
Circular
10
false
0.0
15.0
35.0
+
+ 37.5
+ 4.0
+ 9.81
+
+
Ship Structure
1
10
1
Catapult
3
30
350
200
350
8.0
50
0
FemaleCitizen Infantry Healer Dog
Support Infantry Cavalry Dog Siege Elephant
0
10
true
2000
Heavy Warship
Garrison units for transport and to increase firepower.
Ranged Warship
phase_city
150
40
30
attack/siege/ballist_attack.xml
6.0
0.5
6.0
1.8
1.8
110
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ballista.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ballista.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ballista.xml (revision 22184)
@@ -1,85 +1,88 @@
0.0
150.0
25.0
80.0
26.0
- 2.0
- 150.0
- 9.81
3000
4000
0
Linear
8.0
false
0.0
75.0
5.0
+
+ 150.0
+ 2.0
+ 9.81
+
+
Human Siege
20
20
250
250
2
2.0
200
Bolt Shooter
BoltShooter Ranged
200
0
10
0
10
circle/256x256.png
circle/256x256_mask.png
attack/impact/arrow_metal.xml
attack/weapon/arrowfly.xml
standground
0.9
0.9
120
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml (revision 22184)
@@ -1,88 +1,91 @@
0.0
12.0
2.5
55.0
10.0
10
- 75.0
- 9.81
1200
2000
- 2.0
0
+
+ 75.0
+ 2.0
+ 9.81
+
+
Human
outline_border.png
outline_border_mask.png
0.175
0
1
10
Infantry
40
500
300
20.0
20
0.1
Unit
Support Infantry
0
2
500
Siege Tower
SiegeTower Ranged
Garrison units for transport and to increase firepower.
circle/256x256.png
circle/256x256_mask.png
attack/siege/ram_move.xml
attack/siege/ram_move.xml
attack/impact/arrow_metal.xml
attack/weapon/arrowfly.xml
attack/siege/ram_trained.xml
12.0
0.7
0.7
80
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/plane.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/plane.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/plane.xml (revision 22184)
@@ -1,77 +1,80 @@
0.0
100.0
227.0
120
80
- 75.0
- 9.81
0
10000
- 1.0
0
+
+ 75.0
+ 1.0
+ 9.81
+
+
3
1
Infantry
true
0.0
3.0
7.0
1
0
Support Infantry
1
5
100
true
P-51 Mustang
A World War 2 American fighter plane.
units/global_mustang.png
1.0
true
0.0
3.0
60.0
50.0
40.0
25.0
5.0
10.0
2.0
2.0
50.0
15.0
true
unrestricted
100
units/global/plane.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml (revision 22184)
@@ -1,78 +1,81 @@
0.0
35.0
0.0
55.0
0.0
- 75.0
- 9.81
1000
2000
- 2.0
0
+
+ 75.0
+ 2.0
+ 9.81
+
+
Ship Human
3
13
1
Infantry Cavalry
3
25
150
150
8.0
30
0
FemaleCitizen Infantry Healer Dog
Support Infantry Cavalry Dog Siege Elephant
0
10
true
1400
Medium Warship
Ranged Warship
phase_town
Garrison units for transport and to increase firepower.
100
30
20
attack/impact/arrow_metal.xml
attack/weapon/arrowfly.xml
6.0
0.5
6.0
1.8
1.8
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_onager.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_onager.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_onager.xml (revision 22184)
@@ -1,88 +1,89 @@
0.0
10.0
100.0
80.0
26.0
- 37.5
- 9.81
4000
5000
- 4.0
0
+ 37.5
+ 4.0
+ 9.81
+
props/units/weapons/rock_explosion.xml
0.1
Circular
10
false
0.0
15.0
35.0
Structure
20
25
400
250
4.5
250
Siege Catapult
Catapult Ranged
300
0
20
10
0
square/256x256.png
square/256x256_mask.png
attack/impact/siegeprojectilehit.xml
attack/siege/ballist_attack.xml
standground
0.8
0.8
120
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/athen_champion_ranged_gastraphetes.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/athen_champion_ranged_gastraphetes.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/athen_champion_ranged_gastraphetes.xml (revision 22184)
@@ -1,27 +1,28 @@
0
6.5
0
76
0.0
- 100.0
2320
3000
- 1.0
+
+ 100.0
+
Human
athen
greek
Greek Gastraphetes
Toxótēs Skythikós
units/athen_champion_ranged.png
units/athenians/infantry_crossbowman_c.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/rome_mechanical_siege_onager.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/units/rome_mechanical_siege_onager.xml (revision 22183)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/units/rome_mechanical_siege_onager.xml (revision 22184)
@@ -1,49 +1,51 @@
76
26.0
- 37.5
- 9.81
3700
5000
Circular
10
false
40.0
0.0
40.0
+
+ 37.5
+ 9.81
+
300
100
5.0
rome
Onager
units/rome_siege_onager.png
0.001
0.001
88
units/romans/siege_onager_pivot.xml