Changeset View
Standalone View
binaries/data/mods/public/maps/random/danubius_triggers.js
- This file was added.
// Ships respawn every few minutes, attack the closest warships, then patrol the sea. | |||||
// To prevent unlimited spawning of ships, no more than the amount of ships intended at a given time are spawned. | |||||
// Ships are filled or refilled with new units. | |||||
// The number ships, number of units per ship, as well as ratio of siege engines, champion and heroes | |||||
// increases with time, while keeping an individual and randomized composition for each ship. | |||||
// Each hero exists at most once per map. | |||||
// Every few minutes, equal amount of ships unload units at the sides of the river unless | |||||
// one side of the river was wiped from players. | |||||
// Siege engines attack defensive structures, units attack units then patrol that side of the river. | |||||
const showDebugLog = true; | |||||
var shipTemplate = "gaul_ship_trireme"; | |||||
var siegeTemplate = "gaul_mechanical_siege_ram"; | |||||
var heroTemplates = [ | |||||
"gaul_hero_britomartus", | |||||
bb: shouldn't we trace those from templateManager? so if we (or a modder) add a new hero it could… | |||||
Not Done Inline ActionsOriginally did for heroes, but removed. If we do that, we should do it with all templates then. Then we still need to define all the classes, which might indeed be better for mods and future changes. elexis: Originally did for heroes, but removed. If we do that, we should do it with all templates then. | |||||
"gaul_hero_vercingetorix", | |||||
"gaul_hero_brennus" | |||||
]; | |||||
var femaleTemplate = "gaul_support_female_citizen"; | |||||
var healerTemplate = "gaul_support_healer_b"; | |||||
var citizenInfantryTemplates = [ | |||||
"gaul_infantry_javelinist_b", | |||||
"gaul_infantry_spearman_b", | |||||
"gaul_infantry_slinger_b" | |||||
]; | |||||
var citizenCavalryTemplates = [ | |||||
"gaul_cavalry_javelinist_b", | |||||
"gaul_cavalry_swordsman_b" | |||||
]; | |||||
var citizenTemplates = [...citizenInfantryTemplates, ...citizenCavalryTemplates]; | |||||
var championInfantryTemplates = [ | |||||
"gaul_champion_fanatic", | |||||
"gaul_champion_infantry" | |||||
]; | |||||
var championCavalryTemplates = [ | |||||
"gaul_champion_cavalry" | |||||
]; | |||||
var championTemplates = [...championInfantryTemplates, ...championCavalryTemplates]; | |||||
var ccDefenders = [ | |||||
{ "count": 8, "template": "units/" + pickRandom(citizenInfantryTemplates) }, | |||||
{ "count": 8, "template": "units/" + pickRandom(championInfantryTemplates) }, | |||||
{ "count": 4, "template": "units/" + pickRandom(championCavalryTemplates) }, | |||||
{ "count": 4, "template": "units/" + healerTemplate }, | |||||
{ "count": 5, "template": "units/" + femaleTemplate }, | |||||
{ "count": 10, "template": "gaia/fauna_sheep" } | |||||
]; | |||||
Done Inline Actionsbb: {F113092} | |||||
var gallicBuildingGarrison = [ | |||||
{ | |||||
"buildings": ["House"], | |||||
"units": [femaleTemplate, healerTemplate] | |||||
}, | |||||
{ | |||||
"buildings": ["CivCentre", "Temple"], | |||||
"units": championTemplates | |||||
}, | |||||
{ | |||||
"buildings": ["DefenseTower", "Outpost"], | |||||
"units": championInfantryTemplates | |||||
} | |||||
]; | |||||
/** | |||||
* Notice if gaia becomes too strong, players will just turtle and try to outlast the players on the other side. | |||||
* However we want interaction and fights between the teams. | |||||
* This can be accomplished by not wiping out players buildings entirely. | |||||
*/ | |||||
/** | |||||
* Time between two consecutive waves. | |||||
*/ | |||||
Done Inline Actions"after t minutes" what is t? bb: "after t minutes" what is `t`? | |||||
Not Done Inline ActionsA missing unused argument that would be nice to use, but needs gameplay testing, consideration and might even depend on a gamesetup option elexis: A missing unused argument that would be nice to use, but needs gameplay testing, consideration… | |||||
var shipRespawnTime = () => randFloat(8, 10); | |||||
/** | |||||
* Limit of ships on the map when spawning them. | |||||
* Have at least two ships, so that both sides will be visited. | |||||
*/ | |||||
var shipCount = (t, numPlayers) => Math.max(2, Math.round(Math.min(1.5, t / 10) * numPlayers)); | |||||
/** | |||||
* Order all ships to ungarrison at the shoreline. | |||||
*/ | |||||
var shipUngarrisonInterval = () => randFloat(5, 7); | |||||
/** | |||||
* Time between refillings of all ships with new soldiers. | |||||
*/ | |||||
var shipFillInterval = () => randFloat(4, 5); | |||||
/** | |||||
* Total count of gaia attackers per shipload. | |||||
*/ | |||||
Done Inline Actions"at that time" => "at t minutes" bb: "at that time" => "at t minutes" | |||||
var attackersPerShip = t => Math.min(30, Math.round(t * 2)); | |||||
Done Inline Actionsat the first wave (when t == 0) then there won't be any garrison, perhaps add a constant. bb: at the first wave (when t == 0) then there won't be any garrison, perhaps add a constant. | |||||
Not Done Inline Actions0 at time 0 intended elexis: 0 at time 0 intended | |||||
/** | |||||
* Likelihood of adding a non-existing hero at t minutes. | |||||
*/ | |||||
Done Inline Actionssiege added already too bb: siege added already too | |||||
var heroProbability = t => Math.max(0, Math.min(1, (t - 25) / 60)); | |||||
/** | |||||
* Percent of healers to add per shipload after potentially adding a hero and siege engines. | |||||
*/ | |||||
var healerRatio = t => randFloat(0, 0.1); | |||||
/** | |||||
* Percent of siege engines to add per shipload. | |||||
*/ | |||||
var siegeRatio = t => t < 8 ? 0 : randFloat(0.03, 0.06); | |||||
/** | |||||
* Percent of champions to be added after spawning heroes, healers and siege engines. | |||||
* Rest will be citizen soldiers. | |||||
*/ | |||||
Done Inline Actionsland units don't attack ships right? s/ships/units bb: land units don't attack ships right? s/ships/units | |||||
Not Done Inline Actionsgood catch elexis: good catch | |||||
var championRatio = t => Math.min(1, Math.max(0, (t - 25) / 75)); | |||||
/** | |||||
* Ships and land units will queue attack orders for this amount of closest units. | |||||
*/ | |||||
var targetCount = 3; | |||||
/** | |||||
* Number of trigger points to patrol when not having enemies to attack. | |||||
*/ | |||||
var patrolCount = 5; | |||||
/** | |||||
* Which units ships should focus when attacking and patroling. | |||||
*/ | |||||
var shipTargetClass = "WarShip"; | |||||
Done Inline Actionsshouldn't they also try to attack docks and trade and fishing ships? bb: shouldn't they also try to attack docks and trade and fishing ships? | |||||
Not Done Inline ActionsEntirely don't care about fishing ship (in particular it's about priorization, though the code complexity could be increased too to account for that, don't see the point though). I've seen ships staying at docks too, just not staying focused on it. elexis: Entirely don't care about fishing ship (in particular it's about priorization, though the code… | |||||
/** | |||||
* Which entities siege engines should focus when attacking and patroling. | |||||
*/ | |||||
Done Inline Actionsincorrect and keep sync with style above bb: incorrect and keep sync with style above | |||||
var siegeTargetClass = "Defensive"; | |||||
/** | |||||
* Which entities units should focus when attacking and patroling. | |||||
*/ | |||||
var unitTargetClass = "Unit -Ship"; | |||||
/** | |||||
* Ungarrison ships when being in this range of the target. | |||||
*/ | |||||
var shipUngarrisonDistance = 50; | |||||
/** | |||||
Done Inline Actionsname as formationProbablity bb: name as `formationProbablity` | |||||
* Currently formations are not working properly and enemies in vision range are often ignored. | |||||
* So only have a small chance of using formations. | |||||
*/ | |||||
var formationProbability = 0.2; | |||||
var unitFormations = [ | |||||
"box", | |||||
"battle_line", | |||||
"line_closed", | |||||
"column_closed" | |||||
]; | |||||
/** | |||||
* Chance for the units at the meeting place to participate in the ritual. | |||||
*/ | |||||
var ritualProbability = 0.75; | |||||
/** | |||||
* Units celebrating at the meeting place will perform one of these animations | |||||
* if idle and switch back when becoming idle again. | |||||
*/ | |||||
var ritualAnimations = { | |||||
"female": ["attack_slaughter"], | |||||
"male": ["attack_capture", "promotion", "attack_slaughter"], | |||||
"healer": ["attack_capture", "promotion", "heal"] | |||||
}; | |||||
var triggerPointShipSpawn = "A"; | |||||
var triggerPointShipPatrol = "B"; | |||||
var triggerPointUngarrisonLeft = "C"; | |||||
var triggerPointUngarrisonRight = "D"; | |||||
var triggerPointLandPatrolLeft = "E"; | |||||
var triggerPointLandPatrolRight = "F"; | |||||
/** | |||||
* Which playerID to use for the opposing gallic reinforcements. | |||||
Done Inline Actionsiirc survival has this function too, perhaps time for a global function, but out of scope bb: iirc survival has this function too, perhaps time for a global function, but out of scope | |||||
Not Done Inline ActionsIIRC polar sea too elexis: IIRC polar sea too | |||||
*/ | |||||
var gaulPlayer = 0; | |||||
Trigger.prototype.debugLog = function(txt) | |||||
{ | |||||
if (showDebugLog) | |||||
print( | |||||
"DEBUG [" + | |||||
Math.round(Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime() / 60 / 1000) + "] " + txt + "\n"); | |||||
}; | |||||
Done Inline ActionsPerhaps place this in random.js. under GetRandomAmounts(array, count) bb: Perhaps place this in random.js. under `GetRandomAmounts(array, count)` | |||||
Not Done Inline ActionsSince this should be used by survival too, could be done in a separate diff, especially since people don't like new maps changing existing library code elexis: Since this should be used by survival too, could be done in a separate diff, especially since… | |||||
/** | |||||
* Return a random amount of these templates whose sum is count. | |||||
*/ | |||||
Trigger.prototype.RandomAttackerTemplates = function(templates, count) | |||||
{ | |||||
let ratios = new Array(templates.length).fill(1).map(i => randFloat(0, 1)); | |||||
let ratioSum = ratios.reduce((current, sum) => current + sum, 0); | |||||
let remainder = count; | |||||
let templateCounts = {}; | |||||
for (let i in templates) | |||||
{ | |||||
let currentCount = +i == templates.length - 1 ? remainder : Math.round(ratios[i] / ratioSum * count); | |||||
if (!currentCount) | |||||
continue; | |||||
templateCounts[templates[i]] = currentCount; | |||||
remainder -= currentCount; | |||||
} | |||||
if (remainder != 0) | |||||
warn("Not as many templates as expected: " + count + " vs " + uneval(templateCounts)); | |||||
return templateCounts; | |||||
}; | |||||
Trigger.prototype.GarrisonAllGallicBuildings = function(gaiaEnts) | |||||
{ | |||||
this.debugLog("Garrisoning all gallic buildings"); | |||||
for (let buildingGarrison of gallicBuildingGarrison) | |||||
for (let building of buildingGarrison.buildings) | |||||
this.SpawnAndGarrisonBuilding(gaiaEnts, building, buildingGarrison.units); | |||||
}; | |||||
/** | |||||
* Garrisons all targetEnts that match the targetClass with newly spawned entities of the given template. | |||||
*/ | |||||
Trigger.prototype.SpawnAndGarrisonBuilding = function(gaiaEnts, targetClass, templates) | |||||
{ | |||||
for (let gaiaEnt of gaiaEnts) | |||||
{ | |||||
let cmpIdentity = Engine.QueryInterface(gaiaEnt, IID_Identity); | |||||
Done Inline Actionsnone said a house requires a garrisonHolder (same for temple cc fort tower etc.) so check for it. bb: none said a house requires a garrisonHolder (same for temple cc fort tower etc.) so check for… | |||||
Not Done Inline ActionsThose templates do, meh, fine. Stupid mod support elexis: Those templates do, meh, fine. Stupid mod support | |||||
if (!cmpIdentity || !cmpIdentity.HasClass(targetClass)) | |||||
continue; | |||||
let cmpGarrisonHolder = Engine.QueryInterface(gaiaEnt, IID_GarrisonHolder); | |||||
if (!cmpGarrisonHolder) | |||||
continue; | |||||
Done Inline Actionsif one hates braces and likes too long lines this can be inlined. also AddEnt()? Probably worth it adding a triggerHelper function that creates the ents with owner but doesn't spawn it for footprint. bb: if one hates braces and likes too long lines this can be inlined.
also `AddEnt()`? Probably… | |||||
Not Done Inline Actions120 characters is my tolerance limit, this one has 121, so ok. In particular consistent with below elexis: 120 characters is my tolerance limit, this one has 121, so ok. In particular consistent with… | |||||
let unitCounts = this.RandomAttackerTemplates(templates, cmpGarrisonHolder.GetCapacity()); | |||||
this.debugLog("Garrisoning " + uneval(unitCounts) + " at " + targetClass); | |||||
for (let template in unitCounts) | |||||
for (let newEnt of TriggerHelper.SpawnUnits(gaiaEnt, "units/" + template, unitCounts[template], gaulPlayer)) | |||||
Engine.QueryInterface(gaiaEnt, IID_GarrisonHolder).Garrison(newEnt); | |||||
} | |||||
}; | |||||
/** | |||||
* Spawn units of the template at each gaia Civic Center and set them to defensive. | |||||
*/ | |||||
Trigger.prototype.SpawnCCDefenders = function(gaiaEnts) | |||||
{ | |||||
this.debugLog("To defend CCs, spawning " + uneval(ccDefenders)); | |||||
for (let gaiaEnt of gaiaEnts) | |||||
{ | |||||
let cmpIdentity = Engine.QueryInterface(gaiaEnt, IID_Identity); | |||||
if (!cmpIdentity || !cmpIdentity.HasClass("CivCentre")) | |||||
continue; | |||||
for (let ccDefender of ccDefenders) | |||||
for (let ent of TriggerHelper.SpawnUnits(gaiaEnt, ccDefender.template, ccDefender.count, gaulPlayer)) | |||||
Engine.QueryInterface(ent, IID_UnitAI).SwitchToStance("defensive"); | |||||
} | |||||
}; | |||||
/** | |||||
* Remember most Humans present at the beginning of the match (before spawning any unit) and | |||||
* make them defensive. | |||||
*/ | |||||
Trigger.prototype.StartCelticRitual = function(gaiaEnts) | |||||
{ | |||||
for (let ent of gaiaEnts) | |||||
{ | |||||
let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); | |||||
if (!cmpIdentity || !cmpIdentity.HasClass("Human")) | |||||
continue; | |||||
if (randBool(ritualProbability)) | |||||
this.ritualEnts.push(ent); | |||||
let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); | |||||
if (cmpUnitAI) | |||||
cmpUnitAI.SwitchToStance("defensive"); | |||||
} | |||||
this.DoRepeatedly(5 * 1000, "UpdateCelticRitual", {}); | |||||
}; | |||||
/** | |||||
* Play one of the given animations for most participants if and only if they are idle. | |||||
*/ | |||||
Trigger.prototype.UpdateCelticRitual = function() | |||||
{ | |||||
for (let ent of this.ritualEnts) | |||||
{ | |||||
let cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); | |||||
Done Inline Actionsalready checked for cmpIdentity so correct bb: already checked for cmpIdentity so correct | |||||
if (!cmpUnitAI || cmpUnitAI.GetCurrentState() != "INDIVIDUAL.IDLE") | |||||
continue; | |||||
let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); | |||||
if (!cmpIdentity) | |||||
continue; | |||||
Done Inline ActionsNoone ever said a unit requires a visual component, also not when its spawned and putted in some array with units. But then it would greatly crash for no real reason. bb: Noone ever said a unit requires a visual component, also not when its spawned and putted in… | |||||
Not Done Inline ActionsIf they don't have a visual component, what are they doing on the map? elexis: If they don't have a visual component, what are they doing on the map?
It would crash so… | |||||
let animations = ritualAnimations[ | |||||
cmpIdentity.HasClass("Healer") ? "healer" : | |||||
cmpIdentity.HasClass("Female") ? "female" : "male"]; | |||||
let cmpVisual = Engine.QueryInterface(ent, IID_Visual); | |||||
Done Inline Actionsbad name as also used for ritualents bb: bad name as also used for ritualents | |||||
Not Done Inline Actionsoyes. Since we're likely running into naming conflicts, DanubiusOwnershipChange? elexis: oyes. Since we're likely running into naming conflicts, DanubiusOwnershipChange? | |||||
Done Inline ActionsAFAIK OnOwnershipChange function are always placed on the bottom of the file, just above the trigger definitions bb: AFAIK OnOwnershipChange function are always placed on the bottom of the file, just above the… | |||||
if (!cmpVisual) | |||||
continue; | |||||
if (animations.indexOf(cmpVisual.GetAnimationName()) == -1) | |||||
cmpVisual.SelectAnimation(pickRandom(animations), false, 1, ""); | |||||
} | |||||
}; | |||||
Done Inline ActionsStore index, used twice bb: Store index, used twice | |||||
Not Done Inline Actionscheck, nice elexis: check, nice | |||||
/** | |||||
* Spawn ships with a unique attacker composition each until | |||||
* the number of ships is reached that is supposed to exist at the given time. | |||||
*/ | |||||
Done Inline Actionsstore the index since it is used twice bb: store the index since it is used twice | |||||
Trigger.prototype.SpawnShips = function() | |||||
{ | |||||
Done Inline Actionswe don't garrison them here anymore bb: we don't garrison them here anymore | |||||
Not Done Inline ActionsO yes elexis: O yes | |||||
let time = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime() / 60 / 1000; | |||||
let numPlayers = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetNumPlayers(); | |||||
let shipSpawnCount = shipCount(time, numPlayers) - this.ships.length; | |||||
this.debugLog("Spawning " + shipSpawnCount + " ships"); | |||||
while (this.ships.length < shipSpawnCount) | |||||
this.ships.push(TriggerHelper.SpawnUnits(pickRandom(this.GetTriggerPoints(triggerPointShipSpawn)), "units/" + shipTemplate, 1, gaulPlayer)[0]); | |||||
Done Inline Actionsnuke vars, only used once bb: nuke vars, only used once | |||||
Not Done Inline Actionsmeh, might also want to add a check whether the component exists :P elexis: meh, might also want to add a check whether the component exists :P | |||||
for (let ship of this.ships) | |||||
this.AttackAndPatrol([ship], shipTargetClass, triggerPointShipPatrol, "Ships"); | |||||
this.DoAfterDelay(shipRespawnTime() * 60 * 1000, "SpawnShips", {}); | |||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | |||||
cmpTimer.CancelTimer(this.fillShipsTimer); | |||||
this.FillShips(); | |||||
}; | |||||
Trigger.prototype.FillShips = function() | |||||
{ | |||||
let time = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime() / 60 / 1000; | |||||
let attackerCount = attackersPerShip(time); | |||||
for (let ship of this.ships) | |||||
{ | |||||
let cmpGarrisonHolder = Engine.QueryInterface(ship, IID_GarrisonHolder); | |||||
if (!cmpGarrisonHolder) | |||||
Done Inline Actionswarship without garrisonHolder isn't that unrealistic, something similar to fireships... (those have one currently iirc). And what would it hurt to be nice for modders? 2 lines of code... bb: warship without garrisonHolder isn't that unrealistic, something similar to fireships... (those… | |||||
continue; | |||||
let remainder = Math.max(0, attackerCount - cmpGarrisonHolder.GetEntities().length); | |||||
let toSpawn = []; | |||||
let siegeCount = Math.round(siegeRatio(time) * remainder); | |||||
if (siegeCount) | |||||
toSpawn.push({ "template": siegeTemplate, "count": siegeCount }); | |||||
remainder -= siegeCount; | |||||
Done Inline Actionsimo do hero first, then siege bb: imo do hero first, then siege
also update the JsDocs on top when changing | |||||
Not Done Inline ActionsNah, the siegeRatio is the most important part in the balancing consideration IMO elexis: Nah, the siegeRatio is the most important part in the balancing consideration IMO | |||||
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; | |||||
} | |||||
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; | |||||
} | |||||
let citizenTemplateCounts = this.RandomAttackerTemplates(citizenTemplates, remainder); | |||||
for (let template in citizenTemplateCounts) | |||||
{ | |||||
let count = citizenTemplateCounts[template]; | |||||
toSpawn.push({ "template": template, "count": count }); | |||||
remainder -= count; | |||||
} | |||||
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, | |||||
// needlessly trying all spawn points near the ships footprint which all fail | |||||
for (let i = 0; i < spawn.count; ++i) | |||||
{ | |||||
let ent = Engine.AddEntity("units/" + spawn.template); | |||||
Engine.QueryInterface(ent, IID_Ownership).SetOwner(gaulPlayer); | |||||
if (spawn.hero) | |||||
this.heroes.push({ "template": spawn.template, "ent": ent }); | |||||
cmpGarrisonHolder.Garrison(ent); | |||||
} | |||||
Done Inline Actionsunneeded braces bb: unneeded braces | |||||
Not Done Inline Actionstechnically yes, but would look odd with that comment and without a newline elexis: technically yes, but would look odd with that comment and without a newline | |||||
} | |||||
} | |||||
Done Inline Actionsmerge this loop with the loop above, useless duplication here bb: merge this loop with the loop above, useless duplication here | |||||
Not Done Inline Actionsmust be a relict, good catch. No complaint about duplicate garrisonholder getter? elexis: must be a relict, good catch.
No complaint about duplicate garrisonholder getter? | |||||
this.fillShipsTimer = this.DoAfterDelay(shipFillInterval() * 60 * 1000, "FillShips", {}); | |||||
}; | |||||
/** | |||||
* Attack the closest enemy ships around, then patrol the sea. | |||||
*/ | |||||
Trigger.prototype.AttackAndPatrol = function(attackers, targetClass, triggerPointRef, debugName) | |||||
Not Done Inline Actionsperhaps make targetClass => targetClasses an array made in call and putted directly into the command bb: perhaps make targetClass => targetClasses an array made in call and putted directly into the… | |||||
{ | |||||
if (!attackers.length) | |||||
return; | |||||
let allTargets = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetNonGaiaEntities().filter(ent => { | |||||
let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); | |||||
return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), targetClass); | |||||
}); | |||||
let targets = allTargets.sort((ent1, ent2) => | |||||
DistanceBetweenEntities(attackers[0], ent1) - DistanceBetweenEntities(attackers[0], ent2)).slice(0, targetCount); | |||||
Done Inline Actionsshouldn't we use squared distances here? bb: shouldn't we use squared distances here? | |||||
Not Done Inline ActionsMust be a performance concern. Should use a new function then, refs D185 elexis: Must be a performance concern. Should use a new function then, refs D185 | |||||
this.debugLog(debugName + " " + uneval(attackers) + " attack " + uneval(targets)); | |||||
ProcessCommand(gaulPlayer, { | |||||
"type": "stance", | |||||
"entities": attackers, | |||||
"name": "violent", | |||||
"queued": true | |||||
Done Inline Actionstrailling comma why is the command queued? doesn't seem to be a command blocking this. bb: trailling comma
why is the command queued? doesn't seem to be a command blocking this. | |||||
Not Done Inline Actionsprior unload command that should really not be overwritten? elexis: prior unload command that should really not be overwritten? | |||||
}); | |||||
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)); | |||||
for (let patrolTarget of patrolTargets) | |||||
{ | |||||
let pos = Engine.QueryInterface(patrolTarget, IID_Position).GetPosition2D(); | |||||
ProcessCommand(gaulPlayer, { | |||||
"type": "patrol", | |||||
"entities": attackers, | |||||
"x": pos.x, | |||||
"z": pos.y, | |||||
"targetClasses": { | |||||
"attack": [targetClass] | |||||
}, | |||||
"queued": true, | |||||
"allowCapture": false | |||||
}); | |||||
Done Inline Actionsintentation, whole block has tab too many bb: intentation, whole block has tab too many | |||||
} | |||||
}; | |||||
/** | |||||
* Order all ships to abort naval warfare and move to the shoreline all few minutes. | |||||
*/ | |||||
Trigger.prototype.UngarrisonShipsOrder = function() | |||||
{ | |||||
// To avoid unloading unlimited amounts of units on empty riversides, | |||||
// only ungarrison on riversides where player buildings exist | |||||
let ungarrisonLeft = false; | |||||
let ungarrisonRight = false; | |||||
let mapSize = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain).GetTilesPerSide() * 4; | |||||
for (let ent of Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetNonGaiaEntities()) | |||||
{ | |||||
let cmpIdentity = Engine.QueryInterface(ent, IID_Identity); | |||||
if (!cmpIdentity || !cmpIdentity.HasClass("Structure")) | |||||
continue; | |||||
if (Engine.QueryInterface(ent, IID_Position).GetPosition2D().x < mapSize / 2) | |||||
ungarrisonLeft = true; | |||||
else | |||||
ungarrisonRight = true; | |||||
if (ungarrisonLeft && ungarrisonRight) | |||||
break; | |||||
} | |||||
if (!ungarrisonLeft && !ungarrisonRight) | |||||
return; | |||||
// Determine which ships should ungarrison on which side of the river | |||||
let shipsLeft = []; | |||||
Done Inline Actionssniff, once removed it won't come back... bb: sniff, once removed it won't come back...
(add them on new garrison wave) | |||||
Not Done Inline ActionsRight! Someone could survive on an island or the water and rebuild later. But better not cancelling the timer in the first place elexis: Right! Someone could survive on an island or the water and rebuild later.
But better not… | |||||
let shipsRight = []; | |||||
if (ungarrisonLeft && ungarrisonRight) | |||||
{ | |||||
shipsLeft = shuffleArray(this.ships).slice(0, Math.round(this.ships.length / 2)); | |||||
shipsRight = this.ships.filter(ship => shipsLeft.indexOf(ship) == -1); | |||||
} | |||||
else if (ungarrisonLeft) | |||||
shipsLeft = this.ships; | |||||
else if (ungarrisonRight) | |||||
shipsRight = this.ships; | |||||
// Determine which ships should ungarrison and patrol at which trigger point names | |||||
let sides = []; | |||||
if (shipsLeft.length) | |||||
sides.push({ | |||||
"ships": shipsLeft, | |||||
"ungarrisonPointRef": triggerPointUngarrisonLeft, | |||||
"landPointRef": triggerPointLandPatrolLeft | |||||
}); | |||||
Done Inline Actionsperhaps cleaner to test for shipsLeft.length instead, also for right bb: perhaps cleaner to test for shipsLeft.length instead, also for right | |||||
Not Done Inline Actionss/cleaner/actually functional elexis: s/cleaner/actually functional | |||||
if (shipsRight.length) | |||||
sides.push({ | |||||
"ships": shipsRight, | |||||
"ungarrisonPointRef": triggerPointUngarrisonRight, | |||||
"landPointRef": triggerPointLandPatrolRight | |||||
}); | |||||
// Order those ships to move to a randomly chosen trigger point on the determined | |||||
// side of the river. Remember that chosen ungarrison point and the name of the | |||||
// trigger points where the ungarrisoned units should patrol afterwards. | |||||
for (let side of sides) | |||||
for (let ship of side.ships) | |||||
{ | |||||
let ungarrisonPoint = pickRandom(this.GetTriggerPoints(side.ungarrisonPointRef)); | |||||
Done Inline Actionstoo long line split comment over 3 lines, add period, s/then/afterwards bb: too long line split comment over 3 lines, add period, s/then/afterwards | |||||
let ungarrisonPos = Engine.QueryInterface(ungarrisonPoint, IID_Position).GetPosition2D(); | |||||
this.debugLog("Ship " + ship + " will ungarrison at " + side.ungarrisonPointRef + | |||||
" (" + ungarrisonPos.x + "," + ungarrisonPos.y + ")"); | |||||
Engine.QueryInterface(ship, IID_UnitAI).Walk(ungarrisonPos.x, ungarrisonPos.y, false); | |||||
this.shipTarget[ship] = { "landPointRef": side.landPointRef, "ungarrisonPoint": ungarrisonPoint }; | |||||
} | |||||
this.DoAfterDelay(shipUngarrisonInterval() * 60 * 1000, "UngarrisonShipsOrder", {}); | |||||
}; | |||||
/** | |||||
* Check frequently whether the ships are close enough to unload at the shoreline. | |||||
*/ | |||||
Trigger.prototype.CheckShipRange = function() | |||||
{ | |||||
Done Inline Actionsthe first part works, but second is bugged (better not done now) bb: the first part works, but second is bugged (better not done now) | |||||
Not Done Inline ActionsHm, removing that comment which I had intended to workaround once apparently. elexis: Hm, removing that comment which I had intended to workaround once apparently. | |||||
for (let ship of this.ships) | |||||
{ | |||||
if (!this.shipTarget[ship] || DistanceBetweenEntities(ship, this.shipTarget[ship].ungarrisonPoint) > shipUngarrisonDistance) | |||||
continue; | |||||
let cmpGarrisonHolder = Engine.QueryInterface(ship, IID_GarrisonHolder); | |||||
if (!cmpGarrisonHolder) | |||||
continue; | |||||
Done Inline Actionscheck for him as before bb: check for him as before | |||||
let attackers = cmpGarrisonHolder.GetEntities(); | |||||
let siegeEngines = attackers.filter(ent => Engine.QueryInterface(ent, IID_Identity).HasClass("Siege")); | |||||
let others = attackers.filter(ent => siegeEngines.indexOf(ent) == -1); | |||||
this.debugLog("Ungarrisoning ship " + ship + " at " + uneval(this.shipTarget[ship])); | |||||
cmpGarrisonHolder.UnloadAll(); | |||||
if (randBool(formationProbability)) | |||||
ProcessCommand(gaulPlayer, { | |||||
"type": "formation", | |||||
"entities": others, | |||||
"name": "formations/" + pickRandom(unitFormations) | |||||
}); | |||||
this.AttackAndPatrol(siegeEngines, siegeTargetClass, this.shipTarget[ship].landPointRef, "Siege"); | |||||
this.AttackAndPatrol(others, unitTargetClass, this.shipTarget[ship].landPointRef, "Units"); | |||||
delete this.shipTarget[ship]; | |||||
this.AttackAndPatrol([ship], shipTargetClass, triggerPointShipPatrol, "Ships"); | |||||
Done Inline Actionsafter ungarisson, the ship should patrol again bb: after ungarisson, the ship should patrol again | |||||
Not Done Inline ActionsAdding a AttackAndPatrol call, but it will mean that we will queue duplicate AttackAndPatrol orders once the timeout kicks in again. Not a big issue since the ungarrison walkcommand thing will reset the order queue more frequently than queueing new orders, so we will never have more than twice those patrol+stance things queued. Adding a check for the unitAI order queue sounds bad and ideally we should avoid making assumptions about the sim as far as possible to reduce maintenance cost as far as possible elexis: Adding a `AttackAndPatrol` call, but it will mean that we will queue duplicate AttackAndPatrol… | |||||
Done Inline Actionscould simply be OnOwnershipChange as is in sotf. bb: could simply be OnOwnershipChange as is in sotf. | |||||
Not Done Inline Actions(In case I didn't post it already) Yes, but we might launch many other trigger scripts that all subscribe to ownership changes, so should really avoid naming conflicts here. At some point we need a custom prototype or something. elexis: (In case I didn't post it already) Yes, but we might launch many other trigger scripts that all… | |||||
} | |||||
}; | |||||
Trigger.prototype.DanubiusOwnershipChange = function(data) | |||||
Done Inline Actionswhy 2 newlines? bb: why 2 newlines? | |||||
Not Done Inline ActionsIn accordance with every other trigger script file I put my hands on. To keep it notably different from the function scopes. elexis: In accordance with every other trigger script file I put my hands on. To keep it notably… | |||||
{ | |||||
if (data.to != -1) | |||||
return; | |||||
let shipIdx = this.ships.indexOf(data.entity); | |||||
if (shipIdx != -1) | |||||
{ | |||||
this.debugLog("Ship " + data.entity + " sunk"); | |||||
this.ships.splice(shipIdx, 1); | |||||
} | |||||
Done Inline Actionswhen are killed heroes removed from this list? bb: when are killed heroes removed from this list? | |||||
Not Done Inline Actions:-S. added elexis: :-S. added | |||||
let ritualIdx = this.ritualEnts.indexOf(data.entity); | |||||
if (ritualIdx != -1) | |||||
this.ritualEnts.splice(ritualIdx, 1); | |||||
let heroIdx = this.heroes.findIndex(hero => hero.ent == data.entity); | |||||
if (ritualIdx != -1) | |||||
this.heroes.splice(heroIdx, 1); | |||||
}; | |||||
Done Inline Actionsnope wrong, wave will come in a set time (that can differ a bit per match), but every interval within a match should be different, so we gotta do this the old-fashioned way with calling the function from within the function with a delay. bb: nope wrong, wave will come in a set time (that can differ a bit per match), but every interval… | |||||
Not Done Inline Actions-.- who said we have to differ this each time wave instead of each match? but yes, those globals should not be functions then elexis: -.- who said we have to differ this each time wave instead of each match? but yes, those… | |||||
Done Inline Actionscorrect. bb: correct. | |||||
Done Inline Actionsalso wrong as above bb: also wrong as above | |||||
{ | |||||
let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); | |||||
Done Inline Actionsadd newline in end bb: add newline in end | |||||
Not Done Inline Actionsexists elexis: exists | |||||
Not Done Inline Actionsmy arc is yelling about it though... bb: my arc is yelling about it though... | |||||
Not Done Inline Actionsbut there is a newline at the end of the file? elexis: but there is a newline at the end of the file?
Perhaps it's rather complaining about the… | |||||
let gaiaEnts = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetEntitiesByPlayer(0); | |||||
cmpTrigger.ritualEnts = []; | |||||
// To prevent spawning more than the limits, track IDs of current entities | |||||
cmpTrigger.ships = []; | |||||
cmpTrigger.heroes = []; | |||||
// 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.SpawnShips(); | |||||
cmpTrigger.DoAfterDelay(shipUngarrisonInterval() * 60 * 1000, "UngarrisonShipsOrder", {}); | |||||
cmpTrigger.DoRepeatedly(5 * 1000, "CheckShipRange", {}); | |||||
cmpTrigger.RegisterTrigger("OnOwnershipChanged", "DanubiusOwnershipChange", { "enabled": true }); | |||||
} |
shouldn't we trace those from templateManager? so if we (or a modder) add a new hero it could be spawned.
Perhaps also good for the infantry/cav/champs/etc.
maybe even for woman and healers