Index: binaries/data/mods/public/art/actors/props/units/turret_ptol_champ_ele.xml =================================================================== --- binaries/data/mods/public/art/actors/props/units/turret_ptol_champ_ele.xml +++ binaries/data/mods/public/art/actors/props/units/turret_ptol_champ_ele.xml @@ -5,7 +5,7 @@ props/ptol_howdah.dae - + @@ -18,7 +18,7 @@ props/ptol_howdah.dae - + @@ -31,7 +31,7 @@ props/ptol_howdah.dae - + 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 @@ -1,37 +1,22 @@ -//Number of rounds of firing per 2 seconds -const roundCount = 10; -const attackType = "Ranged"; +function TurretAI() {} -function BuildingAI() {} +TurretAI.prototype.Schema = + ""; -BuildingAI.prototype.Schema = - "" + - "" + - "" + - "" + - "" + - "" + - "" + - "" + - ""; +TurretAI.prototype.MAX_PREFERENCE_BONUS = 2; -BuildingAI.prototype.MAX_PREFERENCE_BONUS = 2; - /** - * Initialize BuildingAI Component + * Initialize TurretAI Component */ -BuildingAI.prototype.Init = function() +TurretAI.prototype.Init = function() { - if (this.GetDefaultArrowCount() > 0 || this.GetGarrisonArrowMultiplier() > 0) - { - this.currentRound = 0; - //Arrows left to fire - this.arrowsLeft = 0; - this.targetUnits = []; - } + var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); + if (!cmpAttack) + return; + this.bestAttack = cmpAttack.GetBestAttack(); }; -BuildingAI.prototype.OnOwnershipChanged = function(msg) +TurretAI.prototype.OnOwnershipChanged = function(msg) { // Remove current targets, to prevent them from being added twice this.targetUnits = []; @@ -44,7 +29,7 @@ this.SetupGaiaRangeQuery(msg.to); }; -BuildingAI.prototype.OnDiplomacyChanged = function(msg) +TurretAI.prototype.OnDiplomacyChanged = function(msg) { var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); if (cmpOwnership && cmpOwnership.GetOwner() == msg.player) @@ -58,7 +43,7 @@ /** * Cleanup on destroy */ -BuildingAI.prototype.OnDestroy = function() +TurretAI.prototype.OnDestroy = function() { if (this.timer) { @@ -78,7 +63,7 @@ /** * Setup the Range Query to detect units coming in & out of range */ -BuildingAI.prototype.SetupRangeQuery = function(owner) +TurretAI.prototype.SetupRangeQuery = function(owner) { var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); @@ -103,7 +88,7 @@ players.push(i); } - var range = cmpAttack.GetRange(attackType); + 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); }; @@ -110,7 +95,7 @@ // Set up a range query for Gaia units within LOS range which can be attacked. // This should be called whenever our ownership changes. -BuildingAI.prototype.SetupGaiaRangeQuery = function() +TurretAI.prototype.SetupGaiaRangeQuery = function() { var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); var owner = cmpOwnership.GetOwner(); @@ -134,7 +119,7 @@ if (!cmpPlayer.IsEnemy(0)) return; - var range = cmpAttack.GetRange(attackType); + 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")); @@ -144,7 +129,7 @@ /** * Called when units enter or leave range */ -BuildingAI.prototype.OnRangeUpdate = function(msg) +TurretAI.prototype.OnRangeUpdate = function(msg) { var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); @@ -190,53 +175,16 @@ 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); - var attackTimers = cmpAttack.GetTimers(attackType); - this.timer = cmpTimer.SetInterval(this.entity, IID_BuildingAI, "FireArrows", attackTimers.prepare, attackTimers.repeat / roundCount, null); + this.timer = cmpTimer.SetInterval(this.entity, IID_TurretAI, "Attack", attackTimers.prepare, attackTimers.repeat, null); }; -BuildingAI.prototype.GetDefaultArrowCount = function() +TurretAI.prototype.Attack = function() { - var arrowCount = +this.template.DefaultArrowCount; - return ApplyValueModificationsToEntity("BuildingAI/DefaultArrowCount", arrowCount, this.entity); -}; - -BuildingAI.prototype.GetGarrisonArrowMultiplier = function() -{ - var arrowMult = +this.template.GarrisonArrowMultiplier; - return ApplyValueModificationsToEntity("BuildingAI/GarrisonArrowMultiplier", arrowMult, this.entity); -}; - -BuildingAI.prototype.GetGarrisonArrowClasses = function() -{ - var string = this.template.GarrisonArrowClasses; - if (string) - return string.split(/\s+/); - return []; -}; - -/** - * Returns the number of arrows which needs to be fired. - * DefaultArrowCount + Garrisoned Archers(ie., any unit capable - * of shooting arrows from inside buildings) - */ -BuildingAI.prototype.GetArrowCount = function() -{ - var count = this.GetDefaultArrowCount(); - var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); - if (cmpGarrisonHolder) - { - count += Math.round(cmpGarrisonHolder.GetGarrisonedArcherCount(this.GetGarrisonArrowClasses()) * this.GetGarrisonArrowMultiplier()); - } - return count; -}; - -/** - * Fires arrows. Called 'roundCount' times every 'RepeatTime' seconds when there are units in the range - */ -BuildingAI.prototype.FireArrows = function() -{ if (!this.targetUnits.length) { if (this.timer) @@ -246,6 +194,7 @@ cmpTimer.CancelTimer(this.timer); this.timer = undefined; } + this.SelectAnimation("idle"); return; } @@ -252,82 +201,52 @@ var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); if (!cmpAttack) return; - - var arrowsToFire = 0; - if (this.currentRound > (roundCount - 1)) + while (true) { - //Reached end of rounds. Reset count - this.currentRound = 0; - } - - if (this.currentRound == 0) - { - //First round. Calculate arrows to fire - this.arrowsLeft = this.GetArrowCount(); - } - - if (this.currentRound == (roundCount - 1)) - { - //Last round. Need to fire all left-over arrows - arrowsToFire = this.arrowsLeft; - } - else - { - //Fire N arrows, 0 <= N <= Number of arrows left - arrowsToFire = Math.min( - Math.round(2*Math.random() * this.GetArrowCount()/roundCount), - this.arrowsLeft - ); - } - if (arrowsToFire <= 0) - { - this.currentRound++; - return; - } - var targets = new WeightedList(); - for (var i = 0; i < this.targetUnits.length; i++) - { - var target = this.targetUnits[i]; - var preference = cmpAttack.GetPreference(target); - var weight = 1; - if (preference !== null && preference !== undefined) + var selectedIndex = -1; + // if we had a target, stick to it + if (!this.target || this.targetUnits.indexOf(this.target) == -1) { - // Lower preference scores indicate a higher preference so they - // should result in a higher weight. - weight = 1 + this.MAX_PREFERENCE_BONUS / (1 + preference); + var targets = new WeightedList(); + for (var i = 0; i < this.targetUnits.length; i++) + { + var target = this.targetUnits[i]; + var preference = cmpAttack.GetPreference(target); + var 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.MAX_PREFERENCE_BONUS / (1 + preference); + } + targets.push(target, weight); + } + selectedIndex = targets.randomIndex() + this.target = targets.itemAt(selectedIndex); } - targets.push(target, weight); - } - for (var i = 0;i < arrowsToFire;i++) - { - var selectedIndex = targets.randomIndex(); - var selectedTarget = targets.itemAt(selectedIndex); - if (selectedTarget && this.CheckTargetVisible(selectedTarget)) + if (this.target && this.CheckTargetVisible(this.target)) { - cmpAttack.PerformAttack(attackType, selectedTarget); - PlaySound("attack", this.entity); + this.TurnTowardsTarget(); + cmpAttack.PerformAttack(this.bestAttack, this.target); + break; } else { - targets.removeAt(selectedIndex); - i--; // one extra arrow left to fire - if(targets.length() < 1) + this.target = 0; + if (selectedIndex != -1) { - this.arrowsLeft += arrowsToFire; - // no targets found in this round, save arrows and go to next round - break; + targets.removeAt(selectedIndex); + if(targets.length() < 1) + break; } } } - - this.arrowsLeft -= arrowsToFire; - this.currentRound++; }; /** * Returns true if the target entity is visible through the FoW/SoD. */ -BuildingAI.prototype.CheckTargetVisible = function(target) +TurretAI.prototype.CheckTargetVisible = function(target) { var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); if (!cmpOwnership) @@ -342,4 +261,50 @@ return true; }; -Engine.RegisterComponentType(IID_BuildingAI, "BuildingAI", BuildingAI); +TurretAI.prototype.SelectAnimation = function(name, once, speed, sound) +{ + var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); + if (!cmpVisual) + return; + + var soundgroup; + if (sound) + { + var cmpSound = Engine.QueryInterface(this.entity, IID_Sound); + if (cmpSound) + soundgroup = cmpSound.GetSoundGroup(sound); + } + + // Set default values if unspecified + if (once === undefined) + once = false; + if (speed === undefined) + speed = 1.0; + if (soundgroup === undefined) + soundgroup = ""; + + cmpVisual.SelectAnimation(name, once, speed, soundgroup); +}; + +TurretAI.prototype.SetAnimationSync = function(actiontime, repeattime) +{ + var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); + if (!cmpVisual) + return; + + cmpVisual.SetAnimationSyncRepeat(repeattime); + cmpVisual.SetAnimationSyncOffset(actiontime); +}; + +TurretAI.prototype.TurnTowardsTarget = function() +{ + var cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position); + var cmpTargetPosition = Engine.QueryInterface(this.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,88 @@ +function TurretHolder() {} + +TurretHolder.prototype.Schema = + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + ""; + +/** + * Initialize TurretHolder Component + */ +TurretHolder.prototype.Init = function() +{ + this.turrets = []; + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); +/* if (cmpTimer.GetTime() == 0) + cmpTimer.SetTimeout( + else*/ + this.CreateTurrets(); +}; + +TurretHolder.prototype.CreateTurrets = function() +{ + for each (var 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; +}; + +TurretHolder.prototype.OnDestroy = function() +{ + for (var ent of this.turrets) + Engine.DestroyEntity(ent); +}; + +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 (var 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/UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/UnitAI.js +++ binaries/data/mods/public/simulation/components/UnitAI.js @@ -4274,6 +4274,10 @@ return false; var range = cmpGarrisonHolder.GetLoadingRange(); + var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); + if (cmpObstruction) + range.max += cmpObstruction.GetUnitRadius()*1.5; // multiply by something larger than sqrt(2) + var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); }; 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 @@ -1 +1 @@ -Engine.RegisterInterface("BuildingAI"); +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,47 @@ + + + + + 80.0 + 0.01 + 0.0 + + + Unit + Turret + + + + unit + + + + + + circle/128x128.png + circle/128x128_mask.png + + + + + + interface/alarm/alarm_attackplayer.xml + + + + 2.0 + 0.333 + 5.0 + + + + 60 + false + false + + + true + false + false + + Index: binaries/data/mods/public/simulation/templates/units/sele_champion_elephant.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/sele_champion_elephant.xml +++ binaries/data/mods/public/simulation/templates/units/sele_champion_elephant.xml @@ -1,5 +1,13 @@ + + + + + 070 + + + sele Armored War Elephant @@ -11,4 +19,4 @@ units/seleucids/champion_elephant.xml - \ No newline at end of file + Index: binaries/data/mods/public/simulation/templates/units/sele_champion_elephant_turret.xml =================================================================== --- binaries/data/mods/public/simulation/templates/units/sele_champion_elephant_turret.xml +++ binaries/data/mods/public/simulation/templates/units/sele_champion_elephant_turret.xml @@ -1,5 +1,24 @@ - + + + + 10.0 + 0.0 + 0.0 + 20.0 + 1000 + + + Cavalry + 2.0 + + + Elephant + 1.5 + + + + sele Silver Shield Pikeman @@ -8,6 +27,19 @@ units/sele_champion_infantry_pikeman.png successors/unlock_traditional_army + + + + voice/hellenes/civ/civ_male_ack.xml + voice/hellenes/civ/civ_male_attack.xml + voice/hellenes/civ/civ_male_ack.xml + voice/hellenes/civ/civ_male_ack.xml + actor/human/movement/walk.xml + actor/human/movement/walk.xml + attack/weapon/sword.xml + actor/human/death/death.xml + + units/seleucids/champion_infantry_pikeman.xml