@@ -3,7 +3,7 @@ import { MaterialsCache } from "./helper/MaterialsCache";
3
3
import { CraftSettingsItem , WorkshopSettings } from "./settings/WorkshopSettings" ;
4
4
import { TabManager } from "./TabManager" ;
5
5
import { objectEntries } from "./tools/Entries" ;
6
- import { cdebug , cerror } from "./tools/Log" ;
6
+ import { cerror } from "./tools/Log" ;
7
7
import { isNil , mustExist } from "./tools/Maybe" ;
8
8
import { Resource , ResourceCraftable , UpgradeInfo } from "./types" ;
9
9
import { CraftableInfo , ResourceInfo } from "./types/craft" ;
@@ -90,9 +90,25 @@ export class WorkshopManager extends UpgradeManager implements Automation {
90
90
autoCraft (
91
91
crafts : Partial < Record < ResourceCraftable , CraftSettingsItem > > = this . settings . resources
92
92
) {
93
- const trigger = this . settings . trigger ;
93
+ const craftRequests = new Map <
94
+ CraftSettingsItem ,
95
+ {
96
+ countRequested : number ;
97
+ materials : Array < {
98
+ resource : Resource ;
99
+ consume : number ;
100
+ } > ;
101
+ }
102
+ > ( ) ;
94
103
104
+ // Find all resources we would want to craft.
105
+ // For crafts that require resources with a capacity, those resources must
106
+ // be at or above the trigger for them to be considered to be crafted.
95
107
for ( const craft of Object . values ( crafts ) ) {
108
+ if ( ! craft . enabled ) {
109
+ continue ;
110
+ }
111
+
96
112
const current = ! craft . max ? false : this . getResource ( craft . resource ) ;
97
113
98
114
const max = craft . max === - 1 ? Number . POSITIVE_INFINITY : craft . max ;
@@ -112,23 +128,111 @@ export class WorkshopManager extends UpgradeManager implements Automation {
112
128
. filter ( material => 0 < material . maxValue ) ;
113
129
114
130
const allMaterialsAboveTrigger =
115
- requiredMaterials . filter ( material => material . value / material . maxValue < trigger )
116
- . length === 0 ;
131
+ requiredMaterials . filter (
132
+ material => material . value / material . maxValue < this . settings . trigger
133
+ ) . length === 0 ;
117
134
118
135
if ( ! allMaterialsAboveTrigger ) {
119
136
continue ;
120
137
}
121
138
122
- const amount = this . getLowestCraftAmount (
123
- craft . resource ,
124
- craft . limited ,
125
- 0 < requiredMaterials . length
126
- ) ;
139
+ craftRequests . set ( craft , {
140
+ countRequested : 1 ,
141
+ materials : materials . map ( material => ( {
142
+ resource : material ,
143
+ consume : 0 ,
144
+ } ) ) ,
145
+ } ) ;
146
+ }
147
+
148
+ if ( craftRequests . size < 1 ) {
149
+ return ;
150
+ }
151
+
152
+ // For all crafts under consideration, find the crafts that share resources in their requirements.
153
+ // We will use this to split crafts evenly among the available stock of that resource.
154
+ const billOfMaterials = new Map < Resource , Array < ResourceCraftable > > ( ) ;
155
+ for ( const [ craft , request ] of craftRequests ) {
156
+ for ( const material of request . materials ) {
157
+ if ( ! billOfMaterials . has ( material . resource ) ) {
158
+ billOfMaterials . set ( material . resource , new Array < ResourceCraftable > ( ) ) ;
159
+ }
160
+ const consumers = mustExist ( billOfMaterials . get ( material . resource ) ) ;
161
+ consumers . push ( craft . resource ) ;
162
+ }
163
+ }
164
+
165
+ // Determine how much of each resource we want to spend on each craft.
166
+ for ( const [ , request ] of craftRequests ) {
167
+ for ( const material of request . materials ) {
168
+ const available = this . getValueAvailable ( material . resource ) ;
169
+ material . consume = available / mustExist ( billOfMaterials . get ( material . resource ) ) . length ;
170
+ }
171
+ }
172
+
173
+ // Determine how much of each craft we want to perform, given our resource allocations.
174
+ for ( const [ craft , request ] of craftRequests ) {
175
+ const materials = this . getMaterials ( craft . resource ) ;
176
+ let amount = Number . MAX_VALUE ;
177
+ for ( const material of request . materials ) {
178
+ // How much of the material is needed to craft 1 new resource.
179
+ const materialAmount = mustExist ( materials [ material . resource ] ) ;
180
+
181
+ const materialResource = this . getResource ( material . resource ) ;
182
+ const materialCraft =
183
+ material . resource in this . settings . resources
184
+ ? this . settings . resources [ material . resource as ResourceCraftable ]
185
+ : undefined ;
186
+ if (
187
+ // For unlimited crafts, assign all resources.
188
+ ! craft . limited ||
189
+ // For materials that have a resource cap, also assign all resources.
190
+ // It makes no sense to apply source material balancing here. If we did, we'd stop
191
+ // crafting resources when the source material becomes capped. We would never be able
192
+ // to get enough source stock so the balancing would allow for more crafts.
193
+ 0 < materialResource . maxValue ||
194
+ // For materials that are also crafted, if they have already been crafted to their `max`,
195
+ // treat them the same as capped source materials, to avoid the same conflict.
196
+ ( materialCraft ? materialCraft . max - materialResource . value < 1 : false ) ||
197
+ // Handle the ship override.
198
+ ( craft . resource === "ship" && this . settings . shipOverride . enabled )
199
+ ) {
200
+ amount = Math . min ( amount , material . consume / materialAmount ) ;
201
+ continue ;
202
+ }
203
+
204
+ const ratio = this . _host . gamePage . getResCraftRatio ( craft . resource ) ;
205
+
206
+ // Quantity of source and target resource currently available.
207
+ const availableSource =
208
+ this . getValueAvailable ( material . resource , true ) /
209
+ mustExist ( billOfMaterials . get ( material . resource ) ) . length ;
210
+ const availableTarget = this . getValueAvailable ( craft . resource , true ) ;
211
+
212
+ // How much source resource is consumed and target resource is crafted per craft operation.
213
+ const recipeRequires = materialAmount ;
214
+ const recipeProduces = 1 + ratio ;
215
+
216
+ // How many crafts could we do given the amount of source resource available.
217
+ const craftsPossible = availableSource / recipeRequires ;
218
+
219
+ // How many crafts were hypothetically done to produce the current amount of target resource.
220
+ const craftsDone = availableTarget / recipeProduces ;
221
+
222
+ // Craft only when the craftsPossible >= craftsDone.
223
+ // Crafting gets progressively more expensive as the amount of the target increases.
224
+ // This heuristic gives other, cheaper, targets a chance to get built from the same source resource.
225
+ // There is no checking if there actually exists a different target that could get built.
226
+ amount = Math . min ( amount , craftsPossible - craftsDone , material . consume / materialAmount ) ;
227
+ }
228
+ request . countRequested = Math . max ( 0 , amount ) ;
229
+ }
127
230
128
- // If we can craft any of this item, do it.
129
- if ( 0 < amount ) {
130
- this . craft ( craft . resource , amount ) ;
231
+ for ( const [ craft , request ] of craftRequests ) {
232
+ if ( request . countRequested < 1 ) {
233
+ continue ;
131
234
}
235
+ this . craft ( craft . resource , request . countRequested ) ;
132
236
}
133
237
}
134
238
@@ -141,7 +245,7 @@ export class WorkshopManager extends UpgradeManager implements Automation {
141
245
craft ( name : ResourceCraftable , amount : number ) : void {
142
246
amount = Math . floor ( amount ) ;
143
247
144
- if ( ! name || 1 > amount ) {
248
+ if ( ! name || amount < 1 ) {
145
249
return ;
146
250
}
147
251
if ( ! this . _canCraft ( name , amount ) ) {
@@ -153,15 +257,15 @@ export class WorkshopManager extends UpgradeManager implements Automation {
153
257
154
258
this . _host . gamePage . craft ( craft . name , amount ) ;
155
259
156
- const iname = mustExist ( this . _host . gamePage . resPool . get ( name ) ) . title ;
260
+ const resourceName = mustExist ( this . _host . gamePage . resPool . get ( name ) ) . title ;
157
261
158
- // determine actual amount after crafting upgrades
262
+ // Determine actual amount after crafting upgrades
159
263
amount = parseFloat ( ( amount * ( 1 + ratio ) ) . toFixed ( 2 ) ) ;
160
264
161
- this . _host . engine . storeForSummary ( iname , amount , "craft" ) ;
265
+ this . _host . engine . storeForSummary ( resourceName , amount , "craft" ) ;
162
266
this . _host . engine . iactivity (
163
267
"act.craft" ,
164
- [ this . _host . gamePage . getDisplayValueExt ( amount ) , iname ] ,
268
+ [ this . _host . gamePage . getDisplayValueExt ( amount ) , resourceName ] ,
165
269
"ks-craft"
166
270
) ;
167
271
}
@@ -219,97 +323,14 @@ export class WorkshopManager extends UpgradeManager implements Automation {
219
323
}
220
324
221
325
const materials = this . getMaterials ( name ) ;
222
- for ( const [ mat , amount ] of objectEntries < Resource , number > ( materials ) ) {
223
- if ( this . getValueAvailable ( mat , true ) < amount ) {
326
+ for ( const [ material , amount ] of objectEntries ( materials ) ) {
327
+ if ( this . getValueAvailable ( material , true ) < amount ) {
224
328
return false ;
225
329
}
226
330
}
227
331
return true ;
228
332
}
229
333
230
- /**
231
- * Determine the limit of how many items to craft of a given resource.
232
- *
233
- * @param name The resource to craft.
234
- * @param limited Is the crafting of the resource currently limited?
235
- * @param capacityControlled Is this craft dependant on materials that have a stock capacity?
236
- * @returns The amount of resources to craft.
237
- */
238
- getLowestCraftAmount (
239
- name : ResourceCraftable ,
240
- limited : boolean ,
241
- capacityControlled = false
242
- ) : number {
243
- const materials = this . getMaterials ( name ) ;
244
-
245
- const craft = this . getCraft ( name ) ;
246
- const ratio = this . _host . gamePage . getResCraftRatio ( craft . name ) ;
247
-
248
- // The ship override allows the user to treat ships as "unlimited" while there's less than 243.
249
- const shipOverride = this . settings . shipOverride . enabled ;
250
-
251
- const res = this . getResource ( name ) ;
252
-
253
- // Iterate over the materials required for this craft.
254
- // We want to find the lowest amount of items we could craft, so start with the largest number possible.
255
- let amount = Number . MAX_VALUE ;
256
- for ( const [ resource , materialAmount ] of objectEntries ( materials ) ) {
257
- // The delta is the smallest craft amount based on the current material.
258
- let delta = undefined ;
259
-
260
- // Either if the build isn't limited, or we're handling the ship override.
261
- if (
262
- ! limited ||
263
- capacityControlled ||
264
- ( name === "ship" && shipOverride && this . getResource ( "ship" ) . value < 243 )
265
- ) {
266
- // If there is a storage limit, we can just use everything returned by getValueAvailable,
267
- // since the regulation happens there
268
- delta = this . getValueAvailable ( resource ) / materialAmount ;
269
- } else {
270
- // Quantity of source and target resource currently available.
271
- const srcAvailable = this . getValueAvailable ( resource , true ) ;
272
- const tgtAvailable = this . getValueAvailable ( name , true ) ;
273
-
274
- // How much source resource is consumed and target resource is crafted per craft operation.
275
- const recipeRequires = materialAmount ;
276
- const recipeProduces = 1 + ratio ;
277
-
278
- // How many crafts could we do given the amount of source resource available.
279
- const craftsPossible = srcAvailable / recipeRequires ;
280
-
281
- // How many crafts were hypothetically done to produce the current amount of target resource.
282
- const craftsDone = tgtAvailable / recipeProduces ;
283
-
284
- // Craft only when the craftsPossible >= craftsDone.
285
- // Crafting gets progressively more expensive as the amount of the target increases.
286
- // This heuristic gives other, cheaper, targets a chance to get built from the same source resource.
287
- // There is no checking if there actually exists a different target that could get built.
288
- delta = craftsPossible - craftsDone ;
289
-
290
- // If crafting is not going to happen, explain why not.
291
- const explanationMessages = false ;
292
- if ( explanationMessages && delta < 1.0 ) {
293
- // delta >= 1.0 when craftsPossible >= craftsDone.
294
- const srcNeeded = recipeRequires * craftsDone ;
295
- cdebug ( `[GLCA] not crafting '${ name } ' until '${ resource } ' >= '${ srcNeeded } '` ) ;
296
- }
297
- }
298
-
299
- amount = Math . min ( delta , amount ) ;
300
- }
301
-
302
- // If we have a maximum value, ensure that we don't produce more than
303
- // this value. This should currently only impact wood crafting, but is
304
- // written generically to ensure it works for any craft that produces a
305
- // good with a maximum value.
306
- if ( 0 < res . maxValue && res . maxValue - res . value < amount ) {
307
- amount = res . maxValue - res . value ;
308
- }
309
-
310
- return Math . floor ( amount ) ;
311
- }
312
-
313
334
/**
314
335
* Returns a hash of the required source resources and their
315
336
* amount to craft the given resource.
0 commit comments