Ticket #4121: test_foundation.7.diff

File test_foundation.7.diff, 15.1 KB (added by sanderd17, 8 years ago)
  • binaries/data/mods/public/simulation/components/Foundation.js

     
    11function Foundation() {}
    22
     3
    34Foundation.prototype.Schema =
    45    "<a:component type='internal'/><empty/>";
    56
     
    1617    this.buildMultiplier = 1; // Multiplier for the amount of work builders do.
    1718   
    1819    this.previewEntity = INVALID_ENTITY;
     20
     21    // penalty for multiple builders
     22    this.buildTimePenalty = 0.7;
    1923};
    2024
    2125Foundation.prototype.InitialiseConstruction = function(owner, template)
     
    2933    // Remember the cost here, so if it changes after construction begins (from auras or technologies)
    3034    // we will use the correct values to refund partial construction costs
    3135    let cmpCost = Engine.QueryInterface(this.entity, IID_Cost);
     36    if (!cmpCost)
     37        error("A foundation must have a cost component to know the build time");
     38
    3239    this.costs = cmpCost.GetResourceCosts(owner);
    3340
    3441    this.maxProgress = 0;
     
    6269    var hitpoints = cmpHealth.GetHitpoints();
    6370    var maxHitpoints = cmpHealth.GetMaxHitpoints();
    6471
    65     return (hitpoints / maxHitpoints);
     72    return hitpoints / maxHitpoints;
    6673};
    6774
    6875Foundation.prototype.GetBuildPercentage = function()
     
    116123 */
    117124Foundation.prototype.AddBuilder = function(builderEnt)
    118125{
    119     if (this.builders.indexOf(builderEnt) === -1)
    120     {
    121         this.builders.push(builderEnt);
    122         Engine.QueryInterface(this.entity, IID_Visual).SetVariable("numbuilders", this.builders.length);
    123         this.SetBuildMultiplier();
    124         Engine.PostMessage(this.entity, MT_FoundationBuildersChanged, { "to": this.builders });
    125     }
     126    if (this.builders.indexOf(builderEnt) != -1)
     127        return;
     128
     129    this.builders.push(builderEnt);
     130    let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     131    if (cmpVisual)
     132        cmpVisual.SetVariable("numbuilders", this.builders.length);
     133
     134    this.SetBuildMultiplier();
     135    Engine.PostMessage(this.entity, MT_FoundationBuildersChanged, { "to": this.builders });
    126136};
    127137
    128138Foundation.prototype.RemoveBuilder = function(builderEnt)
    129139{
    130     if (this.builders.indexOf(builderEnt) !== -1)
    131     {
    132         this.builders.splice(this.builders.indexOf(builderEnt),1);
    133         Engine.QueryInterface(this.entity, IID_Visual).SetVariable("numbuilders", this.builders.length);
    134         this.SetBuildMultiplier();
    135         Engine.PostMessage(this.entity, MT_FoundationBuildersChanged, { "to": this.builders });
    136     }
     140    let index = this.builders.indexOf(builderEnt);
     141    if (index == -1)
     142        return;
     143
     144    this.builders.splice(index, 1);
     145    let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     146    if (cmpVisual)
     147        cmpVisual.SetVariable("numbuilders", this.builders.length);
     148
     149    this.SetBuildMultiplier();
     150    Engine.PostMessage(this.entity, MT_FoundationBuildersChanged, { "to": this.builders });
    137151 };
    138152
    139153/**
     
    146160    if (numBuilders < 2)
    147161        this.buildMultiplier = 1;
    148162    else
    149         this.buildMultiplier = Math.pow(numBuilders, 0.7) / numBuilders;
     163        this.buildMultiplier = Math.pow(numBuilders, this.buildTimePenalty) / numBuilders;
    150164};
    151165
    152166/**
     
    199213    // Handle the initial 'committing' of the foundation
    200214    if (!this.committed)
    201215    {
     216        // The obstruction always blocks new foundations/construction,
     217        // but we've temporarily allowed units to walk all over it
     218        // (via CCmpTemplateManager). Now we need to remove that temporary
     219        // blocker-disabling, so that we'll perform standard unit blocking instead.
    202220        if (cmpObstruction && cmpObstruction.GetBlockMovementFlag())
    203         {
    204             // The obstruction always blocks new foundations/construction,
    205             // but we've temporarily allowed units to walk all over it
    206             // (via CCmpTemplateManager). Now we need to remove that temporary
    207             // blocker-disabling, so that we'll perform standard unit blocking instead.
    208221            cmpObstruction.SetDisableBlockMovementPathfinding(false, false, -1);
    209222
    210             // Call the related trigger event
    211             var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
    212             cmpTrigger.CallEvent("ConstructionStarted", {"foundation": this.entity, "template": this.finalTemplateName});
    213         }
     223        // Call the related trigger event
     224        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     225        cmpTrigger.CallEvent("ConstructionStarted", {
     226            "foundation": this.entity,
     227            "template": this.finalTemplateName
     228        });
    214229
    215230        // Switch foundation to scaffold variant
    216231        var cmpFoundationVisual = Engine.QueryInterface(this.entity, IID_Visual);
     
    249264
    250265    // Add an appropriate proportion of hitpoints
    251266    var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
     267    if (!cmpHealth)
     268    {
     269        error("Foundation " + this.entity + " does not have a health component.");
     270        return;
     271    }
    252272    var maxHealth = cmpHealth.GetMaxHitpoints();
    253273    var deltaHP = Math.max(work, Math.min(maxHealth, Math.floor(work * this.GetBuildRate() * this.buildMultiplier)));
    254274    if (deltaHP > 0)
     
    274294            cmpBuildingVisual.SetActorSeed(cmpVisual.GetActorSeed());
    275295
    276296        var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     297        if (!cmpPosition || !cmpPosition.IsInWorld())
     298        {
     299            error("Foundation " + this.entity + " does not have a position in-world.");
     300            Engine.DestroyEntity(building);
     301            return;
     302        }
    277303        var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position);
    278         var pos = cmpPosition.GetPosition();
    279         cmpBuildingPosition.JumpTo(pos.x, pos.z);
     304        if (!cmpBuildingPosition)
     305        {
     306            error("New building " + building + " has no position component.");
     307            Engine.DestroyEntity(building);
     308            return;
     309        }
     310        var pos = cmpPosition.GetPosition2D();
     311        cmpBuildingPosition.JumpTo(pos.x, pos.y);
    280312        var rot = cmpPosition.GetRotation();
    281313        cmpBuildingPosition.SetYRotation(rot.y);
    282314        cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
     
    307339        else
    308340        {
    309341            let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     342            if (!cmpOwnership)
     343            {
     344                error("Foundation " + this.entity + " has no ownership.");
     345                Engine.DestroyEntity(building);
     346                return;
     347            }
    310348            owner = cmpOwnership.GetOwner();
    311349        }
    312350        var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);
     351        if (!cmpBuildingOwnership)
     352        {
     353            error("New Building " + building + " has no ownership.");
     354            Engine.DestroyEntity(building);
     355            return;
     356        }
    313357        cmpBuildingOwnership.SetOwner(owner);
    314358       
    315         // ----------------------------------------------------------------------
     359        /*
     360        Copy over the obstruction control group IDs from the foundation
     361        entities. This is needed to ensure that when a foundation is completed
     362        and replaced by a new entity, it remains in the same control group(s)
     363        as any other foundation entities that may surround it. This is the
     364        mechanism that is used to e.g. enable wall pieces to be built closely
     365        together, ignoring their mutual obstruction shapes (since they would
     366        otherwise be prevented from being built so closely together). If the
     367        control groups are not copied over, the new entity will default to a
     368        new control group containing only itself, and will hence block
     369        construction of any surrounding foundations that it was previously in
     370        the same control group with.
    316371       
    317         // Copy over the obstruction control group IDs from the foundation entities. This is needed to ensure that when a foundation
    318         // is completed and replaced by a new entity, it remains in the same control group(s) as any other foundation entities that
    319         // may surround it. This is the mechanism that is used to e.g. enable wall pieces to be built closely together, ignoring their
    320         // mutual obstruction shapes (since they would otherwise be prevented from being built so closely together). If the control
    321         // groups are not copied over, the new entity will default to a new control group containing only itself, and will hence block
    322         // construction of any surrounding foundations that it was previously in the same control group with.
     372        Note that this will result in the completed building entities having
     373        control group IDs that equal entity IDs of old (and soon to be deleted)
     374        foundation entities. This should not have any consequences, however,
     375        since the control group IDs are only meant to be unique identifiers,
     376        which is still true when reusing the old ones.
     377        */
    323378       
    324         // Note that this will result in the completed building entities having control group IDs that equal entity IDs of old (and soon
    325         // to be deleted) foundation entities. This should not have any consequences, however, since the control group IDs are only meant
    326         // to be unique identifiers, which is still true when reusing the old ones.
    327        
    328         var cmpObstruction = Engine.QueryInterface(this.entity, IID_Obstruction);
    329379        var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction);
    330         cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
    331         cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
     380        if (cmpObstruction && cmpBuildingObstruction)
     381        {
     382            cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
     383            cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
     384        }
    332385       
    333         // ----------------------------------------------------------------------
    334 
    335386        var cmpPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
    336387        if (cmpPlayerStatisticsTracker)
    337388            cmpPlayerStatisticsTracker.IncreaseConstructedBuildingsCounter(building);
    338389
    339         var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
    340390        var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);
    341         cmpBuildingHealth.SetHitpoints(cmpHealth.GetHitpoints());
     391        if (cmpBuildingHealth)
     392            cmpBuildingHealth.SetHitpoints(cmpHealth.GetHitpoints());
    342393
    343394        PlaySound("constructed", building);
    344395
  • binaries/data/mods/public/simulation/components/tests/setup.js

     
    4444    for (var cid in g_Components[ent])
    4545    {
    4646        var cmp = g_Components[ent][cid];
    47         if (cmp.Deinit)
     47        if (cmp && cmp.Deinit)
    4848            cmp.Deinit();
    4949    }
    5050
     
    7676    g_Components[ent][iid] = mock;
    7777};
    7878
     79global.DeleteMock = function(ent, iid)
     80{
     81    if (!g_Components[ent])
     82        g_Components[ent] = {};
     83    delete g_Components[ent][iid];
     84};
     85
    7986global.ConstructComponent = function(ent, name, template)
    8087{
    8188    var cmp = new g_ComponentTypes[name].ctor();
  • binaries/data/mods/public/simulation/components/tests/test_Foundation.js

     
     1Engine.LoadHelperScript("Player.js");
     2Engine.LoadComponentScript("interfaces/Cost.js");
     3Engine.LoadComponentScript("interfaces/Foundation.js");
     4Engine.LoadComponentScript("interfaces/Health.js");
     5Engine.LoadComponentScript("interfaces/RallyPoint.js");
     6Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
     7Engine.LoadComponentScript("interfaces/TerritoryDecay.js");
     8Engine.LoadComponentScript("interfaces/Trigger.js");
     9Engine.LoadComponentScript("Foundation.js");
     10
     11AddMock(SYSTEM_ENTITY, IID_Trigger, {
     12    "CallEvent": () => {},
     13});
     14
     15let player = 1;
     16let playerEnt = 3;
     17
     18AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
     19    "GetPlayerByID": () => playerEnt,
     20});
     21
     22AddMock(playerEnt, IID_StatisticsTracker, {
     23    "IncreaseConstructedBuildingsCounter": ent =>
     24    {
     25        TS_ASSERT_EQUALS(ent, newEnt);
     26    },
     27});
     28
     29let foundationEnt = 20;
     30let newEnt = 21;
     31let finalTemplate = "structures/athen_civil_centre.xml";
     32let foundationHP = 1;
     33let maxHP = 100;
     34
     35AddMock(foundationEnt, IID_Cost, {
     36    "GetBuildTime": () => 50,
     37    "GetResourceCosts": () => ({ "wood": 100 }),
     38});
     39
     40AddMock(foundationEnt, IID_Health, {
     41    "GetHitpoints": () => foundationHP,
     42    "GetMaxHitpoints": () => maxHP,
     43    "Increase": hp =>
     44    {
     45        foundationHP = Math.min(foundationHP + hp, maxHP);
     46        cmpFoundation.OnHealthChanged();
     47    },
     48});
     49
     50AddMock(foundationEnt, IID_Obstruction, {
     51    "GetBlockMovementFlag": () => true,
     52    "GetUnitCollisions": () => [],
     53    "SetDisableBlockMovementPathfinding": () => {},
     54});
     55
     56AddMock(foundationEnt, IID_Ownership, {
     57    "GetOwner": () => player,
     58});
     59
     60AddMock(foundationEnt, IID_Position, {
     61    "GetPosition2D": () => new Vector2D(1, 2),
     62    "GetRotation": () => new Vector3D(1, 2, 3),
     63    "IsInWorld": () => true,
     64});
     65
     66Engine.AddEntity = function(template)
     67{
     68    AddMock(newEnt, IID_Position, {
     69        "JumpTo": (x, y) => { TS_ASSERT_EQUALS(x, 1); TS_ASSERT_EQUALS(y, 2); },
     70        "SetYRotation": r => { TS_ASSERT_EQUALS(r, 2); },
     71        "SetXZRotation": (rx, rz) =>
     72        {
     73            TS_ASSERT_EQUALS(rx, 1);
     74            TS_ASSERT_EQUALS(rz, 3);
     75        },
     76    });
     77    AddMock(newEnt, IID_Ownership, {
     78        "SetOwner": owner => { TS_ASSERT_EQUALS(owner, player); },
     79    });
     80    return newEnt;
     81};
     82
     83let PlaySound = function(name, source)
     84{
     85    TS_ASSERT_EQUALS(name, "constructed");
     86    TS_ASSERT_EQUALS(source, newEnt);
     87};
     88Engine.RegisterGlobal("PlaySound", PlaySound);
     89Engine.RegisterGlobal("MT_EntityRenamed", "entityRenamed");
     90
     91let cmpFoundation = ConstructComponent(foundationEnt, "Foundation", {});
     92
     93// INITIALISE
     94cmpFoundation.InitialiseConstruction(player, finalTemplate);
     95
     96TS_ASSERT_EQUALS(cmpFoundation.owner, player);
     97TS_ASSERT_EQUALS(cmpFoundation.finalTemplateName, finalTemplate);
     98TS_ASSERT_EQUALS(cmpFoundation.maxProgress, 0);
     99TS_ASSERT_EQUALS(cmpFoundation.initialised, true);
     100
     101// BUILDER COUNT
     102TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 0);
     103cmpFoundation.AddBuilder(10);
     104TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 1);
     105TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, 1);
     106cmpFoundation.AddBuilder(11);
     107TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 2);
     108TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, Math.pow(2, cmpFoundation.buildTimePenalty) / 2);
     109cmpFoundation.AddBuilder(11);
     110TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 2);
     111TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, Math.pow(2, cmpFoundation.buildTimePenalty) / 2);
     112cmpFoundation.RemoveBuilder(11);
     113TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 1);
     114TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, 1);
     115cmpFoundation.RemoveBuilder(11);
     116TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 1);
     117TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, 1);
     118// with cmpVisual
     119AddMock(foundationEnt, IID_Visual, {
     120    "SetVariable": (key, num) =>
     121    {
     122        TS_ASSERT_EQUALS(key, "numbuilders");
     123        TS_ASSERT_EQUALS(num, 2);
     124    },
     125});
     126cmpFoundation.AddBuilder(11);
     127DeleteMock(foundationEnt, IID_Visual, null);
     128cmpFoundation.RemoveBuilder(11);
     129
     130// COMMIT FOUNDATION
     131TS_ASSERT_EQUALS(cmpFoundation.committed, false);
     132let work = 5;
     133cmpFoundation.Build(10, work);
     134TS_ASSERT_EQUALS(cmpFoundation.committed, true);
     135TS_ASSERT_EQUALS(foundationHP, 1 + work * cmpFoundation.GetBuildRate() * cmpFoundation.buildMultiplier);
     136TS_ASSERT_EQUALS(cmpFoundation.maxProgress, foundationHP / maxHP);
     137
     138// FINISH CONSTRUCTION
     139cmpFoundation.Build(10, 1000);
     140TS_ASSERT_EQUALS(cmpFoundation.maxProgress, 1);
     141TS_ASSERT_EQUALS(foundationHP, maxHP);
     142
     143
     144