Ticket #1724: collectiveBehavior-v5.diff

File collectiveBehavior-v5.diff, 17.6 KB (added by mimo, 11 years ago)
  • binaries/data/mods/public/simulation/components/interfaces/UnitAI.js

     
    1111// Message of the form { "to": orderData }.
    1212// sent whenever the unit changes state
    1313Engine.RegisterMessageType("UnitAIOrderDataChanged");
     14
     15// Message of the form { "attacker": attacker-entity, "target": attacked-entity }.
     16// sent whenever a nearby unit is attacked
     17Engine.RegisterMessageType("NearbyUnitAttacked");
     18
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    152152        // ignore attacker
    153153    },
    154154
     155    "NearbyAttacked": function(msg) {
     156        // ignore nearby attack
     157    },
     158
    155159    "HealthChanged": function(msg) {
    156160        // ignore
    157161    },
     
    329333    },
    330334
    331335    "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);
    335337        if (ok)
    336338        {
    337339            // We've started fleeing from the given target
     
    341343                this.SetNextState("INDIVIDUAL.FLEEING");
    342344        }
    343345        else
    344         {
    345             // We are already at the target, or can't move at all
    346             this.StopMoving();
    347346            this.FinishOrder();
    348         }
    349347    },
    350348
    351349    "Order.Attack": function(msg) {
     
    11491147        },
    11501148
    11511149        "IDLE": {
     1150            "NearbyAttacked": function(msg) {
     1151                this.RespondToTargetedEntities([msg.data.attacker]);
     1152            },
     1153
    11521154            "enter": function() {
    11531155                // Switch back to idle animation to guarantee we won't
    11541156                // get stuck with an incorrect animation
     
    13101312                        // Return to our original position
    13111313                        if (this.GetStance().respondHoldGround)
    13121314                            this.WalkToHeldPosition();
     1315                        else if (this.IsAnimal())
     1316                            this.SetNextState("ROAMING");
    13131317                    }
    13141318                },
    13151319
     
    14981502                        // Return to our original position
    14991503                        if (this.GetStance().respondHoldGround)
    15001504                            this.WalkToHeldPosition();
     1505                        else if (this.IsAnimal())
     1506                            this.SetNextState("ROAMING");
    15011507                    }
    15021508                },
    15031509
     
    23362342            }
    23372343        },
    23382344
     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
    23392365        "Order.LeaveFoundation": function(msg) {
    23402366            // Run away from the foundation
    23412367            this.MoveToTargetRangeExplicit(msg.data.target, +this.template.FleeDistance, +this.template.FleeDistance);
     
    24062432
    24072433            "leave": function() {
    24082434                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                }
    24092443            },
    24102444
    24112445            "LosRangeUpdate": function(msg) {
     
    24182452                    }
    24192453                }
    24202454                // Start attacking one of the newly-seen enemy (if any)
    2421                 else if (this.template.NaturalBehaviour == "violent")
     2455                else if (this.IsDangerousAnimal())
    24222456                {
    24232457                    this.AttackVisibleEntity(msg.data.added);
    24242458                }
     
    29813015UnitAI.prototype.OnAttacked = function(msg)
    29823016{
    29833017    UnitFsm.ProcessMessage(this, {"type": "Attacked", "data": msg});
     3018    // warn nearby units that we are attacked
     3019    this.WarnNearbyUnitsOfAttack(msg.attacker);
    29843020};
    29853021
     3022UnitAI.prototype.OnNearbyUnitAttacked = function(msg)
     3023{
     3024    UnitFsm.ProcessMessage(this, {"type": "NearbyAttacked", "data": msg});
     3025};
     3026
    29863027UnitAI.prototype.OnHealthChanged = function(msg)
    29873028{
    29883029    UnitFsm.ProcessMessage(this, {"type": "HealthChanged", "from": msg.from, "to": msg.to});
     
    39734014};
    39744015
    39754016/**
    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.
    39784018 */
    39794019UnitAI.prototype.Flee = function(target, queued)
    39804020{
     
    39824022};
    39834023
    39844024/**
     4025 * Internal function to abstract the details of the runaway
     4026 * data.target is the attacker
     4027 */
     4028UnitAI.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/**
    39854086 * Adds cheer order to the queue. Forced so it won't be interrupted by attacks.
    39864087 */
    39874088UnitAI.prototype.Cheer = function()
     
    41714272    return false;
    41724273};
    41734274
     4275/**
     4276 * Warn units inside a given range that we are attacked
     4277 */
     4278UnitAI.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
    41744298//// Helper functions ////
    41754299
    41764300UnitAI.prototype.CanAttack = function(target, forceResponse)
     
    44034527    return (cmpPack && cmpPack.IsPacking());
    44044528};
    44054529
    4406 //// Animal specific functions ////
    44074530
    4408 UnitAI.prototype.MoveRandomly = function(distance)
    4409 {
    4410     // We want to walk in a random direction, but avoid getting stuck
    4411     // 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 will
    4414     // lead to us avoiding obstacles and moving towards free space.
    4415 
    4416     // TODO: we probably ought to have a 'home' point, and drift towards
    4417     // that, so we don't spread out all across the whole map
    4418 
    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 prefer
    4431     // 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 
    44394531UnitAI.prototype.AttackEntitiesByPreference = function(ents)
    44404532{
    44414533    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
     
    44654557    );
    44664558};
    44674559
     4560//// Animal specific functions ////
     4561
     4562UnitAI.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
    44684604Engine.RegisterComponentType(IID_UnitAI, "UnitAI", UnitAI);
  • binaries/data/mods/public/simulation/components/Identity.js

     
    1515        "<text/>" +
    1616    "</element>" +
    1717    "<optional>" +
     18        "<element name='Species' a:help='Species this unit belongs to'>" +
     19            "<text/>" +
     20        "</element>" +
     21    "</optional>" +
     22    "<optional>" +
    1823        "<element name='SpecificName' a:help='Specific native-language name for this unit type'>" +
    1924            "<text/>" +
    2025        "</element>" +
     
    138143    return this.template.GenericName;
    139144};
    140145
     146Identity.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
    141155Engine.RegisterComponentType(IID_Identity, "Identity", Identity);
  • binaries/data/mods/public/simulation/templates/gaia/fauna_elephant.xml

     
    1111  <Identity>
    1212    <Civ>gaia</Civ>
    1313    <Classes datatype="tokens">Elephant</Classes>
     14    <Species>Elephant</Species>
    1415    <SpecificName>Elephant</SpecificName>
    1516    <Icon>gaia/fauna_elephant.png</Icon>
    1617  </Identity>
  • binaries/data/mods/public/simulation/templates/gaia/fauna_lioness.xml

     
    1515  </Footprint>
    1616  <Identity>
    1717    <Civ>gaia</Civ>
     18    <Species>Lion</Species>
    1819    <SpecificName>Lion</SpecificName>
    1920    <Icon>gaia/fauna_lion.png</Icon>
    2021  </Identity>
  • binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_african_bush.xml

     
    4444  <Identity>
    4545    <Civ>gaia</Civ>
    4646    <Classes datatype="tokens">Elephant</Classes>
     47    <Species>Elephant</Species>
    4748    <SpecificName>African Bush Elephant</SpecificName>
    4849    <Icon>gaia/fauna_elephant_african_bush.png</Icon>
    4950  </Identity>
  • binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_african_infant.xml

     
    1212  <Identity>
    1313    <Civ>gaia</Civ>
    1414    <Classes datatype="tokens">Elephant</Classes>
     15    <Species>Elephant</Species>
    1516    <SpecificName>African Elephant (Infant)</SpecificName>
    1617    <Icon>gaia/fauna_elephant_african_infant.png</Icon>
    1718  </Identity>
  • binaries/data/mods/public/simulation/templates/gaia/fauna_lion.xml

     
    1515  </Footprint>
    1616  <Identity>
    1717    <Civ>gaia</Civ>
     18    <Species>Lion</Species>
    1819    <SpecificName>Lion</SpecificName>
    1920    <Icon>gaia/fauna_lion.png</Icon>
    2021  </Identity>
  • binaries/data/mods/public/simulation/templates/gaia/fauna_wolf_snow.xml

     
    1515  </Footprint>
    1616  <Identity>
    1717    <Civ>gaia</Civ>
     18    <Species>Wolf</Species>
    1819    <SpecificName>Snow Wolf</SpecificName>
    1920    <Icon>gaia/fauna_wolf_snow.png</Icon>
    2021  </Identity>
  • binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_north_african.xml

     
    3131  <Identity>
    3232    <Civ>gaia</Civ>
    3333    <Classes datatype="tokens">Elephant</Classes>
     34    <Species>Elephant</Species>
    3435    <SpecificName>North African Elephant</SpecificName>
    3536    <Icon>gaia/fauna_elephant_north_african.png</Icon>
    3637  </Identity>
  • binaries/data/mods/public/simulation/templates/gaia/fauna_wolf.xml

     
    1515  </Footprint>
    1616  <Identity>
    1717    <Civ>gaia</Civ>
     18    <Species>Wolf</Species>
    1819    <SpecificName>Wolf</SpecificName>
    1920    <Icon>gaia/fauna_wolf.png</Icon>
    2021  </Identity>
  • binaries/data/mods/public/simulation/templates/gaia/fauna_elephant_asian.xml

     
    4444  <Identity>
    4545    <Civ>gaia</Civ>
    4646    <Classes datatype="tokens">Elephant</Classes>
     47    <Species>Elephant</Species>
    4748    <SpecificName>Asian Elephant</SpecificName>
    4849    <Icon>gaia/fauna_elephant_african_bush.png</Icon>
    4950  </Identity>