expdef: name:Deathmatch info:~ This experiment simulates a team deathmatch. It means that framstick creatures are grouped in teams and the basic rule is: "My team vs WORLD!". The basic battle rules and collectibles are implemented. Statistics for the battle are saved to text files in CSV format: creatures.txt, teams.txt. For more information, see http://www.framsticks.com/deathmatch ~ code:~ //Author: Mateusz Cicheński @ Poznan University of Technology, 2012 @include "deathmatch-utils.inc" @include "deathmatch-levels.inc" global idleSimulationSteps; global battleNumber; global teams; function onExpDefLoad() { idleSimulationSteps = 0; battleNumber = 0; initLevels(); initPopulations(); Shapes.set("1p_weaponbox","//0\n"+ "p:-0.5, -0.5, -0.5\n"+ "p: 0.5, -0.5, -0.5\n"+ "p: 0.5, 0.5, -0.5\n"+ "p: -0.5, 0.5, -0.5\n"+ "p:-0.5, -0.5, 0.5\n"+ "p:0.5, -0.5, 0.5\n"+ "p:0.5, 0.5, 0.5\n"+ "p:-0.5, 0.5, 0.5\n"+ "j: 0,1\n"+ "j: 1,2\n"+ "j: 2,3\n"+ "j: 3,0\n"+ "j: 4,5\n"+ "j: 5,6\n"+ "j: 6,7\n"+ "j: 7,4\n"+ "j: 0,4\n"+ "j: 1,5\n"+ "j: 2,6\n"+ "j: 3,7", 0x0FAA0F); Shapes.set("1p_hpbox","//0\n"+ "p:-0.5, -0.5, -0.5\n"+ "p: 0.5, -0.5, -0.5\n"+ "p: 0.5, 0.5, -0.5\n"+ "p: -0.5, 0.5, -0.5\n"+ "p:-0.5, -0.5, 0.5\n"+ "p:0.5, -0.5, 0.5\n"+ "p:0.5, 0.5, 0.5\n"+ "p:-0.5, 0.5, 0.5\n"+ "j: 0,1\n"+ "j: 1,2\n"+ "j: 2,3\n"+ "j: 3,0\n"+ "j: 4,5\n"+ "j: 5,6\n"+ "j: 6,7\n"+ "j: 7,4\n"+ "j: 0,4\n"+ "j: 1,5\n"+ "j: 2,6\n"+ "j: 3,7", 0xAA0F0F); } function initPopulations() { //clear anything in populations Populations.clear(); //add Food population (group 0) Population.name = "Food"; Population.nnsim = 0; Population.enableperf = 0; Population.death = 1; Population.energy = 1; Population.selfmask = 0x20002; Population.othermask = 0x10002; //add Weapon Upgrade population (group 1) Populations.addGroup("Upgrades"); Population.nnsim = 0; Population.enableperf = 0; Population.death = 1; Population.energy = 1; Population.selfmask = 0x20002; Population.othermask = 0x10002; //add Teams populations (group above 2) var i = 0; teams = ExpParams.teamCount; if (teams == 1) { GenePools.group = 0; teams = GenePool.size; } for (i = 0; i < teams; i++) { Populations.addGroup("Team "+i); Population.nnsim = 1; Population.enableperf = 1; Population.death = 0; Population.energy = 1; Population.selfmask = 0x50001; Population.othermask = 0x60001; } //Collisions on 32 bit mask: // standard - rebound creatures => if lower bits 0xFFFF // custom - custom function handler => if higher bits 0xFFFF0000 //Collisions map: // Food vs Food = 0x20002 & 0x10002 = 2 - standard // Upgrades vs Upgrades = 0x20002 & 0x10002 = 2 - standard // Team X vs Team X = 0x50001 & 0x60001 = 0x40001 - standard + custom // Team X vs Team Y = 0x50001 & 0x60001 = 0x40001 - standard + custom // Food vs Upgrades = 0x20002 & 0x10002 = 2 - standard // Food vs Team X = 0x20002 & 0x60001 = 0x20000 || 0x50001 & 0x10002 = 0x10000 - custom // Upgrades vs Team X = 0x20002 & 0x60001 = 0x20000 || 0x50001 & 0x10002 = 0x10000 - custom } function initCreatures() { var i = 0; var j = 0; GenePools.group = 0; for (i = 0; i < Populations.size - 2; i++) { Populations.group = i + 2; for (j = 0; j < ExpParams.teamSize; j++) { if (ExpParams.teamCount != 1) GenePools.genotype = Math.random(GenePool.size); else GenePools.genotype = i; Population.createFromGenotype(); Creature.name = "Member " + j; } } } function onExpInit() { battleNumber = 0; initAll(); } function initAll() { idleSimulationSteps = 0; battleNumber += 1; initLevel(); initPopulations(); initCreatures(); initFiles(); writeTeamStatistics(); } function initFiles() { var f=File.createDirect("creatures"+battleNumber+".txt","Creature statistics"); f.writeString("#Creature name; Team; Kills; Assists; Total damage dealt; Total damage received; HP boxes collected; Upgrade boxes collected; Lifespan\n"); f.close(); var s = "#Time interval; "; var i = 0; for (i = 0; i < Populations.size - 2; i++) { s += "Team " + i + "; "; s += "Team members; "; } s += "Total energy\n"; var f=File.createDirect("teams"+battleNumber+".txt","Teams statistics"); f.writeString(s); f.close(); } function initLevel() { var levelNumber; if (ExpParams.level == -1) levelNumber = Math.random(levels.size); else levelNumber = ExpParams.level; var level = levels.get(levelNumber); Simulator.print("Level #" + (levelNumber + 1) + ": " + level[0]); World.wrldbnd = level[2]; World.wrldsiz = level[1]; World.wrldwat = -1; World.wrldtyp = level[3]; World.wrldmap = level[4]; World.wrldchg(); } function tryCreateHPBox() { Populations.group = 0; if (Population.size >= ExpParams.hpBoxTotalCount) return; if (Math.rnd01 > ExpParams.hpBoxProbability) return; var food = Populations.createFromString("//0\nm:Vstyle=hpbox\np:"); food.name = "HP Box"; food.idleen = ExpParams.hpBoxIdleEnergy; food.energ0 = ExpParams.hpBoxStartingEnergy; food.energy = food.energ0; food.nnenabled = 0; } function tryCreateUpgradeBox() { Populations.group = 1; if (Population.size >= ExpParams.upgradeBoxTotalCount) return; if (Math.rnd01 > ExpParams.upgradeBoxProbability) return; var weapon = Populations.createFromString("//0\nm:Vstyle=weaponbox\np:"); weapon.name = "Weapon Box"; weapon.idleen = ExpParams.upgradeBoxIdleEnergy; weapon.energ0 = ExpParams.upgradeBoxStartingEnergy; weapon.energy = weapon.energ0; weapon.nnenabled = 0; } //handle creature placement in the world function onBorn() { // place newly born creature var retry = 20; //try 20 times var placed_ok = 0; while (retry-- && !placed_ok) { switch (Populations.group) { case 0: //food placement place_centerhead(); break; case 1: //upgrade placement place_centerhead(); break; default: Creature.user1 = Vector.new(); Creature.user1.add(0.0); //attack cooldown (0) Creature.user1.add(0.0); //bonus damage (1) Creature.user2 = Vector.new(); Creature.user2.add(0); //kills (0) Creature.user2.add(0); //assists (1) Creature.user2.add(0); //total damage dealt (2) Creature.user2.add(0); //total damage received (3) Creature.user2.add(0); //HP boxes collected (4) Creature.user2.add(0); //Upgrade boxes collected (5) Creature.user3 = Vector.new(); //who dealt damage to this creature Creature.energ0 = ExpParams.creatureStartingEnergy; Creature.energy = Creature.energ0; place_inTeamSpot(); break; } if (!Populations.creatBBCollisions(0)) placed_ok = 1; } if (!placed_ok) print("onBorn() could not avoid collisions."); } //place creature in random spot and make it head center of the map function place_centerhead() { var x, y, z; x = (World.wrldsiz - Creature.size_x) * Math.rnd01 - Creature.size_x / 2; y = (World.wrldsiz - Creature.size_y) * Math.rnd01 - Creature.size_y / 2; z = WorldMap.getHeight(x, y); var hx = 0.0 + (World.wrldsiz / 2) - x; var hy = 0.0 + (World.wrldsiz / 2) - y; var alpha_rad = vectorsAngle([1.0, 0.0], [hx, hy]); Creature.rotate(0, 0, alpha_rad); Creature.moveAbs(x, y, z - 0.999); print("Creature placed in [" + x +" " + y + " " + z); return ; } //Place creature in correct team spot (valid for Team creatures only) //Creatures are placed in circle with random rotation (commented out: heading center of the map) in their respective team groups function place_inTeamSpot() { var radius = 360.00 * (Populations.group - 1) / teams; var x, y, z; x = (ExpParams.teamSpawningAreaSize) * Math.rnd01 - Creature.size_x / 2 - ExpParams.teamSpawningAreaSize/2; y = (ExpParams.teamSpawningAreaSize) * Math.rnd01 - Creature.size_y / 2 - ExpParams.teamSpawningAreaSize/2; //vector of length half the world size minus spawning area size var vect = [0.0 + World.wrldsiz/2 - ExpParams.teamSpawningAreaSize/2 - 1.0, 0.0]; vect = rotateVector(vect, radius); //translate creature by given vector //then translate by halfworld (as it is the center of the world //and the positions were calculated for point [0,0] x = x + vect[0] + World.wrldsiz/2; y = y + vect[1] + World.wrldsiz/2; z = WorldMap.getHeight(x, y); var alpha_rad = 360.0 * Math.rnd01; //random rotation //this would rotate creatures so that they would face the world center: //var hx = 0.0 + (World.wrldsiz / 2) - x; //var hy = 0.0 + (World.wrldsiz / 2) - y; //var alpha_rad = vectorsAngle([1.0, 0.0], [hx, hy]); Creature.rotate(0, 0, alpha_rad); //place it mid-air Creature.moveAbs(x, y, z); return ; } // //////////////////////////////// // Simulation steps function onStep() { idleSimulationSteps += 1; tryCreateHPBox(); tryCreateUpgradeBox(); lowerCooldownCounters(); applySuddenDeathModifier(); clearDeadCreatures(); checkGameState(); } function writeTeamStatistics() { var s = "" + Simulator.time + "; "; var i = 0; var j = 0; var total = 0.0; for (i = 0; i < Populations.size - 2; i++) { Populations.group = i + 2; var sum = 0.0; for (j = 0; j < Population.size; j++) { Populations.creature = j; sum += Creature.energy; } s += "" + sum + "; "; s += "" + Population.size + "; "; total += sum; } if (total < 0) total == 0; s += "" + total + "\n"; var f=File.appendDirect("teams"+battleNumber+".txt","Teams statistics"); f.writeString(s); f.close(); } function clearDeadCreatures() { var i = 0; var j = 0; for (i = 0; i < Populations.size - 2; i++) { Populations.group = i + 2; for (j = 0; j < Population.size; j++) { Populations.creature = j; if (Creature.energy <= 0) Populations.killSelected(); } } } function applySuddenDeathModifier() { if (idleSimulationSteps < ExpParams.stagnationInterval) return; var i = 0; var j = 0; for (i = 0; i < Populations.size - 2; i++) { Populations.group = i + 2; for (j = 0; j < Population.size; j++) { Populations.creature = j; Creature.energy -= ExpParams.stagnationHealthReduce; } } } function lowerCooldownCounters() { var i = 0; var j = 0; for (i = 0; i < Populations.size - 2; i++) { Populations.group = i + 2; for (j = 0; j < Population.size; j++) { Populations.creature = j; if (Creature.user1 != null) { if (Creature.user1.get(0) > 0) Creature.user1.set(0, Creature.user1.get(0) - 1); if (Creature.user1.get(0) < 0) Creature.user1.set(0,0.0); if (Creature.user1.get(1) > 0) Creature.user1.set(1, Creature.user1.get(1) - ExpParams.upgradeBoxDecay); if (Creature.user1.get(1) < 0) Creature.user1.set(1,0.0); } } } } function checkGameState() { var i = 0; var alivePop = 0; for (i = 0; i < Populations.size - 2; i++) { Populations.group = i + 2; if (Population.size > 0) alivePop += 1; } if (alivePop == 0 && Simulator.time <= 1) { onExpInit(); //do initialization for user, because he forgot to do so return; } if (alivePop <= 1) { for (i = 0; i < Populations.size - 2; i++) { Populations.group = i + 2; if (Population.size > 0) { Simulator.print("Battle is OVER! The winner is: " + Population.name); //write stats of last living creatures var f=File.appendDirect("creatures"+battleNumber+".txt","Creature statistics"); var j = 0; for (j = 0; j < Population.size; j++) { Populations.creature = j; //f.writeString("Creature name; Team; Kills; Assists; Total damage dealt; Total damage received; HP boxes collected; Upgrade boxes collected; Lifespan"); if (Creature.user2 != null) f.writeString(Creature.name + "; " + Population.name + "; " + Creature.user2.get(0) + "; " + Creature.user2.get(1) + "; " + Creature.user2.get(2) + "; " + Creature.user2.get(3) + "; " + Creature.user2.get(4) + "; " + Creature.user2.get(5) + "; " + Creature.lifespan + "\n"); } f.close(); } } if (alivePop == 0) Simulator.print("Battle is OVER! Total annihilation!"); Simulator.stop(); } } function print(msg) { if (ExpParams.debug) Simulator.print(msg); } function stop(msg) { Simulator.print(msg); Simulator.stop(); } // //////////////////////////// // Collisions //function executed on any Creature collission function onTeam0CrCollision, onTeam1CrCollision, onTeam2CrCollision, onTeam3CrCollision, onTeam4CrCollision, onTeam5CrCollision, onTeam6CrCollision, onTeam7CrCollision, onTeam8CrCollision, onTeam9CrCollision() { var c1 = CrCollision.Creature1; var c2 = CrCollision.Creature2; if (c1 == null) stop("2"); if (c2 == null) stop("2"); if (c1.energy <= 0 || c2.energy <= 0) { //collision with already killed creature (chain resolution) print("onCrCollision called with less than 2 Creatures"); return ; } if (c1.group == null || c2.group == null) { //collision of creature without a group print("onCrCollision called for creature without group, ignoring"); return ; } //first condition should never occur, but who knows? if (c1.group.index < 2 || c2.group.index < 2) { //collision with HP Box and Upgrades are handled separately print("onCrCollision called for population 0 or 1, ignoring"); return ; } idleSimulationSteps = 0; var kill_c1 = 0; var kill_c2 = 0; var dice1 = 0; var dice2 = 0; var changed = 0; var i = 0; for (i = 0; i < ExpParams.diceCount; i++) { dice1 = dice1 + Math.random(ExpParams.diceSides) + 1; dice2 = dice2 + Math.random(ExpParams.diceSides) + 1; } var energy1 = 0.0 + ExpParams.creatureDamage * dice1; var energy2 = 0.0 + ExpParams.creatureDamage * dice2; energy1 += ExpParams.upgradeMultiplier * c1.user1.get(1); energy2 += ExpParams.upgradeMultiplier * c2.user1.get(1); if (c1.group == c2.group) { energy1 = ExpParams.friendlyFireDamageMultiplier * energy1; energy2 = ExpParams.friendlyFireDamageMultiplier * energy2; } if (c1.user1 == null) stop("3"); if (c1.user2 == null) stop("3"); if (c1.user3 == null) stop("3"); if (c2.user1 == null) stop("3"); if (c2.user2 == null) stop("3"); if (c2.user2 == null) stop("3"); if (c2.user1.get(0) <= 0 && energy2 > 0) { changed = 1; c1.energy = c1.energy - energy2; if (c1.energy < 0) c1.energy = 0; print(c2.name + " [" + c2.group.name + "] rolled " + dice2 + " and dealt " + energy2 + " [+" + (ExpParams.upgradeMultiplier * c2.user1.get(1)) +" bonus] damage to " + c1.name + " [" + c1.group.name + "]"); c2.user1.set(0, ExpParams.attackCooldown); c2.user2.set(2, c2.user2.get(2) + energy1); c1.user2.set(3, c1.user2.get(3) + energy1); var vect = Vector.new(); vect.add(c2.uid); vect.add(c2.name); var arrindex = arrayContains(c1.user3, 0, c2.uid); if (arrindex != -1) { vect.add(energy2 + c1.user3.get(arrindex).get(2)); c1.user3.set(arrindex, vect); } else { vect.add(energy2); if (c1.user3 == null) c1.user3 = Vector.new(); c1.user3.add(vect); if (c1.energy > 0) { c2.user2.set(1, c2.user2.get(1) + 1); //increase assists statistic } } if (c1.energy <= 0) { c2.user2.set(0, c2.user2.get(0) + 1); //increase kills statistic kill_c1 = 1; } } if (c1.user1.get(0) <= 0 && energy1 > 0) { changed = 1; c2.energy = c2.energy - energy1; if (c2.energy < 0) c2.energy = 0; print(c1.name + " [" + c1.group.name + "] rolled " + dice1 + " and dealt " + energy1 + " [+" + (ExpParams.upgradeMultiplier * c1.user1.get(1)) +" bonus] damage to " + c2.name + " [" + c2.group.name + "]"); c1.user1.set(0, ExpParams.attackCooldown); c1.user2.set(2, c1.user2.get(2) + energy1); c2.user2.set(3, c2.user2.get(3) + energy1); var vect = Vector.new(); vect.add(c1.uid); vect.add(c1.name); var arrindex = arrayContains(c2.user3, 0, c1.uid); if (arrindex != -1) { vect.add(energy1 + c2.user3.get(arrindex).get(2)); c2.user3.set(arrindex, vect); } else { vect.add(energy1); if (c2.user3 == null) c2.user3 = Vector.new(); c2.user3.add(vect); if (c2.energy > 0) { c1.user2.set(1, c1.user2.get(1) + 1); //increase assists statistic } } if (c2.energy <= 0) { c1.user2.set(0, c1.user2.get(0) + 1); //increase kills statistic kill_c2 = 1; } } if (changed == 1) { writeTeamStatistics(); } //resolve kills if (kill_c1 == 1) { Populations.group = c1.group.index; Populations.creature = c1.group.findUID(c1.uid); if (Populations.creature == -1) stop("4"); Populations.killSelected(); } if (kill_c2 == 1) { Populations.group = c2.group.index; Populations.creature = c2.group.findUID(c2.uid); if (Populations.creature == -1) stop("4"); Populations.killSelected(); } } //function executed on collision with Food population group function onFoodCollision() { if (ExpParams.pickupNotStagnation == 1) idleSimulationSteps = 0; //collect HP Box (by eating it :D) print(Collision.Creature2.name + " [" + Collision.Creature2.group.name + "] picked a HP box"); Collision.Creature2.user2.set(4, Collision.Creature2.user2.get(4) + 1); Collision.Creature1.energy_m = Collision.Creature1.energy_m + Collision.Creature1.energy; Collision.Creature2.energy_p = Collision.Creature2.energy_p + Collision.Creature1.energy; //kill HP Box Populations.group = Collision.Creature1.group.index; Populations.creature = Collision.Creature1.group.findUID(Collision.Creature1.uid); Populations.killSelected(); writeTeamStatistics(); } //function executed on collision with Upgrade population group function onUpgradesCollision() { if (ExpParams.pickupNotStagnation == 1) idleSimulationSteps = 0; //collect Upgrade Box (by eating it :D) print(Collision.Creature2.name + " [" + Collision.Creature2.group.name + "] picked an upgrade box"); Collision.Creature2.user2.set(5, Collision.Creature2.user2.get(5) + 1); Collision.Creature1.energy_m = Collision.Creature1.energy_m + Collision.Creature1.energy; Collision.Creature2.user1.set(1, Collision.Creature2.user1.get(1) + Collision.Creature1.energy); //kill Upgrade Box Populations.group = Collision.Creature1.group.index; Populations.creature = Collision.Creature1.group.findUID(Collision.Creature1.uid); Populations.killSelected(); writeTeamStatistics(); } //function executed on Creature death //TODO: implement some statistics for dieing creature function onDied() { //ignore death of hp boxes and upgrades if (Populations.group < 2) return ; print(Creature.name + " was killed"); var i = 0; for (i = 0; i < Creature.user3.size; i++) { print(Creature.user3.get(i).get(1) + " dealt " + Creature.user3.get(i).get(2) + " damage"); } var f=File.appendDirect("creatures"+battleNumber+".txt","Creature statistics"); //f.writeString("Creature name; Team; Kills; Assists; Total damage dealt; Total damage received; HP boxes collected; Upgrade boxes collected; Lifespan"); f.writeString(Creature.name + "; " + Population.name + "; " + Creature.user2.get(0) + "; " + Creature.user2.get(1) + "; " + Creature.user2.get(2) + "; " + Creature.user2.get(3) + "; " + Creature.user2.get(4) + "; " + Creature.user2.get(5) + "; " + Creature.lifespan + "\n"); f.close(); writeTeamStatistics(); } // //////////////////////////// // ExpParams setters //reinit populations on change function ExpParams_teamCount_set() { initPopulations(); } function ExpParams_level_set() { initLevel(); } ~ # ################################ # Team prop: id:teamCount name:Number of teams in Deathmatch type:d 1 10 1 group:Team help:If set to 1, the number of teams will be equal to the number of genotypes in gene pool. prop: id:teamSize name:Number of creatures per team type:d 1 10 5 group:Team prop: id:teamSpawningAreaSize name:Spawning area size for team type:f 10 30 20 group:Team prop: id:creatureStartingEnergy name:Starting energy of creature type:f 0 1000 300 group:Team help:Base starting energy level for each creature in teams # ################################ # Attack parameters prop: id:diceSides name:Number of sides of dices type:f 1 10 1 group:Attack rules prop: id:diceCount name:Number of dices to roll per attack type:f 0 10 5 group:Attack rules prop: id:creatureDamage name:Basic damage multiplied by result of dice roll type:f 0 100 10 group:Attack rules prop: id:friendlyFireDamageMultiplier name:Multiplier of energy taken when Creatures of the same team collide type:f 0 1 0.0 group:Attack rules help:Set to 0 for no friendly fire, set to 1 if full damage should be dealt when interacting with team member prop: id:attackCooldown name:Number of simulation steps between two consecutive attacks of a creature type:f 100 10000 1000 group:Attack rules help:Set this to 100 to nearly instantly resolve battles # ################################ # Collectibles - HP Boxes & Upgrades prop: id:pickupNotStagnation name:Collectible pick-up resets stagnation counter? type:d 0 1 1 help:If set to true picking up an item will restart counting towards stagnation, effects in longer battles. prop: id:hpBoxStartingEnergy name:Starting energy of HP Box type:f 0 1000 100 group:Collectibles help:Base starting energy level for HP Box prop: id:hpBoxIdleEnergy name:Amount of energy lost by HP Box type:f 0 10 0.00 group:Collectibles help:How much energy HP Box looses each step (0 - it will not disappear) prop: id:hpBoxProbability name:Probablity that new HP Box will spawn type:f 0 1 0.0005 group:Collectibles help:Probability of HP Box appearing each step of simulation prop: id:hpBoxTotalCount name:Maximum number of HP Boxes on the field type:d 0 20 10 group:Collectibles help:The total number of HP Boxes on the field will never exceed this number prop: id:upgradeBoxStartingEnergy name:Starting energy of Upgrade Box type:f 0 1000 500 group:Collectibles help:Base starting energy level for Upgrade Box prop: id:upgradeBoxIdleEnergy name:Amount of energy lost by Upgrade Box type:f 0 10 0.00 group:Collectibles help:How much energy Upgrade Box looses each step (0 - it will not disappear) prop: id:upgradeBoxProbability name:Probablity that new Upgrade Box will spawn type:f 0 1 0.0005 group:Collectibles help:Probability of Upgrade Box appearing each step of simulation prop: id:upgradeBoxTotalCount name:Maximum number of Upgrade Boxes on the field type:d 0 20 10 group:Collectibles help:The total number of Upgrade Boxes on the field will never exceed this number prop: id:upgradeBoxDecay name:How fast upgrade boost fades from creature type:f 0 10 0.1 group:Collectibles help:Each step creature damage bonus will be decreased by given amount until it reaches 0 prop: id:upgradeMultiplier name:Multiplier of bonus damage type:f 0 1 0.1 group:Collectibles help:Value of collected bonus multiplied by this variable is taken as bonus damage # ################################ # Other prop: id:level name:Level type:d -1 2 0 help:Number of a level to battle (-1 is random) prop: id:stagnationInterval name:Number of idle simulation steps before Sudden Death type:d 0 10000 5000 help:If no combat occurs during given number of steps, the Sudden Death mode is turned on (0 = Sudden Death all the time) prop: id:stagnationHealthReduce name:Sudden Death health reduce type:f 0 100 0.1 help:If Suddent Death is turned on, creatures will lose given amount of HP each simulation step prop: id:debug name:Show additional debug messages type:d 0 1 0