Page MenuHomeWildfire Games

Survival Of The Fittest - exponential waves, heroes, template refactoring, cleanup
ClosedPublic

Authored by elexis on Feb 15 2017, 11:11 PM.

Details

Summary
  • Move constants to globals, so that the enemy waves can be balanced without changing the code
  • Use exponential attacker wave growth (instead of linear), so that it's strength doubles in a constant time delta, preventing boring and seemingly endless matches, independent of how many resources the players receive, limited to at most N attackers total.
  • Differentiate templates between by types (champions and siege engines), so that the script can consciously chose how many of each type and of each template to spawn (instead of having the ratio determined by number of templates provided and always spawning the same number of attackers of each template in the array). Thus prepares to add citizen soldier templates in earlier waves and increasing the likelihood of champions later.
  • Spawns a gaia hero per player, probably after minute N, certainly after minute M, but only if the previous hero isn't alive anymore
  • Adds command-line only debug output and a dry-run mode to test how many attackers would be spawned if the players survive all of the waves
  • Removes treasure picking woman after defeat, as its white dot on the minimap can be confused with a treasure.
  • Splits code into shorter, more readable functions.
  • Removes unused variables.
Test Plan
  • Apply the patch in D143, as the proposal is affected by those segfaults!
  • Enable the dryRun mode, use non-visual replay on the attached commands.txt with a grep for DEBUG to see how many units are spawned: pyrogenesis -replay="/path/to/commands.txt" -mod=public | grep DEBUG
  • Validating that at most 1 gaia hero per player spawns can be done by setting the hero spawn times to < 1, starting a game, garrisoning the CC. Hit F9 and type Engine.SetSimRate(20) to fast forward with 20x gamespeed. Then we can observe that there is no more and no less than 1 hero at a time. The debug output also informs us that the hero isn't spawned again
  • Validating that the treasure seeker woman is removed can be done by resigning or deleting the CC
  • Notice from the debug output that independent of the gaia hero spawning or not, the sum of units actually spawned matches the chosen number
  • Balancing:
    • Practical: Nothing can replace an actual playtest with experienced players. The usual suspects are likely eager to try this this patch and if it's wrong, we can rather easily adapt it afterwards.
    • Theoretical: From this a21 replay or this empty 3h long replay and this patch, we can extract how many units are spawned at which time (pyrogenesis -replay="/path/to/0ad/commands.txt" -mod=public | grep WARNING)

      In this spreadsheet, the formula used by the algorithm computes the proposed attackers per wave. We can simply change the constants in that formula to see how it affects the balancing. Both series are added to a graph, allowing to compare and esimtate whether the goal is achieved without making it to tough. We observe that for the first 60min, the balancing isn't changed significantly (as the balancing was about fine for that period before). But then the strength of the waves increases rapidly, solving the original issue.

Diff Detail

Repository
rP 0 A.D. Public Repository
Lint
Automatic diff as part of commit; lint not applicable.
Unit
Automatic diff as part of commit; unit tests not applicable.

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
bb added inline comments.Mar 21 2017, 1:10 AM
binaries/data/mods/public/maps/random/survivalofthefittest.js
13 ↗(On Diff #853)

All these tTier's may be moved together.

64 ↗(On Diff #853)

Inline these.

183 ↗(On Diff #853)

Not sure if this is correct clPlayer isn't removed, and shouldn't this be an array?

binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
236 ↗(On Diff #853)

Since the attackers in first wave is randomized a bit here (nextWaveTime) imo there is no need in randomizing the initialAttackers also.

259 ↗(On Diff #853)

Bergh, more of them in file

260 ↗(On Diff #853)

If 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.

318 ↗(On Diff #853)

This 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.

330 ↗(On Diff #853)

Once #252 is implemented we could add some prefType: !Capture in this, to avoid capturing, for now leave as is.

400 ↗(On Diff #853)

Was 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.

452 ↗(On Diff #853)

Can't this be merged with InitGame trigger? or at least let this trigger below the initGame one.

This revision now requires changes to proceed.Mar 21 2017, 1:10 AM
elexis marked an inline comment as done.Mar 21 2017, 12:50 PM
elexis added inline comments.
binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
19 ↗(On Diff #853)

Going 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.

52 ↗(On Diff #853)

Last time I checked it was supported, pulled through the GUIInterface

elexis marked 50 inline comments as done.Mar 24 2017, 4:38 PM

So besides tons of whitespace changes and moving lines around, the actually relevant code changes are:

(1) Nuking the entire array of hardcoded templates and extending the LoadHeroTemplates function to do that for us. (Nice surprise side effect of spawning non-hero units that can't be trained currently.)
(2) Non-biased randomization for templates.

binaries/data/mods/public/maps/random/survivalofthefittest.js
60 ↗(On Diff #853)

Got a big commit for that incoming nuking all of these and I feel all occurances in the file should be changed if I change these.
Actually using let here to fix variable declaration on global ix below

64 ↗(On Diff #853)

Ack, fixes var defs below too

80 ↗(On Diff #853)

slightly pedantic XP Also this one needs to be merged with all dupes in all rms files

94 ↗(On Diff #853)

Nope, 2 * PI :P

177 ↗(On Diff #853)

Didn't make up my mind entirely on those, because those are min max pairs, but yeah, might be actually more consistent

183 ↗(On Diff #853)

constraints can be a single constraint or an array
clLand already excludes clPlayer, clLand is the middle area, everywhere where treasure can be spawned

224 ↗(On Diff #853)

dont see it

244 ↗(On Diff #853)

I guess I have to when I'm already fixing the other whitespace in the same line.

binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
279 ↗(On Diff #573)

Intended shorter code. Ok with doing an actual randomization with your formula.

354 ↗(On Diff #573)

startsWith

47 ↗(On Diff #853)

true

81 ↗(On Diff #853)

that TODO shouldnt be in the code

131 ↗(On Diff #853)

Can't occur

218 ↗(On Diff #853)

Renaming to InitSurvival

230 ↗(On Diff #853)

Didn't like on first sight, but on second :-)

234 ↗(On Diff #853)

agree

260 ↗(On Diff #853)

Agree on tryint to avoid stupid people and if that fails, let them have errors so that they notice their mistake instead of muting it

294 ↗(On Diff #853)

unused, nuke, jshint ftw

309 ↗(On Diff #853)

whitespace + inlining + 2D as height not used

318 ↗(On Diff #853)

Guess that will be relevant once we check for heroes in mulitple occurances. Looks like it would need a loop then, so avoiding for now.

330 ↗(On Diff #853)

s/could/should
Actually this should be done for all gaia units, independent of the trigger scripts. Gaia capturing houses, then losing them again and capturinga again is plain stupid and improvable.

356 ↗(On Diff #853)

ack

381 ↗(On Diff #853)

ack. Didn't want to move lines I didn't change to ease reviewing originally.

385 ↗(On Diff #853)

Moved out of the loop

400 ↗(On Diff #853)

Also not limited to gaia, promoted units getting attacked in a battle can make a significant impact.

411 ↗(On Diff #853)

Indeed, 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 !

452 ↗(On Diff #853)

Having 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 updated this revision to Diff 927.Mar 24 2017, 5:56 PM
elexis edited edge metadata.
elexis marked 21 inline comments as done.

Nuke hardcoded pointless templates array and load it dynamically instead, just like the heroes.
This now includes gaia units (thebans) and the fireraiser siege engine, which seems strong but acceptable. Will be a fun surprise.
Address all of bb's comments, i.e. actual randomization, some little things I forgot about, reordering, few style improvements and lots of whitespace.

New dynamically generated set of possibly spawned units:

({
	athen: {
		heroes: [
			"units/athen_hero_iphicrates",
			"units/athen_hero_pericles",
			"units/athen_hero_themistocles"],
		champions: [
			"units/athen_black_cloak",
			"units/athen_champion_infantry",
			"units/athen_champion_marine",
			"units/athen_champion_ranged",
			"units/athen_thureophoros"],
		siege: [
			"units/athen_mechanical_siege_lithobolos_packed",
			"units/athen_mechanical_siege_oxybeles_packed"]
	},
	brit: {
		heroes: [
			"units/brit_hero_boudicca",
			"units/brit_hero_boudicca_sword",
			"units/brit_hero_caratacos",
			"units/brit_hero_cunobelin",
			"units/brit_hero_cunobelin_infantry"],
		champions: [
			"units/brit_champion_cavalry",
			"units/brit_champion_cavalry_barracks",
			"units/brit_champion_infantry",
			"units/brit_champion_infantry_barracks"],
		siege: [
			"units/brit_mechanical_siege_ram"]
	},
	cart: {
		heroes: [
			"units/cart_hero_hamilcar",
			"units/cart_hero_hannibal",
			"units/cart_hero_maharbal"],
		champions: [
			"units/cart_champion_cavalry",
			"units/cart_champion_infantry",
			"units/cart_champion_pikeman"],
		siege: [
			"units/cart_champion_elephant",
			"units/cart_mechanical_siege_ballista_packed",
			"units/cart_mechanical_siege_oxybeles_packed"]
	},
	gaul: {
		heroes: [
			"units/gaul_hero_brennus",
			"units/gaul_hero_britomartus",
			"units/gaul_hero_vercingetorix"],
		champions: [
			"units/gaul_champion_cavalry",
			"units/gaul_champion_cavalry_barracks",
			"units/gaul_champion_fanatic",
			"units/gaul_champion_infantry",
			"units/gaul_champion_infantry_barracks"],
		siege: [
			"units/gaul_mechanical_siege_ram"]
	},
	iber: {
		heroes: [
			"units/iber_hero_caros",
			"units/iber_hero_indibil",
			"units/iber_hero_viriato"],
		champions: [
			"units/iber_champion_cavalry",
			"units/iber_champion_cavalry_barracks",
			"units/iber_champion_infantry",
			"units/iber_champion_infantry_barracks"],
		siege: [
			"units/iber_mechanical_siege_ram"]
	},
	mace: {
		heroes: [
			"units/mace_hero_alexander",
			"units/mace_hero_craterus",
			"units/mace_hero_demetrius",
			"units/mace_hero_philip",
			"units/mace_hero_philip_pike",
			"units/mace_hero_pyrrhus"],
		champions: [
			"units/mace_champion_cavalry",
			"units/mace_champion_cavalry_barracks",
			"units/mace_champion_infantry_a",
			"units/mace_champion_infantry_a_barracks",
			"units/mace_champion_infantry_e",
			"units/mace_champion_infantry_e_barracks",
			"units/mace_thorakites",
			"units/mace_thureophoros"],
		siege: [
			"units/mace_mechanical_siege_lithobolos_packed",
			"units/mace_mechanical_siege_oxybeles_packed",
			"units/mace_mechanical_siege_ram",
			"units/mace_mechanical_siege_tower",
			"units/ptol_mechanical_siege_lithobolos_packed"]
	},
	maur: {
		heroes: [
			"units/maur_hero_ashoka",
			"units/maur_hero_chanakya",
			"units/maur_hero_maurya"],
		champions: [
			"units/maur_champion_chariot",
			"units/maur_champion_infantry",
			"units/maur_champion_infantry_barracks",
			"units/maur_champion_maiden",
			"units/maur_champion_maiden_archer",
			"units/maur_champion_maiden_barracks"],
		siege: [
			"units/maur_champion_elephant"]
	},
	gaia: {
		heroes: [],
		champions: [
			"units/merc_black_cloak",
			"units/merc_thorakites",
			"units/merc_thureophoros",
			"units/samnite_skirmisher",
			"units/samnite_spearman",
			"units/samnite_swordsman",
			"units/thebes_sacred_band_hoplitai",
			"units/thespian_melanochitones"],
		siege: [
			"units/theb_mechanical_siege_fireraiser"]
	},
	pers: {
		heroes: [
			"units/pers_hero_cyrus",
			"units/pers_hero_darius",
			"units/pers_hero_xerxes",
			"units/pers_hero_xerxes_chariot"],
		champions: [
			"units/pers_arstibara",
			"units/pers_champion_cavalry",
			"units/pers_champion_cavalry_archer",
			"units/pers_champion_infantry",
			"units/pers_kardakes_hoplite",
			"units/pers_kardakes_skirmisher"],
		siege: [
			"units/pers_champion_elephant",
			"units/pers_mechanical_siege_ram"]
	},
	ptol: {
		heroes: [
			"units/ptol_hero_cleopatra",
			"units/ptol_hero_ptolemy_I",
			"units/ptol_hero_ptolemy_IV"],
		champions: [
			"units/ptol_champion_cavalry",
			"units/ptol_champion_infantry_pikeman"],
		siege: [
			"units/ptol_champion_elephant",
			"units/ptol_mechanical_siege_polybolos_packed",
			"units/ptol_mechanical_siege_tower"]
	},
	rome: {
		heroes: [
			"units/rome_hero_marcellus",
			"units/rome_hero_maximus",
			"units/rome_hero_scipio"],
		champions: [
			"units/rome_centurio_imperial",
			"units/rome_champion_cavalry",
			"units/rome_champion_cavalry_barracks",
			"units/rome_champion_infantry",
			"units/rome_champion_infantry_barracks",
			"units/rome_legionnaire_imperial",
			"units/rome_legionnaire_marian"],
		siege: [
			"units/rome_mechanical_siege_ballista_packed",
			"units/rome_mechanical_siege_ram",
			"units/rome_mechanical_siege_scorpio_packed"]
	},
	sele: {
		heroes: [
			"units/sele_hero_antiochus_great",
			"units/sele_hero_antiochus_righteous",
			"units/sele_hero_seleucus_victor"],
		champions: [
			"units/sele_champion_cavalry",
			"units/sele_champion_chariot",
			"units/sele_champion_infantry_pikeman",
			"units/sele_champion_infantry_swordsman"],
		siege: [
			"units/sele_champion_elephant",
			"units/sele_mechanical_siege_lithobolos_packed",
			"units/sele_mechanical_siege_tower"]
	},
	spart: {
		heroes: [
			"units/spart_hero_agis",
			"units/spart_hero_brasidas",
			"units/spart_hero_leonidas"],
		champions: [
			"units/spart_black_cloak",
			"units/spart_champion_infantry_pike",
			"units/spart_champion_infantry_spear",
			"units/spart_thorakites",
			"units/spart_thureophoros"],
		siege: [
			"units/spart_mechanical_siege_oxybeles_packed",
			"units/spart_mechanical_siege_ram"]
	}
})

Build is green

Updating workspaces.
Build (release)...
Build (debug)...
Running release tests...
Running cxxtest tests (305 tests).................................................................................................................................................................................................................................................................................................................OK!
Running debug tests...
Running cxxtest tests (305 tests).................................................................................................................................................................................................................................................................................................................OK!

http://jw:8080/job/phabricator/589/ for more details.

elexis updated this revision to Diff 945.Mar 25 2017, 8:15 AM

Filter _barracks templates

Build is green

Updating workspaces.
Build (release)...
Build (debug)...
Running release tests...
Running cxxtest tests (305 tests).................................................................................................................................................................................................................................................................................................................OK!
Running debug tests...
Running cxxtest tests (305 tests).................................................................................................................................................................................................................................................................................................................OK!

http://jw:8080/job/phabricator/598/ for more details.

bb requested changes to this revision.Mar 29 2017, 4:21 PM

Sometimes treasures spawn on each other see in the replay the first gathered treasure by myself.

Nice to see siege towers in the waves, but curently they are 100% useless since they have no attack...
Some ppl might look strange to see the theban fire thingy's, but I am ok with that.

binaries/data/mods/public/maps/random/survivalofthefittest.js
187 ↗(On Diff #945)

1.0?

224 ↗(On Diff #853)

Why are there 2 tabs on the lines above, instead of 1?

binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
423 ↗(On Diff #573)

One might have when there is promotion, upgrading. Yes they aren't in code now, but we are asking for errors now

411 ↗(On Diff #853)

Wasn't exactly this, what i proposed 9 month ago, but was similar indeed. Agree on doing this in another patch.

17 ↗(On Diff #945)

.splice(0,1 "Earliest and latest time w")

22 ↗(On Diff #945)

/s/Least and greatest/Smallest and largest

(don't use "amount", since "amount of minutes" sounds weird)

97 ↗(On Diff #945)

-these

99 ↗(On Diff #945)

}?

128 ↗(On Diff #945)

All templates need an Identity component, so correct.

shouldn't it be named cmpIdentity?

243 ↗(On Diff #945)

Bad name, it are the templates, not the types.

246 ↗(On Diff #945)

This assignment is strictly unneeded, since every element is only used once, probably more readable to keep it though.

254 ↗(On Diff #945)

Wondering why Math.floor is better than Math.round, but meh.

257 ↗(On Diff #945)

use renamed attackerType

303 ↗(On Diff #945)

Wondering 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.

This revision now requires changes to proceed.Mar 29 2017, 4:21 PM
elexis marked 10 inline comments as done.Mar 29 2017, 5:22 PM
elexis added inline comments.
binaries/data/mods/public/maps/random/survivalofthefittest.js
187 ↗(On Diff #945)

ack

224 ↗(On Diff #853)

ok, saw it x) (the entire block is off, not only that one line)

binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
423 ↗(On Diff #573)

There are 2 cases:

  1. Entity getting destroyed (for example hannibal using the developer cheat again). In that case we don't have an error
  2. Promotion, still not getting that error while adding that check doesn't implement the promotion check completely. So suggesting to rather fix survival once we have female promotion or use a different template (like citizen soldier with more vision range)
97 ↗(On Diff #945)

these because it excludes some templates

128 ↗(On Diff #945)

Nah, people might confuse it with the component of an entity, but it's only template data without functions

243 ↗(On Diff #945)

true, going for attackerTypeTemplates

246 ↗(On Diff #945)

You're right, that map is stupid, inlining that division does even make it more readable imo, makes me feel like a noob xD

254 ↗(On Diff #945)

Used 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
with floor -> 2, 2, 3

Yep, round it is. And afaics it can never yield more elements than requested.

257 ↗(On Diff #945)

ack, good eyesight

303 ↗(On Diff #945)

After getting my hands dirty with polar sea and danube, performance improvements are possible.
Doing it outside of the loop as you mentioned is one valid point.
The next one would be not using TriggerHelper (or extending trigger helper to not use the footprint cmp).
Another one being not using ProcessCommand but just accessing UnitAI directly.

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 updated this revision to Diff 993.Mar 29 2017, 5:27 PM
elexis edited edge metadata.
elexis marked 5 inline comments as done.

Math.round, code style, unneeded var removal.

bb accepted this revision.Mar 29 2017, 5:51 PM

Accepting as those whitespaces won't break anything.

binaries/data/mods/public/maps/random/survivalofthefittest.js
241 ↗(On Diff #993)

2 whitespaces missing

This revision is now accepted and ready to land.Mar 29 2017, 5:51 PM
This revision was automatically updated to reflect the committed changes.

Thanks for all the reviews, finding my oversights and lazy code bb! I hereby return the review coupon for the next random patch cleanup or any other patch of your choice!

Build is green

Updating workspaces.
Build (release)...
Build (debug)...
Running release tests...
Running cxxtest tests (305 tests).................................................................................................................................................................................................................................................................................................................OK!
Running debug tests...
Running cxxtest tests (305 tests).................................................................................................................................................................................................................................................................................................................OK!

http://jw:8080/job/phabricator/633/ for more details.

elexis added a comment.EditedAug 11 2017, 2:37 PM

(The bug fixed by D767 explains why we were often defeated at the bolt-shooter stage. It wasn't clear whether the reason that they are placed in the same location was the reason for that)