Line data Source code
1 : function Upgrade() {}
2 :
3 1 : const UPGRADING_PROGRESS_INTERVAL = 250;
4 :
5 1 : Upgrade.prototype.Schema =
6 : "<oneOrMore>" +
7 : "<element>" +
8 : "<anyName />" +
9 : "<interleave>" +
10 : "<element name='Entity' a:help='Entity to upgrade to'>" +
11 : "<text/>" +
12 : "</element>" +
13 : "<optional>" +
14 : "<element name='Icon' a:help='Icon to show in the GUI'>" +
15 : "<text/>" +
16 : "</element>" +
17 : "</optional>" +
18 : "<optional>" +
19 : "<element name='Variant' a:help='The name of the variant to switch to when upgrading'>" +
20 : "<text/>" +
21 : "</element>" +
22 : "</optional>" +
23 : "<optional>" +
24 : "<element name='Tooltip' a:help='This will be added to the tooltip to help the player choose why to upgrade.'>" +
25 : "<text/>" +
26 : "</element>" +
27 : "</optional>" +
28 : "<optional>" +
29 : "<element name='Time' a:help='Time required to upgrade this entity, in seconds'>" +
30 : "<data type='nonNegativeInteger'/>" +
31 : "</element>" +
32 : "</optional>" +
33 : "<optional>" +
34 : "<element name='Cost' a:help='Resource cost to upgrade this unit'>" +
35 : "<oneOrMore>" +
36 : "<choice>" +
37 : Resources.BuildSchema("nonNegativeInteger") +
38 : "</choice>" +
39 : "</oneOrMore>" +
40 : "</element>" +
41 : "</optional>" +
42 : "<optional>" +
43 : RequirementsHelper.BuildSchema() +
44 : "</optional>" +
45 : "<optional>" +
46 : "<element name='CheckPlacementRestrictions' a:help='Upgrading will check for placement restrictions (nb:GUI only)'><empty/></element>" +
47 : "</optional>" +
48 : "</interleave>" +
49 : "</element>" +
50 : "</oneOrMore>";
51 :
52 1 : Upgrade.prototype.Init = function()
53 : {
54 1 : this.elapsedTime = 0;
55 1 : this.expendedResources = {};
56 : };
57 :
58 : // This will also deal with the "OnDestroy" case.
59 1 : Upgrade.prototype.OnOwnershipChanged = function(msg)
60 : {
61 1 : if (!this.completed)
62 1 : this.CancelUpgrade(msg.from);
63 :
64 1 : if (msg.to != INVALID_PLAYER)
65 : {
66 1 : this.owner = msg.to;
67 1 : this.DetermineUpgrades();
68 : }
69 : };
70 :
71 1 : Upgrade.prototype.DetermineUpgrades = function()
72 : {
73 1 : this.upgradeTemplates = {};
74 :
75 1 : for (const choice in this.template)
76 : {
77 1 : const nativeCiv = Engine.QueryInterface(this.entity, IID_Identity).GetCiv();
78 1 : const playerCiv = QueryPlayerIDInterface(this.owner, IID_Identity).GetCiv();
79 1 : const name = this.template[choice].Entity.
80 : replace(/\{native\}/g, nativeCiv).
81 : replace(/\{civ\}/g, playerCiv);
82 :
83 1 : if (!Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager).TemplateExists(name))
84 0 : continue;
85 :
86 1 : if (this.upgradeTemplates[name])
87 0 : warn("Upgrade Component: entity " + this.entity + " has two upgrades to the same entity, only the last will be used.");
88 :
89 1 : this.upgradeTemplates[name] = choice;
90 : }
91 : };
92 :
93 1 : Upgrade.prototype.ChangeUpgradedEntityCount = function(amount)
94 : {
95 2 : if (!this.IsUpgrading())
96 0 : return;
97 :
98 2 : let cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
99 2 : let template = cmpTempMan.GetTemplate(this.upgrading);
100 :
101 : let categoryTo;
102 2 : if (template.TrainingRestrictions)
103 0 : categoryTo = template.TrainingRestrictions.Category;
104 2 : else if (template.BuildRestrictions)
105 0 : categoryTo = template.BuildRestrictions.Category;
106 :
107 2 : if (!categoryTo)
108 2 : return;
109 :
110 : let categoryFrom;
111 0 : let cmpTrainingRestrictions = Engine.QueryInterface(this.entity, IID_TrainingRestrictions);
112 0 : let cmpBuildRestrictions = Engine.QueryInterface(this.entity, IID_BuildRestrictions);
113 0 : if (cmpTrainingRestrictions)
114 0 : categoryFrom = cmpTrainingRestrictions.GetCategory();
115 0 : else if (cmpBuildRestrictions)
116 0 : categoryFrom = cmpBuildRestrictions.GetCategory();
117 :
118 0 : if (categoryTo == categoryFrom)
119 0 : return;
120 :
121 0 : let cmpEntityLimits = QueryPlayerIDInterface(this.owner, IID_EntityLimits);
122 0 : if (cmpEntityLimits)
123 0 : cmpEntityLimits.ChangeCount(categoryTo, amount);
124 : };
125 :
126 1 : Upgrade.prototype.CanUpgradeTo = function(template)
127 : {
128 0 : return this.upgradeTemplates[template] !== undefined;
129 : };
130 :
131 1 : Upgrade.prototype.GetUpgrades = function()
132 : {
133 3 : let ret = [];
134 :
135 3 : for (const option in this.upgradeTemplates)
136 : {
137 3 : const choice = this.template[this.upgradeTemplates[option]];
138 :
139 3 : let cost = {};
140 3 : if (choice.Cost)
141 3 : cost = this.GetResourceCosts(option);
142 3 : if (choice.Time)
143 3 : cost.time = this.GetUpgradeTime(option);
144 :
145 3 : let hasCost = choice.Cost || choice.Time;
146 3 : ret.push({
147 : "entity": option,
148 : "icon": choice.Icon || undefined,
149 : "cost": hasCost ? cost : undefined,
150 : "tooltip": choice.Tooltip || undefined,
151 : "requirements": this.GetRequirements(option),
152 : });
153 : }
154 :
155 3 : return ret;
156 : };
157 :
158 1 : Upgrade.prototype.CancelTimer = function()
159 : {
160 1 : if (!this.timer)
161 0 : return;
162 :
163 1 : let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
164 1 : cmpTimer.CancelTimer(this.timer);
165 1 : delete this.timer;
166 : };
167 :
168 1 : Upgrade.prototype.IsUpgrading = function()
169 : {
170 9 : return !!this.upgrading;
171 : };
172 :
173 1 : Upgrade.prototype.GetUpgradingTo = function()
174 : {
175 1 : return this.upgrading;
176 : };
177 :
178 1 : Upgrade.prototype.WillCheckPlacementRestrictions = function(template)
179 : {
180 0 : if (!this.upgradeTemplates[template])
181 0 : return undefined;
182 :
183 : // is undefined by default so use X in Y
184 0 : return "CheckPlacementRestrictions" in this.template[this.upgradeTemplates[template]];
185 : };
186 :
187 1 : Upgrade.prototype.GetRequirements = function(templateArg)
188 : {
189 3 : let choice = this.upgradeTemplates[templateArg] || templateArg;
190 :
191 3 : if (this.template[choice].Requirements)
192 0 : return this.template[choice].Requirements;
193 :
194 3 : if (!("Requirements" in this.template[choice]))
195 3 : return undefined;
196 :
197 0 : let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
198 0 : let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
199 :
200 0 : let entType = this.template[choice].Entity;
201 0 : if (cmpIdentity)
202 0 : entType = entType.replace(/\{civ\}/g, cmpIdentity.GetCiv());
203 :
204 0 : let template = cmpTemplateManager.GetTemplate(entType);
205 0 : return template.Identity.Requirements || undefined;
206 : };
207 :
208 1 : Upgrade.prototype.GetResourceCosts = function(template)
209 : {
210 4 : if (!this.upgradeTemplates[template])
211 0 : return undefined;
212 :
213 4 : if (this.IsUpgrading() && template == this.GetUpgradingTo())
214 1 : return clone(this.expendedResources);
215 :
216 3 : let choice = this.upgradeTemplates[template];
217 3 : if (!this.template[choice].Cost)
218 0 : return {};
219 :
220 3 : let costs = {};
221 3 : for (let r in this.template[choice].Cost)
222 6 : costs[r] = ApplyValueModificationsToEntity("Upgrade/Cost/"+r, +this.template[choice].Cost[r], this.entity);
223 :
224 3 : return costs;
225 : };
226 :
227 1 : Upgrade.prototype.Upgrade = function(template)
228 : {
229 1 : if (this.IsUpgrading() || !this.upgradeTemplates[template])
230 0 : return false;
231 :
232 1 : let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
233 1 : if (!cmpPlayer)
234 0 : return false;
235 :
236 1 : let cmpProductionQueue = Engine.QueryInterface(this.entity, IID_ProductionQueue);
237 1 : if (cmpProductionQueue && cmpProductionQueue.HasQueuedProduction())
238 : {
239 0 : let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
240 0 : cmpGUIInterface.PushNotification({
241 : "players": [cmpPlayer.GetPlayerID()],
242 : "message": markForTranslation("Entity is producing. Cannot start upgrading."),
243 : "translateMessage": true
244 : });
245 0 : return false;
246 : }
247 :
248 1 : this.expendedResources = this.GetResourceCosts(template);
249 1 : if (!cmpPlayer || !cmpPlayer.TrySubtractResources(this.expendedResources))
250 : {
251 0 : this.expendedResources = {};
252 0 : return false;
253 : }
254 :
255 1 : this.upgrading = template;
256 1 : this.SetUpgradeAnimationVariant();
257 :
258 : // Prevent cheating
259 1 : this.ChangeUpgradedEntityCount(1);
260 :
261 1 : if (this.GetUpgradeTime(template) !== 0)
262 : {
263 1 : let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
264 1 : this.timer = cmpTimer.SetInterval(this.entity, IID_Upgrade, "UpgradeProgress", 0, UPGRADING_PROGRESS_INTERVAL, { "upgrading": template });
265 : }
266 : else
267 0 : this.UpgradeProgress();
268 :
269 1 : return true;
270 : };
271 :
272 1 : Upgrade.prototype.CancelUpgrade = function(owner)
273 : {
274 2 : if (!this.IsUpgrading())
275 1 : return;
276 :
277 1 : let cmpPlayer = QueryPlayerIDInterface(owner, IID_Player);
278 1 : if (cmpPlayer)
279 1 : cmpPlayer.AddResources(this.expendedResources);
280 :
281 1 : this.expendedResources = {};
282 1 : this.ChangeUpgradedEntityCount(-1);
283 :
284 : // Do not update visual actor if the animation didn't change.
285 1 : let choice = this.upgradeTemplates[this.upgrading];
286 1 : if (choice && this.template[choice].Variant)
287 : {
288 0 : let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
289 0 : if (cmpVisual)
290 0 : cmpVisual.SelectAnimation("idle", false, 1.0);
291 : }
292 :
293 1 : delete this.upgrading;
294 1 : this.CancelTimer();
295 1 : this.SetElapsedTime(0);
296 : };
297 :
298 1 : Upgrade.prototype.GetUpgradeTime = function(templateArg)
299 : {
300 4 : let template = this.upgrading || templateArg;
301 4 : let choice = this.upgradeTemplates[template];
302 :
303 4 : if (!choice)
304 0 : return undefined;
305 :
306 4 : if (!this.template[choice].Time)
307 0 : return 0;
308 :
309 4 : return ApplyValueModificationsToEntity("Upgrade/Time", +this.template[choice].Time, this.entity);
310 : };
311 :
312 1 : Upgrade.prototype.GetElapsedTime = function()
313 : {
314 0 : return this.elapsedTime;
315 : };
316 :
317 1 : Upgrade.prototype.GetProgress = function()
318 : {
319 0 : if (!this.IsUpgrading())
320 0 : return undefined;
321 0 : return this.GetUpgradeTime() == 0 ? 1 : Math.min(this.elapsedTime / 1000.0 / this.GetUpgradeTime(), 1.0);
322 : };
323 :
324 1 : Upgrade.prototype.SetElapsedTime = function(time)
325 : {
326 1 : this.elapsedTime = time;
327 1 : Engine.PostMessage(this.entity, MT_UpgradeProgressUpdate, null);
328 : };
329 :
330 1 : Upgrade.prototype.SetUpgradeAnimationVariant = function()
331 : {
332 1 : let choice = this.upgradeTemplates[this.upgrading];
333 :
334 1 : if (!choice || !this.template[choice].Variant)
335 1 : return;
336 :
337 0 : let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
338 0 : if (!cmpVisual)
339 0 : return;
340 :
341 0 : cmpVisual.SelectAnimation(this.template[choice].Variant, false, 1.0);
342 : };
343 :
344 1 : Upgrade.prototype.UpgradeProgress = function(data, lateness)
345 : {
346 0 : if (this.elapsedTime/1000.0 < this.GetUpgradeTime())
347 : {
348 0 : this.SetElapsedTime(this.GetElapsedTime() + UPGRADING_PROGRESS_INTERVAL + lateness);
349 0 : return;
350 : }
351 :
352 0 : this.CancelTimer();
353 :
354 0 : this.completed = true;
355 0 : this.ChangeUpgradedEntityCount(-1);
356 0 : this.expendedResources = {};
357 :
358 0 : let newEntity = ChangeEntityTemplate(this.entity, this.upgrading);
359 :
360 0 : if (newEntity)
361 0 : PlaySound("upgraded", newEntity);
362 : };
363 :
364 1 : Engine.RegisterComponentType(IID_Upgrade, "Upgrade", Upgrade);
|