Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/ship_speed.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/ship_speed.json (revision 14245) +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/ship_speed.json (nonexistent) @@ -1,5 +0,0 @@ -{ - "affects":["Ship"], - "affectedPlayers":["Player","MutualAlly"], - "modifications":[{"value":"UnitMotion/WalkSpeed","multiply":1.5}] -} Index: ps/trunk/binaries/data/mods/public/simulation/components/Auras.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Auras.js (revision 14245) +++ ps/trunk/binaries/data/mods/public/simulation/components/Auras.js (revision 14246) @@ -1,364 +1,368 @@ function Auras() {} Auras.prototype.Schema = "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "formation" + "range" + "garrison" + "global" + "" + "" + "" + ""; Auras.prototype.Init = function() { var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); this.templateName = cmpTemplateManager.GetCurrentTemplateName(this.entity); var auraNames = this.GetAuraNames(); this.auras = {}; var cmpTechnologyTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TechnologyTemplateManager); for each (var name in auraNames) this.auras[name] = cmpTechnologyTemplateManager.GetAuraTemplate(name); }; Auras.prototype.GetAuraNames = function() { return Object.keys(this.template); }; Auras.prototype.GetRange = function(name) { if (!this.IsRangeAura(name)) return undefined; if (this.IsGlobalAura(name)) return -1; // -1 is infinite range return +this.template[name].Radius; }; Auras.prototype.GetClasses = function(name) { return this.auras[name].affects; }; Auras.prototype.GetModifications = function(name) { return this.auras[name].modifications; }; Auras.prototype.GetAffectedPlayers = function(name) { if (this.auras[name].affectedPlayers) var affectedPlayers = this.auras[name].affectedPlayers; else var affectedPlayers = ["Player"]; var ret = []; var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); if (!cmpPlayer) return ret; var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); var numPlayers = cmpPlayerManager.GetNumPlayers(); for (var i = 0; i < numPlayers; ++i) { for each (var p in affectedPlayers) { if (p == "Player" ? cmpPlayer.GetPlayerID() == i : cmpPlayer["Is" + p](i)) { ret.push(i); break; } } } return ret; }; Auras.prototype.HasFormationAura = function() { return this.GetAuraNames().some(this.IsFormationAura.bind(this)); }; Auras.prototype.HasGarrisonAura = function() { return this.GetAuraNames().some(this.IsGarrisonAura.bind(this)); }; Auras.prototype.GetType = function(name) { return this.template[name].Type; }; Auras.prototype.IsFormationAura = function(name) { return this.GetType(name) == "formation"; }; Auras.prototype.IsGarrisonAura = function(name) { return this.GetType(name) == "garrison"; }; Auras.prototype.IsRangeAura = function(name) { // A global aura is also treated as a range aura with infinite range. return ["range", "global"].indexOf(this.GetType(name)) != -1; }; Auras.prototype.IsGlobalAura = function(name) { return this.GetType(name) == "global"; }; /** * clean all bonuses. Remove the old ones and re-apply the new ones */ Auras.prototype.Clean = function() { var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); var auraNames = this.GetAuraNames(); // remove all bonuses for each (var name in auraNames) { if (!this[name]) continue; if (this.IsGlobalAura(name)) this.RemoveTemplateBonus(name); for each(var ent in this[name].targetUnits) this.RemoveBonus(name, ent); if (this[name].rangeQuery) cmpRangeManager.DestroyActiveQuery(this[name].rangeQuery); } for each (var name in auraNames) { // initialise range query this[name] = {}; this[name].targetUnits = []; var affectedPlayers = this.GetAffectedPlayers(name); if (!affectedPlayers.length) continue; - if (this.IsGlobalAura(name)) - this.ApplyTemplateBonus(name, affectedPlayers); - if (!this.IsRangeAura(name)) continue; + this[name].rangeQuery = cmpRangeManager.CreateActiveQuery( this.entity, 0, this.GetRange(name), affectedPlayers, IID_Identity, cmpRangeManager.GetEntityFlagMask("normal") ); cmpRangeManager.EnableActiveQuery(this[name].rangeQuery); - // Add self to your own query for consistency with templates. - this.OnRangeUpdate({"tag":this[name].rangeQuery, "added":[this.entity], "removed":[]}); + + if (this.IsGlobalAura(name)) + { + // update stats in for all templates + this.ApplyTemplateBonus(name, affectedPlayers); + // Add self to your own query for consistency with templates. + this.OnRangeUpdate({"tag":this[name].rangeQuery, "added":[this.entity], "removed":[]}); + } } }; Auras.prototype.GiveMembersWithValidClass = function(auraName, entityList) { var validClasses = this.GetClasses(auraName); var r = []; for each (var ent in entityList) { var cmpIdentity = Engine.QueryInterface(ent, IID_Identity); var targetClasses = cmpIdentity.GetClassesList(); for each (var classCollection in validClasses) { if (classCollection.split(/\s+/).every(function(c) {return targetClasses.indexOf(c) > -1})) { r.push(ent); break; } } } return r; } Auras.prototype.OnRangeUpdate = function(msg) { var auraNames = this.GetAuraNames(); for each (var n in auraNames) { if (msg.tag == this[n].rangeQuery) { var name = n; break; } } if (!name) return; var targetUnits = this[name].targetUnits; var classes = this.GetClasses(name); if (msg.added.length > 0) { var validList = this.GiveMembersWithValidClass(name, msg.added); for each (var e in validList) { targetUnits.push(e); this.ApplyBonus(name, e); } } if (msg.removed.length > 0) { for each (var e in msg.removed) { targetUnits.splice(targetUnits.indexOf(e), 1); this.RemoveBonus(name, e); } } }; Auras.prototype.ApplyFormationBonus = function(memberList) { var auraNames = this.GetAuraNames(); for each (var name in auraNames) { if (!this.IsFormationAura(name)) continue; var validList = this.GiveMembersWithValidClass(name, memberList); for each (var ent in validList) { this[name].targetUnits.push(ent); this.ApplyBonus(name,ent); } } }; Auras.prototype.ApplyGarrisonBonus = function(structure) { var auraNames = this.GetAuraNames(); for each (var name in auraNames) { if (!this.IsGarrisonAura(name)) continue; var validList = this.GiveMembersWithValidClass(name, [structure]); if (validList.length) { this[name].targetUnits.push(validList[0]); this.ApplyBonus(name,validList[0]); } } }; Auras.prototype.ApplyTemplateBonus = function(name, players) { if (!this.IsGlobalAura(name)) return; var modifications = this.GetModifications(name); var cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager); var classes = this.GetClasses(name); for each (var mod in modifications) for each (var player in players) cmpAuraManager.ApplyTemplateBonus(mod.value, player, classes, mod, this.templateName + "/" + name + "/" + mod.value); }; Auras.prototype.RemoveFormationBonus = function(memberList) { var auraNames = this.GetAuraNames(); for each (var name in auraNames) { if (!this.IsFormationAura(name)) continue; for each (var ent in memberList) { this.RemoveBonus(name,ent); this[name].targetUnits.splice(this[name].targetUnits.indexOf(ent), 1); } } }; Auras.prototype.RemoveGarrisonBonus = function(structure) { var auraNames = this.GetAuraNames(); for each (var name in auraNames) { if (!this.IsGarrisonAura(name)) continue; this.RemoveBonus(name,structure); this[name].targetUnits.splice(this[name].targetUnits.indexOf(structure), 1); } }; Auras.prototype.RemoveTemplateBonus = function(name) { if (!this.IsGlobalAura(name)) return; var modifications = this.GetModifications(name); var cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager); var classes = this.GetClasses(name); var players = this.GetAffectedPlayers(name); for each (var mod in modifications) for each (var player in players) cmpAuraManager.RemoveTemplateBonus(mod.value, player, classes, this.templateName + "/" + name + "/" + mod.value); }; Auras.prototype.ApplyBonus = function(name, ent) { var modifications = this.GetModifications(name); var cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager); for each (var mod in modifications) cmpAuraManager.ApplyBonus(mod.value, ent, mod, this.templateName + "/" + name + "/" + mod.value); }; Auras.prototype.RemoveBonus = function(name, ent) { var modifications = this.GetModifications(name); var cmpAuraManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_AuraManager); for each (var mod in modifications) cmpAuraManager.RemoveBonus(mod.value, ent, this.templateName + "/" + name + "/" + mod.value); }; Auras.prototype.OnOwnershipChanged = function(msg) { this.Clean(); }; Auras.prototype.OnDiplomacyChanged = function(msg) { var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); if (cmpOwnership && cmpOwnership.GetOwner() == msg.player) this.Clean(); }; Auras.prototype.OnValueModification = function(msg) { if (msg.component == "Auras") this.Clean(); }; Engine.RegisterComponentType(IID_Auras, "Auras", Auras); Index: ps/trunk/binaries/data/mods/public/simulation/components/Health.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/Health.js (revision 14245) +++ ps/trunk/binaries/data/mods/public/simulation/components/Health.js (revision 14246) @@ -1,359 +1,356 @@ function Health() {} Health.prototype.Schema = "Deals with hitpoints and death." + "" + "100" + "1.0" + "corpse" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "vanish" + "corpse" + "remain" + "" + "" + "" + "" + "" + "" + "" + ""; Health.prototype.Init = function() { // Cache this value so it allows techs to maintain previous health level this.maxHitpoints = +this.template.Max; // Default to , but use if it's undefined or zero // (Allowing 0 initial HP would break our death detection code) this.hitpoints = +(this.template.Initial || this.GetMaxHitpoints()); - this.regenRate = +this.template.RegenRate; + this.regenRate = ApplyValueModificationsToEntity("Health/RegenRate", +this.template.RegenRate, this.entity); this.CheckRegenTimer(); }; //// Interface functions //// /** * Returns the current hitpoint value. * This is 0 if (and only if) the unit is dead. */ Health.prototype.GetHitpoints = function() { return this.hitpoints; }; Health.prototype.GetMaxHitpoints = function() { return this.maxHitpoints; }; Health.prototype.SetHitpoints = function(value) { // If we're already dead, don't allow resurrection if (this.hitpoints == 0) return; var old = this.hitpoints; this.hitpoints = Math.max(1, Math.min(this.GetMaxHitpoints(), value)); var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); if (cmpRangeManager) { if (this.hitpoints < this.GetMaxHitpoints()) cmpRangeManager.SetEntityFlag(this.entity, "injured", true); else cmpRangeManager.SetEntityFlag(this.entity, "injured", false); } Engine.PostMessage(this.entity, MT_HealthChanged, { "from": old, "to": this.hitpoints }); }; Health.prototype.IsRepairable = function() { return (this.template.Repairable == "true"); }; Health.prototype.IsUnhealable = function() { return (this.template.Unhealable == "true" || this.GetHitpoints() <= 0 || this.GetHitpoints() >= this.GetMaxHitpoints()); }; Health.prototype.GetRegenRate = function() { return this.regenRate; }; Health.prototype.ExecuteRegeneration = function() { var regen = this.GetRegenRate(); if (regen > 0) this.Increase(regen); else this.Reduce(-regen); }; /* * Check if the regeneration timer needs to be started or stopped */ Health.prototype.CheckRegenTimer = function() { // check if we need a timer if (this.GetRegenRate() == 0 || this.GetHitpoints() == this.GetMaxHitpoints() && this.GetRegenRate() > 0 || this.GetHitpoints() == 0) { // we don't need a timer, disable if one exists if (this.regenTimer) { var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); cmpTimer.CancelTimer(this.regenTimer); this.regenTimer = undefined; } return; } - // we need a timer, enable is one doesn't exist + // we need a timer, enable if one doesn't exist if (this.regenTimer) return; var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); this.regenTimer = cmpTimer.SetInterval(this.entity, IID_Health, "ExecuteRegeneration", 1000, 1000, null); }; Health.prototype.Kill = function() { this.Reduce(this.hitpoints); }; /** * Reduces entity's health by amount HP. * Returns object of the form { "killed": false, "change": -12 } */ Health.prototype.Reduce = function(amount) { var state = { "killed": false }; if (amount >= 0 && this.hitpoints == this.GetMaxHitpoints()) { var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); if (cmpRangeManager) cmpRangeManager.SetEntityFlag(this.entity, "injured", true); } var oldHitpoints = this.hitpoints; if (amount >= this.hitpoints) { // If this is the first time we reached 0, then die. // (The entity will exist a little while after calling DestroyEntity so this // might get called multiple times) if (this.hitpoints) { state.killed = true; PlaySound("death", this.entity); // If SpawnEntityOnDeath is set, spawn the entity if(this.template.SpawnEntityOnDeath) this.CreateDeathSpawnedEntity(); if (this.template.DeathType == "corpse") { this.CreateCorpse(); Engine.DestroyEntity(this.entity); } else if (this.template.DeathType == "vanish") { Engine.DestroyEntity(this.entity); } else if (this.template.DeathType == "remain") { var resource = this.CreateCorpse(true); if (resource != INVALID_ENTITY) Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: resource }); Engine.DestroyEntity(this.entity); } this.hitpoints = 0; Engine.PostMessage(this.entity, MT_HealthChanged, { "from": oldHitpoints, "to": this.hitpoints }); } } else { this.hitpoints -= amount; Engine.PostMessage(this.entity, MT_HealthChanged, { "from": oldHitpoints, "to": this.hitpoints }); } state.change = this.hitpoints - oldHitpoints; return state; }; Health.prototype.Increase = function(amount) { if (this.hitpoints == this.GetMaxHitpoints()) return {"old": this.hitpoints, "new":this.hitpoints}; // If we're already dead, don't allow resurrection if (this.hitpoints == 0) return undefined; var old = this.hitpoints; this.hitpoints = Math.min(this.hitpoints + amount, this.GetMaxHitpoints()); if (this.hitpoints == this.GetMaxHitpoints()) { var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); if (cmpRangeManager) cmpRangeManager.SetEntityFlag(this.entity, "injured", false); } Engine.PostMessage(this.entity, MT_HealthChanged, { "from": old, "to": this.hitpoints }); // We return the old and the actual hp return { "old": old, "new": this.hitpoints}; }; //// Private functions //// Health.prototype.CreateCorpse = function(leaveResources) { // If the unit died while not in the world, don't create any corpse for it // since there's nowhere for the corpse to be placed var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); if (!cmpPosition.IsInWorld()) return INVALID_ENTITY; // Either creates a static local version of the current entity, or a // persistent corpse retaining the ResourceSupply element of the parent. var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); var templateName = cmpTempMan.GetCurrentTemplateName(this.entity); var corpse; if (leaveResources) corpse = Engine.AddEntity("resource|" + templateName); else corpse = Engine.AddLocalEntity("corpse|" + templateName); // Copy various parameters so it looks just like us var cmpCorpsePosition = Engine.QueryInterface(corpse, IID_Position); var pos = cmpPosition.GetPosition(); cmpCorpsePosition.JumpTo(pos.x, pos.z); var rot = cmpPosition.GetRotation(); cmpCorpsePosition.SetYRotation(rot.y); cmpCorpsePosition.SetXZRotation(rot.x, rot.z); var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); var cmpCorpseOwnership = Engine.QueryInterface(corpse, IID_Ownership); cmpCorpseOwnership.SetOwner(cmpOwnership.GetOwner()); var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); var cmpCorpseVisual = Engine.QueryInterface(corpse, IID_Visual); cmpCorpseVisual.SetActorSeed(cmpVisual.GetActorSeed()); // Make it fall over cmpCorpseVisual.SelectAnimation("death", true, 1.0, ""); return corpse; }; Health.prototype.CreateDeathSpawnedEntity = function() { // If the unit died while not in the world, don't spawn a death entity for it // since there's nowhere for it to be placed var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); if (!cmpPosition.IsInWorld()) return INVALID_ENTITY; // Create SpawnEntityOnDeath entity var spawnedEntity = Engine.AddLocalEntity(this.template.SpawnEntityOnDeath); // Move to same position var cmpSpawnedPosition = Engine.QueryInterface(spawnedEntity, IID_Position); var pos = cmpPosition.GetPosition(); cmpSpawnedPosition.JumpTo(pos.x, pos.z); var rot = cmpPosition.GetRotation(); cmpSpawnedPosition.SetYRotation(rot.y); cmpSpawnedPosition.SetXZRotation(rot.x, rot.z); var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); var cmpSpawnedOwnership = Engine.QueryInterface(spawnedEntity, IID_Ownership); if (cmpOwnership && cmpSpawnedOwnership) cmpSpawnedOwnership.SetOwner(cmpOwnership.GetOwner()); return spawnedEntity; }; Health.prototype.Repair = function(builderEnt, work) { var damage = this.GetMaxHitpoints() - this.GetHitpoints(); // Do nothing if we're already at full hitpoints if (damage <= 0) return; // Calculate the amount of hitpoints that will be added // TODO: what computation should we use? // TODO: should we do some diminishing returns thing? (see Foundation.Build) var amount = Math.min(damage, work); // TODO: resource costs? // Add hitpoints this.Increase(amount); // If we repaired all the damage, send a message to entities // to stop repairing this building if (amount >= damage) { Engine.PostMessage(this.entity, MT_ConstructionFinished, { "entity": this.entity, "newentity": this.entity }); } }; Health.prototype.OnValueModification = function(msg) { if (msg.component != "Health") return; var oldMaxHitpoints = this.GetMaxHitpoints(); var newMaxHitpoints = Math.round(ApplyValueModificationsToEntity("Health/Max", +this.template.Max, this.entity)); if (oldMaxHitpoints != newMaxHitpoints) { var newHitpoints = Math.round(this.GetHitpoints() * newMaxHitpoints/oldMaxHitpoints); this.maxHitpoints = newMaxHitpoints; this.SetHitpoints(newHitpoints); } var oldRegenRate = this.regenRate; this.regenRate = ApplyValueModificationsToEntity("Health/RegenRate", +this.template.RegenRate, this.entity); - if (this.IsUnhealable()) - this.regenRate = Math.min(0,this.regenRate); - if (this.regenRate != oldRegenRate) this.CheckRegenTimer(); }; Health.prototype.OnHealthChanged = function() { this.CheckRegenTimer(); }; Engine.RegisterComponentType(IID_Health, "Health", Health); Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/champion_attack_5.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/champion_attack_5.json (nonexistent) +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/champion_attack_5.json (revision 14246) @@ -0,0 +1,5 @@ +{ + "affects":["Champion"], + "affectedPlayers":["Player"], + "modifications":[{"value":"Attack/Ranged/Pierce","add":5},{"value":"Attack/Melee/Hack","add":5}] +} Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/champion_speed_25.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/champion_speed_25.json (nonexistent) +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/champion_speed_25.json (revision 14246) @@ -0,0 +1,5 @@ +{ + "affects":["Champion"], + "affectedPlayers":["Player"], + "modifications":[{"value":"UnitMotion/WalkSpeed","multiply":1.25}] +} Index: ps/trunk/binaries/data/mods/public/simulation/data/auras/unit_attack_2.json =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/data/auras/unit_attack_2.json (nonexistent) +++ ps/trunk/binaries/data/mods/public/simulation/data/auras/unit_attack_2.json (revision 14246) @@ -0,0 +1,5 @@ +{ + "affects":["Unit"], + "affectedPlayers":["Ally"], + "modifications":[{"value":"Attack/Ranged/Pierce","add":2},{"value":"Attack/Melee/Hack","add":2}] +} Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca.xml (revision 14245) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca.xml (revision 14246) @@ -1,46 +1,54 @@ + + + global + + + global + + 7 7 8 4 50 200 100 100 5.0 brit Chariot Boudicca - Hero Aura: Increased Attack and Speed for Champion Units. + Hero Aura: +5 Attack and +25% Speed for Champion Units. Ammianus Marcellinus described how difficult it would be for a band of foreigners to deal with a Celt if he called in the help of his wife. For she was stronger than he was and could rain blows and kicks upon the assailants equal in force to the shots of a catapult. Boudicca, queen of the Iceni, was said to be 'very tall and terrifying in appearance; her voice was very harsh and a great mass of red hair fell over her shoulders. She wore a tunic of many colors over which a thick cloak was fastened by a brooch. units/celt_hero_boudicca.png voice/hellenes/civ/female/civ_female_walk.xml voice/hellenes/civ/female/civ_female_go_out_against.xml voice/hellenes/civ/female/civ_female_gather_together.xml voice/hellenes/civ/female/civ_female_repair.xml voice/hellenes/civ/female/civ_female_garrison.xml actor/human/death/female_death.xml units/celts/boudicca_chariot.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca_sword.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca_sword.xml (revision 14245) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca_sword.xml (revision 14246) @@ -1,19 +1,27 @@ + + + global + + + global + + 5 6 6 brit Boudicca Heroine - Hero Aura: TBD. + Hero Aura: +5 Attack and +25% Speed for Champion units. . units/celt_hero_boudicca.png units/celts/boudicca.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_caratacos.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_caratacos.xml (revision 14245) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_caratacos.xml (revision 14246) @@ -1,26 +1,31 @@ + + + global + + 6 6 7 27.0 54.0 brit Caratacos - Hero Aura: Increased Speed for all units during his lifetime. + Hero Aura: +15% Speed for all units during his lifetime. Caratacos's name is better known in its Romanized form, Caratacus. Under this name he is remembered as a fierce defender of Britain against the Romans after their invasion in 43 AD. Son of King Cynvelin of the Catuvellauni tribe, Caratacos fought for nine years against the Romans with little success, eventually fleeing to the tribes in Wales, where he was defeated decisively. Finally he entered Northern Britain, where was handed over to the Romans. Taken to Rome, Caratacos was allowed to live by the Emperor Claudius and died in Italy. units/celt_hero_caratacos.png units/celts/caradoc.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_cynvelin.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_cynvelin.xml (revision 14245) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/brit_hero_cynvelin.xml (revision 14246) @@ -1,16 +1,22 @@ + + + range + 60 + + 10 brit Cynvelin - Hero Aura: Has a large and powerful Healing Aura, only useful when idle. + Hero Aura: Has a large and powerful Healing Aura. Cynvelin was a powerful ruler centered in the territory around modern day London. Ruling the Catuvellauni from Camulodunum, he was a warrior king who conquered a neighboring tribe and was referred to by the Romans as the King of the Britons. Eventually Cynvelin retired to become the arch-druid of Siluria, but was taken to Rome with his son upon Caratacos's capture. According to legend, the Apostle Paul baptized Cynvelin into Christianity before he died in Italy. units/celt_hero_cynvelin.png units/celts/cynvelin_horse.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/cart_hero_hamilcar.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/cart_hero_hamilcar.xml (revision 14245) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/cart_hero_hamilcar.xml (revision 14246) @@ -1,17 +1,22 @@ + + + global + + cart Ḥimelqart Baraq Hamilcar Barca Father of Hannibal and virtual military dictator of Carthage. Hamilcar Barca was a soldier and politician who excelled along his entire career. Lived 275-228 BC. While overshadowed by his sons, Hamilcar was great general in his own right, earning the nickname Baraq or Barca for the "lightning" speed of his advance. units/cart_hero_hamilcar.png Classes: Hero Melee Cavalry Sword. "Lightning" Aura: All of the player's units +15% movement speed (walk and run, but not charge) while he lives. Counters: 2x vs. Archers, All Support Units, and Siege Weapons. Countered by: Spearmen, Cavalry Skirmishers, and Elephants. units/carthaginians/hero_hamilcar_horse.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/units/cart_hero_hannibal.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/units/cart_hero_hannibal.xml (revision 14245) +++ ps/trunk/binaries/data/mods/public/simulation/templates/units/cart_hero_hannibal.xml (revision 14246) @@ -1,18 +1,24 @@ + + + range + 60 + + cart Hannibal Barca Ḥannibaʿal Baraq Carthage's most famous son. Hannibal Barca was the eldest son of Hamilcar Barca and proved an even greater commander than his father. Lived 247-182 BC. While he ultimately lost the Second Punic War, his victories at Trebia, Lake Trasimene, and Cannae, and the feat of crossing the Alps have secured his position as among the best tacticians and strategists in history. units/cart_hero_hannibal.png Classes: Hero Melee Elephant. -"Tactician" Aura: All allied units +15% attack within vision range of him. +"Tactician" Aura: All allied units +2 attack within vision range of him. "Strategist" Ability: The player can see changes within the fog of war while Hannibal lives. Counters: 2x vs. All Cavalry, 1.5x vs. All Structures. Extra 1.5x vs. Gates. Countered by: Skirmishers and Swordsmen. Can run amok. units/carthaginians/hero_hannibal_mount.xml