diff --git a/scripts/gui/microbe_hud.mjs b/scripts/gui/microbe_hud.mjs index 4cdf037fbbb..a1924640dfa 100644 --- a/scripts/gui/microbe_hud.mjs +++ b/scripts/gui/microbe_hud.mjs @@ -444,7 +444,7 @@ function checkExtinction(population){ } function checkGeneration (generation, population){ - if(generation >= 20 && population >= 500 && wonOnce == false){ + if(generation >= 20 && population >= 400 && wonOnce == false){ document.getElementById("winTitle").style.display = "inline-block"; document.getElementById("winBody").style.display = "inline-block"; document.getElementById("winContainer").style.display = "inline-block"; diff --git a/scripts/gui/thrive_gui.html b/scripts/gui/thrive_gui.html index 020882f8215..9bde8728e47 100644 --- a/scripts/gui/thrive_gui.html +++ b/scripts/gui/thrive_gui.html @@ -326,7 +326,7 @@

For this Release if your population (top tab) drops to zero you go extinct.
-But if you survive for twenty generations with 500 population, you are considered to have won, after winning you get a popup and can continue playing as you wish. +But if you survive for twenty generations with 400 population, you are considered to have won, after winning you get a popup and can continue playing as you wish.

Be wary because your competitors are evolving alongside you. Every time you enter the editor they evolve as well.

diff --git a/scripts/microbe_stage/configs.as b/scripts/microbe_stage/configs.as index 83e592cf76c..3930895c47a 100644 --- a/scripts/microbe_stage/configs.as +++ b/scripts/microbe_stage/configs.as @@ -40,9 +40,14 @@ const auto MAX_OPACITY_MUTATION = 0.01f; // Mutation Variables const auto MUTATION_BACTERIA_TO_EUKARYOTE = 1; -const auto MUTATION_CREATION_RATE = 0.1f; +const auto MUTATION_CREATION_RATE = 0.5f; +const auto MUTATION_EXTRA_CREATION_RATE = 0.1f; const auto MUTATION_DELETION_RATE = 0.1f; -const auto MUTATION_REPLACEMENT_RATE = 0.1f; +const auto MUTATION_REPLACEMENT_RATE = 0.3f; + +// Genus splitting and name mutation +const auto MUTATION_CHANGE_GENUS = 33; +const auto MUTATION_WORD_EDIT = 75; //Removal cost const auto ORGANELLE_REMOVE_COST = 10; diff --git a/scripts/microbe_stage/microbe.as b/scripts/microbe_stage/microbe.as index e51500de5b5..83c0cbf9687 100644 --- a/scripts/microbe_stage/microbe.as +++ b/scripts/microbe_stage/microbe.as @@ -465,11 +465,16 @@ class MicrobeSystem : ScriptSystem{ if (microbeComponent.hostileEngulfer != NULL_OBJECT){ auto predatorPosition = world.GetComponent_Position(microbeComponent.hostileEngulfer); auto ourPosition = world.GetComponent_Position(microbeEntity); + auto predatorMembraneComponent = world.GetComponent_MembraneComponent(microbeComponent.hostileEngulfer); + auto circleRad = predatorMembraneComponent.calculateEncompassingCircleRadius(); MicrobeComponent@ hostileMicrobeComponent = cast( world.GetScriptComponentHolder("MicrobeComponent").Find(microbeComponent.hostileEngulfer)); + if (hostileMicrobeComponent.isBacteria){ + circleRad = circleRad/2; + } if ((hostileMicrobeComponent is null) || (!hostileMicrobeComponent.engulfMode) || (hostileMicrobeComponent.dead) || (ourPosition._Position - predatorPosition._Position).LengthSquared() >= - ((hostileMicrobeComponent.totalHexCountCache+3)*HEX_SIZE)+50){ + circleRad){ microbeComponent.hostileEngulfer = NULL_OBJECT; microbeComponent.isBeingEngulfed = false; } @@ -802,7 +807,7 @@ class MicrobeSystem : ScriptSystem{ if(organellesToAdd.length() > 0){ // Redo the cell membrane. membraneComponent.clear(); - MicrobeOperations::rebuildProcessList(world, microbeEntity); + MicrobeOperations::rebuildProcessList(world, microbeEntity); } if(reproductionStageComplete && microbeComponent.reproductionStage < 2){ diff --git a/scripts/microbe_stage/microbe_operations.as b/scripts/microbe_stage/microbe_operations.as index e4ef6bf3abe..9b5902f25b5 100644 --- a/scripts/microbe_stage/microbe_operations.as +++ b/scripts/microbe_stage/microbe_operations.as @@ -906,14 +906,6 @@ ObjectID spawnMicrobe(CellStageWorld@ world, Float3 pos, const string &in specie if(pos.Y != 0) LOG_WARNING("spawnMicrobe: spawning at y-coordinate: " + pos.Y); - auto processor = getProcessorComponent(world, speciesName); - - if(processor is null){ - LOG_ERROR("Skipping microbe spawn because species '" + speciesName + - "' doesn't have a processor component"); - - return NULL_OBJECT; - } // Create microbeEntity with correct template, physics and species name auto microbeEntity = _createMicrobeEntity(world, aiControlled, speciesName, @@ -958,16 +950,6 @@ ObjectID spawnBacteria(CellStageWorld@ world, Float3 pos, const string &in speci if(pos.Y != 0) LOG_WARNING("spawnBacteria: spawning at y-coordinate: " + pos.Y); - auto processor = getProcessorComponent(world, speciesName); - - if(processor is null){ - - LOG_ERROR("Skipping microbe spawn because species '" + speciesName + - "' doesn't have a processor component"); - - return NULL_OBJECT; - } - // Create microbeEntity with correct template, physics and species name auto microbeEntity = _createMicrobeEntity(world, aiControlled, speciesName, // in_editor @@ -1087,17 +1069,6 @@ ObjectID _createMicrobeEntity(CellStageWorld@ world, bool aiControlled, return entity; } - auto processor = world.GetComponent_ProcessorComponent(speciesEntity); - - if(processor is null){ - LOG_ERROR("Microbe species '" + microbeComponent.speciesName + - "' doesn't have a processor component"); - } else { - // Each microbe now has their own processor component to allow - // the process system to run safely while species are deleted - Species::copyProcessesFromSpecies(world, species, entity); - } - if(microbeComponent.organelles.length() > 0) assert(false, "Freshly created microbe has organelles in it"); @@ -1108,6 +1079,7 @@ ObjectID _createMicrobeEntity(CellStageWorld@ world, bool aiControlled, // up to date with the species so either this should apply the species processes OR // there should be a ProcessConfiguration object that would be shared between the // ProcessorComponent both in the species and individual cells + // this also sets up the processor component Species::applyTemplate(world, entity, species, shape); // ------------------------------------ // @@ -1115,6 +1087,7 @@ ObjectID _createMicrobeEntity(CellStageWorld@ world, bool aiControlled, assert(microbeComponent.organelles.length() > 0, "Microbe has no " "organelles in initializeMicrobe"); + auto rigidBody = world.Create_Physics(entity, position); _applyMicrobeCollisionShape(world, rigidBody, microbeComponent, shape); diff --git a/scripts/microbe_stage/procedural_microbes.as b/scripts/microbe_stage/procedural_microbes.as index 48f8404bdf8..7a5e26bf76e 100644 --- a/scripts/microbe_stage/procedural_microbes.as +++ b/scripts/microbe_stage/procedural_microbes.as @@ -281,6 +281,7 @@ string mutateMicrobe(const string &in stringCode, bool isBacteria) completeString = join(modifiedArray,"|"); + // Can add up to 6 new organelles (Which should allow AI to catch up to player more // We can insert new organelles at the end of the list if(GetEngine().GetRandom().GetNumber(0.f, 1.f) < MUTATION_CREATION_RATE){ auto organelleList = positionOrganelles(completeString); @@ -292,6 +293,25 @@ string mutateMicrobe(const string &in stringCode, bool isBacteria) completeString+="|"+returnedGenome; } + /* + Probability of mutation occuring 5 time(s) = 0.15 = 1.0E-5 + Probability of mutation NOT occuring = (1 - 0.1)5 = 0.59049 + Probability of mutation occuring = 1 - (1 - 0.1)5 = 0.40951 + */ + // We can insert new organelles at the end of the list + for(int n = 0; n < 5; n++ ){ + // We can insert new organelles at the end of the list + if(GetEngine().GetRandom().GetNumber(0.f, 1.f) < MUTATION_EXTRA_CREATION_RATE){ + auto organelleList = positionOrganelles(completeString); + const auto letter = getRandomLetter(isBacteria); + string name = string(organelleLetters[letter]); + string returnedGenome = translateOrganelleToGene(getRealisticPosition(name,organelleList)); + //LOG_INFO("Adding"); + //LOG_INFO("chromosomes:"+returnedGenome); + completeString+="|"+returnedGenome; + } + } + LOG_INFO("Mutated: "+completeString); return completeString; } diff --git a/scripts/microbe_stage/setup.as b/scripts/microbe_stage/setup.as index c70f8b58e89..d20c763fa06 100644 --- a/scripts/microbe_stage/setup.as +++ b/scripts/microbe_stage/setup.as @@ -233,11 +233,7 @@ void onReturnFromEditor(CellStageWorld@ world) SpeciesComponent@ ourActualSpecies = MicrobeOperations::getSpeciesComponent(world, player); auto membraneComponent = world.GetComponent_MembraneComponent(player); - // Call this before creating the clone. - Species::initProcessorComponent(world, player, ourActualSpecies); // Can probabbly wrap this into the usual init to keep things clean - // This makes sure that the player's processer are up to date with their species - Species::copyProcessesFromSpecies(world, ourActualSpecies, player); PlayerSpeciesSpawner factory("Default"); @@ -256,6 +252,7 @@ void onReturnFromEditor(CellStageWorld@ world) world.GetScriptComponentHolder("MicrobeComponent").Find(player)); // Reset the player cell to be the same as the species template + // This also sets the processor component Species::restoreOrganelleLayout(world, player, microbeComponent, playerSpecies); // Reset Players reproduction diff --git a/scripts/microbe_stage/species_system.as b/scripts/microbe_stage/species_system.as index 05c6ae09368..78360f6556a 100644 --- a/scripts/microbe_stage/species_system.as +++ b/scripts/microbe_stage/species_system.as @@ -65,40 +65,60 @@ Float4 randomProkayroteColour(float opaqueness = randomOpacityBacteria()) } string mutateWord(string name) { + // const array vowels = {"a", "e", "i", "o", "u"}; + const array pronoucablePermutation = {"th", "sh", "ch", "wh", "Th", "Sh", "Ch", "Wh"}; const array consonants = {"b", "c", "d", "f", "g", "h", "j", "k", "l", "m", - "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z"}; + "n", "p", "q", "s", "t", "v", "w", "x", "y", "z"}; string newName = name; - int changeLimit = 4; + int changeLimit = 1; + int letterChangeLimit = 2; + int letterChanges=0; int changes=0; - //Ignore the first letter and last letter - for(uint i = 1; i < newName.length()-1; i++) { - // Index we are adding or erasing chromosomes at - uint index = newName.length() - i - 1; - - // Are we a vowel or are we a consonant? - bool isVowel = vowels.find(newName.substr(index,1)) >= 0; - - //30 percent chance replace - if(GetEngine().GetRandom().GetNumber(0,20) <= 6 && changes <= changeLimit) { - newName.erase(index, 1); - changes++; - - if (isVowel) - newName.insert(index, randomChoice(vowels)); - else - newName.insert(index, randomChoice(consonants)); + // th, sh, ch, wh + for(uint i = 1; i < newName.length(); i++) { + if(changes <= changeLimit && i > 1){ + // Index we are adding or erasing chromosomes at + uint index = newName.length() - i - 1; + // Are we a vowel or are we a consonant? + bool isPermute = pronoucablePermutation.find(newName.substr(index,2)) > 0; + string original = newName.substr(index, 2); + if (GetEngine().GetRandom().GetNumber(0,20) <= 10 && isPermute){ + newName.erase(index, 2); + changes++; + newName.insert(index,randomChoice(pronoucablePermutation)); + } } + } + + // 2% chance each letter + for(uint i = 1; i < newName.length(); i++) { + if(GetEngine().GetRandom().GetNumber(0,120) <= 1 && changes <= changeLimit){ + // Index we are adding or erasing chromosomes at + uint index = newName.length() - i - 1; + + // Are we a vowel or are we a consonant? + bool isVowel = vowels.find(newName.substr(index,1)) >= 0; + + bool isPermute=false; + if (i > 1){ + if (pronoucablePermutation.find(newName.substr(index-1,2)) > 0 || + pronoucablePermutation.find(newName.substr(index-2,2)) > 0 || + pronoucablePermutation.find(newName.substr(index,2)) > 0){ + isPermute=true; + //LOG_INFO(i + ":"+newName.substr(index-1,2)); + //LOG_INFO(i + ":"+newName.substr(index-2,2)); + //LOG_INFO(i + ":"+newName.substr(index,2)); + } + } - //10 percent chance new syllable - if(GetEngine().GetRandom().GetNumber(0,20) <= 2 && changes <= changeLimit){ string original = newName.substr(index, 1); - newName.erase(index, 1); - changes++; - if (!isVowel){ + if (!isVowel && newName.substr(index,1)!="r" && !isPermute){ + newName.erase(index, 1); + changes++; switch (GetEngine().GetRandom().GetNumber(0,5)) { case 0: newName.insert(index, randomChoice(vowels) + randomChoice(consonants)); @@ -120,9 +140,10 @@ string mutateWord(string name) { break; } } - // If is vowel - else { + else if (newName.substr(index,1)!="r" && !isPermute){ + newName.erase(index, 1); + changes++; if(GetEngine().GetRandom().GetNumber(0,20) <= 10) newName.insert(index, randomChoice(consonants) + randomChoice(vowels) + original); else @@ -131,14 +152,63 @@ string mutateWord(string name) { } } + //Ignore the first letter and last letter + for(uint i = 1; i < newName.length(); i++) { + // Index we are adding or erasing chromosomes at + uint index = newName.length() - i - 1; + + bool isPermute=false; + if (i > 1){ + if (pronoucablePermutation.find(newName.substr(index-1,2)) > 0 || + pronoucablePermutation.find(newName.substr(index-2,2)) > 0 || + pronoucablePermutation.find(newName.substr(index,2)) > 0){ + isPermute=true; + //LOG_INFO(i + ":"+newName.substr(index-1,2)); + //LOG_INFO(i + ":"+newName.substr(index-2,2)); + //LOG_INFO(i + ":"+newName.substr(index,2)); + } + } + + // Are we a vowel or are we a consonant? + bool isVowel = vowels.find(newName.substr(index,1)) >= 0; + + //50 percent chance replace + if(GetEngine().GetRandom().GetNumber(0,20) <= 10 && changes <= changeLimit) { + if (!isVowel && newName.substr(index,1)!="r" && !isPermute){ + newName.erase(index, 1); + letterChanges++; + newName.insert(index, randomChoice(consonants)); + } + else if (!isPermute){ + newName.erase(index, 1); + letterChanges++; + newName.insert(index, randomChoice(vowels)); + } + } + + } + // Our base case - if(changes == 0) { + if(letterChanges < letterChangeLimit && changes==0 ) { //We didnt change our word at all, try again recursviely until we do return mutateWord(name); } + // Convert to lower case + for(uint i = 1; i < newName.length()-1; i++) { + if(newName[i]>=65 && newName[i]<=92){ + newName[i]=newName[i]+32; + } + } + + // Convert first letter to upper case + if(newName[0]>=97 && newName[0]<=122){ + newName[0]=newName[0]-32; + } + + LOG_INFO("Mutating Name:"+name +" to new name:"+newName); - return newName; + return newName;; } string generateNameSection() @@ -272,7 +342,7 @@ class Species{ name = randomSpeciesName(); //Mutate the epithet - if (GetEngine().GetRandom().GetNumber(0, 10) < 5){ + if (GetEngine().GetRandom().GetNumber(0, 100) < MUTATION_WORD_EDIT){ epithet = mutateWord(parent.epithet); } else { @@ -295,11 +365,11 @@ class Species{ LOG_INFO("X:"+parent.colour.X+" Y:"+parent.colour.Y+" Z:"+parent.colour.Z+" W:"+parent.colour.W); LOG_INFO("X:"+colour.X+" Y:"+colour.Y+" Z:"+colour.Z+" W:"+colour.W); // Chance of new color needs to be low - if (GetEngine().GetRandom().GetNumber(0,100) <= 20) + if (GetEngine().GetRandom().GetNumber(0,100) <= MUTATION_CHANGE_GENUS) { LOG_INFO("New Genus"); // We can do more fun stuff here later - if (GetEngine().GetRandom().GetNumber(0, 10) < 5){ + if (GetEngine().GetRandom().GetNumber(0, 100) < MUTATION_WORD_EDIT){ genus = mutateWord(parent.genus); } else { @@ -593,7 +663,7 @@ class Species{ genus = parent.genus; colour=parent.colour; //Mutate the epithet - if (GetEngine().GetRandom().GetNumber(0, 10) < 5){ + if (GetEngine().GetRandom().GetNumber(0, 100) < MUTATION_WORD_EDIT){ epithet = mutateWord(parent.epithet); } else { @@ -616,13 +686,13 @@ class Species{ LOG_INFO("X:"+parent.colour.X+" Y:"+parent.colour.Y+" Z:"+parent.colour.Z+" W:"+parent.colour.W); LOG_INFO("X:"+colour.X+" Y:"+colour.Y+" Z:"+colour.Z+" W:"+colour.W); - if (GetEngine().GetRandom().GetNumber(0,100) <= 20) + if (GetEngine().GetRandom().GetNumber(0,100) <= MUTATION_CHANGE_GENUS) { LOG_INFO("New Genus of bacteria"); // We can do more fun stuff here later //Mutate the genus - if (GetEngine().GetRandom().GetNumber(0, 10) < 5){ + if (GetEngine().GetRandom().GetNumber(0, 100) < MUTATION_WORD_EDIT){ genus = mutateWord(parent.genus); } else { @@ -716,14 +786,14 @@ class Species{ const auto INITIAL_POPULATION = 3000; // How much time does it take for the simulation to update. -const auto SPECIES_SIM_INTERVAL = 5000; +const auto SPECIES_SIM_INTERVAL = 2500; // If a specie's population goes below this it goes extinct. const auto MIN_POP_SIZE = 500; // If a specie's population goes above this it gets split in half and a // new mutated species apears. this should be randomized -const auto MAX_POP_SIZE = 6000; +const auto MAX_POP_SIZE = 3000; // The amount of species at the start of the microbe stage (not counting Default/Player) const auto INITIAL_SPECIES = 7; @@ -1040,7 +1110,6 @@ void applyTemplate(CellStageWorld@ world, ObjectID microbe, SpeciesComponent@ sp MicrobeComponent@ microbeComponent = cast( world.GetScriptComponentHolder("MicrobeComponent").Find(microbe)); - // TODO: Make this also set the microbe's ProcessorComponent microbeComponent.speciesName = species.name; MicrobeOperations::setMembraneType(world, microbe, species.speciesMembraneType); MicrobeOperations::setMembraneColour(world, microbe, species.colour); @@ -1090,110 +1159,10 @@ void restoreOrganelleLayout(CellStageWorld@ world, ObjectID microbeEntity, microbeComponent.isBacteria = species.isBacteria; // Call this to reset processor component - Species::initProcessorComponent(world, microbeEntity, species); - // This makes sure that the microbes processer are up to date with their species - Species::copyProcessesFromSpecies(world, species, microbeEntity); -} - -void initProcessorComponent(CellStageWorld@ world, ObjectID entity, - SpeciesComponent@ speciesComponent) -{ - assert(world.GetComponent_SpeciesComponent(entity) !is speciesComponent, - "Wrong speciesComponent passed to initProcessorComponent"); - - initProcessorComponent(world,speciesComponent); -} - - -void initProcessorComponent(CellStageWorld@ world, - SpeciesComponent@ speciesComponent) -{ - assert(speciesComponent.organelles.length() > 0, "initProcessorComponent given a " - "species that has no organelles"); - auto speciesEntity = findSpeciesEntityByName(world, speciesComponent.name); - - ProcessorComponent@ processorComponent = world.GetComponent_ProcessorComponent( - speciesEntity); - - dictionary capacities; - for(uint i = 0; i < speciesComponent.organelles.length(); i++){ - - const Organelle@ organelleDefinition = cast(speciesComponent.organelles[i]).organelle; - if(organelleDefinition is null){ - - LOG_ERROR("Organelle table has a null organelle in it, position: " + i + - "', that was added to a species entity"); - continue; - } - - for(uint processNumber = 0; - processNumber < organelleDefinition.processes.length(); ++processNumber) - { - // This name needs to match the one in bioProcessRegistry - TweakedProcess@ process = organelleDefinition.processes[processNumber]; - - if(!capacities.exists(process.process.internalName)){ - capacities[process.process.internalName] = double(0.0f); - } - - // Here the second capacities[process.name] was initially capacities[process] - // but the processes are just strings inside the Organelle class - capacities[process.process.internalName] = double(capacities[ - process.process.internalName]) + - process.capacity; - } - } - - uint64 processCount = SimulationParameters::bioProcessRegistry().getSize(); - for(BioProcessId bioProcessId = 0; bioProcessId < processCount; ++bioProcessId){ - auto processName = SimulationParameters::bioProcessRegistry().getInternalName( - bioProcessId); - - if(capacities.exists(processName)){ - double capacity; - if(!capacities.get(processName, capacity)){ - LOG_ERROR("capacities has invalid value"); - continue; - } - - // LOG_INFO("Process: " + processName + " Capacity: " + capacity); - processorComponent.setCapacity(bioProcessId, capacity); - } else { - // If it doesnt exist: - capacities.set(processName, 0.0f); - - // This is related to https://github.com/Revolutionary-Games/Thrive/issues/599 - processorComponent.setCapacity(bioProcessId, 0.0f); - } - } + MicrobeOperations::rebuildProcessList(world,microbeEntity); } -//! This function copies process data from a species to an entity with a ProcessorComponent -void copyProcessesFromSpecies(CellStageWorld@ world, - SpeciesComponent@ speciesComponent, ObjectID entity) -{ - ProcessorComponent@ cellProcessorComponent = world.GetComponent_ProcessorComponent( - entity); - if(cellProcessorComponent is null) - { - LOG_ERROR("Entity doesn't have a ProcessorComponent for process copy target"); - return; - } - - auto speciesEntity = findSpeciesEntityByName(world, speciesComponent.name); - - ProcessorComponent@ speciesProcessor = world.GetComponent_ProcessorComponent( - speciesEntity); - - if(speciesProcessor is null){ - LOG_ERROR("Species lacks processor component for copying processes from"); - return; - } - - // Use assignment operator to copy all data - cellProcessorComponent = speciesProcessor; -} //! Creates a species from the initial template. This doesn't register with SpeciesSystem //! because this is (currently) only used for the player's species which isn't managed by it @@ -1261,9 +1230,6 @@ ObjectID createSpecies(CellStageWorld@ world, const string &in name, const strin } } - ProcessorComponent@ processorComponent = world.Create_ProcessorComponent( - speciesEntity); - speciesComponent.colour = colour; speciesComponent.speciesMembraneType = speciesMembraneType; @@ -1296,8 +1262,7 @@ ObjectID createSpecies(CellStageWorld@ world, const string &in name, const strin speciesComponent.avgCompoundAmounts[formatUInt(compound.id)] = compoundAmount; } - // Call init instead of going through the same code here again. - initProcessorComponent(world,speciesComponent); + return speciesEntity; }