Line data Source code
1 : function TechnologyManager() {}
2 :
3 2 : TechnologyManager.prototype.Schema =
4 : "<empty/>";
5 :
6 : /**
7 : * This object represents a technology under research.
8 : * @param {string} templateName - The name of the template to research.
9 : * @param {number} player - The player ID researching.
10 : * @param {number} researcher - The entity ID researching.
11 : */
12 2 : TechnologyManager.prototype.Technology = function(templateName, player, researcher)
13 : {
14 9 : this.player = player;
15 9 : this.researcher = researcher;
16 9 : this.templateName = templateName;
17 : };
18 :
19 : /**
20 : * Prepare for the queue.
21 : * @param {Object} techCostMultiplier - The multipliers to use when calculating costs.
22 : * @return {boolean} - Whether the technology was successfully initiated.
23 : */
24 2 : TechnologyManager.prototype.Technology.prototype.Queue = function(techCostMultiplier)
25 : {
26 5 : const template = TechnologyTemplates.Get(this.templateName);
27 5 : if (!template)
28 0 : return false;
29 :
30 5 : this.resources = {};
31 5 : if (template.cost)
32 5 : for (const res in template.cost)
33 5 : this.resources[res] = Math.floor(techCostMultiplier[res] * template.cost[res]);
34 :
35 : // ToDo: Subtract resources here or in cmpResearcher?
36 5 : const cmpPlayer = Engine.QueryInterface(this.player, IID_Player);
37 : // TrySubtractResources should report error to player (they ran out of resources).
38 5 : if (!cmpPlayer?.TrySubtractResources(this.resources))
39 0 : return false;
40 :
41 5 : const time = techCostMultiplier.time * (template.researchTime || 0) * 1000;
42 5 : this.timeRemaining = time;
43 5 : this.timeTotal = time;
44 :
45 5 : const playerID = cmpPlayer.GetPlayerID();
46 5 : Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger).CallEvent("OnResearchQueued", {
47 : "playerid": playerID,
48 : "technologyTemplate": this.templateName,
49 : "researcherEntity": this.researcher
50 : });
51 :
52 5 : return true;
53 : };
54 :
55 2 : TechnologyManager.prototype.Technology.prototype.Stop = function()
56 : {
57 3 : const cmpPlayer = Engine.QueryInterface(this.player, IID_Player);
58 3 : cmpPlayer?.RefundResources(this.resources);
59 3 : delete this.resources;
60 :
61 3 : if (this.started && this.templateName.startsWith("phase"))
62 0 : Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
63 : "type": "phase",
64 : "players": [cmpPlayer.GetPlayerID()],
65 : "phaseName": this.templateName,
66 : "phaseState": "aborted"
67 : });
68 : };
69 :
70 : /**
71 : * Called when the first work is performed.
72 : */
73 2 : TechnologyManager.prototype.Technology.prototype.Start = function()
74 : {
75 4 : this.started = true;
76 4 : if (!this.templateName.startsWith("phase"))
77 4 : return;
78 :
79 0 : const cmpPlayer = Engine.QueryInterface(this.player, IID_Player);
80 0 : Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
81 : "type": "phase",
82 : "players": [cmpPlayer.GetPlayerID()],
83 : "phaseName": this.templateName,
84 : "phaseState": "started"
85 : });
86 : };
87 :
88 2 : TechnologyManager.prototype.Technology.prototype.Finish = function()
89 : {
90 2 : this.finished = true;
91 :
92 2 : const template = TechnologyTemplates.Get(this.templateName);
93 2 : if (template.soundComplete)
94 0 : Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager)?.PlaySoundGroup(template.soundComplete, this.researcher);
95 :
96 2 : if (template.modifications)
97 : {
98 0 : const cmpModifiersManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ModifiersManager);
99 0 : cmpModifiersManager.AddModifiers("tech/" + this.templateName, DeriveModificationsFromTech(template), this.player);
100 : }
101 :
102 2 : const cmpEntityLimits = Engine.QueryInterface(this.player, IID_EntityLimits);
103 2 : const cmpTechnologyManager = Engine.QueryInterface(this.player, IID_TechnologyManager);
104 2 : if (template.replaces && template.replaces.length > 0)
105 0 : for (const i of template.replaces)
106 : {
107 0 : cmpTechnologyManager.MarkTechnologyAsResearched(i);
108 0 : cmpEntityLimits?.UpdateLimitsFromTech(i);
109 : }
110 :
111 2 : cmpTechnologyManager.MarkTechnologyAsResearched(this.templateName);
112 :
113 : // ToDo: Move to EntityLimits.js.
114 2 : cmpEntityLimits?.UpdateLimitsFromTech(this.templateName);
115 :
116 2 : const playerID = Engine.QueryInterface(this.player, IID_Player).GetPlayerID();
117 2 : Engine.PostMessage(this.player, MT_ResearchFinished, { "player": playerID, "tech": this.templateName });
118 :
119 2 : if (this.templateName.startsWith("phase") && !template.autoResearch)
120 0 : Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
121 : "type": "phase",
122 : "players": [playerID],
123 : "phaseName": this.templateName,
124 : "phaseState": "completed"
125 : });
126 : };
127 :
128 : /**
129 : * @param {number} allocatedTime - The time allocated to this item.
130 : * @return {number} - The time used for this item.
131 : */
132 2 : TechnologyManager.prototype.Technology.prototype.Progress = function(allocatedTime)
133 : {
134 6 : if (!this.started)
135 4 : this.Start();
136 6 : if (this.paused)
137 1 : this.Unpause();
138 6 : if (this.timeRemaining > allocatedTime)
139 : {
140 4 : this.timeRemaining -= allocatedTime;
141 4 : return allocatedTime;
142 : }
143 2 : this.Finish();
144 2 : return this.timeRemaining;
145 : };
146 :
147 2 : TechnologyManager.prototype.Technology.prototype.Pause = function()
148 : {
149 1 : this.paused = true;
150 : };
151 :
152 2 : TechnologyManager.prototype.Technology.prototype.Unpause = function()
153 : {
154 1 : delete this.paused;
155 : };
156 :
157 2 : TechnologyManager.prototype.Technology.prototype.GetBasicInfo = function()
158 : {
159 1 : return {
160 : "paused": this.paused,
161 : "progress": 1 - (this.timeRemaining / (this.timeTotal || 1)),
162 : "researcher": this.researcher,
163 : "templateName": this.templateName,
164 : "timeRemaining": this.timeRemaining
165 : };
166 : };
167 :
168 2 : TechnologyManager.prototype.Technology.prototype.SerializableAttributes = [
169 : "paused",
170 : "player",
171 : "researcher",
172 : "resources",
173 : "started",
174 : "templateName",
175 : "timeRemaining",
176 : "timeTotal"
177 : ];
178 :
179 2 : TechnologyManager.prototype.Technology.prototype.Serialize = function()
180 : {
181 4 : const result = {};
182 4 : for (const att of this.SerializableAttributes)
183 32 : if (this.hasOwnProperty(att))
184 28 : result[att] = this[att];
185 4 : return result;
186 : };
187 :
188 2 : TechnologyManager.prototype.Technology.prototype.Deserialize = function(data)
189 : {
190 4 : for (const att of this.SerializableAttributes)
191 32 : if (att in data)
192 28 : this[att] = data[att];
193 : };
194 :
195 2 : TechnologyManager.prototype.Init = function()
196 : {
197 : // Holds names of technologies that have been researched.
198 6 : this.researchedTechs = new Set();
199 :
200 : // Maps from technolgy name to the technology object.
201 6 : this.researchQueued = new Map();
202 :
203 6 : this.classCounts = {}; // stores the number of entities of each Class
204 6 : this.typeCountsByClass = {}; // stores the number of entities of each type for each class i.e.
205 : // {"someClass": {"unit/spearman": 2, "unit/cav": 5} "someOtherClass":...}
206 :
207 : // Some technologies are automatically researched when their conditions are met. They have no cost and are
208 : // researched instantly. This allows civ bonuses and more complicated technologies.
209 6 : this.unresearchedAutoResearchTechs = new Set();
210 6 : let allTechs = TechnologyTemplates.GetAll();
211 6 : for (let key in allTechs)
212 0 : if (allTechs[key].autoResearch || allTechs[key].top)
213 0 : this.unresearchedAutoResearchTechs.add(key);
214 : };
215 :
216 2 : TechnologyManager.prototype.SerializableAttributes = [
217 : "researchedTechs",
218 : "classCounts",
219 : "typeCountsByClass",
220 : "unresearchedAutoResearchTechs"
221 : ];
222 :
223 2 : TechnologyManager.prototype.Serialize = function()
224 : {
225 4 : const result = {};
226 4 : for (const att of this.SerializableAttributes)
227 16 : if (this.hasOwnProperty(att))
228 16 : result[att] = this[att];
229 :
230 4 : result.researchQueued = [];
231 4 : for (const [techName, techObject] of this.researchQueued)
232 4 : result.researchQueued.push(techObject.Serialize());
233 :
234 4 : return result;
235 : };
236 :
237 2 : TechnologyManager.prototype.Deserialize = function(data)
238 : {
239 4 : for (const att of this.SerializableAttributes)
240 16 : if (att in data)
241 16 : this[att] = data[att];
242 :
243 4 : this.researchQueued = new Map();
244 4 : for (const tech of data.researchQueued)
245 : {
246 4 : const newTech = new this.Technology();
247 4 : newTech.Deserialize(tech);
248 4 : this.researchQueued.set(tech.templateName, newTech);
249 : }
250 : };
251 :
252 2 : TechnologyManager.prototype.OnUpdate = function()
253 : {
254 0 : this.UpdateAutoResearch();
255 : };
256 :
257 : // This function checks if the requirements of any autoresearch techs are met and if they are it researches them
258 2 : TechnologyManager.prototype.UpdateAutoResearch = function()
259 : {
260 2 : for (let key of this.unresearchedAutoResearchTechs)
261 : {
262 0 : let tech = TechnologyTemplates.Get(key);
263 0 : if ((tech.autoResearch && this.CanResearch(key)) ||
264 : (tech.top && (this.IsTechnologyResearched(tech.top) || this.IsTechnologyResearched(tech.bottom))))
265 : {
266 0 : this.unresearchedAutoResearchTechs.delete(key);
267 0 : this.ResearchTechnology(key);
268 0 : return; // We will have recursively handled any knock-on effects so can just return
269 : }
270 : }
271 : };
272 :
273 : // Checks an entity template to see if its technology requirements have been met
274 2 : TechnologyManager.prototype.CanProduce = function(templateName)
275 : {
276 0 : var cmpTempManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
277 0 : var template = cmpTempManager.GetTemplate(templateName);
278 :
279 0 : if (template.Identity?.Requirements)
280 0 : return RequirementsHelper.AreRequirementsMet(template.Identity.Requirements, Engine.QueryInterface(this.entity, IID_Player).GetPlayerID());
281 : // If there is no required technology then this entity can be produced
282 0 : return true;
283 : };
284 :
285 2 : TechnologyManager.prototype.IsTechnologyQueued = function(tech)
286 : {
287 3 : return this.researchQueued.has(tech);
288 : };
289 :
290 2 : TechnologyManager.prototype.IsTechnologyResearched = function(tech)
291 : {
292 8 : return this.researchedTechs.has(tech);
293 : };
294 :
295 : // Checks the requirements for a technology to see if it can be researched at the current time
296 2 : TechnologyManager.prototype.CanResearch = function(tech)
297 : {
298 0 : let template = TechnologyTemplates.Get(tech);
299 :
300 0 : if (!template)
301 : {
302 0 : warn("Technology \"" + tech + "\" does not exist");
303 0 : return false;
304 : }
305 :
306 0 : if (template.top && this.IsInProgress(template.top) ||
307 : template.bottom && this.IsInProgress(template.bottom))
308 0 : return false;
309 :
310 0 : if (template.pair && !this.CanResearch(template.pair))
311 0 : return false;
312 :
313 0 : if (this.IsInProgress(tech))
314 0 : return false;
315 :
316 0 : if (this.IsTechnologyResearched(tech))
317 0 : return false;
318 :
319 0 : return this.CheckTechnologyRequirements(DeriveTechnologyRequirements(template, Engine.QueryInterface(this.entity, IID_Identity).GetCiv()));
320 : };
321 :
322 : /**
323 : * Private function for checking a set of requirements is met
324 : * @param {Object} reqs - Technology requirements as derived from the technology template by globalscripts
325 : * @param {boolean} civonly - True if only the civ requirement is to be checked
326 : *
327 : * @return true if the requirements pass, false otherwise
328 : */
329 2 : TechnologyManager.prototype.CheckTechnologyRequirements = function(reqs, civonly = false)
330 : {
331 9 : let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
332 :
333 9 : if (!reqs)
334 2 : return false;
335 :
336 7 : if (civonly || !reqs.length)
337 5 : return true;
338 :
339 2 : return reqs.some(req => {
340 2 : return Object.keys(req).every(type => {
341 2 : switch (type)
342 : {
343 : case "techs":
344 0 : return req[type].every(this.IsTechnologyResearched, this);
345 :
346 : case "entities":
347 2 : return req[type].every(this.DoesEntitySpecPass, this);
348 : }
349 0 : return false;
350 : });
351 : });
352 : };
353 :
354 2 : TechnologyManager.prototype.DoesEntitySpecPass = function(entity)
355 : {
356 2 : switch (entity.check)
357 : {
358 : case "count":
359 2 : if (!this.classCounts[entity.class] || this.classCounts[entity.class] < entity.number)
360 1 : return false;
361 1 : break;
362 :
363 : case "variants":
364 0 : if (!this.typeCountsByClass[entity.class] || Object.keys(this.typeCountsByClass[entity.class]).length < entity.number)
365 0 : return false;
366 0 : break;
367 : }
368 1 : return true;
369 : };
370 :
371 2 : TechnologyManager.prototype.OnGlobalOwnershipChanged = function(msg)
372 : {
373 : // This automatically updates classCounts and typeCountsByClass
374 0 : var playerID = (Engine.QueryInterface(this.entity, IID_Player)).GetPlayerID();
375 0 : if (msg.to == playerID)
376 : {
377 0 : var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
378 0 : var template = cmpTemplateManager.GetCurrentTemplateName(msg.entity);
379 :
380 0 : var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
381 0 : if (!cmpIdentity)
382 0 : return;
383 :
384 0 : var classes = cmpIdentity.GetClassesList();
385 : // don't use foundations for the class counts but check if techs apply (e.g. health increase)
386 0 : if (!Engine.QueryInterface(msg.entity, IID_Foundation))
387 : {
388 0 : for (let cls of classes)
389 : {
390 0 : this.classCounts[cls] = this.classCounts[cls] || 0;
391 0 : this.classCounts[cls] += 1;
392 :
393 0 : this.typeCountsByClass[cls] = this.typeCountsByClass[cls] || {};
394 0 : this.typeCountsByClass[cls][template] = this.typeCountsByClass[cls][template] || 0;
395 0 : this.typeCountsByClass[cls][template] += 1;
396 : }
397 : }
398 : }
399 0 : if (msg.from == playerID)
400 : {
401 0 : var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
402 0 : var template = cmpTemplateManager.GetCurrentTemplateName(msg.entity);
403 :
404 : // don't use foundations for the class counts
405 0 : if (!Engine.QueryInterface(msg.entity, IID_Foundation))
406 : {
407 0 : var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
408 0 : if (cmpIdentity)
409 : {
410 0 : var classes = cmpIdentity.GetClassesList();
411 0 : for (let cls of classes)
412 : {
413 0 : this.classCounts[cls] -= 1;
414 0 : if (this.classCounts[cls] <= 0)
415 0 : delete this.classCounts[cls];
416 :
417 0 : this.typeCountsByClass[cls][template] -= 1;
418 0 : if (this.typeCountsByClass[cls][template] <= 0)
419 0 : delete this.typeCountsByClass[cls][template];
420 : }
421 : }
422 : }
423 : }
424 : };
425 :
426 : /**
427 : * This does neither apply effects nor verify requirements.
428 : * @param {string} tech - The name of the technology to mark as researched.
429 : */
430 2 : TechnologyManager.prototype.MarkTechnologyAsResearched = function(tech)
431 : {
432 2 : this.researchedTechs.add(tech);
433 2 : this.UpdateAutoResearch();
434 : };
435 :
436 : /**
437 : * Note that this does not verify whether the requirements are met.
438 : * @param {string} tech - The technology to research.
439 : * @param {number} researcher - Optionally the entity to couple with the research.
440 : */
441 2 : TechnologyManager.prototype.ResearchTechnology = function(tech, researcher = INVALID_ENTITY)
442 : {
443 0 : if (this.IsTechnologyQueued(tech) || this.IsTechnologyResearched(tech))
444 0 : return;
445 0 : const technology = new this.Technology(tech, this.entity, researcher);
446 0 : technology.Finish();
447 : };
448 :
449 : /**
450 : * Marks a technology as being queued for research at the given entityID.
451 : * @param {string} tech - The technology to queue.
452 : * @param {number} researcher - The entity ID of the entity researching this technology.
453 : * @param {Object} techCostMultiplier - The multipliers used when calculating the costs.
454 : *
455 : * @return {boolean} - Whether we successfully have queued the technology.
456 : */
457 2 : TechnologyManager.prototype.QueuedResearch = function(tech, researcher, techCostMultiplier)
458 : {
459 : // ToDo: Check whether the technology is researched already?
460 5 : const technology = new this.Technology(tech, this.entity, researcher);
461 5 : if (!technology.Queue(techCostMultiplier))
462 0 : return false;
463 5 : this.researchQueued.set(tech, technology);
464 5 : return true;
465 : };
466 :
467 : /**
468 : * Marks a technology as not being currently researched and optionally sends a GUI notification.
469 : * @param {string} tech - The name of the technology to stop.
470 : * @param {boolean} notification - Whether a GUI notification ought to be sent.
471 : */
472 2 : TechnologyManager.prototype.StoppedResearch = function(tech)
473 : {
474 3 : this.researchQueued.get(tech).Stop();
475 3 : this.researchQueued.delete(tech);
476 : };
477 :
478 : /**
479 : * @param {string} tech -
480 : */
481 2 : TechnologyManager.prototype.Pause = function(tech)
482 : {
483 1 : this.researchQueued.get(tech).Pause();
484 : };
485 :
486 : /**
487 : * @param {string} tech - The technology to advance.
488 : * @param {number} allocatedTime - The time allocated to the technology.
489 : * @return {number} - The time we've actually used.
490 : */
491 2 : TechnologyManager.prototype.Progress = function(techName, allocatedTime)
492 : {
493 6 : const technology = this.researchQueued.get(techName);
494 6 : const usedTime = technology.Progress(allocatedTime);
495 6 : if (technology.finished)
496 2 : this.researchQueued.delete(techName);
497 6 : return usedTime;
498 : };
499 :
500 : /**
501 : * @param {string} tech - The technology name to retreive some basic information for.
502 : * @return {Object} - Some basic information about the technology under research.
503 : */
504 2 : TechnologyManager.prototype.GetBasicInfo = function(tech)
505 : {
506 0 : return this.researchQueued.get(tech).GetBasicInfo();
507 : };
508 :
509 : /**
510 : * Checks whether a technology is set to be researched.
511 : */
512 2 : TechnologyManager.prototype.IsInProgress = function(tech)
513 : {
514 10 : return this.researchQueued.has(tech);
515 : };
516 :
517 2 : TechnologyManager.prototype.GetBasicInfoOfStartedTechs = function()
518 : {
519 1 : const result = {};
520 1 : for (const [techName, tech] of this.researchQueued)
521 1 : if (tech.started)
522 1 : result[techName] = tech.GetBasicInfo();
523 1 : return result;
524 : };
525 :
526 : /**
527 : * Called by GUIInterface for PlayerData. AI use.
528 : */
529 2 : TechnologyManager.prototype.GetQueuedResearch = function()
530 : {
531 0 : return this.researchQueued;
532 : };
533 :
534 : /**
535 : * Returns the names of technologies that have already been researched.
536 : */
537 2 : TechnologyManager.prototype.GetResearchedTechs = function()
538 : {
539 0 : return this.researchedTechs;
540 : };
541 :
542 2 : TechnologyManager.prototype.GetClassCounts = function()
543 : {
544 0 : return this.classCounts;
545 : };
546 :
547 2 : TechnologyManager.prototype.GetTypeCountsByClass = function()
548 : {
549 0 : return this.typeCountsByClass;
550 : };
551 :
552 2 : Engine.RegisterComponentType(IID_TechnologyManager, "TechnologyManager", TechnologyManager);
|