372 lines
11 KiB
Lua
372 lines
11 KiB
Lua
--[[
|
|
|
|
Tower Crane Mod
|
|
===============
|
|
|
|
Copyright (C) 2017-2020 Joachim Stolberg
|
|
LGPLv2.1+
|
|
See LICENSE.txt for more information
|
|
|
|
]]--
|
|
|
|
local DAYS_WITHOUT_USE = 72 * 5
|
|
local mod_player_monoids = minetest.get_modpath("player_monoids")
|
|
|
|
-- for lazy programmers
|
|
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
|
|
local S2P = minetest.string_to_pos
|
|
|
|
local S = towercrane.S
|
|
|
|
-- To prevent race condition crashes
|
|
local Currently_left_the_game = {}
|
|
|
|
-- pos is the switch position
|
|
local function is_my_crane(pos, clicker)
|
|
local base_pos = {x=pos.x, y=pos.y-1, z=pos.z}
|
|
return towercrane.is_my_crane(base_pos, clicker)
|
|
end
|
|
|
|
-- pos is the switch position
|
|
local function get_crane_data(pos)
|
|
local base_pos = {x=pos.x, y=pos.y-1, z=pos.z}
|
|
return towercrane.get_crane_data(base_pos)
|
|
end
|
|
|
|
local function get_my_crane_pos(player)
|
|
-- check operator state
|
|
local pl_meta = player:get_meta()
|
|
if not pl_meta or pl_meta:get_int("towercrane_isoperator") ~= 1 then
|
|
return
|
|
end
|
|
-- check owner
|
|
local pos = S2P(pl_meta:get_string("towercrane_pos"))
|
|
local player_name = (player and player:get_player_name()) or ""
|
|
local data = get_crane_data(pos)
|
|
if not data or player_name ~= data.owner then
|
|
return
|
|
end
|
|
-- check protection
|
|
if minetest.is_protected(pos, player_name) then
|
|
return
|
|
end
|
|
|
|
return pos -- switch pos
|
|
end
|
|
|
|
-- pos is the switch position
|
|
local function is_crane_running(pos)
|
|
local meta = minetest.get_meta(pos)
|
|
return meta:get_int("running") == 1
|
|
end
|
|
|
|
local function is_operator(player)
|
|
local pl_meta = player:get_meta()
|
|
if not pl_meta or pl_meta:get_int("towercrane_isoperator") ~= 1 then
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
local function set_operator_privs(player, pos)
|
|
local privs = minetest.get_player_privs(player:get_player_name())
|
|
local meta = player:get_meta()
|
|
-- Check access conflicts with other mods
|
|
if meta:get_int("player_physics_locked") == 0 then
|
|
if pos and meta and privs then
|
|
meta:set_string("towercrane_pos", P2S(pos))
|
|
-- store the player privs default values
|
|
meta:set_string("towercrane_fast", privs["fast"] and "true" or "false")
|
|
meta:set_string("towercrane_fly", privs["fly"] and "true" or "false")
|
|
-- set operator privs
|
|
meta:set_int("towercrane_isoperator", 1)
|
|
meta:set_int("player_physics_locked", 1)
|
|
privs["fly"] = true
|
|
privs["fast"] = nil
|
|
minetest.set_player_privs(player:get_player_name(), privs)
|
|
|
|
if mod_player_monoids then
|
|
player_monoids.speed:add_change(player, 0.7, "towercrane:crane")
|
|
else
|
|
local physics = player:get_physics_override()
|
|
meta:set_int("towercrane_speed", physics.speed)
|
|
physics.speed = 0.7
|
|
-- write back
|
|
player:set_physics_override(physics)
|
|
end
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function reset_operator_privs(player)
|
|
local privs = minetest.get_player_privs(player:get_player_name())
|
|
local meta = player:get_meta()
|
|
if meta and privs and meta:get_int("towercrane_isoperator") ~= 0 then
|
|
meta:set_string("towercrane_pos", "")
|
|
-- restore the player privs default values
|
|
meta:set_int("towercrane_isoperator", 0)
|
|
meta:set_int("player_physics_locked", 0)
|
|
privs["fast"] = meta:get_string("towercrane_fast") == "true" or nil
|
|
privs["fly"] = meta:get_string("towercrane_fly") == "true" or nil
|
|
minetest.set_player_privs(player:get_player_name(), privs)
|
|
-- delete stored default values
|
|
meta:set_string("towercrane_fast", "")
|
|
meta:set_string("towercrane_fly", "")
|
|
|
|
if mod_player_monoids then
|
|
player_monoids.speed:del_change(player, "towercrane:crane")
|
|
else
|
|
local physics = player:get_physics_override()
|
|
physics.speed = meta:get_int("towercrane_speed")
|
|
meta:set_string("towercrane_speed", "")
|
|
if physics.speed == 0 then physics.speed = 1 end
|
|
-- write back
|
|
player:set_physics_override(physics)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function place_player(pos, player)
|
|
if pos and player then
|
|
local data = get_crane_data(pos)
|
|
if data then
|
|
local new_pos = vector.add(pos, data.dir)
|
|
new_pos.y = new_pos.y - 1
|
|
player:set_pos(new_pos)
|
|
local meta = minetest.get_meta(pos)
|
|
meta:set_string("last_known_pos", P2S(new_pos))
|
|
end
|
|
end
|
|
end
|
|
|
|
-- state must be "on" or "off"
|
|
local function swap_node(pos, state)
|
|
-- check node
|
|
local node = minetest.get_node(pos)
|
|
if node.name ~= "towercrane:mast_ctrl_"..(state == "on" and "off" or "on") then
|
|
return
|
|
end
|
|
-- switch node
|
|
node.name = "towercrane:mast_ctrl_"..state
|
|
minetest.swap_node(pos, node)
|
|
end
|
|
|
|
-- pos is the switch position
|
|
local function store_last_used(pos)
|
|
local meta = minetest.get_meta(pos)
|
|
meta:set_int("last_used", minetest.get_day_count() + DAYS_WITHOUT_USE)
|
|
end
|
|
|
|
local function stop_crane(pos, player)
|
|
swap_node(pos, "off")
|
|
local meta = minetest.get_meta(pos)
|
|
meta:set_int("running", 0)
|
|
store_last_used(pos)
|
|
place_player(pos, player)
|
|
end
|
|
|
|
local function start_crane(pos, player)
|
|
swap_node(pos, "on")
|
|
local meta = minetest.get_meta(pos)
|
|
meta:set_int("running", 1)
|
|
store_last_used(pos)
|
|
place_player(pos, player)
|
|
end
|
|
|
|
local function calc_construction_area(pos)
|
|
local data = get_crane_data(pos)
|
|
if data then
|
|
-- pos1 = close/right/below
|
|
local dir = towercrane.turnright(data.dir)
|
|
local pos1 = vector.add(pos, vector.multiply(dir, data.width/2))
|
|
dir = towercrane.turnleft(dir)
|
|
pos1 = vector.add(pos1, vector.multiply(dir, 1))
|
|
pos1.y = pos.y - 2 + data.height - towercrane.rope_length
|
|
-- pos2 = far/left/above
|
|
local pos2 = vector.add(pos1, vector.multiply(dir, data.width-1))
|
|
dir = towercrane.turnleft(dir)
|
|
pos2 = vector.add(pos2, vector.multiply(dir, data.width))
|
|
pos2.y = pos.y - 3 + data.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
|
|
return pos1, pos2
|
|
end
|
|
end
|
|
|
|
local function control_player(pos, pos1, pos2, player_name)
|
|
if Currently_left_the_game[player_name] then
|
|
Currently_left_the_game[player_name] = nil
|
|
return
|
|
end
|
|
local player = player_name and minetest.get_player_by_name(player_name)
|
|
if player then
|
|
if is_crane_running(pos) then
|
|
-- check if outside of the construction area
|
|
local correction = false
|
|
local pl_pos = player:get_pos()
|
|
if pl_pos then
|
|
if pl_pos.x < pos1.x then pl_pos.x = pos1.x; correction = true end
|
|
if pl_pos.x > pos2.x then pl_pos.x = pos2.x; correction = true end
|
|
if pl_pos.y < pos1.y then pl_pos.y = pos1.y; correction = true end
|
|
if pl_pos.y > pos2.y then pl_pos.y = pos2.y; correction = true end
|
|
if pl_pos.z < pos1.z then pl_pos.z = pos1.z; correction = true end
|
|
if pl_pos.z > pos2.z then pl_pos.z = pos2.z; correction = true end
|
|
-- check if a protected area is violated
|
|
if correction == false and minetest.is_protected(pl_pos, player_name) then
|
|
minetest.chat_send_player(player_name, "[Tower Crane] "..S("Area is protected."))
|
|
correction = true
|
|
end
|
|
local meta = minetest.get_meta(pos)
|
|
if correction == true then
|
|
local last_pos = S2P(meta:get_string("last_known_pos"))
|
|
if last_pos then
|
|
player:set_pos(last_pos)
|
|
end
|
|
else -- store last known correct position
|
|
meta:set_string("last_known_pos", P2S(pl_pos))
|
|
end
|
|
minetest.after(1, control_player, pos, pos1, pos2, player_name)
|
|
end
|
|
else
|
|
store_last_used(pos)
|
|
place_player(pos, player)
|
|
reset_operator_privs(player)
|
|
end
|
|
else
|
|
local meta = minetest.get_meta(pos)
|
|
meta:set_int("running", 0)
|
|
end
|
|
end
|
|
|
|
minetest.register_node("towercrane:mast_ctrl_on", {
|
|
description = S("Tower Crane Mast Ctrl On"),
|
|
drawtype = "node",
|
|
tiles = {
|
|
"towercrane_base.png",
|
|
"towercrane_base.png",
|
|
"towercrane_base.png",
|
|
"towercrane_base.png",
|
|
"towercrane_base.png^towercrane_button_on.png",
|
|
"towercrane_base.png^towercrane_button_on.png",
|
|
},
|
|
-- switch the crane OFF
|
|
on_rightclick = function (pos, node, clicker)
|
|
local pos2 = get_my_crane_pos(clicker)
|
|
if pos2 and vector.equals(pos, pos2) or minetest.check_player_privs(clicker, "server") then
|
|
stop_crane(pos, clicker)
|
|
end
|
|
end,
|
|
|
|
on_construct = function(pos)
|
|
local meta = minetest.get_meta(pos)
|
|
meta:set_string("infotext", S("Switch crane on/off"))
|
|
end,
|
|
|
|
drop = "",
|
|
paramtype = "light",
|
|
paramtype2 = "facedir",
|
|
light_source = 3,
|
|
sunlight_propagates = true,
|
|
is_ground_content = false,
|
|
groups = {crumbly=0, not_in_creative_inventory=1},
|
|
})
|
|
|
|
minetest.register_node("towercrane:mast_ctrl_off", {
|
|
description = S("Tower Crane Mast Ctrl Off"),
|
|
drawtype = "node",
|
|
tiles = {
|
|
"towercrane_base.png",
|
|
"towercrane_base.png",
|
|
"towercrane_base.png",
|
|
"towercrane_base.png",
|
|
"towercrane_base.png^towercrane_button_off.png",
|
|
"towercrane_base.png^towercrane_button_off.png",
|
|
},
|
|
-- switch the crane ON
|
|
on_rightclick = function (pos, node, clicker)
|
|
if is_my_crane(pos, clicker) and not is_operator(clicker) then
|
|
if set_operator_privs(clicker, pos) then
|
|
start_crane(pos, clicker)
|
|
local pos1, pos2 = calc_construction_area(pos)
|
|
if pos1 and pos2 then
|
|
-- control player every second
|
|
minetest.after(1, control_player, pos, pos1, pos2, clicker:get_player_name())
|
|
else
|
|
-- Something weird happened, restore privileges
|
|
stop_crane(pos, clicker)
|
|
reset_operator_privs(clicker)
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
|
|
on_construct = function(pos)
|
|
-- add infotext
|
|
local meta = minetest.get_meta(pos)
|
|
meta:set_string("infotext", S("Switch crane on/off"))
|
|
end,
|
|
|
|
drop = "",
|
|
paramtype = "light",
|
|
paramtype2 = "facedir",
|
|
sunlight_propagates = true,
|
|
is_ground_content = false,
|
|
groups = {crumbly=0, not_in_creative_inventory=1},
|
|
})
|
|
|
|
minetest.register_on_joinplayer(function(player)
|
|
local pos = get_my_crane_pos(player)
|
|
if pos then
|
|
stop_crane(pos, player)
|
|
end
|
|
-- To recover from a crash, this must be done unconditionally
|
|
reset_operator_privs(player)
|
|
end)
|
|
|
|
minetest.register_on_leaveplayer(function(player)
|
|
if is_operator(player) then
|
|
Currently_left_the_game[player:get_player_name()] = true
|
|
end
|
|
end)
|
|
|
|
minetest.register_on_dieplayer(function(player, reason)
|
|
if is_operator(player) then
|
|
local pos = get_my_crane_pos(player)
|
|
if pos then
|
|
reset_operator_privs(player)
|
|
stop_crane(pos, player)
|
|
end
|
|
end
|
|
end)
|
|
|
|
minetest.register_lbm({
|
|
label = "[towercrane] break down",
|
|
name = "towercrane:break_down",
|
|
nodenames = {"towercrane:mast_ctrl_off", "towercrane:mast_ctrl_on"},
|
|
run_at_every_load = true,
|
|
action = function(pos, node)
|
|
local t = minetest.get_day_count()
|
|
local meta = minetest.get_meta(pos)
|
|
local last_used = meta:get_int("last_used") or 0
|
|
if last_used == 0 then
|
|
meta:set_int("last_used", t + DAYS_WITHOUT_USE)
|
|
elseif t > last_used then
|
|
local base_pos = {x=pos.x, y=pos.y-1, z=pos.z}
|
|
towercrane.get_crane_down(base_pos)
|
|
end
|
|
end
|
|
})
|
|
|
|
-------------------------------------------------------------------------------
|
|
-- export
|
|
-------------------------------------------------------------------------------
|
|
towercrane.is_crane_running = is_crane_running
|