Ticket #2577: turrets_scripted.diff
File turrets_scripted.diff, 21.3 KB (added by , 10 years ago) |
---|
-
binaries/data/mods/public/art/actors/props/units/turret_ptol_champ_ele.xml
5 5 <variant frequency="1" name="Ptolemaic elephant champion howdah gray"> 6 6 <mesh>props/ptol_howdah.dae</mesh> 7 7 <props> 8 < prop actor="units/seleucids/champion_elephant_rider1.xml" attachpoint="root"/>8 <!--<prop actor="units/seleucids/champion_elephant_rider1.xml" attachpoint="root"/>--> 9 9 <prop actor="props/units/shields/ptol_hero_1.xml" attachpoint="shield1"/> 10 10 <prop actor="props/units/shields/ptol_hero_1.xml" attachpoint="shield2"/> 11 11 </props> … … 18 18 <variant frequency="1" name="Ptolemaic elephant champion howdah brown"> 19 19 <mesh>props/ptol_howdah.dae</mesh> 20 20 <props> 21 < prop actor="units/seleucids/champion_elephant_rider1.xml" attachpoint="root"/>21 <!--<prop actor="units/seleucids/champion_elephant_rider1.xml" attachpoint="root"/>--> 22 22 <prop actor="props/units/shields/ptol_hero_2.xml" attachpoint="shield1"/> 23 23 <prop actor="props/units/shields/ptol_hero_2.xml" attachpoint="shield2"/> 24 24 </props> … … 31 31 <variant frequency="1" name="Ptolemaic elephant champion howdah white"> 32 32 <mesh>props/ptol_howdah.dae</mesh> 33 33 <props> 34 < prop actor="units/seleucids/champion_elephant_rider1.xml" attachpoint="root"/>34 <!--<prop actor="units/seleucids/champion_elephant_rider1.xml" attachpoint="root"/>--> 35 35 <prop actor="props/units/shields/ptol_hero_1.xml" attachpoint="shield1"/> 36 36 <prop actor="props/units/shields/ptol_hero_1.xml" attachpoint="shield2"/> 37 37 </props> -
binaries/data/mods/public/simulation/components/TurretAI.js
1 //Number of rounds of firing per 2 seconds 2 const roundCount = 10; 3 const attackType = "Ranged"; 1 function TurretAI() {} 4 2 5 function BuildingAI() {} 3 TurretAI.prototype.Schema = 4 "<empty/>"; 6 5 7 BuildingAI.prototype.Schema = 8 "<element name='DefaultArrowCount'>" + 9 "<data type='nonNegativeInteger'/>" + 10 "</element>" + 11 "<element name='GarrisonArrowMultiplier'>" + 12 "<ref name='nonNegativeDecimal'/>" + 13 "</element>" + 14 "<element name='GarrisonArrowClasses'>" + 15 "<text/>" + 16 "</element>"; 6 TurretAI.prototype.MAX_PREFERENCE_BONUS = 2; 17 7 18 BuildingAI.prototype.MAX_PREFERENCE_BONUS = 2;19 20 8 /** 21 * Initialize BuildingAI Component9 * Initialize TurretAI Component 22 10 */ 23 BuildingAI.prototype.Init = function()11 TurretAI.prototype.Init = function() 24 12 { 25 if (this.GetDefaultArrowCount() > 0 || this.GetGarrisonArrowMultiplier() > 0) 26 { 27 this.currentRound = 0; 28 //Arrows left to fire 29 this.arrowsLeft = 0; 30 this.targetUnits = []; 31 } 13 var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 14 if (!cmpAttack) 15 return; 16 this.bestAttack = cmpAttack.GetBestAttack(); 32 17 }; 33 18 34 BuildingAI.prototype.OnOwnershipChanged = function(msg)19 TurretAI.prototype.OnOwnershipChanged = function(msg) 35 20 { 36 21 // Remove current targets, to prevent them from being added twice 37 22 this.targetUnits = []; … … 44 29 this.SetupGaiaRangeQuery(msg.to); 45 30 }; 46 31 47 BuildingAI.prototype.OnDiplomacyChanged = function(msg)32 TurretAI.prototype.OnDiplomacyChanged = function(msg) 48 33 { 49 34 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 50 35 if (cmpOwnership && cmpOwnership.GetOwner() == msg.player) … … 58 43 /** 59 44 * Cleanup on destroy 60 45 */ 61 BuildingAI.prototype.OnDestroy = function()46 TurretAI.prototype.OnDestroy = function() 62 47 { 63 48 if (this.timer) 64 49 { … … 78 63 /** 79 64 * Setup the Range Query to detect units coming in & out of range 80 65 */ 81 BuildingAI.prototype.SetupRangeQuery = function(owner)66 TurretAI.prototype.SetupRangeQuery = function(owner) 82 67 { 83 68 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 84 69 var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); … … 103 88 players.push(i); 104 89 } 105 90 106 var range = cmpAttack.GetRange( attackType);91 var range = cmpAttack.GetRange(this.bestAttack); 107 92 this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(this.entity, range.min, range.max, range.elevationBonus, players, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal")); 108 93 cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery); 109 94 }; … … 110 95 111 96 // Set up a range query for Gaia units within LOS range which can be attacked. 112 97 // This should be called whenever our ownership changes. 113 BuildingAI.prototype.SetupGaiaRangeQuery = function()98 TurretAI.prototype.SetupGaiaRangeQuery = function() 114 99 { 115 100 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 116 101 var owner = cmpOwnership.GetOwner(); … … 134 119 if (!cmpPlayer.IsEnemy(0)) 135 120 return; 136 121 137 var range = cmpAttack.GetRange( attackType);122 var range = cmpAttack.GetRange(this.bestAttack); 138 123 139 124 // This query is only interested in Gaia entities that can attack. 140 125 this.gaiaUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(this.entity, range.min, range.max, range.elevationBonus, [0], IID_Attack, cmpRangeManager.GetEntityFlagMask("normal")); … … 144 129 /** 145 130 * Called when units enter or leave range 146 131 */ 147 BuildingAI.prototype.OnRangeUpdate = function(msg)132 TurretAI.prototype.OnRangeUpdate = function(msg) 148 133 { 149 134 150 135 var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); … … 190 175 if (!this.targetUnits.length || this.timer) 191 176 return; 192 177 178 var attackTimers = cmpAttack.GetTimers(this.bestAttack); 179 this.SelectAnimation("attack_" + this.bestAttack.toLowerCase(), false, 1.0, "attack"); 180 this.SetAnimationSync(attackTimers.prepare, attackTimers.repeat); 193 181 // units entered the range, prepare to shoot 194 182 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 195 var attackTimers = cmpAttack.GetTimers(attackType); 196 this.timer = cmpTimer.SetInterval(this.entity, IID_BuildingAI, "FireArrows", attackTimers.prepare, attackTimers.repeat / roundCount, null); 183 this.timer = cmpTimer.SetInterval(this.entity, IID_TurretAI, "Attack", attackTimers.prepare, attackTimers.repeat, null); 197 184 }; 198 185 199 BuildingAI.prototype.GetDefaultArrowCount= function()186 TurretAI.prototype.Attack = function() 200 187 { 201 var arrowCount = +this.template.DefaultArrowCount;202 return ApplyValueModificationsToEntity("BuildingAI/DefaultArrowCount", arrowCount, this.entity);203 };204 205 BuildingAI.prototype.GetGarrisonArrowMultiplier = function()206 {207 var arrowMult = +this.template.GarrisonArrowMultiplier;208 return ApplyValueModificationsToEntity("BuildingAI/GarrisonArrowMultiplier", arrowMult, this.entity);209 };210 211 BuildingAI.prototype.GetGarrisonArrowClasses = function()212 {213 var string = this.template.GarrisonArrowClasses;214 if (string)215 return string.split(/\s+/);216 return [];217 };218 219 /**220 * Returns the number of arrows which needs to be fired.221 * DefaultArrowCount + Garrisoned Archers(ie., any unit capable222 * of shooting arrows from inside buildings)223 */224 BuildingAI.prototype.GetArrowCount = function()225 {226 var count = this.GetDefaultArrowCount();227 var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);228 if (cmpGarrisonHolder)229 {230 count += Math.round(cmpGarrisonHolder.GetGarrisonedArcherCount(this.GetGarrisonArrowClasses()) * this.GetGarrisonArrowMultiplier());231 }232 return count;233 };234 235 /**236 * Fires arrows. Called 'roundCount' times every 'RepeatTime' seconds when there are units in the range237 */238 BuildingAI.prototype.FireArrows = function()239 {240 188 if (!this.targetUnits.length) 241 189 { 242 190 if (this.timer) … … 246 194 cmpTimer.CancelTimer(this.timer); 247 195 this.timer = undefined; 248 196 } 197 this.SelectAnimation("idle"); 249 198 return; 250 199 } 251 200 … … 252 201 var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 253 202 if (!cmpAttack) 254 203 return; 255 256 var arrowsToFire = 0; 257 if (this.currentRound > (roundCount - 1)) 204 while (true) 258 205 { 259 //Reached end of rounds. Reset count 260 this.currentRound = 0; 261 } 262 263 if (this.currentRound == 0) 264 { 265 //First round. Calculate arrows to fire 266 this.arrowsLeft = this.GetArrowCount(); 267 } 268 269 if (this.currentRound == (roundCount - 1)) 270 { 271 //Last round. Need to fire all left-over arrows 272 arrowsToFire = this.arrowsLeft; 273 } 274 else 275 { 276 //Fire N arrows, 0 <= N <= Number of arrows left 277 arrowsToFire = Math.min( 278 Math.round(2*Math.random() * this.GetArrowCount()/roundCount), 279 this.arrowsLeft 280 ); 281 } 282 if (arrowsToFire <= 0) 283 { 284 this.currentRound++; 285 return; 286 } 287 var targets = new WeightedList(); 288 for (var i = 0; i < this.targetUnits.length; i++) 289 { 290 var target = this.targetUnits[i]; 291 var preference = cmpAttack.GetPreference(target); 292 var weight = 1; 293 if (preference !== null && preference !== undefined) 206 var selectedIndex = -1; 207 // if we had a target, stick to it 208 if (!this.target || this.targetUnits.indexOf(this.target) == -1) 294 209 { 295 // Lower preference scores indicate a higher preference so they 296 // should result in a higher weight. 297 weight = 1 + this.MAX_PREFERENCE_BONUS / (1 + preference); 210 var targets = new WeightedList(); 211 for (var i = 0; i < this.targetUnits.length; i++) 212 { 213 var target = this.targetUnits[i]; 214 var preference = cmpAttack.GetPreference(target); 215 var weight = 1; 216 if (preference !== null && preference !== undefined) 217 { 218 // Lower preference scores indicate a higher preference so they 219 // should result in a higher weight. 220 weight = 1 + this.MAX_PREFERENCE_BONUS / (1 + preference); 221 } 222 targets.push(target, weight); 223 } 224 selectedIndex = targets.randomIndex() 225 this.target = targets.itemAt(selectedIndex); 298 226 } 299 targets.push(target, weight); 300 } 301 for (var i = 0;i < arrowsToFire;i++) 302 { 303 var selectedIndex = targets.randomIndex(); 304 var selectedTarget = targets.itemAt(selectedIndex); 305 if (selectedTarget && this.CheckTargetVisible(selectedTarget)) 227 if (this.target && this.CheckTargetVisible(this.target)) 306 228 { 307 cmpAttack.PerformAttack(attackType, selectedTarget); 308 PlaySound("attack", this.entity); 229 this.TurnTowardsTarget(); 230 cmpAttack.PerformAttack(this.bestAttack, this.target); 231 break; 309 232 } 310 233 else 311 234 { 312 targets.removeAt(selectedIndex); 313 i--; // one extra arrow left to fire 314 if(targets.length() < 1) 235 this.target = 0; 236 if (selectedIndex != -1) 315 237 { 316 t his.arrowsLeft += arrowsToFire;317 // no targets found in this round, save arrows and go to next round318 break;238 targets.removeAt(selectedIndex); 239 if(targets.length() < 1) 240 break; 319 241 } 320 242 } 321 243 } 322 323 this.arrowsLeft -= arrowsToFire;324 this.currentRound++;325 244 }; 326 245 327 246 /** 328 247 * Returns true if the target entity is visible through the FoW/SoD. 329 248 */ 330 BuildingAI.prototype.CheckTargetVisible = function(target)249 TurretAI.prototype.CheckTargetVisible = function(target) 331 250 { 332 251 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 333 252 if (!cmpOwnership) … … 342 261 return true; 343 262 }; 344 263 345 Engine.RegisterComponentType(IID_BuildingAI, "BuildingAI", BuildingAI); 264 TurretAI.prototype.SelectAnimation = function(name, once, speed, sound) 265 { 266 var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); 267 if (!cmpVisual) 268 return; 269 270 var soundgroup; 271 if (sound) 272 { 273 var cmpSound = Engine.QueryInterface(this.entity, IID_Sound); 274 if (cmpSound) 275 soundgroup = cmpSound.GetSoundGroup(sound); 276 } 277 278 // Set default values if unspecified 279 if (once === undefined) 280 once = false; 281 if (speed === undefined) 282 speed = 1.0; 283 if (soundgroup === undefined) 284 soundgroup = ""; 285 286 cmpVisual.SelectAnimation(name, once, speed, soundgroup); 287 }; 288 289 TurretAI.prototype.SetAnimationSync = function(actiontime, repeattime) 290 { 291 var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); 292 if (!cmpVisual) 293 return; 294 295 cmpVisual.SetAnimationSyncRepeat(repeattime); 296 cmpVisual.SetAnimationSyncOffset(actiontime); 297 }; 298 299 TurretAI.prototype.TurnTowardsTarget = function() 300 { 301 var cmpThisPosition = Engine.QueryInterface(this.entity, IID_Position); 302 var cmpTargetPosition = Engine.QueryInterface(this.target, IID_Position); 303 if (!cmpThisPosition || !cmpTargetPosition || !cmpThisPosition.IsInWorld() || !cmpTargetPosition.IsInWorld()) 304 return; 305 306 var pos = cmpTargetPosition.GetPosition2D().sub(cmpThisPosition.GetPosition2D()); 307 cmpThisPosition.TurnTo(Math.atan2(pos.x, pos.y)); 308 }; 309 310 Engine.RegisterComponentType(IID_TurretAI, "TurretAI", TurretAI); -
binaries/data/mods/public/simulation/components/TurretHolder.js
1 function TurretHolder() {} 2 3 TurretHolder.prototype.Schema = 4 "<element name='TurretPoints'>" + 5 "<zeroOrMore>" + 6 "<element a:help='Element containing the offset coordinates and the template'>" + 7 "<anyName/>" + 8 "<interleave>" + 9 "<element name='Template'>" + 10 "<text/>" + 11 "</element>" + 12 "<element name='X'>" + 13 "<data type='decimal'/>" + 14 "</element>" + 15 "<element name='Y'>" + 16 "<data type='decimal'/>" + 17 "</element>" + 18 "<element name='Z'>" + 19 "<data type='decimal'/>" + 20 "</element>" + 21 "</interleave>" + 22 "</element>" + 23 "</zeroOrMore>" + 24 "</element>"; 25 26 /** 27 * Initialize TurretHolder Component 28 */ 29 TurretHolder.prototype.Init = function() 30 { 31 this.turrets = []; 32 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 33 /* if (cmpTimer.GetTime() == 0) 34 cmpTimer.SetTimeout( 35 else*/ 36 this.CreateTurrets(); 37 }; 38 39 TurretHolder.prototype.CreateTurrets = function() 40 { 41 for each (var turretPoint in this.template.TurretPoints) 42 { 43 var ent = Engine.AddEntity(turretPoint.Template); 44 var offset = new Vector3D(+turretPoint.X, +turretPoint.Y, +turretPoint.Z); 45 var cmpPosition = Engine.QueryInterface(ent, IID_Position); 46 if (cmpPosition) 47 cmpPosition.SetTurretParent(this.entity, offset); 48 this.turrets.push(ent); 49 } 50 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 51 if (cmpOwnership && cmpOwnership.GetOwner() != -1) 52 this.ChangeTurretOwnership(cmpOwnership.GetOwner()); 53 }; 54 55 /** 56 * Return the list of entities garrisoned inside 57 */ 58 TurretHolder.prototype.GetTurrets = function() 59 { 60 return this.turrets; 61 }; 62 63 TurretHolder.prototype.OnDestroy = function() 64 { 65 for (var ent of this.turrets) 66 Engine.DestroyEntity(ent); 67 }; 68 69 TurretHolder.prototype.OnOwnershipChanged = function(msg) 70 { 71 this.ChangeTurretOwnership(msg.to); 72 }; 73 74 /** 75 * Set the ownership of all present turrets to the same owner 76 */ 77 TurretHolder.prototype.ChangeTurretOwnership = function(owner) 78 { 79 for (var ent of this.turrets) 80 { 81 var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); 82 if (cmpOwnership) 83 cmpOwnership.SetOwner(owner); 84 } 85 }; 86 87 Engine.RegisterComponentType(IID_TurretHolder, "TurretHolder", TurretHolder); 88 -
binaries/data/mods/public/simulation/components/UnitAI.js
4274 4274 return false; 4275 4275 var range = cmpGarrisonHolder.GetLoadingRange(); 4276 4276 4277 var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction); 4278 if (cmpObstruction) 4279 range.max += cmpObstruction.GetUnitRadius()*1.5; // multiply by something larger than sqrt(2) 4280 4277 4281 var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 4278 4282 return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max); 4279 4283 }; -
binaries/data/mods/public/simulation/components/interfaces/TurretAI.js
1 Engine.RegisterInterface(" BuildingAI");1 Engine.RegisterInterface("TurretAI"); -
binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js
1 Engine.RegisterInterface("TurretHolder"); 2 -
binaries/data/mods/public/simulation/templates/template_turret.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <Entity parent="template_entity_full"> 3 <Decay> 4 <Inactive/> 5 <DelayTime>80.0</DelayTime> 6 <SinkRate>0.01</SinkRate> 7 <SinkAccel>0.0</SinkAccel> 8 </Decay> 9 <Identity> 10 <GenericName>Unit</GenericName> 11 <Classes datatype="tokens">Turret</Classes> 12 </Identity> 13 <Looter/> 14 <Minimap> 15 <Type>unit</Type> 16 </Minimap> 17 <OverlayRenderer/> 18 <Selectable> 19 <Overlay> 20 <Texture> 21 <MainTexture>circle/128x128.png</MainTexture> 22 <MainTextureMask>circle/128x128_mask.png</MainTextureMask> 23 </Texture> 24 </Overlay> 25 </Selectable> 26 <Sound> 27 <SoundGroups> 28 <attacked>interface/alarm/alarm_attackplayer.xml</attacked> 29 </SoundGroups> 30 </Sound> 31 <StatusBars> 32 <BarWidth>2.0</BarWidth> 33 <BarHeight>0.333</BarHeight> 34 <HeightOffset>5.0</HeightOffset> 35 </StatusBars> 36 <TurretAI/> 37 <Vision> 38 <Range>60</Range> 39 <RetainInFog>false</RetainInFog> 40 <AlwaysVisible>false</AlwaysVisible> 41 </Vision> 42 <VisualActor> 43 <SilhouetteDisplay>true</SilhouetteDisplay> 44 <SilhouetteOccluder>false</SilhouetteOccluder> 45 <VisibleInAtlasOnly>false</VisibleInAtlasOnly> 46 </VisualActor> 47 </Entity> -
binaries/data/mods/public/simulation/templates/units/sele_champion_elephant.xml
Property changes on: binaries/data/mods/public/simulation/templates/template_turret.xml ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/xml \ No newline at end of property Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 <Entity parent="template_unit_champion_elephant_melee"> 3 <TurretHolder> 4 <TurretPoints> 5 <Pike> 6 <Template>units/sele_champion_elephant_turret</Template> 7 <X>0</X><Y>7</Y><Z>0</Z> 8 </Pike> 9 </TurretPoints> 10 </TurretHolder> 3 11 <Identity> 4 12 <Civ>sele</Civ> 5 13 <GenericName>Armored War Elephant</GenericName> … … 11 19 <VisualActor> 12 20 <Actor>units/seleucids/champion_elephant.xml</Actor> 13 21 </VisualActor> 14 </Entity> 15 No newline at end of file 22 </Entity> -
binaries/data/mods/public/simulation/templates/units/sele_champion_elephant_turret.xml
1 1 <?xml version="1.0" encoding="utf-8"?> 2 <Entity parent="template_unit_champion_infantry_pikeman"> 2 <Entity parent="template_turret"> 3 <Attack> 4 <Melee> 5 <Hack>10.0</Hack> 6 <Pierce>0.0</Pierce> 7 <Crush>0.0</Crush> 8 <MaxRange>20.0</MaxRange> 9 <RepeatTime>1000</RepeatTime> 10 <Bonuses> 11 <BonusCavalry> 12 <Classes>Cavalry</Classes> 13 <Multiplier>2.0</Multiplier> 14 </BonusCavalry> 15 <BonusEles> 16 <Classes>Elephant</Classes> 17 <Multiplier>1.5</Multiplier> 18 </BonusEles> 19 </Bonuses> 20 </Melee> 21 </Attack> 3 22 <Identity> 4 23 <Civ>sele</Civ> 5 24 <GenericName>Silver Shield Pikeman</GenericName> … … 8 27 <Icon>units/sele_champion_infantry_pikeman.png</Icon> 9 28 <RequiredTechnology>successors/unlock_traditional_army</RequiredTechnology> 10 29 </Identity> 30 <Sound> 31 <SoundGroups> 32 <select>voice/hellenes/civ/civ_male_select.xml</select> 33 <order_walk>voice/hellenes/civ/civ_male_ack.xml</order_walk> 34 <order_attack>voice/hellenes/civ/civ_male_attack.xml</order_attack> 35 <order_gather>voice/hellenes/civ/civ_male_ack.xml</order_gather> 36 <order_repair>voice/hellenes/civ/civ_male_ack.xml</order_repair> 37 <walk>actor/human/movement/walk.xml</walk> 38 <run>actor/human/movement/walk.xml</run> 39 <attack>attack/weapon/sword.xml</attack> 40 <death>actor/human/death/death.xml</death> 41 </SoundGroups> 42 </Sound> 11 43 <VisualActor> 12 44 <Actor>units/seleucids/champion_infantry_pikeman.xml</Actor> 13 45 </VisualActor>