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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/theb_mechanical_siege_fireraiser.xml (revision 20204) @@ -1,41 +1,42 @@ 50.0 0.0 50.0 12 8.0 10.0 9.81 2000 2000 2.0 + 0 Circular 20 true 200.0 200.0 200.0 4.5 gaia Fire Raiser Pyrobolos units/hele_mechanical_siege_lithobolos.png 60 units/thebans/siege_fireraiser.xml Index: ps/trunk/binaries/data/mods/public/simulation/components/Attack.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Attack.js (revision 20203) +++ ps/trunk/binaries/data/mods/public/simulation/components/Attack.js (revision 20204) @@ -1,603 +1,605 @@ 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" + "" + "" + "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 + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + // 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 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") }; 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; //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); 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); + cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000 + +this.template.Ranged.Delay, 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/tests/test_Damage.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Damage.js (revision 20203) +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Damage.js (revision 20204) @@ -1,538 +1,538 @@ 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/Sound.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 } } ); + let cmpAttack = ConstructComponent(attacker, "Attack", { "Ranged": { "ProjectileSpeed": 500, "Gravity": 9.81, "Spread": 0.5, "MaxRange": 50, "MinRange": 0, "Delay": 0 } } ); 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, "GetNumPlayers": () => 5 }); 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, "GetNumPlayers": () => 2 }); 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/other/plane.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/other/plane.xml (revision 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/other/plane.xml (revision 20204) @@ -1,77 +1,78 @@ 0.0 100.0 227.0 120 80 75.0 9.81 0 10000 1.0 + 0 3 1 Infantry true 0.0 3.0 7.0 1 0 Support Infantry 1 5 100 true gaia 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/structures/rome_army_camp.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml (revision 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml (revision 20204) @@ -1,115 +1,116 @@ -5.0 -5.0 -1.0 1.0 5.0 1.0 0.0 25.0 0.0 50.0 0.0 75.0 9.81 1200 2000 1.5 + 0 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 rubble/rubble_rome_sb rome Entrenched Army Camp Castrum Vallum ArmyCamp 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 attack/destruction/building_collapse_large.xml 37.5 60 structures/romans/camp.xml structures/fndn_8x8.xml 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml (revision 20204) @@ -1,130 +1,131 @@ 2 140 5 5 5 15 3 0.0 12.0 0.0 72.0 0.0 75.0 9.81 1200 2000 1.5 + 0 Human 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 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 phase_town_athen phase_city phase_city_athen unlock_spies spy_counter food wood stone metal true interface/complete/building/complete_civ_center.xml attack/weapon/arrowfly.xml attack/destruction/building_collapse_large.xml interface/alarm/alarm_alert_0.xml interface/alarm/alarm_alert_1.xml interface/alarm/alarm_alert_2.xml true 140 10000 90 structures/fndn_6x6.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml (revision 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml (revision 20204) @@ -1,103 +1,104 @@ 0.0 12.0 0.0 76.0 10.0 15 75.0 9.81 1200 2000 1.5 + 0 Human 1 1 Infantry DefenseTower DefenseTower 60 150 100 100 15.0 5 0.1 Unit Support Infantry 0 2 1000 rubble/rubble_stone_2x2 Defense Tower Shoots arrows. Garrison to provide extra defense. Needs the murder holes tech to protect its foot. Tower GarrisonTower -ConquestCritical Town DefenseTower structures/defense_tower.png phase_town 0 20 0.7 attack_tower_watch attack_tower_crenellations attack_tower_range attack_tower_murderholes attack_tower_defense interface/complete/building/complete_tower.xml attack/weapon/arrowfly.xml attack/destruction/building_collapse_large.xml 6.0 0.6 21.0 false 32 30000 80 structures/fndn_2x2.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml (revision 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml (revision 20204) @@ -1,104 +1,105 @@ 5 20 1 1 5 1 0.0 16.0 0.0 55.0 13.0 75.0 9.81 1200 2000 1.5 + 0 Human 1 Outpost Outpost 50 own neutral 40 80 0 15.0 1 0.1 Unit Support Infantry 0 2 800 rubble/rubble_stone_2x2 Outpost Build in neutral and own territories to scout areas of the map. Slowly converts to Gaia while in neutral territory. -ConquestCritical Village Outpost structures/outpost.png 8 0.7 vision_outpost decay_outpost interface/complete/building/complete_tower.xml attack/destruction/building_collapse_large.xml 6.0 0.6 18.0 2 80 props/special/palisade_rocks_outpost.xml structures/fndn_2x2.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml (revision 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml (revision 20204) @@ -1,101 +1,102 @@ 0.0 8.0 0.0 72.0 12.0 75.0 9.81 1200 2000 1.5 + 0 Human 0 1 Infantry land-shore Wall 1200 80 90 8.0 2 0.1 Unit Support Infantry 0 2 4000 rubble/rubble_stone_wall_tower Wall Turret Shoots arrows. Garrison to defend a city wall against attackers. -ConquestCritical StoneWall Tower structures/tower.png phase_town 0 15 0.8 pair_walls_01 4.5 interface/complete/building/complete_tower.xml attack/weapon/arrowfly.xml attack/destruction/building_collapse_large.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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml (revision 20204) @@ -1,112 +1,113 @@ 5 5 3 0.0 16.0 0.0 72.0 0.0 75.0 9.81 1200 2000 1.5 + 0 Human 3 1 Fortress Fortress 80 4000 10.0 10 500 0 1000 8.0 20 0.075 Unit Support Infantry Cavalry Siege 0 6 4200 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 attack/weapon/arrowfly.xml attack/destruction/building_collapse_large.xml false 100 40000 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml (revision 20204) @@ -1,33 +1,34 @@ 0 9.0 0 16.0 0.0 75.0 9.81 1000 1500 3.0 + 0 Human 120 Ranged Cavalry Ranged formations/skirmish attack/weapon/arrowfly.xml 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_archer.xml (revision 20204) @@ -1,44 +1,45 @@ 0 14.0 0 76.0 0.0 75.0 9.81 500 1000 1.0 + 0 Human 100 Ranged Archer Champion Cavalry Archer. formations/skirmish actor/fauna/death/death_horse.xml attack/weapon/arrowfly.xml 20.5 28.0 1000.0 10.0 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_javelinist.xml (revision 20204) @@ -1,44 +1,45 @@ 0.0 36.0 0.0 32.0 0.0 62.5 9.81 750 1250 1.0 + 0 Human 100 Ranged Javelin Champion Cavalry Skirmisher formations/skirmish actor/fauna/death/death_horse.xml attack/weapon/arrowfly.xml 20.5 28.0 1000.0 10.0 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml (revision 20204) @@ -1,44 +1,45 @@ 0 6.5 0 76 0.0 75.0 9.81 300 500 1.0 + 0 Human 100 120 Ranged Archer Champion Archer formations/skirmish attack/weapon/arrowfly.xml 11.0 18.0 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelinist.xml (revision 20204) @@ -1,44 +1,45 @@ 0 26.0 0 28.0 0.0 62.5 9.81 500 1000 1.0 + 0 Human 100 120 Ranged Javelin Champion Skirmisher formations/skirmish attack/weapon/arrowfly.xml 16.0 18.0 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml (revision 20204) @@ -1,25 +1,26 @@ 0 35 0 80.0 0.0 75.0 9.81 1200 2000 0.5 + 0 Human Ranged Archer Hero Cavalry Archer 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_javelinist.xml (revision 20204) @@ -1,40 +1,41 @@ 0.0 60.0 0.0 36.0 0.0 62.5 9.81 750 1250 0.5 + 0 Human 3.0 1500 formations/skirmish Hero Cavalry Skirmisher Javelin Ranged 17.0 28.0 1000.0 16.0 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_archer.xml (revision 20204) @@ -1,30 +1,31 @@ 0 8.0 0 80 0.0 75.0 9.81 200 300 0.5 + 0 Human Hero Archer Ranged Archer formations/skirmish attack/weapon/arrowfly.xml 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_hero_infantry_javelinist.xml (revision 20204) @@ -1,30 +1,31 @@ 0 50.0 0 32.0 0.0 62.5 9.81 600 1000 0.5 + 0 Human Ranged Javelin Hero Skirmisher formations/skirmish attack/weapon/arrowfly.xml 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml (revision 20204) @@ -1,38 +1,39 @@ 1 1 10 0 1.5 0 10.0 0.0 75.0 9.81 750 1250 3.0 + 0 Human 50 Ranged Ranged formations/skirmish attack/weapon/arrowfly.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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml (revision 20204) @@ -1,77 +1,78 @@ 0.0 35.0 0.0 45.0 0.0 75.0 9.81 1000 2000 2.0 + 0 Ship Human 2 10 1 Infantry Cavalry 2 20 125 50 10.0 20 0 FemaleCitizen Infantry 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/weapon/arrowfly.xml attack/impact/arrow_metal.xml 6.0 0.5 6.0 14 18.0 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml (revision 20204) @@ -1,88 +1,89 @@ 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 Ship Structure 1 10 1 Catapult 3 30 350 200 350 8.0 50 0 FemaleCitizen Infantry 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 16 20 110 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml (revision 20204) @@ -1,77 +1,78 @@ 0.0 35.0 0.0 55.0 0.0 75.0 9.81 1000 2000 2.0 + 0 Ship Human 3 13 1 Infantry Cavalry 3 25 150 150 8.0 30 0 FemaleCitizen Infantry 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/weapon/arrowfly.xml attack/impact/arrow_metal.xml 6.0 0.5 6.0 16 20.0 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ballista.xml (revision 20204) @@ -1,72 +1,73 @@ 0.0 150.0 25.0 80.0 8.0 2.0 150.0 9.81 3000 4000 + 0 Linear 8.0 false 0.0 75.0 5.0 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 8 12.0 120 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_onager.xml (revision 20204) @@ -1,80 +1,81 @@ 0.0 10.0 100.0 80.0 12.0 37.5 9.81 4000 5000 4.0 + 0 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 circle/256x256.png circle/256x256_mask.png attack/siege/ballist_attack.xml 4.0 0.5 7 10.0 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 20203) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml (revision 20204) @@ -1,82 +1,83 @@ 0.0 12.0 2.5 55.0 0.0 10 75.0 9.81 1200 2000 2.0 + 0 Human 0 1 10 Infantry 60 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_attack.xml attack/siege/ram_move.xml 4.0 0.5 12.0 6.5 10.0 80