nuke/init.lua

771 lines
20 KiB
Lua
Raw Permalink Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

-- Nuke Mod 1.5 by sfan5
-- Licensed under GPLv2
-- differences: https://github.com/HybridDog/nuke/compare/original...master
local time_load_start = os.clock()
minetest.log("verbose", "[nuke] loading...")
if not rawget(_G, "nuke") then
nuke = {}
end
--nuke.drop_items = false --this will only cause lags
nuke.RANGE = {}
dofile(minetest.get_modpath("nuke").."/settings.lua")
local MESE_TNT_RANGE = nuke.RANGE.mese
local IRON_TNT_RANGE = nuke.RANGE.iron
local MOSSY_TNT_RANGE = nuke.RANGE.mossy
nuke.bombs_list = {
{"iron", "Iron"},
{"mese", "Mese"},
{"mossy", "Mossy"}
}
local function log(msg, lv)
lv = lv or "info"
minetest.log(lv, msg)
end
minetest.after(3, function()
nuke.mossy_nodes = nuke.mossy_nodes or {}
for _,i in pairs(moss.registered_moss) do
table.insert(nuke.mossy_nodes, {i.node, i.result})
end
nuke.mossy_nds = {}
for i,node in pairs(nuke.mossy_nodes) do
nuke.mossy_nds[i] = {minetest.get_content_id(node[1]), minetest.get_content_id(node[2])}
end
end)
function nuke.r_area(manip, size, pos)
local emerged_pos1, emerged_pos2 = manip:read_from_map(
{x=pos.x-size, y=pos.y-size, z=pos.z-size},
{x=pos.x+size, y=pos.y+size, z=pos.z+size}
)
return VoxelArea:new({MinEdge=emerged_pos1, MaxEdge=emerged_pos2})
end
function nuke.set_vm_data(manip, nodes, pos, t1, msg)
manip:set_data(nodes)
manip:write_to_map()
log(string.format("[nuke] "..msg.." at " .. minetest.pos_to_string(pos) ..
" after ca. %.2fs", os.clock() - t1))
end
local function table_icontains(t, v)
for i = 1,#t do
if v == t[i] then
return true
end
end
return false
end
function spawn_tnt(pos, entname)
minetest.sound_play("nuke_ignite", {pos = pos,gain = 1.0,max_hear_distance = 8,})
return minetest.add_entity(pos, entname)
end
function do_tnt_physics(pos, r)
for k, obj in pairs(minetest.get_objects_inside_radius(pos, r)) do
local p = obj:getpos()
if obj:is_player() then
local dmg = math.floor(20.5-(vector.distance(pos, p)*20/r))
obj:set_hp(obj:get_hp() - dmg)
else
local v = vector.add(vector.add(obj:getvelocity(), vector.subtract(p, pos)), {x=r/2, y=r, z=r/2})
if not table_icontains(
{"experimental:tnt", "nuke:iron_tnt"},
obj:get_luaentity().name
) then
v = vector.divide(v, 2)
end
obj:setvelocity(v)
end
end
end
--[[function nuke.get_nuke_random(pos)
return PseudoRandom(math.abs(pos.x+pos.y*3+pos.z*5)+nuke.seed)
end]]
local c_air = minetest.get_content_id("air")
local c_chest = minetest.get_content_id("default:chest")
local function add_c_to_tab(tab, c, nd)
if not nd then
tab[c] = 1
else
tab[c] = nd+1
end
end
local function get_drops(data)
local tab = {}
for c,n in pairs(data) do
local nodename = minetest.get_name_from_content_id(c)
while n > 0 do
local drops = minetest.get_node_drops(nodename)
for _, item in pairs(drops) do
local curcnt = tab[item]
if not curcnt then
tab[item] = 1
else
tab[item] = curcnt+1
end
end
n = n-1
end
end
return tab
end
local nuke_puncher
local function add_to_inv(nodes)
local inv = nuke_puncher:get_inventory()
if not inv then
return
end
for name,cnt in pairs(nodes) do
local item = name.." "..cnt
if inv:room_for_item("main", item) then
inv:add_item("main", item)
end
end
end
local function move_items(data)
add_to_inv(get_drops(data))
end
if nuke.safe_mode then
function nuke.explode(pos, tab, range)
local t1 = os.clock()
local player = nuke_puncher
minetest.sound_play("nuke_explode", {pos = pos, gain = 1, max_hear_distance = range*200})
for _,npos in pairs(tab) do
local p = vector.add(pos, npos[1])
local node = minetest.get_node(p)
if node.name ~= c_air then
if npos[2] then
if math.random(2) == 1 then
minetest.node_dig(p, node, player)
end
else
minetest.node_dig(p, node, player)
end
end
end
log(string.format("[nuke] exploded at ("..pos.x.."|"..pos.y.."|"..pos.z..") after ca. %.2fs", os.clock() - t1))
end
else
function nuke.explode(pos, tab, range)
local t1 = os.clock()
minetest.sound_play("nuke_explode", {pos = pos, gain = 1, max_hear_distance = range*200})
local manip = minetest.get_voxel_manip()
local area = nuke.r_area(manip, range+1, pos)
local nodes = manip:get_data()
if nuke.preserve_items then
node_tab = {}
num = 1
for _,npos in pairs(tab) do
local p = vector.add(pos, npos[1])
local p_p = area:index(p.x, p.y, p.z)
local d_p_p = nodes[p_p]
if d_p_p ~= c_air
and d_p_p ~= c_chest then
add_c_to_tab(node_tab, d_p_p, node_tab[d_p_p])
nodes[p_p] = c_air
end
end
move_items(node_tab)
else
for _,npos in pairs(tab) do
local f = npos[1]
local p = {x=pos.x+f.x, y=pos.y+f.y, z=pos.z+f.z}
local p_p = area:index(p.x, p.y, p.z)
local d_p_p = nodes[p_p]
if d_p_p ~= c_air
and d_p_p ~= c_chest then
nodes[p_p] = c_air
end
end
end
nuke.set_vm_data(manip, nodes, pos, t1, "exploded")
end
end
function nuke.explode_inv(pos, tab, range, dir)
local t1 = os.clock()
minetest.sound_play("piston_extend", {pos = pos, max_hear_distance = range*200})
local manip = minetest.get_voxel_manip()
local area = nuke.r_area(manip, range+1, pos)
local nodes = manip:get_data()
local dones = {}
local strange = {}
for _,npos in pairs(tab) do
local f = npos[1]
local dif = vector.scalar(dir, f)
if dif < 0
and dif ~= math.huge then
dif = -dif*2
local p1 = vector.add(pos, f)
local p = vector.add(p1, vector.multiply(dir, dif))
local p2 = vector.round(p)
local p_p = area:indexp(p1)
local p_p2 = area:indexp(p2)
--if not dones[p_p]
if dones[p_p2] then
table.insert(strange, {p1, p})
else
--dones[p_p] = true
if not npos[2]
or math.random(2) == 1 then
dones[p_p2] = true
nodes[p_p],nodes[p_p2] = nodes[p_p2],nodes[p_p]
end
--[[local d1 = nodes[p_p]
local d2 = nodes[p_p2]
nodes[p_p] = d2 or c_air
nodes[p_p2] = d1 or c_air]]
end
end
end
for _,ps in pairs(strange) do
local p1,p = unpack(ps)
local x,y,z = p.x,p.y,p.z
local fi
for s = 0.1,range,0.1 do
local f = s
for ax = -f,f,1 do
for ay = -f,f,1 do
for az = -f,f,1 do
local t = vector.round({x=x+ax, y=y+ay, z=z+az})
local p_t = area:indexp(t)
if not dones[p_t] then
dones[p_t] = true
local p_p = area:indexp(p1)
nodes[p_p],nodes[p_t] = nodes[p_t],nodes[p_p]
fi = true
break
end
end
if fi then break end
end
if fi then break end
end
if fi then break end
end
end
nuke.set_vm_data(manip, nodes, pos, t1, "explodid")
minetest.sound_play("piston_retract", {pos = pos, max_hear_distance = range*200})
end
function nuke.explode_mossy(pos, tab, range)
local t1 = os.clock()
minetest.sound_play("nuke_explode", {pos = pos, gain = 1, max_hear_distance = range*200})
local manip = minetest.get_voxel_manip()
local area = nuke.r_area(manip, range+1, pos)
local nodes = manip:get_data()
for _,npos in pairs(tab) do
local f = npos[1]
local p = vector.add(pos, f)
local p_p = area:index(p.x, p.y, p.z)
local d_p_p = nodes[p_p]
if d_p_p ~= c_air
and d_p_p ~= c_chest then
if npos[2] then
if math.random(5) >= 4 then
nodes[p_p] = c_air
else
for _,node in pairs(nuke.mossy_nds) do
if d_p_p == node[1] then
nodes[p_p] = node[2]
break
end
end
end
else
nodes[p_p] = c_air
end
end
end
nuke.set_vm_data(manip, nodes, pos, t1, "exploded (mossy)")
end
-- Returns how long the explosion calculation took in seconds
function nuke.explode_tnt(pos, tab, range)
local t1 = minetest.get_us_time()
-- Remove the nodes, this should be relatively time-intensive
local manip = minetest.get_voxel_manip()
local area = nuke.r_area(manip, range+1, pos)
local nodes = manip:get_data()
if nuke.preserve_items then
node_tab = {}
num = 1
for _,npos in pairs(tab) do
local p = vector.add(pos, npos[1])
local p_p = area:index(p.x, p.y, p.z)
local d_p_p = nodes[p_p]
if d_p_p ~= c_air
and d_p_p ~= c_chest then
local nd = node_tab[d_p_p]
add_c_to_tab(node_tab, d_p_p, nd)
nodes[p_p] = c_air
end
end
move_items(node_tab)
else
for _,npos in pairs(tab) do
local f = npos[1]
local p = {x=pos.x+f.x, y=pos.y+f.y, z=pos.z+f.z}
local p_p = area:index(p.x, p.y, p.z)
local d_p_p = nodes[p_p]
if d_p_p ~= c_air
and d_p_p ~= c_chest then
nodes[p_p] = c_air
end
end
end
manip:set_data(nodes)
manip:write_to_map()
-- Do the audiovisual things
minetest.sound_play("nuke_explode",
{pos = pos, gain = 1, max_hear_distance = range*200})
minetest.add_particle({
pos = pos,
vel = {x=0,y=0,z=0},
acc = {x=0,y=0,z=0},
expirationtime = 0.5,
size = 16*(range*2-1),
collisiondetection = false,
texture = "smoke_puff.png"
})
for _,i in pairs({
{{x=pos.x-range, y=pos.y-range, z=pos.z-range}, {x=-3, y=0, z=-3}},
{{x=pos.x+range, y=pos.y-range, z=pos.z-range}, {x=3, y=0, z=-3}},
{{x=pos.x-range, y=pos.y-range, z=pos.z+range}, {x=-3, y=0, z=3}},
{{x=pos.x+range, y=pos.y-range, z=pos.z+range}, {x=3, y=0, z=3}},
}) do
minetest.add_particlespawner({
amount = 5*range, --amount
time = 0.1, --time
minpos = i[1], --minpos
maxpos = {x=pos.x, y=pos.y+range, z=pos.z}, --maxpos
minvel = i[2], --minvel
maxvel = {x=0, y=0, z=0}, --maxvel
minacc = {x=0,y=5,z=0}, --minacc
maxacc = {x=0,y=10,z=0}, --maxacc
minexptime = 0.1, --minexptime
maxexptime = 1, --maxexptime
minsize = 8, --minsize
maxsize = 15, --maxsize
collisiondetection = false, --collisiondetection
texture = "smoke_puff.png" --texture
})
end
local time_used = (minetest.get_us_time() - t1) / 1000000.0
log("[nuke] exploded at " .. minetest.pos_to_string(pos) ..
(" after ca. %.3g s"):format(time_used))
return time_used
end
--[[local function expl_moss(pos, range)
local t1 = os.clock()
minetest.sound_play("nuke_explode", {pos = pos, gain = 1, max_hear_distance = range*200})
local manip = minetest.get_voxel_manip()
local area = nuke.r_area(manip, range+1, pos)
local nodes = manip:get_data()
local pr = nuke.get_nuke_random(pos)
local radius = range^2 + range
for x=-range,range do
for y=-range,range do
for z=-range,range do
local r = x*x+y*y+z*z
if r <= radius then
local np={x=pos.x+x, y=pos.y+y, z=pos.z+z}
local p_np = area:index(np.x, np.y, np.z)
local d_p_np = nodes[p_np]
if d_p_np ~= c_air
and d_p_np ~= c_chest then
if math.floor(math.sqrt(r) +0.5) > range-1 then
if pr:next(1,5) >= 4 then
nodes[p_np] = c_air
--destroy_node(np)
else
for _,node in pairs(nuke.mossy_nds) do
if d_p_np == node[1] then
nodes[p_np] = node[2]
break
end
end
end
else
nodes[p_np] = c_air
--destroy_node(np)
end
end
-- activate_if_tnt(n.name, np, pos, range)
end
end
end
end
nuke.set_vm_data(manip, nodes, pos, t1, "exploded (mossy)")
end]]
--Crafting:
if nuke.allow_crafting then
local w = 'default:wood'
local c = 'default:coal_lump'
for _,i in pairs({
{"mese", "mese_crystal"},
{"iron", "steel_ingot"}
}) do
local s = "default"..i[2]
minetest.register_craft({
output = 'nuke:'..i[1]..'_tnt 4',
recipe = {
{'', w, ''},
{ s, c, s },
{'', w, ''}
}
})
end
end
function nuke.lit_tnt(pos, node, puncher)
minetest.remove_node(pos)
spawn_tnt(pos, node.name)
minetest.check_for_falling(pos)
nuke_puncher = puncher
end
for _,i in pairs(nuke.bombs_list) do
minetest.register_node("nuke:"..i[1].."_tnt", {
description = i[2].." Bomb",
tiles = {"nuke_"..i[1].."_tnt_top.png", "nuke_"..i[1].."_tnt_bottom.png", "nuke_"..i[1].."_tnt_side.png"},
diggable = false,
on_punch = function(...)
nuke.lit_tnt(...)
end
})
end
local function blinkystep(self, dtime)
self.timer = self.timer + dtime
self.blinktimer = self.blinktimer + dtime
if self.timer > 5 then
self.blinktimer = self.blinktimer + dtime
if self.timer > 8 then
self.blinktimer = self.blinktimer + dtime + dtime
end
end
if self.blinktimer <= 0.5 then
return
end
self.blinktimer = self.blinktimer - 0.5
if self.blinkstatus then
self.object:settexturemod("")
else
self.object:settexturemod("^[brighten")
end
self.blinkstatus = not self.blinkstatus
end
local function can_explode(self, r)
if self.timer < 10 then
return
end
local pos = vector.round(self.object:getpos())
self.object:remove()
do_tnt_physics(pos, r)
if minetest.get_item_group(minetest.get_node(pos).name, "water") == 0 then
return pos
end
end
function nuke.tnt_ent(textures)
return {
-- Static definition
physical = true, -- Collides with things
-- weight = 5,
collisionbox = {-0.5,-0.5,-0.5, 0.5,0.5,0.5},
visual = "cube",
textures = textures,
-- Initial value for our timer
timer = 0,
-- Number of punches required to defuse
health = 1,
blinktimer = 0,
blinkstatus = true,
on_activate = function(self)
self.object:setvelocity({x=0, y=4, z=0})
self.object:setacceleration({x=0, y=-10, z=0})
self.object:settexturemod("^[brighten")
end
}
end
local function on_punch_lit(self, hitter, name)
if not hitter then
return
end
local inv = hitter:get_inventory()
if not inv then
return
end
self.health = self.health - 1
if self.health <= 0 then
self.object:remove()
hitter:get_inventory():add_item("main", name)
end
end
-- Iron TNT
local IRON_TNT = nuke.tnt_ent({
"nuke_iron_tnt_top.png", "nuke_iron_tnt_bottom.png",
"nuke_iron_tnt_side.png", "nuke_iron_tnt_side.png",
"nuke_iron_tnt_side.png", "nuke_iron_tnt_side.png"
})
function IRON_TNT:on_step(dtime)
blinkystep(self, dtime)
local pos = can_explode(self, (IRON_TNT_RANGE+3)/2)
if pos then
nuke.explode(pos, vector.explosion_perlin(3, IRON_TNT_RANGE, {seed=37}), IRON_TNT_RANGE)
end
end
function IRON_TNT:on_punch(player)
return on_punch_lit(self, player, "nuke:iron_tnt")
end
minetest.register_entity("nuke:iron_tnt", IRON_TNT)
-- Mese TNT
local MESE_TNT = nuke.tnt_ent({
"nuke_mese_tnt_top.png", "nuke_mese_tnt_bottom.png",
"nuke_mese_tnt_side.png", "nuke_mese_tnt_side.png",
"nuke_mese_tnt_side.png", "nuke_mese_tnt_side.png"
})
function MESE_TNT:on_step(dtime)
blinkystep(self, dtime)
if self.timer < 10 then
return
end
local pos = self.object:getpos()
self.object:remove()
pos.x = math.floor(pos.x+0.5)
pos.y = math.floor(pos.y+0.5)
pos.z = math.floor(pos.z+0.5)
do_tnt_physics(pos, MESE_TNT_RANGE)
if minetest.get_node(pos).name == "default:water_source"
or minetest.get_node(pos).name == "default:water_flowing" then
-- Cancel the Explosion
return
end
nuke.explode(pos, vector.explosion_perlin(4, MESE_TNT_RANGE, {seed=42}), MESE_TNT_RANGE)
end
function MESE_TNT:on_punch(player)
return on_punch_lit(self, player, "nuke:mese_tnt")
end
minetest.register_entity("nuke:mese_tnt", MESE_TNT)
-- Mossy TNT
local MOSSY_TNT = nuke.tnt_ent({
"nuke_mossy_tnt_top.png", "nuke_mossy_tnt_bottom.png",
"nuke_mossy_tnt_side.png", "nuke_mossy_tnt_side.png",
"nuke_mossy_tnt_side.png", "nuke_mossy_tnt_side.png"
})
function MOSSY_TNT:on_step(dtime)
blinkystep(self, dtime)
local pos = can_explode(self, (1.5+MOSSY_TNT_RANGE)/2)
if pos then
nuke.explode_mossy(pos, vector.explosion_perlin(1.5, MOSSY_TNT_RANGE, {seed=52}), MOSSY_TNT_RANGE)
end
end
function MOSSY_TNT:on_punch(player)
return on_punch_lit(self, player, "nuke:mossy_tnt")
end
minetest.register_entity("nuke:mossy_tnt", MOSSY_TNT)
-- Rocket Launcher
nuke.rocket_speed = 1
nuke.rocket_a = 100
nuke.rocket_range = 100
nuke.rocket_expl_range = 6
local last_rocket_expl_delay = 0
function nuke.rocket_shoot(player, range, particle_texture, projectile_sound)
local playerpos=player:getpos()
local dir=player:get_look_dir()
local startpos = {x=playerpos.x, y=playerpos.y+1.625, z=playerpos.z}
local _, target_pos = minetest.line_of_sight(startpos,
vector.add(vector.multiply(dir, range), startpos), 1)
if not target_pos then
return
end
projectile_sound = minetest.sound_play(projectile_sound,
{pos = playerpos, max_hear_distance = range})
local delay = vector.straightdelay(math.max(vector.distance(startpos,
target_pos)-0.5, 0), nuke.rocket_speed, nuke.rocket_a)
minetest.add_particle({
pos = startpos,
vel = vector.multiply(dir, nuke.rocket_speed),
acc = vector.multiply(dir, nuke.rocket_a),
expirationtime = delay,
size = 1,
collisiondetection = false,
texture = particle_texture .. "^[transform" .. math.random(0,7)
})
local nodenam = minetest.get_node(target_pos).name
if nodenam == "ignore"
or nodenam == "default:water_source"
or nodenam == "default:water_flowing"
or nodenam == "air" then
-- Do not explode when shooting into water, unloaded area or too far
return
end
-- Subtract the previous explosion calculation time to have the explosion
-- in the right moment
minetest.after(math.max(delay - last_rocket_expl_delay, 0), function()
local nodenam = minetest.get_node(target_pos).name
if nodenam == "air" then
-- Avoid exploding at the same position multiple times
-- when using the automatic shooting
local _
_, target_pos = minetest.line_of_sight(target_pos,
vector.add(vector.multiply(dir, range), target_pos), 1)
if not target_pos then
return
end
end
minetest.sound_stop(projectile_sound)
last_rocket_expl_delay = nuke.explode_tnt(target_pos,
vector.explosion_perlin(2, nuke.rocket_expl_range, {seed=53}),
nuke.rocket_expl_range)
do_tnt_physics(target_pos, nuke.rocket_expl_range)
end)
end
local launcher_active, timer
minetest.register_tool("nuke:rocket_launcher", {
description = "Rocket Launcher",
inventory_image = "nuke_rocket_launcher.png",
range = 0,
stack_max = 1,
on_use = function(_, user)
launcher_active = true
timer = -0.8
nuke_puncher = user
nuke.rocket_shoot(user, nuke.rocket_range,
"nuke_rocket_launcher_back.png", "nuke_rocket_launcher")
end,
})
minetest.register_globalstep(function(dtime)
-- abort if noone uses it
if not launcher_active then
return
end
-- abort that it doesn't shoot too often (change it if your pc runs faster)
timer = timer+dtime
if timer < 0.1 then
return
end
timer = 0
local active
for _,player in pairs(minetest.get_connected_players()) do
if player:get_wielded_item():to_string() == "nuke:rocket_launcher"
and player:get_player_control().LMB then
nuke_puncher = player
nuke.rocket_shoot(player, nuke.rocket_range, "nuke_rocket_launcher_back.png", "nuke_rocket_launcher")
active = true
end
end
-- disable the function if noone currently uses it to reduce lag
if not active then
launcher_active = false
end
end)
local srw_range = 15
minetest.register_tool("nuke:mirrortool", {
description = "SRW",
inventory_image = "nuke_rocket_launcher.png^[brighten^[transform"..math.random(0,7),
range = 0,
stack_max = 1,
on_use = function(_, user)
nuke_puncher = user
local pos = user:getpos()
pos.y = pos.y+1.625
pos = vector.round(pos)
local dir = user:get_look_dir()
nuke.explode_inv(pos, vector.explosion_table(srw_range), srw_range, dir)
end,
})
--dofile(minetest.get_modpath("nuke").."/b.lua")
log(string.format("[nuke] loaded after ca. %.2fs", os.clock() - time_load_start))