towercrane/init.lua

678 lines
21 KiB
Lua
Raw Normal View History

2017-06-05 14:57:26 +02:00
--[[
Tower Crane Mod
===============
2017-07-16 12:27:05 +02:00
v0.07 by JoSt
2017-06-10 20:26:53 +02:00
Copyright (C) 2017 Joachim Stolberg
LGPLv2.1+
See LICENSE.txt for more information
History:
2017-06-04 v0.01 first version
2017-06-06 v0.02 Hook bugfix
2017-06-07 v0.03 fixed 2 bugs, added config.lua and sound
2017-06-08 v0.04 recipe and rope length now configurable
2017-06-10 20:26:53 +02:00
2017-06-10 v0.05 resizing bugfix, area protection added
2017-07-11 v0.06 fixed the space check bug, settingtypes added
2017-07-16 12:27:05 +02:00
2017-07-16 v0.07 crane remove bug fix
2017-06-10 20:26:53 +02:00
2017-06-05 14:57:26 +02:00
]]--
towercrane = {}
dofile(minetest.get_modpath("towercrane") .. "/config.lua")
2017-06-05 14:57:26 +02:00
--##################################################################################################
--## Tower Crane Hook
--##################################################################################################
local hook = {
2017-06-10 20:26:53 +02:00
physical = true,
collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2},
2017-06-10 20:26:53 +02:00
collide_with_objects = false,
visual = "cube",
visual_size = {x=0.6, y=0.6},
textures = {
"towercrane_hook.png",
"towercrane_hook.png",
"towercrane_hook.png",
"towercrane_hook.png",
"towercrane_hook.png",
"towercrane_hook.png",
},
groups = {cracky=1},
-- local variabels
driver = nil,
speed_forward=0,
speed_right=0,
speed_up=0,
sound=nil,
2017-06-05 14:57:26 +02:00
}
2017-06-10 20:26:53 +02:00
2017-06-05 14:57:26 +02:00
----------------------------------------------------------------------------------------------------
-- Enter/leave the Hook
----------------------------------------------------------------------------------------------------
function hook:on_rightclick(clicker)
local name = clicker:get_player_name()
if self.driver and clicker == self.driver then -- leave?
clicker:set_detach()
default.player_attached[name] = false
default.player_set_animation(clicker, "stand" , 10)
self.driver = nil
if self.sound ~= nil then
minetest.sound_stop(self.sound)
self.sound = nil
end
elseif not self.driver then -- enter?
self.driver = clicker
clicker:set_attach(self.object, "", {x=0,y=15,z=-3}, {x=0,y=0,z=0})
default.player_attached[name] = true
default.player_set_animation(clicker, "sit" , 10)
end
2017-06-05 14:57:26 +02:00
end
----------------------------------------------------------------------------------------------------
-- Hook control
----------------------------------------------------------------------------------------------------
function hook:on_step(dtime)
-- remove hook from last visit
if self.pos1 == nil or self.pos2 == nil then
self.object:remove()
return
end
if self.driver then
local ctrl = self.driver:get_player_control()
local yaw = self.driver:get_look_horizontal()
local pos = self.driver:getpos()
local max_speed = 5
local velocity = 0.5
if ctrl.up then -- forward
self.speed_forward = math.min(self.speed_forward + velocity, max_speed)
elseif ctrl.down then -- backward
self.speed_forward = math.max(self.speed_forward - velocity, -max_speed)
elseif self.speed_forward > 0 then
self.speed_forward = self.speed_forward - velocity
elseif self.speed_forward < 0 then
self.speed_forward = self.speed_forward + velocity
end
2017-06-10 20:26:53 +02:00
if ctrl.right then -- right
self.speed_right = math.min(self.speed_right + velocity, max_speed)
elseif ctrl.left then -- left
self.speed_right = math.max(self.speed_right - velocity, -max_speed)
elseif self.speed_right > 0 then
self.speed_right = self.speed_right - velocity
elseif self.speed_right < 0 then
self.speed_right = self.speed_right + velocity
end
if ctrl.jump then -- up
self.speed_up = math.min(self.speed_up + velocity, 5)
elseif ctrl.sneak then -- down
self.speed_up = math.max(self.speed_up - velocity, -5)
elseif self.speed_up > 0 then
self.speed_up = self.speed_up - velocity
elseif self.speed_up < 0 then
self.speed_up = self.speed_up + velocity
end
-- calculate the direction vector
local vx = math.cos(yaw+math.pi/2) * self.speed_forward + math.cos(yaw) * self.speed_right
local vz = math.sin(yaw+math.pi/2) * self.speed_forward + math.sin(yaw) * self.speed_right
-- check if outside of the construction area
if pos.x < self.pos1.x then vx= velocity end
if pos.x > self.pos2.x then vx= -velocity end
if pos.y < self.pos1.y then self.speed_up= velocity end
if pos.y > self.pos2.y then self.speed_up= -velocity end
if pos.z < self.pos1.z then vz= velocity end
if pos.z > self.pos2.z then vz= -velocity end
2017-06-10 20:26:53 +02:00
-- sound control
if vx ~= 0 or vz ~= 0 or self.speed_up ~= 0 then
if self.sound == nil then
2017-06-22 19:16:37 +02:00
self.sound = minetest.sound_play({name="crane"},{object=self.object, pos=pos,
gain=towercrane.gain, max_hear_distance=20,
loop=true})
end
elseif self.sound ~= nil then
minetest.sound_stop(self.sound)
self.sound = nil
end
2017-06-10 20:26:53 +02:00
self.object:setvelocity({x=vx, y=self.speed_up,z=vz})
else
self.object:setvelocity({x=0, y=0,z=0})
end
2017-06-05 14:57:26 +02:00
end
----------------------------------------------------------------------------------------------------
-- LuaEntitySAO (non-player moving things): see http://dev.minetest.net/LuaEntitySAO
----------------------------------------------------------------------------------------------------
minetest.register_entity("towercrane:hook", hook)
--##################################################################################################
--## Tower Crane
--##################################################################################################
local function turnright(dir)
local facedir = minetest.dir_to_facedir(dir)
return minetest.facedir_to_dir((facedir + 1) % 4)
2017-06-05 14:57:26 +02:00
end
local function turnleft(dir)
local facedir = minetest.dir_to_facedir(dir)
return minetest.facedir_to_dir((facedir + 3) % 4)
2017-06-05 14:57:26 +02:00
end
----------------------------------------------------------------------------------------------------
-- generic function for contruction and removement
2017-06-05 14:57:26 +02:00
----------------------------------------------------------------------------------------------------
local function crane_body_plan(pos, dir, height, width, clbk, tArg)
pos.y = pos.y + 1
clbk(pos, "towercrane:mast_ctrl_off", tArg)
for i = 1,height+1 do
pos.y = pos.y + 1
clbk(pos, "towercrane:mast", tArg)
end
2017-06-10 20:26:53 +02:00
pos.y = pos.y - 2
pos.x = pos.x - dir.x
pos.z = pos.z - dir.z
clbk(pos, "towercrane:arm2", tArg)
pos.x = pos.x - dir.x
pos.z = pos.z - dir.z
clbk(pos, "towercrane:arm", tArg)
pos.x = pos.x - dir.x
pos.z = pos.z - dir.z
clbk(pos, "towercrane:balance", tArg)
pos.x = pos.x + 3 * dir.x
pos.z = pos.z + 3 * dir.z
2017-06-10 20:26:53 +02:00
for i = 1,width do
pos.x = pos.x + dir.x
pos.z = pos.z + dir.z
if i % 2 == 0 then
clbk(pos, "towercrane:arm2", tArg)
else
clbk(pos, "towercrane:arm", tArg)
end
end
2017-06-05 14:57:26 +02:00
end
----------------------------------------------------------------------------------------------------
-- Check space for mast and arm
2017-06-05 14:57:26 +02:00
----------------------------------------------------------------------------------------------------
local function check_space(pos, dir, height, width)
local remove = function(pos, node_name, tArg)
if minetest.get_node_or_nil(pos).name ~= "air" then
tArg.res = false
end
end
local tArg = {res = true}
crane_body_plan(pos, dir, height, width, remove, tArg)
return tArg.res
end
----------------------------------------------------------------------------------------------------
-- Constuct mast and arm
----------------------------------------------------------------------------------------------------
local function construct_crane(pos, dir, height, width, owner)
local add = function(pos, node_name, tArg)
minetest.add_node(pos, {name=node_name, param2=minetest.dir_to_facedir(tArg.dir)})
end
local tArg = {dir = dir}
crane_body_plan(table.copy(pos), dir, height, width, add, tArg)
pos.y = pos.y + 1
local meta = minetest.get_meta(pos)
meta:set_string("dir", minetest.pos_to_string(dir))
meta:set_string("owner", owner)
meta:set_int("height", height)
meta:set_int("width", width)
end
2017-06-10 20:26:53 +02:00
----------------------------------------------------------------------------------------------------
-- Remove the crane
----------------------------------------------------------------------------------------------------
local function remove_crane(pos, dir, height, width)
local remove = function(pos, node_name, tArg)
2017-07-16 12:27:05 +02:00
if minetest.get_node_or_nil(pos).name == node_name or
minetest.get_node_or_nil(pos).name == "towercrane:mast_ctrl_on" then
minetest.remove_node(pos)
end
end
crane_body_plan(pos, dir, height, width, remove, {})
2017-06-05 14:57:26 +02:00
end
----------------------------------------------------------------------------------------------------
-- Place the hook in front of the base
----------------------------------------------------------------------------------------------------
local function place_hook(pos, dir)
pos.y = pos.y - 1
pos.x = pos.x + dir.x
pos.z = pos.z + dir.z
return minetest.add_entity(pos, "towercrane:hook")
2017-06-05 14:57:26 +02:00
end
2017-06-10 20:26:53 +02:00
----------------------------------------------------------------------------------------------------
-- Check if the given construction area is not already protected
----------------------------------------------------------------------------------------------------
local function check_area(pos1, pos2, owner)
if not areas then return true end
for id, a in ipairs(areas:getAreasIntersectingArea(pos1, pos2)) do
print(dump(a.owner))
if a.owner ~= owner then
return false
end
end
return true
end
----------------------------------------------------------------------------------------------------
-- Calculate and set the protection area (pos1, pos2)
----------------------------------------------------------------------------------------------------
local function protect_area(pos, dir, height, width, owner)
if not areas then return 0 end
-- pos1 = close/right/below
dir = turnright(dir)
dir = turnright(dir)
local pos1 = vector.add(pos, vector.multiply(dir, 2))
dir = turnleft(dir)
pos1 = vector.add(pos1, vector.multiply(dir, width/2))
dir = turnleft(dir)
pos1.y = pos.y - 2
-- pos2 = far/left/above
local pos2 = vector.add(pos1, vector.multiply(dir, width+2))
dir = turnleft(dir)
pos2 = vector.add(pos2, vector.multiply(dir, width))
pos2.y = pos.y + 2 + height
-- add area
local canAdd, errMsg = areas:canPlayerAddArea(pos1, pos2, owner)
if canAdd then
local id = areas:add(owner, owner .. "'s construction site", pos1, pos2, nil)
areas:save()
return id
end
return nil
end
----------------------------------------------------------------------------------------------------
-- Remove the protection area
----------------------------------------------------------------------------------------------------
local function remove_area(id, owner)
if not areas then return end
if areas:isAreaOwner(id, owner) then
areas:remove(id)
areas:save()
end
end
----------------------------------------------------------------------------------------------------
-- Check user input (height, width)
----------------------------------------------------------------------------------------------------
local function check_input(fields)
local size = string.split(fields.size, ",")
if #size == 2 then
local height = tonumber(size[1])
local width = tonumber(size[2])
if height ~= nil and width ~= nil then
--height = math.max(height, 8)
height = math.max(height, 2)
2017-06-10 20:26:53 +02:00
height = math.min(height, towercrane.max_height)
--width = math.max(width, 8)
width = math.max(width, 2)
2017-06-10 20:26:53 +02:00
width = math.min(width, towercrane.max_width)
return height, width
end
end
return 0, 0
end
2017-06-05 14:57:26 +02:00
----------------------------------------------------------------------------------------------------
-- Register Crane base
----------------------------------------------------------------------------------------------------
minetest.register_node("towercrane:base", {
description = "Tower Crane Base",
inventory_image = "towercrane_invent.png",
tiles = {
"towercrane_base_top.png",
"towercrane_base.png",
"towercrane_base.png",
"towercrane_base.png",
"towercrane_base.png",
"towercrane_base.png",
},
paramtype2 = "facedir",
is_ground_content = false,
groups = {cracky=2},
-- set meta data (form for crane height and width, dir of the arm)
after_place_node = function(pos, placer)
local meta = minetest.get_meta(pos)
local owner = placer:get_player_name()
meta:set_string("owner", owner)
local formspec = "size[5,4]"..
"label[0,0;Construction area size]" ..
"field[1,1.5;3,1;size;height,width;]" ..
"button_exit[1,2;2,1;exit;Save]"
meta:set_string("formspec", formspec)
local fdir = minetest.dir_to_facedir(placer:get_look_dir(), false)
local dir = minetest.facedir_to_dir(fdir)
meta:set_string("dir", minetest.pos_to_string(dir))
end,
-- evaluate user input (height, width), destroyed old crane and build a new one with
-- the given size
on_receive_fields = function(pos, formname, fields, player)
if fields.size == nil then
return
end
local meta = minetest.get_meta(pos)
2017-06-10 20:26:53 +02:00
local owner = meta:get_string("owner")
local dir = minetest.string_to_pos(meta:get_string("dir"))
local height = meta:get_int("height")
local width = meta:get_int("width")
local id = meta:get_int("id")
if not player or not player:is_player() then
return
end
if player:get_player_name() ~= owner then
return
end
2017-06-10 20:26:53 +02:00
-- destroy area and crane
if dir ~= nil and height ~= nil and width ~= nil then
2017-06-10 20:26:53 +02:00
remove_area(id, owner)
remove_crane(table.copy(pos), dir, height, width)
end
2017-06-10 20:26:53 +02:00
-- evaluate user input
height, width = check_input(fields)
if height ~= 0 then
meta:set_int("height", height)
meta:set_int("width", width)
meta:set_string("infotext", "Crane size: " .. height .. "," .. width)
if dir ~= nil then
if check_space(table.copy(pos), dir, height, width) then
-- add protection area
local id = protect_area(table.copy(pos), table.copy(dir), height, width, owner)
if id ~= nil then
meta:set_int("id", id)
construct_crane(table.copy(pos), table.copy(dir), height, width, owner)
else
2017-06-10 20:26:53 +02:00
minetest.chat_send_player(owner, "Construction area is already protected!")
end
2017-06-10 20:26:53 +02:00
else
minetest.chat_send_player(owner, "Too less space to raise up the tower crane!")
end
end
end
end,
-- remove mast and arm if base gets destroyed
on_destruct = function(pos)
local meta = minetest.get_meta(pos)
local dir = minetest.string_to_pos(meta:get_string("dir"))
local height = meta:get_int("height")
local width = meta:get_int("width")
2017-06-10 20:26:53 +02:00
local id = meta:get_int("id")
local owner = meta:get_string("owner")
2017-06-10 20:26:53 +02:00
-- remove protection area
if id ~= nil then
remove_area(id, owner)
end
-- remove crane
if dir ~= nil and height ~= nil and width ~= nil then
2017-06-10 20:26:53 +02:00
remove_crane(pos, dir, height, width)
end
-- remove hook
2017-06-10 20:26:53 +02:00
id = minetest.hash_node_position(pos)
if towercrane.id then
towercrane.id:remove()
towercrane.id = nil
end
end,
2017-06-05 14:57:26 +02:00
})
----------------------------------------------------------------------------------------------------
-- Register Crane balance
----------------------------------------------------------------------------------------------------
minetest.register_node("towercrane:balance", {
description = "Tower Crane Balance",
tiles = {
"towercrane_base.png",
"towercrane_base.png",
"towercrane_base.png",
"towercrane_base.png",
"towercrane_base.png",
"towercrane_base.png",
},
paramtype2 = "facedir",
is_ground_content = false,
groups = {crumbly=0, not_in_creative_inventory=1},
2017-06-05 14:57:26 +02:00
})
----------------------------------------------------------------------------------------------------
-- Register Crane mast
----------------------------------------------------------------------------------------------------
minetest.register_node("towercrane:mast", {
description = "Tower Crane Mast",
drawtype = "glasslike_framed",
tiles = {
"towercrane_mast.png",
"towercrane_mast.png",
"towercrane_mast.png",
"towercrane_mast.png",
"towercrane_mast.png",
"towercrane_mast.png",
},
paramtype2 = "facedir",
is_ground_content = false,
groups = {crumbly=0, not_in_creative_inventory=1},
2017-06-05 14:57:26 +02:00
})
----------------------------------------------------------------------------------------------------
-- Register Crane Switch (on)
----------------------------------------------------------------------------------------------------
minetest.register_node("towercrane:mast_ctrl_on", {
description = "Tower Crane Mast Ctrl On",
drawtype = "node",
tiles = {
"towercrane_mast_ctrl.png",
"towercrane_mast_ctrl.png",
"towercrane_mast_ctrl_on.png",
"towercrane_mast_ctrl_on.png",
"towercrane_mast_ctrl.png",
"towercrane_mast_ctrl.png",
},
2017-06-10 20:26:53 +02:00
-- switch the crane OFF
on_rightclick = function (pos, node, clicker)
local meta = minetest.get_meta(pos)
if not clicker or not clicker:is_player() then
return
end
if clicker:get_player_name() ~= meta:get_string("owner") then
return
end
node.name = "towercrane:mast_ctrl_off"
minetest.swap_node(pos, node)
local id = minetest.hash_node_position(pos)
if towercrane.id then
towercrane.id:remove()
towercrane.id = nil
end
end,
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Switch crane on/off")
end,
after_place_node = function(pos, placer, itemstack, pointed_thing)
local meta = minetest.get_meta(pos)
local owner = placer:get_player_name()
meta:set_string("owner", owner)
end,
paramtype2 = "facedir",
is_ground_content = false,
groups = {crumbly=0, not_in_creative_inventory=1},
2017-06-05 14:57:26 +02:00
})
----------------------------------------------------------------------------------------------------
-- Register Crane Switch (off)
----------------------------------------------------------------------------------------------------
minetest.register_node("towercrane:mast_ctrl_off", {
description = "Tower Crane Mast Ctrl Off",
drawtype = "node",
tiles = {
"towercrane_mast_ctrl.png",
"towercrane_mast_ctrl.png",
"towercrane_mast_ctrl_off.png",
"towercrane_mast_ctrl_off.png",
"towercrane_mast_ctrl.png",
"towercrane_mast_ctrl.png",
},
2017-06-10 20:26:53 +02:00
-- switch the crane ON
on_rightclick = function (pos, node, clicker)
2017-06-10 20:26:53 +02:00
-- calculate the construction area, and place the hook
local meta = minetest.get_meta(pos)
-- only the owner is allowed to switch
if not clicker or not clicker:is_player() then
return
end
if clicker:get_player_name() ~= meta:get_string("owner") then
return
end
-- swap to the other node
node.name = "towercrane:mast_ctrl_on"
minetest.swap_node(pos, node)
local dir = minetest.string_to_pos(meta:get_string("dir"))
if pos ~= nil and dir ~= nil then
-- store hook instance in 'towercrane'
local id = minetest.hash_node_position(pos)
towercrane.id = place_hook(table.copy(pos), dir)
2017-06-10 20:26:53 +02:00
--
-- calculate the construction area dimension (pos1, pos2)
--
local height = meta:get_int("height")
local width = meta:get_int("width")
-- pos1 = close/right/below
dir = turnright(dir)
local pos1 = vector.add(pos, vector.multiply(dir, width/2))
dir = turnleft(dir)
local pos1 = vector.add(pos1, vector.multiply(dir, 1))
pos1.y = pos.y - 2 + height - towercrane.rope_length
-- pos2 = far/left/above
local pos2 = vector.add(pos1, vector.multiply(dir, width-1))
dir = turnleft(dir)
pos2 = vector.add(pos2, vector.multiply(dir, width))
pos2.y = pos.y - 3 + height
-- normalize x/z so that pos2 > pos1
if pos2.x < pos1.x then
pos2.x, pos1.x = pos1.x, pos2.x
end
if pos2.z < pos1.z then
pos2.z, pos1.z = pos1.z, pos2.z
end
-- store pos1/pos2 in the hook (LuaEntitySAO)
towercrane.id:get_luaentity().pos1 = pos1
towercrane.id:get_luaentity().pos2 = pos2
end
end,
on_construct = function(pos)
-- add infotext
local meta = minetest.get_meta(pos)
meta:set_string("infotext", "Switch crane on/off")
end,
after_place_node = function(pos, placer, itemstack, pointed_thing)
-- store owner for dig protection
local meta = minetest.get_meta(pos)
local owner = placer:get_player_name()
meta:set_string("owner", owner)
end,
paramtype2 = "facedir",
is_ground_content = false,
groups = {crumbly=0, not_in_creative_inventory=1},
2017-06-05 14:57:26 +02:00
})
----------------------------------------------------------------------------------------------------
-- Register Crane arm 1
----------------------------------------------------------------------------------------------------
minetest.register_node("towercrane:arm", {
description = "Tower Crane Arm",
drawtype = "glasslike_framed",
tiles = {
"towercrane_arm.png",
"towercrane_arm.png",
"towercrane_arm.png",
"towercrane_arm.png",
"towercrane_arm.png",
"towercrane_arm.png",
},
paramtype2 = "facedir",
is_ground_content = false,
groups = {crumbly=0, not_in_creative_inventory=1},
2017-06-05 14:57:26 +02:00
})
----------------------------------------------------------------------------------------------------
-- Register Crane arm 2
----------------------------------------------------------------------------------------------------
minetest.register_node("towercrane:arm2", {
description = "Tower Crane Arm2",
drawtype = "glasslike_framed",
tiles = {
"towercrane_arm2.png",
"towercrane_arm2.png",
"towercrane_arm2.png",
"towercrane_arm2.png",
"towercrane_arm2.png",
"towercrane_arm2.png",
},
paramtype2 = "facedir",
is_ground_content = false,
groups = {crumbly=0, not_in_creative_inventory=1},
2017-06-05 14:57:26 +02:00
})
2017-06-06 21:42:02 +02:00
----------------------------------------------------------------------------------------------------
-- Register Recipe
----------------------------------------------------------------------------------------------------
if towercrane.recipe then
minetest.register_craft({
output = "towercrane:base",
recipe = {
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
{"default:steel_ingot", "", ""},
{"default:steel_ingot", "dye:yellow", ""}
}
})
end
2017-06-06 21:42:02 +02:00