Ticket #1724: collectiveBehavior-v5.diff
File collectiveBehavior-v5.diff, 17.6 KB (added by , 11 years ago) |
---|
-
binaries/data/mods/public/simulation/components/interfaces/UnitAI.js
11 11 // Message of the form { "to": orderData }. 12 12 // sent whenever the unit changes state 13 13 Engine.RegisterMessageType("UnitAIOrderDataChanged"); 14 15 // Message of the form { "attacker": attacker-entity, "target": attacked-entity }. 16 // sent whenever a nearby unit is attacked 17 Engine.RegisterMessageType("NearbyUnitAttacked"); 18 -
binaries/data/mods/public/simulation/components/UnitAI.js
152 152 // ignore attacker 153 153 }, 154 154 155 "NearbyAttacked": function(msg) { 156 // ignore nearby attack 157 }, 158 155 159 "HealthChanged": function(msg) { 156 160 // ignore 157 161 }, … … 329 333 }, 330 334 331 335 "Order.Flee": function(msg) { 332 // We use the distance between the enities to account for ranged attacks 333 var distance = DistanceBetweenEntities(this.entity, this.order.data.target) + (+this.template.FleeDistance); 334 var ok = this.MoveToTargetRangeExplicit(this.order.data.target, distance, -1); 336 var ok = this.PerformFlee(this.order.data); 335 337 if (ok) 336 338 { 337 339 // We've started fleeing from the given target … … 341 343 this.SetNextState("INDIVIDUAL.FLEEING"); 342 344 } 343 345 else 344 {345 // We are already at the target, or can't move at all346 this.StopMoving();347 346 this.FinishOrder(); 348 }349 347 }, 350 348 351 349 "Order.Attack": function(msg) { … … 1149 1147 }, 1150 1148 1151 1149 "IDLE": { 1150 "NearbyAttacked": function(msg) { 1151 this.RespondToTargetedEntities([msg.data.attacker]); 1152 }, 1153 1152 1154 "enter": function() { 1153 1155 // Switch back to idle animation to guarantee we won't 1154 1156 // get stuck with an incorrect animation … … 1310 1312 // Return to our original position 1311 1313 if (this.GetStance().respondHoldGround) 1312 1314 this.WalkToHeldPosition(); 1315 else if (this.IsAnimal()) 1316 this.SetNextState("ROAMING"); 1313 1317 } 1314 1318 }, 1315 1319 … … 1498 1502 // Return to our original position 1499 1503 if (this.GetStance().respondHoldGround) 1500 1504 this.WalkToHeldPosition(); 1505 else if (this.IsAnimal()) 1506 this.SetNextState("ROAMING"); 1501 1507 } 1502 1508 }, 1503 1509 … … 2336 2342 } 2337 2343 }, 2338 2344 2345 "NearbyAttacked": function(msg) { 2346 // All wild animals in a given distance respond to the attack : 2347 // If skittish behaviour, they flee 2348 // If defensive behaviour, they attack if it is a member of their species which is involved 2349 if (this.template.NaturalBehaviour == "skittish" || 2350 this.template.NaturalBehaviour == "passive") 2351 { 2352 this.Flee(msg.data.attacker, false); 2353 } 2354 else if (this.IsDangerousAnimal() || this.template.NaturalBehaviour == "defensive") 2355 { 2356 var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); 2357 if (cmpIdentity && cmpIdentity.IsSameSpecy(msg.data.target)) 2358 { 2359 if (this.CanAttack(msg.data.attacker)) 2360 this.Attack(msg.data.attacker, false); 2361 } 2362 } 2363 }, 2364 2339 2365 "Order.LeaveFoundation": function(msg) { 2340 2366 // Run away from the foundation 2341 2367 this.MoveToTargetRangeExplicit(msg.data.target, +this.template.FleeDistance, +this.template.FleeDistance); … … 2406 2432 2407 2433 "leave": function() { 2408 2434 this.StopTimer(); 2435 // If not yet done, setup heldPosition for wild animals 2436 // (initialize here as animals start in feeding state) 2437 if (!this.IsDomestic() && !this.heldPosition) 2438 { 2439 var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); 2440 if (cmpPosition && cmpPosition.IsInWorld()) 2441 this.heldPosition = cmpPosition.GetPosition(); 2442 } 2409 2443 }, 2410 2444 2411 2445 "LosRangeUpdate": function(msg) { … … 2418 2452 } 2419 2453 } 2420 2454 // Start attacking one of the newly-seen enemy (if any) 2421 else if (this. template.NaturalBehaviour == "violent")2455 else if (this.IsDangerousAnimal()) 2422 2456 { 2423 2457 this.AttackVisibleEntity(msg.data.added); 2424 2458 } … … 2981 3015 UnitAI.prototype.OnAttacked = function(msg) 2982 3016 { 2983 3017 UnitFsm.ProcessMessage(this, {"type": "Attacked", "data": msg}); 3018 // warn nearby units that we are attacked 3019 this.WarnNearbyUnitsOfAttack(msg.attacker); 2984 3020 }; 2985 3021 3022 UnitAI.prototype.OnNearbyUnitAttacked = function(msg) 3023 { 3024 UnitFsm.ProcessMessage(this, {"type": "NearbyAttacked", "data": msg}); 3025 }; 3026 2986 3027 UnitAI.prototype.OnHealthChanged = function(msg) 2987 3028 { 2988 3029 UnitFsm.ProcessMessage(this, {"type": "HealthChanged", "from": msg.from, "to": msg.to}); … … 3973 4014 }; 3974 4015 3975 4016 /** 3976 * Adds flee order to the queue, not forced, so it can be 3977 * interrupted by attacks. 4017 * Adds flee order to the queue, not forced, so it can be interrupted by attacks. 3978 4018 */ 3979 4019 UnitAI.prototype.Flee = function(target, queued) 3980 4020 { … … 3982 4022 }; 3983 4023 3984 4024 /** 4025 * Internal function to abstract the details of the runaway 4026 * data.target is the attacker 4027 */ 4028 UnitAI.prototype.PerformFlee = function(data) 4029 { 4030 var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); 4031 if (!cmpPosition || !cmpPosition.IsInWorld()) 4032 return false; 4033 var cmpAttackerPosition = Engine.QueryInterface(data.target, IID_Position); 4034 if (!cmpAttackerPosition || !cmpAttackerPosition.IsInWorld()) 4035 return false; 4036 4037 var pos = cmpPosition.GetPosition(); 4038 var posAttacker = cmpAttackerPosition.GetPosition(); 4039 var dx = posAttacker.x - pos.x; 4040 var dz = posAttacker.z - pos.z; 4041 var dist = Math.sqrt(dx*dx+dz*dz); 4042 if (Engine.QueryInterface(data.target, IID_UnitAI)) 4043 { 4044 // The attacker is (in principle) facing its target, 4045 // so if we are not the target, we must have an angle compared to this direction 4046 // and we will flee in a direction which increase this angle 4047 var cosa = -dx/dist; // direction from attacker to this unit 4048 var sina = -dz/dist; 4049 var rotAttacker = cmpAttackerPosition.GetRotation(); 4050 var cosb = Math.sin(rotAttacker.y); // direction the attacker is pointing at 4051 var sinb = Math.cos(rotAttacker.y); 4052 var cosc = cosa*cosb + sina*sinb; // angle between these directions 4053 var sinc = sina*cosb - cosa*sinb; 4054 // We now compute from it a new angle which tends to move away from the attacker's direction 4055 if (cosc > 0) 4056 { 4057 cosc = cosc*cosc; 4058 if (sinc < 0) 4059 sinc = -Math.sqrt(1 - cosc*cosc); 4060 else 4061 sinc = Math.sqrt(1 - cosc*cosc); 4062 } 4063 } 4064 else 4065 { 4066 var cosc = 1; 4067 var sinc = 0; 4068 } 4069 // add a small additionnal random flee direction : 4070 // take a distribution flat in sin between about -pi/6 and pi/6 4071 // and approximate cos by 1-sin*sin/2 for these small angles 4072 var sinb = -0.5 + Math.random(); 4073 var cosb = 1 - sinb*sinb/2; 4074 var cosa = cosc*cosb - sinc*sinb; 4075 var sina = sinc*cosb + cosc*sinb; 4076 var tx = cosa*dx - sina*dz + pos.x; 4077 var tz = sina*dx + cosa*dz + pos.z; 4078 // We add the distance between the unit and the attacker to account for ranged attacks 4079 var distance = (+this.template.FleeDistance) + dist; 4080 var ok = this.MoveToPointRange(tx, tz, distance, -1); 4081 if (!ok) this.StopMoving(); 4082 return ok; 4083 }; 4084 4085 /** 3985 4086 * Adds cheer order to the queue. Forced so it won't be interrupted by attacks. 3986 4087 */ 3987 4088 UnitAI.prototype.Cheer = function() … … 4171 4272 return false; 4172 4273 }; 4173 4274 4275 /** 4276 * Warn units inside a given range that we are attacked 4277 */ 4278 UnitAI.prototype.WarnNearbyUnitsOfAttack = function(attacker) 4279 { 4280 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 4281 if (!cmpOwnership) return; 4282 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 4283 // Get units from same owner in a given range 4284 // TODO what range ? and what about allied units ? 4285 // For range, ideally we should take the vision range of the nearby unit, 4286 // but would be time expensive to do range check for each unit, so take the attack unit vision range 4287 // with a minimal value 4288 var player = [ cmpOwnership.GetOwner() ]; 4289 var range = 20; 4290 var cmpVision = Engine.QueryInterface(this.entity, IID_Vision); 4291 if (cmpVision) 4292 range = Math.max(range, cmpVision.GetRange()); 4293 var nearby = cmpRangeManager.ExecuteQuery(this.entity, 0, range, player, IID_UnitAI); 4294 for each (var ent in nearby) 4295 Engine.PostMessage(ent, MT_NearbyUnitAttacked, { "attacker": attacker, "target": this.entity }); 4296 }; 4297 4174 4298 //// Helper functions //// 4175 4299 4176 4300 UnitAI.prototype.CanAttack = function(target, forceResponse) … … 4403 4527 return (cmpPack && cmpPack.IsPacking()); 4404 4528 }; 4405 4529 4406 //// Animal specific functions ////4407 4530 4408 UnitAI.prototype.MoveRandomly = function(distance)4409 {4410 // We want to walk in a random direction, but avoid getting stuck4411 // in obstacles or narrow spaces.4412 // So pick a circular range from approximately our current position,4413 // and move outwards to the nearest point on that circle, which will4414 // lead to us avoiding obstacles and moving towards free space.4415 4416 // TODO: we probably ought to have a 'home' point, and drift towards4417 // that, so we don't spread out all across the whole map4418 4419 var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);4420 if (!cmpPosition)4421 return;4422 4423 if (!cmpPosition.IsInWorld())4424 return;4425 4426 var pos = cmpPosition.GetPosition();4427 4428 var jitter = 0.5;4429 4430 // Randomly adjust the range's center a bit, so we tend to prefer4431 // moving in random directions (if there's nothing in the way)4432 var tx = pos.x + (2*Math.random()-1)*jitter;4433 var tz = pos.z + (2*Math.random()-1)*jitter;4434 4435 var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);4436 cmpMotion.MoveToPointRange(tx, tz, distance, distance);4437 };4438 4439 4531 UnitAI.prototype.AttackEntitiesByPreference = function(ents) 4440 4532 { 4441 4533 var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack); … … 4465 4557 ); 4466 4558 }; 4467 4559 4560 //// Animal specific functions //// 4561 4562 UnitAI.prototype.MoveRandomly = function(distance) 4563 { 4564 var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); 4565 if (!cmpPosition || !cmpPosition.IsInWorld()) 4566 return; 4567 var pos = cmpPosition.GetPosition(); 4568 4569 // If we are far away from our starting position, we try to move closer 4570 // with a probability increasing with the distance 4571 if (this.heldPosition) 4572 { 4573 var dx = this.heldPosition.x - pos.x; 4574 var dz = this.heldPosition.z - pos.z; 4575 var dist = Math.sqrt(dx*dx + dz*dz); 4576 var proba = RandomInt(0, 100); 4577 if (20*dist/distance > Math.max(proba,30)) 4578 { 4579 dist -= distance; 4580 var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 4581 if (!cmpMotion.MoveToPointRange(this.heldPosition.x, this.heldPosition.z, dist, dist)) 4582 this.heldPosition = pos; 4583 return; 4584 } 4585 } 4586 4587 // Otherwise we want to walk in a random direction, but avoid getting stuck 4588 // in obstacles or narrow spaces. 4589 // So pick a circular range from approximately our current position, 4590 // and move outwards to the nearest point on that circle, which will 4591 // lead to us avoiding obstacles and moving towards free space. 4592 4593 var jitter = 0.5; 4594 4595 // Randomly adjust the range's center a bit, so we tend to prefer 4596 // moving in random directions (if there's nothing in the way) 4597 var tx = pos.x + (2*Math.random()-1)*jitter; 4598 var tz = pos.z + (2*Math.random()-1)*jitter; 4599 4600 var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); 4601 cmpMotion.MoveToPointRange(tx, tz, distance, distance); 4602 }; 4603 4468 4604 Engine.RegisterComponentType(IID_UnitAI, "UnitAI", UnitAI); -
binaries/data/mods/public/simulation/components/Identity.js
15 15 "<text/>" + 16 16 "</element>" + 17 17 "<optional>" + 18 "<element name='Species' a:help='Species this unit belongs to'>" + 19 "<text/>" + 20 "</element>" + 21 "</optional>" + 22 "<optional>" + 18 23 "<element name='SpecificName' a:help='Specific native-language name for this unit type'>" + 19 24 "<text/>" + 20 25 "</element>" + … … 138 143 return this.template.GenericName; 139 144 }; 140 145 146 Identity.prototype.IsSameSpecy = function(entity) 147 { 148 var cmpIdentity = Engine.QueryInterface(entity, IID_Identity); 149 if (this.template.Species) 150 return (cmpIdentity && this.template.Species == cmpIdentity.template.Species); 151 else 152 return (cmpIdentity && this.template.SpecificName == cmpIdentity.template.SpecificName); 153 }; 154 141 155 Engine.RegisterComponentType(IID_Identity, "Identity", Identity); -
binaries/data/mods/public/simulation/templates/gaia/fauna_elephant.xml
11 11 <Identity> 12 12 <Civ>gaia</Civ> 13 13 <Classes datatype="tokens">Elephant</Classes> 14 <Species>Elephant</Species> 14 15 <SpecificName>Elephant</SpecificName> 15 16 <Icon>gaia/fauna_elephant.png</Icon> 16 17 </Identity> -
binaries/data/mods/public/simulation/templates/gaia/fauna_lioness.xml
15 15 </Footprint> 16 16 <Identity> 17 17 <Civ>gaia</Civ> 18 <Species>Lion</Species> 18 19 <SpecificName>Lion</SpecificName> 19 20 <Icon>gaia/fauna_lion.png</Icon> 20 21 </Identity> -
binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_african_bush.xml
44 44 <Identity> 45 45 <Civ>gaia</Civ> 46 46 <Classes datatype="tokens">Elephant</Classes> 47 <Species>Elephant</Species> 47 48 <SpecificName>African Bush Elephant</SpecificName> 48 49 <Icon>gaia/fauna_elephant_african_bush.png</Icon> 49 50 </Identity> -
binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_african_infant.xml
12 12 <Identity> 13 13 <Civ>gaia</Civ> 14 14 <Classes datatype="tokens">Elephant</Classes> 15 <Species>Elephant</Species> 15 16 <SpecificName>African Elephant (Infant)</SpecificName> 16 17 <Icon>gaia/fauna_elephant_african_infant.png</Icon> 17 18 </Identity> -
binaries/data/mods/public/simulation/templates/gaia/fauna_lion.xml
15 15 </Footprint> 16 16 <Identity> 17 17 <Civ>gaia</Civ> 18 <Species>Lion</Species> 18 19 <SpecificName>Lion</SpecificName> 19 20 <Icon>gaia/fauna_lion.png</Icon> 20 21 </Identity> -
binaries/data/mods/public/simulation/templates/gaia/fauna_wolf_snow.xml
15 15 </Footprint> 16 16 <Identity> 17 17 <Civ>gaia</Civ> 18 <Species>Wolf</Species> 18 19 <SpecificName>Snow Wolf</SpecificName> 19 20 <Icon>gaia/fauna_wolf_snow.png</Icon> 20 21 </Identity> -
binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_north_african.xml
31 31 <Identity> 32 32 <Civ>gaia</Civ> 33 33 <Classes datatype="tokens">Elephant</Classes> 34 <Species>Elephant</Species> 34 35 <SpecificName>North African Elephant</SpecificName> 35 36 <Icon>gaia/fauna_elephant_north_african.png</Icon> 36 37 </Identity> -
binaries/data/mods/public/simulation/templates/gaia/fauna_wolf.xml
15 15 </Footprint> 16 16 <Identity> 17 17 <Civ>gaia</Civ> 18 <Species>Wolf</Species> 18 19 <SpecificName>Wolf</SpecificName> 19 20 <Icon>gaia/fauna_wolf.png</Icon> 20 21 </Identity> -
binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_asian.xml
44 44 <Identity> 45 45 <Civ>gaia</Civ> 46 46 <Classes datatype="tokens">Elephant</Classes> 47 <Species>Elephant</Species> 47 48 <SpecificName>Asian Elephant</SpecificName> 48 49 <Icon>gaia/fauna_elephant_african_bush.png</Icon> 49 50 </Identity>