From ed014af8a477cb3b2c3300e2428afcc2f297f019 Mon Sep 17 00:00:00 2001 From: Jacob Jensen Date: Wed, 30 Oct 2013 19:56:29 +0100 Subject: [PATCH] Add ProcessOrganelle for converting compounds Closes #41 squashed commits: Add ProcessOrganelle for converting compounds Closes #41 Add ProcessOrganelle Fixed syntax in process_organelle.lua and added hasInputAgent method to ProcessOrganelle class Added ProcessOrganelle support into Microbe class, and added process_organelle.lua to manifest Added code to setup.lua to test ProcessOrganelle (currently not working) Minor fixes to ProcessOrganelle related code in process_organelle.lua and microbe.lua Minor formatting and reintroduced function missing in process_organelle.lua Fix some minor errors in the ProcessOrganelle script * Use table.insert instead of a[#a+1] to not repeat the table name * Call superclass' constructor in ProcessOrganelle.__init * Explicitly pass self to Organelle.update in ProcessOrganelle.update * Lua has no -= (or += for that matter) * Typos Fixed error in loading a savegame with ProcessOrganelle. Removed debugging code in microbe.lua Created cooldown system for Process organelles, limiting the frequency of agent absorbtion and production Fixed missing local specifier on two variables in process_organelle.lua Implemeneted colours for process organelles to reflect how filled it is. Also minor bugfixes Move "local" to where it belongs Made distribution of agents, dependant on time elapsed instead of frames. (1 agent per agent type distributed per 100ms) Release candidate 1 Done. - Fixed compile error - Removed debugging code - Changed setup of compounds/agents to reflect mitochondrion Added additional documentation for processing compounds. Minor refactoring to compound distribution for more accuracy. Replaced magic value with proper constant Re-changed emitters to use new oxygen, glucose, atp, co2 system. Cleanup of old variable and comment names in setup.lua --- res/scripts/microbe_stage/manifest.txt | 2 +- res/scripts/microbe_stage/microbe.lua | 34 ++++ .../microbe_stage/process_organelle.lua | 187 ++++++++++++++++++ res/scripts/microbe_stage/setup.lua | 180 +++++++++-------- 4 files changed, 317 insertions(+), 86 deletions(-) create mode 100644 res/scripts/microbe_stage/process_organelle.lua diff --git a/res/scripts/microbe_stage/manifest.txt b/res/scripts/microbe_stage/manifest.txt index 0c942ba3e65..52c5fc47ed4 100644 --- a/res/scripts/microbe_stage/manifest.txt +++ b/res/scripts/microbe_stage/manifest.txt @@ -10,6 +10,6 @@ switch_game_state_system.lua organelle.lua movement_organelle.lua storage_organelle.lua - +process_organelle.lua // Setup setup.lua diff --git a/res/scripts/microbe_stage/microbe.lua b/res/scripts/microbe_stage/microbe.lua index 0943fd47219..595312c9494 100644 --- a/res/scripts/microbe_stage/microbe.lua +++ b/res/scripts/microbe_stage/microbe.lua @@ -6,10 +6,13 @@ -------------------------------------------------------------------------------- class 'MicrobeComponent' (Component) +COMPOUND_DISTRIBUTION_INTERVAL = 100 -- quantity of physics time between each loop distributing agents to organelles. TODO: Modify to reflect microbe size. + function MicrobeComponent:__init() Component.__init(self) self.organelles = {} self.vacuoles = {} + self.processOrganelles = {} self.movementDirection = Vector3(0, 0, 0) self.facingTargetPoint = Vector3(0, 0, 0) self.initialized = false @@ -111,6 +114,7 @@ Microbe.COMPONENTS = { -- The entity this microbe wraps function Microbe:__init(entity) self.entity = entity + self.residuePhysicsTime = 0 for key, typeId in pairs(Microbe.COMPONENTS) do local component = entity:getComponent(typeId) assert(component ~= nil, "Can't create microbe from this entity, it's missing " .. key) @@ -176,6 +180,15 @@ function Microbe:addVacuole(vacuole) end +-- Adds a process organelle +-- +-- @param processOrganelle +-- An object of type ProcessOrganelle +function Microbe:addProcessOrganelle(processOrganelle) + table.insert(self.microbe.processOrganelles, processOrganelle) +end + + -- Queries the currently stored amount of an agent -- -- @param agentId @@ -316,12 +329,33 @@ end -- Updates the microbe's state function Microbe:update(milliseconds) -- Vacuoles + for agentId, vacuoleList in pairs(self.microbe.vacuoles) do + -- Check for agents to store local amount = self.agentAbsorber:absorbedAgentAmount(agentId) if amount > 0.0 then self:storeAgent(agentId, amount) end end + -- Distribute agents to StorageOrganelles + self.residuePhysicsTime = self.residuePhysicsTime + milliseconds + while self.residuePhysicsTime > COMPOUND_DISTRIBUTION_INTERVAL do -- For every COMPOUND_DISTRIBUTION_INTERVAL passed + for agentId, vacuoleList in pairs(self.microbe.vacuoles) do -- Foreach agent type. + if self:getAgentAmount(agentId) > 0 then -- If microbe contains the compound + local candidateIndices = {} -- Indices of organelles that want the agent + for i, processOrg in ipairs(self.microbe.processOrganelles) do + if processOrg:wantsInputAgent(agentId) then + table.insert(candidateIndices, i) -- Organelle has determined that it is interrested in obtaining the agnet + end + end + if #candidateIndices > 0 then -- If there were any candidates, pick a random winner. + local chosenProcessOrg = self.microbe.processOrganelles[candidateIndices[rng:getInt(1,#candidateIndices)]] + chosenProcessOrg:storeAgent(agentId, self:takeAgent(agentId, 1)) + end + end + end + self.residuePhysicsTime = self.residuePhysicsTime - COMPOUND_DISTRIBUTION_INTERVAL + end -- Other organelles for _, organelle in pairs(self.microbe.organelles) do organelle:update(self, milliseconds) diff --git a/res/scripts/microbe_stage/process_organelle.lua b/res/scripts/microbe_stage/process_organelle.lua new file mode 100644 index 00000000000..0a3a3a6861f --- /dev/null +++ b/res/scripts/microbe_stage/process_organelle.lua @@ -0,0 +1,187 @@ +-------------------------------------------------------------------------------- +-- Class for Organelles capable of producing agents +-------------------------------------------------------------------------------- +class 'ProcessOrganelle' (Organelle) + +-- Constructor +function ProcessOrganelle:__init(processCooldown) + Organelle.__init(self) + self.processCooldown = processCooldown + self.remainingCooldown = processCooldown -- Countdown var until next output batch can be produced + self.bufferSum = 0 -- Number of agent units summed over all buffers + self.inputSum = 0 -- Number of agent units summed over all input requirements + self.originalColour = ColourValue(1,1,1,1) + self.buffers = {} + self.inputAgents = {} + self.outputAgents = {} +end + + +-- Overridded from Organelle:onAddedToMicrobe +function ProcessOrganelle:onAddedToMicrobe(microbe, q, r) + Organelle.onAddedToMicrobe(self, microbe, q, r) + microbe:addProcessOrganelle(self) +end + + +-- Set the minimum time that has to pass between agents are produced +-- +-- @param milliseconds +-- The amount of time +function ProcessOrganelle:setprocessCooldown(milliseconds) + self.processCooldown = milliseconds +end + + +-- Add input agent to the recipy of the organelle +-- +-- @param agentId +-- The agent to be used as input +-- +-- @param amount +-- The amount of the agent needed +function ProcessOrganelle:addRecipyInput(agentId, amount) + self.inputAgents[agentId] = amount + self.buffers[agentId] = 0 + self.inputSum = self.inputSum + amount; + self:updateColourDynamic() +end + + +-- Add output agent to the recipy of the organelle +-- +-- @param agentId +-- The agent to be used as output +-- +-- @param amount +-- The amount of the agent produced +function ProcessOrganelle:addRecipyOutput(agentId, amount) + self.outputAgents[agentId] = amount +end + + +-- Store agent in buffer of processing organelle. +-- This will force the organelle to store the agent, even if wantInputAgent is false. +-- It is recommended to check if wantInputAgent is true before calling. +-- +-- @param agentId +-- The agent to be stored +-- +-- @param amount +-- The amount to be stored +function ProcessOrganelle:storeAgent(agentId, amount) + self.buffers[agentId] = self.buffers[agentId] + amount + self.bufferSum = self.bufferSum + amount + self:updateColourDynamic() + self._needsColourUpdate = true +end + + +-- Private function used to update colour of organelle based on how full it is +function ProcessOrganelle:updateColourDynamic() + local rt = self.bufferSum/self.inputSum -- Ratio: how close to required input + if rt > 1 then rt = 1 end + self._colour = ColourValue(0.6 + (self.originalColour.r-0.6)*rt, + 0.6 + (self.originalColour.g-0.6)*rt, + 0.6 + (self.originalColour.b-0.6)*rt, 1) -- Calculate colour relative to how close the organelle is to have enough input agents to produce +end + + +-- Checks if processing organelle wants to store a given agent. +-- It wants an agent if it has that agent as input and its buffer relatively more full than it's process cooldown has left. +-- +-- @param agentId +-- The agent to check for +-- +-- @returns wantsAgent +-- true if the agent wants the agent, false if it can't use or doesn't want the agent +function ProcessOrganelle:wantsInputAgent(agentId) + return (self.inputAgents[agentId] ~= nil and + self.remainingCooldown / (self.inputAgents[agentId] - self.buffers[agentId]) < (self.processCooldown / self.inputAgents[agentId])) -- calculate if it has enough buffered relative the amount of time left. +end + + +-- Called by Microbe:update +-- +-- Add output agent to the recipy of the organelle +-- +-- @param microbe +-- The microbe containing the organelle +-- +-- @param milliseconds +-- The time since the last call to update() +function ProcessOrganelle:update(microbe, milliseconds) + Organelle.update(self, microbe, milliseconds) + self.remainingCooldown = self.remainingCooldown - milliseconds -- update process cooldown + if self.remainingCooldown < 0 then self.remainingCooldown = 0 end + if self.remainingCooldown <= 0 then + -- Attempt to produce + for agentId,amount in pairs(self.inputAgents) do + if self.buffers[agentId] < self.inputAgents[agentId] then + return -- not enough agent material for some agent type. Cannot produce. + end + end + -- Sufficient agent material is available for production + self.remainingCooldown = self.processCooldown -- Restart cooldown + + for agentId,amount in pairs(self.inputAgents) do -- Foreach input agent, take it out of the buffer + self.buffers[agentId] = self.buffers[agentId] - amount + self.bufferSum = self.bufferSum - amount + end + self._needsColourUpdate = true -- Update colours for displaying completeness of organelle production + self:updateColourDynamic() + for agentId,amount in pairs(self.outputAgents) do + microbe:storeAgent(agentId, amount) + end + end +end + + +-- Override from Organelle:setColour +function ProcessOrganelle:setColour(colour) + Organelle.setColour(self, colour) + self.originalColour = colour + local rt = self.bufferSum/self.inputSum -- Ratio: how close to required input + self:updateColourDynamic() + self._needsColourUpdate = true +end + +-- Buffer amounts aren't stored, could be added fairly easily +function ProcessOrganelle:storage() + local storage = Organelle.storage(self) + storage:set("remainingCooldown", self.remainingCooldown) + local inputAgentsSt = StorageList() + for agentId, amount in pairs(self.inputAgents) do + inputStorage = StorageContainer() + inputStorage:set("agentId", agentId) + inputStorage:set("amount", amount) + inputAgentsSt:append(inputStorage) + end + storage:set("inputAgents", inputAgentsSt) + local outputAgentsSt = StorageList() + for agentId, amount in pairs(self.outputAgents) do + outputStorage = StorageContainer() + outputStorage:set("agentId", agentId) + outputStorage:set("amount", amount) + outputAgentsSt:append(outputStorage) + end + storage:set("outputAgents", outputAgentsSt) + return storage +end + + +function ProcessOrganelle:load(storage) + Organelle.load(self, storage) + self.originalColour = self._colour + self.remainingCooldown = storage:get("remainingCooldown", 0) + local inputAgentsSt = storage:get("inputAgents", {}) + for i = 1,inputAgentsSt:size() do + local inputStorage = inputAgentsSt:get(i) + self:addRecipyInput(inputStorage:get("agentId", 0), inputStorage:get("amount", 0)) + end + local outputAgentsSt = storage:get("outputAgents", {}) + for i = 1,outputAgentsSt:size() do + local outputStorage = outputAgentsSt:get(i) + self:addRecipyOutput(outputStorage:get("agentId", 0), outputStorage:get("amount", 0)) + end +end diff --git a/res/scripts/microbe_stage/setup.lua b/res/scripts/microbe_stage/setup.lua index 340522e7cea..b25639d4595 100644 --- a/res/scripts/microbe_stage/setup.lua +++ b/res/scripts/microbe_stage/setup.lua @@ -33,17 +33,19 @@ local function setupCamera() end local function setupAgents() - AgentRegistry.registerAgentType("energy", "Energy") + AgentRegistry.registerAgentType("atp", "ATP") AgentRegistry.registerAgentType("oxygen", "Oxygen") AgentRegistry.registerAgentType("nitrate", "Nitrate") - AgentRegistry.registerAgentType("faxekondium", "Faxekondium") + AgentRegistry.registerAgentType("glucose", "Glucose") + AgentRegistry.registerAgentType("co2", "CO2") + AgentRegistry.registerAgentType("oxytoxy", "OxyToxy NT") end local function createSpawnSystem() local spawnSystem = SpawnSystem() local testFunction = function(pos) - -- Setting up an emitter for energy + -- Setting up an emitter for oxygen local entity = Entity() -- Rigid body local rigidBody = RigidBodyComponent() @@ -66,34 +68,77 @@ local function createSpawnSystem() local sceneNode = OgreSceneNodeComponent() sceneNode.meshName = "molecule.mesh" entity:addComponent(sceneNode) - -- Emitter energy - local energyEmitter = AgentEmitterComponent() - entity:addComponent(energyEmitter) - energyEmitter.agentId = AgentRegistry.getAgentId("energy") - energyEmitter.emitInterval = 1000 - energyEmitter.emissionRadius = 1 - energyEmitter.maxInitialSpeed = 10 - energyEmitter.minInitialSpeed = 2 - energyEmitter.minEmissionAngle = Degree(0) - energyEmitter.maxEmissionAngle = Degree(360) - energyEmitter.meshName = "molecule.mesh" - energyEmitter.particlesPerEmission = 1 - energyEmitter.particleLifeTime = 5000 - energyEmitter.particleScale = Vector3(0.3, 0.3, 0.3) - energyEmitter.potencyPerParticle = 300.0 + -- Emitter oxygen + local oxygenEmitter = AgentEmitterComponent() + entity:addComponent(oxygenEmitter) + oxygenEmitter.agentId = AgentRegistry.getAgentId("oxygen") + oxygenEmitter.emitInterval = 1000 + oxygenEmitter.emissionRadius = 1 + oxygenEmitter.maxInitialSpeed = 10 + oxygenEmitter.minInitialSpeed = 2 + oxygenEmitter.minEmissionAngle = Degree(0) + oxygenEmitter.maxEmissionAngle = Degree(360) + oxygenEmitter.meshName = "molecule.mesh" + oxygenEmitter.particlesPerEmission = 1 + oxygenEmitter.particleLifeTime = 5000 + oxygenEmitter.particleScale = Vector3(0.3, 0.3, 0.3) + oxygenEmitter.potencyPerParticle = 2.0 + + return entity + end + local testFunction2 = 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 = 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 glucose + local glucoseEmitter = AgentEmitterComponent() + entity:addComponent(glucoseEmitter) + glucoseEmitter.agentId = AgentRegistry.getAgentId("glucose") + glucoseEmitter.emitInterval = 2000 + glucoseEmitter.emissionRadius = 1 + glucoseEmitter.maxInitialSpeed = 10 + glucoseEmitter.minInitialSpeed = 2 + glucoseEmitter.minEmissionAngle = Degree(0) + glucoseEmitter.maxEmissionAngle = Degree(360) + glucoseEmitter.meshName = "molecule.mesh" + glucoseEmitter.particlesPerEmission = 1 + glucoseEmitter.particleLifeTime = 5000 + glucoseEmitter.particleScale = Vector3(0.3, 0.3, 0.3) + glucoseEmitter.potencyPerParticle = 1.0 return entity end --Spawn one emitter on average once in every square of sidelength 10 -- (square dekaunit?) - spawnSystem:addSpawnType(testFunction, 1/10^2, 30) + spawnSystem:addSpawnType(testFunction, 1/20^2, 30) + spawnSystem:addSpawnType(testFunction2, 1/20^2, 30) return spawnSystem end local function setupEmitter() - -- Setting up an emitter for energy - local entity = Entity("energy-emitter") + -- Setting up an emitter for glucose + local entity = Entity("glucose-emitter") -- Rigid body local rigidBody = RigidBodyComponent() rigidBody.properties.friction = 0.2 @@ -118,59 +163,21 @@ local function setupEmitter() local sceneNode = OgreSceneNodeComponent() sceneNode.meshName = "molecule.mesh" entity:addComponent(sceneNode) - -- Emitter energy - local energyEmitter = AgentEmitterComponent() - entity:addComponent(energyEmitter) - energyEmitter.agentId = AgentRegistry.getAgentId("energy") - energyEmitter.emitInterval = 1000 - energyEmitter.emissionRadius = 1 - energyEmitter.maxInitialSpeed = 10 - energyEmitter.minInitialSpeed = 2 - energyEmitter.minEmissionAngle = Degree(0) - energyEmitter.maxEmissionAngle = Degree(360) - energyEmitter.meshName = "molecule.mesh" - energyEmitter.particlesPerEmission = 1 - energyEmitter.particleLifeTime = 5000 - energyEmitter.particleScale = Vector3(0.3, 0.3, 0.3) - energyEmitter.potencyPerParticle = 3.0 - -- Setting up an emitter for agent 2 - local entity2 = Entity("oxygen-emitter") - -- Rigid body - 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( - Vector3(20, -10, 0), - Quaternion(Radian(Degree(0)), Vector3(1, 0, 0)), - Vector3(0, 0, 0), - Vector3(0, 0, 0) - ) - rigidBody.properties:touch() - entity2:addComponent(rigidBody) - -- Scene node - sceneNode = OgreSceneNodeComponent() - sceneNode.meshName = "molecule.mesh" - entity2:addComponent(sceneNode) - -- Emitter Agent 2 - local agent2Emitter = AgentEmitterComponent() - entity2:addComponent(agent2Emitter) - agent2Emitter.agentId = AgentRegistry.getAgentId("oxygen") - agent2Emitter.emitInterval = 1000 - agent2Emitter.emissionRadius = 1 - agent2Emitter.maxInitialSpeed = 10 - agent2Emitter.minInitialSpeed = 2 - agent2Emitter.minEmissionAngle = Degree(0) - agent2Emitter.maxEmissionAngle = Degree(360) - agent2Emitter.meshName = "molecule.mesh" - agent2Emitter.particlesPerEmission = 1 - agent2Emitter.particleLifeTime = 5000 - agent2Emitter.particleScale = Vector3(0.3, 0.3, 0.3) - agent2Emitter.potencyPerParticle = 1.0 + -- Emitter glucose + local glucoseEmitter = AgentEmitterComponent() + entity:addComponent(glucoseEmitter) + glucoseEmitter.agentId = AgentRegistry.getAgentId("glucose") + glucoseEmitter.emitInterval = 1000 + glucoseEmitter.emissionRadius = 1 + glucoseEmitter.maxInitialSpeed = 10 + glucoseEmitter.minInitialSpeed = 2 + glucoseEmitter.minEmissionAngle = Degree(0) + glucoseEmitter.maxEmissionAngle = Degree(360) + glucoseEmitter.meshName = "molecule.mesh" + glucoseEmitter.particlesPerEmission = 1 + glucoseEmitter.particleLifeTime = 5000 + glucoseEmitter.particleScale = Vector3(0.3, 0.3, 0.3) + glucoseEmitter.potencyPerParticle = 3.0 end @@ -210,7 +217,6 @@ local function setupHud() playerAgentCountText.properties.left = -80 playerAgentCountText.properties.top = -AGENTS_HEIGHT playerAgentCountText.properties:touch() - end local function setupPlayer() @@ -236,26 +242,30 @@ local function setupPlayer() backwardOrganelle:setColour(ColourValue(1, 0, 0, 1)) player:addOrganelle(0, -2, backwardOrganelle) -- Storage energy - local storageOrganelle = StorageOrganelle(AgentRegistry.getAgentId("energy"), 100.0) + local storageOrganelle = StorageOrganelle(AgentRegistry.getAgentId("atp"), 100.0) storageOrganelle:addHex(0, 0) storageOrganelle:setColour(ColourValue(0, 1, 0, 1)) player:addOrganelle(0, 0, storageOrganelle) - player:storeAgent(AgentRegistry.getAgentId("energy"), 10) + player:storeAgent(AgentRegistry.getAgentId("atp"), 20) -- Storage agent 2 local storageOrganelle2 = StorageOrganelle(AgentRegistry.getAgentId("oxygen"), 100.0) storageOrganelle2:addHex(0, 0) - storageOrganelle2:setColour(ColourValue(0, 1, 1, 1)) + storageOrganelle2:setColour(ColourValue(0, 1, 0.5, 1)) player:addOrganelle(0, -1, storageOrganelle2) -- Storage agent 3 - local storageOrganelle3 = StorageOrganelle(AgentRegistry.getAgentId("faxekondium"), 100.0) + local storageOrganelle3 = StorageOrganelle(AgentRegistry.getAgentId("glucose"), 100.0) storageOrganelle3:addHex(0, 0) - storageOrganelle3:setColour(ColourValue(1, 1, 0, 1)) + storageOrganelle3:setColour(ColourValue(0.5, 1, 0, 1)) player:addOrganelle(-1, 0, storageOrganelle3) - -- Storage agent 4 - local storageOrganelle4 = StorageOrganelle(AgentRegistry.getAgentId("nitrate"), 100.0) - storageOrganelle4:addHex(0, 0) - storageOrganelle4:setColour(ColourValue(1, 0, 1, 0)) - player:addOrganelle(1, -1, storageOrganelle4) + -- Producer making atp from oxygen and glucose + local processOrganelle1 = ProcessOrganelle(20000) -- 20 second minimum time between producing oxytoxy + processOrganelle1:addRecipyInput(AgentRegistry.getAgentId("glucose"), 1) + processOrganelle1:addRecipyInput(AgentRegistry.getAgentId("oxygen"), 6) + processOrganelle1:addRecipyOutput(AgentRegistry.getAgentId("atp"), 38) + processOrganelle1:addRecipyOutput(AgentRegistry.getAgentId("co2"), 6) + processOrganelle1:addHex(0, 0) + processOrganelle1:setColour(ColourValue(1, 0, 1, 0)) + player:addOrganelle(1, -1, processOrganelle1) end setupAgents()