495 lines
16 KiB
Lua
495 lines
16 KiB
Lua
local WALKING_PERIOD = worm.config.WALKING_PERIOD
|
|
|
|
-- Defines a "worm" (spawnitem and nodes)
|
|
-- @param modname The name of the mod, which registers the nodes
|
|
-- @param name The name of the creature; Has to be unique
|
|
-- @param data A table containing:
|
|
-- - leftover The node placed behind the tail when moving away
|
|
-- - walking_steps The number of steps to pass one node
|
|
-- - can_move A function (Position * Node -> Boolean) defining which
|
|
-- nodes can be destroyed by the worm
|
|
-- - flying Movement behaviour
|
|
-- - drop drop of body parts; default: the spawn item
|
|
-- - node_damage Damage done inside the head
|
|
-- - attack_damage Damage groups for attacks; nil means no attack
|
|
-- - follow_objects Can be: false, "straight", "winding", true = "mixed"
|
|
-- - *_texture See examples, some values are indispensable
|
|
-- *_tile
|
|
-- @return True if successful
|
|
worm.register_worm = function (modname, name, data)
|
|
-- Default values:
|
|
if not data.leftover then
|
|
data.leftover = "air"
|
|
end
|
|
if not data.walking_steps then
|
|
data.walking_steps = worm.config.DEFAULT_WALKING_STEPS
|
|
end
|
|
if not data.can_move then
|
|
data.can_move = function(_, node)
|
|
return node.name == "air"
|
|
end
|
|
end
|
|
-- Reusing textures
|
|
if not data.bottem_texture then
|
|
data.bottem_texture = data.side_texture
|
|
end
|
|
if not data.top_texture then
|
|
data.top_texture = data.side_texture
|
|
end
|
|
if not data.left_texture then
|
|
data.left_texture = data.side_texture
|
|
end
|
|
if not data.inventory_image then
|
|
data.inventory_image = data.face_texture
|
|
end
|
|
if not data.attack_texture then
|
|
data.attack_texture = data.face_texture
|
|
end
|
|
-- Complete tiles
|
|
if not data.top_tile then
|
|
data.top_tile = data.top_texture.."^[transform1"
|
|
end
|
|
if not data.bottem_tile then
|
|
data.bottem_tile = data.bottem_texture.."^[transform3"
|
|
end
|
|
if not data.side_tile then
|
|
data.side_tile = data.side_texture
|
|
end
|
|
if not data.left_tile then
|
|
data.left_tile = data.left_texture.."^[transform2"
|
|
end
|
|
if not data.face_tile then
|
|
data.face_tile = data.face_texture
|
|
end
|
|
if not data.tail_tile then
|
|
data.tail_tile = data.tail_texture
|
|
end
|
|
-- Spawn item related
|
|
if not data.drop then
|
|
data.drop = modname..":"..name.."_spawnegg"
|
|
end
|
|
if not data.spawn_in then
|
|
data.spawn_in = "air"
|
|
end
|
|
-- Definition of the spawn item
|
|
minetest.register_craftitem(modname..":"..name.."_spawnegg", {
|
|
description = "spawn "..name,
|
|
inventory_image = data.inventory_image,
|
|
on_use = function(itemstack, user, pointed_thing)
|
|
if pointed_thing.type ~= "node" then
|
|
return itemstack
|
|
end
|
|
local pos = pointed_thing.above
|
|
local player_name = user:get_player_name()
|
|
if minetest.is_protected(pos, player_name) then
|
|
return itemstack
|
|
end
|
|
if minetest.get_node(pos).name ~= data.spawn_in then
|
|
return itemstack
|
|
end
|
|
local zero = {x = 0, y = 0, z = 0}
|
|
local player_dir = vector.subtract(zero, user:get_look_dir())
|
|
local facedir = minetest.dir_to_facedir(player_dir)
|
|
local dir = minetest.facedir_to_dir(facedir)
|
|
local length = itemstack:get_count()
|
|
minetest.set_node(pos, {
|
|
name = modname..":"..name.."_head_1",
|
|
param2 = facedir
|
|
})
|
|
pos = vector.subtract(pos, dir)
|
|
local i = 0
|
|
while i < length do
|
|
if minetest.get_node(pos).name ~= data.spawn_in or
|
|
(worm.config.TAIL_PROTECTION and minetest.is_protected(pos, player_name)) then
|
|
pos = vector.add(pos, dir)
|
|
minetest.set_node(pos, {
|
|
name = modname..":"..name.."_tail_1",
|
|
param2 = facedir
|
|
})
|
|
break
|
|
end
|
|
minetest.set_node(pos, {
|
|
name = modname..":"..name.."_body",
|
|
param2 = facedir
|
|
})
|
|
pos = vector.subtract(pos, dir)
|
|
i = i + 1
|
|
end
|
|
minetest.set_node(pos, {
|
|
name = modname..":"..name.."_tail_1",
|
|
param2 = facedir
|
|
})
|
|
local tailtimer = minetest.get_node_timer(pos)
|
|
tailtimer:set(WALKING_PERIOD, 0)
|
|
itemstack:take_item(i)
|
|
return itemstack
|
|
end,
|
|
})
|
|
|
|
-- Definition of head nodes
|
|
for n = 1, data.walking_steps do
|
|
-- Called on nodetimer
|
|
-- Incomplete head -> Update the head
|
|
local step = function(pos, node)
|
|
node.name = modname..":"..name.."_head_"..(n+1)
|
|
minetest.swap_node(pos, node)
|
|
end
|
|
-- Check for attack at first step
|
|
if data.attack_damage and n == 1 then
|
|
step = function(pos, node)
|
|
local dir = minetest.facedir_to_dir(node.param2)
|
|
local bite_pos = vector.add(pos, dir)
|
|
if data.can_move(bite_pos, minetest.get_node(bite_pos)) then
|
|
local objects = minetest.get_objects_inside_radius(bite_pos, 1)
|
|
if objects[1] then
|
|
node.name = modname..":"..name.."_body"
|
|
minetest.swap_node(pos, node)
|
|
node.name = modname..":"..name.."_head_attack"
|
|
minetest.set_node(bite_pos, node)
|
|
objects[1]:punch(objects[1], nil, -- This is a workaround, first argument should be nil
|
|
{damage_groups = data.attack_damage,}, dir)
|
|
return
|
|
end
|
|
end
|
|
node.name = modname..":"..name.."_head_"..(n+1)
|
|
minetest.swap_node(pos, node)
|
|
end
|
|
end
|
|
-- Complete head -> Move head to a different position
|
|
if n == data.walking_steps then
|
|
step = function(pos, node)
|
|
local facedir = node.param2
|
|
local dirs -- Hierarchy of preferred directions
|
|
if data.follow_nodes then -- follow food nodes
|
|
local radius = data.follow_nodes_radius
|
|
if not radius then radius = 5 end
|
|
local obj_pos = minetest.find_node_near(pos, radius, data.follow_nodes)
|
|
if obj_pos then
|
|
obj_pos = vector.subtract(obj_pos, pos)
|
|
dirs = {}
|
|
if not data.flying then
|
|
dirs[1] = 4
|
|
end
|
|
if obj_pos.x > 0 then
|
|
dirs[2] = 1
|
|
dirs[5] = 3
|
|
elseif obj_pos.x < 0 then
|
|
dirs[2] = 3
|
|
dirs[5] = 1
|
|
else
|
|
dirs[5] = 1
|
|
dirs[8] = 3
|
|
end
|
|
if obj_pos.z > 0 then
|
|
dirs[3] = 0
|
|
dirs[6] = 2
|
|
elseif obj_pos.z < 0 then
|
|
dirs[3] = 2
|
|
dirs[6] = 0
|
|
else
|
|
dirs[6] = 0
|
|
dirs[9] = 2
|
|
end
|
|
if math.abs(obj_pos.z) > math.abs(obj_pos.x) then
|
|
local tmp = dirs[2]
|
|
dirs[2] = dirs[3]
|
|
dirs[3] = tmp
|
|
end
|
|
if obj_pos.y > 0 then
|
|
dirs[4] = 8
|
|
dirs[7] = 4
|
|
else
|
|
dirs[4] = 4
|
|
dirs[7] = 8
|
|
end
|
|
end
|
|
end
|
|
if data.follow_objects then -- follow objects
|
|
local radius = data.follow_objects_radius
|
|
if not radius then radius = 5 end
|
|
local objects = minetest.get_objects_inside_radius(pos, radius)
|
|
if objects[1] then
|
|
local obj_pos = objects[1]:getpos()
|
|
obj_pos = vector.subtract(obj_pos, pos)
|
|
dirs = {}
|
|
if not data.flying then
|
|
dirs[1] = 4
|
|
end
|
|
if obj_pos.x > 0 then
|
|
dirs[2] = 1
|
|
dirs[5] = 3
|
|
else
|
|
dirs[2] = 3
|
|
dirs[5] = 1
|
|
end
|
|
if obj_pos.z > 0 then
|
|
dirs[3] = 0
|
|
dirs[6] = 2
|
|
else
|
|
dirs[3] = 2
|
|
dirs[6] = 0
|
|
end
|
|
if data.follow_objects == "straight" then
|
|
if math.abs(obj_pos.z) > math.abs(obj_pos.x) then
|
|
local tmp = dirs[2]
|
|
dirs[2] = dirs[3]
|
|
dirs[3] = tmp
|
|
end
|
|
elseif data.follow_objects == "winding" then
|
|
if math.abs(obj_pos.z) < math.abs(obj_pos.x) then
|
|
local tmp = dirs[2]
|
|
dirs[2] = dirs[3]
|
|
dirs[3] = tmp
|
|
end
|
|
end
|
|
if obj_pos.y > 0 then
|
|
dirs[4] = 8
|
|
dirs[7] = 4
|
|
else
|
|
dirs[4] = 4
|
|
dirs[7] = 8
|
|
end
|
|
if data.flying and (data.follow_objects == "straight") and (math.abs(obj_pos.z) < 1) and (math.abs(obj_pos.x) < 1) and (math.abs(obj_pos.y) > 1) then
|
|
dirs[1] = dirs[4]
|
|
dirs[4] = nil
|
|
end
|
|
end
|
|
end
|
|
-- default movement
|
|
if not dirs then
|
|
if data.flying then
|
|
dirs = {
|
|
facedir % 4, -- 4 -> 0
|
|
(facedir + 1) % 4,
|
|
(facedir + 2) % 4,
|
|
(facedir + 3) % 4,
|
|
8,
|
|
4
|
|
}
|
|
else
|
|
dirs = {
|
|
4,
|
|
facedir % 4, -- 4 -> 0
|
|
(facedir + 1) % 4,
|
|
(facedir + 2) % 4,
|
|
(facedir + 3) % 4,
|
|
8
|
|
}
|
|
end
|
|
end
|
|
for i = 1, 9 do
|
|
if dirs[i] then
|
|
local newpos = vector.add(pos, minetest.facedir_to_dir(dirs[i]))
|
|
local newnode = minetest.get_node(newpos)
|
|
local grow = minetest.get_node_group(newnode.name, "swallowable")
|
|
if grow > 0 then
|
|
grow = grow + 1
|
|
if grow > data.walking_steps then
|
|
grow = data.walking_steps
|
|
end
|
|
minetest.set_node(newpos, {
|
|
name = modname..":"..name.."_head_"..grow,
|
|
param2 = dirs[i]
|
|
})
|
|
minetest.set_node(pos, {
|
|
name = modname..":"..name.."_body",
|
|
param2 = dirs[i]
|
|
})
|
|
return
|
|
elseif data.can_move(newpos, newnode) then
|
|
minetest.set_node(newpos, {
|
|
name = modname..":"..name.."_head_1",
|
|
param2 = dirs[i]
|
|
})
|
|
minetest.set_node(pos, {
|
|
name = modname..":"..name.."_body",
|
|
param2 = dirs[i]
|
|
})
|
|
return
|
|
end
|
|
end
|
|
end
|
|
-- Worm stuck
|
|
end
|
|
end
|
|
local drawtype
|
|
if data.walking_steps > 1 then
|
|
drawtype = "nodebox"
|
|
end
|
|
minetest.register_node(modname..":"..name.."_head_"..n, {
|
|
description = name.." head",
|
|
paramtype = "light",
|
|
paramtype2 = "facedir",
|
|
drawtype = drawtype,
|
|
node_box = {
|
|
type = "fixed",
|
|
fixed = {
|
|
{-0.5, -0.5, -0.5, 0.5, 0.5, -0.5 + (1 / data.walking_steps) * n},
|
|
},
|
|
},
|
|
tiles = {
|
|
data.top_tile,
|
|
data.bottem_tile,
|
|
data.side_tile,
|
|
data.left_tile,
|
|
data.face_tile,
|
|
data.side_tile
|
|
},
|
|
groups = {snappy = 1, level = 2, not_in_creative_inventory = 1},
|
|
damage_per_second = data.node_damage,
|
|
wormstep = step,
|
|
drop = "",
|
|
})
|
|
end
|
|
|
|
-- Definition of the body node
|
|
minetest.register_node(modname..":"..name.."_body", {
|
|
description = name.." body part",
|
|
paramtype = "light",
|
|
paramtype2 = "facedir",
|
|
tiles = {
|
|
data.top_tile,
|
|
data.bottem_tile,
|
|
data.side_tile,
|
|
data.left_tile,
|
|
data.side_tile,
|
|
data.side_tile
|
|
},
|
|
groups = {snappy = 1, level = 2, not_in_creative_inventory = 1},
|
|
-- Add a new tail to keep demolition
|
|
after_dig_node = function(pos, node, _, _)
|
|
local newpos = vector.add(pos, minetest.facedir_to_dir(node.param2))
|
|
local newnode = minetest.get_node(newpos)
|
|
if newnode.name == modname..":"..name.."_body" then
|
|
newnode.name = modname..":"..name.."_tail_1"
|
|
minetest.set_node(newpos, newnode)
|
|
local timer = minetest.get_node_timer(newpos)
|
|
timer:set(WALKING_PERIOD, 0)
|
|
elseif newnode.name:find(modname..":"..name.."_head") == 1 then
|
|
newnode.name = "air"
|
|
minetest.set_node(newpos, newnode)
|
|
end
|
|
end,
|
|
drop = data.drop,
|
|
})
|
|
|
|
local call_head = function (pos, node)
|
|
local dir
|
|
repeat
|
|
dir = minetest.facedir_to_dir(node.param2)
|
|
pos = vector.add(pos, dir)
|
|
node = minetest.get_node(pos)
|
|
until node.name ~= modname..":"..name.."_body"
|
|
if node.name:find(modname..":"..name.."_head") then
|
|
minetest.registered_nodes[node.name].wormstep(pos, node)
|
|
end
|
|
end
|
|
|
|
-- Definition of tail nodes
|
|
for n = 1, data.walking_steps do
|
|
-- Called on nodetimer
|
|
-- Non empty tail -> update the tail
|
|
local step = function(pos, _)
|
|
local node = minetest.get_node(pos)
|
|
node.name = modname..":"..name.."_tail_"..(n+1)
|
|
minetest.set_node(pos, node)
|
|
local timer = minetest.get_node_timer(pos)
|
|
timer:set(WALKING_PERIOD, 0)
|
|
call_head(pos, node)
|
|
return false
|
|
end
|
|
-- Empty tail -> Update the head
|
|
if n == data.walking_steps then
|
|
step = function(pos, _)
|
|
local node = minetest.get_node(pos)
|
|
local facedir = node.param2
|
|
local newpos = vector.add(pos, minetest.facedir_to_dir(facedir))
|
|
local newnode = minetest.get_node(newpos)
|
|
if newnode.name == modname..":"..name.."_body" then
|
|
newnode.name = modname..":"..name.."_tail_1"
|
|
minetest.set_node(newpos, newnode)
|
|
local timer = minetest.get_node_timer(newpos)
|
|
timer:set(WALKING_PERIOD, 0)
|
|
elseif newnode.name:find(modname..":"..name.."_head") == 1 then
|
|
minetest.set_node(newpos, {name = "air"})
|
|
end
|
|
minetest.set_node(pos, {name = data.leftover})
|
|
call_head(newpos, newnode)
|
|
return false
|
|
end
|
|
end
|
|
local drawtype
|
|
if data.walking_steps > 1 then
|
|
drawtype = "nodebox"
|
|
end
|
|
minetest.register_node(modname..":"..name.."_tail_"..n, {
|
|
description = name.." tail",
|
|
paramtype = "light",
|
|
paramtype2 = "facedir",
|
|
drawtype = drawtype,
|
|
node_box = {
|
|
type = "fixed",
|
|
fixed = {
|
|
{ -0.5, -0.5, -0.5 + (1 / data.walking_steps) * (n - 1), 0.5, 0.5, 0.5},
|
|
},
|
|
},
|
|
tiles = {
|
|
data.top_tile,
|
|
data.bottem_tile,
|
|
data.side_tile,
|
|
data.left_tile,
|
|
data.side_tile,
|
|
data.tail_tile
|
|
},
|
|
on_timer = step,
|
|
after_dig_node = function(pos, node, _, _)
|
|
local newpos = vector.add(pos, minetest.facedir_to_dir(node.param2))
|
|
local newnode = minetest.get_node(newpos)
|
|
if newnode.name == modname..":"..name.."_body" then
|
|
newnode.name = modname..":"..name.."_tail_1"
|
|
minetest.set_node(newpos, newnode)
|
|
local timer = minetest.get_node_timer(newpos)
|
|
timer:set(WALKING_PERIOD, 0)
|
|
elseif newnode.name:find(modname..":"..name.."_head") == 1 then
|
|
newnode.name = "air"
|
|
minetest.set_node(newpos, newnode)
|
|
end
|
|
end,
|
|
groups = {snappy = 1, level = 2, not_in_creative_inventory = 1},
|
|
drop = "",
|
|
})
|
|
end
|
|
|
|
--Definition of the special attack head node
|
|
if data.attack_damage then
|
|
minetest.register_node(modname..":"..name.."_head_attack", {
|
|
description = name.." attacking head",
|
|
paramtype = "light",
|
|
paramtype2 = "facedir",
|
|
tiles = {
|
|
data.top_tile,
|
|
data.bottem_tile,
|
|
data.side_tile,
|
|
data.left_tile,
|
|
data.attack_texture,
|
|
data.side_tile
|
|
},
|
|
groups = {snappy = 1, level = 2, not_in_creative_inventory = 1},
|
|
wormstep = function(pos, node)
|
|
local pos_b = vector.subtract(pos, minetest.facedir_to_dir(node.param2))
|
|
local node_b = minetest.get_node(pos_b)
|
|
node.name = "air"
|
|
minetest.swap_node(pos, node)
|
|
if node_b.name == modname..":"..name.."_body" then
|
|
node_b.name = modname..":"..name.."_head_"..(math.min(3, data.walking_steps))
|
|
minetest.swap_node(pos_b, node_b)
|
|
local timer = minetest.get_node_timer(pos_b)
|
|
timer:set(WALKING_PERIOD, 0)
|
|
end
|
|
end,
|
|
drop = "",
|
|
})
|
|
end
|
|
return true
|
|
end
|