Merge 'remotes/trunk'
[0ad.git] / binaries / data / mods / public / maps / random / survivalofthefittest_triggers.js
blob5cc21872ea14397d6e02bdf7cfc6112cd72e26c7
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         "structures/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(TriggerHelper.GetMinutes()) + "]  " + 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                 QueryPlayerIDInterface(i).SetDisabledTemplates(disabledTemplates(QueryPlayerIDInterface(i, IID_Identity).GetCiv()));
144  *  Remember civic centers and make women invincible.
145  */
146 Trigger.prototype.InitStartingUnits = function()
148         for (let playerID = 1; playerID < TriggerHelper.GetNumberOfPlayers(); ++playerID)
149         {
150                 this.playerCivicCenter[playerID] = TriggerHelper.GetPlayerEntitiesByClass(playerID, "CivilCentre")[0];
151                 this.treasureFemale[playerID] = TriggerHelper.GetPlayerEntitiesByClass(playerID, "FemaleCitizen")[0];
152                 Engine.QueryInterface(this.treasureFemale[playerID], IID_Resistance).SetInvulnerability(true);
153         }
156 Trigger.prototype.InitializeEnemyWaves = function()
158         let time = firstWaveTime() * 60 * 1000;
159         Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).AddTimeNotification({
160                 "message": markForTranslation("The first wave will start in %(time)s!"),
161                 "translateMessage": true
162         }, time);
163         this.DoAfterDelay(time, "StartAnEnemyWave", {});
166 Trigger.prototype.StartAnEnemyWave = function()
168         let currentMin = TriggerHelper.GetMinutes();
169         let nextWaveTime = waveTime();
170         let civ = pickRandom(Object.keys(attackerUnitTemplates));
172         // Determine total attacker count of the current wave.
173         // Exponential increase with time, capped to the limit and fluctuating proportionally with the current wavetime.
174         let totalAttackers = Math.ceil(Math.min(totalAttackerLimit,
175                 initialAttackers * Math.pow(percentPerMinute, currentMin) * nextWaveTime / maxWaveTime));
177         let siegeRatio = siegeFraction();
179         this.debugLog("Spawning " + totalAttackers + " attackers, siege ratio " + siegeRatio.toFixed(2));
181         let attackerCount = TriggerHelper.BalancedTemplateComposition(
182                 [
183                         {
184                                 "templates": attackerUnitTemplates[civ].heroes,
185                                 "count": currentMin > heroTime() && attackerUnitTemplates[civ].heroes.length ? 1 : 0
186                         },
187                         {
188                                 "templates": attackerUnitTemplates[civ].siege,
189                                 "frequency": siegeRatio
190                         },
191                         {
192                                 "templates": attackerUnitTemplates[civ].champions,
193                                 "frequency": 1 - siegeRatio
194                         }
195                 ],
196                 totalAttackers);
198         this.debugLog("Templates: " + uneval(attackerCount));
200         // Spawn the templates
201         let spawned = false;
202         for (let point of this.GetTriggerPoints("A"))
203         {
204                 if (dryRun)
205                 {
206                         spawned = true;
207                         break;
208                 }
210                 // Don't spawn attackers for defeated players and players that lost their cc after win
211                 let cmpPlayer = QueryOwnerInterface(point, IID_Player);
212                 if (!cmpPlayer)
213                         continue;
215                 let playerID = cmpPlayer.GetPlayerID();
216                 let civicCentre = this.playerCivicCenter[playerID];
217                 if (!civicCentre)
218                         continue;
220                 // Check if the cc is garrisoned in another building
221                 let targetPos = TriggerHelper.GetEntityPosition2D(civicCentre);
222                 if (!targetPos)
223                         continue;
225                 for (let templateName in attackerCount)
226                 {
227                         let isHero = attackerUnitTemplates[civ].heroes.indexOf(templateName) != -1;
229                         // Don't spawn gaia hero if the previous one is still alive
230                         if (this.gaiaHeroes[playerID] && isHero)
231                         {
232                                 let cmpHealth = Engine.QueryInterface(this.gaiaHeroes[playerID], IID_Health);
233                                 if (cmpHealth && cmpHealth.GetHitpoints() != 0)
234                                 {
235                                         this.debugLog("Not spawning hero for player " + playerID + " as the previous one is still alive");
236                                         continue;
237                                 }
238                         }
240                         if (dryRun)
241                                 continue;
243                         let entities = TriggerHelper.SpawnUnits(point, templateName, attackerCount[templateName], 0);
245                         ProcessCommand(0, {
246                                 "type": "attack-walk",
247                                 "entities": entities,
248                                 "x": targetPos.x,
249                                 "z": targetPos.y,
250                                 "targetClasses": undefined,
251                                 "allowCapture": false,
252                                 "queued": true
253                         });
255                         if (isHero)
256                                 this.gaiaHeroes[playerID] = entities[0];
257                 }
258                 spawned = true;
259         }
261         if (!spawned)
262                 return;
264         Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
265                 "message": markForTranslation("An enemy wave is attacking!"),
266                 "translateMessage": true
267         });
269         this.DoAfterDelay(nextWaveTime * 60 * 1000, "StartAnEnemyWave", {});
272 Trigger.prototype.PlaceTreasures = function()
274         let triggerPoints = this.GetTriggerPoints(pickRandom(["B", "C", "D"]));
275         for (let point of triggerPoints)
276                 TriggerHelper.SpawnUnits(point, pickRandom(treasures), 1, 0);
278         this.DoAfterDelay(treasureTime() * 60 * 1000, "PlaceTreasures", {});
281 Trigger.prototype.OnOwnershipChanged = function(data)
283         if (data.entity == this.playerCivicCenter[data.from])
284         {
285                 this.playerCivicCenter[data.from] = undefined;
287                 TriggerHelper.DefeatPlayer(
288                         data.from,
289                         markForTranslation("%(player)s has been defeated (lost civic center)."));
290         }
291         else if (data.entity == this.treasureFemale[data.from])
292         {
293                 this.treasureFemale[data.from] = undefined;
294                 Engine.DestroyEntity(data.entity);
295         }
300         let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
302         cmpTrigger.treasureFemale = [];
303         cmpTrigger.playerCivicCenter = [];
304         cmpTrigger.gaiaHeroes = [];
306         cmpTrigger.RegisterTrigger("OnInitGame", "InitSurvival", { "enabled": true });
307         cmpTrigger.RegisterTrigger("OnOwnershipChanged", "OnOwnershipChanged", { "enabled": true });