Ticket #4121: test_foundation.6.diff

File test_foundation.6.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    if (this.builders.indexOf(builderEnt) != -1)
     123        return;
     124
     125    this.builders.push(builderEnt);
     126    let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     127    if (cmpVisual)
     128        cmpVisual.SetVariable("numbuilders", this.builders.length);
     129
     130    this.SetBuildMultiplier();
     131    Engine.PostMessage(this.entity, MT_FoundationBuildersChanged, { "to": this.builders });
    126132};
    127133
    128134Foundation.prototype.RemoveBuilder = function(builderEnt)
    129135{
    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     }
     136    let index = this.builders.indexOf(builderEnt);
     137    if (index == -1)
     138        return;
     139
     140    this.builders.splice(index, 1);
     141    let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     142    if (cmpVisual)
     143        cmpVisual.SetVariable("numbuilders", this.builders.length);
     144
     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", {
     223            "foundation": this.entity,
     224            "template": this.finalTemplateName
     225        });
    214226
    215227        // Switch foundation to scaffold variant
    216228        var cmpFoundationVisual = Engine.QueryInterface(this.entity, IID_Visual);
     
    249261
    250262    // Add an appropriate proportion of hitpoints
    251263    var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
     264    if (!cmpHealth)
     265    {
     266        error("Foundation " + this.entity + " does not have a health component.");
     267        return;
     268    }
    252269    var maxHealth = cmpHealth.GetMaxHitpoints();
    253270    var deltaHP = Math.max(work, Math.min(maxHealth, Math.floor(work * this.GetBuildRate() * this.buildMultiplier)));
    254271    if (deltaHP > 0)
     
    274291            cmpBuildingVisual.SetActorSeed(cmpVisual.GetActorSeed());
    275292
    276293        var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
     294        if (!cmpPosition || !cmpPosition.IsInWorld())
     295        {
     296            error("Foundation " + this.entity + " does not have a position in-world.");
     297            Engine.DestroyEntity(building);
     298            return;
     299        }
    277300        var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position);
    278         var pos = cmpPosition.GetPosition();
    279         cmpBuildingPosition.JumpTo(pos.x, pos.z);
     301        if (!cmpBuildingPosition)
     302        {
     303            error("New building " + building + " has no position component.");
     304            Engine.DestroyEntity(building);
     305            return;
     306        }
     307        var pos = cmpPosition.GetPosition2D();
     308        cmpBuildingPosition.JumpTo(pos.x, pos.y);
    280309        var rot = cmpPosition.GetRotation();
    281310        cmpBuildingPosition.SetYRotation(rot.y);
    282311        cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
     
    307336        else
    308337        {
    309338            let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
     339            if (!cmpOwnership)
     340            {
     341                error("Foundation " + this.entity + " has no ownership.");
     342                Engine.DestroyEntity(building);
     343                return;
     344            }
    310345            owner = cmpOwnership.GetOwner();
    311346        }
    312347        var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);
     348        if (!cmpBuildingOwnership)
     349        {
     350            error("New Building " + building + " has no ownership.");
     351            Engine.DestroyEntity(building);
     352            return;
     353        }
    313354        cmpBuildingOwnership.SetOwner(owner);
    314355       
    315         // ----------------------------------------------------------------------
     356        /*
     357        Copy over the obstruction control group IDs from the foundation
     358        entities. This is needed to ensure that when a foundation is completed
     359        and replaced by a new entity, it remains in the same control group(s)
     360        as any other foundation entities that may surround it. This is the
     361        mechanism that is used to e.g. enable wall pieces to be built closely
     362        together, ignoring their mutual obstruction shapes (since they would
     363        otherwise be prevented from being built so closely together). If the
     364        control groups are not copied over, the new entity will default to a
     365        new control group containing only itself, and will hence block
     366        construction of any surrounding foundations that it was previously in
     367        the same control group with.
    316368       
    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.
     369        Note that this will result in the completed building entities having
     370        control group IDs that equal entity IDs of old (and soon to be deleted)
     371        foundation entities. This should not have any consequences, however,
     372        since the control group IDs are only meant to be unique identifiers,
     373        which is still true when reusing the old ones.
     374        */
    323375       
    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);
    329376        var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction);
    330         cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
    331         cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
     377        if (cmpObstruction && cmpBuildingObstruction)
     378        {
     379            cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
     380            cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
     381        }
    332382       
    333         // ----------------------------------------------------------------------
    334 
    335383        var cmpPlayerStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
    336384        if (cmpPlayerStatisticsTracker)
    337385            cmpPlayerStatisticsTracker.IncreaseConstructedBuildingsCounter(building);
    338386
    339         var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
    340387        var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);
    341         cmpBuildingHealth.SetHitpoints(cmpHealth.GetHitpoints());
     388        if (cmpBuildingHealth)
     389            cmpBuildingHealth.SetHitpoints(cmpHealth.GetHitpoints());
    342390
    343391        PlaySound("constructed", building);
    344392
  • 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, 0.7) / 2);
     109cmpFoundation.AddBuilder(11);
     110TS_ASSERT_EQUALS(cmpFoundation.GetNumBuilders(), 2);
     111TS_ASSERT_EQUALS(cmpFoundation.buildMultiplier, Math.pow(2, 0.7) / 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