Fix indents in mcl_explosions

This commit is contained in:
Wuzzy 2020-04-30 21:12:30 +02:00
parent 679e2b1b70
commit 026d406d4b
1 changed files with 279 additions and 285 deletions

View File

@ -1,19 +1,13 @@
--[[ .__ .__ --[[
____ ___ _________ | | ____ _____|__| ____ ____ ______ Explosion API mod for Minetest (adapted to MineClone 2)
_/ __ \\ \/ /\____ \| | / _ \/ ___/ |/ _ \ / \ / ___/
\ ___/ > < | |_> > |_( <_> )___ \| ( <_> ) | \\___ \
\___ >__/\_ \| __/|____/\____/____ >__|\____/|___| /____ >
\/ \/|__| \/ \/ \/
Explosion API mod for Minetest (adapted to MineClone 2) This mod is based on the Minetest explosion API mod, but has been changed
to have the same explosion mechanics as Minecraft and work with MineClone.
The computation-intensive parts of the mod has been optimized to allow for
larger explosions and faster world updating.
This mod is based on the Minetest explosion API mod, but has been changed This mod was created by Elias Astrom <ryvnf@riseup.net> and is released
to have the same explosion mechanics as Minecraft and work with MineClone. under the LGPLv2.1 license.
The computation-intensive parts of the mod has been optimized to allow for
larger explosions and faster world updating.
This mod was created by Elias Astrom <ryvnf@riseup.net> and is released
under the LGPLv2.1 license.
--]] --]]
@ -36,348 +30,348 @@ local STEP_LENGTH = 0.3
local N_EXPOSURE_RAYS = 16 local N_EXPOSURE_RAYS = 16
minetest.register_on_mods_loaded(function() minetest.register_on_mods_loaded(function()
-- Store blast resistance values by content ids to improve performance. -- Store blast resistance values by content ids to improve performance.
for name, def in pairs(minetest.registered_nodes) do for name, def in pairs(minetest.registered_nodes) do
node_blastres[minetest.get_content_id(name)] = def._mcl_blast_resistance or 0 node_blastres[minetest.get_content_id(name)] = def._mcl_blast_resistance or 0
node_on_blast[minetest.get_content_id(name)] = def.on_blast node_on_blast[minetest.get_content_id(name)] = def.on_blast
node_walkable[minetest.get_content_id(name)] = def.walkable node_walkable[minetest.get_content_id(name)] = def.walkable
end end
end) end)
-- Compute the rays which make up a sphere with radius. Returns a list of rays -- Compute the rays which make up a sphere with radius. Returns a list of rays
-- which can be used to trace explosions. This function is not efficient -- which can be used to trace explosions. This function is not efficient
-- (especially for larger radiuses), so the generated rays for various radiuses -- (especially for larger radiuses), so the generated rays for various radiuses
-- should be cached and reused. -- should be cached and reused.
-- --
-- Should be possible to improve by using a midpoint circle algorithm multiple -- Should be possible to improve by using a midpoint circle algorithm multiple
-- times to create the sphere, currently uses more of a brute-force approach. -- times to create the sphere, currently uses more of a brute-force approach.
local function compute_sphere_rays(radius) local function compute_sphere_rays(radius)
local rays = {} local rays = {}
local sphere = {} local sphere = {}
for i=1, 2 do for i=1, 2 do
for y = -radius, radius do for y = -radius, radius do
for z = -radius, radius do for z = -radius, radius do
for x = -radius, 0, 1 do for x = -radius, 0, 1 do
local d = x * x + y * y + z * z local d = x * x + y * y + z * z
if d <= radius * radius then if d <= radius * radius then
local pos = { x = x, y = y, z = z } local pos = { x = x, y = y, z = z }
sphere[minetest.hash_node_position(pos)] = pos sphere[minetest.hash_node_position(pos)] = pos
break break
end end
end end
end end
end end
end end
for i=1,2 do for i=1,2 do
for x = -radius, radius do for x = -radius, radius do
for z = -radius, radius do for z = -radius, radius do
for y = -radius, 0, 1 do for y = -radius, 0, 1 do
local d = x * x + y * y + z * z local d = x * x + y * y + z * z
if d <= radius * radius then if d <= radius * radius then
local pos = { x = x, y = y, z = z } local pos = { x = x, y = y, z = z }
sphere[minetest.hash_node_position(pos)] = pos sphere[minetest.hash_node_position(pos)] = pos
break break
end end
end end
end end
end end
end end
for i=1,2 do for i=1,2 do
for x = -radius, radius do for x = -radius, radius do
for y = -radius, radius do for y = -radius, radius do
for z = -radius, 0, 1 do for z = -radius, 0, 1 do
local d = x * x + y * y + z * z local d = x * x + y * y + z * z
if d <= radius * radius then if d <= radius * radius then
local pos = { x = x, y = y, z = z } local pos = { x = x, y = y, z = z }
sphere[minetest.hash_node_position(pos)] = pos sphere[minetest.hash_node_position(pos)] = pos
break break
end end
end end
end end
end end
end end
for _, pos in pairs(sphere) do for _, pos in pairs(sphere) do
rays[#rays + 1] = vector.normalize(pos) rays[#rays + 1] = vector.normalize(pos)
end end
return rays return rays
end end
-- Add particles from explosion -- Add particles from explosion
-- --
-- Parameters: -- Parameters:
-- pos - The position of the explosion -- pos - The position of the explosion
-- radius - The radius of the explosion -- radius - The radius of the explosion
local function add_particles(pos, radius) local function add_particles(pos, radius)
minetest.add_particlespawner({ minetest.add_particlespawner({
amount = 64, amount = 64,
time = 0.125, time = 0.125,
minpos = pos, minpos = pos,
maxpos = pos, maxpos = pos,
minvel = {x = -radius, y = -radius, z = -radius}, minvel = {x = -radius, y = -radius, z = -radius},
maxvel = {x = radius, y = radius, z = radius}, maxvel = {x = radius, y = radius, z = radius},
minacc = vector.new(), minacc = vector.new(),
maxacc = vector.new(), maxacc = vector.new(),
minexptime = 0.5, minexptime = 0.5,
maxexptime = 1.0, maxexptime = 1.0,
minsize = radius * 0.5, minsize = radius * 0.5,
maxsize = radius * 1.0, maxsize = radius * 1.0,
texture = "tnt_smoke.png", texture = "tnt_smoke.png",
}) })
end end
-- Get position from hash. This should be identical to -- Get position from hash. This should be identical to
-- 'minetest.get_position_from_hash' but is used in case the hashing function -- 'minetest.get_position_from_hash' but is used in case the hashing function
-- would change. -- would change.
local function get_position_from_hash(hash) local function get_position_from_hash(hash)
local pos = {} local pos = {}
pos.x = (hash % 65536) - 32768 pos.x = (hash % 65536) - 32768
hash = math.floor(hash / 65536) hash = math.floor(hash / 65536)
pos.y = (hash % 65536) - 32768 pos.y = (hash % 65536) - 32768
hash = math.floor(hash / 65536) hash = math.floor(hash / 65536)
pos.z = (hash % 65536) - 32768 pos.z = (hash % 65536) - 32768
return pos return pos
end end
-- Traces the rays of an explosion, and updates the environment. -- Traces the rays of an explosion, and updates the environment.
-- --
-- Parameters: -- Parameters:
-- pos - Where the rays in the explosion should start from -- pos - Where the rays in the explosion should start from
-- strength - The strength of each ray -- strength - The strength of each ray
-- raydirs - The directions for each ray -- raydirs - The directions for each ray
-- radius - The maximum distance each ray will go -- radius - The maximum distance each ray will go
-- drop_chance - The chance that destroyed nodes will drop their items -- drop_chance - The chance that destroyed nodes will drop their items
-- --
-- Note that this function has been optimized, it contains code which has been -- Note that this function has been optimized, it contains code which has been
-- inlined to avoid function calls and unnecessary table creation. This was -- inlined to avoid function calls and unnecessary table creation. This was
-- measured to give a significant performance increase. -- measured to give a significant performance increase.
local function trace_explode(pos, strength, raydirs, radius, drop_chance) local function trace_explode(pos, strength, raydirs, radius, drop_chance)
local vm = minetest.get_voxel_manip() local vm = minetest.get_voxel_manip()
local emin, emax = vm:read_from_map(vector.subtract(pos, radius), local emin, emax = vm:read_from_map(vector.subtract(pos, radius),
vector.add(pos, radius)) vector.add(pos, radius))
local emin_x = emin.x local emin_x = emin.x
local emin_y = emin.y local emin_y = emin.y
local emin_z = emin.z local emin_z = emin.z
local ystride = (emax.x - emin_x + 1) local ystride = (emax.x - emin_x + 1)
local zstride = ystride * (emax.y - emin_y + 1) local zstride = ystride * (emax.y - emin_y + 1)
local pos_x = pos.x local pos_x = pos.x
local pos_y = pos.y local pos_y = pos.y
local pos_z = pos.z local pos_z = pos.z
local area = VoxelArea:new { local area = VoxelArea:new {
MinEdge = emin, MinEdge = emin,
MaxEdge = emax MaxEdge = emax
} }
local data = vm:get_data() local data = vm:get_data()
local destroy = {} local destroy = {}
-- Trace rays for environment destruction -- Trace rays for environment destruction
for i = 1, #raydirs do for i = 1, #raydirs do
local rpos_x = pos.x local rpos_x = pos.x
local rpos_y = pos.y local rpos_y = pos.y
local rpos_z = pos.z local rpos_z = pos.z
local rdir_x = raydirs[i].x local rdir_x = raydirs[i].x
local rdir_y = raydirs[i].y local rdir_y = raydirs[i].y
local rdir_z = raydirs[i].z local rdir_z = raydirs[i].z
local rstr = (0.7 + math.random() * 0.6) * strength local rstr = (0.7 + math.random() * 0.6) * strength
for r = 0, math.ceil(radius * (1.0 / STEP_LENGTH)) do for r = 0, math.ceil(radius * (1.0 / STEP_LENGTH)) do
local npos_x = math.floor(rpos_x + 0.5) local npos_x = math.floor(rpos_x + 0.5)
local npos_y = math.floor(rpos_y + 0.5) local npos_y = math.floor(rpos_y + 0.5)
local npos_z = math.floor(rpos_z + 0.5) local npos_z = math.floor(rpos_z + 0.5)
local idx = (npos_z - emin_z) * zstride + (npos_y - emin_y) * ystride + local idx = (npos_z - emin_z) * zstride + (npos_y - emin_y) * ystride +
npos_x - emin_x + 1 npos_x - emin_x + 1
local cid = data[idx] local cid = data[idx]
local br = node_blastres[cid] local br = node_blastres[cid]
local hash = (npos_z + 32768) * 65536 * 65536 + local hash = (npos_z + 32768) * 65536 * 65536 +
(npos_y + 32768) * 65536 + (npos_y + 32768) * 65536 +
npos_x + 32768 npos_x + 32768
rpos_x = rpos_x + STEP_LENGTH * rdir_x rpos_x = rpos_x + STEP_LENGTH * rdir_x
rpos_y = rpos_y + STEP_LENGTH * rdir_y rpos_y = rpos_y + STEP_LENGTH * rdir_y
rpos_z = rpos_z + STEP_LENGTH * rdir_z rpos_z = rpos_z + STEP_LENGTH * rdir_z
rstr = rstr - 0.75 * STEP_LENGTH - (br + 0.3) * STEP_LENGTH rstr = rstr - 0.75 * STEP_LENGTH - (br + 0.3) * STEP_LENGTH
if rstr <= 0 then if rstr <= 0 then
break break
end end
if cid ~= minetest.CONTENT_AIR then if cid ~= minetest.CONTENT_AIR then
destroy[hash] = idx destroy[hash] = idx
end end
end end
end end
-- Entities in radius of explosion -- Entities in radius of explosion
local punch_radius = 2 * strength local punch_radius = 2 * strength
local objs = minetest.get_objects_inside_radius(pos, punch_radius) local objs = minetest.get_objects_inside_radius(pos, punch_radius)
-- Trace rays for entity damage -- Trace rays for entity damage
for _, obj in pairs(objs) do for _, obj in pairs(objs) do
local ent = obj:get_luaentity() local ent = obj:get_luaentity()
-- Ignore items to lower lag -- Ignore items to lower lag
if obj:is_player() or (ent and ent.name ~= '__builtin.item') then if obj:is_player() or (ent and ent.name ~= '__builtin.item') then
local opos = obj:get_pos() local opos = obj:get_pos()
local collisionbox = nil local collisionbox = nil
if obj:is_player() then if obj:is_player() then
collisionbox = { -0.3, 0.0, -0.3, 0.3, 1.77, 0.3 } collisionbox = { -0.3, 0.0, -0.3, 0.3, 1.77, 0.3 }
elseif ent.name then elseif ent.name then
local def = minetest.registered_entities[ent.name] local def = minetest.registered_entities[ent.name]
collisionbox = def.collisionbox collisionbox = def.collisionbox
end end
if collisionbox then if collisionbox then
-- Create rays from random points in the collision box -- Create rays from random points in the collision box
local x1 = collisionbox[1] * 2 local x1 = collisionbox[1] * 2
local y1 = collisionbox[2] * 2 local y1 = collisionbox[2] * 2
local z1 = collisionbox[3] * 2 local z1 = collisionbox[3] * 2
local x2 = collisionbox[4] * 2 local x2 = collisionbox[4] * 2
local y2 = collisionbox[5] * 2 local y2 = collisionbox[5] * 2
local z2 = collisionbox[6] * 2 local z2 = collisionbox[6] * 2
local x_len = math.abs(x2 - x1) local x_len = math.abs(x2 - x1)
local y_len = math.abs(y2 - y1) local y_len = math.abs(y2 - y1)
local z_len = math.abs(z2 - z1) local z_len = math.abs(z2 - z1)
-- Move object position to the center of its bounding box -- Move object position to the center of its bounding box
opos.x = opos.x + x1 + x2 opos.x = opos.x + x1 + x2
opos.y = opos.y + y1 + y2 opos.y = opos.y + y1 + y2
opos.z = opos.z + z1 + z2 opos.z = opos.z + z1 + z2
-- Count number of rays from collision box which are unobstructed -- Count number of rays from collision box which are unobstructed
local count = N_EXPOSURE_RAYS local count = N_EXPOSURE_RAYS
for i = 1, N_EXPOSURE_RAYS do for i = 1, N_EXPOSURE_RAYS do
local rpos_x = opos.x + math.random() * x_len - x_len / 2 local rpos_x = opos.x + math.random() * x_len - x_len / 2
local rpos_y = opos.y + math.random() * y_len - y_len / 2 local rpos_y = opos.y + math.random() * y_len - y_len / 2
local rpos_z = opos.z + math.random() * z_len - z_len / 2 local rpos_z = opos.z + math.random() * z_len - z_len / 2
local rdir_x = pos.x - rpos_x local rdir_x = pos.x - rpos_x
local rdir_y = pos.y - rpos_y local rdir_y = pos.y - rpos_y
local rdir_z = pos.z - rpos_z local rdir_z = pos.z - rpos_z
local rdir_len = math.hypot(rdir_x, math.hypot(rdir_y, rdir_z)) local rdir_len = math.hypot(rdir_x, math.hypot(rdir_y, rdir_z))
rdir_x = rdir_x / rdir_len rdir_x = rdir_x / rdir_len
rdir_y = rdir_y / rdir_len rdir_y = rdir_y / rdir_len
rdir_z = rdir_z / rdir_len rdir_z = rdir_z / rdir_len
for i=0, rdir_len / STEP_LENGTH do for i=0, rdir_len / STEP_LENGTH do
rpos_x = rpos_x + rdir_x * STEP_LENGTH rpos_x = rpos_x + rdir_x * STEP_LENGTH
rpos_y = rpos_y + rdir_y * STEP_LENGTH rpos_y = rpos_y + rdir_y * STEP_LENGTH
rpos_z = rpos_z + rdir_z * STEP_LENGTH rpos_z = rpos_z + rdir_z * STEP_LENGTH
local npos_x = math.floor(rpos_x + 0.5) local npos_x = math.floor(rpos_x + 0.5)
local npos_y = math.floor(rpos_y + 0.5) local npos_y = math.floor(rpos_y + 0.5)
local npos_z = math.floor(rpos_z + 0.5) local npos_z = math.floor(rpos_z + 0.5)
local idx = (npos_z - emin_z) * zstride + (npos_y - emin_y) * ystride + local idx = (npos_z - emin_z) * zstride + (npos_y - emin_y) * ystride +
npos_x - emin_x + 1 npos_x - emin_x + 1
local cid = data[idx] local cid = data[idx]
local walkable = node_walkable[cid] local walkable = node_walkable[cid]
if walkable then if walkable then
count = count - 1 count = count - 1
break break
end end
end end
end end
-- Punch entity with damage depending on explosion exposure and -- Punch entity with damage depending on explosion exposure and
-- distance to explosion -- distance to explosion
local exposure = count / N_EXPOSURE_RAYS local exposure = count / N_EXPOSURE_RAYS
local punch_vec = vector.subtract(opos, pos) local punch_vec = vector.subtract(opos, pos)
local punch_dir = vector.normalize(punch_vec) local punch_dir = vector.normalize(punch_vec)
local impact = (1 - vector.length(punch_vec) / punch_radius) * exposure local impact = (1 - vector.length(punch_vec) / punch_radius) * exposure
if impact < 0 then if impact < 0 then
impact = 0 impact = 0
end end
local damage = math.floor((impact * impact + impact) * 7 * strength + 1) local damage = math.floor((impact * impact + impact) * 7 * strength + 1)
obj:punch(obj, 10, { damage_groups = { full_punch_interval = 1, obj:punch(obj, 10, { damage_groups = { full_punch_interval = 1,
fleshy = damage, knockback = impact * 20.0 } }, punch_dir) fleshy = damage, knockback = impact * 20.0 } }, punch_dir)
if obj:is_player() then if obj:is_player() then
obj:add_player_velocity(vector.multiply(punch_dir, impact * 20)) obj:add_player_velocity(vector.multiply(punch_dir, impact * 20))
elseif ent.tnt_knockback then elseif ent.tnt_knockback then
obj:add_velocity(vector.multiply(punch_dir, impact * 20)) obj:add_velocity(vector.multiply(punch_dir, impact * 20))
end end
end end
end end
end end
-- Remove destroyed blocks and drop items -- Remove destroyed blocks and drop items
for hash, idx in pairs(destroy) do for hash, idx in pairs(destroy) do
local do_drop = not creative_mode and math.random() <= drop_chance local do_drop = not creative_mode and math.random() <= drop_chance
local on_blast = node_on_blast[data[idx]] local on_blast = node_on_blast[data[idx]]
local remove = true local remove = true
if do_drop or on_blast ~= nil then if do_drop or on_blast ~= nil then
local npos = get_position_from_hash(hash) local npos = get_position_from_hash(hash)
if on_blast ~= nil then if on_blast ~= nil then
remove = on_blast(npos, 1.0) remove = on_blast(npos, 1.0)
else else
local name = minetest.get_name_from_content_id(data[idx]) local name = minetest.get_name_from_content_id(data[idx])
local drop = minetest.get_node_drops(name, "") local drop = minetest.get_node_drops(name, "")
for _, item in ipairs(drop) do for _, item in ipairs(drop) do
if item ~= "string" then if item ~= "string" then
item = item:get_name() .. item:get_count() item = item:get_name() .. item:get_count()
end end
minetest.add_item(npos, item) minetest.add_item(npos, item)
end end
end end
end end
if remove then if remove then
data[idx] = minetest.CONTENT_AIR data[idx] = minetest.CONTENT_AIR
end end
end end
-- Log explosion -- Log explosion
minetest.log('action', 'Explosion at ' .. minetest.pos_to_string(pos) .. minetest.log('action', 'Explosion at ' .. minetest.pos_to_string(pos) ..
' with strength ' .. strength .. ' and radius ' .. radius) ' with strength ' .. strength .. ' and radius ' .. radius)
-- Update environment -- Update environment
vm:set_data(data) vm:set_data(data)
vm:write_to_map(data) vm:write_to_map(data)
vm:update_liquids() vm:update_liquids()
end end
-- Create an explosion with strength at pos. -- Create an explosion with strength at pos.
-- --
-- Parameters: -- Parameters:
-- pos - The position where the explosion originates from -- pos - The position where the explosion originates from
-- strength - The blast strength of the explosion (a TNT explosion uses 4) -- strength - The blast strength of the explosion (a TNT explosion uses 4)
-- info - Table containing information about explosion. -- info - Table containing information about explosion.
-- --
-- Values in info: -- Values in info:
-- drop_chance - If specified becomes the drop chance of all nodes in the -- drop_chance - If specified becomes the drop chance of all nodes in the
-- explosion (defaults to 1.0 / strength) -- explosion (defaults to 1.0 / strength)
-- no_sound - If true then the explosion will not play a sound -- no_sound - If true then the explosion will not play a sound
-- no_particle - If true then the explosion will not create particles -- no_particle - If true then the explosion will not create particles
function mcl_explosions.explode(pos, strength, info) function mcl_explosions.explode(pos, strength, info)
-- The maximum blast radius (in the air) -- The maximum blast radius (in the air)
local radius = math.ceil(1.3 * strength / (0.3 * 0.75) * 0.3) local radius = math.ceil(1.3 * strength / (0.3 * 0.75) * 0.3)
if not sphere_shapes[radius] then if not sphere_shapes[radius] then
sphere_shapes[radius] = compute_sphere_rays(radius) sphere_shapes[radius] = compute_sphere_rays(radius)
end end
shape = sphere_shapes[radius] shape = sphere_shapes[radius]
trace_explode(pos, strength, shape, radius, (info and info.drop_chance) or 1 / strength) trace_explode(pos, strength, shape, radius, (info and info.drop_chance) or 1 / strength)
if not (info and info.no_sound) then if not (info and info.no_sound) then
add_particles(pos, radius) add_particles(pos, radius)
end end
if not (info and info.no_particle) then if not (info and info.no_particle) then
minetest.sound_play("tnt_explode", { minetest.sound_play("tnt_explode", {
pos = pos, gain = 1.0, pos = pos, gain = 1.0,
max_hear_distance = strength * 16 max_hear_distance = strength * 16
}, true) }, true)
end end
end end