Line data Source code
1 : function Researcher() {}
2 :
3 2 : Researcher.prototype.Schema =
4 : "<a:help>Allows the entity to research technologies.</a:help>" +
5 : "<a:example>" +
6 : "<TechCostMultiplier>" +
7 : "<food>0.5</food>" +
8 : "<wood>0.1</wood>" +
9 : "<stone>0</stone>" +
10 : "<metal>2</metal>" +
11 : "<time>0.9</time>" +
12 : "</TechCostMultiplier>" +
13 : "<Technologies datatype='tokens'>" +
14 : "\n phase_town_{civ}\n phase_metropolis_ptol\n unlock_shared_los\n wonder_population_cap\n " +
15 : "</Technologies>" +
16 : "</a:example>" +
17 : "<optional>" +
18 : "<element name='Technologies' a:help='Space-separated list of technology names that this building can research. When present, the special string \"{civ}\" will be automatically replaced either by the civ code of the building's owner if such a tech exists, or by \"generic\".'>" +
19 : "<attribute name='datatype'>" +
20 : "<value>tokens</value>" +
21 : "</attribute>" +
22 : "<text/>" +
23 : "</element>" +
24 : "</optional>" +
25 : "<optional>" +
26 : "<element name='TechCostMultiplier' a:help='Multiplier to modify resources cost and research time of technologies researched in this building.'>" +
27 : Resources.BuildSchema("nonNegativeDecimal", ["time"]) +
28 : "</element>" +
29 : "</optional>";
30 :
31 : /**
32 : * This object represents a technology being researched.
33 : * @param {string} templateName - The name of the template we ought to research.
34 : * @param {number} researcher - The entity ID of our researcher.
35 : * @param {string} metadata - Optionally any metadata to attach to us.
36 : */
37 2 : Researcher.prototype.Item = function(templateName, researcher, metadata)
38 : {
39 7 : this.templateName = templateName;
40 7 : this.researcher = researcher;
41 7 : this.metadata = metadata;
42 : };
43 :
44 : /**
45 : * Prepare for the queue.
46 : * @param {Object} techCostMultiplier - The multipliers to use when calculating costs.
47 : * @return {boolean} - Whether the item was successfully initiated.
48 : */
49 2 : Researcher.prototype.Item.prototype.Queue = function(techCostMultiplier)
50 : {
51 4 : this.player = QueryOwnerInterface(this.researcher).GetPlayerID();
52 4 : const cmpTechnologyManager = QueryPlayerIDInterface(this.player, IID_TechnologyManager);
53 4 : if (!cmpTechnologyManager.QueuedResearch(this.templateName, this.researcher, techCostMultiplier))
54 0 : return false;
55 :
56 4 : return true;
57 : };
58 :
59 2 : Researcher.prototype.Item.prototype.Stop = function()
60 : {
61 2 : QueryPlayerIDInterface(this.player, IID_TechnologyManager).StoppedResearch(this.templateName);
62 2 : delete this.started;
63 : };
64 :
65 : /**
66 : * Called when the first work is performed.
67 : */
68 2 : Researcher.prototype.Item.prototype.Start = function()
69 : {
70 3 : this.started = true;
71 : };
72 :
73 2 : Researcher.prototype.Item.prototype.Finish = function()
74 : {
75 2 : this.finished = true;
76 : };
77 :
78 : /**
79 : * @param {number} allocatedTime - The time allocated to this item.
80 : * @return {number} - The time used for this item.
81 : */
82 2 : Researcher.prototype.Item.prototype.Progress = function(allocatedTime)
83 : {
84 5 : if (!this.started)
85 3 : this.Start();
86 5 : if (this.paused)
87 0 : this.Unpause();
88 5 : const cmpTechnologyManager = QueryPlayerIDInterface(this.player, IID_TechnologyManager);
89 5 : const usedTime = cmpTechnologyManager.Progress(this.templateName, allocatedTime);
90 5 : if (!cmpTechnologyManager.IsTechnologyQueued(this.templateName))
91 2 : this.Finish();
92 5 : return usedTime;
93 : };
94 :
95 2 : Researcher.prototype.Item.prototype.Pause = function()
96 : {
97 0 : QueryPlayerIDInterface(this.player, IID_TechnologyManager).Pause(this.templateName);
98 0 : this.paused = true;
99 : };
100 :
101 2 : Researcher.prototype.Item.prototype.Unpause = function()
102 : {
103 0 : delete this.paused;
104 : };
105 :
106 : /**
107 : * @return {Object} - Some basic information of this item.
108 : */
109 2 : Researcher.prototype.Item.prototype.GetBasicInfo = function()
110 : {
111 0 : const result = QueryPlayerIDInterface(this.player, IID_TechnologyManager).GetBasicInfo(this.templateName);
112 0 : result.technologyTemplate = this.templateName;
113 0 : result.metadata = this.metadata;
114 0 : return result;
115 : };
116 :
117 2 : Researcher.prototype.Item.prototype.SerializableAttributes = [
118 : "metadata",
119 : "paused",
120 : "player",
121 : "researcher",
122 : "started",
123 : "templateName"
124 : ];
125 :
126 2 : Researcher.prototype.Item.prototype.Serialize = function(id)
127 : {
128 3 : const result = {
129 : "id": id
130 : };
131 3 : for (const att of this.SerializableAttributes)
132 18 : if (this.hasOwnProperty(att))
133 15 : result[att] = this[att];
134 3 : return result;
135 : };
136 :
137 2 : Researcher.prototype.Item.prototype.Deserialize = function(data)
138 : {
139 3 : for (const att of this.SerializableAttributes)
140 18 : if (att in data)
141 15 : this[att] = data[att];
142 : };
143 :
144 2 : Researcher.prototype.Init = function()
145 : {
146 9 : this.nextID = 1;
147 9 : this.queue = new Map();
148 : };
149 :
150 2 : Researcher.prototype.Serialize = function()
151 : {
152 3 : const queue = [];
153 3 : for (const [id, item] of this.queue)
154 3 : queue.push(item.Serialize(id));
155 :
156 3 : return {
157 : "nextID": this.nextID,
158 : "queue": queue
159 : };
160 : };
161 :
162 2 : Researcher.prototype.Deserialize = function(data)
163 : {
164 3 : this.Init();
165 3 : this.nextID = data.nextID;
166 3 : for (const item of data.queue)
167 : {
168 3 : const newItem = new this.Item();
169 3 : newItem.Deserialize(item);
170 3 : this.queue.set(item.id, newItem);
171 : }
172 : };
173 :
174 : /*
175 : * Returns list of technologies that can be researched by this entity.
176 : */
177 2 : Researcher.prototype.GetTechnologiesList = function()
178 : {
179 10 : const string = ApplyValueModificationsToEntity("Researcher/Technologies/_string", this.template?.Technologies?._string || "", this.entity);
180 10 : if (!string)
181 0 : return [];
182 :
183 10 : const owner = Engine.QueryInterface(this.entity, IID_Ownership)?.GetOwner();
184 10 : if (!owner || owner === INVALID_PLAYER)
185 0 : return [];
186 :
187 10 : const playerEnt = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetPlayerByID(owner);
188 10 : if (!playerEnt)
189 0 : return [];
190 :
191 10 : const cmpTechnologyManager = Engine.QueryInterface(playerEnt, IID_TechnologyManager);
192 10 : if (!cmpTechnologyManager)
193 0 : return [];
194 :
195 10 : const cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
196 10 : if (!cmpPlayer)
197 0 : return [];
198 :
199 10 : let techs = string.split(/\s+/);
200 :
201 : // Replace the civ specific technologies.
202 10 : const civ = Engine.QueryInterface(playerEnt, IID_Identity).GetCiv();
203 10 : for (let i = 0; i < techs.length; ++i)
204 : {
205 29 : const tech = techs[i];
206 29 : if (tech.indexOf("{civ}") == -1)
207 15 : continue;
208 14 : const civTech = tech.replace("{civ}", civ);
209 14 : techs[i] = TechnologyTemplates.Has(civTech) ? civTech : tech.replace("{civ}", "generic");
210 : }
211 :
212 : // Remove any technologies that can't be researched by this civ.
213 10 : techs = techs.filter(tech =>
214 29 : cmpTechnologyManager.CheckTechnologyRequirements(
215 : DeriveTechnologyRequirements(TechnologyTemplates.Get(tech), civ),
216 : true));
217 :
218 10 : const techList = [];
219 10 : const superseded = {};
220 :
221 10 : const disabledTechnologies = cmpPlayer.GetDisabledTechnologies();
222 :
223 : // Add any top level technologies to an array which corresponds to the displayed icons.
224 : // Also store what technology is superseded in the superseded object { "tech1":"techWhichSupercedesTech1", ... }.
225 10 : for (const tech of techs)
226 : {
227 29 : if (disabledTechnologies && disabledTechnologies[tech])
228 2 : continue;
229 :
230 27 : const template = TechnologyTemplates.Get(tech);
231 27 : if (!template.supersedes || techs.indexOf(template.supersedes) === -1)
232 27 : techList.push(tech);
233 : else
234 0 : superseded[template.supersedes] = tech;
235 : }
236 :
237 : // Now make researched/in progress techs invisible.
238 10 : for (const i in techList)
239 : {
240 27 : let tech = techList[i];
241 27 : while (this.IsTechnologyResearchedOrInProgress(tech))
242 1 : tech = superseded[tech];
243 :
244 27 : techList[i] = tech;
245 : }
246 :
247 10 : const ret = [];
248 :
249 : // This inserts the techs into the correct positions to line up the technology pairs.
250 10 : for (let i = 0; i < techList.length; ++i)
251 : {
252 27 : const tech = techList[i];
253 27 : if (!tech)
254 : {
255 1 : ret[i] = undefined;
256 1 : continue;
257 : }
258 :
259 26 : const template = TechnologyTemplates.Get(tech);
260 26 : if (template.top)
261 0 : ret[i] = { "pair": true, "top": template.top, "bottom": template.bottom };
262 : else
263 26 : ret[i] = tech;
264 : }
265 :
266 10 : return ret;
267 : };
268 :
269 : /**
270 : * @return {Object} - The multipliers to change the costs of any research with.
271 : */
272 2 : Researcher.prototype.GetTechCostMultiplier = function()
273 : {
274 4 : const techCostMultiplier = {};
275 4 : for (const res of Resources.GetCodes().concat(["time"]))
276 14 : techCostMultiplier[res] = ApplyValueModificationsToEntity(
277 : "Researcher/TechCostMultiplier/" + res,
278 : +(this.template?.TechCostMultiplier?.[res] || 1),
279 : this.entity);
280 :
281 4 : return techCostMultiplier;
282 : };
283 :
284 : /**
285 : * Checks whether we can research the given technology, minding paired techs.
286 : */
287 2 : Researcher.prototype.IsTechnologyResearchedOrInProgress = function(tech)
288 : {
289 28 : if (!tech)
290 1 : return false;
291 :
292 27 : const cmpTechnologyManager = QueryOwnerInterface(this.entity, IID_TechnologyManager);
293 27 : if (!cmpTechnologyManager)
294 0 : return false;
295 :
296 27 : const template = TechnologyTemplates.Get(tech);
297 27 : if (template.top)
298 0 : return cmpTechnologyManager.IsTechnologyResearched(template.top) ||
299 : cmpTechnologyManager.IsInProgress(template.top) ||
300 : cmpTechnologyManager.IsTechnologyResearched(template.bottom) ||
301 : cmpTechnologyManager.IsInProgress(template.bottom);
302 :
303 27 : return cmpTechnologyManager.IsTechnologyResearched(tech) || cmpTechnologyManager.IsInProgress(tech);
304 : };
305 :
306 : /**
307 : * @param {string} templateName - The technology to queue.
308 : * @param {string} metadata - Any metadata attached to the item.
309 : * @return {number} - The ID of the item. -1 if the item could not be researched.
310 : */
311 2 : Researcher.prototype.QueueTechnology = function(templateName, metadata)
312 : {
313 4 : if (!this.GetTechnologiesList().some(tech =>
314 4 : tech && (tech == templateName ||
315 : tech.pair && (tech.top == templateName || tech.bottom == templateName))))
316 : {
317 0 : error("This entity cannot research " + templateName + ".");
318 0 : return -1;
319 : }
320 :
321 4 : const item = new this.Item(templateName, this.entity, metadata);
322 :
323 4 : const techCostMultiplier = this.GetTechCostMultiplier();
324 4 : if (!item.Queue(techCostMultiplier))
325 0 : return -1;
326 :
327 4 : const id = this.nextID++;
328 4 : this.queue.set(id, item);
329 4 : return id;
330 : };
331 :
332 : /**
333 : * @param {number} id - The id of the technology researched here we need to stop.
334 : */
335 2 : Researcher.prototype.StopResearching = function(id)
336 : {
337 2 : this.queue.get(id).Stop();
338 2 : this.queue.delete(id);
339 : };
340 :
341 : /**
342 : * @param {number} id - The id of the technology.
343 : */
344 2 : Researcher.prototype.PauseTechnology = function(id)
345 : {
346 0 : this.queue.get(id).Pause();
347 : };
348 :
349 : /**
350 : * @param {number} id - The ID of the item to check.
351 : * @return {boolean} - Whether we are currently training the item.
352 : */
353 2 : Researcher.prototype.HasItem = function(id)
354 : {
355 0 : return this.queue.has(id);
356 : };
357 :
358 : /**
359 : * @parameter {number} id - The id of the research.
360 : * @return {Object} - Some basic information about the research.
361 : */
362 2 : Researcher.prototype.GetResearchingTechnology = function(id)
363 : {
364 0 : return this.queue.get(id).GetBasicInfo();
365 : };
366 :
367 : /**
368 : * @param {number} id - The ID of the item we spent time on.
369 : * @param {number} allocatedTime - The time we spent on the given item.
370 : * @return {number} - The time we've actually used.
371 : */
372 2 : Researcher.prototype.Progress = function(id, allocatedTime)
373 : {
374 5 : const item = this.queue.get(id);
375 5 : const usedTime = item.Progress(allocatedTime);
376 5 : if (item.finished)
377 2 : this.queue.delete(id);
378 5 : return usedTime;
379 : };
380 :
381 2 : Engine.RegisterComponentType(IID_Researcher, "Researcher", Researcher);
|