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.
8 * If set to true, it will print how many templates would be spawned if the players were not defeated.
13 * If enabled, prints the number of units to the command line output.
15 const showDebugLog = false;
17 // TODO: harass attackers
19 // TODO: use Object.assign to remove redundancy
21 var jebelBarkal_rank = "Advanced";
24 * These are the templates spawned at the gamestart and during the game.
26 var jebelBarkal_templates = deepfreeze({
27 "heroes": TriggerHelper.GetTemplateNamesByClasses("Hero", "kush", undefined, jebelBarkal_rank, true),
28 "champions": TriggerHelper.GetTemplateNamesByClasses("Champion+!Elephant", "kush", undefined, jebelBarkal_rank, true),
29 "elephants": TriggerHelper.GetTemplateNamesByClasses("Champion+Elephant", "kush", undefined, jebelBarkal_rank, true),
30 "champion_infantry": TriggerHelper.GetTemplateNamesByClasses("Champion+Infantry", "kush", undefined, jebelBarkal_rank, true),
31 "champion_infantry_melee": TriggerHelper.GetTemplateNamesByClasses("Champion+Infantry+Infantry", "kush", undefined, jebelBarkal_rank, true),
32 "champion_infantry_ranged": TriggerHelper.GetTemplateNamesByClasses("Champion+Infantry+Ranged", "kush", undefined, jebelBarkal_rank, true),
33 "champion_cavalry": TriggerHelper.GetTemplateNamesByClasses("Champion+Cavalry", "kush", undefined, jebelBarkal_rank, true),
34 "citizenSoldiers": TriggerHelper.GetTemplateNamesByClasses("CitizenSoldier+Infantry", "kush", undefined, jebelBarkal_rank, true),
35 "citizenSoldier_infantry": TriggerHelper.GetTemplateNamesByClasses("CitizenSoldier+Infantry", "kush", undefined, jebelBarkal_rank, true),
36 "citizenSoldier_infantry_melee": TriggerHelper.GetTemplateNamesByClasses("CitizenSoldier+Infantry", "kush", undefined, jebelBarkal_rank, true),
37 "citizenSoldier_infantry_ranged": TriggerHelper.GetTemplateNamesByClasses("CitizenSoldier+Infantry", "kush", undefined, jebelBarkal_rank, true),
38 "citizenSoldier_cavalry": TriggerHelper.GetTemplateNamesByClasses("CitizenSoldier+Cavalry", "kush", undefined, jebelBarkal_rank, true),
39 "healers": TriggerHelper.GetTemplateNamesByClasses("Healer","kush", undefined, jebelBarkal_rank, true),
40 "females": TriggerHelper.GetTemplateNamesByClasses("FemaleCitizen","kush", undefined, jebelBarkal_rank),
41 "siege_towers": TriggerHelper.GetTemplateNamesByClasses("SiegeTower", "kush", undefined, jebelBarkal_rank)
45 * These are the formations patroling and attacking units can use.
47 * "special/formations/line_open",
48 * "special/formations/wedge",
49 * "special/formations/syntagma"
51 var jebelBarkal_formations = [
52 "special/formations/line_closed",
53 "special/formations/column_closed"
57 * Balancing helper function.
59 * @returns min0 value at the beginning of the game, min60 after an hour of gametime or longer and
60 * a proportionate number between these two values before the first hour is reached.
62 var scaleByTime = (minCurrent, min0, min60) => min0 + (min60 - min0) * Math.min(1, minCurrent / 60);
65 * @returns min0 value at the beginning of the game, min60 after an hour of gametime or longer and
66 * a proportionate number between these two values before the first hour is reached.
68 var scaleByMapSize = (min, max) => min + (max - min) * (TriggerHelper.GetMapSizeTiles() - 128) / (512 - 128);
71 * Defensive Infantry units patrol along the paths of the city.
73 var jebelBarkal_cityPatrolGroup_count = time => scaleByTime(time, 3, scaleByMapSize(3, 10));
74 var jebelBarkal_cityPatrolGroup_interval = time => scaleByTime(time, 5, 3);
75 var jebelBarkal_cityPatrolGroup_balancing = {
76 "buildingClasses": ["Wonder", "Temple", "CivCentre", "Fortress", "Barracks", "Embassy"],
77 "unitCount": time => Math.min(20, scaleByTime(time, 10, 45)),
78 "unitComposition": (time, heroes) => [
80 "templates": jebelBarkal_templates.champion_infantry_melee,
81 "frequency": scaleByTime(time, 0, 2)
84 "templates": jebelBarkal_templates.champion_infantry_ranged,
85 "frequency": scaleByTime(time, 0, 3)
88 "templates": jebelBarkal_templates.citizenSoldier_infantry_melee,
89 "frequency": scaleByTime(time, 2, 0)
92 "templates": jebelBarkal_templates.citizenSoldier_infantry_ranged,
93 "frequency": scaleByTime(time, 3, 0)
99 * Frequently the buildings spawn different units that attack the players groupwise.
101 var jebelBarkal_attackInterval = time => randFloat(5, 7);
104 * Frequently cavalry is spawned at few buildings to harass the enemy trade.
106 var jebelBarkal_harassInterval = time => randFloat(6, 8);
109 * Assume gaia to be the native kushite player.
111 var jebelBarkal_playerID = 0;
114 * Soldiers will patrol along the city grid.
116 var jebelBarkal_triggerPointPath = "A";
119 * Attacker groups approach these player buildings and attack enemies of the given classes encountered.
121 var jebelBarkal_pathTargetClasses = "CivCentre Wonder";
124 * Soldiers and siege towers prefer these targets when attacking or patroling.
126 var jebelBarkal_soldierTargetClasses = "Unit+!Ship";
129 * Elephants focus these units when attacking.
131 var jebelBarkal_elephantTargetClasses = "Defensive SiegeEngine";
134 * This defines which units are spawned and garrisoned at the gamestart per building.
136 var jebelBarkal_buildingGarrison = [
138 "buildingClasses": ["Wonder", "Temple", "CivCentre", "Fortress"],
139 "unitTemplates": jebelBarkal_templates.champions
142 "buildingClasses": ["Barracks", "Embassy"],
143 "unitTemplates": [...jebelBarkal_templates.citizenSoldiers, ...jebelBarkal_templates.champions]
146 "buildingClasses": ["DefenseTower"],
147 "unitTemplates": jebelBarkal_templates.champion_infantry
150 "buildingClasses": ["ElephantStables"],
151 "unitTemplates": jebelBarkal_templates.elephants
154 "buildingClasses": ["Stable"],
155 "unitTemplates": jebelBarkal_templates.champion_cavalry
158 "buildingClasses": ["Pyramid"],
159 "unitTemplates": [...jebelBarkal_templates.citizenSoldiers, ...jebelBarkal_templates.healers]
162 "buildingClasses": ["House"],
163 "unitTemplates": [...jebelBarkal_templates.females, ...jebelBarkal_templates.healers]
168 * This defines which units are spawned at the different buildings at the given time.
169 * The buildings are ordered by strength.
170 * Notice that there are always 2 groups of these count spawned, one for each side!
171 * The units should do a walk-attack to random player CCs
173 var jebelBarkal_attackerGroup_balancing = [
175 "buildingClasses": ["Wonder"],
176 "unitCount": time => scaleByTime(time, 0, 85),
177 "unitComposition": (time, heroes) => [
179 "templates": jebelBarkal_templates.heroes,
180 "count": randBool(scaleByTime(time, -0.5, 2)) ? 1 : 0,
181 "unique_entities": heroes
184 "templates": jebelBarkal_templates.healers,
185 "frequency": randFloat(0, 0.1)
188 "templates": jebelBarkal_templates.champions,
189 "frequency": scaleByTime(time, 0, 0.6)
192 "templates": jebelBarkal_templates.champion_infantry_ranged,
193 "frequency": scaleByTime(time, 0, 0.4)
196 "templates": jebelBarkal_templates.citizenSoldiers,
197 "frequency": scaleByTime(time, 1, 0)
200 "templates": jebelBarkal_templates.citizenSoldier_infantry_ranged,
201 "frequency": scaleByTime(time, 1, 0)
206 "buildingClasses": ["Fortress"],
207 "unitCount": time => scaleByTime(time, 0, 45),
208 "unitComposition": (time, heroes) => [
210 "templates": jebelBarkal_templates.heroes,
211 "count": randBool(scaleByTime(time, -0.5, 1.5)) ? 1 : 0,
212 "unique_entities": heroes
215 "templates": jebelBarkal_templates.champions,
216 "frequency": scaleByTime(time, 0, 1)
221 "buildingClasses": ["Temple"],
222 "unitCount": time => Math.min(45, scaleByTime(time, 0, 90)),
223 "unitComposition": (time, heroes) => [
225 "templates": jebelBarkal_templates.heroes,
226 "count": randBool(scaleByTime(time, -0.5, 1)) ? 1 : 0,
227 "unique_entities": heroes
230 "templates": jebelBarkal_templates.champion_infantry_melee,
234 "templates": jebelBarkal_templates.champion_infantry_ranged,
238 "templates": jebelBarkal_templates.healers,
239 "frequency": randFloat(0.05, 0.2)
244 "buildingClasses": ["CivCentre"],
245 "unitCount": time => Math.min(40, scaleByTime(time, 0, 80)),
246 "unitComposition": (time, heroes) => [
248 "templates": jebelBarkal_templates.heroes,
249 "count": randBool(scaleByTime(time, -0.5, 0.5)) ? 1 : 0,
250 "unique_entities": heroes
253 "templates": jebelBarkal_templates.champion_infantry,
254 "frequency": scaleByTime(time, 0, 1)
257 "templates": jebelBarkal_templates.citizenSoldiers,
258 "frequency": scaleByTime(time, 1, 0)
263 "buildingClasses": ["Stable"],
264 "unitCount": time => Math.min(30, scaleByTime(time, 0, 80)),
265 "harasserSize": time => Math.min(20, scaleByTime(time, 0, 50)),
266 "unitComposition": (time, heroes) => [
268 "templates": jebelBarkal_templates.citizenSoldier_cavalry,
269 "frequency": scaleByTime(time, 2, 0)
272 "templates": jebelBarkal_templates.champion_cavalry,
273 "frequency": scaleByTime(time, 0, 1)
278 "buildingClasses": ["Barracks", "Embassy"],
279 "unitCount": time => Math.min(35, scaleByTime(time, 0, 70)),
280 "unitComposition": (time, heroes) => [
282 "templates": jebelBarkal_templates.citizenSoldier_infantry,
288 "buildingClasses": ["ElephantStables"],
289 "unitCount": time => scaleByTime(time, 0, 10),
290 "unitComposition": (time, heroes) => [
292 "templates": jebelBarkal_templates.elephants,
299 Trigger.prototype.debugLog = function(txt)
302 print("DEBUG [" + Math.round(TriggerHelper.GetMinutes()) + "] " + txt + "\n");
305 Trigger.prototype.JebelBarkal_Init = function()
307 this.JebelBarkal_TrackUnits();
308 this.JebelBarkal_SetDefenderStance();
309 this.JebelBarkal_GarrisonBuildings();
310 this.JebelBarkal_SpawnCityPatrolGroups();
311 this.JebelBarkal_SpawnAttackerGroups();
314 Trigger.prototype.JebelBarkal_TrackUnits = function()
316 // Each item is an entity ID
317 this.jebelBarkal_heroes = [];
319 // Each item is an array of entity IDs
320 this.jebelBarkal_patrolingUnits = [];
322 // Array of entityIDs where patrol groups can spawn
323 this.jebelBarkal_patrolGroupSpawnPoints = TriggerHelper.GetPlayerEntitiesByClass(
324 jebelBarkal_playerID,
325 jebelBarkal_cityPatrolGroup_balancing.buildingClasses);
327 this.debugLog("Patrol spawn points: " + uneval(this.jebelBarkal_patrolGroupSpawnPoints));
329 // Array of entityIDs where attacker groups can spawn
330 this.jebelBarkal_attackerGroupSpawnPoints = TriggerHelper.GetPlayerEntitiesByClass(
331 jebelBarkal_playerID,
332 jebelBarkal_attackerGroup_balancing.reduce((classes, attackerSpawning) => classes.concat(attackerSpawning.buildingClasses), []));
334 this.debugLog("Attacker spawn points: " + uneval(this.jebelBarkal_attackerGroupSpawnPoints));
337 Trigger.prototype.JebelBarkal_SetDefenderStance = function()
339 for (let ent of TriggerHelper.GetPlayerEntitiesByClass(jebelBarkal_playerID, "Soldier"))
340 TriggerHelper.SetUnitStance(ent, "defensive");
343 Trigger.prototype.JebelBarkal_GarrisonBuildings = function()
345 for (let buildingGarrison of jebelBarkal_buildingGarrison)
346 TriggerHelper.SpawnAndGarrisonAtClasses(jebelBarkal_playerID, buildingGarrison.buildingClasses, buildingGarrison.unitTemplates, 1);
350 * Spawn new groups if old ones were wiped out.
352 Trigger.prototype.JebelBarkal_SpawnCityPatrolGroups = function()
354 if (!this.jebelBarkal_patrolGroupSpawnPoints.length)
357 // TODO: the patrol group size should be increased
358 let time = TriggerHelper.GetMinutes();
359 let groupCount = Math.floor(Math.max(0, jebelBarkal_cityPatrolGroup_count(time)) - this.jebelBarkal_patrolingUnits.length);
361 for (let i = 0; i < groupCount; ++i)
363 let spawnEnt = pickRandom(this.jebelBarkal_patrolGroupSpawnPoints);
365 let templateCounts = TriggerHelper.BalancedTemplateComposition(
366 jebelBarkal_cityPatrolGroup_balancing.unitComposition(time, this.jebelBarkal_heroes),
367 jebelBarkal_cityPatrolGroup_balancing.unitCount(time));
369 this.debugLog("Spawning " + groupCount + " city patrol groups, " +
370 this.jebelBarkal_patrolingUnits.length + " exist, templates:\n" + uneval(templateCounts));
372 let groupEntities = this.JebelBarkal_SpawnTemplates(spawnEnt, templateCounts);
374 this.jebelBarkal_patrolingUnits.push(groupEntities);
376 for (let ent of groupEntities)
377 TriggerHelper.SetUnitStance(ent, "defensive");
379 TriggerHelper.SetUnitFormation(jebelBarkal_playerID, groupEntities, pickRandom(jebelBarkal_formations));
381 // TODO: order waypoints
382 for (let entTriggerPoint of this.GetTriggerPoints(jebelBarkal_triggerPointPath))
384 let pos = TriggerHelper.GetEntityPosition2D(entTriggerPoint);
385 ProcessCommand(jebelBarkal_playerID, {
387 "entities": groupEntities,
391 "attack": jebelBarkal_soldierTargetClasses
394 "allowCapture": false
399 this.DoAfterDelay(jebelBarkal_cityPatrolGroup_interval(time) * 60 * 1000, "JebelBarkal_SpawnCityPatrolGroups", {});
402 Trigger.prototype.JebelBarkal_SpawnTemplates = function(spawnEnt, templateCounts)
404 let groupEntities = [];
406 for (let templateName in templateCounts)
408 let ents = TriggerHelper.SpawnUnits(spawnEnt, templateName, templateCounts[templateName], jebelBarkal_playerID);
410 groupEntities = groupEntities.concat(ents);
412 if (jebelBarkal_templates.heroes.indexOf(templateName) != -1 && ents[0])
413 this.jebelBarkal_heroes.push(ents[0]);
416 return groupEntities;
420 * Spawn a group of attackers at every remaining building.
422 Trigger.prototype.JebelBarkal_SpawnAttackerGroups = function()
424 let time = TriggerHelper.GetMinutes();
425 this.debugLog("Attacker wave");
427 let targets = TriggerHelper.GetAllPlayersEntitiesByClass(jebelBarkal_pathTargetClasses);
431 let spawnedAnything = false;
432 for (let spawnEnt of this.jebelBarkal_attackerGroupSpawnPoints)
434 let spawnPointBalancing = jebelBarkal_attackerGroup_balancing.find(balancing =>
435 TriggerHelper.EntityMatchesClassList(spawnEnt, balancing.buildingClasses));
437 let unitCount = Math.round(spawnPointBalancing.unitCount(time));
442 let templateCounts = TriggerHelper.BalancedTemplateComposition(spawnPointBalancing.unitComposition(time, this.jebelBarkal_heroes), unitCount);
444 this.debugLog("Spawning " + unitCount + " attackers at " + uneval(spawnPointBalancing.buildingClasses) + " " + spawnEnt + ":\n" + uneval(templateCounts));
449 let groupEntities = this.JebelBarkal_SpawnTemplates(spawnEnt, templateCounts);
450 spawnedAnything = true;
452 // TODO Move to some place, 30 seconds later attack
454 let isElephant = TriggerHelper.EntityMatchesClassList(groupEntities[0], "Elephant");
456 for (let ent of groupEntities)
457 TriggerHelper.SetUnitStance(ent, isElephant ? "aggressive" : "violent");
460 TriggerHelper.SetUnitFormation(jebelBarkal_playerID, groupEntities, pickRandom(jebelBarkal_formations));
462 let targetPos = TriggerHelper.GetEntityPosition2D(pickRandom(targets));
466 ProcessCommand(jebelBarkal_playerID, {
467 "type": "attack-walk",
468 "entities": groupEntities,
471 "targetClasses": isElephant ? jebelBarkal_elephantTargetClasses : jebelBarkal_soldierTargetClasses,
472 "allowCapture": false,
478 Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
479 "message": markForTranslation("Napata is attacking!"),
480 "translateMessage": true
483 this.DoAfterDelay(jebelBarkal_attackInterval(TriggerHelper.GetMinutes()) * 60 * 1000, "JebelBarkal_SpawnAttackerGroups", {});
487 * Keep track of heroes, so that each of them remains unique.
488 * Keep track of spawn points, as only there units should be spawned.
491 Trigger.prototype.JebelBarkal_OwnershipChange = function(data)
496 for (let array of [this.jebelBarkal_heroes, this.jebelBarkal_patrolGroupSpawnPoints, this.jebelBarkal_attackerGroupSpawnPoints, ...this.jebelBarkal_patrolingUnits])
498 let idx = array.indexOf(data.entity);
500 array.splice(idx, 1);
503 this.jebelBarkal_patrolingUnits = this.jebelBarkal_patrolingUnits.filter(entities => entities.length);
508 let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
509 cmpTrigger.RegisterTrigger("OnInitGame", "JebelBarkal_Init", { "enabled": true });
510 cmpTrigger.RegisterTrigger("OnOwnershipChanged", "JebelBarkal_OwnershipChange", { "enabled": true });