Changeset View
Standalone View
binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
/** | |||||
* If set to true, it will print how many templates would be spawned if the players were not defeated. | |||||
*/ | |||||
const dryRun = false; | |||||
/** | |||||
* If enabled, prints the number of units to the command line output. | |||||
*/ | |||||
const debugLog = false; | |||||
bb: Imo the times could be randomized a bit holding 4 as average. | |||||
Not Done Inline ActionsIt influences the growth curve a lot, gotta test my formula in that spreadsheet quickly elexis: It influences the growth curve a lot, gotta test my formula in that spreadsheet quickly | |||||
/** | |||||
* Least and greatest number of minutes to pass between spawning new treasures. | |||||
*/ | |||||
var treasureTime = [3, 5]; | |||||
Done Inline ActionsMake random agnostic. bb: Make random agnostic. | |||||
/** | |||||
* When the first wave of attackers will be spawned. | |||||
bbUnsubmitted Done Inline Actions.splice(0,1 "Earliest and latest time w") bb: .splice(0,1 "Earliest and latest time w") | |||||
*/ | |||||
var firstWaveTime = [4, 6]; | |||||
Done Inline ActionsGoing to make this an array and determine the number in the synchronized part of the code. This occurance might not bug, but having randFloats called each time upon initialization of the file is asking for OOS on rejoin. elexis: Going to make this an array and determine the number in the synchronized part of the code. This… | |||||
/** | |||||
* Least and greatest number of minutes between two consecutive waves. | |||||
bbUnsubmitted Done Inline Actions/s/Least and greatest/Smallest and largest (don't use "amount", since "amount of minutes" sounds weird) bb: /s/Least and greatest/Smallest and largest
(don't use "amount", since "amount of minutes"… | |||||
*/ | |||||
var waveTime = [2, 4]; | |||||
Done Inline ActionsOne could merge these min/max into an array or object, no strong opinion. bb: One could merge these min/max into an array or object, no strong opinion. | |||||
Done Inline ActionsMuch better merging all of these! Done elexis: Much better merging all of these! Done | |||||
/** | |||||
* Roughly the number of attackers on the first wave. | |||||
*/ | |||||
var initialAttackers = 5; | |||||
/** | |||||
* Increase the number of attackers exponentially, by this percent value per minute. | |||||
*/ | |||||
var percentPerMinute = 1.05; | |||||
/** | |||||
* Greatest amount of attackers that can be spawned. | |||||
*/ | |||||
Done Inline ActionsAFAIK it is Greatest amount or Largest number bb: AFAIK it is `Greatest amount` or `Largest number` | |||||
var totalAttackerLimit = 150; | |||||
/** | |||||
* Least and greatest amount of siege engines per wave. | |||||
*/ | |||||
var siegeFraction = [0.2, 0.5]; | |||||
/** | |||||
* Potentially / definitely spawn a gaia hero after this number of minutes. | |||||
*/ | |||||
Done Inline Actionsamount of time? rly ==> number of minutes bb: amount of time? rly ==> number of minutes | |||||
Done Inline Actionstrue elexis: true | |||||
var heroTime = [20, 60]; | |||||
/** | |||||
* The following templates can't be built by any player. | |||||
*/ | |||||
Done Inline ActionsTODO AI I guess.... bb: TODO AI I guess.... | |||||
Done Inline ActionsLast time I checked it was supported, pulled through the GUIInterface elexis: Last time I checked it was supported, pulled through the GUIInterface | |||||
var disabledTemplates = (civ) => [ | var disabledTemplates = (civ) => [ | ||||
// Economic structures | // Economic structures | ||||
"structures/" + civ + "_corral", | "structures/" + civ + "_corral", | ||||
"structures/" + civ + "_farmstead", | "structures/" + civ + "_farmstead", | ||||
"structures/" + civ + "_field", | "structures/" + civ + "_field", | ||||
"structures/" + civ + "_storehouse", | "structures/" + civ + "_storehouse", | ||||
"structures/" + civ + "_rotarymill", | "structures/" + civ + "_rotarymill", | ||||
"units/maur_support_elephant", | "units/maur_support_elephant", | ||||
Show All 9 Lines | var disabledTemplates = (civ) => [ | ||||
// Shoreline | // Shoreline | ||||
"structures/" + civ + "_dock", | "structures/" + civ + "_dock", | ||||
"structures/brit_crannog", | "structures/brit_crannog", | ||||
"structures/cart_super_dock", | "structures/cart_super_dock", | ||||
"structures/ptol_lighthouse" | "structures/ptol_lighthouse" | ||||
]; | ]; | ||||
var treasures = | /** | ||||
[ | * Spawn these treasures in regular intervals. | ||||
*/ | |||||
Not Done Inline Actionsthat TODO shouldnt be in the code elexis: that TODO shouldnt be in the code | |||||
var treasures = [ | |||||
"gaia/special_treasure_food_barrel", | "gaia/special_treasure_food_barrel", | ||||
"gaia/special_treasure_food_bin", | "gaia/special_treasure_food_bin", | ||||
"gaia/special_treasure_food_crate", | "gaia/special_treasure_food_crate", | ||||
"gaia/special_treasure_food_jars", | "gaia/special_treasure_food_jars", | ||||
"gaia/special_treasure_metal", | "gaia/special_treasure_metal", | ||||
"gaia/special_treasure_stone", | "gaia/special_treasure_stone", | ||||
"gaia/special_treasure_wood", | "gaia/special_treasure_wood", | ||||
"gaia/special_treasure_wood", | "gaia/special_treasure_wood", | ||||
"gaia/special_treasure_wood" | "gaia/special_treasure_wood" | ||||
]; | ]; | ||||
var attackerEntityTemplates = | /** | ||||
[ | * An object that maps from civ [f.e. "spart"] to an object | ||||
[ | * that has the keys "champions", "siege" and "heroes", | ||||
"units/athen_champion_infantry", | * which is an array containing all these templates, | ||||
bbUnsubmitted Not Done Inline Actions-these bb: -these | |||||
elexisAuthorUnsubmitted Not Done Inline Actionsthese because it excludes some templates elexis: these because it excludes some templates | |||||
"units/athen_champion_marine", | * trainable from a building or not. | ||||
"units/athen_champion_ranged", | }*/ | ||||
bbUnsubmitted Done Inline Actions}? bb: }? | |||||
"units/athen_mechanical_siege_lithobolos_packed", | var attackerUnitTemplates = {}; | ||||
Done Inline Actionsworth a JsDoc imo bb: worth a JsDoc imo | |||||
Done Inline Actionsmeh, should be self-explanatory elexis: meh, should be self-explanatory | |||||
"units/athen_mechanical_siege_oxybeles_packed", | |||||
], | Trigger.prototype.InitSurvival = function() | ||||
[ | { | ||||
"units/brit_champion_cavalry", | this.InitStartingUnits(); | ||||
"units/brit_champion_infantry", | this.LoadAttackerTemplates(); | ||||
Done Inline Actionsdunno if ...waveTime is cleaner. bb: dunno if `...waveTime` is cleaner. | |||||
Not Done Inline ActionsDidn't like on first sight, but on second :-) elexis: Didn't like on first sight, but on second :-) | |||||
"units/brit_mechanical_siege_ram", | this.SetDisableTemplates(); | ||||
Done Inline Actionsrandom agnostic Why is there a round? isn't it allowed to have a wave after 4:33? bb: random agnostic
Why is there a round? isn't it allowed to have a wave after 4:33? | |||||
Not Done Inline ActionsAgree, much more readable with randFloat! elexis: Agree, much more readable with randFloat!
round -> I think it was in there to pass integers to… | |||||
], | this.PlaceTreasures(); | ||||
[ | this.InitializeEnemyWaves(); | ||||
"units/cart_champion_cavalry", | }; | ||||
Done Inline Actions6000 see somewhere below. bb: 6000 see somewhere below. | |||||
Done Inline Actionsfixed elexis: fixed | |||||
Done Inline ActionstotalAttackers seems to imply this, better have some docs about the way it is calculated i.o exponential with the given parameters. bb: `totalAttackers` seems to imply this, better have some docs about the way it is calculated i.o… | |||||
Not Done Inline Actionsagree elexis: agree | |||||
"units/cart_champion_elephant", | |||||
"units/cart_champion_infantry", | |||||
"units/cart_champion_pikeman", | |||||
], | |||||
[ | |||||
"units/gaul_champion_cavalry", | |||||
"units/gaul_champion_fanatic", | |||||
"units/gaul_champion_infantry", | |||||
"units/gaul_mechanical_siege_ram", | |||||
], | |||||
[ | |||||
"units/iber_champion_cavalry", | |||||
"units/iber_champion_infantry", | |||||
"units/iber_mechanical_siege_ram", | |||||
], | |||||
[ | |||||
"units/mace_champion_cavalry", | |||||
"units/mace_champion_infantry_a", | |||||
"units/mace_champion_infantry_e", | |||||
"units/mace_mechanical_siege_lithobolos_packed", | |||||
"units/mace_mechanical_siege_oxybeles_packed", | |||||
], | |||||
[ | |||||
"units/maur_champion_chariot", | |||||
"units/maur_champion_elephant", | |||||
"units/maur_champion_infantry", | |||||
"units/maur_champion_maiden", | |||||
"units/maur_champion_maiden_archer", | |||||
], | |||||
[ | |||||
"units/pers_champion_cavalry", | |||||
"units/pers_champion_infantry", | |||||
"units/pers_champion_elephant", | |||||
], | |||||
[ | |||||
"units/ptol_champion_cavalry", | |||||
"units/ptol_champion_elephant", | |||||
], | |||||
[ | |||||
"units/rome_champion_cavalry", | |||||
"units/rome_champion_infantry", | |||||
"units/rome_mechanical_siege_ballista_packed", | |||||
"units/rome_mechanical_siege_scorpio_packed", | |||||
], | |||||
[ | |||||
"units/sele_champion_cavalry", | |||||
"units/sele_champion_chariot", | |||||
"units/sele_champion_elephant", | |||||
"units/sele_champion_infantry_pikeman", | |||||
"units/sele_champion_infantry_swordsman", | |||||
], | |||||
[ | |||||
"units/spart_champion_infantry_pike", | |||||
"units/spart_champion_infantry_spear", | |||||
"units/spart_champion_infantry_sword", | |||||
"units/spart_mechanical_siege_ram", | |||||
], | |||||
]; | |||||
Done Inline Actionsinline pickrandom bb: inline pickrandom | |||||
Not Done Inline Actionsthat pickRandom has been a really useful addition, we see it in many commits nowadays elexis: that pickRandom has been a really useful addition, we see it in many commits nowadays | |||||
Trigger.prototype.StartAnEnemyWave = function() | Trigger.prototype.debugLog = function(txt) | ||||
Done Inline ActionsSince the attackers in first wave is randomized a bit here (nextWaveTime) imo there is no need in randomizing the initialAttackers also. bb: Since the attackers in first wave is randomized a bit here (`nextWaveTime`) imo there is no… | |||||
{ | { | ||||
Not Done Inline ActionsBergh, more of them in file bb: Bergh, more of them in file | |||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); | if (!debugLog) | ||||
Done Inline ActionsIf someone stupidly adds a siegeratio>1 we will get errors here. Perhaps add a Math.min or leave as is since ppl shouldn't be that stupid. bb: If someone stupidly adds a siegeratio>1 we will get errors here. Perhaps add a Math.min or… | |||||
Not Done Inline ActionsAgree on tryint to avoid stupid people and if that fails, let them have errors so that they notice their mistake instead of muting it elexis: Agree on tryint to avoid stupid people and if that fails, let them have errors so that they… | |||||
let attackerTemplates = attackerEntityTemplates[Math.floor(Math.random() * attackerEntityTemplates.length)]; | return; | ||||
// A soldier for each 2-3 minutes of the game. Should be waves of 20 soldiers after an hour | |||||
let nextTime = Math.round(120000 + Math.random() * 60000); | |||||
let attackersPerTemplate = Math.ceil(cmpTimer.GetTime() / nextTime / attackerTemplates.length); | |||||
let spawned = false; | |||||
for (let point of this.GetTriggerPoints("A")) | print("DEBUG [" + Math.round(Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime() / 60 / 1000) + "] " + txt + "\n"); | ||||
}; | |||||
Trigger.prototype.LoadAttackerTemplates = function() | |||||
Done Inline ActionsSame as above maybe ...heroTime bb: Same as above maybe `...heroTime` | |||||
{ | { | ||||
let cmpPlayer = QueryOwnerInterface(point, IID_Player); | let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); | ||||
Done Inline Actionsinline random agnostic bb: inline random agnostic | |||||
Done Inline ActionsMath.random is agnostic too :P elexis: Math.random is agnostic too :P
Stupid me should have used randFloat before though (that existed… | |||||
if (cmpPlayer.GetPlayerID() == 0 || cmpPlayer.GetState() != "active") | |||||
continue; | |||||
let cmpPosition = Engine.QueryInterface(this.playerCivicCenter[cmpPlayer.GetPlayerID()], IID_Position); | for (let templateName of cmpTemplateManager.FindAllTemplates(false)) | ||||
if (!cmpPosition || !cmpPosition.IsInWorld) | { | ||||
if (!templateName.startsWith("units/") || templateName.endsWith("_unpacked") || templateName.endsWith("_barracks")) | |||||
continue; | continue; | ||||
Done Inline ActionsDoesn't seem necessary to remove. bb: Doesn't seem necessary to remove. | |||||
Done Inline ActionsWhy is this removed? bb: Why is this removed? | |||||
Not Done Inline ActionsCan't occur elexis: Can't occur | |||||
Done Inline Actionspickrandom bb: pickrandom | |||||
Done Inline Actionssweet elexis: sweet | |||||
Not Done Inline Actionswhitespace + inlining + 2D as height not used elexis: whitespace + inlining + 2D as height not used | |||||
let targetPos = cmpPosition.GetPosition(); | |||||
for (let template of attackerTemplates) | let identity = cmpTemplateManager.GetTemplate(templateName).Identity; | ||||
bbUnsubmitted Not Done Inline ActionsAll templates need an Identity component, so correct. shouldn't it be named cmpIdentity? bb: All templates need an Identity component, so correct.
shouldn't it be named `cmpIdentity`? | |||||
elexisAuthorUnsubmitted Not Done Inline ActionsNah, people might confuse it with the component of an entity, but it's only template data without functions elexis: Nah, people might confuse it with the component of an entity, but it's only template data… | |||||
{ | |||||
let entities = TriggerHelper.SpawnUnits(point, template, attackersPerTemplate, 0); | |||||
ProcessCommand(0, { | if (!attackerUnitTemplates[identity.Civ]) | ||||
Done Inline ActionsOnce #252 is implemented we could add some prefType: !Capture in this, to avoid capturing, for now leave as is. bb: Once #252 is implemented we could add some `prefType: !Capture` in this, to avoid capturing… | |||||
Not Done Inline Actionss/could/should elexis: s/could/should
Actually this should be done for all gaia units, independent of the trigger… | |||||
"type": "attack-walk", | attackerUnitTemplates[identity.Civ] = { | ||||
"entities": entities, | "heroes": [], | ||||
Done Inline ActionsWhen hero still alive there is one attacker less and no new hero's will come, so its seems it is sort of good to keep hero alive. bb: When hero still alive there is one attacker less and no new hero's will come, so its seems it… | |||||
Done Inline ActionsI would never kill my own gaia units unless I had to xD elexis: I would never kill my own gaia units unless I had to xD | |||||
Done Inline ActionsThis check works ofc, but wouldn't it be cleaner to use the onOwnershipChange messages to remove the hero from this.gaiaHeroes[cmpPlayer.GetPlayerID()]? This would need some further changes though (storing the ent id instead of player's), no strong opinion. bb: This check works ofc, but wouldn't it be cleaner to use the onOwnershipChange messages to… | |||||
Not Done Inline ActionsGuess that will be relevant once we check for heroes in mulitple occurances. Looks like it would need a loop then, so avoiding for now. elexis: Guess that will be relevant once we check for heroes in mulitple occurances. Looks like it… | |||||
"x": targetPos.x, | "champions": [], | ||||
"z": targetPos.z, | "siege": [] | ||||
Done Inline Actionsrandfloat and can be inlined later when removing debug. bb: randfloat and can be inlined later when removing debug. | |||||
Not Done Inline ActionsrandFloat, but want those debug messages in case I ever need to look at it again. Even when playing its useful to see what happened (in a replay f.e.) elexis: randFloat, but want those debug messages in case I ever need to look at it again. Even when… | |||||
"queued": true, | }; | ||||
Done Inline ActionsWe now only have siege and champion classes, when we want to add more this needs change ofc. bb: We now only have siege and champion classes, when we want to add more this needs change ofc. | |||||
"targetClasses": undefined | |||||
}); | |||||
} | |||||
spawned = true; | |||||
} | |||||
if (!spawned) | let classes = GetIdentityClasses(identity); | ||||
return; | |||||
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); | // Notice some heroes are elephants and war elephants are champions | ||||
cmpGUIInterface.PushNotification({ | if (classes.indexOf("Hero") != -1) | ||||
"message": markForTranslation("An enemy wave is attacking!"), | attackerUnitTemplates[identity.Civ].heroes.push(templateName); | ||||
"translateMessage": true | else if (classes.indexOf("Siege") != -1 || classes.indexOf("Elephant") != -1 && classes.indexOf("Melee") != -1) | ||||
}); | attackerUnitTemplates[identity.Civ].siege.push(templateName); | ||||
this.DoAfterDelay(nextTime, "StartAnEnemyWave", {}); // The next wave will come in 3 minutes | else if (classes.indexOf("Champion") != -1) | ||||
attackerUnitTemplates[identity.Civ].champions.push(templateName); | |||||
} | |||||
Not Done Inline Actionsunused, nuke, jshint ftw elexis: unused, nuke, jshint ftw | |||||
this.debugLog("Attacker templates:"); | |||||
this.debugLog(uneval(attackerUnitTemplates)); | |||||
}; | }; | ||||
Trigger.prototype.InitGame = function() | Trigger.prototype.SetDisableTemplates = function() | ||||
Done Inline ActionsMath.min unneeded as attackerTypeCount should always be integer. random agnostic This is not a fair distribution, since the first template is expected to have more than the second, third etc. (This does not hold for only 2 templates, but there are arrays of more.) Here an example for 3 templates: rand1=randFloat(0,1); rand2=randFloat(0,1); rand3=randFloat(0,1); total=rand1+rand2+rand3; ratio1=rand1/total; ratio2=rand2/total; ratio3=rand3/total; and use these ratio's to calculate the amount of attackers per type. With these changes maybe the Math.min(a,b) is needed again. bb: Math.min unneeded as attackerTypeCount should always be integer.
random agnostic
This is not… | |||||
Done Inline ActionsYou mean Math.min is not needed because Math.random is smaller than 1, agree. elexis: You mean Math.min is not needed because Math.random is smaller than 1, agree.
I don't think we… | |||||
Done Inline ActionsI don't think you exactly got what I meant here. Lets see this example: In my proposed distribution the expected values will all be equal so imo the distribution is more fair. (And tbh I don't see any performance cost in that, I would actually make the code cleaner since the +i == attackerEntityTemplates[civ][attackerType].length - 1 is not necessary anymore). We only need to loop twice through attackerEntityTemplates[civ][attackerType] now. bb: I don't think you exactly got what I meant here. Lets see this example:
We have 3 different… | |||||
Not Done Inline ActionsIntended shorter code. Ok with doing an actual randomization with your formula. elexis: Intended shorter code. Ok with doing an actual randomization with your formula. | |||||
{ | |||||
for (let i = 1; i < TriggerHelper.GetNumberOfPlayers(); ++i) | |||||
{ | { | ||||
let numberOfPlayers = TriggerHelper.GetNumberOfPlayers(); | let cmpPlayer = QueryPlayerIDInterface(i); | ||||
// Find all of the civic centers, disable some structures | cmpPlayer.SetDisabledTemplates(disabledTemplates(cmpPlayer.GetCiv())); | ||||
for (let i = 1; i < numberOfPlayers; ++i) | } | ||||
Done Inline ActionsSeen factor 60* 1000 before as 6000, change either of them. there are some occurrences below too of 60*1000 bb: Seen factor 60* 1000 before as 6000, change either of them. there are some occurrences below… | |||||
Not Done Inline Actions60 * 1000 seems nicer to read IMO and the JIT should make short work of it elexis: 60 * 1000 seems nicer to read IMO and the JIT should make short work of it | |||||
}; | |||||
/** | |||||
* Remember civic centers and make women invincible. | |||||
*/ | |||||
Trigger.prototype.InitStartingUnits = function() | |||||
Done Inline Actionsinit helper function, move directly under init functions. bb: init helper function, move directly under init functions. | |||||
Not Done Inline Actionsack elexis: ack | |||||
{ | { | ||||
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | ||||
let playerEntities = cmpRangeManager.GetEntitiesByPlayer(i); // Get all of each player's entities | for (let i = 1; i < TriggerHelper.GetNumberOfPlayers(); ++i) | ||||
{ | |||||
let playerEntities = cmpRangeManager.GetEntitiesByPlayer(i); | |||||
Done Inline Actionsmissing whitespace bb: missing whitespace | |||||
Not Done Inline ActionsstartsWith elexis: startsWith | |||||
Not Done Inline ActionsMoved out of the loop elexis: Moved out of the loop | |||||
for (let entity of playerEntities) | for (let entity of playerEntities) | ||||
{ | { | ||||
if (TriggerHelper.EntityHasClass(entity, "CivilCentre")) | if (TriggerHelper.EntityHasClass(entity, "CivilCentre")) | ||||
this.playerCivicCenter[i] = entity; | this.playerCivicCenter[i] = entity; | ||||
else if (TriggerHelper.EntityHasClass(entity, "FemaleCitizen")) | else if (TriggerHelper.EntityHasClass(entity, "FemaleCitizen")) | ||||
{ | { | ||||
this.treasureFemale[i] = entity; | |||||
let cmpDamageReceiver = Engine.QueryInterface(entity, IID_DamageReceiver); | let cmpDamageReceiver = Engine.QueryInterface(entity, IID_DamageReceiver); | ||||
cmpDamageReceiver.SetInvulnerability(true); | cmpDamageReceiver.SetInvulnerability(true); | ||||
Done Inline Actionsmissing semicolon elexis: missing semicolon | |||||
let cmpHealth = Engine.QueryInterface(entity, IID_Health); | let cmpHealth = Engine.QueryInterface(entity, IID_Health); | ||||
cmpHealth.SetUndeletable(true); | cmpHealth.SetUndeletable(true); | ||||
Done Inline ActionsWas wondering wheter this could be abused to distract the waves to attack the woman and thus become invincible, but move the woman up requires lots of resources and so it would be almost impossible. A way to fix this is letting the gaia's not attack invincible units, needs same fix as buildings attacking those. bb: Was wondering wheter this could be abused to distract the waves to attack the woman and thus… | |||||
Not Done Inline ActionsAlso not limited to gaia, promoted units getting attacked in a battle can make a significant impact. elexis: Also not limited to gaia, promoted units getting attacked in a battle can make a significant… | |||||
} | } | ||||
Done Inline Actionsperiod bb: period | |||||
Not Done Inline ActionsTrue, got that period everywhere else in this file. elexis: True, got that period everywhere else in this file. | |||||
} | } | ||||
} | } | ||||
}; | |||||
this.PlaceTreasures(); | Trigger.prototype.InitializeEnemyWaves = function() | ||||
Done Inline ActionsName starts with init: move all of them together at top. bb: Name starts with init: move all of them together at top. | |||||
Not Done Inline Actionsack. Didn't want to move lines I didn't change to ease reviewing originally. elexis: ack. Didn't want to move lines I didn't change to ease reviewing originally. | |||||
{ | |||||
let time = randFloat(...firstWaveTime) * 60 * 1000; | |||||
Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).AddTimeNotification({ | |||||
"message": markForTranslation("The first wave will start in %(time)s!"), | |||||
"translateMessage": true | |||||
}, time); | |||||
this.DoAfterDelay(time, "StartAnEnemyWave", {}); | |||||
}; | |||||
for (let i = 1; i < numberOfPlayers; ++i) | Trigger.prototype.StartAnEnemyWave = function() | ||||
{ | { | ||||
let cmpPlayer = QueryPlayerIDInterface(i); | let currentMin = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer).GetTime() / 60 / 1000; | ||||
let civ = cmpPlayer.GetCiv(); | let nextWaveTime = randFloat(...waveTime); | ||||
cmpPlayer.SetDisabledTemplates(disabledTemplates(civ)); | let civ = pickRandom(Object.keys(attackerUnitTemplates)); | ||||
// Determine total attacker count of the current wave. | |||||
// Exponential increase with time, capped to the limit and fluctuating proportionally with the current wavetime. | |||||
let totalAttackers = Math.ceil(Math.min(totalAttackerLimit, | |||||
initialAttackers * Math.pow(percentPerMinute, currentMin) * nextWaveTime / waveTime[1])); | |||||
this.debugLog("Spawning " + totalAttackers + " attackers"); | |||||
let attackerTemplates = []; | |||||
Done Inline ActionsLoads of trailing comma's: meh bb: Loads of trailing comma's: meh | |||||
Done Inline ActionsPlanning to add citizen soldiers next time I add features to this map, so rather have these commas now and avoid changed lines in the next proposal. elexis: Planning to add citizen soldiers next time I add features to this map, so rather have these… | |||||
// Add hero | |||||
if (currentMin > randFloat(...heroTime) && attackerUnitTemplates[civ].heroes.length) | |||||
{ | |||||
this.debugLog("Spawning hero"); | |||||
Not Done Inline ActionsRenaming to InitSurvival elexis: Renaming to InitSurvival | |||||
attackerTemplates.push({ | |||||
"template": pickRandom(attackerUnitTemplates[civ].heroes), | |||||
"count": 1, | |||||
"hero": true | |||||
}); | |||||
--totalAttackers; | |||||
} | } | ||||
// Random siege to champion ratio | |||||
let siegeRatio = randFloat(...siegeFraction); | |||||
let siegeCount = Math.round(siegeRatio * totalAttackers); | |||||
this.debugLog("Siege Ratio: " + Math.round(siegeRatio * 100) + "%"); | |||||
let attackerTypeCounts = { | |||||
"siege": siegeCount, | |||||
"champions": totalAttackers - siegeCount | |||||
}; | }; | ||||
Trigger.prototype.PlaceTreasures = function() | this.debugLog("Spawning:" + uneval(attackerTypeCounts)); | ||||
// Random ratio of the given templates | |||||
for (let attackerType in attackerTypeCounts) | |||||
{ | { | ||||
let point = ["B", "C", "D"][Math.floor(Math.random() * 3)]; | let attackerTypes = attackerUnitTemplates[civ][attackerType]; | ||||
bbUnsubmitted Not Done Inline ActionsBad name, it are the templates, not the types. bb: Bad name, it are the templates, not the types. | |||||
elexisAuthorUnsubmitted Not Done Inline Actionstrue, going for attackerTypeTemplates elexis: true, going for attackerTypeTemplates | |||||
let triggerPoints = this.GetTriggerPoints(point); | let attackerEntityRatios = new Array(attackerTypes.length).fill(1).map(i => randFloat(0, 1)); | ||||
for (let point of triggerPoints) | let attackerEntityRatioSum = attackerEntityRatios.reduce((current, sum) => current + sum, 0); | ||||
attackerEntityRatios = attackerEntityRatios.map(ratio => ratio / attackerEntityRatioSum); | |||||
bbUnsubmitted Done Inline ActionsThis assignment is strictly unneeded, since every element is only used once, probably more readable to keep it though. bb: This assignment is strictly unneeded, since every element is only used once, probably more… | |||||
elexisAuthorUnsubmitted Not Done Inline ActionsYou're right, that map is stupid, inlining that division does even make it more readable imo, makes me feel like a noob xD elexis: You're right, that map is stupid, inlining that division does even make it more readable imo… | |||||
let remainder = attackerTypeCounts[attackerType]; | |||||
for (let i in attackerTypes) | |||||
{ | |||||
let count = | |||||
+i == attackerTypes.length - 1 ? | |||||
remainder : | |||||
Math.floor(attackerEntityRatios[i] * attackerTypeCounts[attackerType]); | |||||
bbUnsubmitted Not Done Inline ActionsWondering why Math.floor is better than Math.round, but meh. bb: Wondering why Math.floor is better than Math.round, but meh. | |||||
elexisAuthorUnsubmitted Not Done Inline ActionsUsed floor as I wanted to avoid overflows at all costs. Let's see. max =7, probability = 0.4, 0.4, 0.2 -> 2.8, 2.8, 1.4 with round -> 3, 3, 1 Yep, round it is. And afaics it can never yield more elements than requested. elexis: Used floor as I wanted to avoid overflows at all costs. Let's see.
max =7, probability = 0.4… | |||||
attackerTemplates.push({ | |||||
"template": attackerUnitTemplates[civ][attackerType][i], | |||||
bbUnsubmitted Done Inline Actionsuse renamed attackerType bb: use renamed `attackerType` | |||||
elexisAuthorUnsubmitted Not Done Inline Actionsack, good eyesight elexis: ack, good eyesight | |||||
"count": count | |||||
}); | |||||
remainder -= count; | |||||
} | |||||
if (remainder != 0) | |||||
warn("Didn't spawn as many attackers as intended: " + remainder); | |||||
} | |||||
this.debugLog("Templates: " + uneval(attackerTemplates)); | |||||
// Spawn the templates | |||||
let spawned = false; | |||||
for (let point of this.GetTriggerPoints("A")) | |||||
{ | { | ||||
let template = treasures[Math.floor(Math.random() * treasures.length)]; | if (dryRun) | ||||
TriggerHelper.SpawnUnits(point, template, 1, 0); | { | ||||
spawned = true; | |||||
break; | |||||
} | } | ||||
this.DoAfterDelay(4*60*1000, "PlaceTreasures", {}); // Place more treasures after 4 minutes | |||||
}; | |||||
Trigger.prototype.InitializeEnemyWaves = function() | let cmpPlayer = QueryOwnerInterface(point, IID_Player); | ||||
// Trigger point owned by Gaia if the player is defeated | |||||
if (cmpPlayer.GetPlayerID() == 0) | |||||
continue; | |||||
let targetPos = Engine.QueryInterface(this.playerCivicCenter[cmpPlayer.GetPlayerID()], IID_Position).GetPosition2D(); | |||||
for (let attackerTemplate of attackerTemplates) | |||||
{ | { | ||||
let time = (5 + Math.round(Math.random() * 10)) * 60 * 1000; | // Don't spawn gaia hero if the previous one is still alive | ||||
if (attackerTemplate.hero && this.gaiaHeroes[cmpPlayer.GetPlayerID()]) | |||||
{ | |||||
let cmpHealth = Engine.QueryInterface(this.gaiaHeroes[cmpPlayer.GetPlayerID()], IID_Health); | |||||
if (cmpHealth && cmpHealth.GetHitpoints() != 0) | |||||
{ | |||||
this.debugLog("Not spawning hero for player " + cmpPlayer.GetPlayerID() + " as the previous one is still alive"); | |||||
continue; | |||||
} | |||||
} | |||||
if (dryRun) | |||||
continue; | |||||
let entities = TriggerHelper.SpawnUnits(point, attackerTemplate.template, attackerTemplate.count, 0); | |||||
ProcessCommand(0, { | |||||
bbUnsubmitted Done Inline ActionsWondering whether we would win some performance when we push all attacker for 1 player into 1 list and send this command for the whole bunch at once. Probably not, though. bb: Wondering whether we would win some performance when we push all attacker for 1 player into 1… | |||||
elexisAuthorUnsubmitted Not Done Inline ActionsAfter getting my hands dirty with polar sea and danube, performance improvements are possible. I'm not convinced that these are needed for survival. If we spawn tons of units, it will lag as hell because the units collide with each other all the time, so 150 is our limit currently. I suggest to spawn them in an area, not at a single point, should alleviate the issue vastly and allow even more attackers. elexis: After getting my hands dirty with polar sea and danube, performance improvements are possible. | |||||
"type": "attack-walk", | |||||
"entities": entities, | |||||
"x": targetPos.x, | |||||
"z": targetPos.y, | |||||
"queued": true, | |||||
"targetClasses": undefined | |||||
}); | |||||
if (attackerTemplate.hero) | |||||
this.gaiaHeroes[cmpPlayer.GetPlayerID()] = entities[0]; | |||||
} | |||||
spawned = true; | |||||
} | |||||
if (!spawned) | |||||
return; | |||||
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); | let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); | ||||
cmpGUIInterface.AddTimeNotification({ | cmpGUIInterface.PushNotification({ | ||||
"message": markForTranslation("The first wave will start in %(time)s!"), | "message": markForTranslation("An enemy wave is attacking!"), | ||||
"translateMessage": true | "translateMessage": true | ||||
}, time); | }); | ||||
this.DoAfterDelay(time, "StartAnEnemyWave", {}); | this.DoAfterDelay(nextWaveTime * 60 * 1000, "StartAnEnemyWave", {}); | ||||
}; | |||||
Not Done Inline Actionsmissing semicolon elexis: missing semicolon | |||||
Trigger.prototype.PlaceTreasures = function() | |||||
Done Inline ActionsMaybe add a message to the players too. bb: Maybe add a message to the players too. | |||||
Not Done Inline ActionsDiscussed with @Hannibal_Barca, we prefer the players to pay attention elexis: Discussed with @Hannibal_Barca, we prefer the players to pay attention | |||||
{ | |||||
let point = pickRandom(["B", "C", "D"]); | |||||
Done Inline Actionspickrandom bb: pickrandom | |||||
let triggerPoints = this.GetTriggerPoints(point); | |||||
for (let point of triggerPoints) | |||||
TriggerHelper.SpawnUnits(point, pickRandom(treasures), 1, 0); | |||||
Done Inline ActionsMaybe randomize a bit. bb: Maybe randomize a bit. | |||||
Not Done Inline ActionsIndeed, a must-have! elexis: Indeed, a must-have! | |||||
Done Inline Actionspickrandom and inline bb: pickrandom and inline | |||||
Done Inline ActionsWe now have 3 sets of places where treasures will spawn, so players can simply trac those and move there woman in the best position. I guess we should make this ratio a global. bb: We now have 3 sets of places where treasures will spawn, so players can simply trac those and… | |||||
Not Done Inline ActionsIndeed, out of scope and afaik you already had a patch for that. Remember that this patch originated from a review of that patch you uploaded 9 months ago to #3102 ! elexis: Indeed, out of scope and afaik you already had a patch for that. Remember that this patch… | |||||
Not Done Inline ActionsWasn't exactly this, what i proposed 9 month ago, but was similar indeed. Agree on doing this in another patch. bb: Wasn't exactly this, what i proposed 9 month ago, but was similar indeed. Agree on doing this… | |||||
this.DoAfterDelay(randFloat(...treasureTime) * 60 * 1000, "PlaceTreasures", {}); | |||||
Done Inline Actionshmmmm, init? bb: hmmmm, init? | |||||
}; | }; | ||||
Trigger.prototype.DefeatPlayerOnceCCIsDestroyed = function(data) | Trigger.prototype.OnOwnershipChanged = function(data) | ||||
Done Inline ActionsDo notice this function will also be called when adding any entity to the map from a triggerscript. bb: Do notice this function will also be called when adding any entity to the map from a… | |||||
Done Inline ActionsNoticed. Not an issue for regicide, nor capture the relic. Will keep it in mind for future trigger scripts. Also balancing changes drastically each release :-/ elexis: Noticed. Not an issue for regicide, nor capture the relic. Will keep it in mind for future… | |||||
{ | { | ||||
if (data.entity == this.playerCivicCenter[data.from]) | if (data.entity == this.playerCivicCenter[data.from]) | ||||
TriggerHelper.DefeatPlayer(data.from); | TriggerHelper.DefeatPlayer(data.from); | ||||
else if (data.entity == this.treasureFemale[data.from]) | |||||
Done Inline ActionsFor safety one might also check for data.to == -1 And we might ever get an onEntityRenamed call, maybe handle those too (don't think they are present now). bb: For safety one might also check for `data.to == -1`
And we might ever get an `onEntityRenamed`… | |||||
Done Inline ActionsWon't have these calls indeed. elexis: Won't have these calls indeed. | |||||
Not Done Inline ActionsOne might have when there is promotion, upgrading. Yes they aren't in code now, but we are asking for errors now bb: One might have when there is promotion, upgrading. Yes they aren't in code now, but we are… | |||||
Not Done Inline ActionsThere are 2 cases:
elexis: There are 2 cases:
1) Entity getting destroyed (for example hannibal using the developer cheat… | |||||
{ | |||||
this.treasureFemale[data.from] = undefined; | |||||
Engine.DestroyEntity(data.entity); | |||||
} | |||||
}; | }; | ||||
Done Inline ActionsMore init helper bb: More init helper | |||||
{ | { | ||||
let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); | let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger); | ||||
cmpTrigger.playerCivicCenter = {}; | |||||
Done Inline ActionsCan't this be merged with InitGame trigger? or at least let this trigger below the initGame one. bb: Can't this be merged with InitGame trigger? or at least let this trigger below the initGame one. | |||||
Not Done Inline ActionsHaving it as a separate function seems more readable, is also done with some other helpers. Don't see the point of waiting 1 simulation second. elexis: Having it as a separate function seems more readable, is also done with some other helpers. | |||||
cmpTrigger.DoAfterDelay(1000, "InitializeEnemyWaves", {}); | cmpTrigger.treasureFemale = []; | ||||
cmpTrigger.RegisterTrigger("OnInitGame", "InitGame", { "enabled": true }); | cmpTrigger.playerCivicCenter = []; | ||||
cmpTrigger.RegisterTrigger("OnOwnershipChanged", "DefeatPlayerOnceCCIsDestroyed", { "enabled": true }); | cmpTrigger.gaiaHeroes = []; | ||||
cmpTrigger.RegisterTrigger("OnInitGame", "InitSurvival", { "enabled": true }); | |||||
cmpTrigger.RegisterTrigger("OnOwnershipChanged", "OnOwnershipChanged", { "enabled": true }); | |||||
Done Inline ActionsPut all init function together above all helper functions. bb: Put all init function together above all helper functions. | |||||
Not Done Inline ActionsYou're right, we have Init always at the top. Appears nicer that way too elexis: You're right, we have Init always at the top. Appears nicer that way too | |||||
} | } |
Imo the times could be randomized a bit holding 4 as average.