Refactor and move random template composition triggerscript code used for gaia attack...
[0ad.git] / binaries / data / mods / public / maps / random / survivalofthefittest_triggers.js
blob1688a2336a17f94530b1909baf6f736807e5d8c7
1 /**
2  * If set to true, it will print how many templates would be spawned if the players were not defeated.
3  */
4 const dryRun = false;
6 /**
7  * If enabled, prints the number of units to the command line output.
8  */
9 const debugLog = false;
11 /**
12  * Get the number of minutes to pass between spawning new treasures.
13  */
14 var treasureTime = () => randFloat(3, 5);
16 /**
17  * Get the time in minutes when the first wave of attackers will be spawned.
18  */
19 var firstWaveTime = () => randFloat(4, 6);
21 /**
22  * Maximum time in minutes between two consecutive waves.
23  */
24 var maxWaveTime = 4;
26 /**
27  * Get the next attacker wave delay.
28  */
29 var waveTime = () => randFloat(0.5, 1) * maxWaveTime;
31 /**
32  * Roughly the number of attackers on the first wave.
33  */
34 var initialAttackers = 5;
36 /**
37  * Increase the number of attackers exponentially, by this percent value per minute.
38  */
39 var percentPerMinute = 1.05;
41 /**
42  * Greatest amount of attackers that can be spawned.
43  */
44 var totalAttackerLimit = 200;
46 /**
47  * Least and greatest amount of siege engines per wave.
48  */
49 var siegeFraction = () => randFloat(0.2, 0.5);
51 /**
52  * Potentially / definitely spawn a gaia hero after this number of minutes.
53  */
54 var heroTime = () => randFloat(20, 60);
56 /**
57  * The following templates can't be built by any player.
58  */
59 var disabledTemplates = (civ) => [
60         // Economic structures
61         "structures/" + civ + "_corral",
62         "structures/" + civ + "_farmstead",
63         "structures/" + civ + "_field",
64         "structures/" + civ + "_storehouse",
65         "structures/" + civ + "_rotarymill",
66         "units/maur_support_elephant",
68         // Expansions
69         "structures/" + civ + "_civil_centre",
70         "structures/" + civ + "_military_colony",
72         // Walls
73         "structures/" + civ + "_wallset_stone",
74         "structures/rome_wallset_siege",
75         "other/wallset_palisade",
77         // Shoreline
78         "structures/" + civ + "_dock",
79         "structures/brit_crannog",
80         "structures/cart_super_dock",
81         "structures/ptol_lighthouse"
84 /**
85  * Spawn these treasures in regular intervals.
86  */
87 var treasures = [
88         "gaia/treasure/food_barrel",
89         "gaia/treasure/food_bin",
90         "gaia/treasure/food_crate",
91         "gaia/treasure/food_jars",
92         "gaia/treasure/metal",
93         "gaia/treasure/stone",
94         "gaia/treasure/wood",
95         "gaia/treasure/wood",
96         "gaia/treasure/wood"
99 /**
100  * An object that maps from civ [f.e. "spart"] to an object
101  * that has the keys "champions", "siege" and "heroes",
102  * which is an array containing all these templates,
103  * trainable from a building or not.
104  */
105 var attackerUnitTemplates = {};
107 Trigger.prototype.InitSurvival = function()
109         this.InitStartingUnits();
110         this.LoadAttackerTemplates();
111         this.SetDisableTemplates();
112         this.PlaceTreasures();
113         this.InitializeEnemyWaves();
116 Trigger.prototype.debugLog = function(txt)
118         if (!debugLog)
119                 return;
121         print("DEBUG [" + Math.round(Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime() / 60 / 1000) + "]  " + txt + "\n");
124 Trigger.prototype.LoadAttackerTemplates = function()
126         for (let civ of ["gaia", ...Object.keys(loadCivFiles(false))])
127                 attackerUnitTemplates[civ] = {
128                         "heroes": TriggerHelper.GetTemplateNamesByClasses("Hero", civ, undefined, true),
129                         "champions": TriggerHelper.GetTemplateNamesByClasses("Champion+!Elephant", civ, undefined, true),
130                         "siege": TriggerHelper.GetTemplateNamesByClasses("Siege Champion+Elephant", civ, "packed", undefined)
131                 };
133         this.debugLog("Attacker templates:");
134         this.debugLog(uneval(attackerUnitTemplates));
137 Trigger.prototype.SetDisableTemplates = function()
139         for (let i = 1; i < TriggerHelper.GetNumberOfPlayers(); ++i)
140         {
141                 let cmpPlayer = QueryPlayerIDInterface(i);
142                 cmpPlayer.SetDisabledTemplates(disabledTemplates(cmpPlayer.GetCiv()));
143         }
147  *  Remember civic centers and make women invincible.
148  */
149 Trigger.prototype.InitStartingUnits = function()
151         let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
152         for (let i = 1; i < TriggerHelper.GetNumberOfPlayers(); ++i)
153         {
154                 let playerEntities = cmpRangeManager.GetEntitiesByPlayer(i);
156                 for (let entity of playerEntities)
157                 {
158                         if (TriggerHelper.EntityMatchesClassList(entity, "CivilCentre"))
159                                 this.playerCivicCenter[i] = entity;
160                         else if (TriggerHelper.EntityMatchesClassList(entity, "FemaleCitizen"))
161                         {
162                                 this.treasureFemale[i] = entity;
164                                 let cmpDamageReceiver = Engine.QueryInterface(entity, IID_DamageReceiver);
165                                 cmpDamageReceiver.SetInvulnerability(true);
166                         }
167                 }
168         }
171 Trigger.prototype.InitializeEnemyWaves = function()
173         let time = firstWaveTime() * 60 * 1000;
174         Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).AddTimeNotification({
175                 "message": markForTranslation("The first wave will start in %(time)s!"),
176                 "translateMessage": true
177         }, time);
178         this.DoAfterDelay(time, "StartAnEnemyWave", {});
181 Trigger.prototype.StartAnEnemyWave = function()
183         let currentMin = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime() / 60 / 1000;
184         let nextWaveTime = waveTime();
185         let civ = pickRandom(Object.keys(attackerUnitTemplates));
187         // Determine total attacker count of the current wave.
188         // Exponential increase with time, capped to the limit and fluctuating proportionally with the current wavetime.
189         let totalAttackers = Math.ceil(Math.min(totalAttackerLimit,
190                 initialAttackers * Math.pow(percentPerMinute, currentMin) * nextWaveTime / maxWaveTime));
192         let siegeRatio = siegeFraction();
194         this.debugLog("Spawning " + totalAttackers + " attackers, siege ratio " + siegeRatio.toFixed(2));
196         let attackerCount = TriggerHelper.BalancedTemplateComposition(
197                 [
198                         {
199                                 "templates": attackerUnitTemplates[civ].heroes,
200                                 "count": currentMin > heroTime() && attackerUnitTemplates[civ].heroes.length ? 1 : 0
201                         },
202                         {
203                                 "templates": attackerUnitTemplates[civ].siege,
204                                 "frequency": siegeRatio
205                         },
206                         {
207                                 "templates": attackerUnitTemplates[civ].champions,
208                                 "frequency": 1 - siegeRatio
209                         }
210                 ],
211                 totalAttackers);
213         this.debugLog("Templates: " + uneval(attackerCount));
215         // Spawn the templates
216         let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
217         let spawned = false;
218         for (let point of this.GetTriggerPoints("A"))
219         {
220                 if (dryRun)
221                 {
222                         spawned = true;
223                         break;
224                 }
226                 // Don't spawn attackers for defeated players and players that lost their cc after win
227                 let playerID = QueryOwnerInterface(point, IID_Player).GetPlayerID();
228                 let civicCentre = this.playerCivicCenter[playerID];
229                 if (!civicCentre)
230                         continue;
232                 // Check if the cc is garrisoned in another building
233                 let cmpPosition = Engine.QueryInterface(civicCentre, IID_Position);
234                 if (!cmpPosition || !cmpPosition.IsInWorld())
235                         continue;
237                 let targetPos = cmpPosition.GetPosition2D();
239                 for (let templateName in attackerCount)
240                 {
241                         let isHero = attackerUnitTemplates[civ].heroes.indexOf(templateName) != -1;
243                         // Don't spawn gaia hero if the previous one is still alive
244                         if (this.gaiaHeroes[playerID] && isHero)
245                         {
246                                 let cmpHealth = Engine.QueryInterface(this.gaiaHeroes[playerID], IID_Health);
247                                 if (cmpHealth && cmpHealth.GetHitpoints() != 0)
248                                 {
249                                         this.debugLog("Not spawning hero for player " + playerID + " as the previous one is still alive");
250                                         continue;
251                                 }
252                         }
254                         if (dryRun)
255                                 continue;
257                         let entities = TriggerHelper.SpawnUnits(point, templateName, attackerCount[templateName], 0);
259                         ProcessCommand(0, {
260                                 "type": "attack-walk",
261                                 "entities": entities,
262                                 "x": targetPos.x,
263                                 "z": targetPos.y,
264                                 "targetClasses": undefined,
265                                 "allowCapture": false,
266                                 "queued": true
267                         });
269                         if (isHero)
270                                 this.gaiaHeroes[playerID] = entities[0];
271                 }
272                 spawned = true;
273         }
275         if (!spawned)
276                 return;
278         Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
279                 "message": markForTranslation("An enemy wave is attacking!"),
280                 "translateMessage": true
281         });
283         this.DoAfterDelay(nextWaveTime * 60 * 1000, "StartAnEnemyWave", {});
286 Trigger.prototype.PlaceTreasures = function()
288         let point = pickRandom(["B", "C", "D"]);
289         let triggerPoints = this.GetTriggerPoints(point);
290         for (let point of triggerPoints)
291                 TriggerHelper.SpawnUnits(point, pickRandom(treasures), 1, 0);
293         this.DoAfterDelay(treasureTime() * 60 * 1000, "PlaceTreasures", {});
296 Trigger.prototype.OnOwnershipChanged = function(data)
298         if (data.entity == this.playerCivicCenter[data.from])
299         {
300                 this.playerCivicCenter[data.from] = undefined;
302                 TriggerHelper.DefeatPlayer(
303                         data.from,
304                         markForTranslation("%(player)s has been defeated (lost civic center)."));
305         }
306         else if (data.entity == this.treasureFemale[data.from])
307         {
308                 this.treasureFemale[data.from] = undefined;
309                 Engine.DestroyEntity(data.entity);
310         }
315         let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
317         cmpTrigger.treasureFemale = [];
318         cmpTrigger.playerCivicCenter = [];
319         cmpTrigger.gaiaHeroes = [];
321         cmpTrigger.RegisterTrigger("OnInitGame", "InitSurvival", { "enabled": true });
322         cmpTrigger.RegisterTrigger("OnOwnershipChanged", "OnOwnershipChanged", { "enabled": true });