Index: binaries/data/mods/public/maps/random/danubius.js =================================================================== --- binaries/data/mods/public/maps/random/danubius.js +++ binaries/data/mods/public/maps/random/danubius.js @@ -7,6 +7,8 @@ const triggerPointShipUnloadRight = "special/trigger_point_D"; const triggerPointLandPatrolLeft = "special/trigger_point_E"; const triggerPointLandPatrolRight = "special/trigger_point_F"; +const triggerPointCCAttackerPatrolLeft = "special/trigger_point_G"; +const triggerPointCCAttackerPatrolRight = "special/trigger_point_H"; // Terrain textures const tRoad = "steppe_river_rocks"; @@ -123,11 +125,13 @@ const numPlayers = getNumPlayers(); const mapSize = getMapSize(); +var clMiddle = createTileClass(); var clPlayer = createTileClass(); var clForest = createTileClass(); var clWater = createTileClass(); var clLand = [createTileClass(), createTileClass()]; var clLandPatrolPoint = [createTileClass(), createTileClass()]; +var clCCAttackerPatrolPoint = [createTileClass(), createTileClass()]; var clShore = [createTileClass(), createTileClass()]; var clShoreUngarrisonPoint = [createTileClass(), createTileClass()]; var clShip = createTileClass(); @@ -157,7 +161,8 @@ var randomTreasureCount = randIntInclusive(0, 3 * numPlayers); // Place a gaia village on small maps and larger -if (mapSize >= smallMapSize) +var gallicCC = mapSize >= smallMapSize; +if (gallicCC) { log("Creating gallic villages..."); let gaulCityRadius = 12; @@ -273,7 +278,7 @@ let wall = [ "gate", "hut", "palisade_tower", "wallLong", "wallLong", "cornerIn", "defense_tower", "wallLong", "wallLong", "temple", - "palisade_tower", "wallLong", "house", "wallLong", "palisade_tower", "longhouse", "wallLong", "wallLong", + "palisade_tower", "wallLong", "house", "gate", "palisade_tower", "longhouse", "wallLong", "wallLong", "cornerIn", "defense_tower", "wallLong", "tavern", "wallLong", "palisade_tower"]; wall = wall.concat(wall); placeCustomFortress(gX, gZ, new Fortress("Geto-Dacian Tribal Confederation", wall), "gaul", 0, PI); @@ -776,7 +781,9 @@ ); log("Creating patrol points for land attackers..."); +addToClass(mapSize/2, mapSize/2, clMiddle); for (let i = 0; i < 2; ++i) +{ createObjectGroups( new SimpleGroup( [new SimpleObject( @@ -794,6 +801,35 @@ 100 ); + if (gallicCC) + createObjectGroups( + new SimpleGroup( + [new SimpleObject( + i == 0 ? triggerPointCCAttackerPatrolLeft : triggerPointCCAttackerPatrolRight, + 1, 1, + 0, 0)], + true, + clCCAttackerPatrolPoint[i]), + 0, + [ + // Don't avoid the forest, so that as many places as possible on the border are visited + avoidClasses( + clWater, 5, + clHill, 3, + clFood, 1, + clRock, 4, + clMetal, 4, + clPlayer, 15, + clGauls, 0, + clCCAttackerPatrolPoint[i], 5, + clMiddle, mapSize * 0.5 - 15), + stayClasses(clLand[i], 0) + ], + 10000, + 100 + ); +} + log("Creating water logs..."); createObjectGroups( new SimpleGroup([new SimpleObject(aWaterLog, 1, 1, 0, 0)], true, clWaterLog), Index: binaries/data/mods/public/maps/random/danubius_triggers.js =================================================================== --- binaries/data/mods/public/maps/random/danubius_triggers.js +++ binaries/data/mods/public/maps/random/danubius_triggers.js @@ -79,6 +79,16 @@ */ /** + * Time in minutes between two consecutive waves spawned from the gaia civic centers, if they still exist. + */ +var ccAttackerInterval = t => randFloat(6, 8); + +/** + * Number of attackers spawned at a civic center at t minutes ingame time. + */ +var ccAttackerCount = t => Math.min(20, Math.max(0, Math.round(t * 1.5))); + +/** * Time between two consecutive waves. */ var shipRespawnTime = () => randFloat(8, 10); @@ -189,6 +199,8 @@ var triggerPointUngarrisonRight = "D"; var triggerPointLandPatrolLeft = "E"; var triggerPointLandPatrolRight = "F"; +var triggerPointCCAttackerPatrolLeft = "G"; +var triggerPointCCAttackerPatrolRight = "H"; /** * Which playerID to use for the opposing gallic reinforcements. @@ -266,7 +278,7 @@ /** * Spawn units of the template at each gaia Civic Center and set them to defensive. */ -Trigger.prototype.SpawnCCDefenders = function(gaiaEnts) +Trigger.prototype.SpawnInitialCCDefenders = function(gaiaEnts) { this.debugLog("To defend CCs, spawning " + uneval(ccDefenders)); @@ -276,12 +288,47 @@ if (!cmpIdentity || !cmpIdentity.HasClass("CivCentre")) continue; + this.civicCenters.push(gaiaEnt); + for (let ccDefender of ccDefenders) for (let ent of TriggerHelper.SpawnUnits(gaiaEnt, ccDefender.template, ccDefender.count, gaulPlayer)) Engine.QueryInterface(ent, IID_UnitAI).SwitchToStance("defensive"); } }; +Trigger.prototype.SpawnCCAttackers = function() +{ + let time = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime() / 60 / 1000; + let mapSize = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain).GetTilesPerSide() * 4; + + for (let gaiaCC of this.civicCenters) + { + let toSpawn = this.GetAttackerComposition(ccAttackerCount(time), false); + this.debugLog("Spawning civic center attackers at " + gaiaCC + ": " + uneval(toSpawn)); + + let ccAttackers = []; + + for (let spawn of toSpawn) + { + let ents = TriggerHelper.SpawnUnits(gaiaCC, "units/" + spawn.template, spawn.count, gaulPlayer); + + if (spawn.hero && ents[0]) + this.heroes.push({ "template": spawn.template, "ent": ents[0] }); + + ccAttackers = ccAttackers.concat(ents); + } + + let patrolPointRef = Engine.QueryInterface(gaiaCC, IID_Position).GetPosition2D().x < mapSize / 2 ? + triggerPointCCAttackerPatrolLeft : + triggerPointCCAttackerPatrolRight; + + this.AttackAndPatrol(ccAttackers, unitTargetClass, patrolPointRef, "CCAttackers", false); + } + + if (this.civicCenters.length) + this.DoAfterDelay(ccAttackerInterval() * 60 * 1000, "SpawnCCAttackers", {}); +}; + /** * Remember most Humans present at the beginning of the match (before spawning any unit) and * make them defensive. @@ -359,61 +406,65 @@ this.FillShips(); }; -Trigger.prototype.FillShips = function() +Trigger.prototype.GetAttackerComposition = function(attackerCount, addSiege) { let time = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime() / 60 / 1000; - let attackerCount = attackersPerShip(time); + let toSpawn = []; + let remainder = attackerCount; - for (let ship of this.ships) + let siegeCount = addSiege ? Math.round(siegeRatio(time) * remainder) : 0; + if (siegeCount) + toSpawn.push({ "template": siegeTemplate, "count": siegeCount }); + remainder -= siegeCount; + + let heroTemplate = pickRandom(heroTemplates.filter(hTemp => this.heroes.every(hero => hTemp != hero.template))); + if (heroTemplate && remainder && randBool(heroProbability(time))) { - let cmpGarrisonHolder = Engine.QueryInterface(ship, IID_GarrisonHolder); - if (!cmpGarrisonHolder) - continue; + toSpawn.push({ "template": heroTemplate, "count": 1, "hero": true }); + --remainder; + } - let remainder = Math.max(0, attackerCount - cmpGarrisonHolder.GetEntities().length); + let healerCount = Math.round(healerRatio(time) * remainder); + if (healerCount) + toSpawn.push({ "template": healerTemplate, "count": healerCount }); + remainder -= healerCount; - let toSpawn = []; + let championCount = Math.round(championRatio(time) * remainder); + let championTemplateCounts = this.RandomAttackerTemplates(championTemplates, championCount); + for (let template in championTemplateCounts) + { + let count = championTemplateCounts[template]; + toSpawn.push({ "template": template, "count": count }); + championCount -= count; + remainder -= count; + } - let siegeCount = Math.round(siegeRatio(time) * remainder); - if (siegeCount) - toSpawn.push({ "template": siegeTemplate, "count": siegeCount }); - remainder -= siegeCount; + let citizenTemplateCounts = this.RandomAttackerTemplates(citizenTemplates, remainder); + for (let template in citizenTemplateCounts) + { + let count = citizenTemplateCounts[template]; + toSpawn.push({ "template": template, "count": count }); + remainder -= count; + } - let heroTemplate = pickRandom(heroTemplates.filter(hTemp => this.heroes.every(hero => hTemp != hero.template))); - if (heroTemplate && remainder && randBool(heroProbability(time))) - { - toSpawn.push({ "template": heroTemplate, "count": 1, "hero": true }); - --remainder; - } + if (remainder != 0) + warn("Didn't spawn as many attackers as were intended (" + remainder + " remaining)"); - let healerCount = Math.round(healerRatio(time) * remainder); - if (healerCount) - toSpawn.push({ "template": healerTemplate, "count": healerCount }); - remainder -= healerCount; - - let championCount = Math.round(championRatio(time) * remainder); - let championTemplateCounts = this.RandomAttackerTemplates(championTemplates, championCount); - for (let template in championTemplateCounts) - { - let count = championTemplateCounts[template]; - toSpawn.push({ "template": template, "count": count }); - championCount -= count; - remainder -= count; - } + return toSpawn; +}; - let citizenTemplateCounts = this.RandomAttackerTemplates(citizenTemplates, remainder); - for (let template in citizenTemplateCounts) - { - let count = citizenTemplateCounts[template]; - toSpawn.push({ "template": template, "count": count }); - remainder -= count; - } +Trigger.prototype.FillShips = function() +{ + let time = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime() / 60 / 1000; + for (let ship of this.ships) + { + let cmpGarrisonHolder = Engine.QueryInterface(ship, IID_GarrisonHolder); + if (!cmpGarrisonHolder) + continue; + let toSpawn = this.GetAttackerComposition(Math.max(0, attackersPerShip(time) - cmpGarrisonHolder.GetEntities().length), true); this.debugLog("Filling ship " + ship + " with " + uneval(toSpawn)); - if (remainder != 0) - warn("Didn't spawn as many attackers as were intended (" + remainder + " remaining)"); - for (let spawn of toSpawn) { // Don't use TriggerHelper.SpawnUnits here because that is too slow, @@ -438,7 +489,7 @@ /** * Attack the closest enemy ships around, then patrol the sea. */ -Trigger.prototype.AttackAndPatrol = function(attackers, targetClass, triggerPointRef, debugName) +Trigger.prototype.AttackAndPatrol = function(attackers, targetClass, triggerPointRef, debugName, attack = true) { if (!attackers.length) return; @@ -460,14 +511,15 @@ "queued": true }); - for (let target of targets) - ProcessCommand(gaulPlayer, { - "type": "attack", - "entities": attackers, - "target": target, - "queued": true, - "allowCapture": false - }); + if (attack) + for (let target of targets) + ProcessCommand(gaulPlayer, { + "type": "attack", + "entities": attackers, + "target": target, + "queued": true, + "allowCapture": false + }); let patrolTargets = shuffleArray(this.GetTriggerPoints(triggerPointRef)).slice(0, patrolCount); this.debugLog(debugName + " " + uneval(attackers) + " patrol to " + uneval(patrolTargets)); @@ -624,6 +676,13 @@ let heroIdx = this.heroes.findIndex(hero => hero.ent == data.entity); if (ritualIdx != -1) this.heroes.splice(heroIdx, 1); + + let ccIdx = this.civicCenters.indexOf(data.entity); + if (ccIdx != -1) + { + this.debugLog("Gaia civic center " + data.entity + " destroyed"); + this.civicCenters.splice(ccIdx, 1); + } }; @@ -638,13 +697,17 @@ cmpTrigger.ships = []; cmpTrigger.heroes = []; + // Remember gaia CCs to spawn attackers from + cmpTrigger.civicCenters = []; + // Maps from gaia ship entity ID to ungarrison trigger point entity ID and land patrol triggerpoint name cmpTrigger.shipTarget = {}; cmpTrigger.fillShipsTimer = undefined; cmpTrigger.StartCelticRitual(gaiaEnts); cmpTrigger.GarrisonAllGallicBuildings(gaiaEnts); - cmpTrigger.SpawnCCDefenders(gaiaEnts); + cmpTrigger.SpawnInitialCCDefenders(gaiaEnts); + cmpTrigger.SpawnCCAttackers(gaiaEnts); cmpTrigger.SpawnShips(); cmpTrigger.DoAfterDelay(shipUngarrisonInterval() * 60 * 1000, "UngarrisonShipsOrder", {});