Index: binaries/data/mods/public/art/actors/units/celts/boudicca_chariot.xml =================================================================== --- binaries/data/mods/public/art/actors/units/celts/boudicca_chariot.xml +++ binaries/data/mods/public/art/actors/units/celts/boudicca_chariot.xml @@ -12,7 +12,6 @@ - Index: binaries/data/mods/public/art/actors/units/celts/champion_unit_4.xml =================================================================== --- binaries/data/mods/public/art/actors/units/celts/champion_unit_4.xml +++ binaries/data/mods/public/art/actors/units/celts/champion_unit_4.xml @@ -13,7 +13,6 @@ - Index: binaries/data/mods/public/art/actors/units/mauryans/hero_chariot.xml =================================================================== --- binaries/data/mods/public/art/actors/units/mauryans/hero_chariot.xml +++ binaries/data/mods/public/art/actors/units/mauryans/hero_chariot.xml @@ -14,8 +14,6 @@ - - Index: binaries/data/mods/public/art/actors/units/persians/cavalry_archer_a.xml =================================================================== --- binaries/data/mods/public/art/actors/units/persians/cavalry_archer_a.xml +++ binaries/data/mods/public/art/actors/units/persians/cavalry_archer_a.xml @@ -14,7 +14,6 @@ - Index: binaries/data/mods/public/art/actors/units/persians/cavalry_archer_e.xml =================================================================== --- binaries/data/mods/public/art/actors/units/persians/cavalry_archer_e.xml +++ binaries/data/mods/public/art/actors/units/persians/cavalry_archer_e.xml @@ -15,7 +15,6 @@ - Index: binaries/data/mods/public/simulation/components/Attack.js =================================================================== --- binaries/data/mods/public/simulation/components/Attack.js +++ binaries/data/mods/public/simulation/components/Attack.js @@ -155,6 +155,9 @@ "" + "" + "" + + ""+ + "" + + "" + "" + "" + "" + @@ -202,9 +205,10 @@ Attack.prototype.Init = function() { + this.latestTarget = INVALID_ENTITY; }; -Attack.prototype.Serialize = null; // we have no dynamic state to save +Attack.prototype.Serialize = null; // we have no dynamic state to save Attack.prototype.GetAttackTypes = function() { @@ -492,8 +496,12 @@ * and should only be called after GetTimers().repeat msec has passed since the last * call to PerformAttack. */ -Attack.prototype.PerformAttack = function(type, target) +Attack.prototype.PerformAttack = function(type, target, turretId) { + if(this.template[type].TurretsOnly) + if (!turretId && this.template[type].TurretsOnly == "true") + return; + // If this is a ranged attack, then launch a projectile if (type == "Ranged") { @@ -561,7 +569,7 @@ var graphicalPosition = Vector3D.mult(missileDirection, 2).add(realTargetPosition); // Launch the graphical projectile var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); - var id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity); + var id = cmpProjectileManager.LaunchProjectileAtPoint(turretId || this.entity, realTargetPosition, horizSpeed, gravity); var playerId = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner(); var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); Index: binaries/data/mods/public/simulation/components/TurretAI.js =================================================================== --- binaries/data/mods/public/simulation/components/TurretAI.js +++ binaries/data/mods/public/simulation/components/TurretAI.js @@ -0,0 +1,344 @@ +function TurretAI() {} +TurretAI.prototype.Schema = + ""; + +TurretAI.prototype.g_MaxPreferenceBonus = 2; +TurretAI.prototype.g_LatestTarget = 0 ; + +/** + * Initialize TurretAI Component. + */ +TurretAI.prototype.Init = function() +{ +}; + +/** + * Get the turrent parent. + */ +TurretAI.prototype.GetTurretParent = function() +{ + var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); + if (!cmpPosition) + return INVALID_ENTITY; + return cmpPosition.GetTurretParent(); +}; + +/** + * Get the attack component + */ +TurretAI.prototype.GetCmpAttack = function() +{ + // use own attack, or turretHolder's attack + var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (cmpAttack) + return cmpAttack; + return Engine.QueryInterface(this.GetTurretParent(), IID_Attack); +}; + +/** + * Sends the message to change ownership + */ +TurretAI.prototype.OnOwnershipChanged = function(msg) +{ + var cmpAttack = this.GetCmpAttack(); + if (!cmpAttack) + return; + //Return the first possible Attack Type, Capturing being the last. + this.bestAttack = cmpAttack.GetAttackTypes()[0]; + + // Remove current targets, to prevent them from being added twice + this.targetUnits = []; + + if (msg.to != -1) + this.SetupRangeQuery(msg.to); + + // Non-Gaia buildings should attack certain Gaia units. + if (msg.to != 0 || this.gaiaUnitsQuery) + this.SetupGaiaRangeQuery(msg.to); +}; + +/** + * Change Diplomacy State + */ +TurretAI.prototype.OnDiplomacyChanged = function(msg) +{ + var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + if (cmpOwnership && cmpOwnership.GetOwner() == msg.player) + { + // Remove now allied/neutral units + this.targetUnits = []; + this.SetupRangeQuery(msg.player); + } +}; + +/** + * Cleanup on destroy + */ +TurretAI.prototype.OnDestroy = function() +{ + if (this.timer) + { + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.CancelTimer(this.timer); + this.timer = undefined; + } + + // Clean up range queries + var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + if (this.enemyUnitsQuery) + cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); + if (this.gaiaUnitsQuery) + cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); +}; + +/** + * Setup the Range Query to detect units coming in & out of range + */ +TurretAI.prototype.SetupRangeQuery = function(owner) +{ + var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); + var cmpAttack = this.GetCmpAttack(); + if (!cmpAttack) + return; + + if (this.enemyUnitsQuery) + { + cmpRangeManager.DestroyActiveQuery(this.enemyUnitsQuery); + this.enemyUnitsQuery = undefined; + } + + var players = []; + var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(owner), IID_Player); + var numPlayers = cmpPlayerManager.GetNumPlayers(); + + for (let i = 1; i < numPlayers; ++i) + { + // TODO: How to handle neutral players - Special query to attack military only? + if (cmpPlayer.IsEnemy(i)) + players.push(i); + } + + var range = cmpAttack.GetRange(this.bestAttack); + this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(this.entity, range.min, range.max, range.elevationBonus, players, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal")); + cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery); +}; + +/** + *Set up a range query for Gaia units within LOS range which can be attacked. + * This should be called whenever our ownership changes. + */ +TurretAI.prototype.SetupGaiaRangeQuery = function() +{ + var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + var owner = cmpOwnership.GetOwner(); + + var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); + var cmpAttack = this.GetCmpAttack(); + if (!cmpAttack) + return; + + if (this.gaiaUnitsQuery) + { + cmpRangeManager.DestroyActiveQuery(this.gaiaUnitsQuery); + this.gaiaUnitsQuery = undefined; + } + + if (owner == -1) + return; + + var cmpPlayer = Engine.QueryInterface(playerMan.GetPlayerByID(owner), IID_Player); + if (!cmpPlayer.IsEnemy(0)) + return; + + var range = cmpAttack.GetRange(this.bestAttack); + + // This query is only interested in Gaia entities that can attack. + this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(this.entity, range.min, range.max, range.elevationBonus, [0], IID_Attack, cmpRangeManager.GetEntityFlagMask("normal")); + cmpRangeManager.EnableActiveQuery(this.gaiaUnitsQuery); +}; + +/** + * Called when units enter or leave range + */ +TurretAI.prototype.OnRangeUpdate = function(msg) +{ + var cmpAttack = this.GetCmpAttack(); + if (!cmpAttack) + return; + + if (msg.tag == this.gaiaUnitsQuery) + { + const filter = function(e) { + let cmpUnitAI = Engine.QueryInterface(e, IID_UnitAI); + return (cmpUnitAI && (!cmpUnitAI.IsAnimal() || cmpUnitAI.IsDangerousAnimal())); + }; + + if (msg.added.length) + msg.added = msg.added.filter(filter); + + // Removed entities may not have cmpUnitAI. + for (let i = 0; i < msg.removed.length; ++i) + if (this.targetUnits.indexOf(msg.removed[i]) == -1) + msg.removed.splice(i--, 1); + } + else if (msg.tag != this.enemyUnitsQuery) + return; + + if (msg.added.length > 0) + for (let entity of msg.added) + if (cmpAttack.CanAttack(entity)) + this.targetUnits.push(entity); + + if (msg.removed.length > 0) + for (let entity of msg.removed) + { + let index = this.targetUnits.indexOf(entity); + if (index > -1) + this.targetUnits.splice(index, 1); + } + + if (!this.targetUnits.length || this.timer) + return; + + var attackTimers = cmpAttack.GetTimers(this.bestAttack); + this.SelectAnimation("attack_" + this.bestAttack.toLowerCase(), false, 1.0, "attack"); + this.SetAnimationSync(attackTimers.prepare, attackTimers.repeat); + // units entered the range, prepare to shoot + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + this.timer = cmpTimer.SetInterval(this.entity, IID_TurretAI, "Attack", attackTimers.prepare, attackTimers.repeat, null); +}; + +/** + * Get Previous Target + */ +TurretAI.prototype.GetPreviousTarget = function() +{ + var cmpAttack = this.GetCmpAttack(); + if (!cmpAttack) + return INVALID_ENTITY; + var previousTarget = this.g_LatestTarget; + var cmpUnitAI = Engine.QueryInterface(this.GetTurretParent(), IID_UnitAI); + // if a unit ai, use that to overcome the difference between rangeManager range and unitMotion range + if (cmpUnitAI) + { + if (cmpUnitAI.CheckTargetAttackRange(previousTarget, this.bestAttack)) + return previousTarget; + return INVALID_ENTITY; + } + // else just use the range manager (which gives us the list of possible targets); + if (this.targetUnits.indexOf(previousTarget) != -1) + return previousTarget; + return INVALID_ENTITY; +}; + +/** + * Attack other entities + */ +TurretAI.prototype.Attack = function() +{ + if (!this.targetUnits.length) + { + if (this.timer) + { + // stop the timer + let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.CancelTimer(this.timer); + this.timer = undefined; + } + this.SelectAnimation("idle"); + return; + } + + var cmpAttack = this.GetCmpAttack(); + if (!cmpAttack) + return; + var target = this.GetPreviousTarget(); + // if no target, select a random one + if (target == INVALID_ENTITY) + { + var selectedIndex = -1; + var targets = new WeightedList(); + for (let i = 0; i < this.targetUnits.length; i++) + { + let target = this.targetUnits[i]; + let preference = cmpAttack.GetPreference(target); + let weight = 1; + if (preference !== null && preference !== undefined) + { + // Lower preference scores indicate a higher preference so they should result in a higher weight. + weight = 1 + this.g_MaxPreferenceBonus / (1 + preference); + } + targets.push(target, weight); + } + selectedIndex = targets.randomIndex() + target = targets.itemAt(selectedIndex); + } + // now we hope there's a target + if (target != INVALID_ENTITY) + { + let cmpUnitAI = Engine.QueryInterface(this.GetTurretParent(), IID_UnitAI); + + if (cmpUnitAI.order.data.target){ + target = cmpUnitAI.order.data.target + let allowCapture = cmpUnitAI.order.data.allowCapture; + this.TurnTowardsTarget(target); + cmpAttack.PerformAttack(cmpUnitAI.order.data.attackType, target, this.entity); + this.g_LatestTarget = target; + } + else{ + this.TurnTowardsTarget(target); + cmpAttack.PerformAttack(this.bestAttack, target, this.entity); + this.g_LatestTarget = target; + } + } +}; + +/** + * Selection the animations for the turret + */ +TurretAI.prototype.SelectAnimation = function(name, once = false, speed = 1.0, sound) +{ + var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); + if (!cmpVisual) + return; + + var soundgroup; + if (sound) + { + let cmpSound = Engine.QueryInterface(this.entity, IID_Sound); + if (cmpSound) + soundgroup = cmpSound.GetSoundGroup(sound); + } + cmpVisual.SelectAnimation(name, once, speed, soundgroup || ""); +}; + +/** + * Synchronise Animations + */ +TurretAI.prototype.SetAnimationSync = function(actiontime, repeattime) +{ + var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); + if (!cmpVisual) + return; + + cmpVisual.SetAnimationSyncRepeat(repeattime); + cmpVisual.SetAnimationSyncOffset(actiontime); +}; + +/** + * Orient the turret toward his foe. + */ +TurretAI.prototype.TurnTowardsTarget = function(target) +{ + var cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position); + var cmpTargetPosition = Engine.QueryInterface(target, IID_Position); + if (!cmpThisPosition || !cmpTargetPosition || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld()) + return; + + var pos = cmpTargetPosition.GetPosition2D().sub(cmpThisPosition.GetPosition2D()); + cmpThisPosition.TurnTo(Math.atan2(pos.x, pos.y)); +}; + +Engine.RegisterComponentType(IID_TurretAI, "TurretAI", TurretAI); Index: binaries/data/mods/public/simulation/components/TurretHolder.js =================================================================== --- binaries/data/mods/public/simulation/components/TurretHolder.js +++ binaries/data/mods/public/simulation/components/TurretHolder.js @@ -0,0 +1,99 @@ +function TurretHolder() {} + +TurretHolder.prototype.Schema = + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + +/** + * Initialize TurretHolder Component. + */ +TurretHolder.prototype.Init = function() +{ + this.turrets = []; + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + // hack for atlas, don't create the turrets in Atlas, as the references get lost + // TODO implement some sort of tag for the turrets so they never get saved by Atlas +/* if (cmpTimer.GetTime() == 0) + cmpTimer.SetTimeout(this.entity, IID_TurretHolder, "CreateTurrets", 100, null); + else*/ + this.CreateTurrets(); +}; + +/** + * Create the Turrets. + */ +TurretHolder.prototype.CreateTurrets = function() +{ + for each (let turretPoint in this.template.TurretPoints) + { + var ent = Engine.AddEntity(turretPoint.Template); + var offset = new Vector3D(+turretPoint.X, +turretPoint.Y, +turretPoint.Z); + var cmpPosition = Engine.QueryInterface(ent, IID_Position); + if (cmpPosition) + cmpPosition.SetTurretParent(this.entity, offset); + this.turrets.push(ent); + } + var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + if (cmpOwnership && cmpOwnership.GetOwner() != -1) + this.ChangeTurretOwnership(cmpOwnership.GetOwner()); +}; + +/** + * Return the list of entities garrisoned inside. + */ +TurretHolder.prototype.GetTurrets = function() +{ + return this.turrets; +}; + +/** + * Destroys the entities. + */ +TurretHolder.prototype.OnDestroy = function() +{ + for (let ent of this.turrets) + Engine.DestroyEntity(ent); +}; + +/** + * Change the ownership of the turret. + */ +TurretHolder.prototype.OnOwnershipChanged = function(msg) +{ + this.ChangeTurretOwnership(msg.to); +}; + +/** + * Set the ownership of all present turrets to the same owner. + */ +TurretHolder.prototype.ChangeTurretOwnership = function(owner) +{ + for (let ent of this.turrets) + { + var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); + if (cmpOwnership) + cmpOwnership.SetOwner(owner); + } +}; + +Engine.RegisterComponentType(IID_TurretHolder, "TurretHolder", TurretHolder); + Index: binaries/data/mods/public/simulation/components/interfaces/TurretAI.js =================================================================== --- binaries/data/mods/public/simulation/components/interfaces/TurretAI.js +++ binaries/data/mods/public/simulation/components/interfaces/TurretAI.js @@ -0,0 +1 @@ +Engine.RegisterInterface("TurretAI"); Index: binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js =================================================================== --- binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js +++ binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js @@ -0,0 +1,2 @@ +Engine.RegisterInterface("TurretHolder"); + Index: binaries/data/mods/public/simulation/templates/template_turret.xml =================================================================== --- binaries/data/mods/public/simulation/templates/template_turret.xml +++ binaries/data/mods/public/simulation/templates/template_turret.xml @@ -0,0 +1,35 @@ + + + + false + false + 80.0 + 0.01 + 0.0 + + + unit + + + + + + interface/alarm/alarm_attackplayer.xml + + + + + false + false + true + true + + + 100 + + + true + false + false + + Index: binaries/data/mods/public/simulation/templates/units/brit_champion_cavalry.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/brit_champion_cavalry.xml +++ binaries/data/mods/public/simulation/templates/units/brit_champion_cavalry.xml @@ -1,5 +1,10 @@ + + + true + + 5.0 @@ -13,6 +18,16 @@ The Britons were one of the last European peoples to use two-horse chariots in combat. They had two iron-rimmed wheels and a flat riding platform that typically carried a driver and a warrior. Useless as shock weapons against tightly packed troops, they were useful for running down individual soldiers and as a stable mount to launch javelins from. The heads of defeated opponents often adorned the chassis to show the warrior's prowess. units/celt_champion_cavalry_brit.png + + + + + 0 + 1.4 + -2.5 + + + units/celts/champion_unit_4.xml Index: binaries/data/mods/public/simulation/templates/units/brit_champion_cavalry_r.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/brit_champion_cavalry_r.xml +++ binaries/data/mods/public/simulation/templates/units/brit_champion_cavalry_r.xml @@ -0,0 +1,6 @@ + + + + units/celts/champion_unit_4_r.xml + + Index: binaries/data/mods/public/simulation/templates/units/brit_hero_bouddica_r.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/brit_hero_bouddica_r.xml +++ binaries/data/mods/public/simulation/templates/units/brit_hero_bouddica_r.xml @@ -0,0 +1,6 @@ + + + + units/celts/boudicca_r2.xml + + Index: binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca.xml +++ binaries/data/mods/public/simulation/templates/units/brit_hero_boudicca.xml @@ -1,5 +1,10 @@ + + + true + + global @@ -26,6 +31,16 @@ 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 + + + + + 0 + 1.4 + -2.5 + + + units/celts/boudicca_chariot.xml Index: binaries/data/mods/public/simulation/templates/units/maur_champion_maiden_r.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/maur_champion_maiden_r.xml +++ binaries/data/mods/public/simulation/templates/units/maur_champion_maiden_r.xml @@ -0,0 +1,6 @@ + + + + units/mauryans/hero_chariot_maiden_archer.xml + + Index: binaries/data/mods/public/simulation/templates/units/maur_hero_ashoka.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/maur_hero_ashoka.xml +++ binaries/data/mods/public/simulation/templates/units/maur_hero_ashoka.xml @@ -1,5 +1,10 @@ + + + true + + 5.0 @@ -15,6 +20,22 @@ Hero Special: "Edicts of Ashoka" - Edict Pillars of Ashoka can be built during Ashoka's lifetime. TBD. + + + + + 0.9 + 1.4 + -3 + + + + -0.9 + 1.4 + -3 + + + units/mauryans/hero_chariot.xml Index: binaries/data/mods/public/simulation/templates/units/maur_hero_ashoka_r.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/maur_hero_ashoka_r.xml +++ binaries/data/mods/public/simulation/templates/units/maur_hero_ashoka_r.xml @@ -0,0 +1,6 @@ + + + + units/mauryans/hero_ashoka_rider.xml + + Index: binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_a.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_a.xml +++ binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_a.xml @@ -1,5 +1,10 @@ + + + true + + Advanced @@ -6,6 +11,16 @@ units/pers_cavalry_archer_e + + + + + 1 + 2.2 + -4.0 + + + units/persians/cavalry_archer_a.xml Index: binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_e.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_e.xml +++ binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_e.xml @@ -4,6 +4,16 @@ Elite + + + + + 1 + 2.2 + -4.0 + + + units/persians/cavalry_archer_e.xml Index: binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_rider_a.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_rider_a.xml +++ binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_rider_a.xml @@ -0,0 +1,6 @@ + + + + units/persians/cavalry_archer_a_r.xml + + Index: binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_rider_e.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_rider_e.xml +++ binaries/data/mods/public/simulation/templates/units/pers_cavalry_archer_rider_e.xml @@ -0,0 +1,6 @@ + + + + units/persians/cavalry_archer_e_r.xml + +