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