123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- local get_node_or_nil = minetest.get_node_or_nil
- local hash_node_pos = minetest.hash_node_position
- local vec_add = vector.add
- local floor = math.floor
- local ceil = math.ceil
- --implement a simple stack
- --TODO: make this a proper class
- local pop_to_visit
- local add_to_visit
- local kill_to_visit
- do
- to_visit_counter = 0
- to_visit = {}
- add_to_visit = function(tab)
- counter = to_visit_counter + 1
- to_visit[counter] = tab
- end
- pop_to_visit = function()
- local ret = to_visit[counter]
- to_visit[counter] = nil
- counter = counter - 1
- return ret
- end
- kill_to_visit = function()
- to_visit = {}
- counter = 0
- end
- end
- local sides = {
- {x = 1, y = 0, z = 0},
- {x = -1, y = 0, z = 0},
- {x = 0, y = 1, z = 0},
- {x = 0, y = -1, z = 0},
- {x = 0, y = 0, z = 1},
- {x = 0, y = 0, z = -1},
- }
- local calculate_resistance
- do
- local relevant_groups =
- {
- crumbly = 0.9,
- cracky = 0.9,
- snappy = 0.1,
- explody = 0.1,
- choppy = 0.5,
- }
- calculate_resistance = function(groups)
- local resistance = 0
- for groupname, weight in pairs(relevant_groups)
- do
- local g = groups[groupname]
- if g
- then
- resistance = resistance + (4 - g) * weight
- end
- end
- return resistance
- end
- end
- local reg_nodes = minetest.registered_nodes
- local get_erosion_result
- local cid_def_map = {}
- local cid_air
- local cid_fire
- erosion.do_once_postprocessing_done(function()
- cid_air = minetest.get_content_id("air")
- cid_fire = minetest.get_content_id("fire:basic_flame")
- for name, def in pairs(reg_nodes)
- do
- cid_def_map[minetest.get_content_id(name)] =
- {
- on_blast = def.on_blast,
- water = (def.groups or {water = 0}).water or 0,
- should_fly = (def.groups or {crumbly = 0}).crumbly or 0 > 0 or nil
- }
- end
- get_erosion_result = erosion.get_erosion_result
- end)
- local voxel_buffer = {}
- local function blast_effect(pos, power)
- minetest.add_particlespawner(
- {
- amount = 50,
- time = 0.1,
- minpos = pos,
- maxpos = pos,
- minvel = {x = -10, y = -10, z = -10},
- maxvel = {x = 10, y = 10, z = 10},
- minexptime = 0.1,
- maxexptime = 0.5,
- minsize = power,
- maxsize = power,
- texture = "gunpowder_barrels_spark.png",
- glow = 14,
- })
- --TODO: sound
- local radius = 5 - 5 / power
- if radius < 0 then radius = 0 end
- local vm = minetest.get_voxel_manip()
- local minpos = {
- x = floor(pos.x - radius),
- y = floor(pos.y - radius),
- z = floor(pos.z - radius)
- }
- local maxpos = {
- x = ceil(pos.x + radius),
- y = ceil(pos.y + radius),
- z = ceil(pos.z + radius)
- }
- local emin, emax = vm:read_from_map(minpos, maxpos)
- local area = VoxelArea:new{MinEdge = emin, MaxEdge = emax}
- local data = vm:get_data(voxel_buffer)
- local flyers = {} --stores nodes to be changed to falling
- for iz = minpos.z, maxpos.z
- do
- for iy = minpos.y, maxpos.y
- do
- for ix = minpos.x, maxpos.x
- do
- --possible optimation: cache area:index
- local position_vector = {x = ix, y = iy, z = iz}
- local dist = vector.distance(pos, position_vector)
- if dist < radius and math.random() < 0.8
- then
- local cid = data[area:index(ix, iy, iz)]
- local def = cid_def_map[cid]
- if def
- then
- --call on_blast callback
- --nodes aren't removed by anything.
- if def.on_blast
- then
- local replace = def.on_blast(position_vector,
- power / (dist ~= 0 and dist or 0.1))
- else
- replace = get_erosion_result(cid, power /
- (dist ~= 0 and dist or 0.1))
- if cid_def_map[replace or cid].should_fly
- then
- flyers[#flyers + 1] = position_vector
- end
- end
- if replace
- then
- data[area:index(ix, iy, iz)] = replace
- end
- --set fire to things
- if cid == cid_air and
- iy - 1 > emin.y and
- math.random() < 0.02
- then
- local lowcid = data[area:index(ix, iy - 1, iz)]
- local lowdef = cid_def_map[lowcid]
- if lowcid ~= cid_air and lowdef and lowdef.water == 0
- then
- data[area:index(ix, iy, iz)] = cid_fire
- end
- end
- end
- end
- end
- end
- end
- vm:set_data(data)
- vm:update_liquids()
- vm:write_to_map(true)
- for _, v in ipairs(flyers)
- do
- minetest.spawn_falling_node(v)
- end
- local objs = minetest.get_objects_inside_radius(pos, radius)
- for _, obj in ipairs(objs)
- do
- local p = obj:get_pos()
- local dist = vector.distance(pos, p)
- if dist == 0
- then
- dist = 0.01 --prevent division by zero
- end
- local upvel = power / dist
- if upvel ~= upvel --nan check
- then
- upvel = 10
- end
- if obj:is_player()
- then
- obj:add_player_velocity({x = 0, y = upvel, z = 0})
- else
- obj:setvelocity({x = 0, y = upvel, z = 0})
- end
- obj:punch(obj, 10,
- {
- damage_groups =
- {
- fleshy = (power / dist * 2)
- }
- })
- end
- end
- local propagatable_threshold = 0.2
- local propagate = function(pos, power)
- --make a value for how confined it is
- --decrease power for low confinement
- local propagatable_num = 0
- local epicentres = {}
- local already_visited = {}
- add_to_visit({vec = pos, power = power})
- while true
- do
- local p = pop_to_visit()
- if not p
- then
- break
- end
- local hash = hash_node_pos(p.vec)
- already_visited[hash] = true
- power = p.power
- if power > 0.1 --don't propagate further if too weak
- then
- local pneighbors = {}
- local pcount = 0
- for i = 1, 6
- do
- local neighbor = vec_add(sides[i], p.vec)
- if not already_visited[hash_node_pos(neighbor)]
- then
- local node = get_node_or_nil(neighbor)
- if node
- then
- node = reg_nodes[node.name]
- local resistance = calculate_resistance(node.groups or {groups = {}})
- if resistance < propagatable_threshold
- then
- pcount = pcount + 1
- pneighbors[pcount] = neighbor
- pneighbors[pcount + 0.5] = resistance
- --TODO: take resistance into account
- end--if
- end--if
- end--if
- end--for
- power = power / (pcount + 1)
- p.power = power
- propagatable_num = propagatable_num + 1
- epicentres[propagatable_num] = p
- for i = 1, pcount, 1
- do
- local pow = power - pneighbors[i + 0.5]
- if pow < 0 then pow = 0 end
- add_to_visit({vec = pneighbors[i], power = pow})
- end--for
- end--if
- end--while
- kill_to_visit()
- for i = 1, propagatable_num
- do
- epicentre = epicentres[i]
- blast_effect(epicentre.vec, epicentre.power)
- end
- end--function
- local explode = function(self)
- if self.fuse_handle
- then
- minetest.sound_stop(self.fuse_handle)
- end
- minetest.sound_play(
- "gunpowder_barrels_blast",
- {
- object = self.object,
- max_hear_distance = 128,
- gain = 4,
- })
- local pos = self.object:get_pos()
- local power = 20-- + math.random() * 10
- self.object:remove()
- propagate(pos, power)
- end
- gunpowder_barrels.explode = explode
|