Ticket #1960: elevation_advantage_with_gui.2.diff

File elevation_advantage_with_gui.2.diff, 39.4 KB (added by sanderd17, 11 years ago)
  • binaries/data/mods/public/gui/session/input.js

     
    121121            // Show placement info tooltip if invalid position
    122122            placementSupport.tooltipError = !result.success;
    123123            placementSupport.tooltipMessage = result.success ? "" : result.message;
    124             return result.success;
     124
     125            if (!result.success)
     126                return false;
     127
     128            if (placementSupport.attack)
     129            {
     130                // building can be placed here, and has an attack
     131                // show the range advantage in the tooltip
     132                var cmd = {x: placementSupport.position.x,
     133                    z: placementSupport.position.z,
     134                    range: placementSupport.attack.maxRange,
     135                    elevationBonus: placementSupport.attack.elevationBonus,
     136                };
     137                var averageRange = Engine.GuiInterfaceCall("GetAverageRangeForBuildings",cmd);
     138                placementSupport.tooltipMessage = "Basic range: "+Math.round(cmd.range/4)+"\nAverage bonus range: "+Math.round((averageRange - cmd.range)/4);
     139            }
     140            return true;
    125141        }
    126142    }
    127143    else if (placementSupport.mode === "wall")
     
    14941510        placementSupport.template = buildTemplate;
    14951511        inputState = INPUT_BUILDING_PLACEMENT;
    14961512    }
     1513
     1514    if (templateData.attack &&
     1515        templateData.attack.Ranged &&
     1516        templateData.attack.Ranged.maxRange)
     1517    {
     1518        // add attack information to display a good tooltip
     1519        placementSupport.attack = templateData.attack.Ranged;
     1520    }
    14971521}
    14981522
    14991523// Called by GUI when user changes preferred trading goods
  • binaries/data/mods/public/gui/session/placement.js

     
    2222   
    2323    this.SetDefaultAngle();
    2424    this.RandomizeActorSeed();
     25
     26    this.attack = null;
    2527   
    2628    Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""});
    2729    Engine.GuiInterfaceCall("SetWallPlacementPreview", {"wallSet": null});
  • binaries/data/mods/public/gui/session/selection_details.js

     
    183183        getGUIObjectByName("icon").sprite = "bkFillBlack";
    184184    }
    185185
    186     // Attack and Armor
    187     var type = "";
    188     if (entState.attack)
    189         type = entState.attack.type + " ";
    190 
    191     attack = "[font=\"serif-bold-13\"]"+type+"Attack:[/font] " + damageTypeDetails(entState.attack);
    192     // Show max attack range if ranged attack, also convert to tiles (4m per tile)
    193     if (entState.attack && entState.attack.type == "Ranged")
    194         attack += ", [font=\"serif-bold-13\"]Range:[/font] " + Math.round(entState.attack.maxRange/4);
    195     getGUIObjectByName("attackAndArmorStats").tooltip = attack + "\n[font=\"serif-bold-13\"]Armor:[/font] " + armorTypeDetails(entState.armour);
    196 
    197     // Icon Tooltip
     186    // Attack and Armor
     187    var type = "";
     188    if (entState.attack)
     189    {
     190        type = entState.attack.type + " ";
     191
     192        attack = "[font=\"serif-bold-13\"]"+type+"Attack:[/font] " + damageTypeDetails(entState.attack);
     193        // Show max attack range if ranged attack, also convert to tiles (4m per tile)
     194        if (entState.attack.type == "Ranged")
     195        {
     196            var realRange = entState.attack.elevationAdaptedRange;
     197            var range =  entState.attack.maxRange;
     198            attack += ", [font=\"serif-bold-13\"]Range:[/font] " +
     199                Math.round(range/4);
     200
     201            if (Math.round((realRange - range)/4) > 0)
     202            {
     203                attack += " (+" + Math.round((realRange - range)/4) + ")";
     204            }
     205            else if (Math.round((realRange - range)/4) < 0)
     206            {
     207                attack += " (" + Math.round((realRange - range)/4) + ")";
     208            } // don't show when it's 0
     209
     210        }
     211    }
     212   
     213    getGUIObjectByName("attackAndArmorStats").tooltip = attack + "\n[font=\"serif-bold-13\"]Armor:[/font] " + armorTypeDetails(entState.armour);
     214
     215    // Icon Tooltip
    198216    var iconTooltip = "";
    199217
    200218    if (genericName)
  • binaries/data/mods/public/simulation/components/Attack.js

     
    6767            "<Crush>0.0</Crush>" +
    6868            "<MaxRange>44.0</MaxRange>" +
    6969            "<MinRange>20.0</MinRange>" +
     70            "<optional>"+
     71                "<element name='ElevationBonus' a:help='give an elevation advantage (in meters)'><ref name='nonNegativeDecimal'/></element>" +
     72            "</optional>" +
    7073            "<PrepareTime>800</PrepareTime>" +
    7174            "<RepeatTime>1600</RepeatTime>" +
    7275            "<ProjectileSpeed>50.0</ProjectileSpeed>" +
     
    125128                "<element name='Crush' a:help='Crush damage strength'><ref name='nonNegativeDecimal'/></element>" +
    126129                "<element name='MaxRange' a:help='Maximum attack range (in metres)'><ref name='nonNegativeDecimal'/></element>" +
    127130                "<element name='MinRange' a:help='Minimum attack range (in metres)'><ref name='nonNegativeDecimal'/></element>" +
     131                "<optional>"+
     132                    "<element name='ElevationBonus' a:help='give an elevation advantage (in meters)'><ref name='nonNegativeDecimal'/></element>" +
     133                "</optional>" +
    128134                "<element name='PrepareTime' a:help='Time from the start of the attack command until the attack actually occurs (in milliseconds). This value relative to RepeatTime should closely match the \"event\" point in the actor&apos;s attack animation'>" +
    129135                    "<data type='nonNegativeInteger'/>" +
    130136                "</element>" +
     
    360366   
    361367    var min = +(this.template[type].MinRange || 0);
    362368    min = ApplyTechModificationsToEntity("Attack/" + type + "/MinRange", min, this.entity);
     369
     370    var elevationBonus = +(this.template[type].ElevationBonus || 0);
     371    elevationBonus = ApplyTechModificationsToEntity("Attack/" + type + "/ElevationBonus", elevationBonus, this.entity);
    363372   
    364     return { "max": max, "min": min };
     373    return { "max": max, "min": min, "elevationBonus": elevationBonus};
    365374};
    366375
    367376// Calculate the attack damage multiplier against a target
  • binaries/data/mods/public/simulation/components/BuildingAI.js

     
    1212        "<ref name='nonNegativeDecimal'/>" +
    1313    "</element>";
    1414
     15
    1516/**
    1617 * Initialize BuildingAI Component
    1718 */
     
    99100    if (cmpAttack)
    100101    {
    101102        var range = cmpAttack.GetRange("Ranged");
    102         this.enemyUnitsQuery = cmpRangeManager.CreateActiveQuery(this.entity, range.min, range.max, players, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal"));
     103        this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(this.entity, range.min, range.max, range.elevationBonus, players, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal"));
    103104        cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery);
    104105    }
    105106};
     
    133134        var range = cmpAttack.GetRange("Ranged");
    134135
    135136        // This query is only interested in Gaia entities that can attack.
    136         this.gaiaUnitsQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, [0], IID_Attack, rangeMan.GetEntityFlagMask("normal"));
     137        this.gaiaUnitsQuery = rangeMan.CreateActiveParabolicQuery(this.entity, range.min, range.max, range.elevationBonus, [0], IID_Attack, rangeMan.GetEntityFlagMask("normal"));
    137138        rangeMan.EnableActiveQuery(this.gaiaUnitsQuery);
    138139    }
    139140};
     
    214215    var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
    215216    if (cmpAttack)
    216217    {
     218
    217219        var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    218220        this.timer = cmpTimer.SetTimeout(this.entity, IID_BuildingAI, "FireArrows", timerInterval, {});
    219221        var arrowsToFire = 0;
     
    239241            //Fire N arrows, 0 <= N <= Number of arrows left
    240242            arrowsToFire = Math.floor(Math.random() * this.arrowsLeft);
    241243        }
     244
    242245        if (this.targetUnits.length > 0)
    243246        {
     247            var clonedTargets = this.targetUnits.slice();
    244248            for (var i = 0;i < arrowsToFire;i++)
    245249            {
    246                 cmpAttack.PerformAttack("Ranged", this.targetUnits[Math.floor(Math.random() * this.targetUnits.length)]);
    247                 PlaySound("arrowfly", this.entity);
     250                var target = clonedTargets[Math.floor(Math.random() * this.targetUnits.length)];
     251                if (
     252                    target &&
     253                    this.CheckTargetVisible(target)
     254                   )
     255                {
     256                    cmpAttack.PerformAttack("Ranged", target);
     257                    PlaySound("arrowfly", this.entity);
     258
     259                }
     260                else
     261                {
     262                    clonedTargets.splice(clonedTargets.indexOf(target),1);
     263                    i--; // one extra arrow left to fire
     264                    if(clonedTargets.length < 1)
     265                    {
     266                        this.arrowsLeft += arrowsToFire;
     267                        // no targets found in this round, save arrows and go to next round
     268                        break;
     269                    }
     270                }
    248271            }
    249272            this.arrowsLeft -= arrowsToFire;
    250273        }
     
    252275    }
    253276};
    254277
     278/**
     279 * Returns true if the target entity is visible through the FoW/SoD.
     280 */
     281BuildingAI.prototype.CheckTargetVisible = function(target)
     282{
     283    var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     284    if (!cmpOwnership)
     285        return false;
     286
     287    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     288
     289    if (cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner(), false) == "hidden")
     290        return false;
     291
     292    // Either visible directly, or visible in fog
     293    return true;
     294};
     295
    255296Engine.RegisterComponentType(IID_BuildingAI, "BuildingAI", BuildingAI);
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    173173    if (cmpPosition && cmpPosition.IsInWorld())
    174174    {
    175175        ret.position = cmpPosition.GetPosition();
     176        ret.rotation = cmpPosition.GetRotation();
    176177    }
    177178
    178179    var cmpHealth = Engine.QueryInterface(ent, IID_Health);
     
    184185        ret.needsHeal = !cmpHealth.IsUnhealable();
    185186    }
    186187
     188    var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
     189    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    187190    var cmpAttack = Engine.QueryInterface(ent, IID_Attack);
     191
    188192    if (cmpAttack)
    189193    {
    190194        var type = cmpAttack.GetBestAttack(); // TODO: how should we decide which attack to show? show all?
     
    193197        ret.attack.type = type;
    194198        ret.attack.minRange = range.min;
    195199        ret.attack.maxRange = range.max;
     200        if (type == "Ranged")
     201        {
     202            ret.attack.elevationBonus = range.elevationBonus;
     203            if (cmpUnitAI && cmpPosition && cmpPosition.IsInWorld())
     204            {
     205                // For units, take the rage in front of it, no spread. So angle = 0
     206                ret.attack.elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(ret.position, ret.rotation, range.max, range.elevationBonus, 0);
     207            }
     208            else if(cmpPosition && cmpPosition.IsInWorld())
     209            {
     210                // For buildings, take the average elevation around it. So angle = 2*pi
     211                ret.attack.elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(ret.position, ret.rotation, range.max, range.elevationBonus, 2*Math.PI);
     212            }
     213            else
     214            {
     215                // not in world, set a default?
     216                ret.attack.elevationAdaptedRange = ret.attack.maxRange;
     217            }
     218           
     219        }
     220        else
     221        {
     222            // not a ranged attack, set some defaults
     223            ret.attack.elevationBonus = 0;
     224            ret.attack.elevationAdaptedRange = ret.attack.maxRange;
     225        }
    196226    }
    197227
    198228    var cmpArmour = Engine.QueryInterface(ent, IID_DamageReceiver);
     
    310340            "req": cmpPromotion.GetRequiredXp()
    311341        };
    312342    }
    313    
    314     var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
     343
    315344    if (cmpUnitAI)
    316345    {
    317346        ret.unitAI = {
     
    322351        if (cmpUnitAI.isGarrisoned && ret.player)
    323352            ret.template = "p" + ret.player + "&" + ret.template;
    324353    }
    325    
     354
    326355    var cmpGate = Engine.QueryInterface(ent, IID_Gate);
    327356    if (cmpGate)
    328357    {
     
    346375        };
    347376    }
    348377
    349     var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
    350378    ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false);
    351379
    352380    return ret;
    353381};
    354382
     383GuiInterface.prototype.GetAverageRangeForBuildings = function(player, cmd)
     384{
     385    var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
     386    var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
     387    var rot = {x:0, y:0, z:0};
     388    var pos = {x:cmd.x,z:cmd.z};
     389    pos.y = cmpTerrain.GetGroundLevel(cmd.x, cmd.z);
     390    var elevationBonus = cmd.elevationBonus || 0;
     391    var range = cmd.range;
     392
     393    return cmpRangeManager.GetElevationAdaptedRange(pos, rot, range, elevationBonus, 2*Math.PI);
     394};
     395
     396
     397
    355398GuiInterface.prototype.GetTemplateData = function(player, extendedName)
    356399{
    357400    var name = extendedName;
     
    390433                "crush": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/Crush", +(template.Attack[type].Crush || 0)),
    391434                "minRange": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/MinRange", +(template.Attack[type].MinRange || 0)),
    392435                "maxRange": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/MaxRange", +template.Attack[type].MaxRange),
     436                "elevationBonus": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/ElevationBonus", +(template.Attack[type].ElevationBonus || 0)),
    393437            };
    394438        }
    395439    }
     
    17261770    "GetRenamedEntities": 1,
    17271771    "ClearRenamedEntities": 1,
    17281772    "GetEntityState": 1,
     1773    "GetAverageRangeForBuildings": 1,
    17291774    "GetTemplateData": 1,
    17301775    "GetTechnologyData": 1,
    17311776    "IsTechnologyResearched": 1,
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    367367        this.attackType = type;
    368368
    369369        // If we are already at the target, try attacking it from here
    370         if (this.CheckTargetRange(this.order.data.target, IID_Attack, this.attackType))
     370        if (this.CheckTargetAttackRange(this.order.data.target, IID_Attack, this.attackType))
    371371        {
    372372            this.StopMoving();
    373373            // For packable units within attack range:
     
    427427            return;
    428428        }
    429429
    430         // Try to move within attack range
    431         if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
     430        // Try to move within attack range 
     431        if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.attackType,0.5))
    432432        {
    433433            // We've started walking to the given point
    434434            if (this.IsAnimal())
     
    13141314                },
    13151315
    13161316                "MoveCompleted": function() {
    1317                     // If the unit needs to unpack, do so
    1318                     if (this.CanUnpack())
    1319                         this.SetNextState("UNPACKING");
    1320                     else
    1321                         this.SetNextState("ATTACKING");
     1317
     1318                    if (this.CheckTargetAttackRange(this.order.data.target, IID_Attack , this.attackType))
     1319                    {
     1320                        // If the unit needs to unpack, do so
     1321                        if (this.CanUnpack())
     1322                            this.SetNextState("UNPACKING");
     1323                        else
     1324                            this.SetNextState("ATTACKING");
     1325                    }
     1326                    else
     1327                    {
     1328                        if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.attackType,0))
     1329                        {
     1330                            this.SetNextState("APPROACHING");
     1331                        }
     1332                        else
     1333                        {
     1334                            // Give up
     1335                            this.FinishOrder();
     1336                        }
     1337                    }
    13221338                },
    13231339
    13241340                "Attacked": function(msg) {
     
    13341350            "UNPACKING": {
    13351351                "enter": function() {
    13361352                    // If we're not in range yet (maybe we stopped moving), move to target again
    1337                     if (!this.CheckTargetRange(this.order.data.target, IID_Attack, this.attackType))
     1353                    if (!this.CheckTargetAttackRange(this.order.data.target, IID_Attack, this.attackType))
    13381354                    {
    1339                         if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.attackType))
     1355                        if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.attackType,0))
    13401356                            this.SetNextState("APPROACHING");
    13411357                        else
    13421358                        {
     
    14021418                    if (this.TargetIsAlive(target) && this.CanAttack(target, this.order.data.forceResponse || null))
    14031419                    {
    14041420                        // Check we can still reach the target
    1405                         if (this.CheckTargetRange(target, IID_Attack, this.attackType))
     1421                        if (this.CheckTargetAttackRange(target, IID_Attack, this.attackType))
    14061422                        {
    14071423                            var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
    14081424                            this.lastAttacked = cmpTimer.GetTime() - msg.lateness;
     
    32873303    return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
    32883304};
    32893305
     3306/**
     3307 * Move unit so we hope the target is in the attack range
     3308 * for melee attacks, this goes straight to the default range checks
     3309 * for ranged attacks, the parabolic range is used, so we can't know exactly at what horizontal range the target can be reached
     3310 * That's why a guess is needed
     3311 * a guess of 1 will take the maximum of the possible ranges, and stay far away
     3312 * a guess of 0 will take the minimum of the possible ranges and, in most cases, will have the target in range.
     3313 * every guess inbetween is a linear interpollation
     3314 */
     3315UnitAI.prototype.MoveToTargetAttackRange = function(target, iid, type,guess)
     3316{
     3317
     3318    if(type!= "Ranged") {
     3319        return this.MoveToTargetRange(target, iid, type);
     3320    }
     3321    if (!this.CheckTargetVisible(target)) {
     3322        return false;
     3323    }
     3324    var cmpRanged = Engine.QueryInterface(this.entity, iid);
     3325    var range = cmpRanged.GetRange(type);
     3326
     3327    var thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     3328    var s = thisCmpPosition.GetPosition();
     3329
     3330    var targetCmpPosition = Engine.QueryInterface(target, IID_Position);
     3331    if(!targetCmpPosition.IsInWorld())
     3332        return false;   
     3333
     3334    var t = targetCmpPosition.GetPosition();
     3335    // h is positive when I'm higher than the target
     3336    var h = s.y-t.y+range.elevationBonus;
     3337
     3338    // No negative roots please
     3339    if(h>-range.max/2) {
     3340        var parabolicMaxRange = Math.sqrt(range.max*range.max+2*range.max*h);
     3341    } else {
     3342        // return false? Or hope you come close enough?
     3343        var parabolicMaxRange = 0;
     3344        //return false;
     3345    }
     3346    // the parabole changes while walking, take something in the middle
     3347    var guessedMaxRange = Math.max(range.max, parabolicMaxRange)*guess+Math.min(range.max, parabolicMaxRange)*(1-guess) ;
     3348   
     3349    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
     3350    var r = cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange);
     3351    return r;
     3352};
     3353
    32903354UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max)
    32913355{
    32923356    if (!this.CheckTargetVisible(target))
     
    33113375    return cmpUnitMotion.IsInTargetRange(target, range.min, range.max);
    33123376};
    33133377
     3378/**
     3379 * Check if the target is inside the attack range
     3380 * For melee attacks, this goes straigt to the regular range calculation
     3381 * For ranged attacks, the parabolic formula is used to accout for bigger ranges
     3382 * when the target is lower, and smaller ranges when the target is higher
     3383 */
     3384UnitAI.prototype.CheckTargetAttackRange = function(target, iid, type)
     3385{
     3386
     3387    if (type != "Ranged")
     3388    {
     3389        return this.CheckTargetRange(target,iid,type);
     3390    }
     3391   
     3392    var targetCmpPosition = Engine.QueryInterface(target, IID_Position);
     3393    if (!targetCmpPosition || !targetCmpPosition.IsInWorld())
     3394    {
     3395        return false;
     3396    }
     3397
     3398    var cmpRanged = Engine.QueryInterface(this.entity, iid);
     3399    var range = cmpRanged.GetRange(type);
     3400
     3401    var thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     3402    var s = thisCmpPosition.GetPosition();
     3403
     3404    var t = targetCmpPosition.GetPosition();
     3405
     3406    var h = s.y-t.y+range.elevationBonus;
     3407    var maxRangeSq = 2*range.max*(h + range.max/2);
     3408    if (maxRangeSq<0)
     3409    {
     3410        // certainly outside the range, the target is even too high to reach.
     3411        return false;
     3412    }
     3413
     3414    // use native range checking function to take account of target shape
     3415    // in case of big targets
     3416    return this.CheckTargetRangeExplicit(target,range.min,Math.sqrt(maxRangeSq));
     3417};
     3418
    33143419UnitAI.prototype.CheckTargetRangeExplicit = function(target, min, max)
    33153420{
    33163421    var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
  • binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml

     
    22<Entity parent="template_structure_defense">
    33  <Attack>
    44    <Ranged>
    5       <Hack>0.0</Hack>
    6       <Pierce>20.0</Pierce>
    7       <Crush>0.0</Crush>
    8       <MaxRange>70.0</MaxRange>
    9       <MinRange>16.0</MinRange>
    10       <ProjectileSpeed>75.0</ProjectileSpeed>
    11       <PrepareTime>1200</PrepareTime>
    12       <RepeatTime>2000</RepeatTime>
     5      <Hack>0.0</Hack>
     6      <Pierce>20.0</Pierce>
     7      <Crush>0.0</Crush>
     8      <MaxRange>57.0</MaxRange>
     9      <MinRange>16.0</MinRange>
     10      <ElevationBonus>15</ElevationBonus>
     11      <ProjectileSpeed>75.0</ProjectileSpeed>
     12      <PrepareTime>1200</PrepareTime>
     13      <RepeatTime>2000</RepeatTime>
    1314      <Spread>1.5</Spread>
    1415    </Ranged>
    1516  </Attack>
  • source/scriptinterface/ScriptInterface.h

     
    3838// Set the maximum number of function arguments that can be handled
    3939// (This should be as small as possible (for compiler efficiency),
    4040// but as large as necessary for all wrapped functions)
    41 #define SCRIPT_INTERFACE_MAX_ARGS 7
     41#define SCRIPT_INTERFACE_MAX_ARGS 8
    4242
    4343// TODO: what's a good default?
    4444#define DEFAULT_RUNTIME_SIZE 16 * 1024 * 1024
  • source/simulation2/components/CCmpRangeManager.cpp

     
    2020#include "simulation2/system/Component.h"
    2121#include "ICmpRangeManager.h"
    2222
     23#include "ICmpTerrain.h"
    2324#include "simulation2/MessageTypes.h"
    2425#include "simulation2/components/ICmpPosition.h"
    2526#include "simulation2/components/ICmpTerritoryManager.h"
    2627#include "simulation2/components/ICmpVision.h"
     28#include "simulation2/components/ICmpWaterManager.h"
    2729#include "simulation2/helpers/Render.h"
    2830#include "simulation2/helpers/Spatial.h"
    2931
     
    4446struct Query
    4547{
    4648    bool enabled;
     49    bool parabolic;
    4750    entity_id_t source;
    4851    entity_pos_t minRange;
    4952    entity_pos_t maxRange;
     53    entity_pos_t elevationBonus;
    5054    u32 ownersMask;
    5155    i32 interface;
    5256    std::vector<entity_id_t> lastMatch;
     
    8892}
    8993
    9094/**
     95 * Checks whether v is in a parabolic range of (0,0,0)
     96 * The highest point of the paraboloid is (0,range/2,0)
     97 * and the circle of distance 'range' around (0,0,0) on height y=0 is part of the paraboloid
     98 *
     99 * Avoids sqrting and overflowing.
     100 */
     101static bool InParabolicRange(CFixedVector3D v, fixed range)
     102{
     103    i64 x = (i64)v.X.GetInternalValue(); // abs(x) <= 2^31
     104    i64 z = (i64)v.Z.GetInternalValue();
     105    i64 xx = (x * x); // xx <= 2^62
     106    i64 zz = (z * z);
     107    i64 d2 = (xx + zz) >> 1; // d2 <= 2^62 (no overflow)
     108   
     109    i64 y = (i64)v.Y.GetInternalValue();
     110
     111    i64 c = (i64)range.GetInternalValue();
     112    i64 c_2 = c >> 1;
     113
     114    i64 c2 = (c_2-y)*c;
     115
     116    if (d2 <= c2)
     117        return true;
     118
     119    return false;
     120}
     121
     122struct EntityParabolicRangeOutline
     123{
     124    entity_id_t source;
     125    CFixedVector3D position;
     126    entity_pos_t range;
     127    std::vector<entity_pos_t> outline;
     128};
     129
     130static std::map<entity_id_t, EntityParabolicRangeOutline> ParabolicRangesOutlines;
     131
     132/**
    91133 * Representation of an entity, with the data needed for queries.
    92134 */
    93135struct EntityData
     
    113155    void operator()(S& serialize, const char* UNUSED(name), Query& value)
    114156    {
    115157        serialize.Bool("enabled", value.enabled);
     158        serialize.Bool("parabolic",value.parabolic);
    116159        serialize.NumberU32_Unbounded("source", value.source);
    117160        serialize.NumberFixed_Unbounded("min range", value.minRange);
    118161        serialize.NumberFixed_Unbounded("max range", value.maxRange);
     162        serialize.NumberFixed_Unbounded("elevation bonus", value.elevationBonus);
    119163        serialize.NumberU32_Unbounded("owners mask", value.ownersMask);
    120164        serialize.NumberI32_Unbounded("interface", value.interface);
    121165        SerializeVector<SerializeU32_Unbounded>()(serialize, "last match", value.lastMatch);
     
    589633        return id;
    590634    }
    591635
     636    virtual tag_t CreateActiveParabolicQuery(entity_id_t source,
     637        entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus,
     638        std::vector<int> owners, int requiredInterface, u8 flags)
     639    {
     640        tag_t id = m_QueryNext++;
     641        m_Queries[id] = ConstructParabolicQuery(source, minRange, maxRange, elevationBonus, owners, requiredInterface, flags);
     642
     643        return id;
     644    }
     645
    592646    virtual void DestroyActiveQuery(tag_t tag)
    593647    {
    594648        if (m_Queries.find(tag) == m_Queries.end())
     
    812866                r.push_back(it->first);
    813867            }
    814868        }
    815         else
     869        // Not the entire world, so check a parabolic range, or a regular range
     870        else if (q.parabolic)
    816871        {
     872            // elevationBonus is part of the 3D position, as the source is really that much heigher
     873            CFixedVector3D pos3d = cmpSourcePosition->GetPosition()+
     874                CFixedVector3D(entity_pos_t::Zero(), q.elevationBonus, entity_pos_t::Zero()) ;
     875            // Get a quick list of entities that are potentially in range, with a cutoff of 2*maxRange
     876            std::vector<entity_id_t> ents = m_Subdivision.GetNear(pos, q.maxRange*2);
     877
     878            for (size_t i = 0; i < ents.size(); ++i)
     879            {
     880                std::map<entity_id_t, EntityData>::const_iterator it = m_EntityData.find(ents[i]);
     881                ENSURE(it != m_EntityData.end());
     882
     883                if (!TestEntityQuery(q, it->first, it->second))
     884                    continue;
     885               
     886                CmpPtr<ICmpPosition> cmpSecondPosition(GetSimContext(), ents[i]);
     887                if (!cmpSecondPosition || !cmpSecondPosition->IsInWorld())
     888                    continue;
     889                CFixedVector3D secondPosition = cmpSecondPosition->GetPosition();
     890
     891                // Restrict based on precise distance
     892                if (!InParabolicRange(
     893                        CFixedVector3D(it->second.x, secondPosition.Y, it->second.z)
     894                            - pos3d,
     895                        q.maxRange))
     896                    continue;
     897
     898                if (!q.minRange.IsZero())
     899                {
     900                    int distVsMin = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange);
     901                    if (distVsMin < 0)
     902                        continue;
     903                }
     904
     905                r.push_back(it->first);
     906
     907            }
     908        }
     909        // check a regular range (i.e. not the entire world, and not parabolic)
     910        else
     911        {
    817912            // Get a quick list of entities that are potentially in range
    818913            std::vector<entity_id_t> ents = m_Subdivision.GetNear(pos, q.maxRange);
    819914
     
    838933                }
    839934
    840935                r.push_back(it->first);
     936
    841937            }
    842938        }
    843939    }
    844940
     941
     942    virtual entity_pos_t GetElevationAdaptedRange(CFixedVector3D pos, CFixedVector3D rot, entity_pos_t range, entity_pos_t elevationBonus, entity_pos_t angle)
     943    {
     944        entity_pos_t r = entity_pos_t::Zero() ;
     945       
     946        pos.Y += elevationBonus;
     947        entity_pos_t orientation = rot.Y;
     948
     949        entity_pos_t maxAngle = orientation + angle/2;
     950        entity_pos_t minAngle = orientation - angle/2;
     951
     952        int numberOfSteps = 16;
     953
     954        if (angle == entity_pos_t::Zero())
     955            numberOfSteps = 1;
     956
     957        std::vector<entity_pos_t> coords = getParabolicRangeForm(pos, range, range*2, minAngle, maxAngle, numberOfSteps);
     958
     959        entity_pos_t part =  entity_pos_t::FromInt(numberOfSteps);
     960
     961        for (int i = 0; i < numberOfSteps; i++)
     962        {
     963            r = r + CFixedVector2D(coords[2*i],coords[2*i+1]).Length() / part;
     964        }
     965       
     966        return r;
     967       
     968    }
     969
     970    virtual std::vector<entity_pos_t> getParabolicRangeForm(CFixedVector3D pos, entity_pos_t maxRange, entity_pos_t cutoff, entity_pos_t minAngle, entity_pos_t maxAngle, int numberOfSteps)
     971    {
     972       
     973        // angle = 0 goes in the positive Z direction
     974        entity_pos_t precision = entity_pos_t::FromInt((int)TERRAIN_TILE_SIZE)/8;
     975
     976        std::vector<entity_pos_t> r;
     977       
     978
     979        CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
     980        CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY);
     981        entity_pos_t waterLevel = cmpWaterManager->GetWaterLevel(pos.X,pos.Z);
     982        entity_pos_t thisHeight = pos.Y > waterLevel ? pos.Y : waterLevel;
     983
     984        if (cmpTerrain)
     985        {
     986            for (int i = 0; i < numberOfSteps; i++)
     987            {
     988                entity_pos_t angle = minAngle + (maxAngle - minAngle) / numberOfSteps * i;
     989                entity_pos_t sin;
     990                entity_pos_t cos;
     991                entity_pos_t minDistance = entity_pos_t::Zero();
     992                entity_pos_t maxDistance = cutoff;
     993                sincos_approx(angle,sin,cos);
     994
     995                CFixedVector2D minVector = CFixedVector2D(entity_pos_t::Zero(),entity_pos_t::Zero());
     996                CFixedVector2D maxVector = CFixedVector2D(sin,cos).Multiply(cutoff);
     997                entity_pos_t targetHeight = cmpTerrain->GetGroundLevel(pos.X+maxVector.X,pos.Z+maxVector.Y);
     998                // use water level to display range on water
     999                targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
     1000
     1001                if (InParabolicRange(CFixedVector3D(maxVector.X,targetHeight-thisHeight,maxVector.Y),maxRange))
     1002                {
     1003                    r.push_back(maxVector.X);
     1004                    r.push_back(maxVector.Y);
     1005                    continue;
     1006                }
     1007               
     1008                // Loop until vectors come close enough
     1009                while ((maxVector - minVector).CompareLength(precision) > 0)
     1010                {
     1011                    // difference still bigger than precision, bisect to get smaller difference
     1012                    entity_pos_t newDistance = (minDistance+maxDistance)/entity_pos_t::FromInt(2);
     1013
     1014                    CFixedVector2D newVector = CFixedVector2D(sin,cos).Multiply(newDistance);
     1015
     1016                    // get the height of the ground
     1017                    targetHeight = cmpTerrain->GetGroundLevel(pos.X+newVector.X,pos.Z+newVector.Y);
     1018                    targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
     1019
     1020                    if (InParabolicRange(CFixedVector3D(newVector.X,targetHeight-thisHeight,newVector.Y),maxRange))
     1021                    {
     1022                        // new vector is in parabolic range, so this is a new minVector
     1023                        minVector = newVector;
     1024                        minDistance = newDistance;
     1025                    }
     1026                    else
     1027                    {
     1028                        // new vector is out parabolic range, so this is a new maxVector
     1029                        maxVector = newVector;
     1030                        maxDistance = newDistance;
     1031                    }
     1032                   
     1033                }
     1034                r.push_back(maxVector.X);
     1035                r.push_back(maxVector.Y);
     1036               
     1037            }
     1038            r.push_back(r[0]);
     1039            r.push_back(r[1]);
     1040
     1041        }
     1042        return r;
     1043
     1044    }
     1045   
    8451046    Query ConstructQuery(entity_id_t source,
    8461047        entity_pos_t minRange, entity_pos_t maxRange,
    8471048        const std::vector<int>& owners, int requiredInterface, u8 flagsMask)
     
    8561057
    8571058        Query q;
    8581059        q.enabled = false;
     1060        q.parabolic = false;
    8591061        q.source = source;
    8601062        q.minRange = minRange;
    8611063        q.maxRange = maxRange;
     1064        q.elevationBonus = entity_pos_t::Zero();
    8621065
    8631066        q.ownersMask = 0;
    8641067        for (size_t i = 0; i < owners.size(); ++i)
     
    8701073        return q;
    8711074    }
    8721075
     1076    Query ConstructParabolicQuery(entity_id_t source,
     1077        entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus,
     1078        const std::vector<int>& owners, int requiredInterface, u8 flagsMask)
     1079    {
     1080        Query q = ConstructQuery(source,minRange,maxRange,owners,requiredInterface,flagsMask);
     1081        q.parabolic = true;
     1082        q.elevationBonus = elevationBonus;
     1083        return q;
     1084    }
     1085
     1086
    8731087    void RenderSubmit(SceneCollector& collector)
    8741088    {
    8751089        if (!m_DebugOverlayEnabled)
    8761090            return;
    877 
    8781091        CColor enabledRingColour(0, 1, 0, 1);
    8791092        CColor disabledRingColour(1, 0, 0, 1);
    8801093        CColor rayColour(1, 1, 0, 0.2f);
     
    8931106                CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
    8941107
    8951108                // Draw the max range circle
    896                 m_DebugOverlayLines.push_back(SOverlayLine());
    897                 m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColour : disabledRingColour);
    898                 SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.maxRange.ToFloat(), m_DebugOverlayLines.back(), true);
     1109                if (!q.parabolic)
     1110                {
     1111                    m_DebugOverlayLines.push_back(SOverlayLine());
     1112                    m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColour : disabledRingColour);
     1113                    SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.maxRange.ToFloat(), m_DebugOverlayLines.back(), true);
     1114                }
     1115                else
     1116                {
     1117                    // elevation bonus is part of the 3D position. As if the unit is really that much higher
     1118                    CFixedVector3D pos = cmpSourcePosition->GetPosition();
     1119                    pos.Y += q.elevationBonus;
    8991120
     1121                    std::vector<entity_pos_t> coords;
     1122                   
     1123                    // Get the outline from cache if possible
     1124                    if (ParabolicRangesOutlines.find(q.source) != ParabolicRangesOutlines.end())
     1125                    {
     1126                        EntityParabolicRangeOutline e = ParabolicRangesOutlines[q.source];
     1127                        if (e.position == pos && e.range == q.maxRange)
     1128                        {
     1129                            // outline is cached correctly, use it
     1130                            coords = e.outline;
     1131                        }
     1132                        else
     1133                        {
     1134                            // outline was cached, but important parameters changed
     1135                            // (position, elevation, range)
     1136                            // update it
     1137                            coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
     1138                            e.outline = coords;
     1139                            e.range = q.maxRange;
     1140                            e.position = pos;
     1141                            ParabolicRangesOutlines[q.source] = e;
     1142                        }
     1143                    }
     1144                    else
     1145                    {
     1146                        // outline wasn't cached (first time you enable the range overlay
     1147                        // or you created a new entiy)
     1148                        // cache a new outline
     1149                        coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
     1150                        EntityParabolicRangeOutline e;
     1151                        e.source = q.source;
     1152                        e.range = q.maxRange;
     1153                        e.position = pos;
     1154                        e.outline = coords;
     1155                        ParabolicRangesOutlines[q.source] = e;
     1156                    }
     1157                   
     1158                    CColor thiscolor = q.enabled ? enabledRingColour : disabledRingColour;
     1159                   
     1160                    // draw the outline (piece by piece)   
     1161                    for (size_t i = 3; i < coords.size(); i += 2)
     1162                    {
     1163                        std::vector<float> c;
     1164                        c.push_back((coords[i-3]+pos.X).ToFloat());
     1165                        c.push_back((coords[i-2]+pos.Z).ToFloat());
     1166                        c.push_back((coords[i-1]+pos.X).ToFloat());
     1167                        c.push_back((coords[i]+pos.Z).ToFloat());
     1168                        m_DebugOverlayLines.push_back(SOverlayLine());
     1169                        m_DebugOverlayLines.back().m_Color = thiscolor;
     1170                        SimRender::ConstructLineOnGround(GetSimContext(), c, m_DebugOverlayLines.back(), true);
     1171                    }
     1172                }
     1173
    9001174                // Draw the min range circle
    9011175                if (!q.minRange.IsZero())
    9021176                {
    903                     m_DebugOverlayLines.push_back(SOverlayLine());
    904                     m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColour : disabledRingColour);
    9051177                    SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.minRange.ToFloat(), m_DebugOverlayLines.back(), true);
    9061178                }
    9071179
  • source/simulation2/components/ICmpRangeManager.cpp

     
    3636BEGIN_INTERFACE_WRAPPER(RangeManager)
    3737DEFINE_INTERFACE_METHOD_5("ExecuteQuery", std::vector<entity_id_t>, ICmpRangeManager, ExecuteQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int)
    3838DEFINE_INTERFACE_METHOD_6("CreateActiveQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int, u8)
     39DEFINE_INTERFACE_METHOD_7("CreateActiveParabolicQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveParabolicQuery, entity_id_t, entity_pos_t, entity_pos_t, entity_pos_t, std::vector<int>, int, u8)
    3940DEFINE_INTERFACE_METHOD_1("DestroyActiveQuery", void, ICmpRangeManager, DestroyActiveQuery, ICmpRangeManager::tag_t)
    4041DEFINE_INTERFACE_METHOD_1("EnableActiveQuery", void, ICmpRangeManager, EnableActiveQuery, ICmpRangeManager::tag_t)
    4142DEFINE_INTERFACE_METHOD_1("DisableActiveQuery", void, ICmpRangeManager, DisableActiveQuery, ICmpRangeManager::tag_t)
     
    4546DEFINE_INTERFACE_METHOD_1("GetEntitiesByPlayer", std::vector<entity_id_t>, ICmpRangeManager, GetEntitiesByPlayer, player_id_t)
    4647DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpRangeManager, SetDebugOverlay, bool)
    4748DEFINE_INTERFACE_METHOD_2("SetLosRevealAll", void, ICmpRangeManager, SetLosRevealAll, player_id_t, bool)
     49DEFINE_INTERFACE_METHOD_5("GetElevationAdaptedRange", entity_pos_t, ICmpRangeManager, GetElevationAdaptedRange, CFixedVector3D, CFixedVector3D, entity_pos_t, entity_pos_t, entity_pos_t)
    4850DEFINE_INTERFACE_METHOD_3("GetLosVisibility", std::string, ICmpRangeManager, GetLosVisibility_wrapper, entity_id_t, player_id_t, bool)
    4951DEFINE_INTERFACE_METHOD_1("SetLosCircular", void, ICmpRangeManager, SetLosCircular, bool)
    5052DEFINE_INTERFACE_METHOD_0("GetLosCircular", bool, ICmpRangeManager, GetLosCircular)
  • source/simulation2/components/ICmpRangeManager.h

     
    1818#ifndef INCLUDED_ICMPRANGEMANAGER
    1919#define INCLUDED_ICMPRANGEMANAGER
    2020
     21#include "maths/FixedVector3D.h"
     22
    2123#include "simulation2/system/Interface.h"
    2224#include "simulation2/helpers/Position.h"
    2325#include "simulation2/helpers/Player.h"
     
    102104    virtual tag_t CreateActiveQuery(entity_id_t source,
    103105        entity_pos_t minRange, entity_pos_t maxRange, std::vector<int> owners, int requiredInterface, u8 flags) = 0;
    104106
     107    /**
     108     * Construct an active query of a paraboloic form around the unit.
     109     * The query will be disabled by default.
     110     * @param source the entity around which the range will be computed.
     111     * @param minRange non-negative minimum horizontal distance in metres (inclusive). MinRange doesn't do parabolic checks.
     112     * @param maxRange non-negative maximum distance in metres (inclusive) for units on the same elevation;
     113     *      or -1.0 to ignore distance.
     114     *      For units on a different elevation, a physical correct paraboloid with height=maxRange/2 above the unit is used to query them
     115     * @param elevationBonus extra bonus so the source can be placed higher and shoot further
     116     * @param owners list of player IDs that matching entities may have; -1 matches entities with no owner.
     117     * @param requiredInterface if non-zero, an interface ID that matching entities must implement.
     118     * @param flags if a entity in range has one of the flags set it will show up.
     119     * @return unique non-zero identifier of query.
     120     */
     121    virtual tag_t CreateActiveParabolicQuery(entity_id_t source,
     122        entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus, std::vector<int> owners, int requiredInterface, u8 flags) = 0;
     123
     124
    105125    /**
     126     * Get the average elevation over 8 points on distance range around the entity
     127     * @param id the entity id to look around
     128     * @param range the distance to compare terrain height with
     129     * @return a fixed number representing the average difference. It's positive when the entity is on average higher than the terrain surrounding it.
     130     */
     131    virtual entity_pos_t GetElevationAdaptedRange(CFixedVector3D pos, CFixedVector3D rot, entity_pos_t range, entity_pos_t elevationBonus, entity_pos_t angle) = 0;
     132
     133    /**
    106134     * Destroy a query and clean up resources. This must be called when an entity no longer needs its
    107135     * query (e.g. when the entity is destroyed).
    108136     * @param tag identifier of query.
  • source/simulation2/system/InterfaceScripted.h

     
    8585        6, \
    8686        JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT },
    8787
     88#define DEFINE_INTERFACE_METHOD_7(scriptname, rettype, classname, methodname, arg1, arg2, arg3, arg4, arg5, arg6, arg7) \
     89    { scriptname, \
     90        ScriptInterface::callMethod<rettype, arg1, arg2, arg3, arg4, arg5, arg6, arg7, &class_##classname, classname, &classname::methodname>, \
     91        7, \
     92        JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT },
     93
    8894#endif // INCLUDED_INTERFACE_SCRIPTED