Ticket #4121: test_foundation.4.diff

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

     
    2929    // Remember the cost here, so if it changes after construction begins (from auras or technologies)
    3030    // we will use the correct values to refund partial construction costs
    3131    let cmpCost = Engine.QueryInterface(this.entity, IID_Cost);
     32    if (!cmpCost)
     33        error("A foundation must have a cost component to know the build time");
     34
    3235    this.costs = cmpCost.GetResourceCosts(owner);
    3336
    3437    this.maxProgress = 0;
     
    6265    var hitpoints = cmpHealth.GetHitpoints();
    6366    var maxHitpoints = cmpHealth.GetMaxHitpoints();
    6467
    65     return (hitpoints / maxHitpoints);
     68    return hitpoints / maxHitpoints;
    6669};
    6770
    6871Foundation.prototype.GetBuildPercentage = function()
     
    116119 */
    117120Foundation.prototype.AddBuilder = function(builderEnt)
    118121{
    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     }
     122    let index = this.builders.indexOf(builderEnt);
     123    if (index != -1)
     124        return;
     125
     126    this.builders.push(builderEnt);
     127    let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     128    if (cmpVisual)
     129        cmpVisual.SetVariable("numbuilders", this.builders.length);
     130
     131    this.SetBuildMultiplier();
     132    Engine.PostMessage(this.entity, MT_FoundationBuildersChanged, { "to": this.builders });
    126133};
    127134
    128135Foundation.prototype.RemoveBuilder = function(builderEnt)
    129136{
    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     }
     137    let index = this.builders.indexOf(builderEnt);
     138    if (index == -1)
     139        return;
     140
     141    this.builders.splice(index, 1);
     142    let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     143    if (cmpVisual)
     144        cmpVisual.SetVariable("numbuilders", this.builders.length);
     145    this.SetBuildMultiplier();
     146    Engine.PostMessage(this.entity, MT_FoundationBuildersChanged, { "to": this.builders });
    137147 };
    138148
    139149/**
     
    142152 */
    143153Foundation.prototype.SetBuildMultiplier = function()
    144154{
     155    // TODO get rid of magic 0.7
    145156    let numBuilders = this.builders.length;
    146157    if (numBuilders < 2)
    147158        this.buildMultiplier = 1;
     
    199210    // Handle the initial 'committing' of the foundation
    200211    if (!this.committed)
    201212    {
     213        // The obstruction always blocks new foundations/construction,
     214        // but we've temporarily allowed units to walk all over it
     215        // (via CCmpTemplateManager). Now we need to remove that temporary
     216        // blocker-disabling, so that we'll perform standard unit blocking instead.
    202217        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.
    208218            cmpObstruction.SetDisableBlockMovementPathfinding(false, false, -1);
    209219
    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         }
     220        // Call the related trigger event
     221        var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
     222        cmpTrigger.CallEvent("ConstructionStarted", { "foundation": this.entity, "template": this.finalTemplateName });
    214223
    215224        // Switch foundation to scaffold variant
    216225        var cmpFoundationVisual = Engine.QueryInterface(this.entity, IID_Visual);
     
    249258
    250259    // Add an appropriate proportion of hitpoints
    251260    var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
     261    if (!cmpHealth)
     262    {
     263        error("Foundation " + this.entity + " does not have a health component.");
     264        return;
     265    }
    252266    var maxHealth = cmpHealth.GetMaxHitpoints();
    253267    var deltaHP = Math.max(work, Math.min(maxHealth, Math.floor(work * this.GetBuildRate() * this.buildMultiplier)));
    254268    if (deltaHP > 0)
     
    274288            cmpBuildingVisual.SetActorSeed(cmpVisual.GetActorSeed());
    275289
    276290        var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     291        if (!cmpPosition || !cmpPosition.IsInWorld())
     292        {
     293            error("Foundation " + this.entity + " does not have a position in-world.");
     294            Engine.DestroyEntity(building);
     295            return;
     296        }
    277297        var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position);
    278         var pos = cmpPosition.GetPosition();
    279         cmpBuildingPosition.JumpTo(pos.x, pos.z);
     298        if (!cmpBuildingPosition)
     299        {
     300            error("New building " + building + " has no position component.");
     301            Engine.DestroyEntity(building);
     302            return;
     303        }
     304        var pos = cmpPosition.GetPosition2D();
     305        cmpBuildingPosition.JumpTo(pos.x, pos.y);
    280306        var rot = cmpPosition.GetRotation();
    281307        cmpBuildingPosition.SetYRotation(rot.y);
    282308        cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
     
    307333        else
    308334        {
    309335            let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     336            if (!cmpOwnership)
     337            {
     338                error("Foundation " + this.entity + " has no ownership.");
     339                Engine.DestroyEntity(building);
     340                return;
     341            }
    310342            owner = cmpOwnership.GetOwner();
    311343        }
    312344        var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);
     345        if (!cmpBuildingOwnership)
     346        {
     347            error("New Building " + building + " has no ownership.");
     348            Engine.DestroyEntity(building);
     349            return;
     350        }
    313351        cmpBuildingOwnership.SetOwner(owner);
    314352       
    315         // ----------------------------------------------------------------------
     353        /*
     354        Copy over the obstruction control group IDs from the foundation
     355        entities. This is needed to ensure that when a foundation is completed
     356        and replaced by a new entity, it remains in the same control group(s)
     357        as any other foundation entities that may surround it. This is the
     358        mechanism that is used to e.g. enable wall pieces to be built closely
     359        together, ignoring their mutual obstruction shapes (since they would
     360        otherwise be prevented from being built so closely together). If the
     361        control groups are not copied over, the new entity will default to a
     362        new control group containing only itself, and will hence block
     363        construction of any surrounding foundations that it was previously in
     364        the same control group with.
    316365       
    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.
     366        Note that this will result in the completed building entities having
     367        control group IDs that equal entity IDs of old (and soon to be deleted)
     368        foundation entities. This should not have any consequences, however,
     369        since the control group IDs are only meant to be unique identifiers,
     370        which is still true when reusing the old ones.
     371        */
    323372       
    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);
    329373        var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction);
    330         cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
    331         cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
     374        if (cmpObstruction && cmpBuildingObstruction)
     375        {
     376            cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
     377            cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
     378        }
    332379       
    333         // ----------------------------------------------------------------------
    334 
    335380        var cmpPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
    336381        if (cmpPlayerStatisticsTracker)
    337382            cmpPlayerStatisticsTracker.IncreaseConstructedBuildingsCounter(building);
    338383
    339         var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
    340384        var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);
    341         cmpBuildingHealth.SetHitpoints(cmpHealth.GetHitpoints());
     385        if (cmpBuildingHealth)
     386            cmpBuildingHealth.SetHitpoints(cmpHealth.GetHitpoints());
    342387
    343388        PlaySound("constructed", building);
    344389
  • 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    g_Components[ent][iid] = null;
     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": b => { TS_ASSERT_EQUALS(b, newEnt); },
     24});
     25
     26let foundationEnt = 20;
     27let newEnt = 21;
     28let finalTemplate = "structures/athen_civil_centre.xml";
     29let foundationHP = 1;
     30let maxHP = 100;
     31
     32AddMock(foundationEnt, IID_Cost, {
     33    "GetBuildTime": () => 50,
     34    "GetResourceCosts": () => ({ "wood": 100 }),
     35});
     36
     37AddMock(foundationEnt, IID_Health, {
     38    "GetHitpoints": () => foundationHP,
     39    "GetMaxHitpoints": () => maxHP,
     40    "Increase": (a) => { foundationHP = Math.min(foundationHP + a, maxHP); cmpFoundation.OnHealthChanged(); },
     41});
     42
     43AddMock(foundationEnt, IID_Obstruction, {
     44    "GetBlockMovementFlag": () => true,
     45    "GetUnitCollisions": () => [],
     46    "SetDisableBlockMovementPathfinding": () => {},
     47});
     48
     49AddMock(foundationEnt, IID_Ownership, {
     50    "GetOwner": () => player,
     51});
     52
     53AddMock(foundationEnt, IID_Position, {
     54    "GetPosition2D": () => new Vector2D(1, 2),
     55    "GetRotation": () => new Vector3D(1, 2, 3),
     56    "IsInWorld": () => true,
     57});
     58
     59Engine.AddEntity = function(template)
     60{
     61    AddMock(newEnt, IID_Position, {
     62        "JumpTo": (x, y) => { TS_ASSERT_EQUALS(x, 1); TS_ASSERT_EQUALS(y, 2); },
     63        "SetYRotation": r => { TS_ASSERT_EQUALS(r, 2); },
     64        "SetXZRotation": (rx, rz) => { TS_ASSERT_EQUALS(rx, 1); TS_ASSERT_EQUALS(rz, 3); },
     65    });
     66    AddMock(newEnt, IID_Ownership, {
     67        "SetOwner": owner => { TS_ASSERT_EQUALS(owner, player); },
     68    });
     69    return newEnt;
     70};
     71
     72let PlaySound = function(name, source)
     73{
     74    TS_ASSERT_EQUALS(name, "constructed");
     75    TS_ASSERT_EQUALS(source, newEnt);
     76};
     77Engine.RegisterGlobal("PlaySound", PlaySound);
     78Engine.RegisterGlobal("MT_EntityRenamed", "entityRenamed");
     79
     80let cmpFoundation = ConstructComponent(foundationEnt, "Foundation", {});
     81
     82// INITIALISE
     83cmpFoundation.InitialiseConstruction(player, finalTemplate);
     84
     85TS_ASSERT_EQUALS(cmpFoundation.owner, player);
     86TS_ASSERT_EQUALS(cmpFoundation.finalTemplateName, finalTemplate);
     87TS_ASSERT_EQUALS(cmpFoundation.maxProgress, 0);
     88TS_ASSERT_EQUALS(cmpFoundation.initialised, true);
     89
     90// BUILDER COUNT
     91TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 0);
     92cmpFoundation.AddBuilder(10);
     93TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 1);
     94TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, 1);
     95cmpFoundation.AddBuilder(11);
     96TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 2);
     97TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, Math.pow(2, 0.7) / 2);
     98cmpFoundation.AddBuilder(11);
     99TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 2);
     100TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, Math.pow(2, 0.7) / 2);
     101cmpFoundation.RemoveBuilder(11);
     102TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 1);
     103TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, 1);
     104cmpFoundation.RemoveBuilder(11);
     105TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 1);
     106TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, 1);
     107// with cmpVisual
     108AddMock(foundationEnt, IID_Visual, {
     109    "SetVariable": (key, num) => { TS_ASSERT_EQUALS(key, "numbuilders"); TS_ASSERT_EQUALS(num, 2); },
     110});
     111cmpFoundation.AddBuilder(11);
     112DeleteMock(foundationEnt, IID_Visual, null);
     113cmpFoundation.RemoveBuilder(11);
     114
     115// COMMIT FOUNDATION
     116TS_ASSERT_EQUALS(cmpFoundation.committed, false);
     117let work = 5;
     118cmpFoundation.Build(10, work);
     119TS_ASSERT_EQUALS(cmpFoundation.committed, true);
     120TS_ASSERT_EQUALS(foundationHP, 1 + work * cmpFoundation.GetBuildRate() * cmpFoundation.buildMultiplier);
     121TS_ASSERT_EQUALS(cmpFoundation.maxProgress, foundationHP / maxHP);
     122
     123// FINISH CONSTRUCTION
     124cmpFoundation.Build(10, 1000);
     125TS_ASSERT_EQUALS(cmpFoundation.maxProgress, 1);
     126TS_ASSERT_EQUALS(foundationHP, maxHP);
     127
     128
     129