Line data Source code
1 : function Player() {}
2 :
3 6 : Player.prototype.Schema =
4 : "<element name='BarterMultiplier' a:help='Multipliers for barter prices.'>" +
5 : "<interleave>" +
6 : "<element name='Buy' a:help='Multipliers for the buy prices.'>" +
7 : Resources.BuildSchema("positiveDecimal") +
8 : "</element>" +
9 : "<element name='Sell' a:help='Multipliers for the sell prices.'>" +
10 : Resources.BuildSchema("positiveDecimal") +
11 : "</element>" +
12 : "</interleave>" +
13 : "</element>" +
14 : "<element name='Formations' a:help='Space-separated list of formations this player can use.'>" +
15 : "<attribute name='datatype'>" +
16 : "<value>tokens</value>" +
17 : "</attribute>" +
18 : "<text/>" +
19 : "</element>" +
20 : "<element name='SharedLosTech' a:help='Allies will share los when this technology is researched. Leave empty to never share LOS.'>" +
21 : "<text/>" +
22 : "</element>" +
23 : "<element name='SharedDropsitesTech' a:help='Allies will share dropsites when this technology is researched. Leave empty to never share dropsites.'>" +
24 : "<text/>" +
25 : "</element>" +
26 : "<element name='SpyCostMultiplier'>" +
27 : "<ref name='nonNegativeDecimal'/>" +
28 : "</element>";
29 :
30 : // The GUI expects these strings.
31 6 : Player.prototype.STATE_ACTIVE = "active";
32 6 : Player.prototype.STATE_DEFEATED = "defeated";
33 6 : Player.prototype.STATE_WON = "won";
34 :
35 : /**
36 : * Don't serialize diplomacyColor or displayDiplomacyColor since they're modified by the GUI.
37 : */
38 6 : Player.prototype.Serialize = function()
39 : {
40 0 : let state = {};
41 0 : for (let key in this)
42 0 : if (this.hasOwnProperty(key))
43 0 : state[key] = this[key];
44 :
45 0 : state.diplomacyColor = undefined;
46 0 : state.displayDiplomacyColor = false;
47 0 : return state;
48 : };
49 :
50 6 : Player.prototype.Deserialize = function(state)
51 : {
52 0 : for (let prop in state)
53 0 : this[prop] = state[prop];
54 : };
55 :
56 : /**
57 : * Which units will be shown with special icons at the top.
58 : */
59 6 : var panelEntityClasses = "Hero Relic";
60 :
61 6 : Player.prototype.Init = function()
62 : {
63 6 : this.playerID = undefined;
64 6 : this.color = undefined;
65 6 : this.diplomacyColor = undefined;
66 6 : this.displayDiplomacyColor = false;
67 6 : this.popUsed = 0; // Population of units owned or trained by this player.
68 6 : this.popBonuses = 0; // Sum of population bonuses of player's entities.
69 6 : this.maxPop = 300; // Maximum population.
70 6 : this.trainingBlocked = false; // Indicates whether any training queue is currently blocked.
71 6 : this.resourceCount = {};
72 6 : this.resourceGatherers = {};
73 6 : this.tradingGoods = []; // Goods for next trade-route and its probabilities * 100.
74 6 : this.team = -1; // Team number of the player, players on the same team will always have ally diplomatic status. Also this is useful for team emblems, scoring, etc.
75 6 : this.teamsLocked = false;
76 6 : this.state = this.STATE_ACTIVE;
77 6 : this.diplomacy = []; // Array of diplomatic stances for this player with respect to other players (including gaia and self).
78 6 : this.sharedDropsites = false;
79 6 : this.formations = this.template.Formations._string.split(" ");
80 6 : this.startCam = undefined;
81 6 : this.controlAllUnits = false;
82 6 : this.isAI = false;
83 6 : this.cheatsEnabled = false;
84 6 : this.panelEntities = [];
85 6 : this.resourceNames = {};
86 6 : this.disabledTemplates = {};
87 6 : this.disabledTechnologies = {};
88 6 : this.spyCostMultiplier = +this.template.SpyCostMultiplier;
89 6 : this.barterEntities = [];
90 6 : this.barterMultiplier = {
91 : "buy": clone(this.template.BarterMultiplier.Buy),
92 : "sell": clone(this.template.BarterMultiplier.Sell)
93 : };
94 :
95 : // Initial resources.
96 6 : let resCodes = Resources.GetCodes();
97 6 : for (let res of resCodes)
98 : {
99 20 : this.resourceCount[res] = 300;
100 20 : this.resourceNames[res] = Resources.GetResource(res).name;
101 20 : this.resourceGatherers[res] = 0;
102 : }
103 : // Trading goods probability in steps of 5.
104 6 : let resTradeCodes = Resources.GetTradableCodes();
105 6 : let quotient = Math.floor(20 / resTradeCodes.length);
106 6 : let remainder = 20 % resTradeCodes.length;
107 6 : for (let i in resTradeCodes)
108 20 : this.tradingGoods.push({
109 : "goods": resTradeCodes[i],
110 : "proba": 5 * (quotient + (+i < remainder ? 1 : 0))
111 : });
112 : };
113 :
114 6 : Player.prototype.SetPlayerID = function(id)
115 : {
116 1 : this.playerID = id;
117 : };
118 :
119 6 : Player.prototype.GetPlayerID = function()
120 : {
121 7 : return this.playerID;
122 : };
123 :
124 6 : Player.prototype.SetColor = function(r, g, b)
125 : {
126 0 : let colorInitialized = !!this.color;
127 :
128 0 : this.color = { "r": r / 255, "g": g / 255, "b": b / 255, "a": 1 };
129 :
130 : // Used in Atlas.
131 0 : if (colorInitialized)
132 0 : Engine.BroadcastMessage(MT_PlayerColorChanged, {
133 : "player": this.playerID
134 : });
135 : };
136 :
137 6 : Player.prototype.SetDiplomacyColor = function(color)
138 : {
139 0 : this.diplomacyColor = { "r": color.r / 255, "g": color.g / 255, "b": color.b / 255, "a": 1 };
140 : };
141 :
142 6 : Player.prototype.SetDisplayDiplomacyColor = function(displayDiplomacyColor)
143 : {
144 0 : this.displayDiplomacyColor = displayDiplomacyColor;
145 : };
146 :
147 6 : Player.prototype.GetColor = function()
148 : {
149 0 : return this.color;
150 : };
151 :
152 6 : Player.prototype.GetDisplayedColor = function()
153 : {
154 0 : return this.displayDiplomacyColor ? this.diplomacyColor : this.color;
155 : };
156 :
157 : // Try reserving num population slots. Returns 0 on success or number of missing slots otherwise.
158 6 : Player.prototype.TryReservePopulationSlots = function(num)
159 : {
160 0 : if (num != 0 && num > (this.GetPopulationLimit() - this.popUsed))
161 0 : return num - (this.GetPopulationLimit() - this.popUsed);
162 :
163 0 : this.popUsed += num;
164 0 : return 0;
165 : };
166 :
167 6 : Player.prototype.UnReservePopulationSlots = function(num)
168 : {
169 0 : this.popUsed -= num;
170 : };
171 :
172 6 : Player.prototype.GetPopulationCount = function()
173 : {
174 1 : return this.popUsed;
175 : };
176 :
177 6 : Player.prototype.AddPopulation = function(num)
178 : {
179 0 : this.popUsed += num;
180 : };
181 :
182 6 : Player.prototype.SetPopulationBonuses = function(num)
183 : {
184 0 : this.popBonuses = num;
185 : };
186 :
187 6 : Player.prototype.AddPopulationBonuses = function(num)
188 : {
189 0 : this.popBonuses += num;
190 : };
191 :
192 6 : Player.prototype.GetPopulationLimit = function()
193 : {
194 1 : return Math.min(this.GetMaxPopulation(), this.popBonuses);
195 : };
196 :
197 6 : Player.prototype.SetMaxPopulation = function(max)
198 : {
199 0 : this.maxPop = max;
200 : };
201 :
202 6 : Player.prototype.GetMaxPopulation = function()
203 : {
204 1 : return Math.round(ApplyValueModificationsToEntity("Player/MaxPopulation", this.maxPop, this.entity));
205 : };
206 :
207 6 : Player.prototype.CanBarter = function()
208 : {
209 4 : return this.barterEntities.length > 0;
210 : };
211 :
212 6 : Player.prototype.GetBarterMultiplier = function()
213 : {
214 1 : return this.barterMultiplier;
215 : };
216 :
217 6 : Player.prototype.GetSpyCostMultiplier = function()
218 : {
219 1 : return this.spyCostMultiplier;
220 : };
221 :
222 6 : Player.prototype.GetPanelEntities = function()
223 : {
224 0 : return this.panelEntities;
225 : };
226 :
227 6 : Player.prototype.IsTrainingBlocked = function()
228 : {
229 0 : return this.trainingBlocked;
230 : };
231 :
232 6 : Player.prototype.BlockTraining = function()
233 : {
234 0 : this.trainingBlocked = true;
235 : };
236 :
237 6 : Player.prototype.UnBlockTraining = function()
238 : {
239 0 : this.trainingBlocked = false;
240 : };
241 :
242 6 : Player.prototype.SetResourceCounts = function(resources)
243 : {
244 0 : for (let res in resources)
245 0 : this.resourceCount[res] = resources[res];
246 : };
247 :
248 6 : Player.prototype.GetResourceCounts = function()
249 : {
250 40 : return this.resourceCount;
251 : };
252 :
253 6 : Player.prototype.GetResourceGatherers = function()
254 : {
255 0 : return this.resourceGatherers;
256 : };
257 :
258 : /**
259 : * @param {string} type - The generic type of resource to add the gatherer for.
260 : */
261 6 : Player.prototype.AddResourceGatherer = function(type)
262 : {
263 0 : ++this.resourceGatherers[type];
264 : };
265 :
266 : /**
267 : * @param {string} type - The generic type of resource to remove the gatherer from.
268 : */
269 6 : Player.prototype.RemoveResourceGatherer = function(type)
270 : {
271 0 : --this.resourceGatherers[type];
272 : };
273 :
274 : /**
275 : * Add resource of specified type to player.
276 : * @param {string} type - Generic type of resource.
277 : * @param {number} amount - Amount of resource, which should be added.
278 : */
279 6 : Player.prototype.AddResource = function(type, amount)
280 : {
281 0 : this.resourceCount[type] += +amount;
282 : };
283 :
284 : /**
285 : * Add resources to player.
286 : */
287 6 : Player.prototype.AddResources = function(amounts)
288 : {
289 16 : for (let type in amounts)
290 16 : this.resourceCount[type] += +amounts[type];
291 : };
292 :
293 6 : Player.prototype.GetNeededResources = function(amounts)
294 : {
295 : // Check if we can afford it all.
296 304 : let amountsNeeded = {};
297 304 : for (let type in amounts)
298 307 : if (this.resourceCount[type] != undefined && amounts[type] > this.resourceCount[type])
299 2 : amountsNeeded[type] = amounts[type] - Math.floor(this.resourceCount[type]);
300 :
301 304 : if (Object.keys(amountsNeeded).length == 0)
302 302 : return undefined;
303 2 : return amountsNeeded;
304 : };
305 :
306 6 : Player.prototype.SubtractResourcesOrNotify = function(amounts)
307 : {
308 304 : let amountsNeeded = this.GetNeededResources(amounts);
309 :
310 : // If we don't have enough resources, send a notification to the player.
311 304 : if (amountsNeeded)
312 : {
313 2 : let parameters = {};
314 2 : let i = 0;
315 2 : for (let type in amountsNeeded)
316 : {
317 2 : ++i;
318 2 : parameters["resourceType" + i] = this.resourceNames[type];
319 2 : parameters["resourceAmount" + i] = amountsNeeded[type];
320 : }
321 :
322 2 : let msg = "";
323 : // When marking strings for translations, you need to include the actual string,
324 : // not some way to derive the string.
325 2 : if (i < 1)
326 0 : warn("Amounts needed but no amounts given?");
327 2 : else if (i == 1)
328 2 : msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s");
329 0 : else if (i == 2)
330 0 : msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s, %(resourceAmount2)s %(resourceType2)s");
331 0 : else if (i == 3)
332 0 : msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s, %(resourceAmount2)s %(resourceType2)s, %(resourceAmount3)s %(resourceType3)s");
333 0 : else if (i == 4)
334 0 : msg = markForTranslation("Insufficient resources - %(resourceAmount1)s %(resourceType1)s, %(resourceAmount2)s %(resourceType2)s, %(resourceAmount3)s %(resourceType3)s, %(resourceAmount4)s %(resourceType4)s");
335 : else
336 0 : warn("Localisation: Strings are not localised for more than 4 resources");
337 :
338 : // Send as time-notification.
339 2 : let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
340 2 : cmpGUIInterface.PushNotification({
341 : "players": [this.playerID],
342 : "message": msg,
343 : "parameters": parameters,
344 : "translateMessage": true,
345 : "translateParameters": {
346 : "resourceType1": "withinSentence",
347 : "resourceType2": "withinSentence",
348 : "resourceType3": "withinSentence",
349 : "resourceType4": "withinSentence"
350 : }
351 : });
352 2 : return false;
353 : }
354 :
355 302 : for (let type in amounts)
356 305 : this.resourceCount[type] -= amounts[type];
357 :
358 302 : return true;
359 : };
360 :
361 6 : Player.prototype.TrySubtractResources = function(amounts)
362 : {
363 304 : if (!this.SubtractResourcesOrNotify(amounts))
364 2 : return false;
365 :
366 302 : let cmpStatisticsTracker = QueryPlayerIDInterface(this.playerID, IID_StatisticsTracker);
367 302 : if (cmpStatisticsTracker)
368 0 : for (let type in amounts)
369 0 : cmpStatisticsTracker.IncreaseResourceUsedCounter(type, amounts[type]);
370 :
371 302 : return true;
372 : };
373 :
374 6 : Player.prototype.RefundResources = function(amounts)
375 : {
376 1 : const cmpStatisticsTracker = QueryPlayerIDInterface(this.playerID, IID_StatisticsTracker);
377 1 : if (cmpStatisticsTracker)
378 0 : for (const type in amounts)
379 0 : cmpStatisticsTracker.IncreaseResourceUsedCounter(type, -amounts[type]);
380 :
381 1 : this.AddResources(amounts);
382 : };
383 :
384 6 : Player.prototype.GetNextTradingGoods = function()
385 : {
386 0 : let value = randFloat(0, 100);
387 0 : let last = this.tradingGoods.length - 1;
388 0 : let sumProba = 0;
389 0 : for (let i = 0; i < last; ++i)
390 : {
391 0 : sumProba += this.tradingGoods[i].proba;
392 0 : if (value < sumProba)
393 0 : return this.tradingGoods[i].goods;
394 : }
395 0 : return this.tradingGoods[last].goods;
396 : };
397 :
398 6 : Player.prototype.GetTradingGoods = function()
399 : {
400 0 : let tradingGoods = {};
401 0 : for (let resource of this.tradingGoods)
402 0 : tradingGoods[resource.goods] = resource.proba;
403 :
404 0 : return tradingGoods;
405 : };
406 :
407 6 : Player.prototype.SetTradingGoods = function(tradingGoods)
408 : {
409 0 : let resTradeCodes = Resources.GetTradableCodes();
410 0 : let sumProba = 0;
411 0 : for (let resource in tradingGoods)
412 : {
413 0 : if (resTradeCodes.indexOf(resource) == -1 || tradingGoods[resource] < 0)
414 : {
415 0 : error("Invalid trading goods: " + uneval(tradingGoods));
416 0 : return;
417 : }
418 0 : sumProba += tradingGoods[resource];
419 : }
420 :
421 0 : if (sumProba != 100)
422 : {
423 0 : error("Invalid trading goods probability: " + uneval(sumProba));
424 0 : return;
425 : }
426 :
427 0 : this.tradingGoods = [];
428 0 : for (let resource in tradingGoods)
429 0 : this.tradingGoods.push({
430 : "goods": resource,
431 : "proba": tradingGoods[resource]
432 : });
433 : };
434 :
435 : /**
436 : * @param {string} message - The message to send in the chat. May be undefined.
437 : */
438 6 : Player.prototype.Win = function(message)
439 : {
440 0 : this.SetState(this.STATE_WON, message);
441 : };
442 :
443 : /**
444 : * @param {string} message - The message to send in the chat. May be undefined.
445 : */
446 6 : Player.prototype.Defeat = function(message)
447 : {
448 0 : this.SetState(this.STATE_DEFEATED, message);
449 : };
450 :
451 : /**
452 : * @return {string} - The string identified with the current state.
453 : */
454 6 : Player.prototype.GetState = function()
455 : {
456 0 : return this.state;
457 : };
458 :
459 : /**
460 : * @return {boolean} -
461 : */
462 6 : Player.prototype.IsActive = function()
463 : {
464 0 : return this.state === this.STATE_ACTIVE;
465 : };
466 :
467 : /**
468 : * @return {boolean} -
469 : */
470 6 : Player.prototype.IsDefeated = function()
471 : {
472 0 : return this.state === this.STATE_DEFEATED;
473 : };
474 :
475 : /**
476 : * @return {boolean} -
477 : */
478 6 : Player.prototype.HasWon = function()
479 : {
480 0 : return this.state === this.STATE_WON;
481 : };
482 :
483 : /**
484 : * @param {string} newState - Either "defeated" or "won".
485 : * @param {string|undefined} message - A string to be shown in chat, for example
486 : * markForTranslation("%(player)s has been defeated (failed objective).").
487 : * If it is undefined, the caller MUST send that GUI notification manually.
488 : */
489 6 : Player.prototype.SetState = function(newState, message)
490 : {
491 0 : if (!this.IsActive())
492 0 : return;
493 :
494 0 : if (newState != this.STATE_WON && newState != this.STATE_DEFEATED)
495 : {
496 0 : warn("Can't change playerstate to " + newState);
497 0 : return;
498 : }
499 :
500 0 : if (!this.playerID)
501 : {
502 0 : warn("Gaia can't change state.");
503 0 : return;
504 : }
505 :
506 0 : this.state = newState;
507 :
508 0 : const won = this.HasWon();
509 0 : let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
510 0 : if (won)
511 0 : cmpRangeManager.SetLosRevealAll(this.playerID, true);
512 : else
513 : {
514 : // Reassign all player's entities to Gaia.
515 0 : let entities = cmpRangeManager.GetEntitiesByPlayer(this.playerID);
516 :
517 : // The ownership change is done in two steps so that entities don't hit idle
518 : // (and thus possibly look for "enemies" to attack) before nearby allies get
519 : // converted to Gaia as well.
520 0 : for (let entity of entities)
521 : {
522 0 : let cmpOwnership = Engine.QueryInterface(entity, IID_Ownership);
523 0 : cmpOwnership.SetOwnerQuiet(0);
524 : }
525 :
526 : // With the real ownership change complete, send OwnershipChanged messages.
527 0 : for (let entity of entities)
528 0 : Engine.PostMessage(entity, MT_OwnershipChanged, {
529 : "entity": entity,
530 : "from": this.playerID,
531 : "to": 0
532 : });
533 : }
534 :
535 0 : if (message)
536 0 : Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
537 : "type": won ? "won" : "defeat",
538 : "players": [this.playerID],
539 : "allies": [this.playerID],
540 : "message": message
541 : });
542 :
543 0 : Engine.PostMessage(this.entity, won ? MT_PlayerWon : MT_PlayerDefeated, { "playerId": this.playerID });
544 : };
545 :
546 6 : Player.prototype.GetTeam = function()
547 : {
548 0 : return this.team;
549 : };
550 :
551 6 : Player.prototype.SetTeam = function(team)
552 : {
553 0 : if (this.teamsLocked)
554 0 : return;
555 :
556 0 : this.team = team;
557 :
558 : // Set all team members as allies.
559 0 : if (this.team != -1)
560 : {
561 0 : let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers();
562 0 : for (let i = 0; i < numPlayers; ++i)
563 : {
564 0 : let cmpPlayer = QueryPlayerIDInterface(i);
565 0 : if (this.team != cmpPlayer.GetTeam())
566 0 : continue;
567 :
568 0 : this.SetAlly(i);
569 0 : cmpPlayer.SetAlly(this.playerID);
570 : }
571 : }
572 :
573 0 : Engine.BroadcastMessage(MT_DiplomacyChanged, {
574 : "player": this.playerID,
575 : "otherPlayer": null
576 : });
577 : };
578 :
579 6 : Player.prototype.SetLockTeams = function(value)
580 : {
581 0 : this.teamsLocked = value;
582 : };
583 :
584 6 : Player.prototype.GetLockTeams = function()
585 : {
586 0 : return this.teamsLocked;
587 : };
588 :
589 6 : Player.prototype.GetDiplomacy = function()
590 : {
591 1 : return this.diplomacy.slice();
592 : };
593 :
594 6 : Player.prototype.SetDiplomacy = function(dipl)
595 : {
596 2 : this.diplomacy = dipl.slice();
597 :
598 2 : Engine.BroadcastMessage(MT_DiplomacyChanged, {
599 : "player": this.playerID,
600 : "otherPlayer": null
601 : });
602 : };
603 :
604 6 : Player.prototype.SetDiplomacyIndex = function(idx, value)
605 : {
606 0 : let cmpPlayer = QueryPlayerIDInterface(idx);
607 0 : if (!cmpPlayer)
608 0 : return;
609 :
610 0 : if (!this.IsActive() || !cmpPlayer.IsActive())
611 0 : return;
612 :
613 0 : this.diplomacy[idx] = value;
614 :
615 0 : Engine.BroadcastMessage(MT_DiplomacyChanged, {
616 : "player": this.playerID,
617 : "otherPlayer": cmpPlayer.GetPlayerID()
618 : });
619 :
620 : // Mutual worsening of relations.
621 0 : if (cmpPlayer.diplomacy[this.playerID] > value)
622 0 : cmpPlayer.SetDiplomacyIndex(this.playerID, value);
623 : };
624 :
625 6 : Player.prototype.UpdateSharedLos = function()
626 : {
627 0 : let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
628 0 : let cmpTechnologyManager = Engine.QueryInterface(this.entity, IID_TechnologyManager);
629 0 : if (!cmpRangeManager || !cmpTechnologyManager)
630 0 : return;
631 :
632 0 : if (!cmpTechnologyManager.IsTechnologyResearched(this.template.SharedLosTech))
633 : {
634 0 : cmpRangeManager.SetSharedLos(this.playerID, [this.playerID]);
635 0 : return;
636 : }
637 :
638 0 : cmpRangeManager.SetSharedLos(this.playerID, this.GetMutualAllies());
639 : };
640 :
641 6 : Player.prototype.GetFormations = function()
642 : {
643 0 : return this.formations;
644 : };
645 :
646 6 : Player.prototype.SetFormations = function(formations)
647 : {
648 0 : this.formations = formations;
649 : };
650 :
651 6 : Player.prototype.GetStartingCameraPos = function()
652 : {
653 0 : return this.startCam.position;
654 : };
655 :
656 6 : Player.prototype.GetStartingCameraRot = function()
657 : {
658 0 : return this.startCam.rotation;
659 : };
660 :
661 6 : Player.prototype.SetStartingCamera = function(pos, rot)
662 : {
663 0 : this.startCam = { "position": pos, "rotation": rot };
664 : };
665 :
666 6 : Player.prototype.HasStartingCamera = function()
667 : {
668 0 : return this.startCam !== undefined;
669 : };
670 :
671 6 : Player.prototype.HasSharedLos = function()
672 : {
673 0 : let cmpTechnologyManager = Engine.QueryInterface(this.entity, IID_TechnologyManager);
674 0 : return cmpTechnologyManager && cmpTechnologyManager.IsTechnologyResearched(this.template.SharedLosTech);
675 : };
676 6 : Player.prototype.HasSharedDropsites = function()
677 : {
678 0 : return this.sharedDropsites;
679 : };
680 :
681 6 : Player.prototype.SetControlAllUnits = function(c)
682 : {
683 0 : this.controlAllUnits = c;
684 : };
685 :
686 6 : Player.prototype.CanControlAllUnits = function()
687 : {
688 0 : return this.controlAllUnits;
689 : };
690 :
691 6 : Player.prototype.SetAI = function(flag)
692 : {
693 0 : this.isAI = flag;
694 : };
695 :
696 6 : Player.prototype.IsAI = function()
697 : {
698 0 : return this.isAI;
699 : };
700 :
701 6 : Player.prototype.GetPlayersByDiplomacy = function(func)
702 : {
703 2 : let players = [];
704 2 : for (let i = 0; i < this.diplomacy.length; ++i)
705 10 : if (this[func](i))
706 4 : players.push(i);
707 2 : return players;
708 : };
709 :
710 6 : Player.prototype.SetAlly = function(id)
711 : {
712 0 : this.SetDiplomacyIndex(id, 1);
713 : };
714 :
715 : /**
716 : * Check if given player is our ally.
717 : */
718 6 : Player.prototype.IsAlly = function(id)
719 : {
720 6 : return this.diplomacy[id] > 0;
721 : };
722 :
723 6 : Player.prototype.GetAllies = function()
724 : {
725 1 : return this.GetPlayersByDiplomacy("IsAlly");
726 : };
727 :
728 : /**
729 : * Check if given player is our ally excluding ourself
730 : */
731 6 : Player.prototype.IsExclusiveAlly = function(id)
732 : {
733 0 : return this.playerID != id && this.IsAlly(id);
734 : };
735 :
736 : /**
737 : * Check if given player is our ally, and we are its ally
738 : */
739 6 : Player.prototype.IsMutualAlly = function(id)
740 : {
741 0 : let cmpPlayer = QueryPlayerIDInterface(id);
742 0 : return this.IsAlly(id) && cmpPlayer && cmpPlayer.IsAlly(this.playerID);
743 : };
744 :
745 6 : Player.prototype.GetMutualAllies = function()
746 : {
747 0 : return this.GetPlayersByDiplomacy("IsMutualAlly");
748 : };
749 :
750 : /**
751 : * Check if given player is our ally, and we are its ally, excluding ourself
752 : */
753 6 : Player.prototype.IsExclusiveMutualAlly = function(id)
754 : {
755 0 : return this.playerID != id && this.IsMutualAlly(id);
756 : };
757 :
758 6 : Player.prototype.SetEnemy = function(id)
759 : {
760 0 : this.SetDiplomacyIndex(id, -1);
761 : };
762 :
763 : /**
764 : * Check if given player is our enemy
765 : */
766 6 : Player.prototype.IsEnemy = function(id)
767 : {
768 6 : return this.diplomacy[id] < 0;
769 : };
770 :
771 6 : Player.prototype.GetEnemies = function()
772 : {
773 1 : return this.GetPlayersByDiplomacy("IsEnemy");
774 : };
775 :
776 6 : Player.prototype.SetNeutral = function(id)
777 : {
778 0 : this.SetDiplomacyIndex(id, 0);
779 : };
780 :
781 : /**
782 : * Check if given player is neutral
783 : */
784 6 : Player.prototype.IsNeutral = function(id)
785 : {
786 0 : return this.diplomacy[id] == 0;
787 : };
788 :
789 : /**
790 : * Do some map dependant initializations
791 : */
792 6 : Player.prototype.OnGlobalInitGame = function(msg)
793 : {
794 : // Replace the "{civ}" code with this civ ID.
795 0 : let disabledTemplates = this.disabledTemplates;
796 0 : this.disabledTemplates = {};
797 0 : const civ = Engine.QueryInterface(this.entity, IID_Identity).GetCiv();
798 0 : for (let template in disabledTemplates)
799 0 : if (disabledTemplates[template])
800 0 : this.disabledTemplates[template.replace(/\{civ\}/g, civ)] = true;
801 : };
802 :
803 : /**
804 : * Keep track of population effects of all entities that
805 : * become owned or unowned by this player.
806 : */
807 6 : Player.prototype.OnGlobalOwnershipChanged = function(msg)
808 : {
809 4 : if (msg.from != this.playerID && msg.to != this.playerID)
810 0 : return;
811 :
812 4 : let cmpCost = Engine.QueryInterface(msg.entity, IID_Cost);
813 :
814 4 : if (msg.from == this.playerID)
815 : {
816 1 : if (cmpCost)
817 0 : this.popUsed -= cmpCost.GetPopCost();
818 :
819 1 : let panelIndex = this.panelEntities.indexOf(msg.entity);
820 1 : if (panelIndex >= 0)
821 0 : this.panelEntities.splice(panelIndex, 1);
822 :
823 1 : let barterIndex = this.barterEntities.indexOf(msg.entity);
824 1 : if (barterIndex >= 0)
825 1 : this.barterEntities.splice(barterIndex, 1);
826 : }
827 4 : if (msg.to == this.playerID)
828 : {
829 3 : if (cmpCost)
830 0 : this.popUsed += cmpCost.GetPopCost();
831 :
832 3 : let cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
833 3 : if (!cmpIdentity)
834 0 : return;
835 :
836 3 : if (MatchesClassList(cmpIdentity.GetClassesList(), panelEntityClasses))
837 0 : this.panelEntities.push(msg.entity);
838 :
839 3 : if (cmpIdentity.HasClass("Barter") && !Engine.QueryInterface(msg.entity, IID_Foundation))
840 1 : this.barterEntities.push(msg.entity);
841 : }
842 : };
843 :
844 6 : Player.prototype.OnResearchFinished = function(msg)
845 : {
846 0 : if (msg.tech == this.template.SharedLosTech)
847 0 : this.UpdateSharedLos();
848 0 : else if (msg.tech == this.template.SharedDropsitesTech)
849 0 : this.sharedDropsites = true;
850 : };
851 :
852 6 : Player.prototype.OnDiplomacyChanged = function()
853 : {
854 0 : this.UpdateSharedLos();
855 : };
856 :
857 6 : Player.prototype.OnValueModification = function(msg)
858 : {
859 0 : if (msg.component != "Player")
860 0 : return;
861 :
862 0 : if (msg.valueNames.indexOf("Player/SpyCostMultiplier") != -1)
863 0 : this.spyCostMultiplier = ApplyValueModificationsToEntity("Player/SpyCostMultiplier", +this.template.SpyCostMultiplier, this.entity);
864 :
865 0 : if (msg.valueNames.some(mod => mod.startsWith("Player/BarterMultiplier/")))
866 0 : for (let res in this.template.BarterMultiplier.Buy)
867 : {
868 0 : this.barterMultiplier.buy[res] = ApplyValueModificationsToEntity("Player/BarterMultiplier/Buy/"+res, +this.template.BarterMultiplier.Buy[res], this.entity);
869 0 : this.barterMultiplier.sell[res] = ApplyValueModificationsToEntity("Player/BarterMultiplier/Sell/"+res, +this.template.BarterMultiplier.Sell[res], this.entity);
870 : }
871 : };
872 :
873 6 : Player.prototype.SetCheatsEnabled = function(flag)
874 : {
875 0 : this.cheatsEnabled = flag;
876 : };
877 :
878 6 : Player.prototype.GetCheatsEnabled = function()
879 : {
880 0 : return this.cheatsEnabled;
881 : };
882 :
883 6 : Player.prototype.TributeResource = function(player, amounts)
884 : {
885 0 : let cmpPlayer = QueryPlayerIDInterface(player);
886 0 : if (!cmpPlayer)
887 0 : return;
888 :
889 0 : if (!this.IsActive() || !cmpPlayer.IsActive())
890 0 : return;
891 :
892 0 : let resTribCodes = Resources.GetTributableCodes();
893 0 : for (let resCode in amounts)
894 0 : if (resTribCodes.indexOf(resCode) == -1 ||
895 : !Number.isInteger(amounts[resCode]) ||
896 : amounts[resCode] < 0)
897 : {
898 0 : warn("Invalid tribute amounts: " + uneval(resCode) + ": " + uneval(amounts));
899 0 : return;
900 : }
901 :
902 0 : if (!this.SubtractResourcesOrNotify(amounts))
903 0 : return;
904 0 : cmpPlayer.AddResources(amounts);
905 :
906 0 : let total = Object.keys(amounts).reduce((sum, type) => sum + amounts[type], 0);
907 0 : let cmpOurStatisticsTracker = QueryPlayerIDInterface(this.playerID, IID_StatisticsTracker);
908 0 : if (cmpOurStatisticsTracker)
909 0 : cmpOurStatisticsTracker.IncreaseTributesSentCounter(total);
910 0 : let cmpTheirStatisticsTracker = QueryPlayerIDInterface(player, IID_StatisticsTracker);
911 0 : if (cmpTheirStatisticsTracker)
912 0 : cmpTheirStatisticsTracker.IncreaseTributesReceivedCounter(total);
913 :
914 0 : let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
915 0 : if (cmpGUIInterface)
916 0 : cmpGUIInterface.PushNotification({
917 : "type": "tribute",
918 : "players": [player],
919 : "donator": this.playerID,
920 : "amounts": amounts
921 : });
922 :
923 0 : Engine.BroadcastMessage(MT_TributeExchanged, {
924 : "to": player,
925 : "from": this.playerID,
926 : "amounts": amounts
927 : });
928 : };
929 :
930 6 : Player.prototype.AddDisabledTemplate = function(template)
931 : {
932 0 : this.disabledTemplates[template] = true;
933 0 : Engine.BroadcastMessage(MT_DisabledTemplatesChanged, { "player": this.playerID });
934 : };
935 :
936 6 : Player.prototype.RemoveDisabledTemplate = function(template)
937 : {
938 0 : this.disabledTemplates[template] = false;
939 0 : Engine.BroadcastMessage(MT_DisabledTemplatesChanged, { "player": this.playerID });
940 : };
941 :
942 6 : Player.prototype.SetDisabledTemplates = function(templates)
943 : {
944 0 : this.disabledTemplates = {};
945 0 : for (let template of templates)
946 0 : this.disabledTemplates[template] = true;
947 0 : Engine.BroadcastMessage(MT_DisabledTemplatesChanged, { "player": this.playerID });
948 : };
949 :
950 6 : Player.prototype.GetDisabledTemplates = function()
951 : {
952 0 : return this.disabledTemplates;
953 : };
954 :
955 6 : Player.prototype.AddDisabledTechnology = function(tech)
956 : {
957 0 : this.disabledTechnologies[tech] = true;
958 0 : Engine.BroadcastMessage(MT_DisabledTechnologiesChanged, { "player": this.playerID });
959 : };
960 :
961 6 : Player.prototype.RemoveDisabledTechnology = function(tech)
962 : {
963 0 : this.disabledTechnologies[tech] = false;
964 0 : Engine.BroadcastMessage(MT_DisabledTechnologiesChanged, { "player": this.playerID });
965 : };
966 :
967 6 : Player.prototype.SetDisabledTechnologies = function(techs)
968 : {
969 0 : this.disabledTechnologies = {};
970 0 : for (let tech of techs)
971 0 : this.disabledTechnologies[tech] = true;
972 0 : Engine.BroadcastMessage(MT_DisabledTechnologiesChanged, { "player": this.playerID });
973 : };
974 :
975 6 : Player.prototype.GetDisabledTechnologies = function()
976 : {
977 2 : return this.disabledTechnologies;
978 : };
979 :
980 6 : Player.prototype.OnGlobalPlayerDefeated = function(msg)
981 : {
982 0 : let cmpSound = Engine.QueryInterface(this.entity, IID_Sound);
983 0 : if (!cmpSound)
984 0 : return;
985 :
986 0 : const soundGroup = cmpSound.GetSoundGroup(this.playerID === msg.playerId ? "defeated" : this.IsAlly(msg.playerId) ? "defeated_ally" : this.HasWon() ? "won" : "defeated_enemy");
987 0 : if (soundGroup)
988 0 : Engine.QueryInterface(SYSTEM_ENTITY, IID_SoundManager).PlaySoundGroupForPlayer(soundGroup, this.playerID);
989 : };
990 :
991 6 : Engine.RegisterComponentType(IID_Player, "Player", Player);
|