Remove some duplication in the Jebel Barkal template loading.
[0ad.git] / binaries / data / mods / public / maps / random / jebel_barkal_triggers.js
blob941f1b451f68296b6a910700e4a2349dcf1ec56f
1 /**
2  * The city is patroled along its paths by infantry champions that respawn reoccuringly.
3  * There are increasingly great gaia attacks started from the different buildings.
4  * The players can destroy gaia buildings to reduce the number of attackers for the future.
5  */
7 /**
8  * If set to true, it will print how many templates would be spawned if the players were not defeated.
9  */
10 const dryRun = false;
12 /**
13  * If enabled, prints the number of units to the command line output.
14  */
15 const showDebugLog = false;
17 // TODO: harass attackers
19 var jebelBarkal_rank = "Advanced";
21 /**
22  * These are the templates spawned at the gamestart and during the game.
23  */
24 var jebelBarkal_templateClasses = deepfreeze({
25         "heroes": "Hero",
26         "champions": "Champion+!Elephant",
27         "elephants": "Champion+Elephant",
28         "champion_infantry": "Champion+Infantry",
29         "champion_infantry_melee": "Champion+Infantry+Melee",
30         "champion_infantry_ranged": "Champion+Infantry+Ranged",
31         "champion_cavalry": "Champion+Cavalry",
32         "citizenSoldiers": "CitizenSoldier",
33         "citizenSoldier_infantry": "CitizenSoldier+Infantry",
34         "citizenSoldier_infantry_melee": "CitizenSoldier+Infantry+Melee",
35         "citizenSoldier_infantry_ranged": "CitizenSoldier+Infantry+Ranged",
36         "citizenSoldier_cavalry": "CitizenSoldier+Cavalry",
37         "healers": "Healer",
38         "females": "FemaleCitizen"
39 });
41 var jebelBarkal_templates = deepfreeze(Object.keys(jebelBarkal_templateClasses).reduce((templates, name) => {
42         templates[name] = TriggerHelper.GetTemplateNamesByClasses(jebelBarkal_templateClasses[name], "kush", undefined, jebelBarkal_rank, true);
43         return templates;
44 }, {}));
46 /**
47  * These are the formations patroling and attacking units can use.
49 var jebelBarkal_formations = [
50         "special/formations/line_closed",
51         "special/formations/column_closed"
54 /**
55  *  Balancing helper function.
56  *
57  *  @returns min0 value at the beginning of the game, min60 after an hour of gametime or longer and
58  *  a proportionate number between these two values before the first hour is reached.
59  */
60 var scaleByTime = (minCurrent, min0, min60) => min0 + (min60 - min0) * Math.min(1, minCurrent / 60);
62 /**
63  *  @returns min0 value at the beginning of the game, min60 after an hour of gametime or longer and
64  *  a proportionate number between these two values before the first hour is reached.
65  */
66 var scaleByMapSize = (min, max) => min + (max - min) * (TriggerHelper.GetMapSizeTiles() - 128) / (512 - 128);
68 /**
69  * Defensive Infantry units patrol along the paths of the city.
70  */
71 var jebelBarkal_cityPatrolGroup_count = time => scaleByTime(time, 3, scaleByMapSize(3, 10));
72 var jebelBarkal_cityPatrolGroup_interval = time => scaleByTime(time, 5, 3);
73 var jebelBarkal_cityPatrolGroup_balancing = {
74         "buildingClasses": ["Wonder", "Temple", "CivCentre", "Fortress", "Barracks", "Embassy"],
75         "unitCount": time => Math.min(20, scaleByTime(time, 10, 45)),
76         "unitComposition": (time, heroes) => [
77                 {
78                         "templates": jebelBarkal_templates.champion_infantry_melee,
79                         "frequency": scaleByTime(time, 0, 2)
80                 },
81                 {
82                         "templates": jebelBarkal_templates.champion_infantry_ranged,
83                         "frequency": scaleByTime(time, 0, 3)
84                 },
85                 {
86                         "templates": jebelBarkal_templates.citizenSoldier_infantry_melee,
87                         "frequency": scaleByTime(time, 2, 0)
88                 },
89                 {
90                         "templates": jebelBarkal_templates.citizenSoldier_infantry_ranged,
91                         "frequency": scaleByTime(time, 3, 0)
92                 }
93         ]
96 /**
97  * Frequently the buildings spawn different units that attack the players groupwise.
98  */
99 var jebelBarkal_attackInterval = time => randFloat(5, 7);
102  * Frequently cavalry is spawned at few buildings to harass the enemy trade.
103  */
104 var jebelBarkal_harassInterval = time => randFloat(6, 8);
107  * Assume gaia to be the native kushite player.
108  */
109 var jebelBarkal_playerID = 0;
112  * Soldiers will patrol along the city grid.
113  */
114 var jebelBarkal_triggerPointPath = "A";
117  * Attacker groups approach these player buildings and attack enemies of the given classes encountered.
118  */
119 var jebelBarkal_pathTargetClasses = "CivCentre Wonder";
122  * Soldiers and siege towers prefer these targets when attacking or patroling.
123  */
124 var jebelBarkal_soldierTargetClasses = "Unit+!Ship";
127  * Elephants focus these units when attacking.
128  */
129 var jebelBarkal_elephantTargetClasses = "Defensive SiegeEngine";
132  * This defines which units are spawned and garrisoned at the gamestart per building.
133  */
134 var jebelBarkal_buildingGarrison = [
135         {
136                 "buildingClasses": ["Wonder", "Temple", "CivCentre", "Fortress"],
137                 "unitTemplates": jebelBarkal_templates.champions
138         },
139         {
140                 "buildingClasses": ["Barracks", "Embassy"],
141                 "unitTemplates": [...jebelBarkal_templates.citizenSoldiers, ...jebelBarkal_templates.champions]
142         },
143         {
144                 "buildingClasses": ["DefenseTower"],
145                 "unitTemplates": jebelBarkal_templates.champion_infantry
146         },
147         {
148                 "buildingClasses": ["ElephantStables"],
149                 "unitTemplates": jebelBarkal_templates.elephants
150         },
151         {
152                 "buildingClasses": ["Stable"],
153                 "unitTemplates": jebelBarkal_templates.champion_cavalry
154         },
155         {
156                 "buildingClasses": ["Pyramid"],
157                 "unitTemplates": [...jebelBarkal_templates.citizenSoldiers, ...jebelBarkal_templates.healers]
158         },
159         {
160                 "buildingClasses": ["House"],
161                 "unitTemplates": [...jebelBarkal_templates.females, ...jebelBarkal_templates.healers]
162         }
166  * This defines which units are spawned at the different buildings at the given time.
167  * The buildings are ordered by strength.
168  * Notice that there are always 2 groups of these count spawned, one for each side!
169  * The units should do a walk-attack to random player CCs
170  */
171 var jebelBarkal_attackerGroup_balancing = [
172         {
173                 "buildingClasses": ["Wonder"],
174                 "unitCount": time => scaleByTime(time, 0, 85),
175                 "unitComposition": (time, heroes) => [
176                         {
177                                 "templates": jebelBarkal_templates.heroes,
178                                 "count": randBool(scaleByTime(time, -0.5, 2)) ? 1 : 0,
179                                 "unique_entities": heroes
180                         },
181                         {
182                                 "templates": jebelBarkal_templates.healers,
183                                 "frequency": randFloat(0, 0.1)
184                         },
185                         {
186                                 "templates": jebelBarkal_templates.champions,
187                                 "frequency": scaleByTime(time, 0, 0.6)
188                         },
189                         {
190                                 "templates": jebelBarkal_templates.champion_infantry_ranged,
191                                 "frequency": scaleByTime(time, 0, 0.4)
192                         },
193                         {
194                                 "templates": jebelBarkal_templates.citizenSoldiers,
195                                 "frequency": scaleByTime(time, 1, 0)
196                         },
197                         {
198                                 "templates": jebelBarkal_templates.citizenSoldier_infantry_ranged,
199                                 "frequency": scaleByTime(time, 1, 0)
200                         }
201                 ]
202         },
203         {
204                 "buildingClasses": ["Fortress"],
205                 "unitCount": time => scaleByTime(time, 0, 45),
206                 "unitComposition": (time, heroes) => [
207                         {
208                                 "templates": jebelBarkal_templates.heroes,
209                                 "count": randBool(scaleByTime(time, -0.5, 1.5)) ? 1 : 0,
210                                 "unique_entities": heroes
211                         },
212                         {
213                                 "templates": jebelBarkal_templates.champions,
214                                 "frequency": scaleByTime(time, 0, 1)
215                         }
216                 ]
217         },
218         {
219                 "buildingClasses": ["Temple"],
220                 "unitCount": time => Math.min(45, scaleByTime(time, 0, 90)),
221                 "unitComposition": (time, heroes) => [
222                         {
223                                 "templates": jebelBarkal_templates.heroes,
224                                 "count": randBool(scaleByTime(time, -0.5, 1)) ? 1 : 0,
225                                 "unique_entities": heroes
226                         },
227                         {
228                                 "templates": jebelBarkal_templates.champion_infantry_melee,
229                                 "frequency": 0.5
230                         },
231                         {
232                                 "templates": jebelBarkal_templates.champion_infantry_ranged,
233                                 "frequency": 0.5
234                         },
235                         {
236                                 "templates": jebelBarkal_templates.healers,
237                                 "frequency": randFloat(0.05, 0.2)
238                         }
239                 ]
240         },
241         {
242                 "buildingClasses": ["CivCentre"],
243                 "unitCount": time => Math.min(40, scaleByTime(time, 0, 80)),
244                 "unitComposition": (time, heroes) => [
245                         {
246                                 "templates": jebelBarkal_templates.heroes,
247                                 "count": randBool(scaleByTime(time, -0.5, 0.5)) ? 1 : 0,
248                                 "unique_entities": heroes
249                         },
250                         {
251                                 "templates": jebelBarkal_templates.champion_infantry,
252                                 "frequency": scaleByTime(time, 0, 1)
253                         },
254                         {
255                                 "templates": jebelBarkal_templates.citizenSoldiers,
256                                 "frequency": scaleByTime(time, 1, 0)
257                         }
258                 ]
259         },
260         {
261                 "buildingClasses": ["Stable"],
262                 "unitCount": time => Math.min(30, scaleByTime(time, 0, 80)),
263                 "harasserSize": time => Math.min(20, scaleByTime(time, 0, 50)),
264                 "unitComposition": (time, heroes) => [
265                         {
266                                 "templates": jebelBarkal_templates.citizenSoldier_cavalry,
267                                 "frequency": scaleByTime(time, 2, 0)
268                         },
269                         {
270                                 "templates": jebelBarkal_templates.champion_cavalry,
271                                 "frequency": scaleByTime(time, 0, 1)
272                         }
273                 ]
274         },
275         {
276                 "buildingClasses": ["Barracks", "Embassy"],
277                 "unitCount": time => Math.min(35, scaleByTime(time, 0, 70)),
278                 "unitComposition": (time, heroes) => [
279                         {
280                                 "templates": jebelBarkal_templates.citizenSoldier_infantry,
281                                 "frequency": 1
282                         }
283                 ]
284         },
285         {
286                 "buildingClasses": ["ElephantStables"],
287                 "unitCount": time => scaleByTime(time, 0, 10),
288                 "unitComposition": (time, heroes) => [
289                         {
290                                 "templates": jebelBarkal_templates.elephants,
291                                 "frequency": 1
292                         }
293                 ]
294         }
297 Trigger.prototype.debugLog = function(txt)
299         if (showDebugLog)
300                 print("DEBUG [" + Math.round(TriggerHelper.GetMinutes()) + "] " + txt + "\n");
303 Trigger.prototype.JebelBarkal_Init = function()
305         this.JebelBarkal_TrackUnits();
306         this.JebelBarkal_SetDefenderStance();
307         this.JebelBarkal_GarrisonBuildings();
308         this.JebelBarkal_SpawnCityPatrolGroups();
309         this.JebelBarkal_SpawnAttackerGroups();
312 Trigger.prototype.JebelBarkal_TrackUnits = function()
314         // Each item is an entity ID
315         this.jebelBarkal_heroes = [];
317         // Each item is an array of entity IDs
318         this.jebelBarkal_patrolingUnits = [];
320         // Array of entityIDs where patrol groups can spawn
321         this.jebelBarkal_patrolGroupSpawnPoints = TriggerHelper.GetPlayerEntitiesByClass(
322                 jebelBarkal_playerID,
323                 jebelBarkal_cityPatrolGroup_balancing.buildingClasses);
325         this.debugLog("Patrol spawn points: " + uneval(this.jebelBarkal_patrolGroupSpawnPoints));
327         // Array of entityIDs where attacker groups can spawn
328         this.jebelBarkal_attackerGroupSpawnPoints = TriggerHelper.GetPlayerEntitiesByClass(
329                 jebelBarkal_playerID,
330                 jebelBarkal_attackerGroup_balancing.reduce((classes, attackerSpawning) => classes.concat(attackerSpawning.buildingClasses), []));
332         this.debugLog("Attacker spawn points: " + uneval(this.jebelBarkal_attackerGroupSpawnPoints));
335 Trigger.prototype.JebelBarkal_SetDefenderStance = function()
337         for (let ent of TriggerHelper.GetPlayerEntitiesByClass(jebelBarkal_playerID, "Soldier"))
338                 TriggerHelper.SetUnitStance(ent, "defensive");
341 Trigger.prototype.JebelBarkal_GarrisonBuildings = function()
343         for (let buildingGarrison of jebelBarkal_buildingGarrison)
344                 TriggerHelper.SpawnAndGarrisonAtClasses(jebelBarkal_playerID, buildingGarrison.buildingClasses, buildingGarrison.unitTemplates, 1);
348  * Spawn new groups if old ones were wiped out.
349  */
350 Trigger.prototype.JebelBarkal_SpawnCityPatrolGroups = function()
352         if (!this.jebelBarkal_patrolGroupSpawnPoints.length)
353                 return;
355         let time = TriggerHelper.GetMinutes();
356         let groupCount = Math.floor(Math.max(0, jebelBarkal_cityPatrolGroup_count(time)) - this.jebelBarkal_patrolingUnits.length);
358         for (let i = 0; i < groupCount; ++i)
359         {
360                 let spawnEnt = pickRandom(this.jebelBarkal_patrolGroupSpawnPoints);
362                 let templateCounts = TriggerHelper.BalancedTemplateComposition(
363                         jebelBarkal_cityPatrolGroup_balancing.unitComposition(time, this.jebelBarkal_heroes),
364                         jebelBarkal_cityPatrolGroup_balancing.unitCount(time));
366                 this.debugLog("Spawning " + groupCount + " city patrol groups, " +
367                         this.jebelBarkal_patrolingUnits.length + " exist, templates:\n" + uneval(templateCounts));
369                 let groupEntities = this.JebelBarkal_SpawnTemplates(spawnEnt, templateCounts);
371                 this.jebelBarkal_patrolingUnits.push(groupEntities);
373                 for (let ent of groupEntities)
374                         TriggerHelper.SetUnitStance(ent, "defensive");
376                 TriggerHelper.SetUnitFormation(jebelBarkal_playerID, groupEntities, pickRandom(jebelBarkal_formations));
378                 for (let entTriggerPoint of this.GetTriggerPoints(jebelBarkal_triggerPointPath))
379                 {
380                         let pos = TriggerHelper.GetEntityPosition2D(entTriggerPoint);
381                         ProcessCommand(jebelBarkal_playerID, {
382                                 "type": "patrol",
383                                 "entities": groupEntities,
384                                 "x": pos.x,
385                                 "z": pos.y,
386                                 "targetClasses": {
387                                         "attack": jebelBarkal_soldierTargetClasses
388                                 },
389                                 "queued": true,
390                                 "allowCapture": false
391                         });
392                 }
393         }
395         this.DoAfterDelay(jebelBarkal_cityPatrolGroup_interval(time) * 60 * 1000, "JebelBarkal_SpawnCityPatrolGroups", {});
398 Trigger.prototype.JebelBarkal_SpawnTemplates = function(spawnEnt, templateCounts)
400         let groupEntities = [];
402         for (let templateName in templateCounts)
403         {
404                 let ents = TriggerHelper.SpawnUnits(spawnEnt, templateName, templateCounts[templateName], jebelBarkal_playerID);
406                 groupEntities = groupEntities.concat(ents);
408                 if (jebelBarkal_templates.heroes.indexOf(templateName) != -1 && ents[0])
409                         this.jebelBarkal_heroes.push(ents[0]);
410         }
412         return groupEntities;
416  * Spawn a group of attackers at every remaining building.
417  */
418 Trigger.prototype.JebelBarkal_SpawnAttackerGroups = function()
420         let time = TriggerHelper.GetMinutes();
421         this.debugLog("Attacker wave");
423         let targets = TriggerHelper.GetAllPlayersEntitiesByClass(jebelBarkal_pathTargetClasses);
424         if (!targets.length)
425                 return;
427         let spawnedAnything = false;
428         for (let spawnEnt of this.jebelBarkal_attackerGroupSpawnPoints)
429         {
430                 let spawnPointBalancing = jebelBarkal_attackerGroup_balancing.find(balancing =>
431                         TriggerHelper.EntityMatchesClassList(spawnEnt, balancing.buildingClasses));
433                 let unitCount = Math.round(spawnPointBalancing.unitCount(time));
435                 if (!unitCount)
436                         continue;
438                 let templateCounts = TriggerHelper.BalancedTemplateComposition(spawnPointBalancing.unitComposition(time, this.jebelBarkal_heroes), unitCount);
440                 this.debugLog("Spawning " + unitCount + " attackers at " + uneval(spawnPointBalancing.buildingClasses) + " " + spawnEnt + ":\n" + uneval(templateCounts));
442                 if (dryRun)
443                         continue;
445                 let groupEntities = this.JebelBarkal_SpawnTemplates(spawnEnt, templateCounts);
446                 spawnedAnything = true;
448                 let isElephant = TriggerHelper.EntityMatchesClassList(groupEntities[0], "Elephant");
450                 for (let ent of groupEntities)
451                         TriggerHelper.SetUnitStance(ent, isElephant ? "aggressive" : "violent");
453                 if (!isElephant)
454                         TriggerHelper.SetUnitFormation(jebelBarkal_playerID, groupEntities, pickRandom(jebelBarkal_formations));
456                 let targetPos = TriggerHelper.GetEntityPosition2D(pickRandom(targets));
457                 if (!targetPos)
458                         continue;
460                 ProcessCommand(jebelBarkal_playerID, {
461                         "type": "attack-walk",
462                         "entities": groupEntities,
463                         "x": targetPos.x,
464                         "z": targetPos.y,
465                         "targetClasses": isElephant ? jebelBarkal_elephantTargetClasses : jebelBarkal_soldierTargetClasses,
466                         "allowCapture": false,
467                         "queued": false
468                 });
469         }
471         if (spawnedAnything)
472                 Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
473                         "message": markForTranslation("Napata is attacking!"),
474                         "translateMessage": true
475                 });
477         this.DoAfterDelay(jebelBarkal_attackInterval(TriggerHelper.GetMinutes()) * 60 * 1000, "JebelBarkal_SpawnAttackerGroups", {});
481  * Keep track of heroes, so that each of them remains unique.
482  * Keep track of spawn points, as only there units should be spawned.
483  * 
484  */
485 Trigger.prototype.JebelBarkal_OwnershipChange = function(data)
487         if (data.from != 0)
488                 return;
490         for (let array of [this.jebelBarkal_heroes, this.jebelBarkal_patrolGroupSpawnPoints, this.jebelBarkal_attackerGroupSpawnPoints, ...this.jebelBarkal_patrolingUnits])
491         {
492                 let idx = array.indexOf(data.entity);
493                 if (idx != -1)
494                         array.splice(idx, 1);
495         }
497         this.jebelBarkal_patrolingUnits = this.jebelBarkal_patrolingUnits.filter(entities => entities.length);
502         let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
503         cmpTrigger.RegisterTrigger("OnInitGame", "JebelBarkal_Init", { "enabled": true });
504         cmpTrigger.RegisterTrigger("OnOwnershipChanged", "JebelBarkal_OwnershipChange", { "enabled": true });