diff --git a/assets/definitions/compounds.xml b/assets/definitions/compounds.xml
index 17365072f86..6e7be02f470 100644
--- a/assets/definitions/compounds.xml
+++ b/assets/definitions/compounds.xml
@@ -1,47 +1,47 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/scripts/microbe_editor/microbe_editor.lua b/scripts/microbe_editor/microbe_editor.lua
index 5da464206a4..43dff195c63 100644
--- a/scripts/microbe_editor/microbe_editor.lua
+++ b/scripts/microbe_editor/microbe_editor.lua
@@ -19,12 +19,12 @@ function MicrobeEditor:__init(hudSystem)
self.gridVisible = true
self.mutationPoints = 100
self.placementFunctions = {["nucleus"] = MicrobeEditor.createNewMicrobe,
- ["flagelium"] = MicrobeEditor.addMovementOrganelle,
- ["mitochondrion"] = MicrobeEditor.addProcessOrganelle,
- ["chloroplast"] = MicrobeEditor.addProcessOrganelle,
- ["toxin"] = MicrobeEditor.addAgentVacuole,
+ ["flagellum"] = MicrobeEditor.addOrganelle,
+ ["mitochondrion"] = MicrobeEditor.addOrganelle,
+ ["chloroplast"] = MicrobeEditor.addOrganelle,
+ ["oxytoxy"] = MicrobeEditor.addOrganelle,
- ["vacuole"] = MicrobeEditor.addStorageOrganelle,
+ ["vacuole"] = MicrobeEditor.addOrganelle,
-- ["aminosynthesizer"] = MicrobeEditor.addProcessOrganelle,
["remove"] = MicrobeEditor.removeOrganelle}
self.actionHistory = nil
@@ -54,6 +54,7 @@ function MicrobeEditor:activate()
end
function MicrobeEditor:update(renderTime, logicTime)
+ -- self.nextMicrobeEntity being a temporary used to pass the microbe from game to editor
if self.nextMicrobeEntity ~= nil then
self.currentMicrobe = Microbe(self.nextMicrobeEntity)
self.currentMicrobe.sceneNode.transform.orientation = Quaternion(Radian(Degree(180)), Vector3(0, 0, 1))-- Orientation
@@ -154,56 +155,14 @@ function MicrobeEditor:getMouseHex()
return qr, rr
end
-function MicrobeEditor:removeOrganelle()
- local q, r = self:getMouseHex()
- if not (q == 0 and r == 0) then -- Don't remove nucleus
- local organelle = self.currentMicrobe:getOrganelleAt(q,r)
- if organelle then
- local storage = organelle:storage()
- self:enqueueAction{
- cost = 10,
- redo = function()
- self.currentMicrobe:removeOrganelle(q, r)
- self.currentMicrobe.sceneNode.transform:touch()
- self.organelleCount = self.organelleCount - 1
- end,
- undo = function()
- self.currentMicrobe:addOrganelle(q, r, Organelle.loadOrganelle(storage))
- self.organelleCount = self.organelleCount + 1
- end
- }
- end
- end
-end
-
-
-function MicrobeEditor:addStorageOrganelle(organelleType)
- -- self.currentMicrobe = Microbe(Entity("working_microbe", GameState.MICROBE))
- local q, r = self:getMouseHex()
- if self.currentMicrobe:getOrganelleAt(q, r) == nil then
- self:enqueueAction{
- cost = Organelle.mpCosts["vacuole"],
- redo = function()
- self.currentMicrobe:addOrganelle(q, r, OrganelleFactory.make_vacuole({}))
- self.organelleCount = self.organelleCount + 1
- end,
- undo = function()
- self.currentMicrobe:removeOrganelle(q, r)
- self.currentMicrobe.sceneNode.transform:touch()
- self.organelleCount = self.organelleCount - 1
- end
- }
- end
-end
-
-
-function MicrobeEditor:addMovementOrganelle(organelleType)
+function MicrobeEditor:addOrganelle(organelleType)
local q, r = self:getMouseHex()
if self.currentMicrobe:getOrganelleAt(q, r) == nil then
- self:enqueueAction{
- cost = Organelle.mpCosts["flagellum"],
+ local data = {["name"]=organelleType, ["q"]=q, ["r"]=r}
+ self:enqueueAction({
+ cost = Organelle.mpCosts[organelleType],
redo = function()
- self.currentMicrobe:addOrganelle(q,r, OrganelleFactory.make_flagellum{["q"]=q, ["r"]=r})
+ self.currentMicrobe:addOrganelle(q,r, OrganelleFactory.makeOrganelle(data))
self.organelleCount = self.organelleCount + 1
end,
undo = function()
@@ -211,45 +170,26 @@ function MicrobeEditor:addMovementOrganelle(organelleType)
self.currentMicrobe.sceneNode.transform:touch()
self.organelleCount = self.organelleCount - 1
end
- }
+ })
end
end
-function MicrobeEditor:addProcessOrganelle(organelleType)
+function MicrobeEditor:removeOrganelle()
local q, r = self:getMouseHex()
-
- if organelleType and self.currentMicrobe:getOrganelleAt(q, r) == nil then
- local data = { ["name"] = organelleType }
-
- self:enqueueAction{
- cost = Organelle.mpCosts[data.name],
- redo = function()
- self.currentMicrobe:addOrganelle(q, r, OrganelleFactory.makeOrganelle(data))
- self.organelleCount = self.organelleCount + 1
- end,
- undo = function()
- self.currentMicrobe:removeOrganelle(q, r)
- self.currentMicrobe.sceneNode.transform:touch()
- self.organelleCount = self.organelleCount - 1
- end
- }
- end
-end
-
-function MicrobeEditor:addAgentVacuole(organelleType)
- if organelleType == "toxin" then
- local q, r = self:getMouseHex()
- if self.currentMicrobe:getOrganelleAt(q, r) == nil then
+ if not (q == 0 and r == 0) then -- Don't remove nucleus
+ local organelle = self.currentMicrobe:getOrganelleAt(q,r)
+ if organelle then
+ local storage = organelle:storage()
self:enqueueAction{
- cost = Organelle.mpCosts["oxytoxy"],
+ cost = 10,
redo = function()
- self.currentMicrobe:addOrganelle(q, r, OrganelleFactory.make_oxytoxy({}))
- self.organelleCount = self.organelleCount + 1
- end,
- undo = function()
self.currentMicrobe:removeOrganelle(q, r)
self.currentMicrobe.sceneNode.transform:touch()
self.organelleCount = self.organelleCount - 1
+ end,
+ undo = function()
+ self.currentMicrobe:addOrganelle(q, r, Organelle.loadOrganelle(storage))
+ self.organelleCount = self.organelleCount + 1
end
}
end
@@ -257,7 +197,7 @@ function MicrobeEditor:addAgentVacuole(organelleType)
end
function MicrobeEditor:addNucleus()
- local nucleusOrganelle = OrganelleFactory.make_nucleus({})
+ local nucleusOrganelle = OrganelleFactory.makeOrganelle({["name"]="nucleus"})
self.currentMicrobe:addOrganelle(0, 0, nucleusOrganelle)
end
@@ -270,7 +210,6 @@ function MicrobeEditor:loadMicrobe(entityId)
self.currentMicrobe.entity:stealName("working_microbe")
self.currentMicrobe.sceneNode.transform.orientation = Quaternion(Radian(Degree(180)), Vector3(0, 0, 1))-- Orientation
self.currentMicrobe.sceneNode.transform:touch()
- self.currentMicrobe.collisionHandler:addCollisionGroup("powerupable")
Engine:playerData():setActiveCreature(entityId, GameState.MICROBE_EDITOR)
self.mutationPoints = 0
-- resetting the action history - it should not become entangled with the local file system
@@ -289,7 +228,6 @@ function MicrobeEditor:createNewMicrobe()
self.currentMicrobe.entity:stealName("working_microbe")
self.currentMicrobe.sceneNode.transform.orientation = Quaternion(Radian(Degree(180)), Vector3(0, 0, 1))-- Orientation
self.currentMicrobe.sceneNode.transform:touch()
- self.currentMicrobe.collisionHandler:addCollisionGroup("powerupable")
self:addNucleus()
self.mutationPoints = 100
Engine:playerData():setActiveCreature(self.currentMicrobe.entity.id, GameState.MICROBE_EDITOR)
@@ -310,7 +248,6 @@ function MicrobeEditor:createNewMicrobe()
self.currentMicrobe.entity:stealName("working_microbe")
self.currentMicrobe.sceneNode.transform.orientation = Quaternion(Radian(Degree(180)), Vector3(0, 0, 1))-- Orientation
self.currentMicrobe.sceneNode.transform:touch()
- self.currentMicrobe.collisionHandler:addCollisionGroup("powerupable")
for position,storage in pairs(organelleStorage) do
local q, r = decodeAxial(position)
self.currentMicrobe:addOrganelle(q, r, Organelle.loadOrganelle(storage))
diff --git a/scripts/microbe_editor/microbe_editor_hud.lua b/scripts/microbe_editor/microbe_editor_hud.lua
index 129cc8061f9..e0bfdd7dfe0 100644
--- a/scripts/microbe_editor/microbe_editor_hud.lua
+++ b/scripts/microbe_editor/microbe_editor_hud.lua
@@ -34,20 +34,20 @@ function MicrobeEditorHudSystem:init(gameState)
-- self.mpProgressBar = root:getChild("BottomSection"):getChild("MutationPoints"):getChild("MPBar")
self.organelleScrollPane = root:getChild("scrollablepane");
local nucleusButton = root:getChild("NewMicrobe")
- local flageliumButton = root:getChild("scrollablepane"):getChild("AddFlagellum")
+ local flagellumButton = root:getChild("scrollablepane"):getChild("AddFlagellum")
local mitochondriaButton = root:getChild("scrollablepane"):getChild("AddMitochondria")
local vacuoleButton = root:getChild("scrollablepane"):getChild("AddVacuole")
local toxinButton = root:getChild("scrollablepane"):getChild("AddToxinVacuole")
local chloroplastButton = root:getChild("scrollablepane"):getChild("AddChloroplast")
self.organelleButtons["nucleus"] = nucleusButton
- self.organelleButtons["flagelium"] = flageliumButton
+ self.organelleButtons["flagellum"] = flagellumButton
self.organelleButtons["mitochondrion"] = mitochondriaButton
self.organelleButtons["chloroplast"] = chloroplastButton
self.organelleButtons["vacuole"] = vacuoleButton
self.organelleButtons["Toxin"] = toxinButton
self.activeButton = nil
nucleusButton:registerEventHandler("Clicked", function() self:nucleusClicked() end)
- flageliumButton:registerEventHandler("Clicked", function() self:flageliumClicked() end)
+ flagellumButton:registerEventHandler("Clicked", function() self:flagellumClicked() end)
mitochondriaButton:registerEventHandler("Clicked", function() self:mitochondriaClicked() end)
chloroplastButton:registerEventHandler("Clicked", function() self:chloroplastClicked() end)
vacuoleButton:registerEventHandler("Clicked", function() self:vacuoleClicked() end)
@@ -134,7 +134,7 @@ function MicrobeEditorHudSystem:update(renderTime, logicTime)
self.editor:performLocationAction()
end
elseif keyCombo(kmp.flagellum) then
- self:flageliumClicked()
+ self:flagellumClicked()
self.editor:performLocationAction()
elseif keyCombo(kmp.mitochondrion) then
self:mitochondriaClicked()
@@ -241,13 +241,13 @@ function MicrobeEditorHudSystem:nucleusClicked()
self:setActiveAction("nucleus")
end
-function MicrobeEditorHudSystem:flageliumClicked()
+function MicrobeEditorHudSystem:flagellumClicked()
if self.activeButton ~= nil then
self.activeButton:enable()
end
- self.activeButton = self.organelleButtons["flagelium"]
+ self.activeButton = self.organelleButtons["flagellum"]
self.activeButton:disable()
- self:setActiveAction("flagelium")
+ self:setActiveAction("flagellum")
end
function MicrobeEditorHudSystem:mitochondriaClicked()
@@ -292,7 +292,7 @@ function MicrobeEditorHudSystem:toxinClicked()
end
self.activeButton = self.organelleButtons["Toxin"]
self.activeButton:disable()
- self:setActiveAction("toxin")
+ self:setActiveAction("oxytoxy")
end
diff --git a/scripts/microbe_stage/manifest.txt b/scripts/microbe_stage/manifest.txt
index f5bed3db272..04cc0e237ee 100644
--- a/scripts/microbe_stage/manifest.txt
+++ b/scripts/microbe_stage/manifest.txt
@@ -6,6 +6,8 @@ microbe.lua
camera.lua
microbe_control.lua
spawn_system.lua
+species_system.lua
+patch_system.lua
switch_game_state_system.lua
// Organelles
diff --git a/scripts/microbe_stage/microbe.lua b/scripts/microbe_stage/microbe.lua
index 88cd10054d5..5430020ddb5 100644
--- a/scripts/microbe_stage/microbe.lua
+++ b/scripts/microbe_stage/microbe.lua
@@ -17,9 +17,9 @@ REPRODUCTASE_TO_SPLIT = 5
RELATIVE_VELOCITY_TO_BUMP_SOUND = 6
INITIAL_EMISSION_RADIUS = 2
-function MicrobeComponent:__init(isPlayerMicrobe)
+function MicrobeComponent:__init(isPlayerMicrobe, speciesName)
Component.__init(self)
- self.speciesName = "Default"
+ self.speciesName = speciesName
self.hitpoints = 10
self.maxHitpoints = 10
self.dead = false
@@ -140,6 +140,7 @@ function MicrobeComponent:load(storage)
self.maxBandwidth = storage:get("maxBandwidth", 0)
self.remainingBandwidth = storage:get("remainingBandwidth", 0)
self.isPlayerMicrobe = storage:get("isPlayerMicrobe", false)
+ self.speciesName = storage:get("speciesName", "")
local storedCompound = storage:get("storedCompounds", {})
for i = 1,storedCompound:size() do
local compound = storedCompound:get(i)
@@ -171,6 +172,7 @@ function MicrobeComponent:storage()
storage:set("remainingBandwidth", self.remainingBandwidth)
storage:set("maxBandwidth", self.maxBandwidth)
storage:set("isPlayerMicrobe", self.isPlayerMicrobe)
+ storage:set("speciesName", self.speciesName)
local storedCompounds = StorageList()
for compoundId, amount in pairs(self.compounds) do
compound = StorageContainer()
@@ -208,7 +210,7 @@ class 'Microbe'
--
-- @returns microbe
-- An object of type Microbe
-function Microbe.createMicrobeEntity(name, aiControlled)
+function Microbe.createMicrobeEntity(name, aiControlled, speciesName)
local entity
if name then
entity = Entity(name)
@@ -247,7 +249,7 @@ function Microbe.createMicrobeEntity(name, aiControlled)
local components = {
CompoundAbsorberComponent(),
OgreSceneNodeComponent(),
- MicrobeComponent(not aiControlled),
+ MicrobeComponent(not aiControlled, speciesName),
reactionHandler,
rigidBody,
compoundEmitter,
@@ -301,6 +303,12 @@ function Microbe:__init(entity)
self.playerAlreadyShownVictory = false
end
+-- Getter for microbe species
+--
+-- returns the species component or nil if it doesn't have a valid species
+function Microbe:getSpeciesComponent()
+ return Entity(self.microbe.speciesName):getComponent(SpeciesComponent.TYPE_ID)
+end
-- Adds a new organelle
--
@@ -690,22 +698,20 @@ function Microbe:kill()
self.microbe.movementDirection = Vector3(0,0,0)
self.rigidBody:clearForces()
microbeSceneNode.visible = false
+ --[[ since other microbes can kll each other now, this is deprecated
if self.microbe.isPlayerMicrobe ~= true then
if not self.playerAlreadyShownVictory then
self.playerAlreadyShownVictory = true
showMessage("VICTORY!!!")
end
end
+ --]]
end
--- Copies this microbe. The new microbe will not have the stored compounds of this one.
+-- Copies this microbe. The new microbe will not have the stored compounds of this one.
function Microbe:reproduce()
copy = Microbe.createMicrobeEntity(nil, true)
- for _, organelle in pairs(self.microbe.organelles) do
- local organelleStorage = organelle:storage()
- local organelle = Organelle.loadOrganelle(organelleStorage)
- copy:addOrganelle(organelle.position.q, organelle.position.r, organelle)
- end
+ self:getSpeciesComponent():template(copy)
copy.rigidBody.dynamicProperties.position = Vector3(self.rigidBody.dynamicProperties.position.x, self.rigidBody.dynamicProperties.position.y, 0)
copy:storeCompound(CompoundRegistry.getCompoundId("atp"), 20, false)
copy.microbe:_resetCompoundPriorities()
@@ -715,7 +721,6 @@ function Microbe:reproduce()
end
end
-
-- Updates the microbe's state
function Microbe:update(logicTime)
if not self.microbe.dead then
@@ -736,90 +741,113 @@ function Microbe:update(logicTime)
end
self.microbe.compoundCollectionTimer = self.microbe.compoundCollectionTimer + logicTime
- while self.microbe.compoundCollectionTimer > EXCESS_COMPOUND_COLLECTION_INTERVAL do -- For every COMPOUND_DISTRIBUTION_INTERVAL passed
- -- Gather excess compounds that are the compounds that the storage organelles automatically emit to stay less than full
- local excessCompounds = {}
- while self.microbe.stored/self.microbe.capacity > STORAGE_EJECTION_THRESHHOLD+0.01 do
- -- Find lowest priority compound type contained in the microbe
- local lowestPriorityId = nil
- local lowestPriority = math.huge
- for compoundId,_ in pairs(self.microbe.compounds) do
- assert(self.microbe.compoundPriorities[compoundId] ~= nil, "Compound priority table was missing compound")
- if self.microbe.compounds[compoundId] > 0 and self.microbe.compoundPriorities[compoundId] < lowestPriority then
- lowestPriority = self.microbe.compoundPriorities[compoundId]
- lowestPriorityId = compoundId
- end
- end
- assert(lowestPriorityId ~= nil, "The microbe didn't seem to contain any compounds but was over the threshold")
- assert(self.microbe.compounds[lowestPriorityId] ~= nil, "Microbe storage was over threshold but didn't have any valid compounds to expell")
- -- Return an amount that either is how much the microbe contains of the compound or until it goes to the threshhold
- local amountInExcess
-
- amountInExcess = math.min(self.microbe.compounds[lowestPriorityId],self.microbe.stored - self.microbe.capacity * STORAGE_EJECTION_THRESHHOLD)
- excessCompounds[lowestPriorityId] = self:takeCompound(lowestPriorityId, amountInExcess)
- end
- -- Expell compounds of priority 0 periodically
- for compoundId,_ in pairs(self.microbe.compounds) do
- if self.microbe.compoundPriorities[compoundId] == 0 and self.microbe.compounds[compoundId] > 1 then
- local uselessCompoundAmount
- uselessCompoundAmount = self.microbe:getBandwidth(self.microbe.compounds[compoundId], compoundId)
- if excessCompounds[compoundId] ~= nil then
- excessCompounds[compoundId] = excessCompounds[compoundId] + self:takeCompound(compoundId, uselessCompoundAmount)
- else
- excessCompounds[compoundId] = self:takeCompound(compoundId, uselessCompoundAmount)
- end
- end
- end
- for compoundId, amount in pairs(excessCompounds) do
- if amount > 0 then
- self:ejectCompound(compoundId, amount, 160, 200)
- end
- end
- -- Damage microbe if its too low on ATP
- if self.microbe.compounds[CompoundRegistry.getCompoundId("atp")] ~= nil and self.microbe.compounds[CompoundRegistry.getCompoundId("atp")] < 1.0 then
- if self.microbe.isPlayerMicrobe and not self.playerAlreadyShownAtpDamage then
- self.playerAlreadyShownAtpDamage = true
- showMessage("No ATP hurts you!")
- end
- self:damage(EXCESS_COMPOUND_COLLECTION_INTERVAL * 0.00002 * self.microbe.maxHitpoints) -- Microbe takes 2% of max hp per second in damage
- end
- -- Split microbe if it has enough reproductase
- if self.microbe.compounds[CompoundRegistry.getCompoundId("reproductase")] ~= nil and self.microbe.compounds[CompoundRegistry.getCompoundId("reproductase")] > REPRODUCTASE_TO_SPLIT then
- self:takeCompound(CompoundRegistry.getCompoundId("reproductase"), 5)
- self:reproduce()
- end
+ while self.microbe.compoundCollectionTimer > EXCESS_COMPOUND_COLLECTION_INTERVAL do
+ -- For every COMPOUND_DISTRIBUTION_INTERVAL passed
+
self.microbe.compoundCollectionTimer = self.microbe.compoundCollectionTimer - EXCESS_COMPOUND_COLLECTION_INTERVAL
+
+ self:purgeCompounds()
+
+ self:atpDamage()
+
+ self:attemptReproduce()
end
+
-- Other organelles
for _, organelle in pairs(self.microbe.organelles) do
organelle:update(self, logicTime)
end
+
+ self.compoundAbsorber:setAbsorbtionCapacity(self.microbe.remainingBandwidth)
else
self.microbe.deathTimer = self.microbe.deathTimer - logicTime
if self.microbe.deathTimer <= 0 then
if self.microbe.isPlayerMicrobe == true then
- self.microbe.dead = false
- self.microbe.deathTimer = 0
- self.residuePhysicsTime = 0
- self.microbe.hitpoints = self.microbe.maxHitpoints
-
- self.rigidBody:setDynamicProperties(
- Vector3(0,0,0), -- Position
- Quaternion(Radian(Degree(0)), Vector3(1, 0, 0)), -- Orientation
- Vector3(0, 0, 0), -- Linear velocity
- Vector3(0, 0, 0) -- Angular velocity
- )
- local sceneNode = self.entity:getComponent(OgreSceneNodeComponent.TYPE_ID)
- sceneNode.visible = true
- self:storeCompound(CompoundRegistry.getCompoundId("atp"), 20, false)
+ self:respawn()
else
self:destroy()
end
end
end
- self.compoundAbsorber:setAbsorbtionCapacity(self.microbe.remainingBandwidth)
end
+function Microbe:purgeCompounds()
+ -- Gather excess compounds that are the compounds that the storage organelles automatically emit to stay less than full
+ local excessCompounds = {}
+ while self.microbe.stored/self.microbe.capacity > STORAGE_EJECTION_THRESHHOLD+0.01 do
+ -- Find lowest priority compound type contained in the microbe
+ local lowestPriorityId = nil
+ local lowestPriority = math.huge
+ for compoundId,_ in pairs(self.microbe.compounds) do
+ assert(self.microbe.compoundPriorities[compoundId] ~= nil, "Compound priority table was missing compound")
+ if self.microbe.compounds[compoundId] > 0 and self.microbe.compoundPriorities[compoundId] < lowestPriority then
+ lowestPriority = self.microbe.compoundPriorities[compoundId]
+ lowestPriorityId = compoundId
+ end
+ end
+ assert(lowestPriorityId ~= nil, "The microbe didn't seem to contain any compounds but was over the threshold")
+ assert(self.microbe.compounds[lowestPriorityId] ~= nil, "Microbe storage was over threshold but didn't have any valid compounds to expell")
+ -- Return an amount that either is how much the microbe contains of the compound or until it goes to the threshhold
+ local amountInExcess
+
+ amountInExcess = math.min(self.microbe.compounds[lowestPriorityId],self.microbe.stored - self.microbe.capacity * STORAGE_EJECTION_THRESHHOLD)
+ excessCompounds[lowestPriorityId] = self:takeCompound(lowestPriorityId, amountInExcess)
+ end
+
+ -- Expel compounds of priority 0 periodically
+ for compoundId,_ in pairs(self.microbe.compounds) do
+ if self.microbe.compoundPriorities[compoundId] == 0 and self.microbe.compounds[compoundId] > 1 then
+ local uselessCompoundAmount
+ uselessCompoundAmount = self.microbe:getBandwidth(self.microbe.compounds[compoundId], compoundId)
+ if excessCompounds[compoundId] ~= nil then
+ excessCompounds[compoundId] = excessCompounds[compoundId] + self:takeCompound(compoundId, uselessCompoundAmount)
+ else
+ excessCompounds[compoundId] = self:takeCompound(compoundId, uselessCompoundAmount)
+ end
+ end
+ end
+ for compoundId, amount in pairs(excessCompounds) do
+ if amount > 0 then
+ self:ejectCompound(compoundId, amount, 160, 200, true)
+ end
+ end
+end
+
+function Microbe:atpDamage()
+ -- Damage microbe if its too low on ATP
+ if self.microbe.compounds[CompoundRegistry.getCompoundId("atp")] ~= nil and self.microbe.compounds[CompoundRegistry.getCompoundId("atp")] < 1.0 then
+ if self.microbe.isPlayerMicrobe and not self.playerAlreadyShownAtpDamage then
+ self.playerAlreadyShownAtpDamage = true
+ showMessage("No ATP hurts you!")
+ end
+ self:damage(EXCESS_COMPOUND_COLLECTION_INTERVAL * 0.00002 * self.microbe.maxHitpoints) -- Microbe takes 2% of max hp per second in damage
+ end
+end
+
+function Microbe:attemptReproduce()
+ -- Split microbe if it has enough reproductase
+ if self.microbe.compounds[CompoundRegistry.getCompoundId("reproductase")] ~= nil and self.microbe.compounds[CompoundRegistry.getCompoundId("reproductase")] > REPRODUCTASE_TO_SPLIT then
+ self:takeCompound(CompoundRegistry.getCompoundId("reproductase"), 5)
+ self:reproduce()
+ end
+end
+
+function Microbe:respawn()
+ self.microbe.dead = false
+ self.microbe.deathTimer = 0
+ self.residuePhysicsTime = 0
+ self.microbe.hitpoints = self.microbe.maxHitpoints
+
+ self.rigidBody:setDynamicProperties(
+ Vector3(0,0,0), -- Position
+ Quaternion(Radian(Degree(0)), Vector3(1, 0, 0)), -- Orientation
+ Vector3(0, 0, 0), -- Linear velocity
+ Vector3(0, 0, 0) -- Angular velocity
+ )
+ local sceneNode = self.entity:getComponent(OgreSceneNodeComponent.TYPE_ID)
+ sceneNode.visible = true
+ self:storeCompound(CompoundRegistry.getCompoundId("atp"), 20, false)
+end
-- Private function for initializing a microbe's components
function Microbe:_initialize()
diff --git a/scripts/microbe_stage/microbe_replacement.lua b/scripts/microbe_stage/microbe_replacement.lua
index 9f8e49eccef..ac8f72bc2b4 100644
--- a/scripts/microbe_stage/microbe_replacement.lua
+++ b/scripts/microbe_stage/microbe_replacement.lua
@@ -5,22 +5,37 @@ class 'MicrobeReplacementSystem' (System)
global_newEditorMicrobe = false
function MicrobeReplacementSystem:__init()
+ self.globalSpeciesNameCounter = 1
System.__init(self)
end
function MicrobeReplacementSystem:activate()
activeCreatureId = Engine:playerData():activeCreature()
if Engine:playerData():isBoolSet("edited_microbe") then
- Engine:playerData():setBool("edited_microbe", false);
+ Engine:playerData():setBool("edited_microbe", false)
+
workingMicrobe = Microbe(Entity(activeCreatureId, GameState.MICROBE_EDITOR))
- if workingMicrobe:getCompoundAmount(CompoundRegistry.getCompoundId("atp")) == 0 then
- workingMicrobe:storeCompound(CompoundRegistry.getCompoundId("atp"), 10)
+
+ speciesEntity = Entity(workingMicrobe.microbe.speciesName, GameState.MICROBE)
+ species = SpeciesComponent(workingMicrobe.microbe.speciesName)
+ speciesEntity:addComponent(species)
+ self.globalSpeciesNameCounter = self.globalSpeciesNameCounter + 1
+ species:fromMicrobe(workingMicrobe)
+
+ workingMicrobe.entity:destroy()
+
+ newMicrobe = Microbe.createMicrobeEntity(PLAYER_NAME, false, workingMicrobe.microbe.speciesName)
+ species:template(newMicrobe)
+
+ if newMicrobe:getCompoundAmount(CompoundRegistry.getCompoundId("atp")) < 10 then
+ newMicrobe:storeCompound(CompoundRegistry.getCompoundId("atp"), 10)
end
- newMicrobeEntity = workingMicrobe.entity
- newPlayerMicrobe = newMicrobeEntity:transfer(GameState.MICROBE)
- newPlayerMicrobe:stealName(PLAYER_NAME)
+
+ newMicrobe.collisionHandler:addCollisionGroup("powerupable")
+ newMicrobeEntity = newMicrobe.entity:transfer(GameState.MICROBE)
+ newMicrobeEntity:stealName(PLAYER_NAME)
global_newEditorMicrobe = false
- Engine:playerData():setActiveCreature(newPlayerMicrobe.id, GameState.MICROBE)
+ Engine:playerData():setActiveCreature(newMicrobeEntity.id, GameState.MICROBE)
end
end
diff --git a/scripts/microbe_stage/microbe_stage_hud.lua b/scripts/microbe_stage/microbe_stage_hud.lua
index 5adce778f77..e0a13fc4364 100644
--- a/scripts/microbe_stage/microbe_stage_hud.lua
+++ b/scripts/microbe_stage/microbe_stage_hud.lua
@@ -8,6 +8,8 @@ function HudSystem:__init()
self.hitpointsCountLabel = nil
self.hitpointsBar = nil
self.compoundListItems = {}
+ self.rootGuiWindow = nil
+ self.populationNumberLabel = nil
self.rootGUIWindow = nil
end
@@ -63,7 +65,8 @@ function HudSystem:update(renderTime)
self.hitpointsBar:progressbarSetProgress(playerMicrobe.microbe.hitpoints/playerMicrobe.microbe.maxHitpoints)
self.hitpointsCountLabel:setText("".. math.floor(playerMicrobe.microbe.hitpoints))
-
+ local playerSpecies = playerMicrobe:getSpeciesComponent()
+ --TODO display population in home patch here
for compoundID in CompoundRegistry.getCompoundList() do
local compoundsString = string.format("%s - %d", CompoundRegistry.getCompoundDisplayName(compoundID), playerMicrobe:getCompoundAmount(compoundID))
if self.compoundListItems[compoundID] == nil then
diff --git a/scripts/microbe_stage/movement_organelle.lua b/scripts/microbe_stage/movement_organelle.lua
index c0a79ae84c6..ecb71acac1d 100644
--- a/scripts/microbe_stage/movement_organelle.lua
+++ b/scripts/microbe_stage/movement_organelle.lua
@@ -13,6 +13,7 @@ function MovementOrganelle:__init(force, torque)
self.energyMultiplier = 0.025
self.force = force
self.torque = torque
+ self.backwards_multiplier = 0
end
function MovementOrganelle:onAddedToMicrobe(microbe, q, r)
diff --git a/scripts/microbe_stage/organelle.lua b/scripts/microbe_stage/organelle.lua
index 9b0fff433f0..b425c8a4641 100644
--- a/scripts/microbe_stage/organelle.lua
+++ b/scripts/microbe_stage/organelle.lua
@@ -28,6 +28,7 @@ function Organelle:__init()
self._internalEdgeColour = ColourValue(0.5, 0.5, 0.5, 1)
self._externalEdgeColour = ColourValue(0, 0, 0, 1)
self._needsColourUpdate = false
+ self.name = ""
end
@@ -97,6 +98,7 @@ function Organelle:load(storage)
self._colour = storage:get("colour", ColourValue.White)
self._internalEdgeColour = storage:get("internalEdgeColour", ColourValue.Grey)
self._externalEdgeColour = storage:get("externalEdgeColour", ColourValue.Black)
+ self.name = storage:get("name", "")
end
@@ -181,6 +183,7 @@ function Organelle:storage()
hexes:append(hexStorage)
end
storage:set("hexes", hexes)
+ storage:set("name", self.name)
storage:set("q", self.position.q)
storage:set("r", self.position.r)
storage:set("colour", self._colour)
@@ -260,6 +263,7 @@ function OrganelleFactory.makeOrganelle(data)
end
local success, organelle = pcall(make_organelle)
if success then
+ organelle.name = data.name
return organelle
else
if data.name == "" or data.name == nil then data.name = "" end
diff --git a/scripts/microbe_stage/patch_system.lua b/scripts/microbe_stage/patch_system.lua
new file mode 100644
index 00000000000..74d3d6950d6
--- /dev/null
+++ b/scripts/microbe_stage/patch_system.lua
@@ -0,0 +1,114 @@
+--------------------------------------------------------------------------------
+-- PatchComponent
+--
+-- Controls population management within a certain region of space
+--------------------------------------------------------------------------------
+
+class "PatchComponent" (Component)
+
+function PatchComponent:__init()
+ Component.__init(self)
+ -- map species to populations
+ -- model environment
+end
+
+--[[
+We need a bunch of stuff handled here:
+Compound movement:
+- environment -> species
+- species -> environment
+- predation: species -> other species and environment
+
+Population changes:
+- immigration -- add tentative populations, keep those viable enough to displace
+ another species (and give them resources of species displaced, with leakage)
+- extinction, by culling
+- speciation? We may need to slightly control here when it happens
+
+Culling:
+- when population drops below sustainable level, free all compounds
+- when population mutates, and criteria for branching not met, move compounds
+ from ancestor to new, with leakage
+- another case?
+
+--]]
+
+REGISTER_COMPONENT("PatchComponent", PatchComponent)
+
+--------------------------------------------------------------------------------
+-- Population
+--
+-- Holds information about a specific population (species \intersect patch)
+--------------------------------------------------------------------------------
+class 'Population'
+
+function Population:__init(species)
+ self.species = species
+ self.heldCompounds = {} -- compounds that are available for intracellular processes
+ self.lockedCompounds = {} -- compounds that aren't, but will be released on deaths
+end
+
+--[[
+Whatever population calculations the patch does that would be useful to factor out will go here
+
+Getting the effective population number from the lockedCompounds pool is a bit complicated
+- each organelle will need an amortized cost, in locked compounds (protein, lipids, polysaccharides)
+- SpeciesComponent should calculate average organism cost
+- divide out current locked pool by per-unit cost, pick lowest
+- fudge
+--]]
+
+--------------------------------------------------------------------------------
+-- PatchSystem
+--
+-- System for simulating populations of species and their spatial distributions
+--------------------------------------------------------------------------------
+
+class "PatchSystem" (System)
+
+PATCH_SIM_INTERVAL = 1200
+
+function PatchSystem:__init()
+ System.__init(self)
+
+ self.entities = EntityFilter(
+ {
+ PatchComponent
+ },
+ true
+ )
+ self.timeSinceLastCycle = 0
+end
+
+
+-- Override from System
+function PatchSystem:init(gameState)
+ System.init(self, gameState)
+ self.entities:init(gameState)
+end
+
+-- Override from System
+function PatchSystem:shutdown()
+ self.entities:shutdown()
+ System.shutdown(self)
+end
+
+-- Override from System
+function PatchSystem:activate()
+ --[[
+ This handles two (three?) cases:
+ - First, it runs on game entry from main menu -- orchestrate with rest of world-generation
+ - Second, game entry from editor -- split patches,
+ move patches, merge patches
+ - Third (possibly) it might run on load.
+ --]]
+end
+
+-- Override from System
+function PatchSystem:update(_, milliseconds)
+ self.timeSinceLastCycle = self.timeSinceLastCycle + milliseconds
+ while self.timeSinceLastCycle > SPECIES_SIM_INTERVAL do
+ -- do population-management here
+ self.timeSinceLastCycle = self.timeSinceLastCycle - PATCH_SIM_INTERVAL
+ end
+end
diff --git a/scripts/microbe_stage/setup.lua b/scripts/microbe_stage/setup.lua
index be3c30fbf84..4a897bf4796 100644
--- a/scripts/microbe_stage/setup.lua
+++ b/scripts/microbe_stage/setup.lua
@@ -34,6 +34,7 @@ local function setupCamera()
viewportEntity:addComponent(viewportComponent)
end
+-- there must be some more robust way to script agents than having stuff all over the place.
function oxytoxyEffect(entityId, potency)
Microbe(Entity(entityId)):damage(potency*15, "toxin")
end
@@ -66,6 +67,45 @@ local function setupProcesses()
end
end
+function setupSpecies()
+ --[[
+ This function should be the entry point for all initial-species generation
+ For now, it can go through the XML and instantiate all the species, but later this
+ would be all procedural.
+
+ Together with the mutate function, these would be the only ways species are created
+ ]]
+ SpeciesRegistry.loadFromXML("../definitions/microbes.xml")
+ for _, name in ipairs(SpeciesRegistry.getSpeciesNames()) do
+ speciesEntity = Entity(name)
+ speciesComponent = SpeciesComponent(name)
+ speciesEntity:addComponent(speciesComponent)
+ -- print("made entity and component")
+ local organelles = {}
+ assert(pcall(function () SpeciesRegistry.getSize(name) end), "could not load species", name, "from XML")
+ -- In this case the species is a default one loaded from xml
+ -- print("loaded", name)
+ local numOrganelles = SpeciesRegistry.getSize(name)
+ -- print("# organelles = "..numOrganelles)
+ for i = 0,(numOrganelles-1) do
+ -- returns a property table
+ local organelleData = SpeciesRegistry.getOrganelle(name, i)
+ organelles[#organelles+1] = organelleData
+ end
+ -- for _, org in pairs(organelles) do print(org.name, org.q, org.r) end
+ speciesComponent.organelles = organelles
+
+ -- iterates over all compounds, and sets amounts and priorities
+ for compoundID in CompoundRegistry.getCompoundList() do
+ compound = CompoundRegistry.getCompoundInternalName(compoundID)
+ amount = SpeciesRegistry.getCompoundAmount(name, compound)
+ priority = SpeciesRegistry.getCompoundPriority(name, compound)
+ speciesComponent.avgCompoundAmounts[compoundID] = amount
+ speciesComponent.compoundPriorities[compoundID] = priority
+ end
+ end
+end
+
-- speciesName decides the template to use, while individualName is used for referencing the instance
function microbeSpawnFunctionGeneric(pos, speciesName, aiControlled, individualName)
local microbe = Microbe.createMicrobeEntity(individualName, aiControlled)
@@ -77,192 +117,103 @@ function microbeSpawnFunctionGeneric(pos, speciesName, aiControlled, individualN
Vector3(0, 0, 0) -- Angular velocity
)
end
- local numOrganelles = SpeciesRegistry.getSize(speciesName)
- for i = 0,(numOrganelles-1) do
- -- TODO make a SpeciesRegistry::getOrganelle function that returns a property table
- local organelleData = SpeciesRegistry.getOrganelle(speciesName, i)
- local organelle = OrganelleFactory.makeOrganelle(organelleData)
- microbe:addOrganelle(organelleData.q, organelleData.r, organelle)
- end
- -- TODO figure out way to iterate this over all compounds, and set priorities too
- for compoundID in CompoundRegistry.getCompoundList() do
- compound = CompoundRegistry.getCompoundInternalName(compoundID)
- amount = SpeciesRegistry.getCompoundAmount(speciesName, compound)
- if amount ~= 0 then
- microbe:storeCompound(compoundID, amount, false)
- end
- priority = SpeciesRegistry.getCompoundPriority(speciesName, compound)
- if priority ~= 0 then
- microbe:setDefaultCompoundPriority(compoundID, priority)
- end
- end
- microbe.microbe.speciesName = speciesName
+ -- set organelles, starting compound amounts, all that
+ -- TODO:
+ Entity(speciesName):getComponent(SpeciesComponent.TYPE_ID):template(microbe)
return microbe
end
+local function setSpawnablePhysics(entity, pos, mesh, scale, collisionShape)
+ -- Rigid body
+ local rigidBody = RigidBodyComponent()
+ rigidBody.properties.friction = 0.2
+ rigidBody.properties.linearDamping = 0.8
+
+ rigidBody.properties.shape = collisionShape
+ rigidBody:setDynamicProperties(
+ pos,
+ Quaternion(Radian(Degree(math.random()*360)), Vector3(0, 0, 1)),
+ Vector3(0, 0, 0),
+ Vector3(0, 0, 0)
+ )
+ rigidBody.properties:touch()
+ entity:addComponent(rigidBody)
+ -- Scene node
+ local sceneNode = OgreSceneNodeComponent()
+ sceneNode.meshName = mesh
+ sceneNode.transform.scale = Vector3(scale, scale, scale)
+ entity:addComponent(sceneNode)
+ return entity
+end
+
+local function addEmitter2Entity(entity, compound)
+ local compoundEmitter = CompoundEmitterComponent()
+ entity:addComponent(compoundEmitter)
+ compoundEmitter.emissionRadius = 1
+ compoundEmitter.maxInitialSpeed = 10
+ compoundEmitter.minInitialSpeed = 2
+ compoundEmitter.minEmissionAngle = Degree(0)
+ compoundEmitter.maxEmissionAngle = Degree(360)
+ compoundEmitter.particleLifeTime = 5000
+ local timedEmitter = TimedCompoundEmitterComponent()
+ timedEmitter.compoundId = CompoundRegistry.getCompoundId(compound)
+ timedEmitter.particlesPerEmission = 1
+ timedEmitter.potencyPerParticle = 2.0
+ timedEmitter.emitInterval = 1000
+ entity:addComponent(timedEmitter)
+end
+
local function createSpawnSystem()
local spawnSystem = SpawnSystem()
SpeciesRegistry.loadFromXML("../definitions/microbes.xml")
local spawnOxygenEmitter = function(pos)
- -- Setting up an emitter for oxygen
local entity = Entity()
- -- Rigid body
- local rigidBody = RigidBodyComponent()
- rigidBody.properties.friction = 0.2
- rigidBody.properties.linearDamping = 0.8
- rigidBody.properties.shape = CylinderShape(
- CollisionShape.AXIS_X,
- 0.4,
- 2.0
- )
- rigidBody:setDynamicProperties(
- pos,
- Quaternion(Radian(Degree(math.random()*360)), Vector3(0, 0, 1)),
- Vector3(0, 0, 0),
- Vector3(0, 0, 0)
- )
- rigidBody.properties:touch()
- entity:addComponent(rigidBody)
- -- Scene node
- local sceneNode = OgreSceneNodeComponent()
- sceneNode.meshName = "molecule.mesh"
- entity:addComponent(sceneNode)
- -- Emitter oxygen
- local oxygenEmitter = CompoundEmitterComponent()
- entity:addComponent(oxygenEmitter)
- oxygenEmitter.emissionRadius = 1
- oxygenEmitter.maxInitialSpeed = 10
- oxygenEmitter.minInitialSpeed = 2
- oxygenEmitter.minEmissionAngle = Degree(0)
- oxygenEmitter.maxEmissionAngle = Degree(360)
- oxygenEmitter.particleLifeTime = 5000
- local timedEmitter = TimedCompoundEmitterComponent()
- timedEmitter.compoundId = CompoundRegistry.getCompoundId("oxygen")
- timedEmitter.particlesPerEmission = 1
- timedEmitter.potencyPerParticle = 2.0
- timedEmitter.emitInterval = 1000
- entity:addComponent(timedEmitter)
+ setSpawnablePhysics(entity, pos, "molecule.mesh", 1, CylinderShape(
+ CollisionShape.AXIS_X,
+ 0.4,
+ 2.0
+ ))
+ addEmitter2Entity(entity, "oxygen")
return entity
end
local spawnCO2Emitter = function(pos)
- -- Setting up an emitter for co2
local entity = Entity()
- -- Rigid body
- local rigidBody = RigidBodyComponent()
- rigidBody.properties.friction = 0.2
- rigidBody.properties.linearDamping = 0.8
- rigidBody.properties.shape = CylinderShape(
- CollisionShape.AXIS_X,
- 0.4,
- 2.0
- )
- rigidBody:setDynamicProperties(
- pos,
- Quaternion(Radian(Degree(math.random()*360)), Vector3(0, 0, 1)),
- Vector3(0, 0, 0),
- Vector3(0, 0, 0)
- )
- rigidBody.properties:touch()
- entity:addComponent(rigidBody)
- -- Scene node
- local sceneNode = OgreSceneNodeComponent()
- sceneNode.meshName = "co2.mesh"
- sceneNode.transform.scale = Vector3(0.4, 0.4, 0.4)
- entity:addComponent(sceneNode)
- -- Emitter carbon dioxide
- local co2Emitter = CompoundEmitterComponent()
- entity:addComponent(co2Emitter)
- co2Emitter.emissionRadius = 1
- co2Emitter.maxInitialSpeed = 10
- co2Emitter.minInitialSpeed = 2
- co2Emitter.minEmissionAngle = Degree(0)
- co2Emitter.maxEmissionAngle = Degree(360)
- co2Emitter.particleLifeTime = 5000
- local timedEmitter = TimedCompoundEmitterComponent()
- timedEmitter.compoundId = CompoundRegistry.getCompoundId("co2")
- timedEmitter.particlesPerEmission = 1
- timedEmitter.potencyPerParticle = 2.0
- timedEmitter.emitInterval = 1000
- entity:addComponent(timedEmitter)
+ setSpawnablePhysics(entity, pos, "co2.mesh", 0.4, CylinderShape(
+ CollisionShape.AXIS_X,
+ 0.4,
+ 2.0
+ ))
+ addEmitter2Entity(entity, "co2")
return entity
end
local spawnGlucoseEmitter = function(pos)
- -- Setting up an emitter for glucose
local entity = Entity()
- -- Rigid body
- local rigidBody = RigidBodyComponent()
- rigidBody.properties.friction = 0.2
- rigidBody.properties.linearDamping = 0.8
- rigidBody.properties.shape = SphereShape(HEX_SIZE)
- rigidBody:setDynamicProperties(
- pos,
- Quaternion(Radian(Degree(math.random()*360)), Vector3(0, 0, 1)),
- Vector3(0, 0, 0),
- Vector3(0, 0, 0)
- )
- rigidBody.properties:touch()
- entity:addComponent(rigidBody)
- -- Scene node
- local sceneNode = OgreSceneNodeComponent()
- sceneNode.meshName = "glucose.mesh"
- entity:addComponent(sceneNode)
- -- Emitter glucose
- local glucoseEmitter = CompoundEmitterComponent()
- entity:addComponent(glucoseEmitter)
- glucoseEmitter.emissionRadius = 1
- glucoseEmitter.maxInitialSpeed = 10
- glucoseEmitter.minInitialSpeed = 2
- glucoseEmitter.minEmissionAngle = Degree(0)
- glucoseEmitter.maxEmissionAngle = Degree(360)
- glucoseEmitter.particleLifeTime = 5000
- local timedEmitter = TimedCompoundEmitterComponent()
- timedEmitter.compoundId = CompoundRegistry.getCompoundId("glucose")
- timedEmitter.particlesPerEmission = 1
- timedEmitter.potencyPerParticle = 1.0
- timedEmitter.emitInterval = 2000
- entity:addComponent(timedEmitter)
+ setSpawnablePhysics(entity, pos, "glucose.mesh", 1, SphereShape(HEX_SIZE))
+ addEmitter2Entity(entity, "glucose")
return entity
end
local spawnAmmoniaEmitter = function(pos)
- -- Setting up an emitter for ammonia
local entity = Entity()
- -- Rigid body
- local rigidBody = RigidBodyComponent()
- rigidBody.properties.friction = 0.2
- rigidBody.properties.linearDamping = 0.8
- rigidBody.properties.shape = SphereShape(HEX_SIZE)
- rigidBody:setDynamicProperties(
- pos,
- Quaternion(Radian(Degree(math.random()*360)), Vector3(0, 0, 1)),
- Vector3(0, 0, 0),
- Vector3(0, 0, 0)
- )
- rigidBody.properties:touch()
- entity:addComponent(rigidBody)
- -- Scene node
- local sceneNode = OgreSceneNodeComponent()
- sceneNode.meshName = "ammonia.mesh"
- sceneNode.transform.scale = Vector3(0.5, 0.5, 0.5)
- entity:addComponent(sceneNode)
- -- Emitter ammonia
- local ammoniaEmitter = CompoundEmitterComponent()
- entity:addComponent(ammoniaEmitter)
- ammoniaEmitter.emissionRadius = 1
- ammoniaEmitter.maxInitialSpeed = 10
- ammoniaEmitter.minInitialSpeed = 2
- ammoniaEmitter.minEmissionAngle = Degree(0)
- ammoniaEmitter.maxEmissionAngle = Degree(360)
- ammoniaEmitter.particleLifeTime = 5000
- local timedEmitter = TimedCompoundEmitterComponent()
- timedEmitter.compoundId = CompoundRegistry.getCompoundId("ammonia")
- timedEmitter.particlesPerEmission = 1
- timedEmitter.potencyPerParticle = 1.0
- timedEmitter.emitInterval = 1000
- entity:addComponent(timedEmitter)
+ setSpawnablePhysics(entity, pos, "ammonia.mesh", 0.5, SphereShape(HEX_SIZE))
+ addEmitter2Entity(entity, "ammonia")
return entity
end
+ local toxinOrganelleSpawnFunction = function(pos)
+ powerupEntity = Entity()
+ setSpawnablePhysics(powerupEntity, pos, "AgentVacuole.mesh", 0.9, SphereShape(HEX_SIZE))
+
+ local reactionHandler = CollisionComponent()
+ reactionHandler:addCollisionGroup("powerup")
+ powerupEntity:addComponent(reactionHandler)
+
+ local powerupComponent = PowerupComponent()
+ powerupComponent:setEffect(unlockToxin)
+ powerupEntity:addComponent(powerupComponent)
+ return powerupEntity
+ end
+
local microbeDefault = function(pos)
return microbeSpawnFunctionGeneric(pos, "Default", true, nil)
end
@@ -282,6 +233,7 @@ local function createSpawnSystem()
local microbeToxinPredator = function(pos)
return microbeSpawnFunctionGeneric(pos, "ToxinPredator", true, nil)
end
+
local microbeNoper = function(pos)
return microbeSpawnFunctionGeneric(pos, "Noper", true, nil)
end
@@ -328,6 +280,8 @@ local function createSpawnSystem()
spawnSystem:addSpawnType(spawnCO2Emitter, 1/500, 30)
spawnSystem:addSpawnType(spawnGlucoseEmitter, 1/500, 30)
spawnSystem:addSpawnType(spawnAmmoniaEmitter, 1/1250, 30)
+
+ -- Microbe spawning needs to be handled by species/population
spawnSystem:addSpawnType(microbeDefault, 1/12000, 40)
spawnSystem:addSpawnType(microbeTeeny, 1/6000, 40)
spawnSystem:addSpawnType(microbePlankton, 1/32000, 40)
@@ -367,20 +321,7 @@ local function setupEmitter()
sceneNode.meshName = "molecule.mesh"
entity:addComponent(sceneNode)
-- Emitter test
- local testEmitter = CompoundEmitterComponent()
- entity:addComponent(testEmitter)
- testEmitter.emissionRadius = 1.5
- testEmitter.maxInitialSpeed = 10
- testEmitter.minInitialSpeed = 2
- testEmitter.minEmissionAngle = Degree(0)
- testEmitter.maxEmissionAngle = Degree(360)
- testEmitter.particleLifeTime = 5000
- local timedEmitter = TimedCompoundEmitterComponent()
- timedEmitter.compoundId = CompoundRegistry.getCompoundId("oxygen")
- timedEmitter.particlesPerEmission = 1
- timedEmitter.potencyPerParticle = 3.0
- timedEmitter.emitInterval = 1000
- entity:addComponent(timedEmitter)
+ addEmitter2Entity(entity, "glucose")
end
function unlockToxin(entityId)
@@ -393,17 +334,17 @@ function unlockToxin(entityId)
return true
end
-function createStarterMicrobe(name, aiControlled)
- local microbe = microbeSpawnFunctionGeneric(nil,"Default",aiControlled, name)
- return microbe
-end
-
local function setupPlayer()
SpeciesRegistry.loadFromXML("../definitions/microbes.xml")
- microbe = createStarterMicrobe(PLAYER_NAME, false)
+ microbe = microbeSpawnFunctionGeneric(nil, "Default", false, PLAYER_NAME)
microbe.collisionHandler:addCollisionGroup("powerupable")
Engine:playerData():lockedMap():addLock("Toxin")
Engine:playerData():setActiveCreature(microbe.entity.id, GameState.MICROBE)
+ speciesEntity = Entity("defaultMicrobeSpecies")
+ species = SpeciesComponent("defaultMicrobeSpecies")
+ species:fromMicrobe(microbe)
+ speciesEntity:addComponent(species)
+ microbe.microbe.speciesName = "defaultMicrobeSpecies"
end
local function setupSound()
@@ -462,6 +403,9 @@ local function createMicrobeStage(name)
TimedLifeSystem(),
CompoundMovementSystem(),
CompoundAbsorberSystem(),
+ --PopulationSystem(),
+ PatchSystem(),
+ SpeciesSystem(),
-- Physics
RigidBodyInputSystem(),
UpdatePhysicsSystem(),
@@ -489,6 +433,7 @@ local function createMicrobeStage(name)
setupBackground()
setupCamera()
setupEmitter()
+ setupSpecies()
setupPlayer()
setupSound()
end,
@@ -498,3 +443,4 @@ end
GameState.MICROBE = createMicrobeStage("microbe")
GameState.MICROBE_ALTERNATE = createMicrobeStage("microbe_alternate")
+--Engine:setCurrentGameState(GameState.MICROBE)
diff --git a/scripts/microbe_stage/species_system.lua b/scripts/microbe_stage/species_system.lua
new file mode 100644
index 00000000000..53c7b08e84d
--- /dev/null
+++ b/scripts/microbe_stage/species_system.lua
@@ -0,0 +1,186 @@
+
+--------------------------------------------------------------------------------
+-- SpeciesComponent
+--
+-- Holds information about an entity representing a species
+--------------------------------------------------------------------------------
+class 'SpeciesComponent' (Component)
+
+SPECIES_NUM = 0
+
+function SpeciesComponent:__init(name)
+ Component.__init(self)
+ self.num = SPECIES_NUM
+ SPECIES_NUM = SPECIES_NUM + 1
+ if name == nil then
+ self.name = "noname"..self.num
+ else
+ self.name = name
+ end
+
+ self.organelles = {} -- stores a table of organelle {q,r,name} tables
+
+ self.avgCompoundAmounts = {} -- maps each compound name to the amount a new spawn should get. Nonentries are zero.
+ -- we could also add some measure of variability to make things more ...variable.
+ self.compoundPriorities = {} -- maps compound name to priority.
+end
+
+--is this still todo?
+--todo - store moar data
+function SpeciesComponent:load(storage)
+ Component.load(self, storage)
+ self.name = storage:get("name", "")
+ self.compoundPriorities = {}
+ priorityData = storage:get("compoundPriorities", nil)
+ organelleData = storage:get("organelleData", nil)
+ self.organelles = {}
+ if organelleData ~= nil then
+ i = 1
+ while organelleData:contains(""..i) do
+ organelle = {}
+ orgData = organelleData:get(""..i, nil)
+ if orgData ~= nil then
+ organelle.name = orgData:get("name", "")
+ organelle.q = orgData:get("q", 0)
+ organelle.r = orgData:get("r", 0)
+ end
+ self.organelles[i] = organelle
+ i = i + 1
+ end
+ end
+end
+
+--todo
+function SpeciesComponent:storage()
+ local storage = Component.storage(self)
+ storage:set("name", self.name)
+ compoundPriorities = StorageContainer()
+ for k,v in pairs(self.compoundPriorities) do
+ compoundPriorities:set(k,v)
+ end
+ storage:set("compoundPriorities", compoundPriorities)
+ organelles = StorageContainer()
+ for i, org in ipairs(self.organelles) do
+ orgData = StorageContainer()
+ orgData:set("name", org.name)
+ orgData:set("q", org.q)
+ orgData:set("r", org.r)
+ organelles:set(""..i, orgData)
+ end
+ storage:set("organelleData", organelles)
+ return storage
+end
+
+function SpeciesComponent:mutate(aiControlled, population)
+ --[[
+ SpeciesComponent:mutate should call the correct evolution-handler
+ (Ie, trigger an entry into microbe editor for player; and auto-evo
+ for AI), and then create and add the new species to the system.
+ - uses the population to access information about
+
+ For player mutation, this would be the entry point into the editor
+
+ - note, this architecture may be reconsidered
+
+ ]]
+end
+
+
+-- Given a newly-created microbe, this sets the organelles and all other species-specific microbe data
+-- like agent codes, for example.
+function SpeciesComponent:template(microbe)
+ microbe.microbe.speciesName = self.name
+ -- give it organelles
+ for i, orgdata in pairs(self.organelles) do
+ organelle = OrganelleFactory.makeOrganelle(orgdata)
+ microbe:addOrganelle(orgdata.q, orgdata.r, organelle)
+ end
+
+ for compoundID, amount in pairs(self.avgCompoundAmounts) do
+ if amount ~= 0 then
+ microbe:storeCompound(compoundID, amount, false)
+ end
+ end
+ for compoundID, priority in pairs(self.compoundPriorities) do
+ if priority ~= 0 then
+ microbe:setDefaultCompoundPriority(compoundID, priority)
+ end
+ end
+ -- complimentary serving of atp
+ --newMicrobe:storeCompound(CompoundRegistry.getCompoundId("atp"), 10)
+ return microbe
+end
+
+--[[
+Modify the species, using a microbe as the template for the new genome.
+
+]]
+function SpeciesComponent:fromMicrobe(microbe)
+ microbe = microbe.microbe -- shouldn't break, I think
+ self.name = microbe.speciesName
+ --print("self.name: "..self.name)
+ -- Create species' organelle data
+ for i, organelle in pairs(microbe.organelles) do
+ --print(i)
+ local data = {}
+ data.name = organelle.name
+ data.q = organelle.position.q
+ data.r = organelle.position.r
+ self.organelles[i] = data
+ end
+end
+
+REGISTER_COMPONENT("SpeciesComponent", SpeciesComponent)
+
+SPECIES_SIM_INTERVAL = 20000
+
+--------------------------------------------------------------------------------
+-- SpeciesSystem
+--
+-- System for estimating and simulating population count for various species
+--------------------------------------------------------------------------------
+class 'SpeciesSystem' (System)
+
+function SpeciesSystem:__init()
+ System.__init(self)
+
+ self.entities = EntityFilter(
+ {
+ SpeciesComponent
+ },
+ true
+ )
+ self.timeSinceLastCycle = 0
+end
+
+-- Override from System
+function SpeciesSystem:init(gameState)
+ System.init(self, gameState)
+ self.entities:init(gameState)
+end
+
+-- Override from System
+function SpeciesSystem:shutdown()
+ self.entities:shutdown()
+ System.shutdown(self)
+end
+
+-- Override from System
+function SpeciesSystem:activate()
+ --[[
+ This runs in two (three?) use-cases:
+ - First, it runs on game entry from main menu -- in this case, it must set up for new game
+ - Second, it runs on game entry from editor -- in this case, it can set up the new species
+ (in conjunction with stuff in editor, called on finish-click)
+ - Third (possibly) it might run on load.
+ --]]
+end
+
+-- Override from System
+function SpeciesSystem:update(_, milliseconds)
+ self.timeSinceLastCycle = self.timeSinceLastCycle + milliseconds
+ while self.timeSinceLastCycle > SPECIES_SIM_INTERVAL do
+ -- do mutation-management here
+ self.timeSinceLastCycle = self.timeSinceLastCycle - SPECIES_SIM_INTERVAL
+ end
+end
diff --git a/scripts/util.lua b/scripts/util.lua
index f2e68dac28f..7ce65cf63ee 100644
--- a/scripts/util.lua
+++ b/scripts/util.lua
@@ -51,3 +51,102 @@ function sign(x)
end
end
+--[[
+Decorate a function with once to ensure it only runs once. Ever.
+]]
+function once(fn)
+ local able = true
+ function foo(...)
+ local ret = nil
+ if able then
+ ret = fn(...)
+ able = false
+ end
+ if ret ~= nil then return ret end
+ end
+ return foo
+end
+
+--[[
+We need an extension of once that provides each "once" with an accompanying "enable", that
+simply allows the once to run again
+]]
+
+function onceWithReset(fn)
+ local active = true
+ function run(...)
+ local ret = nil
+ if active then
+ ret = fn(...)
+ active = false
+ end
+ if ret ~= nil then return ret end
+ end
+ function enable()
+ active = true
+ end
+ return {run = run, enable = enable}
+end
+
+--[[
+Decorates two functions at a time, forcing them to happen in alternating order
+Example use-case: coordinating work done when moving between gamestates
+]]
+function forceAlternating(in_first, in_second)
+ local firsts_turn = true
+ function do_first(...)
+ local ret = nil
+ if firsts_turn then
+ if in_first ~= nil then ret = in_first(...) end
+ firsts_turn = false
+ end
+ return ret
+ end
+ function do_second(...)
+ local ret = nil
+ if firsts_turn == false then
+ if in_second ~= nil then ret = in_second(...) end
+ firsts_turn = true
+ end
+ return ret
+ end
+ return {first = do_first, second = do_second}
+end
+
+--[[
+No idea if this works, but should make the decorated function run
+only once per each instance of the object with the decorated method.
+]]
+function oncePer(fn)
+ function foo(...)
+ t = {...}
+ -- we need to know how the object is injected into the method -- by closure? by argument?
+ if t[1]["__oncePer__"] == nil then t[1]["__oncePer__"] = {} end
+ -- indexing the table with the function itself could be a source of issues
+ if t[1]["__oncePer__"][fn] == nil then
+ ret = fn(...)
+ t[1]["__oncePer__"][fn] = true
+ return ret
+ end
+ end
+ return foo
+end
+
+--[[
+Memoizes the decorated function.
+Could use some optimization, probably.
+Should probably be done in C++, actually.
+
+It's actually quite a bit too ugly in lua, requiring loads of work to detect identity between arg tables
+...without which it's useless, of course.
+]]
+function memoize(fn)
+ memo = {}
+ function foo(...)
+ if memo[{...}] == nil then
+ memo[{...}] = fn(...)
+ end
+ return memo[{...}]
+ end
+ return foo
+end
diff --git a/src/microbe_stage/species_registry.cpp b/src/microbe_stage/species_registry.cpp
index a4a44bf5242..7885e0ecf59 100644
--- a/src/microbe_stage/species_registry.cpp
+++ b/src/microbe_stage/species_registry.cpp
@@ -4,6 +4,7 @@
#include
#include
+#include
#include "tinyxml.h"
@@ -16,6 +17,7 @@ SpeciesRegistry::luaBindings() {
.scope
[
def("loadFromXML", &SpeciesRegistry::loadFromXML),
+ def("getSpeciesNames", &SpeciesRegistry::getSpeciesNames),
def("getSize", &SpeciesRegistry::getSize),
def("getCompoundPriority", &SpeciesRegistry::getCompoundPriority),
def("getCompoundAmount", SpeciesRegistry::getCompoundAmount),
@@ -52,7 +54,11 @@ speciesMap() {
int
SpeciesRegistry::getSize(
const std::string& microbe_name) {
- return speciesMap()[microbe_name].organelles.size();
+ try {
+ return speciesMap()[microbe_name].organelles.size();
+ } catch (...) {
+ return 0;
+ }
}
luabind::object
@@ -60,10 +66,27 @@ SpeciesRegistry::getOrganelle(
const std::string& microbe_name,
int index) {
luabind::object table = luabind::newtable(Game::instance().engine().luaState());
- Organelle organelle = speciesMap()[microbe_name].organelles[index];
- table["name"] = organelle.internalName;
- table["q"] = organelle.q;
- table["r"] = organelle.r;
+ try {
+ Organelle organelle = speciesMap()[microbe_name].organelles[index];
+ table["name"] = organelle.internalName;
+ table["q"] = organelle.q;
+ table["r"] = organelle.r;
+ } catch (...) {
+ table["name"] = "";
+ table["q"] = 0;
+ table["r"] = 0;
+ }
+ return table;
+}
+
+luabind::object
+SpeciesRegistry::getSpeciesNames() {
+ luabind::object table = luabind::newtable(Game::instance().engine().luaState());
+ int i = 1;
+ for (std::string name : speciesMap() | boost::adaptors::map_keys) {
+ table[i] = name;
+ i++;
+ }
return table;
}
@@ -71,14 +94,22 @@ double
SpeciesRegistry::getCompoundPriority(
const std::string& microbe_name,
const std::string& compound_name) {
- return speciesMap()[microbe_name].compounds[compound_name].priority;
+ try {
+ return speciesMap()[microbe_name].compounds[compound_name].priority;
+ } catch (...) {
+ return 0;
+ }
}
double
SpeciesRegistry::getCompoundAmount(
const std::string& microbe_name,
const std::string& compound_name) {
- return speciesMap()[microbe_name].compounds[compound_name].amount;
+ try {
+ return speciesMap()[microbe_name].compounds[compound_name].amount;
+ } catch (...) {
+ return 0;
+ }
}
void
diff --git a/src/microbe_stage/species_registry.h b/src/microbe_stage/species_registry.h
index 44a4a1f09c5..d0e0a5e7022 100644
--- a/src/microbe_stage/species_registry.h
+++ b/src/microbe_stage/species_registry.h
@@ -24,6 +24,7 @@ class SpeciesRegistry {
*
* Exposes:
* - SpeciesRegistry::loadFromXML
+ * - SpeciesRegistry::getSpeciesNames
* - SpeciesRegistry::getSize
* - SpeciesRegistry::getOrganelle
* - SpeciesRegistry::getCompoundPriority
@@ -56,6 +57,9 @@ class SpeciesRegistry {
int index
);
+ static luabind::object
+ getSpeciesNames();
+
static double
getCompoundPriority(
const std::string& microbe_name,