Ticket #563: animalAI.patch
File animalAI.patch, 16.7 KB (added by , 14 years ago) |
---|
-
binaries/data/mods/public/simulation/components/AnimalAI.js
31 31 "</element>"; 32 32 33 33 var AnimalFsmSpec = { 34 35 "SKITTISH": { 36 37 "ResourceGather": function(msg) { 38 // If someone's carving chunks of meat off us, then run away 34 "ResourceGather": function(msg) { 35 // If someone's carving chunks of meat off us, then run away 36 if(this.behavior == "SKITTISH" || this.behavior == "PASSIVE") 37 { 39 38 this.MoveAwayFrom(msg.gatherer, +this.template.FleeDistance); 40 39 this.SetNextState("FLEEING"); 41 40 this.PlaySound("panic"); 41 } 42 else if(this.behavior == "VIOLENT" || this.behavior == "AGGRESSIVE" 43 || this.behavior == "DEFENSIVE") 44 { 45 this.Riposte(msg.gatherer); 46 } 47 }, 48 49 "Attacked": function(msg) { 50 if(this.behavior == "VIOLENT" || this.behavior == "AGGRESSIVE" 51 || this.behavior == "DEFENSIVE") 52 { 53 this.Riposte(msg.data.attacker); 54 } 55 }, 56 57 "LosRangeUpdate": function(msg) { 58 if(this.behavior == "SKITTISH") 59 { 60 if(msg.data.added.length>0) 61 { 62 this.MoveAwayFrom(msg.data.added[0], +this.template.FleeDistance); 63 this.SetNextState("FLEEING"); 64 this.PlaySound("panic"); 65 return; 66 } 67 } 68 // Start attacking one of the newly-seen enemy (if any) 69 else if(this.behavior == "VIOLENT") 70 this.AttackVisibleEntity(msg.data.added); 71 }, 72 73 "FEEDING": { 74 "enter": function() { 75 // Stop and eat for a while 76 this.SelectAnimation("feeding"); 77 this.StopMoving(); 78 79 if (this.behavior == "AGGRESSIVE" && this.losRangeQuery) 80 { 81 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 82 this.residEnts = rangeMan.ResetActiveQuery(this.losRangeQuery); 83 } 84 85 86 this.StartTimer(RandomInt(+this.template.FeedTimeMin, +this.template.FeedTimeMax)); 42 87 }, 43 88 44 "ROAMING": { 45 "enter": function() { 46 // Walk in a random direction 47 this.SelectAnimation("walk", false, this.GetWalkSpeed()); 48 this.MoveRandomly(+this.template.RoamDistance); 49 // Set a random timer to switch to feeding state 50 this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax)); 51 }, 89 "leave": function() { 90 this.StopTimer(); 91 }, 52 92 53 "leave": function() { 54 this.StopTimer(); 55 }, 93 "MoveStopped": function() { }, 56 94 57 "Timer": function(msg) { 58 this.SetNextState("FEEDING"); 59 }, 95 "Timer": function(msg) { 96 if (this.behavior == "AGGRESSIVE" && this.losRangeQuery) 97 { 98 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 99 var ents = rangeMan.ResetActiveQuery(this.losRangeQuery); 100 for each (var ent in this.residEnts) 101 { 102 for each (var tent in ents) 103 { 104 if (ent == tent) 105 { 106 this.Riposte(ent); 107 this.StopTimer(); 108 return; 109 } 110 } 111 } 112 } 113 this.SetNextState("ROAMING"); 114 }, 115 }, 60 116 61 "MoveStopped": function() { 62 this.MoveRandomly(+this.template.RoamDistance); 63 }, 117 "ROAMING": { 118 "enter": function() { 119 // Walk in a random direction 120 this.SelectAnimation("walk", false, this.GetWalkSpeed()); 121 this.MoveRandomly(+this.template.RoamDistance); 122 // Set a random timer to switch to feeding state 123 this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax)); 64 124 }, 65 125 66 "FEEDING": { 67 "enter": function() { 68 // Stop and eat for a while 69 this.SelectAnimation("feeding"); 70 this.StopMoving(); 71 this.StartTimer(RandomInt(+this.template.FeedTimeMin, +this.template.FeedTimeMax)); 72 }, 73 74 "leave": function() { 75 this.StopTimer(); 76 }, 126 "leave": function() { 127 this.StopTimer(); 128 }, 77 129 78 "MoveStopped": function() { }, 130 "Timer": function(msg) { 131 this.SetNextState("FEEDING"); 132 }, 79 133 80 "Timer": function(msg) { 81 this.SetNextState("ROAMING"); 82 }, 134 "MoveStopped": function() { 135 this.MoveRandomly(+this.template.RoamDistance); 83 136 }, 137 }, 84 138 85 86 87 88 89 90 91 139 "FLEEING": { 140 "enter": function() { 141 // Run quickly 142 var speed = this.GetRunSpeed(); 143 this.SelectAnimation("run", false, speed); 144 this.SetMoveSpeed(speed); 145 }, 92 146 93 94 95 96 147 "leave": function() { 148 // Reset normal speed 149 this.SetMoveSpeed(this.GetWalkSpeed()); 150 }, 97 151 98 "MoveStopped": function() { 99 // When we've run far enough, go back to the roaming state 100 this.SetNextState("ROAMING"); 101 }, 152 "MoveStopped": function() { 153 // When we've run far enough, go back to the roaming state 154 this.SetNextState("ROAMING"); 102 155 }, 156 157 "Timer": function(msg) { 158 159 } 103 160 }, 104 161 105 "PASSIVE": {106 162 163 "Order.Attack": function(msg) { 164 // Work out how to attack the given target 165 var type = this.GetBestAttack(); 166 if (!type) 167 { 168 // Oops, we can't attack at all 169 return; 170 } 171 this.attackType = type; 172 173 // Try to move within attack range 174 if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType)) 175 { 176 // We've started walking to the given point 177 this.SetNextState("COMBAT.APPROACHING"); 178 } 179 else 180 { 181 // We are already at the target, or can't move at all, 182 // so try attacking it from here. 183 // TODO: need better handling of the can't-reach-target case 184 this.SetNextState("COMBAT.ATTACKING"); 185 } 186 }, 187 188 "COMBAT": { 107 189 "ResourceGather": function(msg) { 108 // If someone's carving chunks of meat off us, then run away109 // this.MoveAwayFrom(msg.gatherer, +this.template.FleeDistance);110 // this.SetNextState("FLEEING");111 // this.PlaySound("panic");112 190 }, 113 191 114 "ROAMING": { 192 "Attacked": function(msg) { 193 }, 194 195 "LosRangeUpdate": function(msg) { 196 }, 197 198 "APPROACHING": { 115 199 "enter": function() { 116 // Walk in a random direction117 200 this.SelectAnimation("walk", false, this.GetWalkSpeed()); 118 this.MoveRandomly(+this.template.RoamDistance);119 // Set a random timer to switch to feeding state120 this.StartTimer(RandomInt(+this.template.RoamTimeMin, +this.template.RoamTimeMax));121 201 }, 122 202 123 "leave": function() {124 this.StopTimer();125 },126 127 "Timer": function(msg) {128 this.SetNextState("FEEDING");129 },130 131 203 "MoveStopped": function() { 132 this. MoveRandomly(+this.template.RoamDistance);204 this.SetNextState("ATTACKING"); 133 205 }, 134 206 }, 135 207 136 " FEEDING": {208 "ATTACKING": { 137 209 "enter": function() { 138 // Stop and eat for a while 139 this.SelectAnimation("feeding"); 140 this.StopMoving(); 141 this.StartTimer(RandomInt(+this.template.FeedTimeMin, +this.template.FeedTimeMax)); 210 var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 211 this.attackTimers = cmpAttack.GetTimers(this.attackType); 212 213 this.SelectAnimation("melee", false, 1.0, "attack"); 214 //this.SetAnimationSync(this.attackTimers.prepare, this.attackTimers.repeat); 215 this.StartTimer(this.attackTimers.prepare, this.attackTimers.repeat); 216 // TODO: we should probably only bother syncing projectile attacks, not melee 142 217 }, 143 218 144 219 "leave": function() { 145 220 this.StopTimer(); 146 221 }, 147 222 148 "MoveStopped": function() { }, 223 "Timer": function(msg) { 224 // Check we can still reach the target 225 if (this.CheckTargetRange(this.order.data.target, IID_Attack, this.attackType)) 226 { 227 var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 228 cmpAttack.PerformAttack(this.attackType, this.order.data.target); 229 } 230 else 231 { 232 if (this.losRangeQuery) 233 { 234 // check if the raget is still in sight 235 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 236 var ents = rangeMan.ResetActiveQuery(this.losRangeQuery); 149 237 150 "Timer": function(msg) { 151 this.SetNextState("ROAMING"); 238 for each (var ent in ents) 239 { 240 if (ent == this.order.data.target) 241 { 242 // Try to chase after it 243 if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType)) 244 { 245 this.SetNextState("COMBAT.CHASING"); 246 this.StopTimer(); 247 return; 248 } 249 } 250 } 251 } 252 this.SetNextState("ROAMING"); 253 } 152 254 }, 153 255 }, 154 256 155 " FLEEING": {257 "CHASING": { 156 258 "enter": function() { 157 // Run quickly 158 // var speed = this.GetRunSpeed(); 159 // this.SelectAnimation("run", false, speed); 160 // this.SetMoveSpeed(speed); 259 this.SelectAnimation("walk", false, this.GetWalkSpeed()); 161 260 }, 162 261 163 "leave": function() {164 // Reset normal speed165 this.SetMoveSpeed(this.GetWalkSpeed());166 },167 168 262 "MoveStopped": function() { 169 // When we've run far enough, go back to the roaming state 170 this.SetNextState("ROAMING"); 263 this.SetNextState("ATTACKING"); 171 264 }, 172 265 }, 173 266 }, … … 188 281 var startingState = this.template.NaturalBehaviour; 189 282 startingState = startingState.toUpperCase(startingState); 190 283 191 if (startingState == "SKITTISH") 192 startingState = startingState + ".FEEDING"; 193 else 194 startingState = "PASSIVE.FEEDING"; 284 this.behavior = startingState; 285 AnimalFsm.Init(this, "FEEDING"); 286 }; 195 287 196 AnimalFsm.Init(this, startingState); 288 AnimalAI.prototype.OnOwnershipChanged = function(msg) 289 { 290 this.SetupRangeQuery(msg.to); 197 291 }; 198 292 199 293 AnimalAI.prototype.SetNextState = function(state) … … 235 329 236 330 AnimalAI.prototype.TimerHandler = function(data, lateness) 237 331 { 332 // Reset the timer 333 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 334 this.timer = cmpTimer.SetTimeout(this.entity, IID_AnimalAI, "TimerHandler", data.timerRepeat - lateness, data); 335 336 238 337 this.PushMessage({"type": "Timer", "data": data, "lateness": lateness}); 239 338 }; 240 339 … … 337 436 error("Called StartTimer when there's already an active timer"); 338 437 339 438 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 340 this.timer = cmpTimer.SetTimeout(this.entity, IID_AnimalAI, "TimerHandler", interval, data);439 this.timer = cmpTimer.SetTimeout(this.entity, IID_AnimalAI, "TimerHandler", interval, { "timerRepeat": data}); 341 440 }; 342 441 343 442 AnimalAI.prototype.StopTimer = function() … … 350 449 this.timer = undefined; 351 450 }; 352 451 452 AnimalAI.prototype.OnRangeUpdate = function(msg) 453 { 454 if (msg.tag == this.losRangeQuery) 455 AnimalFsm.ProcessMessage(this, {"type": "LosRangeUpdate", "data": msg}); 456 }; 457 458 AnimalAI.prototype.OnAttacked = function(msg) 459 { 460 AnimalFsm.ProcessMessage(this, {"type": "Attacked", "data": msg}); 461 }; 462 463 AnimalAI.prototype.CanAttack = function(target) 464 { 465 // Verify that we're able to respond to Attack commands 466 var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 467 if (!cmpAttack) 468 return false; 469 470 // TODO: verify that this is a valid target 471 472 return true; 473 }; 474 475 AnimalAI.prototype.CheckTargetRange = function(target, iid, type) 476 { 477 var cmpRanged = Engine.QueryInterface(this.entity, iid); 478 var range = cmpRanged.GetRange(type); 479 480 var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 481 return cmpMotion.IsInAttackRange(target, range.min, range.max); 482 }; 483 484 AnimalAI.prototype.MoveToTargetRange = function(target, iid, type) 485 { 486 var cmpRanged = Engine.QueryInterface(this.entity, iid); 487 var range = cmpRanged.GetRange(type); 488 489 var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 490 return cmpMotion.MoveToAttackRange(target, range.min, range.max); 491 }; 492 493 AnimalAI.prototype.GetBestAttack = function() 494 { 495 var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); 496 if (!cmpAttack) 497 return undefined; 498 return cmpAttack.GetBestAttack(); 499 }; 500 501 AnimalAI.prototype.OnDestroy = function() 502 { 503 // Clean up any timers that are now obsolete 504 this.StopTimer(); 505 506 // Clean up range queries 507 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 508 if (this.losRangeQuery) 509 rangeMan.DestroyActiveQuery(this.losRangeQuery); 510 }; 511 512 // Set up a range query for all enemy units within LOS range 513 // which can be attacked. 514 // This should be called whenever our ownership changes. 515 AnimalAI.prototype.SetupRangeQuery = function(owner) 516 { 517 var cmpVision = Engine.QueryInterface(this.entity, IID_Vision); 518 if (!cmpVision) 519 return; 520 521 var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 522 var playerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); 523 524 if (this.losRangeQuery) 525 rangeMan.DestroyActiveQuery(this.losRangeQuery); 526 527 var range = cmpVision.GetRange(); 528 529 // Find all players (i.e. exclude Gaia) 530 var players = []; 531 for (var i = 1; i < playerMan.GetNumPlayers(); ++i) 532 if (i != owner) 533 players.push(i); 534 535 this.losRangeQuery = rangeMan.CreateActiveQuery(this.entity, range, players, IID_DamageReceiver); 536 rangeMan.EnableActiveQuery(this.losRangeQuery); 537 }; 538 539 /** 540 * Try to find one of the given entities which can be attacked, 541 * and start attacking it. 542 * Returns true if it found something to attack. 543 */ 544 AnimalAI.prototype.AttackVisibleEntity = function(ents) 545 { 546 for each (var target in ents) 547 { 548 if (this.CanAttack(target)) 549 { 550 this.order = { "type": "Attack", "data": { "target": target }}; 551 AnimalFsm.ProcessMessage(this, {"type": "Order."+this.order.type, "data": this.order.data}); 552 return true; 553 } 554 } 555 return false; 556 }; 557 558 AnimalAI.prototype.Riposte = function(attacker) 559 { 560 // Default behaviour: attack back at our attacker 561 if (this.CanAttack(attacker)) 562 { 563 this.order = { "type": "Attack", "data": { "target": attacker }}; 564 AnimalFsm.ProcessMessage(this, {"type": "Order."+this.order.type, "data": this.order.data}); 565 if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType)) 566 { 567 // We've started walking to the given point 568 this.SetNextState("COMBAT.APPROACHING"); 569 } 570 else 571 { 572 // We are already at the target, or can't move at all, 573 // so try attacking it from here. 574 // TODO: need better handling of the can't-reach-target case 575 this.SetNextState("COMBAT.ATTACKING"); 576 } 577 return true; 578 } 579 return false; 580 }; 581 582 353 583 Engine.RegisterComponentType(IID_AnimalAI, "AnimalAI", AnimalAI); -
binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_aggressive.xml
5 5 <AnimalAI> 6 6 <NaturalBehaviour>aggressive</NaturalBehaviour> 7 7 </AnimalAI> 8 <Attack> 9 <Melee> 10 <Hack>1.0</Hack> 11 <Pierce>1.0</Pierce> 12 <Crush>0.0</Crush> 13 <MaxRange>4.0</MaxRange> 14 <RepeatTime>1000</RepeatTime> 15 </Melee> 16 </Attack> 8 17 </Entity> -
binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_violent.xml
5 5 <AnimalAI> 6 6 <NaturalBehaviour>violent</NaturalBehaviour> 7 7 </AnimalAI> 8 <Attack> 9 <Melee> 10 <Hack>1.0</Hack> 11 <Pierce>1.0</Pierce> 12 <Crush>0.0</Crush> 13 <MaxRange>4.0</MaxRange> 14 <RepeatTime>1000</RepeatTime> 15 </Melee> 16 </Attack> 8 17 </Entity> -
binaries/data/mods/public/simulation/templates/template_unit_fauna_wild_aggressive.xml
3 3 <Identity> 4 4 </Identity> 5 5 <AnimalAI> 6 <NaturalBehaviour> violent</NaturalBehaviour>6 <NaturalBehaviour>aggressive</NaturalBehaviour> 7 7 </AnimalAI> 8 <Attack> 9 <Melee> 10 <Hack>1.0</Hack> 11 <Pierce>1.0</Pierce> 12 <Crush>0.0</Crush> 13 <MaxRange>4.0</MaxRange> 14 <RepeatTime>1000</RepeatTime> 15 </Melee> 16 </Attack> 8 17 </Entity> -
binaries/data/mods/public/simulation/templates/template_unit_fauna_hunt_violent.xml
5 5 <AnimalAI> 6 6 <NaturalBehaviour>violent</NaturalBehaviour> 7 7 </AnimalAI> 8 <Attack> 9 <Melee> 10 <Hack>1.0</Hack> 11 <Pierce>1.0</Pierce> 12 <Crush>0.0</Crush> 13 <MaxRange>4.0</MaxRange> 14 <RepeatTime>1000</RepeatTime> 15 </Melee> 16 </Attack> 8 17 </Entity>