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
Branch
/ps/trunk
Lint
No Linters Available
Unit
No Unit Test Coverage
Build Status
Buildable 889
Build 1394: Vulcan BuildJenkins
Build 1393: arc lint + arc unit

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–14

All these tTier's may be moved together.

62–63

Inline these.

181

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
111

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

112

Bergh, more of them in file

113

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.

129–130

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

132

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.

183

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.

352–353

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
20

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.

53

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–61

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

62–63

Ack, fixes var defs below too

78–79

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

92

Nope, 2 * PI :P

175

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

181

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

222

dont see it

242

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
48

true

81

that TODO shouldnt be in the code

105

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

109

agree

113

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

122–126

whitespace + inlining + 2D as height not used

129–130

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.

131

Can't occur

132

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.

147

unused, nuke, jshint ftw

152

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

161–164

ack

168–169

Moved out of the loop

169

startsWith

183

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

189

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

217

Renaming to InitSurvival

333–334

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 !

352–353

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

1.0?

222

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

binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
17

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

22

/s/Least and greatest/Smallest and largest

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

97

-these

99

}?

128

All templates need an Identity component, so correct.

shouldn't it be named cmpIdentity?

243

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

246

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

254

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

257

use renamed attackerType

303

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.

333–334

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

343

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

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

ack

222

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

binaries/data/mods/public/maps/random/survivalofthefittest_triggers.js
97

these because it excludes some templates

128

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

243

true, going for attackerTypeTemplates

246

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

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

ack, good eyesight

303

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.

343

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)
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

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)