VoxelDungeon/mods/voxeldungeon/blobs.lua

348 lines
8.8 KiB
Lua

--[[
Voxel Dungeon
Copyright (C) 2019 Noodlemire
Pixel Dungeon
Copyright (C) 2012-2015 Oleg Dolya
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
--]]
voxeldungeon.blobs = {}
voxeldungeon.blobs.registered_blobs = {}
local TIMESCALE = 1
local MAX_CYCLE_SIZE = 100
local function expandUpon(posses, spreadCondition)
local newposses = voxeldungeon.smartVectorTable()
for i = 1, posses.size() do
local p = posses.getVector(i)
local a = posses.getValue(i)
if a > 0 then
for _, n in pairs(voxeldungeon.utils.NEIGHBORS7) do
local newpos = vector.add(p, n)
local posval = posses.get(newpos) or 0
if spreadCondition(newpos) or minetest.get_node(p).name == "ignore" then
newposses.set(newpos, posval)
end
end
end
end
return newposses
end
--spreadCondition, evolve, effectTerr, effectObj
function voxeldungeon.blobs.register(name, def)
name = "voxeldungeon:blob_"..name
voxeldungeon.blobs.registered_blobs[name] = def
local blob = voxeldungeon.blobs.registered_blobs[name]
blob.posses = voxeldungeon.smartVectorTable()
local possesString = voxeldungeon.storage.getStr(name.."_posses")
if possesString then
blob.posses.table = minetest.deserialize(possesString)
end
blob.timer = voxeldungeon.storage.getNum(name.."_timer") or TIMESCALE
blob.queue = voxeldungeon.storage.getNum(name.."_queue") or 1
blob.on_step = function(dtime)
voxeldungeon.storage.put(name.."_timer", blob.timer)
if blob.timer > 0 then
blob.timer = blob.timer - dtime
return
end
blob.timer = TIMESCALE
if blob.posses.size() > 0 then
blob:evolve()
if blob.effectTerr then
for i = 1, blob.posses.size() do
blob:effectTerr(blob.posses.getVector(i), blob.posses.getValue(i))
end
end
if blob.effectObj then
for _, player in ipairs(minetest.get_connected_players()) do
if not voxeldungeon.buffs.get_buff("voxeldungeon:gasimmunity", player) then
local pos = vector.round(player:get_pos())
local val = blob.posses.get(pos) or 0
if val > 0 then
blob:effectObj(pos, val, player)
end
end
end
for e = 1, entitycontrol.count_entities() do
local entity = entitycontrol.get_entity(e)
if entitycontrol.isAlive(e) and not voxeldungeon.buffs.get_buff("voxeldungeon:gasimmunity", entity) then
local pos = vector.round(entity:get_pos())
local val = blob.posses.get(pos) or 0
if val > 0 then
blob:effectObj(pos, val, entity)
end
end
end
end
end
voxeldungeon.storage.put(name.."_posses", minetest.serialize(blob.posses.table))
voxeldungeon.storage.put(name.."_queue", blob.queue)
end
blob.evolve = blob.evolve or function(blob)
local offload = expandUpon(blob.posses, blob.spreadCondition)
local cycleSize = math.min(MAX_CYCLE_SIZE, offload.size())
for i = blob.queue, blob.queue + offload.size() - 1 do
local p = offload.getVector(i)
if p then
if minetest.get_node(p).name ~= "ignore" then
if blob.spreadCondition(p) then
local count = 1
local sum = blob.posses.getValue(i) or 0
for _, n in pairs(voxeldungeon.utils.NEIGHBORS6) do
local neighbor = vector.add(p, n)
if blob.spreadCondition(neighbor) then
sum = sum + (blob.posses.get(neighbor) or 0)
count = count + 1
end
end
local value = 0
if sum >= count then
value = math.floor(sum / count) - 1
end
offload.set(p, value)
else
offload.del(p)
end
else
local value = blob.posses.get(p) or 0
offload.set(p, value)
end
end
end
for i = offload.size(), 1, -1 do
if offload.getValue(i) == 0 then
offload.del(offload.getVector(i))
end
end
blob.posses = offload
blob.queue = blob.queue + MAX_CYCLE_SIZE
if blob.queue > offload.size() then
blob.queue = 1
end
end
blob.seed = function(pos, amount)
blob.posses.set(pos, amount)
end
blob.clear = function(pos)
local cur = blob.posses.get(pos)
if cur then
blob.posses.del(pos)
end
end
minetest.register_globalstep(blob.on_step)
end
function voxeldungeon.blobs.seed(name, pos, amount)
name = "voxeldungeon:blob_"..name
pos = vector.round(pos)
voxeldungeon.blobs.registered_blobs[name].seed(pos, amount)
end
function voxeldungeon.blobs.clear(names, pos)
if type(names) == "string" then
names = {names}
end
pos = vector.round(pos)
for _, name in ipairs(names) do
voxeldungeon.blobs.registered_blobs[name].clear(pos)
end
end
function voxeldungeon.blobs.get(name, pos)
return voxeldungeon.blobs.registered_blobs[name].posses.get(vector.round(pos)) or 0
end
voxeldungeon.blobs.register("corrosivegas", {
spreadCondition = function(pos)
return not voxeldungeon.utils.solid(pos)
end,
effectTerr = function(blob, pos, amount)
if voxeldungeon.utils.randomDecimal(blob.posses.size() / (blob.posses.size() + 2)) <= 1/3 then
voxeldungeon.particles.corrosion(pos)
end
end,
effectObj = function(blob, pos, amount, obj)
voxeldungeon.buffs.attach_buff("voxeldungeon:corrosion", obj, 2, {dmg = math.floor(amount / 25)})
end
})
voxeldungeon.blobs.register("fire", {
spreadCondition = function(pos)
local node = minetest.get_node(pos)
local near_fire = false
for _, offset in ipairs(voxeldungeon.utils.NEIGHBORS7) do
local pos_o = vector.add(pos, offset)
local node_o = minetest.get_node(pos_o)
if minetest.get_item_group(node_o.name, "water") > 0 then
return false
elseif voxeldungeon.blobs.get("voxeldungeon:blob_fire", pos_o) > 0 and minetest.get_item_group(node_o.name, "flammable") > 0 then
near_fire = true
end
end
if near_fire then return true end
return voxeldungeon.blobs.get("voxeldungeon:blob_fire", pos) > 0 or minetest.get_item_group(node.name, "flammable") > 0
end,
evolve = function(blob)
local offload = expandUpon(blob.posses, blob.spreadCondition)
local cycleSize = math.min(MAX_CYCLE_SIZE, offload.size())
for i = blob.queue, blob.queue + offload.size() - 1 do
local p = offload.getVector(i)
if p then
if minetest.get_node(p).name ~= "ignore" then
if blob.spreadCondition(p) then
local value = voxeldungeon.blobs.get("voxeldungeon:blob_fire", p) - 1
if value == 0 then
local node = minetest.get_node_or_nil(p)
if node and minetest.get_item_group(node.name, "flammable") >= 1 then
local on_burn = minetest.registered_nodes[node.name].on_burn
if on_burn then
on_burn(p)
else
minetest.remove_node(p)
minetest.punch_node(p)
minetest.place_node(p, {name = "voxeldungeon:embers"})
end
end
offload.del(p)
else
if value < 0 then
value = 2
end
offload.set(p, value)
end
else
offload.del(p)
end
else
local value = blob.posses.get(p) or 0
offload.set(p, value)
cycleSize = cycleSize + 1
end
end
end
for i = offload.size(), 1, -1 do
if offload.getValue(i) == 0 then
offload.del(offload.getVector(i))
end
end
blob.posses = offload
blob.queue = blob.queue + MAX_CYCLE_SIZE
if blob.queue > offload.size() then
blob.queue = 1
end
end,
effectTerr = function(blob, pos, amount, objs)
voxeldungeon.particles.fire(pos)
end,
effectObj = function(blob, pos, amount, obj)
voxeldungeon.buffs.attach_buff("voxeldungeon:burning", obj, 8)
end
})
voxeldungeon.blobs.register("paralyticgas", {
spreadCondition = function(pos)
return not voxeldungeon.utils.solid(pos) and voxeldungeon.blobs.get("voxeldungeon:blob_toxicgas", pos) == 0
end,
effectTerr = function(blob, pos, amount)
if voxeldungeon.utils.randomDecimal(blob.posses.size() / (blob.posses.size() + 2)) <= 1/3 then
voxeldungeon.particles.paralytic(pos)
end
end,
effectObj = function(blob, pos, amount, obj)
voxeldungeon.buffs.attach_buff("voxeldungeon:paralyzed", obj, 10)
end
})
voxeldungeon.blobs.register("toxicgas", {
spreadCondition = function(pos)
return not voxeldungeon.utils.solid(pos) and voxeldungeon.blobs.get("voxeldungeon:blob_paralyticgas", pos) == 0
end,
effectTerr = function(blob, pos, amount)
if voxeldungeon.utils.randomDecimal(blob.posses.size() / (blob.posses.size() + 2)) <= 1/3 then
voxeldungeon.particles.toxic(pos)
end
end,
effectObj = function(blob, pos, amount, obj)
voxeldungeon.mobs.damage(obj, voxeldungeon.utils.getChapter(pos), "toxic gas")
end
})