Ticket #4121: test_foundation.10.diff

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

     
    1616    this.buildMultiplier = 1; // Multiplier for the amount of work builders do.
    1717   
    1818    this.previewEntity = INVALID_ENTITY;
     19
     20    // penalty for multiple builders
     21    this.buildTimePenalty = 0.7;
    1922};
    2023
    2124Foundation.prototype.InitialiseConstruction = function(owner, template)
     
    2932    // Remember the cost here, so if it changes after construction begins (from auras or technologies)
    3033    // we will use the correct values to refund partial construction costs
    3134    let cmpCost = Engine.QueryInterface(this.entity, IID_Cost);
     35    if (!cmpCost)
     36        error("A foundation must have a cost component to know the build time");
     37
    3238    this.costs = cmpCost.GetResourceCosts(owner);
    3339
    3440    this.maxProgress = 0;
     
    6268    var hitpoints = cmpHealth.GetHitpoints();
    6369    var maxHitpoints = cmpHealth.GetMaxHitpoints();
    6470
    65     return (hitpoints / maxHitpoints);
     71    return hitpoints / maxHitpoints;
    6672};
    6773
    6874Foundation.prototype.GetBuildPercentage = function()
     
    116122 */
    117123Foundation.prototype.AddBuilder = function(builderEnt)
    118124{
    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     }
     125    if (this.builders.indexOf(builderEnt) != -1)
     126        return;
     127
     128    this.builders.push(builderEnt);
     129    let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     130    if (cmpVisual)
     131        cmpVisual.SetVariable("numbuilders", this.builders.length);
     132
     133    this.SetBuildMultiplier();
     134    Engine.PostMessage(this.entity, MT_FoundationBuildersChanged, { "to": this.builders });
    126135};
    127136
    128137Foundation.prototype.RemoveBuilder = function(builderEnt)
    129138{
    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     }
     139    let index = this.builders.indexOf(builderEnt);
     140    if (index == -1)
     141        return;
     142
     143    this.builders.splice(index, 1);
     144    let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     145    if (cmpVisual)
     146        cmpVisual.SetVariable("numbuilders", this.builders.length);
     147
     148    this.SetBuildMultiplier();
     149    Engine.PostMessage(this.entity, MT_FoundationBuildersChanged, { "to": this.builders });
    137150 };
    138151
    139152/**
     
    146159    if (numBuilders < 2)
    147160        this.buildMultiplier = 1;
    148161    else
    149         this.buildMultiplier = Math.pow(numBuilders, 0.7) / numBuilders;
     162        this.buildMultiplier = Math.pow(numBuilders, this.buildTimePenalty) / numBuilders;
    150163};
    151164
    152165/**
     
    199212    // Handle the initial 'committing' of the foundation
    200213    if (!this.committed)
    201214    {
     215        // The obstruction always blocks new foundations/construction,
     216        // but we've temporarily allowed units to walk all over it
     217        // (via CCmpTemplateManager). Now we need to remove that temporary
     218        // blocker-disabling, so that we'll perform standard unit blocking instead.
    202219        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.
    208220            cmpObstruction.SetDisableBlockMovementPathfinding(false, false, -1);
    209221
    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         }
     222        // Call the related trigger event
     223        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     224        cmpTrigger.CallEvent("ConstructionStarted", {
     225            "foundation": this.entity,
     226            "template": this.finalTemplateName
     227        });
    214228
    215229        // Switch foundation to scaffold variant
    216230        var cmpFoundationVisual = Engine.QueryInterface(this.entity, IID_Visual);
     
    249263
    250264    // Add an appropriate proportion of hitpoints
    251265    var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
     266    if (!cmpHealth)
     267    {
     268        error("Foundation " + this.entity + " does not have a health component.");
     269        return;
     270    }
    252271    var maxHealth = cmpHealth.GetMaxHitpoints();
    253272    var deltaHP = Math.max(work, Math.min(maxHealth, Math.floor(work * this.GetBuildRate() * this.buildMultiplier)));
    254273    if (deltaHP > 0)
     
    274293            cmpBuildingVisual.SetActorSeed(cmpVisual.GetActorSeed());
    275294
    276295        var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     296        if (!cmpPosition || !cmpPosition.IsInWorld())
     297        {
     298            error("Foundation " + this.entity + " does not have a position in-world.");
     299            Engine.DestroyEntity(building);
     300            return;
     301        }
    277302        var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position);
    278         var pos = cmpPosition.GetPosition();
    279         cmpBuildingPosition.JumpTo(pos.x, pos.z);
     303        if (!cmpBuildingPosition)
     304        {
     305            error("New building " + building + " has no position component.");
     306            Engine.DestroyEntity(building);
     307            return;
     308        }
     309        var pos = cmpPosition.GetPosition2D();
     310        cmpBuildingPosition.JumpTo(pos.x, pos.y);
    280311        var rot = cmpPosition.GetRotation();
    281312        cmpBuildingPosition.SetYRotation(rot.y);
    282313        cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
     
    307338        else
    308339        {
    309340            let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     341            if (!cmpOwnership)
     342            {
     343                error("Foundation " + this.entity + " has no ownership.");
     344                Engine.DestroyEntity(building);
     345                return;
     346            }
    310347            owner = cmpOwnership.GetOwner();
    311348        }
    312349        var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);
     350        if (!cmpBuildingOwnership)
     351        {
     352            error("New Building " + building + " has no ownership.");
     353            Engine.DestroyEntity(building);
     354            return;
     355        }
    313356        cmpBuildingOwnership.SetOwner(owner);
    314357       
    315         // ----------------------------------------------------------------------
     358        /*
     359        Copy over the obstruction control group IDs from the foundation
     360        entities. This is needed to ensure that when a foundation is completed
     361        and replaced by a new entity, it remains in the same control group(s)
     362        as any other foundation entities that may surround it. This is the
     363        mechanism that is used to e.g. enable wall pieces to be built closely
     364        together, ignoring their mutual obstruction shapes (since they would
     365        otherwise be prevented from being built so closely together). If the
     366        control groups are not copied over, the new entity will default to a
     367        new control group containing only itself, and will hence block
     368        construction of any surrounding foundations that it was previously in
     369        the same control group with.
    316370       
    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.
     371        Note that this will result in the completed building entities having
     372        control group IDs that equal entity IDs of old (and soon to be deleted)
     373        foundation entities. This should not have any consequences, however,
     374        since the control group IDs are only meant to be unique identifiers,
     375        which is still true when reusing the old ones.
     376        */
    323377       
    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);
    329378        var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction);
    330         cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
    331         cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
     379        if (cmpObstruction && cmpBuildingObstruction)
     380        {
     381            cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
     382            cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
     383        }
    332384       
    333         // ----------------------------------------------------------------------
    334 
    335385        var cmpPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
    336386        if (cmpPlayerStatisticsTracker)
    337387            cmpPlayerStatisticsTracker.IncreaseConstructedBuildingsCounter(building);
    338388
    339         var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
    340389        var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);
    341         cmpBuildingHealth.SetHitpoints(cmpHealth.GetHitpoints());
     390        if (cmpBuildingHealth)
     391            cmpBuildingHealth.SetHitpoints(cmpHealth.GetHitpoints());
    342392
    343393        PlaySound("constructed", building);
    344394
  • 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        foundationHP = Math.min(foundationHP + hp, maxHP);
     45        cmpFoundation.OnHealthChanged();
     46    },
     47});
     48
     49AddMock(foundationEnt, IID_Obstruction, {
     50    "GetBlockMovementFlag": () => true,
     51    "GetUnitCollisions": () => [],
     52    "SetDisableBlockMovementPathfinding": () => {},
     53});
     54
     55AddMock(foundationEnt, IID_Ownership, {
     56    "GetOwner": () => player,
     57});
     58
     59AddMock(foundationEnt, IID_Position, {
     60    "GetPosition2D": () => new Vector2D(1, 2),
     61    "GetRotation": () => new Vector3D(1, 2, 3),
     62    "IsInWorld": () => true,
     63});
     64
     65Engine.AddEntity = function(template)
     66{
     67    AddMock(newEnt, IID_Position, {
     68        "JumpTo": (x, y) => { TS_ASSERT_EQUALS(x, 1); TS_ASSERT_EQUALS(y, 2); },
     69        "SetYRotation": r => { TS_ASSERT_EQUALS(r, 2); },
     70        "SetXZRotation": (rx, rz) => {
     71            TS_ASSERT_EQUALS(rx, 1);
     72            TS_ASSERT_EQUALS(rz, 3);
     73        },
     74    });
     75    AddMock(newEnt, IID_Ownership, {
     76        "SetOwner": owner => { TS_ASSERT_EQUALS(owner, player); },
     77    });
     78    return newEnt;
     79};
     80
     81function PlaySound(name, source)
     82{
     83    TS_ASSERT_EQUALS(name, "constructed");
     84    TS_ASSERT_EQUALS(source, newEnt);
     85};
     86Engine.RegisterGlobal("PlaySound", PlaySound);
     87Engine.RegisterGlobal("MT_EntityRenamed", "entityRenamed");
     88
     89let cmpFoundation = ConstructComponent(foundationEnt, "Foundation", {});
     90
     91// INITIALISE
     92cmpFoundation.InitialiseConstruction(player, finalTemplate);
     93
     94TS_ASSERT_EQUALS(cmpFoundation.owner, player);
     95TS_ASSERT_EQUALS(cmpFoundation.finalTemplateName, finalTemplate);
     96TS_ASSERT_EQUALS(cmpFoundation.maxProgress, 0);
     97TS_ASSERT_EQUALS(cmpFoundation.initialised, true);
     98
     99// BUILDER COUNT
     100TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 0);
     101cmpFoundation.AddBuilder(10);
     102TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 1);
     103TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, 1);
     104cmpFoundation.AddBuilder(11);
     105TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 2);
     106TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, Math.pow(2, cmpFoundation.buildTimePenalty) / 2);
     107cmpFoundation.AddBuilder(11);
     108TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 2);
     109TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, Math.pow(2, cmpFoundation.buildTimePenalty) / 2);
     110cmpFoundation.RemoveBuilder(11);
     111TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 1);
     112TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, 1);
     113cmpFoundation.RemoveBuilder(11);
     114TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 1);
     115TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, 1);
     116// with cmpVisual
     117AddMock(foundationEnt, IID_Visual, {
     118    "SetVariable": (key, num) => {
     119        TS_ASSERT_EQUALS(key, "numbuilders");
     120        TS_ASSERT_EQUALS(num, 2);
     121    },
     122});
     123cmpFoundation.AddBuilder(11);
     124DeleteMock(foundationEnt, IID_Visual);
     125cmpFoundation.RemoveBuilder(11);
     126
     127// COMMIT FOUNDATION
     128TS_ASSERT_EQUALS(cmpFoundation.committed, false);
     129let work = 5;
     130cmpFoundation.Build(10, work);
     131TS_ASSERT_EQUALS(cmpFoundation.committed, true);
     132TS_ASSERT_EQUALS(foundationHP, 1 + work * cmpFoundation.GetBuildRate() * cmpFoundation.buildMultiplier);
     133TS_ASSERT_EQUALS(cmpFoundation.maxProgress, foundationHP / maxHP);
     134
     135// FINISH CONSTRUCTION
     136cmpFoundation.Build(10, 1000);
     137TS_ASSERT_EQUALS(cmpFoundation.maxProgress, 1);
     138TS_ASSERT_EQUALS(foundationHP, maxHP);
     139
     140
     141