Changeset 25206

Timestamp:
Apr 8, 2021, 7:31:34 AM (3 years ago)
Author:
Freagarach
Message:

Gather using ResourceGatherer instead of UnitAI.

Moves the gathering logic from UnitAI to ResourceGatherer.
Makes it easier for modders to change gathering behaviour, e.g. letting structures gather.
Refs. #4293 by optimising a bit.

Differential revision: D2662
Comments by: @bb, @Stan, @wraitii

Location:
ps/trunk/binaries/data/mods/public/simulation/components
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • ps/trunk/binaries/data/mods/public/simulation/components/ResourceGatherer.js

    r25139 r25206  
    3232    "</element>";
    3333
     34
     35
     36
     37
     38
     39
    3440ResourceGatherer.prototype.Init = function()
    3541{
     
    162168{
    163169    return { "max": +this.template.MaxDistance, "min": 0 };
    164     // maybe this should depend on the unit or target or something?
    165 };
    166 
    167 /**
    168  * Gather from the target entity. This should only be called after a successful range check,
    169  * and if the target has a compatible ResourceSupply.
    170  * Call interval will be determined by gather rate, so always gather 1 amount when called.
    171  */
    172 ResourceGatherer.prototype.PerformGather = function(target)
    173 {
    174     if (!this.GetTargetGatherRate(target))
    175         return { "exhausted": true };
    176 
    177     let gatherAmount = 1;
     170};
     171
     172/**
     173 * @param {number} target - The target to gather from.
     174 * @param {number} callerIID - The IID to notify on specific events.
     175 * @return {boolean} - Whether we started gathering.
     176 */
     177ResourceGatherer.prototype.StartGathering = function(target, callerIID)
     178{
     179    if (this.target)
     180        this.StopGathering();
     181
     182    let rate = this.GetTargetGatherRate(target);
     183    if (!rate)
     184        return false;
    178185
    179186    let cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
     187
     188
     189
     190
     191
     192
     193
     194
     195
     196
     197
     198
     199
     200
     201
     202
     203
     204
     205
     206
     207
     208
     209
     210
     211
     212
     213
     214
     215
     216
     217
     218
     219
     220
     221
     222
     223
     224
     225
     226
     227
     228
     229
     230
     231
     232
     233
     234
     235
     236
     237
     238
     239
     240
     241
     242
     243
     244
     245
     246
     247
     248
     249
     250
     251
     252
     253
     254
     255
     256
     257
     258
     259
     260
     261
     262
     263
     264
     265
     266
     267
     268
     269
     270
     271
     272
     273
     274
    180275    let type = cmpResourceSupply.GetType();
    181 
    182     // Initialise the carried count if necessary
    183276    if (!this.carrying[type.generic])
    184277        this.carrying[type.generic] = 0;
    185278
    186     // Find the maximum so we won't exceed our capacity
    187279    let maxGathered = this.GetCapacity(type.generic) - this.carrying[type.generic];
    188 
    189     let status = cmpResourceSupply.TakeResources(Math.min(gatherAmount, maxGathered));
    190 
     280    let status = cmpResourceSupply.TakeResources(Math.min(this.GATHER_AMOUNT, maxGathered));
    191281    this.carrying[type.generic] += status.amount;
    192 
    193282    this.lastCarriedType = type;
    194283
    195284    // Update stats of how much the player collected.
    196285    // (We have to do it here rather than at the dropsite, because we
    197     // need to know what subtype it was)
     286    // need to know what subtype it was)
    198287    let cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
    199288    if (cmpStatisticsTracker)
     
    202291    Engine.PostMessage(this.entity, MT_ResourceCarryingChanged, { "to": this.GetCarryingStatus() });
    203292
    204 
    205     return {
    206         "amount": status.amount,
    207         "exhausted": status.exhausted,
    208         "filled": this.carrying[type.generic] >= this.GetCapacity(type.generic)
    209     };
     293    if (!this.CanCarryMore(type.generic))
     294        this.StopGathering("InventoryFilled");
     295    else if (status.exhausted)
     296        this.StopGathering("TargetInvalidated");
    210297};
    211298
     
    218305{
    219306    let cmpResourceSupply = QueryMiragedInterface(target, IID_ResourceSupply);
    220     if (!cmpResourceSupply)
     307    if (!cmpResourceSupply)
    221308        return 0;
    222309
     
    225312    let rate = 0;
    226313    if (type.specific)
    227         rate = this.GetGatherRate(type.generic+"."+type.specific);
     314        rate = this.GetGatherRate(type.generictype.specific);
    228315    if (rate == 0 && type.generic)
    229316        rate = this.GetGatherRate(type.generic);
     
    234321
    235322    return rate;
     323
     324
     325
     326
     327
     328
     329
     330
     331
    236332};
    237333
     
    377473};
    378474
     475
     476
     477
     478
     479
     480
     481
     482
     483
     484
    379485// Since we cache gather rates, we need to make sure we update them when tech changes.
    380486// and when our owner change because owners can had different techs.
  • ps/trunk/binaries/data/mods/public/simulation/components/TreasureCollecter.js

    r24989 r25206  
    4747        return false;
    4848
     49
     50
     51
     52
    4953    this.target = target;
    5054    this.callerIID = callerIID;
     
    7074    delete this.target;
    7175
    72     // The callerIID component may start gathering again,
    73     // replacing the callerIID, which gets deleted after
    74     // the callerIID has finished. Hence save the data.
     76    let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
     77    if (cmpVisual)
     78        cmpVisual.SelectAnimation("idle", false, 1.0);
     79
     80    // The callerIID component may start collecting again,
     81    // replacing the callerIID, hence save that.
    7582    let callerIID = this.callerIID;
    7683    delete this.callerIID;
  • ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js

    r25192 r25206  
    24962496            "GATHERING": {
    24972497                "enter": function() {
    2498                     this.gatheringTarget = this.order.data.target || INVALID_ENTITY; // deleted in "leave".
    2499 
    2500                     // Check if the resource is full.
    2501                     // Will only be added if we're not already in.
    2502                     let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
    2503                     if (!cmpSupply || !cmpSupply.AddActiveGatherer(this.entity))
    2504                     {
    2505                         this.SetNextState("FINDINGNEWTARGET");
     2498                    let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
     2499                    if (!cmpResourceGatherer)
     2500                    {
     2501                        this.FinishOrder();
     2502                        return true;
     2503                    }
     2504
     2505                    if (!this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer))
     2506                    {
     2507                        this.ProcessMessage("OutOfRange");
    25062508                        return true;
    25072509                    }
     
    25122514                    this.order.data.autoharvest = true;
    25132515
    2514                     // Calculate timing based on gather rates
    2515                     // This allows the gather rate to control how often we gather, instead of how much.
    2516                     let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
    2517                     let rate = cmpResourceGatherer.GetTargetGatherRate(this.gatheringTarget);
    2518 
    2519                     if (!rate)
    2520                     {
    2521                         // Try to find another target if the current one stopped existing
    2522                         if (!Engine.QueryInterface(this.gatheringTarget, IID_Identity))
    2523                         {
    2524                             this.SetNextState("FINDINGNEWTARGET");
    2525                             return true;
    2526                         }
    2527 
    2528                         // No rate, give up on gathering
    2529                         this.FinishOrder();
     2516                    if (!cmpResourceGatherer.StartGathering(this.order.data.target, IID_UnitAI))
     2517                    {
     2518                        this.ProcessMessage("TargetInvalidated");
    25302519                        return true;
    25312520                    }
    25322521
    2533                     // Scale timing interval based on rate, and start timer
    2534                     // The offset should be at least as long as the repeat time so we use the same value for both.
    2535                     let offset = 1000 / rate;
    2536                     this.StartTimer(offset, offset);
    2537 
    2538                     // We want to start the gather animation as soon as possible,
    2539                     // but only if we're actually at the target and it's still alive
    2540                     // (else it'll look like we're chopping empty air).
    2541                     // (If it's not alive, the Timer handler will deal with sending us
    2542                     // off to a different target.)
    2543                     if (this.CheckTargetRange(this.gatheringTarget, IID_ResourceGatherer))
    2544                     {
    2545                         this.SetDefaultAnimationVariant();
    2546                         this.FaceTowardsTarget(this.order.data.target);
    2547                         this.SelectAnimation("gather_" + this.order.data.type.specific);
    2548                         cmpResourceGatherer.AddToPlayerCounter(this.order.data.type.generic);
    2549                     }
     2522                    this.FaceTowardsTarget(this.order.data.target);
    25502523                    return false;
    25512524                },
    25522525
    25532526                "leave": function() {
    2554                     this.StopTimer();
    2555 
    2556                     // Don't use ownership because this is called after a conversion/resignation
    2557                     // and the ownership would be invalid then.
    2558                     let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
    2559                     if (cmpSupply)
    2560                         cmpSupply.RemoveGatherer(this.entity);
    2561 
    25622527                    let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
    25632528                    if (cmpResourceGatherer)
    2564                         cmpResourceGatherer.RemoveFromPlayerCounter();
    2565 
    2566                     delete this.gatheringTarget;
    2567 
    2568                     this.ResetAnimation();
    2569                 },
    2570 
    2571                 "Timer": function(msg) {
    2572                     let resourceTemplate = this.order.data.template;
    2573                     let resourceType = this.order.data.type;
    2574 
    2575                     // TODO: we are leaking information here - if the target died in FOW, we'll know it's dead
    2576                     // straight away.
    2577                     // Seems one would have to listen to ownership changed messages to make it work correctly
    2578                     // but that's likely prohibitively expansive performance wise.
    2579 
    2580                     let cmpSupply = Engine.QueryInterface(this.gatheringTarget, IID_ResourceSupply);
    2581                     // If we can't gather from the target, find a new one.
    2582                     if (!cmpSupply || !cmpSupply.IsAvailableTo(this.entity) ||
    2583                         !this.CanGather(this.gatheringTarget))
    2584                     {
     2529                        cmpResourceGatherer.StopGathering();
     2530                },
     2531
     2532                "InventoryFilled": function(msg) {
     2533                    let nearestDropsite = this.FindNearestDropsite(this.order.data.type.generic);
     2534                    if (!nearestDropsite)
     2535                        this.FinishOrder();
     2536
     2537                    this.PushOrderFront("ReturnResource", { "target": nearestDropsite, "force": false });
     2538                },
     2539
     2540                "OutOfRange": function(msg) {
     2541                    if (this.MoveToTargetRange(this.order.data.target, IID_ResourceGatherer))
     2542                        this.SetNextState("APPROACHING");
     2543                    // Our target is no longer visible - go to its last known position first
     2544                    // and then hopefully it will become visible.
     2545                    else if (!this.CheckTargetVisible(this.order.data.target) && this.order.data.lastPos)
     2546                        this.PushOrderFront("Walk", {
     2547                            "x": this.order.data.lastPos.x,
     2548                            "z": this.order.data.lastPos.z,
     2549                            "force": this.order.data.force
     2550                        });
     2551                    else
    25852552                        this.SetNextState("FINDINGNEWTARGET");
    2586                         return;
    2587                     }
    2588 
    2589                     if (!this.CheckTargetRange(this.gatheringTarget, IID_ResourceGatherer))
    2590                     {
    2591                         // Try to follow the target
    2592                         if (this.MoveToTargetRange(this.gatheringTarget, IID_ResourceGatherer))
    2593                             this.SetNextState("APPROACHING");
    2594                         // Our target is no longer visible - go to its last known position first
    2595                         // and then hopefully it will become visible.
    2596                         else if (!this.CheckTargetVisible(this.gatheringTarget) && this.order.data.lastPos)
    2597                             this.PushOrderFront("Walk", {
    2598                                 "x": this.order.data.lastPos.x,
    2599                                 "z": this.order.data.lastPos.z,
    2600                                 "force": this.order.data.force
    2601                             });
    2602                         else
    2603                             this.SetNextState("FINDINGNEWTARGET");
    2604                         return;
    2605                     }
    2606 
    2607 
    2608                     let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
    2609 
    2610                     // If we've already got some resources but they're the wrong type,
    2611                     // drop them first to ensure we're only ever carrying one type
    2612                     if (cmpResourceGatherer.IsCarryingAnythingExcept(resourceType.generic))
    2613                         cmpResourceGatherer.DropResources();
    2614 
    2615                     this.FaceTowardsTarget(this.order.data.target);
    2616 
    2617                     let status = cmpResourceGatherer.PerformGather(this.gatheringTarget);
    2618 
    2619                     if (status.filled)
    2620                     {
    2621                         let nearestDropsite = this.FindNearestDropsite(resourceType.generic);
    2622                         if (nearestDropsite)
    2623                         {
    2624                             // (Keep this Gather order on the stack so we'll
    2625                             // continue gathering after returning)
    2626                             // However mark our target as invalid if it's exhausted, so we don't waste time
    2627                             // trying to gather from it.
    2628                             if (status.exhausted)
    2629                                 this.order.data.target = INVALID_ENTITY;
    2630                             this.PushOrderFront("ReturnResource", { "target": nearestDropsite, "force": false });
    2631                             return;
    2632                         }
    2633 
    2634                         // Oh no, couldn't find any drop sites. Give up on gathering.
    2635                         this.FinishOrder();
    2636                         return;
    2637                     }
    2638 
    2639                     if (status.exhausted)
    2640                         this.SetNextState("FINDINGNEWTARGET");
     2553                },
     2554
     2555                "TargetInvalidated": function(msg) {
     2556                    this.SetNextState("FINDINGNEWTARGET");
    26412557                },
    26422558            },
     
    29732889                    }
    29742890                    this.FaceTowardsTarget(this.order.data.target);
    2975                     this.SelectAnimation("collecting_treasure");
    29762891                    return false;
    29772892                },
     
    29812896                    if (cmpTreasureCollecter)
    29822897                        cmpTreasureCollecter.StopCollecting();
    2983                     this.ResetAnimation();
    29842898                },
    29852899
  • ps/trunk/binaries/data/mods/public/simulation/components/interfaces/ResourceGatherer.js

    r18581 r25206  
    22
    33/**
    4  * Message of the form { "to": [{ "type": string, "amount": number, "max":number }] }
     4 * Message of the form { "to": [{ "type": string, "amount": number, "max":number }] }
    55 * sent from ResourceGatherer component whenever the amount of carried resources changes.
    66 */
  • ps/trunk/binaries/data/mods/public/simulation/components/tests/test_ResourceGatherer.js

    r24670 r25206  
    22    "BuildSchema": () => {
    33        let schema = "";
    4         for (let res of ["food", "metal"])
     4        for (let res of ["food", "metal"])
    55        {
    66            for (let subtype in ["meat", "grain"])
     
    1414            "subtypes": {
    1515                "meat": "meat",
    16                 "grain": "grain"
     16                "grain": "grain",
     17                "tree": "tree"
    1718            }
    1819        };
     
    2526Engine.LoadComponentScript("interfaces/ResourceSupply.js");
    2627Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
     28
     29
    2730Engine.LoadComponentScript("ResourceGatherer.js");
     31
     32
     33
     34
     35
    2836
    2937Engine.RegisterGlobal("ApplyValueModificationsToEntity", (prop, oVal, ent) => oVal);
    30 
    31 const entity = 11;
    32 const target = 12;
     38Engine.RegisterGlobal("QueryOwnerInterface", () => {});
     39let cmpTimer;
     40
     41const gathererID = 11;
     42const dropsiteID = 12;
     43const supplyID = 13;
    3344
    3445let template = {
     
    3647    "BaseSpeed": "1",
    3748    "Rates": {
    38         "food.grain": "1"
     49        "food.grain": "1",
     50        "wood.tree": "2"
    3951    },
    4052    "Capacities": {
    41         "food": "10"
     53        "food": "10",
     54        "wood": "20"
    4255    }
    4356};
    4457
    45 let cmpResourceGatherer = ConstructComponent(entity, "ResourceGatherer", template);
     58let cmpResourceGatherer = ConstructComponent(, "ResourceGatherer", template);
    4659cmpResourceGatherer.RecalculateGatherRates();
    4760cmpResourceGatherer.RecalculateCapacities();
     
    5770TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(), []);
    5871
    59 // Test gathering.
    60 AddMock(target, IID_ResourceSupply, {
    61     "GetType": () => {
    62         return {
    63             "generic": "food",
    64             "specific": "grain"
    65         };
    66     },
    67     "TakeResources": (amount) => {
    68         return {
    69             "amount": amount,
    70             "exhausted": false
    71         };
    72     },
    73     "GetDiminishingReturns": () => null
    74 });
    75 
    76 TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.PerformGather(target), {
    77     "amount": 1,
    78     "exhausted": false,
    79     "filled": false
    80 });
    81 TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(), [{
    82     "type": "food",
    83     "amount": 1,
    84     "max": 10
    85 }]);
    86 
    8772// Test committing resources.
    88 AddMock(target, IID_ResourceDropsite, {
     73AddMock(, IID_ResourceDropsite, {
    8974    "ReceiveResources": (resources, ent) => {
    9075        return {
     
    9378    }
    9479});
    95 
    96 cmpResourceGatherer.CommitResources(target);
     80cmpResourceGatherer.GiveResources([{ "type": "food", "amount": 1 }]);
     81
     82cmpResourceGatherer.CommitResources(dropsiteID);
    9783TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(), []);
    9884
     
    10490    "amount": 1
    10591}]);
    106 cmpResourceGatherer.CommitResources(target);
     92cmpResourceGatherer.CommitResources();
    10793TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(), [ {
    10894    "type": "wood",
    10995    "amount": 1,
    110     "max": 0
     96    "max": 0
    11197}]);
     98
     99
     100
     101
     102
     103
     104
     105
     106
     107
     108
     109
     110
     111
     112
     113
     114
     115
     116
     117
     118
     119
     120
     121
     122
     123
     124
     125
     126
     127
     128
     129
     130
     131
     132
     133
     134
     135
     136
     137
     138
     139
     140
     141
     142
     143
     144
     145
     146
     147
     148
     149
     150
     151
     152
     153
     154
     155
     156
     157
     158
     159
     160
     161
     162
     163
     164
     165
     166
     167
     168
     169
     170
     171
     172
     173
     174
     175
     176
     177
     178
     179
     180
     181
     182
     183
     184
     185
     186
     187
     188
     189
     190
     191
     192
     193
     194
     195
     196
     197
     198
     199
     200
     201
     202
     203
     204
     205
     206
     207
     208
     209
     210
     211
     212
     213
     214
     215
     216
     217
     218
     219
     220
     221
     222
     223
     224
     225
     226
     227
     228
     229
     230
     231
     232
     233
     234
     235
     236
     237
     238
     239
     240
     241
     242
     243
     244
     245
     246
     247
     248
     249
     250
     251
     252
     253
     254
     255
     256
     257
     258
     259
     260
     261
     262
     263
     264
     265
     266
     267
     268
     269
     270
     271
     272
     273
     274
     275
     276
     277
     278
     279
     280
     281
     282
     283
     284
     285
     286
     287
     288
     289
     290
     291
     292
     293
     294
     295
     296
  • ps/trunk/binaries/data/mods/public/simulation/components/tests/test_Resources.js

    r24992 r25206  
    3939Engine.LoadComponentScript("interfaces/ResourceSupply.js");
    4040Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
     41
     42
    4143Engine.LoadComponentScript("Player.js");
    4244Engine.LoadComponentScript("ResourceDropsite.js");
    4345Engine.LoadComponentScript("ResourceGatherer.js");
    4446Engine.LoadComponentScript("ResourceSupply.js");
     47
    4548
    4649Engine.RegisterGlobal("ApplyValueModificationsToEntity", (prop, oVal, ent) => oVal);
     50
     51
     52
     53
     54
    4755
    4856const owner = 1;
     
    100108// Test gathering.
    101109
    102 TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.PerformGather(supply), {
    103     "amount": 1,
    104     "exhausted": false,
    105     "filled": false
    106 });
     110TS_ASSERT(cmpResourceGatherer.StartGathering(supply));
     111cmpTimer.OnUpdate({ "turnLength": 1 });
    107112TS_ASSERT_UNEVAL_EQUALS(cmpResourceSupply.GetCurrentAmount(), 999);
    108113TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(), [{
     
    111116    "max": 10
    112117}]);
     118
    113119
    114120// Test committing resources.
     
    117123TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.GetCarryingStatus(), []);
    118124
    119 TS_ASSERT_UNEVAL_EQUALS(cmpResourceGatherer.PerformGather(supply), {
    120     "amount": 1,
    121     "exhausted": false,
    122     "filled": false
    123 });
     125TS_ASSERT(cmpResourceGatherer.StartGathering(supply));
     126cmpTimer.OnUpdate({ "turnLength": 1 });
     127cmpResourceGatherer.StopGathering();
    124128cmpResourceGatherer.GiveResources([{
    125129    "type": "wood",
Note: See TracChangeset for help on using the changeset viewer.